From 14bf7e59f2c08e6a1b73fc1b7f32828a51b8e441 Mon Sep 17 00:00:00 2001 From: James Dansereau <106631954+jkdansereau@users.noreply.github.com> Date: Thu, 10 Sep 2026 23:58:44 -0400 Subject: [PATCH 1/3] TEMP probe(D217): log decode-time palette[0] keyed by texnum (GE_D217TEX) --- src/game/image.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/game/image.c b/src/game/image.c index 56daa0d9..998c0761 100644 --- a/src/game/image.c +++ b/src/game/image.c @@ -224,6 +224,19 @@ s32 texInflateZlib(u8 *src, u8 *dst, s32 arg2, s32 forcenumimages, struct texpoo palette[i] = texReadBits(16); } +#ifdef PORT + /* TEMP D217 (M-89 lead a): log the palette bytes as decoded straight off + * the bitstream, keyed by texture number, so a run on BUNKER1 and a run + * on FACILITY can be diffed to see whether the SAME texnum yields the + * SAME palette[0] -- if not, the divergence is upstream of texInflateZlib + * (ROM offset table / bitstream source), not the runtime TLUT-address + * math (already ruled out, see findings.md D217 M-89). */ + if (getenv("GE_D217TEX")) + osSyncPrintf("D217TEX texnum=%d format=%d numcolours=%d pal0=%04x pal1=%04x\n", + (int)arg4->rightpos->texturenum, (int)format, (int)numcolours, + (unsigned)palette[0], (unsigned)(numcolours > 1 ? palette[1] : 0)); +#endif + foundthething = FALSE; for (j = 0; j < numimages; j++) From 78f002e5cfa336a625ede772e5b2e25ef7783ab1 Mon Sep 17 00:00:00 2001 From: James Dansereau <106631954+jkdansereau@users.noreply.github.com> Date: Fri, 11 Sep 2026 00:01:57 -0400 Subject: [PATCH 2/3] probe+docs(D217): M-89 cont. - decode proven correct+identical; parked defect 2 fix does NOT fix this; new stale-arena-pointer theory Added a GE_D217TEX env-gated probe (src/game/image.c, texInflateZlib) logging texnum/numcolours/palette[0..1] straight off the decode bitstream -- the most direct check of whether the SAME texture number decodes to the SAME bytes across levels. Result: texnum=1608 (the PP7 grip, confirmed by its pal0=0001 matching the previously-documented BUNKER1-correct value) decodes IDENTICALLY on both BUNKER1 and FACILITY. This disproves the 'different ROM bytes per level' theory and confirms the bug is strictly downstream of a provably correct decode. Then built and tested the parked fix/d217-model-texture-palette (f6fc0399, the CI-texture-cache content-hash fix, defect (2)) directly against the FACILITY repro -- still renders green. That fix does not close this row; defect (1) and (2) are confirmed separate bugs. New leading theory: a stale/dangling tex->data pointer into the texpool arena, re-resolved once and not re-checked at draw time, so a later texture decode reusing the same arena address on a texture-heavy level silently overwrites the grip's palette bytes. Documented as the concrete next probe for whoever picks this up. Probe left in tree, env-gated (GE_D217TEX), inert by default. tools_pc/verify.sh bunker1: PASS, no regression. Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG --- docs/dev/findings.md | 79 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/docs/dev/findings.md b/docs/dev/findings.md index 4e2dd373..76fd5ff3 100644 --- a/docs/dev/findings.md +++ b/docs/dev/findings.md @@ -8649,3 +8649,82 @@ applying it — do not repeat this fix:** that is the one load-bearing assumption ("same weapon, same ROM bytes") this whole investigation has taken for granted but never directly checked with a probe. + +## D217 — M-89 cont.: decisive probe — decode is proven correct+identical across levels; the parked defect ② fix does NOT fix the visible symptom; new leading theory is a stale/dangling `tex->data` arena pointer, not a decode or cache-key bug + +Ran the actual empirical test the M-89 section above called for, rather than +delegating it further. **Result answers the open question decisively and +rules out both of this row's previous leads.** + +**Probe added** (`src/game/image.c`, `texInflateZlib`, `#ifdef PORT` + +`getenv("GE_D217TEX")`, follows the `GE_TEXDUMP`/`GE_D85TEX` pattern): +logs `texnum`/`format`/`numcolours`/`palette[0]`/`palette[1]` **straight off +the decode bitstream**, before any arena/pointer math — the most direct +possible check of "does this texture number decode to the same bytes on +both levels." + +**Result:** ran BUNKER1 (`-level_09`) and FACILITY (`-level_34`) headless +with `GE_D217TEX=1`. `texnum=1608` is the only `numcolours=1` texture common +to both logs, and its `pal0` value (`0001`, black) matches the previously +documented BUNKER1-correct value exactly — confirming this is the PP7 grip. +**`texnum=1608` decodes to `pal0=0001` on BOTH levels, identically, every +time it's called.** This directly disproves the M-89 "different ROM bytes +per level" open question (lead a is dead — the decode is correct and +level-invariant) and also rules out any remaining decode-math theory: the +bug is not in `texInflateZlib`, `texGetDepthAndSize`, or `gfx_dp_load_tlut` +(already shown correct M-89) — it is **strictly downstream of a provably +correct decode**. + +**Then tested the parked defect ② fix (`fix/d217-model-texture-palette`, +`f6fc0399` — the CI-texture-cache-key content-hash) directly against this +exact FACILITY repro**, since "correct decode, wrong render" is precisely +the signature that fix targets (a stale cache `HIT` serving a different +material's already-decoded palette). **Built it, ran `-level_34`, captured +frame 380: the grip is still solid green.** The already-written, previously +untested-against-this-repro defect ② fix does **not** fix the visible +symptom. This means defect ① and defect ② are **not the same underlying +bug** — they were right to be tracked separately, and defect ②'s fix should +still land (real, separate, still needs its 21-level sweep) but will not +close this row on its own. + +**New leading theory (untested, most concrete lead yet):** since a fresh +decode is proven correct, and the fast3d-side cache-key fix doesn't help, +the render-time GBI command for this material must be reading a `tex->data` +pointer whose *arena contents have changed* between when it was decoded and +when the DL that references it is actually drawn — i.e. a **stale/dangling +pointer into the `texpool` arena**, not a decode or cache-lookup bug. GE's +`texpool` (`image.c` `texLoad`/`texInitPool`, two-ended `leftpos`/`rightpos` +allocator) is reused across many textures per level; if it resets or the +model-node/GDL that references texture 1608 caches a raw pointer/segment +address resolved once (e.g. at weapon-pickup or level-load time) rather +than re-resolving through `texFindInPool` every time it draws, then any +*later* texture decode that reuses that same arena address (plausible on a +texture-heavy level like FACILITY, less likely on the lighter BUNKER1) would +silently overwrite the grip's palette bytes with whatever the next tenant's +content is — explaining both "same weapon, different wrong color per level" +(depends on what else that level's arena happened to decode nearby/after) +and "clean on the lighter level" (less arena churn/reuse pressure). + +**Confidence:** HIGH the decode itself and the fast3d/cache-key layer are +now both cleared (empirically, not just by inspection). MEDIUM-LOW on the +stale-arena-pointer mechanism — plausible and consistent with all evidence +so far, but not yet probed directly. + +**For whoever picks this up next:** probe whichever code path holds the +`Gfx*`/model-node reference to this material's `SetTextureImage` address at +*draw* time (`texWriteLoadToTmemAddr`'s caller chain from the weapon +model-DL walk, not `texLoad` itself) and log the `tex->data` pointer value ++ a fresh read of the two bytes at the computed palette offset **at draw +time**, alongside a second log of what `texInflateZlib` most recently wrote +to that same address (extend the `GE_D217TEX` probe with an address field +to correlate). If the drawn address's live content no longer matches what +was last decoded there, that confirms the stale-arena-pointer theory +directly and narrows the fix to wherever the model DL caches that pointer +instead of re-resolving it. Probe left in tree (`GE_D217TEX`, env-gated, +inert by default). + +**VERIFY performed this session:** `tools_pc/verify.sh bunker1` — PASS, +no regression from the (inert-by-default) probe. `-level_09`/`-level_34` +`GE_D217TEX=1` headless captures — decisive result above. Built and ran +the parked defect ② fix directly against `-level_34` frame 380 — still +green, screenshot evidence in this session's scratchpad. From 3d3044a4696fed9fe024c8737459b77ee400d3b5 Mon Sep 17 00:00:00 2001 From: James Dansereau <106631954+jkdansereau@users.noreply.github.com> Date: Fri, 11 Sep 2026 00:06:25 -0400 Subject: [PATCH 3/3] docs(GE-ENV-PROBES): add GE_D217TEX row (validate CI gate) --- docs/dev/GE-ENV-PROBES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev/GE-ENV-PROBES.md b/docs/dev/GE-ENV-PROBES.md index 4d690de7..183dc924 100644 --- a/docs/dev/GE-ENV-PROBES.md +++ b/docs/dev/GE-ENV-PROBES.md @@ -70,6 +70,7 @@ Two classes: | `GE_D69STAN` | `src/game/stan.c:266,276` | D69 stan-tile trace | dead | | `GE_D85DUMP` | `src/game/bg.c:2510` | D85 `bgWidenRoomGdl` 8→16B dump | dead (D85 closed) | | `GE_D85TEX` | `src/game/image.c:2479`, `src/game/tex.c:859,1050` | D85 texpool-full events (Depot analysis) | dead (D85 closed) — was useful for §3 | +| `GE_D217TEX` | `src/game/image.c:234` (`texInflateZlib`) | D217: logs `texnum`/`format`/`numcolours`/`palette[0..1]` straight off the decode bitstream (before any arena/pointer math) — the decode-correctness check that proved the affected texture (grip, `texnum=1608`) decodes byte-identically across levels, ruling out a decode/ROM-data bug. | **live** (D217 open — investigation instrument) | | `GE_D86` | `src/game/model.c:6295`, `src/game/objecthandler_2.c:143` | D86 model rwdata trace | dead (D86 closed) | | `GE_D87` | `src/game/ramromreplay.c:296,363,381` | D87 `ramromfilestructure` endianness | dead (D87 closed) | | `GE_D88` | `src/game/prop.c:1367`, `src/game/stan.c:3090` | D88 `Usetup*Z` propDef stream | dead (D88 family closed; D88.4 resolved) |