Files
goldeneye-pc-port/AGENTS.md
T
James DansereauandClaude Sonnet 5 0bcf5f1777 D86+D87: player gait-model pointer truncation + attract-mode demo endian fix
D86 (resolved): modelInitRwData crashed on a bad ModelNode pointer during
room-triggered gameplay init. Root cause: initplayergaitobject.c did
`player_gait_object_header.RootNode = (int)&player_gait_hdr;` -- a
same-width pointer<->int round trip that's a no-op on N64 but truncates a
real 64-bit pointer on PC, dropping the executable's load-base high bits.
Fixed under #ifdef PORT by assigning the real pointer directly. Only
exercised once real gameplay starts (past the intro), which is why it was
never hit before D69 let the game reach stage load.

D87 (resolved): once D86 stopped blocking progress, an idle (no-input) run
reaches the front end's genuine attract-mode demo playback and crashes in
ramrom_replay_handler. Root cause: ramromfilestructure is a real
ROM-compiled asset (big-endian) loaded via romCopyAligned's raw byte copy
(D66) with no byteswap, so every multi-byte field reads scrambled and
drives wild pointer arithmetic in the block-buffer consumers. Fixed with a
#ifdef PORT ramromFixupEndian() called once after the load (D54 cseq-fixup
pattern).

Both root-caused with new env-gated TEMP probes (GE_D86, GE_D87), left in
place per the existing convention. D88 (per-level "Usetup*Z" stage-setup
file: zero PC porting done, struct-width + endian bugs stacked) is the new
next blocker, root-caused but intentionally not patched inline -- it's
D69-scale format-conversion work, logged as an open finding instead.

Also documents a fast, deterministic BUNKER1 repro (-level_09 command-line
arg, bypasses the front end/attract-mode entirely) and a correction to the
gdb guidance: attach mode (vs. launch-only) works well for watching a
global's write on an already-running process.

Docs: docs/PCPortResearch.md §F (D86/D87 resolved, D88 new), docs/HANDOFF.md
(session K), AGENTS.md summary line.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3xib6jjMV8o2iE4JfixAo
2026-08-27 21:47:04 -04:00

14 KiB
Raw Blame History

AGENTS.md — GoldenEye 007 PC Port

What this is

  • n64decomp/007: WIP decompilation of GoldenEye 007 (N64), byte-matches US/EU/JP ROMs.
  • Active work: PC port modelled on the Perfect Dark PC port (same Rare "Indy" engine family).
  • Read docs/PCPortResearch.md first — architecture, GE-specific RSP deltas, phased plan, and review findings (A1–D33 in §11; current handoff + plan in §H).
  • Starting a fresh session? Read docs/HANDOFF.md — the paste-ready brief with the immediate task, standing procedure, and environment gotchas.

Non-negotiables

  1. N64 build untouched. Makefile, tools/, rsp/, ld/ belong to the N64 build. Never modify them for the PC port.
  2. Game logic is unmodified. The decomp's control flow and behavior are ground truth for 1:1 fidelity — never change them. All N64 hardware dependencies are satisfied by the port/ layer; if a game file seems to need a behavioral change, stop — the fix belongs in port/. Narrow exception (ABI/layout only): the 32→64-bit transition forces a small class of mechanical, semantics-preserving edits that cannot be isolated in port/ — chiefly pointer-width reconciliation in ROM-serialized structs (a struct with a 32-bit-pointer field misaligns when read as 64-bit). These follow the PD ground-truth pattern (store the embedded address as u32, cast to a real pointer at the use site), change no logic or behavior, and are each documented in docs/PCPortResearch.md §F/D3x. No other game-code edits are permitted.
  3. Region macros mirror the Makefile. CMakeLists.txt REGION_DEFS must match the N64 Makefile's per-region macro set exactly (finding A1). Divergence = silent branch divergence + link failures.
  4. src/libultrare/Makefile.libultrare is ground truth for original-vs-Rare libultra files (finding B3). The PC build compiles: libultra/audio, libultrare/audio (drvrNew/env/reverb), libultra/gu, and libultrare/io/vitbl.c only. All other io/ + os/ files are excluded and shimmed in port/src/libultra.c.
  5. rsp/graphics/gmain.s is the RSP ground truth — the authoritative reference for which GBI commands GE emits (modified fast3d, 1545 lines). We do not run it on PC; port/fast3d/ replaces it. Use it to validate the software RSP's command decoding and the custom CC/RM modes.

