Prepare for public release
- README, LICENSE (MIT for the port layer), NOTICE, CONTRIBUTING. - Restructure docs: internals.md, porting-notes.md, dev-process.md + dev/agentic-development.md (the research write-up), dev/ (raw record). - Replace decomp status-badge CI with a PC-port validate workflow. - Remove session/agent scaffolding and stale planning docs from the tree. - Add .gitattributes (normalize line endings to LF); add screenshots. The pre-2026-08 history is the GoldenEye 007 decompilation by Larry Ficken (KholdFuzion) and contributors, preserved unchanged. The PC-port work from 2026-08-17 on is mine.
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.z64 binary
|
||||||
|
*.exe binary
|
||||||
|
*.bin binary
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
name: EU-Status
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: master
|
|
||||||
paths-ignore: # Don't trigger on files that are updated by the CI or other non-breaking files
|
|
||||||
- 'tools/**'
|
|
||||||
- '*.md'
|
|
||||||
- '.gitignore'
|
|
||||||
- 'CppProperties.json'
|
|
||||||
pull_request:
|
|
||||||
branches: master
|
|
||||||
|
|
||||||
# Allows you to run this workflow manually from the Actions tab
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
buildeu:
|
|
||||||
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
|
|
||||||
- name: Bypass Cloudflare for GitHub Action
|
|
||||||
uses: xiaotianxt/bypass-cloudflare-for-github-action@a87c9ac0348806058904e62e5c8e70d8e37b4a65 # v1.1.1
|
|
||||||
with:
|
|
||||||
cf_zone_id: ${{ secrets.CF_ZONE_ID }}
|
|
||||||
cf_api_token: ${{ secrets.CF_API_TOKEN }}
|
|
||||||
|
|
||||||
- name: Install Depedencies
|
|
||||||
run: |
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install binutils-mips-linux-gnu make
|
|
||||||
wget https://github.com/n64decomp/qemu-irix/releases/download/v2.11-deb/qemu-irix-2.11.0-2169-g32ab296eef_amd64.deb
|
|
||||||
sudo dpkg -i qemu-irix-2.11.0-2169-g32ab296eef_amd64.deb
|
|
||||||
|
|
||||||
- name: Get precompiled recomp to save time
|
|
||||||
env:
|
|
||||||
IDO_RECOMP: ${{ secrets.FILE_RECOMP }}
|
|
||||||
run: |
|
|
||||||
wget -O recomp.zip $IDO_RECOMP
|
|
||||||
unzip -u recomp.zip -d tools/ido5.3_recomp
|
|
||||||
|
|
||||||
- name: Get US File
|
|
||||||
env:
|
|
||||||
FILE_US: ${{ secrets.SECRET_FILE_US }}
|
|
||||||
run: wget -O baserom.u.z64 $FILE_US
|
|
||||||
|
|
||||||
- name: Get EU File
|
|
||||||
env:
|
|
||||||
FILE_EU: ${{ secrets.SECRET_FILE_EU }}
|
|
||||||
run: wget -O baserom.e.z64 $FILE_EU
|
|
||||||
|
|
||||||
- name: Extract US Assets
|
|
||||||
run: ./scripts/extract_baserom.u.sh
|
|
||||||
|
|
||||||
- name: Extract EU Assets
|
|
||||||
run: ./scripts/extract_diff.e.sh
|
|
||||||
|
|
||||||
- name: Make EU
|
|
||||||
run: make all -j IDO_RECOMP=YES VERSION=EU VERBOSE=1
|
|
||||||
|
|
||||||
- name: Make report
|
|
||||||
run: make -C tools/report
|
|
||||||
|
|
||||||
- name: Stats
|
|
||||||
run: python3 tools/ge-stats.py --version eu --report
|
|
||||||
|
|
||||||
- name: Upload output files
|
|
||||||
uses: AXDOOMER/copy_file_to_another_repo_action@e54ae5087212906dac7ef96dee19e31190913257
|
|
||||||
id: upload
|
|
||||||
continue-on-error: true
|
|
||||||
env:
|
|
||||||
API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }}
|
|
||||||
API_TOKEN_USER: ${{ secrets.API_TOKEN_USER }}
|
|
||||||
API_TOKEN_EMAIL: ${{ secrets.API_TOKEN_EMAIL }}
|
|
||||||
with:
|
|
||||||
destination_repo: 'kholdfuzion/goldeneyestatus'
|
|
||||||
user_email: $API_TOKEN_EMAIL
|
|
||||||
user_name: $API_TOKEN_USER
|
|
||||||
destination_branch: 'master'
|
|
||||||
source_file: tools/report/EU.htm
|
|
||||||
|
|
||||||
- name: Check upload
|
|
||||||
if: steps.upload.outcome != 'success'
|
|
||||||
run: echo "::Upload output files::Failed to upload output files. Report and stats were not updated."
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
name: JP-Status
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: master # Only trigger on master
|
|
||||||
paths-ignore: # Don't trigger on files that are updated by the CI or other non-breaking files
|
|
||||||
- 'tools/**'
|
|
||||||
- '*.md'
|
|
||||||
- '.gitignore'
|
|
||||||
- 'CppProperties.json'
|
|
||||||
pull_request:
|
|
||||||
branches: master # Only trigger on master
|
|
||||||
|
|
||||||
# Allows you to run this workflow manually from the Actions tab
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
buildjp:
|
|
||||||
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
|
|
||||||
- name: Bypass Cloudflare for GitHub Action
|
|
||||||
uses: xiaotianxt/bypass-cloudflare-for-github-action@a87c9ac0348806058904e62e5c8e70d8e37b4a65 # v1.1.1
|
|
||||||
with:
|
|
||||||
cf_zone_id: ${{ secrets.CF_ZONE_ID }}
|
|
||||||
cf_api_token: ${{ secrets.CF_API_TOKEN }}
|
|
||||||
|
|
||||||
- name: Install Depedencies
|
|
||||||
run: |
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install binutils-mips-linux-gnu make
|
|
||||||
wget https://github.com/n64decomp/qemu-irix/releases/download/v2.11-deb/qemu-irix-2.11.0-2169-g32ab296eef_amd64.deb
|
|
||||||
sudo dpkg -i qemu-irix-2.11.0-2169-g32ab296eef_amd64.deb
|
|
||||||
|
|
||||||
- name: Get precompiled recomp to save time
|
|
||||||
env:
|
|
||||||
IDO_RECOMP: ${{ secrets.FILE_RECOMP }}
|
|
||||||
run: |
|
|
||||||
wget -O recomp.zip $IDO_RECOMP
|
|
||||||
unzip -u recomp.zip -d tools/ido5.3_recomp
|
|
||||||
|
|
||||||
- name: Get US File
|
|
||||||
env:
|
|
||||||
FILE_US: ${{ secrets.SECRET_FILE_US }}
|
|
||||||
run: wget -O baserom.u.z64 $FILE_US
|
|
||||||
|
|
||||||
- name: Get JP File
|
|
||||||
env:
|
|
||||||
FILE_JP: ${{ secrets.SECRET_FILE_JP }}
|
|
||||||
run: wget -O baserom.j.z64 $FILE_JP
|
|
||||||
|
|
||||||
- name: Extract US Assets
|
|
||||||
run: ./scripts/extract_baserom.u.sh
|
|
||||||
|
|
||||||
- name: Extract JP Assets
|
|
||||||
run: ./scripts/extract_diff.j.sh
|
|
||||||
|
|
||||||
- name: Make JP
|
|
||||||
run: make all -j IDO_RECOMP=YES VERSION=JP VERBOSE=1
|
|
||||||
|
|
||||||
- name: Make report
|
|
||||||
run: make -C tools/report
|
|
||||||
|
|
||||||
- name: Stats
|
|
||||||
run: python3 tools/ge-stats.py --version jp --report
|
|
||||||
|
|
||||||
- name: Upload output files
|
|
||||||
uses: AXDOOMER/copy_file_to_another_repo_action@e54ae5087212906dac7ef96dee19e31190913257
|
|
||||||
id: upload
|
|
||||||
continue-on-error: true
|
|
||||||
env:
|
|
||||||
API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }}
|
|
||||||
API_TOKEN_USER: ${{ secrets.API_TOKEN_USER }}
|
|
||||||
API_TOKEN_EMAIL: ${{ secrets.API_TOKEN_EMAIL }}
|
|
||||||
with:
|
|
||||||
destination_repo: 'kholdfuzion/goldeneyestatus'
|
|
||||||
user_email: $API_TOKEN_EMAIL
|
|
||||||
user_name: $API_TOKEN_USER
|
|
||||||
destination_branch: 'master'
|
|
||||||
source_file: tools/report/JPN.htm
|
|
||||||
|
|
||||||
- name: Check upload
|
|
||||||
if: steps.upload.outcome != 'success'
|
|
||||||
run: echo "::Upload output files::Failed to upload output files. Report and stats were not updated."
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
paths-ignore: [ '**.md', 'docs/**', 'LICENSE', 'NOTICE' ]
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# The full port build needs assets extracted from a copyrighted ROM, which CI
|
||||||
|
# does not have. This job validates everything that does NOT depend on those
|
||||||
|
# assets: the PC-port tooling, the generated ROM-asset symbol files, and a
|
||||||
|
# CMake configure of all three regions (which checks REGION_DEFS, the source
|
||||||
|
# list, and that SDL2 / zlib / OpenGL are discoverable).
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y cmake python3 libsdl2-dev zlib1g-dev libgl1-mesa-dev
|
||||||
|
|
||||||
|
- name: Byte-compile Python tooling
|
||||||
|
run: python3 -m compileall -q scripts tools_pc
|
||||||
|
|
||||||
|
- name: Regenerate ROM-asset symbol files
|
||||||
|
run: |
|
||||||
|
python3 scripts/gen_romassets.py u
|
||||||
|
python3 scripts/gen_romassets.py e
|
||||||
|
python3 scripts/gen_romassets.py j
|
||||||
|
git diff --stat -- port/src/romassets_u.s
|
||||||
|
|
||||||
|
- name: CMake configure (all regions)
|
||||||
|
run: |
|
||||||
|
for r in ntsc-final pal-final jpn-final; do
|
||||||
|
echo "::group::configure $r"
|
||||||
|
cmake -S . -B "build-$r" -DROMID="$r"
|
||||||
|
echo "::endgroup::"
|
||||||
|
done
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
name: NTSC-Status
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: master # Only trigger on master
|
|
||||||
paths-ignore: # Don't trigger on files that are updated by the CI or other non-breaking files
|
|
||||||
- 'tools/**'
|
|
||||||
- '*.md'
|
|
||||||
- '.gitignore'
|
|
||||||
- 'CppProperties.json'
|
|
||||||
pull_request:
|
|
||||||
branches: master # Only trigger on master
|
|
||||||
|
|
||||||
# Allows you to run this workflow manually from the Actions tab
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
buildus:
|
|
||||||
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
|
|
||||||
- name: Bypass Cloudflare for GitHub Action
|
|
||||||
uses: xiaotianxt/bypass-cloudflare-for-github-action@a87c9ac0348806058904e62e5c8e70d8e37b4a65 # v1.1.1
|
|
||||||
with:
|
|
||||||
cf_zone_id: ${{ secrets.CF_ZONE_ID }}
|
|
||||||
cf_api_token: ${{ secrets.CF_API_TOKEN }}
|
|
||||||
|
|
||||||
- name: Install Depedencies
|
|
||||||
run: |
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install binutils-mips-linux-gnu make
|
|
||||||
wget https://github.com/n64decomp/qemu-irix/releases/download/v2.11-deb/qemu-irix-2.11.0-2169-g32ab296eef_amd64.deb
|
|
||||||
sudo dpkg -i qemu-irix-2.11.0-2169-g32ab296eef_amd64.deb
|
|
||||||
|
|
||||||
- name: Get precompiled recomp to save time
|
|
||||||
env:
|
|
||||||
IDO_RECOMP: ${{ secrets.FILE_RECOMP }}
|
|
||||||
run: |
|
|
||||||
wget -O recomp.zip $IDO_RECOMP
|
|
||||||
unzip -u recomp.zip -d tools/ido5.3_recomp
|
|
||||||
|
|
||||||
- name: Get US File
|
|
||||||
env:
|
|
||||||
FILE_US: ${{ secrets.SECRET_FILE_US }}
|
|
||||||
run: wget -O baserom.u.z64 $FILE_US
|
|
||||||
|
|
||||||
- name: Extract US Assets
|
|
||||||
run: ./scripts/extract_baserom.u.sh
|
|
||||||
|
|
||||||
- name: Make US
|
|
||||||
run: make all -j IDO_RECOMP=YES VERSION=US VERBOSE=1
|
|
||||||
|
|
||||||
- name: Make report
|
|
||||||
run: make -C tools/report
|
|
||||||
|
|
||||||
- name: Stats
|
|
||||||
run: python3 tools/ge-stats.py --version us --report
|
|
||||||
|
|
||||||
- name: Upload output files
|
|
||||||
uses: AXDOOMER/copy_file_to_another_repo_action@e54ae5087212906dac7ef96dee19e31190913257
|
|
||||||
id: upload
|
|
||||||
continue-on-error: true
|
|
||||||
env:
|
|
||||||
API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }}
|
|
||||||
API_TOKEN_USER: ${{ secrets.API_TOKEN_USER }}
|
|
||||||
API_TOKEN_EMAIL: ${{ secrets.API_TOKEN_EMAIL }}
|
|
||||||
with:
|
|
||||||
destination_repo: 'kholdfuzion/goldeneyestatus'
|
|
||||||
user_email: $API_TOKEN_EMAIL
|
|
||||||
user_name: $API_TOKEN_USER
|
|
||||||
destination_branch: 'master'
|
|
||||||
source_file: |
|
|
||||||
tools/report/cursor.cur
|
|
||||||
tools/report/index.html
|
|
||||||
tools/report/sfx.ogg
|
|
||||||
tools/report/background.jpg
|
|
||||||
|
|
||||||
- name: Check upload
|
|
||||||
if: steps.upload.outcome != 'success'
|
|
||||||
run: echo "::Upload output files::Failed to upload output files. Report and stats were not updated."
|
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Canonicalize display names (does not rewrite history).
|
||||||
|
# Pre-2026-08 history is the GoldenEye 007 decompilation.
|
||||||
|
Larry Ficken <kholdfuzion@gmail.com> <kholdfuzion@gmail.com>
|
||||||
|
James Dansereau <106631954+jkdansereau@users.noreply.github.com>
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
# 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).
|
|
||||||
- **`docs/PCPortResearch.md` is the reference** — architecture, GE-specific RSP deltas, phased plan (§1–§10), and the Dxx finding log (§F + §H, indexed at the top of §F). Read the section you need; don't linear-read it. Current task lives in `docs/HANDOFF.md`.
|
|
||||||
- **Starting a fresh session?** Read `docs/HANDOFF.md` — the paste-ready brief with the immediate task, standing procedure, and environment gotchas.
|
|
||||||
- **Dispatching subagents?** `docs/AGENT-WORKFLOW.md` — task budgets/deadlines, file partitioning, pre-flight, the standard brief template. Every investigation subagent reads `docs/PORT-LEARNINGS.md` (recurring bug classes) 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 — 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 (§1–§10) + Dxx finding log (§F/§H, indexed at top of §F). Reference, not a linear read. |
|
|
||||||
| `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
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./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` §F/§H style (next Dxx label after the last used) and add the label to the §F index.
|
|
||||||
|
|
||||||
## 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; the **entire
|
|
||||||
intro renders** (logos → gun barrel → cast). BUNKER1 (`-level_09`)
|
|
||||||
**loads, renders recognisably** (textured rooms, skeletal guards) and
|
|
||||||
**survives a firefight** crash-free. Now pivoted **breadth-first** —
|
|
||||||
see `docs/PLAN-linear-level-sweep.md` (the plan of record): make
|
|
||||||
boot→menu→level-select functional, then sweep all 21 solo levels for
|
|
||||||
load+render+no-crash.
|
|
||||||
- **Cross-level blocker:** D88.4 — `Usetup*Z` `propDefs` record stream
|
|
||||||
not byteswapped by `tools_pc/d88_emit.py` (crashes in `setupDoor`).
|
|
||||||
- **Cosmetic defects are parked** in `docs/GRAPHICS-BACKLOG.md`
|
|
||||||
(D75 3D model transforms, D76 disclaimer, D77 audio, D114/D116 text
|
|
||||||
X-mirror). Below crash/level work.
|
|
||||||
- **Recurring sub-task:** ABI/layout fixes per the D32 procedure
|
|
||||||
(§H) as new asset types are touched — the classes are catalogued in
|
|
||||||
`docs/PORT-LEARNINGS.md`.
|
|
||||||
- Full finding history: `docs/PCPortResearch.md` §F + §H (D31–D121,
|
|
||||||
indexed at top of §F); session narrative in `docs/HANDOFF-ARCHIVE.md`.
|
|
||||||
- **Phase 3 (audio + input):** input layer DONE (D118 — `port/src/input.c`, SDL keyboard/mouse/gamepad → N64 pad; polish bugs D118a/b open). Audio remains: libaudio → SDL, copy-and-adapt from PD (`mixer.c` 722 lines vs our 31-line stub; §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.
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
# CLAUDE.md — GoldenEye 007 PC port
|
|
||||||
|
|
||||||
This file auto-loads every session. It is the operating manual. The full
|
|
||||||
rules are in `AGENTS.md` (imported below); this file adds the two
|
|
||||||
checklists that were being skipped.
|
|
||||||
|
|
||||||
@AGENTS.md
|
|
||||||
|
|
||||||
## Doc load order — read deliberately, not everything
|
|
||||||
|
|
||||||
Context is scarce. Load by tier; do not blind-read whole files.
|
|
||||||
|
|
||||||
- **Tier 0 — always (auto-loaded):** `CLAUDE.md`, `AGENTS.md`. Nothing
|
|
||||||
else auto-loads.
|
|
||||||
- **Tier 1 — every session start, read fully (both are short):**
|
|
||||||
`docs/HANDOFF.md` (current state + next task + environment),
|
|
||||||
`docs/PORT-LEARNINGS.md` (recurring bug classes).
|
|
||||||
- **Tier 2 — on demand only, do NOT read start-to-finish:**
|
|
||||||
- `docs/PCPortResearch.md` §1–§10 — architecture / RSP deltas / phased
|
|
||||||
plan. Read the section you need.
|
|
||||||
- `docs/PCPortResearch.md` §F + §H — the Dxx finding log. **Jump to a
|
|
||||||
specific `Dxx` via the index at the top of §F.** Never linear-read.
|
|
||||||
- `docs/HANDOFF-ARCHIVE.md` — prior-session narrative (M-2…). Only when
|
|
||||||
tracing the history of one fix.
|
|
||||||
- `docs/AGENT-WORKFLOW.md` — before dispatching a subagent (also Tier 1
|
|
||||||
if you may dispatch this session).
|
|
||||||
- `docs/PLAN-linear-level-sweep.md` — the active plan of record.
|
|
||||||
|
|
||||||
## Session preflight — do this in your FIRST few tool calls, before acting
|
|
||||||
|
|
||||||
Having a concrete "next step" from the handoff is NOT license to skip this.
|
|
||||||
|
|
||||||
1. Read the Tier 1 docs: `docs/HANDOFF.md` (immediate task, standing
|
|
||||||
procedure, environment gotchas) and `docs/PORT-LEARNINGS.md`
|
|
||||||
(recurring bug classes — you will re-derive catalogued bugs if you don't).
|
|
||||||
2. Skim `docs/PLAN-linear-level-sweep.md` — the active plan of record.
|
|
||||||
3. If you may dispatch a subagent this session, read `docs/AGENT-WORKFLOW.md`
|
|
||||||
NOW — not at dispatch time.
|
|
||||||
|
|
||||||
## Dispatch preflight — EVERY Agent/Task spawn, no exceptions
|
|
||||||
|
|
||||||
A subagent brief is INVALID and must not be sent unless it contains all of:
|
|
||||||
|
|
||||||
- **FILES YOU MAY TOUCH** — disjoint from every other running agent.
|
|
||||||
- **BUDGET** — `<= N build->run->inspect cycles` or `~M min`. Tight for
|
|
||||||
cheap-class bugs (truncations, off-by-ones); more room for structural ones
|
|
||||||
(matrix handedness, format specs). Escalate the default as the project hardens.
|
|
||||||
- **ON EXPIRY** — stop, revert temp probes, write up what you have in
|
|
||||||
`docs/PCPortResearch.md` §F with an explicit confidence rating. A good
|
|
||||||
write-up of a half-solved bug is a deliverable, not a failure.
|
|
||||||
- **KNOWN-GOOD / RULED OUT**, **CONSTRAINTS** (no game-logic changes;
|
|
||||||
ABI/layout/format only; `#ifdef PORT`; documented in §F), **REPORT** shape.
|
|
||||||
- Instruction to read `docs/PORT-LEARNINGS.md` first and append any
|
|
||||||
generalisable quirk to it.
|
|
||||||
|
|
||||||
Full template: `docs/AGENT-WORKFLOW.md` -> "Standard investigation-subagent
|
|
||||||
brief template".
|
|
||||||
|
|
||||||
## Verification cadence (integrator / overseer)
|
|
||||||
|
|
||||||
- **Per merged patch:** single-frame `GE_PCDUMP` capture diffed against the
|
|
||||||
committed golden baseline. Cheap. Do NOT gate every patch on a long soak.
|
|
||||||
- **Per batch / integration checkpoint:** one 60s `-level_09` crash-free run.
|
|
||||||
- Subagents on small disjoint subsystems (input, audio, saves, converters)
|
|
||||||
don't run the game — the overseer verifies.
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
# game C sources (src/game, src) for a modern host (Windows/Linux/macOS)
|
# game C sources (src/game, src) for a modern host (Windows/Linux/macOS)
|
||||||
# against the PC shims in `port/`. The N64 build is untouched.
|
# against the PC shims in `port/`. The N64 build is untouched.
|
||||||
#
|
#
|
||||||
# See docs/PCPortResearch.md for the architecture and plan.
|
# See docs/internals.md for the architecture and plan.
|
||||||
# Modelled on the Perfect Dark PC port (perfect-dark-pc-port/perfect_dark).
|
# Modelled on the Perfect Dark PC port (perfect-dark-pc-port/perfect_dark).
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
@@ -22,7 +22,7 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
|
|||||||
# N64/IDO behaviour the decomp relies on); Clang treats it as a bare forward
|
# N64/IDO behaviour the decomp relies on); Clang treats it as a bare forward
|
||||||
# declaration (no members) and rejects base-member access. So prefer GCC.
|
# declaration (no members) and rejects base-member access. So prefer GCC.
|
||||||
# (GCC does hard-error on the one struct that declares a member with the same
|
# (GCC does hard-error on the one struct that declares a member with the same
|
||||||
# name as an inherited one — CCTVRecord's `pad` — see docs/PCPortResearch.md.)
|
# name as an inherited one — CCTVRecord's `pad` — see docs/internals.md.)
|
||||||
# An explicit -DCMAKE_C_COMPILER=... always wins.
|
# An explicit -DCMAKE_C_COMPILER=... always wins.
|
||||||
if(NOT DEFINED CMAKE_C_COMPILER)
|
if(NOT DEFINED CMAKE_C_COMPILER)
|
||||||
find_program(_GE007_GCC NAMES gcc)
|
find_program(_GE007_GCC NAMES gcc)
|
||||||
@@ -209,7 +209,7 @@ add_compile_options(
|
|||||||
)
|
)
|
||||||
# The decomp was written for IDO's leniency. These downgrade hard errors to
|
# The decomp was written for IDO's leniency. These downgrade hard errors to
|
||||||
# warnings so the unmodified game code compiles; each class is a tracked
|
# warnings so the unmodified game code compiles; each class is a tracked
|
||||||
# decomp finding (see docs/PCPortResearch.md):
|
# decomp finding (see docs/internals.md):
|
||||||
# int-conversion: raw 32-bit ROM address constants assigned to pointers
|
# int-conversion: raw 32-bit ROM address constants assigned to pointers
|
||||||
# (WIP decomp; genuine 64-bit hazards to fix upstream as phases progress)
|
# (WIP decomp; genuine 64-bit hazards to fix upstream as phases progress)
|
||||||
# incompatible-pointer-types: Mtxf union vs float[4][4], Gfx array idioms
|
# incompatible-pointer-types: Mtxf union vs float[4][4], Gfx array idioms
|
||||||
@@ -343,7 +343,7 @@ file(GLOB_RECURSE SRC_GAME "${CMAKE_SOURCE_DIR}/src/game/*.c")
|
|||||||
# the PD port, which swaps its assembly rzip for a real-zlib-backed rzip_c.c.
|
# the PD port, which swaps its assembly rzip for a real-zlib-backed rzip_c.c.
|
||||||
# The only externally-referenced symbols from these two files are
|
# The only externally-referenced symbols from these two files are
|
||||||
# decompressdata() and rzipGetSomething(); both are provided by
|
# decompressdata() and rzipGetSomething(); both are provided by
|
||||||
# port/src/rzdecomp.c (see the symbol sweep in docs/PCPortResearch.md §F).
|
# port/src/rzdecomp.c (see the symbol sweep in docs/internals.md §F).
|
||||||
list(REMOVE_ITEM SRC_GAME
|
list(REMOVE_ITEM SRC_GAME
|
||||||
"${CMAKE_SOURCE_DIR}/src/game/decompress.c"
|
"${CMAKE_SOURCE_DIR}/src/game/decompress.c"
|
||||||
"${CMAKE_SOURCE_DIR}/src/game/zlib.c")
|
"${CMAKE_SOURCE_DIR}/src/game/zlib.c")
|
||||||
@@ -390,8 +390,8 @@ list(PREPEND SRC_LIBULTRARE_LIBC "${CMAKE_SOURCE_DIR}")
|
|||||||
# absolute cart addresses in the generated romassets_<region>.s (see
|
# absolute cart addresses in the generated romassets_<region>.s (see
|
||||||
# scripts/gen_romassets.py), backed by the ROM image that romdata.c maps at
|
# scripts/gen_romassets.py), backed by the ROM image that romdata.c maps at
|
||||||
# 0x10000000. The per-model data itself (chr/*/Model.c etc.) is gitignored
|
# 0x10000000. The per-model data itself (chr/*/Model.c etc.) is gitignored
|
||||||
# ROM-derived and absent from this checkout. See docs/PCPortResearch.md §11.
|
# ROM-derived and absent from this checkout. See docs/internals.md §11.
|
||||||
# D34 (docs/PCPortResearch.md): animationtable_{data,entries}.c are NOT
|
# D34 (docs/internals.md): animationtable_{data,entries}.c are NOT
|
||||||
# compiled on PC. Their arrays exist only so that &ANIM_DATA_x evaluates to
|
# compiled on PC. Their arrays exist only so that &ANIM_DATA_x evaluates to
|
||||||
# the record's offset within the animation_data segment (N64 links it at VMA
|
# the record's offset within the animation_data segment (N64 links it at VMA
|
||||||
# 0); on x86-64 assets/animationtable_data.h redefines them as lvalues at
|
# 0); on x86-64 assets/animationtable_data.h redefines them as lvalues at
|
||||||
@@ -413,7 +413,7 @@ list(PREPEND SRC_ASSETS "${CMAKE_SOURCE_DIR}")
|
|||||||
# init.c — provides mainproc() (the game's real main entry) + the thread
|
# init.c — provides mainproc() (the game's real main entry) + the thread
|
||||||
# setup helpers. Its N64-only init() is compiled but NOT called;
|
# setup helpers. Its N64-only init() is compiled but NOT called;
|
||||||
# the N64 symbols init() references are stubbed in
|
# the N64 symbols init() references are stubbed in
|
||||||
# port/src/n64stubs.c (see A3 in docs/PCPortResearch.md).
|
# port/src/n64stubs.c (see A3 in docs/internals.md).
|
||||||
# cfb.c — defines cfb_16 (the framebuffer), referenced by fr.c and game
|
# cfb.c — defines cfb_16 (the framebuffer), referenced by fr.c and game
|
||||||
# code (bondview2.c, front.c). Pure data.
|
# code (bondview2.c, front.c). Pure data.
|
||||||
# stacks.c — thread-stack arrays (sp_boot/sp_rmon/sp_idle/sp_shed/sp_main/
|
# stacks.c — thread-stack arrays (sp_boot/sp_rmon/sp_idle/sp_shed/sp_main/
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
Thanks for your interest. This is a small hobby project; issues and pull
|
||||||
|
requests are welcome, but please read the ground rules first — they are what
|
||||||
|
keep the port faithful to the original game.
|
||||||
|
|
||||||
|
## Ground rules
|
||||||
|
|
||||||
|
1. **The decompilation is not modified for the port.** Everything under `src/`
|
||||||
|
and `include/` is compiled unmodified. `Makefile`, `tools/`, `rsp/`, `ld/`
|
||||||
|
belong to the N64 build and are never touched for PC work. If PC code seems
|
||||||
|
to need a behavioural change in the game, the fix belongs in `port/`.
|
||||||
|
|
||||||
|
2. **Narrow ABI exception.** The 32->64-bit transition forces a small class of
|
||||||
|
mechanical, semantics-preserving edits to ROM-serialized structs (storing an
|
||||||
|
embedded 32-bit pointer as `u32` and casting at the use site). These are
|
||||||
|
allowed, must be guarded with `#ifdef PORT`, must change no behaviour, and
|
||||||
|
must be documented. See `docs/porting-notes.md` for the catalogue of
|
||||||
|
patterns.
|
||||||
|
|
||||||
|
3. **Region macros mirror the Makefile.** `CMakeLists.txt` `REGION_DEFS` must
|
||||||
|
match the N64 `Makefile`'s per-region macro set exactly.
|
||||||
|
|
||||||
|
4. **Verify before you push.** A build-affecting change needs, at minimum, a
|
||||||
|
clean configure + build for `ntsc-final` and a crash-free run of one level
|
||||||
|
(`./build-pc/ge007.x86_64 -level_09`).
|
||||||
|
|
||||||
|
## Style
|
||||||
|
|
||||||
|
- C/C++ formatting follows `.clang-format` and `.editorconfig` in the repo
|
||||||
|
root. Match the surrounding code.
|
||||||
|
- Port-layer code is plain C11 / C++17, SDL2 + OpenGL, no extra dependencies.
|
||||||
|
- Keep commits focused and describe *why*, not just *what*.
|
||||||
|
|
||||||
|
## Where to look
|
||||||
|
|
||||||
|
- `docs/internals.md` — how the port is structured.
|
||||||
|
- `docs/porting-notes.md` — the recurring bug classes; check here before
|
||||||
|
debugging a crash, you are likely looking at a known pattern.
|
||||||
|
- `docs/dev/` — the full finding log and per-level status.
|
||||||
@@ -1,237 +0,0 @@
|
|||||||
{
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "MIPS2-DEBUG",
|
|
||||||
"configurationType": "Debug",
|
|
||||||
"generator": "Unix Makefiles",
|
|
||||||
"compilerPath": "",
|
|
||||||
"compilerSwitches": "-Wab,-r4300_mul -non_shared -Olimit 2000 -G 0 -Xcpluscomm -mips2 -32",
|
|
||||||
"cStandard": "c89",
|
|
||||||
"includePath": [
|
|
||||||
"\\\\wsl.localhost\\Ubuntu\\usr\\include"
|
|
||||||
/*INCLUDE := -I . -I include -I include/ultra64 -I include/PR -I src -I src/game -I src/inflate*/,
|
|
||||||
"${workspaceRoot}",
|
|
||||||
"${workspaceRoot}\\include",
|
|
||||||
"${workspaceRoot}\\src",
|
|
||||||
"${workspaceRoot}\\src\\game",
|
|
||||||
"${workspaceRoot}\\src\\inflate"
|
|
||||||
],
|
|
||||||
"intelliSenseMode": "linux-gcc-x86",
|
|
||||||
"defines": [
|
|
||||||
"DEBUG",
|
|
||||||
"TRI4_Ext",
|
|
||||||
"VERSION_US",
|
|
||||||
"TARGET_N64",
|
|
||||||
"OUTCODE=u",
|
|
||||||
"LANG_US",
|
|
||||||
"REFRESH_NTSC",
|
|
||||||
"LEFTOVERDEBUG",
|
|
||||||
"LEFTOVERSPECTRUM",
|
|
||||||
"BUGFIX_R0",
|
|
||||||
"BYTEMATCH"
|
|
||||||
/* IDO 5.3 Defs below */,
|
|
||||||
"_MIPS_FPSET=16",
|
|
||||||
"_MIPS_ISA=2",
|
|
||||||
"_ABIO32=1",
|
|
||||||
"_MIPS_SIM=_ABIO32",
|
|
||||||
"_MIPS_SZINT=32",
|
|
||||||
"_MIPS_SZLONG=32",
|
|
||||||
"_MIPS_SZPTR=32",
|
|
||||||
"__EXTENSIONS__",
|
|
||||||
"LANGUAGE_C",
|
|
||||||
"_LANGUAGE_C",
|
|
||||||
"__INLINE_INTRINSICS",
|
|
||||||
"sgi",
|
|
||||||
"__sgi",
|
|
||||||
"unix",
|
|
||||||
"mips",
|
|
||||||
"host_mips",
|
|
||||||
"__unix",
|
|
||||||
"__host_mips",
|
|
||||||
"_SVR4_SOURCE",
|
|
||||||
"_MODERN_C",
|
|
||||||
"_SGI_SOURCE",
|
|
||||||
"__DSO__",
|
|
||||||
"SYSTYPE_SVR4",
|
|
||||||
"_SYSTYPE_SVR4",
|
|
||||||
"_LONGLONG",
|
|
||||||
"__mips=2",
|
|
||||||
"_MIPSEB",
|
|
||||||
"MIPSEB",
|
|
||||||
"__STDC__=1"
|
|
||||||
],
|
|
||||||
"undefines": [
|
|
||||||
"__cplusplus"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "MIPS2-FINAL US",
|
|
||||||
"configurationType": "Release",
|
|
||||||
"generator": "Unix Makefiles",
|
|
||||||
"cStandard": "c89",
|
|
||||||
"includePath": [
|
|
||||||
"\\\\wsl.localhost\\Ubuntu\\usr\\include",
|
|
||||||
"${workspaceRoot}",
|
|
||||||
"${workspaceRoot}\\include",
|
|
||||||
"${workspaceRoot}\\src",
|
|
||||||
"${workspaceRoot}\\src\\game",
|
|
||||||
"${workspaceRoot}\\src\\inflate"
|
|
||||||
],
|
|
||||||
"intelliSenseMode": "linux-gcc-x86",
|
|
||||||
"defines": [
|
|
||||||
"TRI4_Ext",
|
|
||||||
"VERSION_US",
|
|
||||||
"TARGET_N64",
|
|
||||||
"OUTCODE=u",
|
|
||||||
"LANG_US",
|
|
||||||
"REFRESH_NTSC",
|
|
||||||
"LEFTOVERDEBUG",
|
|
||||||
"LEFTOVERSPECTRUM",
|
|
||||||
"BUGFIX_R0",
|
|
||||||
"BYTEMATCH"
|
|
||||||
/* IDO 5.3 Defs below */,
|
|
||||||
"_MIPS_FPSET=16",
|
|
||||||
"_MIPS_ISA=2",
|
|
||||||
"_ABIO32=1",
|
|
||||||
"_MIPS_SIM=_ABIO32",
|
|
||||||
"_MIPS_SZINT=32",
|
|
||||||
"_MIPS_SZLONG=32",
|
|
||||||
"_MIPS_SZPTR=32",
|
|
||||||
"__EXTENSIONS__",
|
|
||||||
"LANGUAGE_C",
|
|
||||||
"_LANGUAGE_C",
|
|
||||||
"__INLINE_INTRINSICS",
|
|
||||||
"sgi",
|
|
||||||
"__sgi",
|
|
||||||
"unix",
|
|
||||||
"mips",
|
|
||||||
"host_mips",
|
|
||||||
"__unix",
|
|
||||||
"__host_mips",
|
|
||||||
"_SVR4_SOURCE",
|
|
||||||
"_MODERN_C",
|
|
||||||
"_SGI_SOURCE",
|
|
||||||
"__DSO__",
|
|
||||||
"SYSTYPE_SVR4",
|
|
||||||
"_SYSTYPE_SVR4",
|
|
||||||
"_LONGLONG",
|
|
||||||
"__mips=2",
|
|
||||||
"_MIPSEB",
|
|
||||||
"MIPSEB",
|
|
||||||
"__STDC__=1"
|
|
||||||
],
|
|
||||||
"undefines": [
|
|
||||||
"__cplusplus"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "LibUltra-Debug",
|
|
||||||
"configurationType": "Release",
|
|
||||||
"generator": "Unix Makefiles",
|
|
||||||
"cStandard": "c89",
|
|
||||||
"includePath": [
|
|
||||||
"\\\\wsl.localhost\\Ubuntu\\usr\\include"
|
|
||||||
/*include -I include/PR -I src/libultra*/,
|
|
||||||
"${workspaceRoot}",
|
|
||||||
"${workspaceRoot}\\include",
|
|
||||||
"${workspaceRoot}\\include\\PR",
|
|
||||||
"${workspaceRoot}\\src\\libultrare",
|
|
||||||
"${workspaceRoot}\\src\\libultra"
|
|
||||||
],
|
|
||||||
"intelliSenseMode": "linux-gcc-x86",
|
|
||||||
"defines": [
|
|
||||||
"_DEBUG",
|
|
||||||
"TRI4_Ext",
|
|
||||||
"VERSION_US",
|
|
||||||
"TARGET_N64"
|
|
||||||
/* IDO 5.3 Defs below */,
|
|
||||||
"_MIPS_FPSET=16",
|
|
||||||
"_MIPS_ISA=2",
|
|
||||||
"_ABIO32=1",
|
|
||||||
"_MIPS_SIM=_ABIO32",
|
|
||||||
"_MIPS_SZINT=32",
|
|
||||||
"_MIPS_SZLONG=32",
|
|
||||||
"_MIPS_SZPTR=32",
|
|
||||||
"__EXTENSIONS__",
|
|
||||||
"LANGUAGE_C",
|
|
||||||
"_LANGUAGE_C",
|
|
||||||
"__INLINE_INTRINSICS",
|
|
||||||
"sgi",
|
|
||||||
"__sgi",
|
|
||||||
"unix",
|
|
||||||
"mips",
|
|
||||||
"host_mips",
|
|
||||||
"__unix",
|
|
||||||
"__host_mips",
|
|
||||||
"_SVR4_SOURCE",
|
|
||||||
"_MODERN_C",
|
|
||||||
"_SGI_SOURCE",
|
|
||||||
"__DSO__",
|
|
||||||
"SYSTYPE_SVR4",
|
|
||||||
"_SYSTYPE_SVR4",
|
|
||||||
"_LONGLONG",
|
|
||||||
"__mips=2",
|
|
||||||
"_MIPSEB",
|
|
||||||
"MIPSEB",
|
|
||||||
"__STDC__=1"
|
|
||||||
],
|
|
||||||
"undefines": [
|
|
||||||
"__cplusplus"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "LibUltra-FINAL",
|
|
||||||
"configurationType": "Release",
|
|
||||||
"generator": "Unix Makefiles",
|
|
||||||
"cStandard": "c89",
|
|
||||||
"includePath": [
|
|
||||||
"\\\\wsl.localhost\\Ubuntu\\usr\\include",
|
|
||||||
"${workspaceRoot}",
|
|
||||||
"${workspaceRoot}\\include",
|
|
||||||
"${workspaceRoot}\\include\\PR",
|
|
||||||
"${workspaceRoot}\\src\\libultrare",
|
|
||||||
"${workspaceRoot}\\src\\libultra"
|
|
||||||
],
|
|
||||||
"intelliSenseMode": "linux-gcc-x86",
|
|
||||||
"defines": [
|
|
||||||
"_FINALROM",
|
|
||||||
"TRI4_Ext",
|
|
||||||
"VERSION_US",
|
|
||||||
"TARGET_N64"
|
|
||||||
/* IDO 5.3 Defs below */,
|
|
||||||
"_MIPS_FPSET=16",
|
|
||||||
"_MIPS_ISA=2",
|
|
||||||
"_ABIO32=1",
|
|
||||||
"_MIPS_SIM=_ABIO32",
|
|
||||||
"_MIPS_SZINT=32",
|
|
||||||
"_MIPS_SZLONG=32",
|
|
||||||
"_MIPS_SZPTR=32",
|
|
||||||
"__EXTENSIONS__",
|
|
||||||
"LANGUAGE_C",
|
|
||||||
"_LANGUAGE_C",
|
|
||||||
"__INLINE_INTRINSICS",
|
|
||||||
"sgi",
|
|
||||||
"__sgi",
|
|
||||||
"unix",
|
|
||||||
"mips",
|
|
||||||
"host_mips",
|
|
||||||
"__unix",
|
|
||||||
"__host_mips",
|
|
||||||
"_SVR4_SOURCE",
|
|
||||||
"_MODERN_C",
|
|
||||||
"_SGI_SOURCE",
|
|
||||||
"__DSO__",
|
|
||||||
"SYSTYPE_SVR4",
|
|
||||||
"_SYSTYPE_SVR4",
|
|
||||||
"_LONGLONG",
|
|
||||||
"__mips=2",
|
|
||||||
"_MIPSEB",
|
|
||||||
"MIPSEB",
|
|
||||||
"__STDC__=1"
|
|
||||||
],
|
|
||||||
"undefines": [
|
|
||||||
"__cplusplus"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 James Dansereau
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
GoldenEye 007 PC port
|
||||||
|
=====================
|
||||||
|
|
||||||
|
WHAT IS AND ISN'T COVERED BY THIS PROJECT'S LICENSE
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
The MIT License in the LICENSE file covers the original work in this
|
||||||
|
repository:
|
||||||
|
|
||||||
|
* the port layer port/
|
||||||
|
* the PC build system CMakeLists.txt, cmake/, build-pc.sh
|
||||||
|
* PC-port tooling tools_pc/
|
||||||
|
* documentation docs/, README.md
|
||||||
|
|
||||||
|
Third-party code vendored into the port layer keeps its own license:
|
||||||
|
|
||||||
|
* port/fast3d/ - software RSP adapted from the Perfect Dark port /
|
||||||
|
libultraship / sm64-port lineage; see port/fast3d/LICENSE.txt.
|
||||||
|
* port/fast3d/glad/ - generated OpenGL loader (glad), public domain / MIT.
|
||||||
|
|
||||||
|
It does NOT cover:
|
||||||
|
|
||||||
|
* src/, include/, assets/, Makefile, tools/, rsp/, ld/, and the other
|
||||||
|
files inherited from the GoldenEye 007 decompilation
|
||||||
|
(https://github.com/n64decomp/007). The upstream project is published
|
||||||
|
without an explicit license; those files are included here on the same
|
||||||
|
basis, unmodified except where a PC-port ABI fix is marked `#ifdef PORT`
|
||||||
|
and documented in docs/dev/findings.md. This repository is a downstream
|
||||||
|
fork of that decompilation with a PC port added on top.
|
||||||
|
|
||||||
|
As is standard for console decompilation projects, some game data is
|
||||||
|
present as re-expressed source (animation tables, display lists, data
|
||||||
|
tables) inherited verbatim from the upstream repository. The bulk game
|
||||||
|
assets -- textures, audio, models, level geometry, in-game text -- are
|
||||||
|
NOT included and are extracted from your own ROM at build time.
|
||||||
|
|
||||||
|
NO NINTENDO / RARE CONTENT IS DISTRIBUTED HERE
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
This repository contains NO copyrighted ROM data, game assets (textures,
|
||||||
|
models, levels, audio, text), or Nintendo/Rare code. Building a playable
|
||||||
|
executable requires a GoldenEye 007 Nintendo 64 ROM that you legally own;
|
||||||
|
the build extracts the required assets from that ROM on your own machine.
|
||||||
|
|
||||||
|
GoldenEye 007 is a trademark of its respective rights holders. This project
|
||||||
|
is a non-commercial, fan-made preservation and porting effort, and is not
|
||||||
|
affiliated with or endorsed by Nintendo, Rare, MGM, EON Productions, Danjaq,
|
||||||
|
or any other rights holder.
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# GoldenEye 007 — PC Port
|
|
||||||
|
|
||||||
A work-in-progress port of [GoldenEye 007](https://en.wikipedia.org/wiki/GoldenEye_007)
|
|
||||||
to modern platforms (Windows / Linux / macOS), built on the decompilation in
|
|
||||||
this repository.
|
|
||||||
|
|
||||||
It follows the approach of the
|
|
||||||
[Perfect Dark PC port](https://github.com/perfect-dark-pc-port/perfect_dark),
|
|
||||||
which ports the same Rare "Indy" engine (a later revision).
|
|
||||||
|
|
||||||
> **Status: Phase 2 (rendering) in progress.** The game boots on PC, plays the
|
|
||||||
> intro music, and renders the entire intro (logo → gun barrel → cast);
|
|
||||||
> the current blocker is first stage load (BG-file endianness, finding D69).
|
|
||||||
> Current session state and immediate tasks: **[docs/HANDOFF.md](docs/HANDOFF.md)**.
|
|
||||||
|
|
||||||
## Read this first
|
|
||||||
|
|
||||||
* **[docs/PCPortResearch.md](docs/PCPortResearch.md)** — the preliminary
|
|
||||||
research: how the PD port works, what GE-specific work remains (the `G_SETTEX`
|
|
||||||
RSP command, custom color-combiner/render modes), the hardware surfaces that
|
|
||||||
must be shimmed, and the phased plan. **Start here.**
|
|
||||||
* **[port/README.md](port/README.md)** — the port layer layout and how it fits
|
|
||||||
together.
|
|
||||||
|
|
||||||
## How it works (one paragraph)
|
|
||||||
|
|
||||||
The N64 game code (`src/game`, `src`) is compiled **unmodified** for the host.
|
|
||||||
The **RSP (the N64's 3D coprocessor) is emulated in software**
|
|
||||||
(`port/fast3d/`): it interprets the display list the game builds and emits
|
|
||||||
OpenGL calls, bypassing the RDP. All other N64 hardware (VI, AI, PI, SI, OS
|
|
||||||
threads/timers) is shimmed in `port/src/libultra.c` and friends. Saves
|
|
||||||
(Memory Pak / EEPROM) are backed by files.
|
|
||||||
|
|
||||||
## Layout
|
|
||||||
|
|
||||||
```
|
|
||||||
CMakeLists.txt # PC build (parallel to the N64 Makefile)
|
|
||||||
cmake/ # CMake helper modules
|
|
||||||
build-pc.sh # configure + build helper
|
|
||||||
port/
|
|
||||||
├── fast3d/ # software RSP (adapted from the PD port)
|
|
||||||
├── include/ # port-facing headers
|
|
||||||
└── src/ # port layer (main, libultra shims, video, audio, input, ...)
|
|
||||||
docs/PCPortResearch.md
|
|
||||||
```
|
|
||||||
|
|
||||||
The N64 build (`Makefile`, `tools/`, `rsp/`, etc.) is **untouched**.
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./build-pc.sh ntsc-final # or pal-final / jpn-final
|
|
||||||
```
|
|
||||||
|
|
||||||
Then put your ROM in `./data/` and run the binary from the repo root.
|
|
||||||
(The ROM is not distributed; you must own the game.)
|
|
||||||
|
|
||||||
## Phased plan (summary)
|
|
||||||
|
|
||||||
| Phase | Goal | Status |
|
|
||||||
|-------|------|--------|
|
|
||||||
| 0 | Scaffolding: build system + port-layer interfaces | done |
|
|
||||||
| 1 (+1.5) | Boot to window / first frame: ROM map, OS shims, ABI reconciliation | done |
|
|
||||||
| 2 | Rendering: fast3d software RSP, asset conversion/fixups | **in progress** (full intro renders; stage load = D69) |
|
|
||||||
| 3 | Audio + input (SDL device, mixer, gamepad) | not started |
|
|
||||||
| 4 | Saves + polish (file-backed EEPROM, widescreen, config) | not started |
|
|
||||||
|
|
||||||
Details and risks: [docs/PCPortResearch.md §8–9](docs/PCPortResearch.md).
|
|
||||||
@@ -0,0 +1,320 @@
|
|||||||
|
# GoldenEye 007 — PC port
|
||||||
|
|
||||||
|
A work-in-progress native port of **GoldenEye 007** (Rare, 1997) to modern
|
||||||
|
desktop platforms, built on top of the
|
||||||
|
[GoldenEye decompilation](https://github.com/n64decomp/007).
|
||||||
|
|
||||||
|
It follows the architecture of the
|
||||||
|
[Perfect Dark PC port](https://github.com/fgsfdsfgs/perfect_dark) — the same
|
||||||
|
Rare "Indy" engine family, one hardware generation apart. The unmodified game C
|
||||||
|
sources are compiled for the host; the N64's Reality Signal Processor is
|
||||||
|
emulated in software; every other hardware surface (video, audio, input,
|
||||||
|
timers, save storage) is shimmed in a dedicated `port/` layer.
|
||||||
|
|
||||||
|
> **You must supply your own GoldenEye 007 ROM.** This repository contains no
|
||||||
|
> Nintendo code or assets, and no ROM. Nothing here is distributable as a
|
||||||
|
> playable game — see [Requirements](#requirements) and [Legal](#legal).
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="docs/img/archives-1.png" width="49%" alt="Archives level rendering in the port">
|
||||||
|
<img src="docs/img/bunker1-1.png" width="49%" alt="Bunker 1 rendering in the port">
|
||||||
|
<br>
|
||||||
|
<img src="docs/img/archives-2.png" width="32%" alt="Guards in the Archives level">
|
||||||
|
<img src="docs/img/bunker1-2.png" width="32%" alt="Bunker 1 with HUD">
|
||||||
|
<img src="docs/img/bunker1-3.png" width="32%" alt="Bunker 1 storage room">
|
||||||
|
<br><em>In-engine captures — Archives and Bunker 1 running in the port.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
This port is primarily a **research project on agentic software development** —
|
||||||
|
how far coding agents can be driven, by one person, through a large and
|
||||||
|
unfamiliar low-level codebase (~230 translation units of unmodified
|
||||||
|
big-endian MIPS game code, made to run on a 64-bit desktop).
|
||||||
|
|
||||||
|
It used two agents:
|
||||||
|
|
||||||
|
- a **local open-weight model** —
|
||||||
|
`unsloth/Qwen3.8-27B-GGUF:UD-Q4_K_XL` on a single **RTX 5090**, driven mainly
|
||||||
|
through the **[pi](https://pi.dev/)** coding agent (Unsloth Desktop was also
|
||||||
|
trialed) — which did the groundwork: the CMake build, the boot chain, the
|
||||||
|
software-RSP integration, the offline asset-conversion pipeline, and the
|
||||||
|
first rendered frames;
|
||||||
|
- **Claude** (via **Claude Code** — mostly **Sonnet 5**, with **Opus 5** as an
|
||||||
|
escalation tier for the hardest bugs), which joined on 27 Aug 2026 for a
|
||||||
|
collaborative phase: the 21-level load/render/no-crash sweep, the ABI/layout
|
||||||
|
finding catalogue, the SDL input layer, and the front-end flow.
|
||||||
|
|
||||||
|
The two **handed work back and forth** through shared written artifacts:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
H["human<br/>direction · integration<br/>build + playtest"]
|
||||||
|
C["Claude<br/>(Claude Code)"]
|
||||||
|
Q["Qwen 3.8<br/>(pi, local RTX 5090)"]
|
||||||
|
D[("shared artifacts<br/>finding log · bug-class notes · handoff doc")]
|
||||||
|
H -->|"scoped task + budget"| C
|
||||||
|
H -->|"scoped task + budget"| Q
|
||||||
|
C <-->|read / append| D
|
||||||
|
Q <-->|read / append| D
|
||||||
|
C -.->|"hit usage limit ->"| Q
|
||||||
|
Q -.->|"needs deeper reasoning ->"| C
|
||||||
|
C -->|"patch + write-up"| H
|
||||||
|
Q -->|"patch + write-up"| H
|
||||||
|
```
|
||||||
|
|
||||||
|
The handoff document was originally a session-to-session note; it became the
|
||||||
|
**interface between the two models** — when Claude hit a usage limit
|
||||||
|
mid-problem, the local model picked the task up from that state and continued.
|
||||||
|
|
||||||
|
**By the numbers:** ~2 weeks, one person part-time · 217 commits · 160
|
||||||
|
root-caused bugs logged · ~30 handoff sessions · `#ifdef PORT` ABI edits in 63
|
||||||
|
game-source files · ~17k lines of port layer + ~6k lines of Python asset
|
||||||
|
tooling. By day 4 the full ~230-unit codebase compiled and linked; by day 8
|
||||||
|
the intro rendered; by **day 13 all 21 solo missions ran crash-free**; by
|
||||||
|
day 14 the front end was playable end to end. Estimated effort split
|
||||||
|
(milestone-weighted): **~60% Claude / ~40% local model** — the local model
|
||||||
|
built the entire foundation (build, boot, RSP wiring, converters).
|
||||||
|
|
||||||
|
Full write-up, timeline chart, and an honest "what worked / what didn't":
|
||||||
|
[`docs/dev/agentic-development.md`](docs/dev/agentic-development.md). The
|
||||||
|
workflow itself: [`docs/dev-process.md`](docs/dev-process.md).
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
**Phase 2 of 4 (rendering).** The port boots, renders, and is playable through
|
||||||
|
the front end into the early game. It is not finished and it is not stable.
|
||||||
|
|
||||||
|
**Working**
|
||||||
|
|
||||||
|
- Boot -> Rare/Nintendo logos -> gun-barrel -> cast intro, fully rendered.
|
||||||
|
- Front end: main menu -> mission select -> difficulty -> briefing -> mission start.
|
||||||
|
- **All 21 solo missions load, render, and survive an unattended play window
|
||||||
|
without crashing** (see [`docs/dev/LEVEL-STATUS.md`](docs/dev/LEVEL-STATUS.md)).
|
||||||
|
- Software RSP (fast3d): textured world geometry, skeletal characters, HUD,
|
||||||
|
the GE-specific color-combiner / render modes and `G_TRI4`.
|
||||||
|
- Input: keyboard + mouse (with mode-aware mouse-look) and SDL game
|
||||||
|
controllers, mapped onto the N64 pad. Tunable via `ge007.ini`.
|
||||||
|
- File-backed EEPROM saves.
|
||||||
|
|
||||||
|
**Not yet working**
|
||||||
|
|
||||||
|
- **Audio** — not implemented (Phase 3). The game runs silent.
|
||||||
|
- Front-end 3D models (spinning Nintendo logo, gun-barrel Bond, cast) are
|
||||||
|
mispositioned or absent.
|
||||||
|
- Assorted cosmetic defects (some text layout, a few incomplete asset
|
||||||
|
conversions) are tracked in
|
||||||
|
[`docs/dev/GRAPHICS-BACKLOG.md`](docs/dev/GRAPHICS-BACKLOG.md) and parked
|
||||||
|
below crash/level work.
|
||||||
|
- Only `x86_64` Windows and Linux are exercised regularly. No macOS/ARM
|
||||||
|
testing yet; no controller rebinding UI; no widescreen.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
You need a **GoldenEye 007 (Nintendo 64) ROM** that you legally own, in
|
||||||
|
big-endian (`.z64`) format, matching one of:
|
||||||
|
|
||||||
|
| Region | ROMID | ROM filename (in `data/`) | SHA-1 |
|
||||||
|
|--------|-------|---------------------------|-------|
|
||||||
|
| NTSC-U (US) | `ntsc-final` | `ge007.ntsc-final.z64` | `abe01e4aeb033b6c0836819f549c791b26cfde83` |
|
||||||
|
| PAL (EU) | `pal-final` | `ge007.pal-final.z64` | `167c3c433dec1f1eb921736f7d53fac8cb45ee31` |
|
||||||
|
| NTSC-J (JP) | `jpn-final` | `ge007.jpn-final.z64` | `2a5dade32f7fad6c73c659d2026994632c1b3174` |
|
||||||
|
|
||||||
|
US is the recommended and best-tested version.
|
||||||
|
|
||||||
|
The port also relies on the decompilation's asset-extraction step, which pulls
|
||||||
|
the level, model, texture and music data out of your ROM at build time. That
|
||||||
|
step, too, requires your ROM and is part of [Building](#building).
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Prerequisites: CMake >= 3.16, a C/C++ toolchain, SDL2, zlib, OpenGL, Python 3,
|
||||||
|
plus the decompilation's own build dependencies (an IRIX MIPS toolchain via
|
||||||
|
`qemu-irix`, used only for the one-time asset extraction). See
|
||||||
|
[`docs/building.md`](docs/building.md) for the full walkthrough and the
|
||||||
|
asset-extraction details.
|
||||||
|
|
||||||
|
### Windows (MSYS2)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# in the MINGW64 shell
|
||||||
|
pacman -S mingw-w64-x86_64-toolchain mingw-w64-x86_64-SDL2 \
|
||||||
|
mingw-w64-x86_64-zlib mingw-w64-x86_64-cmake \
|
||||||
|
mingw-w64-x86_64-python make git
|
||||||
|
|
||||||
|
git clone https://github.com/jkdansereau/goldeneye-pc-port.git
|
||||||
|
cd goldeneye-pc-port
|
||||||
|
# 1. extract assets from your ROM (see docs/building.md)
|
||||||
|
# 2. build the port
|
||||||
|
./build-pc.sh ntsc-final # or pal-final / jpn-final
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt install build-essential cmake python3 libsdl2-dev zlib1g-dev libgl1-mesa-dev
|
||||||
|
git clone https://github.com/jkdansereau/goldeneye-pc-port.git
|
||||||
|
cd goldeneye-pc-port
|
||||||
|
# extract assets (docs/building.md), then:
|
||||||
|
./build-pc.sh ntsc-final
|
||||||
|
```
|
||||||
|
|
||||||
|
The executable is written to `build-pc/ge007.x86_64` (Windows: `.exe`).
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
1. Create a `data/` directory in the repo root.
|
||||||
|
2. Put your ROM in it, named as in the table above
|
||||||
|
(e.g. `data/ge007.ntsc-final.z64`).
|
||||||
|
3. Run the executable **from the repo root**:
|
||||||
|
`./build-pc/ge007.x86_64`.
|
||||||
|
|
||||||
|
Configuration is written to `ge007.ini` on first run.
|
||||||
|
|
||||||
|
### Default controls
|
||||||
|
|
||||||
|
| Action | Keyboard / mouse | Controller |
|
||||||
|
|-------------------|-------------------------|-----------------------|
|
||||||
|
| Move / strafe | `W` `A` `S` `D` / arrows | Left stick |
|
||||||
|
| Aim / look | Mouse | Right stick |
|
||||||
|
| Fire (Z) | Left mouse / `LCtrl` | Right trigger |
|
||||||
|
| Aim mode (R) | Right mouse / `LShift` | Left trigger |
|
||||||
|
| Use / accept (A) | `Space` / `E` | A / X |
|
||||||
|
| Reload / cancel (B)| `R` / `F` | B / Y / RB |
|
||||||
|
| L trigger | `Q` | LB |
|
||||||
|
| Start | `Enter` / `Tab` | Start |
|
||||||
|
|
||||||
|
Mouse sensitivity, Y-inversion and the aim/turn split are tunable in the
|
||||||
|
`[Input]` section of `ge007.ini`.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
The R4300 game code in `src/` is compiled **completely unmodified** — the
|
||||||
|
decompilation's control flow is treated as ground truth. Everything that would
|
||||||
|
touch N64 hardware is redirected into `port/`:
|
||||||
|
|
||||||
|
- **`port/fast3d/`** — a software RSP. It interprets the GBI display list the
|
||||||
|
game builds each frame and emits OpenGL, bypassing the RDP. Adapted from the
|
||||||
|
Perfect Dark port and extended for GoldenEye's custom GBI.
|
||||||
|
- **`port/src/gesched.c`** — replaces the RCP scheduler; instead of poking RSP
|
||||||
|
registers it drives the software RSP directly.
|
||||||
|
- **`port/src/libultra.c`** — single-threaded shims for the libultra OS API
|
||||||
|
(threads, messages, timers, PI/SI/AI/VI) over the host.
|
||||||
|
- **`port/src/{video,audio,input,fs,romdata,config}.c`** — the SDL2 / OpenGL /
|
||||||
|
filesystem backends.
|
||||||
|
|
||||||
|
The 32->64-bit transition forces a small, catalogued class of mechanical
|
||||||
|
ABI-only edits to ROM-serialized structs (pointer-width reconciliation); these
|
||||||
|
change no behaviour and are documented individually.
|
||||||
|
|
||||||
|
Where it diverges from the Perfect Dark port: GoldenEye's N64 serialized asset
|
||||||
|
formats (level setup, models, backgrounds) are converted **offline** by a set
|
||||||
|
of Python "sidecar" converters in `tools_pc/`, rather than fixed up at load
|
||||||
|
time. See [`docs/internals.md`](docs/internals.md) and
|
||||||
|
[`docs/porting-notes.md`](docs/porting-notes.md).
|
||||||
|
|
||||||
|
## Project layout
|
||||||
|
|
||||||
|
```
|
||||||
|
CMakeLists.txt PC build (parallel to the decomp's Makefile, which is untouched)
|
||||||
|
build-pc.sh configure + build helper
|
||||||
|
src/ include/ the decompilation (game + libultra) - compiled unmodified
|
||||||
|
port/
|
||||||
|
fast3d/ software RSP -> OpenGL
|
||||||
|
src/ port layer (main, OS shims, video, audio, input, fs, ...)
|
||||||
|
include/ port-facing headers
|
||||||
|
tools/ Makefile the N64 build + asset extraction (from the decomp; do not modify)
|
||||||
|
tools_pc/ PC-port helper + analysis scripts
|
||||||
|
docs/ see below
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [`docs/building.md`](docs/building.md) — full build + asset-extraction guide.
|
||||||
|
- [`docs/internals.md`](docs/internals.md) — architecture, the RSP-emulation
|
||||||
|
approach, GE-vs-PD engine differences, the phased plan.
|
||||||
|
- [`docs/porting-notes.md`](docs/porting-notes.md) — the recurring N64->PC bug
|
||||||
|
classes hit during the port, with fixes.
|
||||||
|
- [`docs/dev/agentic-development.md`](docs/dev/agentic-development.md) — the
|
||||||
|
research angle: the two-agent setup, timeline, handoff workflow, and a
|
||||||
|
candid assessment of what did and didn't work.
|
||||||
|
- [`docs/dev-process.md`](docs/dev-process.md) — the investigation workflow in
|
||||||
|
detail (budgets, file partitioning, the finding-log discipline).
|
||||||
|
- [`docs/dev/`](docs/dev/) — the raw engineering record: the full finding log,
|
||||||
|
per-level status, graphics backlog, playtest matrices.
|
||||||
|
- [`docs/SetupGuide.md`](docs/SetupGuide.md),
|
||||||
|
[`docs/StructureGuide.md`](docs/StructureGuide.md),
|
||||||
|
[`docs/StyleGuide.md`](docs/StyleGuide.md) — inherited from the decompilation.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
This port is a thin layer on a large amount of other people's work.
|
||||||
|
|
||||||
|
**Prior work it is built on**
|
||||||
|
|
||||||
|
- The [GoldenEye 007 decompilation](https://github.com/n64decomp/007) — years of
|
||||||
|
effort by kholdfuzion, Larry Ficken, and the project's contributors; plus
|
||||||
|
zoinkity's GoldenEye documentation, which the decomp started from. This port
|
||||||
|
is a fork of that repository.
|
||||||
|
- The [Perfect Dark PC port](https://github.com/fgsfdsfgs/perfect_dark)
|
||||||
|
(Ryan Dwyer and contributors) — the reference architecture for this port and
|
||||||
|
the source of the `fast3d` software RSP.
|
||||||
|
- The [Perfect Dark decompilation](https://github.com/n64decomp/perfect_dark) —
|
||||||
|
the sibling decomp the PD port is built on.
|
||||||
|
|
||||||
|
**Vendored / adapted code**
|
||||||
|
|
||||||
|
- `port/fast3d/` — the software RSP, adapted from the PD port. It originates
|
||||||
|
with the [Ship of Harkinian](https://github.com/HarbourMasters) /
|
||||||
|
libultraship fast3d (© Emill, MaikelChan; MIT — see `port/fast3d/LICENSE.txt`),
|
||||||
|
which itself descends from
|
||||||
|
[sm64-port](https://github.com/sm64-port/sm64-port)'s fast3d and audio mixer.
|
||||||
|
- `port/fast3d/glad/` — OpenGL loader generated by
|
||||||
|
[glad](https://github.com/Dav1dde/glad) (David Herberth, MIT).
|
||||||
|
- The decompilation toolchain: [`ido-static-recomp`](https://github.com/decompals/ido-static-recomp)
|
||||||
|
(Emill / decompals), [`qemu-irix`](https://github.com/n64decomp/qemu-irix) and
|
||||||
|
`rabbitizer` (n64decomp).
|
||||||
|
- [SDL2](https://libsdl.org) and [zlib](https://zlib.net).
|
||||||
|
|
||||||
|
**Methodology**
|
||||||
|
|
||||||
|
- Chris Lewis, [*"Decompiling a Nintendo 64 Game in 84 Days"*](https://blog.chrislewis.au/decompiling-a-nintendo-64-game-in-84-days/) (the Snowboard
|
||||||
|
Kids decompilation write-up) — the agent-workflow practices in
|
||||||
|
[`docs/dev-process.md`](docs/dev-process.md) are adapted from it.
|
||||||
|
|
||||||
|
**Tools and models used to develop the port**
|
||||||
|
|
||||||
|
- [Qwen 3.8](https://github.com/QwenLM/Qwen) (Alibaba Qwen team);
|
||||||
|
[Unsloth](https://unsloth.ai) (the GGUF quantisation and Unsloth Desktop).
|
||||||
|
- [pi](https://pi.dev/) — the local coding-agent harness.
|
||||||
|
- [Claude / Claude Code](https://claude.com/claude-code) (Anthropic).
|
||||||
|
|
||||||
|
## Legal
|
||||||
|
|
||||||
|
This is a non-commercial fan preservation/research project, in the same
|
||||||
|
category as the many other N64 decompilation and native-port repositories on
|
||||||
|
GitHub. It follows the same conventions they do:
|
||||||
|
|
||||||
|
- **No ROM and no bulk game assets are distributed.** Textures, audio, models,
|
||||||
|
level data and in-game text are extracted from a ROM *you already own*, on
|
||||||
|
*your* machine, at build time.
|
||||||
|
- The repository is a fork of the public
|
||||||
|
[GoldenEye 007 decompilation](https://github.com/n64decomp/007) and inherits
|
||||||
|
its contents unmodified (see [`NOTICE`](NOTICE) for what that includes).
|
||||||
|
- No official logos, box art, or marketing assets are used. "GoldenEye 007",
|
||||||
|
"007", "James Bond" and related marks belong to their respective owners
|
||||||
|
(Nintendo, Microsoft/Rare, MGM, Danjaq, EON Productions).
|
||||||
|
- No binaries containing game data are distributed. Any build you make is
|
||||||
|
personal to you.
|
||||||
|
|
||||||
|
This project is **not affiliated with, endorsed by, or sponsored by** Nintendo,
|
||||||
|
Rare, Microsoft, MGM, Danjaq, EON Productions, or any rights holder in
|
||||||
|
GoldenEye or James Bond. If you are a rights holder with a concern, open an
|
||||||
|
issue and it will be addressed.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The original work in this repository — the port layer (`port/`), the PC build
|
||||||
|
system, `tools_pc/`, and the documentation — is released under the MIT License,
|
||||||
|
see [`LICENSE`](LICENSE). Everything inherited from the upstream decompilation
|
||||||
|
is covered by [`NOTICE`](NOTICE), not by that license.
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
# Usage:
|
# Usage:
|
||||||
# ./build-pc.sh [ntsc-final|pal-final|jpn-final]
|
# ./build-pc.sh [ntsc-final|pal-final|jpn-final]
|
||||||
#
|
#
|
||||||
# Dependencies (see docs/PCPortResearch.md):
|
# Dependencies (see docs/internals.md):
|
||||||
# - CMake >= 3.16
|
# - CMake >= 3.16
|
||||||
# - SDL2 dev
|
# - SDL2 dev
|
||||||
# - zlib dev
|
# - zlib dev
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
# Agent / subagent workflow for this port
|
|
||||||
|
|
||||||
Guidance distilled from Chris Lewis's *"Decompiling a Nintendo 64 Game
|
|
||||||
in 84 Days"* (`docs/reference/`, Snowboard Kids, 2026) — an AI-assisted
|
|
||||||
N64 decomp run 7× faster than its predecessor — adapted to this project,
|
|
||||||
which is a **PC port on top of a decomp**, not a byte-match decomp. The
|
|
||||||
mechanics differ (we have no permuter / m2c loop; our inner loop is
|
|
||||||
root-cause → narrow `#ifdef PORT` fix → visual verify) but the
|
|
||||||
project-structure lessons transfer directly.
|
|
||||||
|
|
||||||
## 1. Every subagent task gets an explicit budget, stated in the brief
|
|
||||||
|
|
||||||
Their key finding: an open-ended tool (the permuter) that "runs until
|
|
||||||
100% match or manually stopped" wrecks throughput; giving the agent a
|
|
||||||
**deadline it can see** let it trade tool-time against other
|
|
||||||
problem-solving and judge when to give up on a hard function.
|
|
||||||
|
|
||||||
Our open-ended tools are the same shape: rebuild-and-run-the-game cycles,
|
|
||||||
gdb, env-gated probe iteration, staring at PPM dumps. So every subagent
|
|
||||||
brief must state:
|
|
||||||
|
|
||||||
- a **wall/iteration budget** — e.g. "≤ N build→run→inspect cycles" or
|
|
||||||
"~M minutes of investigation"; and
|
|
||||||
- the **fallback on expiry**: *stop, revert probes, write up what you
|
|
||||||
have with a confidence rating* — a good write-up of a half-solved bug
|
|
||||||
is a deliverable, not a failure.
|
|
||||||
|
|
||||||
Escalate the budget as the project hardens: early bugs are cheap
|
|
||||||
(one-line truncations, off-by-ones), so cap tight; once those are gone
|
|
||||||
the survivors are structural (matrix handedness, format specs) and
|
|
||||||
deserve more room. Revisit the default each session.
|
|
||||||
|
|
||||||
## 2. A shared, append-only learnings file that briefs point every agent at
|
|
||||||
|
|
||||||
They had agents record generalisable IDO quirks in
|
|
||||||
`DECOMPILATION_LEARNINGS.md`; later agents read it and got better. "Agents
|
|
||||||
helped document IDO, and the documentation made subsequent agents better."
|
|
||||||
|
|
||||||
Our equivalent already exists but is diffuse: `docs/PCPortResearch.md` §F
|
|
||||||
is 200 KB of narrative. The **recurring PC-port quirk classes** should be
|
|
||||||
distilled into a short cheat-sheet every subagent brief links and every
|
|
||||||
subagent is told to append to:
|
|
||||||
|
|
||||||
- pointer-width struct growth (32→64) in ROM-serialized / pun-allocated
|
|
||||||
structs — the dominant bug class (D53.2, D56, D79, D98–D102)
|
|
||||||
- 16-byte PC `Gfx`/`Vtx` vs 8-byte N64 — reservation & copy sizing
|
|
||||||
(D50.6, D58, D85, D95)
|
|
||||||
- big-endian rodata read on LE: `f32` word-pairs (D73, D112), header
|
|
||||||
offset tables (D68, D87), packed bitfields (D78, D83)
|
|
||||||
- N64 hardware idioms fast3d doesn't emulate (fill-rect Z clear → D105;
|
|
||||||
LOD/detail tiles → D107; K0 segment address folds → D58, D84)
|
|
||||||
- offline sidecar converter preferred over runtime fixup for whole
|
|
||||||
formats (D43, D69, D88)
|
|
||||||
|
|
||||||
This is `docs/PORT-LEARNINGS.md` — one screen, an index into §F, not a
|
|
||||||
replacement. Every investigation-subagent brief links it; every subagent
|
|
||||||
is told to append any new generalisable quirk before reporting.
|
|
||||||
|
|
||||||
## 3. Parallel work: partition by file, consolidate often
|
|
||||||
|
|
||||||
They ran 4 git worktrees sharded by a hash of the candidate list, and
|
|
||||||
found the cost was **synchronisation** — a match in one worktree stayed
|
|
||||||
invisible to the others until merged, and merging all four took >1 h.
|
|
||||||
Their fix: make the similarity search read *all* worktrees so a new
|
|
||||||
reference propagates immediately, and merge to main periodically anyway.
|
|
||||||
|
|
||||||
For us (parallel investigation subagents, not worktrees yet):
|
|
||||||
|
|
||||||
- **Partition tracks by the files they will touch** so patches don't
|
|
||||||
collide. This session: Track 1 = `bg.c`, Track 2 = `chr.c`/`model*.c`,
|
|
||||||
Track 3 = `gunfire.c` + a new audit doc — disjoint, merged clean.
|
|
||||||
A brief that can't be file-partitioned shouldn't be parallelised.
|
|
||||||
- Subagents must **revert their own probes** before reporting (leave only
|
|
||||||
committed, env-gated, capped probes) and must **not** touch another
|
|
||||||
track's uncommitted edits — call them out in the report instead.
|
|
||||||
- The integrator (main session) consolidates to `master` per-track with
|
|
||||||
the §F write-up, then the next round of agents starts from a clean
|
|
||||||
tree. Don't let investigation branches drift.
|
|
||||||
- If we move to real worktrees: `data/` must be **copied, never
|
|
||||||
junctioned** (see the `data-dir-junction-hazard` memory —
|
|
||||||
`git worktree remove --force` follows the junction and deletes the real
|
|
||||||
baserom + sidecars).
|
|
||||||
|
|
||||||
## 4. Cheap automated pass before spending agent tokens
|
|
||||||
|
|
||||||
They scripted `m2c` across every unmatched function first (0.93 % hit
|
|
||||||
rate, but near-free) and told agents to exhaust known SDK
|
|
||||||
versions / compiler flags / `#if` paths before writing original code.
|
|
||||||
|
|
||||||
Our pre-flight before dispatching an investigation agent:
|
|
||||||
|
|
||||||
- `./build-pc.sh` green + `/linkcheck` clean
|
|
||||||
- run the existing env-gated probes for the area (`GE_D96`, `GE_D104`,
|
|
||||||
`GE_D69*`, …) and attach the output to the brief
|
|
||||||
- `GE_PCDUMP` + `tools_pc/pixcount.py` baseline capture
|
|
||||||
- grep `docs/PCPortResearch.md` §F for a prior instance of the bug class
|
|
||||||
- check `PD_PORT_CHECKOUT` for the PD analogue
|
|
||||||
(already a non-negotiable) — the brief should name the file to look at
|
|
||||||
|
|
||||||
Give the agent the results, not just the task. "Give the agent the
|
|
||||||
findings, not just the problem" is the through-line of the whole post.
|
|
||||||
|
|
||||||
## 5. What does *not* transfer
|
|
||||||
|
|
||||||
- **Model choice.** Their (unscientific) take was Codex > Claude for
|
|
||||||
reproducing IDO codegen. Our task is behavioural correctness, not
|
|
||||||
compiler-output matching — not comparable; ignore.
|
|
||||||
- **The permuter / match-percentage loop.** We don't bit-match. Our
|
|
||||||
"done" bar is: no fault + visually correct vs. N64 footage
|
|
||||||
(`docs/reference/n64-footage-*`), measured with `pixcount.py`.
|
|
||||||
- **"Understanding the function is the easy part."** True for decomp,
|
|
||||||
inverted here — our hard part is often *what N64 hardware/ABI behaviour
|
|
||||||
the C is silently relying on*.
|
|
||||||
|
|
||||||
## Standard investigation-subagent brief template
|
|
||||||
|
|
||||||
```
|
|
||||||
Repo: REPO_ROOT (git, master, Windows; PowerShell + Bash).
|
|
||||||
READ FIRST: docs/HANDOFF.md (top), docs/PORT-LEARNINGS.md,
|
|
||||||
docs/PCPortResearch.md §F <specific Dxx entries>, AGENTS.md non-negotiables.
|
|
||||||
TASK: <one bug, one subsystem>.
|
|
||||||
FILES YOU MAY TOUCH: <disjoint from other running agents>.
|
|
||||||
KNOWN-GOOD / RULED OUT: <list — do not re-investigate>.
|
|
||||||
PRE-FLIGHT ATTACHED: <probe output, pixcount baseline, pd_port pointer>.
|
|
||||||
BUDGET: <N build→run cycles / ~M min>. On expiry: revert probes, write up with confidence.
|
|
||||||
CONSTRAINTS: no game-logic changes; ABI/layout/format only, #ifdef PORT, documented in §F.
|
|
||||||
revert your own temp probes; do not touch other agents' uncommitted edits.
|
|
||||||
REPORT: (a) root cause + file:line evidence (b) fix diff or why-not
|
|
||||||
(c) probes left in tree (d) confidence. Append any generalisable
|
|
||||||
quirk to docs/PORT-LEARNINGS.md.
|
|
||||||
```
|
|
||||||
@@ -1,303 +0,0 @@
|
|||||||
All of these items should be completed once the main original game is playable from start to finish on PC with no crashing or major issues.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Playtest defects — logged M-28 (2026-08-30), not yet worked
|
|
||||||
|
|
||||||
User-reported during free playtest. Suggested priority order below (cheap
|
|
||||||
input fixes first, then the texture correctness bug, then the filtering
|
|
||||||
feature).
|
|
||||||
|
|
||||||
- **B1 — texture filtering / "interlaced" shimmer (feature).** Whole game
|
|
||||||
has an interlaced/shimmering look. Want N64 3-point-filter emulation +
|
|
||||||
bilinear/trilinear handling like the Nightdive / Quake-remaster ports.
|
|
||||||
MVP: one good default filtering mode in `port/fast3d/` (3-point emu
|
|
||||||
shader or clean bilinear). Long-term: full toggle set (see "PC graphics
|
|
||||||
settings" → Quality + stretch goals below). Biggest of the four.
|
|
||||||
- **B2 — glitchy textures / wrong colors (correctness bug).** Some
|
|
||||||
textures render with wrong colors / garbage — **very noticeable on
|
|
||||||
Depot (`-level_30`)**, present in many other spots too. Likely a
|
|
||||||
texture-decode/format bug (CI/TLUT, RZ decode `port/src/rzdecomp.c`, or
|
|
||||||
the `import_texture_*` path in `gfx_pc.cpp`). Distinct from B1. Needs
|
|
||||||
investigation — could be a real fidelity blocker, not cosmetic-parked.
|
|
||||||
- **B3 — RMB aim far too mouse-sensitive.** Manual-aim (RMB/LShift) mouse
|
|
||||||
sensitivity is way too high. `Input.MouseAimSpeed` (default 50) still
|
|
||||||
overshoots badly. Quick fix: lower default + recheck the aim-band curve
|
|
||||||
in `port/src/input.c` (the 61..80 stick push added M-24). Port-only.
|
|
||||||
- **B4 — menu crosshair-cursor screens: PC input is wrong.** Menu screens
|
|
||||||
that use the crosshair cursor don't behave like a PC pointer. The
|
|
||||||
original front-end was PC-style "select a file with the mouse" — menu
|
|
||||||
input should be a **separate mode from in-game input**, not the same
|
|
||||||
analog-stick emulation. Needs a dedicated menu-pointer path in
|
|
||||||
`port/src/input.c` (absolute cursor → menu hit-test) rather than
|
|
||||||
feeding synthetic stick deltas. Port-only.
|
|
||||||
- **B5 — no game save on level completion.** On the OG cartridge, finishing
|
|
||||||
a level writes progress to EEPROM (unlocks next mission, records
|
|
||||||
time/difficulty). On PC nothing is saved — progress is lost on quit.
|
|
||||||
This is **Phase 4** (file-backed EEPROM): `osEepromWrite/Read` are
|
|
||||||
currently no-op stubs; build on PD's `fs.c` (file-backed save dir). The
|
|
||||||
save *logic* already runs in the decomp — it just needs real backing
|
|
||||||
storage. Blocks any multi-session campaign playthrough.
|
|
||||||
|
|
||||||
### PC graphics settings
|
|
||||||
|
|
||||||
Optional video/rendering options, modelled on the PD port (baseline) with
|
|
||||||
Quake 1 remaster / Nightdive-style extras as stretch goals. **Every option
|
|
||||||
defaults to the current behavior** — zero visual delta at defaults.
|
|
||||||
|
|
||||||
**Starting point (verified):**
|
|
||||||
- `port/src/config.c` exists but is minimal (58 lines: Int + String only;
|
|
||||||
PD's is 300 lines with Float/UInt, clamping, INI sections). Copy-and-adapt
|
|
||||||
from `pd_port/port/src/config.c`.
|
|
||||||
- `port/src/video.c` is a trimmed port of PD's (250 vs 588 lines). Render path
|
|
||||||
is architecturally identical to PD: native-res FBO (GE = 640×480 NTSC LAN1)
|
|
||||||
→ scaled blit to window, so all PD display options port over cleanly.
|
|
||||||
- No `#ifdef PC` blocks exist in `src/` yet; only the documented ABI/layout
|
|
||||||
exception edits (D3x) are present. Anything below that changes *behavior*
|
|
||||||
(FOV, HUD placement, menu contents) is a policy decision — see route notes.
|
|
||||||
|
|
||||||
**Baseline (PD parity)** — each item cites its PD source:
|
|
||||||
1. Config system: full `configRegister{Int,UInt,Float,String}` + clamps
|
|
||||||
(`pd_port/port/src/config.c`).
|
|
||||||
2. Window/display (`video.c` + SDL): fullscreen checkbox, fullscreen mode
|
|
||||||
(borderless/exclusive), maximize, center window, resolution dropdown
|
|
||||||
(display-mode list + "Custom"), HiDpi toggle.
|
|
||||||
3. Frame pacing: vsync (Adaptive/Off/On/N-frames via `SDL_GL_SetSwapInterval`),
|
|
||||||
framerate cap slider (PD caps FPS when vsync is off — keep that guard).
|
|
||||||
4. Quality: MSAA Off/2x/4x/8x/16x (FBO `samples` — **verify our FBO path
|
|
||||||
accepts samples>0 first**; PD reads it back via `videoGetMSAA`), texture
|
|
||||||
filter Nearest/Bilinear, anisotropic 1–16 slider, mipmap filter,
|
|
||||||
on-screen FPS display toggle.
|
|
||||||
5. **FOV slider (baseline per owner decision).** PD does this game-side
|
|
||||||
(`Game.PlayerN.FovY`, `g_PlayerExtCfg` in their `main.c` + decomp edits).
|
|
||||||
Two routes — decide in a spike:
|
|
||||||
- **(a) Port-layer:** override the camera projection in our fast3d where
|
|
||||||
the view matrix/projection is consumed. No src changes; must find the
|
|
||||||
exact injection point (camera math flows through game GBI setup).
|
|
||||||
- **(b) PD-style port-only `src/` edit** reading a port-provided value.
|
|
||||||
This would be the first *behavioral* src edit — exceeds the current
|
|
||||||
ABI/layout exception in AGENTS.md #2; if chosen, expand the documented
|
|
||||||
exception ("PC-port feature hooks, opt-in via config, default = original
|
|
||||||
behavior") and record a Dxx finding.
|
|
||||||
6. **Widescreen handling:** non-4:3 window aspect → stretch vs pillarbox
|
|
||||||
choice, plus HUD centering (None/4:3/Wide à la PD `Game.CenterHUD`). PD
|
|
||||||
implements the HUD part with `G_ASPECT_*_EXT` extended GBI mods set
|
|
||||||
game-side; for us that's either a fast3d viewport intercept of the HUD
|
|
||||||
quads (route a) or a port-only src edit (route b). Same policy note as FOV.
|
|
||||||
|
|
||||||
**In-game video options menu:** v1 = config file (+ CLI flags if trivial).
|
|
||||||
PD's in-game menu (`pd_port/port/src/optionsmenu.c`, 2042 lines) hooks into
|
|
||||||
*their* decomp's menu tables with port-side handlers; GE's menu code differs,
|
|
||||||
so it's an adaptation, not a copy. Feasible under route (b) policy; revisit
|
|
||||||
after the config-file v1 works.
|
|
||||||
|
|
||||||
**Stretch goals (Quake 1 remaster / Nightdive):**
|
|
||||||
- Internal resolution scale: render at 2x/4x native then downscale (free
|
|
||||||
supersampling AA, big fidelity win on modern displays).
|
|
||||||
- FXAA post-process toggle (cheap AA alternative to MSAA).
|
|
||||||
- SSAO + bloom / light-shaft post-processes — Quake Enhanced ships each
|
|
||||||
enhancement as an individually-toggleable option; keep that pattern (one
|
|
||||||
toggle each, all default off, shared post-FBO stage with FXAA/colorblind).
|
|
||||||
- Integer-scaling option.
|
|
||||||
- Texture upscaling filter (ESRGAN-style or pre-generated upscales) — the
|
|
||||||
biggest item; do last. Note GE textures are RZ-compressed at load
|
|
||||||
(`port/src/rzdecomp.c`) — an upscale pass would hook there or post-decode.
|
|
||||||
|
|
||||||
**Acceptance:** every option persists via config file; toggles apply live
|
|
||||||
where PD's do; `tools_pc/golden/` dumps byte-identical at all-defaults;
|
|
||||||
no `src/` changes unless route (b) is explicitly chosen and documented.
|
|
||||||
|
|
||||||
### LAN deathmatch / multiplayer — the netplay foundation (do first)
|
|
||||||
|
|
||||||
**The idea:** play GE's *existing* competitive multiplayer (deathmatch /
|
|
||||||
mission-based, 2–4P) across up to **4 instances** of the PC port on a local
|
|
||||||
network. This is the **nettech proving ground**: the game logic already
|
|
||||||
exists in the decomp (original N64 feature — no new mode to invent), so this
|
|
||||||
item isolates and de-risks the entire networking layer. Co-op (below) bolts
|
|
||||||
onto whatever this produces.
|
|
||||||
|
|
||||||
**Milestone 0 (prerequisite, real port work):** verify GE's *existing*
|
|
||||||
split-screen multiplayer works locally on PC — two cameras, split viewport,
|
|
||||||
two independent input streams, full match lifecycle. Every code path netplay
|
|
||||||
will drive must first work on one box.
|
|
||||||
|
|
||||||
**Net model (research item — owner leans client-server):**
|
|
||||||
- Leading candidate: **host = server + player** (Source-engine style): host
|
|
||||||
runs the authoritative sim, clients send inputs and receive state. Friends
|
|
||||||
connect by manual IP entry on a LAN. Self-healing desync, one machine owns
|
|
||||||
truth.
|
|
||||||
- Cost to size up: a full per-tick **state snapshot format** (players,
|
|
||||||
entities/props, AI, doors/props) must be defined from scratch — no existing
|
|
||||||
save/replay infra covers frame state (EEPROM saves are mission-level only).
|
|
||||||
- Alternative for the research phase: **lockstep/deterministic** (exchange
|
|
||||||
~10 bytes of input per 60Hz tick, both machines run the full sim). Far less
|
|
||||||
code *if* the sim is bit-deterministic across hosts — PRNG is ported
|
|
||||||
verbatim (`port/src/random.c`, good), but host-scheduling nondeterminism is
|
|
||||||
a known risk (see D24-implications note below). Desync = hard stop.
|
|
||||||
A per-tick state-hash desync detector is cheap insurance either way.
|
|
||||||
- PD port has **zero networking code** — nothing to copy; the net layer is
|
|
||||||
new (`port/src/net.c`-style, SDL_net or plain sockets).
|
|
||||||
|
|
||||||
**Shape of the work under host-authoritative:** generalize to host + N clients
|
|
||||||
(N≤3), per-client input channels, state snapshots fanned out to all clients.
|
|
||||||
The host already runs the full sim, so scaling past 2P is mostly protocol
|
|
||||||
fan-out — which is why 2P is a natural intermediate milestone on the way to
|
|
||||||
the 4-instance bar.
|
|
||||||
|
|
||||||
**v1 bar:** LAN only, manual IP entry (host listens / client connects),
|
|
||||||
deathmatch playable; 2P first, then scale to 4 instances. No matchmaking,
|
|
||||||
discovery, reconnect, or internet play. Stretch: ping display, graceful
|
|
||||||
reconnect-on-drop, mission-based (non-deathmatch) multiplayer variants,
|
|
||||||
remote-player model interpolation between snapshots (Quake Enhanced ships
|
|
||||||
it; hides LAN jitter).
|
|
||||||
|
|
||||||
### Co-op (new mode — built on the deathmatch net layer)
|
|
||||||
|
|
||||||
**The idea:** two players progress the single-player campaign levels
|
|
||||||
together — locally on one PC, and over LAN with each player on their own PC.
|
|
||||||
Sequenced **after** LAN deathmatch: reuse its connection handling, snapshot
|
|
||||||
format, and input channels; this item adds the mode itself.
|
|
||||||
|
|
||||||
**Key fact (defines the scope): GE has no co-op mode.** Unlike PD, GoldenEye
|
|
||||||
only ships split-screen *competitive* multiplayer. Co-op on solo levels exists
|
|
||||||
in **PD only** — so this is a net-new game mode: mapping/level and GE game-
|
|
||||||
code feature work, on top of the (already-existing) networking plumbing.
|
|
||||||
Consequences:
|
|
||||||
- Non-negotiable #2 ("game logic unmodified") is about not altering *existing*
|
|
||||||
behavior; adding a new mode is a separate, explicit owner decision. Record
|
|
||||||
the policy call when work starts.
|
|
||||||
- `pd_port/` is the reference implementation: PD's decomp shows how two-player
|
|
||||||
co-op was wired into this same Indy engine (shared objective progression,
|
|
||||||
both players' AI/enemy interaction, win/lose conditions, split-screen
|
|
||||||
presentation). Expect an adaptation study, not a copy — GE's mission/
|
|
||||||
objective code differs file-by-file.
|
|
||||||
- Cheaper fallback worth considering during design: "freeplay" co-op (both
|
|
||||||
players active on a solo level, but only P1's objective progress counts /
|
|
||||||
or objectives are duplicated per player). Much less new logic; decide after
|
|
||||||
the PD study.
|
|
||||||
|
|
||||||
**v1 bar:** LAN + local 2P co-op on solo levels over the deathmatch net layer.
|
|
||||||
No matchmaking, discovery, reconnect, or internet play.
|
|
||||||
|
|
||||||
### Fun features (remaster research: Quake 1 Remaster / Nightdive)
|
|
||||||
|
|
||||||
Feature mining from the Quake 1 Remaster (id, 2016), Nightdive's remaster
|
|
||||||
line (Duke Nukem 3D: Forever, DOOM, System Shock), and — round 2,
|
|
||||||
2026-08-29 — Quake Enhanced, Quake II Enhanced, Turok Resurrection, Doom 64
|
|
||||||
Definitive Edition, SiN Reloaded. Pattern across all of them: classic
|
|
||||||
fidelity as default + a shelf of opt-in QoL/accessibility/asset options.
|
|
||||||
Tiered by effort; all stay in `port/` unless noted.
|
|
||||||
|
|
||||||
**Cheap wins (config toggles over existing infra):**
|
|
||||||
- **Screenshot hotkey.** Productize the existing debug dump:
|
|
||||||
`gfx_opengl_dump_bound_fbo()` (`port/src/video.c:199`) is already there —
|
|
||||||
wire a keybind + configurable output dir, timestamped filenames. Every
|
|
||||||
Nightdive title has this; ours is ~an afternoon.
|
|
||||||
- **Screen-shake intensity slider (0–10x).** All shake funnels through
|
|
||||||
`viShake()` (`src/game/bondview2.c:8300` et al.) which lands in our port
|
|
||||||
shim — scale amplitude there, zero src changes. PD precedent:
|
|
||||||
`Game.ScreenShakeIntensity`.
|
|
||||||
- **No hit-flash / damage-indicator toggle.** The red full-screen flash on
|
|
||||||
taking damage is a top community-mod option; Nightdive ships it. Impl: find
|
|
||||||
how the flash overlay is drawn (likely a full-screen HUD quad) and intercept
|
|
||||||
in the port layer, or route-(b) one-liner.
|
|
||||||
- **Auto-pause / input-hold on window focus loss.** SDL focus events; trivial,
|
|
||||||
prevents drive-by deaths when alt-tabbing.
|
|
||||||
- **Controller polish (copy from PD `input.c`):** per-player gamepad
|
|
||||||
assignment (`Input.ControllerIndex`), per-stick deadzone/sensitivity sliders,
|
|
||||||
rumble scale + rumble mapping to GE's feedback moments. Note `osMotor*`
|
|
||||||
(Rumble Pak) is currently stubbed — accessory detection only
|
|
||||||
(PCPortResearch §4) — so wiring it to `SDL_GameControllerRumble` is part of
|
|
||||||
this item; Doom 64 DE shipped haptic feedback (a KEX first). We have basic
|
|
||||||
pad support (D118); this is the remaster-grade layer on top.
|
|
||||||
- **Tick-rate / framerate options.** PD ships `Game.TickRateDivisor` +
|
|
||||||
framerate cap. Flag: any tick-rate change must be reconciled with the future
|
|
||||||
netplay determinism requirement before shipping as a user option (lock it to
|
|
||||||
60Hz default, document).
|
|
||||||
- **Fog / draw-distance option.** Turok's KEX port removed the N64's heavy
|
|
||||||
distance fog; Quake II Enhanced added height-based fog with custom colors.
|
|
||||||
fast3d already decodes fog generically (PCPortResearch §5) — a config
|
|
||||||
slider scaling/overriding fog density, default = original values. Zero src
|
|
||||||
changes.
|
|
||||||
|
|
||||||
**Medium:**
|
|
||||||
- **High-res / user texture packs (the big goodwill item).** Quake 1 Remaster's
|
|
||||||
core feature is swappable asset sets (classic vs remastered toggle). GE
|
|
||||||
textures are RZ-compressed and decoded at load (`port/src/rzdecomp.c`) — a
|
|
||||||
per-texture override lookup against a user directory (fall back to ROM) hooks
|
|
||||||
there. Enables the community to make packs; also our escape hatch for the
|
|
||||||
blurry-asset complaints without touching fidelity defaults.
|
|
||||||
- **Accessibility: colorblind post-process filters** (protanopia/
|
|
||||||
deuteranopia/tritanopia full-screen shader pass) + the screen-shake slider
|
|
||||||
above. Nightdive includes these; cheap once we have a post-process stage
|
|
||||||
(shared infra with FXAA from the graphics item — sequence together).
|
|
||||||
Quake II Enhanced also shipped high-contrast / readability UI options —
|
|
||||||
add where the port layer can reach (bitmap HUD text limits scope;
|
|
||||||
document what's not doable under route b).
|
|
||||||
- **Crosshair customization** (size/opacity at minimum; GE's crosshair is
|
|
||||||
game-drawn — check whether it's a textured quad we can scale in the port
|
|
||||||
layer before assuming route b).
|
|
||||||
- **Widescreen FOV compensation** — already baseline in the graphics item;
|
|
||||||
listed here because it's the feature every remaster ships and users will
|
|
||||||
expect.
|
|
||||||
|
|
||||||
**Big / stretch:**
|
|
||||||
- **Demo / replay record + playback.** Record the input stream (+ initial
|
|
||||||
state), play back headlessly. Quake tradition; Nightdive titles have
|
|
||||||
replay-ish tooling. **Strong synergy with netplay work:** a working replay
|
|
||||||
system *is* a determinism test harness — if a recorded demo replays
|
|
||||||
bit-identically, lockstep netplay's hardest assumption is proven. Consider
|
|
||||||
building it as the netplay pre-flight tool even before it's user-facing.
|
|
||||||
- **Photo mode.** Pause + free orbit camera + capture (System Shock
|
|
||||||
precedent). Free camera = port-layer camera override; pairs with the
|
|
||||||
screenshot hotkey.
|
|
||||||
- **In-game console.** Type commands at runtime. Less "remaster feature"
|
|
||||||
than dev tool, but for a WIP port it's arguably the highest-value item on
|
|
||||||
this list: toggle debug flags, dump state, force conditions without
|
|
||||||
restarting. PD has no equivalent; we'd own the design.
|
|
||||||
- **Benchmark mode.** Scripted auto-run sequence + FPS report (Quake
|
|
||||||
tradition); trivial once tick-rate options exist.
|
|
||||||
- **AI opponents in deathmatch.** Quake Enhanced ships bots in DM. GE's N64
|
|
||||||
MP is human-only, so bots = net-new game logic — same policy class as
|
|
||||||
co-op (record the owner decision when work starts). Stretch: lets a solo
|
|
||||||
player exercise the netplay layer and makes MP approachable with 1–2 humans.
|
|
||||||
- **Animation smoothing ("wiggle elimination").** Quake II Enhanced restored
|
|
||||||
high-res skeletal animations specifically to kill low-fps "wiggle". GE's
|
|
||||||
model keyframes are N64-rate; interpolating (slerp) between keyframes in
|
|
||||||
the port-layer model-transform path would visibly smooth guard/Bond motion
|
|
||||||
on modern displays. Stretch — sequence with the graphics backlog, do late.
|
|
||||||
- **Dynamic shadows.** Quake Enhanced's headline graphical feature. For
|
|
||||||
fast3d this means a shadow-map pass + per-light casting — deep stretch;
|
|
||||||
park next to texture upscaling (both "do last" items).
|
|
||||||
- **Remastered audio option (Phase 3 note).** System Shock's remake added
|
|
||||||
new sounds/music. Keep N64 ADPCM fidelity as the default; optionally offer
|
|
||||||
a lossless re-encoded soundtrack toggle — owner decision + content work,
|
|
||||||
park until Phase 3 lands.
|
|
||||||
- **Optional modern UI skin.** SiN Reloaded shipped upgraded 2D screens /
|
|
||||||
menu art; System Shock an overhauled interface. For GE this is route (b)
|
|
||||||
game-side territory (menu tables in `front.c` / `initmenus.c`) — park as
|
|
||||||
an explicit owner decision; lowest priority on this list.
|
|
||||||
- **Stereo 3D** — both reference families shipped it; explicitly
|
|
||||||
deprioritized as anachronistic. Park unless someone asks.
|
|
||||||
|
|
||||||
Out of scope by the v1 bars above: online matchmaking, leaderboards,
|
|
||||||
anti-cheat, cross-play (Quake 1 Remaster's biggest features; revisit only
|
|
||||||
after LAN netplay is solid).
|
|
||||||
|
|
||||||
Also out of scope by policy (community port): online-platform integration —
|
|
||||||
Steam/GOG/Epic achievements, cloud saves, storefront add-on downloads. No
|
|
||||||
platform tie-in of any kind.
|
|
||||||
|
|
||||||
--Old is above here
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Technical cleanup (backlogged; not gated on playability)
|
|
||||||
|
|
||||||
- **Remove TEMP D63 debug scaffolding.** ~16 references in `port/src/` (`libultra.c`): the master-DL scanner and env-gated `GE_D63` logging inside `osSpTaskStartGo()`. D63 is closed; this is dead weight in the hottest function in the port.
|
|
||||||
- **Add a D24-implications bug class to `docs/PORT-LEARNINGS.md`.** "Host-scheduling nondeterminism / fake priority semantics": the pthread kernel does not enforce N64's 0–31 priorities (`osYieldThread` = `Sleep(0)`), so interleavings impossible on console can occur, and frame timing has host jitter. Consequence for future sessions: when a flaky timing bug appears (especially Phase 3 audio underruns), reach first for host thread priorities (`SetThreadPriority`: scheduler thread time-critical, tick normal) and the deferred `GE_DETERM` mode (D117) — not for kernel changes.
|
|
||||||
- **Watch item: `osYieldThread` = `Sleep(0)`.** The one place we fake cooperative behavior. If a level-sweep hot loop misbehaves under host load, this shim is the first thing to inspect. Keep as-is until then.
|
|
||||||
|
|
||||||
## Architecture decision (recorded; no action)
|
|
||||||
|
|
||||||
- **Keep the pthread + real `src/sched.c` model.** Compared against the PD port's actual design (verified in `pd_port/`: zero threads — thread API is pure no-op, `mainProc()` runs as the host main thread, `pdsched.c` is a gutted frame-callback wrapper, audio driven inline via `amgrFrame()`, M_AUDTASK dropped). PD could do that only because their *original N64* scheduler already used direct-call submission ("nothing writes to the cmdQ in PD") and they adapted game-side lib code (`audiomgr.c`). For GE — classic `osSendMesg(cmdQ)` protocol, real `__scMain` loop, `amMain` blocking on retrace/reply messages — that route means editing `src/audi.c`/submission points (non-negotiable #2 violation) and regressing Phase 3, which currently runs the full authentic audio scheduling protocol. No rewrite. Related: audio stays inline in the `M_AUDTASK` branch (`docs/AUDIO-PLAN.md`, Option A + sub-decision A3); no dedicated audio worker thread unless host thread priorities provably fail.
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
# BRIEF — B2: Depot (-level_30) wrong-colour / garbage textures
|
|
||||||
|
|
||||||
M-29 diagnostic pass. **Not fixed.** Evidence captured, leading hypothesis
|
|
||||||
recorded. Companion: `docs/BACKLOG.md` B1/B2, `docs/SMALL-FIXES.md` C-section.
|
|
||||||
|
|
||||||
## Symptom (captured)
|
|
||||||
|
|
||||||
`-level_30`, opening loading-bay area, `GE_PCDUMP` frames ~700-1150
|
|
||||||
(camera panning up toward the roof):
|
|
||||||
|
|
||||||
- The **ceiling / roof surface** renders as bright blue with a dense green
|
|
||||||
speckle and radial "light-ray" streaks emanating from a point. On N64 this
|
|
||||||
is a dark, low-contrast corrugated roof.
|
|
||||||
- Walls (green wall panels right, tan crates left) look approximately
|
|
||||||
correct — mild green tint at most.
|
|
||||||
- HUD ammo digits mirrored = D116, unrelated.
|
|
||||||
|
|
||||||
Frames: `ppm/frame_0007..1150` this session (regenerate with
|
|
||||||
`GE_PCDUMP="700-1600:150" ./build-pc/ge007.x86_64.exe -level_30`).
|
|
||||||
|
|
||||||
## UPDATE (M-29, later): filtering RULED OUT
|
|
||||||
|
|
||||||
Enabling N64 3-point filtering + trilinear mips + 4x anisotropy game-wide
|
|
||||||
(the B1 work, now default) **did not change the Depot roof** — the blue
|
|
||||||
speckle + radial rays render identically at 640x480 native. So this is NOT
|
|
||||||
grazing-angle aliasing. It is a real texture-data / UV / effect bug on that
|
|
||||||
specific surface. The radial rays converging to a point look like either a
|
|
||||||
broken texture matrix (rotating/scrolling tc), garbage UVs on the ceiling
|
|
||||||
geometry, or a mis-rendered light-shaft effect (wrong blend/opacity). Needs
|
|
||||||
the `GE_DTEX` probe below to identify what draws it.
|
|
||||||
|
|
||||||
## (superseded) earlier hypothesis: grazing-angle aliasing / B1
|
|
||||||
|
|
||||||
The radial-ray + sparkle pattern is the **classic grazing-angle aliasing**
|
|
||||||
signature of a high-frequency texture sampled with point/bilinear filtering
|
|
||||||
and no working LOD/mipmap chain. The ceiling is at a very shallow angle to
|
|
||||||
the camera in these frames — exactly where N64 3-point + LOD filtering
|
|
||||||
suppresses shimmer and our fast3d path does not (see BACKLOG B1: "whole game
|
|
||||||
has an interlaced/shimmering look"; `port/fast3d/gfx_pc.cpp` mip handling is
|
|
||||||
stubbed — `gfx_lod_tile_offset` / "for now just ignore smaller mips" at
|
|
||||||
~1540, and D107 notes fast3d fabricates a crop when detail textures are off).
|
|
||||||
|
|
||||||
**Decode path was audited and looks correct:**
|
|
||||||
- `gfx_dp_load_tlut` byteswaps entries (`PD_BE16`), `palette_to_rgba32`
|
|
||||||
decodes RGBA16 5551 and IA16 correctly, `rdp.palette_fmt` is set from
|
|
||||||
`other_mode_h & (3<<G_MDSFT_TEXTLUT)` (line ~2389).
|
|
||||||
- `import_texture_rgba16` / `ci4` / `ci8` decode textbook-correct, BE reads.
|
|
||||||
- D72 already disabled the PD normal-derived texgen for GE, so the streaks
|
|
||||||
are not env-mapping.
|
|
||||||
|
|
||||||
Nothing in the decode path obviously produces "blue + green speckle".
|
|
||||||
|
|
||||||
## Next diagnostic step (for a focused session)
|
|
||||||
|
|
||||||
1. **Probe the ceiling draw:** in `gfx_pc.cpp` `import_texture` / the tri
|
|
||||||
setup, `GE_DTEX=1`-gate a log of `{orig_addr, fmt, siz, palette_index,
|
|
||||||
line_size_bytes, width, height, cms/cmt, other_mode_h & TEXTFILT, tex_lod}`
|
|
||||||
for the first ~200 unique textures on `-level_30`. Identify which entry is
|
|
||||||
the ceiling (large, tiled, grazing).
|
|
||||||
2. If its `fmt/siz` and data look sane → confirm B1: the fix is proper
|
|
||||||
trilinear + a real mip chain (or an N64 3-point-filter emulation shader),
|
|
||||||
per BACKLOG B1 "MVP: one good default filtering mode". Do B1 first, then
|
|
||||||
re-shoot this frame; B2 likely collapses into it.
|
|
||||||
3. If the data is garbage (wrong TMEM offset, wrong decompressed RZ section)
|
|
||||||
→ it's a real decode/addressing bug: check `line_size_bytes` vs
|
|
||||||
`full_image_line_size_bytes` (the `SUPPORT_CHECK` at
|
|
||||||
`import_texture_rgba16:657` is *commented out* with a TODO about garbage
|
|
||||||
`full_image_line_size_bytes`), and the RZ section offset from
|
|
||||||
`rzipGetSomething()` in `src/game/image.c`.
|
|
||||||
|
|
||||||
## Risk / scope
|
|
||||||
|
|
||||||
B1 (filtering + mips) is a medium fast3d task with a clear reference (PD port,
|
|
||||||
Nightdive 3-point emu). It is the highest-leverage single graphics fix —
|
|
||||||
addresses the game-wide shimmer complaint AND (hypothesis) most of B2. Do it
|
|
||||||
as its own focused effort, not inline.
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
# BRIEF C1 — `chrIsNotDeadOrShot` crash on 6 levels (blocks the level sweep)
|
|
||||||
|
|
||||||
Repo: `REPO_ROOT` (git, branch `master`, Windows;
|
|
||||||
PowerShell + Bash tool).
|
|
||||||
|
|
||||||
## READ FIRST
|
|
||||||
- `docs/HANDOFF.md` (top) — state, environment, repro line.
|
|
||||||
- `docs/PORT-LEARNINGS.md` — **§A (pointer-width struct growth) and §C
|
|
||||||
(BE rodata on LE) are the prime suspects.** Also §A D119
|
|
||||||
(`weapons_held[]->chr` type-pun) — closest prior instance.
|
|
||||||
- `docs/PCPortResearch.md` §F entries **D119, D115, D101, D102, D56, D57**
|
|
||||||
(jump via the index — do NOT linear-read).
|
|
||||||
- `docs/AUDIT-M6-player-offsets.md` — the raw-offset landmine class.
|
|
||||||
- `AGENTS.md` non-negotiables (#2: narrow `#ifdef PORT` ABI/layout only,
|
|
||||||
N64 line kept under `#else`, logged in §F).
|
|
||||||
|
|
||||||
## SYMPTOM (WS4 sweep, this session — `docs/LEVEL-STATUS.md`)
|
|
||||||
`./build-pc/ge007.x86_64.exe -level_XX` (bare — pools auto-injected, D121),
|
|
||||||
crashes **early, before frame 1**, deterministically, at:
|
|
||||||
|
|
||||||
```
|
|
||||||
chrIsNotDeadOrShot src/game/chraction.c:4483 s8 currentaction = self->actiontype;
|
|
||||||
```
|
|
||||||
|
|
||||||
`EXCEPTION 0xc0000005`, FAULT ADDR `0x8` (Dam). Crash **`self` = the same
|
|
||||||
value as backtrace frame #1 = `0x1401296a0`** — an address *inside the
|
|
||||||
compiled binary's rodata*, in the `chraidata.c` global-AI-list blob
|
|
||||||
(`m_AimAtBond[]` / `m_*[]` at `chraidata.c:61+`). So a `ChrRecord *self`
|
|
||||||
is pointing at AI-list bytecode rodata — a pointer swap / pun / wrong
|
|
||||||
struct-offset read, not mere corruption.
|
|
||||||
|
|
||||||
Affected: **Dam 33, Runway 35, Frigate 26, Statue 22, Streets 29,
|
|
||||||
Cradle 41** (6). NOT Bunker1 09 / Silo 20 / Archives 24 / Train 25 /
|
|
||||||
Caverns 39 / Egypt 32 / Cuba 54 (those 7 PASS, render ~91%).
|
|
||||||
|
|
||||||
## TASK
|
|
||||||
**One question: why is a `ChrRecord*` actually an AI-list rodata pointer
|
|
||||||
on these 6 levels, and what is the narrow fix?**
|
|
||||||
|
|
||||||
Leading hypotheses:
|
|
||||||
1. A per-level `Usetup*Z` guard record (`GuardRecord` type 9 /
|
|
||||||
`GuardAttributeRecord` type 18, or the chr-spawn record) has a pointer
|
|
||||||
or `s16/s16` packed field converted wrongly — or not at all — by
|
|
||||||
`tools_pc/d88_propdefs.py` (cf. D122: the "generic" fallback arm
|
|
||||||
word-swaps sub-word fields). Enumerate every propDef `type` byte the 6
|
|
||||||
crashing levels emit vs the 7 passing ones
|
|
||||||
(`tools_pc/d88_propdef_scan.py` histogram) — the delta type is the
|
|
||||||
suspect. The 7 pass / 6 fail split is the strongest lead.
|
|
||||||
2. The AI-list bytecode pointers in `chraidata.c` `.rodata`
|
|
||||||
(`SetChrAiList`, list-table entries) are BE and consumed raw — a chr
|
|
||||||
gets `aiList` set to a byteswapped pointer, then a subsequent tick
|
|
||||||
treats it as / derefs it as a `ChrRecord*`. Check `chraifunc.c` /
|
|
||||||
`chraidata.c` list dispatch and any `AiListRecord`-style table.
|
|
||||||
3. `ChrRecord` / `GuardRecord` struct is pointer-width-grown on x86-64
|
|
||||||
and a per-level record is read from ROM bytes at N64 offsets (D56/D57
|
|
||||||
class) — `self->actiontype` at the wrong byte.
|
|
||||||
|
|
||||||
Determine which. Prefer an **offline converter extension**
|
|
||||||
(`d88_propdefs.py` / `d88_emit.py`, D43/D88 pattern) over a runtime fixup;
|
|
||||||
else a narrow `#ifdef PORT` ABI edit.
|
|
||||||
|
|
||||||
## FILES YOU MAY TOUCH
|
|
||||||
- `src/game/chraction.c`, `src/game/chr.c`, `src/game/chraidata.c`,
|
|
||||||
`src/game/chraifunc.c` (narrow `#ifdef PORT` only).
|
|
||||||
- `tools_pc/d88_propdefs.py`, `tools_pc/d88_emit.py`,
|
|
||||||
`tools_pc/d88_layoutprobe.c`, `tools_pc/d88_propdef_scan.py`.
|
|
||||||
- New probe files `tools_pc/c1_*.py` / env-gated `GE_C1` blocks.
|
|
||||||
- `docs/PORT-LEARNINGS.md` (append), `docs/PCPortResearch.md` §F (write up
|
|
||||||
as the next Dxx after D122 — check the §F index for the current max —
|
|
||||||
add to the index), `docs/LEVEL-STATUS.md` (flip the rows you fix).
|
|
||||||
- **NOT** `port/fast3d/*` or `src/game/propobj.c` (other tracks).
|
|
||||||
|
|
||||||
## KNOWN-GOOD / RULED OUT — do not re-investigate
|
|
||||||
- D121 per-level pool injection — works, committed.
|
|
||||||
- D122 propDef `obj` half-swap — committed `f2beae4b`; your bug is a
|
|
||||||
*different* record type/field (BUNKER1/Silo unaffected).
|
|
||||||
- BUNKER1 + Silo + Archives + Train + Caverns + Egypt + Cuba chr/AI setup
|
|
||||||
— works. Use them as the "good" control for the propDef-type diff.
|
|
||||||
- The intermittent BUNKER1 `loadobjectmodel.c:393` crash — D117
|
|
||||||
nondeterminism, unrelated.
|
|
||||||
|
|
||||||
## PRE-FLIGHT ATTACHED
|
|
||||||
- `./build-pc.sh ntsc-final` green (`export PATH="/c/msys64/mingw64/bin:$PATH"` first).
|
|
||||||
- Sidecars current for `f2beae4b`. Regen after any converter change:
|
|
||||||
`python tools_pc/d43_emit.py ntsc-final && python tools_pc/d69_emit.py ntsc-final && python tools_pc/d88_emit.py ntsc-final --regen`
|
|
||||||
- Repro: `./build-pc/ge007.x86_64.exe -level_33` (Dam). Crash log
|
|
||||||
`ge007.crash.log` at repo root. Symbolicate:
|
|
||||||
`addr2line -e build-pc/ge007.x86_64.exe -f -C 0x<PC>` (PATH first;
|
|
||||||
image base 0x140000000, PC in log is absolute).
|
|
||||||
- Frame capture `GE_PCDUMP="80-260:40"` → `./ppm/`; `python tools_pc/pixcount.py ppm/<last>.ppm`.
|
|
||||||
- **`timeout` does NOT kill the game on Windows.** Launch `... &`,
|
|
||||||
`sleep 24`, `taskkill //F //IM ge007.x86_64.exe`.
|
|
||||||
- gdb attach is fast: game running via `nohup ... &`, then
|
|
||||||
`gdb -batch -x cmds.txt -p <winpid>` (winpid = 4th col of `ps -p <bashpid>`).
|
|
||||||
Hardware watchpoint on the chr's `aiList` field, or on the bad `self`,
|
|
||||||
catches the corrupting write.
|
|
||||||
- PD analogue: check `PD_PORT_CHECKOUT` chr / guard
|
|
||||||
setup + its propdef converter before writing anything new.
|
|
||||||
|
|
||||||
## BUDGET
|
|
||||||
Structural → **~12 build→run→inspect cycles or ~90 min.** On expiry:
|
|
||||||
revert temp probes (leave only committed env-gated ones), write up spec
|
|
||||||
progress + best hypothesis in §F with an explicit confidence rating, list
|
|
||||||
every file you touched. A good half-solved write-up is a deliverable.
|
|
||||||
|
|
||||||
## CONSTRAINTS
|
|
||||||
No game-logic / control-flow changes. ABI/layout/format only, each
|
|
||||||
`#ifdef PORT` with the N64 line verbatim under `#else`, each documented in
|
|
||||||
§F. Prefer converter extension over runtime fixup. Revert your own temp
|
|
||||||
probes before reporting. Do not touch other tracks' uncommitted edits
|
|
||||||
(none expected).
|
|
||||||
|
|
||||||
## REPORT
|
|
||||||
1. Root cause — `file:line` evidence, which hypothesis, why the 6 differ
|
|
||||||
from the 7 passing levels.
|
|
||||||
2. Fix — the diff, or why-not if unresolved.
|
|
||||||
3. Verification — `-level_33/35/26/22/29/41` each boot to ≥1
|
|
||||||
non-degenerate frame (`pixcount.py` > a few %), AND `-level_09` +
|
|
||||||
`-level_20` unregressed (`tools_pc/framediff.py`).
|
|
||||||
4. Probes left in the tree (env-gated, capped).
|
|
||||||
5. Confidence rating.
|
|
||||||
6. Generalisable quirk appended to `docs/PORT-LEARNINGS.md`.
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
# BRIEF C2 — fast3d bad texture pointer on Facility + Jungle
|
|
||||||
|
|
||||||
Repo: `REPO_ROOT` (git, `master`, Windows;
|
|
||||||
PowerShell + Bash).
|
|
||||||
|
|
||||||
## READ FIRST
|
|
||||||
- `docs/HANDOFF.md` (top), `docs/PORT-LEARNINGS.md` (§C BE rodata, §D N64
|
|
||||||
hardware idioms fast3d doesn't emulate).
|
|
||||||
- `docs/PCPortResearch.md` §F entries **D69, D68, D39, D67, D107, D84,
|
|
||||||
D58** (texture / segment-address history — jump via §F index).
|
|
||||||
- `AGENTS.md` non-negotiables. `rsp/graphics/gmain.s` = GBI ground truth.
|
|
||||||
- PD analogue: `PD_PORT_CHECKOUT` `port/fast3d/`
|
|
||||||
texture import path — check first.
|
|
||||||
|
|
||||||
## SYMPTOM (WS4 sweep — `docs/LEVEL-STATUS.md`)
|
|
||||||
Bare `./build-pc/ge007.x86_64.exe -level_XX`, early deterministic crash:
|
|
||||||
|
|
||||||
| Level | # | Site | Fault addr |
|
|
||||||
|---|---|---|---|
|
|
||||||
| Facility | 34 | `import_texture_i8` `port/fast3d/gfx_pc.cpp:821` | `0x72181ee8` (looks like an unresolved N64 segment addr) |
|
|
||||||
| Jungle | 37 | `gfx_tex_normalize_source` `port/fast3d/gfx_pc.cpp:644` | `0xabcd0824` (sentinel / uninit value) |
|
|
||||||
|
|
||||||
fast3d is handed a garbage texture address. 7 levels render fine
|
|
||||||
(Bunker1/Silo/Archives/Train/Caverns/Egypt/Cuba) so the common path is
|
|
||||||
sound — Facility/Jungle pull in a texture asset or a GBI texture-image
|
|
||||||
segment address the others don't.
|
|
||||||
|
|
||||||
## TASK
|
|
||||||
Root-cause both (likely the same class) and apply the narrowest fix.
|
|
||||||
Hypotheses:
|
|
||||||
1. A per-level texture asset mis-converted / missing —
|
|
||||||
`tools_pc/d69_emit.py` (pccg path) or `port/src/pccg.c`. `0xabcd....`
|
|
||||||
is a classic uninit-slot sentinel — a texture slot that was never
|
|
||||||
filled because the converter skipped a format/opcode.
|
|
||||||
2. A GBI `gsDPSetTextureImage` segment address not resolved to a real
|
|
||||||
pointer before `import_texture_*` (segment-fold class — D58/D84;
|
|
||||||
`(u32)` 32-bit wrap on `BG_SEG_TO_PTR` / K0 folds). `0x72181ee8` is in
|
|
||||||
the N64 segment-mapped range.
|
|
||||||
3. A texture LOD / detail-tile path (D107) hit only by these levels.
|
|
||||||
|
|
||||||
## FILES YOU MAY TOUCH
|
|
||||||
- `port/fast3d/gfx_pc.cpp` (narrow — a resolve/guard at the import site).
|
|
||||||
- `tools_pc/d69_emit.py` (+ `tools_pc/d69_*` helpers), `port/src/pccg.c`,
|
|
||||||
`port/src/romdata*.c` (segment resolution).
|
|
||||||
- New probe files `tools_pc/c2_*` / env-gated `GE_C2` blocks.
|
|
||||||
- `docs/PORT-LEARNINGS.md` (append), `docs/PCPortResearch.md` §F (next Dxx
|
|
||||||
after the current §F-index max; add to index), `docs/LEVEL-STATUS.md`.
|
|
||||||
- **NOT** `src/game/chr*.c` or `src/game/propobj.c` (other tracks).
|
|
||||||
|
|
||||||
## KNOWN-GOOD / RULED OUT
|
|
||||||
- D121 pool injection, D122 propDef fix — committed, work.
|
|
||||||
- 7 levels render clean — the base texture path is fine.
|
|
||||||
- HUD/text X-mirror (D114/D116) — parked, do NOT re-trace (PORT-LEARNINGS §D2).
|
|
||||||
|
|
||||||
## PRE-FLIGHT
|
|
||||||
- `export PATH="/c/msys64/mingw64/bin:$PATH" && ./build-pc.sh ntsc-final` — green.
|
|
||||||
- Repro `./build-pc/ge007.x86_64.exe -level_34`. Crash log
|
|
||||||
`ge007.crash.log`. `addr2line -e build-pc/ge007.x86_64.exe -f -C 0x<PC>`.
|
|
||||||
- `GE_PCDUMP="80-260:40"` → `./ppm/`; `python tools_pc/pixcount.py`.
|
|
||||||
- `timeout` does NOT kill on Windows: `... &`, `sleep 24`,
|
|
||||||
`taskkill //F //IM ge007.x86_64.exe`.
|
|
||||||
- Regen sidecars after converter change:
|
|
||||||
`python tools_pc/d43_emit.py ntsc-final && python tools_pc/d69_emit.py ntsc-final && python tools_pc/d88_emit.py ntsc-final --regen`.
|
|
||||||
|
|
||||||
## BUDGET
|
|
||||||
**~10 build→run→inspect cycles or ~75 min.** On expiry: revert probes,
|
|
||||||
write up in §F with confidence rating + files touched.
|
|
||||||
|
|
||||||
## CONSTRAINTS
|
|
||||||
No game-logic changes; ABI/layout/format / port-layer only. Prefer
|
|
||||||
converter fix over runtime. Revert temp probes. Don't touch other tracks'
|
|
||||||
uncommitted edits.
|
|
||||||
|
|
||||||
## REPORT
|
|
||||||
1. Root cause + `file:line` evidence, why Facility/Jungle differ.
|
|
||||||
2. Fix diff or why-not.
|
|
||||||
3. Verification: `-level_34` + `-level_37` boot to ≥1 non-degenerate
|
|
||||||
frame; `-level_09` + `-level_20` unregressed (`framediff.py`).
|
|
||||||
4. Probes left in tree.
|
|
||||||
5. Confidence.
|
|
||||||
6. Quirk appended to `docs/PORT-LEARNINGS.md`.
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
# BRIEF C2-GDL — model/prop GDL runtime relocation misaligns dst (Facility, Runway)
|
|
||||||
|
|
||||||
Repo: `REPO_ROOT` (git `master`, Windows).
|
|
||||||
|
|
||||||
## READ FIRST
|
|
||||||
- `docs/HANDOFF.md` top, `docs/PORT-LEARNINGS.md` **§B (16-byte PC Gfx vs
|
|
||||||
8-byte N64 — this is the class)** + §D.
|
|
||||||
- `docs/PCPortResearch.md` §F **D124** (the C2 split — this brief is the
|
|
||||||
unfinished Facility half), **D80, D82, D83, D85, D95, D58, D50.6**
|
|
||||||
(Gfx-stride / GDL history — jump via §F index).
|
|
||||||
- `AGENTS.md` non-negotiables. `rsp/graphics/gmain.s` = GBI ground truth.
|
|
||||||
- PD analogue: `PD_PORT_CHECKOUT` — its model-GDL /
|
|
||||||
texture relocation path. Check before writing anything.
|
|
||||||
|
|
||||||
## SYMPTOM (WS4 re-sweep — `docs/LEVEL-STATUS.md`)
|
|
||||||
Facility `-level_34` (fault 0x72181ee8) and Runway `-level_35` (fault
|
|
||||||
0x721b8ee8) both crash in `import_texture_i8` (`port/fast3d/gfx_pc.cpp:821`)
|
|
||||||
— fast3d fed a garbage `G_SETTIMG` w1.
|
|
||||||
|
|
||||||
Per the D124 investigation: the bad `G_SETTIMG` (`fmt=4 siz=2 w=0`,
|
|
||||||
preceded by an unconsumed `0xba` GE tex-macro opcode) sits in a
|
|
||||||
model/prop GDL relocated at runtime by `texLoadFromGdl()` via
|
|
||||||
`sub_GAME_7F0762E0` (`objecthandler_2.c:82`) — NOT a room GDL, NOT the
|
|
||||||
global bank. A `GE_C2` probe showed `texLoadFromGdl` writing to
|
|
||||||
**non-16-byte-aligned `dst`** (`0x701eac01`, `0x701eb7d5`, …): the
|
|
||||||
`replacementgdl` / `name`(=srcsize) offset arithmetic still mixes N64
|
|
||||||
8-byte and PC 16-byte `Gfx` strides, so converted commands land mid-slot
|
|
||||||
and a subsequent `G_SETTIMG` w1 reads as garbage.
|
|
||||||
|
|
||||||
12 levels PASS — they either don't hit this model-GDL path or hit it with
|
|
||||||
a GDL whose misalignment happens to be benign.
|
|
||||||
|
|
||||||
## TASK
|
|
||||||
Pin the exact stride/offset error in the model-GDL runtime relocation and
|
|
||||||
fix it narrowly (`#ifdef PORT`, N64 line under `#else`, §F-documented).
|
|
||||||
Likely spots:
|
|
||||||
- `src/game/tex.c` `texLoadFromGdl()` — src/dst pointer advance per Gfx.
|
|
||||||
- `src/game/objecthandler_2.c` `sub_GAME_7F0762E0` — `replacementgdl` /
|
|
||||||
`srcsize` math, GDL buffer sizing.
|
|
||||||
- `src/game/model.c` `modelNodeReplaceGdl` / rwdata GDL slot stride.
|
|
||||||
- The `0xba` (`G_GEGBIMACRO_*` / GE tex-load macro) decode — fast3d may
|
|
||||||
need to consume its extra word (cf. gmain.s).
|
|
||||||
|
|
||||||
Use the existing `GE_D86` / model-node probes and add a capped `GE_C2GDL`
|
|
||||||
probe if needed.
|
|
||||||
|
|
||||||
## FILES YOU MAY TOUCH
|
|
||||||
`src/game/tex.c`, `src/game/objecthandler_2.c`, `src/game/model.c`
|
|
||||||
(narrow `#ifdef PORT`); `port/fast3d/gfx_pc.cpp` (only a guard / the 0xba
|
|
||||||
consume, if that's the cause); new `tools_pc/c2gdl_*` probes;
|
|
||||||
`docs/PORT-LEARNINGS.md`, `docs/PCPortResearch.md` §F (extend D124 or new
|
|
||||||
Dxx), `docs/LEVEL-STATUS.md`.
|
|
||||||
**NOT** `src/game/propobj.c` / `prop.c` / `tools_pc/d88*` (C3 agent owns
|
|
||||||
those), **NOT** `port/src/gimgfixup.c` (D124-Jungle, done).
|
|
||||||
|
|
||||||
## KNOWN-GOOD / RULED OUT
|
|
||||||
- D123 (C1), D124-Jungle (`gimgfixup.c`) — landed, work.
|
|
||||||
- 12 levels PASS — the common GDL path is fine.
|
|
||||||
- Global-bank / explosion-DL sync (D124) — separate, fixed.
|
|
||||||
- HUD/text X-mirror (D114/D116) — parked, do NOT re-trace.
|
|
||||||
|
|
||||||
## PRE-FLIGHT
|
|
||||||
- `export PATH="/c/msys64/mingw64/bin:$PATH" && ./build-pc.sh ntsc-final` — green.
|
|
||||||
- Repro `./build-pc/ge007.x86_64.exe -level_34`. `ge007.crash.log`;
|
|
||||||
`addr2line -e build-pc/ge007.x86_64.exe -f -C 0x<PC>`.
|
|
||||||
- `GE_PCDUMP="80-260:40"` → `./ppm/`; `python tools_pc/pixcount.py`.
|
|
||||||
- `timeout` does NOT kill on Windows: `... &`, `sleep 24`,
|
|
||||||
`taskkill //F //IM ge007.x86_64.exe`.
|
|
||||||
- gdb attach fast (`gdb -batch -x cmds.txt -p <winpid>`). HW watchpoint on
|
|
||||||
the `dst` cursor in `texLoadFromGdl` catches the mis-advance.
|
|
||||||
- Regen sidecars after any converter change (none expected here):
|
|
||||||
`python tools_pc/d43_emit.py ntsc-final && python tools_pc/d69_emit.py ntsc-final && python tools_pc/d88_emit.py ntsc-final --regen`.
|
|
||||||
|
|
||||||
## BUDGET
|
|
||||||
Structural / shared-infra → **~14 build→run→inspect cycles or ~2 h.** On
|
|
||||||
expiry: revert temp probes, write up the stride spec + best hypothesis in
|
|
||||||
§F with a confidence rating + files touched. A good half-solved write-up
|
|
||||||
is a deliverable.
|
|
||||||
|
|
||||||
## CONSTRAINTS
|
|
||||||
No game-logic / control-flow changes. ABI/layout/format only, `#ifdef
|
|
||||||
PORT` with N64 line under `#else`, §F-documented. Revert your own probes.
|
|
||||||
Don't touch the C3 agent's files.
|
|
||||||
|
|
||||||
## REPORT
|
|
||||||
1. Root cause — `file:line`, the exact stride/offset bug, why 12 levels
|
|
||||||
are unaffected.
|
|
||||||
2. Fix diff or why-not.
|
|
||||||
3. Verification — `-level_34` + `-level_35` boot to ≥1 non-degenerate
|
|
||||||
frame (`pixcount.py` > a few %); `-level_09` + `-level_20` unregressed
|
|
||||||
(`framediff.py`); re-check Jungle `-level_37` + Streets `-level_29`
|
|
||||||
(may improve).
|
|
||||||
4. Probes left in tree.
|
|
||||||
5. Confidence rating.
|
|
||||||
6. Generalisable quirk appended to `docs/PORT-LEARNINGS.md`.
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
# BRIEF C3+C6 — prop/door/item model-load crashes (Aztec, Bunker2, Surface2)
|
|
||||||
|
|
||||||
Dispatch AFTER C1 lands (shares `tools_pc/d88_propdefs.py` +
|
|
||||||
`src/game/prop*.c` with C1 — do not run concurrently with C1).
|
|
||||||
|
|
||||||
Repo: `REPO_ROOT` (git `master`, Windows).
|
|
||||||
|
|
||||||
## READ FIRST
|
|
||||||
- `docs/HANDOFF.md` top, `docs/PORT-LEARNINGS.md` §A + §C (esp. the D122
|
|
||||||
"polymorphic-record converter with a forgotten type" note).
|
|
||||||
- `docs/PCPortResearch.md` §F **D122, D120, D112, D53.2, D57, D67**
|
|
||||||
(jump via index).
|
|
||||||
- `docs/BRIEF-D122-per-level-modelload.md` — direct predecessor, same class.
|
|
||||||
- `AGENTS.md` non-negotiables.
|
|
||||||
|
|
||||||
## SYMPTOM (WS4 sweep — `docs/LEVEL-STATUS.md`)
|
|
||||||
| Level | # | Site | Note |
|
|
||||||
|---|---|---|---|
|
|
||||||
| Aztec | 28 | `propobj.c:13601` `door->model->obj->RootNode->Child->Child` | fault 0x10 |
|
|
||||||
| Bunker2 | 27 | `propobj.c:13523` door displacement `linkedDoor` list walk | fault 0x8 |
|
|
||||||
| Surface2 | 43 | `loadobjectmodel.c:393` `PitemZ_entries[modelid].header->RootNode` | fault 0x0 — direct D122 continuation |
|
|
||||||
|
|
||||||
All three: a prop/door/item model pointer or model-id from a `Usetup*Z`
|
|
||||||
propDef record is wrong. D122 fixed the `[s16 obj][s16 pad]` half-swap for
|
|
||||||
6 `inherits ObjectRecord` types; these levels emit a door subtype or
|
|
||||||
another record type whose `model` / `linkedDoor` / `modelid` field is
|
|
||||||
still converted wrong (or a placeholder-sized type:
|
|
||||||
`VEHICLE`/`AIRCRAFT`/`TANK`/`AMMO`/`DEPOSIT_IN_ROOM` per
|
|
||||||
`d88_propdefs.py`). Run `tools_pc/d88_propdef_scan.py` for the per-level
|
|
||||||
type histogram; diff Aztec/Bunker2/Surface2 against the 7 passing levels.
|
|
||||||
|
|
||||||
## TASK
|
|
||||||
Root-cause all three (likely one converter gap), extend
|
|
||||||
`tools_pc/d88_propdefs.py` (+ `sizepropdef()` `#ifdef PORT` stride in
|
|
||||||
`src/game/prop.c` to match, per the D122 pattern). Resolve any
|
|
||||||
placeholder `PROPDEF_PC_BYTES` via `tools_pc/d88_layoutprobe.c`.
|
|
||||||
|
|
||||||
## FILES YOU MAY TOUCH
|
|
||||||
`tools_pc/d88_propdefs.py`, `d88_emit.py`, `d88_layoutprobe.c`,
|
|
||||||
`d88_propdef_scan.py`; `src/game/prop.c`, `src/game/propobj.c`,
|
|
||||||
`src/game/loadobjectmodel.c` (narrow `#ifdef PORT`); new `tools_pc/c3_*`
|
|
||||||
probes; `docs/PORT-LEARNINGS.md`, `docs/PCPortResearch.md` §F,
|
|
||||||
`docs/LEVEL-STATUS.md`.
|
|
||||||
|
|
||||||
## KNOWN-GOOD / RULED OUT
|
|
||||||
D121, D122 (committed `f2beae4b`), the 7 passing levels' prop/model load,
|
|
||||||
C1's chr fix (landed first — rebase on it).
|
|
||||||
|
|
||||||
## PRE-FLIGHT / ENV / BUDGET / CONSTRAINTS / REPORT
|
|
||||||
Same as `docs/BRIEF-C1-chr-airecord.md` (repro `-level_28` / `-level_27` /
|
|
||||||
`-level_43`; `taskkill //F //IM ge007.x86_64.exe`; regen all 3 sidecars
|
|
||||||
after converter change; `#ifdef PORT` with N64 line under `#else`; verify
|
|
||||||
the 3 levels boot to a frame + `-level_09`/`-level_20` unregressed).
|
|
||||||
Budget: **~12 cycles / ~90 min.**
|
|
||||||
|
|
||||||
## STILL OPEN AFTER THIS (singletons, own briefs later)
|
|
||||||
- **C4** Depot 30 — `prop.c:902` `sp4C->room` after
|
|
||||||
`walkTilesBetweenPoints_NoCallback` (BG tile/room table).
|
|
||||||
- **C5** Control 23 — `bg.c:5723` `portal_pts->numPoints`
|
|
||||||
(`g_BgPortals[].offset_portal` unresolved/BE), via `chrprop.c:62`.
|
|
||||||
- **C7** Surface1 36 — `snd.c:653` `sndSetupSound` (audio subsystem is
|
|
||||||
parked; may just need a narrow load-time guard until Phase 3).
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
# BRIEF D122 — per-level prop/item model-load crash (blocks the level sweep)
|
|
||||||
|
|
||||||
Repo: `REPO_ROOT` (git, branch `master`, Windows;
|
|
||||||
PowerShell + Bash tool available).
|
|
||||||
|
|
||||||
## READ FIRST
|
|
||||||
|
|
||||||
- `docs/HANDOFF.md` (top) — current state, environment, repro line.
|
|
||||||
- `docs/PORT-LEARNINGS.md` — recurring bug classes. **A / C are the
|
|
||||||
prime suspects here** (pointer-width struct growth; BE rodata on LE).
|
|
||||||
- `docs/PCPortResearch.md` §F index (top) + entries **D43, D53.2, D56,
|
|
||||||
D57, D67, D68, D39, D108–D112, D120** — model/asset conversion +
|
|
||||||
pointer-width history. Jump to each via the index; do NOT linear-read.
|
|
||||||
- `AGENTS.md` non-negotiables (esp. #2 game logic unmodified — narrow
|
|
||||||
`#ifdef PORT` ABI/layout/format edits only, each logged in §F).
|
|
||||||
|
|
||||||
## CONTEXT (established this session, M-12)
|
|
||||||
|
|
||||||
- **WS1 landed** (commit `02c12068`, D121): a bare
|
|
||||||
`./build-pc/ge007.x86_64.exe -level_XX` now auto-injects that stage's
|
|
||||||
`memallocstringtable[]` pool row. Every solo level boots with correct
|
|
||||||
pools now — no manual `-m*` needed.
|
|
||||||
- **D88.4 (propDefs converter) is effectively done for the 21 solo
|
|
||||||
levels.** `tools_pc/d88_emit.py` + `d88_propdefs.py` are committed
|
|
||||||
(`ff563812`, `4ccc0d3c`); `python tools_pc/d88_emit.py ntsc-final
|
|
||||||
--regen` reports `21/21 converted … ALL CHECKS PASSED`. The placeholder
|
|
||||||
`PROPDEF_PC_BYTES` for VEHICLE/AIRCRAFT/TANK/AMMO are **not exercised by
|
|
||||||
any solo level** (`tools_pc/d88_propdef_scan.py` histogram — no
|
|
||||||
VEHICLE/AIRCRAFT/TANK in any of the 21). So D88.4 is NOT the blocker.
|
|
||||||
- **The actual blocker: prop/item model loading crashes on most levels.**
|
|
||||||
Smoke test (each `-level_XX`, `GE_PCDUMP="60-240:60"`, ~25 s):
|
|
||||||
|
|
||||||
| level | # | result |
|
|
||||||
|---|---|---|
|
|
||||||
| Bunker1 | 09 | renders, playable (known good) |
|
|
||||||
| Silo | 20 | renders ~91% frame content, no crash |
|
|
||||||
| Dam | 33 | **CRASH** `modelInitRwData` (`src/game/model.c:6249`) — `node->Opcode` deref, bad `node` in the ModelNode walk |
|
|
||||||
| Facility | 34 | **CRASH** `modelLoad` (`src/game/loadobjectmodel.c:393`) — `PitemZ_entries[modelid].header->RootNode`, bad `header` (or OOB `modelid`) |
|
|
||||||
| Runway | 35 | **CRASH** same as Facility (`loadobjectmodel.c:393`) |
|
|
||||||
|
|
||||||
Crashes are **early and deterministic** (before frame 1 renders), same
|
|
||||||
PC every run — distinct from BUNKER1's rare intermittent
|
|
||||||
`loadobjectmodel.c:393` (D117 nondeterminism). `EXCEPTION 0xc0000005`,
|
|
||||||
`FAULT ADDR: ffffffffffffffff`. `Rax` at the Facility crash holds ASCII
|
|
||||||
bytes ("…o ng nol…") — looks like a model/texture name string landed in
|
|
||||||
a register, consistent with a mis-strided table read.
|
|
||||||
|
|
||||||
## TASK
|
|
||||||
|
|
||||||
**One question: why do prop/item models load fine for BUNKER1/Silo but
|
|
||||||
crash for Dam/Facility/Runway, and what is the narrow fix?**
|
|
||||||
|
|
||||||
Almost certainly one of:
|
|
||||||
1. `PitemZ_entries[341]` (`struct ItemModelFileRecord { ModelFileHeader
|
|
||||||
*header; char *filename; float scale; }`, `src/bondtypes.h:1466`) —
|
|
||||||
table stride or the `header`/`filename` pointer values wrong on PC.
|
|
||||||
Find where this array is **defined** (it is `extern` everywhere I
|
|
||||||
looked — `src/game/prop.c:40`, `chrobjdata.h:20`; the definition is
|
|
||||||
likely a generated `.s`/rodata blob or an asset — `grep` harder,
|
|
||||||
check `port/src/romassets_*.s`, `scripts/gen_romassets.py`,
|
|
||||||
`scripts/gen_*`). If the `header` pointers come from BE ROM rodata
|
|
||||||
they need rebasing/byteswap (D39 Globalimagetable / D68 class).
|
|
||||||
2. `modelid` passed to `modelLoad` is out of range / unswapped — it comes
|
|
||||||
from a propDef `ObjectRecord`-family `modelid` field. If a record type
|
|
||||||
used by Dam/Facility props (but not BUNKER1) has its `modelid` at a
|
|
||||||
field offset the `d88_propdefs.py` converter byteswaps wrongly (or
|
|
||||||
not at all), `modelid` is garbage. Cross-check the per-type field
|
|
||||||
maps in `tools_pc/d88_propdefs.py` against the record structs.
|
|
||||||
3. The `pcmodels` sidecar (`tools_pc/d43_emit.py`, loaded at
|
|
||||||
`0x10C00000`, "512 sidecars") is missing/mis-converting a model
|
|
||||||
feature (ModelNode opcode, `PointUsage[]` chain — cf. D120,
|
|
||||||
`d43_emit.py` opcode-0x18 gap) that Dam/Facility prop models use.
|
|
||||||
`model.c:6249` already has a `GE_D86` env probe for the node walk —
|
|
||||||
use it.
|
|
||||||
|
|
||||||
Determine which, root-cause, and apply the **narrowest** fix (converter
|
|
||||||
extension preferred over runtime fixup for a whole format — D43/D88
|
|
||||||
pattern; or a narrow `#ifdef PORT` ABI edit with the N64 line kept under
|
|
||||||
`#else`).
|
|
||||||
|
|
||||||
## FILES YOU MAY TOUCH
|
|
||||||
|
|
||||||
- `src/game/model.c`, `src/game/loadobjectmodel.c`, `src/game/prop.c`
|
|
||||||
(narrow `#ifdef PORT` only).
|
|
||||||
- `tools_pc/d43_emit.py` (+ `tools_pc/d43_*.py` helpers, `d43_layoutprobe.c`).
|
|
||||||
- `tools_pc/d88_propdefs.py` / `tools_pc/d88_emit.py` (only if the cause
|
|
||||||
is a propDef `modelid` field).
|
|
||||||
- `port/src/` model/asset shims if the table is patched at runtime like
|
|
||||||
`pcmodelsPatchTable` (`grep pcmodelsPatchTable`, `port/src/romdata*.c`).
|
|
||||||
- New probe files: `tools_pc/d122_*.py` / env-gated `GE_D122` blocks.
|
|
||||||
- `docs/PORT-LEARNINGS.md` (append any new generalisable quirk),
|
|
||||||
`docs/PCPortResearch.md` §F (write up as **D122**, add to the index).
|
|
||||||
|
|
||||||
Nothing else is running — no file collisions to worry about.
|
|
||||||
|
|
||||||
## KNOWN-GOOD / RULED OUT — do not re-investigate
|
|
||||||
|
|
||||||
- WS1 per-level pool injection (D121) — works, committed.
|
|
||||||
- D88.4 propDefs stream conversion for solo levels — works (21/21).
|
|
||||||
- BUNKER1 + Silo prop/model loading — works.
|
|
||||||
- The intermittent BUNKER1 `loadobjectmodel.c:393` crash — that is
|
|
||||||
D117-class nondeterminism, separate; do not chase it, use the
|
|
||||||
deterministic Dam/Facility/Runway repro.
|
|
||||||
- `d88_propdef_scan.py` "STOP … unmapped type NOTHING" — that is the
|
|
||||||
*scan tool's* limitation, not a converter bug (`d88_emit.py` completes).
|
|
||||||
|
|
||||||
## PRE-FLIGHT (done — results above)
|
|
||||||
|
|
||||||
- `./build-pc.sh ntsc-final` green; binary at `build-pc/ge007.x86_64.exe`.
|
|
||||||
- Sidecars regenerated this session (`d43_emit.py`, `d69_emit.py`,
|
|
||||||
`d88_emit.py --regen`) — all pass.
|
|
||||||
- Smoke-test table above.
|
|
||||||
|
|
||||||
## ENVIRONMENT
|
|
||||||
|
|
||||||
```sh
|
|
||||||
export PATH="/c/msys64/mingw64/bin:$PATH"
|
|
||||||
./build-pc.sh ntsc-final # ~5 s, from repo root
|
|
||||||
./build-pc/ge007.x86_64.exe -level_34 # Facility repro (bare — D121)
|
|
||||||
```
|
|
||||||
|
|
||||||
- `timeout` does NOT reliably kill the game on Windows. Launch with
|
|
||||||
`… &`, `sleep N`, then `kill $PID; pkill -f ge007`.
|
|
||||||
- Crash log: `ge007.crash.log` (repo root). Symbolicate:
|
|
||||||
`addr2line -e build-pc/ge007.x86_64.exe -f -C 0x<PC>` (image base
|
|
||||||
`0x140000000`; PC in the log is already absolute).
|
|
||||||
- Frame capture: `GE_PCDUMP="a-b:step"` → `./ppm/`; content check
|
|
||||||
`python tools_pc/pixcount.py ppm/frame_XXXX.ppm`.
|
|
||||||
- gdb: **attach** mode is fast (`gdb -batch -x cmds.txt -p <winpid>`,
|
|
||||||
winpid = 4th col of `ps -p <bashpid>`; game must already be running).
|
|
||||||
Launch mode is too slow. A hardware watchpoint on `PitemZ_entries` or
|
|
||||||
the bad `node` catches the corrupting write fast.
|
|
||||||
- Standalone probe compiles: `-std=c11`.
|
|
||||||
|
|
||||||
## BUDGET
|
|
||||||
|
|
||||||
Structural bug → generous: **~12 build→run→inspect cycles or ~90 min**.
|
|
||||||
On expiry: revert all temp probes (leave only committed env-gated ones),
|
|
||||||
write up the spec progress + best hypothesis in `docs/PCPortResearch.md`
|
|
||||||
§F as **D122** with an explicit confidence rating, and list exactly which
|
|
||||||
files you changed. A good half-solved write-up is a deliverable.
|
|
||||||
|
|
||||||
## CONSTRAINTS
|
|
||||||
|
|
||||||
- No game-logic / control-flow changes. ABI/layout/format only, each
|
|
||||||
`#ifdef PORT` with the N64 line kept verbatim under `#else`, each
|
|
||||||
documented in §F/D122.
|
|
||||||
- Prefer an offline sidecar-converter extension over a runtime fixup for
|
|
||||||
a whole asset format.
|
|
||||||
- Revert your own temp probes before reporting.
|
|
||||||
- Regenerate sidecars after any converter change:
|
|
||||||
`python tools_pc/d43_emit.py ntsc-final && python tools_pc/d69_emit.py
|
|
||||||
ntsc-final && python tools_pc/d88_emit.py ntsc-final --regen`.
|
|
||||||
|
|
||||||
## REPORT
|
|
||||||
|
|
||||||
1. Root cause — `file:line` evidence, which of the 3 hypotheses (or
|
|
||||||
other), why Dam/Facility/Runway differ from BUNKER1/Silo.
|
|
||||||
2. Fix — the diff, or why-not if unresolved.
|
|
||||||
3. Verification — `-level_33`, `-level_34`, `-level_35` each boot to ≥1
|
|
||||||
non-degenerate frame (`pixcount.py` > a few %), AND `-level_09`
|
|
||||||
(BUNKER1) still renders (no regression — `tools_pc/framediff.py`).
|
|
||||||
4. Probes left in the tree (env-gated, capped).
|
|
||||||
5. Confidence rating.
|
|
||||||
6. Append any generalisable quirk to `docs/PORT-LEARNINGS.md`.
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
> **RESOLVED — historical.** D69 is closed (see PCPortResearch.md §F/§H
|
|
||||||
> D69, D78–D84). Kept for the bg/stan format spec. Not active work.
|
|
||||||
|
|
||||||
# Brief — M1 / D69: Stage (BG/stan) loading for PC
|
|
||||||
|
|
||||||
_Paste-ready milestone brief. Authoritative context: `AGENTS.md`,
|
|
||||||
`docs/HANDOFF.md`, `docs/PCPortResearch.md` §F/D69 (+D68), §G, §H._
|
|
||||||
|
|
||||||
## Mission
|
|
||||||
|
|
||||||
Get the first stage (**BUNKER1**) to load and render after the intro, without
|
|
||||||
faulting. Then extend the same conversion to **all levels'** `bg/*.seg` +
|
|
||||||
`Tbg_*_stanZ` files (all three regions). Success bar: **loads without fault +
|
|
||||||
plausible pixels** — not pixel-perfect (3D pipeline correctness is a separate
|
|
||||||
milestone, D75; do not let visual wrongness displace this one).
|
|
||||||
|
|
||||||
## Non-negotiables (from AGENTS.md)
|
|
||||||
|
|
||||||
1. N64 build untouched: `Makefile`, `tools/`, `rsp/`, `ld/`.
|
|
||||||
2. Game logic unmodified. Only narrow `#ifdef PORT` ABI/layout exceptions,
|
|
||||||
each documented as its own finding in §F. **The intended solution for this
|
|
||||||
task requires ZERO game-code edits** — pure offline data conversion +
|
|
||||||
port-layer table patch. If you believe a game-file edit is needed, stop and
|
|
||||||
document why as a finding before making it.
|
|
||||||
3. Offline sidecar conversion preferred over runtime fixup (AGENTS.md; the
|
|
||||||
D43/Plan-B precedent in `tools_pc/d43_emit.py` + `port/src/pcmodels.c`).
|
|
||||||
4. The PD port (`PD_PORT_CHECKOUT`) is an *approach*
|
|
||||||
reference only — its BG format differs (zipped multi-section). Validate
|
|
||||||
every field against GE's own consumers (`bg.c`, `stan.c`, N64 disasm).
|
|
||||||
|
|
||||||
## Current state (verified)
|
|
||||||
|
|
||||||
- Build GREEN: `export PATH="/c/msys64/mingw64/bin:$PATH" && ./build-pc.sh ntsc-final` (~5 s).
|
|
||||||
- Game boots → full intro (Rareware logo, gun barrel w/ Brosnan, cast; ~frame
|
|
||||||
2100) → crashes in `load_bg_file`.
|
|
||||||
- Crash: EXCEPTION 0xc0000005; symbolicated PC = **`load_bg_file`,
|
|
||||||
src/game/bg.c:865**; the crash stack contains BG header words
|
|
||||||
`0x0F000334` / `0x0F00032C` — confirms the BE-offset misread below.
|
|
||||||
|
|
||||||
## The bug, precisely
|
|
||||||
|
|
||||||
- BG-file internal offsets are N64 **big-endian u32s in segment-0x0F form**
|
|
||||||
(`0x0Fxxxxxx`).
|
|
||||||
- `BG_SEG_TO_PTR` (bg.h:23):
|
|
||||||
`((void*)(((u32)(base)) + (((u32)(off)) + 0xF1000000)))` — on N64,
|
|
||||||
`off = 0x0Fxxxxxx` and `+ 0xF1000000` wraps in 32-bit arithmetic to the
|
|
||||||
plain file offset `0x00xxxxxx`.
|
|
||||||
- On PC, `((s32 *)ptr_bg_data)[1]` reads LE: ROM bytes `0F 00 00 14` become
|
|
||||||
`0x1400000F`; the fold yields `base + 0x0500000F` ≈ 5 MB past the header
|
|
||||||
buffer → fault dereferencing `[1].pPointTableBin`.
|
|
||||||
- Scope: every bg `.seg` header word and record (room file-position list,
|
|
||||||
portals, etc.) **and** the whole `Tbg_*_stanZ` geometry file
|
|
||||||
(`StanPrefixRecord` + everything `stanDetermineEOF`/`stanLoadFile` walk).
|
|
||||||
Same class as D68 (image table) but a far larger format surface.
|
|
||||||
|
|
||||||
## Known data points (BUNKER1)
|
|
||||||
|
|
||||||
- Per-level filenames: `levelinfotable` (bg.c ~line 183) →
|
|
||||||
`bg_seg_filename` / `bg_stan_filename`. Name→symbol map:
|
|
||||||
`assets/obseg/file_resource_table.inc.c` (e.g. line 6
|
|
||||||
`"bg/bg_sev_all_p.seg"` → `&bg_sev_all_p_seg`; line 586
|
|
||||||
`"Tbg_sev_all_p_stanZ"` → `&Tbg_sev_all_p_stanZ`).
|
|
||||||
- BUNKER1 files: `bg/bg_sev_all_p.seg` @ cart **0x10438660**, size **0x10DF0**;
|
|
||||||
`Tbg_sev_all_p_stanZ` @ cart **0x1087C3F0**, size **0x3DD0**.
|
|
||||||
- Header words (BE values): word0 = `0x00000000`; word1 = `0x0F000014`
|
|
||||||
(room file-position list @ file offset 0x14); word2 = `0x0F000334`;
|
|
||||||
word3 = `0x0F00032C`.
|
|
||||||
- `bg_room_data` records: `pPointTableBin` at record+0x28 (per crash disasm),
|
|
||||||
more `0x0Fxxxxxx` offsets per record.
|
|
||||||
- Load path: `obLoadBGFileBytesAtOffset(name, buf, offset, size)` does partial
|
|
||||||
loads via memcpy from `&fileentry->hw_address[offset]`; stan files load via
|
|
||||||
`_fileNameLoadToBank(..., bank 2)`. Both already work on PC — the bug is
|
|
||||||
**interpretation only**, so redirecting `hw_address` at converted bytes is
|
|
||||||
sufficient.
|
|
||||||
|
|
||||||
## Task 1 — decode the formats (write notes as you go)
|
|
||||||
|
|
||||||
1. Dump BUNKER1's `.seg` header + first records from the ROM; map every word
|
|
||||||
to its consumer in `bg.c` (`load_bg_file` ~line 795+ and the table walks it
|
|
||||||
performs: rooms/portals/bgcmds/lights). Note which words are
|
|
||||||
`0x0Fxxxxxx` offsets vs plain data.
|
|
||||||
2. Same for `Tbg_sev_all_p_stanZ`: `StanPrefixRecord` + whatever
|
|
||||||
`stanDetermineEOF`/`stanLoadFile` walk (`src/game/stan.c`). Identify vertex
|
|
||||||
batches, bounds (cf. `RoomVtxBatchBounds` in bg.h), Gfx/DL references, and
|
|
||||||
texture refs inside stan data.
|
|
||||||
3. **Classify every field:** u32 numeric (bswap32 for PC) / s16 numeric
|
|
||||||
(bswap16) / byte stream (leave alone — compressed payloads etc.). Where the
|
|
||||||
decomp is ambiguous, cross-check struct field offsets against the N64
|
|
||||||
disasm of the consumer function.
|
|
||||||
4. Write the format notes into `PCPortResearch.md` §F as you go (labels
|
|
||||||
D78+) — they are the converter spec and must be complete enough to
|
|
||||||
regenerate the converter from them.
|
|
||||||
|
|
||||||
## Task 2 — implement offline conversion (Plan-B pattern)
|
|
||||||
|
|
||||||
**Key design constraint: in-place, size-preserving conversion.** If every
|
|
||||||
converted file is byte-identical in *size* to its ROM original, all internal
|
|
||||||
`0x0Fxxxxxx` offsets remain valid after `BG_SEG_TO_PTR` folding, and the game
|
|
||||||
code needs **zero changes** — bswapped words read back as the original BE
|
|
||||||
values. (bswap32/bswap16 are size-preserving by construction; byte streams
|
|
||||||
pass through untouched.)
|
|
||||||
|
|
||||||
1. New converter `tools_pc/d69_emit.py` (model on `d43_emit.py`): per region,
|
|
||||||
for every `bg/*.seg` + `Tbg_*_stanZ` referenced by `levelinfotable`:
|
|
||||||
extract from ROM → convert per the Task 1 spec → concatenate into
|
|
||||||
`data/pccg-<region>/pccg.bin` + `manifest.csv` (`name,offset,size`,
|
|
||||||
decimal — same format as pcmodels).
|
|
||||||
2. Port layer: clone the `port/src/pcmodels.c` pattern (it is the template —
|
|
||||||
read it first): sidecar image lives in the cart reservation extension at
|
|
||||||
`[CART_BASE + romSize, ...)`; a one-shot table patch matches
|
|
||||||
`file_resource_table[]` by filename and rewrites `hw_address` +
|
|
||||||
`rom_size`. Either extend `pcmodels.c` or add `port/src/pccg.c`. Wiring
|
|
||||||
points (all small):
|
|
||||||
- `port/src/romdata.c` ~line 165: include the pccg size in the
|
|
||||||
`VirtualAlloc(CART_BASE, romSize + sideTotal)` reservation; load the
|
|
||||||
sidecar next to `pcmodelsLoadSidecars`.
|
|
||||||
- `port/src/romdata.c` ~line 253 (`romdataCartAddrValid`) and
|
|
||||||
`port/src/libultra.c` ~line 821 (DMA source validation, D60 safety net):
|
|
||||||
extend the bounds check to cover pccg bytes.
|
|
||||||
- One-shot patch call site: same pattern as
|
|
||||||
`load_object_fill_header → pcmodelsPatchTable()` (or hook the BG load
|
|
||||||
path's first use — your choice, document it).
|
|
||||||
3. Regenerate per region (`ntsc-final` / `pal-final` / `jpn-final`); add
|
|
||||||
`data/pccg-*/` to `.gitignore` like `pcmodels-*`.
|
|
||||||
|
|
||||||
**Fallback:** if the decode shows only a handful of fields matter for
|
|
||||||
first-frame render, a runtime port-layer fixup hook (D68-style bswap after
|
|
||||||
load) is acceptable as an *interim* step — but stage geometry is deep; expect
|
|
||||||
most fields to need conversion anyway. Document the choice as a finding.
|
|
||||||
|
|
||||||
## Task 3 — verify
|
|
||||||
|
|
||||||
1. **Clean run from the repo root** (`cd <repo> && ./build-pc/ge007.x86_64.exe`)
|
|
||||||
— running from `build-pc/` picks up the wrong ROM and misses sidecars
|
|
||||||
(spurious early crash). Expect: intro → BUNKER1 loads without fault; stage
|
|
||||||
geometry visible (plausible pixels, not pixel-perfect).
|
|
||||||
2. `GE_PCDUMP="2100-2400:5"` frames + `tools_pc/pixcount.py` — confirm
|
|
||||||
non-black stage content past the old crash point.
|
|
||||||
3. **All levels:** converter must cover every bg/stan file in
|
|
||||||
`levelinfotable` (20+); the table patch logs its hit count — verify it
|
|
||||||
matches the manifest row count exactly.
|
|
||||||
4. Verification ritual (AGENTS.md): build green; no new undefined/duplicate
|
|
||||||
symbols (`/linkcheck`); every touched file parses.
|
|
||||||
5. Strip TEMP probes before committing (keep the `GE_D69` probe in `ob.c`
|
|
||||||
until resolved, then strip it per the HANDOFF Task 3 list).
|
|
||||||
|
|
||||||
## Acceptance criteria (all must hold)
|
|
||||||
|
|
||||||
- [ ] Clean run reaches BUNKER1 without fault (no new `ge007.crash.log` past ~frame 2100).
|
|
||||||
- [ ] Stage renders plausible geometry (PPM-verified, non-black content).
|
|
||||||
- [ ] Converter covers all levels' bg/stan files; all three regions regenerable from the script.
|
|
||||||
- [ ] Zero game-code edits — or any `#ifdef PORT` edit individually justified and documented as a D7x finding.
|
|
||||||
- [ ] Format spec written in `PCPortResearch.md` §F (D78+).
|
|
||||||
- [ ] Build green + `/linkcheck` clean; TEMP probes stripped.
|
|
||||||
|
|
||||||
## Environment gotchas
|
|
||||||
|
|
||||||
- MSYS2 tools: `export PATH="/c/msys64/mingw64/bin:$PATH"`. Build ~5 s.
|
|
||||||
- Run the game from the **repo root**, not `build-pc/`.
|
|
||||||
- Crash log `ge007.crash.log` (repo root) is the first stop for any fault;
|
|
||||||
symbolicate offline: `addr2line -e build-pc/ge007.x86_64.exe -f -C <pc>`
|
|
||||||
(image base 0x140000000).
|
|
||||||
- gdb is launch-mode only and far too slow for timing-dependent issues —
|
|
||||||
prefer env-gated probes + the crash log.
|
|
||||||
- Heavy logging perturbs tick-driven timing (`osGetCount` advances at
|
|
||||||
~46.5525 ticks/µs) — diagnose stability with clean runs only; use short,
|
|
||||||
targeted probe windows.
|
|
||||||
- `Gfx` is 16 bytes on PC; ROM-copied N64 DLs stay 8 bytes in memory — walk
|
|
||||||
them at 8-byte stride when scanning.
|
|
||||||
- N64 CPU/RDP data in ROM is big-endian; any raw `*(u32*)` read of a
|
|
||||||
ROM-copied buffer needs bswap under PORT (D68 pattern) or offline conversion.
|
|
||||||
- **Don't "fix" the s32 address idioms.** The decomp passes addresses through
|
|
||||||
`s32`/`u32` (e.g. `ptr_bg_data = (s32)header`); this works because all
|
|
||||||
emulated addresses (cart 0x10000000, DRAM 0x70/0x80xxxxxx) and host
|
|
||||||
stack/heap fit in the low 4 GB, so truncation is lossless. Changing these to
|
|
||||||
64-bit types breaks the `BG_SEG_TO_PTR` fold and is out of scope.
|
|
||||||
- Standalone probe compiles need `-std=c11`.
|
|
||||||
|
|
||||||
## Out of scope (do not let these displace the milestone)
|
|
||||||
|
|
||||||
- D75 (3D matrix/model bugs), D76 (copyright-screen 2D partial draw),
|
|
||||||
D77 (no audio). The stage may render *wrong* — that is expected; the bar is
|
|
||||||
loads-without-fault + plausible pixels.
|
|
||||||
- Pixel-perfect intro-logo check vs N64 footage.
|
|
||||||
|
|
||||||
## Documentation requirements
|
|
||||||
|
|
||||||
- New findings: `PCPortResearch.md` §F, labels **D78+** (D75–D77 are taken).
|
|
||||||
- Update §G status and `docs/HANDOFF.md` at each sub-milestone.
|
|
||||||
- Commit per sub-milestone: (1) format spec notes, (2) converter + sidecar,
|
|
||||||
(3) port wiring, (4) verification + probe strip.
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
# Local-agent handoff tasks (Qwen 27B) — M-20
|
|
||||||
|
|
||||||
The two M-19 tasks (D63 scaffolding removal in `libultra.c`; PORT-LEARNINGS
|
|
||||||
§E entries) are **done and committed** (`1fc3cff6`). Below are three fresh
|
|
||||||
tasks.
|
|
||||||
|
|
||||||
**All three are pure static analysis** — read code, produce a table, write
|
|
||||||
a doc section. **No builds. No running the game. No `.c` edits.** Deliverable
|
|
||||||
is a markdown write-up appended to `docs/PCPortResearch.md` §F (add the new
|
|
||||||
label to the §F index at the top of the file) plus, where noted, one line in
|
|
||||||
`docs/PORT-LEARNINGS.md`.
|
|
||||||
|
|
||||||
Disjoint from the active session work (intro-render investigation in
|
|
||||||
`port/fast3d/`, propDef converter audit in `tools_pc/d88_propdefs.py`). **Do
|
|
||||||
NOT touch `port/fast3d/*`, `tools_pc/d88*`, or any `.c`/`.cpp` file.**
|
|
||||||
|
|
||||||
READ FIRST for all tasks: `docs/PORT-LEARNINGS.md` (whole file — it is one
|
|
||||||
screen), then the specific §F entries named in each task (jump via the §F
|
|
||||||
index; do NOT linear-read `PCPortResearch.md`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task A — `osVirtualToPhysical()` latent-truncation site audit (D131 corollary)
|
|
||||||
|
|
||||||
**Background:** `docs/PORT-LEARNINGS.md` §A "D131 corollary". The port shim
|
|
||||||
`osVirtualToPhysical(va)` returns `(u32)(uintptr_t)va`, which silently drops
|
|
||||||
the high 32 bits of a **compiled module symbol** pointer (`.bss` / `.rodata`
|
|
||||||
address, image-based at `0x140000000`). D131 fixed the one live instance
|
|
||||||
(`explosionRenderPropSmoke`) centrally in `seg_addr()` in
|
|
||||||
`port/fast3d/gfx_pc.cpp`, which now restores the module high word for a
|
|
||||||
fallthrough w1 in `[0x40000000, 0x70000000)`.
|
|
||||||
|
|
||||||
**Task:** grep every call site of `osVirtualToPhysical(` across `src/`
|
|
||||||
(`grep -rn 'osVirtualToPhysical(' src/`). For each, produce a table row:
|
|
||||||
|
|
||||||
| file:line | argument expression | arg is: runtime-DRAM ptr / compiled symbol / segmented / unknown | reaches a GBI w1 (`gSPMatrix`/`gSPVertex`/`gSPDisplayList`/`gDma*`)? | covered by D131 `seg_addr` fix? | residual risk |
|
|
||||||
|
|
||||||
Classify "compiled symbol" when the argument is `&someGlobal`, a `.bss`/
|
|
||||||
`.rodata` array/struct address, or a function-local `static`. Classify
|
|
||||||
"runtime-DRAM ptr" when it is the result of a heap/bank allocation
|
|
||||||
(`mempAlloc*`, `malloc`, a `->` deref of loaded data). Note which are armed
|
|
||||||
only when a specific effect first draws (explosion / glass / blood /
|
|
||||||
bondview2 — the §A note lists these).
|
|
||||||
|
|
||||||
**Deliverable:** the table + a short "residual risk" summary (are there sites
|
|
||||||
that reach fast3d NOT through `seg_addr`, e.g. a raw `memcpy` or a
|
|
||||||
non-w1 use?) → `docs/PCPortResearch.md` §F, new subsection
|
|
||||||
**"D131 corollary — osVirtualToPhysical site audit (M-20)"**, label added to
|
|
||||||
the §F index.
|
|
||||||
|
|
||||||
**On expiry:** commit the partial table.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task B — in-place ROM-struct relayout aliasing audit (D130 corollary)
|
|
||||||
|
|
||||||
**Background:** `docs/PORT-LEARNINGS.md` §A "D130 corollary". `romdataFixupFont`
|
|
||||||
(`port/src/romdata.c`) re-lays-out a ROM struct array (`fontchar` 24→32B) *in
|
|
||||||
place*: `dst = base + PCstride*i`, `src = base + N64stride*i`, and for small
|
|
||||||
`i` the two overlap by less than the per-element read span, so a
|
|
||||||
field-by-field copy loop clobbers a not-yet-read source field. D130 fixed
|
|
||||||
that one loop by staging all fields into locals first.
|
|
||||||
|
|
||||||
**Task:** find every other in-place N64→PC struct-array relayout. Search:
|
|
||||||
`grep -rn 'romdataFixup\|Fixup\|relayout\|in.place\|24.*32\|->32B' port/src/`
|
|
||||||
and read `port/src/romdata.c` fully (it is the main offender file). For each
|
|
||||||
loop that (a) reads struct fields from a source offset and writes to a
|
|
||||||
different-stride destination **in the same buffer**, tabulate:
|
|
||||||
|
|
||||||
| file:line (fn) | struct | N64 stride | PC stride | loop direction | dst/src overlap for low i? | reads all fields before writing? | verdict (SAFE / ALIASING BUG / needs staging) |
|
|
||||||
|
|
||||||
**Deliverable:** the table → `docs/PCPortResearch.md` §F, new subsection
|
|
||||||
**"D130 corollary — in-place relayout audit (M-20)"**, label in the §F index.
|
|
||||||
If you find a concrete ALIASING BUG, describe the fix (stage fields into
|
|
||||||
locals, as D130 did) in prose in the write-up — **do not edit the `.c`
|
|
||||||
file**; flag it for the integrator.
|
|
||||||
|
|
||||||
**On expiry:** commit the partial table.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task C — negative / OOB glyph-index audit (PORT-LEARNINGS §A last bullet)
|
|
||||||
|
|
||||||
**Background:** `docs/PORT-LEARNINGS.md` §A final bullet: `chars[*text - 0x21]`
|
|
||||||
with a control byte (`*text < 0x21`) indexes before `chars[0]`. Benign on
|
|
||||||
N64 (hits the adjacent 4-byte kerning table → garbage-but-valid glyph);
|
|
||||||
**fatal on PC** — the widened `fontchar` has an 8-byte `pixeldata` at a
|
|
||||||
higher offset, so a negative index reads a wild pointer → fast3d AV. No
|
|
||||||
level is known to feed a control byte to HUD text yet, so this is latent.
|
|
||||||
|
|
||||||
**Task:** in `src/game/textrelated.c` (and any other file that indexes a
|
|
||||||
`fontchar`/`chars` array — grep `chars[` and `- 0x21` / `- 33` across
|
|
||||||
`src/game/`), enumerate every glyph-index expression on the `textRender*` /
|
|
||||||
`textMeasure*` ASCII paths. For each, tabulate:
|
|
||||||
|
|
||||||
| file:line (fn) | index expression | can the byte be `< 0x21` or `>= last glyph`? (what feeds it — dialogue? HUD? menu? filename?) | guarded already? | consequence on PC if OOB |
|
|
||||||
|
|
||||||
**Deliverable:** the table + a recommendation: where exactly a
|
|
||||||
`GLYPH_IDX` clamp macro (`clamp to [0, nglyphs-1]`, or skip the glyph) would
|
|
||||||
go, expressed as a prose description of the one-line `#ifdef PORT` guard per
|
|
||||||
site — **do not edit `.c`**. → `docs/PCPortResearch.md` §F, new subsection
|
|
||||||
**"Glyph-index OOB audit (M-20)"**, label in the §F index. Add one terse
|
|
||||||
line to `docs/PORT-LEARNINGS.md` §A noting the audit is done and where the
|
|
||||||
clamp sites are.
|
|
||||||
|
|
||||||
**On expiry:** commit the partial table.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Verify (all tasks)
|
|
||||||
|
|
||||||
`git diff --stat` shows **only** `docs/PCPortResearch.md` and (Task C)
|
|
||||||
`docs/PORT-LEARNINGS.md` changed. No other files. Markdown still renders.
|
|
||||||
`git status` shows no deleted files. Commit message per task, e.g.
|
|
||||||
`docs: M-20 Qwen — D131 corollary osVirtualToPhysical site audit`.
|
|
||||||
**No `Co-Authored-By` / `Claude-Session` trailers** (repo policy).
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
> **RESOLVED — historical.** D7 closed at the compile+link milestone.
|
|
||||||
> Kept for the AI X-macro analysis. Not active work.
|
|
||||||
|
|
||||||
# HANDOFF — AI X-macro system (D7), the last build blocker
|
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
Full `ge007` build: **224 objects compile; only `chraidata.c` fails.** All other
|
|
||||||
`.c` files build clean. Findings D1–D6 are resolved and recorded in
|
|
||||||
`docs/PCPortResearch.md` §11. D7 (this file) is the remaining blocker.
|
|
||||||
|
|
||||||
Build command:
|
|
||||||
```sh
|
|
||||||
export PATH="/c/msys64/mingw64/bin:$PATH"
|
|
||||||
cd build-pc && cmake --build . --target ge007 2>&1 | grep -A3 -i "chraidata\|aicommands\|bondaicommands"
|
|
||||||
```
|
|
||||||
|
|
||||||
## The failing file and its includes
|
|
||||||
|
|
||||||
`src/game/chraidata.c` includes (lines 14–17):
|
|
||||||
```c
|
|
||||||
#include <ultra64.h>
|
|
||||||
#include <bondgame.h>
|
|
||||||
#include <bondaicommands.h> // <- pulls in the whole AI X-macro system
|
|
||||||
#include "chrobjdata.h"
|
|
||||||
```
|
|
||||||
|
|
||||||
`bondaicommands.h` includes:
|
|
||||||
- `bondconstants.h` (which at line 17 includes `<CPPLib.h>`, and at line 731
|
|
||||||
includes `"aicommands.def"` to build the `AI_CMD` enum)
|
|
||||||
- `aicommands2.h` (line 864) — a **pre-generated** header (from
|
|
||||||
`tools/cmdbuilder.c`), already preprocessed.
|
|
||||||
|
|
||||||
So the failing path is the **raw** `aicommands.def` include (bondconstants.h:731)
|
|
||||||
plus the `bondaicommands.h` macros (`isSubroutine`, `SWITCH`, `CALL`, …) that
|
|
||||||
chraidata.c uses directly.
|
|
||||||
|
|
||||||
## Exact error chain (verified by rebuild)
|
|
||||||
|
|
||||||
Trigger: `CALL(GAILIST_PLAY_IDLE_ANIMATION)` at chraidata.c:114 (and similar at
|
|
||||||
123, 126, …). The chain:
|
|
||||||
|
|
||||||
1. `CALL(...)` expands to code that calls `isSubroutine(<ID>)`.
|
|
||||||
2. `isSubroutine(ID)` (bondaicommands.h:275–284) contains:
|
|
||||||
```c
|
|
||||||
#define isSubroutine(ID) ((ID == GAILIST_PLAY_IDLE_ANIMATION) |\
|
|
||||||
(ID == GAILIST_BASH_KEYBOARD) | \
|
|
||||||
(ID == GAILIST_ATTACK_BOND) | \
|
|
||||||
(ID == GAILIST_RUN_TO_BOND) | \
|
|
||||||
(ID == GAILIST_STARTLE_AND_RUN_TO_BOND) | \
|
|
||||||
(ID == GAILIST_WAIT_ONE_SECOND) \
|
|
||||||
IF(NOT(DEFINED(SETUPSUBROUTINES(ID))))\
|
|
||||||
(\
|
|
||||||
| SETUPSUBROUTINES(ID)\
|
|
||||||
))
|
|
||||||
```
|
|
||||||
3. `DEFINED(x)` (CPPLib.h:279–280):
|
|
||||||
```c
|
|
||||||
#define DEFINED(x) _DEFINED(x)
|
|
||||||
#define _DEFINED(x) IS_PROBE(CAT(_DEFINED, _##x##_))
|
|
||||||
```
|
|
||||||
With `x = SETUPSUBROUTINES(ID)`, the `##` pasting `_##x##_` tries to paste
|
|
||||||
`_` against `SETUPSUBROUTINES(ID)` — a **macro call (multiple tokens)**, not a
|
|
||||||
single pp-token. GCC hard-errors:
|
|
||||||
```
|
|
||||||
bondaicommands.h:281:63: error: pasting ')' and '_' does not give a valid
|
|
||||||
preprocessing token
|
|
||||||
```
|
|
||||||
4. CPPLib.h:287 has a special case that was *meant* to handle this:
|
|
||||||
```c
|
|
||||||
#define _DEFINED_SETUPSUBROUTINES(ID) PROBE(~),
|
|
||||||
```
|
|
||||||
i.e. the intent is that `_##SETUPSUBROUTINES(ID)##_` →
|
|
||||||
`_DEFINED_SETUPSUBROUTINES(ID)_` → the defined macro above. But GCC's `##`
|
|
||||||
requires each side to be a single pp-token, so it never reaches that special
|
|
||||||
case. IDO's preprocessor evidently tolerated / expanded this differently.
|
|
||||||
|
|
||||||
### Secondary errors (same root family)
|
|
||||||
|
|
||||||
- `chraidata.c:183/202/249/640`: `macro 'SWITCH' requires 49 arguments, but only
|
|
||||||
20/14/26/26 given`. The `SWITCH` macro (bondaicommands.h, ~line 776) has **49
|
|
||||||
fixed parameters** (1 VAR + 16×3 case slots) and uses `IF_VA(NOT(IS_EMPTY(..)))`
|
|
||||||
to detect which cases are present. Game code calls it with fewer args, relying
|
|
||||||
on IDO leniency. GCC enforces the exact arg count.
|
|
||||||
- `aicommands.def:514:270 / 550:21 / 2846:21`: "expected expression before ','"
|
|
||||||
/ "expected '}' before 'AI_Return'" / "expected '}' before
|
|
||||||
'AI_IFICouldSeeBond'". These are downstream of the `isSubroutine`/`DEFINED`
|
|
||||||
failure (the `CALL` expansion aborts mid-macro, leaving dangling tokens). Note
|
|
||||||
the reported line numbers (514/550/2846) do **not** match the raw
|
|
||||||
`aicommands.def` lines (those are comment/brace lines) — they are positions in
|
|
||||||
the *expanded* macro text, not the source file. Don't chase them as source
|
|
||||||
lines.
|
|
||||||
|
|
||||||
## Key facts / ground truth
|
|
||||||
|
|
||||||
- `aicommands.def` call counts: **255** `_AI_CMD(`, **119** `_AI_CMD_POLYMORPH(`,
|
|
||||||
**2** `_AI_DEBUG(`.
|
|
||||||
- `_AI_CMD_POLYMORPH` definitions across the tree:
|
|
||||||
- `aicommands.def:97` — 4-arg `(PRE, NAME, CMD_PARAMS_POST, POLY_PARAMS)` but
|
|
||||||
inside a `#if 0` block (lines 88–100) → **inactive**.
|
|
||||||
- `bondconstants.h:721` — 5-arg `(CMD, A, P, Q, D)` under `#ifdef __sgi`
|
|
||||||
(not us).
|
|
||||||
- `bondconstants.h:726` — **variadic `(...)`** under `#else` (this is the one
|
|
||||||
active for GCC when building the `AI_CMD` enum at bondconstants.h:731).
|
|
||||||
- `chrai.c:170` — 5-arg `(CMD, A, P, Q, D)`.
|
|
||||||
- `chrai.c:915` — 5-arg `(C, N, P1, P2, D)`.
|
|
||||||
- `aicommands.def:85–87`: `#ifndef __GNUC__ #include <CPPLib.h> #endif` → for GCC
|
|
||||||
CPPLib.h is **not** included from aicommands.def itself; it comes in via
|
|
||||||
`bondconstants.h:17`.
|
|
||||||
- `aicommands.def:11888`: `#undef _AI_CMD_POLYMORPH` (late in the file).
|
|
||||||
- `aicommands2.h` is pre-generated (header says "Do not edit … generated by
|
|
||||||
cmdbuilder from src/aicommands.def"). It is included by bondaicommands.h:864
|
|
||||||
and deb_loadallmodels.c:8. It is **not** the failing path.
|
|
||||||
- `isSubroutine` is the only macro that feeds a *macro call*
|
|
||||||
(`SETUPSUBROUTINES(ID)`) into `DEFINED()`. That is the unique trigger for the
|
|
||||||
`##` pasting error.
|
|
||||||
|
|
||||||
## Recommended next steps (in order)
|
|
||||||
|
|
||||||
1. **Fix the `DEFINED(SETUPSUBROUTINES(ID))` pasting** — the single root cause of
|
|
||||||
the `CALL`/`isSubroutine` errors. Preferred: a `port/` shim that redefines
|
|
||||||
`DEFINED` (or `_DEFINED`) *after* CPPLib.h is included, special-casing the
|
|
||||||
`SETUPSUBROUTINES(ID)` argument so it does not `##`-paste a multi-token
|
|
||||||
macro-call. Sketch:
|
|
||||||
```c
|
|
||||||
/* port/shim/... after <CPPLib.h> */
|
|
||||||
#undef DEFINED
|
|
||||||
#define DEFINED(x) _PORT_DEFINED(x)
|
|
||||||
#define _PORT_DEFINED(x) /* if x is SETUPSUBROUTINES(ID) -> PROBE(~), else fall back */
|
|
||||||
```
|
|
||||||
The tricky part: detecting "x is exactly `SETUPSUBROUTINES(ID)`" in the
|
|
||||||
preprocessor. Alternative: redefine `isSubroutine` in the shim to compute the
|
|
||||||
`SETUPSUBROUTINES` presence without `DEFINED()` (e.g. via a per-setup
|
|
||||||
`#define HAS_SETUPSUBROUTINES 0/1` flag set before the include). Investigate
|
|
||||||
which is less invasive before committing.
|
|
||||||
- Constraint: `bondaicommands.h` and `CPPLib.h` are game files — do **not**
|
|
||||||
edit them. All fixes go in `port/` (shim headers found first via include
|
|
||||||
path) or, if truly unavoidable, a `-DPORT`-gated `src/` change with a
|
|
||||||
`PC port:` comment.
|
|
||||||
2. **Fix the `SWITCH` 49-arg requirement** — make a shim `SWITCH` variadic (or
|
|
||||||
pad the call sites). Verify the `IF_VA`/`IS_EMPTY` case-detection still works
|
|
||||||
under GCC once variadic.
|
|
||||||
3. **Re-verify the aicommands.def:514/550/2846 errors disappear** once (1) and
|
|
||||||
(2) are in — they are likely downstream of the `CALL` expansion abort, not
|
|
||||||
independent bugs.
|
|
||||||
4. **Rebuild** and confirm `chraidata.c` compiles → full `ge007` links.
|
|
||||||
5. **Record the resolution** in `docs/PCPortResearch.md` §11 D7 (replace the
|
|
||||||
"OPEN" / "TBD" text) and delete this handoff file.
|
|
||||||
|
|
||||||
## Do NOT
|
|
||||||
|
|
||||||
- Do not edit `tools/`, `Makefile`, `rsp/`, `ld/` (N64 build, non-negotiable).
|
|
||||||
- Do not edit `bondaicommands.h`, `bondconstants.h`, `CPPLib.h`,
|
|
||||||
`aicommands.def`, `aicommands2.h` unless a `-DPORT`-gated, layout/semantics-
|
|
||||||
preserving change is truly unavoidable (and then flag it `PC port:`).
|
|
||||||
- Do not chase the aicommands.def:514/550/2846 line numbers as source lines
|
|
||||||
(they are expanded-macro positions).
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
> **RESOLVED — historical.** D7/D8 closed at the compile+link milestone.
|
|
||||||
> Kept for the IDO-vs-GCC preprocessor analysis. Not active work.
|
|
||||||
|
|
||||||
# HANDOFF — SWITCH/CALL macro port (D7/D8) — IN PROGRESS
|
|
||||||
|
|
||||||
## Status: BLOCKED on SWITCH expansion mangling (root cause now fully understood)
|
|
||||||
|
|
||||||
The `ge007` PC build fails on `src/game/chraidata.c` (the **only** file that uses
|
|
||||||
`SWITCH()`; 4 call sites) due to IDO-vs-GCC preprocessor differences in the AI-list
|
|
||||||
macro system. The `isSubroutine`/`MODELSKELETON` shims are done and working. The
|
|
||||||
**variadic `SWITCH` shim produces a mangled expansion** — this session confirmed the
|
|
||||||
exact mechanism and uncovered the *intended design* of the macro, which reframes the
|
|
||||||
fix.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What was done this session (investigation + empirical confirmation)
|
|
||||||
|
|
||||||
### 1. PD port is NOT a reference (kills the previous top-priority lead)
|
|
||||||
`PD_PORT_CHECKOUT` has **no `bondaicommands.h` / `aicommands*.h`
|
|
||||||
at all** — Perfect Dark is a different game with no AI-list macro system. Do not look
|
|
||||||
there for a SWITCH shim. (It does have `include/PR/os_message.h` with `IS_EMPTY`,
|
|
||||||
unrelated.)
|
|
||||||
|
|
||||||
### 2. Empirically CONFIRMED the content-splitting mechanism
|
|
||||||
Minimal test `build-pc/swtest.c` (still on disk): a `PlayAnimation(A..E)` macro that
|
|
||||||
expands to a trailing-comma list, a 6-param `IMPL`, and a variadic
|
|
||||||
`SWITCH(VAR, ...) → IMPL(VAR, __VA_ARGS__, ,,,,,)`. Result:
|
|
||||||
|
|
||||||
```
|
|
||||||
error: macro 'IMPL' passed 16 arguments, but takes just 6
|
|
||||||
```
|
|
||||||
|
|
||||||
**Mechanism (now proven, not hypothesized):**
|
|
||||||
1. At SWITCH parse time the content `PlayAnimation(...) BREAK` is **one** argument
|
|
||||||
(its commas are inside `PlayAnimation`'s parens).
|
|
||||||
2. C rule: each macro arg is **fully macro-expanded before substitution**. The content
|
|
||||||
expands to a **top-level comma list** (`AI_PlayAnimation , ... , BREAK`).
|
|
||||||
3. Forwarding `__VA_ARGS__` into `_PORT_SWITCH_IMPL(...)` **re-parses** that token
|
|
||||||
stream at top-level commas → the content is **split into N separate args**, which
|
|
||||||
mis-fill `CASE0..CASE_CONTENTF`. The trailing comma of `PlayAnimation` adds one
|
|
||||||
extra empty arg (hence 16, not 15).
|
|
||||||
|
|
||||||
### 3. Preprocessed the real file — mangled output confirmed
|
|
||||||
`gcc -E src/game/chraidata.c` (full flags in §Build) shows at the line-183 call:
|
|
||||||
|
|
||||||
```
|
|
||||||
AI_SetNewRandom 0}; Error: Switch Limited to 15 elements + 1 default u8 (183) & 0x00FF[] = {
|
|
||||||
((0) & 0xFF00) >> 8 ((0) & 0x00FF,lblNext) 0x10(lblDone)
|
|
||||||
AI_Label , lblNext , AI_PlayAnimation (((ANIM_scratching_leg) & 0xFF00) >> 8,lblNext)
|
|
||||||
((183) & 0xFF00) >> 8(lblDone) AI_Label , lblNext , AI_GotoNext , ANIM_scratching_leg , & 0x00FF(lblDone) ...
|
|
||||||
```
|
|
||||||
|
|
||||||
Content fragments each get `(lblNext)`/`(lblDone)` spliced in; `CASEF` gets filled
|
|
||||||
(triggers "Switch Limited"); a fragment forms `BREAK(<2 args>)` → "BREAK passed 2
|
|
||||||
arguments" at chraidata.c:202.
|
|
||||||
|
|
||||||
### 4. Discovered the INTENDED DESIGN of the content (key reframing)
|
|
||||||
The content is **not** meant to survive as a single arg. It is a **template whose last
|
|
||||||
token is a dangling function-like macro name**, and the SWITCH body **appends
|
|
||||||
`(lblDone)` to complete that call**:
|
|
||||||
|
|
||||||
- `BREAK(LABEL)` → `GotoNext(LABEL)` → `AI_GotoNext , LABEL ,` (aicommands2.h:70)
|
|
||||||
- `GotoNext(LABEL)` → `AI_GotoNext , LABEL ,` (aicommands2.h:52)
|
|
||||||
- `PlayAnimation(...)` → `AI_PlayAnimation , CharArrayFrom16(..) , ... , INTERPOL ,`
|
|
||||||
(trailing comma; aicommands2.h:386)
|
|
||||||
- `Label(ID)` → `AI_Label , ID ,` (aicommands2.h:137)
|
|
||||||
- `SetNewRandom()` → `AI_SetNewRandom ,` (aicommands2.h:1555)
|
|
||||||
- `IFRandomGreaterThan(BYTE,LBL)` → `AI_IFRandomGreaterThan , BYTE , LBL ,` (aicommands2.h:1607)
|
|
||||||
- `IFNewRandomGreaterThan(BYTE,LBL)` → `SetNewRandom()IFRandomGreaterThan(BYTE,LBL)` (aicommands2.h:1623)
|
|
||||||
|
|
||||||
Game code writes the content as e.g. `PlayAnimation(...) BREAK` (line 183) or
|
|
||||||
`TRYFiringRun` / `... TRYFireAtBondKneeling` (line 305) — i.e. a run of macro
|
|
||||||
calls/names **ending in a bare function-like name** (`BREAK`, `TRYFiringRun`,
|
|
||||||
`TRYFireAtBondKneeling`). The body line
|
|
||||||
`EXPAND_ARGS_STACK(CASE_CONTENTx)(lblDone)` therefore expands the dangling name into
|
|
||||||
`NAME(lblDone)`.
|
|
||||||
|
|
||||||
**Consequences for the port:**
|
|
||||||
- `EXPAND_ARGS_STACK(CASE_CONTENTx)` is *not* a 1-arg call in practice: after
|
|
||||||
substitution the content's comma list fills `A,B,C,...` of its 33 params and the
|
|
||||||
rest are empty (IDO leniency). Its body re-joins the non-empty args with `COMMA()`
|
|
||||||
→ it is effectively an **identity / full-expansion pass** over the content. So the
|
|
||||||
shim's replacement of `EXPAND_ARGS_STACK(CASE_CONTENTx)` with bare `CASE_CONTENTx`
|
|
||||||
is semantically fine *if* the content arrives intact.
|
|
||||||
- `IS_EMPTY(CASE_CONTENTx)` becomes a **multi-arg call** to a 1-param macro after
|
|
||||||
substitution. IDO tolerated it (first-arg → `_IS_EMPTY_<tok>_` undefined → 0 =
|
|
||||||
"not empty", which is the correct answer when content is present). GCC hard-errors
|
|
||||||
(the `pasting '_' and '('` at CPPLib.h:257 is this).
|
|
||||||
|
|
||||||
### 5. Exact arg counts of the 4 call sites (corrects the old "4–25" note)
|
|
||||||
Counted top-level commas (comments stripped, paren-aware):
|
|
||||||
|
|
||||||
| line | args | shape |
|
|
||||||
|---|---|---|
|
|
||||||
| 183 | 20 | VAR + 5×(CASE,VAL,CONTENT) + `,,`default + CONTENT |
|
|
||||||
| 236 | 14 | VAR + 3×triple + `,,`default + CONTENT |
|
|
||||||
| 305 | 11 | VAR + 6×triple + `,,` + CONTENT + `,,` + CONTENT (two defaults) |
|
|
||||||
| 623 | 26 | VAR + 8×triple + `,,`default + CONTENT |
|
|
||||||
|
|
||||||
So the range is **11–26 args**, always `VAR` + a run of `(CASE, VAL, CONTENT)` triples
|
|
||||||
where a "default" is encoded as `,,` (empty CASE, empty VAL) before a CONTENT.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## The core blocker (precise statement)
|
|
||||||
|
|
||||||
The content must reach the body **as one argument** (so `IS_EMPTY`/`EXPAND_ARGS_STACK`
|
|
||||||
see it whole and `(lblDone)` lands on the dangling name). But:
|
|
||||||
|
|
||||||
- It is **one arg only at parse time**; C expands it to a top-level comma list before
|
|
||||||
substitution (step 2 above).
|
|
||||||
- Any forwarding of that expanded list through `__VA_ARGS__` into a fixed-param macro
|
|
||||||
**re-parses** it and splits it. That is exactly what the current shim does.
|
|
||||||
|
|
||||||
The original 49-param SWITCH avoided this because the content was bound to a **named
|
|
||||||
param** (`CASE_CONTENTx`) at parse time and substituted directly — never re-parsed.
|
|
||||||
GCC won't let us call a 49-param macro with 11–26 args, which is why we went variadic,
|
|
||||||
which reintroduces the re-parse. **That tension is the whole problem.**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Directions for the next session (in suggested order)
|
|
||||||
|
|
||||||
1. **Protect the content from re-parsing by wrapping, not by re-parsing.**
|
|
||||||
The only C protection for a comma list is parentheses. Investigate a `SWITCH(VAR,
|
|
||||||
...)` that forwards each arg **parenthesized** so the content's commas can't split
|
|
||||||
it, then strips the parens in the body. The open sub-problem: identifying which
|
|
||||||
args are content vs. simple CASE/VAL without already having split them. Note CASE
|
|
||||||
and VAL are always simple single tokens (`IFRandomGreaterThan`, `250`); only
|
|
||||||
CONTENT carries commas. A positional scheme (content = every 3rd arg) may be
|
|
||||||
exploitable *if* the wrap happens before expansion — verify whether that's
|
|
||||||
achievable at all, since expansion precedes any body processing.
|
|
||||||
|
|
||||||
2. **Write a fresh GCC-clean SWITCH that emits byte-identical output** (abandon the
|
|
||||||
"verbatim original body" approach). First capture the *clean* IDO output for one
|
|
||||||
call (e.g. hand-expand the line-236 14-arg call, or reconstruct from the body
|
|
||||||
semantics now understood), then design a variadic macro that reproduces it. This
|
|
||||||
sidesteps `IS_EMPTY`/`EXPAND_ARGS_STACK` multi-arg issues entirely if the new body
|
|
||||||
doesn't route content through them.
|
|
||||||
|
|
||||||
3. **Shim `CPPLib.h`** (new `port/shim/CPPLib.h`, PORT-only, includes the real one)
|
|
||||||
to make `IS_EMPTY`/`_IS_EMPTY`/`EXPAND_ARGS_STACK` tolerant of multi-arg /
|
|
||||||
short-arg calls (variadic + first-token semantics). This is likely needed *in
|
|
||||||
addition to* whichever content-protection scheme is chosen, because even an intact
|
|
||||||
content becomes a multi-arg `IS_EMPTY`/`EXPAND_ARGS_STACK` call after substitution.
|
|
||||||
Keep the N64 build untouched (shim is PORT-gated).
|
|
||||||
|
|
||||||
4. **Verify byte-identity** by diffing the PC preprocessed `m_IdleAnimations[]` (and
|
|
||||||
the other 3 arrays) against a reference expansion. There is no IDO binary to diff
|
|
||||||
against, so the reference must be reconstructed from the body semantics (direction
|
|
||||||
2) — treat "compiles + plausible AI byte layout" as the practical bar, and record
|
|
||||||
the reasoning.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files touched / present
|
|
||||||
|
|
||||||
| File | State |
|
|
||||||
|---|---|
|
|
||||||
| `port/shim/bondaicommands.h` | D7 shim. `isSubroutine` ✅. `SWITCH` variadic → **mangled** (this is the blocker). |
|
|
||||||
| `port/shim/bondconstants.h` | D8 shim. `MODELSKELETON`/`New_ModelSkeleton` ✅. |
|
|
||||||
| `build-pc/swtest.c` | Throwaway minimal repro of the split (16-vs-6 args). Keep until fixed, then delete. |
|
|
||||||
|
|
||||||
## Build / preprocess commands
|
|
||||||
|
|
||||||
```sh
|
|
||||||
export PATH="/c/msys64/mingw64/bin:$PATH"
|
|
||||||
cd build-pc && cmake --build . --target ge007 2>&1 | grep -iE "error|FAILED" | grep -vE "Building C object"
|
|
||||||
```
|
|
||||||
|
|
||||||
Preprocess just the failing file (flags mirror `build-pc/compile_commands.json`):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd REPO_ROOT
|
|
||||||
gcc -DAVOID_UB=1 -DBUGFIX_R0 -DBYTEMATCH -DLANG_US -DLEFTOVERDEBUG -DLEFTOVERSPECTRUM \
|
|
||||||
-DPAL=0 -DPLATFORM_64BIT=1 -DPORT=1 -DREFRESH_NTSC -DVERSION_US -D_LANGUAGE_C=1 \
|
|
||||||
-Iport/shim -I. -Iinclude -Iinclude/PR -Isrc -Isrc/game -Isrc/libultra -Isrc/libultra/audio \
|
|
||||||
-Iport/include -Ibuild-pc/port/include -std=c11 -funsigned-char \
|
|
||||||
-include build-pc/port/include/versioninfo.h -E src/game/chraidata.c
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key references
|
|
||||||
|
|
||||||
- `src/bondaicommands.h` ~790–810 — original 49-param `SWITCH` body (ground truth for
|
|
||||||
intended expansion).
|
|
||||||
- `src/aicommands2.h` — all AI command macros (lines cited in §4 above).
|
|
||||||
- `include/CPPLib.h` — `IS_EMPTY`/`_IS_EMPTY` (256–258), `IS_PROBE`/`PROBE` (227–231),
|
|
||||||
`IF_VA`/`_IF_VA_0/1` (~328, 330–331), `EXPAND_ARGS_STACK` (337+), `EVAL` (85–98),
|
|
||||||
`COMMA` (16), `CAT` (190).
|
|
||||||
- `src/game/chraidata.c:183,236,305,623` — the 4 `SWITCH` call sites.
|
|
||||||
- `docs/PCPortResearch.md` §11 — findings log; record the new findings as **D9+**
|
|
||||||
(D7 = isSubroutine/SWITCH shim, D8 = MODELSKELETON, both already logged).
|
|
||||||
|
|
||||||
## Constraints (from AGENTS.md)
|
|
||||||
|
|
||||||
- Game code (`src/game/*.c`, `src/*.c`) compiles **UNMODIFIED**. All fixes go in `port/`.
|
|
||||||
- Shims are `PORT`-only and inert for the N64 build.
|
|
||||||
- `port/shim` is first on the include path, so `<bondaicommands.h>` /
|
|
||||||
`<bondconstants.h>` (and, if added, `<CPPLib.h>`) resolve to the shims; shims
|
|
||||||
include the real headers via `src/...` / `include/...` paths.
|
|
||||||
- Output must be byte-identical to the original IDO expansion.
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
# M-24 — input rework + playtest QoL (review sheet)
|
|
||||||
|
|
||||||
> **Follow-up work: `docs/M-25-QOL-REVIEW.md`** (`[Video]` ini knobs, …).
|
|
||||||
|
|
||||||
Session M-24, 2026-08-30. Three commits on `master`:
|
|
||||||
|
|
||||||
| Commit | Title |
|
|
||||||
|---|---|
|
|
||||||
| `f3ec5170` | mouse-look rework (D118b/c) + real `ge007.ini` parser |
|
|
||||||
| `65ed0315` | F12 screenshot + mouse frees on focus loss |
|
|
||||||
| `33506aee` | gamepad hotplug, wheel weapon-cycle, title FPS |
|
|
||||||
|
|
||||||
**Scope guarantee:** every change is in `port/` (shim layer). **No `src/`
|
|
||||||
file was touched** — no game-logic / ABI / asset-format change. Nothing
|
|
||||||
here is `#ifdef PORT` in game code. The N64 build is untouched.
|
|
||||||
|
|
||||||
Files changed: `port/src/input.c`, `port/src/video.c`, `port/src/config.c`,
|
|
||||||
`port/include/input.h`, plus docs (`HANDOFF.md`, `LEVEL-PLAYTEST.md`,
|
|
||||||
`PORT-LEARNINGS.md`, `PCPortResearch.md` §F D118).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Mouse-look rework (`port/src/input.c`) — fixes D118b, D118c
|
|
||||||
|
|
||||||
### The bug
|
|
||||||
|
|
||||||
The old bridge always converted mouse-Y to digital C-up/C-down presses.
|
|
||||||
That was wrong two ways, because GE's aim model is **mode-dependent**
|
|
||||||
(read from `src/game/bondview2.c` `bondviewProcessInput` / `MoveData`):
|
|
||||||
|
|
||||||
| Mode | Yaw source | Pitch source | What C-up/C-down do |
|
|
||||||
|---|---|---|---|
|
|
||||||
| Hipfire (`!insightaimmode`) | analog stick-X ("natural turn") | **digital C-up/C-down only** (stick-Y is move fwd/back, no pitch) | look up / down |
|
|
||||||
| Aim mode (R / RMB held) | analog stick past ±60 → `(stick-60)/10` | analog stick past ±60 | **crouch / lean / zoom** (`bondview2.c:5340,5351`) — *not* aim |
|
|
||||||
|
|
||||||
- **D118c** (aim + mouse-down → crouch): emitting C-down for "look down"
|
|
||||||
while aiming triggered `moveData.crouchDown`.
|
|
||||||
- **D118b** (inverted Y): GE's native pitch is inverted —
|
|
||||||
`U_CBUTTONS → speedVertaDown` (`bondview2.c:5272-5279`), i.e. C-up looks
|
|
||||||
*down*. Mapping mouse-down → C-down gave mouse-down = look up.
|
|
||||||
|
|
||||||
### The fix
|
|
||||||
|
|
||||||
`inputComputePad()` now branches on whether the aim button is held (our own
|
|
||||||
RMB/LShift state — the exact signal for the default hold-to-aim scheme):
|
|
||||||
|
|
||||||
- **Aim mode:** push `stick_x` / `stick_y` into the 61..80 band,
|
|
||||||
proportional to the per-poll mouse delta (`× MouseAimSpeed/100 × 4`,
|
|
||||||
clamped). **Emit no C-buttons.** → crouch bug gone; yaw and pitch now
|
|
||||||
behave identically (both analog).
|
|
||||||
- **Hipfire:** yaw on `stick_x += dx × MouseTurnSpeed/100 × 6`; pitch =
|
|
||||||
digital C-up/C-down on `|dy| ≥ 1.5` px/poll.
|
|
||||||
- "Mouse-down looks down" is the default (accounts for GE's inversion);
|
|
||||||
`MouseInvertY` flips it.
|
|
||||||
- Per-poll deltas, **no accumulator** (mouse = displacement device, GE aim
|
|
||||||
= rate device; consume the whole delta each poll, stop moving → stick
|
|
||||||
releases immediately).
|
|
||||||
|
|
||||||
### Known residual — D118a (lower priority, not a regression)
|
|
||||||
|
|
||||||
In **hipfire only**, yaw (analog) and pitch (digital C-button) still feel
|
|
||||||
different. Aim mode — where GE's precise vertical aiming actually happens —
|
|
||||||
is now fully analog and consistent. A fully-analog hipfire pitch would need
|
|
||||||
an `#ifdef PORT` hook in `bondview.c` (would also be needed for
|
|
||||||
toggle-aim control schemes, where "RMB held" ≠ `insightaimmode`). Deferred.
|
|
||||||
|
|
||||||
### Review risk
|
|
||||||
|
|
||||||
Low. Pure output-shaping of the N64 pad struct. Worst case is bad *feel*,
|
|
||||||
tunable at runtime (below). Cannot crash or desync — same `OSContPad`
|
|
||||||
contract as before.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. `ge007.ini` parser (`port/src/config.c`) — was a stub
|
|
||||||
|
|
||||||
`configLoad()` / `configSave()` only logged `TODO` before. The registered
|
|
||||||
options (`configRegisterInt`) therefore could not be changed without a
|
|
||||||
recompile.
|
|
||||||
|
|
||||||
Now: parses `$S/ge007.ini` — `[Section]` blocks, `Key = value`, `#` / `;`
|
|
||||||
comments; clamps ints to their registered `min`/`max`; writes a defaults
|
|
||||||
file on first run; rewrites on clean exit (`main.c` already calls
|
|
||||||
`configLoad()` at startup and `configSave()` on the unreachable-in-practice
|
|
||||||
shutdown path). Dotted keys (`Input.MouseAimSpeed`) group into
|
|
||||||
`[Input]` on disk.
|
|
||||||
|
|
||||||
**Live-tunable `[Input]` knobs** (edit with the game closed):
|
|
||||||
|
|
||||||
| Key | Default | Meaning |
|
|
||||||
|---|---|---|
|
|
||||||
| `MouseEnabled` | 1 | mouse-look on/off |
|
|
||||||
| `MouseAimSpeed` | 50 | aim-mode (RMB) sensitivity, percent |
|
|
||||||
| `MouseTurnSpeed` | 100 | hipfire yaw sensitivity, percent (new; split from AimSpeed) |
|
|
||||||
| `MouseInvertY` | 0 | 1 = mouse-down looks up |
|
|
||||||
|
|
||||||
Other sections already register options (`[Audio] BufferSize`,
|
|
||||||
`QueueLimit`) and now round-trip too.
|
|
||||||
|
|
||||||
**Implementation notes for review:** no `<ctype.h>` / `atoi` — the project's
|
|
||||||
`src/str.c` defines `isspace`, which collided with libmsvcrt at link time
|
|
||||||
once `config.c` referenced the libc one, so `config.c` carries tiny local
|
|
||||||
`isws` / `lc` / `parseInt` helpers. Options registered *after* `configLoad()`
|
|
||||||
(none today — all are `PD_CONSTRUCTOR`) would miss the first-run save.
|
|
||||||
|
|
||||||
### Review risk
|
|
||||||
|
|
||||||
Low-medium. New file I/O in `port/`. Failure modes are contained: missing
|
|
||||||
file → write defaults; unparseable line → skipped; unknown key → logged and
|
|
||||||
ignored. No effect on any code path that does not read a registered option.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Playtest QoL (`port/src/video.c` + `port/src/input.c`)
|
|
||||||
|
|
||||||
All wired through the existing host-thread SDL event pump
|
|
||||||
(`videoPumpEvents`).
|
|
||||||
|
|
||||||
| Feature | How | Notes |
|
|
||||||
|---|---|---|
|
|
||||||
| **F12 screenshot** | keydown sets a flag; consumed in `videoEndFrame` (render thread, GL context current) via the existing `gfx_opengl_dump_bound_fbo` | → `ppm/shot_NNN.ppm`; view with `tools_pc/ppm2bmp.py`. Same mechanism as `GE_PCDUMP`. |
|
|
||||||
| **Alt-tab frees the mouse** | `SDL_WINDOWEVENT_FOCUS_LOST/GAINED` → `inputSetMouseGrab(0/1)` | New `inputSetMouseGrab()` in `input.c`: toggles SDL relative-mouse mode (respecting `MouseEnabled`), zeroes the aim delta, and gates `inputUpdate()` mouse reads on a `mouseGrabbed` flag so a released cursor can't drift the aim. |
|
|
||||||
| **Mouse wheel = weapon cycle** | `SDL_MOUSEWHEEL` → `inputPostWheel()` queues a 2-poll `GE_CONT_A` press | GE's default scheme cycles weapon forward on a bare A edge (`invButtons = A_BUTTON`, `bondview2.c:5162` → `weaponForwardOffset`, `:5326`). Both wheel directions cycle **forward** — no clean backward input without the A+fire combo. Same overload the keyboard already has (Space/Z/E = A). |
|
|
||||||
| **Gamepad hotplug** | `SDL_CONTROLLERDEVICEADDED/REMOVED` → `inputRescanPads()` (close all + re-open) | Caveat: the game latches `inputConnectedMask()` at `osContInit` (boot). A pad added *after* boot still works for play (it merges into controller 0 — `inputComputePad(0)` always checks `pads[0]`) but won't appear as a separate controller channel. Plug it before launch for genuine multi-pad. |
|
|
||||||
| **Window title shows FPS** | `videoPumpEvents` calls `wmAPI->set_window_title("GoldenEye 007 - N fps")` ~1 Hz from `vidAvgFPS` | Cosmetic. |
|
|
||||||
|
|
||||||
Alt-Enter fullscreen toggle was **already present** (`gfx_sdl2.cpp:299`) —
|
|
||||||
not added here, just documented.
|
|
||||||
|
|
||||||
### Review risk
|
|
||||||
|
|
||||||
Low. Screenshot reuses a proven path. Focus/grab and hotplug are SDL window
|
|
||||||
ops on the thread that created the window. Wheel→A is opt-in physical input
|
|
||||||
identical in effect to tapping the existing keyboard A key.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Verification done
|
|
||||||
|
|
||||||
- Build green (`./build-pc.sh ntsc-final`), no new warnings in touched files.
|
|
||||||
- `-level_09` (BUNKER1): crash-free to frame 900 (30–35 s window), **91.6%
|
|
||||||
pixel coverage — unregressed** vs the committed baseline, checked after
|
|
||||||
each of the 3 commits (3/3, 2/2, 2/2 runs; the one early NO-FRAME was the
|
|
||||||
known D117/D134 machine-load flakiness, cleared on retry).
|
|
||||||
- `-level_20` (Silo): crash-free.
|
|
||||||
- `ge007.ini`: written on first run, re-read with **no unknown-key
|
|
||||||
warnings**, values round-trip.
|
|
||||||
- Input is a headless-agent blind spot — **live feel of the mouse rework,
|
|
||||||
the wheel cycle, and a real gamepad are UNVERIFIED**; that's the WS6
|
|
||||||
playtest's job. See `docs/LEVEL-PLAYTEST.md`.
|
|
||||||
|
|
||||||
## What a reviewer should check by playing
|
|
||||||
|
|
||||||
1. Mouse aim in **aim mode (hold RMB)** feels smooth and 1:1-ish; no crouch
|
|
||||||
when you pull the mouse down to look down (D118c).
|
|
||||||
2. Mouse-Y not inverted at `MouseInvertY = 0` (D118b); flipping the ini
|
|
||||||
value inverts it.
|
|
||||||
3. Editing `MouseAimSpeed` / `MouseTurnSpeed` in `data/ge007.ini` (game
|
|
||||||
closed) actually changes sensitivity next launch.
|
|
||||||
4. Mouse wheel swaps weapons; F12 drops a `ppm/shot_*.ppm`; alt-tab frees
|
|
||||||
the cursor and returns cleanly; a gamepad plugged in before launch works.
|
|
||||||
|
|
||||||
## Still TODO (not started)
|
|
||||||
|
|
||||||
Key rebinding, in-render on-screen FPS overlay, in-game options menu,
|
|
||||||
backward weapon cycle, the `bondview.c` analog-aim hook (D118a + toggle-aim).
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
# M-25 — port-layer QoL / playtest-experience polish (review sheet)
|
|
||||||
|
|
||||||
Session M-25, 2026-08-30. Continues `docs/M-24-QOL-REVIEW.md`.
|
|
||||||
|
|
||||||
**Scope guarantee:** every change is in `port/` (shim layer). **No `src/`
|
|
||||||
file touched** — no game-logic / ABI / asset-format change, no `#ifdef PORT`
|
|
||||||
in game code. N64 build untouched. Every new behavior defaults to the
|
|
||||||
pre-M-25 behavior — a fresh or partial `ge007.ini` changes nothing.
|
|
||||||
|
|
||||||
| Commit | Title |
|
|
||||||
|---|---|
|
|
||||||
| `9ebed821` | `[Video]` ge007.ini knobs + config auto-migration |
|
|
||||||
| `5a7a1035` | `tools_pc/playtest.sh` — name lookup, `--list`, auto-addr2line |
|
|
||||||
| _(this)_ | `[Input]` knobs — mouse Y scale / smoothing, pad deadzone / trigger / invert-Y |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. `[Video]` ge007.ini knobs + config auto-migration
|
|
||||||
|
|
||||||
### Rationale
|
|
||||||
|
|
||||||
`video.c` hardcoded five display settings that testers reasonably want to
|
|
||||||
change per-machine (weak GPU → drop MSAA; capture work → nearest-filter for
|
|
||||||
crisp texels; high-refresh monitor → uncap fps). All were compile-time only.
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
**`port/src/video.c`** — new `PD_CONSTRUCTOR videoConfigInit()` registers:
|
|
||||||
|
|
||||||
| Key | Default | Effect | Applied |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `Video.VSync` | 1 | swap interval 0/1 | `wmAPI->set_swap_interval` in `videoInit` |
|
|
||||||
| `Video.FpsCap` | 0 | frame cap in fps, 0 = uncapped | `gfx_set_target_fps` in `videoInit` |
|
|
||||||
| `Video.MSAA` | 1 | 1/2/4/8 samples (snapped down to a power of two) | `gfx_msaa_level` before `gfx_init` |
|
|
||||||
| `Video.TextureFilter` | 1 | 0 = nearest, 1 = bilinear | `gfx_set_texture_filter` + mipmap filter |
|
|
||||||
| `Video.Fullscreen` | 0 | 0 = windowed, 1 = borderless fullscreen | `window_settings.fullscreen` at window create |
|
|
||||||
|
|
||||||
Every default reproduces the exact prior hardcoded value
|
|
||||||
(`set_swap_interval(1)`, no fps target, `gfx_msaa_level = 1`,
|
|
||||||
`FILTER_LINEAR` / `MIPMAP_LINEAR`, windowed). MSAA is clamped to
|
|
||||||
`{1,2,4,8}` by a snap-down expression so a bogus `MSAA = 3` can't reach the
|
|
||||||
GL layer. `gfx_pc.cpp` already self-disables MSAA if framebuffers are
|
|
||||||
unavailable.
|
|
||||||
|
|
||||||
**`port/src/config.c`** — `configLoad()` now tracks, per registered option,
|
|
||||||
whether the on-disk file actually mentioned it (`seen` flag set in
|
|
||||||
`applyKV`). If any registered key was missing after the parse, it calls
|
|
||||||
`configSave()` once to rewrite the file with the new key at its default.
|
|
||||||
|
|
||||||
- Without this, a new `[Video]` block would never appear in an existing
|
|
||||||
`ge007.ini` (M-24: `configLoad` only writes defaults when the file is
|
|
||||||
*absent*, and the clean-exit `configSave` is unreachable in practice) —
|
|
||||||
the knobs would exist but be undiscoverable.
|
|
||||||
- An **up-to-date** file (every registered key present) is **not**
|
|
||||||
rewritten — user comments and ordering are preserved, exactly as before.
|
|
||||||
- A file that *is* missing keys gets rewritten in the canonical grouped
|
|
||||||
form (comments dropped — same contract M-24 already documented for the
|
|
||||||
clean-exit save). Existing values are preserved (they're parsed into the
|
|
||||||
option storage before the re-save).
|
|
||||||
|
|
||||||
### Review risk
|
|
||||||
|
|
||||||
Low. `video.c` changes are all "read an int, pick a code path that already
|
|
||||||
existed". `config.c` change is one extra conditional `configSave()` on a
|
|
||||||
path that already round-trips through the same serializer.
|
|
||||||
|
|
||||||
Failure modes: a corrupt ini value is clamped (ints) or ignored (unknown
|
|
||||||
key, already handled); the migration re-save uses `fopen(path,"w")` which
|
|
||||||
already had a `cannot write` warning path.
|
|
||||||
|
|
||||||
### Verification
|
|
||||||
|
|
||||||
- Build green (`./build-pc.sh ntsc-final`), no new warnings in `video.c` /
|
|
||||||
`config.c`. (Pre-existing `getenv` implicit-decl warning in the untouched
|
|
||||||
`GE_PCDUMP` block is not from this change.)
|
|
||||||
- Fresh ini (deleted): `[Video]` block written with all five defaults.
|
|
||||||
- Migration: hand-written ini with only `[Input] MouseAimSpeed = 77` +
|
|
||||||
`[Audio]` → after one launch, `[Video]` added, `MouseAimSpeed = 77`
|
|
||||||
preserved.
|
|
||||||
- No-op: ini with all keys present + a custom leading comment → launch
|
|
||||||
leaves the comment in place (no rewrite).
|
|
||||||
- `-level_09`: `MSAA=4 / TextureFilter=0 / VSync=0 / FpsCap=120` →
|
|
||||||
crash-free to frame 380+, **91.67% pixel coverage (unregressed)**.
|
|
||||||
Defaults → crash-free to frame 620+, 91.67%.
|
|
||||||
- `-level_20` (Silo): crash-free to frame 620+ (defaults). NB Silo has a
|
|
||||||
known pre-existing early freeze on the silo→Bond camera descent (~3 s
|
|
||||||
in) — reported M-25, unrelated to this change, tracked for a later
|
|
||||||
session.
|
|
||||||
|
|
||||||
### What to check by playing
|
|
||||||
|
|
||||||
1. Edit `data/ge007.ini` `[Video]` with the game closed; values take effect
|
|
||||||
next launch.
|
|
||||||
2. `TextureFilter = 0` → visibly crisp/pixelated textures. `MSAA = 4` →
|
|
||||||
smoother polygon edges. `Fullscreen = 1` → starts borderless-fullscreen
|
|
||||||
(Alt-Enter still toggles).
|
|
||||||
3. `FpsCap = 30` → title-bar fps pins near 30; `VSync = 0` + `FpsCap = 0` →
|
|
||||||
uncapped.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. `tools_pc/playtest.sh` — usability
|
|
||||||
|
|
||||||
Tooling only — not compiled, not in the build. No game-code impact.
|
|
||||||
|
|
||||||
- Accepts a **level name** as well as the `-level_XX` number
|
|
||||||
(`playtest.sh bunker2`, `playtest.sh "surface 1"` — case/space/underscore
|
|
||||||
insensitive), via an in-script name→number table (mission order).
|
|
||||||
- `playtest.sh --list` prints the 21-level table (`#`, `-level_XX`, name).
|
|
||||||
No-arg / `--help` prints usage + the table.
|
|
||||||
- On a crash it now reads `ge007.crash.log` and runs `addr2line` on the top
|
|
||||||
faulting PCs automatically (was: "see log"), and copies the crash log
|
|
||||||
next to the playtest log.
|
|
||||||
|
|
||||||
Verified: `--list` output correct; name + number resolution; a real
|
|
||||||
`bunker1` launch (killed after 15 s) reports cleanly.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. `[Input]` knobs (`port/src/input.c`)
|
|
||||||
|
|
||||||
### Rationale
|
|
||||||
|
|
||||||
Feel tuning that was hardcoded. Every default is the exact prior constant,
|
|
||||||
so a fresh/partial ini changes nothing (verified against a from-baseline
|
|
||||||
build — see below).
|
|
||||||
|
|
||||||
| Key | Default | Effect |
|
|
||||||
|---|---|---|
|
|
||||||
| `Input.MouseYScale` | 100 | extra vertical (pitch) sensitivity multiplier, % — applied to the mouse dy before aim/hipfire mapping |
|
|
||||||
| `Input.MouseSmoothing` | 0 | 0 = raw deltas (today); 1..90 = exponential low-pass, value = % blend of the previous poll |
|
|
||||||
| `Input.PadDeadzone` | 7000 | left-stick deadzone, raw 0..32767 (was `STICK_DEADZONE`) |
|
|
||||||
| `Input.PadTriggerPct` | 23 | trigger press point, % of travel (≈ the old `30*256` threshold: 23 × 327 ≈ 7521 vs 7680) |
|
|
||||||
| `Input.PadLookInvertY` | 0 | 1 = invert right-stick (look) Y |
|
|
||||||
|
|
||||||
`MouseYScale` / `MouseSmoothing` feed through the same mode-aware
|
|
||||||
mouse→pad path as the M-24 rework (aim-mode band vs hipfire digital pitch);
|
|
||||||
smoothing state is a pair of static doubles, reset implicitly when the
|
|
||||||
deltas go to zero. `PadDeadzone` flows through `scaleAxis()` (clamped
|
|
||||||
0..30000 so the divisor can't go non-positive). `PadTriggerPct`
|
|
||||||
recomputes the threshold from a percentage; `PadLookInvertY` negates `ry`
|
|
||||||
before the C-button mapping.
|
|
||||||
|
|
||||||
### Review risk
|
|
||||||
|
|
||||||
Low. Same class as §1 — read an int, feed an existing code path.
|
|
||||||
`scaleAxis` divisor is guarded. Worst case is bad feel, all runtime-tunable.
|
|
||||||
Cannot change the `OSContPad` contract.
|
|
||||||
|
|
||||||
### Verification
|
|
||||||
|
|
||||||
- Build green; the two `input.c` warnings (`fabs` implicit-decl, `getenv`)
|
|
||||||
are **pre-existing** — confirmed identical on a stashed baseline build,
|
|
||||||
not introduced here.
|
|
||||||
- `-level_09`: crash-free to frame 620, **91.6% coverage (unregressed)**.
|
|
||||||
- `-level_20` (Silo): crash-free; frame capture stops at ~320 —
|
|
||||||
**confirmed identical on the pre-change baseline build** (the known Silo
|
|
||||||
camera-descent freeze the user reported this session, not a regression).
|
|
||||||
- New keys land in `[Input]` on next launch via the §1 migration path;
|
|
||||||
existing values (`MouseAimSpeed = 77`) preserved.
|
|
||||||
|
|
||||||
### What to check by playing
|
|
||||||
|
|
||||||
1. `MouseYScale = 150` → faster vertical look, horizontal unchanged.
|
|
||||||
2. `MouseSmoothing = 60` → visibly smoothed / slightly laggy aim; `0` crisp.
|
|
||||||
3. `PadDeadzone = 3000` → stick responds to smaller nudges; `12000` → larger
|
|
||||||
dead center. `PadLookInvertY = 1` → inverted pad look. `PadTriggerPct`
|
|
||||||
low → hair trigger.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Known pre-existing bugs noted this session (not addressed — out of scope)
|
|
||||||
|
|
||||||
- **Silo (`-level_20`) capture freeze — likely just D117/D134 load
|
|
||||||
sensitivity, NOT a real bug.** A full-length `GE_PCDUMP` capture froze at
|
|
||||||
~frame 320 (silo→Bond camera descent) twice, and the pre-M-25 baseline
|
|
||||||
comparison froze too — but all of those ran on a machine still loaded from
|
|
||||||
the 21-level sweep. On an idle machine the user drove Silo live past
|
|
||||||
~1800 frames with a healthy VI pacemaker and no heartbeat stall. Not an
|
|
||||||
M-25 regression; not currently believed deterministic. Recount N runs on
|
|
||||||
an idle box if it recurs.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Still TODO (carried from M-24)
|
|
||||||
|
|
||||||
Key rebinding, in-render on-screen FPS/pos overlay, in-game options menu,
|
|
||||||
backward weapon cycle, the `bondview.c` analog-aim hook (D118a), PNG (not
|
|
||||||
PPM) screenshots (needs stb_image_write vendored into `port/`), window
|
|
||||||
size/pos persistence (needs a reliable save-on-exit hook — `configSave` is
|
|
||||||
currently only on the unreachable clean-shutdown path).
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
# M-26 QoL review — save-on-exit, window persistence, raw mouse, --help, STALLED verdict
|
|
||||||
|
|
||||||
Port-layer only. Zero `src/` / game-logic change. Every default reproduces
|
|
||||||
prior behaviour exactly (all new knobs default-off / default-current).
|
|
||||||
|
|
||||||
## A — `atexit()` save-on-exit + `[Window]` persistence
|
|
||||||
|
|
||||||
- `port/src/main.c`: `atexit(portAtExit)` registered right after
|
|
||||||
`configLoad()`. `portAtExit` = `videoSaveWindowState()` then
|
|
||||||
`configSave()`. The `exit(0)` in `videoPumpEvents` (SDL_QUIT / ESC /
|
|
||||||
window-close) is reachable, so the handler fires on every clean quit.
|
|
||||||
Crash / `sysFatalError` paths call `abort()` → handler does **not** run
|
|
||||||
(intended — don't persist a broken state).
|
|
||||||
- `port/src/video.c`: new `[Window]` config section — `Width`, `Height`,
|
|
||||||
`X`, `Y`, `Maximized`. Sentinels reproduce the old hardcoded behaviour:
|
|
||||||
`Width/Height = 0` → native (640×480 / 640×400), `X/Y = -1` → SDL
|
|
||||||
centres the window. `videoInit` uses them for the `GfxWindowInitSettings`.
|
|
||||||
- `videoSaveWindowState()` reads `wmAPI->get_fullscreen_state` /
|
|
||||||
`get_maximized_state` / `get_dimensions` (all already in the SDL2 wapi
|
|
||||||
backend) and writes geometry back into the cfg vars. A maximized or
|
|
||||||
fullscreen window keeps its last *restored* size/pos on disk and only
|
|
||||||
updates the flag. `Video.Fullscreen` now round-trips too.
|
|
||||||
- **Verified:** `-level_09` / `-level_20` boot crash-free (6/6 GE_PCDUMP
|
|
||||||
frames each, unregressed); `ge007.ini` gains a populated `[Window]`
|
|
||||||
block on exit; relaunch reads it back with no unknown-key warnings.
|
|
||||||
|
|
||||||
## B — `level_sweep.sh` STALLED verdict
|
|
||||||
|
|
||||||
Pure shell (not built). A level that renders a few frames then freezes
|
|
||||||
while the process stays alive (the Silo `-level_20` fly-down case,
|
|
||||||
D117/D134 machine-load sensitivity) previously reported **PASS** because
|
|
||||||
`last=$(ls ppm/*.ppm | tail -1)` was non-empty. Now the watchdog records
|
|
||||||
`stalled=1` when the frame count is frozen for `STALL_SECS`, and `alive=1`
|
|
||||||
if the process was still running when the watchdog gave up →
|
|
||||||
`STALLED (froze at N/M frames)`. A complete dump set is still a real PASS.
|
|
||||||
STALLED is retried once (like NO-FRAMES) before it's believed.
|
|
||||||
|
|
||||||
## C — raw mouse input (`Input.MouseRawInput`, default 0)
|
|
||||||
|
|
||||||
`port/src/input.c inputInit`: when `MouseRawInput=1`, sets
|
|
||||||
`SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE=0` and
|
|
||||||
`SDL_HINT_MOUSE_RELATIVE_MODE_WARP=0` before `SDL_SetRelativeMouseMode` —
|
|
||||||
removes OS pointer acceleration / warp emulation from aim. Off by default
|
|
||||||
so nothing changes unless the user opts in.
|
|
||||||
|
|
||||||
## E — `--help` / `--version`
|
|
||||||
|
|
||||||
`port/src/main.c`, handled before `crashInit()`:
|
|
||||||
- `--version` → rom id / target platform / build hash, exit 0.
|
|
||||||
- `--help` / `-h` → the above + usage + the 21 solo-level `-level_XX`
|
|
||||||
table (matches `tools_pc/level_sweep.sh` / `playtest.sh --list`), exit 0.
|
|
||||||
|
|
||||||
## D — `[Debug]` ini flags mirroring the dev env vars
|
|
||||||
|
|
||||||
`port/src/config.c`: `Debug.FrameDump` (string, mirrors `GE_PCDUMP`,
|
|
||||||
`"lo-hi[:step]"`) and `Debug.InputLog` (int, mirrors `GE_INPUTLOG`).
|
|
||||||
`configGetFrameDump()` / `configGetInputLog()` check the env var first
|
|
||||||
(existing `tools_pc/*.sh` scripts unaffected), fall back to the ini.
|
|
||||||
Consumers switched: `video.c videoEndFrame` (was `gfx_opengl_pcdump_enabled()`
|
|
||||||
+ `getenv`), `input.c inputComputePad` (was `getenv("GE_INPUTLOG")`).
|
|
||||||
`GE_DETERM` is design-only (D117, no code path) so it is not mirrored.
|
|
||||||
|
|
||||||
- **Verified:** ini-only `FrameDump = 40-120:20` produces exactly those 5
|
|
||||||
frames; `GE_PCDUMP` still overrides; `-level_09` crash-free.
|
|
||||||
- **Cosmetic:** `configSave` emits int and string opts in separate passes,
|
|
||||||
so a section with both (`[Debug]`) prints its header twice. Round-trips
|
|
||||||
fine on reload. Pre-existing quirk, not worth the churn now.
|
|
||||||
|
|
||||||
## Not done (Tier 2/3 from the board)
|
|
||||||
|
|
||||||
F (minidump), G (PNG screenshots), H (rumble), I/J (in-render overlay /
|
|
||||||
toasts) — left for a dedicated session.
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
# Plan — Session M-6: toward a playable, low-glitch BUNKER1 PC build
|
|
||||||
|
|
||||||
Baseline: D112 committed (`67d674da`). Build green. `-level_09` renders
|
|
||||||
BUNKER1 continuously, crash-free. Three tracks below, in priority order.
|
|
||||||
Each track: **investigate → root-cause → narrow `#ifdef PORT` fix or
|
|
||||||
write-up** per AGENTS.md non-negotiables (no game-logic changes; ABI /
|
|
||||||
layout / format exceptions only, each documented in
|
|
||||||
`docs/PCPortResearch.md` §F).
|
|
||||||
|
|
||||||
## Track 1 — Portal-BFS under-reach ("the void") [highest visual impact]
|
|
||||||
|
|
||||||
**Symptom:** in BUNKER storage area (rooms 27–29) the per-frame visible
|
|
||||||
room count collapses to 1–2; large sky-fill void where an adjacent room
|
|
||||||
should show through an open doorway. Portal *visibility* is otherwise
|
|
||||||
healthy post-D106 (room 18 → 8 rooms deep on enabled doorways).
|
|
||||||
|
|
||||||
**Known-good / ruled out:** `currentPlayerGetProjectionMatrix()`,
|
|
||||||
`g_CurrentPlayer->screensize`, the intentional `g_RoomLoadBudget` 200→3
|
|
||||||
FP-cam drop, `zfar`, the plane-side metric cull (`bg.c:4135/4144`).
|
|
||||||
|
|
||||||
**Suspects:** `sub_GAME_7F0B5864` screen-space portal projection
|
|
||||||
(`bg.c:1619`) — sibling of the D106 z==0 near-plane case, but for
|
|
||||||
partially-off-screen (not straddling) portals; `bgQueuePortalTraversal`
|
|
||||||
(`bg.c:3814/3843`) depth/dedup; `bgDetermineVisibleRooms` (`bg.c:4615`)
|
|
||||||
seed set; `sub_GAME_7F0B5528` clip-point generation (`bg.c:1551`);
|
|
||||||
`transform3Dto2DWithZScaling` (`bondview.c:730`).
|
|
||||||
|
|
||||||
**Deliverable:** root-cause writeup in §F ("D113"); if a narrow PORT fix
|
|
||||||
is obviously correct (e.g. another degenerate-box / clamp mismatch),
|
|
||||||
apply it, else stop and document.
|
|
||||||
|
|
||||||
**RESULT (Track 1 agent):** NOT a portal-BFS bug. The BFS output is
|
|
||||||
correct for BUNKER1's topology — rooms 26/27 sit behind portals 25/26
|
|
||||||
which carry `PORTALFLAG_DISABLED` (closed doors), culled identically on
|
|
||||||
N64. Stable 3 visible rooms {29,28,25}, not "1–2". The void is the
|
|
||||||
**closed-door prop models (obj 153/154) rendering with a wrong world
|
|
||||||
transform** — their leaf DLs are emitted (`GE_D96`) but land outside the
|
|
||||||
doorway, leaving the wall hole unoccluded → sky. **Folds into Track 2**
|
|
||||||
(same model-matrix defect as the inverted guard / mislocated crate). No
|
|
||||||
`bg.c` change. Probes reverted, tree clean.
|
|
||||||
|
|
||||||
## Track 2 — Character pose / orientation
|
|
||||||
|
|
||||||
**Symptom:** post-D112 a BUNKER guard renders as a coherent humanoid but
|
|
||||||
appears **inverted**. Also a mispositioned static crate prop (top-left in
|
|
||||||
the control room).
|
|
||||||
|
|
||||||
**Known-good:** joint *rotations* from the anim bitstream; `render_pos[]`
|
|
||||||
positions now sane (~±60 units) after the D112 f32 fix; `basemtx` from
|
|
||||||
`camGetWorldToScreenMtxf()` (`bondview.c:824`) was a clean orthonormal
|
|
||||||
lookat.
|
|
||||||
|
|
||||||
**Suspects:** matrix handedness / lookat sign in the joint base matrix;
|
|
||||||
`modelBuildGroupMatrices` / `process_02_position` / `subcalcmatrices`
|
|
||||||
composition order on LE; a converter sign/axis issue in `d43_emit.py`
|
|
||||||
group `Origin` or matrix rodata that survived the D112 fix (byte order
|
|
||||||
right, axis convention wrong); guMtxF2L / Mtxf→Mtx path.
|
|
||||||
|
|
||||||
**Deliverable:** root-cause writeup in §F ("D114"); narrow fix if
|
|
||||||
obviously correct.
|
|
||||||
|
|
||||||
## Track 3 — `struct player` raw-offset audit [gate to playability]
|
|
||||||
|
|
||||||
**Symptom:** 1P weapon model doesn't draw. Landmine (§F D100): raw
|
|
||||||
hardcoded-offset accessors into `struct player` / `struct hand` are NOT
|
|
||||||
PORT-adjusted and are already PC-wrong.
|
|
||||||
|
|
||||||
**Known instances:** `gunfire.c` `THROWMTX_OFFSET 0xAD8` / `THROWPOS`
|
|
||||||
`0xB08` / `THROWPREV` `0xB48` (all `g_CurrentPlayer + handoffset + K`);
|
|
||||||
plus D102 (weapon Model pool) already fixed.
|
|
||||||
|
|
||||||
**Task:** enumerate *every* raw byte-offset / hardcoded-size access into
|
|
||||||
`struct player`, `struct hand`, and siblings across `src/game/` (grep for
|
|
||||||
`+ 0x`, `handoffset`, `(u8 *)`, hardcoded `0x2A80`-class sizes, pointer
|
|
||||||
arithmetic on player). For each: N64 offset vs PC offset (struct is
|
|
||||||
0x2A8-class larger per D100), whether it's on a BUNKER1-reachable path,
|
|
||||||
severity. Produce a table + recommended fix order. Do NOT fix yet —
|
|
||||||
this is a survey feeding a follow-up.
|
|
||||||
|
|
||||||
**Deliverable:** `docs/AUDIT-M6-player-offsets.md` table + §F pointer.
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
- Tracks 1, 2, 3 dispatched as parallel investigation subagents
|
|
||||||
(read-mostly; each returns a writeup + optional narrow patch).
|
|
||||||
- Integrator (this session) reviews each, applies/adjusts verified narrow
|
|
||||||
fixes, rebuilds (`export PATH="/c/msys64/mingw64/bin:$PATH" &&
|
|
||||||
./build-pc.sh ntsc-final`), spot-checks `-level_09`, commits per-track
|
|
||||||
with §F docs.
|
|
||||||
- Repro: `./build-pc/ge007.x86_64.exe -level_09 -ml0 -me0 -mgfx100
|
|
||||||
-mvtx50 -mt700 -ma150`. Frame capture: `GE_PCDUMP="<range>:<stride>"` +
|
|
||||||
`tools_pc/pixcount.py` / `tools_pc/pixcount.py`.
|
|
||||||
- Sidecar regen (if a converter changes): `python tools_pc/d43_emit.py
|
|
||||||
ntsc-final`.
|
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
# PLAN — Linear pass: boot → menu → every solo level loads & renders
|
|
||||||
|
|
||||||
Paste-ready brief for a fresh session. Pivot from depth-first
|
|
||||||
(BUNKER1 + graphics rabbit-holes) to **breadth-first**: make the normal
|
|
||||||
player path work end-to-end (intro → credits → main menu → mission/level
|
|
||||||
select), then confirm **all 21 solo levels load and render** without
|
|
||||||
crashing. Turns the remaining work into a scoped list.
|
|
||||||
|
|
||||||
## Preflight (unchanged)
|
|
||||||
|
|
||||||
`CLAUDE.md` + `AGENTS.md` non-negotiables → `docs/HANDOFF.md` →
|
|
||||||
`docs/PORT-LEARNINGS.md` → `docs/AGENT-WORKFLOW.md` before any subagent.
|
|
||||||
|
|
||||||
## Scope decisions (locked with the user)
|
|
||||||
|
|
||||||
- **Intro / credits / menus:** bar is *functional, not crashing*. Menus
|
|
||||||
navigable start to finish; the intro is skippable with any button.
|
|
||||||
Cosmetic defects are **out of scope** here — log them in
|
|
||||||
`docs/GRAPHICS-BACKLOG.md`: gun-barrel Bond model missing (D75),
|
|
||||||
Nintendo-logo transform (D75), mirrored glyphs (D114/D116), no audio
|
|
||||||
(D77), partial disclaimer screen (D76).
|
|
||||||
- **Text X-mirror (D114/D116):** stays parked. Menus are navigable with
|
|
||||||
mirrored text.
|
|
||||||
- **Level sweep depth:** *load + render + no immediate crash* only. A
|
|
||||||
"soak" is pointless with no input (level triggers never fire). Play-to-
|
|
||||||
completion validation is a **separate human step** (complex input),
|
|
||||||
driven by a per-level checklist this plan produces (WS6).
|
|
||||||
- **N64 reference captures:** deferred. Use PC golden frames +
|
|
||||||
`tools_pc/framediff.py`; eyeball against memory/video for "matches
|
|
||||||
original". `tools_pc/mupen64/` is available if a level looks wrong.
|
|
||||||
|
|
||||||
## Facts established (exploration, this session)
|
|
||||||
|
|
||||||
**Front end is a stage** — `LEVELID_TITLE` (0x5A). Logic in
|
|
||||||
`src/game/front.c` (~8700 lines) + `src/game/title.c` (gun-barrel/logo
|
|
||||||
3D) + `src/game/ramromreplay.c` (attract demos). State machine = `MENU_*`
|
|
||||||
enum (`src/bondconstants.h:1831`), driven per frame by `menu_init()`
|
|
||||||
(`front.c:8536`) through three switch tables (`update_/init_/interface_
|
|
||||||
menuXX_*` at `front.c:8598/8637/8666`); rendered by
|
|
||||||
`menu_jump_constructor_handler()` (`front.c:8718`). Transitions:
|
|
||||||
`frontChangeMenu(menu, reload)` (`front.c:8513`). Boot entry:
|
|
||||||
`initgamedata.c:9` sets `menu_update = MENU_LEGAL_SCREEN`.
|
|
||||||
|
|
||||||
**Intro states** (each: `init_` loads assets, `interface_` advances on
|
|
||||||
timer OR any button, `constructor_` renders):
|
|
||||||
`MENU_LEGAL_SCREEN` → `NINTENDO_LOGO` → `RAREWARE_LOGO` → `EYE_INTRO`
|
|
||||||
(gun-barrel walk, "007", blood) → `GOLDENEYE_LOGO` → `DISPLAY_CAST`
|
|
||||||
(credit roll) → idle → `select_ramrom_to_play()` (attract demo).
|
|
||||||
Any button during intro sets `prev_keypresses` → logos short-circuit to
|
|
||||||
`MENU_FILE_SELECT`, cast roll skipped. Gun-barrel sub-state machine is
|
|
||||||
`gunbarrel_mode` (u8) in `title.c` (`insert_bond_eye_intro` `title.c:297`,
|
|
||||||
`renderGunbarrelEyeIntroSequence` `title.c:608`).
|
|
||||||
|
|
||||||
**Menu → level launch:** mission select →
|
|
||||||
`selected_stage = mission_folder_setup_entries[…].stage_id`
|
|
||||||
(`front.c:3301`) → `MENU_DIFFICULTY` → `MENU_BRIEFING` /
|
|
||||||
`MENU_007_OPTIONS` → `init_menu0B_runstage()` (`front.c:6900`) →
|
|
||||||
`bossSetLoadedStage(selected_stage)` → `g_MainStageNum` → `bossMainloop`
|
|
||||||
inner loop exits → `g_StageNum = g_MainStageNum` (`boss.c:647`) →
|
|
||||||
`lvlStageLoad`. **Level → level always routes back through the front end**
|
|
||||||
(`MENU_MISSION_COMPLETE`, `interface_menu0D_missioncomplete`
|
|
||||||
`front.c:7119`; auto-advance scans `mission_folder_setup_entries` for the
|
|
||||||
next `stage_id >= 0`). No direct in-stage jump.
|
|
||||||
|
|
||||||
**21 solo levels; LEVELID value == `-level_XX` number** (`boss.c:337`
|
|
||||||
decode `d0*10 + d1 - 0x210`). Mission order + numbers:
|
|
||||||
Dam 33, Facility 34, Runway 35, Surface1 36, Bunker1 09, Silo 20,
|
|
||||||
Frigate 26, Surface2 43, Bunker2 27, Statue 22, Archives 24, Streets 29,
|
|
||||||
Depot 30, Train 25, Jungle 37, Control 23, Caverns 39, Cradle 41,
|
|
||||||
Aztec 28, Egypt 32, Cuba 54. Enum: `src/bondconstants.h:1633-1685`;
|
|
||||||
mission table `mission_folder_setup_entries[]` `front.c:434-466`.
|
|
||||||
|
|
||||||
**Per-level memory args already have a table** — `memallocstringtable[]`
|
|
||||||
(`boss.c:101-142`), one row/LEVELID (`-ml -me -mgfx -mvtx -mt -ma`). The
|
|
||||||
auto-inject loop `boss.c:369-414` already selects the right row per stage
|
|
||||||
load — but it is gated on `g_DebugAndUpdateStageFlag`, which the port
|
|
||||||
only sets when `-level_` is **absent** (`boss.c:199-202`;
|
|
||||||
`rmonGetToken()` → 0 on PC, `port/src/n64stubs.c:134`). So bare
|
|
||||||
`-level_09` today uses default pools; hence the manual
|
|
||||||
`-ml0 -me0 -mgfx100 -mvtx50 -mt700 -ma150` in the HANDOFF repro line.
|
|
||||||
Documented TODO: `PCPortResearch.md` §H (D95 area) and `docs/HANDOFF.md`
|
|
||||||
"Next task".
|
|
||||||
|
|
||||||
**D88.4 is a cross-level blocker.** Every level's `Usetup*Z` feeds the
|
|
||||||
`propDefs` polymorphic record stream, which `tools_pc/d88_emit.py`
|
|
||||||
deliberately leaves un-byteswapped. BUNKER1 crashes in `setupDoor`
|
|
||||||
(`prop.c:971`) on a garbage BE `door->obj`. Any level hits the same class
|
|
||||||
when its prop set is walked. Groundwork: `tools_pc/d88_propdefs.py`,
|
|
||||||
`tools_pc/d88_layoutprobe.c` (machine `SZ`/`PTR` lines),
|
|
||||||
`tools_pc/d88_propdef_scan.py` (per-level type histogram).
|
|
||||||
Placeholder sizes still to resolve in `d88_propdefs.py`:
|
|
||||||
`VEHICLE`/`AIRCRAFT`/`TANK`/`AMMO`/`DEPOSIT_IN_ROOM`.
|
|
||||||
|
|
||||||
**Tooling:** `GE_PCDUMP="a-b:step"` → `./ppm/` (`port/src/video.c:181`);
|
|
||||||
`tools_pc/pixcount.py` (non-black pixel count / content check),
|
|
||||||
`tools_pc/framediff.py` (structural regression vs `tools_pc/golden/` —
|
|
||||||
the port is NOT frame-deterministic, D117), `tools_pc/ppm2bmp.py`.
|
|
||||||
Crash log `ge007.crash.log`; symbolicate
|
|
||||||
`addr2line -e build-pc/ge007.x86_64.exe -f -C <pc>` (image base
|
|
||||||
0x140000000). Objectives: `propDefs` `PROPDEF_OBJECTIVE_*`
|
|
||||||
(`bondconstants.h:4332`), status `src/game/objective_status.c`
|
|
||||||
(`get_status_of_objective()` `:161`) — needed only for WS6.
|
|
||||||
|
|
||||||
## Workstreams
|
|
||||||
|
|
||||||
WS1 is a prerequisite for all. WS2 (`front.c`/`title.c`/`lv.c`/
|
|
||||||
`ramromreplay.c`/`initmenus.c`/`port/`) and WS3 (`tools_pc/d88*` +
|
|
||||||
narrow `prop.c`) are disjoint → parallelisable. WS4 depends on WS3.
|
|
||||||
WS5 iterative. WS6 is the user's.
|
|
||||||
|
|
||||||
### WS1 — Frictionless per-level boot (small, first)
|
|
||||||
|
|
||||||
Goal: `./build-pc/ge007.x86_64.exe -level_XX` with no other args boots
|
|
||||||
any level with its correct pools.
|
|
||||||
|
|
||||||
Change: in `src/boss.c` under `#ifdef PORT`, also set
|
|
||||||
`g_DebugAndUpdateStageFlag = 1` when `-level_` **is** present (mirror of
|
|
||||||
`boss.c:199-202`). The existing loop `boss.c:369-414` then supplies the
|
|
||||||
right `memallocstringtable` row per stage; the generic preload at
|
|
||||||
`boss.c:207-210` is harmless (per-loop `tokenSetString` at `:413`
|
|
||||||
overrides). Debug-harness / ABI-class `#ifdef PORT` edit (same character
|
|
||||||
as the `-level_` ASCII parse already in the file). Document as a new Dxx
|
|
||||||
in `PCPortResearch.md` §F. Alt if boss.c is judged out of bounds:
|
|
||||||
synthesize the string in the port token layer (`port/src/system.c`
|
|
||||||
`sysGetTokenString`). Boss.c is simpler and preferred.
|
|
||||||
|
|
||||||
Also update `docs/HANDOFF.md` repro line; drop the manual `-m*` list once
|
|
||||||
verified.
|
|
||||||
|
|
||||||
Verify: `-level_09` alone == today's full-arg behaviour (same frame,
|
|
||||||
`framediff.py` green vs `tools_pc/golden/`).
|
|
||||||
|
|
||||||
### WS2 — Front-end walkthrough & crash repair (human playtest loop)
|
|
||||||
|
|
||||||
Goal: a human can go boot → (watch or skip intro) → file/folder select →
|
|
||||||
mode select → mission select → difficulty → briefing → level launches,
|
|
||||||
and after a level, mission-complete → next briefing, with **no crash /
|
|
||||||
hang / dead-end**. Cosmetic wrongness acceptable and logged.
|
|
||||||
|
|
||||||
Method: same loop as the M-10 input playtest — overseer builds, human
|
|
||||||
drives, defects come back as a list, overseer root-causes each (expect
|
|
||||||
the pointer-width / BE-rodata / N64-Gfx-sizing classes in
|
|
||||||
`PORT-LEARNINGS.md`), narrow `#ifdef PORT` fix, re-test.
|
|
||||||
|
|
||||||
Files in scope: `src/game/front.c`, `src/game/lv.c`
|
|
||||||
(`interface_menu05_fileselect` is here), `src/game/title.c`,
|
|
||||||
`src/game/initmenus.c`, `src/game/ramromreplay.c`, `port/src/*`. NOT
|
|
||||||
`prop.c` / `tools_pc/d88*` (WS3).
|
|
||||||
|
|
||||||
Human checklist:
|
|
||||||
1. Boot → legal → Nintendo → Rareware → gun-barrel → GE logo → cast roll
|
|
||||||
→ (idle) attract demo. Note any crash/hang; cosmetic noted separately.
|
|
||||||
2. Press a key during intro → confirm short-circuit to file select.
|
|
||||||
3. File/folder select: create a folder, enter it; folder-delete sub-UI;
|
|
||||||
30 s idle → back to legal screen.
|
|
||||||
4. Mode select → Mission select: page through all 20 mission entries,
|
|
||||||
each briefing text/photo loads without fault.
|
|
||||||
5. Pick mission + Agent difficulty + briefing → Start → level loads.
|
|
||||||
6. In-level: die / abort → mission-failed → menu. Complete a short level
|
|
||||||
(or cheat) → mission-complete → auto-advance to next briefing.
|
|
||||||
7. MP chain (lower priority): MP options → scenario/stage/char/handicap/
|
|
||||||
teams → MP match loads.
|
|
||||||
|
|
||||||
Do NOT fix here (log/confirm in `GRAPHICS-BACKLOG.md`): D75, D76, D77,
|
|
||||||
D114/D116.
|
|
||||||
|
|
||||||
Deliverable: front-end crash list + root causes + fixes;
|
|
||||||
`GRAPHICS-BACKLOG.md` updated with confirmed cosmetic state.
|
|
||||||
|
|
||||||
### WS3 — D88.4 propDefs converter (cross-level blocker)
|
|
||||||
|
|
||||||
Goal: `Usetup*Z` `propDefs` converts correctly offline for all 21 levels
|
|
||||||
so `setupDoor` / prop instantiation stops reading BE garbage.
|
|
||||||
|
|
||||||
Method: subagent brief, same shape as D69 / D88.1-3 (offline sidecar
|
|
||||||
converter). Byte-spec every `PROPDEF_*` record type (~40, enum
|
|
||||||
`bondconstants.h`), per-type field byteswap + pointer-width audit,
|
|
||||||
extend `tools_pc/d88_emit.py`. Use `d88_propdefs.py` /
|
|
||||||
`d88_layoutprobe.c` / `d88_propdef_scan.py`. Resolve the placeholder
|
|
||||||
sizes (`VEHICLE`/`AIRCRAFT`/`TANK`/`AMMO`/`DEPOSIT_IN_ROOM`) via
|
|
||||||
`d88_layoutprobe.c`.
|
|
||||||
|
|
||||||
Files: `tools_pc/d88_emit.py`, `tools_pc/d88_propdefs.py`,
|
|
||||||
`tools_pc/d88_layoutprobe.c`, + narrow `#ifdef PORT` in `src/game/prop.c`
|
|
||||||
if a struct-width mismatch surfaces (D79/D88 class).
|
|
||||||
|
|
||||||
Budget: structural — generous. On expiry: write up spec progress in
|
|
||||||
§F/D88 with confidence rating.
|
|
||||||
|
|
||||||
Verify: `-level_09` walks past `setupDoor` to a rendered BUNKER1 frame
|
|
||||||
with props; spot-check 2-3 other levels' prop walk.
|
|
||||||
|
|
||||||
### WS4 — Per-level load + render sweep
|
|
||||||
|
|
||||||
Goal: status matrix for all 21 solo levels — (a) `lvlStageLoad`
|
|
||||||
completes without fault, (b) ≥1 non-degenerate frame renders, (c)
|
|
||||||
survives ~300 frames / ~15 s with no input (immediate-crash check only).
|
|
||||||
|
|
||||||
Prereq: WS1 + WS3 landed.
|
|
||||||
|
|
||||||
Method: agent-driven, one level at a time (`-level_XX`). Each: launch
|
|
||||||
`GE_PCDUMP="1-300:30"`, capture `ge007.crash.log`, run `pixcount.py`.
|
|
||||||
Record PASS / CRASH@<site> / RENDERS-EMPTY / HANG. Symbolicate every
|
|
||||||
crash (`addr2line`). Group failures by root-cause class.
|
|
||||||
|
|
||||||
Deliverable: `docs/LEVEL-STATUS.md` — table (level, number, load, render,
|
|
||||||
crash site, notes), refreshed as WS5 closes items.
|
|
||||||
|
|
||||||
### WS5 — Per-level crash triage (iterative)
|
|
||||||
|
|
||||||
Goal: drive the WS4 matrix to "all 21 load + render".
|
|
||||||
|
|
||||||
Method: one focused investigation per distinct failure class (subagent
|
|
||||||
brief per `AGENT-WORKFLOW.md` template; read `PORT-LEARNINGS.md` first).
|
|
||||||
Expected classes: pointer-width struct growth in per-level assets,
|
|
||||||
BE-rodata not converted, N64-Gfx/Vtx sizing, per-level pool exhaustion
|
|
||||||
(`memallocstringtable` PC bumps, cf. D95). Fix = narrow `#ifdef PORT` or
|
|
||||||
converter extension; document each in §F.
|
|
||||||
|
|
||||||
Verify per fix: target level's WS4 check → PASS *and* `-level_09`
|
|
||||||
framediff stays green.
|
|
||||||
|
|
||||||
### WS6 — Human completion-validation pass (user-run, after WS4 green)
|
|
||||||
|
|
||||||
Goal: confirm each level is playable start to finish and matches the
|
|
||||||
original — the part needing real input.
|
|
||||||
|
|
||||||
Deliverable produced for the user: `docs/LEVEL-PLAYTEST.md` — per-level
|
|
||||||
checklist template: spawn point; each difficulty-gated objective
|
|
||||||
(`objective_status.c`) reachable and registering COMPLETE; guards / props
|
|
||||||
/ doors behave; level exit / mission-complete fires clean; timing +
|
|
||||||
layout match retail. User fills it per level; findings feed a WS5-style
|
|
||||||
triage round.
|
|
||||||
|
|
||||||
## Verification (overall)
|
|
||||||
|
|
||||||
- Per merged patch: `export PATH="/c/msys64/mingw64/bin:$PATH" &&
|
|
||||||
./build-pc.sh ntsc-final` green, `/linkcheck` clean, single-frame
|
|
||||||
`GE_PCDUMP` diff vs `tools_pc/golden/` via `framediff.py`.
|
|
||||||
- WS1: `-level_09` alone == old manual-arg behaviour.
|
|
||||||
- WS3: BUNKER1 renders props past `setupDoor`.
|
|
||||||
- WS4/WS5: `docs/LEVEL-STATUS.md` all 21 rows = load+render PASS.
|
|
||||||
- Regenerate sidecars after any converter change: `python
|
|
||||||
tools_pc/d43_emit.py ntsc-final && python tools_pc/d69_emit.py
|
|
||||||
ntsc-final && python tools_pc/d88_emit.py ntsc-final --regen`.
|
|
||||||
|
|
||||||
## Out of scope (tracked elsewhere — do not start here)
|
|
||||||
|
|
||||||
- D75 / D76 / D114 / D116 / D77 and all `docs/GRAPHICS-BACKLOG.md` items.
|
|
||||||
- D118a/b input polish, rebinding, gamepad hotplug, `ge007.ini`.
|
|
||||||
- Weapon-model-doesn't-draw (AUDIT-M6 #5), rest of the `struct player`
|
|
||||||
offset audit.
|
|
||||||
- Audio (Phase 3), saves/EEPROM (Phase 4), `docs/BACKLOG.md` (PC
|
|
||||||
graphics options, co-op, netplay).
|
|
||||||
@@ -1,459 +0,0 @@
|
|||||||
# SMALL-FIXES — ready-to-execute fix notes for small / low-level open items
|
|
||||||
|
|
||||||
**Read-only document.** This file records *proposed fixes and verification
|
|
||||||
procedures* for the small, low-level bugs and noted issues that are parked in
|
|
||||||
`BACKLOG.md`, `GRAPHICS-BACKLOG.md`, `HANDOFF.md`, and the §F/§H finding log.
|
|
||||||
No code was changed when writing it (M-28). Each item is self-contained enough
|
|
||||||
to be picked up cold; items marked **VERIFY-ONLY** already have their fix in
|
|
||||||
the tree and only need confirmation + commit hygiene.
|
|
||||||
|
|
||||||
Standing rules for every item below:
|
|
||||||
|
|
||||||
- Game-code edits must stay inside the narrow `#ifdef PORT` ABI/layout
|
|
||||||
exception (AGENTS.md #2); port-layer work has no such limit. Anything
|
|
||||||
bigger than described here → stop, write it up with a confidence rating.
|
|
||||||
- New findings get the next free Dxx in `docs/PCPortResearch.md` §F/§H + an
|
|
||||||
index row; recurring bug classes go to `docs/PORT-LEARNINGS.md`.
|
|
||||||
- After any converter change: full regen chain
|
|
||||||
`python tools_pc/d43_emit.py ntsc-final && python tools_pc/d69_emit.py
|
|
||||||
ntsc-final && python tools_pc/d88_emit.py ntsc-final --regen`.
|
|
||||||
- Verification anchors (quiet machine only — D153): target level boots to ≥1
|
|
||||||
non-degenerate frame (`tools_pc/pixcount.py`), `-level_09` + `-level_20`
|
|
||||||
unregressed (`tools_pc/framediff.py`, use `--mask` for the HUD).
|
|
||||||
- Crashes: `tools_pc/debug.ps1` (gdb + FATAL line + backtrace → `gdb.txt`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## A. VERIFY-ONLY — fix is in the tree, needs confirmation
|
|
||||||
|
|
||||||
### A1. D154 — `bg.c` room-geometry hit-test GBI parser (committed WIP `072b5c44`)
|
|
||||||
|
|
||||||
**Status:** written + builds green; **never exercised** (a no-input
|
|
||||||
`GE_PCDUMP` capture fires no bullets, and the M-28 box was thrashed).
|
|
||||||
|
|
||||||
**What it is:** `bgTestRayIntersectionInRoom` (`src/game/bg.c:~3331`, called
|
|
||||||
from `bgTestBulletHitBackground` on every shot that resolves against
|
|
||||||
background geometry) was the D135 sibling: raw 8-byte-N64-`Gfx` byte/word
|
|
||||||
indexing into the 16-byte PC room DL → desync → OOB `vtxbase[idx]` / walk-off
|
|
||||||
AV the first time a wall or floor is shot. The `#ifdef PORT` port reads
|
|
||||||
`gdl->words.w0/.w1` (G_TRI1 from `(w1>>16/8/0)&0xff/10`; G_TRI4 nibbles from
|
|
||||||
`(u32)words.w0/.w1` with the byte-mapping table in the code comment); both
|
|
||||||
`texturenum` recoveries return `-1` (generic impact decal/sound, parked w/ D77).
|
|
||||||
|
|
||||||
**Procedure:**
|
|
||||||
1. Quiet machine. `./tools_pc/debug.ps1 -level_09`.
|
|
||||||
2. Get into a firefight and **shoot walls/floor deliberately** (not just
|
|
||||||
guards) for ~60 s — this is the only trigger.
|
|
||||||
3. Pass = no AV in `bgTestRayIntersectionInRoom` / `bgTestBulletHitBackground`,
|
|
||||||
bullets still resolve hits (decal/sound may be generic).
|
|
||||||
4. `-level_09` + `-level_20` framediff unregressed.
|
|
||||||
5. Then fix up the commit (`072b5c44` is labelled "WIP, unverified") and close
|
|
||||||
the §F D154 row.
|
|
||||||
|
|
||||||
**Risk:** low — `#ifdef PORT` only, N64 path verbatim under `#else`. If a new
|
|
||||||
fault appears in this function, the nibble-mapping table is the thing to
|
|
||||||
re-derive against `rsp/graphics/gmain.s` (ground truth for GBI decoding).
|
|
||||||
|
|
||||||
### A2. D139 — stage-unload `cleanupObjects` type byte (fix committed M-23)
|
|
||||||
|
|
||||||
**Status:** fix in (`src/game/cleanup_objects.c`, `#ifdef PORT`
|
|
||||||
`CLEANUP_PDTYPE(o)` = `((PropDefHeaderRecord*)o)->type` instead of `(u8)obj[0]`,
|
|
||||||
which on LE reads `extrascale` and walks off the propDef blob). **Never
|
|
||||||
verified** — the fast teardown test hit D140 first (since fixed), and M-27's
|
|
||||||
level-exit loop was playtested through the *real* front-end flow, which does
|
|
||||||
exercise `lvlUnloadStageTextData` → `cleanupObjects` on level exit.
|
|
||||||
|
|
||||||
**Procedure:**
|
|
||||||
1. Re-check whether M-27's Dam→Facility playthrough already exercised it:
|
|
||||||
if a real level exit completed without a teardown crash after the D139
|
|
||||||
commit, this is done — just record "verified via M-27 exit loop" on the
|
|
||||||
§F row and stop.
|
|
||||||
2. Otherwise: `debug.ps1 -level_09`, complete/abort a real level exit (not
|
|
||||||
pause-abort) and confirm no AV in `objFree` / `objFreeEmbedmentOrProjectile`.
|
|
||||||
|
|
||||||
**Risk:** low — one-line read-source change, every other propDef consumer
|
|
||||||
already uses the struct member.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## B. Root-caused small fixes (not yet applied)
|
|
||||||
|
|
||||||
### B1. D74 — dead + OOB texture-wrap block in fast3d (`port/fast3d/gfx_pc.cpp:~1570`)
|
|
||||||
|
|
||||||
**Status:** latent. Harmless for CLAMP glyphs today; could corrupt wrapped
|
|
||||||
textures on triangles (room panels, model skins).
|
|
||||||
|
|
||||||
**Root cause (confirmed against the tree):** two independent defects in the
|
|
||||||
D74 pre-wrap block inside the per-vertex UV loop:
|
|
||||||
1. **Always-false guard.** The codebase's `gbi.h` (`include/PR/gbi.h`, used by
|
|
||||||
both game and fast3d via `port/shim/PR/gbi.h`) encodes wrap mode as a
|
|
||||||
2-bit field: `G_TX_NOMIRROR|G_TX_WRAP = 0x0`, `G_TX_MIRROR = 0x1`,
|
|
||||||
`G_TX_CLAMP = 0x2`. So `cms & G_TX_WRAP` is `& 0` — **always false**. The
|
|
||||||
"is this tile in wrap mode" test must be **"clamp bit not set"**:
|
|
||||||
`(cms & G_TX_CLAMP) == 0`. (Compare the correct switch at
|
|
||||||
`gfx_opengl.cpp:706-712`, which keys on the four combinations.)
|
|
||||||
2. **OOB index.** `tex_width2[i]` / `tex_height2[i]` are **per-texunit `[2]`
|
|
||||||
arrays** (set up at `gfx_pc.cpp:~1471`), but the block is inside the
|
|
||||||
**per-vertex** loop where `i` is 0..2 → read of slot `[2]` if the guard
|
|
||||||
ever fires.
|
|
||||||
|
|
||||||
**Fix:**
|
|
||||||
1. Hoist the tile-window computation out of the vertex loop: for each used
|
|
||||||
texunit `t`, compute once — `tw = tex_width2[t]`, `th = tex_height2[t]`,
|
|
||||||
`wrapS = (cms & G_TX_CLAMP) == 0 && tw < tex_width[t]`, same for T.
|
|
||||||
2. In the vertex loop, apply `u = fmodf(u, tw); if (u<0) u += tw; u += uls/4`
|
|
||||||
(and V) only when the precomputed flag is set.
|
|
||||||
3. Delete nothing else — leave the surrounding D74 comment, extend it with a
|
|
||||||
line noting the `G_TX_WRAP == 0` encoding trap (PORT-LEARNINGS §D class:
|
|
||||||
"N64 hardware idioms fast3d does not emulate" — add the bit-encoding note).
|
|
||||||
|
|
||||||
**Verify:** build; `-level_09` + `-level_20` framediff; then a level with
|
|
||||||
visible wrapped triangle textures — **Depot (`-level_30`)** is the known
|
|
||||||
worst case (overlaps B2, but D74's block only fires for WRAP tiles). Check
|
|
||||||
`tools_pc/golden/` dumps for no new garbage.
|
|
||||||
|
|
||||||
**Risk:** low-medium. The block has never run, so this is *new* behavior —
|
|
||||||
if wrapped textures suddenly look wrong, the fmod window size (`tw` from
|
|
||||||
`lrs-uls+4`/4) is the thing to validate against `rsp/graphics/gmain.s`'s
|
|
||||||
tile-window handling before "fixing" it further.
|
|
||||||
|
|
||||||
### B2. D152 — find the leaking `osSetIntMask` site + proper narrow fix
|
|
||||||
|
|
||||||
**Status:** mitigated (self-healing lock in `port/src/libultra.c`, M-28) — a
|
|
||||||
leaked section now costs ~2 s + a log line instead of a permanent black
|
|
||||||
screen. The **root leak is still unidentified**.
|
|
||||||
|
|
||||||
**Procedure to root it:**
|
|
||||||
1. Replay the trigger: Facility (`-level_34`), kill Trevelyan → mission-fail
|
|
||||||
fade-out, under `tools_pc/debug.ps1`.
|
|
||||||
2. When the log shows `D152: ... stealing from owner=...`, take the logged
|
|
||||||
stale-owner address **and** the stealing caller's return address;
|
|
||||||
`addr2line -e build-pc/ge007.x86_64.exe -f -C <both>` (image base
|
|
||||||
`0x140000000`). The *owner* is the leak site.
|
|
||||||
3. Audit target: libaudio's `alEvtqPostEvent` / `alEvtqNextEvent` /
|
|
||||||
`sndSetSfxSlotVolume` paths (`src/libultra/audio/event.c`, `src/snd.c`) —
|
|
||||||
look for early returns between an `osSetIntMask(OS_IM_NONE)` and its
|
|
||||||
matching `OS_IM_ALL` (the D147 assumption that "the decomp never blocks /
|
|
||||||
never early-returns while masked" is falsified on this path). Also check
|
|
||||||
transient threads that acquire-and-exit (`gdb.txt` showed New Thread /
|
|
||||||
exited churn every few k frames).
|
|
||||||
|
|
||||||
**Proper narrow fix (once the site is known), pick one:**
|
|
||||||
- **(a) Preferred:** a dedicated `ALEventQueue` lock (mutex per queue or one
|
|
||||||
global in the port layer) taken by `alEvtqPostEvent`/`alEvtqNextEvent`, so
|
|
||||||
the audio event paths stop using the process-wide `osSetIntMask` section at
|
|
||||||
all; then the self-healing steal becomes dead weight that can be reverted to
|
|
||||||
the plain D147 recursive mutex.
|
|
||||||
- **(b) Cheaper:** make the leak site balanced (the actual bug), keep the
|
|
||||||
self-healing lock as belt-and-braces.
|
|
||||||
|
|
||||||
**Verify:** replay the mission-fail 3×: no `D152:` steal log lines, fade-out
|
|
||||||
completes, screen returns to the mission-failed UI. `-level_09` framediff.
|
|
||||||
|
|
||||||
**Risk:** (a) is port-layer only if the lock lives in the shimmed
|
|
||||||
`alEvtq*` wrappers — but note `event.c` is *compiled game-side libultra*, so
|
|
||||||
the lock likely has to go inside `event.c` under `#ifdef PORT` (ABI-exception
|
|
||||||
class, document as Dxx). Keep the self-healing lock either way until audio
|
|
||||||
(Phase 3) lands.
|
|
||||||
|
|
||||||
### B3. D120 — blood-stain hang: extend `d43_emit.py` for opcode-0x18 — **DONE (M-30)**
|
|
||||||
|
|
||||||
**Status:** FIXED (`tools_pc/d43_emit.py`, commit `da98cdf1` + validation
|
|
||||||
`4a7ab609`). The `CollisionVertices` sub-array + its `.index` /
|
|
||||||
`CollisionRelatedNode` / `CollisionRelatedIndex` fields were already converted
|
|
||||||
(`op24_is_collision` path). The real gap was narrower than this note assumed:
|
|
||||||
**`PointUsage[]` was reserved in the layout pass (`add_region(puo, 2*nv)`) but
|
|
||||||
the emit pass never wrote it → all-zero → `chr.c`'s decal walk
|
|
||||||
`index = PointUsage[index]` cycled 0→0** (capped by the guard). Fix:
|
|
||||||
`op24_pointusage[puo] = nv` + a byteswap loop in the emit pass. Converter now
|
|
||||||
validates: 1469 regions / 70177 `s16` entries round-trip, every index
|
|
||||||
< numVertices, none all-zero. The `chr.c` guard is kept as a safety net.
|
|
||||||
**Verify:** BUNKER1 firefight — blood decals on shot guards should appear, no
|
|
||||||
hang. Regen chain must be re-run for the sidecar to update.
|
|
||||||
|
|
||||||
**Procedure:**
|
|
||||||
1. Byte-spec the record: dump one converted guard model's opcode-0x18 region
|
|
||||||
from a sidecar (`data/pccg-*`) and diff against the raw ROM bytes; write
|
|
||||||
out the N64 layout (fields, sub-array strides, terminators) in the
|
|
||||||
converter comment — same method as the D69/D88 audits.
|
|
||||||
2. Extend `d43_emit.py`: emit the widened struct with the 6 pointers via
|
|
||||||
`put_ptr`, and convert both sub-arrays (endianness + PC stride).
|
|
||||||
3. Keep the `chr.c` guard until verification passes, then decide whether to
|
|
||||||
keep it as a safety net (recommended — same philosophy as D85's bounds
|
|
||||||
check) or revert.
|
|
||||||
4. Regen full chain; verify: BUNKER1 (`-level_09`) guard firefight — blood
|
|
||||||
decals now appear and no hang; framediff anchors.
|
|
||||||
|
|
||||||
**Risk:** medium — converter work with a runtime guard underneath, so a bad
|
|
||||||
conversion degrades to today's behavior rather than crashing. Good subagent
|
|
||||||
brief on its own (per HANDOFF-ARCHIVE M-9).
|
|
||||||
|
|
||||||
### B4. B3 — RMB aim too sensitive (`port/src/input.c`)
|
|
||||||
|
|
||||||
**Status:** user-reported (M-28 playtest). Port-only.
|
|
||||||
|
|
||||||
**Current math (aim mode, `input.c:~471`):**
|
|
||||||
`m = 61 + |dx| * (MouseAimSpeed/100) * AIM_GAIN(4.0)`, clamped to
|
|
||||||
`60 + AIM_BAND(20)` = 80. At the default `MouseAimSpeed=50`, **25 px/poll
|
|
||||||
saturates the band** — a fast flick is "max stick" for many polls, and GE's
|
|
||||||
aim curve `(stick-60)/10` then runs at full rate the whole time.
|
|
||||||
|
|
||||||
**Fix (in order of cheapness):**
|
|
||||||
1. Lower the default: `mouseAimSpeed = 50 → 25` (`input.c:~128`). One line;
|
|
||||||
users can still dial it back up in `ge007.ini`.
|
|
||||||
2. Make the band itself tunable: register `Input.AimBand` (int, 5..40,
|
|
||||||
default 20) and replace the `AIM_BAND` constant in the two clamp sites —
|
|
||||||
a smaller band = finer control near the gate, a larger one = more headroom.
|
|
||||||
3. Optional curve change if it still feels bad: make the mapping into the
|
|
||||||
band sub-linear (`m = 61 + AIM_BAND * sqrt(gx / saturating_px)`) so small
|
|
||||||
movements get proportionally less stick — but only after 1+2 are
|
|
||||||
playtested; don't stack changes.
|
|
||||||
|
|
||||||
**Verify:** playtest-only (no headless signal): aim at a guard in BUNKER1,
|
|
||||||
flick the mouse, confirm no overshoot orbiting the target; hipfire turn
|
|
||||||
(`MouseTurnSpeed`) is untouched by this.
|
|
||||||
|
|
||||||
**Risk:** trivial — port-only, config-backed, default-reversible.
|
|
||||||
|
|
||||||
### B5. B4 — menu screens need an absolute pointer mode (`port/src/input.c`)
|
|
||||||
|
|
||||||
**Status:** user-reported (M-28). Port-only design item; **no code yet**.
|
|
||||||
|
|
||||||
**Problem:** front-end / watch menus with the crosshair cursor behave like
|
|
||||||
an analog-stick game, not a PC pointer. Today all mouse input is *delta*
|
|
||||||
based (turn/look), and menu navigation goes through `joyGetStickX/Y` level
|
|
||||||
checks in `front.c` / `options.c` — synthetic stick deltas make menu cursors
|
|
||||||
crawl or slam.
|
|
||||||
|
|
||||||
**Design (port-only, no `src/` change):**
|
|
||||||
1. Add a **menu-pointer mode** to the input layer: when active, emit
|
|
||||||
*absolute* stick values derived from the cursor position —
|
|
||||||
`sx = (mouseX / windowW) * 2 - 1 → [-80..80]`, same for Y. GE's front-end
|
|
||||||
maps stick deflection to on-screen cursor position, so absolute→absolute
|
|
||||||
gives true pointer behavior with zero game-code changes.
|
|
||||||
2. **Mode detection is the open question** — the port doesn't know when the
|
|
||||||
game is in a menu. Options, cheapest first:
|
|
||||||
- **(a)** Heuristic: mouse is *ungrabbed* (cursor visible) ⇒ menu mode.
|
|
||||||
Pair with a rule that front-end screens release the grab and gameplay
|
|
||||||
grabs it (the grab state already flips on focus loss; extend it to be
|
|
||||||
driven by a key, e.g. hold-to-release, or auto-detect via the same
|
|
||||||
signals D145 used for ESC-as-B). Simple, no game-state peeking.
|
|
||||||
- **(b)** Peek a game-side "am I in the front end" flag through an existing
|
|
||||||
exported symbol (check `front.c` / `lv.c` globals) and branch on it in
|
|
||||||
`inputPoll`. Read-only access to a global is arguably inside the
|
|
||||||
documented exception class, but **record a Dxx** if used.
|
|
||||||
- **(c)** Explicit user toggle (config key + hotkey). Ugly but zero risk.
|
|
||||||
3. While in menu mode: suppress aim/turn delta emission entirely; wheel and
|
|
||||||
clicks map to A/B as today.
|
|
||||||
|
|
||||||
**Verify:** drive the mission-select / SELECT FILE screens with the mouse —
|
|
||||||
cursor should track 1:1, click selects; in-game aim unaffected.
|
|
||||||
|
|
||||||
**Risk:** low (port-only) under option (a)/(c); (b) needs a Dxx write-up.
|
|
||||||
Sequence *after* B4 so the gameplay feel is settled first.
|
|
||||||
|
|
||||||
### B6. D118d latent sibling — `front.c` menu-nav stick level checks
|
|
||||||
|
|
||||||
**Status:** latent, noted M-27. Same bug class as the fixed watch-menu
|
|
||||||
over-scroll (`options.c`, `GE_WATCH_STICK_FAST{UP,DOWN}` → 0 under PORT).
|
|
||||||
|
|
||||||
**What to do:** find the `joyGetStick*InRange` / raw `joyGetStickY < -0x46`
|
|
||||||
style *level* checks in `front.c` menu navigation (main menu, mode-select,
|
|
||||||
mission-select paging) and apply the same treatment: under `#ifdef PORT`
|
|
||||||
drop the stick fast-scroll term so keyboard/digital input goes through the
|
|
||||||
latched single-step path only. Keep N64 verbatim under `#else`.
|
|
||||||
|
|
||||||
**Verify:** keyboard W/S (and D-pad) paging on every front-end menu = one
|
|
||||||
step per press, no multi-skip; controller sticks still fast-scroll.
|
|
||||||
|
|
||||||
**Risk:** low — mechanical copy of an already-landed fix. Do it together with
|
|
||||||
B5 so the menu-input area is touched once.
|
|
||||||
|
|
||||||
### B7. D118a residual — hipfire pitch is digital, yaw is analog
|
|
||||||
|
|
||||||
**Status:** parked minor feel issue. The *full* fix needs a game-side hook;
|
|
||||||
the deferred design is:
|
|
||||||
|
|
||||||
An `#ifdef PORT` analog-aim hook in `bondview.c`/`bondview2.c` where GE reads
|
|
||||||
digital C-up/C-down for hipfire pitch — replace the digital read with the
|
|
||||||
analog stick-Y value (which our input layer can emit even in hipfire, since
|
|
||||||
stick-Y is "move" only *when the game consumes it as such*; validate that
|
|
||||||
emitting a small stick-Y does not also make Bond walk). This is the first
|
|
||||||
*behavioral* `src/` edit of the aim path — it exceeds pure ABI/layout; **write
|
|
||||||
it up with a confidence rating before implementing** (AGENTS.md #2: "stop and
|
|
||||||
write it up"), and gate it behind a config key defaulting to *current*
|
|
||||||
behavior.
|
|
||||||
|
|
||||||
**Cheaper alternative worth trying first:** raise `MOUSE_PITCH_THRESH`
|
|
||||||
granularity by emitting the hipfire pitch as *repeated C-button pulses with
|
|
||||||
dwell proportional to speed* — pure port-layer, approximates analog. Try this
|
|
||||||
before touching `src/`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## C. Cosmetic / graphics — investigation plans (root cause not fully known)
|
|
||||||
|
|
||||||
These are parked below crash/level work (`GRAPHICS-BACKLOG.md`). Each entry is
|
|
||||||
the *next diagnostic step*, because the root cause is not yet pinned. Do not
|
|
||||||
treat them as ready-to-apply fixes.
|
|
||||||
|
|
||||||
### C1. D114/D116 — HUD text + ammo digits X-mirrored per glyph
|
|
||||||
|
|
||||||
**Hard rule (PORT-LEARNINGS §D2): do NOT re-run the static trace** (four
|
|
||||||
sessions did; every stage verified non-mirrored at the GL boundary) and do NOT
|
|
||||||
apply a global texrect S-swap (mirrors the whole screen).
|
|
||||||
|
|
||||||
**Next attempt — pick one, in order:**
|
|
||||||
1. **RenderDoc (or apitrace) capture of exactly one glyph's texrect draw.**
|
|
||||||
The contradiction is "VBO verified non-mirrored but pixels mirrored" — a
|
|
||||||
GPU-side capture resolves it in one shot: inspect the bound texture's
|
|
||||||
upload bytes vs the `gDPLoadTextureBlock` source, and the final quad's UVs.
|
|
||||||
2. **Asymmetric-1-texel experiment:** temporarily force one HUD glyph's image
|
|
||||||
to a 2×1 or 3×1 *asymmetric* pattern (e.g. left half white) in the port
|
|
||||||
layer; if it renders right-half-white, the flip is between upload and
|
|
||||||
sample (texture origin / `glPixelStorei(GL_UNPACK_ROW_LENGTH)` / Y-flip);
|
|
||||||
if it renders correctly, the mirror is applied *per-glyph placement*
|
|
||||||
(texrect S-range inversion in `textRenderGlyph`'s GBI — check the
|
|
||||||
`gDPTile`/`gDPSetTile` uls/lrs signs for the HUD tiles specifically).
|
|
||||||
|
|
||||||
Suspects to check with the capture in hand: 4-bit CI tile upload stride vs
|
|
||||||
`G_IM_SIZ_4b` sampling, TLUT index endianness (wrong-but-symmetric TLUT would
|
|
||||||
not mirror — deprioritize), and the `uls/lrs` sign convention for *rect*
|
|
||||||
draws (D116 is a rect path; triangles were never suspected).
|
|
||||||
|
|
||||||
### C2. D75 — front-end 3D models misplaced / absent (logo, gun-barrel Bond, cast)
|
|
||||||
|
|
||||||
Triage per §F D75 — **distinguish (a) vs (b) before any fix:**
|
|
||||||
1. For the Nintendo logo and one cast model, determine which matrix path
|
|
||||||
builds their transforms: `guRotate`/`guLookAt` (→ candidate (a), D73
|
|
||||||
scope gap in `guint.h` `DVAL()`) or the `animInit` + embedded raw-offsets
|
|
||||||
into `struct player` path (→ candidate (b), independent model/RW-pool bug,
|
|
||||||
cf. D56/D100/D102 — all now-fixed struct-pun classes; a *new* instance of
|
|
||||||
the class is the leading hypothesis).
|
|
||||||
2. Capture `GE_PCDUMP` frames across the logo transition and gun-barrel/cast
|
|
||||||
segments to localize which frame first diverges.
|
|
||||||
3. Files: `src/libultra/gu/guint.h`, `src/game/model.c`, `bondview2.c`
|
|
||||||
(animInit / modelSetScale), `port/fast3d/gfx_pc.cpp`.
|
|
||||||
|
|
||||||
**Note:** D148/D149 below are the same family — fixing D75's root cause will
|
|
||||||
likely take them with it. The D149 garbled geometry is also worth one check on
|
|
||||||
the *converter* side: the malformed sub-DL (`seg5+0x9ee4`, unresolved matrix
|
|
||||||
ptr + garbage opcodes) may be a `d43_emit.py` conversion defect rather than a
|
|
||||||
runtime transform bug — dump that sub-DL's raw ROM bytes vs the sidecar before
|
|
||||||
assuming it's runtime.
|
|
||||||
|
|
||||||
### C3. D76 — disclaimer screen only partially drawn
|
|
||||||
|
|
||||||
Likely the D68 image-table class, incomplete: some `sImageTableEntry`s the
|
|
||||||
disclaimer references are missed by `port/src/gimgfixup.c`'s bswap/shadow-sync.
|
|
||||||
|
|
||||||
**Procedure:**
|
|
||||||
1. Enumerate the image entries the disclaimer screen references (trace
|
|
||||||
`textRender`/`display_image_at_position` calls in the legal-screen code, or
|
|
||||||
log every `gDPLoadTextureBlock` source during PPM frames f20–f100).
|
|
||||||
2. For each, confirm it is covered by the D68 fixup (bswapped IMAGESEG w1 +
|
|
||||||
synced compiled globalDL shadow). The "only the classification line + one
|
|
||||||
line below draw" symptom = exactly the covered subset rendering.
|
|
||||||
3. Files: `port/src/gimgfixup.c`, `src/game/tex.c`, image-table consumers.
|
|
||||||
|
|
||||||
### C4. D148 / D149 — Dam exit cutscene absent; MISSION COMPLETE models garbled
|
|
||||||
|
|
||||||
Both are D75 family (see C2). Additional notes:
|
|
||||||
- **D148:** the missing piece is a *scripted* sequence — cinematic camera +
|
|
||||||
Bond rappel anim. Once D75's model path is fixed, verify the cutscene
|
|
||||||
trigger still fires (check whether the exit trigger even dispatches the
|
|
||||||
cinematic on PC — it may be gated on a front-end state flag our flow skips;
|
|
||||||
`GE_STARTMENU` tooling can crash-test the screen in isolation).
|
|
||||||
- **D149:** see the converter-side check in C2. D144/D146 already keep the
|
|
||||||
game alive on the corrupt DL; the fix should make the model *correct*, then
|
|
||||||
D146's "unknown opcode → end DL" guard can stay as a safety net.
|
|
||||||
|
|
||||||
### C5. D85 (residual) — room DL `texLoadFromGdl` decode
|
|
||||||
|
|
||||||
**Status:** OPEN but safety-netted; geometry renders, so this is below level
|
|
||||||
work. Remaining: decode the room-specific opcodes / CC-RM-LUT markers that
|
|
||||||
`texLoadFromGdl` still handles generically, and verify `csize_*_DL_binary`
|
|
||||||
sizing. Only worth touching if a level shows placeholder/garbage textures on
|
|
||||||
room geometry that can't be explained by B2 (Depot wrong-colors).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## D. Housekeeping / cleanup (no gameplay effect)
|
|
||||||
|
|
||||||
### D1. Stale §F index row — D132 says "proposed fix (not applied)"
|
|
||||||
|
|
||||||
The D132 fix **is applied** in the tree: `tools_pc/d88_propdefs.py`
|
|
||||||
(`PROPDEF_PC_BYTES[14/19]=32, [44]=40`; the `(14,19,38,44)` handler emits each
|
|
||||||
`Index{k}` into the low 4 B of the 8-aligned slot at PC `8+8*k`) and
|
|
||||||
`src/game/loadobjectmodel.c sizepropdef()` (LINK/SWITCH/LOCK_DOOR → 8 words,
|
|
||||||
SAFE_ITEM → 10). The §F index row still reads "proposed fix (not applied) —
|
|
||||||
see below" while the subsection says "APPLIED (M-20, commit pending)".
|
|
||||||
**Action:** update the index row to `resolved (M-20)` and confirm the M-20
|
|
||||||
commit landed (git-blame both files). Docs-only.
|
|
||||||
|
|
||||||
### D2. Stale BACKLOG technical-cleanup items
|
|
||||||
|
|
||||||
`docs/BACKLOG.md` "Technical cleanup" lists:
|
|
||||||
- ~~"Add a D24-implications bug class to PORT-LEARNINGS"~~ — **done M-19**
|
|
||||||
(PORT-LEARNINGS §E, lines ~374).
|
|
||||||
- ~~"Watch item: `osYieldThread` = `Sleep(0)`"~~ — **done M-19** (§E, line
|
|
||||||
~407). Keep the watch item itself; it's still accurate (the one place we
|
|
||||||
fake cooperative scheduling — first suspect for level-sweep hot-loop
|
|
||||||
misbehavior under host load).
|
|
||||||
- "Remove TEMP D63 debug scaffolding" — **partially done M-19** (`libultra.c`
|
|
||||||
+ game-side probe blocks removed). Remaining: `port/fast3d/gfx_pc.cpp`
|
|
||||||
trail code — the `/* TEMP D63 (strip before commit) */` block at ~2462, the
|
|
||||||
`GE_D63`-gated dram-branch trace at ~2516-2531, and the unknown-opcode dump
|
|
||||||
at ~2759-2765. All env-gated / inert; strip in one pass (keep the
|
|
||||||
`cached = getenv("GE_D63")` pattern only if a probe is retained — recommend
|
|
||||||
removing all of it).
|
|
||||||
|
|
||||||
**Action:** one docs+code cleanup commit: mark the two done items, strip the
|
|
||||||
gfx_pc.cpp D63 trail, rebuild + `-level_09` framediff.
|
|
||||||
|
|
||||||
### D3. `osYieldThread = Sleep(0)` watch item (no action — recorded)
|
|
||||||
|
|
||||||
When a flaky timing bug appears (especially Phase 3 audio underruns): reach
|
|
||||||
first for host thread priorities (`SetThreadPriority`: scheduler thread
|
|
||||||
time-critical, tick normal) and the deferred `GE_DETERM` mode (D117) — not
|
|
||||||
for kernel changes. See PORT-LEARNINGS §E.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Status (updated M-30)
|
|
||||||
|
|
||||||
- **B3 / D120** — DONE (PointUsage emit + validation).
|
|
||||||
- **B4** — DONE (M-29 `0bd0ceec`).
|
|
||||||
- **B5** — DONE (M-30 velocity mode; **M-31 D165** replaced it with a true
|
|
||||||
~1:1 P-controller pointer — `Input.MenuPointerMode` 1=pointer default,
|
|
||||||
0=old velocity). Feel-check owed.
|
|
||||||
- **B7 / D118a** — DONE (M-31 **D166**, cheaper port-only alternative):
|
|
||||||
hipfire pitch is now speed-proportional C-button pulses, not a digital
|
|
||||||
threshold. `Input.HipfirePitchSpeed` tunes it. The full game-side analog
|
|
||||||
hook remains unimplemented (not needed unless the pulse still feels off).
|
|
||||||
- **B6** — investigated,
|
|
||||||
the `front.c` stick checks are already edge-gated / toggle-guarded, not a
|
|
||||||
real over-scroll (unlike the fixed options.c watch case). No-op.
|
|
||||||
- **RC4** (palette off-by-one, was in `C` / TEXTURE-GLITCH-ANALYSIS) —
|
|
||||||
RETRACTED: the current RGBA5551 decode is correct.
|
|
||||||
- **A2 / D139** — likely closeable: M-27's Dam→Facility playthrough ran a
|
|
||||||
real `lvlUnloadStageTextData` → `cleanupObjects` with no teardown crash
|
|
||||||
after the fix. Confirm the commit landed, then close the §F row.
|
|
||||||
- **D2 (D63 scaffolding strip)** — the `port/fast3d/gfx_pc.cpp` trail is
|
|
||||||
stripped (M-30 `d0789358`). The remaining `#if defined(PORT)` `GE_D63`
|
|
||||||
probe blocks in ~10 `src/game/*.c` files are inert but **some sibling
|
|
||||||
`getenv`/`static` lines nearby are NOT `#if defined(PORT)`-guarded** (e.g.
|
|
||||||
`bg.c:2880` `d63bgprimarycount`) — a strip pass must verify N64-build
|
|
||||||
safety first (Non-Negotiable #1). Low value; defer.
|
|
||||||
|
|
||||||
## Suggested order for what's left
|
|
||||||
|
|
||||||
1. **A1** (D154 wall-shoot GBI parser) — needs a firefight-into-a-wall
|
|
||||||
playtest, then close/commit.
|
|
||||||
2. **B1** (D74 wrap-block) — the in-place fix boot-crashes; needs the
|
|
||||||
hoist-out-of-vertex-loop rework + per-level visual check. Cosmetic, gate
|
|
||||||
behind a config flag like RC2. Risky, low priority.
|
|
||||||
3. **B2** (Depot ceiling blue-speckle) — RC2 didn't touch it; needs `GE_DTEX`
|
|
||||||
/ RenderDoc on that specific surface. RC1 (wallet-Bond) is the D75
|
|
||||||
front-end-model track.
|
|
||||||
4. **C1–C4** cosmetics — after in-level crashes / campaign playtest.
|
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
# Building the PC port
|
||||||
|
|
||||||
|
Building has two stages:
|
||||||
|
|
||||||
|
1. **Extract assets from your ROM** — a one-time step that uses the
|
||||||
|
decompilation's own toolchain to pull levels, models, textures, fonts and
|
||||||
|
music out of your ROM into `assets/`.
|
||||||
|
2. **Build the port** — a CMake build that compiles the game sources plus the
|
||||||
|
`port/` layer into a native executable.
|
||||||
|
|
||||||
|
You need a GoldenEye 007 N64 ROM you legally own (`.z64`, big-endian). See the
|
||||||
|
[Requirements table in the README](../README.md#requirements) for accepted
|
||||||
|
versions and hashes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Dependencies
|
||||||
|
|
||||||
|
### Port build
|
||||||
|
|
||||||
|
| Need | Windows (MSYS2 MINGW64) | Debian/Ubuntu | macOS (Homebrew) |
|
||||||
|
|------|------------------------|---------------|------------------|
|
||||||
|
| toolchain | `mingw-w64-x86_64-toolchain` | `build-essential` | Xcode CLT / `gcc` |
|
||||||
|
| CMake | `mingw-w64-x86_64-cmake` | `cmake` | `cmake` |
|
||||||
|
| SDL2 | `mingw-w64-x86_64-SDL2` | `libsdl2-dev` | `sdl2` |
|
||||||
|
| zlib | `mingw-w64-x86_64-zlib` | `zlib1g-dev` | `zlib` |
|
||||||
|
| OpenGL | (in the toolchain) | `libgl1-mesa-dev` | (system) |
|
||||||
|
| Python 3 | `mingw-w64-x86_64-python` | `python3` | `python3` |
|
||||||
|
|
||||||
|
### Asset extraction (decompilation toolchain)
|
||||||
|
|
||||||
|
The extraction scripts need `binutils-mips-linux-gnu` (or an equivalent MIPS
|
||||||
|
binutils), `make`, `git`, `python3`, and `qemu-irix` for the IRIX IDO
|
||||||
|
compiler. On Windows this is easiest under WSL or a Linux VM. Full details and
|
||||||
|
alternatives (Docker, the recompiled IDO toolchain) are in
|
||||||
|
[`SetupGuide.md`](SetupGuide.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Extract assets
|
||||||
|
|
||||||
|
Put your **US** ROM at the repository root as `baserom.u.z64` (this name is
|
||||||
|
required by the extraction scripts; it is git-ignored and never committed),
|
||||||
|
then:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./scripts/extract_baserom.u.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
For PAL or JP, additionally place `baserom.e.z64` / `baserom.j.z64` at the root
|
||||||
|
and run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./scripts/extract_baserom.u.sh && ./scripts/extract_diff.e.sh # PAL
|
||||||
|
./scripts/extract_baserom.u.sh && ./scripts/extract_diff.j.sh # JP
|
||||||
|
```
|
||||||
|
|
||||||
|
(US extraction is a prerequisite for the others.)
|
||||||
|
|
||||||
|
This populates `assets/` with the generated `.bin` blobs the build needs. See
|
||||||
|
[`SetupGuide.md`](SetupGuide.md) for the in-depth build/asset pipeline.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./build-pc.sh ntsc-final # or: pal-final / jpn-final
|
||||||
|
```
|
||||||
|
|
||||||
|
which is equivalent to:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake -S . -B build-pc -DROMID=ntsc-final
|
||||||
|
cmake --build build-pc -j
|
||||||
|
```
|
||||||
|
|
||||||
|
For PAL/JP you must first generate that region's ROM-asset symbol file
|
||||||
|
(the US one is committed):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 scripts/gen_romassets.py e # PAL -> port/src/romassets_e.s
|
||||||
|
python3 scripts/gen_romassets.py j # JP -> port/src/romassets_j.s
|
||||||
|
```
|
||||||
|
|
||||||
|
The executable lands at `build-pc/ge007.x86_64` (`.exe` on Windows). PAL/JP
|
||||||
|
builds are named `ge007.pal-final.x86_64` / `ge007.jpn-final.x86_64`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p data
|
||||||
|
cp /path/to/your/rom.z64 data/ge007.ntsc-final.z64
|
||||||
|
./build-pc/ge007.x86_64 # run from the repo root
|
||||||
|
```
|
||||||
|
|
||||||
|
The port maps the ROM at the N64 cart address at runtime, so the ROM file
|
||||||
|
itself is still required to run — not just to build. `ge007.ini` is written
|
||||||
|
next to the working directory on first launch.
|
||||||
|
|
||||||
|
### Useful flags / env
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| `-level_NN` | boot straight into a solo level (e.g. `-level_09` = Bunker 1) |
|
||||||
|
| `GE_PCDUMP="first-last:step"` | dump rendered frames as PPM (debugging) |
|
||||||
|
|
||||||
|
More diagnostic switches are catalogued in
|
||||||
|
[`dev/GE-ENV-PROBES.md`](dev/GE-ENV-PROBES.md).
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
# How this port is developed
|
||||||
|
|
||||||
|
Most of this port's work is not writing code — it is **diagnosis**: figuring
|
||||||
|
out which N64 hardware or ABI behaviour a piece of unmodified game code is
|
||||||
|
silently relying on, and satisfying it in the `port/` layer without touching
|
||||||
|
the game's logic. A lot of that diagnosis is done with LLM coding agents, and
|
||||||
|
the workflow below is what makes that productive rather than chaotic.
|
||||||
|
|
||||||
|
For the project context — the two-model split, the timeline, and an honest
|
||||||
|
assessment of what did and did not work — see
|
||||||
|
[`dev/agentic-development.md`](dev/agentic-development.md).
|
||||||
|
|
||||||
|
It is adapted from Chris Lewis's write-up
|
||||||
|
[*"Decompiling a Nintendo 64 Game in 84 Days"*](https://blog.chrislewis.au/decompiling-a-nintendo-64-game-in-84-days/)
|
||||||
|
(Snowboard Kids, 2026), an AI-assisted decompilation that ran several times
|
||||||
|
faster than its predecessor. That project bit-matches compiler output;
|
||||||
|
this one matches runtime behaviour, so the mechanics differ — but the
|
||||||
|
project-structure lessons carry over directly.
|
||||||
|
|
||||||
|
## 1. Every investigation task gets a visible budget
|
||||||
|
|
||||||
|
An open-ended tool — for them the permuter, for us "rebuild and run the game
|
||||||
|
and stare at a frame dump" — wrecks throughput when an agent will grind it
|
||||||
|
indefinitely. Giving the agent a **deadline it can see** lets it trade
|
||||||
|
tool-time against thinking and decide when to give up.
|
||||||
|
|
||||||
|
So every investigation brief states:
|
||||||
|
|
||||||
|
- a **budget** — "≤ N build → run → inspect cycles" or "~M minutes"; and
|
||||||
|
- the **fallback on expiry**: stop, revert any temporary probes, and write up
|
||||||
|
what was found with an explicit confidence rating. A good write-up of a
|
||||||
|
half-solved bug is a deliverable, not a failure.
|
||||||
|
|
||||||
|
The budget scales with the project's maturity. Early bugs are cheap (one-line
|
||||||
|
truncations, off-by-ones) and get a tight cap; once those are gone the
|
||||||
|
survivors are structural (matrix handedness, serialization formats) and get
|
||||||
|
more room.
|
||||||
|
|
||||||
|
## 2. A shared, append-only learnings file
|
||||||
|
|
||||||
|
Lewis's agents recorded generalisable IDO quirks in a learnings file; later
|
||||||
|
agents read it and did better. Our equivalent is
|
||||||
|
[`porting-notes.md`](porting-notes.md) — the recurring N64->PC bug classes,
|
||||||
|
one screen, each entry an index into the full [`dev/findings.md`](dev/findings.md)
|
||||||
|
log. Every investigation brief links it, and every investigation ends by
|
||||||
|
appending any new generalisable quirk.
|
||||||
|
|
||||||
|
The classes it currently tracks:
|
||||||
|
|
||||||
|
- pointer-width struct growth (32->64) in ROM-serialized or pun-allocated
|
||||||
|
structs — the dominant class;
|
||||||
|
- 16-byte host `Gfx`/`Vtx` vs 8-byte N64 — reservation and copy sizing;
|
||||||
|
- big-endian rodata read on a little-endian host: `f32` word-pairs, header
|
||||||
|
offset tables, packed bitfields;
|
||||||
|
- N64 hardware idioms the software RSP does not emulate (fill-rect Z clear,
|
||||||
|
LOD/detail tiles, segment-address folds);
|
||||||
|
- whole serialized formats are converted by an **offline sidecar** rather than
|
||||||
|
patched at runtime.
|
||||||
|
|
||||||
|
## 3. Parallelise by file, consolidate often
|
||||||
|
|
||||||
|
Lewis ran multiple git worktrees and found the real cost was
|
||||||
|
*synchronisation*. For parallel investigation agents the same rule applies:
|
||||||
|
|
||||||
|
- **Partition tracks by the files they will touch**, so patches never collide.
|
||||||
|
A task that can't be cleanly file-partitioned shouldn't be parallelised.
|
||||||
|
- Each agent reverts its own temporary probes before reporting, leaving only
|
||||||
|
committed, env-gated, capped diagnostics — and never touches another track's
|
||||||
|
uncommitted edits.
|
||||||
|
- One integrator merges each track with its write-up, then the next round
|
||||||
|
starts from a clean tree. Investigation branches are not allowed to drift.
|
||||||
|
|
||||||
|
## 4. Spend the cheap automated pass before spending agent time
|
||||||
|
|
||||||
|
Before dispatching an investigation agent:
|
||||||
|
|
||||||
|
- confirm the build is green and the link is clean;
|
||||||
|
- run the existing env-gated probes for the area and attach their output;
|
||||||
|
- capture a baseline frame dump;
|
||||||
|
- grep the finding log for a prior instance of the same bug class;
|
||||||
|
- check the [Perfect Dark port](https://github.com/fgsfdsfgs/perfect_dark) for
|
||||||
|
the analogous code — same engine family.
|
||||||
|
|
||||||
|
Then hand the agent the *findings*, not just the problem.
|
||||||
|
|
||||||
|
## 5. What does not transfer from the decomp workflow
|
||||||
|
|
||||||
|
- **The match-percentage loop.** This port doesn't bit-match. "Done" is: no
|
||||||
|
fault, and visually correct against N64 reference footage.
|
||||||
|
- **"Understanding the function is the easy part."** True when decompiling;
|
||||||
|
inverted here — the hard part is identifying the implicit hardware/ABI
|
||||||
|
contract the already-readable C depends on.
|
||||||
|
|
||||||
|
## Investigation-brief template
|
||||||
|
|
||||||
|
```
|
||||||
|
TASK: <one bug, one subsystem>.
|
||||||
|
READ FIRST: docs/porting-notes.md; docs/dev/findings.md <specific Dxx entries>.
|
||||||
|
FILES YOU MAY TOUCH: <disjoint from any other in-flight work>.
|
||||||
|
KNOWN-GOOD / RULED OUT: <list — do not re-investigate>.
|
||||||
|
PRE-FLIGHT ATTACHED: <probe output, baseline capture, PD-port pointer>.
|
||||||
|
BUDGET: <N build->run cycles / ~M min>. On expiry: revert probes, write up with confidence.
|
||||||
|
CONSTRAINTS: no game-logic changes; ABI/layout/format only; #ifdef PORT; documented in the finding log.
|
||||||
|
REPORT: (a) root cause + file:line evidence (b) fix diff, or why not
|
||||||
|
(c) probes left in tree (d) confidence.
|
||||||
|
Append any generalisable quirk to docs/porting-notes.md.
|
||||||
|
```
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# Audio Plan — Phase 3 (libaudio → SDL)
|
# Audio Plan — Phase 3 (libaudio → SDL)
|
||||||
|
|
||||||
Status: **draft for evaluation** (written read-only; no code changes made).
|
Status: **draft for evaluation** (written read-only; no code changes made).
|
||||||
Supersedes the audio paragraphs of §6 in `docs/PCPortResearch.md` as the plan of record
|
Supersedes the audio paragraphs of §6 in `docs/internals.md` as the plan of record
|
||||||
for closing **D77** (no audible music/sound on PC). Companion docs: `docs/HANDOFF.md`,
|
for closing **D77** (no audible music/sound on PC). Companion docs: `docs/HANDOFF.md`,
|
||||||
`docs/PORT-LEARNINGS.md`.
|
`docs/porting-notes.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ problem.
|
|||||||
| `aEnvMixer` | env.c:410/414 | yes — `aEnvMixerImpl` |
|
| `aEnvMixer` | env.c:410/414 | yes — `aEnvMixerImpl` |
|
||||||
| `aADPCMdec` | load.c:483 | yes — `aADPCMdecImpl` (SSE4.1/NEON/scalar) |
|
| `aADPCMdec` | load.c:483 | yes — `aADPCMdecImpl` (SSE4.1/NEON/scalar) |
|
||||||
|
|
||||||
PD reference = `PD_PORT_CHECKOUT` +
|
PD reference = the Perfect Dark port `port/src/mixer.c` +
|
||||||
`port/include/mixer.h`. Reusable: the DSP *math* (ADPCM 8-coefficient loop filter,
|
`port/include/mixer.h`. Reusable: the DSP *math* (ADPCM 8-coefficient loop filter,
|
||||||
resample table + interpolation, envelope ramping, clamps). **Not** reusable as-is:
|
resample table + interpolation, envelope ramping, clamps). **Not** reusable as-is:
|
||||||
signatures and argument conventions (naudio ABI ≠ IDO ABI, §5.3).
|
signatures and argument conventions (naudio ABI ≠ IDO ABI, §5.3).
|
||||||
@@ -305,16 +305,16 @@ Record as a new Dxx finding when implemented (next free label after D127).
|
|||||||
aSaveBuffer` (+`aSegment` no-op) in `mixer.c`, reusing PD's DSP math where the ABI
|
aSaveBuffer` (+`aSegment` no-op) in `mixer.c`, reusing PD's DSP math where the ABI
|
||||||
maps. **Milestone: SFX audible (gunshots, doors); dry music plays.** Expect at least
|
maps. **Milestone: SFX audible (gunshots, doors); dry music plays.** Expect at least
|
||||||
one Dxx-class finding here (state-layout or endianness surprise is the usual bug
|
one Dxx-class finding here (state-layout or endianness surprise is the usual bug
|
||||||
class — see `docs/PORT-LEARNINGS.md`).
|
class — see `docs/porting-notes.md`).
|
||||||
- **Phase 3 — FX.** `aPoleFilter` + validate the full `reverb.c` chorus path against
|
- **Phase 3 — FX.** `aPoleFilter` + validate the full `reverb.c` chorus path against
|
||||||
the disassembly. **Milestone: music matches N64 character (chorus/reverb present, no
|
the disassembly. **Milestone: music matches N64 character (chorus/reverb present, no
|
||||||
pumping); close D77.**
|
pumping); close D77.**
|
||||||
- **Phase 4 — Polish & bookkeeping.** Resolve Q1; remove/adjust temporary probes and
|
- **Phase 4 — Polish & bookkeeping.** Resolve Q1; remove/adjust temporary probes and
|
||||||
`GE_DAUDIO` defaults; latency/buffer tuning (`Audio.BufferSize`, `QueueLimit`);
|
`GE_DAUDIO` defaults; latency/buffer tuning (`Audio.BufferSize`, `QueueLimit`);
|
||||||
host thread-priority tuning per A3 *only if* underruns were observed in Phases 2–3;
|
host thread-priority tuning per A3 *only if* underruns were observed in Phases 2–3;
|
||||||
record findings in `docs/PCPortResearch.md` §F/§H (next Dxx after D127) + update the
|
record findings in `docs/internals.md` §F/§H (next Dxx after D127) + update the
|
||||||
§F index, `AGENTS.md` Phase 3 status, `docs/HANDOFF.md`; append recurring bug classes
|
§F index, `AGENTS.md` Phase 3 status, `docs/HANDOFF.md`; append recurring bug classes
|
||||||
to `docs/PORT-LEARNINGS.md`.
|
to `docs/porting-notes.md`.
|
||||||
|
|
||||||
## 11. Verification ritual (audio-specific, on top of the standard one in AGENTS.md)
|
## 11. Verification ritual (audio-specific, on top of the standard one in AGENTS.md)
|
||||||
|
|
||||||
@@ -116,9 +116,9 @@ RW pool dedicated storage, and `gunRenderFirstPersonGunModels` /
|
|||||||
|
|
||||||
## §F pointer
|
## §F pointer
|
||||||
|
|
||||||
Add to `docs/PCPortResearch.md` §F after D112: *“**D115 (audit)** —
|
Add to `docs/internals.md` §F after D112: *“**D115 (audit)** —
|
||||||
`struct player`/`struct hand` raw-offset pass: see
|
`struct player`/`struct hand` raw-offset pass: see
|
||||||
`docs/AUDIT-M6-player-offsets.md`. Live HIGH bugs = `gunfire.c` THROWMTX/POS/PREV
|
`docs/dev/AUDIT-M6-player-offsets.md`. Live HIGH bugs = `gunfire.c` THROWMTX/POS/PREV
|
||||||
macros (`+handoffset+0xAD8/0xB08/0xB48`), not PORT-adjusted; fix = field
|
macros (`+handoffset+0xAD8/0xB08/0xB48`), not PORT-adjusted; fix = field
|
||||||
accessors. MED = D102 weapon `render_pos` transient-arena aliasing (likely
|
accessors. MED = D102 weapon `render_pos` transient-arena aliasing (likely
|
||||||
the ‘weapon model doesn’t draw’ cause) and the D56 watch-Model pool
|
the ‘weapon model doesn’t draw’ cause) and the D56 watch-Model pool
|
||||||
@@ -10,7 +10,7 @@ once the game is playable start-to-finish without crashes.
|
|||||||
|----|---------|----------------|
|
|----|---------|----------------|
|
||||||
| **D114/D116** | HUD text + ammo digits render each glyph individually X-mirrored (screen positions correct, texture content flipped). Likely also explains "inverted" guard skins / mislocated door props. | Root cause NOT found after 4 sessions (M-6/7/8/11). Every stage `textRenderGlyph`→GBI→fast3d→`buf_vbo`→GL runtime-verified non-mirrored (`GE_D116` `[D116/vbo]` probe). Contradiction unresolved. **Next attempt requires a RenderDoc/apitrace capture of one glyph texrect, or the asymmetric-1-texel-texture experiment** — do NOT re-run the static trace, and do NOT apply a global texrect S-swap (mirrors the whole screen). |
|
| **D114/D116** | HUD text + ammo digits render each glyph individually X-mirrored (screen positions correct, texture content flipped). Likely also explains "inverted" guard skins / mislocated door props. | Root cause NOT found after 4 sessions (M-6/7/8/11). Every stage `textRenderGlyph`→GBI→fast3d→`buf_vbo`→GL runtime-verified non-mirrored (`GE_D116` `[D116/vbo]` probe). Contradiction unresolved. **Next attempt requires a RenderDoc/apitrace capture of one glyph texrect, or the asymmetric-1-texel-texture experiment** — do NOT re-run the static trace, and do NOT apply a global texrect S-swap (mirrors the whole screen). |
|
||||||
| **D74 wrap-block** | `gfx_pc.cpp:1546/1551` PORT block is dead code: guard `cms & G_TX_WRAP` with `G_TX_WRAP==0` is always false; and it indexes per-texunit `[2]` arrays with the vertex index `i` (0..2) → OOB if reached. | Latent. Harmless for CLAMP glyphs; could affect wrapped textures on triangles (room panels, model skins). Fix: `cms == G_TX_WRAP`, and compute the tile window once per tile not per vertex. Verify against `tools_pc/golden/` after. |
|
| **D74 wrap-block** | `gfx_pc.cpp:1546/1551` PORT block is dead code: guard `cms & G_TX_WRAP` with `G_TX_WRAP==0` is always false; and it indexes per-texunit `[2]` arrays with the vertex index `i` (0..2) → OOB if reached. | Latent. Harmless for CLAMP glyphs; could affect wrapped textures on triangles (room panels, model skins). Fix: `cms == G_TX_WRAP`, and compute the tile window once per tile not per vertex. Verify against `tools_pc/golden/` after. |
|
||||||
| **D75** | Nintendo logo mispositioned; gun-barrel Bond model absent; cast-roll character models absent. (Rareware logo OK.) | Front-end 3D model path. Partially overlaps D114 (shared mirror on asymmetric content). Triage plan in `PCPortResearch.md` §F D75. |
|
| **D75** | Nintendo logo mispositioned; gun-barrel Bond model absent; cast-roll character models absent. (Rareware logo OK.) | Front-end 3D model path. Partially overlaps D114 (shared mirror on asymmetric content). Triage plan in `findings.md` §F D75. |
|
||||||
| **D76** | 2D disclaimer screen only partially drawn. | §F D76. |
|
| **D76** | 2D disclaimer screen only partially drawn. | §F D76. |
|
||||||
| **D77** | No audible music on PC. | Phase 3 (audio). §F D77. |
|
| **D77** | No audible music on PC. | Phase 3 (audio). §F D77. |
|
||||||
| **D116 (HUD)** | (subset of D114) proportional-font strings + ammo counter mirrored. | Same root cause as D114. |
|
| **D116 (HUD)** | (subset of D114) proportional-font strings + ammo counter mirrored. | Same root cause as D114. |
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# HANDOFF ARCHIVE — session-by-session narrative
|
# HANDOFF ARCHIVE — session-by-session narrative
|
||||||
|
|
||||||
> **Reference only.** Current state, next task, and environment live in
|
> **Reference only.** Current state, next task, and environment live in
|
||||||
> `docs/HANDOFF.md`; per-finding detail in `docs/PCPortResearch.md` §F/§H
|
> `docs/HANDOFF.md`; per-finding detail in `docs/internals.md` §F/§H
|
||||||
> (indexed at the top of §F). This file is the frozen accumulation of
|
> (indexed at the top of §F). This file is the frozen accumulation of
|
||||||
> prior sessions' handoff briefs (through session M-11), kept so the
|
> prior sessions' handoff briefs (through session M-11), kept so the
|
||||||
> reasoning behind a fix is recoverable. Everything below is as-written
|
> reasoning behind a fix is recoverable. Everything below is as-written
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
|
|
||||||
1. `CLAUDE.md` (auto-loaded) + `AGENTS.md` non-negotiables.
|
1. `CLAUDE.md` (auto-loaded) + `AGENTS.md` non-negotiables.
|
||||||
2. This file, top-to-bottom.
|
2. This file, top-to-bottom.
|
||||||
3. `docs/PORT-LEARNINGS.md` — recurring bug classes.
|
3. `docs/porting-notes.md` — recurring bug classes.
|
||||||
4. Dispatching a subagent? `docs/AGENT-WORKFLOW.md` FIRST. Every brief needs
|
4. Dispatching a subagent? `docs/dev-process.md` FIRST. Every brief needs
|
||||||
FILES / BUDGET / ON-EXPIRY / CONSTRAINTS / REPORT (see CLAUDE.md "Dispatch preflight").
|
FILES / BUDGET / ON-EXPIRY / CONSTRAINTS / REPORT (see CLAUDE.md "Dispatch preflight").
|
||||||
|
|
||||||
_Paste-ready brief. Authoritative context: `AGENTS.md`,
|
_Paste-ready brief. Authoritative context: `AGENTS.md`,
|
||||||
`docs/PCPortResearch.md` §F (D69, D78-D102), `docs/BRIEF-D69-stage-load.md`._
|
`docs/internals.md` §F (D69, D78-D102), `docs/BRIEF-D69-stage-load.md`._
|
||||||
|
|
||||||
**NEW DIRECTION (2026-08-29): `docs/PLAN-linear-level-sweep.md`** — pivot
|
**NEW DIRECTION (2026-08-29): `docs/PLAN-linear-level-sweep.md`** — pivot
|
||||||
to breadth-first: fix the boot→intro→menu→level-select path to
|
to breadth-first: fix the boot→intro→menu→level-select path to
|
||||||
@@ -117,7 +117,7 @@ transition, then the next stage.
|
|||||||
they rank BELOW level-progression and crash work.
|
they rank BELOW level-progression and crash work.
|
||||||
4. **Rest of the `struct player` offset pass** — D115 fixed the HIGH
|
4. **Rest of the `struct player` offset pass** — D115 fixed the HIGH
|
||||||
`gunfire.c` THROW* bugs; #6 (watch-preview Model pool) + the broader
|
`gunfire.c` THROW* bugs; #6 (watch-preview Model pool) + the broader
|
||||||
audit remain (`docs/AUDIT-M6-player-offsets.md`).
|
audit remain (`docs/dev/AUDIT-M6-player-offsets.md`).
|
||||||
5. Add an f32 value spot-check to `d43_emit.py`'s verify pass (it only
|
5. Add an f32 value spot-check to `d43_emit.py`'s verify pass (it only
|
||||||
checks pointers/opcodes today — the D112 bug passed "ALL CHECKS PASSED").
|
checks pointers/opcodes today — the D112 bug passed "ALL CHECKS PASSED").
|
||||||
6. **GE_DETERM fixed-tick mode (§F D117)** — deferred as not-narrow; would
|
6. **GE_DETERM fixed-tick mode (§F D117)** — deferred as not-narrow; would
|
||||||
@@ -244,8 +244,8 @@ each nonzero OSContPad poll. Rebinding + gamepad hotplug are TODO (§F
|
|||||||
D118).
|
D118).
|
||||||
|
|
||||||
**Session M-6 — see §F D113/D114/D115, `docs/PLAN-M6-playable.md`,
|
**Session M-6 — see §F D113/D114/D115, `docs/PLAN-M6-playable.md`,
|
||||||
`docs/AUDIT-M6-player-offsets.md`, `docs/AGENT-WORKFLOW.md`,
|
`docs/dev/AUDIT-M6-player-offsets.md`, `docs/dev-process.md`,
|
||||||
`docs/PORT-LEARNINGS.md`.** D113: portal BFS is correct, not the void.
|
`docs/porting-notes.md`.** D113: portal BFS is correct, not the void.
|
||||||
D114: matrix chain + converter verified clean, residual = shared fast3d
|
D114: matrix chain + converter verified clean, residual = shared fast3d
|
||||||
mirror (open). D115: player raw-offset audit + `gunfire.c` THROW* fix
|
mirror (open). D115: player raw-offset audit + `gunfire.c` THROW* fix
|
||||||
(uncommitted). Build green.
|
(uncommitted). Build green.
|
||||||
@@ -345,7 +345,7 @@ Pattern: textures/text draw; **animated/skeletal character models never
|
|||||||
appear**; static 3D (logos) appears but with a bad transform. Leading
|
appear**; static 3D (logos) appears but with a bad transform. Leading
|
||||||
hypothesis is D75(b) — the animated-model path (`animInit` + raw offsets
|
hypothesis is D75(b) — the animated-model path (`animInit` + raw offsets
|
||||||
into `struct player`, cf. D56) is broken independently of the D73 matrix
|
into `struct player`, cf. D56) is broken independently of the D73 matrix
|
||||||
sin/cos fix. Full triage plan in `PCPortResearch.md` §F D75.
|
sin/cos fix. Full triage plan in `findings.md` §F D75.
|
||||||
|
|
||||||
The rest of this document (below) is the **last known-good, committed**
|
The rest of this document (below) is the **last known-good, committed**
|
||||||
status as of commit `8c9c6a2c` (D86+D87 resolved) — still accurate except
|
status as of commit `8c9c6a2c` (D86+D87 resolved) — still accurate except
|
||||||
@@ -501,7 +501,7 @@ locations attract mode happens to pick).
|
|||||||
(`gdb -batch -x cmds.txt -p <winpid>`, `<winpid>` = 4th column of
|
(`gdb -batch -x cmds.txt -p <winpid>`, `<winpid>` = 4th column of
|
||||||
`ps -p <bashpid>`) works well and is fast for "watch a global for a
|
`ps -p <bashpid>`) works well and is fast for "watch a global for a
|
||||||
legitimate vs. corrupted write" questions on an already-running,
|
legitimate vs. corrupted write" questions on an already-running,
|
||||||
not-yet-crashed process — see `docs/PCPortResearch.md` §F environment
|
not-yet-crashed process — see `docs/internals.md` §F environment
|
||||||
reminders for the exact recipe used to root-cause D87.
|
reminders for the exact recipe used to root-cause D87.
|
||||||
|
|
||||||
## Non-negotiables (unchanged, see AGENTS.md)
|
## Non-negotiables (unchanged, see AGENTS.md)
|
||||||
@@ -509,7 +509,7 @@ locations attract mode happens to pick).
|
|||||||
1. N64 build files untouched.
|
1. N64 build files untouched.
|
||||||
2. Game logic unmodified except narrow, documented `#ifdef PORT`
|
2. Game logic unmodified except narrow, documented `#ifdef PORT`
|
||||||
ABI/layout exceptions (D86/D87 this session, both logged in
|
ABI/layout exceptions (D86/D87 this session, both logged in
|
||||||
`PCPortResearch.md` §F). D88 is explicitly **not** patched with a
|
`findings.md` §F). D88 is explicitly **not** patched with a
|
||||||
quick inline hack — it needs the same disciplined
|
quick inline hack — it needs the same disciplined
|
||||||
spec-then-convert/fixup treatment as D69, logged as an open finding
|
spec-then-convert/fixup treatment as D69, logged as an open finding
|
||||||
instead per AGENTS.md's "stop and write it up" guidance for anything
|
instead per AGENTS.md's "stop and write it up" guidance for anything
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Texture Glitch Analysis — PC Port
|
# Texture Glitch Analysis — PC Port
|
||||||
|
|
||||||
**Original diagnosis:** 2026-08-29 (read-only) · **Status table refreshed:** 2026-08-31 (M-31)
|
**Original diagnosis:** 2026-08-29 (read-only) · **Status table refreshed:** 2026-08-31 (M-31)
|
||||||
Companion artifacts: `CAPTURES_DIR` (outside repo).
|
Companion artifacts: `<local-path>` (outside repo).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ Useful ground truth for whoever implements the fixes:
|
|||||||
the first N texLoads (~10 lines in fast3d) → one Depot run gives true ground truth and a
|
the first N texLoads (~10 lines in fast3d) → one Depot run gives true ground truth and a
|
||||||
before/after reference (also settles H-C if it ever resurfaces).
|
before/after reference (also settles H-C if it ever resurfaces).
|
||||||
|
|
||||||
Each item gets its own Dxx entry in `docs/PCPortResearch.md` §F/§H at commit time
|
Each item gets its own Dxx entry in `docs/internals.md` §F/§H at commit time
|
||||||
(proposed: next free labels after D126 — check the §F index first; a stray "D160" appears
|
(proposed: next free labels after D126 — check the §F index first; a stray "D160" appears
|
||||||
once in the doc and may need reconciling).
|
once in the doc and may need reconciling).
|
||||||
|
|
||||||
@@ -275,7 +275,7 @@ and eyeball the corrugated roof / repeating wall panels vs default; flip the
|
|||||||
default in `port/src/video.c` (`cfgWrapFix = 1`) if clean. The overflow-band clamp
|
default in `port/src/video.c` (`cfgWrapFix = 1`) if clean. The overflow-band clamp
|
||||||
is an approximation of console TMEM-smear behaviour, not an exact emulation.
|
is an approximation of console TMEM-smear behaviour, not an exact emulation.
|
||||||
|
|
||||||
## 7. Artifacts (in `CAPTURES_DIR`, outside repo)
|
## 7. Artifacts (in `<local-path>`, outside repo)
|
||||||
|
|
||||||
| File | What it is |
|
| File | What it is |
|
||||||
|------|-----------|
|
|------|-----------|
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
# Agentic development: goal, method, timeline, assessment
|
||||||
|
|
||||||
|
## Why this project exists
|
||||||
|
|
||||||
|
The playable port is real, but it is not the primary deliverable. The goal was
|
||||||
|
to **test how well coding agents hold up on a large, unfamiliar, low-level
|
||||||
|
codebase** — one with none of the properties that make web-app work easy for
|
||||||
|
an LLM:
|
||||||
|
|
||||||
|
- ~230 translation units of decompiled Nintendo 64 game C, compiled
|
||||||
|
**unmodified**;
|
||||||
|
- big-endian, 32-bit, MIPS ABI assumptions throughout, run on a little-endian
|
||||||
|
64-bit host;
|
||||||
|
- bugs that surface as a fault or a garbled frame hundreds of milliseconds
|
||||||
|
after the actual cause, often in a different subsystem;
|
||||||
|
- a graphics coprocessor (the RSP) that has to be emulated in software before
|
||||||
|
anything draws at all.
|
||||||
|
|
||||||
|
Concretely it set out to validate a **two-agent arrangement**: a
|
||||||
|
locally-hosted open-weight model doing the bulk of the work on a single
|
||||||
|
consumer GPU, a hosted frontier model brought in for a collaborative phase,
|
||||||
|
and — the part that turned out most interesting — the two **handing work back
|
||||||
|
and forth** through shared written artifacts, directed by one human.
|
||||||
|
|
||||||
|
## The setup
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Local agent | `unsloth/Qwen3.8-27B-GGUF:UD-Q4_K_XL` on a single **NVIDIA RTX 5090**, driven mainly through the **[pi](https://pi.dev/)** coding agent. Unsloth Desktop (a newer local agentic-coding harness) was also trialed but not used significantly. |
|
||||||
|
| Hosted agent | **Claude**, via **Claude Code**, on a Claude Pro subscription — mostly **Sonnet 5**, with **Opus 5** used as an escalation tier for the hardest problems and whenever there was subscription budget to spend on it |
|
||||||
|
| Human | one person: direction, work partitioning, integration, and every build / playtest / frame-capture the agents could not run |
|
||||||
|
| Base | fork of the [GoldenEye 007 decompilation](https://github.com/n64decomp/007) (years of prior work by Larry Ficken / "KholdFuzion" and contributors) |
|
||||||
|
| Reused | the [Perfect Dark PC port](https://github.com/fgsfdsfgs/perfect_dark)'s `fast3d` software RSP — same Rare engine family |
|
||||||
|
|
||||||
|
The port is the GoldenEye-specific porting work **on top of** those two
|
||||||
|
existing bodies of work; it is not a from-scratch reimplementation of either.
|
||||||
|
|
||||||
|
In practice the models formed a **three-tier escalation**: the local model
|
||||||
|
handled well-scoped work, Sonnet 5 took what it stalled on, and Opus 5 was
|
||||||
|
reserved for the bugs that needed the most reasoning held at once — the same
|
||||||
|
"escalate when stuck" move applied at every level.
|
||||||
|
|
||||||
|
## Timeline
|
||||||
|
|
||||||
|
All dates from this repository's own commit history (August 2026).
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
gantt
|
||||||
|
dateFormat YYYY-MM-DD
|
||||||
|
axisFormat %b %d
|
||||||
|
title GoldenEye 007 PC port — from fork to playable front end
|
||||||
|
section Local model - Qwen 3.8 via pi
|
||||||
|
Fork + PC-port scaffolding :m1, 2026-08-16, 1d
|
||||||
|
Full compile + link (~230 TUs) :m2, 2026-08-20, 1d
|
||||||
|
Boot to window (ROM map + SDL2) :m3, 2026-08-20, 1d
|
||||||
|
OS shims, threads, fast3d integration :2026-08-21, 2d
|
||||||
|
First rendered frames :m4, 2026-08-22, 1d
|
||||||
|
Offline asset-conversion pipeline :2026-08-22, 2d
|
||||||
|
Entire intro renders (logos->cast) :m5, 2026-08-24, 1d
|
||||||
|
section Both agents - handoff workflow
|
||||||
|
Claude joins :milestone, 2026-08-27, 0d
|
||||||
|
Stage load; Bunker 1 renders + firefight :2026-08-27, 2d
|
||||||
|
21 solo levels: load + render + no-crash :m6, 2026-08-29, 1d
|
||||||
|
SDL input layer (kbd/mouse/gamepad) :2026-08-29, 1d
|
||||||
|
Front-end flow menu->briefing->start :m7, 2026-08-30, 1d
|
||||||
|
File-backed EEPROM saves :2026-08-31, 1d
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Day 0** (16 Aug): repository forked, PC-port scaffolding added.
|
||||||
|
- **Day 4** (20 Aug): the entire ~230-TU game + libultra set compiles and
|
||||||
|
links as a host binary.
|
||||||
|
- **Day 6** (22 Aug): first real frames render.
|
||||||
|
- **Day 8** (24 Aug): the whole intro sequence renders — logos, gun-barrel,
|
||||||
|
cast roll.
|
||||||
|
- **Day 11** (27 Aug): second agent joins.
|
||||||
|
- **Day 13** (29 Aug): **all 21 solo missions load, render, and survive an
|
||||||
|
unattended play window without crashing.**
|
||||||
|
- **Day 14** (30–31 Aug): front end playable end to end (menu -> mission
|
||||||
|
select -> difficulty -> briefing -> start); file-backed saves.
|
||||||
|
|
||||||
|
So roughly **two weeks**, one person part-time, to take a decompilation from
|
||||||
|
"builds an N64 ROM" to "boots on desktop, renders every solo level, playable
|
||||||
|
through the front end into the early game" — with audio and a set of cosmetic
|
||||||
|
issues still outstanding.
|
||||||
|
|
||||||
|
We are not aware of a prior Nintendo 64 native PC port developed primarily
|
||||||
|
through coding agents, or of one reaching this state on this timeline. That is
|
||||||
|
a statement about how little precedent there is, not a claim of a record.
|
||||||
|
|
||||||
|
## By the numbers
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Calendar time | 16 days (16 Aug – 1 Sep 2026), one person part-time |
|
||||||
|
| Commits on the port | 217 |
|
||||||
|
| Root-caused bugs logged | 160 (`D1`–`D167` in [`findings.md`](findings.md)) |
|
||||||
|
| Handoff sessions | ~30 (`M-2` … `M-32`) |
|
||||||
|
| Game-source files given `#ifdef PORT` ABI edits | 63 files, 241 blocks |
|
||||||
|
| New port-layer / tooling files | 128 |
|
||||||
|
| Port layer | ~17,000 lines C/C++ (`port/`) |
|
||||||
|
| PC asset-conversion tooling | ~6,300 lines Python (`tools_pc/`) |
|
||||||
|
| Outcome | intro + all 21 solo missions render, front end playable; audio not yet done |
|
||||||
|
|
||||||
|
### Commit velocity
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
xychart-beta
|
||||||
|
title "Commits per day"
|
||||||
|
x-axis ["8/16", "8/17", "8/20", "8/21", "8/22", "8/23", "8/24", "8/27", "8/28", "8/29", "8/30", "8/31", "9/1"]
|
||||||
|
y-axis "commits" 0 --> 60
|
||||||
|
bar [1, 2, 2, 6, 9, 2, 4, 7, 57, 39, 39, 46, 3]
|
||||||
|
```
|
||||||
|
|
||||||
|
The 8/25–8/26 gap is between phases; the jump on 8/28 is the second agent
|
||||||
|
joining, plus the parallel multi-agent "bursts" used near the end.
|
||||||
|
|
||||||
|
### Who did what
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
pie showData title "Commits by phase"
|
||||||
|
"Phase A - local model, solo (16-24 Aug)" : 26
|
||||||
|
"Phase B - collaborative (27 Aug-1 Sep)" : 191
|
||||||
|
```
|
||||||
|
|
||||||
|
| Milestone / workstream | Primary agent | Weight |
|
||||||
|
|---|---|---|
|
||||||
|
| PC build system, region macros, full compile + link of ~230 units | local model | large |
|
||||||
|
| Boot chain: ROM map, OS-shim layer (`libultra.c`), host threads, dual-mapped DRAM | local model | large |
|
||||||
|
| Software-RSP integration + replacement scheduler (`gesched.c`) | local model | medium |
|
||||||
|
| Offline asset-conversion architecture + first converters (`tools_pc/`) | local model | large |
|
||||||
|
| Intro rendering (logos → gun-barrel → cast) | local model | medium |
|
||||||
|
| Stage load unblocked (`D69`–`D87`) | Claude | medium |
|
||||||
|
| 21-level crash sweep — ~12 crash classes root-caused (`D88`–`D167`) | Claude | large |
|
||||||
|
| SDL input layer (keyboard / mouse / gamepad, mouse-look) | Claude | medium |
|
||||||
|
| Front-end flow (menu → briefing → start), EEPROM saves | Claude | medium |
|
||||||
|
| Parallel struct-layout / converter static audits | local model | medium |
|
||||||
|
| Continuing tasks after Claude hit a usage limit | local model | small |
|
||||||
|
| The hardest structural bugs (matrix handedness, format specs) | Claude | — |
|
||||||
|
| Every build, playtest, frame capture, integration, and direction | human | — |
|
||||||
|
|
||||||
|
**Estimated effort split.** By raw commit count it is ~28% local model /
|
||||||
|
~72% Claude (Phase A entirely local, ~15–20% of Phase B local). Weighting by
|
||||||
|
milestone difficulty rather than commit count — the foundation the local model
|
||||||
|
built is a heavier third of the project than its commit count suggests — the
|
||||||
|
developer's estimate is roughly:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
pie showData title "Estimated agent effort (milestone-weighted)"
|
||||||
|
"Claude" : 60
|
||||||
|
"Local model (Qwen 3.8 / pi)" : 40
|
||||||
|
```
|
||||||
|
|
||||||
|
The local model's ~40% is front-loaded and foundational (the build, the boot
|
||||||
|
chain, the RSP wiring, the converter architecture) plus continuous parallel
|
||||||
|
support; Claude's ~60% is the higher-volume Phase-B debugging, the two big
|
||||||
|
discrete features, and the hardest structural bugs. A 27B open-weight model on
|
||||||
|
one consumer GPU carrying the entire foundation of a project like this is the
|
||||||
|
result worth taking away.
|
||||||
|
|
||||||
|
## The handoff workflow
|
||||||
|
|
||||||
|
This is the part worth paying attention to.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
H[Human: direction, integration,<br/>build + playtest verification]
|
||||||
|
C[Claude / Claude Code<br/>frontier, hosted]
|
||||||
|
Q["Qwen 3.8 via pi<br/>open-weight, local RTX 5090"]
|
||||||
|
D[(Shared artifacts:<br/>HANDOFF.md · findings.md · porting-notes.md)]
|
||||||
|
|
||||||
|
H -->|scopes task, budget, files| C
|
||||||
|
H -->|scopes task, budget, files| Q
|
||||||
|
C <-->|reads / appends| D
|
||||||
|
Q <-->|reads / appends| D
|
||||||
|
C -.->|usage limit reached ->| Q
|
||||||
|
Q -.->|hard structural bug ->| C
|
||||||
|
C -->|patch + writeup| H
|
||||||
|
Q -->|patch + writeup| H
|
||||||
|
```
|
||||||
|
|
||||||
|
Both agents worked against the **same three written artifacts**, which is what
|
||||||
|
let them substitute for each other:
|
||||||
|
|
||||||
|
1. **`HANDOFF.md`** — the current state, the immediate next task, and the
|
||||||
|
environment gotchas. Originally a session-to-session note for one agent, it
|
||||||
|
became the **interface between the two agents**: when Claude reached a
|
||||||
|
usage limit mid-problem, the local model picked the task up from the
|
||||||
|
HANDOFF state and continued; when the local model hit a bug that needed
|
||||||
|
deeper structural reasoning, it wrote up where it was and Claude took over.
|
||||||
|
2. **`findings.md`** — the chronological finding log. 160 numbered entries,
|
||||||
|
each a root cause with `file:line` evidence and the fix. New agents (either
|
||||||
|
model) are pointed at the relevant entries before they start.
|
||||||
|
3. **`porting-notes.md`** — the append-only "recurring bug classes" file. The
|
||||||
|
single highest-leverage artifact: it stopped both models from
|
||||||
|
re-deriving the same class of N64->PC bug over and over.
|
||||||
|
|
||||||
|
Around these, the working rules (full detail in
|
||||||
|
[`../dev-process.md`](../dev-process.md)):
|
||||||
|
|
||||||
|
- **file-partitioned tasks** — each agent's task scoped to a disjoint set of
|
||||||
|
files so patches never collided;
|
||||||
|
- **visible budgets** — every investigation task carried an explicit
|
||||||
|
"N build->run cycles" limit and a defined fallback (revert probes, write up
|
||||||
|
with a confidence rating);
|
||||||
|
- **the human owns verification** — building, running the game, capturing a
|
||||||
|
frame, and judging it against N64 reference footage was never delegated.
|
||||||
|
|
||||||
|
## Assessment
|
||||||
|
|
||||||
|
Honest notes, for anyone weighing whether this transfers.
|
||||||
|
|
||||||
|
**Worked well**
|
||||||
|
|
||||||
|
- The **local model carried the groundwork phase** — build system, boot
|
||||||
|
chain, OS shims, and the offline-converter architecture. None of it was
|
||||||
|
rewritten later. A 27B open model on one consumer GPU was genuinely productive
|
||||||
|
on this.
|
||||||
|
- **The written-artifact discipline made agent output compound.** An agent
|
||||||
|
starting cold late in the project was more effective than one early on,
|
||||||
|
because the accumulated notes were good. This is also what made the two
|
||||||
|
models interchangeable on a given task.
|
||||||
|
- **Handoff on limit** turned Claude's usage cap from a hard stop into a
|
||||||
|
slowdown — the local model kept the problem moving.
|
||||||
|
- Bounded behavioural bugs (a truncated pointer, a byte-swap off by one) are a
|
||||||
|
good fit for an agent given a tight loop and a reference to diff against.
|
||||||
|
|
||||||
|
**Worked poorly / needed the human**
|
||||||
|
|
||||||
|
- **Anything requiring the running game.** Build, playtest, capture a frame,
|
||||||
|
decide whether it looks right — that loop was the human's job throughout,
|
||||||
|
and it was the bottleneck.
|
||||||
|
- **Non-deterministic bugs.** Frame-timing stalls and concurrent-build
|
||||||
|
flakiness repeatedly fooled agents into "fixing" regressions that were not
|
||||||
|
real. A number of findings are corrections of earlier findings.
|
||||||
|
- **Long structural bugs** (matrix handedness, a whole serialized-format
|
||||||
|
spec) often ended in a "here is what I know, confidence medium" writeup
|
||||||
|
rather than a fix, even with the budget raised.
|
||||||
|
- **The capability gap is a gradient, not a wall.** The local model was strong
|
||||||
|
on well-scoped work and weaker when a bug needed several interacting facts
|
||||||
|
held at once — those went to Sonnet 5, and the few that stalled Sonnet went
|
||||||
|
to Opus 5. Each tier earned its place on the problems the tier below it
|
||||||
|
couldn't close.
|
||||||
|
|
||||||
|
**Still outstanding** (parked below crash/level work): audio is not
|
||||||
|
implemented; several front-end 3D transforms and some text rendering are
|
||||||
|
wrong. See [`GRAPHICS-BACKLOG.md`](GRAPHICS-BACKLOG.md).
|
||||||
@@ -1,432 +1,10 @@
|
|||||||
# GoldenEye 007 — PC Port: Preliminary Research
|
# PC port — finding log
|
||||||
|
|
||||||
This document captures the preliminary research done to plan a PC port of
|
The raw, chronological engineering record for the port: every runtime bug,
|
||||||
GoldenEye 007, based on the decompilation in this repository
|
its root cause, and the fix. Kept unedited for transparency. Recurring
|
||||||
(`n64decomp/007`). It is modelled on the approach used by the
|
patterns are distilled in [../porting-notes.md](../porting-notes.md).
|
||||||
[Perfect Dark PC port](https://github.com/perfect-dark-pc-port/perfect_dark),
|
|
||||||
which ports the same Rare "Indy" engine (a later revision) to modern
|
|
||||||
platforms.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Executive summary
|
|
||||||
|
|
||||||
* **Feasibility: HIGH.** GoldenEye and Perfect Dark share the same Rare
|
|
||||||
engine family. The PD port proves the whole approach works end-to-end
|
|
||||||
(singleplayer + split-screen multiplayer, mouse-look, widescreen, 60 FPS,
|
|
||||||
mods). GE is, if anything, a *simpler* graphics pipeline than PD in some
|
|
||||||
respects, though it has a few custom RSP extensions (see §5).
|
|
||||||
* **Recommended strategy: fork this decomp and add a `port/` layer**, exactly
|
|
||||||
mirroring the PD port's layout. The N64 build (Makefile + IDO) stays
|
|
||||||
untouched; the PC build is a parallel CMake build that compiles the same
|
|
||||||
game C sources against a set of PC shims.
|
|
||||||
* **The single hardest component is the RSP (Reality Signal Processor)
|
|
||||||
emulator.** The PD port ships a complete, working "fast3d" software RSP
|
|
||||||
(`port/fast3d/`, ~7k lines of C++) that interprets GBI display lists and
|
|
||||||
emits OpenGL calls. It **already handles `G_TRI4`**, which is the main
|
|
||||||
custom triangle command GE uses. GE's other custom command, `G_SETTEX`,
|
|
||||||
appears **unused** (its emitter `gsSPUseTexture` is never called), so the
|
|
||||||
remaining GE-specific work is mostly **verifying the custom
|
|
||||||
render/color-combiner modes** against the RSP ucode.
|
|
||||||
* **Audio is straightforward.** GE uses the standard libaudio (AL) engine on
|
|
||||||
the R4300, the same as PD. The PD port's SDL audio + mixer approach carries
|
|
||||||
over directly.
|
|
||||||
* **Saves** are **EEPROM-based** (controller EEPROM via `osEeprom*`), not
|
|
||||||
Memory Pak. The PFS/motor code in `joy.c` is only for accessory (Rumble Pak
|
|
||||||
/ Memory Pak) detection and can be stubbed. Phase 4 is therefore a
|
|
||||||
file-backed EEPROM plus no-op PFS/motor shims — simpler than a full Memory
|
|
||||||
Pak emulation.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. What the PD port actually does (the reference architecture)
|
|
||||||
|
|
||||||
The PD port is a **fork of the PD decomp** with one added top-level
|
|
||||||
directory, `port/`. The original game code (`src/game/*.c`, `src/lib/*.c`)
|
|
||||||
is compiled **unmodified** for the PC. All N64-hardware dependencies are
|
|
||||||
satisfied by the `port/` layer:
|
|
||||||
|
|
||||||
```
|
|
||||||
port/
|
|
||||||
├── fast3d/ # Software RSP: interprets GBI display lists -> OpenGL
|
|
||||||
│ ├── gfx_pc.cpp # the RSP interpreter (matrices, lights, tris, fog)
|
|
||||||
│ ├── gfx_opengl.cpp # OpenGL rendering backend (shaders, textures, zbuf)
|
|
||||||
│ ├── gfx_sdl2.cpp # SDL2 window-manager backend
|
|
||||||
│ ├── gfx_cc.cpp # color-combiner -> GLSL shader compiler
|
|
||||||
│ └── ...
|
|
||||||
├── include/ # port-facing headers
|
|
||||||
│ ├── audio.h config.h fs.h input.h mixer.h mod.h
|
|
||||||
│ ├── romdata.h system.h video.h utils.h platform.h
|
|
||||||
└── src/
|
|
||||||
├── main.c # PC entry point (replaces boot.s / _start.s)
|
|
||||||
├── libultra.c # shims for the libultra OS API (threads, time, VI, PI, SI, SP, AI)
|
|
||||||
├── video.c # SDL2 window + GL context + frame pacing
|
|
||||||
├── audio.c # SDL audio device, buffer queueing
|
|
||||||
├── mixer.c # audio mixing / RSP-audio-buffer processing
|
|
||||||
├── mod.c # MOD music playback
|
|
||||||
├── input.c # SDL2 keyboard/mouse/gamepad -> N64 controller structs
|
|
||||||
├── fs.c # filesystem abstraction over the ROM + data dir
|
|
||||||
├── romdata.c # loads the .z64 ROM from disk, sets up segments
|
|
||||||
├── config.c # INI config (pd.ini)
|
|
||||||
├── system.c # platform primitives (time, logging, paths)
|
|
||||||
├── crash.c # crash handler / stack traces
|
|
||||||
├── pdsched.c # replacement RCP scheduler that drives the software RSP
|
|
||||||
└── utils.c
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.1 The key architectural insight: emulate the RSP, bypass the RDP
|
|
||||||
|
|
||||||
On real hardware the render path is:
|
|
||||||
|
|
||||||
```
|
|
||||||
R4300 builds a Gfx display list
|
|
||||||
|
|
|
||||||
v
|
|
||||||
RSP (coproc 0) runs "fast3d" ucode, turns GBI cmds into RDP cmds
|
|
||||||
|
|
|
||||||
v
|
|
||||||
RDP (coproc 1) rasterizes into the framebuffer
|
|
||||||
```
|
|
||||||
|
|
||||||
The PD port **emulates the RSP in software** (`fast3d/gfx_pc.cpp`) and
|
|
||||||
**bypasses the RDP entirely**: the software RSP translates GBI commands
|
|
||||||
directly into OpenGL calls. The R4300 game code is run natively and is
|
|
||||||
unaware of the difference — it just builds display lists and "starts the
|
|
||||||
RSP" as usual.
|
|
||||||
|
|
||||||
The frame flow (from `port/src/pdsched.c` + `port/src/video.c`):
|
|
||||||
|
|
||||||
```
|
|
||||||
videoStartFrame() -> gfx_start_frame()
|
|
||||||
game builds display list (Gfx*)
|
|
||||||
videoSubmitCommands(Gfx* cmds) -> gfx_run(cmds) # <- the software RSP runs here
|
|
||||||
videoEndFrame() -> gfx_end_frame() # swap buffers, FPS accounting
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 How the RSP is invoked
|
|
||||||
|
|
||||||
The game's RCP scheduler (`src/lib/sched.c` in PD, `src/sched.c` in GE) calls
|
|
||||||
`osSpTaskLoad()` + `osSpTaskStartGo()` to hand a task (gfx or audio) to the
|
|
||||||
RSP. The port:
|
|
||||||
|
|
||||||
* **Excludes** the original `libultra/io/sptask.c` (and the other `io/*.c`
|
|
||||||
hardware drivers) from the PC build.
|
|
||||||
* Provides its own scheduler (`pdsched.c`) that, instead of writing to RSP
|
|
||||||
registers, calls `gfx_run()` on the display list for gfx tasks and feeds the
|
|
||||||
audio buffer for audio tasks.
|
|
||||||
* Shims the rest of the libultra OS API in `libultra.c` (threads become
|
|
||||||
no-ops / single-threaded, `osGetTime` maps to a host clock, VI/PI/SI/AI
|
|
||||||
functions map to the port's video/audio/fs/input layers).
|
|
||||||
|
|
||||||
### 2.3 Build system
|
|
||||||
|
|
||||||
The PC build is **CMake** (the N64 build stays Make + IDO). Dependencies:
|
|
||||||
**SDL2, zlib, OpenGL** (plus `stdc++`, `m`, `dl`). It compiles:
|
|
||||||
|
|
||||||
* `src/game/*.c` (all of it)
|
|
||||||
* a curated list of `src/lib/*.c` (the engine helpers that are
|
|
||||||
platform-independent: mema, memp, model, music, snd, vi, rdp, etc.)
|
|
||||||
* `src/lib/ultra/audio/*.c` (libaudio — runs fine on the CPU)
|
|
||||||
* `src/lib/ultra/gu/*.c` (gu matrix helpers)
|
|
||||||
* `port/src/*.c` + `port/src/*.cpp` + `port/fast3d/*.cpp`
|
|
||||||
|
|
||||||
It deliberately **excludes** the N64 I/O drivers (`src/lib/ultra/io/*.c`),
|
|
||||||
the boot/TLB assembly, and the RSP microcode (`.s` under `rsp/`).
|
|
||||||
|
|
||||||
### 2.4 PD-port copy-candidate audit (session 2026-08-22)
|
|
||||||
|
|
||||||
The PD port (`PD_PORT_CHECKOUT`) is a **standing reference**
|
|
||||||
for this project (see AGENTS.md): same Rare "Indy"/Bond engine family, and its
|
|
||||||
port layer solves the same problem classes we are currently hitting. Audited
|
|
||||||
against our remaining work items:
|
|
||||||
|
|
||||||
**Direct copy candidates (Phase 3/4 — our stubs were scaffolded to be replaced
|
|
||||||
by these):**
|
|
||||||
|
|
||||||
| PD file | Lines | Replaces our | Notes |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `port/src/mixer.c` | 722 | 31-line stub | libaudio→SDL final mixer; same RareAL family. Adapt to GE's `audi.c` wiring (sample rate/buffering) |
|
|
||||||
| `port/src/input.c` | 1551 | 48-line stub | SDL_GameController backend: hotplug, rumble, keyboard fallback. Remap the button table to GE's scheme (C-buttons/Z-trig differ from PD) |
|
|
||||||
| `port/src/fs.c` | 294 | 81 lines | File-backed save dir (`--savedir`, `$S` path expansion) — foundation for Phase 4 EEPROM/PFS file backing (ours are no-op stubs today) |
|
|
||||||
| `port/src/audio.c` | 75 | 70 lines | Near-identical; diff when Phase 3 starts |
|
|
||||||
|
|
||||||
**Reference implementations (Phase 2 — the D32/D37/D43 class).** PD's port
|
|
||||||
layer has a `port/src/preprocess/` module (~4,125 lines): a per-ROM-segment
|
|
||||||
`preprocessfunc` table in their `romdata.c` that converts N64-layout asset data
|
|
||||||
to PC-native layout *at load time* — a systematic solution to the problem class
|
|
||||||
we have been fixing ad-hoc per asset type.
|
|
||||||
- `filemodel.c` (1,114) — the direct **D43** analogue: same `PROMOTE` idiom,
|
|
||||||
**same vma 0x5000000**. NOT a drop-in copy (GE's `ModelRoData` union + opcode
|
|
||||||
set differ — PD has stargunfire/headspot/gundl node types), but the node-walk
|
|
||||||
order, placement/alignment choices, and record handling are Rare-validated
|
|
||||||
against near-identical data: adapt rather than design from scratch.
|
|
||||||
- `filebg.c` (524) — BG tables (an expected D43 follow-on fault).
|
|
||||||
- `segaudio.c` (362) — audio bank-tree conversion; cross-check for our D37
|
|
||||||
`romdataFixupAudioBank`.
|
|
||||||
- `filelang.c`, `filetiles.c`, `filepads.c`, `filesetup.c`, `gbi.c`,
|
|
||||||
`segfonts.c` — the remaining asset types we will hit.
|
|
||||||
|
|
||||||
**Not copyable:** GE-specific `ModelRoData` record map; fast3d CC/RM
|
|
||||||
correctness (GE's custom GBI modes vs `gmain.s` do not exist in PD); PD extras
|
|
||||||
(`config.c`, `optionsmenu.c`, `mpsetups.c`, `mod.c`) — options menu, MP setup,
|
|
||||||
MOD player; not part of 1:1 fidelity.
|
|
||||||
|
|
||||||
**Caveats:** copy **port-layer files only** — their decomp was modified for PC
|
|
||||||
(e.g. `types.h` keeps N64 offset comments while using real pointers); our
|
|
||||||
non-negotiables are unchanged. Same family ≠ identical format: validate every
|
|
||||||
copied conversion per-field against GE headers + ROM ground truth (the standing
|
|
||||||
D32 procedure).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. GoldenEye decomp: what we're porting
|
|
||||||
|
|
||||||
* **Repo:** `n64decomp/007` (this repo). WIP decomp that byte-matches the
|
|
||||||
US/EU/JP ROMs. Game code lives in `src/game/` (~242 files) plus engine
|
|
||||||
files in `src/`.
|
|
||||||
* **Engine layout** (same family as PD):
|
|
||||||
* `src/sched.c` — RCP scheduler (`osCreateScheduler`, `__scExec`,
|
|
||||||
`__scYield`). Structurally near-identical to PD's `src/lib/sched.c`.
|
|
||||||
* `src/game/rsp.c` — builds the Gfx display list, wraps it in an `OSScTask`,
|
|
||||||
and starts it via the scheduler (`rspGfxTaskStart`).
|
|
||||||
* `src/fr.c` — frame/VI management, video modes (`osViSetMode`).
|
|
||||||
* `src/audi.c`, `src/snd.c`, `src/music.c` — libaudio-based sound + music.
|
|
||||||
* `src/joy.c` — controllers, **EEPROM** save handling (via `joyGamePak*`
|
|
||||||
-> `osEeprom*`), plus PFS/motor code for accessory (Rumble Pak / Memory
|
|
||||||
Pak) detection only.
|
|
||||||
* `src/init.c` — `init()` -> `mainproc()` -> `bossEntry()` (game entry).
|
|
||||||
**Compiled for the PC** (provides `mainproc()`); its N64-only `init()` is
|
|
||||||
stubbed (see A3).
|
|
||||||
* **Custom RSP microcode:** `rsp/graphics/gmain.s` (1545 lines) — a modified
|
|
||||||
fast3d. This is the RSP-side code that consumes the display list. For the PC
|
|
||||||
port we do **not** run this; we replace it with the software RSP. But it is
|
|
||||||
the authoritative reference for which GBI commands GE actually emits.
|
|
||||||
|
|
||||||
### 3.1 Entry / main loop
|
|
||||||
|
|
||||||
```
|
|
||||||
boot.s -> init() [src/init.c]
|
|
||||||
-> decompress data segment
|
|
||||||
-> osInitialize(), TLB setup
|
|
||||||
-> osCreateThread(mainThread, mainproc)
|
|
||||||
mainproc()
|
|
||||||
-> idleCreateThread(), piCreateManager(), rmonCreateThread()
|
|
||||||
-> schedulerInitThread() # osCreateScheduler + osScAddClient(gfxClient)
|
|
||||||
-> bossEntry() # [src/boss.c] real game start
|
|
||||||
```
|
|
||||||
|
|
||||||
The port's `main.c` replaces `boot.s`/`init()` and drives `mainproc()` (or
|
|
||||||
`bossEntry()`) directly after setting up video/audio/input/ROM.
|
|
||||||
|
|
||||||
### 3.2 The two libultra trees: `src/libultra/` and `src/libultrare/`
|
|
||||||
|
|
||||||
GE ships **two** libultra trees. `src/libultra/` is the standard Nintendo
|
|
||||||
libultra; `src/libultrare/` holds Rare's modified/replacement files. The N64
|
|
||||||
build selects which files to compile via `src/libultrare/Makefile.libultrare`,
|
|
||||||
which explicitly lists each file as *original* (from `libultra/`) or *Rare*
|
|
||||||
(from `libultrare/`). The Rare files **override** the originals where they
|
|
||||||
overlap.
|
|
||||||
|
|
||||||
What the PC build must do with each:
|
|
||||||
|
|
||||||
* **`src/libultra/audio/*.c`** — standard libaudio (AL). Runs on the CPU;
|
|
||||||
**compile** (the game's `audi.c`/`snd.c`/`music.c` depend on it).
|
|
||||||
* **`src/libultrare/audio/*.c`** (`drvrNew.c`, `env.c`, `reverb.c`) — Rare's
|
|
||||||
audio "New" driver. `synthesizer.c` calls `alSaveNew`/`alAuxBusNew`/
|
|
||||||
`alMainBusNew`/`alLoadNew`/`alResampleNew`/`alEnvmixerNew`/`alEnvmixerParam`/
|
|
||||||
`alFxParam`, all defined here. Runs on the CPU; **compile**.
|
|
||||||
* **`src/libultra/gu/*.c`** — matrix helpers (`guMtxF`, `guOrtho`, ...).
|
|
||||||
**Compile**.
|
|
||||||
* **`src/libultrare/io/vitbl.c`** — defines `osViModeTable` (a data table),
|
|
||||||
referenced ~15× by `fr.c`. **Compile** (it's data, not a driver). This is the
|
|
||||||
*only* `io/` file we compile.
|
|
||||||
* **All other `src/libultra/io/*.c` and `src/libultrare/io/*.c`** — hardware
|
|
||||||
drivers (PI, SI, SP, DP, VI, controller, PFS, motor, EEPROM, ...). **Exclude
|
|
||||||
and shim** in `port/src/libultra.c`. Notably `pfsinit.c`/`pfsisplug.c`
|
|
||||||
(`osPfsInit`/`osPfsIsPlug`) and `motor.c` (`osMotor*`) are called by
|
|
||||||
`joy.c` only for accessory detection, so they are shimmed as no-ops (see §7).
|
|
||||||
* **`src/libultra/os/*.c` + `src/libultrare/os/*.c`** — OS core (threads,
|
|
||||||
message queues, timers, cache, TLB). **Exclude and shim** in
|
|
||||||
`port/src/libultra.c` (+ `n64stubs.c` for the boot/TLB/FPU symbols).
|
|
||||||
|
|
||||||
> The exact original-vs-Rare file lists are in
|
|
||||||
> `src/libultrare/Makefile.libultrare` (`LIBULTRA_*_C_FILES` vs
|
|
||||||
> `LIBULTRARE_*_C_FILES`). Use it as the ground truth when curating the PC
|
|
||||||
> build's source list.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. N64 hardware surfaces that must be shimmed
|
|
||||||
|
|
||||||
| Subsystem | GE API used | Port strategy (from PD) |
|
|
||||||
|-----------|-------------|--------------------------|
|
|
||||||
| RSP (coproc 0) | `osSpTaskLoad/StartGo/Yield`, display lists | Software RSP (`fast3d`) — **main work** |
|
|
||||||
| RDP (coproc 1) | `osDpSetStatus`, `osDpSetNextBuffer`, render modes | Bypassed; OpenGL backend |
|
|
||||||
| VI (video) | `osViSetMode`, `osViVSyncCallback`, `osViWaitVSync` | SDL2 window + GL context + frame pacing |
|
|
||||||
| AI (audio) | `osAiSetFrequency/SetNextBuffer/GetLength` | SDL audio device + queue |
|
|
||||||
| PI (peripheral) | `osPiRawStartDma`, `osPiCreateManager`, cart reads | Read from ROM file on disk |
|
|
||||||
| SI (controller) | `osContInit/StartReadData`, `osEeprom*`, `osPfs*`/`osMotor*` | SDL input + file-backed EEPROM; PFS/motor stubbed (accessory detection only) |
|
|
||||||
| OS core | threads, msg queues, timers, `osGetTime`, cache | Single-threaded shims + host clock |
|
|
||||||
| R4300 specifics | TLB, cache, FPU csr, K0/K1 segments | No-ops / identity (native x86-64) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. GE-specific graphics differences vs PD (the RSP work)
|
|
||||||
|
|
||||||
GE's `include/gbi_extension.h` defines the custom GBI surface. Compared to the
|
|
||||||
PD port's `fast3d`, the deltas are:
|
|
||||||
|
|
||||||
1. **`G_TRI4`** — draws up to 4 triangles in one command using 4-bit vertex
|
|
||||||
indices (0–15). A memory-saving optimization (Rare packed 4 tris into the
|
|
||||||
slot normally used for `G_TRI2`).
|
|
||||||
* **Status: already supported** by the PD `fast3d` (`gfx_sp_tri4`, and the
|
|
||||||
PD game itself uses it). ✅
|
|
||||||
2. **`G_SETTEX`** (`0xc0`) — "use texture from bank": selects a texture by
|
|
||||||
bank/tile with detail/mipmap type, min-level, detail-id and texture-id.
|
|
||||||
Emitted only by `gsSPUseTexture` (`include/gbi_extension.h:193`).
|
|
||||||
* **Status: appears UNUSED.** `gsSPUseTexture` is defined but never called
|
|
||||||
anywhere in the game code or the RSP ucode (`rsp/graphics/gmain.s`), and
|
|
||||||
`G_SETTEX` appears nowhere outside its `#define`. The game uses the
|
|
||||||
standard texture commands (`gSPTexture`, `gSPTextureL`,
|
|
||||||
`gSPTextureRectangle`, `gSPTextureRectangleFlip`) — all already handled
|
|
||||||
by the PD `fast3d`. **If this holds (verify the decomp is complete),
|
|
||||||
Phase 2 shrinks to verifying the custom CC/RM modes and the PD fast3d
|
|
||||||
may work largely as-is.** Keep a `G_SETTEX` decode path as a safety net,
|
|
||||||
but it is no longer the headline task.
|
|
||||||
3. **Custom color-combiner modes** — `G_CC_MODULATEIFADE`, `G_CC_FADE`,
|
|
||||||
`G_CC_DECALFADE`, `G_CC_BLENDRGBFADEA`, etc. (fade/blend variants).
|
|
||||||
* **Status: handled generically** by the fast3d color-combiner -> GLSL
|
|
||||||
shader compiler (`gfx_cc.cpp`), which compiles arbitrary CC equations.
|
|
||||||
Needs verification that each GE mode produces correct GLSL.
|
|
||||||
4. **Custom render mode** — `G_RM_CUSTOM_AA_ZB_XLU_SURF` (AA_ZB_XLU with
|
|
||||||
`Z_UPD`).
|
|
||||||
* **Status: handled** by the render-mode -> GL state mapping; verify.
|
|
||||||
5. **`gDPLoadTLUT06/07`** — custom TLUT load variants.
|
|
||||||
* **Status: minor**, map onto the existing `G_LOADTLUT` path.
|
|
||||||
|
|
||||||
**Conclusion:** the PD `fast3d` is a strong starting point. `G_SETTEX` appears
|
|
||||||
unused (see item 2), so the concrete new RSP work is **verification of the
|
|
||||||
custom CC/RM modes** against `gmain.s`, with `G_SETTEX` as a low-priority
|
|
||||||
safety net. Everything else (matrices, lights, fog, tri1/tri4, texture load,
|
|
||||||
sync/flush) is shared.
|
|
||||||
|
|
||||||
> Note: GE's on-hardware RSP ucode is `rsp/graphics/gmain.s`. We do not run it
|
|
||||||
> on PC, but it is the ground truth for the exact command encodings GE emits —
|
|
||||||
> use it to validate the software RSP's command decoding.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Audio
|
|
||||||
|
|
||||||
* GE uses **libaudio (AL)** on the R4300 (`src/audi.c`, `src/snd.c`,
|
|
||||||
`src/music.c`) — identical architecture to PD. Sound synthesis (ADPCM
|
|
||||||
decode, mixing, FX) runs on the CPU; the RSP audio ucode is a thin
|
|
||||||
pass-through to the AI DMA.
|
|
||||||
* The PD port runs libaudio natively and routes the mixed output through
|
|
||||||
`audio.c` (SDL device) + `mixer.c`. **Carries over directly.**
|
|
||||||
* Music: GE uses the same MOD-style music system; PD's `mod.c` is the model.
|
|
||||||
* Output rate: PD uses 22020 Hz stereo s16. GE's `OUTPUT_RATE` in `audi.c`
|
|
||||||
should be matched.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Input & saves
|
|
||||||
|
|
||||||
* **Controllers:** `src/joy.c` uses `osContInit` / `osContStartReadData`.
|
|
||||||
The port maps SDL2 keyboard/mouse/gamepad into the N64 `OSError`/controller
|
|
||||||
structs. PD implements 1964GEPD-style and Xbox-style bindings — a good
|
|
||||||
default for GE too (fire=Z, aim=R, etc.).
|
|
||||||
* **Saves:** GE saves are **EEPROM-based**, not Memory Pak. The actual save
|
|
||||||
I/O (`src/game/file2.c`) uses `joyGamePakProbe/LongRead/LongWrite`, which
|
|
||||||
map to `osEepromProbe/LongRead/LongWrite` (controller EEPROM). The PFS/motor
|
|
||||||
code in `joy.c` (`osPfsInit`, `osMotorInit/Start/Stop`) is only for
|
|
||||||
accessory (Rumble Pak / Memory Pak) detection. The port therefore:
|
|
||||||
* backs the EEPROM with a file in the data dir (e.g. `eeprom.bin`), and
|
|
||||||
* stubs PFS/motor to report "no accessory" so the game proceeds without
|
|
||||||
rumble/mempak.
|
|
||||||
This is simpler than a full Memory Pak/PFS emulation.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Recommended plan
|
|
||||||
|
|
||||||
### Phase 0 — Scaffolding (this change)
|
|
||||||
* Add `port/` directory structure + headers + stub sources.
|
|
||||||
* Add a top-level `CMakeLists.txt` (+ `cmake/` modules) for the PC build,
|
|
||||||
parallel to the N64 `Makefile`.
|
|
||||||
* This document + `port/README.md`.
|
|
||||||
|
|
||||||
### Phase 1 — Boot to a window
|
|
||||||
* Port `system.c`, `fs.c`, `romdata.c`, `config.c`, `main.c`.
|
|
||||||
* Port `video.c` + a minimal `fast3d` (clear color, present).
|
|
||||||
* Goal: load `ge007.u.z64`, open a window, draw a clear color.
|
|
||||||
|
|
||||||
### Phase 1.5 — Boot to first frame (ABI reconciliation)
|
|
||||||
* Get mainThread through all of `bossInitMainthreadData()` → `bossEntry()` →
|
|
||||||
`bossMainloop()` and render frame 1. (Done — D34–D58; see §G.)
|
|
||||||
* Recurring mechanism: **ROM-data struct ABI/layout fixes** (the D32 pattern) —
|
|
||||||
a 32-bit-pointer struct read as a 64-bit-pointer struct misaligns; convert
|
|
||||||
embedded pointer fields to `u32` + cast at use sites (PD ground truth), then
|
|
||||||
verify the load-time rebase yields valid DRAM addresses. See §H for the
|
|
||||||
step-by-step procedure and the standing "non-negotiable #2 ABI exception."
|
|
||||||
* Goal: title screen / first level actually drawn on the GL surface.
|
|
||||||
|
|
||||||
### Phase 2 — RSP / rendering
|
|
||||||
* Bring in the PD `fast3d` (gfx_pc / gfx_opengl / gfx_cc / gfx_sdl2).
|
|
||||||
* Verify the custom CC/RM modes against `gmain.s`; add a `G_SETTEX` decode
|
|
||||||
path only if it turns out to be used (see §5 — it appears unused).
|
|
||||||
* Port `pdsched.c` (GE variant) to drive the software RSP.
|
|
||||||
* Goal: render the title screen / first level.
|
|
||||||
|
|
||||||
### Phase 3 — Audio + input
|
|
||||||
* Port `audio.c`, `mixer.c`, `mod.c`; wire libaudio output to SDL.
|
|
||||||
* **Decide the ASP strategy** (see §11 C2): if audio runs the `aspMain`
|
|
||||||
microcode, wire `aspMainTextStart`/`aspMainDataStart` to the real microcode
|
|
||||||
bytes in the ROM; if audio is CPU-only, the `port/src/ucode.c` dummies stand.
|
|
||||||
* Port `input.c` (keyboard/mouse/gamepad).
|
|
||||||
* Goal: playable with sound.
|
|
||||||
|
|
||||||
### Phase 4 — Saves + polish
|
|
||||||
* File-backed EEPROM (saves); PFS/motor already stubbed as no-ops.
|
|
||||||
* Widescreen, mouse-look, FPS options, config file, crash handler.
|
|
||||||
* Goal: feature-parity with the original + QoL extras.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Open questions / risks
|
|
||||||
|
|
||||||
1. **`G_SETTEX` (low priority)** — appears unused (`gsSPUseTexture` is never
|
|
||||||
called). If a future decomp revision or a missed call site shows it is used,
|
|
||||||
reverse the texture-bank selection (bank -> image, tile, detail/mipmap type)
|
|
||||||
from `gmain.s` + `tex.c` / `initmttex.c`. Not currently the main unknown.
|
|
||||||
2. **Custom CC correctness** — the GLSL compiler must handle every fade/blend
|
|
||||||
mode GE uses; validate per-mode against the original. This is now the main
|
|
||||||
rendering unknown.
|
|
||||||
3. **Scheduler fidelity** — GE's `sched.c` has speed-graph / profiling hooks
|
|
||||||
(`speedgraphMarkerHandler`) and a slightly different task model than PD;
|
|
||||||
the port scheduler must reproduce the gfx/audio interleaving and VSync
|
|
||||||
pacing.
|
|
||||||
4. **Decomp completeness** — the decomp is WIP; some functions may still be
|
|
||||||
stubs/`unk_`. The PC port inherits whatever state the decomp is in. Track
|
|
||||||
the upstream `n64decomp/007` and rebase.
|
|
||||||
5. **Endianness / alignment** — GE, like PD, is big-endian N64 code with
|
|
||||||
unaligned accesses; the PD port handles this with careful casts and
|
|
||||||
`-fno-strict-aliasing`. Reuse those flags.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. References
|
|
||||||
|
|
||||||
* PD port: https://github.com/perfect-dark-pc-port/perfect_dark
|
|
||||||
* PD decomp: https://github.com/n64decomp/perfect_dark
|
|
||||||
* GE decomp (this repo): https://github.com/n64decomp/007
|
|
||||||
* GE docs: https://github.com/kholdfuzion/goldeneye_docs
|
|
||||||
* N64 devkit / libultra: `include/PR/*.h` in this repo
|
|
||||||
* GE custom GBI: `include/gbi_extension.h`
|
|
||||||
* GE RSP ucode: `rsp/graphics/gmain.s`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. Scaffolding review: findings & resolutions
|
## 11. Scaffolding review: findings & resolutions
|
||||||
|
|
||||||
The initial scaffolding was reviewed against the sources. Each finding below
|
The initial scaffolding was reviewed against the sources. Each finding below
|
||||||
@@ -3736,7 +3314,7 @@ the inline-Model / bondhead-matrix region that could itself have caused
|
|||||||
re-apply snippet in the M-6 agent report.
|
re-apply snippet in the M-6 agent report.
|
||||||
|
|
||||||
**D115 (session M-6) — `struct player` / `struct hand` raw-offset audit +
|
**D115 (session M-6) — `struct player` / `struct hand` raw-offset audit +
|
||||||
first fix.** Full survey in `docs/AUDIT-M6-player-offsets.md` (10 offset
|
first fix.** Full survey in `docs/dev/AUDIT-M6-player-offsets.md` (10 offset
|
||||||
sites; 3 live HIGH, 1 dead const block, 2 MED, 4 already-correct).
|
sites; 3 live HIGH, 1 dead const block, 2 MED, 4 already-correct).
|
||||||
**Fixed:** `gunfire.c:4960-4962` `THROWMTX` / `THROWPOS(k)` / `THROWPREV(k)`
|
**Fixed:** `gunfire.c:4960-4962` `THROWMTX` / `THROWPOS(k)` / `THROWPREV(k)`
|
||||||
were raw byte offsets — `(u8*)g_CurrentPlayer + handoffset + 0xAD8/0xB08/
|
were raw byte offsets — `(u8*)g_CurrentPlayer + handoffset + 0xAD8/0xB08/
|
||||||
@@ -4404,7 +3982,7 @@ happen to convert cleanly; Facility's do not.
|
|||||||
|
|
||||||
*Probes:* none left in tree — `GE_C2` scratch prints removed after
|
*Probes:* none left in tree — `GE_C2` scratch prints removed after
|
||||||
root-cause. *Files touched:* `port/src/gimgfixup.c` (fix),
|
root-cause. *Files touched:* `port/src/gimgfixup.c` (fix),
|
||||||
`docs/PORT-LEARNINGS.md`, `docs/PCPortResearch.md`, `docs/LEVEL-STATUS.md`.
|
`docs/porting-notes.md`, `docs/internals.md`, `docs/LEVEL-STATUS.md`.
|
||||||
*Confidence:* Jungle fix **high** (mechanism proven with a `[C2sync]`
|
*Confidence:* Jungle fix **high** (mechanism proven with a `[C2sync]`
|
||||||
trace showing 32 slots going `abcd08xx -> 700e****`, verified render).
|
trace showing 32 slots going `abcd08xx -> 700e****`, verified render).
|
||||||
Facility diagnosis **medium** (probe evidence strong; exact off-by in
|
Facility diagnosis **medium** (probe evidence strong; exact off-by in
|
||||||
@@ -4504,7 +4082,7 @@ tree as the offline-side regression guard.
|
|||||||
*Probes:* **none left in tree** — the two `#if defined(PORT)`/`GE_C3`
|
*Probes:* **none left in tree** — the two `#if defined(PORT)`/`GE_C3`
|
||||||
scratch prints in `src/game/prop.c` (lines ~1206 and ~1865) and the
|
scratch prints in `src/game/prop.c` (lines ~1206 and ~1865) and the
|
||||||
temp scan scripts `tools_pc/c3_scan.py` / `tools_pc/c3_doors.py` were all
|
temp scan scripts `tools_pc/c3_scan.py` / `tools_pc/c3_doors.py` were all
|
||||||
reverted/deleted. *Files touched:* only `docs/PCPortResearch.md` (this
|
reverted/deleted. *Files touched:* only `docs/internals.md` (this
|
||||||
entry) + `docs/LEVEL-STATUS.md` (one line). No code, no converter, no
|
entry) + `docs/LEVEL-STATUS.md` (one line). No code, no converter, no
|
||||||
sidecar change — **sidecars do NOT need regen**, tree builds clean at
|
sidecar change — **sidecars do NOT need regen**, tree builds clean at
|
||||||
`f2beae4b` + M-13 uncommitted set.
|
`f2beae4b` + M-13 uncommitted set.
|
||||||
@@ -4617,7 +4195,7 @@ handler emitting the leading s32 words then an 8-byte zero `next`;
|
|||||||
**Bunker2 `-level_27`, Depot `-level_30`, Surface2 `-level_43` now PASS**
|
**Bunker2 `-level_27`, Depot `-level_30`, Surface2 `-level_43` now PASS**
|
||||||
(91.5% / 79.8% / 70.4%); Bunker1/Silo/Aztec/Archives/Egypt/Train
|
(91.5% / 79.8% / 70.4%); Bunker1/Silo/Aztec/Archives/Egypt/Train
|
||||||
unregressed. Sweep 13 → 16/21. Generalisable quirk appended to
|
unregressed. Sweep 13 → 16/21. Generalisable quirk appended to
|
||||||
`PORT-LEARNINGS.md` §A. Remaining crashes: C2 Runway/Facility (model-GDL
|
`porting-notes.md` §A. Remaining crashes: C2 Runway/Facility (model-GDL
|
||||||
align), C2m Jungle (`G_MTX`), C5 Control (BG portal), C7 Surface1
|
align), C2m Jungle (`G_MTX`), C5 Control (BG portal), C7 Surface1
|
||||||
(`sndSetupSound`).
|
(`sndSetupSound`).
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 453 KiB |
|
After Width: | Height: | Size: 380 KiB |
|
After Width: | Height: | Size: 318 KiB |
|
After Width: | Height: | Size: 269 KiB |
|
After Width: | Height: | Size: 268 KiB |
@@ -0,0 +1,436 @@
|
|||||||
|
# GoldenEye 007 — PC Port: architecture & plan
|
||||||
|
|
||||||
|
> This began as the pre-implementation research note and is kept as the
|
||||||
|
> architecture reference. Sections 1–9 describe the design; section 10 lists
|
||||||
|
> external references. The phased plan in section 8 is largely done through
|
||||||
|
> Phase 2 — for current status see the [README](../README.md) and
|
||||||
|
> [`dev/LEVEL-STATUS.md`](dev/LEVEL-STATUS.md); for the blow-by-blow finding
|
||||||
|
> log see [`dev/findings.md`](dev/findings.md).
|
||||||
|
|
||||||
|
This document captures the research done to plan a PC port of
|
||||||
|
GoldenEye 007, based on the decompilation this repository is forked from
|
||||||
|
([`n64decomp/007`](https://github.com/n64decomp/007)). It is modelled on the
|
||||||
|
approach used by the
|
||||||
|
[Perfect Dark PC port](https://github.com/fgsfdsfgs/perfect_dark),
|
||||||
|
which ports the same Rare "Indy" engine (a later revision) to modern
|
||||||
|
platforms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Executive summary
|
||||||
|
|
||||||
|
* **Feasibility: HIGH.** GoldenEye and Perfect Dark share the same Rare
|
||||||
|
engine family. The PD port proves the whole approach works end-to-end
|
||||||
|
(singleplayer + split-screen multiplayer, mouse-look, widescreen, 60 FPS,
|
||||||
|
mods). GE is, if anything, a *simpler* graphics pipeline than PD in some
|
||||||
|
respects, though it has a few custom RSP extensions (see §5).
|
||||||
|
* **Recommended strategy: fork this decomp and add a `port/` layer**, exactly
|
||||||
|
mirroring the PD port's layout. The N64 build (Makefile + IDO) stays
|
||||||
|
untouched; the PC build is a parallel CMake build that compiles the same
|
||||||
|
game C sources against a set of PC shims.
|
||||||
|
* **The single hardest component is the RSP (Reality Signal Processor)
|
||||||
|
emulator.** The PD port ships a complete, working "fast3d" software RSP
|
||||||
|
(`port/fast3d/`, ~7k lines of C++) that interprets GBI display lists and
|
||||||
|
emits OpenGL calls. It **already handles `G_TRI4`**, which is the main
|
||||||
|
custom triangle command GE uses. GE's other custom command, `G_SETTEX`,
|
||||||
|
appears **unused** (its emitter `gsSPUseTexture` is never called), so the
|
||||||
|
remaining GE-specific work is mostly **verifying the custom
|
||||||
|
render/color-combiner modes** against the RSP ucode.
|
||||||
|
* **Audio is straightforward.** GE uses the standard libaudio (AL) engine on
|
||||||
|
the R4300, the same as PD. The PD port's SDL audio + mixer approach carries
|
||||||
|
over directly.
|
||||||
|
* **Saves** are **EEPROM-based** (controller EEPROM via `osEeprom*`), not
|
||||||
|
Memory Pak. The PFS/motor code in `joy.c` is only for accessory (Rumble Pak
|
||||||
|
/ Memory Pak) detection and can be stubbed. Phase 4 is therefore a
|
||||||
|
file-backed EEPROM plus no-op PFS/motor shims — simpler than a full Memory
|
||||||
|
Pak emulation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. What the PD port actually does (the reference architecture)
|
||||||
|
|
||||||
|
The PD port is a **fork of the PD decomp** with one added top-level
|
||||||
|
directory, `port/`. The original game code (`src/game/*.c`, `src/lib/*.c`)
|
||||||
|
is compiled **unmodified** for the PC. All N64-hardware dependencies are
|
||||||
|
satisfied by the `port/` layer:
|
||||||
|
|
||||||
|
```
|
||||||
|
port/
|
||||||
|
├── fast3d/ # Software RSP: interprets GBI display lists -> OpenGL
|
||||||
|
│ ├── gfx_pc.cpp # the RSP interpreter (matrices, lights, tris, fog)
|
||||||
|
│ ├── gfx_opengl.cpp # OpenGL rendering backend (shaders, textures, zbuf)
|
||||||
|
│ ├── gfx_sdl2.cpp # SDL2 window-manager backend
|
||||||
|
│ ├── gfx_cc.cpp # color-combiner -> GLSL shader compiler
|
||||||
|
│ └── ...
|
||||||
|
├── include/ # port-facing headers
|
||||||
|
│ ├── audio.h config.h fs.h input.h mixer.h mod.h
|
||||||
|
│ ├── romdata.h system.h video.h utils.h platform.h
|
||||||
|
└── src/
|
||||||
|
├── main.c # PC entry point (replaces boot.s / _start.s)
|
||||||
|
├── libultra.c # shims for the libultra OS API (threads, time, VI, PI, SI, SP, AI)
|
||||||
|
├── video.c # SDL2 window + GL context + frame pacing
|
||||||
|
├── audio.c # SDL audio device, buffer queueing
|
||||||
|
├── mixer.c # audio mixing / RSP-audio-buffer processing
|
||||||
|
├── mod.c # MOD music playback
|
||||||
|
├── input.c # SDL2 keyboard/mouse/gamepad -> N64 controller structs
|
||||||
|
├── fs.c # filesystem abstraction over the ROM + data dir
|
||||||
|
├── romdata.c # loads the .z64 ROM from disk, sets up segments
|
||||||
|
├── config.c # INI config (pd.ini)
|
||||||
|
├── system.c # platform primitives (time, logging, paths)
|
||||||
|
├── crash.c # crash handler / stack traces
|
||||||
|
├── pdsched.c # replacement RCP scheduler that drives the software RSP
|
||||||
|
└── utils.c
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.1 The key architectural insight: emulate the RSP, bypass the RDP
|
||||||
|
|
||||||
|
On real hardware the render path is:
|
||||||
|
|
||||||
|
```
|
||||||
|
R4300 builds a Gfx display list
|
||||||
|
|
|
||||||
|
v
|
||||||
|
RSP (coproc 0) runs "fast3d" ucode, turns GBI cmds into RDP cmds
|
||||||
|
|
|
||||||
|
v
|
||||||
|
RDP (coproc 1) rasterizes into the framebuffer
|
||||||
|
```
|
||||||
|
|
||||||
|
The PD port **emulates the RSP in software** (`fast3d/gfx_pc.cpp`) and
|
||||||
|
**bypasses the RDP entirely**: the software RSP translates GBI commands
|
||||||
|
directly into OpenGL calls. The R4300 game code is run natively and is
|
||||||
|
unaware of the difference — it just builds display lists and "starts the
|
||||||
|
RSP" as usual.
|
||||||
|
|
||||||
|
The frame flow (from `port/src/pdsched.c` + `port/src/video.c`):
|
||||||
|
|
||||||
|
```
|
||||||
|
videoStartFrame() -> gfx_start_frame()
|
||||||
|
game builds display list (Gfx*)
|
||||||
|
videoSubmitCommands(Gfx* cmds) -> gfx_run(cmds) # <- the software RSP runs here
|
||||||
|
videoEndFrame() -> gfx_end_frame() # swap buffers, FPS accounting
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 How the RSP is invoked
|
||||||
|
|
||||||
|
The game's RCP scheduler (`src/lib/sched.c` in PD, `src/sched.c` in GE) calls
|
||||||
|
`osSpTaskLoad()` + `osSpTaskStartGo()` to hand a task (gfx or audio) to the
|
||||||
|
RSP. The port:
|
||||||
|
|
||||||
|
* **Excludes** the original `libultra/io/sptask.c` (and the other `io/*.c`
|
||||||
|
hardware drivers) from the PC build.
|
||||||
|
* Provides its own scheduler (`pdsched.c`) that, instead of writing to RSP
|
||||||
|
registers, calls `gfx_run()` on the display list for gfx tasks and feeds the
|
||||||
|
audio buffer for audio tasks.
|
||||||
|
* Shims the rest of the libultra OS API in `libultra.c` (threads become
|
||||||
|
no-ops / single-threaded, `osGetTime` maps to a host clock, VI/PI/SI/AI
|
||||||
|
functions map to the port's video/audio/fs/input layers).
|
||||||
|
|
||||||
|
### 2.3 Build system
|
||||||
|
|
||||||
|
The PC build is **CMake** (the N64 build stays Make + IDO). Dependencies:
|
||||||
|
**SDL2, zlib, OpenGL** (plus `stdc++`, `m`, `dl`). It compiles:
|
||||||
|
|
||||||
|
* `src/game/*.c` (all of it)
|
||||||
|
* a curated list of `src/lib/*.c` (the engine helpers that are
|
||||||
|
platform-independent: mema, memp, model, music, snd, vi, rdp, etc.)
|
||||||
|
* `src/lib/ultra/audio/*.c` (libaudio — runs fine on the CPU)
|
||||||
|
* `src/lib/ultra/gu/*.c` (gu matrix helpers)
|
||||||
|
* `port/src/*.c` + `port/src/*.cpp` + `port/fast3d/*.cpp`
|
||||||
|
|
||||||
|
It deliberately **excludes** the N64 I/O drivers (`src/lib/ultra/io/*.c`),
|
||||||
|
the boot/TLB assembly, and the RSP microcode (`.s` under `rsp/`).
|
||||||
|
|
||||||
|
### 2.4 PD-port copy-candidate audit (session 2026-08-22)
|
||||||
|
|
||||||
|
The [Perfect Dark port](https://github.com/fgsfdsfgs/perfect_dark) is a **standing reference**
|
||||||
|
for this project (see CONTRIBUTING.md): same Rare "Indy"/Bond engine family, and its
|
||||||
|
port layer solves the same problem classes we are currently hitting. Audited
|
||||||
|
against our remaining work items:
|
||||||
|
|
||||||
|
**Direct copy candidates (Phase 3/4 — our stubs were scaffolded to be replaced
|
||||||
|
by these):**
|
||||||
|
|
||||||
|
| PD file | Lines | Replaces our | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `port/src/mixer.c` | 722 | 31-line stub | libaudio→SDL final mixer; same RareAL family. Adapt to GE's `audi.c` wiring (sample rate/buffering) |
|
||||||
|
| `port/src/input.c` | 1551 | 48-line stub | SDL_GameController backend: hotplug, rumble, keyboard fallback. Remap the button table to GE's scheme (C-buttons/Z-trig differ from PD) |
|
||||||
|
| `port/src/fs.c` | 294 | 81 lines | File-backed save dir (`--savedir`, `$S` path expansion) — foundation for Phase 4 EEPROM/PFS file backing (ours are no-op stubs today) |
|
||||||
|
| `port/src/audio.c` | 75 | 70 lines | Near-identical; diff when Phase 3 starts |
|
||||||
|
|
||||||
|
**Reference implementations (Phase 2 — the D32/D37/D43 class).** PD's port
|
||||||
|
layer has a `port/src/preprocess/` module (~4,125 lines): a per-ROM-segment
|
||||||
|
`preprocessfunc` table in their `romdata.c` that converts N64-layout asset data
|
||||||
|
to PC-native layout *at load time* — a systematic solution to the problem class
|
||||||
|
we have been fixing ad-hoc per asset type.
|
||||||
|
- `filemodel.c` (1,114) — the direct **D43** analogue: same `PROMOTE` idiom,
|
||||||
|
**same vma 0x5000000**. NOT a drop-in copy (GE's `ModelRoData` union + opcode
|
||||||
|
set differ — PD has stargunfire/headspot/gundl node types), but the node-walk
|
||||||
|
order, placement/alignment choices, and record handling are Rare-validated
|
||||||
|
against near-identical data: adapt rather than design from scratch.
|
||||||
|
- `filebg.c` (524) — BG tables (an expected D43 follow-on fault).
|
||||||
|
- `segaudio.c` (362) — audio bank-tree conversion; cross-check for our D37
|
||||||
|
`romdataFixupAudioBank`.
|
||||||
|
- `filelang.c`, `filetiles.c`, `filepads.c`, `filesetup.c`, `gbi.c`,
|
||||||
|
`segfonts.c` — the remaining asset types we will hit.
|
||||||
|
|
||||||
|
**Not copyable:** GE-specific `ModelRoData` record map; fast3d CC/RM
|
||||||
|
correctness (GE's custom GBI modes vs `gmain.s` do not exist in PD); PD extras
|
||||||
|
(`config.c`, `optionsmenu.c`, `mpsetups.c`, `mod.c`) — options menu, MP setup,
|
||||||
|
MOD player; not part of 1:1 fidelity.
|
||||||
|
|
||||||
|
**Caveats:** copy **port-layer files only** — their decomp was modified for PC
|
||||||
|
(e.g. `types.h` keeps N64 offset comments while using real pointers); our
|
||||||
|
non-negotiables are unchanged. Same family ≠ identical format: validate every
|
||||||
|
copied conversion per-field against GE headers + ROM ground truth (the standing
|
||||||
|
D32 procedure).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. GoldenEye decomp: what we're porting
|
||||||
|
|
||||||
|
* **Repo:** `n64decomp/007` (this repo). WIP decomp that byte-matches the
|
||||||
|
US/EU/JP ROMs. Game code lives in `src/game/` (~242 files) plus engine
|
||||||
|
files in `src/`.
|
||||||
|
* **Engine layout** (same family as PD):
|
||||||
|
* `src/sched.c` — RCP scheduler (`osCreateScheduler`, `__scExec`,
|
||||||
|
`__scYield`). Structurally near-identical to PD's `src/lib/sched.c`.
|
||||||
|
* `src/game/rsp.c` — builds the Gfx display list, wraps it in an `OSScTask`,
|
||||||
|
and starts it via the scheduler (`rspGfxTaskStart`).
|
||||||
|
* `src/fr.c` — frame/VI management, video modes (`osViSetMode`).
|
||||||
|
* `src/audi.c`, `src/snd.c`, `src/music.c` — libaudio-based sound + music.
|
||||||
|
* `src/joy.c` — controllers, **EEPROM** save handling (via `joyGamePak*`
|
||||||
|
-> `osEeprom*`), plus PFS/motor code for accessory (Rumble Pak / Memory
|
||||||
|
Pak) detection only.
|
||||||
|
* `src/init.c` — `init()` -> `mainproc()` -> `bossEntry()` (game entry).
|
||||||
|
**Compiled for the PC** (provides `mainproc()`); its N64-only `init()` is
|
||||||
|
stubbed (see A3).
|
||||||
|
* **Custom RSP microcode:** `rsp/graphics/gmain.s` (1545 lines) — a modified
|
||||||
|
fast3d. This is the RSP-side code that consumes the display list. For the PC
|
||||||
|
port we do **not** run this; we replace it with the software RSP. But it is
|
||||||
|
the authoritative reference for which GBI commands GE actually emits.
|
||||||
|
|
||||||
|
### 3.1 Entry / main loop
|
||||||
|
|
||||||
|
```
|
||||||
|
boot.s -> init() [src/init.c]
|
||||||
|
-> decompress data segment
|
||||||
|
-> osInitialize(), TLB setup
|
||||||
|
-> osCreateThread(mainThread, mainproc)
|
||||||
|
mainproc()
|
||||||
|
-> idleCreateThread(), piCreateManager(), rmonCreateThread()
|
||||||
|
-> schedulerInitThread() # osCreateScheduler + osScAddClient(gfxClient)
|
||||||
|
-> bossEntry() # [src/boss.c] real game start
|
||||||
|
```
|
||||||
|
|
||||||
|
The port's `main.c` replaces `boot.s`/`init()` and drives `mainproc()` (or
|
||||||
|
`bossEntry()`) directly after setting up video/audio/input/ROM.
|
||||||
|
|
||||||
|
### 3.2 The two libultra trees: `src/libultra/` and `src/libultrare/`
|
||||||
|
|
||||||
|
GE ships **two** libultra trees. `src/libultra/` is the standard Nintendo
|
||||||
|
libultra; `src/libultrare/` holds Rare's modified/replacement files. The N64
|
||||||
|
build selects which files to compile via `src/libultrare/Makefile.libultrare`,
|
||||||
|
which explicitly lists each file as *original* (from `libultra/`) or *Rare*
|
||||||
|
(from `libultrare/`). The Rare files **override** the originals where they
|
||||||
|
overlap.
|
||||||
|
|
||||||
|
What the PC build must do with each:
|
||||||
|
|
||||||
|
* **`src/libultra/audio/*.c`** — standard libaudio (AL). Runs on the CPU;
|
||||||
|
**compile** (the game's `audi.c`/`snd.c`/`music.c` depend on it).
|
||||||
|
* **`src/libultrare/audio/*.c`** (`drvrNew.c`, `env.c`, `reverb.c`) — Rare's
|
||||||
|
audio "New" driver. `synthesizer.c` calls `alSaveNew`/`alAuxBusNew`/
|
||||||
|
`alMainBusNew`/`alLoadNew`/`alResampleNew`/`alEnvmixerNew`/`alEnvmixerParam`/
|
||||||
|
`alFxParam`, all defined here. Runs on the CPU; **compile**.
|
||||||
|
* **`src/libultra/gu/*.c`** — matrix helpers (`guMtxF`, `guOrtho`, ...).
|
||||||
|
**Compile**.
|
||||||
|
* **`src/libultrare/io/vitbl.c`** — defines `osViModeTable` (a data table),
|
||||||
|
referenced ~15× by `fr.c`. **Compile** (it's data, not a driver). This is the
|
||||||
|
*only* `io/` file we compile.
|
||||||
|
* **All other `src/libultra/io/*.c` and `src/libultrare/io/*.c`** — hardware
|
||||||
|
drivers (PI, SI, SP, DP, VI, controller, PFS, motor, EEPROM, ...). **Exclude
|
||||||
|
and shim** in `port/src/libultra.c`. Notably `pfsinit.c`/`pfsisplug.c`
|
||||||
|
(`osPfsInit`/`osPfsIsPlug`) and `motor.c` (`osMotor*`) are called by
|
||||||
|
`joy.c` only for accessory detection, so they are shimmed as no-ops (see §7).
|
||||||
|
* **`src/libultra/os/*.c` + `src/libultrare/os/*.c`** — OS core (threads,
|
||||||
|
message queues, timers, cache, TLB). **Exclude and shim** in
|
||||||
|
`port/src/libultra.c` (+ `n64stubs.c` for the boot/TLB/FPU symbols).
|
||||||
|
|
||||||
|
> The exact original-vs-Rare file lists are in
|
||||||
|
> `src/libultrare/Makefile.libultrare` (`LIBULTRA_*_C_FILES` vs
|
||||||
|
> `LIBULTRARE_*_C_FILES`). Use it as the ground truth when curating the PC
|
||||||
|
> build's source list.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. N64 hardware surfaces that must be shimmed
|
||||||
|
|
||||||
|
| Subsystem | GE API used | Port strategy (from PD) |
|
||||||
|
|-----------|-------------|--------------------------|
|
||||||
|
| RSP (coproc 0) | `osSpTaskLoad/StartGo/Yield`, display lists | Software RSP (`fast3d`) — **main work** |
|
||||||
|
| RDP (coproc 1) | `osDpSetStatus`, `osDpSetNextBuffer`, render modes | Bypassed; OpenGL backend |
|
||||||
|
| VI (video) | `osViSetMode`, `osViVSyncCallback`, `osViWaitVSync` | SDL2 window + GL context + frame pacing |
|
||||||
|
| AI (audio) | `osAiSetFrequency/SetNextBuffer/GetLength` | SDL audio device + queue |
|
||||||
|
| PI (peripheral) | `osPiRawStartDma`, `osPiCreateManager`, cart reads | Read from ROM file on disk |
|
||||||
|
| SI (controller) | `osContInit/StartReadData`, `osEeprom*`, `osPfs*`/`osMotor*` | SDL input + file-backed EEPROM; PFS/motor stubbed (accessory detection only) |
|
||||||
|
| OS core | threads, msg queues, timers, `osGetTime`, cache | Single-threaded shims + host clock |
|
||||||
|
| R4300 specifics | TLB, cache, FPU csr, K0/K1 segments | No-ops / identity (native x86-64) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. GE-specific graphics differences vs PD (the RSP work)
|
||||||
|
|
||||||
|
GE's `include/gbi_extension.h` defines the custom GBI surface. Compared to the
|
||||||
|
PD port's `fast3d`, the deltas are:
|
||||||
|
|
||||||
|
1. **`G_TRI4`** — draws up to 4 triangles in one command using 4-bit vertex
|
||||||
|
indices (0–15). A memory-saving optimization (Rare packed 4 tris into the
|
||||||
|
slot normally used for `G_TRI2`).
|
||||||
|
* **Status: already supported** by the PD `fast3d` (`gfx_sp_tri4`, and the
|
||||||
|
PD game itself uses it). ✅
|
||||||
|
2. **`G_SETTEX`** (`0xc0`) — "use texture from bank": selects a texture by
|
||||||
|
bank/tile with detail/mipmap type, min-level, detail-id and texture-id.
|
||||||
|
Emitted only by `gsSPUseTexture` (`include/gbi_extension.h:193`).
|
||||||
|
* **Status: appears UNUSED.** `gsSPUseTexture` is defined but never called
|
||||||
|
anywhere in the game code or the RSP ucode (`rsp/graphics/gmain.s`), and
|
||||||
|
`G_SETTEX` appears nowhere outside its `#define`. The game uses the
|
||||||
|
standard texture commands (`gSPTexture`, `gSPTextureL`,
|
||||||
|
`gSPTextureRectangle`, `gSPTextureRectangleFlip`) — all already handled
|
||||||
|
by the PD `fast3d`. **If this holds (verify the decomp is complete),
|
||||||
|
Phase 2 shrinks to verifying the custom CC/RM modes and the PD fast3d
|
||||||
|
may work largely as-is.** Keep a `G_SETTEX` decode path as a safety net,
|
||||||
|
but it is no longer the headline task.
|
||||||
|
3. **Custom color-combiner modes** — `G_CC_MODULATEIFADE`, `G_CC_FADE`,
|
||||||
|
`G_CC_DECALFADE`, `G_CC_BLENDRGBFADEA`, etc. (fade/blend variants).
|
||||||
|
* **Status: handled generically** by the fast3d color-combiner -> GLSL
|
||||||
|
shader compiler (`gfx_cc.cpp`), which compiles arbitrary CC equations.
|
||||||
|
Needs verification that each GE mode produces correct GLSL.
|
||||||
|
4. **Custom render mode** — `G_RM_CUSTOM_AA_ZB_XLU_SURF` (AA_ZB_XLU with
|
||||||
|
`Z_UPD`).
|
||||||
|
* **Status: handled** by the render-mode -> GL state mapping; verify.
|
||||||
|
5. **`gDPLoadTLUT06/07`** — custom TLUT load variants.
|
||||||
|
* **Status: minor**, map onto the existing `G_LOADTLUT` path.
|
||||||
|
|
||||||
|
**Conclusion:** the PD `fast3d` is a strong starting point. `G_SETTEX` appears
|
||||||
|
unused (see item 2), so the concrete new RSP work is **verification of the
|
||||||
|
custom CC/RM modes** against `gmain.s`, with `G_SETTEX` as a low-priority
|
||||||
|
safety net. Everything else (matrices, lights, fog, tri1/tri4, texture load,
|
||||||
|
sync/flush) is shared.
|
||||||
|
|
||||||
|
> Note: GE's on-hardware RSP ucode is `rsp/graphics/gmain.s`. We do not run it
|
||||||
|
> on PC, but it is the ground truth for the exact command encodings GE emits —
|
||||||
|
> use it to validate the software RSP's command decoding.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Audio
|
||||||
|
|
||||||
|
* GE uses **libaudio (AL)** on the R4300 (`src/audi.c`, `src/snd.c`,
|
||||||
|
`src/music.c`) — identical architecture to PD. Sound synthesis (ADPCM
|
||||||
|
decode, mixing, FX) runs on the CPU; the RSP audio ucode is a thin
|
||||||
|
pass-through to the AI DMA.
|
||||||
|
* The PD port runs libaudio natively and routes the mixed output through
|
||||||
|
`audio.c` (SDL device) + `mixer.c`. **Carries over directly.**
|
||||||
|
* Music: GE uses the same MOD-style music system; PD's `mod.c` is the model.
|
||||||
|
* Output rate: PD uses 22020 Hz stereo s16. GE's `OUTPUT_RATE` in `audi.c`
|
||||||
|
should be matched.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Input & saves
|
||||||
|
|
||||||
|
* **Controllers:** `src/joy.c` uses `osContInit` / `osContStartReadData`.
|
||||||
|
The port maps SDL2 keyboard/mouse/gamepad into the N64 `OSError`/controller
|
||||||
|
structs. PD implements 1964GEPD-style and Xbox-style bindings — a good
|
||||||
|
default for GE too (fire=Z, aim=R, etc.).
|
||||||
|
* **Saves:** GE saves are **EEPROM-based**, not Memory Pak. The actual save
|
||||||
|
I/O (`src/game/file2.c`) uses `joyGamePakProbe/LongRead/LongWrite`, which
|
||||||
|
map to `osEepromProbe/LongRead/LongWrite` (controller EEPROM). The PFS/motor
|
||||||
|
code in `joy.c` (`osPfsInit`, `osMotorInit/Start/Stop`) is only for
|
||||||
|
accessory (Rumble Pak / Memory Pak) detection. The port therefore:
|
||||||
|
* backs the EEPROM with a file in the data dir (e.g. `eeprom.bin`), and
|
||||||
|
* stubs PFS/motor to report "no accessory" so the game proceeds without
|
||||||
|
rumble/mempak.
|
||||||
|
This is simpler than a full Memory Pak/PFS emulation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Recommended plan
|
||||||
|
|
||||||
|
### Phase 0 — Scaffolding (this change)
|
||||||
|
* Add `port/` directory structure + headers + stub sources.
|
||||||
|
* Add a top-level `CMakeLists.txt` (+ `cmake/` modules) for the PC build,
|
||||||
|
parallel to the N64 `Makefile`.
|
||||||
|
* This document + `port/README.md`.
|
||||||
|
|
||||||
|
### Phase 1 — Boot to a window
|
||||||
|
* Port `system.c`, `fs.c`, `romdata.c`, `config.c`, `main.c`.
|
||||||
|
* Port `video.c` + a minimal `fast3d` (clear color, present).
|
||||||
|
* Goal: load `ge007.u.z64`, open a window, draw a clear color.
|
||||||
|
|
||||||
|
### Phase 1.5 — Boot to first frame (ABI reconciliation)
|
||||||
|
* Get mainThread through all of `bossInitMainthreadData()` → `bossEntry()` →
|
||||||
|
`bossMainloop()` and render frame 1. (Done — D34–D58; see §G.)
|
||||||
|
* Recurring mechanism: **ROM-data struct ABI/layout fixes** (the D32 pattern) —
|
||||||
|
a 32-bit-pointer struct read as a 64-bit-pointer struct misaligns; convert
|
||||||
|
embedded pointer fields to `u32` + cast at use sites (PD ground truth), then
|
||||||
|
verify the load-time rebase yields valid DRAM addresses. See §H for the
|
||||||
|
step-by-step procedure and the standing "non-negotiable #2 ABI exception."
|
||||||
|
* Goal: title screen / first level actually drawn on the GL surface.
|
||||||
|
|
||||||
|
### Phase 2 — RSP / rendering
|
||||||
|
* Bring in the PD `fast3d` (gfx_pc / gfx_opengl / gfx_cc / gfx_sdl2).
|
||||||
|
* Verify the custom CC/RM modes against `gmain.s`; add a `G_SETTEX` decode
|
||||||
|
path only if it turns out to be used (see §5 — it appears unused).
|
||||||
|
* Port `pdsched.c` (GE variant) to drive the software RSP.
|
||||||
|
* Goal: render the title screen / first level.
|
||||||
|
|
||||||
|
### Phase 3 — Audio + input
|
||||||
|
* Port `audio.c`, `mixer.c`, `mod.c`; wire libaudio output to SDL.
|
||||||
|
* **Decide the ASP strategy** (see §11 C2): if audio runs the `aspMain`
|
||||||
|
microcode, wire `aspMainTextStart`/`aspMainDataStart` to the real microcode
|
||||||
|
bytes in the ROM; if audio is CPU-only, the `port/src/ucode.c` dummies stand.
|
||||||
|
* Port `input.c` (keyboard/mouse/gamepad).
|
||||||
|
* Goal: playable with sound.
|
||||||
|
|
||||||
|
### Phase 4 — Saves + polish
|
||||||
|
* File-backed EEPROM (saves); PFS/motor already stubbed as no-ops.
|
||||||
|
* Widescreen, mouse-look, FPS options, config file, crash handler.
|
||||||
|
* Goal: feature-parity with the original + QoL extras.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Open questions / risks
|
||||||
|
|
||||||
|
1. **`G_SETTEX` (low priority)** — appears unused (`gsSPUseTexture` is never
|
||||||
|
called). If a future decomp revision or a missed call site shows it is used,
|
||||||
|
reverse the texture-bank selection (bank -> image, tile, detail/mipmap type)
|
||||||
|
from `gmain.s` + `tex.c` / `initmttex.c`. Not currently the main unknown.
|
||||||
|
2. **Custom CC correctness** — the GLSL compiler must handle every fade/blend
|
||||||
|
mode GE uses; validate per-mode against the original. This is now the main
|
||||||
|
rendering unknown.
|
||||||
|
3. **Scheduler fidelity** — GE's `sched.c` has speed-graph / profiling hooks
|
||||||
|
(`speedgraphMarkerHandler`) and a slightly different task model than PD;
|
||||||
|
the port scheduler must reproduce the gfx/audio interleaving and VSync
|
||||||
|
pacing.
|
||||||
|
4. **Decomp completeness** — the decomp is WIP; some functions may still be
|
||||||
|
stubs/`unk_`. The PC port inherits whatever state the decomp is in. Track
|
||||||
|
the upstream `n64decomp/007` and rebase.
|
||||||
|
5. **Endianness / alignment** — GE, like PD, is big-endian N64 code with
|
||||||
|
unaligned accesses; the PD port handles this with careful casts and
|
||||||
|
`-fno-strict-aliasing`. Reuse those flags.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. References
|
||||||
|
|
||||||
|
* PD port: https://github.com/fgsfdsfgs/perfect_dark
|
||||||
|
* PD decomp: https://github.com/n64decomp/perfect_dark
|
||||||
|
* GE decomp (this repo): https://github.com/n64decomp/007
|
||||||
|
* GE docs: https://github.com/kholdfuzion/goldeneye_docs
|
||||||
|
* N64 devkit / libultra: `include/PR/*.h` in this repo
|
||||||
|
* GE custom GBI: `include/gbi_extension.h`
|
||||||
|
* GE RSP ucode: `rsp/graphics/gmain.s`
|
||||||
|
|
||||||
|
---
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
# PORT-LEARNINGS — recurring N64→PC bug classes
|
# Porting notes — recurring N64→PC bug classes
|
||||||
|
|
||||||
One-screen index into `docs/PCPortResearch.md` §F. **Every investigation
|
A one-screen field guide to the bug classes that keep recurring when running
|
||||||
subagent reads this first and appends any new generalisable quirk.**
|
big-endian 32-bit N64 game code, unmodified, on a little-endian 64-bit host.
|
||||||
Details, evidence, and the fix for each instance live in §F under the
|
Each entry cites a `Dxx` label; the full evidence and fix for that instance
|
||||||
cited Dxx label.
|
live in [`dev/findings.md`](dev/findings.md) under the same label.
|
||||||
|
|
||||||
|
If you are debugging a crash in this port, read this first — the odds are
|
||||||
|
good that you are looking at one of these.
|
||||||
|
|
||||||
## A. Pointer-width struct growth (32→64) — the dominant class
|
## A. Pointer-width struct growth (32→64) — the dominant class
|
||||||
|
|
||||||
@@ -30,7 +33,7 @@ state.
|
|||||||
`.act_*.attack_item` — aliased `WeaponObjRecord.weaponnum` at 0x80 on
|
`.act_*.attack_item` — aliased `WeaponObjRecord.weaponnum` at 0x80 on
|
||||||
N64 via act-union@0x2C+84; act union moves to ~0x38 on PC).
|
N64 via act-union@0x2C+84; act union moves to ~0x38 on PC).
|
||||||
- **Open landmine:** raw hardcoded-offset accessors into `struct player`
|
- **Open landmine:** raw hardcoded-offset accessors into `struct player`
|
||||||
/ `struct hand` — see `docs/AUDIT-M6-player-offsets.md`.
|
/ `struct hand` — see `docs/dev/AUDIT-M6-player-offsets.md`.
|
||||||
- **D126 corollary — a trailing runtime list pointer in a ROM-serialized
|
- **D126 corollary — a trailing runtime list pointer in a ROM-serialized
|
||||||
record.** If a fixed-size ROM record ends with a `T *next` (or `*child`,
|
record.** If a fixed-size ROM record ends with a `T *next` (or `*child`,
|
||||||
`*parent`) that game code *writes* while walking the setup stream
|
`*parent`) that game code *writes* while walking the setup stream
|
||||||
@@ -381,7 +384,7 @@ S-swap that mirrors the whole screen — a bad trade for a cosmetic bug.
|
|||||||
**Next attempt needs a RenderDoc/apitrace capture of one glyph texrect
|
**Next attempt needs a RenderDoc/apitrace capture of one glyph texrect
|
||||||
OR the asymmetric-1-texel-texture experiment — nothing else.** Cosmetic,
|
OR the asymmetric-1-texel-texture experiment — nothing else.** Cosmetic,
|
||||||
deprioritised below level-progression / crash work. See
|
deprioritised below level-progression / crash work. See
|
||||||
`docs/GRAPHICS-BACKLOG.md`.
|
`docs/dev/GRAPHICS-BACKLOG.md`.
|
||||||
|
|
||||||
## D3. GCC/mingw makes an all-non-negative `enum` UNSIGNED
|
## D3. GCC/mingw makes an all-non-negative `enum` UNSIGNED
|
||||||
|
|
||||||
@@ -492,7 +495,7 @@ past it (walk off the end).
|
|||||||
`tools_pc/pixcount.py` vs `docs/reference/n64-footage-*`).
|
`tools_pc/pixcount.py` vs `docs/reference/n64-footage-*`).
|
||||||
- gdb **launch** mode is too slow for timing-dependent faults; gdb
|
- gdb **launch** mode is too slow for timing-dependent faults; gdb
|
||||||
**attach** to an already-running process is fine and fast.
|
**attach** to an already-running process is fine and fast.
|
||||||
- Check `PD_PORT_CHECKOUT` for the PD analogue before
|
- Check the [Perfect Dark port](https://github.com/fgsfdsfgs/perfect_dark) for the PD analogue before
|
||||||
writing anything new — same Rare engine family.
|
writing anything new — same Rare engine family.
|
||||||
- Don't re-investigate a closed §F finding or re-derive a format spec
|
- Don't re-investigate a closed §F finding or re-derive a format spec
|
||||||
that already has a converter.
|
that already has a converter.
|
||||||
@@ -1,510 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en-au" data-darkreader-mode="dynamic" data-darkreader-scheme="dark"><head>
|
|
||||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8"><style class="darkreader darkreader--fallback" media="screen"></style><style class="darkreader darkreader--user-agent" media="screen">@layer {
|
|
||||||
html {
|
|
||||||
background-color: var(--darkreader-background-ffffff, #181a1b) !important;
|
|
||||||
}
|
|
||||||
html {
|
|
||||||
color-scheme: dark !important;
|
|
||||||
}
|
|
||||||
iframe {
|
|
||||||
color-scheme: dark !important;
|
|
||||||
}
|
|
||||||
html, body {
|
|
||||||
background-color: var(--darkreader-background-ffffff, #181a1b);
|
|
||||||
}
|
|
||||||
html, body {
|
|
||||||
border-color: var(--darkreader-border-4c4c4c, #736b5e);
|
|
||||||
color: var(--darkreader-text-000000, #e8e6e3);
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: var(--darkreader-text-0040ff, #3391ff);
|
|
||||||
}
|
|
||||||
table {
|
|
||||||
border-color: var(--darkreader-border-808080, #545b5e);
|
|
||||||
}
|
|
||||||
mark {
|
|
||||||
color: var(--darkreader-text-000000, #e8e6e3);
|
|
||||||
}
|
|
||||||
::placeholder {
|
|
||||||
color: var(--darkreader-text-a9a9a9, #b2aba1);
|
|
||||||
}
|
|
||||||
input:-webkit-autofill,
|
|
||||||
textarea:-webkit-autofill,
|
|
||||||
select:-webkit-autofill {
|
|
||||||
background-color: var(--darkreader-background-faffbd, #404400) !important;
|
|
||||||
color: var(--darkreader-text-000000, #e8e6e3) !important;
|
|
||||||
}
|
|
||||||
* {
|
|
||||||
scrollbar-color: var(--darkreader-background-b0b0b0, #454a4d) var(--darkreader-background-f1f1f1, #202324);
|
|
||||||
}
|
|
||||||
::selection {
|
|
||||||
background-color: var(--darkreader-background-0060d4, #004daa) !important;
|
|
||||||
color: var(--darkreader-text-ffffff, #e8e6e3) !important;
|
|
||||||
}
|
|
||||||
::-moz-selection {
|
|
||||||
background-color: var(--darkreader-background-0060d4, #004daa) !important;
|
|
||||||
color: var(--darkreader-text-ffffff, #e8e6e3) !important;
|
|
||||||
}
|
|
||||||
}</style><style class="darkreader darkreader--text" media="screen"></style><style class="darkreader darkreader--invert" media="screen">.jfk-bubble.gtx-bubble, .captcheck_answer_label > input + img, span#closed_text > img[src^="https://www.gstatic.com/images/branding/googlelogo"], span[data-href^="https://www.hcaptcha.com/"] > #icon, img.Wirisformula, a[data-testid="headerMediumLogo"]>svg, .d2l-navigation-link-image-container, .d2l-iframe-loading-container, div.mc-ip-hide[style*="background: rgb(255, 255, 255) !important"], [style*="color: rgb(0, 0, 1) !important;"] {
|
|
||||||
filter: invert(100%) hue-rotate(180deg) contrast(90%) !important;
|
|
||||||
}</style><style class="darkreader darkreader--inline" media="screen">[data-darkreader-inline-bgcolor] {
|
|
||||||
background-color: var(--darkreader-inline-bgcolor) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-bgimage] {
|
|
||||||
background-image: var(--darkreader-inline-bgimage) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-border] {
|
|
||||||
border-color: var(--darkreader-inline-border) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-border-bottom] {
|
|
||||||
border-bottom-color: var(--darkreader-inline-border-bottom) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-border-left] {
|
|
||||||
border-left-color: var(--darkreader-inline-border-left) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-border-right] {
|
|
||||||
border-right-color: var(--darkreader-inline-border-right) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-border-top] {
|
|
||||||
border-top-color: var(--darkreader-inline-border-top) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-boxshadow] {
|
|
||||||
box-shadow: var(--darkreader-inline-boxshadow) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-color] {
|
|
||||||
color: var(--darkreader-inline-color) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-fill] {
|
|
||||||
fill: var(--darkreader-inline-fill) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-stroke] {
|
|
||||||
stroke: var(--darkreader-inline-stroke) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-outline] {
|
|
||||||
outline-color: var(--darkreader-inline-outline) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-stopcolor] {
|
|
||||||
stop-color: var(--darkreader-inline-stopcolor) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-bg] {
|
|
||||||
background: var(--darkreader-inline-bg) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-border-short] {
|
|
||||||
border: var(--darkreader-inline-border-short) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-border-bottom-short] {
|
|
||||||
border-bottom: var(--darkreader-inline-border-bottom-short) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-border-left-short] {
|
|
||||||
border-left: var(--darkreader-inline-border-left-short) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-border-right-short] {
|
|
||||||
border-right: var(--darkreader-inline-border-right-short) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-border-top-short] {
|
|
||||||
border-top: var(--darkreader-inline-border-top-short) !important;
|
|
||||||
}
|
|
||||||
[data-darkreader-inline-invert] {
|
|
||||||
filter: invert(100%) hue-rotate(180deg);
|
|
||||||
}</style><style class="darkreader darkreader--variables" media="screen">:root {
|
|
||||||
--darkreader-neutral-background: var(--darkreader-background-ffffff, #181a1b);
|
|
||||||
--darkreader-neutral-text: var(--darkreader-text-000000, #e8e6e3);
|
|
||||||
--darkreader-selection-background: var(--darkreader-background-0060d4, #004daa);
|
|
||||||
--darkreader-selection-text: var(--darkreader-text-ffffff, #e8e6e3);
|
|
||||||
}</style><style class="darkreader darkreader--root-vars" media="screen"></style><meta http-equiv="X-Clacks-Overhead" content="GNU Terry Pratchett"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Decompiling a Nintendo 64 Game in 84 Days | Chris' Blog</title><meta name="title" content="Decompiling a Nintendo 64 Game in 84 Days"><meta name="description" content="The original Snowboard Kids has been fully decompiled in only 84 days! In this post I delve into what changed and what lessons might apply to future projects."><meta name="keywords" content=""><meta property="og:url" content="http://blog.chrislewis.au/decompiling-a-nintendo-64-game-in-84-days/"><meta property="og:site_name" content="Chris' Blog"><meta property="og:title" content="Decompiling a Nintendo 64 Game in 84 Days"><meta property="og:description" content="The original Snowboard Kids has been fully decompiled in only 84 days! In this post I delve into what changed and what lessons might apply to future projects."><meta property="og:locale" content="en_au"><meta property="og:type" content="article"><meta property="article:section" content="blog"><meta property="article:published_time" content="2026-08-26T20:23:59-07:00"><meta property="article:modified_time" content="2026-08-26T20:23:59-07:00"><meta property="og:image" content="http://blog.chrislewis.au/snowboard-kids-progress.png"><meta name="twitter:card" content="summary_large_image"><meta name="twitter:image" content="http://blog.chrislewis.au/snowboard-kids-progress.png"><meta name="twitter:title" content="Decompiling a Nintendo 64 Game in 84 Days"><meta name="twitter:description" content="The original Snowboard Kids has been fully decompiled in only 84 days! In this post I delve into what changed and what lessons might apply to future projects."><meta itemprop="name" content="Decompiling a Nintendo 64 Game in 84 Days"><meta itemprop="description" content="The original Snowboard Kids has been fully decompiled in only 84 days! In this post I delve into what changed and what lessons might apply to future projects."><meta itemprop="datePublished" content="2026-08-26T20:23:59-07:00"><meta itemprop="dateModified" content="2026-08-26T20:23:59-07:00"><meta itemprop="wordCount" content="2266"><meta itemprop="image" content="http://blog.chrislewis.au/snowboard-kids-progress.png"><meta name="referrer" content="no-referrer-when-downgrade"><style>:root{--width:720px;--font-main:Verdana, sans-serif;--font-secondary:Verdana, sans-serif;--font-scale:1em;--background-color:#fff;--heading-color:#222;--text-color:#444;--link-color:#3273dc;--visited-color:#8b6fcb;--blockquote-color:#222}@media(prefers-color-scheme:dark){:root{--background-color:#01242e;--heading-color:#eee;--text-color:#ddd;--link-color:#8cc2dd;--visited-color:#8b6fcb;--blockquote-color:#ccc}}body{font-family:var(--font-secondary);font-size:var(--font-scale);margin:auto;padding:20px;max-width:var(--width);text-align:left;background-color:var(--background-color);word-wrap:break-word;overflow-wrap:break-word;line-height:1.5;color:var(--text-color)}h1,h2,h3,h4,h5,h6{font-family:var(--font-main);color:var(--heading-color)}a{color:var(--link-color);cursor:pointer;text-decoration:none}a:hover{text-decoration:underline}nav a{margin-right:8px}strong,b{color:var(--heading-color)}button{margin:0;cursor:pointer}time{font-family:monospace;font-style:normal;font-size:15px}main{line-height:1.6}table{width:100%}hr{border:0;border-top:1px dashed}img{max-width:100%}code{font-family:monospace;padding:2px;border-radius:3px}blockquote{border-left:1px solid #999;color:var(--blockquote-color);padding-left:20px;font-style:italic}footer{padding:25px 0;text-align:center}.title:hover{text-decoration:none}.title h1{font-size:1.5em}.inline{width:auto!important}.highlight,.code{border-radius:3px;margin-block-start:1em;margin-block-end:1em;overflow-x:auto}ul.blog-posts{list-style-type:none;padding:unset}ul.blog-posts li{display:flex}ul.blog-posts li span{flex:0 0 130px}ul.blog-posts li a:visited{color:var(--visited-color)}</style><style class="darkreader darkreader--sync" media="screen"></style><style type="text/css">img{margin:0 auto;display:block}@media(prefers-color-scheme:dark){img[src$="#darksafe"]{filter:invert(1)}}.image-caption{font-size:calc(var(--font-scale) * .8);margin:0 20px;font-style:italic;opacity:.9;text-align:center}.highlight{background-color:#272822}.bluesky-comments{margin-top:2.5rem}.bluesky-comments h2{font-size:1.25em;margin-bottom:.5rem}.bluesky-comments__status,.bluesky-comments__summary{font-size:calc(var(--font-scale) * .9)}.bluesky-comments__summary{align-items:center;display:flex;flex-wrap:wrap;gap:.5rem 1rem}.bluesky-comments__reply{display:grid;gap:.75rem;grid-template-columns:44px 1fr;margin-top:1rem}.bluesky-comments__avatar{aspect-ratio:1;border-radius:50%;display:block;height:44px;object-fit:cover;width:44px}.bluesky-comments__avatar--blank{background:var(--blockquote-color);opacity:.25}.bLine truncated
|
|
||||||
background-color: white !important;
|
|
||||||
}
|
|
||||||
.vimvixen-hint {
|
|
||||||
background-color: var(--darkreader-background-ffd76e, #684b00) !important;
|
|
||||||
border-color: var(--darkreader-background-c59d00, #9e7e00) !important;
|
|
||||||
color: var(--darkreader-text-302505, #d7d4cf) !important;
|
|
||||||
}
|
|
||||||
#vimvixen-console-frame {
|
|
||||||
color-scheme: light !important;
|
|
||||||
}
|
|
||||||
::placeholder {
|
|
||||||
opacity: 0.5 !important;
|
|
||||||
}
|
|
||||||
#edge-translate-panel-body,
|
|
||||||
.MuiTypography-body1,
|
|
||||||
.nfe-quote-text {
|
|
||||||
color: var(--darkreader-neutral-text) !important;
|
|
||||||
}
|
|
||||||
gr-main-header {
|
|
||||||
background-color: var(--darkreader-background-add8e6, #1b4958) !important;
|
|
||||||
}
|
|
||||||
.tou-z65h9k,
|
|
||||||
.tou-mignzq,
|
|
||||||
.tou-1b6i2ox,
|
|
||||||
.tou-lnqlqk {
|
|
||||||
background-color: var(--darkreader-neutral-background) !important;
|
|
||||||
}
|
|
||||||
.tou-75mvi {
|
|
||||||
background-color: var(--darkreader-background-cfecf5, #0f3a47) !important;
|
|
||||||
}
|
|
||||||
.tou-ta9e87,
|
|
||||||
.tou-1w3fhi0,
|
|
||||||
.tou-1b8t2us,
|
|
||||||
.tou-py7lfi,
|
|
||||||
.tou-1lpmd9d,
|
|
||||||
.tou-1frrtv8,
|
|
||||||
.tou-17ezmgn {
|
|
||||||
background-color: var(--darkreader-background-f5f5f5, #1e2021) !important;
|
|
||||||
}
|
|
||||||
.tou-uknfeu {
|
|
||||||
background-color: var(--darkreader-background-faedda, #432c09) !important;
|
|
||||||
}
|
|
||||||
.tou-6i3zyv {
|
|
||||||
background-color: var(--darkreader-background-85c3d8, #245d70) !important;
|
|
||||||
}
|
|
||||||
div.mermaid-viewer-control-panel .btn {
|
|
||||||
background-color: var(--darkreader-neutral-background);
|
|
||||||
fill: var(--darkreader-neutral-text);
|
|
||||||
}
|
|
||||||
svg g rect.er {
|
|
||||||
fill: var(--darkreader-neutral-background) !important;
|
|
||||||
}
|
|
||||||
svg g rect.er.entityBox {
|
|
||||||
fill: var(--darkreader-neutral-background) !important;
|
|
||||||
}
|
|
||||||
svg g rect.er.attributeBoxOdd {
|
|
||||||
fill: var(--darkreader-neutral-background) !important;
|
|
||||||
}
|
|
||||||
svg g rect.er.attributeBoxEven {
|
|
||||||
fill: var(--darkreader-selection-background);
|
|
||||||
fill-opacity: 0.8 !important;
|
|
||||||
}
|
|
||||||
svg rect.er.relationshipLabelBox {
|
|
||||||
fill: var(--darkreader-neutral-background) !important;
|
|
||||||
}
|
|
||||||
svg g g.nodes rect,
|
|
||||||
svg g g.nodes polygon {
|
|
||||||
fill: var(--darkreader-neutral-background) !important;
|
|
||||||
}
|
|
||||||
svg g rect.task {
|
|
||||||
fill: var(--darkreader-selection-background) !important;
|
|
||||||
}
|
|
||||||
svg line.messageLine0,
|
|
||||||
svg line.messageLine1 {
|
|
||||||
stroke: var(--darkreader-neutral-text) !important;
|
|
||||||
}
|
|
||||||
div.mermaid .actor {
|
|
||||||
fill: var(--darkreader-neutral-background) !important;
|
|
||||||
}
|
|
||||||
mitid-authenticators-code-app > .code-app-container {
|
|
||||||
background-color: white !important;
|
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
|
||||||
iframe#unpaywall[src$="unpaywall.html"] {
|
|
||||||
color-scheme: light !important;
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
--darkreader-bg--form-control-background-color: rgba(22, 22, 22, 0) !important;
|
|
||||||
}
|
|
||||||
body#tumblr {
|
|
||||||
--darkreader-bg--secondary-accent: 31, 32, 34 !important;
|
|
||||||
--darkreader-bg--white: 23, 23, 23 !important;
|
|
||||||
--darkreader-text--black: 228, 224, 218 !important;
|
|
||||||
}
|
|
||||||
:host {
|
|
||||||
--d2l-border-color: var(--darkreader-bg--d2l-color-gypsum) !important;
|
|
||||||
--d2l-button-icon-background-color-hover: var(--darkreader-bg--d2l-color-gypsum) !important;
|
|
||||||
--d2l-color-ferrite: var(--darkreader-neutral-text) !important;
|
|
||||||
--d2l-color-sylvite: var(--darkreader-bg--d2l-color-sylvite) !important;
|
|
||||||
--d2l-dropdown-background-color: var(--darkreader-neutral-background) !important;
|
|
||||||
--d2l-dropdown-border-color: var(--darkreader-border--d2l-color-mica) !important;
|
|
||||||
--d2l-input-backgroud-color: var(--darkreader-neutral-background) !important;
|
|
||||||
--d2l-menu-border-color: var(--darkreader-bg--d2l-color-gypsum) !important;
|
|
||||||
--d2l-tooltip-background-color: var(--darkreader-neutral-background) !important;
|
|
||||||
--d2l-tooltip-border-color: var(--darkreader-bg--d2l-color-gypsum) !important;
|
|
||||||
}
|
|
||||||
:host([_floating]) .d2l-floating-buttons-container {
|
|
||||||
background-color: var(--darkreader-neutral-background) !important;
|
|
||||||
border-top-color: var(--darkreader-border--d2l-color-mica) !important;
|
|
||||||
opacity: 0.88 !important;
|
|
||||||
}
|
|
||||||
d2l-card {
|
|
||||||
background: var(--darkreader-neutral-background) !important;
|
|
||||||
border-color: var(--darkreader-border--d2l-color-gypsum) !important;
|
|
||||||
}
|
|
||||||
d2l-dropdown-content > div,
|
|
||||||
d2l-menu-item {
|
|
||||||
background-color: var(--darkreader-neutral-background) !important;
|
|
||||||
border-radius: 10px !important;
|
|
||||||
}
|
|
||||||
d2l-empty-state-simple {
|
|
||||||
border-color: var(--darkreader-bg--d2l-color-gypsum) !important;
|
|
||||||
}
|
|
||||||
.d2l-button-filter > ul > li > a.vui-button {
|
|
||||||
border-color: var(--darkreader-border--d2l-color-mica) !important;
|
|
||||||
}
|
|
||||||
.d2l-label-text:has(.d2l-button-subtle-content):hover,
|
|
||||||
.d2l-label-text:has(.d2l-button-subtle-content):focus,
|
|
||||||
.d2l-label-text:has(.d2l-button-subtle-content):active {
|
|
||||||
background-color: var(--darkreader-bg--d2l-color-gypsum) !important;
|
|
||||||
}
|
|
||||||
.d2l-navigation-centerer {
|
|
||||||
color: inherit !important;
|
|
||||||
}
|
|
||||||
.d2l-tabs-layout {
|
|
||||||
border-color: var(--darkreader-border--d2l-color-gypsum) !important;
|
|
||||||
}
|
|
||||||
.d2l-input,
|
|
||||||
.d2l-calendar-date,
|
|
||||||
.d2l-htmleditor-container {
|
|
||||||
background-color: var(--darkreader-neutral-background) !important;
|
|
||||||
}
|
|
||||||
.d2l-collapsible-panel {
|
|
||||||
border: 1px solid var(--darkreader-border--d2l-color-mica) !important;
|
|
||||||
border-radius: 0.4rem !important;
|
|
||||||
}
|
|
||||||
.d2l-collapsible-panel-divider {
|
|
||||||
border-bottom: 1px solid var(--darkreader-border--d2l-color-mica) !important;
|
|
||||||
}
|
|
||||||
.d2l-w2d-flex {
|
|
||||||
border-bottom: 2px solid var(--darkreader-border--d2l-color-mica) !important;
|
|
||||||
}
|
|
||||||
.d2l-collapsible-panel scrolled,
|
|
||||||
.d2l-collapsible-panel-header,
|
|
||||||
.d2l-w2d-collection-fixed {
|
|
||||||
background-color: var(--darkreader-neutral-background) !important;
|
|
||||||
}
|
|
||||||
.d2l-loading-spinner-bg {
|
|
||||||
fill: var(--darkreader-bg--d2l-color-gypsum) !important;
|
|
||||||
}
|
|
||||||
.d2l-loading-spinner-bg-stroke {
|
|
||||||
stroke: var(--darkreader-border--d2l-color-mica) !important;
|
|
||||||
}
|
|
||||||
.d2l-loading-spinner-wrapper svg path,
|
|
||||||
.d2l-loading-spinner-wrapper svg circle {
|
|
||||||
fill: var(--darkreader-neutral-background) !important;
|
|
||||||
}
|
|
||||||
.d2l-twopanelselector-side.d2l-twopanelselector-side-sep {
|
|
||||||
background: var(--darkreader-bg--d2l-color-mica) !important;
|
|
||||||
}
|
|
||||||
.d2l-le-TreeAccordionItem-anchor::before {
|
|
||||||
background: var(--darkreader-bg--d2l-color-corundum) !important;
|
|
||||||
}
|
|
||||||
table[class|="moz-header"] {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
*[style*="color: rgba(0,0,0,"]:not([style*="color: rgba(0,0,0,0)"]),
|
|
||||||
*[style*="color: rgba(0, 0, 0,"]:not([style*="color: rgba(0, 0, 0, 0)"]),
|
|
||||||
*[style*="color:#000"],
|
|
||||||
*[style*="color:black"],
|
|
||||||
*[style*="color:#282828"],
|
|
||||||
*[style*="color:#444"],
|
|
||||||
*[style*="color: #222222"] {
|
|
||||||
color: var(--darkreader-neutral-text) !important;
|
|
||||||
}
|
|
||||||
*[style*="background:#fff"],
|
|
||||||
*[style*="background-color:#fff"],
|
|
||||||
*[style*="background: #fff"] {
|
|
||||||
background: var(--darkreader-neutral-background) !important;
|
|
||||||
}
|
|
||||||
*[bgcolor="#F5F5F5"],
|
|
||||||
*[style*="background-color: #F5F5F5"] {
|
|
||||||
background: var(--darkreader-background-f5f5f5, #1e2021) !important;
|
|
||||||
}
|
|
||||||
*[style*="background: #e6e6e6"] {
|
|
||||||
background: var(--darkreader-background-e6e6e6, #26292b) !important;
|
|
||||||
}
|
|
||||||
body[style*="background: rgb(255, 255, 255) !important"] *:not(a),
|
|
||||||
.body[style*="background-color: rgb(254, 254, 254) !important"] *:not(a) {
|
|
||||||
background-color: var(--darkreader-neutral-background) !important;
|
|
||||||
}
|
|
||||||
div[style*="www.gstatic.com/android/"] {
|
|
||||||
background: none !important;
|
|
||||||
}
|
|
||||||
*[style*="background:#f9f9f9"] {
|
|
||||||
background: var(--darkreader-background-f9f9f9, #1b1e1f) !important;
|
|
||||||
}
|
|
||||||
body.mediawiki.skin-vector #mw-page-base {
|
|
||||||
background-image: none !important;
|
|
||||||
}
|
|
||||||
.overflow-hidden .spinning-bg,
|
|
||||||
.overflow-hidden .solid-bg {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
embed[type="application/pdf"] { filter: invert(100%) contrast(90%); }</style><style></style></head><body><header><a href="https://blog.chrislewis.au/" class="title"><h2>Chris' Blog</h2></a><nav></nav></header><main><h1>Decompiling a Nintendo 64 Game in 84 Days</h1><p><i><time datetime="2026-08-26">26 Aug, 2026</time></i></p><content><p>I’m very pleased to announce that the original <em>Snowboard Kids</em> is now 100% decompiled! This means that all functions<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> have matching C implementations that, when compiled, produce identical machine code to the original game.</p><p>This was obviously not a one-person effort. I am particularly grateful to <a href="https://github.com/inspectredc">inspectredc</a>, <a href="https://github.com/Bl00D4NGEL">Bl00D4NGEL</a>, and <a href="https://github.com/queueRAM">queueRAM</a> for their significant contributions to the project. No amount of AI would have been able to replace them.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> I would also like to thank <a href="https://github.com/iFuzzle">iFuzzle</a>, <a href="https://github.com/JamesBLewis">JamesBLewis</a>, and <a href="https://github.com/douglasjv">douglasjv</a> for lending their tokens to the cause.</p><p>My
|
|
||||||
hope is that a full decompilation will prove useful to the Snowboard
|
|
||||||
Kids community. Speedrunners in particular have long focused on the
|
|
||||||
first game. Working source code can help shed light on externally
|
|
||||||
observed phenomena such as CPU pathing and the exact factors
|
|
||||||
contributing to player speed. A full understanding of the source code
|
|
||||||
will also be useful for static recompilation and more ambitious modding
|
|
||||||
efforts in the future.</p><p>The speed at which the project was finished is also noteworthy. <em>Snowboard Kids</em> took only 84 days to decompile compared with 596 days for <em>Snowboard Kids 2</em>, roughly one-seventh of the elapsed time.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p><p><a href="https://decomp.dev/cdlewis/snowboardkids-decomp"><img src="Decompiling%20a%20Nintendo%2064%20Game%20in%2084%20Days%20_%20Chris'%20Blog_files/snowboard-kids-progress_aOx2.svg" alt="chart showing weekly Snowboard Kids decompilation progress"></a></p><p class="image-caption"><a href="https://decomp.dev/cdlewis/snowboardkids-decomp">Weekly progress of the Snowboard Kids decompilation.</a></p><p></p><p>What
|
|
||||||
explains this difference? Well, it’s 2026, so the answer is at least
|
|
||||||
partially AI. But it would be a gross oversimplification to attribute
|
|
||||||
the difference entirely to LLMs.</p><h2 id="what-was-different">What Was Different</h2><p>To
|
|
||||||
state the obvious, I was not starting from scratch. By this point I had
|
|
||||||
already spent nearly two years on a similar project and was vastly
|
|
||||||
faster than when I began. This advantage is difficult to quantify and
|
|
||||||
was somewhat offset by new challenges such as working with a different
|
|
||||||
compiler.</p><p>Overall, roughly 4.8% of matching commits involved expert intervention.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>
|
|
||||||
I have already credited these amazing people once, but it’s worth
|
|
||||||
reiterating. This project would not have been possible without
|
|
||||||
significant help from the decompilation community, particularly <a href="https://github.com/inspectredc">inspectredc</a>, <a href="https://github.com/queueRAM">queueRAM</a>, and <a href="https://github.com/Bl00D4NGEL">Bl00D4NGEL</a> <3.</p><p>The difficulty generally didn’t come from trying to understand what a function did<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> but rather how that logic was expressed in C and how the resulting code was compiled. <em>Snowboard Kids</em> was compiled with IDO 5.3 rather than the GCC 2.7.2 compiler used by <em>Snowboard Kids 2</em>.</p><p>Most
|
|
||||||
programmers will be familiar with GCC. It is a widely used open-source
|
|
||||||
compiler still in active development today. IDO, on the other hand, was a
|
|
||||||
proprietary compiler developed by SGI, whose own story is closely
|
|
||||||
entwined with that of the Nintendo 64.<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>
|
|
||||||
Its source was not available, and its original development environment
|
|
||||||
was tied to obsolete SGI hardware and software. To use and properly
|
|
||||||
understand it today, the decompilation community has had to
|
|
||||||
reverse-engineer and <a href="https://github.com/n64decomp/ido">decompile parts of the compiler toolchain</a> as well as <a href="https://github.com/decompals/ido-static-recomp">statically recompile the IDO 5.3 and 7.1 suites</a> to run on modern hardware. That closed history makes IDO harder to reason about than an open compiler such as GCC.</p><p>IDO splits optimisation and code generation across several different passes, transforming code quite aggressively along the way.<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup> Tiny changes to the C can then ripple through those passes and produce a completely different register allocation.</p><p>The
|
|
||||||
community has made great strides in understanding IDO and its quirks,
|
|
||||||
but this remains more of an art than a science. LLMs and I are not
|
|
||||||
particularly good at reproducing its output. The usual workflow, for
|
|
||||||
both me and the agents, was to figure out what a function did and then
|
|
||||||
write C that approximated that purpose. From there, small tweaks
|
|
||||||
assisted by the <a href="https://github.com/simonlindholm/decomp-permuter">permuter</a>
|
|
||||||
could catch any remaining differences. But you can’t permute yourself
|
|
||||||
into a match in all cases, particularly when the underlying structure is
|
|
||||||
wrong. IDO’s behaviour made this workflow far less predictable.</p><p>A motivated human team with the right expertise and intuition can match or even exceed the pace of the <em>Snowboard Kids</em> decompilation. The <a href="https://github.com/gcsmith/Pilotwings64Decomp"><em>Pilotwings 64</em></a> decompilation was completed in only 74 days.<sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> <em>Pilotwings 64</em> had 16% fewer functions than <em>Snowboard Kids</em>, but more compiled code overall, so this is not a clean comparison either.</p><p><img src="Decompiling%20a%20Nintendo%2064%20Game%20in%2084%20Days%20_%20Chris'%20Blog_files/pilotwings_aOx2.webp" alt="screenshot of Pilotwings 64"></p><p class="image-caption">Pilotwings 64, a charming flight simulator, was a Nintendo 64 launch title and remains a cult classic to this day.</p><p></p><p><em>Snowboard Kids</em> was also smaller than its sequel, containing 2,145 functions compared with 2,995 in <em>Snowboard Kids 2</em>. Function count is a crude measure of difficulty, but there were simply fewer functions to decompile.</p><h2 id="where-agents-did-help">Where Agents Did Help</h2><p>I’ve <a href="https://blog.chrislewis.au/the-long-tail-of-llm-assisted-decompilation/">already written elsewhere</a>
|
|
||||||
about using agents to decompile functions. The same basic process was
|
|
||||||
used here, so I’ll focus on what changed. Unlike the previous project,
|
|
||||||
this one began with access to frontier models and a capable <a href="https://github.com/cdlewis/nigel">agent harness</a>.</p><h3 id="library-code-and-other-low-hanging-fruit">Library Code and Other Low-Hanging Fruit</h3><p>I
|
|
||||||
was interested to see how agents would fare during the early stages of a
|
|
||||||
project. One area where they thrived was matching standard-library
|
|
||||||
code. In theory, this is the most obvious chunk of almost any Nintendo
|
|
||||||
64 decompilation. The code is not unique to the game, and versions of it
|
|
||||||
are available online. <em>Snowboard Kids</em> contains more than a hundred source segments from Nintendo’s <code>libultra</code>, alongside functions from the <code>libmus</code> audio library. Tools such as <a href="https://github.com/shygoo/n64sym">N64Sym</a> can identify probable library functions in the ROM.</p><p>This
|
|
||||||
pass was fairly successful. The main stumbling block was convincing
|
|
||||||
agents to rely on the existing library source rather than decompile the
|
|
||||||
same functions again from scratch. This required stronger prompting.
|
|
||||||
Once a likely library function was identified, agents were instructed to
|
|
||||||
treat the corresponding source as their starting point and exhaust
|
|
||||||
plausible SDK versions, compiler options, and conditional compilation
|
|
||||||
paths before attempting their own implementation.</p><p>Another optimisation was to have an agent write a script that ran <a href="https://github.com/matt-kempster/m2c"><code>m2c</code></a>
|
|
||||||
against every unmatched function and automatically integrated any exact
|
|
||||||
matches, rather than relying on agents to attempt those functions
|
|
||||||
individually.<sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup>
|
|
||||||
The script matched only 17 of 1,830 functions, an incredibly low
|
|
||||||
success rate of 0.93%, but anything matched this way was cheaper than
|
|
||||||
burning agent tokens.</p><h3 id="ido-tooling-and-skills">IDO Tooling and Skills</h3><p>IDO
|
|
||||||
is weird, but it is often weird in recurring ways. Successfully
|
|
||||||
matching one function could reveal a compiler quirk that applied to many
|
|
||||||
others. Codex has become better at carrying lessons between tasks
|
|
||||||
through features such as local <a href="https://learn.chatgpt.com/docs/customization/memories">memories</a>. To make those lessons available beyond a single agent, I prompted agents to record observed IDO behaviour in a <a href="https://github.com/cdlewis/snowboardkids-decomp/blob/main/DECOMPILATION_LEARNINGS.md">DECOMPILATION_LEARNINGS.md</a>
|
|
||||||
file. When an agent discovered a generalisable compiler quirk, it could
|
|
||||||
record the evidence there for later attempts. This created a useful
|
|
||||||
feedback loop. Agents helped document IDO, and the resulting
|
|
||||||
documentation made subsequent agents better at matching IDO code.</p><p>But the most useful resource was <a href="https://github.com/akratch/n64-decomp-workbench">N64 Decomp Workbench</a>,
|
|
||||||
a collection of tooling and documentation for debugging late-stage MIPS
|
|
||||||
decompilation mismatches. It can classify mismatches, account for
|
|
||||||
relocations, replay individual compiler passes, and help distinguish a
|
|
||||||
structural problem from a register-allocation problem. Pass replay
|
|
||||||
requires the relevant compiler binaries and project-specific setup, but
|
|
||||||
once configured it exposes information that a raw assembly diff cannot. A
|
|
||||||
raw diff tells you that two functions differ. The Workbench can give
|
|
||||||
agents a much better idea of why the functions differ and what sort of
|
|
||||||
change might fix them.</p><h3 id="worktrees-and-synchronisation">Worktrees and Synchronisation</h3><p>For
|
|
||||||
this project, I ran the decompilation harness across four Git
|
|
||||||
worktrees. Each worktree gave an agent an independent copy of the
|
|
||||||
repository, allowing several functions to be attempted in parallel.</p><p>One small but useful improvement was to give every task an explicit deadline and expose that deadline to the agent. During the <em>Snowboard Kids 2</em>
|
|
||||||
project, agents often struggled to use the permuter effectively because
|
|
||||||
it would continue running until it found a 100% match or was manually
|
|
||||||
stopped. An explicit deadline allowed agents to set sensible timeouts
|
|
||||||
and trade off permuting time against other forms of problem-solving.
|
|
||||||
Anecdotally, it also helped them judge how long to keep working on a
|
|
||||||
difficult function before giving up. I could then increase the time
|
|
||||||
allowance as the easy functions disappeared and the remaining work
|
|
||||||
became more difficult.</p><p>Another problem that had existed in <em>Snowboard Kids 2</em> but became more apparent as I added worktrees was synchronisation. As discussed in my <a href="https://blog.chrislewis.au/the-long-tail-of-llm-assisted-decompilation/">previous post</a>,
|
|
||||||
each agent was given a function to decompile alongside a set of similar
|
|
||||||
functions that had already been matched. These provided useful
|
|
||||||
reference points for reproducing particular IDO instruction patterns.
|
|
||||||
Work was divided between the worktrees using Nigel’s <code>--shard</code> option, which uses basic hashing to partition candidates between a specified number of workers.</p><p>This
|
|
||||||
allowed more work to happen in parallel without duplication, but
|
|
||||||
introduced a new problem as the worktrees diverged. One worktree, for
|
|
||||||
example, might successfully decompile a function that was 99% similar to
|
|
||||||
a function being attempted elsewhere, but that new reference would
|
|
||||||
remain invisible to the other agent until the changes were merged.
|
|
||||||
Periodically merging everything into the main branch and resynchronising
|
|
||||||
the worktrees fixed this, but synchronising all four could take more
|
|
||||||
than an hour. Synchronising continuously wasted time, while waiting too
|
|
||||||
long increased drift.</p><p>To make synchronisation less critical, I
|
|
||||||
updated the similarity search to inspect every worktree. A newly matched
|
|
||||||
function could immediately become a reference for another agent without
|
|
||||||
waiting for it to reach the main branch. The harness would produce
|
|
||||||
messages such as the following.</p><div class="highlight"><pre tabindex="0" style="color: rgb(248, 248, 242); background-color: rgb(39, 40, 34); tab-size: 4; --darkreader-inline-color: var(--darkreader-text-f8f8f2, #e8e8d5); --darkreader-inline-bgcolor: var(--darkreader-background-272822, #1c1e1f);" data-darkreader-inline-color="" data-darkreader-inline-bgcolor=""><code class="language-text" data-lang="text"><span style="display:flex"><span style="white-space: pre; user-select: none; margin-right: 0.4em; padding: 0px 0.4em; color: rgb(127, 127, 127); --darkreader-inline-color: var(--darkreader-text-7f7f7f, #988f81);" data-darkreader-inline-color="">1</span><span>✓ Candidate ["func_80094A94", "func_80094FF4 (../sbk-c)",
|
|
||||||
</span></span><span style="display:flex"><span style="white-space: pre; user-select: none; margin-right: 0.4em; padding: 0px 0.4em; color: rgb(127, 127, 127); --darkreader-inline-color: var(--darkreader-text-7f7f7f, #988f81);" data-darkreader-inline-color="">2</span><span> "func_80094808", "func_8009491C (../sbk-a)",
|
|
||||||
</span></span><span style="display:flex"><span style="white-space: pre; user-select: none; margin-right: 0.4em; padding: 0px 0.4em; color: rgb(127, 127, 127); --darkreader-inline-color: var(--darkreader-text-7f7f7f, #988f81);" data-darkreader-inline-color="">3</span><span> "func_8009469C (../sbk-c)", "func_80093144"] was fixed!
|
|
||||||
</span></span></code></pre></div><p>This helped the process remain
|
|
||||||
efficient as the easy functions disappeared. Synchronisation was still
|
|
||||||
needed to consolidate and push changes, but it was no longer required
|
|
||||||
for agents to learn from one another.</p><h3 id="model-choice">Model Choice</h3><p>I
|
|
||||||
tried GPT-5.5 and 5.6, Claude 4.5 and Fable, and GLM 5.2. This is
|
|
||||||
completely unscientific. The models were tested against a changing set
|
|
||||||
of difficult functions, sometimes after another model had already made
|
|
||||||
partial progress. Broadly, though, Codex continued to outperform Claude,
|
|
||||||
as it had towards the end of the previous project. Sol xhigh was
|
|
||||||
particularly effective once it became available.</p><p>GLM 5.2, served
|
|
||||||
by z.ai, was very disappointing. I had previously been a big fan of GLM.
|
|
||||||
It was effective, even if it was not quite a frontier model, and its
|
|
||||||
generous usage limits made up for the gap. That tradeoff became much
|
|
||||||
less attractive as the limits grew less generous while latency remained
|
|
||||||
awful. The feedback cycles were so long that I stopped giving it work
|
|
||||||
and eventually cancelled my subscription.</p><h2 id="what-next">What Next?</h2><p>The
|
|
||||||
immediate priority is to better document the game. A 100% match means
|
|
||||||
we have C code for every function; it does not mean we understand what
|
|
||||||
every function does. There are still generated names to replace, unknown
|
|
||||||
structure fields to identify, awkward matches to clean up, and large
|
|
||||||
amounts of data to describe.</p><p>Work is also underway on a <em>Snowboard Kids</em> recompilation. Fortunately, the first game shares many of the quirks addressed by patches in <a href="https://blog.chrislewis.au/snowboard-kids-2-is-recompiled/"><em>Snowboard Kids 2: Recompiled</em></a>.</p><p><img src="Decompiling%20a%20Nintendo%2064%20Game%20in%2084%20Days%20_%20Chris'%20Blog_files/snowboard-kids-recomp-screenshot_aOx2.webp" alt="early screenshot of Snowboard Kids: Recompiled"></p><p>I’m
|
|
||||||
looking into porting the levels and other content from the first game
|
|
||||||
into the second game’s engine, although I have no idea how much work
|
|
||||||
that will be yet.</p><p>Beyond that, I’m interested in decompiling <em>Snowboard Kids Plus</em> on the PlayStation, a Japan-exclusive expanded release of the first game with extra levels and characters.</p><p><em>If you’ve made it this far, you’re probably interested in decompilation and Snowboard Kids. Take a look at the <a href="https://github.com/cdlewis/snowboardkids-decomp">Snowboard Kids decompilation project</a>. There is still plenty of cleanup and documentation work to do, and contributions are very welcome.</em></p><div class="footnotes" role="doc-endnotes"><hr><ol><li id="fn:1"><p>Almost.
|
|
||||||
A small amount of handwritten assembly from system libraries remains
|
|
||||||
because assembly was the original source rather than a temporary
|
|
||||||
decompilation placeholder. <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:2"><p>In the best case, I think the project would have stalled around 89–90% without their IDO expertise. <a href="#fnref:2" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:3"><p>For <em>Snowboard Kids 2</em>, I measured from the <a href="https://github.com/cdlewis/snowboardkids2-decomp/commit/f1025d16a8aa1d11ec937f8c721af59149feee7c">first commit on 28 September 2024</a> to the <a href="https://github.com/cdlewis/snowboardkids2-decomp/commit/ec405bd1aa8a6a2fed4421e0ba33e37b973db4ec">final matching function on 17 May 2026</a>, a span of 596 days. The 84-day figure for <em>Snowboard Kids</em> is my project-level count; the public repository itself spans from the <a href="https://github.com/cdlewis/snowboardkids-decomp/commit/08f9b60604fd084215770d4a787f7fa9d4d61865">first commit on 28 May 2026</a> to the <a href="https://github.com/cdlewis/snowboardkids-decomp/commit/875002983e210f492aec61ebe78444d2227e270b">final matching function on 19 August 2026</a>, just over 83 days. <a href="#fnref:3" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:4"><p>Based
|
|
||||||
on a manual review of the Git history, I identified roughly 41
|
|
||||||
expert-assisted matches, corresponding to about 4.8% of the commits I
|
|
||||||
classified as function-matching work. This retrospective classification
|
|
||||||
is approximate because commits can contain multiple functions, help was
|
|
||||||
often incorporated into my own commits, and the figure does not measure
|
|
||||||
person-hours. Note that I am not counting myself as an “expert”. <a href="#fnref:4" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:5"><p>One notable exception was <a href="https://github.com/cdlewis/snowboardkids-decomp/blob/ba90a9ead954514cfaa1e7c0143957fdfc754092/src/menu/main_menu/controller_main_menu_flow.c#L560"><code>validateControllerPakSave</code></a>,
|
|
||||||
which appears to have been used to debug Controller Pak saves. Most of
|
|
||||||
its debugging and validation logic was omitted from the final
|
|
||||||
build. <a href="#fnref:5" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:6"><p><a href="https://iwataasks.nintendo.com/interviews/wii/sinandpunishment/0/0/">Nintendo has described</a>
|
|
||||||
adopting an architecture from Silicon Graphics for the Nintendo 64. The
|
|
||||||
original development system was similarly SGI-centric. Nintendo’s <a href="https://ultra64.ca/files/documentation/online-manuals/man-v5-1/pro-man/pro01/index.htm">development documentation</a>
|
|
||||||
describes connecting its emulator board to an SGI Indy workstation, and
|
|
||||||
IDO was one of the supported compilers. The relationship therefore
|
|
||||||
extended from the console’s underlying architecture to the machines and
|
|
||||||
tools used to develop its games. <a href="#fnref:6" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:7"><p>IDO used a <a href="https://techpubs.jurassic.nl/library/manuals/2000/007-2479-001/sgi_html/ch01.html">multi-stage compilation pipeline</a>. Its <a href="https://techpubs.jurassic.nl/manpages_0530/cat5/uopt.html"><code>uopt</code> documentation</a>
|
|
||||||
says that eligible loops were unrolled four times by default.
|
|
||||||
Coincidentally, four is also the number of racers in the game. Loops
|
|
||||||
with four iterations are therefore candidates for complete unrolling,
|
|
||||||
and they occur frequently 🫠. <a href="#fnref:7" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:8"><p>Measured from the <a href="https://github.com/gcsmith/Pilotwings64Decomp/commit/64ae65fbc0dcbb1de772361b1dd84faf0816dc56">addition of progress tracking on 26 January 2026</a> to the <a href="https://github.com/gcsmith/Pilotwings64Decomp/commit/c041f5190e232988fec0cd17b1a5e6ad9182c25d">final matching functions on 10 April 2026</a>, the decompilation took almost exactly 74 days. <em>Pilotwings 64</em> had 1,791 functions compared with 2,145 for <em>Snowboard Kids</em>, but approximately 838 KB of code compared with 732 KB for <em>Snowboard Kids</em>. <a href="#fnref:8" class="footnote-backref" role="doc-backlink">↩︎</a></p></li><li id="fn:9"><p>Using a one-shot script like this is not a particularly novel idea. I’m pretty sure I copied it from somewhere, possibly <em>Diddy Kong Racing</em>’s <a href="https://github.com/DavidSM64/Diddy-Kong-Racing/blob/84f0ea569b07903ba8a4f9f252e8dc8e4ba54bdc/m2c_all.sh">m2c_all.sh</a>. <a href="#fnref:9" class="footnote-backref" role="doc-backlink">↩︎</a></p></li></ol></div></content><section class="bluesky-comments" data-bluesky-post="https://bsky.app/profile/did:plc:5j7epmcl35kl5pni4y6ehpl2/post/3mu34bwrh4k2m"><h2>Comments</h2><p class="bluesky-comments__status">Loading Bluesky comments...</p></section><script defer="defer" src="Decompiling%20a%20Nintendo%2064%20Game%20in%2084%20Days%20_%20Chris'%20Blog_files/bluesky-comments_aOx2.js"></script><div style="font-size:.8em;display:flex;gap:16px;justify-content:center"><p><a href="https://blog.chrislewis.au/snowboard-kids-2-is-recompiled/"><< Previous Post</a></p><p>|</p><p><strike>Next Post >></strike></p></div><p></p></main><footer></footer><script type="module" src="Decompiling%20a%20Nintendo%2064%20Game%20in%2084%20Days%20_%20Chris'%20Blog_files/v3d52b47920f24c319d37e2661827c42b1787588026925_aOx2" integrity="sha512-d9sL6GJLXn6fInD1+TVXhTcQOsmxeHfmHAvwGDIxp5TO+uo1fiWW7mHomMj4MLRlCsJDTqXzWLHJFFlPCEIj/A==" data-cf-beacon="{"version":"2024.11.0","token":"10d5aece9021411ab2fd8081c3b4e020","r":1}" crossorigin="anonymous"></script>
|
|
||||||
<div>
|
|
||||||
<div class="vtResetstyles">
|
|
||||||
<div id="vtoverlay" class="vtoverlay w3-animate-right w3-container vtfadein">
|
|
||||||
<div class="vtmain" id="vtmain">
|
|
||||||
<div class="vtcontactus">
|
|
||||||
<a id="linktest" href="https://www.virustotal.com/">
|
|
||||||
<img class="vtlogo-popup" style="
|
|
||||||
width: 24px;
|
|
||||||
height: 32px;
|
|
||||||
margin-right: 5px !important;
|
|
||||||
" src="Decompiling%20a%20Nintendo%2064%20Game%20in%2084%20Days%20_%20Chris'%20Blog_files/gti-vt-icon2_aOx2.png">
|
|
||||||
</a>
|
|
||||||
<a href="https://www.virustotal.com/">VT4Browsers + Google TI
|
|
||||||
</a>
|
|
||||||
<a id="vtclose" class="vtclose" style="margin-left: 15px;"><svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 24 24" fill="currentColor" style="vertical-align: middle">
|
|
||||||
<path d="M6.4 18.654 5.346 17.6l5.6-5.6-5.6-5.6L6.4 5.346l5.6 5.6 5.6-5.6L18.654 6.4l-5.6 5.6 5.6 5.6-1.054 1.054-5.6-5.6-5.6 5.6Z"></path>
|
|
||||||
</svg></a>
|
|
||||||
</div>
|
|
||||||
<div class="vttextpopup" id="statustext">
|
|
||||||
<div class="vtscanstatus" id="vtscanstatus">Fetching file...<br></div>
|
|
||||||
<div class="vtprogress" id="vtprogress"></div>
|
|
||||||
<br id="brfile" class="vthidden">
|
|
||||||
<div class="vtscanstatus vthidden" id="vtscanfilelink"></div>
|
|
||||||
</div>
|
|
||||||
<div class="vttextpopup vthidden" id="widgettext">
|
|
||||||
<div id="vtwidgetError"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="vt-augment-container" class="vtzindex vt-augment drawer" style="background: rgb(49, 61, 90); --darkreader-inline-bgcolor: var(--darkreader-background-313d5a, #273148); --darkreader-inline-bgimage: none;" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage=""></div></div><span class="vttooltiptext vthidden"> Click to open IoC report <br>with VirusTotal Augment</span><style></style><style class="darkreader darkreader--sync" media="screen"></style></body></html>
|
|
||||||
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 94 KiB |
@@ -4,8 +4,8 @@ This directory contains the PC port layer: the code that lets the (unmodified)
|
|||||||
GoldenEye game sources run on a modern host instead of N64 hardware.
|
GoldenEye game sources run on a modern host instead of N64 hardware.
|
||||||
|
|
||||||
It mirrors the layout of the [Perfect Dark PC
|
It mirrors the layout of the [Perfect Dark PC
|
||||||
port](https://github.com/perfect-dark-pc-port/perfect_dark), which ports the
|
port](https://github.com/fgsfdsfgs/perfect_dark), which ports the
|
||||||
same Rare engine family. See `docs/PCPortResearch.md` (repo root) for the full
|
same Rare engine family. See `docs/internals.md` (repo root) for the full
|
||||||
research write-up and phased plan.
|
research write-up and phased plan.
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
@@ -66,5 +66,5 @@ exact command encodings.
|
|||||||
|
|
||||||
**Scaffolding only.** The files in `src/` and `fast3d/` are stubs that define
|
**Scaffolding only.** The files in `src/` and `fast3d/` are stubs that define
|
||||||
the interfaces and mark the work to be done. The actual implementation is
|
the interfaces and mark the work to be done. The actual implementation is
|
||||||
brought in phase by phase (see `docs/PCPortResearch.md` §8), largely by
|
brought in phase by phase (see `docs/internals.md` §8), largely by
|
||||||
adapting the PD port's `port/` layer.
|
adapting the PD port's `port/` layer.
|
||||||
@@ -1,51 +1,37 @@
|
|||||||
# fast3d — software RSP for the GE PC port
|
# fast3d — software RSP
|
||||||
|
|
||||||
This directory will hold the **software RSP**: a C++ interpreter that consumes
|
A C++ interpreter that consumes the `Gfx` display list the game builds each
|
||||||
the Gfx display list the game builds and emits OpenGL calls, bypassing the N64
|
frame and emits OpenGL calls, replacing the Nintendo 64's Reality Signal
|
||||||
RDP entirely.
|
Processor and Reality Display Processor.
|
||||||
|
|
||||||
## Source: adapt from the Perfect Dark PC port
|
## Origin and license
|
||||||
|
|
||||||
The PD port ships a complete, working fast3d at
|
This code is **adapted from the [Perfect Dark PC
|
||||||
`perfect-dark-pc-port/perfect_dark/port/fast3d/`:
|
port](https://github.com/fgsfdsfgs/perfect_dark)** (`port/fast3d/`), which in
|
||||||
|
turn is the [Ship of Harkinian](https://github.com/HarbourMasters) /
|
||||||
|
libultraship fast3d, itself descended from
|
||||||
|
[sm64-port](https://github.com/sm64-port/sm64-port).
|
||||||
|
|
||||||
| File | Role | ~Lines |
|
`LICENSE.txt` in this directory (MIT, © 2020 Emill, MaikelChan) covers that
|
||||||
|--------------------|-------------------------------------------------------------|--------|
|
lineage. `glad/` is generated by [glad](https://github.com/Dav1dde/glad)
|
||||||
| `gfx_pc.cpp` | The RSP interpreter: matrices, lights, fog, tri1/tri4, vtx | 2800 |
|
(© David Herberth, MIT). GoldenEye-specific changes in this directory are
|
||||||
| `gfx_opengl.cpp` | OpenGL backend: shaders, textures, zbuf, framebuffers | 1300 |
|
under the repository's top-level MIT license.
|
||||||
| `gfx_cc.cpp` | Color-combiner equation -> GLSL shader compiler | 60 |
|
|
||||||
| `gfx_sdl2.cpp` | SDL2 window-manager backend | 460 |
|
|
||||||
| `gfx_api.h` | Public API (`gfx_init/run/start_frame/end_frame/...`) | 58 |
|
|
||||||
| `gfx_pc.h` | Internal state (RSP struct, texture cache, combiner pool) | 60 |
|
|
||||||
| `gfx_rendering_api.h` | Rendering-API vtable (GL backend implements this) | 61 |
|
|
||||||
| `gfx_window_manager_api.h` | Window-manager vtable (SDL2 implements this) | 54 |
|
|
||||||
| `glad/` | GL loader | — |
|
|
||||||
|
|
||||||
**The PD fast3d already handles `G_TRI4`** (GE's 4-triangle command) — the PD
|
## Files
|
||||||
game uses it too. So the bulk of the RSP work is done; we adapt, not rewrite.
|
|
||||||
|
|
||||||
## GE-specific work to add
|
| File | Role |
|
||||||
|
|---|---|
|
||||||
|
| `gfx_pc.cpp` | the RSP interpreter — matrices, lights, fog, `G_TRI1`/`G_TRI4`, vtx |
|
||||||
|
| `gfx_opengl.cpp` | OpenGL backend — shaders, textures, z-buffer, framebuffers |
|
||||||
|
| `gfx_cc.cpp` | color-combiner equation -> GLSL shader |
|
||||||
|
| `gfx_sdl2.cpp` | SDL2 window-manager backend |
|
||||||
|
| `gfx_api.h` | public API (`gfx_init` / `gfx_run` / `gfx_start_frame` / ...) |
|
||||||
|
| `gbiex.h` | GoldenEye custom GBI command definitions |
|
||||||
|
| `glad/` | OpenGL loader |
|
||||||
|
|
||||||
1. **`G_SETTEX`** (opcode `0xc0`) — GE's custom texture-bank command
|
## GoldenEye-specific work
|
||||||
(`include/gbi_extension.h`, `gsSPUseTexture`). Add a case in the `gfx_pc.cpp`
|
|
||||||
dispatch and a corresponding texture-bank selection in the GL backend.
|
|
||||||
Reverse the exact semantics from `rsp/graphics/gmain.s` + `src/game/tex.c` /
|
|
||||||
`initmttex.c`.
|
|
||||||
2. **Verify custom color-combiner modes** — `G_CC_MODULATEIFADE`, `G_CC_FADE`,
|
|
||||||
`G_CC_DECALFADE`, `G_CC_BLENDRGBFADEA`, etc. The `gfx_cc.cpp` compiler
|
|
||||||
handles arbitrary CC equations; confirm each GE mode compiles to correct
|
|
||||||
GLSL.
|
|
||||||
3. **Verify the custom render mode** — `G_RM_CUSTOM_AA_ZB_XLU_SURF`
|
|
||||||
(AA_ZB_XLU with `Z_UPD`).
|
|
||||||
4. **`gDPLoadTLUT06/07`** — map onto the existing `G_LOADTLUT` path.
|
|
||||||
|
|
||||||
## Ground truth
|
The PD fast3d already handles `G_TRI4` (GoldenEye uses it too). The GE additions
|
||||||
|
are the custom color-combiner / render modes and the handling of GE's GBI
|
||||||
`rsp/graphics/gmain.s` (repo root) is the real GE RSP ucode. We do **not** run
|
extension commands. `rsp/graphics/gmain.s` (GE's real RSP microcode, in the
|
||||||
it on PC, but it is the authoritative reference for the exact command
|
repo root) is the ground truth for the exact command encodings.
|
||||||
encodings GE emits — use it to validate the software RSP's decoding.
|
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
**Empty (scaffolding).** Populate during Phase 2 by adapting the PD fast3d and
|
|
||||||
adding the GE-specific commands above.
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* GE adaptation header for fast3d (see docs/PCPortResearch.md).
|
* GE adaptation header for fast3d (see docs/internals.md).
|
||||||
*
|
*
|
||||||
* fast3d was written against Perfect Dark's extended GBI set
|
* fast3d was written against Perfect Dark's extended GBI set
|
||||||
* (pd_port/src/include/gbiex.h + PD's include/PR/gbi.h). GoldenEye shares the
|
* (pd_port/src/include/gbiex.h + PD's include/PR/gbi.h). GoldenEye shares the
|
||||||
|
|||||||
@@ -610,7 +610,7 @@ void gfx_texture_cache_delete_range(const uint8_t* start, const uint8_t* end) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// D71 (docs/PCPortResearch.md): texture sources arrive in two byte conventions.
|
// D71 (docs/internals.md): texture sources arrive in two byte conventions.
|
||||||
// Raw N64 big-endian byte streams: ROM cart map (0x10xxxxxx), model-sidecar
|
// Raw N64 big-endian byte streams: ROM cart map (0x10xxxxxx), model-sidecar
|
||||||
// blobs (cart extension 0x10Cxxxxx), KSEG0 mirror (0x80xxxxxx) and V1
|
// blobs (cart extension 0x10Cxxxxx), KSEG0 mirror (0x80xxxxxx) and V1
|
||||||
// dram/BSS/heap buffers (0x70xxxxxx, e.g. tex.c texture pool, rle_expand_8bit
|
// dram/BSS/heap buffers (0x70xxxxxx, e.g. tex.c texture pool, rle_expand_8bit
|
||||||
@@ -1287,7 +1287,7 @@ static void gfx_sp_vertex(size_t n_vertices, size_t dest_index, const Vtx* verti
|
|||||||
d->color.g = g > 255 ? 255 : g;
|
d->color.g = g > 255 ? 255 : g;
|
||||||
d->color.b = b > 255 ? 255 : b;
|
d->color.b = b > 255 ? 255 : b;
|
||||||
|
|
||||||
/* D72 (docs/PCPortResearch.md): unlike the PD port, GE never
|
/* D72 (docs/internals.md): unlike the PD port, GE never
|
||||||
* generates texture coordinates from vertex normals. This PD-
|
* generates texture coordinates from vertex normals. This PD-
|
||||||
* inherited block overwrote the authored tc[] UVs on every
|
* inherited block overwrote the authored tc[] UVs on every
|
||||||
* lit+textured surface (lookat_enabled defaults to true here),
|
* lit+textured surface (lookat_enabled defaults to true here),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#include <PR/ultratypes.h>
|
#include <PR/ultratypes.h>
|
||||||
|
|
||||||
/* D68 (docs/PCPortResearch.md): endianness fixup for the Globalimagetable
|
/* D68 (docs/internals.md): endianness fixup for the Globalimagetable
|
||||||
* segment after texReset()'s romCopy(). The N64 CPU is big-endian, so every
|
* segment after texReset()'s romCopy(). The N64 CPU is big-endian, so every
|
||||||
* u32 in the ROM copy is BE-encoded; PC consumers read them natively (LE).
|
* u32 in the ROM copy is BE-encoded; PC consumers read them natively (LE).
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
/* Generated by CMake from hoststdlib.h.in — do not edit.
|
/* Generated by CMake from hoststdlib.h.in — do not edit.
|
||||||
* Includes the HOST <stdlib.h> by absolute path so that port/shim/stdlib.h
|
* Includes the HOST <stdlib.h> by absolute path so that port/shim/stdlib.h
|
||||||
* can bypass the N64 include/stdlib.h (whose lldiv_t/ldiv_t typedefs clash
|
* can bypass the N64 include/stdlib.h (whose lldiv_t/ldiv_t typedefs clash
|
||||||
* with the host's in C++ TUs). See docs/PCPortResearch.md §11. */
|
* with the host's in C++ TUs). See docs/internals.md §11. */
|
||||||
#include "@PORT_HOST_STDLIB_H@"
|
#include "@PORT_HOST_STDLIB_H@"
|
||||||
#endif
|
#endif
|
||||||
@@ -4,6 +4,6 @@
|
|||||||
* Includes the HOST <string.h> by absolute path so that port/shim/string.h
|
* Includes the HOST <string.h> by absolute path so that port/shim/string.h
|
||||||
* can bypass the N64 include/string.h (which declares strlen/strchr with
|
* can bypass the N64 include/string.h (which declares strlen/strchr with
|
||||||
* `const unsigned char *` and conflicts with the host declarations that SDL
|
* `const unsigned char *` and conflicts with the host declarations that SDL
|
||||||
* and other system headers pull in). See docs/PCPortResearch.md §11 D12. */
|
* and other system headers pull in). See docs/internals.md §11 D12. */
|
||||||
#include "@PORT_HOST_STRING_H@"
|
#include "@PORT_HOST_STRING_H@"
|
||||||
#endif
|
#endif
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* pc_protos.h - PC-port-only prototypes for implicitly declared functions.
|
* pc_protos.h - PC-port-only prototypes for implicitly declared functions.
|
||||||
*
|
*
|
||||||
* D38 (docs/PCPortResearch.md section F): the decomp has ~72 translation units
|
* D38 (docs/internals.md section F): the decomp has ~72 translation units
|
||||||
* that call ~400 functions without any visible prototype (missing #include of
|
* that call ~400 functions without any visible prototype (missing #include of
|
||||||
* the declaring header). Under C11 an implicit declaration assumes `int f()`,
|
* the declaring header). Under C11 an implicit declaration assumes `int f()`,
|
||||||
* which on N64 (MIPS, 32-bit pointers) is harmless, but on x86-64 it silently
|
* which on N64 (MIPS, 32-bit pointers) is harmless, but on x86-64 it silently
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* D69 (docs/PCPortResearch.md): PC-layout stage bg/*.seg + Tbg_*_stanZ
|
/* D69 (docs/internals.md): PC-layout stage bg/*.seg + Tbg_*_stanZ
|
||||||
* sidecars -- same Plan-B pattern as D50/pcmodels.c.
|
* sidecars -- same Plan-B pattern as D50/pcmodels.c.
|
||||||
*
|
*
|
||||||
* N64 bg/*.seg and Tbg_*_stanZ files are big-endian images with internal
|
* N64 bg/*.seg and Tbg_*_stanZ files are big-endian images with internal
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* D50 (docs/PCPortResearch.md): PC-layout model sidecars — Plan B (D48/D49).
|
/* D50 (docs/internals.md): PC-layout model sidecars — Plan B (D48/D49).
|
||||||
*
|
*
|
||||||
* N64 ROM model files are 32-bit-pointer big-endian images the PC build
|
* N64 ROM model files are 32-bit-pointer big-endian images the PC build
|
||||||
* cannot read in place (D37/D43). tools_pc/d43_emit.py converts all of them
|
* cannot read in place (D37/D43). tools_pc/d43_emit.py converts all of them
|
||||||
|
|||||||
@@ -5,6 +5,6 @@
|
|||||||
* the real zlib (inflateInit2/inflate) without picking up the game's own
|
* the real zlib (inflateInit2/inflate) without picking up the game's own
|
||||||
* src/game/zlib.h, which sits on the -I search path and shadows a plain
|
* src/game/zlib.h, which sits on the -I search path and shadows a plain
|
||||||
* `#include <zlib.h>` in a port TU. Same absolute-path trick as hoststring.h.
|
* `#include <zlib.h>` in a port TU. Same absolute-path trick as hoststring.h.
|
||||||
* See docs/PCPortResearch.md §F (D32). */
|
* See docs/internals.md §F (D32). */
|
||||||
#include "@PORT_REAL_ZLIB_H@"
|
#include "@PORT_REAL_ZLIB_H@"
|
||||||
#endif
|
#endif
|
||||||
@@ -37,7 +37,7 @@ u32 romdataGetRomSize(void);
|
|||||||
int romdataCartAddrValid(u32 addr, u32 size);
|
int romdataCartAddrValid(u32 addr, u32 size);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* D33 (docs/PCPortResearch.md): the .z64 file stores structured multi-byte
|
* D33 (docs/internals.md): the .z64 file stores structured multi-byte
|
||||||
* fields in big-endian byte order (bit-packed streams are raw). Per-field
|
* fields in big-endian byte order (bit-packed streams are raw). Per-field
|
||||||
* BE->LE transform of the animation data segment after romCopy() and before
|
* BE->LE transform of the animation data segment after romCopy() and before
|
||||||
* expand_ani_table_entries(): u32 -> bswap32, u16 -> bswap16, u8 -> identity.
|
* expand_ani_table_entries(): u32 -> bswap32, u16 -> bswap16, u8 -> identity.
|
||||||
@@ -52,7 +52,7 @@ void romdataFixupAnimationData(u8 *blob, u32 blobSize,
|
|||||||
const s32 *tableA, const s32 *tableB);
|
const s32 *tableA, const s32 *tableB);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* D35 (docs/PCPortResearch.md): the music sequence table segment ( RareALSeqBankFile )
|
* D35 (docs/internals.md): the music sequence table segment ( RareALSeqBankFile )
|
||||||
* stores its fields big-endian: +0 u16 seqCount, then 8-byte entries at +4:
|
* stores its fields big-endian: +0 u16 seqCount, then 8-byte entries at +4:
|
||||||
* u32 address offset, u16 uncompressed_len, u16 len. Per-field BE->LE
|
* u32 address offset, u16 uncompressed_len, u16 len. Per-field BE->LE
|
||||||
* transform after romCopy(). Entries are decoded only as far as blobSize
|
* transform after romCopy(). Entries are decoded only as far as blobSize
|
||||||
@@ -62,21 +62,21 @@ void romdataFixupAnimationData(u8 *blob, u32 blobSize,
|
|||||||
void romdataFixupMusicSeqTable(u8 *blob, u32 blobSize);
|
void romdataFixupMusicSeqTable(u8 *blob, u32 blobSize);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* D54 (docs/PCPortResearch.md): decode a decompressed compact-sequence file's
|
* D54 (docs/internals.md): decode a decompressed compact-sequence file's
|
||||||
* big-endian ALCMidiHdr in place (16 x u32 trackOffset + u32 division;
|
* big-endian ALCMidiHdr in place (16 x u32 trackOffset + u32 division;
|
||||||
* see romdata.c). Call after decompressdata(), before alCSeqNew().
|
* see romdata.c). Call after decompressdata(), before alCSeqNew().
|
||||||
*/
|
*/
|
||||||
void romdataFixupCseq(u8 *blob);
|
void romdataFixupCseq(u8 *blob);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* D50 (docs/PCPortResearch.md): decode a language bank's big-endian offset
|
* D50 (docs/internals.md): decode a language bank's big-endian offset
|
||||||
* table in place ([u32 offsets x N][text]; see romdata.c). blobSize is the
|
* table in place ([u32 offsets x N][text]; see romdata.c). blobSize is the
|
||||||
* decompressed bank size (resource_lookup_data_array[].poolRemaining).
|
* decompressed bank size (resource_lookup_data_array[].poolRemaining).
|
||||||
*/
|
*/
|
||||||
void romdataFixupLangBank(u8 *blob, u32 blobSize);
|
void romdataFixupLangBank(u8 *blob, u32 blobSize);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* D50 (docs/PCPortResearch.md): re-lay out a font segment (struct font:
|
* D50 (docs/internals.md): re-lay out a font segment (struct font:
|
||||||
* kerning[169] + chars[94] + glyph pixel data) from the N64 ROM layout to
|
* kerning[169] + chars[94] + glyph pixel data) from the N64 ROM layout to
|
||||||
* the PC C layout, shifting the pixel block below the expanded char array.
|
* the PC C layout, shifting the pixel block below the expanded char array.
|
||||||
* src/n64Size locate the N64 image (ROM segment); the caller allocates
|
* src/n64Size locate the N64 image (ROM segment); the caller allocates
|
||||||
@@ -88,7 +88,7 @@ u32 romdataFontPcSize(const u8 *src, u32 n64Size);
|
|||||||
void romdataFixupFont(u8 *blob, u32 n64Size);
|
void romdataFixupFont(u8 *blob, u32 n64Size);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* D37 (docs/PCPortResearch.md): the libaudio SFX/instrument bank segments
|
* D37 (docs/internals.md): the libaudio SFX/instrument bank segments
|
||||||
* (ALBankFile trees: ALBank -> ALInstrument -> ALSound -> ALWaveTable /
|
* (ALBankFile trees: ALBank -> ALInstrument -> ALSound -> ALWaveTable /
|
||||||
* ALEnvelope / ALKeyMap / ALADPCMloop / ALADPCMBook) are serialized in ROM
|
* ALEnvelope / ALKeyMap / ALADPCMloop / ALADPCMBook) are serialized in ROM
|
||||||
* as big-endian scalars with 4-byte packed table-relative offsets where the
|
* as big-endian scalars with 4-byte packed table-relative offsets where the
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* PC port shim for include/CPPLib.h (see docs/PCPortResearch.md §11 D9).
|
* PC port shim for include/CPPLib.h (see docs/internals.md §11 D9).
|
||||||
*
|
*
|
||||||
* CPPLib's emptiness test pastes its argument into a macro name:
|
* CPPLib's emptiness test pastes its argument into a macro name:
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* PC port shim for PR/R4300.h (see docs/PCPortResearch.md).
|
* PC port shim for PR/R4300.h (see docs/internals.md).
|
||||||
*
|
*
|
||||||
* On the N64, PHYS_TO_K0 turns a physical address into a KSEG0 virtual
|
* On the N64, PHYS_TO_K0 turns a physical address into a KSEG0 virtual
|
||||||
* address by OR'ing in 0x80000000. On the PC port the game's working RAM
|
* address by OR'ing in 0x80000000. On the PC port the game's working RAM
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* PC port shim for PR/gbi.h (see docs/PCPortResearch.md).
|
* PC port shim for PR/gbi.h (see docs/internals.md).
|
||||||
*
|
*
|
||||||
* The real header (include/PR/gbi.h) already has 64-bit-ready Gwords and
|
* The real header (include/PR/gbi.h) already has 64-bit-ready Gwords and
|
||||||
* gSP* macros that pack full 64-bit pointers into words.w1. However, the
|
* gSP* macros that pack full 64-bit pointers into words.w1. However, the
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* PC port shim for PR/os.h — DRAM address conversions (see docs/PCPortResearch.md).
|
* PC port shim for PR/os.h — DRAM address conversions (see docs/internals.md).
|
||||||
*
|
*
|
||||||
* The game's working RAM lives in the s32-safe DRAM view at 0x70000000
|
* The game's working RAM lives in the s32-safe DRAM view at 0x70000000
|
||||||
* ("V1"), with a byte-identical KSEG0 mirror at 0x80000000 ("V2"); see
|
* ("V1"), with a byte-identical KSEG0 mirror at 0x80000000 ("V2"); see
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* PC port shim for bondaicommands.h (see docs/PCPortResearch.md §11 D7).
|
* PC port shim for bondaicommands.h (see docs/internals.md §11 D7).
|
||||||
*
|
*
|
||||||
* The real header is src/bondaicommands.h. It was written for the N64/IDO
|
* The real header is src/bondaicommands.h. It was written for the N64/IDO
|
||||||
* preprocessor, which is more lenient than GCC in two ways that break the
|
* preprocessor, which is more lenient than GCC in two ways that break the
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
* marker that produces an obvious error if new game code ever uses it here.
|
* marker that produces an obvious error if new game code ever uses it here.
|
||||||
*/
|
*/
|
||||||
#undef SWITCH
|
#undef SWITCH
|
||||||
#define SWITCH(...) _PORT_SWITCH_NOT_SUPPORTED_ON_PORT /* D7: use a #ifdef PORT hand-written equivalent in chraidata.c style; see docs/PCPortResearch.md §11 */
|
#define SWITCH(...) _PORT_SWITCH_NOT_SUPPORTED_ON_PORT /* D7: use a #ifdef PORT hand-written equivalent in chraidata.c style; see docs/internals.md §11 */
|
||||||
|
|
||||||
/* --- D11: CALL() without the double trailing comma ------------------------
|
/* --- D11: CALL() without the double trailing comma ------------------------
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* PC port shim for bondconstants.h (see docs/PCPortResearch.md).
|
* PC port shim for bondconstants.h (see docs/internals.md).
|
||||||
*
|
*
|
||||||
* The real header is src/bondconstants.h. Its BITFLAG macro is only defined
|
* The real header is src/bondconstants.h. Its BITFLAG macro is only defined
|
||||||
* under `#ifdef __sgi` (the N64/IDO compiler); on other compilers it is a
|
* under `#ifdef __sgi` (the N64/IDO compiler); on other compilers it is a
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* PC port shim for bondtypes.h (see docs/PCPortResearch.md).
|
* PC port shim for bondtypes.h (see docs/internals.md).
|
||||||
*
|
*
|
||||||
* The real header is src/bondtypes.h. It includes game/chrobjdata.h at line 31,
|
* The real header is src/bondtypes.h. It includes game/chrobjdata.h at line 31,
|
||||||
* but chrobjdata.h declares extern arrays of ItemModelFileRecord /
|
* but chrobjdata.h declares extern arrays of ItemModelFileRecord /
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
* rejects `New_Vector()` against a 3-parameter macro ("requires 3 arguments,
|
* rejects `New_Vector()` against a 3-parameter macro ("requires 3 arguments,
|
||||||
* but only 1 given"). The game code only ever calls them with 0 args (to make
|
* but only 1 given"). The game code only ever calls them with 0 args (to make
|
||||||
* a zero vector/coord), so redefine to accept any arg count and expand to a
|
* a zero vector/coord), so redefine to accept any arg count and expand to a
|
||||||
* zero initializer. See docs/PCPortResearch.md. */
|
* zero initializer. See docs/internals.md. */
|
||||||
#undef New_Vector
|
#undef New_Vector
|
||||||
#undef New_Coord3d
|
#undef New_Coord3d
|
||||||
#define New_Vector(...) {0, 0, 0}
|
#define New_Vector(...) {0, 0, 0}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
* `#include <stdlib.h>`, which resolves to the N64 header via -I), and the
|
* `#include <stdlib.h>`, which resolves to the N64 header via -I), and the
|
||||||
* lldiv_t/ldiv_t typedefs then conflict with the host's. The shim routes C++
|
* lldiv_t/ldiv_t typedefs then conflict with the host's. The shim routes C++
|
||||||
* TUs to the host header by absolute path (generated hoststdlib.h, same
|
* TUs to the host header by absolute path (generated hoststdlib.h, same
|
||||||
* trick as port/shim/string.h — see docs/PCPortResearch.md §11 D12).
|
* trick as port/shim/string.h — see docs/internals.md §11 D12).
|
||||||
*
|
*
|
||||||
* Inert in the N64 build (port/shim is not on its include path).
|
* Inert in the N64 build (port/shim is not on its include path).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* PC port shim for include/string.h (see docs/PCPortResearch.md §11 D12).
|
* PC port shim for include/string.h (see docs/internals.md §11 D12).
|
||||||
*
|
*
|
||||||
* The N64 header declares:
|
* The N64 header declares:
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* gimgfixup.c - D68 (docs/PCPortResearch.md): Globalimagetable endianness
|
* gimgfixup.c - D68 (docs/internals.md): Globalimagetable endianness
|
||||||
* fixup for the PC port. See port/include/gimgfixup.h for the rationale.
|
* fixup for the PC port. See port/include/gimgfixup.h for the rationale.
|
||||||
*
|
*
|
||||||
* The offset tables below must stay in lockstep with the g_pc_gimg_off_*
|
* The offset tables below must stay in lockstep with the g_pc_gimg_off_*
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* libultra.c needs.
|
* libultra.c needs.
|
||||||
*
|
*
|
||||||
* ------------------------------------------------------------------------
|
* ------------------------------------------------------------------------
|
||||||
* BINDING SCHEME (documented in docs/PCPortResearch.md sec F "D118")
|
* BINDING SCHEME (documented in docs/internals.md sec F "D118")
|
||||||
*
|
*
|
||||||
* GE's default "1.1" control style: analog stick = move/strafe, the four
|
* GE's default "1.1" control style: analog stick = move/strafe, the four
|
||||||
* C-buttons = aim/turn/look (DIGITAL on N64), R = aim mode, Z = fire.
|
* C-buttons = aim/turn/look (DIGITAL on N64), R = aim mode, Z = fire.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
* osRecvMesg() waits on a per-queue condition variable. (An earlier
|
* osRecvMesg() waits on a per-queue condition variable. (An earlier
|
||||||
* setjmp/longjmp green-thread kernel corrupted FPU state across longjmps
|
* setjmp/longjmp green-thread kernel corrupted FPU state across longjmps
|
||||||
* on Windows x64/MinGW — synthetic STATUS_FLOATING_POINT_INVALID_OPERATION
|
* on Windows x64/MinGW — synthetic STATUS_FLOATING_POINT_INVALID_OPERATION
|
||||||
* crashes at PC=0; see docs/PCPortResearch.md.)
|
* crashes at PC=0; see docs/internals.md.)
|
||||||
* - RSP tasks (osSpTaskStartGo) run the software RSP (fast3d) inline and
|
* - RSP tasks (osSpTaskStartGo) run the software RSP (fast3d) inline and
|
||||||
* post the RSP_DONE / RDP_DONE messages sched.c waits for.
|
* post the RSP_DONE / RDP_DONE messages sched.c waits for.
|
||||||
* - VI retrace is a vsync-paced tick posted by a dedicated kernel thread,
|
* - VI retrace is a vsync-paced tick posted by a dedicated kernel thread,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
* 4. start the thread kernel and run the game's mainproc() as a real OS
|
* 4. start the thread kernel and run the game's mainproc() as a real OS
|
||||||
* thread (it IS the N64 mainThread) — which runs bossEntry(), the real
|
* thread (it IS the N64 mainThread) — which runs bossEntry(), the real
|
||||||
* game loop. The game's own scheduler (src/sched.c) drives frames; see
|
* game loop. The game's own scheduler (src/sched.c) drives frames; see
|
||||||
* docs/PCPortResearch.md.
|
* docs/internals.md.
|
||||||
* 5. the host main thread then owns SDL event pumping for the lifetime of
|
* 5. the host main thread then owns SDL event pumping for the lifetime of
|
||||||
* the process (Windows only dispatches window messages to the creating
|
* the process (Windows only dispatches window messages to the creating
|
||||||
* thread, and every game thread can be blocked on a queue).
|
* thread, and every game thread can be blocked on a queue).
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* N64-boot-specific symbol stubs.
|
* N64-boot-specific symbol stubs.
|
||||||
*
|
*
|
||||||
* We compile src/init.c (for mainproc() — see A3 in docs/PCPortResearch.md),
|
* We compile src/init.c (for mainproc() — see A3 in docs/internals.md),
|
||||||
* but its N64-only init() is never called on the PC. The compiler still needs
|
* but its N64-only init() is never called on the PC. The compiler still needs
|
||||||
* the symbols init() references to resolve at link time, so this file provides
|
* the symbols init() references to resolve at link time, so this file provides
|
||||||
* inert stubs for the ones that are NOT part of the libultra OS API (those
|
* inert stubs for the ones that are NOT part of the libultra OS API (those
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* pc_netorder.c - D38: host byte-order helpers (see docs/PCPortResearch.md).
|
* pc_netorder.c - D38: host byte-order helpers (see docs/internals.md).
|
||||||
*
|
*
|
||||||
* src/bondconstants.h implements ntohl()/ntohs() as function-like macros over
|
* src/bondconstants.h implements ntohl()/ntohs() as function-like macros over
|
||||||
* CharArrayTo16/32 ("rewrite these to use char array as system provided funcs
|
* CharArrayTo16/32 ("rewrite these to use char array as system provided funcs
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ uint32_t pccgReserveSize(const uint8_t *romImg)
|
|||||||
if (!pccgFindDataFile(rel, s_binPath, sizeof(s_binPath))) {
|
if (!pccgFindDataFile(rel, s_binPath, sizeof(s_binPath))) {
|
||||||
sysLogPrintf(LOG_WARNING,
|
sysLogPrintf(LOG_WARNING,
|
||||||
"pccg: %s not found -- stage loads will fault; run "
|
"pccg: %s not found -- stage loads will fault; run "
|
||||||
"\"python tools_pc/d69_emit.py\" (see docs/HANDOFF.md)",
|
"\"python tools_pc/d69_emit.py\" (see docs/building.md)",
|
||||||
rel);
|
rel);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ uint32_t pcmodelsReserveSize(const uint8_t *romImg)
|
|||||||
if (!pcmodelsFindDataFile(rel, s_binPath, sizeof(s_binPath))) {
|
if (!pcmodelsFindDataFile(rel, s_binPath, sizeof(s_binPath))) {
|
||||||
sysLogPrintf(LOG_WARNING,
|
sysLogPrintf(LOG_WARNING,
|
||||||
"pcmodels: %s not found — model loads will fail; run "
|
"pcmodels: %s not found — model loads will fail; run "
|
||||||
"\"python tools_pc/d43_emit.py\" (see docs/HANDOFF.md)",
|
"\"python tools_pc/d43_emit.py\" (see docs/building.md)",
|
||||||
rel);
|
rel);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ static u8 *rom = NULL; /* heap copy (fallback path) */
|
|||||||
static u32 romSize = 0;
|
static u32 romSize = 0;
|
||||||
static int mappedAtCartBase = 0;
|
static int mappedAtCartBase = 0;
|
||||||
|
|
||||||
/* D34 (docs/PCPortResearch.md): PE image base, low 32 bits zero. On N64 the
|
/* D34 (docs/internals.md): PE image base, low 32 bits zero. On N64 the
|
||||||
* animation_data segment links at VMA 0, so game code computes record
|
* animation_data segment links at VMA 0, so game code computes record
|
||||||
* addresses as `(s32)&ANIM_DATA_x + ptr_animation_table` where &ANIM_DATA_x
|
* addresses as `(s32)&ANIM_DATA_x + ptr_animation_table` where &ANIM_DATA_x
|
||||||
* is a small segment offset. A PE image cannot be placed at a low address,
|
* is a small segment offset. A PE image cannot be placed at a low address,
|
||||||
@@ -184,7 +184,7 @@ int romdataInit(void)
|
|||||||
pcmodelsLoadSidecars(CART_BASE, romSize);
|
pcmodelsLoadSidecars(CART_BASE, romSize);
|
||||||
pccgLoadSidecars(CART_BASE + romSize + pcmodelsTotalSize());
|
pccgLoadSidecars(CART_BASE + romSize + pcmodelsTotalSize());
|
||||||
|
|
||||||
/* D55 (docs/PCPortResearch.md): the RLE folder-menu background at
|
/* D55 (docs/internals.md): the RLE folder-menu background at
|
||||||
* `unknown2` (romassets_<r>.s) is stored in the ROM with a
|
* `unknown2` (romassets_<r>.s) is stored in the ROM with a
|
||||||
* big-endian w/h header (01 B8 01 2B = 440x299), but
|
* big-endian w/h header (01 B8 01 2B = 440x299), but
|
||||||
* rle_expand_8bit() reads it little-endian. The N64 build embeds
|
* rle_expand_8bit() reads it little-endian. The N64 build embeds
|
||||||
@@ -269,7 +269,7 @@ const void *romdataMapVa(u32 va)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* D33 (docs/PCPortResearch.md): the .z64 file stores structured multi-byte
|
* D33 (docs/internals.md): the .z64 file stores structured multi-byte
|
||||||
* fields in big-endian byte order; bit-packed data streams are raw. No single
|
* fields in big-endian byte order; bit-packed data streams are raw. No single
|
||||||
* uniform word transform exists (mixed field widths), so the animation_data
|
* uniform word transform exists (mixed field widths), so the animation_data
|
||||||
* segment is converted per field at load time, after romCopy() and before
|
* segment is converted per field at load time, after romCopy() and before
|
||||||
@@ -356,7 +356,7 @@ void romdataFixupAnimationData(u8 *blob, u32 blobSize,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* D35 (docs/PCPortResearch.md): the music sequence table segment stores its
|
* D35 (docs/internals.md): the music sequence table segment stores its
|
||||||
* fields big-endian:
|
* fields big-endian:
|
||||||
*
|
*
|
||||||
* header at blob+0 (RareALSeqBankFile):
|
* header at blob+0 (RareALSeqBankFile):
|
||||||
@@ -399,7 +399,7 @@ void romdataFixupMusicSeqTable(u8 *blob, u32 blobSize)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* D54 (docs/PCPortResearch.md): a decompressed compact-sequence (cseq) file
|
* D54 (docs/internals.md): a decompressed compact-sequence (cseq) file
|
||||||
* starts with struct ALCMidiHdr — 16 big-endian u32 trackOffset values plus a
|
* starts with struct ALCMidiHdr — 16 big-endian u32 trackOffset values plus a
|
||||||
* big-endian u32 division. N64 reads them natively; on a little-endian host
|
* big-endian u32 division. N64 reads them natively; on a little-endian host
|
||||||
* alCSeqNew() byte-swaps them (track 0 offset 0x44 becomes 0x44000000),
|
* alCSeqNew() byte-swaps them (track 0 offset 0x44 becomes 0x44000000),
|
||||||
@@ -416,7 +416,7 @@ void romdataFixupCseq(u8 *blob)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* D50 (docs/PCPortResearch.md): a language bank is [u32 offsets x N][text].
|
* D50 (docs/internals.md): a language bank is [u32 offsets x N][text].
|
||||||
* Offsets are big-endian and point into the text region; langGet() reads them
|
* Offsets are big-endian and point into the text region; langGet() reads them
|
||||||
* little-endian on PC and would build garbage pointers. Scan from the front
|
* little-endian on PC and would build garbage pointers. Scan from the front
|
||||||
* and bswap word i while bswap32(word_i) is a plausible offset (strictly past
|
* and bswap word i while bswap32(word_i) is a plausible offset (strictly past
|
||||||
@@ -438,7 +438,7 @@ void romdataFixupLangBank(u8 *blob, u32 blobSize)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* D50 (docs/PCPortResearch.md): font segments (Zurich Bold, Bank Gothic)
|
* D50 (docs/internals.md): font segments (Zurich Bold, Bank Gothic)
|
||||||
* serialize `struct font` in the N64 layout — big-endian scalars, 4-byte
|
* serialize `struct font` in the N64 layout — big-endian scalars, 4-byte
|
||||||
* pointers:
|
* pointers:
|
||||||
* [s32 kerning x 169][fontchar x 94 @ stride 24][glyph pixel data]
|
* [s32 kerning x 169][fontchar x 94 @ stride 24][glyph pixel data]
|
||||||
@@ -524,7 +524,7 @@ void romdataFixupFont(u8 *blob, u32 n64Size)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* D37 (docs/PCPortResearch.md): convert a ROM-serialized libaudio bank tree
|
* D37 (docs/internals.md): convert a ROM-serialized libaudio bank tree
|
||||||
* (ALBankFile -> ALBank -> ALInstrument -> ALSound -> ALWaveTable /
|
* (ALBankFile -> ALBank -> ALInstrument -> ALSound -> ALWaveTable /
|
||||||
* ALEnvelope / ALKeyMap / ALADPCMloop / ALRawLoop / ALADPCMBook) to
|
* ALEnvelope / ALKeyMap / ALADPCMloop / ALRawLoop / ALADPCMBook) to
|
||||||
* PC-native form. See the header for the contract.
|
* PC-native form. See the header for the contract.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* PC-port replacement for src/game/decompress.c + src/game/zlib.c, which are
|
* PC-port replacement for src/game/decompress.c + src/game/zlib.c, which are
|
||||||
* EXCLUDED from the build (see CMakeLists.txt and docs/PCPortResearch.md §F D32).
|
* EXCLUDED from the build (see CMakeLists.txt and docs/internals.md §F D32).
|
||||||
*
|
*
|
||||||
* GE's RZ files are: a 2-byte header (0x11 0x72) followed by a raw deflate
|
* GE's RZ files are: a 2-byte header (0x11 0x72) followed by a raw deflate
|
||||||
* stream that ends with an end-of-block marker. The original decompressdata()
|
* stream that ends with an end-of-block marker. The original decompressdata()
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ void videoEndFrame(void)
|
|||||||
}
|
}
|
||||||
gfx_end_frame();
|
gfx_end_frame();
|
||||||
|
|
||||||
/* TEMP D70 (env-gated, strip per HANDOFF Task 3): GE_PCDUMP="first-last"
|
/* TEMP D70 (env-gated, debug-only, env-gated): GE_PCDUMP="first-last"
|
||||||
* or "first-last:step" dumps the presented frame as ./ppm/frame_NNNNNN.ppm
|
* or "first-last:step" dumps the presented frame as ./ppm/frame_NNNNNN.ppm
|
||||||
* (one frame behind real time — read happens after SwapWindow). Used to
|
* (one frame behind real time — read happens after SwapWindow). Used to
|
||||||
* pixel-verify the intro's 3D content. Also honours [Debug] FrameDump in
|
* pixel-verify the intro's 3D content. Also honours [Debug] FrameDump in
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
# Goldeneye 007
|
|
||||||
|
|
||||||
[![NTSC-Status][NTCS-badge]][NTCS-link]
|
|
||||||
[![JP-Status][JP-badge]][JP-link]
|
|
||||||
[![PAL-Status][PAL-badge]][PAL-link]
|
|
||||||
|
|
||||||
[NTCS-link]: https://kholdfuzion.github.io/goldeneyestatus/
|
|
||||||
[NTCS-badge]: ../../workflows/NTSC-Status/badge.svg
|
|
||||||
|
|
||||||
[JP-link]: https://kholdfuzion.github.io/goldeneyestatus/JPN.htm
|
|
||||||
[JP-badge]: ../../workflows/JP-Status/badge.svg
|
|
||||||
|
|
||||||
[PAL-link]: https://kholdfuzion.github.io/goldeneyestatus/EU.htm
|
|
||||||
[PAL-badge]: ../../workflows/EU-Status/badge.svg
|
|
||||||
|
|
||||||
## About this repository
|
|
||||||
|
|
||||||
This is a WIP decompilation of Goldeneye 007!
|
|
||||||
|
|
||||||
It builds the following ROMs:
|
|
||||||
|
|
||||||
* ge007.u.z64 `sha1: abe01e4aeb033b6c0836819f549c791b26cfde83`
|
|
||||||
* ge007.j.z64 `sha1: 2a5dade32f7fad6c73c659d2026994632c1b3174`
|
|
||||||
* ge007.e.z64 `sha1: 167c3c433dec1f1eb921736f7d53fac8cb45ee31`
|
|
||||||
|
|
||||||
**Note: This repository does not include all assets necessary for compiling the ROMs. A prior copy of the game is required to extract the assets.**
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
* [Setup Guide:](./docs/SetupGuide.md) useful information about installing the necessary dependencies and how to use it
|
|
||||||
* [Structure Guide:](./docs/StructureGuide.md) learn more about the project structure of this repository
|
|
||||||
* [Style Guide:](./docs/StyleGuide.md) code style conventions if you want to contribute code
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""One-shot D38 generator for port/include/pc_protos.h (see docs/PCPortResearch.md §F/D38).
|
"""One-shot D38 generator for port/include/pc_protos.h (see docs/internals.md §F/D38).
|
||||||
|
|
||||||
Pipeline (all inputs were captured from a PC build of the decomp):
|
Pipeline (all inputs were captured from a PC build of the decomp):
|
||||||
1. Compile the game TUs and collect `implicit function declaration` warnings
|
1. Compile the game TUs and collect `implicit function declaration` warnings
|
||||||
@@ -50,7 +50,7 @@ for n in sorted(names):
|
|||||||
hdr = '''/*
|
hdr = '''/*
|
||||||
* pc_protos.h - PC-port-only prototypes for implicitly declared functions.
|
* pc_protos.h - PC-port-only prototypes for implicitly declared functions.
|
||||||
*
|
*
|
||||||
* D38 (docs/PCPortResearch.md section F): the decomp has ~72 translation units
|
* D38 (docs/internals.md section F): the decomp has ~72 translation units
|
||||||
* that call ~400 functions without any visible prototype (missing #include of
|
* that call ~400 functions without any visible prototype (missing #include of
|
||||||
* the declaring header). Under C11 an implicit declaration assumes `int f()`,
|
* the declaring header). Under C11 an implicit declaration assumes `int f()`,
|
||||||
* which on N64 (MIPS, 32-bit pointers) is harmless, but on x86-64 it silently
|
* which on N64 (MIPS, 32-bit pointers) is harmless, but on x86-64 it silently
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ def build_segments(region, csv, obseg_resolved, ramrom_files, music_tracks,
|
|||||||
off, size = csv1("animationtable_data.bin")
|
off, size = csv1("animationtable_data.bin")
|
||||||
seg["animation_data"] = (off, size, True)
|
seg["animation_data"] = (off, size, True)
|
||||||
off, size = csv1("Globalimagetable.bin")
|
off, size = csv1("Globalimagetable.bin")
|
||||||
# D39 (docs/PCPortResearch.md): the CSV asset is truncated to the 17 Gfx
|
# D39 (docs/internals.md): the CSV asset is truncated to the 17 Gfx
|
||||||
# display lists (0xAC8). The N64 linker segment (ge007.ld) places ALL of
|
# display lists (0xAC8). The N64 linker segment (ge007.ld) places ALL of
|
||||||
# oddtextures.o (.data) here — Gfx DLs + 32 sImageTableEntry tables =
|
# oddtextures.o (.data) here — Gfx DLs + 32 sImageTableEntry tables =
|
||||||
# 0x13F8 bytes. texReset() copies End-Start, so the marker must span the
|
# 0x13F8 bytes. texReset() copies End-Start, so the marker must span the
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
to PC layout and write a concatenated sidecar image + manifest.csv (same
|
to PC layout and write a concatenated sidecar image + manifest.csv (same
|
||||||
shape/pattern as tools_pc/d43_emit.py / port/src/pcmodels.c).
|
shape/pattern as tools_pc/d43_emit.py / port/src/pcmodels.c).
|
||||||
|
|
||||||
Format spec: docs/PCPortResearch.md D78-D82. Summary:
|
Format spec: docs/internals.md D78-D82. Summary:
|
||||||
|
|
||||||
bg/*.seg (raw, uncompressed on ROM; stays uncompressed on PC):
|
bg/*.seg (raw, uncompressed on ROM; stays uncompressed on PC):
|
||||||
header (0x40B, only words 0-4 consulted): word0 must be 0 (bswap32,
|
header (0x40B, only words 0-4 consulted): word0 must be 0 (bswap32,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ to PC layout, appending them to the same pccg.bin/manifest.csv sidecar image
|
|||||||
produced by d69_emit.py (port/src/pccg.c already serves any filename listed
|
produced by d69_emit.py (port/src/pccg.c already serves any filename listed
|
||||||
in the manifest, so this script just needs to add rows).
|
in the manifest, so this script just needs to add rows).
|
||||||
|
|
||||||
Format spec: docs/PCPortResearch.md D88.x. Summary:
|
Format spec: docs/internals.md D88.x. Summary:
|
||||||
|
|
||||||
`struct stagesetup` (bondtypes.h) is a 10-pointer-field header (40B on N64,
|
`struct stagesetup` (bondtypes.h) is a 10-pointer-field header (40B on N64,
|
||||||
80B on PC -- pointers grow 4->8B) whose fields store plain (non-segment)
|
80B on PC -- pointers grow 4->8B) whose fields store plain (non-segment)
|
||||||
@@ -20,7 +20,7 @@ room-offset array, generalized to many interleaved regions).
|
|||||||
|
|
||||||
Verified against BUNKER1 (UsetupsevbunkerZ) by full byte-level ROM
|
Verified against BUNKER1 (UsetupsevbunkerZ) by full byte-level ROM
|
||||||
reconstruction (offsets/counts cross-checked against every pointer field in
|
reconstruction (offsets/counts cross-checked against every pointer field in
|
||||||
the file -- see tools_pc/d88_emit.py history / PCPortResearch.md D88.2):
|
the file -- see tools_pc/d88_emit.py history / internals.md D88.2):
|
||||||
the file's leftover byte-stream regions (propDefs polymorphic prop-def
|
the file's leftover byte-stream regions (propDefs polymorphic prop-def
|
||||||
table, AI opcode streams, pad/boundpad `plink` name strings, pad/boundpad
|
table, AI opcode streams, pad/boundpad `plink` name strings, pad/boundpad
|
||||||
name (`pname`) strings) all sit BETWEEN and interleaved with the 8 growing
|
name (`pname`) strings) all sit BETWEEN and interleaved with the 8 growing
|
||||||
@@ -51,7 +51,7 @@ the cumulative shift from growth ahead of them in the file):
|
|||||||
KNOWN BUG (D125, unfixed as of M-13): the converted blob does not land
|
KNOWN BUG (D125, unfixed as of M-13): the converted blob does not land
|
||||||
correctly in the emitted file for some levels (Aztec/Bunker2/Surface2) --
|
correctly in the emitted file for some levels (Aztec/Bunker2/Surface2) --
|
||||||
suspect the pass-1 delta / region-`end` handling here (~L308-340), NOT
|
suspect the pass-1 delta / region-`end` handling here (~L308-340), NOT
|
||||||
convert_stream itself. See PCPortResearch.md D125 + docs/LEVEL-STATUS.md.
|
convert_stream itself. See internals.md D125 + docs/LEVEL-STATUS.md.
|
||||||
- waypoint.neighbours / waygroup.neighbours / waygroup.waypoints /
|
- waypoint.neighbours / waygroup.neighbours / waygroup.waypoints /
|
||||||
PathRecord.waypoints target arrays: plain `s32` ID lists (NOT file
|
PathRecord.waypoints target arrays: plain `s32` ID lists (NOT file
|
||||||
offsets -- graph node indices), NULL/-1 terminated, walked with real
|
offsets -- graph node indices), NULL/-1 terminated, walked with real
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Compares a directory of candidate frames against a golden baseline
|
|||||||
|
|
||||||
WHY STRUCTURAL / TOLERANT BY DEFAULT
|
WHY STRUCTURAL / TOLERANT BY DEFAULT
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
The PC port is NOT frame-deterministic (see docs/PCPortResearch.md
|
The PC port is NOT frame-deterministic (see docs/internals.md
|
||||||
Section F "D117"): logic advances a wall-clock-variable number of 60Hz
|
Section F "D117"): logic advances a wall-clock-variable number of 60Hz
|
||||||
ticks per rendered frame (src/game/frametiming.c waitForNextFrame /
|
ticks per rendered frame (src/game/frametiming.c waitForNextFrame /
|
||||||
boss.c mainTickElapsed gate driven by osGetCount(), which the port maps
|
boss.c mainTickElapsed gate driven by osGetCount(), which the port maps
|
||||||
|
|||||||