Files
goldeneye-pc-port/docs/dev/WATCH-SETTINGS-PLAN.md
T

14 KiB
Raw Blame History

Watch-backed settings in PC Options and F10 — proposal for review

Status: watch-backed subset implemented (D350), robustness fixes landed (D352: transition hold-not-drop, chooser spinlock, queue coalescing, warning logs), and the Turok-style regrouping is done (D353: DISPLAY/GRAPHICS split, BOND FILE absorbs the music/FX sliders, designated rows, overflow guard, file-scope title annotation). Wider M3 remains outstanding: master volume, window-mode/display rows, per-section reset (Gate E), key rebinding. PC-only per-file persistence and explicit Bond-file chooser approved; details below. See docs/dev/findings.md D350–D353 for verification. This is the watch-integration part of M3, not a replacement for the native watch menu. The older tracked docs/dev/OPTIONS-MENU-PLAN.md and local docs/dev/notes/OPTIONS-MENU-PLAN.md describe the wider M3 scope (master volume, window mode/display, resets).

Goal and ownership

Show GE's existing watch settings in both PC settings surfaces (MENU_PC_OPTIONS and F10) without maintaining a second copy of each value. Both surfaces already consume one row table in port/src/optionsoverlay.c; the watch remains an independent UI editing the same underlying GE value. Changes made in either direction must be visible on the next read. Preserve the N64 watch, its behavior, and the N64 build.

Approved ownership: per-file GE settings belong in the Bond save; machine-wide PC settings belong in ge007.ini. GE's original watch edits are not persisted on normal play (D349 below): watch-made changes remain session-only, while PC-UI-made changes persist to the selected Bond file. This is a deliberate port-side QoL divergence, not existing GE behavior. Do not register watch-backed settings as ini variables, copy watch values into config globals, or overwrite a newly loaded Bond file with stale ini values. A proposed PC master volume is separate: it multiplies the final output and does not replace the watch's Music/FX volumes.

Inventory and proposed rows

Source of truth: src/game/options.c (game_options_entries, cur_player_get/set_*, get_mTrack2Vol/set_mTrack2Vol, sub_GAME_7F0A91A0), src/game/file2.c:1246-1350 (per-folder serialization), and port/src/input.c:~939 (forced control style). Audit exact getter/setter availability and initialization before implementing each row.

Watch control PC presentation Notes
Music / FX volume Audio section, separate Music and FX sliders GE range 0..32767, stored in the Bond save at 8-bit precision; use actual GE APIs, not an SDL channel gain in place of either. Master volume stays a distinct global row.
Look up/down (reverse/upright), auto-aim, aim control (hold/toggle), sight on screen, look ahead, ammo on screen Bond file / gameplay subsection, with GE-specific labels These are the watch's game_options_entries and save bits. Watch look-up/down is distinct from PC MouseInvertY and PadLookInvertY: the independent inversions compose (XOR). Label/help text must explain the stacking; do not combine their stored values.
Screen (Full/Wide/Cinema), Ratio (Normal/16:9) Separate clearly marked GE presentation controls, if useful Not PC window resolution/mode or Video.NativeWidescreen. Verify interactions with native widescreen/crop before exposing.
Controller style / controller diagram Defer pending input design The diagram is not a setting. inputComputePad(0) currently reasserts Honey or Solitaire from Input.NaturalPitch every poll, including after save load or watch changes; bondview.c and multiplayer load also force Honey in some contexts. A naive style row would be immediately undone. Decide whether the watch selector should be informational, limited to supported PC mappings, or integrated with the port mapping; no row until this is reconciled. Dual-controller styles need their own decision.

This inventory is not permission to expose every field automatically. Especially do not silently merge identically named PC and GE toggles that have different input paths or persistence.

Persistence ground truth (D349)

fileSaveSettingsForFolder (src/game/file2.c:1246) has one caller, fileClearSavefileForFolder (:1379), reached from the erase path. Normal watch edits change live values but do not write the file's options/music_vol/sfx_vol fields or EEPROM. init_watch_at_start_of_stage (src/game/options.c:415) calls fileLoadSaveSettingsForSelectedFolder, so the next stage reloads those fields and discards unsaved watch edits. This is a GE/N64 quirk, not a PC regression; Gate A should re-audit indirect writes before implementation and record any correction to D349.

