Files
goldeneye-pc-port/docs/HANDOFF.md
T
James Dansereau b3000825f2 PC port: phase 1.5 — handoff notes updated for D33 implementation session
Record ROM-dump verification (No-Intro [!] byte-identical, CRCs re-checked)
in AGENTS.md phase status, PCPortResearch §H state, and HANDOFF current
state, so the next session starts from a verified foundation.
2026-08-21 20:30:50 -04:00

8.4 KiB
Raw Blame History

Handoff brief — GoldenEye 007 PC port (Phase 1.5: boot to first frame)

Paste-ready briefing for the next agent session. The authoritative detail lives in AGENTS.md and docs/PCPortResearch.md (§H handoff, §F/D31–D33 findings); this is the summary + the immediate task.

Your job

Get ge007.x86_64.exe booting to a rendered first frame on PC. You are mid-Phase-1.5: the game boots, maps the ROM, opens the window, and runs mainThread through most of bossInitMainthreadData(), but currently SIGSEGVs in weapon-animation init. Work agentically — fix → build → verify under gdb → commit at each working milestone. The goal when the user is back: a booting game.

Read first (authoritative, in order)

  1. AGENTS.md — non-negotiables, critical files, build, verification ritual, phase status.
  2. docs/PCPortResearch.md §H (handoff + plan), then §F/D33 (root cause of the current blocker + complete fix design), D31–D32 for background.

Non-negotiables (the ones that bite)

  • N64 build files (Makefile, tools/, rsp/, ld/) — never touch.
  • Game logic is unmodified. Hardware deps go in port/. Narrow exception: mechanical, semantics-preserving ABI/layout fixes forced by the 32→64-bit transition, each documented as a D3x finding. No behavior changes.
  • Region macros in CMakeLists.txt must mirror the N64 Makefile exactly.

Current state (verified this session)

  • ROM dump verified: the No-Intro good dump GoldenEye 007 (U) [!].z64 is byte-identical to both dev ROM copies and its header CRCs re-compute clean (tools_pc/romverify.c). Fingerprint + provenance in Environment below — trust it, don't re-verify.
  • D31 FIXED: langInit file-load crash gone (real-zlib decompressdata in port/src/rzdecomp.c). mainThread boots into bossInitMainthreadData().
  • D32 Part A DONE: ModelAnimation pointer fields → u32 (N64 layout, sizeof 64).
  • D32 Part B = ROOT-CAUSED (finding D33). Two independent bugs, both confirmed:
    1. ROM endianness. The .z64 file stores structured multi-byte fields in big-endian byte order (bit-packed streams are raw). The animation blob's 20-byte record headers and descriptor arrays must be converted per-field at load time: u32 → bswap32, u16 → bswap16, u8 → identity. No uniform word transform exists. Validated in Python over all 173 ROM entries: 0 bad ranges, 0 non-sequential descriptor arrays; simulated root-motion frames show smooth per-frame deltas. The exact garbage seen under gdb (0xC82BD8C0) is reproduced by LE-reading the BE file bytes + rebase. Evidence: GE-specific ROM header, tools/utils.h "v64→z64" convention, build pipeline with no byte-swap step, BE toolchain defaults.
    2. x86-64 stride bug. expand_ani_table_entries() iterates with s32** var_v0; var_v0++ — +4 bytes/iter on N64, but +8 on x86-64 (verified: add $0x8,%rdx in the compiled binary). Only even-indexed entries get rebased; odd ones keep raw small offsets (fire_standing is index 1 → would stay 0x144 → SIGSEGV on use). The earlier "zeros at odd indices" gdb observation was a display artifact — the compiled .data array is dense (objdump-verified). This pattern occurs nowhere else in the set.

