Split The code is public; the app answers only to the crew.
Motion you did not ask for: honour prefers-reduced-motion where it is decoration, and nowhere else #217
Check · Review · Land · Live done — Landed and live in production.
`settings.ts` gains `motion: "full" | "reduced"`, guarded exactly like `difficulty`, with a new `src/client/motion.ts` owning the two values, their labels and the media query. `parseSettings` takes the system's answer as an optional `unchosen` fallback and reads a stored `motion` first, so `prefers-reduced-motion` can only ever supply a first-run default — a choice already made wins outright in bo…
shipped merged
commented
probe
Approved
Approved — the full review is the anchored comment on src/client/motion.ts.
Sign in to join this conversation.
settings.tsgainsmotion: "full" | "reduced", guarded exactly likedifficulty, with a newsrc/client/motion.tsowning the two values, their labels and the media query.parseSettingstakes the system's answer as an optionalunchosenfallback and reads a storedmotionfirst, soprefers-reduced-motioncan only ever supply a first-run default — a choice already made wins outright in both directions, andloadSettingsis tested end to end against stubbedlocalStorageandmatchMediato prove it. The switch is a Motion select in the menu and again in the pause overlay, paired and live the way Difficulty is;drawHudtakes onemotionvalue afteralphaand decides once a frame, and the renderer takes it as a number throughsetMotionso the hot loop has no second path.Reduced turns down two things and only two: the walk's sway on the gun in your hands (
bobScalegoes to 0, which draws exactly the gun a player standing still already holds — the renderer test pins that equivalence to the last float), and the damage wedge's fade, which now holds its strength for its life and then goes, keeping the direction and the weight of the hit. Everything else is byte-identical on both settings: a HUD test draws a frame with a hit marker, a kill-feed line and an open crosshair at both motions and asserts the whole call list is equal, and a renderer test shows the kick, the reload dip and the muzzle flash all survive reduced motion, because they are things that happened rather than idle motion.Three items on the issue's list turned out not to exist in this tree, and I pinned them rather than inventing motion to remove: the hurt vignette has no clock in it at all — its alpha is a function of
view.healthalone — so it is already the steady tint reduced motion would ask for, and a test asserts its alpha does not move withworld.timeon either setting while still deepening as you are shot. The spectate camera is already a cut:main.tsbuilds it fromeyePos(watched(...))with nothing interpolated. And I deliberately left the muzzle flash alone on both settings — removing your own flash would clear the view while spraying, which is an aim advantage, and the issue's own rule is that any change making the game easier under reduced motion is the wrong change. The README's Playing section says all of this, including the sentence about why the recoil punch stays, andreadme.test.tsholds it there.To try it: open the preview, set Motion: Reduced in the menu (or pause mid-match), walk with a rifle out and watch the gun stop swaying while the recoil, the flinch and the crosshair are untouched; take a hit and watch the wedge hold instead of sliding away. On a machine with reduced motion on at the OS level, a fresh browser profile starts on Reduced with no stored settings, and stays wherever you put it afterwards.
bun run checkis green: 779 tests, 0 failures.🤖 Generated with Claude Code
Closes #77