D236 pass 10: refute "nondeterministic instantiation", it's room occlusion

Per the rule-2-clarification pass-10 brief in findings.md, applied the new
three-bucket taxonomy to D280's leading theory that the 0x0c184b50
discrete-tree-card render-mode class instantiates inconsistently across
runs of the deterministic -level_36 boot (~4 tris vs ~720).

Added a small env-gated diagnostic (GE_D236RM, port/fast3d/gfx_pc.cpp
gfx_sp_tri1) counting that class's per-frame triangle count, heartbeat-
logged every 300 frames even at zero. Three independent -level_36
launches (one with sustained GE_INPUTSCRIPT camera rotation) produced
byte-identical per-frame counts, falsifying the "run-to-run dependent"
premise outright. Cross-referencing with the existing GE_D104 room-census
probe in the same run shows the count drops to zero at the exact frame
the player's curRoom transitions from 12 (17-20 rooms drawn, an outdoor
vista) to 18 (1-5 rooms, an enclosed indoor space) via the boot's own
scripted intro camera — ordinary portal/room occlusion, not a bug. None
of buckets (a)/(b)/(c) apply; no src/game or port/ fix made or warranted.

The original "wall not discrete sprites" symptom is untouched by this
pass — narrowed the real next step to getting a camera into room 12
facing the tree line directly, in docs/dev/findings.md.

Added the generalisable "deterministic boot flag != deterministic camera
path" quirk to porting-notes.md §E. findings-index.csv regenerated.
This commit is contained in:
James Dansereau committed 2026-09-16 17:58:01 -04:00
1 parent 1c60da6f46
commit 23da815dda
5 files changed
+114 -4

No files matched your search

