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
Check · Review · Land · Live done — Landed and live in production.
Pushed. `bun run test` (typecheck + 1312 tests) passes locally.
shipped production is serving it deploy ae07c2d
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
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
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 forunadjustedMovementfirst, somovementXis 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
mousedownandauxclickprevented 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.tsbuilds 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 throughtextContent.#311 — crisp at 125%.
src/engine/pixels.tsis 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, andOverlay.scaleis 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
/rangeand/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