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

forked from trevato/cs

buggy on windows. tried playing with a mouse and keyboard on windows in edge… #329

Merged opened this in answer to

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

Merged

Pushed. `bun run test` (typecheck + 1312 tests) passes locally.

shipped production is serving it deploy ae07c2d

opened this

Pushed. bun run test (typecheck + 1312 tests) passes locally.

Windows in Edge is four separate faults, and this branch answers all four of #307's children.

#308 — the pointer. Controls.lock() is the one place a game asks for the mouse: it asks for unadjustedMovement first, so movementX is raw counts rather than Windows' pointer speed plus "enhance pointer precision", and falls back to a plain ask. The answer is read instead of dropped, so a refused lock leaves the pause panel up with a sentence rather than a match running with a dead mouse. One absurd event cannot fling the view, and the Keyboard Lock API is asked for every key each game binds — on Windows crouch is Ctrl, so crouch-and-forward is Ctrl+W.

#309 — the whole mouse. The wheel binds by name and is counted in notches, so one wheel click is one jump whether the browser calls it 100 pixels, three lines or a page, and a trackpad has to travel a click's distance. Every bound button has its mousedown and auxclick prevented while the game has the input, so a thumb on Back mid-round no longer leaves the page; unbound buttons stay the browser's.

#310 — something to paste. src/client/support.ts builds one line: browser and platform, the ANGLE/D3D11 driver string, the stage and the pixel ratio, then how the pointer was granted (raw / adjusted / unknown / free), whose Escape it is, full screen, the tick rate, the frame time and the <meta name=build> commit. A refused shader is caught at boot and becomes a heading, a paragraph and the driver's log on the end of that line, with the loop stopped and the server-rendered rest of the page intact. Every piece comes from outside the page, so every piece is flattened, stripped of the separator, cut to 120 characters and set through textContent.

#311 — crisp at 125%. src/engine/pixels.ts is the one place that says how large a backing store is and where a bar of pixels must start for both edges to land on one. The overlay draws in device pixels with no context transform; a crosshair arm is a filled rectangle whose thickness bends by a pixel rather than letting the centre move by half of one, and Overlay.scale is now the window's width times the screen's density, so a dense screen stops rendering the HUD at 70%. Tests hold the crosshair whole and symmetric at 1, 1.25, 1.5, 2 and 3.

What to look at in the preview: the crosshair at 125% (the arms are one unbroken colour, not three columns of two-thirds), the wheel and thumb buttons mid-round, and What this browser is doing under the stage — open it while a match is locked and the second half of the line fills in.

What I left out: the diagnostics panel is still only on the shooter's page, not /range and /example — #310 asks for all three, and that is the one box on it I did not tick. I also did not touch #74 (the world stepping while the pause panel is up), which Atlas notes is a Windows symptom via Alt+Tab but is its own issue, and I left the range's fallback horizon line and its blips unsnapped on purpose: both move with the view, and snapping a moving line makes it step.

🤖 Generated with Claude Code

Closes #307

commented

Merged main and pushed; bun run test (typecheck + 1335 tests) passes on the merge result.

Windows in Edge is four separate faults, and this branch answers all four of #307's children.

#308 — the pointer. Controls.lock() is the one place a game asks for the mouse: it asks for unadjustedMovement first, so movementX is raw counts rather than Windows' pointer speed plus "enhance pointer precision", and falls back to a plain ask. The answer is read, so a refused lock leaves the pause panel up with a sentence instead of a match running with a dead mouse. One absurd event cannot fling the view, and the Keyboard Lock API is asked for every key each game binds — on Windows crouch is Ctrl, so crouch-and-forward is Ctrl+W.

#309 — the whole mouse. The wheel binds by name and is counted in notches, so one click is one jump whether the browser calls it 100 pixels, three lines or a page, and a trackpad has to travel a click's distance. Every bound button has its mousedown and auxclick prevented while the game has the input, so a thumb on Back mid-round no longer leaves the page; unbound buttons stay the browser's.

#310 — something to paste. src/client/support.ts builds one line: browser and platform, the ANGLE/D3D11 driver string, the stage and the pixel ratio, then how the pointer was granted (raw / adjusted / unknown / free), whose Escape it is, full screen, the tick rate, the frame time and the <meta name=build> commit. A refused shader is caught at boot and becomes a heading, a paragraph and the driver's log on the end of that line, with the loop stopped and the server-rendered rest of the page intact. Every piece comes from outside the page, so every piece is flattened, stripped of the separator, cut to 120 characters and set through textContent.

#311 — crisp at 125%. src/engine/pixels.ts is the one place that says how large a backing store is and where a bar of pixels must start for both edges to land on one. The overlay draws in device pixels with no context transform; a crosshair arm is a filled rectangle whose thickness bends by a pixel rather than letting the centre move by half of one, and Overlay.scale is the window's width times the screen's density, so a dense screen stops rendering the HUD at 70%. Tests hold the crosshair whole and symmetric at 1, 1.25, 1.5, 2 and 3.

The merge with multiplayer took the union of both PAGE_IDS lists, and one line of the new room code — a bare sceneCanvas.requestPointerLock() — now goes through takePointer like every other start, since a room is where a silently refused lock hurts most: the round begins on the server whether this page took the mouse or not. All three bundle ceilings moved back to the rule in src/games.ts (measured plus five to ten per cent); with both sides' code the app had 392 and the range 24 bytes of gzip headroom left.

What to look at in the preview: the crosshair at 125%, the wheel and thumb buttons mid-round, and What this browser is doing under the stage — open it while a match is locked and the second half of the line fills in.

What I left out: the diagnostics panel is still only on the shooter's page, not /range and /example — the one box on #310 I did not tick. I did not touch #74 (the world stepping behind the pause panel), and I left the range's fallback horizon line and its blips unsnapped on purpose: both move with the view, and snapping a moving line makes it step.

🤖 Generated with Claude Code

commented

Merged main (contact shadows) and pushed; bun run test — typecheck plus 1342 tests — passes on the merge result, and all four pages answer in single-digit milliseconds.

Windows in Edge is four separate faults, and this branch answers all four of #307's children.

#308 — the pointer. Controls.lock() is the one place a game asks for the mouse: it asks for unadjustedMovement first, so movementX is raw counts rather than Windows' pointer speed plus "enhance pointer precision", and falls back to a plain ask. The answer is read, so a refused lock leaves the pause panel up with a sentence instead of a match running with a dead mouse. One absurd event cannot fling the view, and the Keyboard Lock API is asked for every key each game binds — on Windows crouch is Ctrl, so crouch-and-forward is Ctrl+W.

#309 — the whole mouse. The wheel binds by name and is counted in notches, so one click is one jump whether the browser calls it 100 pixels, three lines or a page, and a trackpad has to travel a click's distance. Every bound button has its mousedown and auxclick prevented while the game has the input, so a thumb on Back mid-round no longer leaves the page; unbound buttons stay the browser's.

#310 — something to paste. src/client/support.ts builds one line: browser and platform, the ANGLE/D3D11 driver string, the stage and the pixel ratio, then how the pointer was granted (raw / adjusted / unknown / free), whose Escape it is, full screen, the tick rate, the frame time and the <meta name=build> commit. A refused shader is caught at boot and becomes a heading, a paragraph and the driver's log on the end of that line, with the loop stopped and the server-rendered rest of the page intact. Every piece comes from outside the page, so every piece is flattened, stripped of the separator, cut to 120 characters and set through textContent.

#311 — crisp at 125%. src/engine/pixels.ts is the one place that says how large a backing store is and where a bar of pixels must start for both edges to land on one. The overlay draws in device pixels with no context transform; a crosshair arm is a filled rectangle whose thickness bends by a pixel rather than letting the centre move by half of one, and Overlay.scale is the window's width times the screen's density, so a dense screen stops rendering the HUD at 70%. Tests hold the crosshair whole and symmetric at 1, 1.25, 1.5, 2 and 3.

Two merges with main are folded in. Multiplayer's room start called sceneCanvas.requestPointerLock() directly — the fault #308 removed everywhere else — and now goes through takePointer, since a room is where a silent refusal hurts most: the round begins on the server whether this page took the mouse or not. The contact-shadow merge is a union of two doors (surface.json at 0.6.0), two remeasured guide weights, and three ceilings measured with everything together; the example was 404 bytes over its old number once the shadows arrived, so all three are back to the rule in src/games.ts rather than a few hundred bytes of headroom.

What to look at in the preview: the crosshair at 125%, the wheel and thumb buttons mid-round, and What this browser is doing under the stage — open it while a match is locked and the second half of the line fills in.

What I left out: the diagnostics panel is still only on the shooter's page, not /range and /example — the one box on #310 I did not tick. I did not touch #74 (the world stepping behind the pause panel), and I left the range's fallback horizon line and its blips unsnapped on purpose: both move with the view, and snapping a moving line makes it step.

🤖 Generated with Claude Code

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs