docs(D148/D160/D173): re-confirm propDef stride ruled-out; rule out PadRecord.stan and act_anim aliasing for puppet-position bug (#61)

Investigation pass on the cutscene-reliability cluster (D148/D160/D173),
part of the 1.0 acceptance-bar #4 push. No build/runtime toolchain was
available in this worktree (no gcc/cmake on PATH, MSYS2 MINGW64 not
active), so this is a static-analysis-only pass; no code changes.

D160 (Dam end-cutscene / D148 label): independently re-derived (not
just re-cited) that hypothesis (a), a propDef sizepropdef() stride
desync, is ruled out — CutsceneRecord (propDef type 46) is all-scalar,
28B, zero pointer/union fields, so the D122/D126/D132/D209 pointer-
width-growth class cannot apply. Confirms the existing M-31 audit
recorded in porting-notes.md. Hypotheses (b)/(c) (AI trigger flow /
invisible puppet render) remain unconfirmed — still need the shipped
GE_D160=1 trace driven live.

D173 (third-person Bond puppet floats / spins in cutscenes): checked
the two most D209-shaped candidates for a puppet Y-position aliasing
bug and ruled out both. (1) PadRecord.stan is a ROM-serialized pointer
field zeroed by tools_pc/d88_emit.py's emit_pad() with a "dead-on-load"
comment; verified against the N64 source asset tables (UsetupdamZ.c)
that every PadRecord literal initializes stan to 0 on N64 too, so the
PC zeroing is byte-identical to ground truth, not a converter bug.
(2) The anim-root-motion Y-accumulation chain was already exhaustively
audited during D193/D209 (M-80/M-81) and the padding[]-union sweep
that found D209/D210 explicitly cleared act_anim/act_dead as
layout-stable. Root cause not found; the D209-style struct-aliasing
pattern does not appear to be the shared explanation here, unlike the
hope stated in the dispatch brief. Flagged next steps: a live Cradle
capture, and locating the actual two-actor Cuba end-credits code path
(not found this session).

Added a generalizable porting-notes.md quirk: before assuming a zeroed
ROM-serialized pointer-width tail slot is a converter miss, check the
N64 source literal — some fields are legitimately always NULL on both
platforms.

Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG
This commit is contained in:
James Dansereau authored and GitHub committed 2026-09-12 10:15:12 -04:00
1 parent 650ac57fc6
commit 7e99344741
2 files changed
+22 -2

No files matched your search

+20
View File
@@ -340,6 +340,26 @@ through a converter or a runtime bswap fixup reads scrambled.
elsewhere. The Dam rappel cutscene (D148) reached this wall: data path proven intact;
residual cause is runtime AI-script control-flow / `CAMERAMODE_POSEND` cinematic
render (D75 family). `GE_D160=1` diagnostic ships; needs a live Dam-to-exit playthrough.
**M-106 addendum:** independently re-derived (not just re-cited) — `CutsceneRecord`
(propDef type 46) has zero pointer/union fields, so it cannot exhibit this class at
all; confirms the M-31 stride audit rather than superseding it. D148/D160's residual
cause is still unlocated (needs the live trace); see D173 for a related negative
result on the puppet-position side.
- **D132/D126 corollary — a zeroed ROM-serialized pointer-width tail slot is not
automatically a converter bug; check the N64 source literal first (M-106).**
Before assuming an offline converter wrongly zeroed a widened pointer field
(the D122/D126/D132 "dead-on-load" pattern), grep the asset `.c` source tables
(`assets/obseg/**/*.c`) for that field's literal initialiser. `PadRecord.stan`
(`src/bondtypes.h:1744-1751`) looked exactly like a D126/D132 miss — a ROM-
serialized tail pointer, zeroed by `tools_pc/d88_emit.py`'s `emit_pad()` — but
every `PadRecord` in every checked setup table (`UsetupdamZ.c` etc.) initialises
`stan` to literal `0`; N64 never carried a live pointer there either, so the PC
zeroing is byte-identical to ground truth, not a regression. The tell that saves
the trip: if the *source*, not just the ROM binary, always writes `0`/`NULL` for
a field, it was never "dead-on-load" in the D123-corollary sense (a field that
reads a real small int/id before being overwritten) — it's simply always
runtime-computed, on both platforms, and a NULL seed into whatever consumes it
is the original game's normal case, not a port defect.
- **D151 — a ROM-serialized `s32` slot decoded by the struct as `[u16 hi][u16 lo]`
reads zero on LE.** N64 code frequently splits a 32-bit setup-stream word into
`u16 reserved; u16 realvalue;` where the useful value is always small and lands