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

forked from trevato/cs · proposes · 4 behind

The frame becomes engine: a fixed-step loop and a bindings-driven controller #14

Merged opened this

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

Merged

`src/engine/loop.ts` is the fixed step with nothing of this game in it: `advance(elapsed)` clamps a long frame before banking it, returns whole ticks and keeps the remainder, and `start`/`stop` drive from an injected `requestAnimationFrame` and `now`. `src/engine/controls.ts` takes a bindings table (`{ KeyW: "forward" }`) and hands back a held-action set, a thumb stick and drained look; its surfac…

shipped production is serving it deploy ae07c2d

opened this

src/engine/loop.ts is the fixed step with nothing of this game in it: advance(elapsed) clamps a long frame before banking it, returns whole ticks and keeps the remainder, and start/stop drive from an injected requestAnimationFrame and now. src/engine/controls.ts takes a bindings table ({ KeyW: "forward" }) and hands back a held-action set, a thumb stick and drained look; its surface, window and document are parameters typed by the two methods it calls, so controls.test.ts drives the whole thing — keys, repeat, mouse under lock, the stick, preventDefault and { passive: false } on the touch handlers, blur, lost lock, on-screen buttons — with a stub target and no browser. src/client/binding.ts is the game's half: CS_KEYS, CS_MOUSE, CS_TAP and one readIntent(held, stick) to an Input, pinned by binding.test.ts. src/client/input.ts is gone; main.ts lost the accumulator and the key table and is ~20 lines shorter.

Numbers were moved, not tuned: 0.022 and 0.06 look scales, 2.2 sensitivity with its 0.2–12 clamp, 0.38 of the width and 60 px of drag for the stick, 120 ms on jump/reload/buy, preventDefault on Tab, Space and every touch handler, and held actions cleared on both blur and lost pointer lock. Two deliberate differences worth a look: detach() now removes every listener it registered rather than the three the old module tracked, and mouse buttons come from a table, so only left fires and right scopes — a middle or side button used to fire, and no longer does.

bun test is green at 180 (145 before; 35 new), and the bundle is rebuilt as static/app.1ac7f396c7.js (67.6 kB, up 0.7 kB). There is no browser in this sandbox, so I could not read #fps myself — that is the one check left for the preview. What I did measure, headlessly: the world step is 0.189 ms at 1/64 over a simulated minute, and the whole per-frame engine path (loop.advance + takeLook + readIntent) is 0.34 µs, about 0.002% of a 16.7 ms frame, so the split costs nothing a frame timer can see. On the preview, start a round and check #fps sits where it did, then confirm WASD, the mouse, Tab, 1/2/3, the buy menu and the touch stick all still answer.

🤖 Generated with Claude Code

Closes #10

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs