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
@@ -523,6 +523,9 @@ const passMopBeat = () => { // ...stopping at the walk-on: past it, the next o
|
||||
// the KILL FALL plays first — the felled body is still on court, so oppOf() must not count it
|
||||
for (let i = 0; i < 5 * 60 && G.matchFall; i++) { G.modal = null; G.modalQ.length = 0; update(DT); }
|
||||
for (let i = 0; i < (TUNE.matchMopT + 8) * 60 && !oppOf(); i++) { G.modal = null; G.modalQ.length = 0; update(DT); }
|
||||
// the walk-on is a MODAL now (2026-08-28) and it arrives WITH the opponent — tap it away too,
|
||||
// or every step after this one runs against a frozen world
|
||||
G.modal = null; G.modalQ.length = 0;
|
||||
};
|
||||
|
||||
test('a match owns the court like every other mode', () => {
|
||||
@@ -2028,5 +2031,89 @@ test('the stand reads the SIZE of the moment, and a small cheer inside a big one
|
||||
crowd.cheer = crowd.pop = crowd.burst = 0;
|
||||
});
|
||||
|
||||
// ---- 2026-08-28: the walk-on modal, the training court, and THE READ ---------------------------
|
||||
|
||||
test('the walk-on is a MODAL the player taps away, and it holds the world', () => {
|
||||
// Oliver: "die Intros bitte als richtiges Modal, das man wegtappen muss — you can actually read
|
||||
// it without time pressure, and tap it away fast when you know it." The timed 4 s card is gone.
|
||||
G.seen.match = true; // the rules popup is not the subject here
|
||||
assert.ok(_startMatch());
|
||||
assert.ok(G.modal, 'the opponent announces as a modal, not a band card');
|
||||
assert.equal(G.modal.title, cfg.matchFoe(1).name, 'it names the body');
|
||||
assert.equal(G.modal.icon, 'z_' + cfg.matchFoe(1).fam + 'head', '...and wears its head');
|
||||
const z0 = oppOf().z, intro0 = G.interlude.introT;
|
||||
for (let i = 0; i < 60; i++) update(DT);
|
||||
assert.equal(oppOf().z, z0, 'the frozen court holds while it is read');
|
||||
assert.equal(G.interlude.introT, intro0, '...and the intro beat holds with it, however long that takes');
|
||||
G.modal = null; G.modalQ.length = 0;
|
||||
endMatch();
|
||||
});
|
||||
|
||||
test('THE TRAINING COURT: the rung is parked, nothing banks, and bleeding out is a modal', () => {
|
||||
assert.ok(_startMatch(false, null, 5), 'training rides the match machinery');
|
||||
G.modal = null; G.modalQ.length = 0;
|
||||
const d = G.match;
|
||||
assert.ok(d.train, 'flagged as the training court');
|
||||
assert.equal(d.opp, 5, 'the picked rung is the one that walks on');
|
||||
const runGames0 = G.runGames;
|
||||
G.interlude.introT = 0;
|
||||
G.hp = 3; // a scuffed pool, to prove the reset
|
||||
for (let k = 0; k < 4; k++) { resolveInterlude('P'); G.modal = null; G.modalQ.length = 0; }
|
||||
passMopBeat(); // the kill fall, the mop, the SAME body walking on
|
||||
assert.equal(G.match.opp, 5, 'a felled body comes back as itself — the ladder never advances');
|
||||
assert.equal(G.runGames, runGames0, 'no ladder metric moves on the training court');
|
||||
assert.equal(G.hp, G.hpMax, 'every rep starts whole — the reset, without the trainer ceremony');
|
||||
// ...and running out of blood is a popup and a fresh pool, never the death screen
|
||||
G.interlude.introT = 0;
|
||||
G.hp = 1;
|
||||
resolveInterlude('O');
|
||||
assert.notEqual(G.mode, 'eaten', 'the training court has no death in it');
|
||||
assert.ok(G.modal && G.modal.title === 'IT BLED YOU OUT', 'bleeding out announces itself');
|
||||
assert.equal(G.hp, G.hpMax, '...and hands the pool back');
|
||||
G.modal = null; G.modalQ.length = 0;
|
||||
endMatch();
|
||||
});
|
||||
|
||||
test('THE READ: a habit is learned by repetition and a break wrong-foots the racket', () => {
|
||||
// The twist for the post-COACH wall ("very fast, barely make mistakes"): repeat a lane and the
|
||||
// opponent anticipates it, break an established habit and it is wrong-footed for that ball.
|
||||
G.seen.match = true;
|
||||
_startMatch(); G.modal = null; G.modalQ.length = 0;
|
||||
const zb = oppOf(), d = G.match;
|
||||
d.opp = cfg.LADDER; zb.foe = cfg.matchFoe(cfg.LADDER); // the champion's dials, live
|
||||
G.interlude.introT = 0;
|
||||
const hit = fx => { // one real player swing, aimed by a committed flick
|
||||
G.phase = 'rally'; P.x = 0; P.z = 23; P.swingT = -1;
|
||||
zb.x = 0; zb.z = 2; zb.swingCool = 999; // it must not return the rig's ball
|
||||
ball.active = true; ball.lastHit = 'O'; ball.bounces = 1;
|
||||
ball.x = P.x; ball.z = P.z - 0.4; ball.y = 1.0; ball.vx = 0; ball.vy = 0; ball.vz = 1;
|
||||
G.swipe = { ax: Math.sign(fx), fx, fy: 0, mag: TUNE.flickFull };
|
||||
keyPressed('Space');
|
||||
for (let i = 0; i < 20 && ball.lastHit !== 'P'; i++) update(DT);
|
||||
G.swipe = null;
|
||||
assert.equal(ball.lastHit, 'P', 'the rig landed a swing');
|
||||
ball.active = false; // the rally is not the subject
|
||||
};
|
||||
for (let i = 0; i < TUNE.readHabit; i++) hit(-1);
|
||||
assert.equal(d.laneRun, TUNE.readHabit, 'a run of the same lane goes on the books');
|
||||
assert.ok(!d.surprised && !d.expected, 'the habit itself surprises nobody');
|
||||
hit(-1); // one more of the same: the anticipated ball
|
||||
assert.ok(d.expected && !d.surprised, 'the repeated ball is anticipated — spam feeds the machine');
|
||||
hit(1); // the break
|
||||
assert.ok(d.surprised && !d.expected, 'the break wrong-foots him for this one flight');
|
||||
assert.equal(d.laneRun, 1, '...and the new lane starts its own book');
|
||||
G.modal = null; G.modalQ.length = 0;
|
||||
endMatch();
|
||||
// the dials must MATTER at the top and vanish at the bottom — pinned so they cannot drift:
|
||||
const spd = n => (TUNE.matchSpeedBase + (n - 1) * TUNE.matchSpeedPer) * (cfg.matchFoe(n).speed ?? 1);
|
||||
assert.ok(spd(cfg.LADDER) * (1 - TUNE.surpSlow) < spd(14),
|
||||
'a wrong-footed champion runs slower than the head coach at full stride');
|
||||
const champFlub = TUNE.matchFlub * Math.pow(TUNE.matchFlubKeep, cfg.LADDER - 1) * cfg.matchFoe(cfg.LADDER).flub;
|
||||
assert.ok(champFlub < 0.02, '(the champion barely misses on his own — the wall being answered)');
|
||||
assert.ok(champFlub + TUNE.surpFlub > 0.25, 'the surprise opens a real whiff window in that racket');
|
||||
assert.ok(TUNE.readAnticipate <= 1, 'anticipation never reads better than perfect');
|
||||
assert.ok(TUNE.readHabit >= 2 && TUNE.readHabit <= 6, 'a habit is learnable inside one game');
|
||||
});
|
||||
|
||||
console.log(`\n${passed} tests passed`);
|
||||
process.exit(0); // render.js schedules viewport timers at load; don't let them outlive the report
|
||||
Reference in new issue
Block a user