Thus a front-end row that edits only live watch state will be silently undone before gameplay. The maintainer approved port-side per-file persistence of PC-UI changes, without changing the N64 save format or the N64 watch's session-only behavior. Do not auto-persist edits made directly in the watch.

UI / data design

  1. Add a watch-backed row type to the existing shared row model, not another settings menu or fake config registration. Its read/write/availability callbacks operate on GE's real option API; existing config-backed rows continue to work unchanged. Keep enum labels, range/step and value formatting in the shared row table so F10 and the GE-styled screen cannot diverge.
  2. Use GE's existing getters/setters for all eight proposed entries (src/game/options.c:526-598) and both volume controls (get_mTrack2Vol/set_mTrack2Vol, call_sndGetSfxSlotFirstNaturalVolume/sub_GAME_7F0A91A0). For FX use the same game volume API as the watch, not an SDL channel gain. No src/game bridge or Rule-2 game-code edit is expected for these rows. Preserve the watch's side effects; persistence, if approved, is a separate port-side operation.
  3. Threading is a design gate: F10 input runs on the scheduler thread (inputComputePad(0)); the GE-styled screen and watch run on the game thread. Do not directly mutate game/watch state from the F10 thread merely because a getter or pointer is accessible. Route F10 writes as bounded requests to a safe game-thread point, and publish a safe, current snapshot for F10 rendering/reads, or establish an equivalently reviewed synchronization scheme. Cancel or retarget queued writes when the active folder/stage changes; never apply a write to the next folder by accident. Check audio/music initialization before writing Music/FX in the front end: the music getter reads a global, but the setters may touch not-yet-ready playback state.
  4. Make scope visible to the player: use a dedicated Bond file section/page in MENU_PC_OPTIONS for GE watch options, with an explicit Bond-file chooser; never trust selected_folder_num on that screen (frontoptions init even assigns it a fallback). Do not silently choose folder 1. F10 in-stage targets the active file with no chooser. Switching chooser folders must not change the game's active file. D350 reads saved fields directly without calling fileLoadSettingsForFolder (which would audibly change front-end music). Unvalidated/absent slots are skipped. Test new/blank files in a normal boot as well as the D299 GE_STARTMENU shortcut.
  5. Approved port-side persistence: resolve the actual save-slot pointer with fileGetSaveForFoldernum(folder); saves[folder] is not generally the folder's physical slot (fileWriteSave requires a pointer into saves[SAVESLOT1..SAVESLOTRAMROM)). For F10, use a validated in-place path: serialize the live settings into a temporary copy with fileSaveSettingsForFolder(&copy), then copy only the PC-UI-edited field/bit(s) into the real save-slot pointer and call fileWriteSave(save) with existing CRC/EEPROM handling. In the front end, where no GE player is active, merge only the selected field directly into the chosen saved record instead of loading it into live watch state. Retain OPTION_CONTROLTYPE from the real save: the port reasserts Honey/Solitaire every poll and must not overwrite stored controller style. This field-scoped merge also prevents a watch-only edit to a different field from piggybacking onto the next PC-UI save, preserving the approved session-only asymmetry. Validate the path against absent/free slots and ensure the live state belongs to only the chosen folder (do not load or copy values from a different folder). Never call fileClearSavefileForFolder as a generic settings save. Toggle/enum changes persist on value change; slider drags persist on release, not each detent. F10 requests must reach the correct game-thread/file target before write. The GE watch's own edits remain session-only. Test stage load, file switch, copy/erase, and restart for cross-file contamination; no N64 game-code changes. Known erase edge: GE's fileClearSavefileForFolder serializes current live settings into the reset file; after editing B, erasing A could copy B's live options into wiped A (also possible on N64 after watch edits). Document/test, do not change game code.
  6. For future per-section reset-to-defaults: ini rows reset from registered PC defaults; watch rows reset to BLANKSAVEDATA/DEFAULT_OPTIONS (bondconstants.h:2151, file2.h:23) for the selected file, not from a global ini snapshot. New saves start with Music/FX 0xFF (full volume) and auto-aim/sight/look-ahead/ammo enabled. Separate confirmation and persistence policy before wiring a button.

