# Front menu and precise controls — 5 October 2026

## Sensitivity

The old 0.1 lower bound was a browser settings/storage restriction. The numeric field now accepts finite values from zero to 20, with no decimal-place rounding or positive minimum. Examples: `0.001`, `0.00000001`. Zero turns off mouse look without disabling turn keys. The slider remains a convenient coarse adjustment; its 0.01 steps never quantize values entered in the numeric field. Invalid or empty edits restore the last valid value.

The existing 0.022 degrees per count × sensitivity relationship remains unchanged. No frame-duration scaling, acceleration, filtering or easing is applied to mouse events. A yaw already inside the normal range avoids modulo arithmetic to reduce cancellation at tiny sensitivities. Preferences keep the existing v3 key and migrate older records with turn binds disabled and unbound.

## Turn binds: reference and evidence boundary

Reference: Valve's public [Source SDK client input](https://github.com/ValveSoftware/source-sdk-2013/blob/master/src/game/client/in_main.cpp), specifically `KeyState`, `DetermineKeySpeed`, `AdjustYaw`, `CreateMove`, and `ExtraMouseSample`. Code was independently implemented; Valve code is not copied or distributed.

The referenced default yaw speed is 210 degrees/second, bounded at ±100000. Left adds yaw, right subtracts; held opposing buttons cancel. The reference button-state weights are: new press 0.5, held 1, release 0, press/release 0.25, release/repress 0.75. Repeats are ignored and each action tracks up to two physical keys. Mouse and keyboard yaw contributions add independently. Wheel turns act as press/release pairs. These properties have direct, analytical test expectations.

Browser button impulses are consumed once per authoritative 15 ms simulation command. A read-only camera prediction covers the accumulator remainder, so visual turns can update between commands without extra movement ticks. Final view angles are recorded in the existing replay format; server verification and physics identities are unchanged.

**Not certified exact CSS client parity:** the SDK is shared-engine evidence. Native CSS can divide keyboard sampling between command creation and render-time extra samples. This implementation deliberately uses a deterministic tick boundary, so an edge can differ by a sample from native CSS. No native CSS client input telemetry was captured for this change. Legacy `+strafe` / `+speed` modifiers and console aliases are not implemented. Mouse-device/browser delivery and floating-point angle precision also prevent a universal bit-identical claim.

Settings → Controls places the standard key bindings first, followed by a full-width Turn binds disclosure. Its 66 px click target has a border, hover state, On/Off status and an expand arrow; the title remains 11 px. Only enabled users see left/right bindings, the cycle binding and yaw speeds. Disabling retains saved bindings but clears held keys and ignores them during play. Focus loss, pointer-lock release, rebinding and restart also clear turn state.

Users can save up to eight distinct yaw speeds in their chosen order, edit or remove them, and select an active speed. At least one remains. The optional **Cycle yaw speed** binding starts unbound, advances once per press (key-repeat does not advance it), and wraps from the last preset to the first. Mouse buttons and wheel bindings also work. The current rate and preset number appear in a small HUD readout only while turn binds are enabled, and are hidden during replay playback.

The preset list and selected speed persist in the existing v3 preferences. A saved single speed migrates to a one-item list without changing its value. Invalid, duplicate and non-finite stored values are sanitized; invalid edits show a message and keep the prior valid value. Reset controls disables turn binds, clears all three turn bindings, and restores a single 210°/s preset.

Cycling is a view-input operation, equivalent in intent to switching cl_yawspeed with user bindings: it changes the rate without releasing held turn or movement keys, adding physics ticks, or invalidating a timed run. The resulting view angles remain fully recorded and subject to the existing server replay verification. This is a browser convenience, not a full Source console/alias implementation. Editing the list, sensitivity, FOV or bindings in Settings also preserves eligibility; the live menu continues recording neutral commands while controls are released.

## Front menu

The initial screen is a course browser with consistent Settings, Leaderboard, Account and Practice navigation. It uses actual renderer screenshots, captured by `scripts/capture-map-previews.ts`; all three JPEGs total approximately 256 KiB. The previews retain the original map/asset ownership and notices.

