diff --git a/docs/dev/findings-index.csv b/docs/dev/findings-index.csv index 26a6e0a2..7297a7e1 100644 --- a/docs/dev/findings-index.csv +++ b/docs/dev/findings-index.csv @@ -167,7 +167,7 @@ D286,OPEN,"User-confirmed real ~8s frame freeze on Caverns, Steam Deck —","OPE D287,OPEN,"User-confirmed real, non-self-recovering freeze on Windows (M-141, 2026-09-15/16) after ~15-20 minutes with no input, triggered by clicking back into the… —","OPEN — one confirmed occurrence, real (not self-recovering), not root-caused; gfxFrameMsgQ full is a lead, not a confirmed cause." D288,OPEN,"Silo intro: a huge, flat-shaded, untextured brown/tan triangle sweeps across the frame during the camera pan down the missile shaft (M-141, user report —… —",OPEN — does NOT reproduce on the current build (2026-09-23: D289,OPEN,"Dam: a crash in the computer room, not reproducible on a second attempt (M-141, Steam Deck user playtest).","OPEN — needs a human re-test specifically on a build with the D255 fix (this session's release/v0.3.0, not any earlier build) to see if it still happens.…" -D290,OPEN,"Watch's in-game ""controller"" section shows no control-pad graphic (M-141, Steam Deck user playtest).","OPEN — observed, not investigated." +D290,FIXED,"Watch's in-game ""controller"" section shows no control-pad graphic (M-141, Steam Deck user playtest).","FIXED (2026-09-23): D264-pattern template read in watchRenderController (flags read 0), plus 3 pointer-width/raw-Gfx sites on the same path; controller model verified rendering on Windows." 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.…" diff --git a/docs/dev/findings.md b/docs/dev/findings.md index 184600ff..b0db4b93 100644 --- a/docs/dev/findings.md +++ b/docs/dev/findings.md @@ -575,7 +575,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D287 | **User-confirmed real, non-self-recovering freeze on Windows (M-141, 2026-09-15/16) after ~15-20 minutes with no input, triggered by clicking back into the…** — full `## D287` entry at file tail | OPEN — one confirmed occurrence, real (not self-recovering), not root-caused; `gfxFrameMsgQ` full is a lead, not a confirmed cause. | | D288 | **Silo intro: a huge, flat-shaded, untextured brown/tan triangle sweeps across the frame during the camera pan down the missile shaft (M-141, user report —…** — full `## D288` entry at file tail | OPEN — does NOT reproduce on the current build (2026-09-23: dense Silo-intro capture + colour scan; D271 A/B ruled out as the fixer). Close if the Silo playtest does not see it. | | D289 | **Dam: a crash in the computer room, not reproducible on a second attempt (M-141, Steam Deck user playtest).** — full `## D289` entry at file tail | OPEN — needs a human re-test specifically on a build with the D255 fix (this session's `release/v0.3.0`, not any earlier build) to see if it still happens.… | -| D290 | **Watch's in-game "controller" section shows no control-pad graphic (M-141, Steam Deck user playtest).** — full `## D290` entry at file tail | OPEN — observed, not investigated. Needs a live repro (open the watch, navigate to the controller-config page) plus a screenshot for comparison against N64 reference. | +| D290 | **Watch's in-game "controller" section shows no control-pad graphic (M-141, Steam Deck user playtest).** — full `## D290` entry at file tail | FIXED (2026-09-23): D264-pattern template read in `watchRenderController` (flags read 0), plus 3 pointer-width/raw-Gfx sites on the same path; controller model verified rendering on Windows. | | 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.… | @@ -11748,12 +11748,16 @@ Superseded history below (the original OPEN investigation, root cause not found **Status (from §F table):** OPEN — M-152 ruled out the entire `src/game/` decomp side (item ID, resource table, equip pipeline, gating) with high confidence via static trace; root cause not found, best-narrowed hypothesis points at a fast3d translucent-blend edge case, needs a live repro with `GE_PCDUMP` at both watch fade states to confirm/refute (see falsifier above). +**FIXED (2026-09-23, v0.4.0): ABI/layout class, the D264 pattern on a second call site.** Headless repro: `-level_09`, `GE_INPUTSCRIPT="1000:START;1400:RIGHT;1600:RIGHT"` (watch → Inventory → Control Options), `GE_PCDUMP` through the page. Labels drew and the model didn't, in **both** fade states (frames 1650–1700 fading, 1725+ settled), which falsifies M-152's blend-mode hypothesis. Cause: `watchRenderController` (`gunfire.c`) builds its `ModelRenderData` from `*(ModelRenderData *)((u8 *)D_80035D04 + 0x3c)`. On N64 that 64 bytes is D_80035D04's last word (0) followed by `watchControllerButtonBases` `{1, 3, 0…}` (gun.c): the same template D264 documents (basemtx NULL, zbufferenabled TRUE, flags 3). On PC `basemtx` is 8 bytes and the globals aren't at those offsets, so `flags` read 0 and `subdraw()` gated off every node. D264 fixed only `set_enviro_fog_for_items_in_solo_watch_menu`. **Fix (`#ifdef PORT`, same explicit template as D264).** Three more pointer-width/Gfx sites on the same path were fixed because the model now draws: both `matrix_4x4_copy((u32)modelstack.render_pos + i*sizeof(Mtxf), …)` loops (u32 pointer truncation; harmless on this Windows build because the pool sits below 4 GB, fatal under Linux PIE), `watchRenderControllerOpaque(…, (s32) watchTable, …)` in `options.c` (truncated stack pointer), and the dual-controller branch's hand-encoded N64 `G_MTX` word `0x01030040` (B class; now `gSPMatrix`, as the decomp comment says it should be). **Verified:** native Windows capture shows the N64 pad model and the floating button icons (`scratch/d290_after.png`); bunker1 golden 3/3; Windows and Linux builds clean. The dual-controller branch (2.x control styles with a second pad) was not exercised at runtime. + ## D291 — Dam: texture glitches when the truck drives past (M-141, Steam Deck user playtest). **Dam: texture glitches when the truck drives past (M-141, Steam Deck user playtest).** No further detail captured (which surface/texture, timing, one-shot vs. every pass). Could be a moving-vehicle-specific instance of a known texture-cache/TMEM-residency class (cf. D124's stale-DL-marker mechanism, a different object family) or something new. **Status (from §F table):** OPEN — observed, not investigated. Needs a live repro with the truck's exact position/timing and, ideally, a screenshot. +**v0.4.0 check (2026-09-23): not reachable headlessly.** Bare `-level_33` capture (300–3100 every 70 frames): the truck is parked in the intro flyby (frames 580–720, textures correct) and never drives through the spawn view; it is set off by the player moving toward the checkpoint. Needs the playtest brief item (Dam, watch the truck pass, F12 if glitched). + ## D292 — Death animations are glitched — Bond is often sunk halfway into the floor (M-141, Steam Deck user playtest). **Death animations are glitched — Bond is often sunk halfway into the floor (M-141, Steam Deck user playtest).** **User confirms this is the same broader "Bond's world model is positioned wrong" problem as D173** — not a new/separate mechanism, another surface of the same known-but-under-investigated bug. The umbrella is already tracked: `GRAPHICS-BACKLOG.md` D148 is explicitly "the tracking entry for the whole cutscene-reliability problem," already lists D173 (floating at level start / Cuba credits) and a Cradle instance ("Bond spins in circles" during the Trevelyan-falls cutscene) under it — findings.md's D173 entry has the fullest investigation detail (M-106's two ruled-out D209-style struct-aliasing candidates, LOW-MEDIUM confidence that class is even the right one). Nothing has actually been lost — the write-up just isn't concentrated in one place (split across `GRAPHICS-BACKLOG.md` D148/D173 and `findings.md` D173), which reads as "known but not detailed" unless you go looking. Sunk-into-floor is a third concrete instance of the family (level-start floating, cutscene spin, now death-pose sinking) — worth keeping as one thread when it's next investigated live, since a single root cause (most likely the render-side puppet/joint transform, not collision) could explain all three. diff --git a/docs/porting-notes.md b/docs/porting-notes.md index 3c24d07e..630f438d 100644 --- a/docs/porting-notes.md +++ b/docs/porting-notes.md @@ -251,6 +251,12 @@ framing — it never looked like game logic to begin with. decompilation of what the write side already does, `#ifdef PORT` with the N64 hand-rolled offset kept verbatim under `#else`. §F **D248**. +- **One layout bug often has sibling call sites.** D264 fixed a + cross-global `ModelRenderData` template read in the watch item preview; + the identical read in `watchRenderController` (`D_80035D04 + 0x3c`) stayed + broken until D290. When fixing a raw-offset global read, grep for every + other read of the same globals (`D_80035D00`/`D_80035D04` here) before closing. + ## B. 16-byte PC `Gfx` / `Vtx` vs 8-byte N64 Any buffer reservation, `memcpy` size, slot stride, or pool budget diff --git a/src/game/gunfire.c b/src/game/gunfire.c index 8b0eb4d9..026cee77 100644 --- a/src/game/gunfire.c +++ b/src/game/gunfire.c @@ -2029,7 +2029,21 @@ Gfx* watchRenderController(Gfx* gdl, Mtxf* basemtx, s32 envcolour, bool animateb struct coord3d coord_node12_pos; struct coord3d coord_node12_base; +#ifdef PORT + /* D290 (ABI/layout class; same pattern as D264 above): on N64 this + * 64-byte copy starts at the last word of D_80035D04 (0) and runs into + * watchControllerButtonBases {1, 3, 0..} (gun.c), i.e. the same + * ModelRenderData template as D264: basemtx=NULL, zbufferenabled=TRUE, + * flags=3, rest zero. On PC the struct is wider (8-byte pointers) and the + * globals are not laid out at those byte offsets, so flags read 0 and + * subdraw() skipped every node: the watch control-options page showed + * its labels but no controller model. Use the explicit template. */ + renderdata = (ModelRenderData){0}; + renderdata.zbufferenabled = TRUE; + renderdata.flags = 3; +#else renderdata = *(ModelRenderData *)((u8 *)D_80035D04 + 0x3c); +#endif sub_GAME_7F05DA8C(GUNRIGHT, 0x55); @@ -2091,7 +2105,13 @@ Gfx* watchRenderController(Gfx* gdl, Mtxf* basemtx, s32 envcolour, bool animateb for (i = 0; i < objheader->numMatrices; i++) { +#ifdef PORT + /* D290: N64 does this pointer math through a u32 cast, which truncates a + * 64-bit pointer (fatal once the pool sits above 4 GB, e.g. Linux PIE). */ + matrix_4x4_copy((Mtxf *)((u8 *)modelstack.render_pos + i * sizeof(Mtxf)), &sp41c); +#else matrix_4x4_copy((u32)modelstack.render_pos + i * sizeof(Mtxf), &sp41c); +#endif matrix_4x4_f32_to_s32(&sp41c, &modelstack.render_pos[i]); } @@ -2326,7 +2346,13 @@ Gfx* watchRenderController(Gfx* gdl, Mtxf* basemtx, s32 envcolour, bool animateb for (i = 0; i < objheader->numMatrices; i++) { +#ifdef PORT + /* D290: N64 does this pointer math through a u32 cast, which truncates a + * 64-bit pointer (fatal once the pool sits above 4 GB, e.g. Linux PIE). */ + matrix_4x4_copy((Mtxf *)((u8 *)modelstack.render_pos + i * sizeof(Mtxf)), &sp41c); +#else matrix_4x4_copy((u32)modelstack.render_pos + i * sizeof(Mtxf), &sp41c); +#endif matrix_4x4_f32_to_s32(&sp41c, &modelstack.render_pos[i]); } diff --git a/src/game/options.c b/src/game/options.c index 28bc3b86..1a8a321e 100644 --- a/src/game/options.c +++ b/src/game/options.c @@ -3420,7 +3420,12 @@ Gfx *draw_watch_controller(Gfx *gdl) } else { +#ifdef PORT + /* D290: the (s32) cast truncates a 64-bit stack pointer. */ + gdl = watchRenderControllerOpaque(gdl, &finalmtx, 1, watchTable, &contpadnum0); +#else gdl = watchRenderControllerOpaque(gdl, &finalmtx, 1, (s32) watchTable, &contpadnum0); +#endif } if (controllerCheckDualControllerTypesAllowed()) @@ -3460,7 +3465,15 @@ Gfx *draw_watch_controller(Gfx *gdl) * * but it doesn't match for some reason. */ +#ifdef PORT + /* D290 (B class): a hand-encoded N64 G_MTX word does not decode as the + * same command in the PC Gfx layout; emit it through the macro (the + * form the comment above says it really is). */ + cmd1 = gdl++; + gSPMatrix(cmd1, osVirtualToPhysical(perspmtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); +#else cmd1 = gdl++; cmd1->words.w0 = 0x01030040; cmd1->words.w1 = osVirtualToPhysical(perspmtx); +#endif gdl = sub_GAME_7F0A6EE8(gdl); green = g_WatchBackgroundGreen;