Files
James Dansereau fe57d73924 optionsoverlay/watchsettings/frontoptions: D356 settings regroup — functional sections, save-file scoping, per-section reset-to-defaults (Gate E)
- 5 sections (INPUT/GAMEPLAY/GRAPHICS/AUDIO/VIDEO, all below MAX_PROWS):
  DISPLAY+GRAPHICS merged into GRAPHICS; AUDIO carries the watch music/FX
  sliders out of the Bond-file page; VIDEO keeps the pure video rows;
  __HdrMouse/__HdrPad merged into INPUT; __HdrBond/__BondFile retired for
  the front-only top save-file row (frontoptions.c)
- save-file scoping: per-file sections (GAMEPLAY, AUDIO) annotated
  '(File N)'/'(no file)' via the new Row.saveScoped flag +
  watchSettingsActiveFolder(); per-file rows in mixed sections carry a dim
  '(save)' tag; the D353 optionsRowIsBondChooser special-case is
  generalized (both UIs)
- N64-watch rows Bond.Look / Bond.AimControl commented out of the menu
  surface (D181/D216 style: keys + watch paths stay live)
- per-section 'Reset to defaults' replaces the Turok rows (plan Gate E):
  edge-triggered two-step arm->confirm within 3 s, one commit, disarm on
  timeout / navigate-away / close (optionsResetMaintain/Clear);
  rowResetSection walks the section's declared range: watch rows ->
  BLANKSAVEDATA through the D352 commit path (front direct, stage queue),
  ini rows -> kResetDefaults[] (the port's C initializers); __Resolution
  is a documented exclusion (action-backed)
- GE_WSPROBE_RESET probe: front tick-120 prepare/dispatch/verify, stage
  tick-120 dispatch / tick-121 verify; a direct -level_XX boot starts its
  timer from phase 0 (front prepare is front-only)
- docs: findings.md D356 + index + CSV, WATCH-SETTINGS-PLAN status,
  OPTIONS-MENU-PLAN pointer, plan doc status -> LANDED

Verified headless: GE_WSPROBE_RESET front (all 5 sections failures=0,
__Resolution untouched, file-2 scope isolation OK) and -level_33 (6
commands queued, all sections verified post-drain, saved music/sfx = 0xFF);
GE_WSPROBE / GE_WSPROBE_FRONT regressions clean; 248/248 build + link.
2026-09-27 14:49:08 -04:00

119 lines
5.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# In-game PC options menu — design + resume point
Status: **config-system foundation landed (M-35); menu surface not built.**
(Updated 2026-09-27: the menu surface *has* since been built and iterated
as D346 -> D356; the current layout and per-section reset live in
`docs/dev/D356-SETTINGS-REGROUP-PLAN.md` + `docs/dev/WATCH-SETTINGS-PLAN.md`
and `docs/dev/findings.md` D346–D356. This doc remains the historical
M3-scope record.)
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
> **2026-09-27: route B chosen (v0.4.0 intake).** The entry point is on the
> file select screen, not a main options menu (GE's front end has none). M1
> landed as D343: an "Options" label that opens the F10 overlay, via one
> Rule-2 `#ifdef PORT` draw hook. M2 (a GE-styled screen that reuses the
> overlay's rows) follows.
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).