Files
goldeneye-pc-port/AGENTS.md
T
James Dansereau 471eecd6d3 D401: Rumble Pak -> real gamepad haptics (re-promotes D224/M-87)
Port layer only (zero src/ edits), per docs/dev/RUMBLE-PLAN.md:
- port/src/input.c: inputRumbleSupported/inputRumble -> SDL_GameControllerRumble;
  detection via SDL_GameControllerHasRumble (SDL >= 2.0.18) else
  SDL_JoystickIsHaptic + known-type Windows fallback; zeroed on pad close.
  Single global gRumbleScale (dflt 0.5) registered as Input.RumbleScale.
- port/src/libultra.c: the two M-87 blockers resolved - g_contStatus[i].status
  now sets CONT_CARD_ON for connected rumble-capable pads (so
  joyRumblePakInit arms) and osPfsInit returns PFS_ERR_DEVICE for them (so
  osMotorInit is attempted); osMotorInit/Start/Stop drive the haptics API.
- port/src/optionsoverlay.c: one global 'Rumble strength' ROW_SLIDER
  (Input.RumbleScale, 0.05 detents, %.2f) in the INPUT section +
  kResetDefaults entry. The four per-player Input.PlayerN.RumbleScale keys
  were dropped (user decision 2026-09-28); stale keys in an existing ini
  log four 'unknown key' LOG_NOTE lines and self-clean on next configSave.
- Real-pad haptics confirmed working by the user (2026-09-28).

Build env: build-pc.sh self-heal guard revision - re-exec now writes a
self-contained .ps1 (msys->native argv conversion mangles $-bearing
-Command strings; file write is conversion-free), builds PATH explicitly,
picks TMP from writable candidates (GetTempPath honours the broken
inherited TMP), self-logs to build-pc/ge007-native-reexec-*.log, and
treats a null LASTEXITCODE as 'did not execute' (PS 5.1 piped native
commands error non-terminating). OPEN: the guard still fails inside
build-pc.sh while the identical standalone .ps1 invocation works - AGENTS.md
documents the workaround. docs: RUMBLE-PLAN.md (new, tracked), findings.md
D401 entry + index rows, QOL-INVENTORY D224 -> DONE (D401),
findings-index.csv regenerated.
2026-09-27 23:23:05 -04:00

11 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).
  • Reference docs: docs/internals.md — architecture, GE-specific RSP deltas, phased plan (§1–§10). docs/dev/findings.md — the Dxx finding log (§F + §H). Look up findings via docs/dev/findings-index.csv (label, one-liner, status; regenerate with tools_pc/gen_findings_index.py), then read only the specific ## Dxx entry (multi-pass labels like D202/D176(a) have large sections — grep within the section or read with offset/limit, don't slurp it whole). Never linear-read. docs/porting-notes.md — the recurring N64→PC bug classes (dense; skim the headers, read what's relevant).
  • Current status: the README "Status" section, and docs/dev/LEVEL-STATUS.md for the per-level sweep. Current task + environment: docs/HANDOFF.md (a rolling local working file — may be absent in a fresh clone; fall back to the README "Status" section).
  • Dispatching subagents? docs/dev-process.md — task budgets/deadlines, file partitioning, pre-flight, the standard brief template. Every investigation subagent reads docs/porting-notes.md first and appends to it.

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 and check the exception below before assuming the fix belongs in port/. Diagnosis is never restricted — only the fix. Trace a bug as deep into src/game state as the evidence leads, and name the exact struct/field/logic responsible, before deciding which of three buckets it falls in (full framing: docs/dev-process.md). Narrow exception (ABI/layout only): the 32→64-bit pointer-width transition forces a small class of mechanical, semantics-preserving edits that cannot be isolated in port/ — any struct-layout or pointer-width-driven misread caused by 32→64-bit widening, whether in a ROM-serialized record (a struct with a 32-bit-pointer field misaligns when read as 64-bit) or a live runtime struct/union (a raw-byte offset alias into a union arm whose true field shifted because an earlier member in the same union widened — the D209/D210/D255 pattern; see docs/porting-notes.md §A1 for the full catalogue and its diagnostic tells). These follow the PD ground-truth pattern (store the embedded address as u32 and cast to a real pointer at the use site; or read the correctly-named/typed field instead of a raw-offset/mistyped alias), change no logic or behavior, and are each documented in docs/dev/findings.md §F/D3x, cross-tagged to §A1 where that pattern applies. A genuine behavioral difference — the decomp's byte-identical code, given verified-correct inputs, still diverging from real N64 behavior — is not covered by this exception and requires the rule-2 sign-off procedure (docs/dev-process.md) before any src/game edit. 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/internals.md Architecture + RSP deltas + phased plan (§1–§10). Reference, not a linear read.
docs/dev/findings.md The Dxx finding log (§F/§H); lookups via docs/dev/findings-index.csv.
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.
reference/mouse-injector/README.md Stub only — the vendored GEPD-Edition Mouse Injector source (GPLv2) was removed from the public repo 2026-09-19 (license hygiene: GPL code + prebuilt binary in an MIT project; it was never compiled here). The stub records provenance + how to re-vendor locally (gitignored). The ported mouse-aim model lives in port/src/input.c (D194 lineage); design record: docs/dev/GEPD-INPUT-PLAN.md.
A local Perfect Dark PC port checkout (fgsfdsfgs/perfect_dark) Standing reference — consult it whenever a 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; filemodel.c is the D43 near-analogue) and mixer.c/input.c/fs.c. Port-layer files only; same family ≠ identical format — validate per field. Full audit: docs/internals.md §2.4.

