The bench learns to host the living: two player slots for run-cycle tuning

Oliver: "add a normal player male and female to the hidden bench and let it
run there." The fam walk (up/down) now runs two slots past ZFAMS — PLAYER
MALE and PLAYER FEMALE. In a living slot the racket zombie steps outside the
frame, G.hideO lifts (splatter set it when the match turned), and the real O
runs the walk stop's own back-and-forth at the classic CPU's speed class, so
the cadence on the bench is the cadence the game plays. SK() reads the slot
for the _f suffix, so both tours review without touching the picked tour.
The HUD swaps the wound readout for the tuning one: runHz and aiSpeed, the
dials a live session turns through T.TUNE. T also gains a keyPressed alias.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Fable 5 committed 2026-08-14 14:39:36 +02:00
1 parent ad624d281c
commit 90d7ac1096
4 files changed
+42 -7

No files matched your search

+23 -3
View File
@@ -199,6 +199,8 @@ export function endBench() {
// wound stage — the bench must ask the same question the game asks or it is testing something else.
function benchApply() {
const zb = zombies.find(z => z.variant === 'r'); if (!zb) return;
// the two slots past ZFAMS are the LIVING opponents; render's SK() reads b.char for the suffix
G.bench.char = G.bench.fam >= ZFAMS.length ? (G.bench.fam - ZFAMS.length ? 'of' : 'o') : null;
zb.hpMax = BENCH_HP;
zb.hp = Math.max(1, Math.round(BENCH_HP * [1, 0.5, 0.2][G.bench.stage]));
// ...and the named states take the wound stage with them: z_r<suffix> at full health,
@@ -228,9 +230,10 @@ export function benchKey(code) {
const b = G.bench;
if (code === 'ArrowLeft' || code === 'KeyA') b.stage = (b.stage + 2) % 3;
else if (code === 'ArrowRight' || code === 'KeyD') b.stage = (b.stage + 1) % 3;
// up/down walks the OPPONENT FAMILIES — one today, one row in config.ZFAMS per variant after that
else if (code === 'ArrowUp' || code === 'KeyW') b.fam = (b.fam + ZFAMS.length - 1) % ZFAMS.length;
else if (code === 'ArrowDown' || code === 'KeyS') b.fam = (b.fam + 1) % ZFAMS.length;
// up/down walks the OPPONENT FAMILIES — plus two LIVING slots past the end (the tennis
// opponents m/f, Oliver's run-cycle finetune bench): the zombie steps aside and the real O runs
else if (code === 'ArrowUp' || code === 'KeyW') b.fam = (b.fam + ZFAMS.length + 1) % (ZFAMS.length + 2);
else if (code === 'ArrowDown' || code === 'KeyS') b.fam = (b.fam + 1) % (ZFAMS.length + 2);
// F plays the fall. Before the fallback below, which treats ANY other key as "next state" — a new
// control that forgets to land above that line silently steps the stop instead.
else if (code === 'KeyF') return benchFall();
@@ -247,6 +250,23 @@ export function updateBench(dt) {
// ...and a body that is COLLAPSING is not pacing. tickMatchFall owns the frame while the fall runs,
// so the walk stop must stop walking or the corpse slides sideways through its own death.
if (G.matchFall) return;
// THE LIVING SLOTS: the zombie waits outside the frame while the real opponent runs the same
// back-and-forth the walk stop uses — same speed class as the classic CPU, so the cadence being
// judged is the cadence the game plays at. Every stop shows the run; the run is what the bench
// slot exists to tune.
if (b.char) {
zb.x = 40; zb.vx = 0; zb.pose = 'idle';
G.hideO = false; // splatter hid him when the match turned; the bench un-hides
O.z = 5.5;
const step = TUNE.aiSpeed * 0.7 * dt;
O.x += b.dir * step;
O.runT += dt;
if (Math.abs(O.x) > 4.0) { b.dir *= -1; O.x = Math.sign(O.x) * 4.0; }
O.facing = b.dir;
O.pose = 'run';
return;
}
O.pose = 'idle'; O.x = HOME.O.x; O.z = HOME.O.z; G.hideO = true; // fam stops: the living man steps back out
if (item.walk) {
// THE STRIDE STEPS OFF GROUND COVERED, like the real one — a bench that animated on a clock
// would be flattering exactly the sprites that foot-slide in play.