The finishing shot is spent, and the wound skins come back
"one-shot is bad - way too easy - also we're losing all the nice gore animation sprites" and "I noticed the last kill of a game can carry to the spawn of a new zombie of the next round". drillOneShot is off. The second half of that note is the part worth remembering: a 1 hp body never passes through a wound stage, so every skin in ZWOUND — arm off, ribs out, still walking — is skipped and the roster's best art never renders at all. Kept as a switch, never a default. The carry-over was a real unfairness, not a curiosity. A ball keeps pace through a kill and travels UP-COURT, which is exactly where the next batch is born — so the shot that finished your round flew on into bodies that did not exist when you took it, and faulted you on a round you had not been shown yet. The shot that ends a round is spent now, and drillCaught does the same since it also refills the court up-court. Pierce within a batch is untouched: that is honest occlusion you could have read, this was not. Verified with real aimed serves through a whole round: round banks, ball goes inactive, the next batch walks on with 0 faults and a clean sequence, and the gang carries roster HP again (brute 5, ball boy 3). 33 tests, 15 seeds clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0f524a08c9
commit
57e96d629e
5 files changed
+68
-19
No files matched your search
+15
-5
@@ -739,11 +739,21 @@ Watch: **a roller in the middle of a sequence is seven aimed serves.** Oliver as
|
|||||||
robustness to be kept, so it is — but if a round ever feels like grinding rather than reading, a
|
robustness to be kept, so it is — but if a round ever feels like grinding rather than reading, a
|
||||||
drill-only HP scale is the knob, not a change to the roster.
|
drill-only HP scale is the knob, not a change to the roster.
|
||||||
|
|
||||||
**TENTH PASS — two testing switches, and neither should be left on by drift.**
|
**TENTH PASS — two switches, and the round-boundary bug.**
|
||||||
- **`TUNE.drillOneShot` (ON)**: every body on a drill court drops to 1 hp. Asked for to make testing
|
- **`TUNE.drillOneShot` — tried ON for one playtest, now OFF.** *"one-shot is bad - way too easy -
|
||||||
quick, but be aware what it removes — **the roster's HP is what makes the called ORDER matter**. A
|
also we're losing all the nice gore animation sprites"*. The second half is the one that is easy to
|
||||||
roller is normally seven serves, so being told to take it third means starting on it first; at one
|
miss and worth remembering generally: **a 1 hp body never passes through a wound stage**, so every
|
||||||
hit that whole layer of planning is gone and the sequence is pure recall. Decide it on feel.
|
skin in `ZWOUND` (arm off, ribs out, still walking) is skipped and the roster's best art never
|
||||||
|
renders at all. The HP is also what makes the called ORDER matter — a roller is seven serves, so
|
||||||
|
being told to take it third means starting on it first. Kept as a switch, never a default.
|
||||||
|
- **THE ROUND-BOUNDARY CARRY-OVER, and it was a real unfairness.** *"the last kill of a game can
|
||||||
|
carry to the spawn of a new zombie of the next round"*. A ball keeps pace through a kill
|
||||||
|
(`TUNE.zPierce`) and it travels UP-COURT — which is precisely where the next batch is born. So the
|
||||||
|
shot that finished your round flew on into bodies that did not exist when you took it and faulted
|
||||||
|
you on a round you had not been shown yet. **The shot that ends a round is spent** (`ball.active =
|
||||||
|
false` in drillKill's completion branch, and in `drillCaught` for the same reason: that also
|
||||||
|
refills the court up-court). Pierce WITHIN a batch is untouched — that is honest occlusion you
|
||||||
|
could have read; this was not.
|
||||||
- **`TUNE.drillEaterMax` 3 -> 0 (OFF)**: *"remove the fake zombies for now"*. The drill is the pure
|
- **`TUNE.drillEaterMax` 3 -> 0 (OFF)**: *"remove the fake zombies for now"*. The drill is the pure
|
||||||
puzzle with no second threat at the moment. The mechanism is untouched — `spawnEater`, the wide
|
puzzle with no second threat at the moment. The mechanism is untouched — `spawnEater`, the wide
|
||||||
entry away from the gang, the never-a-fault rule — so one number brings it back.
|
entry away from the gang, the never-a-fault rule — so one number brings it back.
|
||||||
|
|||||||
+1
-1
@@ -399,6 +399,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="src/main.js?v=161"></script>
|
<script type="module" src="src/main.js?v=162"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+7
-6
@@ -374,12 +374,13 @@ export const TUNE = {
|
|||||||
// brings it back. Past 3 the puzzle stops being readable.
|
// brings it back. Past 3 the puzzle stops being readable.
|
||||||
drillEaterSpeed: 1.15,// they walk a touch faster than the batch: the batch is a list, this is a threat
|
drillEaterSpeed: 1.15,// they walk a touch faster than the batch: the batch is a list, this is a threat
|
||||||
drillRampT: 210, // s to go from the bottom of every curve above to the top
|
drillRampT: 210, // s to go from the bottom of every curve above to the top
|
||||||
// TESTING KNOB, currently ON. Every body in the drill dies to one splat-quality hit, whatever the
|
// TESTING KNOB, and it is OFF again after one playtest: "one-shot is bad - way too easy - also
|
||||||
// roster says it should soak. Flip to 0 for the real thing — and note that this is not purely a
|
// we're losing all the nice gore animation sprites". Both halves matter and the second is the one
|
||||||
// convenience: the roster's HP is what makes the called ORDER matter (a roller is seven serves, so
|
// that is easy to miss — a 1 hp body never passes through any of its WOUND STAGES, so every skin
|
||||||
// being told to take it third means starting on it first), and one-shot bodies delete that whole
|
// in ZWOUND (arm off, ribs out, still walking) is skipped entirely and the roster's best art never
|
||||||
// layer. Decide it on feel rather than leaving it on by drift.
|
// renders. The HP is also what makes the called ORDER matter: a roller is seven serves, so being
|
||||||
drillOneShot: 1, // >= 0.5: batch bodies AND eaters all drop to 1 hp
|
// told to take it third means starting on it first. Left here as a switch, not a default.
|
||||||
|
drillOneShot: 0, // >= 0.5: batch bodies AND eaters all drop to 1 hp
|
||||||
drillCaughtBack: 45, // s of ramp handed back when one reaches you. A real setback, not a reset
|
drillCaughtBack: 45, // s of ramp handed back when one reaches you. A real setback, not a reset
|
||||||
fps: 60, // frame cap — 30 halves battery/heat but Oliver felt it; smoothness wins (0 = uncapped)
|
fps: 60, // frame cap — 30 halves battery/heat but Oliver felt it; smoothness wins (0 = uncapped)
|
||||||
lowPower: 1, // >= 0.5 caps RES at 3 (−44% of pixels painted per frame) — the battery saver,
|
lowPower: 1, // >= 0.5 caps RES at 3 (−44% of pixels painted per frame) — the battery saver,
|
||||||
|
|||||||
+8
-1
@@ -19,7 +19,7 @@
|
|||||||
// render.js reads DRILL_COLS and draws.
|
// render.js reads DRILL_COLS and draws.
|
||||||
|
|
||||||
import { TUNE } from './config.js';
|
import { TUNE } from './config.js';
|
||||||
import { G } from './state.js';
|
import { G, ball } from './state.js';
|
||||||
import { zombies, spawnZombie } from './zombies.js';
|
import { zombies, spawnZombie } from './zombies.js';
|
||||||
import { voice } from './audio.js';
|
import { voice } from './audio.js';
|
||||||
|
|
||||||
@@ -176,6 +176,12 @@ export function drillKill(zb) {
|
|||||||
if (d.step >= d.seq.length) { // round complete — and the next one is already here
|
if (d.step >= d.seq.length) { // round complete — and the next one is already here
|
||||||
d.rounds++;
|
d.rounds++;
|
||||||
d.msg = { text: 'GOOD', t: 0.9 };
|
d.msg = { text: 'GOOD', t: 0.9 };
|
||||||
|
// THE SHOT THAT ENDS A ROUND IS SPENT. A ball keeps pace through a kill (TUNE.zPierce) and it
|
||||||
|
// is travelling UP-COURT — which is exactly where the next batch is about to be born. So the
|
||||||
|
// ball that finished your round flew on into bodies that did not exist when you took the shot
|
||||||
|
// and faulted you on a round you had not been shown yet. Pierce WITHIN a batch stays: that is
|
||||||
|
// honest occlusion you could have read. This is not.
|
||||||
|
ball.active = false;
|
||||||
newRound(d);
|
newRound(d);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -206,6 +212,7 @@ export function drillCaught(text = 'CAUGHT') {
|
|||||||
voice('game_opponent', 0.1);
|
voice('game_opponent', 0.1);
|
||||||
d.msg = { text, t: 1.8, bad: true };
|
d.msg = { text, t: 1.8, bad: true };
|
||||||
zombies.length = 0;
|
zombies.length = 0;
|
||||||
|
ball.active = false; // same reason as the round-complete case: the court refills up-court
|
||||||
d.seq = []; d.step = 0; d.faults = 0; d.spawnT = 0;
|
d.seq = []; d.step = 0; d.faults = 0; d.spawnT = 0;
|
||||||
d.t = Math.max(0, d.t - TUNE.drillCaughtBack); // ...and the ramp backs off a little
|
d.t = Math.max(0, d.t - TUNE.drillCaughtBack); // ...and the ramp backs off a little
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -490,17 +490,48 @@ test('a wrong kill never leaves an un-winnable round', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('drillOneShot drops every body on the drill court to a single hit', () => {
|
test('the shot that ends a round cannot carry into the next batch', () => {
|
||||||
|
// Oliver: "the last kill of a game can carry to the spawn of a new zombie of the next round". A
|
||||||
|
// ball keeps pace through a kill and travels UP-COURT, which is exactly where the next batch is
|
||||||
|
// born — so it faulted you on a round you had not been shown yet.
|
||||||
|
drillPlaying();
|
||||||
|
const seq = G.drill.seq.slice();
|
||||||
|
for (const v of seq) drillKillType(v);
|
||||||
|
assert.ok(G.drill.rounds >= 1, 'setup: the round completed');
|
||||||
|
assert.equal(ball.active, false, 'the finishing shot is spent, not left in flight');
|
||||||
|
// let the next batch walk on and confirm it arrives untouched
|
||||||
|
for (let i = 0; i < 30; i++) update(DT);
|
||||||
|
const gang = zombies.filter(z => z.drill);
|
||||||
|
assert.ok(gang.length >= 2, 'the next batch is on court');
|
||||||
|
assert.equal(G.drill.faults, 0, 'and it did not inherit a fault from the previous round');
|
||||||
|
assert.equal(G.drill.step, 0, 'nor a half-finished sequence');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('being caught also spends the ball, for the same reason', () => {
|
||||||
|
drillPlaying();
|
||||||
|
ball.active = true; ball.lastHit = 'P';
|
||||||
|
killPlayer();
|
||||||
|
assert.equal(ball.active, false, 'the court refills up-court; a live ball would meet it');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('drillOneShot is a switch, and OFF means the roster HP is intact', () => {
|
||||||
|
// Off by default: one-shot bodies were "way too easy" AND skipped every wound skin in ZWOUND,
|
||||||
|
// because a 1 hp body never passes through a damage stage. Asserts whichever way the knob is set
|
||||||
|
// rather than being deleted when it was turned off.
|
||||||
startDrill();
|
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')
|
const tank = zombies.find(z => z.drill && z.variant === 'k')
|
||||||
|| zombies.find(z => z.drill && z.variant === 'g') || zombies.find(z => z.drill);
|
|| zombies.find(z => z.drill && z.variant === 'g');
|
||||||
|
if (TUNE.drillOneShot >= 0.5) {
|
||||||
|
for (const z of zombies) assert.equal(z.hp, 1, z.variant + ' should be one-shot, saw hp ' + z.hp);
|
||||||
|
} else if (tank) {
|
||||||
|
assert.ok(tank.hp > 1, 'the heavy bodies keep the roster HP: ' + tank.variant + ' at ' + tank.hp);
|
||||||
|
// ...and that is what lets it show a wound stage on the way down
|
||||||
ball.active = true; ball.lastHit = 'P'; ball.y = 1; ball.pow = 1; ball.bounces = 0;
|
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;
|
ball.x = tank.x; ball.z = tank.z; ball.vx = ball.vy = ball.vz = 0;
|
||||||
update(DT);
|
update(DT);
|
||||||
assert.ok(!zombies.includes(tank), 'one hit put down a ' + tank.variant);
|
assert.ok(zombies.includes(tank), 'one hit does not put down a ' + tank.variant);
|
||||||
|
assert.ok(tank.hp < tank.hpMax, 'but it is wounded: ' + tank.hp + '/' + tank.hpMax);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('a batch body that walks past you counts as caught, never as a free escape', () => {
|
test('a batch body that walks past you counts as caught, never as a free escape', () => {
|
||||||
|
|||||||
Reference in new issue
Block a user