Triple-tap the scoreboard for a sandbox

A test mode for trying the new content without playing twenty games to reach it.
Three taps on the scoreboard toggles it, three more turns it off, and a single
tap is still the miss-log overlay. Same shape as the umpire triple-tap cheat it
sits next to.

It OVERRIDES the difficulty director rather than editing it, so the wave is the
same whether you opened it in game 1 or game 40 and the run keeps running
underneath: 8 bodies, a fixed 1.15 m/s shamble, a spawn every 1.6 s, and
pickVariant pretending to be at game 20 so every body type can turn up. All four
barrel weapons sit on the rack and are topped back up each frame, because
committing a burst spends the whole tin and a sandbox that runs dry is not one.
A barrel rolls out every 14 s so the pickup itself is testable too.

The third tap must NOT toggle logShow: taps one and two already toggled it twice
and it is back where it started, so toggling again left the miss-log overlay
switched on behind you every single time.

Also records the trap that cost a debugging round getting here: index.html loads
main.js?v=N, and fetching 'main.js' does not bust that URL -- so main.js stayed
stale through a reload while every other module was fresh, and the tap handler
appeared to do nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-03 14:20:14 +02:00
1 parent 282f5145a9
commit 46e268973d
7 files changed
+101 -12

No files matched your search

+11 -5
View File
@@ -34,8 +34,10 @@ export function spawnZombie(x, z, variant) {
// Keyed on GAMES FINISHED, not on splatT: the old `1 + min(1.5, splatT / 70)` maxed out after
// 105 seconds of splatter, so every shambler from roughly game 3 onward walked at the same top
// speed for the rest of a 50-game run. Games are what the player experiences as progress.
// test mode pins the shamble at a fixed MEDIUM speed instead of riding the per-game ramp
const ramp = 1 + Math.min(TUNE.zSpeedMax, G.runGames * TUNE.zSpeedPerGame);
const zb = { x, z, v: TUNE.zSpeed * ramp * (0.8 + Math.random() * 0.4), walkT: Math.random() * 9,
const base = G.testMode ? TUNE.testZSpeed : TUNE.zSpeed * ramp;
const zb = { x, z, v: base * (0.8 + Math.random() * 0.4), walkT: Math.random() * 9,
homeOff: (Math.random() * 2 - 1) * 0.8, variant }; // personal lane offset so they don't stack into a column
if (variant === 'f') { zb.v = Math.min(TUNE.runnerVMax, zb.v * TUNE.runnerSpeed); shriek(); } // shriek = the fairness contract; capped so late runners stay reactable
if (variant === 'a' || variant === 'b') zb.hp = TUNE.shamblerHp; // classic shamblers take a wound before they drop — only the runner is one-shot glass
@@ -260,7 +262,7 @@ function fireAtPlayer(x, z, spread = 0, speed = TUNE.machSpeed) {
// Now the advanced SHARE keeps climbing after everything is unlocked, which is what makes game 40
// feel different from game 20 rather than merely faster.
function pickVariant() {
const g = G.runGames;
const g = G.testMode ? TUNE.testLevel : G.runGames; // test mode unlocks the whole roster
const pool = [];
if (g >= 2 && !zombies.some(z => z.variant === 'f')) pool.push('f'); // at most one runner alive
if (g >= 6) pool.push('g');
@@ -735,11 +737,15 @@ export function updateZombies(dt) {
// wall — the cap pinned at 14 by game 11 and the gap floored at 1.0 s by game 10, after which
// the game literally stopped changing. The gap is hyperbolic now so it keeps tightening on its
// own instead of running into a floor; zSpawnMin is only a safety stop.
const cap = Math.min(TUNE.zMaxCap, TUNE.zMax + Math.floor(G.runGames * TUNE.zMaxPerGame));
if (zombies.length >= cap - 1) aside('ump_hindrance', 0.6); // the court is genuinely full
// TEST MODE overrides the whole director with one fixed medium setting, so the sandbox is the
// same wave whether you triple-tapped in game 1 or game 40.
const cap = G.testMode ? TUNE.testZMax
: Math.min(TUNE.zMaxCap, TUNE.zMax + Math.floor(G.runGames * TUNE.zMaxPerGame));
if (zombies.length >= cap - 1 && !G.testMode) aside('ump_hindrance', 0.6); // the court is genuinely full
if (spawnT <= 0 && zombies.length < cap) {
spawnZombie((Math.random() * 2 - 1) * 4.8, -1.5 - Math.random(), pickVariant());
spawnT = Math.max(TUNE.zSpawnMin, TUNE.zSpawnEvery / (1 + G.runGames * TUNE.zSpawnAccel));
spawnT = G.testMode ? TUNE.testSpawnEvery
: Math.max(TUNE.zSpawnMin, TUNE.zSpawnEvery / (1 + G.runGames * TUNE.zSpawnAccel));
}
}
}