v0.5.0: settings UI, control styles, save import, documentation

PD-style F10 menu with six groups, checkbox/slider/dropdown controls (D504),
wording pass and real-unit sliders (D505-D509), overlay/FPS-counter window
scaling (D510, D512), small settings (D511), control scheme Ext/Original
(D513) with the game's own N64 styles (D516-D518), emulator-format save
import on first launch (D514), build arch tag (D515), converter argv quoting
and sidecar size check, one PC options UI (D519) with by-eye follow-ups,
antialiased overlay sliders (D520), README settings paths, ROADMAP trim,
D441 tier-1 all-level sweep (21/21 pass).

Refs: D503-D520 D349 D441
This commit is contained in:
James Dansereau committed 2026-10-06 18:35:43 -04:00
1 parent daa2522511
commit aa5a1b81fe
36 files changed
+2911 -1812

No files matched your search

+4 -5
View File
@@ -111,14 +111,13 @@ Two classes:
| `GE_BINDCAPTUREPROBE` (+ `GE_BINDCAPTUREACTION`) | `port/src/optionsoverlay.c:1000,1005` | D383 isolated host→menu smoke probe: when the overlay force-opens, injects a scripted bind-capture keypress (ESC/DEL/F2). Test data dir must be private — capture persists the key. | **live** (diagnostic) |
| `GE_OPTIONSOVERLAY_SECTION` | `port/src/optionsoverlay.c:989` | Isolated screenshot/probe of a nested F10 page by row key (opens that header; never a menu mode). | **live** (diagnostic) |
| `GE_OPTIONSOVERLAY_SELECTBACK` | `port/src/optionsoverlay.c:995` | Screenshot-only: pins the selection to the Back row when the overlay auto-opens, to check it against a normally selected setting on the same page. | **live** (diagnostic) |
| `GE_OPTIONSOVERLAY_DROPDOWN` | `port/src/optionsoverlay.c:1195` | Screenshot-only: with the overlay auto-opened on a page, opens the dropdown popup of the named row key (e.g. `Video.TextureFilter`, `__Resolution`) and focuses it. | **live** (diagnostic) |
| `GE_OPTIONTREEPROBE` | `port/src/optionsoverlay.c:888` | D388: opt-in in-memory walk of the real F10 page tree; writes no settings, binds or profile bytes; restores UI state afterward. | **live** (diagnostic) |
| `GE_PRESETPROBE` | `port/src/optionsoverlay.c:957` (`presetProbe`) | D440: one-shot at overlay init — drives the "Original N64" and "Port defaults" preset rows through the real arm -> confirm activation and checks the "Active" state; logs `GE_PRESETPROBE: PASS/FAIL`. **Writes the live config** (persisted on exit) — use a throwaway data dir. | **live** (diagnostic) |
| `GE_OVPOINTERMAP` | `port/src/optionsoverlay.c:1726` | D391 pointer-map diagnostic: on LMB-down (and every 120 samples) logs SDL mouse coords vs GL drawable pixel coords + the hover row, to separate hit-band/scroll errors from coordinate-space mismatch. | **live** (diagnostic) |
| `GE_FOOTERPROBE` | `port/src/optionsoverlay.c:2232` | One-shot log of the overlay footer/help-line metrics (x extents). | **live** (diagnostic) |
| `GE_FRONTOPTIONS_SECTION` | `port/src/frontoptions.c:295` | Isolated headless screenshot of a nested front-end options page by key (changes navigation only; never edits profile or config values). | **live** (diagnostic) |
| `GE_FRONTNAVLOG` | `port/src/frontoptions.c:430,573` | Logs each front-options navigation select (section/row/item number). | **live** (diagnostic) |
| `GE_FRONTOPTS_WIDTHS` | `port/src/frontoptions.c:673` | D406b: one-shot label-metrics log for the options rows (layout diagnostics). | **live** (diagnostic) |
| `GE_FRONTOPTIONS_AUTO` | `port/src/frontoptions.c:818` | D406 test hook: enters the options screen without a mouse click so headless runs (`GE_INPUTSCRIPT` D-pad stepping + `GE_FRONTNAVLOG`) can exercise it. Never active without the env. | **live** (diagnostic) |
| `GE_FOOTERPROBE` | (removed) | **removed** -- the F10 footer was replaced by the hint line under the box (menu PD parity); tombstone kept. | **removed** |
| `GE_OVNAV` | `port/src/frontoptions.c:106` | D519 follow-up harness: scripted overlay navigation on file select (O toggle, D/U move, A open, B back, P log the place), one token per 20 file-select frames. | **live** (harness) |
| `GE_FILESELECT_OVERLAY` | `port/src/frontoptions.c:96` | One-PC-options-UI (D519) test hook: opens the F10 overlay over file select without a click; value `n` > 1 also closes it `n` label-frames later. Replaces the retired front-screen probes (the FRONTOPTIONS_SECTION, FRONTNAVLOG, FRONTOPTS_WIDTHS and FRONTOPTIONS_AUTO probes). | **live** (harness) |
| `GE_WSPROBE` (+ `GE_WSPROBE_FRONT`, `GE_WSPROBE_RESET`, `GE_WSPROBE_F10FRONT`) | `port/src/watchsettings.c:616,568,512`, `port/src/optionsoverlay.c:1566` | D354 watch-settings probe family: `WSPROBE[=tick]` logs before/after the queued settings commit one tick apart; `_FRONT[=tick]` drives a headless front-end change scenario; `_RESET` prepares an options-reset after 120 front ticks and verifies persistence once the stage drains; `_F10FRONT` fires a one-shot F10→front poll after the file-select save becomes available (avoids the joyDisablePoll deadlock). | **live** (diagnostic) |
| `GE_ZF` (+ `GE_ZFTOL`, `GE_ZFBOX`) | `port/fast3d/gfx_pc.cpp:1653-1657` | Z-fight grid probe: per tri emit, buckets the centroid cell and flags cells that fight ≥5 times (cap 600 hits); `ZFTOL`=tolerance, `ZFBOX="x0 y0 x1 y1"`=world-box filter. | **live** (diagnostic) |
+21 -4
View File
@@ -226,7 +226,7 @@ D345,FIXED,"Front-end crosshair snaps away from the pointer on screen/page chang
D346,CLOSED,PC-settings wording pass:,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
D347,FIXED,F10 overlay controller:,"FIXED (port-only, optionsoverlay.c):"
D348,CLOSED,"File-select ""PC Options"" label:",CLOSED (2026-09-28 bookkeeping:
D349,CLOSED,GE watch settings are not normally saved to the Bond file:,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
D349,CLOSED,GE watch settings ARE saved to the Bond file when the watch closes:,"SUPERSEDED (2026-10-04, P2b correction):"
D350,CLOSED,Watch-backed PC options:,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
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):"
@@ -306,7 +306,7 @@ D432,FIXED,"Gunshot sounds ""off"" after D427:",FIXED in tree (gate offset round
D433,OPEN,"Slight audible difference in automatic-gun shot sound vs GEPD/1964 after cadence was matched (user by-ear, Caverns).",OPEN (not a timing issue; audio layer suspected).
D434,FIXED,Raw ((u8*)&g_Textures[n])[0] & 0xf hit-type reads are wrong on PC; latent OOB once D431 returned real texture ids (found by sub-agent review).,"FIXED (.hitTexture at 3 sites, port-guarded); live check owed."
D440,FIXED,"""Original N64"" / ""Port defaults"" one-press presets in the F10 overlay and front-end PC Options (+ -n64preset/-portpreset).",FIXED (port-only; headless-verified; user live accept owed).
D441,PARTIAL,64-bit pointer-width sweep (part 1):,"PARTIAL (census + GE_HIGHARENA_BASE tier-1 build + 35 sites fixed + CI ratchet, 2026-10-03; tier-1 menu and -level_09 pixel-identical; owed tier-1 all-level sweep and the above-4-GiB tier)."
D441,PARTIAL,64-bit pointer-width sweep (part 1):,"PARTIAL (census + GE_HIGHARENA_BASE tier-1 build + 35 sites fixed + CI ratchet, 2026-10-03; tier-1 all-level sweep 21/21 PASS, 0 crashes, 2026-10-05; the above-4-GiB tier still owed)."
D442,FIXED,Game.AllUnlocked reworked into a pure RAM/query-time override:,"FIXED; verified 2026-10-03 headless (mission select all 20 / only real progress when OFF, full cheat menu, eep byte-identical)."
D443,FIXED,"D357 settings-display cleanup (mouse ×, whole-% deadzone and volume steps, FOV in horizontal degrees, MSAA 16×) plus a ""Restart game"" action (orderly D344 quit…",FIXED (port-only; reset probes pass; both-UI visual check and one Restart click owed; Linux relaunch path not compiled).
D454,FIXED,Knife slash crashed on Windows:,FIXED (verified by maintainer live:
@@ -351,7 +351,7 @@ D488,FIXED,PAL build reported the Brazilian MPAL TV type instead of PAL (osTvTyp
D489,FIXED,FPS dropped sharply while adjusting F10 watch sliders (music/FX) with a pad or arrow keys:,"FIXED 2026-10-02 (port-only UI layer, fix/d489-pad-slider-commit, merged into release/v0.4.1 2026-10-03); maintainer pad check passed 2026-10-03"
D490,FIXED,Dam first-tower guard walked a small circle:,"FIXED 2026-10-02 (fix/d490-glass-hull, merged into release/v0.4.1 2026-10-03); maintainer by-eye Dam check passed 2026-10-02"
D491,FIXED,"Struct reads across separately-declared adjacent globals (the D5/D215 class), found by the D24 overflow audit:","FIXED 2026-10-03 (fix/d491-adjacent-global-reads, local); builds clean, 900-frame -level_09 smoke clean; no by-eye effect expected beyond room-membership-driven behaviour"
D492,OPEN,Emulator .eep saves are not drop-in compatible:,OPEN (investigated 2026-10-03; fix decided:
D492,CLOSED,Emulator .eep saves are not drop-in compatible:,CLOSED (converter shipped 2026-10-04:
D493,FIXED,Pad rumble kept going after quitting the game:,"FIXED 2026-10-03, maintainer-verified in play (DualSense; Original-mode edges incl."
D494,FIXED,"Sniper rifle: the PC crouch key now crouches/stands freely with a disable-crouch weapon (GEPD parity), never injecting C-down for it; supersedes D483's stance…","FIXED 2026-10-03, maintainer-verified in play"
D495,FIXED,"Tank: with the default pad preset X changed the tank weapon and Y left it, because in-tank faces fell back to the fixed menu faces; PadMapCtx.tank routes Next/…","FIXED 2026-10-03, maintainer-verified in play"
@@ -362,5 +362,22 @@ D499,FIXED,Trilinear texture filter option:,"FIXED 2026-10-03, maintainer-checke
D500,FIXED,"Aztec shuttle area: polygons ""fell apart""; the per-frame vtx pool (dyn.c, no bounds check, Aztec -mvtx40) could overrun into the half the render worker draws;…","FIXED 2026-10-03, maintainer-verified at the Aztec spot"
D501,CLOSED,Ultrawide 16:9 pillarbox for cutscenes and the watch:,CLOSED (reverted 31ade91f)
D502,FIXED,PC Options pad navigation:,"FIXED 2026-10-03, maintainer-verified with a pad"
D503,?,Surface far dish shows as a faint silhouette with straight edges:,KNOWN ISSUE
D503,PARTIAL,"Surface: near-full-fog silhouettes on 32-bit output (fog snap applied); the remaining Surface dish is the widescreen frustum, 4:3 matches N64 —",FIX LANDED (by-eye pass owed)
D504,FIXED,F10 / PC Options menu:,FIXED (port-only; by-eye pass vs the PD port owed)
D505,FIXED,Menu wording pass: 27 maintainer-approved text edits across both PC options UIs —,FIXED (port-only; by-eye pass owed)
D506,FIXED,Menu slider values standardized:,FIXED (port-only; by-eye pass owed)
D507,FIXED,Menu: one-line description of the focused option in both PC options UIs —,FIXED (by-eye pass owed)
D508,FIXED,Forced aspect ratios:,FIXED (by-eye pass owed)
D509,FIXED,Held frames after an aspect-ratio change:,FIXED (by-eye pass owed)
D510,FIXED,F10 overlay + FPS counter the same on-screen size on the front end and in a level —,FIXED (live check owed)
D511,OPEN,Four PD-parity settings:,DONE headless (live checks owed)
D512,OPEN,Game.HudScale also scales the F10 overlay and FPS counter —,DONE headless (live check owed)
D513,OPEN,"Input.ControlScheme: Modern / OG controls (#139, T7) —",DONE headless (pad pass owed)
D514,OPEN,Emulator saves load directly:,DONE headless (by-eye mission select owed)
D515,FIXED,Empty arch tag from a fresh build dir:,FIXED (headless-verified)
D516,FIXED,"Control style Original shows the game's own N64 styles (1.1-2.4, per seat, saved to the Bond file) instead of the D498 presets —",FIXED (live pass 2026-10-04 incl.
D517,FIXED,Holding left/right on a Bond settings row dropped the fps:,FIXED (live pass 2026-10-04)
D518,FIXED,Pad layout preset was one global value:,FIXED (live pass 2026-10-04 incl.
D519,FIXED,"One PC options UI: the file-select ""PC Options"" entry opens the F10 overlay; the MENU_PC_OPTIONS screen is deleted —",FIXED (headless-verified 2026-10-04; by-eye pass owed)
D520,FIXED,"F10 overlay slider wedge, slider marker and dropdown arrow antialiased (they stepped in whole canvas pixels that grow with the window) —",FIXED (headless before/after capture; by-eye owed)
D450,FIXED,"Unplugging and replugging a controller was not picked up (user, GameSir-G7 Pro; 2026-09-29).","FIXED — port-only: the render thread's gfx_sdl_handle_events drains the same SDL queue as video.c's host pump but had no case for SDL_CONTROLLERDEVICEADDED/REMOVED, so it usually swallowed them and i…"
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
226 D346
227 D347
228 D348
229 D349
230 D350
231 D351
232 D352
306 D433
307 D434
308 D440
309 D441
310 D442
311 D443
312 D454
351 D489
352 D490
353 D491
354 D492
355 D493
356 D494
357 D495
362 D500
363 D501
364 D502
365 D503
366 D504
367 D505
368 D506
369 D507
370 D508
371 D509
372 D510
373 D511
374 D512
375 D513
376 D514
377 D515
378 D516
379 D517
380 D518
381 D519
382 D520
383 D450
+272 -6
View File
@@ -634,7 +634,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D346 | **PC-settings wording pass: option labels/values aligned to Nightdive/Turok + PD-port conventions (title-case On/Off, "Frame rate cap", "Anti-aliasing"/"None", units in values not labels, self-explanatory auto-FOV toggles); VIEW section merged into VIDEO (2026-09-27).** — full `## D346` entry at file tail | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. |
| D347 | **F10 overlay controller: left/right did nothing (only A/Y adjusted values) and no input had hold-to-repeat (2026-09-27).** — full `## D347` entry at file tail | FIXED (port-only, `optionsoverlay.c`): D-pad left/right + stick-X wired into value adjust (A/X/B/Y kept); 18/4-frame hold-to-repeat on adjust and up/down nav, matching the options screen's D345(e)/(f) cadence. User validated physical-controller pass. |
| D348 | **File-select "PC Options" label: no settings icon exists in the game art (mainfolderimages = copy/del/select-arrow/X/check/dot only) -- a mini slider glyph is drawn instead (2026-09-27).** — full `## D348` entry at file tail | **CLOSED (2026-09-28 bookkeeping: superseded by D398).** Earlier: SUPERSEDED by D398 (glyph replaced with the IMG_DOT texture; no new art). PCDUMP pixel-verified when shipped (135 knob px vs 9 paper). |
| D349 | **GE watch settings are not normally saved to the Bond file: erase is the only caller of the settings serializer, and stage start reloads the file's old values.** — full `## D349` entry at file tail | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. |
| D349 | **GE watch settings ARE saved to the Bond file when the watch closes: close/abort routes through `deleteCurrentSelectedFolder` → `fileClearSavefileForFolder` (the same path as Erase), which serializes live settings; stage start reloads them. The original "session-only" static audit was wrong (2026-10-04 runtime correction, run 2).** — full `## D349` entry at file tail | **SUPERSEDED (2026-10-04, P2b correction):** session-only claim false — watch close persists per Bond file (`run_p2b2.log`, options `0x3A→0x17A`); no-write control run matches the live-vs-saved memcmp gate. Earlier 2026-09-28 CLOSED bookkeeping superseded. |
| 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 | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. |
| 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. |
@@ -714,7 +714,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D433 | **Slight audible difference in automatic-gun shot sound vs GEPD/1964 after cadence was matched (user by-ear, Caverns).** — full `## D433` entry at file tail | OPEN (not a timing issue; audio layer suspected). |
| D434 | **Raw `((u8*)&g_Textures[n])[0] & 0xf` hit-type reads are wrong on PC; latent OOB once D431 returned real texture ids (found by sub-agent review).** — full `## D434` entry at file tail | FIXED (`.hitTexture` at 3 sites, port-guarded); live check owed. |
| D440 | **"Original N64" / "Port defaults" one-press presets in the F10 overlay and front-end PC Options (+ `-n64preset`/`-portpreset`).** — full `## D440` entry at file tail | FIXED (port-only; headless-verified; user live accept owed). Follow-up: preset stretches 4:3 in non-4:3 windows (no pillarbox mode). |
| D441 | **64-bit pointer-width sweep (part 1): 17 files widened `(u32)`/`(s32)` pointer casts to `uintptr_t` under `#ifdef PORT` (ABI/layout exception; ROADMAP §2 "Latent 64-bit ABI hazards").** — full `## D441` entry at file tail | PARTIAL (census + `GE_HIGHARENA_BASE` tier-1 build + 35 sites fixed + CI ratchet, 2026-10-03; tier-1 menu and `-level_09` pixel-identical; owed tier-1 all-level sweep and the above-4-GiB tier). |
| D441 | **64-bit pointer-width sweep (part 1): 17 files widened `(u32)`/`(s32)` pointer casts to `uintptr_t` under `#ifdef PORT` (ABI/layout exception; ROADMAP §2 "Latent 64-bit ABI hazards").** — full `## D441` entry at file tail | PARTIAL (census + `GE_HIGHARENA_BASE` tier-1 build + 35 sites fixed + CI ratchet, 2026-10-03; **tier-1 all-level sweep 21/21 PASS, 0 crashes, 2026-10-05**; the above-4-GiB tier still owed). |
| D442 | **`Game.AllUnlocked` reworked into a pure RAM/query-time override: #ifdef PORT hooks in `fileGetIsCheatUnlocked` and `fileIsStageUnlockedAtDifficulty` (every stage reports COMPLETED); the EEPROM patch and the D387 merge are removed (RULE-2-SIGNOFF, maintainer opt-in feature).** Full `## D442` entry at file tail | FIXED; verified 2026-10-03 headless (mission select all 20 / only real progress when OFF, full cheat menu, eep byte-identical). |
| D443 | **D357 settings-display cleanup (mouse ×, whole-% deadzone and volume steps, FOV in horizontal degrees, MSAA 16×) plus a "Restart game" action (orderly D344 quit, then relaunch).** Full `## D443` entry at file tail | FIXED (port-only; reset probes pass; both-UI visual check and one Restart click owed; Linux relaunch path not compiled). |
| D454 | **Knife slash crashed on Windows: `var_a0_2` in `gunTickHandState` was `u32`, truncating the exe-data address of `D_80034CA4/E0C` (PE image base 0x140000000) before `gunSample1PTransform` (ABI/layout exception, §A1).** Full `## D454` entry at file tail | FIXED (verified by maintainer live: knife slash no longer crashes). |
@@ -759,7 +759,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D489 | **FPS dropped sharply while adjusting F10 watch sliders (music/FX) with a pad or arrow keys: every step was a full EEPROM persist on the game thread** — full `## D489` entry at file tail | FIXED 2026-10-02 (port-only UI layer, `fix/d489-pad-slider-commit`, merged into `release/v0.4.1` 2026-10-03); maintainer pad check passed 2026-10-03 |
| D490 | **Dam first-tower guard walked a small circle: `sub_GAME_7F03ECC0` overflowed `s32 rem[4]` on flat bboxes (glass/windows), corrupting the collision polygon; `rem[8]` under `AVOID_UB` (UB/stack-layout class, porting-notes D24)** — full `## D490` entry at file tail | FIXED 2026-10-02 (`fix/d490-glass-hull`, merged into `release/v0.4.1` 2026-10-03); maintainer by-eye Dam check passed 2026-10-02 |
| D491 | **Struct reads across separately-declared adjacent globals (the D5/D215 class), found by the D24 overflow audit: `bg.c` `sub_GAME_7F0BA2D4` (portal bbox seeds, so every portal's Y/Z max stayed FLT_MAX and props joined extra rooms in `chrpropUpdateRoomList`) and both `gunfire.c` casing `rot` reads (y/z from `g_ZeroTriggerState`); `#ifdef PORT` reads the named globals** — full `## D491` entry at file tail | FIXED 2026-10-03 (`fix/d491-adjacent-global-reads`, local); builds clean, 900-frame `-level_09` smoke clean; no by-eye effect expected beyond room-membership-driven behaviour |
| D492 | **Emulator `.eep` saves are not drop-in compatible: the port stores the save checksum words and `options` little-endian, so an unconverted 1964 save fails `fileValidateSaves` and all five slots are wiped on first boot** — full `## D492` entry at file tail | OPEN (investigated 2026-10-03; fix decided: `tools_pc/eep_convert.py` converter + README section, on-disk format unchanged; queued after the D441 census) |
| D492 | **Emulator `.eep` saves are not drop-in compatible: the port stores the save checksum words and `options` little-endian, so an unconverted 1964 save fails `fileValidateSaves` and all five slots are wiped on first boot** — full `## D492` entry at file tail | CLOSED (converter shipped 2026-10-04: `tools_pc/eep_convert.py`, both directions + `verify`; output byte-identical to the in-game-proven converted save, round-trip clean; README "Using an emulator save" section added; on-disk format unchanged; review fixes 2026-10-04: conversion only re-stamps regions valid in the source, refuses (writes nothing) when nothing validates, bundled as `tools/eep_convert.py`, in-game import planned pre-1.0 (ROADMAP §5a)) |
| D493 | **Pad rumble kept going after quitting the game: the clean exit never runs `inputDestroy`, so `portAtExit` now calls `inputRumbleStopAll()`; same commit fixes D493b, a 1-unit live-pixel strip at the pillarbox edges in Original mode** — full `## D493` entry at file tail (D493b is a sub-section) | FIXED 2026-10-03, maintainer-verified in play (DualSense; Original-mode edges incl. ultrawide) |
| D494 | **Sniper rifle: the PC crouch key now crouches/stands freely with a disable-crouch weapon (GEPD parity), never injecting C-down for it; supersedes D483's stance freeze** — full `## D494` entry at file tail | FIXED 2026-10-03, maintainer-verified in play |
| D495 | **Tank: with the default pad preset X changed the tank weapon and Y left it, because in-tank faces fell back to the fixed menu faces; `PadMapCtx.tank` routes Next/Prev weapon to N64 A and aim/fire to R/Z** — full `## D495` entry at file tail | FIXED 2026-10-03, maintainer-verified in play |
@@ -770,7 +770,24 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D500 | **Aztec shuttle area: polygons "fell apart"; the per-frame vtx pool (`dyn.c`, no bounds check, Aztec `-mvtx40`) could overrun into the half the render worker draws; moved to 2x384 KB at DRAM +0x700000 with a budget log** — full `## D500` entry at file tail | FIXED 2026-10-03, maintainer-verified at the Aztec spot |
| D501 | **Ultrawide 16:9 pillarbox for cutscenes and the watch: built, then reverted (original framing, not breakage)** — full `## D501` entry at file tail | CLOSED (reverted 31ade91f) |
| D502 | **PC Options pad navigation: highlight on enter/back/page, back lands on the origin section, crosshair snap moved left; Trilinear listed after Bilinear** — full `## D502` entry at file tail | FIXED 2026-10-03, maintainer-verified with a pad |
| D503 | **Surface far dish shows as a faint silhouette with straight edges: ~97% fogged, the N64's 16-bit colour hid it, 32-bit output reveals it (any settings)** — full `## D503` entry at file tail | KNOWN ISSUE |
| D503 | **Surface: near-full-fog silhouettes on 32-bit output (fog snap applied); the remaining Surface dish is the widescreen frustum, 4:3 matches N64** — full `## D503` entry at file tail | FIX LANDED (by-eye pass owed) |
| D504 | **F10 / PC Options menu: PD-style dialog look and PD's six groups (Video, Audio, Mouse, Controller, Game, Key Bindings); checkbox / wedge slider / dropdown widgets; Display mode (Modern / Original N64 / Custom) replaces the two preset rows** — full `## D504` entry at file tail | FIXED (port-only; by-eye pass vs the PD port owed) |
| D505 | **Menu wording pass: 27 maintainer-approved text edits across both PC options UIs** — Text only; D504 follow-up; full `## D505` entry at file tail | FIXED (port-only; by-eye pass owed) |
| D506 | **Menu slider values standardized: real units everywhere, no `n/100` or `n/255` scales** — Display only; D505 follow-up; full `## D506` entry at file tail | FIXED (port-only; by-eye pass owed) |
| D507 | **Menu: one-line description of the focused option in both PC options UIs** — Port-only; D505/D506 follow-up; full `## D507` entry at file tail | FIXED (by-eye pass owed) |
| D508 | **Forced aspect ratios: Aspect ratio gains 16:9 and 21:9** — Port-only; D447 extension; full `## D508` entry at file tail | FIXED (by-eye pass owed) |
| D509 | **Held frames after an aspect-ratio change: one mis-projected frame (gun/hand glitch) removed** — Port-only; D508 follow-up; full `## D509` entry at file tail | FIXED (by-eye pass owed) |
| D510 | **F10 overlay + FPS counter the same on-screen size on the front end and in a level** — Port-only; D504 follow-up; full `## D510` entry at file tail | FIXED (live check owed) |
| D511 | **Four PD-parity settings: Fullscreen mode, Center window, Crosshair opacity, Crosshair color by health** — Port-only; ROADMAP PD Extended Options parity; full `## D511` entry at file tail | DONE headless (live checks owed) |
| D512 | **Game.HudScale also scales the F10 overlay and FPS counter** — Port-only; D226/D510 follow-up; full `## D512` entry at file tail | DONE headless (live check owed) |
| D513 | **Input.ControlScheme: Modern / OG controls (#139, T7)** — Port-only; D498/D166/D194 follow-up; full `## D513` entry at file tail | DONE headless (pad pass owed) |
| D514 | **Emulator saves load directly: `ge007.eep` in 1964/Project64 format is converted on first launch** — Port-only; D492/D297 follow-up; full `## D514` entry at file tail | DONE headless (by-eye mission select owed) |
| D515 | **Empty arch tag from a fresh build dir: the re-exec env drops `PROCESSOR_ARCHITECTURE`, CMake caches an empty processor, and the binary is named `ge007..exe`** — Build infra; D513 follow-up; full `## D515` entry at file tail | FIXED (headless-verified) |
| D516 | **Control style Original shows the game's own N64 styles (1.1-2.4, per seat, saved to the Bond file) instead of the D498 presets** — Port-only; D513/D498 follow-up; full `## D516` entry at file tail | FIXED (live pass 2026-10-04 incl. split-screen 2P; adjust-perf follow-up = D517) |
| D517 | **Holding left/right on a Bond settings row dropped the fps: every hold-repeat step committed (D489 staged sliders only)** — Port-only; D489 scope gap, found in the D516 live pass; full `## D517` entry at file tail | FIXED (live pass 2026-10-04) |
| D518 | **Pad layout preset was one global value: setting another player's preset changed the main player's mapping too** — Port-only; D498 follow-up, maintainer report; full `## D518` entry at file tail | FIXED (live pass 2026-10-04 incl. split-screen 2P) |
| D519 | **One PC options UI: the file-select "PC Options" entry opens the F10 overlay; the MENU_PC_OPTIONS screen is deleted** — Port-only (the one front.c entry call is kept); Bond-file rows + watch chooser re-keyed to file select; 4 probes retired, `GE_FILESELECT_OVERLAY` added; full `## D519` entry at the end of this file. | FIXED (headless-verified 2026-10-04; by-eye pass owed) |
| D520 | **F10 overlay slider wedge, slider marker and dropdown arrow antialiased (they stepped in whole canvas pixels that grow with the window)** — Port-only; D504 follow-up, P4k investigation; full `## D520` entry at file tail | FIXED (headless before/after capture; by-eye owed) |
| D407 | **Tanks cannot be boarded/exited on PC — the v0.4.0 use/reload split (D378/D393) removed the B-button tap the engine's tank handlers in `bondview2.c` consume (user report 2026-09-28, Runway/Streets)** — full `## D407` entry at file tail | PARTIAL (fix landed in v0.4.0, port-only): present `GE_CONT_B` on the use (E / pad A) rising edge only while `g_PlayerIsInTank == 1` or `g_BondCanEnterTank != 0`; E keeps its D378 no-reload-fallback semantics elsewhere, N64 layout unaffected. D407(b) same cycle: front PC Options page-edge highlight clamp + mouse-wheel / W-S paging (wheel queue consumed on the menu, D223); its wheel mapping shipped inverted and was fixed to match W/S (wheel up = step up). D407(c): board-animation lockout, menu-accept B gate in tank states, in-tank aim routed through the legacy velocity stick + `Input.TankAimScale` knob. RESOLVED 2026-09-28: all port-only (src/game zero-diff), 10 TANKDBG probes stripped, release binary verified clean, user live tank drive signed off (board + exit OK, aim feel good). |
| D408 | **`Game.SkipIntro` skips the post-mission failure dossier: with it on, entering a level then aborting (watch Z+A) or dying (KIA) returns straight to the menus — no REPORT / "Mission status: KILLED IN ACTION / ABORTED" screen** (user report 2026-09-28; save/AllUnlocked ruled out) — full `## D408` entry at file tail | OPEN (cosmetic, not root-caused past the handoff site). v0.4.0 decision: SkipIntro stays EXPERIMENTAL / not recommended for regular users — F10 row relabelled "Skip intro (EXPERIMENTAL)". |
| D450 | **Unplugging and replugging a controller was not picked up (user, GameSir-G7 Pro; 2026-09-29).** — full `## D450` entry at file tail | FIXED — port-only: the render thread's `gfx_sdl_handle_events` drains the same SDL queue as video.c's host pump but had no case for `SDL_CONTROLLERDEVICEADDED/REMOVED`, so it usually swallowed them and `inputRescanPads` never ran. Both pumps now post a request; the rescan runs at the top of `inputUpdate`, on the thread that reads the pads. |
@@ -14013,6 +14030,12 @@ to the user (proportions/knob offsets are one-constant tweaks if it looks off).
**Defaults:** `BLANKSAVEDATA` (`src/bondconstants.h:2151`) starts `music_vol`/`sfx_vol` at 0xFF and `options` at `DEFAULT_OPTIONS` (`src/game/file2.h:23`: auto-aim, sight, look-ahead, ammo on). No code change or runtime verification in this finding.
**Correction (2026-10-04, P2b runtime investigation — supersedes the session-only claim):** The premise "erase is the only caller of the settings serializer" is false. **Closing (or aborting) the in-game watch also reaches it:** watch close → `deleteCurrentSelectedFolder` (`src/game/file.c:82`) → `fileClearSavefileForFolder` (`src/game/file2.c:1386`) → `fileSaveSettingsForFolder`. **Runtime proof (run 2, `run_p2b2.log`, pre-run eep `scratch/eep_before_run2.bin`):** with the live mission save in slot 4, opening the watch, changing Screen Full→Wide and closing it logged `fileOverwriteSaveSlotWithNewSave: DoReset slot = 4` + `fileWriteSave slot=4 bitflags=08` with options `0x17A` (base `0x3A` | SCREENWIDE `0x040` | CONTROLTYPE `0x100`); the previously-active slot was reset to `bitflags=80`. Watch settings therefore **persist per Bond file on watch close**, and stage start's `fileLoadSettingsForFolder` (`file2.c:1334-1383`) does apply screen size/ratio from the save — the "re-pick after every mission" consequence above (and §5(a)'s persistence argument in `docs/dev/notes/WATCH-SCREEN-RATIO-DESIGN.md`) is void. Control run (run 1): a plain open/close with no edits wrote nothing — the write is gated by a live-vs-saved `memcmp` early-out (`file2.c:1405`), so unchanged settings produce no save traffic.
**Test artifact (run 3, `run_p2b3.log`):** an apparent "persistence failure" was an artifact of the test harness, not a bug. `-level_XX` hardcodes `fileSetCurrentFolder(FOLDER1)=0`, and the test eep's FOLDER1 slot held only the blank placeholder save (slots 1–3 = other folders' progress; slot 4 free). Run 3 loaded slot 4's options (`0x17A`) correctly at stage start, and closing the watch produced no write because live == saved (the same `memcmp` gate) — consistent with a successful load. One residual unexplained observation: an early frame read showed the watch displaying FULL despite the persisted Wide — possibly the native-widescreen display bypass (`bondview2.c:8150-8159`) or frame timing. Flagged as a by-eye check only; not chased further (run budget). In normal front-end play (one real save per folder) the round-trip is clean by code reading.
**Net effect:** D350's PC-only persistence bridge is now a superset of console behaviour rather than a workaround for a session-only gap, and the approved divergence "edits made directly in GE's watch stay session-only (faithful to N64)" no longer describes either platform. Decision on the ROADMAP PD-parity item: **(c) keep Screen size/Ratio on the in-game watch only** — no PC menu rows; the README notes that they combine with the PC Aspect ratio setting.
## D350 — Watch-backed PC settings: shared rows, explicit chooser and per-file save (2026-09-27)
**Port-only, no `src/game` changes.** `port/src/optionsoverlay.c` adds an Audio (Bond File) section (Music/FX sliders) and a Bond File page (explicit file chooser + watch look direction, auto-aim, aim control, sight, look-ahead, ammo). These rows are shared by F10 and `MENU_PC_OPTIONS`; control style, GE Screen/Ratio and unrelated M3 items remain deferred. `watchSettingsFieldForKey` routes them to `port/src/watchsettings.c` rather than `config.c`/ge007.ini. Empty/unvalidated files cannot be edited; the chooser is independent of `selected_folder_num`, skips absent saves, and reads save fields directly rather than loading a folder and changing front-end audio. In a running level, F10 targets the active file and reads a game-thread-published snapshot so direct watch changes appear next frame. The watch's own edits remain session-only.
@@ -16049,6 +16072,10 @@ Under PORT `struct image_entry` puts `dataoffset:24` in bits 0-23 (D67), so byte
**2026-10-01: #107 folded in.** The two sites from outside PR #107 (italoarruda), both confirmed by the census as REAL hazards for an arena at or above 2 GiB, are fixed with the PR's own `uintptr_t` arithmetic under `#ifdef PORT` (`#else` arms unchanged): `lightfixture.c` `lightFindVertexBaseForTri` (segment-14 vertex base `(s32)vertices + offset` re-cast to `Vtx*`) and `bg.c` `bgRoomCalcBB` (loop bound `(Vtx*)((s32)vertices + usize)` compared with a full-width pointer; above 2 GiB the loop never runs and the room keeps an inverted bounding box). Identical values with today's low arena; `-level_09` load smoke clean (rc=0, 0 red-zone hits). Read-side census (part 2, 403 `-Wint-conversion`/`-Wint-to-pointer-cast` warnings) is in the local note `docs/dev/notes/ABI-CENSUS-D441-PART2.md`; its model.c rows predate D457(c)(d) and need a re-run. **Also 2026-10-01:** `options.c` `draw_watch_controller` second-controller opaque path passed `(s32)(&table2)` (a stack address) into a `WatchContButtonPositions *`, the missed sibling of the D290 fix 60 lines above (census TOP-10 item 8); now `&table2` under `#ifdef PORT`. Read-side census re-run after D457(c)(d): 403 -> 320 warnings (model.c 65 -> 12, bondview2.c 27 -> 14; chr.c 190 = 31 distinct D34 initialiser lines). Same pass: `boss.c` main loop sent the stack-local `localGfxDoneMsg` address to `rspGfxTaskStart` through the `s32 rspReplyMsg` every frame (census TOP-10 item 8); now `(OSMesg)&localGfxDoneMsg` under `#ifdef PORT` (menu + `-level_09` smoke clean).
**2026-10-05: tier-1 all-level sweep (P6, maintainer time slot).** The tier-1 build (`-DPORT_DRAM_V1_BASE=0x90000000`) ran against the default build on all 21 solo levels, including the Cuba ending cutscene. Gate = the tools_pc/golden recipe per level (seed `0x0123456789abcdef`, `20:START` intro skip, `GE_PCDUMP 900-1500:300`; Cuba = no input, `300-900:300`), ini pinned 640x480, then `tools_pc/framediff.py` with the per-level run-to-run limits verify.sh uses (1 % tol-2; Jungle/Surface 2 3 %; Cuba structural only). Driver: `scratch/d441/sweep2.py` (worktree), levels in parallel in isolated instance dirs (copied `data/`, pinned ini, `MasterVolume = 0`); ~5 min wall at 4-way. **Result: 21/21 PASS, 0 crashes; worst over-tol 2.38 % (Jungle) and 1.38 % (Surface 2), both under their 3 % limit; every 1 %-limit level <= 0.24 % (worst Silo 0.238 %, Egypt 0.173 %) = residual self-noise.** No fix batch was needed, so the abi ratchet stays at 389.
**Measurement caveats (for future sweeps, all measured 2026-10-05):** (1) the 60-480 window the P6 prompt suggested is invalid — D117 intro-camera nondeterminism gives ~74 % px divergence between two seeded same-build runs there; the seeded golden window (900-1500) is the gate (self-noise in the golden window is well under the per-level limits; e.g. dam build-vs-build 0.01-0.04 % at tol 2). (2) The 60 Hz vsync-on pace is part of the recipe: uncap the frame rate (VSync=0/FpsCap=0) and the wall-clock 100 ms timers' tick alignment shifts, self-noise ~5 % px (tol 0). (3) `GE_DETERM=1` is NOT usable for this gate when uncapped: its pacemaker branch still services wall-clock timers, so self-noise is 33-35 % px at 900-1500 — D117 follow-up: virtualise the timers in determinism mode before trusting it for frame gates. Status: tier-1 sweep owed item CLOSED; remaining owed = the above-4-GiB tier (u32 slots widened / `portHostToN64`), `mema.c` widening (D453), `initanitable.c` heap slots, the remaining `-Wint-conversion`/`-Wpointer-to-int-cast` rows.
## D442 — Game.AllUnlocked reworked to a pure RAM override; save patch + D387 merge removed (2026-09-30)
**Status: RULE-2-SIGNOFF** (maintainer-granted opt-in port feature, edit confined to `#ifdef PORT` in `src/game/file2.c`). Maintainer, 2026-09-30: "All unlock - want that to allow for all levels/cheats to be unlocked, don't really care how we do it if debug/overrides are better than what we currently have" and, on the proposed hook design, "new plan sounds great". Runtime-verified (falsifier passed, first hook); second hook build-verified only, maintainer to check level select by eye.
@@ -16732,7 +16759,7 @@ Found by the porting-notes D24 audit (a `-Warray-bounds=2 -Wstringop-overflow=4`
**Verified (headless A/B):** converted save → all five slots `-> OK`, `wsfront` reads its music byte, mission select shows all 20 levels; blank control → only Dam. `AllUnlocked = 0` in the stage ini (checked). The investigation's Python CRC replica (`scratch/eepinv/eepinv_analyze.py`, from `fileGenerateCRC` + `src/random.s`) matches the binary on both files.
**Decision (maintainer, 2026-10-03):** converter tool (`tools_pc/eep_convert.py`, both directions, `--verify`) + a README "Using an emulator save" section; the on-disk format stays as is (no save-path or game-code change). Queued after the D441 census. Seed: `scratch/eepinv/convert.py`.
**Class:** §A1-adjacent (serialized multi-byte fields are host-endian in this port; raw-byte CRCs hide it until the stored words are compared). Cross-ref D297 (validation-failure wipe class).
**Status:** OPEN (converter not written yet).
**Status:** CLOSED 2026-10-04 — `tools_pc/eep_convert.py` shipped (`n64-to-pc`, `pc-to-n64`, `verify [--as n64|pc]` with auto-detect, faithful `fileGenerateCRC` replica from the seed). Verified against the investigation files: converted output byte-identical to the in-game-proven `1964_converted.eep`, round-trip byte-identical, wrong-size/missing input exits 2. README "Using an emulator save" section added. Review fixes 2026-10-04: conversion only re-stamps regions valid in the source; refuses (writes nothing) when nothing validates; bundled as `tools/eep_convert.py`; in-game import planned pre-1.0 (ROADMAP §5a).
## D493 — Pad rumble kept going after quitting the game (2026-10-03)
@@ -16847,4 +16874,243 @@ ROADMAP 5a "Gamepad rebinding + modern controller presets"; reference table in `
**Measured (port capture, `build-pc/ppm/shot_000.ppm`):** dish pixels (93-95, 93-95, 124-127); Surface fog colour (96, 96, 128) (`bgfog.c:138`); sky/haze around (96-100, 96-98, 128-131). The dish is about 97% fogged.
**Mechanism:** Surface fog is `gSPFogPosition(996, 1000)` with near 2 / far 2500, a ramp from about 417 to 2456 units; the dish sits near its end. fast3d's vertex fog is the standard `z/w * fm + fo` (same as the PD port), and the Draw/LOD multipliers are exactly 1.0 at 100 (`video.c` `portDrawDistanceMultiplier` / `portLodDistanceMultiplier`; 100 is the slider minimum). The N64's 16-bit framebuffer (5 bits per channel) plus the full-fog clamp make a 2-5 level difference vanish; the port's 32-bit output keeps it, so the nearly fogged dish and the straight edge of GE's room-rect scissor (`propobj.c:7510` `getPropCombinedRoomsBBox2D` -> `bgScissorCurrentPlayerViewF`) become visible. Raised Draw/LOD distance makes more such objects visible. D491 (props filed under fewer, correct rooms) is not the cause. Same "reveal, not regression" pattern as D227/D249.
**Options (ROADMAP §5c row):** snap fog at or above ~97% to full (port-only, global by-eye pass); an N64 16-bit colour/dither output mode for the Original preset.
**Status:** KNOWN ISSUE (ROADMAP Known issues).
**Follow-up 2026-10-04 (verdict: both, confidence med-high):** the 247 snap (F) helps the left/right objects, but the dish still showed. At 4:3 with N64 FOV the maintainer reports the view matches 1964, so the remaining dish is (C): the widescreen/FOV frustum, not fog (the D218 / D18-class widening; `camIsPosInScreen`/`c_lodscalez` FOV terms not yet audited by code). The 247 cutoff was never derived from measured vertex fog; no probe was run since the 4:3 test settled it. The Draw/LOD multipliers are exactly 1.0 at the 100% minimum (`video.c`).
**Related change (maintainer request):** the Draw distance / LOD distance slider ceiling is raised 400% to 800% (`video.c` registered max + `portDrawDistanceMultiplier` clamp 6.0 to 8.0 + LOD clamp 800; `optionsoverlay.c` `uiMax=800`, UI normalization `(pct-100)/7`, so the 250% default now shows 21/100). The old note warns about far-field z-fighting beyond about 4x; by-eye check at high values owed.
**2026-10-04 (decision owed):** the residual is tracked under the ROADMAP §3 D222 row ("FOV-scale edge culling"), not D218 (closed). Maintainer decision owed: expected widescreen behaviour (the Original N64 / 4:3 view is the faithful one) vs fixing the FOV terms. The 800% ceiling makes near-full-fog objects more visible (opt-in).
**Status:** FIX LANDED 2026-10-04 (`gfx_pc.cpp` vertex fog: `fog >= 247` snaps to 255, port-only); dish residual reclassified as (C), tracked under D218; by-eye pass across levels owed before the known-issue row is deleted.
## D504: F10 overlay restyled to the PD settings menu; both PC options UIs regrouped to PD's six groups
Port-only (`port/src/optionsoverlay.c`, `frontoptions.c` grouping fallout, `optionsoverlay.h`). Folds in DMENU-1..7 (worktree notes) and requirements R1-R8 of the menu-parity plan. Cross-ref D440 (preset key table, now behind the Display mode row), D356 (resets), D469 (Select player / Controller), D472 (owner pad), D502 (pad nav).
**Grouping (R7/R8).** Hub order Video, Audio, Mouse, Controller, Game, Key Bindings, then a separator and Restart game / Quit to desktop. Controller opens Select player (Player 1-4), which opens that seat's Controller page. The Input sub-hub, the Display mode / Graphics / HUD pages and the Movement / Actions sub-pages are gone; Key Bindings is one flat list. Field of view moved from Graphics to Game (PD keeps Vert FOV there); the HUD rows, crosshair rows and Crouch mode sit on Game. Mouse gained rows for the existing ini keys `Input.MenuPointerMode`, `Input.MouseSmoothing`, `Input.MouseRawInput` (reset defaults added). One reset row per dialog (6); each covers exactly its dialog's keys (`GE_WSPROBE_RESET` 6/6 front, 6/6 stage). The hub / tab order is `kRootOrder`; rows stay physically contiguous per header, so the front end walks the same table (`optionsRootHeader`).
**Display mode (T4-lite).** `__DisplayMode` row: value derived Modern / Original N64 / Custom from `videoPresetIsActive`. F10: a dropdown (all three shown, current marked; picking Custom is a no-op); left/right on either UI steps Modern <-> Original N64 and applies at once (the first arm -> confirm version hid the other modes; maintainer feedback). Persists across restart (maintainer-verified). No new ini key. `GE_PRESETPROBE` walks Modern -> N64 -> edit a bundled key -> Custom -> dropdown pick Modern. Backspace also goes back one page (QoL).
**Look (R1/R2/R4/R5).** PD's green palette (`g_MenuColours[]`, menu.c:110), content-sized centred box, gradient title bar 2 units wider each side with two shimmer streaks, shadowed yellow title, flat ~78 % body (PD 50 %, raised twice for legibility, maintainer), 1 px borders, light dim. No CONTROLS footer: a two-line hint under the box, shown only while the last input was pad/keyboard (or a bind capture is waiting), with D471 per-family button names. Labels are mixed case in Zurich Bold (the only mixed-case face; Bank Gothic is caps-only) drawn at 78 % through the HUD span scale (`drawBody`), hub rows normal size. Focus: text pulses toward white (40-tick period) with PD's `#004400` outline; no focus line (`focusRule` deleted). Values stay in the unfocused colour. Mouse hover gets the same cue without moving the selection (the D304 feedback loop stays closed). Scroll eases toward the target row; rows only draw while fully inside the body (the scissor does not clip these 2D rects).
**Widgets (R3).** On/Off rows are PD checkboxes; numeric rows are a PD wedge slider with a triangle marker and the value text right of it; enum, anti-aliasing, resolution, frame cap and output device rows are dropdowns (popup under the row, or above when short of room; up to 9 items then scrolls). Enter/A/click opens; up/down/wheel/hover pick; Enter/A/click confirm; Esc/B/right-click/outside-click/closing the menu cancel; left/right on a closed row still steps. Named two-way toggles (Reverse/Upright, Hold/Toggle, All unlocked) keep value text. Front end: no widgets (R8), grouping only.
**Deviations / gaps.** `Game.HudScale` is not applied to this menu (PD's menus do not scale either; applying it would need an inverse in the mouse map). Background blur is a logged gap (needs new renderer work), so on bright frozen frames the 50 % body is less legible than PD's. Hover over a row while a pad is also active follows the last input source. No PD reference capture was produced headless.
**Verified (headless, `GE_QUITFRAME`):** build rc=0, no new warnings in touched files; `GE_OPTIONTREEPROBE` PASS (updated: 6 roots + 3 hub rows, scroll-window hit test instead of a no-scroll row cap, Back last on every page); `GE_PRESETPROBE` PASS; `GE_WSPROBE_RESET` 6/6 front and 6/6 stage, other-file isolation intact; `gen_env_probes.py --check` clean (new `GE_OPTIONSOVERLAY_DROPDOWN`, `GE_FOOTERPROBE` tombstoned). **Owed (maintainer):** live look-and-feel pass vs the PD port; dropdown / checkbox / slider with mouse, keyboard and pad in F10 (in a level and on the front end); the regrouped front-end pages; Display mode round trip across a restart.
## D505: menu wording pass (text only)
Port-only (`port/src/optionsoverlay.c`, `frontoptions.c`, one release-notes sentence). Follows D504. A wording table of every user-visible string in both PC options UIs was reviewed by the maintainer; all 27 proposed edits were applied, nothing else (no layout, key, order or behaviour change).
**Renamed (old -> new).** Aspect -> Aspect ratio (value Window -> Fill window); Mouse horizontal / vertical sensitivity -> Horizontal / Vertical sensitivity; Invert look (mouse) and (controller) -> Invert look; Mouse smoothing -> Smoothing; Deadzone (left/right stick) -> Left/Right stick deadzone; X/Y axis look sensitivity (controller) -> Horizontal / Vertical look sensitivity; Look smoothing (controller) -> Look smoothing; Fire key / Aim key -> Fire / Aim; AI sees the wider view -> Guards see the wider view (release notes quote updated). Values: "ON (restart)" -> "On (restart)" (the front end's green cue now also matches "On ("); capture prompts "Press button..." / "Key/mouse..." -> "Press a button..." / "Press a key..."; resolution n/a -> "(n/a)"; the front-end "[ENTER]" and "for restart options" value texts removed. Tags: front end "(per profile)" -> "(profile)" (matches F10). Hint lines: front-end and F10 key/pad hints share one phrasing ("Keys and mouse only", no colons, "Esc"/"Del", `{BACK}` token).
**Deliberately not changed (maintainer decisions owed, in the table's flag rows):** Layout preset is NOT renamed "Control style" (the game's own term; T7 / #139); the Use / Action / Use-back naming of the A/B binds; the abstract `n/100` and `n/255` value formats; "No hit flash" polarity; Widescreen auto FOV naming. EXPERIMENTAL labels keep the capitals (red ink matches the literal).
**Verified (headless, `GE_QUITFRAME` on every run):** `./build-pc.sh ntsc-final` rc=0, no new warnings in touched files; `GE_OPTIONTREEPROBE` PASS; `GE_PRESETPROBE` PASS; `GE_WSPROBE_RESET` 6/6 sections verified, 0 failures, other-file isolation intact; `GE_FRONTOPTS_WIDTHS` widest label "Guards see the wider view" (175) shows with its value unclipped; contact sheet of every F10 dialog and front-end page showed no clipping or overlap. No config key changed, so the written ini is unchanged by construction (byte compare vs 23004d20 not run). **Owed (maintainer):** by-eye read of the new wording in both UIs.
## D506: slider values standardized (display only)
Port-only (`port/src/optionsoverlay.c`; both UIs share `valueText`). Maintainer asked for matching values across rows and no odd defaults (e.g. `41/200`). The calibrated "50/100 at the default" mapping (`calibratedDefault`) is retired (now returns "none"); every slider is a linear bar in a real unit. Storage, ini keys, ranges and defaults are unchanged.
**Now shown as:** multipliers (raw 100 = 1.0x) for Horizontal / Vertical sensitivity and Horizontal / Vertical look sensitivity (vertical mouse and both pad look rows gained linear bars; mouse vertical uses UI range 10..300 like the horizontal row); `%` for Trigger threshold (raw is already a percent, default 23%), Look smoothing (raw 0-10 -> 0..100%), deadzones, volumes, vibration, shake, smoothing, crosshair size, HUD scale and the crosshair R/G/B channels (percent of full); `x` for Draw / LOD distance (ini 100..800 -> 1.0x..8.0x, default 2.5x), anisotropy. Field of view stays in degrees (it is a real unit and depends on the window aspect). The unused `dispMax` row field was removed.
**Verified (headless):** build rc=0; `GE_OPTIONTREEPROBE` PASS; `GE_WSPROBE_RESET` 6/6 sections 0 failures; F10 Mouse / Controller / Video / Game dumps show the new values (`scratch/vals_sheet.png`). **Owed (maintainer):** drag each changed slider with mouse and pad, both UIs; the vertical-mouse and pad-look bars now start at ~35-57% instead of the middle.
## D507: option descriptions under the menus
Port-only (`optionsoverlay.c`, `optionsoverlay.h`, `frontoptions.c`). Maintainer ask: hints that explain the less obvious options (e.g. "Guards see the wider view"). `kRowHelp[]` maps a row key to a one-line description (45 rows; self-explanatory rows have none); `optionsRowHelp(i)` returns it. F10: drawn centred as the first line under the box for the focused row from any input source (mouse included); the two key-hint lines move down one line while it shows. `OV_HINT_H` 26 -> 53 reserves three lines (the safe-area crop eats the bottom margin, so 41 pushed the key hints off screen); a 240-line canvas loses about two rows of page height (Game and Video already scrolled). Front end: the description replaces the bottom hint line while a row is highlighted (page bottom has room for one line of about 44 characters; bind-capture hints still win during capture). Texts are written to stay within that width.
**Verified (headless):** build rc=0; `GE_OPTIONTREEPROBE` PASS; `GE_PRESETPROBE` PASS; `GE_WSPROBE_RESET` 6/6; F10 Game page dump shows description + both key hints; front-end Game page with "No hit flash" highlighted shows its description (scripted DOWN steps). **Owed (maintainer):** read all descriptions for accuracy (Look ahead, Skip intro, Aim style are my reading of the code, not the watch's wording); front-end line position at your window size; F10 row count on short windows.
## D508: forced aspect ratios
Port-only (`port/src/video.c`, `optionsoverlay.c`). `Video.AspectMode` (D447) had Fill window (0) and Original (1). Maintainer ask: let players force other shapes. The D447 output-rect path already letter/pillarboxes to any target aspect (`gfx_set_output_aspect`) and sets `gfx_current_dimensions.aspect_ratio` to it, so Native widescreen projects the world at the forced aspect with no fast3d change. New values 2..3 = 16:9, 21:9 (7:3). 4:3 (same as Original unless the watch Ratio is 16:9) and Cinema 2.35:1 (near-identical to 21:9) were dropped as redundant after the first by-eye pass; ini range widened to 0..3, default and the Original N64 preset (1) unchanged. Any non-Fill, non-Original value counts as "Custom" in the Display mode row (it is a bundled key). The overlay and mouse mapping follow the output rect as before. A window narrower than the target letterboxes, wider pillarboxes.
**Verified (headless):** build rc=0; `-level_09` frame 400 at 1280x720 for modes 3, 4, 5: 16:9 fills the window, 21:9 and 2.35:1 letterbox with the expected bar sizes (`scratch/asp_sheet.png`); F10 Video dump shows "21:9" with Display mode "Custom"; tree / preset / reset probes pass. **Owed (maintainer):** by-eye in a level and the front end for each ratio, HUD/crosshair/menu placement at 21:9 and 2.35:1 (the HUD uses the same Hor+ path as a real ultrawide window), mouse aim at the letterbox edges, fullscreen on a 16:9 panel.
## D509: one wrong frame on every aspect-ratio change (Bond's gun/hand glitched)
Port-only (`port/fast3d/gfx_pc.cpp`). Maintainer saw the first-person gun/hand glitch ("reset") for a moment when changing Aspect ratio with F10 open in Surface 1. Measured with a temporary cycling probe on `-level_36` (since removed): every output-aspect change gave one extra frame that differed across the whole scene from the next one. Cause: `gfx_set_output_aspect` takes effect at the render worker's next frame start (D481), but the game thread has already built the next one or two display lists with the previous projection (`portNativeAspect()` reads the render side's last aspect), so those frames were drawn mis-projected into the new rect; the viewmodel is the most visible part. Fix: after a change `gfx_run` drops the next 2 frames (the same path a minimised window uses), so the previous image is held and the first frame shown is already correct. Only changes of `Video.AspectMode` trigger it; steady state and every other setting are unchanged. Not covered: window resize, fullscreen toggle and Native widescreen toggles have the same one-frame mismatch in principle (not reported).
**Verified (headless):** on the cycling probe, per-change frame diffs went from a big jump plus a second smaller one (frames N, N+2) to one jump; tree / preset / reset probes pass; 60 s `-level_09` smoke rc=0. A gun-pose reset not caused by the projection mismatch could not be reproduced headless. **Owed (maintainer):** switch Original / 16:9 / 21:9 repeatedly in Surface 1 holding a gun with F10 open; confirm no gun flash. Also: `Input.PadTriggerPct` default 23 -> 25 (maintainer: rounder), in the same commit; an ini that already stores 23 keeps it until reset.
**Residual (maintainer, after the fix):** the glitch still happens rarely, mostly Fill window to Original or a forced ratio, best reproduced on Surface 1 holding a gun with F10 open for a while. Logged as a ROADMAP known issue; deliberately not investigated further (cosmetic, one-off). If revisited: capture many frames across repeated changes and compare the viewmodel only; the viewmodel (D215) may cache a per-aspect value on the game thread that the 2-frame hold does not cover.
## D510: the F10 overlay rendered ~27 % smaller on the front end than in a level
**Symptom (maintainer):** at the same window size the overlay box and text were a different size on the main menu / File Select than in Bond's view. The FPS counter (same display list) too.
**Diagnosis (logged, 640x480 and 1280x720):** hypotheses (a) and (b) were both true, (c) was not. (b) `viGetX() x viGetY()` is 440x330 on the front end and 320x240 in a level (`front.c` `viSetXY(440, 330)`; `osViSetMode` resizes fast3d's SCREEN_WIDTH to match). The overlay laid out in those raw units (a fixed 196-unit hub card), so it covered 196/440 = 45 % of the window on the front end versus 196/320 = 61 % in a level (294 px vs 392 px at 640 wide). (a) The safe-area crop rect (`gfx_get_ui_screen_rect`) follows the last game viewport, so the overlay was also stretched by the in-level "Full" viewport inset and the 1-unit D246 edge trim (logged rect was the full window at 640x480 in both, but it is viewport-dependent).
**Fix (port-only):** the overlay is PC chrome. `optionsoverlay.c` now lays out on a canonical 320-wide canvas (`ovW()`/`ovH()`, height kept at the real aspect) and calls `gfx_set_overlay_scale(viGetX()/320)`; fast3d scales the overlay DL's rectangles/texrects by that factor about the origin (after the D226 HUD span scale) and, while the overlay DL runs (`g_overlay_window_space`), bypasses the safe-area crop and the D493b edge trim. `gfx_get_ui_screen_rect` returns the window-space rect, so the mouse mapping (now on the canonical canvas) follows. The overlay-closed frame is untouched (flag only set around the overlay DL; no overlay DL, no change).
**Verified (headless):** front end vs level overlay shots at 640x480 and 1280x720 identical in box size and position; also with native widescreen and with `SafeAreaCrop=0`; FPS counter identical size and top-right offset at 1280x720; tree / preset / reset probes PASS; 60 s `-level_09` smoke rc=0. **Owed (maintainer):** live check at the usual window size and fullscreen, on the front end and in a level; mouse hover/click rows in both; split-screen (2P) with the overlay open (safe-area crop is already off there). Not done: `Game.HudScale` on the menu (S4).
## D511: four small PD-parity settings (S3)
All port-only; no `src/game` edit (the crosshair alpha goes through the existing `portCrosshairApplyTintCombine` hook, so `gunfire.c` is untouched). Each new key defaults to today's behaviour, has a shared row (so both UIs show it), a per-dialog reset entry and a one-line description, and is NOT in the Display mode bundles (the preset probe confirms editing them does not flip Display mode).
- **`Video.FullscreenMode`** (0 borderless, default; 1 exclusive): dropdown shown only while Fullscreen is on. Applied on the host thread via a new `videoRequestFullscreenMode` (window request kind 4) that calls the backend's existing `set_fullscreen_exclusive` (re-enters fullscreen if already on); at startup it feeds `fullscreen_is_exclusive`. Survives a restart through the ini.
- **Center window**: action row `__CenterWindow`, shown only while Fullscreen is off, no ini key. `videoRequestCenterWindow` (kind 3) reads the current window size and moves it with `get_centered_positions` + `set_dimensions`; logs old and new position. Maximized: SDL ignores the move.
- **`Video.CrosshairAlpha`** 0-100 % (default 100): scales the env-colour alpha byte (0x6E at the call site) after the sprite DL is built. At 100 the command is untouched.
- **`Video.CrosshairHealthColor`** (default off): PD's `sightGetCrosshairHealthColor` ramp (green variant; MIT), ratio = `bondhealth + bondarmour` of `g_CurrentPlayer` (both 0..1, clamped to 0..2; read in `input.c` `portCrosshairHealthRatio`). Red below 0.2, red to yellow to 0.6, yellow to green at 1.0, green to cyan with armour above. Precedence (as PD): when on it replaces Crosshair color's colour; the alpha-mask combiner is used whenever either is non-default.
**Verified (headless):** build rc=0; `-level_09` with `GE_STARTWEAPON=13` and a scripted aim hold (`400:RHOLD`), frame 700: default red crosshair, opacity 40 visibly faded, health colour on shows green at full health (log: ratio 1.000 -> tint 0,255,0); clean-ini run writes only the new lines (`CrosshairAlpha = 100`, `CrosshairHealthColor = 0`, `FullscreenMode = 0`); tree / preset / reset probes PASS; 60 s `-level_09` smoke rc=0. **Not verified headless:** real window state for fullscreen mode and Center window (a game window would be disruptive here). **Owed (maintainer):** exclusive fullscreen enter/leave and surviving a restart (Windows, Deck if convenient); Center window incl. on a second monitor; crosshair opacity and health colour by eye while taking damage. **Docs:** the ROADMAP "PD Extended Options parity" row was not edited here (it is an uncommitted change in the main checkout); move these four items from "Small, port-only" to done when committing it.
## D512: Game.HudScale applies to the F10 overlay (S4)
**PD reference:** pd_port has no HUD/menu scale to follow (the only scale hit is the debug menu, `src/game/debug1.c`), so this is GE-native: the whole dialog scales (box, text, hint line, FPS counter), not only the text.
**Change (port-only, `optionsoverlay.c`):** D510's canonical canvas now also divides by the HUD scale: `ovW()/ovH()` shrink by 100/hud and `ovScale()` (the fast3d overlay scale) grows by hud/100. The box stays centred, always fully on screen, and a larger scale just shows fewer rows (the existing scroll). Mouse hit-testing, dropdown popups and the FPS corner already derive from `ovW/ovH`, so they follow. The text clip size passed to `textRender` is now the canonical size. Changing the slider re-lays out on the next frame (layout is recomputed every emit). 100 % is the D510 mapping exactly.
**Not done:** the front-end PC Options page (`frontoptions.c`) is hand-laid on the file-select paper art in the game's own 440x330 display list; scaling only its contents would misalign with the art, so it is left at 100 %. Say if you want it (it would need the paper drawn at the same scale).
**Verified (headless):** 100 %: overlay-open front-end and `-level_09` dumps match the release build apart from the animated title-bar shimmer (the release build differs from itself in the same rows); level box widths at 640x480 are 295 / 392 / 588 px at 75 / 100 / 150 % (expected 294 / 392 / 588), at 1280x720 588 / 784 / 1176, all inside the window; contact sheet shows the 150 % hub scrolling with the box on screen. Tree / preset / reset probes PASS; clean ini byte-identical to the release build's; 60 s `-level_09` smoke rc=0. Pointer mapping is correct by construction (same helpers) but was not exercised headless. **Owed (maintainer):** by-eye pass at 75 / 100 / 150 in a level and on the front end with mouse and pad; the front-end PC Options page decision above.
## D513: Input.ControlScheme -- Modern / OG controls (#139, ROADMAP "Control scheme selectable")
**Design:** `docs/dev/notes/T7-CONTROL-SCHEME-DESIGN.md` (local note; PD review: PD adds an "Ext" fifth entry `CONTROLMODE_PC` to the game's own style list, kept per player in `extcontrols`, forced from the ini at load; GE cannot add a watch entry port-only, so the same idea is a port-side switch). Maintainer answers 2026-10-04: D498 style pins stay in Modern, presets are layout + stick swap only in OG; Display mode does not select OG; no 1.3/1.4 button permutation (OG passes buttons through as N64 buttons, faithful: those styles exist to move fire to A / aim to Z); 2.x: see the correction below; global key for v1, per seat later with Select Player; row at the top of the Controller page via the shared table.
**Change (port-only; `port/src/input.c`, `port/src/optionsoverlay.c`):** new `Input.ControlScheme` (0 Modern, default; 1 OG controls). `styleIsSolitare()` replaces the four `stickSolitare()` call sites (keyboard forward/back, mouse hipfire pitch, pad sticks, and the per-poll re-assert): Modern returns `stickSolitare()` unchanged; OG reads the slot's live `cur_player_control_type_0` (SOLITARE 1.2 and GOODNIGHT 1.4 use the natural-look roles, everything else the 1.1 roles for the first pad) and the re-assert block no longer writes the style, so the watch/save/MP-setup selection stands. OG logs each slot's style change once (`input: original control style, slot N style S (...)`, plus a dual-controller note for 2.x). `Input.NaturalPitch` is unchanged and still only matters in Modern. Row "Control scheme" (Modern / OG controls) at the top of the Controller page, reset entry, help line, not in the Display mode bundles. Test-only env `GE_TESTSTYLE` (GE-ENV-PROBES.md).
**Verified (headless, scripted slot 0, `-level_09`, seeded):** Modern: the written ini differs from the release build's only by `ControlScheme = 0`, and the 368-line `GE_INPUTLOG` stream (60 non-zero stick polls) is identical to release/v0.4.1. OG: with `GE_TESTSTYLE` 0 / 1 / 2 / 3 each style is logged once and survives to the end of the 366-poll run unchanged; style 4 logs the dual-controller note once (wording corrected below). At stage start the game loads the style from the Bond file (this eep copy held 1.2, written back by earlier builds: log shows 1.1 at init, then 1.2 from the save, then the test style); the port does not touch it. Tree / preset / reset probes PASS; 60 s `-level_09` smoke rc=0. **Not verified headless:** the scripted source bypasses the keyboard and pad mapping branches, so the role mappings themselves in OG (1.1 vs 1.2 sticks, 1.3/1.4 buttons) and a write-then-reload save round trip were not exercised. **Owed (maintainer):** pad pass in Modern and OG with watch-selected 1.1 / 1.2 / 1.3 / 1.4: movement, look, aim, fire, menu nav; split-screen 2P with different styles; mouse unaffected.
**README:** Controls section documents the scheme and that saves from earlier builds may hold 1.2 (OG players pick their style on the watch once).
**ROADMAP row text (not edited here):** "Control scheme selectable: Modern / OG controls | done (D513, pad pass owed) | #139, D513, D498, D166 | `Input.ControlScheme` (Modern default / OG controls) at the top of the Controller page; OG leaves the game's own style (watch / save / MP setup) alone; presets set layout and stick swap only in OG; 2.x runs faithfully (second stick from controller idx+playerCount, not mapped); global in v1, per seat with the Select Player work. Pad pass owed in both modes."
**Naming follow-up (maintainer, 2026-10-04):** the displayed row is "Control style" with values "Ext" (was Modern) and "N64" (was OG controls), matching PD's "Control Style" / "Ext" and the port's other N64/PC wording. The ini key and values (`Input.ControlScheme` 0/1) are unchanged. README updated.
**Second naming pass (maintainer, 2026-10-04):** the second value is "Original" (not N64), in line with the aspect-ratio and Display mode wording; the log line reads `input: original control style, slot N style S (...)`. Values shown: Ext / Original.
**Correction on 2.x (maintainer review, 2026-10-04):** the earlier claim "2.x acts as 1.1" was wrong. In Original mode the port does not write the style, so the game really runs the 2.x style, and `bondview2.c:4997-5003` reads the second stick from controller `playernum + playerCount` (faithful N64 dual-controller). The port only maps the first pad, with 1.1 roles, so with one pad the second stick is idle, as on an N64. Kept as is. The log line now reads `...; dual-controller style: second stick read from controller <idx + getPlayerCount()>` (once per change); README says a second pad assigned to that slot should provide the 2.x second stick (untested), otherwise use a 1.x style. `GE_TESTSTYLE` is marked scaffolding to strip before the release.
**Build note for filing (fresh worktree, empty arch tag):** the first build in the new worktree `gh-fullhist-wt-t7` (from `git worktree add ../gh-fullhist-wt-t7 -b feat/control-scheme release/v0.4.1`, then `build-pc/data` copied in) was `./build-pc.sh ntsc-final` from a Git Bash agent shell (`MSYSTEM=MINGW64`, `PROCESSOR_ARCHITECTURE=AMD64` in that shell, PATH prefixed `/c/msys64/mingw64/bin:/c/msys64/usr/bin`). It went through the PowerShell re-exec path ("native TMP unusable"). The cmake log (`build-pc/ge007-native-reexec-cmake.log`) printed `-- Target arch: (64bit=FALSE)`, `-- Target platform: -windows`, `-- Output binary: ge007.`, and the link produced `build-pc/ge007..exe` (a normal 64-bit exe; compile defs still had `-DPLATFORM_64BIT=1`). The cause is in the cache: `build-pc/CMakeFiles/4.4.2/CMakeSystem.cmake` in the new dir has `set(CMAKE_HOST_SYSTEM_PROCESSOR "")` and `set(CMAKE_SYSTEM_PROCESSOR "")`, while the main repo's older `build-pc` has `"AMD64"` for both (same cmake 4.4.2, same generator Ninja, same `CMAKE_C_COMPILER=C:/msys64/mingw64/bin/gcc.exe`). `cmake/TargetArch.cmake` takes `CMAKE_SYSTEM_PROCESSOR` when not cross-compiling (`CMAKE_CROSSCOMPILING` was FALSE), so an empty processor gives an empty `TARGET_ARCH` and `BIN_NAME` `ge007.` + `.exe`. Not verified: why the processor was empty; my hypothesis is that the stripped environment of the `.ps1` re-exec (see AGENTS.md build notes, which restore PATHEXT and ComSpec) lacks `PROCESSOR_ARCHITECTURE`, so cmake sees no host processor. It only bites a fresh build directory (the value is cached in `CMakeSystem.cmake`), which is why older build dirs are fine. Workaround used: copy `ge007..exe` to `ge007.x86_64.exe`.
## D514: emulator saves load directly — `ge007.eep` in 1964/Project64 format is converted on first launch (ROADMAP §5a, S8)
**Change (port-only; new `port/src/eepimport.c`, one call in `port/src/libultra.c` `geEepromLoad`, README):** copying an emulator `.eep` in as `data/ge007.eep` now just works. Before the game's first EEPROM read, `geEepImportEmulatorSave()` checks each region (block 0 + the five slots) and converts in place only regions that validate in emulator format AND not in port format; a region invalid in both is left alone for the game's own wipe path; port-valid regions are untouched. If anything converts: the original bytes go to `ge007.eep.emulator.bak` first (never overwriting an existing backup — `.bak2`, `.bak3`, …), then the converted file is written, and one line is logged: `eep: imported emulator save (N regions converted, backup <path>)`. Nothing converted -> no write, no log.
**Rules mirror `tools_pc/eep_convert.py` exactly** (D492): per slot, byte-swap `options` (u16 @12; it lies INSIDE the CRC range [8..96), so a plain word swap would still fail) then re-stamp `chksum1/2` over [8..96) stored little-endian — via the game's own `fileGenerateCRC`, not a C replica; block 0 (no multi-byte field in its CRC range) is a plain byte-swap of its checksum pair. Validation reads the stored words big- or little-endian and compares against `fileGenerateCRC` of the raw bytes (the CRC value is endian-independent). No CMakeLists change (`SRC_PORT` is `GLOB_RECURSE`, as for D297's `legacycrc.c`). Export to an emulator stays with `eep_convert.py`; README "Using an emulator save" now says "copy it in".
**Verified (headless, `GE_QUITFRAME=2500` boots, `data/ge007.eep` swapped per case):**
1. `./build-pc.sh ntsc-final` rc=0, no new warnings in touched files; `geEepImportEmulatorSave` defined exactly once (nm on the exe), statics folded away.
2. `scratch/eepinv/1964_orig.eep` (md5 `c9d7da5a…`) -> after boot, `ge007.eep` md5 `3ca7cf84…` = byte-identical to the in-game-proven `1964_converted.eep`; `ge007.eep.emulator.bak` byte-identical to `1964_orig.eep`; log line "6 regions converted" present, before `eeprom: loaded`.
3. `1964_converted.eep` (a port save) -> byte-unchanged (`3ca7cf84…`), no backup created, no log line.
4. `1964_orig.eep` with one byte flipped inside slot 3's CRC region (offset 370) -> "5 regions converted"; `GE_SAVELOG=1` shows slots 0-2/4 `-> OK` and slot 3 `stored_crc=51aea8db,50d2558b calc_crc=840fcfe9,c77fd429 -> BAD -> RESET (progress wiped)` — i.e. the importer left it as-is and the game's own wipe path took over (the wiped slot re-stamps to `d4ca8847,a793c0eb`, exactly the converted original's value, so the final file again equals `1964_converted.eep`); second validation pass all five OK. Backup byte-identical to the corrupted input.
5. Second boot after case 2 -> no change (`3ca7cf84…`), no log line, no `.bak2`.
6. `python tools_pc/eep_convert.py verify --as pc` on case 2's result: all six regions OK.
**Owed (maintainer):** by-eye mission select after importing a real 1964 save (all levels shown as in the emulator).
**ROADMAP row text (not edited here — applies in the main checkout):** "Emulator saves load directly in the game (no tool for import) | verify (D514, by-eye mission select owed) | D492; `port/src/eepimport.c` called from `geEepromLoad`; D297 `legacycrc.c` same pattern | Copying a 1964/Project64 `.eep` in as `data/ge007.eep` works: on first launch each region (block 0 + 5 slots) that validates in emulator format and not port format is converted in place (same rules as `eep_convert.py`, CRCs from the game's own `fileGenerateCRC`); regions invalid in both are left to the game's wipe; port saves untouched. Original backed up to `ge007.eep.emulator.bak` (never overwritten). On-disk format stays port format. Export stays with `eep_convert.py`."
## D515: empty arch tag from a fresh build dir (`ge007..exe`) — the re-exec env drops `PROCESSOR_ARCHITECTURE` (build infra; D513 follow-up)
**Symptom.** The first build in a fresh worktree configured through
`build-pc.sh`'s PowerShell re-exec cached `CMAKE_HOST_SYSTEM_PROCESSOR ""`
and `CMAKE_SYSTEM_PROCESSOR ""` in `build-*/CMakeFiles/<ver>/CMakeSystem.cmake`.
`cmake/TargetArch.cmake` takes `CMAKE_SYSTEM_PROCESSOR` when not
cross-compiling, so `TARGET_ARCH` came out empty: configure printed
`Target arch: (64bit=FALSE)` / `Output binary: ge007.` and the link produced
`ge007..exe`. The binary was a **correct 64-bit build** — `PLATFORM_64BIT`
comes from `port/include/platform.h`'s own `__x86_64__`, not the CMake value
(only `tools_pc/d88_layoutprobe.c` reads the CMake side) — so only the name
and tag were wrong. Found during T7 (D513 build note); existing build dirs
were immune because the value was cached from an earlier good configure.
**Root cause.** The stripped re-exec env (the msys→native conversion that
also dropped PATHEXT/ComSpec, AGENTS.md 2026-09-30 notes) can drop
`PROCESSOR_ARCHITECTURE`, which CMake reads to fill
`CMAKE_HOST_SYSTEM_PROCESSOR`. Same bug class, one more missing var.
**Fix (`fix/build-arch-tag`).**
1. `build-pc.sh` re-exec `.ps1`: next to the PATHEXT/ComSpec restore, restore
`PROCESSOR_ARCHITECTURE` from the machine environment
(`[Environment]::GetEnvironmentVariable('PROCESSOR_ARCHITECTURE','Machine')`,
fallback `'AMD64'`); `SystemRoot` and `windir` the same way (fallback
`C:\Windows`). One diag line logging all four vars goes to
`build-*/ge007-native-reexec-diag.log` (kept permanently; cheap, useful).
2. Belt and braces in `cmake/TargetArch.cmake`: when not cross-compiling and
`CMAKE_SYSTEM_PROCESSOR` is empty, ask the compiler
(`${CMAKE_C_COMPILER} -dumpmachine`, same regex as the cross-compile
branch); if that is empty too, `message(FATAL_ERROR)` naming this bug
instead of silently producing `ge007.`. The non-empty path is unchanged.
**Verified (Git Bash agent shell).** Note on reproduction: this harness's
msys→native conversion passes the full env through (71 vars intact; a plain
forced re-exec kept `PROCESSOR_ARCHITECTURE`), so the stripped-env case was
simulated with `env -u PROCESSOR_ARCHITECTURE` — the same mechanism D513 hit.
1. Before the fix, fresh dir + forced re-exec (`TMP='C:\Windows\'`) +
stripped env: reproduced `ge007..exe`, diag showed
`PROCESSOR_ARCHITECTURE=[]`, `CMakeSystem.cmake` empty/empty.
2. After the fix, same command: diag shows `PROCESSOR_ARCHITECTURE=[AMD64]`
(restored), `Target arch: x86_64 (64bit=TRUE)`, `ge007.x86_64.exe`,
`CMakeSystem.cmake` AMD64/AMD64.
3. After the fix, direct path (normal TMP), fresh dir: same as 2.
4. TargetArch fallback on its own: MSYS2 cmake + gcc + Ninja, stripped env,
fresh dir → `CMakeSystem.cmake` still empty/empty but
`Target arch: x86_64 (64bit=TRUE)` via `-dumpmachine`.
5. Incremental rerun in 2's dir: one-time relink (cache normalisation), then
`ninja: no work to do`, rc=0.
**Owed / notes.** `../gh-fullhist-wt-t7/build-pc` still holds the empty cached
value and a `ge007..exe` — it needs one reconfigure (or a fresh build dir);
left untouched by this fix. A build dir configured before the fix is fine to
keep: the next `./build-pc.sh` run reconfigures in place and picks up x86_64.
## D516: Control style Original offers the game's own styles, not the D498 presets (port-only; D513/D498 follow-up)
**Problem (maintainer live pass 2026-10-04).** With Control style = Original the Controller page still listed the D498 port presets ("1.1 Jinx", "1.2 Christmas", ...). They reuse the N64 numbering but are port layouts, so in Original they read as "modern", and the game's real styles could only be picked on the watch.
**Change (port-only: `optionsoverlay.c`, `watchsettings.c/.h`, `input.c`, README).**
1. New `Row.schemeOnly` (1 = Ext only, 2 = Original only; resolved to a pointer at init, checked in both the F10 visibility builder and `optionsRowIsShown`, so the front-end PC Options screen follows). `Input.PadPreset` and the Custom pad-bind rows are Ext-only; Ext keeps exactly today's rows.
2. New row "Controller style" (`Bond.Control`, Original only; 1.1 Honey, 1.2 Solitaire, 1.3 Kissy, 1.4 Goodnight, 2.1 Plenty, 2.2 Galore, 2.3 Domino, 2.4 Goodhead, the names in front.c's `MP_controller_configuration_table`). It is a new watch field `WATCH_SETTING_CONTROL` (3-bit value in `save->options` bits 8-10, `OPTION_CONTROLTYPE`), so it uses the same queue -> game-thread apply -> `watchSettingsPersistField` path as the other Bond rows. Seat = the Controller page's player (`watchSettingsSetControlSeat`, set where `s_padSeat` changes). Solo applies via `cur_player_set_control_type` (the watch's own setter) and persists to the Bond file; split-screen writes the seat's player struct directly (D416 mirror, no `g_CurrentPlayer` swap) and does not persist (file2.c forces 1.1 for 2+ players, as on the N64). No new ini key.
3. **Front end / no live player for the seat: the row reads "(in a mission)"** and does not respond (`watchSettingsRead` returns -1 from the game-thread snapshot; the style lives on the live player, and the front end has none). Seats beyond the player count are the same.
4. Button layout in Original: `padPresetEff()` returns 0 unless the stored preset is Custom, so the stored 1.2/1.3/1.4 preset no longer permutes sticks (`stickSwap`, `stickSolitare` fallback). Custom binds still apply but are hidden (edited in Ext only). The table build itself is unchanged: only `padPreset == 3` ever changed buttons, so "preset 0 mapping" is just the non-custom default table.
5. Per-dialog "Reset to defaults" skips `Bond.Control` (the save's style is not a port setting); `GE_WSPROBE_RESET` skips it too.
**Verified (headless, `-level_09`, `GE_QUITFRAME`).** Build rc=0, no warnings in touched files. Ext: `GE_OPTIONTREEPROBE` PASS, `GE_PRESETPROBE` PASS, `GE_WSPROBE_RESET` 6/6 sections, saved bytes OK; no ini key added (written ini unchanged); `padPresetEff()` is `padPreset` whenever `ControlScheme` is 0. Original: tree probe + preset probe PASS; `GE_D516=<n>` (TEMP hook, queues the row through the real watch path) took the live style 1 -> 2 and 2 -> 7, `d516: post live=N read=N saved=N`, the D513 "original control style" log line followed (7 logs the dual-controller note), `watchsettings: persisted field 8 in Bond file 1`; rows: "Layout preset" and pad binds hidden, "Controller style" shown (Ext: the reverse). Front end, Original: screenshot shows "Controller style (in a mission)", no crash. 60 s `-level_09` smoke rc=0.
**Live pass (maintainer, 2026-10-04).** F10 in a mission: set Control style to Original and cycled "Controller style" with left/right on the pad -- works, "mostly fine". Two same-day follow-ups landed on `feat/original-styles`: (a) opening the popup while the row is n/a (front end, no live player -> selection -1) overflowed the overlay display list and froze it ("DL overflow", "unknown GBI opcode") -- the popup now refuses to open while the row is unavailable; (b) the Controller page plus the open 8-item popup needed ~8.5k commands against the 8192-command buffer (`DL overflow (8546)` right after the dropdown opened, then freeze) -- buffer raised to 16384. Adjusting the style in-game dropped the fps while held -- a separate pre-existing bug, **D517**. **Split-screen 2P live pass (maintainer, 2026-10-04):** different styles per seat run correctly in a mission ("all good"). Still owed: the 2.x second-pad pass (a real second controller feeding the second stick).
**Merged 2026-10-04:** `feat/original-styles` (D516 + D517 + D518) merged into `release/v0.4.1` @ 91595597; the branch was deleted. The temporary `GE_D516`/`GE_D516_SEAT` probe in `watchsettings.c` is stripped before the release (ROADMAP scaffolding row).
## D517: Holding left/right on a Bond settings row dropped the fps -- every hold-repeat step committed (D489 staged sliders only)
**Problem (maintainer live pass 2026-10-04, during the D516 test).** In a mission, adjusting "Controller style" (or any other Bond row: the Look/AutoAim/AimControl/Sight/LookAhead/Ammo toggles) with left/right on the pad dropped the frame rate for as long as the direction was held.
**Root cause.** D489 made *sliders* stage their steps and commit once on release (`s_adjWatchField` + `optionsAdjustCommitPending()`), because "each commit is three joyDisablePoll/joyEnablePoll handshakes plus an EEPROM file rewrite on the game thread". The other value kinds -- ROW_TOGGLE, ROW_ENUM (incl. the new `Bond.Control`), ROW_MSAA, ROW_FPSCAP -- still called `rowSet()` (commit = 1) on EVERY step. Each commit queues through to the game thread's `watchSettingsPersistField()` -> `fileWriteSave()`. Hold-repeat fires every 4 frames, so a held direction kept the game thread persisting: reproduced headlessly with a pad physically held -- `GE_PERFSTAT` showed 60 fps baseline vs 40-50 fps while cycling, and the log shows the style walking 1->0->7->...->3 with `watchsettings: persisted field 8 in Bond file 1` 57 times over a 900-frame run. (The overlay's own render cost is not the cause: `dl=` stays ~0.3 ms with the heaviest page open.)
**Fix (port-only, `optionsoverlay.c`).** New `rowSetStepWatch()`: a stepped row that has a watch field now stages exactly like a slider (commit = 0; switching the pending field commits the previous one first), and the existing `optionsAdjustCommitPending()` -- which already runs every frame while left/right is not held in BOTH UIs (F10 `overlayHandleInputLocked`, frontoptions release path) and on F10 close -- saves once on release. Non-watch rows fall through to a plain commit, so ini rows are byte-for-byte unchanged. The dropdown popup confirm (`ddConfirm`) still commits immediately: one deliberate pick, not a hold.
**Verified.** Build rc=0, no warnings in touched files. Headless: `GE_OPTIONTREEPROBE` PASS + `GE_PRESETPROBE` PASS (front), `GE_WSPROBE_RESET` stage 6/6 sections + saved bytes OK; `GE_PERFSTAT` baseline still 60 fps with the overlay closed. **Live pass (maintainer, 2026-10-04):** confirmed fixed -- "those things are fixed".
## D518: The pad layout preset was one global value -- setting another player's preset changed the main player's mapping too
**Problem (maintainer report 2026-10-04, during the D516/D517 stress test).** In single player, opening Select player -> Player 2 and changing that seat's "Layout preset" also changed P1's live mapping -- "I don't want it to affect at all".
**Root cause.** `Input.PadPreset` was a single global int in input.c; the per-seat Controller pages (D469 Select player, `s_padSeat`) all edited the same value, and `padTabGet()` built every seat's table from it. The custom binds were already per seat (`Input.Pad[N].<Action>`); only the preset was shared.
**Fix (port-only).** input.c: `padPreset[MAX_PADS]`; ini keeps `Input.PadPreset` as **seat 1's** key (existing inis keep their P1 value) and adds `Input.PadPreset2/3/4` (default Jinx). All the preset-derived helpers (`padPresetCustom`, `padPresetEff`, `stickSolitare`, `stickSwap`, `styleIsSolitare`) take a seat; every call site is inside `inputComputePadSlot(int idx, ...)` and passes its own slot. The table signature hashes all four presets (custom bind strings included when any seat is Custom). New accessor `inputPadPresetPtr(seat)`. optionsoverlay.c: new `padSetSeat()` re-points the "Layout preset" row's `ptr` and the Custom-only bind rows' `showPtr` at the selected seat's value; all three seat-change sites (F10 link open, front-end `optionsRowLinkOpened`, ROW_PADSEAT step) go through it. GE_PADMAPTEST now drives the per-seat array (all seats Custom in the custom phase).
**Verified.** Build rc=0. Headless: `GE_PADMAPTEST` PASS (seat-independence checks c1-c11), `GE_OPTIONTREEPROBE` + `GE_PRESETPROBE` PASS, and a temporary `Input.PadPreset2 = 4` in the ini produced exactly `pad table rebuilt (presets 0/4/0/0)` (ini restored byte-identical after). **Live pass (maintainer, 2026-10-04):** single player, P2 preset changed -- P1's page and mapping untouched, bind rows follow the per-seat Custom state; "good". **Split-screen 2P live pass:** each seat ran its own preset in a mission ("all good").
## D519: One PC options UI -- the file-select "PC Options" entry opens the F10 overlay; the MENU_PC_OPTIONS screen is gone (port-only)
**Why (maintainer idea 2026-10-04).** Two PC options UIs (D343 front-end screen, D504-D512 F10 overlay) meant two navigation implementations, two probe sets, and the `frontOptionsBlocksOverlay` gate.
**Change.** `port/src/frontoptions.c` is now only the file-select entry label: a click (A/Z/Start) calls `optionsOverlayToggle()`; file select stays underneath and its idle timer is held (`g_MenuTimer = 0`, already in the hook) while the overlay is open. Gaps found by the P4 design pass: (1) Bond-file rows ("Controller style", profile chooser) were shown only on the old screen -- `optionsRowIsShown` and the chooser's left/right now key on `current_menu == MENU_FILE_SELECT`; they use the same data path as before (`watchsettings.c` `frontFolder()`: chosen file > `selected_folder_num` > first valid). (2) The watch chooser was keyed to `MENU_PC_OPTIONS`; re-keyed likewise.
**Deleted.** The screen's init/update/interface/draw, paging, hover/pad/keyboard navigation (about 800 lines), the `MENU_PC_OPTIONS` enum value, its four `front.c` dispatch/draw sites and their `#ifdef PORT` blocks, `frontOptionsBlocksOverlay()` and its gates in `video.c` (F10) and `input.c` (pad Select), and the `input.c` keyboard/wheel branch for that screen. **Kept in `src/game`:** the single `#ifdef PORT` call to `optionsFileSelectLabel()` in `constructor_menu05_fileselect` (it is the entry itself; the D343 Rule-2 sign-off covers it) and the D399 `copypos` shift. `pcOptionsKeyboardPad` in `input.c` is now only used by the self-tests.
**Probes.** Retired `GE_FRONTOPTIONS_SECTION`, `GE_FRONTNAVLOG`, `GE_FRONTOPTS_WIDTHS`, `GE_FRONTOPTIONS_AUTO`; added `GE_FILESELECT_OVERLAY=<n>` (open; n > 1 closes after n frames) because nothing else opens the overlay from file select headlessly.
**Verified (headless, 4 build cycles).** Build rc=0. Front end with the overlay open: `GE_OPTIONTREEPROBE` PASS, `GE_PRESETPROBE` PASS, `GE_WSPROBE_RESET` front path all sections failures=0. Frame captures of file select with the overlay open (tree over the folders) and after the close (plain file select, no drop to title). In a level (`-level_09`, 60 s, `GE_QUITFRAME=3600`): tree + preset PASS, stage reset probe failures=0, saved bytes OK, exit 0. **Owed (maintainer, by eye):** mouse, keyboard and pad on the file-select overlay, the profile chooser and Controller style rows with a real profile, and the overlay footer text overlapping the Copy/Erase bar.
**Follow-ups 2026-10-04 (P4j; maintainer by-eye pass found 4 faults + 1 request).** (1) *Profile chooser missing:* it was never an overlay row -- the old screen drew a virtual "Profile" row at the top of every page (not in the row table), so the re-key in D519 had nothing to show. Now `__BondFile` (ROW_BOND_FILE) is a real table row that `overlayUpdateVisible` injects as the first row of every page that holds profile-backed rows (Audio, Controller, Game), on file select only; left/right/accept step the chosen file. Verified headless (`GE_OVNAV` log: chooser visible at row 1 of Audio; Game capture shows "Profile 1" first). (2) *Pad double-press:* the overlay's edge state (`prev*`) was zeroed while closed, so the A (or click/Enter) that opened it counted as a fresh press on the first handled frame; now the first frame after any open adopts the held buttons as "previous" (`s_seedPrev`). Code-verified only (no headless pad). (3) *Crosshair pointer froze:* the overlay branch in `input.c` returned before the front-end pointer block; now, on a front-end screen with the mouse enabled, `overlayFrontEndCrosshair()` maps the OS mouse to the game's cursor with an exact (non-inset) window-to-canvas map, so the drawn crosshair is where the overlay hit-tests, and the OS cursor is hidden (one pointer). Not verified headless (needs a real mouse). (4) *Hint overlapped Copy/Erase:* the hint lines now sit in a box-body panel under the card (drawn with the card, before any text: a fill after text turned the glyphs into blocks) and are fitted to the box width. The bar is still faintly visible through the translucent panel (a darker underlay would fix it). (5) *Resume where you left off:* `optionsOverlayToggle` records section / focused row / scroll / pad seat on close and restores them on open when the row is still shown, else the top of its section, else the root; backing out to the root remembers the root. Verified headless with `GE_OVNAV`. New harness token hook `GE_OVNAV` (O toggle, D/U, A open, B back, P log; one token per 20 file-select frames). Side note: one reset-probe run on a real two-file eep logged `Bond.LookAhead = 0, want 1` for the Game section (failures=1); two reruns passed -- looks state/timing dependent, not touched by this change, left open.
## D520: F10 overlay slider wedge, marker and dropdown arrow antialiased (port-only; D504 follow-up)
**Problem (maintainer by-eye pass 2026-10-04).** The overlay's sliders and dropdown arrows looked more aliased than the PD port's menus. **Cause (P4k investigation, local note `OVERLAY-AA-INVESTIGATION.md`):** the overlay lays out on a 320-wide canvas scaled to the window (`ovScale`), and `drawWedge` / the dropdown tick built their diagonals from integer-pixel fill rects (2-px columns, whole-pixel heights), so every stair step is `ovScale` screen pixels (4 px at 1280, 8 px at 2560). PD draws the same shapes as triangles (`menugfx.c:861-906`) that MSAA smooths. Text was not the issue (bilinear-magnified glyphs: soft, not jagged).
**Fix (`port/src/optionsoverlay.c`).** `drawWedge`: the solid body as runs of equal whole height plus one coverage-alpha top pixel per 1-px column; the marker and the dropdown arrow via `drawTriDownAA` (per-pixel area coverage from 4 sub-rows). Render state is set once per shape (`aaBegin`) and each piece is 2 commands (`aaPiece`, pieces under alpha 6 skipped), so the display-list size stays about where it was (D516 overflow history; `OV_BUF_CMDS` 16384). Applies in every Display mode (maintainer: the overlay is port UI, not part of the N64 picture).
**Verified.** Build rc=0, no warnings; `GE_OPTIONSOVERLAY=1 GE_OPTIONSOVERLAY_SECTION=__HdrAudio GE_PCDUMP=300-300` before/after: the ramps go from ~7 visible steps to a smooth ramp, markers/arrows softened; no DL overflow logged. **Owed:** maintainer by-eye at their window size.