Immediate task — implement the D33 fix (design complete, ~3 small edits)

  1. port/src/romdata.c + port/include/romdata.h: add void romdataFixupAnimationData(u8 *blob, u32 blobSize, const s32 *tableA, const s32 *tableB). For each non-null (≠0 and ≠1 — sentinel is 1) offset in both tables:
    • record at blob+off (20 bytes): bswap32 fields at +0x00 (address), +0x08 (bitDescriptors), +0x10 (bitStream); bswap16 fields at +0x04 (frame count), +0x0C (bitsPerFrame root motion), +0x0E (frame size bits); bytes +0x06/+0x07 (u8 angle width / loop flag) untouched.
    • descriptor array at [blob+bd, blob+bs) step 6 (ModelAnimBitField): bswap16 words at +0 and +4 (bitOffset, valueOffset); bytes +2/+3 (bitCount, pad) untouched. Guards: skip if bd==0 && bs==0; require bd < bs <= blobSize and (bs-bd)%6==0. Full field semantics + layout rules in D33 (blob interleave: entry i's payload sits at [PTR_ANIM_{i-1}+0x14, PTR_ANIM_i); all regions disjoint).
  2. src/game/initanitable.c, alloc_load_expand_ani_table() (~line 265): call the fixup between romCopy(...) and expand_ani_table_entries(...) — it must run before expand (expand reads/writes those fields as LE after fixup). Add #include "romdata.h" (port/include is already on the include path). One-line mechanical ABI edit — document with a D32/D33 comment.
  3. src/game/initanitable.c, expand_ani_table_entries() (line 233): change the loop pointer from s32** var_v0 to s32 *var_v0 (cast at assignment: var_v0 = (s32 *)arg0;) so every entry is visited on x86-64. Semantics-preserving on N64. Keep the rest of the function body identical (*var_v0 += base, ((struct anim_entry *)(s32)*var_v0)->unk08/unk10, and loop 2's *(s32 *)(s32)*var_v0 += entries_base).

Do NOT transform the entries segment (per-frame joint angles, file 0x124AC0, 0x169EC0 bytes) — it is identity-encoded bit-packed data, not consumed at boot; verify visually in Phase 2 when Bond first animates.

Verify (gdb, launch mode only — attach fails with error 87)

  • Break at boss.c:233 (right after alloc_load_expand_ani_table returns). base = &ptr_animation_table->data.
  • For a sample of animation_table_ptrs1/2[i] entries (include odd indices!): *(u32*)(entry+8) and *(u32*)(entry+16) must be in [0x70xxxxxx] (base + blob offset), and the record's address field (+0x00) must be a cart address 0x10xxxxxx.
  • Confirm boot advances past init_weapon_animation_groups_maybe() into the next init step of bossInitMainthreadData. Expect more D32-class faults further along (models, textures, other tables) — apply the standing procedure below each time.

Standing procedure (you will hit more of these)

Every ROM-serialized struct with a pointer field faults the same way once you reach more asset loading. The D32 fix procedure (doc §H): at the fault run ptype /o <Struct>; if a pointer field's offset/size diverges from its N64 offset comment, change it to u32 + cast at the use sites; verify the load-time rebase yields valid V1 addresses; also check the ROM bytes' endianness per-field (D33 rule: u32 bswap32 / u16 bswap16 / u8 identity) and any fixup loop's pointer stride on x86-64; rebuild; confirm boot advances. Log each as D3x in §F and note it in AGENTS.md phase status.

Environment (do not rediscover these)

  • MSYS2/MinGW tools are in /c/msys64/mingw64/bin/ — not on PATH. Prefix every shell: export PATH="/c/msys64/mingw64/bin:$PATH".
  • Build: ./build-pc.sh ntsc-final (incremental; full rebuild only when port/shim/ changes). ROM is at data/ge007.ntsc-final.z64 (= baserom.u.z64, byte-identical).
  • ROM provenance (verified 2026-08): all three copies are byte-identical to the No-Intro good dump GoldenEye 007 (U) [!].z64 (kept in data/). MD5 70c525880240c1e838b8b1be35666c3b, SHA-256 2cdcec8a9f0cb6e36337f3ee39d8ad105dc8afa6ba1c02d466e8f5b771f9a162, 0xC00000 bytes, country 'E' (US). Header CRCs DCBC50D1/09FD1AA3 re-verify with tools_pc/romverify.exe (build line in its header comment).
  • gdb: launch mode only (gdb -batch -ex "handle SIGSEGV stop" -ex run …). Symbolicate offline with addr2line -e build-pc/ge007.x86_64.exe -f -C <0x140000000+rel>. Image base is 0x140000000 (re-verify with info address after a rebuild).
  • objdump -d --disassemble=<fn> / objdump -s -j .data on build-pc/ge007.x86_64.exe is a fast way to check what the compiler actually emitted (used this session to confirm the stride bug and the dense table array).
  • Many init functions use a fake RBP — compute stack offsets from the entry RSP, not RBP.
  • The D30 crash handler did not write ge007.crash.log for the game-thread fault; attribute crashes via gdb. (Worth a separate look later.)
  • Python on this box: no f-strings with nested quotes (< 3.12) — use % formatting.

Definition of done (this milestone)

mainThread reaches bossMainloop() and a frame is actually drawn on the GL surface (not just the clear color). After that, Phase 2 (fast3d CC/RM correctness vs gmain.s) makes it look right. Commit + push to origin/master at each working checkpoint using the message style PC port: <phase> — <what>.