diff --git a/docs/dev-process.md b/docs/dev-process.md index 59e6aaea..f1147b16 100644 --- a/docs/dev-process.md +++ b/docs/dev-process.md @@ -63,6 +63,18 @@ The classes it currently tracks: - whole serialized formats are converted by an **offline sidecar** rather than patched at runtime. +**Two process lessons from the sibling GEVR VR port** (same decomp lineage, +reference-only survey in `scratchpad/GEVR-TRIAGE.md` item D — their corpus +hit both repeatedly enough to name them): (1) **a grep standing in for a +read** — a site list built by grepping one helper name or spelling misses +siblings spelled differently or living in an adjacent file (their own +missed-smoke-scissor-site example); treat a grep-built site list as a draft, +not a census, until it's been read against. (2) **write the falsifier before +the run** — state up front what result would kill the hypothesis ("if X +survives, hypothesis Y is dead"), not just what would confirm it; this fits +the existing budget/findings discipline directly and costs one sentence per +brief. + ## 3. Parallelise by file, consolidate often Lewis ran multiple git worktrees and found the real cost was diff --git a/docs/dev/WIDESCREEN-FOV-PLAN.md b/docs/dev/WIDESCREEN-FOV-PLAN.md index 164ec8f3..8577d50d 100644 --- a/docs/dev/WIDESCREEN-FOV-PLAN.md +++ b/docs/dev/WIDESCREEN-FOV-PLAN.md @@ -125,6 +125,43 @@ From the `pd_port` checkout (N64 PD had no widescreen; the port added it): ("Decide Phase 1" below) — correcting an earlier note here that overstated this as settled. +### 6. GEVR portal-scissor findings — read before Phase 2/3 game-code work + +The sibling GEVR VR port (same n64decomp/007 lineage, reference-only survey +in `scratchpad/GEVR-TRIAGE.md` item A) independently measured, on the same +decomp, that widescreen "missing wall / see-through slab" artifacts come +from the **per-room portal `SETSCISSOR` rects**, not the room-visibility +pass (their cull census was byte-identical between arms) — giving every room +the full view removed the artifacts (one level, one viewpoint; treat as a +hypothesis, not settled). Re-derived in **our** tree (2026-07): + +| Class | Our sites | Notes | +|---|---|---| +| SCISSOR — room passes | `bg.c:685`, `bg.c:747` (`bgScissorCurrentPlayerViewF`) | primary + secondary/xlu | +| SCISSOR — default variants | `bg.c:723`, `bg.c:1306`, `chrprop.c:598` (via `bgScissorCurrentPlayerViewDefault`, `bg.c:1332`) | | +| SCISSOR — characters | `chr.c:3070/3074` (`chrRender`) | | +| SCISSOR — explosions | `explosion.c:876/880` **and `explosion.c:1488/1492` (smoke)** | the smoke site is the one GEVR missed on their first pass | +| CULL (removes props, not pixels) | `propobj.c:13584` `posIsOnScreen` (used at `:5881`) | a too-tight rect *deletes* a prop — GEVR's black-wall candidate | +| **AI / SPAWN logic** | `chraction.c:10628` `chrIsPosOffScreen` → `bgGet2dBboxByRoomId` (`bg.c:573`) | reads the rect for character AI/spawn decisions | + +**Design trap (their hardest-won lesson):** do NOT put a clip/widescreen +knob inside `bgGet2dBboxByRoomId` — it's the chokepoint that also feeds +`chrIsPosOffScreen`, so a "clip-only" change there would silently alter what +characters do. Scissor sites can be widened directly; the cull box and the +AI/spawn consumer must be reasoned about separately. + +**Caveats:** (1) removing all per-room scissoring has an unknown +overdraw/perf cost — measure before shipping; (2) their fix evidence is one +level + one viewpoint; (3) the "two notions of view width" trap (their doc +278): a port knob that redefines native content width from the *window* +aspect while the game's scissor-tracked canvas stays 4:3 leaves ~29% of +columns undrawn → stale-buffer slabs. Whatever this plan's implementation +does, the game's canvas notion and the window notion must be reconciled +explicitly at one named place — re-verify the line numbers above (cheap +greps: `bgScissorCurrentPlayerView`, `bgGet2dBboxByRoomId`, `posIsOnScreen`, +`chrIsPosOffScreen`) before acting on them, since this table predates any +Phase 2/3 work actually landing. + ## Option B — the standard (native 16:9, PD-parity) ### Architecture diff --git a/docs/porting-notes.md b/docs/porting-notes.md index d1a44af0..aba04c8c 100644 --- a/docs/porting-notes.md +++ b/docs/porting-notes.md @@ -1225,3 +1225,30 @@ and turns every such overrun into a fatal `*** stack smashing detected ***` path (`cd C:\Users\...`) is unreliable in this same shell and can silently mis-parse to a mangled path on a later command in the same tool call — prefer `cd /c/Users/...` (forward slashes) for the build working directory. +- **A crash fault address of `0xffffffffffffffff` on x86-64 usually means a + non-canonical #GP from a garbage-high-bits pointer (bits 63:48 not a sign + extension of bit 47), not a computed `-1` sentinel** (cross-validated + against the sibling GEVR VR port, same decomp lineage — see + `scratchpad/GEVR-TRIAGE.md` item C; we independently hit the same register + shape in D191). Two companion habits worth keeping: re-read map anchors + from the freshly rebuilt map after every rebuild (a stale map silently + resolves the wrong symbol), and confirm the fault PC actually disassembles + on an instruction boundary before trusting a resolved symbol. +- **Frame-rate assumptions — read before proposing any non-60 Hz mode** + (cross-validated against GEVR, `scratchpad/GEVR-TRIAGE.md` item B). GE's + logic is delta-scaled through `g_GlobalTimerDelta` (~216 call sites); the + PAL build (×1.2) is a shipping proof that delta-scaled code tolerates + other rates. But: **Bond's walk is frame-rate dependent by construction** + — `MoveBond` (`bondview2.c`) is an exponential approach toward a target, + advanced once per frame with no timestep, so convergence is measured in + frames, not seconds (GEVR measured ~1.487× walk speed at 90 Hz vs 60 Hz, + ≈90/60 exactly). Turn/aim *is* delta-scaled; walk is not. High-refresh or + uncapped presentation modes are therefore out of scope without a + game-logic change, which is forbidden — we tick at fixed 60 Hz (D52 + pacing) by design, not as a limitation to fix. Also: `g_ClockTimer` must + stay integer (24 iteration sites depend on it); of 44 + `/g_GlobalTimerDelta` divide sites, 40 are guarded (three different + spellings) and 4 are not (front-end cast camera `front.c:8271-8273`, + `propobj.c:~2393`) — those 4 rely on div-by-zero yielding NaN/inf without + trapping on both MIPS and x86-64 SSE, matching retail behavior; do not + "fix" them.