docs(D217): rule out stale-tail; best theory = CI-cache/palette collision

GE_D217Z experiment (zero rdp.palette[count..255] after each partial
load) did NOT change the green grip -> bad texels index within [0..count),
so the wrong palette is *bound* to the grip tile, not tail overflow.
Best remaining theory: TextureCacheKey collision / missing CI-cache
invalidation on palette change (key has no palette-content hash). Facility
renders green-uniformed guards right before the viewmodel -> a stale
cache HIT / palette_addrs collision makes the grip decode against the
guard-fatigue palette. Next probes + the all-levels regression
requirement noted in the finding.

Claude-Session: https://claude.ai/code/session_01U7gyYC3pcatXHhD1kPCgsR
This commit is contained in:
James Dansereau committed 2026-09-09 23:43:38 -04:00
1 parent 2e3d8be7e6
commit 2cdecd3aa6
1 file changed
+1 -1
+1 -1
View File
@@ -433,7 +433,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D214 | **Keyboard rebinding — `[Input.Bind]` config section (M-83, port-only, PD parity, Phase 4 debt).** `port/src/input.c` gains 12 gameplay actions (Forward/Back/StrafeLeft/StrafeRight/TurnLeft/TurnRight/Fire/Aim/Action/Cancel/LeanLeft/Start), each a comma-separated list of up to 4 SDL scancode names (`Input.Bind.Forward = W,Up` …). `inputRebuildBinds()` (called from `inputInit()`, after `configLoad()`) parses them via `SDL_GetScancodeFromName`; unknown names log a warning and are skipped. The old hardcoded `keyDown(ks, SDL_SCANCODE_*)` block in `inputComputePad` is replaced by `actHeld(ks, IA_*)`. Mouse buttons (fire = LMB, aim = RMB) stay hardwired; the mouse-wheel weapon-cycle A-pulse is untouched. **Defaults reproduce the previous FPS layout exactly — verified: all 20 default key names round-trip to the identical `SDL_Scancode` the old code used (`SDL_GetScancodeFromName` test, 0 mismatches).** Config-file only (no in-overlay text entry — matches PD, whose rebinding is also file-driven); a fresh `ge007.ini` gets the full `[Input.Bind]` block with the defaults visible. | **PARTIAL — M-84: code PASS (alt/ctrl); one doc-only fix.** LANDED, port-only, no `#ifdef PORT`; build clean. One doc bug fixed (no code change): `config.c` groups keys by the substring *before the last `.`*, so the section header is **`[Input.Bind]`**, not `[Bind]` — earlier finding/commit/QOL text was wrong. Working syntax: put `Fire = Left Alt` under `[Input.Bind]`, or write a fully-qualified `Input.Bind.Fire = Left Alt` line anywhere (`config.c:307`). Separately: the pause→**watch menu** does not wire Esc→back (still Enter to open/close/back) — pre-existing, `options.c`, minor. |
| D215 | **1P weapon viewmodel never drew in-level — `gunRenderFirstPersonGunModels` builds its `ModelRenderData` from a reinterpret across separately-declared adjacent globals that only works on N64 (M-83, D3x/ABI class; retires VIEWMODEL-RESEARCH.md).** The N64 line `renderdata = *(ModelRenderData *)&D_80035CC0;` (`gunfire.c`) casts the lone `u32 D_80035CC0 = 0;` to a whole `ModelRenderData` and reads on into the next declared global, `u32 D_80035CC4[] = {1, 3, 0, 0, …}` (`gun.c`) — yielding `{ basemtx=0, zbufferenabled=1, flags=3, rest=0 }` **only because the N64 linker lays the two globals out contiguously in declaration order and pointers are 4 B**. On x86-64 it breaks twice: `ModelRenderData`'s three pointer members (`basemtx`/`gdl`/`mtxlist`) widen 4→8 B so `flags` is no longer at struct offset 8, and C guarantees nothing about the two globals' relative placement. Measured (`GE_DVM=1`, BUNKER1): `renderdata.flags == 0`, so **every** weapon-model DL node was gated off at `modelRenderNodeGundl` (`(renderdata->flags & 1)`), and `subdraw()` for the weapon emitted just its `gSPSegment` — 1 gfx command instead of ~150. All three VIEWMODEL-RESEARCH.md ranked suspects were ruled out by the same probe first: `field_87F==1` (gate open), `RootNode` valid, `numRecords 22 < 192` (pool fine), node walk visits 24+ valid `DL`/`SWITCH`/`BSP` nodes. **Fix:** `#ifdef PORT` → set the three fields explicitly (`zbufferenabled=TRUE; flags=3;`), N64 line verbatim under `#else`. ABI/layout only, no logic change. | **FIXED (`src/game/gunfire.c`, `#ifdef PORT`).** Build clean. `GE_DVM=1` post-fix: `flags=0x3`, weapon `subdraw()` now emits **153** gfx commands (was 1). Regression sweep crash-free. `GE_DVM` gate+render probes left in, env-gated (cf. `GE_D193*`). **Owed:** one BUNKER1 eyeball that the gun model is actually visible + correctly posed (headless can't see pixels; the DL is now provably generated). The committed `bunker1` golden will need re-baselining — the gun now renders in-frame. |
| D216 | **`Game.SkipIntro` — boot straight to the file-select menu (M-83, port + one `#ifdef PORT` game-code line, PD parity, Phase 4 quick win).** New `s32 portSkipIntro` (`port/src/video.c`, `configRegisterInt("Game.SkipIntro", 0, 1)`), read once in `src/game/lv.c` at `LEVELID_TITLE` stage load — in the `else` of the existing `GE_STARTMENU` `#ifdef PORT` block. When set it does what every intro stage's own `!is_first_time_on_main_menu` branch does (`front.c`): `is_first_time_on_main_menu = FALSE; prev_keypresses = TRUE; maybe_is_in_menu = TRUE; menu_update = MENU_FILE_SELECT;` — i.e. it reuses the game's own post-intro route rather than inventing one, so the legal screen + Nintendo/Rare/GoldenEye logo attract loop is skipped. `GE_STARTMENU` still wins when both are set. Default 0 ⇒ untouched. | **LANDED + VERIFIED this session.** Build clean. Headless: default boot frame 90 = the TWYCROSS classification screen (10% non-black), `SkipIntro=1` frame 90 = the **SELECT FILE menu** (folders/photos/cursor all render, 89% non-black), no crash across repeated boots. Screenshots captured + eyeballed. **Owed:** nothing critical — a human boot to confirm the menu is fully interactive (cursor moves, a save slot opens). |
| D217 | **1P weapon viewmodel + third-person Bond model textures render garbled — wrong/stale palette on model-DL CI textures (M-84, user QA; surfaced by D215 now that the viewmodel draws).** BUNKER1/Facility: the PP7 viewmodel has yellow+blue scrambled texels and a solid **green** patch by the hammer; Bond's in-game **watch face** shows the same green mismatch; user reports the **third-person Bond world model** does this "sometimes" too. Signature matches the **D161 family** — a CI-format tile drawn without the expected TLUT state, so fast3d's `import_texture` indexes a **stale `rdp.palette`**. D161 fixed the explicit `G_TT_NONE` case; this looks like model DLs not emitting `gDPLoadTLUT` through the port path, or the palette segment not resolving for weapon/character geometry. **Not a D215 regression** — D215 only un-gated the DL. | **OPEN — repro pinned to FACILITY (M-85).** Clean on `-level_09` BUNKER1 (any FovScale / TextureFilter / FixMipTextures / Anisotropy). **Reproduces on `-level_34` FACILITY, ~frame 300 (~5 s in, right after the vent-drop intro cutscene hands control to the player):** the PP7 **grip is a solid dark-green block**, a **green ring** at the barrel/slide joint, and the **hand has a mottled green cast** — while the **slide/barrel upper renders correct black**. So *some* of the weapon+hand model's CI tiles decode against a wrong palette and others don't. `GE_TEXI` shows the weapon tiles do import as `fmt=2 palfmt=0x8000 palidx=0` with a loaded TLUT — `gDPLoadTLUT` **does** reach `gfx_pc.cpp` for model DLs, so "model DLs don't emit LoadTLUT" is disproven. The green-tinted **hand** (a skin texture reading a green palette entry gives exactly this look) points at a **wrong palette**, not corrupt index data. **M-85 probe** (`GE_D217` temp log in `gfx_dp_load_tlut` + the CI branch of `import_texture`, since reverted; logs in session scratchpad `d217_fac.log` / `d217_bnk.log`): (i) **user confirms it is not Facility-specific — guns on many levels, and multiple weapon types, render with wrong or missing textures**; the Facility PP7 is just a clean headless repro. (ii) GE weapon/character model DLs issue **partial-count `G_LOADTLUT` commands** — `count` = 1, 28, 53, 103, 123, 125, 172, 193, … (never `<<2`-encoded, so the decode is reading the raw count field of `gsDPLoadTLUTCmd` correctly; 256 also appears for world tiles). A `count`=125 load writes `rdp.palette[0..124]` and leaves `[125..255]` holding **whatever the previous TLUT load left** — and those stale upper entries differ per level (Facility `pal[128,200,255]=8c1b eedf f7f5` vs BUNKER1 `841f 9b15 fa53`). (iii) The weapon TLUT load **sequence + counts are identical on BUNKER1 and FACILITY** (same PP7), and the weapon CI8 tiles import with `palidx=0` + `cache=HIT` on the steady-state frames. So the delta is purely the **stale upper-palette contents** a weapon CI8 texel lands in when it indexes ≥ its palette's `count`. Root cause still open: either (a) the port's CI8 decode reads texel bytes past the base image (RC2/D183 mip-or-pitch bleed → high indices) **only** when the level's asset layout triggers it, or (b) GE's weapon textures legitimately rely on N64 TMEM-TLUT leftover semantics that the port's `rdp.palette` doesn't reproduce. Next: dump the **max texel index** actually used by a green weapon tile + its base `size_bytes`/`line`/`full_line`, vs the active `count`; if `maxidx >= count`, chase the decode (RC2/D183) — if `maxidx < count`, the load itself is short. Also try zeroing `rdp.palette[count..255]` after each partial load and see if the green → black (N64-plausible) without regressing world tiles. **Wants a dedicated debug session / scoped subagent.** Screenshots: session scratchpad `fac_gun_crop.png`; `docs/dev/GRAPHICS-BACKLOG.md` cross-ref. |
| D217 | **1P weapon viewmodel + third-person Bond model textures render garbled — wrong/stale palette on model-DL CI textures (M-84, user QA; surfaced by D215 now that the viewmodel draws).** BUNKER1/Facility: the PP7 viewmodel has yellow+blue scrambled texels and a solid **green** patch by the hammer; Bond's in-game **watch face** shows the same green mismatch; user reports the **third-person Bond world model** does this "sometimes" too. Signature matches the **D161 family** — a CI-format tile drawn without the expected TLUT state, so fast3d's `import_texture` indexes a **stale `rdp.palette`**. D161 fixed the explicit `G_TT_NONE` case; this looks like model DLs not emitting `gDPLoadTLUT` through the port path, or the palette segment not resolving for weapon/character geometry. **Not a D215 regression** — D215 only un-gated the DL. | **OPEN — repro pinned to FACILITY (M-85).** Clean on `-level_09` BUNKER1 (any FovScale / TextureFilter / FixMipTextures / Anisotropy). **Reproduces on `-level_34` FACILITY, ~frame 300 (~5 s in, right after the vent-drop intro cutscene hands control to the player):** the PP7 **grip is a solid dark-green block**, a **green ring** at the barrel/slide joint, and the **hand has a mottled green cast** — while the **slide/barrel upper renders correct black**. So *some* of the weapon+hand model's CI tiles decode against a wrong palette and others don't. `GE_TEXI` shows the weapon tiles do import as `fmt=2 palfmt=0x8000 palidx=0` with a loaded TLUT — `gDPLoadTLUT` **does** reach `gfx_pc.cpp` for model DLs, so "model DLs don't emit LoadTLUT" is disproven. The green-tinted **hand** (a skin texture reading a green palette entry gives exactly this look) points at a **wrong palette**, not corrupt index data. **M-85 probe** (`GE_D217` temp log in `gfx_dp_load_tlut` + the CI branch of `import_texture`, since reverted; logs in session scratchpad `d217_fac.log` / `d217_bnk.log`): (i) **user confirms it is not Facility-specific — guns on many levels, and multiple weapon types, render with wrong or missing textures**; the Facility PP7 is just a clean headless repro. (ii) GE weapon/character model DLs issue **partial-count `G_LOADTLUT` commands** — `count` = 1, 28, 53, 103, 123, 125, 172, 193, … (never `<<2`-encoded, so the decode is reading the raw count field of `gsDPLoadTLUTCmd` correctly; 256 also appears for world tiles). A `count`=125 load writes `rdp.palette[0..124]` and leaves `[125..255]` holding **whatever the previous TLUT load left** — and those stale upper entries differ per level (Facility `pal[128,200,255]=8c1b eedf f7f5` vs BUNKER1 `841f 9b15 fa53`). (iii) The weapon TLUT load **sequence + counts are identical on BUNKER1 and FACILITY** (same PP7), and the weapon CI8 tiles import with `palidx=0` + `cache=HIT` on the steady-state frames. So the delta is purely the **stale upper-palette contents** a weapon CI8 texel lands in when it indexes ≥ its palette's `count`. (iv) **`GE_D217Z` experiment — zeroing `rdp.palette[count..255]` after every partial load did NOT change the green grip/ring/hand** → the bad texels index **within** the loaded `[0..count)` range, so the *palette content bound to the grip tile is wrong*, not stale-tail overflow. (v) Best remaining theory: **`TextureCacheKey` collision / missing CI-cache invalidation on palette change** (`gfx_pc.cpp:990` — key = `{orig_addr, palette_addrs[0/1], fmt, siz, palidx, size_bytes}`, no palette *content* hash; `rdp.textures_changed` is set by `gfx_dp_load_tlut` but may not evict CI entries). Facility has **green-uniformed guards rendered right before the viewmodel**; if the grip tile takes a stale `cache=HIT` (or its `palette_addrs` collide with the guards' TLUT source) it decodes against the **guard-fatigue palette** → green grip + green-cast hand, barrel (different material/palette, imported clean) stays black. Fits "widespread, weapon- and proximity-dependent." Next: log CI-cache hit/miss + a cheap palette checksum per weapon tile on Facility; test making the CI cache key include a palette-content hash (or evicting CI entries on `textures_changed`) — **must regression-sweep all 21 levels' world CI textures**, this is a hot path. **Wants a dedicated debug session / scoped subagent.** Screenshots: session scratchpad `fac_gun_crop.png`; add to `docs/dev/GRAPHICS-BACKLOG.md`. |
| D218 | **Wide FOV exposes the per-level draw-distance / fog boundary — "blue artifacting" down long peripheral sightlines (M-84, user QA on the D211 FOV slider).** BUNKER→**Dam main tunnel** at `FovScale` ~150: geometry / backdrop past the level's fog-end distance shows at the widened frustum edges (blue = Dam's fog tint). Not a render bug — `fogLoadCurrentEnvironment` (`bgfog.c:305`) does `viSetZRange(Visibility.BlendMultiplier, Visibility.FarFog)`, so **`Visibility.FarFog` is both the far clip plane and the fog-saturation distance**; levels tune it for the stock ~60° FOV. | **Logged (M-84), fix proposed + parked.** Plan: `Video.DrawDistance` (percent, 100–400, default 100 ⇒ no-op) `#ifdef PORT` multiplier on `FarFog` at `bgfog.c:305` (+ the direct `arg0->Visibility.FarFog` reads just below, so fog stays consistent) plus `Video.DrawDistanceAutoFov` (0/1, default 1 — couples draw distance to `FovScale` unless `DrawDistance` is set explicitly). Clamp effective multiplier ≲2.5× (small znear ⇒ far-field z-fighting risk). |
| D85 | room primary/secondary DL → `texLoadFromGdl` garbage | OPEN (safety-netted; widen/pool fixes landed, geometry now renders) |
| D86 · D87 | modelInitRwData truncated ptr · attract-demo BE `ramromfilestructure` | resolved |