diff --git a/index.html b/index.html index 26fcc0e..cb2241f 100644 --- a/index.html +++ b/index.html @@ -97,6 +97,6 @@

- + diff --git a/render.js b/render.js index 3a7b8c0..3bf87a4 100644 --- a/render.js +++ b/render.js @@ -258,6 +258,11 @@ function drawBoy(b) { ctx.drawImage(img, -w / 2, -h, w, h); ctx.restore(); } +// Frames stepped by DISTANCE WALKED need a POSITIVE modulo. Bodies spawn BEHIND the far baseline at +// negative z, and JS keeps the sign of the dividend: floor(-1.5 / 0.25) % 4 is -2, so the pick asked +// for 'z_g-1' — a sprite that does not exist, and drawStatic silently draws nothing. That is the +// blinking Oliver saw on the way in, on exactly the bodies that step off distance: brutes and bosses. +const stepFrame = (v, n) => ((Math.floor(v) % n) + n) % n; // 0..n-1, whatever the sign of v const ZFRAMES = { a: 4, b: 4, f: 4, d: 4, g: 4 }; // 4-frame walk cycles where sheets exist; the rest stay 2 // The rogue ball machine, on its own generated sheet: a dented hopper cart on wheels with a barrel // aimed down-court, and a second frame with the muzzle flashing. Oliver called the first, procedural @@ -288,7 +293,7 @@ function drawZombie(zb) { if (!SPR['z_' + v + N]) N = 2; // frames still loading (or sheet rejected): 2-frame fallback // same CYCLE frequency as before — more frames subdivide the stride, they don't speed it up let frame = rk && zb.pose === 'swing' ? 2 - : zb.variant === 'g' ? Math.floor(zb.z / (TUNE.bruteStride * 2 / N)) % N + 1 // heaviest body: step off DISTANCE + : zb.variant === 'g' ? stepFrame(zb.z / (TUNE.bruteStride * 2 / N), N) + 1 // heaviest body: step off DISTANCE // WALKED, like its wound lurch below, // or the feet slide (same cycle length) : Math.floor(zb.walkT * (zb.variant === 'f' ? 6 : 2.6) * N / 2) % N + 1; @@ -307,7 +312,7 @@ function drawZombie(zb) { // was pure foot-slide. This replaced a procedural squash-and-sway that read as exactly what // it was: fake. The comedy is in the gait, and the gait has to be drawn. const b = stage + 'b'; - name = SPR[b] && Math.floor(zb.z / TUNE.bruteStride) % 2 ? b : stage; + name = SPR[b] && stepFrame(zb.z / TUNE.bruteStride, 2) ? b : stage; h *= SPR[stage].height / SPRITE_H; // height ALWAYS off the A frame, or the size pops each step } else if (SPR.z_gw) name = 'z_gw'; // a missing asset degrades to the last skin that loaded } @@ -336,10 +341,10 @@ function drawZombie(zb) { const idx = f >= 1 ? 0 : Math.min(nStage, Math.max(1, Math.ceil((1 - f) * nStage))); const wanted = zb.windT > 0 ? A.throws[Math.min(idx, A.throws.length - 1)] : idx ? A.wounds[idx - 1] - : A.walk[Math.floor(zb.z / TUNE.bruteStride) % 2]; + : A.walk[stepFrame(zb.z / TUNE.bruteStride, 2)]; // a design whose sheet has not been generated (or has not loaded) degrades to design 1 rather // than vanishing, since drawStatic silently draws nothing for a missing sprite - const stage = SPR[wanted] ? wanted : BOSS_ART[0].walk[Math.floor(zb.z / TUNE.bruteStride) % 2]; + const stage = SPR[wanted] ? wanted : BOSS_ART[0].walk[stepFrame(zb.z / TUNE.bruteStride, 2)]; if (SPR[stage]) { name = stage; h *= SPR[stage].height / SPRITE_H; } // height ALWAYS off the frame, or the size pops } drawStatic(name, zb.x, zb.z, h, Math.floor(zb.walkT * 5.2) % 2);