- M-35-QOL-REVIEW.md: per-change file:area breakdown, config keys with ranges/defaults, verification evidence. - M-35-PLAYTEST-CHECKLIST.md: the human handoff — what to do, what correct feels like, which key tunes it. Item 0 (default-config regression) first. - OPTIONS-MENU-PLAN.md: WI-3 design — recommends a port-layer fast3d 2D overlay over a src/ menu edit; resume checklist. Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
6.5 KiB
M-35 — Native-PC input + options-menu QoL review
Branch: qol/native-pc-input-menu (cut from def0e0ac). Port-layer only
except one documented route-(b) #ifdef PORT line in src/fr.c (D181).
Every new behaviour is config-gated with the default reproducing prior
behaviour; tools_pc/golden/ headless captures are unaffected at defaults
(no mouse in a headless run; Game.ScreenShakeIntensity default 1.0 is a
no-op multiply).
Findings: D180 (input pass), D181 (screen-shake hook). See
docs/dev/findings.md §F.
WI-1 — In-game mouse: Quake-style click-to-lock
Status: landed, feel-check owed.
What changed
| File:area | Change |
|---|---|
port/src/input.c — new statics mouseCaptureMode / captureArmed / windowFocused |
Capture-mode state. mouseCaptureMode registered as Input.MouseCaptureMode (0/1, default 0). |
port/src/input.c inputInit |
In capture mode, start ungrabbed (SDL_SetRelativeMouseMode(FALSE)), wait for a click. Legacy mode unchanged (grab on start). |
port/src/input.c applyGrab() / reconcileGrab(menuMode) |
applyGrab is the old inputSetMouseGrab body (SDL relative-mode toggle + delta drain). reconcileGrab computes the wanted grab state: legacy → windowFocused; capture → captureArmed && windowFocused && !menuMode. Called once per controller-0 poll and from every notify hook. |
port/src/input.c inputSetMouseGrab(on) |
Now just records windowFocused and calls reconcileGrab — so focus loss/gain works in both modes without special-casing. |
port/src/input.c inputNotifyClick() |
Capture mode + focused → captureArmed = 1 + regrab. No-op otherwise (game sees the click). |
port/src/input.c inputReleaseCapture() |
Capture mode + grabbed → captureArmed = 0, release, return 1 (caller swallows the key). Returns 0 otherwise (ESC falls through to the N64 B button, D145). |
port/src/input.c inputComputePad idx 0 |
Calls reconcileGrab(menuMode); when capture-mode + free + in a stage, zeroes the local mb so mouse buttons never reach the game (no phantom fire from the re-lock click). |
port/src/video.c event pump |
SDL_MOUSEBUTTONDOWN → inputNotifyClick(). SDLK_ESCAPE keydown → inputReleaseCapture() (before the existing fall-through). |
port/include/input.h |
Prototypes for the three new hooks. |
Config keys
| Key | Range | Default | Effect |
|---|---|---|---|
Input.MouseCaptureMode |
0–1 | 0 | 0 = legacy (cursor grabbed whenever the window is focused). 1 = click-to-lock: free cursor until you click the window; ESC / focus-loss / opening a menu frees it; entering a stage while "armed" re-locks. |
Input.MouseAimSpeed |
1–500 | 16 (was 25) | Aim-mode (RMB / LShift) sensitivity %. B3: still overshot at 25. |
Controller input is completely independent of all of the above — the pad
branch in inputComputePad is untouched, and reconcileGrab only ever
toggles SDL_SetRelativeMouseMode.
D166 / hipfire-pitch interaction
The D166 hipfire pitch pulse is unchanged. It only runs when the cursor is
grabbed (mouse deltas are zero while free), so click-to-lock does not
perturb it. The M-24 aim-band (61 + gain into the 60 + aimBand ceiling)
is unchanged; only the mouseAimSpeed scalar feeding gx/gy moved.
Owed feel-checks (playtest checklist items 1–4)
Headless runs cannot move the mouse, so the capture ergonomics, the new
MouseAimSpeed = 16 default, and the D166 pulse consistency under the new
model are all inferred, not observed.
WI-2 — Menu pointer: absolute cursor tracking
Status: landed for capture mode, feel-check owed. Route (a), port-only.
What changed
port/src/input.c, the menuMode pointer branch (the D165/D169
P-controller). Added an absolute-position target source: when
mouseCaptureMode && !mouseGrabbed (i.e. a real free OS cursor over a
front-end screen), read SDL_GetMouseState + SDL_GetWindowSize of the
mouse-focus window, normalise to [0,1]², and map straight onto the live
virtual front-end rect (loH..hiH × loV..hiV, already derived from
getPlayer_c_screen* for D169). That becomes menuTgt{H,V} directly
(no accumulation, no drift). The P-controller/estimator below is unchanged
and simply chases the target, so the game's cursor_h_pos/v_pos lands
exactly where the OS pointer is.
The relative-delta path (legacy MenuPointerMode, or capture mode while
grabbed) is unchanged — haveAbs gates only the new assignment.
Why this is enough for 1:1
The D169 fix already made the clamp rect track the real 440×330 field. The only thing missing for true 1:1 was a non-drifting target: a free OS cursor is an absolute device, so mapping its window fraction onto the rect is exact. No route-(b) hook needed.
Keyboard nav parity
Untouched — the menu branch only ever adds to sx/sy; D-pad / stick keys
still feed the same axes. The D118d front.c latent sibling was not
touched (no evidence the absolute path exposes it; the stick values emitted
are small P-controller corrections, never a slammed ±80).
Caveat
Absolute tracking is only wired for MouseCaptureMode = 1. In legacy mode
(default) the menu still uses the D165/D169 relative estimator. If
click-to-lock becomes the default later, WI-2 is 1:1 for free; until then
menu 1:1 requires Input.MouseCaptureMode = 1.
WI-3 — In-game video/options menu
Status: config-system foundation landed; menu surface = design doc only.
See docs/dev/OPTIONS-MENU-PLAN.md.
Landed
configRegisterFloat/configRegisterUInt+ clamps (port/src/config.c,port/include/config.h).configSaverewritten to emit all four option kinds section-grouped in one pass.Game.ScreenShakeIntensity(0.0–10.0, default 1.0) — the first route-(b) hook, D181, proving the pattern the menu will lean on.
Not landed (budget) — precise resume point in the design doc
- The menu tables / handlers themselves. GE's menu system (
front.c/initmenus.c/mainmenu.c) differs enough from PD'soptionsmenu.cthat it is an adaptation, not a copy. Design doc covers the entry point, the handler shape, and the minimum option set.
Verification evidence
| Check | Result |
|---|---|
./build-pc.sh ntsc-final |
green, 241/241 |
-level_09 framediff (200/320/440) |
3/3 within threshold; nonclear Δ 0.00–0.01pp (91.6%); phash 0/1/5 = documented D117 intro-pan noise |
GE_STARTMENU=7 boot |
crash-free, no ge007.crash.log |
| golden dumps at defaults | unaffected (no headless mouse; shake scale 1.0 = no-op) |
| linkcheck | see PR body |