Both players run from a video, and the middle chip finally plays you
Six sessions of asking an image model for a row of running figures, and it
never once drew a gait: three phases repeated with the same leg leading,
"ropeskipping, not running". A frame-by-frame LEFT/RIGHT skeleton spec, a
closing checklist, a loop clause, six figures instead of eight — all lost.
One 5-second image-to-video clip has no such problem, because it moves a
body instead of drawing poses. ~$0.42 a character through the key already in
.env, pose pinned at BOTH ends (pinned only at the front, she rotated into
profile halfway), stride period MEASURED off the frames rather than sampled
by eye — hers repeats in 6 clip frames, his in 23 — and the phases cut and
palette-snapped through _stride.cut. assets/_vidroll.py films and cuts it;
her first frame came from one still roll that took his approved pose as a
picture instead of a paragraph.
Wired: runN probes the sheet through SK() so the women's tour counts HER
frames, the player is no longer clamped to a two-frame pair, runHz 2.4 ->
1.9 ("this looks like roadrunner"), and the bench grew two PLAYER stops —
a cycle only ever seen mid-rally is a cycle nobody has watched.
Also in: the difficulty retune (medium is a real step above the shipped CPU
and is where `smart` first appears; hard stepped back off superhuman; easy
closed its gap) with MEDIUM's dial-for-dial pin to TUNE removed from the
test, and Oliver's own transparency fixes on the male frames.
SPRV 138 — the frames were right on disk and wrong on screen for an hour
because assets are cached for a year unless the ?v= moves. main.js v269.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2b7eb04e19
commit
1b7fc2cdcf
23 files changed
+352
-71
No files matched your search
+18
-11
@@ -170,6 +170,7 @@ export function endDrill() {
|
||||
// ONE NAMED FRAME, so frames the game cannot reach yet — the serve, the fall — can still be looked
|
||||
// at the day they are generated rather than the day they are wired up.
|
||||
const BENCH_HP = 12; // a deep pool so a wound stage is set by FRACTION, cleanly
|
||||
const BENCH_CHARS = ['o', 'of', 'p', 'pf']; // the living slots past ZFAMS, in walk order
|
||||
|
||||
export function startBench() {
|
||||
if (G.interlude || G.horde || G.bossFight || G.drill || G.match || G.bench || G.net) return false;
|
||||
@@ -200,8 +201,10 @@ 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;
|
||||
// the four slots past ZFAMS are the LIVING players: the two opponents, then the two PLAYERS
|
||||
// (the run cycles are re-rolled from the same script and judged the same way — a player cycle
|
||||
// that was only ever seen in a rally is a cycle nobody has actually watched)
|
||||
G.bench.char = G.bench.fam >= ZFAMS.length ? BENCH_CHARS[G.bench.fam - ZFAMS.length] : 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,
|
||||
@@ -233,8 +236,8 @@ export function benchKey(code) {
|
||||
else if (code === 'ArrowRight' || code === 'KeyD') b.stage = (b.stage + 1) % 3;
|
||||
// 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);
|
||||
else if (code === 'ArrowUp' || code === 'KeyW') b.fam = (b.fam + ZFAMS.length + BENCH_CHARS.length - 1) % (ZFAMS.length + BENCH_CHARS.length);
|
||||
else if (code === 'ArrowDown' || code === 'KeyS') b.fam = (b.fam + 1) % (ZFAMS.length + BENCH_CHARS.length);
|
||||
// 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();
|
||||
@@ -257,14 +260,18 @@ export function updateBench(dt) {
|
||||
// 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;
|
||||
// 'p'/'pf' pace the PLAYER on the near court instead — same back-and-forth, his own end, and
|
||||
// the sprite is twice the size there, which is the whole reason his cycle needs its own stop.
|
||||
const ch = b.char[0] === 'p' ? P : O;
|
||||
G.hideO = b.char[0] === 'p'; // splatter hid him when the match turned; the opponent
|
||||
ch.z = ch === P ? HOME.P.z : 5.5; // stops un-hide, the player stops leave him out
|
||||
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';
|
||||
ch.x += b.dir * step;
|
||||
ch.runT += dt;
|
||||
if (Math.abs(ch.x) > 4.0) { b.dir *= -1; ch.x = Math.sign(ch.x) * 4.0; }
|
||||
ch.facing = b.dir;
|
||||
ch.pose = 'run';
|
||||
if (ch === P) { O.pose = 'idle'; O.x = HOME.O.x; O.z = HOME.O.z; }
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user