One mode owns the court: the duel can no longer start inside a horde, and the game has tests
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4569981365
commit
05930f0877
4 files changed
+210
-5
No files matched your search
+25
-3
@@ -470,6 +470,10 @@ function pointTo(side, reason) {
|
||||
const blocksClear = z => !'rmo'.includes(z.variant);
|
||||
|
||||
export function splatPoint(side, quiet = false) {
|
||||
// The duel is one clean tennis point and resolveInterlude is its ONLY exit. A machine or leftover
|
||||
// killed mid-duel used to score here, and a game banked mid-duel re-entered proceedAfterClear —
|
||||
// which can start a boss, a card, or a horde while the duel still owns the court.
|
||||
if (G.interlude) return;
|
||||
if (side === 'P' && ++G.killCount < TUNE.killsPerPoint) return; // K kills bank one point; the rest just splatter
|
||||
if (side === 'P') G.killCount = 0;
|
||||
const gameOver = advancePoint(side);
|
||||
@@ -708,7 +712,9 @@ function tickTestMode(dt) {
|
||||
// Tennis rules apply for exactly one point — the absurdity peak: the umpire never stopped watching.
|
||||
// This is now the ONLY source of weapons in the game: win the point or leave empty-handed.
|
||||
export function startDuel() {
|
||||
if (G.interlude) return;
|
||||
// Hard mutual exclusion, HERE, where every caller routes through: a duel must never begin while
|
||||
// another control mode owns the court. Each of these has ended a run when they overlapped.
|
||||
if (G.interlude || G.horde || G.bossFight) return;
|
||||
if (!zombies.some(z => z.variant === 'r')) spawnRacketZombie(); // the schedule guarantees an opponent
|
||||
for (const z of zombies) if (z.variant === 'r') z.dueled = true; // a veteran can be challenged again — this marks the fresh duel
|
||||
G.weapon = 0; // tennis rules for the duel: plain balls, no tins
|
||||
@@ -786,6 +792,19 @@ export function endBossFight() {
|
||||
let stuckT = 0, stuckSig = '';
|
||||
function watchdog(dt) {
|
||||
if (G.mode !== 'splatter' || G.modal) { stuckT = 0; return; } // a popup waits on the PLAYER: legitimate
|
||||
// INVARIANT NET, before the clock-based test — because the clock-based test is blind to exactly
|
||||
// this: a horde's own timer keeps ticking, so the sig always "moves" while the player is welded
|
||||
// in place. Two states the guards above should make impossible; if a path still reaches them,
|
||||
// repair on the spot and leave evidence in the miss log.
|
||||
if (G.horde && G.interlude) { // a duel inside a horde: no steering, no serving, spawns keep coming
|
||||
logSwing({ result: 'INVARIANT', reason: 'duel active during horde — interlude dropped, duel re-scheduled' });
|
||||
G.interlude = false; G.duelDue = true; G.duelLerp = 0;
|
||||
}
|
||||
if (G.horde && G.phase !== 'rally') { // pretoss under a horde = the finger steers nothing
|
||||
logSwing({ result: 'INVARIANT', reason: `phase '${G.phase}' during horde — forced back to rally`, phase: G.phase });
|
||||
G.phase = 'rally'; ball.active = false;
|
||||
P.pose = 'idle'; P.poseT = 0; P.swingT = -1;
|
||||
}
|
||||
const blocked = G.horde || G.mgT > 0 || G.changeover || G.phase === 'over'
|
||||
|| (G.interlude && G.interlude.introT > 0)
|
||||
|| (G.phase === 'toss' && ball.y < 2.0); // a toss on the floor: only a held aim keeps it there
|
||||
@@ -867,7 +886,7 @@ const xLimit = () => Math.max(xVisible(), COURT.W / 2);
|
||||
|
||||
// the swarm keeps coming: no serving, the racket auto-fires, the thumb drag steers you
|
||||
function startHorde() {
|
||||
if (G.horde) return;
|
||||
if (G.horde || G.interlude || G.bossFight) return; // same contract as startDuel: one mode owns the court
|
||||
G.changeover = false; // a cheat-forced horde mid-changeover must not leave spawns locked
|
||||
const grow = 1 + (G.sets[0] + G.sets[1]) * TUNE.hordeGrow; // each set banked makes the next wave worse
|
||||
// the gun grows WITH the wave: spawn rate and length both scale by grow, so a flat gun fell
|
||||
@@ -1499,7 +1518,10 @@ export function update(dt) {
|
||||
if (G.meleeT > 0) G.meleeT -= dt;
|
||||
if (G.rackGlow && (G.rackGlow.t -= dt) <= 0) G.rackGlow = null; // the fresh-pickup pulse fades out
|
||||
watchdog(dt);
|
||||
if (G.clearing && !zombies.some(blocksClear)) proceedAfterClear(); // the won-game wave is mopped up — advance
|
||||
// !G.horde: a horde mows the court down to an EMPTY FRAME between spawns, and a clear latched
|
||||
// before the wave began (a game won during the set card) then advanced the beat mid-horde —
|
||||
// which is how a sudden-death duel once started INSIDE a horde. The clear waits for endHorde.
|
||||
if (G.clearing && !G.horde && !zombies.some(blocksClear)) proceedAfterClear(); // the won-game wave is mopped up — advance
|
||||
if (G.mode === 'transition') transition(dt);
|
||||
|
||||
// --- interlude trigger: the court is clear, an opponent holds the far court, you're about
|
||||
|
||||
Reference in new issue
Block a user