Build

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

Needs CMake + SDL2 + zlib + OpenGL, and must run from the MSYS2 MINGW64 shell (see build-pc.sh header and docs/building.md). ROM goes in ./data/ (not distributed); assets must be extracted from it first (docs/building.md).

Windows build environment (three recurring failure modes — diagnose in this order):

  1. Cannot create temporary file in C:\Windows\: Permission denied at the link step. The PE toolchain (ninja → cmd → gcc/ld) needs a writable TMP/TEMP; the msys→native env conversion drops it in non-login shells (agent harnesses; the C:/msys64 tree here was built for D:/M/msys64, so its conversion is unreliable). build-pc.sh now self-heals: it probes a native child's TMP (via a file — piped cmd.exe stdout is unreliable under msys console emulation) and, if broken, re-runs cmake+build under PowerShell with TMP/TEMP set natively (the native→native boundary passes env through intact; only msys→native is broken). If you run cmake/ninja by hand from a broken shell: run them from cmd/PowerShell with C:\msys64\mingw64\bin on PATH. Guard revision (2026-09-28): the re-exec writes a self-contained .ps1 to a temp file and runs powershell -File with plain path/word args only (the msys→native argv conversion mangles $-bearing -Command strings; a file write is conversion-free). Inside: PATH is built explicitly (never trust the inherited value); TMP is chosen from writable candidates (%LocalAppData%\Temp first — [System.IO.Path]::GetTempPath() honours the inherited broken TMP, observed as C:\Windows\); every native step self-logs to build-pc/ge007-native-reexec-{diag,cmake,build}.log; a null $LASTEXITCODE ("did not execute") is a distinct failure (exit 2/3) — in PS 5.1 a piped native command errors out non-terminating and never sets it, so an unguarded if ($LASTEXITCODE -ne 0) passes vacuously. In .ps1, -DROMID=$Var is a literal (bare tokens don't expand) — it must be "-DROMID=$Var". Launch powershell plainly — env -i before it breaks the nested cmake launch (verified 2026-09-28). OPEN (next session): the guard still fails inside build-pc.sh (cmake "did not execute": the .ps1 runs, but $LASTEXITCODE stays empty) while the identical standalone invocation succeeds (cmake runs and the build completes — that is how the D401 builds were made). First test: diff the heredoc-generated .ps1 against a hand-extracted copy (suspect: heredoc line-ending/whitespace corruption). Working workaround: extract the heredoc .ps1 and run powershell -NoProfile -ExecutionPolicy Bypass -File <ps1> "C:\msys64\usr\bin" "C:\msys64\mingw64\bin" C:/msys64 <repo-win-path> build-pc <romid>.
  2. cannot open output file ge007.x86_64.exe: Permission denied. A running ge007.x86_64.exe locks the output file (Windows rule; you can't relink over a live PE). Check with Get-Process | Where-Object { $_.ProcessName -like '*ge007*' } and close the game before rebuilding. An agent must not kill the user's game process to make a link succeed.
  3. cc1.exe: ... libmpfr-6.dll: cannot open shared object file. The calling PATH lacks C:\msys64\mingw64\bin (the gcc driver finds cc1 via its own directory, but the child needs the mingw DLL dir on PATH). In an msys shell: export PATH="/c/msys64/mingw64/bin:/c/msys64/usr/bin:$PATH" before building.

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; ./build-pc.sh is the final word.

Run /linkcheck for this sweep. Record new findings in docs/dev/findings.md §F/§H style (next Dxx label after the last used) and add the label to the §F index.

Phase status (summary — see README + docs/dev/findings.md for detail)

  • Phase 0–1.5: done. Build system, boot chain, OS-shim layer, fast3d integration, first frames, full intro rendering.
  • Phase 2 (rendering): in progress. All 20 solo missions (plus the ending-credits sequence, -level_54) load + render + survive an unattended window; front end (menu → mission select → briefing → start) is functional; file-backed EEPROM saves work. Cosmetic defects are parked in docs/dev/GRAPHICS-BACKLOG.md.
  • Phase 3 (audio + input): input layer done (port/src/input.c); polish bugs open (D118* mouse-look residuals; interactive feel-checks owed). Audio mixer done (libaudio → SDL software mixer, D198–D201); D204 tempo drift fixed + measured; D202 stuck door loop root-caused with a port-side expiration (M-66b) awaiting by-ear verification.
  • Phase 4 (saves + polish): file-backed EEPROM done; widescreen, config, rebinding UI outstanding.