diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..290ad9d0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +* text=auto eol=lf +*.png binary +*.jpg binary +*.z64 binary +*.exe binary +*.bin binary diff --git a/.github/workflows/EU.yml b/.github/workflows/EU.yml deleted file mode 100644 index e4760010..00000000 --- a/.github/workflows/EU.yml +++ /dev/null @@ -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." diff --git a/.github/workflows/JPN.yml b/.github/workflows/JPN.yml deleted file mode 100644 index 926bd33f..00000000 --- a/.github/workflows/JPN.yml +++ /dev/null @@ -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." diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..710826df --- /dev/null +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 5ee68352..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -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." - \ No newline at end of file diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000..45788008 --- /dev/null +++ b/.mailmap @@ -0,0 +1,4 @@ +# Canonicalize display names (does not rewrite history). +# Pre-2026-08 history is the GoldenEye 007 decompilation. +Larry Ficken +James Dansereau <106631954+jkdansereau@users.noreply.github.com> diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 1befb576..00000000 --- a/AGENTS.md +++ /dev/null @@ -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_.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. diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index e934ab90..00000000 --- a/CLAUDE.md +++ /dev/null @@ -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. diff --git a/CMakeLists.txt b/CMakeLists.txt index cb78927c..50dd3e56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ # game C sources (src/game, src) for a modern host (Windows/Linux/macOS) # 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). # # 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 # 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 -# 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. if(NOT DEFINED CMAKE_C_COMPILER) 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 # 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 # (WIP decomp; genuine 64-bit hazards to fix upstream as phases progress) # 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 only externally-referenced symbols from these two files are # 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 "${CMAKE_SOURCE_DIR}/src/game/decompress.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_.s (see # 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 -# ROM-derived and absent from this checkout. See docs/PCPortResearch.md §11. -# D34 (docs/PCPortResearch.md): animationtable_{data,entries}.c are NOT +# ROM-derived and absent from this checkout. See docs/internals.md §11. +# D34 (docs/internals.md): animationtable_{data,entries}.c are NOT # 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 # 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 # setup helpers. Its N64-only init() is compiled but NOT called; # 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 # code (bondview2.c, front.c). Pure data. # stacks.c — thread-stack arrays (sp_boot/sp_rmon/sp_idle/sp_shed/sp_main/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..537e76a4 --- /dev/null +++ b/CONTRIBUTING.md @@ -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. diff --git a/CppProperties.json b/CppProperties.json deleted file mode 100644 index 09ffa046..00000000 --- a/CppProperties.json +++ /dev/null @@ -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" - ] - } - ] -} diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f08cf65d --- /dev/null +++ b/LICENSE @@ -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. diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000..039e090b --- /dev/null +++ b/NOTICE @@ -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. diff --git a/PORTING.md b/PORTING.md deleted file mode 100644 index c3e2b07a..00000000 --- a/PORTING.md +++ /dev/null @@ -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). diff --git a/README.md b/README.md new file mode 100644 index 00000000..7b319a90 --- /dev/null +++ b/README.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). + +

+ Archives level rendering in the port + Bunker 1 rendering in the port +
+ Guards in the Archives level + Bunker 1 with HUD + Bunker 1 storage room +
In-engine captures — Archives and Bunker 1 running in the port. +

+ +## 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
direction · integration
build + playtest"] + C["Claude
(Claude Code)"] + Q["Qwen 3.8
(pi, local RTX 5090)"] + D[("shared artifacts
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. diff --git a/assets/font/readme.md b/assets/font/readme.md index 0e59a63d..73db961b 100644 --- a/assets/font/readme.md +++ b/assets/font/readme.md @@ -1,21 +1,21 @@ -# Font Asset Readme - -Font information. - -* first font table controller - small watch text -* second font table controller - small watch text -* first font table controller - large -* second font table controller - large - - - -### second controller format -Size of 0x18 for each entry - -entry layout: -* 0x0: -* 0x4: vertical offset -* 0x8: height -* 0xC: width -* 0x10: -* 0x14: offset to image (converted to pointer at runtime) +# Font Asset Readme + +Font information. + +* first font table controller - small watch text +* second font table controller - small watch text +* first font table controller - large +* second font table controller - large + + + +### second controller format +Size of 0x18 for each entry + +entry layout: +* 0x0: +* 0x4: vertical offset +* 0x8: height +* 0xC: width +* 0x10: +* 0x14: offset to image (converted to pointer at runtime) diff --git a/assets/images/readme.md b/assets/images/readme.md index 5e4f2c4d..9b703d1a 100644 --- a/assets/images/readme.md +++ b/assets/images/readme.md @@ -1,41 +1,41 @@ -# Image Assets - -## Overview - -This directory contains the texture image system for GoldenEye 007. - -Original image files would have been SGI .rgb files. These would be passed through a converter (rgb2c) to get CI byte arrays. - -## Directory Structure - -- **`split/`** - Individual texture files (2698 images) - - Named using IMAGE enum from images.def (e.g., COPYICON.bin, X.bin) - - Extracted from ROM using ROM offsets from imagelist.u.csv - - Raw binary texture data - -- **`combined/`** - Combined texture binary - - `combined.bin` - All textures concatenated in ROM order - - Padded to 16-byte (0x10) boundary - - Size: 3075872 bytes (3075864 data + 8 padding) - - SHA1 (unpadded): 044fca472bf6ef6691fa02ff1b65ff474d86a9fa - -## Build System - -The image build system synchronizes two sources: -- **`imagelist.u.csv`** (root) - ROM offsets and sizes (ground truth for positioning) -- **`assets/images.def`** - Image names and properties (ground truth for naming) - -See `scripts/IMAGE_BUILD_SYSTEM.md` for detailed documentation. - -## Quick Commands - -```bash -# Extract images from ROM -./scripts/extract_baserom.u.sh - -# Rebuild combined.bin -make assets/images/combined/combined.bin - -# Full ROM build -make -``` +# Image Assets + +## Overview + +This directory contains the texture image system for GoldenEye 007. + +Original image files would have been SGI .rgb files. These would be passed through a converter (rgb2c) to get CI byte arrays. + +## Directory Structure + +- **`split/`** - Individual texture files (2698 images) + - Named using IMAGE enum from images.def (e.g., COPYICON.bin, X.bin) + - Extracted from ROM using ROM offsets from imagelist.u.csv + - Raw binary texture data + +- **`combined/`** - Combined texture binary + - `combined.bin` - All textures concatenated in ROM order + - Padded to 16-byte (0x10) boundary + - Size: 3075872 bytes (3075864 data + 8 padding) + - SHA1 (unpadded): 044fca472bf6ef6691fa02ff1b65ff474d86a9fa + +## Build System + +The image build system synchronizes two sources: +- **`imagelist.u.csv`** (root) - ROM offsets and sizes (ground truth for positioning) +- **`assets/images.def`** - Image names and properties (ground truth for naming) + +See `scripts/IMAGE_BUILD_SYSTEM.md` for detailed documentation. + +## Quick Commands + +```bash +# Extract images from ROM +./scripts/extract_baserom.u.sh + +# Rebuild combined.bin +make assets/images/combined/combined.bin + +# Full ROM build +make +``` diff --git a/assets/music/readme.md b/assets/music/readme.md index c9892f82..931d6964 100644 --- a/assets/music/readme.md +++ b/assets/music/readme.md @@ -1,3 +1,3 @@ -# Music Asset Readme - -Music information. +# Music Asset Readme + +Music information. diff --git a/assets/obseg/readme.md b/assets/obseg/readme.md index 14fa3abe..dc71aba0 100644 --- a/assets/obseg/readme.md +++ b/assets/obseg/readme.md @@ -1,65 +1,65 @@ -# obseg Asset Readme - -These assets consist of model and level information, and all text in the game. - -There are eight categories (subfolders) of obseg assets included in source code. Visit each subfolder for a more detailed readme. - -obseg asset categories are: - -* `bg` -* `brief` -* `chr` -* `gun` -* `prop` -* `setup` -* `stan` -* `text` - -## `bg` Assets -Level background geometry display lists and portal info. - -## `brief` Assets -Maps text strings into mission briefings. - -## `chr` Assets -Character models display lists. - -## `gun` Assets -On screen gun models display lists. - -## `prop` Assets -Prop and item models display lists. - -## `setup` Assets -Setups contain the mission scripting logic, AI Scripting, object information, Pad and Portal placement, guard paths and nav mesh. (see below for descriptions) - -## `stan` Assets -Stand Tiles. Refereed to by Rare Staff as "stans" in GE or simply "Tiles" in PD. (see below for descriptions) - -## `text` Assets -All game text. - -## Misc Assets -In the root of this folder is a file listing all of the assets included in the build, with path and filename info. This is used at runtime to resolve the list of available assets. - ------ - -# Concepts - -# Room -A level is divided into rooms. - -# Portal -Portals allow Bond to see through them to connected rooms that are otherwise culled to save unnecessary processing. - -# Pad -A room contains one or more pads. - -# Path -Guard pathing information between pads. - -# Nav mesh -Also known as "sets". Colleciton of pads, to aid in the AI algorithm to find the shortest path to Bond. - -# Stan or Tiles -Defines floor information. +# obseg Asset Readme + +These assets consist of model and level information, and all text in the game. + +There are eight categories (subfolders) of obseg assets included in source code. Visit each subfolder for a more detailed readme. + +obseg asset categories are: + +* `bg` +* `brief` +* `chr` +* `gun` +* `prop` +* `setup` +* `stan` +* `text` + +## `bg` Assets +Level background geometry display lists and portal info. + +## `brief` Assets +Maps text strings into mission briefings. + +## `chr` Assets +Character models display lists. + +## `gun` Assets +On screen gun models display lists. + +## `prop` Assets +Prop and item models display lists. + +## `setup` Assets +Setups contain the mission scripting logic, AI Scripting, object information, Pad and Portal placement, guard paths and nav mesh. (see below for descriptions) + +## `stan` Assets +Stand Tiles. Refereed to by Rare Staff as "stans" in GE or simply "Tiles" in PD. (see below for descriptions) + +## `text` Assets +All game text. + +## Misc Assets +In the root of this folder is a file listing all of the assets included in the build, with path and filename info. This is used at runtime to resolve the list of available assets. + +----- + +# Concepts + +# Room +A level is divided into rooms. + +# Portal +Portals allow Bond to see through them to connected rooms that are otherwise culled to save unnecessary processing. + +# Pad +A room contains one or more pads. + +# Path +Guard pathing information between pads. + +# Nav mesh +Also known as "sets". Colleciton of pads, to aid in the AI algorithm to find the shortest path to Bond. + +# Stan or Tiles +Defines floor information. diff --git a/assets/obseg/setup/readme.md b/assets/obseg/setup/readme.md index e8490d11..a4b07dd3 100644 --- a/assets/obseg/setup/readme.md +++ b/assets/obseg/setup/readme.md @@ -1,128 +1,128 @@ -# Setup File Readme - -Known as `setup`. Setups contain the mission scripting logic, AI Scripting, object information, Pad and Portal placement, guard paths and nav mesh, intro camera and cutscene configuration, and the end credits. - -Values are stored most significant byte first. - -## Definitions - -This readme uses the term "section" to refer to a continuous sequence of bytes in the setup file. There are "main" sections which are referenced by the pointer in the header. Seperate from "main" sections are "filler" sections, which lie in between main sections. This usually consists of data referenced by a main section. - -If a section (sequence of bytes) is not referenced by anything in the setup file it will be referred to as "unreferenced". A main section can be unreferenced, such as the `padnames`. More common, a filler section is unreferenced e.g., multiplayer maps empty/unused nav mesh. - -# File Strucutre - -The file always starts with the header, followed by the padlist and pad3dlist sections. If there is a credits data block, it should follow the pad3dlist section. Next are the object and intro sections. - -The remaining sections and data can appear in various order. - -## Header - -The header consists of a data structure of 10 pointers in the following order: - -- path table section -- path link section (path list section) -- intro block -- object / propdef / objective section -- path sets section -- AI List section -- pad list section -- pad3d list section -- pad names section -- pad3d names section - -## Pad List Section - -List of sequence of pads. Does not reference any data outside this section. Always ends with an empty entry - - { {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, NULL, 0 } - -## Pad3d List Section - -List of sequence of pad3ds. Does not reference any data outside this section. Always ends with an empty entry - - { {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, NULL, 0, {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f} } - -## Object / Objective Section - -Each entry is specified by type `enum PROPDEF_TYPE`. This section lists - -- guards (and hats) -- props (doors, vehicle, tank, monitor, etc) -- collectibles (body armor, ammo, weapons, items in safe, etc) -- objectives -- autogun / drones -- CCTV / surveilance cameras -- keys, locked doors, and locks -- glass -- watch menu text -- poison gas (facility) -- meta items (rename, link, switch) -- meta programming (tag, `PROPDEF_END`) - -This section does not reference any data outside this section. The section always ends with a propdef entry with type `PROPDEF_END`. - -## Intro Section - -Each entry is specified by type `enum INTRO_TYPE`. This section describes - -- Spawn location -- Starting inventory and ammo amounts -- Opening cinemas and camera locations -- Bond's outfit (cuffs) -- Special flag to mark as credits - -The "credits" type can reference data outside this section, otherwise all data is contained within the section itself. The section always ends with a propdef entry with type `INTROTYPE_END`. - -### Intro: Animation - -Discord comment: -> Animation is used by Bond during the intro swirl process. It's actually a bunch of preset values for each animation. To get around this limitations, some setups such as Silo will instead not set an animation used for the intro swirl, but will have a dedicated AI list that'll just watch for when the intro swirl has triggered, and then trigger an animation that isn't selectable by the intro block animation's range. Sometimes this is used because the intro swirl is quite long, so some manual timing is required via AI list. - -### Intro: Credits - -A "credits" type intro entry consists of a single pointer to a block of credits data. This just a list of (global) text ids with alignment and position info. See `CreditsEntry` for struct layout. - -The credits block ends with an empty entry - - { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 } - -## Path Table Section - -List of sequence of path tables. Elements will reference variables defined immediately prior to this section. Always ends with an empty entry - - { 0xffff, 0xffff, NULL, 0x00000000, 0x00000000 } - -## Path Link Section - -List of sequence of path links. Elements will reference variables defined immediately prior to this section. Always ends with an empty entry - - { NULL, NULL, 0 } - -## Path Sets Section - -List of sequence of path sets. Elements will reference variables defined immediately prior to this section. Always ends with an empty entry - - { NULL, 0x00000000 } - -## Padnames Section - -List of sequence of pad names. This section is optional. It may also consist of data, but be unreferenced. This section references strings in .rodata. If present, the section always ends with a single `NULL` entry. - -## Pad3dnames Section - -List of sequence of pad3d names. This section is optional. It may also consist of data, but be unreferenced. This section references strings in .rodata. If present, the section always ends with a single `NULL` entry. - -## AI List Section - -List of pointers and ids to AI functions. These are used by guards or as part of the level. Elements will reference sequences defined immediately prior to this section; sequences are word aligned, but the last data value in the sequence is a single byte `0x04`. The AI List ends with an empty entry - - { NULL, 0x00000000 } - -## Filler Blocks - -Occasionally a main section will be empty except the "end of sequence" entry (e.g., multiplayer setups). The area immediately prior to the main section then might have an unreferenced/unused sequence of data. This is usually only one or two bytes, of either `{ 0 }` or `{ -1 }`. The AI List entries block may lead with an unreferenced AI Function, the single byte `0x04` (word aligned / zero padded). - -## .rodata - +# Setup File Readme + +Known as `setup`. Setups contain the mission scripting logic, AI Scripting, object information, Pad and Portal placement, guard paths and nav mesh, intro camera and cutscene configuration, and the end credits. + +Values are stored most significant byte first. + +## Definitions + +This readme uses the term "section" to refer to a continuous sequence of bytes in the setup file. There are "main" sections which are referenced by the pointer in the header. Seperate from "main" sections are "filler" sections, which lie in between main sections. This usually consists of data referenced by a main section. + +If a section (sequence of bytes) is not referenced by anything in the setup file it will be referred to as "unreferenced". A main section can be unreferenced, such as the `padnames`. More common, a filler section is unreferenced e.g., multiplayer maps empty/unused nav mesh. + +# File Strucutre + +The file always starts with the header, followed by the padlist and pad3dlist sections. If there is a credits data block, it should follow the pad3dlist section. Next are the object and intro sections. + +The remaining sections and data can appear in various order. + +## Header + +The header consists of a data structure of 10 pointers in the following order: + +- path table section +- path link section (path list section) +- intro block +- object / propdef / objective section +- path sets section +- AI List section +- pad list section +- pad3d list section +- pad names section +- pad3d names section + +## Pad List Section + +List of sequence of pads. Does not reference any data outside this section. Always ends with an empty entry + + { {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, NULL, 0 } + +## Pad3d List Section + +List of sequence of pad3ds. Does not reference any data outside this section. Always ends with an empty entry + + { {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, NULL, 0, {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f} } + +## Object / Objective Section + +Each entry is specified by type `enum PROPDEF_TYPE`. This section lists + +- guards (and hats) +- props (doors, vehicle, tank, monitor, etc) +- collectibles (body armor, ammo, weapons, items in safe, etc) +- objectives +- autogun / drones +- CCTV / surveilance cameras +- keys, locked doors, and locks +- glass +- watch menu text +- poison gas (facility) +- meta items (rename, link, switch) +- meta programming (tag, `PROPDEF_END`) + +This section does not reference any data outside this section. The section always ends with a propdef entry with type `PROPDEF_END`. + +## Intro Section + +Each entry is specified by type `enum INTRO_TYPE`. This section describes + +- Spawn location +- Starting inventory and ammo amounts +- Opening cinemas and camera locations +- Bond's outfit (cuffs) +- Special flag to mark as credits + +The "credits" type can reference data outside this section, otherwise all data is contained within the section itself. The section always ends with a propdef entry with type `INTROTYPE_END`. + +### Intro: Animation + +Discord comment: +> Animation is used by Bond during the intro swirl process. It's actually a bunch of preset values for each animation. To get around this limitations, some setups such as Silo will instead not set an animation used for the intro swirl, but will have a dedicated AI list that'll just watch for when the intro swirl has triggered, and then trigger an animation that isn't selectable by the intro block animation's range. Sometimes this is used because the intro swirl is quite long, so some manual timing is required via AI list. + +### Intro: Credits + +A "credits" type intro entry consists of a single pointer to a block of credits data. This just a list of (global) text ids with alignment and position info. See `CreditsEntry` for struct layout. + +The credits block ends with an empty entry + + { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 } + +## Path Table Section + +List of sequence of path tables. Elements will reference variables defined immediately prior to this section. Always ends with an empty entry + + { 0xffff, 0xffff, NULL, 0x00000000, 0x00000000 } + +## Path Link Section + +List of sequence of path links. Elements will reference variables defined immediately prior to this section. Always ends with an empty entry + + { NULL, NULL, 0 } + +## Path Sets Section + +List of sequence of path sets. Elements will reference variables defined immediately prior to this section. Always ends with an empty entry + + { NULL, 0x00000000 } + +## Padnames Section + +List of sequence of pad names. This section is optional. It may also consist of data, but be unreferenced. This section references strings in .rodata. If present, the section always ends with a single `NULL` entry. + +## Pad3dnames Section + +List of sequence of pad3d names. This section is optional. It may also consist of data, but be unreferenced. This section references strings in .rodata. If present, the section always ends with a single `NULL` entry. + +## AI List Section + +List of pointers and ids to AI functions. These are used by guards or as part of the level. Elements will reference sequences defined immediately prior to this section; sequences are word aligned, but the last data value in the sequence is a single byte `0x04`. The AI List ends with an empty entry + + { NULL, 0x00000000 } + +## Filler Blocks + +Occasionally a main section will be empty except the "end of sequence" entry (e.g., multiplayer setups). The area immediately prior to the main section then might have an unreferenced/unused sequence of data. This is usually only one or two bytes, of either `{ 0 }` or `{ -1 }`. The AI List entries block may lead with an unreferenced AI Function, the single byte `0x04` (word aligned / zero padded). + +## .rodata + The .bin file will end with the .rodata section consisting of the strings used in padlist, pad3dlist, padnames, and pad3dnames. \ No newline at end of file diff --git a/assets/obseg/stan/readme.md b/assets/obseg/stan/readme.md index 9cc8c432..023d0d97 100644 --- a/assets/obseg/stan/readme.md +++ b/assets/obseg/stan/readme.md @@ -1,82 +1,82 @@ -# Stand Tile Readme - -Known as Tile, Stand Tile, or stan. This defines floor information where Bond can stand. - -The model conversion tool rare used has at least 2 modes for output. Release/16bit signed; no debug strings. And debug/32bit float with debug strings. - -Each stan file is composed of a short header, ends with a standard footer, and in between is a list of tiles and points. - -Values are stored most significant byte first. - -## Header - -The header consists of 4 empty bytes, followed by a 32 bit integer giving the offset to tiles from the start of the file (pointer to first tile). There are optional bytes between the tile offset number and the start of the tiles, usually an empty 4 bytes. - -The header is the same for both release and debug. - -## Tiles - -Tiles consist of a 64 bit tile description, followed by an array of points. - -byte 0-3: a name, id or descriptor. One byte seems to be a "group id" and the other two bytes are some kind of tile id, but exact definition is unknown. -byte 4: room number -byte 5 (upper 4 bits): flags or attributes. -byte 5 (lower 4 bits): Red -byte 6 (upper 4 bits): Green -byte 6 (lower 4 bits): Blue -byte 7 (upper 4 bits): number of points within the tile - -The remaining 3 fields generally define the most extreme points within the tile - -byte 7 (lower 4 bits): first point -byte 8 (upper 4 bits): second point -byte 8 (lower 4 bits): third point - -### Tile Points - -A tile point is described by four 16-bit fields, giving X,Y,Z coordinates of the point as signed 16-bit integer. - -The remaining field is the "link" - -## Beta Tiles - -Beta tiles consist of a 96 bit tile description, followed by an array of points. - -byte 0-4: pointer to string giving the tile name -byte 5 (upper 4 bits): flags or attributes. (same as regular tile) -byte 5 (lower 4 bits): Red (same as regular tile) -byte 6 (upper 4 bits): Green (same as regular tile) -byte 6 (lower 4 bits): Blue (same as regular tile) -byte 7-8: unknown 16-bit field. This may be the room. -byte 9: number of points within the tile - -The remaining 3 fields generally define the most extreme points within the tile (same as regular tile, except one full byte) - -byte 10: first point -byte 11: second point -byte 12: third point - -### Beta Tile Points - -Beta tile points store the same information as regular tiles, but in a different format. - -The X,Y,Z coordinates are now 32-bit floats. - -The "link" is now a 32-bit int. - -## Tile list - -The tile section in the stand tile file ends with an empty entry. This is a regular stand tile of all zeroes (8 bytes of zero). The beta tile list ends with 8 bytes of zero as well (even though the struct size is 12 bytes). - -"Tile beginning with room 0 is the true way the file format ends, engine does not check for unstric string". For beta tiles, this is a check against the first 4 bytes of the struct. - -## Tile Attributes - -Possible values for tile "flags" or "attributes": - -0 = Normal -1 = Kneeling -3 = Ladder - -## Footer -After the tile list is a standard footer. This consists of an 8 character zero terminated string "unstric", followed by 4 null points (16 bytes of zero). +# Stand Tile Readme + +Known as Tile, Stand Tile, or stan. This defines floor information where Bond can stand. + +The model conversion tool rare used has at least 2 modes for output. Release/16bit signed; no debug strings. And debug/32bit float with debug strings. + +Each stan file is composed of a short header, ends with a standard footer, and in between is a list of tiles and points. + +Values are stored most significant byte first. + +## Header + +The header consists of 4 empty bytes, followed by a 32 bit integer giving the offset to tiles from the start of the file (pointer to first tile). There are optional bytes between the tile offset number and the start of the tiles, usually an empty 4 bytes. + +The header is the same for both release and debug. + +## Tiles + +Tiles consist of a 64 bit tile description, followed by an array of points. + +byte 0-3: a name, id or descriptor. One byte seems to be a "group id" and the other two bytes are some kind of tile id, but exact definition is unknown. +byte 4: room number +byte 5 (upper 4 bits): flags or attributes. +byte 5 (lower 4 bits): Red +byte 6 (upper 4 bits): Green +byte 6 (lower 4 bits): Blue +byte 7 (upper 4 bits): number of points within the tile + +The remaining 3 fields generally define the most extreme points within the tile + +byte 7 (lower 4 bits): first point +byte 8 (upper 4 bits): second point +byte 8 (lower 4 bits): third point + +### Tile Points + +A tile point is described by four 16-bit fields, giving X,Y,Z coordinates of the point as signed 16-bit integer. + +The remaining field is the "link" + +## Beta Tiles + +Beta tiles consist of a 96 bit tile description, followed by an array of points. + +byte 0-4: pointer to string giving the tile name +byte 5 (upper 4 bits): flags or attributes. (same as regular tile) +byte 5 (lower 4 bits): Red (same as regular tile) +byte 6 (upper 4 bits): Green (same as regular tile) +byte 6 (lower 4 bits): Blue (same as regular tile) +byte 7-8: unknown 16-bit field. This may be the room. +byte 9: number of points within the tile + +The remaining 3 fields generally define the most extreme points within the tile (same as regular tile, except one full byte) + +byte 10: first point +byte 11: second point +byte 12: third point + +### Beta Tile Points + +Beta tile points store the same information as regular tiles, but in a different format. + +The X,Y,Z coordinates are now 32-bit floats. + +The "link" is now a 32-bit int. + +## Tile list + +The tile section in the stand tile file ends with an empty entry. This is a regular stand tile of all zeroes (8 bytes of zero). The beta tile list ends with 8 bytes of zero as well (even though the struct size is 12 bytes). + +"Tile beginning with room 0 is the true way the file format ends, engine does not check for unstric string". For beta tiles, this is a check against the first 4 bytes of the struct. + +## Tile Attributes + +Possible values for tile "flags" or "attributes": + +0 = Normal +1 = Kneeling +3 = Ladder + +## Footer +After the tile list is a standard footer. This consists of an 8 character zero terminated string "unstric", followed by 4 null points (16 bytes of zero). diff --git a/assets/ramrom/readme.md b/assets/ramrom/readme.md index 71c707c5..dfff6d29 100644 --- a/assets/ramrom/readme.md +++ b/assets/ramrom/readme.md @@ -1,3 +1,3 @@ -# ramrom Asset Readme - -Contains information for a pre-recorded demo. +# ramrom Asset Readme + +Contains information for a pre-recorded demo. diff --git a/assets/readme.md b/assets/readme.md index b9798e2c..1e088e18 100644 --- a/assets/readme.md +++ b/assets/readme.md @@ -1,30 +1,30 @@ -# Asset Readme - -Assets consist of the things like sounds, images, animations, models, level descriptions, etc. - -There are five categories (subfolders) of assets included in source code. Visit each subfolder for a more detailed readme. - -Asset categories are: - -* `font` -* `images` -* `music` -* `obseg` -* `ramrom` - -## `font` Assets -This contains font information for rendering text. - -## `images` Assets -Contains ALL textures used in game (UI, Objects and BG) - -## `music` Assets - -## `obseg` Assets -Contains level information, game models, and all text used in the game. - -## `ramrom` Assets -This contains demo information the game plays if no folder is selected from the first screen. - -## Misc Assets -In addition to the assets outlined above, the root assets folder also contains model animation definition, as well as a global image table. +# Asset Readme + +Assets consist of the things like sounds, images, animations, models, level descriptions, etc. + +There are five categories (subfolders) of assets included in source code. Visit each subfolder for a more detailed readme. + +Asset categories are: + +* `font` +* `images` +* `music` +* `obseg` +* `ramrom` + +## `font` Assets +This contains font information for rendering text. + +## `images` Assets +Contains ALL textures used in game (UI, Objects and BG) + +## `music` Assets + +## `obseg` Assets +Contains level information, game models, and all text used in the game. + +## `ramrom` Assets +This contains demo information the game plays if no folder is selected from the first screen. + +## Misc Assets +In addition to the assets outlined above, the root assets folder also contains model animation definition, as well as a global image table. diff --git a/build-pc.sh b/build-pc.sh index 432b0a0c..9110d42d 100644 --- a/build-pc.sh +++ b/build-pc.sh @@ -5,7 +5,7 @@ # Usage: # ./build-pc.sh [ntsc-final|pal-final|jpn-final] # -# Dependencies (see docs/PCPortResearch.md): +# Dependencies (see docs/internals.md): # - CMake >= 3.16 # - SDL2 dev # - zlib dev diff --git a/docs/AGENT-WORKFLOW.md b/docs/AGENT-WORKFLOW.md deleted file mode 100644 index ae485e9c..00000000 --- a/docs/AGENT-WORKFLOW.md +++ /dev/null @@ -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 , AGENTS.md non-negotiables. -TASK: . -FILES YOU MAY TOUCH: . -KNOWN-GOOD / RULED OUT: . -PRE-FLIGHT ATTACHED: . -BUDGET: . 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. -``` diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md deleted file mode 100644 index 136570e2..00000000 --- a/docs/BACKLOG.md +++ /dev/null @@ -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. \ No newline at end of file diff --git a/docs/BRIEF-B2-depot-textures.md b/docs/BRIEF-B2-depot-textures.md deleted file mode 100644 index e2d2ca23..00000000 --- a/docs/BRIEF-B2-depot-textures.md +++ /dev/null @@ -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<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` (PATH first; - image base 0x140000000, PC in log is absolute). -- Frame capture `GE_PCDUMP="80-260:40"` → `./ppm/`; `python tools_pc/pixcount.py ppm/.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 = 4th col of `ps -p `). - 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`. diff --git a/docs/BRIEF-C2-fast3d-texptr.md b/docs/BRIEF-C2-fast3d-texptr.md deleted file mode 100644 index 2a752526..00000000 --- a/docs/BRIEF-C2-fast3d-texptr.md +++ /dev/null @@ -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`. -- `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`. diff --git a/docs/BRIEF-C2gdl-model-reloc.md b/docs/BRIEF-C2gdl-model-reloc.md deleted file mode 100644 index 02a0a453..00000000 --- a/docs/BRIEF-C2gdl-model-reloc.md +++ /dev/null @@ -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`. -- `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 `). 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`. diff --git a/docs/BRIEF-C3-C6-prop-model.md b/docs/BRIEF-C3-C6-prop-model.md deleted file mode 100644 index 70e6e9e7..00000000 --- a/docs/BRIEF-C3-C6-prop-model.md +++ /dev/null @@ -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). diff --git a/docs/BRIEF-D122-per-level-modelload.md b/docs/BRIEF-D122-per-level-modelload.md deleted file mode 100644 index 6dc98ff3..00000000 --- a/docs/BRIEF-D122-per-level-modelload.md +++ /dev/null @@ -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` (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 = 4th col of `ps -p `; 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`. diff --git a/docs/BRIEF-D69-stage-load.md b/docs/BRIEF-D69-stage-load.md deleted file mode 100644 index 80352886..00000000 --- a/docs/BRIEF-D69-stage-load.md +++ /dev/null @@ -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-/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 && ./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 ` - (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. diff --git a/docs/HANDOFF-LOCAL-AGENT.md b/docs/HANDOFF-LOCAL-AGENT.md deleted file mode 100644 index b6ab939f..00000000 --- a/docs/HANDOFF-LOCAL-AGENT.md +++ /dev/null @@ -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). diff --git a/docs/HANDOFF-ai-xmacro.md b/docs/HANDOFF-ai-xmacro.md deleted file mode 100644 index 9b1d9679..00000000 --- a/docs/HANDOFF-ai-xmacro.md +++ /dev/null @@ -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 -#include -#include // <- pulls in the whole AI X-macro system -#include "chrobjdata.h" -``` - -`bondaicommands.h` includes: -- `bondconstants.h` (which at line 17 includes ``, 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()`. -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 #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 */ - #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). diff --git a/docs/HANDOFF-switch-macro.md b/docs/HANDOFF-switch-macro.md deleted file mode 100644 index 758681f4..00000000 --- a/docs/HANDOFF-switch-macro.md +++ /dev/null @@ -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__` 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 `` / - `` (and, if added, ``) resolve to the shims; shims - include the real headers via `src/...` / `include/...` paths. -- Output must be byte-identical to the original IDO expansion. diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md deleted file mode 100644 index 166d6308..00000000 --- a/docs/HANDOFF.md +++ /dev/null @@ -1,1093 +0,0 @@ -# HANDOFF — GoldenEye 007 PC port (Phase 2, breadth-first level sweep) - -Tier 1 doc. Current state + next task + environment. Finding history is -`docs/PCPortResearch.md` §F/§H (indexed at top of §F); session narrative -is `docs/HANDOFF-ARCHIVE.md`. - -## Preflight (do not skip because you have a "next step") - -1. `CLAUDE.md` (auto) + `AGENTS.md` non-negotiables. -2. This file, top to bottom. -3. `docs/PORT-LEARNINGS.md` — recurring bug classes (you WILL re-derive - catalogued bugs otherwise). -4. `docs/PLAN-linear-level-sweep.md` — the plan of record. -5. Dispatching a subagent? `docs/AGENT-WORKFLOW.md` first — every brief - needs FILES / BUDGET / ON-EXPIRY / CONSTRAINTS / REPORT. - -## Direction (2026-08-29) - -**Breadth-first pivot.** Stop the depth-first BUNKER1 + graphics -rabbit-holes. Instead: - -1. Make boot → intro → menu → level-select **functional (not crashing)** — - cosmetics parked in `docs/GRAPHICS-BACKLOG.md`. -2. Sweep all **21 solo levels** for load + render + no-crash → - `docs/LEVEL-STATUS.md`. - -Full workstream breakdown (WS1–WS6) in `docs/PLAN-linear-level-sweep.md`. - -## Diagnostic env probes - -All `GE_*` env-var probes in the tree are catalogued in -**`docs/GE-ENV-PROBES.md`** (var → file:line → what it does → live/dead). -Live tooling: `GE_PCDUMP`, `GE_INPUTSCRIPT`, `GE_STARTMENU`, `GE_UNLOCK_ALL`, -`GE_INPUTLOG`, `GE_SAVELOG`, `GE_D160`, `GE_DTEX`, `GE_TEXDUMP`. The rest are -closed-finding `GE_Dxx` scaffolding (strip candidates; all env-gated and inert, -except `bg.c`'s `GE_D63` lines which are not `#ifdef PORT`-guarded). - -## Where things stand - -- **BUNKER1 (`-level_09`) is playable-ish.** Loads, renders recognisably - (textured rooms, storage racks, floor; skeletal guards render as - humanoids), and **survives a guard firefight** — 45 s+ crash-free - (D103–D120). **Silo (`-level_20`) also loads + renders clean.** - Committed through `f2beae4b` (M-12: D121 WS1 boot, D122 propDef fix). -- **Input layer (Phase 3) landed and playtested** (D118, M-9/M-10). - `port/src/input.c` is real: keyboard+mouse + SDL_GameController → N64 - pad. Core aim/move works. **M-24 mouse-look rework** (`port/src/input.c`, - port-only): mode-aware map — aim mode (RMB) drives the analog stick past - ±60 and emits no C-buttons; hipfire keeps digital C-up/C-down pitch. - **D118b (mouse-Y inverted) and D118c (aim + mouse-down → crouch) FIXED**; - **D118a residual** — hipfire pitch still digital vs analog yaw (minor). - `config.c` INI load/save now implemented → `ge007.ini` is written on - first run and re-read; `[Input]` `MouseEnabled` / `MouseAimSpeed` (50) / - `MouseTurnSpeed` (100) / `MouseInvertY` (0) are live-tunable. Rebinding / - gamepad hotplug still TODO. Weapon switch on kbd = A button - (`Space`/`Z`/`E`) — no dedicated key. -- **Recent fixes (M-10, `d1e93b76`):** D119 guard-attack crash - (`weapons_held[]->chr` type-pun) fixed; D120 blood-stain hang guarded - (not fixed — `d43_emit.py` opcode-0x18 converter gap). -- **Cosmetic backlog (parked, `docs/GRAPHICS-BACKLOG.md`):** D75 front-end - 3D model transforms (Nintendo logo misplaced, gun-barrel Bond absent, - cast models absent), D76 disclaimer screen partial, D77 no audio, - D114/D116 HUD/text X-mirror (**DO NOT re-static-trace** — see - PORT-LEARNINGS §D2), D74 dead wrap-block. - -## Done this session (M-32b) — tactical follow-up (2026-08-31) - -3 commits on `master` (`49ce620a`, `7071b110`, + this reconciliation). - -- **Debug-scaffolding strip (`49ce620a`).** Removed inert env-gated - `GE_D54`/`GE_D63`/`GE_D90` probe blocks from compiled `src/` - (`memp.c`, `bg.c` incl. the bare non-PORT `d63bgprimarycount` static + - entry log, `blood_animation.c`, `dyn.c`, `rsp.c`, `bondview_r.c`, - `csplayer.c`, `load.c`, `seqplayer.c`; 161 lines). `GE_D54`/`GE_D90` now - fully gone; `GE_D63` partially (remaining blocks all `#ifdef PORT` + - getenv, inert — strip candidates for later). Build green; `-level_09` + - `-level_20` render crash-free (19 frames each). `docs/GE-ENV-PROBES.md` - updated. -- **D75 Bug 2 runtime probe (`7071b110`).** `GE_D75=1` in `title.c` - `sub_GAME_7F007F30`. Booted bare front end through the gun-barrel: - Bond + gun models absent; **both Model instances valid** (nMtx 21/1); - **`render_pos` valid + fresh each frame** (== that frame's `dynAllocate` - mtxlist base, clean double-buffer, o2p unchanged) → the "stale arena" - hypothesis (D115 #5) is **RULED OUT** — do not land a persistent - `render_pos` buffer. **Zero fast3d DL warnings** → not D144/D146 either. - Failure is downstream in `drawjointlist`/`dotube` vtx/node-DL resolution - or an off-screen `basemtx`. Next: a drawjointlist-level probe. Full - write-up: §F "D75 Bug 2 — RUNTIME PROBE". -- **RC3 Depot eyeball — INCONCLUSIVE, default stays OFF.** Captured Depot - (`-level_30`) with `GE_WRAPFIX=1` vs `0`. Depot is too dark in the - boot-camera area to judge the non-PoT (65×65 / 96×48) ceiling/panel - surfaces the fix targets; the visible corrugated-container walls look - unchanged and un-regressed with the fix on. Not enough signal to flip - `cfgWrapFix=1`. Needs a **live human eyeball** on Depot with - `Video.WrapFix=1` (walk to a lit area / the ceiling), or a `GE_DTEX` - capture on the specific surface. `port/src/video.c` `cfgWrapFix` unchanged. -- **Sidecars regenerated** (`d43_emit.py` / `d69_emit.py` / `d88_emit.py - --regen`, ntsc-final) — ready for a campaign playtest. - -### Next (M-32b) -- **RC3** — human eyeball Depot with `Video.WrapFix=1`; flip `cfgWrapFix=1` - in `port/src/video.c` if a lit non-PoT surface is visibly better and - `-09/-20/-34` don't regress. -- **D75 Bug 2** — drawjointlist/`dotube`-level probe (resolved vtx/nodeDl - ptrs, whether any tris emit, composed `basemtx*render_pos` for joint 0). -- **D143** blank briefing/objective text — untouched this session. -- **D154 / D152+ / D160** — still playtest-gated. - -## Done this session (M-31/M-32) — 6-agent parallel burst #2 (2026-08-31) - -6 worktree subagents, files partitioned; 5 merged to `master` -(`0c918ab4`..`1753ac7a` cherry-picked, then a reconciliation commit). -Combined tree builds clean (240/240); `-level_09`/`-level_20` render -crash-free (framediff phash = documented D117 intro-pan noise, nonclear -coverage stable to 0.04pp); 6/6 texture-heavy levels (Depot/Facility/ -Runway/Archives/Streets/Caverns) boot+render+no-crash. - -| Item | Result | Verified | -|---|---|---| -| **D154** (`bg.c`) | Existing wall-shoot GBI-parser port was mostly right, but `vtxoff = gdl->dma.par & 0xf` was **always 0** — the PC `Gdma_le` shim maps `.par` to word0 bits 0-23 (packed length), not the N64 params byte. Fixed → `((u32)gdl->words.w0 >> 16) & 0xf`. Also ported a sibling raw parser: `bgTestBulletHitBackground` G_SETTILE back-scan (~3841). `GE_D154=1` diag added. | build 242/242; **playtest-gated** (needs firefight into a wall on an idle machine) | -| **D152+** (`snd.c`, `libultra.c`) | Audit: every compiled-audio `osSetIntMask(OS_IM_NONE)` is **balanced** — the §F "unbalanced early-return" guess was wrong. Real fixes: `sndSetSfxSlotVolume` now holds the mask across its `ALSoundState` walk (matches its twin `sndDeactivateAllSfxByFlag`) + `sndApplyVolumeAllSfxSlot` batches its loop under one recursive hold (kills the mission-failed fade lock-storm); `portThreadWrapper` → `imThreadExitRelease()` releases an orphaned lock on thread exit (kills the transient-thread-died leak + pthread-id-reuse re-wedge). Steal-lock kept as backstop. | build 240/240; `-level_09/-20` crash-free; **fade-out repro playtest-gated** | -| **RC3 / D167** (`gfx_pc.cpp`) | fast3d never stored the N64 tile `mask` — wrapped every repeating texture at GL image size, not `1<render_pos` → transient `dynAllocate` arena (D115 item #5). Needs a runtime probe. | write-up only | -| **D160 / D148** (docs) | "propDef command-index walk desync" hypothesis **DISPROVEN** — exhaustive static trace: every record type Dam emits has `converter PC bytes == sizepropdef()×4`, stream tiles byte-exact, walk in lockstep, all 21 levels clean. Dam rappel cutscene is runtime AI-script / `CAMERAMODE_POSEND` cinematic render (D75 family). `GE_D160=1` diag already ships. | needs live Dam-to-exit playthrough | -| **Docs** | `TEXTURE-GLITCH-ANALYSIS.md` §0 status table reconciled (RC2 FIXED / RC4 RETRACTED / D159+D161 FIXED / RC3 done-behind-knob); new `docs/GE-ENV-PROBES.md` (full env-var table); GE_D63 strip-safety checklist (all inert; only `bg.c:2876/2880` is a bare non-PORT `static` + entry log worth deleting for N64 hygiene). | — | - -**Worktree-harness note:** 3 of the 6 agent worktrees were created from a -**stale commit** (`0a4b3bae`, ~30 behind); a mid-flight `git reset --hard -master` per agent recovered them. Watch for this on future bursts — check -each agent's HEAD in its first report. - -### Next -- **D75 Bug 2** — runtime `GE_PCDUMP` + capped probe on the front-end - animated-model `render_pos` / `dynAllocate` path (the one in-scope lead). -- **RC3** — eyeball Depot with `Video.WrapFix=1`, flip `cfgWrapFix=1` in - `port/src/video.c` if clean. -- **D154 / D152+ / D160** — all playtest-gated; verify during the campaign - playthrough (wall-shoot / mission-fail / Dam exit). -- Strip the `bg.c:2876/2880` bare D63 entry log. - -## Done this session (M-12) — 2 commits - -- **WS1 / D121 (`02c12068`)** — bare `./build-pc/ge007.x86_64.exe - -level_XX` auto-injects that stage's `memallocstringtable[]` `-m*` pool - row (`#ifdef PORT` in `boss.c bossInitMainthreadData`). No manual `-m*` - args any more. Gotchas recorded: setting `g_DebugAndUpdateStageFlag=1` - is the WRONG fix (routes boot through the title-stage intro), and - `tokenSetString` *replaces* the whole token buffer so the injected - string must carry `-level_XX` forward. See §H D121. -- **D88.4 was already resolved** (committed `ff563812`/`4ccc0d3c` weeks - ago). The "OPEN — cross-level blocker / `setupDoor` crash" note in the - old §F index / plan was **stale**. `d88_emit.py --regen` = 21/21 pass. -- **D122 (`f2beae4b`)** — the real cross-level blocker was prop/item - **model loading**. `tools_pc/d88_propdefs.py` had a per-type handler - table + a generic fallback; 6 `inherits ObjectRecord` propDef types - (47 TINTED_GLASS, 39 VEHICHLE, 40 AIRCRAFT, 45 TANK, 13 AUTOGUN, 20 - AMMO/MultiAmmoCrate) had **no handler** → fell to the generic - word-granular `bswap32`, which swapped the `[s16 obj][s16 pad]` word as - one u32 → model id `obj` in the wrong half → OOB `PitemZ_entries[]` - deref → crash in `modelLoad` (`loadobjectmodel.c:393`) / - `modelInitRwData` (`model.c:6249`). Fixed in the converter - (`OBJ_TAIL_DESC` handler) + matching `sizepropdef()` `#ifdef PORT` - stride. BUNKER1/Silo were never affected (they don't emit those types). - Full write-up: §F/§H **D122**. Confidence: crash fix high; types - 39/40/45 tail layout medium (structs have "locs unconfirmed" notes — - nudge if a level actually drives a tank/vehicle). - -**Uncommitted docs** (tangled with the in-progress big docs restructure — -`HANDOFF.md`, `AGENTS.md`, `CLAUDE.md`): the D121 + D122 entries in -`docs/PCPortResearch.md` §F/§H and this file's edits. Commit or fold in. - -## Done this session (M-17) — 4 commits, 13→18/21 PASS - -- **D126** (`e851e2ab`) — objective sub-records `criteria_picture` (30), - `criteria_roomentered` (32), `criteria_deposit` (33), - `setup_objective_text` (35) each end in a `T *next` list pointer that - `set_parent_cur_obj_*` / `setup_briefing_text_entry_parent` write while - walking the setup stream. Widens 4→8B and 8-aligns at offset 16 on PC → - struct is 24B/6w (N64 16/20); the old N64-sized emit meant the runtime - 8-byte `->next` store clobbered the *next* propdef record's header → - walk desync → command indices drifted ~100 → `setupDoor` `linkedDoor` - resolution landed on the wrong record. Fixed `d88_propdefs.py` - (`PROPDEF_PC_BYTES[30/32/33/35]=24` + typed handler) + - `loadobjectmodel.c` `sizepropdef` PORT returns 6. **Cleared C3r Bunker2 - + C4 Depot + C6 Surface2** in one change. -- **D127** (`1129f63d`) — `sndPlaySfx` guards a bogus `ALSound*` (the - converted libaudio bank has fewer/rearranged `soundArray` slots than - N64; audio is Phase-3 parked). **Cleared C7 Surface1.** -- **D128** (`749feb9f`) — `sub_GAME_7F0B37EC` special-portal marker used - the hardcoded N64 8-byte `bg_portal_data_entry` stride / `controlbytes1@6`; - on PC the struct is 16B with `controlbytes1@10`, so `|= 2` scribbled - into the middle of a portal's `offset_portal` pointer → `bg.c:5723` - crash on any level with a `specialportalarray` entry. `#ifdef PORT` - uses the struct accessor. **Cleared C5 Control.** -- **D129** (``) — `langGet` bounds-checks the slot id and - resolved bank pointer. Bare `-level_XX` boots that reach the - cast/credits text path (Cuba, `bondviewRenderCredits`, D76 area) - reference banks the menu flow never loaded. Reduces the Cuba bare-boot - end-credits crash; **Cuba itself loads + renders 300+ frames fine** and - the credits work through the real front-end flow. - -## Done this session (M-18) — 1 fix, 18→20/21 PASS - -- **D130** (`port/src/romdata.c`, uncommitted) — Facility `-level_34` + - Runway `-level_35` C2 crash (`import_texture_i8` AV on wild ptr - `0x72181ee8`). Root cause was NOT the model-GDL relocation the - D124-Facility addendum / `BRIEF-C2gdl-model-reloc.md` suspected — - disproved: `texLoadFromGdl` never copies a `G_SETTIMG` on these levels, - `texWriteLoadToTmem*` is never called, and `gdl` in `sub_GAME_7F0762E0` - is still a segmented `0x05xxxxxx` value so its `& 0x00ffffff` masks are - correct. Real cause: **`romdataFixupFont` corrupts BankGothic/ZurichBold - glyph indices 0/1/2** — the in-place N64 24B → PC 32B `fontchar` relayout - aliases `dst`/`src` for low glyph indices (stride grew 8B < the 24B field - read span), so a field write clobbers a later field's source mid-loop - (glyph 1 `width` = `bswap(index)`, glyph 2 `pixeldata` ≈ `0x020002e8` → - `+= font_base` → wild). Facility's title "Chemical Warfare Facility #2" - renders `#` → `gDPLoadTextureBlock(gdl, curchar->pixeldata=wild, …)` → - fast3d AV. Fix: stage all 6 N64 fields in a local `f[6]` before writing. - §F/§H **D130**; PORT-LEARNINGS §A. Verified: -level_34 0/~14, -level_35 - 0/4; -level_09/20/24 unregressed. - -## Done this session (M-19) — D131 (Jungle), D63 scaffolding removal - -- **D131** (`port/fast3d/gfx_pc.cpp`) — Jungle `-level_37` C2m crash. - `explosionRenderPropSmoke` passes a compiled `.bss` matrix symbol - (`&dword_CODE_bss_8007A100`) through `osVirtualToPhysical()` — a - `u32`-returning shim — into `gSPMatrix`, truncating the `0x1_00000000` - module high word → w1 = `0x401c68e0` → wild deref in `gfx_sp_matrix` - when the first explosion draws (~frame 300). NOT `gimgfixup` / the - model-GDL path (that premise was already dead per D130). Fix: `seg_addr` - restores the module high word for a fallthrough w1 in - `[0x40000000, 0x70000000)`. Covers ~30 latent `osVirtualToPhysical()` sites (explosion/glass/blood/bondview2). Verified: - Jungle renders to frame 1500–2400+ crash-free; `-level_20`/`-level_24` - unregressed. §F/§H **D131**, PORT-LEARNINGS §A. -- **D63 scaffolding removed** — dead TEMP D63 probe calls stripped from - `port/src/libultra.c` (watchdog thread + activity ring + slot watchers) - and the `#ifdef PORT` probe blocks in `blood_animation.c`, `dyn.c`, - `front.c`, `rsp.c`, `title.c`. All inert (env-gated / no side effects); - build links clean. `port/fast3d/gfx_pc.cpp` D63 trail code left for a - later pass. Bug class D24-implications + `osYieldThread` watch item added - to PORT-LEARNINGS §E. - -## Done this session (M-23) — WS6 Facility playthrough: D135/D137/D138/D139 FIXED, D136/D140/D118c open - -First real human WS6 playtest. User speed-ran Dam OK, then hit a chain of -crashes on Facility (`-level_34`) — each one a subsystem the 21-level sweep -never exercises (no weapon fire, no stage unload, no pause menu). Fixed four, -two new open, Facility now near-complete. **Committed `ec51d9c5`** (the -older D121/D122/D126/D128/D130/D131 write-ups are still uncommitted, tangled -in the docs-restructure — fold those separately). - -- **D135 (FIXED)** `src/game/propobj.c` — firefight crash. `bgTestHitOnObj` - (bullet-ray vs object triangle geometry, via `propobjFindHit` on every - shot that resolves on an object model) is an **unported N64 GBI parser**: - raw 8-byte-`Gfx` byte/word indices desync on the PC 16-byte `Gfx` stream → - walks off the DL → AV `0x709ae02a`. Ported `#ifdef PORT` to `gdl->words.w0/ - .w1` shifts (mirrors PD `pd_port/src/game/bg.c:3635`). Second fault after - the parser fix: `objHit` `propobj.c:9717` `% impact_sounds->thing2_len` int - div-by-0 — the ported texnum-recovery (`*(s16*)(phys(w1)-8)`) returned a - bogus `texturenum` → OOB `g_HitTypeSounds[]`. Fixed: PORT texnum branch - always `-1` (`isnd_default`, safe; generic impact sound/decal, park w/ D77). -- **D137 (FIXED)** `src/game/gunfire.c` — right-mouse (raise crosshair) crash. - `gunDrawSight` `s32 sp54` holds a `Gfx*` the whole fn (`texSelect`/ - `display_image_at_position` take `Gfx**`); 4-byte slot → `*(Gfx**)&sp54` - splices adjacent stack → wild DL write in `texSetRenderMode`. `#ifdef PORT`: - `sp54` is `Gfx *`. §A. -- **D138 (FIXED)** `src/snd.c` — the "1 room from the end" *freeze* (not a - crash — kernel-heartbeat watchdog). `sndCreatePostEvent` → - `alEvtqPostEvent` (`libultra/audio/event.c:110`) walks `evtq->allocList`; - parked audio thread never drains it, `chrobjSndCreatePostEvent` posts per - objTick per ambient-sound object, Facility is dense with machinery → the - list walk stalls the main thread. `#ifdef PORT`: `sndCreatePostEvent` is a - no-op until audio lands (D77). No gameplay effect. -- **D139 (FIXED, UNVERIFIED)** `src/game/cleanup_objects.c` — stage-unload - crash (`lvlUnloadStageTextData` → `cleanupObjects` → `objFree`, `obj->prop` - = packed-float garbage). Root cause: `cleanupObjects` walks propDefs with - `(u8)obj[0]` for the type, which works only because the header word - `[u16 extrascale][u8 state][u8 type]` is **big-endian** (type = low byte). - On LE the low byte is `extrascale` → the walk never matches `PROPDEF_END`, - runs off the blob, dispatches the switch on garbage → `objFreePermanently` - on non-objects → crash. `#ifdef PORT`: use `((PropDefHeaderRecord*)obj)-> - type` (offset 3) like every other consumer (`sizepropdef`, proplvreset). - **Not confirmed** — when the user hit pause to trigger a fast teardown - test, the pause menu itself crashed first (D140). `-level_09` unregressed - (91.65%). §F **D139**, PORT-LEARNINGS §C (BE header byte on LE). -- **D140 (OPEN)** — **pause menu crashes.** `maybe_mp_interface` → - `bondviewRenderWatch` (`bondview2.c:8604`) → `bondviewTransformManyPos- - ToViewMatrix(g_CurrentPlayer->field_23C = NULL, objheader->numMatrices=9)` - → `matrix_4x4_copy(src=0x0)` AV. The watch model's per-node view-matrix - cache (`struct player.field_23C`) is never allocated (or `field_23C` is at - the wrong PC offset — `struct player` layout, `docs/AUDIT-M6-player- - offsets.md`). Blocks the WS6 pause-menu / objective-status checks and the - watch gadgets. D75 3D-model-transform family. Files: `src/game/bondview2.c`, - `src/bondtypes.h` (`struct player`). -- **D118c (OPEN, low pri)** — in manual-aim mode, mouse-down → **crouch**. - Mouse Y emits C-down; `bondview2.c:5351` maps C-down in `insightaimmode` - to `crouchDown`/zoom as well as aim pitch. No input-layer-only fix; needs - the deferred analog-aim `#ifdef PORT` hook (§F D118). Documented, parked. -- **New harness** `tools_pc/repro_gdb.sh ` — nohup-launch a level, attach - gdb, dump full `bt full` + locals on the first SIGSEGV/SIGFPE, rolling - frame dump to `ppm/`. Needed because `ge007.crash.log`'s own backtrace is - `#01 = null` (native unwinder fails). Also `build-pc/d136_cmds.txt` — a - gdb cmd file that traces every `objFree` call (`type`/`obj`/`prop`/`model`) - then catches the fault; the "last line before FAULT" localises D136-class - walk-off bugs. **Attach the winpid via `ps -W | grep ge007.x86_64 | - awk '{print $4}'`** — `repro_gdb.sh`'s own `ps -p $!` derivation is wrong. -- **Follow-up (not done):** the BG room-geometry hit-test in `bg.c` - (`~3373-3646`, walks the D85-widened `ptr_expanded_mapping_info`) is the - identical unported GBI parser to D135 — latent, fires on shooting - walls/floor. Same mechanical port. §F D135 + PORT-LEARNINGS §B. -- **Controls** (for the new session's own playtesting): kbd — WASD/arrows - move, `A`/`D` sidestep, mouse X turn (analog), mouse Y look (digital - C-button), LMB/LCtrl fire, RMB/LShift aim, `Space`/`Z`/`E` = A (action AND - weapon-switch — GE overloads it), `X`/`R`/`F` = B (reload), `Q` = L, - `Enter`/`Tab` = Start, `Esc` quit. No dedicated weapon key. - -## Done this session (M-24) — mouse-look rework + real INI parser + playtest QoL - -Pre-playtest QoL, port-layer only, no `src/` / game-logic change. -**Full review sheet: `docs/M-24-QOL-REVIEW.md`** (rationale, per-change -risk, verification, what to check by playing). Committed `f3ec5170`, -`65ed0315`, `33506aee`. - -- **`port/src/input.c` mouse-look rework.** Read `bondviewProcessInput` / - `MoveData`: GE aim is **mode-dependent** — hipfire yaw = analog stick-X, - pitch = digital C-up/C-down (stick-Y = move, no pitch); aim mode (R held) - yaw+pitch = analog stick past ±60, and C-up/C-down there = crouch/lean/ - zoom, *not* aim. New map keyed on our own RMB/LShift (hold-to-aim proxy): - aim mode pushes `stick_x/y` into the 61..80 band and emits **no** - C-buttons; hipfire keeps digital C-pitch. GE's native pitch is inverted - (C-up→look down) — hidden so mouse-down looks down; `MouseInvertY` flips. - - **D118b FIXED** (mouse-Y inversion), **D118c FIXED** (aim+down→crouch — - no `src/` hook needed, aim look no longer emits C-down). - - **D118a residual**: hipfire pitch digital vs analog yaw. Minor (precise - vertical aim is an aim-mode activity). Full fix = the deferred analog - `#ifdef PORT` `bondview.c` hook. -- **`port/src/config.c` — INI load/save implemented** (was a stub that only - logged TODO). Parses `$S/ge007.ini` (`[Section]` blocks, `Key = value`, - `#`/`;` comments), clamps ints to registered bounds, writes defaults on - first run, rewrites on clean exit. Unblocks live tuning of the mouse - knobs + all future video options. Note: constructor-registered options - present at `configLoad()` time (main.c:52) are captured; anything - registered later would miss the first save. -- **New knob** `Input.MouseTurnSpeed` (hipfire yaw %, default 100), split - from `MouseAimSpeed` (aim-mode %, default 50) since they feed different - game curves. -- Verified: `-level_09` boots crash-free to 900+ frames @ 91.65% coverage - (unregressed); `-level_20` crash-free; `ge007.ini` written then re-read - with no unknown-key warnings. Build green (`ntsc-final`). -- **`port/src/video.c` QoL:** **F12** takes a screenshot - (`ppm/shot_NNN.ppm`, dumped on the render thread via the existing - `gfx_opengl_dump_bound_fbo`); window **focus loss frees the mouse** - (`inputSetMouseGrab(0)` on `SDL_WINDOWEVENT_FOCUS_LOST`, re-grab on - gain) so alt-tab works. `inputSetMouseGrab()` also zeroes the aim delta - and suspends mouse reads while released. -- Alt-Enter fullscreen toggle already existed (`gfx_sdl2.cpp:299`). -- **Gamepad hotplug** — `SDL_CONTROLLERDEVICEADDED/REMOVED` → `inputRescanPads()` - (close all + re-open). Caveat: the game latches `inputConnectedMask()` at - `osContInit` (boot), so a pad added later merges into controller 0 for - play but won't appear as a separate channel — plug it before launch for - multi-pad. -- **Mouse-wheel = weapon cycle** — a notch queues a 2-poll A-button press; - GE's default scheme (`invButtons = A_BUTTON`, `bondview2.c:5162/5326`) - cycles the weapon forward on a bare A edge. Both wheel directions cycle - forward (no clean backward input without the A+fire combo). -- **Window title shows live FPS** (`wmAPI->set_window_title`, ~1 Hz). -- **Still TODO (not started):** key rebinding, on-screen (in-render) FPS - overlay, in-game options menu. - -## Done this session (M-25) — 3 commits, port-layer QoL - -Free-roam QoL pass, `port/` only, zero `src/` / game-logic change. Full -review sheet: **`docs/M-25-QOL-REVIEW.md`**. All defaults reproduce prior -behavior exactly. - -- **`9ebed821` — `[Video]` ge007.ini knobs.** `Video.VSync` / `FpsCap` / - `MSAA` (1/2/4/8) / `TextureFilter` (0 nearest, 1 bilinear) / `Fullscreen`, - wired in `video.c videoInit`. Plus **config auto-migration**: `config.c` - now rewrites `ge007.ini` once when the build registers a key the file - never had (so a new section actually appears) — an up-to-date file with a - user comment is left untouched. -- **`5a7a1035` — `tools_pc/playtest.sh`** takes a level *name* - (`playtest.sh bunker2`) or number, `--list` prints the 21-level table, and - a crash now auto-runs `addr2line` on the faulting PCs. -- **`863f436b` — `[Input]` ge007.ini knobs.** `MouseYScale`, - `MouseSmoothing` (0 = off = today), `PadDeadzone` (7000), `PadTriggerPct` - (23), `PadLookInvertY`. -- Verified per commit: build green, `-level_09` crash-free to frame 620 @ - 91.6% (unregressed), `-level_20` crash-free. -- **Silo `-level_20` capture freeze — D117/D134 load sensitivity, NOT a - real bug and not an M-25 regression.** Full-length captures froze at - ~frame 320 (silo→Bond camera descent) twice, but only on a machine still - loaded from the sweep (the pre-M-25 baseline froze the same way). On an - idle machine the user drove Silo live past ~1800 frames, VI pacemaker - healthy, no stall. See `docs/M-25-QOL-REVIEW.md`. - -## Done this session (M-26) — port-layer QoL batch (A/B/C/E) - -Low-risk QoL, `port/` only, zero `src/` change. All defaults reproduce -prior behaviour. Full review: **`docs/M-26-QOL-REVIEW.md`**. - -- **A — save-on-exit + `[Window]` persistence.** `atexit(portAtExit)` in - `main.c` (`videoSaveWindowState()` + `configSave()`), fires on every - clean quit (`exit(0)` in `videoPumpEvents`); `abort()` paths skip it. - New `[Window]` ini section (`Width/Height/X/Y/Maximized`, sentinels = - old behaviour) in `video.c`; `Video.Fullscreen` now round-trips. -- **B — `level_sweep.sh` STALLED verdict.** Frames rendered then froze - with the process still alive → `STALLED (froze at N/M frames)` instead - of a bogus PASS (the Silo case). Retried once like NO-FRAMES. -- **C — raw mouse input.** `Input.MouseRawInput=1` (default 0) sets the - SDL relative-scale / warp hints off in `inputInit` — no OS pointer accel. -- **E — `--help` / `--version`** in `main.c` before `crashInit()`: build - id, usage, the 21-level `-level_XX` table. -- **D — `[Debug]` ini flags.** `Debug.FrameDump` (mirrors `GE_PCDUMP`), - `Debug.InputLog` (mirrors `GE_INPUTLOG`); env var wins, ini is the - fallback. `config.c` `configGetFrameDump()`/`configGetInputLog()`. -- **Verified:** build green (`ntsc-final`); `-level_09` + `-level_20` - boot crash-free 6/6 GE_PCDUMP frames (unregressed); `ge007.ini` gains a - populated `[Window]` block on exit. - -## Done this session (M-27 continued) — FULL front-end loop works end to end - -**User playtested Dam → exit → post-mission report → Facility briefing → -Start, exactly like the retail game.** No crashes, no hangs. 10 commits -`bb6ac627`..`0fdb14a3` (all committed). - -- **D142** (`src/bondconstants.h`) — GCC/mingw compiles an all-non-negative - `enum` as UNSIGNED, so `for (s = SP_LEVEL_EGYPT; s >= SP_LEVEL_DAM; s--)` - in `fileGetHighestStageDifficultyCompletedForFolder` never terminated → - SELECT FILE screen froze the game. Fix: `#ifdef PORT` negative sentinel - in `LEVEL_SOLO_SEQUENCE`. PORT-LEARNINGS §D3. -- **D143** (`src/game/textrelated.c`) — `langGet()` returns NULL for a - string slot the PC menu flow hasn't loaded; `textRender/textMeasure/ - textWrap` faulted on it. NULL guards → blank text instead of crash. - (A string bank still isn't loading — briefing/objective text is blank. - Cosmetic, chase later.) -- **D144 / D146** (`port/fast3d/gfx_pc.cpp`) — a front-end 3D model - (MISSION COMPLETE dossier / mode-select wallets, D75 family) emits a - malformed compiled sub-DL (`seg5+0x9ee4`): unresolved matrix pointer, - then garbage opcodes. D144 = bad matrix ptr → identity; D146 = unknown - opcode → end the DL (was `sysFatalError`→`abort()`, no crash log) + - `fast3d_ptr_ok()` guards on `gfx_sp_vertex/movemem/set_vertex_colors`. - Model renders wrong/absent (GRAPHICS-BACKLOG D149) but the game survives. -- **D145** (`port/src/video.c`, `gfx_sdl2.cpp`, `input.c`) — bare **ESC was - `exit(0)`** in two event handlers; on the debrief screens ESC is the - natural "back" key so paging with it quit the game (clean exit, no log, - looked like a crash). ESC now = N64 B (back/cancel); quit = window-X / - Alt+F4. -- **D147** (`port/src/libultra.c`) — **the end-of-Dam hang.** `sndPlaySfx` - → `alEvtqPostEvent` on the main thread races the `amMain` audio thread - in `sndRemoveEvents` on the same `ALEventQueue`; both "lock" with - `osSetIntMask(OS_IM_NONE)` which was a **no-op** → list corruption → - infinite spin. `osSetIntMask` is now a process-wide **recursive mutex** - (`OS_IM_NONE` acquires, `OS_IM_ALL` token releases). Thread dump - confirmed. PORT-LEARNINGS §D4. Verified `-level_09` 1200 frames clean. - -**New test tooling (committed):** -- `tools_pc/debug.ps1` — `.\tools_pc\debug.ps1 [-level_09] [-Menu 13] - [-NoBuild]`. Builds, runs under gdb, prints FATAL line + backtrace + - crash log on exit → `gdb.txt`. Use this for every playtest so a - crash/hang always leaves a trace. -- `GE_INPUTSCRIPT=":,…;…"` (`port/src/input.c`) — headless - scripted controller-0 input (buttons pulse, `SUP/SDOWN/SLEFT/SRIGHT/ - SNONE` sustain). Sole input source when set. -- `GE_STARTMENU=` (+ `_PAGE`, `_DIFF`) (`src/game/lv.c`) — boot - straight into a front-end menu (13=MISSION_COMPLETE, 10=BRIEFING, - 7=MISSION_SELECT, 12=MISSION_FAILED, 6=MODE_SELECT). Skips a save/unlock - so it lands on Dam regardless; good for crash-testing a screen, not for - reproducing real objective state. - -### Still open (cosmetic / not blocking, parked in GRAPHICS-BACKLOG) -- **D148** — Dam level-end cutscene (Bond rappelling down the dam) doesn't - play; cuts straight to the report. Scripted-cutscene / cinematic-camera - + rappel anim; D75 family. -- **D149** — front-end MISSION COMPLETE / mode-select 3D models garbled - or absent (the D144/D146 corrupt DL). D75 family. -- **D143 side effect** — briefing / objective text renders blank (a lang - string bank not loaded in the PC menu flow). Find which bank. - -### Next -- Continue the mission list: Facility → Runway → … playtest with - `tools_pc/debug.ps1`, same loop. In-level ABI/GBI crashes are the - expected work (D122/D135/D147 pattern). -- The D75 front-end / cutscene 3D-model family (D148/D149) is now the - biggest *visible* gap but is cosmetic — below crashes. - -## Done this session (M-30) — user batch defect list; commits + docs - -### M-30b — 6-agent parallel burst (2026-08-31), all merged + verified - -7 commits `84a8693e`..`1b69c8ba`. Merged tree builds clean; `-level_09/-20/-30/-34/-37/-28` -render + no crash; menu + Depot visually verified; Agent D's 21/21 with-input -(walk+fire) sweep crash-free. Three texture-pipeline fixes compose correctly. - -| Dxx | Fix | Verified | -|---|---|---| -| **D158** | KF7 muzzle-flash `((f32*)stackpad2)[-8]` negative-stack write → `0xc000001d` on fire. Real `#ifdef PORT` local. | Caverns survives sustained KF7 fire; stackpad audit found no others | -| **D159** | `texSwapAltRowBytes` (odd-row byte-swap for an N64 RDP TMEM XOR fast3d doesn't emulate) scrambled every ~1:1-viewed texture = the user's "interlaced textures". `#ifdef PORT` skip. | menu wallet-photo: comb → clean portrait; `-level_09` unregressed | -| **D161** | Depot ceiling: 16×16 CI8 tile drawn with TLUT off (`G_TT_NONE`) → fast3d did a palette lookup on stale palette → blue speckle / radial rays. Decode CI+`G_TT_NONE` as intensity. | Depot ceiling: blue garbage → dark industrial roof; `-30/09/34` pass | -| **D164** | Legal/disclaimer screen drew 1 line — `legal_text_end = &legalscreen_MRD` assumed linker adjacency (false on mingw). `#ifdef PORT` bound on the array. | high-confidence static (nm-verified); needs a boot to eyeball | -| **D165** | Front-end mouse cursor: velocity² feel → P-controller 1:1 pointer. `Input.MenuPointerMode` (def 1), `Input.MenuPointerSpeed`. | build + menu smoke; user feel-check pending | -| **D166** | Hipfire vertical aim: fixed digital threshold → speed-proportional C-button duty cycle. `Input.HipfirePitchSpeed`. | build; user feel-check pending | -| **D160** | Dam rappel cutscene (D148) not root-caused — `GE_D160=1` diagnostic shipped. Hypothesis: propDef command-index walk desync (D122/D132 family). | **needs: user runs Dam to exit with `$env:GE_D160=1`, pastes `D160:` lines** | - -Also this session (pre-burst): D157 (campaign unlock — user-confirmed), RC2 -(`Video.FixMipTextures`), D120 (PointUsage), D74 (`Video.WrapFix` opt-in). - -### M-30 state / next -**⚠ Before the next playtest: `./build-pc.sh ntsc-final` AND the full sidecar -regen** (`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`) — D120 changed -`d43_emit.py` (PointUsage) and needs the regen; `debug.ps1` rebuilds the exe -but does NOT regen sidecars. (Done on this session's machine already.) - -**Campaign progression + saving works (D157, user-confirmed).** The user can -now do the full Track A playthrough — each level boundary exercises the same -save path. `GE_UNLOCK_ALL=1` jumps to any level; `GE_SAVELOG=1` traces the -save chain if a later level misbehaves. - -Fixed + committed this session (all on `master`, unpushed): -- **D165 / D166** (M-31, `port/src/input.c`, port-only) — input polish. - D165: the front-end cursor is now a true ~1:1 pointer (P-controller - estimating the game cursor through `front.c`'s own integrator) instead of - the M-30 velocity² feel; `Input.MenuPointerMode` (default 1) falls back to - the old mode, `Input.MenuPointerSpeed` scales it, `GE_INPUTLOG` traces - `menuptr est/tgt/eff/stick`. D166: hipfire mouse pitch is speed-proportional - C-button pulses, not a digital threshold (`Input.HipfirePitchSpeed`). - Build green; `-level_09` + `GE_STARTMENU=6` smoke crash-free. **User - feel-check owed** (headless can't drive the mouse) — tune MenuPointerSpeed - up if the menu sweep feels short. -- **D157** — campaign never unlocked (objective-difficulty byte read at the - wrong offset on LE). Root-caused + user-confirmed. §F D157. -- **RC2** (`Video.FixMipTextures`, default on) — LOD textures were uploaded - ~1.3× too tall; clip to base-tile height. Modest visual win, no regression. -- **D120** — `d43_emit.py` now emits the opcode-0x18 `PointUsage[]` chain - (was zero-filled → blood-decal walk cycled, guarded). Interactive verify - pending (BUNKER1 firefight). -- **Menu #4** — stripped the per-frame D63 log flood that collapsed the - front-end to 5fps (`d0789358`). -- **Menu #2** — front-end mouse-pointer mode (`current_menu != RUN_STAGE` → - mouse drives the cursor both axes, LMB=A/RMB=B); Y-invert fixed. -- **RC4 RETRACTED** — the palette decode is correct RGBA5551, the analysis - doc's "spec" was ARGB1555. Don't "fix" it. -- **21/21 level sweep PASS** (first all-green incl. Cuba) — no regressions. - -Still open / next: -- **D148** (Dam exit rappel cutscene doesn't play) — user will see it right - after Dam. D75 front-end/cutscene-model family, cosmetic, deep. Not started. -- **RC1 / wallet-Bond photo garble** — D75 family (`texLoadFromGdl` model-GDL - expansion broken for front-end models). `TEXTURE-GLITCH-ANALYSIS.md` §6b. -- **Depot ceiling blue-speckle** — B2 light-shaft/UV bug on that surface; - RC2 didn't touch it. Needs `GE_DTEX`/RenderDoc on the specific surface. -- **D154** (`bg.c` wall-shoot GBI parser, committed `072b5c44`) — needs a - firefight-into-a-wall to verify, then close. -- In-level ABI/GBI crashes as the campaign playtest proceeds (D122/D135 - pattern) — the expected work. -- Latent: `fileSetDifficultyStageTime` `times[]` OOB read for 007-difficulty - late levels (retail has it too; edge case, don't fix without growing - `save_data` which breaks EEPROM layout). - -### BLOCKER (M-30) — campaign progression not persisting — **FIXED (D157), CONFIRMED by user playtest** -User replayed Dam on Agent post-fix: `gdb.txt` shows `obj 0 objdiff=1` / -`obj 1,2 objdiff=2` (skipped on Agent) / `obj 3 objdiff=0 status=1` → -`objectiveIsAllComplete=1` → `end_of_mission_briefing` → `fileUnlockStage... -stage=0 diff=0` → `fileWriteSave slot=0 bitflags=10`. `data/ge007.eep` slot 0 -now carries the Dam completion time; the Agent checkmark shows in mission- -select **and persists across a kill + restart**. Campaign progression works. -(`GE_SAVELOG` diagnostics — `#ifdef PORT`, env-gated — left in -`objective_status.c` / `boss.c` / `file.c` / `file2.c` for now; strip once a -few more level boundaries are playtested. `dump_objectives.py` per-criterion -`MinDif=` is only valid on the `type=23` lines — garbage on 2-word sub-records, -harmless.) - -
D157 root cause -`D157` (`src/bondtypes.h`, `#ifdef PORT`): the type-23 objective record's -`MinDificulty` is a BE `s32` at word 3; `d88_propdefs.py` bswap32's it, so on -LE the value moved from byte 0xF to 0xC, but `struct objective_entry.difficulty` -still read `s8`@0xF → **0 for every objective**. `get_difficulty_for_objective()` -returned Agent(0) for all → `objectiveIsAllComplete()` on Agent evaluated -objectives that should be difficulty-gated out (Dam "Neutralize all alarms" = -Secret Agent, "Install covert modem" = 00 Agent) → always incomplete → -`end_of_mission_briefing()` (the unlock write) never ran. **User was right — -you don't shoot an alarm on Agent Dam.** Fixed the struct tail order under -`#ifdef PORT`; also fixed `dump_objectives.py` (same byte-offset bug — it -showed "Agent" for everything). **Next: user replays Dam on Agent (just reach -the exit) under `GE_SAVELOG=1`; expect `MinDif=1/2/2/0` on the crit lines, -`objectiveIsAllComplete=1`, a `fileWriteSave slot=N ... TIMES` line, and -Facility unlocked in mission-select.** If obj 3 (Agent, flag 0x1000) still -shows incomplete, the level isn't setting that stage flag on exit — separate -issue. `GE_UNLOCK_ALL=1` remains the playtest workaround meanwhile. -
- -
Original M-30 blocker writeup (superseded by D157) -User completed Dam, hit "previous" from the debrief to mission select, -**Facility still locked**. `data/ge007.eep` has zero completion times → the -unlock save isn't landing (or is wiped on reload). Chain: -`bossReturnTitleStage` (`objectiveIsAllComplete()` gate) → -`end_of_mission_briefing` (`briefingpage`/`selected_difficulty` guard) → -`fileUnlockStageInFolderAtDifficulty` → `fileOverwriteSaveSlotWithNewSave` -(needs a slot with `SAVEFLAG_DORESET`) → `fileWriteSave` (`fileGamePakProbe`) -→ EEPROM; then `fileValidateSaves` CRC-checks every slot on menu re-entry and -`fileResetSave`s any mismatch. -**M-30 GE_SAVELOG run 1 (user's gdb.txt) — narrowed:** -`bossReturnTitleStage stage=33` (Dam) fires with **`objectiveIsAllComplete=0`** -→ `end_of_mission_briefing` never called → no unlock write. `fileValidateSaves` -/ CRC / DORESET-slot all fine (first-boot fresh-save init, expected). So the -blocker is **objective-completion detection**: `objectiveIsAllComplete()` bails -at obj 0 (`get_status_of_objective(0) != COMPLETE`). Dam obj 0 = 4× -DESTROY_OBJECT (ALARM tags). This is D126/D151 propDef-decode family — either -`objective->ObjRefID` at the wrong byte offset on LE (criteria records -`_bswap32`'d, `MissionObjectiveRecord.ObjRefID` s32@4 vs the criterion's -`u16` tag), or the `sizepropdef` walk stride is off for a criterion type. -Note `sizepropdef(PROPDEF_OBJECTIVE_START)` PC-branch returns **4** and -`d88_propdefs.py` emits **16 B** for type 23 — but `MissionObjectiveRecord` -(bondtypes.h) is header+ObjRefID+TextID+MinDificulty+`*nextentry` = 20 B N64 -/ 24 B PC. If `nextentry` is real+serialized, both the stride and the -converter are 1-2 words short → whole-stream desync. (`struct objective_entry`, -the other view of type 23, is only 16 B and has no nextentry — the two -structs disagree; the "// maybe wrong..." comment flags it.) -**Diagnostic committed (``):** `GE_SAVELOG=1` now also dumps -each criterion's `type / ObjRefID / TextID / sizepropdef stride / first 3 -words / currentstatus` (gated by `g_savelogObjOnce` so it only fires on the -`bossReturnTitleStage` call, not the per-frame poll). -**Next: user does ONE genuine full Dam completion (all objectives, walk out -the exit) under `GE_SAVELOG=1`; the `SAVELOG: crit ...` lines pin the exact -field/stride that's wrong.** Also noted but not chased: -`fileSetDifficultyStageTime` `offset = ((diff*20)+levelid)*10` indexes -`times[]` up to `[98/99]` but the array is `u8 times[76]` — OOB for -high level/difficulty combos (writes into the next slot's checksum). Not the -Dam/Agent case (index 0) but a latent save-corruption bug. -
- -## Done this session (M-30) — user batch defect list; earlier items - -User playtest batch report (5 items; #1 disregarded): - -- **#4 main-menu hang (file-select → main menu → back → 5fps, no input, force-close).** - Crash log was D146 spam + `D63 [1479] from=... No stack`. **Root cause: the - D63 debug trail in `gfx_pc.cpp` fired ~10 `sysLogPrintf` lines + a full trail - walk on EVERY unknown GBI opcode, unconditionally** — on the D149 corrupt - front-end model DL that ran per-frame and collapsed the menu to ~5fps with - input starved. **FIXED `d0789358`:** stripped the D63 trail (statics, - per-G_DL recording, default-case dump); the rate-limited D146 "ending DL" - log (cap 20) stays. Headless: FILE_SELECT / MODE_SELECT / MISSION_SELECT all - boot + render 900+ frames clean, 0-1 D146 lines, no hang. `-level_09` 3/3 - framediff, `-level_20` 91.6% unregressed. **User to re-verify the live - menu-nav transition** — the exact folder→mode→back path wasn't reproducible - headless, but the per-frame log flood (the "5fps no input") is gone. -- **#2 file-select mouse only moves side-to-side.** The front-end cursor is - stick-driven (`frontUpdateControlStickPosition` reads `joyGetStickX/Y`); the - port only fed mouse-X to the stick in menus (mouse-Y → C-buttons, ignored by - menus). **FIXED ``:** `port/src/input.c` — when - `current_menu != MENU_RUN_STAGE` (and `!= MENU_INVALID`, so bare `-level_XX` - in-game aim is untouched) mouse velocity drives stick X+Y as a pointer, no - C-buttons / aim band, LMB=A (select) RMB=B (back). Reads the game global - `current_menu` for UI context only (enum MENU is ABI int) — no logic change. - New knob `Input.MenuPointerSpeed` (%, default 100). **User to verify feel.** -- **#5 menu + saving for playtest.** EEPROM saves (`osEeprom*` → - `data/ge007.eep`, write-through, M-29 `bf5e4d3d`) audited — sound; progress - persists across restart *once the menu works* (#4). No change needed; verify - after #4. -- **#3 "interlaced" textures** (`screenshots/interlaced texture example.jpg`). - **RC2 (mip contamination) FIXED, default on** (``, - `port/fast3d/gfx_pc.cpp`). New `GE_DTEX=1` probe confirmed it: every LOD - texture uploaded ~1.3x too tall (64x64 I4 → 64x87), the extra rows being mip - bytes rendered as image rows. `import_texture()` now clips a full-width LOD - block to the SETTILESIZE base height; GL builds correct mips from a correct - base. Knob `Video.FixMipTextures` (=0 → old behaviour, byte-identical to - golden). BUNKER1 side-wall textures visibly cleaner with it on; Silo - unregressed. **`-level_09` framediff phash on frames 320/440 is D117 noise, - not a regression** — M-30 verified: BUNKER1's intro camera pans continuously - for 1000+ frames and its speed varies run-to-run, so *any* fixed capture - frame lands on a different pan moment each run (a re-baseline attempt to a - "settled" 700-1000 window failed the same way — there is no static window in - the intro). Trust `nonclear` (coverage %, stays stable) + the 21-level sweep - for `-level_09`/`-level_20`; the golden phash frames are only meaningful for - gross breakage. Depot M-30 A/B: RC2 makes the wall panels mildly cleaner, - does NOT touch the ceiling blue-speckle (separate B2 light-shaft/UV bug), - makes nothing worse. **21/21 sweep PASS with the fix on.** - - **Still open:** the wallet-Bond *photo* garble is RC1 / D75 front-end model - family (the model GDL / `texLoadFromGdl` expansion is broken for front-end - models — fast3d walks into `0xfafbfbfc` garbage, not a missing opcode). - Separate larger track — see `docs/TEXTURE-GLITCH-ANALYSIS.md` §6b. - - RC4 (palette off-by-one, `gfx_pc.cpp:835`) and RC3 (wrap period) still - open — small, in the same doc §6. -- Regenerated `docs/LEVEL-OBJECTIVES.md` for all 21 solo levels (Track A); - committed loose reference docs/tools. - -## Done this session (M-29) — race-to-release plan; audio DEFERRED; 2 commits - -**Direction (user, M-29):** ship the solo campaign start→finish, fastest. -Three tracks: A = full 21-level playtest (release gate, user batch-reports a -defect list from a solo playthrough); B = **audio DEFERRED** — ship silent, -add sound post-launch (`docs/AUDIO-PLAN.md` stays valid for later, do NOT -start it); C = solo quick wins, priority: **B2 textures** > B3 blood-stain -converter > B5/B6 menu input > C2 (D75 models) > C4 (D148 cutscene) > -C1 (mirrored HUD — user unsure it's worth it). Full plan + -`docs/SMALL-FIXES.md` mapping in the `m29-release-push` memory. - -- **`0bd0ceec`** — aim sensitivity (SMALL-FIXES B4 / BACKLOG B3): - `Input.MouseAimSpeed` default 50→25 (overshot), new `Input.AimBand` knob - (5..40, def 20). Port-only, `ge007.ini`-tunable. Needs playtest feel check. -- **`bf5e4d3d`** — file-backed EEPROM saves (BACKLOG B5 / Phase 4): - `osEeprom*` in `port/src/libultra.c` now back `data/ge007.eep` (2 KB / - 16Kbit, lazy-load + write-through, PD pattern). `osEepromProbe` → - `EEPROM_TYPE_16K`. Verified: game writes the file (checksum @blk 0 + - save_data @blk 4), `-level_09` unregressed, persists across runs. - Campaign progress now survives quit. -- **SMALL-FIXES B1 (D74 wrap block) — DEFERRED.** The in-place one-liner - (`G_TX_CLAMP` test + `[t]` index) activates never-run code and boot-crashes - `-level_09` (0 frames). Left inert with a NOTE in `gfx_pc.cpp`. Needs the - hoist-out-of-vertex-loop rework + per-level visual check. -- **B1 3-point filtering — wired up as opt-in (`a418f4d0`), NOT default.** - The sm64ex 3-point shader path existed but was unreachable + its min-filter - row was mip-less. Fixed the row (trilinear), aniso default 0→4, exposed - `Video.TextureFilter=2`. **Default stays 1 (bilinear)** — 3-point softens - textures at normal distance and (tested) did NOT fix the Depot roof. -- **B2 (Depot wrong textures) — filtering RULED OUT, still not fixed.** - `docs/BRIEF-B2-depot-textures.md`. The roof renders as blue speckle + - radial rays converging to a point; identical with 3-point+trilinear+aniso - at 640×480 native, so NOT grazing aliasing. Decode path (TLUT bswap, - `palette_to_rgba32`, `import_texture_*`) audited clean. It's a texture-data - / UV / light-shaft-effect bug on that surface. **Next step: `GE_DTEX` - probe** to identify what draws it (params in the brief). This is a - RenderDoc/probe-class investigation — D114/D116 rabbit-hole family, needs - a focused session, not inline. -- **`7d7f5fb2` D155 — Facility outro-cutscene "hang" FIXED.** User report. - `waitForNextFrame()` passed `deltaFrames` unclamped; on the port - `osGetCount()` is wall-clock (D117) so a real-time stall at the - cutscene→debrief asset load (worse with a local LLM thrashing the box) - made it balloon to hundreds → `g_ClockTimer` huge → `modelTickAnim` - `while(numticks--)` × N chrs + dozens of `for(ispeed`/`playspeed` → `frameb` huge/NaN → - `floorFloatToInt` garbage → ~2^31 iterations. `#ifdef PORT` guards: snap a - non-finite/`|x|>=1e6` `frameb` to `framea`, and fall back `frame`/`frame2` - to the pre-loop values in `modelTickAnim`. One-shot `stderr` diagnostic - dumps the model speed fields when it fires. **User to re-verify the - Facility outro.** If it still hangs OR the diagnostic prints, the NaN - source is upstream (suspect a D100/D140-style misaligned `Model` field, or - the cutscene data). §F D155+D156, PORT-LEARNINGS §E. -- **D153 reminder cost real time this session:** back-to-back `-level_09` - runs during builds boot-crash under machine thrash; only regression-test on - a settled machine (clean run = 600+ frames fine). - -## Done this session (M-28) — D150 (watch page crash) + D151 (watch text blank) - -- **D150** (`src/str.c`, `#ifdef PORT`, uncommitted) — user found a crash - opening **level objectives on the watch**. AV in `strcat` (`str.c:25`, - NULL src). The watch BRIEF/OBJECTIVES pages (`options.c:3940-4068`) build - text with `strcat(buf, langGet(id))`; `langGet` returns NULL on PC for the - briefing/objective string banks the in-level watch flow never loads - (same missing-bank issue as the D143 "briefing text renders blank" note). - Fix: NULL-tolerant `strcpy/strncpy/strcat` under `#ifdef PORT`. Note these - are `__nonnull__` builtins to GCC so a plain `if (src==NULL)` is optimised - away — the guard laundras the ptr through an empty `__asm__` (`GE_IS_NULL`). - Build green; `-level_09` 600+ frames @ 91.67% unregressed. **Watch-page - repro is interactive — needs user re-verify.** §F/§H D150, PORT-LEARNINGS §C. -- **D151** (`src/bondtypes.h`, `#ifdef PORT`, uncommitted) — the D150 - "objective/briefing text renders blank" consequence, ROOT-CAUSED and fixed. - NOT a missing bank: the per-level lang bank IS loaded in-level - (`langLoadToAddr`, `prop.c:1274`). The propDef records that carry the watch - text (types 35 `WatchMenuObjectiveText`, 23 `ObjectiveStart`) store a plain - `s32` slot id in their 3rd word; `struct watchMenuObjectiveText` / - `struct objective_entry` decode it as `u16 reserved; u16 text;` and read - `text` at offset `0xA` — works only because on BE the id is in the low 16 - bits. `d88_propdefs.py` `_bswap32`'s the whole word → `text` @0xA reads 0 → - `langGet(0)` → NULL → blank. Fix: `text` is a full `u32` at the word offset - under `#ifdef PORT` (N64 `u16 reserved; u16 text;` kept under `#else`). No - converter change / no sidecar regen. Compiles clean (link needs the running - game closed). **Interactive re-verify pending** — open the watch, all of - OBJECTIVES / mission background / M / Q / Moneypenny should now show text. - §F **D151**, PORT-LEARNINGS §C. - -### D152 (OPEN) — mission-failed → permanent black screen = `osSetIntMask` lock deadlock -User killed Trevelyan in Facility (`Ctrl` = **fire**, no crouch bind) → failed -the objective → fade to black, never returns; process alive. **Live `gdb.txt` -inspected** (game left running under `debug.ps1`): NOT the front-end -mission-failed *screen* — it's a **deadlock on `s_imLock`** (the D147 -recursive-mutex behind `osSetIntMask`). Rendering froze at frame 12600. -`mainThread` in `sndSetScalerApplyVolumeAllSfxSlot`→`alEvtqPostEvent`→ -`osSetIntMask(OS_IM_NONE)`→`pthread_mutex_lock` (waiting); `amMain` in -`sndPlayerVoiceHandler`→`alEvtqNextEvent`→same, also waiting; **neither owns the -lock** → a leaked unbalanced `OS_IM_NONE` (libaudio early-return paths) or a -transient thread that acquired-and-exited holds it. The mission-failed **audio -fade-out** (`sndSetScalerApplyVolumeAllSfxSlot` posts `AL_SNDP_RELEASE_EVT` per -`ALSoundState` per frame) is what exercises the window. D147 family; D147's -"cannot deadlock" claim is now falsified. §F **D152**, PORT-LEARNINGS §D4. -**MITIGATED (M-28, `port/src/libultra.c`, port-only, uncommitted).** -`osSetIntMask` is now a **self-healing** logical lock: same recursive -acquire/release as the D147 mutex on the normal (microsecond) path, but a -waiter blocked **> 2 s** (`OS_IM_STUCK_NS`) declares the holder leaked it, -logs `LOG_ERROR` with the stale owner + the stealing caller's return -address, and steals the section. A leaked/unbalanced `OS_IM_NONE` (or an -acquire-and-exit transient thread) can no longer wedge the game forever — -worst case is a ~2 s audio hiccup + a log line that names the leak. Builds -clean. Still OPEN: the exact leaking call site (read it from the next -`D152: ... stealing from owner=` log) + the proper narrow fix (dedicated -`ALEventQueue` lock). Next: replay the Facility mission-fail; if the steal -fires, `addr2line` the logged caller. -Separate minor gap: no crouch keybind (BACKLOG B3/B4). - -### D154 (WRITTEN, UNVERIFIED, UNCOMMITTED) — `bg.c` room hit-test GBI parser port -`bgTestRayIntersectionInRoom` (`src/game/bg.c:3331`) is the D135 sibling the -docs kept flagging: an unported N64 GBI parser (`((u8*)gdl)[k]` / `((u32*)gdl)[i]` -byte/word indexing) walking the 16-byte PC room DL as if it were 8-byte N64 -`Gfx`. Fires on **shooting walls/floor** (`bgTestBulletHitBackground`), which no -level-sweep exercises. Ported under `#ifdef PORT` (N64 path verbatim under -`#else`): header fields via the `.dma` view like `bgBuildRoomVtxBounds`; G_TRI1 -+ G_TRI4 vertex-index nibbles recovered from `(u32)gdl->words.w0/.w1` -(derivation table cross-checked twice); `texturenum` -> -1 like D135 (KSEG0 -deref invalid for converted GDLs; only flavours the impact decal/sound, D77). -Builds clean. **NOT verified** — a no-input capture never calls this function, -so it needs a real firefight into a wall on an **idle** machine; couldn't get a -clean run this session (D153/driver-crash under machine thrash — see below). -Next session: build, `-level_09`, fire at a wall, confirm no crash + `-level_09` -framediff green, then commit. §F **D154**. - -### D153 — `-level_09` frame ~900 crash = load flakiness, NOT a bug (closed) -Chased for ~30 min in M-28: `-level_09` crashed `0xc0000005` at a -nondeterministic frame 900↔1400, 6/6 runs — **but every one was while the -machine was hammered with concurrent builds + multi-run loops.** On a quiet -machine `-level_09` ran past frame 2400, 0 heartbeats, no crash. It's the -D117/D134 host-scheduling flakiness the docs warn about every session (M-25 -Silo ~frame 300 is the same). Not a regression, not D150/D151/D152. -`romdataFixupMusicSeqTable: seqCount 63 exceeds blob capacity 1` is a benign -expected warning (header-only first call). **Lesson: never regression-test -while builds/other game runs are in flight.** §F **D153** (closed). - -
M-27 earlier — front-end/pause bucket: D140 + D141 FIXED - -Started the parked "front-end / transitions / pause / watch" bucket. -**Pressing Start in-level no longer crashes** — the pause / watch menu -renders (weapon page verified via a temp `GE_AUTOPAUSE` input probe, -since removed). Cosmetics unchanged and still parked: mirrored watch text -(D114/D116), dark/faint weapon model (D75). - -- **D140 (FIXED)** `src/game/bondview.h`, `src/game/bondview2.c` — - pause-menu crash `bondviewRenderWatch` → `bondviewTransformManyPos- - ToViewMatrix(field_23C=NULL)`. `something_with_watch_object_instance` - (N64 player +0x230) is a **`struct Model` + RW-pool punned into a - 0x184-byte field-run**; `field_23C`/`watch_scale_destination`/ - `pause_watch_related_adjust` are actually `.render_pos`/`.scale`/ - `.animframe1`. PC `sizeof(struct Model)` grows → the aliases break → - NULL `render_pos`. Fix: real inline `struct Model` + - `u32 watchRwPool[192]` under `#ifdef PORT`; the 3 named reads redirect to - the member via `GE_WATCH_{ANIMFRAME,SCALE,RENDERPOS}` macros (N64 `#else` - = verbatim field). D56 branch of `sub_GAME_7F07E7CC` now uses the inline - pool. D100/D102 pattern. §F/§H **D140**, PORT-LEARNINGS §A. -- **D118d (FIXED, feel unconfirmed)** `src/game/options.c` — user bug report: - watch inventory list over-scrolls with keyboard W/S (one press skips several - items). GE's "slam the stick" fast-scroll is a raw per-frame level check - (`joyGetStickY < -0x46`); keyboard/digital pads sit at max every frame. - `#ifdef PORT` drops the stick term → list-nav goes through the latched - single-step path (one step per press). Build green, `-level_09` unregressed; - couldn't verify the feel headlessly (INVENTORY watch page unreachable without - page-cycling input) — **needs an in-game check**. Latent sibling in `front.c` - menu nav (same `joyGetStick*InRange` level-check pattern). §F **D118d**. -- **D141 (FIXED)** `src/game/gunfire.c` — the crash D140 exposed. - `set_enviro_fog_for_items_in_solo_watch_menu` walks `bodymodel->Switches[]` - (a `ModelNode*` array) with raw byte offsets `+0x48`/`+0x5c`, `j += 4` - (4-byte-pointer constants) → PC 8-byte stride reads garbage → bogus node - → AV in `modelGetNodeRwData`. Fix: `#ifdef PORT` uses - `Switches[18 + (j>>2)]` / `Switches[23 + (j>>2)]`. §F **D141**, - PORT-LEARNINGS §B (cf. D128). -- **Verified:** build green (`ntsc-final`); `-level_09` framediff 3/3, - `-level_20` crash-free (both unregressed); autopause probe → watch page - renders 400+ frames, no `ge007.crash.log`. -- **D139** (stage-unload, M-23, still UNVERIFIED) not exercised — it needs - a real level-exit teardown, not a pause. Next front-end item. -- **Still in the bucket:** D139 verify; unpause / watch-page navigation - (the temp probe didn't test exiting the watch); level exit → MISSION - COMPLETE → debrief → auto-advance; main-menu / mission-select walkthrough - (WS2); watch objectives page + gadgets. - -**Update (M-27 end): the level-exit → MISSION COMPLETE → debrief → -auto-advance → next briefing → Start loop is DONE and playtested (D142– -D147).** D139 got exercised for free. Remaining bucket items (unpause / -watch-page nav) are minor. -
- -## Next task (M-24 — Opus 5) - -**Scope call (user, M-23):** D139 (stage unload) + D140 (pause menu / watch) -are the same **not-yet-built front-end/transition layer** — level exit → -debrief → next briefing, pause, the watch. Bare `-level_XX` skips all of it -and the sweep never touches it. Do **not** build a throwaway exit path just -to verify D139 — its fix is unambiguous (LE reads the wrong header byte) and -committed; it'll be exercised for free once that layer is built. Both go in -a **parked "front-end / transitions / pause / watch" bucket** for one -dedicated session later (see `docs/PLAN-linear-level-sweep.md` WS2). - -**M-24 = keep doing in-level playtesting.** In-level crashes (D135/D137/D138 -type — pointer-width / ABI / GBI-parser bugs) are the real work and show up -during normal `-level_XX` play. - -1. **Resume WS6** `docs/LEVEL-PLAYTEST.md`, bare `-level_XX`. Do the in-level - ~80% (spawn, geometry, guards, doors, lifts, switches, pickups, each - difficulty-gated objective *registers* COMPLETE via `objective_status`, - alarms/reinforcements). **Defer** the exit-trigger / auto-advance / pause / - watch checks — those need the parked front-end bucket. Facility first - (it's playable start→~end now), then down the mission list. -2. Each new in-level crash: `tools_pc/repro_gdb.sh `, get the real - `bt full`, expect another `#ifdef PORT` ABI/layout fix - (D122/D126/D132/D135/D137/D139 pattern). Log as the next Dxx. -3. **Re-run `tools_pc/level_sweep.sh`** once — D135/D138 touch shared code - paths; sweep is boot-only so it only proves no *boot* regression. Refresh - `docs/LEVEL-STATUS.md`. -4. `bg.c` hit-test sibling port (§F D135 follow-up — identical unported GBI - parser to D135, `~3373-3646`) — do it before a level where wall-shooting - is heavy (i.e. soon). - -**Parked bucket — front-end / transitions (STARTED M-27):** -- ~~D140 (pause menu → `bondviewRenderWatch` → NULL `field_23C`)~~ FIXED M-27 - (+ D141, the watch-page crash it exposed). Pause now renders. -- D139 verify (stage unload / `cleanupObjects`) — still needs a real exit -- unpause / watch-page navigation (M-27 probe only tested opening it) -- level exit → MISSION COMPLETE → debrief → auto-advance to next briefing -- main menu / mission-select / difficulty-select playtest (WS2) -- the watch (objectives page, gadgets) — needed for the WS6 exit/objective - checks, D75 3D-model-transform family - -**Verification per fix:** target level boots to ≥1 non-degenerate frame -(`tools_pc/pixcount.py`), AND `-level_09` + `-level_20` unregressed -(`tools_pc/framediff.py`). Regen sidecars after any converter change. - -
M-22 and earlier "Next task" (superseded — §H authoritative) - --1. **D134 DONE (M-22)** — the frame-2 boot hang that every session since - M-13 wrote off as "sweep flakiness / D117 / machine load" was a **real, - fixed bug**: the SP/DP task-done event was posted `OS_MESG_NOBLOCK` into - the sched `interruptQ` that the 60 Hz VI pacemaker also fills, so a slow - synchronous fast3d frame let a retrace backlog swallow the done event → - permanent stall. Fix in `port/src/libultra.c` (`portPostEventForce` + - 2 reserved slots in `portPostVIEvent`). `-level_09` 6/6 boots to frame 600, - 0 heartbeats (pre-fix 1/3). §F/§H **D134**, PORT-LEARNINGS §E. - -0. **D132 DONE (M-21, commit `fa296b17`)** — propDef union-index slots for - types 14/19/38/44 (LINK/SWITCH/LOCK_DOOR/SAFE_ITEM) now emitted at the - right PC offsets (`d88_propdefs.py` + `loadobjectmodel.c sizepropdef` - PORT). - -1. **`-level_09` (BUNKER1) boot crash — DISPROVEN as a regression** (M-20, - coordinator: 3 runs / 1800 frames crash-free on `1fc3cff6`; re-confirmed - M-20 this pass: 690+ frames at 91.7% coverage). Was sweep flakiness / - D117 nondeterminism under machine load. Do not investigate. -2. **Intro "renders mostly black" is NOT a regression — it is the known - D75/D76 parked-cosmetic steady state** (M-20 / **D133**). Verified by - building M-17 (`9ec6121e`, whose handoff claimed "the entire intro - renders") in a scratch worktree and capturing the same `GE_PCDUMP` - window: coverage is **pixel-identical** to HEAD (legal screen 6677 - non-clear px = 2.17% both builds; logo-ish frames ~7%; black gaps in - between). The 2D/text layers draw; the animated character-model layers - (Nintendo-logo transform, gun-barrel Bond, cast models) never appear — - exactly D75. The M-17 "entire intro renders" handoff line was - aspirational, not a measured state. **Parked below level/crash work - (`docs/GRAPHICS-BACKLOG.md`).** Silo (#3, fly-down hang) is the same - class or D117 nondeterminism — not a fresh regression. -3. **Facility `-level_34` + Jungle `-level_37` re-verify FAILED this pass** - (M-20, machine lightly loaded). Facility: boot crash `frames=0`, PC - `0x1400c3b77` (addr2line unresolved). Jungle: renders frames 1–2 then - kernel-heartbeat hang ("no frame rendered", `frames=2`). Both were - claimed PASS in M-18/M-19 (D130/D131). **Needs a clean-machine - re-verify** before deciding regression vs. flakiness — see - `docs/LEVEL-STATUS.md`. `-level_09`/`-level_20` still PASS, so the - capture harness is sound. - -After that: all 21 load+render → hand `docs/LEVEL-PLAYTEST.md` to the -user for the WS6 completion pass (real input, per-level objective -checklist). - -
- -**Regen gotcha:** `d69_emit.py` rewrites `data/pccg-/pccg.bin` from -scratch with only the 52 bg/stan rows — you MUST follow it with -`d88_emit.py --regen` to re-add the 21 `Usetup*Z` rows, or every level -falls back to the raw N64 setup and crashes identically in -`proplvreset2`. Always run the full `d43 && d69 && d88 --regen` chain. - -Sweep note: `level_sweep.sh` was flaky this session (spurious NO-FRAMES on -known-good levels under machine load / the 24 s watchdog + D117 -nondeterminism). Verify individual levels with a 35–45 s window and -`GE_PCDUMP="60-500:40"` when a sweep row looks wrong. - -Sweep runner: `tools_pc/level_sweep.sh` (bare `-level_XX`, `GE_PCDUMP="80-260:40"`, -24 s watchdog, `taskkill //F //IM ge007.x86_64.exe`; **`export PATH=".../mingw64/bin:$PATH"` -before `addr2line`** — the script's own symbolication no-op'd without it). - -The M-13 matrix below is partially superseded by M-14 (see §H D125 for the current -12/21 status) — treat §H as authoritative. - -| Class | Levels | Site | -|---|---|---| -| **C1** | Dam 33, Runway 35, Frigate 26, Statue 22, Streets 29, Cradle 41 | `chrIsNotDeadOrShot` chraction.c:4483 — `self` = AI-list rodata ptr | -| **C2** | Facility 34, Jungle 37 | `import_texture_i8`/`gfx_tex_normalize_source` — bad tex ptr | -| **C3** | Aztec 28, Bunker2 27 | `propobj.c` door model / `linkedDoor` walk | -| **C4** | Depot 30 | `prop.c:902` `sp4C->room` after `walkTilesBetweenPoints` | -| **C5** | Control 23 | `bg.c:5723` `portal_pts->numPoints` | -| **C6** | Surface2 43 | `loadobjectmodel.c:393` `PitemZ_entries[modelid].header` (D122 cont.) | -| **C7** | Surface1 36 | `snd.c:653` `sndSetupSound` (audio — parked subsystem) | - -**Done M-13:** C1 fixed (**D123**, `tools_pc/d88_propdefs.py` — the C1 -crash was D122 fallout: widened `Vehichle/AircraftRecord.ailist` slot -zeroed instead of carrying its read-before-write int id). C2 split: -Jungle's texture crash fixed (**D124**, `port/src/gimgfixup.c` — compiled -explosion-DL sync keyed on an already-erased marker); Facility+Runway -diagnosed (model-GDL relocation misaligns `dst`, `objecthandler_2.c` / -`texLoadFromGdl`, D80/D82/D83 area) — NOT fixed. - -Full re-sweep after both: **12 / 21 PASS** (`docs/LEVEL-STATUS.md`). -9 crashes remain in 6 classes: **C2** Runway+Facility (model-GDL align — -biggest), **C3** Aztec+Bunker2 (`docs/BRIEF-C3-C6-prop-model.md`), -**C2m** Jungle (explosion-DL `G_MTX`), **C6** Surface2 (`PitemZ` modelid), -**C5** Control (BG portal), **C4** Depot (BG tile/room), **C7** Surface1 -(`sndSetupSound`). Priority order + files in `docs/LEVEL-STATUS.md` "Next". - -Uncommitted M-13 edits: `tools_pc/d88_propdefs.py`, `port/src/gimgfixup.c`, -§F/§H D123+D124, `docs/LEVEL-STATUS.md`, `docs/LEVEL-PLAYTEST.md`, -`docs/PORT-LEARNINGS.md`, `tools_pc/level_sweep.sh`, the BRIEF-C* files, -this file. Fold into the docs-restructure commit set. - -Re-run `tools_pc/level_sweep.sh` after each fix; keep `-level_09` + -`-level_20` green (`framediff.py`). All-21-PASS → hand the user -`docs/LEVEL-PLAYTEST.md` for WS6. - -**Verification per fix:** target level boots to ≥1 non-degenerate frame -(`tools_pc/pixcount.py` > a few %), AND `-level_09` + `-level_20` -unregressed (`tools_pc/framediff.py`). Regen 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`. - -**Parked (do NOT start here):** WS2 front-end menu playtest (needs a -human driving; cosmetics D75/D76/D77/D114/D116 all out of scope — -`GRAPHICS-BACKLOG.md`). Audio (Phase 3), saves (Phase 4). - -## Environment / build - -```sh -export PATH="/c/msys64/mingw64/bin:$PATH" && ./build-pc.sh ntsc-final # ~5 s -``` - -Run from the **repo root**, not `build-pc/`. - -- **Repro (BUNKER1, skips attract mode):** - `./build-pc/ge007.x86_64.exe -level_09` - (per-level `-m*` pool sizes are now auto-injected — D121/WS1; pass them - by hand only to override). `boss.c:337` decodes `-level_XX` as - `d0*10 + d1 - 0x210`; LEVELID == the `-level_XX` number. -- **Sidecar regen** (after any converter change; `data/` is gitignored): - ```sh - 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 - ``` - If `data/` is missing: `cp baserom.u.z64 data/ge007.ntsc-final.z64` - first (see §F "`data/` deletion + recovery"). -- **Frame capture:** `GE_PCDUMP="-:"` → `./ppm/` - (gitignored). Analyse with `tools_pc/pixcount.py` (non-black content - check) and `tools_pc/framediff.py ` (structural regression vs - `tools_pc/golden/` — the port is **NOT frame-deterministic**, D117; use - `--mask` for the HUD, not an exact compare). `tools_pc/ppm2bmp.py` to - view. -- **Crashes:** `ge007.crash.log` (repo root). Symbolicate - `addr2line -e build-pc/ge007.x86_64.exe -f -C ` (image base - `0x140000000`). Frames past the true chain may be stale (D56). -- **gdb:** launch mode is too slow for timing-dependent faults. **Attach** - mode is fast: `gdb -batch -x cmds.txt -p ` (`` = 4th - column of `ps -p `; game must already be running, e.g. `nohup - … &`). A hardware watchpoint on a global catches a bad write in < 1 min. -- Standalone probe compiles need `-std=c11`. - -## Non-negotiables (full list in AGENTS.md) - -1. N64 build files (`Makefile`, `tools/`, `rsp/`, `ld/`) untouched. -2. Game logic unmodified except narrow, documented `#ifdef PORT` - ABI/layout/format exceptions — each logged as a Dxx in §F/§H with the - N64 line kept verbatim under `#else`. Anything beyond a narrow, - obviously-correct exception: **stop and write it up** with a confidence - rating, don't hack it in. -3. Prefer an **offline sidecar converter** (`tools_pc/d*_emit.py`, - D43/D69/D88 pattern) over a runtime fixup for a whole ROM-asset format. diff --git a/docs/M-24-QOL-REVIEW.md b/docs/M-24-QOL-REVIEW.md deleted file mode 100644 index fcd71070..00000000 --- a/docs/M-24-QOL-REVIEW.md +++ /dev/null @@ -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 `` / `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). diff --git a/docs/M-25-QOL-REVIEW.md b/docs/M-25-QOL-REVIEW.md deleted file mode 100644 index 13a60e1e..00000000 --- a/docs/M-25-QOL-REVIEW.md +++ /dev/null @@ -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). diff --git a/docs/M-26-QOL-REVIEW.md b/docs/M-26-QOL-REVIEW.md deleted file mode 100644 index 2447aeb3..00000000 --- a/docs/M-26-QOL-REVIEW.md +++ /dev/null @@ -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. diff --git a/docs/PLAN-M6-playable.md b/docs/PLAN-M6-playable.md deleted file mode 100644 index bfead3c9..00000000 --- a/docs/PLAN-M6-playable.md +++ /dev/null @@ -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=":"` + - `tools_pc/pixcount.py` / `tools_pc/pixcount.py`. -- Sidecar regen (if a converter changes): `python tools_pc/d43_emit.py - ntsc-final`. diff --git a/docs/PLAN-linear-level-sweep.md b/docs/PLAN-linear-level-sweep.md deleted file mode 100644 index 8ef3ceb5..00000000 --- a/docs/PLAN-linear-level-sweep.md +++ /dev/null @@ -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 ` (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@ / 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). diff --git a/docs/SMALL-FIXES.md b/docs/SMALL-FIXES.md deleted file mode 100644 index 664c09a4..00000000 --- a/docs/SMALL-FIXES.md +++ /dev/null @@ -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 ` (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. diff --git a/docs/building.md b/docs/building.md new file mode 100644 index 00000000..93d052d8 --- /dev/null +++ b/docs/building.md @@ -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). diff --git a/docs/dev-process.md b/docs/dev-process.md new file mode 100644 index 00000000..ad368fcc --- /dev/null +++ b/docs/dev-process.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: . +READ FIRST: docs/porting-notes.md; docs/dev/findings.md . +FILES YOU MAY TOUCH: . +KNOWN-GOOD / RULED OUT: . +PRE-FLIGHT ATTACHED: . +BUDGET: 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. +``` diff --git a/docs/AUDIO-PLAN.md b/docs/dev/AUDIO-PLAN.md similarity index 98% rename from docs/AUDIO-PLAN.md rename to docs/dev/AUDIO-PLAN.md index 4ef08d63..eeb4da6a 100644 --- a/docs/AUDIO-PLAN.md +++ b/docs/dev/AUDIO-PLAN.md @@ -1,9 +1,9 @@ # Audio Plan — Phase 3 (libaudio → SDL) 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`, -`docs/PORT-LEARNINGS.md`. +`docs/porting-notes.md`. --- @@ -88,7 +88,7 @@ problem. | `aEnvMixer` | env.c:410/414 | yes — `aEnvMixerImpl` | | `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, resample table + interpolation, envelope ramping, clamps). **Not** reusable as-is: 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 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 - class — see `docs/PORT-LEARNINGS.md`). + class — see `docs/porting-notes.md`). - **Phase 3 — FX.** `aPoleFilter` + validate the full `reverb.c` chorus path against the disassembly. **Milestone: music matches N64 character (chorus/reverb present, no pumping); close D77.** - **Phase 4 — Polish & bookkeeping.** Resolve Q1; remove/adjust temporary probes and `GE_DAUDIO` defaults; latency/buffer tuning (`Audio.BufferSize`, `QueueLimit`); 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 - to `docs/PORT-LEARNINGS.md`. + to `docs/porting-notes.md`. ## 11. Verification ritual (audio-specific, on top of the standard one in AGENTS.md) diff --git a/docs/AUDIT-M6-player-offsets.md b/docs/dev/AUDIT-M6-player-offsets.md similarity index 98% rename from docs/AUDIT-M6-player-offsets.md rename to docs/dev/AUDIT-M6-player-offsets.md index 87041c99..77444ec7 100644 --- a/docs/AUDIT-M6-player-offsets.md +++ b/docs/dev/AUDIT-M6-player-offsets.md @@ -116,9 +116,9 @@ RW pool dedicated storage, and `gunRenderFirstPersonGunModels` / ## §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 -`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 accessors. MED = D102 weapon `render_pos` transient-arena aliasing (likely the ‘weapon model doesn’t draw’ cause) and the D56 watch-Model pool diff --git a/docs/GE-ENV-PROBES.md b/docs/dev/GE-ENV-PROBES.md similarity index 100% rename from docs/GE-ENV-PROBES.md rename to docs/dev/GE-ENV-PROBES.md diff --git a/docs/GRAPHICS-BACKLOG.md b/docs/dev/GRAPHICS-BACKLOG.md similarity index 97% rename from docs/GRAPHICS-BACKLOG.md rename to docs/dev/GRAPHICS-BACKLOG.md index 41d255a9..74ce6f0b 100644 --- a/docs/GRAPHICS-BACKLOG.md +++ b/docs/dev/GRAPHICS-BACKLOG.md @@ -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). | | **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. | | **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. | diff --git a/docs/HANDOFF-ARCHIVE.md b/docs/dev/HANDOFF-ARCHIVE.md similarity index 97% rename from docs/HANDOFF-ARCHIVE.md rename to docs/dev/HANDOFF-ARCHIVE.md index b2c83dbc..da394cd1 100644 --- a/docs/HANDOFF-ARCHIVE.md +++ b/docs/dev/HANDOFF-ARCHIVE.md @@ -1,7 +1,7 @@ # HANDOFF ARCHIVE — session-by-session narrative > **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 > prior sessions' handoff briefs (through session M-11), kept so the > reasoning behind a fix is recoverable. Everything below is as-written @@ -16,12 +16,12 @@ 1. `CLAUDE.md` (auto-loaded) + `AGENTS.md` non-negotiables. 2. This file, top-to-bottom. -3. `docs/PORT-LEARNINGS.md` — recurring bug classes. -4. Dispatching a subagent? `docs/AGENT-WORKFLOW.md` FIRST. Every brief needs +3. `docs/porting-notes.md` — recurring bug classes. +4. Dispatching a subagent? `docs/dev-process.md` FIRST. Every brief needs FILES / BUDGET / ON-EXPIRY / CONSTRAINTS / REPORT (see CLAUDE.md "Dispatch preflight"). _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 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. 4. **Rest of the `struct player` offset pass** — D115 fixed the HIGH `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 checks pointers/opcodes today — the D112 bug passed "ALL CHECKS PASSED"). 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). **Session M-6 — see §F D113/D114/D115, `docs/PLAN-M6-playable.md`, -`docs/AUDIT-M6-player-offsets.md`, `docs/AGENT-WORKFLOW.md`, -`docs/PORT-LEARNINGS.md`.** D113: portal BFS is correct, not the void. +`docs/dev/AUDIT-M6-player-offsets.md`, `docs/dev-process.md`, +`docs/porting-notes.md`.** D113: portal BFS is correct, not the void. D114: matrix chain + converter verified clean, residual = shared fast3d mirror (open). D115: player raw-offset audit + `gunfire.c` THROW* fix (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 hypothesis is D75(b) — the animated-model path (`animInit` + raw offsets 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** 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 `, `` = 4th column of `ps -p `) works well and is fast for "watch a global for a 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. ## Non-negotiables (unchanged, see AGENTS.md) @@ -509,7 +509,7 @@ locations attract mode happens to pick). 1. N64 build files untouched. 2. Game logic unmodified except narrow, documented `#ifdef PORT` 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 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 diff --git a/docs/LEVEL-OBJECTIVES.md b/docs/dev/LEVEL-OBJECTIVES.md similarity index 100% rename from docs/LEVEL-OBJECTIVES.md rename to docs/dev/LEVEL-OBJECTIVES.md diff --git a/docs/LEVEL-PLAYTEST.md b/docs/dev/LEVEL-PLAYTEST.md similarity index 100% rename from docs/LEVEL-PLAYTEST.md rename to docs/dev/LEVEL-PLAYTEST.md diff --git a/docs/LEVEL-STATUS.md b/docs/dev/LEVEL-STATUS.md similarity index 100% rename from docs/LEVEL-STATUS.md rename to docs/dev/LEVEL-STATUS.md diff --git a/docs/TEXTURE-GLITCH-ANALYSIS.md b/docs/dev/TEXTURE-GLITCH-ANALYSIS.md similarity index 99% rename from docs/TEXTURE-GLITCH-ANALYSIS.md rename to docs/dev/TEXTURE-GLITCH-ANALYSIS.md index 1385aa6a..95d07615 100644 --- a/docs/TEXTURE-GLITCH-ANALYSIS.md +++ b/docs/dev/TEXTURE-GLITCH-ANALYSIS.md @@ -1,7 +1,7 @@ # Texture Glitch Analysis — PC Port **Original diagnosis:** 2026-08-29 (read-only) · **Status table refreshed:** 2026-08-31 (M-31) -Companion artifacts: `CAPTURES_DIR` (outside repo). +Companion artifacts: `` (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 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 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 is an approximation of console TMEM-smear behaviour, not an exact emulation. -## 7. Artifacts (in `CAPTURES_DIR`, outside repo) +## 7. Artifacts (in ``, outside repo) | File | What it is | |------|-----------| diff --git a/docs/dev/agentic-development.md b/docs/dev/agentic-development.md new file mode 100644 index 00000000..76c04bf2 --- /dev/null +++ b/docs/dev/agentic-development.md @@ -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,
build + playtest verification] + C[Claude / Claude Code
frontier, hosted] + Q["Qwen 3.8 via pi
open-weight, local RTX 5090"] + D[(Shared artifacts:
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). diff --git a/docs/PCPortResearch.md b/docs/dev/findings.md similarity index 94% rename from docs/PCPortResearch.md rename to docs/dev/findings.md index be702f8a..ca835b90 100644 --- a/docs/PCPortResearch.md +++ b/docs/dev/findings.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 -GoldenEye 007, based on the decompilation in this repository -(`n64decomp/007`). It is modelled on the approach used by the -[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. +The raw, chronological engineering record for the port: every runtime bug, +its root cause, and the fix. Kept unedited for transparency. Recurring +patterns are distilled in [../porting-notes.md](../porting-notes.md). --- - -## 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 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. **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). **Fixed:** `gunfire.c:4960-4962` `THROWMTX` / `THROWPOS(k)` / `THROWPREV(k)` 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 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]` trace showing 32 slots going `abcd08xx -> 700e****`, verified render). 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` 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 -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 sidecar change — **sidecars do NOT need regen**, tree builds clean at `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** (91.5% / 79.8% / 70.4%); Bunker1/Silo/Aztec/Archives/Egypt/Train 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 (`sndSetupSound`). diff --git a/docs/img/archives-1.png b/docs/img/archives-1.png new file mode 100644 index 00000000..96052755 Binary files /dev/null and b/docs/img/archives-1.png differ diff --git a/docs/img/archives-2.png b/docs/img/archives-2.png new file mode 100644 index 00000000..806909d6 Binary files /dev/null and b/docs/img/archives-2.png differ diff --git a/docs/img/bunker1-1.png b/docs/img/bunker1-1.png new file mode 100644 index 00000000..682726a3 Binary files /dev/null and b/docs/img/bunker1-1.png differ diff --git a/docs/img/bunker1-2.png b/docs/img/bunker1-2.png new file mode 100644 index 00000000..dc7c7a0c Binary files /dev/null and b/docs/img/bunker1-2.png differ diff --git a/docs/img/bunker1-3.png b/docs/img/bunker1-3.png new file mode 100644 index 00000000..4a6c99ae Binary files /dev/null and b/docs/img/bunker1-3.png differ diff --git a/docs/internals.md b/docs/internals.md new file mode 100644 index 00000000..767c7c1b --- /dev/null +++ b/docs/internals.md @@ -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` + +--- diff --git a/docs/PORT-LEARNINGS.md b/docs/porting-notes.md similarity index 98% rename from docs/PORT-LEARNINGS.md rename to docs/porting-notes.md index 9ca6c9a7..46d55743 100644 --- a/docs/PORT-LEARNINGS.md +++ b/docs/porting-notes.md @@ -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 -subagent reads this first and appends any new generalisable quirk.** -Details, evidence, and the fix for each instance live in §F under the -cited Dxx label. +A one-screen field guide to the bug classes that keep recurring when running +big-endian 32-bit N64 game code, unmodified, on a little-endian 64-bit host. +Each entry cites a `Dxx` label; the full evidence and fix for that instance +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 @@ -30,7 +33,7 @@ state. `.act_*.attack_item` — aliased `WeaponObjRecord.weaponnum` at 0x80 on N64 via act-union@0x2C+84; act union moves to ~0x38 on PC). - **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 record.** If a fixed-size ROM record ends with a `T *next` (or `*child`, `*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 OR the asymmetric-1-texel-texture experiment — nothing else.** Cosmetic, 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 @@ -492,7 +495,7 @@ past it (walk off the end). `tools_pc/pixcount.py` vs `docs/reference/n64-footage-*`). - gdb **launch** mode is too slow for timing-dependent faults; gdb **attach** to an already-running process is fine and fast. -- Check `PD_PORT_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. - Don't re-investigate a closed §F finding or re-derive a format spec that already has a converter. diff --git a/docs/reference/Decompiling a Nintendo 64 Game in 84 Days _ Chris' Blog.htm b/docs/reference/Decompiling a Nintendo 64 Game in 84 Days _ Chris' Blog.htm deleted file mode 100644 index 62601afe..00000000 --- a/docs/reference/Decompiling a Nintendo 64 Game in 84 Days _ Chris' Blog.htm +++ /dev/null @@ -1,510 +0,0 @@ - - -Decompiling a Nintendo 64 Game in 84 Days | Chris' Blog

Chris' Blog

Decompiling a Nintendo 64 Game in 84 Days

I’m very pleased to announce that the original Snowboard Kids is now 100% decompiled! This means that all functions1 have matching C implementations that, when compiled, produce identical machine code to the original game.

This was obviously not a one-person effort. I am particularly grateful to inspectredc, Bl00D4NGEL, and queueRAM for their significant contributions to the project. No amount of AI would have been able to replace them.2 I would also like to thank iFuzzle, JamesBLewis, and douglasjv for lending their tokens to the cause.

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.

The speed at which the project was finished is also noteworthy. Snowboard Kids took only 84 days to decompile compared with 596 days for Snowboard Kids 2, roughly one-seventh of the elapsed time.3

chart showing weekly Snowboard Kids decompilation progress

Weekly progress of the Snowboard Kids decompilation.

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.

What Was Different

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.

Overall, roughly 4.8% of matching commits involved expert intervention.4 - 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 inspectredc, queueRAM, and Bl00D4NGEL <3.

The difficulty generally didn’t come from trying to understand what a function did5 but rather how that logic was expressed in C and how the resulting code was compiled. Snowboard Kids was compiled with IDO 5.3 rather than the GCC 2.7.2 compiler used by Snowboard Kids 2.

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.6 - 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 decompile parts of the compiler toolchain as well as statically recompile the IDO 5.3 and 7.1 suites to run on modern hardware. That closed history makes IDO harder to reason about than an open compiler such as GCC.

IDO splits optimisation and code generation across several different passes, transforming code quite aggressively along the way.7 Tiny changes to the C can then ripple through those passes and produce a completely different register allocation.

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 permuter - 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.

A motivated human team with the right expertise and intuition can match or even exceed the pace of the Snowboard Kids decompilation. The Pilotwings 64 decompilation was completed in only 74 days.8 Pilotwings 64 had 16% fewer functions than Snowboard Kids, but more compiled code overall, so this is not a clean comparison either.

screenshot of Pilotwings 64

Pilotwings 64, a charming flight simulator, was a Nintendo 64 launch title and remains a cult classic to this day.

Snowboard Kids was also smaller than its sequel, containing 2,145 functions compared with 2,995 in Snowboard Kids 2. Function count is a crude measure of difficulty, but there were simply fewer functions to decompile.

Where Agents Did Help

I’ve already written elsewhere - 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 agent harness.

Library Code and Other Low-Hanging Fruit

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. Snowboard Kids contains more than a hundred source segments from Nintendo’s libultra, alongside functions from the libmus audio library. Tools such as N64Sym can identify probable library functions in the ROM.

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.

Another optimisation was to have an agent write a script that ran m2c - against every unmatched function and automatically integrated any exact - matches, rather than relying on agents to attempt those functions -individually.9 - 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.

IDO Tooling and Skills

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 memories. To make those lessons available beyond a single agent, I prompted agents to record observed IDO behaviour in a DECOMPILATION_LEARNINGS.md - 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.

But the most useful resource was N64 Decomp Workbench, - 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.

Worktrees and Synchronisation

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.

One small but useful improvement was to give every task an explicit deadline and expose that deadline to the agent. During the Snowboard Kids 2 - 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.

Another problem that had existed in Snowboard Kids 2 but became more apparent as I added worktrees was synchronisation. As discussed in my previous post, - 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 --shard option, which uses basic hashing to partition candidates between a specified number of workers.

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.

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.

1✓ Candidate ["func_80094A94", "func_80094FF4 (../sbk-c)",
-2  "func_80094808", "func_8009491C (../sbk-a)",
-3  "func_8009469C (../sbk-c)", "func_80093144"] was fixed!
-

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.

Model Choice

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.

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.

What Next?

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.

Work is also underway on a Snowboard Kids recompilation. Fortunately, the first game shares many of the quirks addressed by patches in Snowboard Kids 2: Recompiled.

early screenshot of Snowboard Kids: Recompiled

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.

Beyond that, I’m interested in decompiling Snowboard Kids Plus on the PlayStation, a Japan-exclusive expanded release of the first game with extra levels and characters.

If you’ve made it this far, you’re probably interested in decompilation and Snowboard Kids. Take a look at the Snowboard Kids decompilation project. There is still plenty of cleanup and documentation work to do, and contributions are very welcome.


  1. Almost. - A small amount of handwritten assembly from system libraries remains -because assembly was the original source rather than a temporary -decompilation placeholder. ↩︎

  2. In the best case, I think the project would have stalled around 89–90% without their IDO expertise. ↩︎

  3. For Snowboard Kids 2, I measured from the first commit on 28 September 2024 to the final matching function on 17 May 2026, a span of 596 days. The 84-day figure for Snowboard Kids is my project-level count; the public repository itself spans from the first commit on 28 May 2026 to the final matching function on 19 August 2026, just over 83 days. ↩︎

  4. 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”. ↩︎

  5. One notable exception was validateControllerPakSave, - which appears to have been used to debug Controller Pak saves. Most of -its debugging and validation logic was omitted from the final -build. ↩︎

  6. Nintendo has described - adopting an architecture from Silicon Graphics for the Nintendo 64. The - original development system was similarly SGI-centric. Nintendo’s development documentation - 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. ↩︎

  7. IDO used a multi-stage compilation pipeline. Its uopt documentation - 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 🫠. ↩︎

  8. Measured from the addition of progress tracking on 26 January 2026 to the final matching functions on 10 April 2026, the decompilation took almost exactly 74 days. Pilotwings 64 had 1,791 functions compared with 2,145 for Snowboard Kids, but approximately 838 KB of code compared with 732 KB for Snowboard Kids. ↩︎

  9. Using a one-shot script like this is not a particularly novel idea. I’m pretty sure I copied it from somewhere, possibly Diddy Kong Racing’s m2c_all.sh. ↩︎

Comments

Loading Bluesky comments...

<< Previous Post

|

Next Post >>

-
-
-
-
- -
-
Fetching file...
-
-
- -
-
-
-
-
-
-
-
 Click to open IoC report 
with VirusTotal Augment
\ No newline at end of file diff --git a/docs/reference/n64-footage-2026-08-24.jpg b/docs/reference/n64-footage-2026-08-24.jpg deleted file mode 100644 index cdd16f36..00000000 Binary files a/docs/reference/n64-footage-2026-08-24.jpg and /dev/null differ diff --git a/docs/reference/n64-footage-gun-barrel.png b/docs/reference/n64-footage-gun-barrel.png deleted file mode 100644 index 7668de42..00000000 Binary files a/docs/reference/n64-footage-gun-barrel.png and /dev/null differ diff --git a/port/README.md b/port/README.md index e4f3ee90..d6297ecf 100644 --- a/port/README.md +++ b/port/README.md @@ -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. It mirrors the layout of the [Perfect Dark PC -port](https://github.com/perfect-dark-pc-port/perfect_dark), which ports the -same Rare engine family. See `docs/PCPortResearch.md` (repo root) for the full +port](https://github.com/fgsfdsfgs/perfect_dark), which ports the +same Rare engine family. See `docs/internals.md` (repo root) for the full research write-up and phased plan. ## Layout @@ -66,5 +66,5 @@ exact command encodings. **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 -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. diff --git a/port/fast3d/README.md b/port/fast3d/README.md index 17c31794..109fc563 100644 --- a/port/fast3d/README.md +++ b/port/fast3d/README.md @@ -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 -the Gfx display list the game builds and emits OpenGL calls, bypassing the N64 -RDP entirely. +A C++ interpreter that consumes the `Gfx` display list the game builds each +frame and emits OpenGL calls, replacing the Nintendo 64's Reality Signal +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 -`perfect-dark-pc-port/perfect_dark/port/fast3d/`: +This code is **adapted from the [Perfect Dark PC +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 | -|--------------------|-------------------------------------------------------------|--------| -| `gfx_pc.cpp` | The RSP interpreter: matrices, lights, fog, tri1/tri4, vtx | 2800 | -| `gfx_opengl.cpp` | OpenGL backend: shaders, textures, zbuf, framebuffers | 1300 | -| `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 | — | +`LICENSE.txt` in this directory (MIT, © 2020 Emill, MaikelChan) covers that +lineage. `glad/` is generated by [glad](https://github.com/Dav1dde/glad) +(© David Herberth, MIT). GoldenEye-specific changes in this directory are +under the repository's top-level MIT license. -**The PD fast3d already handles `G_TRI4`** (GE's 4-triangle command) — the PD -game uses it too. So the bulk of the RSP work is done; we adapt, not rewrite. +## Files -## 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 - (`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. +## GoldenEye-specific work -## Ground truth - -`rsp/graphics/gmain.s` (repo root) is the real GE RSP ucode. We do **not** run -it on PC, but it is the authoritative reference for the exact command -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. +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 +extension commands. `rsp/graphics/gmain.s` (GE's real RSP microcode, in the +repo root) is the ground truth for the exact command encodings. diff --git a/port/fast3d/gbiex.h b/port/fast3d/gbiex.h index 03c22d8c..1246b655 100644 --- a/port/fast3d/gbiex.h +++ b/port/fast3d/gbiex.h @@ -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 * (pd_port/src/include/gbiex.h + PD's include/PR/gbi.h). GoldenEye shares the diff --git a/port/fast3d/gfx_pc.cpp b/port/fast3d/gfx_pc.cpp index 16d70136..f395f564 100644 --- a/port/fast3d/gfx_pc.cpp +++ b/port/fast3d/gfx_pc.cpp @@ -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 // blobs (cart extension 0x10Cxxxxx), KSEG0 mirror (0x80xxxxxx) and V1 // 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.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- * inherited block overwrote the authored tc[] UVs on every * lit+textured surface (lookat_enabled defaults to true here), diff --git a/port/include/gimgfixup.h b/port/include/gimgfixup.h index 7faccc5f..5ec53908 100644 --- a/port/include/gimgfixup.h +++ b/port/include/gimgfixup.h @@ -3,7 +3,7 @@ #include -/* 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 * u32 in the ROM copy is BE-encoded; PC consumers read them natively (LE). * diff --git a/port/include/hoststdlib.h.in b/port/include/hoststdlib.h.in index 53f13114..c08eb265 100644 --- a/port/include/hoststdlib.h.in +++ b/port/include/hoststdlib.h.in @@ -3,6 +3,6 @@ /* Generated by CMake from hoststdlib.h.in — do not edit. * Includes the HOST by absolute path so that port/shim/stdlib.h * 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@" #endif diff --git a/port/include/hoststring.h.in b/port/include/hoststring.h.in index a407d609..cebb0a46 100644 --- a/port/include/hoststring.h.in +++ b/port/include/hoststring.h.in @@ -4,6 +4,6 @@ * Includes the HOST by absolute path so that port/shim/string.h * can bypass the N64 include/string.h (which declares strlen/strchr with * `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@" #endif diff --git a/port/include/pc_protos.h b/port/include/pc_protos.h index 13221613..15587143 100644 --- a/port/include/pc_protos.h +++ b/port/include/pc_protos.h @@ -1,7 +1,7 @@ /* * 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 * 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 diff --git a/port/include/pccg.h b/port/include/pccg.h index e90eeb1b..c6ee8d6f 100644 --- a/port/include/pccg.h +++ b/port/include/pccg.h @@ -7,7 +7,7 @@ extern "C" { #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. * * N64 bg/*.seg and Tbg_*_stanZ files are big-endian images with internal diff --git a/port/include/pcmodels.h b/port/include/pcmodels.h index 16fbd04e..523d1656 100644 --- a/port/include/pcmodels.h +++ b/port/include/pcmodels.h @@ -7,7 +7,7 @@ extern "C" { #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 * cannot read in place (D37/D43). tools_pc/d43_emit.py converts all of them diff --git a/port/include/realzlib.h.in b/port/include/realzlib.h.in index f204f948..5e9acc45 100644 --- a/port/include/realzlib.h.in +++ b/port/include/realzlib.h.in @@ -5,6 +5,6 @@ * 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 * `#include ` 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@" #endif diff --git a/port/include/romdata.h b/port/include/romdata.h index e969d93f..7cd41be5 100644 --- a/port/include/romdata.h +++ b/port/include/romdata.h @@ -37,7 +37,7 @@ u32 romdataGetRomSize(void); 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 * BE->LE transform of the animation data segment after romCopy() and before * 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); /* - * 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: * u32 address offset, u16 uncompressed_len, u16 len. Per-field BE->LE * 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); /* - * 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; * see romdata.c). Call after decompressdata(), before alCSeqNew(). */ 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 * decompressed bank size (resource_lookup_data_array[].poolRemaining). */ 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 * the PC C layout, shifting the pixel block below the expanded char array. * 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); /* - * 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 / * ALEnvelope / ALKeyMap / ALADPCMloop / ALADPCMBook) are serialized in ROM * as big-endian scalars with 4-byte packed table-relative offsets where the diff --git a/port/shim/CPPLib.h b/port/shim/CPPLib.h index f2e3f052..1d9901e2 100644 --- a/port/shim/CPPLib.h +++ b/port/shim/CPPLib.h @@ -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: * diff --git a/port/shim/PR/R4300.h b/port/shim/PR/R4300.h index 0e8eeac5..12fdb450 100644 --- a/port/shim/PR/R4300.h +++ b/port/shim/PR/R4300.h @@ -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 * address by OR'ing in 0x80000000. On the PC port the game's working RAM diff --git a/port/shim/PR/gbi.h b/port/shim/PR/gbi.h index 482ed348..ca4a6f92 100644 --- a/port/shim/PR/gbi.h +++ b/port/shim/PR/gbi.h @@ -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 * gSP* macros that pack full 64-bit pointers into words.w1. However, the diff --git a/port/shim/PR/os.h b/port/shim/PR/os.h index 2a4f4976..5378e3ec 100644 --- a/port/shim/PR/os.h +++ b/port/shim/PR/os.h @@ -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 * ("V1"), with a byte-identical KSEG0 mirror at 0x80000000 ("V2"); see diff --git a/port/shim/bondaicommands.h b/port/shim/bondaicommands.h index cf83b947..80b888f6 100644 --- a/port/shim/bondaicommands.h +++ b/port/shim/bondaicommands.h @@ -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 * 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. */ #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 ------------------------ * diff --git a/port/shim/bondconstants.h b/port/shim/bondconstants.h index fc50b53d..c876c076 100644 --- a/port/shim/bondconstants.h +++ b/port/shim/bondconstants.h @@ -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 * under `#ifdef __sgi` (the N64/IDO compiler); on other compilers it is a diff --git a/port/shim/bondtypes.h b/port/shim/bondtypes.h index 0f7ddc5e..cfe6596f 100644 --- a/port/shim/bondtypes.h +++ b/port/shim/bondtypes.h @@ -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, * but chrobjdata.h declares extern arrays of ItemModelFileRecord / @@ -27,7 +27,7 @@ * 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 * 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_Coord3d #define New_Vector(...) {0, 0, 0} diff --git a/port/shim/stdlib.h b/port/shim/stdlib.h index 9ef24236..868956c1 100644 --- a/port/shim/stdlib.h +++ b/port/shim/stdlib.h @@ -9,7 +9,7 @@ * `#include `, 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++ * 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). */ diff --git a/port/shim/string.h b/port/shim/string.h index 1bd905b5..a86371b4 100644 --- a/port/shim/string.h +++ b/port/shim/string.h @@ -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: * diff --git a/port/src/gimgfixup.c b/port/src/gimgfixup.c index 1b6c02ba..daa3b380 100644 --- a/port/src/gimgfixup.c +++ b/port/src/gimgfixup.c @@ -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. * * The offset tables below must stay in lockstep with the g_pc_gimg_off_* diff --git a/port/src/input.c b/port/src/input.c index 8f137830..0407d310 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -14,7 +14,7 @@ * 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 * C-buttons = aim/turn/look (DIGITAL on N64), R = aim mode, Z = fire. diff --git a/port/src/libultra.c b/port/src/libultra.c index 52622bc5..3a20d356 100644 --- a/port/src/libultra.c +++ b/port/src/libultra.c @@ -9,7 +9,7 @@ * osRecvMesg() waits on a per-queue condition variable. (An earlier * setjmp/longjmp green-thread kernel corrupted FPU state across longjmps * 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 * post the RSP_DONE / RDP_DONE messages sched.c waits for. * - VI retrace is a vsync-paced tick posted by a dedicated kernel thread, diff --git a/port/src/main.c b/port/src/main.c index 71992d1d..878a947c 100644 --- a/port/src/main.c +++ b/port/src/main.c @@ -9,7 +9,7 @@ * 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 * 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 * the process (Windows only dispatches window messages to the creating * thread, and every game thread can be blocked on a queue). diff --git a/port/src/n64stubs.c b/port/src/n64stubs.c index d727d57b..d85aa77c 100644 --- a/port/src/n64stubs.c +++ b/port/src/n64stubs.c @@ -1,7 +1,7 @@ /* * 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 * 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 diff --git a/port/src/pc_netorder.c b/port/src/pc_netorder.c index a9c74bb4..d8f7eae8 100644 --- a/port/src/pc_netorder.c +++ b/port/src/pc_netorder.c @@ -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 * CharArrayTo16/32 ("rewrite these to use char array as system provided funcs diff --git a/port/src/pccg.c b/port/src/pccg.c index a126938f..39c1a1d3 100644 --- a/port/src/pccg.c +++ b/port/src/pccg.c @@ -93,7 +93,7 @@ uint32_t pccgReserveSize(const uint8_t *romImg) if (!pccgFindDataFile(rel, s_binPath, sizeof(s_binPath))) { sysLogPrintf(LOG_WARNING, "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); return 0; } diff --git a/port/src/pcmodels.c b/port/src/pcmodels.c index de58b6df..247a8694 100644 --- a/port/src/pcmodels.c +++ b/port/src/pcmodels.c @@ -101,7 +101,7 @@ uint32_t pcmodelsReserveSize(const uint8_t *romImg) if (!pcmodelsFindDataFile(rel, s_binPath, sizeof(s_binPath))) { sysLogPrintf(LOG_WARNING, "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); return 0; } diff --git a/port/src/romdata.c b/port/src/romdata.c index df919050..c35fc09a 100644 --- a/port/src/romdata.c +++ b/port/src/romdata.c @@ -51,7 +51,7 @@ static u8 *rom = NULL; /* heap copy (fallback path) */ static u32 romSize = 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 * 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, @@ -184,7 +184,7 @@ int romdataInit(void) pcmodelsLoadSidecars(CART_BASE, romSize); 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_.s) is stored in the ROM with a * big-endian w/h header (01 B8 01 2B = 440x299), but * 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 * uniform word transform exists (mixed field widths), so the animation_data * 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: * * 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 * big-endian u32 division. N64 reads them natively; on a little-endian host * 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 * 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 @@ -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 * pointers: * [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 / * ALEnvelope / ALKeyMap / ALADPCMloop / ALRawLoop / ALADPCMBook) to * PC-native form. See the header for the contract. diff --git a/port/src/rzdecomp.c b/port/src/rzdecomp.c index 840ad55d..138c1279 100644 --- a/port/src/rzdecomp.c +++ b/port/src/rzdecomp.c @@ -1,6 +1,6 @@ /* * 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 * stream that ends with an end-of-block marker. The original decompressdata() diff --git a/port/src/video.c b/port/src/video.c index 8de2018c..28a3b01e 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -277,7 +277,7 @@ void videoEndFrame(void) } 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 * (one frame behind real time — read happens after SwapWindow). Used to * pixel-verify the intro's 3D content. Also honours [Debug] FrameDump in diff --git a/readme.md b/readme.md deleted file mode 100644 index f8f28b1b..00000000 --- a/readme.md +++ /dev/null @@ -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 diff --git a/scripts/gen_pcprotos.py b/scripts/gen_pcprotos.py index 71ea3e4c..ec215780 100644 --- a/scripts/gen_pcprotos.py +++ b/scripts/gen_pcprotos.py @@ -1,5 +1,5 @@ #!/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): 1. Compile the game TUs and collect `implicit function declaration` warnings @@ -50,7 +50,7 @@ for n in sorted(names): hdr = '''/* * 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 * 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 diff --git a/scripts/gen_romassets.py b/scripts/gen_romassets.py index f603c0b9..4ab9afe2 100644 --- a/scripts/gen_romassets.py +++ b/scripts/gen_romassets.py @@ -362,7 +362,7 @@ def build_segments(region, csv, obseg_resolved, ramrom_files, music_tracks, off, size = csv1("animationtable_data.bin") seg["animation_data"] = (off, size, True) 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 # oddtextures.o (.data) here — Gfx DLs + 32 sImageTableEntry tables = # 0x13F8 bytes. texReset() copies End-Start, so the marker must span the diff --git a/scripts/test_files_readme.md b/scripts/test_files_readme.md index 66f1be0a..57e5becf 100644 --- a/scripts/test_files_readme.md +++ b/scripts/test_files_readme.md @@ -1,112 +1,112 @@ -### test files readme ### - -This is a tool suite to identify discrepancies between builds. It will extract relevant sections from ELF binary and compare against known good md5. The purpose is to help identify where the build is failing to match. - -### Quick start ### - - ./test_files.sh -i ge007.u-test_basis.csv - -### Overview ### - -This consists of two bash scripts: - - make/build_hashtable.sh - test_files.sh - -The first is used to generate a list of md5 checksums. This should only be used if a new build is added, or you want to generate checksums against a particular variant. - -### Making test file ### - -Output format is simple csv, one entry per line. Each line is a 32 character md5, the name of section extracted from ELF binary, and path to file relative from repo root (where this script is). - -Escaped csv, quotes or commas in filenames are not supported. - -The script iterates the following build directories, looking for `.o` files: - -- src/ -- src/game -- assets/obseg/bg -- assets/obseg/brief -- assets/obseg/setup -- assets/obseg/stan -- assets/obseg/text - -For source files, the following ELF sections are extracted: - -- .text -- .code -- .bss -- .data -- .rodata - -For asset files, the following ELF sections are extracted: - -- .bss -- .data -- .rodata - -The supported versions have already had the md5 checksums extracted from known good builds. These files are: - -- ge007.u-test_basis.csv -- ge007.j-test_basis.csv -- ge007.e-test_basis.csv - -**build test file example** - - ./make/build_hashtable.sh -v u -o ge007.u-test_basis.csv - ./make/build_hashtable.sh -v j -o ge007.j-test_basis.csv - ./make/build_hashtable.sh -v e -o ge007.e-test_basis.csv - -### Testing build ### - -A build can be compared against a test file by running `./test_files.sh`. The input file is required. This will This lists the path to the build object files. Example - - ./test_files.sh -i ge007.u-test_basis.csv - -This lists each section and file as it is tested. You should see output like - - ... - pass: section'.bss' build/u/assets/obseg/text/LsiloJ.o - pass: section'.data' build/u/assets/obseg/text/LsiloJ.o - pass: section'.rodata' build/u/assets/obseg/text/LsiloJ.o - pass: section'.bss' build/u/assets/obseg/text/LstatE.o - pass: section'.data' build/u/assets/obseg/text/LstatE.o - ... - -The output stops on the first failure. Use option `-c` to continue on mismatch. - -### Examples ### - -**Rename function**: - -Modify `src/game/bg.c` method `bgRectIsInside` (an unreferenced method). - -*result*: all files pass. - -**Change function**: - -Modify `src/game/bg.c` method `bgRectIsInside` (an unreferenced method) to return a different value. Rebuild the project and test. - -*result*: Test script should stop with a message similar to the following: - - checksums differ, section'.text', file: 'build/u/src/game/bg.o'. Actual=[6747949109773c07983a2584101ef214], expected=[0a3e9300d0406ae8036a97b5596f3f5c] - -**change .rodata** - -Modify `src/fr.c` method `indyGrabJpg16bit` and change one of the printf strings: - - sprintf(buffer, "zzzgrab.%d.jpeg", *pgrabnum); - -*result*: Test script should stop with a message similar to the following: - - checksums differ, section'.rodata', file: 'build/u/src/fr.o'. Actual=[9e108ace3e0dab31c819d22ed1d063b9], expected=[94e2bec0d83b53385ba626feb3005cb4] - -**Change setup file** - -Modify `assets/obseg/setup/Ump_setupcaveZ.c` padlist and change one of the float variables. Here is modifying the first entry in the array: - - { {1.0f, -726.0f, -378.0f}, {0.0f, 1.0f, 0.0f}, {-2e-06, 0.0f, 1.0f}, "p1884a", 0 }, - -*result*: Test script should stop with a message similar to the following: - - checksums differ, section'.data', file: 'build/u/assets/obseg/setup/Ump_setupcaveZ.o'. Actual=[1dd1b59ed38408ba67b6616f9194c6d9], expected=[9caabe90e7f9b62668b607b286e14738] +### test files readme ### + +This is a tool suite to identify discrepancies between builds. It will extract relevant sections from ELF binary and compare against known good md5. The purpose is to help identify where the build is failing to match. + +### Quick start ### + + ./test_files.sh -i ge007.u-test_basis.csv + +### Overview ### + +This consists of two bash scripts: + + make/build_hashtable.sh + test_files.sh + +The first is used to generate a list of md5 checksums. This should only be used if a new build is added, or you want to generate checksums against a particular variant. + +### Making test file ### + +Output format is simple csv, one entry per line. Each line is a 32 character md5, the name of section extracted from ELF binary, and path to file relative from repo root (where this script is). + +Escaped csv, quotes or commas in filenames are not supported. + +The script iterates the following build directories, looking for `.o` files: + +- src/ +- src/game +- assets/obseg/bg +- assets/obseg/brief +- assets/obseg/setup +- assets/obseg/stan +- assets/obseg/text + +For source files, the following ELF sections are extracted: + +- .text +- .code +- .bss +- .data +- .rodata + +For asset files, the following ELF sections are extracted: + +- .bss +- .data +- .rodata + +The supported versions have already had the md5 checksums extracted from known good builds. These files are: + +- ge007.u-test_basis.csv +- ge007.j-test_basis.csv +- ge007.e-test_basis.csv + +**build test file example** + + ./make/build_hashtable.sh -v u -o ge007.u-test_basis.csv + ./make/build_hashtable.sh -v j -o ge007.j-test_basis.csv + ./make/build_hashtable.sh -v e -o ge007.e-test_basis.csv + +### Testing build ### + +A build can be compared against a test file by running `./test_files.sh`. The input file is required. This will This lists the path to the build object files. Example + + ./test_files.sh -i ge007.u-test_basis.csv + +This lists each section and file as it is tested. You should see output like + + ... + pass: section'.bss' build/u/assets/obseg/text/LsiloJ.o + pass: section'.data' build/u/assets/obseg/text/LsiloJ.o + pass: section'.rodata' build/u/assets/obseg/text/LsiloJ.o + pass: section'.bss' build/u/assets/obseg/text/LstatE.o + pass: section'.data' build/u/assets/obseg/text/LstatE.o + ... + +The output stops on the first failure. Use option `-c` to continue on mismatch. + +### Examples ### + +**Rename function**: + +Modify `src/game/bg.c` method `bgRectIsInside` (an unreferenced method). + +*result*: all files pass. + +**Change function**: + +Modify `src/game/bg.c` method `bgRectIsInside` (an unreferenced method) to return a different value. Rebuild the project and test. + +*result*: Test script should stop with a message similar to the following: + + checksums differ, section'.text', file: 'build/u/src/game/bg.o'. Actual=[6747949109773c07983a2584101ef214], expected=[0a3e9300d0406ae8036a97b5596f3f5c] + +**change .rodata** + +Modify `src/fr.c` method `indyGrabJpg16bit` and change one of the printf strings: + + sprintf(buffer, "zzzgrab.%d.jpeg", *pgrabnum); + +*result*: Test script should stop with a message similar to the following: + + checksums differ, section'.rodata', file: 'build/u/src/fr.o'. Actual=[9e108ace3e0dab31c819d22ed1d063b9], expected=[94e2bec0d83b53385ba626feb3005cb4] + +**Change setup file** + +Modify `assets/obseg/setup/Ump_setupcaveZ.c` padlist and change one of the float variables. Here is modifying the first entry in the array: + + { {1.0f, -726.0f, -378.0f}, {0.0f, 1.0f, 0.0f}, {-2e-06, 0.0f, 1.0f}, "p1884a", 0 }, + +*result*: Test script should stop with a message similar to the following: + + checksums differ, section'.data', file: 'build/u/assets/obseg/setup/Ump_setupcaveZ.o'. Actual=[1dd1b59ed38408ba67b6616f9194c6d9], expected=[9caabe90e7f9b62668b607b286e14738] diff --git a/src/libultra/libc/bcopy.s b/src/libultra/libc/bcopy.s index 79c4138e..f658aa98 100644 --- a/src/libultra/libc/bcopy.s +++ b/src/libultra/libc/bcopy.s @@ -1,233 +1,233 @@ -# asm file. -# -# TODO: this is an -O1 match, the libultra version is -O2 using IDO `as` toolchain. - -# assembler directives -.set noat # allow manual use of $at -.set noreorder # don't insert nops after branches -.set gp=64 - -.include "macros.inc" - - -.section .text, "ax" -glabel bcopy -/* 010570 7000F970 10C0001A */ beqz $a2, .L7000F9DC -/* 010574 7000F974 00A03825 */ move $a3, $a1 -/* 010578 7000F978 10850018 */ beq $a0, $a1, .L7000F9DC -/* 01057C 7000F97C 00A4082A */ slt $at, $a1, $a0 -/* 010580 7000F980 54200008 */ bnezl $at, .L7000F9A4 -/* 010584 7000F984 28C10010 */ slti $at, $a2, 0x10 -/* 010588 7000F988 00861020 */ add $v0, $a0, $a2 -/* 01058C 7000F98C 00A2082A */ slt $at, $a1, $v0 -/* 010590 7000F990 50200004 */ beql $at, $zero, .L7000F9A4 -/* 010594 7000F994 28C10010 */ slti $at, $a2, 0x10 -/* 010598 7000F998 1000005B */ b .L7000FB08 -/* 01059C 7000F99C 28C10010 */ slti $at, $a2, 0x10 -/* 0105A0 7000F9A0 28C10010 */ slti $at, $a2, 0x10 -.L7000F9A4: -/* 0105A4 7000F9A4 14200005 */ bnez $at, .L7000F9BC -/* 0105A8 7000F9A8 00000000 */ nop -/* 0105AC 7000F9AC 30820003 */ andi $v0, $a0, 3 -/* 0105B0 7000F9B0 30A30003 */ andi $v1, $a1, 3 -/* 0105B4 7000F9B4 1043000B */ beq $v0, $v1, .L7000F9E4 -/* 0105B8 7000F9B8 00000000 */ nop -.L7000F9BC: -/* 0105BC 7000F9BC 10C00007 */ beqz $a2, .L7000F9DC -/* 0105C0 7000F9C0 00000000 */ nop -/* 0105C4 7000F9C4 00861821 */ addu $v1, $a0, $a2 -.L7000F9C8: -/* 0105C8 7000F9C8 80820000 */ lb $v0, ($a0) -/* 0105CC 7000F9CC 24840001 */ addiu $a0, $a0, 1 -/* 0105D0 7000F9D0 24A50001 */ addiu $a1, $a1, 1 -/* 0105D4 7000F9D4 1483FFFC */ bne $a0, $v1, .L7000F9C8 -/* 0105D8 7000F9D8 A0A2FFFF */ sb $v0, -1($a1) -.L7000F9DC: -/* 0105DC 7000F9DC 03E00008 */ jr $ra -/* 0105E0 7000F9E0 00E01025 */ move $v0, $a3 - -.L7000F9E4: -/* 0105E4 7000F9E4 10400018 */ beqz $v0, .L7000FA48 -/* 0105E8 7000F9E8 24010001 */ li $at, 1 -/* 0105EC 7000F9EC 1041000F */ beq $v0, $at, .L7000FA2C -/* 0105F0 7000F9F0 24010002 */ li $at, 2 -/* 0105F4 7000F9F4 50410008 */ beql $v0, $at, .L7000FA18 -/* 0105F8 7000F9F8 84820000 */ lh $v0, ($a0) -/* 0105FC 7000F9FC 80820000 */ lb $v0, ($a0) -/* 010600 7000FA00 24840001 */ addiu $a0, $a0, 1 -/* 010604 7000FA04 24A50001 */ addiu $a1, $a1, 1 -/* 010608 7000FA08 24C6FFFF */ addiu $a2, $a2, -1 -/* 01060C 7000FA0C 1000000E */ b .L7000FA48 -/* 010610 7000FA10 A0A2FFFF */ sb $v0, -1($a1) -/* 010614 7000FA14 84820000 */ lh $v0, ($a0) -.L7000FA18: -/* 010618 7000FA18 24840002 */ addiu $a0, $a0, 2 -/* 01061C 7000FA1C 24A50002 */ addiu $a1, $a1, 2 -/* 010620 7000FA20 24C6FFFE */ addiu $a2, $a2, -2 -/* 010624 7000FA24 10000008 */ b .L7000FA48 -/* 010628 7000FA28 A4A2FFFE */ sh $v0, -2($a1) -.L7000FA2C: -/* 01062C 7000FA2C 80820000 */ lb $v0, ($a0) -/* 010630 7000FA30 84830001 */ lh $v1, 1($a0) -/* 010634 7000FA34 24840003 */ addiu $a0, $a0, 3 -/* 010638 7000FA38 24A50003 */ addiu $a1, $a1, 3 -/* 01063C 7000FA3C 24C6FFFD */ addiu $a2, $a2, -3 -/* 010640 7000FA40 A0A2FFFD */ sb $v0, -3($a1) -/* 010644 7000FA44 A4A3FFFE */ sh $v1, -2($a1) -.L7000FA48: -/* 010648 7000FA48 28C10020 */ slti $at, $a2, 0x20 -/* 01064C 7000FA4C 54200016 */ bnezl $at, .L7000FAA8 -/* 010650 7000FA50 28C10010 */ slti $at, $a2, 0x10 -/* 010654 7000FA54 8C820000 */ lw $v0, ($a0) -/* 010658 7000FA58 8C830004 */ lw $v1, 4($a0) -/* 01065C 7000FA5C 8C880008 */ lw $t0, 8($a0) -/* 010660 7000FA60 8C89000C */ lw $t1, 0xc($a0) -/* 010664 7000FA64 8C8A0010 */ lw $t2, 0x10($a0) -/* 010668 7000FA68 8C8B0014 */ lw $t3, 0x14($a0) -/* 01066C 7000FA6C 8C8C0018 */ lw $t4, 0x18($a0) -/* 010670 7000FA70 8C8D001C */ lw $t5, 0x1c($a0) -/* 010674 7000FA74 24840020 */ addiu $a0, $a0, 0x20 -/* 010678 7000FA78 24A50020 */ addiu $a1, $a1, 0x20 -/* 01067C 7000FA7C 24C6FFE0 */ addiu $a2, $a2, -0x20 -/* 010680 7000FA80 ACA2FFE0 */ sw $v0, -0x20($a1) -/* 010684 7000FA84 ACA3FFE4 */ sw $v1, -0x1c($a1) -/* 010688 7000FA88 ACA8FFE8 */ sw $t0, -0x18($a1) -/* 01068C 7000FA8C ACA9FFEC */ sw $t1, -0x14($a1) -/* 010690 7000FA90 ACAAFFF0 */ sw $t2, -0x10($a1) -/* 010694 7000FA94 ACABFFF4 */ sw $t3, -0xc($a1) -/* 010698 7000FA98 ACACFFF8 */ sw $t4, -8($a1) -/* 01069C 7000FA9C 1000FFEA */ b .L7000FA48 -/* 0106A0 7000FAA0 ACADFFFC */ sw $t5, -4($a1) -.L7000FAA4: -/* 0106A4 7000FAA4 28C10010 */ slti $at, $a2, 0x10 -.L7000FAA8: -/* 0106A8 7000FAA8 5420000E */ bnezl $at, .L7000FAE4 -/* 0106AC 7000FAAC 28C10004 */ slti $at, $a2, 4 -/* 0106B0 7000FAB0 8C820000 */ lw $v0, ($a0) -/* 0106B4 7000FAB4 8C830004 */ lw $v1, 4($a0) -/* 0106B8 7000FAB8 8C880008 */ lw $t0, 8($a0) -/* 0106BC 7000FABC 8C89000C */ lw $t1, 0xc($a0) -/* 0106C0 7000FAC0 24840010 */ addiu $a0, $a0, 0x10 -/* 0106C4 7000FAC4 24A50010 */ addiu $a1, $a1, 0x10 -/* 0106C8 7000FAC8 24C6FFF0 */ addiu $a2, $a2, -0x10 -/* 0106CC 7000FACC ACA2FFF0 */ sw $v0, -0x10($a1) -/* 0106D0 7000FAD0 ACA3FFF4 */ sw $v1, -0xc($a1) -/* 0106D4 7000FAD4 ACA8FFF8 */ sw $t0, -8($a1) -/* 0106D8 7000FAD8 1000FFF2 */ b .L7000FAA4 -/* 0106DC 7000FADC ACA9FFFC */ sw $t1, -4($a1) -.L7000FAE0: -/* 0106E0 7000FAE0 28C10004 */ slti $at, $a2, 4 -.L7000FAE4: -/* 0106E4 7000FAE4 1420FFB5 */ bnez $at, .L7000F9BC -/* 0106E8 7000FAE8 00000000 */ nop -/* 0106EC 7000FAEC 8C820000 */ lw $v0, ($a0) -/* 0106F0 7000FAF0 24840004 */ addiu $a0, $a0, 4 -/* 0106F4 7000FAF4 24A50004 */ addiu $a1, $a1, 4 -/* 0106F8 7000FAF8 24C6FFFC */ addiu $a2, $a2, -4 -/* 0106FC 7000FAFC 1000FFF8 */ b .L7000FAE0 -/* 010700 7000FB00 ACA2FFFC */ sw $v0, -4($a1) -/* 010704 7000FB04 28C10010 */ slti $at, $a2, 0x10 -.L7000FB08: -/* 010708 7000FB08 00862020 */ add $a0, $a0, $a2 -/* 01070C 7000FB0C 14200005 */ bnez $at, .L7000FB24 -/* 010710 7000FB10 00A62820 */ add $a1, $a1, $a2 -/* 010714 7000FB14 30820003 */ andi $v0, $a0, 3 -/* 010718 7000FB18 30A30003 */ andi $v1, $a1, 3 -/* 01071C 7000FB1C 1043000D */ beq $v0, $v1, .L7000FB54 -/* 010720 7000FB20 00000000 */ nop -.L7000FB24: -/* 010724 7000FB24 10C0FFAD */ beqz $a2, .L7000F9DC -/* 010728 7000FB28 00000000 */ nop -/* 01072C 7000FB2C 2484FFFF */ addiu $a0, $a0, -1 -/* 010730 7000FB30 24A5FFFF */ addiu $a1, $a1, -1 -/* 010734 7000FB34 00861823 */ subu $v1, $a0, $a2 -.L7000FB38: -/* 010738 7000FB38 80820000 */ lb $v0, ($a0) -/* 01073C 7000FB3C 2484FFFF */ addiu $a0, $a0, -1 -/* 010740 7000FB40 24A5FFFF */ addiu $a1, $a1, -1 -/* 010744 7000FB44 1483FFFC */ bne $a0, $v1, .L7000FB38 -/* 010748 7000FB48 A0A20001 */ sb $v0, 1($a1) -/* 01074C 7000FB4C 03E00008 */ jr $ra -/* 010750 7000FB50 00E01025 */ move $v0, $a3 - -.L7000FB54: -/* 010754 7000FB54 10400018 */ beqz $v0, .L7000FBB8 -/* 010758 7000FB58 24010003 */ li $at, 3 -/* 01075C 7000FB5C 1041000F */ beq $v0, $at, .L7000FB9C -/* 010760 7000FB60 24010002 */ li $at, 2 -/* 010764 7000FB64 50410008 */ beql $v0, $at, .L7000FB88 -/* 010768 7000FB68 8482FFFE */ lh $v0, -2($a0) -/* 01076C 7000FB6C 8082FFFF */ lb $v0, -1($a0) -/* 010770 7000FB70 2484FFFF */ addiu $a0, $a0, -1 -/* 010774 7000FB74 24A5FFFF */ addiu $a1, $a1, -1 -/* 010778 7000FB78 24C6FFFF */ addiu $a2, $a2, -1 -/* 01077C 7000FB7C 1000000E */ b .L7000FBB8 -/* 010780 7000FB80 A0A20000 */ sb $v0, ($a1) -/* 010784 7000FB84 8482FFFE */ lh $v0, -2($a0) -.L7000FB88: -/* 010788 7000FB88 2484FFFE */ addiu $a0, $a0, -2 -/* 01078C 7000FB8C 24A5FFFE */ addiu $a1, $a1, -2 -/* 010790 7000FB90 24C6FFFE */ addiu $a2, $a2, -2 -/* 010794 7000FB94 10000008 */ b .L7000FBB8 -/* 010798 7000FB98 A4A20000 */ sh $v0, ($a1) -.L7000FB9C: -/* 01079C 7000FB9C 8082FFFF */ lb $v0, -1($a0) -/* 0107A0 7000FBA0 8483FFFD */ lh $v1, -3($a0) -/* 0107A4 7000FBA4 2484FFFD */ addiu $a0, $a0, -3 -/* 0107A8 7000FBA8 24A5FFFD */ addiu $a1, $a1, -3 -/* 0107AC 7000FBAC 24C6FFFD */ addiu $a2, $a2, -3 -/* 0107B0 7000FBB0 A0A20002 */ sb $v0, 2($a1) -/* 0107B4 7000FBB4 A4A30000 */ sh $v1, ($a1) -.L7000FBB8: -/* 0107B8 7000FBB8 28C10020 */ slti $at, $a2, 0x20 -/* 0107BC 7000FBBC 54200016 */ bnezl $at, .L7000FC18 -/* 0107C0 7000FBC0 28C10010 */ slti $at, $a2, 0x10 -/* 0107C4 7000FBC4 8C82FFFC */ lw $v0, -4($a0) -/* 0107C8 7000FBC8 8C83FFF8 */ lw $v1, -8($a0) -/* 0107CC 7000FBCC 8C88FFF4 */ lw $t0, -0xc($a0) -/* 0107D0 7000FBD0 8C89FFF0 */ lw $t1, -0x10($a0) -/* 0107D4 7000FBD4 8C8AFFEC */ lw $t2, -0x14($a0) -/* 0107D8 7000FBD8 8C8BFFE8 */ lw $t3, -0x18($a0) -/* 0107DC 7000FBDC 8C8CFFE4 */ lw $t4, -0x1c($a0) -/* 0107E0 7000FBE0 8C8DFFE0 */ lw $t5, -0x20($a0) -/* 0107E4 7000FBE4 2484FFE0 */ addiu $a0, $a0, -0x20 -/* 0107E8 7000FBE8 24A5FFE0 */ addiu $a1, $a1, -0x20 -/* 0107EC 7000FBEC 24C6FFE0 */ addiu $a2, $a2, -0x20 -/* 0107F0 7000FBF0 ACA2001C */ sw $v0, 0x1c($a1) -/* 0107F4 7000FBF4 ACA30018 */ sw $v1, 0x18($a1) -/* 0107F8 7000FBF8 ACA80014 */ sw $t0, 0x14($a1) -/* 0107FC 7000FBFC ACA90010 */ sw $t1, 0x10($a1) -/* 010800 7000FC00 ACAA000C */ sw $t2, 0xc($a1) -/* 010804 7000FC04 ACAB0008 */ sw $t3, 8($a1) -/* 010808 7000FC08 ACAC0004 */ sw $t4, 4($a1) -/* 01080C 7000FC0C 1000FFEA */ b .L7000FBB8 -/* 010810 7000FC10 ACAD0000 */ sw $t5, ($a1) -.L7000FC14: -/* 010814 7000FC14 28C10010 */ slti $at, $a2, 0x10 -.L7000FC18: -/* 010818 7000FC18 5420000E */ bnezl $at, .L7000FC54 -/* 01081C 7000FC1C 28C10004 */ slti $at, $a2, 4 -/* 010820 7000FC20 8C82FFFC */ lw $v0, -4($a0) -/* 010824 7000FC24 8C83FFF8 */ lw $v1, -8($a0) -/* 010828 7000FC28 8C88FFF4 */ lw $t0, -0xc($a0) -/* 01082C 7000FC2C 8C89FFF0 */ lw $t1, -0x10($a0) -/* 010830 7000FC30 2484FFF0 */ addiu $a0, $a0, -0x10 -/* 010834 7000FC34 24A5FFF0 */ addiu $a1, $a1, -0x10 -/* 010838 7000FC38 24C6FFF0 */ addiu $a2, $a2, -0x10 -/* 01083C 7000FC3C ACA2000C */ sw $v0, 0xc($a1) -/* 010840 7000FC40 ACA30008 */ sw $v1, 8($a1) -/* 010844 7000FC44 ACA80004 */ sw $t0, 4($a1) -/* 010848 7000FC48 1000FFF2 */ b .L7000FC14 -/* 01084C 7000FC4C ACA90000 */ sw $t1, ($a1) -.L7000FC50: -/* 010850 7000FC50 28C10004 */ slti $at, $a2, 4 -.L7000FC54: -/* 010854 7000FC54 1420FFB3 */ bnez $at, .L7000FB24 -/* 010858 7000FC58 00000000 */ nop -/* 01085C 7000FC5C 8C82FFFC */ lw $v0, -4($a0) -/* 010860 7000FC60 2484FFFC */ addiu $a0, $a0, -4 -/* 010864 7000FC64 24A5FFFC */ addiu $a1, $a1, -4 -/* 010868 7000FC68 24C6FFFC */ addiu $a2, $a2, -4 -/* 01086C 7000FC6C 1000FFF8 */ b .L7000FC50 -/* 010870 7000FC70 ACA20000 */ sw $v0, ($a1) -.size bcopy, . - bcopy +# asm file. +# +# TODO: this is an -O1 match, the libultra version is -O2 using IDO `as` toolchain. + +# assembler directives +.set noat # allow manual use of $at +.set noreorder # don't insert nops after branches +.set gp=64 + +.include "macros.inc" + + +.section .text, "ax" +glabel bcopy +/* 010570 7000F970 10C0001A */ beqz $a2, .L7000F9DC +/* 010574 7000F974 00A03825 */ move $a3, $a1 +/* 010578 7000F978 10850018 */ beq $a0, $a1, .L7000F9DC +/* 01057C 7000F97C 00A4082A */ slt $at, $a1, $a0 +/* 010580 7000F980 54200008 */ bnezl $at, .L7000F9A4 +/* 010584 7000F984 28C10010 */ slti $at, $a2, 0x10 +/* 010588 7000F988 00861020 */ add $v0, $a0, $a2 +/* 01058C 7000F98C 00A2082A */ slt $at, $a1, $v0 +/* 010590 7000F990 50200004 */ beql $at, $zero, .L7000F9A4 +/* 010594 7000F994 28C10010 */ slti $at, $a2, 0x10 +/* 010598 7000F998 1000005B */ b .L7000FB08 +/* 01059C 7000F99C 28C10010 */ slti $at, $a2, 0x10 +/* 0105A0 7000F9A0 28C10010 */ slti $at, $a2, 0x10 +.L7000F9A4: +/* 0105A4 7000F9A4 14200005 */ bnez $at, .L7000F9BC +/* 0105A8 7000F9A8 00000000 */ nop +/* 0105AC 7000F9AC 30820003 */ andi $v0, $a0, 3 +/* 0105B0 7000F9B0 30A30003 */ andi $v1, $a1, 3 +/* 0105B4 7000F9B4 1043000B */ beq $v0, $v1, .L7000F9E4 +/* 0105B8 7000F9B8 00000000 */ nop +.L7000F9BC: +/* 0105BC 7000F9BC 10C00007 */ beqz $a2, .L7000F9DC +/* 0105C0 7000F9C0 00000000 */ nop +/* 0105C4 7000F9C4 00861821 */ addu $v1, $a0, $a2 +.L7000F9C8: +/* 0105C8 7000F9C8 80820000 */ lb $v0, ($a0) +/* 0105CC 7000F9CC 24840001 */ addiu $a0, $a0, 1 +/* 0105D0 7000F9D0 24A50001 */ addiu $a1, $a1, 1 +/* 0105D4 7000F9D4 1483FFFC */ bne $a0, $v1, .L7000F9C8 +/* 0105D8 7000F9D8 A0A2FFFF */ sb $v0, -1($a1) +.L7000F9DC: +/* 0105DC 7000F9DC 03E00008 */ jr $ra +/* 0105E0 7000F9E0 00E01025 */ move $v0, $a3 + +.L7000F9E4: +/* 0105E4 7000F9E4 10400018 */ beqz $v0, .L7000FA48 +/* 0105E8 7000F9E8 24010001 */ li $at, 1 +/* 0105EC 7000F9EC 1041000F */ beq $v0, $at, .L7000FA2C +/* 0105F0 7000F9F0 24010002 */ li $at, 2 +/* 0105F4 7000F9F4 50410008 */ beql $v0, $at, .L7000FA18 +/* 0105F8 7000F9F8 84820000 */ lh $v0, ($a0) +/* 0105FC 7000F9FC 80820000 */ lb $v0, ($a0) +/* 010600 7000FA00 24840001 */ addiu $a0, $a0, 1 +/* 010604 7000FA04 24A50001 */ addiu $a1, $a1, 1 +/* 010608 7000FA08 24C6FFFF */ addiu $a2, $a2, -1 +/* 01060C 7000FA0C 1000000E */ b .L7000FA48 +/* 010610 7000FA10 A0A2FFFF */ sb $v0, -1($a1) +/* 010614 7000FA14 84820000 */ lh $v0, ($a0) +.L7000FA18: +/* 010618 7000FA18 24840002 */ addiu $a0, $a0, 2 +/* 01061C 7000FA1C 24A50002 */ addiu $a1, $a1, 2 +/* 010620 7000FA20 24C6FFFE */ addiu $a2, $a2, -2 +/* 010624 7000FA24 10000008 */ b .L7000FA48 +/* 010628 7000FA28 A4A2FFFE */ sh $v0, -2($a1) +.L7000FA2C: +/* 01062C 7000FA2C 80820000 */ lb $v0, ($a0) +/* 010630 7000FA30 84830001 */ lh $v1, 1($a0) +/* 010634 7000FA34 24840003 */ addiu $a0, $a0, 3 +/* 010638 7000FA38 24A50003 */ addiu $a1, $a1, 3 +/* 01063C 7000FA3C 24C6FFFD */ addiu $a2, $a2, -3 +/* 010640 7000FA40 A0A2FFFD */ sb $v0, -3($a1) +/* 010644 7000FA44 A4A3FFFE */ sh $v1, -2($a1) +.L7000FA48: +/* 010648 7000FA48 28C10020 */ slti $at, $a2, 0x20 +/* 01064C 7000FA4C 54200016 */ bnezl $at, .L7000FAA8 +/* 010650 7000FA50 28C10010 */ slti $at, $a2, 0x10 +/* 010654 7000FA54 8C820000 */ lw $v0, ($a0) +/* 010658 7000FA58 8C830004 */ lw $v1, 4($a0) +/* 01065C 7000FA5C 8C880008 */ lw $t0, 8($a0) +/* 010660 7000FA60 8C89000C */ lw $t1, 0xc($a0) +/* 010664 7000FA64 8C8A0010 */ lw $t2, 0x10($a0) +/* 010668 7000FA68 8C8B0014 */ lw $t3, 0x14($a0) +/* 01066C 7000FA6C 8C8C0018 */ lw $t4, 0x18($a0) +/* 010670 7000FA70 8C8D001C */ lw $t5, 0x1c($a0) +/* 010674 7000FA74 24840020 */ addiu $a0, $a0, 0x20 +/* 010678 7000FA78 24A50020 */ addiu $a1, $a1, 0x20 +/* 01067C 7000FA7C 24C6FFE0 */ addiu $a2, $a2, -0x20 +/* 010680 7000FA80 ACA2FFE0 */ sw $v0, -0x20($a1) +/* 010684 7000FA84 ACA3FFE4 */ sw $v1, -0x1c($a1) +/* 010688 7000FA88 ACA8FFE8 */ sw $t0, -0x18($a1) +/* 01068C 7000FA8C ACA9FFEC */ sw $t1, -0x14($a1) +/* 010690 7000FA90 ACAAFFF0 */ sw $t2, -0x10($a1) +/* 010694 7000FA94 ACABFFF4 */ sw $t3, -0xc($a1) +/* 010698 7000FA98 ACACFFF8 */ sw $t4, -8($a1) +/* 01069C 7000FA9C 1000FFEA */ b .L7000FA48 +/* 0106A0 7000FAA0 ACADFFFC */ sw $t5, -4($a1) +.L7000FAA4: +/* 0106A4 7000FAA4 28C10010 */ slti $at, $a2, 0x10 +.L7000FAA8: +/* 0106A8 7000FAA8 5420000E */ bnezl $at, .L7000FAE4 +/* 0106AC 7000FAAC 28C10004 */ slti $at, $a2, 4 +/* 0106B0 7000FAB0 8C820000 */ lw $v0, ($a0) +/* 0106B4 7000FAB4 8C830004 */ lw $v1, 4($a0) +/* 0106B8 7000FAB8 8C880008 */ lw $t0, 8($a0) +/* 0106BC 7000FABC 8C89000C */ lw $t1, 0xc($a0) +/* 0106C0 7000FAC0 24840010 */ addiu $a0, $a0, 0x10 +/* 0106C4 7000FAC4 24A50010 */ addiu $a1, $a1, 0x10 +/* 0106C8 7000FAC8 24C6FFF0 */ addiu $a2, $a2, -0x10 +/* 0106CC 7000FACC ACA2FFF0 */ sw $v0, -0x10($a1) +/* 0106D0 7000FAD0 ACA3FFF4 */ sw $v1, -0xc($a1) +/* 0106D4 7000FAD4 ACA8FFF8 */ sw $t0, -8($a1) +/* 0106D8 7000FAD8 1000FFF2 */ b .L7000FAA4 +/* 0106DC 7000FADC ACA9FFFC */ sw $t1, -4($a1) +.L7000FAE0: +/* 0106E0 7000FAE0 28C10004 */ slti $at, $a2, 4 +.L7000FAE4: +/* 0106E4 7000FAE4 1420FFB5 */ bnez $at, .L7000F9BC +/* 0106E8 7000FAE8 00000000 */ nop +/* 0106EC 7000FAEC 8C820000 */ lw $v0, ($a0) +/* 0106F0 7000FAF0 24840004 */ addiu $a0, $a0, 4 +/* 0106F4 7000FAF4 24A50004 */ addiu $a1, $a1, 4 +/* 0106F8 7000FAF8 24C6FFFC */ addiu $a2, $a2, -4 +/* 0106FC 7000FAFC 1000FFF8 */ b .L7000FAE0 +/* 010700 7000FB00 ACA2FFFC */ sw $v0, -4($a1) +/* 010704 7000FB04 28C10010 */ slti $at, $a2, 0x10 +.L7000FB08: +/* 010708 7000FB08 00862020 */ add $a0, $a0, $a2 +/* 01070C 7000FB0C 14200005 */ bnez $at, .L7000FB24 +/* 010710 7000FB10 00A62820 */ add $a1, $a1, $a2 +/* 010714 7000FB14 30820003 */ andi $v0, $a0, 3 +/* 010718 7000FB18 30A30003 */ andi $v1, $a1, 3 +/* 01071C 7000FB1C 1043000D */ beq $v0, $v1, .L7000FB54 +/* 010720 7000FB20 00000000 */ nop +.L7000FB24: +/* 010724 7000FB24 10C0FFAD */ beqz $a2, .L7000F9DC +/* 010728 7000FB28 00000000 */ nop +/* 01072C 7000FB2C 2484FFFF */ addiu $a0, $a0, -1 +/* 010730 7000FB30 24A5FFFF */ addiu $a1, $a1, -1 +/* 010734 7000FB34 00861823 */ subu $v1, $a0, $a2 +.L7000FB38: +/* 010738 7000FB38 80820000 */ lb $v0, ($a0) +/* 01073C 7000FB3C 2484FFFF */ addiu $a0, $a0, -1 +/* 010740 7000FB40 24A5FFFF */ addiu $a1, $a1, -1 +/* 010744 7000FB44 1483FFFC */ bne $a0, $v1, .L7000FB38 +/* 010748 7000FB48 A0A20001 */ sb $v0, 1($a1) +/* 01074C 7000FB4C 03E00008 */ jr $ra +/* 010750 7000FB50 00E01025 */ move $v0, $a3 + +.L7000FB54: +/* 010754 7000FB54 10400018 */ beqz $v0, .L7000FBB8 +/* 010758 7000FB58 24010003 */ li $at, 3 +/* 01075C 7000FB5C 1041000F */ beq $v0, $at, .L7000FB9C +/* 010760 7000FB60 24010002 */ li $at, 2 +/* 010764 7000FB64 50410008 */ beql $v0, $at, .L7000FB88 +/* 010768 7000FB68 8482FFFE */ lh $v0, -2($a0) +/* 01076C 7000FB6C 8082FFFF */ lb $v0, -1($a0) +/* 010770 7000FB70 2484FFFF */ addiu $a0, $a0, -1 +/* 010774 7000FB74 24A5FFFF */ addiu $a1, $a1, -1 +/* 010778 7000FB78 24C6FFFF */ addiu $a2, $a2, -1 +/* 01077C 7000FB7C 1000000E */ b .L7000FBB8 +/* 010780 7000FB80 A0A20000 */ sb $v0, ($a1) +/* 010784 7000FB84 8482FFFE */ lh $v0, -2($a0) +.L7000FB88: +/* 010788 7000FB88 2484FFFE */ addiu $a0, $a0, -2 +/* 01078C 7000FB8C 24A5FFFE */ addiu $a1, $a1, -2 +/* 010790 7000FB90 24C6FFFE */ addiu $a2, $a2, -2 +/* 010794 7000FB94 10000008 */ b .L7000FBB8 +/* 010798 7000FB98 A4A20000 */ sh $v0, ($a1) +.L7000FB9C: +/* 01079C 7000FB9C 8082FFFF */ lb $v0, -1($a0) +/* 0107A0 7000FBA0 8483FFFD */ lh $v1, -3($a0) +/* 0107A4 7000FBA4 2484FFFD */ addiu $a0, $a0, -3 +/* 0107A8 7000FBA8 24A5FFFD */ addiu $a1, $a1, -3 +/* 0107AC 7000FBAC 24C6FFFD */ addiu $a2, $a2, -3 +/* 0107B0 7000FBB0 A0A20002 */ sb $v0, 2($a1) +/* 0107B4 7000FBB4 A4A30000 */ sh $v1, ($a1) +.L7000FBB8: +/* 0107B8 7000FBB8 28C10020 */ slti $at, $a2, 0x20 +/* 0107BC 7000FBBC 54200016 */ bnezl $at, .L7000FC18 +/* 0107C0 7000FBC0 28C10010 */ slti $at, $a2, 0x10 +/* 0107C4 7000FBC4 8C82FFFC */ lw $v0, -4($a0) +/* 0107C8 7000FBC8 8C83FFF8 */ lw $v1, -8($a0) +/* 0107CC 7000FBCC 8C88FFF4 */ lw $t0, -0xc($a0) +/* 0107D0 7000FBD0 8C89FFF0 */ lw $t1, -0x10($a0) +/* 0107D4 7000FBD4 8C8AFFEC */ lw $t2, -0x14($a0) +/* 0107D8 7000FBD8 8C8BFFE8 */ lw $t3, -0x18($a0) +/* 0107DC 7000FBDC 8C8CFFE4 */ lw $t4, -0x1c($a0) +/* 0107E0 7000FBE0 8C8DFFE0 */ lw $t5, -0x20($a0) +/* 0107E4 7000FBE4 2484FFE0 */ addiu $a0, $a0, -0x20 +/* 0107E8 7000FBE8 24A5FFE0 */ addiu $a1, $a1, -0x20 +/* 0107EC 7000FBEC 24C6FFE0 */ addiu $a2, $a2, -0x20 +/* 0107F0 7000FBF0 ACA2001C */ sw $v0, 0x1c($a1) +/* 0107F4 7000FBF4 ACA30018 */ sw $v1, 0x18($a1) +/* 0107F8 7000FBF8 ACA80014 */ sw $t0, 0x14($a1) +/* 0107FC 7000FBFC ACA90010 */ sw $t1, 0x10($a1) +/* 010800 7000FC00 ACAA000C */ sw $t2, 0xc($a1) +/* 010804 7000FC04 ACAB0008 */ sw $t3, 8($a1) +/* 010808 7000FC08 ACAC0004 */ sw $t4, 4($a1) +/* 01080C 7000FC0C 1000FFEA */ b .L7000FBB8 +/* 010810 7000FC10 ACAD0000 */ sw $t5, ($a1) +.L7000FC14: +/* 010814 7000FC14 28C10010 */ slti $at, $a2, 0x10 +.L7000FC18: +/* 010818 7000FC18 5420000E */ bnezl $at, .L7000FC54 +/* 01081C 7000FC1C 28C10004 */ slti $at, $a2, 4 +/* 010820 7000FC20 8C82FFFC */ lw $v0, -4($a0) +/* 010824 7000FC24 8C83FFF8 */ lw $v1, -8($a0) +/* 010828 7000FC28 8C88FFF4 */ lw $t0, -0xc($a0) +/* 01082C 7000FC2C 8C89FFF0 */ lw $t1, -0x10($a0) +/* 010830 7000FC30 2484FFF0 */ addiu $a0, $a0, -0x10 +/* 010834 7000FC34 24A5FFF0 */ addiu $a1, $a1, -0x10 +/* 010838 7000FC38 24C6FFF0 */ addiu $a2, $a2, -0x10 +/* 01083C 7000FC3C ACA2000C */ sw $v0, 0xc($a1) +/* 010840 7000FC40 ACA30008 */ sw $v1, 8($a1) +/* 010844 7000FC44 ACA80004 */ sw $t0, 4($a1) +/* 010848 7000FC48 1000FFF2 */ b .L7000FC14 +/* 01084C 7000FC4C ACA90000 */ sw $t1, ($a1) +.L7000FC50: +/* 010850 7000FC50 28C10004 */ slti $at, $a2, 4 +.L7000FC54: +/* 010854 7000FC54 1420FFB3 */ bnez $at, .L7000FB24 +/* 010858 7000FC58 00000000 */ nop +/* 01085C 7000FC5C 8C82FFFC */ lw $v0, -4($a0) +/* 010860 7000FC60 2484FFFC */ addiu $a0, $a0, -4 +/* 010864 7000FC64 24A5FFFC */ addiu $a1, $a1, -4 +/* 010868 7000FC68 24C6FFFC */ addiu $a2, $a2, -4 +/* 01086C 7000FC6C 1000FFF8 */ b .L7000FC50 +/* 010870 7000FC70 ACA20000 */ sw $v0, ($a1) +.size bcopy, . - bcopy diff --git a/src/libultra/os/exception.s b/src/libultra/os/exception.s index 1a79e921..4192c087 100644 --- a/src/libultra/os/exception.s +++ b/src/libultra/os/exception.s @@ -1,673 +1,673 @@ -# asm file. -# modified version of "exceptasm.s" - -# asm file. -# -# TODO: this is an -mips2 match, the libultra version is instruction set `-mips3 -o32`. - -# assembler directives -.set noat # allow manual use of $at -.set noreorder # don't insert nops after branches -.set gp=64 - -.include "macros.inc" - - -.section .text, "ax" -glabel __osExceptionPreamble -/* 010D90 70010190 3C1A7001 */ lui $k0, %hi(__osException) # $k0, 0x7001 -/* 010D94 70010194 275A01A0 */ addiu $k0, %lo(__osException) # addiu $k0, $k0, 0x1a0 -/* 010D98 70010198 03400008 */ jr $k0 -/* 010D9C 7001019C 00000000 */ nop -glabel __osException -/* 010DA0 700101A0 3C1A8007 */ lui $k0, %hi(__osThreadSave) # $k0, 0x8007 -/* 010DA4 700101A4 275A8E30 */ addiu $k0, %lo(__osThreadSave) # addiu $k0, $k0, -0x71d0 -/* 010DA8 700101A8 FF410020 */ sd $at, 0x20($k0) -/* 010DAC 700101AC 401B6000 */ mfc0 $k1, $12 -/* 010DB0 700101B0 AF5B0118 */ sw $k1, 0x118($k0) -/* 010DB4 700101B4 2401FFFC */ li $at, -4 -/* 010DB8 700101B8 0361D824 */ and $k1, $k1, $at -/* 010DBC 700101BC 409B6000 */ mtc0 $k1, $12 -/* 010DC0 700101C0 FF480058 */ sd $t0, 0x58($k0) -/* 010DC4 700101C4 FF490060 */ sd $t1, 0x60($k0) -/* 010DC8 700101C8 FF4A0068 */ sd $t2, 0x68($k0) -/* 010DCC 700101CC AF400018 */ sw $zero, 0x18($k0) -/* 010DD0 700101D0 40086800 */ mfc0 $t0, $13 -/* 010DD4 700101D4 03404025 */ move $t0, $k0 -/* 010DD8 700101D8 3C1A8002 */ lui $k0, %hi(__osRunningThread) # $k0, 0x8002 -/* 010DDC 700101DC 8F5A7730 */ lw $k0, %lo(__osRunningThread)($k0) -/* 010DE0 700101E0 DD090020 */ ld $t1, 0x20($t0) -/* 010DE4 700101E4 FF490020 */ sd $t1, 0x20($k0) -/* 010DE8 700101E8 DD090118 */ ld $t1, 0x118($t0) -/* 010DEC 700101EC FF490118 */ sd $t1, 0x118($k0) -/* 010DF0 700101F0 DD090058 */ ld $t1, 0x58($t0) -/* 010DF4 700101F4 FF490058 */ sd $t1, 0x58($k0) -/* 010DF8 700101F8 DD090060 */ ld $t1, 0x60($t0) -/* 010DFC 700101FC FF490060 */ sd $t1, 0x60($k0) -/* 010E00 70010200 DD090068 */ ld $t1, 0x68($t0) -/* 010E04 70010204 FF490068 */ sd $t1, 0x68($k0) -/* 010E08 70010208 8F5B0118 */ lw $k1, 0x118($k0) -/* 010E0C 7001020C 00004012 */ mflo $t0 -/* 010E10 70010210 FF480108 */ sd $t0, 0x108($k0) -/* 010E14 70010214 00004010 */ mfhi $t0 -/* 010E18 70010218 3369FF00 */ andi $t1, $k1, 0xff00 -/* 010E1C 7001021C FF420028 */ sd $v0, 0x28($k0) -/* 010E20 70010220 FF430030 */ sd $v1, 0x30($k0) -/* 010E24 70010224 FF440038 */ sd $a0, 0x38($k0) -/* 010E28 70010228 FF450040 */ sd $a1, 0x40($k0) -/* 010E2C 7001022C FF460048 */ sd $a2, 0x48($k0) -/* 010E30 70010230 FF470050 */ sd $a3, 0x50($k0) -/* 010E34 70010234 FF4B0070 */ sd $t3, 0x70($k0) -/* 010E38 70010238 FF4C0078 */ sd $t4, 0x78($k0) -/* 010E3C 7001023C FF4D0080 */ sd $t5, 0x80($k0) -/* 010E40 70010240 FF4E0088 */ sd $t6, 0x88($k0) -/* 010E44 70010244 FF4F0090 */ sd $t7, 0x90($k0) -/* 010E48 70010248 FF500098 */ sd $s0, 0x98($k0) -/* 010E4C 7001024C FF5100A0 */ sd $s1, 0xa0($k0) -/* 010E50 70010250 FF5200A8 */ sd $s2, 0xa8($k0) -/* 010E54 70010254 FF5300B0 */ sd $s3, 0xb0($k0) -/* 010E58 70010258 FF5400B8 */ sd $s4, 0xb8($k0) -/* 010E5C 7001025C FF5500C0 */ sd $s5, 0xc0($k0) -/* 010E60 70010260 FF5600C8 */ sd $s6, 0xc8($k0) -/* 010E64 70010264 FF5700D0 */ sd $s7, 0xd0($k0) -/* 010E68 70010268 FF5800D8 */ sd $t8, 0xd8($k0) -/* 010E6C 7001026C FF5900E0 */ sd $t9, 0xe0($k0) -/* 010E70 70010270 FF5C00E8 */ sd $gp, 0xe8($k0) -/* 010E74 70010274 FF5D00F0 */ sd $sp, 0xf0($k0) -/* 010E78 70010278 FF5E00F8 */ sd $fp, 0xf8($k0) -/* 010E7C 7001027C FF5F0100 */ sd $ra, 0x100($k0) -/* 010E80 70010280 1120000D */ beqz $t1, .L700102B8 -/* 010E84 70010284 FF480110 */ sd $t0, 0x110($k0) -/* 010E88 70010288 3C088002 */ lui $t0, %hi(__OSGlobalIntMask) -/* 010E8C 7001028C 2508698C */ addiu $t0, %lo(__OSGlobalIntMask) # addiu $t0, $t0, 0x698c -/* 010E90 70010290 8D080000 */ lw $t0, ($t0) -/* 010E94 70010294 2401FFFF */ li $at, -1 -/* 010E98 70010298 01014026 */ xor $t0, $t0, $at -/* 010E9C 7001029C 3C01FFFF */ lui $at, (0xFFFF00FF >> 16) # lui $at, 0xffff -/* 010EA0 700102A0 3108FF00 */ andi $t0, $t0, 0xff00 -/* 010EA4 700102A4 342100FF */ ori $at, (0xFFFF00FF & 0xFFFF) # ori $at, $at, 0xff -/* 010EA8 700102A8 01284825 */ or $t1, $t1, $t0 -/* 010EAC 700102AC 0361D824 */ and $k1, $k1, $at -/* 010EB0 700102B0 0369D825 */ or $k1, $k1, $t1 -/* 010EB4 700102B4 AF5B0118 */ sw $k1, 0x118($k0) -.L700102B8: -/* 010EB8 700102B8 3C09A430 */ lui $t1, %hi(0xA430000C) # $t1, 0xa430 -/* 010EBC 700102BC 8D29000C */ lw $t1, %lo(0xA430000C)($t1) -/* 010EC0 700102C0 1120000B */ beqz $t1, .L700102F0 -/* 010EC4 700102C4 00000000 */ nop -/* 010EC8 700102C8 3C088002 */ lui $t0, %hi(__OSGlobalIntMask) -/* 010ECC 700102CC 2508698C */ addiu $t0, %lo(__OSGlobalIntMask) # addiu $t0, $t0, 0x698c -/* 010ED0 700102D0 8D080000 */ lw $t0, ($t0) -/* 010ED4 700102D4 8F4C0128 */ lw $t4, 0x128($k0) -/* 010ED8 700102D8 2401FFFF */ li $at, -1 -/* 010EDC 700102DC 00084402 */ srl $t0, $t0, 0x10 -/* 010EE0 700102E0 01014026 */ xor $t0, $t0, $at -/* 010EE4 700102E4 3108003F */ andi $t0, $t0, 0x3f -/* 010EE8 700102E8 010C4024 */ and $t0, $t0, $t4 -/* 010EEC 700102EC 01284825 */ or $t1, $t1, $t0 -.L700102F0: -/* 010EF0 700102F0 AF490128 */ sw $t1, 0x128($k0) -/* 010EF4 700102F4 40087000 */ mfc0 $t0, $14 -/* 010EF8 700102F8 AF48011C */ sw $t0, 0x11c($k0) -/* 010EFC 700102FC 8F480018 */ lw $t0, 0x18($k0) -/* 010F00 70010300 11000014 */ beqz $t0, .L70010354 -/* 010F04 70010304 00000000 */ nop -/* 010F08 70010308 4448F800 */ cfc1 $t0, $31 -/* 010F0C 7001030C 00000000 */ nop -/* 010F10 70010310 AF48012C */ sw $t0, 0x12c($k0) -/* 010F14 70010314 F7400130 */ sdc1 $f0, 0x130($k0) -/* 010F18 70010318 F7420138 */ sdc1 $f2, 0x138($k0) -/* 010F1C 7001031C F7440140 */ sdc1 $f4, 0x140($k0) -/* 010F20 70010320 F7460148 */ sdc1 $f6, 0x148($k0) -/* 010F24 70010324 F7480150 */ sdc1 $f8, 0x150($k0) -/* 010F28 70010328 F74A0158 */ sdc1 $f10, 0x158($k0) -/* 010F2C 7001032C F74C0160 */ sdc1 $f12, 0x160($k0) -/* 010F30 70010330 F74E0168 */ sdc1 $f14, 0x168($k0) -/* 010F34 70010334 F7500170 */ sdc1 $f16, 0x170($k0) -/* 010F38 70010338 F7520178 */ sdc1 $f18, 0x178($k0) -/* 010F3C 7001033C F7540180 */ sdc1 $f20, 0x180($k0) -/* 010F40 70010340 F7560188 */ sdc1 $f22, 0x188($k0) -/* 010F44 70010344 F7580190 */ sdc1 $f24, 0x190($k0) -/* 010F48 70010348 F75A0198 */ sdc1 $f26, 0x198($k0) -/* 010F4C 7001034C F75C01A0 */ sdc1 $f28, 0x1a0($k0) -/* 010F50 70010350 F75E01A8 */ sdc1 $f30, 0x1a8($k0) -.L70010354: -/* 010F54 70010354 40086800 */ mfc0 $t0, $13 -/* 010F58 70010358 AF480120 */ sw $t0, 0x120($k0) -/* 010F5C 7001035C 24090002 */ li $t1, 2 -/* 010F60 70010360 A7490010 */ sh $t1, 0x10($k0) -/* 010F64 70010364 3109007C */ andi $t1, $t0, 0x7c -/* 010F68 70010368 240A0024 */ li $t2, 36 -/* 010F6C 7001036C 112A00B0 */ beq $t1, $t2, .L70010630 -/* 010F70 70010370 00000000 */ nop -/* 010F74 70010374 240A002C */ li $t2, 44 -/* 010F78 70010378 112A00FF */ beq $t1, $t2, .L70010778 -/* 010F7C 7001037C 00000000 */ nop -/* 010F80 70010380 240A0000 */ li $t2, 0 -/* 010F84 70010384 152A00C3 */ bne $t1, $t2, .L70010694 -/* 010F88 70010388 00000000 */ nop -/* 010F8C 7001038C 03688024 */ and $s0, $k1, $t0 -glabel next_interrupt -.L70010390: -/* 010F90 70010390 3209FF00 */ andi $t1, $s0, 0xff00 -/* 010F94 70010394 00095302 */ srl $t2, $t1, 0xc -/* 010F98 70010398 15400003 */ bnez $t2, .L700103A8 -/* 010F9C 7001039C 00000000 */ nop -/* 010FA0 700103A0 00095202 */ srl $t2, $t1, 8 -/* 010FA4 700103A4 214A0010 */ addi $t2, $t2, 0x10 -.L700103A8: -/* 010FA8 700103A8 3C018003 */ lui $at, %hi(__osIntOffTable) -/* 010FAC 700103AC 002A0821 */ addu $at, $at, $t2 -/* 010FB0 700103B0 902A9440 */ lbu $t2, %lo(__osIntOffTable)($at) -/* 010FB4 700103B4 3C018003 */ lui $at, %hi(__osIntTable) -/* 010FB8 700103B8 002A0821 */ addu $at, $at, $t2 -/* 010FBC 700103BC 8C2A9460 */ lw $t2, %lo(__osIntTable)($at) -/* 010FC0 700103C0 01400008 */ jr $t2 -/* 010FC4 700103C4 00000000 */ nop -glabel IP6_Hdlr -/* 010FC8 700103C8 2401DFFF */ li $at, -8193 -/* 010FCC 700103CC 1000FFF0 */ b .L70010390 -/* 010FD0 700103D0 02018024 */ and $s0, $s0, $at -glabel IP7_Hdlr -/* 010FD4 700103D4 2401BFFF */ li $at, -16385 -/* 010FD8 700103D8 1000FFED */ b .L70010390 -/* 010FDC 700103DC 02018024 */ and $s0, $s0, $at -glabel counter -/* 010FE0 700103E0 40095800 */ mfc0 $t1, $11 -/* 010FE4 700103E4 40895800 */ mtc0 $t1, $11 -/* 010FE8 700103E8 0C0041B1 */ jal send_mesg -/* 010FEC 700103EC 24040018 */ li $a0, 24 -/* 010FF0 700103F0 3C01FFFF */ lui $at, (0xFFFF7FFF >> 16) # lui $at, 0xffff -/* 010FF4 700103F4 34217FFF */ ori $at, (0xFFFF7FFF & 0xFFFF) # ori $at, $at, 0x7fff -/* 010FF8 700103F8 1000FFE5 */ b .L70010390 -/* 010FFC 700103FC 02018024 */ and $s0, $s0, $at -glabel cart -/* 011000 70010400 2401F7FF */ li $at, -2049 -/* 011004 70010404 02018024 */ and $s0, $s0, $at -/* 011008 70010408 240A0004 */ li $t2, 4 -/* 01100C 7001040C 3C018002 */ lui $at, %hi(__osHwIntTable) -/* 011010 70010410 002A0821 */ addu $at, $at, $t2 -/* 011014 70010414 8C2A7700 */ lw $t2, %lo(__osHwIntTable)($at) -/* 011018 70010418 3C1D8006 */ lui $sp, %hi(leoDiskStack) # $sp, 0x8006 -/* 01101C 7001041C 27BD7DF0 */ addiu $sp, %lo(leoDiskStack) # addiu $sp, $sp, 0x7df0 -/* 011020 70010420 24040010 */ li $a0, 16 -/* 011024 70010424 11400007 */ beqz $t2, .L70010444 -/* 011028 70010428 27BD0FF0 */ addiu $sp, $sp, 0xff0 -/* 01102C 7001042C 0140F809 */ jalr $t2 -/* 011030 70010430 00000000 */ nop -/* 011034 70010434 10400003 */ beqz $v0, .L70010444 -/* 011038 70010438 00000000 */ nop -/* 01103C 7001043C 10000082 */ b .L70010648 -/* 011040 70010440 00000000 */ nop -.L70010444: -/* 011044 70010444 0C0041B1 */ jal send_mesg -/* 011048 70010448 00000000 */ nop -/* 01104C 7001044C 1000FFD0 */ b .L70010390 -/* 011050 70010450 00000000 */ nop -glabel rcp -/* 011054 70010454 3C088002 */ lui $t0, %hi(__OSGlobalIntMask) -/* 011058 70010458 2508698C */ addiu $t0, %lo(__OSGlobalIntMask) # addiu $t0, $t0, 0x698c -/* 01105C 7001045C 8D080000 */ lw $t0, ($t0) -/* 011060 70010460 3C11A430 */ lui $s1, %hi(0xA4300008) # $s1, 0xa430 -/* 011064 70010464 8E310008 */ lw $s1, %lo(0xA4300008)($s1) -/* 011068 70010468 00084402 */ srl $t0, $t0, 0x10 -/* 01106C 7001046C 02288824 */ and $s1, $s1, $t0 -/* 011070 70010470 32290001 */ andi $t1, $s1, 1 -/* 011074 70010474 11200013 */ beqz $t1, .L700104C4 -/* 011078 70010478 00000000 */ nop -/* 01107C 7001047C 3C0CA404 */ lui $t4, %hi(0xA4040010) # $t4, 0xa404 -/* 011080 70010480 8D8C0010 */ lw $t4, %lo(0xA4040010)($t4) -/* 011084 70010484 24090008 */ li $t1, 8 -/* 011088 70010488 3C01A404 */ lui $at, %hi(0xA4040010) # $at, 0xa404 -/* 01108C 7001048C 318C0300 */ andi $t4, $t4, 0x300 -/* 011090 70010490 3231003E */ andi $s1, $s1, 0x3e -/* 011094 70010494 11800007 */ beqz $t4, .L700104B4 -/* 011098 70010498 AC290010 */ sw $t1, %lo(0xA4040010)($at) -/* 01109C 7001049C 0C0041B1 */ jal send_mesg -/* 0110A0 700104A0 24040020 */ li $a0, 32 -/* 0110A4 700104A4 12200038 */ beqz $s1, .L70010588 -/* 0110A8 700104A8 00000000 */ nop -/* 0110AC 700104AC 10000005 */ b .L700104C4 -/* 0110B0 700104B0 00000000 */ nop -.L700104B4: -/* 0110B4 700104B4 0C0041B1 */ jal send_mesg -/* 0110B8 700104B8 24040058 */ li $a0, 88 -/* 0110BC 700104BC 12200032 */ beqz $s1, .L70010588 -/* 0110C0 700104C0 00000000 */ nop -.L700104C4: -/* 0110C4 700104C4 32290008 */ andi $t1, $s1, 8 -/* 0110C8 700104C8 11200007 */ beqz $t1, .L700104E8 -/* 0110CC 700104CC 3C01A440 */ lui $at, %hi(0xA4400010) # $at, 0xa440 -/* 0110D0 700104D0 32310037 */ andi $s1, $s1, 0x37 -/* 0110D4 700104D4 AC200010 */ sw $zero, %lo(0xA4400010)($at) -/* 0110D8 700104D8 0C0041B1 */ jal send_mesg -/* 0110DC 700104DC 24040038 */ li $a0, 56 -/* 0110E0 700104E0 12200029 */ beqz $s1, .L70010588 -/* 0110E4 700104E4 00000000 */ nop -.L700104E8: -/* 0110E8 700104E8 32290004 */ andi $t1, $s1, 4 -/* 0110EC 700104EC 11200009 */ beqz $t1, .L70010514 -/* 0110F0 700104F0 00000000 */ nop -/* 0110F4 700104F4 24090001 */ li $t1, 1 -/* 0110F8 700104F8 3C01A450 */ lui $at, %hi(0xA450000C) # $at, 0xa450 -/* 0110FC 700104FC 3231003B */ andi $s1, $s1, 0x3b -/* 011100 70010500 AC29000C */ sw $t1, %lo(0xA450000C)($at) -/* 011104 70010504 0C0041B1 */ jal send_mesg -/* 011108 70010508 24040030 */ li $a0, 48 -/* 01110C 7001050C 1220001E */ beqz $s1, .L70010588 -/* 011110 70010510 00000000 */ nop -.L70010514: -/* 011114 70010514 32290002 */ andi $t1, $s1, 2 -/* 011118 70010518 11200007 */ beqz $t1, .L70010538 -/* 01111C 7001051C 3C01A480 */ lui $at, %hi(0xA4800018) # $at, 0xa480 -/* 011120 70010520 3231003D */ andi $s1, $s1, 0x3d -/* 011124 70010524 AC200018 */ sw $zero, %lo(0xA4800018)($at) -/* 011128 70010528 0C0041B1 */ jal send_mesg -/* 01112C 7001052C 24040028 */ li $a0, 40 -/* 011130 70010530 12200015 */ beqz $s1, .L70010588 -/* 011134 70010534 00000000 */ nop -.L70010538: -/* 011138 70010538 32290010 */ andi $t1, $s1, 0x10 -/* 01113C 7001053C 11200009 */ beqz $t1, .L70010564 -/* 011140 70010540 00000000 */ nop -/* 011144 70010544 24090002 */ li $t1, 2 -/* 011148 70010548 3C01A460 */ lui $at, %hi(0xA4600010) # $at, 0xa460 -/* 01114C 7001054C 3231002F */ andi $s1, $s1, 0x2f -/* 011150 70010550 AC290010 */ sw $t1, %lo(0xA4600010)($at) -/* 011154 70010554 0C0041B1 */ jal send_mesg -/* 011158 70010558 24040040 */ li $a0, 64 -/* 01115C 7001055C 1220000A */ beqz $s1, .L70010588 -/* 011160 70010560 00000000 */ nop -.L70010564: -/* 011164 70010564 32290020 */ andi $t1, $s1, 0x20 -/* 011168 70010568 11200007 */ beqz $t1, .L70010588 -/* 01116C 7001056C 00000000 */ nop -/* 011170 70010570 24090800 */ li $t1, 2048 -/* 011174 70010574 3C01A430 */ lui $at, 0xa430 -/* 011178 70010578 3231001F */ andi $s1, $s1, 0x1f -/* 01117C 7001057C AC290000 */ sw $t1, ($at) -/* 011180 70010580 0C0041B1 */ jal send_mesg -/* 011184 70010584 24040048 */ li $a0, 72 -.L70010588: -/* 011188 70010588 2401FBFF */ li $at, -1025 -/* 01118C 7001058C 1000FF80 */ b .L70010390 -/* 011190 70010590 02018024 */ and $s0, $s0, $at -glabel prenmi -/* 011194 70010594 8F5B0118 */ lw $k1, 0x118($k0) -/* 011198 70010598 2401EFFF */ li $at, -4097 -/* 01119C 7001059C 3C098002 */ lui $t1, %hi(__osShutdown) -/* 0111A0 700105A0 0361D824 */ and $k1, $k1, $at -/* 0111A4 700105A4 AF5B0118 */ sw $k1, 0x118($k0) -/* 0111A8 700105A8 25296988 */ addiu $t1, %lo(__osShutdown) # addiu $t1, $t1, 0x6988 -/* 0111AC 700105AC 8D2A0000 */ lw $t2, ($t1) -/* 0111B0 700105B0 11400003 */ beqz $t2, .L700105C0 -/* 0111B4 700105B4 2401EFFF */ li $at, -4097 -/* 0111B8 700105B8 10000023 */ b .L70010648 -/* 0111BC 700105BC 02018024 */ and $s0, $s0, $at -.L700105C0: -/* 0111C0 700105C0 240A0001 */ li $t2, 1 -/* 0111C4 700105C4 AD2A0000 */ sw $t2, ($t1) -/* 0111C8 700105C8 0C0041B1 */ jal send_mesg -/* 0111CC 700105CC 24040070 */ li $a0, 112 -/* 0111D0 700105D0 3C0A8002 */ lui $t2, %hi(__osRunQueue) -/* 0111D4 700105D4 8D4A7728 */ lw $t2, %lo(__osRunQueue)($t2) -/* 0111D8 700105D8 2401EFFF */ li $at, -4097 -/* 0111DC 700105DC 02018024 */ and $s0, $s0, $at -/* 0111E0 700105E0 8D5B0118 */ lw $k1, 0x118($t2) -/* 0111E4 700105E4 0361D824 */ and $k1, $k1, $at -/* 0111E8 700105E8 10000017 */ b .L70010648 -/* 0111EC 700105EC AD5B0118 */ sw $k1, 0x118($t2) -glabel sw2 -/* 0111F0 700105F0 2401FDFF */ li $at, -513 -/* 0111F4 700105F4 01014024 */ and $t0, $t0, $at -/* 0111F8 700105F8 40886800 */ mtc0 $t0, $13 -/* 0111FC 700105FC 0C0041B1 */ jal send_mesg -/* 011200 70010600 24040008 */ li $a0, 8 -/* 011204 70010604 2401FDFF */ li $at, -513 -/* 011208 70010608 1000FF61 */ b .L70010390 -/* 01120C 7001060C 02018024 */ and $s0, $s0, $at -glabel swl -/* 011210 70010610 2401FEFF */ li $at, -257 -/* 011214 70010614 01014024 */ and $t0, $t0, $at -/* 011218 70010618 40886800 */ mtc0 $t0, $13 -/* 01121C 7001061C 0C0041B1 */ jal send_mesg -/* 011220 70010620 24040000 */ li $a0, 0 -/* 011224 70010624 2401FEFF */ li $at, -257 -/* 011228 70010628 1000FF59 */ b .L70010390 -/* 01122C 7001062C 02018024 */ and $s0, $s0, $at -.L70010630: -/* 011230 70010630 24090001 */ li $t1, 1 -/* 011234 70010634 A7490012 */ sh $t1, 0x12($k0) -/* 011238 70010638 0C0041B1 */ jal send_mesg -/* 01123C 7001063C 24040050 */ li $a0, 80 -/* 011240 70010640 10000001 */ b .L70010648 -/* 011244 70010644 00000000 */ nop -glabel redispatch -.L70010648: -/* 011248 70010648 3C0A8002 */ lui $t2, %hi(__osRunQueue) -/* 01124C 7001064C 8D4A7728 */ lw $t2, %lo(__osRunQueue)($t2) -/* 011250 70010650 8F490004 */ lw $t1, 4($k0) -/* 011254 70010654 8D4B0004 */ lw $t3, 4($t2) -/* 011258 70010658 012B082A */ slt $at, $t1, $t3 -/* 01125C 7001065C 10200007 */ beqz $at, .L7001067C -/* 011260 70010660 00000000 */ nop -/* 011264 70010664 3C048002 */ lui $a0, %hi(__osRunQueue) -/* 011268 70010668 03402825 */ move $a1, $k0 -/* 01126C 7001066C 0C00422B */ jal __osEnqueueThread -/* 011270 70010670 24847728 */ addiu $a0, %lo(__osRunQueue) # addiu $a0, $a0, 0x7728 -/* 011274 70010674 08004241 */ j __osDispatchThread -/* 011278 70010678 00000000 */ nop - -glabel enqueueRunning -.L7001067C: -/* 01127C 7001067C 3C098002 */ lui $t1, %hi(__osRunQueue) -/* 011280 70010680 25297728 */ addiu $t1, %lo(__osRunQueue) # addiu $t1, $t1, 0x7728 -/* 011284 70010684 8D2A0000 */ lw $t2, ($t1) -/* 011288 70010688 AF4A0000 */ sw $t2, ($k0) -/* 01128C 7001068C 08004241 */ j __osDispatchThread -/* 011290 70010690 AD3A0000 */ sw $k0, ($t1) - -.L70010694: -/* 011294 70010694 3C018002 */ lui $at, %hi(__osFaultedThread) -/* 011298 70010698 AC3A7734 */ sw $k0, %lo(__osFaultedThread)($at) -/* 01129C 7001069C 24090001 */ li $t1, 1 -/* 0112A0 700106A0 A7490010 */ sh $t1, 0x10($k0) -/* 0112A4 700106A4 24090002 */ li $t1, 2 -/* 0112A8 700106A8 A7490012 */ sh $t1, 0x12($k0) -/* 0112AC 700106AC 400A4000 */ mfc0 $t2, $8 -/* 0112B0 700106B0 AF4A0124 */ sw $t2, 0x124($k0) -/* 0112B4 700106B4 0C0041B1 */ jal send_mesg -/* 0112B8 700106B8 24040060 */ li $a0, 96 -/* 0112BC 700106BC 08004241 */ j __osDispatchThread -/* 0112C0 700106C0 00000000 */ nop - -glabel send_mesg -/* 0112C4 700106C4 3C0A8006 */ lui $t2, %hi(__osEventStateTab) -/* 0112C8 700106C8 254A6A10 */ addiu $t2, %lo(__osEventStateTab) # addiu $t2, $t2, 0x6a10 -/* 0112CC 700106CC 01445021 */ addu $t2, $t2, $a0 -/* 0112D0 700106D0 8D490000 */ lw $t1, ($t2) -/* 0112D4 700106D4 03E09025 */ move $s2, $ra -/* 0112D8 700106D8 11200025 */ beqz $t1, .L70010770 -/* 0112DC 700106DC 00000000 */ nop -/* 0112E0 700106E0 8D2B0008 */ lw $t3, 8($t1) -/* 0112E4 700106E4 8D2C0010 */ lw $t4, 0x10($t1) -/* 0112E8 700106E8 016C082A */ slt $at, $t3, $t4 -/* 0112EC 700106EC 10200020 */ beqz $at, .L70010770 -/* 0112F0 700106F0 00000000 */ nop -/* 0112F4 700106F4 8D2D000C */ lw $t5, 0xc($t1) -/* 0112F8 700106F8 01AB6821 */ addu $t5, $t5, $t3 -/* 0112FC 700106FC 01AC001A */ div $zero, $t5, $t4 -/* 011300 70010700 15800002 */ bnez $t4, .L7001070C -/* 011304 70010704 00000000 */ nop -/* 011308 70010708 0007000D */ break 7 -.L7001070C: -/* 01130C 7001070C 2401FFFF */ li $at, -1 -/* 011310 70010710 15810004 */ bne $t4, $at, .L70010724 -/* 011314 70010714 3C018000 */ lui $at, 0x8000 -/* 011318 70010718 15A10002 */ bne $t5, $at, .L70010724 -/* 01131C 7001071C 00000000 */ nop -/* 011320 70010720 0006000D */ break 6 -.L70010724: -/* 011324 70010724 8D2C0014 */ lw $t4, 0x14($t1) -/* 011328 70010728 00006810 */ mfhi $t5 -/* 01132C 7001072C 000D6880 */ sll $t5, $t5, 2 -/* 011330 70010730 018D6021 */ addu $t4, $t4, $t5 -/* 011334 70010734 8D4D0004 */ lw $t5, 4($t2) -/* 011338 70010738 256A0001 */ addiu $t2, $t3, 1 -/* 01133C 7001073C AD8D0000 */ sw $t5, ($t4) -/* 011340 70010740 AD2A0008 */ sw $t2, 8($t1) -/* 011344 70010744 8D2A0000 */ lw $t2, ($t1) -/* 011348 70010748 8D4B0000 */ lw $t3, ($t2) -/* 01134C 7001074C 11600008 */ beqz $t3, .L70010770 -/* 011350 70010750 00000000 */ nop -/* 011354 70010754 0C00423D */ jal __osPopThread -/* 011358 70010758 01202025 */ move $a0, $t1 -/* 01135C 7001075C 00405025 */ move $t2, $v0 -/* 011360 70010760 3C048002 */ lui $a0, %hi(__osRunQueue) -/* 011364 70010764 01402825 */ move $a1, $t2 -/* 011368 70010768 0C00422B */ jal __osEnqueueThread -/* 01136C 7001076C 24847728 */ addiu $a0, %lo(__osRunQueue) # addiu $a0, $a0, 0x7728 -.L70010770: -/* 011370 70010770 02400008 */ jr $s2 -/* 011374 70010774 00000000 */ nop -glabel handle_CpU -.L70010778: -/* 011378 70010778 3C013000 */ lui $at, 0x3000 -/* 01137C 7001077C 01014824 */ and $t1, $t0, $at -/* 011380 70010780 00094F02 */ srl $t1, $t1, 0x1c -/* 011384 70010784 240A0001 */ li $t2, 1 -/* 011388 70010788 152AFFC2 */ bne $t1, $t2, .L70010694 -/* 01138C 7001078C 00000000 */ nop -/* 011390 70010790 8F5B0118 */ lw $k1, 0x118($k0) -/* 011394 70010794 3C012000 */ lui $at, 0x2000 -/* 011398 70010798 24090001 */ li $t1, 1 -/* 01139C 7001079C 0361D825 */ or $k1, $k1, $at -/* 0113A0 700107A0 AF490018 */ sw $t1, 0x18($k0) -/* 0113A4 700107A4 1000FFB5 */ b .L7001067C -/* 0113A8 700107A8 AF5B0118 */ sw $k1, 0x118($k0) -glabel __osEnqueueAndYield -/* 0113AC 700107AC 3C058002 */ lui $a1, %hi(__osRunningThread) -/* 0113B0 700107B0 8CA57730 */ lw $a1, %lo(__osRunningThread)($a1) -/* 0113B4 700107B4 40086000 */ mfc0 $t0, $12 -/* 0113B8 700107B8 8CBB0018 */ lw $k1, 0x18($a1) -/* 0113BC 700107BC 35080002 */ ori $t0, $t0, 2 -/* 0113C0 700107C0 ACA80118 */ sw $t0, 0x118($a1) -/* 0113C4 700107C4 FCB00098 */ sd $s0, 0x98($a1) -/* 0113C8 700107C8 FCB100A0 */ sd $s1, 0xa0($a1) -/* 0113CC 700107CC FCB200A8 */ sd $s2, 0xa8($a1) -/* 0113D0 700107D0 FCB300B0 */ sd $s3, 0xb0($a1) -/* 0113D4 700107D4 FCB400B8 */ sd $s4, 0xb8($a1) -/* 0113D8 700107D8 FCB500C0 */ sd $s5, 0xc0($a1) -/* 0113DC 700107DC FCB600C8 */ sd $s6, 0xc8($a1) -/* 0113E0 700107E0 FCB700D0 */ sd $s7, 0xd0($a1) -/* 0113E4 700107E4 FCBC00E8 */ sd $gp, 0xe8($a1) -/* 0113E8 700107E8 FCBD00F0 */ sd $sp, 0xf0($a1) -/* 0113EC 700107EC FCBE00F8 */ sd $fp, 0xf8($a1) -/* 0113F0 700107F0 FCBF0100 */ sd $ra, 0x100($a1) -/* 0113F4 700107F4 13600009 */ beqz $k1, .L7001081C -/* 0113F8 700107F8 ACBF011C */ sw $ra, 0x11c($a1) -/* 0113FC 700107FC 445BF800 */ cfc1 $k1, $31 -/* 011400 70010800 F4B40180 */ sdc1 $f20, 0x180($a1) -/* 011404 70010804 F4B60188 */ sdc1 $f22, 0x188($a1) -/* 011408 70010808 F4B80190 */ sdc1 $f24, 0x190($a1) -/* 01140C 7001080C F4BA0198 */ sdc1 $f26, 0x198($a1) -/* 011410 70010810 F4BC01A0 */ sdc1 $f28, 0x1a0($a1) -/* 011414 70010814 F4BE01A8 */ sdc1 $f30, 0x1a8($a1) -/* 011418 70010818 ACBB012C */ sw $k1, 0x12c($a1) -.L7001081C: -/* 01141C 7001081C 8CBB0118 */ lw $k1, 0x118($a1) -/* 011420 70010820 3369FF00 */ andi $t1, $k1, 0xff00 -/* 011424 70010824 1120000D */ beqz $t1, .L7001085C -/* 011428 70010828 00000000 */ nop -/* 01142C 7001082C 3C088002 */ lui $t0, %hi(__OSGlobalIntMask) -/* 011430 70010830 2508698C */ addiu $t0, %lo(__OSGlobalIntMask) # addiu $t0, $t0, 0x698c -/* 011434 70010834 8D080000 */ lw $t0, ($t0) -/* 011438 70010838 2401FFFF */ li $at, -1 -/* 01143C 7001083C 01014026 */ xor $t0, $t0, $at -/* 011440 70010840 3C01FFFF */ lui $at, (0xFFFF00FF >> 16) # lui $at, 0xffff -/* 011444 70010844 3108FF00 */ andi $t0, $t0, 0xff00 -/* 011448 70010848 342100FF */ ori $at, (0xFFFF00FF & 0xFFFF) # ori $at, $at, 0xff -/* 01144C 7001084C 01284825 */ or $t1, $t1, $t0 -/* 011450 70010850 0361D824 */ and $k1, $k1, $at -/* 011454 70010854 0369D825 */ or $k1, $k1, $t1 -/* 011458 70010858 ACBB0118 */ sw $k1, 0x118($a1) -.L7001085C: -/* 01145C 7001085C 3C1BA430 */ lui $k1, %hi(0xA430000C) # $k1, 0xa430 -/* 011460 70010860 8F7B000C */ lw $k1, %lo(0xA430000C)($k1) -/* 011464 70010864 1360000B */ beqz $k1, .L70010894 -/* 011468 70010868 00000000 */ nop -/* 01146C 7001086C 3C1A8002 */ lui $k0, %hi(__OSGlobalIntMask) # $k0, 0x8002 -/* 011470 70010870 275A698C */ addiu $k0, %lo(__OSGlobalIntMask) # addiu $k0, $k0, 0x698c -/* 011474 70010874 8F5A0000 */ lw $k0, ($k0) -/* 011478 70010878 8CA80128 */ lw $t0, 0x128($a1) -/* 01147C 7001087C 2401FFFF */ li $at, -1 -/* 011480 70010880 001AD402 */ srl $k0, $k0, 0x10 -/* 011484 70010884 0341D026 */ xor $k0, $k0, $at -/* 011488 70010888 335A003F */ andi $k0, $k0, 0x3f -/* 01148C 7001088C 0348D024 */ and $k0, $k0, $t0 -/* 011490 70010890 037AD825 */ or $k1, $k1, $k0 -.L70010894: -/* 011494 70010894 10800003 */ beqz $a0, .L700108A4 -/* 011498 70010898 ACBB0128 */ sw $k1, 0x128($a1) -/* 01149C 7001089C 0C00422B */ jal __osEnqueueThread -/* 0114A0 700108A0 00000000 */ nop -.L700108A4: -/* 0114A4 700108A4 08004241 */ j __osDispatchThread -/* 0114A8 700108A8 00000000 */ nop - -glabel __osEnqueueThread -/* 0114AC 700108AC 8C980000 */ lw $t8, ($a0) -/* 0114B0 700108B0 8CAF0004 */ lw $t7, 4($a1) -/* 0114B4 700108B4 0080C825 */ move $t9, $a0 -/* 0114B8 700108B8 8F0E0004 */ lw $t6, 4($t8) -/* 0114BC 700108BC 01CF082A */ slt $at, $t6, $t7 -/* 0114C0 700108C0 14200007 */ bnez $at, .L700108E0 -/* 0114C4 700108C4 00000000 */ nop -.L700108C8: -/* 0114C8 700108C8 0300C825 */ move $t9, $t8 -/* 0114CC 700108CC 8F180000 */ lw $t8, ($t8) -/* 0114D0 700108D0 8F0E0004 */ lw $t6, 4($t8) -/* 0114D4 700108D4 01CF082A */ slt $at, $t6, $t7 -/* 0114D8 700108D8 1020FFFB */ beqz $at, .L700108C8 -/* 0114DC 700108DC 00000000 */ nop -.L700108E0: -/* 0114E0 700108E0 8F380000 */ lw $t8, ($t9) -/* 0114E4 700108E4 ACB80000 */ sw $t8, ($a1) -/* 0114E8 700108E8 AF250000 */ sw $a1, ($t9) -/* 0114EC 700108EC 03E00008 */ jr $ra -/* 0114F0 700108F0 ACA40008 */ sw $a0, 8($a1) - -glabel __osPopThread -/* 0114F4 700108F4 8C820000 */ lw $v0, ($a0) -/* 0114F8 700108F8 8C590000 */ lw $t9, ($v0) -/* 0114FC 700108FC 03E00008 */ jr $ra -/* 011500 70010900 AC990000 */ sw $t9, ($a0) - -glabel __osDispatchThread -/* 011504 70010904 3C048002 */ lui $a0, %hi(__osRunQueue) -/* 011508 70010908 0C00423D */ jal __osPopThread -/* 01150C 7001090C 24847728 */ addiu $a0, %lo(__osRunQueue) # addiu $a0, $a0, 0x7728 -/* 011510 70010910 3C018002 */ lui $at, %hi(__osRunningThread) -/* 011514 70010914 AC227730 */ sw $v0, %lo(__osRunningThread)($at) -/* 011518 70010918 24080004 */ li $t0, 4 -/* 01151C 7001091C A4480010 */ sh $t0, 0x10($v0) -/* 011520 70010920 0040D025 */ move $k0, $v0 -glabel __osDispatchThreadSave -/* 011524 70010924 3C088002 */ lui $t0, %hi(__OSGlobalIntMask) -/* 011528 70010928 8F5B0118 */ lw $k1, 0x118($k0) -/* 01152C 7001092C 2508698C */ addiu $t0, %lo(__OSGlobalIntMask) # addiu $t0, $t0, 0x698c -/* 011530 70010930 8D080000 */ lw $t0, ($t0) -/* 011534 70010934 3C01FFFF */ lui $at, (0xFFFF00FF >> 16) # lui $at, 0xffff -/* 011538 70010938 3369FF00 */ andi $t1, $k1, 0xff00 -/* 01153C 7001093C 342100FF */ ori $at, (0xFFFF00FF & 0xFFFF) # ori $at, $at, 0xff -/* 011540 70010940 3108FF00 */ andi $t0, $t0, 0xff00 -/* 011544 70010944 01284824 */ and $t1, $t1, $t0 -/* 011548 70010948 0361D824 */ and $k1, $k1, $at -/* 01154C 7001094C 0369D825 */ or $k1, $k1, $t1 -/* 011550 70010950 409B6000 */ mtc0 $k1, $12 -/* 011554 70010954 DF5B0108 */ ld $k1, 0x108($k0) -/* 011558 70010958 DF410020 */ ld $at, 0x20($k0) -/* 01155C 7001095C DF420028 */ ld $v0, 0x28($k0) -/* 011560 70010960 03600013 */ mtlo $k1 -/* 011564 70010964 DF5B0110 */ ld $k1, 0x110($k0) -/* 011568 70010968 DF430030 */ ld $v1, 0x30($k0) -/* 01156C 7001096C DF440038 */ ld $a0, 0x38($k0) -/* 011570 70010970 DF450040 */ ld $a1, 0x40($k0) -/* 011574 70010974 DF460048 */ ld $a2, 0x48($k0) -/* 011578 70010978 DF470050 */ ld $a3, 0x50($k0) -/* 01157C 7001097C DF480058 */ ld $t0, 0x58($k0) -/* 011580 70010980 DF490060 */ ld $t1, 0x60($k0) -/* 011584 70010984 DF4A0068 */ ld $t2, 0x68($k0) -/* 011588 70010988 DF4B0070 */ ld $t3, 0x70($k0) -/* 01158C 7001098C DF4C0078 */ ld $t4, 0x78($k0) -/* 011590 70010990 DF4D0080 */ ld $t5, 0x80($k0) -/* 011594 70010994 DF4E0088 */ ld $t6, 0x88($k0) -/* 011598 70010998 DF4F0090 */ ld $t7, 0x90($k0) -/* 01159C 7001099C DF500098 */ ld $s0, 0x98($k0) -/* 0115A0 700109A0 DF5100A0 */ ld $s1, 0xa0($k0) -/* 0115A4 700109A4 DF5200A8 */ ld $s2, 0xa8($k0) -/* 0115A8 700109A8 DF5300B0 */ ld $s3, 0xb0($k0) -/* 0115AC 700109AC DF5400B8 */ ld $s4, 0xb8($k0) -/* 0115B0 700109B0 DF5500C0 */ ld $s5, 0xc0($k0) -/* 0115B4 700109B4 DF5600C8 */ ld $s6, 0xc8($k0) -/* 0115B8 700109B8 DF5700D0 */ ld $s7, 0xd0($k0) -/* 0115BC 700109BC DF5800D8 */ ld $t8, 0xd8($k0) -/* 0115C0 700109C0 DF5900E0 */ ld $t9, 0xe0($k0) -/* 0115C4 700109C4 DF5C00E8 */ ld $gp, 0xe8($k0) -/* 0115C8 700109C8 03600011 */ mthi $k1 -/* 0115CC 700109CC DF5D00F0 */ ld $sp, 0xf0($k0) -/* 0115D0 700109D0 DF5E00F8 */ ld $fp, 0xf8($k0) -/* 0115D4 700109D4 DF5F0100 */ ld $ra, 0x100($k0) -/* 0115D8 700109D8 8F5B011C */ lw $k1, 0x11c($k0) -/* 0115DC 700109DC 409B7000 */ mtc0 $k1, $14 -/* 0115E0 700109E0 8F5B0018 */ lw $k1, 0x18($k0) -/* 0115E4 700109E4 13600013 */ beqz $k1, .L70010A34 -/* 0115E8 700109E8 00000000 */ nop -/* 0115EC 700109EC 8F5B012C */ lw $k1, 0x12c($k0) -/* 0115F0 700109F0 44DBF800 */ ctc1 $k1, $31 -/* 0115F4 700109F4 D7400130 */ ldc1 $f0, 0x130($k0) -/* 0115F8 700109F8 D7420138 */ ldc1 $f2, 0x138($k0) -/* 0115FC 700109FC D7440140 */ ldc1 $f4, 0x140($k0) -/* 011600 70010A00 D7460148 */ ldc1 $f6, 0x148($k0) -/* 011604 70010A04 D7480150 */ ldc1 $f8, 0x150($k0) -/* 011608 70010A08 D74A0158 */ ldc1 $f10, 0x158($k0) -/* 01160C 70010A0C D74C0160 */ ldc1 $f12, 0x160($k0) -/* 011610 70010A10 D74E0168 */ ldc1 $f14, 0x168($k0) -/* 011614 70010A14 D7500170 */ ldc1 $f16, 0x170($k0) -/* 011618 70010A18 D7520178 */ ldc1 $f18, 0x178($k0) -/* 01161C 70010A1C D7540180 */ ldc1 $f20, 0x180($k0) -/* 011620 70010A20 D7560188 */ ldc1 $f22, 0x188($k0) -/* 011624 70010A24 D7580190 */ ldc1 $f24, 0x190($k0) -/* 011628 70010A28 D75A0198 */ ldc1 $f26, 0x198($k0) -/* 01162C 70010A2C D75C01A0 */ ldc1 $f28, 0x1a0($k0) -/* 011630 70010A30 D75E01A8 */ ldc1 $f30, 0x1a8($k0) -.L70010A34: -/* 011634 70010A34 8F5B0128 */ lw $k1, 0x128($k0) -/* 011638 70010A38 3C1A8002 */ lui $k0, %hi(__OSGlobalIntMask) # $k0, 0x8002 -/* 01163C 70010A3C 275A698C */ addiu $k0, %lo(__OSGlobalIntMask) # addiu $k0, $k0, 0x698c -/* 011640 70010A40 8F5A0000 */ lw $k0, ($k0) -/* 011644 70010A44 001AD402 */ srl $k0, $k0, 0x10 -/* 011648 70010A48 037AD824 */ and $k1, $k1, $k0 -/* 01164C 70010A4C 001BD840 */ sll $k1, $k1, 1 -/* 011650 70010A50 3C1A8003 */ lui $k0, %hi(__osRcpImTable) # $k0, 0x8003 -/* 011654 70010A54 275A93A0 */ addiu $k0, %lo(__osRcpImTable) # addiu $k0, $k0, -0x6c60 -/* 011658 70010A58 037AD821 */ addu $k1, $k1, $k0 -/* 01165C 70010A5C 977B0000 */ lhu $k1, ($k1) -/* 011660 70010A60 3C1AA430 */ lui $k0, %hi(0xA430000C) # $k0, 0xa430 -/* 011664 70010A64 275A000C */ addiu $k0, %lo(0xA430000C) # addiu $k0, $k0, 0xc -/* 011668 70010A68 AF5B0000 */ sw $k1, ($k0) -/* 01166C 70010A6C 00000000 */ nop -/* 011670 70010A70 00000000 */ nop -/* 011674 70010A74 00000000 */ nop -/* 011678 70010A78 00000000 */ nop -/* 01167C 70010A7C 42000018 */ eret -glabel __osCleanupThread -/* 011680 70010A80 0C006ED8 */ jal osDestroyThread -/* 011684 70010A84 00002025 */ move $a0, $zero - - -.section .data -/*80027704*/ -glabel __osHwIntTable -.word 0, 0, 0, 0 -.word 0, 0, 0, 0 - -.section .rodata -/*80029440*/ -glabel __osIntOffTable -.byte 0,0x14,0x18,0x18,0x1C,0x1C,0x1C,0x1C -.byte 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20 -.byte 0, 4, 8, 8, 0xC, 0xC, 0xC, 0xC -.byte 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10 - -/*80029460*/ -glabel __osIntTable -.word redispatch -.word swl -.word sw2 -.word rcp -.word cart -.word prenmi -.word IP6_Hdlr -.word IP7_Hdlr -.word counter +# asm file. +# modified version of "exceptasm.s" + +# asm file. +# +# TODO: this is an -mips2 match, the libultra version is instruction set `-mips3 -o32`. + +# assembler directives +.set noat # allow manual use of $at +.set noreorder # don't insert nops after branches +.set gp=64 + +.include "macros.inc" + + +.section .text, "ax" +glabel __osExceptionPreamble +/* 010D90 70010190 3C1A7001 */ lui $k0, %hi(__osException) # $k0, 0x7001 +/* 010D94 70010194 275A01A0 */ addiu $k0, %lo(__osException) # addiu $k0, $k0, 0x1a0 +/* 010D98 70010198 03400008 */ jr $k0 +/* 010D9C 7001019C 00000000 */ nop +glabel __osException +/* 010DA0 700101A0 3C1A8007 */ lui $k0, %hi(__osThreadSave) # $k0, 0x8007 +/* 010DA4 700101A4 275A8E30 */ addiu $k0, %lo(__osThreadSave) # addiu $k0, $k0, -0x71d0 +/* 010DA8 700101A8 FF410020 */ sd $at, 0x20($k0) +/* 010DAC 700101AC 401B6000 */ mfc0 $k1, $12 +/* 010DB0 700101B0 AF5B0118 */ sw $k1, 0x118($k0) +/* 010DB4 700101B4 2401FFFC */ li $at, -4 +/* 010DB8 700101B8 0361D824 */ and $k1, $k1, $at +/* 010DBC 700101BC 409B6000 */ mtc0 $k1, $12 +/* 010DC0 700101C0 FF480058 */ sd $t0, 0x58($k0) +/* 010DC4 700101C4 FF490060 */ sd $t1, 0x60($k0) +/* 010DC8 700101C8 FF4A0068 */ sd $t2, 0x68($k0) +/* 010DCC 700101CC AF400018 */ sw $zero, 0x18($k0) +/* 010DD0 700101D0 40086800 */ mfc0 $t0, $13 +/* 010DD4 700101D4 03404025 */ move $t0, $k0 +/* 010DD8 700101D8 3C1A8002 */ lui $k0, %hi(__osRunningThread) # $k0, 0x8002 +/* 010DDC 700101DC 8F5A7730 */ lw $k0, %lo(__osRunningThread)($k0) +/* 010DE0 700101E0 DD090020 */ ld $t1, 0x20($t0) +/* 010DE4 700101E4 FF490020 */ sd $t1, 0x20($k0) +/* 010DE8 700101E8 DD090118 */ ld $t1, 0x118($t0) +/* 010DEC 700101EC FF490118 */ sd $t1, 0x118($k0) +/* 010DF0 700101F0 DD090058 */ ld $t1, 0x58($t0) +/* 010DF4 700101F4 FF490058 */ sd $t1, 0x58($k0) +/* 010DF8 700101F8 DD090060 */ ld $t1, 0x60($t0) +/* 010DFC 700101FC FF490060 */ sd $t1, 0x60($k0) +/* 010E00 70010200 DD090068 */ ld $t1, 0x68($t0) +/* 010E04 70010204 FF490068 */ sd $t1, 0x68($k0) +/* 010E08 70010208 8F5B0118 */ lw $k1, 0x118($k0) +/* 010E0C 7001020C 00004012 */ mflo $t0 +/* 010E10 70010210 FF480108 */ sd $t0, 0x108($k0) +/* 010E14 70010214 00004010 */ mfhi $t0 +/* 010E18 70010218 3369FF00 */ andi $t1, $k1, 0xff00 +/* 010E1C 7001021C FF420028 */ sd $v0, 0x28($k0) +/* 010E20 70010220 FF430030 */ sd $v1, 0x30($k0) +/* 010E24 70010224 FF440038 */ sd $a0, 0x38($k0) +/* 010E28 70010228 FF450040 */ sd $a1, 0x40($k0) +/* 010E2C 7001022C FF460048 */ sd $a2, 0x48($k0) +/* 010E30 70010230 FF470050 */ sd $a3, 0x50($k0) +/* 010E34 70010234 FF4B0070 */ sd $t3, 0x70($k0) +/* 010E38 70010238 FF4C0078 */ sd $t4, 0x78($k0) +/* 010E3C 7001023C FF4D0080 */ sd $t5, 0x80($k0) +/* 010E40 70010240 FF4E0088 */ sd $t6, 0x88($k0) +/* 010E44 70010244 FF4F0090 */ sd $t7, 0x90($k0) +/* 010E48 70010248 FF500098 */ sd $s0, 0x98($k0) +/* 010E4C 7001024C FF5100A0 */ sd $s1, 0xa0($k0) +/* 010E50 70010250 FF5200A8 */ sd $s2, 0xa8($k0) +/* 010E54 70010254 FF5300B0 */ sd $s3, 0xb0($k0) +/* 010E58 70010258 FF5400B8 */ sd $s4, 0xb8($k0) +/* 010E5C 7001025C FF5500C0 */ sd $s5, 0xc0($k0) +/* 010E60 70010260 FF5600C8 */ sd $s6, 0xc8($k0) +/* 010E64 70010264 FF5700D0 */ sd $s7, 0xd0($k0) +/* 010E68 70010268 FF5800D8 */ sd $t8, 0xd8($k0) +/* 010E6C 7001026C FF5900E0 */ sd $t9, 0xe0($k0) +/* 010E70 70010270 FF5C00E8 */ sd $gp, 0xe8($k0) +/* 010E74 70010274 FF5D00F0 */ sd $sp, 0xf0($k0) +/* 010E78 70010278 FF5E00F8 */ sd $fp, 0xf8($k0) +/* 010E7C 7001027C FF5F0100 */ sd $ra, 0x100($k0) +/* 010E80 70010280 1120000D */ beqz $t1, .L700102B8 +/* 010E84 70010284 FF480110 */ sd $t0, 0x110($k0) +/* 010E88 70010288 3C088002 */ lui $t0, %hi(__OSGlobalIntMask) +/* 010E8C 7001028C 2508698C */ addiu $t0, %lo(__OSGlobalIntMask) # addiu $t0, $t0, 0x698c +/* 010E90 70010290 8D080000 */ lw $t0, ($t0) +/* 010E94 70010294 2401FFFF */ li $at, -1 +/* 010E98 70010298 01014026 */ xor $t0, $t0, $at +/* 010E9C 7001029C 3C01FFFF */ lui $at, (0xFFFF00FF >> 16) # lui $at, 0xffff +/* 010EA0 700102A0 3108FF00 */ andi $t0, $t0, 0xff00 +/* 010EA4 700102A4 342100FF */ ori $at, (0xFFFF00FF & 0xFFFF) # ori $at, $at, 0xff +/* 010EA8 700102A8 01284825 */ or $t1, $t1, $t0 +/* 010EAC 700102AC 0361D824 */ and $k1, $k1, $at +/* 010EB0 700102B0 0369D825 */ or $k1, $k1, $t1 +/* 010EB4 700102B4 AF5B0118 */ sw $k1, 0x118($k0) +.L700102B8: +/* 010EB8 700102B8 3C09A430 */ lui $t1, %hi(0xA430000C) # $t1, 0xa430 +/* 010EBC 700102BC 8D29000C */ lw $t1, %lo(0xA430000C)($t1) +/* 010EC0 700102C0 1120000B */ beqz $t1, .L700102F0 +/* 010EC4 700102C4 00000000 */ nop +/* 010EC8 700102C8 3C088002 */ lui $t0, %hi(__OSGlobalIntMask) +/* 010ECC 700102CC 2508698C */ addiu $t0, %lo(__OSGlobalIntMask) # addiu $t0, $t0, 0x698c +/* 010ED0 700102D0 8D080000 */ lw $t0, ($t0) +/* 010ED4 700102D4 8F4C0128 */ lw $t4, 0x128($k0) +/* 010ED8 700102D8 2401FFFF */ li $at, -1 +/* 010EDC 700102DC 00084402 */ srl $t0, $t0, 0x10 +/* 010EE0 700102E0 01014026 */ xor $t0, $t0, $at +/* 010EE4 700102E4 3108003F */ andi $t0, $t0, 0x3f +/* 010EE8 700102E8 010C4024 */ and $t0, $t0, $t4 +/* 010EEC 700102EC 01284825 */ or $t1, $t1, $t0 +.L700102F0: +/* 010EF0 700102F0 AF490128 */ sw $t1, 0x128($k0) +/* 010EF4 700102F4 40087000 */ mfc0 $t0, $14 +/* 010EF8 700102F8 AF48011C */ sw $t0, 0x11c($k0) +/* 010EFC 700102FC 8F480018 */ lw $t0, 0x18($k0) +/* 010F00 70010300 11000014 */ beqz $t0, .L70010354 +/* 010F04 70010304 00000000 */ nop +/* 010F08 70010308 4448F800 */ cfc1 $t0, $31 +/* 010F0C 7001030C 00000000 */ nop +/* 010F10 70010310 AF48012C */ sw $t0, 0x12c($k0) +/* 010F14 70010314 F7400130 */ sdc1 $f0, 0x130($k0) +/* 010F18 70010318 F7420138 */ sdc1 $f2, 0x138($k0) +/* 010F1C 7001031C F7440140 */ sdc1 $f4, 0x140($k0) +/* 010F20 70010320 F7460148 */ sdc1 $f6, 0x148($k0) +/* 010F24 70010324 F7480150 */ sdc1 $f8, 0x150($k0) +/* 010F28 70010328 F74A0158 */ sdc1 $f10, 0x158($k0) +/* 010F2C 7001032C F74C0160 */ sdc1 $f12, 0x160($k0) +/* 010F30 70010330 F74E0168 */ sdc1 $f14, 0x168($k0) +/* 010F34 70010334 F7500170 */ sdc1 $f16, 0x170($k0) +/* 010F38 70010338 F7520178 */ sdc1 $f18, 0x178($k0) +/* 010F3C 7001033C F7540180 */ sdc1 $f20, 0x180($k0) +/* 010F40 70010340 F7560188 */ sdc1 $f22, 0x188($k0) +/* 010F44 70010344 F7580190 */ sdc1 $f24, 0x190($k0) +/* 010F48 70010348 F75A0198 */ sdc1 $f26, 0x198($k0) +/* 010F4C 7001034C F75C01A0 */ sdc1 $f28, 0x1a0($k0) +/* 010F50 70010350 F75E01A8 */ sdc1 $f30, 0x1a8($k0) +.L70010354: +/* 010F54 70010354 40086800 */ mfc0 $t0, $13 +/* 010F58 70010358 AF480120 */ sw $t0, 0x120($k0) +/* 010F5C 7001035C 24090002 */ li $t1, 2 +/* 010F60 70010360 A7490010 */ sh $t1, 0x10($k0) +/* 010F64 70010364 3109007C */ andi $t1, $t0, 0x7c +/* 010F68 70010368 240A0024 */ li $t2, 36 +/* 010F6C 7001036C 112A00B0 */ beq $t1, $t2, .L70010630 +/* 010F70 70010370 00000000 */ nop +/* 010F74 70010374 240A002C */ li $t2, 44 +/* 010F78 70010378 112A00FF */ beq $t1, $t2, .L70010778 +/* 010F7C 7001037C 00000000 */ nop +/* 010F80 70010380 240A0000 */ li $t2, 0 +/* 010F84 70010384 152A00C3 */ bne $t1, $t2, .L70010694 +/* 010F88 70010388 00000000 */ nop +/* 010F8C 7001038C 03688024 */ and $s0, $k1, $t0 +glabel next_interrupt +.L70010390: +/* 010F90 70010390 3209FF00 */ andi $t1, $s0, 0xff00 +/* 010F94 70010394 00095302 */ srl $t2, $t1, 0xc +/* 010F98 70010398 15400003 */ bnez $t2, .L700103A8 +/* 010F9C 7001039C 00000000 */ nop +/* 010FA0 700103A0 00095202 */ srl $t2, $t1, 8 +/* 010FA4 700103A4 214A0010 */ addi $t2, $t2, 0x10 +.L700103A8: +/* 010FA8 700103A8 3C018003 */ lui $at, %hi(__osIntOffTable) +/* 010FAC 700103AC 002A0821 */ addu $at, $at, $t2 +/* 010FB0 700103B0 902A9440 */ lbu $t2, %lo(__osIntOffTable)($at) +/* 010FB4 700103B4 3C018003 */ lui $at, %hi(__osIntTable) +/* 010FB8 700103B8 002A0821 */ addu $at, $at, $t2 +/* 010FBC 700103BC 8C2A9460 */ lw $t2, %lo(__osIntTable)($at) +/* 010FC0 700103C0 01400008 */ jr $t2 +/* 010FC4 700103C4 00000000 */ nop +glabel IP6_Hdlr +/* 010FC8 700103C8 2401DFFF */ li $at, -8193 +/* 010FCC 700103CC 1000FFF0 */ b .L70010390 +/* 010FD0 700103D0 02018024 */ and $s0, $s0, $at +glabel IP7_Hdlr +/* 010FD4 700103D4 2401BFFF */ li $at, -16385 +/* 010FD8 700103D8 1000FFED */ b .L70010390 +/* 010FDC 700103DC 02018024 */ and $s0, $s0, $at +glabel counter +/* 010FE0 700103E0 40095800 */ mfc0 $t1, $11 +/* 010FE4 700103E4 40895800 */ mtc0 $t1, $11 +/* 010FE8 700103E8 0C0041B1 */ jal send_mesg +/* 010FEC 700103EC 24040018 */ li $a0, 24 +/* 010FF0 700103F0 3C01FFFF */ lui $at, (0xFFFF7FFF >> 16) # lui $at, 0xffff +/* 010FF4 700103F4 34217FFF */ ori $at, (0xFFFF7FFF & 0xFFFF) # ori $at, $at, 0x7fff +/* 010FF8 700103F8 1000FFE5 */ b .L70010390 +/* 010FFC 700103FC 02018024 */ and $s0, $s0, $at +glabel cart +/* 011000 70010400 2401F7FF */ li $at, -2049 +/* 011004 70010404 02018024 */ and $s0, $s0, $at +/* 011008 70010408 240A0004 */ li $t2, 4 +/* 01100C 7001040C 3C018002 */ lui $at, %hi(__osHwIntTable) +/* 011010 70010410 002A0821 */ addu $at, $at, $t2 +/* 011014 70010414 8C2A7700 */ lw $t2, %lo(__osHwIntTable)($at) +/* 011018 70010418 3C1D8006 */ lui $sp, %hi(leoDiskStack) # $sp, 0x8006 +/* 01101C 7001041C 27BD7DF0 */ addiu $sp, %lo(leoDiskStack) # addiu $sp, $sp, 0x7df0 +/* 011020 70010420 24040010 */ li $a0, 16 +/* 011024 70010424 11400007 */ beqz $t2, .L70010444 +/* 011028 70010428 27BD0FF0 */ addiu $sp, $sp, 0xff0 +/* 01102C 7001042C 0140F809 */ jalr $t2 +/* 011030 70010430 00000000 */ nop +/* 011034 70010434 10400003 */ beqz $v0, .L70010444 +/* 011038 70010438 00000000 */ nop +/* 01103C 7001043C 10000082 */ b .L70010648 +/* 011040 70010440 00000000 */ nop +.L70010444: +/* 011044 70010444 0C0041B1 */ jal send_mesg +/* 011048 70010448 00000000 */ nop +/* 01104C 7001044C 1000FFD0 */ b .L70010390 +/* 011050 70010450 00000000 */ nop +glabel rcp +/* 011054 70010454 3C088002 */ lui $t0, %hi(__OSGlobalIntMask) +/* 011058 70010458 2508698C */ addiu $t0, %lo(__OSGlobalIntMask) # addiu $t0, $t0, 0x698c +/* 01105C 7001045C 8D080000 */ lw $t0, ($t0) +/* 011060 70010460 3C11A430 */ lui $s1, %hi(0xA4300008) # $s1, 0xa430 +/* 011064 70010464 8E310008 */ lw $s1, %lo(0xA4300008)($s1) +/* 011068 70010468 00084402 */ srl $t0, $t0, 0x10 +/* 01106C 7001046C 02288824 */ and $s1, $s1, $t0 +/* 011070 70010470 32290001 */ andi $t1, $s1, 1 +/* 011074 70010474 11200013 */ beqz $t1, .L700104C4 +/* 011078 70010478 00000000 */ nop +/* 01107C 7001047C 3C0CA404 */ lui $t4, %hi(0xA4040010) # $t4, 0xa404 +/* 011080 70010480 8D8C0010 */ lw $t4, %lo(0xA4040010)($t4) +/* 011084 70010484 24090008 */ li $t1, 8 +/* 011088 70010488 3C01A404 */ lui $at, %hi(0xA4040010) # $at, 0xa404 +/* 01108C 7001048C 318C0300 */ andi $t4, $t4, 0x300 +/* 011090 70010490 3231003E */ andi $s1, $s1, 0x3e +/* 011094 70010494 11800007 */ beqz $t4, .L700104B4 +/* 011098 70010498 AC290010 */ sw $t1, %lo(0xA4040010)($at) +/* 01109C 7001049C 0C0041B1 */ jal send_mesg +/* 0110A0 700104A0 24040020 */ li $a0, 32 +/* 0110A4 700104A4 12200038 */ beqz $s1, .L70010588 +/* 0110A8 700104A8 00000000 */ nop +/* 0110AC 700104AC 10000005 */ b .L700104C4 +/* 0110B0 700104B0 00000000 */ nop +.L700104B4: +/* 0110B4 700104B4 0C0041B1 */ jal send_mesg +/* 0110B8 700104B8 24040058 */ li $a0, 88 +/* 0110BC 700104BC 12200032 */ beqz $s1, .L70010588 +/* 0110C0 700104C0 00000000 */ nop +.L700104C4: +/* 0110C4 700104C4 32290008 */ andi $t1, $s1, 8 +/* 0110C8 700104C8 11200007 */ beqz $t1, .L700104E8 +/* 0110CC 700104CC 3C01A440 */ lui $at, %hi(0xA4400010) # $at, 0xa440 +/* 0110D0 700104D0 32310037 */ andi $s1, $s1, 0x37 +/* 0110D4 700104D4 AC200010 */ sw $zero, %lo(0xA4400010)($at) +/* 0110D8 700104D8 0C0041B1 */ jal send_mesg +/* 0110DC 700104DC 24040038 */ li $a0, 56 +/* 0110E0 700104E0 12200029 */ beqz $s1, .L70010588 +/* 0110E4 700104E4 00000000 */ nop +.L700104E8: +/* 0110E8 700104E8 32290004 */ andi $t1, $s1, 4 +/* 0110EC 700104EC 11200009 */ beqz $t1, .L70010514 +/* 0110F0 700104F0 00000000 */ nop +/* 0110F4 700104F4 24090001 */ li $t1, 1 +/* 0110F8 700104F8 3C01A450 */ lui $at, %hi(0xA450000C) # $at, 0xa450 +/* 0110FC 700104FC 3231003B */ andi $s1, $s1, 0x3b +/* 011100 70010500 AC29000C */ sw $t1, %lo(0xA450000C)($at) +/* 011104 70010504 0C0041B1 */ jal send_mesg +/* 011108 70010508 24040030 */ li $a0, 48 +/* 01110C 7001050C 1220001E */ beqz $s1, .L70010588 +/* 011110 70010510 00000000 */ nop +.L70010514: +/* 011114 70010514 32290002 */ andi $t1, $s1, 2 +/* 011118 70010518 11200007 */ beqz $t1, .L70010538 +/* 01111C 7001051C 3C01A480 */ lui $at, %hi(0xA4800018) # $at, 0xa480 +/* 011120 70010520 3231003D */ andi $s1, $s1, 0x3d +/* 011124 70010524 AC200018 */ sw $zero, %lo(0xA4800018)($at) +/* 011128 70010528 0C0041B1 */ jal send_mesg +/* 01112C 7001052C 24040028 */ li $a0, 40 +/* 011130 70010530 12200015 */ beqz $s1, .L70010588 +/* 011134 70010534 00000000 */ nop +.L70010538: +/* 011138 70010538 32290010 */ andi $t1, $s1, 0x10 +/* 01113C 7001053C 11200009 */ beqz $t1, .L70010564 +/* 011140 70010540 00000000 */ nop +/* 011144 70010544 24090002 */ li $t1, 2 +/* 011148 70010548 3C01A460 */ lui $at, %hi(0xA4600010) # $at, 0xa460 +/* 01114C 7001054C 3231002F */ andi $s1, $s1, 0x2f +/* 011150 70010550 AC290010 */ sw $t1, %lo(0xA4600010)($at) +/* 011154 70010554 0C0041B1 */ jal send_mesg +/* 011158 70010558 24040040 */ li $a0, 64 +/* 01115C 7001055C 1220000A */ beqz $s1, .L70010588 +/* 011160 70010560 00000000 */ nop +.L70010564: +/* 011164 70010564 32290020 */ andi $t1, $s1, 0x20 +/* 011168 70010568 11200007 */ beqz $t1, .L70010588 +/* 01116C 7001056C 00000000 */ nop +/* 011170 70010570 24090800 */ li $t1, 2048 +/* 011174 70010574 3C01A430 */ lui $at, 0xa430 +/* 011178 70010578 3231001F */ andi $s1, $s1, 0x1f +/* 01117C 7001057C AC290000 */ sw $t1, ($at) +/* 011180 70010580 0C0041B1 */ jal send_mesg +/* 011184 70010584 24040048 */ li $a0, 72 +.L70010588: +/* 011188 70010588 2401FBFF */ li $at, -1025 +/* 01118C 7001058C 1000FF80 */ b .L70010390 +/* 011190 70010590 02018024 */ and $s0, $s0, $at +glabel prenmi +/* 011194 70010594 8F5B0118 */ lw $k1, 0x118($k0) +/* 011198 70010598 2401EFFF */ li $at, -4097 +/* 01119C 7001059C 3C098002 */ lui $t1, %hi(__osShutdown) +/* 0111A0 700105A0 0361D824 */ and $k1, $k1, $at +/* 0111A4 700105A4 AF5B0118 */ sw $k1, 0x118($k0) +/* 0111A8 700105A8 25296988 */ addiu $t1, %lo(__osShutdown) # addiu $t1, $t1, 0x6988 +/* 0111AC 700105AC 8D2A0000 */ lw $t2, ($t1) +/* 0111B0 700105B0 11400003 */ beqz $t2, .L700105C0 +/* 0111B4 700105B4 2401EFFF */ li $at, -4097 +/* 0111B8 700105B8 10000023 */ b .L70010648 +/* 0111BC 700105BC 02018024 */ and $s0, $s0, $at +.L700105C0: +/* 0111C0 700105C0 240A0001 */ li $t2, 1 +/* 0111C4 700105C4 AD2A0000 */ sw $t2, ($t1) +/* 0111C8 700105C8 0C0041B1 */ jal send_mesg +/* 0111CC 700105CC 24040070 */ li $a0, 112 +/* 0111D0 700105D0 3C0A8002 */ lui $t2, %hi(__osRunQueue) +/* 0111D4 700105D4 8D4A7728 */ lw $t2, %lo(__osRunQueue)($t2) +/* 0111D8 700105D8 2401EFFF */ li $at, -4097 +/* 0111DC 700105DC 02018024 */ and $s0, $s0, $at +/* 0111E0 700105E0 8D5B0118 */ lw $k1, 0x118($t2) +/* 0111E4 700105E4 0361D824 */ and $k1, $k1, $at +/* 0111E8 700105E8 10000017 */ b .L70010648 +/* 0111EC 700105EC AD5B0118 */ sw $k1, 0x118($t2) +glabel sw2 +/* 0111F0 700105F0 2401FDFF */ li $at, -513 +/* 0111F4 700105F4 01014024 */ and $t0, $t0, $at +/* 0111F8 700105F8 40886800 */ mtc0 $t0, $13 +/* 0111FC 700105FC 0C0041B1 */ jal send_mesg +/* 011200 70010600 24040008 */ li $a0, 8 +/* 011204 70010604 2401FDFF */ li $at, -513 +/* 011208 70010608 1000FF61 */ b .L70010390 +/* 01120C 7001060C 02018024 */ and $s0, $s0, $at +glabel swl +/* 011210 70010610 2401FEFF */ li $at, -257 +/* 011214 70010614 01014024 */ and $t0, $t0, $at +/* 011218 70010618 40886800 */ mtc0 $t0, $13 +/* 01121C 7001061C 0C0041B1 */ jal send_mesg +/* 011220 70010620 24040000 */ li $a0, 0 +/* 011224 70010624 2401FEFF */ li $at, -257 +/* 011228 70010628 1000FF59 */ b .L70010390 +/* 01122C 7001062C 02018024 */ and $s0, $s0, $at +.L70010630: +/* 011230 70010630 24090001 */ li $t1, 1 +/* 011234 70010634 A7490012 */ sh $t1, 0x12($k0) +/* 011238 70010638 0C0041B1 */ jal send_mesg +/* 01123C 7001063C 24040050 */ li $a0, 80 +/* 011240 70010640 10000001 */ b .L70010648 +/* 011244 70010644 00000000 */ nop +glabel redispatch +.L70010648: +/* 011248 70010648 3C0A8002 */ lui $t2, %hi(__osRunQueue) +/* 01124C 7001064C 8D4A7728 */ lw $t2, %lo(__osRunQueue)($t2) +/* 011250 70010650 8F490004 */ lw $t1, 4($k0) +/* 011254 70010654 8D4B0004 */ lw $t3, 4($t2) +/* 011258 70010658 012B082A */ slt $at, $t1, $t3 +/* 01125C 7001065C 10200007 */ beqz $at, .L7001067C +/* 011260 70010660 00000000 */ nop +/* 011264 70010664 3C048002 */ lui $a0, %hi(__osRunQueue) +/* 011268 70010668 03402825 */ move $a1, $k0 +/* 01126C 7001066C 0C00422B */ jal __osEnqueueThread +/* 011270 70010670 24847728 */ addiu $a0, %lo(__osRunQueue) # addiu $a0, $a0, 0x7728 +/* 011274 70010674 08004241 */ j __osDispatchThread +/* 011278 70010678 00000000 */ nop + +glabel enqueueRunning +.L7001067C: +/* 01127C 7001067C 3C098002 */ lui $t1, %hi(__osRunQueue) +/* 011280 70010680 25297728 */ addiu $t1, %lo(__osRunQueue) # addiu $t1, $t1, 0x7728 +/* 011284 70010684 8D2A0000 */ lw $t2, ($t1) +/* 011288 70010688 AF4A0000 */ sw $t2, ($k0) +/* 01128C 7001068C 08004241 */ j __osDispatchThread +/* 011290 70010690 AD3A0000 */ sw $k0, ($t1) + +.L70010694: +/* 011294 70010694 3C018002 */ lui $at, %hi(__osFaultedThread) +/* 011298 70010698 AC3A7734 */ sw $k0, %lo(__osFaultedThread)($at) +/* 01129C 7001069C 24090001 */ li $t1, 1 +/* 0112A0 700106A0 A7490010 */ sh $t1, 0x10($k0) +/* 0112A4 700106A4 24090002 */ li $t1, 2 +/* 0112A8 700106A8 A7490012 */ sh $t1, 0x12($k0) +/* 0112AC 700106AC 400A4000 */ mfc0 $t2, $8 +/* 0112B0 700106B0 AF4A0124 */ sw $t2, 0x124($k0) +/* 0112B4 700106B4 0C0041B1 */ jal send_mesg +/* 0112B8 700106B8 24040060 */ li $a0, 96 +/* 0112BC 700106BC 08004241 */ j __osDispatchThread +/* 0112C0 700106C0 00000000 */ nop + +glabel send_mesg +/* 0112C4 700106C4 3C0A8006 */ lui $t2, %hi(__osEventStateTab) +/* 0112C8 700106C8 254A6A10 */ addiu $t2, %lo(__osEventStateTab) # addiu $t2, $t2, 0x6a10 +/* 0112CC 700106CC 01445021 */ addu $t2, $t2, $a0 +/* 0112D0 700106D0 8D490000 */ lw $t1, ($t2) +/* 0112D4 700106D4 03E09025 */ move $s2, $ra +/* 0112D8 700106D8 11200025 */ beqz $t1, .L70010770 +/* 0112DC 700106DC 00000000 */ nop +/* 0112E0 700106E0 8D2B0008 */ lw $t3, 8($t1) +/* 0112E4 700106E4 8D2C0010 */ lw $t4, 0x10($t1) +/* 0112E8 700106E8 016C082A */ slt $at, $t3, $t4 +/* 0112EC 700106EC 10200020 */ beqz $at, .L70010770 +/* 0112F0 700106F0 00000000 */ nop +/* 0112F4 700106F4 8D2D000C */ lw $t5, 0xc($t1) +/* 0112F8 700106F8 01AB6821 */ addu $t5, $t5, $t3 +/* 0112FC 700106FC 01AC001A */ div $zero, $t5, $t4 +/* 011300 70010700 15800002 */ bnez $t4, .L7001070C +/* 011304 70010704 00000000 */ nop +/* 011308 70010708 0007000D */ break 7 +.L7001070C: +/* 01130C 7001070C 2401FFFF */ li $at, -1 +/* 011310 70010710 15810004 */ bne $t4, $at, .L70010724 +/* 011314 70010714 3C018000 */ lui $at, 0x8000 +/* 011318 70010718 15A10002 */ bne $t5, $at, .L70010724 +/* 01131C 7001071C 00000000 */ nop +/* 011320 70010720 0006000D */ break 6 +.L70010724: +/* 011324 70010724 8D2C0014 */ lw $t4, 0x14($t1) +/* 011328 70010728 00006810 */ mfhi $t5 +/* 01132C 7001072C 000D6880 */ sll $t5, $t5, 2 +/* 011330 70010730 018D6021 */ addu $t4, $t4, $t5 +/* 011334 70010734 8D4D0004 */ lw $t5, 4($t2) +/* 011338 70010738 256A0001 */ addiu $t2, $t3, 1 +/* 01133C 7001073C AD8D0000 */ sw $t5, ($t4) +/* 011340 70010740 AD2A0008 */ sw $t2, 8($t1) +/* 011344 70010744 8D2A0000 */ lw $t2, ($t1) +/* 011348 70010748 8D4B0000 */ lw $t3, ($t2) +/* 01134C 7001074C 11600008 */ beqz $t3, .L70010770 +/* 011350 70010750 00000000 */ nop +/* 011354 70010754 0C00423D */ jal __osPopThread +/* 011358 70010758 01202025 */ move $a0, $t1 +/* 01135C 7001075C 00405025 */ move $t2, $v0 +/* 011360 70010760 3C048002 */ lui $a0, %hi(__osRunQueue) +/* 011364 70010764 01402825 */ move $a1, $t2 +/* 011368 70010768 0C00422B */ jal __osEnqueueThread +/* 01136C 7001076C 24847728 */ addiu $a0, %lo(__osRunQueue) # addiu $a0, $a0, 0x7728 +.L70010770: +/* 011370 70010770 02400008 */ jr $s2 +/* 011374 70010774 00000000 */ nop +glabel handle_CpU +.L70010778: +/* 011378 70010778 3C013000 */ lui $at, 0x3000 +/* 01137C 7001077C 01014824 */ and $t1, $t0, $at +/* 011380 70010780 00094F02 */ srl $t1, $t1, 0x1c +/* 011384 70010784 240A0001 */ li $t2, 1 +/* 011388 70010788 152AFFC2 */ bne $t1, $t2, .L70010694 +/* 01138C 7001078C 00000000 */ nop +/* 011390 70010790 8F5B0118 */ lw $k1, 0x118($k0) +/* 011394 70010794 3C012000 */ lui $at, 0x2000 +/* 011398 70010798 24090001 */ li $t1, 1 +/* 01139C 7001079C 0361D825 */ or $k1, $k1, $at +/* 0113A0 700107A0 AF490018 */ sw $t1, 0x18($k0) +/* 0113A4 700107A4 1000FFB5 */ b .L7001067C +/* 0113A8 700107A8 AF5B0118 */ sw $k1, 0x118($k0) +glabel __osEnqueueAndYield +/* 0113AC 700107AC 3C058002 */ lui $a1, %hi(__osRunningThread) +/* 0113B0 700107B0 8CA57730 */ lw $a1, %lo(__osRunningThread)($a1) +/* 0113B4 700107B4 40086000 */ mfc0 $t0, $12 +/* 0113B8 700107B8 8CBB0018 */ lw $k1, 0x18($a1) +/* 0113BC 700107BC 35080002 */ ori $t0, $t0, 2 +/* 0113C0 700107C0 ACA80118 */ sw $t0, 0x118($a1) +/* 0113C4 700107C4 FCB00098 */ sd $s0, 0x98($a1) +/* 0113C8 700107C8 FCB100A0 */ sd $s1, 0xa0($a1) +/* 0113CC 700107CC FCB200A8 */ sd $s2, 0xa8($a1) +/* 0113D0 700107D0 FCB300B0 */ sd $s3, 0xb0($a1) +/* 0113D4 700107D4 FCB400B8 */ sd $s4, 0xb8($a1) +/* 0113D8 700107D8 FCB500C0 */ sd $s5, 0xc0($a1) +/* 0113DC 700107DC FCB600C8 */ sd $s6, 0xc8($a1) +/* 0113E0 700107E0 FCB700D0 */ sd $s7, 0xd0($a1) +/* 0113E4 700107E4 FCBC00E8 */ sd $gp, 0xe8($a1) +/* 0113E8 700107E8 FCBD00F0 */ sd $sp, 0xf0($a1) +/* 0113EC 700107EC FCBE00F8 */ sd $fp, 0xf8($a1) +/* 0113F0 700107F0 FCBF0100 */ sd $ra, 0x100($a1) +/* 0113F4 700107F4 13600009 */ beqz $k1, .L7001081C +/* 0113F8 700107F8 ACBF011C */ sw $ra, 0x11c($a1) +/* 0113FC 700107FC 445BF800 */ cfc1 $k1, $31 +/* 011400 70010800 F4B40180 */ sdc1 $f20, 0x180($a1) +/* 011404 70010804 F4B60188 */ sdc1 $f22, 0x188($a1) +/* 011408 70010808 F4B80190 */ sdc1 $f24, 0x190($a1) +/* 01140C 7001080C F4BA0198 */ sdc1 $f26, 0x198($a1) +/* 011410 70010810 F4BC01A0 */ sdc1 $f28, 0x1a0($a1) +/* 011414 70010814 F4BE01A8 */ sdc1 $f30, 0x1a8($a1) +/* 011418 70010818 ACBB012C */ sw $k1, 0x12c($a1) +.L7001081C: +/* 01141C 7001081C 8CBB0118 */ lw $k1, 0x118($a1) +/* 011420 70010820 3369FF00 */ andi $t1, $k1, 0xff00 +/* 011424 70010824 1120000D */ beqz $t1, .L7001085C +/* 011428 70010828 00000000 */ nop +/* 01142C 7001082C 3C088002 */ lui $t0, %hi(__OSGlobalIntMask) +/* 011430 70010830 2508698C */ addiu $t0, %lo(__OSGlobalIntMask) # addiu $t0, $t0, 0x698c +/* 011434 70010834 8D080000 */ lw $t0, ($t0) +/* 011438 70010838 2401FFFF */ li $at, -1 +/* 01143C 7001083C 01014026 */ xor $t0, $t0, $at +/* 011440 70010840 3C01FFFF */ lui $at, (0xFFFF00FF >> 16) # lui $at, 0xffff +/* 011444 70010844 3108FF00 */ andi $t0, $t0, 0xff00 +/* 011448 70010848 342100FF */ ori $at, (0xFFFF00FF & 0xFFFF) # ori $at, $at, 0xff +/* 01144C 7001084C 01284825 */ or $t1, $t1, $t0 +/* 011450 70010850 0361D824 */ and $k1, $k1, $at +/* 011454 70010854 0369D825 */ or $k1, $k1, $t1 +/* 011458 70010858 ACBB0118 */ sw $k1, 0x118($a1) +.L7001085C: +/* 01145C 7001085C 3C1BA430 */ lui $k1, %hi(0xA430000C) # $k1, 0xa430 +/* 011460 70010860 8F7B000C */ lw $k1, %lo(0xA430000C)($k1) +/* 011464 70010864 1360000B */ beqz $k1, .L70010894 +/* 011468 70010868 00000000 */ nop +/* 01146C 7001086C 3C1A8002 */ lui $k0, %hi(__OSGlobalIntMask) # $k0, 0x8002 +/* 011470 70010870 275A698C */ addiu $k0, %lo(__OSGlobalIntMask) # addiu $k0, $k0, 0x698c +/* 011474 70010874 8F5A0000 */ lw $k0, ($k0) +/* 011478 70010878 8CA80128 */ lw $t0, 0x128($a1) +/* 01147C 7001087C 2401FFFF */ li $at, -1 +/* 011480 70010880 001AD402 */ srl $k0, $k0, 0x10 +/* 011484 70010884 0341D026 */ xor $k0, $k0, $at +/* 011488 70010888 335A003F */ andi $k0, $k0, 0x3f +/* 01148C 7001088C 0348D024 */ and $k0, $k0, $t0 +/* 011490 70010890 037AD825 */ or $k1, $k1, $k0 +.L70010894: +/* 011494 70010894 10800003 */ beqz $a0, .L700108A4 +/* 011498 70010898 ACBB0128 */ sw $k1, 0x128($a1) +/* 01149C 7001089C 0C00422B */ jal __osEnqueueThread +/* 0114A0 700108A0 00000000 */ nop +.L700108A4: +/* 0114A4 700108A4 08004241 */ j __osDispatchThread +/* 0114A8 700108A8 00000000 */ nop + +glabel __osEnqueueThread +/* 0114AC 700108AC 8C980000 */ lw $t8, ($a0) +/* 0114B0 700108B0 8CAF0004 */ lw $t7, 4($a1) +/* 0114B4 700108B4 0080C825 */ move $t9, $a0 +/* 0114B8 700108B8 8F0E0004 */ lw $t6, 4($t8) +/* 0114BC 700108BC 01CF082A */ slt $at, $t6, $t7 +/* 0114C0 700108C0 14200007 */ bnez $at, .L700108E0 +/* 0114C4 700108C4 00000000 */ nop +.L700108C8: +/* 0114C8 700108C8 0300C825 */ move $t9, $t8 +/* 0114CC 700108CC 8F180000 */ lw $t8, ($t8) +/* 0114D0 700108D0 8F0E0004 */ lw $t6, 4($t8) +/* 0114D4 700108D4 01CF082A */ slt $at, $t6, $t7 +/* 0114D8 700108D8 1020FFFB */ beqz $at, .L700108C8 +/* 0114DC 700108DC 00000000 */ nop +.L700108E0: +/* 0114E0 700108E0 8F380000 */ lw $t8, ($t9) +/* 0114E4 700108E4 ACB80000 */ sw $t8, ($a1) +/* 0114E8 700108E8 AF250000 */ sw $a1, ($t9) +/* 0114EC 700108EC 03E00008 */ jr $ra +/* 0114F0 700108F0 ACA40008 */ sw $a0, 8($a1) + +glabel __osPopThread +/* 0114F4 700108F4 8C820000 */ lw $v0, ($a0) +/* 0114F8 700108F8 8C590000 */ lw $t9, ($v0) +/* 0114FC 700108FC 03E00008 */ jr $ra +/* 011500 70010900 AC990000 */ sw $t9, ($a0) + +glabel __osDispatchThread +/* 011504 70010904 3C048002 */ lui $a0, %hi(__osRunQueue) +/* 011508 70010908 0C00423D */ jal __osPopThread +/* 01150C 7001090C 24847728 */ addiu $a0, %lo(__osRunQueue) # addiu $a0, $a0, 0x7728 +/* 011510 70010910 3C018002 */ lui $at, %hi(__osRunningThread) +/* 011514 70010914 AC227730 */ sw $v0, %lo(__osRunningThread)($at) +/* 011518 70010918 24080004 */ li $t0, 4 +/* 01151C 7001091C A4480010 */ sh $t0, 0x10($v0) +/* 011520 70010920 0040D025 */ move $k0, $v0 +glabel __osDispatchThreadSave +/* 011524 70010924 3C088002 */ lui $t0, %hi(__OSGlobalIntMask) +/* 011528 70010928 8F5B0118 */ lw $k1, 0x118($k0) +/* 01152C 7001092C 2508698C */ addiu $t0, %lo(__OSGlobalIntMask) # addiu $t0, $t0, 0x698c +/* 011530 70010930 8D080000 */ lw $t0, ($t0) +/* 011534 70010934 3C01FFFF */ lui $at, (0xFFFF00FF >> 16) # lui $at, 0xffff +/* 011538 70010938 3369FF00 */ andi $t1, $k1, 0xff00 +/* 01153C 7001093C 342100FF */ ori $at, (0xFFFF00FF & 0xFFFF) # ori $at, $at, 0xff +/* 011540 70010940 3108FF00 */ andi $t0, $t0, 0xff00 +/* 011544 70010944 01284824 */ and $t1, $t1, $t0 +/* 011548 70010948 0361D824 */ and $k1, $k1, $at +/* 01154C 7001094C 0369D825 */ or $k1, $k1, $t1 +/* 011550 70010950 409B6000 */ mtc0 $k1, $12 +/* 011554 70010954 DF5B0108 */ ld $k1, 0x108($k0) +/* 011558 70010958 DF410020 */ ld $at, 0x20($k0) +/* 01155C 7001095C DF420028 */ ld $v0, 0x28($k0) +/* 011560 70010960 03600013 */ mtlo $k1 +/* 011564 70010964 DF5B0110 */ ld $k1, 0x110($k0) +/* 011568 70010968 DF430030 */ ld $v1, 0x30($k0) +/* 01156C 7001096C DF440038 */ ld $a0, 0x38($k0) +/* 011570 70010970 DF450040 */ ld $a1, 0x40($k0) +/* 011574 70010974 DF460048 */ ld $a2, 0x48($k0) +/* 011578 70010978 DF470050 */ ld $a3, 0x50($k0) +/* 01157C 7001097C DF480058 */ ld $t0, 0x58($k0) +/* 011580 70010980 DF490060 */ ld $t1, 0x60($k0) +/* 011584 70010984 DF4A0068 */ ld $t2, 0x68($k0) +/* 011588 70010988 DF4B0070 */ ld $t3, 0x70($k0) +/* 01158C 7001098C DF4C0078 */ ld $t4, 0x78($k0) +/* 011590 70010990 DF4D0080 */ ld $t5, 0x80($k0) +/* 011594 70010994 DF4E0088 */ ld $t6, 0x88($k0) +/* 011598 70010998 DF4F0090 */ ld $t7, 0x90($k0) +/* 01159C 7001099C DF500098 */ ld $s0, 0x98($k0) +/* 0115A0 700109A0 DF5100A0 */ ld $s1, 0xa0($k0) +/* 0115A4 700109A4 DF5200A8 */ ld $s2, 0xa8($k0) +/* 0115A8 700109A8 DF5300B0 */ ld $s3, 0xb0($k0) +/* 0115AC 700109AC DF5400B8 */ ld $s4, 0xb8($k0) +/* 0115B0 700109B0 DF5500C0 */ ld $s5, 0xc0($k0) +/* 0115B4 700109B4 DF5600C8 */ ld $s6, 0xc8($k0) +/* 0115B8 700109B8 DF5700D0 */ ld $s7, 0xd0($k0) +/* 0115BC 700109BC DF5800D8 */ ld $t8, 0xd8($k0) +/* 0115C0 700109C0 DF5900E0 */ ld $t9, 0xe0($k0) +/* 0115C4 700109C4 DF5C00E8 */ ld $gp, 0xe8($k0) +/* 0115C8 700109C8 03600011 */ mthi $k1 +/* 0115CC 700109CC DF5D00F0 */ ld $sp, 0xf0($k0) +/* 0115D0 700109D0 DF5E00F8 */ ld $fp, 0xf8($k0) +/* 0115D4 700109D4 DF5F0100 */ ld $ra, 0x100($k0) +/* 0115D8 700109D8 8F5B011C */ lw $k1, 0x11c($k0) +/* 0115DC 700109DC 409B7000 */ mtc0 $k1, $14 +/* 0115E0 700109E0 8F5B0018 */ lw $k1, 0x18($k0) +/* 0115E4 700109E4 13600013 */ beqz $k1, .L70010A34 +/* 0115E8 700109E8 00000000 */ nop +/* 0115EC 700109EC 8F5B012C */ lw $k1, 0x12c($k0) +/* 0115F0 700109F0 44DBF800 */ ctc1 $k1, $31 +/* 0115F4 700109F4 D7400130 */ ldc1 $f0, 0x130($k0) +/* 0115F8 700109F8 D7420138 */ ldc1 $f2, 0x138($k0) +/* 0115FC 700109FC D7440140 */ ldc1 $f4, 0x140($k0) +/* 011600 70010A00 D7460148 */ ldc1 $f6, 0x148($k0) +/* 011604 70010A04 D7480150 */ ldc1 $f8, 0x150($k0) +/* 011608 70010A08 D74A0158 */ ldc1 $f10, 0x158($k0) +/* 01160C 70010A0C D74C0160 */ ldc1 $f12, 0x160($k0) +/* 011610 70010A10 D74E0168 */ ldc1 $f14, 0x168($k0) +/* 011614 70010A14 D7500170 */ ldc1 $f16, 0x170($k0) +/* 011618 70010A18 D7520178 */ ldc1 $f18, 0x178($k0) +/* 01161C 70010A1C D7540180 */ ldc1 $f20, 0x180($k0) +/* 011620 70010A20 D7560188 */ ldc1 $f22, 0x188($k0) +/* 011624 70010A24 D7580190 */ ldc1 $f24, 0x190($k0) +/* 011628 70010A28 D75A0198 */ ldc1 $f26, 0x198($k0) +/* 01162C 70010A2C D75C01A0 */ ldc1 $f28, 0x1a0($k0) +/* 011630 70010A30 D75E01A8 */ ldc1 $f30, 0x1a8($k0) +.L70010A34: +/* 011634 70010A34 8F5B0128 */ lw $k1, 0x128($k0) +/* 011638 70010A38 3C1A8002 */ lui $k0, %hi(__OSGlobalIntMask) # $k0, 0x8002 +/* 01163C 70010A3C 275A698C */ addiu $k0, %lo(__OSGlobalIntMask) # addiu $k0, $k0, 0x698c +/* 011640 70010A40 8F5A0000 */ lw $k0, ($k0) +/* 011644 70010A44 001AD402 */ srl $k0, $k0, 0x10 +/* 011648 70010A48 037AD824 */ and $k1, $k1, $k0 +/* 01164C 70010A4C 001BD840 */ sll $k1, $k1, 1 +/* 011650 70010A50 3C1A8003 */ lui $k0, %hi(__osRcpImTable) # $k0, 0x8003 +/* 011654 70010A54 275A93A0 */ addiu $k0, %lo(__osRcpImTable) # addiu $k0, $k0, -0x6c60 +/* 011658 70010A58 037AD821 */ addu $k1, $k1, $k0 +/* 01165C 70010A5C 977B0000 */ lhu $k1, ($k1) +/* 011660 70010A60 3C1AA430 */ lui $k0, %hi(0xA430000C) # $k0, 0xa430 +/* 011664 70010A64 275A000C */ addiu $k0, %lo(0xA430000C) # addiu $k0, $k0, 0xc +/* 011668 70010A68 AF5B0000 */ sw $k1, ($k0) +/* 01166C 70010A6C 00000000 */ nop +/* 011670 70010A70 00000000 */ nop +/* 011674 70010A74 00000000 */ nop +/* 011678 70010A78 00000000 */ nop +/* 01167C 70010A7C 42000018 */ eret +glabel __osCleanupThread +/* 011680 70010A80 0C006ED8 */ jal osDestroyThread +/* 011684 70010A84 00002025 */ move $a0, $zero + + +.section .data +/*80027704*/ +glabel __osHwIntTable +.word 0, 0, 0, 0 +.word 0, 0, 0, 0 + +.section .rodata +/*80029440*/ +glabel __osIntOffTable +.byte 0,0x14,0x18,0x18,0x1C,0x1C,0x1C,0x1C +.byte 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20 +.byte 0, 4, 8, 8, 0xC, 0xC, 0xC, 0xC +.byte 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10 + +/*80029460*/ +glabel __osIntTable +.word redispatch +.word swl +.word sw2 +.word rcp +.word cart +.word prenmi +.word IP6_Hdlr +.word IP7_Hdlr +.word counter diff --git a/src/libultra/os/getcount.s b/src/libultra/os/getcount.s index 171395e3..808d9e1a 100644 --- a/src/libultra/os/getcount.s +++ b/src/libultra/os/getcount.s @@ -1,16 +1,16 @@ -# asm file. - -# assembler directives -.set noat # allow manual use of $at -.set noreorder # don't insert nops after branches -.set gp=64 - -.include "macros.inc" - - -.section .text, "ax" -glabel osGetCount -/* 00E4E0 7000D8E0 40024800 */ mfc0 $v0, $9 -/* 00E4E4 7000D8E4 03E00008 */ jr $ra -/* 00E4E8 7000D8E8 00000000 */ nop -.size osGetCount, . - osGetCount +# asm file. + +# assembler directives +.set noat # allow manual use of $at +.set noreorder # don't insert nops after branches +.set gp=64 + +.include "macros.inc" + + +.section .text, "ax" +glabel osGetCount +/* 00E4E0 7000D8E0 40024800 */ mfc0 $v0, $9 +/* 00E4E4 7000D8E4 03E00008 */ jr $ra +/* 00E4E8 7000D8E8 00000000 */ nop +.size osGetCount, . - osGetCount diff --git a/src/libultra/os/getfpccsr.s b/src/libultra/os/getfpccsr.s index 3b5a5f3c..0dc4a82c 100644 --- a/src/libultra/os/getfpccsr.s +++ b/src/libultra/os/getfpccsr.s @@ -1,16 +1,16 @@ -# asm file. - -# assembler directives -.set noat # allow manual use of $at -.set noreorder # don't insert nops after branches -.set gp=64 - -.include "macros.inc" - - -.section .text, "ax" -glabel __osGetFpcCsr -/* 00E010 7000D410 4442F800 */ cfc1 $v0, $31 -/* 00E014 7000D414 03E00008 */ jr $ra -/* 00E018 7000D418 00000000 */ nop +# asm file. + +# assembler directives +.set noat # allow manual use of $at +.set noreorder # don't insert nops after branches +.set gp=64 + +.include "macros.inc" + + +.section .text, "ax" +glabel __osGetFpcCsr +/* 00E010 7000D410 4442F800 */ cfc1 $v0, $31 +/* 00E014 7000D414 03E00008 */ jr $ra +/* 00E018 7000D418 00000000 */ nop \ No newline at end of file diff --git a/src/libultra/os/gettlbhi.s b/src/libultra/os/gettlbhi.s index 79e314a6..26c526fa 100644 --- a/src/libultra/os/gettlbhi.s +++ b/src/libultra/os/gettlbhi.s @@ -1,23 +1,23 @@ -# asm file. - -# assembler directives -.set noat # allow manual use of $at -.set noreorder # don't insert nops after branches -.set gp=64 - -.include "macros.inc" - - -.section .text, "ax" - -glabel __osGetTLBHi -/* 00F480 7000E880 40840000 */ mtc0 $a0, $0 -/* 00F484 7000E884 00000000 */ nop -/* 00F488 7000E888 42000001 */ tlbr -/* 00F48C 7000E88C 00000000 */ nop -/* 00F490 7000E890 00000000 */ nop -/* 00F494 7000E894 00000000 */ nop -/* 00F498 7000E898 40025000 */ mfc0 $v0, $10 -/* 00F49C 7000E89C 03E00008 */ jr $ra -/* 00F4A0 7000E8A0 00000000 */ nop - +# asm file. + +# assembler directives +.set noat # allow manual use of $at +.set noreorder # don't insert nops after branches +.set gp=64 + +.include "macros.inc" + + +.section .text, "ax" + +glabel __osGetTLBHi +/* 00F480 7000E880 40840000 */ mtc0 $a0, $0 +/* 00F484 7000E884 00000000 */ nop +/* 00F488 7000E888 42000001 */ tlbr +/* 00F48C 7000E88C 00000000 */ nop +/* 00F490 7000E890 00000000 */ nop +/* 00F494 7000E894 00000000 */ nop +/* 00F498 7000E898 40025000 */ mfc0 $v0, $10 +/* 00F49C 7000E89C 03E00008 */ jr $ra +/* 00F4A0 7000E8A0 00000000 */ nop + diff --git a/src/libultra/os/invaldcache.s b/src/libultra/os/invaldcache.s index b15286ee..52f2129b 100644 --- a/src/libultra/os/invaldcache.s +++ b/src/libultra/os/invaldcache.s @@ -1,62 +1,62 @@ -# asm file. - -# assembler directives -.set noat # allow manual use of $at -.set noreorder # don't insert nops after branches -.set gp=64 - -.include "macros.inc" - - -.section .text, "ax" -glabel osInvalDCache -/* 011720 70010B20 18A0001F */ blez $a1, .L70010BA0 -/* 011724 70010B24 00000000 */ nop -/* 011728 70010B28 240B2000 */ li $t3, 8192 -/* 01172C 70010B2C 00AB082B */ sltu $at, $a1, $t3 -/* 011730 70010B30 1020001D */ beqz $at, .L70010BA8 -/* 011734 70010B34 00000000 */ nop -/* 011738 70010B38 00804025 */ move $t0, $a0 -/* 01173C 70010B3C 00854821 */ addu $t1, $a0, $a1 -/* 011740 70010B40 0109082B */ sltu $at, $t0, $t1 -/* 011744 70010B44 10200016 */ beqz $at, .L70010BA0 -/* 011748 70010B48 00000000 */ nop -/* 01174C 70010B4C 310A000F */ andi $t2, $t0, 0xf -/* 011750 70010B50 11400007 */ beqz $t2, .L70010B70 -/* 011754 70010B54 2529FFF0 */ addiu $t1, $t1, -0x10 -/* 011758 70010B58 010A4023 */ subu $t0, $t0, $t2 -/* 01175C 70010B5C BD150000 */ cache 0x15, ($t0) -/* 011760 70010B60 0109082B */ sltu $at, $t0, $t1 -/* 011764 70010B64 1020000E */ beqz $at, .L70010BA0 -/* 011768 70010B68 00000000 */ nop -/* 01176C 70010B6C 25080010 */ addiu $t0, $t0, 0x10 -.L70010B70: -/* 011770 70010B70 312A000F */ andi $t2, $t1, 0xf -/* 011774 70010B74 11400006 */ beqz $t2, .L70010B90 -/* 011778 70010B78 00000000 */ nop -/* 01177C 70010B7C 012A4823 */ subu $t1, $t1, $t2 -/* 011780 70010B80 BD350010 */ cache 0x15, 0x10($t1) -/* 011784 70010B84 0128082B */ sltu $at, $t1, $t0 -/* 011788 70010B88 14200005 */ bnez $at, .L70010BA0 -/* 01178C 70010B8C 00000000 */ nop -.L70010B90: -/* 011790 70010B90 BD110000 */ cache 0x11, ($t0) -/* 011794 70010B94 0109082B */ sltu $at, $t0, $t1 -/* 011798 70010B98 1420FFFD */ bnez $at, .L70010B90 -/* 01179C 70010B9C 25080010 */ addiu $t0, $t0, 0x10 -.L70010BA0: -/* 0117A0 70010BA0 03E00008 */ jr $ra -/* 0117A4 70010BA4 00000000 */ nop - -.L70010BA8: -/* 0117A8 70010BA8 3C088000 */ lui $t0, 0x8000 -/* 0117AC 70010BAC 010B4821 */ addu $t1, $t0, $t3 -/* 0117B0 70010BB0 2529FFF0 */ addiu $t1, $t1, -0x10 -.L70010BB4: -/* 0117B4 70010BB4 BD010000 */ cache 1, ($t0) -/* 0117B8 70010BB8 0109082B */ sltu $at, $t0, $t1 -/* 0117BC 70010BBC 1420FFFD */ bnez $at, .L70010BB4 -/* 0117C0 70010BC0 25080010 */ addiu $t0, $t0, 0x10 -/* 0117C4 70010BC4 03E00008 */ jr $ra -/* 0117C8 70010BC8 00000000 */ nop -.size osInvalDCache, . - osInvalDCache +# asm file. + +# assembler directives +.set noat # allow manual use of $at +.set noreorder # don't insert nops after branches +.set gp=64 + +.include "macros.inc" + + +.section .text, "ax" +glabel osInvalDCache +/* 011720 70010B20 18A0001F */ blez $a1, .L70010BA0 +/* 011724 70010B24 00000000 */ nop +/* 011728 70010B28 240B2000 */ li $t3, 8192 +/* 01172C 70010B2C 00AB082B */ sltu $at, $a1, $t3 +/* 011730 70010B30 1020001D */ beqz $at, .L70010BA8 +/* 011734 70010B34 00000000 */ nop +/* 011738 70010B38 00804025 */ move $t0, $a0 +/* 01173C 70010B3C 00854821 */ addu $t1, $a0, $a1 +/* 011740 70010B40 0109082B */ sltu $at, $t0, $t1 +/* 011744 70010B44 10200016 */ beqz $at, .L70010BA0 +/* 011748 70010B48 00000000 */ nop +/* 01174C 70010B4C 310A000F */ andi $t2, $t0, 0xf +/* 011750 70010B50 11400007 */ beqz $t2, .L70010B70 +/* 011754 70010B54 2529FFF0 */ addiu $t1, $t1, -0x10 +/* 011758 70010B58 010A4023 */ subu $t0, $t0, $t2 +/* 01175C 70010B5C BD150000 */ cache 0x15, ($t0) +/* 011760 70010B60 0109082B */ sltu $at, $t0, $t1 +/* 011764 70010B64 1020000E */ beqz $at, .L70010BA0 +/* 011768 70010B68 00000000 */ nop +/* 01176C 70010B6C 25080010 */ addiu $t0, $t0, 0x10 +.L70010B70: +/* 011770 70010B70 312A000F */ andi $t2, $t1, 0xf +/* 011774 70010B74 11400006 */ beqz $t2, .L70010B90 +/* 011778 70010B78 00000000 */ nop +/* 01177C 70010B7C 012A4823 */ subu $t1, $t1, $t2 +/* 011780 70010B80 BD350010 */ cache 0x15, 0x10($t1) +/* 011784 70010B84 0128082B */ sltu $at, $t1, $t0 +/* 011788 70010B88 14200005 */ bnez $at, .L70010BA0 +/* 01178C 70010B8C 00000000 */ nop +.L70010B90: +/* 011790 70010B90 BD110000 */ cache 0x11, ($t0) +/* 011794 70010B94 0109082B */ sltu $at, $t0, $t1 +/* 011798 70010B98 1420FFFD */ bnez $at, .L70010B90 +/* 01179C 70010B9C 25080010 */ addiu $t0, $t0, 0x10 +.L70010BA0: +/* 0117A0 70010BA0 03E00008 */ jr $ra +/* 0117A4 70010BA4 00000000 */ nop + +.L70010BA8: +/* 0117A8 70010BA8 3C088000 */ lui $t0, 0x8000 +/* 0117AC 70010BAC 010B4821 */ addu $t1, $t0, $t3 +/* 0117B0 70010BB0 2529FFF0 */ addiu $t1, $t1, -0x10 +.L70010BB4: +/* 0117B4 70010BB4 BD010000 */ cache 1, ($t0) +/* 0117B8 70010BB8 0109082B */ sltu $at, $t0, $t1 +/* 0117BC 70010BBC 1420FFFD */ bnez $at, .L70010BB4 +/* 0117C0 70010BC0 25080010 */ addiu $t0, $t0, 0x10 +/* 0117C4 70010BC4 03E00008 */ jr $ra +/* 0117C8 70010BC8 00000000 */ nop +.size osInvalDCache, . - osInvalDCache diff --git a/src/libultra/os/invalicache.s b/src/libultra/os/invalicache.s index 8ed6cb5b..a1142d4e 100644 --- a/src/libultra/os/invalicache.s +++ b/src/libultra/os/invalicache.s @@ -1,47 +1,47 @@ -# asm file. - -# assembler directives -.set noat # allow manual use of $at -.set noreorder # don't insert nops after branches -.set gp=64 - -.include "macros.inc" - - -.section .text, "ax" -glabel osInvalICache -/* 00DF50 7000D350 18A00011 */ blez $a1, .L7000D398 -/* 00DF54 7000D354 00000000 */ nop -/* 00DF58 7000D358 240B4000 */ li $t3, 16384 -/* 00DF5C 7000D35C 00AB082B */ sltu $at, $a1, $t3 -/* 00DF60 7000D360 1020000F */ beqz $at, .L7000D3A0 -/* 00DF64 7000D364 00000000 */ nop -/* 00DF68 7000D368 00804025 */ move $t0, $a0 -/* 00DF6C 7000D36C 00854821 */ addu $t1, $a0, $a1 -/* 00DF70 7000D370 0109082B */ sltu $at, $t0, $t1 -/* 00DF74 7000D374 10200008 */ beqz $at, .L7000D398 -/* 00DF78 7000D378 00000000 */ nop -/* 00DF7C 7000D37C 310A001F */ andi $t2, $t0, 0x1f -/* 00DF80 7000D380 2529FFE0 */ addiu $t1, $t1, -0x20 -/* 00DF84 7000D384 010A4023 */ subu $t0, $t0, $t2 -.L7000D388: -/* 00DF88 7000D388 BD100000 */ cache 0x10, ($t0) -/* 00DF8C 7000D38C 0109082B */ sltu $at, $t0, $t1 -/* 00DF90 7000D390 1420FFFD */ bnez $at, .L7000D388 -/* 00DF94 7000D394 25080020 */ addiu $t0, $t0, 0x20 -.L7000D398: -/* 00DF98 7000D398 03E00008 */ jr $ra -/* 00DF9C 7000D39C 00000000 */ nop - -.L7000D3A0: -/* 00DFA0 7000D3A0 3C088000 */ lui $t0, 0x8000 -/* 00DFA4 7000D3A4 010B4821 */ addu $t1, $t0, $t3 -/* 00DFA8 7000D3A8 2529FFE0 */ addiu $t1, $t1, -0x20 -.L7000D3AC: -/* 00DFAC 7000D3AC BD000000 */ cache 0, ($t0) -/* 00DFB0 7000D3B0 0109082B */ sltu $at, $t0, $t1 -/* 00DFB4 7000D3B4 1420FFFD */ bnez $at, .L7000D3AC -/* 00DFB8 7000D3B8 25080020 */ addiu $t0, $t0, 0x20 -/* 00DFBC 7000D3BC 03E00008 */ jr $ra -/* 00DFC0 7000D3C0 00000000 */ nop -.size osInvalICache, . - osInvalICache +# asm file. + +# assembler directives +.set noat # allow manual use of $at +.set noreorder # don't insert nops after branches +.set gp=64 + +.include "macros.inc" + + +.section .text, "ax" +glabel osInvalICache +/* 00DF50 7000D350 18A00011 */ blez $a1, .L7000D398 +/* 00DF54 7000D354 00000000 */ nop +/* 00DF58 7000D358 240B4000 */ li $t3, 16384 +/* 00DF5C 7000D35C 00AB082B */ sltu $at, $a1, $t3 +/* 00DF60 7000D360 1020000F */ beqz $at, .L7000D3A0 +/* 00DF64 7000D364 00000000 */ nop +/* 00DF68 7000D368 00804025 */ move $t0, $a0 +/* 00DF6C 7000D36C 00854821 */ addu $t1, $a0, $a1 +/* 00DF70 7000D370 0109082B */ sltu $at, $t0, $t1 +/* 00DF74 7000D374 10200008 */ beqz $at, .L7000D398 +/* 00DF78 7000D378 00000000 */ nop +/* 00DF7C 7000D37C 310A001F */ andi $t2, $t0, 0x1f +/* 00DF80 7000D380 2529FFE0 */ addiu $t1, $t1, -0x20 +/* 00DF84 7000D384 010A4023 */ subu $t0, $t0, $t2 +.L7000D388: +/* 00DF88 7000D388 BD100000 */ cache 0x10, ($t0) +/* 00DF8C 7000D38C 0109082B */ sltu $at, $t0, $t1 +/* 00DF90 7000D390 1420FFFD */ bnez $at, .L7000D388 +/* 00DF94 7000D394 25080020 */ addiu $t0, $t0, 0x20 +.L7000D398: +/* 00DF98 7000D398 03E00008 */ jr $ra +/* 00DF9C 7000D39C 00000000 */ nop + +.L7000D3A0: +/* 00DFA0 7000D3A0 3C088000 */ lui $t0, 0x8000 +/* 00DFA4 7000D3A4 010B4821 */ addu $t1, $t0, $t3 +/* 00DFA8 7000D3A8 2529FFE0 */ addiu $t1, $t1, -0x20 +.L7000D3AC: +/* 00DFAC 7000D3AC BD000000 */ cache 0, ($t0) +/* 00DFB0 7000D3B0 0109082B */ sltu $at, $t0, $t1 +/* 00DFB4 7000D3B4 1420FFFD */ bnez $at, .L7000D3AC +/* 00DFB8 7000D3B8 25080020 */ addiu $t0, $t0, 0x20 +/* 00DFBC 7000D3BC 03E00008 */ jr $ra +/* 00DFC0 7000D3C0 00000000 */ nop +.size osInvalICache, . - osInvalICache diff --git a/src/libultra/os/setfpccsr.s b/src/libultra/os/setfpccsr.s index 82d49d16..667b149c 100644 --- a/src/libultra/os/setfpccsr.s +++ b/src/libultra/os/setfpccsr.s @@ -1,16 +1,16 @@ -# asm file. - -# assembler directives -.set noat # allow manual use of $at -.set noreorder # don't insert nops after branches -.set gp=64 - -.include "macros.inc" - - -.section .text, "ax" -glabel __osSetFpcCsr -/* 00E020 7000D420 4442F800 */ cfc1 $v0, $31 -/* 00E024 7000D424 44C4F800 */ ctc1 $a0, $31 -/* 00E028 7000D428 03E00008 */ jr $ra -/* 00E02C 7000D42C 00000000 */ nop +# asm file. + +# assembler directives +.set noat # allow manual use of $at +.set noreorder # don't insert nops after branches +.set gp=64 + +.include "macros.inc" + + +.section .text, "ax" +glabel __osSetFpcCsr +/* 00E020 7000D420 4442F800 */ cfc1 $v0, $31 +/* 00E024 7000D424 44C4F800 */ ctc1 $a0, $31 +/* 00E028 7000D428 03E00008 */ jr $ra +/* 00E02C 7000D42C 00000000 */ nop diff --git a/src/libultra/os/setintmask.s b/src/libultra/os/setintmask.s index 094830e4..4c034e64 100644 --- a/src/libultra/os/setintmask.s +++ b/src/libultra/os/setintmask.s @@ -1,64 +1,64 @@ -# asm file. - -# assembler directives -.set noat # allow manual use of $at -.set noreorder # don't insert nops after branches -.set gp=64 - -.include "macros.inc" - - -.section .text, "ax" -glabel osSetIntMask -/* 00E930 7000DD30 400C6000 */ mfc0 $t4, $12 -/* 00E934 7000DD34 3182FF01 */ andi $v0, $t4, 0xff01 -/* 00E938 7000DD38 3C088002 */ lui $t0, %hi(__OSGlobalIntMask) -/* 00E93C 7000DD3C 2508698C */ addiu $t0, %lo(__OSGlobalIntMask) # addiu $t0, $t0, 0x698c -/* 00E940 7000DD40 8D0B0000 */ lw $t3, ($t0) -/* 00E944 7000DD44 2401FFFF */ li $at, -1 -/* 00E948 7000DD48 01614026 */ xor $t0, $t3, $at -/* 00E94C 7000DD4C 3108FF00 */ andi $t0, $t0, 0xff00 -/* 00E950 7000DD50 00481025 */ or $v0, $v0, $t0 -/* 00E954 7000DD54 3C0AA430 */ lui $t2, %hi(0xA430000C) # $t2, 0xa430 -/* 00E958 7000DD58 8D4A000C */ lw $t2, %lo(0xA430000C)($t2) -/* 00E95C 7000DD5C 11400005 */ beqz $t2, .L7000DD74 -/* 00E960 7000DD60 000B4C02 */ srl $t1, $t3, 0x10 -/* 00E964 7000DD64 2401FFFF */ li $at, -1 -/* 00E968 7000DD68 01214826 */ xor $t1, $t1, $at -/* 00E96C 7000DD6C 3129003F */ andi $t1, $t1, 0x3f -/* 00E970 7000DD70 01495025 */ or $t2, $t2, $t1 -.L7000DD74: -/* 00E974 7000DD74 000A5400 */ sll $t2, $t2, 0x10 -/* 00E978 7000DD78 004A1025 */ or $v0, $v0, $t2 -/* 00E97C 7000DD7C 3C01003F */ lui $at, 0x3f -/* 00E980 7000DD80 00814024 */ and $t0, $a0, $at -/* 00E984 7000DD84 010B4024 */ and $t0, $t0, $t3 -/* 00E988 7000DD88 000843C2 */ srl $t0, $t0, 0xf -/* 00E98C 7000DD8C 3C0A8003 */ lui $t2, %hi(__osRcpImTable) -/* 00E990 7000DD90 01485021 */ addu $t2, $t2, $t0 -/* 00E994 7000DD94 954A93A0 */ lhu $t2, %lo(__osRcpImTable)($t2) -/* 00E998 7000DD98 3C01A430 */ lui $at, %hi(0xA430000C) # $at, 0xa430 -/* 00E99C 7000DD9C AC2A000C */ sw $t2, %lo(0xA430000C)($at) -/* 00E9A0 7000DDA0 3088FF01 */ andi $t0, $a0, 0xff01 -/* 00E9A4 7000DDA4 3169FF00 */ andi $t1, $t3, 0xff00 -/* 00E9A8 7000DDA8 01094024 */ and $t0, $t0, $t1 -/* 00E9AC 7000DDAC 3C01FFFF */ lui $at, (0xFFFF00FF >> 16) # lui $at, 0xffff -/* 00E9B0 7000DDB0 342100FF */ ori $at, (0xFFFF00FF & 0xFFFF) # ori $at, $at, 0xff -/* 00E9B4 7000DDB4 01816024 */ and $t4, $t4, $at -/* 00E9B8 7000DDB8 01886025 */ or $t4, $t4, $t0 -/* 00E9BC 7000DDBC 408C6000 */ mtc0 $t4, $12 -/* 00E9C0 7000DDC0 00000000 */ nop -/* 00E9C4 7000DDC4 00000000 */ nop -/* 00E9C8 7000DDC8 03E00008 */ jr $ra -/* 00E9CC 7000DDCC 00000000 */ nop - -.section .rodata -glabel __osRcpImTable - .half 0x555, 0x556, 0x559, 0x55a, 0x565, 0x566, 0x569, 0x56a - .half 0x595, 0x596, 0x599, 0x59a, 0x5a5, 0x5a6, 0x5a9, 0x5aa - .half 0x655, 0x656, 0x659, 0x65a, 0x665, 0x666, 0x669, 0x66a - .half 0x695, 0x696, 0x699, 0x69a, 0x6a5, 0x6a6, 0x6a9, 0x6aa - .half 0x955, 0x956, 0x959, 0x95a, 0x965, 0x966, 0x969, 0x96a - .half 0x995, 0x996, 0x999, 0x99a, 0x9a5, 0x9a6, 0x9a9, 0x9aa - .half 0xa55, 0xa56, 0xa59, 0xa5a, 0xa65, 0xa66, 0xa69, 0xa6a - .half 0xa95, 0xa96, 0xa99, 0xa9a, 0xaa5, 0xaa6, 0xaa9, 0xaaa +# asm file. + +# assembler directives +.set noat # allow manual use of $at +.set noreorder # don't insert nops after branches +.set gp=64 + +.include "macros.inc" + + +.section .text, "ax" +glabel osSetIntMask +/* 00E930 7000DD30 400C6000 */ mfc0 $t4, $12 +/* 00E934 7000DD34 3182FF01 */ andi $v0, $t4, 0xff01 +/* 00E938 7000DD38 3C088002 */ lui $t0, %hi(__OSGlobalIntMask) +/* 00E93C 7000DD3C 2508698C */ addiu $t0, %lo(__OSGlobalIntMask) # addiu $t0, $t0, 0x698c +/* 00E940 7000DD40 8D0B0000 */ lw $t3, ($t0) +/* 00E944 7000DD44 2401FFFF */ li $at, -1 +/* 00E948 7000DD48 01614026 */ xor $t0, $t3, $at +/* 00E94C 7000DD4C 3108FF00 */ andi $t0, $t0, 0xff00 +/* 00E950 7000DD50 00481025 */ or $v0, $v0, $t0 +/* 00E954 7000DD54 3C0AA430 */ lui $t2, %hi(0xA430000C) # $t2, 0xa430 +/* 00E958 7000DD58 8D4A000C */ lw $t2, %lo(0xA430000C)($t2) +/* 00E95C 7000DD5C 11400005 */ beqz $t2, .L7000DD74 +/* 00E960 7000DD60 000B4C02 */ srl $t1, $t3, 0x10 +/* 00E964 7000DD64 2401FFFF */ li $at, -1 +/* 00E968 7000DD68 01214826 */ xor $t1, $t1, $at +/* 00E96C 7000DD6C 3129003F */ andi $t1, $t1, 0x3f +/* 00E970 7000DD70 01495025 */ or $t2, $t2, $t1 +.L7000DD74: +/* 00E974 7000DD74 000A5400 */ sll $t2, $t2, 0x10 +/* 00E978 7000DD78 004A1025 */ or $v0, $v0, $t2 +/* 00E97C 7000DD7C 3C01003F */ lui $at, 0x3f +/* 00E980 7000DD80 00814024 */ and $t0, $a0, $at +/* 00E984 7000DD84 010B4024 */ and $t0, $t0, $t3 +/* 00E988 7000DD88 000843C2 */ srl $t0, $t0, 0xf +/* 00E98C 7000DD8C 3C0A8003 */ lui $t2, %hi(__osRcpImTable) +/* 00E990 7000DD90 01485021 */ addu $t2, $t2, $t0 +/* 00E994 7000DD94 954A93A0 */ lhu $t2, %lo(__osRcpImTable)($t2) +/* 00E998 7000DD98 3C01A430 */ lui $at, %hi(0xA430000C) # $at, 0xa430 +/* 00E99C 7000DD9C AC2A000C */ sw $t2, %lo(0xA430000C)($at) +/* 00E9A0 7000DDA0 3088FF01 */ andi $t0, $a0, 0xff01 +/* 00E9A4 7000DDA4 3169FF00 */ andi $t1, $t3, 0xff00 +/* 00E9A8 7000DDA8 01094024 */ and $t0, $t0, $t1 +/* 00E9AC 7000DDAC 3C01FFFF */ lui $at, (0xFFFF00FF >> 16) # lui $at, 0xffff +/* 00E9B0 7000DDB0 342100FF */ ori $at, (0xFFFF00FF & 0xFFFF) # ori $at, $at, 0xff +/* 00E9B4 7000DDB4 01816024 */ and $t4, $t4, $at +/* 00E9B8 7000DDB8 01886025 */ or $t4, $t4, $t0 +/* 00E9BC 7000DDBC 408C6000 */ mtc0 $t4, $12 +/* 00E9C0 7000DDC0 00000000 */ nop +/* 00E9C4 7000DDC4 00000000 */ nop +/* 00E9C8 7000DDC8 03E00008 */ jr $ra +/* 00E9CC 7000DDCC 00000000 */ nop + +.section .rodata +glabel __osRcpImTable + .half 0x555, 0x556, 0x559, 0x55a, 0x565, 0x566, 0x569, 0x56a + .half 0x595, 0x596, 0x599, 0x59a, 0x5a5, 0x5a6, 0x5a9, 0x5aa + .half 0x655, 0x656, 0x659, 0x65a, 0x665, 0x666, 0x669, 0x66a + .half 0x695, 0x696, 0x699, 0x69a, 0x6a5, 0x6a6, 0x6a9, 0x6aa + .half 0x955, 0x956, 0x959, 0x95a, 0x965, 0x966, 0x969, 0x96a + .half 0x995, 0x996, 0x999, 0x99a, 0x9a5, 0x9a6, 0x9a9, 0x9aa + .half 0xa55, 0xa56, 0xa59, 0xa5a, 0xa65, 0xa66, 0xa69, 0xa6a + .half 0xa95, 0xa96, 0xa99, 0xa9a, 0xaa5, 0xaa6, 0xaa9, 0xaaa diff --git a/src/libultra/os/unmaptlb.s b/src/libultra/os/unmaptlb.s index 85d9e2fd..d535ae83 100644 --- a/src/libultra/os/unmaptlb.s +++ b/src/libultra/os/unmaptlb.s @@ -1,28 +1,28 @@ -# asm file. - -# assembler directives -.set noat # allow manual use of $at -.set noreorder # don't insert nops after branches -.set gp=64 - -.include "macros.inc" - - -.section .text, "ax" -glabel osUnmapTLB -/* 00DFD0 7000D3D0 40085000 */ mfc0 $t0, $10 -/* 00DFD4 7000D3D4 40840000 */ mtc0 $a0, $0 -/* 00DFD8 7000D3D8 3C098000 */ lui $t1, 0x8000 -/* 00DFDC 7000D3DC 40895000 */ mtc0 $t1, $10 -/* 00DFE0 7000D3E0 40801000 */ mtc0 $zero, $2 -/* 00DFE4 7000D3E4 40801800 */ mtc0 $zero, $3 -/* 00DFE8 7000D3E8 00000000 */ nop -/* 00DFEC 7000D3EC 42000002 */ tlbwi -/* 00DFF0 7000D3F0 00000000 */ nop -/* 00DFF4 7000D3F4 00000000 */ nop -/* 00DFF8 7000D3F8 00000000 */ nop -/* 00DFFC 7000D3FC 00000000 */ nop -/* 00E000 7000D400 40885000 */ mtc0 $t0, $10 -/* 00E004 7000D404 03E00008 */ jr $ra -/* 00E008 7000D408 00000000 */ nop - +# asm file. + +# assembler directives +.set noat # allow manual use of $at +.set noreorder # don't insert nops after branches +.set gp=64 + +.include "macros.inc" + + +.section .text, "ax" +glabel osUnmapTLB +/* 00DFD0 7000D3D0 40085000 */ mfc0 $t0, $10 +/* 00DFD4 7000D3D4 40840000 */ mtc0 $a0, $0 +/* 00DFD8 7000D3D8 3C098000 */ lui $t1, 0x8000 +/* 00DFDC 7000D3DC 40895000 */ mtc0 $t1, $10 +/* 00DFE0 7000D3E0 40801000 */ mtc0 $zero, $2 +/* 00DFE4 7000D3E4 40801800 */ mtc0 $zero, $3 +/* 00DFE8 7000D3E8 00000000 */ nop +/* 00DFEC 7000D3EC 42000002 */ tlbwi +/* 00DFF0 7000D3F0 00000000 */ nop +/* 00DFF4 7000D3F4 00000000 */ nop +/* 00DFF8 7000D3F8 00000000 */ nop +/* 00DFFC 7000D3FC 00000000 */ nop +/* 00E000 7000D400 40885000 */ mtc0 $t0, $10 +/* 00E004 7000D404 03E00008 */ jr $ra +/* 00E008 7000D408 00000000 */ nop + diff --git a/src/libultra/os/writebackdcache.s b/src/libultra/os/writebackdcache.s index bd7a3e28..a7fea73f 100644 --- a/src/libultra/os/writebackdcache.s +++ b/src/libultra/os/writebackdcache.s @@ -1,46 +1,46 @@ -# asm file. - -# assembler directives -.set noat # allow manual use of $at -.set noreorder # don't insert nops after branches -.set gp=64 - -.include "macros.inc" - - -.section .text, "ax" -glabel osWritebackDCache -/* 0117D0 70010BD0 18A00011 */ blez $a1, .L70010C18 -/* 0117D4 70010BD4 00000000 */ nop -/* 0117D8 70010BD8 240B2000 */ li $t3, 8192 -/* 0117DC 70010BDC 00AB082B */ sltu $at, $a1, $t3 -/* 0117E0 70010BE0 1020000F */ beqz $at, .L70010C20 -/* 0117E4 70010BE4 00000000 */ nop -/* 0117E8 70010BE8 00804025 */ move $t0, $a0 -/* 0117EC 70010BEC 00854821 */ addu $t1, $a0, $a1 -/* 0117F0 70010BF0 0109082B */ sltu $at, $t0, $t1 -/* 0117F4 70010BF4 10200008 */ beqz $at, .L70010C18 -/* 0117F8 70010BF8 00000000 */ nop -/* 0117FC 70010BFC 310A000F */ andi $t2, $t0, 0xf -/* 011800 70010C00 2529FFF0 */ addiu $t1, $t1, -0x10 -/* 011804 70010C04 010A4023 */ subu $t0, $t0, $t2 -.L70010C08: -/* 011808 70010C08 BD190000 */ cache 0x19, ($t0) -/* 01180C 70010C0C 0109082B */ sltu $at, $t0, $t1 -/* 011810 70010C10 1420FFFD */ bnez $at, .L70010C08 -/* 011814 70010C14 25080010 */ addiu $t0, $t0, 0x10 -.L70010C18: -/* 011818 70010C18 03E00008 */ jr $ra -/* 01181C 70010C1C 00000000 */ nop - -.L70010C20: -/* 011820 70010C20 3C088000 */ lui $t0, 0x8000 -/* 011824 70010C24 010B4821 */ addu $t1, $t0, $t3 -/* 011828 70010C28 2529FFF0 */ addiu $t1, $t1, -0x10 -.L70010C2C: -/* 01182C 70010C2C BD010000 */ cache 1, ($t0) -/* 011830 70010C30 0109082B */ sltu $at, $t0, $t1 -/* 011834 70010C34 1420FFFD */ bnez $at, .L70010C2C -/* 011838 70010C38 25080010 */ addiu $t0, $t0, 0x10 -/* 01183C 70010C3C 03E00008 */ jr $ra -/* 011840 70010C40 00000000 */ nop +# asm file. + +# assembler directives +.set noat # allow manual use of $at +.set noreorder # don't insert nops after branches +.set gp=64 + +.include "macros.inc" + + +.section .text, "ax" +glabel osWritebackDCache +/* 0117D0 70010BD0 18A00011 */ blez $a1, .L70010C18 +/* 0117D4 70010BD4 00000000 */ nop +/* 0117D8 70010BD8 240B2000 */ li $t3, 8192 +/* 0117DC 70010BDC 00AB082B */ sltu $at, $a1, $t3 +/* 0117E0 70010BE0 1020000F */ beqz $at, .L70010C20 +/* 0117E4 70010BE4 00000000 */ nop +/* 0117E8 70010BE8 00804025 */ move $t0, $a0 +/* 0117EC 70010BEC 00854821 */ addu $t1, $a0, $a1 +/* 0117F0 70010BF0 0109082B */ sltu $at, $t0, $t1 +/* 0117F4 70010BF4 10200008 */ beqz $at, .L70010C18 +/* 0117F8 70010BF8 00000000 */ nop +/* 0117FC 70010BFC 310A000F */ andi $t2, $t0, 0xf +/* 011800 70010C00 2529FFF0 */ addiu $t1, $t1, -0x10 +/* 011804 70010C04 010A4023 */ subu $t0, $t0, $t2 +.L70010C08: +/* 011808 70010C08 BD190000 */ cache 0x19, ($t0) +/* 01180C 70010C0C 0109082B */ sltu $at, $t0, $t1 +/* 011810 70010C10 1420FFFD */ bnez $at, .L70010C08 +/* 011814 70010C14 25080010 */ addiu $t0, $t0, 0x10 +.L70010C18: +/* 011818 70010C18 03E00008 */ jr $ra +/* 01181C 70010C1C 00000000 */ nop + +.L70010C20: +/* 011820 70010C20 3C088000 */ lui $t0, 0x8000 +/* 011824 70010C24 010B4821 */ addu $t1, $t0, $t3 +/* 011828 70010C28 2529FFF0 */ addiu $t1, $t1, -0x10 +.L70010C2C: +/* 01182C 70010C2C BD010000 */ cache 1, ($t0) +/* 011830 70010C30 0109082B */ sltu $at, $t0, $t1 +/* 011834 70010C34 1420FFFD */ bnez $at, .L70010C2C +/* 011838 70010C38 25080010 */ addiu $t0, $t0, 0x10 +/* 01183C 70010C3C 03E00008 */ jr $ra +/* 011840 70010C40 00000000 */ nop diff --git a/src/libultra/os/writebackdcacheall.s b/src/libultra/os/writebackdcacheall.s index 4077cc62..63050c58 100644 --- a/src/libultra/os/writebackdcacheall.s +++ b/src/libultra/os/writebackdcacheall.s @@ -1,24 +1,24 @@ -# asm file. - -# assembler directives -.set noat # allow manual use of $at -.set noreorder # don't insert nops after branches -.set gp=64 - -.include "macros.inc" - - -.section .text, "ax" -glabel osWritebackDCacheAll -/* 00DF20 7000D320 3C088000 */ lui $t0, 0x8000 -/* 00DF24 7000D324 240A2000 */ li $t2, 8192 -/* 00DF28 7000D328 010A4821 */ addu $t1, $t0, $t2 -/* 00DF2C 7000D32C 2529FFF0 */ addiu $t1, $t1, -0x10 -.L7000D330: -/* 00DF30 7000D330 BD010000 */ cache 1, ($t0) -/* 00DF34 7000D334 0109082B */ sltu $at, $t0, $t1 -/* 00DF38 7000D338 1420FFFD */ bnez $at, .L7000D330 -/* 00DF3C 7000D33C 25080010 */ addiu $t0, $t0, 0x10 -/* 00DF40 7000D340 03E00008 */ jr $ra -/* 00DF44 7000D344 00000000 */ nop -.size osWritebackDCacheAll, . - osWritebackDCacheAll +# asm file. + +# assembler directives +.set noat # allow manual use of $at +.set noreorder # don't insert nops after branches +.set gp=64 + +.include "macros.inc" + + +.section .text, "ax" +glabel osWritebackDCacheAll +/* 00DF20 7000D320 3C088000 */ lui $t0, 0x8000 +/* 00DF24 7000D324 240A2000 */ li $t2, 8192 +/* 00DF28 7000D328 010A4821 */ addu $t1, $t0, $t2 +/* 00DF2C 7000D32C 2529FFF0 */ addiu $t1, $t1, -0x10 +.L7000D330: +/* 00DF30 7000D330 BD010000 */ cache 1, ($t0) +/* 00DF34 7000D334 0109082B */ sltu $at, $t0, $t1 +/* 00DF38 7000D338 1420FFFD */ bnez $at, .L7000D330 +/* 00DF3C 7000D33C 25080010 */ addiu $t0, $t0, 0x10 +/* 00DF40 7000D340 03E00008 */ jr $ra +/* 00DF44 7000D344 00000000 */ nop +.size osWritebackDCacheAll, . - osWritebackDCacheAll diff --git a/tools/ge-stats.py b/tools/ge-stats.py index 8f831132..a0b86ce6 100644 --- a/tools/ge-stats.py +++ b/tools/ge-stats.py @@ -1,748 +1,748 @@ -import datetime -import errno -import functools -import getopt -import os -import re -import shlex -import socket -import subprocess -import sys -import time - -""" -re-writing the original ge-stats py. - Ben Burns, Dec 18, 2021 - -The general approach is to scan for source files in the specified directories (*.s, *.c). -Each file is evaluated, and a list of assembly function definitions is generated. -The map file is then parsed, giving a list of all known functions. -Stats are then computed for asm functions out of all known functions. - -The logic to determine whether a function has an assembly definition is rather simple. -It doesn't handle any c preprocessor macros, so the results are not guaranteed to -be accurate. If a line begins with the text "glabel" and is only followed by one -word, that is assumed to be an assembly function definition. For the purposes -of this script, any assembly definition for any ROM version will count against -the total. -""" - -# lower case -__supported_versions = ['us', 'jp', 'eu'] - -# any function read from .map file exceeding this size will throw an exception -__invalid_func_length_size = 1000000 - -# attempt to filter out extra glabels that are not function definitions. This is for jump table entries -__re_jpt_label = re.compile(r"\.L[0-9a-fA-F]{8}") - -# report output paths -__report_dir = "tools" + os.path.sep + "report" -__report_bin = __report_dir + os.path.sep + "report" -__report_template = __report_dir + os.path.sep + "template.html" -__report_out_us = __report_dir + os.path.sep + "index.html" -__report_out_jp = __report_dir + os.path.sep + "JPN.htm" -__report_out_eu = __report_dir + os.path.sep + "EU.htm" - -# default fallback if recently modified timestamps don't work -__mtime_fallback_filename = 'ge.u.z64' - -class FunctionInfo: - """ - Info about a function - """ - def __init__(self, name: str): - # function name - self.name = name - # flag to indicate if an asm function definition exists - self.nonmatching = False - # length of function as computed from map file - self.length = 0 - # parent SourceFileContent - self.parent = None - - def __str__(self): - return self.name - - def __repr__(self): - return self.name - - -class SourceFileContent: - """ - Information about a source file - """ - def __init__(self, path: str): - # path to file - self.path = path - # list of all asm function definitions found in the file - self.asm_functions = set() - # list of FunctionInfo as parsed from map file - self.all_functions = [] - - if path.endswith(".c") or path.endswith(".s") or path.endswith(".o"): - self.path_without_ext = path[:-2] - else: - self.path_without_ext = path - - # reference to SearchDir - self.parent = None - # last modified time, in seconds since epoch - self.mtime = 0 - - def __str__(self): - return self.path - - def __repr__(self): - return self.path - - def __eq__(self, other): - if not isinstance(other, SourceFileContent): - # don't attempt to compare against unrelated types - return NotImplemented - - return self.path == other.path - -class SearchDir: - """ - container to define search paramaters. - """ - def __init__(self, path: str, recurse: bool, ignore = None, completed = None): - """ - default constructor - - :param str path: folder path, relative to root where script is run. Should not - contain leading or trailing slash. - :param bool recurse: whether or not to recursively search contents of path. - :param ignore: optional files to ignore. List of strings. This is an explicit list of - files with any necessary folder prefix. Do not include leading slash. Wildcards - are not supported. - :param completed: optional files to mark as completed. List of strings. This is an - explicit list of files with any necessary folder prefix. Do not include leading - slash. Wildcards are not supported. - """ - self.path = path - self.recurse = not not recurse - # (config option) ignored files won't be added to source files - if ignore is None: - ignore = [] - self.ignore = ignore - # (config option) completed files won't have asm definitions added - if completed is None: - completed = [] - self.completed = completed - - # (runtime stat) count of functions found in/under this directory - self.function_count = 0 - # (runtime stat) length in bytes for all functions found - self.function_byte_count = 0 - # (runtime stat) count of non matching functions found in/under this directory - self.nonmatching_count = 0 - # (runtime stat) length in bytes for all non matching functions found - self.nonmatching_byte_count = 0 - # (runtime stat) count of found files - self.file_count = 0 - # (runtime stat) count of found files with no asm definitions - self.completed_file_count = 0 - - # fix path seperator if this is run on windows os (visual only, it will still run...) - self.path = self.path.replace('/', os.path.sep) - - def __str__(self): - return self.path - - def __repr__(self): - return self.path - - def __eq__(self, other): - if not isinstance(other, SearchDir): - # don't attempt to compare against unrelated types - return NotImplemented - - return self.path == other.path - - -class StatResults: - def __init__(self): - # "now" starting point, modification times must be before this datetime. - # Only applies to git log, OS modified time is unaffected. - self.now = datetime.datetime.now() - self.search_dirs = [] - # reference to SourceFileContent with highest mtime - self.last_modified_file = None - # status flag. Used to determine if last modified timestamps are valid. - self.last_mtime_valid = False - # count of all functions - self.total_function_count = 0 - # length in bytes for all functions - self.total_function_byte_count = 0 - # count of non matching functions - self.total_nonmatching_count = 0 - # length in bytes for all non matching functions - self.total_nonmatching_byte_count = 0 - # count of found files with no asm definitions - self.total_completed_file_count = 0 - # list of SourceFileContent - self.source_files = [] - - - -def mtime_os(file, now, qlocal = False): - if not qlocal: - print('using os') - return int(os.path.getmtime(file)) - - - -def mtime_git(file, now, qlocal = False): - if not qlocal: - print('using git') - try: - date_str = now.strftime('%Y-%m-%dT%H:%M:%S%z') - result = subprocess.run(['git', 'log', '-1', '--format=\"%ct\"', '--date=local', '--before=\"' + date_str + '\"', '--', file], stdout=subprocess.PIPE, universal_newlines=True) - except: - print ('fatal error reading git log history, maybe use OS modified time resolver, --mtime_os option. File: "' + file + '", date_str: "' + date_str + '"') - sys.exit(7) - - log_out = result.stdout.strip().replace('"', '') - if not log_out: - return 0 - return int(log_out) - - -def process_source_files(search, stats: StatResults, mtime_resolver, qlocal = False): - - stats.search_dirs = search - - # regular expression: a line that starts with text "glabel", followed by - # a single space, followed by (captured) anything that is not whitespace - # one or more times, followed by end of line. - p1 = re.compile(r"^glabel (\S+)$") - - # iterate each search location, looking inside source files for asm function definitions - for s in search: - if not os.path.isdir(s.path): - print('fatal: directory not found:', s.path) - sys.exit(3) - - for root, dir_names, filenames in os.walk(s.path): - for filename in filenames: - file = os.path.join(root, filename) - - if not (filename.lower().endswith(".c") or filename.lower().endswith(".s")): - continue - - if filename in s.ignore: - continue - - #print(file) - - sfc = SourceFileContent(file) - - sfc.asm_functions = set() - sfc.parent = s - sfc.mtime = mtime_resolver(file, stats.now, qlocal) - import datetime - - x = datetime.datetime.fromtimestamp(sfc.mtime) - if not qlocal: - print(sfc.path, ' = ', x.strftime('%c')) - - # The `completed` list is manually configured to specify which files should not be - # counted against the total. This is done by simply not adding asm function - # definitions into the asm_functions property. - if filename not in s.completed: - with open(file) as fp: - # The "in" operator will be faster than regex here, but will include stuff - # we don't want. Do an initial pass with "in", then refine results with - # regex. - # The container is a hashset for cases where the function is - # declared multiple times (e.g., different versions) - file_glabel_lines = [line for line in fp.readlines() if "glabel" in line] - for line in file_glabel_lines: - r1 = p1.findall(line) - if r1 and len(r1) == 1 and not re.match(__re_jpt_label, r1[0]): - sfc.asm_functions.add(r1[0]) - - stats.source_files.append(sfc) - - if not s.recurse: - break - - return stats - - -def apply_build_map(stats: StatResults, version: str): - """ - This iterates over the build map file. - It scans for the start of a `.text` definition indicating a new file. - Then it parses each following line to get function names within the file - until a new section is encountered. - """ - version_code = version[0] - map_file_path = 'build' + os.path.sep + version_code + os.path.sep + 'ge007.' + version_code + '.map' - - infile = False - prevromaddr = 0 - - # regex: - # a line that starts with text " .text", followed by any whitespace (at least one), then a 64 bit hex address. - # the lower 32 bits of the address are captured in a group. Followed by any whitespace (at least one), - # followed by a hex address (capturing everything after "0x"), followed by any whitespace (at least one). - # It then matches a build path, based on version, capturing everything after the version and slash - # up to the file extension, followed by end of line. - p1 = re.compile(r"^ \.text\s+0x(?:00000000)?([0-9a-f]{8})\s+0x([0-9a-f]+)\s+build\/" + re.escape(version_code) + r"\/([^\s]+)$") - - # regex to match subsequent lines after the above regex matches. This will - # match any whitespace, then 64 bit address (capturing the lower 32 bits), then - # any whitespace (at least one), then capture the remaining non-whitespace-text until end of line (in capture group). - # That last capture group is the function name. - p2 = re.compile(r"^\s+0x(?:00000000)?([0-9a-f]{8})\s+(\S+)$") - - current_sfc = None - current_fi = None - - with open(map_file_path, 'r') as map_file: - lines = map_file.readlines() - - for line in lines: - - m1 = p1.findall(line) - m2 = p2.findall(line) - - # m1 match means this is a new .text section (new file) - if m1: - filestart = int(m1[0][0], 16) - filelen = int(m1[0][1], 16) - # normalize paths - segment = (m1[0][2]).replace('/', os.path.sep) - infile = True - prevromaddr = 0 - prevfuncname = None - - current_fi = None - - if segment.endswith(".c") or segment.endswith(".s") or segment.endswith(".o"): - segment_without_ext = segment[:-2] - else: - segment_without_ext = segment - - # get the SourceFileContent container for this file listing - current_sfc = next((x for x in stats.source_files if x.path_without_ext == segment_without_ext), None) - # if there's no existing reference, that means this function is listed - # in the map file but not found when searching directories previously. - # In that case, this file/function should be ignored. - if current_sfc is None: - infile = False - - elif infile and m2: - romaddr = int(m2[0][0], 16) - funcname = m2[0][1] - - if re.match(__re_jpt_label, funcname): - continue - - if current_sfc is None: - print('invalid state: current_sfc not set') - sys.exit(4) - - if current_fi is not None: - current_fi.length = romaddr - prevromaddr - if current_fi.length > __invalid_func_length_size: - raise ValueError("function length too large: " + str(funcname) + " len = " + str(current_fi.length)) - - # get the function info for this function, or create a new one - current_fi = next((x for x in current_sfc.all_functions if x.name == funcname), None) - if current_fi is None: - current_fi = FunctionInfo(funcname) - current_fi.parent = current_sfc - # check to see if there was an asm definition for this function - if funcname in current_sfc.asm_functions: - current_fi.nonmatching = True - current_sfc.all_functions.append(current_fi) - - prevromaddr = romaddr - prevfuncname = funcname - - elif infile: - infile = False - - # this is a one line definition in the map file - if current_fi is None: - current_fi = FunctionInfo(funcname) - current_fi.parent = current_sfc - # check to see if there was an asm definition for this function - if funcname in current_sfc.asm_functions: - current_fi.nonmatching = True - # since this is only one entry, use the entire size - current_fi.length = filelen - current_sfc.all_functions.append(current_fi) - else: - current_fi.length = (filestart + filelen) - prevromaddr - - if current_fi.length > __invalid_func_length_size: - raise ValueError("function length too large: " + str(funcname) + " len = " + str(current_fi.length)) - - - -def percent_complete(num, den): - if not (den > 0): - return 1 - elif num == den: - return 1 - - frac = num / den - - if frac <= 0: - return 0 - elif frac > 0 and frac < 0.01: - return 0.01 - elif frac >= 0.01 and frac <= 0.99: - return frac - elif frac >= 0.99 and frac < 1: - return 0.99 - - raise ValueError("Can't compute percent complete: " + str(num) + " / " + str(den)) - - -def generate_default_stats(stats: StatResults): - """ - Iterate each file, and each function in each file, to compute - the total bytes/functions/files in each searchdir, and sum - the totals for the entire search. - """ - - # track unique modification times - seen_mtime = set() - - for file in stats.source_files: - # if this file comes from the .map file and does not have the - # parent association setup then it should be ignored. - if file.parent is None: - continue - - if stats.last_modified_file is None or stats.last_modified_file.mtime < file.mtime: - stats.last_modified_file = file - - seen_mtime.add(file.mtime) - - for func in file.all_functions: - file.parent.function_count += 1 - file.parent.function_byte_count += func.length - stats.total_function_count += 1 - stats.total_function_byte_count += func.length - - if func.nonmatching: - file.parent.nonmatching_count += 1 - file.parent.nonmatching_byte_count += func.length - stats.total_nonmatching_count += 1 - stats.total_nonmatching_byte_count += func.length - - file.parent.file_count += 1 - - if len(file.asm_functions) == 0: - file.parent.completed_file_count += 1 - stats.total_completed_file_count += 1 - - # More hacks to check for valid modification times. - # 10 is an arbitrary number, but this says that if there are - # at least 10 unique modification times, assume the timestamps - # are valid. Otherwise this will fallbac kto show the default file. - if len(seen_mtime) > 10: - stats.last_mtime_valid = True - - # If it wasn't possible to determine recently modified file (i.e., github actions) - # then fallback to "ge.u.z64" - if not stats.last_mtime_valid: - stats.last_modified_file = SourceFileContent(__mtime_fallback_filename) - - -def print_default_stats(stats: StatResults, version): - - print() - print('~> Decomp Statistics for ' + version.upper() +' version') - print('__________________________________________________________________') - print() - - print('FILES') - print('\trecently modified:') - if stats.last_mtime_valid: - recent_files = sorted(stats.source_files, key=lambda x: x.mtime, reverse=True)[:5] - for rf in recent_files: - print('\t\t' + rf.path) - else: - print('\t\t' + __mtime_fallback_filename) - print() - for s in stats.search_dirs: - print('\t{:12}\t{:5,} / {:5,} \t{:6.2f}%'.format(s.path, int(s.completed_file_count), int(s.file_count), (percent_complete(s.completed_file_count, s.file_count) * 100))) - print() - print('\ttotal \t{:5,} / {:5,} \t{:6.2f}%'.format(int(stats.total_completed_file_count), len(stats.source_files), (percent_complete(stats.total_completed_file_count, len(stats.source_files)) * 100))) - print('__________________________________________________________________') - print() - - print('FUNCTIONS') - for s in stats.search_dirs: - d_completed_func_count = s.function_count - s.nonmatching_count - print('\t{:12}\t{:7,} / {:7,} \t{:6.2f}%'.format(s.path, int(d_completed_func_count), int(s.function_count), (percent_complete(d_completed_func_count, s.function_count) * 100))) - print() - total_completed_func_count = stats.total_function_count - stats.total_nonmatching_count - print('\ttotal \t{:7,} / {:7,} \t{:6.2f}%'.format(int(total_completed_func_count), int(stats.total_function_count), (percent_complete(total_completed_func_count, stats.total_function_count) * 100))) - print('__________________________________________________________________') - print() - - print('BYTES') - for s in stats.search_dirs: - d_completed_byte_count = s.function_byte_count - s.nonmatching_byte_count - print('\t{:12}\t{:11,} / {:11,} \t{:6.2f}%'.format(s.path, int(d_completed_byte_count), int(s.function_byte_count), (percent_complete(d_completed_byte_count, s.function_byte_count) * 100))) - print() - completed_byte_count = stats.total_function_byte_count - stats.total_nonmatching_byte_count - print('\ttotal \t{:11,} / {:11,} \t{:6.2f}%'.format(int(completed_byte_count), int(stats.total_function_byte_count), (percent_complete(completed_byte_count, stats.total_function_byte_count) * 100))) - print('__________________________________________________________________') - print() - - - -def print_non_matching_stats(stats: StatResults, version): - """ - List all non matching functions. Sort by byte length. - Print as csv of filename, function name, function length. - """ - functions = [] - for file in stats.source_files: - for func in file.all_functions: - if func.nonmatching: - functions.append(func) - functions.sort(key=lambda x: x.length, reverse=True) - try: - print("filename, function, length") - for f in functions: - print(f.parent.path + ", " + f.name + ", " + str(f.length)) - except socket.error as e: - if e.errno == errno.EPIPE: - sys.exit(0) - raise - - -def bytes_to_words(b): - return (int(b) >> 2) << 2 - - -def generate_report(stats: StatResults, version): - """ - Send values over to the c report app. - Arguments must be added in the following order: - - note: changing from "libultra" to "libultrare" - - SRC_DIR: int, size in 32-bit words of completed functions, in src/ directory - SRC_DIR_MAX: int, size in 32-bit words of all tracked functions, in src/ directory - GAME_DIR: int, size in 32-bit words of completed functions, in src/game/ directory - GAME_DIR_MAX: int, size in 32-bit words of all tracked functions, in src/game/ directory - INFLATE_DIR: int, size in 32-bit words of completed functions, in src/inflate/ directory - INFLATE_DIR_MAX: int, size in 32-bit words of all tracked functions, in src/inflate/ directory - LIBULTRA_DIR: int, size in 32-bit words of completed functions, in src/libultrare/ directory - LIBULTRA_DIR_MAX: int, size in 32-bit words of all tracked functions, in src/libultrare/ directory - DECOMPILED_WORDS: int, size in 32-bit words of all completed functions - DECOMPILED_WORDS_MAX: int, size in 32-bit words of all tracked functions - DECOMPILED_FILES: int, count of all fully completed files - DECOMPILED_FILES_MAX: int, count of all tracked files - HTML_TEMPLATE: string: path to html template input file - HTML_OUTPUT: string: path to generated html file - LAST_MODIFIED_FILE: string: name of last modified file - LOG_LEVEL: int (enum): LOG_MIN = 0, LOG_DEF, LOG_MAX - - """ - - sd = next((x for x in stats.search_dirs if x.path == 'src'), None) - if sd is None: - raise ValueError('Could not find "src" SearchDir') - src_dir = bytes_to_words(sd.function_byte_count - sd.nonmatching_byte_count) - src_dir_max = bytes_to_words(sd.function_byte_count) - - sd = next((x for x in stats.search_dirs if x.path == 'src/game'), None) - if sd is None: - raise ValueError('Could not find "src/game" SearchDir') - game_dir = bytes_to_words(sd.function_byte_count - sd.nonmatching_byte_count) - game_dir_max = bytes_to_words(sd.function_byte_count) - - sd = next((x for x in stats.search_dirs if x.path == 'src/inflate'), None) - if sd is None: - raise ValueError('Could not find "src/inflate" SearchDir') - inflate_dir = bytes_to_words(sd.function_byte_count - sd.nonmatching_byte_count) - inflate_dir_max = bytes_to_words(sd.function_byte_count) - - # note: libultrare - sd = next((x for x in stats.search_dirs if x.path == 'src/libultrare'), None) - if sd is None: - raise ValueError('Could not find "src/libultrare" SearchDir') - libultrare_dir = bytes_to_words(sd.function_byte_count - sd.nonmatching_byte_count) - libultrare_dir_max = bytes_to_words(sd.function_byte_count) - - decompiled_words = bytes_to_words(stats.total_function_byte_count - stats.total_nonmatching_byte_count) - decompiled_words_max = bytes_to_words(stats.total_function_byte_count) - - decompiled_files = stats.total_completed_file_count - decompiled_files_max = len(stats.source_files) - - if version == 'us': - html_output = __report_out_us - elif version == 'jp': - html_output = __report_out_jp - elif version == 'eu': - html_output = __report_out_eu - else: - print('fatal: version', version, 'not supported! Supported versions are: ', ', '.join(__supported_versions)) - sys.exit(2) - - printstring = __report_bin + ' ' + \ - str(src_dir) + ' ' + \ - str(src_dir_max) + ' ' + \ - str(game_dir) + ' ' + \ - str(game_dir_max) + ' ' + \ - str(inflate_dir) + ' ' + \ - str(inflate_dir_max) + ' ' + \ - str(libultrare_dir) + ' ' + \ - str(libultrare_dir_max) + ' ' + \ - str(decompiled_words) + ' ' + \ - str(decompiled_words_max) + ' ' + \ - str(decompiled_files) + ' ' + \ - str(decompiled_files_max) + ' ' + \ - __report_template + ' ' + \ - html_output + ' ' + \ - stats.last_modified_file.path + ' ' + \ - '0' - - subprocess.Popen(printstring.split()) - - -def print_help(): - print('Usage: python3 ge-stats2.py [OPTION]') - print('Generate Decompilation Stats for Goldeneye 007') - print('Example: python3 ge-stats2.py --version us') - print(' ') - print(' This script expects to be run from the root directory. There must be') - print(' a map file in ./build/u/ge007.u.map (or equivalent version). Source files') - print(' must be located in a folder in the root directory.') - print(' ') - print(' This script does not attempt to parse preprocessor definitions and') - print(' ignores version differences in source files. For the purposes of these') - print(' stats, any version with an asm definition of a function will count') - print(' against completion. However, a version is still required to specify') - print(' which build map to reference against.') - print(' ') - print('Options:') - print(' -v, --version=CODE generate decomp stats for version CODE (us, jp, or eu). Default is us') - print(' -r, --report generate html report using report tool') - print(' -n, --non_matching print csv of all non matching function definitions') - print(' --mtime_os use OS last modified time instead of git log') - print(' -h, --help display this help text and exit') - print(' --qlocal quiet "local" version. Disable all the extra print statements included for github actions.') - -def main(): - # default to US version - version = 'us' - run_report = False - print_method = 'default' - mtime_use_os = False - qlocal = False - - if (len(sys.argv) > 1): - arguments, values = getopt.getopt(sys.argv[1:], "hv:rn", ["help", "version=", "report", "non_matching", "mtime_os", "qlocal"]) - - for current_argument, current_value in arguments: - if current_argument in ("-h", "--help"): - print_help() - sys.exit() - elif current_argument in ("-v", "--version"): - version = current_value.lower() - elif current_argument in ("-r", "--report"): - run_report = True - elif current_argument in ("-n", "--non_matching"): - print_method = 'non_matching' - elif current_argument in ("--mtime_os"): - mtime_use_os = True - elif current_argument in ("--qlocal"): - qlocal = True - - if version not in __supported_versions: - print('fatal: version', version, 'not supported! Supported versions are: ', ', '.join(__supported_versions)) - sys.exit(2) - - if run_report: - if not os.path.isfile(__report_bin): - print('fatal: file not found: ' + __report_bin) - sys.exit(6) - - # if this is just getting status on nonmatching files, ignore modified time lookup. - if print_method == 'non_matching' and not run_report: - mtime_use_os = True - - #if not qlocal: - # print(subprocess.run(['git', 'diff', 'origin/master', '--name-only', '"@{10 minutes ago}"']).stdout) - # Default to using git log to get the file's modified date. - # Git log will be much slower, but cloning a new repo (i.e., github actions online) - # will reset all the modified timestamps to the same value, so will need to - # resolve the last modified time from git history. - mtime_resolver = mtime_os - if not mtime_use_os: - if os.path.isdir('.git'): - mtime_resolver = mtime_git - else: - # ok, do one last check (for github actions) - try: - result = subprocess.run(['git', 'log', '-1', '--format=\"%ct\"', '--', 'readme.md'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) - if result.returncode == 0: - mtime_resolver = mtime_git - except: - pass - - # files to count as complete, in src/ directory - src_completed_list = [ - '_start.s', - 'aspboot.s', - 'boot.s', - 'bootcode.s', - 'getra.s', - 'gspboot.s', - 'osMapTLB.s', - 'random.s', - 'rom_header.s', - 'rspboot.s', - 'tlb_random.s', - 'tlb_hardwire.s', - 'tlb_resolve.s'] - - # files to count as complete, in src/libultrare directory - src_libultrare_completed_list = [ - 'ultra80069080.s'] - - # files to count as complete, in src/game directory - # These are intentionally kept in assembly. - src_game_completed_list = [ - 'sub_GAME_7F01D1C0.s', - 'chrObjRandom.s', - 'math_sincos.s'] - - search = [] - search.append(SearchDir('src', False, completed=src_completed_list)) - search.append(SearchDir('src/game', False, completed=src_game_completed_list)) - search.append(SearchDir('src/inflate', False)) - search.append(SearchDir('src/libultrare', True, completed=src_libultrare_completed_list)) - - stats = StatResults() - - process_source_files(search, stats, mtime_resolver, qlocal) - apply_build_map(stats, version) - generate_default_stats(stats) - - if print_method == 'default': - print_default_stats(stats, version) - elif print_method == 'non_matching': - print_non_matching_stats(stats, version) - - if run_report: - generate_report(stats, version) - -if __name__ == '__main__': - #start_time = time.time() - main() - #print("--- %s seconds ---" % (time.time() - start_time)) +import datetime +import errno +import functools +import getopt +import os +import re +import shlex +import socket +import subprocess +import sys +import time + +""" +re-writing the original ge-stats py. - Ben Burns, Dec 18, 2021 + +The general approach is to scan for source files in the specified directories (*.s, *.c). +Each file is evaluated, and a list of assembly function definitions is generated. +The map file is then parsed, giving a list of all known functions. +Stats are then computed for asm functions out of all known functions. + +The logic to determine whether a function has an assembly definition is rather simple. +It doesn't handle any c preprocessor macros, so the results are not guaranteed to +be accurate. If a line begins with the text "glabel" and is only followed by one +word, that is assumed to be an assembly function definition. For the purposes +of this script, any assembly definition for any ROM version will count against +the total. +""" + +# lower case +__supported_versions = ['us', 'jp', 'eu'] + +# any function read from .map file exceeding this size will throw an exception +__invalid_func_length_size = 1000000 + +# attempt to filter out extra glabels that are not function definitions. This is for jump table entries +__re_jpt_label = re.compile(r"\.L[0-9a-fA-F]{8}") + +# report output paths +__report_dir = "tools" + os.path.sep + "report" +__report_bin = __report_dir + os.path.sep + "report" +__report_template = __report_dir + os.path.sep + "template.html" +__report_out_us = __report_dir + os.path.sep + "index.html" +__report_out_jp = __report_dir + os.path.sep + "JPN.htm" +__report_out_eu = __report_dir + os.path.sep + "EU.htm" + +# default fallback if recently modified timestamps don't work +__mtime_fallback_filename = 'ge.u.z64' + +class FunctionInfo: + """ + Info about a function + """ + def __init__(self, name: str): + # function name + self.name = name + # flag to indicate if an asm function definition exists + self.nonmatching = False + # length of function as computed from map file + self.length = 0 + # parent SourceFileContent + self.parent = None + + def __str__(self): + return self.name + + def __repr__(self): + return self.name + + +class SourceFileContent: + """ + Information about a source file + """ + def __init__(self, path: str): + # path to file + self.path = path + # list of all asm function definitions found in the file + self.asm_functions = set() + # list of FunctionInfo as parsed from map file + self.all_functions = [] + + if path.endswith(".c") or path.endswith(".s") or path.endswith(".o"): + self.path_without_ext = path[:-2] + else: + self.path_without_ext = path + + # reference to SearchDir + self.parent = None + # last modified time, in seconds since epoch + self.mtime = 0 + + def __str__(self): + return self.path + + def __repr__(self): + return self.path + + def __eq__(self, other): + if not isinstance(other, SourceFileContent): + # don't attempt to compare against unrelated types + return NotImplemented + + return self.path == other.path + +class SearchDir: + """ + container to define search paramaters. + """ + def __init__(self, path: str, recurse: bool, ignore = None, completed = None): + """ + default constructor + + :param str path: folder path, relative to root where script is run. Should not + contain leading or trailing slash. + :param bool recurse: whether or not to recursively search contents of path. + :param ignore: optional files to ignore. List of strings. This is an explicit list of + files with any necessary folder prefix. Do not include leading slash. Wildcards + are not supported. + :param completed: optional files to mark as completed. List of strings. This is an + explicit list of files with any necessary folder prefix. Do not include leading + slash. Wildcards are not supported. + """ + self.path = path + self.recurse = not not recurse + # (config option) ignored files won't be added to source files + if ignore is None: + ignore = [] + self.ignore = ignore + # (config option) completed files won't have asm definitions added + if completed is None: + completed = [] + self.completed = completed + + # (runtime stat) count of functions found in/under this directory + self.function_count = 0 + # (runtime stat) length in bytes for all functions found + self.function_byte_count = 0 + # (runtime stat) count of non matching functions found in/under this directory + self.nonmatching_count = 0 + # (runtime stat) length in bytes for all non matching functions found + self.nonmatching_byte_count = 0 + # (runtime stat) count of found files + self.file_count = 0 + # (runtime stat) count of found files with no asm definitions + self.completed_file_count = 0 + + # fix path seperator if this is run on windows os (visual only, it will still run...) + self.path = self.path.replace('/', os.path.sep) + + def __str__(self): + return self.path + + def __repr__(self): + return self.path + + def __eq__(self, other): + if not isinstance(other, SearchDir): + # don't attempt to compare against unrelated types + return NotImplemented + + return self.path == other.path + + +class StatResults: + def __init__(self): + # "now" starting point, modification times must be before this datetime. + # Only applies to git log, OS modified time is unaffected. + self.now = datetime.datetime.now() + self.search_dirs = [] + # reference to SourceFileContent with highest mtime + self.last_modified_file = None + # status flag. Used to determine if last modified timestamps are valid. + self.last_mtime_valid = False + # count of all functions + self.total_function_count = 0 + # length in bytes for all functions + self.total_function_byte_count = 0 + # count of non matching functions + self.total_nonmatching_count = 0 + # length in bytes for all non matching functions + self.total_nonmatching_byte_count = 0 + # count of found files with no asm definitions + self.total_completed_file_count = 0 + # list of SourceFileContent + self.source_files = [] + + + +def mtime_os(file, now, qlocal = False): + if not qlocal: + print('using os') + return int(os.path.getmtime(file)) + + + +def mtime_git(file, now, qlocal = False): + if not qlocal: + print('using git') + try: + date_str = now.strftime('%Y-%m-%dT%H:%M:%S%z') + result = subprocess.run(['git', 'log', '-1', '--format=\"%ct\"', '--date=local', '--before=\"' + date_str + '\"', '--', file], stdout=subprocess.PIPE, universal_newlines=True) + except: + print ('fatal error reading git log history, maybe use OS modified time resolver, --mtime_os option. File: "' + file + '", date_str: "' + date_str + '"') + sys.exit(7) + + log_out = result.stdout.strip().replace('"', '') + if not log_out: + return 0 + return int(log_out) + + +def process_source_files(search, stats: StatResults, mtime_resolver, qlocal = False): + + stats.search_dirs = search + + # regular expression: a line that starts with text "glabel", followed by + # a single space, followed by (captured) anything that is not whitespace + # one or more times, followed by end of line. + p1 = re.compile(r"^glabel (\S+)$") + + # iterate each search location, looking inside source files for asm function definitions + for s in search: + if not os.path.isdir(s.path): + print('fatal: directory not found:', s.path) + sys.exit(3) + + for root, dir_names, filenames in os.walk(s.path): + for filename in filenames: + file = os.path.join(root, filename) + + if not (filename.lower().endswith(".c") or filename.lower().endswith(".s")): + continue + + if filename in s.ignore: + continue + + #print(file) + + sfc = SourceFileContent(file) + + sfc.asm_functions = set() + sfc.parent = s + sfc.mtime = mtime_resolver(file, stats.now, qlocal) + import datetime + + x = datetime.datetime.fromtimestamp(sfc.mtime) + if not qlocal: + print(sfc.path, ' = ', x.strftime('%c')) + + # The `completed` list is manually configured to specify which files should not be + # counted against the total. This is done by simply not adding asm function + # definitions into the asm_functions property. + if filename not in s.completed: + with open(file) as fp: + # The "in" operator will be faster than regex here, but will include stuff + # we don't want. Do an initial pass with "in", then refine results with + # regex. + # The container is a hashset for cases where the function is + # declared multiple times (e.g., different versions) + file_glabel_lines = [line for line in fp.readlines() if "glabel" in line] + for line in file_glabel_lines: + r1 = p1.findall(line) + if r1 and len(r1) == 1 and not re.match(__re_jpt_label, r1[0]): + sfc.asm_functions.add(r1[0]) + + stats.source_files.append(sfc) + + if not s.recurse: + break + + return stats + + +def apply_build_map(stats: StatResults, version: str): + """ + This iterates over the build map file. + It scans for the start of a `.text` definition indicating a new file. + Then it parses each following line to get function names within the file + until a new section is encountered. + """ + version_code = version[0] + map_file_path = 'build' + os.path.sep + version_code + os.path.sep + 'ge007.' + version_code + '.map' + + infile = False + prevromaddr = 0 + + # regex: + # a line that starts with text " .text", followed by any whitespace (at least one), then a 64 bit hex address. + # the lower 32 bits of the address are captured in a group. Followed by any whitespace (at least one), + # followed by a hex address (capturing everything after "0x"), followed by any whitespace (at least one). + # It then matches a build path, based on version, capturing everything after the version and slash + # up to the file extension, followed by end of line. + p1 = re.compile(r"^ \.text\s+0x(?:00000000)?([0-9a-f]{8})\s+0x([0-9a-f]+)\s+build\/" + re.escape(version_code) + r"\/([^\s]+)$") + + # regex to match subsequent lines after the above regex matches. This will + # match any whitespace, then 64 bit address (capturing the lower 32 bits), then + # any whitespace (at least one), then capture the remaining non-whitespace-text until end of line (in capture group). + # That last capture group is the function name. + p2 = re.compile(r"^\s+0x(?:00000000)?([0-9a-f]{8})\s+(\S+)$") + + current_sfc = None + current_fi = None + + with open(map_file_path, 'r') as map_file: + lines = map_file.readlines() + + for line in lines: + + m1 = p1.findall(line) + m2 = p2.findall(line) + + # m1 match means this is a new .text section (new file) + if m1: + filestart = int(m1[0][0], 16) + filelen = int(m1[0][1], 16) + # normalize paths + segment = (m1[0][2]).replace('/', os.path.sep) + infile = True + prevromaddr = 0 + prevfuncname = None + + current_fi = None + + if segment.endswith(".c") or segment.endswith(".s") or segment.endswith(".o"): + segment_without_ext = segment[:-2] + else: + segment_without_ext = segment + + # get the SourceFileContent container for this file listing + current_sfc = next((x for x in stats.source_files if x.path_without_ext == segment_without_ext), None) + # if there's no existing reference, that means this function is listed + # in the map file but not found when searching directories previously. + # In that case, this file/function should be ignored. + if current_sfc is None: + infile = False + + elif infile and m2: + romaddr = int(m2[0][0], 16) + funcname = m2[0][1] + + if re.match(__re_jpt_label, funcname): + continue + + if current_sfc is None: + print('invalid state: current_sfc not set') + sys.exit(4) + + if current_fi is not None: + current_fi.length = romaddr - prevromaddr + if current_fi.length > __invalid_func_length_size: + raise ValueError("function length too large: " + str(funcname) + " len = " + str(current_fi.length)) + + # get the function info for this function, or create a new one + current_fi = next((x for x in current_sfc.all_functions if x.name == funcname), None) + if current_fi is None: + current_fi = FunctionInfo(funcname) + current_fi.parent = current_sfc + # check to see if there was an asm definition for this function + if funcname in current_sfc.asm_functions: + current_fi.nonmatching = True + current_sfc.all_functions.append(current_fi) + + prevromaddr = romaddr + prevfuncname = funcname + + elif infile: + infile = False + + # this is a one line definition in the map file + if current_fi is None: + current_fi = FunctionInfo(funcname) + current_fi.parent = current_sfc + # check to see if there was an asm definition for this function + if funcname in current_sfc.asm_functions: + current_fi.nonmatching = True + # since this is only one entry, use the entire size + current_fi.length = filelen + current_sfc.all_functions.append(current_fi) + else: + current_fi.length = (filestart + filelen) - prevromaddr + + if current_fi.length > __invalid_func_length_size: + raise ValueError("function length too large: " + str(funcname) + " len = " + str(current_fi.length)) + + + +def percent_complete(num, den): + if not (den > 0): + return 1 + elif num == den: + return 1 + + frac = num / den + + if frac <= 0: + return 0 + elif frac > 0 and frac < 0.01: + return 0.01 + elif frac >= 0.01 and frac <= 0.99: + return frac + elif frac >= 0.99 and frac < 1: + return 0.99 + + raise ValueError("Can't compute percent complete: " + str(num) + " / " + str(den)) + + +def generate_default_stats(stats: StatResults): + """ + Iterate each file, and each function in each file, to compute + the total bytes/functions/files in each searchdir, and sum + the totals for the entire search. + """ + + # track unique modification times + seen_mtime = set() + + for file in stats.source_files: + # if this file comes from the .map file and does not have the + # parent association setup then it should be ignored. + if file.parent is None: + continue + + if stats.last_modified_file is None or stats.last_modified_file.mtime < file.mtime: + stats.last_modified_file = file + + seen_mtime.add(file.mtime) + + for func in file.all_functions: + file.parent.function_count += 1 + file.parent.function_byte_count += func.length + stats.total_function_count += 1 + stats.total_function_byte_count += func.length + + if func.nonmatching: + file.parent.nonmatching_count += 1 + file.parent.nonmatching_byte_count += func.length + stats.total_nonmatching_count += 1 + stats.total_nonmatching_byte_count += func.length + + file.parent.file_count += 1 + + if len(file.asm_functions) == 0: + file.parent.completed_file_count += 1 + stats.total_completed_file_count += 1 + + # More hacks to check for valid modification times. + # 10 is an arbitrary number, but this says that if there are + # at least 10 unique modification times, assume the timestamps + # are valid. Otherwise this will fallbac kto show the default file. + if len(seen_mtime) > 10: + stats.last_mtime_valid = True + + # If it wasn't possible to determine recently modified file (i.e., github actions) + # then fallback to "ge.u.z64" + if not stats.last_mtime_valid: + stats.last_modified_file = SourceFileContent(__mtime_fallback_filename) + + +def print_default_stats(stats: StatResults, version): + + print() + print('~> Decomp Statistics for ' + version.upper() +' version') + print('__________________________________________________________________') + print() + + print('FILES') + print('\trecently modified:') + if stats.last_mtime_valid: + recent_files = sorted(stats.source_files, key=lambda x: x.mtime, reverse=True)[:5] + for rf in recent_files: + print('\t\t' + rf.path) + else: + print('\t\t' + __mtime_fallback_filename) + print() + for s in stats.search_dirs: + print('\t{:12}\t{:5,} / {:5,} \t{:6.2f}%'.format(s.path, int(s.completed_file_count), int(s.file_count), (percent_complete(s.completed_file_count, s.file_count) * 100))) + print() + print('\ttotal \t{:5,} / {:5,} \t{:6.2f}%'.format(int(stats.total_completed_file_count), len(stats.source_files), (percent_complete(stats.total_completed_file_count, len(stats.source_files)) * 100))) + print('__________________________________________________________________') + print() + + print('FUNCTIONS') + for s in stats.search_dirs: + d_completed_func_count = s.function_count - s.nonmatching_count + print('\t{:12}\t{:7,} / {:7,} \t{:6.2f}%'.format(s.path, int(d_completed_func_count), int(s.function_count), (percent_complete(d_completed_func_count, s.function_count) * 100))) + print() + total_completed_func_count = stats.total_function_count - stats.total_nonmatching_count + print('\ttotal \t{:7,} / {:7,} \t{:6.2f}%'.format(int(total_completed_func_count), int(stats.total_function_count), (percent_complete(total_completed_func_count, stats.total_function_count) * 100))) + print('__________________________________________________________________') + print() + + print('BYTES') + for s in stats.search_dirs: + d_completed_byte_count = s.function_byte_count - s.nonmatching_byte_count + print('\t{:12}\t{:11,} / {:11,} \t{:6.2f}%'.format(s.path, int(d_completed_byte_count), int(s.function_byte_count), (percent_complete(d_completed_byte_count, s.function_byte_count) * 100))) + print() + completed_byte_count = stats.total_function_byte_count - stats.total_nonmatching_byte_count + print('\ttotal \t{:11,} / {:11,} \t{:6.2f}%'.format(int(completed_byte_count), int(stats.total_function_byte_count), (percent_complete(completed_byte_count, stats.total_function_byte_count) * 100))) + print('__________________________________________________________________') + print() + + + +def print_non_matching_stats(stats: StatResults, version): + """ + List all non matching functions. Sort by byte length. + Print as csv of filename, function name, function length. + """ + functions = [] + for file in stats.source_files: + for func in file.all_functions: + if func.nonmatching: + functions.append(func) + functions.sort(key=lambda x: x.length, reverse=True) + try: + print("filename, function, length") + for f in functions: + print(f.parent.path + ", " + f.name + ", " + str(f.length)) + except socket.error as e: + if e.errno == errno.EPIPE: + sys.exit(0) + raise + + +def bytes_to_words(b): + return (int(b) >> 2) << 2 + + +def generate_report(stats: StatResults, version): + """ + Send values over to the c report app. + Arguments must be added in the following order: + + note: changing from "libultra" to "libultrare" + + SRC_DIR: int, size in 32-bit words of completed functions, in src/ directory + SRC_DIR_MAX: int, size in 32-bit words of all tracked functions, in src/ directory + GAME_DIR: int, size in 32-bit words of completed functions, in src/game/ directory + GAME_DIR_MAX: int, size in 32-bit words of all tracked functions, in src/game/ directory + INFLATE_DIR: int, size in 32-bit words of completed functions, in src/inflate/ directory + INFLATE_DIR_MAX: int, size in 32-bit words of all tracked functions, in src/inflate/ directory + LIBULTRA_DIR: int, size in 32-bit words of completed functions, in src/libultrare/ directory + LIBULTRA_DIR_MAX: int, size in 32-bit words of all tracked functions, in src/libultrare/ directory + DECOMPILED_WORDS: int, size in 32-bit words of all completed functions + DECOMPILED_WORDS_MAX: int, size in 32-bit words of all tracked functions + DECOMPILED_FILES: int, count of all fully completed files + DECOMPILED_FILES_MAX: int, count of all tracked files + HTML_TEMPLATE: string: path to html template input file + HTML_OUTPUT: string: path to generated html file + LAST_MODIFIED_FILE: string: name of last modified file + LOG_LEVEL: int (enum): LOG_MIN = 0, LOG_DEF, LOG_MAX + + """ + + sd = next((x for x in stats.search_dirs if x.path == 'src'), None) + if sd is None: + raise ValueError('Could not find "src" SearchDir') + src_dir = bytes_to_words(sd.function_byte_count - sd.nonmatching_byte_count) + src_dir_max = bytes_to_words(sd.function_byte_count) + + sd = next((x for x in stats.search_dirs if x.path == 'src/game'), None) + if sd is None: + raise ValueError('Could not find "src/game" SearchDir') + game_dir = bytes_to_words(sd.function_byte_count - sd.nonmatching_byte_count) + game_dir_max = bytes_to_words(sd.function_byte_count) + + sd = next((x for x in stats.search_dirs if x.path == 'src/inflate'), None) + if sd is None: + raise ValueError('Could not find "src/inflate" SearchDir') + inflate_dir = bytes_to_words(sd.function_byte_count - sd.nonmatching_byte_count) + inflate_dir_max = bytes_to_words(sd.function_byte_count) + + # note: libultrare + sd = next((x for x in stats.search_dirs if x.path == 'src/libultrare'), None) + if sd is None: + raise ValueError('Could not find "src/libultrare" SearchDir') + libultrare_dir = bytes_to_words(sd.function_byte_count - sd.nonmatching_byte_count) + libultrare_dir_max = bytes_to_words(sd.function_byte_count) + + decompiled_words = bytes_to_words(stats.total_function_byte_count - stats.total_nonmatching_byte_count) + decompiled_words_max = bytes_to_words(stats.total_function_byte_count) + + decompiled_files = stats.total_completed_file_count + decompiled_files_max = len(stats.source_files) + + if version == 'us': + html_output = __report_out_us + elif version == 'jp': + html_output = __report_out_jp + elif version == 'eu': + html_output = __report_out_eu + else: + print('fatal: version', version, 'not supported! Supported versions are: ', ', '.join(__supported_versions)) + sys.exit(2) + + printstring = __report_bin + ' ' + \ + str(src_dir) + ' ' + \ + str(src_dir_max) + ' ' + \ + str(game_dir) + ' ' + \ + str(game_dir_max) + ' ' + \ + str(inflate_dir) + ' ' + \ + str(inflate_dir_max) + ' ' + \ + str(libultrare_dir) + ' ' + \ + str(libultrare_dir_max) + ' ' + \ + str(decompiled_words) + ' ' + \ + str(decompiled_words_max) + ' ' + \ + str(decompiled_files) + ' ' + \ + str(decompiled_files_max) + ' ' + \ + __report_template + ' ' + \ + html_output + ' ' + \ + stats.last_modified_file.path + ' ' + \ + '0' + + subprocess.Popen(printstring.split()) + + +def print_help(): + print('Usage: python3 ge-stats2.py [OPTION]') + print('Generate Decompilation Stats for Goldeneye 007') + print('Example: python3 ge-stats2.py --version us') + print(' ') + print(' This script expects to be run from the root directory. There must be') + print(' a map file in ./build/u/ge007.u.map (or equivalent version). Source files') + print(' must be located in a folder in the root directory.') + print(' ') + print(' This script does not attempt to parse preprocessor definitions and') + print(' ignores version differences in source files. For the purposes of these') + print(' stats, any version with an asm definition of a function will count') + print(' against completion. However, a version is still required to specify') + print(' which build map to reference against.') + print(' ') + print('Options:') + print(' -v, --version=CODE generate decomp stats for version CODE (us, jp, or eu). Default is us') + print(' -r, --report generate html report using report tool') + print(' -n, --non_matching print csv of all non matching function definitions') + print(' --mtime_os use OS last modified time instead of git log') + print(' -h, --help display this help text and exit') + print(' --qlocal quiet "local" version. Disable all the extra print statements included for github actions.') + +def main(): + # default to US version + version = 'us' + run_report = False + print_method = 'default' + mtime_use_os = False + qlocal = False + + if (len(sys.argv) > 1): + arguments, values = getopt.getopt(sys.argv[1:], "hv:rn", ["help", "version=", "report", "non_matching", "mtime_os", "qlocal"]) + + for current_argument, current_value in arguments: + if current_argument in ("-h", "--help"): + print_help() + sys.exit() + elif current_argument in ("-v", "--version"): + version = current_value.lower() + elif current_argument in ("-r", "--report"): + run_report = True + elif current_argument in ("-n", "--non_matching"): + print_method = 'non_matching' + elif current_argument in ("--mtime_os"): + mtime_use_os = True + elif current_argument in ("--qlocal"): + qlocal = True + + if version not in __supported_versions: + print('fatal: version', version, 'not supported! Supported versions are: ', ', '.join(__supported_versions)) + sys.exit(2) + + if run_report: + if not os.path.isfile(__report_bin): + print('fatal: file not found: ' + __report_bin) + sys.exit(6) + + # if this is just getting status on nonmatching files, ignore modified time lookup. + if print_method == 'non_matching' and not run_report: + mtime_use_os = True + + #if not qlocal: + # print(subprocess.run(['git', 'diff', 'origin/master', '--name-only', '"@{10 minutes ago}"']).stdout) + # Default to using git log to get the file's modified date. + # Git log will be much slower, but cloning a new repo (i.e., github actions online) + # will reset all the modified timestamps to the same value, so will need to + # resolve the last modified time from git history. + mtime_resolver = mtime_os + if not mtime_use_os: + if os.path.isdir('.git'): + mtime_resolver = mtime_git + else: + # ok, do one last check (for github actions) + try: + result = subprocess.run(['git', 'log', '-1', '--format=\"%ct\"', '--', 'readme.md'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + if result.returncode == 0: + mtime_resolver = mtime_git + except: + pass + + # files to count as complete, in src/ directory + src_completed_list = [ + '_start.s', + 'aspboot.s', + 'boot.s', + 'bootcode.s', + 'getra.s', + 'gspboot.s', + 'osMapTLB.s', + 'random.s', + 'rom_header.s', + 'rspboot.s', + 'tlb_random.s', + 'tlb_hardwire.s', + 'tlb_resolve.s'] + + # files to count as complete, in src/libultrare directory + src_libultrare_completed_list = [ + 'ultra80069080.s'] + + # files to count as complete, in src/game directory + # These are intentionally kept in assembly. + src_game_completed_list = [ + 'sub_GAME_7F01D1C0.s', + 'chrObjRandom.s', + 'math_sincos.s'] + + search = [] + search.append(SearchDir('src', False, completed=src_completed_list)) + search.append(SearchDir('src/game', False, completed=src_game_completed_list)) + search.append(SearchDir('src/inflate', False)) + search.append(SearchDir('src/libultrare', True, completed=src_libultrare_completed_list)) + + stats = StatResults() + + process_source_files(search, stats, mtime_resolver, qlocal) + apply_build_map(stats, version) + generate_default_stats(stats) + + if print_method == 'default': + print_default_stats(stats, version) + elif print_method == 'non_matching': + print_non_matching_stats(stats, version) + + if run_report: + generate_report(stats, version) + +if __name__ == '__main__': + #start_time = time.time() + main() + #print("--- %s seconds ---" % (time.time() - start_time)) diff --git a/tools_pc/d69_emit.py b/tools_pc/d69_emit.py index 9566773e..8b40432b 100644 --- a/tools_pc/d69_emit.py +++ b/tools_pc/d69_emit.py @@ -3,7 +3,7 @@ to PC layout and write a concatenated sidecar image + manifest.csv (same 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): header (0x40B, only words 0-4 consulted): word0 must be 0 (bswap32, diff --git a/tools_pc/d88_emit.py b/tools_pc/d88_emit.py index 855fcd47..9448ef42 100644 --- a/tools_pc/d88_emit.py +++ b/tools_pc/d88_emit.py @@ -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 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, 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 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 table, AI opcode streams, pad/boundpad `plink` name strings, pad/boundpad 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 correctly in the emitted file for some levels (Aztec/Bunker2/Surface2) -- 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 / PathRecord.waypoints target arrays: plain `s32` ID lists (NOT file offsets -- graph node indices), NULL/-1 terminated, walked with real diff --git a/tools_pc/framediff.py b/tools_pc/framediff.py index d4d5d394..bd6fdcc6 100644 --- a/tools_pc/framediff.py +++ b/tools_pc/framediff.py @@ -6,7 +6,7 @@ Compares a directory of candidate frames against a golden baseline 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 ticks per rendered frame (src/game/frametiming.c waitForNextFrame / boss.c mainTickElapsed gate driven by osGetCount(), which the port maps