+1
View File
@@ -56,6 +56,7 @@ Two classes:
| `GE_TEXDUMP` | `port/fast3d/gfx_pc.cpp:1020`, `port/fast3d/gfx_opengl.cpp:700` | PPM dump of every uploaded texture + a `fmt/siz/palfmt/palidx/pal[0..3]` line. Root-caused D161 (Depot ceiling). Kept as an inert diagnostic. | **live** (RC3 not finished) |
| `GE_D116` | `port/fast3d/gfx_pc.cpp:1710,2362`, `src/game/textrelated.c:265,541` (cached `ge_d116`) | HUD/text "X-mirror" (D114/D116) vbo/uv trace. **D114/D116 CLOSED — NOT A BUG (M-33/D168): the captures were upside-down, not mirrored.** Probe is now dead scaffolding — strip on the next cleanup pass. | dead (D114/D116 closed) |
| `GE_D288` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_tri1`, right after the D233 trivial-reject guard; cached `ge_d288`) | M-152 diag for the Silo intro stray triangle: logs any triangle whose clip-space NDC bbox covers >15% of the screen or goes non-finite, with each vertex's `w` and `any_behind_camera`. **Investigation in progress** — narrowed to two candidates (an unguarded `w`-division NaN leak in the backface-cull test just below, or the D106/D271 portal-BFS family), not yet root-caused; needs a live/headless `-level_20` run to disambiguate. | **live** (D288) |
| `GE_D236RM` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_tri1`, unconditional getenv, no state kept when unset) | M-155/pass-10 diag for D236: per-frame (`num_dls`) triangle count of the `0x0c184b50` render-mode class (`other_mode_l` exact match), heartbeat-logged every 300 frames even at zero. **REFRAMED D280's "run-to-run dependent instantiation" as ordinary room-based occlusion**, not a CPU-side gating bug — see `## D236` pass-10 writeup. | **live** (D236 OPEN, kept for future passes) |
## Dxx diagnostic probes — finding closed unless noted
+1 -1
View File
@@ -132,7 +132,7 @@ D164,PROPOSED,"Disclaimer / legal screen only draws the first line (D76 root cau
D165,FIXED,"Front-end menu cursor doesn't feel like a mouse pointer (M-31, user bug B5 follow-up).","FIXED (port/src/input.c; Input.MenuPointerMode, Input.HipfirePitchSpeed added)."
D166,FIXED,Hipfire mouse pitch is digital while yaw is analog —,FIXED (port/src/input.c).…
D235,OPEN,Wrong textures load after dying/restarting a level —,OPEN — needs: does it repro on a *fresh* level boot (rules out reload-state) vs only after death/restart (confirms it); which surfaces; a screenshot.…
D236,OPEN,"Surface 1: 2D billboard trees render as a wall/sheet of tree texture instead of discrete camera-facing tree sprites (M-106, user QA report).",OPEN — ninth pass closed with no fix; see D276/D280
D236,OPEN,"Surface 1: 2D billboard trees render as a wall/sheet of tree texture instead of discrete camera-facing tree sprites (M-106, user QA report).","OPEN — tenth pass refuted the ""instantiation is nondeterministic"" theory (ordinary room-based occlusion, not a bug); original wall-vs-sprites symptom still unresolved; see D276/D280/M-155"
D237,OPEN,"QoL ask (M-106, user QA):",OPEN — not designed; port-only
D238,OPEN,"QoL ask (M-106, user QA):",OPEN — not designed; port-only (port/src/input.c + config.c)
D239,?,"QoL ask (M-106, user QA):",CLOSED — executed in the v0.2.0 prep pass (port-only)…
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
132 D165
133 D166
134 D235
135 D236
136 D237
137 D238
138 D239
+72 -3
View File
@@ -539,7 +539,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D233 | **All levels (intermittent): indoor level geometry invisible near doors — you can see through walls/floors into rooms you can't see into, while world objects…** — full `## D233` entry at file tail | **FIXED + VISUALLY VERIFIED (M-107 fix, M-108 verify).** See the other D233 row for full detail. |
| D234 | **Dam: guard towers along the dam structure invisible except the first one (M-106, user QA report).** — full `## D234` entry at file tail | **VISUALLY VERIFIED FIXED (M-108), mechanism unexplained.** See the other D234 row for detail. |
| D235 | **Wrong textures load after dying/restarting a level — not Facility-specific, seen on multiple levels (M-106, user QA report; clarified same session).** — full `## D235` entry at file tail | OPEN — needs: does it repro on a *fresh* level boot (rules out reload-state) vs only after death/restart (confirms it); which surfaces; a screenshot.… |
| D236 | **Surface 1: 2D billboard trees render as a wall/sheet of tree texture instead of discrete camera-facing tree sprites (M-106, user QA report).** — full `## D236` entry at file tail | OPEN — ninth pass closed with no fix; see D276/D280 |
| D236 | **Surface 1: 2D billboard trees render as a wall/sheet of tree texture instead of discrete camera-facing tree sprites (M-106, user QA report).** — full `## D236` entry at file tail | OPEN — tenth pass refuted the "instantiation is nondeterministic" theory (ordinary room-based occlusion, not a bug); original wall-vs-sprites symptom still unresolved; see D276/D280/M-155 |
| D237 | **QoL ask (M-106, user QA): F10 options overlay — make it scrollable with the mouse wheel, expose more settings into it (e.g. screen-flash / no-hit-flash…** — full `## D237` entry at file tail | OPEN — not designed; port-only |
| D238 | **QoL ask (M-106, user QA): mouse sensitivity rework toward normal PC-shooter feel.** — full `## D238` entry at file tail | OPEN — not designed; port-only (`port/src/input.c` + `config.c`) |
| D239 | **QoL ask (M-106, user QA): click-to-lock vs always-grab parity.** — full `## D239` entry at file tail | CLOSED — executed in the v0.2.0 prep pass (port-only)… |
@@ -10660,9 +10660,9 @@ crash class C2 (Facility `-level_34`, Runway `-level_35`): `import_texture_i8` A
## D236 — Surface 1: 2D billboard trees render as a wall/sheet of tree texture instead of discrete camera-facing tree sprites (M-106, user QA report).
**Surface 1: 2D billboard trees render as a wall/sheet of tree texture instead of discrete camera-facing tree sprites (M-106, user QA report).** Eight passes so far (D265, D266–D270, D271, D272, D273, D274, D275–D276). Ruled out: IA4/IA8 decode (as originally scoped), the blend pipeline, fog combine (as originally scoped), portal-culling over-draw (D271, real bug, fixed, not the cause), converter/UV-conversion bugs (D272, code-read clean), the CI palette bit-layout (D273/D274, tested twice against real content), "mips absent" (D274, confirmed present/enabled), "identical UV window" *for the CI8 classes* (D272). **Current leading picture (D276, M-134): the actual visible weave at confirmed weave pixels is painted by a different, previously-dismissed class — an IA8 blocky-noise texture (`oml=0xc81049d8`/`fmt=3`) sampled with a fixed, byte-identical 2×-wrapped UV window and low fog, reproducing D265's original "same window" observation exactly (D269's CI8-classes framing had reframed away from that lead prematurely).** A third, unattributed CI8 class (`0x0c184b50`) may be the reference's actual discrete-tree layer and shows an erratic z/w suggesting its own occlusion/billboard bug — D236 may split into two root causes. **Ninth pass (D280, M-135): fog math verified N64-faithful (±2 truncation over 2505 verts), the IA8 noise class visually confirmed as the weave's painter (HF 14.8→2.9 when collapsed), `0xc8102078` ruled out (terrain, 1.4M tris), and the depth-race read inverted — `0x0c184b50` tree cards are IN FRONT of the noise quad (z/w smaller=closer) yet still don't visibly win; under deterministic `-level_36` boot they barely instantiate at all (~4 tris vs 720 in a free run). No quick win: remaining work is CPU-side instantiation/room-streaming, not a port fix.** Distinct from D176(b)'s painted tree-line backdrop. Logged in `GRAPHICS-BACKLOG.md`.
**Surface 1: 2D billboard trees render as a wall/sheet of tree texture instead of discrete camera-facing tree sprites (M-106, user QA report).** Eight passes so far (D265, D266–D270, D271, D272, D273, D274, D275–D276). Ruled out: IA4/IA8 decode (as originally scoped), the blend pipeline, fog combine (as originally scoped), portal-culling over-draw (D271, real bug, fixed, not the cause), converter/UV-conversion bugs (D272, code-read clean), the CI palette bit-layout (D273/D274, tested twice against real content), "mips absent" (D274, confirmed present/enabled), "identical UV window" *for the CI8 classes* (D272). **Current leading picture (D276, M-134): the actual visible weave at confirmed weave pixels is painted by a different, previously-dismissed class — an IA8 blocky-noise texture (`oml=0xc81049d8`/`fmt=3`) sampled with a fixed, byte-identical 2×-wrapped UV window and low fog, reproducing D265's original "same window" observation exactly (D269's CI8-classes framing had reframed away from that lead prematurely).** A third, unattributed CI8 class (`0x0c184b50`) may be the reference's actual discrete-tree layer and shows an erratic z/w suggesting its own occlusion/billboard bug — D236 may split into two root causes. **Ninth pass (D280, M-135): fog math verified N64-faithful (±2 truncation over 2505 verts), the IA8 noise class visually confirmed as the weave's painter (HF 14.8→2.9 when collapsed), `0xc8102078` ruled out (terrain, 1.4M tris), and the depth-race read inverted — `0x0c184b50` tree cards are IN FRONT of the noise quad (z/w smaller=closer) yet still don't visibly win; under deterministic `-level_36` boot they barely instantiate at all (~4 tris vs 720 in a free run). No quick win: remaining work is CPU-side instantiation/room-streaming, not a port fix.** **Tenth pass (M-155): that CPU-side-instantiation framing itself is REFUTED — three independent deterministic re-runs of the same `-level_36` boot (one with sustained camera rotation) proved the class's per-frame count is byte-identical, not run-to-run dependent; the earlier "~4 vs 720" comparison was an outdoor-vs-indoor room mismatch (the boot's scripted intro walks the camera out of the tree-bearing room at a fixed frame), not nondeterminism. No fix; the original wall-vs-discrete-sprites symptom is unresolved and untouched by this pass.** Distinct from D176(b)'s painted tree-line backdrop. Logged in `GRAPHICS-BACKLOG.md`.
**Status (from §F table):** OPEN — ninth pass closed with no fix; see D276/D280
**Status (from §F table):** OPEN — tenth pass refuted the "instantiation is nondeterministic" theory (ordinary room-based occlusion, not a bug); original wall-vs-sprites symptom still unresolved; see D276/D280/M-155
**Next pass framing (post rule-2-clarification):**
@@ -10718,6 +10718,75 @@ REPORT: (a) the exact struct/field/counter gating instantiation, file:line,
§A1 if this turns out to be another instance of the pattern.
```
**Pass 10 result (M-155, 2026-09-16): D280's "instantiation is run-to-run
dependent" premise is REFUTED — it's ordinary room-based occlusion, not a
CPU-side gating bug. No fix; this closes the specific mystery the pass-10
brief posed, D236 itself stays OPEN on the original "wall not discrete
sprites" symptom.**
Added a small env-gated diagnostic (`GE_D236RM`, `port/fast3d/gfx_pc.cpp`
`gfx_sp_tri1`, see `GE-ENV-PROBES.md`) that counts triangles per frame
(`num_dls`) whose `other_mode_l == 0x0c184b50` (the discrete-tree-card
render-mode signature from D280), heartbeat-logging every 300 frames even
at zero so a class going silent is visible, not just absent.
Ran the deterministic `-level_36` boot **three times independently**
(two static, one with `GE_INPUTSCRIPT="1:SRIGHT"` sustained rotation) —
**all three produced byte-identical per-frame counts**: 4 tris/frame for
frames 1-536, 2 tris/frame for frames 537-747, then **zero for the rest
of a 55-60s run** (verified to frame 3299, well past D280's tick-1500
sample point). This alone falsifies "not pinned by the boot flag" as
stated — the boot IS fully deterministic frame-by-frame; D280's
comparison must have been against session state, not process determinism.
Cross-referencing with the already-in-tree `GE_D104` room-census probe
(`bg.c:654`, unrelated finding, still live) in the same run pinpoints the
mechanism exactly: **the class's count drops to zero at the same tick
(~9601 controller-reads ≈ frame ~740-748) that `curRoom` transitions from
12 to 18, and `roomsDrawn` collapses from 17-20 rooms to 1-5.** Room 12's
neighbour set (17-20 rooms, several with small portal-window bboxes —
consistent with a big outdoor vista) is where the tree-card class draws;
room 18 (near-fullscreen bbox, 1-5 rooms visible — consistent with an
enclosed indoor space) is where it permanently stops, and stays at zero
through the rest of the capture regardless of continuous `SRIGHT`
rotation. This is the deterministic `-level_36` boot's own scripted
intro camera walking from an outdoor establishing shot into the player's
actual indoor start point — the class legitimately has nothing to draw
once indoors, the same way any outdoor-only geometry would correctly stop
rendering behind walls on real N64. D280's "~4 tris on deterministic
boot vs ~720 in a free run" was comparing this boot's post-intro *indoor*
sample against a live session's *outdoor* one — an apples-to-oranges
comparison, not evidence of nondeterministic instantiation.
**Bucket classification: none of (a)/(b)/(c) — the room-transition
explanation accounts for the entire observed variance without invoking
an ABI misread, a port-layer timing bug, or a genuine N64 divergence.**
No `src/game` or `port/` edit made or warranted by this pass.
**What this does NOT resolve:** whether, from *inside* room 12 looking
directly at the tree line, the discrete tree cards draw enough
individual, correctly-placed triangles to look like trees rather than
merging into D276's IA8 noise "wall" — this pass never got the camera
into that outdoor room's tree-facing view (the scripted intro's own path
through room 12 stays at low counts, 4→2, never approaching D280's 720
figure). **Next pass, concrete:** find (or script) a camera path that
stays in room 12 and faces the tree line directly — e.g. `GE_D104` to
locate room 12's screen-space bbox windows facing the trees, then a
`GE_INPUTSCRIPT` sequence that moves (not just rotates) the player
toward one — and re-run `GE_D236RM` there; a healthy count scaling up
toward the ~720 range as the camera closes on the tree line would confirm
the CPU-side instantiation is fine and the remaining D236 mystery is
purely a rendering/blend-order question (D276's territory); a count that
stays anomalously low even facing the trees head-on from inside room 12
would be the first real evidence of a genuine instantiation bug and
should restart the bucket-(a)/(b)/(c) check from there.
Probe left in tree (`GE_D236RM`, env-gated, zero cost unset, cataloged in
`GE-ENV-PROBES.md`). Confidence: HIGH on the room-transition mechanism
(three independent deterministic reproductions, direct room-index
correlation); LOW-to-none on the original "wall vs discrete sprites"
symptom, which this pass didn't reach.
## D237 — QoL ask (M-106, user QA): F10 options overlay — make it scrollable with the mouse wheel, expose more settings into it (e.g. screen-flash / no-hit-flash…
**QoL ask (M-106, user QA): F10 options overlay — make it scrollable with the mouse wheel, expose more settings into it (e.g. screen-flash / no-hit-flash `Game.NoHitFlash`, and other easily-exposed config keys), and organise rows by category (mouse settings together, video together, etc.).** The overlay (`port/src/optionsoverlay.c`) already renders over the registered `config.c` option list; what's missing is wheel-scroll nav, a broader row set, and grouping headers. Cross-ref `OPTIONS-MENU-PLAN.md` §3 (v1 minimum set) + QOL-INVENTORY "PD has, GE lacks".
+16
View File
@@ -1129,6 +1129,22 @@ made mutually acceptable (dual validation / one-time migration).
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.
- **A "deterministic boot" flag (`-level_XX`) pins the level and starting
state, but NOT the camera's later path through it — don't assume two
captures from the same boot flag are comparing the same view.** D236
pass 10 (M-155) found a render-mode-class triangle count that looked
"run-to-run dependent" across separate captures using the same
`-level_36` boot; three independent re-launches (including one with a
full `GE_INPUTSCRIPT` sustained-rotation sweep) actually produced
byte-identical, fully deterministic per-frame counts — the real
variance was the boot's own scripted intro camera walking from an
outdoor room into an indoor one at a fixed frame, and the two captures
being compared had sampled before vs. after that walk (or against an
unrelated live/free-play session entirely). **Before concluding a
count/state is nondeterministic across runs of the same boot flag,
correlate against a room/position probe (`GE_D104`-style) in the SAME
run** to rule out "the camera simply isn't where you assumed it was
yet" before chasing an instantiation or streaming bug.
- **Anything blocking on the scheduler thread throttles the whole sim**
(D186, cf. D134). The gfx task runs synchronously on `src/sched.c`'s
thread, and that same thread delivers VI-retrace events; any `sysSleep` /
+24
View File
@@ -1637,6 +1637,30 @@ static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bo
}
}
/* D236 pass 10 (M-155, TEMP): does the 0x0c184b50 render-mode class's
* per-frame triangle count actually vary frame-to-frame WITHIN one
* deterministic -level_36 boot, or is D280's "run-to-run dependent"
* observation only visible ACROSS separate process launches (which
* would point at room-streaming state that isn't reset/seeded the
* same way twice, not at true per-frame nondeterminism)? Log the
* count once per display list (frame) while it's nonzero. Remove
* once D236 pass 10 concludes. */
if (getenv("GE_D236RM")) {
static uint32_t d236rm_last_dl = 0xFFFFFFFFu;
static uint32_t d236rm_count = 0;
extern uint32_t num_dls;
if (num_dls != d236rm_last_dl) {
if (d236rm_count != 0 || (num_dls % 300) == 0) {
fprintf(stderr, "D236RM dl=%u count=%u\n", d236rm_last_dl, d236rm_count);
}
d236rm_last_dl = num_dls;
d236rm_count = 0;
}
if (rdp.other_mode_l == 0x0c184b50u) {
d236rm_count++;
}
}
if ((rsp.geometry_mode & G_CULL_BOTH) != 0) {
float dx1 = v1->x / (v1->w) - v2->x / (v2->w);
float dy1 = v1->y / (v1->w) - v2->y / (v2->w);