Critical files

File Role
docs/PCPortResearch.md Research + plan + findings (A1–C2). Read first.
CMakeLists.txt PC build (parallel to the N64 Makefile). Source list + REGION_DEFS live here.
port/src/ Shims: libultra.c (OS API), gesched.c (scheduler), n64stubs.c (boot/TLB/FPU/rmon), random.c (PRNG ported verbatim from random.s), ucode.c (microcode segment markers), main.c, video.c, …
port/fast3d/ Software RSP (adapted from the PD port). The main Phase 2 work.
rsp/graphics/gmain.s GE's RSP ucode — ground truth for GBI/CC/RM.
PD_PORT_CHECKOUT Perfect Dark PC port checkout — standing reference for this project: consult it whenever a remaining work item has a PD analogue (same Rare engine family). Port-layer ground truth (port/fast3d/, crash/system/video), plus copy candidates: port/src/preprocess/ (N64→PC asset conversion at load; filemodel.c is the D43 near-analogue, same vma 0x5000000) and mixer.c/input.c/fs.c (Phase 3/4 — our stubs were scaffolded to be replaced by these). Caveats: port-layer files only; same family ≠ identical format — validate per field. Full audit: docs/PCPortResearch.md §2.4.

Build

./build-pc.sh ntsc-final   # or pal-final / jpn-final

Needs CMake + SDL2 + zlib + OpenGL (MSYS2 packages: see build-pc.sh header). ROM goes in ./data/ (not distributed).

