Add a game-thread bridge and field-scoped per-file persistence for PC UI edits, with an explicit file chooser; keep watch-only edits session-only. Fix D351 row initializer offsets for units and auto-hide. Document verification and review follow-ups.
13 KiB
Watch-backed settings in PC Options and F10 — proposal for review
Status: watch-backed subset implemented (D350), headless-verified; maintainer reports tests good, detailed review requested next session. Wider M3 remains outstanding. PC-only per-file persistence and explicit Bond-file chooser approved; details below. See docs/dev/findings.md D350 for verification and D351 for a pre-existing row initializer fix. 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
- 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.
- 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. Nosrc/gamebridge 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. - 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. - Make scope visible to the player: use a dedicated Bond file section/page in
MENU_PC_OPTIONSfor GE watch options, with an explicit Bond-file chooser; never trustselected_folder_numon 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 callingfileLoadSettingsForFolder(which would audibly change front-end music). Unvalidated/absent slots are skipped. Test new/blank files in a normal boot as well as the D299GE_STARTMENUshortcut. - Approved port-side persistence: resolve the actual save-slot pointer with
fileGetSaveForFoldernum(folder);saves[folder]is not generally the folder's physical slot (fileWriteSaverequires a pointer intosaves[SAVESLOT1..SAVESLOTRAMROM)). For F10, use a validated in-place path: serialize the live settings into a temporary copy withfileSaveSettingsForFolder(©), then copy only the PC-UI-edited field/bit(s) into the real save-slot pointer and callfileWriteSave(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. RetainOPTION_CONTROLTYPEfrom 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 callfileClearSavefileForFolderas 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'sfileClearSavefileForFolderserializes 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. - 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. 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=15is a per-page cap infrontoptions.c, withMAX_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/gamebehavior 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.