One last-played or URL-selected course preloads. Before joining, no simulation runs, ambience plays, or continuous 3D rendering happens on the front menu. Selecting another card changes only its preview. A single **Join map** click captures the mouse immediately, loads the selected course and enters automatically. Capturing before the download preserves browser activation even on slow connections; simulation and audio remain paused during loading. Escape or focus loss cancels entry without recapturing the mouse when loading finishes. Failed downloads release the mouse and offer **Retry join**. The current single-course renderer is disposed when replaced. In-game Escape opens the compact live menu; Change map opens the course browser over the running game. Opening these menus, Account or Settings releases controls while momentum, time and command recording continue. The game remains visible behind them, with the current time and ranked status in the menu. A finish saves once without closing an open panel. Hidden tabs and long frame stalls still interrupt ranked runs. Guest finish restoration and shared replay entry still take precedence over the initial front menu.

## Validation

The large featured preview cycles through all maps every six seconds. The map
grid remains fully available. Rotation changes only menu presentation: it does
not load another course, change the URL, advance physics or play ambience.
Hovering the feature (including Join) pauses it; manual selection, keyboard
focus, joining or a load failure stops it until Auto is selected. Previous/next
buttons also choose a stable map. Loading, hidden pages, focus loss, Settings
and account dialogs do not advance the carousel. Reduced-motion preferences
and explicit map/replay links start with rotation disabled. Join always uses
the visible map and keeps the existing one-click loading flow.
Preview artwork uses a 700 ms eased crossfade between two image layers; the
title, details and Join control stay in place. Incoming images are decoded
before switching, and rapid choices coalesce to the latest preview after the
current blend finishes. Reduced motion uses an immediate image swap. Hiding
the menu cancels its animation; no scene rendering or map loading is added.
`scripts/lobby-rotation-qa.ts` checks a complete real-time nine-map cycle,
hover/pause/focus behavior, selection and joining, responsive header layout,
reduced motion and direct links without modifying player state.

The one-click joining revision is checked by the same browser suite: delayed
downloads outlasting transient activation, immediate mouse capture, paused
simulation during loading, direct entry, Escape cancellation, failed-switch
mouse release and direct retry. The suite also retains the settings, controls,
responsive layout and preloaded-map checks below. No movement rules change.

- `npm test`: 307 passed, including all existing course witnesses, physics fixtures and online tests. Input fixtures cover preset migration, validation, ordering/wraparound, zero/negative/fractional rates, held-key preservation, key repeat, mouse/wheel cycling, and disabled/unlocked controls.
- `npm run test:lobby` against the production preview: twelve browser checks covering initial state, lazy map selection, tiny/zero sensitivity, saved/unbound turn keys, preset editing/removal/persistence, native keyboard turning and cycling under Pointer Lock, navigation/focus, all three joins, six viewport sizes, resetting controls, and failed-download recovery. Cycling preserves run validity and produces the expected recorded yaw increments at each selected rate. Only the deliberately failed downloads produce console errors.
- `scripts/ui-review-qa.ts`: 26 passing checks for settings, account, leaderboard, finish and sharing flows. `tests/guest-game.browser.ts`: two passing checks for restoring a completed guest run, including a different selected map and verification completing before the map loads.
- `scripts/browser-classic-qa.ts`: nine passing checks for course joins, actual movement input, audio, persistence, repeated map replacement, and all eight practice stations. The optional marker-only pass was skipped because zone geometry is unchanged. No console errors or warnings.
- `scripts/browser-classic-replay-qa.ts` against the production preview: all six checks passed. Boreas (2758 commands), Utopia (3867), and Mesa (3653) completed through ordinary recorded commands, then restarted into normal play. Finish tick, time, checkpoint splits and complete final player state exactly match the headless course witnesses; replay playback never changes personal records. No console errors or warnings. This is a regression check, not new evidence of native CSS parity.
- An initial development-server replay check hit a 717 ms frame stall during concurrent browser/development work and correctly paused playback. The isolated production-build rerun completed all three courses; the stall protection and comparison tolerances were not relaxed. A stale finish-text assertion was updated to the existing UI wording.
- Production build and type checking passed. The existing Three.js bundle size advisory remains; no new runtime dependency was added. The front menu and settings were visually inspected at desktop and narrow widths.
- Turn command sequences are exactly equal at 30/60/144/240 FPS. Source button-weight fixtures use exact expectations; yaw-rate comparison tolerance is 1e-10 degrees in unit tests and 1e-9 degrees in the isolated browser check. The browser rate check sets mouse sensitivity to zero to exclude real recentering mouse events rather than widening its tolerance.
- Browser reports and review screenshots: `test-results/lobby-input-qa.json`, `test-results/lobby-*.png`, and `test-results/settings-*.png` (local generated evidence).

No new maps, movement formula changes, production deployment or account migration are part of this update.
