diff --git a/docs/HANDOFF-LOCAL-AGENT.md b/docs/HANDOFF-LOCAL-AGENT.md new file mode 100644 index 00000000..4da0ca0f --- /dev/null +++ b/docs/HANDOFF-LOCAL-AGENT.md @@ -0,0 +1,74 @@ +# Local-agent handoff tasks (Qwen 27B) + +Two self-contained, low-risk tasks that are **disjoint from the active Jungle +`-level_37` G_MTX investigation** (that work is in `port/fast3d/gfx_pc.cpp` +around `gfx_sp_matrix` / `seg_addr` / `gfx_run_dl` — do NOT touch that file). + +Source: `docs/BACKLOG.md` "Technical cleanup" section. + +--- + +## Task A — doc only, zero code risk (do this first) + +**Goal:** add two entries to `docs/PORT-LEARNINGS.md`. + +1. New bug class **"D24-implications — host-scheduling nondeterminism / fake + priority semantics"**: the pthread kernel does not enforce N64's 0–31 + thread priorities (`osYieldThread` == `Sleep(0)`), so thread interleavings + impossible on real hardware can occur, and frame timing carries host + jitter. **How to apply:** when a flaky timing bug appears (especially + Phase 3 audio underruns), reach first for host thread priorities + (`SetThreadPriority`: scheduler thread time-critical, tick thread normal) + and the deferred `GE_DETERM` fixed-tick mode (§F D117) — NOT for kernel + changes. Link `[[d117]]`-style to the D117 finding. +2. Watch item under the same class: **`osYieldThread` == `Sleep(0)`** is the + one place the port fakes cooperative scheduling. If a level-sweep hot loop + misbehaves under host load, inspect this shim first. Keep as-is until then. + +Put both in section **E. Process / method notes** (or a new subsection if +that reads better). Match the file's existing terse style. No code changes. + +**Verify:** file still renders as markdown; `git diff` is docs-only. + +**On expiry:** commit whatever is written; partial is fine. + +--- + +## Task B — mechanical dead-code removal, single file + +**Goal:** remove the closed-out **D63** debug scaffolding from +**`port/src/libultra.c` ONLY** (leave `port/fast3d/gfx_pc.cpp`'s D63 code +alone — that file is in use by the Jungle work; a separate cleanup later). + +**FILES YOU MAY TOUCH:** `port/src/libultra.c` only. + +D63 is a closed finding; this code is dead weight in the hottest function in +the port. Remove, in `port/src/libultra.c`: + +- the helper block around lines ~490–610: `d63WatchGunbarrelSlot`, + `d63CheckSlot` (the "log only when the slot value changes" fn), the + `D63Act` struct + `s_d63act` ring buffer + `D63_ACT_N`, `d63On`, `d63Tid`, + `d63Act`, the watchdog thread fn + `d63WatchdogStart`, and the + `D63 clobber-watch` block. +- every call site marked `/* TEMP D63 */`: `d63WatchdogStart()` (~261), + `d63Act("send")` + `d63WatchGunbarrelSlot()` (~617–618), + `d63Act("recv")` + `d63WatchGunbarrelSlot()` (~658–659), + `d63Act(...)` (~1110), and the two `getenv("GE_D63")` gfx-task logging + blocks (~1117–1147). + +Grep `D63` and `d63` in the file afterwards — should be zero hits. + +**CONSTRAINTS:** no behavior change; deletion only; do not touch any other +file; do not remove non-D63 code even if it looks unused. + +**BUDGET:** <= 3 build cycles. `export PATH="/c/msys64/mingw64/bin:$PATH" && +./build-pc.sh ntsc-final` must stay green (240/240 link). + +**Verify:** build green; `grep -in d63 port/src/libultra.c` empty; +`./build-pc/ge007.x86_64.exe -level_09` still boots to frames (no crash in +first ~15 s). + +**On expiry:** `git checkout port/src/libultra.c` (revert) and write up which +symbols were still entangled. + +**REPORT:** list of removed symbols + line count delta + build result. diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 478d5d14..21a3027b 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -128,19 +128,52 @@ Full workstream breakdown (WS1–WS6) in `docs/PLAN-linear-level-sweep.md`. §F/§H **D130**; PORT-LEARNINGS §A. Verified: -level_34 0/~14, -level_35 0/4; -level_09/20/24 unregressed. -## Next task — Jungle `-level_37` (C2m, the last crash) +## Done this session (M-19) — D131 (Jungle), D63 scaffolding removal -Jungle renders **~300 frames** then faults in `gfx_sp_matrix` -(`gfx_pc.cpp:1046`, fault `0x401c68e0`) — an explosion / particle DL -`G_MTX` with a bad matrix pointer. D75 / D124-Jungle matrix-family -territory (compiled/global DL, NOT the per-level model-GDL path — that -premise is dead, see D130). `docs/BRIEF-C2gdl-model-reloc.md` is now -mostly obsolete. Likely near `port/src/gimgfixup.c` (D124-Jungle) / -compiled `globalDL_0xNNN` matrix refs, or fast3d's `seg_addr` for a -`G_MTX` w1. Then all 21 → hand `docs/LEVEL-PLAYTEST.md` to the user (WS6). +- **D131** (`port/fast3d/gfx_pc.cpp`) — Jungle `-level_37` C2m crash. + `explosionRenderPropSmoke` passes a compiled `.bss` matrix symbol + (`&dword_CODE_bss_8007A100`) through `osVirtualToPhysical()` — a + `u32`-returning shim — into `gSPMatrix`, truncating the `0x1_00000000` + module high word → w1 = `0x401c68e0` → wild deref in `gfx_sp_matrix` + when the first explosion draws (~frame 300). NOT `gimgfixup` / the + model-GDL path (that premise was already dead per D130). Fix: `seg_addr` + restores the module high word for a fallthrough w1 in + `[0x40000000, 0x70000000)`. Covers ~30 latent `osVirtualToPhysical()` sites (explosion/glass/blood/bondview2). Verified: + Jungle renders to frame 1500–2400+ crash-free; `-level_20`/`-level_24` + unregressed. §F/§H **D131**, PORT-LEARNINGS §A. +- **D63 scaffolding removed** — dead TEMP D63 probe calls stripped from + `port/src/libultra.c` (watchdog thread + activity ring + slot watchers) + and the `#ifdef PORT` probe blocks in `blood_animation.c`, `dyn.c`, + `front.c`, `rsp.c`, `title.c`. All inert (env-gated / no side effects); + build links clean. `port/fast3d/gfx_pc.cpp` D63 trail code left for a + later pass. Bug class D24-implications + `osYieldThread` watch item added + to PORT-LEARNINGS §E. -Everything else: hand `docs/LEVEL-PLAYTEST.md` to the user for the WS6 -completion pass (real input, per-level objective checklist). +## Next task — two pre-existing regressions vs the M-18 handoff + +Both reproduce on a **clean checkout of master `0a4b3bae`** (proven by +`git stash` + rebuild), so they predate M-19's changes — a fast3d-only +edit cannot cause a pre-first-frame boot crash. + +1. **`-level_09` (BUNKER1) crashes at boot** — `frames=0`, PC + `0x1400066fc`, fault `0x00a2fc68` (= stack ptr `0x0fa2fc68` with the top + nibble masked → a 24/28-bit segment mask hitting a real pointer). The + M-18 handoff's "`-level_09`/20/24 unregressed" is now false for 09. + **Start here:** `git bisect` D125 (`a06eb364`) → HEAD against + `./build-pc/ge007.x86_64.exe -level_9` (`frames=0` + crash = bad). + Suspect D126 (objective sub-record relayout) or D128 (portal stride). +2. **Intro renders mostly black** — legal/logo frames are 0–6% non-clear + on clean master (`GE_PCDUMP="1-600:60"` + `pixcount.py`). The M-18 + handoff claims "the entire intro renders". Check `framediff.py` vs + `tools_pc/golden/`. +3. **Silo intro fly-down cinematic hang** (user report M-19) — + intermittent; one `-level_20` run went 300 frames clean. May be the + same class as #2 or D117 nondeterminism under load. + +After those: all 21 load+render → hand `docs/LEVEL-PLAYTEST.md` to the +user for the WS6 completion pass (real input, per-level objective +checklist). **Regen gotcha:** `d69_emit.py` rewrites `data/pccg-/pccg.bin` from scratch with only the 52 bg/stan rows — you MUST follow it with diff --git a/docs/LEVEL-STATUS.md b/docs/LEVEL-STATUS.md index 07d3eab1..6808a8df 100644 --- a/docs/LEVEL-STATUS.md +++ b/docs/LEVEL-STATUS.md @@ -12,7 +12,7 @@ Build = `f2beae4b` + `tools_pc/d88_propdefs.py` (D123) + `port/src/gimgfixup.c` | Level | # | Status | Crash site | Class | |---|---|---|---|---| -| Bunker1 | 09 | **PASS** 91.7% | — | — | +| Bunker1 | 09 | **CRASH (regressed since M-18)** | boot crash `frames=0`, PC `0x1400066fc`, fault `0x00a2fc68` (stack ptr, top nibble masked). Reproduces on clean master `0a4b3bae` → pre-existing, not D131. Bisect D125→HEAD. | **new** | | Silo | 20 | **PASS** 91.7% | — | — | | Archives | 24 | **PASS** 91.1% | — | — | | Train | 25 | **PASS** 87.0% | — | — | @@ -26,7 +26,7 @@ Build = `f2beae4b` + `tools_pc/d88_propdefs.py` (D123) + `port/src/gimgfixup.c` | Cradle | 41 | **PASS** 55.4% (low — partial render, no crash) | — (C1 fixed, D123) | — | | Runway | 35 | **PASS** | — (D130: `romdataFixupFont` glyph-relayout aliasing) | — | | Facility | 34 | **PASS** | — (D130: `romdataFixupFont` corrupted glyph `#`/`"` pixeldata → fast3d AV) | — | -| Jungle | 37 | CRASH | `gfx_sp_matrix` gfx_pc.cpp:1046 (fault 0x401c68e0) — renders ~300 frames then explosion-DL `G_MTX` (D75/matrix family); separate from D130 | **C2m** | +| Jungle | 37 | **PASS** 91.7% (frame 1500–2400+ crash-free) | — (D131: `osVirtualToPhysical()` truncated a compiled `.bss` matrix ptr in `explosionRenderPropSmoke` → `seg_addr` now restores the module high word) | — | | Aztec | 28 | **PASS** 90.8% | — (C3 fixed, D125) | — | | Bunker2 | 27 | **PASS** 91.5% | — (C3r fixed, D126) | — | | Depot | 30 | **PASS** 79.8% | — (C4 fixed, D126) | — | @@ -34,7 +34,12 @@ Build = `f2beae4b` + `tools_pc/d88_propdefs.py` (D123) + `port/src/gimgfixup.c` | Surface2 | 43 | **PASS** 70.4% | — (C6 fixed, D126) | — | | Surface1 | 36 | **PASS** 77.5% | — (C7 guarded, D127) | — | -**20 / 21 PASS** (M-18). D130 cleared C2 Facility + Runway — the crash was +**19 / 21** (M-19). D131 cleared C2m Jungle (explosion-DL `G_MTX` truncated +compiled-symbol pointer). **But `-level_09` (Bunker1) regressed to a boot +crash sometime between M-18 and now** — reproduces on clean master, so it +is not an M-19 change; bisect D125→HEAD. Net 20→19 until 09 is refixed. + +**(M-18) 20 / 21 PASS.** D130 cleared C2 Facility + Runway — the crash was `romdataFixupFont` corrupting fontchar glyphs 0/1/2 (in-place 24→32B relayout aliases for low indices), NOT the model-GDL relocation the D124-Facility addendum suspected. Only Jungle (C2m, explosion-DL `G_MTX`, ~frame 300) diff --git a/docs/PCPortResearch.md b/docs/PCPortResearch.md index f7427760..8f193399 100644 --- a/docs/PCPortResearch.md +++ b/docs/PCPortResearch.md @@ -821,6 +821,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D125 | crash classes C3+C6 (Aztec/Bunker2/Surface2): **root cause found (M-16)** — `tools_pc/d88_emit.py:374` assigns a 4-byte literal to an 8-byte slice (`out[dst_o+0x30:dst_o+0x38] = b"\x00…"`, the stan zero-fill in `emit_pad`); every pad/boundpad record silently shrinks the output bytearray by 4 B, and once the tail falls below the boundpad plink string blob, later verbatim leaf writes hit Python out-of-range slice semantics (insert at current end, not relocated offset) → boundpad names drift/truncate (Aztec pad33 `p138d2`→`8d2`) → `stanPackId()` reject → `getposstan()` NULL stan → `setupDoor` leaves door `model=NULL` → crash `propobj.c:13601`. M-14's "propDefs zeros in RAM" was a misread; sidecar propdefs were always correct (M-15) and post-load RAM matches the sidecar byte-for-byte (M-16) | **resolved (M-16b)** — line 374 → 8 NULs; all 21 sidecars regen'd; **Aztec `-level_28` now PASSES** (was C3 CRASH). Bunker2 falls through to a separate DOOR-tail `linkedDoor` layout bug (C3 residual) | | D126 | crash classes C3r/C4/C6 (Bunker2 `-level_27` `door7F054FB4` propobj.c:13523, Depot `-level_30` prop.c:902, Surface2 `-level_43` loadobjectmodel.c:393): the objective sub-records `criteria_picture` (30), `criteria_roomentered` (32), `criteria_deposit` (33), `setup_objective_text` (35) each end in a `T *next` list pointer that the setup walk (`set_parent_cur_obj_*` / `setup_briefing_text_entry_parent`) writes unconditionally. On PC that pointer widens 4→8B and lands 8-aligned at offset 16 → struct is 24B/6w (N64 16/20). `d88_propdefs.py` emitted them at N64 size via the generic arm → the runtime 8-byte `->next` write clobbered the *next* record's header → propdef walk desynced, command indices drifted ~100, `linkedDoorOffset + arg2` resolved to the wrong record → door `linkedDoor` chain walked into garbage. | resolved — `d88_propdefs.py` PROPDEF_PC_BYTES[30/32/33/35]=24 + typed handler; `loadobjectmodel.c` sizepropdef PORT returns 6. Bunker2/Depot/Surface2 now PASS (13→16/21) | | D124 | crash class C2: fast3d bad texture pointer. **Jungle** (`0xabcd0824`): `gimgSyncCompiledGlobalDLs()` slot-detect keyed on the post-fixup marker, which `texLoad()` had already overwritten → compiled `globalDL_0xNNN` explosion DLs kept link-time `IMAGESEG` words → latent on every level, tripped by the first explosion-DL draw. **Facility** (`0x72181ee8`): see **D130** — the model-GDL-relocation hypothesis (M-14 addendum) was WRONG; real cause was `romdataFixupFont` | Jungle fixed (`port/src/gimgfixup.c`); Facility → D130 | +| D131 | crash class C2m (Jungle `-level_37`): fast3d `gfx_sp_matrix` AV on a wild matrix pointer `0x401c68e0` (`gfx_pc.cpp:1046`) ~frame 300, when the first explosion/smoke prop renders. `explosionRenderPropSmoke` builds `gSPMatrix(gdl++, osVirtualToPhysical((void*)&dword_CODE_bss_8007A100), …MODELVIEW)` (+ `applyRoomMatrixToDisplayList`). `osVirtualToPhysical()` is a `u32`-returning shim (`libultra.c:1207`) → truncates the compiled `.bss` symbol's `0x1_00000000` module high word → w1 = `0x40xxxxxx` → `seg_addr()` returns it raw. Same class as D94 (chraction 32-bit ptr truncation), but in a GBI DL word. The projection matrix in the same DL is fine (`get_BONDdata_field_10E0()` is a runtime `0x70xxxxxx` ptr, fits u32); the `gSPDisplayList(&globalDL_0xNNN)` refs are fine (port `Gwords.w1` is 64-bit `uintptr_t`, no macro truncation). ~30 `osVirtualToPhysical()` sites exist (`explosion.c`, `glass*.c`, `blood_animation.c`, `bondview2.c`) — all latent until that effect first draws. | resolved — `seg_addr()` restores the module high word for any fallthrough w1 in `[0x40000000, 0x70000000)` (module fixed-based at `0x140000000`, no ASLR; DRAM/KSEG0/segmented/phys all handled earlier). Jungle renders to frame 2400+ clean; `-level_20`/`-level_24` unregressed. **18→19/21.** (`-level_09` has a separate pre-existing boot crash, see below.) | | D130 | crash class C2 (Facility `-level_34`, Runway `-level_35`): `import_texture_i8` AV on a wild texture pointer (`0x72181ee8`), baked into the HUD glyph DL by `gDPLoadTextureBlock(gdl, curchar->pixeldata, …)` when the level-title string ("Chemical Warfare Facility #2") renders `#`/`"`. Root cause: `romdataFixupFont` (`port/src/romdata.c`) converts the N64 24B fontchar array to the PC 32B array **in place**, forward-field, backward-glyph. PC−N64 stride = 8, so for glyphs 0/1/2 `d` overlaps `s` by less than the 24B read span and an early field write clobbers a later field's source mid-`for k<5` loop → glyph 1's `width` = `bswap(index)` = `0x01000000`, glyph 2's `pixeldata` = `pcPixOff+(index−pixStart)` ≈ `0x020002e8` → `+= font_base` → wild → fast3d AV. On N64 the array is read-only rodata, never re-laid-out, so no bug there. NOT the model-GDL relocation (`sub_GAME_7F0762E0` / `texLoadFromGdl`) the D124-Facility addendum suspected — `gdl` there is still a segmented `0x05xxxxxx` value so the `& 0x00ffffff` masks are correct, and `texLoadFromGdl` never copies a `G_SETTIMG` for these levels. | resolved — stage all 6 N64 fields in a local `f[6]` before writing any. Facility + Runway now PASS (16→18/21). Probes reverted. | Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS @@ -4390,6 +4391,63 @@ unregressed. Sweep 13 → 16/21. Generalisable quirk appended to align), C2m Jungle (`G_MTX`), C5 Control (BG portal), C7 Surface1 (`sndSetupSound`). +**D131 (session M-19) — Jungle `-level_37` C2m crash: `osVirtualToPhysical()` +truncates a compiled-symbol pointer inside a GBI matrix command.** Jungle +renders ~300 frames then AVs in `gfx_sp_matrix` (`gfx_pc.cpp:1046`, +`int32_t int_part = addr[…]`) with `addr = 0x401c68e0`. Caller is the `G_MTX` +case of `gfx_run_dl` → `seg_addr(cmd->words.w1)`. + +Probe (`GE_MTXPROBE`, reverted) dumped the faulting DL — a well-formed model +render sequence in a per-frame DRAM GDL: `gSPMatrix(PROJECTION,LOAD)` w1 = +`0x700d8070` (valid), `gSPMatrix(MODELVIEW,LOAD)` w1 = `0x401acbe0` (bad), +`gSPMatrix(MODELVIEW,MUL)` w1 = `0x401c68e0` (bad, the fault), `gSPVertex` +w1 = `0x700da350` (valid). All lens `0x40`, opcodes sane — the walk is *not* +desynced, only the modelview-matrix pointers are wrong. + +`0x401c68e0` == `(u32)(0x1_401c68e0)` — a compiled-module address with the +`0x1_00000000` high word dropped (module fixed-based at `0x140000000`, cf. +D94 `chraction.c:1243` comment). Source: `explosionRenderPropSmoke` +(`explosion.c:1501`) does +`gSPMatrix(gdl++, osVirtualToPhysical((void*)&dword_CODE_bss_8007A100), +G_MTX_NOPUSH|G_MTX_MUL|G_MTX_MODELVIEW)` — `&dword_CODE_bss_8007A100` is a +compiled `.bss` matrix; `osVirtualToPhysical()` is `(u32)(uintptr_t)va` +(`libultra.c:1207`), so the store truncates. The `[-1]` LOAD matrix is the +room matrix from `applyRoomMatrixToDisplayList` (same truncation path). +Why the projection matrix is fine: `get_BONDdata_field_10E0()` returns a +runtime `0x70xxxxxx` pointer that survives the `u32` cast. Why +`gSPDisplayList(&globalDL_0xNNN)` is fine: the port's `Gwords.w1` is a +64-bit `uintptr_t` (`include/PR/gbi.h:1730`) and `gDma1p` stores the full +pointer — only `osVirtualToPhysical` truncates. + +~30 sites pass a compiled matrix/vtx symbol through `osVirtualToPhysical` +(`explosion.c`, `glass.c`/`glass2.c`, `blood_animation.c`, `bondview2.c`) — +each latent until that effect first draws, which is why only Jungle (an +early scripted explosion) tripped it in the no-input sweep. + +**Fix** (`port/fast3d/gfx_pc.cpp` `seg_addr`): at the final fallthrough, +if `w1 ∈ [0x40000000, 0x70000000)` restore the module high word +(`(mod_hi | w1)`, `mod_hi` read from this TU's own load address). Everything +legit that reaches the fallthrough is either DRAM V1 (`≥0x70000000`), KSEG0 +(`≥0x80000000`), an LSB-set / low-nibble segmented address, or a +sub-`0x800000` physical offset — all handled in earlier branches — so the +range is unambiguous. Narrow, ABI/pointer-width class (same as D94/D3x). +Verified: Jungle renders to logic-frame 2400+ crash-free at 91.7% non-clear; +`-level_20` (Silo) + `-level_24` (Archives) unregressed. **Sweep 18→19/21.** + +Not fixed this session: **`-level_09` (BUNKER1) now crashes at boot** +(`frames=0`, PC `0x1400066fc`, fault `0x00a2fc68` — a stack pointer +`0x0fa2fc68` with the top nibble masked, i.e. another 28/24-bit segment-mask +applied to a real pointer). Reproduced on a **clean `git stash` checkout of +master `0a4b3bae`** → pre-existing regression, not caused by D131 (a +fast3d-only change cannot affect a pre-first-frame boot crash). Contradicts +the M-18 handoff's "`-level_09` unregressed" claim; likely fallout from a +D126/D128 propdef/portal change or an environmental sidecar/ROM mismatch. +Next session: `git bisect` D125→HEAD against `-level_09`. Silo also shows an +intermittent hang in the intro fly-down-to-Bond cinematic (user report; +not always reproducible — ran 300 frames clean here). + +--- + **D130 (session M-18) — Facility + Runway C2 crash was `romdataFixupFont`, NOT the model-GDL relocation.** `-level_34` (and `-level_35`) fault in `import_texture_i8` (`gfx_pc.cpp:821`) on `loaded_texture.addr = 0x72181ee8`, diff --git a/docs/PORT-LEARNINGS.md b/docs/PORT-LEARNINGS.md index 8f0cf1bd..4619e0a0 100644 --- a/docs/PORT-LEARNINGS.md +++ b/docs/PORT-LEARNINGS.md @@ -62,6 +62,22 @@ state. a level actually feeds a control byte to the HUD text (none do yet; D130 was a font-relayout bug, not this). +- **D131 corollary — `osVirtualToPhysical()` truncates a compiled-symbol + pointer to 32 bits.** The port shim is `(u32)(uintptr_t)va` (`libultra.c`). + For a runtime DRAM pointer (`0x70xxxxxx`) the cast is lossless; for a + **compiled module symbol** (`.bss`/`.rodata` matrix, e.g. + `&dword_CODE_bss_8007A100` in `explosionRenderPropSmoke`) it drops the + `0x1_00000000` module high word, so the GBI w1 becomes `0x40xxxxxx` and + `seg_addr()` hands fast3d a wild pointer → AV in `gfx_sp_matrix` / + `gfx_sp_vertex`. Same class as D94 (`chraction.c:1243`), but in a DL word. + ~30 latent sites (`grep 'osVirtualToPhysical(' src/game/{explosion,glass,glass2,blood_animation,bondview2}.c`), + each armed only when that effect first draws. Fixed once in `seg_addr()`: + restore the high word for a fallthrough `w1 ∈ [0x40000000, 0x70000000)` + (module is fixed-based at `0x140000000`; DRAM/KSEG0/segmented/phys are all + handled in earlier branches). `gSPDisplayList(&globalDL_0xNNN)` is NOT + affected — the port's `Gwords.w1` is 64-bit and `gDma1p` stores the full + pointer; only `osVirtualToPhysical` truncates. + ## B. 16-byte PC `Gfx` / `Vtx` vs 8-byte N64 Any buffer reservation, `memcpy` size, slot stride, or pool budget @@ -214,3 +230,13 @@ deprioritised below level-progression / crash work. See fixed-tick mode was assessed not-narrow (redesigns retrace/tick semantics); design is in §F D117 if someone picks it up, after which `framediff.py --exact` becomes usable. +- **D24-implications — host-scheduling nondeterminism / fake priority + semantics:** the pthread kernel does not enforce N64's 0–31 priorities + (`osYieldThread` = `Sleep(0)`), so interleavings impossible on console can + occur, and frame timing has host jitter. When a flaky timing bug appears + (especially Phase 3 audio underruns), reach first for host thread + priorities (`SetThreadPriority`: scheduler thread time-critical, tick + normal) and the deferred `GE_DETERM` mode (D117) — not for kernel changes. +- **Watch item: `osYieldThread` = `Sleep(0)`.** The one place we fake + cooperative behavior. If a level-sweep hot loop misbehaves under host load, + this shim is the first thing to inspect. Keep as-is until then. diff --git a/port/fast3d/gfx_pc.cpp b/port/fast3d/gfx_pc.cpp index 77cd9ada..54dd3fd9 100644 --- a/port/fast3d/gfx_pc.cpp +++ b/port/fast3d/gfx_pc.cpp @@ -2405,6 +2405,23 @@ static inline void *seg_addr(uintptr_t w1) { if (w1 < 0x800000) { return (void *)(w1 + 0x80000000); } + // D131: a GBI DL built by game code can reference a COMPILED symbol via + // osVirtualToPhysical() (a u32-returning shim), which truncates the + // module's 0x1_00000000 high word. Seen in explosionRenderPropSmoke: + // gSPMatrix(gdl++, osVirtualToPhysical((void*)&dword_CODE_bss_8007A100), + // ..MODELVIEW) -> w1 == 0x40xxxxxx -> wild deref in gfx_sp_matrix. The + // module is based at 0x140000000 (fixed, no ASLR), and nothing legit + // reaches this fallthrough with a value in [0x40000000, DRAM_V1): DRAM + // (>=0x70000000), KSEG0 (>=0x80000000), segmented addrs and sub-0x800000 + // physical offsets are all handled above. Restore the high word from + // this TU's own load address. + { + static const uintptr_t mod_hi = + ((uintptr_t)(void *)&segmentPointers[0]) & 0xffffffff00000000ULL; + if (mod_hi && w1 >= 0x40000000 && w1 < 0x70000000) { + return (void *)(mod_hi | w1); + } + } return (void *)w1; }