Sequence / review gates

  • Gate A — behavior audit + D349: re-verify the erase-only write and stage-start reload (including indirect save paths); record the ground-truth quirk, defaults, stored control-type preservation, erase cross-file edge and exact API for each row. Trace folder-to-physical-slot mapping, FX/music setter readiness, watch-to-gameplay consumers, composed inversion and the F10/game-thread boundary. Approved policy: explicit chooser + PC-UI edits persist; watch edits remain session-only.
  • Gate B — foundation: IMPLEMENTED in D350, including explicit file chooser, CRC guard, stage/file identity and thread-safe F10 command/snapshot path. D352 (review of 1303c0eb) fixed the remaining robustness gaps: pending commands now survive stage transitions (hold-not-drop), the UI-thread chooser state is spinlocked with a front-screen guard, the queue coalesces to one pending command per field, and every silent no-op path logs a warning. Manual gamepad/mouse playtest still owed.
  • Gate C — audio: ROWS IMPLEMENTED in D350, Music/FX save path headless-verified. Watch↔F10 live and audible checks still owed. Global master gain is a separate, not-yet-implemented M3 item.
  • Gate D — watch gameplay: ROWS IMPLEMENTED in D350 for six GE watch booleans; auto-aim and watch look-bit per-file saves headless-verified. Each watch/in-game effect and two-file isolation still need hands-on checks. Screen/Ratio remain a separate product decision.
  • Gate E — control style and resets: design decisions first, then separate work. Avoid exposing a control-style row that the port immediately overwrites. Do not make a global reset wipe per-file watch settings.

Acceptance checks

  • Watch changes show in both PC surfaces without reopening/restarting, and PC changes show in the watch; no extra watch-key values in ge007.ini.
  • With two Bond files, change Music/FX and a gameplay toggle in A via PC UI; switch to B, verify B retains its own values; return to A, start a stage, quit/restart and reload, verify A's values. Repeat an edit via F10 while in-stage. Drag sliders: only one save on release, not per detent. PC-UI persistence is a port QoL test, not a claim about N64; watch-only edits should still revert on stage reload. Check that stored controller-style bits survive PC saves and that file copy and erase retain GE behavior (including the erase cross-file edge above).
  • F10 changes are not lost or applied to another file across a stage/menu transition. Check full-range sliders, watch's save quantization, no unexpected audio spikes, and no race/crash on quit.
  • Menu layouts: use a dedicated Bond file section/page plus Audio; MAX_PROWS=15 is a per-page cap in frontoptions.c, with MAX_PAGES=8; F10 scrolls. Check the 440x330 front-end page, F10 visible rows, mouse, keyboard, and real pad navigation; ASCII-only new text for region safety. Check NTSC headless captures, widescreen, golden gate, and PAL/JP build where possible (only NTSC ROM locally).
  • Build/link checks and the normal verification ritual after implementation. Document any game-side change as its own Rule-2 request before editing it.

Decisions and remaining scope

  • Approved: PC-UI edits persist per selected Bond file; watch-only edits stay session-only. The front screen gets an explicit file chooser; F10 uses the active file. Preserve stored controller type; toggles/enums save on change, slider drags on release. No src/game behavior change or N64 save-format change.
  • Still separate product decisions: whether/how to expose GE Screen/Ratio alongside PC video settings; whether to expose control style at all given the port's forced mapping (deferred); whether Bond-file reset needs its own confirmation rather than being bundled with global PC defaults.
  • Landed 2026-09-27 (D352/D353, port-only): bridge robustness fixes (D352) and the Turok-style row-table regrouping (D353: six sections — DISPLAY, GRAPHICS, BOND FILE, MOUSE / AIM, CONTROLLER, GAME; the music/FX sliders sit in BOND FILE; the page title annotates the selected file; buildPages overflow logs). A standalone AUDIO section (master volume + the config-side volume keys) and per-section reset-to-defaults (Gate E) are the next settings-scope items; key rebinding stays Phase 4.