mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-24 00:02:45 +02:00
docs: M-35 QoL run — review sheet, playtest checklist, options-menu plan
- 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
This commit is contained in:
1 parent
849ee512cd
commit
10b278311f
3 files changed
+328
No files matched your search
@@ -0,0 +1,89 @@
|
||||
# M-35 QoL playtest checklist
|
||||
|
||||
Everything on this branch is config-gated to prior behaviour, so the
|
||||
**first** thing to confirm is that nothing changed with a default
|
||||
`ge007.ini`. Then flip the new keys and check the feel.
|
||||
|
||||
Edit `data/ge007.ini` while the game is closed.
|
||||
|
||||
---
|
||||
|
||||
## 0. Regression (default config) — do first
|
||||
|
||||
- [ ] Delete `data/ge007.ini`, launch, quit. It rewrites with new keys:
|
||||
`[Input] MouseCaptureMode = 0`, `[Game] ScreenShakeIntensity = 1`.
|
||||
Existing keys keep their values.
|
||||
- [ ] Play a level normally (mouse grabbed on focus, as before). Mouse look,
|
||||
aim (RMB), menus — all exactly as the last build. **No behaviour change
|
||||
expected at defaults.**
|
||||
- [ ] Alt-tab away and back — cursor frees / re-grabs (unchanged).
|
||||
|
||||
## 1. Click-to-lock capture — `Input.MouseCaptureMode = 1`
|
||||
|
||||
- [ ] Launch. At the main menu the **OS cursor is visible and free**; moving
|
||||
the mouse moves the game crosshair 1:1 (see item 3).
|
||||
- [ ] Click in the window during a level → cursor hides, mouse-look engages.
|
||||
- [ ] Press **ESC** during a level → cursor reappears, mouse-look stops,
|
||||
you can move the pointer off the window. (ESC does *not* quit.)
|
||||
- [ ] Click back in → re-locks. First click only re-locks — it must **not**
|
||||
fire the weapon.
|
||||
- [ ] Open the pause/watch (Start), then a front-end menu → cursor frees
|
||||
automatically. Return to the stage → re-locks without a click.
|
||||
- [ ] Alt-tab out mid-level → cursor frees; alt-tab back → stays free until
|
||||
you click (Quake behaviour).
|
||||
- **Correct feel:** exactly like Quake/Nightdive remasters — click to play,
|
||||
ESC to get your cursor back.
|
||||
- **Tune:** none — it's a mode toggle.
|
||||
|
||||
## 2. Aim sensitivity — B3
|
||||
|
||||
- [ ] With `Input.MouseCaptureMode` either value, hold RMB (aim mode) and
|
||||
make small aiming corrections. Default is now `MouseAimSpeed = 16`
|
||||
(was 25).
|
||||
- **Correct feel:** you can settle the sight on a distant guard's head
|
||||
without overshooting; a fast flick still crosses the screen.
|
||||
- **Tune:** `Input.MouseAimSpeed` (1–100+). Up = faster. If hipfire turning
|
||||
now feels faster than aiming, raise `MouseAimSpeed` or lower
|
||||
`Input.MouseTurnSpeed` (default 100). `Input.AimBand` (5–40, default 20)
|
||||
widens the usable stick range past the aim gate — raise it if aim feels
|
||||
capped/notchy at the extremes.
|
||||
- [ ] Check the D166 hipfire pitch (mouse up/down without RMB): still
|
||||
proportional taps, consistent with the yaw. Tune
|
||||
`Input.HipfirePitchSpeed` (default 100).
|
||||
|
||||
## 3. Menu pointer 1:1 — needs `Input.MouseCaptureMode = 1`
|
||||
|
||||
- [ ] On the mission-select grid, move the OS cursor to each of the four
|
||||
**corner** level tiles — the game crosshair must reach all of them
|
||||
(this is the D169 case; absolute mapping should make it exact now).
|
||||
- [ ] File-select, mode-select, difficulty-select, watch menu — crosshair
|
||||
tracks the OS cursor with no drift, no lag, no acceleration.
|
||||
- [ ] Keyboard nav (arrows / stick) still works on every screen.
|
||||
- **Correct feel:** the crosshair sits exactly under the OS pointer, like a
|
||||
normal PC menu.
|
||||
- **Tune:** `Input.MenuPointerSpeed` (default 100) still scales the legacy
|
||||
relative path; with capture mode the absolute path ignores it. If tracking
|
||||
feels off, check the window isn't a weird aspect ratio (the map assumes the
|
||||
render fills the window).
|
||||
- **Note:** in legacy mode (`MouseCaptureMode = 0`) the menu still uses the
|
||||
D165/D169 relative estimator — 1:1 menu tracking requires capture mode.
|
||||
|
||||
## 4. Screen shake — `Game.ScreenShakeIntensity`
|
||||
|
||||
- [ ] Default `1` — explosions shake the screen exactly as before.
|
||||
- [ ] Set `0` — no shake on explosions / big hits.
|
||||
- [ ] Set `2`–`3` — exaggerated.
|
||||
- **Correct:** `1` is indistinguishable from the current build.
|
||||
|
||||
---
|
||||
|
||||
## Single most important thing to test first
|
||||
|
||||
**Item 0** — confirm a default `ge007.ini` produces a build that plays
|
||||
identically to `def0e0ac`. If that holds, everything else is opt-in polish.
|
||||
|
||||
## Known not-done (parked, see OPTIONS-MENU-PLAN.md)
|
||||
|
||||
- No in-game options **menu** yet — only the config file + the expanded
|
||||
`config.c` API and the `Game.ScreenShakeIntensity` hook. The menu is a
|
||||
port-layer overlay design, documented, not built.
|
||||
@@ -0,0 +1,132 @@
|
||||
# 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 **un**grabbed (`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`). `configSave` rewritten 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's `optionsmenu.c`
|
||||
that 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 |
|
||||
@@ -0,0 +1,107 @@
|
||||
# In-game PC options menu — design + resume point
|
||||
|
||||
Status: **config-system foundation landed (M-35); menu surface not built.**
|
||||
Pattern follows `docs/dev/AUDIO-PLAN.md` — a plan doc that a later session
|
||||
executes.
|
||||
|
||||
Findings context: D180 (input), D181 (first route-(b) hook). Config API:
|
||||
`configRegister{Int,UInt,Float,String}` with clamps (`port/include/config.h`).
|
||||
|
||||
---
|
||||
|
||||
## 1. What PD does, and why it doesn't port directly
|
||||
|
||||
PD's `pd_port/port/src/optionsmenu.c` (~2000 lines) hooks port-side handlers
|
||||
into **PD's own decomp menu tables** (`menudata`/`menuitem` arrays with
|
||||
`MENUITEMTYPE_SLIDER` etc.). It works because PD's front end is a generic
|
||||
data-driven list menu.
|
||||
|
||||
GE is different. GE's settings UI is the **in-game watch** (`src/game/options.c`):
|
||||
`draw_watch_game_options_page` / `draw_watch_control_options_page` +
|
||||
`watch_screenN_navigation` — hand-drawn pages with bespoke nav functions,
|
||||
not a table. The front-end menus (`front.c`, `initmenus.c`, `mpmenu.c`) are
|
||||
also bespoke draw/nav pairs, not a reusable list widget.
|
||||
|
||||
So there is **no table to inject rows into**. A PC options surface is either
|
||||
(A) a new hand-built watch page, (B) a new hand-built front-end screen, or
|
||||
(C) a port-layer overlay drawn outside the game's menu system.
|
||||
|
||||
## 2. Recommended approach: (C) port-layer overlay
|
||||
|
||||
A self-contained immediate-mode overlay in `port/`, toggled by a hotkey
|
||||
(e.g. `F10`), drawn as fast3d 2D quads + the game's own `textRender` over
|
||||
the top of whatever is on screen, with keyboard/mouse nav handled entirely
|
||||
in the port layer. Reasons:
|
||||
|
||||
- **Zero `src/` menu-code edits.** No new bespoke nav function to get wrong
|
||||
(the watch-nav code is exactly the D118d / over-scroll family).
|
||||
- Works in-level *and* in the front end (it's above the game).
|
||||
- The values it edits are already port-owned `config.c` variables — the
|
||||
overlay is just a view over the registered option list.
|
||||
- Precedent: the port already draws port-owned UI (FPS in the window title,
|
||||
F12 screenshot). This is the render-side equivalent.
|
||||
|
||||
Cost: an immediate-mode widget layer (label, slider, toggle, dropdown) in
|
||||
fast3d 2D. ~300–500 lines. The `config.c` registry already gives us
|
||||
key/min/max/value + a type tag (after M-35) — enough to auto-generate rows.
|
||||
|
||||
### Sketch
|
||||
|
||||
```
|
||||
port/src/optionsoverlay.c
|
||||
optionsOverlayToggle() // F10 in video.c event pump
|
||||
optionsOverlayHandleInput() // called from inputComputePad idx 0 when open:
|
||||
// swallow kbd/mouse from the game, drive the cursor
|
||||
optionsOverlayRender(Gfx**) // called from a fast3d 2D hook after the game DL
|
||||
```
|
||||
|
||||
Registry additions needed in `config.c`:
|
||||
- `configForEachOption(cb)` — iterate {key, type, ptr, min, max}.
|
||||
- optional per-option metadata: display label, step, enum-value names,
|
||||
"apply live" vs "needs restart". Add a `configRegisterIntEx(...)` variant
|
||||
or a side table keyed by dotted key.
|
||||
|
||||
Live-apply: most keys already re-read their variable every frame
|
||||
(`mouseAimSpeed`, `portScreenShakeScale`, `cfgVSync` via `videoSetVSync`).
|
||||
Ones that need a hook (MSAA, fullscreen) get an `configOnChange` callback
|
||||
or are flagged "restart".
|
||||
|
||||
## 3. Minimum option set for v1
|
||||
|
||||
| Row | Key | Type | Notes |
|
||||
|---|---|---|---|
|
||||
| VSync | `Video.VSync` | toggle | live (`SDL_GL_SetSwapInterval`) |
|
||||
| Frame cap | `Video.FpsCap` | int slider 0–360 | live |
|
||||
| MSAA | `Video.MSAA` | dropdown 1/2/4/8 | needs FBO rebuild — flag "restart" for v1 |
|
||||
| Texture filter | `Video.TextureFilter` | dropdown nearest/bilinear/3-point | live-ish |
|
||||
| Mouse aim speed | `Input.MouseAimSpeed` | slider 1–100 | live |
|
||||
| Mouse turn speed | `Input.MouseTurnSpeed` | slider 1–100 | live |
|
||||
| Mouse invert Y | `Input.MouseInvertY` | toggle | live |
|
||||
| Capture mode | `Input.MouseCaptureMode` | toggle | live |
|
||||
| Screen shake | `Game.ScreenShakeIntensity` | slider 0–3 (×) | live (D181) |
|
||||
| Screenshot key | — | (F12, documented; rebind = later) | — |
|
||||
|
||||
`configSave()` on overlay close.
|
||||
|
||||
## 4. Alternative if an in-fiction surface is required later
|
||||
|
||||
Route (B): a new front-end screen `constructor_menuXX_pcoptions` +
|
||||
`menuXX_pcoptions_navigation` in `front.c` under `#ifdef PORT`, reached from
|
||||
a new "PC OPTIONS" row on the main options menu. Each row calls a
|
||||
`configGet/Set` shim. This is a genuine route-(b) `src/` edit (menu content
|
||||
change) → its own Dxx, N64 verbatim under `#else`, opt-in. More faithful,
|
||||
more surface area, more nav bugs. Defer unless the owner wants it in-fiction.
|
||||
|
||||
## 5. Resume checklist
|
||||
|
||||
1. Add `configForEachOption` + a label/step/enum side-table to `config.c`.
|
||||
2. Build the fast3d 2D immediate-mode widget helpers (reuse `gDPFillRectangle`
|
||||
+ `textRender`; see how `options.c draw_watch_game_options_page` emits
|
||||
text for the font/`Gfx**` conventions).
|
||||
3. `port/src/optionsoverlay.c` state machine + F10 toggle in `video.c`.
|
||||
4. Route input: when open, `inputComputePad` idx 0 returns a neutral pad and
|
||||
forwards nav to the overlay (same "swallow" pattern WI-1 uses for the
|
||||
free-cursor click).
|
||||
5. `configOnChange` hooks for MSAA/fullscreen; everything else is live.
|
||||
6. Verify: overlay open/close in-level and in the front end; golden dumps
|
||||
unaffected with the overlay closed (it draws nothing).
|
||||
Reference in new issue
Block a user