The opponent shows up, and the death loop dies

"still very weak and slow opponent. it also spawns at places where it is
impossible to react to my serve. so i win like always. in the cases i dont
win there is a bug. when trying to serve after i got hurt i get hurt again
and again until i die".

The death loop was G.serveNum. Tennis's pointTo is the only thing that ever
reset it, and the match bypasses pointTo entirely — so the first fault of the
match armed "second serve" permanently, and every missed serve after it was a
double fault, a point, and blood, repeating to death. Exactly the spiral a
rattled player serves into. matchReady(), the match's own point boundary,
resets it now; a regression pins it.

The receiver starts every point on its mark: matchReady drops it onto the
exact receiver position the tennis branch computes for O, deuce/ad mirrored,
instead of a random spawn x or wherever the last rally left it. An opponent
that cannot reach the serve is not weak, it is absent.

And the base opponent is a player now: matchSpeedBase 1.4 (the duel's 2.8m/s
lumber was tuned for one sudden-death point), matchFlub 0.08 (0.2 gives away
a fifth of all returns). Measured with the same autopilot that previously won
seven straight games: it now loses the match. Possibly too hot — the two
dials are named, Oliver's thumb decides.

40 tests, 15 seeds clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-11 22:41:10 +02:00
1 parent ea6e50f8f8
commit f67411e330
6 files changed
+83 -14

No files matched your search

+27 -2
View File
@@ -81,7 +81,7 @@ globalThis.ResizeObserver = class { observe() {} disconnect() {} };
globalThis.getComputedStyle = () => ({ paddingTop: '0' });
// ---- game modules (real code) ----------------------------------------------
const { G, P, score, ball, setupPoint } = await import('./src/state.js');
const { G, P, O, score, ball, setupPoint } = await import('./src/state.js');
const { update, forceZombies, splatPoint, restartRun, flickTarget } = await import('./src/gameplay.js');
const { startDuel, resolveInterlude, startBossFight, endBossFight, startDrill: _startDrill, endDrill, startMatch: _startMatch, endMatch } = await import('./src/beats.js');
// the drill's first-sight modal freezes the world (that is what modals do) — dismiss it the way a
@@ -557,11 +557,36 @@ test('a match ball never wounds the body: the score is the only way through', ()
assert.equal(zb.hp, zb.hpMax, 'and unwounded — "killing by smashing" is out of the match');
});
test('THE DEATH LOOP: a fault before a lost point must not arm second-serve forever', () => {
// The match bypasses tennis's pointTo — the only thing that ever reset G.serveNum — so the first
// fault of the match made EVERY later missed serve a double fault: a point, blood, and repeat
// until dead. Oliver hit it: "when trying to serve after i got hurt i get hurt again and again
// until i die".
startMatch();
G.serveNum = 2; // a fault has burned the first serve
resolveInterlude('O'); // the point is lost (double fault, passing shot, whatever)
assert.equal(G.serveNum, 1, 'the NEXT point starts on a first serve');
resolveInterlude('P');
assert.equal(G.serveNum, 1, '...whoever won it');
});
test('every point starts with the receiver on its mark', () => {
startMatch();
const rz = oppOf();
rz.x = 6; rz.z = 12; // stranded mid-court by the last rally
resolveInterlude('P');
assert.ok(Math.abs(rz.x) < 3 && rz.z <= 1.5,
'reset to the receiver spot, not wherever the rally left it: x=' + rz.x.toFixed(1) + ' z=' + rz.z.toFixed(1));
assert.equal(rz.x, O.x, 'on the exact mark the tennis branch computed for the receiver');
});
test('the ladder escalates in speed and reliability, not in hp', () => {
// pure arithmetic on the knobs racketAI reads — pinned so a future tuning pass cannot silently
// flatten the ladder again
assert.ok(TUNE.matchSpeedBase > 1, 'opponent one is already quicker than the duel lumber');
assert.ok(TUNE.matchSpeedPer > 0, 'each opponent is faster');
assert.ok(TUNE.matchFlubKeep < 1, 'and whiffs less');
assert.ok(TUNE.matchFlub < TUNE.racketFlub, 'the match whiffs less than the duel from the start');
assert.ok(TUNE.matchFlubKeep < 1, 'and less again per rung');
});
test('a game won heals, so a clean player can go the distance', () => {