D341: intro gun-barrel blood drip drew static garbage

Two fast3d defects. (1) The 1-cycle blood texture rectangle sampled a
stale tile-1 CI8 declaration: the title leaves G_TL_LOD set and the D236
detail-base rule redirected texunit 0 to tile 1. LOD tile selection is
2-cycle-only on the RDP, so texrects outside 2-cycle now sample their own
tile. (2) The drip image is regenerated in place in the dyn pool (reached
via the KSEG0 mirror); the address-only cache key froze it on its first
image. Dyn-pool textures now key on a content hash. Headless-verified,
golden 3/3. A/B: GE_DYNTEXHASH_OFF.
This commit is contained in:
James Dansereau committed 2026-09-26 19:20:06 -04:00
1 parent f242992360
commit 5c643e3acb
5 files changed
+66

No files matched your search

+1
View File
@@ -69,6 +69,7 @@ Two classes:
| `GE_D252` | `port/fast3d/gfx_pc.cpp:733` (`import_texture`, cached `s_d252`) | D252 rainbow-particle diag: one-shot per (address,size) import log of the tile state (tmem/fmt/siz/addr/size/line) for small textures so a suspect particle tile can be checked against its source bytes by hand. Remove once D252 is resolved. | **live** (D252 OPEN — non-reproducible) |
| `GE_D252POOL` | `src/game/image.c` (`texLoad`), `src/game/initmttex.c`, `port/src/gimgfixup.c` | **TEMP** (M-201): one line per new texture load (pool main/other, free bytes) + `POOL-FULL` events + stage pool range; `D252SYNC: N slots re-synced, M changed` per stage load (the D252 fix at work: M>0 from the 2nd stage on). Load-time only, no fps cost. | **TEMP** (D252 fixed; remove after release) |
| `GE_D245_OLDVTX=1` / `GE_D245_OLDTEX=1` | `src/game/sky.c` (`skyPortRenderPoly`) / `port/fast3d/gfx_pc.cpp` (CI8 import + tri-path dims) | **A/B** (M-201): restore the pre-fix s16 sky/water vertex path / the pre-fix 32x43, 16-wide water texture geometry. | **TEMP** (D245 fixed; remove post-release) |
| `GE_DYNTEXHASH_OFF=1` | `port/fast3d/gfx_pc.cpp` (`import_texture` cache key) | **A/B** (M-201, D341): disable the content-hashed cache key for dyn-pool textures (reverts to the stale address-only key). | **TEMP** (A/B switch) |
| `GE_D207S` | `src/game/gunfire.c` (player shot-sound block, `#ifdef PORT`) | **TEMP** (M-201): one line per player shot-sound decision: `D207S t=<g_GlobalTimer> hand item rate -> H1|H2|SKIP` (SKIP = both per-hand sound handles still held). Read-only, uncapped, cheap; use instead of `GE_AUDIOTRACE` for live captures (that probe does file I/O on the audio path). | **TEMP** (D207/D241) |
| `GE_D306C="f0 f1 [wmax]"` | `port/fast3d/gfx_pc.cpp` (after `zf_note_emit`) | **TEMP** (M-201): depth-state census of every emitted triangle in frames f0..f1 (GL depth mode, geometry_mode, other_mode_l, tile-0 texture, NDC centroid, z/w range, min w); optional `wmax` keeps near geometry only. Cap 60000 lines. | **TEMP** (D306 closed; remove post-release) |
| `GE_INTROCAM=<n>` | `src/game/bondview_r.c` (`#ifdef PORT`, intro camera pick) | **TEMP/test-only**: force the level intro's first camera index (Dam truck shot = 5). The PRNG draw is still consumed. Logs `D306: intro camera index` when set (or with `GE_D306C`). | **TEMP** (useful repro tool) |
+1
View File
@@ -218,3 +218,4 @@ D336,FIXED,"Frigate (and any level) ""all polygons break except the gun"" after
D337,FIXED,Aim modes folded (user decision):,FIXED (2026-09-26 bookkeeping:
D338,FIXED,"Input.AimRange (F10 ""Aim range""):",FIXED (2026-09-26 bookkeeping:
D339,OPEN,"#92 (low-end ~30 fps, Celeron N3060 / HD 400) —","OPEN (2026-09-26, user:"
D341,FIXED,"Intro gun-barrel blood drip draws as static blocks/lines instead of dripping (user, v0.4.0, 2026-09-26; death blood fine).",FIXED (M-201): two fast3d defects —
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
218 D337
219 D338
220 D339
221 D341
+15
View File
@@ -626,6 +626,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D337 | **Aim modes folded (user decision): N64 (default) = the N64 aim model with the mouse fed through the game's integrator at PD's damp (D332 made default); CENTRED (PC) opt-in; Q/L and Toggle-aim now route mouse aim like RMB; pad/keyboard aim no longer swallowed by the mouse integration.** — full `## D337` entry at file tail | FIXED (2026-09-26 bookkeeping: shipped; user reports it feels good in v0.4.0 play). Earlier: IMPLEMENTED (port/src/input.c + optionsoverlay.c); headless-measured with new scripted-mouse harness; golden 3/3; Win + Linux builds. Open: N64-range calibration decision. |
| D338 | **`Input.AimRange` (F10 "Aim range"): PC (default, GEPD/mouse-injector full-screen crosshair) or N64 (original stick limits: 65% of half-width, camera turn from ~49%).** — full `## D338` entry at file tail | FIXED (2026-09-26 bookkeeping: shipped; user reports it feels good in v0.4.0 play). Earlier: IMPLEMENTED + measured (PC 98%/72%, N64 65%/49%); golden 3/3; Win + Linux builds. |
| D339 | **#92 (low-end ~30 fps, Celeron N3060 / HD 400) — measured CPU budget: game thread ~0.2–0.35 ms/frame, render-thread display-list pass 0.4–1.8 ms/frame on this box; CPU very likely not the bottleneck. New `GE_PERFSTAT=1` probe (render phases, tris/batches, game-thread busy, GPU timer query).** — full `## D339` entry at file tail | OPEN (2026-09-26, user: review toward the end of v0.4.0; needs a test machine with similar HD 400-class hardware). Earlier: INVESTIGATING — probe landed; target-hardware numbers needed before any fix. |
| D341 | **Intro gun-barrel blood drip draws as static blocks/lines instead of dripping (user, v0.4.0, 2026-09-26; death blood fine).** — full `## D341` entry at file tail | FIXED (M-201): two fast3d defects — the 1-cycle blood texrect sampled a stale tile-1 declaration via the D236 LOD rule, and the in-place-regenerated dyn-pool texture hit a stale address-keyed cache entry. |
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
@@ -13497,3 +13498,17 @@ Implemented as `Input.PdMouseAim` (**default off**, because it changes aim feel)
**Next (needs HD 400-class hardware, not reporter logs):** run `GE_PERFSTAT=1` on a comparable machine at defaults vs MSAA 1 / aniso 1 / bilinear; if GPU-bound, consider a first-run low-end preset (the Steam Deck preset, `da31edaa`, is the precedent) keyed on the GL renderer. CPU-side headroom if ever needed: batches per frame (100–411) is the obvious lever (state-change batching in `gfx_flush`).
## D341 — Intro gun-barrel blood drip draws as static blocks/lines instead of dripping (M-201, 2026-09-26)
**Report:** the 2D red blood "drip" at the end of the gun-barrel intro glitches and doesn't draw right; the death blood looks normal.
**Measured:** `die_blood_image_routine` builds a correct 96x80 I4 drip image each step (dumped and rendered: a growing top band with drips); `gunbarrelBloodOverlayDL` draws it as a full-screen 1-cycle texture rectangle at the title's 440x330 VI (dsdx 223, dtdy 248, tile 0 = I4 96x80, `GE_D116` confirmed). The screen showed a constant pattern of red blocks and horizontal lines that never changed.
**Cause 1 (the wrong image):** texture-unit 0 imported from **tile 1** (`unit=0 tile=1 cyc=1CYCLE lod=1`): the title leaves `G_TL_LOD` set from earlier draws, and `gfx_lod_tile_offset`'s D236 detail-base rule (tile 1 in different TMEM with a different format → sample tile 1) redirected the rectangle to a stale CI8 tile-1 declaration sharing TMEM 0 — 128 bytes of the blood buffer decoded as CI8 and stretched. On the RDP, LOD tile selection only exists in 2-cycle mode; a 1-cycle texture rectangle samples exactly its command's tile. Fix: `gfx_lod_tile_offset` returns `i` while a texture rectangle is drawing outside 2-cycle mode (`s_in_texrect`, set around `gfx_draw_rectangle` in the texrect + wide-texrect paths). 3D triangle paths (D107/D236) are unchanged.
**Cause 2 (frozen animation):** the image is regenerated in place in the per-frame dynamic pool, at the same address every step, and reached via `OS_K0_TO_PHYSICAL` → the KSEG0 mirror (`0x800a8f20`). The GL texture cache keys non-CI textures on address only, so after the first import every later step was a stale hit. Fix: textures in the dyn pool (`[g_VtxBuffers[0], g_VtxBuffers[2])`, with the 0x80000000 mirror normalised to V1) get an FNV-1a content hash in the key. A/B with `GE_DYNTEXHASH_OFF=1`: blood coverage stays at 1% (frozen); with it, the drip grows.
**Verified:** headless boot capture — the blood now runs down from the top over the gun barrel as on N64 (`scratch/blood_after.png`). Golden gate 3/3. User visual check owed.
**Status: FIXED (M-201, headless-verified 2026-09-26).**
+4
View File
@@ -915,6 +915,10 @@ through a converter or a runtime bswap fixup reads scrambled.
(ginit.s is missing from this repo; the expansion lives there), not UV,
shade, or the combiner.
## D14. LOD tile selection is a 2-cycle-only RDP feature; stale `G_TL_LOD` must not affect 1-cycle texture rectangles (D341)
`G_TL_LOD` is sticky other-mode state. Game code that draws a 1-cycle texture rectangle after an LOD-textured draw never clears it, because on the RDP it is irrelevant outside 2-cycle mode. fast3d's LOD/detail tile folding (D107/D236) must therefore not run for 1-cycle texrects, or they sample a neighbouring tile's stale declaration. Related: textures the CPU rewrites in place at recurring addresses (dyn pool) need a content-hashed cache key, and remember game code reaches them through the 0x80000000 KSEG0 mirror.
## D12. "Collapse an LOD binding to tile 0" is wrong for a two-texture DETAIL binding (D236)
**M-198 (finding D236 pass 26).** GE's `texLoadFromGdl` resolves a room DL's
+45
View File
@@ -583,6 +583,8 @@ extern "C" int gfx_texture_cache_count(void) {
return (int)gfx_texture_cache.map.size();
}
extern "C" u8 *g_VtxBuffers[3]; /* dyn.c per-frame pool (dynamic-texture cache key) */
static bool gfx_texture_cache_lookup(int i, const TextureCacheKey& key) {
TextureCacheMap::iterator it = gfx_texture_cache.map.find(key);
TextureCacheNode** n = &rendering_state.textures[i];
@@ -1103,6 +1105,34 @@ static void import_texture(int i, int tile, bool importReplacement) {
key = { orig_addr, {}, fmt, siz, palette_index, loaded_texture.size_bytes, 0u };
}
/* Intro blood (M-201): textures the game regenerates IN PLACE in the
* per-frame dynamic pool (dynAllocate, [g_VtxBuffers[0], g_VtxBuffers[2]))
* -- e.g. the gun-barrel / death blood-drip image rebuilt by
* die_blood_image_routine -- reuse the same addresses frame after frame,
* so an address-only key returns a stale GL texture from an earlier frame.
* Key those on a content hash (FNV-1a) instead; static textures keep the
* free address key. GE_DYNTEXHASH_OFF=1 disables for A/B. */
{
static int dynhash_off = -1;
if (dynhash_off < 0) dynhash_off = getenv("GE_DYNTEXHASH_OFF") != NULL;
/* Game code often reaches this memory through OS_K0_TO_PHYSICAL,
* which fast3d resolves into the byte-identical KSEG0 mirror at
* 0x80000000 (port/src/dram.c V2) -- normalise to the V1 view the
* dyn pool pointers use before the range test. */
const uint8_t* v1addr = orig_addr;
if ((uintptr_t)v1addr >= 0x80000000UL && (uintptr_t)v1addr < 0x80800000UL) {
v1addr -= 0x10000000UL;
}
if (!dynhash_off && g_VtxBuffers[0] && v1addr >= g_VtxBuffers[0] && v1addr < g_VtxBuffers[2]) {
uint32_t h = 2166136261u;
const uint32_t n = loaded_texture.size_bytes;
for (uint32_t b = 0; b < n; b++) {
h = (h ^ orig_addr[b]) * 16777619u;
}
key.palette_hash = h ? h : 1u;
}
}
if (gfx_texture_cache_lookup(i, key)) {
return;
}
@@ -2119,7 +2149,18 @@ static void gfx_sp_modify_vertex(uint16_t vtx_idx, uint8_t where, uint32_t val)
v->v = t;
}
/* Intro blood (M-201): set while a texture rectangle is being drawn. */
static bool s_in_texrect = false;
static inline int gfx_lod_tile_offset(const int i) {
/* Intro blood (M-201): a texture rectangle outside 2-cycle mode samples
* exactly the tile its command names -- the RDP only selects LOD tiles in
* 2-cycle mode. The title leaves G_TL_LOD set from earlier draws, and the
* D236 detail-base rule below then sent the 1-cycle gun-barrel blood
* rectangle to a stale tile-1 CI8 declaration (static garbage instead of
* the drip). Scoped to texrects so 3D LOD/detail paths are untouched. */
if (s_in_texrect && (rdp.other_mode_h & (3U << G_MDSFT_CYCLETYPE)) != G_CYC_2CYCLE)
return i;
if (gfx_detail_textures_enabled)
return ((rdp.tex_lod && !rdp.tex_detail) ? 0 : i);
// D107: GE has no true detail textures (gfx_detail_textures_enabled is
@@ -3566,7 +3607,9 @@ static void gfx_dp_texture_rectangle(int32_t ulx, int32_t uly, int32_t lrx, int3
}
rdp.first_tile_index = tile;
s_in_texrect = true;
gfx_draw_rectangle(ulx, uly, lrx, lry);
s_in_texrect = false;
if (saved_tile != tile) {
rdp.textures_changed[0] = true;
rdp.textures_changed[1] = true;
@@ -3612,7 +3655,9 @@ static void gfx_dp_image_rectangle(int32_t tile, int32_t w, int32_t h,
}
rdp.first_tile_index = tile;
s_in_texrect = true;
gfx_draw_rectangle(ulx, uly, lrx, lry);
s_in_texrect = false;
if (saved_tile != tile) {
rdp.textures_changed[0] = true;
rdp.textures_changed[1] = true;