The staff learn your habits, the walk-on waits for your tap, and the club opens a practice court
Four answers to one phone report: - THE READ: repeat a lane and the opponent anticipates it; break the habit and it is wrong-footed for that ball — scaled by rung, so surprise is the weapon that opens the ladder past the HEAD COACH (TUNE block THE READ). - The opponent walk-on is a real modal now (head, punchline, tap to dismiss) in arcade, tournament and training — readable without a clock. - THE TRAINING COURT: third card under ZOMBIE TENNIS, pick any rung, one game at a time, the felled body walks straight back on, nothing banks, bleeding out is a popup instead of a death. mode_train.png rolled in the banner style. - MOVEMENT READS: sprinting feet kick per-court dust and fast bodies leave a two-ghost speed streak (trailMin gates both; feel verdict pending). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a42dc6fc81
commit
43784c5b33
9 files changed
+360
-43
No files matched your search
+23
-3
@@ -3,7 +3,7 @@
|
||||
// Module map: config (tuning/constants) · state (mutable game state) · gameplay (logic/update)
|
||||
// render (camera/drawing) · input (keys/touch) · audio (sfx/umpire) · panel (T tuning UI)
|
||||
|
||||
import { TUNE, COURTS, TOURS, KITS, SKINS, MODES, MENU, DIFFS, EATEN_RESTART, DONE_RESTART, WEAPONS, bossArt, CUP_ROUNDS, matchFoe } from './config.js';
|
||||
import { TUNE, COURTS, TOURS, KITS, SKINS, MODES, MENU, DIFFS, EATEN_RESTART, DONE_RESTART, WEAPONS, bossArt, CUP_ROUNDS, matchFoe, LADDER } from './config.js';
|
||||
import { P, O, ball, score, G, setupPoint, sendBoy, selectCourt, selectTour, selectKit, selectSkin } from './state.js';
|
||||
import { update, restartRun, keyPressed, solveShot, inServeBox, inCourt, forceZombies, startGore, splatPoint, deliverTin, selectWeapon, grantWeapon, showModal, dismissModal, flickTarget, dragServe, toggleTestMode } from './gameplay.js';
|
||||
import { startDuel, resolveInterlude, startHorde, startBossFight, endBossFight, startDrill, endDrill, startMatch, endMatch, startBench, endBench, FALL, benchFall, startNetVs, endNetVs } from './beats.js';
|
||||
@@ -383,10 +383,24 @@ document.getElementById('brBack').addEventListener('click', () => {
|
||||
});
|
||||
document.getElementById('cupGo').addEventListener('click', () => start('cup'));
|
||||
|
||||
// ---- THE TRAINING PICKER (Oliver, 2026-08-28: "ein Trainingsbereich — Auswahl des Gegners, ein
|
||||
// Spiel, tap to replay, bis man keine Lust mehr hat"). The whole ladder face-up, one tile per rung,
|
||||
// wearing the same z_*head crop the draw and the walk-on wear. Tap = start — the wizard's own
|
||||
// tap-advances rule; the SERVE UP button routed here instead of starting (see cStart below).
|
||||
let trainRung = 1;
|
||||
const trainList = document.getElementById('trainList');
|
||||
for (let n = 1; n <= LADDER; n++) {
|
||||
const f = matchFoe(n), b = document.createElement('button');
|
||||
b.innerHTML = `<img src="${foeArt(f)}" alt=""><b>${f.name}</b><i>RUNG ${n}</i>`;
|
||||
b.addEventListener('click', () => { trainRung = n; start('train'); });
|
||||
trainList.appendChild(b);
|
||||
}
|
||||
document.getElementById('trBack').addEventListener('click', () => go('setup'));
|
||||
|
||||
// Which RECORD BOOK a run mode writes to. The four run modes share three books — the arcade ladder
|
||||
// and the tournament are both zombie tennis and are counted together (see the record-shape note in
|
||||
// stats.endRun). Lived inline in three places before the world board grew tabs of its own.
|
||||
const boardFor = m => m === 'zombie' || m === 'cup' ? stats.MODE_MATCH
|
||||
const boardFor = m => m === 'zombie' || m === 'cup' || m === 'train' ? stats.MODE_MATCH
|
||||
: m === 'classic' ? stats.MODE_CLASSIC : stats.MODE_GORE;
|
||||
|
||||
// what the setup footer says you are about to play, and what it is worth beating
|
||||
@@ -404,7 +418,9 @@ function summary() {
|
||||
? m.name + ' · ' + (DIFFS.find(d => d.id === pickedDiff) || DIFFS[1]).name : m.name;
|
||||
document.getElementById('sSum').innerHTML =
|
||||
`<span>${name} · ${t.name.replace(' SINGLES', '')} · ${c.name}</span>`
|
||||
+ `<span>${best ? best + unit + ' TO BEAT' : 'FIRST RUN'}</span>`;
|
||||
// training banks nothing, so there is no mark to beat and the footer must not invent one
|
||||
+ `<span>${m.id === 'train' ? 'NOTHING AT STAKE — JUST REPS'
|
||||
: best ? best + unit + ' TO BEAT' : 'FIRST RUN'}</span>`;
|
||||
}
|
||||
|
||||
// The splash carries ONE number, on the STATS button. A boxed BEST RUN / WORLD RANK bar across the
|
||||
@@ -440,6 +456,8 @@ function start(modeId = pickedMode) {
|
||||
// ...and the cup carries the draw the bracket screen just made. Null (the #cup back door) still
|
||||
// means "roll it yourself", which is what the mode did before the screen existed.
|
||||
if (modeId === 'cup') { intoSplatter(); startMatch(true, cupPicks); }
|
||||
// training: the picked rung rides the match machinery with the ladder parked (beats.startMatch)
|
||||
if (modeId === 'train') { intoSplatter(); startMatch(false, null, trainRung); }
|
||||
}
|
||||
let stPeriod = 'all', stMode = stats.MODE_GORE;
|
||||
// the stats screen opens on the mode you last PICKED to play — that is the one whose numbers you
|
||||
@@ -523,6 +541,7 @@ nbName.addEventListener('keydown', e => { if (e.key === 'Enter') document.getEle
|
||||
// carries its own start button, so this stays the one place a run begins from the setup screen.
|
||||
document.getElementById('cStart').addEventListener('click', () => {
|
||||
if (pickedMode === 'cup') { buildBracket(); go('bracket'); return; }
|
||||
if (pickedMode === 'train') { go('train'); return; } // the picker starts it — tap advances
|
||||
start();
|
||||
});
|
||||
for (const id of ['mBack', 'stBack', 'rkBack', 'scBack'])
|
||||
@@ -713,6 +732,7 @@ addEventListener('keydown', e => {
|
||||
// no-ops on an empty run, so closing the tab at the menu writes nothing.
|
||||
addEventListener('pagehide', () => {
|
||||
if (G.mode !== 'splatter') return;
|
||||
if (G.match && G.match.train) return; // the training court banks nothing, walked out on or not
|
||||
// ...tagged with the mode that was being played, and carrying THAT mode's two headline numbers —
|
||||
// see the record-shape note in stats.endRun. A match abandoned mid-rally is a match run.
|
||||
if (G.match) stats.endRun(G.match.opp - 1, score.games[0] + score.games[1], G.splatT, false, stats.MODE_MATCH);
|
||||
|
||||
Reference in new issue
Block a user