forked from Ports/goldeneye-pc-port
Distilled from Chris Judnich's 'Decompiling a Nintendo 64 Game in 84 Days' (saved under docs/reference/): explicit per-task budgets exposed to the agent, a shared append-only learnings file, file-partitioned parallelism, cheap pre-flight passes, and a standard investigation-subagent brief template. PORT-LEARNINGS.md is a one-screen index of the recurring N64->PC bug classes into PCPortResearch.md §F; AGENTS.md points to both.
3.5 KiB
3.5 KiB
PORT-LEARNINGS — recurring N64→PC bug classes
One-screen index into docs/PCPortResearch.md §F. Every investigation
subagent reads this first and appends any new generalisable quirk.
Details, evidence, and the fix for each instance live in §F under the
cited Dxx label.
A. Pointer-width struct growth (32→64) — the dominant class
A decomp struct with pointer fields, or one pun-allocated into a fixed N64-sized hole / hardcoded byte count, is larger on x86-64. Reading it from ROM bytes misaligns; allocating it N64-sized overruns adjacent state.
- Symptom: garbage pointer deref, corrupted neighbour field, delayed fault far from the cause.
- Fix: store embedded ROM addresses as
u32, cast at use site (PD ground truth); or give real inline storage /sizeof()-based alloc under#ifdef PORT. - Instances: D53.2 (ModelSlot pun), D56 (watch Model raw offsets), D57 (rwdata record count), D67 (image_entry), D79 (bg_room_data), D98 (struct player alloc), D100 (player.model inline Model), D101/D102 (ModelNode*/weapon Model puns), D115 (gunfire THROW* raw offsets).
- Open landmine: raw hardcoded-offset accessors into
struct player/struct hand— seedocs/AUDIT-M6-player-offsets.md.
B. 16-byte PC Gfx / Vtx vs 8-byte N64
Any buffer reservation, memcpy size, slot stride, or pool budget
expressed in N64 Gfx/Vtx units is half-size on PC.
- Instances: D50.6 (texCopyGdls copied only w0 of each 16-byte slot),
D58 (DL reserve 0x100→0x200), D85 (
bgWidenRoomGdl8→16 + bswap), D95 (2× master-DL buffer + raised mempool ceiling).
C. Big-endian rodata / ROM data read on little-endian PC
ROM assets and compiled-in .rodata are big-endian. Anything not run
through a converter or a runtime bswap fixup reads scrambled.
f32values are BE word pairs — a naive byte reversal off-by-one corrupts every float: D73 (sinf/cosfdupairs →DVAL()macro), D112 (d43_emit.py put_f32src[doff:doff+4][::-1]).- Header offset tables / pointers: D54 (cseq ALMidiHdr), D68 (Globalimagetable), D87 (ramromfilestructure), D88 (Usetup* tables).
- Packed bitfields cross byte boundaries differently: D78 / D83 (StandTile id/room, header mid/tail).
- Rule: prefer an offline sidecar converter (D43, D69, D88 pattern,
tools_pc/d*_emit.py) over a runtime fixup for a whole format.
D. N64 hardware idioms fast3d does not emulate
- Z buffer cleared by pointing the colour image at it + fill-rect → does
nothing in fast3d; must emit
G_CLEAR_DEPTH_EXT(D105). - LOD / detail mip tiles: fast3d fabricates a crop when detail textures are off → force base tile (D107).
- K0 segment-address folds (
OS_K0_TO_PHYSICAL | 0x80000000) and hand-inlinedBG_SEG_TO_PTRneed the(u32)32-bit wrap the macro has (D58, D84). - Portal near-plane: z==0 clip points project to ±1e20; x86-64 float
garbage can come back
min>max/ non-finite and slip past a degenerate-box check that clamps to full-screen on N64 (D106).
E. Process / method notes
- Investigation loop is: reproduce → env-gated capped probe → root-cause
→ narrow
#ifdef PORTfix → visual verify (GE_PCDUMP+tools_pc/pixcount.pyvsdocs/reference/n64-footage-*). - gdb launch mode is too slow for timing-dependent faults; gdb attach to an already-running process is fine and fast.
- Check
PD_PORT_CHECKOUTfor the PD analogue before writing anything new — same Rare engine family. - Don't re-investigate a closed §F finding or re-derive a format spec that already has a converter.