Split The code is public; the app answers only to the crew.

Utility, part one: a grenade you can throw, and an HE that hurts #36

shipped merged opened this has it
opened this

Give both sides the one thing a CS round has that this game has none of: a grenade you buy in the freeze time, throw on an arc, and that hurts everyone it can see when it goes off.

Where it stands. WEAPON_LIST in src/client/weapons.ts has no thrown item. world.ts has pickups and bomb as its only non-player entities. Input carries switchTo: WeaponId | null, and the buy panel builds itself from buyable(team) in src/client/main.ts:148.

Done looks like:

  • [ ] A new src/client/grenades.ts owning one concept — a thrown object's flight: position, velocity, gravity, bounce and friction against Box[], a fuse. No WebGL, no audio and no HUD in it, headless-testable like every other module beside it.
  • [ ] world.update steps them behind the same empty-list fast path updatePickups uses (if (world.grenades.length === 0) return), so the training arena, which never spawns one, pays one comparison a tick.
  • [ ] An HE grenade buyable at $300, one carried at a time, in the existing buy panel — listed with its price and disabled when the wallet or the round says no, like every other item.
  • [ ] Held as a fourth slot: 4 on the keyboard and a 4 nade button in #touch, chosen through the existing switchTo/slotWanted path and not a new one. Fire throws it, from the eye, forward and slightly up, with the thrower's velocity added. A player holding none cannot select the slot.
  • [ ] It bounces off walls and crates, loses speed each time, comes to rest, and detonates 1.6 s after it left the hand, wherever it is by then.
  • [ ] Damage: full at the centre, falling to zero at the blast radius, reduced by armour, and nothing at all through a wall — the visible test in trace.ts, eye to blast. It counts towards the thrower's damage, and a kill goes through the same path a bullet's does: kill feed, corpse, dropped gun, checkRoundEnd. No friendly fire, as everywhere else in this match, but the thrower can kill themselves.
  • [ ] Feedback with no new mechanisms: a sound effect on the bounce and on the blast, panned by ears like every other world sound; the grenade visible in the world while it flies; the blast drawn with the impact/flash the renderer already has; damage taken going through hurt/flinch, so the vignette, the directional wedge and the pain sound all work for free.
  • [ ] The HUD says what you are holding and how many are left. startRound gives none back: a grenade thrown is gone, one kept is still yours, which is the rule the guns already follow.
  • [ ] Tests (src/client/grenades.test.ts and world.test.ts): a throw from a known state lands where the numbers say; a bounce keeps it inside the map; the fuse fires once and only once; damage falls off with range; a wall between the blast and a player means zero; a blast can end a round; the buy is refused outside the freeze and when one is already carried.
  • [ ] bun test green; README's Playing table and What is real say the key, the price and the numbers.

Not in this issue: smoke and flash (their own issues), bots buying or throwing utility (they keep buying guns), and anything in src/rl — the shipped weights were trained in an arena with no grenades, so observe.ts stays at 32 inputs and the arena stays as it is.

Files I expect to change: src/client/grenades.ts + grenades.test.ts (new), src/client/world.ts, weapons.ts, input.ts, main.ts, hud.ts, renderer.ts, models.ts, audio.ts, src/pages/game.ts, README.md, and the tests beside each.

Risks: the arena steps world.update millions of times — keep the fast path, run bun run scripts/train.ts --bench before and after, and say what you measured (the README claims ~13,000 ticks/s on one core). NO_INPUT and every Input literal in bots.ts and src/rl/arena.ts need the new field defaulted. slotWanted is typed 1 | 2 | 3 | null in input.ts — widen it deliberately rather than casting. A blast must not kill a player twice or reopen a round that is already over.

Crew: vesper — this is the loop, the physics and the renderer.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs