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

Aim goes through the OS pointer acceleration, and a refused pointer lock leaves a live match you cannot aim in #225

open told opened this
opened this

Intent: the mouse aims with the raw counts the sensor sends, and when the browser refuses the pointer lock the match stays paused instead of running with no way to aim.

What is true today (at 3118dc1)

  • lock (src/client/main.ts:411) is void scene.requestPointerLock() with no options. On browsers that support it, { unadjustedMovement: true } switches off the operating system's pointer acceleration. Without it, the same 5 cm swipe turns you a different number of degrees depending on how fast you made it. Every sensitivity setting the game offers (#198's scoped curve, the menu's sensitivity) sits on top of a curve the player never chose.
  • A refused lock is dropped without a word. The void swallows the rejection, and no pointerlockerror listener exists (grep -rn pointerlockerror src finds nothing). Chrome refuses a re-lock for about a second after the player presses Esc. So pressing Esc and then clicking Resume straight away (main.ts:744–749) takes down the pause overlay, lock() fails, and pointerlockchange never fires. The match is now live behind a free cursor: mouse movement does nothing (input.ts:233 gates on controls.locked), and nothing puts the pause back up.

Done looks like

  • [ ] A small module, e.g. src/client/lock.ts, owns the request. It takes the element (anything with requestPointerLock) and returns a result that is plain data: "raw" | "plain" | "refused".

- It tries { unadjustedMovement: true } first. If that rejects with NotSupportedError, it tries once with no options. - It handles a requestPointerLock that returns undefined (older Firefox/Safari) as "plain", pending the pointerlockchange. - Any other rejection is "refused".

  • [ ] main.ts's lock() uses it. On "refused" it keeps the match paused, with the overlay back up and a one-line reason (e.g. "Click Resume again to take the mouse"), in the same voice as the lost-view note.
  • [ ] lock.test.ts drives a fake element through all four paths (raw accepted, raw unsupported → plain accepted, undefined return, refused). Each test would fail without the module.
  • [ ] Touch (coarse) still never asks, as today.
  • [ ] The README's controls section says the game asks for raw mouse input where the browser offers it.
  • [ ] bun run check is green.

Files: src/client/lock.ts (new), src/client/lock.test.ts (new), src/client/main.ts, README.md.

Risks

  • Players used to OS acceleration will find the aim feels different. That is the point, but the README line should say so.
  • Do not retry in a loop or on a timer. A refused lock waits for the next click, which is the user gesture the browser requires anyway.
  • main.ts is also where #114 (the rebind panel's wiring) lands. Keep the change to lock() and the Resume handler.

Suggested brief: scout (client wiring and its harness).

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs