Public: anyone can read this; the crew can change it.

forked from trevato/cs · proposes

The first grenade: an HE you buy, throw and hear, thrown through the engine's own solver #157

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Working tree clean, `bun test` green at 684 pass / 0 fail, and the branch is pushed at `bce1aa4`.

shipped production is serving it deploy ae07c2d

opened this

Working tree clean, bun test green at 684 pass / 0 fail, and the branch is pushed at bce1aa4.

---

An HE grenade you buy for $300, throw on G (or the new nade touch button), and hear bounce off a wall before it goes off.

The body is the engine's. src/engine/physics.ts gains exactly one number — restitution, the share a blocked axis gives back, reversed. A walker's is 0 and the guard leaves their velocity untouched, so player movement is unchanged to the bit. src/client/movement.ts holds the grenade's pair beside CS's: NADE_MOVE (0.4 of CS gravity, 0.45 elasticity, stepSize: 0, friction that brings it to rest) and NADE_HULL, a four-unit cube — its own, so it cannot climb an eighteen-unit ledge like a person. src/client/grenade.ts is the throw, the fuse (burnt by dt on the 64 Hz clock, never a frame) and the blast; there is no second solver in it.

The blast is arithmetic with its units stated. 98 at the centre falling linearly to nothing at 350 units (Source's RadiusDamage falloff, written in the comment), a 1.5-second fuse, armour taking its half through the same throughArmor a bullet now goes through — and visible() from the middle of the blast to the middle of the chest decides whether any of it arrives. src/client/grenade.test.ts checks the flight against the closed form of the solver's semi-implicit Euler (z = z₀ − g·dt²·n(n+1)/2), the bounce at −337.5 u/s off a wall face, 83 at a standing body's chest, 47 at half the radius, nothing at 350, that a crate between the two takes all of it, and that 96 ticks of fuse is 96 ticks whether the frame runs one tick or four.

The digests moved; the play did not. replay.ts's snapshot now hashes each player's pocket and every live grenade's body and fuse, so all three pinned runs — the ten-bot round, the arena duel and the generated-map round — are re-pinned. That the simulation itself is untouched is measured, not asserted: folding the same two runs on this code with the old field list reproduces the old digests 6e2532e2… and 5cdb7419… exactly. A seeded round that buys and throws one is bit-for-bit reproducible and differs from the same round where nobody threw.

Bots are untouched. throwGrenade is one more field on Input, false in NO_INPUT and in action.ts's toInput; no bot buys an HE, and src/rl/arena.ts with the shipped weights runs unchanged.

Not done, deliberately: no smoke, no flash, no cooking, no bots throwing (the issue after this). No friendly fire and therefore no self-damage — the blast obeys the rule a bullet already obeys. The thrower's own velocity is not added to the throw (CS adds it); that is one number a later pull can add. The README was not touched — the page's own prose and help line were.

The app's ceiling did not fit: 83,653 raw / 38,038 gzipped passed the old gzip ceiling by 38 bytes, so it is raised to 88,000 / 40,500 with the measurement logged in src/games.ts. The range is unchanged; the example is the same size to the byte and differs only in minified names.

Worth a look in the preview: buy an HE in the freeze, aim about ten degrees below where you want it, press G, and watch it bounce and tumble — then check the kill feed says HE Grenade. Worth a look in the diff: the one-line change in src/engine/physics.ts and its guard, and blastHits in src/client/grenade.ts.

One note for the reviewer: during this run I saw dying moves the camera behind a teammate… in spectate.test.ts fail once and pass on every run since, including the final one. I did not chase it — it looks order-dependent rather than caused by this change, but I cannot claim that from one observation.

🤖 Generated with Claude Code

Budget reached at $8.51; this is where it stopped.

Closes #142

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs