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

The gun says what it is doing: a muzzle flash, a reload you can see, and a raise when you switch #70

shipped merged opened this has it
opened this

Give the held gun a muzzle flash, a reload you can see and a raise when you switch, so the strongest feedback a shooter has stops being missing.

renderer.ts:376–400 draws the view model with a walk bob and a 0.12 s kick off lastShotAt, and nothing else ever happens to it. There is no light or sprite at any muzzle — not yours, and not anyone else's, so a bot firing across the map is a tracer that comes from nowhere. reloadUntil lives on the player and only the HUD reads it: the hands keep pointing the gun for the whole 2.5 s. And the weapon you switch to appears instantly, because the switch at world.ts:809 is instantaneous — which is a simulation rule, and stays one in this issue.

Done looks like:

  • [ ] A muzzle flash on the held gun: short, bright, additive, a couple of frames long, sized by weapon kind, placed at the model's muzzle and drawn for neither the knife nor the grenade.
  • [ ] The same flash in the world at every other shooter's muzzle, driven by the { kind: "shot" } effect world.ts already pushes (it carries pos, weapon and mine) — a firefight across the map reads as people shooting, not as lines appearing.
  • [ ] A reload the hands perform: while world.time < view.reloadUntil the view model dips away and comes back, driven by progress through the reload, and it is finished exactly when the reload is.
  • [ ] A raise when the held weapon changes: the model comes up over a fixed, short time. Purely visual — no nextShotAt, no deploy rule, nothing observe.ts or the RL arena can see.
  • [ ] No new per-frame allocation: the flashes come out of the existing line buffer or one pre-uploaded quad/mesh, they respect LINE_CAPACITY rather than overflowing it, and the pull states the frame time before and after on the same machine.
  • [ ] Tests against the recording context: the frame right after a shot draws more than a quiet frame; the knife draws no flash; the view model is still not drawn while scoped and not drawn while spectating a dead teammate; a reloading frame places the model differently from an idle one; ten simultaneous shots do not exceed the line budget.
  • [ ] bun test green, and the README's What is real says the flash is drawn and that the swap is animation only.

Not in this issue: a deploy time in world.ts, a reload cancelled by switching, ejected shells, dynamic lighting on the level, and smoke (#37 owns clouds).

Files I expect to change: src/client/renderer.ts (+ test), src/client/models.ts, README.md.

Risks: it waits on the context-loss issue because that one restructures how createRenderer owns its resources — a flash mesh added first would have to be rewritten into the rebuild path, and two pulls would edit the same forty lines. The other trap is the line budget: a flash per shooter is ten more line pairs a frame in a full round.

Crew: vesper — WebGL2 by hand, in the file whose frame time they measure.

commented

#68 landed as pull #75, so this one is ready and the ground under it has moved in your favour. createRenderer now keeps every GL resource in one Gpu value made by build(gl) (renderer.ts:184) and rebuilt on webglcontextrestored; draw returns early while lost is true and the renderer exposes that flag. So: the muzzle flash's mesh or quad belongs in build, not in a closure beside it, or a restore will come back without it. renderer.test.ts also gained a canvas that can take listeners and a set of lost/restored tests — the frame-shape assertions you need are already in that style.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs