diff --git a/docs/dev/findings.md b/docs/dev/findings.md index 4be6fc9e..f3829f96 100644 --- a/docs/dev/findings.md +++ b/docs/dev/findings.md @@ -405,7 +405,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D203 | **Steam Deck (SteamOS, x86_64 Linux) v0.1.0 release bundle: Facility (`-level_34`) crashes "after loading in as James Bond" (user bug report).** Idle repro attempts on WSL Ubuntu with the actual `dist/goldeneye-pc-port-0.1.0-linux-x86_64.tar.gz` are **negative**: 120 s idle → frame 3300+ clean exit (the intro auto-advances, so "sitting as Bond" is covered), and a ~4-min stick-forward-only run → frame 6900+ also survived. So the trigger needs real gameplay input (fire/action) or is Deck-environment-specific. **Leading suspect: the D191 family** — both known v0.1.0 field crashes are SIGSEGVs in `modelGetNodeRwData` (`model.c:478`) over a truncated/NULL `root->Parent` node link, and both were triggered by *gameplay state* (first guard kill on Bunker ii; post-cutscene on Statue), never by idling; Facility opens into the Ourumov + soldier-squad scripted beat (D193a instance), which fits the trigger profile. Alternatives: Deck GL driver path (RADV/zink vs llvmpipe) or 40/50/60 Hz timing (D193 family). **Next:** (1) WSL run under gdb with a walk+fire `GE_INPUTSCRIPT` — generate the script into a file first (earlier attempts were blocked by a `bash -lc` quoting quirk that emptied the loop var, so no fire buttons ever went out); on SIGSEGV compare the fault PC against `modelGetNodeRwData`; (2) if not reproducible on WSL, get `ge007.crash.log` from the Deck (the Linux build writes it — proven by a stale one in the smoke tree) + ask what Bond was doing at crash time; optionally a gdb run from the SteamOS terminal. | OPEN — negative idle repros only; not yet reproduced with input. | | D201 | **`src/libultra/audio/bnkf.c` bank/instrument/sound/wavetable relocation offsets were `s32`, truncating the real 64-bit base pointer.** `alSeqFileNew`/`alBnkfNew` compute `offset = (s32) base` from a heap pointer and thread it through `_bnkfPatchBank/Inst/Sound/WaveTable` as `s32 offset, table` parameters, used to rebase every embedded pointer field in a loaded bank/inst/sound/wavetable/book/loop record. On N64 (32-bit pointers) the truncation is a no-op; on PC, sign-extension of the truncated `s32` during the rebase arithmetic reconstructs the wrong 64-bit address whenever the base allocation's low 32 bits have the top bit set (~50% of allocations, allocator-dependent) — corrupting every relocated pointer in the bank file, a plausible contributor to bad model/audio pointers surfacing downstream (D191-family symptoms are a node-tree walk, not this, but the class is the same). **Fix (ABI-layer, no logic change):** widen `offset`/`table`/`woffset` and all four `_bnkfPatch*` parameters from `s32` to `uintptr_t` (``) — already this codebase's idiom for pointer-safe integers in `src/game/*.c`. No behavior change on the 32-bit N64 build. | FIXED (`src/libultra/audio/bnkf.c`) — part of the Phase-3 mixer landing, not yet build/runtime-verified in isolation | | D202 | **Silenced PPK plays a "slap" instead of a gunshot, PLUS user reports broader real-time audio corruption (M-52 → M-59 → REOPENED M-60 → M-61/M-62 closed the data chain, recommended close → REOPENED M-63 on a fuller symptom report).** M-56–M-62: exhaustively verified the index/bank/pointer/decode/ROM-offset/source-data chain — soundIndex 46 is correctly requested and the data is ground truth (byte-match checksum corroboration, M-62). **M-63: user's actual complaint is broader — wrong sound always plays, correct one SOMETIMES plays too (both heard together), and sounds "pile up and spirally glitch out" over a session until only a stuck looping sound (e.g. Bunker's door) remains.** Got a live build+run repro this session (`-level_09`, 60s, `GE_MIXERTRACE`+`GE_AUDIOTRACE`+`GE_AUDIODUMP`, 58 scripted PPK fires). **Ruled out voice-pool exhaustion/leak** — only 16 distinct voice slots used, properly recycled (`sndDeactivate` count tracks `sndPlaySfx` count 1:1). **Found an unexplained anomaly**: `soundIndex=109` (AK47 bolt-action SFX) fires 94 times in 60s despite no AK47 in the repro, some retriggering the same voice slot at a suspiciously uniform ~32.6ms cadence (4-in-a-row) — far faster than plausible weapon fire; looks like something re-issuing `sndPlaySfx` every audio tick instead of once per game event. Not yet confirmed as the cause of the user's symptom, but the top concrete lead. Traces preserved: `scratchpad/d202-m63/`. (Side note, not a real bug: chased a `load_bg_file` crash this session that turned out to be an incomplete test-data setup on the investigator's part — see M-63 write-up.) **M-65 PARTIALLY resolved:** the "eventual silence" cascade is root-caused and mitigated (ownerless `SOUND_FLAG_LOOPED` SFX — sound 203, `METAL_SLIDE_CLOSE_SFX`, `decayTime=-1`, played by `doorPlayCloseSound0/1` with `NULL` owner — leaks 7 of 8 voices; a 4-line guard in the preemption scan reclaims only provably-ownerless loops, measured 171→629 acquisitions), and a second independent cause is fixed (`sndCreatePostEvent` was stubbed out per D138, removing ALL distance attenuation). The audible stuck door loop is still not reproduced — every candidate mechanism ruled out by measurement (see M-65); needs a user capture at the failing door with `GE_AUDIOTRACE=1 GE_AUDIODUMP=1`. | ROOT CAUSE ESTABLISHED (M-66); DISPOSITION C IMPLEMENTED + MEASURED (M-66b) — the audible stuck loop is sound 203 (`METAL_SLIDE_CLOSE_SFX`): ROM infinite ADPCM loop (`count=-1`) + `decayTime=-1`, played fire-and-forget by `doorPlayCloseSound0/1` with NULL owner, never stored in any slot, exempt from preemption (flag 0x12) and from decay-stop scheduling, priority 0x41 makes it unstealable — **faithful N64 behaviour / original quirk, not a port bug**. 327 ms period = -550-cent pitch (keyBase 54 + detune 50 − shift 6000 → ratio 0.7278; 5248 samples @ 22050×0.7278 = 327.007 ms). M-65's guard fires only under pool pressure (twice in the user capture) and cannot silence the idle loop. **Disposition C implemented + measured (M-66b):** PC-only `AL_SNDP_PORT_EXPIRE_EVT` fades provably-ownerless infinite-loop SFX out ~2.5 s after start (2 s delay + 0.5 s fade); per-second spectral check: pre-fix drone flat at max t≈39→361 s, post-fix transient bursts with full silence between; run ends allocated=0/8. **M-67: static analysis exhausted — full-bank data scan clean (all 261), DSP audit complete vs PD, A_LOOP branch dead code in GE, reference clip B00I00S2D.wav REFUTED as an in-game capture (best waveform CC 0.19 ≈ noise; M-61's +0.999 was envelope-only correlation); new `GE_VOICEDUMP` per-voice probe + timestamped WIRE/AUDIOTRACE for the decisive runtime A/B; self-test found the offline resample direction had been inverted all along (y[k]=x[ratio·k]) and that GE "music" is an SFX-sequence system with its own ~8-sample set sharing the 8-voice pool; parked anomaly: sound 232 requested at level start produces no audible audio. **M-68 (user captures ×3): all three complaint classes verified per-voice at runtime — doors CC 0.91–0.99, PPK 46 CC 0.90–0.97, armor 81 full 833 ms vs ROM-predicted 837 ms @ ratio 0.5612; the earlier "instant death" metric was a line-count artifact (µs-timestamped `[EVT]` probe shows every STOP lands on schedule). No port bug: the heard "wrong" sounds are the original design (overlapping SFX, short decays, quick-stop truncation).** **M-69 (full-corpus exact match, 320 scripted-run requests): 286/320 CC ≥ 0.85 — every allocated voice plays the exact ROM sample at the exact ROM pitch; the initial 44 "BAD" were tooling artifacts (voicedump stamps are block-END times; coarse-slide missed sharp onsets; refs not clipped at voice death). Remaining failures = dropped allocations (no `[VOICE+]` — suspected 8-voice pool exhaustion, unverified) + shared-wavetable wire misattribution. No corruption.** **M-70 (decisive clean re-capture, 209 requests, matcher v3): every voiced request plays the exact ROM sample — 200/209 auto CC ≥ 0.85; the 4 MID are idx=109 verified at CC 0.946 by manual ratio sweep (residual = wall-clock-bursty dump stamps + short live windows); the 5 NO-WIRE are 1 matcher window artifact + 4 never-voiced requests, NOT pool exhaustion (alloc ≤ 5/8 at each drop). Zero wrong-sample playback — the D202 wrong-sample/mixer-corruption hypothesis is refuted.** **M-71: user by-ear pass on M-66b = PASS ("the metal door sound does not loop forever now") — Disposition C validated; D202 ready for probe removal + close. The user's remaining audio complaints (slap/glass over gunfire, explosion→scream, armor pickup wrong sample — N64 A/B confirms non-fidelity) are a DIFFERENT bug: PC requests different/extra sound indices than N64; bank converter + playback chain exonerated → split out to D205.** | -| D205 | **PC plays wrong/extra SFX for gameplay events the user can A/B against N64: general gunfire has a slap/glass layer audible on PC but silent on N64; an explosion plays a soldier-scream sample; armor pickup plays a wrong sample; unarmed melee (slap) plays Klobb's shooting sound idx 106 instead of the slap effect (M-71/M-72, user by-ear + N64 A/B).** The playback chain is proven faithful end-to-end — M-68/M-70 exact match: every voiced request plays the exact ROM sample at the exact ROM pitch — and this session's offline harness (`scratchpad/banktest/`: real D37 converter + `diff_bank.py`) shows all 261 bank entries map identically N64→PC (base/len/type/keyMap/env, 0 mismatches). So the bug is upstream of the bank: **PC requests different or extra sound indices than N64 in the same scenario.** Ranked suspects: (a) data-driven sndID/explosion-type/item-sound fields in converted level/setup assets (pccg sidecars) differ from ROM — explosions play `sp44->sndID` from `g_ExplosionTypes[]` (`src/game/explosion.c:285`), explosion *type* comes from level data; explosion SFX are idx 169–183, a scream is outside that range; armor idx 81 verified exact in M-68, so a wrong armor sample means a different index is requested; (b) extra hit events — converted collision geometry makes bullet raycasts (D154/D135, both ABI-verified) or explosion proximity checks hit surfaces/NPCs the N64 misses → layered impact/ricochet/scream SFX; the slap+glass-over-gunshot signature is exactly the non-penetrating-OBJ-hit path (`HIT_BULLET_GLASS_SFX` idx 70 via `sub_GAME_7F064720`, `src/game/gunfire.c`). | OPEN — bank + playback exonerated (M-71); next: one controlled explosion + armor pickup on PC with `GE_AUDIOTRACE=1`, read the requested soundIndex list, compare against ROM ranges; N64-side reference via mupen64 or sidecar-vs-ROM byte diff | +| D205 | **PC plays wrong/extra SFX for gameplay events the user can A/B against N64: general gunfire has a slap/glass layer audible on PC but silent on N64; an explosion plays a soldier-scream sample; armor pickup plays a wrong sample; unarmed melee (slap) plays Klobb's shooting sound idx 106 instead of the slap effect (M-71/M-72, user by-ear + N64 A/B). M-73: user combat capture root-causes all three "wrong-sound" moments to ONE phenomenon — guards stuck in sustained ACT_ATTACK re-triggering weapon fire sound idx 109 (AK47/Spectre) 256×/30 s at fixed per-guard periods with NO corresponding bullets/impacts; the "Klobb" was never 106 (never requested); explosion 182 + armor 81 each requested exactly once and correct, but masked by the 109 barrage. Sustained-attack gate = `seen_bond_time` refreshed via `stanTestLineUnobstructed` LOS on converted geometry → leading suspect is a LOS/collision query returning clear when N64 blocks.** The playback chain is proven faithful end-to-end — M-68/M-70 exact match: every voiced request plays the exact ROM sample at the exact ROM pitch — and this session's offline harness (`scratchpad/banktest/`: real D37 converter + `diff_bank.py`) shows all 261 bank entries map identically N64→PC (base/len/type/keyMap/env, 0 mismatches). So the bug is upstream of the bank: **PC requests different or extra sound indices than N64 in the same scenario.** Ranked suspects: (a) data-driven sndID/explosion-type/item-sound fields in converted level/setup assets (pccg sidecars) differ from ROM — explosions play `sp44->sndID` from `g_ExplosionTypes[]` (`src/game/explosion.c:285`), explosion *type* comes from level data; explosion SFX are idx 169–183, a scream is outside that range; armor idx 81 verified exact in M-68, so a wrong armor sample means a different index is requested; (b) extra hit events — converted collision geometry makes bullet raycasts (D154/D135, both ABI-verified) or explosion proximity checks hit surfaces/NPCs the N64 misses → layered impact/ricochet/scream SFX; the slap+glass-over-gunshot signature is exactly the non-penetrating-OBJ-hit path (`HIT_BULLET_GLASS_SFX` idx 70 via `sub_GAME_7F064720`, `src/game/gunfire.c`). | OPEN — bank + playback exonerated (M-71); M-73 root-caused the user's three "wrong-sound" moments to guards stuck in sustained fire-sound re-triggering (idx 109 barrage, no bullets) — next: A/B whether N64 guards sustain fire in the same combat + verify `stanTestLineUnobstructed` LOS results on converted geometry vs ROM for guard→Bond segments | | D152+ | D152 addendum (M-31): static audit of every compiled-audio `osSetIntMask(OS_IM_NONE)` — **all balanced**, no unbalanced early-return. Real fixes: `sndSetSfxSlotVolume` now holds the mask across its list walk (matches its twin `sndDeactivateAllSfxByFlag`) + `sndApplyVolumeAllSfxSlot` batches the slot loop under one recursive hold (kills the fade-out lock-acquire storm); `portThreadWrapper` calls `imThreadExitRelease()` on thread exit (kills the "transient thread acquired `OS_IM_NONE` and died" leak + the pthread-id-reuse re-wedge). Steal-lock kept as backstop. | FIXED (`src/snd.c`, `port/src/libultra.c`, `#ifdef PORT`) — fade-out repro playtest-gated, not headless-verified | | RC3 · D167 | **Non-power-of-two texture wrap period (`docs/dev/TEXTURE-GLITCH-ANALYSIS.md` §6 RC3 — "textures repeat oddly", residual Depot-ceiling noise after D161).** The N64 RDP masks the texel coordinate of a wrapping render tile at `1< 0 && field_178 < g_GlobalTimer` — so **sustained +ACT_ATTACK = sustained weapon roar regardless of bullets**. Sustained attack +requires `self->seen_bond_time >= g_GlobalTimer - CHRLV_SEEN_RECENT_CHECK` +(100, chraction.h:13; check at chraction.c:6593); `seen_bond_time` is +refreshed by `setSeenBondTimeToNow`, gated on +`stanTestLineUnobstructed(... CDTYPE_OBJS|DOORS|CHRS|PATHBLOCKER|AIOPAQUE ...)` +LOS queries (chraction.c:3917, :3972, and `chrCanSeeBond` :3945). **If the +converted collision geometry makes those guard→Bond LOS raycasts return +clear where the N64 blocks them, guards never lose their target → 30 s of +sustained fire sound.** Per-symptom accounting: (1) "Klobb sound on melee" — +idx 106 is NEVER requested in the capture; the user heard the 109 barrage +(gun-bolt SFX) during his melee combat. (2) "explosion → scream" — explosion +182 was requested exactly once (correct, in 169–183); GET_HIT_MALE screams +(137/138/139) fired ±1 s around it (guards hit by the blast — possibly +legitimate, needs N64 A/B), all under the 109 barrage. (3) "armor wrong" — +idx 81 requested exactly once at t≈1317.4 s (correct!), bracketed by 109 ticks +at 1317.35/1317.45 — the barrage masks it. Continuity note: this is the same +phenomenon as D202 M-63's "unexplained anomaly" (idx 109 ×94 in 60 s during a +scripted PPK repro with no AK47 present) — guards at level_09 doing the same +thing. **Remaining question: WHY do PC guards sustain ACT_ATTACK for ~30 s** — +(i) `stanTestLineUnobstructed` on converted geometry returns clear where ROM +blocks (D154/D135 family), or (ii) AI attack enter/exit differs. Open A/B: +do N64 guards in the same combat also sustain fire for 30 s? + **Therefore: PC requests different or extra sound indices than N64 in the same scenario.** Two mechanisms, ranked: (a) **Converted level/setup data** — sndID / explosion-type / item-sound @@ -7539,30 +7577,23 @@ same scenario.** Two mechanisms, ranked: GBU/TAJ/RICO_4 (19–26, 31–33) = wall only. **Next (cheapest first):** -0. **User capture at the exact failing moment** (M-72 made this the - cheapest split): run the PC with `GE_AUDIOTRACE=1` and reproduce one - melee punch / one shot-with-layering / one explosion; the requested - index list in the event window decides per symptom — e.g. melee: 47–49/ - 105 *plus* 106 → extra NPC fire event (mechanism b); 106 *only* → new - mechanism, escalate. Launcher must have `C:\msys64\mingw64\bin` on PATH. -1. One controlled explosion + one armor pickup on PC with - `GE_AUDIOTRACE=1` (`-level_09` boots straight into Bunker). Read the - requested soundIndex list in each event window: any index outside - 169–183 at the blast (→ scream index = extra event, mechanism b) or a - 169–183 index whose ROM sample is *not* an explosion (→ type/sndID - wrong, mechanism a). Armor: requested index 81 or something else? -2. N64-side request list for the same scenario: mupen64 at repo root - (untracked emulator checkout, now gitignored) with a `sndPlaySfx` - breakpoint/log — or infer from ROM data by decoding one level's pccg - sidecar vs the ROM segment and diffing the sound-related fields. -3. If mechanism (a): find the converter bug in the level/setup asset - pipeline (`scripts/` + `port/src/romdata.c` fixups) — byte-compare one - level's sidecar against the ROM original. -4. If mechanism (b): dump converted collision near Bond's line of fire for - one shot, decode in Python against the ROM original; identify the - converter bug class. -5. After root-cause + fix: user re-test of explosion + armor pickup + - gunshot layering vs N64. +0. ~~User capture~~ — DONE (M-73): all three moments explained by the guard + 109-barrage; no per-symptom data bug found (182 and 81 both requested + correctly). +1. **N64 A/B of the sustained-fire itself**: in the same combat, do N64 + guards also fire for ~30 s straight? If not → PC guard AI/LOS differs; + if yes → the difference is elsewhere (e.g. sound-only re-trigger vs + actual bullets) and this may be a faithful-AI / different-perception case. +2. **Probe the PC side**: temporary `GE_AUDIOTRACE`-style probe on + `setSeenBondTimeToNow` (chraction.c:3874) — log guard id + position when + `seen_bond_time` refreshes during a sustained-fire episode; if it fires + every frame while Bond is behind cover → the LOS query is wrong. +3. **Verify `stanTestLineUnobstructed` on converted geometry** for one + guard→Bond segment from step 2: replay the exact ray against the ROM + original collision (decode in Python) vs the pccg sidecar; identify the + converter/parser bug class (D154/D135 family). +4. After root-cause + fix: user re-test of melee / explosion / armor pickup + vs N64 — expect all three "wrong sounds" to disappear with the barrage. **Probes added this session (live under `GE_AUDIOTRACE`, catalogued in GE-ENV-PROBES.md):** `[VOL] t=… state=… rawVol=… playing=…` (`src/snd.c: