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

A click shorter than a frame still fires: latch input edges instead of sampling them #9

shipped merged opened this has it
opened this

Make a press count even when it starts and ends between two frames, so firing, jumping and using feel immediate rather than occasionally ignored.

createControls in src/client/input.ts mutates controls.input on every key or pointer event, and src/client/main.ts reads that object once a frame. A press that begins and ends inside one frame is therefore invisible: fire is set true and back to false before the loop looks. That is a real lost shot on a fast tap with a pistol, and on touch it is the common case — attachButtons already works around it for jump, reload and buy with a 120 ms setTimeout, and does nothing for fire and use, which are exactly the two that matter. The workaround should go; the loop should be told what was pressed, not what is held.

Keep the shape: the latch is its own concept, so put it in a small headless module (src/client/press.ts is the obvious name) that takes edges in and gives a frame's worth of intent out, and test that module — input.ts itself needs a DOM and is one of the modules the README says is untested.

Done looks like

  • [ ] A new headless module owns the latch: an edge is recorded when a control goes down, and the frame that consumes it sees the control as down even if the up already arrived.
  • [ ] A press is held for at least one simulation tick, so a tap fires exactly one bullet and never zero.
  • [ ] Holding a control still behaves as it does today: an automatic weapon keeps firing, walk and duck are level-triggered, not edge-triggered.
  • [ ] fire, jump, use and reload go through the latch; the setTimeout workaround in attachButtons is gone and the touch buttons still work.
  • [ ] The loop in main.ts consumes the latch once a frame, before the fixed steps, and a frame that runs several 64 Hz steps does not repeat an edge across all of them.
  • [ ] bun test has a test per rule above that fails without the change: a down+up inside one frame yields one firing tick; a held control yields firing ticks for as long as it is held; releasing clears it.
  • [ ] bun run scripts/build.ts run and the rebuilt static/app.<hash>.js committed.

Files I expect to change

src/client/press.ts (new), src/client/press.test.ts (new), src/client/input.ts, src/client/main.ts, static/app.<hash>.js.

Risks

Latching too long turns a tap into a burst on an automatic weapon; the test for "one tap, one bullet" is the guard. Edge state consumed inside the while (accumulator >= TICK) loop rather than outside it would fire once per step on a slow frame — consume before the loop and pass the same input down.

Who

A builder with the client loop in hand. Small, self-contained, and it unblocks the frame-loop work in the render-scale issue.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs