Two testing switches: one-shot bodies, and the eaters off

"please make the zombie killable with a single hit for testing" and "remove
the fake zombies for now".

drillOneShot drops every body on a drill court to 1 hp, applied at spawn to
the batch and the eaters alike. Worth knowing what it removes: the roster's
HP is what makes the called ORDER matter — a roller is normally seven serves,
so being told to take it third means starting on it first. At one hit that
planning layer is gone and the sequence is pure recall. Flagged in PROJECT.md
so it gets decided on feel rather than left on by drift.

drillEaterMax 3 -> 0 switches the plain shamblers off. The mechanism is
untouched — spawnEater, the wide entry away from the gang, the never-a-fault
rule — so one number brings them back.

The eater test now asserts whichever contract the knob describes (at 0:
nothing but the puzzle ever appears; above 0: they arrive, capped, always
plain shamblers) instead of being deleted and rotting. And the never-a-fault
rule gets its own test with a hand-placed body, so the rule the eaters depend
on stays covered while they are switched off.

31 tests, 15 seeds clean. Verified live: brute/steward/roller all at 1 hp
where the roster says 5/5/7, zero strays over 40s, one aimed serve puts down
the roller.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-11 20:37:01 +02:00
1 parent d34683bc68
commit 0f524a08c9
5 files changed
+73 -23

No files matched your search

+42 -19
View File
@@ -358,13 +358,14 @@ test('the puzzle is WINNABLE: every called TYPE is on the far side, and nothing
}
});
test('the eaters are the danger: plain shamblers, homing, and killing one is never a fault', () => {
test('the eaters obey drillEaterMax — including 0, which is OFF', () => {
// They are switched off right now ("remove the fake zombies for now") but the mechanism is intact,
// so this asserts whichever contract the knob currently describes rather than being deleted and
// rotting. The never-a-fault rule below is checked EITHER WAY, because that is the part that has
// to still be true on the day they are switched back on.
const eaten = TUNE.eatenR; TUNE.eatenR = 0; // the soak trick: don't get caught mid-measurement
try {
startDrill();
// COUNT ARRIVALS, NOT SURVIVORS. An eater crosses the whole court in about 24 s and then
// despawns, so a snapshot taken at 40 s can legitimately show an empty court — that read as
// "the eaters never came" and made this test fail about one run in five.
const seen = new Set();
let peak = 0;
for (let i = 0; i < 40 * 60; i++) {
@@ -377,24 +378,33 @@ test('the eaters are the danger: plain shamblers, homing, and killing one is nev
'an eater is ALWAYS a plain shambler — that is what keeps a called type unambiguous');
}
}
assert.ok(seen.size > 0, 'plain shamblers arrive on their own clock');
assert.ok(peak <= TUNE.drillEaterMax, 'and never more than ' + TUNE.drillEaterMax + ' at once, saw ' + peak);
const eaters = [...seen].filter(z => zombies.includes(z));
if (!eaters.length) return; // all of them have walked off again: nothing left to shoot at
// killing one must not touch the combination
const seq = G.drill.seq.slice(), faults = G.drill.faults;
const e = eaters[0];
for (let i = 0; i < 40 && zombies.includes(e); i++) {
ball.active = true; ball.lastHit = 'P'; ball.y = 1; ball.pow = 1; ball.bounces = 0;
ball.x = e.x; ball.z = e.z; ball.vx = ball.vy = ball.vz = 0;
update(DT);
}
assert.ok(!zombies.includes(e), 'setup: the eater is down');
assert.deepEqual(G.drill.seq, seq, 'the combination is untouched');
assert.equal(G.drill.faults, faults, 'and it costs no fault — it never wore a colour');
assert.ok(peak <= TUNE.drillEaterMax, 'never more than ' + TUNE.drillEaterMax + ' at once, saw ' + peak);
if (TUNE.drillEaterMax === 0)
assert.equal(seen.size, 0, 'at 0 the court carries nothing but the puzzle');
else
assert.ok(seen.size > 0, 'otherwise plain shamblers arrive on their own clock');
} finally { TUNE.eatenR = eaten; }
});
test('killing a body that is not in the gang is never a fault', () => {
// The rule the eaters depend on, tested with a hand-placed shambler so it holds whether or not
// the spawner is switched on.
drillPlaying();
const seq = G.drill.seq.slice(), faults = G.drill.faults, step = G.drill.step;
spawnZombie(P.x + 2, 6, 'a');
const stray = zombies[zombies.length - 1];
assert.ok(!stray.drill, 'setup: a hand-placed body is not part of the gang');
for (let i = 0; i < 40 && zombies.includes(stray); i++) {
ball.active = true; ball.lastHit = 'P'; ball.y = 1; ball.pow = 1; ball.bounces = 0;
ball.x = stray.x; ball.z = stray.z; ball.vx = ball.vy = ball.vz = 0;
update(DT);
}
assert.ok(!zombies.includes(stray), 'setup: it is down');
assert.deepEqual(G.drill.seq, seq, 'the combination is untouched');
assert.equal(G.drill.faults, faults, 'and it costs no fault');
assert.equal(G.drill.step, step, 'and does not advance the sequence');
});
test('the ramp raises the batch, the walk and the eater rate', () => {
const eaten = TUNE.eatenR; TUNE.eatenR = 0;
try {
@@ -480,6 +490,19 @@ test('a wrong kill never leaves an un-winnable round', () => {
}
});
test('drillOneShot drops every body on the drill court to a single hit', () => {
startDrill();
assert.ok(TUNE.drillOneShot >= 0.5, 'setup: the testing knob is on');
for (const z of zombies) assert.equal(z.hp, 1, z.variant + ' should be one-shot, saw hp ' + z.hp);
// and it really dies to ONE splat-quality ball, roster HP notwithstanding
const tank = zombies.find(z => z.drill && z.variant === 'k')
|| zombies.find(z => z.drill && z.variant === 'g') || zombies.find(z => z.drill);
ball.active = true; ball.lastHit = 'P'; ball.y = 1; ball.pow = 1; ball.bounces = 0;
ball.x = tank.x; ball.z = tank.z; ball.vx = ball.vy = ball.vz = 0;
update(DT);
assert.ok(!zombies.includes(tank), 'one hit put down a ' + tank.variant);
});
test('a batch body that walks past you counts as caught, never as a free escape', () => {
// Types move at different speeds now, and drill bodies hold their lane — so one in a lane you are
// not standing in can cross the baseline untouched. Left as a plain despawn that stranded the