D320: resolve sweep via forced repro — ai_19/ai_9 pins confirmed, ai_12 clean; watchdog generalized (table-driven + uncapped repeat rescue); D318 N64-manifestation corrected (unverified; 1964 deliberate-timing A/B armed)

Also commits the prior session's carried-uncommitted set: D294 room-pool
fix (sqrt + 2x cap, user-verified state), D324 stdlib-shim prototypes,
D323 explicit-LOD census + tools_pc tooling, GRAPHICS-BACKLOG row.
texdump*/ probe artifacts gitignored.
This commit is contained in:
James Dansereau committed 2026-09-22 22:30:32 -04:00
1 parent 12490b95e6
commit 97c7e38845
18 files changed
+1375 -112

No files matched your search

+1
View File
@@ -34,6 +34,7 @@ once the game is playable start-to-finish without crashes.
| **D197** | Character face/head textures wrap wrong — the face texture repeats around the side of the head "and continues weird" instead of clamping to the front. Seen on **Silo** (M-49 playtest), intermittent ("sometimes"). Reads as **`G_TX_CLAMP` not honoured** for the character-skin tile in fast3d (→ `GL_REPEAT`, so the face UV region tiles around the skull), or wrong `shifts`/`shiftt` / LOD-tile selection on the head texture, or the D167 non-PoT wrap-period issue (head textures are often non-PoT) hitting skinned models. | **Observed, not investigated** (M-49, user bug report — needs a capture + which chr/level). Distinct from D176(b)/D182(2) (those are *bg wall* pitch shear). Related: RC3/D167 (`Video.WrapFix`, non-PoT wrap period) + the `texWriteTileLods` `shifts=shiftt=lod` note in findings §F "D176(b) → 4. candidate 2". First checks: does `gfx_pc.cpp` map N64 `cms/cmt` `G_TX_CLAMP` → `GL_CLAMP_TO_EDGE` for character DLs; is the head tile getting the base tile vs an LOD tile (D107); try `Video.WrapFix=1` on a repro. |
| **D195** | Transparency / alpha-blended surfaces render wrong — reported on **Control** (Severnaya bunker basement — Natalya's console room; heavy on glass partitions and the translucent projected/holographic wall displays). Exact failure mode not yet pinned by the reporter (M-49 playtest): candidates are alpha-blended geometry drawing fully opaque, translucent panels drawing invisible, or draw-order/sort so translucent surfaces occlude what's behind. | **Observed, not investigated** (M-49, user bug report — needs a `GE_PCDUMP` capture + which surfaces). fast3d render-mode / combiner path. Same neighborhood as D161 (CI8 tile + `G_TT_NONE` → garbage), D172 (particle-sprite channel order), D176(b) (texture pitch shear) — but this is a *blend/alpha* defect, not a texture-decode one. Note D128 already touched Control's `specialportalarray` (portal struct stride) — check whether the affected surfaces are portal-adjacent. First checks: does `gfx_pc.cpp` honour `G_RM_*` XLU render modes + `G_AC_THRESHOLD`/`G_CC` alpha for these DLs; is `gDPSetRenderMode` `w1` being parsed (cf. findings §F "zbuf-clear w1 scribble"); is Z-write-vs-blend ordering right for XLU surfaces. Get the reporter to say: opaque-where-should-be-clear, invisible, or wrong edges? |
| **D252** | Particle effects cycle through a rainbow palette — bullet-impact sparks and lingering smoke/explosion residue drift through the full hue wheel over time (~10 shots to sweep it) instead of holding their intended grey/orange palette (M-124, user playtest). Full mechanism + suspect in findings §F **D252**: colour comes from 15 distinct `IMAGE_SMOKE_N`×`IMAGE_FIRE_N` (RGBA16) pairs the particles age through; prime suspect is `import_texture_rgba16` deriving width/height from the tile line size (32×7 instead of 16×14 for the fire tiles), scrambling each stage's sprite into a different wrong hue. | **Documented only — not fixed for v0.2.0-pre** (user instruction). Confirm with one dumped fire tile vs raw `IMAGE_FIRE_N` bytes, then derive width/height from the LOADBLOCK dtx/dlh. D172/M-82 lineage. |
| **D323** | Distant geometry on 15 levels samples driver-generated mips from the finest texture level; N64 samples Rare's hand-authored mip chain for explicit-LOD textures bound under `G_TL_LOD` (heaviest: Statue 378 binds, Aztec 806). Overwhelmingly CI8/IA8 — subtle fidelity gap, no reported artifact; the only RGBA5551 cut-outs are the D236 set (already handled). | **OPEN / parked** — mechanism confirmed, census in `tools_pc/bg_lod_audit.py`, fix path documented in findings §F **D323** (upload GL mip chains for TEXTLOD-on bindings). Not worth doing speculatively; revisit only if a playtest flags mushy distant detail. |
| **1P weapon viewmodel** | **RESOLVED (M-83, D215).** In-level first-person weapon models drew nothing. Root cause: `gunRenderFirstPersonGunModels` built its `ModelRenderData` via `*(ModelRenderData*)&D_80035CC0` — a reinterpret across two separately-declared adjacent globals that only lands right on the N64 linker + 4-byte pointers; on x86-64 `renderdata.flags` read 0, gating every weapon DL node off in `modelRenderNodeGundl`. All 3 ranked suspects in VIEWMODEL-RESEARCH.md were ruled out first by the `GE_DVM=1` probe. Fixed `#ifdef PORT` (explicit field init). `subdraw` 1→153 gfx cmds; owed one BUNKER1 eyeball. |
## Resolved (for reference)
+4 -2
View File
@@ -171,7 +171,7 @@ D290,OPEN,"Watch's in-game ""controller"" section shows no control-pad graphic (
D291,OPEN,"Dam: texture glitches when the truck drives past (M-141, Steam Deck user playtest).","OPEN — observed, not investigated."
D292,FIXED,Death animations are glitched —,"MOSTLY FIXED as a side effect of D243 (2026-09-18, user-confirmed alongside D173) —"
D293,FIXED,"QoL ask: no discoverable way to quit the game from the UI (M-141, Steam Deck user playtest —","FIXED (M-141) — ""Quit to desktop"" row added to the F10 overlay; build-verified Win+Linux, not yet live-confirmed.…"
D294,OPEN,"Statue: ground geometry near the large CCCP/hammer-and-sickle monument briefly renders solid black over a wide area, then self-recovers 10-30 seconds later… —",OPEN — not investigated.…
D294,FIXED,"Statue: ground geometry near the large CCCP/hammer-and-sickle monument briefly renders solid black over a wide area, then self-recovers 10-30 seconds later… —",FIXED pending user playtest (2026-09-22) —
D295,FIXED,"Campaign-halting crash at the end of Control Center on 00 Agent difficulty, then a crash on every subsequent relaunch/resume (GitHub #87, user campaign playtes…",FIXED (M-149) — M-148 root-caused + reproduced on HEAD; fix landed:
D296,OPEN,"Control Center, 00 Agent:","OPEN — single live report, un-triaged; observed only under a gdb-attached debug-harness launch (tools_pc/debug.ps1), not the packaged release artifact —"
D297,FIXED,v0.3.0 silently wipes ALL pre-fix player saves on first launch (D284 side effect; found during D295/M-148).,FIXED (M-150) — migration shim landed:
@@ -197,6 +197,8 @@ D316,FIXED,"Mouse-clicking some menu items glitches and lands on the item below
D317,CLOSED,"Crash when toggling Game.AllUnlocked back and forth in-game (user report, v0.3.0 playtest build, 2026-09-18) —","CLOSED — symptom of D316 (confirmed instant window close, no WER report; the D316 hit-band bias let a click on ""All unlocked"" fall through to the adjacent ""Quit to desktop"" row)."
D318,RESOLVED,Facility (regular play path):,RESOLVED (2026-09-19):
D319,FIXED,"Frame cap slider was mostly a dead zone; replaced with a plain 30/60 toggle (user ask, 2026-09-18).",FIXED — new ROW_FPSCAP cycle (30/60 only) in port/src/optionsoverlay.c; default changed to 60 in port/src/video.c.
D320,OPEN,"D318-class sweep: aim-hold → _update AI lists in 8 levels carry the same as-authored softlock race (static analysis, 2026-09-20).","OPEN — static sweep complete (Facility ai_19, Control ai_9, Depot ai_12 flagged high/med-high; Bond-combat loops likely safe); no live confirmation beyond D318 itself; probe-verify path + generalized…"
D320,RESOLVED,"D318-class sweep: aim-hold → _update AI lists in 8 levels carry the same as-authored softlock race (static analysis, 2026-09-20).",RESOLVED (2026-09-22):
D321,CLOSED,D318 trigger chain fully mapped:,CLOSED — trigger chain mapped and faithful; D318 watchdog validated in live play.
D322,OPEN,Long-session audio degradation:,"OPEN — static triage done (teardown audit, mixer statelessness, D202-coverage check); ranked hypotheses:"
D323,OPEN,G_TL_LOD bindings of explicit-LOD textures:,"OPEN / parked — mechanism confirmed in code; census (tools_pc/bg_lod_audit.py) shows 15 levels with explicit-LOD textures bound under TEXTLOD-on classes (stat 378, azt 806 binds heaviest); overwhelmi…"
D324,FIXED,"GE's <stdlib.h> stub declares nothing, so TUs that include it get NO CRT prototypes —",FIXED (2026-09-22) — proven impact:
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
171 D291
172 D292
173 D293
174 D294
175 D295
176 D296
177 D297
197 D317
198 D318
199 D319
200 D320
201 D321
202 D322
203 D323
204 D324
+79 -10
View File
@@ -579,7 +579,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D291 | **Dam: texture glitches when the truck drives past (M-141, Steam Deck user playtest).** — full `## D291` entry at file tail | OPEN — observed, not investigated. Needs a live repro with the truck's exact position/timing and, ideally, a screenshot. |
| D292 | **Death animations are glitched — Bond is often sunk halfway into the floor (M-141, Steam Deck user playtest).** — full `## D292` entry at file tail | **MOSTLY FIXED as a side effect of D243 (2026-09-18, user-confirmed alongside D173) — same fix, same outcome: right 9/10 times, small residual offset only when off, no more full sinking/glitching.** See D173 for the user's exact confirmation wording; not separately re-tested, reported together. |
| D293 | **QoL ask: no discoverable way to quit the game from the UI (M-141, Steam Deck user playtest — "couldn't see a way to exit the game. Maybe build this into the…** — full `## D293` entry at file tail | FIXED (M-141) — "Quit to desktop" row added to the F10 overlay; build-verified Win+Linux, not yet live-confirmed.… |
| D294 | **Statue: ground geometry near the large CCCP/hammer-and-sickle monument briefly renders solid black over a wide area, then self-recovers 10-30 seconds later…** — full `## D294` entry at file tail | OPEN — not investigated.… |
| D294 | **Statue: ground geometry near the large CCCP/hammer-and-sickle monument briefly renders solid black over a wide area, then self-recovers 10-30 seconds later…** — full `## D294` entry at file tail | **FIXED pending user playtest (2026-09-22)** — root cause: room-model POOL exhaustion (authored `-ma220`=220 KB < ~250 KB monument-plaza demand at FovScale 130 + max DD/Lod); `memaAlloc` NULL → silent skip → undrawn room shows the night-sky clear colour. Stochastic (wall-clock seed → load order). Fix: `portRoomPoolScale()` (video.c, identity at N64-faithful settings) applied in `bossMainloop`; verified headless (faithful→exact authored size; user settings→0 failures; `GE_ROOMPOOL=0.25`→exhaustion on demand). Playtest: main patch gone; residual corner flicker traced to full-level residency (487 KB = 2.2× authored) starving the 2× pool; the 3×/linear fix REGRESSED the renderer on the menu-driven path (D156 NaN anim state, not a bank overflow) → reverted to sqrt + 2.0 cap (ma=450560). Residual open; probes in place for next capture. High-DD horizon edge on night levels = setting-inherent, documented only.… |
| D295 | **Campaign-halting crash at the end of Control Center on 00 Agent difficulty, then a crash on every subsequent relaunch/resume (GitHub #87, user campaign playtest).** — full `## D295` entry at file tail | FIXED (M-149) — M-148 root-caused + reproduced on HEAD; fix landed: port-side JPN-cache seed (`port/src/main.c`) + `#ifdef PORT` buffer widen in `front.c` (D8 class). Verified: previously-crashing input now clean, 21/21 sweep PASS. Companion save-wipe regression → D297. History — M-148 fully root-caused + **reproduced on current HEAD**: `difficultytext[4]` (`front.c:2616`) overflow — `strcpy("00 Agent")`/`strcat("\n")` spills onto `textpos.p[0]` (y, ≥0x80 in normal geometries) → high-bit byte → `langGetJpnCharPixels` NULL deref (`language.c:320`, `g_JpnCacheCacheItems` NULL outside JPN mode — likewise unallocated on N64 non-JP builds, crash potential is decomp-native). The user's save is a *valid pre-D284* save (all 5 slot CRCs bit-exact under the old PRNG); post-fix builds wipe it at validation, which is why the unmodified artifact no longer triggers it — NOT a fix. No OOB exists (M-147 guess retracted: 007 difficulty has no stored times). Fix options flagged for sign-off (port-side JPN-cache init preferred; `difficultytext` enlarge = D8 class). Side finding: v0.3.0 silently wipes all pre-fix saves on first launch (D284 side effect — own D entry owed). Separate still-open issue found: unconditional `[EVTQ-DROP]` audio-pool-exhaustion logging, `src/libultra/audio/event.c:92`.… |
| D296 | **Control Center, 00 Agent: noticeable stutter, user's own read is "maybe from the drone guns" (M-146, same D295 investigation session).** — full `## D296` entry at file tail | OPEN — single live report, un-triaged; observed only under a gdb-attached debug-harness launch (`tools_pc/debug.ps1`), not the packaged release artifact — user flagged that harness overhead has caused false stutter before. Needs a plain-exe / real-release repro check before any perf tracing.… |
| D297 | **v0.3.0 silently wipes ALL pre-fix player saves on first launch (D284 side effect; found during D295/M-148).** — full `## D297` entry at file tail | FIXED (M-150) — migration shim landed: `port/src/legacycrc.c` (isolated, deliberately-wrong pre-D284 PRNG, reviewer-verified bit-exact against `0de99b4d^`) + eeprom-shim read-path hook in `libultra.c` (block 4 only; persists pristine+migrated bytes before the read-time-only AllUnlocked patch). Verified: user's #87 artifact → `D297-migrated 5`, all slots OK, idempotent on relaunch; native post-fix save untouched; corrupt slot left to the game's own wipe path; fresh eep untouched; 21/21 sweep PASS. Block-0 header needed no migration (factory seal, verified). |
@@ -603,11 +603,13 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D315 | **Menu scaling differs between main menu and in-game — main menu scales OK, in-game menu "seems kinda big sometimes" (user report, v0.3.0 playtest build, 2026-09-18).** — full `## D315` entry at file tail | OPEN — disclosed as a known issue, deferred to v0.3.1 (bigger design question, not a mechanical fix). |
| D316 | **Mouse-clicking some menu items glitches and lands on the item below instead (user report, v0.3.0 playtest build, 2026-09-18).** — full `## D316` entry at file tail | FIXED — root-caused to two compounding bugs (naive mouse->logical mapping ignoring the safe-area crop; asymmetric row hit-test band) in `port/src/optionsoverlay.c` + `port/fast3d/gfx_pc.cpp`. Build-verified (golden diff PASS, worst_cell=0); interactive click verification owed. |
| D317 | **Crash when toggling Game.AllUnlocked back and forth in-game (user report, v0.3.0 playtest build, 2026-09-18) — no dump left behind.** — full `## D317` entry at file tail | CLOSED — symptom of D316 (confirmed instant window close, no WER report; the D316 hit-band bias let a click on "All unlocked" fall through to the adjacent "Quit to desktop" row). Same fix resolves it. |
| D318 | **Facility (regular play path): after Objective C (destroying the bottling tanks) completes, Ourumov freezes and does not shoot Trevelyan as he should (user report, 2026-09-18).** — full `## D318` entry at file tail | RESOLVED (2026-09-19): as-authored latent race (gas bit mid-monologue derails c78's pre-aim hold into a permanent ACT_ATTACK anim pin; N64-identical), NOT a port bug; port-side watchdog `port/src/d318watchdog.c` re-inits the attack on confirm. D263 mechanism map stands (different failure mode). |
| D318 | **Facility (regular play path): after Objective C (destroying the bottling tanks) completes, Ourumov freezes and does not shoot Trevelyan as he should (user report, 2026-09-18).** — full `## D318` entry at file tail | RESOLVED (2026-09-19): as-authored latent race (gas bit mid-monologue derails c78's pre-aim hold into a permanent ACT_ATTACK anim pin), NOT a port bug; port-side watchdog `port/src/d318watchdog.c` re-inits the attack on confirm. D263 mechanism map stands (different failure mode). **N64 manifestation unverified** (2026-09-22 correction: user's empirical prior is no such N64 freeze; upstream-port-divergence vs unobserved — A/B vs N64 RAM capture is the discriminating test). |
| D319 | **Frame cap slider was mostly a dead zone; replaced with a plain 30/60 toggle (user ask, 2026-09-18).** — full `## D319` entry at file tail | FIXED — new `ROW_FPSCAP` cycle (30/60 only) in `port/src/optionsoverlay.c`; default changed to 60 in `port/src/video.c`. Root cause was D186 (sim tick ceiling + low-cap guard); this just fixes the menu's exposure of it. Build-verified; interactive confirmation owed. |
| D320 | **D318-class sweep: aim-hold → `_update` AI lists in 8 levels carry the same as-authored softlock race (static analysis, 2026-09-20).** — full `## D320` entry at file tail | OPEN — static sweep complete (Facility ai_19, Control ai_9, Depot ai_12 flagged high/med-high; Bond-combat loops likely safe); no live confirmation beyond D318 itself; probe-verify path + generalized-watchdog recommendation documented. |
| D320 | **D318-class sweep: aim-hold → `_update` AI lists in 8 levels carry the same as-authored softlock race (static analysis, 2026-09-20).** — full `## D320` entry at file tail | RESOLVED (2026-09-22): forced repro confirms the pin on Facility ai_19 (off=483) and Control ai_9 (off=69, ~2/3 variants); Depot ai_12 clean 3/3 (scripted kill progresses — not covered). D318T detector fixed to scan `g_ChrSlots` as well as `g_ActiveChrs`; watchdog generalized to a table with uncapped repeat rescue. Open threads: N64-fidelity A/B via unthrottled emulator (per D318 correction), single Depot ai_10/TREED pin observation, ai_13/14/15 pad lists. |
| D321 | **D318 trigger chain fully mapped: tanks → combat bit is an authored ~3.5 s gas-cascade delay (chr 254 script); derail lands same-tick; PC behavior confirmed faithful to N64 (probe captures, 2026-09-20).** — full `## D321` entry at file tail | CLOSED — trigger chain mapped and faithful; D318 watchdog validated in live play. Optional deferred tuning: `D318_DEADLOCK_TICKS` 600→300 (user's call). |
| D322 | **Long-session audio degradation: full campaign on v0.3.0 — audio progressively worsens from Silo, by Caverns/Cradle the OST is inaudible and SFX "come and go"; restarting the game restores it (issue #87, user report, 2026-09-20).** — full `## D322` entry at file tail | OPEN — static triage done (teardown audit, mixer statelessness, D202-coverage check); ranked hypotheses: voice-pool exhaustion/counter drift > queue starvation > evtq saturation. `GE_D322` pool-telemetry probe shipped; needs a campaign capture with `GE_D322=1 GE_D204=1`. |
| D323 | **G_TL_LOD bindings of explicit-LOD textures: PC samples driver mips from LOD0, N64 samples the authored mip chain (static census, 2026-09-21).** — full `## D323` entry at file tail | OPEN / parked — mechanism confirmed in code; census (`tools_pc/bg_lod_audit.py`) shows 15 levels with explicit-LOD textures bound under TEXTLOD-on classes (stat 378, azt 806 binds heaviest); overwhelmingly CI8/IA8 (subtle), the only RGBA5551 cut-outs are the D236 set (already fixed). No reported artifact; fix path documented (upload GL mip chains for TEXTLOD-on bindings) but not worth doing speculatively. Also documents GE's OMH field-write encoding (op 0xBA, value at absolute bit position in w1). |
| D324 | **GE's <stdlib.h> stub declares nothing, so TUs that include it get NO CRT prototypes — implicit `int f()` declarations silently corrupt non-int returns: `atof` garbage in every float ini parse (D38 class).** — full `## D324` entry at file tail | FIXED (2026-09-22) — proven impact: `Game.ScreenShakeIntensity` (the only float ini option) silently parsed to garbage whenever a user set it; caught via the D294 `GE_ROOMPOOL` hook. Fix: `port/shim/stdlib.h` now declares atof/strtod/strtol/atexit/getenv unconditionally (K&R form); pc_protos.h gains atof/strtod for game TUs; glass2.c PORT block uses `__builtin_memset`. Residual harmless implicit decls (atoi/floorf/sqrtf/memset builtins, lround) documented. |
Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS
threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software
@@ -11755,6 +11757,22 @@ Superseded history below (the original OPEN investigation, root cause not found
**Status (from §F table):** OPEN — not investigated. Next: reproduce on Statue (`-level_22`) at the reported settings (`Video.FovScale≈130`, max draw distance/LOD) near the ramp by the CCCP monument; a `GE_D271`/`GE_D104`-style room-visibility trace (`roomsDrawn` count, portal-cull hits) correlated with the black-ground window would confirm or rule out the room-visibility-BFS hypothesis quickly.
**ROOT-CAUSED AND FIXED (2026-09-22 session) — not the room-visibility BFS; it is room-model POOL exhaustion under widened PC visibility settings.** The room-visibility hypothesis above is ruled out: `roomsDrawn`/portal-cull state stays healthy throughout the black window. The actual chain: (1) each level's authored `memallocstringtable` row budgets the room-model pool — Statue's is `-ma220` = **220 KB**; (2) the PC visibility knobs (`Video.FovScale`, `WidescreenAuto`, `DrawDistance`, `LodDistance`) keep more rooms on screen at once than the N64's fixed ~60°/4:3/authored-far-clip view, so more room models must stay resident simultaneously — the monument-plaza 12-room set alone totals ~250 KB "used", already over the authored pool; (3) when demand exceeds supply, `memaAlloc()` returns NULL (after its 8 defrag passes) and `bgLoadRoomModelData()` bails **silently** — the undrawn room's scissor region then shows the framebuffer clear colour. On Statue that is the night-sky fill RGB(0,0,8): a wide solid-black patch of "ground" that self-recovers 10–30 s later when the player moves and the rooms reload into freed space — exactly the reported symptom shape (transient, ground-specific, self-correcting). **A/B confirmed both knobs independently trigger it:** FovScale=130 alone → 372 alloc failures; DrawDistance=400 alone → 33 (and `DrawDistanceAutoFov`, default ON, couples DD to 4×FovScale when DD is at its default, so "FOV-only" configs carry a hidden 5.2× effective DD). **Stochastic overlay:** `randomSetSeed(nowCount)` uses the wall clock, so trajectory → room load order varies per run; one run logged 174 failures while six subsequent identical runs logged zero — even at the *authored* pool size in short walks. That is why the bug was intermittent for the user and evaded early headless repro.
**Fix (port-only, `#ifdef PORT`):** `portRoomPoolScale()` in `port/src/video.c` returns a pool multiplier composed from the same knobs that widen the view — FOV term = manual FovScale × the WidescreenAuto aspect factor (both widen the frustum horizontally); DD/Lod terms = **square root** of the distance multipliers (room *count* grows far slower than distance² — measured 9→12 rooms at 4× DD on Statue — so sqrt is a deliberate under-estimate). Capped at 2.0× (worst case `-ma350` row → 700 KB, well inside the PC's ~6 MB STAGE bank, D95); **identity (1.0) when every knob is N64-faithful**, so all-default settings keep the exact authored budget and golden dumps stay byte-identical. Applied in `bossMainloop` (`src/boss.c`) immediately after the `-ma` token parse — the single chokepoint both play flows (menu-driven and `-level_XX`) pass through — clamped to STAGE-bank-minus-128 KB headroom. `GE_ROOMPOOL=<float>` overrides the computed scale outright (test hook; floor 0.25 for stress-testing the exhaustion path; also a user opt-out).
**Verified headless (`-level_22`, probes `GE_D294`/`GE_D104`; the `GE_D294` stage-load print is left in `boss.c`, env-gated):** all-N64-faithful INI → `ma=225280` = exact authored size (identity ✓); user settings (FovScale 130, DD/Lod 400) → `ma=450560` (2×) with **zero** alloc failures across repeated runs; `GE_ROOMPOOL=0.25` → `ma=56320` and exhaustion reproduces on demand (thousands of `FAIL alloc` lines with movement) — proving both the override and the mechanism.
**Residual, documented only (NOT a port bug): the far-horizon hard edge at high DrawDistance on night levels.** Where fog saturates to the sky colour before the geometry's far clip, the horizon reads as a sharp lit→black boundary. That is inherent to the D218 draw-distance setting on levels whose sky ≈ black (Statue, and similar); the N64 never shows it because its far clip is authored short enough that fog always saturates first. No fix warranted; if it bothers a user, lower DrawDistance.
**Side finding (CLI footgun, no fix — game code is ground truth):** `tokenFind(1, "-m")` **prefix-matches** `-ma`, `-ml`, `-me`, …, so passing a bare `-ma N` (or `-m`) on the PC command line corrupts `g_CurentMMallocValue` (→ 0 → divide-by-zero crash). The in-ROM rows are the only intended source of these tokens; don't pass `-m*` flags on the CLI. (The D121 guard `tokenFind(1, "-m") == 0` correctly means "no -m* flag supplied".)
**Playtest update (evening of 2026-09-22).** First live Statue session at the user's settings: the wide monument-plaza patch is **gone**; residual report — "minor geometry going black/void at the corner of the screen at max FOV when running forward/backwards fast". An instrumented re-test (`scratch/d294_capture.bat`) did not visibly reproduce, but the log caught **75 `FAIL alloc` events anyway** (room 11 ×43 @33 KB, plus 23/7/17) — brief per-frame-retried starvation episodes the eye doesn't register. Root of the residual: GE keeps loaded rooms resident, and Statue's **full 27-room set is 487 KB = 2.2× its authored pool** — running around at max visibility eventually demands the whole set, which starves even the 2× (450 KB) pool. The sqrt rationale measured only *simultaneously visible* rooms; it missed accumulated residency.
**3×/linear attempt REGRESSED — reverted (same evening).** Distance terms linear + cap 3.0 (`ma=675840`) fixed the starvation headroom but broke the renderer on the **menu-driven** Statue path: "world renders as untextured polygons, Bond's hands normal". Log evidence: `[D156] bad anim frame guarded: frame=nan ...` + `subcalcmatrices: frameb out of range!` spam starting ~5 frames after stage load; the corrupted Model has valid fields (speed=0.54, anim ptr into PERMANENT) mixed with NaN/garbage floats (newspeed≈1e24, elapsespeed≈-FLT_MAX). **Not a bank overflow** — `GE_D294BANK` probe shows STAGE [0x70050000..0x706a8000] with pos=0x702ebb30 (2.6 MB used, 3.7 MB free) and `needmemallocation=0`. **Path-dependent**: direct `-level_22` boots were clean in 4/4 headless runs (Statue at vi post #1); the user's menu-driven load (Statue at vi post ~#3541, after ~60 s of front-end) corrupted. Suspects: menu-path-specific init order, or wall-clock-seeded stochasticity that the short headless runs didn't hit. Reverted to sqrt + 2.0 cap (user-verified working state; `ma=450560` re-verified). Probes left in place for the next repro: D156 hexdump of the corrupted struct (`src/game/model.c`, TEMP-marked) + `GE_D294BANK` bank-state dump (`src/game/bg.c`, wired into `scratch/d294_capture.bat`).
**Status:** **FIXED (main symptom, user-confirmed); residual corner flicker OPEN** (2026-09-22). The 2× build is the shipped state. To close the residual: capture-bat run at max FOV with fast movement → if `FAIL alloc` recurs, the fix must cover whole-level residency (487 KB) **without** the 3× regression — options: scale only when the level's room-set total is known to exceed pool (bg data has per-room sizes at load), or a smaller targeted bump (e.g. 2.5×); any change needs BOTH a direct-boot AND a menu-driven verification.
## D211 — PC FOV slider — `Video.FovScale` (M-83 QoL wave, port-only, `WIDESCREEN-FOV-PLAN.md` Phase 4).
**PC FOV slider — `Video.FovScale` (M-83 QoL wave, port-only, `WIDESCREEN-FOV-PLAN.md` Phase 4).** fast3d applies a multiplicative half-angle scale to every *perspective* projection matrix as it is loaded (`gfx_apply_fov_scale()` in `gfx_pc.cpp gfx_sp_matrix`, on the `G_MTX_PROJECTION | G_MTX_LOAD` path only — GE always reloads its view projection via `guPerspectiveF` at `fr.c:725`, so a relative `G_MTX_MUL` onto an already-scaled matrix cannot compound). Columns 0 and 1 are scaled by the same factor `tan(half_orig)/tan(half_adj)` → H and V FOV widen uniformly, aspect ratio exactly preserved (no stretch), and because the game rewrites fovy every frame for aim-zoom the scale composes with zoom for free. Ortho matrices (`P[3][3]==1`) are left untouched so HUD/menu 2D is unaffected. `Video.FovScale` = percent of the original vertical FOV, 50–150, **default 100 ⇒ `g_fov_scale == 1.0f` ⇒ guarded early-return, zero FP ops on the matrix ⇒ golden dumps byte-identical**. F10 overlay row "FOV scale %". Math checked offline (`s=1.25 → fovy 60°→75°`, aspect held at 1.3333).
@@ -12466,7 +12484,9 @@ Ourumov = chr 78 on list 0x0417 (ai_22). Key offsets: off=38 pre-aim at Trevelya
### Classification
**As-authored latent race, not a port bug.** Every link — ai_22 flow, the update command's missing re-init, the `framea==frameb` pin, the ACT_ATTACK-gated stop loop — is byte-matched ROM logic; the N64 has the identical race and the identical unrecoverable softlock. The trigger (gas bit landing during the monologue while c78 is in pre-aim hold) is organic level state on both platforms.
**As-authored latent race, not a port bug.** Every link — ai_22 flow, the update command's missing re-init, the `framea==frameb` pin, the ACT_ATTACK-gated stop loop — is byte-matched ROM logic.
**N64 manifestation — CORRECTION (2026-09-22, user challenge).** The earlier claim "the N64 has the identical race and the identical unrecoverable softlock" was *inference* from byte-matched logic, never observation — we cannot instrument an N64. The user's empirical prior (and ~30 years of community play with no such known bug) is that guards do **not** freeze on real hardware. For that to hold, the derail must effectively never occur on N64 — i.e., the gas bit never lands inside c78's pre-aim hold window there. But that window is player-timing-gated (tanks destroyed during the hold) and tick counts are platform-invariant, so if the port were fully tick-faithful upstream of this beat, an organic PC freeze should reproduce on N64 with the same play. Two live hypotheses: **(i) an upstream port divergence** (timing shim / scheduler phase / earlier state misread) opens a derail window on PC that never opens on N64 — in which case the freeze is a *port-exposed* artifact and the real fix is finding that divergence; **(ii)** it can occur on N64 but has simply never been observed (narrow window). **Discriminating test (revised 2026-09-22.)** Primary — **deliberate timing on an unthrottled emulator**; the derail window is *player-controllable*, so no instrumentation is needed. Per D321 the combat bit lands 212 ticks (~3.5 s) after the last tank destruction, and c78's vulnerable span runs from pre-aim (off=38) through the monologue loop (off=75, check re-evaluated continuously; ~22 s). So: on an **unthrottled emulator** (stable 60 Hz tick regime — the same regime as the port; Mupen64Plus-Next/Project64 at full speed) with save states, trigger the Ourumov scene, then destroy the last tank ~1–15 s into the monologue; repeat with fresh loads to sample the aim-variant lottery. **Freezes ≈ the port's forced-repro rate (~2/3)** → the race is reachable under stable ticks; "N64 never freezes" is an organic-timing/frame-drop story (hypothesis ii; watchdog = insurance, no port bug). **Never freezes across ~10 deliberate placements** → the mechanism differs environmentally (hypothesis i) and there is a genuine upstream port divergence to hunt. Control first: confirm the *port* freezes at the expected rate under the same deliberate timing (forced-repro probe can calibrate this headlessly). Secondary — **PRNG-stream A/B** (only if the emulator never freezes and hypothesis (i) needs hunting): the port already ships `GE_RSEED_LV` (`port/src/d318watchdog.c`) + DET seed logging — read `g_randomSeed` from N64 RAM `0x80024460` at a canonical moment (emulator memory viewer; that read is the only console-side step), inject it with `GE_RSEED_LV=<hex>@<tick>`, replay the same play, and compare DET lines: any divergence pinpoints the tick where the port's stream leaves the original's. The watchdog remains justified either way as a port-side liveness measure: it acts only in a state where the game otherwise hard-locks.
### Fix (port-side, `port/src/d318watchdog.c` — same pattern as D202 M-66b)
@@ -12503,7 +12523,7 @@ Verified: build clean (MSYS2 MINGW64/ninja); `tools_pc/verify.sh bunker1` — PA
## D320 — D318-class sweep: aim-hold → `_update` AI lists in 8 levels carry the same as-authored softlock race (static analysis, 2026-09-20).
**Context.** D318 (Facility ai_22) root-caused a permanent softlock to a *generic* game-code mechanism, not to anything Facility-specific: `actor_fire_or_aim_at_target_update` (`chraction.c:4882`) succeeds whenever the chr is in ACT_ATTACK with `attacktype & (TARGET_AIM_ONLY | TARGET_DONTTURN)`, retargets, and calls `chrlvAttackActionRelated` — which only changes the model's **endframe** and **never calls `modelSetAnimation`**. If the chr is pinned on a non-looping aim-hold pose whose endframe H sits *before* the variant's shoot window (`shoot_start_frame`), and the new endframe E lands above H, nothing ever re-inits the animation (the `chrlvTickAttackCommon` "endframe <= frame" block's re-init sub-branches are all gated out: `unk31==0`, `unk33 != unk34`) → the model stays pinned below the shoot window → the timer/frame-gated fire events (`shoot_start <= frame < shoot_end`) never open → the chr never fires. If the surrounding AI script's only escape is an `if_guard_has_stopped_moving` gate, it can never pass: `chrHasStoppedOrPatroling` (`chraction.c:4291`) returns FALSE for ACT_ATTACK. **Permanent softlock.** Every link is byte-matched ROM logic; the N64 has the identical race (the random aim-variant pick in `chrlvInitActAttack`, `randomGetNext() % len`, differs per run on both platforms).
**Context.** D318 (Facility ai_22) root-caused a permanent softlock to a *generic* game-code mechanism, not to anything Facility-specific: `actor_fire_or_aim_at_target_update` (`chraction.c:4882`) succeeds whenever the chr is in ACT_ATTACK with `attacktype & (TARGET_AIM_ONLY | TARGET_DONTTURN)`, retargets, and calls `chrlvAttackActionRelated` — which only changes the model's **endframe** and **never calls `modelSetAnimation`**. If the chr is pinned on a non-looping aim-hold pose whose endframe H sits *before* the variant's shoot window (`shoot_start_frame`), and the new endframe E lands above H, nothing ever re-inits the animation (the `chrlvTickAttackCommon` "endframe <= frame" block's re-init sub-branches are all gated out: `unk31==0`, `unk33 != unk34`) → the model stays pinned below the shoot window → the timer/frame-gated fire events (`shoot_start <= frame < shoot_end`) never open → the chr never fires. If the surrounding AI script's only escape is an `if_guard_has_stopped_moving` gate, it can never pass: `chrHasStoppedOrPatroling` (`chraction.c:4291`) returns FALSE for ACT_ATTACK. **Permanent softlock.** Every link is byte-matched ROM logic. (N64 manifestation unverified — see D318's 2026-09-22 correction: the user's empirical prior is that no such N64 freeze exists; the same upstream-divergence-vs-unobserved question applies to every list in this sweep.)
**Sweep.** All 21 levels' AI script sources (`assets/obseg/setup/*.c`) scanned for lists containing both an aim command (`guard_try_fire_or_aim_at_target[_kneel]`, BITFIELD byte with `TARGET_AIM_ONLY` 0x20 set — the hold pose) and a `guard_try_fire_or_aim_at_target_update` reachable while the hold is active. 17 lists in 8 levels match; control-flow checked per list:
@@ -12525,15 +12545,22 @@ Verified: build clean (MSYS2 MINGW64/ninja); `tools_pc/verify.sh bunker1` — PA
**Why the Bond-combat loops are probably not affected.** In normal sustained fire the aim-hold endframe sits *at/inside* the shoot window (the hold pose is the recoil pose; shots fire from the `attack_time`/`unk44` timer machinery in `chrlvTickAttackCommon`, not from frame progression past endframe), so a `_update` 0x21→0x01 keeps the model pinned *inside* the firing window and the guard keeps shooting — that is how continuous fire works by design. The D318 danger requires the hold pose to be a distinct pre-shoot pose (H < shoot_start) — which is what "aim at X but don't fire yet" scripts produce. These combat lists are also exercised on every playtest of every level with no observed freezes, which is strong empirical evidence. This distinction is inferred from code reading + playtest history, **not** probe-verified per list.
**Classification.** As-authored latent race(s), faithfully reproduced — NOT a port bug (same classification as D318). No `src/game` fix is warranted or permitted absent Rule-2 sign-off; the N64 carries the identical risk on the identical random-variant branch.
**Classification.** As-authored latent race(s) in the byte-matched state machine; PC manifestation now confirmed by forced repro (below). N64 manifestation unverified — inherits D318's 2026-09-22 correction (upstream-divergence-vs-unobserved; A/B against an N64 capture is the discriminating test). No `src/game` fix is warranted or permitted absent Rule-2 sign-off.
**Verification path (owed).** The pin branch depends on the per-variant animation-table fields (H vs E), which are data, not code — static analysis cannot price the risk per list. Use the D318T-style timeline probe (`port/src/d318watchdog.c` `d318TimelineTick`, generalized to log any chr in ACT_ATTACK with an AIM_ONLY attacktype whose `modelGetAnimFrame` is byte-stable across ticks while `aioffset` sits in a stop-check loop) during playtests of: Facility (the ai_19 beat — the guard who monologues at the captive scientists before shooting), Control (the ai_9 beat), Depot (the ai_12 execution beat). A frozen run shows the exact D318 signature: stable `animframe1`, zero `modelSetAnimation` calls post-`_update`.
**Forced-repro results (2026-09-22, headless).** Method: probe `port/src/d320repro.c` (env-gated `GE_D320R`, TEMP) hijacks a named guard's AI list + offset at chosen ticks in a bare `-level_XX` boot. **Detector fix first:** the D318T timeline scanned only `g_ActiveChrs`, which holds *background script entities only* (chrnum 0xFE, allocated from AI lists with ID ≥ 0x1000 in `deb_loadallmodels.c`) — it was blind to every real guard. Real guards live in `g_ChrSlots[0..g_NumChrSlots)`; the detector now builds a unified pointer array over both (both are ticked by `chrlvAllChrTick`, chraction.c:9539).
- **ai_19 (Facility): PIN CONFIRMED.** c5 pinned at off=483 (label 0x34 loop), act=8, atk=0x8, ent=109 (pad 0x6d), frame stable 30.00/23.00 for thousands of ticks.
- **ai_9 (Control): PIN CONFIRMED, variant-dependent** (~2/3 of seed variants pin; the rest escape — the same random-variant structure as D318). Pinned at off=69 (label 0x0b loop), act=8, atk=0x04, ent=252, frame stable 23.00.
- **ai_12 (Depot): CLEAN 3/3.** The scripted `chr_hit_body_part_with_item_damage` kill progresses the beat to off=267+ regardless of pin state; no objective-block observed under forced repro → deliberately NOT covered by the watchdog.
**If confirmed in live play:** generalize the D318 watchdog instead of writing per-list copies — parameterize (AI list ID, offset range, chr number, target entityid, confirmation tick count) as a small table; the detection signature and the `sub_GAME_7F025560` re-init are already generic. The current `d318WatchdogTick` is hardcoded to ai_22/c78/c67 by design (one-shot, zero false-positive surface); keep it that way until a second list actually freezes in play.
**Watchdog generalized (`port/src/d318watchdog.c`).** Table-driven entries: `{0x0417, [321,333), ent=67, c67-alive}`, `{0x0414, [480,548), any}`, `{0x040a, [60,86), any}`; detection = ACT_ATTACK + live-fire attacktype (no TARGET_AIM_ONLY bit) + offset in range [+ per-entry target-alive condition], held 600 ticks; recovery = `sub_GAME_7F025560(chr, attacktype, entityid)` (the game's own fresh-attack entry). **Repeat rescue:** a successful reseed lets the attack play out → stop-check passes → the list re-aims → a new variant can re-pin (~240 ticks later), so if the signature persists the watchdog re-fires every 360 ticks, uncapped. Verified on both Facility and Control forced runs: continuous rescues at ~600-tick cadence, guard never frozen >~6 s. D318's entry behavior is unchanged by the refactor (code path re-verified in a live playtest is still owed). Fidelity note: detection + one-shot rescue is faithful (the N64 escape path does exactly this re-init); the *uncapped repeat* is an intentional port-side accommodation for the forced/context-dependent re-pin — same class as D318/D202 M-66b; logged (`D318W:`) for playtest audit.
**Observation (single, not reproduced).** Depot bare headless boot: c0 on ai_10 (a TREED guard list) pinned at off=49, act=8, atk=0x1 (live-fire), ent=0, frame stable 33.00 for 1200+ ticks — a combat-loop list the sweep rated "likely safe." Not reproduced on re-run. If seen in playtests, the "combat loops are safe" inference needs revisiting; severity is low either way (a frozen combat guard doesn't block objectives — the player can kill him).
**Status:** RESOLVED (2026-09-22) — pins confirmed on ai_19 + ai_9, clean on ai_12; generalized table-driven watchdog shipped. Open threads: (a) N64-fidelity classification per D318's 2026-09-22 correction (unthrottled-emulator deliberate-timing A/B); (b) the Depot ai_10 observation; (c) ai_13/14/15 pad lists untested (LOW-MED). Cross-ref: D318 (mechanism + watchdog), D263 (ai_22 mechanism map).
**Second-pass verification (2026-09-20, review of this entry).** All mechanism claims re-checked against source and confirmed: `chrHasStoppedOrPatroling` (chraction.c:4291) returns TRUE only for ACT_STAND/ACT_ANIM/ACT_PATROL; `actor_fire_or_aim_at_target_update` (chraction.c:4882) succeeds on ACT_ATTACK + (AIM_ONLY\|DONTTURN) and calls `chrlvAttackActionRelated`, which only ever calls `modelSetAnimEndFrame` (recoil/shoot start or end frame) — never `modelSetAnimation`. The three high/med-high lists were re-read in full: ai_19 (UsetuparkZ.c 2630–2826), ai_9 (UsetupcontrolZ.c 2279–2313), ai_12 (UsetupdepoZ.c 2174+) — structures match the table above, with the two precision corrections applied to the ai_19/ai_12 rows. **AI command-ID mapping settled:** `bondaicommands.h`'s explicit IDs are the cmdbuilder enum values from `aicommands.def` order (cross-checked on four known commands; a naive `#define CMDNAME` count is off by 2). In particular `if_guard_bitfield_is_set_on` = ID 0x96 = `AI_IFMyFlags2Has` — it tests the **executing guard's own** flags2 (`AiIFMyFlags2HasRecord {cmd, BITS, GOTOLABEL}`, handler chrai.c:3146), and `guard_bitfield_set_on` = 0x94 = `AI_SetMyFlags2`. The per-chr-targeting family is separate (0x97–0x99, `chr_bitfield_*`). `AI_TRYFireOrAimAtTarget[Kneel]` jumps to GOTOLABEL on success, falls through on failure (chrai.c:1372+). TARGET_ flag decode for the hold commands: 0x24 = CHR\|AIM_ONLY, 0x28 = PAD\|AIM_ONLY (hold poses); updates set 0x04/0x08 (live fire, no AIM_ONLY).
**Status:** OPEN — static sweep complete and second-pass verified; no live confirmation yet for any list beyond D318 itself. Cross-ref: D318 (mechanism + watchdog), D263 (ai_22 mechanism map).
(Superseded 2026-09-22 — see the RESOLVED status line at the end of this entry.)
## D321 — D318 trigger chain fully mapped: tanks → combat bit is an authored ~3.5 s gas-cascade delay (chr 254 script); derail lands same-tick; PC behavior confirmed faithful to N64 (probe captures, 2026-09-20).
@@ -12554,7 +12581,7 @@ Verified: build clean (MSYS2 MINGW64/ninja); `tools_pc/verify.sh bunker1` — PA
**Optional tuning (deferred, user's call).** `D318_DEADLOCK_TICKS` 600→300 would halve the worst-case pin-branch pause (10 s → 5 s); the margin over the natural escape (<2 s; run B: 0 ticks) stays ≥2.5×. One-line `#define` in `port/src/d318watchdog.c`; not done pre-ship to avoid re-validating the watchdog on the release branch.
**Status:** CLOSED — trigger chain mapped and faithful; D318 watchdog validated in live play (fired + recovered in run A, correctly inert in run B). Cross-ref: D318 (pin mechanism + watchdog), D320 (sweep of the same race in 8 levels), D263 (ai_22 mechanism map), M-192 (release review).
**Status:** CLOSED — trigger chain mapped and faithful *at the script-data level* (the ~3.5 s cascade delay and same-tick derail are identical ROM data on both platforms); D318 watchdog validated in live play (fired + recovered in run A, correctly inert in run B). Caveat per D318's 2026-09-22 correction: "faithful end-to-end" does not establish that the derail window ever opens organically on N64 — that remains the open upstream-divergence-vs-unobserved question. Cross-ref: D318 (pin mechanism + watchdog + N64-manifestation correction), D320 (sweep of the same race in 8 levels), D263 (ai_22 mechanism map), M-192 (release review).
## D322 — Long-session audio degradation: full campaign on v0.3.0, audio progressively worsens (Silo → Caverns/Cradle: OST inaudible, SFX "come and go"); restart restores it (issue #87, user report, 2026-09-20)
@@ -12614,3 +12641,45 @@ Full campaign (or at minimum Silo → Caverns/Cradle) with **`GE_D322=1 GE_D204=
**Live verification:** bare front-end boot (existing binary, new data only). `GE_PCDUMP="240-330:10"`: logo window goes 45 → 851 → 8,599 → **23,302 non-black px (7.59%)** over frames 260–320 with a wide wordmark bbox `(94,190)-(542,290)`; `GE_PCDUMP="340-560:20"` shows the logo continuing its rotation animation at 3–10% coverage (ASCII-art inspection: elliptical ring + shaded 3D lettering, grey/white palette consistent with the wordmark texture). Pre-fix baseline (M-196): 0–33 px/frame.
**Status:** **FIXED, user-confirmed live (2026-09-21)** — "the images and legal page and Nintendo logo are good now." Tooling fix + ntsc-final sidecars regenerated; headless-verified before the live check. Remaining: `pal-final`/`jpn-final` sidecar regeneration on machines holding those ROMs (`python tools_pc/d43_emit.py <region>` — the fix is in the tool, so any future regen is correct), and D75's last unverified scope: cast-roll models (status unknown since M-33; recheck with `GE_PCDUMP="1400-2200:25"`).
## D323 — G_TL_LOD bindings of explicit-LOD textures: PC samples driver mips from LOD0; N64 samples the authored mip chain (static census, 2026-09-21)
**Context.** D107 established that GE's room DLs emit `G_TL_LOD` and that the port folds every LOD draw to tile 0 (driver-generated GL mips from the finest level). D236 then found + fixed the one case where that folding was *visibly* wrong for a type-1 (DETAIL) binding of an RGBA5551 cut-out. This entry closes the loop on the rest of the surface: how often do LOD-on draw classes bind textures that actually carry **authored** mip levels, and does the N64 hardware behavior differ from the port's?
**N64 mechanism (confirmed in code).** For a texture whose imagelist header declares `hasExplicitLods` with N images (`tex->maxlod = N`, image.c:343), every binding type emits `gDPSetTextureLOD(G_TL_LOD)` + `G_TD_DETAIL` and sets up **N tiles**: tile 0 at the image-table TMEM slot, tiles 1..N-1 at successive TMEM addresses with halved dims/masks (`texWriteTileLods`, tex.c:355; one LOADBLOCK covering the whole chain via `texGetDepthAndSize`). With TEXTLOD on, the RDP ORs the minification level into the tile index, so distant geometry samples the **authored** coarser images.
**Port behavior.** fast3d folds all of tiles 0..N-1 to GL texture unit 0 (D107; D236's type-1 carve-out excepted), with `glGenerateMipmap` mips from LOD0. So on PC, minified geometry samples **driver-filtered** versions of the finest image instead of Rare's hand-authored mips.
**Census (`tools_pc/bg_lod_audit.py`, ntsc-final, bg room DLs only).** GE's modified-fast3d `G_SETOTHERMODE_H` is a partial field write (op 0xBA; w0 bits 8-15 = shift, bits 0-7 = width; the value sits at its *absolute* bit position in w1 — fast3d calls `gfx_sp_set_other_mode(shift+32, nbits, w1<<32)`, an identity map). TEXTLOD = bit 16. The tool walks every room DL tracking OML/OMH state and cross-references each bind's texnum against the imagelist explicit-LOD flag:
- **423** textures carry explicit LODs; **15 levels** bind at least one of them under a TEXTLOD-on class (azt 806, stat 378, arec 88, sevx 84, crad 17, pete 15, jun 25, depo 28, dest 8, run 8, arch 4, ark 4, sev 4, sevb 4, silo 2 LOD-on binds).
- Formats: overwhelmingly **RGBA16_CI8 / IA8** (7-8 authored levels each) — driver mips of these are usually close to the authored ones; this is a subtle fidelity gap, not an artifact.
- The only **RGBA5551** (1-bit alpha cut-out) explicit-LOD textures bound under G_TL_LOD anywhere are tex1198-1201 in sevx/sev/sevb — the D236 set, already handled by the type-1 carve-out; user reports Surface 2's treeline reads fine.
**Assessment.** Real N64-vs-PC divergence, pervasive but low-visibility: no reported artifact, and the one high-visibility subset (cut-outs) is already covered by D236. A full fix means uploading GL mip chains from the explicit LOD images for TEXTLOD-on bindings (tex.c + fast3d texture-cache plumbing), which would invalidate golden baselines across all 15 levels for a change nobody has seen to be wrong — not worth doing speculatively.
**Status:** OPEN / parked (GRAPHICS-BACKLOG). If a playtest ever flags mushy distant detail in one of the 15 levels above (Statue and Aztec are the heaviest), the fix path is: in fast3d's texture upload, when the binding class has TEXTLOD on and `tex->maxlod > 1`, feed `glTexImage2D` levels 0..N-1 from the decoded explicit images instead of `glGenerateMipmap`. Audit tool: `tools_pc/bg_lod_audit.py` (also documents the GE OMH field-write encoding, which no other tool captures).
## D324 — GE's <stdlib.h> stub declares nothing, so TUs that include it get NO CRT prototypes: implicit `int f()` declarations silently corrupt non-int returns (atof garbage in every float ini parse; D38 class)
**Context.** Found while verifying the D294 fix: the new `GE_ROOMPOOL` env hook in `port/src/video.c` called `atof()`, and the parsed value was deterministic-per-call-site garbage (observed 15279232 / 7087232 / 6817888 / 14288992 across runs) instead of the env string's value. The scale override was silently ignored on every run until prototypes were added.
**Mechanism.** `include/stdlib.h` (the N64 decomp's libc stub) wraps its entire body in `#if 0` — only the `lldiv_t`/`ldiv_t` typedefs are live. `port/shim/stdlib.h` (what `<stdlib.h>` resolves to for C TUs; see docs/internals.md §11 D12) includes that stub and then declared a couple of stragglers — but `getenv`/`realloc` **only on non-Windows**, on the empirically-wrong assumption that MinGW's other CRT headers "leak" prototypes (config.c gets no leaked `getenv`; the implicit-declaration warning proves it). So any port/src or game TU that calls a CRT function nothing else declared compiles with an **implicit `int f()` declaration**. On x86-64 SysV that means: a `double`/`float` return in **xmm0 is read from eax** (garbage), pointer returns are truncated to 32 bits, and `long` returns truncate on LP64 (Linux) builds.
**Why only some functions actually misbehave (the triage rule for this class):**
- **GCC builtins** (`floorf`, `sqrtf`, `memset`, …): GCC recognises them even when implicitly declared — it warns (`-Wbuiltin-declaration-mismatch`) but generates the *correct* xmm0/inline code. Harmless in practice. (This is why `sky.c`'s implicit `floorf` never visibly broke: correct values, and its fold result is additionally masked by `SKY_TC_WRAP` periodicity — any multiple of the wrap period is visually identical on a wrapping sky texture.)
- **int-returning** (`atoi`, `atexit`): the 32-bit int fits in eax exactly. Harmless (still UB per the standard; warning only).
- **Non-builtin float/double/pointer/64-bit-long returns** (`atof`, `strtod`, `getenv`, `strtol` on LP64, …): genuine silent corruption. This is the only bucket that needs fixing.
**Proven impact.** (1) `port/src/config.c` `parseFloat` → **every float ini option parsed to garbage whenever a user set one** — currently exactly one exists, `Game.ScreenShakeIntensity`, and the affected user had it set to 10 (silently became an eax-derived float for months). (2) The D294 `GE_ROOMPOOL` hook (video.c) — caught during verification. (3) `getenv` in 8 port TUs + `src/libultra/audio/csplayer.c`: pointer truncated to 32 bits — harmless *so far* on Windows (CRT env block sits below 4 GB), a landmine otherwise.
**Ruled out during diagnosis** (all tested, all clean): msvcrt corruption, locale, stack alignment, C++ runtime interference — plain MinGW test programs parse identically; only this build is affected, purely via the header shadowing. Garbage values are deterministic per call-site (same eax state each time) but vary between runs.
**Fix (port-layer only):**
- `port/shim/stdlib.h`: now declares `atof` / `strtod` / `strtol` / `atexit` / `getenv` **unconditionally** (K&R form — compatible with any real or leaked prototype), after including the N64 stub. `realloc` stays non-Windows-only (strict-GCC leak gap).
- `port/include/pc_protos.h`: gains `double atof(); double strtod();` for game TUs that include `<ultra64.h>` but never `<stdlib.h>` (no game TU currently calls them; future-proofing + canonical D38 list).
- `src/game/glass2.c` (the D219 `#ifdef PORT` block): `memset` → `__builtin_memset` so that block needs no prototype at all.
**Residual implicit declarations (audited, all in the harmless buckets above — warning-only, no action):** `atoi` in `src/game/chr.c`, `src/game/sky.c`, `src/game/bondview2.c`, `port/src/optionsoverlay.c`; `lround` in `optionsoverlay.c` (fine on Windows LLP64 where `long`=32 bits; would truncate on an LP64 Linux build — revisit only if a Linux port is actively maintained); the builtin family (`floorf`/`sqrtf`/`memset`) wherever it appears.
**Status:** FIXED (2026-09-22). Build clean; D294 verification battery re-run post-fix (faithful→authored size, user settings→0 failures, `GE_ROOMPOOL=0.25`→honoured + exhaustion on demand). Cross-ref: D38 (original implicit-declaration finding), D294 (where it was caught).