diff --git a/docs/dev/WATCH-SETTINGS-PLAN.md b/docs/dev/WATCH-SETTINGS-PLAN.md index a6042bd2..a3b39d93 100644 --- a/docs/dev/WATCH-SETTINGS-PLAN.md +++ b/docs/dev/WATCH-SETTINGS-PLAN.md @@ -1,6 +1,6 @@ # 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). +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 @@ -39,7 +39,7 @@ Thus a front-end row that edits only live watch state will be silently undone be ## 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 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. @@ -56,3 +56,4 @@ Thus a front-end row that edits only live watch state will be silently undone be - **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. diff --git a/docs/dev/findings-index.csv b/docs/dev/findings-index.csv index 4bc29512..8091a548 100644 --- a/docs/dev/findings-index.csv +++ b/docs/dev/findings-index.csv @@ -230,3 +230,4 @@ D349,PARTIAL,GE watch settings are not normally saved to the Bond file:,ANALYZED D350,PARTIAL,Watch-backed PC options:,"PARTIAL (implemented; user reports tests good, review pending):" D351,FIXED,Existing D346 value/unit rows had positional initializers shifted into hidePtr:,"FIXED (port-only): designated row initializers restore unit, deadzone divisor and hide-if-auto fields; build clean, headless smoke clean." D352,FIXED,D350 review of 1303c0eb:,"FIXED (port-only, port/src/watchsettings.c):" +D353,FIXED,Turok-style settings regrouping:,"FIXED (port-only, optionsoverlay.c/frontoptions.c):" diff --git a/docs/dev/findings.md b/docs/dev/findings.md index 35d72ea8..74b31902 100644 --- a/docs/dev/findings.md +++ b/docs/dev/findings.md @@ -638,6 +638,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D350 | **Watch-backed PC options: shared front/F10 Bond-file rows, explicit chooser, game-thread F10 bridge and field-scoped PC-only persistence.** — full `## D350` entry at file tail | PARTIAL (implemented; user reports tests good, review pending): headless front + F10 write/reload probes, NTSC build/golden 3/3, save CRC/control-style preserved; two-file targeting checked. Detailed edge-case coverage not specified by user. | | D351 | **Existing D346 value/unit rows had positional initializers shifted into `hidePtr`: %/x rows hidden or formatted without units.** — full `## D351` entry at file tail | FIXED (port-only): designated row initializers restore unit, deadzone divisor and hide-if-auto fields; build clean, headless smoke clean. Manual settings visual check owed. | | D352 | **D350 review of 1303c0eb: stage transitions silently drop queued F10 setting changes (queue drained before the `stageActive` check); the UI thread's chooser state is unsynchronized; rapid slider drags push one RSP command per detent and re-persist the same value each frame; persist failures are silent (2026-09-27).** — full `## D352` entry at file tail | FIXED (port-only, `port/src/watchsettings.c`): hold-not-drop at stage transitions, spinlock on chooser state + front-screen guard, per-field coalesced queue commits, warning logs on all no-op paths, `PersistField` made static. NTSC build clean, headless menu smoke clean. | +| D353 | **Turok-style settings regrouping: the D346 VIDEO mega-section (14–15 rows, at the MAX_PROWS limit) re-split into DISPLAY + GRAPHICS, the "AUDIO (BOND FILE)" hack header dropped (music/FX sliders fold into BOND FILE), all remaining rows converted to designated initializers, `buildPages` overflow now logs, and the Bond-file page title annotates the selected file (2026-09-27).** — full `## D353` entry at file tail | FIXED (port-only, `optionsoverlay.c`/`frontoptions.c`): 6 sections of 3–11 rows, all rows designated-initialized, `optionsRowIsBondChooser` accessor, build clean + headless smoke clean. Master-volume row, per-section reset (Gate E) and key rebinding remain in the M3 backlog. | Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software @@ -13806,3 +13807,15 @@ Design review of `1303c0eb` (D350) of the game-thread bridge in `port/src/watchs 4. **L3/L6 — visible failures, smaller API.** Every silent no-op path now logs a one-line `LOG_WARNING` (`frontoptions`/`watchSettings`-tagged): stage transition holding the queue (rate-limited by the coalescing), front-path commit with nothing staged, F10 commit with no valid save, no loaded player record, save absent from the EEPROM range. `watchSettingsPersistField` was only ever called inside `watchsettings.c` — made `static`, removed from the public header. Verification: NTSC build clean (no new warnings in `watchsettings.c`), headless menu smoke boots to the front end and renders (frames advancing, EEPROM load). Edge cases (setting F10 during a level transition; rapid slider drag; committing with an invalid file) are log-visible but need a manual pass to confirm by eye. + +## D353 — Turok-style settings regrouping: DISPLAY/GRAPHICS split, AUDIO(BOND FILE) header dropped, designated rows, overflow guard (2026-09-27) + +The D346 "single Video section" compromise (14–15 rows, exactly at `MAX_PROWS=15`) is re-grouped along the Turok/Nightdive menu axes (per `docs/dev/notes/OPTIONS-MENU-PLAN.md` §6), all port-side, no config-key renames: + +- **VIDEO → DISPLAY + GRAPHICS.** DISPLAY: fullscreen, resolution, vsync, frame-rate cap (the window-management block Turok's "Video" panel covers). GRAPHICS: MSAA, texture filter, anisotropy, FOV scale, native widescreen, auto-FOV pair, overscan crop, the two draw/LOD-distance sliders and their auto-FOV hide-ifs. 4 + 11 rows, both well under the page limit. +- **"AUDIO (BOND FILE)" hack header dropped.** The music/FX sliders now sit under the BOND FILE section, next to the other persisted-per-file watch rows — the section is exactly the per-file save, so the parenthetical header is redundant. No standalone master-volume row yet (that is the M3 addition, which will land in a future AUDIO section alongside the config-side volume keys). +- **Scope annotation.** The Bond-file page title carries the chooser's value ("Game\n(File 2)" / "(no file)" in dim ink), so a player navigating from the front end sees which save the section writes without scrolling to the "Edit file" row. `frontoptions.c` level-1 draw only; F10 shows the chooser row inline (its value already there) so no annotation is needed. +- **Full designated-initializer conversion.** The remaining positional `struct Row` entries (all of them) are now designated, per the D351 policy: new fields (like `found`) can never silently shift a value into the wrong member again. Keys, labels, clamps, steps, names, hide-ifs, units are all carried over unchanged — this is a display-only regroup, no value or config-key change. +- **Overflow guard + accessor.** `buildPages` logs a `LOG_WARNING` naming the section and the last visible row if a section ever outgrows `MAX_PROWS` (the D346 VIDEO section truncated silently at exactly 15 rows before this); `optionsRowIsBondChooser()` is exported so the front-end title annotation and future F10 code can find the chooser row by role instead of index. + +Section order: DISPLAY, GRAPHICS, BOND FILE, MOUSE / AIM, CONTROLLER, GAME (6 sections, under `MAX_PAGES=8`). Per-section "reset to defaults" (Gate E) and the §6-missing master-volume row remain outstanding. Verification: NTSC build clean, headless menu smoke boots to the front end; the regrouped page flow (arrows through 6 pages, annotation visible, no overflow log) needs a manual visual pass. diff --git a/port/include/optionsoverlay.h b/port/include/optionsoverlay.h index 9c0328fc..4dfd7715 100644 --- a/port/include/optionsoverlay.h +++ b/port/include/optionsoverlay.h @@ -51,6 +51,7 @@ int optionsRowIsHeader(int i); int optionsRowIsShown(int i); const char *optionsRowLabel(int i); int optionsRowIsSlider(int i); +int optionsRowIsBondChooser(int i); /* D353: the Bond-file chooser row */ int optionsRowNeedsRestart(int i); double optionsRowFraction(int i); void optionsRowSetFraction(int i, double f); diff --git a/port/src/frontoptions.c b/port/src/frontoptions.c index 59c4ce6a..51dff719 100644 --- a/port/src/frontoptions.c +++ b/port/src/frontoptions.c @@ -69,10 +69,11 @@ extern struct rectbbox folder_option_ERASE_bound; /* front.c:439 */ #define TITLE_Y 0x2B #define ROW_X 0x37 /* the cheat list's x */ #define ROW_Y0 0x41 /* cheat list starts 0x35; +12 for the title line */ -/* D346b: 20 -> 18 px so the merged Video section (14 rows default, 15 with - * "Draw dist. follows FOV" off) fits the paper on one page. Hit bands stay - * contiguous at any pitch >= 6; the 22px highlight box still clears the next - * row (y+14 < y+17). */ +/* D346b: 20 -> 18 px so a full section fits the paper on one page. Hit bands + * stay contiguous at any pitch >= 6; the 22px highlight box still clears the + * next row (y+14 < y+17). D353: the largest section is now GRAPHICS at 11 + * rows (both auto-FOV toggles off); MAX_PROWS=15 keeps headroom, and + * buildPages() logs any future overflow instead of silently truncating. */ #define ROW_DY 18 #define NUM_X 0x37 /* "1." on the section list */ #define SEC_X 0x4B @@ -169,11 +170,22 @@ static void buildPages(void) } s_rowN = 0; if (s_pageN > 0) { + int overflow = 0; for (int i = s_pageHdr[s_page] + 1; i < n && !optionsRowIsHeader(i); i++) { - if (optionsRowIsShown(i) && s_rowN < MAX_PROWS) { + if (!optionsRowIsShown(i)) continue; + if (s_rowN < MAX_PROWS) { s_rowIdx[s_rowN++] = i; + } else { + overflow++; } } + /* D353: the old code stopped at MAX_PROWS silently; a section that + * outgrew the page (the D346b VIDEO section did, at exactly 15) lost + * rows with no trace. */ + if (overflow) + sysLogPrintf(LOG_WARNING, "frontoptions: section '%s' has %d row(s) past MAX_PROWS=%d; last visible: '%s'", + optionsRowLabel(s_pageHdr[s_page]), overflow, MAX_PROWS, + optionsRowLabel(s_rowIdx[MAX_PROWS - 1])); } } @@ -434,9 +446,30 @@ Gfx *frontOptionsMenuDraw(Gfx *DL) } else { char title[32]; titleCase(optionsRowLabel(s_pageHdr[s_page]), title, sizeof(title)); + int titleW = measureW(title); strcat(title, "\n"); DL = ink(DL, ROW_X, TITLE_Y, title, INK); + /* D353: scope annotation -- the Bond-file section's title carries + * the chooser's value ("(File 2)" / "(no file)"), so the player sees + * which save the per-file toggles and sliders touch without scrolling + * to the "Edit file" row. */ + { + int bondRow = -1; + for (int k = 0; k < s_rowN; k++) { + if (optionsRowIsBondChooser(s_rowIdx[k])) { bondRow = k; break; } + } + if (bondRow >= 0) { + char vt[32] = "", note[40]; + optionsRowValueText(s_rowIdx[bondRow], vt, sizeof(vt)); + if (vt[0] && strcmp(vt, "Select file") != 0) + snprintf(note, sizeof(note), "(%s)", vt); + else + snprintf(note, sizeof(note), "(no file)"); + DL = ink(DL, ROW_X + titleW + 8, TITLE_Y, note, INK_DIM); + } + } + for (int k = 0; k < s_rowN; k++) { int i = s_rowIdx[k]; int y = rowY(k); diff --git a/port/src/optionsoverlay.c b/port/src/optionsoverlay.c index 0913ea46..a20d5e32 100644 --- a/port/src/optionsoverlay.c +++ b/port/src/optionsoverlay.c @@ -142,27 +142,32 @@ struct Row { }; static struct Row rows[] = { - { "__HdrDisplay", "VIDEO", ROW_HEADER, 0, NULL, 0, 0, 0, 0,0,0,0,0 }, - { "Video.Fullscreen", "Fullscreen", ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, - { "__Resolution", "Resolution", ROW_RES, 0, NULL, 0, 0, 0, 0,0,0,0,0 }, - { "Video.VSync", "VSync", ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, - { "Video.FpsCap", "Frame rate cap", ROW_FPSCAP, 0, NULL, 0, 0, 0, 0,0,0,0,0 }, - { "Video.MSAA", "Anti-aliasing", ROW_MSAA, 0, NULL, 1, 0, 0, 0,0,0,0,0 }, - { "Video.TextureFilter", "Texture filter", ROW_ENUM, 1, kTexFilter, 0, 0, 0, 0,0,0,0,0 }, + /* D353: Turok-style regrouping. The D346b single 15-row VIDEO section + * sat exactly at the front page's MAX_PROWS cap (a new row would have + * been silently truncated). Split into DISPLAY (presentation) and + * GRAPHICS (quality + distance); sections now top out at 11 rows + * (GRAPHICS with both auto-FOV toggles off). All rows use designated + * initializers (D351 class: positional shifts into hidePtr/unit). */ + { .key="__HdrDisplay", .label="DISPLAY", .kind=ROW_HEADER }, + { .key="Video.Fullscreen", .label="Fullscreen", .kind=ROW_TOGGLE, .step=1, .names=kOnOff }, + { .key="__Resolution", .label="Resolution", .kind=ROW_RES }, + { .key="Video.VSync", .label="VSync", .kind=ROW_TOGGLE, .step=1, .names=kOnOff }, + { .key="Video.FpsCap", .label="Frame rate cap", .kind=ROW_FPSCAP }, + { .key="__HdrGraphics", .label="GRAPHICS", .kind=ROW_HEADER }, + { .key="Video.MSAA", .label="Anti-aliasing", .kind=ROW_MSAA, .restart=1 }, + { .key="Video.TextureFilter", .label="Texture filter", .kind=ROW_ENUM, .step=1, .names=kTexFilter }, { .key="Video.Anisotropy", .label="Anisotropic filtering", .kind=ROW_SLIDER, .step=1, .unit="x" }, - /* D346b: the old VIEW section (FOV / widescreen / draw distance) is merged - * into VIDEO -- one Nightdive-style "video" section; rows keep order. */ { .key="Video.FovScale", .label="FOV scale", .kind=ROW_SLIDER, .step=5, .unit="%" }, /* D334: native widescreen (world projected at the window aspect, Hor+). * While on, "Widescreen auto FOV" has no effect (it was the stretch-era * vertical-FOV compensation). */ - { "Video.NativeWidescreen", "Native widescreen",ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, - { "Video.WidescreenAuto", "Widescreen auto FOV",ROW_TOGGLE,1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, - { "Video.SafeAreaCrop", "Crop overscan", ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, + { .key="Video.NativeWidescreen", .label="Native widescreen", .kind=ROW_TOGGLE, .step=1, .names=kOnOff }, + { .key="Video.WidescreenAuto", .label="Widescreen auto FOV", .kind=ROW_TOGGLE, .step=1, .names=kOnOff }, + { .key="Video.SafeAreaCrop", .label="Crop overscan", .kind=ROW_TOGGLE, .step=1, .names=kOnOff }, { .key="Video.DrawDistance", .label="Draw distance", .kind=ROW_SLIDER, .step=25, .hiddenIfOn="Video.DrawDistanceAutoFov", .unit="%" }, - { "Video.DrawDistanceAutoFov","Draw dist. follows FOV",ROW_TOGGLE,1,kOnOff, 0, 0, 0, 0,0,0,0,0 }, + { .key="Video.DrawDistanceAutoFov", .label="Draw dist. follows FOV", .kind=ROW_TOGGLE, .step=1, .names=kOnOff }, { .key="Video.LodDistance", .label="LOD distance", .kind=ROW_SLIDER, .step=25, .hiddenIfOn="Video.LodDistanceAutoFov", .unit="%" }, - { "Video.LodDistanceAutoFov", "LOD dist. follows FOV",ROW_TOGGLE,1,kOnOff, 0, 0, 0, 0,0,0,0,0 }, + { .key="Video.LodDistanceAutoFov", .label="LOD dist. follows FOV", .kind=ROW_TOGGLE, .step=1, .names=kOnOff }, /* Aim row edits Input.AimModeSens -- the knob the default GEPD aim path * actually uses (Input.MouseAimSpeed only feeds the legacy velocity-stick * fallback, so it was inert here). D304: both this row and the turn-speed @@ -206,14 +211,16 @@ static struct Row rows[] = { .names=kOnOff, .found=1, .uiMax=1, .cfgMax=1 }, { .key="Bond.Ammo", .label="Ammo on screen", .kind=ROW_TOGGLE, .names=kOnOff, .found=1, .uiMax=1, .cfgMax=1 }, - { .key="__HdrBondAudio", .label="AUDIO (BOND FILE)", .kind=ROW_HEADER, .found=1 }, + /* D353: the separate "AUDIO (BOND FILE)" header is retired -- the two + * sliders belong to the same per-file section as the watch toggles. A + * port-level master volume (M3) will start a real AUDIO section. */ { .key="Bond.Music", .label="Music volume", .kind=ROW_SLIDER, .step=128, .uiMax=32767, .cfgMax=32767, .found=1, .unit="%", .dispDiv=328 }, { .key="Bond.FX", .label="FX volume", .kind=ROW_SLIDER, .step=128, .uiMax=32767, .cfgMax=32767, .found=1, .unit="%", .dispDiv=328 }, - { "__HdrMouse", "MOUSE / AIM", ROW_HEADER, 0, NULL, 0, 0, 0, 0,0,0,0,0 }, - { "Input.MouseSensitivity", "Mouse sensitivity",ROW_SLIDER, 5, NULL, 0, 0, 0, 0,0,0,0,0 }, - { "Input.MouseInvertY", "Invert look (mouse)",ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, + { .key="__HdrMouse", .label="MOUSE / AIM", .kind=ROW_HEADER }, + { .key="Input.MouseSensitivity", .label="Mouse sensitivity", .kind=ROW_SLIDER, .step=5 }, + { .key="Input.MouseInvertY", .label="Invert look (mouse)", .kind=ROW_TOGGLE, .step=1, .names=kOnOff }, /* Input.PdMouseAim (findings D332): the Perfect Dark port's mouse-aim * model. The port only accumulates the mouse; the game's own crosshair * integrator is driven by the port-supplied turn with PD's near-zero damp @@ -224,14 +231,14 @@ static struct Row rows[] = { * with the mouse fed through the game's integrator at PD's mouse damp -- * the default. CENTRED (PC) = opt-in FPS-style aim (#104), not N64. Applies * to every aim input (RMB, Shift, Q/L, pad trigger, Toggle mode). */ - { "Input.AimMode", "Aim style", ROW_ENUM, 1, kAimMode, 0, 0, 0, 0,0,0,0,0 }, + { .key="Input.AimMode", .label="Aim style", .kind=ROW_ENUM, .step=1, .names=kAimMode }, /* D338: how far the N64-style crosshair travels. PC = to the screen edge * (GEPD / mouse-injector feel, default); N64 = the original stick limits * (65% of the half-width, camera turn from ~49%). Hidden while the aim * style is CENTRED (PC), where the crosshair doesn't travel. */ - { "Input.AimRange", "Aim range", ROW_ENUM, 1, kAimRange, 0, 0, 0, 0,0,0,0,0, "Input.AimMode" }, - { "__HdrPad", "CONTROLLER", ROW_HEADER, 0, NULL, 0, 0, 0, 0,0,0,0,0 }, - { "Input.PadLookInvertY", "Invert look (controller)",ROW_TOGGLE,1,kOnOff, 0, 0, 0, 0,0,0,0,0 }, + { .key="Input.AimRange", .label="Aim range", .kind=ROW_ENUM, .step=1, .names=kAimRange, .hiddenIfOn="Input.AimMode" }, + { .key="__HdrPad", .label="CONTROLLER", .kind=ROW_HEADER }, + { .key="Input.PadLookInvertY", .label="Invert look (controller)", .kind=ROW_TOGGLE, .step=1, .names=kOnOff }, { .key="Input.PadDeadzone", .label="Stick deadzone", .kind=ROW_SLIDER, .step=500, .unit="%", .dispDiv=300 }, { .key="Input.PadTriggerPct", .label="Trigger threshold", .kind=ROW_SLIDER, .step=1, .unit="%" }, /* D181/Game.ScreenShakeIntensity: user testing (v0.2.1) found the slider @@ -243,13 +250,13 @@ static struct Row rows[] = { * hook stay in place. */ /* D232: the community "no damage flash" toggle (suppresses the red/green * hit-flash overlay in bondview2). */ - { "__HdrGame", "GAME", ROW_HEADER, 0, NULL, 0, 0, 0, 0,0,0,0,0 }, + { .key="__HdrGame", .label="GAME", .kind=ROW_HEADER }, /* D226: scales the ammo counter, pickup / status messages and dialogue * about their screen anchors. 100% = original, nothing emitted. */ { .key="Game.HudScale", .label="HUD scale", .kind=ROW_SLIDER, .step=5, .unit="%" }, - { "Game.SkipIntro", "Skip intro", ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, - { "Video.DisplayFPS", "Show FPS", ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, - { "Game.NoHitFlash", "No hit flash", ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, + { .key="Game.SkipIntro", .label="Skip intro", .kind=ROW_TOGGLE, .step=1, .names=kOnOff }, + { .key="Video.DisplayFPS", .label="Show FPS", .kind=ROW_TOGGLE, .step=1, .names=kOnOff }, + { .key="Game.NoHitFlash", .label="No hit flash", .kind=ROW_TOGGLE, .step=1, .names=kOnOff }, /* D216/Game.SkipIntro: user report (v0.2.1 testing) that it breaks audio * -- pulled from the menu until root-caused. Not exposed to players; the * config var + lv.c hook stay in place (dead unless an existing ini has @@ -257,12 +264,12 @@ static struct Row rows[] = { * fixing the underlying issue first. */ /* D257: everything-unlocked goodie (default ON). Consumed at startup by * main.c -- applies from the next launch. */ - { "Game.AllUnlocked", "All unlocked", ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, + { .key="Game.AllUnlocked", .label="All unlocked", .kind=ROW_TOGGLE, .step=1, .names=kOnOff }, /* D293: only quit path used to be the OS window-close / Alt+F4 -- no * discoverable in-game way to exit, a real gap on Deck/controller-only * setups. Not config-backed (like __Resolution); activating it exits * the same way video.c's SDL_QUIT/Alt+F4 handlers already do. */ - { "__QuitToDesktop", "Quit to desktop", ROW_ACTION, 0, NULL, 0, 0, 0, 0,0,0,0,0 }, + { .key="__QuitToDesktop", .label="Quit to desktop", .kind=ROW_ACTION }, }; #define NUM_ROWS ((int)(sizeof(rows) / sizeof(rows[0]))) @@ -1269,6 +1276,13 @@ int optionsRowIsSlider(int i) return r && r->kind == ROW_SLIDER && rowHi(r) > rowLo(r); } +/* D353: the explicit Bond-file chooser row (front options screen only). */ +int optionsRowIsBondChooser(int i) +{ + struct Row *r = rowAt(i); + return r && r->kind == ROW_BOND_FILE; +} + int optionsRowNeedsRestart(int i) { struct Row *r = rowAt(i);