Verification ritual (after any build-affecting change)

  1. Undefined symbols. Every symbol referenced by the compiled set (see CMakeLists.txt: SRC_GAME, SRC_ENGINE, SRC_LIBAUDIO, SRC_LIBULTRARE_AUDIO, SRC_LIBULTRARE_DATA, SRC_GU, SRC_PORT*) must be defined exactly once in the compiled set or in port/. Symbols that live in EXCLUDED files (libultra/io/*, libultrare/io/* except vitbl.c, libultra/os/*, libultrare/os/*, sched.c, rmon.c, vi.c, src/*.s) must be provided by port/src/libultra.c, gesched.c, n64stubs.c, random.c, or ucode.c.
  2. Duplicates. No symbol defined twice across the compiled set (watch sp_* stacks, rmon*, os* shims, segment markers).
  3. Syntax. Every touched file must parse; once the toolchain is installed, ./build-pc.sh is the final word.

Run /linkcheck for this sweep. Record new findings in docs/PCPortResearch.md §11 style (next label after the last used).

Phase status

  • Phase 0 (scaffolding): done, committed.
  • Compile+link milestone: all ~235 TUs compile and ge007.x86_64.exe links clean (ninja ge007 -k 0, 236/236). Findings D7–D17 in docs/PCPortResearch.md §11; asset stubs in port/src/assetstubs.c, fast3d no-ops in port/src/gfxstub.c (delete when real fast3d lands).
  • Phase 1 (boot to window): done — ROM loads/validates/maps at cart base 0x10000000 (romdata.c), SDL2 window + GL clear loop (video.c), absolute asset symbols from scripts/gen_romassets.py → port/src/romassets_<r>.s (findings D18–D23).
  • Phase 1.5 (boot to first frame): DONE — boot chain complete AND real frames render ([NOTE] frame N rendered in … us). D34–D42 closed the blockers on top of D31–D33: ANIM_DATA lvalues, music seq-table ABI+endianness, music-heap sizing, libaudio bank-tree re-layout, implicit-declaration prototype shim (D38), Globalimagetable rebasing + true segment size (D39), ModelHitEntry pool sizing (D40), cross-thread GL context release (D41), rsp task-settings toggle (D42). ROM dump verified against the No-Intro good dump (byte-identical, header CRCs re-checked — tools_pc/romverify.c).
  • Phase 2 (rendering): IN PROGRESS — fast3d integrated + real frames render. Plan B EXECUTED (D50, committed): all 512 NTSC model files converted offline by tools_pc/d43_emit.py → data/pcmodels-ntsc-final/pcmodels.bin + manifest.csv (single concatenated RZ image; regenerate per region with the script — sidecars are gitignored), served through the existing load path via a port-layer table patch (port/src/pcmodels.c, romdata cart-extension, one-shot pcmodelsPatchTable() + poolRemaining reset hook in load_object_fill_header); runtime C fixups for language-bank BE offset tables + font re-layout (D50.3/D50.4); legal-screen UB seed (D50.5); D50.6 fixed the first model-render crash — texCopyGdls copied only w0 of each 16-byte Gfx slot on PC (the union's trailing long long is 8 bytes), leaving stale mempool w1s in the compacted GDLs; byte-proven against PlegalpageZ, fixed with a full-slot copy under #ifdef PORT. Model GDLs now execute to completion (PlegalpageZ sub-DLs). D51 RESOLVED (font pixeldata fixup wrote the pointer at the wrong offsets — frame-5 import_texture_i8 crash gone; GE opcode space decoded along the way, G_TEXRECT=0xE4/0xE5 non-F3DEX numbering), D52 RESOLVED (osGetCount now advances at the N64 RSP counter rate ≈ 46.5525 ticks/µs — fixed the non-deterministic post-frame-2 hang), D53 RESOLVED (model RW-data pool addressing: D53.1 Model.datas word-stride → u32 * under PORT + casts; D53.2 ModelSlot/AnimModelSlot re-laid on top of the PC struct Model — in-use@0x10 / pool@0x20, slot size = sizeof(Model) = 0xE8 — plus PORT-guarded heap-fallback bumps in model.c), D54 RESOLVED (cseq ALCMidiHdr BE header: port-layer romdataFixupCseq bswap32 of the first 17 u32s, called from music.c after each decompress), D54b RESOLVED (synth param slots sized for ALStartParamAlt = 0x28 in synthesizer.c alSynInit under PORT), D55 RESOLVED (RLE folder-menu background BE w/h header: bswap32 of the first word at (u32 *)&unknown2 at cart-map time in romdataInit, guarded). Build GREEN; game boots, plays intro music, renders through logo/gun-barrel, instantiates Brosnan, and executes the gun-barrel hole DL clean to ENDDL. D56 RESOLVED (watch Model/RW-pool raw offsets: field-name Model access + static 0xC8-byte pool under PORT in bondview2.c), D57 RESOLVED (pointer-grown rwdata records overflowed N64-sized spare pools — Brosnan PC numRecords 153 > 140: capacities grown in initunk_005520.c + dynamic slot+pool fallback in model.c), D58 RESOLVED (gun-barrel DL: vertex-pointer K0 idiom rebuilt via OS_K0_TO_PHYSICAL(…) | 0x80000000 in title.c + DL reserve 0x100→0x200 because sizeof(Gfx) is 16 on PC). D59–D68 RESOLVED (committed): the "GL DLL" crash was D64's unbounded blood-RLE write (sentinel section-adjacency lost on PC) clobbering the gun-barrel sub-DL; then D60–D62 (DMA target validation, OSMesgQueue layout), D65/D65b (HEADS/BODIES enum signed sentinels under PORT), D66 (romCopyAligned + ramrom replay 64-bit pointer width), D67 (struct image_entry N64 layout), D68 (Globalimagetable BE→LE fixup — port/src/gimgfixup.c bswaps IMAGESEG Gfx w1 words + sImageTableEntry.index after texReset's romCopy; compiled globalDL shadows synced for explosion.c). The game now renders the entire intro (logo → gun barrel w/ Brosnan → cast; ~frame 2100, ≈2 min wall-clock at the current ~20 fps clean-run rate). D69 RESOLVED (committed): load_bg_file no longer faults on stage load. BG-file headers/records are N64 big-endian segment-0x0F offsets with internal self-relative offsets; fixed via offline sidecar conversion (tools_pc/d69_emit.py → data/pccg-<region>/pccg.bin + manifest, same Plan-B pattern as D43/pcmodels, port-layer wiring in port/src/pccg.c) plus four ABI layout fixes surfaced along the way: D78 (StandTile id/room bitfield packing), D79 (bg_room_data pointer-width), D83 (StandTileHeaderMid/Tail bitfield packing — same class as D78, caused an infinite loop in stanBuildRoomData until fixed), D84 (hand-inlined BG_SEG_TO_PTR-style folds in room-DL loaders needed the same (u32)-cast 32-bit wrap the macro already has). D85 OPEN, D86/D87 RESOLVED, D88 OPEN (current blocker) — found during/after D69 verification: D85 — room primary/secondary DL binaries decompress correctly but texLoadFromGdl (the already-working model-GDL runtime converter) produces garbage from their content, not yet root-caused; safety-netted (bounds-checked in bgBuildRoomVtxBounds so it can't segfault, but geometry is wrong/empty). D86 (RESOLVED) — modelInitRwData crash was a single (int)-truncated pointer in initplayergaitobject.c's embedded player gait-model init; fixed under #ifdef PORT. D87 (RESOLVED) — the front end's genuine attract-mode demo playback crashed on an unswapped big-endian ramromfilestructure; fixed with a #ifdef PORT byteswap fixup in ramromreplay.c (D54-cseq pattern). D88 (OPEN, current blocker) — the per-level Usetup*Z "stage setup" file (prop.c/struct stagesetup) has zero PC porting work: it's raw ROM bytes read directly through a pointer-widened, still-big-endian C struct (D79-class width bug + D87-class endian bug, stacked); crashes in proplvreset2 (prop.c:1306), reproducible in seconds via -level_09 (bypasses the front end, no attract-mode wait). Needs D69-scale format-conversion work (byte-accurate spec + offline converter or careful runtime fixup), not a quick inline patch — logged as an open finding per AGENTS.md's "stop and write up" guidance. See docs/HANDOFF.md and docs/PCPortResearch.md §F/D86-D88 for full detail and next steps. D70–D74 (intro-logo pixels, RESOLVED): D70 env-gated PPM frame capture (GE_PCDUMP → ./ppm/, gitignored — kept as permanent dev tooling); D71 C-array texture sources (the four rarewarelogo.c RGBA16 images) byte-swapped on LE PC → port-layer per-source bswap in import_texture; D72 the UV path (GE always uses authored tc[] UVs; lookat_enabled defaults false); D73 = root cause of old D72.3 — sinf/cosf du double constants are big-endian word pairs, garbage on LE PC → guMtxF2L emitted −32768 for every sin/cos entry → logo triangles projected off-screen; fixed with the PORT-only DVAL() macro in src/libultra/gu/guint.h (sinf.c/cosf.c .d reads replaced; N64 build untouched); D74 texture import fallback no longer truncates valid gDPLoadBlock data (was dropping letter mip chains + reducing D_02005FF0 to 32×3), TextureCacheKey gained size_bytes, and the VBO path wraps UVs by tile size for WRAP sub-tiles (N64 semantics). The Rareware logo now renders its four gold letters on the dark-blue plate (PPM-verified ~frame 555; final pixel-perfect check vs N64 footage optional). D69 is resolved; D88 is the current blocker to a rendered stage frame, with D85 next after it (see above). Committed through D87. Recurring sub-task: ABI/layout fixes per the D32 procedure (§H) as more asset types are first touched (PD's preprocess/ module already solves most of them; prefer the offline pattern for format work — D48; full-struct Gfx copies on PC — D50.6; audit struct type-puns after pointer-width changes — D53.2; raw byte offsets into pointer-containing structs shift on PC — D56; N64-sized reservations must be re-checked against 16-byte Gfx/Vtx widths — D58). Open this session (tracked in §F as D75–D77, lower priority than D69): D75 3D rendering still wrong (Nintendo logo misplacement + intro player models absent); D76 2D disclaimer screen only partially drawn; D77 no audible music on PC. Prioritization: rendering over audio unless a shared root cause is found (Q1).
  • Phase 3 (audio + input): libaudio → SDL — largely copy-and-adapt from PD (mixer.c 722 lines vs our 31-line stub; input.c SDL_GameController backend; §2.4); decide the ASP strategy (C2: aspMain* dummies in ucode.c).
  • Phase 4 (saves + polish): file-backed EEPROM — build on PD's fs.c (file-backed save dir); PFS/motor already no-op stubs.