Fire the laser as light, and stop him jogging on the spot

The auto-fire jog was one guard. gameplay skipped the pose update in mgSteer mode,
so the pose stuck on 'idle' -- and render covered for it by faking a run cycle off
G.idleT, which animated whether he was moving or not. That fake is what read as
"this hectic run animation... odd all the time". The pose follows MOVEMENT in every
mode now and the fake is deleted, so auto-fire, the horde and the melee racket all
use the ordinary run animation and standing still is standing still. The comment
that justified the fake -- two rackets when idle -- was solving a smaller problem
than it caused; at sprite scale the overlap does not read.

The laser, second pass, all of it "it must read as light he fired":
 - it draws AFTER the net and every body. It was drawn before the far-side pass,
   so the net and the crowd painted over it, which is exactly why it looked like
   it was lying on the floor and sitting under the net.
 - beamLen 26 -> 34: at 26 it stopped just past the far baseline and read as cut
   off in mid-court. It carries into the courtside ads now.
 - beamW0 0.35 -> 0.06, a POINT at the strings. At 0.35 it left the racket as a
   0.7 m bar hanging in mid-air instead of something emitted from it.
 - it DELETES ordinary bodies, hp irrelevant. A plane of light that left a roller
   man standing made it a worse ball, not a weapon. A boss is the exception and
   takes dmgBeam 8 -- severe, survivable.
 - four nested wedges composited 'lighter' (violet bloom, blue, cyan, white core),
   each fading along its length. One quad with a gradient was a coloured shape.

Rocket: speed 17 -> 38, and a real jet plume laid tight ON the flight line rather
than scattered sparks, which looked like a firework.

Beacon: a finer one-pixel thread and a much brighter ball, so the BALL is the
bright thing and the trail only says where it has been.

And the bug in the middle of that: firePellet stamps a whitelist of fields onto
the round, and jet/trailR went onto the WEAPONS rows without being added to the
list -- so the rocket flew with no plume and nothing errored. Any new pellet field
has to be added in both places.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-03 14:44:57 +02:00
1 parent 46e268973d
commit 91053b4c35
6 files changed
+151 -62

No files matched your search

+26 -12
View File
@@ -314,10 +314,11 @@ export function fireBeam(dirX, dirZ) {
const off = Math.abs(rx * uz - rz * ux); // lateral offset from the lane
const halfW = TUNE.beamW0 + (TUNE.beamW1 - TUNE.beamW0) * (along / TUNE.beamLen);
if (off > halfW + hitR(zb)) continue;
// THE BEAM DELETES ORDINARY BODIES. hp does not enter into it: a plane of light that left a
// roller man standing was the thing that made it feel like a worse ball rather than a weapon.
// A BOSS is the exception and takes a heavy wound instead — "they also must suffer severely".
if (isBoss(zb)) { if (wound(zb, TUNE.dmgBeam)) killZombie(i, true); }
else if (zb.variant === 'o') killZombie(i, true); // it pops a barrel too, which is fair
else if ((zb.hpMax || 0) > 1 && (zb.hp -= TUNE.dmgBeam) > 0) woundBurst(zb);
else splatter(i);
else killZombie(i, !!G.horde);
}
}
@@ -573,7 +574,7 @@ export function firePellet(w = WEAPONS.gun) {
const pe = { x: P.x, y: 0.9, z: P.z, vx: Math.sin(a) * s, vz: -Math.cos(a) * s,
t: w.life ?? 1.2, r: w.r ?? TUNE.mgPelletR, dmg: w.dmg ?? TUNE.dmgPellet, hp: w.hp ?? TUNE.dmgPelletHp,
pierce: !!w.pierce, boom: !!w.boom, ignite: w.ignite ? (w.igniteR ?? 1.8) : 0,
trail: w.trail || 0, col: w.col, glow: w.glow };
trail: w.trail || 0, trailR: w.trailR, jet: w.jet || 0, col: w.col, glow: w.glow };
// A LOBBED round: it is thrown, not sprayed. Solved so it comes down about where the crowd is, so
// "it throws itself" reads as aiming rather than as a flat stream that happens to be fat.
if (w.arc) {
@@ -952,15 +953,28 @@ export function updateZombies(dt) {
pe.vy -= pe.g * dt; pe.y += pe.vy * dt;
if (pe.y <= 0.05) { pe.y = 0.05; landed = true; }
}
// a colour dragged behind it — the beacon and the rocket are supposed to be seen from across
// the court, and this is what makes a lobbed round read as a THROWN object rather than a dot
if (pe.trail && parts.length < 220)
// A JET PLUME, not a shower of sparks: the exhaust is laid down tight on the flight line,
// barely moving, so it reads as a rocket motor drawing a line through the air. Scattered
// sparks with their own velocities looked like a firework, which is not what a rocket does.
if (pe.jet && parts.length < 240)
for (let k = 0; k < pe.jet; k++) {
const b = (k + Math.random()) / pe.jet; // spread ALONG this frame's step
parts.push({
x: pe.x - pe.vx * dt * b + (Math.random() * 2 - 1) * 0.05,
y: pe.y - (pe.vy || 0) * dt * b + (Math.random() * 2 - 1) * 0.05,
z: pe.z - pe.vz * dt * b + (Math.random() * 2 - 1) * 0.05,
vx: 0, vy: 0.25, vz: 0, // it hangs and lifts, it does not fly off
t: 0.30 + Math.random() * 0.30, jet: 1, spark: '#fff2c0' });
}
// ...and a FINE thread for the beacon: one small mote a frame, tight to the ball, so the ball
// itself is the bright thing and the trail only says where it has been.
if (pe.trail && parts.length < 240)
for (let k = 0; k < pe.trail; k++) parts.push({
x: pe.x + (Math.random() * 2 - 1) * 0.12, y: pe.y + (Math.random() * 2 - 1) * 0.12,
z: pe.z + (Math.random() * 2 - 1) * 0.12,
vx: (Math.random() * 2 - 1) * 0.5, vy: (Math.random() * 2 - 1) * 0.5 + 0.3,
vz: (Math.random() * 2 - 1) * 0.5,
t: 0.18 + Math.random() * 0.22, spark: pe.col || '#fff' });
x: pe.x + (Math.random() * 2 - 1) * (pe.trailR ?? 0.12),
y: pe.y + (Math.random() * 2 - 1) * (pe.trailR ?? 0.12),
z: pe.z + (Math.random() * 2 - 1) * (pe.trailR ?? 0.12),
vx: 0, vy: 0.12, vz: 0,
t: 0.16 + Math.random() * 0.16, fine: 1, spark: pe.col || '#fff' });
for (let j = zombies.length - 1; j >= 0; j--) {
const zb = zombies[j];
if (!zb) continue;