Input and audio features D469-D472

Gamepad presets and per-seat rebinding (D469, building on
italoarruda's #109), master volume and output
device (D470), controller-family button names and player LEDs (D471), F10
owner-pad input and widescreen pillarbox polish (D472).

Refs: D469 D470 D471 D472

Co-authored-by: italoarruda <3868006+italoarruda@users.noreply.github.com>
This commit is contained in:
James Dansereauanditaloarruda committed 2026-10-06 18:35:41 -04:00
1 parent 9dd98bdb20
commit b735190fd9
24 files changed
+1285 -521

No files matched your search

+5 -2
View File
@@ -33,6 +33,7 @@ Two classes:
| `GE_D204` | `port/src/audio.c` (`audioSetNextBuffer`) | **Audio-health monitor (D204).** One line per 5 s of wall clock: `rt=` produced-audio/wall ratio (1.000 healthy; below 1 = the 30 Hz audio retrace is starving and the device is padding with silence), `q=` SDL queue depth / `queueLimit` (pinning at the limit with `drop=` climbing = overproduction, the "eventually no audio" state), `drop=` blocks discarded, `max=` largest block vs its `g_MaxFrameSize*4` allocation. **Deliberately cheap (~30 clock reads/s) — safe to leave on for a full playtest, unlike `GE_MIXERTRACE`.** The instrument of record for D202. | **live** |
| `GE_D204_OLD` | `port/src/audio.c` (`d204OldMode`) | Restores pre-D204 behaviour (`osAiGetLength()` reports the whole SDL queue depth; `queueLimit` back to 8192) so before/after can be A/B'd in **one binary**, without build-to-build variance. Temporary — remove when D204 closes. | **dead** (D204 closed) — removed 2026-09-30 (ROADMAP §7 strip pass, batch 1, branch `chore/scaffolding-removal-1`); tombstone — the generator flags it GONE |
| `GE_AUDIOTRACE` | `src/snd.c` (`sndPlaySfx`, `sndDeactivate`), `src/game/propobj.c` (door sounds), `src/libultra/audio/load.c` (`alLoadParam`) | Per-`sndPlaySfx` resolution trace to `audiotrace.log`: `soundIndex` → `ALSound*`/keyMap/wavetable/base/len/book, the returned `ALSoundState*`, every `sndDeactivate`, and wavetable bind events. M-65 additions: `[VOICE+]`/`[VOICE-]` pairing every voice acquire/release with sound/state/flags (NB: written from two threads to one unbuffered FILE* — occasional torn lines, parse defensively), `[SLOTWRITE]` door slot ownership, `[RETRIGGER-POST]`, `[WAVELOOP]` (per-sound `ALADPCMloop` presence), `[DOORSND]` every door sound state change incl. an `ORPHAN-both-slots-busy` verdict. M-66b addition: `[EXPIRE]` — posted whenever the port-side ownerless-infinite-loop expiration is scheduled (sound/state/delay/fade); this line is **permanent** (part of the D202 option-C fix), unlike the rest of the probe set which is removable on close. M-67/M-69 additions: `[WIRE] t=µs filter=… <- table=…` (`src/libultra/audio/load.c`, every wavetable→decoder bind, µs-timestamped) and `[VOICEMAP]` (`src/libultra/audio/synthesizer.c`, full PVoice→decoder/resampler/env-state address map at synth init — this is how offline voicedump slot addresses tie to WIRE filter addresses). M-68 additions: `[EVT] t=µs type=N state=P` + `[STOP-EVT] … deltaUs=N` (event types per `src/snd.h`: 1=PLAY, 2=STOP, 8=VOL, 64=DECAY, 128=END). M-71 additions: `[VOL] t=µs state=… rawVol=… playing=…` (`src/snd.c`, per-VOL-event value before the slot-volume law) and `[DISTVOL] pos=(…) player=(…) dist=… vol=…` (`src/game/propobj.c`, the distance-attenuation funnel `sub_GAME_7F053894` — this is how an SFX's final volume is computed from hit position). NB: lines from the multiple file handles interleave — occasional torn/corrupt lines, parse defensively. `dumppos=` gives the exact byte offset into `audiodump.raw` (quantised to the 2880-byte audio block). Low cost. | **live** (D202/D205) |
| `GE_AUDIOGAINLOG=1` | `port/src/audio.c` (`audioSetNextBuffer`, cached `GE_ENVFLAG`) | D470: once per second logs `D470 gain: master=N peak=P`, the max abs sample of the final (post master-volume) buffer. Cheap; shows the gain effect headlessly. | **live** |
| `GE_PULLTRACE` | `src/libultra/audio/load.c` (`alAdpcmPull`) | `[PASTEND]` assertion: fires if a voice's `f->sample` exceeds its wave's total samples while `nOver == 0` (i.e. an unstopped voice keeps decoding past the end instead of zero-filling). Rate-limited to 40 lines. M-65: 0 hits — exhausted voices go silent correctly. | **live** (D202) |
| `GE_DMEMWIPE` | `port/src/mixer.c` (`aClearBufferImpl`) | Zeroes the whole software DMEM at the frame boundary (detected as `alMainBusPull`'s clear of `AL_MAIN_L_OUT`, addr 1088). Tests whether audio state survives across frames in DMEM. M-65: no-op on output — ruled out. | **live** (D202) |
| `GE_NOWET` | `port/src/mixer.c` (`aSetVolumeImpl`) | Mutes the reverb send (`wetamt = 0`), isolating the reverb delay lines as a possible source of persistent signal. M-65: drone unchanged — ruled out (NB `aPoleFilterImpl` is still an unimplemented no-op, D199). | **live** (D202) |
@@ -74,7 +75,7 @@ Two classes:
| `GE_D245_OLDVTX=1` / `GE_D245_OLDTEX=1` | `src/game/sky.c` (`skyPortRenderPoly`) / `port/fast3d/gfx_pc.cpp` (CI8 import + tri-path dims) | **A/B** (M-201): restore the pre-fix s16 sky/water vertex path / the pre-fix 32x43, 16-wide water texture geometry. | **dead** (D245 fixed) — removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE. The pre-fix s16 sky/water vertex path (`skyPortRenderPoly` tail + `skyPortEmitTileShift`) went with it; the float path is now unconditional |
| `GE_DYNTEXHASH_OFF=1` | `port/fast3d/gfx_pc.cpp` (`import_texture` cache key) | **A/B** (M-201, D341): disable the content-hashed cache key for dyn-pool textures (reverts to the stale address-only key). | **dead** (D341 closed) — removed 2026-09-30 (ROADMAP §7 strip pass, batch 1, branch `chore/scaffolding-removal-1`); tombstone — the generator flags it GONE |
| `GE_D207S` | `src/game/gunfire.c` (player shot-sound block, `#ifdef PORT`) | **TEMP** (M-201): one line per player shot-sound decision: `D207S t=<g_GlobalTimer> hand item rate -> H1|H2|SKIP` (SKIP = both per-hand sound handles still held). Read-only, uncapped, cheap; use instead of `GE_AUDIOTRACE` for live captures (that probe does file I/O on the audio path). | **dead** (D207/D241 closed) — removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE |
| `GE_D306C="f0 f1 [wmax]"` | `port/fast3d/gfx_pc.cpp` (after `zf_note_emit`) | **TEMP** (M-201): depth-state census of every emitted triangle in frames f0..f1 (GL depth mode, geometry_mode, other_mode_l, tile-0 texture, NDC centroid, z/w range, min w); optional `wmax` keeps near geometry only. Cap 60000 lines. | **TEMP** (D306 closed; remove post-release) |
| `GE_D306C="f0 f1 [wmax]"` | `port/fast3d/gfx_pc.cpp` (after `zf_note_emit`) | **TEMP** (M-201): depth-state census of every emitted triangle in frames f0..f1 (GL depth mode, geometry_mode, other_mode_l, tile-0 texture, NDC centroid, z/w range, min w); optional `wmax` keeps near geometry only. Cap 60000 lines. | dead — fully removed 2026-10-01 (ROADMAP §7 scaffolding strip, batch 2; kept here as a tombstone) |
| `GE_INTROCAM=<n>` | `src/game/bondview_r.c` (`#ifdef PORT`, intro camera pick) | **TEMP/test-only**: force the level intro's first camera index (Dam truck shot = 5). The PRNG draw is still consumed. Logs `D306: intro camera index` when set (or with `GE_D306C`). | **TEMP** (useful repro tool) |
| `GE_STARTWEAPON=<ITEM_IDS>` | `src/game/bondview_r.c` (`#ifdef PORT`, after the start-inventory setup) | **TEMP/test-only** (M-201, D303): adds the item + 400 rounds of its ammo and makes it the starting right-hand weapon (M16 = 13). Pair with `GE_INPUTSCRIPT` `ZHOLD`. | **TEMP** (useful repro tool) |
| `GE_D235` | `src/boss.c` (per-stage `while (!done)` block, after `memaReset`; `#ifdef PORT`) | D235 evidence probe: logs how many GL-texture-cache entries the per-stage `videoResetTextureCache()` call purges at each level load. A cold boot into a level should log 0; a death/restart (or any transition from a level that uploaded textures) logs >0 — direct evidence of the stale-entry mechanism. The clear itself is **not** gated on this var (it's the fix, see below); the var only controls the log line. | **live** (D235) |
@@ -89,8 +90,10 @@ Two classes:
| `GE_PDMOUSEAIM` | `port/src/input.c:487` (`pdMouseAimEnabled`) | Force on/off the PD-style mouse-aim model (D194 lineage); unset = default (on in N64 aim mode unless legacy GEPD aim is selected). | **live** |
| `GE_QUITFRAME` | `port/src/video.c:966` | D344 harness clean self-quit: exits after frame `<n>` completes and swaps (replaces `timeout` kills; the host never exits under an in-flight frame). Cached. | **live** (harness) |
| `GE_PADMAPTEST` | `port/src/input.c` (`inputInit` -> `padMapSelfTest`) | D469: boot-time self-test of the gamepad action table. Proves preset 0 == the legacy hard-wired mapping (20,971,520 synthetic cases), logs the per-source mask table, a Custom remap, per-seat independence, the Start guard and the binding API; restores config afterwards (nothing persisted); the game then continues. Cached (`GE_ENVFLAG`). | **live** (harness) |
| `GE_D466LOG=<n>` | `port/src/drawdistgameplay.c` (`portD466FrameLog`, called from `bgDetermineVisibleRooms`) | D466 dev probe: every `<n>` calls (per player view) logs `D466 f=.. active= ext= auth= subset= hext= hauth= chrOn= chrGp= seen= hgame=` to stderr: extended vs authored room-set counts + hashes, ONSCREEN chrs vs gameplay-on-screen chrs, `HAS_BEEN_ON_SCREEN` count, and a chr-state hash. Cached. | **live** (D466 dev probe) |
| `GE_D468_SCALE=<0.05..1>` | `port/src/drawdistgameplay.c` (`portD468UpdateClamp`) | D468 dev falsifier: multiplies the AI-view-clamp fractions (and so turns the clamp on even at 16:9 / FOV 100), so a headless `GE_D466LOG` run can show the authored room set and gameplay on-screen chrs shrinking. Cached; out-of-range values ignored. | **live** (D468 dev probe) |
| `GE_PADFAMILY=xbox\|ps\|nintendo` | `port/src/input.c` (`inputPadFamily`) | D471 dev override: force the controller family used for button names in the PC menus (headless screenshots without the hardware). Cached. | **live** (D471 dev knob) |
| `GE_D466_FORCE=1` | `port/src/drawdistgameplay.c` (`portD466Active`) | D466: run the authored-distance split even at DrawDistance 100% (identity test: authored set must equal extended). Cached. | **live** (D466 test hook) |
| `GE_D466_OFF=1` | `port/src/drawdistgameplay.c` (`portD466Active`) | D466 kill switch: disable the gameplay/render split (every gameplay reader falls back to the extended-range state, i.e. the pre-fix #125 behaviour). For A/B only. Cached. | **live** (D466 kill switch) |
| `GE_CRASHTEST` | `port/src/crash.c` (`crashTestMaybe`, called at the end of `crashInit`) | Deliberate-crash hook for verifying the crash handler: `GE_CRASHTEST=<N>` recurses N frames deep (capped at 64) right after the handler is installed and faults with a null write. `ge007.crash.log` should then show `#00`..`#N-1` in `crashTestRecurse`, then `SDL_main`, CRT and system frames; each frame line carries `va=0x…` for `addr2line -f -p -e build-pc/ge007.x86_64.exe <va>`. Unset/0 = no effect. | **live** (harness) |
@@ -146,7 +149,7 @@ Two classes:
| `GE_D318R` | `port/src/random.c:69,147` (`#ifdef PORT`) | PRNG golden probe (D284/D318): logs the first 4000 gameplay-PRNG draws with tick + caller return address, and every `randomSeed` set — verifies the bit-exact port of `random.s` and which call sites consume the stream. Inert unset. | **live** (D284 verification) |
| `GE_RSEED` | `port/src/random.c:134` (`#ifdef PORT`) | Test-only boot-seed override: `GE_RSEED=<hex64>` pins `g_randomSeed` at first seed instead of the wall-clock-derived value — for determinism and N64-parity tests. Inert unset. | **live, test-only** |
| `GE_RSEED_LV` | `port/src/d318watchdog.c:232` (`#ifdef PORT`) | One-shot level-start seed log: logs `g_randomSeed` at the first tick after level start (optionally armed for a specific tick via `<hex>@<tick>`) — ties a saved/loaded game's PRNG state to a level for D318-class repro. Inert unset. | **dead** (D318 closed) — removed 2026-09-30 (ROADMAP §7 strip pass, batch 1, branch `chore/scaffolding-removal-1`); tombstone — the generator flags it GONE |
| `GE_D318T` | `port/src/d318watchdog.c:270` (`#ifdef PORT`) | D318 watchdog timeline probe: per-tick log of the Facility ai_22 softlock signature window (c78 list/offset/attack state, c67 health) while the watchdog watches. Inert unset. | **live** (D318 verification) |
| `GE_D318T` | `port/src/d318watchdog.c:270` (`#ifdef PORT`) | D318 watchdog timeline probe: per-tick log of the Facility ai_22 softlock signature window (c78 list/offset/attack state, c67 health) while the watchdog watches. Inert unset. | dead — fully removed 2026-10-01 (ROADMAP §7 scaffolding strip, batch 2; kept here as a tombstone) |
| `GE_D322` | `port/src/audio.c` (`audioSetNextBuffer`), `src/snd.c` (`sndD322PoolSummary`) | D322 pool-telemetry probe: one line per 5 s of wall clock with the three pool layers a long session can exhaust — `sfx=count/max` (8-voice SFX soft cap), `evtq=n/64` (event queue occupancy; full = silent post drops, M-71), `pv free/lame/alloc` (24 physical synth voices shared by SFX + music; `-1`s before sndInit), plus the D204 pipeline fields. Cheap (~30 reads/s), campaign-safe; read together with `GE_D204=1`. | **live** (D322 capture) |
| `GE_D318W` | `port/src/d318watchdog.c` (`d318WatchdogTick`, `#ifdef PORT`) | **The D318/D320 pin detector, ON by default, DETECT-AND-LOG ONLY since 2026-09-30.** Watches the Facility ai_22 / ai_19 and Control ai_9 hold-pose pin signatures (findings D318/D320) and logs `D318W:` lines (first detection, confirmation after 600 ticks, periodic reminders while held). It used to re-seed the attack via `sub_GAME_7F025560`; that recovery was removed because D329 fixed the cause, so a hit now means a D329 regression. `GE_D318W=0` silences it. | **live** (D329 regression detector) |
| `GE_D294` | `src/boss.c:498` (`#ifdef PORT`) | One line per stage reset: stage number + current MA malloc value (D294 pool investigation). | **live** (D294) |
+4
View File
@@ -328,4 +328,8 @@ D465,FIXED,#127 Dam/Caverns/Complex water slid side to side:,"FIXED 2026-10-01 (
D466,FIXED,#125 Xenia's boss fight on Jungle triggers early:,"FIXED 2026-10-01 (fix/d466-drawdist-gameplay, local); RULE-2-SIGNOFF (port-feature leak fix; design-review approval); verified headless (room sets, on-screen counts), player-facing shot/autoaim/use/p…"
D467,CLOSED,#130 Golden Gun empty click with each shot:,CLOSED: faithful by code + log (audited 2026-10-01; by-ear check of held-trigger behaviour vs 1964 is maintainer-owned)
D468,FIXED,AI view clamp: under ultrawide (> 16:9) or Video.FovScale > 100 the AI's on-screen tests used the wider rendered frustum; they now keep the cartridge-widest vi…,"FIXED 2026-10-01 (feat/d468-ai-view-clamp, local); RULE-2-SIGNOFF (policy approved by the maintainer 2026-10-01); verified headless (clamp on/off switching + falsifier)."
D469,FIXED,Gamepad presets + per-seat controller rebinding (ROADMAP 5a; supersedes outside PR #109):,"FIXED 2026-10-01 (merged into release/v0.4.1, local); preset 0 == old mapping proven (20,971,520 cases); live pad test owed (maintainer); Jinx 1.2/1.3 undefined (gap)"
D470,FIXED,Audio master volume + output-device selection (ROADMAP 5a):,FIXED (live listening test owed)
D471,FIXED,Controller-family button names (no glyphs) in the PC menus + player-index LEDs:,FIXED 2026-10-01 (local); headless captures for Xbox/PS/Nintendo; live PS/Nintendo pad check owed
D472,FIXED,"F10 overlay polish: multi-pad ownership (audit G1-G3), widescreen pillarbox (D335b), red EXPERIMENTAL rows, unknown-ini-key warning.","FIXED 2026-10-01 (merged; port-only, no src/game)."
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.
328 D466
329 D467
330 D468
331 D469
332 D470
333 D471
334 D472
335 D450
+73 -1
View File
@@ -736,6 +736,10 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D466 | **#125 Xenia's boss fight on Jungle triggers early: `Video.DrawDistance` (default 250%) leaked into GAMEPLAY visibility (AI "on screen" / "seen" / target-in-sight / out-of-view spawn all read state computed from the extended far clip)** - full `## D466` entry at file tail | FIXED 2026-10-01 (`fix/d466-drawdist-gameplay`, local); RULE-2-SIGNOFF (port-feature leak fix; design-review approval); verified headless (room sets, on-screen counts), player-facing shot/autoaim/use/photo tests follow the render (maintainer decision); MAINTAINER-VERIFIED 2026-10-01: Jungle/Xenia at DD 400 correct, Statue spot check fine |
| D467 | **#130 Golden Gun empty click with each shot: code audit finds the path faithful (2026-10-01)**: the dry-fire click only plays when the trigger is still held ~14 ticks after the shot; no simultaneous play at 60 fps. Open: by-ear/input-length check. | CLOSED: faithful by code + log (audited 2026-10-01; by-ear check of held-trigger behaviour vs 1964 is maintainer-owned) |
| D468 | **AI view clamp: under ultrawide (> 16:9) or `Video.FovScale` > 100 the AI's on-screen tests used the wider rendered frustum; they now keep the cartridge-widest view (its own Ratio 16:9 mode at the game's FOV), as a centred sub-box of the screen; `Game.AIWideView=1` opts out** — full `## D468` entry at file tail | FIXED 2026-10-01 (`feat/d468-ai-view-clamp`, local); RULE-2-SIGNOFF (policy approved by the maintainer 2026-10-01); verified headless (clamp on/off switching + falsifier). |
| D469 | **Gamepad presets + per-seat controller rebinding (ROADMAP 5a; supersedes outside PR #109): data-driven pad action table, `Input.PadPreset` (Jinx 1.1 default / Custom), `Input.Pad[N].<Action>`, Input -> Controller page in F10 and front-end PC Options** — full `## D469` entry at file tail | FIXED 2026-10-01 (merged into `release/v0.4.1`, local); preset 0 == old mapping proven (20,971,520 cases); live pad test owed (maintainer); Jinx 1.2/1.3 undefined (gap) |
| D470 | **Audio master volume + output-device selection (ROADMAP 5a):** `Audio.MasterVolume` (0..100, scratch-copy gain on the final mix, default 100 = byte-identical) and `Audio.Device` (name, default system device; live reopen on the audio-producer thread, fallback on missing/removed device). | FIXED (live listening test owed) |
| D471 | **Controller-family button names (no glyphs) in the PC menus + player-index LEDs: PlayStation/Nintendo pads see their own button names in the F10/front-end help lines and the pad-binding column; Xbox/Steam Deck/unknown keep the old text exactly** — full `## D471` entry at file tail | FIXED 2026-10-01 (local); headless captures for Xbox/PS/Nintendo; live PS/Nintendo pad check owed |
| D472 | **F10 overlay polish: multi-pad ownership (audit G1-G3), widescreen pillarbox (D335b), red EXPERIMENTAL rows, unknown-ini-key warning.** — full `## D472` entry at file tail | FIXED 2026-10-01 (merged; port-only, no src/game). Headless-verified pillarbox/red rows/warning at 1280x720; multi-pad ownership by code reading only, live check 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. |
@@ -15893,7 +15897,7 @@ Under PORT `struct image_entry` puts `dataoffset:24` in bits 0-23 (D67), so byte
**Status:** FIXED (port-only; matrix passed); user live accept owed (play a mission ON, then OFF).
**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.
**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).
## 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.
@@ -16263,6 +16267,8 @@ Whichever loop runs `SDL_PollEvent` first gets the event, and the render loop us
**Confidence:** high that the listed readers are the gameplay consumers of the extended distance and that the authored sets equal what DD 100 computes (room hash match 694/700, the 6 others explained by phase); medium that nothing else gameplay-relevant keys off `headSwitchVisible` (see the known remaining difference); not confirmed against the actual Xenia fight.
**2026-10-01 follow-up: locomotion follows the render (maintainer decision "A").** Design review of the residual found that D466 had also routed the game's **locomotion level-of-detail** checks to the authored distance: `chraction.c` 3935 (goto-pad setup), 9334 / 9485 (gopos / patrol leave WAYMODE_MAGIC when on screen) and the `lastvisible60` / `act_gopos.unk9c` timestamps (their only readers are the magic-mode entry checks, `chraction.c:9310` and the patrol twin). With those authored but `chrTick`'s movement branches on the extended (render) flag, a guard visible only in the extended band would drop into magic mode after the timer and then run the N64's one-frame "visible magic" transition branch every tick (position snapped to the model offset, no walk animation): sliding/frozen far guards, a state the N64 never produced. These readers are presentation (how an unseen character is moved cheaply), not awareness, so they are reverted to the original code (render flag). Awareness stays authored: AI on-screen commands (`chrai.c`), sight range (`chrCheckTargetInSight`), spawn-out-of-view / pad room checks, `CHRFLAG_HAS_BEEN_ON_SCREEN`. Cost: a far guard the player can see walks its route like a visible guard (the N64's behaviour when the player stands closer) instead of the magic approximation. Jungle DD 250 headless after the change: still 8 drawn vs 3 AI-visible at frame 600-800, `seen` unchanged, rc=0.
## D457 - 64-bit ABI retypes part 2 (ABI/layout exception, 2026-09-30)
**Branches.** `fix/abi-bg-model-slots` = 4 commits (a)-(d) + this entry; `fix/abi-bg-slots-ab` = (a)+(b) only (the safe subset). No merge, no push. Census driver: `gcc -fsyntax-only -Wint-conversion -Wint-to-pointer-cast -Wpointer-to-int-cast` over compile_commands.json with the build's flags, unique file:line:col count.
@@ -16313,3 +16319,69 @@ Whichever loop runs `SDL_PollEvent` first gets the event, and the render loop us
**Bucket:** faithful (no `src/game` change, no Rule-2 request needed).
**Probes left on the branch (TEMP, `GE_D433LOG`, cached getenv):** `D433P` macro + SHOT/FIRESND/DRYCLICK/STATS hooks in `src/game/gunfire.c`. Strip before merge.
**Confidence:** high for the code path and the log; medium for "the reporter's trigger-hold explains it" (unmeasured).
## D469
**Status: DONE (port-only; live pad test owed).** Branch `feat/pad-presets-rebind` (cut from `release/v0.4.1`). No `src/game` edits.
**What.** The hard-wired pad block in `port/src/input.c` (triggers, face buttons, shoulders, stick clicks, Start, D-pad) is now a pure function `padMapPure()` over a per-seat action table. Actions (Jinx 1.1 defaults): `Fire=righttrigger`, `Aim=lefttrigger,leftshoulder`, `Use=a`, `Reload=x`, `Crouch=leftstick,rightstick`, `NextWeapon=y`, `PrevWeapon=none` (new; same A+Z backward idiom as the mouse wheel), `Gadget=b`, `Start=start`. Sticks/look, D-pad, Back (opens F10), Southpaw, trigger threshold and the menu accept/cancel (A,X -> native A; B,Y -> native B outside a playable stage) are NOT rebindable (lockout guard). Start is forced back to `start` if a Custom table unbinds it.
**Config keys.** `Input.PadPreset` 0 = Jinx 1.1 (default), 1/2 = Jinx 1.2/1.3 (reserved; resolve to 1.1; the UI offers only 0 and 3), 3 = Custom. Seat 0: `Input.Pad.<Action>`; seats 1..3: `Input.Pad2/3/4.<Action>` (`Fire Aim Use Reload Crouch NextWeapon PrevWeapon Gadget Start`). Value = up to two SDL names, comma separated (`a,x`; `none` = empty slot; positional, `none,x` keeps slot 2). Names: `a b x y leftshoulder rightshoulder leftstick rightstick start dpup dpdown dpleft dpright lefttrigger righttrigger`. A non-Custom preset ignores the strings. Seat = `pads[]` index (D448), not the controller slot: with keyboard/mouse as P1 the pad on seat 0 drives slot 1 (`padSeatForSlot`). Gating: analog trigger sources of Fire/Aim act in any state; every digital source acts only while a stage is playable (so LB aim does not fire in menus, exactly as before).
**Threading.** The poll thread (`inputComputePad`) hashes preset (+ all Custom strings) per call; on change it rebuilds the table into the spare of two buffers and publishes via `SDL_AtomicSetPtr`. No strtok; `g_CurrentPlayer` untouched (D419); getenv cached.
**UI.** Input -> `Controller...` page (shared row table, so both F10 and front-end PC Options get it): Layout preset (Jinx 1.1 / Custom), Controller seat (Pad 1-4, Custom only), one row per action with Primary/Secondary slots (D-pad left/right switches slot), Reset. Capture: A/X/Enter/click starts a modal; nothing binds until all buttons are released (arming); then the first pressed source binds. Cancel: Esc, tap B or tap Back (pad 0 or the edited pad). Clear: Delete, hold Back 0.6 s, or Y on the row (no modal). Hold B 0.6 s binds B itself. The modal flag blocks F10/Back/Start closing mid-capture; input is swallowed until buttons release afterwards.
**Equivalence evidence (preset 0 == old mapping).** `GE_PADMAPTEST=1` runs `padMapPure(preset-0 table)` against `padMapLegacy()`, a verbatim transcription of the pre-D469 block (release/v0.4.1 `input.c`, trigger/face section) over every raw source combination (2^15) x previous A/B/X/Y (16) x ctx {facePlayable, slotNative, southpaw} (8) x incoming masks {0, G, R, G|R, 0xFFFF} (5): `20971520 cases compared, 0 mismatches` (button mask, crouch, gadget edge). It also logs the per-source table (e.g. `a menu -> 8000`, `y playable -> 8000`, `b playable -> gadget=1`, `leftshoulder playable -> 0010`, `leftshoulder menu -> 0000`, `righttrigger -> 2000`, `leftstick playable -> crouch=1`), and checks: Custom `Fire=a` moves fire to A and frees RT; seat 1 stays independent; seat 2 `Use=y`; seat 3 Start guard; preset 0 ignores `Input.Pad.*`; the binding API round trip (set/clear slot positions). Run on a menu boot and on `-level_09` (1500 frames): PASS both. One non-equivalence, outside preset-0 gameplay: the overlay path now also records A/X press state for slot 0 (padActPrev), previously only B/Y, which only matters for pads-own-P1 slot 0 after closing F10 with A/X held.
**Verification run.** build ntsc-final clean (4 cycles incl. one syntax fix); launches: menu boot + GE_PADMAPTEST (rc=0, no crash log), overlay `GE_OPTIONSOVERLAY_SECTION=__HdrController` with `PadPreset=3` + `GE_PCDUMP` frame (page renders: preset, seat, 9 action rows, reset), `-level_09` 1500 frames rc=0. `GE_OPTIONTREEPROBE` reports FAIL but the older `build-pc/ge007.base.exe` fails identically (row-cap check; pre-existing, not from D469). ini/eep backed up and restored around every run.
**Not done / gaps.** (1) Jinx 1.2 / 1.3 (and 1.4): D394 is only a reconstructed stub that does not record those tables, so presets 1/2 are reserved slots that behave as 1.1 and are hidden from the selector; supply the Xbox control table to fill them (a data-only change). (2) Per-seat preset (preset is global; only Custom strings are per seat). (3) Two actions bound to the same source both fire (no conflict warning). (4) `start` is not abbreviated in the value column. (5) Pad capture reads pad `seat` only; editing seat N needs that pad connected to bind with it (Esc/B cancel from pad 0 or keyboard always work).
**Maintainer live-pad checklist.** (a) Fresh ini, no edits: Jinx 1.1 plays exactly as before (RT fire, LT/LB aim, A use, X reload, B gadget, Y next weapon, stick clicks crouch, Start pause; menus A/X accept, B/Y cancel). (b) F10 (Back) -> Input -> Controller...: Layout preset right -> Custom; seat/action rows appear. (c) Select Fire, press A: "Press button..." (the A press does not bind itself); press LB -> Primary: LB; fire with LB in a level, RT no longer fires. (d) Capture then tap B: cancelled, row unchanged, page does not close. Capture then hold B ~1 s: binds B. (e) Capture then tap Back: cancel; hold Back ~1 s: slot cleared. Esc and Delete work too. (f) Y on a row clears the slot without a modal. Start unbound -> still pauses (guard). (g) Reset to defaults restores Jinx 1.1 strings on all seats. (h) Controller-only: do (b)-(f) without touching the keyboard; never trapped. (i) 2P split-screen with two pads: set seat Pad 2 to a different Fire; confirm P1 and P2 differ; Pad 2 Y/B in menus still accept/cancel. With keyboard/mouse as P1 (one pad, MPMode Auto) the single pad uses the seat 0 table. (j) Unplug/replug a pad mid-match: bindings stay with the seat. (k) PrevWeapon (unbound by default): bind it and confirm it steps weapons backwards (untested live; reuses the wheel-back A+Z idiom). (l) Front-end PC Options -> Input -> Controller: same page; A starts capture, hold Back clears. (m) Quit and relaunch: the ini keeps `PadPreset` and the `Input.Pad*.` strings.
**Credit.** The two-names-per-action SDL string format, the menu lockout guard idea and release-before-capture arming follow outside PR #109 (italoarruda); the commits carry `Co-authored-by: italoarruda`.
**Confidence.** High: preset 0 byte-equivalence (exhaustive over the mapping function), build, boot/level/overlay smoke, page render. Medium: pad-driven capture timing and the front-end page behaviour (no physical pad here). Low: PrevWeapon feel.
**Maintainer live test (2026-10-01, release `eeec4bd8`, two Xbox Series X pads, menu launch):** (a) plays as before: needs more testing; (b) Custom bind (Fire to LB): works; (c) B tap cancel / hold B binds B: worked; (d) Back tap cancel / hold Back clear, Y clear: worked; (e) controller-only, never trapped: no issues so far (more paces wanted); (f) 2P per-seat tables: good; (g) unplug/replug mid-match keeps bindings, both pads with Custom bindings: good; (h) relaunch persistence: tested fine (Custom bindings survive a relaunch). The log shows Custom binds per seat (`Input.Pad.Fire seat 1 = righttrigger`, `seat 2 = a`), 15 Custom table rebuilds, seat detach/reattach, a 2P stage, and a clean `Restart game` quit (D443 Restart row exercised live, no BSOD). No crash log.
## D470 -- Audio master volume + output-device selection (ROADMAP 5a)
**Keys.** `Audio.MasterVolume` (int 0..100, default 100) and `Audio.Device` (string, default empty = system default device). Both in `port/src/audio.c`; rows "Master volume" / "Output device" at the top of the AUDIO section (shared by the F10 overlay and front-end PC Options; whole %).
**Gain.** Applied in `audioSetNextBuffer` to a static scratch copy of the mixed block (`s*gain/100`, gain <= 100 so no clip is possible), after the game's own Music/FX volumes. At 100 the original buffer pointer is queued untouched (byte-identical). Live: the int is read per block.
**Device.** SDL audio here is queue-based (`SDL_QueueAudio`), all queue/size calls happen on the audio-producer thread. UI/hot-plug only set atomics (`s_devReopenReq`, `s_devRemovedId`); the close/reopen runs at the top of `audioSetNextBuffer` on that same thread, so no queue call races the close (a few ms of queued audio is dropped on a switch). Named device that fails to open -> `LOG_WARNING` + default device. `SDL_AUDIODEVICEREMOVED` (pumped in video.c) -> warning, close, reopen default, config name cleared. Startup logs every enumerated output (`audioInit: output device N: "..."`). Default config makes the same `SDL_OpenAudioDevice(NULL, ...)` call as before.
**Probe.** `GE_AUDIOGAINLOG=1`: once per second, max |sample| of the final queued buffer (`D470 gain: master=N peak=P`).
**Verification (Windows, -level_09, GE_QUITFRAME=600, 640x480, MouseEnabled=0, driver directsound).** a) defaults: opens default device, rc=0, peaks 2717/8158/7459/11225/... b) `MasterVolume=50` + `Device=Speakers (Focusrite USB Audio)`: log "opened audio device by name", rc=0, peaks 1358/4576/4242/5218/... (first window exactly half of a; later windows ~0.5-0.56, timing-dependent mixes). c) `Device=NoSuchDevice`: `WARN audioInit: audio device "NoSuchDevice" unavailable (No such device.); falling back to the default device`, then default opened. In run c the process did NOT self-quit at GE_QUITFRAME (ran to the 120 s `timeout`, rc=124, ~7000 frames, no crash; no "quit requested" line); runs a and b quit normally with identical settings otherwise, so it is probably a harness/quit-path flake unrelated to audio, but it was not re-run (launch budget) -- confidence medium on that point. No crash log in any run.
**Maintainer listening checklist.** (1) Master 100 sounds identical to before. (2) Slider 0/25/50/100 in F10 and in front-end PC Options changes loudness live, music+FX together; 0 is silent. (3) Output device row cycles Default + devices both directions; audio moves to the chosen device immediately, no crash, brief gap only. (4) Restart: chosen device and volume persist. (5) Unplug the active USB device mid-game: game keeps running and audio resumes on the default device. (6) Edit the ini to a bogus device name: game starts on default. (7) "Reset to defaults" in AUDIO restores 100 and Default (does not touch Music/FX per-file volumes beyond their existing reset).
**Lead re-check (2026-10-01):** run c (`Audio.Device = NoSuchDevice`, `-level_09`, `GE_QUITFRAME=600`) repeated once on the same build: fallback warning, default device opened, `quit requested (GE_QUITFRAME)` and a clean exit (rc=0, no crash log). The agent's earlier non-quitting run c did not reproduce; not an audio-path issue.
## D471 — Controller-family button names (no glyphs) + player-index LEDs
**Why.** The pad-menu audit (local note `docs/dev/notes/PAD-MENU-AUDIT.md`) found the PC menus fully pad-operable, but every prompt named buttons in Xbox terms. The maintainer wanted names only, no glyph art (GoldenEye has no glyph UI). For reference, the Perfect Dark PC port does not rename buttons at all (fixed `JOY1_A`… names, `pd_port/port/src/input.c:151`) and queries the controller type only for rumble. It does set the pad's player-index LED, which we adopt.
**Design (port-only).** `port/src/input.c`: `padFamily[seat]` (0 Xbox default incl. Steam Deck/unknown/virtual, 1 PlayStation PS3/PS4/PS5, 2 Nintendo Switch Pro/Joy-Cons), from `SDL_GameControllerGetType` once when a pad is seated (guarded `SDL_VERSION_ATLEAST(2,0,12)`, PS5 at 2.0.14, Joy-Con enums at 2.24), moved with the pad on seat compaction, cleared on detach. Helpers: `inputPadFamily(seat)` (`GE_PADFAMILY=xbox|ps|nintendo` dev override, cached), `inputPadHelpFmt` / `inputPadHelpFmtFam` (expand `{A}{B}{X}{Y}{BACK}{START}` templates with the menu pad's (seat 0) names), `inputPadSourceFamilyName(seat, src)` (binding-column names, NULL = keep the existing Xbox text). Nintendo face letters stay A/B/X/Y (SDL's default `GAMECONTROLLER_USE_BUTTON_LABELS=1` reports buttons by printed label); only L/R/ZL/ZR/−/+ differ. Help templates: F10 overlay footer (`optionsoverlay.c`) and the front-end PC Options capture/help lines (`frontoptions.c`); a footer line that would overflow the card first compacts `LEFT/RIGHT` → `L/R`, then falls back to the Xbox letters. Player LEDs: `SDL_GameControllerSetPlayerIndex(pad, seat + (KbmP1 ? 1 : 0))` re-applied on every pad scan, `-1` before close.
**Verification (headless, `GE_OPTIONSOVERLAY=1` on the Controller page, Custom preset, frame 200).** Default: help and binding column identical to before. `GE_PADFAMILY=ps`: R2/L2/Cross/Square/L3/Triangle/Circle/Options in the column; footer "CROSS BIND L/R SLOT TRIANGLE CLEAR" / "CIRCLE/ESC BACK F10 CLOSE", inside the card. `GE_PADFAMILY=nintendo`: ZR/ZL/Plus, A/B/X/Y unchanged. All rc=0, no crash. Not verified: a real PlayStation or Nintendo pad, and the LEDs (maintainer, if hardware is available). Front-end page text not captured (the same helper as the overlay).
**Also from the audit, not done here:** F10 overlay ownership under 2+ pads (G1–G3: navigation reads pad 0 only).
## D472 -- F10 overlay polish: pad ownership, pillarbox, experimental colour, unknown-key warning
**Ownership (PAD-MENU-AUDIT G1-G3).** `input.c`: `padSelectPrev` is now per pad; `s_ovOwner` records the pad whose Back opened F10 (reset to -1 whenever the overlay is closed); `inputOverlayOwnerPad()` returns the opener while attached, else the lowest attached pad (keyboard-opened, or the owner detached), else 0. Only the owner's Back closes it from the poll path (non-owner Back is edge-tracked and ignored while open). The overlay tick still runs from slot 0's poll, but every `inputPadButton(0, ..)`/`inputPadAxis(0, ..)` in `optionsoverlay.c` (nav, A/X, B, Start, Y-clear, D469 capture fallbacks) now reads the owner. A non-zero owner's own slot is returned neutral while open (as pad 0 always was). `inputPadHelpFmt` uses the owner's family while the overlay is open (front-end page still seat 0). Solo single pad: owner is always pad 0 = unchanged. D343 respected (`frontOptionsBlocksOverlay`). No `g_CurrentPlayer` access (D419).
**Pillarbox (D335b).** The dim stays full-window; card + text are wrapped in `PORT_HUD_ASPECT(CENTER)` ... `NONE` (hudaspect.h, no emission at <= 4:3, so 4:3 is byte-identical). Mouse: `optionsoverlay.c` maps the window fraction through the same centred region (`vis = (4/3)/portNativeAspect()`, as the D335 front-end pointer) before the logical x, so hover/click/slider-drag/close-box hit-test the drawn layout.
**Red experimental rows.** Rows whose label contains "EXPERIMENTAL" draw label (and ON value) in red ink (selected brighter). F10 only; the front-end PC Options page has its own draw and was left alone.
**Unknown ini keys.** `config.c` counts keys that `applyKV` rejects during `configLoad` (`configUnknownKeyCount/First`); the F10 root page shows an amber "N UNKNOWN INI KEY(S) - SEE LOG" in the title line. Non-blocking, no behaviour change. Legacy migrated keys (e.g. old `Input.AimStyle`) also count, once, until the ini self-cleans on exit.
**Verified (Windows, 1280x720, MouseEnabled=0, `GE_OPTIONSOVERLAY=1`, GE_PCDUMP frame 250, rc=0, no crash log):** root page pillarboxed with the warning (`scratch/f472/root.png`); Gameplay page with red Skip intro / All unlocked rows (`scratch/f472/gp250.png`). **Not verifiable headless:** multi-pad ownership. Maintainer live checks: with 2 pads, P2 Back opens F10, P2 navigates/adjusts/B-backs/Start-closes while P1 Back/Start does nothing; detach the owner mid-menu, the lowest remaining pad takes over; keyboard F10 + pad 1 still drives; solo single-pad unchanged; pillarboxed mouse hover/click lands on the drawn row at 16:9 and 21:9; PS pad in seat 2 shows PS names in the help line when it owns the overlay.