Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66e38e27d7 |
No files matched your search
@@ -7,8 +7,9 @@ body:
|
||||
value: |
|
||||
Thanks for filing a report. First, please check:
|
||||
|
||||
- Known open issues (rendering, audio, platform) are tracked in
|
||||
[`docs/ROADMAP.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/ROADMAP.md).
|
||||
- The port is **Phase 2 of 4** — rendering is incomplete and many
|
||||
cosmetic defects are already tracked in
|
||||
[`docs/dev/GRAPHICS-BACKLOG.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/dev/GRAPHICS-BACKLOG.md).
|
||||
Search open issues and that file before filing.
|
||||
- Only the **Windows / MSYS2 MINGW64** build is tested. Linux and
|
||||
macOS are best-effort — build breaks there are expected.
|
||||
|
||||
@@ -4,7 +4,7 @@ contact_links:
|
||||
url: https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/building.md
|
||||
about: Read this first for build/extraction problems — most are covered here.
|
||||
- name: Known rendering defects
|
||||
url: https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/ROADMAP.md
|
||||
url: https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/dev/GRAPHICS-BACKLOG.md
|
||||
about: Cosmetic / rendering issues already known and tracked. Check before filing.
|
||||
- name: Security vulnerability
|
||||
url: https://github.com/jkdansereau/goldeneye-pc-port/security/advisories/new
|
||||
|
||||
@@ -1,66 +1,12 @@
|
||||
# Security Policy
|
||||
|
||||
This project is a non-commercial, fan-made research port. It has no server
|
||||
component and no telemetry, and no networking unless you turn on the opt-in
|
||||
update check (`Game.CheckUpdates`, off by default), which makes one HTTPS
|
||||
request to GitHub's releases API per launch (GitHub sees that request and
|
||||
your IP, as with any web request). On Windows it loads the system
|
||||
`winhttp.dll` from System32 only then; on Linux/Steam Deck it runs the
|
||||
system `curl`. It never downloads or installs anything. Each release ships
|
||||
prebuilt Windows and Linux binaries, but never a ROM or any game asset — you
|
||||
supply those yourself from a copy you legally own. The realistic security
|
||||
surface is:
|
||||
component and ships no binaries. The realistic security surface is:
|
||||
|
||||
- the PC port executable parsing your own ROM and asset files at load time
|
||||
(a malformed ROM/asset could in principle crash it or worse), and
|
||||
- the build/extraction scripts and CI workflow.
|
||||
|
||||
A full walkthrough of what a release actually installs on your machine, and
|
||||
what a from-source build pulls in, is in
|
||||
[`docs/security.md`](../docs/security.md).
|
||||
|
||||
### Why the binaries are unsigned
|
||||
|
||||
The Windows `.exe` and the Linux build are **not code-signed** — this is a
|
||||
free, single-developer, non-commercial hobby project with no code-signing
|
||||
budget, not a sign of anything wrong with the binary. Two things you may
|
||||
run into as a result:
|
||||
|
||||
- **Windows SmartScreen** may show "Windows protected your PC" on first
|
||||
run. Click "More info" → "Run anyway."
|
||||
- Some antivirus engines flag `ge007-convert.exe` (the one-time ROM-asset
|
||||
converter bundled in the release) specifically, because it's packaged
|
||||
with PyInstaller's `--onefile` mode — a well-known trigger for heuristic
|
||||
AV engines industry-wide, unrelated to what this project's code actually
|
||||
does. The game launches this converter itself on the first run, when the
|
||||
derived asset folders are missing, so an AV prompt can appear at that
|
||||
moment rather than when you unpack the release. If you want to verify what
|
||||
it does, its full source is `prepare-assets.py`, shipped right next to it in
|
||||
the same bundle folder. The build pins the PyInstaller version and the CI
|
||||
actions it uses (by commit SHA), so what ships is reproducible from this
|
||||
repository.
|
||||
|
||||
### Verifying a release build (build provenance)
|
||||
|
||||
From **v0.5.0** on, the tag-only CI job attaches an in-toto SLSA
|
||||
build-provenance attestation to each release artifact (the zip/tarball and
|
||||
its `.sha256` sidecars). The attestation is signed by GitHub's OIDC key for
|
||||
*this repository's* workflow running the tagged commit. It does not replace a
|
||||
code signature — but it proves that the exact file you downloaded was produced
|
||||
by this repo's CI from the release tag, and nothing else. Verify any
|
||||
downloaded artifact with:
|
||||
|
||||
```sh
|
||||
gh attestation verify goldeneye-pc-port-0.5.0-win64.zip \
|
||||
--repo jkdansereau/goldeneye-pc-port
|
||||
```
|
||||
|
||||
(use the name of the file you downloaded, e.g.
|
||||
`goldeneye-pc-port-0.5.0-linux-x86_64.tar.gz` on Linux). A passing check confirms both
|
||||
the file's digest and its provenance; a tampered or foreign file fails.
|
||||
Releases before v0.5.0 have no attestations — for those, the per-file
|
||||
`.sha256` sidecars and a from-source build are the available checks.
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
Please **do not** open a public issue for a security problem.
|
||||
@@ -92,6 +38,4 @@ errors, and anything requiring a ROM or assets we do not distribute.
|
||||
|
||||
## Supported versions
|
||||
|
||||
Only the tip of the default branch and the
|
||||
[latest release](https://github.com/jkdansereau/goldeneye-pc-port/releases/latest)
|
||||
are supported.
|
||||
Only the tip of the default branch is supported. There are no releases yet.
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
- name: rendering
|
||||
color: 5319e7
|
||||
description: Graphics / fast3d / RSP output defect (see docs/ROADMAP.md)
|
||||
description: Graphics / fast3d / RSP output defect (see docs/dev/GRAPHICS-BACKLOG.md)
|
||||
|
||||
- name: audio
|
||||
color: 1d76db
|
||||
|
||||
@@ -13,14 +13,9 @@ rules there are non-negotiable.
|
||||
|
||||
## Scope check
|
||||
|
||||
- [ ] No changes under `src/` or `include/` — **or** the only changes are
|
||||
the narrow `#ifdef PORT` ABI exception (CONTRIBUTING.md rule 2),
|
||||
checked against `docs/porting-notes.md` §A1, and documented in
|
||||
`docs/dev/findings.md` (§F/D3x).
|
||||
- [ ] If this PR contains a genuine `src/game` behavior change (not an
|
||||
ABI/layout fix): an explicit rule-2 sign-off was granted
|
||||
(`docs/dev-process.md` §7) and is recorded in a
|
||||
`RULE-2-SIGNOFF`-tagged `docs/dev/findings.md` entry linked here.
|
||||
- [ ] No changes under `src/` or `include/` — **or** the only changes are the
|
||||
narrow `#ifdef PORT` ABI exception (CONTRIBUTING.md rule 2), and each is
|
||||
documented in `docs/porting-notes.md` / `docs/dev/findings.md`.
|
||||
- [ ] `Makefile`, `tools/`, `rsp/`, `ld/` untouched (N64 build).
|
||||
- [ ] If `CMakeLists.txt` `REGION_DEFS` changed, it still matches the N64
|
||||
`Makefile` per-region macro set exactly.
|
||||
|
||||
@@ -1,294 +1,65 @@
|
||||
# GoldenEye 007 PC Port — v<version>
|
||||
## GoldenEye 007 PC Port — v0.1.0 alpha
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/jkdansereau/goldeneye-pc-port/raw/v<version>/docs/img/shots/feature-splitscreen.jpg" width="340" alt="2-player split-screen multiplayer in the port">
|
||||
<img src="https://github.com/jkdansereau/goldeneye-pc-port/raw/v<version>/docs/img/shots/feature-options.jpg" width="340" alt="The PC options menu: custom crosshair colour sliders">
|
||||
</p>
|
||||
|
||||
Platforms: **Windows x86-64** and **Linux x86-64 (including Steam Deck)**.
|
||||
Region: **NTSC-U (US) only**. See [Known issues](#known-issues).
|
||||
|
||||
## Highlights
|
||||
|
||||
This is the first release since v0.4.0. It includes everything that was
|
||||
staged as v0.4.1, which was never published on its own.
|
||||
|
||||
- **2-4 player split-screen.** The game's multiplayer runs with a pad per
|
||||
player, on every map, with each seat keeping its own controls.
|
||||
- **One options menu.** The file-select **PC Options** entry and the F10 overlay
|
||||
are now the same menu, laid out like the Perfect Dark PC port's, with
|
||||
checkboxes, sliders and dropdowns. The game's own crosshair is the mouse
|
||||
pointer, long pages scroll, and a tip explains the selected option.
|
||||
- **The game's N64 control styles, per player.** Pick **Original** to use the
|
||||
game's own styles 1.1-2.4; each seat keeps its own, saved with the Bond file.
|
||||
- **Emulator saves load directly.** Copy a Project64, mupen or 1964 save in as
|
||||
`data/ge007.eep` and it is converted on first launch.
|
||||
- **Fog and haze closer to the N64.** Ground fog no longer chunks away tile by
|
||||
tile, the sky fades into fog at the horizon, and the haze around Bond stays
|
||||
steady as he moves. A new **Fog distance** setting (100-800%, 100% = N64) is separate
|
||||
from Draw distance, and both apply live.
|
||||
- **Fixes you can see:** the Dam ending camera swivels onto Bond as on the N64,
|
||||
and rockets no longer pass
|
||||
through the ground.
|
||||
- **New settings:** an optional always-on crosshair, a custom
|
||||
crosshair colour (0-255 RGB, like the Perfect Dark port), fullscreen mode,
|
||||
center window, crosshair opacity and crosshair colour by health, a scalable
|
||||
HUD overlay, and an optional update check (off by default).
|
||||
|
||||
Played on Windows and Steam Deck: campaign spot-checks, 2-4 player split-screen
|
||||
and controller-only sessions.
|
||||
|
||||
---
|
||||
|
||||
## What's new
|
||||
|
||||
### Options menu
|
||||
|
||||
- Six sections in the Perfect Dark port's order: Video, Audio, Mouse,
|
||||
Controller, Key Bindings and Game. **Display mode** is a dropdown; **Backspace** goes back.
|
||||
- The F10 overlay and FPS counter are the same on-screen size in the front end
|
||||
and in a level, and **Game.HudScale** scales them with the in-game HUD.
|
||||
Sliders and arrows are drawn smooth at any window size.
|
||||
- The OS cursor stays visible over the overlay (no main-menu flicker),
|
||||
right-click goes back, and the tip follows the hovered row.
|
||||
- Clearer wording: 27 menu-wording edits, sliders with real units (percent,
|
||||
frames, seconds, pixels), wrapped tips, one `Key: Action` hint style and
|
||||
consistent names (Original / Extended, "Original layout"). *Skip intro* and
|
||||
*All unlocked* are no longer marked experimental, and *All unlocked* applies
|
||||
live. The game's own *Look up/down* row is gone; use *Invert look* on the
|
||||
Mouse and Controller pages.
|
||||
|
||||
### Controls
|
||||
|
||||
- **Control style:** **Ext** (this port's scheme, unchanged) or **Original**
|
||||
(the game's N64 styles 1.1-2.4), chosen per seat.
|
||||
- The Xbox-release presets (1.1 Jinx, 1.2 Christmas, 1.3 Frost, 1.4 Elektra)
|
||||
and Custom rebinding carry over. Preset names refer to the Xbox
|
||||
release's control styles; this project is not affiliated with Microsoft or Rare.
|
||||
- The right stick aims and the D-pad strafes, as on the N64; the Xbox
|
||||
release's "left stick aims the crosshair" and "D-pad copies the left stick"
|
||||
behaviours are not reproduced.
|
||||
- New profiles default to Crouch Toggle and Look ahead off (it fights mouse look
|
||||
on PC). Existing untouched empty save folders get the same on first play;
|
||||
played or customised profiles are not changed.
|
||||
|
||||
### Saves
|
||||
|
||||
- An emulator-format `data/ge007.eep` is converted on first launch (the
|
||||
original is kept as `ge007.eep.emulator.bak`). The converter `eep_convert.py` still does explicit two-way conversion.
|
||||
(Live playtest owed.)
|
||||
|
||||
### Picture and settings
|
||||
|
||||
- Fog and near-plane clipping closer to the N64 (details under the technical
|
||||
section below). Forced aspect ratios letterbox/pillarbox without re-cropping
|
||||
the play area.
|
||||
- Draw and LOD distance default to 2.0x (N64 = 1.0x), stick deadzone to 25%,
|
||||
mouse sensitivity maximum is 4x. **FOV** is vertical degrees, 30-90, with
|
||||
60 = the N64 view. "Guard AI uses full wide view" is now **Gameplay view
|
||||
area** (Original / Extended). Old values migrate once; existing `ge007.ini`
|
||||
files still load.
|
||||
|
||||
### Window and updates
|
||||
|
||||
- The title bar is static (the FPS readout is still in the F10 overlay) and
|
||||
the window/taskbar icon is a new project mark.
|
||||
- **Check for updates** (Game, **off by default**): when on, one HTTPS request
|
||||
to GitHub per launch and an "Update available" row linking to the releases page.
|
||||
|
||||
### Also new since v0.4.0
|
||||
|
||||
- **Split-screen multiplayer** for 2-4 players, played live in 2P on every
|
||||
multiplayer map and in 4P on Temple.
|
||||
- Controller presets and rebinding, PlayStation and Nintendo button names,
|
||||
master volume and audio-device selection.
|
||||
- A steady 60 fps on low-end GPUs (tested on an Intel HD 3000 laptop).
|
||||
- Fidelity fixes checked against the N64 game: AI visibility at long draw
|
||||
distances, sniper zoom, turret pacing, Dam/Caverns water, weapons.
|
||||
- Fixes for reported issues: fire rate (#114), the tank-crush sound loop
|
||||
(#115), tank movement (#116), weapon sway (#117), recoil on some GPUs (#118)
|
||||
and light fixtures that didn't react to shots (#119).
|
||||
|
||||
---
|
||||
|
||||
## Install and upgrade
|
||||
> ⚠️ **Research alpha. Playable, not polished.**
|
||||
> The full single-player campaign runs. In a full-campaign playtest on this
|
||||
> build, **19 of 21 missions were completable start to finish**. Known issues:
|
||||
> - **no audio** (Phase 3, not started) — the game runs silent;
|
||||
> - **two levels crash mid-mission** — Bunker ii and Statue (one root cause, D191);
|
||||
> - **AI characters move too slowly**, which breaks Cradle (the final level)
|
||||
> via Trevelyan's scripted behavior (D193);
|
||||
> - **cutscenes frequently glitch** — skipped, wrong camera, misplaced actors (D148/D160);
|
||||
> - some front-end 3D models are broken (the spinning Nintendo logo, the
|
||||
> MISSION COMPLETE / mode-select models);
|
||||
> - outdoor levels render with a **black sky**;
|
||||
> - mouse aim and some textures/transparency have rough edges.
|
||||
>
|
||||
> If you just want to *play* GoldenEye on PC today, use one of the Xbox 360
|
||||
> recompilation projects instead — see the
|
||||
> [project README](https://github.com/jkdansereau/goldeneye-pc-port#how-this-differs-from-the-other-goldeneye-pc-projects).
|
||||
|
||||
### Downloads
|
||||
|
||||
| File | Platform |
|
||||
|---|---|
|
||||
| `goldeneye-pc-port-<version>-win64.zip` | Windows x86-64 |
|
||||
| `goldeneye-pc-port-<version>-linux-x86_64.tar.gz` | Linux x86-64 (incl. Steam Deck) |
|
||||
| `goldeneye-pc-port-<version>-linux-x86_64.tar.gz` | Linux x86-64 |
|
||||
|
||||
Each contains the engine executable, a README, license texts, and the
|
||||
one-time asset tool. **No ROM, no game assets.** The Windows bundle carries
|
||||
its runtime DLLs and the Linux bundle carries SDL2, so nothing needs to be
|
||||
installed first. The Linux bundle runs on glibc 2.31 or newer (Ubuntu 20.04+,
|
||||
Debian 11+, SteamOS).
|
||||
`prepare-assets` tool. **No ROM, no game assets.** The Windows bundle also
|
||||
carries its runtime DLLs; the Linux bundle links against your distro's SDL2 /
|
||||
zlib / libGL (`sudo apt install libsdl2-2.0-0 zlib1g libgl1`, or the equivalent).
|
||||
|
||||
Windows development and playtesting is the primary path; the Linux build boots
|
||||
and renders (tested on WSLg) but has had far less exercise.
|
||||
|
||||
### Running it
|
||||
|
||||
You need your own **GoldenEye 007 N64 ROM** (US version, `.z64`).
|
||||
You supply your own **NTSC-U GoldenEye 007 N64 ROM** (`.z64`, big-endian,
|
||||
`SHA-1 abe01e4aeb033b6c0836819f549c791b26cfde83`). Only the US ROM is supported
|
||||
in this alpha.
|
||||
|
||||
1. Unpack the archive.
|
||||
2. Make a `data/` folder next to the executable and put the ROM in it, named
|
||||
2. Make a `data/` folder next to the executable and put the ROM in it as
|
||||
`ge007.ntsc-final.z64`.
|
||||
3. Run the executable from that folder. The first start takes a few extra
|
||||
seconds while it generates its asset files from your ROM.
|
||||
3. Run the one-time asset step (needs Python 3.8+):
|
||||
`python3 prepare-assets/prepare-assets.py`
|
||||
— it reads your ROM and writes the two `data/pc*-ntsc-final/` folders the
|
||||
engine needs. Standard library only; a few seconds.
|
||||
4. Run the executable **from that folder**.
|
||||
|
||||
**Updating from an earlier version:** unpack into a new folder and copy your
|
||||
ROM, `ge007.eep` (your progress) and `ge007.ini` (your settings) into its
|
||||
`data/` folder. Don't copy the old `pcmodels-*` / `pccg-*` folders: the new
|
||||
version makes its own.
|
||||
Full steps are in the bundled `README.md`.
|
||||
|
||||
**Steam Deck:** do the steps above on the Deck, then add the executable as a
|
||||
non-Steam game. The options overlay opens with **Select** and is fully
|
||||
controller-driven.
|
||||
|
||||
Full steps, controls and troubleshooting are in the bundled `README.md`.
|
||||
|
||||
---
|
||||
|
||||
## Known issues
|
||||
|
||||
| Issue | Impact | Workaround |
|
||||
|---|---|---|
|
||||
| PAL and JP ROMs aren't supported in release packages | NTSC-U only | Use an NTSC-U ROM. Both regions convert, build and boot from source; packaging is still open |
|
||||
| Changing aspect ratio inside a level can briefly glitch the gun/hand model, rarely | Cosmetic, one-off | Change the ratio from the front-end PC Options, or accept it |
|
||||
| No macOS or ARM builds | Platform | — |
|
||||
| Saves from v0.4.0 and earlier can hold fake unlocks from `All unlocked` | Save data | Not repaired automatically. Since v0.5.0 the option never writes the save; keep a backup of `data/ge007.eep` from before you used it |
|
||||
| The first frame of a level takes a little longer while its textures upload | Brief FPS-counter dip | None needed |
|
||||
| Far objects almost fully in fog are now hidden as on the N64, except in widescreen where a faint distant building edge can still show (e.g. Surface's dish from the start area); 4:3 matches the N64 | Cosmetic | Higher Draw/LOD distance shows more |
|
||||
|
||||
The full list, with workarounds, is the
|
||||
[known-issues table](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/ROADMAP.md#known-issues).
|
||||
|
||||
---
|
||||
|
||||
## Fixes and changes, with finding labels
|
||||
|
||||
Menu and settings work:
|
||||
|
||||
- One options UI: the file-select entry opens the F10 menu (D519). Six
|
||||
PD-ordered sections with checkboxes, sliders and dropdowns (D504); display
|
||||
mode dropdown and Backspace-back (D504 follow-up).
|
||||
- Control styles: Ext / Original with the N64 styles 1.1-2.4 (D513, D516),
|
||||
chosen per seat and saved with the Bond file (D518, D516).
|
||||
- Emulator `ge007.eep` import (D514).
|
||||
- F10 overlay and FPS counter match in size front-end vs level (D510); HudScale
|
||||
covers them (D512).
|
||||
- Fullscreen mode, Center window, crosshair opacity and crosshair colour by
|
||||
health: the four settings the Perfect Dark port had that this port lacked (D511).
|
||||
- Wording, units, tips and spelling: D505, D506, D507; settings wording pass
|
||||
and tip wrapping (D554); Look up/down row removed (D564).
|
||||
- Settings standardised: defaults, FOV in vertical degrees, Gameplay view area (D546).
|
||||
- Optional update check (D551). All unlocked applies live (D547).
|
||||
- Crosshair mouse pointer and scrollbar (D555, D556); long tips no longer cut
|
||||
off inside a level (D558). New defaults: Crouch Toggle, Look ahead off (D556,
|
||||
D557); same for untouched empty save folders (D559).
|
||||
- Crosshair always on, opt-in, off by default (D436, #123 by dolent). Crosshair colour is
|
||||
Original or Custom RGB with 0-255 sliders, as in the Perfect Dark port; a
|
||||
saved named preset carries over as Custom (D569). Long dropdowns stay on
|
||||
screen (D568).
|
||||
- F10 overlay mouse behaviour (D544). Static title bar and new icon (D550).
|
||||
- Overlay slider wedges, markers and dropdown arrows used to step in whole
|
||||
canvas pixels and grew visibly at large window sizes; now smooth (D520).
|
||||
Holding left/right on a Bond settings row no longer drops the FPS counter (D517).
|
||||
- Watch menu: closing the in-level **Watch** menu saves the watch's own screen
|
||||
size and ratio, which no longer fight the front-end **Aspect ratio** setting;
|
||||
the earlier findings-log note was corrected (D349).
|
||||
|
||||
Rendering and gameplay fidelity:
|
||||
|
||||
- **Dam ending camera:** the first shot of the ending cutscene swivels up onto
|
||||
Bond again, as on the N64; an old port workaround snapped it straight onto
|
||||
him (D552).
|
||||
- **Fog rework (Surface 2, from Deck playtest):** ground fog no longer "chunks"
|
||||
away tile by tile, and the sky fades into fog toward the horizon as on the
|
||||
N64 (D540). Large near-camera triangles are now clipped against the near
|
||||
plane the way the N64's RSP does, with per-vertex fog recomputed on the
|
||||
clipped edge, so the haze around Bond stays steady as he moves instead of
|
||||
brightening and darkening (D543, D553; compared against hardware-level
|
||||
emulation). A new **Fog distance** setting (100% = N64) is independent of
|
||||
Draw distance, and Draw and Fog distance now apply live instead of on the
|
||||
next level load.
|
||||
- **Banded walls near explosions:** in Aztec's dark corridor, walls near rocket
|
||||
fire turned into black/white/yellow/blue bands (also seen on the Deck); fixed
|
||||
by clamping colour-combiner inputs to the range the N64 can produce (D548).
|
||||
- **Rocket and thrown-item crashes/clipping:** a player-fired rocket could pass
|
||||
through the ground when fired near Bond's feet (a stack-layout difference
|
||||
from the N64, D545), and the same class of bug is fixed in thrown grenades,
|
||||
knives and objects (D549).
|
||||
- **Menu double-trigger (Steam Deck):** one A press could advance two menu
|
||||
screens, and one B could back out two overlay pages. A short release
|
||||
hold-off on menu A/B/Start and a single back action per press are now in
|
||||
place. This is a mitigation: the exact cause is unconfirmed (the Deck
|
||||
re-check passed) (D541).
|
||||
- **Fog snap:** objects far in fog are hidden as on the N64; in widescreen
|
||||
the wider view can still show a faint distant building edge (e.g. Surface's
|
||||
dish from the start area); 4:3 matches the N64 (D503).
|
||||
- **Aspect ratio:** forced ratios letterbox/pillarbox without re-cropping the
|
||||
play area (D508); the rare gun/hand model glitch after changing ratio
|
||||
mid-level is largely fixed, a rare remainder is logged (D509).
|
||||
|
||||
Build, tooling and harness:
|
||||
|
||||
- A fresh build directory no longer produces an untagged executable name (D515).
|
||||
- The asset converter's argument quoting and sidecar size check are
|
||||
tightened; the CI cache actions no longer carry stale ROM paths.
|
||||
- The reference-frame tooling is non-destructive and drift-free: a verification
|
||||
sweep used to overwrite your own `data/ge007.ini` and leave the previous
|
||||
level's save file behind, and now pins both idempotently (your ini and save
|
||||
are restored whatever the run does); the capture helper reads its frame
|
||||
windows out of the gate instead of a stale hardcoded copy (D524). The gate's
|
||||
recipe was re-based: per-level windows in settled gameplay rather than the
|
||||
intro flyby, and the save file pinned present (D522, D523).
|
||||
- Debug env vars are documented: `GE_PCDUMP` must be `first-last:step` (a colon
|
||||
where the dash belongs silently dumps every frame, D527), and a
|
||||
`GE_STARTMENU` boot skips the EEPROM import, so the imported-save test must
|
||||
run from a normal boot (D528). A per-triangle draw-state census probe
|
||||
(`GE_D526`/`GE_D526BOX`/`GE_D526MAX`) is available for transparency/texture
|
||||
triage (D526; the P13 Dam-ending grate show-through it targeted was confirmed
|
||||
faithful N64 behaviour on 1964/GEPD, so no change shipped).
|
||||
|
||||
## Verification
|
||||
|
||||
**Reference-frame gate for this release:** Windows, Linux and Steam Deck each
|
||||
carry the full 21-level reference-frame set (63 frames each) at the same stems,
|
||||
re-captured for this release's fog and near-plane changes and confirmed by two
|
||||
independent capture passes per platform. The full pixel gate is green on all
|
||||
three against their own goldens on the final build: Windows 21/21, Linux
|
||||
(Intel HD 3000) 21/21 and Steam Deck 21/21. The recipe pins the run for you: the
|
||||
save file's CONTENT is pinned (the gate installs its own canonical save per
|
||||
level, the same way it pins the display ini — D529) and the PRNG seed is
|
||||
hard-pinned (a stray `GE_RSEED` in your environment warns and is ignored rather
|
||||
than re-seeding the gate — D532), so a gate run and a capture run see the same
|
||||
starting state. The cross-platform spread on the shipped sets (0.188-4.553% of
|
||||
pixels over tol 2 on the 20 non-Cuba levels) is informational only — it is not
|
||||
a cross-platform pixel-parity claim.
|
||||
|
||||
## Thanks
|
||||
|
||||
Outside contributions merged for this release: dolent (PRs #120–#123), MST246
|
||||
(#125 draw-distance investigation), italoarruda (#109 gamepad-preset ideas),
|
||||
TenebrusoM (DexDrive / the D514 save-format sample), plus reporter credits on
|
||||
the issue numbers named above.
|
||||
|
||||
## Verify the download
|
||||
### Verify the download
|
||||
|
||||
```
|
||||
sha256sum -c goldeneye-pc-port-<version>-win64.zip.sha256
|
||||
sha256sum -c goldeneye-pc-port-<version>-linux-x86_64.tar.gz.sha256
|
||||
```
|
||||
|
||||
## Source & docs
|
||||
### Source & docs
|
||||
|
||||
<https://github.com/jkdansereau/goldeneye-pc-port>, built on the
|
||||
[GoldenEye 007 decompilation](https://github.com/n64decomp/007), with its
|
||||
architecture modelled on the [Perfect Dark PC port](https://github.com/fgsfdsfgs/perfect_dark).
|
||||
<https://github.com/jkdansereau/goldeneye-pc-port> — built on the
|
||||
[GoldenEye 007 decompilation](https://github.com/n64decomp/007), architecture
|
||||
after the [Perfect Dark PC port](https://github.com/fgsfdsfgs/perfect_dark).
|
||||
Non-commercial fan preservation/research project; not affiliated with any
|
||||
rights holder. **AI disclosure:** built through agentic AI coding (Claude
|
||||
Code + a local open-weight model), directed by one person in their spare
|
||||
time. See the README's
|
||||
[How it was made](https://github.com/jkdansereau/goldeneye-pc-port#how-it-was-made)
|
||||
section for the full account.
|
||||
rights holder.
|
||||
@@ -55,9 +55,11 @@
|
||||
.tab span { writing-mode:vertical-rl; text-orientation:mixed;
|
||||
font-family:'Arimo',sans-serif; font-weight:700; font-size:15px;
|
||||
letter-spacing:4px; color:#3b3b33; transform:rotate(180deg); }
|
||||
/* the project mark: in-game default crosshair proportions (ring outer r 18,
|
||||
bars reach ~1.43x r with an open centre ~0.24x r); see tools_pc/make_icon.py */
|
||||
.reticle { position:absolute; right:30px; top:242px; width:52px; height:52px; z-index:5; }
|
||||
.reticle { position:absolute; right:34px; top:246px; width:44px; height:44px; z-index:5; }
|
||||
.reticle::before, .reticle::after { content:""; position:absolute; background:#c62828; }
|
||||
.reticle::before { left:50%; top:-6px; width:3px; height:56px; margin-left:-1.5px; }
|
||||
.reticle::after { top:50%; left:-6px; height:3px; width:56px; margin-top:-1.5px; }
|
||||
.reticle i { position:absolute; inset:4px; border:3px solid #c62828; border-radius:50%; }
|
||||
|
||||
/* the parchment card */
|
||||
.card { position:absolute; left:40px; top:40px; right:96px; bottom:40px;
|
||||
@@ -116,18 +118,18 @@
|
||||
<div class="h1">GOLDENEYE <span class="r">007</span><br><small>PC PORT</small></div>
|
||||
|
||||
<div class="meta">
|
||||
Mission: Native PC port of GoldenEye 007, compiled from the N64 decomp project
|
||||
Mission: Native PC port of GoldenEye, compiled from the N64 decomp project
|
||||
</div>
|
||||
|
||||
<div class="rep">REPORT:</div>
|
||||
|
||||
<div class="rows">
|
||||
<div class="statusline">Status: <span class="status">RELEASED v0.5.0 — WINDOWS · LINUX · STEAM DECK</span></div>
|
||||
<div class="statusline">Status: <span class="status">ALPHA v0.1.0 — PHASE 2 OF 4</span></div>
|
||||
<div class="stats">
|
||||
<span class="k">Statistics:</span><span>All 20 solo missions — full campaign completed in playtesting</span>
|
||||
<span></span><span>~210 game-code units, compiled unmodified</span>
|
||||
<span></span><span>~34k lines C/C++ · ~10k Python (port layer + tools)</span>
|
||||
<span></span><span>430 findings logged · AI coding agents + a handful of contributors</span>
|
||||
<span class="k">Statistics:</span><span>19 / 21 solo missions completable in a full playthrough</span>
|
||||
<span></span><span>~230 game-code units, compiled unmodified</span>
|
||||
<span></span><span>~17k lines C/C++ · ~6k Python (port layer + tools)</span>
|
||||
<span></span><span>~190 bugs root-caused · built in ~3 weeks, part-time</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -140,6 +142,6 @@
|
||||
<div class="tab"><span>NEXT</span></div>
|
||||
<div class="tab"><span>PREVIOUS</span></div>
|
||||
</div>
|
||||
<svg class="reticle" viewBox="-26 -26 52 52" aria-hidden="true"><g fill="none" stroke="#c62828" stroke-width="3"><circle r="16.5"/><path d="M0-26V-4.5M0 4.5V26M-26 0H-4.5M4.5 0H26"/></g></svg>
|
||||
<div class="reticle"><i></i></div>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 597 KiB After Width: | Height: | Size: 583 KiB |
@@ -66,31 +66,12 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
code: ${{ steps.filter.outputs.code }}
|
||||
# Windows builds default to the self-hosted box (fast, local ccache;
|
||||
# docs/dev/notes/SELFHOSTED-RUNNER-PLAN.md §G2) for everything EXCEPT:
|
||||
# (a) a release build (tag push) -- keep that on a clean, reproducible
|
||||
# GitHub-hosted runner for the artifact that actually ships, and
|
||||
# (b) a fork PR -- untrusted PR code must never reach a box that will
|
||||
# later hold a ROM. Same-repo (non-fork) PRs now also get routed to
|
||||
# self-hosted per the 2026-09-12 standing preference.
|
||||
windows_selfhosted: ${{ steps.runner.outputs.selfhosted }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- id: runner
|
||||
run: |
|
||||
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
|
||||
echo "selfhosted=false" >> "$GITHUB_OUTPUT"
|
||||
elif [ "${{ github.event_name }}" = "pull_request" ] && \
|
||||
[ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then
|
||||
echo "selfhosted=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "selfhosted=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- id: filter
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
@@ -120,7 +101,7 @@ jobs:
|
||||
if: needs.changes.outputs.code == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -132,26 +113,6 @@ jobs:
|
||||
- name: Byte-compile Python tooling
|
||||
run: python3 -m compileall -q scripts tools_pc
|
||||
|
||||
# Generated dev-docs stay in sync with the tree (speed-ups plan Step 4).
|
||||
- name: Check generated dev indexes are current
|
||||
run: |
|
||||
python3 tools_pc/gen_findings_index.py --check
|
||||
python3 tools_pc/gen_env_probes.py --check
|
||||
|
||||
# Docs budget guardrail (ctx-opt P2): warn, don't fail. The §F index
|
||||
# table should stay one-liners; fat cell content belongs in `## Dxx`
|
||||
# sections at the file tail (lookup path: docs/dev/findings-index.csv).
|
||||
- name: Check dev-docs size budgets (warn only)
|
||||
run: |
|
||||
# Budget = ~55 KB of table rows + ~110 KB of legacy D24–D33 / M-xxx
|
||||
# narrative that predates the ctx-opt migration. The signal is fat
|
||||
# ROWS re-accumulating, so the threshold sits ~15 KB above today's
|
||||
# 166 KB total.
|
||||
f=$(awk '/^### F\./{f=1;next} /^### G\./{f=0} f' docs/dev/findings.md | wc -c)
|
||||
[ "$f" -le 184320 ] || echo "WARNING: findings.md §F region is $f bytes (> 180 KB) — fat rows accumulating; migrate cell content to ## Dxx sections at the file tail"
|
||||
p=$(wc -c < docs/porting-notes.md)
|
||||
[ "$p" -le 122880 ] || echo "WARNING: porting-notes.md is $p bytes (> 120 KB) — consider splitting or adding a TOC block"
|
||||
|
||||
# US only. gen_romassets.py resolves every symbol for the US region from
|
||||
# the committed CSVs with no ROM present; PAL/JP each need a handful of
|
||||
# region-specific prop/font offsets that only come from that region's ROM
|
||||
@@ -171,91 +132,29 @@ jobs:
|
||||
# here is a "does it compile + link" signal only, not a runtime claim.
|
||||
# Not a required check yet; flip it to required once it is reliably green.
|
||||
#
|
||||
# Toolchain: Steam Runtime 3 "sniper" SDK container (Debian 11 base,
|
||||
# glibc 2.31) + its gcc-14. Building against the old glibc is what sets the
|
||||
# tarball's floor: the ubuntu-24.04 build needed glibc 2.38 (C23
|
||||
# __isoc23_* symbols), so it refused to start on Ubuntu 22.04 / Debian 12.
|
||||
# The image is pinned by version AND digest.
|
||||
# SDL2 is built from source (2.30.0, the version the 24.04 build bundled
|
||||
# and every Linux/Deck test used): the SDK's own libSDL2 is sdl2-compat,
|
||||
# which needs SDL3 at runtime. pip is bootstrapped from a hash-pinned
|
||||
# get-pip.py: Debian 11's security archive no longer serves python3-pip.
|
||||
# Toolchain: ubuntu-24.04 + gcc-14. The port is developed on MinGW GCC 16;
|
||||
# pinning a recent GCC here keeps the first bring-up focused on real
|
||||
# portability gaps rather than a decade of accumulated old-GCC pedantry.
|
||||
# (A "does it build on the GCC a typical distro ships" pass can come later.)
|
||||
linux-build:
|
||||
runs-on: ubuntu-24.04
|
||||
container:
|
||||
image: registry.gitlab.steamos.cloud/steamrt/sniper/sdk:3.0.20260805.254768@sha256:1c33c507bc75d012e77df5727f93b0d5b8c3f7c8d4142ba5f7a16882cc92e014
|
||||
# A container job's default `run` shell is sh, not bash (the packaging
|
||||
# step's ${GITHUB_SHA::7} is a bashism).
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
env:
|
||||
CC: gcc-14
|
||||
CXX: g++-14
|
||||
# CMakeLists.txt opt-in enables CMAKE_{C,CXX}_COMPILER_LAUNCHER=ccache
|
||||
# when ccache is on PATH (b4de1c68, speed-ups plan Step 5). Game
|
||||
# sources are frozen (1:1 with the decomp), so after the first run only
|
||||
# touched port/ files miss cache -- near-total hit rate. CCACHE_DIR is
|
||||
# set in a step: in a container job ${{ github.workspace }} is the
|
||||
# host path, not the container's.
|
||||
CCACHE_MAXSIZE: 500M
|
||||
SDL2_VERSION: 2.30.0
|
||||
SDL2_SHA256: 36e2e41557e0fa4a1519315c0f5958a87ccb27e25c51776beb6f1239526447b0
|
||||
GET_PIP_SHA256: 95c0ae79ccf9ac1e47e4187f49081d6ae1f45997afd4549e14f37337cbcfd766
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
echo "CCACHE_DIR=$GITHUB_WORKSPACE/.ccache" >> "$GITHUB_ENV"
|
||||
apt-get update
|
||||
apt-get install -y patchelf
|
||||
curl -fsSL -o /tmp/get-pip.py https://bootstrap.pypa.io/pip/3.9/get-pip.py
|
||||
echo "$GET_PIP_SHA256 /tmp/get-pip.py" | sha256sum -c
|
||||
python3 /tmp/get-pip.py --quiet
|
||||
|
||||
- name: Build SDL2 ${{ env.SDL2_VERSION }} from source
|
||||
run: |
|
||||
curl -fsSL -o /tmp/sdl2.tar.gz \
|
||||
"https://github.com/libsdl-org/SDL/releases/download/release-$SDL2_VERSION/SDL2-$SDL2_VERSION.tar.gz"
|
||||
echo "$SDL2_SHA256 /tmp/sdl2.tar.gz" | sha256sum -c
|
||||
tar xzf /tmp/sdl2.tar.gz -C /tmp
|
||||
cmake -S "/tmp/SDL2-$SDL2_VERSION" -B /tmp/sdl-build -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local -DSDL_STATIC=OFF -DSDL_TEST=OFF
|
||||
cmake --build /tmp/sdl-build -j"$(nproc)"
|
||||
cmake --install /tmp/sdl-build
|
||||
ldconfig
|
||||
|
||||
- name: Restore ccache
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: .ccache
|
||||
key: ccache-linux-sniper-${{ hashFiles('CMakeLists.txt') }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
ccache-linux-sniper-${{ hashFiles('CMakeLists.txt') }}-
|
||||
ccache-linux-sniper-
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential gcc-14 g++-14 cmake python3 \
|
||||
libsdl2-dev zlib1g-dev libgl1-mesa-dev
|
||||
|
||||
- name: Regenerate ROM-asset symbol file (US)
|
||||
run: python3 scripts/gen_romassets.py u
|
||||
|
||||
# Drop-in ROM support: freeze the asset-prep tool into a single
|
||||
# ge007-convert binary the engine spawns on first run when the
|
||||
# pcmodels/pccg sidecars are missing (port/src/romconvert.c). Pure-
|
||||
# stdlib scripts, so PyInstaller --onefile is clean; lands in
|
||||
# tools_pc/dist/prepare-assets/ where bundle-linux.sh picks it up.
|
||||
- name: Freeze asset converter (PyInstaller)
|
||||
run: |
|
||||
python3 -c "import PyInstaller, sys; sys.exit(PyInstaller.__version__ != '6.22.3')" 2>/dev/null \
|
||||
|| python3 -m pip install --quiet pyinstaller==6.22.3
|
||||
python3 -m PyInstaller --onefile --noconfirm --clean \
|
||||
--name ge007-convert \
|
||||
--distpath tools_pc/dist/prepare-assets \
|
||||
--workpath build-pyinst --specpath build-pyinst \
|
||||
tools_pc/dist/prepare-assets/prepare-assets.py
|
||||
ls -la tools_pc/dist/prepare-assets/ge007-convert
|
||||
|
||||
- name: Configure (ntsc-final)
|
||||
run: cmake -S . -B build-linux -DROMID=ntsc-final
|
||||
-DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14
|
||||
@@ -263,18 +162,15 @@ jobs:
|
||||
- name: Build (ntsc-final)
|
||||
run: cmake --build build-linux -j"$(nproc)"
|
||||
|
||||
- name: ccache stats
|
||||
run: ccache -s
|
||||
|
||||
- name: Show binary
|
||||
run: |
|
||||
ls -la build-linux/ge007* || true
|
||||
file build-linux/ge007* || true
|
||||
|
||||
# Package the Linux bundle on every run so the release path stays
|
||||
# exercised. SDL2 is bundled (patchelf sets the exe's rpath to $ORIGIN)
|
||||
# so the tarball runs as-is on any distro / a sideloaded Steam Deck;
|
||||
# zlib + libGL stay system deps. Same ROM / size guards as Windows.
|
||||
# exercised. No shared libs are bundled — the README tells the user to
|
||||
# install SDL2 / zlib / libGL from their distro. Same ROM / size guards
|
||||
# as the Windows bundle.
|
||||
- name: Package Linux bundle
|
||||
run: |
|
||||
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
|
||||
@@ -285,7 +181,7 @@ jobs:
|
||||
tools_pc/bundle-linux.sh "$V"
|
||||
|
||||
- name: Upload bundle
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: goldeneye-pc-port-linux-x86_64
|
||||
path: |
|
||||
@@ -304,91 +200,34 @@ jobs:
|
||||
windows-build:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.code == 'true'
|
||||
# Self-hosted by default (fast, local ccache); windows-latest only for a
|
||||
# release (tag) build or a fork PR — see the `changes` job's `runner`
|
||||
# step for the exact rule (docs/dev/notes/SELFHOSTED-RUNNER-PLAN.md §G2).
|
||||
runs-on: ${{ needs.changes.outputs.windows_selfhosted == 'true' && fromJSON('["self-hosted", "windows"]') || 'windows-latest' }}
|
||||
runs-on: windows-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
env:
|
||||
# Same opt-in ccache CMakeLists.txt already wires up (Step 5). Note:
|
||||
# this cache path is read/written by the JS actions/cache step outside
|
||||
# the msys2 shell too, so it stays a plain workspace-relative path
|
||||
# rather than an msys /c/... form.
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_MAXSIZE: 500M
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# windows-latest (release tag / fork PR) is ephemeral: full install +
|
||||
# update every run. Self-hosted (everything else): `release: false`
|
||||
# reuses the box's own C:\msys64 as-is and, with update/install both
|
||||
# empty here, does no pacman -Syu / pacman -S at all (verified against
|
||||
# the action's source) — it only registers the `msys2 {0}` shell
|
||||
# wrapper every job needs. Packages themselves are hand-maintained on
|
||||
# the box; the dependency check below (not this step) verifies they're
|
||||
# present.
|
||||
- uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: MINGW64
|
||||
release: ${{ needs.changes.outputs.windows_selfhosted != 'true' }}
|
||||
update: ${{ needs.changes.outputs.windows_selfhosted != 'true' }}
|
||||
install: ${{ needs.changes.outputs.windows_selfhosted != 'true' && 'mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-SDL2 mingw-w64-x86_64-zlib mingw-w64-x86_64-python mingw-w64-x86_64-ccache zip unzip' || '' }}
|
||||
|
||||
# Self-hosted only: fails fast with exact install instructions if the
|
||||
# box's hand-maintained MSYS2 is missing a required package, instead of
|
||||
# a confusing failure deeper in the build. windows-latest just installed
|
||||
# everything above, so this would be redundant there.
|
||||
- name: Check self-hosted dependencies
|
||||
if: needs.changes.outputs.windows_selfhosted == 'true'
|
||||
run: tools_pc/check-selfhosted-deps.sh
|
||||
|
||||
- name: Restore ccache
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ccache-windows-${{ hashFiles('CMakeLists.txt') }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
ccache-windows-${{ hashFiles('CMakeLists.txt') }}-
|
||||
ccache-windows-
|
||||
update: true
|
||||
install: >-
|
||||
mingw-w64-x86_64-toolchain
|
||||
mingw-w64-x86_64-cmake
|
||||
mingw-w64-x86_64-SDL2
|
||||
mingw-w64-x86_64-zlib
|
||||
mingw-w64-x86_64-python
|
||||
zip
|
||||
unzip
|
||||
|
||||
- name: Regenerate ROM-asset symbol file (US)
|
||||
run: python scripts/gen_romassets.py u
|
||||
|
||||
# Drop-in ROM support: freeze the asset-prep tool into a single
|
||||
# ge007-convert.exe the engine spawns on first run when the pcmodels/
|
||||
# pccg sidecars are missing (port/src/romconvert.c). Lands in
|
||||
# tools_pc/dist/prepare-assets/ where bundle-win.sh picks it up.
|
||||
- name: Freeze asset converter (PyInstaller)
|
||||
run: |
|
||||
# MSYS2's mingw python ships without pip and has ensurepip
|
||||
# disabled — install pip from pacman, then PyInstaller.
|
||||
python -c "import PyInstaller, sys; sys.exit(PyInstaller.__version__ != '6.22.3')" 2>/dev/null || {
|
||||
pacman -S --noconfirm --needed mingw-w64-x86_64-python-pip
|
||||
python -m pip install --quiet --break-system-packages pyinstaller==6.22.3
|
||||
}
|
||||
python -m PyInstaller --onefile --noconfirm --clean \
|
||||
--name ge007-convert \
|
||||
--distpath tools_pc/dist/prepare-assets \
|
||||
--workpath build-pyinst --specpath build-pyinst \
|
||||
tools_pc/dist/prepare-assets/prepare-assets.py
|
||||
ls -la tools_pc/dist/prepare-assets/ge007-convert.exe
|
||||
|
||||
- name: Build (ntsc-final)
|
||||
run: ./build-pc.sh ntsc-final
|
||||
|
||||
# D441 ratchet: fail if the per-file count of 64-bit pointer/int ABI
|
||||
# warnings rises relative to tools_pc/abi_baseline.json. Re-baseline
|
||||
# locally with `python tools_pc/abi_ratchet.py build-pc --update`.
|
||||
- name: ABI warning ratchet (D441)
|
||||
run: python tools_pc/abi_ratchet.py build-pc
|
||||
|
||||
- name: ccache stats
|
||||
run: ccache -s
|
||||
|
||||
# Package the distributable bundle on every run so the release path is
|
||||
# always exercised. On a tag build VERSION is the tag; otherwise it is a
|
||||
# throwaway CI string. bundle-win.sh hard-fails if a ROM image or an
|
||||
@@ -403,7 +242,7 @@ jobs:
|
||||
tools_pc/bundle-win.sh "$V"
|
||||
|
||||
- name: Upload bundle
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: goldeneye-pc-port-win64
|
||||
path: |
|
||||
@@ -419,74 +258,39 @@ jobs:
|
||||
|
||||
# Tags only. Attach the packaged Windows + Linux bundles to a DRAFT
|
||||
# pre-release so a human can do the ROM smoke-test on each platform before
|
||||
# publishing. This job — and only this job — gets contents: write, and it
|
||||
# is also the only job that attests build provenance (id-token: write +
|
||||
# attestations: write; attest-build-provenance needs both).
|
||||
# publishing. This job — and only this job — gets contents: write.
|
||||
release:
|
||||
if: github.ref_type == 'tag'
|
||||
needs: [ changes, validate, windows-build, linux-build ]
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# The pushed tag must match the version baked into the exe (the update
|
||||
# check compares against it); bump GE007_VERSION in CMakeLists.txt first.
|
||||
- name: Check tag matches GE007_VERSION
|
||||
run: |
|
||||
ver=$(sed -n 's/^set(GE007_VERSION "\([^"]*\)").*/\1/p' CMakeLists.txt)
|
||||
if [ -z "$ver" ] || [ "${GITHUB_REF_NAME}" != "v${ver}" ]; then
|
||||
echo "tag ${GITHUB_REF_NAME} != v${ver} (CMakeLists.txt GE007_VERSION)"; exit 1
|
||||
fi
|
||||
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: goldeneye-pc-port-win64
|
||||
path: dist
|
||||
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: goldeneye-pc-port-linux-x86_64
|
||||
path: dist
|
||||
|
||||
# SLSA build provenance for the release artifacts (in-toto, signed by
|
||||
# GitHub's OIDC key for this repo's workflow at the tagged commit).
|
||||
# Anyone can check a downloaded file:
|
||||
# gh attestation verify <file> --repo jkdansereau/goldeneye-pc-port
|
||||
- uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
|
||||
with:
|
||||
subject-path: |
|
||||
dist/*.zip
|
||||
dist/*.zip.sha256
|
||||
dist/*.tar.gz
|
||||
dist/*.tar.gz.sha256
|
||||
|
||||
- name: Create or update draft pre-release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
TAG="${GITHUB_REF_NAME}"
|
||||
ASSETS=(dist/*.zip dist/*.zip.sha256 dist/*.tar.gz dist/*.tar.gz.sha256)
|
||||
NOTES="$(mktemp)"
|
||||
# Asset names are built from the tag with the leading v stripped
|
||||
# (bundle-{linux,win}.sh), so substitute the same form -- a raw
|
||||
# TAG would make the notes reference goldeneye-pc-port-v*.* assets
|
||||
# that don't exist.
|
||||
NOTES_VERSION="${TAG#v}"
|
||||
sed "s/<version>/${NOTES_VERSION}/g" .github/release-notes.md > "$NOTES"
|
||||
if gh release view "$TAG" >/dev/null 2>&1; then
|
||||
gh release upload "$TAG" "${ASSETS[@]}" --clobber
|
||||
# Re-stamp the notes body too: it is only set at creation, and
|
||||
# release-notes.md may have moved since the draft was first cut.
|
||||
gh release edit "$TAG" --notes-file "$NOTES"
|
||||
else
|
||||
gh release create "$TAG" "${ASSETS[@]}" \
|
||||
--draft --prerelease \
|
||||
--title "GoldenEye 007 PC Port $TAG" \
|
||||
--notes-file "$NOTES"
|
||||
--notes-file .github/release-notes.md
|
||||
fi
|
||||
@@ -1,200 +0,0 @@
|
||||
# Self-hosted runtime (ROM-gated)
|
||||
#
|
||||
# Runs the build + the 21-level headless sweep (tools_pc/verify.sh) on the
|
||||
# self-hosted runner, the one machine that has a legal ROM.
|
||||
# This is the job class ci.yml explicitly does NOT do: CI is build-only, no
|
||||
# ROM, so runtime checks live here. Setup + rollout plan (gitignored, local):
|
||||
# docs/dev/notes/SELFHOSTED-RUNNER-PLAN.md
|
||||
#
|
||||
# Persistence model — everything lives under the runner's own paths, managed
|
||||
# by the pipeline; no hand-maintained directories:
|
||||
# - ccache -> actions/cache (stored by the runner under _work/_temp)
|
||||
# - ROM -> copied every run from a seed dir on the runner:
|
||||
# {runner_home}/_rom-seed/, or the repo variable
|
||||
# GE_ROM_SEED_DIR. Bootstrap: put ge007.ntsc-final.z64,
|
||||
# pcmodels-ntsc-final/ and pccg-ntsc-final/ there once.
|
||||
# Integrity is re-verified every run against the committed
|
||||
# ge007.u.sha1 (repo-tracked US ROM hash).
|
||||
# The ROM is deliberately NOT stored in actions/cache (it
|
||||
# used to be, key ge-rom-data-ntsc-v1). Cache entries live on
|
||||
# GitHub's side, and pull_request workflows, including ci.yml
|
||||
# runs from fork PRs, can restore caches created on the base
|
||||
# branch, so a PR could read the ROM out of the cache. (The
|
||||
# old save never actually persisted on this box, so no entry
|
||||
# was ever created; checked 2026-09-30.)
|
||||
# - MSYS2 -> machine-level install at C:\msys64 (verify.sh hardcodes the
|
||||
# prefix), installed and its packages maintained BY HAND on
|
||||
# the box. msys2/setup-msys2 still runs every job (it's what
|
||||
# registers the `msys2 {0}` shell wrapper), but with
|
||||
# `release: false` and update/install both omitted — verified
|
||||
# against the action's source to do no pacman -Syu / pacman -S
|
||||
# / network round-trip, just a fast reuse of the existing
|
||||
# C:\msys64. Package presence is checked (not installed) by
|
||||
# tools_pc/check-selfhosted-deps.sh, which fails fast with
|
||||
# exact install instructions if something's missing.
|
||||
#
|
||||
# Trigger gating (non-negotiable):
|
||||
# - workflow_dispatch ONLY for now. Flip on `push: branches: [main]` once
|
||||
# the first manual runs are reliably green.
|
||||
# - NEVER add `pull_request`: unreviewed fork code must not be routable to
|
||||
# a runner that can see the ROM.
|
||||
#
|
||||
# Leak rules (docs/dev/notes/AGENT-VM-FORGEJO-PLAN.md checklist):
|
||||
# - The ROM + sidecars live only in the runner's local seed dir (never in
|
||||
# actions/cache); nothing in this workflow uploads from data/.
|
||||
# - Artifacts here are console logs + a JSON verdict only. Rendered frames
|
||||
# (ppm/) are ROM-derived content and are NOT uploaded.
|
||||
|
||||
name: Self-hosted runtime
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
levels:
|
||||
description: "Sweep subset, SWEEP_LEVELS format (blank = all 21)"
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# One ROM job at a time per ref; never cancel an in-flight sweep out from
|
||||
# under itself (the sweep is serialized by its own lockdir + watchdog). Group
|
||||
# + cancel-in-progress:false is what actually serializes runs — GitHub
|
||||
# Actions' workflow-level `concurrency` has no `max-parallel` key (that only
|
||||
# exists under a job's `strategy:` for matrix jobs); a stray one here is a
|
||||
# hard parse error that blocks every dispatch.
|
||||
concurrency:
|
||||
group: selfhosted-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
GE_ROM_SEED_DIR: ${{ vars.GE_ROM_SEED_DIR }}
|
||||
|
||||
jobs:
|
||||
build-and-sweep:
|
||||
runs-on: [ self-hosted, windows ]
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_MAXSIZE: 500M
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# `release: false` reuses the box's own C:\msys64 as-is instead of
|
||||
# downloading/installing MSYS2 fresh (verified against the action's
|
||||
# source: with this set, and update/install both omitted, it does
|
||||
# exactly one `uname -a` + a pacman.conf sed tweak — no pacman -Syu,
|
||||
# no pacman -S, no network round-trip). This step still has to run
|
||||
# every time — it's what registers the `msys2 {0}` shell wrapper
|
||||
# above — but does no install/update work.
|
||||
- uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0
|
||||
with:
|
||||
msystem: MINGW64
|
||||
release: false
|
||||
|
||||
# Packages themselves are hand-maintained on the box (never installed
|
||||
# by this workflow) — fails fast with exact install instructions if
|
||||
# one's missing, instead of a confusing failure deeper in the build.
|
||||
- name: Check self-hosted dependencies
|
||||
run: tools_pc/check-selfhosted-deps.sh
|
||||
|
||||
# --- ROM + sidecars: runner-local seed dir (never actions/cache) ------
|
||||
# {runner_home} = two levels up from RUNNER_WORKSPACE ({home}/_work/{repo}).
|
||||
# The seed dir is the only copy the workflow uses; it is never deleted.
|
||||
- name: Stage ROM + sidecars (runner seed dir)
|
||||
run: |
|
||||
seed="${GE_ROM_SEED_DIR:-$(dirname "$(dirname "$RUNNER_WORKSPACE")")/_rom-seed}"
|
||||
for f in ge007.ntsc-final.z64 pcmodels-ntsc-final pccg-ntsc-final; do
|
||||
[ -e "$seed/$f" ] || {
|
||||
echo "missing $seed/$f" >&2
|
||||
echo "One-time bootstrap: copy ge007.ntsc-final.z64, pcmodels-ntsc-final/" >&2
|
||||
echo "and pccg-ntsc-final/ into $seed (or set the repo variable GE_ROM_SEED_DIR)." >&2
|
||||
exit 2
|
||||
}
|
||||
done
|
||||
mkdir -p data
|
||||
# -r: pcmodels-*/pccg-* are directories, not single files (found by
|
||||
# testing — a plain cp failed with "omitting directory").
|
||||
cp -r "$seed/ge007.ntsc-final.z64" "$seed/pcmodels-ntsc-final" \
|
||||
"$seed/pccg-ntsc-final" data/
|
||||
echo "staged ROM store from $seed"
|
||||
|
||||
- name: Verify ROM integrity (committed US sha1)
|
||||
run: |
|
||||
cd data
|
||||
want="$(cut -d' ' -f1 ../ge007.u.sha1)"
|
||||
got="$(sha1sum ge007.ntsc-final.z64 | cut -d' ' -f1)"
|
||||
[ "$want" = "$got" ] || { echo "ROM hash mismatch: want $want got $got" >&2; exit 2; }
|
||||
echo "ROM sha1 ok: $got"
|
||||
|
||||
# --- build -------------------------------------------------------------
|
||||
# Same opt-in ccache CMakeLists.txt wires up (speed-ups plan Step 5);
|
||||
# persisted via actions/cache like ci.yml's windows-build.
|
||||
- name: Restore ccache
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ccache-selfhosted-${{ hashFiles('CMakeLists.txt') }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
ccache-selfhosted-${{ hashFiles('CMakeLists.txt') }}-
|
||||
ccache-selfhosted-
|
||||
|
||||
- name: Regenerate ROM-asset symbol file (US)
|
||||
run: python scripts/gen_romassets.py u
|
||||
|
||||
- name: Build (ntsc-final)
|
||||
run: ./build-pc.sh ntsc-final
|
||||
|
||||
# verify.sh: build-if-stale -> headless GE_PCDUMP per level -> crash
|
||||
# symbolication -> pixcount -> framediff vs tools_pc/golden/. Serialized
|
||||
# + watchdog + one retry by design (D117: not frame-deterministic).
|
||||
- name: Level sweep (all 21, or dispatch subset)
|
||||
env:
|
||||
INPUT_LEVELS: ${{ inputs.levels }}
|
||||
run: |
|
||||
if [ -n "$INPUT_LEVELS" ]; then
|
||||
if ! printf '%s' "$INPUT_LEVELS" | grep -Eq '^[A-Za-z0-9]+:[0-9]+( [A-Za-z0-9]+:[0-9]+)*$'; then
|
||||
echo "levels input must be space-separated Name:NN entries, e.g. 'Dam:33 Facility:34'" >&2
|
||||
exit 1
|
||||
fi
|
||||
export SWEEP_LEVELS="$INPUT_LEVELS"
|
||||
fi
|
||||
tools_pc/verify.sh sweep --json > sweep.log 2>&1
|
||||
rc=$?
|
||||
cat sweep.log
|
||||
exit $rc
|
||||
|
||||
# Pull the JSON verdict array out of the mixed stdout for easy review.
|
||||
- name: Extract JSON verdict
|
||||
if: always()
|
||||
run: |
|
||||
python -c "
|
||||
import json, sys
|
||||
s = open('sweep.log', encoding='utf-8', errors='replace').read()
|
||||
i = s.rfind('[{')
|
||||
out = '[]'
|
||||
if i >= 0:
|
||||
d, _ = json.JSONDecoder().raw_decode(s[i:])
|
||||
out = json.dumps(d, indent=1)
|
||||
open('sweep-verdict.json', 'w').write(out + '\n')
|
||||
print(out)
|
||||
"
|
||||
|
||||
# Console logs + verdict only — no frames, no ROM bytes (leak rules).
|
||||
- name: Upload sweep report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: selfhosted-sweep-${{ github.run_number }}
|
||||
path: |
|
||||
sweep.log
|
||||
sweep-verdict.json
|
||||
ge007.crash.log
|
||||
if-no-files-found: ignore
|
||||
retention-days: 14
|
||||
@@ -142,38 +142,14 @@ ge007.crash.log
|
||||
run_out.log
|
||||
ppm/
|
||||
screenshots/
|
||||
# tools_pc/perf.ps1 output (raw ETW traces + CSV analysis, regenerable)
|
||||
perf_captures/
|
||||
# local Mupen64 Plus checkout (N64 reference footage); binaries, do not commit
|
||||
tools_pc/mupen64/
|
||||
# local N64 emulator checkout (D205 A/B reference)
|
||||
mupen64/
|
||||
|
||||
# PC port run/scratch logs
|
||||
run_*.log
|
||||
d6*.log
|
||||
ge007.crash.log
|
||||
|
||||
# agent scratch space + audio probe artifacts (regenerable dumps/traces)
|
||||
scratchpad/
|
||||
# session logs / investigation scratch — local only, never in the public repo
|
||||
# (untracked 2026-09-19 during the v0.3.0 release review)
|
||||
/scratch/
|
||||
# re-vendored GPL reference source (see reference/mouse-injector/README.md)
|
||||
/reference/mouse-injector/*
|
||||
!reference/mouse-injector/README.md
|
||||
# local research/backlog working docs (TCRF cut-content cross-reference etc.)
|
||||
backlog/
|
||||
# montage work artifact: the tiled contact sheet behind docs/img/shots/ and
|
||||
# the preview video (regenerable from the same session recordings)
|
||||
docs/img/shots/CONTACT-SHEET.jpg
|
||||
/audiodump.raw
|
||||
/audiotrace.log
|
||||
/mixertrace.log
|
||||
# texture-dump probe artifacts (regenerable via GE_TEXDUMP)
|
||||
/texdump/
|
||||
/texdump_p23_old/
|
||||
|
||||
# Claude Code local state / agent worktrees
|
||||
.claude/
|
||||
|
||||
@@ -183,25 +159,3 @@ nohup.out
|
||||
|
||||
gdb.txt
|
||||
gdb-hang.txt
|
||||
|
||||
# frozen asset converter (built by CI via PyInstaller; local freezes land here)
|
||||
tools_pc/dist/prepare-assets/ge007-convert*
|
||||
build-pyinst/
|
||||
|
||||
# Local working junk (frame captures, timerun state, PPM dumps) - moved here
|
||||
# from repo root / scratch on 2026-09-18; never commit.
|
||||
/local/
|
||||
build-pc-cmd.bat
|
||||
|
||||
# v0.4.0 modern-options wave: per-landing sweep capture dirs (capture.sh output)
|
||||
tools_pc/sweep-captures/capture-*/
|
||||
|
||||
# Sweep capture output dirs and PPM dumps: regenerable framebuffer captures
|
||||
# (capture.sh OUTDIR), useful on disk, never in the tracked tree.
|
||||
tools_pc/sweep-captures/cap-*/
|
||||
tools_pc/sweep-captures/**/*.ppm
|
||||
|
||||
# Stray full CPython install that landed in repo root (2026-09-28);
|
||||
# referenced by no project tooling. Never commit.
|
||||
/Python/
|
||||
docs/media/*.mp4
|
||||
@@ -4,98 +4,28 @@
|
||||
|
||||
- `n64decomp/007`: WIP decompilation of GoldenEye 007 (N64), byte-matches US/EU/JP ROMs.
|
||||
- Active work: **PC port** modelled on the Perfect Dark PC port (same Rare "Indy" engine family).
|
||||
- **Reference docs:** `docs/internals.md` — architecture, GE-specific RSP deltas, phased plan (§1–§10). `docs/dev/findings.md` — the `Dxx` finding log (§F + §H). **Look up findings via `docs/dev/findings-index.csv` (label, one-liner, status; regenerate with `tools_pc/gen_findings_index.py`), then read only the specific `## Dxx` entry (multi-pass labels like D202/D176(a) have large sections — grep within the section or read with offset/limit, don't slurp it whole). Never linear-read.** `docs/porting-notes.md` — the recurring N64→PC bug classes (dense; skim the headers, read what's relevant).
|
||||
- **Open work / known issues:** `docs/ROADMAP.md` — the single tracker; add new items there, never start a new backlog/roadmap file.
|
||||
- **Current status:** the README "Status" section (`docs/dev/LEVEL-STATUS.md` is a historical per-level sweep record). Current task + environment: `docs/HANDOFF.md` (a rolling local working file — may be absent in a fresh clone; fall back to the README "Status" section).
|
||||
- **Reference docs:** `docs/internals.md` — architecture, GE-specific RSP deltas, phased plan (§1–§10). `docs/dev/findings.md` — the `Dxx` finding log (§F + §H, indexed at the top of §F); read the entry you need, don't linear-read. `docs/porting-notes.md` — the recurring N64→PC bug classes (dense; skim the headers, read what's relevant).
|
||||
- **Current status:** the README "Status" section, and `docs/dev/LEVEL-STATUS.md` for the per-level sweep. Current task + environment: `docs/HANDOFF.md` (a rolling local working file — may be absent in a fresh clone; fall back to the README "Status" section).
|
||||
- **Dispatching subagents?** `docs/dev-process.md` — task budgets/deadlines, file partitioning, pre-flight, the standard brief template. Every investigation subagent reads `docs/porting-notes.md` first and appends to it.
|
||||
|
||||
## Public site pages (`docs/`) — presentation layer, not a source of truth
|
||||
|
||||
The repo's homepage and sub-pages are published by GitHub Pages; the local
|
||||
preview is `node scratch/build_preview.mjs site` → `scratch/_site/` (served
|
||||
with `node scratch/serve_preview.mjs 8777`). The site is a **presentation
|
||||
layer over the reference docs, kept in a separate area, and allowed to go
|
||||
stale** — never let it poison a work item:
|
||||
|
||||
- **Pure site pages** — `docs/index.md`, `documentation.md`,
|
||||
`the-software-rsp.md`, `the-asset-pipeline.md`, `framerate-and-pacing.md`,
|
||||
`input-and-aim.md`, `saves.md`, `security.md`, `fidelity.md` —
|
||||
summarise the ROADMAP / README / finding log / release notes. **Do not
|
||||
consult them as work information, cross-check against them, or treat a
|
||||
mismatch with `docs/ROADMAP.md` or the finding log as a bug** (that is
|
||||
the expected, independent-staleness case). Update them only when the task
|
||||
says so, and source every fact from the ROADMAP / README / finding log —
|
||||
never the other way round. The footer "last updated" date in
|
||||
`docs/_layouts/default.html` is the same class of site-only detail.
|
||||
- **Shared files** — `docs/internals.md`, `porting-notes.md`,
|
||||
`building.md`, `dev-process.md`, `dev/agentic-development.md` are
|
||||
authoritative working docs that the site publishes verbatim; edit them
|
||||
normally as part of dev work and the site picks up the change for free.
|
||||
|
||||
Keep the split intact: no working notes migrate into pure site pages, and
|
||||
never edit a shared doc merely to make the site read better — if the site is
|
||||
stale, fix the site page (or the fact upstream, as its own task).
|
||||
|
||||
## Non-negotiables
|
||||
|
||||
1. **N64 build untouched.** `Makefile`, `tools/`, `rsp/`, `ld/` belong to the N64 build. Never modify them for the PC port.
|
||||
2. **Game logic is unmodified.** The decomp's control flow and behavior are ground truth for 1:1 fidelity — never change them. All N64 *hardware* dependencies are satisfied by the `port/` layer; if a game file seems to need a behavioral change, stop and check the exception below before assuming the fix belongs in `port/`. **Diagnosis is never restricted — only the fix.** Trace a bug as deep into `src/game` state as the evidence leads, and name the exact struct/field/logic responsible, before deciding which of three buckets it falls in (full framing: `docs/dev-process.md`). **Narrow exception (ABI/layout only):** the 32→64-bit pointer-width transition forces a small class of mechanical, semantics-preserving edits that cannot be isolated in `port/` — **any struct-layout or pointer-width-driven misread caused by 32→64-bit widening**, whether in a ROM-serialized record (a struct with a 32-bit-pointer field misaligns when read as 64-bit) or a **live runtime struct/union** (a raw-byte offset alias into a union arm whose true field shifted because an earlier member in the same union widened — the D209/D210/D255 pattern; see `docs/porting-notes.md` §A1 for the full catalogue and its diagnostic tells). These follow the PD ground-truth pattern (store the embedded address as `u32` and cast to a real pointer at the use site; or read the correctly-named/typed field instead of a raw-offset/mistyped alias), change no logic or behavior, and are each documented in `docs/dev/findings.md` §F/D3x, cross-tagged to §A1 where that pattern applies. **A genuine behavioral difference** — the decomp's byte-identical code, given verified-correct inputs, still diverging from real N64 behavior — is not covered by this exception and requires the rule-2 sign-off procedure (`docs/dev-process.md`) before any `src/game` edit. No other game-code edits are permitted.
|
||||
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/dev/findings.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.
|
||||
|
||||
## Tree hygiene & release gate
|
||||
|
||||
Standing rules for anything that lands in the tracked tree or in a push. These
|
||||
exist because the repo is public, MIT-licensed, forked, and its history is
|
||||
mostly agent-authored.
|
||||
|
||||
1. **No local machine paths.** Tracked files carry no local absolute paths and
|
||||
no usernames. Use the placeholder convention: `<repo>` = this working
|
||||
directory, `<repos>` = the local source-repos root, `<games>` / `<videos>` =
|
||||
local media roots, `<python>` = the local CPython install, `<temp>` = a
|
||||
writable native temp dir (legend at the top of
|
||||
`docs/dev/HANDOFF-ARCHIVE.md`). Tools must not default to a machine-specific
|
||||
path — take the path as an argument and error if it is missing.
|
||||
2. **No contributor emails in prose.** Credit people by name plus the PR/issue
|
||||
that carries the change ("co-authored with X (#123)"), never an inline
|
||||
`<email>` trailer inside findings text or docs. Commit-trailer emails must
|
||||
be noreply forms, never a contributor's real address.
|
||||
3. **Every vendored third-party component is declared in `NOTICE`** with its
|
||||
license and holder — the "Third-party code vendored into the port layer" and
|
||||
"Vendored build tooling" lists. GPL sources must ship their license text
|
||||
in-tree (`tools/gzipsrc/COPYING`). MIT covers only our original work; never
|
||||
relicense inherited code and never re-license the repo (forks depend on the
|
||||
MIT `LICENSE` staying put).
|
||||
4. **Screenshots are game content.** A framebuffer capture is a derivative work
|
||||
of Nintendo/MGM art regardless of which engine drew it. Keep repo imagery
|
||||
documentary and small (bug shots, previews — downscaled), and never commit
|
||||
bulk ROM-derived captures: capture output dirs are gitignored
|
||||
(`tools_pc/sweep-captures/cap-*/`, `tools_pc/sweep-captures/**/*.ppm`); the
|
||||
only bulk imagery in the tree is `tools_pc/golden/**/*.png`
|
||||
(63 frames × 3 platforms, 189 total, nested per level at
|
||||
`tools_pc/golden/<level>/{win,linux,deck}/`; the `deck` set added
|
||||
2026-10-05 with maintainer approval), which exists because `verify.sh`
|
||||
consumes it. Downloads ship no game content at all.
|
||||
5. **Batch, don't spam.** Accumulate approved doc/tree fixes across a session
|
||||
into ONE commit; no per-item branches.
|
||||
6. **Release gate.** Before pushing a release: the PII scrub and legal/copyright
|
||||
review reports (gitignored, `docs/dev/notes/`) must be run and clean, and the
|
||||
README Legal section must name every rights holder the game's trademarks
|
||||
implicate. Rewriting pushed history (squash) needs explicit maintainer
|
||||
consent, and any such report states the remote + branch it applies to.
|
||||
|
||||
## Critical files
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `docs/internals.md` | Architecture + RSP deltas + phased plan (§1–§10). Reference, not a linear read. |
|
||||
| `docs/dev/findings.md` | The `Dxx` finding log (§F/§H); lookups via `docs/dev/findings-index.csv`. |
|
||||
| `docs/dev/findings.md` | The `Dxx` finding log (§F/§H, indexed at top of §F). |
|
||||
| `CMakeLists.txt` | PC build (parallel to the N64 Makefile). Source list + `REGION_DEFS` live here. |
|
||||
| `port/src/` | Shims: `libultra.c` (OS API; also shims the scheduler's hardware leaf calls and runs the software RSP inline — the game's real `src/sched.c` is compiled), `n64stubs.c` (boot/TLB/FPU/rmon), `random.c` (PRNG ported verbatim from `random.s`), `ucode.c` (microcode segment markers), `main.c`, `video.c`, … |
|
||||
| `port/src/` | Shims: `libultra.c` (OS API), `gesched.c` (scheduler), `n64stubs.c` (boot/TLB/FPU/rmon), `random.c` (PRNG ported verbatim from `random.s`), `ucode.c` (microcode segment markers), `main.c`, `video.c`, … |
|
||||
| `port/fast3d/` | Software RSP (adapted from the PD port). The main Phase 2 work. |
|
||||
| `rsp/graphics/gmain.s` | GE's RSP ucode — ground truth for GBI/CC/RM. |
|
||||
| `reference/mouse-injector/README.md` | **Stub only** — the vendored GEPD-Edition Mouse Injector source (GPLv2) was removed from the public repo 2026-09-19 (license hygiene: GPL code + prebuilt binary in an MIT project; it was never compiled here). The stub records provenance + how to re-vendor locally (gitignored). The ported mouse-aim model lives in `port/src/input.c` (D194 lineage); design record: `docs/dev/GEPD-INPUT-PLAN.md`. |
|
||||
| A local **Perfect Dark PC port** checkout ([fgsfdsfgs/perfect_dark](https://github.com/fgsfdsfgs/perfect_dark)) | **Standing reference** — consult it whenever a work item has a PD analogue (same Rare engine family): port-layer ground truth (`port/fast3d/`, crash/system/video), plus copy candidates `port/src/preprocess/` (N64→PC asset conversion; `filemodel.c` is the D43 near-analogue) and `mixer.c`/`input.c`/`fs.c`. Port-layer files only; same family ≠ identical format — validate per field. Full audit: `docs/internals.md` §2.4. |
|
||||
|
||||
## Build
|
||||
@@ -108,95 +38,24 @@ Needs CMake + SDL2 + zlib + OpenGL, and must run from the MSYS2 MINGW64 shell
|
||||
(see `build-pc.sh` header and `docs/building.md`). ROM goes in `./data/`
|
||||
(not distributed); assets must be extracted from it first (`docs/building.md`).
|
||||
|
||||
**Windows build environment (three recurring failure modes — diagnose in this order):**
|
||||
|
||||
1. **`Cannot create temporary file in C:\Windows\: Permission denied`** at the
|
||||
link step. The PE toolchain (ninja → cmd → gcc/ld) needs a writable
|
||||
TMP/TEMP; the msys→native env conversion drops it in non-login shells
|
||||
(agent harnesses; the `C:/msys64` tree here was built for a relocated MSYS2
|
||||
root — `<msys>` in the placeholder legend — so its path conversion is
|
||||
unreliable). `build-pc.sh` now **self-heals**: it
|
||||
probes a native child's TMP (via a file — piped `cmd.exe` stdout is
|
||||
unreliable under msys console emulation) and, if broken, re-runs
|
||||
cmake+build under PowerShell with `TMP`/`TEMP` set natively (the
|
||||
native→native boundary passes env through intact; only msys→native is
|
||||
broken). If you run cmake/ninja *by hand* from a broken shell: run them
|
||||
from cmd/PowerShell with `C:\msys64\mingw64\bin` on PATH.
|
||||
**Guard revision (2026-09-28):** the re-exec writes a self-contained
|
||||
`.ps1` to a temp file and runs `powershell -File` with plain path/word
|
||||
args only (the msys→native argv conversion mangles `$`-bearing
|
||||
`-Command` strings; a file write is conversion-free). Inside: PATH is
|
||||
built explicitly (never trust the inherited value); TMP is chosen from
|
||||
writable candidates (`%LocalAppData%\Temp` first —
|
||||
`[System.IO.Path]::GetTempPath()` honours the inherited *broken* TMP,
|
||||
observed as `C:\Windows\`); every native step self-logs to
|
||||
`build-pc/ge007-native-reexec-{diag,cmake,build}.log`; a null
|
||||
`$LASTEXITCODE` ("did not execute") is a distinct failure (exit 2/3) —
|
||||
in PS 5.1 a piped native command errors out non-terminating and never
|
||||
sets it, so an unguarded `if ($LASTEXITCODE -ne 0)` passes vacuously.
|
||||
In `.ps1`, `-DROMID=$Var` is a literal (bare tokens don't expand) — it
|
||||
must be `"-DROMID=$Var"`. Launch powershell plainly — `env -i` before it
|
||||
breaks the nested cmake launch (verified 2026-09-28).
|
||||
**FIXED 2026-09-30 (`fix/build-pc-tmp-reexec`):** the heredoc `.ps1`
|
||||
was byte-identical to a hand-extracted copy — not line endings. Three
|
||||
real bugs: (a) the TMP probe's inline `cmd //c "... \"path\" ..."` never
|
||||
ran (msys re-escapes embedded quotes as `\"`, which cmd rejects), so
|
||||
*every* build took the re-exec, and it only tested that TMP existed;
|
||||
(b) the `.ps1`'s toolchain dirs were derived from `command -v cmake`,
|
||||
which in Git Bash/agent shells is a pip cmake (no MSYS2 toolchain) —
|
||||
the hand-run worked because it passed explicit `C:\msys64\...` paths;
|
||||
(c) `/tmp` + `cygpath` disagree when Git Bash and MSYS2 `usr/bin` are
|
||||
both on PATH, so the `.ps1` could be written to one `/tmp` and
|
||||
powershell pointed at the other (it then exits 0 having done nothing).
|
||||
Now: the MSYS2 `mingw64/bin` is located and validated (override:
|
||||
`GE_MSYS2_ROOT`) and prepended to PATH; the probe is a `.cmd` file that
|
||||
tests TMP *writability*; all native-facing files live in the build dir
|
||||
with paths from bash's own `pwd -W`. Verified from an agent Git Bash
|
||||
shell: fresh + incremental builds, both on the direct path and with a
|
||||
forced unwritable TMP (`TMP=C:\Windows\`) through the re-exec.
|
||||
**FIXED 2026-09-30 (`fix/build-pc-reexec-noop`): the re-exec exited rc=2
|
||||
with the diag log ending `cmake ran LASTEXITCODE=[]`.** From a shell with
|
||||
MSYS2 `usr/bin` first on PATH, powershell inherits a stripped ~13-var env
|
||||
with `PATHEXT=.CPL` and no ComSpec/TMP/TEMP, so `& cmake.exe` silently
|
||||
launches nothing. The `.ps1` now restores PATHEXT and ComSpec. In that
|
||||
shell the re-exec itself is legitimately needed (no TMP), so the probe
|
||||
is correct. Verified: MSYS2-PATH shell, forced `TMP=C:\Windows\`, and a
|
||||
no-change incremental run all exit 0 with a fresh exe.
|
||||
**FIXED 2026-10-04 (`fix/build-arch-tag`, D515):** a FRESH build dir
|
||||
configured through the re-exec cached `CMAKE_HOST_SYSTEM_PROCESSOR ""` /
|
||||
`CMAKE_SYSTEM_PROCESSOR ""` (the stripped env also drops
|
||||
`PROCESSOR_ARCHITECTURE`, which CMake reads for the host processor — same
|
||||
class as PATHEXT/ComSpec), so `cmake/TargetArch.cmake` produced an empty
|
||||
arch tag: `Target arch: (64bit=FALSE)`, binary named `ge007..exe`
|
||||
(a correct 64-bit build; only the name was wrong — found during T7, D513).
|
||||
The `.ps1` now restores `PROCESSOR_ARCHITECTURE` from the machine
|
||||
environment (fallback `AMD64`; `SystemRoot`/`windir` likewise) and logs
|
||||
the four vars to the diag log; `cmake/TargetArch.cmake` falls back to
|
||||
`${CMAKE_C_COMPILER} -dumpmachine` when `CMAKE_SYSTEM_PROCESSOR` is empty
|
||||
and FATAL_ERRORs instead of emitting an untagged binary. Existing build
|
||||
dirs are unaffected (value already cached); a dir configured before the
|
||||
fix picks up x86_64 on its next in-place reconfigure.
|
||||
2. **`cannot open output file ge007.x86_64.exe: Permission denied`.** A
|
||||
**running** `ge007.x86_64.exe` locks the output file (Windows rule; you
|
||||
can't relink over a live PE). Check with `Get-Process | Where-Object {
|
||||
$_.ProcessName -like '*ge007*' }` and close the game before rebuilding.
|
||||
An agent must not kill the user's game process to make a link succeed.
|
||||
3. **`cc1.exe: ... libmpfr-6.dll: cannot open shared object file`.** The
|
||||
calling PATH lacks `C:\msys64\mingw64\bin` (the gcc driver finds cc1 via
|
||||
its own directory, but the child needs the mingw DLL dir on PATH). In an
|
||||
msys shell: `export PATH="/c/msys64/mingw64/bin:/c/msys64/usr/bin:$PATH"`
|
||||
before building.
|
||||
|
||||
## Verification ritual (after any build-affecting change)
|
||||
|
||||
1. **Undefined symbols.** Every symbol referenced by the compiled set (see `CMakeLists.txt`: `SRC_GAME`, `SRC_ENGINE`, `SRC_LIBAUDIO`, `SRC_LIBULTRARE_AUDIO`, `SRC_LIBULTRARE_DATA`, `SRC_GU`, `SRC_PORT*`) must be defined exactly once in the compiled set or in `port/`. Symbols that live in EXCLUDED files (`libultra/io/*`, `libultrare/io/*` except `vitbl.c`, `libultra/os/*`, `libultrare/os/*`, `sched.c`, `rmon.c`, `vi.c`, `src/*.s`) must be provided by `port/src/libultra.c`, `n64stubs.c`, `random.c`, or `ucode.c`.
|
||||
1. **Undefined symbols.** Every symbol referenced by the compiled set (see `CMakeLists.txt`: `SRC_GAME`, `SRC_ENGINE`, `SRC_LIBAUDIO`, `SRC_LIBULTRARE_AUDIO`, `SRC_LIBULTRARE_DATA`, `SRC_GU`, `SRC_PORT*`) must be defined exactly once in the compiled set or in `port/`. Symbols that live in EXCLUDED files (`libultra/io/*`, `libultrare/io/*` except `vitbl.c`, `libultra/os/*`, `libultrare/os/*`, `sched.c`, `rmon.c`, `vi.c`, `src/*.s`) must be provided by `port/src/libultra.c`, `gesched.c`, `n64stubs.c`, `random.c`, or `ucode.c`.
|
||||
2. **Duplicates.** No symbol defined twice across the compiled set (watch `sp_*` stacks, `rmon*`, `os*` shims, segment markers).
|
||||
3. **Syntax.** Every touched file must parse; `./build-pc.sh` is the final word.
|
||||
|
||||
Run `/linkcheck` for this sweep. Record new findings in `docs/dev/findings.md` §F/§H style (next `Dxx` label after the last used) and add the label to the §F index.
|
||||
|
||||
## Phase status
|
||||
## Phase status (summary — see README + `docs/dev/findings.md` for detail)
|
||||
|
||||
Do not keep a status summary here — it goes stale. Open work, known issues
|
||||
and decisions owed: **`docs/ROADMAP.md`** (the single tracker). Current
|
||||
release state: the README "Status" section.
|
||||
- **Phase 0–1.5:** done. Build system, boot chain, OS-shim layer, fast3d
|
||||
integration, first frames, full intro rendering.
|
||||
- **Phase 2 (rendering):** in progress. All 21 solo levels load + render +
|
||||
survive an unattended window; front end (menu → mission select → briefing →
|
||||
start) is functional; file-backed EEPROM saves work. Cosmetic defects are
|
||||
parked in `docs/dev/GRAPHICS-BACKLOG.md`.
|
||||
- **Phase 3 (audio + input):** input layer done (`port/src/input.c`); polish
|
||||
bugs open (D118* mouse-look residuals; interactive feel-checks owed). Audio
|
||||
not started — libaudio → SDL, copy-and-adapt from the PD port's `mixer.c`.
|
||||
- **Phase 4 (saves + polish):** file-backed EEPROM done; widescreen, config,
|
||||
rebinding UI outstanding.
|
||||
@@ -16,17 +16,14 @@ Context is scarce. Load by tier; do not blind-read whole files.
|
||||
`docs/HANDOFF.md` (current state + next task + environment — a rolling
|
||||
local working file; may be absent in a fresh clone, in which case read
|
||||
the README "Status" section instead) — read fully;
|
||||
`docs/ROADMAP.md` (the single tracker of open work — read the section
|
||||
relevant to the task);
|
||||
`docs/porting-notes.md` (recurring bug classes) — skim the section
|
||||
headers, read the classes relevant to the task.
|
||||
- **Tier 2 — on demand only, do NOT read start-to-finish:**
|
||||
- `docs/internals.md` — architecture / RSP deltas / phased plan (§1–§10).
|
||||
Read the section you need.
|
||||
- Findings: look up via `docs/dev/findings-index.csv` (label, one-liner,
|
||||
status), then read only the specific `## Dxx` entry in
|
||||
`docs/dev/findings.md` (multi-pass labels like D202/D176(a) have large
|
||||
sections — grep within or offset/limit). Never linear-read.
|
||||
- `docs/dev/findings.md` — the `Dxx` finding log (§F + §H).
|
||||
**Jump to a specific `Dxx` via the index at the top of §F.** Never
|
||||
linear-read.
|
||||
- `docs/dev/HANDOFF-ARCHIVE.md` — prior-session narrative (M-2…). Only
|
||||
when tracing the history of one fix.
|
||||
- `docs/dev-process.md` — before dispatching a subagent (also Tier 1
|
||||
@@ -43,12 +40,6 @@ Having a concrete "next step" from the handoff is NOT license to skip this.
|
||||
2. Skim the README "Status" section for where the project stands.
|
||||
3. If you may dispatch a subagent this session, read `docs/dev-process.md`
|
||||
NOW — not at dispatch time.
|
||||
4. Tree hygiene applies to anything you commit — see AGENTS.md §Tree hygiene &
|
||||
release gate: no local absolute paths or usernames in tracked files (use
|
||||
`<repo>`/`<repos>`/`<games>`/`<videos>`/`<python>`/`<temp>`), no contributor
|
||||
emails in prose (name + PR number), every vendored third-party license
|
||||
declared in `NOTICE`, no bulk ROM-derived captures in the tree (capture
|
||||
dirs are gitignored), and one batched commit per session — not one per fix.
|
||||
|
||||
## Dispatch preflight — EVERY Agent/Task spawn, no exceptions
|
||||
|
||||
@@ -63,9 +54,6 @@ A subagent brief is INVALID and must not be sent unless it contains all of:
|
||||
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.
|
||||
- **VERIFY** — the exact `tools_pc/verify.sh` / probe invocation that proves
|
||||
the task done, or an explicit "runtime verification impossible here; a human
|
||||
must run X".
|
||||
- Instruction to read `docs/porting-notes.md` first and append any
|
||||
generalisable quirk to it.
|
||||
|
||||
@@ -82,7 +70,7 @@ Full template: `docs/dev-process.md` -> "Investigation-brief template".
|
||||
## Local Qwen dispatch (cost-free, data-local worker)
|
||||
|
||||
A local Qwen3.8-27B is wired in via the `delegate-local` MCP server (see
|
||||
`<repos>\20260902-qwen38claudepair`). Use it as a
|
||||
`C:\Users\james\Source\Repos\20260902-qwen38claudepair`). Use it as a
|
||||
*subagent whose compute runs on-machine* for scoped, checkable work — the lead
|
||||
still owns judgment, planning, and the verification gate.
|
||||
|
||||
@@ -97,27 +85,9 @@ still owns judgment, planning, and the verification gate.
|
||||
- **Backend reality:** ~1 inference slot, slow per token — dispatch
|
||||
**sequentially**, `max_tokens` ~4–8K, treat as batch work. Needs the LiteLLM
|
||||
proxy (`litellm/run.ps1`) + Unsloth Studio both up.
|
||||
- **Corrected 2026-09-04:** the "worker's shell is `cmd.exe`, no grep/cat"
|
||||
claim that used to live here was WRONG — verified directly against the
|
||||
harness's own `_run_bash` (the local `claude-code-delegate-local` harness dir):
|
||||
`grep`, `sed`, `cat`, `ls`, `head`, pipes, and `grep -rn` all work fine
|
||||
(Git-for-Windows/MSYS2 toolchain on PATH). The stale claim, baked into 4 of
|
||||
5 local agent `.md` files, caused a real dispatch (a multi-file
|
||||
cross-referencing investigation) to burn its whole turn budget
|
||||
full-`read_file`-ing files instead of grepping them, hit the turn limit, and
|
||||
returned nothing. Agent files fixed. Always still spot-check the returned
|
||||
artifact against the real files before integrating — the 27B will
|
||||
confidently fabricate if a read fails, independent of this.
|
||||
- **Landing delegate output on its own branch: branch off `main` explicitly,
|
||||
never off whatever's currently checked out.** (2026-09-12: a delegate's
|
||||
docs-only writeup got `git branch <name> <sha>` where `<sha>` happened to
|
||||
sit on top of an unrelated in-flight feature branch — the resulting PR
|
||||
silently carried that branch's whole history, including code that had
|
||||
since been reverted, and it only surfaced as a CI failure (a stale
|
||||
env-probe-index check), not from inspecting the diff.) Before creating the
|
||||
branch, confirm you're branching from `main`/`origin/main` HEAD, not from
|
||||
`HEAD` of the session's current checkout; after pushing, sanity-check with
|
||||
`git log --oneline main..<branch>` that it contains only the commit(s) the
|
||||
delegate's task actually produced.
|
||||
- **Windows quirk:** the worker's shell is `cmd.exe`, not bash — its agent
|
||||
prompt tells it to use `read_file`/`findstr`, not `cat`/`grep`. Always
|
||||
spot-check the returned artifact against the real files before integrating;
|
||||
the 27B will confidently fabricate if a read fails.
|
||||
- Fits the **dispatch preflight** rules above (FILES / BUDGET / ON EXPIRY /
|
||||
CONSTRAINTS / REPORT) — write the `task` string to that shape.
|
||||
@@ -145,13 +145,6 @@ execute_process(COMMAND git rev-parse --short HEAD
|
||||
if(NOT VERSION_HASH)
|
||||
set(VERSION_HASH "unknown")
|
||||
endif()
|
||||
|
||||
# Provenance, printed by --version and at startup (port/src/main.c): a build
|
||||
# keeps pointing at this repository no matter who compiles or renames it.
|
||||
set(VERSION_CODENAME "larkspur")
|
||||
set(GE007_VERSION "0.5.0") # bump on every release
|
||||
set(ORIGIN_URL "https://github.com/jkdansereau/goldeneye-pc-port")
|
||||
|
||||
configure_file(
|
||||
"${CMAKE_SOURCE_DIR}/port/include/versioninfo.h.in"
|
||||
"${CMAKE_BINARY_DIR}/port/include/versioninfo.h" @ONLY)
|
||||
@@ -422,26 +415,7 @@ endif()
|
||||
|
||||
# Absolute symbols inside the reserved N64-DRAM region (port/src/dram.c):
|
||||
# cfb_16 (replaces src/cfb.c) and _bssSegmentEnd (mempool start marker).
|
||||
set(SRC_DRAM_SYMS "${CMAKE_SOURCE_DIR}/port/src/dram_syms.S")
|
||||
|
||||
# D441 high-arena test flavour: relocate the DRAM arena for test builds
|
||||
# (tier 1 = 0x90000000). Build option only — the default build is unchanged.
|
||||
# GE_HIGHARENA_BASE=0x90000000 ./build-pc.sh ntsc-final
|
||||
# or -DGE_HIGHARENA_BASE=0x90000000 at configure time. The value must be a
|
||||
# C/asm integer literal; it overrides PORT_DRAM_V1_BASE in portaddr.h for
|
||||
# C, CXX and ASM (dram.c's mapping and the dram_syms.S absolute symbols both
|
||||
# follow it).
|
||||
set(GE_HIGHARENA_BASE "" CACHE STRING "Override PORT_DRAM_V1_BASE for test builds (high-arena flavour)")
|
||||
if(NOT GE_HIGHARENA_BASE)
|
||||
set(GE_HIGHARENA_BASE "$ENV{GE_HIGHARENA_BASE}")
|
||||
endif()
|
||||
if(GE_HIGHARENA_BASE)
|
||||
message(STATUS "GE_HIGHARENA_BASE = ${GE_HIGHARENA_BASE}")
|
||||
set(_d441_arena_def "-DPORT_DRAM_V1_BASE=${GE_HIGHARENA_BASE}")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_d441_arena_def}")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${_d441_arena_def}")
|
||||
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} ${_d441_arena_def}")
|
||||
endif()
|
||||
set(SRC_DRAM_SYMS "${CMAKE_SOURCE_DIR}/port/src/dram_syms.s")
|
||||
|
||||
# Game code — compiled unmodified.
|
||||
file(GLOB_RECURSE SRC_GAME "${CMAKE_SOURCE_DIR}/src/game/*.c")
|
||||
@@ -601,11 +575,6 @@ target_link_libraries(ge007 ${LIBS})
|
||||
set_target_properties(ge007 PROPERTIES OUTPUT_NAME "${BIN_NAME}")
|
||||
if(WIN32)
|
||||
set_target_properties(ge007 PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||
# Explorer/taskbar icon: the original crosshair mark (cmake/icon/ge007.rc,
|
||||
# a static resource script; the ge007.ico it references is generated by
|
||||
# tools_pc/make_icon.py). Compiled by the resource compiler; ge007.ico is
|
||||
# resolved relative to this .rc file.
|
||||
target_sources(ge007 PRIVATE cmake/icon/ge007.rc)
|
||||
endif()
|
||||
|
||||
message(STATUS "Output binary: ${BIN_NAME}")
|
||||
@@ -11,19 +11,12 @@ keep the port faithful to the original game.
|
||||
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; diagnosis is always allowed.** Trace a bug as
|
||||
deep into the decomp's state as the evidence leads — that's never
|
||||
restricted. Only the *fix* is constrained: the 32→64-bit transition
|
||||
forces a small class of mechanical, semantics-preserving edits — any
|
||||
struct-layout or pointer-width-driven misread caused by 32→64-bit
|
||||
widening, in a ROM-serialized record or a live runtime struct/union alike
|
||||
(see `docs/porting-notes.md` §A1 for the pattern and its tells). These
|
||||
are allowed, must be guarded with `#ifdef PORT`, must change no
|
||||
behaviour, and must be documented. A genuine behavioral difference from
|
||||
real N64 behavior is a separate, much rarer case that needs explicit
|
||||
maintainer sign-off first — see `docs/dev-process.md`'s rule-2 sign-off
|
||||
procedure; it is not something a contributor can self-approve or infer
|
||||
from this exception.
|
||||
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.
|
||||
@@ -32,25 +25,6 @@ keep the port faithful to the original game.
|
||||
clean configure + build for `ntsc-final` and a crash-free run of one level
|
||||
(`./build-pc/ge007.x86_64 -level_09`).
|
||||
|
||||
5. **No game data, ever.** Never commit, upload or attach a ROM or anything
|
||||
extracted from one (textures, audio, models, level data, in-game text,
|
||||
the generated `data/pcmodels-*` / `data/pccg-*` folders) in a pull
|
||||
request or an issue. A small cropped screenshot of a glitch is fine.
|
||||
|
||||
## Bug reports
|
||||
|
||||
Open an [issue](https://github.com/jkdansereau/goldeneye-pc-port/issues/new/choose)
|
||||
with:
|
||||
|
||||
- `ge007.crash.log` if the game crashed (in the folder you launched from),
|
||||
- the output of `ge007.x86_64 --version` (or the release version you
|
||||
downloaded), your OS, and your GPU and driver version,
|
||||
- your ROM's SHA-1 (Windows: `certutil -hashfile <rom> SHA1`, Linux:
|
||||
`sha1sum <rom>`),
|
||||
- what you did, what happened, and what you expected.
|
||||
|
||||
**Never attach the ROM itself.**
|
||||
|
||||
## Style
|
||||
|
||||
- C/C++ formatting follows `.clang-format` and `.editorconfig` in the repo
|
||||
@@ -58,24 +32,6 @@ with:
|
||||
- Port-layer code is plain C11 / C++17, SDL2 + OpenGL, no extra dependencies.
|
||||
- Keep commits focused and describe *why*, not just *what*.
|
||||
|
||||
## Forks and derivative works
|
||||
|
||||
Fork away — the code is MIT and the `port/` layer exists to be extended.
|
||||
Rebranding, localizing and extending are all welcome, and none of them need
|
||||
permission. If you carry the work forward under your own name, please keep
|
||||
two things:
|
||||
|
||||
- **Attribution.** A clear statement, in your README and about page, that the
|
||||
project derives from this one — with a link. The engine prints its origin at
|
||||
startup and in its `--version` output; keeping that intact is part of the
|
||||
ask.
|
||||
- **The record.** `docs/dev/` (the finding log, porting notes, per-level
|
||||
status) is the engineering record behind every fix. Derivative projects are
|
||||
asked to preserve or link to it rather than fork-and-forget.
|
||||
|
||||
Stripping the attribution is the one thing that turns a fork into something
|
||||
we no longer recognize as a descendant of this project.
|
||||
|
||||
## Where to look
|
||||
|
||||
- `docs/internals.md` — how the port is structured.
|
||||
|
||||
@@ -18,19 +18,6 @@ Third-party code vendored into the port layer keeps its own license:
|
||||
libultraship / sm64-port lineage; see port/fast3d/LICENSE.txt.
|
||||
* port/fast3d/glad/ - generated OpenGL loader (glad), public domain / MIT.
|
||||
|
||||
Vendored build tooling keeps its own license (none of it is compiled or linked
|
||||
into the PC port; it belongs to the N64 decompilation build):
|
||||
|
||||
* tools/ido5.3_recomp/rabbitizer/ - disassembler library (rabbitizer), MIT,
|
||||
(c) 2022 Decompollaborate; license text in that directory's LICENSE file.
|
||||
* tools/armips.cpp - assembler (armips), MIT, (c) 2009-2020 Kingcom.
|
||||
* tools/extractor/puff.c, puff.h - puff-style inflate, zlib-style license,
|
||||
(c) Mark Adler.
|
||||
* tools/gzipsrc/ - gzip 1.x sources, GPL-2.0-or-later, (c) 1992-1993
|
||||
Jean-loup Gailly; the GPL-2.0 text is tools/gzipsrc/COPYING. These sources
|
||||
are build tooling only: they are never linked into the port, and they are
|
||||
not part of any download package.
|
||||
|
||||
It does NOT cover:
|
||||
|
||||
* src/, include/, assets/, Makefile, tools/, rsp/, ld/, and the other
|
||||
|
||||
@@ -1,471 +1,253 @@
|
||||
# GoldenEye 007 PC Port
|
||||
|
||||
[](https://github.com/jkdansereau/goldeneye-pc-port/actions/workflows/ci.yml)
|
||||
[](https://github.com/jkdansereau/goldeneye-pc-port/releases)
|
||||

|
||||

|
||||
-orange)
|
||||

|
||||

|
||||

|
||||
|
||||
**GoldenEye 007 (Nintendo 64, 1997) as a native PC game for Windows, Linux
|
||||
and Steam Deck.** Native widescreen, a steady 60 fps, 2–4 player
|
||||
split-screen, mouse and keyboard or a modern controller, rebindable controls
|
||||
and an in-game options menu. No
|
||||
emulator, and not a binary recompilation: the game's own
|
||||
reconstructed C, rebuilt from the
|
||||
[GoldenEye 007 decompilation](https://github.com/n64decomp/007), compiled
|
||||
for your PC and running from your own ROM.
|
||||
**A native PC port of _GoldenEye 007_ (Rare, 1997, Nintendo 64), compiled from
|
||||
the [GoldenEye 007 decompilation](https://github.com/n64decomp/007) — the
|
||||
original N64 game running from reconstructed source, not the Xbox 360 remaster.**
|
||||
The first alpha, **[v0.1.0](../../releases)**, is out for Windows and Linux: it
|
||||
boots, renders the full intro and front end, and runs all 21 solo missions — in
|
||||
a full-campaign playtest 19 of the 21 were completable start to finish (two
|
||||
levels still crash). **There is no audio yet** and rough edges remain; this is
|
||||
Phase 2 of 4. See [Status](#status) and [Background](#background).
|
||||
|
||||
**[Download the latest release](https://github.com/jkdansereau/goldeneye-pc-port/releases)**,
|
||||
then follow the [Quick start](#quick-start).
|
||||
It is also a **research project on AI-agent collaboration in a large,
|
||||
unfamiliar, low-level codebase** — how far two coding agents (a local
|
||||
open-weight model and Claude), driven by one person part-time, can be pushed
|
||||
through ~230 translation units of unmodified big-endian MIPS game code and made
|
||||
to run on a 64-bit desktop. See [Background](#background).
|
||||
|
||||
[Quick start](#quick-start) · [Controls](#controls) · [Features](#features) · [Settings and files](#settings-and-files) · [Troubleshooting](#troubleshooting) · [Roadmap](#roadmap) · [Building](#building-from-source) · [Legal](#legal)
|
||||
Technically, 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 (RSP) is
|
||||
emulated in software; every other hardware surface (video, audio, input,
|
||||
timers, save storage) is shimmed in a dedicated `port/` layer.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **You must supply your own GoldenEye 007 ROM.** This repository and its
|
||||
> downloads contain no Nintendo code or assets, and no ROM. See
|
||||
> [What's a ROM?](#whats-a-rom) and [Legal](#legal).
|
||||
|
||||
**v0.5.0** is the current release. It is a **pre-1.0 release, not a
|
||||
finished product**: the campaign and split-screen multiplayer play end to
|
||||
end, but expect missing pieces (PAL/JP ROMs, macOS) and the occasional
|
||||
breaking change between versions. v1.0 is the target for a feature-complete
|
||||
build.
|
||||
|
||||
**AI disclosure:** this port was developed using AI coding agents (Claude
|
||||
Code and a local open-weight model), directed by one person in their spare time. The
|
||||
project is as much a study of that process as it is a port. See
|
||||
[How it was made](#how-it-was-made).
|
||||
> **You must supply your own GoldenEye 007 ROM.** This repository contains no
|
||||
> Nintendo code or assets, and no ROM. Nothing here is distributable as a
|
||||
> playable game — see [Requirements](#requirements) and [Legal](#legal).
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/media/goldeneye-gh-preview.gif" width="64%"
|
||||
alt="~29 s gameplay montage from live v0.5.0 play sessions">
|
||||
<br><em>All in-engine, running in the port: a ~29 s montage from live
|
||||
v0.5.0 play sessions (the Facility intro, auto-aim in the Archives,
|
||||
the crosshair options and the Dam sniper rifle, Silo, a 2-player rocket kill,
|
||||
the Facility bathroom, the Dam ending) ·
|
||||
<a href="docs/index.md">14 stills in the project index</a></em>
|
||||
<img src="docs/img/attract-bunker1.png" width="32%" alt="Bunker 1 intro camera">
|
||||
<img src="docs/media/goldeneye-demo.gif" width="32%" alt="~15 s of the port running: mission dossier, Facility, Silo, Jungle, Archives">
|
||||
<img src="docs/img/attract-dam.png" width="32%" alt="Dam intro camera">
|
||||
<br><em>In-engine, running in the port — Bunker 1 and Dam attract views, and a
|
||||
~15 s clip (no audio yet, Phase 3): mission dossier → Facility → Silo →
|
||||
Jungle → Archives.</em>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## News
|
||||
|
||||
- **2026-10-07** — **v0.5.0** ([release notes](https://github.com/jkdansereau/goldeneye-pc-port/releases/tag/v0.5.0)), the first release
|
||||
since v0.4.0; it includes the v0.4.1 work, which was never published on
|
||||
its own:
|
||||
- **2–4 player split-screen multiplayer** on every multiplayer map, each
|
||||
player with their own pad, controls and aim settings.
|
||||
- **One options menu** for every setting, laid out like the Perfect Dark
|
||||
port's, with the game's own crosshair as the mouse pointer. Pick the
|
||||
game's own N64 control styles (1.1–2.4) per player, or the Xbox release's
|
||||
presets with per-player pad rebinding; PlayStation and Nintendo pads show
|
||||
their own button names.
|
||||
- **Closer to the N64:** fog and haze (Surface 2's ground fog, the sky at
|
||||
the horizon), automatic-weapon fire rate, sniper and camera zoom, Dam and
|
||||
Caverns water, the Jungle boss fight's timing, guard visibility in
|
||||
widescreen, and the Dam ending camera.
|
||||
- **Fewer crashes and glitches:** a multiplayer crash with the crouched
|
||||
rifle, rockets passing through the ground, and broken geometry around
|
||||
Aztec's shuttle.
|
||||
- **A steady 60 fps on low-end GPUs** (tested on an Intel HD 3000 laptop),
|
||||
with lighter first-launch defaults on Atom/Celeron-class graphics.
|
||||
- **Emulator saves load directly:** copy a 1964 or Project64 save in as
|
||||
`data/ge007.eep` and it is converted on first launch.
|
||||
- Also: more audio, video and control settings (master volume, audio device,
|
||||
trilinear filtering, draw distance up to 800%, PC-friendly crouch and
|
||||
look-ahead defaults), and controllers are picked up again when replugged.
|
||||
- **2026-09-28** — **v0.4.0**: native widescreen, a complete aim system for
|
||||
mouse and controller, in-game key rebinding, crosshair customization,
|
||||
rumble-pak haptics, a rebuilt options overlay, and a broad fidelity-fix
|
||||
pass (water, particles, billboard trees, front-end logo, gunshot SFX, a
|
||||
true stable 60 fps). [Release notes](https://github.com/jkdansereau/goldeneye-pc-port/releases/tag/v0.4.0).
|
||||
- **2026-09-20** — **v0.3.0**: the first release with the complete campaign
|
||||
playable end to end at 60 fps on Windows, Linux, and Steam Deck.
|
||||
[Release notes](https://github.com/jkdansereau/goldeneye-pc-port/releases/tag/v0.3.0).
|
||||
- **2026-09-04 → 2026-09-16** — **v0.1.0 – v0.2.2**: the alpha and beta
|
||||
cycle: build chain, software RSP, first rendered frames, front end, and
|
||||
per-level stabilization across the campaign.
|
||||
|
||||
---
|
||||
|
||||
## System requirements
|
||||
|
||||
**Windows:** Windows 10 or 11, 64-bit. Nothing else to install: the
|
||||
download carries its own runtime libraries and the one-time asset tool (no
|
||||
Python, no emulator).
|
||||
|
||||
**Linux / Steam Deck:** 64-bit x86 Linux with glibc 2.31 or newer (Ubuntu
|
||||
20.04+, Debian 11+, Fedora 32+, SteamOS). SDL2 is bundled, so
|
||||
the tarball runs as-is; it sideloads onto a Steam Deck with nothing installed.
|
||||
|
||||
**Both:** a GPU with OpenGL 3.0 drivers (practically any PC GPU from the last
|
||||
fifteen years; keep the driver up to date). A 2011 laptop with Intel HD
|
||||
3000 graphics holds 60 fps; on very weak integrated GPUs (Atom/Celeron class)
|
||||
the first launch lowers draw distance and anti-aliasing to keep up. A
|
||||
keyboard and mouse, or a controller (Xbox, PlayStation, Switch and most
|
||||
SDL-supported pads); split-screen needs one controller per extra player.
|
||||
|
||||
**Not yet:** macOS, ARM Linux and Windows 7 are on the [roadmap](#roadmap).
|
||||
|
||||
## Quick start
|
||||
|
||||
1. From [Releases](https://github.com/jkdansereau/goldeneye-pc-port/releases),
|
||||
download the **win64.zip** (Windows) or the **linux tarball** (Linux and
|
||||
Steam Deck), and extract it. On Windows, never run the exe from inside the
|
||||
zip.
|
||||
2. Make a `data/` folder next to the executable and put your ROM in it as
|
||||
`ge007.ntsc-final.z64`. Only the **US (NTSC-U)** ROM works today; see
|
||||
[What's a ROM?](#whats-a-rom) to check yours.
|
||||
3. Launch the executable from that folder. Windows may show "Windows
|
||||
protected your PC" (SmartScreen, because the exe is not code-signed):
|
||||
click **More info**, then **Run anyway**.
|
||||
4. The first start takes a few extra seconds: the port reads your ROM and
|
||||
generates its asset files once. After that it starts normally.
|
||||
5. Play. **F10** (pad: **Select**) opens the options overlay; on the file-select
|
||||
screen the *PC Options* entry opens the same overlay. The
|
||||
[Controls](#controls) table lists the default keys.
|
||||
You supply your own **NTSC-U GoldenEye 007 N64 ROM** (`.z64`, big-endian) — no
|
||||
ROM or game asset is included or distributed. Then:
|
||||
|
||||
**Steam Deck:** the Linux tarball is the Deck build. Download it on the Deck
|
||||
(or copy it over from your PC), extract it, add your ROM to `data/`, launch
|
||||
it once, and add the executable as a non-Steam game. On first start it
|
||||
applies Deck-friendly display defaults (native 1280×800 fullscreen); if the
|
||||
resolution is wrong, set it under F10 → *Video → Resolution*.
|
||||
1. Download the Windows or Linux bundle from [Releases](../../releases) and unpack it.
|
||||
2. Make a `data/` folder next to the executable and drop the ROM in as `ge007.ntsc-final.z64`.
|
||||
3. Run the one-time asset step: `python3 prepare-assets/prepare-assets.py` (Python 3.8+, stdlib only).
|
||||
4. Launch the executable from that folder.
|
||||
|
||||
---
|
||||
Building from source instead: see [Building](#building). Read the
|
||||
[Status](#status) caveats first — this is an early alpha.
|
||||
|
||||
## Controls
|
||||
## Contents
|
||||
|
||||
| Action | Keyboard / mouse | Controller |
|
||||
|---------------------|--------------------------|---------------|
|
||||
| Move / strafe | `W` `A` `S` `D` | Left stick (or D-pad) |
|
||||
| Move / turn | Arrow keys | — |
|
||||
| Aim / look | Mouse | Right stick |
|
||||
| Fire | Left mouse | Right trigger |
|
||||
| Aim mode | Right mouse / `LShift` | Left trigger / LB |
|
||||
| Use / interact | `E` | A |
|
||||
| Reload | `R` | X |
|
||||
| Crouch (toggles) | `LCtrl` | Left or right stick click |
|
||||
| Cycle owned gadgets | Watch inventory | B |
|
||||
| Next weapon | Mouse wheel down / `Q` | Y |
|
||||
| Previous weapon | Mouse wheel up | — |
|
||||
| Start | `Enter` / `Tab` | Start |
|
||||
| Options overlay | `F10` | Select |
|
||||
- [Quick start](#quick-start)
|
||||
- [Background](#background)
|
||||
- [How this differs from the other GoldenEye PC projects](#how-this-differs-from-the-other-goldeneye-pc-projects)
|
||||
- [Status](#status)
|
||||
- [Download](#download)
|
||||
- [Requirements](#requirements)
|
||||
- [Building](#building) · [Windows (MSYS2)](#windows-msys2) · [Linux](#linux)
|
||||
- [Running](#running) · [Default controls](#default-controls)
|
||||
- [How it works](#how-it-works)
|
||||
- [Project layout](#project-layout)
|
||||
- [Documentation](#documentation)
|
||||
- [Credits](#credits)
|
||||
- [Legal](#legal)
|
||||
- [License](#license)
|
||||
|
||||
The keyboard defaults are a GEPD-style layout. The controller layout matches
|
||||
Rare's Xbox 1.1 (Jinx) button roles; the other Xbox styles (1.2 Christmas,
|
||||
1.3 Frost, 1.4 Elektra, which swap the stick roles) are selectable under
|
||||
*Controller* (pick a player first, then *Layout preset*). PlayStation and Nintendo controllers
|
||||
show their own button names in the menus.
|
||||
## Background
|
||||
|
||||
**Control style:** *Controller → Control style* (named after the watch's own
|
||||
setting, with Perfect Dark's "Ext" for the port's controls) picks how the
|
||||
game's controller style (the watch's 1.1–1.4 selector) is treated. **Ext**
|
||||
(the default) keeps the port's feel and fixes the style itself, so the watch's
|
||||
selector has no effect. **Original** stops the port from touching the style:
|
||||
pick 1.1, 1.2, 1.3 or 1.4 on the watch (or in the multiplayer setup) and it
|
||||
behaves as on the N64, and the Controller page then offers the game's own
|
||||
styles ("Controller style", 1.1 Honey to 2.4 Goodhead, saved in the Bond file
|
||||
like the watch's) instead of the layout presets, which apply in Ext only. Buttons are passed
|
||||
through as the N64's buttons, so 1.3 and 1.4 move fire and aim exactly as the
|
||||
console does. The 2.x dual-controller styles run faithfully too: the game
|
||||
reads their second stick from the next controller slot, which the port does not
|
||||
map yet (a second pad assigned to that slot should provide it, untested), so with
|
||||
one pad pick a 1.x style. Saves made with earlier builds may hold 1.2 (the port wrote its
|
||||
forced style back into the save), so when you switch to the Original control style, pick
|
||||
your style on the watch once.
|
||||
Beyond the port itself, this is primarily a **research project on agentic
|
||||
software development** — specifically, on AI-agent collaboration in a large,
|
||||
unfamiliar, low-level codebase: how far coding agents can be driven, by one
|
||||
person working part-time, through ~230 translation units of unmodified
|
||||
big-endian MIPS game code, made to run on a 64-bit desktop.
|
||||
|
||||
**Rebinding:** *Key Bindings* in the options overlay rebinds the
|
||||
keyboard and mouse (one list: movement, then actions). Controller buttons are
|
||||
rebindable per controller under *Controller* (pick a player first): set *Layout preset* to
|
||||
*Custom*, select an action and press a pad button (tap B or Esc to cancel,
|
||||
hold B to bind B itself, hold Back to clear; Y clears the selected slot).
|
||||
Each action takes up to two buttons. Menus always keep A/X accept and B/Y
|
||||
cancel, so you can't lock yourself out. There is no warning yet when two
|
||||
actions share a button, and the Xbox release's "left stick aims the
|
||||
crosshair" and "D-pad copies the left stick" behaviors are not reproduced
|
||||
(here the right stick aims and the D-pad strafes, as on the N64).
|
||||
It used two agents:
|
||||
|
||||
**Split-screen:** pick Multiplayer from the main menu. With one controller,
|
||||
keyboard/mouse is player 1 and the controller is player 2; with two or more
|
||||
controllers, the controllers take players 1–4 (`Input.MPMode` in `ge007.ini`
|
||||
changes this). A controller unplugged mid-match keeps its player when
|
||||
plugged back in.
|
||||
- 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 catalog, the SDL input layer, and the front-end flow.
|
||||
|
||||
**Options overlay with a controller** (also how it works on the Deck):
|
||||
**Select** opens it, the D-pad or left stick moves between rows and
|
||||
left/right adjusts the selected value, **A** opens a page or toggles a row,
|
||||
**B** goes back one page (and closes from the top), and **Start** (or Select
|
||||
again) closes. With a keyboard, use `F10`, the arrow keys, Enter and
|
||||
Backspace.
|
||||
The two **handed work back and forth** through shared written artifacts:
|
||||
|
||||
## Features
|
||||
```mermaid
|
||||
flowchart TD
|
||||
H["human<br/>direction · integration · playtest"]
|
||||
C["Claude<br/>(Claude Code)"]
|
||||
Q["Qwen 3.8<br/>(pi, local RTX 5090)"]
|
||||
D[("shared artifacts<br/>findings · notes · handoff doc")]
|
||||
H -->|"scoped task + budget"| C
|
||||
H -->|"scoped task + budget"| Q
|
||||
C -->|"patch + write-up"| D
|
||||
Q -->|"patch + write-up"| D
|
||||
C <-.->|handoff| Q
|
||||
D --> H
|
||||
```
|
||||
|
||||
- **2–4 player split-screen multiplayer** on every MP map, with the N64's own
|
||||
setup flow and per-player HUD.
|
||||
- **Native widescreen** at any aspect ratio: the world is undistorted, the HUD
|
||||
sits at the screen edges, and 4:3 menus are pillarboxed. FOV and draw/LOD
|
||||
distance are adjustable, and AI behaviour stays as on the N64 whatever you
|
||||
set them to.
|
||||
- **The original look in one click:** the *Display mode* row (F10 → *Video*) switches
|
||||
between *Modern* and *Original N64* (it reads *Custom* once you edit a setting it covers),
|
||||
and *Aspect ratio → Original* shows the exact 4:3 frame.
|
||||
- **A steady 60 fps**, with VSync and a frame cap (30 or 60), including on
|
||||
low-end GPUs.
|
||||
- **Graphics options:** resolution, borderless or exclusive fullscreen,
|
||||
a Center window action while windowed, MSAA (up to 16×), anisotropic
|
||||
filtering, and nearest, bilinear, trilinear or the N64's own 3-point
|
||||
texture filter.
|
||||
- **Mouse aim** in your choice of style (the N64's, or centered FPS-style) with
|
||||
per-device sensitivity, smoothing and Y-inversion.
|
||||
- **Modern controller support:** dual-stick layout, deadzones, sensitivity,
|
||||
southpaw, trigger thresholds, rumble-pak vibration, and PlayStation/Nintendo
|
||||
button names.
|
||||
- **Rebindable controls** for keyboard, mouse and each controller.
|
||||
- **HUD and crosshair options:** HUD scale (also scales the in-game options
|
||||
menu), an optional always-on crosshair, a custom RGB crosshair color
|
||||
with adjustable opacity and a health-based color, and a no-hit-flash option.
|
||||
- **Full audio:** in-level music and sound effects, with music, FX and master
|
||||
volume and a choice of output device.
|
||||
- **An in-game options overlay** (F10 / pad Select) for all of the above,
|
||||
saved automatically; the game's crosshair is the mouse pointer, and
|
||||
each page has a *Reset to defaults* row.
|
||||
- **Faithful N64 progression by default.** Opt-in extras: *Skip intro* and
|
||||
*All unlocked* (every level, 007 mode and the full cheat menu, without
|
||||
touching your save), and *Check for updates* (off
|
||||
by default; one request to GitHub per launch when on, see Security).
|
||||
- **Saves in a plain file** next to the game, so backing up is a file copy;
|
||||
1964 and Project64 saves load directly ([details](#using-an-emulator-save)).
|
||||
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.
|
||||
|
||||
## Status
|
||||
**By the numbers:**
|
||||
|
||||
**Fully playable, with a small set of known caveats.** All 20 solo missions
|
||||
and the end-of-campaign credits load, render and run crash-free. v0.5.0 was
|
||||
played on Windows and Steam Deck (campaign spot-checks, 2–4 player
|
||||
split-screen and controller-only sessions), and every level's reference
|
||||
frames were compared pixel by pixel on Windows, Linux and Steam Deck. The
|
||||
full campaign was last played end to end, at Agent difficulty on all three
|
||||
platforms, for v0.4.0. Split-screen was played live in 2P on every
|
||||
multiplayer map and in 4P on Temple.
|
||||
|
||||
**Known issues:** see the **[known-issues table](docs/ROADMAP.md#known-issues)**
|
||||
(what you'll notice, impact, workarounds). The short version: NTSC-U ROMs
|
||||
only in the release packages, and no macOS/ARM builds yet.
|
||||
|
||||
> ***All unlocked* never changes your save** since v0.5.0: it unlocks every
|
||||
> level and cheat in memory only, and switching it off shows your real
|
||||
> progress. v0.4.0 and earlier could write fake unlocks into the save, and
|
||||
> those are not repaired; if you used it on an older build, keep a backup of
|
||||
> `data/ge007.eep`.
|
||||
|
||||
What a release installs (no networking unless you turn on the opt-in update check, which is off by default and makes one HTTPS request to GitHub per launch; no telemetry, no ROM or game assets)
|
||||
and how faithfully the port tracks the original game's logic:
|
||||
[Security](docs/security.md) and [fidelity](docs/fidelity.md) status.
|
||||
The binaries are not code-signed; from v0.5.0 on each release artifact also
|
||||
carries a GitHub build-provenance attestation you can check with
|
||||
`gh attestation verify <file> --repo jkdansereau/goldeneye-pc-port`
|
||||
(see [SECURITY.md](.github/SECURITY.md)).
|
||||
|
||||
---
|
||||
|
||||
## Settings and files
|
||||
|
||||
**Options overlay:** press **F10** (pad: **Select**) in game or in the menus.
|
||||
Changes are saved automatically. Most settings live in `ge007.ini`; the
|
||||
ones that come from the game's own watch menu (auto-aim, aim
|
||||
control, sight, look ahead, ammo on screen, screen size, screen ratio, music
|
||||
and FX volume) are part of your save, as on the N64. Screen size (Full / Wide /
|
||||
Cinema) and screen ratio (Normal / 16:9) combine with the PC **Aspect ratio**
|
||||
setting: Aspect ratio owns the window shape, while the watch's two options
|
||||
only change what the game renders inside it.
|
||||
|
||||
**Where files live:** everything stays inside the game's own folder. The port
|
||||
never writes to `%APPDATA%`, the registry or any system location.
|
||||
|
||||
| File | What it is |
|
||||
| Metric | Value |
|
||||
|---|---|
|
||||
| `data/ge007.ntsc-final.z64` | Your ROM (you put it there). |
|
||||
| `data/ge007.eep` | Your game progress (the N64 save). |
|
||||
| `data/ge007.ini` | Settings, written on first start. |
|
||||
| `data/pcmodels-<region>/`, `data/pccg-<region>/` | Asset files generated from your ROM on first start. Safe to delete: they are made again on the next start. |
|
||||
| `data/ge007.shaders` | A list of the graphics shaders the game used, compiled at startup to avoid stutter. Safe to delete. |
|
||||
| `ge007.crash.log` | Written in the folder you launched from if the game crashes. |
|
||||
| Timeline | ~3 weeks, one person part-time |
|
||||
| Commits | ~320 |
|
||||
| Root-caused bugs logged | ~190 (`D1`–`D196`; some later merged or withdrawn) |
|
||||
| Handoff sessions | ~49 |
|
||||
| ABI edits to game code | 69 files, all `#ifdef PORT` |
|
||||
| Port layer / tooling | ~17k lines C/C++ · ~6k lines Python |
|
||||
| Effort split (milestone-weighted) | ~60% Claude · ~40% local model |
|
||||
|
||||
**Removing ROM-derived content:** the only files on your machine that come
|
||||
from the game are the ROM you put there and the two generated asset folders
|
||||
(`data/pcmodels-<region>/`, `data/pccg-<region>/`). Delete those and every
|
||||
ROM-derived file is gone; your save (`ge007.eep`) and settings (`ge007.ini`)
|
||||
are separate files, yours to keep or delete.
|
||||
The local model built the entire foundation — build, boot chain, RSP wiring,
|
||||
asset converters — then both agents ran the debugging phase together:
|
||||
|
||||
**`ge007.ini`** uses `[Section]` headers and `Key = value` lines. A few
|
||||
common keys and their defaults:
|
||||
- **Day 4** — the full ~230-unit codebase compiles and links
|
||||
- **Day 8** — the whole intro renders
|
||||
- **Day 13** — all 21 solo missions run crash-free
|
||||
- **Day 14** — front end playable end to end
|
||||
- **Week 3** — v0.1.0 alpha; a full-campaign playtest completes 19 of 21 missions
|
||||
|
||||
```ini
|
||||
[Video]
|
||||
Fullscreen = 0
|
||||
VSync = 1
|
||||
FpsCap = 60
|
||||
MSAA = 2
|
||||
TextureFilter = 1
|
||||
NativeWidescreen = 1
|
||||
FovScale = 100
|
||||
DrawDistance = 200
|
||||
|
||||
[Audio]
|
||||
MasterVolume = 100
|
||||
```
|
||||
|
||||
`Fullscreen` 1 is borderless fullscreen; `TextureFilter` is 0 nearest,
|
||||
1 bilinear, 2 the N64 3-point filter, 3 trilinear; `FovScale` and `DrawDistance` are
|
||||
percentages of the original (the options overlay shows the field of view in
|
||||
degrees). On Atom/Celeron-class GPUs the first launch writes lighter values
|
||||
for draw distance, LOD distance and `MSAA`. Edit the file with the game
|
||||
closed: it is rewritten on a clean exit, and comments are dropped.
|
||||
|
||||
**Launch options:** `-fresh` wipes the save and settings before starting (a
|
||||
clean-slate run). `--version` prints the build id.
|
||||
|
||||
## What's a ROM?
|
||||
|
||||
A ROM is a copy of the game cartridge's data, made with a cartridge dumper
|
||||
from a cartridge you own. This project can't provide or link one. The port
|
||||
needs the big-endian `.z64` format, and supports the US version today:
|
||||
|
||||
| Region | ROM filename (in `data/`) | SHA-1 | Supported |
|
||||
|--------|---------------------------|-------|-----------|
|
||||
| NTSC-U (US) | `ge007.ntsc-final.z64` | `abe01e4aeb033b6c0836819f549c791b26cfde83` | yes |
|
||||
| PAL (EU) | `ge007.pal-final.z64` | `167c3c433dec1f1eb921736f7d53fac8cb45ee31` | not yet ([#85](https://github.com/jkdansereau/goldeneye-pc-port/issues/85)) |
|
||||
| NTSC-J (JP) | `ge007.jpn-final.z64` | `2a5dade32f7fad6c73c659d2026994632c1b3174` | not yet ([#85](https://github.com/jkdansereau/goldeneye-pc-port/issues/85)) |
|
||||
|
||||
To check which file you have, compute its SHA-1: on Windows,
|
||||
`certutil -hashfile ge007.ntsc-final.z64 SHA1`; on Linux,
|
||||
`sha1sum ge007.ntsc-final.z64`.
|
||||
|
||||
## Updating
|
||||
|
||||
Extract the new release into a new folder, then copy your ROM, `ge007.eep`
|
||||
and `ge007.ini` from the old `data/` folder into the new one. Don't copy the
|
||||
generated `pcmodels-*` and `pccg-*` folders: the new version makes its own on first start, and
|
||||
stale ones from an older version can cause glitches.
|
||||
|
||||
## Using an emulator save
|
||||
|
||||
Saves from N64 emulators (1964, Project64, …) load directly: with the game
|
||||
closed, just copy your `.eep` to `data/ge007.eep`. On first launch the game
|
||||
detects the emulator byte order and converts it in place, keeping a backup of
|
||||
the original file next to it as `ge007.eep.emulator.bak` (never overwriting an
|
||||
existing one). Any slot that is corrupt in both formats is left alone and gets
|
||||
reset by the game, exactly as before. An existing port save is untouched.
|
||||
|
||||
To take a save back into an emulator, use the small converter that ships in
|
||||
the release folder as `tools/eep_convert.py`
|
||||
(`tools_pc/eep_convert.py` in a source checkout; plain Python 3, no
|
||||
dependencies):
|
||||
|
||||
```sh
|
||||
python tools/eep_convert.py pc-to-n64 data/ge007.eep back-to-emulator.eep # port -> emulator
|
||||
python tools/eep_convert.py verify some-file.eep # which format is this?
|
||||
```
|
||||
|
||||
`verify` checks every save checksum and, without `--as n64|pc`, tells you
|
||||
which convention the file uses.
|
||||
|
||||
## Uninstalling
|
||||
|
||||
Delete the folder. The port writes nothing anywhere else, so that removes the
|
||||
game, your save and your settings; back up `data/ge007.eep` first if you
|
||||
want to keep your progress.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **The game says the ROM is missing or wrong:** check it is in `data/`, named
|
||||
`ge007.ntsc-final.z64`, in `.z64` byte order, and that its SHA-1 matches the
|
||||
US row in [What's a ROM?](#whats-a-rom). PAL and JP ROMs are not supported
|
||||
yet.
|
||||
- **Windows shows a SmartScreen warning:** the exe is not code-signed. Click
|
||||
**More info**, then **Run anyway**.
|
||||
- **Black screen or no window:** update your graphics driver; the port needs
|
||||
OpenGL 3.0.
|
||||
- **Wrong resolution on the Steam Deck:** F10 → *Video → Resolution*.
|
||||
- **Where are my saves?** `data/ge007.eep`. Copy it somewhere safe while the
|
||||
game is not running to back it up.
|
||||
- **The game crashes:** open an
|
||||
[issue](https://github.com/jkdansereau/goldeneye-pc-port/issues/new/choose)
|
||||
and attach `ge007.crash.log` (in the folder you launched from), the output of
|
||||
`--version`, your OS, your GPU and your ROM's SHA-1. **Never attach the ROM
|
||||
itself.**
|
||||
|
||||
---
|
||||
|
||||
## Roadmap
|
||||
|
||||
The goal is a **feature-complete, faithful** port: every mode and region the
|
||||
N64 cartridge ships, behaving 1:1 with the original, with modern options on
|
||||
top that can always be switched back to the original look and feel. The full
|
||||
list of open items, and which ones are waiting on a decision, lives in one
|
||||
place: [`docs/ROADMAP.md`](docs/ROADMAP.md). In brief:
|
||||
|
||||
- **PAL and JP ROM support** in the release packages
|
||||
([#85](https://github.com/jkdansereau/goldeneye-pc-port/issues/85)). Both
|
||||
regions already convert, build and boot from source.
|
||||
- **macOS and ARM builds**.
|
||||
- The remaining small accuracy differences, each checked against the N64
|
||||
game in an emulator.
|
||||
- **1.0 sign-off**: a full campaign playthrough at more than one difficulty
|
||||
plus a split-screen session, on every platform that ships.
|
||||
|
||||
This is spare-time work, so there's no timeline. Opt-in extras beyond the N64
|
||||
game (LAN play, bots, co-op, HD textures, enhanced visuals, frame rates above
|
||||
60) come after 1.0, and always off by default. Not planned: online
|
||||
multiplayer over the internet, achievements or cloud saves, remake-scope
|
||||
assets (new models, music or voice), and new movement mechanics. If any of
|
||||
these matter to you, open an issue — it helps prioritize.
|
||||
|
||||
## How it was made
|
||||
|
||||
The port was built by several coding agents directed by one maintainer, with
|
||||
contributions from a handful of people (credited in the release notes).
|
||||
A local open-weight model (`unsloth/Qwen3.8-27B-GGUF` on one RTX 5090, via the
|
||||
[pi](https://github.com/earendil-works/pi) agent) did the groundwork: build, boot chain,
|
||||
software-RSP integration, asset pipeline, first frames. **Claude Code**
|
||||
(Sonnet 5 and Opus 5, now 5.5; Opus for the hardest bugs) joined for the collaborative phase:
|
||||
the 21-level sweep, the ABI finding catalog, SDL input, front end. The two
|
||||
handed work back and forth through shared written notes. Up to v0.5.0: 51
|
||||
days (16 Aug – 6 Oct), 1,537 commits (the public history squashes the
|
||||
v0.4.1–v0.5.0 work into 17), and 419 findings root-caused and logged
|
||||
(`D1`–`D564`).
|
||||
|
||||
The full write-up (timeline, handoff mechanism, effort breakdown, 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). To cite the
|
||||
project or its findings: [`CITATION.cff`](CITATION.cff) (GitHub's "Cite this
|
||||
repository" menu).
|
||||
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). To cite this
|
||||
project or its findings, use [`CITATION.cff`](CITATION.cff) (GitHub's "Cite
|
||||
this repository" menu).
|
||||
|
||||
## How this differs from the other GoldenEye PC projects
|
||||
|
||||
This is a native port of the original 1997 Nintendo 64 game, built from its
|
||||
actual reconstructed source code, the same lineage as the Perfect Dark PC
|
||||
port. The other well-known "GoldenEye on PC" projects are something
|
||||
different: they machine-translate the shipped binary of the *unreleased Xbox
|
||||
360 XBLA remaster*, a different game on a different codebase with no shared
|
||||
code with this one.
|
||||
This is a **native port of the original 1997 Nintendo 64 game, built from its
|
||||
actual reconstructed source code** — the same lineage as the Perfect Dark PC
|
||||
port. The other well-known "GoldenEye on PC" projects are something different:
|
||||
they machine-translate the shipped binary of the *unreleased Xbox 360 XBLA
|
||||
remaster* — a different game, a different codebase, no shared code with this.
|
||||
|
||||
| | This project | The other well-known "GoldenEye on PC" recompilation projects (and their Steam Deck builds) |
|
||||
| | This project | [GoldenEye-Recomp](https://github.com/SunJaycy/GoldenEye-Recomp) / [Steam Deck build](https://github.com/couchk1ng/GoldenEye-Recomp-SteamDeck) |
|
||||
|---|---|---|
|
||||
| **What it ports** | The original **Nintendo 64** game (1997) | The **Xbox 360 XBLA** HD remaster (built ~2007, never released) |
|
||||
| **How** | Decompilation-based source port: human-reconstructed C, compiled for the host; game logic runs as written | Static binary recompilation: the shipped machine code is auto-translated to C; the source is machine-generated |
|
||||
| **How** | **Decompilation-based source port** — human-reconstructed C, compiled for the host; game logic runs as written | **Static binary recompilation** — the shipped machine code is auto-translated to C; no source-level understanding |
|
||||
| **Lineage** | [GoldenEye 007 decompilation](https://github.com/n64decomp/007) + [Perfect Dark PC port](https://github.com/fgsfdsfgs/perfect_dark) engine family | Xbox 360 "…Recompiled" static-recompilation family |
|
||||
| **Renderer** | Software RSP → OpenGL | Hardware (Vulkan) |
|
||||
| **Status** | Pre-1.0 releases; full campaign and split-screen playable at 60 fps (see [Status](#status)) | Playable full game |
|
||||
| **Why it exists** | To run the *original* N64 game from source, and as a [case study in AI-agent collaboration](#how-it-was-made) on a hard low-level codebase | To get a playable PC release of the remaster |
|
||||
| **Status** | WIP, Phase 2, alpha v0.1.0 — runs all 21 levels, 19/21 completable in a full playthrough; no audio; rough edges | Playable full game, higher frame rates, online multiplayer |
|
||||
| **Why it exists** | A [case study in AI-agent collaboration](#background) on a hard low-level codebase; the port is the target, not a product | A polished, playable PC release of the remaster |
|
||||
|
||||
---
|
||||
**If you just want to play GoldenEye on PC today, use one of the recompilation
|
||||
projects — they are finished and this is not.** What is interesting here is the
|
||||
other half: getting the *original* game running from source, and how much of
|
||||
that work was driven by AI agents.
|
||||
|
||||
## Building from source
|
||||
## Status
|
||||
|
||||
Prerequisites: CMake >= 3.16, a C/C++ toolchain, SDL2, zlib, OpenGL and
|
||||
Python 3, plus MIPS binutils, `make` and `git` for the one-time asset
|
||||
extraction (no IDO/IRIX toolchain is involved; on Windows the extraction is
|
||||
easiest under WSL). See [`docs/building.md`](docs/building.md) for the full
|
||||
walkthrough and the asset-extraction details.
|
||||
> [!WARNING]
|
||||
> **Alpha — playable, not polished.** This is a work-in-progress research port.
|
||||
> It runs the full single-player campaign, but there is **no audio**, some
|
||||
> front-end 3D models and cutscenes are broken, AI characters move too slowly,
|
||||
> input has known rough edges, and two levels (Bunker ii, Statue) still crash.
|
||||
> Treat it as an early alpha of the porting work, not a finished way to play
|
||||
> GoldenEye.
|
||||
|
||||
**Phase 2 of 4 (rendering). First alpha: [v0.1.0](../../releases).** The port
|
||||
boots, renders, and plays through the front end and the campaign. It is not
|
||||
finished and it is not fully 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 and render.** In a full-campaign playtest on the
|
||||
packaged Windows build, **19 of 21 were completable start to finish**; Bunker
|
||||
ii and Statue crash mid-level (one root cause, tracked as D191). 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.
|
||||
- **Windows and Linux** (`x86_64`). Windows is the primary development and
|
||||
playtest path; the Linux build boots, renders, and passes the level sweep,
|
||||
with far less human playtime.
|
||||
|
||||
**Not yet working**
|
||||
|
||||
- **Audio** — not implemented (Phase 3). The game runs silent.
|
||||
- **AI pacing** — scripted and combat AI characters travel to their
|
||||
destinations noticeably slower than on N64. This breaks Cradle (the final
|
||||
level) via Trevelyan's scripted progression. Top post-alpha fix (D193).
|
||||
- **Cutscenes** — frequently glitch: skipped, wrong camera, misplaced or
|
||||
hovering actors, wrong timing (D148/D160).
|
||||
- Some front-end 3D models — the spinning Nintendo logo, and the MISSION
|
||||
COMPLETE / mode-select models — are mispositioned or absent. (The
|
||||
gun-barrel Bond intro renders correctly.)
|
||||
- Outdoor levels render with a **black sky**; assorted other cosmetic defects
|
||||
are tracked in
|
||||
[`docs/dev/GRAPHICS-BACKLOG.md`](docs/dev/GRAPHICS-BACKLOG.md).
|
||||
- No macOS or ARM support; no controller rebinding UI; no widescreen.
|
||||
|
||||
Next up: audio (Phase 3), the AI-pacing fix, and the two remaining level
|
||||
crashes. Cosmetic defects are tracked in
|
||||
[`docs/dev/GRAPHICS-BACKLOG.md`](docs/dev/GRAPHICS-BACKLOG.md).
|
||||
|
||||
## Download
|
||||
|
||||
Pre-built **Windows** and **Linux** `x86_64` bundles are published under
|
||||
[Releases](../../releases), starting with **v0.1.0**. Each contains the engine
|
||||
executable, its runtime libraries, and the one-time `prepare-assets` tool —
|
||||
**no ROM and no game assets**. See [Quick start](#quick-start) for the four
|
||||
steps to get it running, [Requirements](#requirements) for accepted ROMs, and
|
||||
[Status](#status) for the alpha caveats.
|
||||
|
||||
You can also build it yourself; see [Building](#building).
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -479,13 +261,13 @@ 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 # NTSC-U; PAL/JP: see docs/building.md
|
||||
./build-pc.sh ntsc-final # or pal-final / jpn-final
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
The Linux build is compiled on every push by CI (ubuntu-24.04); the release
|
||||
bundle additionally bundles SDL2 so no system packages are needed at runtime.
|
||||
> **Untested.** No one has built or run the port on Linux. The steps below are
|
||||
> the intended path, not a verified one — expect to fix build issues yourself.
|
||||
|
||||
```sh
|
||||
sudo apt install build-essential cmake python3 libsdl2-dev zlib1g-dev libgl1-mesa-dev
|
||||
@@ -496,38 +278,66 @@ cd goldeneye-pc-port
|
||||
```
|
||||
|
||||
The executable is written to `build-pc/ge007.x86_64` (on Windows,
|
||||
`build-pc/ge007.x86_64.exe`). To run a source build, create `data/` in the
|
||||
repo root, put your ROM in it as in [What's a ROM?](#whats-a-rom), and run
|
||||
`./build-pc/ge007.x86_64` from the repo root.
|
||||
`build-pc/ge007.x86_64.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` / `X` | 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 game code in `src/` is the decompilation's, and its control flow is the
|
||||
reference we preserve. Everything that would touch N64 hardware is redirected into
|
||||
`port/`: a **software RSP** (`port/fast3d/`, adapted from the Perfect Dark
|
||||
port) that interprets the graphics command stream (the GBI display list)
|
||||
the game builds each frame and
|
||||
emits OpenGL, bypassing the RDP; libultra OS shims (`port/src/libultra.c`,
|
||||
game threads as real host threads) that let the game's own scheduler run,
|
||||
with the software RSP on its own render thread as the RSP was its own chip;
|
||||
and SDL2/OpenGL/filesystem backends for video, audio, input and storage.
|
||||
Every port change inside `src/` is gated behind `#ifdef PORT` and
|
||||
documented. They are few in kind, all narrow in scope: mechanical 32→64-bit
|
||||
pointer/struct fixes (the pointer-width transition touches many structs, so
|
||||
there are a lot of them), a few maintainer-approved timing fixes where
|
||||
per-frame N64 code ran too fast at the PC's 60 fps (for example sniper zoom
|
||||
and turret fire), the opt-in *All unlocked* hook, and env-gated diagnostic
|
||||
probes tied to the bug catalogue (off by default, no behavior change). Where it diverges from the Perfect Dark port: GoldenEye's N64
|
||||
serialized asset formats are converted in a separate step rather than
|
||||
fixed up at load time — on first start for a download, or by the offline
|
||||
Python converters in `tools_pc/` for a source build. Full detail:
|
||||
[`docs/internals.md`](docs/internals.md) and
|
||||
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, cataloged class of mechanical
|
||||
ABI-only edits to ROM-serialized structs (pointer-width reconciliation); these
|
||||
change no behavior 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); port edits are #ifdef PORT
|
||||
src/ include/ the decompilation (game + libultra) - compiled unmodified
|
||||
port/
|
||||
fast3d/ software RSP -> OpenGL
|
||||
src/ port layer (main, OS shims, video, audio, input, fs, ...)
|
||||
@@ -540,28 +350,18 @@ docs/ see below
|
||||
## Documentation
|
||||
|
||||
Key docs are also published as a site:
|
||||
<https://jkdansereau.github.io/goldeneye-pc-port/>.
|
||||
**<https://jkdansereau.github.io/goldeneye-pc-port/>**.
|
||||
|
||||
| Doc | What's in it |
|
||||
|---|---|
|
||||
| [`docs/ROADMAP.md`](docs/ROADMAP.md) | The single tracker of open work and known issues. |
|
||||
| [`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/game-behavior-reference.md`](docs/dev/game-behavior-reference.md) | How the retail N64 game is meant to behave — combat/AI model, difficulty scaling, per-level objectives, timers, weapon data, original-game quirks. Playtest reference. |
|
||||
| [`docs/dev/agentic-development.md`](docs/dev/agentic-development.md) | The research angle: the two-agent setup, timeline, handoff workflow, and an 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, playtest matrices, and [`docs/dev/game-behavior-reference.md`](docs/dev/game-behavior-reference.md) (a secondary-sourced playtest reference for how the retail game is meant to behave; repo-only, code is ground truth). |
|
||||
| [`docs/SetupGuide.md`](docs/SetupGuide.md), [`docs/StyleGuide.md`](docs/StyleGuide.md) | Inherited from the decompilation this repo forks. |
|
||||
|
||||
## Contributing and forking
|
||||
|
||||
Issues and pull requests are welcome, and so are forks: the port layer, build
|
||||
system and `tools_pc/` are MIT-licensed and exist to be extended. Read
|
||||
[`CONTRIBUTING.md`](CONTRIBUTING.md) first: it has the ground rules that keep
|
||||
the port faithful (game logic stays unmodified), what to verify before a
|
||||
pull request, and what a fork is asked to keep.
|
||||
|
||||
---
|
||||
| [`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
|
||||
|
||||
@@ -569,30 +369,24 @@ 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
|
||||
- The [GoldenEye 007 decompilation](https://github.com/n64decomp/007) — years of
|
||||
effort by Larry Ficken ("kholdfuzion") 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
|
||||
(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 [Perfect Dark decompilation](https://github.com/n64decomp/perfect_dark) —
|
||||
the sibling decomp the PD port is built on.
|
||||
- **Carnivorous**: author of the *Mouse Injector* input plugin for 1964 (the
|
||||
"GEPD Edition" bundle). Its mouse-aim behaviour for GoldenEye/Perfect Dark is
|
||||
what the in-game GEPD-style aim mode is modelled on; the implementation here
|
||||
is an independent reimplementation of that behaviour, not derived code.
|
||||
Thanks also to **Rice** and **schibo** of the 1964 team for the emulator it
|
||||
shipped with.
|
||||
|
||||
**Vendored / adapted code**
|
||||
|
||||
- `port/fast3d/`: the software RSP, adapted from the PD port. It originates
|
||||
- `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`),
|
||||
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
|
||||
- `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
|
||||
@@ -602,102 +396,48 @@ This port is a thin layer on a large amount of other people's work.
|
||||
**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
|
||||
Kids decompilation write-up) — the agent-workflow practices in
|
||||
[`docs/dev-process.md`](docs/dev-process.md) are adapted from it.
|
||||
|
||||
**Contributors**
|
||||
|
||||
- **dolent** (#120, #121, #122, #123), **italoarruda** (#107, #109),
|
||||
**JosephAHK** (#133), and **MST246** (the #125 investigation), plus
|
||||
everyone who filed issues.
|
||||
|
||||
**Consultation**
|
||||
|
||||
- **f1zz1ec0ke** ([GitHub](https://github.com/f1zz1ec0ke)): LLM consultation on model
|
||||
tuning, agent harnesses, and agentic strategy throughout the port's development.
|
||||
|
||||
**Tools and models used to develop the port**
|
||||
|
||||
Development ran for most of its life on a single local model — **Unsloth's
|
||||
Qwen 3.8 27B** — which did nearly all of the local (groundwork) work on its
|
||||
own. In the final week that grew into a small menu, and the **current standard
|
||||
is NInfer + Strata**; tools are chosen per task, with local models carrying
|
||||
the high-volume work and hosted models the exception. The full record of the
|
||||
models used, and the harnesses that drove them:
|
||||
|
||||
| Model | Ran as | How it was used |
|
||||
|---|---|---|
|
||||
| **Qwen 3.8 27B** ([Unsloth](https://huggingface.co/unsloth/Qwen3.8-27B-GGUF)) | `UD-Q4_K_XL`, one RTX 5090 | The primary local workhorse for the bulk of the port's development. |
|
||||
| **Qwen 3.8 27B** via [NInfer](https://github.com/natpate/ninfer-windows) (Windows) | base, NVFP4 and NVFP4-full artifacts; MTP (DFlash2) spec-decode; one 5090 | Trialed in the final week, now half of the standard; the fast day-to-day local driver. |
|
||||
| **Qwen 3.8 27B** ([ISTA-DASLab](https://huggingface.co/ISTA-DASLab/Qwen3.8-27B-GSQ-RCO-GGUF)) | `IQ3_S` (GSQ-RCO), larger context | Briefly trialed for larger-context workloads; shelved, little use since v0.2.0. |
|
||||
| **Qwen 3.8 Flash-Next** via [Strata](https://github.com/Niko1221/Strata) | 125B MoE, `IQ1_M` (1-bit), one gaming GPU | The big-context local tier: a model that normally needs a server, run locally for the largest-context passes. |
|
||||
| **Claude** (Anthropic) | Sonnet 5 / Opus 5, now **Sonnet 5.5 / Opus 5.5** (out in the last few weeks) | The orchestrator in the two-agent phase: held what the 27B couldn't close, took over on limit handoff; the 5.5 tier has driven the most recent work. |
|
||||
| **GPT-6 (Sol)** (OpenAI), via [OpenRouter](https://openrouter.ai) | a frontier hosted model | Trialed. |
|
||||
|
||||
*Harnesses and tooling:*
|
||||
|
||||
- **[pi](https://github.com/earendil-works/pi)** — the local coding-agent
|
||||
harness that drives the local models.
|
||||
- **[Claude Code](https://github.com/anthropics/claude-code)** (Anthropic) —
|
||||
the agentic harness that drove the Claude models above; the orchestrator in
|
||||
the two-agent phase.
|
||||
- **[claude-code-delegate-local](https://github.com/fegone/claude-code-delegate-local)** —
|
||||
the MCP bridge that let Claude and the local models talk (delegating Claude's
|
||||
subagents to the local engines).
|
||||
- Qwen 3.8 (Alibaba Qwen team), run locally as the
|
||||
[`unsloth/Qwen3.8-27B-GGUF`](https://huggingface.co/unsloth/Qwen3.8-27B-GGUF)
|
||||
`UD-Q4_K_XL` quant;
|
||||
[Unsloth](https://unsloth.ai) (the GGUF quantisation and Unsloth Desktop,
|
||||
used as the local model server).
|
||||
- [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 is **not affiliated with, endorsed by, or sponsored by** Nintendo,
|
||||
Rare, Microsoft, Valve, MGM, Danjaq, EON Productions, or any rights holder in
|
||||
GoldenEye or James Bond. "GoldenEye 007", "007", "James Bond" and related
|
||||
marks belong to their respective owners; they are used here only to say which
|
||||
game this port runs. No official logos, box art, or marketing assets are used.
|
||||
GitHub. It follows the same conventions they do:
|
||||
|
||||
### What the download contains
|
||||
- **No ROM and no game assets are distributed** — not in this repository and
|
||||
not in any release. 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).
|
||||
- **Pre-built binaries** published under [Releases](../../releases) contain
|
||||
only the engine — the `port/` layer plus the compiled decompilation, with no
|
||||
game data of any kind — bundled with permissively-licensed runtime libraries
|
||||
(SDL2, zlib, the MinGW runtime; their licenses travel in the download). Any
|
||||
build — yours or ours — is useless without a ROM you supply.
|
||||
|
||||
- **The engine:** the `port/` layer plus the compiled decompilation.
|
||||
- **Runtime libraries** under permissive licenses (SDL2, zlib, the MinGW
|
||||
runtime); their licenses travel in the download.
|
||||
- **The one-time asset tool**, which reads your ROM on your machine.
|
||||
|
||||
It contains **no ROM and no game assets of any kind**. Any build, yours or a
|
||||
release, is useless without a ROM you supply.
|
||||
|
||||
### What happens on your computer
|
||||
|
||||
Textures, audio, models, level data and in-game text are extracted from a ROM
|
||||
*you already own*, on *your* machine, the first time you start the game. The
|
||||
port has no network access unless you turn on the opt-in *Check for updates* option (`Game.CheckUpdates`, off by default). When on, it makes one HTTPS request per launch to GitHub's releases API to look for a newer release (GitHub sees the request and your IP, as with any web request), never downloads or installs anything, and never uploads anything.
|
||||
|
||||
### This repository
|
||||
|
||||
The repository is a fork of the public
|
||||
[GoldenEye 007 decompilation](https://github.com/n64decomp/007) and inherits
|
||||
its contents (see [`NOTICE`](NOTICE) for what that includes). The few
|
||||
port-specific edits inside the decompiled code are marked `#ifdef PORT`.
|
||||
|
||||
The project mark / favicon is original *generated* art -- a generic version
|
||||
of the game's default aim cross (a circle with N/S/E/W lines crossing its
|
||||
edge), no game screenshots or logos in it.
|
||||
|
||||
### Your part
|
||||
|
||||
- Only use a ROM that you dumped from a cartridge you own.
|
||||
- Never ask for, link to or share ROMs or extracted game files in issues,
|
||||
discussions or pull requests.
|
||||
|
||||
If you are a rights holder with a concern, open an issue and it will be
|
||||
addressed.
|
||||
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;
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
*Last updated 2026-10-07 — v0.5.0 · <a href="https://github.com/jkdansereau/goldeneye-pc-port">GitHub</a> · <a href="https://github.com/jkdansereau/goldeneye-pc-port/releases">Releases</a>*
|
||||
@@ -14,7 +14,7 @@
|
||||
# Example (Linux):
|
||||
# sudo apt install cmake libsdl2-dev zlib1g-dev libgl1-mesa-dev
|
||||
# Example (macOS):
|
||||
# brew install cmake libsdl2-dev
|
||||
# brew install cmake sdl2 zlib
|
||||
# Example (Windows/MSYS2):
|
||||
# pacman -S mingw-w64-x86_64-toolchain mingw-w64-x86_64-SDL2 \
|
||||
# mingw-w64-x86_64-zlib mingw-w64-x86_64-cmake
|
||||
@@ -24,226 +24,6 @@ set -euo pipefail
|
||||
ROMID="${1:-ntsc-final}"
|
||||
BUILD_DIR="${BUILD_DIR:-build-pc}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Windows toolchain env guard (see AGENTS.md "Build" — the recurring
|
||||
# "Cannot create temporary file in C:\Windows\: Permission denied" link
|
||||
# failure).
|
||||
#
|
||||
# The PE toolchain (ninja -> cmd -> gcc/ld) creates temp files under the
|
||||
# child's TMP/TEMP. A full MSYS2 MINGW64 login shell arranges that for
|
||||
# native children; other shells (agent harnesses, non-login shells) do not
|
||||
# -- the msys->native env conversion silently drops/breaks TMP and the
|
||||
# linker falls back to C:\Windows\. Probe what a native child actually
|
||||
# sees (via a file: msys console emulation makes piped cmd.exe stdout
|
||||
# unreliable), and if broken, re-run the cmake+build steps under
|
||||
# PowerShell. Crucially the temp vars are set INSIDE PowerShell: the broken
|
||||
# boundary is msys->native, while native->native (powershell -> cmake ->
|
||||
# ninja) passes env vars through intact.
|
||||
# GE_PC_BUILD_VIA_NATIVE=1 prevents a re-exec loop.
|
||||
# ---------------------------------------------------------------------------
|
||||
# Locate the MSYS2 MinGW64 toolchain dir (cmake.exe + gcc.exe, inside an
|
||||
# MSYS2 install). Whatever `cmake`/`gcc` happen to be first on PATH is not
|
||||
# trustworthy: Git Bash's /mingw64 has no toolchain, and a pip-installed
|
||||
# cmake in a Python Scripts dir can shadow the MSYS2 one. GE_MSYS2_ROOT
|
||||
# (e.g. C:\msys64) overrides the search.
|
||||
_ge_find_mingw_bin() {
|
||||
local c root
|
||||
root="${GE_MSYS2_ROOT:-}"
|
||||
[ -n "${root}" ] && root="$(cygpath -u "${root}" 2>/dev/null || echo "${root}")"
|
||||
for c in \
|
||||
${root:+"${root}/mingw64/bin"} \
|
||||
"$(dirname "$(command -v gcc 2>/dev/null || echo /nonexistent/gcc)")" \
|
||||
"$(dirname "$(command -v cmake 2>/dev/null || echo /nonexistent/cmake)")" \
|
||||
"/mingw64/bin" \
|
||||
"/c/msys64/mingw64/bin"; do
|
||||
if [ -f "${c}/cmake.exe" ] && [ -f "${c}/gcc.exe" ] && [ -f "${c}/../../usr/bin/msys-2.0.dll" ]; then
|
||||
(cd "${c}" && pwd)
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if [ -n "${MSYSTEM:-}" ] && [ -z "${GE_PC_BUILD_VIA_NATIVE:-}" ]; then
|
||||
_mingw_bin="$(_ge_find_mingw_bin)" || {
|
||||
echo "ERROR: could not find the MSYS2 MinGW64 toolchain (mingw64/bin with cmake.exe + gcc.exe)." >&2
|
||||
echo "Install it (see docs/building.md) or point GE_MSYS2_ROOT at the MSYS2 root, e.g. GE_MSYS2_ROOT='C:\msys64'." >&2
|
||||
exit 1
|
||||
}
|
||||
# Failure mode 3 (AGENTS.md): the toolchain's DLL dir must be on PATH,
|
||||
# and ahead of any other cmake/gcc. Only mingw64/bin — NOT usr/bin:
|
||||
# from a Git Bash shell that would swap in MSYS2's cygpath/mktemp,
|
||||
# whose /tmp is a different Windows dir than this bash's /tmp.
|
||||
export PATH="${_mingw_bin}:${PATH}"
|
||||
# Every path that crosses to a native process lives in the build dir and
|
||||
# is converted with bash's own `pwd -W` — never /tmp + cygpath, which
|
||||
# silently disagree when two msys runtimes are on PATH (Git Bash + MSYS2:
|
||||
# the .ps1 was written to one /tmp and powershell was pointed at the
|
||||
# other; the TMP probe likewise always read "unusable").
|
||||
mkdir -p "${BUILD_DIR}"
|
||||
_bd_win="$(cd "${BUILD_DIR}" && pwd -W)"
|
||||
_bd_win="${_bd_win//\//\\}"
|
||||
_probe="${BUILD_DIR}/.ge007-tmpprobe"
|
||||
rm -rf "${_probe}"; mkdir -p "${_probe}"
|
||||
# The probe is a .cmd FILE: an inline `cmd //c "... \"path\" ..."` never
|
||||
# worked — the msys argv conversion re-escapes embedded quotes as \"
|
||||
# (which cmd does not understand), so neither ok.txt nor bad.txt was
|
||||
# ever written and every build took the re-exec. It also tests that TMP
|
||||
# is WRITABLE, not merely that it exists (the failure mode is TMP =
|
||||
# C:\Windows\, which exists). %~dp0 = the probe's own dir: no paths
|
||||
# cross the boundary. CRLF: cmd's goto/label scan is unreliable on LF.
|
||||
sed 's/$/\r/' > "${_probe}/probe.cmd" <<'CMD'
|
||||
@echo off
|
||||
if "%TMP%"=="" goto bad
|
||||
(echo x> "%TMP%\ge007-tmpprobe.tmp") 2>nul || goto bad
|
||||
del "%TMP%\ge007-tmpprobe.tmp" 2>nul
|
||||
echo ok> "%~dp0ok.txt"
|
||||
exit /b 0
|
||||
:bad
|
||||
echo bad> "%~dp0bad.txt"
|
||||
CMD
|
||||
/c/Windows/system32/cmd.exe //c "${_bd_win}\\.ge007-tmpprobe\\probe.cmd" >/dev/null 2>&1 || true
|
||||
if [ ! -s "${_probe}/ok.txt" ]; then
|
||||
command -v powershell >/dev/null 2>&1 || {
|
||||
echo "ERROR: native TMP is unusable from this shell and powershell.exe was not found for the fallback." >&2
|
||||
echo "Manual fix: run cmake+ninja from cmd/PowerShell with C:\\msys64\\mingw64\\bin on PATH, or" >&2
|
||||
echo "set the user env vars (setx TMP \"C:\\msys64\\tmp\"; setx TEMP \"C:\\msys64\\tmp\") and re-open the shell." >&2
|
||||
rm -rf "${_probe}"; exit 1
|
||||
}
|
||||
echo "==> native TMP unusable from this shell; re-running cmake+build via PowerShell"
|
||||
export GE_PC_BUILD_VIA_NATIVE=1
|
||||
_here="$(pwd -W)"
|
||||
# Windows-side toolchain paths: from the validated MSYS2 mingw64/bin
|
||||
# found above (NOT blindly from `command -v cmake`, which in a Git
|
||||
# Bash / agent shell resolves to e.g. a pip-installed cmake and fed
|
||||
# the .ps1 a bin dir with no MSYS2 toolchain — the "re-exec fails
|
||||
# in-script, works by hand with explicit paths" bug). usr/bin first
|
||||
# on the .ps1's PATH: mingw tools may need msys-2.0.dll from there.
|
||||
_msys_root="$(cd "${_mingw_bin}/../.." && pwd -W)" # msys install root (bin -> mingw64 -> root)
|
||||
_bin_w="$(cd "${_mingw_bin}" && pwd -W)"
|
||||
_bin_w="${_bin_w//\//\\}"
|
||||
_usrbin_w="${_msys_root//\//\\}\\usr\\bin"
|
||||
# The PowerShell script is written to a .ps1 FILE, not passed via
|
||||
# -Command: the msys->native argv conversion mangles $-bearing
|
||||
# strings (a '\$LASTEXITCODE' check arrived as an empty token and a
|
||||
# failed cmake would have exited 0 silently). A file write is
|
||||
# conversion-free; only plain path/word arguments cross the boundary.
|
||||
_ps1="${BUILD_DIR}/ge007-native-reexec.ps1"
|
||||
_ps1_win="${_bd_win}\\ge007-native-reexec.ps1"
|
||||
cat > "${_ps1}" <<'PS1'
|
||||
param([string]$UsrBin, [string]$Bin, [string]$MsysRoot, [string]$Here, [string]$BuildDir, [string]$RomId)
|
||||
# Self-logging: a headless shell loses this process's console output, so
|
||||
# every fact that matters goes to diag/log files in the build dir.
|
||||
# Resolve BuildDir against the repo root, not the inherited cwd.
|
||||
Set-Location $Here
|
||||
$diag = Join-Path $BuildDir "ge007-native-reexec-diag.log"
|
||||
$cmakeLog = Join-Path $BuildDir "ge007-native-reexec-cmake.log"
|
||||
$buildLog = Join-Path $BuildDir "ge007-native-reexec-build.log"
|
||||
# The inherited native PATH may be mangled/dropped by the broken msys->native
|
||||
# env conversion, so build it explicitly: the toolchain only needs the two
|
||||
# msys bin dirs plus the usual system dirs. (usr/bin first: mingw gcc/cmake
|
||||
# need msys-2.0.dll from there.)
|
||||
$env:PATH = "$UsrBin;$Bin;C:\Windows\System32;C:\Windows;C:\Windows\System32\Wbem"
|
||||
# A PowerShell launched from an MSYS2 bash (the agent-shell case: MSYS2's
|
||||
# usr/bin first on PATH, so `env bash` is MSYS2's bash) inherits a stripped
|
||||
# ~13-var env with PATHEXT=".CPL" and no ComSpec. With that PATHEXT,
|
||||
# `& cmake.exe` silently does NOTHING: no process, no output, $LASTEXITCODE
|
||||
# stays $null (the rc=2 "did not execute" regression). Restore them.
|
||||
$env:PATHEXT = '.COM;.EXE;.BAT;.CMD'
|
||||
if (-not $env:ComSpec) { $env:ComSpec = 'C:\Windows\System32\cmd.exe' }
|
||||
# D515: same class of bug as PATHEXT/ComSpec above — a stripped re-exec env
|
||||
# can also drop PROCESSOR_ARCHITECTURE, which CMake reads to fill
|
||||
# CMAKE_HOST_SYSTEM_PROCESSOR; empty value -> empty TARGET_ARCH -> "ge007..".
|
||||
# Restore it from the machine environment (fallback 'AMD64'), and restore
|
||||
# SystemRoot/windir the same way (standard Windows vars native tools expect).
|
||||
if (-not $env:PROCESSOR_ARCHITECTURE) {
|
||||
$env:PROCESSOR_ARCHITECTURE = [Environment]::GetEnvironmentVariable('PROCESSOR_ARCHITECTURE', 'Machine')
|
||||
if (-not $env:PROCESSOR_ARCHITECTURE) { $env:PROCESSOR_ARCHITECTURE = 'AMD64' }
|
||||
}
|
||||
if (-not $env:SystemRoot) {
|
||||
$env:SystemRoot = [Environment]::GetEnvironmentVariable('SystemRoot', 'Machine')
|
||||
if (-not $env:SystemRoot) { $env:SystemRoot = 'C:\Windows' }
|
||||
}
|
||||
if (-not $env:windir) {
|
||||
$env:windir = [Environment]::GetEnvironmentVariable('windir', 'Machine')
|
||||
if (-not $env:windir) { $env:windir = 'C:\Windows' }
|
||||
}
|
||||
# Writable temp for the native toolchain. [System.IO.Path]::GetTempPath()
|
||||
# CANNOT be used here: it honours the inherited (broken) TMP/TEMP env vars
|
||||
# and returned C:\Windows\. Pick the first writable candidate instead.
|
||||
$tmpdir = $null
|
||||
foreach ($cand in @((Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'Temp'), (Join-Path $MsysRoot 'tmp'))) {
|
||||
try {
|
||||
if (-not (Test-Path $cand)) { New-Item -ItemType Directory -Force -Path $cand -ErrorAction Stop | Out-Null }
|
||||
$probe = Join-Path $cand 'ge007-tmp-write-test.tmp'
|
||||
'x' | Out-File -FilePath $probe -ErrorAction Stop
|
||||
Remove-Item $probe -ErrorAction SilentlyContinue
|
||||
$tmpdir = $cand
|
||||
break
|
||||
} catch { }
|
||||
}
|
||||
if ($null -eq $tmpdir) { "no writable temp dir found" | Out-File $diag -Encoding ascii; Write-Host 'no writable temp dir for the native toolchain'; exit 4 }
|
||||
$env:TMP = $tmpdir
|
||||
$env:TEMP = $tmpdir
|
||||
"start UsrBin=[$UsrBin] Bin=[$Bin] MsysRoot=[$MsysRoot] Here=[$Here] BuildDir=[$BuildDir] RomId=[$RomId] tmp=[$tmpdir]" | Out-File $diag -Encoding ascii
|
||||
# D515 diag: the stripped re-exec env can also drop PROCESSOR_ARCHITECTURE,
|
||||
# which CMake reads to fill CMAKE_HOST_SYSTEM_PROCESSOR (empty value ->
|
||||
# empty TARGET_ARCH -> "ge007.."). One line, cheap, useful next time.
|
||||
"env PROCESSOR_ARCHITECTURE=[$env:PROCESSOR_ARCHITECTURE] PROCESSOR_ARCHITEW6432=[$env:PROCESSOR_ARCHITEW6432] SystemRoot=[$env:SystemRoot] windir=[$env:windir]" | Out-File $diag -Append -Encoding ascii
|
||||
$exe = "$Bin\cmake.exe"
|
||||
"exe=[$exe] exists=[$(Test-Path -LiteralPath $exe)]" | Out-File $diag -Append -Encoding ascii
|
||||
# A missing exe makes `& $exe` throw (caught below) and leaves LASTEXITCODE
|
||||
# null, which used to surface only as an opaque "did not execute".
|
||||
if (-not (Test-Path -LiteralPath $exe)) { Write-Host "cmake.exe not found at [$exe] (diag: $diag)"; exit 5 }
|
||||
Set-Location $Here
|
||||
"cwd=[$([System.IO.Directory]::GetCurrentDirectory())]" | Out-File $diag -Append -Encoding ascii
|
||||
try {
|
||||
# -DROMID must be a DOUBLE-QUOTED string: a bare `-DROMID=$RomId` token
|
||||
# does NOT expand the variable in PowerShell (cmake got the literal
|
||||
# "$RomId" and failed with "Unknown ROMID").
|
||||
$out = & $exe -S . -B $BuildDir "-DROMID=$RomId" 2>&1
|
||||
"cmake ran LASTEXITCODE=[$LASTEXITCODE]" | Out-File $diag -Append -Encoding ascii
|
||||
$out | Out-File -FilePath $cmakeLog -Encoding ascii
|
||||
} catch {
|
||||
"cmake TERMINAL ERROR: $($_.Exception.Message)" | Out-File $diag -Append -Encoding ascii
|
||||
}
|
||||
if ($null -eq $LASTEXITCODE) { Write-Host "cmake.exe did not execute (diag: $diag)"; exit 2 }
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host "cmake re-configure failed (rc=$LASTEXITCODE; log: $cmakeLog)"; exit $LASTEXITCODE }
|
||||
Write-Host "cmake reconfigured (TMP=$($env:TMP))"
|
||||
try {
|
||||
$out = & $exe --build $BuildDir -j 2>&1
|
||||
"build ran LASTEXITCODE=[$LASTEXITCODE]" | Out-File $diag -Append -Encoding ascii
|
||||
$out | Out-File -FilePath $buildLog -Encoding ascii
|
||||
} catch {
|
||||
"build TERMINAL ERROR: $($_.Exception.Message)" | Out-File $diag -Append -Encoding ascii
|
||||
}
|
||||
if ($null -eq $LASTEXITCODE) { Write-Host "cmake --build did not execute (diag: $diag)"; exit 3 }
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host "build failed (rc=$LASTEXITCODE; log: $buildLog)"; exit $LASTEXITCODE }
|
||||
"done" | Out-File $diag -Append -Encoding ascii
|
||||
Write-Host "cmake + build ok (native re-exec, TMP=$($env:TMP))"
|
||||
PS1
|
||||
rm -rf "${_probe}"
|
||||
rc=0
|
||||
# NOTE: do NOT launch powershell via `env -i` (tried 2026-09-28): a
|
||||
# native child started with a scrubbed msys env cannot launch
|
||||
# cmake.exe (CreateProcess fails silently; LASTEXITCODE stays null).
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "${_ps1_win}" "${_usrbin_w}" "${_bin_w}" "${_msys_root}" "${_here}" "${BUILD_DIR}" "${ROMID}" || rc=$?
|
||||
rm -f "${_ps1}"
|
||||
if [ "${rc}" -ne 0 ]; then
|
||||
# Surface the .ps1's own diagnostics (console output is lost in a
|
||||
# headless shell) plus whatever the failing step wrote.
|
||||
for _lg in "${BUILD_DIR}/ge007-native-reexec-diag.log" "${BUILD_DIR}/ge007-native-reexec-cmake.log" "${BUILD_DIR}/ge007-native-reexec-build.log"; do
|
||||
if [ -s "${_lg}" ]; then
|
||||
echo "---- ${_lg} (last 40 lines) ----" >&2
|
||||
tail -40 "${_lg}" >&2
|
||||
fi
|
||||
done
|
||||
fi
|
||||
exit "${rc}"
|
||||
fi
|
||||
rm -rf "${_probe}"
|
||||
fi
|
||||
|
||||
echo "==> Configuring PC port (ROMID=${ROMID})"
|
||||
cmake -S . -B "${BUILD_DIR}" -DROMID="${ROMID}"
|
||||
|
||||
|
||||
@@ -6,14 +6,6 @@ function(target_architecture OUT_VAR)
|
||||
# Derive from the cross-compiler target, e.g. x86_64-w64-mingw32-gcc
|
||||
string(REGEX MATCH "^(i[3-6]86|x86_64|aarch64|arm|powerpc|wasm32)" _arch
|
||||
"${CMAKE_C_COMPILER_TARGET}")
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "")
|
||||
# D515: a stripped native env (e.g. the build-pc.sh PowerShell re-exec
|
||||
# before it restored PROCESSOR_ARCHITECTURE) leaves
|
||||
# CMAKE_HOST_SYSTEM_PROCESSOR / CMAKE_SYSTEM_PROCESSOR empty. Ask the
|
||||
# compiler instead of silently producing an untagged binary ("ge007..").
|
||||
execute_process(COMMAND "${CMAKE_C_COMPILER}" -dumpmachine
|
||||
OUTPUT_VARIABLE _dm OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
string(REGEX MATCH "^(i[3-6]86|x86_64|aarch64|arm|powerpc|wasm32)" _arch "${_dm}")
|
||||
else()
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$")
|
||||
set(_arch "x86_64")
|
||||
@@ -32,9 +24,5 @@ function(target_architecture OUT_VAR)
|
||||
set(_arch "i686")
|
||||
endif()
|
||||
|
||||
if(_arch STREQUAL "")
|
||||
message(FATAL_ERROR "TargetArch: could not determine the target architecture (CMAKE_SYSTEM_PROCESSOR empty and compiler -dumpmachine did not match); refusing to build an untagged binary -- the 'ge007..' bug, see D515.")
|
||||
endif()
|
||||
|
||||
set(${OUT_VAR} "${_arch}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
Before Width: | Height: | Size: 3.4 KiB |
@@ -1,4 +0,0 @@
|
||||
/* Windows executable icon (Explorer / taskbar).
|
||||
* Original crosshair mark generated by tools_pc/make_icon.py (cmake/icon/
|
||||
* ge007.ico) -- same art as the site favicon (docs/img/icon/). */
|
||||
IDI_ICON1 ICON "ge007.ico"
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
title: 404
|
||||
description: Page not found.
|
||||
layout: default
|
||||
permalink: /404.html
|
||||
---
|
||||
|
||||
## 404
|
||||
|
||||
This page doesn't exist. The site's real pages are linked from the
|
||||
nav: [home]({{ '/' | relative_url }}) ·
|
||||
[documentation]({{ '/documentation' | relative_url }}) ·
|
||||
[building]({{ '/building' | relative_url }}).
|
||||
|
||||
*Note for anyone following a moved link: the old "under the hood" page now
|
||||
lives at [documentation]({{ '/documentation' | relative_url }}).*
|
||||
|
||||
<!-- Links use the | relative_url filter on purpose: a 404 page is served for
|
||||
ANY unknown path, so plain relative links would resolve against the
|
||||
requested (dead) URL instead of the site root. -->
|
||||
@@ -1,318 +0,0 @@
|
||||
# Roadmap and open items
|
||||
|
||||
**This is the single tracker for high-level work on the port.** If an item
|
||||
is not here, it is either done or not planned. The README's *Known issues*
|
||||
and *Roadmap* sections are short summaries of this file. Root-cause detail
|
||||
for each `Dxx` lives in [`dev/findings.md`](dev/findings.md) (find it via
|
||||
[`dev/findings-index.csv`](dev/findings-index.csv); that index is generated,
|
||||
first-line-only, and can lag the entry tail, so read the entry).
|
||||
|
||||
Last full triage: 2026-09-29. Sources: the findings log, all GitHub issues,
|
||||
PRs and releases, every local backlog/plan/roadmap note, `HANDOFF.md`, all
|
||||
local and remote branches, and known community forks.
|
||||
|
||||
## How to use this file
|
||||
|
||||
- **One row per item.** Link the `Dxx` and/or GitHub `#issue`. Keep detail
|
||||
in the finding, not here.
|
||||
- **Status vocabulary:** `open` · `partial` · `verify` (fixed, a live check
|
||||
is owed) · `parked` (work exists on a branch or patch, not merged) ·
|
||||
`decision` (blocked on a maintainer call).
|
||||
- **When an item closes, delete its row.** The history is kept in the
|
||||
finding and in git. Do not strike rows through. Struck rows are what made
|
||||
the old backlogs unreadable.
|
||||
- **New work goes here first.** Do not start a new backlog, roadmap or
|
||||
plan-of-record file. A design doc for one item is fine; link it from the
|
||||
row and delete it when the item lands.
|
||||
- Release scoping (which items go into which version) happens in the
|
||||
release notes, not here.
|
||||
- The **release gate** (what must be green before pushing / publishing a
|
||||
release) is kept as a local working note — `docs/dev/notes/RELEASE-BLOCKERS-v050.md`
|
||||
(gitignored, like the other P-pass reports). Keep it current; don't fork a
|
||||
second copy of it into the tracked tree.
|
||||
- **Known issues** (the player-facing subset) live in the table below. The
|
||||
README links to it and does not duplicate it.
|
||||
|
||||
## Goal and definition of done
|
||||
|
||||
**Feature complete and faithful to the N64 original.** Every mode and
|
||||
region the cartridge ships should work and behave 1:1 with the N64,
|
||||
checked against an accurate emulator (1964/GEPD) or real hardware. On top
|
||||
of that sits a **modern-remaster layer** (the bar set by Nightdive's
|
||||
Quake/Turok remasters, the XBLA remaster and GE+/Dab's mod). It must never
|
||||
change game logic by default, and it must always be possible to get back
|
||||
to the original look and feel (see the *Original N64 preset*, D440).
|
||||
|
||||
The port is **feature complete** when all of these hold:
|
||||
|
||||
1. Every solo mission (all 20 plus the credits) is completable at every
|
||||
difficulty, on Windows and Linux.
|
||||
2. No crash on any level under normal play, on either platform.
|
||||
3. Audio is complete and faithful: music, positional SFX, menu and weapon
|
||||
sounds.
|
||||
4. Cutscenes play correctly (camera, actor positions, timing).
|
||||
5. AI locomotion and pacing match the N64.
|
||||
6. Input feels like GoldenEye on a controller, and like a modern FPS on
|
||||
mouse and keyboard.
|
||||
7. Rendering has no visible defect versus 1964/GEPD at default settings,
|
||||
apart from the port's deliberate modern defaults.
|
||||
8. **Multiplayer (2–4 player split-screen) works.** It is the largest item
|
||||
left (§1).
|
||||
9. **PAL and JP ROMs work** (§1, pending a decision).
|
||||
|
||||
**Exit gate:** a full two-platform campaign playthrough at more than one
|
||||
difficulty, plus a split-screen session, on a build with the §3
|
||||
verification debt cleared.
|
||||
|
||||
Where things stand (2026-10-01): 1–7 hold for NTSC-U, with the small
|
||||
accuracy gaps in §2. 8 is merged on `release/v0.4.1` (3P/4P with real pads
|
||||
owed). 9 is open (Stage B). **Parity rule (2026-09-30):** every released
|
||||
platform carries the same gate: a full 21-level sweep plus a campaign pass.
|
||||
|
||||
## Order of work (decided 2026-09-30)
|
||||
|
||||
Work continues on `release/v0.4.1`, **shipped as v0.5.0 (named 2026-10-03,
|
||||
cut staged 2026-10-05, maintainer)** — the docs, golden set (both platforms,
|
||||
63 frames each) and tooling are complete; the P11 squash + tag + push is owed
|
||||
maintainer consent (push checklist: `dev/notes/PUSH-TASKS-v041.md`). There are
|
||||
two stages:
|
||||
|
||||
**Stage A: now → the pre-1.0 build**
|
||||
1. Clear the §3 verification debt.
|
||||
2. Split-screen (§1): keep working and testing until it is good.
|
||||
3. `All unlocked` rework (§5a).
|
||||
4. D357 settings values (§5a), plus the restart button.
|
||||
5. Outside PRs (§5a), in the proposed order once the maintainer approves it.
|
||||
6. Pillarbox with the exact original aspect for the Original N64 preset (§5a).
|
||||
7. Platform parity: re-base the golden baselines and extend them to both
|
||||
platforms (§7), then run the gate on each platform.
|
||||
8. Investigate and triage every §2 fidelity gap to fix or accept. Do not
|
||||
leave any of them as "open, unexamined".
|
||||
|
||||
**Stage B: pre-1.0 build → 1.0**
|
||||
1. PAL/JP (§1). It goes last on purpose, so that region-specific
|
||||
regressions are easy to isolate and unwind.
|
||||
2. macOS/ARM: merge the `macos` branch (§4), with the D441 census
|
||||
(store-to-u32 tagging) and a high-arena test.
|
||||
3. The exit gate on every shipped platform.
|
||||
4. Remove the temporary scaffolding (§7).
|
||||
|
||||
**Next (decided 2026-10-03, after the Birdman64 comparison; local note `BIRDMAN64-LESSONS.md`):**
|
||||
1. D441 census: **done 2026-10-03** (§2).
|
||||
2. Fix the listed sites as mechanical, build-checked batches on a cheap
|
||||
worker; the lead reviews every diff (§7). **Batches 1-5 done
|
||||
2026-10-03.** Next: the tier-1 sweep over every level with a seeded
|
||||
frame compare, then further batches from it.
|
||||
3. Scripted-playthrough harness: moved to post-1.0 (maintainer
|
||||
2026-10-04; revisit with newer models) (§7).
|
||||
|
||||
## Known issues
|
||||
|
||||
What a player will notice in the current release. **This table is the
|
||||
single list:** the README links here instead of keeping its own copy. When
|
||||
an issue is fixed and released, delete its row. When a new player-visible
|
||||
issue is confirmed, add a row here (and its tracking row in the sections
|
||||
below).
|
||||
|
||||
| Issue | Impact | Workaround | Tracking |
|
||||
|---|---|---|---|
|
||||
| PAL and JP ROMs aren't supported in release packages | NTSC-U only | Use an NTSC-U ROM. Both regions convert, build and boot from source (D258, D487, D488; see `docs/building.md`); packaging (per-region exe, CI matrix, converter region check) is still open | D258, #85, §1 |
|
||||
| Changing Aspect ratio in a level (mostly Fill window to Original or a forced ratio) can briefly glitch the gun / hand model, rarely | Cosmetic, one-off | Change the ratio from the front-end PC Options, or accept it | D509 (frame hold fixed the common case; the rare remainder is logged, not investigated) |
|
||||
| No macOS or ARM builds | Platform | — | #88, #95, #101, §4 |
|
||||
| Saves from v0.4.0 and earlier can hold fake unlocks from `All unlocked` | Save data | Not repaired automatically. Since v0.5.0 the option never writes the save; keep a backup of `data/ge007.eep` from before you used it | D442, D387 |
|
||||
| The first frame of a level takes a little longer while its textures upload | Brief FPS-counter dip | None needed | D475, D480 |
|
||||
| Far objects almost fully in fog are now hidden as on the N64, except in widescreen, where the wider view can still show a faint distant building edge (e.g. Surface's dish from the start area). 4:3 matches the N64 | Cosmetic | None; higher Draw/LOD distance shows more | D503, §5c |
|
||||
|
||||
---
|
||||
|
||||
## 1. Feature completeness vs the N64 cartridge
|
||||
|
||||
| Item | Status | Refs | Notes |
|
||||
|---|---|---|---|
|
||||
| **2–4 player split-screen multiplayer** | verify | #99, D416–D423, D496 | Merged 2026-10-01. D417 Rule-2 approved. **Maintainer playtest 2026-10-03:** 2P and 3P with real Xbox and PS5 pads, handicaps, teams and game modes all good (D496 fixed an uninitialised weapon-text buffer). **Still owed:** 4P with real pads, pads-only mode, MP audio, D422 centred aim live on P2+ (`AimMode = 1`), D428 sway on P2+. |
|
||||
| **PAL and JP ROM support** | partial | D258, D487, #85 | **2026-10-02: both regions convert, build and boot** (D258 byte-verified against real PAL and JP ROMs); both play Dam with no crash; NTSC-U unchanged. Build: `BUILD_DIR=build-pal ./build-pc.sh pal-final` / `build-jpn ... jpn-final` with `data/ge007.<region>.z64`. D487, D488 fixed (PAL frame height, TV type). **Open:** full runtime passes per region (levels, menus, audio, saves; PAL 50 Hz feel incl. the D427/D451/D486 gates; EU-only text); packaging (per-region exe names, CI matrix, converter region gate, #6). ROMs: `ge-port-reference/1964_GEPD_Edition/ROMs`. #85 needs reopening or a fresh tracker when this ships. |
|
||||
| Drop-in ROM converter, remaining parts | decision | #6, [`dev/release-dropin-rom-plan.md`](dev/release-dropin-rom-plan.md) | NTSC-U drop-in shipped. Left: per-region output names and re-exec guard (with PAL/JP), native C emitters to replace the PyInstaller converter. |
|
||||
| Cut content (TCRF weapons/items) | decision | [`dev/CUT-CONTENT-BACKLOG.md`](dev/CUT-CONTENT-BACKLOG.md) | Not part of the shipped game. Tier 0 already works via the debug/cheat menus. Tier 1 (config-toggle grants) is small and port-only. Tier 2/3 (placing props, re-enabling unused MP maps) need game data changes and are out of scope for this repo. |
|
||||
|
||||
## 2. Accuracy (behaviour differs from the N64)
|
||||
|
||||
| Item | Status | Refs | Notes |
|
||||
|---|---|---|---|
|
||||
| Third-person Bond offset residual | partial; intro puppet explained (2026-10-02) | D173, D292 | Mostly fixed. On the level-start puppet the variance is tick/PRNG-dependent idle timing (D13 class, faithful, no sign-off requested); one 1-frame first-tick Y transient is untraced. If the residual is seen elsewhere (cutscene body model), the maintainer should note where. |
|
||||
| AI awareness follows two non-default render settings | fixed 2026-10-06 (D565: Fog distance floor 100; AI fog cull rescaled to the game's FOV) | D565, D466, D468, D540, D222 | AI review of v0.4.0..v0.5.0 (interpreter, movement/timing, visibility/combat): no regression at shipped settings (Draw 2.0x, Fog 1.0x, FOV 60, native widescreen). Two leaks of player render settings into AI awareness remain: **(1) `Video.FogDistance` below 100** shrinks the far clip (`bgfog.c fogLoadCurrentEnvironment`, clip = FarFog x min(Fog, Draw)). AI on-screen checks require the render `PROPFLAG_ONSCREEN` (`drawdistgameplay.c portPropGameplayOnScreen`), so guards beyond the shortened clip stop counting as on screen; at Draw 1.0x `portD466Active()` is off and sight range (`fogGetScaledFarFogIntensitySquared`) shrinks too. Effect: guards notice Bond / on-screen triggers fire later than the N64. Fix (shipped): `Video.FogDistance` is floored at 100 (`video.c` registration, so 50/75 in an ini clamp to 100 on load; F10 slider `uiMin` 100); 100-800 only extends, which D466 already keeps off gameplay. **(2) FOV other than 60 (`Video.FovScale` != 100, or native widescreen off at a wide aspect)**: the gameplay fog-cull `propobj.c portSub7F054C58Gameplay` uses `getPlayer_c_lodscalez()`, which grows with the rendered FOV (D222), so at a wider FOV far fogged positions drop out of the AI on-screen verdict sooner (narrower: later). Fix: divide out the port FOV factor (as `portFovYScaleFactor`), keeping the game's own zoom FOV changes. Minor at defaults: the D466 verdict reuses the extended pass's room screen bbox (`posIsOnScreen`); at Fog 1.0x the clip is the authored far, so the two passes match. D491 (room-list portal seeds) and D431 (light-fixture hit scoring) were checked: both restore N64 data reads and only move AI toward the N64. |
|
||||
| Dam ending cutscene | done (2026-10-06) | D526, D552 | Railing/grate transparency faithful (D526); first-shot swivel restored (D552, maintainer-verified). |
|
||||
| Title-screen attract demo runs too fast; F10 box rescales while it plays | backlogged (after v0.5.0, maintainer 2026-10-04) | D87 (ramrom file struct); timing: D13, D248, D427, D451, D486 | **Reported 2026-10-04 (live pass):** attract plays visibly too fast; not investigated. Lead: it is a ramrom demo replay whose recordings carry a per-frame tick count, which the port's 60 fps / 1-tick pacing may override. Rule out PC causes, then compare with 1964. Same pass: the F10 box briefly rescaled during the demo (not reproduced; candidate fix = overlay reads fast3d's native viewport, parked locally). |
|
||||
| Sniper rifle: zoom level and speed vs 1964 | verify | #136, D484, D485, D494 | Crouch: **D494** (2026-10-03) makes the PC crouch key crouch/stand freely with the sniper, as GEPD does (superseding D483's freeze); maintainer-verified. Zoom level (D484, 20 -> 7 degrees) and speed (D485, tick-scaled, Rule-2) fixed 2026-10-02. **Owed:** 1964 A/B of zoom level and speed; close #136 after it. |
|
||||
| Crash on save slots 1/3/4, plus a host BSOD (fltmgr.sys) | decided | #94 | Needs a repro. Two maintainer retest requests (v0.3.0, v0.4.0) got no reply. Likely D299/D301 (save-slot crash, fixed) and D344 (BSOD on exit, fixed). **Decided 2026-10-01: close as stale when the release ships**, pointing at D299/D301/D344; no GitHub activity before then. |
|
||||
| Authored mip chains not sampled for explicit-LOD textures | post-1.0 | D323 | Distant textures use driver mips instead of Rare's hand-authored chain (heaviest on Statue/Aztec). Parked as a fidelity enhancement. **Decided 2026-10-01:** after 1.0, together with the LOD-fraction approximation as one "real RDP LOD" project. |
|
||||
| Approximations in the software RSP | post-1.0 | `gfx_pc.cpp` `G_CCMUX_LOD_FRACTION`; D323 | The LOD-fraction CC input is an eyeballed approximation, bundled with D323 as one "real RDP LOD" project after 1.0 (2026-09-30 triage). **WrapFix settled 2026-10-02:** the maintainer checked the Depot building ceilings against 1964 with the default `Video.WrapFix = 0` and they look right, so the default stays off (the opt-in knob remains; RC3/D167). |
|
||||
| Latent 64-bit ABI hazards | partial | D441, #107, #108; AUDIT-M6 `struct player`/`struct hand` raw offsets (porting-notes §A1) | **State (2026-10-03, D441):** complete census (local `scratch/d441/`; many hits are intentional `(s32)&ANIM_DATA_*` offsets, D34); 17 files widened to `uintptr_t` under `#ifdef PORT`; 35 sign-extension sites fixed; CI ratchet `tools_pc/abi_ratchet.py` (baseline 389); arena windows centralised (`port/include/portaddr.h`); tier-1 high-arena build (`GE_HIGHARENA_BASE=0x90000000`) runs the menu and `-level_09` pixel-identical to default. D478: nothing live on the current layout; the rest would bite only on macOS/ARM/ASLR. **Sweep (2026-10-05):** tier-1 all-level sweep done, 21/21 PASS, no fix batches, ratchet unchanged (389), recipe in the D441 entry. **Owed:** tier 2 (above 4 GiB; u32 slots widened); store-to-u32 vs arithmetic-only tag per site (`portHostToN64` under macOS port_addr); `mema.c` widening (D453); `initanitable.c` heap slots; GE_DETERM wall-clock timers (D117 follow-up, blocks using determinism mode for frame gates). **#108:** widening the `sndPlaySfx` bound disables the guard everywhere; prefer a range check. |
|
||||
| Port-memory vertex/pool bounds hardening | post-1.0 (decided; not a v0.5.0 blocker) | D545, D549 | D545 (rockets through floor, fixed) left one residual: ~1 dropped rocket in ~60 spawns (Archives, akimbo), consistent with the room-pool hypothesis (`bgBuildRoomVtxBounds` allocation failing leaves no bounds) but unproven; D549 (same stack-overflow class in grenade/knife/object throws) is fixed. Both shipped. Proposed hardening: a port-memory bounds table validating vtx/pool pointers against the known arena windows. |
|
||||
|
||||
## 3. Verification debt (fixed; a live check is owed)
|
||||
|
||||
A single campaign pass with these on the checklist would clear most of them.
|
||||
|
||||
| Item | Refs | Check |
|
||||
|---|---|---|
|
||||
| Presets: Original N64 / Port defaults | D440 | Press both rows in F10 and in the front-end PC Options with a mouse and a pad; draw distance applies from the next level load. `GE_OPTIONTREEPROBE` already fails on the base commit (separate look). |
|
||||
| Probe strip (2026-09-30) | D245, D302, D318 | Frigate or Dam water/sky A/B against the previous build (the s16 sky path was removed); one Facility playthrough to confirm the D318 watchdog stays silent. |
|
||||
| Infra fixes | D179, D515, build-pc.sh, crash.c, CI | 2026-09-30 re-exec no-op (PATHEXT) fixed; 2026-10-04 empty-arch-tag bug (D515) fixed headless (details in AGENTS.md) — confirm on your next fresh-dir build. **Owed:** `./build-pc.sh` from a genuine MSYS2 login shell; the next CI run (pinned actions, workflow input check). `GE_CRASHTEST=6` shows resolved frames. The romdata `VirtualAlloc` failure path now fails boot instead of falling back to the heap copy: confirm you want that. |
|
||||
| Light-fixture hit-type reads (impact sound + sparks) | D434, #119 | Shoot fixtures on Bunker and Caverns. |
|
||||
| Dam/Caverns water animation | D465, #127 | Fixed 2026-10-01 (16-byte `Gfx` raw-index write). Maintainer: still "slightly faster" than 1964 on Dam; measured rate equals N64 within 0.1%. Optional objective check: time ten shimmer cycles on both (expect ~26 s). |
|
||||
| End credits after Cradle | #129 | Did not reproduce on `release/v0.4.1` 2026-10-01 (maintainer finished Cradle on Agent, credits played, clean exit). Close #129 as not reproducing when the release ships. |
|
||||
| v0.4.1 batch (unreleased; fixes live on `release/v0.4.1`) | D424–D432; #114 fire rate, #115 tank-crush audio loop, #116 tank movement, #117 sway, #118 GL recoil, #119 light fixtures | All user-verified against GEPD/1964 on 2026-09-29. Close the issues when it ships. |
|
||||
| FOV-scale edge culling | D222 | Max FOV, pan across NPCs at the screen edge. Also audit the FOV/aspect terms in `camIsPosInScreen` and `c_lodscalez` for the Surface dish (D503 residual). Maintainer decision owed: expected widescreen behaviour vs fix. |
|
||||
| Draw/LOD 800% | D503 | Draw/LOD distance ceiling raised 400% → 800% (2026-10-04). Owed: at 600-800, check far-field z-fighting on Dam (tunnel), Surface and Jungle; on Aztec or Streets, grep the log for the D500 vtx-pool budget line. If z-fighting shows, lower the ceiling to 600 rather than touching depth precision. |
|
||||
| Water pulse, Surface 2 | D229 | Frigate is verified. Only the Surface 2 by-eye check is owed. |
|
||||
| Linux save/config path when launched outside the game dir | D256 | Save-flow check on Linux. |
|
||||
| Settings/F10 batch | D356, D358, D361, D363, D374, D406 | Live accept of each item in the F10 overlay. (D377–D379 residuals were waived.) |
|
||||
| Controller feel | CONTROLLER-INPUT-PLAN Wave A, D204 | Per-stick deadzone/smoothing feel on a pad and on Deck; D204 tempo by ear; `Input.RumbleScale=0` silences rumble. |
|
||||
| Widescreen HUD at 21:9 | D334/D335 | One ultrawide HUD playthrough. |
|
||||
| Older "owed" notes | D371, D372 | **Bookkeeping done 2026-10-01:** D150, D177, D264 ticked off against the 2026-09-28 campaign sign-off; D369 accepted in use. Still owed: D371 (GEPD key-layout preset + reload/crouch bindings: select the preset in F10 and play a level). (D372 low-end preset: no further effort; low-end work is real optimizations, see §4.) |
|
||||
|
||||
## 4. Platform
|
||||
|
||||
| Item | Status | Refs | Notes |
|
||||
|---|---|---|---|
|
||||
| macOS (Intel + Apple Silicon) / ARM64 Linux | open (target 0.9.0/1.0, maintainer 2026-10-02) | PRs #88, #95; #101 | Outside drafts, parked until after 1.0. **Address model decided (2026-09-30): port_addr (#95)**, a `portN64ToHost`/`PORT_N64PTR` chokepoint with a `PORT_ADDR_STRICT` validator. The `macos` integration branch is on origin with #88 (merged), #132 and #95 (open); nothing merges to `main` before the post-1.0 pass. **Open against `macos`:** #131 (self-hosted sweep scaffold, never run on a Mac; needs #95 first). **On the next rebase onto `main`:** take #131's `selfhosted.yml`; drop #95's `crouched_rifle` hunk (our #120/D445 is correct); relabel #95's D29x/D405-D421 labels. Needs the §2 ABI-hazard sweep; the maintainer tests firsthand before merging. |
|
||||
| Platform parity gate (Linux co-equal with Windows) | open | — | **Decided 2026-09-30:** yes. Every released platform carries the same gate: a 21-level sweep plus a campaign pass. Needs the golden baselines on both platforms first (§7). The last known Linux crash (D190) was closed as not-a-bug. |
|
||||
| Low-end GPUs (~30 fps on Celeron/HD 400) | fixed, verify on N3060 class | D339, D481, D482, #92 | **D481 (2026-10-02):** fast3d ran inline on the scheduler thread, delaying retrace delivery so the tick gate skipped frames; the render worker took a Lenovo X220 (HD 3000) from 44-46 to 60.0 fps. **D482:** Atom/Celeron-class GPUs (HD 400-605, UHD 600/605, software renderers) get DrawDistance/LodDistance 100 and MSAA 1 once, where still at defaults. **D473:** uncached per-batch getenv fixed. Direction: real optimizations, not a preset. **Next only if N3060-class hardware still drops:** one `GE_PERFSTAT=1` run (`gpu=` vs `run=`), then a lighter renderer-keyed default (GPU) or fast3d per-batch cuts (CPU; local `PERF-HOTSPOTS.md`). |
|
||||
| Statue/Cradle hitching (maintainer, 2026-09-30) | parked (not player-visible, 2026-10-02) | `GE_PERFSTAT` spike counters | **Diagnosed (D475, D477, D480):** no sustained load — the ~33 ms frames are the frame limiter skipping a retrace after a late gfx task (`boss.c`, D13-class timing); CPU bursts were CI-palette re-imports (ruled out, D477) and in-play shader compiles (fixed, D480). **Parked 2026-10-02:** a play session with the timeline probe felt no hitching. Reopen only if a visible hitch is reported (lever: pre-warm at level load). |
|
||||
| Steam Deck preset skipped when first launched from Desktop Mode | verify | D283 | Fixed on `release/v0.4.1`: the Deck is detected by DMI hardware id (Valve Jupiter/Galileo; `STEAMOS` as a second signal) and the preset applies once per ini, only while Fullscreen/Maximized are untouched. Only a Windows build and a Linux `-fsyntax-only` check ran. **Owed: one run on a real Deck** (Desktop-Mode-created ini, then Game Mode; expect fullscreen 1280x800 and the log line `video: Steam Deck (DMI Valve ...)`). The Known-issues row goes when this ships. |
|
||||
| Windows 7 | open | #98 | **Re-checked 2026-10-02: likely feasible around 1.0.** The exe imports no Windows 8+ functions and the bundled SDL2 2.32.10 still supports Windows 7. The real blocker is the first-run converter `ge007-convert.exe` (PyInstaller, Python 3.9+): the native C converter (#6 Part B) removes it, or a separate Python 3.8 build. Work: one Windows 7 VM test, then manual per-release checks (CI cannot test it). Supersedes the earlier 'won't-fix' call. |
|
||||
| Windows display scaling: the game is not DPI-aware on scaled displays | open | — | Seen 2026-10-07 during the v0.5.0 4K captures: on a 3840x2160 display at 125% Windows gives the game a 3072x1728 surface and stretches it, so the picture is softer than native. `gfx_sdl2.cpp` sets `SDL_HINT_WINDOWS_DPI_AWARENESS` only when `allow_hidpi` is off; setting it (`permonitorv2`) on the HiDPI path too renders at native resolution. Workaround today: environment variable `SDL_WINDOWS_DPI_AWARENESS=permonitorv2`. |
|
||||
|
||||
## 5. Modern-remaster layer (port features beyond the N64)
|
||||
|
||||
**The bar:** feature parity with a Nightdive-style remaster and with
|
||||
GE+/Dab's mod where it's practical. Shipped so far: native widescreen and
|
||||
ultrawide, FOV, MSAA/anisotropic/texture-filter options (including N64
|
||||
3-point), draw and LOD distance, VSync and a frame cap, show-FPS,
|
||||
keyboard/mouse rebinding with a GEPD-style layout, pad deadzone,
|
||||
sensitivity, smoothing, southpaw and triggers, rumble, crosshair and HUD
|
||||
customisation, HUD scale, music/FX volume, no-hit-flash, SkipIntro,
|
||||
All unlocked (see D387, D442), and the F10 overlay.
|
||||
|
||||
The modern defaults (native widescreen, overscan crop, 250% draw/LOD
|
||||
distance, bilinear filtering) are **deliberate**. They are not a fidelity
|
||||
defect.
|
||||
|
||||
Background research lives in the maintainer's local notes (gitignored):
|
||||
`GEPORT-REFERENCE-DEEPDIVE.md`, `MODERN-PORT-FEATURES-BACKLOG.md`,
|
||||
`MODERN-OPTIONS-PLAN.md` and `PD-LEGACY-SURVEY.md` under `docs/dev/notes/`.
|
||||
|
||||
### 5a. Before 1.0
|
||||
|
||||
| Item | Status | Refs | Notes |
|
||||
|---|---|---|---|
|
||||
| Log file (next release) | open | maintainer 2026-10-06 | Write `ge007.log` next to the ini (console output is lost on a normal launch). Crosshair pointer done in v0.5.0 (D555). |
|
||||
| F10 / PC Options menu: PD-style dialog refactor + watch-consolidation review | verify | D504-D512, `MENU-PD-ALIGNMENT-PLAN.md` (maintainer's local notes) | **Built 2026-10-04 (D504); follow-ups merged:** D505/D506/D507 (wording, real-unit sliders, descriptions); D508 forced aspect ratios (16:9 / 21:9); D509 2-frame hold after an aspect change (rare gun glitch = known issue); D510 overlay + FPS counter same size front end vs level; D512 `Game.HudScale` scales the F10 overlay. **Owed (live pass, `docs/dev/notes/MENU-LIVE-CHECKLIST.md`, launches A/B/C):** look-and-feel vs PD; mouse, keyboard and pad in a level and on the front end; HudScale 75/100/150; 21:9; display-mode round trip across a restart; split-screen 2P with the overlay open. **Gaps:** background blur (post-1.0, §5c); per-seat control scheme is #139 / D513. Watch consolidation: not cheaply feasible (parked as T5). |
|
||||
| Defaults/presets consolidation: one `Display mode` switch | verify | D504, D440, `MENU-PD-ALIGNMENT-PLAN.md` §5 (local notes) | **Landed 2026-10-04 (D504):** the derived `__DisplayMode` row shows Modern / Original N64 / Custom (no ini key); F10 dropdown + left/right stepping, front end steps too; one reset row per dialog covering exactly its keys; editing a non-bundled key does not flip Display mode. **Decided 2026-10-03: tri-state**, flipping to Custom when the user edits a bundled key. **Owed (live):** display-mode round trip across a restart; read of the regrouped pages and wording (MENU-LIVE-CHECKLIST.md A1/A2/A4). |
|
||||
| Control style Original shows the game's own N64 styles | verify | #139 follow-up, D516, D517 | **Landed 2026-10-04 (D516, merged @ 91595597):** "Controller style" row (Original only; the game's own 1.1-2.4 per seat, saved to the Bond file; front end reads "(in a mission)"); D498 presets hidden in Original. **D517:** hold-repeat on any Bond row dropped fps (EEPROM rewrite per detent); now staged like sliders. Live passes 2026-10-04 (maintainer): single player + split-screen 2P with different styles per seat. **Owed:** the 2.x second-pad pass (real second controller). |
|
||||
| Pad layout preset per player (setting P2's no longer moves P1) | verify | D518, D498 follow-up | **Landed 2026-10-04 (D518):** `Input.PadPreset` kept as seat 1's key + new `Input.PadPreset2/3/4`; the F10/front-end preset row follows the Select-player seat. Headless GE_PADMAPTEST PASS; **live passes 2026-10-04** (P2 change leaves P1 alone; split-screen: each seat ran its own preset). |
|
||||
| Control scheme selectable: Ext / Original control style | verify | #139, D513, D498, D166 | **Landed 2026-10-04 (D513, merged into `release/v0.4.1`).** `Input.ControlScheme` (row "Control style", Ext default / Original) on the Controller page, both UIs. Original leaves the game's own style alone; D498 presets set layout and stick swap only in Original; 2.x runs faithfully; global in v1, per seat with the Select Player work. **Owed:** pad pass in both modes, watch-selected 1.1-1.4, split-screen 2P with different styles, 2.x with a second pad. |
|
||||
| Outside PRs #107–#109, #122–#124 | decided | #122 hot-plugged pads never reopen, #123 persistent crosshair (`gunfire.c`), #124 compact health/armour bars (`bondview2.c`); #107/#108/#109 (italoarruda) | **Decided 2026-10-01:** **#120/#121/#122** merged locally (#122 = D450); close on push. **#123**: merged 2026-10-06 into v0.5.0 (D436; presets set it off, tip, Rule-2 signed off; by-eye hipfire / sniper zoom / 2P owed). **#124**: deferred (ignores HudScale, overlaps status text, ultrawide anchoring, split-screen untested). **#133**: duplicates D472; close as superseded on push. **#109** (pad rebinding): decline as-is; supersede with our pad presets (§5a), crediting the author (Co-authored-by). **#107**: folded into D441; close on push. **#108**: belongs with `macos`; ask for a range check. |
|
||||
| Gamepad presets: duplicate-binding warning, Xbox stick behaviours | open (after v0.5.0; documented in the README) | D469, D498, D518, D394 | D469 (pad action table, Jinx 1.1 default, Custom per-seat bindings), **D498** (Xbox 1.2 Christmas / 1.3 Frost / 1.4 Elektra) and the per-seat preset (**D518**) are done and maintainer-verified with pads. Not done (maintainer 2026-10-04: skip in game for v0.5.0, document only): a warning when two actions share a button; the Xbox "left stick aims the crosshair" and "D-pad copies the left stick" behaviours (D394). |
|
||||
| Pad button names on a Nintendo pad | verify | D471; local note `PAD-MENU-AUDIT.md` | Audit done 2026-10-01 (no controller-only trap; D472 fixed G1-G3). D471 family button names + player LEDs: **PS5 DualSense verified 2026-10-03.** **Owed:** a Nintendo (Switch) pad check; the maintainer has no such pad, so this waits for one or for a user report. |
|
||||
| Mute and auto-pause on focus loss; PNG screenshots to `screenshots/` | parked | D231, PR #56 (closed unmerged), `docs/dev/notes/parked/0001-QoL-*.patch` (local) | Deprioritised by the maintainer on 2026-09-25. F12 today is the dev PPM dump. |
|
||||
| Frame rate above 60 (120/144/uncapped with interpolation) | decision | [`dev/UNLOCKED-FPS-PLAN.md`](dev/UNLOCKED-FPS-PLAN.md) | The menu cap is {30, 60} (the ini accepts 0 = uncapped, but the sim ticks at the VI rate). The plan's Phase 0 is resolved by D248 (the default was a port bug; it is a real 60 fps) and its Phase 1 audit and Phase 2 slice scope are written (landed 2026-09-30), but the header still reads parked. Next step is a standing sim/render-rate measurement, not the old 30 fps investigation. Must stay compatible with netplay determinism. |
|
||||
| PD Extended Options parity: settings that need new dev work | done (reviewed 2026-10-04; two rows deferred) | `MENU-PARITY-SONNET-PLAN.md` §3 (local notes); pd_port `port/src/optionsmenu.c` @ 32a1cb9 | **Reviewed 2026-10-04 (maintainer decisions; local notes).** **Done:** D511 fullscreen mode, Center window, crosshair opacity and color by health (live check owed, MENU-LIVE-CHECKLIST.md A4/A5/B3). **Watch Screen size/Ratio:** stay on the watch, no PC rows; the game saves watch settings on watch close (D349 correction) and they don't fight Aspect ratio (README). **Deferred past v0.5.0:** mouse lock mode (Auto/Always/Off; aim lock itself = `Input.AimMode`, D337); per-seat controller device select (needs a stable pad id; MP input layer). **Closed:** edge deadzone = `Input.AimRange` (D338); crosshair sway N/A (GE has none; gun sway is D428); use key reloads, no toggle (Ext keeps D378's GEPD split, Control style Original gives the N64 behaviour); per-seat options/binds (pad binds already per seat, D469/D518; keyboard/mouse is one device); no Detail textures toggle (`Video.DetailBaseTile` is the D236 fix); N/A confirmed: HUD centering (D335b), glare, overexposure, GE64 muzzle flashes, MP death music, radial menu speed, language filter, crosshair speed. **Other rows:** analog movement/stick scale/crouch → #139, T7; tickrate/framerate limit → frame rate above 60. |
|
||||
| One PC options UI: front-end "PC Options" opens the F10 overlay over the folder graphic | verify | D519, D343, D504, D510, D512 | **Landed 2026-10-04 (D519):** the file-select "PC Options" entry opens the F10 overlay over file select; the `MENU_PC_OPTIONS` screen and four old probes are gone; Bond-file rows and the watch chooser are keyed to file select. Headless checks pass. **Owed:** by-eye mouse, keyboard and pad pass on file select; profile chooser + Controller style with a real profile; footer text overlaps the Copy/Erase bar. |
|
||||
| Emulator saves load directly in the game (no tool for import) | verify | D514, D492; `port/src/eepimport.c` called from `geEepromLoad`; D297 `legacycrc.c` same pattern | **Landed 2026-10-04 (D514).** Copying a 1964/Project64 `.eep` in as `data/ge007.eep` works: on first launch each region that validates in emulator format and not port format is converted in place (same rules as `eep_convert.py`); regions invalid in both are left to the game's wipe; port saves untouched; the original is backed up to `ge007.eep.emulator.bak`. Export stays with `eep_convert.py`. Headless verify 6/6 byte-identical to the reference. **Owed:** by-eye mission select after importing a real 1964 save. |
|
||||
| DexDrive / GameFAQs `.n64` saves | idea (easy, post-1.0) | D514; sample `scratch/eepinv/gamefaqs_dexdrive_2001.eep` (local) | **Noted 2026-10-04.** Old GameFAQs GoldenEye saves are DexDrive Controller Pak dumps (`123-456-STD` header, then a 32 KB pak) whose note (game code `3BADD1E5`, 2 pages) holds the 512-byte cartridge EEPROM. Extracting it and zero-padding to 2048 bytes gives a valid emulator-format save (TenebrusoM sample: all 6 CRCs OK). Support = walk the note table, pull the note, pad, hand to the D514 importer (or an `eep_convert.py dexdrive-to-pc` subcommand). Not tested in game. |
|
||||
| Title bar and window icon (no FPS counter; static "GoldenEye 007" title + GE-crosshair window icon) | done (v0.5.0 polish) | maintainer request 2026-10-17; D550; `port/src/video.c`, `port/fast3d/gfx_sdl2.cpp`, `cmake/icon/ge007.rc`; icon art = `docs/img/icon/ge007-icon.png` (`tools_pc/make_icon.py`) | **DONE 2026-10-17 (D550):** static title bar (live FPS stays on F10), `SDL_SetWindowIcon` from a generated 32x32 header, exe icon via `ge007.rc`. Original generated mark, no game imagery. Build-verified; maintainer live window/taskbar check owed. |
|
||||
| Opt-in update check (`Game.CheckUpdates`, off by default) | verify | D551; `port/src/updatecheck.c` | **Landed 2026-10-06:** Game-page toggle; one HTTPS GET per launch to `/releases/latest` (no pre-releases); Windows loads `winhttp.dll` at runtime only. Live-verified (Win/Linux/Deck), security-reviewed. **Follow-up PR (2026-10-06):** (1) the Linux/Deck `curl` path reads into a 64 KB buffer and treats `curl`'s exit code as failure, so a `/releases/latest` response over 64 KB (long notes, many assets) silently gives no result even though `tag_name` is in the first ~1.5 KB; raise the cap and accept a full buffer (v0.4.0's response is 27 KB, so v0.5.0 is safe). (2) Validation matrix: offline harness for `versionNewer`/`extractTag` edge cases; throwaway builds with `GE007_VERSION` set to 0.3.0/0.4.0 against the live endpoint, Windows + Deck; a pre-release never prompts; after a publish, an older-version build shows the F10 row and opens the releases page; network-off and setting-off runs. |
|
||||
|
||||
### 5b. GE+ / Dab's-mod gameplay options (opt-in; touch game code)
|
||||
|
||||
| Item | Status | Refs | Notes |
|
||||
|---|---|---|---|
|
||||
| Disable knockback / hitstun, damage sound, endless death cam | post-1.0 | hooks at `chraction.c:2233`, `gunfire.c:3334`, `bondview2.c:8354` | Needs one batched Rule-2 sign-off. **Decided 2026-10-01:** after 1.0, all opt-in (default OFF), one batched Rule-2 sign-off. |
|
||||
| Bodies stay where they fell | post-1.0 | — | **Decided 2026-10-01:** after 1.0, opt-in. |
|
||||
| Port-native third-person camera | post-1.0 | #110 | The 007 Plus ROM patch itself is out of scope. Decide whether a port-native TP camera is in or out. **Decided 2026-10-01:** decide in/out after 1.0 with the other §5b options. |
|
||||
|
||||
### 5c. Enhanced visuals (optional; after feature-complete)
|
||||
|
||||
The XBLA/Nightdive-style "new look". None of it has started in this repo.
|
||||
An outside fork has a large Original/Enhanced/Remaster render-mode stack
|
||||
(see *Community forks* below). Adopting any of it is its own decision and
|
||||
needs Rule-2 review.
|
||||
|
||||
| Item | Status | Notes |
|
||||
|---|---|---|
|
||||
| Post-process chain: FXAA/SMAA, bloom, depth of field, colour grade/LUT, CRT filter, brightness/gamma, colourblind filters, SSAO | post-1.0 | Build the framebuffer pass once. Every effect defaults OFF. **Decided 2026-10-01:** after 1.0; our own opt-in implementation (default OFF; the Original N64 preset restores the N64 look); thepont's fork is reference only. |
|
||||
| Near-full fog reveal (D503): far objects about 97% fogged show as faint silhouettes with straight room-clip edges on 32-bit output (e.g. Surface's dish) | verify | **Snap landed 2026-10-04** (cutoff 247, not derived from measured vertex fog); dish residual = (C) widescreen frustum, moved to the §3 D222 row; by-eye pass across levels owed. **Decided 2026-10-03 (maintainer): option (a)**, snap fog at or above ~97% to full fog (port-only, fast3d vertex fog). Option (b) (N64 16-bit colour/dither output) stays a candidate for the Original N64 bundle. Measurements: findings D503. |
|
||||
| Render scale / supersampling, integer scaling, "authentic 320×240" mode | post-1.0 | **Decided 2026-10-01:** after 1.0; our own opt-in implementation (default OFF; the Original N64 preset restores the N64 look); thepont's fork is reference only. |
|
||||
| Dynamic/coloured lighting, dynamic shadows, water reflections | post-1.0 | Large. Consult the PD port first. #105 is about the fork's version of reflections, not ours. **Decided 2026-10-01:** after 1.0; our own opt-in implementation (default OFF; the Original N64 preset restores the N64 look); thepont's fork is reference only. |
|
||||
| Path-traced render mode (opt-in, RTGL1) | post-1.0 | Experimental, for fun (maintainer, 2026-10-01): **deprioritised to after 1.0**. Plan in the maintainer's local notes: vendor MIT RTGL1 behind `port/rt/`, default OFF; fast3d stays the default backend and the fidelity ground truth; Phase 0 is a toolchain spike (RTGL1 under MSYS2/MINGW64). Nothing starts before 1.0. |
|
||||
| HD texture packs: loader + texnum dumper; model packs | post-1.0 | #100. There is a staged design. Step 1 (texture identity registry) touches `src/game`, so it needs Rule-2 framing. **Decided 2026-10-01:** after 1.0; our own opt-in implementation (default OFF; the Original N64 preset restores the N64 look); thepont's fork is reference only. |
|
||||
| Animation smoothing (keyframe slerp) | post-1.0 | **Decided 2026-10-01:** after 1.0; our own opt-in implementation (default OFF; the Original N64 preset restores the N64 look); thepont's fork is reference only. |
|
||||
| F10 background blur + GUI-only texture filtering | post-1.0 | **Backlogged 2026-10-04 (maintainer): visual nice-to-have after 1.0.** Design done (local `MENU-BLUR-GUIFILTER-DESIGN.md`, ready Sonnet prompts in its §4). Blur: fast3d already decodes PD's framebuffer EXT opcodes and has the 16-tap kernel; only an emitter is missing (`port/fast3d/gbiex.h`); capture once on open (live capture too costly on HD 3000, D481). GUI filter: `Video.GuiTextNearest` point-samples texrects only. Port-only, default off. |
|
||||
|
||||
### 5d. Extras (nice to have)
|
||||
|
||||
In-game console · demo/replay record and playback (doubles as the
|
||||
determinism harness, §7) · photo mode · benchmark mode · subtitles · HUD
|
||||
opacity and a flash-intensity slider · a screen-shake slider that covers
|
||||
every shake source (D181 only scales explosions) · F11 video recording ·
|
||||
in-game patch notes and update check · mod loader (parked: branches
|
||||
`land/modloader-*`, D413/D414; only the content tier M1/C3′ is viable here)
|
||||
and a stage loader.
|
||||
|
||||
## 6. Multiplayer beyond the N64
|
||||
|
||||
| Item | Status | Refs | Notes |
|
||||
|---|---|---|---|
|
||||
| LAN netplay | post-1.0 | — | Depends on split-screen. Determinism groundwork is D117 (not achieved). **Decided 2026-10-01:** in scope after 1.0. |
|
||||
| MP bots | post-1.0 | #111 | Not N64 (GoldenEye's MP is human-only; Simulants are Perfect Dark). birdturtle `simulants` is an outside prototype. **Decided 2026-10-01:** in scope after 1.0 (opt-in; not N64). |
|
||||
| Co-op campaign | post-1.0 | — | Not N64. thepont `feature/coop` is an outside prototype. Listed under Not planned unless this changes. **Decided 2026-10-01:** in scope after 1.0 (opt-in; not N64). |
|
||||
| Cheats beyond the N64 cheat menu | post-1.0 | #113 | Only in scope as an opt-in toggle. **Decided 2026-10-01:** in scope after 1.0, opt-in toggles only. |
|
||||
|
||||
## 7. Tooling, infrastructure, security
|
||||
|
||||
| Item | Status | Refs | Notes |
|
||||
|---|---|---|---|
|
||||
| 1964-vs-port comparison harness | partial | `tools_pc/abpad/` | One millisecond timeline drives 1964 (virtual XInput pad) and the port (`GE_INPUTSCRIPT_MS`); both windows captured on one clock with side-by-side sheets, verified through intro skip, file select and folder open. Not frame-identical (1964 has no seed pin or frame dump). **Left:** in-level timelines (e.g. the D486 turret check); the two sides use different save files. |
|
||||
| Scripted-playthrough harness | post-1.0 (maintainer 2026-10-04: not a good use of current LLMs, revisit with newer models) | `GE_INPUTSCRIPT`, D117 | Input script + `GE_PCDUMP` + crash-log check for unattended regression runs. Levers: Invincibility + Invisibility cheats, closed-loop waypoint steering, pinned seed. Spike: one Dam route x5, measure end-position spread, then go/no-go. |
|
||||
| Cheap worker for mechanical, build-checked tasks | open | `delegate-local` MCP; memory of past Qwen issues | Birdman64's fast worker model wrote 44% of its commits while the lead planned and reviewed. Ours: pattern-applied edits (D441 fix batches, scaffolding strips, small tools), each gated by a clean build + a specific check + the lead's diff review, in the worker's own worktree. Local Qwen for edit-only tasks (it fabricates on reading tasks), or a hosted fast model via `delegate_to_provider` (the maintainer's cost call). |
|
||||
| Golden baselines | partial | `tools_pc/verify.sh`, `tools_pc/golden/README.md`, D117/D522/D529/D531 | **2026-10-06: all three sets (win/linux/deck) re-based for D543/D546**, A/B-confirmed; history in the golden README. **Windows gate GREEN (2026-10-05 re-base):** 21/21 PASS honest sequential. The recipe pins the save CONTENT (D529: `verify.sh` installs the canonical in-tree save `tools_pc/golden/ge007.eep` per level) and per-level windows — `900-1500:300` on 14 levels, `1000-1400:200` on Surface 2/Streets/Depot/Cradle/Frigate, `1500-1700:100` on Dam — because the 900 stem sits inside the wall-clock-paced intro flyby (D117; dam measured 1.6–7.2 % run to run). No tol limit raised. **Linux set (D525 → D530 → D531):** D530's re-round ran under the box-local save (`capture_p7.sh`'s re-pin overwrote D529's canonical install); D531 fixed the tool and re-captured all 21 levels under the canonical eep (47/63 frames moved). **Cross-platform spread recomputed 2026-10-05** from the committed pairs (`framediff.py` win-vs-linux at the stems, tol 2): exact **0.380–5.120 %** on the 20 non-Cuba levels (silo's 900 stem is the outlier: cell mean 34.3, phash 10), structural **21/21 clean**, max phash 40 (cradle, at threshold); Cuba 1.357–12.663 % is the structural-tier-only level. **Pixel gate green on all three, final v0.5.0 build (2026-10-06):** win 21/21; linux 21/21 on the X220 (`DISPLAY=:1` makes the GL probe see the real GPU; one Jungle flyby-jitter miss passed twice solo); deck 21/21 (needs `XAUTHORITY` for the probe over ssh). |
|
||||
| Flicker scan (A/B/A frame alternation) | parked (2026-10-03: tooling, nothing blocked on it; local spec `scratch/birdman64-rows-investigation.md` §4) | `GE_PCDUMP`, `GE_INPUTSCRIPT` | Dump **every** frame of a scripted run at low resolution and flag frame n when it differs from n-1 but matches n-2. For any old-vs-new pixel diff outside the golden gate, run a same-commit control first. Reference: Birdman64 `tools/flicker_scan.py`. |
|
||||
| Release build-provenance attestation | verify | `.github/workflows/ci.yml` release job; `.github/SECURITY.md`; README Status section | **Implemented 2026-10-04:** SHA-pinned `actions/attest-build-provenance` attests `dist/*.zip[.sha256]` + `dist/*.tar.gz[.sha256]` in the tag-only release job (`id-token: write` + `attestations: write` on that job only). Backs SECURITY.md's "unsigned, but reproducible" stance with `gh attestation verify <file> --repo jkdansereau/goldeneye-pc-port`; documented in SECURITY.md and the README. **Owed:** the v0.5.0 tag build is the first real run; confirm the step passes and verification succeeds on a downloaded asset, then delete this row. |
|
||||
| Self-hosted CI runner link failure | open | D244 | Needs a `pacman -Syu` on the runner (maintainer-only access). Release tag builds use GitHub runners, so it doesn't block a release. |
|
||||
| Remove temporary scaffolding before a release | partial | [`dev/GE-ENV-PROBES.md`](dev/GE-ENV-PROBES.md), D302 | Passes 2026-09-30 to 2026-10-03 removed the closed-finding probe suites (registry in `GE-ENV-PROBES.md`, `gen_env_probes.py --check` passing); TEMP probes `GE_TESTSTYLE` / `GE_D516` removed 2026-10-04. **Left:** D104 (kept while D294 is in verify); the M-183/M-185 clamps were **deleted 2026-10-04** (`879f879a`, `model.c` pure decomp again; the M-189 root fix stays) and the cutscene re-check was covered by the 2026-10-05 Deck campaign playthrough on rc1 (level intros, endings incl. Facility and Dam, credits). |
|
||||
| P12 U5 review of `tools_pc/**` (release-diff, B10) | partial | D531/D532/D533 | Reviewed 2026-10-05 (the unit that owed the P7 merge). **Blocker found + fixed (D531):** `capture_p7.sh`'s per-level re-pin restored the local save (see Golden baselines). **Should-fixes applied as D532 (tooling-only):** `verify.sh`'s `pin_ini_640x480` fails closed like `pin_eep`; `GOLDEN_SEED` hard-pinned with a warning for a stray `GE_RSEED`; `audiodebug.ps1`'s dead `-AB`/`-Old` switches removed. Box pixel verify done 2026-10-06 (see Golden baselines). |
|
||||
| Findings log and doc size | partial | `tools_pc/docs_budget.py` | Budget checker (per-file and tier-1 token budgets; `--toc FILE`). `findings.md` is still about 2 MB (~500k tokens), so agents must keep using the index. Nothing blocks; trim only when a budget check fails. |
|
||||
| Branch and PR housekeeping | open | — | 30 landed local branches pruned 2026-09-30 (tips in `prune-branches-2026-09-30.txt`, notes archive). **On hold by the maintainer (do not prune yet):** probe-only `d243-verify-local`, `investigate/d243-cutscene-race`, `park/d236-probes-m139`. Local `main` and `release/v0.4.1` are deliberately not pushed. PR #62 (mouse dt-decouple) stays held until mouse feel is fundamentally better. |
|
||||
| Docs site: Web 1.0 phase 2 | decision | — | Phase 1 shipped. Phase 2 (centred serif page) is plan-only. |
|
||||
| Docs site: press/SEO pass (2026-10-05) | partial | `docs/` site pages | Landed on the pure site pages + layout: real `<h1>` masthead, `color-scheme`/`theme-color`/`max-image-preview`/`rel=me`, ink-mute >=4.5:1, focus-visible + selection, 15px body, mobile padding; `date`/`modified_time` front matter on the pure pages. Home-page additions (pull-quote, Overview fact table, press section) **removed on review 2026-10-05** as duplicates. The five short topic notes (the-software-rsp, the-asset-pipeline, framerate-and-pacing, input-and-aim, saves) were dropped from the published set (files stay as working material). **Open:** the same front matter on the *shared* docs is deliberately untouched (authoritative working docs, not edited for site polish). |
|
||||
| Agentic-development deep dive (doc) | queued — not yet (2026-10-05) | `docs/dev/agentic-development.md` | Write-up owed for the public case study: per-agent commit/work estimates (manual attribution pass over the session log, incl. D350-style commit-date divergences), a code-quality audit of the trial models (GPT-6 (Sol) et al.), and process feedback / findings. Deferred by the maintainer; nothing here blocks. |
|
||||
| Docs site: split Security & fidelity -> two pages + v0.5.0 update pass (2026-10-05) | done (local, unpushed) | `docs/security.md`, `docs/fidelity.md` (was `security-and-fidelity-status.md`) | Split on review (the homepage pointer sat near Known issues and read as if there were security *issues*); both pages cross-linked and linked from the hub and index.md's Download section. Update pass 2026-10-05 against the `release/v0.4.1` tree: no network includes/symbols/HTTP literals in compiled `src/`+`port/`; no registry or library-loading calls; converter imports no network module; bundle manifests re-checked (allowlist + ROM/oversized-blob hard-fail); region macros re-compared vs the Makefile (all three regions); library classification re-checked (CMake globs libultra+libultrare audio, gu, `io/vitbl.c` only). Full game-logic audit not re-run; recent `src/game` edits are finding-gated. |
|
||||
| Docs site: final sanity sweep + anchor fixes (2026-10-05) | done (local, unpushed) | `docs/porting-notes.md` (3 TOC hrefs), `docs/404.html` (permalink) | Audit of the 10-page published set (one h1 per page, no heading skips, in-page anchors resolve, unique titles/descriptions, og:image/canonical, JSON-LD, no local paths, served == built). Three `porting-notes.md` TOC hrefs had a double hyphen where kramdown emits one: corrected at source against the live ids. `docs/404.html` got `permalink: /404.html`. Preview-builder slug rule fixed to kramdown's. |
|
||||
|
||||
## Community forks (not ours; reference only)
|
||||
|
||||
| Fork | What it has | Relevance |
|
||||
|---|---|---|
|
||||
| thepont/goldeneye-pc-port `feature/graphics-final-stack`, `fix/original-water-fidelity`, `feature/coop` | ORIGINAL/ENHANCED/REMASTER render mode, lighting, post-FX, bump/shadow/DoF, water reflections, vector HUD font, 2P co-op | Source of #105. Heavy `src/game` edits. **Their D323–D326 and "D329" labels clash with ours**, so relabel anything ported. |
|
||||
| birdturtle `feature/local-mp-input`, `simulants` | Local MP restore; first MP bot | Cross-check for #99 and #111. |
|
||||
| kaziema `vita` | PS Vita port | Platform reference. |
|
||||
| mattymattmattmatt/goldeneye-pcvr-port `vr/stereo-headaim` | VR stereo + head aim | Platform reference. |
|
||||
|
||||
## Decisions owed (maintainer)
|
||||
|
||||
Decided 2026-09-30 and 2026-10-01 (all moved into the rows above): PAL/JP in Stage B; `All unlocked` as a RAM override; D357 approved with changes; platform parity gate; feature PRs get a design review first; port_addr for #88/#95; D417 approved; the gameplay-visibility policy (§2); §5b, §5c and §6 post-1.0 and opt-in; D433 code audit; D323 post-1.0; #94 closes as stale on release.
|
||||
|
||||
Nothing else owed right now (outside PRs decided 2026-10-01, see §5a).
|
||||
|
||||
## Not planned
|
||||
|
||||
Online (non-LAN) multiplayer, matchmaking, leaderboards, anti-cheat,
|
||||
cross-play · Steamworks, achievements, cloud saves · HDR, frame
|
||||
generation, stereo 3D (ray tracing only as the opt-in post-1.0 experiment
|
||||
in §5c) · remake-scope assets (new models, music or voice) · new movement
|
||||
mechanics (jump, roll, melee) · the 007 Plus ROM patch as such (#110) · a
|
||||
per-user save folder (decided 2026-10-03: saves stay next to the game,
|
||||
keeping the security doc's "never writes to `%APPDATA%`" promise) · a
|
||||
first-launch controls card and controller toasts (decided 2026-10-03: new
|
||||
UI for little gain; F10 is documented in both READMEs).
|
||||
@@ -1,10 +1,5 @@
|
||||
# Setup Guide
|
||||
|
||||
> Inherited from the decompilation this repo forks. It covers building the
|
||||
> **N64 ROM** with the IDO toolchain — needed only for the one-time
|
||||
> asset-extraction step referenced from [`building.md`](building.md), not for
|
||||
> building or running the PC port itself.
|
||||
|
||||
<!-- TOC -->
|
||||
* [Setup Guide](#setup-guide)
|
||||
* [Prerequisites](#prerequisites)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Project Structure
|
||||
|
||||
```
|
||||
goldeneye_src
|
||||
|-- .github/workflows: GitHub use only
|
||||
├── assets: game assets
|
||||
│ ├── font: font data
|
||||
│ ├── images: image data
|
||||
│ │ └── split: split image data
|
||||
│ ├── music: music data
|
||||
│ ├── obseg: animation data
|
||||
│ │ ├── bg: bg data
|
||||
│ │ ├── brief: briefing data
|
||||
│ │ ├── chr: c model data
|
||||
│ │ ├── gun: g model data
|
||||
│ │ ├── prop: p model data
|
||||
│ │ ├── setup: setup data
|
||||
│ │ ├── stan: stan data
|
||||
│ │ └── text: text data
|
||||
│ └── ramrom: demo data
|
||||
├── bin: files that haven't been touched
|
||||
├── build: output directory
|
||||
├── include: header files
|
||||
├── rsp: Custom GBI code (Assembly) (C0 and 4Tri)
|
||||
├── src: C source code for game
|
||||
│ ├── game: core ge specific code 0x7f000000 range
|
||||
│ ├── inflate: statically linked initial decompression code
|
||||
│ ├── libultra: currently used libultra.s
|
||||
│ └── libultrarare: libultra modified by Rare
|
||||
└── tools: build tools
|
||||
```
|
||||
|
||||
Documentation has been moved to https://github.com/kholdfuzion/goldeneye_docs/tree/master/notes
|
||||
|
||||
Style Guide is https://github.com/kholdfuzion/goldeneye_src/blob/AIListLogic/notes/StyleGuide.md
|
||||
|
||||
This decompilation was only made possible thanks to many awesome 00 Agents who will be revealed only if they wish.
|
||||
|
||||
GE and PD documentation made by Zoinkity.
|
||||
@@ -1,15 +1,11 @@
|
||||
# GitHub Pages config. Site is served from the /docs folder on the default
|
||||
# branch (Settings -> Pages -> Deploy from a branch -> main / docs).
|
||||
title: GoldenEye 007 PC Port
|
||||
# Keep this SHORT (Bing SEO): jekyll-seo-tag splices it into the
|
||||
# home page <title> whenever the page title equals the site title, and the
|
||||
# long version made a ~230-char title. It also renders as the masthead
|
||||
# tagline on every page. Per-page meta/og descriptions come from each page's
|
||||
# front matter `description:` (see index.md), not from here.
|
||||
description: GoldenEye 007 (Nintendo 64, 1997) on the PC - decompiled, ported, and playable.
|
||||
# No GitHub theme: the site uses a hand-rolled _layouts/default.html plus the
|
||||
# design in _includes/head-custom.html (the dark "amber terminal" theme: a
|
||||
# near-black starfield, a single amber accent, mono chrome over a sans body).
|
||||
description: >-
|
||||
A native PC port of the original Nintendo 64 GoldenEye 007, built from its
|
||||
decompiled source, and a case study in AI-agent collaboration on a large
|
||||
low-level codebase.
|
||||
theme: jekyll-theme-cayman
|
||||
show_downloads: false
|
||||
|
||||
# Canonical site location - used by jekyll-seo-tag (canonical URLs, og: tags)
|
||||
@@ -19,7 +15,7 @@ baseurl: /goldeneye-pc-port
|
||||
|
||||
# Link-preview card image for jekyll-seo-tag (og:image / twitter:image).
|
||||
# Path is relative to the site root; the plugin prepends url + baseurl.
|
||||
image: /img/shots/shot-28.jpg
|
||||
image: /img/attract-bunker1.png
|
||||
|
||||
# Both are on the GitHub Pages plugin whitelist - no Gemfile needed.
|
||||
# jekyll-seo-tag: per-page <title>/description/canonical/OpenGraph via {% seo %}
|
||||
@@ -30,63 +26,26 @@ plugins:
|
||||
- jekyll-sitemap
|
||||
|
||||
# jekyll-relative-links (on by default for Pages) rewrites the .md links in
|
||||
# these docs to .html so cross-links work on the published site. Links that
|
||||
# must reach docs kept out of the site build point at the GitHub blob URL
|
||||
# instead, so they resolve from the published pages too.
|
||||
# these docs to .html so cross-links work on the published site.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The site publishes a small curated set:
|
||||
# index.md, documentation.md (the documentation hub), building.md, plus
|
||||
# 404.html (themed not-found page),
|
||||
# the four docs grouped behind that hub: internals.md, porting-notes.md,
|
||||
# dev-process.md, dev/agentic-development.md, and
|
||||
# security-and-fidelity-status.md (split on review 2026-10-05 into
|
||||
# security.md + fidelity.md, linked from index.md's Download section and
|
||||
# the hub).
|
||||
# The five short topic notes (the-software-rsp.md, the-asset-pipeline.md,
|
||||
# framerate-and-pacing.md, input-and-aim.md, saves.md) were dropped from the
|
||||
# site on review 2026-10-05: the Perfect Dark port documents the same
|
||||
# subjects and nothing here was significantly new; they stay in the repo as
|
||||
# working material. Everything else under docs/ is working material - the
|
||||
# finding log, level status, backlogs, plans, session notes, inherited
|
||||
# decomp guides. It stays in the repo (and on GitHub) but is excluded from
|
||||
# the site build so it never shows up as a published page or in sitemap.xml.
|
||||
# NOTE: keep this list in sync as dev/ working docs are added; a new unlisted
|
||||
# .md under docs/ is silently published to the site + sitemap.xml.
|
||||
# Rolling local working files - not useful as published pages.
|
||||
exclude:
|
||||
- the-software-rsp.md
|
||||
- the-asset-pipeline.md
|
||||
- framerate-and-pacing.md
|
||||
- input-and-aim.md
|
||||
- saves.md
|
||||
- HANDOFF.md
|
||||
- ROADMAP.md
|
||||
- SetupGuide.md
|
||||
- StyleGuide.md
|
||||
- dev/ACCURACY-AUDIT-2026-09-20.md
|
||||
- dev/AUDIO-PLAN.md
|
||||
- dev/AUDIT-M6-player-offsets.md
|
||||
- dev/CONTROLLER-INPUT-PLAN.md
|
||||
- dev/CUT-CONTENT-BACKLOG.md
|
||||
- dev/D176a-SKY-NOTES.md
|
||||
- dev/D356-SETTINGS-REGROUP-PLAN.md
|
||||
- dev/D357-SETTINGS-VALUES-PLAN.md
|
||||
- dev/GE-ENV-PROBES.md
|
||||
- dev/GEPD-INPUT-PLAN.md
|
||||
- dev/HANDOFF-ARCHIVE.md
|
||||
- dev/LEVEL-OBJECTIVES.md
|
||||
- dev/LEVEL-PLAYTEST.md
|
||||
- dev/LEVEL-STATUS.md
|
||||
- dev/notes
|
||||
- dev/OPTIONS-MENU-PLAN.md
|
||||
- dev/preview-video.md
|
||||
- dev/QOL-INVENTORY.md
|
||||
- dev/RUMBLE-PLAN.md
|
||||
- dev/TEXTURE-GLITCH-ANALYSIS.md
|
||||
- dev/UNLOCKED-FPS-PLAN.md
|
||||
- dev/VIEWMODEL-RESEARCH.md
|
||||
- dev/WATCH-SETTINGS-PLAN.md
|
||||
- dev/findings-index.csv
|
||||
- dev/findings.md
|
||||
- dev/game-behavior-reference.md
|
||||
- dev/release-dropin-rom-plan.md
|
||||
|
||||
# Keep the raw engineering record (finding log, per-level status, triage
|
||||
# notes, backlogs) out of sitemap.xml so search engines aren't steered at it.
|
||||
# The pages still build and stay reachable from cross-links; the two dev/
|
||||
# docs that are linked from the site index are kept in.
|
||||
defaults:
|
||||
- scope:
|
||||
path: "dev"
|
||||
values:
|
||||
sitemap: false
|
||||
- scope:
|
||||
path: "dev/agentic-development.md"
|
||||
values:
|
||||
sitemap: true
|
||||
- scope:
|
||||
path: "dev/game-behavior-reference.md"
|
||||
values:
|
||||
sitemap: true
|
||||
@@ -3,366 +3,286 @@
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600;700&family=IBM+Plex+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
/* ----------------------------------------------------------------------
|
||||
Site theme (replaces the 2026-09-28 "night desk" theme).
|
||||
Look: a dark, minimal "amber terminal" -- near-black space background
|
||||
(a faint starfield), a single amber accent (GoldenEye gold), a
|
||||
console/mono identity for chrome (IBM Plex Mono) over a clean
|
||||
readable sans body (IBM Plex Sans). Flat and calm: to reduce eye
|
||||
strain and evoke the old-school web. Content is untouched; this is
|
||||
a presentation layer only.
|
||||
Design pass over the stock Cayman theme.
|
||||
Restrained "spy-tech" palette: near-black, slate, a single amber accent.
|
||||
Loaded after the theme stylesheet, so these rules win without !important.
|
||||
--------------------------------------------------------------------- */
|
||||
:root {
|
||||
--bg: #050607; /* true near-black */
|
||||
--bg-frame: #0c0d0f; /* the page frame */
|
||||
--bg-panel: #121316; /* code / cards / tables */
|
||||
--ink: #e6e4df; /* warm off-white */
|
||||
--ink-soft: #a9a7a0; /* secondary text */
|
||||
--ink-mute: #8a8a83; /* captions, footer (>=4.5:1 on --bg-frame:
|
||||
the old #70706a measured 3.9:1 and failed
|
||||
WCAG AA on the tagline + legal line) */
|
||||
--line: #26282c; /* 1px borders */
|
||||
--line-2: #3a3d44; /* stronger borders */
|
||||
--accent: #e8a13a; /* GoldenEye gold (amber) */
|
||||
--accent-hi: #ffca7a; /* link / accent hover */
|
||||
--code-ink: #ffcf9e; /* code text (warm) */
|
||||
--th: #15161a; /* table header fill */
|
||||
--zebra: #0e0f12; /* alt row fill */
|
||||
--measure: 46rem;
|
||||
--mono: "IBM Plex Mono","Courier New",monospace;
|
||||
--sans: "IBM Plex Sans",Verdana,Geneva,sans-serif;
|
||||
color-scheme: light dark;
|
||||
--ink: #14171c;
|
||||
--ink-soft: #3d444d;
|
||||
--ink-mute: #626b76;
|
||||
--line: #e4e6e9;
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f8fa;
|
||||
--accent: #b7791f;
|
||||
--accent-ink: #8a5a12;
|
||||
--header-1: #0d1117;
|
||||
--header-2: #1c2530;
|
||||
--radius: 8px;
|
||||
--measure: 44rem;
|
||||
}
|
||||
|
||||
html { background: var(--bg); }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--ink: #e7eaee;
|
||||
--ink-soft: #b9c0c9;
|
||||
--ink-mute: #8b939e;
|
||||
--line: #2a2f37;
|
||||
--bg: #0f1216;
|
||||
--bg-soft: #171b21;
|
||||
--accent: #e0a94a;
|
||||
--accent-ink: #f0c274;
|
||||
--header-1: #0a0d11;
|
||||
--header-2: #161d27;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
color: var(--ink);
|
||||
font-family: var(--sans);
|
||||
font-size: 15px; /* 14px read as console chrome, but the
|
||||
body is prose people actually read */
|
||||
line-height: 1.6;
|
||||
/* faint starfield (the old-school "space" tile, softened) */
|
||||
background-color: var(--bg);
|
||||
background-image:
|
||||
radial-gradient(1px 1px at 22px 34px, rgba(255,255,255,.50), transparent),
|
||||
radial-gradient(1px 1px at 120px 82px, rgba(255,255,255,.28), transparent),
|
||||
radial-gradient(1.4px 1.4px at 68px 138px, rgba(232,161,58,.45), transparent),
|
||||
radial-gradient(1px 1px at 172px 44px, rgba(255,255,255,.20), transparent),
|
||||
radial-gradient(1px 1px at 92px 172px, rgba(255,255,255,.32), transparent);
|
||||
background-size: 200px 200px;
|
||||
background: var(--bg);
|
||||
font-size: 17px;
|
||||
line-height: 1.7;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent);
|
||||
/* ---- Header --------------------------------------------------------- */
|
||||
.page-header {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
text-align: center;
|
||||
padding: 4rem 1rem 3.75rem;
|
||||
background: linear-gradient(135deg, var(--header-1), var(--header-2));
|
||||
border-bottom: 1px solid rgba(224, 169, 74, 0.28);
|
||||
}
|
||||
.page-header::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
background-image: url("{{ '/img/attract-bunker1.png' | relative_url }}");
|
||||
background-size: cover;
|
||||
background-position: center 38%;
|
||||
opacity: 0.16;
|
||||
filter: grayscale(0.35) contrast(1.05);
|
||||
}
|
||||
.page-header::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
background: linear-gradient(180deg, rgba(13,17,23,0.35), rgba(13,17,23,0.82));
|
||||
}
|
||||
.project-name {
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
font-size: 2.4rem;
|
||||
margin: 0 0 0.4rem;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 24px rgba(0,0,0,0.5);
|
||||
}
|
||||
.project-tagline {
|
||||
font-weight: 400;
|
||||
font-size: 1.12rem;
|
||||
line-height: 1.55;
|
||||
color: #d3d9e0;
|
||||
opacity: 1;
|
||||
max-width: 42rem;
|
||||
margin: 0 auto 1.6rem;
|
||||
}
|
||||
.page-header .btn {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1.1rem;
|
||||
margin: 0.3rem 0.25rem 0;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.01em;
|
||||
color: #f3f5f7;
|
||||
background: rgba(255,255,255,0.06);
|
||||
border: 1px solid rgba(255,255,255,0.22);
|
||||
border-radius: var(--radius);
|
||||
transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
|
||||
}
|
||||
.page-header .btn:hover {
|
||||
background: rgba(224,169,74,0.16);
|
||||
border-color: var(--accent);
|
||||
text-decoration: none;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* ---- Body ---------------------------------------------------------- */
|
||||
.main-content {
|
||||
max-width: 52rem;
|
||||
margin: 0 auto;
|
||||
padding: 3rem 1.5rem 1rem;
|
||||
color: var(--ink);
|
||||
font-size: 17px;
|
||||
}
|
||||
.main-content p,
|
||||
.main-content ul,
|
||||
.main-content ol,
|
||||
.main-content blockquote { max-width: var(--measure); }
|
||||
|
||||
.main-content h1,
|
||||
.main-content h2,
|
||||
.main-content h3,
|
||||
.main-content h4 {
|
||||
color: var(--ink);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.015em;
|
||||
line-height: 1.3;
|
||||
margin-top: 2.4rem;
|
||||
}
|
||||
.main-content h1 { font-size: 1.9rem; }
|
||||
.main-content h2 {
|
||||
font-size: 1.4rem;
|
||||
padding-bottom: 0.35rem;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.main-content h3 { font-size: 1.15rem; }
|
||||
.main-content > h1:first-child { margin-top: 0.5rem; }
|
||||
|
||||
.main-content a {
|
||||
color: var(--accent-ink);
|
||||
text-decoration: underline;
|
||||
text-decoration-color: rgba(232,161,58,.45);
|
||||
text-decoration-color: rgba(183,121,31,0.4);
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
a:hover { color: var(--accent-hi); text-decoration-color: currentColor; }
|
||||
|
||||
/* keyboard focus + selection: the amber thread again, so a focused
|
||||
control is visible on the near-black ground (a11y / keyboard users) */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--accent-hi);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
::selection { background: var(--accent); color: #0b0b0d; }
|
||||
|
||||
/* ---- the centred "page" frame -------------------------------------- */
|
||||
.frame {
|
||||
max-width: 50rem;
|
||||
margin: 20px auto 32px;
|
||||
background: var(--bg-frame);
|
||||
border: 1px solid var(--line-2);
|
||||
box-shadow: 0 0 0 1px #000, 0 18px 50px -30px #000;
|
||||
}
|
||||
|
||||
/* ---- banner -------------------------------------------------------- */
|
||||
.banner { text-align: center; padding: 2.4rem 1rem 1.4rem; }
|
||||
.banner .t {
|
||||
margin: 0; /* it is an <h1> now (see the layout) */
|
||||
font-family: var(--mono);
|
||||
font-weight: 700;
|
||||
font-size: 1.7rem;
|
||||
letter-spacing: .05em;
|
||||
color: var(--accent); /* brand name in goldeneye gold -- the amber
|
||||
thread (masthead -> nav -> rule -> page title) */
|
||||
}
|
||||
.banner .tag { color: var(--ink-mute); font-size: .82rem; margin-top: .55rem; }
|
||||
|
||||
/* ---- top nav row --------------------------------------------------- */
|
||||
.sitenav {
|
||||
text-align: center;
|
||||
padding: .5rem 1rem;
|
||||
font-family: var(--mono);
|
||||
font-size: .78rem;
|
||||
letter-spacing: .05em;
|
||||
}
|
||||
.sitenav a {
|
||||
.main-content a:hover {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
padding: 0 .35rem;
|
||||
text-decoration-color: currentColor;
|
||||
}
|
||||
.sitenav a:hover { color: var(--accent-hi); text-decoration: underline; }
|
||||
.sitenav .sep { color: var(--ink-mute); }
|
||||
|
||||
/* the amber rule */
|
||||
.rule { border: 0; height: 1px; background: var(--line); margin: 0; }
|
||||
.rule.g {
|
||||
background: linear-gradient(90deg, transparent, var(--accent), transparent);
|
||||
height: 1px; opacity: .55;
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.main-content a { color: var(--accent); }
|
||||
}
|
||||
|
||||
/* ---- content ------------------------------------------------------- */
|
||||
.doc-content { padding: 1.6rem 1.6rem .6rem; }
|
||||
.doc-content p, .doc-content ul, .doc-content ol,
|
||||
.doc-content blockquote { max-width: var(--measure); }
|
||||
|
||||
.doc-content h1 {
|
||||
font-family: var(--mono);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
letter-spacing: .02em;
|
||||
margin: .2rem 0 1rem;
|
||||
}
|
||||
.doc-content > h1:first-child { margin-top: .2rem; }
|
||||
.doc-content h2 {
|
||||
font-family: var(--mono);
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
letter-spacing: .03em;
|
||||
margin: 2rem 0 .8rem;
|
||||
padding-bottom: .35rem;
|
||||
border-bottom: 1px solid var(--line-2);
|
||||
}
|
||||
.doc-content h2::before { content: "\25aa "; color: var(--accent); }
|
||||
/* Page-title look: the 2026-09 SEO pass demoted page headings to h2
|
||||
(the masthead owns h1; no duplicate page titles) -- see the SEO
|
||||
conventions note in scratch/pr-seo-revise-prompt.md. The first
|
||||
content h2 of each page gets the h1 look; presentation only, the
|
||||
heading levels stay per-convention. (On the home page the masthead
|
||||
itself is the title, so its first h2 -- Status -- leads as a section.) */
|
||||
/* .flat-title (front matter on the home page): the masthead IS the
|
||||
page title there, so the first h2 must read as a plain section --
|
||||
otherwise masthead-h1 + amber first-h2 stack as a double header. */
|
||||
.doc-content:not(.flat-title) > h2:first-of-type {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
margin: .2rem 0 1rem;
|
||||
padding-bottom: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.doc-content h3 {
|
||||
font-family: var(--mono);
|
||||
font-size: .92rem;
|
||||
color: var(--accent-hi);
|
||||
margin: 1.6rem 0 .5rem;
|
||||
}
|
||||
.doc-content > p > em:only-child {
|
||||
font-style: normal; color: var(--ink-soft); font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.doc-content hr {
|
||||
border: 0; height: 1px; background: var(--line-2); margin: 2rem 0;
|
||||
}
|
||||
|
||||
.doc-content blockquote {
|
||||
color: var(--ink-soft);
|
||||
.main-content blockquote {
|
||||
color: var(--ink-mute);
|
||||
border-left: 3px solid var(--accent);
|
||||
padding: .2rem 0 .2rem 1rem;
|
||||
padding: 0.2rem 0 0.2rem 1.1rem;
|
||||
margin-left: 0;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* ---- code ---------------------------------------------------------- */
|
||||
.doc-content code {
|
||||
font-family: var(--mono);
|
||||
font-size: .86em;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--line);
|
||||
padding: .1em .4em;
|
||||
color: var(--code-ink);
|
||||
.main-content hr {
|
||||
height: 1px;
|
||||
background: var(--line);
|
||||
border: 0;
|
||||
margin: 2.5rem 0;
|
||||
}
|
||||
.doc-content pre {
|
||||
background: var(--bg-panel);
|
||||
|
||||
/* Lead paragraph on doc pages (the italic summary under the H1) */
|
||||
.main-content > p > em:only-child {
|
||||
font-style: normal;
|
||||
color: var(--ink-soft);
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
/* ---- Code --------------------------------------------------------- */
|
||||
.main-content code {
|
||||
font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 0.86em;
|
||||
background: var(--bg-soft);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 4px;
|
||||
padding: 0.12em 0.4em;
|
||||
color: var(--ink);
|
||||
}
|
||||
.main-content pre {
|
||||
background: var(--header-1);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
padding: 1rem 1.15rem;
|
||||
max-width: var(--measure);
|
||||
overflow-x: auto;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.doc-content pre code {
|
||||
background: transparent; border: 0; padding: 0;
|
||||
color: var(--code-ink); font-size: .84rem;
|
||||
.main-content pre code {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
color: #e6edf3;
|
||||
font-size: 0.84rem;
|
||||
}
|
||||
|
||||
/* ---- tables -------------------------------------------------------- */
|
||||
.doc-content table {
|
||||
border-collapse: collapse; width: 100%;
|
||||
max-width: var(--measure); margin: 1.5rem 0; font-size: .9rem;
|
||||
/* ---- Tables ------------------------------------------------------- */
|
||||
.main-content table {
|
||||
border-collapse: collapse;
|
||||
display: table;
|
||||
width: 100%;
|
||||
max-width: var(--measure);
|
||||
margin: 1.5rem 0;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
.doc-content th, .doc-content td {
|
||||
border: 1px solid var(--line); padding: .5rem .7rem; text-align: left;
|
||||
}
|
||||
.doc-content th {
|
||||
background: var(--th); color: var(--accent);
|
||||
font-family: var(--mono); font-size: .74rem;
|
||||
letter-spacing: .05em; font-weight: 600;
|
||||
}
|
||||
.doc-content tr:nth-child(2n) td { background: var(--zebra); }
|
||||
|
||||
/* ---- console blocks ------------------------------------------------ */
|
||||
/* Status / News / Download / Known issues panels (home page). Same
|
||||
amber-terminal language as the code blocks above, but the content
|
||||
keeps its semantic list/table markup (SEO + screen readers).
|
||||
|
||||
Site formatting rules (2026-10-05 PR pass + 2026-09 SEO conventions)
|
||||
— keep these:
|
||||
- Heading LEVELS follow the SEO pass: the page heading is h2 (the
|
||||
masthead owns h1; no duplicate page titles), sections are h2,
|
||||
h3 labels a real sub-topic only (e.g. the current-release line
|
||||
under Status). The first content h2 gets the amber "page-title"
|
||||
look via CSS only (.doc-content > h2:first-of-type) -- never by
|
||||
promoting the markup to h1.
|
||||
- h2 = the section, outside the panel.
|
||||
- Directives ("always use the latest release") are .callouts inside
|
||||
panels, or a .warn line (amber rule, ink text) where no panel
|
||||
exists; never headings — headings label topics,
|
||||
never emphasis.
|
||||
- List intros ("what a player will notice…") stay plain paragraphs.
|
||||
- Dated list-item leads ("2026-10-05 — v0.5.0") stay <strong> leads
|
||||
inside the <li>, not headings.
|
||||
- Headings must be single source lines: marked ends an ATX heading at
|
||||
the line break, so a wrapped "### …" silently becomes a heading plus
|
||||
a stray paragraph (Jekyll/other renderers differ — don't rely on
|
||||
multi-line headings at all). */
|
||||
.doc-content .cblock {
|
||||
background: var(--bg-panel);
|
||||
.main-content table th,
|
||||
.main-content table td {
|
||||
border: 1px solid var(--line);
|
||||
padding: 1.1rem 1.3rem .9rem;
|
||||
margin: 1.6rem 0;
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
.main-content table th { background: var(--bg-soft); font-weight: 600; }
|
||||
.main-content table tr { background: transparent; }
|
||||
.main-content table tr:nth-child(2n) { background: var(--bg-soft); }
|
||||
|
||||
/* Expandable panels (<details>, e.g. the release history folded under
|
||||
Status): same panel language as .cblock; the summary line is the
|
||||
amber handle. The content is in the DOM either way -- collapsing is
|
||||
presentation, not hiding from search. */
|
||||
.doc-content details {
|
||||
background: var(--bg-panel);
|
||||
.main-content img {
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--line);
|
||||
padding: .3rem 1.3rem .8rem;
|
||||
margin: 1.6rem 0;
|
||||
}
|
||||
.doc-content summary {
|
||||
font-family: var(--mono);
|
||||
font-size: .9rem;
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
letter-spacing: .03em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.cblock h3 { margin-top: 0; color: var(--ink); }
|
||||
.cblock p, .cblock ul, details p, details ul {
|
||||
font-family: var(--mono);
|
||||
font-size: .88rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.cblock li, details li { margin: .5rem 0; }
|
||||
.cblock li::marker, details li::marker { color: var(--accent); }
|
||||
.cblock strong, details strong { color: var(--accent); font-weight: 600; }
|
||||
.cblock code, details code { background: transparent; border: 0; padding: 0; }
|
||||
.doc-content .callout {
|
||||
border-left: 3px solid var(--accent);
|
||||
padding: .15rem 0 .15rem .8rem;
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
.doc-content .callout p { color: var(--ink-soft); }
|
||||
|
||||
/* Inline directive marker -- "Warning:" leading a sentence, amber like
|
||||
the rest of the accent thread, no panel around it. */
|
||||
.doc-content details .warn {
|
||||
border-left: 3px solid var(--accent);
|
||||
padding: .1rem 0 .1rem .7rem;
|
||||
margin: .2rem 0 .9rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.cblock table { background: transparent; margin: 1rem 0 .4rem; }
|
||||
.cblock > p:last-child { margin-bottom: 0; }
|
||||
|
||||
/* ---- images -------------------------------------------------------- */
|
||||
.doc-content img {
|
||||
border: 1px solid var(--line); border-radius: 0; max-width: 100%;
|
||||
height: auto; /* no squashed stills when a width= is set */
|
||||
}
|
||||
|
||||
/* ---- console-viewer carousel (home page media) --------------------- */
|
||||
.viewer {
|
||||
max-width: 38rem; margin: 1rem auto;
|
||||
border: 1px solid var(--line-2);
|
||||
background: var(--bg-panel);
|
||||
font-family: var(--mono);
|
||||
/* ---- Footer ------------------------------------------------------- */
|
||||
.site-footer {
|
||||
max-width: 52rem;
|
||||
margin: 3rem auto 0;
|
||||
padding: 1.5rem 1.5rem 2.5rem;
|
||||
border-top: 1px solid var(--line);
|
||||
color: var(--ink-mute);
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
.viewer .vbar {
|
||||
display: flex; align-items: center; gap: .5rem;
|
||||
padding: .4rem .6rem; border-bottom: 1px solid var(--line);
|
||||
font-size: .72rem; color: var(--ink-mute); letter-spacing: .05em;
|
||||
}
|
||||
.viewer .vbar .dots { display: flex; gap: .3rem; }
|
||||
.viewer .vbar .dots i {
|
||||
width: 8px; height: 8px; border: 1px solid var(--line-2);
|
||||
display: inline-block;
|
||||
}
|
||||
.viewer .vbar .dots i:nth-child(1) { border-color: var(--accent); }
|
||||
.viewer .vbar .lbl { flex: 1; }
|
||||
.viewer .vbar .idx { color: var(--accent); }
|
||||
.viewer .vstage { position: relative; background: #000; aspect-ratio: 16 / 9; }
|
||||
.viewer .vstage img { width: 100%; height: 100%; object-fit: contain; display: block; }
|
||||
.viewer .vbtn {
|
||||
position: absolute; top: 50%; transform: translateY(-50%);
|
||||
background: rgba(6,7,8,.55);
|
||||
border: 1px solid var(--line-2);
|
||||
color: var(--accent);
|
||||
font-family: var(--mono); font-size: 1.3rem; line-height: 1;
|
||||
width: 34px; height: 52px; cursor: pointer; padding: 0;
|
||||
}
|
||||
.viewer .vbtn:hover { color: var(--accent-hi); border-color: var(--accent); }
|
||||
.viewer .vbtn.prev { left: 0; }
|
||||
.viewer .vbtn.next { right: 0; }
|
||||
.viewer .vcap {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: .45rem .6rem; border-top: 1px solid var(--line);
|
||||
font-size: .74rem; color: var(--ink-soft); letter-spacing: .02em;
|
||||
.site-footer a { color: var(--accent-ink); }
|
||||
@media (prefers-color-scheme: dark) { .site-footer a { color: var(--accent); } }
|
||||
|
||||
@media (max-width: 42rem) {
|
||||
.project-name { font-size: 1.9rem; }
|
||||
.page-header { padding: 3rem 1rem 2.75rem; }
|
||||
.main-content { padding-top: 2rem; }
|
||||
}
|
||||
|
||||
/* ---- footer -------------------------------------------------------- */
|
||||
.site-footer { text-align: center; padding: 1.2rem 1rem 1.6rem; }
|
||||
.footer-stamp {
|
||||
font-family: var(--mono); font-size: .78rem;
|
||||
letter-spacing: .03em; color: var(--ink-soft);
|
||||
margin: 0 0 .7rem;
|
||||
/* ---- Mermaid diagrams ------------------------------------------------ */
|
||||
/* Kramdown renders ```mermaid fences as <pre><code class="language-mermaid">.
|
||||
The script below hands their text to mermaid and swaps in the SVG. Keep
|
||||
the pre unstyled (no dark code background) while it waits to render. */
|
||||
.main-content pre.mermaid,
|
||||
.main-content pre > code.language-mermaid {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: var(--ink);
|
||||
padding: 0;
|
||||
}
|
||||
.footer-stamp a { color: var(--accent); text-decoration: none; }
|
||||
.footer-stamp a:hover { color: var(--accent-hi); text-decoration: underline; }
|
||||
.footer-legal {
|
||||
color: var(--ink-mute); font-size: .72rem; line-height: 1.55;
|
||||
margin: 0 auto; max-width: var(--measure);
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
.frame { margin: 8px; }
|
||||
.banner { padding: 1.4rem .8rem 1rem; }
|
||||
.banner .t { font-size: 1.35rem; }
|
||||
.doc-content { padding: 1.1rem 1rem .5rem; }
|
||||
.doc-content table { font-size: .82rem; }
|
||||
.doc-content pre { padding: .7rem .8rem; }
|
||||
}
|
||||
|
||||
.main-content pre.mermaid { text-align: center; overflow-x: auto; }
|
||||
.main-content pre.mermaid:not([data-processed]) { visibility: hidden; }
|
||||
</style>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
||||
|
||||
// Move each ```mermaid code block's text into a <pre class="mermaid"> that
|
||||
// mermaid.run() recognises.
|
||||
const dark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
document.querySelectorAll("pre > code.language-mermaid").forEach((code) => {
|
||||
const pre = document.createElement("pre");
|
||||
pre.className = "mermaid";
|
||||
pre.textContent = code.textContent;
|
||||
code.parentElement.replaceWith(pre);
|
||||
});
|
||||
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
securityLevel: "loose", // our own docs; needed for <br/> in node labels
|
||||
theme: dark ? "dark" : "neutral",
|
||||
});
|
||||
mermaid.run({ querySelector: "pre.mermaid" });
|
||||
</script>
|
||||
@@ -1,71 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- Paint the browser chrome (scrollbars, form controls, new-tab cards)
|
||||
dark before CSS loads, so the page never flashes white on mobile. -->
|
||||
<meta name="color-scheme" content="dark">
|
||||
<meta name="theme-color" content="#050607">
|
||||
<!-- Indexing is NOT restricted: this only asks Google/Bing to show the
|
||||
large image preview in search cards, which is what makes the
|
||||
in-engine stills legible in a coverage roundup. -->
|
||||
<meta name="robots" content="max-image-preview:large">
|
||||
<!-- Favicon: the project's own crosshair mark (tools_pc/make_icon.py),
|
||||
not a game still -- original art, no derivative-work exposure. -->
|
||||
<!-- favicon.ico is a byte copy of cmake/icon/ge007.ico (the exe icon, with
|
||||
the hand-tuned 16/24/32 px variants); a browser-downscaled 512 px PNG
|
||||
washes out at tab size. Re-copy it after running make_icon.py. -->
|
||||
<link rel="icon" href="{{ '/favicon.ico' | relative_url }}" sizes="16x16 24x24 32x32 48x48 64x64 128x128 256x256">
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="{{ '/img/icon/ge007-icon.png' | relative_url }}">
|
||||
<!-- Entity disambiguation: the maintainer behind every page. -->
|
||||
<link rel="me" href="https://github.com/jkdansereau">
|
||||
{%- seo -%}
|
||||
{%- include head-custom.html -%}
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
|
||||
<!-- The masthead IS the page's h1: every page gets exactly one h1
|
||||
(the brand), and the page's own heading starts at h2. A <div>
|
||||
here left the document with no h1 at all, which is what search
|
||||
and screen readers key on. Presentation only -- the CSS class
|
||||
carries the look. -->
|
||||
<header class="banner">
|
||||
<h1 class="t">{{ site.title }}</h1>
|
||||
<p class="tag">{{ site.description }}</p>
|
||||
</header>
|
||||
|
||||
<nav class="sitenav" aria-label="Site navigation">
|
||||
<a href="{{ '/' | relative_url }}">home</a><span class="sep">/</span>
|
||||
<a href="{{ '/documentation' | relative_url }}">documentation</a><span class="sep">/</span>
|
||||
<a href="{{ '/building' | relative_url }}">building</a>
|
||||
</nav>
|
||||
<div class="rule g"></div>
|
||||
|
||||
<!-- flat_title: this page's masthead IS its title, so the layout opts
|
||||
out of the amber first-h2 "page-title" look (no double header).
|
||||
Jekyll + the preview builder both honour this conditional. -->
|
||||
<main class="doc-content{% if page.flat_title %} flat-title{% endif %}">
|
||||
{{ content }}
|
||||
</main>
|
||||
|
||||
<div class="rule g"></div>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="footer-stamp">
|
||||
<a href="https://github.com/jkdansereau">maintained by jkdansereau</a>
|
||||
· last updated: 2026-10-07 · v0.5.0
|
||||
· <a href="https://github.com/jkdansereau/goldeneye-pc-port">GitHub</a>
|
||||
· <a href="https://github.com/jkdansereau/goldeneye-pc-port/releases">releases</a>
|
||||
</div>
|
||||
<div class="footer-legal">
|
||||
Non-commercial fan preservation / research project. No ROM or game assets are
|
||||
distributed; you supply a ROM you already own. Not affiliated with or endorsed
|
||||
by Nintendo, Rare, Microsoft, MGM, Danjaq, or EON Productions.
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,19 +1,14 @@
|
||||
---
|
||||
title: Building
|
||||
description: Full build and asset-extraction guide for the GoldenEye 007 PC port, covering Windows (MSYS2) and Linux.
|
||||
---
|
||||
|
||||
## Building the PC port
|
||||
# Building the PC port
|
||||
|
||||
Stages:
|
||||
|
||||
1. **Extract assets from your ROM** (§2): a one-time step using the
|
||||
1. **Extract assets from your ROM** (§2) — a one-time step using the
|
||||
decompilation's own toolchain to pull levels, models, textures, fonts and
|
||||
music into `assets/`. *(Only needed to regenerate the committed data files;
|
||||
a plain `git clone` already has what the PC build compiles.)*
|
||||
2. **Build the port** (§3): a CMake build compiling the game sources plus the
|
||||
2. **Build the port** (§3) — a CMake build compiling the game sources plus the
|
||||
`port/` layer into a native executable. Needs no ROM.
|
||||
3. **Generate the PC asset sidecars** (§4): two pure-Python converters turn
|
||||
3. **Generate the PC asset sidecars** (§4) — two pure-Python converters turn
|
||||
ROM model / stage data into the PC-layout `data/pcmodels-*` / `data/pccg-*`
|
||||
files the port loads at runtime. **Required to run.**
|
||||
|
||||
@@ -27,11 +22,10 @@ for accepted versions and hashes.
|
||||
|
||||
### Port build
|
||||
|
||||
> **The Windows (MSYS2 MINGW64) path is the primary one.** The Linux build is
|
||||
> compiled by CI on every push and ships in the release bundle (also
|
||||
> playtested on Steam Deck hardware); the macOS column is best-effort
|
||||
> guidance and has never been built or run there. Expect to fix build breaks
|
||||
> yourself on untested platforms.
|
||||
> **Only the Windows (MSYS2 MINGW64) path is tested.** The Debian/Ubuntu and
|
||||
> macOS columns below are best-effort guidance — the port has never been built
|
||||
> or run on Linux or macOS. Expect to fix build breaks yourself on those
|
||||
> platforms.
|
||||
|
||||
| Need | Windows (MSYS2 MINGW64) | Debian/Ubuntu | macOS (Homebrew) |
|
||||
|------|------------------------|---------------|------------------|
|
||||
@@ -46,12 +40,12 @@ for accepted versions and hashes.
|
||||
|
||||
The extraction scripts need `binutils-mips-linux-gnu` (or an equivalent MIPS
|
||||
binutils), `make`, `git`, and `python3`. They build a small host-compiled
|
||||
`tools/extractor` and slice blobs straight out of the ROM; **no IDO / IRIX
|
||||
`tools/extractor` and slice blobs straight out of the ROM — **no IDO / IRIX
|
||||
toolchain is involved in extraction or in the PC build.** (The IDO toolchain is
|
||||
only needed to build the N64 ROM itself, and its proprietary SGI binaries are
|
||||
not distributed here; see [`SetupGuide.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/SetupGuide.md) "Recompile IDO".)
|
||||
not distributed here — see [`SetupGuide.md`](SetupGuide.md) "Recompile IDO".)
|
||||
On Windows this is easiest under WSL or a Linux VM. Full details and
|
||||
alternatives (Docker) are in `SetupGuide.md`.
|
||||
alternatives (Docker) are in [`SetupGuide.md`](SetupGuide.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -76,7 +70,7 @@ and run:
|
||||
(US extraction is a prerequisite for the others.)
|
||||
|
||||
This populates `assets/` with the generated `.bin` blobs the build needs. See
|
||||
[`SetupGuide.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/SetupGuide.md) for the in-depth build/asset pipeline.
|
||||
[`SetupGuide.md`](SetupGuide.md) for the in-depth build/asset pipeline.
|
||||
|
||||
---
|
||||
|
||||
@@ -93,15 +87,6 @@ cmake -S . -B build-pc -DROMID=ntsc-final
|
||||
cmake --build build-pc -j
|
||||
```
|
||||
|
||||
On Windows, `build-pc.sh` also locates the MSYS2 MinGW64 toolchain itself
|
||||
(so it works from Git Bash or a non-login shell too, even with another
|
||||
`cmake` earlier on PATH) and, if native child processes would get an
|
||||
unwritable `TMP`, re-runs the configure+build under PowerShell with a
|
||||
writable one. If MSYS2 is not installed at `C:\msys64`, set
|
||||
`GE_MSYS2_ROOT` (e.g. `GE_MSYS2_ROOT='C:\tools\msys64'` for a custom
|
||||
install location). Re-exec logs:
|
||||
`build-pc/ge007-native-reexec-*.log`.
|
||||
|
||||
For PAL/JP you must first generate that region's ROM-asset symbol file
|
||||
(the US one is committed):
|
||||
|
||||
@@ -118,7 +103,7 @@ builds are named `ge007.pal-final.x86_64` / `ge007.jpn-final.x86_64`.
|
||||
## 4. Generate the PC asset sidecars (required to run)
|
||||
|
||||
The port does **not** read model geometry, stage bg/stan data, or per-level
|
||||
setup data from the raw ROM at runtime; it reads them from PC-layout *sidecar*
|
||||
setup data from the raw ROM at runtime — it reads them from PC-layout *sidecar*
|
||||
files under `data/`, produced offline by three converters. **Without them the
|
||||
game shows the intro logos and then crashes** in
|
||||
`modelPromoteNodeOffsetsToPointers` (finding D179) or on the first level load
|
||||
@@ -137,16 +122,9 @@ to d69's output):
|
||||
```sh
|
||||
python3 tools_pc/d43_emit.py ntsc-final # -> data/pcmodels-ntsc-final/{pcmodels.bin,manifest.csv} (~1.3 MB)
|
||||
python3 tools_pc/d69_emit.py ntsc-final # -> data/pccg-ntsc-final/{pccg.bin,manifest.csv} (bg + stan)
|
||||
python3 tools_pc/d88_emit.py ntsc-final --regen # appends the 21 solo Usetup*Z + 13 multiplayer Ump_setup*Z stage-setup files -> ~3.6 MB
|
||||
python3 tools_pc/d88_emit.py ntsc-final --regen # appends the 21 per-level Usetup*Z stage-setup files -> ~3.6 MB
|
||||
```
|
||||
|
||||
**PAL / JP note:** both regions convert, build and boot from source (finding
|
||||
D258, fixed 2026-10-02; D487/D488 fixed the PAL frame height and TV type).
|
||||
Run the three emit passes with `pal-final` or `jpn-final`, and build into a
|
||||
separate directory, for example `BUILD_DIR=build-pal ./build-pc.sh pal-final`.
|
||||
Release packages are still NTSC-U only until per-region packaging lands
|
||||
(issue #85).
|
||||
|
||||
These are **pure-stdlib Python 3** (no MIPS toolchain, independent of the
|
||||
step-2 asset extraction) and read only the ROM plus files already committed to
|
||||
the repo (`scripts/filelist.u.csv`, `assets/obseg/file_resource_table.inc.c`,
|
||||
@@ -155,12 +133,10 @@ deterministic function of the ROM. Re-run after any change to `d43_emit.py` /
|
||||
`d69_emit.py` / `d88_emit.py` or the model/bg converters (`d43_*`, `d69_*`,
|
||||
`d88_propdefs.py`).
|
||||
|
||||
> The release bundle ships the same converter frozen as
|
||||
> `prepare-assets/ge007-convert`; the game spawns it on first launch when the
|
||||
> sidecars are missing, so end users never run it by hand. See the bundled
|
||||
> `README.md`.
|
||||
> The release bundle ships `prepare-assets/prepare-assets.py`, which runs all
|
||||
> three passes against your ROM automatically — see the bundled `README.md`.
|
||||
|
||||
> `data/pcmodels-*/` and `data/pccg-*/` are gitignored ROM-derived game data;
|
||||
> `data/pcmodels-*/` and `data/pccg-*/` are gitignored ROM-derived game data —
|
||||
> never commit or redistribute them.
|
||||
|
||||
---
|
||||
@@ -182,4 +158,4 @@ runtime. `ge007.ini` is written under `data/` on first launch.
|
||||
| `GE_PCDUMP="first-last:step"` | dump rendered frames as PPM (debugging) |
|
||||
|
||||
More diagnostic switches are cataloged in
|
||||
[`dev/GE-ENV-PROBES.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/dev/GE-ENV-PROBES.md).
|
||||
[`dev/GE-ENV-PROBES.md`](dev/GE-ENV-PROBES.md).
|
||||
@@ -1,76 +1,32 @@
|
||||
---
|
||||
title: Development Process
|
||||
description: The investigation workflow behind the port; task budgets, file partitioning, and the finding-log discipline used across sessions and agents.
|
||||
---
|
||||
# How this port is developed
|
||||
|
||||
## How this port is developed
|
||||
|
||||
Most of this port's work is not writing code; it is **diagnosis**: figuring
|
||||
Most of this port's work is not writing code — it is **diagnosis**: figuring
|
||||
out which N64 hardware or ABI behavior 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
|
||||
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 behavior, so the mechanics differ; but the
|
||||
this one matches runtime behavior, so the mechanics differ — but the
|
||||
project-structure lessons carry over directly.
|
||||
|
||||
### Diagnosis is unrestricted; only the fix is constrained
|
||||
|
||||
AGENTS.md non-negotiable #2 restricts *changing* `src/game` behavior. It
|
||||
has never restricted *investigating* it. An agent that stops short of
|
||||
naming the exact `src/game` struct, field, or logic responsible for a bug
|
||||
— because touching that file "feels" off-limits — is doing worse
|
||||
diagnosis than the rules require, not better compliance with them.
|
||||
Several of this port's hardest, highest-impact bugs (D209, D210, D255)
|
||||
were exactly this shape: a `src/game` state variable reading wrong,
|
||||
root-caused only by tracing fully into the game file that owned it.
|
||||
|
||||
Every investigation should end able to say which of three buckets
|
||||
applies:
|
||||
|
||||
- **(a) ABI/layout-class.** The true cause is a struct-layout or
|
||||
pointer-width misread introduced by the 32→64-bit transition — in a
|
||||
ROM-serialized record or a live runtime struct/union alike. Fixable
|
||||
under the narrow exception, entirely inside the game file under
|
||||
`#ifdef PORT`, no sign-off needed. Check `docs/porting-notes.md` §A1
|
||||
first (the pattern's tells + worked examples) — this is the most
|
||||
common shape a "`src/game` behavior looks wrong" report turns out to
|
||||
have.
|
||||
- **(b) Port-layer bug that merely manifests through game state.** The
|
||||
game code is reading state that's correct for what N64 would have
|
||||
produced; something in `port/` (a shim, fast3d, frame timing, an
|
||||
uninitialized or mis-ordered port-owned value) is what actually
|
||||
differs. Fixable entirely in `port/`, no `src/game` touch, no sign-off
|
||||
needed.
|
||||
- **(c) Genuine game-logic behavior difference.** The decomp's
|
||||
byte-identical control flow, given verified-correct inputs, provably
|
||||
diverges from real N64 behavior. Rare, and requires the rule-2
|
||||
sign-off procedure (§7 below) before any `src/game` edit.
|
||||
|
||||
Bucket (c) is rare by design — most bugs that look like (c) turn out to
|
||||
be (a) or (b) in disguise — but ruling it in or out requires actually
|
||||
tracing the state to its source, not stopping at the file boundary.
|
||||
"This state lives in `src/game`" is a reason to keep tracing, not a
|
||||
reason to stop.
|
||||
|
||||
## 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
|
||||
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
|
||||
- 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.
|
||||
@@ -84,17 +40,16 @@ more room.
|
||||
|
||||
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,
|
||||
each entry a terse index into the full
|
||||
[`dev/findings.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/dev/findings.md)
|
||||
[`porting-notes.md`](porting-notes.md) — the recurring N64→PC bug classes,
|
||||
each entry a terse 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;
|
||||
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,
|
||||
@@ -102,18 +57,6 @@ The classes it currently tracks:
|
||||
- whole serialized formats are converted by an **offline sidecar** rather than
|
||||
patched at runtime.
|
||||
|
||||
**Two process lessons from the sibling GEVR VR port** (same decomp lineage,
|
||||
reference-only survey in `scratchpad/GEVR-TRIAGE.md` item D — their corpus
|
||||
hit both repeatedly enough to name them): (1) **a grep standing in for a
|
||||
read** — a site list built by grepping one helper name or spelling misses
|
||||
siblings spelled differently or living in an adjacent file (their own
|
||||
missed-smoke-scissor-site example); treat a grep-built site list as a draft,
|
||||
not a census, until it's been read against. (2) **write the falsifier before
|
||||
the run** — state up front what result would kill the hypothesis ("if X
|
||||
survives, hypothesis Y is dead"), not just what would confirm it; this fits
|
||||
the existing budget/findings discipline directly and costs one sentence per
|
||||
brief.
|
||||
|
||||
## 3. Parallelise by file, consolidate often
|
||||
|
||||
Lewis ran multiple git worktrees and found the real cost was
|
||||
@@ -122,7 +65,7 @@ Lewis ran multiple git worktrees and found the real cost was
|
||||
- **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
|
||||
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.
|
||||
@@ -136,7 +79,7 @@ Before dispatching an investigation agent:
|
||||
- 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.
|
||||
the analogous code — same engine family.
|
||||
|
||||
Then hand the agent the *findings*, not just the problem.
|
||||
|
||||
@@ -145,136 +88,19 @@ Then hand the agent the *findings*, not just the problem.
|
||||
- **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
|
||||
inverted here — the hard part is identifying the implicit hardware/ABI
|
||||
contract the already-readable C depends on.
|
||||
|
||||
## 6. Keep PR/git-history noise low
|
||||
|
||||
A session produces a lot of small, independently-verifiable units of work;
|
||||
one Dxx row, one docs pass, one mechanical fix. Left unmanaged that turns
|
||||
into a pile of small open PRs that's harder to review than the work
|
||||
justifies. Rules:
|
||||
|
||||
- **Squash-merge single-purpose and docs-only PRs.** The repo has squash
|
||||
merge enabled; use it (`gh pr merge --squash --delete-branch`) for anything
|
||||
whose in-branch commit history (iteration, probe-add, probe-strip) isn't
|
||||
itself useful to keep; which is most PRs. Reserve a plain merge commit for
|
||||
a PR whose individual commits are each a distinct, reviewable unit someone
|
||||
might want to `git revert` independently (e.g. this session's PR #50: a
|
||||
fix commit + a separate analysis-only commit).
|
||||
- **Batch same-session, same-flavor docs updates into one PR/branch**
|
||||
instead of opening a new PR per finding row. A runtime re-check that
|
||||
updates one `findings.md` row (like #48) doesn't need its own PR if
|
||||
another docs-only branch is already open in the same session; fold it in.
|
||||
Only split into separate PRs when the pieces have genuinely different
|
||||
review/merge timing (e.g. one needs a human playtest, the other doesn't).
|
||||
- **A branch takes only the change it's named for.** Don't let unrelated
|
||||
housekeeping (a stray gitignore fix, a config tweak) ride along on an
|
||||
active feature/fix branch just because it's the one checked out; cut a
|
||||
separate branch, even for a one-line change.
|
||||
- **Stacked PRs declare the dependency in the title/body** ("step 2 of N,
|
||||
step 1 is #N") and get rebased/merged in order promptly; don't let a
|
||||
later step sit open so long the earlier step's content drifts under it.
|
||||
- **Triage the open-PR list at least once a session**, ordered by
|
||||
time-to-close (docs-only and non-draft first, human-playtest-gated last).
|
||||
A PR idle long enough that `main` has moved past the finding rows it
|
||||
touches is **stale, not just old**; check for real textual conflict
|
||||
(`git merge-tree`), not just calendar age. If `main` already has more
|
||||
current information than the PR (a bug the PR still lists `OPEN` that's
|
||||
since been fixed and verified), **close it rather than force a merge or
|
||||
spend a cycle rebasing it**; re-derive only whatever part of its content
|
||||
is still true as a fresh, small PR against current `main`. Merging stale
|
||||
content back over newer information is a regression, not a save.
|
||||
- **Commit/tree hygiene is a standing rule, not a per-session judgment** —
|
||||
see AGENTS.md §Tree hygiene & release gate. In short: no local absolute
|
||||
paths or usernames in tracked files (use the placeholder convention), no
|
||||
contributor emails in prose (name + PR number), every vendored third-party
|
||||
license declared in `NOTICE`, no bulk ROM-derived captures in the tree
|
||||
(capture output dirs are gitignored — keep them on disk, untracked), and
|
||||
one batched commit per session rather than one per approved fix.
|
||||
|
||||
## 7. The rule-2 sign-off procedure (genuine `src/game` behavior changes)
|
||||
|
||||
Use this only for bucket (c) from the diagnosis framing above — never for
|
||||
the ABI/layout exception (§A1-class fixes need no sign-off, only
|
||||
documentation) and never for anything expressible in `port/`.
|
||||
|
||||
**To request sign-off, the write-up must state:**
|
||||
|
||||
1. The exact `src/game` file:line and the specific behavior being
|
||||
proposed for change.
|
||||
2. **Proof of divergence.** Given verified-correct inputs — i.e. every
|
||||
upstream ABI/layout suspect (§A1) and every port-layer suspect has
|
||||
been checked and ruled out, with the ruling-out cited — the
|
||||
byte-identical decomp code still produces behavior that differs from
|
||||
real N64 reference behavior. Name the reference (a capture, documented
|
||||
prior N64 behavior, a cited external source). "It looks wrong" is not
|
||||
proof; "it disagrees with `<reference>`, verified by `<method>`" is.
|
||||
3. Why the fix cannot be expressed in `port/`, and why it does not
|
||||
qualify under the ABI/layout exception — the two questions that must
|
||||
be answered "no" before this procedure applies at all.
|
||||
4. The proposed fix, scoped as tightly as possible to the one named
|
||||
behavior, plus a same-engine precedent if one exists — the Perfect
|
||||
Dark port is the standing reference; cite the analogous PD change the
|
||||
way `docs/dev/WIDESCREEN-FOV-PLAN.md`'s "Rule #2 exception framing"
|
||||
section did.
|
||||
|
||||
**Granting.** The user reviews the above and replies with an explicit
|
||||
go-ahead in-thread. There is no implicit or inferred sign-off: silence,
|
||||
"seems reasonable," a prior similar grant, or approval of a *different*
|
||||
behavior in the same file do not carry over to a new request.
|
||||
|
||||
**Recording.** Every sign-off gets its own `docs/dev/findings.md` entry
|
||||
(next `Dxx` label), tagged **`RULE-2-SIGNOFF`** in its status line
|
||||
alongside the normal finding classification, and recording: the request
|
||||
as stated above, the grant (quoted or summarized, with date), and the
|
||||
resulting diff. Add the label to `findings-index.csv` like any other row,
|
||||
so `tools_pc/gen_findings_index.py` keeps it discoverable — chat history
|
||||
is not the record of approval; the finding entry is.
|
||||
|
||||
**Constraints that still apply after approval:**
|
||||
|
||||
- Still `#ifdef PORT` — the N64 build is untouched, and the `#else` arm
|
||||
(or the file's un-PORT-guarded default) still matches the original
|
||||
decomp exactly.
|
||||
- Still scoped to the one named behavior — a grant is not a standing
|
||||
license; a different behavior difference in the same file needs its own
|
||||
request, even if related.
|
||||
- Still fully documented in the `RULE-2-SIGNOFF` finding entry — that
|
||||
entry is the permanent record of *why* this specific exception to
|
||||
non-negotiable #2 exists.
|
||||
- If the fix would visibly change behavior at *default*
|
||||
settings/difficulty/region beyond the one bug being fixed, treat that
|
||||
as a signal the scope drifted mid-implementation — stop and go back for
|
||||
a fresh sign-off on the wider scope rather than shipping it under the
|
||||
narrower original grant.
|
||||
|
||||
This generalizes the ad hoc precedent in
|
||||
`docs/dev/WIDESCREEN-FOV-PLAN.md`'s "Rule #2 exception framing" section (a
|
||||
real, already-granted case, kept as historical record) into a reusable
|
||||
process; new requests cite this section rather than re-deriving the case
|
||||
for a process each time.
|
||||
|
||||
## Investigation-brief template
|
||||
|
||||
```
|
||||
TASK: <one bug, one subsystem>.
|
||||
READ FIRST: docs/porting-notes.md; docs/dev/findings.md <specific Dxx entries>.
|
||||
FILES YOU MAY TOUCH: <disjoint from any other in-flight work>.
|
||||
KNOWN-GOOD / RULED OUT: <list; do not re-investigate>.
|
||||
KNOWN-GOOD / RULED OUT: <list — do not re-investigate>.
|
||||
PRE-FLIGHT ATTACHED: <probe output, baseline capture, PD-port pointer>.
|
||||
BUDGET: <N build->run cycles / ~M min>. On expiry: revert probes, write up with confidence.
|
||||
CONSTRAINTS: diagnosis may trace as deep into src/game state as the evidence
|
||||
needs — name the exact struct/field/logic responsible before concluding
|
||||
which bucket applies (see "Diagnosis is unrestricted" above). The FIX is
|
||||
still constrained: no src/game behavior changes unless (a) it's an
|
||||
ABI/layout-class struct-layout or pointer-width misread — check
|
||||
docs/porting-notes.md §A1 first; #ifdef PORT; documented in the finding
|
||||
log under §F/D3x — or (b) it's fixable entirely in port/ (no src/game
|
||||
touch). A genuine game-logic behavior difference (bucket c) is NOT
|
||||
authorized by this brief — stop and write up a rule-2 sign-off request
|
||||
per docs/dev-process.md §7 instead of applying it.
|
||||
VERIFY: <the exact tools_pc/verify.sh (or probe) invocation that proves this done; or, if runtime verification is impossible in this environment, say so and name what a human must run>.
|
||||
CONSTRAINTS: no game-logic changes; ABI/layout/format only; #ifdef PORT; documented in the finding log.
|
||||
REPORT: (a) root cause + file:line evidence (b) fix diff, or why not
|
||||
(c) probes left in tree (d) confidence.
|
||||
Append any generalisable quirk to docs/porting-notes.md.
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
# Accuracy / code review — N64 fidelity sweep (2026-09-20)
|
||||
|
||||
A thorough pass over the PC port with one question: *where does the port diverge from the
|
||||
N64 original, and what latent failure classes of the D318 kind are still unpriced?*
|
||||
Method: static review against the decomp ground truth + targeted verification builds.
|
||||
Findings below; new finding **D320** logged in `findings.md`.
|
||||
|
||||
## 1. PRNG — verified bit-exact
|
||||
|
||||
`port/src/random.c` re-checked line-by-line against both `src/random.s` and
|
||||
`src/game/chrObjRandom.s`. The MIPS64 `dsll32`/`dsrl32` semantics (post-D284) are
|
||||
emulated correctly: the 32-bit LFSR state advances identically, and
|
||||
`randomGetNext()` returns the same 31-bit values in the same order. No divergence.
|
||||
|
||||
## 2. Known bug-class sweeps — all clean or already fixed
|
||||
|
||||
| Class | Result |
|
||||
|---|---|
|
||||
| **A1** raw-byte/padding aliases (32→64-bit layout shift) | Remaining `padding[]` uses are in pointer-free structs (layout-stable) or already fixed (D115, D209, D312). No live misreads. |
|
||||
| Shifts by 32 / pointer-width arithmetic in game logic | Only port-layer code (`d318watchdog` print, `DRAM_SIZE`). Game logic clean. |
|
||||
| Signed-overflow UB | `-fwrapv` enabled in `CMakeLists.txt` — the whole class is neutralized to N64-wrap semantics. |
|
||||
| `#ifdef PORT` hygiene (N64 path preserved) | 307 hunks audited; 178 are pure additions (diagnostics / struct extensions). No hunk alters the non-PORT control flow. |
|
||||
| **§B** Gfx/Vtx 16-byte records (raw byte indexing) | `bg.c` uses typed `.words`/`.dma` fields throughout; no remaining raw byte offsets. D312's fix pattern is the last of this class. |
|
||||
| `osVirtualToPhysical` call sites (88) | All flow through fast3d's `seg_addr()` (D131 fix intact). No site assumes N64 segment RAM layout. |
|
||||
| Dead code | `objecthandler_2.c` compiles but is not linked on PC (symbols absent from the binary). Harmless; noted for a future source-list prune. |
|
||||
|
||||
## 3. Timing layer — intentional, documented
|
||||
|
||||
Frame-locked virtual clock (vsync tick), `GE_DETERM` fixed-tick mode, and
|
||||
`g_GlobalTimerDelta` zero-guarded at every division site. All deliberate port-layer
|
||||
design, each documented in its finding. No hidden drift source found beyond the
|
||||
already-fixed D204 tempo work.
|
||||
|
||||
## 4. D318-class latent softlock sweep → **D320** (the main new result)
|
||||
|
||||
D318's mechanism is generic, not Facility-specific: `actor_fire_or_aim_at_target_update`
|
||||
(`chraction.c:4882`) retargets any ACT_ATTACK chr with an AIM_ONLY/DONTTURN attacktype and
|
||||
only moves the model's **endframe** — it never re-inits the animation. If the chr is pinned
|
||||
on a pre-shoot aim-hold pose (hold endframe H < variant shoot window) and the new endframe
|
||||
lands above H, the model pins forever and the timer-gated fire events never open; if the
|
||||
surrounding AI script's only escape is an `if_guard_has_stopped_moving` gate, it can never
|
||||
pass (`chrHasStoppedOrPatroling` is FALSE for ACT_ATTACK). **Permanent softlock — on N64 too.**
|
||||
|
||||
Swept every level's AI scripts (`assets/obseg/setup/*.c`): 17 lists in 8 levels contain an
|
||||
aim-hold + `_update` pair. Risk-ranked table in the D320 entry. Headlines:
|
||||
|
||||
- **Facility ai_19** — HIGH. Same non-self-healing shape as ai_22 (second softlock point in
|
||||
the same level).
|
||||
- **Control ai_9** — HIGH. Near-exact ai_22 mirror (pre-aim hold → timer → update → fire
|
||||
loop gated on the target dying from this guard's shot).
|
||||
- **Depot ai_12** — MED-HIGH. The kill is scripted, but the objective bit is gated behind a
|
||||
stop-check; pinned guard → level-complete softlock.
|
||||
- **Bond-combat loops** (Aztec/Control/Egyptian/Depot/Streets/Bunker/Surface) — likely safe:
|
||||
sustained fire keeps the model inside the shoot window by design, and every playtest
|
||||
exercises them without freezes. Inferred, not probe-verified per list.
|
||||
|
||||
Classification: **as-authored latent race(s), faithfully reproduced** — same bucket as D318,
|
||||
not a port bug. Verification path (generalized timeline probe) and the
|
||||
generalize-the-watchdog recommendation are in D320.
|
||||
|
||||
## 5. Fixed this session
|
||||
|
||||
- `port/fast3d/gfx_pc.cpp:2417` — the GE_D116 diagnostic printf used `%f` for `uint32_t`
|
||||
args (`v->u`, `tex_width`) and `%d` for unsigned tile fields; wrong debug output only, no
|
||||
runtime impact. Casts added; build now warning-free on that file.
|
||||
|
||||
## 6. Verification ritual
|
||||
|
||||
Full `./build-pc.sh ntsc-final` configure + compile + link: **PASS**, no warnings on
|
||||
touched files. All new/changed symbols resolve exactly once (link is the sweep). D318
|
||||
watchdog/timeline symbols confirmed present in the binary.
|
||||
|
||||
## 7. Deliberately not committed (release-prep WIP, separate workstream)
|
||||
|
||||
`.github/release-notes.md`, `README.md`, `docs/index.md`, `docs/media/goldeneye-gh-preview.gif`,
|
||||
`docs/dev/agentic-development.md`, `.gitignore` (`/local/` exclude), and non-D318 scratch
|
||||
artifacts — left in the working tree for their owner.
|
||||
@@ -1,204 +1,375 @@
|
||||
# AUDIO-PLAN — Phase 3 / ROADMAP B3 (libaudio → SDL)
|
||||
# Audio Plan — Phase 3 (libaudio → SDL)
|
||||
|
||||
> **Historical record — do not act on status claims in this file.** The audio track shipped (D198–D204); the "gaps" and TODOs described below are closed. Kept for the architecture rationale. Current open work and status: [`docs/ROADMAP.md`](../ROADMAP.md) (2026-09-29).
|
||||
|
||||
Status: plan of record for the audio track, 2026-09-05. Companion to
|
||||
`ROADMAP-1.0.md` §B3 ("audio (Phase 3)", "audio track parallel from M-50 →
|
||||
v0.4.0"). Independent of B1/B2 — can start immediately.
|
||||
|
||||
**Status update (M-69):** Phases 0–2 below are DONE — the mixer landed as
|
||||
D199 (`port/src/mixer.c` macro-swap), D200/D201 fixed the two pointer-width
|
||||
landmines it exposed, and D204 fixed a permanent ~2 % tempo drift. Phase 3
|
||||
(verification) is now the active track: D202's sample-correctness question is
|
||||
closed negative through M-69 (full-corpus per-voice exact match — 286/320
|
||||
requests CC ≥ 0.85, every allocated voice plays the exact ROM sample at the
|
||||
exact ROM pitch, no corruption). Remaining: user by-ear pass + N64 A/B on the
|
||||
door-loop cap (M-66b), then D202 probe removal + close; reverb/mix-balance
|
||||
tuning is still owed. See findings.md D199–D204.
|
||||
Status: **draft for evaluation** (written read-only; no code changes made).
|
||||
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/porting-notes.md`.
|
||||
|
||||
---
|
||||
|
||||
## Where things stand (verified 2026-09-05)
|
||||
## Contents
|
||||
|
||||
More is already in place than "audio not started" suggests:
|
||||
1. [Goal and scope](#1-goal-and-scope)
|
||||
2. [Current state](#2-current-state-verified-by-reading-not-assumption)
|
||||
3. [The GE pipeline in one paragraph](#3-the-ge-pipeline-in-one-paragraph)
|
||||
4. [How the PD port does it](#4-how-the-pd-port-does-it-ground-truth-comparison)
|
||||
5. [Architecture options](#5-architecture-options-evaluate-all-recommendation-marked) — options A/B/C + sub-decisions A1–A3
|
||||
6. [DMEM layout](#6-dmem-layout-ido-from-srclibultraaudiosynthinternalsh5771)
|
||||
7. [Shims and ABI fixes required](#7-shims-and-abi-fixes-required) — incl. [7.1 `K0_TO_PHYS` bug](#71-bug-found-k0_to_phys-is-unshimmed-and-corrupts-pointers-on-pc)
|
||||
8. [Phase 0 — recon](#8-phase-0--recon-do-first-informs-everything)
|
||||
9. [Open questions](#9-open-questions-decide-before-phase-1)
|
||||
10. [Phases and milestones](#10-phases-and-milestones)
|
||||
11. [Verification ritual (audio-specific)](#11-verification-ritual-audio-specific-on-top-of-the-standard-one-in-agentsmd)
|
||||
12. [Risks / unknowns](#12-risks--unknowns)
|
||||
13. [Key references](#13-key-references)
|
||||
|
||||
- **The whole CPU-side audio engine already compiles and runs.**
|
||||
`src/audi.c`, `src/snd.c`, `src/music.c` are in `SRC_ENGINE`; IDO libaudio
|
||||
(`src/libultra/audio/*.c`) + Rare's naudio "New" driver
|
||||
(`src/libultrare/audio/*.c`: drvrNew/env/reverb) are in the link. The
|
||||
`amMain` audio thread runs per VI-retrace; D54/D54b already killed its
|
||||
SIGSEGVs — so synthesis bookkeeping is live (D77: "music runs in code but
|
||||
no audible output").
|
||||
- **The scheduler side needs zero changes.** `osSpTaskStartGo`
|
||||
(`port/src/libultra.c:1237`) already has an M_AUDTASK branch that completes
|
||||
the task and posts `OS_EVENT_SP`, so `amMain`'s reply-message handshake
|
||||
works. The branch is currently a no-op — exactly where the audio ucode's
|
||||
job used to go.
|
||||
- **The two TODO stubs are the actual gap:** `osAiSetNextBuffer()` /
|
||||
`osAiGetLength()` in `port/src/libultra.c:852-862` discard everything
|
||||
(`osAiGetLength` returns 0), and `port/src/audio.c` never opens an SDL
|
||||
device. `main.c:141-142` already calls `audioInit()` / `mixerInit()`.
|
||||
- **The PD reference is local** at `<repos>\pd_port`,
|
||||
with the full audio stack: `port/src/audio.c` (75 lines, SDL device +
|
||||
queue), `port/src/mixer.c` (~720 lines, software RSP-audio-ucode), and the
|
||||
wiring trick in `include/PR/abi.h`.
|
||||
---
|
||||
|
||||
## Architecture — PD's macro-swap trick, adapted
|
||||
## 1. Goal and scope
|
||||
|
||||
On N64, `alAudioFrame()` only *builds* an acmd list; the RSP audio ucode
|
||||
(`aspMain`) executes it. PD's PC port never runs that ucode: under
|
||||
`#ifndef PLATFORM_N64`, `include/PR/abi.h` swaps each acmd-building macro
|
||||
`aXxx(pkt, ...)` for a call to a CPU implementation (`aXxxImpl`) operating on
|
||||
a software DMEM array. libaudio/naudio compile unmodified; every "command"
|
||||
executes inline at build time; the M_AUDTASK that reaches the scheduler is an
|
||||
empty formality (PD's `pdsched.c` audio branch is literally empty).
|
||||
Make GoldenEye's audio audible on the PC port with 1:1 fidelity to the N64 pipeline:
|
||||
music (3 cseq tracks) and SFX through the real libaudio synth, out of SDL2.
|
||||
|
||||
**Critical catch — GE and PD use different acmd ABIs** (the AGENTS.md
|
||||
"same family ≠ identical format" case):
|
||||
In scope:
|
||||
- Executing the per-frame **Acmd stream** that stock IDO libaudio emits (the part that
|
||||
ran on the RSP under GE's `aspMain` microcode).
|
||||
- SDL audio device + queue plumbing (`port/src/audio.c`, AI shims in `libultra.c`).
|
||||
- One latent ABI fix in the port shim headers (`K0_TO_PHYS`, §7.1).
|
||||
|
||||
| | GE (`include/PR/abi.h`) | PD (naudio "New") |
|
||||
Out of scope:
|
||||
- MP3 playback (naudio-only, PD-specific; GE never emits it).
|
||||
- Input (done — D118), saves (Phase 4).
|
||||
- Any change to game logic. All work lives in `port/` except the narrow, documented
|
||||
shim-header exception in §7.1 (same class as D78).
|
||||
|
||||
## 2. Current state (verified by reading, not assumption)
|
||||
|
||||
Working already:
|
||||
- **The whole CPU side of libaudio runs.** `src/libultra/audio/*` +
|
||||
`src/libultrare/audio/{drvrNew,env,reverb}.c` compile and execute; the audio thread
|
||||
(`amMain`, `src/audi.c`) gets retrace messages from the real scheduler (`src/sched.c`,
|
||||
compiled — Phase 2 decision), calls `alAudioFrame()`, which fills a 3000-word Acmd
|
||||
list and posts an `M_AUDTASK` to the scheduler. Bank trees, cseq fixups (D54, D36/D37)
|
||||
are done. Sample DMA from cart works via `amDmaCallback` → `osPiStartDma`
|
||||
(synchronous ROM-image shim in `libultra.c`).
|
||||
|
||||
Dead (the three gaps):
|
||||
1. **Acmd list never executed.** `port/src/libultra.c:1108` `osSpTaskStartGo()` has an
|
||||
explicit Phase-3 no-op branch for `M_AUDTASK`. The output buffer (`info->data`) is
|
||||
never filled.
|
||||
2. **AI shims are stubs.** `libultra.c:794–810`: `osAiSetNextBuffer` discards the
|
||||
buffer, `osAiGetLength` returns 0 (so the per-frame sample-count feedback loop in
|
||||
`amHandleFrameMessage`, `audi.c:527`, runs blind).
|
||||
3. **No SDL device.** `port/src/audio.c` `audioInit()` is a TODO stub; and nothing
|
||||
calls `audioEndFrame()` (zero callers repo-wide).
|
||||
|
||||
Existing PORT guards to revisit when audio lands:
|
||||
- **C7 / D127** (`src/snd.c:859`): skips SFX whose resolved `ALSound*` pointer is out of
|
||||
range — added because the converted bank tree (`romdata.c afFixupInst`) yields fewer/
|
||||
rearranged `soundArray` slots than the N64 image on some levels (Surface1). May be
|
||||
masking a real bank-tree defect. See **Q1**.
|
||||
- `src/audi.c:365`: `#if defined(PORT)` memcpy of `CUSTOM_FX_PARAMS_N` (C++-style
|
||||
initializer workaround) — harmless, keep.
|
||||
|
||||
## 3. The GE pipeline in one paragraph
|
||||
|
||||
Audio thread on each retrace: `amHandleFrameMessage()` (`src/audi.c:497`) hands the
|
||||
*previous* frame's output to AI via `osAiSetNextBuffer(lastInfo->data, …)`, computes
|
||||
this frame's sample count from `g_FrameSize - osAiGetLength()>>2` (clamped to
|
||||
`g_MinFrameSize`), calls `alAudioFrame()` which walks the filter chains (decoder →
|
||||
resampler → envmixer → [FX: GE's custom chorus/reverb] → aux bus → main bus → save)
|
||||
emitting **Acmd words** into a 3000-word list, then posts an `M_AUDTASK` (ucode =
|
||||
`aspMain`). The real scheduler (`sched.c:439,485`) queues it in the audio list and runs
|
||||
it through `osSpTaskLoad/osSpTaskStartGo`. On the N64 the RSP executes the Acmd stream
|
||||
against 4 KB of DMEM, writing interleaved s16 stereo to `info->data`; AI DMA then plays
|
||||
it at 22050 Hz. On PC we must replace "RSP executes Acmd stream" — that is the entire
|
||||
problem.
|
||||
|
||||
**Opcode inventory** (grep-verified across `src/libultra/audio/*.c` +
|
||||
`src/libultrare/audio/*.c` — 15 ops):
|
||||
|
||||
| Op | Emitted by | PD reference code? |
|
||||
|---|---|---|
|
||||
| `aADPCMdec` | `(pkt, flags, statePtr)` — params from an `aSetBuffer` context + `ADPCM_STATE[16]` | `(pkt, state, flags, count, inofs, outofs)` — inline args |
|
||||
| `aLoadBuffer` / `aSaveBuffer` | `(pkt, dramAddr)` only — dest/count from the `aSetBuffer` context | explicit dest + count args |
|
||||
| `aInterleave` | `(pkt, lDmem, rDmem)` | no args |
|
||||
| Opcodes | 16: adds `aSetBuffer`, `aPan`, `aSegment`; no `aDisable` / `aPlayMP3` | 15 incl. MP3 (PD-specific) |
|
||||
| `aSetBuffer` (×20) | load, mainbus, resample, save, reverb | yes (`a*Impl` pattern; different arg ABI) |
|
||||
| `aMix` (×7) | mainbus, reverb | yes — `aMixImpl` |
|
||||
| `aLoadBuffer` (×7) | load, reverb | yes — `aLoadBufferImpl` |
|
||||
| `aClearBuffer` (×7) | auxbus, mainbus, load, reverb | yes — `aClearBufferImpl` |
|
||||
| `aSetVolume` (×5) | env.c (Rare envelope) | yes — `aSetVolumeImpl` |
|
||||
| `aSaveBuffer` (×4) | save, reverb | yes — `aSaveBufferImpl` |
|
||||
| `aDMEMMove` (×4) | load, resample, reverb | yes — `aDMEMMoveImpl` |
|
||||
| `aResample` (×2) | resample.c, reverb.c | yes — `aResampleImpl` (SSE4.1/NEON/scalar) |
|
||||
| `aLoadADPCM` (×2) | load.c, reverb.c | yes — `aLoadADPCMImpl` |
|
||||
| `aSetLoop` | load.c:479 | yes — `aSetLoopImpl` |
|
||||
| `aSegment` | synthesizer.c:231 | n/a (no-op on CPU) |
|
||||
| `aPoleFilter` | **reverb.c:402 (GE custom FX)** | **NO — PD's stub is empty ("never gets called?")** |
|
||||
| `aInterleave` | save.c | yes — `aInterleaveImpl` (naudio variant takes no args) |
|
||||
| `aEnvMixer` | env.c:410/414 | yes — `aEnvMixerImpl` |
|
||||
| `aADPCMdec` | load.c:483 | yes — `aADPCMdecImpl` (SSE4.1/NEON/scalar) |
|
||||
|
||||
GE's context model (from the call sites in `src/libultra/audio/`):
|
||||
`aSetBuffer(flags, dmemin, dmemout, count)` sets a working-buffer context;
|
||||
subsequent `aLoadBuffer(dramAddr)` / `aADPCMdec(flags, statePtr)` /
|
||||
`aResample(flags, pitch, statePtr)` operate within it; the tail is
|
||||
`aInterleave(AL_MAIN_L_OUT, AL_MAIN_R_OUT)` → `aSaveBuffer(f->dramout)`.
|
||||
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).
|
||||
|
||||
So this is **not** "copy `mixer.c`": port PD's mixer as the *DSP algorithm
|
||||
reference* (ADPCM decode, linear resample, mix math, env-mixer / pole-filter
|
||||
are the same Rare/Nintendo DSP blocks) and write GE-ABI wrappers that
|
||||
maintain the `aSetBuffer` context and read params from the state structs.
|
||||
## 4. How the PD port does it (ground-truth comparison)
|
||||
|
||||
**GE's full emittable opcode set** (static audit of every call site in
|
||||
`src/libultra/audio/*.c` + `src/libultrare/audio/*.c`): ClearBuffer,
|
||||
LoadADPCM, DMEMMove, SetBuffer, LoadBuffer, SetLoop, ADPCMdec, Resample,
|
||||
Interleave, SaveBuffer, Mix, SetVolume, EnvMixer, PoleFilter, Segment — plus
|
||||
`aPan`, defined in abi.h but apparently uncalled. PD's mixer covers the DSP
|
||||
core for all of them except the trivial bookkeeping ones: SetBuffer /
|
||||
LoadBuffer / SaveBuffer are context tracking + memcpys; Segment is a no-op on
|
||||
PC (`amDmaCallback` already resolves ROM addresses against the mapped image —
|
||||
`osVirtualToPhysical` is identity, `libultra.c:1295`).
|
||||
1. naudio is Rare's own library; its `n_abi.h` has a `#ifndef PLATFORM_N64` branch that
|
||||
redefines every `n_a*` macro as a **direct C call** into `port/src/mixer.c`
|
||||
(`mixer.h` `#undef`s the macros and redirects to `a*Impl`). All DSP therefore runs
|
||||
**inline on the audio thread at task-build time**, inside `amgrFrame()`.
|
||||
2. The `M_AUDTASK` is an empty shell: `pdsched.c:233` receives it with the comment
|
||||
`// send this into the mixer` and an empty body.
|
||||
3. PD **replaced the scheduler** (`pdsched.c`) and drives audio from retrace:
|
||||
`schedAudioFrame()` → `amgrFrame(); audioEndFrame();` (×`diffframe60`). Same thread
|
||||
for build and flush ⇒ no locking in their SDL handoff.
|
||||
4. SDL device layer: queue-based, exactly what our scaffold already copied —
|
||||
`osAiGetLength` = queued **bytes** (`audioGetBytesBuffered()`),
|
||||
`osAiSetNextBuffer` = stash pointer, `audioEndFrame` = `SDL_QueueAudio` with a
|
||||
`queueLimit` backpressure drop. Device opened at 22020 Hz (their TODO admits this
|
||||
"might cause trouble" — it is a silent −9-cent detune vs their 22050 synth rate).
|
||||
5. DMEM: ~3 KB static buffer, **naudio-specific** offsets (main L/R @0x4E0/0x650,
|
||||
aux @0x7C0/0x930) — different from IDO's layout (§6).
|
||||
6. PD-only extras: `aPlayMP3Impl` (minimp3; GE never emits), `aPoleFilterImpl` empty
|
||||
stub (GE *does* emit it).
|
||||
|
||||
## Phases
|
||||
**Why we can't copy it wholesale:** GE compiles the real `src/sched.c` and stock IDO
|
||||
libaudio, whose macros only *write words* into the Acmd list — there is no build-time
|
||||
hook unless we shadow `PR/abi.h`. Something must execute the stream. Two further ABI
|
||||
facts:
|
||||
|
||||
### Phase 0 — Ground truth (research, ~half day, delegable)
|
||||
- **IDO's implicit "current buffer":** e.g. `aADPCMdec(pkt, f, s)` carries only flags +
|
||||
a state pointer; in/out DMEM offsets and sample count come from the preceding
|
||||
`aSetBuffer` (`load.c:482–483`, `resample.c:90–91`). naudio spells everything out per
|
||||
call. Whatever option we pick, the implementation must model current-buffer state.
|
||||
- **Addresses in the stream are RAM pointers** (`K0_TO_PHYS(f->state)`,
|
||||
`osVirtualToPhysical(e->state)`, `f->dramout`) — resolvable on PC *except* for the
|
||||
`K0_TO_PHYS` bug in §7.1.
|
||||
|
||||
1. **Disassemble GE's `aspMain` microcode from the ROM** and document each
|
||||
opcode's exact semantics in findings.md §F (new Dxx): flag-bit meanings
|
||||
(`A_FIRST`, `A_CONTINUE`, …), count units (bytes vs samples), where the
|
||||
interleaved output lands before `aSaveBuffer`, how `aSetLoop` state gets
|
||||
restored, `ENVMIX_STATE[40]` layout. This is the audio analogue of using
|
||||
`rsp/graphics/gmain.s` as GBI ground truth — we don't run the ucode, but
|
||||
it is the spec. (`port/src/ucode.c`'s header already flags this "DECISION
|
||||
NEEDED in Phase 3"; CPU-bypass means the dummy `aspMainTextStart` /
|
||||
`aspMainDataStart` markers stay forever — document that decision there.)
|
||||
2. **Opcode inventory probe** (port-only, zero game-code change): a
|
||||
`GE_AUDIOTRACE` env var in the existing M_AUDTASK branch of
|
||||
`osSpTaskStartGo` that walks `t->t.data_ptr` for `data_size/8` words and
|
||||
histograms `w0 >> 24`. Confirms the static call-site audit and catches
|
||||
anything emitted dynamically.
|
||||
## 5. Architecture options (evaluate all; recommendation marked)
|
||||
|
||||
### Phase 1 — Device plumbing (small, lead work)
|
||||
### Option A — CPU-side Acmd interpreter at task time 【RECOMMENDED】
|
||||
|
||||
- Implement `port/src/audio.c::audioInit`: SDL device at **22050 Hz** (GE's
|
||||
`OUTPUT_RATE = 0x5622`, not PD's 22020), `AUDIO_S16SYS`, stereo. Keep the
|
||||
existing `Audio.BufferSize` / `Audio.QueueLimit` config registrations.
|
||||
- Wire `osAiSetNextBuffer` → `SDL_QueueAudio` guarded by `queueLimit`;
|
||||
`osAiGetLength` → `SDL_GetQueuedAudioSize()`. Faithful mapping: N64's
|
||||
`AI_LEN_REG` ("bytes synthesized but not yet played") ≙ SDL queued-bytes,
|
||||
and the game's self-tuning `frameSamples = g_FrameSize −
|
||||
osAiGetLength()>>2` math (`audi.c:527`) keeps the virtual FIFO at ~736
|
||||
samples (NTSC) with no game-code changes. Queue inline in
|
||||
`osAiSetNextBuffer` (same thread, once per frame) rather than PD's deferred
|
||||
`audioEndFrame`. Overflow safety is already self-limiting: a full queue
|
||||
makes `frameSamples` clamp to `g_MinFrameSize`, and the `queueLimit` check
|
||||
drops rather than crashes.
|
||||
- Add a `GE_AUDIODEBUG` probe logging queue depth + synthesized samples every
|
||||
60 frames; catalog it in `docs/GE-ENV-PROBES.md`.
|
||||
- **Exit:** links 243/243, `/linkcheck` clean; headless `-level_09` 60 s
|
||||
crash-free; debug probe shows the queue filling while intro music "plays"
|
||||
(still silent — acmds don't execute yet).
|
||||
Decode the 64-bit command words from `t->t.data_ptr` inside the existing `M_AUDTASK`
|
||||
branch of `osSpTaskStartGo()` (`libultra.c:1108`), dispatching each op to an
|
||||
implementation in `port/src/mixer.c` operating on a static 4 KB "DMEM".
|
||||
|
||||
### Phase 2 — The mixer (core work, ~1–3 days)
|
||||
- Pros: zero game/header/build changes; treats the stream as data exactly like fast3d
|
||||
does with GBI (project-consistent); natural logging hook (`GE_DAUDIO=1` dump of op +
|
||||
args per frame) for debugging and for validating against the microcode disassembly;
|
||||
execution point matches N64 timing (DSP runs when the RSP would have run it).
|
||||
- Cons: must write the word decoder (~50 lines, trivial — op is bits 24–31 of w0 per
|
||||
`include/PR/abi.h`) and the current-buffer state machine; one scheduler tick of extra
|
||||
latency vs build-time execution (inaudible).
|
||||
|
||||
- Port PD's DSP cores into `port/src/mixer.c`: ADPCM decode (coefficient-book
|
||||
handling via `aLoadADPCM`), linear resampler, stereo mix with `0x8000`
|
||||
scaling, env-mixer + pole-filter (reverb/chorus driven by
|
||||
`CUSTOM_FX_PARAMS_N`, `audi.c:169`), set-volume ramps. Add the GE-ABI
|
||||
context layer: a static `{dmemin, dmemout, count}` updated by
|
||||
`aSetBuffer`, consumed by LoadBuffer / SaveBuffer / ADPCMdec / Resample; a
|
||||
shared software DMEM array across Impls. Replace the current 31-line stub
|
||||
interface (`port/include/mixer.h`) with PD-style `aXxxImpl` declarations.
|
||||
- Edit `include/PR/abi.h` with an `#ifdef PORT` split (N64 path
|
||||
byte-identical): PC includes a port `mixer.h` whose macros match **GE's**
|
||||
arg arities. This is the one non-`port/` edit — same documented
|
||||
ABI/layout-exception class as D78 / D135, logged in §F with its own label.
|
||||
- Unknown / unused opcodes (`aPan`, `aSegment`): log-and-no-op under
|
||||
`GE_AUDIOTRACE`; implement only if Phase 0 shows them live.
|
||||
- All DSP runs on the existing `amMain` thread inside `alAudioFrame` — no new
|
||||
locks, no scheduler changes.
|
||||
- **Exit:** opcode histogram fully covered; headless run clean; queue probe
|
||||
non-zero.
|
||||
### Option B — Shadow `PR/abi.h`, redirect macros to direct C calls (the PD trick)
|
||||
|
||||
### Phase 3 — Verification & tuning (user-gated listen tests)
|
||||
Add `port/shim/PR/abi.h`: `#include "include/PR/abi.h"` then `#undef` the 15 `a*`
|
||||
macros and `#define` them as direct calls into `mixer.c` (PD's `mixer.h` does exactly
|
||||
this for naudio). DSP then runs inline during `alAudioFrame()` on the audio thread; the
|
||||
`M_AUDTASK` branch stays a no-op, exactly like PD.
|
||||
|
||||
- Listen checklist: menu music → briefing → in-level music transitions →
|
||||
gunfire / explosions → **positional SFX** (walk around a source) → weapon
|
||||
one-shots → level changes → **mission-fail fade** (the D152 `s_imLock`
|
||||
scenario — retest killing Trevelyan in Facility with live audio, since the
|
||||
fade hammers the event queue; the D152 self-healing lock mitigation is
|
||||
already in place but must be re-proven under load).
|
||||
- Compare reverb / chorus character against N64 reference footage (that's
|
||||
what `CUSTOM_FX_PARAMS_N` is for).
|
||||
- Regression: `GE_PCDUMP` framediff vs goldens unchanged (audio must not
|
||||
touch rendering) + 60 s `-level_09` soak.
|
||||
- Docs: findings §F entry, porting-notes quirk ("classic-libultra vs
|
||||
naudio-New acmd ABI delta"), ROADMAP B3 status, HANDOFF update.
|
||||
- Pros: battle-tested pattern (shipped in PD); no decoder; slightly less latency.
|
||||
- Cons: shadows a core PR header for every TU that includes it (only libaudio uses these
|
||||
macros on PC, so blast radius is small but nonzero — must verify include-path
|
||||
ordering in `CMakeLists.txt`); stream becomes invisible at runtime (no logging hook —
|
||||
you'd instrument inside each Impl); the shim must exactly mirror IDO arg order, which
|
||||
differs from PD's naudio redirects line-by-line.
|
||||
|
||||
## Risks
|
||||
### Option C — Run the real `aspMain` microcode 【NOT RECOMMENDED】
|
||||
|
||||
1. **Ucode-semantics mismatch** (count units, flag bits) → garbled or
|
||||
wrong-tinted audio, not crashes; mitigated by Phase 0 disassembly +
|
||||
iterative listen checks. Main schedule risk.
|
||||
2. **Timing coupling with B1** (D193 family): low — audio frame size is
|
||||
feedback-driven off `osAiGetLength`, robust to scheduler jitter; re-verify
|
||||
after B1 lands.
|
||||
3. **Performance:** ~736 samples/frame of CPU DSP is trivial on x86-64 (PD
|
||||
shipped exactly this).
|
||||
4. **D152 lock storm under live audio** — covered by the explicit Phase 3
|
||||
retest.
|
||||
Point `aspMainTextStart/DataStart` (`port/src/ucode.c`) at the ROM microcode and execute
|
||||
it with an RSP emulator. Maximum fidelity by construction, but requires a 16-bit-MIPS
|
||||
RSP core + DMEM + interrupt model for one component whose entire job is the 15 ops we
|
||||
can implement directly. Keep the `ucode.c` dummies permanently (their header comment
|
||||
anticipates this decision). The microcode is still *used* — as a **reference to
|
||||
disassemble**, not to run (§8, Phase 0).
|
||||
|
||||
## Delegation shape & effort
|
||||
### Sub-decision A1 — where samples hit the SDL queue 【RECOMMENDED: immediate】
|
||||
|
||||
Per `docs/dev-process.md`:
|
||||
- **Immediate:** `osAiSetNextBuffer()` shim calls into `audio.c` and `SDL_QueueAudio`s
|
||||
right there (on the audio thread), with the existing `queueLimit` backpressure drop.
|
||||
Lock-free (single writer); matches the moment N64 AI DMA starts; `audioEndFrame()`
|
||||
disappears entirely. Safe because `NUMBER_OUTPUT_BUFFERS=3` rotation guarantees the
|
||||
handed-off buffer isn't rewritten before it is queued.
|
||||
- **Deferred (PD-style):** stash in `osAiSetNextBuffer`, flush from a frame-end hook.
|
||||
In GE this crosses threads (audio thread sets; scheduler/gfx path would flush) ⇒ needs
|
||||
a mutex or accepts PD's unlocked pattern, and needs a new `audioEndFrame()` call site
|
||||
that doesn't exist today. Only worth it if immediate queueing shows a problem.
|
||||
|
||||
- Phase 0 disassembly → Claude research subagent (judgment-heavy, read-only +
|
||||
findings write-up).
|
||||
- Phase 1 → lead or mechanical worker (build-verifiable).
|
||||
- Phase 2 DSP-core port → candidate for the local Qwen once the semantics doc
|
||||
exists (mechanical, diff-checkable against PD), with the ABI mapping and
|
||||
review kept by the lead.
|
||||
- Phase 3 listen tests → user.
|
||||
### Sub-decision A2 — device sample rate 【RECOMMENDED: 22050】
|
||||
|
||||
Total ~3–6 focused sessions. Milestone: ROADMAP "audio track (parallel from
|
||||
M-50) → v0.4.0".
|
||||
Request exactly `OUTPUT_RATE` (0x5622 = 22050 Hz), stereo s16, as PD's `audio.c`
|
||||
scaffold already comments. Do **not** copy PD's shipped 22020 (detune). If a platform
|
||||
rejects 22050, let SDL convert (`SDL_OpenAudioDevice` with allowed-conversion) rather
|
||||
than changing the requested rate.
|
||||
|
||||
## Key file map
|
||||
### Sub-decision A3 — timing and thread priorities 【RECOMMENDED: inline + host priorities; no worker thread】
|
||||
|
||||
| File | Role in this plan |
|
||||
Execution stays on the scheduler thread (Option A's execution point). If Phase 2/3
|
||||
milestones show underruns (`audio: ai out of samples` spam — verification step 3) or
|
||||
frame-time spikes attributable to synthesis cost, the **first lever is host thread
|
||||
priorities**: `SetThreadPriority` / `pthread_attr_setschedparam` with the scheduler
|
||||
thread time-critical (it now owns both fast3d and the mixer) and the tick thread
|
||||
normal — a ~10-line fix in `libultra.c`. A dedicated audio worker thread consuming
|
||||
`M_AUDTASK`s is explicitly **not** the next step: it adds a cross-thread handoff for a
|
||||
problem priorities may fully solve, and the kernel's priority semantics are already
|
||||
fake (D24 — see the backlogged bug-class entry in `docs/BACKLOG.md`), so any deeper
|
||||
redesign starts there. Do **not** consider rewriting toward PD's single-threaded
|
||||
inline-`amgrFrame()` model: it works for them only because they adapted game-side lib
|
||||
code (direct `schedSubmitTask` calls, gutted `pdsched.c`); for GE it means editing
|
||||
`src/audi.c` / scheduler submission points — a non-negotiable #2 violation. Rationale
|
||||
recorded in `docs/BACKLOG.md` (Architecture decision).
|
||||
|
||||
## 6. DMEM layout (IDO, from `src/libultra/audio/synthInternals.h:57–71`)
|
||||
|
||||
```
|
||||
AL_MAX_RSP_SAMPLES = 160 (per-chunk; alAudioFrame builds the list in ≤160-sample chunks)
|
||||
offset 0 AL_DECODER_IN / AL_RESAMPLER_OUT / AL_TEMP_0 (320 B)
|
||||
offset 320 AL_DECODER_OUT / AL_TEMP_1 (320 B)
|
||||
offset 640 AL_TEMP_2 (320 B)
|
||||
offset 1088 AL_MAIN_L_OUT (320 B)
|
||||
offset 1408 AL_MAIN_R_OUT (320 B)
|
||||
offset 1728 AL_AUX_L_OUT (320 B)
|
||||
offset 2048 AL_AUX_R_OUT (320 B) → max extent 2368 B
|
||||
```
|
||||
|
||||
Static 4 KB buffer in `mixer.c` (round number, headroom). **Not** PD's naudio offsets.
|
||||
GE's custom FX (`reverb.c`) uses `AL_TEMP_0/1/2` as scratch plus RAM-backed delay lines
|
||||
via `aLoadBuffer`/`aSaveBuffer` to `r->base` — all plain host pointers on PC.
|
||||
|
||||
## 7. Shims and ABI fixes required
|
||||
|
||||
### 7.1 【BUG FOUND】 `K0_TO_PHYS` is unshimmed and corrupts pointers on PC
|
||||
|
||||
`port/shim/PR/R4300.h` redefines `PHYS_TO_K0` (identity) but **not** `K0_TO_PHYS`.
|
||||
Stock: `#define K0_TO_PHYS(x) ((x)&0x1FFFFFFF)` (`include/PR/R4300.h:65`). Game RAM
|
||||
lives at the V1 view `0x70xxxxxx` (`port/src/dram.c`); masking gives `0x10xxxxxx` — an
|
||||
invalid host pointer. Hit sites: `load.c:483` (`aADPCMdec(..., K0_TO_PHYS(f->state))`)
|
||||
and `load.c:479` (`aSetLoop(ptr++, K0_TO_PHYS(f->lstate))`) — the mixer would receive
|
||||
garbage state addresses for every ADPCM voice. Fix: extend the existing shim with
|
||||
`#undef K0_TO_PHYS / #define K0_TO_PHYS(x) ((u32)(x))` (identity; V1 addresses are live
|
||||
host pointers). Inert in the N64 build (no `-DPORT`) — same class as the D78 precedent.
|
||||
Record as a new Dxx finding when implemented (next free label after D127).
|
||||
|
||||
### 7.2 AI shims (`libultra.c:794–810`)
|
||||
|
||||
- `osAiSetFrequency(hz)` → return `hz` (already does; 22050 flows into
|
||||
`alconf->outputRate` and the frame-size math — correct).
|
||||
- `osAiSetNextBuffer(buf, size_bytes)` → queue to SDL per A1.
|
||||
- `osAiGetLength()` → **bytes** currently queued (`SDL_GetQueuedAudioSize`); the game
|
||||
does `>> 2` for frames (`audi.c:527`). PD returns bytes too. This closes the
|
||||
feedback loop that sizes each frame's synthesis.
|
||||
- `osAiSetConvert` — not called by GE; leave stub.
|
||||
|
||||
### 7.3 Misc
|
||||
|
||||
- `port/src/audio.c`: fill in `audioInit()` (SDL_InitSubSystem(AUDIO), open device per
|
||||
A2, unpause); add the queue function per A1; keep `Audio.BufferSize`/`Audio.QueueLimit`
|
||||
config entries; add an `Audio.Enabled` toggle (PD's `g_SndDisabled`) so the level
|
||||
sweep can run silent.
|
||||
- `port/src/mixer.c`: rewrite as the op implementations + (for Option A) the decoder /
|
||||
current-buffer state machine. `mixerInit/Destroy` reset DMEM + op state.
|
||||
- Threading note: mixer runs on the scheduler thread (`sched.c __scMain` →
|
||||
`osSpTaskStartGo` inline); with A1 there is exactly one shared object (the SDL queue,
|
||||
internally locked) — no port-side locks needed.
|
||||
|
||||
## 8. Phase 0 — recon (do first; informs everything)
|
||||
|
||||
1. **Disassemble `bin/aspboot.text.bin`** (+ `.data.bin`; markers in `src/aspboot.s`) —
|
||||
GE's real ASP microcode, 16-bit MIPS RSP. This is the ground truth for:
|
||||
- exact `aSetBuffer` current-buffer semantics (which subsequent ops consume it, and
|
||||
how),
|
||||
- `aADPCMdec` state layout (what the pointer in w1 points at — IDO `ADPCM_STATE` +
|
||||
data addressing),
|
||||
- **`aPoleFilter`** — the one op with no PD reference code; implement from this,
|
||||
- any DMEM convention not derivable from libaudio usage.
|
||||
Methodology mirrors how `rsp/graphics/gmain.s` is used for graphics. A small
|
||||
16-bit-MIPS disassembler script (`tools_pc/`) is sufficient — we decode, not execute.
|
||||
2. **Acmd stream logger** (`GE_DAUDIO=1`, in the Option-A decoder or a temporary probe
|
||||
in the `M_AUDTASK` branch): dump op + decoded args per frame for the first N frames.
|
||||
Confirms the 15-op inventory at runtime, catches surprises (e.g. ops we didn't grep),
|
||||
and gives before/after artifacts for validating the implementation.
|
||||
|
||||
## 9. Open questions (decide before Phase 1)
|
||||
|
||||
- **Q1 — C7/D127 bank-tree guard (`src/snd.c:859`):** root-cause the bogus
|
||||
`ALSound*` resolutions now (likely an `afFixupInst` layout/stride defect in
|
||||
`port/src/romdata.c`, same family as D37's bank-tree re-layout), or keep the guard as
|
||||
a documented stopgap and file a separate finding? Keeping it means some SFX stay
|
||||
silently missing on affected levels (Surface1) even after audio lands.
|
||||
- **Q2 — Option A vs B** (§5): A is recommended (no header shadowing, stream logging,
|
||||
project-consistent with fast3d); B is the PD-proven fallback if current-buffer
|
||||
semantics prove fiddly at task time. Note for the evaluator: this is a **low-stakes,
|
||||
cheap-to-revisit decision** — both options share 100% of the DSP implementation code
|
||||
(§6 layout, all op bodies); only the ~50-line front end (word decoder vs macro
|
||||
redirects) and the execution point differ. If Phase 0's disassembly surfaces anything
|
||||
that makes B look safer, switching costs an afternoon, not a re-architecture.
|
||||
- **Q3 — Pole-filter fidelity bar:** implement `aPoleFilter` strictly from the
|
||||
microcode disassembly (recommended), or accept a devkit-docs approximation and verify
|
||||
by ear? The chorus is audible in most music; a wrong filter coefficient shows as tonal
|
||||
difference, not crash.
|
||||
|
||||
## 10. Phases and milestones
|
||||
|
||||
- **Phase 0 — Recon** (§8): microcode disassembly notes + Acmd log artifacts committed
|
||||
to `docs/` (or `tools_pc/`). Exit: current-buffer semantics + pole filter documented;
|
||||
op inventory confirmed at runtime.
|
||||
- **Phase 1 — Plumbing.** SDL device opens @22050 stereo s16 (A2); A1 queue path live;
|
||||
real AI shims (§7.2); `K0_TO_PHYS` shim fix (§7.1); `Audio.Enabled` toggle. Mixer still
|
||||
a no-op is fine. **Milestone: device drains, `osAiGetLength` feedback loop visible in
|
||||
logs, clean start/stop, silence (not crash).**
|
||||
- **Phase 2 — Core ops.** Implement `aSetBuffer/aLoadBuffer/aClearBuffer/aDMEMMove/
|
||||
aADPCMdec/aResample/aEnvMixer/aMix/aSetVolume/aLoadADPCM/aSetLoop/aInterleave/
|
||||
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/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/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/porting-notes.md`.
|
||||
|
||||
## 11. Verification ritual (audio-specific, on top of the standard one in AGENTS.md)
|
||||
|
||||
1. Boot → title: menu music audible within a couple of seconds; no click at start.
|
||||
2. Enter BUNKER1 (`-level_09`): gunfire/door SFX present and roughly level-correct;
|
||||
level music plays.
|
||||
3. No `audio: ai out of samples` spam from `amHandleDoneMessage` (`audi.c:596`) — that
|
||||
print is the built-in underrun detector.
|
||||
4. Run 2–3 levels incl. one with heavy SFX overlap; watch `SDL_GetQueuedAudioSize`
|
||||
(log it) staying below `queueLimit` in steady state.
|
||||
5. PAL build (`pal-final`) spot-check: 50 Hz retrace drives the same path; frame-size
|
||||
math uses `MAYBE_FRAME_RATE` — confirm no underruns there too.
|
||||
6. `Audio.Enabled=0` → fully silent, zero audio CPU cost, nothing else affected.
|
||||
|
||||
## 12. Risks / unknowns
|
||||
|
||||
| Risk | Mitigation |
|
||||
|---|---|
|
||||
| `src/audi.c` | Game audio manager — compiles unmodified; source of `OUTPUT_RATE`, frame-size math, `CUSTOM_FX_PARAMS_N` |
|
||||
| `include/PR/abi.h` | GE's acmd accessors (16 opcodes, context ABI) — the one non-`port/` edit (`#ifdef PORT` split) |
|
||||
| `src/libultra/audio/*.c`, `src/libultrare/audio/*.c` | CPU synth + naudio New driver — compile unmodified; call sites define the opcode set |
|
||||
| `port/src/audio.c` | SDL device + queue (implement Phase 1) |
|
||||
| `port/src/mixer.c` | Software acmd interpreter + software DMEM (implement Phase 2) |
|
||||
| `port/include/mixer.h` | Stub interface → replace with `aXxxImpl` declarations |
|
||||
| `port/src/libultra.c:848-863` | `osAi*` shims — wire to audio.c (Phase 1) |
|
||||
| `port/src/libultra.c:1237` | M_AUDTASK branch — stays a completing no-op; hosts `GE_AUDIOTRACE` |
|
||||
| `port/src/ucode.c` | Dummy `aspMain*` markers stay forever under CPU-bypass — document decision |
|
||||
| `<repos>\pd_port\port\src\{audio,mixer}.c`, `include\PR\abi.h` | PD ground truth: device glue + DSP cores + the macro-swap pattern |
|
||||
| IDO current-buffer semantics misread → subtle wrong output (no crash) | Phase 0 disassembly is the arbiter; Acmd logger + by-ear checks per phase |
|
||||
| `aPoleFilter` has no PD code | Disassembly (Q3); it's one small IIR — bounded effort |
|
||||
| ADPCM state layout differs from assumption → garbage/cratchy samples | Phase 0 documents the struct from `load.c` usage + microcode; scalar fallback path makes debugging easy |
|
||||
| C7/D127 hides a bank-tree bug that audio makes *audible* (wrong SFX, not just missing) | Q1; keep the guard until root-caused |
|
||||
| SDL queue backpressure drops under load (slow machines) | `queueLimit` drop is graceful (silence gap, no crash); A3: raise scheduler-thread host priority before buffer tuning; PD shipped the same design |
|
||||
| Synthesis cost on the scheduler thread starves gfx frame time / causes underruns | A3: host thread priorities first; a dedicated audio worker thread only if priorities provably insufficient (and revisit D24's fake-priority semantics before designing one) |
|
||||
| Cross-region: PAL sample-count math differs | Verification step 5 |
|
||||
|
||||
## 13. Key references
|
||||
|
||||
| File | What it gives us |
|
||||
|---|---|
|
||||
| `src/audi.c` | audio thread, frame-size feedback loop, task construction (`OUTPUT_RATE` @22) |
|
||||
| `src/sched.c:439,465,485` | real scheduler's M_AUDTASK path → `osSpTaskStartGo` interception point |
|
||||
| `include/PR/abi.h:260–407` | IDO Acmd word formats (the decoder's spec) + A_* opcodes |
|
||||
| `src/libultra/audio/synthInternals.h:57–71` | DMEM layout constants |
|
||||
| `src/libultrare/audio/reverb.c`, `env.c`, `drvrNew.c` | GE custom FX (`alFxNew`), pole filter + chorus, envelope ops |
|
||||
| `bin/aspboot.text.bin` / `.data.bin` (+ `src/aspboot.s`) | **ground-truth ASP microcode to disassemble** |
|
||||
| `pd_port/port/src/mixer.c`, `port/include/mixer.h` | DSP math reference + the Option-B pattern |
|
||||
| `pd_port/port/src/audio.c`, `pdsched.c:233–260`, `libultra.c:150–175` | PD wiring (queue design, empty AUDTASK branch, AI shims) |
|
||||
| `port/src/libultra.c:794–810, 1108` | the three dead seams this plan fills |
|
||||
| `port/src/dram.c`, `port/shim/PR/R4300.h` | address model (V1/V2 views) + the K0_TO_PHYS gap |
|
||||
@@ -1,246 +0,0 @@
|
||||
# Controller & input options wave (port plan)
|
||||
|
||||
Status: **Wave A IMPLEMENTED** (code complete, clean Windows/MSYS2 build; pending
|
||||
user by-ear/by-eye feel-check + Deck tuning of item 5) — the "low-hanging
|
||||
fruit" from the Nightdive *Turok* PC-port options menu that is cheap, well-
|
||||
scoped, and
|
||||
**port-layer only** (zero `src/` / `src/game` edits → rule 2 and the ABI
|
||||
exception do not apply). Sequenced after v0.4.0 ships. This is the *input*
|
||||
wave; the sibling *video/audio/graphics* Tier-A items (borderless window,
|
||||
primary display, brightness, master volume, button glyphs, uncapped-FPS) are a
|
||||
separate follow-on wave — see §Out of scope.
|
||||
|
||||
Reference: `docs/dev/RUMBLE-PLAN.md` (D401) is the closest precedent — a
|
||||
controller option that lives entirely in `port/src/input.c` +
|
||||
`port/src/optionsoverlay.c` + a `configRegister*` key + a `kResetDefaults`
|
||||
entry, with the game side (`src/joy.c`, `src/` stick consumers) untouched.
|
||||
`docs/dev/GEPD-INPUT-PLAN.md` documents the pad poll path this wave extends.
|
||||
|
||||
---
|
||||
|
||||
## Ground truth (current state, verified 2026-09-27)
|
||||
|
||||
**Pad poll = `inputComputePad()`** (`port/src/input.c:1375`), runs on the game
|
||||
thread (`osContStartReadData`). It reads the SDL axes/buttons for pad `idx`,
|
||||
writes out a 16-bit button mask plus `*stick_x` / `*stick_y` (the N64
|
||||
`signed char` stick, clamped to `STICK_MAX`). All the knobs in this wave are
|
||||
applied **inside** `inputComputePad`, before the two `*stick_x`/`*stick_y`
|
||||
writes at `:2063-2064` — i.e. the port re-shapes the *input the game already
|
||||
consumes*, never the game's stick→turn math.
|
||||
|
||||
**Two pitch modes drive the look stick** (selected by `Input.NaturalPitch`,
|
||||
the default-on "SOLITARE" mode, D194/D238):
|
||||
- **Continuous (natural-pitch, the default):** the **right stick** is the
|
||||
continuous look axis — `input.c:1927-1933`:
|
||||
`rxs = scaleAxis(rx);` … `rys = -scaleAxis(ry); if (padLookInvertY)
|
||||
rys = -rys; sx = rxs; sy = rys;`. The left stick becomes digital-step
|
||||
movement. **This is the path the X/Y look-sensitivity + look-smoothing +
|
||||
right-stick deadzone knobs target.**
|
||||
- **Digital (native, natural-pitch off):** the left stick is analog
|
||||
movement/turn and the right stick is digitized into the C-button look
|
||||
(`:1935-1947`, thresholds at `RSTICK_THRESHOLD`). Look here is the faithful
|
||||
N64 *digital* C-button turn — **not sens-scalable** (there is no analog look
|
||||
to scale), so the new look-sens/smoothing knobs are inert in this mode,
|
||||
exactly as `Input.AimMode`/`Input.AimRange` already are per mode.
|
||||
|
||||
**Existing pad knobs** (all `configRegister*` in `input.c`, rows in
|
||||
`optionsoverlay.c` INPUT section): `Input.PadDeadzone` (single,
|
||||
`padDeadzone = STICK_DEADZONE`, `:524`), `Input.PadTriggerPct` (`:525`),
|
||||
`Input.PadLookInvertY` (`:526`), `Input.RumbleScale` (D401). `scaleAxis()`
|
||||
applies the shared deadzone + `STICK_MAX` scale to a raw `int16` axis.
|
||||
|
||||
**Already covered elsewhere** (do not re-plan): mouse sens/invert/smoothing
|
||||
(`Input.MouseSensitivity`/`MouseInvertY`/`MouseSmoothing`), **uncapped FPS**
|
||||
(`docs/dev/UNLOCKED-FPS-PLAN.md` — parked; has a real perf prerequisite, D250),
|
||||
**controller rebinding** (PR #109 / D383 — the hard, open gap, out of scope).
|
||||
|
||||
---
|
||||
|
||||
## Items (all port-layer)
|
||||
|
||||
### 1. `Input.PadLookSensX` — right-stick look **horizontal** sensitivity *(S)*
|
||||
New int key, default **100** (= 1×, current behaviour), range ~**25–200 %**.
|
||||
Applied in the continuous-look branch only: after `rxs = scaleAxis(rx)`,
|
||||
`rxs = clamp(rxs * padLookSensX / 100, ±STICK_MAX)` before `sx = rxs`
|
||||
(`input.c:1928/1932`). Pure gain on the emitted look X. In digital mode: inert.
|
||||
- Row: INPUT, "Look sensitivity (left/right)", slider, after "Rumble strength".
|
||||
- Risk: none (monotonic gain; 100% = byte-identical to today). Deck-testable.
|
||||
|
||||
### 2. `Input.PadLookSensY` — right-stick look **vertical (pitch)** sensitivity *(S)*
|
||||
Same shape, applied to `rys` (`input.c:1930/1933`): `rys = clamp(rys *
|
||||
padLookSensY / 100, ±STICK_MAX)`. Default **100**, range ~**25–200 %**.
|
||||
Kept separate from X (Turok splits them; pitch and yaw feel different on a
|
||||
stick). Independent of the existing `Input.HipfirePitchSpeed` (that one is the
|
||||
hipfire/mouse pitch rate, a different path). In digital mode: inert.
|
||||
- Row: INPUT, "Look sensitivity (up/down)", slider.
|
||||
- Risk: none (same as item 1).
|
||||
|
||||
### 3. `Input.PadSouthpaw` — swap L/R triggers *(S)*
|
||||
New toggle, default **off**. Today (`input.c:1954-1958`): `TRIGGERRIGHT >
|
||||
trigPt → GE_CONT_G`; `TRIGGERLEFT > trigPt → GE_CONT_R`. Under southpaw, swap
|
||||
the two `GE_CONT_G`/`GE_CONT_R` assignments (left trigger becomes the
|
||||
right-shoulder action and vice-versa). Two lines. Lets left-handed /
|
||||
southpaw players put their index finger on the primary fire trigger.
|
||||
- Row: INPUT, "Southpaw", toggle.
|
||||
- Risk: none at default off; trivial to verify by re-aiming.
|
||||
|
||||
### 4. `Input.PadDeadzoneL` / `Input.PadDeadzoneR` — per-stick deadzone *(S–M)*
|
||||
Split the single `Input.PadDeadzone` into a **left (movement)** and a
|
||||
**right (look)** deadzone. Thread the deadzone through `scaleAxis()` (it is
|
||||
currently the global `padDeadzone`) as a per-call argument: left-stick calls
|
||||
use `padDeadzoneL`, right-stick calls use `padDeadzoneR`.
|
||||
- **Back-compat/migration:** on load, if `Input.PadDeadzoneL`/`R` are absent
|
||||
but legacy `Input.PadDeadzone` is set, seed both from it (one-time, like the
|
||||
`AimStyle`→`AimMode` migration at `input.c:449-454`). Keep `Input.PadDeadzone`
|
||||
registered so old inis keep loading; it becomes hidden/superseded (D337/`D333`
|
||||
precedent for a superseded key).
|
||||
- Rows: INPUT, "Deadzone (left stick)" + "Deadzone (right stick)", sliders.
|
||||
- Risk: low — a deadzone is a per-axis floor; splitting it cannot change
|
||||
behaviour when both equal the current single value. Watch the menu-navigation
|
||||
path (`:1918-1922`, left stick in menus) to use the **left** deadzone there.
|
||||
|
||||
### 5. `Input.PadLookSmoothing` — right-stick look low-pass *(M)*
|
||||
New key, default **0 (off)**, range 0–10 (0 = off = today). When >0, low-pass
|
||||
filter the *emitted* right-stick look deflection so the look axis eases rather
|
||||
than tracking the stick 1:1 — the pad analogue of `Input.MouseSmoothing`
|
||||
(which smooths the *relative* mouse delta). Because the continuous stick is
|
||||
**absolute** (not a delta), implement as a per-pad exponential moving average
|
||||
on the emitted `rxs`/`rys` (state kept per `idx`, reset on pad hot-unplug and
|
||||
on aim-mode exit so releasing the stick recentres cleanly). The higher the
|
||||
value, the heavier the easing.
|
||||
- Row: INPUT, "Look smoothing", slider (0 = off).
|
||||
- Risk: **medium** — an EMA on an absolute stick can feel laggy or fail to
|
||||
recentre if the filter isn't driven back to 0 when the stick is centred;
|
||||
needs hands-on tuning on the Deck (the natural test device). Keep default off
|
||||
so it ships inert until tuned.
|
||||
|
||||
### 6. Mouse **vertical** sensitivity row *(trivial — folds in)*
|
||||
`Input.MouseYScale` already exists as a registered key; it just has no F10
|
||||
row. Add one slider row (INPUT) so the asymmetric X/Y mouse sensitivity is
|
||||
user-adjustable without editing the ini. No `input.c` change — row only.
|
||||
|
||||
---
|
||||
|
||||
## Shared plumbing (do once, not per item)
|
||||
- **`configRegisterInt`/`Float`** for each new key in `input.c` next to the
|
||||
existing pad-key registrations (~`:2536-2538`).
|
||||
- **Static state** next to `padDeadzone`/`padLookInvertY` (`:524-526`):
|
||||
`padLookSensX/Y`, `padSouthpaw`, `padDeadzoneL/R`, `padLookSmoothing` + the
|
||||
per-pad EMA state for item 5.
|
||||
- **F10 rows** in `optionsoverlay.c` `rows[]` INPUT section (after
|
||||
"Rumble strength", with the other controller rows) + matching **front options**
|
||||
rows via the shared `optionsRow*` accessors (both UIs read `rows[]` — D401
|
||||
precedent, so one definition covers F10 *and* the front options screen).
|
||||
- **`kResetDefaults`** entries for each new key (neutral: sens 100, smoothing
|
||||
0, southpaw 0, deadzones = `STICK_DEADZONE`) so INPUT "Reset to defaults"
|
||||
covers them (D401 item-3 precedent).
|
||||
|
||||
## Ground rules
|
||||
1. **Port-layer only** — every edit is in `port/src/input.c`,
|
||||
`port/src/optionsoverlay.c`, `port/src/frontoptions.c`, and `CMakeLists.txt`
|
||||
(nothing new to compile — no new files). Zero `src/` / `src/game` edits →
|
||||
rule 2 and the pointer-width ABI exception are not engaged.
|
||||
2. **Off-by-default = today's behaviour.** Every new key defaults to a neutral
|
||||
value that leaves the emitted stick/buttons byte-identical to the current
|
||||
build, so the golden gate (D117/`golden_gate.sh`) is unaffected at defaults
|
||||
and the v0.4.0 playtest baseline is preserved.
|
||||
3. **Per-mode honesty.** The continuous-look knobs (1, 2, 5) apply only in
|
||||
natural-pitch mode; in digital mode they are inert — document this on the
|
||||
rows (mirrors how `Input.AimRange` is hidden in Centred mode, D338), rather
|
||||
than pretending they affect the digital C-button look.
|
||||
4. **Deck is the reference device** for the pad wave (native Steam Controller,
|
||||
the real-world target); Windows + a plugged gamepad as the second check.
|
||||
|
||||
## Verification
|
||||
1. `./build-pc.sh ntsc-final` clean (no new symbols/dupes — the `configRegister*`
|
||||
+ `kResetDefaults` additions are the only new references).
|
||||
2. `/linkcheck` + the golden gate (`tools_pc/golden_gate.sh`) — must pass
|
||||
**unchanged at defaults** (proves the neutral-default invariant).
|
||||
3. Hands-on, on a pad / the Steam Deck: each knob at a non-neutral value
|
||||
changes feel as expected and at default leaves it identical; southpaw
|
||||
re-maps the triggers; per-stick deadzones floor the correct stick; look
|
||||
smoothing eases and **recentres on stick release** (item 5's known risk).
|
||||
4. New finding (D4xx, next free label) recording the wave + per-item
|
||||
Deck-test result, cross-ref'd to D401 (precedent), D337/D338 (per-mode
|
||||
gating pattern), D333 (`AimStyle` migration pattern), PR #109 (out-of-scope
|
||||
rebind).
|
||||
|
||||
## Out of scope for this wave (see the assessment above)
|
||||
- **Sibling Tier-A, other sections (separate follow-on wave):** borderless
|
||||
windowed, primary-display selection, brightness slider, master audio volume,
|
||||
button-prompt glyphs.
|
||||
- **Tier B (medium, contained):** radial fog toggle, HUD opacity, flash-
|
||||
intensity slider (promote `Game.NoHitFlash`), show-HUD master toggle, audio
|
||||
output device, low-pass filter, SMAA/FXAA.
|
||||
- **Uncapped FPS** — `docs/dev/UNLOCKED-FPS-PLAN.md` (parked; D250 perf
|
||||
prerequisite means it is not quick).
|
||||
- **Tier C (rule-2 / big-feature / out-of-scope):** head-bobbing sliders,
|
||||
death-cinematic / switch-on-pickup / walk toggles (all `src/game` flow),
|
||||
bloom / light-scattering / water refraction-reflection / simple shadows,
|
||||
violence / blood colour, multiple graphics APIs, and **full controller
|
||||
rebinding** (PR #109 — the genuinely hard input item).
|
||||
|
||||
## Suggested sequencing (v0.5.0)
|
||||
1. **Wave A (this doc):** items 1–4 + 6 (sens X/Y, southpaw, per-stick
|
||||
deadzone, mouse-Y row) — all low-risk, shippable behind neutral defaults.
|
||||
2. **Item 5 (look smoothing)** lands in the same wave but may ship default-off
|
||||
pending Deck feel-tuning.
|
||||
3. **Wave B:** the sibling video/audio/graphics Tier-A items.
|
||||
4. Revisit Tier C only with explicit sign-off (rule-2 items need the
|
||||
`docs/dev-process.md` procedure).
|
||||
|
||||
---
|
||||
|
||||
## Implementation status (2026-09-28)
|
||||
|
||||
Wave A code is complete and compiles clean (Windows/MSYS2 `ninja` incremental,
|
||||
exit 0, no new warnings — only the pre-existing `ramrom.c` / `gfx_pc.cpp` ones).
|
||||
Every edit is in `port/src/input.c` + `port/src/optionsoverlay.c`;
|
||||
`frontoptions.c` needed **no** change (both UIs read the shared `rows[]` via the
|
||||
`optionsRow*` API). Zero `src/` edits, no new files/symbols — only file-local
|
||||
statics + `configRegisterInt` calls — so the link is unchanged.
|
||||
|
||||
Implemented (all off/neutral-by-default → byte-identical at defaults):
|
||||
- **1/2. `Input.PadLookSensX` / `Input.PadLookSensY`** (25–200 %, dflt 100):
|
||||
right-stick look, applied in the natural-pitch branch of `inputComputePad()`
|
||||
after `scaleAxis()`, re-clamped to `STICK_MAX`. Inert in digital mode (the
|
||||
right stick is digitized there, no analog look to scale).
|
||||
- **3. `Input.PadSouthpaw`** (toggle, dflt 0): swaps the fire (`GE_CONT_G`)
|
||||
and grenade (`GE_CONT_R`) trigger actions *after* the raw edges are captured,
|
||||
so edge-detection stays consistent.
|
||||
- **4. `Input.PadDeadzoneL` / `Input.PadDeadzoneR`** (0–30000, dflt 7000):
|
||||
`scaleAxis()` now takes a per-stick deadzone param; left-stick (movement)
|
||||
calls pass `L`, right-stick (look) calls pass `R`. The legacy
|
||||
`Input.PadDeadzone` key stays registered (no row) so old inis load; a
|
||||
one-time migration in `inputInit()` seeds any unset L/R (C-init sentinel
|
||||
`PAD_DZ_UNSET = -1`) from the legacy value, before any `configSave`. The
|
||||
single "Stick deadzone" row is replaced by "Deadzone (left stick)" /
|
||||
"(right stick)".
|
||||
- **5. `Input.PadLookSmooth`** (0–10, dflt 0 = off, shown "N/10"): per-pad EMA
|
||||
low-pass on the natural-pitch look output (`padSmSX/padSmSY[MAX_PADS]`), reset
|
||||
on hot-unplug (`inputRescanPads`) so a snapped-back stick can't lag from a
|
||||
stale value; otherwise the EMA decays to 0 as the stick recentres. Turok has
|
||||
this as on/off; GE ships a graduated 0-10 knob instead. Ships **default-off**
|
||||
pending Deck feel-tuning.
|
||||
- **6. `Input.MouseYScale`** row (dflt 100): the pre-existing registered key
|
||||
finally gets an INPUT slider row ("Mouse vertical sensitivity") — row only,
|
||||
no `input.c` change.
|
||||
|
||||
**Verbiage + display baseline (Turok PC, 2026-09-28):** row labels now match the
|
||||
Turok options menu verbatim where GE has an analogue — *Mouse horizontal /
|
||||
vertical sensitivity*, *X axis / Y axis look sensitivity*, *Look smoothing*,
|
||||
*Southpaw*, *Vibration* (was "Rumble strength"), *Deadzone (left/right stick)*.
|
||||
The display is baselined to Turok's scales: sensitivity + deadzone knobs read
|
||||
**0-100 with 50 = the default** (the existing `calibratedDefault` "N/100" path),
|
||||
and look smoothing reads **0-10** ("N/10"). (Turok's Mouse/Controller subsection
|
||||
split is not mirrored — GE keeps one flat INPUT section per D356, so the two
|
||||
invert rows retain their "(mouse)"/"(controller)" suffixes to stay unambiguous.)
|
||||
|
||||
`kResetDefaults` covers all six so INPUT "Reset to defaults" is complete;
|
||||
`calibratedDefault()` gives the sensitivity + deadzone rows their 50/100 default
|
||||
midpoint, and `dispMax` renders the smoothing knob as "N/10".
|
||||
|
||||
**Still owed (verification, not code):** user by-ear/by-eye feel-check of the
|
||||
new knobs on a real pad (esp. item 5 smoothing strength + item 1/2 sens range),
|
||||
the Deck cross-check, and confirming the per-stick deadzone migration on an
|
||||
old-ini user (L/R seed from `Input.PadDeadzone`).
|
||||
@@ -1,360 +0,0 @@
|
||||
# CUT-CONTENT-BACKLOG — TCRF "cutting room floor" findings vs decomp/port
|
||||
|
||||
Research branch: `research/cut-content-backlog`. Source of truth: the local TCRF
|
||||
export at `scratchpad/backlog-scrape/restore-cut-content-mod/` (gitignored).
|
||||
|
||||
> **Superseded by the verified cross-reference.** All six article *bodies* were
|
||||
> recovered and every major claim was checked against the decomp source and asset
|
||||
> tables. The full verified findings (with file:line references, corrections to
|
||||
> TCRF, and updated mod tiers) live in `backlog/cut-content/RESEARCH.md`
|
||||
> (gitignored; article markdowns in `backlog/cut-content/articles/`). This doc is
|
||||
> the first pass (image-filename based) — read it for the per-item imagery index,
|
||||
> but trust RESEARCH.md where they disagree. Key corrections: (1) "dest" is the
|
||||
> *shipped* Frigate geometry, not an early version (`bg.c` levelinfotable);
|
||||
> `cryp`/`dish` are shipped Egypt/Temple; (2) Library/Stack share Basement's bg
|
||||
> file — they have geometry; (3) all 29 "unused items" exist in the ROM object
|
||||
> table as pickup+prop pairs except heroine; (4) TCRF's 11 unused cheats, music
|
||||
> track index, MP character table and build dates all verify 1:1 against
|
||||
> `bondconstants.h`/`cheat.c`/`front.c`/`compiletime.c`.
|
||||
>
|
||||
> **Round 2 (decomp-only discoveries — not in TCRF's documentation):**
|
||||
>
|
||||
> 1. **The shipped game still calls the M16 an "AR33"** — pickup text
|
||||
> `"an AR33 assault rifle."` (`LpropobjE.c` PROPOBJ_STR_2A, used at
|
||||
> `propobj.c:10471`), plus unused strings `"US AR33 Assault Rifle"`/
|
||||
> `"US AR33"` (`LgunE.c` GUN_STR_74/75) and `PROP_CHRM16 /* AR33 Assault
|
||||
> Rifle */` comments. TCRF's prerelease page mentions the AR33 name in early
|
||||
> footage but not that it survived into retail text.
|
||||
> 2. **Klobb rename trace verified**: weapon-name strings in `LgunE.c` are
|
||||
> alphabetical except GUN_STR_6B `"Klobb"` (between "Spy File" and "Staff
|
||||
> List") — exactly the last-minute Skorpion→Klobb scar TCRF describes.
|
||||
> 3. **Two cut weapons TCRF's unused-items page doesn't list**: `cartrifle` and
|
||||
> `wristdart` — full pickup+prop resources (`GcartrifleZ`, `GwristdartZ`,
|
||||
> `PchrwristdartZ /* (BETA) */`) but no `ITEM_*` entry and no WeaponStats,
|
||||
> i.e. unusable as weapons. The wrist dart is the hardware behind TCRF's
|
||||
> `AMMO_DARTS` ($0E).
|
||||
> 4. **All four Bonds, fully mapped**: `CUFF_CONNERY/DALTON/MOORE` +
|
||||
> `BOND_BROSNAN/CONNERY/DALTON/MOORE` enums; the actor-switch code
|
||||
> (`bondview2.c:391-460`) survives with every branch flattened to Brosnan;
|
||||
> the save struct keeps the per-dossier actor field. **No Connery/Moore/
|
||||
> Dalton models exist in the retail ROM** (80 `C*Z` chr models enumerated —
|
||||
> none for the former actors); only enum slots + the `SW_CONNERY`/`SW_DINNER`
|
||||
> switches on the watch-arm model remain. TCRF's "four dossiers, four
|
||||
> actors" was pre-release state.
|
||||
> 5. **Cuba is the one orphan level with recoverable content**: `LlenE.c` holds a
|
||||
> cut Natalya dialogue exchange, the "C U B A N J U N G L E" title card, and a
|
||||
> full alternate credits cast. The other 12 unused level text files are empty
|
||||
> stubs.
|
||||
> 6. **Orphan-level geometry IS in the ROM**: `assets/obseg/obseg.h` declares all
|
||||
> nine text-only orphans' bg segments (`bg_sho_all_p_seg[]` …) — a
|
||||
> byte-matching decomp can't fabricate them. What's missing is mission setup
|
||||
> (briefings exist only for the 21 solo levels), not geometry.
|
||||
> 7. **Moneypenny's two risqué Frigate-briefing lines are gone from the ROM**
|
||||
> (final `LdestE.c` keeps only the toned-down "thighs of steel" paragraph);
|
||||
> recoverable only from TCRF's transcription. "Destroyer"→"Frigate" rename is
|
||||
> complete; the Manticore yacht mission has zero ROM trace.
|
||||
> 8. **Music enum ↔ Notes-page GS index fully aligned**: indices 0–48 match
|
||||
> slot-for-slot; four local mismatches past 0x2C (49 `M_CUBA` vs "Ending
|
||||
> theme", 52 `M_MPTHEME3` vs "Surface 2 X", 54 `M_GUITARGLISS` vs "MP Death
|
||||
> (Alt)", 59 `M_SURFACE2X` vs "Surface 1 X") — in-game `DEB_MUSIC` audition
|
||||
> needed to resolve.
|
||||
|
||||
## 1. Findings
|
||||
|
||||
### A. Debug menu & dev tools — fully in the ROM, fully compiled into the PC build
|
||||
|
||||
TCRF main page images: `GEDebugMenu`, `GECheatOptions`, `GoldenEye-displayspeed`,
|
||||
`GoldenEye-moveview`, `GoldenEye-LineMode`, `GoldenEye-testingmanpos`,
|
||||
`GoldenEye-gunwatchpos`.
|
||||
|
||||
All of these exist in decompiled code:
|
||||
|
||||
- **Debug menu** — `src/game/debugmenu_handler.c` (1202 lines). Opened by holding
|
||||
**U + D C-buttons** (`debug_menu_processor`, ~line 451; wired into `boss.c:551`
|
||||
and the main game loop). Full option list at lines 116–185: `move view`,
|
||||
`stan view`, `bond view`, `level` / `region` / `scale` (level switching),
|
||||
`play title`, `bond die`, `select anim`, `gun pos`, `flash colour`,
|
||||
`hit colour`, `music`, `sfx`, `invincible`, `visible`, `collisions`,
|
||||
`all guns`, `max ammo`, `display speed`, `background`, `props`,
|
||||
`stan hit` / `stan region` / `stan problems`, `print man pos`,
|
||||
`port close` / `port inf` / `port approx`, `pr room loads`,
|
||||
`show mem use` / `show mem bars`, `grab rgb` / `grab jpeg` / `grab task`
|
||||
(screenshots), `rnd walk`, **`record ramrom` / `replay ramrom` /
|
||||
`save ramrom` / `load ramrom`** (input recording/replay!), `auto y aim`,
|
||||
`auto x aim`, `agent` / `all` / `fast` (unlock flags), `objectives`,
|
||||
`marg top/bot/left/right/reset`, `screen size`, `screen pos`,
|
||||
`show patrols`, `intro` / `intro edit` / `intro pos`, `world pos`,
|
||||
`gun key pos`, `vis cvg`.
|
||||
- **Free camera** — `src/game/debug_camera.c` (the "move view" option).
|
||||
- **Load-all-models tool** — `src/game/deb_loadallmodels.c` (used by
|
||||
`initguards.c`; dumps every model — how the TCRF screenshots of unused
|
||||
models were likely made).
|
||||
- **Line mode** — `CHEAT_LINEMODE` in `src/game/cheat.c:685` (wireframe toggle,
|
||||
cheat-menu unlockable; `front.c:1017`).
|
||||
- Screenshot options (`grab rgb`/`grab jpeg`) call `indyGrabRgb32bit()` /
|
||||
`indyGrabJpg32bit()` — N64 VI grabs; on PC these are the natural hook for the
|
||||
port's existing screenshot QoL (see `feat/qol-mute-screenshot` lineage).
|
||||
|
||||
**Status: Tier 0.** Everything compiles into the PC build (`CMakeLists.txt`
|
||||
`GLOB_RECURSE src/game/*.c`). The debug menu should already work in the PC port
|
||||
if U+D C-buttons can be produced by `port/src/input.c`. *Verify in-game* (open
|
||||
menu, toggle `display speed`, use free cam). Mod value: document it; consider a
|
||||
config/keybind that synthesizes the U+D c-button hold, and mapping `grab rgb`
|
||||
to the port's screenshot path.
|
||||
|
||||
### B. Cut weapons — fully functional in the ROM, grantable via existing cheat codes
|
||||
|
||||
TCRF images (main + prerelease pages): `GoldenEye-Taser[PR]`, `GoldenEye-KF7[PR]`
|
||||
(+`KF7Watch`), `GoldenEye-GrenadeLauncher[PR]`, `GoldenEye-RocketLauncher[PR]`
|
||||
(+`RocketLauncherWatch`), `GoldenEye-RifleLauncher` (+`RifleLauncherCheat`),
|
||||
`GE64_Shotgun1`, watch variants `MagnumWatchPR`, `PP7WatchPR`, `KlobbWatchPR`,
|
||||
`TaserWatch`.
|
||||
|
||||
Evidence in code/assets:
|
||||
|
||||
- Model + stat files exist for every cut gun under `assets/obseg/gun/`:
|
||||
`taser/`, `rocketlaunch/`, `grenadelaunch/`, `fnp90/`, `spectre/`,
|
||||
`autoshot/`, `ak47/`, `m16/`, `skorpion/` … each with
|
||||
`gunWeaponStat.inc.c` + `ModelFileHeader.inc.c`.
|
||||
- `assets/obseg/gun/gunWeaponStats.inc.c`: complete `WeaponStats` for
|
||||
`taser_stats` (line ~365), `rocketlaunch_stats` (~305),
|
||||
`grenadelaunch_stats` (~295), plus `watchlaser_stats`, `tank_stats`,
|
||||
`bombcase_stats`, etc. These are data-driven — the weapon engine
|
||||
(`src/game/gun.c`, `gunfire.c`) will run them.
|
||||
- First-person view cases exist: `src/game/bondview_r.c:38` handles
|
||||
`ITEM_GRENADELAUNCH`, `:41` `ITEM_ROCKETLAUNCH`.
|
||||
- **Cheat codes grant them**: `src/game/cheat.c` — `CHEAT_2X_ROCKET_LAUNCHER`
|
||||
(line 711), `CHEAT_2X_GRENADE_LAUNCHER` (712), `CHEAT_2X_RCP90` (713),
|
||||
plus unlock sequences (`CHEAT_UNLOCK_2XGL/2XRL/2XFNP0`, lines ~695–734) and
|
||||
the grant logic at lines 1277–1310: `bondinvAddInvItem(ITEM_ROCKETLAUNCH)` /
|
||||
`give_cur_player_ammo(AMMO_ROCKETS, …)` etc. Note the `#if defined(BUGFIX_R1)`
|
||||
guards — check which region macros the PC build defines (non-negotiable #3).
|
||||
- Taser SFX exist: `GUN_TASER_SFX` / `GUN_TASER_LOOP_SFX` (`src/bondconstants.h:2271`).
|
||||
- `ITEM_TASER` is in the item enum and used as an inventory bound in
|
||||
`bondinv.c:854` (JP-text-trigger branch) — the taser was closest to shipping.
|
||||
|
||||
**Status: Tier 0/1.** Rocket launcher, grenade launcher and FN P90 are
|
||||
playable today via the game's own cheat codes (single-player). Taser/Spectre/
|
||||
AutoShot have no cheat path but everything needed is in the binary; a
|
||||
port-layer toggle calling the same `bondinvAddInvItem` +
|
||||
`give_cur_player_ammo` pair the cheats use would be a clean mod (no game-logic
|
||||
change — it replicates an existing code path). Watch-mounted variants
|
||||
(KF7/Magnum/PP7/Taser watch) are *not* in the item enum — likely cut before
|
||||
data landed; **unverified** whether any model exists beyond what TCRF shows.
|
||||
|
||||
### C. "Unused items" page (intact imagery) — objects with full logic, never placed
|
||||
|
||||
The 20 intact images correspond exactly to ROM object entries
|
||||
(`assets/obseg/file_resource_table.inc.c`, 781 entries):
|
||||
|
||||
| Image | Resource ID | Notes |
|
||||
|---|---|---|
|
||||
| `GE007-AudioTape` | `AUDIOTAPE` / `CHRAUDIOTAPE` (lines 125, 265) | pickup + prop variants |
|
||||
| `GE007-BombCase` | `BOMBCASE` (129) | has full `bombcase_stats` (throwable explosive) |
|
||||
| `GE007-Controller` | N64 controller model (`joypad/` dir in gun table) | desk prop |
|
||||
| `GE007-WatchMagnetRepel` | `ITEM_WATCHMAGNETREPEL` (`bondconstants.h:4190`) | watch function, use-logic present |
|
||||
| `GEMicroCamera` | `MICROCAMERA` (176) + `AMMO_MICRO_CAMERA` | |
|
||||
| `GoldenEye-Weapon_Case` | `ITEM_WEAPONCASE` (`gunfire.c:3066` use-case) | |
|
||||
| `GE007-PolarizedGlasses` | `POLARIZEDGLASSES` (185) + `ITEM_POLARIZEDGLASSES` | `darkglasses/` also present |
|
||||
| `GoldenEye-Plans` | `PLANS` (183) | `blueprints/` variant too |
|
||||
| `GoldenEye-Watch_Identifier` | `WATCHIDENTIFIER` (208) + `ITEM_WATCHIDENTIFIER` (`gunfire.c:3076`) | 4th watch function with use-anim, never spawned |
|
||||
| `GoldenEye-Safecracker` | `SAFECRACKERCASE` (190) | |
|
||||
| `GoldenEye-Watch_Communicator` / `Watch_Geiger_Counter` | `ITEM_WATCHCOMMUNICATOR` / `ITEM_WATCHGEIGERCOUNTER` (`bondconstants.h:4188-89`) | shown for contrast — these *are* used (Aztec) |
|
||||
| `GEGasKeyring` | `GASKEYRING` (157) + `ITEM_GASKEYRING` | |
|
||||
|
||||
All have `GUN_ANIM_STATE_USE_ITEM` handling in `gunfire.c:3060-3084` — i.e. the
|
||||
game can *use* them; level data just never places them.
|
||||
|
||||
**Status: Tier 1/2.** Granting any of these to Bond is a port-layer item-spawn
|
||||
(same mechanism as §B). Actually *placing* them in levels requires ROM/level-data
|
||||
editing (a level editor or hand-patched object tables) — Tier 2, out of scope
|
||||
for this repo but viable as a data mod.
|
||||
|
||||
### D. Cut multiplayer maps — 8 commented-out menu entries with dev notes
|
||||
|
||||
`src/game/front.c:556-573` `multi_stage_setups[]`: the shipped MP list is
|
||||
Random/Temple/Complex/Caves/Library/Basement/Stack (default) + Facility/Bunker2/
|
||||
Archives/Caverns/Egypt (progression-unlocked). Immediately below, **commented
|
||||
out with in-source dev annotations**:
|
||||
|
||||
```c
|
||||
//{... LEVELID_CITADEL ...}, //Citadel (old format setup)
|
||||
//{... LEVELID_FRIGATE ...}, //dest (has xbla setup)
|
||||
//{... LEVELID_STATUE ...}, //stat (works)
|
||||
//{... LEVELID_CRADLE ...}, //crad (works)
|
||||
//{... LEVELID_AZTEC ...}, //azt (needs setup)
|
||||
//{... LEVELID_RUNWAY ...}, //runway (xbla setup)
|
||||
//{... LEVELID_DAM ...}, //dam (xbla setup)
|
||||
//{... LEVELID_DEPOT ...}, //depot (xbla setup)
|
||||
```
|
||||
|
||||
TCRF imagery matches: `GECitadel*` (5 screenshots, main page),
|
||||
`GEStatueMulti`, `GECradleMulti`, the whole Depot image set (`DepotCrateStack`,
|
||||
`DepotRockets`, `DepotSafeTable`, `DepotSiloPipe[Ed]`, `DepotTrailers`,
|
||||
`DepotPhoto`), and `GoldenEye-FrigateMap`.
|
||||
|
||||
The port already boots every one of these standalone (`-level_XX`; see
|
||||
`docs/dev/LEVEL-STATUS.md` — Statue `-level_22`, Train `-level_25`, etc. all
|
||||
PASS). So the geometry, AI setup and briefings
|
||||
(`assets/obseg/brief/UbriefdepoZ.c`, `UbrieftraZ.c`, …) are intact; only the MP
|
||||
menu entry was removed.
|
||||
|
||||
**Status: Tier 3 (mod fork).** Re-enabling = editing a game data table in
|
||||
`src/game/front.c` — that's a game-logic change, disallowed in this repo
|
||||
(non-negotiable #2). Viable as a separate mod branch/fork ("8 more MP maps"),
|
||||
or as a documented cheat-menu route (the debug menu's `level`/`region` options
|
||||
may already jump to any level ID — verify).
|
||||
|
||||
### E. Per-level unused props & geometry ("Unused stuff by level" page)
|
||||
|
||||
~150 intact screenshots, grouped by level. Cross-referenced against the object
|
||||
table and level IDs:
|
||||
|
||||
- **Streets** (campaign stage 12; `LEVELID_STREETS`): an entire prop kit was
|
||||
never placed — `StreetsCars`, `StreetsLamp[Base/Big]`, `StreetsSign[2/3]`,
|
||||
`StreetsTrafficLight`, `StreetsShovel`, `StreetsCrossing`, plus two cut set
|
||||
pieces `GE64-StreetsArea1/2` and Ourumov's car driving/crashing
|
||||
(`StreetsOuruGo`, `StreetsOuruCrash`, `GEOurumovsCar`). Suggests a cut
|
||||
street-chase scene.
|
||||
- **Silo**: `SiloVentShaft`, `SiloLadder`, `SiloLaunchDoors`,
|
||||
`SiloWalkway2Door/Guard`, `SiloMissingHandrail` (a bug/oddity screenshot),
|
||||
`GoldenEye_UnusedStartingPoint`, and prerelease-only `PRSiloElevator`,
|
||||
`PRSiloLamps`, `SiloCutWalkway`, **`ThirdSiloEditor`** (editor view of a
|
||||
third silo that didn't ship).
|
||||
- **Facility**: PR-vs-final wall sets (`FacilityWalls[Final]`,
|
||||
`PRFacilityWalls`, `ArchivesWalls`), plus cut rooms: `FacilityBathroom`,
|
||||
`FacilityConveyor`, `FacilityGasTanks`, `FacilityLabTanks`,
|
||||
`FacilityWalkway`, `FacilityArmedScientistPR`, `FacilitySatBoxTex`,
|
||||
`GoldenEye007-FacilityPhoto`.
|
||||
- **Archives**: `ArchivesOverview`, `ArchivesLamp[Scan]`, `ArchivesPhoto`,
|
||||
`PRArchivesNoWalls` (wall-less build view).
|
||||
- **Bunker**: `BunkerExtraMons[Side]` (extra monster/guard spawns),
|
||||
`BunkerPIP`, PR `BunkerLamp`.
|
||||
- **Caverns**: `CavernsPursueObj` (a cut "pursue" objective marker), PR
|
||||
`CavernsWalkway`.
|
||||
- **Dam**: `DamIsland`, `DamPlatform`, `DamDockDoors`, `DamEndDuct`,
|
||||
`DamTruckDoor`; region-diff shots `DamCompUS/EU`.
|
||||
- **Frigate**: `FrigateHarpoonScan` (scan of a harpoon model),
|
||||
`PRTrainTargetGlass`, `Pdest_harpoonZ[2]`, `Ptorpedo_rack`,
|
||||
`FrigateMoneypennyPR` (a Moneypenny model in the Frigate PR build!),
|
||||
region-diff `FrigateIntroUS/JP`.
|
||||
- **Aztec/Temple**: `AztecConsolePads1-4`, `GE-AztScreen2781/2789`,
|
||||
`AztExtraMainframe`, `AztDronePadsLabel`, `unused-Aztec-Path` (a whole
|
||||
unplaced path), `EgyptianRed/WhiteClouds` (two skybox variants),
|
||||
`TempleSkySolo/Multi`, PR: `AztBlueSuitPR` (cut enemy suit),
|
||||
`AztHeavyFogPR`, `AztTableScreensPR`.
|
||||
- **Surface**: `SurfaceBookCrate`, `SurfaceObsPad`, `SurfaceTower`,
|
||||
`SurfaceJerryCan`, `SurfaceHutTable`, `SurfacePropHut`, PR
|
||||
`SurfaceOverviewPR`, `SurfaceVent[PR]`, `SurfaceDish[PR]`.
|
||||
- **Train** (campaign stage 14): `GE007-TrainSecretRoom` (a hidden room),
|
||||
`TrainTargetGlass`.
|
||||
- **Misc props/vehicles**: `GEMotorbike`, `GERedTruck`, `GESovietBoat`,
|
||||
`GEHelicopterPilot`, `GEBiker`, `GoldenEye-moonfemale` (`moonfemale/` chr
|
||||
model exists), `OurumovKey/Briefcase`, `GEDeskMat`, `GoldenEye-Crypt`
|
||||
(confirmed orphaned map — see note below),
|
||||
`GE64-BridgeRooms`, `GEJunglePicture`, `Runwaypicture`,
|
||||
`Controlicon`, `Cactus`.
|
||||
- **Orphaned level geometry** — `assets/obseg/bg/` holds per-level dirs for
|
||||
`ame arch arec ark azt cat cave crad cryp dam depo dest dish len oat ref run
|
||||
sev sevb sevx silo stat tra`. Notable: `cryp` (Crypt) has full geometry +
|
||||
briefing (`UbriefcrypZ.c`) but **no `LEVELID_CRYPT` entry** in
|
||||
`bondconstants.h` — fully orphaned. `len` (Lenin statue area, cf.
|
||||
`StatueLeninPR`) and `dish`/`cat`/`oat`/`arec`/`ame`/`ark`/`ref` are further
|
||||
level dirs beyond the shipped set. No `citadel` dir — its geometry was
|
||||
either removed or shared (TCRF's 5 Citadel screenshots show a finished map,
|
||||
so it existed in some build).
|
||||
|
||||
**Status: Tier 2.** All models are in the ROM's object table (spot-checked:
|
||||
`PchrtesttubeZ`, `Pdest_harpoonZ`, `Pdoor_roller1-4Z`, `PhatfurryZ`,
|
||||
`Pletter_tray1Z`, `Pmissile_rackZ`, `PsilencerZ`, `Psteel_door2[ b]Z`,
|
||||
`Ptorpedo_rack`). Placing them needs level-data editing (object spawn tables) —
|
||||
a data-mod / level-editor project, not a port change. The PR-vs-final texture
|
||||
pairs (`*PR__1vt.jpg`) are interesting for a "pre-release textures" cosmetic mod
|
||||
via the `tools_pc` asset pipeline — but the PR ROM itself isn't available here,
|
||||
so source assets would have to come from TCRF's screenshots (low-res) or a
|
||||
donated PR dump.
|
||||
|
||||
### F. Unused characters & faces
|
||||
|
||||
TCRF main-page imagery: `UnusedFace1/2/3` (each with F/S1/S2 views),
|
||||
`GE64-FemHead1-4`, `KenFaceUnused/Used`, `Rosika[Compare]`, `SciCompare`,
|
||||
`FemaleSci`, `GEBonds`, `Terrorists`, `TerroristBiker`, `StPetersburgGuard`,
|
||||
Bond-suit sets named after actors: `Connery*`, `Dalton*`, `Moore*` (each with
|
||||
HeadFront/Back/Left/Right + Jacket pieces), `GEArmor[our]`.
|
||||
|
||||
Cross-reference:
|
||||
|
||||
- The MP character table (`front.c:601+` `mp_chr_setup[]`) is huge — ~40 named
|
||||
entries beyond the 8 selectable, including `ROSIKA`, `KARL`, `MARTIN`,
|
||||
`MARK`, `DAVE`, `DUNCAN`, … `TERRORIST`, `BIKER`, `STPETERSBURGGUARD`,
|
||||
`HELICOPTERPILOT` — Brosnan-body + swapped-head combos. The "unused faces"
|
||||
are almost certainly extra head models in this pool that no body/entry uses.
|
||||
- Head model dirs exist under `assets/obseg/chr/`: `headken` (the Ken face),
|
||||
`headbrosnan*` (4 costume variants), `moonfemale`, plus ~30 named heads.
|
||||
The actor-named suits (Connery/Dalton/Moore) do **not** appear as chr dirs —
|
||||
**resolved in round 2**: the ROM holds exactly 80 `C*Z` character models and
|
||||
none is a former-Bond actor; only the enum slots + the flattened actor-switch
|
||||
code (`bondview2.c:391-460`) survive (see banner, item 4).
|
||||
|
||||
**Status: Tier 2/3.** Cosmetic mod (extra MP faces) = asset-level work if the
|
||||
models are in the ROM; verify by dumping `assets/obseg/chr/*/Model.c` headers.
|
||||
|
||||
### G. Prerelease / dev-build evidence
|
||||
|
||||
Prerelease page (body blocked; filenames + embeds only):
|
||||
|
||||
- PR-vs-final comparison pairs: Taser, KF7, GrenadeLauncher, RocketLauncher
|
||||
(+watch versions), SurfaceVent/Dish, `StatueLeninPR`, Facility walls/rooms
|
||||
(§E), ArchivesWalls, CavernsWalkway, SiloCutWalkway, ThirdSiloEditor,
|
||||
PRSiloElevator, BunkerLamp, OutletConsoles. Pattern: the PR build had more
|
||||
weapons (watch-mounted guns!) and different Facility/Silo/Aztec geometry.
|
||||
- **Dev-build videos** (YouTube embeds captured in the export):
|
||||
- "Goldeneye 007 - Shoshinkai/Space World 1996"
|
||||
- "Nintendo of America E3 1996 Take Away Video (VHS Rip)"
|
||||
- "Goldeneye Beta WalMart Video"
|
||||
- "Goldeneye 007 Promotional Trailer 1996"
|
||||
- "N64 vous n'en reviendrez pas (VHS RIP 1997)"
|
||||
- "64History - N64 B-Roll (1995)"
|
||||
- "Escape (Banjo-Kazooie, GoldenEye 007)" — engine sibling footage.
|
||||
Useful for dating PR features; not actionable in-repo.
|
||||
|
||||
### H. Misc oddities (main page, unverified)
|
||||
|
||||
- `GoldenEye007MickeyMouse` (by-level page, 2 KB image) — no "mickey" object in
|
||||
the resource table; likely a texture/model easter egg spotted in a level.
|
||||
- `Unused_bond_walk.gif` — an unused Bond walk animation (check
|
||||
`assets/animationtable_entries.c` for orphaned anim IDs).
|
||||
- `GoldenEye007-MovieLogoUnused` / `-MovieNoLogoUnused` — alternate intro logo
|
||||
frames (title sequence data; `src/game/brief*`? verify).
|
||||
- `GE_UnusedAlarm`, `GoldenEye-BloodImpact` / `BoxBlood` / PR `OpaqueBlood` —
|
||||
blood-effect variants (cf. `src/game/blood_animation.c`).
|
||||
- `GoldenEye007-BarrelUnused` / `-SmallBarrelUnused` — barrel props never placed.
|
||||
- `GoldenEye-OddDay`, `Sight[Alt]`, `MountainTex`, `background`, `Pbin1Z`,
|
||||
`PchrtesttubeZ[Explode]`, `PitonGunBungee`, `Psev_door[3_wind]Z` — props in
|
||||
the table, placement unverified.
|
||||
- Bugs & Notes articles: **nothing recoverable from this export** — re-scrape.
|
||||
|
||||
## 2. Feasibility summary
|
||||
|
||||
| Tier | Item | Effort | Where it lands |
|
||||
|---|---|---|---|
|
||||
| **0 — already works** | Debug menu (U+D C-hold), free cam, line mode, ramrom record/replay, cheat-grantable rocket/grenade/P90 | verify in-game; document keybinds | docs + maybe `input.c` keybind for U+D c-buttons |
|
||||
| **1 — port-layer hook** | Grant taser/spectre/autoshot + unused items (watch identifier, bomb case, …) via config toggle replicating the cheat grant path (`bondinvAddInvItem` + `give_cur_player_ammo`) | small; follow F10-toggles pattern (`feat/f10-game-toggles`) | `port/src/` only |
|
||||
| **2 — data/asset mod** | Place unused props per level; extra MP faces; PR texture swap | needs level-editor or object-table patching; separate project | data mod, not this repo's code |
|
||||
| **3 — game-logic change (mod fork)** | Re-enable 8 cut MP maps (`front.c` table), campaign reordering | one-line-per-map table edits + setup fixes ("xbla setup" = unknown editor flag) | separate mod branch/fork only |
|
||||
|
||||
## 3. Next steps
|
||||
|
||||
1. **In-game verification pass** (PC build): open debug menu; confirm free cam,
|
||||
`display speed`, level switch; enter the 2X rocket/grenade/P90 cheat codes;
|
||||
screenshot via `grab rgb`. Record results here + findings.md if anything new
|
||||
breaks (Dxx).
|
||||
2. Confirm which region macros the PC build defines vs the `BUGFIX_R1` guards in
|
||||
the weapon-grant cheats (non-negotiable #3) — the grants may be dead code in
|
||||
our build.
|
||||
3. Dump `assets/obseg/chr/` model headers to identify the UnusedFace1-3 /
|
||||
FemHead1-4 models concretely.
|
||||
4. ~~Re-scrape TCRF article bodies~~ — **done**; all six bodies recovered into
|
||||
`backlog/cut-content/articles/` and cross-referenced (see RESEARCH.md §0).
|
||||
5. If Tier-1 item granting is wanted: spec it as an F10 toggle set, port-layer
|
||||
only, each entry citing the cheat-code precedent in `cheat.c`.
|
||||
@@ -1,127 +0,0 @@
|
||||
# D176(a) Sky — consolidated research notes (read-only)
|
||||
|
||||
Companion to the (since retired) `GRAPHICS-BACKLOG.md` D176(a) and findings.md M-37/M-47.
|
||||
Purpose: make the next implementation session mechanical. No code changed by
|
||||
this doc.
|
||||
|
||||
## State of play
|
||||
|
||||
- **Root cause (M-37):** `skyRenderTri`/`skyRenderFull` emit geometry only as
|
||||
`G_RDPHALF_*` immediates; `gfx_pc.cpp:2901-2903` deliberately no-ops them
|
||||
(same in the PD port) → solid black sky on every cloud-sky level. Clean
|
||||
headless repro: `-level_22` (Statue), frame ~360.
|
||||
- **M-47 draft (Path B)** — `scratchpad/sky.patch`: `#ifdef PORT` hooks in
|
||||
both functions re-emit the sky as ordinary screen-space GBI via
|
||||
`skyPortRenderPoly` (ortho pixel projection + `gSPVertex`/triangles).
|
||||
Geometry reaches GL and textures (top-half luma 0.1 → ~174 on `-level_22`).
|
||||
Two open defects (below). Draft, not shippable.
|
||||
- **Path A** (decode RDPHALF in fast3d) is the backlog's original suggestion;
|
||||
the wire format is now fully decoded (next section), so it is a viable
|
||||
fallback if Path B's units can't be pinned.
|
||||
|
||||
## RDPHALF wire format (decoded from `src/game/sky.c` + `include/PR/gbi.h`)
|
||||
|
||||
- Opcodes (`gbi.h:102-164,216-223`): `G_RDPHALF_1 = 0xe1`,
|
||||
`G_RDPHALF_2 = 0xf1` (final word of a stream), `G_RDPHALF_CONT`
|
||||
(= `G_IMMFIRST-13`). Tri ops: `G_TRI_FILL 0xc8`, `G_TRI_FILL_ZBUFF 0xc9`,
|
||||
`G_TRI_SHADE_TXTR 0xce`, `G_TRI_SHADE_TXTR_ZBUFF 0xcf`.
|
||||
- **All payload values are S15.1 fixed point** — converter is
|
||||
`sub_GAME_7F094298` (`sky.c:218`): `clamp(x, ±32767.9) × 65536`, u32.
|
||||
- **Packing:** each `(G_RDPHALF_1, G_RDPHALF_CONT)` pair carries four
|
||||
halfwords = **two S15.1 values**: `valueA = (w1.hi16, w2.lo16)`,
|
||||
`valueB = (w1.lo16, w2.hi16)`. Multi-value groups are streamed in two
|
||||
passes — all high halves first, then all low halves (see the explicit
|
||||
`(x & 0xffff0000) | (y & 0xffff0000) >> 16` packing at `sky.c:1854-1870`).
|
||||
- **`skyRenderTri` header** (`sky.c:1725-1728`): first word =
|
||||
`(op << 24) | (winding/sign flag bit23) | lo16(first value)`; the stream
|
||||
then carries screen-space positions (`unk28×0.25` = pixel X, `unk2c` = Y×4
|
||||
family) plus per-vertex attributes. The RSP ucode reassembles and DMAs a
|
||||
real edge-walked RDP tri command (per the M-46 comment in sky.patch).
|
||||
- **Vertex attribute vectors are 8 components** (`sky.c:1759-1776`):
|
||||
`(r, g, b, a, S·w′, T·w′, 32767·w′, z)` with perspective weight
|
||||
`w′ = unk34 × min(unk0c)/2` — i.e. color + perspective-corrected S/T +
|
||||
depth. `SkyRelated38` fields: `unk28 = screenX×4`, `unk2c = screenY×4`,
|
||||
`unk30 = z (0..0x7fff)`, `unk34 = 1/w`, `unk20/unk24 = S/T`.
|
||||
- GE emits RDPHALF **nowhere except sky.c** — the command surface is closed
|
||||
and small.
|
||||
|
||||
## Defect 1 — texcoord shear (clouds smeared into streaks)
|
||||
|
||||
`skyPortRenderPoly` uses `tc = unk20 × 32.0f` — a magic number.
|
||||
|
||||
### M-82 update — the scale is right, the magnitude is the bug
|
||||
|
||||
- **Cloud texture is `s_skywaterimages[0]` = `IMAGE_CLOUDS_GRAYSCALE`,
|
||||
`0x40 × 0x40` (64×64), `G_IM_FMT_IA` / `G_IM_SIZ_8b`, `G_TX_WRAP`/`G_TX_WRAP`**
|
||||
(`assets/oddtextures.c:646`). Not a fraction-indexed texture — it *tiles*.
|
||||
- **GE's own screen-space textured-quad idiom is `tc = width << 5`**
|
||||
(`src/game/glass2.c:678,689` — `sImageTableEntry.width << 5` /
|
||||
`.height << 5`). `<< 5` = ×32 = the S10.5 texel fixed-point fast3d expects,
|
||||
and it divides `tc` by `tex_width` before GL_REPEAT. So the patch's `× 32.0f`
|
||||
**is the correct scale** — provided `unk20`/`unk24` are already in *texel*
|
||||
units (they are: the cloud-tiling math in `sub_GAME_7F097388` /
|
||||
`CloudRepeat(skyheight)=10000` produces texel coords in the thousands, and
|
||||
M-47 measured `unk20` reaching ±20 000 near the horizon).
|
||||
- **The bug: `(s16)(20000 * 32)` overflows.** `Vtx.tc` is `s16` (±32767); a
|
||||
horizon vertex needs `tc` ≈ 640 000. It wraps/saturates → the streak.
|
||||
- **Fix (no fast3d change, no grid needed for the near/mid sky):** phase-fold
|
||||
every vertex of *one primitive* by the **same** multiple of the tile period
|
||||
(64 texels), so inter-vertex deltas are preserved but magnitudes stay in
|
||||
s16 range:
|
||||
```c
|
||||
s32 foldS = (s32)floorf(vmin_unk20 / 64.0f) * 64; // vmin over this prim's verts
|
||||
s32 foldT = (s32)floorf(vmin_unk24 / 64.0f) * 64;
|
||||
tc[0] = (s16)((v[i]->unk20 - foldS) * 32.0f);
|
||||
tc[1] = (s16)((v[i]->unk24 - foldT) * 32.0f);
|
||||
```
|
||||
This is exact wherever the S/T *span across a single triangle* is
|
||||
< ~1000 texels (s16 range ÷ 32). It holds for the corner-quad tessellation
|
||||
the current patch emits everywhere except triangles that straddle the
|
||||
horizon line, where the span diverges — those still need Defect-2's
|
||||
finer/adaptive tessellation. Land the fold first (fixes most of the frame),
|
||||
keep the grid as the horizon-band follow-up.
|
||||
|
||||
## Defect 2 — vertical coverage (band fills only top ~25%)
|
||||
|
||||
Leading hypothesis, from reading the N64 path: **the quad is not a plain
|
||||
corner quad.** The N64 stream's four vertices are *perspective-interpolated*
|
||||
points computed in the big interpolation block (`sky.c:1797-1813`, the
|
||||
`sp210/sp290/sp2b0/sp230` vectors), not the raw TL/TR/BL/BR corners that
|
||||
Path B's hook passes through. If those interpolated y-values differ from the
|
||||
corner ys (they will, wherever w varies across the quad), the re-emitted
|
||||
quad lands short.
|
||||
|
||||
Next steps (one focused session):
|
||||
1. Per-call probe on `skyPortRenderPoly`: log nverts + each vertex's
|
||||
(x, y) + the tri op-header word; A/B against N64 reference footage of
|
||||
`-level_22`/`-level_36`.
|
||||
2. If confirmed, either (a) replicate the interpolation block under PORT and
|
||||
emit the 4 computed vertices, or (b) drop to Path A with the decoder spec
|
||||
above (the ucode's own reassembly is then irrelevant — we decode the same
|
||||
values directly).
|
||||
|
||||
## Recommendation
|
||||
|
||||
Finish **Path B** first — geometry already works; both defects are unit/
|
||||
vertex-set questions answerable in one render-iterate session. Keep Path A
|
||||
as fallback; it is now fully specified by this doc if B stalls.
|
||||
|
||||
## Verification ask (M-82 — for the next session with a display)
|
||||
|
||||
The Defect-1 phase-fold is committed to PR #18 but **unverified visually**
|
||||
(this session had no display). One check settles it:
|
||||
|
||||
1. `./build-pc/ge007.x86_64.exe -level_22` (Statue, night). At the intro the
|
||||
camera faces the horizon within a few seconds.
|
||||
2. Look at the upper half of the frame:
|
||||
- **Black** → Path B still not emitting (regression from the rebase —
|
||||
check `skyPortRenderPoly` is reached; `GE_D176=1`).
|
||||
- **Clouds, but smeared into horizontal streaks** → the fold didn't take;
|
||||
re-check `unk20`/`unk24` really are texels (probe their range).
|
||||
- **Recognisable tiled cloud texture, correct near/mid, ragged or missing
|
||||
right at the horizon line** → fold works; that ragged band is Defect 2
|
||||
→ adaptive tessellation is the next task.
|
||||
- **Looks right** → close Defect 1, mark PR #18 ready pending a
|
||||
`-level_36`/`-29`/Dam-exterior spot check.
|
||||
3. A `GE_PCDUMP="330-390:30"` capture of that run attached to PR #18 is enough
|
||||
for a remote reviewer to make the same call.
|
||||
@@ -1,369 +0,0 @@
|
||||
# D356 — Settings regroup: functional sections, save-file scoping, per-section reset
|
||||
|
||||
Status: **LANDED (D356, 2026-09-27, port-only)** — signed off and implemented
|
||||
this session; verification in `docs/dev/findings.md` D356 (GE_WSPROBE_RESET
|
||||
front + in-stage probes, regressions, build). Supersedes the "BOND FILE"
|
||||
section introduced in D353. Baselines: Turok PC port options (Nightdive, list
|
||||
provided by the maintainer), the PD port's `port/src/optionsmenu.c`
|
||||
(`<repos>\pd_port`), and plan §6 of
|
||||
`docs/dev/OPTIONS-MENU-PLAN.md` (the original "align with Turok" note).
|
||||
|
||||
## 1. Goal
|
||||
|
||||
The D353 "BOND FILE" section is the one section named after *where a value is
|
||||
saved* — an implementation detail — while every other section is functional
|
||||
(DISPLAY, GRAPHICS, MOUSE/AIM, CONTROLLER, GAME). Its contents are also a
|
||||
mishmash (aim/look toggles + HUD toggles + audio), and it exposes N64-watch
|
||||
rows that **collide with or duplicate** the PC-native rows the port added
|
||||
(watch look-invert vs `Input.MouseInvertY`/`Input.PadLookInvertY`; watch aim
|
||||
control vs `Input.AimMode`). The baseline ports (Turok, PD) never name a
|
||||
section after the save and never re-expose legacy overrides.
|
||||
|
||||
D356 rewrites the row table so that:
|
||||
|
||||
1. Every section is functional, using the baseline names:
|
||||
**INPUT, GAMEPLAY, GRAPHICS, AUDIO, VIDEO**.
|
||||
2. Save-file scope is communicated the PD way — *in the section title*
|
||||
("GAMEPLAY (File 1)"), generalized from the D353 BOND-FILE annotation —
|
||||
plus a single **Save file: File N [change]** row at the top of the
|
||||
options screen (PD "Player N" pattern; D354's default resolution applies).
|
||||
"Bond file" appears nowhere in the UI.
|
||||
3. Redundant/colliding watch rows are removed from the **menu surface only**
|
||||
(the established D181/D216/D304 pattern): keys, in-game watch behavior,
|
||||
and ini hand-editing all remain untouched. No game-code change (rule 2).
|
||||
4. A **Reset to defaults** action row per section (Turok's per-category
|
||||
reset; plan §6 Gate E, made concrete) with a **confirmation contract**:
|
||||
edge-triggered activation (no auto-repeat), two-step arm→confirm, never
|
||||
repeatable (WATCH-SETTINGS-PLAN §6 gate: "separate confirmation and
|
||||
persistence policy before wiring a button" — §5.4 below is that policy).
|
||||
5. A **context-aware file accessor** (front-end target vs active stage
|
||||
target) with the "none" chooser position retired, so "Save file: File N"
|
||||
is always a true statement (§5.5).
|
||||
6. Section resets iterate the section's **declared** rows (including
|
||||
conditionally hidden ones, §5.6) and reset verification exercises the UI
|
||||
action itself, headlessly (§5.8).
|
||||
|
||||
## 2. Non-goals
|
||||
|
||||
- No `src/game` changes; the N64 watch menu keeps all its options and its
|
||||
N64 wording (ground truth).
|
||||
- No key rebinding (Phase 4) — but the section layout leaves room for a
|
||||
"KEY BINDINGS" page exactly where Turok/PD have one.
|
||||
- Master volume stays M3-backlog; the AUDIO section is shaped for it
|
||||
(Turok's Master/Sound/Music trio).
|
||||
- N64 build untouched.
|
||||
|
||||
## 3. Exposure filter (menu surface only)
|
||||
|
||||
| Row (key) | Verdict | Reason |
|
||||
|---|---|---|
|
||||
| `Bond.Look` ("Look up/down (watch; stacks)") | **Hide** | Duplicates `Input.MouseInvertY` + `Input.PadLookInvertY`; stacking both produces the double-negation trap. |
|
||||
| `Bond.AimControl` ("Aim control") | **Hide** | Collides with `Input.AimMode` (N64 vs PC aim model) — two rows fighting over the same domain. |
|
||||
| `Bond.AutoAim` | Keep | Unique gameplay assist; orthogonal to aim style/range. |
|
||||
| `Bond.LookAhead` | Keep | Unique; no PC counterpart, no conflict. |
|
||||
| `Bond.Sight`, `Bond.Ammo` | Keep | Unique HUD-visibility options; compose with `Game.HudScale`. |
|
||||
| `Bond.Music`, `Bond.FX` | Keep | Core of D350–D355. |
|
||||
|
||||
Hidden rows are commented out in `rows[]` with a D356 note (D181/D216 style:
|
||||
config keys + watch path stay live; ini power users can still set them).
|
||||
|
||||
## 4. New layout (5 pages + save-file row)
|
||||
|
||||
`MAX_PAGES=8`, `MAX_PROWS=15` — all pages below the caps (D353 overflow log
|
||||
still guards).
|
||||
|
||||
Top row (drawn by `frontoptions.c` above the paged content, not paged):
|
||||
**`Save file: File 1 [change]`** — cycles files via `watchSettingsChooseFile`
|
||||
(D354 default resolution: explicit pick > viewed folder > first valid;
|
||||
auto-init fallback). Hidden in F10 (existing `optionsRowIsBondChooser`
|
||||
mechanism, generalized). F10 targets the active file, as today.
|
||||
|
||||
| Page | Rows (key · kind) | Title annotation |
|
||||
|---|---|---|
|
||||
| **INPUT** | `Input.MouseSensitivity` · `Input.MouseInvertY` · `Input.AimMode` · `Input.AimRange` · `Input.PadLookInvertY` · `Input.PadDeadzone` · `Input.PadTriggerPct` · *Reset to defaults* | |
|
||||
| **GAMEPLAY** (File N) | `Bond.AutoAim` · `Bond.LookAhead` · `Bond.Sight` · `Bond.Ammo` *(save, dim "(save)" tag)* · `Game.SkipIntro` · `Game.NoHitFlash` · `Game.AllUnlocked` · `Game.HudScale` · *Reset to defaults* · `__QuitToDesktop` | (File 1) |
|
||||
| **GRAPHICS** | 11 rows unchanged from D353 · *Reset to defaults* | |
|
||||
| **AUDIO** (File N) | `Bond.Music` · `Bond.FX` *(save; title annotation only — homogeneous section, PD-style)* · (master volume, M3) · *Reset to defaults* | (File 1) |
|
||||
| **VIDEO** | `Video.Fullscreen` · `__Resolution` · `Video.VSync` · `Video.FpsCap` · `Video.DisplayFPS` · *Reset to defaults* | |
|
||||
|
||||
Moves from D353 layout: mouse/aim + controller rows merge into **INPUT**
|
||||
(the two old headers `__HdrMouse`/`__HdrPad` are dropped); DISPLAY → **VIDEO**
|
||||
(rename, `Video.DisplayFPS` moves from GAME); GAME section is renamed
|
||||
**GAMEPLAY** and absorbs the surviving watch toggles; the `__HdrBond` header
|
||||
and `__BondFile` chooser row are retired (replaced by the top save-file row);
|
||||
`Bond.Look`/`Bond.AimControl` rows are commented out (§3).
|
||||
|
||||
Scope annotation: the D353 "(File N)"/"(no file)" title annotation — today
|
||||
special-cased to the BOND FILE page via `optionsRowIsBondChooser()` — is
|
||||
generalized: **any page containing per-file rows annotates**. Sections with
|
||||
per-file rows: GAMEPLAY, AUDIO.
|
||||
|
||||
F10: same table (F10 already shows all rows, chooser hidden). The
|
||||
(Gameplay/Audio) annotation in-stage shows the active file number (the
|
||||
context-aware accessor, §5.8 — *not* the raw chooser value). "Reset to
|
||||
defaults" rows stay visible in F10 (O2) under the same confirmation
|
||||
contract (§5.4).
|
||||
|
||||
**(save) tag (O1):** the four mixed-scope GAMEPLAY save rows carry a subtle
|
||||
dim "(save)" suffix — the only place a section mixes scopes. AUDIO is
|
||||
homogeneously per-file, so its title annotation is sufficient (no tags).
|
||||
Implementation: one `int saveScoped` field on `struct Row` + dim-ink suffix in
|
||||
the row-draw path (frontoptions.c already draws the dim annotation).
|
||||
|
||||
## 5. Code changes
|
||||
|
||||
All in `port/` (D350-family); files: `port/src/optionsoverlay.c`,
|
||||
`port/src/frontoptions.c`, `port/include/optionsoverlay.h`,
|
||||
`port/src/watchsettings.c` (only if reset needs a new enqueue helper —
|
||||
expected not to, see below).
|
||||
|
||||
### 5.1 Row table (`optionsoverlay.c`)
|
||||
|
||||
- Rename headers: `__HdrDisplay`→VIDEO (last page), add `__HdrInput`
|
||||
(INPUT), `__HdrGameplay` (GAMEPLAY); delete `__HdrBond`, `__HdrMouse`,
|
||||
`__HdrPad`, and rename `__HdrGame`→GAMEPLAY. New section order in the
|
||||
table: INPUT, GAMEPLAY, GRAPHICS, AUDIO, VIDEO (Turok's order: Input,
|
||||
Gameplay, Graphics, Audio, Video).
|
||||
- Move the 7 mouse/aim + 3 controller rows under INPUT; move the 4 surviving
|
||||
`Bond.*` toggles + music/FX into GAMEPLAY/AUDIO; comment out
|
||||
`Bond.Look`/`Bond.AimControl` (§3).
|
||||
- New `ROW_ACTION` rows, one per section: `.key="__Reset<Section>"`,
|
||||
`.label="Reset to defaults"`. All five use one shared handler
|
||||
(`rowResetSection(section)`).
|
||||
|
||||
### 5.2 Save-file top row (`frontoptions.c`)
|
||||
|
||||
- New non-paged line above the page content: "Save file: File N" + a
|
||||
"change" control (left/right or Enter cycles, reusing
|
||||
`watchSettingsChooseFile(dir)`; D354 lazy default fills N). Dim ink, same
|
||||
visual language as the D353 annotation. `selected_folder_num` unchanged —
|
||||
the row never touches the game's active file (plan Gate B invariant).
|
||||
**Display value reads through the context-aware accessor (§5.5), never
|
||||
the raw chooser value.**
|
||||
- **Unavailable state (maintainer consistency fix):** the D354 auto-init
|
||||
fallback creates the blank file on a *later game tick*, so at an
|
||||
empty/corrupt-save startup the accessor can return -1 for a few ticks.
|
||||
The row then shows the unavailable state ("(no file)"), never a file
|
||||
number the write path cannot use. Once creation succeeds the same tick
|
||||
the row shows "File 1".
|
||||
- F10 context: row suppressed (extend the D353 accessor to cover it).
|
||||
|
||||
### 5.3 Reset to defaults (Gate E)
|
||||
|
||||
- **Per-file rows** (GAMEPLAY/AUDIO save rows): defaults = **BLANKSAVEDATA**
|
||||
(`src/game/file2.c` — the game's own factory save; per WATCH-SETTINGS-PLAN
|
||||
§6, watch rows reset to BLANKSAVEDATA/`DEFAULT_OPTIONS` for the selected
|
||||
file, *not* from a global ini snapshot; new saves start Music/FX 0xFF,
|
||||
auto-aim/sight/look-ahead/ammo enabled — read the fields from
|
||||
`BLANKSAVEDATA` at reset time, no hardcoded table). Reset re-applies live
|
||||
via the existing `watchsettings` apply path **and** persists field-scoped
|
||||
to the selected file (`watchSettingsPersistField`), exactly like a normal
|
||||
commit. In-stage: enqueued through the D352 command queue (N field commits
|
||||
with `commit=1`) — no new threading; game thread applies + persists.
|
||||
- **Ini rows** (INPUT/GRAPHICS/VIDEO/GAMEPLAY-ini): `config.c` has no
|
||||
central default table (first-write "defaults" = current values,
|
||||
config.c:280), so the section handler uses an explicit
|
||||
`static const struct { const char *key; double def; }` reset table in
|
||||
`optionsoverlay.c`, mirroring the port's C initializers (each entry
|
||||
commented with its source variable; **verified against the initializer at
|
||||
implementation time** — e.g. `Game.AllUnlocked` = **0**,
|
||||
`port/src/video.c:116,348`; the D257 comment in optionsoverlay.c that
|
||||
says "default ON" is stale and is corrected as part of D356). Front-end:
|
||||
write live + `configSave()` (existing). In-stage ini resets: applied
|
||||
**immediately** (O3 — consistent with how every other ini row already
|
||||
behaves when changed in F10; video.c applies most display changes on the
|
||||
fly).
|
||||
- **Resolution is excluded from the VIDEO reset (maintainer item 2).**
|
||||
`__Resolution` is an action backed by `s_resSel` +
|
||||
`videoRequestWindowSize()` — not a registered config row, so the numeric
|
||||
default table cannot reset it, and a "default window size" is a
|
||||
display-capability choice, not a tunable. The D356 findings entry
|
||||
documents the exception explicitly: VIDEO reset covers Fullscreen, VSync,
|
||||
FpsCap, DisplayFPS — Resolution keeps the player's current choice.
|
||||
- **Scope-aware by construction:** a section's reset touches each row
|
||||
**by its own scope** (file rows → that file's BLANKSAVEDATA values;
|
||||
ini rows → ini defaults). No cross-scope surprise (Gate E: "do not make
|
||||
a global reset wipe per-file watch settings" — a reset of the selected
|
||||
file never touches other files; §5.8 verifies this).
|
||||
- **Activation contract (§5.4) applies to every section's reset row.**
|
||||
|
||||
### 5.4 Reset activation contract (maintainer item 1)
|
||||
|
||||
A reset is a *destructive bulk write* — GAMEPLAY's in particular changes
|
||||
both ini values **and** the selected save in one action. WATCH-SETTINGS-PLAN
|
||||
§6 gates confirmation + persistence policy; this is that policy:
|
||||
|
||||
- **Edge-triggered:** reset actions fire only on a *fresh* event. The
|
||||
frontoptions left/right key repeat (held keys re-issue the action) is
|
||||
suppressed for `ROW_ACTION` reset rows — the arming/confirm events must
|
||||
carry a no-repeat flag (SDL keydown with `repeat==0` / a fresh click),
|
||||
so a held key or held mouse button can never arm *and* confirm, nor
|
||||
re-trigger.
|
||||
- **Two-step confirm:** first activation **arms** the row (label swaps to
|
||||
"Confirm reset…", armed highlight); a second activation within **3 s**
|
||||
commits. Timeout, or navigating to another row, disarms (label
|
||||
restores). Arm state is per-row, per-open; opening/closing F10 or the
|
||||
options screen clears all armed resets.
|
||||
- **One commit per confirmed activation.** No auto-repeat, no re-arming on
|
||||
the same event. F10 and the options screen share the identical contract
|
||||
(O2: rows stay visible in F10; confirmation makes accidental in-game
|
||||
resets unlikely).
|
||||
- The confirmed action then dispatches the section handler (§5.3) exactly
|
||||
once.
|
||||
|
||||
### 5.5 Context-aware file accessor; retire "none" (maintainer item 3)
|
||||
|
||||
`watchSettingsFolder()` returns the raw `chosen`, which may be -1 before
|
||||
`frontFolder()` lazily resolves, and in-stage `chosen` can differ from the
|
||||
active file (`snapFolder`). The generalized title/top-row accessor must be
|
||||
**context-aware**, not a repurposed chooser-value read:
|
||||
|
||||
- New `watchSettingsActiveFolder(void)` (name tentative):
|
||||
- **Front-end contexts** (options screen, F10-on-file-select, title):
|
||||
the *resolved front target* — explicit `chosen` if set, else
|
||||
`selected_folder_num` if valid, else first valid folder (i.e. the
|
||||
`frontFolder()` resolution; the accessor is pure-read and does not
|
||||
mutate `chosen` — lazy mutation stays in `frontFolder()`/the
|
||||
auto-init fallback).
|
||||
- **In-stage (F10):** the *active stage target* — `selected_folder_num`
|
||||
(the file the stage loaded, `snapFolder` at apply time).
|
||||
- The D353 title annotation and the §5.2 top row both read through this
|
||||
accessor, in both surfaces.
|
||||
- **The chooser's "none" position is retired.** `watchSettingsChooseFile`
|
||||
cycles FOLDER1..MAX only (dropping the -1 stop). Rationale: D354's
|
||||
default resolution + boot-time BLANKSAVEDATA slots + the auto-init
|
||||
fallback guarantee a valid target, so "none" was only reachable by
|
||||
deliberate cycling and serves no remaining purpose — and "Save file:
|
||||
File N" (the §5.2 invariant) is not true when `chosen == -1`.
|
||||
`watchSettingsFolder()`'s "-1 = none" contract is updated/superseded by
|
||||
the new accessor.
|
||||
|
||||
### 5.6 Reset iterates declared rows (maintainer item 4)
|
||||
|
||||
The section handler iterates the section's **declared row range in
|
||||
`rows[]`** (from its header to the next header), *not* `s_visIdx`/the
|
||||
currently visible list — conditionally hidden rows (GRAPHICS draw/LOD
|
||||
sliders while their auto-FOV toggles are on) must be reset too, or they
|
||||
silently survive a purported section reset. Hidden-row defaults come from
|
||||
the same default table; their *live* values are written directly (the
|
||||
`ptr`/`rowSet` path), visibility is irrelevant. The `(save)` tag, hidden-row
|
||||
resets, and the reset table are all keyed off `rows[]` indices, never the
|
||||
visible list.
|
||||
|
||||
### 5.7 F10
|
||||
|
||||
Unchanged machinery: shared table, save-file row suppressed (the D353
|
||||
accessor pattern extended to the §5.2 top row). Annotation shows the active
|
||||
file via §5.5. "Reset to defaults" rows stay **visible** in F10 (O2) under
|
||||
the §5.4 confirmation contract.
|
||||
|
||||
### 5.8 Reset verification probe (maintainer item 5)
|
||||
|
||||
New env-gated probe **`GE_WSPROBE_RESET=<tick>`** (GE_D314/GE_WSPROBE
|
||||
pattern): at tick N of a *front-end* boot it drives the **UI action path**
|
||||
through the same handler the confirm step calls (arm → confirm → dispatch,
|
||||
no bypass). **Every section being verified is itself dispatched:** the
|
||||
probe dispatches all five section resets (INPUT, GAMEPLAY, GRAPHICS, AUDIO,
|
||||
VIDEO) via arm → confirm, and after *each* dispatch verifies that
|
||||
section's rows:
|
||||
|
||||
- **INPUT / GRAPHICS / VIDEO (ini rows):** values == the default table.
|
||||
GRAPHICS specifically covers the **hidden** draw/LOD sliders (hidden
|
||||
while their auto-FOV toggles are on) — a section reset must land them
|
||||
even while they are not visible; VIDEO's `__Resolution` is the documented
|
||||
exclusion (unchanged, logged).
|
||||
- **GAMEPLAY (mixed scope):** the four save rows == BLANKSAVEDATA
|
||||
(auto-aim/sight/look-ahead/ammo) *and* the ini rows (SkipIntro/
|
||||
NoHitFlash/AllUnlocked/HudScale) == the ini default table — the two
|
||||
scopes land independently in one action.
|
||||
- **AUDIO (save rows):** Music/FX == BLANKSAVEDATA (0xFF raw = 32767,
|
||||
the `VOLUME_MAX` endpoint), not the probe's pre-dirtied 4096.
|
||||
- **Scope isolation (after all dispatches):** a *second* folder's
|
||||
`save_data` is byte-identical to its pre-reset snapshot (a reset of the
|
||||
selected file never touches other files).
|
||||
|
||||
Front-end prep (tick N) first dirties live values (Music/FX → 4096,
|
||||
VSync/HudScale → non-defaults) and snapshots the second folder, so the
|
||||
verifications at tick N+1 prove the resets actually moved values back.
|
||||
|
||||
In-stage dispatch is covered by the same env var on a `-level_XX` run:
|
||||
arm → confirm at tick N (file rows enqueue through the D352 queue — the
|
||||
probe logs the enqueued command count), drain at N+1, then the same
|
||||
per-section value verification against the post-apply state.
|
||||
|
||||
### 5.9 No changes
|
||||
|
||||
- `buildPages` in `frontoptions.c` (generic; 5 pages < MAX_PAGES),
|
||||
`watchSettingsGameTick`/queue (D352/D354/D355 logic untouched), the D355
|
||||
track-1 resync, `BLANKSAVEDATA` itself.
|
||||
|
||||
## 6. Threading / persistence gates (unchanged, restated)
|
||||
|
||||
- D350: no GE writes from the F10 input/render thread except the sanctioned
|
||||
front-end commit path; in-stage edits queue to the game thread.
|
||||
- D352: queue holds across transitions; per-field coalescing.
|
||||
- D354: front contexts (including F10-on-file-select) route through the
|
||||
chooser path; `frontFolder()` default resolution; auto-init fallback.
|
||||
- D355: Music commit + level-entry resync of the BGM (track 1) player.
|
||||
- D356 adds no new write path: reset = N existing commits (file rows) +
|
||||
existing ini writes (ini rows).
|
||||
|
||||
## 7. Docs
|
||||
|
||||
- `docs/dev/findings.md` D356 entry + §F index row; regenerate
|
||||
`findings-index.csv`.
|
||||
- `docs/dev/WATCH-SETTINGS-PLAN.md`: status + D353/D354 "Bond File" section
|
||||
references updated; Gate E marked landed (subset: per-section rows).
|
||||
- `docs/dev/OPTIONS-MENU-PLAN.md` (tracked) + local notes: §6 "align with
|
||||
Turok" annotated as landed via D356 (M3 master volume + key bindings
|
||||
remain).
|
||||
|
||||
## 8. Verification
|
||||
|
||||
1. Clean NTSC build (`./build-pc.sh` / `cmake --build build-pc`).
|
||||
2. Headless smoke: boots to front end; log page count (5, under MAX_PAGES),
|
||||
no `buildPages` overflow warning, save-file row resolves to File 1 by
|
||||
default (D354), annotation on GAMEPLAY/AUDIO pages, no "none" reachable
|
||||
(chooser cycles files only, §5.5).
|
||||
3. `GE_WSPROBE` / `GE_WSPROBE_FRONT` regression: identical pass as D355
|
||||
(queue/commit paths untouched).
|
||||
4. **`GE_WSPROBE_RESET` (§5.8) exercises the UI action, not just direct
|
||||
commits:** per-section arm → confirm → dispatch of all five sections,
|
||||
with each section's rows verified right after its own dispatch
|
||||
(ini == table, save == BLANKSAVEDATA, hidden GRAPHICS rows included,
|
||||
VIDEO resolution untouched), then the second-folder byte-identity
|
||||
check. In-stage dispatch: `GE_WSPROBE`-style queue check on a
|
||||
`-level_XX` run (enqueued command count + post-apply state).
|
||||
5. **Confirmation contract check (§5.4):** a held key/click arms but never
|
||||
confirms (edge-triggered); second activation within 3 s commits exactly
|
||||
once; timeout/navigate disarms. Headlessly assertable via the probe
|
||||
(arm-then-timeout leaves values untouched).
|
||||
6. Manual visual pass (owed, same list as D353): arrows through 5 pages,
|
||||
save-file cycling updates the annotation live, `(save)` tag on the four
|
||||
GAMEPLAY save rows, F10 in-stage, reset in stage vs front (including the
|
||||
armed/confirm visual state), rapid slider drag.
|
||||
|
||||
## 9. Open questions — RESOLVED (maintainer sign-off, 2026-09-27)
|
||||
|
||||
- **O1:** **yes** — subtle dim "(save)" tag on the four mixed-scope
|
||||
GAMEPLAY rows; AUDIO's title annotation alone is sufficient (homogeneous
|
||||
section).
|
||||
- **O2:** **visible in F10**, provided resets require the §5.4 confirmation
|
||||
step (which they do).
|
||||
- **O3:** **immediate** in-stage ini resets, consistent with existing ini
|
||||
edits in F10.
|
||||
- **O4:** `__QuitToDesktop` stays in GAMEPLAY, **literally last — after the
|
||||
Reset row** (section order: rows…, *Reset to defaults*, *Quit to desktop*;
|
||||
the §4 page table is corrected accordingly).
|
||||
|
||||
Layout itself is signed off by the maintainer (2026-09-27): five-page
|
||||
regroup + menu-only exposure filter, with the §5.4–§5.8 contracts as
|
||||
specified above.
|
||||
|
||||
## 10. Estimate
|
||||
|
||||
One commit. ~5 files (`optionsoverlay.c`, `frontoptions.c`,
|
||||
`watchsettings.c`, `port/include/optionsoverlay.h`,
|
||||
`port/include/watchsettings.h`), net +400/−140 (table reshuffle + reset
|
||||
contract + context-aware accessor + `GE_WSPROBE_RESET` are the new code).
|
||||
Findings/csv/plan updates ride along.
|
||||
@@ -1,194 +0,0 @@
|
||||
# D357 — Settings thresholds & values: align display units/defaults with the PD port + Turok PC
|
||||
|
||||
Status: **IMPLEMENTED 2026-09-30 (findings D443) for mouse sens ×, deadzone whole-%, volume step, FOV degrees, MSAA 16×, Restart game; texture filter unchanged by decision; frame-rate cap deferred (§0 presets + custom = separate project). Approved with changes; see §0.** Proposal dated 2026-09-27. Baselines: the PD port
|
||||
checkout (`<repos>\pd_port`, `port/src/optionsmenu.c` +
|
||||
`input.c`/`video.c` config registrations), the Turok PC (Nightdive) list from
|
||||
the maintainer's notes (`docs/dev/notes/OPTIONS-MENU-PLAN.md` §6 — *taken from
|
||||
user notes, not checked independently*), and the N64 watch (ground truth for
|
||||
the `Bond.*` rows only). Triggered by maintainer report: the current values
|
||||
are "a bit confusing".
|
||||
|
||||
Companion change landed with this doc's review: the terminology settled on
|
||||
**"Profile"** as the end-user term for a per-player game file -- the
|
||||
front top row reads **Profile**, the per-file row tag reads
|
||||
**"(per profile)"** and the F10 unavailable value reads **"Select a
|
||||
profile"** (§4).
|
||||
|
||||
## 0. Sign-off (2026-09-30) — these override §3 where they differ
|
||||
|
||||
- **FOV:** display in **degrees** (the PC standard). Storage stays unchanged.
|
||||
- **Frame rate cap:** presets **30 / 60 / 90 / 120 / 144 / 240 / Uncapped**,
|
||||
**plus** an arbitrary custom value. This keeps the ini's free range and
|
||||
adds a custom entry in the menu, instead of the plan's presets-only grid.
|
||||
- **MSAA:** add **16×**.
|
||||
- **Mouse invert:** stays a separate On/Off toggle.
|
||||
- **Texture filter: do NOT rename.** The §3 premise ("3-point *is*
|
||||
trilinear") is wrong. 3-point is the N64's own 3-sample bilinear
|
||||
approximation, and "Linear" and "Bilinear" name the same thing. Keep
|
||||
"N64 3-point" and check the current enum strings before touching them.
|
||||
- **Refresh §3's "Now" column before implementing.** The stick deadzone is
|
||||
already split L/R, and the draw/LOD defaults are 250, not 150.
|
||||
- Related QoL, tracked separately in ROADMAP §5a: a **Restart game** action
|
||||
next to Quit to desktop, for restart-flagged rows such as MSAA.
|
||||
|
||||
## 1. Problem
|
||||
|
||||
The D356 regroup fixed the *layout*; the *values* still mix three unit
|
||||
families with no consistent rule:
|
||||
|
||||
- **raw internal units shown as-is**: `Input.MouseSensitivity` (1–500, 100 =
|
||||
1× — the "100" is a % of a 1.0 multiplier, not a count),
|
||||
`Input.PadDeadzone` (raw 0–30000, `dispDiv=300` → "23%" but the step of 500
|
||||
raw is 1.67 display-%, so the slider lands on 23, 25, 26…),
|
||||
`Bond.Music`/`Bond.FX` (0–32767, `dispDiv=328`, step 128 = 0.39 display-%);
|
||||
- **N64-era vocabulary on PC rows**: texture filter "Nearest / Bilinear /
|
||||
**3-Point**" (the N64 watch term for trilinear), "FOV scale %" instead of
|
||||
degrees;
|
||||
- **defaults that don't read as defaults**: MSAA shows "4x" fine, but
|
||||
`FpsCap` 0–1000 with step 1 invites 117 FPS; `Video.LodDistance` default
|
||||
150% vs `DrawDistance` 150% with a 25-step grid (100/125/150/175…).
|
||||
|
||||
The PD port is the house style to copy: per-stick sensitivity as a **±10
|
||||
multiplier (default 1.0, negatives invert)** (`input.c:1537`), mouse speed
|
||||
**-30..30 (default 2.5)**, deadzone **raw 0–32767 per stick+axis**, rumble
|
||||
**0–1**, MSAA as a **2x/4x/8x/16x dropdown**, frame cap as **"N FPS"**,
|
||||
volumes as 0–100 %. Turok's (user's list) conventions: single mouse
|
||||
sensitivity knob, per-section resets (landed, D356), master/sound/music
|
||||
volume trio (master still M3-backlog).
|
||||
|
||||
## 2. Principles
|
||||
|
||||
1. **Display, don't rescale the world.** Every change is port-only (row
|
||||
table, `dispDiv`/step/`unit` fields, value-text, `kResetDefaults`): no
|
||||
`src/game` change, and config *storage units* are kept wherever a change
|
||||
would migrate existing ini files (principle 3).
|
||||
2. **Human units in the value column**: `%` on a 0–100 grid, `×` multipliers
|
||||
with 1.0 as the default, `N FPS`, `Off/2x/4x/8x`, `Unlimited`. A slider
|
||||
step must map to a **whole display unit** (the deadzone 1.67%-step bug is
|
||||
the canonical example to fix).
|
||||
3. **Ini stability**: renaming a config key's storage unit is a migration.
|
||||
Where the plan needs it, register a *new* key (e.g.
|
||||
`Input.PadDeadzonePct`) and keep the old key as a read-once legacy alias —
|
||||
the D333 `Input.AimMode`→`Input.AimStyle` alias pattern (`input.c:1962`).
|
||||
Each migration gets a findings note.
|
||||
4. **`kResetDefaults` stays in lockstep** with the config C initializers
|
||||
(D356 Gate E): every default change in this plan updates the table in
|
||||
`optionsoverlay.c` in the same commit; the `GE_WSPROBE_RESET` probe
|
||||
re-verifies.
|
||||
5. **Defaults = the config C initializers**, never a new menu-only default.
|
||||
|
||||
## 3. Row-by-row audit & proposal
|
||||
|
||||
Current state from `optionsoverlay.c` `rows[]` + the `configRegister*`
|
||||
ranges (2026-09-27). "Display" = what the value column shows today.
|
||||
|
||||
### Implemented 2026-09-30 (D443) -- refreshed "Now" vs. result
|
||||
|
||||
The per-row tables below keep the 2026-09-27 audit text for history. Actual
|
||||
pre-change state and what shipped (all port-only; no default or storage change,
|
||||
so `kResetDefaults` is untouched and `GE_WSPROBE_RESET` stays failures=0):
|
||||
|
||||
| Row | Actual "Now" (pre-D443) | Shipped |
|
||||
|---|---|---|
|
||||
| Mouse horizontal sensitivity | 1-500 · step 5 · shown as calibrated `N/100` (50/100 at raw 100) | UI range 10-300, step 10, shown `%.1fx` (raw/100), linear bar; left the calibrated mapping |
|
||||
| Stick deadzone L / R | already split `Input.PadDeadzoneL/R` 0-30000 · step 500 · calibrated `N/100` | step 300 + `dispDiv=300` `%` (whole-% grid, no key migration); left the calibrated mapping |
|
||||
| Music / FX volume | step 128, `/328` | step 328 (1 display-%) |
|
||||
| FOV | `Video.FovScale` 50-150 · step 5 · `%` | row "Field of view", horizontal degrees (see D443 formula); key/storage unchanged, step walks the displayed degree |
|
||||
| Anti-aliasing | 1/2/4/8 · default 2 (C initialiser) | + 16x; range 1-16 (GL clamps to `GL_MAX_SAMPLES`) |
|
||||
| Texture filter | Nearest / Bilinear / 3-Point | unchanged (decision §0) |
|
||||
| Frame-rate cap | 30/60 toggle (sim ticks at 60 Hz) | unchanged this round |
|
||||
| Restart game | -- | new action row after Quit to desktop (both UIs, shared table) |
|
||||
|
||||
### INPUT
|
||||
|
||||
| Row | Now (default · range · step · display) | PD port | Turok (notes) | Proposal |
|
||||
|---|---|---|---|---|
|
||||
| Mouse sensitivity | 100 · 1–500 · 5 · raw `100` | `MouseSpeedX/Y` float −30..30, def 2.5 (sign = invert) | single knob | **Display as a × multiplier**: keep storage (100 = 1.0×), value text `%.1f×` (v/100); range 10–300 (0.1–3.0×), step 10 (0.1×). Label stays "Mouse sensitivity". |
|
||||
| Invert look (mouse) | Off/On | folded into the −/× sign of MouseSpeed | — | **Keep On/Off** (PD's negative-multiplier style is an alternative; a separate invert is simpler next to the × slider). Decision: confirm. |
|
||||
| Aim style / Aim range | N64/Centred (PC) · PC/N64 | — (GE-only, D337/D338) | — | Keep as-is (GE-specific; hiddenIfOn already sane). |
|
||||
| Invert look (controller) | Off/On | sign of stick scale | — | Keep. |
|
||||
| Stick deadzone | 7000 raw · 0–30000 · 500 · ÷300 → `23%` (1.67%-steps) | raw 0–32767 per stick+axis, `DEFAULT_DEADZONE` | MISSING | **New key `Input.PadDeadzonePct` 0–100, step 1, default 23** (7000/30000×100); `input.c` scales back to raw at apply; old key becomes a legacy alias (principle 3). Display `N%` on a whole-% grid. |
|
||||
| Trigger threshold | 23 · 1–99 · 1 · `%` | — (GE extra) | — | Keep. |
|
||||
|
||||
### GAMEPLAY
|
||||
|
||||
| Row | Now | Baseline | Proposal |
|
||||
|---|---|---|---|
|
||||
| Auto-aim / Sight / Look ahead / Ammo (profile) | Off/On (N64 watch 0/1) | N64 watch = ground truth | Keep; tag now reads **(profile)** (this doc's companion change). |
|
||||
| Skip intro / No hit flash / All unlocked | Off/On | Turok: screen-shake/hit-flash family | Keep. |
|
||||
| HUD scale | 100 · 75–150 · 5 · `%` | — | Keep (whole-% grid already; range cap D226). |
|
||||
|
||||
### GRAPHICS
|
||||
|
||||
| Row | Now | Baseline | Proposal |
|
||||
|---|---|---|---|
|
||||
| Anti-aliasing | 1/2/4/8, def 4, shows `None/2x/4x/8x`, restart | PD dropdown 2x/4x/8x/**16x**, def 4x | **Add 16x to the sequence** (`kMsaaSeq`, range clamp 1–16) — SDL/GL supports it; PD has it. Decision: perf cost on mid GPUs (restart already flagged). |
|
||||
| Texture filter | Nearest / Bilinear / **3-Point** | PC convention: Nearest/Linear/Bilinear | **Rename the enum names to `Nearest / Linear / Bilinear`** (3-point *is* trilinear; the N64 term is the last one left on a PC row). Storage 0/1/2 unchanged. |
|
||||
| Anisotropic filtering | 4 · 1–16 · 1 · `x` | Turok 1–16 | Keep; **dim/disable when filter = Nearest** (anisotropy only applies to filtered sampling) — `hiddenIfOff`-style guard, UI-only. |
|
||||
| FOV scale | 100 · 50–150 · 5 · `%` | Turok 3: 60–120° slider | **Display in degrees**: the 100% baseline is the N64 vertical FOV scaled to the port's widescreen math — value text `N°` (v/100 × baseline, 1 decimal); storage unchanged. Decision: degrees vs % (Turok uses degrees; % is the existing mental model — pick one, recommend degrees). |
|
||||
| Native widescreen / Widescreen auto FOV / Crop overscan | Off/On | — | Keep. |
|
||||
| Draw distance | 150 · 100–400 · 25 · `%` (hidden under auto-FOV) | Turok: draw-distance row | Keep the % grid (25% steps read fine); **step 25 → display already whole-%**. No change. |
|
||||
| LOD distance | 150 · 25–400 · 25 · `%` | — | Keep; note default 150 vs DrawDistance 150 is intentional parity. |
|
||||
|
||||
### AUDIO
|
||||
|
||||
| Row | Now | Baseline | Proposal |
|
||||
|---|---|---|---|
|
||||
| Music volume (profile) | 32767 · 0–32767 · 128 · ÷328 → 0.39%-steps | Turok: 0–100% | **step 128 → 328** (exactly 1 display-%; 32767/328 = 99.9 ≈ "100%"). Storage unchanged (N64 `VOLUME_MAX` semantics). |
|
||||
| FX volume (profile) | same | same | same. |
|
||||
| *(master volume)* | — | Turok trio | M3-backlog, out of scope (shape the section for it, D356 note). |
|
||||
|
||||
### VIDEO
|
||||
|
||||
| Row | Now | Baseline | Proposal |
|
||||
|---|---|---|---|
|
||||
| Fullscreen | Off/On | Turok: window mode (borderless missing) | Keep; borderless = separate backlog item. |
|
||||
| Resolution | action row (presets) | both | Keep. |
|
||||
| VSync | On/Off | both | Keep. |
|
||||
| Frame rate cap | 60 · 0–1000 · 1 · `Uncapped/N FPS` | Turok: "up to 120 FPS" / uncapped | **Preset grid**: 0/30/60/120/144 (step = next preset, wraps to Uncapped); value text already `N FPS`/`Uncapped`. Decision: preset grid vs free 1-step (recommend presets; 117 FPS has no purpose). |
|
||||
| Show FPS | Off/On | both | Keep. |
|
||||
|
||||
## 4. Terminology (end-user term = "Profile")
|
||||
|
||||
Decision (2026-09-27, UX review): the end-user term for a per-player
|
||||
game file is **"Profile"** — the per-player bundle of progress +
|
||||
per-file settings. Rejected: **"game file"** (the instruction manual's
|
||||
current term) reads, in modern PC usage, as the *program's* own files
|
||||
("Verify integrity of game files"), not the user's save; **"Save"** was
|
||||
the fallback (universally understood) but reads as a verb in the row tag
|
||||
and as progress-only, which these files are not. "Profile" covers both
|
||||
and fits the dossier styling of the screen.
|
||||
|
||||
Final string set (both UIs: front options + F10 overlay):
|
||||
|
||||
| String | Where |
|
||||
|---|---|
|
||||
| **"Profile"** + value `N` | front options top row (was "Save file: File N") |
|
||||
| **"(Profile N)"** / **"(none)"** | section-title annotation on save-scoped sections (was "(File N)"/"(no file)") |
|
||||
| **"(per profile)"** | per-file row tag (was "(save)", then "(profile)", then "(game file)") |
|
||||
| **"Select a profile"** | F10 unavailable value (was "Select file") |
|
||||
|
||||
**Manual sync TODO:** the instruction manual's "game file" should be
|
||||
updated to "profile" (the manual is a port document we own).
|
||||
|
||||
"Bond file" remains in code identifiers/config keys (`Bond.*` rows,
|
||||
`watchSettingsChooseFile`, ini keys) only -- never user-visible.
|
||||
|
||||
## 5. Gates
|
||||
|
||||
1. Port-only per principle 1; the deadzone key migration (principle 3) is
|
||||
the only config-surface change and follows the D333 alias pattern.
|
||||
2. `kResetDefaults` updated in the same commit as every default/range
|
||||
change; `GE_WSPROBE_RESET` (front + `-level_33`) re-run, all sections
|
||||
`failures=0`.
|
||||
3. Build via `build-pc-cmd.bat` (the pi-shell MSYS env gap, D356 note).
|
||||
4. Visual check owed (maintainer): front options screen + F10 in all five
|
||||
sections; the overlap fix from the same session (the level-1 save-file
|
||||
row no longer paints under the first content row).
|
||||
|
||||
## 6. Out of scope
|
||||
|
||||
- Key rebinding (Phase 4), master volume (M3), output-device selection,
|
||||
borderless window, per-axis controller sensitivity (PD-style four-knob —
|
||||
GE's one-knob design is D238/D304).
|
||||
- Any `src/game` change; the N64 watch keeps its own values and wording.
|
||||
@@ -1,9 +1,7 @@
|
||||
# GE_* environment probes — consolidated list
|
||||
|
||||
Every `getenv("GE_…")` in the tree. The table prose is hand-curated;
|
||||
**`tools_pc/gen_env_probes.py`** re-greps the live sites and reports drift
|
||||
(NEW / GONE vars + a fresh file:line map) — run it before trusting the
|
||||
File:line cells. Last reconciled 2026-09-30 (ROADMAP §7 strip pass: D157/D157T/D157I/D157TB, D207S, D227V, D245_OLDVTX/OLDTEX, D252POOL, D288, D309, D318B suite, D320R removed and marked as tombstones; D318W demoted to detect-only). Previously 2026-09-28 (v0.4.0 release sweep: rows added for probes introduced since 09-21 — D383 bind-capture smoke, D388 option-tree walk, D406 front-options diagnostics, D354/D344 watch-settings & quit-frame harnesses, D318B boundary suite, z-fight grid; the drift checker now also sees the cached env-flag wrapper reads from `port/include/envflag.h`). All are **env-gated**: unset = zero behavior
|
||||
Every `getenv("GE_…")` in the tree, from a grep of `src/` + `port/` on
|
||||
2026-08-31 (master `31d036da`). All are **env-gated**: unset = zero behavior
|
||||
change, so they are safe to leave in a release build (they only cost a
|
||||
`getenv` on the guarded path). "Dead" below means *the finding it was built
|
||||
for is closed* — the probe is strip-candidate scaffolding, not that it does
|
||||
@@ -20,161 +18,40 @@ Two classes:
|
||||
| Env var | File:line | What it does | Status |
|
||||
|---|---|---|---|
|
||||
| `GE_PCDUMP` | `port/fast3d/gfx_opengl.cpp:1079` (`is_pcdump`), `gfx_opengl.cpp:700`, `port/src/video.c:188`, `port/src/config.c:46` (`configGetFrameDump`) | Per-frame framebuffer PPM dump to `$PCDUMP` dir (verification-ritual golden captures, `tools_pc/framediff.py`/`pixcount.py`). Also settable via `[Debug] FrameDump` ini (env wins). **M-33/D168: the writer now emits rows top-to-bottom — captures before that fix are vertically flipped.** | **live** |
|
||||
| `GE_INPUTSCRIPT` | `port/src/input.c:306` | Headless scripted controller-0 input: `"<frame>:<tok>,…;…"` (`SUP/SDOWN/SLEFT/SRIGHT/SNONE` sustain, buttons pulse; `ZHOLD`/`ZREL` sustain fire, D207 M-201). Sole input source when set. | **live** |
|
||||
| `GE_INPUTSCRIPT_MS` | `port/src/input.c:1194` | abpad: entry times in `GE_INPUTSCRIPT` are milliseconds since SDL init instead of controller reads (`tools_pc/abpad`). C-button holds `CUPHOLD`/`CDOWNHOLD`/`CLEFTHOLD`/`CRIGHTHOLD`/`CNONE` (1.2 walk/strafe) also live in the script parser. | **live** |
|
||||
| `GE_PAD_BACKGROUND` | `port/src/input.c:1472` | `=1` sets `SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS` so the port reads pads while unfocused: one real pad drives the port and 1964 side by side (`tools_pc/abpad/live_ab.ps1`). | **live** |
|
||||
| `GE_INPUTSCRIPT` | `port/src/input.c:306` | Headless scripted controller-0 input: `"<frame>:<tok>,…;…"` (`SUP/SDOWN/SLEFT/SRIGHT/SNONE` sustain, buttons pulse). Sole input source when set. | **live** |
|
||||
| `GE_INPUTLOG` | `port/src/input.c:329`, `port/src/config.c:53` (`configGetInputLog`) | Logs computed pad state (buttons + stick) each poll when non-zero. Also `[Debug] InputLog` ini. Also traces D165 `menuptr est/tgt/eff/stick`. | **live** |
|
||||
| `GE_STARTMENU` (+ `GE_STARTMENU_PAGE`, `GE_STARTMENU_DIFF`) | `src/game/lv.c:398-402` (`#ifdef PORT`) | Boot straight into a front-end menu id (13=MISSION_COMPLETE, 10=BRIEFING, 7=MISSION_SELECT, 12=MISSION_FAILED, 6=MODE_SELECT); `_PAGE`=folder row (def 1=Dam), `_DIFF`=0..3. Crash-test a screen fast. | **live** |
|
||||
| `GE_STARTMP` | `src/game/lv.c:403` (`#ifdef PORT`) | D416: `GE_STARTMP=<players>[,<mpStageIdx>]` starts a local multiplayer match with the menu's default options (as MP options -> START does); pair with `GE_MPVIRT=<players-1>` for the controller count. Unattended split-screen testing. | **live** (D416) |
|
||||
| `GE_MPVIRT` | `port/src/input.c:686` | D416: report `n` extra idle controllers (1..3) so the MP menu unlocks / a 2P+ stage loads on a machine with no gamepads. Test harness only; P2..Pn get no input. | **live** (D416) |
|
||||
| `GE_UNLOCK_ALL` | `src/game/debugmenu_handler.c:1098` (`get_debug_enable_all_levels_flag`, `#ifdef PORT`) | Mission-select shows every solo level (playtest jump-to-any-level aid). Cached in a `static int c`. | **live** |
|
||||
| `GE_DEBUG_UNLOCKALL` | `src/game/file2.c:404` (`#ifdef PORT`) | Forces the "all levels unlocked" flag TRUE regardless of the shipped save — same effect as `GE_UNLOCK_ALL` but at the file2 unlock-query site. Cached in a `static int`. | **live** (playtest aid) |
|
||||
| `GE_DEBUGMENU` | `src/boss.c:566` (`#ifdef PORT`) | Makes the L+R debug menu respond to live controller input (C-up+C-down open/close, stick + buttons read from pad 0) instead of the `#ifndef DEBUGMENU` path; unset reproduces that path exactly. Cached in a `static int`. | **live** (playtest aid) |
|
||||
| `GE_FAKE_DECK` | `port/src/video.c:490` (`videoDetectSteamDeck`) | D283: `=1` forces the Steam Deck path on any host (Windows/Linux desktop) so the once-per-ini Deck preset logic (`Video.DeckPresetApplied`, applied only while `Video.Fullscreen=0` and `Window.Maximized=0`) can be tested; `=0` forces it off on a real Deck (skips DMI + `STEAMOS`). Unset = real detection (DMI `Valve` + `Jupiter`/`Galileo`, then `STEAMOS`). **Writes the ini** (preset + flag) — use a throwaway data dir. | **live** (test hook) |
|
||||
| `GE_OPTIONSOVERLAY` | `port/src/optionsoverlay.c:231` | Force-enable the F10 port-layer options overlay (D184 / PR #10) regardless of ini. `atoi != 0`. | **live** |
|
||||
| `GE_D204` | `port/src/audio.c` (`audioSetNextBuffer`) | **Audio-health monitor (D204).** One line per 5 s of wall clock: `rt=` produced-audio/wall ratio (1.000 healthy; below 1 = the 30 Hz audio retrace is starving and the device is padding with silence), `q=` SDL queue depth / `queueLimit` (pinning at the limit with `drop=` climbing = overproduction, the "eventually no audio" state), `drop=` blocks discarded, `max=` largest block vs its `g_MaxFrameSize*4` allocation. **Deliberately cheap (~30 clock reads/s) — safe to leave on for a full playtest, unlike `GE_MIXERTRACE`.** The instrument of record for D202. | **live** |
|
||||
| `GE_D204_OLD` | `port/src/audio.c` (`d204OldMode`) | Restores pre-D204 behaviour (`osAiGetLength()` reports the whole SDL queue depth; `queueLimit` back to 8192) so before/after can be A/B'd in **one binary**, without build-to-build variance. Temporary — remove when D204 closes. | **dead** (D204 closed) — removed 2026-09-30 (ROADMAP §7 strip pass, batch 1, branch `chore/scaffolding-removal-1`); tombstone — the generator flags it GONE |
|
||||
| `GE_AUDIOTRACE` | `src/snd.c` (`sndPlaySfx`, `sndDeactivate`), `src/game/propobj.c` (door sounds), `src/libultra/audio/load.c` (`alLoadParam`) | Per-`sndPlaySfx` resolution trace to `audiotrace.log`: `soundIndex` → `ALSound*`/keyMap/wavetable/base/len/book, the returned `ALSoundState*`, every `sndDeactivate`, and wavetable bind events. M-65 additions: `[VOICE+]`/`[VOICE-]` pairing every voice acquire/release with sound/state/flags (NB: written from two threads to one unbuffered FILE* — occasional torn lines, parse defensively), `[SLOTWRITE]` door slot ownership, `[RETRIGGER-POST]`, `[WAVELOOP]` (per-sound `ALADPCMloop` presence), `[DOORSND]` every door sound state change incl. an `ORPHAN-both-slots-busy` verdict. M-66b addition: `[EXPIRE]` — posted whenever the port-side ownerless-infinite-loop expiration is scheduled (sound/state/delay/fade); this line is **permanent** (part of the D202 option-C fix), unlike the rest of the probe set which is removable on close. M-67/M-69 additions: `[WIRE] t=µs filter=… <- table=…` (`src/libultra/audio/load.c`, every wavetable→decoder bind, µs-timestamped) and `[VOICEMAP]` (`src/libultra/audio/synthesizer.c`, full PVoice→decoder/resampler/env-state address map at synth init — this is how offline voicedump slot addresses tie to WIRE filter addresses). M-68 additions: `[EVT] t=µs type=N state=P` + `[STOP-EVT] … deltaUs=N` (event types per `src/snd.h`: 1=PLAY, 2=STOP, 8=VOL, 64=DECAY, 128=END). M-71 additions: `[VOL] t=µs state=… rawVol=… playing=…` (`src/snd.c`, per-VOL-event value before the slot-volume law) and `[DISTVOL] pos=(…) player=(…) dist=… vol=…` (`src/game/propobj.c`, the distance-attenuation funnel `sub_GAME_7F053894` — this is how an SFX's final volume is computed from hit position). NB: lines from the multiple file handles interleave — occasional torn/corrupt lines, parse defensively. `dumppos=` gives the exact byte offset into `audiodump.raw` (quantised to the 2880-byte audio block). Low cost. | **live** (D202/D205) |
|
||||
| `GE_AUDIOGAINLOG=1` | `port/src/audio.c` (`audioSetNextBuffer`, cached `GE_ENVFLAG`) | D470: once per second logs `D470 gain: master=N peak=P`, the max abs sample of the final (post master-volume) buffer. Cheap; shows the gain effect headlessly. | **live** |
|
||||
| `GE_PULLTRACE` | `src/libultra/audio/load.c` (`alAdpcmPull`) | `[PASTEND]` assertion: fires if a voice's `f->sample` exceeds its wave's total samples while `nOver == 0` (i.e. an unstopped voice keeps decoding past the end instead of zero-filling). Rate-limited to 40 lines. M-65: 0 hits — exhausted voices go silent correctly. | **live** (D202) |
|
||||
| `GE_DMEMWIPE` | `port/src/mixer.c` (`aClearBufferImpl`) | Zeroes the whole software DMEM at the frame boundary (detected as `alMainBusPull`'s clear of `AL_MAIN_L_OUT`, addr 1088). Tests whether audio state survives across frames in DMEM. M-65: no-op on output — ruled out. | **live** (D202) |
|
||||
| `GE_NOWET` | `port/src/mixer.c` (`aSetVolumeImpl`) | Mutes the reverb send (`wetamt = 0`), isolating the reverb delay lines as a possible source of persistent signal. M-65: drone unchanged — ruled out (NB `aPoleFilterImpl` is still an unimplemented no-op, D199). | **live** (D202) |
|
||||
| `GE_BANKDUMP` | `port/src/romdata.c` | `BANKSRC` lines: the source ROM-layout bank tree per `soundIndex` (instrument → sound → envelope pointers + `decayTime`). Used to prove sound 203's `decay=-1` is genuine ROM data, not a conversion bug. One-shot at load. | **live** (D202) |
|
||||
| `GE_FORCEALARM` | `src/game/propobj.c` (`handle_alarm_gas_timer_calldamage`, `#ifdef PORT`) | **D207 diag (temporary).** Forces the in-game alarm klaxon (`alarmActivate`) on a frame counter — on ~2–10 s, off, on again — and emits a per-frame `[D207] f=… active=… ptr_alarm_sfx=… playstate=… locked=…` line to `audiotrace.log`. Repro harness for the "alarm SFX overrides other audio" report when a level's own alarm can't be triggered headlessly. Remove when D207 closes. | **dead** (D207 closed) — removed 2026-09-30 (ROADMAP §7 strip pass, batch 1, branch `chore/scaffolding-removal-1`); tombstone — the generator flags it GONE |
|
||||
| `GE_KEYMAPDUMP` | `port/src/romdata.c` | `KEYMAP` + `ENVSRC` lines: raw source bytes of every `ALKeyMap` / `ALEnvelope` in the ROM-layout blob, for byte-wise comparison against runtime values. One-shot at load. | **live** (D202) |
|
||||
| `GE_AUDIODUMP` | `port/src/audio.c` (`audioSetNextBuffer`) | Raw-dumps every mixed s16/stereo/22050 Hz buffer to `audiodump.raw` in the CWD for offline waveform inspection. Length ÷ 88200 = seconds of audio produced. | **live** (D202) |
|
||||
| `GE_VOICEDUMP` | `port/src/mixer.c` (`aEnvMixerImpl`) | Dumps each voice's resampled mono stream (post-resample/pre-envelope, i.e. the env-mixer input) to `voicedump.raw` as records of `[u32 stateAddr][u32 nSamples][u64 us][s16 × n]`. **The µs stamp is when the block was MIXED — records are rendered ahead, so a record's samples END at its stamp** (M-69: treating stamps as start offsets misplaces content by up to one ~33 ms block). `stateAddr` = the env-mixer state address from `[VOICEMAP]`. Used with `scratchpad/exact_match.py` for full-corpus per-voice sample verification. | **live** (D202) |
|
||||
| `GE_MIXERTRACE` | `port/src/mixer.c`, `src/audi.c` (`amDmaCallback`), `src/libultra/audio/load.c` (`_decodeChunk`, `alLoadParam`) | Per-opcode mixer trace to `mixertrace.log` (DMEM addresses, ADPCM books, decoded frames, `[DMAREQ]`/`[DMAHIT]`/`[DMAMISS]`/`[BINDTABLE]`). **WARNING: unbuffered per-opcode `fprintf`, ~25 MB/min. It slows the process enough to starve the audio thread — M-63 measured "audio at 13 % of real time" that was entirely this probe's own artifact. NEVER use it for timing-sensitive measurement; use `GE_D204`.** Grep it, never read it whole. | **live** (D202) — timing-unsafe |
|
||||
| `GE_TEXPITCH` | `port/fast3d/gfx_pc.cpp:924` (`gfx_tex_pitch_fix`, cached) | `=0` disables the D183 defensive de-stride in `import_texture` (default on). RC2/RC3 texture-shear A/B knob. | **live** (RC3 not finished) |
|
||||
| `GE_TEXRAW` | `port/fast3d/gfx_pc.cpp:1079` | With `GE_TEXDUMP`, also dumps the raw pre-import source bytes (`texdump/rNNN_f…_s…_WxH.bin`) so a decode bug can be told from a source-data bug offline (D183). | **live** (RC3 not finished) |
|
||||
| `GE_TEXDUMP_ADDR` | `port/fast3d/gfx_pc.cpp` (`import_texture`, cached env check) | D75/M-197: with value `<hex>`, dumps the raw source bytes of **every** import from that exact address (bypassing the 400-dump cap) to `texdump/aNNNNN_WxH.bin` + a `GE_TEXA[…]` log line with fmt/siz/size/min/max/nonzero byte stats. Distinguished "never loaded" from "loaded zeros" for the Nintendo-logo texture buffer (root-caused to the D50 sidecar converter dropping embedded blobs). | **live** (D75) |
|
||||
| `GE_D219RAW` | `src/game/image.c` (`texReadUncompressed`/`texChannelsToPixels`, `#ifdef PORT`) | Diagnostic-only, print-only (no behavior change): logs the runtime texture decompressor's bitstream header (texnum/format/width/height/compmethod) for every non-zlib wide-pixel image, plus a dump of the first row of decoded texels exactly as stored in the pool (post-`PORT_PIXEL16`, i.e. byte-swapped for the 16-bit family) for RGBA16/RGB15/IA16. No texnum filter — lets any suspect texture be cross-referenced against the offline ROM census (`scratch/d219_census.txt`) without narrowing by hand. | **live** (D219, closed — kept for future runtime-decoder triage) |
|
||||
| `GE_WRAPFIX` | `port/fast3d/gfx_pc.cpp:3166` (`gfx_set_wrap_fix`) | RC3 test override for the `Video.WrapFix` D74/D167 wrap-block path (`atoi != 0` wins over the ini). | **live** (RC3 not finished) |
|
||||
| `GE_SAVELOG` | `src/game/file.c:46`, `src/game/file2.c:16` (`SAVELOG` macro), `src/boss.c:749`, `src/game/objective_status.c:297,343` | Traces the campaign save/unlock chain (objective completion → `end_of_mission_briefing` → `fileWriteSave` → EEPROM). Used to confirm D157. `g_savelogObjOnce` gates the per-criterion dump to the `bossReturnTitleStage` call. | **live** (strip once a few more level boundaries are playtested) |
|
||||
| `GE_D160` | `src/boss.c:744`, `src/game/bondview2.c:791`, `src/aicommands.def:10055,10181,10236,10242,10247` (all `#ifdef PORT`) | Dam exit-cutscene (D148/D160) diagnostic: trace points in `bossReturnTitleStage`, `bondviewSetCameraMode`, and AI cmds `EndLevel`/`exit_level`/`CameraLookAtBondFromPad`/`CameraSwitch`. **Investigation in progress** — user owes a `GE_D160=1` Dam run. | **live** |
|
||||
| `GE_D243` | `src/game/front.c` (`menu_init`, cached `s_d243`) | Per-menu-transition trace with a local per-tick frame counter (D243 stale-DL-burst screen identification). Counter is NOT numerically identical to the port's internal frame count — cross-reference by elapsed offset. | **dead** (D243 FIXED) — removed 2026-10-01 (scaffolding strip batch 3, branch `chore/strip-d243-probes`); kept as a tombstone — the generator flags it GONE |
|
||||
| `GE_D243CAM` | `src/game/bondview2.c` (`bondviewFrozenCameraTick`, three `CAMERAMODE_POSEND` branches) | Dumps eye/look-at positions (+theta/verta on the `gBondViewCutscene` branch, +dt/angle/pad on the orbit branch) per call. **Found M-143**: the `gBondViewCutscene` branch fires and its look-at (`field_3C4/8/C`) sweeps through a filter catch-up after the cutscene warp — the shake mechanism. | **dead** (D243 FIXED) — removed 2026-10-01 (scaffolding strip batch 3, branch `chore/strip-d243-probes`); kept as a tombstone — the generator flags it GONE |
|
||||
| `GE_D243M` | `src/game/bondview2.c` (`solo_char_load`, `bondviewRemovePlayerBody`, `playerTick`; cached `d243mEnabled`) + `src/game/objecthandler.c` (`drawjointlist`, both region variants) + `src/game/chr.c` (`chrTick`, `after_position_update:`) | M-151/M-154/M-159 probe for the Dam-abseil "frozen Bonds + shake": `D243M: CREATE/REMOVE` at both `bodyModel` sites; `D243M: tick` (idx/model/**propptr/chrptr**/cam modes/`prop->pos`/`field_488.pos`/`field_3B8`/`field_3C4-8-C`/**rp** = render_pos pointer, **never dereferenced** — M-154 fix 2: tick-time `render_pos` holds arbitrary stale slot contents until the first draw pass, so the pre-view root translation is logged by `chrdraw` only) while the model exists or POSEND/INTRO is active; `D243M: draw` per on-screen player render submission; `D243M: chrdraw` — M-154 per-frame model-draw census, one line per CHR render submission (Model* + raw root translation) while a scripted camera mode (POSEND/INTRO/SWIRL/FADESWIRL) is active; a second Bond instance = extra lines with *distinct* model pointers at the same frame (same model is drawn twice/frame — two passes — dedupe by (frame, model)); `D243M: onscreen` (M-159/M-162/M-163/M-166, `chr.c`) — one line per `chrTick` call while the probe is active: `vis=` `posIsOnScreen`'s result (M-159's frustum hypothesis, REFUTED by M-160) + the live `prop->pos` + `actiontype=`/`anim=` (M-160/M-164: which of `chrTick`'s branches fired, the attached-animation pointer) + `aidx=`/`animframe=` (M-166: the animation reverse-resolved to its `animation_table_ptrs1` index, GE_D193A's technique, + its current playback phase — tests whether the visible chr's animation *phase* at cutscene-entry, not just its identity, is what differs from N64) + `x2active=`/`x2raw=` (M-162/M-163, gated on `GE_D243X2`: whether the decisive save/restore experiment ran this tick, and the raw pre-restore position it overrode — the whole `prop->pos` vector as of M-163, not just Z). Silent during FPS play, silent on the `GE_D243X2` fields unless that env var is also set. Note: `field_3B8` x/z read at 10× world units in the smoke capture — not plain world position. | **dead** (D243 FIXED) — removed 2026-10-01 (scaffolding strip batch 3, branch `chore/strip-d243-probes`); kept as a tombstone — the generator flags it GONE |
|
||||
| `GE_D243X2` | `src/game/chr.c` (`chrTick`, gated additionally on `d243mProbeActive()`) | **Not a probe — a decisive behavioral experiment, off by default, NOT a fix.** M-162/M-163: saves `prop->pos` right before `chrTick`'s position-update dispatch (`chr.c:2541-2617`) runs, restores it at `after_position_update:` — before the render-matrix computation later in the same tick can see the cycled value (unlike M-161, which restored too late to have any effect). Tests whether `chrTick`'s own animation-position write (M-160/M-164) is what produces the Dam-abseil shake. M-162 was Z-only (confirmed the mechanism on that axis, but the visible glitch persisted because that capture's chr cycled on Y instead); M-163 widened it to the whole vector. **M-163's live result: NEGATIVE for the visible shake** — `prop->pos` genuinely went smooth (log-verified) but the shake was unchanged, because the camera's look-at filter reads a *different* variable (`field_488.pos`, see `GE_D243X3`) that this experiment never touches. Kept in the tree as a real, useful (if ultimately not the visible-symptom fix) data point — always pair with `GE_D243M=1` so the `x2active=`/`x2raw=` fields on `D243M: onscreen` show whether/what it overrode. Do not ship. | **dead** (D243 FIXED) — removed 2026-10-01 (scaffolding strip batch 3, branch `chore/strip-d243-probes`); kept as a tombstone — the generator flags it GONE |
|
||||
| `GE_D243X3` | `src/game/bondview2.c` (`playerTick`, the `field_488.pos` write site, gated on `g_CameraMode == CAMERAMODE_POSEND`) | **Not a probe — the experiment that actually confirmed the mechanism, off by default, NOT a fix.** M-167: skips the `field_488.pos.x/y/z` assignment (`bondview2.c:10495-10497`) entirely while POSEND is active, freezing it at whatever value it held the instant suppression engaged. `field_488.pos` — not `prop->pos` — is what the cutscene's leaky-integrator look-at filter (M-143, `field_3B8`/`field_3C4-8-C`) actually reads; it comes from `render_pos` (the model's animated-skeleton readback), an independent data path from `prop->pos`. **Live-confirmed positive: user — "yep camera shake is gone now."** Logs a companion `D243M: f488write frame=... idx=... x3active=... x3skip=... epoch=... f488pos=x,y,z` line (always-on when `GE_D243M=1`, independent of whether `GE_D243X3` itself is set) so the freeze is log-verifiable. **M-169: no longer freezes forever** — re-baselines (`x3skip=0`, write allowed through) exactly on the tick a legitimate shot-change teleport fires (`d243GetTeleportEpoch()`, bumped by `chrai.c`'s `AI_TRYTeleportingChrToPad` right after its real `setsuboffset()` call), addressing the M-168 postmortem's "camera looks the wrong way for later shots" incompleteness — untested live yet, next session's first move. **Superseded: D243 shipped its permanent fix (`4ccab22c`, M-170), which removed the X3 suppression block from `playerTick` — no live site remains.** Kept here as a tombstone; the generator flags it GONE. | **dead** (D243 closed — probe removed) |
|
||||
| `GE_D243X4` | `src/game/chr.c` (`chrTick`, the `render_pos` snapshot/restore, gated on `d243mProbeActive()`) | **Not a probe — the M-168 experiment applying X3's technique to the actual draw-path consumer, off by default, NOT a fix.** Snapshots `model->render_pos[]` after a 2-tick warm-up once POSEND engages, then holds it steady by copying the cached contents back over each subsequent tick's freshly-computed buffer — `subcalcmatrices()` always still runs first (never skipped; the first two attempts skipped it and crashed on a reused-pointer stale `-1` tombstone, see findings.md D243 M-168). Addresses Bond's own rendered-body duplication (the consumer X3 never touched). Crash-free, stable, but same forever-freeze incompleteness as X3 pre-M-169. **M-169: same teleport-epoch re-baseline as X3** — the warm-up counter now also resets (re-snapshots) whenever `d243GetTeleportEpoch()` changes, not only on the active/inactive edge. Untested live yet. Revert once a real root-cause fix replaces it; do not ship as-is. | **dead** (D243 FIXED) — removed 2026-10-01 (scaffolding strip batch 3, branch `chore/strip-d243-probes`); kept as a tombstone — the generator flags it GONE |
|
||||
| `GE_D243SWIRL` | `src/game/bondview2.c:10480` (`playerTick`, cached `d243SwirlTarget`) | D243 follow-up probe: `GE_D243SWIRL=<chrnum>` logs a per-tick line for that chr while the swirl/fade camera path is active (tick counter + state), to verify post-fix behavior of the swirl transition. Inert unless set to a non-negative chr number. | **dead** (D243 FIXED) — removed 2026-10-01 (scaffolding strip batch 3, branch `chore/strip-d243-probes`); kept as a tombstone — the generator flags it GONE |
|
||||
| `GE_D193` | `src/game/frametiming.c` (`waitForNextFrame`, `#ifdef PORT`) | Per-wall-second line: `render=` rendered frames, `sim=` sim ticks delivered (Σ of the `deltaFrames`/`g_ClockTimer` fed downstream), `clamped=` frames whose catch-up hit `FRAMETIMING_PORT_MAX_CATCHUP`, `maxraw=` largest pre-clamp delta. Healthy steady state = `render=30 sim=60 clamped=0 maxraw=2` (measured M-80, all levels idle). Use to check whether the wall-clock timer under-advances under real AI load (D193). Cheap; safe for a full playtest. | **live** (D193) |
|
||||
| `GE_D193A` | `src/game/chr.c` (`chrTick` → `d193aSample`, plus `d193aMoved` in `chrUpdateAnim`; `#ifdef PORT`) | Per-wall-second per-ticked-chr locomotion telemetry (D193/D209): `chr=` chrnum, `act=` ACT_TYPE, `speed=` world-units travelled/s, `gtd=` g_GlobalTimerDelta, `tier=` `act_gopos.unk59` SPEED tier (0 walk / 1 run / 2 sprint; `-1` when not ACT_GOPOS), `gspd=` `act_gopos.speed` (**a turn rate, not a travel rate** — 0 means running straight), `typ=` PROP_TYPE (3=CHR, 6=VIEWER), `tick=`/`move=`/`frz=` chrTick entries vs `chrUpdateAnim` calls vs freeze-gated (move « tick = the chr is being visibility-culled out of its own locomotion; `move=0` at high `speed` = WAYMODE_MAGIC), `hid=` CHRFLAG_HIDDEN, then the root-motion scalars `scale` / `xlscale` / `animrate` / `playspeed` / `mspeed` (model->speed) / `endframe` / `anim` ptr / `aidx=` that pointer reverse-resolved to its `animation_table_ptrs1` index (40 walking, 41 sprinting, 42 running, 107 walking_unarmed — map via `src/game/initanitable.c`). `GE_D193A_CHR=<n>` restricts to one chrnum. **This probe found D209**: Cradle showed `tier=1` (RUN) with `aidx=40/107` (walk anims) on every sample. | **live** (D193/D209) |
|
||||
| `GE_D193B` | `src/game/model.c` (`sub_GAME_7F06D3F4`, `#ifdef PORT`) | Rate-limited (1/97) raw anim root-motion decode trace (D193): `joint=` / `flip=` / `base=` mtx channel index / `frame=` / `tmp=` decoded s16 translation triple from the anim bitstream / `angle=` / `anim` ptr / `bd=`+`bs=` the D32 u32 bitDescriptors/bitStream addresses / `njoints`. M-80 Facility: `base=0`, `angle=0` **always** (root-motion facing delta never non-zero — top lead), `tmp` ≈ (±small, ~1086 hip-height, ±small). Compare `tmp`/`angle` magnitudes against a clean N64 decomp build of the same anim+frame. | **live** (D193) |
|
||||
| `GE_D250` | `src/boss.c` (`bossMainloop`, wraps `lvlRender()`, `#ifdef PORT`) | Per-wall-second line: `[D250] lvlRender avg=Nus n=M` — average + count of `lvlRender()` (DL-building: culling/animation/scene traversal) calls that wall-second, isolated from `gfx_run()`'s software-RSP interpretation cost (already covered by the existing "frame rendered in Nus" line). Added M-120 to chase D250 (fps stuck ~45-50 on Dam, ruled resolution/MSAA/vsync/build-flags out already) — found `lvlRender` itself is cheap (~0.5-1.5ms avg), so it is NOT the missing ~10-15ms/frame either; the still-unexplained cost must be elsewhere in the per-retrace-message loop (scheduler/thread round-trip, audio, or something not yet isolated by a targeted timer). Cheap; safe for a full playtest. | **live** (D250) |
|
||||
| `GE_D172` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_tri1` — SETCOMBINE dedup log + non-LOD multitex tri dump; `#ifdef PORT`) | Particle magenta/cyan colour (D172). Logs each distinct SETCOMBINE + active cycletype, and — for a 2-cycle tri that consumes TEXEL1 with `tex_lod=0` and a distinct 2nd tile — the tile fast3d *samples* for each texunit vs what the DL *configured*. **Found D172**: 40/40 particle tris configured tile 1 @ tmem 392 (fire) but sampled tile 0 (smoke) → `smoke*smoke`. FIXED (`gfx_lod_tile_offset` `return rdp.tex_lod ? 0 : i`). Probe kept inert. | **live** (D172 FIXED, probe kept) |
|
||||
| `GE_D157` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_vertex` + `import_texture`; `#ifdef PORT`) | D219/D252's "rainbow particle" family — bullet-impact spark on Bunker1 door confirmed by screenshot to cycle magenta/green/cyan (M-157). Three sub-probes: original exact-color-match log (`g_BulletSparkColors[]` byte match → geometry_mode/LIGHTING + shaded RGB); **`D157T`** texture-identity-keyed vertex full-path log (tile 0 holds a loaded texture ≤16 KiB in frame window [90,240] → raw `cn[]`, post-shade RGBA, LIGHTING, geometry_mode, combine_mode, tile-0 tmem/fmt/siz/addr/size/line, uv; cap 400 + summary/200); **`D157I`** import census (small textures imported in the window, deduped per source address → addr/fmt/siz/size/tile/tmem). M-158: bug did NOT reproduce in 6 identical dev-box runs — probes left live to capture the full RGBA path on the next occurrence (user Steam Deck run with `GE_D157=1`). **M-201:** F12 opens a ~90-frame full-detail `D157TB` burst; continuous logging cut to a 2000-line baseline + 500 colour-match hits (the uncapped white match ran live play at 4–6 fps). | **dead** (D252 fixed) — removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE |
|
||||
| `GE_DVM` | `src/game/gunfire.c` (`gunUpdateAndFire` gate dump + `gunRenderFirstPersonGunModels` render dump; `#ifdef PORT`) | 1P weapon viewmodel (D215). `[DVM]` line: the `field_87F` show/hide gate terms + `Gun_hand_without_item` sub-terms + `copy_of_body_obj_header` RootNode/Switches/numRecords. `[DVMr]` line: `87F` + `cmds_emitted` = gfx commands the weapon `subdraw()` produced (healthy ≈ 150; the D215 bug showed **1**). Rate-limited ~1/s per hand. **Found + verified D215** (`renderdata.flags` read 0 → all weapon DL nodes gated off). Probe kept inert. | **live** (D215 FIXED, probe kept) |
|
||||
| `GE_DTEX` | `port/fast3d/gfx_pc.cpp:1001` | Per-`import_texture` param log (dims / line / size / lod / gen_mipmaps). Used to root-cause RC2 and D159. Kept as an inert diagnostic. | **live** (RC2/RC3 not finished) |
|
||||
| `GE_TEXDUMP` | `port/fast3d/gfx_pc.cpp:1020`, `port/fast3d/gfx_opengl.cpp:700` | PPM dump of every uploaded texture + a `fmt/siz/palfmt/palidx/pal[0..3]` line. Root-caused D161 (Depot ceiling). Kept as an inert diagnostic. | **live** (RC3 not finished) |
|
||||
| `GE_D116` | `port/fast3d/gfx_pc.cpp:1710,2362`, `src/game/textrelated.c:265,541` (cached `ge_d116`) | HUD/text "X-mirror" (D114/D116) vbo/uv trace. **D114/D116 CLOSED — NOT A BUG (M-33/D168): the captures were upside-down, not mirrored.** Probe is now dead scaffolding — strip on the next cleanup pass. | dead (D114/D116 closed) — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D288` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_tri1`, right after the D233 trivial-reject guard; cached `ge_d288`) | M-152 diag for the Silo intro stray triangle: frame-gated to `num_dls` 550–755 (M-155), now **skips `any_behind_camera==1` triangles** (M-155 follow-up — the x/w divide is meaningless for w≤0; that subset was pure false-lead noise) and logs the render-mode word (`oml`) so a live capture can ID the culprit asset against level tables. Logs any triangle whose clip-space NDC bbox covers >50% of the screen or goes non-finite, with each vertex's `w`. Bare `-level_20` re-run: 1965 sane hits in the window, still zero for the target color RGB(83,62,44) — consistent with M-155's working theory that the bare boot's scripted camera never reaches the bug state; **headless probing is at its limit, live capture is next**. | **dead** (D288 closed) — removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE |
|
||||
| `GE_D245V` | `src/game/sky.c:962` (`skyPortRenderPoly`-adjacent, `#ifdef PORT`) | D245 water-seam diag: per-call min/max of the recomputed S/T texture-coordinate ranges for the water fan vertices (rate-limited), to check whether the seam tracks a tc-shift boundary. Inert unset. | **dead** (D245 fixed) — removed 2026-10-02 (strip batch 4, `chore/strip-batch4`); tombstone |
|
||||
| `GE_D245_FIXEDSHIFT` | `src/game/sky.c:946` (`#ifdef PORT`) | D245 test override: pins the water tc shift `k` to the given integer (0..SKY_TC_MAX_SHIFT) instead of the computed per-vertex value, to A/B the allowShift=TRUE behavior. Off by default (unset = current behavior). | **dead** (D245 fixed) — removed 2026-10-02 with the inert sky tile-capture/shift code (strip batch 4); tombstone |
|
||||
| `GE_D252` | `port/fast3d/gfx_pc.cpp:733` (`import_texture`, cached `s_d252`) | D252 rainbow-particle diag: one-shot per (address,size) import log of the tile state (tmem/fmt/siz/addr/size/line) for small textures so a suspect particle tile can be checked against its source bytes by hand. Remove once D252 is resolved. | **dead** (D252 closed) — removed 2026-09-30 (ROADMAP §7 strip pass, batch 1, branch `chore/scaffolding-removal-1`); tombstone — the generator flags it GONE |
|
||||
| `GE_D252POOL` | `src/game/image.c` (`texLoad`), `src/game/initmttex.c`, `port/src/gimgfixup.c` | **TEMP** (M-201): one line per new texture load (pool main/other, free bytes) + `POOL-FULL` events + stage pool range; `D252SYNC: N slots re-synced, M changed` per stage load (the D252 fix at work: M>0 from the 2nd stage on). Load-time only, no fps cost. | **dead** (D252 fixed) — removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE |
|
||||
| `GE_D245_OLDVTX=1` / `GE_D245_OLDTEX=1` | `src/game/sky.c` (`skyPortRenderPoly`) / `port/fast3d/gfx_pc.cpp` (CI8 import + tri-path dims) | **A/B** (M-201): restore the pre-fix s16 sky/water vertex path / the pre-fix 32x43, 16-wide water texture geometry. | **dead** (D245 fixed) — removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE. The pre-fix s16 sky/water vertex path (`skyPortRenderPoly` tail + `skyPortEmitTileShift`) went with it; the float path is now unconditional |
|
||||
| `GE_DYNTEXHASH_OFF=1` | `port/fast3d/gfx_pc.cpp` (`import_texture` cache key) | **A/B** (M-201, D341): disable the content-hashed cache key for dyn-pool textures (reverts to the stale address-only key). | **dead** (D341 closed) — removed 2026-09-30 (ROADMAP §7 strip pass, batch 1, branch `chore/scaffolding-removal-1`); tombstone — the generator flags it GONE |
|
||||
| `GE_D207S` | `src/game/gunfire.c` (player shot-sound block, `#ifdef PORT`) | **TEMP** (M-201): one line per player shot-sound decision: `D207S t=<g_GlobalTimer> hand item rate -> H1|H2|SKIP` (SKIP = both per-hand sound handles still held). Read-only, uncapped, cheap; use instead of `GE_AUDIOTRACE` for live captures (that probe does file I/O on the audio path). | **dead** (D207/D241 closed) — removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE |
|
||||
| `GE_D306C="f0 f1 [wmax]"` | `port/fast3d/gfx_pc.cpp` (after `zf_note_emit`) | **TEMP** (M-201): depth-state census of every emitted triangle in frames f0..f1 (GL depth mode, geometry_mode, other_mode_l, tile-0 texture, NDC centroid, z/w range, min w); optional `wmax` keeps near geometry only. Cap 60000 lines. | dead — fully removed 2026-10-01 (ROADMAP §7 scaffolding strip, batch 2; kept here as a tombstone) |
|
||||
| `GE_INTROCAM=<n>` | `src/game/bondview_r.c` (`#ifdef PORT`, intro camera pick) | **TEMP/test-only**: force the level intro's first camera index (Dam truck shot = 5). The PRNG draw is still consumed. Logs `D306: intro camera index` when set (or with `GE_D306C`). | **TEMP** (useful repro tool) |
|
||||
| `GE_STARTWEAPON=<ITEM_IDS>` | `src/game/bondview_r.c` (`#ifdef PORT`, after the start-inventory setup) | **TEMP/test-only** (M-201, D303): adds the item + 400 rounds of its ammo and makes it the starting right-hand weapon (M16 = 13). Pair with `GE_INPUTSCRIPT` `ZHOLD`. | **TEMP** (useful repro tool) |
|
||||
| `GE_D235` | `src/boss.c` (per-stage `while (!done)` block, after `memaReset`; `#ifdef PORT`) | D235 evidence probe: logs how many GL-texture-cache entries the per-stage `videoResetTextureCache()` call purges at each level load. A cold boot into a level should log 0; a death/restart (or any transition from a level that uploaded textures) logs >0 — direct evidence of the stale-entry mechanism. The clear itself is **not** gated on this var (it's the fix, see below); the var only controls the log line. | **live** (D235) |
|
||||
| `GE_D235_OFF` | `src/boss.c` (same block) | **TEMP** A/B switch: skips the per-stage `videoResetTextureCache()`, i.e. reverts the D235 fix for one run. For comparing a restart with and without the fix. | **dead** (D235 closed) — removed 2026-10-02 (strip batch 4); tombstone. The D235 fix (`videoResetTextureCache()` per stage load) and the `GE_D235` log stay |
|
||||
| `GE_D235_RELOAD=<frames>` | `src/boss.c` (same block + `bossMainloop` retrace branch) | **TEMP** reload harness: once the title stage loads after the first level (e.g. after a scripted watch Abort), wait `<frames>` retraces, then re-enter that level via `bossSetLoadedStage()`, the front end's own mission-start call. A headless death/restart without the pointer-menu walk. Pair with `GE_INPUTSCRIPT="800:START;1200:A;1300:RIGHT;1400:A"` (abort) on `-level_XX`. | **dead** (D235 closed) — removed 2026-10-02 (strip batch 4); tombstone |
|
||||
| `GE_D235_NEXT=<stage id>` | `src/boss.c` (with `GE_D235_RELOAD`) | **TEMP**: re-enter a *different* stage after the abort (level A → title → level B), used for D336 (Runway → Frigate). `GE_D85TEX=1` now also logs each stage's texture-pool size and address range (`src/game/initmttex.c`). | **dead** (D235 closed) — removed 2026-10-02 (strip batch 4); tombstone |
|
||||
| `GE_D320R="tick:action;..."` | `port/src/d320repro.c` (hooked in `src/game/lv.c` per-tick PORT block) | **TEMP** forced-repro harness keyed on `g_GlobalTimer`. Actions: `roster`, `jump=<chr>,list=<hexid>,off=<n>`, `bit=<hex>`, `trace=<chr>`, `rename=<chr>,<num>`, `seed=<hi>,<lo>` (D320); `explode=<type>,<dist>` (explosionCreate ahead of Bond) and `cheat=gl|rl|inv|<id>` (game cheat activation) for D252 headless explosion/debris captures. | **dead** (D320 fixed via D329) — removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE; `port/src/d320repro.c` + its `lv.c` hook deleted |
|
||||
| `GE_AIMLOG=1` (+ `GE_INPUTSCRIPT` tokens `MDX<n>` `MDY<n>` `RHOLD` `LHOLD` `RREL`) | `port/src/input.c` | D337 aim-model harness: scripted sustained mouse deltas (treated as a grabbed mouse) and aim holds, plus a per-poll line with aim state, active device, the game's drawn crosshair x, `crosshair_x/y_pos` and camera angles. Measures range, edge-scroll and jitter headlessly. | **live** |
|
||||
| `GE_PERFSTAT=1` | `port/fast3d/gfx_pc.cpp` (`gfx_run`/`gfx_end_frame`), `port/fast3d/gfx_opengl.cpp` (start/end_frame), `src/boss.c` (main loop) | #92/D339 perf probe: per-300-frame averages of render phases (pre / display-list / post / swap+pace), tris and draw batches per frame, game-thread busy time per scheduler message, and a GL_TIME_ELAPSED GPU timer (meaningful only when GPU-bound). 2026-10-01: also prints `PERFSTAT spikes: max=..ms at frame N (dl= run=) >20ms= >33ms=` per 300-frame window (hitches hide in the averages). | **live** |
|
||||
| `GE_NOSHADERWARM=1` | `port/fast3d/gfx_pc.cpp` (`gfx_shader_list_open`) | D480 falsifier: disables the shader pre-warm (no read or write of `$S/ge007.shaders`), so first-seen combiners compile mid-frame as before. | **live** (D480) |
|
||||
| `GE_RENDERINLINE=1` | `port/src/libultra.c` (`portRenderWorkerOn`) | D481 falsifier: run gfx tasks inline on the scheduler thread (the pre-D481 path) instead of on the render worker. `GE_DETERM` forces this too. | **live** (D481) |
|
||||
| `GE_FAKE_LOWEND=1/0` | `port/src/video.c` (`videoApplyLowEndDefaults`) | D482: force the low-end GPU classification on (1) or off (0) for the one-time first-launch defaults check (`Video.LowEndConsidered`). Testing only, like `GE_FAKE_DECK`. | **live** (D482) |
|
||||
| `GE_D75V` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_vertex`, end of the copy/transform loop; cached env check) | D75 (Nintendo logo) per-batch vertex probe: for each `G_VTX` batch in frames `lo..hi`, logs frame#, source pointer, n_vertices, original dest_index, raw ob[0..2] of the first vertex, min/max of the transformed x/y/z/w across the batch, and the MP translation row. Used to prove the transform is correct (M-195) and that every batch encodes v0=0 (slot-overwrite hypothesis). Value form `"lo-hi"` (e.g. `300-900` = the logo screen); unset = off. **D473 (2026-10-01): the cache was broken (re-read on every G_VTX when unset); now a real one-shot cache.** | **live** (D75) |
|
||||
| `GE_D75D` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_vertex` G_VTX case + `gfx_sp_tri1` rejection/emit gates; cached env check) | D75 (Nintendo logo) per-frame draw-path probe (M-196): for frames `lo..hi`, logs one line/frame with the V/T token stream (`seq`), max triangle vertex index, cumulative vertices loaded, and per-triangle accounting — trivial-reject / backface-cull counts, how many tris reach GL, how many of those are on-screen (NDC bbox ⊂ [−1,1]²), invisible-flag count, vertex-color min/max, first oml word, depth z/w range + NaN count, texture-sampler presence (`utex` = GL shader, `cutex` = combiner), combine mode, prim/env color. Used to REFUTE the slot-overwrite hypothesis (DL is streamed, max index 15) and narrow the bug to a texture-modulate material rendering black. M-197 added logo-mode `lg_*` fields (sampled only on tris with the logo combine word — the frame-level `utex`/`cutex` are first-tri-wins and were the source of the "combiner claims 0" false anomaly): `lg_utex`/`lg_tex` + tile-0 descriptor + texture buffer address/id/size for the logo material. Value form `"lo-hi"` (logo window ≈ `244-320` on a bare front-end boot); unset = off. Companion one-shot `D75DCC:` log in `gfx_generate_cc` fires when `combine_mode==0x009ffe4f19ffe4f1` to decode the logo's combine slots. | **live** (D75) |
|
||||
|
||||
| `GE_PDMOUSEAIM` | `port/src/input.c:487` (`pdMouseAimEnabled`) | Force on/off the PD-style mouse-aim model (D194 lineage); unset = default (on in N64 aim mode unless legacy GEPD aim is selected). | **live** |
|
||||
| `GE_QUITFRAME` | `port/src/video.c:966` | D344 harness clean self-quit: exits after frame `<n>` completes and swaps (replaces `timeout` kills; the host never exits under an in-flight frame). Cached. | **live** (harness) |
|
||||
| `GE_PADMAPTEST` | `port/src/input.c` (`inputInit` -> `padMapSelfTest`) | D469: boot-time self-test of the gamepad action table. Proves preset 0 == the legacy hard-wired mapping (20,971,520 synthetic cases), logs the per-source mask table, a Custom remap, per-seat independence, the Start guard and the binding API; restores config afterwards (nothing persisted); the game then continues. Cached (`GE_ENVFLAG`). | **live** (harness) |
|
||||
| `GE_D466LOG=<n>` | `port/src/drawdistgameplay.c` (`portD466FrameLog`, called from `bgDetermineVisibleRooms`) | D466 dev probe: every `<n>` calls (per player view) logs `D466 f=.. active= ext= auth= subset= hext= hauth= chrOn= chrGp= seen= hgame=` to stderr: extended vs authored room-set counts + hashes, ONSCREEN chrs vs gameplay-on-screen chrs, `HAS_BEEN_ON_SCREEN` count, and a chr-state hash. Cached. | **live** (D466 dev probe) |
|
||||
| `GE_D468_SCALE=<0.05..1>` | `port/src/drawdistgameplay.c` (`portD468UpdateClamp`) | D468 dev falsifier: multiplies the AI-view-clamp fractions (and so turns the clamp on even at 16:9 / FOV 100), so a headless `GE_D466LOG` run can show the authored room set and gameplay on-screen chrs shrinking. Cached; out-of-range values ignored. | **live** (D468 dev probe) |
|
||||
| `GE_SHADEPERSP=1` | `port/fast3d/gfx_opengl.cpp` (`gfx_opengl_init`, shader build) | D540 A/B switch: perspective-correct shade-colour interpolation instead of the default RDP-style screen-affine (`noperspective`) one (the sky horizon fade). Read once at GL init. | **live** (D540 A/B) |
|
||||
| `GE_FOGPIXEL=1` | `port/fast3d/gfx_opengl.cpp` (`gfx_opengl_init`), `port/fast3d/gfx_pc.cpp` (`gfx_sp_vertex`) | D543 A/B switch: the D540 exact per-pixel fog (fog * w, divided by w in the shader) instead of the default per-vertex RSP fog (honest negative-w divide, CPU near clip lerps it). Read once at GL init. | **live** (D543 A/B) |
|
||||
| `GE_FOGVERTEX` | (removed) | **removed** -- D540's per-vertex-fog opt-in; per-vertex fog is the D543 default, the opt-out is `GE_FOGPIXEL=1`; tombstone kept. | **removed** |
|
||||
| `GE_NEARCLIP=0` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_tri1`) | D543 A/B switch: disable the CPU RSP-style near-plane clip (default on) and leave near-plane clipping to the GPU. Cached on first triangle. | **live** (D543 A/B) |
|
||||
| `GE_NEARCLIPFOG=recompute` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_tri1`) | D543 A/B switch: recompute the per-vertex RSP fog value of near-clip-created vertices from their new z/w (as `gfx_sp_vertex`) instead of lerping it (default). Cached on first triangle. | **live** (D543 A/B) |
|
||||
| `GE_FOGSNAP=1` | `port/fast3d/gfx_opengl.cpp` (fragment shader build) | D540 A/B switch: re-enables the D503 full-fog snap (fog >= 247/255 -> full), off by default since D540 (its hard edge swept across the ground in chunks). Cached on first shader build. | **live** (D540 A/B) |
|
||||
| `GE_PADFAMILY=xbox\|ps\|nintendo` | `port/src/input.c` (`inputPadFamily`) | D471 dev override: force the controller family used for button names in the PC menus (headless screenshots without the hardware). Cached. | **live** (D471 dev knob) |
|
||||
| `GE_D466_FORCE=1` | `port/src/drawdistgameplay.c` (`portD466Active`) | D466: run the authored-distance split even at DrawDistance 100% (identity test: authored set must equal extended). Cached. | **live** (D466 test hook) |
|
||||
| `GE_D466_OFF=1` | `port/src/drawdistgameplay.c` (`portD466Active`) | D466 kill switch: disable the gameplay/render split (every gameplay reader falls back to the extended-range state, i.e. the pre-fix #125 behaviour). For A/B only. Cached. | **live** (D466 kill switch) |
|
||||
| `GE_INPUTSCRIPT_P1` | `port/src/input.c:1216` | Split-screen test harness: scripted input for player slot 1, same entry syntax as `GE_INPUTSCRIPT` (slot 0); mouse tokens and `CHOLD`/`CREL` ignored with one warning. Used with `GE_STARTMP` + `GE_MPVIRT`. | **live** (test harness) |
|
||||
| `GE_INPUTSCRIPT_P2` | `port/src/input.c:1217` | As `GE_INPUTSCRIPT_P1`, for player slot 2. | **live** (test harness) |
|
||||
| `GE_INPUTSCRIPT_P3` | `port/src/input.c:1217` | As `GE_INPUTSCRIPT_P1`, for player slot 3. | **live** (test harness) |
|
||||
| `GE_MEMPREDZONE=1` | `src/memp.c:340`, `src/memp.c:369` (+ periodic check in `port/src/video.c`) | D464 red-zone checker: pads every stage-bank allocation with a 64-byte pattern trailer and logs `MEMPREDZONE: HIT ...` at level unload, every 60 frames and at quit. Unset = one cached branch per allocation. | **live** (D464 dev tool) |
|
||||
| `GE_CRASHTEST` | `port/src/crash.c` (`crashTestMaybe`, called at the end of `crashInit`) | Deliberate-crash hook for verifying the crash handler: `GE_CRASHTEST=<N>` recurses N frames deep (capped at 64) right after the handler is installed and faults with a null write. `ge007.crash.log` should then show `#00`..`#N-1` in `crashTestRecurse`, then `SDL_main`, CRT and system frames; each frame line carries `va=0x…` for `addr2line -f -p -e build-pc/ge007.x86_64.exe <va>`. Unset/0 = no effect. | **live** (harness) |
|
||||
| `GE_ROOMPOOL` | `port/src/video.c:371` (`portRoomPoolScale`) | Scales the port's room pool by a float factor — before/after captures, and a user opt-out if the larger pool ever causes trouble on a level. | **live** |
|
||||
| `GE_BINDPROBE` | `port/src/input.c:885` | One-shot dump of the resolved PC bindings after setup (D383 bind-capture verification). | **live** (diagnostic) |
|
||||
| `GE_BINDCAPTUREPROBE` (+ `GE_BINDCAPTUREACTION`) | `port/src/optionsoverlay.c:1000,1005` | D383 isolated host→menu smoke probe: when the overlay force-opens, injects a scripted bind-capture keypress (ESC/DEL/F2). Test data dir must be private — capture persists the key. | **live** (diagnostic) |
|
||||
| `GE_OPTIONSOVERLAY_SECTION` | `port/src/optionsoverlay.c:989` | Isolated screenshot/probe of a nested F10 page by row key (opens that header; never a menu mode). | **live** (diagnostic) |
|
||||
| `GE_OPTIONSOVERLAY_SELECTBACK` | `port/src/optionsoverlay.c:995` | Screenshot-only: pins the selection to the Back row when the overlay auto-opens, to check it against a normally selected setting on the same page. | **live** (diagnostic) |
|
||||
| `GE_OPTIONSOVERLAY_DROPDOWN` | `port/src/optionsoverlay.c:1195` | Screenshot-only: with the overlay auto-opened on a page, opens the dropdown popup of the named row key (e.g. `Video.TextureFilter`, `__Resolution`) and focuses it. | **live** (diagnostic) |
|
||||
| `GE_OPTIONSOVERLAY_ATFRAME` | `port/src/optionsoverlay.c:3362` | Re-applies the GE_OPTIONSOVERLAY / _SECTION / _SELECT request at emit frame n so it survives a level load (in-level screenshots, D558). Read once. | **live** (diagnostic) |
|
||||
| `GE_OPTIONSOVERLAY_SELECT` | `port/src/optionsoverlay.c:1221` | Screenshot-only: selects the named row key on the auto-opened page (e.g. `Game.NoHitFlash`) so its tip shows (D558). | **live** (diagnostic) |
|
||||
| `GE_OPTIONTREEPROBE` | `port/src/optionsoverlay.c:888` | D388: opt-in in-memory walk of the real F10 page tree; writes no settings, binds or profile bytes; restores UI state afterward. | **live** (diagnostic) |
|
||||
| `GE_PRESETPROBE` | `port/src/optionsoverlay.c:957` (`presetProbe`) | D440: one-shot at overlay init — drives the "Original N64" and "Port defaults" preset rows through the real arm -> confirm activation and checks the "Active" state; logs `GE_PRESETPROBE: PASS/FAIL`. **Writes the live config** (persisted on exit) — use a throwaway data dir. | **live** (diagnostic) |
|
||||
| `GE_OVPOINTERMAP` | `port/src/optionsoverlay.c:1726` | D391 pointer-map diagnostic: on LMB-down (and every 120 samples) logs SDL mouse coords vs GL drawable pixel coords + the hover row, to separate hit-band/scroll errors from coordinate-space mismatch. | **live** (diagnostic) |
|
||||
| `GE_FOOTERPROBE` | (removed) | **removed** -- the F10 footer was replaced by the hint line under the box (menu PD parity); tombstone kept. | **removed** |
|
||||
| `GE_OVNAV` | `port/src/frontoptions.c:106` | D519 follow-up harness: scripted overlay navigation on file select (O toggle, D/U move, A open, B back, P log the place), one token per 20 file-select frames. | **live** (harness) |
|
||||
| `GE_FILESELECT_OVERLAY` | `port/src/frontoptions.c:96` | One-PC-options-UI (D519) test hook: opens the F10 overlay over file select without a click; value `n` > 1 also closes it `n` label-frames later. Replaces the retired front-screen probes (the FRONTOPTIONS_SECTION, FRONTNAVLOG, FRONTOPTS_WIDTHS and FRONTOPTIONS_AUTO probes). | **live** (harness) |
|
||||
| `GE_WSPROBE` (+ `GE_WSPROBE_FRONT`, `GE_WSPROBE_RESET`, `GE_WSPROBE_F10FRONT`) | `port/src/watchsettings.c:616,568,512`, `port/src/optionsoverlay.c:1566` | D354 watch-settings probe family: `WSPROBE[=tick]` logs before/after the queued settings commit one tick apart; `_FRONT[=tick]` drives a headless front-end change scenario; `_RESET` prepares an options-reset after 120 front ticks and verifies persistence once the stage drains; `_F10FRONT` fires a one-shot F10→front poll after the file-select save becomes available (avoids the joyDisablePoll deadlock). | **live** (diagnostic) |
|
||||
| `GE_ZF` (+ `GE_ZFTOL`, `GE_ZFBOX`) | `port/fast3d/gfx_pc.cpp:1653-1657` | Z-fight grid probe: per tri emit, buckets the centroid cell and flags cells that fight ≥5 times (cap 600 hits); `ZFTOL`=tolerance, `ZFBOX="x0 y0 x1 y1"`=world-box filter. | **live** (diagnostic) |
|
||||
| `GE_D526` (+ `GE_D526BOX`, `GE_D526MAX`) | `port/fast3d/gfx_pc.cpp:1895,2024,2032,2034` | D526 per-triangle draw-state census probe (text-only): `GE_D526="lo-hi"` = sim-frame range (same dash syntax as `GE_D75D`); `GE_D526BOX="x0 y0 x1 y1"` = NDC region filter (same syntax as `GE_ZFBOX`, default on-screen); `GE_D526MAX` caps per-triangle lines (default 400). Per emitted in-region tri: frame, NDC bbox, z/w, geometry_mode, other_mode_l/h, combine word, sampled tile tmem/fmt/siz, first_tile_index, prim alpha, vertex alpha min/max, use_alpha/modulate/invisible — plus one per-frame line aggregating in-region tris by (comb, oml, omh, alpha) signature. Retained as a generic transparency/texture triage tool (D195/D266/D268/D273 class); the D526 grate case itself was confirmed faithful N64 behaviour, so no fix shipped. Zero cost unless `GE_D75D`/`GE_ZF`/`GE_D303`/`GE_D526` is set (folded into the D474 `tri_dbg` gate). | **live** (diagnostic) |
|
||||
| `GE_D116` | `port/fast3d/gfx_pc.cpp:1710,2362`, `src/game/textrelated.c:265,541` (cached `ge_d116`) | HUD/text "X-mirror" (D114/D116) vbo/uv trace. **D114/D116 CLOSED — NOT A BUG (M-33/D168): the captures were upside-down, not mirrored.** Probe is now dead scaffolding — strip on the next cleanup pass. | dead (D114/D116 closed) |
|
||||
|
||||
## Dxx diagnostic probes — finding closed unless noted
|
||||
|
||||
| Env var | File:line | Finding / what it logged | Status |
|
||||
|---|---|---|---|
|
||||
| `GE_D51` | `port/src/libultra.c:496`, `src/game/model.c:125,244,544` | msgQ 32-slot overflow watch / ModelSlot layout | dead (D51 closed) — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D54` | — (all blocks stripped M-32, commit `49ce620a`) | music seq-table ABI / endianness (`ALMidiHdr`) | dead (D54 closed) — fully removed (kept here as a tombstone; the generator flags it GONE) |
|
||||
| `GE_D154` | `src/game/bg.c:3418,3447,3632` (capped 64 calls) | bg room-GDL call trace (room / gdlidx / vtxoff / op / raw hdr words) | dead (D154 closed) — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D227V` | `src/game/sky.c` (`skyPortRenderPoly`, `#ifdef PORT`) | Sky/water per-vertex dump: `S`/`T`/`w`/`1/w`, screen xy, the chosen tc shift `k`, the baked `tc`, and whether the tile capture succeeded. **Found D227**: one sky quad spans ~30,000 texels in S and T but `Vtx.tc` is S10.5 in an `s16` (+/-1024 texels), so 7 of its 8 texture coordinates overflowed and wrapped -- the starburst/"two sky processes" seam. Root cause was a unit error (`unk20`/`unk24` are already S10.5, not texels; M-82's `*32` was spurious). FIXED M-100b. Probe kept inert. | **dead** (D227 fixed) — the probe lived in the pre-D245 s16 sky path, unreachable since D245 M-201; removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE |
|
||||
| `GE_D176` | `src/game/bgfog.c:459,471`, `src/game/sky.c:325,381` (all `#ifdef PORT`) | D176 sky/fog env-match + sky-vert trace (Path B, PR #18) | **live** (D176(a)/(b) OPEN) |
|
||||
| `GE_D178` | `src/game/front.c:6586,6594` (`#ifdef PORT`) | briefing-data u16 decode trace (blank-objectives, D143/D178) | dead (D178 closed) — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D56` | `src/game/model.c:227,830` | watch `Model` raw-offset reads | dead (D56 closed) — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D60` | `port/src/libultra.c:521,735` | gfx frame msgQ delivery trace | dead (D60 closed) — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D61` | `port/src/libultra.c:795` (`s_d61opened`) | opens a one-shot log file | dead (D61 closed) — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D62` | `port/src/libultra.c:477` | osRecvMesg trace | dead (D62 closed) — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D63` | remaining: `src/game/bg.c:2525,2908,2953`, `front.c:1410,1416,7814,7889,8083,8091,8099,8366`, `image.c:2453`, `language.c:431`, `model.c:4340`, `title.c:607` | gun-barrel sub-DL clobber hunt (D63/D64/D65/D66 — the labels drift within the blocks). Stripped M-30 (`d0789358`, `gfx_pc.cpp` trail). Stripped M-32 (`49ce620a`): `memp.c`, `bg.c:2880` (incl. the bare non-PORT `d63bgprimarycount` static + entry log), `blood_animation.c`, `dyn.c`, `rsp.c`. **Remaining blocks are all `#ifdef PORT` + getenv-guarded and inert — strip candidates for a later pass.** | dead (D63 closed) — partially removed — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D69` | `src/game/ob.c:165,227` | stage-load (D69) object trace | dead (D69 closed) — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D69BB` | `src/game/bg.c:2460,2479,3020` | D69 bg-binary layout trace | dead — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D69STAN` | `src/game/stan.c:266,276` | D69 stan-tile trace | dead — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D85DUMP` | `src/game/bg.c:2510` | D85 `bgWidenRoomGdl` 8→16B dump | dead (D85 closed) — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D51` | `port/src/libultra.c:496`, `src/game/model.c:125,244,544` | msgQ 32-slot overflow watch / ModelSlot layout | dead (D51 closed) |
|
||||
| `GE_D54` | — (all blocks stripped M-32, commit `49ce620a`) | music seq-table ABI / endianness (`ALMidiHdr`) | dead (D54 closed) — fully removed |
|
||||
| `GE_D56` | `src/game/model.c:227,830` | watch `Model` raw-offset reads | dead (D56 closed) |
|
||||
| `GE_D60` | `port/src/libultra.c:521,735` | gfx frame msgQ delivery trace | dead (D60 closed) |
|
||||
| `GE_D61` | `port/src/libultra.c:795` (`s_d61opened`) | opens a one-shot log file | dead (D61 closed) |
|
||||
| `GE_D62` | `port/src/libultra.c:477` | osRecvMesg trace | dead (D62 closed) |
|
||||
| `GE_D63` | remaining: `src/game/bg.c:2525,2908,2953`, `front.c:1410,1416,7814,7889,8083,8091,8099,8366`, `image.c:2453`, `language.c:431`, `model.c:4340`, `title.c:607` | gun-barrel sub-DL clobber hunt (D63/D64/D65/D66 — the labels drift within the blocks). Stripped M-30 (`d0789358`, `gfx_pc.cpp` trail). Stripped M-32 (`49ce620a`): `memp.c`, `bg.c:2880` (incl. the bare non-PORT `d63bgprimarycount` static + entry log), `blood_animation.c`, `dyn.c`, `rsp.c`. **Remaining blocks are all `#ifdef PORT` + getenv-guarded and inert — strip candidates for a later pass.** | dead (D63 closed) — partially removed |
|
||||
| `GE_D69` | `src/game/ob.c:165,227` | stage-load (D69) object trace | dead (D69 closed) |
|
||||
| `GE_D69BB` | `src/game/bg.c:2460,2479,3020` | D69 bg-binary layout trace | dead |
|
||||
| `GE_D69STAN` | `src/game/stan.c:266,276` | D69 stan-tile trace | dead |
|
||||
| `GE_D85DUMP` | `src/game/bg.c:2510` | D85 `bgWidenRoomGdl` 8→16B dump | dead (D85 closed) |
|
||||
| `GE_D85TEX` | `src/game/image.c:2479`, `src/game/tex.c:859,1050` | D85 texpool-full events (Depot analysis) | dead (D85 closed) — was useful for §3 |
|
||||
| `GE_D217TEX` | `src/game/image.c:234` (`texInflateZlib`) | D217: logs `texnum`/`format`/`numcolours`/`palette[0..1]` straight off the decode bitstream (before any arena/pointer math) — the decode-correctness check that proved the affected texture (grip, `texnum=1608`) decodes byte-identically across levels, ruling out a decode/ROM-data bug. | **live** (D217 open — investigation instrument) |
|
||||
| `GE_D229` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_tri` dual-texunit probe + `import_texture`) | D229: per-draw dump of both texunits' tile/tmem/fmt/siz/UV state for 2-cycle draws, plus a one-shot confirm line when the RGBA16-over-CI8 import remap fires (the fix path). Inert by default. | **live** (D229 fixed — probe kept for the Surface 2 / sweep verification) |
|
||||
| `GE_D86` | `src/game/model.c:6295`, `src/game/objecthandler_2.c:143` | D86 model rwdata trace | dead (D86 closed) — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D87` | `src/game/ramromreplay.c:296,363,381` | D87 `ramromfilestructure` endianness | dead (D87 closed) — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D88` | `src/game/prop.c:1367`, `src/game/stan.c:3090` | D88 `Usetup*Z` propDef stream | dead (D88 family closed; D88.4 resolved) — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D90` | remaining: `src/game/bondview2.c:2146`, `src/game/prop.c:1391` (bondview_r.c blocks stripped M-32, `49ce620a`) | D90 bondview / prop NULL trace | dead (D90 closed) — partially removed — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D96` | `src/game/chrprop.c:436,507` (cached `probe`) | D96 chrprop trace | dead (D96 closed) — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D86` | `src/game/model.c:6295`, `src/game/objecthandler_2.c:143` | D86 model rwdata trace | dead (D86 closed) |
|
||||
| `GE_D87` | `src/game/ramromreplay.c:296,363,381` | D87 `ramromfilestructure` endianness | dead (D87 closed) |
|
||||
| `GE_D88` | `src/game/prop.c:1367`, `src/game/stan.c:3090` | D88 `Usetup*Z` propDef stream | dead (D88 family closed; D88.4 resolved) |
|
||||
| `GE_D90` | remaining: `src/game/bondview2.c:2146`, `src/game/prop.c:1391` (bondview_r.c blocks stripped M-32, `49ce620a`) | D90 bondview / prop NULL trace | dead (D90 closed) — partially removed |
|
||||
| `GE_D96` | `src/game/chrprop.c:436,507` (cached `probe`) | D96 chrprop trace | dead (D96 closed) |
|
||||
| `GE_D104` | `src/game/bg.c:654` (rate-limited, `d104c`) | D104 depth-clear trace | dead (D104 closed) |
|
||||
| `GE_D71LOG` | `port/fast3d/gfx_pc.cpp:645` | D71 fast3d trace | dead (D71 closed) — removed 2026-10-03 (strip batch 5, `chore/strip-batch5`); tombstone — the generator flags it GONE |
|
||||
| `GE_D71LOG` | `port/fast3d/gfx_pc.cpp:645` | D71 fast3d trace | dead (D71 closed) |
|
||||
| `GE_D75` | `src/game/title.c` `sub_GAME_7F007F30` (capped 8) | D75 Bug 2 gun-barrel model probe: logs `chrModelInstance`/`gunModelInstance` ptr+obj+numMatrices, `render_pos`, `renderData.mtxlist`, `g_GfxMemPos`, `osVirtualToPhysical(render_pos)` | **LIVE** (D75 Bug 2 OPEN — see §F "D75 Bug 2 — RUNTIME PROBE") |
|
||||
| `GE_D305` | `src/snd.c:420` (`sndHandleEvent` desync branch, `#ifdef PORT`) | D305: logs the sound-preemption desync state when it is detected (after the NULL-guard fix made the crash itself impossible) — confirms whether `Game.AllUnlocked` correlates or was coincidental, and whether it recurs. Inert unset. | **live** (D305 monitoring) |
|
||||
| `GE_D309` | `src/game/chrai.c:810,3424`, `src/game/chraction.c:10139` (all `#ifdef PORT`) | D309 freeze probe: logs AI label-not-found restarts (`chrai.c`) and the run-to-bond persistent-list step-on path (`chraction.c`, capped 400 lines) — the diagnostics used to root-cause the elusive complete freeze (D309/D310). Inert unset. | **dead** (D309/D310 fixed) — removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE |
|
||||
| `GE_D314` | `port/src/optionsoverlay.c:681` (cached `s_d314Enabled`) | D314 F10-flicker probe: logs the overlay's viGetY / visible-row count / scroll / selection each poll. Inert unset; strip candidate once D314 is triaged. | **live** (D314 pending triage) |
|
||||
| `GE_D318R` | `port/src/random.c:69,147` (`#ifdef PORT`) | PRNG golden probe (D284/D318): logs the first 4000 gameplay-PRNG draws with tick + caller return address, and every `randomSeed` set — verifies the bit-exact port of `random.s` and which call sites consume the stream. Inert unset. | **live** (D284 verification) |
|
||||
| `GE_RSEED` | `port/src/random.c:134` (`#ifdef PORT`) | Test-only boot-seed override: `GE_RSEED=<hex64>` pins `g_randomSeed` at first seed instead of the wall-clock-derived value — for determinism and N64-parity tests. Inert unset. | **live, test-only** |
|
||||
| `GE_RSEED_LV` | `port/src/d318watchdog.c:232` (`#ifdef PORT`) | One-shot level-start seed log: logs `g_randomSeed` at the first tick after level start (optionally armed for a specific tick via `<hex>@<tick>`) — ties a saved/loaded game's PRNG state to a level for D318-class repro. Inert unset. | **dead** (D318 closed) — removed 2026-09-30 (ROADMAP §7 strip pass, batch 1, branch `chore/scaffolding-removal-1`); tombstone — the generator flags it GONE |
|
||||
| `GE_D318T` | `port/src/d318watchdog.c:270` (`#ifdef PORT`) | D318 watchdog timeline probe: per-tick log of the Facility ai_22 softlock signature window (c78 list/offset/attack state, c67 health) while the watchdog watches. Inert unset. | dead — fully removed 2026-10-01 (ROADMAP §7 scaffolding strip, batch 2; kept here as a tombstone) |
|
||||
| `GE_D322` | `port/src/audio.c` (`audioSetNextBuffer`), `src/snd.c` (`sndD322PoolSummary`) | D322 pool-telemetry probe: one line per 5 s of wall clock with the three pool layers a long session can exhaust — `sfx=count/max` (8-voice SFX soft cap), `evtq=n/64` (event queue occupancy; full = silent post drops, M-71), `pv free/lame/alloc` (24 physical synth voices shared by SFX + music; `-1`s before sndInit), plus the D204 pipeline fields. Cheap (~30 reads/s), campaign-safe; read together with `GE_D204=1`. | **live** (D322 capture) |
|
||||
| `GE_D318W` | `port/src/d318watchdog.c` (`d318WatchdogTick`, `#ifdef PORT`) | **The D318/D320 pin detector, ON by default, DETECT-AND-LOG ONLY since 2026-09-30.** Watches the Facility ai_22 / ai_19 and Control ai_9 hold-pose pin signatures (findings D318/D320) and logs `D318W:` lines (first detection, confirmation after 600 ticks, periodic reminders while held). It used to re-seed the attack via `sub_GAME_7F025560`; that recovery was removed because D329 fixed the cause, so a hit now means a D329 regression. `GE_D318W=0` silences it. | **live** (D329 regression detector) |
|
||||
| `GE_D294` | `src/boss.c:498` (`#ifdef PORT`) | One line per stage reset: stage number + current MA malloc value (D294 pool investigation). | **live** (D294) |
|
||||
| `GE_D294BANK` | `src/game/bg.c:605` | D294: restarts the bank-window counter on stage change (pairs with the `GE_D104` windowing). | **live** (D294) |
|
||||
| `GE_D303` | `port/fast3d/gfx_pc.cpp:1717` | D303: logs up to 200 tri emits with per-vertex bbox + w-sum (degenerate-triangle hunt). | **live** (D303) |
|
||||
| `GE_D307` | `src/game/prop.c:709` | D307: load-time dump of every CCTV record's inputs + derived orientation, for word-for-word PC-vs-ROM comparison. | **live** (D307) |
|
||||
| `GE_D318B` (+ `GE_D318B_ALL`, `GE_D318B_CLK`) | `port/src/d318bprobe.c:91,97,210` | D318/D320 boundary probe suite (M-199): per-tick ground truth of the aim-hold/derail/anim-pin state machine for env-selected chrs (`"5,9"`, max 4); `_ALL` logs every ACT_ATTACK tick; `_CLK=N` locksteps N sim ticks per rendered frame. Read-only. **TEMP — remove when D318/D320 conclude.** | **dead** (D318/D320 fixed via D329) — removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE; `port/src/d318bprobe.c` + `port/include/d318bprobe.h` deleted. NB `GE_D318B_CLK` (sim-ticks-per-frame lockstep, used for the D329 pin repro) went with the suite |
|
||||
| `GE_D329` (+ `GE_D329LOG`) | `src/game/model.c:3516,3519` (`#ifdef PORT`) | **The D329 anim-pin fix gate — ON by default** (unlike the other probes here): re-derives a stale pinned frameb when the endframe no longer blocks it. `GE_D329=0` A/B-disables; `GE_D329LOG` logs each trigger (cap 2000). Cached. | **live** (D329 fix) |
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -185,9 +62,5 @@ Two classes:
|
||||
- `[Debug]` ini keys (`Debug.FrameDump`, `Debug.InputLog`) mirror
|
||||
`GE_PCDUMP` / `GE_INPUTLOG` as a fallback (env var wins) — M-26,
|
||||
`port/src/config.c`.
|
||||
- Not GE_-prefixed but same category: `GE_DETERM` is **implemented but
|
||||
experimental — not yet achieving its determinism goal** (fixed-tick
|
||||
mode, §F D117, M-52 four-pass writeup); `GE_DETERM_TRACE` (M-52 4th
|
||||
pass) adds instrumented clock-event logging when used alongside it, for
|
||||
diffing two runs to localize remaining divergence. Both env-gated,
|
||||
default off, zero cost when unset.
|
||||
- Not GE_-prefixed but same category: `GE_DETERM` is **designed, not
|
||||
implemented** (fixed-tick determinism mode, §F D117).
|
||||
@@ -1,217 +0,0 @@
|
||||
# GEPD-inspired input overhaul — FINAL PLAN (issues #89, #90)
|
||||
|
||||
Status: **plan only** — no code changes yet. Companion to `port/src/input.c`
|
||||
(current state: D118/D165/D166/D194/D214/D223/D238 lineage).
|
||||
|
||||
## 1. The reference (no longer vendored in the public repo)
|
||||
|
||||
The GEPD Edition Mouse Injector source was vendored at `reference/mouse-injector/`
|
||||
and **removed from the public repo on 2026-09-19** (GPLv2 code + a prebuilt
|
||||
binary inside an MIT project; never compiled here). See
|
||||
`reference/mouse-injector/README.md` for provenance and how to re-vendor it
|
||||
locally if you need the original. The file that mattered was
|
||||
**`games/goldeneye.c`** (~380 lines, the whole GE aim model), plus `device.c`
|
||||
(raw mouse polling) and `maindll.h` (tickrate — the units every per-tick
|
||||
constant assumes). The model is quoted inline below and in the D194 findings;
|
||||
the ported result lives in `port/src/input.c`.
|
||||
|
||||
### What the Mouse Injector does (GE)
|
||||
|
||||
### What the Mouse Injector actually does (GE)
|
||||
|
||||
| Aspect | GEPD implementation (`games/goldeneye.c`) |
|
||||
|---|---|
|
||||
| Mouse source | Raw driver deltas via ManyMouse (`device.c`), cursor locked by periodic `SetCursorPos`. No OS pointer accel, no deadzone. |
|
||||
| **Hipfire look** | **Direct camera write**: `camx += XPOS/10 * (SENS/40) * (fov/basefov)` degrees per tick; `camy` likewise, clamped ±90 (−20 in tank). Linear in px — this is why GEPD "just feels right". |
|
||||
| **Aim mode (R)** | Crosshair *position* accumulator `crosshairpos += XPOS/10 * SENS/292`, clamped to ±`CROSSHAIRLIMIT` (5.159° = screen edge). Gun/arm pose derived per-weapon from a 33-entry offset table. **View scrolls only past 72% of the way to the edge**: `aimx = (ratio−0.72) * 475 * timestep`, scaled by `fov/basefov`. |
|
||||
| FOV coupling | Every term carries `(fov/basefov)` — scopes automatically get finer control. `basefov` = min(60, override). |
|
||||
| Tank | Separate direct write to the tank x-rotation global (`0x80036484`), different scale (`360/TANKXROT * 2.5`); crosshair X locked to 0 when driving with tank equipped. |
|
||||
| Menus | Mouse moves the menu cursor directly (`menux/y += px/10*SENS*6`, clamped to screen rect); aim button acts as B (back) in menus; wheel = weapon cycle macro. |
|
||||
| Safety gates | Injection only when `camera==4||0 && menupage==11 && !dead && !watch && !pause && !mproundend`. |
|
||||
| Extras | Crouch *toggle* mode, dedicated reload key (ROM hack — not portable to us), FOV override + viewmodel repositioning (ROM hacks — we have the clean equivalents in `port/`), intro skip on FIRE+AIM. |
|
||||
|
||||
### Already inherited (do not redo)
|
||||
|
||||
- **D194 GEPD-mirror aim** (`aimGepdCompute()`): crosshair-position model,
|
||||
±5.159° clamp, 72% edge scroll at 475/s, fov scaling, per-tick overwrite of
|
||||
`crosshair_x/y_pos` + `gun_azimuth_angle/turning`. This *is* GEPD's aim mode.
|
||||
- D223 mouse-wheel weapon cycling (A / A+Z synthesis) — mirrors
|
||||
`GE_Controller()`'s weapon macro; D214 ini key binds; click-to-lock capture;
|
||||
F10 options overlay with aim/turn sensitivity sliders; D194(b) dt-decoupled
|
||||
look; menu pointer via direct `cursor_h/v_pos` writes (mirrors GEPD's
|
||||
`menux/y` writes).
|
||||
|
||||
### Remaining inheritances (this plan)
|
||||
|
||||
GEPD's *hipfire* is a direct linear camera write — we still route hipfire
|
||||
through the N64 stick curve (#89). Also un-inherited: raw-input-by-default,
|
||||
crouch toggle, GEPD-style safety gates on our always-live look paths, and its
|
||||
sensitivity/FOV coupling discipline. That's WIs 1–3 + 6 below.
|
||||
|
||||
### Reference split (GEPD vs PD port)
|
||||
|
||||
Two references, two jobs (decided during #89/#90 triage):
|
||||
|
||||
- **GEPD Mouse Injector** (was `reference/mouse-injector/`, removed from the
|
||||
public repo 2026-09-19 — see its README stub) = *what the mouse does to the
|
||||
camera*. The only reference with GE-specific knowledge (camera fields,
|
||||
crosshair limits, tank case, safety gates). Primary for WI-1.
|
||||
- **PD PC port checkout** (standing reference, see AGENTS.md) = *how a PC game
|
||||
presents and captures input*: cursor-lock behavior, raw input handling,
|
||||
sensitivity units/scaling, and the shipped in-game options/keybind UI.
|
||||
Primary for WI-2 conventions and WI-5's menu design. PD knows nothing about
|
||||
GE — never use it for camera math.
|
||||
|
||||
## 2. Issue #89 — "mouse movement is not uniform; slow motion barely registers"
|
||||
|
||||
### Diagnosis (high confidence)
|
||||
|
||||
The complaint is the **hipfire** path. Today hipfire converts mouse px into an
|
||||
*analog stick value* (`sx += (int)(hipEdx * hipSens * MOUSE_TURN_GAIN)`), and
|
||||
the game then runs that stick through its own N64-era natural-turn response —
|
||||
a **quadratic-ish curve with a soft floor** (D238: "saturates the game's
|
||||
quadratic natural-turn curve at ~13 px/poll"). Consequences, exactly as the
|
||||
reporter describes:
|
||||
|
||||
1. Slow mouse motion → small stick values → squared-off tiny turn rates →
|
||||
"almost unrecognised".
|
||||
2. Fast motion saturates the curve at full 315°/s — so the usable band is
|
||||
narrow and the feel is non-uniform (bang-bang-ish).
|
||||
3. `(int)` truncation of the stick adds a quantized dead zone on top.
|
||||
|
||||
GEPD/PD-PC never hit this because **they write the camera angles directly** —
|
||||
the stick curve is bypassed entirely, and sensitivity is linear in px with
|
||||
FOV scaling. We already do exactly that in aim mode (D194); hipfire is the one
|
||||
leftover N64-stick-shaped path.
|
||||
|
||||
Secondary contributors worth folding in:
|
||||
|
||||
- `Input.MouseRawInput` defaults to **0** — on X11/Wayland (the #90 reporter
|
||||
is on Linux) OS pointer acceleration still shapes relative deltas; GEPD
|
||||
reads the raw driver. Windows is largely unaffected.
|
||||
- Sensitivity is spread over 4 knobs (`MouseSensitivity`, `MouseTurnSpeed`,
|
||||
`AimModeSens`, legacy `MouseAimSpeed`) — the reporter "tried adjusting
|
||||
ge007.ini" and couldn't find the one that mattered for hipfire.
|
||||
|
||||
### Work items
|
||||
|
||||
**WI-1 (core fix): direct camera writes for hipfire look.**
|
||||
Mirror GEPD's hipfire branch in `input.c`.
|
||||
|
||||
*Pre-flight:* skim the PD port's `port/src/input.c` mouse path first and match
|
||||
its conventions (sensitivity units, dt handling, focus-loss behavior) so the
|
||||
result feels like PD PC rather than a reinvention. GEPD supplies the GE camera
|
||||
math; PD supplies the UX shape.
|
||||
|
||||
Implementation:
|
||||
|
||||
- New path `hipDirectCompute()` next to `aimGepdCompute()`: convert this
|
||||
poll's dt-scaled px to degrees using the *current* viewport
|
||||
(deg/px = fovy/viewH, or hfov/viewW — reuse whatever D194 absolute aim used),
|
||||
scale by `MouseTurnSpeed * MouseSensitivity`, and add straight to
|
||||
`g_CurrentPlayer->vv_theta` / `vv_verta` (clamp ±90). Same confinement as
|
||||
today: only when `idx==0`, cursor captured, in a running stage.
|
||||
- **GEPD's safety gates** must be ported over (aim mode got away without them
|
||||
because it requires RMB held; hipfire is always live): check the decomp
|
||||
equivalents of GEPD's `camera==4||0 && menupage==11 && !dead && !watch &&
|
||||
!pause` before writing. `menupage` → `current_menu` (already have); the rest
|
||||
are `player`/game globals — read-only, no logic change.
|
||||
- **Tank**: GEPD writes a separate tank-rotation global with its own scale and
|
||||
locks crosshair X to 0 when driving with the tank equipped. Find the decomp
|
||||
symbol for `0x80036484` (tank.c) and mirror it; if too fiddly, phase 2 —
|
||||
tanks are rare in single-player.
|
||||
- Keyboard turn (arrows → stick) keeps the existing stick path; only mouse px
|
||||
goes direct. Escape hatch: `Input.MouseDirectLook=1` default, `0` reverts to
|
||||
the current stick path (kept as-is).
|
||||
- This makes hipfire and aim mode share one linear mental model: px → screen
|
||||
angle, FOV-scaled, dt-normalized. Expect to then simplify or delete the
|
||||
D166/D238 curve-tuning knobs that become inert (`MouseAimCurve`, `AimBand`,
|
||||
legacy `MouseAimSpeed`) — *after* playtest, and keep the config keys as
|
||||
no-op aliases so existing inis don't break.
|
||||
|
||||
**WI-2: raw input by default.** Flip `Input.MouseRawInput` default to 1 (SDL
|
||||
hints already handled in `inputInit`). Verify Windows relative-mode deltas are
|
||||
unaffected; this mainly fixes Linux feel. Keep the escape hatch.
|
||||
|
||||
**WI-3: one honest sensitivity number.** With WI-1, hipfire and aim both scale
|
||||
off px→degrees, so `MouseSensitivity` becomes genuinely master. Add it to the
|
||||
F10 overlay (see WI-5), document the knob hierarchy in the generated ini
|
||||
header comment, and re-calibrate defaults against GEPD's SENS≈40 ≈ 1:1
|
||||
screen-angle mapping (our current 38/50/100 trio should land close; measure,
|
||||
don't guess).
|
||||
|
||||
**Verification:** `GE_INPUTLOG` linearity check — log px-in vs deg-out at slow
|
||||
and fast speeds (ratio must be constant); headless script harness for the
|
||||
stick-fallback path; manual playtest checklist (hipfire slow/fast, aim mode,
|
||||
scope zoom, tank, menu, death/cutscene safety gates). New finding `Dxxx` in
|
||||
`docs/dev/findings.md`, index row added.
|
||||
|
||||
## 3. Issue #90 — in-game key bindings + "ini binds don't work" + sensitivity menu
|
||||
|
||||
### Diagnosis candidates for "binds don't work properly" (repro before fixing)
|
||||
|
||||
1. **Not a bug, GE design:** in-game, a fresh A edge *without* Z held is the
|
||||
weapon-cycle-forward signal (D223 documents `weaponForwardOffset`). The
|
||||
default Action bind includes E — so "pressing E changes weapon" is the
|
||||
game's own behavior, and "Escape reloads/activates switches" is the default
|
||||
Cancel=Escape → B button. The user's mental model (PC: E = interact)
|
||||
collides with GE's A/B semantics. Address via documentation + possibly a
|
||||
PC-idiomatic default set (product decision; GEPD itself uses Q=A, E=B).
|
||||
2. **Ini syntax footgun:** keys must be written as `Input.Bind.Action=E` (or
|
||||
`Bind.Action=E` under `[Input]`). A bare `Action=E` under `[Input]` resolves
|
||||
to `Input.Action`, which is unregistered and silently ignored. `configLoad`
|
||||
also ignores unknown keys with no warning — add a log line for unrecognized
|
||||
keys so users get feedback.
|
||||
3. **Key-name format:** binds use SDL scancode names (`"Left Ctrl"`, `"Up"`,
|
||||
`"Space"`); nothing documents this in the ini. Add per-key comments in
|
||||
`configSave` output and/or a `# valid names: ...` header.
|
||||
4. There may still be a real parse bug lurking (`strtok` on the seeded buffer,
|
||||
constructor ordering vs `configLoad`) — **repro first**: write an ini with
|
||||
`Input.Bind.Action=E`, launch, confirm via `GE_INPUTLOG` that E alone
|
||||
produces A. Fix whatever actually breaks; don't fix hypotheticals.
|
||||
|
||||
### Work items
|
||||
|
||||
**WI-4: keybind audit + ini ergonomics.** Repro (above), fix real bugs, add
|
||||
unknown-key warnings, document scancode names in the saved ini. Cheap, do first
|
||||
— it unblocks #90's half regardless of the UI work.
|
||||
|
||||
**WI-5: F10 overlay — bindings section + master sensitivity.**
|
||||
Model the UX on the PD port's shipped in-game options menu (standing reference
|
||||
checkout) where anything is player-facing. `optionsoverlay.c` already has
|
||||
row/hit-test/scroll infrastructure and owns all input while open; what's missing:
|
||||
|
||||
- A new row kind `ROW_BIND`: label = action, value = first bound key name;
|
||||
click → "press a key…" state that captures the *next* scancode (keyboard or
|
||||
mouse button), writes it into `g_bindStr[a]` (replacing or appending —
|
||||
decide: GEPD/PD use one primary + optional secondary; simplest v1 = replace
|
||||
single key, keep multi-key only for ini power users), then `inputRebuildBinds()`
|
||||
+ persist via the existing config save path.
|
||||
- Expose `Input.MouseSensitivity` (master), and after WI-1 trim the panel to
|
||||
master + per-mode fine-trim + invert Y (drop inert legacy rows).
|
||||
- Bind capture must swallow all input while armed (overlay already does this)
|
||||
and handle ESC = cancel, Backspace/Delete = clear.
|
||||
|
||||
**WI-6 (nice-to-have, from GEPD): crouch toggle.** GEPD's `CROUCHTOGGLE` state
|
||||
machine (`GE_Crouch`) is ~20 lines and pure port-side (stance flag write).
|
||||
Low priority; include only if WI-1..5 land cleanly.
|
||||
|
||||
## 4. Suggested order
|
||||
|
||||
1. **WI-4** (keybind repro/fix + ini docs) — small, unblocks #90 partially.
|
||||
2. **WI-1** (direct hipfire) + **WI-2** (raw input default) — the #89 fix;
|
||||
playtest-heavy, needs the safety gates done right.
|
||||
3. **WI-5** (overlay binds + master sens) — completes #90.
|
||||
4. **WI-3** (sensitivity consolidation/cleanup) — after WI-1 defaults settle.
|
||||
5. **WI-6** (crouch toggle) — optional garnish.
|
||||
|
||||
## 5. Constraints reminders
|
||||
|
||||
- All work is `port/`-only: direct struct writes are the established D194
|
||||
pattern (read/write live game state from the input poll, no logic change).
|
||||
Any temptation to "fix" the natural-turn curve in `src/game` = rule-2 sign-off
|
||||
territory — don't.
|
||||
- Keep every removed/renamed ini key registered as an alias so shipped inis
|
||||
keep loading; log deprecations at startup.
|
||||
- Findings: new `Dxxx` entries for WI-1 (hipfire direct look), the #90 bind
|
||||
repro outcome, and the overlay bind UI; update `findings-index.csv`.
|
||||
@@ -0,0 +1,39 @@
|
||||
# GRAPHICS-BACKLOG — known cosmetic / rendering issues
|
||||
|
||||
Deferred below level-progression and crash work (user priority, M-11).
|
||||
Minor graphical glitches are acceptable in the current state. Fix these
|
||||
once the game is playable start-to-finish without crashes.
|
||||
|
||||
## Open
|
||||
|
||||
| ID | Symptom | Status / notes |
|
||||
|----|---------|----------------|
|
||||
| ~~**D114/D116**~~ | ~~HUD text + ammo digits render each glyph individually X-mirrored~~ | **CLOSED — NOT A BUG (M-33, §F D168).** The `GE_PCDUMP` PPM writer never reversed `glReadPixels` rows, so every capture was upside-down; "mirrored" was a misread of inverted asymmetric content. The game renders correctly on hardware (developer-confirmed). Writer fixed (`gfx_opengl.cpp`). "Every stage verified non-mirrored" was correct — there was no mirror. |
|
||||
| ~~**D74 wrap-block**~~ | ~~`gfx_pc.cpp:1546/1551` PORT block is dead code (guard `cms & G_TX_WRAP` always false; indexes per-texunit arrays with the vertex index → OOB if reached).~~ | **RESOLVED (M-30, RC3/D167).** The in-vertex-loop wrap block was reworked and hoisted to a per-texunit pre-wrap window (`gfx_pc.cpp` ~1574, now `cms`-correct and indexed by texunit not vertex), behind the `Video.WrapFix` knob (default OFF). No longer dead code or OOB. See §F "RC3 · D167". |
|
||||
| **D75** | ~~Nintendo logo mispositioned; gun-barrel Bond model absent~~; cast-roll character models — status unknown. (Rareware logo OK.) | **Shrunk (M-33, upright captures after D168).** The "overlaps D114 shared mirror" note is withdrawn. **Gun-barrel Bond RENDERS correctly** (walk + fire, upright) — the "absent" reports were the flipped capture. **Nintendo logo IS still broken** — renders as two overlapping white ellipsoids shifted left, no wordmark/geometry (a real `logoinst` model transform/geometry bug, not a flip). Cast roll not re-captured — recheck with `GE_PCDUMP="1400-2200:25"`. `findings.md` §F "D75 Bug 2 — M-33 UPDATE". |
|
||||
| **D76** | 2D disclaimer screen only partially drawn. | §F D76. |
|
||||
| **D77** | No audible music on PC. | Phase 3 (audio). §F D77. |
|
||||
| ~~**D116 (HUD)**~~ | ~~proportional-font strings + ammo counter mirrored~~ | **CLOSED — NOT A BUG (M-33, §F D168).** Same capture-orientation artifact as D114. |
|
||||
| **D148** | Level-end cinematics don't play. Dam: hitting the exit trigger should show Bond rappelling down the dam (the bungee jump); instead it cuts straight to the mission-complete report. **Broader (M-49 full playthrough): cutscenes are buggy in many places across the game**, not just the Dam ending — the user reports frequent cutscene glitches (skipped, wrong camera, misplaced/hovering actors, timing). Treat D148/D160 as the tracking entry for the whole cutscene-reliability problem, not just Dam. | Scripted cutscene / cinematic-camera + Bond rappel anim. Found M-27 during the first full Dam→Facility playthrough. Progression is unaffected — the report and next briefing load fine. The widespread nature (M-49) points at a systemic cause: the propDef/`sizepropdef` stride desync (D122/D132 family) that misreads `CutsceneRecord` lookups, and/or D75 anim-model + D173 actor-position bugs showing up wherever a cutscene puts a character on screen, and/or the D155/D156/D193 timing family driving cutscene camera/anim pacing. **M-31 (§F D160):** static-traced the Dam end ailists (`ai_24`→`ai_17`, `camera_switch`→`CutsceneRecord` type-46 lookup). Diagnostic `GE_D160=1` shipped (`c95713f5`) — user runs Dam to the exit with it set; the trace pins whether the cutscene AI runs, and whether the propDef-index walk (D122/D132 family) hands `camera_switch` a bogus cutscene record. Leading hypothesis: propDef `sizepropdef` stride desync (medium confidence), not D75 anim-model. |
|
||||
| **D149** | Front-end MISSION COMPLETE / mode-select 3D models (dossier, wallets) render as garbled full-screen geometry or not at all — one of the compiled sub-DLs (seg5+0x9ee4) is malformed, D146 now skips it instead of aborting. | D75 family. §F D146. |
|
||||
| **D170** | Named-NPC "flee" scripted behavior looks wrong on PC: on Silo, Ourumov breaks into a full run the instant he alerts ("Ourumov: Kill him!") rather than the N64 walk/retreat; developer reports Trevelyan behaves the same way on Cradle. **M-49 full playthrough: escalated — Cradle (`-level_51`) is "bugged out completely" from Trevelyan's behavior; the final level's scripted progression breaks. Now folded into / superseded by §F D193 (NPC AI locomotion too slow).** | **Observed, not investigated** (M-34, from a Silo playtest capture; user bug report — no fix wanted yet). Could be a genuine ailist / `chrTick` divergence, OR the D155/D156 wall-clock `deltaFrames` locomotion-anim speed-up (a catch-up spike playing the walk cycle at 2–3×). **Verify first:** capture the same beat against N64 reference footage before assuming it diverges — Ourumov may be scripted to flee. If it's real, suspect the frametiming clamp (§F D155/D156) or the Cradle-family AI path (§F D123). |
|
||||
| **D171** | Silo missile-shaft: a large translucent grey triangle sweeps across the lower frame while panning down the shaft (~3 s of the capture). Looks like a stray poly / near-plane clip fan, same family as D106 (portal near-plane min>max). | **Observed, not investigated** (M-34, Silo capture). Cosmetic, intermittent, tied to camera angle in the open vertical shaft. |
|
||||
| **D172** | Bullet-impact / blood particle spray renders magenta or cyan instead of dark red (visible on guards hit at close range on Silo). | **Observed, not investigated** (M-34, Silo capture). A channel-order / CC-mode error in the particle-sprite path (magenta = R+B, cyan = G+B) — not the D64/D120 blood-decal family (those are crash/hang + missing decals, not color). Likely a texture-format or env-color decode in `blood_animation.c` / the fast3d sprite path. |
|
||||
| **D178** | Objectives not listed correctly on the pre-mission briefing screen (the mission overview shown before starting a level). | **M-36 triage: the objective lines are BLANK, not wrong.** `GE_STARTMENU=10` Dam briefing renders the header fine ("OHMSS / Agent: James Bond / Mission 1: Arkangelsk / Part i: Dam / PRIMARY OBJECTIVES:") then just the bullet "a." with no text and no b/c/d. → **D143/D151 blank-text family** (`langGet()` → NULL → silent blank), *not* a propDef walk / difficulty decode. **Family B** in `docs/dev/M-36-TRIAGE.md` — find which lang bank the front-end briefing flow fails to load. Re-confirm the exact shape in a *real* briefing (the `GE_STARTMENU` boot skips real save state). Close relatives: D151 (watch objective text blank — type-35/23 `text` slot u16→u32 fix, M-28; interactive re-verify was still pending), D143 (briefing/objective text blank via `langGet` NULL), D157 (objective difficulty byte offset — could make difficulty-gated objectives appear/disappear wrongly). The pre-start briefing is a *different surface* from the in-level watch, so D151's fix may not cover it. **Next:** ask user which level + what exactly shows vs expected; compare against `docs/dev/LEVEL-OBJECTIVES.md` (regenerated post-D157); if lines are blank → D143/D151 family (`langGet` NULL guard = silent blank), if wrong objectives listed → propDef type-23 record walk / difficulty decode. |
|
||||
| **D175** | In-game stutter / brief hang during normal play — observed opening a door on Runway (mission 3, `-level_35`) and also on Surface. No backtrace captured (gdb attach missed the window; it self-recovered). | **Observed, not investigated** (user QA report). Matches known transient-hang classes rather than looking new: D155 (post-slow-load catch-up spiral — clamped), D156 (anim NaN loop — guarded, prints one-shot stderr diag if it fires), D134 (SP/DP task-done event swallowed — fixed M-22, Runway was an original victim), D147/D152 (audio `s_imLock` steal = ~2 s hiccup). Door-open specifically = new room first visible → one-off texture-import/frame spike (likely benign). **Next:** reproduce under `tools_pc/debug.ps1` with the box idle; check console for the D156 stderr diag; if it recurs, attach gdb mid-hang and pattern-match `bt` (import_texture = benign slow frame; model.c:3131 spin = D156; pthread_mutex_lock on s_imLock = D147/D152). |
|
||||
| **D176** | Surface exterior renders wrong (two co-located defects). **(a)** Sky is solid **black** — the skybox / sky gradient does not draw at all (reference: warm sunset cloud gradient). **(b)** The perimeter **tree-line backdrop renders as grey TV-static / comb interlacing** instead of snow-laden pines — the geometry is present and roughly the right silhouette but every tree texture is scrambled noise. Ground, tunnel and the dish/bunker building draw fine. | **Observed, screenshots committed** (M-35, user QA; `docs/img/bugs/d176-surface-exterior-our-build.png` vs `-reference.png`). Level is the shared Surface exterior — user reached it Dam→Surface on Agent so this is **Surface (i) `-level_36`** (confirm it also affects `-level_43`). **(a)** black sky: **M-36 partial (findings.md "D176(a)").** Env data is correct (`Sky.Clouds=1`, `CloudRGB=240,120,30`, `SkyImageId=0`); `skyRender` runs the cloud path and classifies corners correctly (switch case 12), but `skyRenderTri`/`skyRenderFull` emit nothing — not even the `env->RGB` fill shows. Not root-caused: next is probing `sub_GAME_7F097388` projected coords, verifying `skywaterimages[0]` resolves on PC (`image_bank.c` GIMG offset `0xFB4`), and a fast3d sky-tri trace. `GE_D176=1` probe shipped. **M-37: ROOT-CAUSED** — `skyRenderTri`/`skyRenderFull` build geometry *exclusively* as `G_RDPHALF_*` immediates, which `gfx_pc.cpp` deliberately no-ops (identical no-op in the PD port); fix = decode the RDPHALF tri-raster stream in fast3d (~1 session, genuine new code). **M-42: `-level_22` (Statue) confirmed a clean headless repro** — bare boot, frame ~360, upper half pure black; use it to verify the decoder. `-level_36`/`-43` intro cams face down (not a sky test without `GE_INPUTSCRIPT`). **(b)** tree/rock static: **M-36 triage — NOT the D159 family.** `texSwapAltRowBytes` is a full `#ifdef PORT` no-op (`image.c:2199`), it cannot cause this. Real cause: `port/fast3d/gfx_pc.cpp` `import_texture_*` assume `line_size_bytes == full_image_line_size_bytes` (`SUPPORT_CHECK`; rgba16's is commented out *"trips ... with a garbage size"* `:658`) → non-PoT / sub-rect tiles get a flat linear read → diagonal row shear = grey static. **Family A** in `docs/dev/M-36-TRIAGE.md`. Fix: honour `line_size_bytes` as the real source pitch. Same defect as D182(2). |
|
||||
| **D174** | "No blood effect" — user reports no visible blood when shooting guards / into surfaces. | **User QA report; likely D120, unverified.** Decals: the D120 `PointUsage[]` converter fix (M-30) was never interactively verified and only takes effect after a full sidecar regen (`d43 && d69 && d88 --regen`) — check whether the playtest build had regenerated sidecars before concluding the fix failed. Spray: D172 observed the particle spray *drawing* (wrong color, magenta/cyan) on Silo — if the user sees no spray either, that's new info (different weapon/range, or the D172 decode is worse than "wrong color"). First step: BUNKER1 firefight with regenerated sidecars; if decals still absent, instrument the `chr.c:3328` decal walk. |
|
||||
| **D173** | Third-person Bond model (the player-representing figure shown at level start) spawns too high — floats well above the ground "a lot of the time" while the actual player spawn is correct. Same symptom on the Cuba end credits: JB stands ~6 ft in the air above Natalya. | **Confirmed still present across the full 21-level playthrough (M-49).** **Observed, not investigated** (user QA report). D75 animated-model family but a *position* defect, not absence — M-33 confirmed the gun-barrel Bond renders fine upright. Suspects: (a) intro/credits puppet chr spawn position vs the model's base transform; (b) anim root-motion accumulation (`modelSetAnimFrame2WithChrStuff`, D156 area) offsetting the rendered joints above the body origin; (c) a `coord3d`/packed-float decode in the intro camera/model setup. "Player spawning is OK" localises it to the render-side model, not `g_CurrentPlayer` state. |
|
||||
| **D182** | SELECT FILE screen degrades after navigating away and back (main menu → file select, or backing out of file select and re-entering). On the fresh screen the 007 gun-barrel spiral / Bond silhouette renders behind the file boxes; after a round-trip **the Bond image stops drawing entirely** and the whole background gains the **comb / interlacing** artifact (horizontal line scramble). File boxes, text and the Copy/Erase icons still draw. | **Observed, screenshot committed** (M-35, user QA; `docs/img/bugs/d182-file-select-backout-20260902.png`). Two symptoms, likely linked: **(1)** background 3D image stops rendering after re-entry → a front-end model/DL whose `render_pos` / segment binding is only valid on first construction (D149 / D75 `constructor_menuXX` family — the dossier/wallet models have the same "renders once" behavior). **(2)** interlacing = **M-36 triage: Family A**, the `import_texture_*` `line` vs `full_image_line` shear (see D176(b) / `docs/dev/M-36-TRIAGE.md`) — **not** the D159 `texSwapAltRowBytes` path (that is a full PORT no-op). Open question: why only *after* a round-trip — first import may land `line == width`, re-import may not, or the tile descriptor is stale on re-entry. Repro: `GE_STARTMENU` to file-select, back out with ESC/B, re-enter; `GE_DTEX` on the spiral texture across both visits. Shares defect (2) with D176(b). |
|
||||
| **D197** | Character face/head textures wrap wrong — the face texture repeats around the side of the head "and continues weird" instead of clamping to the front. Seen on **Silo** (M-49 playtest), intermittent ("sometimes"). Reads as **`G_TX_CLAMP` not honoured** for the character-skin tile in fast3d (→ `GL_REPEAT`, so the face UV region tiles around the skull), or wrong `shifts`/`shiftt` / LOD-tile selection on the head texture, or the D167 non-PoT wrap-period issue (head textures are often non-PoT) hitting skinned models. | **Observed, not investigated** (M-49, user bug report — needs a capture + which chr/level). Distinct from D176(b)/D182(2) (those are *bg wall* pitch shear). Related: RC3/D167 (`Video.WrapFix`, non-PoT wrap period) + the `texWriteTileLods` `shifts=shiftt=lod` note in findings §F "D176(b) → 4. candidate 2". First checks: does `gfx_pc.cpp` map N64 `cms/cmt` `G_TX_CLAMP` → `GL_CLAMP_TO_EDGE` for character DLs; is the head tile getting the base tile vs an LOD tile (D107); try `Video.WrapFix=1` on a repro. |
|
||||
| **D195** | Transparency / alpha-blended surfaces render wrong — reported on **Control** (Severnaya bunker basement — Natalya's console room; heavy on glass partitions and the translucent projected/holographic wall displays). Exact failure mode not yet pinned by the reporter (M-49 playtest): candidates are alpha-blended geometry drawing fully opaque, translucent panels drawing invisible, or draw-order/sort so translucent surfaces occlude what's behind. | **Observed, not investigated** (M-49, user bug report — needs a `GE_PCDUMP` capture + which surfaces). fast3d render-mode / combiner path. Same neighborhood as D161 (CI8 tile + `G_TT_NONE` → garbage), D172 (particle-sprite channel order), D176(b) (texture pitch shear) — but this is a *blend/alpha* defect, not a texture-decode one. Note D128 already touched Control's `specialportalarray` (portal struct stride) — check whether the affected surfaces are portal-adjacent. First checks: does `gfx_pc.cpp` honour `G_RM_*` XLU render modes + `G_AC_THRESHOLD`/`G_CC` alpha for these DLs; is `gDPSetRenderMode` `w1` being parsed (cf. findings §F "zbuf-clear w1 scribble"); is Z-write-vs-blend ordering right for XLU surfaces. Get the reporter to say: opaque-where-should-be-clear, invisible, or wrong edges? |
|
||||
| **1P weapon viewmodel** | In-level first-person weapon models don't draw at all (no gun visible while playing). | Already documented — **AUDIT-M6 #5** (`docs/dev/AUDIT-M6-player-offsets.md`, "prime suspect") + §F D115 "still open (MED)"; M-8 handoff listed it NOT STARTED with a plan: instrument `gunfire.c:600-661` setup + `field_87F` gating first, then check `weaponModel.render_pos` arena lifetime and `weaponRwPool[192]` sizing. Note M-32b's "render_pos/dynAllocate RULED OUT" probe was the **front-end gun-barrel** path (`title.c`), not this in-level path — candidate #5 still stands. Distinct from the watch-page "dark/faint weapon model" (D75, D141 note). |
|
||||
|
||||
## Resolved (for reference)
|
||||
|
||||
- D70–D74: intro-logo pixels (Rareware logo renders correctly).
|
||||
- D103: viewport / native-resolution half-frame.
|
||||
- D105: room geometry Z-buffer never cleared (`G_CLEAR_DEPTH_EXT`).
|
||||
- D106: sky-void through doorways (portal near-plane min>max).
|
||||
- D107: blurry ceilings/wall-panels (LOD mip tile fabrication).
|
||||
- D112: skeletal models were "3D lines" (`d43_emit.py put_f32` byte order).
|
||||
@@ -1,7 +1,5 @@
|
||||
# LEVEL-PLAYTEST — WS6 human completion-validation checklist
|
||||
|
||||
> **Historical record — do not act on status claims in this file.** This checklist predates the v0.4.0 input model (D332–D337) and the campaign sign-off; use it as a template only. Current open work and status: [`docs/ROADMAP.md`](../ROADMAP.md) (2026-09-29).
|
||||
|
||||
Run **after** `docs/dev/LEVEL-STATUS.md` is all-21 load+render PASS. This is
|
||||
the part that needs real input — a human plays each level start to finish
|
||||
and confirms it matches retail. Findings feed a WS5-style triage round
|
||||
@@ -49,7 +47,7 @@ Objective status: `src/game/objective_status.c`
|
||||
- Notes / defects:
|
||||
```
|
||||
|
||||
## The 20 solo missions (+ ending sequence; mission order — numbers = `-level_XX`)
|
||||
## 21 solo levels (mission order — numbers = `-level_XX`)
|
||||
|
||||
| # | Level | -level | Briefing asset |
|
||||
|---|---|---|---|
|
||||
@@ -73,14 +71,7 @@ Objective status: `src/game/objective_status.c`
|
||||
| 18 | Cradle | 41 | UbriefcradZ |
|
||||
| 19 | Aztec | 28 | UbriefaztZ |
|
||||
| 20 | Egypt | 32 | UbriefcrypZ |
|
||||
| — | *Ending sequence* ("Cuba") | 54 | not a playable mission — cast/credits stage (D129) |
|
||||
| 21 | Cuba | 54 | (post-Egypt unlock) |
|
||||
|
||||
(Mission table: `front.c`, `mission_folder_setup_entries` — exactly 20
|
||||
playable missions; the game's own enum says it, `SP_LEVEL_MAX // 20` in
|
||||
`bondconstants.h`). "Cuba" (`-level_54`, `LEVELID_CUBA`) is **not** a
|
||||
selectable mission: after the final mission the front end jumps straight to
|
||||
it for the extended cast display + credits scroll
|
||||
(`front.c` `selected_stage = LEVELID_CUBA` → `do_extended_cast_display(TRUE)`;
|
||||
`bondviewRenderCredits`, D129). Older docs and sweep logs count it as a 21st
|
||||
"level" — the campaign is 20 missions; "21/21" sweep results mean 21 stage
|
||||
boots, of which 20 are missions.)
|
||||
(Mission table: `front.c:433`. Cuba = `LEVELID_CUBA`, not in the standard
|
||||
folder list — unlocked after Egypt.)
|
||||
@@ -1,12 +1,4 @@
|
||||
# LEVEL-STATUS — level load+render sweep (WS4) — 20 solo missions + ending sequence
|
||||
|
||||
> **Historical record — do not act on status claims in this file.** The per-level sweep below predates the 2026-09-28 full-campaign sign-off; its open items (re-playthrough gate, M-49 completability, Cuba crash) are resolved. Current open work and status: [`docs/ROADMAP.md`](../ROADMAP.md) (2026-09-29).
|
||||
|
||||
**Counting note:** the campaign is **20 playable missions** (`SP_LEVEL_MAX //
|
||||
20` in `bondconstants.h`; `mission_folder_setup_entries` in `front.c` has
|
||||
exactly 20 entries). The sweep's 21st boot target, "Cuba" (`-level_54`,
|
||||
`LEVELID_CUBA`), is the end-of-campaign cast/credits stage (D129), not a solo
|
||||
mission — so "21/21 PASS" below means *21 stage boots*, 20 of them missions.
|
||||
# LEVEL-STATUS — 21 solo-level load+render sweep (WS4)
|
||||
|
||||
Method: bare `./build-pc/ge007.x86_64.exe -level_XX` (per-level `-m*` pools
|
||||
auto-injected, D121), `GE_PCDUMP="80-260:40"`, ~24 s watchdog then
|
||||
@@ -15,18 +7,6 @@ auto-injected, D121), `GE_PCDUMP="80-260:40"`, ~24 s watchdog then
|
||||
0x140000000). "render %" = `tools_pc/pixcount.py` non-clear on the last
|
||||
captured frame. Binary at `f2beae4b` + this session's build.
|
||||
|
||||
**M-79/M-81 catch-up note (added M-91, since these fixes were never given a
|
||||
LEVEL-STATUS entry of their own).** Both crashers below (D191) and the
|
||||
AI-pacing bug (D193) that blocked Cradle were subsequently root-caused,
|
||||
fixed, and playtest-verified: D191 in M-79 (user replayed both trigger
|
||||
scenes — Bunker ii first-guard kill, Statue post-Trevelyan cutscene — neither
|
||||
crashes; **21/21 solo levels crash-free**), D193 in M-81 (user-verified on
|
||||
Cradle itself — Trevelyan and the guards run at correct pace; root cause was
|
||||
D209). **No known crashing levels as of M-91.** What's still open is a full
|
||||
21-mission end-to-end re-playthrough confirming completability on the
|
||||
current build (the "v0.2.0 gate" in `docs/HANDOFF.md`) — the M-49 playtest
|
||||
below predates both fixes and should not be read as current.
|
||||
|
||||
**M-49 (2026-09-04) — full-campaign playtest, v0.1.0 win64 bundle.**
|
||||
User played every solo mission on the packaged build (fresh save). **19 / 21
|
||||
completable start to finish.** Only two crashes in the whole game — **Bunker ii
|
||||
@@ -75,7 +55,7 @@ cause. Only remaining crash:
|
||||
900. This is the **known D129 residual**: the bare `-level_54` boot
|
||||
reaches the cast/credits scroll referencing a text bank the real
|
||||
front-end flow would have loaded. Not a real-flow blocker; Cuba loads +
|
||||
renders 300+ frames fine. Parked with D76 (since fixed as D164).
|
||||
renders 300+ frames fine. Parked with D76 (`docs/dev/GRAPHICS-BACKLOG.md`).
|
||||
|
||||
| Level | # | Status | Crash site | Class |
|
||||
|---|---|---|---|---|
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# Linux port — status & gap analysis
|
||||
|
||||
Session M-38 (2026-09-02). Goal-2 on the roadmap (after the Windows alpha /
|
||||
release artifact). This is a scoping + first-patch pass; **nothing here has run
|
||||
on a Linux machine yet.**
|
||||
|
||||
## Method
|
||||
|
||||
Read `build-pc.sh`, `CMakeLists.txt`, `docs/building.md`, and grepped
|
||||
`port/src`, `port/fast3d`, `port/include` for Windows-only headers / Win32 APIs
|
||||
/ unguarded `_WIN32` branches. A local Qwen `triage` pass seeded the hunt; every
|
||||
claim below was re-checked against the real files (the 27B was wrong on two of
|
||||
three headline items — see "Ruled out").
|
||||
|
||||
## What is already Linux-ready (verified by reading)
|
||||
|
||||
- **`port/include/platform.h`** — sets `PLATFORM_LINUX` / `PLATFORM_MACOS` /
|
||||
`PLATFORM_WINDOWS` cleanly.
|
||||
- **`CMakeLists.txt`** — `find_package(SDL2 REQUIRED)`, `find_package(ZLIB
|
||||
REQUIRED)`, and `else()` branches already present for the non-Windows case:
|
||||
`GL_LIBRARY = GL` (l.291), `EXTRA_LIBRARIES = stdc++ m dl pthread` (l.304),
|
||||
`elseif(UNIX)` platform block (l.86). `WIN32_EXECUTABLE` and the MinGW zlib
|
||||
fallback are correctly `if(WIN32)` / `if(MINGW)` guarded.
|
||||
- **`build-pc.sh`** — already documents the Linux apt deps
|
||||
(`cmake libsdl2-dev zlib1g-dev libgl1-mesa-dev`) and is a plain bash script,
|
||||
no MSYS-isms in the executable body.
|
||||
- **`port/src/system.c`** — `sysSleep()` has a `nanosleep()` `#else` branch
|
||||
(l.76-82); `<windows.h>` is `PLATFORM_WINDOWS`-guarded.
|
||||
- **`port/src/libultra.c`** — the OS-thread kernel has a real pthreads branch
|
||||
next to the Win32 `_beginthreadex` one.
|
||||
- **`port/src/crash.c`** — `MessageBoxW` / `SetThreadPriority` / `__debugbreak`
|
||||
all `_WIN32`-guarded, with `PLATFORM_LINUX` counterparts.
|
||||
- **`port/src/video.c`** — `GE_MKDIR` has both `_mkdir` and `mkdir(p,0777)`.
|
||||
- No `__declspec`, `#pragma comment`, or hardcoded `\`-path / drive-letter
|
||||
usage anywhere in `port/`.
|
||||
|
||||
## Ruled out (Qwen triage false positives)
|
||||
|
||||
- ~~`system.c:75` `Sleep()` has no POSIX branch~~ — it does (`nanosleep`).
|
||||
- ~~CMake needs `else()` branches for zlib/pthread on Linux~~ — already there.
|
||||
|
||||
## The one real code gap — FIXED this session (verification owed)
|
||||
|
||||
**`port/src/romdata.c`** — the fixed-address cart map (`0x10000000`, so absolute
|
||||
ROM-asset symbols are live host pointers) had a **Windows-only** implementation
|
||||
(`VirtualAlloc`); the `#else` branch was a stub that logged "not implemented on
|
||||
this platform" and fell straight to the heap-copy fallback. In heap-copy mode
|
||||
anything that dereferences a cart address *directly* (not via
|
||||
`romdataGetRom()` / the PI shims) reads wrong memory — so Linux would run, if at
|
||||
all, in the same degraded mode Windows only hits when `VirtualAlloc` loses the
|
||||
address race.
|
||||
|
||||
**Change (M-38):**
|
||||
- Factored the post-map work (memcpy + `pcmodelsLoadSidecars` +
|
||||
`pccgLoadSidecars` + D55 RLE-header fixup) into `romdataFinishCartMap()`,
|
||||
shared by both paths so they cannot drift.
|
||||
- POSIX `#else` now does an anonymous `mmap((void*)CART_BASE, len,
|
||||
PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED_NOREPLACE, -1, 0)`.
|
||||
`MAP_FIXED_NOREPLACE` (Linux 4.17+) fails rather than clobbering an existing
|
||||
mapping; where the macro is absent the plain hint is advisory and the
|
||||
`at == CART_BASE` check catches a relocated result. On any failure it
|
||||
`munmap`s and falls through to the identical heap-copy path as before.
|
||||
- `romdataDestroy()` `munmap`s on POSIX (tracked length in `mappedLen`).
|
||||
|
||||
**Owed verification (needs a Linux box):**
|
||||
1. Compiles under GCC on Linux (`<sys/mman.h>` resolves; no decomp-include-path
|
||||
shadowing surprise).
|
||||
2. `mmap` actually lands at `0x10000000` on a stock ASLR Linux — `0x10000000`
|
||||
(256 MB) is low but usually free; if the kernel routinely refuses it,
|
||||
consider linking the binary no-PIE or reserving the range via a linker
|
||||
script so the loader keeps it clear.
|
||||
3. `-level_09` boots crash-free with the mapped path taken (check the log says
|
||||
"mapped at 0x10000000 (cart base)", not "using heap copy").
|
||||
4. Whether the heap-copy fallback is actually survivable on Linux if step 2
|
||||
fails — audit for direct `0x1xxxxxxx` derefs outside the PI shims.
|
||||
|
||||
## Remaining Linux work after the romdata patch lands
|
||||
|
||||
| # | Item | Size | Note |
|
||||
|---|------|------|------|
|
||||
| 1 | Run the Ubuntu **`validate`** CI job → upgrade it to a real **compile** job | S | `.github/workflows/ci.yml` already configures on Ubuntu; flipping it to `cmake --build` surfaces the real GCC-strictness gaps cheaply |
|
||||
| 2 | First actual Linux runtime bring-up (`-level_09`) | M | expect a short tail of unguarded calls / struct-layout asserts GCC-on-Linux vs MinGW |
|
||||
| 3 | `docs/building.md` — promote Linux from "untested" to "supported" once 1+2 pass | S | |
|
||||
| 4 | SDL2 / GL context creation on X11 + Wayland | M | fast3d is GL 3.3 core; PD port runs on Linux so the path is known-good |
|
||||
| 5 | Package: an AppImage or tarball equivalent of `bundle-win.sh` | M | reuse `prepare-assets.py` verbatim — it is already OS-agnostic |
|
||||
|
||||
## Recommended next step
|
||||
|
||||
Land the romdata patch behind CI (item 1) — turn the Ubuntu `validate` job into
|
||||
a compile job in the same PR so the "does it build on Linux" question is
|
||||
answered by the bots, then do the runtime bring-up on a real machine.
|
||||
@@ -0,0 +1,89 @@
|
||||
# M-35 QoL playtest checklist
|
||||
|
||||
Everything on this branch is config-gated to prior behaviour, so the
|
||||
**first** thing to confirm is that nothing changed with a default
|
||||
`ge007.ini`. Then flip the new keys and check the feel.
|
||||
|
||||
Edit `data/ge007.ini` while the game is closed.
|
||||
|
||||
---
|
||||
|
||||
## 0. Regression (default config) — do first
|
||||
|
||||
- [ ] Delete `data/ge007.ini`, launch, quit. It rewrites with new keys:
|
||||
`[Input] MouseCaptureMode = 0`, `[Game] ScreenShakeIntensity = 1`.
|
||||
Existing keys keep their values.
|
||||
- [ ] Play a level normally (mouse grabbed on focus, as before). Mouse look,
|
||||
aim (RMB), menus — all exactly as the last build. **No behaviour change
|
||||
expected at defaults.**
|
||||
- [ ] Alt-tab away and back — cursor frees / re-grabs (unchanged).
|
||||
|
||||
## 1. Click-to-lock capture — `Input.MouseCaptureMode = 1`
|
||||
|
||||
- [ ] Launch. At the main menu the **OS cursor is visible and free**; moving
|
||||
the mouse moves the game crosshair 1:1 (see item 3).
|
||||
- [ ] Click in the window during a level → cursor hides, mouse-look engages.
|
||||
- [ ] Press **ESC** during a level → cursor reappears, mouse-look stops,
|
||||
you can move the pointer off the window. (ESC does *not* quit.)
|
||||
- [ ] Click back in → re-locks. First click only re-locks — it must **not**
|
||||
fire the weapon.
|
||||
- [ ] Open the pause/watch (Start), then a front-end menu → cursor frees
|
||||
automatically. Return to the stage → re-locks without a click.
|
||||
- [ ] Alt-tab out mid-level → cursor frees; alt-tab back → stays free until
|
||||
you click (Quake behaviour).
|
||||
- **Correct feel:** exactly like Quake/Nightdive remasters — click to play,
|
||||
ESC to get your cursor back.
|
||||
- **Tune:** none — it's a mode toggle.
|
||||
|
||||
## 2. Aim sensitivity — B3
|
||||
|
||||
- [ ] With `Input.MouseCaptureMode` either value, hold RMB (aim mode) and
|
||||
make small aiming corrections. Default is now `MouseAimSpeed = 16`
|
||||
(was 25).
|
||||
- **Correct feel:** you can settle the sight on a distant guard's head
|
||||
without overshooting; a fast flick still crosses the screen.
|
||||
- **Tune:** `Input.MouseAimSpeed` (1–100+). Up = faster. If hipfire turning
|
||||
now feels faster than aiming, raise `MouseAimSpeed` or lower
|
||||
`Input.MouseTurnSpeed` (default 100). `Input.AimBand` (5–40, default 20)
|
||||
widens the usable stick range past the aim gate — raise it if aim feels
|
||||
capped/notchy at the extremes.
|
||||
- [ ] Check the D166 hipfire pitch (mouse up/down without RMB): still
|
||||
proportional taps, consistent with the yaw. Tune
|
||||
`Input.HipfirePitchSpeed` (default 100).
|
||||
|
||||
## 3. Menu pointer 1:1 — needs `Input.MouseCaptureMode = 1`
|
||||
|
||||
- [ ] On the mission-select grid, move the OS cursor to each of the four
|
||||
**corner** level tiles — the game crosshair must reach all of them
|
||||
(this is the D169 case; absolute mapping should make it exact now).
|
||||
- [ ] File-select, mode-select, difficulty-select, watch menu — crosshair
|
||||
tracks the OS cursor with no drift, no lag, no acceleration.
|
||||
- [ ] Keyboard nav (arrows / stick) still works on every screen.
|
||||
- **Correct feel:** the crosshair sits exactly under the OS pointer, like a
|
||||
normal PC menu.
|
||||
- **Tune:** `Input.MenuPointerSpeed` (default 100) still scales the legacy
|
||||
relative path; with capture mode the absolute path ignores it. If tracking
|
||||
feels off, check the window isn't a weird aspect ratio (the map assumes the
|
||||
render fills the window).
|
||||
- **Note:** in legacy mode (`MouseCaptureMode = 0`) the menu still uses the
|
||||
D165/D169 relative estimator — 1:1 menu tracking requires capture mode.
|
||||
|
||||
## 4. Screen shake — `Game.ScreenShakeIntensity`
|
||||
|
||||
- [ ] Default `1` — explosions shake the screen exactly as before.
|
||||
- [ ] Set `0` — no shake on explosions / big hits.
|
||||
- [ ] Set `2`–`3` — exaggerated.
|
||||
- **Correct:** `1` is indistinguishable from the current build.
|
||||
|
||||
---
|
||||
|
||||
## Single most important thing to test first
|
||||
|
||||
**Item 0** — confirm a default `ge007.ini` produces a build that plays
|
||||
identically to `def0e0ac`. If that holds, everything else is opt-in polish.
|
||||
|
||||
## Known not-done (parked, see OPTIONS-MENU-PLAN.md)
|
||||
|
||||
- No in-game options **menu** yet — only the config file + the expanded
|
||||
`config.c` API and the `Game.ScreenShakeIntensity` hook. The menu is a
|
||||
port-layer overlay design, documented, not built.
|
||||
@@ -0,0 +1,132 @@
|
||||
# M-35 — Native-PC input + options-menu QoL review
|
||||
|
||||
Branch: `qol/native-pc-input-menu` (cut from `def0e0ac`). Port-layer only
|
||||
except one documented route-(b) `#ifdef PORT` line in `src/fr.c` (D181).
|
||||
Every new behaviour is config-gated with the default reproducing prior
|
||||
behaviour; `tools_pc/golden/` headless captures are unaffected at defaults
|
||||
(no mouse in a headless run; `Game.ScreenShakeIntensity` default 1.0 is a
|
||||
no-op multiply).
|
||||
|
||||
Findings: **D180** (input pass), **D181** (screen-shake hook). See
|
||||
`docs/dev/findings.md` §F.
|
||||
|
||||
---
|
||||
|
||||
## WI-1 — In-game mouse: Quake-style click-to-lock
|
||||
|
||||
**Status: landed, feel-check owed.**
|
||||
|
||||
### What changed
|
||||
|
||||
| File:area | Change |
|
||||
|---|---|
|
||||
| `port/src/input.c` — new statics `mouseCaptureMode` / `captureArmed` / `windowFocused` | Capture-mode state. `mouseCaptureMode` registered as `Input.MouseCaptureMode` (0/1, **default 0**). |
|
||||
| `port/src/input.c` `inputInit` | In capture mode, start **un**grabbed (`SDL_SetRelativeMouseMode(FALSE)`), wait for a click. Legacy mode unchanged (grab on start). |
|
||||
| `port/src/input.c` `applyGrab()` / `reconcileGrab(menuMode)` | `applyGrab` is the old `inputSetMouseGrab` body (SDL relative-mode toggle + delta drain). `reconcileGrab` computes the wanted grab state: legacy → `windowFocused`; capture → `captureArmed && windowFocused && !menuMode`. Called once per controller-0 poll and from every notify hook. |
|
||||
| `port/src/input.c` `inputSetMouseGrab(on)` | Now just records `windowFocused` and calls `reconcileGrab` — so focus loss/gain works in both modes without special-casing. |
|
||||
| `port/src/input.c` `inputNotifyClick()` | Capture mode + focused → `captureArmed = 1` + regrab. No-op otherwise (game sees the click). |
|
||||
| `port/src/input.c` `inputReleaseCapture()` | Capture mode + grabbed → `captureArmed = 0`, release, return 1 (caller swallows the key). Returns 0 otherwise (ESC falls through to the N64 B button, D145). |
|
||||
| `port/src/input.c` `inputComputePad` idx 0 | Calls `reconcileGrab(menuMode)`; when capture-mode + free + in a stage, zeroes the local `mb` so mouse buttons never reach the game (no phantom fire from the re-lock click). |
|
||||
| `port/src/video.c` event pump | `SDL_MOUSEBUTTONDOWN` → `inputNotifyClick()`. `SDLK_ESCAPE` keydown → `inputReleaseCapture()` (before the existing fall-through). |
|
||||
| `port/include/input.h` | Prototypes for the three new hooks. |
|
||||
|
||||
### Config keys
|
||||
|
||||
| Key | Range | Default | Effect |
|
||||
|---|---|---|---|
|
||||
| `Input.MouseCaptureMode` | 0–1 | **0** | 0 = legacy (cursor grabbed whenever the window is focused). 1 = click-to-lock: free cursor until you click the window; ESC / focus-loss / opening a menu frees it; entering a stage while "armed" re-locks. |
|
||||
| `Input.MouseAimSpeed` | 1–500 | **16** (was 25) | Aim-mode (RMB / LShift) sensitivity %. B3: still overshot at 25. |
|
||||
|
||||
Controller input is completely independent of all of the above — the pad
|
||||
branch in `inputComputePad` is untouched, and `reconcileGrab` only ever
|
||||
toggles `SDL_SetRelativeMouseMode`.
|
||||
|
||||
### D166 / hipfire-pitch interaction
|
||||
|
||||
The D166 hipfire pitch pulse is unchanged. It only runs when the cursor is
|
||||
grabbed (mouse deltas are zero while free), so click-to-lock does not
|
||||
perturb it. The M-24 aim-band (`61 + gain` into the `60 + aimBand` ceiling)
|
||||
is unchanged; only the `mouseAimSpeed` scalar feeding `gx`/`gy` moved.
|
||||
|
||||
### Owed feel-checks (playtest checklist items 1–4)
|
||||
|
||||
Headless runs cannot move the mouse, so the capture ergonomics, the new
|
||||
`MouseAimSpeed = 16` default, and the D166 pulse consistency under the new
|
||||
model are all inferred, not observed.
|
||||
|
||||
---
|
||||
|
||||
## WI-2 — Menu pointer: absolute cursor tracking
|
||||
|
||||
**Status: landed for capture mode, feel-check owed. Route (a), port-only.**
|
||||
|
||||
### What changed
|
||||
|
||||
`port/src/input.c`, the `menuMode` pointer branch (the D165/D169
|
||||
P-controller). Added an **absolute-position** target source: when
|
||||
`mouseCaptureMode && !mouseGrabbed` (i.e. a real free OS cursor over a
|
||||
front-end screen), read `SDL_GetMouseState` + `SDL_GetWindowSize` of the
|
||||
mouse-focus window, normalise to `[0,1]²`, and map straight onto the live
|
||||
virtual front-end rect (`loH..hiH × loV..hiV`, already derived from
|
||||
`getPlayer_c_screen*` for D169). That becomes `menuTgt{H,V}` directly
|
||||
(no accumulation, no drift). The P-controller/estimator below is unchanged
|
||||
and simply chases the target, so the game's `cursor_h_pos/v_pos` lands
|
||||
exactly where the OS pointer is.
|
||||
|
||||
The relative-delta path (legacy `MenuPointerMode`, or capture mode while
|
||||
grabbed) is **unchanged** — `haveAbs` gates only the new assignment.
|
||||
|
||||
### Why this is enough for 1:1
|
||||
|
||||
The D169 fix already made the clamp rect track the real 440×330 field. The
|
||||
only thing missing for true 1:1 was a non-drifting target: a free OS cursor
|
||||
*is* an absolute device, so mapping its window fraction onto the rect is
|
||||
exact. No route-(b) hook needed.
|
||||
|
||||
### Keyboard nav parity
|
||||
|
||||
Untouched — the menu branch only ever adds to `sx/sy`; D-pad / stick keys
|
||||
still feed the same axes. The D118d `front.c` latent sibling was **not**
|
||||
touched (no evidence the absolute path exposes it; the stick values emitted
|
||||
are small P-controller corrections, never a slammed ±80).
|
||||
|
||||
### Caveat
|
||||
|
||||
Absolute tracking is only wired for `MouseCaptureMode = 1`. In legacy mode
|
||||
(default) the menu still uses the D165/D169 relative estimator. If
|
||||
click-to-lock becomes the default later, WI-2 is 1:1 for free; until then
|
||||
menu 1:1 requires `Input.MouseCaptureMode = 1`.
|
||||
|
||||
---
|
||||
|
||||
## WI-3 — In-game video/options menu
|
||||
|
||||
**Status: config-system foundation landed; menu surface = design doc only.**
|
||||
See `docs/dev/OPTIONS-MENU-PLAN.md`.
|
||||
|
||||
### Landed
|
||||
|
||||
- `configRegisterFloat` / `configRegisterUInt` + clamps (`port/src/config.c`,
|
||||
`port/include/config.h`). `configSave` rewritten to emit all four option
|
||||
kinds section-grouped in one pass.
|
||||
- `Game.ScreenShakeIntensity` (0.0–10.0, default 1.0) — the first
|
||||
route-(b) hook, D181, proving the pattern the menu will lean on.
|
||||
|
||||
### Not landed (budget) — precise resume point in the design doc
|
||||
|
||||
- The menu tables / handlers themselves. GE's menu system (`front.c` /
|
||||
`initmenus.c` / `mainmenu.c`) differs enough from PD's `optionsmenu.c`
|
||||
that it is an adaptation, not a copy. Design doc covers the entry point,
|
||||
the handler shape, and the minimum option set.
|
||||
|
||||
---
|
||||
|
||||
## Verification evidence
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| `./build-pc.sh ntsc-final` | green, 241/241 |
|
||||
| `-level_09` framediff (200/320/440) | 3/3 within threshold; nonclear Δ 0.00–0.01pp (91.6%); phash 0/1/5 = documented D117 intro-pan noise |
|
||||
| `GE_STARTMENU=7` boot | crash-free, no `ge007.crash.log` |
|
||||
| golden dumps at defaults | unaffected (no headless mouse; shake scale 1.0 = no-op) |
|
||||
| linkcheck | see PR body |
|
||||
@@ -0,0 +1,94 @@
|
||||
# M-36 — graphics / QA bug triage sweep
|
||||
|
||||
Branch `qol/native-pc-input-menu` (local). Goal: sort the open
|
||||
`GRAPHICS-BACKLOG.md` + QA-report bugs into **root-cause families** so a
|
||||
fix hits several at once, and correct one mis-attribution.
|
||||
|
||||
Method: headless `GE_PCDUMP` captures (`-level_36` Surface, `GE_STARTMENU=10`
|
||||
briefing) + static read of the implicated port code. Frames in
|
||||
`scratchpad/` this session; not committed (auto window size = 1957×1468,
|
||||
can't framediff — visual only).
|
||||
|
||||
---
|
||||
|
||||
## Headline findings
|
||||
|
||||
### 1. The "comb interlacing" is NOT the D159 family
|
||||
|
||||
`GRAPHICS-BACKLOG.md` attributes D176(b) and D182(2) to `texSwapAltRowBytes`
|
||||
"recurring on re-import". **That function is a full `#ifdef PORT` no-op** —
|
||||
`src/game/image.c:2199` `return;` before any work. It cannot be the cause of
|
||||
anything. The mis-attribution is now corrected in the backlog.
|
||||
|
||||
The real defect: `port/fast3d/gfx_pc.cpp` `import_texture_*` assume
|
||||
`full_image_line_size_bytes == line_size_bytes` (explicit `SUPPORT_CHECK`
|
||||
on rgba32/ia4/ia8/ia16/…; the rgba16 one is commented out with
|
||||
*"this trips in some places with a garbage size in
|
||||
full_image_line_size_bytes"*, `gfx_pc.cpp:658`). When a tile's `line`
|
||||
(SETTILE pitch) differs from the source image row width — non-power-of-two
|
||||
textures, or a sub-rect tile load — the importer does a flat linear read and
|
||||
every row walks off by a few bytes → **diagonal shear that reads as grey
|
||||
static**. Matches the Surface cliff/tree walls (`-level_36` frame 300–900:
|
||||
structured rock texture tiling but each tile sheared to noise) and the
|
||||
file-select spiral after re-entry.
|
||||
|
||||
Open question: why does D182 only scramble **after** navigating away and
|
||||
back? Either the first import happens to have `line == width` and the
|
||||
re-import doesn't, or the tile descriptor is stale on the second visit.
|
||||
|
||||
### 2. Briefing objectives are BLANK, not wrong
|
||||
|
||||
`GE_STARTMENU=10` briefing (Dam): header renders perfectly ("OHMSS / Agent:
|
||||
James Bond / Mission 1: Arkangelsk / Part i: Dam / PRIMARY OBJECTIVES:").
|
||||
Under it: just the bullet letter **"a."** with **no objective text**, and no
|
||||
b/c/d lines. So D178 is the **D143/D151 blank-text family** (`langGet()`
|
||||
returns NULL for the objective string slot → text silently blank), *not* a
|
||||
propDef walk-desync or difficulty-decode problem. Fix = find which lang bank
|
||||
the front-end briefing flow fails to load/map.
|
||||
|
||||
Caveat: this repro used the synthetic `GE_STARTMENU` boot (skips real
|
||||
save/unlock state). The user's D178 report is from a live playthrough, and
|
||||
D143 independently documents blank briefing text, so it is a real defect —
|
||||
but re-confirm the exact "a." + blank shape in a real briefing.
|
||||
|
||||
---
|
||||
|
||||
## Family table
|
||||
|
||||
| Family | Bugs | Root cause (hypothesis) | Files | ROI |
|
||||
|---|---|---|---|---|
|
||||
| **A — fast3d texture line/pitch shear** | D176(b) Surface walls; D182(2) file-select bg | **HYPOTHESIS DISPROVEN (D183, `4b71435d`).** 0/166 texture loads on `-level_36` are strided; the wall texture is a 32×32 IA8 whose ROM bytes are genuinely a noise field, decoded correctly. A golden-safe de-stride + `GE_TEXRAW`/`GE_DTEX` diagnostics landed anyway (latent hardening). **D176(b)/D182(2) still OPEN** — next: ROM ground-truth decode of the wall texnum (`TEXTURE-GLITCH-ANALYSIS.md` §7) → likely a tiling-density / UV-scale / `shifts`/`shiftt`-LOD issue (RC3/D167 family) or a wrong-texture bind, not a decode bug. | `port/fast3d/gfx_pc.cpp` | was HIGH, **now needs re-scoping** |
|
||||
| **B — front-end string bank not loaded** | D178 briefing objectives blank; D143 briefing text blank | **FIXED (D178, `cd1ed574`).** Not a bank-load problem — the `Ubrief*Z` segment is a raw 48-byte BE ROM image loaded with no converter; all 24 `u16` (string ids + difficulty gates) read byte-swapped on LE → `langGet()` NULL → blank, and `>=` difficulty gate always-false → objectives vanish. `romdataFixupBriefing()` swaps in place. Verified: all 4 Dam objectives render at 00 Agent with correct gating. **D143 was a symptom of this** — annotate it superseded. D151 (watch objective text) still owed a re-verify. | `port/src/romdata.c`, `src/game/front.c` (`#ifdef PORT`) | **DONE** |
|
||||
| **C — D75 front-end / cutscene 3D models** | D75 Nintendo logo; D148 Dam rappel cutscene; D149 MISSION COMPLETE / mode-select models; D182(1) file-select Bond image "renders once"; D173 3rd-person Bond floats; 1P weapon viewmodel absent | mixed: compiled sub-DL corruption (D144/D146), model transform/geometry, `render_pos` arena lifetime, constructor-runs-once | `port/fast3d/gfx_pc.cpp`, `title.c`, `bondview*.c`, model path | **LOW** per fix, deep. **DEFER** (agreed COA). |
|
||||
| **D — blood / particle** | D172 spray magenta/cyan; D174 no blood decals | D172: CC-mode / texture-format decode in the fast3d sprite path. D174: likely D120 `PointUsage[]` — **only takes effect after a full sidecar regen** (`d43 && d69 && d88 --regen`); verify that first. | `blood_animation.c`, fast3d sprite path; `tools_pc/d43_emit.py` | **MEDIUM**. D174 may already be fixed — regen + BUNKER1 firefight to confirm before touching code. |
|
||||
| **E — transient hang / anim speed / clip** | D175 door-open stutter (Runway/Surface); D170 NPC flee runs not walks; D171 Silo grey-triangle sweep | D175: matches D155/D156/D134/D147 known classes (benign texture-import spike most likely). D170: D155/D156 wall-clock `deltaFrames` anim speed-up, or just scripted. D171: near-plane clip fan (D106 family). | frametiming clamp, `chrTick`/AI, portal near-plane | **LOW–MED** — needs interactive repro + N64 reference footage. **DEFER**. |
|
||||
| **F — misc cosmetic** | D74 dead wrap-block (latent, harmless); D76 disclaimer partial (D164 fix owed an eyeball); D77 audio (Phase 3); **D176(a) black sky** | D176(a): sky DL not emitting / wrong transform — **separate from D176(b)**, own investigation (could be C-family or a sky-pass issue). | various | **LOW**, opportunistic. |
|
||||
|
||||
---
|
||||
|
||||
## Results (2 subagents, integrated + verified on branch HEAD)
|
||||
|
||||
- **Family B → FIXED** (`cd1ed574`). Byte-swapped raw briefing segment. Also
|
||||
explains D143. High confidence.
|
||||
- **Family A → hypothesis disproven** (`4b71435d`, D183). D176(b) is not a
|
||||
texture decode bug. Golden-safe de-stride + diagnostics shipped for the
|
||||
latent case. **D176(b) / D182(2) remain open and need re-scoping** — start
|
||||
from the ROM ground-truth decode of the wall texnum; suspect
|
||||
tiling-density / UV / per-LOD `shifts`/`shiftt` (RC3/D167 family) or a
|
||||
wrong-texture bind.
|
||||
|
||||
Harness note: both agent worktrees were created from `def0e0ac` (the M-35
|
||||
branch point, ~5 commits stale — the M-31 stale-worktree bug again). No harm
|
||||
this round: the integrator re-applied both patches onto branch HEAD and
|
||||
verified there. Check `git worktree list` HEADs on the next burst.
|
||||
|
||||
## Next
|
||||
|
||||
1. **D176(b) re-scope** — offline decode the Surface wall texnum from ROM
|
||||
(`TEXTURE-GLITCH-ANALYSIS.md` §7). Structured rock in ROM → wrong bind or
|
||||
UV/tiling; noise in ROM → not our bug (or a shift-LOD density issue).
|
||||
2. **D176(a)** black sky — untouched, separate investigation.
|
||||
3. **D182** — re-test the file-select round-trip now that `GE_TEXPITCH` /
|
||||
`GE_TEXRAW` exist; the "renders once" half (D182-1) is Family C.
|
||||
4. Reassess C/D/E with the user. D174 (blood) is the cheapest — just needs a
|
||||
sidecar regen + BUNKER1 firefight to confirm the D120 fix.
|
||||
@@ -1,11 +1,6 @@
|
||||
# In-game PC options menu — design + resume point
|
||||
|
||||
Status: **config-system foundation landed (M-35); menu surface not built.**
|
||||
(Updated 2026-09-27: the menu surface *has* since been built and iterated
|
||||
as D346 -> D356; the current layout and per-section reset live in
|
||||
`docs/dev/D356-SETTINGS-REGROUP-PLAN.md` + `docs/dev/WATCH-SETTINGS-PLAN.md`
|
||||
and `docs/dev/findings.md` D346–D356. This doc remains the historical
|
||||
M3-scope record.)
|
||||
Pattern follows `docs/dev/AUDIO-PLAN.md` — a plan doc that a later session
|
||||
executes.
|
||||
|
||||
@@ -90,12 +85,6 @@ or are flagged "restart".
|
||||
|
||||
## 4. Alternative if an in-fiction surface is required later
|
||||
|
||||
> **2026-09-27: route B chosen (v0.4.0 intake).** The entry point is on the
|
||||
> file select screen, not a main options menu (GE's front end has none). M1
|
||||
> landed as D343: an "Options" label that opens the F10 overlay, via one
|
||||
> Rule-2 `#ifdef PORT` draw hook. M2 (a GE-styled screen that reuses the
|
||||
> overlay's rows) follows.
|
||||
|
||||
Route (B): a new front-end screen `constructor_menuXX_pcoptions` +
|
||||
`menuXX_pcoptions_navigation` in `front.c` under `#ifdef PORT`, reached from
|
||||
a new "PC OPTIONS" row on the main options menu. Each row calls a
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# QoL Inventory — PD-vs-GE options diff
|
||||
|
||||
Status: **partially implemented.** M-83 landed three port-only quick wins —
|
||||
DisplayFPS (D213), anisotropic filtering (D212), and the FOV slider (D211, via
|
||||
`WIDESCREEN-FOV-PLAN.md` Phase 4). Rebinding / SkipIntro / per-pad tuning /
|
||||
window HiDpi still open. Companion to `WIDESCREEN-FOV-PLAN.md` and
|
||||
`UNLOCKED-FPS-PLAN.md`.
|
||||
|
||||
Method: diffed the config surface of both ports — every `configRegister*`
|
||||
key in `pd_port/port/src/*.c` vs `port/src/config.c` + the F10 overlay
|
||||
(`port/src/optionsoverlay.c`) + PD's `optionsmenu.c`. Principle applied:
|
||||
**where PD has a QoL feature that is achievable here, it is the standard;
|
||||
GE keeps its own extras.**
|
||||
|
||||
Classification key — **P** = port-only (no rules issues), **G** = needs a
|
||||
game-code exception (rule #2 sign-off, per `WIDESCREEN-FOV-PLAN.md` framing).
|
||||
|
||||
## GE already has (baseline — keep)
|
||||
|
||||
Input: mouse enable/capture/aim+turn speed/invert Y/Y scale/smoothing/raw
|
||||
input, aim band, hipfire pitch speed, menu pointer speed + mode (GE's menu
|
||||
cursor is injected entirely in `port/src/input.c` — no game code), pad
|
||||
deadzone/trigger %. Video: VSync, FpsCap, MSAA, TextureFilter,
|
||||
FixMipTextures, WrapFix, Fullscreen, Window geometry. Game:
|
||||
ScreenShakeIntensity. Debug: FrameDump, InputLog.
|
||||
|
||||
## PD has, GE lacks (candidates)
|
||||
|
||||
| Feature | PD location | Class | Effort | Notes / priority |
|
||||
|---|---|---|---|---|
|
||||
| **Key rebinding** _(DONE M-83, D214)_ | `port/src/input.c` `[Input.Bind]` section | P | M | Keyboard only, 12 actions, `Input.Bind.* = Key,Key` (SDL scancode names), file-driven like PD. Defaults = prior FPS layout (verified). Per-controller pad rebinding + a UI still open. |
|
||||
| **SkipIntro** _(DONE M-83, D216)_ | `Game.SkipIntro` -> `src/game/lv.c` | P | S | Boots to the SELECT FILE menu, skipping the legal screen + Nintendo/Rare/GoldenEye logo attract loop. Reuses the game's own post-intro route (sets `is_first_time_on_main_menu=FALSE` + `menu_update=MENU_FILE_SELECT`); one `#ifdef PORT` line in the existing GE_STARTMENU block. Verified headless (screenshots). |
|
||||
| **DisplayFPS** _(DONE M-83, D213)_ | config `Video.DisplayFPS` | P | S | Top-right readout, config-only; ~0.5 s sample window. |
|
||||
| **Anisotropic filtering** _(DONE M-83, D212)_ | `Video.Anisotropy` (1–16, dflt 4) | P | S | fast3d already had the GL hook + a hardcoded 4×; M-83 exposed it as config. MipmapFilter / TextureFilter2D still not surfaced. |
|
||||
| Per-pad tuning: per-stick deadzones, stick scale, rumble scale, device index, swap sticks, C-button mapping | `input.c` padsCfg block | P | M | Medium. Matters for real-controller users; PD pattern is a config section per pad. **M-87 survey addendum:** PD does *incremental* hot-plug (`SDL_CONTROLLERDEVICEADDED/REMOVED` opens/closes individual pads into the first free slot, remembers `deviceIndex=-1` on removal so a replugged pad returns to its configured seat, `input.c:473-491`); GE's port instead does a full rescan-close-and-reopen-everything (`inputRescanPads()`, `input.c:983-1000`) and the game itself latches the controller mask at boot, so a pad added later merges into controller 0 for play. PD's incremental-with-remembered-seat approach is the natural implementation shape for this row and sidesteps the boot-latch merge — fold into the design when this row gets built, not a separate item. |
|
||||
| **Rumble** _(DONE D401 — D224 / M-87, re-promoted 2026-09-27)_ | `port/src/input.c` + `port/src/libultra.c` + `port/src/optionsoverlay.c` | P | M | D401 implements the PD precedent per `docs/dev/RUMBLE-PLAN.md`: `inputRumbleSupported()`/`inputRumble()` → `SDL_GameControllerRumble()` when the pad reports haptics (SDL ≥ 2.0.18 `HasRumble`, else `SDL_JoystickIsHaptic` + known-type Windows fallback), per-pad haptics detection (`SDL_GameControllerHasRumble` / `SDL_JoystickIsHaptic` + known-type Windows fallback), a single global `Input.RumbleScale` (0..1, dflt 0.5) config + one `Rumble strength` slider row; `libultra.c` sets `CONT_CARD_ON` in `g_contStatus` for rumble-capable pads and routes `osPfsInit` → `PFS_ERR_DEVICE`, `osMotorInit/Start/Stop` → the haptics API — the two M-87 addendum blockers that kept `joyRumblePakInit` from arming. Zero `src/` edits. **Real-pad haptics confirmed working by the user (2026-09-28).** Plan item 3 landed the same day: the original four per-player rows were replaced (user decision) with one global `Rumble strength` slider in the F10 overlay + front options screen (`optionsoverlay.c`, shared `rows[]`), 0.05 detents, `kResetDefaults` 0.5; the four `Input.PlayerN.RumbleScale` ini keys were dropped (stale keys self-clean on the next save). A disabled-when-unsupported row state is deferred with the per-pad tuning row (needs a generic row-disabled mechanism). Unverified: `RumbleScale=0` silence check (trivial early-out). |
|
||||
| FakeGamepads / FirstGamepadNum / UseHIDAPI | `input.c` | P | S–M | Niche (local MP on PC); defer until MP is actually played. |
|
||||
| Window polish: DefaultFullscreen/Maximize, CenterWindow, AllowHiDpi, ExclusiveFullscreen | `video.c` vid* block | P | S | HiDpi + center are the useful ones on modern Windows. |
|
||||
| VSync adaptive (−1..10) | `Video.VSync` range | P | S | Note for FPS plan — GE's 0/1 toggle is fine until then. |
|
||||
| **CenterHUD** (0/1/2 = left/center/right) | `Game.CenterHUD` → `g_HudAlignModeL/R` | G* | — | Already scoped in `WIDESCREEN-FOV-PLAN.md` Option B (Phase 1 decision). Not a separate item. |
|
||||
| Per-player FovY + FovAffectsZoom | `Game.Player%d.FovY`, `g_PlayerExtCfg` | G | M | PD does this in game code. GE's port-only RSP FOV slider (widescreen plan Phase 4) covers the global case; per-player is a stretch goal needing the exception class. |
|
||||
| MemorySize (4–2048 MB emulated RAM) | `Game.MemorySize` → `g_OsMemSizeMb` | P? | S | Check how GE sizes its OS memory heap; if fixed, make it a config knob (stability lever for heavy levels). |
|
||||
| MaxExplosions / GEMuzzleFlashes / DisableMpDeathMusic | PD decomp edits | G | S | Low priority; rule-#2 cost > benefit. Skip unless asked. |
|
||||
| GlareBrightness / OverexposureScale / FramebufferEffects | PD post-FX | — | — | Not applicable: GE's fast3d has no framebuffer-effect path. Excluded. |
|
||||
| **HUD scale → D226 (M-87, new).** Ammo counter + bottom-left pickup/status text + top-of-screen dialogue all funnel through one function, `textRenderOutlined()` (`textrelated.c:688`), from two `bondview2.c` call sites — plain RDP texture-rectangles at native glyph size, no scale factor anywhere. Menus/options/MP UI share the same function but must NOT be scaled by this (different constraint, own layout). | P | M | User-requested. No PD precedent. Scale-about-anchor (bottom stays bottom, top stays top), matching D211's HUD-static principle. F10 slider candidate alongside FovScale. |
|
||||
| **F10 overlay: mouse-wheel scroll + more settings + category grouping → D237 (M-106, new).** The F10 options overlay (`port/src/optionsoverlay.c`) works but has no wheel-scroll navigation and only a minimal row set. User ask: (1) scroll with the mouse wheel; (2) expose more settings that can easily be surfaced — explicitly `Game.NoHitFlash` (D232, screen-flash setting), plus other cheap candidates from this inventory's baseline list (e.g. `Video.Anisotropy`, `Video.TextureFilter`, `Input.MouseYScale`, `Input.MouseSmoothing`, `Input.AimBand`); (3) organise rows by category — mouse settings together, video together, etc. (group headers over the existing flat registry walk). | P | M | User-requested. No PD precedent needed — pure port-layer overlay work on top of the existing `config.c` registry; wheel event already reaches the port (`inputPostWheel`, D223). Cross-ref `OPTIONS-MENU-PLAN.md` §3. |
|
||||
| **Mouse sensitivity rework → D238 (M-106, new).** User wants normal PC-shooter mouse settings: (1) a *separate* RMB aim-mode sensitivity; (2) ideally ONE single master sensitivity driving both turn + aim, **linked by default**, with a future option to set them independently (today `Input.MouseAimSpeed` dflt 16 and `Input.MouseTurnSpeed` dflt 100 are two unrelated keys, `input.c:188-190`); (3) up/down should feel mostly the same as left/right — GEPD Mouse Injector cited as the good-feel reference. Implementation shape: one master `Input.MouseSensitivity` feeding both paths with a per-mode scale factor defaulting to equal, plus independent override keys that decouple only when set; audit the Y-axis gain asymmetry (`AIM_GAIN` vs `MOUSE_TURN_GAIN`, `MouseYScale`, smoothing) so vertical tracks horizontal. Lump with the overall mouse/input fixes (D194/D165/D166 lineage; the SOLITAIRE natural-pitch scheme switch noted on D166 may subsume part of this). | P | M | User-requested. No PD precedent — GE-specific feel work in `port/src/input.c` + `config.c`. |
|
||||
| **Click-to-lock / always-grab parity → D239 (M-106, new).** User: click-to-lock should be the default (it already is — `mouseCaptureMode = 1`, `input.c:185`); "always grab" feels bad on the file-select menu and is not 1:1 with click-to-lock; if parity is too complicated, **drop always-grab and remove the setting**. Re-confirms the M-82 decision to rip out `MouseCaptureMode=0` (`docs/BACKLOG.md` "M-82 playtest feedback"; D192 documents mode 0's unreachable-outer-cells defect). Recommendation: take the fallback — delete mode 0 + the config key, keep click-to-lock only. | P | S | User-requested; closes D192 by deletion rather than fix. |
|
||||
| **Debug unlock-all → D225 (M-87, new, dev-tooling not player QoL).** One env-gated `#ifdef PORT` short-circuit at `fileGetIsCheatUnlocked()` (`src/game/file2.c:391`) unlocks **both** all-mission-select **and** the full cheat menu in one change — the two gates already share that single function. Bonus: exposes leftover Rare dev cheats (`CHEAT_LINEMODE` wireframe, `CHEAT_BONDPHASE`, `CHEAT_DEBUG_POS/UNK5`) that are otherwise unreachable. | P | S | User-requested testing convenience ("always enable all level access + all cheats... goes a long way"). Default off, pure query-time override, zero save-data/gameplay-logic touch. |
|
||||
|
||||
## GE has, PD lacks (keep as-is)
|
||||
|
||||
`Video.FixMipTextures`, `Video.WrapFix`, `Input.AimBand`,
|
||||
`Input.HipfirePitchSpeed`, `Input.MouseSmoothing`, `Debug.FrameDump`,
|
||||
`Debug.InputLog`. No action.
|
||||
|
||||
## Suggested Phase 4 order (after audio; ordering vs widescreen/FPS TBD)
|
||||
|
||||
1. **Quick wins (all P, small):** SkipIntro, DisplayFPS, aniso + mipmap
|
||||
filters, window HiDpi/center.
|
||||
2. **Rebinding** (P, medium) — the headline Phase 4 item; PD pattern is
|
||||
config-string driven, no UI required for parity.
|
||||
3. **Per-pad tuning** (P, medium).
|
||||
4. Niche: fake gamepads / HIDAPI (when MP gets played); MemorySize knob
|
||||
(after the heap check).
|
||||
|
||||
Everything here is parked behind audio like the other plans; nothing in this
|
||||
inventory implies an implementation decision yet.
|
||||
@@ -1,264 +0,0 @@
|
||||
# Rumble Pak → real gamepad haptics (port plan)
|
||||
|
||||
Status: **IMPLEMENTED — finding D401 (2026-09-27/28).** Items 1+2+3 all
|
||||
landed (port layer only; zero `src/` edits), re-promoting the
|
||||
DEPRIORITIZED finding **D224** (M-87) to DONE. **Real-pad haptics confirmed
|
||||
working by the user (2026-09-28).** Menu shape: **one global `Rumble
|
||||
strength` slider** (`Input.RumbleScale`, user decision 2026-09-28 — the
|
||||
four per-player rows were dropped). Only the `RumbleScale=0` silence check
|
||||
remains unverified (trivial early-out path).
|
||||
|
||||
Scope: wire the N64 Rumble Pak path (already present in `src/`) through to
|
||||
`SDL_GameControllerRumble()` on the PC. **Port-layer only — zero `src/game` or
|
||||
`src/` logic edits**, so rule 2 / ABI-exception do not apply.
|
||||
|
||||
Reference: the PD PC port (local checkout `../pd_port`, `port/src/input.c`
|
||||
+ `port/src/libultra.c`) implements the identical feature for the same Rare
|
||||
engine family. Copy its shape; the only friction is that **GE's `src/joy.c`
|
||||
calls the classic libultra names** (`osMotorInit/Start/Stop`, `osPfsInit/
|
||||
osPfsIsPlug`) while PD's engine renamed them (`osMotorProbe`,
|
||||
`__osMotorAccess`, `osPfsInitPak`). We translate names, not logic.
|
||||
|
||||
---
|
||||
|
||||
## Ground truth (current state)
|
||||
|
||||
Game side — **already correct, untouched:**
|
||||
- `src/joy.c` full Rumble-Pak state machine: `joyRumblePakInit` (:178),
|
||||
`joyRumblePakTick` (:298), `joyRumblePakStart`/`Stop`. Call sites (verified
|
||||
2026-09-27 for D401) live in `gunfire.c:3284` (start) / `:3288` (stop),
|
||||
`bondview2.c:9722` (start) / `:9726` (stop, incl. 2P second pad),
|
||||
`lv.c:1688` (stop at level start), plus `sched.c:272` (stop on level
|
||||
teardown).
|
||||
- `src/motor.c` is **EXCLUDED** from the PC build (N64 SI-DMA driver), replaced
|
||||
by no-op stubs. CMakeLists.txt:525 documents this.
|
||||
|
||||
Port side — **all no-oped today, in `port/src/libultra.c`:**
|
||||
- `osPfsInit` (:1413) → `return PFS_ERR_NOPACK;`
|
||||
- `osPfsIsPlug` (:1415) → `*pattern = 0; return 0;`
|
||||
- `osMotorInit` (:1417) → `return -1;`
|
||||
- `osMotorStart` (:1419) → `return -1;`
|
||||
- `osMotorStop` (:1420) → `return -1;`
|
||||
- `g_contStatus[i].status = 0;` (:1102) — `CONT_CARD_ON` **never set**.
|
||||
|
||||
Gamepad side — **exists, no change needed:**
|
||||
- `port/src/input.c` owns `static SDL_GameController *pads[MAX_PADS]`
|
||||
(MAX_PADS = 4, aligned with `MAXCONTROLLERS`), open/close, hotplug,
|
||||
`inputComputePad()`. This is the exact object `SDL_GameControllerRumble()`
|
||||
needs.
|
||||
|
||||
## The two upstream blockers (D224 M-87 addendum — MUST land together)
|
||||
|
||||
`joyRumblePakInit` (src/joy.c:178) only drives the motor if BOTH pass:
|
||||
1. `g_ContStatus[index].status & CONT_CARD_ON` → today always 0, so init
|
||||
never even runs.
|
||||
2. `osPfsInit(...)` returns `PFS_ERR_ID_FATAL` **or** `PFS_ERR_DEVICE` →
|
||||
today returns `PFS_ERR_NOPACK`, so `osMotorInit` is never attempted.
|
||||
|
||||
If the SDL wiring is done without (1)+(2), the state machine parks at
|
||||
`RUMBLEPAKINITSTATE_NOT_READY` forever and nothing rumbles despite "looking
|
||||
wired." Both fixes are in the same file we already touch, so no extra cost.
|
||||
|
||||
---
|
||||
|
||||
## Work items
|
||||
|
||||
### 1. `port/src/input.c` — detection + API
|
||||
|
||||
Add per-pad fields (GE uses flat statics, not PD's `padsCfg[]` array):
|
||||
```c
|
||||
static int padRumbleOn[MAX_PADS]; // set where pads[i] is opened (line ~582)
|
||||
static float padRumbleScale[MAX_PADS] = { 0.5f, 0.5f, 0.5f, 0.5f };
|
||||
```
|
||||
On pad open (in the `SDL_GameControllerOpen` path, line ~575-589), mirror PD:
|
||||
```c
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
||||
padRumbleOn[i] = SDL_GameControllerHasRumble(pads[i]);
|
||||
#else
|
||||
padRumbleOn[i] = SDL_JoystickIsHaptic(SDL_GameControllerGetJoystick(pads[i]));
|
||||
if (!padRumbleOn[i]) {
|
||||
const SDL_GameControllerType t = SDL_GameControllerGetType(pads[i]);
|
||||
padRumbleOn[i] = t && (t != SDL_CONTROLLER_TYPE_VIRTUAL); // Win no-haptic fallback
|
||||
}
|
||||
#endif
|
||||
```
|
||||
On pad close, `padRumbleOn[i] = 0;`. GE has no `inputCloseController`
|
||||
(PD-only) — the close sites are `inputDestroy()` and the close loop in
|
||||
`inputRescanPads()`, both of which zero it (`inputOpenPads` re-detects on
|
||||
open anyway).
|
||||
|
||||
New public fns (declare in `port/include/input.h`, define in input.c):
|
||||
```c
|
||||
int inputRumbleSupported(int idx); // 0<=idx<MAX_PADS ? padRumbleOn[idx] : 0
|
||||
void inputRumble(int idx, float strength, float time); // scaled SDL_GameControllerRumble
|
||||
float inputRumbleGetScale(int idx);
|
||||
void inputRumbleSetScale(int idx, float v);
|
||||
```
|
||||
`inputRumble` body (from PD, verbatim logic):
|
||||
```c
|
||||
if (idx < 0 || idx >= MAX_PADS || !pads[idx]) return;
|
||||
if (padRumbleScale[idx] <= 0.f) return;
|
||||
if (padRumbleOn[idx]) {
|
||||
strength *= padRumbleScale[idx];
|
||||
if (strength <= 0.f) { strength = 0; time = 0; }
|
||||
else { strength *= 65535.f; time *= 1000.f; }
|
||||
SDL_GameControllerRumble(pads[idx], (Uint16)strength, (Uint16)strength, (Uint32)time);
|
||||
}
|
||||
```
|
||||
In init, add the two PS4/5 hints (PD input.c:696-697) so BT pads report
|
||||
haptics:
|
||||
```c
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||
```
|
||||
Register per-pad config (in the existing `configRegister*` block, ~line 2457+):
|
||||
```c
|
||||
char key[48];
|
||||
for (int c = 0; c < MAX_PADS; ++c) {
|
||||
sprintf(key, "Input.Player%d.RumbleScale", c + 1);
|
||||
configRegisterFloat(key, &padRumbleScale[c], 0.f, 1.f);
|
||||
}
|
||||
```
|
||||
Note: **`strFmt` is NOT available in `input.c`** (0 uses, no decl in
|
||||
`port/include`) — use a local `sprintf` buffer as above. `configRegisterFloat`
|
||||
is declared in `port/include/config.h` (already included by input.c:80); it's
|
||||
used elsewhere (video.c) so the symbol is already in the link set.
|
||||
|
||||
### 2. `port/src/libultra.c` — rewire the shims
|
||||
|
||||
`contSnapshotFromKeyboard` (:1096): set the status bit for connected,
|
||||
rumble-capable pads (need a per-channel capability check → call
|
||||
`inputRumbleSupported(i)`; it's declared in input.h so include it):
|
||||
```c
|
||||
g_contStatus[i].type = connected ? CONT_TYPE_NORMAL : 0;
|
||||
g_contStatus[i].status = (connected && inputRumbleSupported(i)) ? CONT_CARD_ON : 0;
|
||||
```
|
||||
Replace the 5 accessory stubs (:1413-1420) with real routing (PD libultra.c:235-351):
|
||||
```c
|
||||
s32 osPfsInit(OSMesgQueue *q, OSPfs *pfs, int channel) {
|
||||
(void)q;
|
||||
// "pretend the Rumble Pak is present" when the real pad can rumble:
|
||||
// returning PFS_ERR_DEVICE is what joyRumblePakInit (src/joy.c:186-190)
|
||||
// needs to proceed to osMotorInit.
|
||||
return inputRumbleSupported(channel) ? PFS_ERR_DEVICE : PFS_ERR_NOPACK;
|
||||
}
|
||||
s32 osPfsIsPlug(OSMesgQueue *q, u8 *pattern) {
|
||||
(void)q;
|
||||
if (pattern) {
|
||||
*pattern = 0;
|
||||
for (int i = 0; i < MAXCONTROLLERS; ++i)
|
||||
if (inputRumbleSupported(i)) *pattern |= (u8)(1 << i);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
s32 osMotorInit(OSMesgQueue *mq, OSPfs *pfs, int channel) {
|
||||
if (pfs && inputRumbleSupported(channel)) {
|
||||
pfs->queue = mq;
|
||||
pfs->channel = channel;
|
||||
pfs->activebank = 0xff; // PD's osMotorProbe stores this too
|
||||
// NOTE: PFS_MOTOR_INITIALIZED is NOT defined in GE's headers, and
|
||||
// joy.c never reads pfs->status — it only checks (osMotorInit()==0)
|
||||
// to set RUMBLEPAKINITSTATE_READY (src/joy.c:190). Skip the status store.
|
||||
return 0;
|
||||
}
|
||||
(void)mq; (void)channel;
|
||||
return PFS_ERR_NOPACK;
|
||||
}
|
||||
s32 osMotorStart(OSPfs *pfs) {
|
||||
if (!pfs) return PFS_ERR_NOPACK;
|
||||
// N64 motor has no duration; the game's joyRumblePakTimer60 (joy.c) turns it
|
||||
// off. Issue one generous window, PD-style ("hope the timer stops it"):
|
||||
inputRumble(pfs->channel, 1.0f, 5.0f);
|
||||
return 0;
|
||||
}
|
||||
s32 osMotorStop(OSPfs *pfs) {
|
||||
if (!pfs) return PFS_ERR_NOPACK;
|
||||
inputRumble(pfs->channel, 0.0f, 0.0f); // zero-duration / zero-strength = stop
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
**JPN note (2026-09-27):** `_HW_VERSION_1` defines `MAXCONTROLLERS` as 6
|
||||
while `MAX_PADS` is 4 — the `MAXCONTROLLERS`-bounded loops above are safe
|
||||
because `inputRumbleSupported()` bounds-checks against `MAX_PADS` and reports
|
||||
unsupported (silent) for `i >= 4`. `osPfsIsPlug` has **no GE game-side call
|
||||
sites** — its rewiring is completeness/hygiene only; the load-bearing stubs
|
||||
are `osPfsInit`/`osMotor*`.
|
||||
|
||||
Headers confirmed: `OSPfs` (`include/PR/os.h:282`) has `queue` + `channel`; the
|
||||
error macros `PFS_ERR_DEVICE` (:642) and `PFS_ERR_ID_FATAL` (:641) and
|
||||
`PFS_ERR_NOPACK` (:631) are all defined there. **`PFS_MOTOR_INITIALIZED` is NOT
|
||||
defined** anywhere in GE's headers and `joy.c` never reads `pfs->status`, so it
|
||||
is not needed. `input.h` is already included by libultra.c (line 50), so
|
||||
`inputRumbleSupported` is directly callable there.
|
||||
|
||||
### 3. (DONE 2026-09-28) options-menu slider — single global
|
||||
|
||||
Landed in `port/src/optionsoverlay.c` (not PD's `optionsmenu.c` — GE's F10
|
||||
overlay + front options screen share `rows[]`, walked by `frontoptions.c`
|
||||
via the `optionsRow*` accessors): ONE `ROW_SLIDER` row `Rumble strength`
|
||||
on `Input.RumbleScale` (0.05 detents, generic `CONFIG_OPT_FLOAT` display
|
||||
`%.2f`), in the INPUT section after the controller rows, plus a
|
||||
`kResetDefaults` entry (0.5, the `gRumbleScale` C initializer) so INPUT's
|
||||
"Reset to defaults" covers it.
|
||||
|
||||
**Decision (user, 2026-09-28):** the original four-per-player-row design
|
||||
(`Rumble (Player 1..4)` on `Input.Player%d.RumbleScale`) was coded and
|
||||
reviewed, then replaced with the single global row **before its first
|
||||
compile**: the scale applies to every rumble-capable pad, and the four
|
||||
`Input.PlayerN.RumbleScale` ini keys are dropped — stale keys in an
|
||||
existing ini log four harmless `unknown key` `LOG_NOTE` lines at config
|
||||
load and self-clean on the next `configSave` (which rewrites the whole
|
||||
file with the registered keys). Per-player rumble tuning stays a G-class
|
||||
stretch goal (QOL-INVENTORY) — and a per-pad override-on-top-of-global
|
||||
design would be a no-op for everyone anyway, because `configSave`
|
||||
rewrites all registered keys and the "user explicitly set this?" signal
|
||||
is burned as soon as any key exists at the 0.5 default. The
|
||||
row-disabled-when-unsupported state is deferred with the per-pad tuning
|
||||
row (the overlay has no generic row-disabled mechanism); the row is inert
|
||||
(no-op) on non-rumble setups.
|
||||
|
||||
**Original plan (for reference):** PD wires the per-pad scale into the
|
||||
options menu (`optionsmenu.c:533-540`): `inputRumbleGetScale(player)`,
|
||||
`inputRumbleSetScale(player, v/10)`, disabled when
|
||||
`!inputRumbleSupported(player)`. In GE this maps onto
|
||||
`port/src/optionsoverlay.c` / the per-pad tuning row (QOL-INVENTORY.md).
|
||||
Config file value alone is enough for the feature; the UI is the polish.
|
||||
|
||||
---
|
||||
|
||||
## Verification ritual (AGENTS.md)
|
||||
|
||||
- [x] `./build-pc.sh ntsc-final` compiles clean (2026-09-27). New
|
||||
`inputRumble*` defined once in input.c, declared in input.h; no new
|
||||
undefined/duplicate symbols (full `/linkcheck` sweep pending at D401
|
||||
close).
|
||||
- [x] `CONT_CARD_ON` + `PFS_ERR_DEVICE` land (the two-blocker addendum);
|
||||
the state machine's READY path is provable by code read — the runtime
|
||||
proof is folded into the real-pad test below.
|
||||
- [x] **By-hand, real pad:** user confirmed the rumble works on a real
|
||||
controller (2026-09-28) — fire a weapon (`gunfire.c:3284` recoil),
|
||||
explosion rumble, 2P second pad (`bondview2.c:9722/9726`). Non-rumble
|
||||
pad and mouse/keyboard-only stay silent by construction (`pads[idx]`
|
||||
NULL / `padRumbleOn` 0 ⇒ `inputRumble` no-op).
|
||||
- [ ] `Input.RumbleScale=0` fully silences (unverified — the trivial
|
||||
`gRumbleScale <= 0` early-out in `inputRumble`); max value loud
|
||||
(confirmed implicitly by the working playtest at scale 0.5).
|
||||
|
||||
## Finding log
|
||||
|
||||
Done (2026-09-27): **D401** added to `docs/dev/findings.md` (§F index +
|
||||
full entry) documenting the D224 implementation + the two-blocker
|
||||
resolution; the `QOL-INVENTORY.md` M-87/D224 row re-promoted
|
||||
DEPRIORITIZED → DONE (D401); `docs/dev/findings-index.csv` regenerated.
|
||||
|
||||
---
|
||||
|
||||
## Effort
|
||||
|
||||
- Items 1+2: **~1–2 h** coding (mostly a rename + copy from PD) + build/linkcheck.
|
||||
- Hands-on pad testing: **~30 min–1 h** (the only genuinely open-ended part —
|
||||
feels-right tuning).
|
||||
- Item 3 (UI slider): **~1 h**, landed 2026-09-28 as the single global
|
||||
`Rumble strength` row.
|
||||
|
||||
**Total for working core: half a day max, <2 h of pure code.**
|
||||
@@ -1,7 +1,5 @@
|
||||
# Texture Glitch Analysis — PC Port
|
||||
|
||||
> **Historical record — do not act on status claims in this file.** Only the status table is current; the diagnosis from §1 onward is from 2026-08-29 and includes retracted fixes (RC4) — never apply them. Current open work and status: [`docs/ROADMAP.md`](../ROADMAP.md) (2026-09-29).
|
||||
|
||||
**Original diagnosis:** 2026-08-29 (read-only) · **Status table refreshed:** 2026-08-31 (M-31)
|
||||
Companion artifacts: `<local-path>` (outside repo).
|
||||
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
# Unlocked FPS Plan
|
||||
|
||||
Status: **proposal, parked** — documented while audio work (project Phase 3) is
|
||||
active. Do not start Phase 0 until audio is done; first action when resumed is
|
||||
the Phase 0 measurement pass.
|
||||
|
||||
**M-119 note:** this plan's premise below ("default preset = ~30fps") is now
|
||||
stale — **D248** (M-118) found and fixed a real port bug (a pointer-width
|
||||
scheduler ABI bug) that had been silently halving the game to 30fps since the
|
||||
port's inception; the corrected default is a genuine 60fps. Re-measure Phase 0
|
||||
against the corrected baseline before resuming this plan. Also see **D250**
|
||||
(M-119, open): the exposed real 60fps cost now drops to 20-40fps intermittently
|
||||
on some levels — a performance pass may be a prerequisite for this plan, not
|
||||
just a baseline re-measurement.
|
||||
Related: `docs/dev/findings.md` §D117 (GE_DETERM), §D155 (catch-up clamp),
|
||||
`port/src/libultra.c` (pacemaker), `src/game/frametiming.c`, `src/sched.c`.
|
||||
|
||||
## Decisions (agreed)
|
||||
|
||||
1. **Default preset = the original pace** — whatever the game currently does
|
||||
(measured ~30 fps; Phase 0 pins the exact rate). All testing, level
|
||||
sweeps, determinism runs (`GE_DETERM`), and playtest baselines stay on the
|
||||
default preset. Its code path must remain byte-for-byte what it is today.
|
||||
2. **Unlocked FPS (60 / 120 / uncapped) is EXPERIMENTAL.** It ships behind a
|
||||
clearly-labelled option and may be less polished than the default.
|
||||
3. **Hard requirement:** raising the presentation rate must never change game
|
||||
speed. Sim rate is invariant across presets. This is "the main speed-up
|
||||
issue" and it gates everything else.
|
||||
4. **Follow the PD PC port where feasible** (`pd_port` checkout): wall-clock
|
||||
accumulator pacing with remainder carry, vsync + framerate-limit options,
|
||||
safety cap when vsync is off.
|
||||
|
||||
## Non-goals (for now)
|
||||
|
||||
- Motion interpolation between sim ticks (the only thing that would make 120
|
||||
fps *look* like 120 fps). Requires per-entity prev/cur state in the
|
||||
DL-building game code — rule #2 territory, parked as a separate proposal.
|
||||
- Changing the sim rate itself (running logic at 120 Hz with halved constants
|
||||
is explicitly out — game logic).
|
||||
|
||||
## Background: why raising FPS today speeds up the game
|
||||
|
||||
One clock drives everything:
|
||||
|
||||
- `portTickThread` (`port/src/libultra.c`) posts a VI-retrace message on a
|
||||
free-running grid (60 Hz NTSC / 50 Hz PAL, `g_tickIntervalUs`).
|
||||
- `src/sched.c` `__scMain` blocks on that retrace; `__scHandleRetrace`
|
||||
notifies clients → **exactly one sim tick + one render per retrace**.
|
||||
- `osGetCount()` is wall-clock scaled to 46.5 MHz. `waitForNextFrame()`
|
||||
(`src/game/frametiming.c`) converts elapsed time to `deltaFrames`, which
|
||||
becomes `g_ClockTimer` (`src/game/lv.c:1008`) driving the per-tick sim loops.
|
||||
|
||||
The quantization is the trap: `nextFrameTime = (elapsed + Q/2) / Q` (Q = one
|
||||
frame quantum). The `+Q/2` bias **rounds every interval up to at least 1
|
||||
tick**, and `waitForNextFrame` spins until ≥1 tick is due. Consequence:
|
||||
**sim speed tracks the retrace rate almost 1:1** — 30 Hz retrace → 30 ticks/s,
|
||||
120 Hz retrace → ~120 ticks/s (each 8.3 ms interval rounds up to a full
|
||||
tick). That is exactly the observed "anything faster speeds up the game".
|
||||
|
||||
Two useful existing properties:
|
||||
|
||||
- GE's game code already handles `g_ClockTimer == 0` and multi-tick deltas
|
||||
everywhere (`for (i = 0; i < g_ClockTimer; i++)`, explicit `== 0` guards in
|
||||
bondhead/bondview2/propobj/explosion). The N64 used this for dropped frames.
|
||||
- D155 already clamps catch-up to 6 ticks after a real-time hitch.
|
||||
|
||||
## What the PD port does (reference)
|
||||
|
||||
- `src/game/timing.c` `frametimeCalculate()`: wall-clock accumulator with
|
||||
remainder carry (`lostframetime60t`) → `diffframe60` / `diffframe240` ticks
|
||||
due **per rendered frame**; render loop runs at display rate.
|
||||
- Vsync + framerate limit options (`VIDEO_MAX_FPS` 200/240); vsync-off with
|
||||
no cap forces the safety cap.
|
||||
- Feasibility mapping to GE:
|
||||
- *Pacing math (accumulator + carry)* → adoptable **port-only**. ✓
|
||||
- *Render frames carrying zero sim ticks* → blocked in GE by
|
||||
`waitForNextFrame`'s ≥1-tick enforcement (`frameDelay`, only settable
|
||||
under `LEFTOVERDEBUG`). A crafted `osGetCount()` cannot defeat it (the
|
||||
spin + `+Q/2` bias always yield ≥1). Needs a game-code change → **parked**
|
||||
(see "Parked" below), and note it buys little without interpolation.
|
||||
|
||||
## Phases
|
||||
|
||||
### Phase 0 — Baseline & ground truth (port-only)
|
||||
|
||||
**RESOLVED (2026-09-12, commit 8f2f9e1d, D248).** The ~30 fps default was
|
||||
confirmed a **port artifact**, not original N64/GE behavior: `sched.c`'s
|
||||
`__scHandleRetrace` read a per-client "every retrace vs. every other
|
||||
retrace" flag via a hardcoded `*((s32*)client + 2)` byte offset (`sched.c`
|
||||
~line 350) that only lands on the right field when `OSScClient` is 8 bytes
|
||||
(32-bit pointers, true on N64). On this 64-bit port the struct is 16 bytes,
|
||||
so the read landed inside the gfx client's own `msgQ` pointer (always
|
||||
non-NULL), silently collapsing the gfx client onto the audio client's 30Hz
|
||||
path even though the VI-retrace pacemaker itself always ticked at a correct
|
||||
60Hz. Fix (`#ifdef PORT`, `sched.c` ~line 348): index the struct
|
||||
(`client[1].next`) instead of the hand-rolled offset, so it scales with
|
||||
pointer width. Verified live via the `Video.DisplayFPS` overlay: rock-stable
|
||||
30 FPS pre-fix, 60 FPS post-fix, same build/scene. Same bug class as
|
||||
D122/D126/D132/D209 (pointer-width struct growth), just in the scheduler.
|
||||
|
||||
**Decision-gate answer:** the default preset is now the console's real
|
||||
60Hz(NTSC)/50Hz(PAL) rate — decision #1 above ("default preset = the
|
||||
original pace") is satisfied by this fix, not by preserving the halved
|
||||
30fps. All prior playtest/determinism baselines predate this fix and should
|
||||
be treated as pre-D248.
|
||||
|
||||
- [x] Find why the default was ~30 rather than the console's 60 Hz — root
|
||||
cause above; §F **D248**.
|
||||
- [x] Record a Dxxx finding with the measured rates and root cause — D248.
|
||||
- [ ] Measure actual sim rate and present rate with a dedicated periodic log
|
||||
line (`sim=..Hz render=..Hz`) rather than the one-off `DisplayFPS`
|
||||
overlay reading used to verify D248. Not yet built — still open if a
|
||||
standing measurement (vs. a spot-check) is wanted before Phase 1/2
|
||||
work lands.
|
||||
- [x] **Decision gate** — resolved above.
|
||||
|
||||
### Phase 1 — PD-style pacing hardening (port-only)
|
||||
|
||||
Makes the default preset stable and drift-free; no game-code changes.
|
||||
|
||||
**Audit (2026-09-12, post-D248):** more of this than the plan assumed
|
||||
already exists. Verified by reading the code directly (not just the
|
||||
delegate's first pass, which mis-read one of these — see below):
|
||||
|
||||
- [x] **Accumulator + remainder carry, tick thread.** `portTickThread`
|
||||
(`port/src/libultra.c` ~294-313) already schedules off `g_nextTickUs +=
|
||||
g_tickIntervalUs` — the *scheduled* time, not `now` — so it doesn't
|
||||
drift under load. It resyncs to `now + interval` only when a tick is
|
||||
already overdue (`g_nextTickUs <= now`), which is a bounded-catch-up
|
||||
behavior, not a naive reset. This already matches the PD
|
||||
`frametimeCalculate` accumulator pattern.
|
||||
- [x] **Accumulator + remainder carry, present-side limiter.**
|
||||
`sync_framerate_with_timer` (`port/fast3d/gfx_sdl2.cpp` ~403-428) uses
|
||||
the same shape: `next = previous_time + interval` (schedule-based, not
|
||||
wall-clock-based), a sleep-then-busy-wait hybrid to hit the deadline
|
||||
precisely, and only snaps `previous_time` to the real wake time `t`
|
||||
when the overshoot exceeds ~1ms (`t - next < 10000` in its 100ns units)
|
||||
— i.e. it already carries the remainder in the normal case and only
|
||||
gives it up after a real hitch. (An earlier audit pass mis-read this as
|
||||
"no remainder carry, resets every frame" — it does not; re-verified by
|
||||
reading `gfx_sdl2.cpp:403-428` directly.)
|
||||
- [ ] **Triple-buffered swap chain.** Not present — presentation uses a
|
||||
standard SDL/GL double-buffer swap (`SDL_GL_SwapWindow`,
|
||||
`gfx_sdl_swap_buffers_begin`, `gfx_sdl2.cpp` ~436-444). Not started.
|
||||
- [ ] **PD safety rule (vsync off + no cap → force a max cap).** Not present.
|
||||
The only existing floor/ceiling logic is the D186 guard in
|
||||
`gfx_sdl_set_target_fps` / `video.c` (~289-292, ~464-467), which goes
|
||||
the *other* direction — it refuses caps below 30 (because pacing still
|
||||
runs inline on the sim thread, see Phase 2 note below) — there is no
|
||||
"vsync off AND cap==0" case that forces a ceiling. Not started.
|
||||
- [x] Config: `Video.VSync` / `Video.FpsCap` already registered
|
||||
(`video.c:106-107`) and live-reapplied (`video.c` ~340-349).
|
||||
|
||||
**Net:** the hard part of Phase 1 (drift-free accumulator scheduling) is
|
||||
already done, on both the sim-tick and present-side clocks. What's left is
|
||||
narrow: a safety-cap rule and, if wanted, triple buffering — plus whatever
|
||||
Phase 2 needs on top.
|
||||
|
||||
**Phase 2 first slice, scoped (2026-09-12).** The real gap for a 120fps
|
||||
*presentation* preset isn't pacing math — it's that `gfx_run()`
|
||||
(`gfx_pc.cpp:3179-3232`) composites straight into the default framebuffer
|
||||
(fb 0) once per sim tick and then calls `gfx_wapi->swap_buffers_begin()`
|
||||
once; there's no mechanism to show that same completed frame again on an
|
||||
extra vsync between sim ticks. The existing `GfxRenderingAPI` already has
|
||||
everything needed to do this **without any new GL primitives or a second
|
||||
thread/context**:
|
||||
- `copy_framebuffer(fb_dst, fb_src, left, top, flip_y, use_back)`
|
||||
(`gfx_opengl.cpp:1270`) already blits the real window backbuffer
|
||||
(`fb_src == 0` + `use_back = true` reads `GL_BACK`, confirmed
|
||||
`gfx_opengl.cpp:1309-1311`) into an arbitrary allocated FBO/texture, and
|
||||
can blit back the other way (`fb_dst == 0` writes straight into the real
|
||||
default framebuffer, `gfx_opengl.cpp:1214`'s `fb_id == 0 -> bind 0`
|
||||
convention).
|
||||
- `gfx_pc.cpp` already keeps a couple of always-allocated internal
|
||||
framebuffers this same way (`game_framebuffer`,
|
||||
`game_framebuffer_msaa_resolved`, created at `gfx_pc.cpp:3069-3070`) — a
|
||||
new "last presented frame" capture FBO is the same pattern, one more
|
||||
entry.
|
||||
- The tick clock (`portTickThread`) runs on its own pthread, independent of
|
||||
the render/sim thread that calls `gfx_run`/`swap_buffers_begin` — so
|
||||
spending extra wall-clock time *inside* `swap_buffers_begin` presenting a
|
||||
duplicate frame does not touch the tick schedule at all; it only changes
|
||||
how much idle time the render thread burns before the next tick message
|
||||
is waiting for it. Sim rate stays exactly invariant by construction, per
|
||||
decision #3.
|
||||
|
||||
**Smallest concrete slice:** capture the just-finished frame into a new
|
||||
internal FBO right before the real `swap_buffers_begin()` call
|
||||
(`gfx_pc.cpp:3230-3231`, via `gfx_rapi->copy_framebuffer(captureFb, 0, -1,
|
||||
-1, false, true)`); when an experimental `Video.PresentRate` config knob
|
||||
requests double-rate (2x — e.g. 120 from a 60Hz NTSC sim, 100 from 50Hz
|
||||
PAL), have `gfx_sdl_swap_buffers_begin` (`gfx_sdl2.cpp`) do the normal swap,
|
||||
then — using the same schedule-accumulator shape as
|
||||
`sync_framerate_with_timer` — sleep to the halfway point of the tick
|
||||
interval, blit the capture FBO back into fb 0
|
||||
(`gfx_rapi->copy_framebuffer(0, captureFb, -1, -1, false, false)`), and
|
||||
`SDL_GL_SwapWindow` again. Default (`Video.PresentRate = 0`) must take none
|
||||
of these code paths, so the default preset stays byte-identical to today.
|
||||
Restrict the first slice to exactly {0 = off, 2 = double}; don't generalize
|
||||
to arbitrary/uncapped multipliers yet — that needs the vsync-off safety cap
|
||||
from the Phase 1 list above first. No options-menu UI in this slice
|
||||
(config-file-only); that's a fast follow once the mechanism is playtest-
|
||||
verified not to tear/stutter.
|
||||
|
||||
### Phase 2 — Experimental presentation rates (port-only)
|
||||
|
||||
The user-visible "60 / 120 / unlocked" feature. Game speed is invariant **by
|
||||
construction**: sim + render stay on the original tick clock; only *present*
|
||||
runs faster by showing each rendered frame more than once.
|
||||
|
||||
- [ ] Frame duplication at the present stage (`video.c` / fast3d SDL swap
|
||||
path, near `gfx_pre_swap_hook`): after a fresh frame is presented,
|
||||
re-present the last finished frame on extra vsyncs until the next sim
|
||||
tick's frame is ready. Target rates: 60, 120, uncapped (safety-capped à
|
||||
la PD `VIDEO_MAX_FPS`).
|
||||
- [ ] Input at present rate: sample SDL input continuously in
|
||||
`port/src/input.c`; each sim tick consumes the latest state (compare
|
||||
against PD `joy.c`). This is the real tangible win of the feature: up
|
||||
to ~half a frame of latency removed even without interpolation.
|
||||
- [ ] Options menu entries, clearly labelled **experimental**
|
||||
(`port/src/optionsoverlay.c`), default preset untouched.
|
||||
- [ ] Audio check: `src/audi.c` uses real-time `osGetTime()` and its own
|
||||
thread; libaudio buffer sends ride the sim/retrace clock. Verify nothing
|
||||
divides by an assumed frame rate; expect no changes.
|
||||
|
||||
### Phase 3 — Verification & sign-off
|
||||
|
||||
- [ ] `GE_DETERM` regression: default preset simulation path byte-identical to
|
||||
pre-change (existing determinism diffs keep passing).
|
||||
- [ ] Preset-invariance playtest: a timed in-game event (e.g. a fixed-length
|
||||
animation or timer) takes the same real time on default / 60 / 120 /
|
||||
unlocked. This is the acceptance test for decision #3.
|
||||
- [ ] Hitch recovery: force a multi-second stall (debugger pause / asset load),
|
||||
confirm D155 clamp holds and no speed spiral on any preset.
|
||||
- [ ] `./build-pc.sh` + `/linkcheck` ritual; new findings in §F/§H with index
|
||||
labels.
|
||||
|
||||
## Timing-consumer audit (each gets a line in the finding)
|
||||
|
||||
| Consumer | Clock class | Action |
|
||||
|---|---|---|
|
||||
| `src/game/frametiming.c` (`waitForNextFrame`, `updateFrameCounters`) | sim-time | unchanged; stays tick-locked |
|
||||
| `src/boss.c:403,517` (main-loop gate, `MAIN_LOOP_TICK_INTERVAL`) | sim-time | unchanged; re-verify vs new pacemaker |
|
||||
| `src/usb.c:387,405` (LibDragon flash timeouts) | real-time | fine as-is; document |
|
||||
| `src/speed_graph.c` | measurement | fine; source of the rate log line |
|
||||
| `libultrare/audio/env.c`, `reverb.c` `lastCnt` | perf counters only | fine; document |
|
||||
| `src/audi.c` `g_DeltaTime` etc. (`osGetTime`) | real-time | fine; verify in Phase 2 |
|
||||
|
||||
## Parked (needs explicit sign-off, separate proposals)
|
||||
|
||||
1. **True decoupled sim/render (PD's zero-tick render frames).** Minimal
|
||||
change class: relax `waitForNextFrame`'s ≥1-tick enforcement so rendered
|
||||
frames can carry `deltaFrames = 0` (GE already guards `g_ClockTimer == 0`
|
||||
everywhere). This is a *behavior-affecting* game-code edit gated off by
|
||||
default — outside the ABI-only D3x exception, needs its own rules decision.
|
||||
Perceptual benefit over Phase 2 duplication is small **without**
|
||||
interpolation, so do this only as a prerequisite for #2.
|
||||
2. **Motion interpolation** (prev/cur entity state, lerp at render time).
|
||||
The actual "120 fps looks like 120 fps" feature. Large, invasive, in
|
||||
DL-building game code. Separate proposal; not implied by this plan.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Phase 0 outcome: is the ~30 fps default a port artifact? (fix vs keep — see
|
||||
decision gate)
|
||||
- PAL builds: default stays 50 Hz pace; experimental present rates identical
|
||||
option set. Confirm no PAL-specific pacing assumptions in Phase 1 math.
|
||||
- Naming/caps for "unlocked": adopt PD's safety-cap value (200/240) or expose
|
||||
the cap as a slider?
|
||||
|
||||
**M-87 PD-legacy-survey note (candidate 8):** if this plan stalls and GE stays
|
||||
fixed-tick, PD's `Game.TickRateDivisor` (0–10, slow-motion/perf lever) +
|
||||
`Game.ExtraSleep` (`port/src/main.c:39,167-168`, applied `src/game/timing.c:49`)
|
||||
is a cheap borrowable fallback lever — not otherwise relevant since this plan's
|
||||
whole direction is decoupling sim from frame rate, the opposite of PD's
|
||||
fixed-tick-with-a-divisor approach.
|
||||
@@ -1,96 +0,0 @@
|
||||
# 1P weapon viewmodel — static research (read-only)
|
||||
|
||||
> **RESOLVED 2026-09-09 (M-83, D215).** None of the three ranked suspects below
|
||||
> was the cause — all were ruled out by the `GE_DVM=1` probe (added this
|
||||
> session). The bug was `renderdata.flags == 0` in `gunRenderFirstPersonGunModels`:
|
||||
> the N64 `*(ModelRenderData*)&D_80035CC0` reinterpret-across-adjacent-globals
|
||||
> breaks under x86-64 pointer widening + no guaranteed global layout. Fixed
|
||||
> `#ifdef PORT` in `src/game/gunfire.c`. See findings.md D215. This doc is kept
|
||||
> for the ruled-out analysis only.
|
||||
|
||||
Companion to the (since retired) `GRAPHICS-BACKLOG.md` ("1P weapon viewmodel") and findings D115
|
||||
item #5 / AUDIT-M6 #5. Goal: narrow the five audit suspects to a ranked,
|
||||
probe-ready list. No code changed by this doc.
|
||||
|
||||
## Path map (verified in source)
|
||||
|
||||
- **Setup (per frame):** `bondview2.c:9040` → `gunUpdateAndFireBothHands()`
|
||||
(`gunfire.c:1018`) → `gunUpdateAndFire(handnum)` (`gunfire.c:327`). Inside,
|
||||
the weapon-model block at `gunfire.c:599-662`: computes `field_87F` (the
|
||||
show/hide gate), then if set: `dynAllocate(numMatrices × sizeof Mtxf)` →
|
||||
identity-fill → `modelCalculateRwDataLen` → `modelInit(HAND_WEAPON_MODEL,
|
||||
©_of_body_obj_header[handnum], HAND_WEAPON_RWPOOL)` →
|
||||
`hand->mtxlist = rwmtx` and (PORT/D102)
|
||||
`weaponModel.render_pos = (RenderPosView*)rwmtx`.
|
||||
- **Render (same frame, later):** `gunRenderFirstPersonGunModels` loop at
|
||||
`gunfire.c:1532-1540`: **`if (handptr->field_87F == 0) continue;`** then
|
||||
`subdraw(&renderdata, HAND_WEAPON_MODEL(handptr))` (`model.c:5326`) which
|
||||
does `gSPSegment(gdl, 3, osVirtualToPhysical(mdl->render_pos))` and walks
|
||||
`mdl->obj->RootNode` — **a NULL RootNode returns silently, drawing
|
||||
nothing**.
|
||||
|
||||
## Statically ruled out (with evidence)
|
||||
|
||||
1. **Transient-arena lifetime** (the audit's prime suspect). `dynAllocate`
|
||||
is a bump allocator on the vtx pool (`src/game/dyn.c:123`), reset only by
|
||||
`dynSwapBuffers()` at frame end. Setup and render both happen inside one
|
||||
frame, so `render_pos`/`mtxlist` are valid when consumed — in solo play.
|
||||
(Split-screen mid-frame swaps would be a separate question; out of scope.)
|
||||
2. **Word-index / record-size mismatch** (`flashvisptr = RWPOOL +
|
||||
RwDataIndex`, `gunfire.c:650`). D52 kept `Model.datas` as `u32*` on PC
|
||||
precisely so N64 word math holds, and `RwDataIndex` values are computed
|
||||
at runtime by `modelCalculateRwDataIndexes` from the *PC* record sizes —
|
||||
self-consistent. (Self-consistent ≠ in-bounds; see suspect 2.)
|
||||
3. **Segment/render_pos mechanism itself.** Every working model in the game
|
||||
(bodies, props) uses the same `gSPSegment(3, render_pos)` +
|
||||
`bondviewTransformManyPosToViewMatrix` machinery — and it renders. The
|
||||
**watch menu draws the same weapon models** via a different path
|
||||
(`set_enviro_fog_for_items_in_solo_watch_menu`, `gunfire.c:1630+`, local
|
||||
matrices, no 87F gate, no hand rw-pool) and they appear (albeit faint —
|
||||
D75/D141 note). So the model assets and headers are very likely fine; the
|
||||
defect is in-level-path-specific.
|
||||
|
||||
## Ranked suspects + probe spec
|
||||
|
||||
**#1 — `field_87F` gating (`gunfire.c:599-610`).** The clear-condition
|
||||
disjunction contains `(Gun_hand_without_item(handnum) == 0)`. That function
|
||||
(`gun.c:812-816`) returns `hand_invisible[h] > 0 || (hand_item[h] == 0 &&
|
||||
field_2A44[h] < 0)` — so the term is TRUE (→ gate cleared → gun hidden)
|
||||
whenever the hand *has* an item and isn't invisible. Read naively, that
|
||||
hides the gun exactly when you're holding one; since the N64 shows the gun,
|
||||
the runtime values of `hand_item`/`field_2A44`/`hand_invisible` must differ
|
||||
from their names suggest — but this is the single most likely place for a
|
||||
PC-only divergence (e.g. a field the port initializes differently). If 87F
|
||||
is 0, the render loop `continue`s and the symptom is exactly "no gun, no
|
||||
crash".
|
||||
|
||||
**#2 — rw-pool capacity.** PC records are wider than N64's (pointer fields);
|
||||
`weaponRwPool[192]` (768 B) was D102's scaled-up budget, but nothing bounds-
|
||||
checks it. The `#ifdef DEBUG` print at `gunfire.c:620` still tests the *N64*
|
||||
32-word threshold — stale on PC. If `numRecords` words exceed 192,
|
||||
`modelInitRwData` overruns the pool into whatever follows it in `struct
|
||||
hand` → corruption that could kill the draw (or worse, silently).
|
||||
|
||||
**#3 — stale `copy_of_body_obj_header`.** Populated only at item load
|
||||
(`gun.c:924` shallow copy / `gun.c:931-951` `load_object_fill_header`). If a
|
||||
PC asset-load quirk zeroed it for the in-level path, `RootNode == NULL` and
|
||||
`subdraw` no-ops silently. Lower priority given the watch menu renders the
|
||||
same models — but cheap to confirm in the same probe.
|
||||
|
||||
**Probe (one session, PORT-only, env-gated `GE_DVM=1`, zero behavior
|
||||
change):** in the `field_87F != 0` block at `gunfire.c:611+`, print once per
|
||||
frame while a pistol is held in BUNKER1: `handnum`, `item`, each clear-term
|
||||
value + final `field_87F`, `©_of_body_obj_header[h]`, `numMatrices`,
|
||||
`numRecords` (words) vs 192, `RootNode`, `rwmtx`. One run triages all three
|
||||
suspects: 87F==0 → suspect #1 (log which term); numRecords>192 → #2;
|
||||
RootNode NULL/garbage → #3.
|
||||
|
||||
## Fix-shape notes (for when the probe lands)
|
||||
|
||||
- If #1: the fix is a `#ifdef PORT` initialization/correction of whichever
|
||||
hand field diverges — **not** an edit to the gate logic itself (rule #2);
|
||||
document as Dxxx with the N64-vs-PC value table.
|
||||
- If #2: enlarge `weaponRwPool` to the measured max + a PORT bounds guard
|
||||
that prints (mirrors the existing DEBUG print, correct threshold).
|
||||
- If #3: trace the `load_object_fill_header` call for the in-level weapon
|
||||
load; likely an asset-offset issue in the port's loader path.
|
||||
@@ -1,63 +0,0 @@
|
||||
# Watch-backed settings in PC Options and F10 — proposal for review
|
||||
|
||||
Status: **watch-backed subset implemented (D350), robustness fixes landed (D352), Turok-style regrouping done (D353), front-end F10 routing + transparent default Bond-file selection landed (D354: non-stage contexts use the chooser path, default = explicit pick > `selected_folder_num` > first valid file, with a one-shot `fileBuildWriteNewSave(FOLDER1)` fallback; GE_WSPROBE/GE_WSPROBE_FRONT headless probes), the in-stage BGM resync landed (D355: the Music commit also drives the track-1 BGM player, matching the game's own MP-path resync; by-ear confirmed), and the settings regroup landed (D356: functional sections INPUT/GAMEPLAY/GRAPHICS/AUDIO/VIDEO, PD-style "(File N)" scoping annotations, N64-watch rows off the menu surface, per-section reset-to-defaults = Gate E; GE_WSPROBE_RESET headless probes front + in-stage).** Wider M3 remains outstanding: master volume, key rebinding. PC-only per-file persistence and explicit Bond-file chooser approved; details below. See `docs/dev/findings.md` D350–D356 for verification. This is the watch-integration part of M3, not a replacement for the native watch menu. The older tracked `docs/dev/OPTIONS-MENU-PLAN.md` and local `docs/dev/notes/OPTIONS-MENU-PLAN.md` describe the wider M3 scope (master volume, window mode/display, resets).
|
||||
|
||||
## Goal and ownership
|
||||
|
||||
Show GE's existing watch settings in both PC settings surfaces (`MENU_PC_OPTIONS` and F10) without maintaining a second copy of each value. Both surfaces already consume one row table in `port/src/optionsoverlay.c`; the watch remains an independent UI editing the **same underlying GE value**. Changes made in either direction must be visible on the next read. Preserve the N64 watch, its behavior, and the N64 build.
|
||||
|
||||
**Approved ownership:** per-file GE settings belong in the Bond save; machine-wide PC settings belong in `ge007.ini`. GE's original watch edits are *not* persisted on normal play (D349 below): **watch-made changes remain session-only**, while **PC-UI-made changes persist to the selected Bond file**. This is a deliberate port-side QoL divergence, not existing GE behavior. Do not register watch-backed settings as ini variables, copy watch values into config globals, or overwrite a newly loaded Bond file with stale ini values. A proposed PC master volume is separate: it multiplies the final output and does not replace the watch's Music/FX volumes.
|
||||
|
||||
## Inventory and proposed rows
|
||||
|
||||
Source of truth: `src/game/options.c` (`game_options_entries`, `cur_player_get/set_*`, `get_mTrack2Vol`/`set_mTrack2Vol`, `sub_GAME_7F0A91A0`), `src/game/file2.c:1246-1350` (per-folder serialization), and `port/src/input.c:~939` (forced control style). Audit exact getter/setter availability and initialization before implementing each row.
|
||||
|
||||
| Watch control | PC presentation | Notes |
|
||||
|---|---|---|
|
||||
| Music / FX volume | Audio section, separate Music and FX sliders | GE range 0..32767, stored in the Bond save at 8-bit precision; use actual GE APIs, not an SDL channel gain in place of either. Master volume stays a distinct global row. |
|
||||
| Look up/down (reverse/upright), auto-aim, aim control (hold/toggle), sight on screen, look ahead, ammo on screen | Bond file / gameplay subsection, with GE-specific labels | These are the watch's `game_options_entries` and save bits. Watch look-up/down is distinct from PC MouseInvertY and PadLookInvertY: the independent inversions compose (XOR). Label/help text must explain the stacking; do not combine their stored values. |
|
||||
| Screen (Full/Wide/Cinema), Ratio (Normal/16:9) | Separate clearly marked GE presentation controls, if useful | Not PC window resolution/mode or `Video.NativeWidescreen`. Verify interactions with native widescreen/crop before exposing. |
|
||||
| Controller style / controller diagram | Defer pending input design | The diagram is not a setting. `inputComputePad(0)` currently reasserts Honey or Solitaire from `Input.NaturalPitch` every poll, including after save load or watch changes; `bondview.c` and multiplayer load also force Honey in some contexts. A naive style row would be immediately undone. Decide whether the watch selector should be informational, limited to supported PC mappings, or integrated with the port mapping; no row until this is reconciled. Dual-controller styles need their own decision. |
|
||||
|
||||
This inventory is not permission to expose every field automatically. Especially do not silently merge identically named PC and GE toggles that have different input paths or persistence.
|
||||
|
||||
### Persistence ground truth (D349)
|
||||
|
||||
`fileSaveSettingsForFolder` (`src/game/file2.c:1246`) has one caller, `fileClearSavefileForFolder` (`:1379`), reached from the erase path. Normal watch edits change live values but do **not** write the file's `options`/`music_vol`/`sfx_vol` fields or EEPROM. `init_watch_at_start_of_stage` (`src/game/options.c:415`) calls `fileLoadSaveSettingsForSelectedFolder`, so the next stage reloads those fields and discards unsaved watch edits. This is a GE/N64 quirk, not a PC regression; Gate A should re-audit indirect writes before implementation and record any correction to D349.
|
||||
|
||||
Thus a front-end row that edits only live watch state will be silently undone before gameplay. The maintainer approved **port-side per-file persistence of PC-UI changes**, without changing the N64 save format or the N64 watch's session-only behavior. Do not auto-persist edits made directly in the watch.
|
||||
|
||||
## UI / data design
|
||||
|
||||
1. Add a **watch-backed row type** to the existing shared row model, not another settings menu or fake config registration. Its read/write/availability callbacks operate on GE's real option API; existing config-backed rows continue to work unchanged. Keep enum labels, range/step and value formatting in the shared row table so F10 and the GE-styled screen cannot diverge.
|
||||
2. Use GE's existing getters/setters for **all eight proposed entries** (`src/game/options.c:526-598`) and both volume controls (`get_mTrack2Vol`/`set_mTrack2Vol`, `call_sndGetSfxSlotFirstNaturalVolume`/`sub_GAME_7F0A91A0`). For FX use the same game volume API as the watch, not an SDL channel gain. No `src/game` bridge or Rule-2 game-code edit is expected for these rows. Preserve the watch's side effects; persistence, if approved, is a separate port-side operation.
|
||||
3. **Threading is a design gate:** F10 input runs on the scheduler thread (`inputComputePad(0)`); the GE-styled screen and watch run on the game thread. Do not directly mutate game/watch state from the F10 thread merely because a getter or pointer is accessible. Route F10 writes as bounded requests to a safe game-thread point, and publish a safe, current snapshot for F10 rendering/reads, or establish an equivalently reviewed synchronization scheme. Cancel or retarget queued writes when the active folder/stage changes; never apply a write to the next folder by accident. Check audio/music initialization before **writing** Music/FX in the front end: the music getter reads a global, but the setters may touch not-yet-ready playback state.
|
||||
4. Make scope visible to the player: use a dedicated **Bond file** section/page in `MENU_PC_OPTIONS` for GE watch options, with an **explicit Bond-file chooser**; never trust `selected_folder_num` on that screen (frontoptions init even assigns it a fallback). F10 in-stage targets the active file with no chooser. **D354 supersedes the "do not silently choose folder 1" stance:** all non-stage contexts (options screen *and* F10 on file select) lazily default to *explicit pick > `selected_folder_num` > first valid folder* so the section works transparently; an explicit "Edit file" cycle (still including "none") always overrides, and a game-thread `fileBuildWriteNewSave(FOLDER1)` one-shot builds a blank file 1 when no valid save exists. Switching chooser folders must not change the game's active file. D350 reads saved fields directly without calling `fileLoadSettingsForFolder` (which would audibly change front-end music). Unvalidated/absent slots are skipped. Test new/blank files in a normal boot as well as the D299 `GE_STARTMENU` shortcut.
|
||||
5. **Approved port-side persistence:** resolve the actual save-slot pointer with `fileGetSaveForFoldernum(folder)`; `saves[folder]` is **not** generally the folder's physical slot (`fileWriteSave` requires a pointer into `saves[SAVESLOT1..SAVESLOTRAMROM)`). For F10, use a validated in-place path: serialize the live settings into a **temporary copy** with `fileSaveSettingsForFolder(©)`, then copy only the PC-UI-edited field/bit(s) into the real save-slot pointer and call `fileWriteSave(save)` with existing CRC/EEPROM handling. In the front end, where no GE player is active, merge only the selected field directly into the chosen saved record instead of loading it into live watch state. Retain `OPTION_CONTROLTYPE` from the real save: the port reasserts Honey/Solitaire every poll and must not overwrite stored controller style. This field-scoped merge also prevents a watch-only edit to a *different* field from piggybacking onto the next PC-UI save, preserving the approved session-only asymmetry. Validate the path against absent/free slots and ensure the live state belongs to **only** the chosen folder (do not load or copy values from a different folder). Never call `fileClearSavefileForFolder` as a generic settings save. Toggle/enum changes persist on value change; slider drags persist on release, not each detent. F10 requests must reach the correct game-thread/file target before write. The GE watch's own edits remain session-only. Test stage load, file switch, copy/erase, and restart for cross-file contamination; no N64 game-code changes. **Known erase edge:** GE's `fileClearSavefileForFolder` serializes *current* live settings into the reset file; after editing B, erasing A could copy B's live options into wiped A (also possible on N64 after watch edits). Document/test, do not change game code.
|
||||
6. For future per-section reset-to-defaults: ini rows reset from registered PC defaults; watch rows reset to **`BLANKSAVEDATA`/`DEFAULT_OPTIONS`** (`bondconstants.h:2151`, `file2.h:23`) for the selected file, not from a global ini snapshot. New saves start with Music/FX 0xFF (full volume) and auto-aim/sight/look-ahead/ammo enabled. Separate confirmation and persistence policy before wiring a button.
|
||||
|
||||
## Sequence / review gates
|
||||
|
||||
- **Gate A — behavior audit + D349:** re-verify the erase-only write and stage-start reload (including indirect save paths); record the ground-truth quirk, defaults, stored control-type preservation, erase cross-file edge and exact API for each row. Trace folder-to-physical-slot mapping, FX/music setter readiness, watch-to-gameplay consumers, composed inversion and the F10/game-thread boundary. Approved policy: explicit chooser + PC-UI edits persist; watch edits remain session-only.
|
||||
- **Gate B — foundation: IMPLEMENTED in D350**, including explicit file chooser, CRC guard, stage/file identity and thread-safe F10 command/snapshot path. D352 (review of `1303c0eb`) fixed the remaining robustness gaps: pending commands now survive stage transitions (hold-not-drop), the UI-thread chooser state is spinlocked with a front-screen guard, the queue coalesces to one pending command per field, and every silent no-op path logs a warning. Manual gamepad/mouse playtest still owed.
|
||||
- **Gate C — audio: ROWS IMPLEMENTED in D350**, Music/FX save path headless-verified. Watch↔F10 live and audible checks still owed. Global master gain is a separate, not-yet-implemented M3 item.
|
||||
- **Gate D — watch gameplay: ROWS IMPLEMENTED in D350** for six GE watch booleans; auto-aim and watch look-bit per-file saves headless-verified. Each watch/in-game effect and two-file isolation still need hands-on checks. Screen/Ratio remain a separate product decision.
|
||||
- **Gate E — control style and resets:** design decisions first, then separate work. Avoid exposing a control-style row that the port immediately overwrites. Do not make a global reset wipe per-file watch settings.
|
||||
|
||||
## Acceptance checks
|
||||
|
||||
- Watch changes show in both PC surfaces without reopening/restarting, and PC changes show in the watch; no extra watch-key values in `ge007.ini`.
|
||||
- With two Bond files, change Music/FX and a gameplay toggle in A via PC UI; switch to B, verify B retains its own values; return to A, start a stage, quit/restart and reload, verify A's values. Repeat an edit via F10 while in-stage. Drag sliders: only one save on release, not per detent. PC-UI persistence is a *port QoL test*, not a claim about N64; watch-only edits should still revert on stage reload. Check that stored controller-style bits survive PC saves and that file copy and erase retain GE behavior (including the erase cross-file edge above).
|
||||
- F10 changes are not lost or applied to another file across a stage/menu transition. Check full-range sliders, watch's save quantization, no unexpected audio spikes, and no race/crash on quit.
|
||||
- Menu layouts: use a dedicated Bond file section/page plus Audio; `MAX_PROWS=15` is a **per-page** cap in `frontoptions.c`, with `MAX_PAGES=8`; F10 scrolls. Check the 440x330 front-end page, F10 visible rows, mouse, keyboard, and real pad navigation; ASCII-only new text for region safety. Check NTSC headless captures, widescreen, golden gate, and PAL/JP build where possible (only NTSC ROM locally).
|
||||
- Build/link checks and the normal verification ritual after implementation. Document any game-side change as its own Rule-2 request before editing it.
|
||||
|
||||
## Decisions and remaining scope
|
||||
|
||||
- **Approved:** PC-UI edits persist per selected Bond file; watch-only edits stay session-only. The front screen gets an explicit file chooser; F10 uses the active file. Preserve stored controller type; toggles/enums save on change, slider drags on release. No `src/game` behavior change or N64 save-format change.
|
||||
- **Still separate product decisions:** whether/how to expose GE Screen/Ratio alongside PC video settings; whether to expose control style at all given the port's forced mapping (deferred); whether Bond-file reset needs its own confirmation rather than being bundled with global PC defaults.
|
||||
- **Landed 2026-09-27 (D352/D353, port-only):** bridge robustness fixes (D352) and the Turok-style row-table regrouping (D353: six sections — DISPLAY, GRAPHICS, BOND FILE, MOUSE / AIM, CONTROLLER, GAME; the music/FX sliders sit in BOND FILE; the page title annotates the selected file; `buildPages` overflow logs).
|
||||
- **Landed 2026-09-27 (D354, port-only):** front-end F10 routing + transparent default Bond-file selection (see item 4 above); in-stage F10 verified working by the `GE_WSPROBE` probe.
|
||||
- **Landed 2026-09-27 (D355, port-only):** the Music commit now also resyncs the in-stage BGM (track 1) via `musicTrack1ApplySeqpVol`, plus a once-per-activation resync on level entry; by-ear confirmed. N64 watch path unchanged.
|
||||
- **Landed 2026-09-27 (D356, port-only):** the settings regroup (plan `docs/dev/D356-SETTINGS-REGROUP-PLAN.md`): Turok-style pages renamed to functional sections (INPUT/GAMEPLAY/GRAPHICS/AUDIO/VIDEO, all below MAX_PROWS), the redundant N64-watch rows (`Bond.Look`, `Bond.AimControl`) commented out of the menu surface (keys + watch paths stay live, D181/D216 style), the `__HdrBond`/`__BondFile` rows retired for the front-only top save-file row, per-file sections annotated `"(File N)"` via the `saveScoped` flag, and the Turok reset rows replaced by a real per-section Reset-to-defaults (edge-triggered two-step arm->confirm within 3 s; watch rows -> BLANKSAVEDATA through the D352 commit path, ini rows -> the port's C initializers; `__Resolution` a documented exclusion). GE_WSPROBE_RESET front + in-stage probes headless-verified.
|
||||
- A standalone AUDIO section (master volume + the config-side volume keys) and per-section reset-to-defaults (Gate E) are the next settings-scope items; key rebinding stays Phase 4.
|
||||
@@ -1,26 +1,19 @@
|
||||
---
|
||||
title: Agentic Development
|
||||
description: A case study in two AI coding agents porting a Nintendo 64 game to PC; the setup, timeline, handoff workflow, and an honest assessment of what worked.
|
||||
---
|
||||
# Agentic development: two AI coding agents porting GoldenEye 007
|
||||
|
||||
## Agentic development: two AI coding agents porting GoldenEye 007
|
||||
|
||||
*How this port was actually built: a local open-weight model (Qwen 3.8 on a
|
||||
single RTX 5090) and a hosted frontier model (Claude / Claude Code) handing
|
||||
work back and forth through shared written notes, under one person's
|
||||
part-time direction. The goal, the setup, the timeline, and an honest read on
|
||||
what did and didn't work.*
|
||||
|
||||
> *Snapshot as of the v0.3.0 milestone (20 Sep 2026; findings through D321).
|
||||
> v0.4.0 (28 Sep) extended the timeline with the options/input work — the
|
||||
> README's "How it was made" section carries the final project numbers.*
|
||||
*A case study in AI-agent collaboration on a large, low-level codebase. A
|
||||
GoldenEye 007 Nintendo 64 → desktop port, taken from "builds a ROM" to
|
||||
"playable front end" in about two weeks — driven mostly by two coding agents (a
|
||||
local open-weight model, Qwen 3.8, on a single RTX 5090, and a hosted frontier
|
||||
model, Claude / Claude Code), handing work back and forth through shared
|
||||
written notes under one person's part-time direction. This is the goal, the
|
||||
method, the timeline, and an honest read on what did and didn't work.*
|
||||
|
||||
## Contents
|
||||
|
||||
- [Why this project exists](#why-this-project-exists)
|
||||
- [The setup](#the-setup)
|
||||
- [Timeline](#timeline)
|
||||
- [By the numbers](#by-the-numbers); [Commit velocity](#commit-velocity) · [Who did what](#who-did-what)
|
||||
- [By the numbers](#by-the-numbers) — [Commit velocity](#commit-velocity) · [Who did what](#who-did-what)
|
||||
- [The handoff workflow](#the-handoff-workflow)
|
||||
- [Assessment](#assessment)
|
||||
|
||||
@@ -28,7 +21,7 @@ what did and didn't work.*
|
||||
|
||||
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
|
||||
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
|
||||
@@ -43,101 +36,108 @@ an LLM:
|
||||
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 — 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://github.com/earendil-works/pi)** coding agent. Unsloth Desktop (Unsloth's local model runtime, which can drive agents such as Claude Code) 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 |
|
||||
| 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 (Unsloth's local model runtime, which can drive agents such as Claude Code) 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 |
|
||||
| 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 the
|
||||
rest; the same "escalate when stuck" move applied at every level.
|
||||
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–September 2026).
|
||||
All dates from this repository's own commit history (August 2026).
|
||||
|
||||
```text
|
||||
# phase timeline (from the port's own commit dates)
|
||||
Aug 16 day 0 fork + PC-port scaffolding
|
||||
Aug 20 day 4 full compile + link (~230 TUs); boot to window
|
||||
Aug 22 day 6 first rendered frames; offline asset-conversion pipeline
|
||||
Aug 24 day 8 entire intro renders (logos -> gun-barrel -> cast)
|
||||
Aug 27 day 11 Claude joins (handoff workflow begins)
|
||||
Aug 29 day 13 21 solo levels load + render + unattended no-crash; SDL input layer
|
||||
Aug 30 day 14 front-end flow (menu -> briefing -> start)
|
||||
Aug 31 day 14 file-backed EEPROM saves
|
||||
Sep 02 audio mixer (music + SFX)
|
||||
Sep 14 Steam Deck hardening + F10 overlay QoL
|
||||
Sep 18 final fix sweep (D309-D321) + release review
|
||||
Sep 20 day 35 v0.3.0 released: full campaign at Agent difficulty, audio
|
||||
complete, bundles for Windows, Linux and Steam Deck
|
||||
```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 to 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 to briefing to start) :m7, 2026-08-30, 1d
|
||||
File-backed EEPROM saves :2026-08-31, 1d
|
||||
```
|
||||
|
||||
Roughly **two weeks**, one person part-time, took the decompilation from
|
||||
- **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". The rest of month one (29 Aug –
|
||||
20 Sep) took it from there to a public release: the 21-level sweep to
|
||||
full-campaign completion, the audio mixer, input and Steam Deck polish,
|
||||
widescreen FOV scaling, and the v0.3.0 bundles — with a set of cosmetic
|
||||
issues documented rather than outstanding.
|
||||
through the front end into the early game" — with audio and a set of cosmetic
|
||||
issues still outstanding.
|
||||
|
||||
## By the numbers
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Calendar time | 36 days (16 Aug – 20 Sep 2026), one person part-time |
|
||||
| Commits on the port | ~835 |
|
||||
| Root-caused bugs logged | `D1`–`D321` in [`findings.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/dev/findings.md), 199 tracked entries in the index (some labels later merged or withdrawn) |
|
||||
| Handoff sessions | 190+ (`M-2` … `M-192`, and counting) |
|
||||
| Game-source files given `#ifdef PORT` ABI edits | 71 files, ~385 blocks |
|
||||
| New port-layer / tooling files | ~119 |
|
||||
| Port layer | ~24,450 lines C/C++ (`port/`) |
|
||||
| PC asset-conversion tooling | ~7,180 lines Python (`tools_pc/`) |
|
||||
| Outcome | **v0.3.0 released**: full campaign playable end to end (all 21 missions playtested at Agent difficulty) at a steady 60 fps, full audio (music + SFX), file-backed saves; Windows, Linux and Steam Deck; known cosmetic defects documented in the finding log |
|
||||
| Calendar time | 16 days (16 Aug – 1 Sep 2026), one person part-time |
|
||||
| Commits on the port | ~223 |
|
||||
| Root-caused bugs logged | 162 (`D1`–`D169` in [`findings.md`](findings.md); some later merged or withdrawn) |
|
||||
| Handoff sessions | ~33 (`M-2` … `M-33`) |
|
||||
| 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
|
||||
|
||||
```text
|
||||
# commits per week (week start)
|
||||
8/16 ## 20
|
||||
8/23 ########### 109
|
||||
8/30 #################### 203
|
||||
9/6 ############## 141
|
||||
9/13 ###################### 220
|
||||
9/20 # 6 (release day only)
|
||||
(each # ~ 10 commits)
|
||||
```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, 9]
|
||||
```
|
||||
|
||||
Weekly totals (the final week is release day only). The step up from the
|
||||
8/23 week onward is the collaborative phase in full swing, including the
|
||||
parallel multi-agent "bursts"; the 9/13–9/19 week peaks on a single-day
|
||||
84-commit burst (9/15) when the Steam Deck playtest-feedback batch landed
|
||||
at once.
|
||||
Days with no commits are omitted. The 8/25–8/26 gap is between phases; the
|
||||
step up from 8/28 onward is the collaborative phase in full swing, including
|
||||
the parallel multi-agent "bursts" used near the end.
|
||||
|
||||
### Who did what
|
||||
|
||||
```text
|
||||
# commits by agent (raw count, snapshot through early September)
|
||||
Claude ################ 161 (72%)
|
||||
Local model (Qwen 3.8/pi) ###### 62 (28%)
|
||||
(each # ~ 10 commits)
|
||||
```mermaid
|
||||
pie showData title "Commits by agent (raw count)"
|
||||
"Claude" : 161
|
||||
"Local model (Qwen 3.8 / pi)" : 62
|
||||
```
|
||||
|
||||
(Phase A, the first 26 commits, to 24 Aug, was entirely the local model,
|
||||
solo; from 27 Aug on the two agents worked in parallel. The raw tally was
|
||||
not re-run for the final third of the project; treat the milestone-weighted
|
||||
estimate below as the better number.)
|
||||
(Phase A — the first 26 commits, to 24 Aug — was entirely the local model,
|
||||
solo; from 27 Aug on the two agents worked in parallel.)
|
||||
|
||||
| Milestone / workstream | Primary agent | Weight |
|
||||
|---|---|---|
|
||||
@@ -147,26 +147,25 @@ estimate below as the better number.)
|
||||
| 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`–`D169`) | Claude | large |
|
||||
| 21-level crash sweep — ~12 crash classes root-caused (`D88`–`D169`) | 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 | n/a |
|
||||
| Every build, playtest, frame capture, integration, and direction | human | n/a |
|
||||
| The hardest structural bugs (matrix handedness, format specs) | Claude | — |
|
||||
| Every build, playtest, frame capture, integration, and direction | human | — |
|
||||
|
||||
**Estimated effort split.** The raw commit count above (~28% local / ~72%
|
||||
Claude) undercounts the local model: Phase A landed the build system and boot
|
||||
chain in relatively few, large commits, and the local model kept contributing
|
||||
~15–20% of Phase B in parallel. Weighting by milestone difficulty rather than
|
||||
raw commits; the foundation is a heavier third of the project than its commit
|
||||
share suggests; the developer's estimate is roughly:
|
||||
raw commits — the foundation is a heavier third of the project than its commit
|
||||
share suggests — the developer's estimate is roughly:
|
||||
|
||||
```text
|
||||
# agent effort, milestone-weighted
|
||||
Claude ############ 60%
|
||||
Local model (Qwen 3.8/pi) ######## 40%
|
||||
(each # ~ 5%)
|
||||
```mermaid
|
||||
pie showData title "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
|
||||
@@ -180,48 +179,48 @@ result worth taking away.
|
||||
|
||||
This is the part worth paying attention to.
|
||||
|
||||
```text
|
||||
# the handoff workflow
|
||||
```mermaid
|
||||
flowchart LR
|
||||
H["Human: direction, integration,<br/>build + playtest verification"]
|
||||
C["Claude / Claude Code<br/>frontier, hosted"]
|
||||
Q["Qwen 3.8 via pi<br/>open-weight, local RTX 5090"]
|
||||
D[(Shared artifacts:<br/>HANDOFF.md · findings.md · porting-notes.md)]
|
||||
|
||||
HUMAN
|
||||
direction, integration, build + playtest verification
|
||||
| scopes task, budget, files | scopes task, budget, files
|
||||
v v
|
||||
CLAUDE (hosted) QWEN 3.8 / pi (local, RTX 5090)
|
||||
| patch + write-up | patch + write-up
|
||||
+--------------+ +--------------+
|
||||
v v
|
||||
SHARED ARTIFACTS (both agents read + append)
|
||||
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 + write-up| H
|
||||
Q -->|patch + write-up| 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
|
||||
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. 321 numbered `D`
|
||||
entries by v0.3.0 (199 tracked in the index; some labels merged or
|
||||
withdrawn), 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
|
||||
2. **`findings.md`** — the chronological finding log. 162 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
|
||||
- **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
|
||||
- **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
|
||||
- **the human owns verification** — building, running the game, capturing a
|
||||
frame, and judging it against N64 reference footage was never delegated.
|
||||
|
||||
## Assessment
|
||||
@@ -230,22 +229,23 @@ Honest notes, for anyone weighing whether this transfers.
|
||||
|
||||
**Worked well**
|
||||
|
||||
- The **local model carried the groundwork phase**; build system, boot
|
||||
- The **local model carried the groundwork phase** — build system, boot
|
||||
chain, OS shims, and the offline-converter architecture. None of it was
|
||||
rewritten later.
|
||||
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.
|
||||
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,
|
||||
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
|
||||
@@ -255,53 +255,10 @@ Honest notes, for anyone weighing whether this transfers.
|
||||
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
|
||||
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:** see
|
||||
[`docs/ROADMAP.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/ROADMAP.md),
|
||||
the single tracker for open work.
|
||||
|
||||
## Since the two-agent phase
|
||||
|
||||
The two-agent setup above is the **initial phase**, snapshotted at v0.3.0
|
||||
(the project has since shipped v0.4.x and is heading to v0.5.0; the model
|
||||
and harness record below is current through that point).
|
||||
|
||||
Since then the emphasis has shifted from "the two models that built it" to
|
||||
**testing a lot of new models and harnesses**, to find what's fastest and
|
||||
cheapest per task. The long-time local workhorse stayed **Unsloth's Qwen 3.8 27B**;
|
||||
in the final week that broadened into a menu, and the **current standard is
|
||||
NInfer + Strata**. On the hosted side, the Claude orchestrator stepped up from
|
||||
**Sonnet 5 / Opus 5** to the **5.5 tier** (Sonnet 5.5 / Opus 5.5, out in the
|
||||
last few weeks), which has driven the most recent work. The local menu:
|
||||
|
||||
- **Qwen 3.8 27B** ([Unsloth](https://huggingface.co/unsloth/Qwen3.8-27B-GGUF),
|
||||
`UD-Q4_K_XL`) — the primary local workhorse for most of the port's life, doing
|
||||
nearly all of the local groundwork on its own. A larger-context
|
||||
[`ISTA-DASLab/Qwen3.8-27B-GSQ-RCO-GGUF`](https://huggingface.co/ISTA-DASLab/Qwen3.8-27B-GSQ-RCO-GGUF)
|
||||
(`IQ3_S`) was also briefly trialed for bigger-context workloads, then shelved
|
||||
(little use since v0.2.0).
|
||||
- **NInfer (Windows)**
|
||||
([natpate/ninfer-windows](https://github.com/natpate/ninfer-windows), v0.9.0,
|
||||
CUDA 13.1 — a standalone Windows port of the NInfer engine, with MTP /
|
||||
DFlash2 speculative decoding) — ran **Qwen 3.8 27B** in base, NVFP4 and
|
||||
NVFP4-full artifacts; trialed in the final week, now half of the standard
|
||||
and the fast day-to-day local driver.
|
||||
- **Qwen 3.8 Flash-Next** via
|
||||
[Strata](https://github.com/Niko1221/Strata) (Niko1221's MIT-licensed
|
||||
C++/CUDA/HIP engine) — a 125B mixture-of-experts model that normally needs a
|
||||
server, run **locally on a single gaming GPU** (IQ1_M, 1-bit) for the
|
||||
biggest-context coding passes. The other half of the standard.
|
||||
- **[claude-code-delegate-local](https://github.com/fegone/claude-code-delegate-local)**
|
||||
— an MCP server that let the Claude Code orchestrator and the local models
|
||||
talk, delegating Claude's subagents to the local engines. A nice tool, but
|
||||
**mostly deprecated now**: hand-offs between agents are done by hand for now,
|
||||
pending a better parallel-agent handoff system that drops the MCP overhead.
|
||||
- **GPT-6 (Sol)** via [OpenRouter](https://openrouter.ai) — a frontier hosted
|
||||
model, **trialed** to see how it held up (not the hardest-work tool).
|
||||
|
||||
The practical effect: the project stopped depending on any one model or its
|
||||
usage limits. The local GPU is now the default path for high-volume work, with
|
||||
hosted frontier models reserved for the problems the local models can't close.
|
||||
**Still outstanding** (parked below crash/level work): audio is not
|
||||
implemented; several front-end 3D transforms and some text rendering are
|
||||
wrong. See [`GRAPHICS-BACKLOG.md`](GRAPHICS-BACKLOG.md).
|
||||
@@ -1,431 +0,0 @@
|
||||
# GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
|
||||
# Grep this to pick which findings.md entries to read; it is not authoritative.
|
||||
label,bucket,one_liner,status_raw
|
||||
D24-D30,RESOLVED,green threads / kernel / crash handler,resolved
|
||||
D31-D42,RESOLVED,"boot ABI: ANIM_DATA, music seq-table, libaudio banks, Globalimagetable rebase, GL context, rsp toggle",resolved
|
||||
D43-D50,RESOLVED,"model-file loading → offline sidecar ""Plan B"" (d43_emit.py); D50.6 texCopyGdls full-slot copy",resolved
|
||||
D51-D58,RESOLVED,"font pixeldata, osGetCount tick rate, model RW pools, cseq BE header, synth slots, RLE menu bg, watch raw offsets, gun-barrel DL",resolved
|
||||
D59-D68,RESOLVED,"intro render: blood-RLE clobber, DMA validate, OSMesgQueue, HEADS/BODIES sentinels, romCopy width, image_entry, Globalimagetable BE→LE",resolved
|
||||
D70-D74,RESOLVED,"intro-logo pixels: C-array bswap, UV path, sinf/cosf DVAL(), texture-import truncation",resolved
|
||||
D75,FIXED,front-end 3D model transforms (D77 audio split out below —,FIXED 2026-10-02 (branch fix/fidelity-gaps; by-eye check owed):
|
||||
D76 · D164,FIXED,Legal screen only draws line 1 —,"FIXED — this row and the ## D164 entry below were stale; the proposed one-line #ifdef PORT fix was actually committed 8dfc578f4 (2026-08-31), well before this correction (2026-09-18)."
|
||||
D159,FIXED,"front-end wallet-Bond photo ""interlaced""/combed (RC1 / D149) —",FIXED (#ifdef PORT no-op the swap in image.c)
|
||||
D161,FIXED,Depot (-level_30) ceiling = bright-blue speckle + radial rays (B2).,FIXED (port/fast3d/gfx_pc.cpp)
|
||||
D165 · D166,FIXED,"Input-layer polish (M-31, port-only):",FIXED
|
||||
D180 · D181,FIXED,Native-PC input pass (QoL run):,FIXED (2026-09-26 bookkeeping:
|
||||
D186,CLOSED,"Video.FpsCap throttles the sim, not just presentation (M-39 diag, M-42 clamp).",CLOSED (2026-09-28 bookkeeping:
|
||||
D187,FIXED,"HUD ammo-type/weapon icon vanished at -O2 (M-46 root-cause, M-47 fix + verify).","FIXED (src/game/gunfire.c, #ifdef AVOID_UB); PR #19"
|
||||
D188,FIXED,Linux -O2 boot crash:,"FIXED (src/libultrare/libc/xprintf.c, src/sprintf.c, #ifdef PORT); PR #22"
|
||||
D189,FIXED,Linux -fstack-protector aborts on the decomp's latent stack-buffer overruns (M-48) —,"FIXED (CMakeLists.txt, src/game/stan.c #ifdef PORT); PR #23"
|
||||
D190,CLOSED,"Linux -level_45 SIGSEGV at load in add_ptr_to_objective (objective.c:64) (M-48, open).","CLOSED — not a port bug; level 45 is MP-only and has no solo setup file in the ROM (root-caused M-105, reproduced on Windows)."
|
||||
D191,FIXED,Windows -O2 release-bundle crash:,FIXED + PLAYTEST-VERIFIED (M-79).…
|
||||
D193,FIXED,NPC AI locomotion is too slow —,FIXED + user-playtest-verified (M-81) —…
|
||||
D194,FIXED,Mouse input needs another tuning pass —,FIXED (2026-09-26: user reports the v0.3.0→v0.4.0 mouse changes feel solid and glitch-free; code-side validation owed).
|
||||
D195,FIXED,"Transparency / alpha surfaces broken on Control (M-49, user playtest) —","FIXED (port/fast3d/gfx_pc.cpp, gfx_sp_vertex) —…"
|
||||
D197,CLOSED,"Character face/head textures wrap around the head, seen on Silo (M-49, user playtest).",CLOSED — user playtest v0.2.0-pre
|
||||
D198,FIXED,alAdpcmPull/_decodeChunk used K0_TO_PHYS (an unconditional & 0x1FFFFFFF mask) on 64-bit heap pointers (src/libultra/audio/load.c).,FIXED (src/libultra/audio/load.c) —
|
||||
D199,FIXED,Phase-3 software audio mixer:,FIXED (2026-09-26 bookkeeping:
|
||||
D200,FIXED,-level_09 segfault: reverb's ALDelay.input/output are u32 —,FIXED — 5-line cast in reverb.c (this session)
|
||||
D196,FIXED,"OS mouse cursor stays visible after closing the F10 options overlay in click-to-lock mode (M-49, user playtest).","FIXED (6e758f1d, merged to main) —…"
|
||||
D192,CLOSED,Front-end mission/level-select grid pointer still can't reach the outer cells —,"CLOSED — the buggy mode 0 was removed entirely in the v0.2.0 prep pass (see D239); click-to-lock is now the only capture mode, so the unreachable-cells path no longer exists"
|
||||
D221,FIXED,SELECT FILE mouse hit-box offset —,"FIXED (M-91) — explicit {min,max} coord2d pairs under #ifdef PORT; human mouse-playtest confirmed."
|
||||
D222,FIXED,High Video.FovScale breaks culling —,"FIXED, awaiting visual verification (M-121)."
|
||||
D223,FIXED,QoL ask: mouse-wheel weapon cycling should be directional like a normal PC shooter —,"FIXED (M-105, port/src/input.c) —…"
|
||||
D224,CLOSED,"QoL gap (M-87, from a PD-PC-port survey):",CLOSED (2026-09-28 bookkeeping:
|
||||
D225,FIXED,"Dev-tooling QoL (M-87, user ask):","FIXED (2026-09-18) — GE_DEBUG_UNLOCKALL=1 short-circuits fileGetIsCheatUnlocked() (src/game/file2.c, #ifdef PORT, cached getenv) to always return TRUE; default off, no save mutation."
|
||||
D226,FIXED,"QoL ask: player-adjustable HUD scale for the ammo counter, bottom-left pickup/item status text, and top-of-screen dialogue/subtitle text.","FIXED (2026-09-24): Game.HudScale 75–150% (user-verified, capped at 150) via fast3d G_HUDSCALE_EXT rect scaling about per-element anchors; dialogue/bottom messages width-capped."
|
||||
D227,FIXED,Sky renders as two independently-moving halves —,"FIXED (M-100b, PR #43) —"
|
||||
D229,FIXED,"IsWater-level water renders green, pulses blue<->green (M-101; Frigate -level_26).","FIXED (port-layer) — ZLIB+CI8 water image now imports via the CI8 palette path (gfx_pc.cpp); Frigate verified live (""looks better""); sweep + Surface 2 by-eye still owed."
|
||||
D230,FIXED,In-level music quality:,"FIXED (M-121, v0.2.0) —"
|
||||
D231,CLOSED,"QoL batch: mute-on-focus-loss + screenshot-hotkey productization (M-105, roadmap Tier 3, port-only).","CLOSED — PARKED/DEPRIORITIZED (user, 2026-09-25)."
|
||||
D233,FIXED,All levels (intermittent):,"FIXED (port/fast3d/gfx_pc.cpp, gfx_sp_tri1) —…"
|
||||
D234,FIXED,"Dam: guard towers along the dam structure invisible except the first one (M-106, user QA report).",VISUALLY VERIFIED FIXED (M-108) —…
|
||||
D244,OPEN,Self-hosted CI runner link failure:,"OPEN, not yet fixed. Next…"
|
||||
D228,FIXED,AK47 white missing-texture patches after D217's texpool fix —,"FIXED (port/fast3d/gfx_pc.cpp, palette_to_rgba32) —"
|
||||
D204,FIXED,PC audio ran ~2 % below real time permanently (AI feedback loop's 3 ms setpoint vs PC jitter).,"FIXED (port/src/audio.c, port/src/libultra.c, port/include/audio.h) —"
|
||||
D203,CLOSED,"Steam Deck (SteamOS, x86_64 Linux) v0.1.0 release bundle:",CLOSED — root cause + fix in D253.
|
||||
D201,FIXED,"src/libultra/audio/bnkf.c bank/instrument/sound/wavetable relocation offsets were s32, truncating the real 64-bit base pointer.",FIXED (src/libultra/audio/bnkf.c) —
|
||||
D202,FIXED,"Silenced PPK ""slap"" + broader real-time mixer corruption (M-52→M-67).","FIXED (2026-09-26, user by-ear:"
|
||||
D206,FIXED,PC played every SFX one bank slot too high —,"FIXED (M-76, src/snd.c:1001, #ifdef PORT) —"
|
||||
D207,CLOSED,"Alarm klaxon starves combat SFX and doesn't recover after it stops (surfaced by D206, M-77).",CLOSED (2026-09-28 bookkeeping:
|
||||
D208,FIXED,Player weapon-fire sound goes permanently silent mid-firefight (all other SFX unaffected) —,FIXED + user playtest-verified (M-78d) —
|
||||
D209,FIXED,Every AI character in the game is locked to a *walk* animation regardless of the speed the ailist commands —,FIXED + user-playtest-verified (M-81).
|
||||
D210,FIXED,chrToPatrol() leaves act_patrol.lastvisible60 uninitialised on PC —,"FIXED (chraction.c:3872, #ifdef PORT → self->act_patrol.lastvisible60 = -1;, N64 line under #else).…"
|
||||
D205,CLOSED,"PC plays wrong/extra SFX for gameplay events (gunfire slap layer, explosion→scream, armour pickup, melee→Klobb idx 106) —",CLOSED (M-78) — root cause was D206; all four symptoms resolved + user-confirmed by ear.
|
||||
D152+,FIXED,D152 addendum (M-31):,"FIXED (src/snd.c, port/src/libultra.c, #ifdef PORT) —"
|
||||
D168,FIXED,GE_PCDUMP / F12 PPM captures were vertically flipped —,FIXED (port/fast3d/gfx_opengl.cpp); D114 / D116 reclassified as capture-orientation artifacts (below).
|
||||
D169,FIXED,Front-end mouse pointer can't reach the outer cells of the mission/level-select grid —,"FIXED (M-33, port/src/input.c, port-only, no #ifdef PORT)."
|
||||
D178,FIXED,Pre-mission briefing screen:,FIXED (M-36) — high confidence
|
||||
D175,CLOSED,In-game stutter / brief hang during normal play —,"CLOSED (v0.4.0 campaign sign-off 2026-09-28, user:"
|
||||
D176,FIXED,"Surface exterior renders wrong (-level_36), two independent defects.",FIXED (2026-09-29 bookkeeping:
|
||||
D177,FIXED,Ladders non-functional —,"FIXED (M-36) — high confidence; interactive climb test owed Owed climb test considered cleared by the 2026-09-28 v0.4.0 campaign sign-off (ROADMAP §3 bookkeeping, 2026-10-01); reopen if seen."
|
||||
D172,FIXED,"Bullet-impact / blood / spark particles render magenta or cyan instead of dark red (M-82, ROOT CAUSE FOUND + FIXED).",FIXED (port/fast3d/gfx_pc.cpp gfx_lod_tile_offset)…
|
||||
D174,CLOSED,"""No blood effect"" (user QA report).","CLOSED — faithful (2026-09-26, user side-by-side on 1964:"
|
||||
D183,CLOSED,"M-36 Family A (""texture line/pitch shear"") is DISPROVEN for the Surface repro (-level_36).","CLOSED (2026-09-26, user-approved:"
|
||||
D173,PARTIAL,Third-person Bond model spawns too high —,PARTIAL (2026-09-29 bookkeeping:
|
||||
D179,FIXED,Packaged build crashes after the logos —,"Diagnosed, not fixed —"
|
||||
D180,FIXED,"Native-PC input pass (M-XX QoL run, qol/native-pc-input-menu, port-only).",FIXED (2026-09-26 bookkeeping:
|
||||
D181,FIXED,Game.ScreenShakeIntensity —,FIXED (2026-09-26 bookkeeping:
|
||||
D184,FIXED,F10 port-layer options overlay —,FIXED (2026-09-26 bookkeeping:
|
||||
D211,FIXED,"PC FOV slider — Video.FovScale (M-83 QoL wave, port-only, WIDESCREEN-FOV-PLAN.md Phase 4).",FIXED (2026-09-26 bookkeeping:
|
||||
D212,FIXED,Anisotropic-filtering config wiring —,FIXED (2026-09-26 bookkeeping:
|
||||
D213,FIXED,On-screen FPS readout —,FIXED (2026-09-26 bookkeeping:
|
||||
D214,FIXED,"Keyboard rebinding — [Input.Bind] config section (M-83, port-only, PD parity, Phase 4 debt).",FIXED (2026-09-26 bookkeeping:
|
||||
D215,FIXED,1P weapon viewmodel never drew in-level —,"FIXED (src/game/gunfire.c, #ifdef PORT)."
|
||||
D216,FIXED,"Game.SkipIntro — boot straight to the file-select menu (M-83, port + one #ifdef PORT game-code line, PD parity, Phase 4 quick win).",FIXED (2026-09-26 bookkeeping:
|
||||
D217,FIXED,1P weapon viewmodel + 3P Bond model textures garbled —,FIXED + merged (PR #47 + PR #49); white residual split out as D228.
|
||||
D218,FIXED,Wide FOV exposes the per-level draw-distance / fog boundary —,FIXED (2026-09-26 reclassified; 2026-09-29 default corrected:
|
||||
D219,RESOLVED,"PPK explosions render purple on Dam (M-87, user QA, ""not 3D but noting"").","RESOLVED (M-115, 2026-09-12) for the original PPK-purple symptom."
|
||||
D220,CLOSED,"1P muzzle flash position shifts upward at higher Video.FovScale (M-87, user QA, Jungle/M16).","CLOSED — merged into D303 (2026-09-26, user:"
|
||||
D85,CLOSED,Room primary/secondary DL → texLoadFromGdl garbage.,"CLOSED (2026-09-26, user-approved:"
|
||||
D86 · D87,RESOLVED,modelInitRwData truncated ptr · attract-demo BE ramromfilestructure.,resolved
|
||||
D88.4,RESOLVED,propDefs polymorphic record stream not byteswapped → setupDoor crash.,resolved (d88_propdefs.py); layout audit M-20 → D132
|
||||
D132,CLOSED,D88 propDefs layout audit (M-20):,CLOSED (2026-09-28 bookkeeping:
|
||||
D157,FIXED,"Campaign never unlocks the next level (M-30, user bug —","FIXED (src/bondtypes.h, #ifdef PORT —…"
|
||||
D160,FIXED,Dam level-end cutscene (Bond off the dam / bungee) never plays —,"FIXED (user, 2026-09-26:"
|
||||
D89-D92,RESOLVED,"stage-load→frame: stan zero-fill overrun, portal address trunc, chr/AI spawn ptrs",resolved
|
||||
D93-D102,RESOLVED,struct-pun / hardcoded-size pass:,resolved (D95 pool bump partial)
|
||||
D103-D107,RESOLVED,"BUNKER1 viewport height, depth-buffer clear (G_CLEAR_DEPTH_EXT), portal near-plane, LOD mip tile",resolved
|
||||
D108-D112,RESOLVED,skeletal models: d43_emit.py put_f32 byte-reversal bug,resolved
|
||||
D113-D116,FIXED,portal BFS ok · matrix chain ok · player raw-offset audit + gunfire.c THROW* · HUD text X-mirror,"D114/D116 CLOSED — NOT A BUG (M-33, see D168):"
|
||||
D117,CLOSED,"Frame nondeterminism root-caused; GE_DETERM attempted, not achieved.",CLOSED (2026-09-28 bookkeeping:
|
||||
D118,RESOLVED,"SDL input layer; M-24 mouse-look rework (mode-aware map, real INI); D118b/c FIXED.",resolved (D118a residual)
|
||||
D150,FIXED,watch OBJECTIVES / BRIEF page crash:,"FIXED (src/str.c #ifdef PORT); interactive re-verify pending Owed re-verify considered cleared by the 2026-09-28 v0.4.0 campaign sign-off (ROADMAP §3 bookkeeping, 2026-10-01); reopen if seen."
|
||||
D260-D265,CLOSED,"v0.2.1 hotfix batch: D260 wheel direction swap (fixed) · D261 watch-menu nav auto-repeat (fixed, feel-check owed) · D262 watch item-preview model missing → roo…",CLOSED (2026-09-26 bookkeeping:
|
||||
D266-D269,CLOSED,"D236/D265 wall, second pass:",CLOSED (2026-09-26 bookkeeping:
|
||||
D270,CLOSED,Backfilled (M-132) — this label was never given its own row; only code comments and the HANDOFF archive documented it.,CLOSED (2026-09-26 bookkeeping:
|
||||
D271,CLOSED,"D236 wall, third pass —",CLOSED (2026-09-29 bookkeeping:
|
||||
D276,CLOSED,"D236 wall, eighth pass —",CLOSED (2026-09-26 bookkeeping:
|
||||
D279,CLOSED,Backfilled — this label was consumed by an undocumented partial session (2026-09-14 ~23:00) that left artifacts in scratch/ (d279_run.log… —,CLOSED (2026-09-26 bookkeeping:
|
||||
D281,FIXED,"Game.AllUnlocked (D257) still breaks audio, and can also break right-mouse aim, when enabled before any save has ever been written (v0.2.1 user testing… —","FIXED (2026-09-23, port/src/libultra.c):"
|
||||
D282,FIXED,"QoL ask (v0.2.1 user testing, 2026-09-15):","FIXED (port/src/input.c, #ifdef not needed —"
|
||||
D283,FIXED,"Steam Deck first-run preset (D-unnumbered in port/src/video.c/main.c, videoApplySteamOSDefaults()) did not apply for a user who added the game via Steam… —","FIXED (2026-09-30, port-only) —"
|
||||
D280,CLOSED,"D236 wall, ninth pass —",CLOSED (2026-09-26 bookkeeping:
|
||||
D275,CLOSED,"D236 wall, eighth pass —",CLOSED (2026-09-26 bookkeeping:
|
||||
D274,CLOSED,"D236 wall, seventh pass —",CLOSED (2026-09-26 bookkeeping:
|
||||
D273,CLOSED,"D236 wall, fifth pass —",CLOSED (2026-09-26 bookkeeping:
|
||||
D272,CLOSED,"D236 wall, fourth pass —",CLOSED (2026-09-26 bookkeeping:
|
||||
D137,FIXED,right-mouse (aim-sight) crash:,FIXED (src/game/gunfire.c #ifdef PORT —
|
||||
D119 · D120,FIXED,guard-attack weapons_held[]->chr pun crash · blood-stain PointUsage[] chain hang,D119 fixed · D120 FIXED (M-30)
|
||||
D120,FIXED,Blood-decal PointUsage[] chain —,FIXED (tools_pc/d43_emit.py).
|
||||
D121,RESOLVED,WS1 frictionless per-level boot (bare -level_XX injects its -m* row).,resolved
|
||||
D122,FIXED,per-level prop/item model-load crash (Dam/Facility/Runway modelLoad/modelInitRwData):,converter fixed; residual chr/fast3d crashes on those levels are separate
|
||||
D123,FIXED,crash class C1: chrIsNotDeadOrShot NULL deref on 6 levels (Dam/Runway/Frigate/Statue/Streets/Cradle).,"converter fixed (OBJ_ID_WORDS); C1 cleared on all 6, residual crashes are fast3d (C2)"
|
||||
D125,RESOLVED,crash classes C3+C6 (Aztec/Bunker2/Surface2):,resolved (M-16b) — line 374 → 8 NULs; all 21 sidecars regen'd; Aztec -level_28 now PASSES (was C3 CRASH).
|
||||
D126,RESOLVED,"crash classes C3r/C4/C6 (Bunker2 -level_27 door7F054FB4 propobj.c:13523, Depot -level_30 prop.c:902, Surface2 -level_43 loadobjectmodel.c:393):",resolved — d88_propdefs.py PROPDEF_PC_BYTES[30/32/33/35]=24 + typed handler; loadobjectmodel.c sizepropdef PORT returns 6.
|
||||
D124,FIXED,crash class C2: fast3d bad texture pointer.,Jungle fixed (port/src/gimgfixup.c); Facility → D130
|
||||
D131,RESOLVED,crash class C2m (Jungle -level_37):,"resolved — seg_addr() restores the module high word for any fallthrough w1 in [0x40000000, 0x70000000) (module fixed-based at 0x140000000, no ASLR; DRAM/KSEG0/segmented/phys all handled earlier).…"
|
||||
D133,RESOLVED,intro render triage (M-20):,"RESOLVED — not a regression, parked (D75/D76, GRAPHICS-BACKLOG.md)"
|
||||
D134,FIXED,"Frame-2 boot hang / sweep ""NO-FRAMES"" flakiness —",FIXED (port/src/libultra.c)
|
||||
D135,FIXED,"Firefight crash — bgTestHitOnObj (propobj.c:8446) is an unported N64 GBI parser (M-22, WS6 playtest).","FIXED (src/game/propobj.c, #ifdef PORT —…"
|
||||
D136,RESOLVED,"Stage-unload crash — objFree obj->prop reads float garbage (M-22, WS6 Facility playthrough).",ROOT-CAUSED → D139 (it was NOT a layout bug —
|
||||
D139,FIXED,Stage-unload crash root cause + fix (M-23).,"FIXED (src/game/cleanup_objects.c, #ifdef PORT —…"
|
||||
D140,FIXED,Pause-menu crash (M-23 open → M-27 fixed).,"FIXED (src/game/bondview.h, src/game/bondview2.c; #ifdef PORT)."
|
||||
D141,FIXED,"Watch-menu item-model crash (M-27), exposed by D140.","FIXED (src/game/gunfire.c, #ifdef PORT)."
|
||||
D152,FIXED,"Mission-failed / objective-failed → permanent black screen (M-28, user bug report —","FIXED (user, 2026-09-26:"
|
||||
D154,CLOSED,"bg.c room-geometry ray/hit-test GBI parser port (M-28, WRITTEN / UNVERIFIED / UNCOMMITTED).",CLOSED (2026-09-28 bookkeeping:
|
||||
D156,FIXED,"Facility outro-cutscene ""hang"" —","FIXED (user, 2026-09-26:"
|
||||
D155,FIXED,"Facility outro-cutscene end → ""hang"" = unclamped deltaFrames spiral (M-29, user bug report).","FIXED (src/game/frametiming.c, #ifdef PORT —…"
|
||||
D153,RESOLVED,"-level_09 frame ~900–1400 0xc0000005 = D117/D134 load flakiness, NOT a bug (M-28).",NOT A BUG — closed. Run anchors on a quiet machine.
|
||||
D151,FIXED,Watch data all blank —,"FIXED (src/bondtypes.h, #ifdef PORT —…"
|
||||
D130,RESOLVED,"crash class C2 (Facility -level_34, Runway -level_35):",resolved — stage all 6 N64 fields in a local f[6] before writing any.
|
||||
D164,FIXED,"Disclaimer / legal screen only draws the first line (D76 root cause, M-31).",FIXED — corrected 2026-09-18:
|
||||
D165,FIXED,"Front-end menu cursor doesn't feel like a mouse pointer (M-31, user bug B5 follow-up).","FIXED (port/src/input.c; Input.MenuPointerMode, Input.HipfirePitchSpeed added)."
|
||||
D166,FIXED,Hipfire mouse pitch is digital while yaw is analog —,FIXED (port/src/input.c).…
|
||||
D235,CLOSED,Wrong textures load after dying/restarting a level —,"CLOSED (user, 2026-09-26:"
|
||||
D236,CLOSED,"Surface 1: 2D billboard trees render as a wall/sheet of tree texture instead of discrete camera-facing tree sprites (M-106, user QA report).","CLOSED (M-198, pass 26, 425237f9; probes stripped 0503d335):"
|
||||
D237,FIXED,"QoL ask (M-106, user QA):","FIXED (2026-09-23): category headers + 5 more settings (SkipIntro, Show FPS, pad invert/deadzone/trigger); wheel scroll already existed."
|
||||
D238,FIXED,"QoL ask (M-106, user QA):",FIXED (2026-09-26: covered by D337/D338 aim work; user reports mouse feel good; code-side validation owed).
|
||||
D239,CLOSED,"QoL ask (M-106, user QA):",CLOSED — executed in the v0.2.0 prep pass (port-only)…
|
||||
D240,CLOSED,"Player gunshot SFX timing/cadence reportedly does not match the N64 original's rate (M-106, user QA report).","CLOSED (v0.4.0 campaign sign-off 2026-09-28, user:"
|
||||
D241,CLOSED,Player gunshot SFX goes silent under some conditions —,"CLOSED as the D207 family (v0.4.0 campaign sign-off 2026-09-28, user:"
|
||||
D242,FIXED,Player-death sequence UX regression vs N64 original:,"FIXED/CLOSED (2026-09-26, user:"
|
||||
D243,FIXED,"Dam end-of-level cutscene (the abseil/bungee sequence, D148/D160's subject):","FIXED, live-confirmed by the user (M-189/M-190, 2026-09-18)."
|
||||
D245,FIXED,IsWater water: two competing patterns with a visible seam that moves as the player looks around (mouse/aim) —,"FIXED (M-201, 2026-09-26, user-verified vs 1964):"
|
||||
D246,FIXED,"Tiny pixel strips at the left and right edges of the screen, ""like overscan"" —","FIXED (2026-09-18 session, port-only, Video.SafeAreaCrop)."
|
||||
D247,FIXED,Black bars top and bottom of screen —,"FIXED (2026-09-18 session, port-only, Video.SafeAreaCrop)."
|
||||
D248,FIXED,"Port runs a stable 30 FPS instead of 60 FPS game-wide (M-117, user asked to verify FPS after the D209 timing-keystone fixes).","FIXED, root-caused to a genuine pointer-width ABI bug in src/sched.c (compiled for the PC build, not excluded)."
|
||||
D249,CLOSED,"LOD/culling breaks down on large open levels (Streets, Egyptian named specifically) at *default* FOV —",CLOSED (2026-09-28 bookkeeping:
|
||||
D250,FIXED,"FPS is inconsistent/low on many levels after 60 Hz shipped (M-119, user QA; D248 follow-on).","FIXED — per-call getenv() cost was the bottleneck; rock-steady 60-61 fps after, user re-tested 1440p/4K clean."
|
||||
D251,FIXED,F10 options overlay QoL pass:,"FIXED (port-only, port/src/optionsoverlay.c)."
|
||||
D252,FIXED,"Particle effects cycle through a rainbow palette (M-124, user playtest of the v0.2.0-pre candidate).","FIXED (M-201, 2026-09-26, user-verified):"
|
||||
D253,FIXED,Facility (and any level with a FORCECROUCH/LADDER linked special tile) SIGSEGV on Linux/Steam Deck —,FIXED — Deck-verified:
|
||||
D254,FIXED,Linux crash handler destroyed its own logs:,FIXED — field-verified.
|
||||
D255,FIXED,Facility firefight SIGSEGV on Steam Deck (v0.2.0-pre CI bundle; D253 fix confirmed working).,"FIXED (M-140) — D3x-class 32-bit pointer truncation (vtxstore_fix_refs read Model* through the mistyped ChrRecord.chrflags 4-byte-enum alias instead of ObjectRecord.model), not the destroy-path race…"
|
||||
D256,FIXED,Saves (and config) silently failed to persist on Linux whenever the game was not launched from the directory containing data/ —,FIXED (path half) — save-flow detail still owed from the user.
|
||||
D257,FIXED,New Game.AllUnlocked option (F10; default flipped OFF for v0.2.0 —,FIXED — verified by user playtest (PR #79 merged); default flipped OFF for v0.2.0 (D259).
|
||||
D259,FIXED,Game.AllUnlocked (D257) breaks audio on a fresh save:,FIXED in v0.2.1 —…
|
||||
D260,FIXED,v0.2.1 (user request):,"FIXED — build green; one-line branch swap, trivially reviewable."
|
||||
D261,FIXED,v0.2.1 (user request:,FIXED — feel-check received; W/S sensitivity follow-up logged above.
|
||||
D262,FIXED,v0.2.1 (user report):,"FIXED (primary cause, D264) —"
|
||||
D264,FIXED,"D262's root cause (ABI/layout class, cf.","FIXED — build green, probes removed; visual confirmation pending."
|
||||
D263,RESOLVED,v0.2.1 (user report):,"RESOLVED (not a bug), refined (v0.2.1 further user testing, 2026-09-15)."
|
||||
D265,CLOSED,"D236 investigation (port-layer, port/fast3d/gfx_pc.cpp):",CLOSED (2026-09-26 bookkeeping:
|
||||
D258,DEFERRED,PAL/JP asset conversion is broken at the source-data level —,DEFERRED (user call 2026-09-28:
|
||||
D284,FIXED,port/src/random.c's MIPS64 dsll32/dsrl32 shift helpers were backwards —,"FIXED, 21-level sweep clean, no crashes —"
|
||||
D285,FIXED,Steam Deck SIGSEGV during D255 live-testing = SEPARATE bug:,"FIXED (user, 2026-09-26:"
|
||||
D286,CLOSED,"User-confirmed real ~8s frame freeze on Caverns, Steam Deck —","CLOSED (2026-09-26, user-approved:"
|
||||
D287,FIXED,"User-confirmed real, non-self-recovering freeze on Windows (M-141, 2026-09-15/16) after ~15-20 minutes with no input, triggered by clicking back into the… —",FIXED (user playtest 2026-09-26:
|
||||
D288,CLOSED,"Silo intro: a huge, flat-shaded, untextured brown/tan triangle sweeps across the frame during the camera pan down the missile shaft (M-141, user report —… —","CLOSED — not reproduced on the current build (2026-09-23 capture) and the 2026-09-25 user Silo playtest confirms it gone (""silo fixed"")."
|
||||
D289,FIXED,"Dam: a crash in the computer room, not reproducible on a second attempt (M-141, Steam Deck user playtest).","FIXED (user, 2026-09-26:"
|
||||
D290,FIXED,"Watch's in-game ""controller"" section shows no control-pad graphic (M-141, Steam Deck user playtest).","FIXED (2026-09-23): D264-pattern template read in watchRenderController (flags read 0), plus 3 pointer-width/raw-Gfx sites on the same path; controller model verified rendering on Windows."
|
||||
D291,CLOSED,"Dam: texture glitches when the truck drives past (M-141, Steam Deck user playtest).","CLOSED — duplicate of D306, faithful (M-201, 2026-09-26:"
|
||||
D292,FIXED,Death animations are glitched —,"MOSTLY FIXED as a side effect of D243 (2026-09-18, user-confirmed alongside D173) —"
|
||||
D293,FIXED,"QoL ask: no discoverable way to quit the game from the UI (M-141, Steam Deck user playtest —","FIXED (M-141) — ""Quit to desktop"" row added to the F10 overlay; build-verified Win+Linux, not yet live-confirmed.…"
|
||||
D294,PARTIAL,"Statue: ground geometry near the large CCCP/hammer-and-sickle monument briefly renders solid black over a wide area, then self-recovers 10-30 seconds later… —",PARTIAL -> fix landed pending by-eye check (2026-10-03:
|
||||
D295,FIXED,"Campaign-halting crash at the end of Control Center on 00 Agent difficulty, then a crash on every subsequent relaunch/resume (GitHub #87, user campaign playtes…",FIXED (M-149) — M-148 root-caused + reproduced on HEAD; fix landed:
|
||||
D296,CLOSED,"Control Center, 00 Agent:","CLOSED (2026-09-26, user-approved:"
|
||||
D297,FIXED,v0.3.0 silently wipes ALL pre-fix player saves on first launch (D284 side effect; found during D295/M-148).,FIXED (M-150) — migration shim landed:
|
||||
D298,CLOSED,Full HUD (not just the aim-mode crosshair) missing in a v0.3.0 dev build —,"CLOSED (2026-09-26, user:"
|
||||
D299,FIXED,Game.SkipIntro crashed selecting any save slot except one —,"FIXED — fileValidateSaves() (only called from the skipped legal screen) never ran under SkipIntro, leaving saves[] all-zero; added the missing call to the SkipIntro shortcut (src/game/lv.c, #ifdef PO…"
|
||||
D300,FIXED,Hipfire look feels non-uniform (issue #89):,"FIXED, user-confirmed live —"
|
||||
D301,FIXED,"fileGetIsCheatUnlocked() NULL-deref, unguarded on every call site, not just the D299/SkipIntro one (GitHub #87, two reports with an identical crash PC/fault ad…",FIXED locally on release/v0.3.0 (not pushed) —
|
||||
D302,FIXED,"Render FPS regression to ~40-45fps on release/v0.3.0 (user QA on the local v0.3.0 pre-release build, 2026-09-18) —","FIXED — 4 uncached getenv() calls added by D236 passes 10/12/14/16 directly inside gfx_sp_tri1 (port/fast3d/gfx_pc.cpp, per-triangle) cached with the same static int x = -1 pattern D250 established e…"
|
||||
D303,FIXED,"Some muzzle flashes (M16 named specifically, ""and some others"") draw wrong —","FIXED (M-201, 2026-09-26):"
|
||||
D304,FIXED,"Mouse sensitivity menu overhaul (user request, 2026-09-18):","FIXED — see full entry for both the range/parity pass and the same-day follow-up (collapsed to a single Input.MouseSensitivity menu row + fixed overlayUpdateScroll()'s unconditional bottom-anchor, th…"
|
||||
D305,FIXED,"Steam Deck SIGSEGV on the v0.3.0 pre-release build (Cavern, early in the level, Game.AllUnlocked previously enabled on the session/save) —",FIXED (defensive guard) —
|
||||
D306,CLOSED,"Dam intro/truck wheels render with odd transparent-looking areas, possibly z-fighting (user QA, 2026-09-18).","CLOSED — faithful, confirmed on 1964 (M-201):"
|
||||
D307,FIXED,"In-level security camera entities can be flipped facing the wrong direction (backwards) on some levels, e.g.","FIXED (M-201, 2026-09-26, user-verified):"
|
||||
D308,CLOSED,"Occasional z-fighting confirmed on some levels' geometry, general/broader than the D306 truck-wheel instance (user QA, 2026-09-18).","CLOSED — faithful (2026-09-26, user:"
|
||||
D309,FIXED,Complete non-self-recovering freeze (Windows) returning from Caverns to the intro screen —,FIXED via D310 — user-playtest-confirmed 2026-09-18 (Caverns→intro clean; the spin record itself observed resuming correctly in the post-fix probe log).
|
||||
D310,FIXED,"RULE-2-SIGNOFF: chraiitemsize() mis-sizes 1-byte AI_PRINT records (NUL-scans forward), corrupting AI execution for any list containing an active PRINT —",FIXED — user-playtest-confirmed 2026-09-18 (v2:
|
||||
D311,FIXED,Complete freeze in modelSetAnimFrame2WithChrStuff root-motion loop —,FIXED — root cause D336 (bheadAdjustAnimation read never-written placeholder fields); the #ifdef PORT guard is retained as a safety net.
|
||||
D312,FIXED,"Throwable items (mines, grenades, etc.) can clip through walls in certain spots (user report, v0.3.0 pre-release playtest, 2026-09-18).",FIXED — bgBuildRoomVtxBounds mis-reads the G_VTX vertex count on PC (Gfx_le.dma.par bit-layout shift):
|
||||
D313,FIXED,Bullet impacts do not appear on some walls/surfaces —,"FIXED — same root cause as D312, merged diagnosis; same one-line fix."
|
||||
D314,FIXED,"F10 menu items flicker randomly/intermittently (reported on the main-menu context, user report, v0.3.0 playtest build, 2026-09-18).",FIXED (user playtest 2026-09-26) —
|
||||
D315,CLOSED,Menu scaling differs between main menu and in-game —,"CLOSED (2026-09-26, user:"
|
||||
D316,FIXED,"Mouse-clicking some menu items glitches and lands on the item below instead (user report, v0.3.0 playtest build, 2026-09-18).",FIXED — root-caused to two compounding bugs (naive mouse->logical mapping ignoring the safe-area crop; asymmetric row hit-test band) in port/src/optionsoverlay.c + port/fast3d/gfx_pc.cpp.
|
||||
D317,CLOSED,"Crash when toggling Game.AllUnlocked back and forth in-game (user report, v0.3.0 playtest build, 2026-09-18) —","CLOSED — symptom of D316 (confirmed instant window close, no WER report; the D316 hit-band bias let a click on ""All unlocked"" fall through to the adjacent ""Quit to desktop"" row)."
|
||||
D318,FIXED,Facility (regular play path):,FIXED via D329 (2026-09-26 bookkeeping:
|
||||
D319,FIXED,"Frame cap slider was mostly a dead zone; replaced with a plain 30/60 toggle (user ask, 2026-09-18).",FIXED — new ROW_FPSCAP cycle (30/60 only) in port/src/optionsoverlay.c; default changed to 60 in port/src/video.c.
|
||||
D320,FIXED,"D318-class sweep: aim-hold → _update AI lists in 8 levels carry the same as-authored softlock race (static analysis, 2026-09-20).",FIXED via D329 (2026-09-26 bookkeeping:
|
||||
D321,FIXED,D318 trigger chain fully mapped:,FIXED via D329 (2026-09-26 bookkeeping:
|
||||
D322,FIXED,Long-session audio degradation:,"FIXED (Windows) for the periodic ""garbled"" bursts —"
|
||||
D323,CLOSED,G_TL_LOD bindings of explicit-LOD textures:,"CLOSED — parked as a fidelity enhancement, not a bug (2026-09-26, user-approved:"
|
||||
D324,FIXED,"GE's <stdlib.h> stub declares nothing, so TUs that include it get NO CRT prototypes —",FIXED (2026-09-22) — proven impact:
|
||||
D325,CLOSED,ROM-mod (xdelta) compatibility:,CLOSED as a question —
|
||||
D326,CLOSED,Random Eye randomizer repacks the ROM too —,CLOSED as a question —
|
||||
D327,RESOLVED,"Mod support, resolved plan:",RESOLVED — superseded by D328; approach (a) is NOT ruled out (these scans missed the 1172-compressed data segment).
|
||||
D328,CLOSED,"Mod support is viable and D327's ""ruled out"" was wrong:",CLOSED as a question —
|
||||
D329,FIXED,The D318/D320 guard anim pin is a tick-granularity divergence:,"FIXED — Rule-2 high-FPS fix in model.c; clk=1 no longer pins (WSL + native Windows), 0 triggers at clk=2; bunker1 golden failure predates it (on/off A/B); user playtest confirms N64-matching Ourumov…"
|
||||
D330,FIXED,"#103 (v0.3.0, Win 11):",FIXED (port/src/input.c) —
|
||||
D331,FIXED,Ejected shell casings never rendered on PC:,"FIXED (gunfire.c #ifdef PORT, explicit template); PP7 casing verified on native Windows (before:"
|
||||
D332,FIXED,Reticule jitter in aim mode (RMB) —,FIXED (fix/mouse-pd-model) —
|
||||
D333,FIXED,"#104 (v0.3.0, Linux):",FIXED (2026-09-26 bookkeeping:
|
||||
D334,FIXED,"Native widescreen, world side (WIDESCREEN-FOV-PLAN Option B Phase 2):",FIXED (2026-09-26 bookkeeping:
|
||||
D335,FIXED,"Native widescreen, HUD + front end (WIDESCREEN-FOV-PLAN Phase 3):",FIXED (2026-09-26 bookkeeping:
|
||||
D336,FIXED,"Frigate (and any level) ""all polygons break except the gun"" after turning:","FIXED (src/game/bondhead.c #ifdef PORT, ABI/layout); headless repro (Runway → exit → Frigate → turn, D294 2× room pool) clean, D156 0 fires; golden 3/3; Win + Linux builds."
|
||||
D337,FIXED,Aim modes folded (user decision):,FIXED (2026-09-26 bookkeeping:
|
||||
D338,FIXED,"Input.AimRange (F10 ""Aim range""):",FIXED (2026-09-26 bookkeeping:
|
||||
D339,OPEN,"#92 (low-end ~30 fps, Celeron N3060 / HD 400) —","OPEN (2026-09-26, user:"
|
||||
D341,FIXED,"Intro gun-barrel blood drip draws as static blocks/lines instead of dripping (user, v0.4.0, 2026-09-26; death blood fine).","FIXED (M-201, user-verified 2026-09-27):"
|
||||
D342,FIXED,"File select: folder bodies and Bond photos vanish after selecting a file and backing out (user, v0.4.0, 2026-09-26).","FIXED (M-201, user-verified):"
|
||||
D343,FIXED,"File select: PC ""Options"" label opens the settings overlay (v0.4.0 feature, M1 of the route-B options screen, 2026-09-27); also fixes F10 on file select jumpin…","FIXED (M1, closed): one Rule-2 #ifdef PORT draw hook (user sign-off 2026-09-27); headless + user-verified (mouse, F10, >30 s, widescreen, physical controller on the bottom-bar label and options scree…"
|
||||
D344,FIXED,Host PC bugchecks (BSOD 0x119 VIDEO_SCHEDULER_INTERNAL_ERROR) during game test runs; probable cause:,"FIXED (probable cause; orderly quit + GE_QUITFRAME), headless + user-verified; no dump, watch for recurrence."
|
||||
D345,FIXED,"Front-end crosshair snaps away from the pointer on screen/page changes (file select → mode select setCursorPOSforMode(0), front.c:2516; every MENU_PC_OPTIONS p…","Port-only, user-verified (parts a–g):"
|
||||
D346,CLOSED,PC-settings wording pass:,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D347,FIXED,F10 overlay controller:,"FIXED (port-only, optionsoverlay.c):"
|
||||
D348,CLOSED,"File-select ""PC Options"" label:",CLOSED (2026-09-28 bookkeeping:
|
||||
D349,CLOSED,GE watch settings ARE saved to the Bond file when the watch closes:,"SUPERSEDED (2026-10-04, P2b correction):"
|
||||
D350,CLOSED,Watch-backed PC options:,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D351,FIXED,Existing D346 value/unit rows had positional initializers shifted into hidePtr:,"FIXED (port-only): designated row initializers restore unit, deadzone divisor and hide-if-auto fields; build clean, headless smoke clean."
|
||||
D352,FIXED,D350 review of 1303c0eb:,"FIXED (port-only, port/src/watchsettings.c):"
|
||||
D353,FIXED,Turok-style settings regrouping:,"FIXED (port-only, optionsoverlay.c/frontoptions.c):"
|
||||
D354,FIXED,Front-end F10 Bond rows silently disabled:,"FIXED (port-only, port/src/watchsettings.c):"
|
||||
D355,FIXED,F10/options Music slider inaudible in-stage:,"FIXED (port-only, port/src/watchsettings.c):"
|
||||
D356,FIXED,"Settings regroup: Turok-style pages renamed to functional sections (INPUT/GAMEPLAY/GRAPHICS/AUDIO/VIDEO) with PD-style ""(File N)"" scoping annotations, the redu…","FIXED (port-only, port/src/optionsoverlay.{h,c} + port/src/watchsettings.{h,c} + port/src/frontoptions.c; plan docs/dev/D356-SETTINGS-REGROUP-PLAN.md):"
|
||||
D358,FIXED,D356 level-1 overlap:,"FIXED (port-only, port/src/frontoptions.c):"
|
||||
D359,CLOSED,"Settings UI file terminology settled on the end-user term ""Profile"":",CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D360,CLOSED,F10 INPUT heading scrolled away; F10 navigation and distance defaults (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D361,FIXED,Main-menu F10 Audio freezes the game:,"FIXED (port-only, port/src/watchsettings.c + F10 probe in optionsoverlay.c):"
|
||||
D362,CLOSED,F10 GoldenEye dossier-style visual refresh (2026-09-27).,CLOSED (superseded by D363 after user playtest):
|
||||
D363,FIXED,F10 return to a dark overlay with the N64 watch's green/Bank Gothic visual language (2026-09-27).,"FIXED (palette/font user-approved, 2026-09-27); D364 follows up on profile tags, click alignment and slider calibration."
|
||||
D364,FIXED,Settings UI feedback:,FIXED (user mostly accepted; D365 follows up on annotation size and click targets).
|
||||
D365,CLOSED,F10 annotation hierarchy and visible mouse change targets (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D366,CLOSED,Input calibration label:,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D367,CLOSED,F10 full-width control boxes misaligned with their contents (2026-09-27).,CLOSED (2026-09-28 bookkeeping:
|
||||
D368,CLOSED,Simplify F10 click targets:,CLOSED (2026-09-28 bookkeeping:
|
||||
D369,FIXED,Remove F10 row boxes/strips entirely (2026-09-27).,FIXED (awaiting live acceptance):
|
||||
D370,CLOSED,F10 distance/volume drags hurt frame rate (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D371,FIXED,"GEPD key-layout preset + reload/crouch bindings, v0.4.0 modern options wave M3 (2026-09-27).",FIXED (headless-verified; awaiting user live accept):
|
||||
D372,FIXED,"Low-end perf preset (v0.4.0 modern options wave M5, 2026-09-27).",FIXED (headless-verified; awaiting user live accept):
|
||||
D373,FIXED,"In-game crosshair on/off + colour (v0.4.0 modern options wave M2, 2026-09-27).",FIXED (user live-tested; rule-2 sign-off recorded 2026-09-27):
|
||||
D374,FIXED,"GEPD becomes the default key layout; preset layering fix (v0.4.0 M3 playtest feedback, 2026-09-27).",FIXED (headless + user live-tested):
|
||||
D375,CLOSED,Crouch wired to the wrong pad bit:,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D376,CLOSED,Crouch follow-up + modern-options spot check (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D377,FIXED,GEPD independent crouch (2026-09-27).,FIXED (2026-09-28 bookkeeping:
|
||||
D378,FIXED,GEPD use/reload separation (2026-09-27).,FIXED (2026-09-28 bookkeeping:
|
||||
D379,FIXED,Crosshair colour: authored texture is red (2026-09-27).,FIXED (2026-09-28 bookkeeping:
|
||||
D380,FIXED,One PC keyboard layout; PD-style rebind design (2026-09-27).,FIXED (follow-up D383):
|
||||
D381,CLOSED,"RULE-2-SIGNOFF: opt-in RGB, size and Beta crosshair (2026-09-27).",CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D382,CLOSED,Crosshair menu placement and slider hitches (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D383,CLOSED,PD-style keyboard rebinding in both options UIs (2026-09-27).,CLOSED (2026-09-28 bookkeeping:
|
||||
D384,CLOSED,Front PC Options key-bind navigation feedback + two-slot UI (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D385,CLOSED,Real mouse 1–5 action rebinding in the two-slot editor (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D386,CLOSED,Correct Aim default from SDL middle (Mouse 2) to right (Mouse 3) (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D387,FIXED,M7 AllUnlocked's read-time fake unlocks leak into saved EEPROM (2026-09-27).,"FIXED 2026-09-30 (port-only per-slot raw/seen merge on EEPROM write, libultra.c geD387MergeSlotWrite; isolated ON-vs-OFF byte-identity matrix passed)."
|
||||
D388,CLOSED,Nest functional binding/HUD menus and remove redundant M5 toggle (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D389,CLOSED,F10 footer clipping and ambiguous submenu links (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D390,CLOSED,F10 pinned Back selection did not visibly change (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D391,CLOSED,F10 click at glyph bottom could select next row (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest.
|
||||
D392,FIXED,Train intro soldier hunched; hand intersects torso vs 1964 —,FIXED (#ifdef PORT ABI/layout in chraction.c); Train aim-angle probe confirms correction; user visually verified the soldier looks fixed against 1964.
|
||||
D393,FIXED,Controller B/Y both crouch; no in-game use button —,FIXED + user live-tested A use / X reload / Y weapon cycle.
|
||||
D394,PARTIAL,Xbox 1.1 Jinx controller parity —,PARTIAL (2026-09-28).
|
||||
D395,RESOLVED,Controller trapped in keyboard/mouse-only binding capture —,"RESOLVED (v0.4.0 campaign sign-off 2026-09-28, user:"
|
||||
D396,RESOLVED,"F10 pad B/A used as slider decrement/increment, not back/select —","RESOLVED (v0.4.0 campaign sign-off 2026-09-28, user:"
|
||||
D397,RESOLVED,SELECT FILE background + intro gun-barrel show an interlaced/comb look —,RESOLVED (2026-09-28):
|
||||
D398,CLOSED,PC Options label: D348's procedural slider glyph replaced with an existing mainfolderimages texture (2026-09-27).,"SUPERSEDED by D400 (icon removed, text-only label; purple-hover bug moot; the D400 textMeasure arg-swap bug was found and fixed there)."
|
||||
D399,CLOSED,File-select bottom row (SELECT FILE text + Copy/Erase + PC Options dot/label) shifted 15px left as a block (user sign-off 2026-09-27).,CLOSED (2026-09-28 bookkeeping:
|
||||
D400,CLOSED,"File-select ""PC Options"" label:",CLOSED (2026-09-28 bookkeeping:
|
||||
D401,CLOSED,Rumble Pak → real gamepad haptics (re-promotes D224/M-87):,CLOSED (2026-09-28). Rumble shipped in v0.4.0 (D401); Steam Deck real-pad validation:
|
||||
D402,RESOLVED,First clean Linux (glibc / SteamOS) build of release/v0.4.0 broke in two port-layer include-resolution spots MinGW tolerated (bondconstants.h first-include cyc…,RESOLVED (Linux/Ubuntu-24.04 build → glibc-2.41-compatible binary for the Steam Deck):
|
||||
D403,RESOLVED,Deck v0.4.0-pre test:,"RESOLVED (2026-09-27, user-confirmed on the Deck:"
|
||||
D404,RESOLVED,"""Aim style: Centred (PC)"" (Input.AimMode=1, D333/D337) has no effect when aiming with a gamepad; the row's own doc comment over-promises (""applies to every aim…","RESOLVED (2026-09-28, port-layer only):"
|
||||
D405,CLOSED,"fast3d/OpenGL: MSAA above the driver max black-screens; two glBindFramebuffer calls pass an index where a GL name is required (PR #106, italoarruda, 2026-09-25…","CLOSED (2026-09-28). Merged from PR #106 (code file only; security-reviewed clean - port-layer GL state fixes, no external input, no allocations)."
|
||||
D406,RESOLVED,front-end PC Options screen:,"RESOLVED (2026-09-28, port-layer only, Steam Deck-validated):"
|
||||
D541,PARTIAL,One A press advanced two menu screens on the Steam Deck (Desktop Mode) —,"PARTIAL 2026-10-05 (mitigation landed, port-only:"
|
||||
D542,OPEN,"Stray polygons left of Bond's gun, frequent on foggy levels (Streets, Surface 2) —",OPEN 2026-10-05 (triage:
|
||||
D543,FIXED,Surface 2: lit area following Bond brighter than 1964 —,"FIXED 2026-10-06 (CPU RSP-style near clip in gfx_sp_tri1 + per-vertex fog default; build-verified, by-eye vs 1964 at next playtest)"
|
||||
D544,FIXED,OS cursor flickers with F10 open on the main menu —,"FIXED 2026-10-05 (port-only, input.c; build-verified, maintainer mouse check owed)"
|
||||
D545,FIXED,Player rockets can pass through the ground when fired near Bond's feet —,"FIXED 2026-10-06 (AVOID_UB array, gun.c; maintainer Archives preset A/B:"
|
||||
D546,FIXED,PC settings standardization:,FIXED 2026-10-05 (port-only; build + maintainer check owed; golden re-base batched with D543)
|
||||
D547,FIXED,Game.AllUnlocked applies live (no restart) —,"FIXED 2026-10-05 (port-only, main.c/optionsoverlay.c; build-verified, maintainer mission-select check owed)"
|
||||
D548,FIXED,Walls near rocket fire/explosions banded black/white/yellow/blue (Aztec dark corridor; also seen on Deck) —,FIXED 2026-10-06 (gfx_opengl.cpp input clamp; root fix = D543 CPU near clip; maintainer Aztec re-check owed)
|
||||
D549,FIXED,Thrown grenade/knife/object:,"FIXED 2026-10-06 (AVOID_UB coord3d, gun.c x3; build-verified)"
|
||||
D550,FIXED,"Static title bar (no more per-second ""NN fps"" refresh) + project mark as favicon / window / taskbar icon —","FIXED 2026-10-17 (port + tooling only, video.c/gfx_sdl2.cpp/make_icon.py; ninja-command-verified + full build 249/249; maintainer live window/taskbar check owed)"
|
||||
D551,?,"Opt-in update check (Game.CheckUpdates, default off):",ADDED 2026-10-06 (port layer only; live-verified Windows/Linux/Deck + F10 UI capture; security review fixes applied)
|
||||
D552,FIXED,Dam ending cutscene: the first camera shot no longer swivels up onto Bond —,FIXED 2026-10-06 (port-only addition removed; code path back to the decomp; maintainer-verified live)
|
||||
D553,FIXED,Surface 2: the haze around Bond brightened/darkened as he moved —,FIXED 2026-10-06 (fast3d:
|
||||
D554,OPEN,F10 settings menu: wording/casing pass + two-line tips —,DONE 2026-10-06 (port/src/optionsoverlay.c; maintainer by-eye check owed)
|
||||
D555,OPEN,Crosshair mouse pointer over the F10 / PC Options overlay —,"DONE 2026-10-06 (port/src/input.c, optionsoverlay.c, tools_pc/make_icon.py; maintainer by-eye check owed)"
|
||||
D556,OPEN,"F10 feedback pass (tips trimmed, conditional-row order, wording fixes) + mouse scrollbar —","DONE 2026-10-06 (port/src/optionsoverlay.c; build only, by-eye check owed)"
|
||||
D557,FIXED,"New/blank profiles start with Look ahead OFF on PC (Rule-2 sign-off, maintainer 2026-10-06) —",FIXED 2026-10-06 (src/game/file2.h DEFAULT_OPTIONS under #ifdef PORT; existing saves untouched; win gate 21/21)
|
||||
D558,FIXED,F10 overlay tips cut off in a level (not on the front end) —,FIXED 2026-10-06 (optionsoverlay.c textRenderWide; verified by in-level capture)
|
||||
D559,FIXED,"Look ahead off for untouched existing folders (maintainer-approved, port layer only) —",FIXED 2026-10-06 (watchsettings.c migrateUntouchedLookAhead)
|
||||
D560,FIXED,F10 copy pass 3 (maintainer line review) —,FIXED 2026-10-06 (optionsoverlay.c tips/labels/value names; display text only)
|
||||
D561,FIXED,"F10: ""Menu pointer"" row removed (front-end-only legacy option; confused players) —","FIXED 2026-10-06 (row/tip/names removed; Input.MenuPointerMode ini key, Direct default and the legacy code path kept for one release)"
|
||||
D562,FIXED,F10 crosshair pointer hidden while a pad/keyboard drives the menu —,FIXED 2026-10-06 (drawCrosshairPointer draws only while s_mouseActive)
|
||||
D563,FIXED,F10 root hub: Left/Right no longer open pages; held nav inputs latched on open/page change —,FIXED 2026-10-06
|
||||
D564,FIXED,"F10: game's own ""Look up/down"" row removed (dead for mouse and centred pad, stacked with Invert look); value pinned to factory; tip polish —","FIXED 2026-10-06 (port only; Mouse / Controller ""Invert look"" are the inversion controls)"
|
||||
D565,FIXED,AI awareness followed two player render settings:,FIXED 2026-10-06 (maintainer decision:
|
||||
D566,FIXED,Steam Deck Game Mode showed the on-screen keyboard at every launch (SDL text input left on) —,FIXED 2026-10-06 (port only:
|
||||
D567,FIXED,Front-end PC Options with a pad pulled the game's crosshair to the top-left (overlay synced the cursor to the untouched OS mouse) —,FIXED 2026-10-06 (port only:
|
||||
D568,FIXED,F10 dropdown popup drawn empty and misplaced for a long list on a mid-page row (negative top) —,FIXED 2026-10-06 (port only:
|
||||
D569,?,Crosshair color follows the PD port:,CHANGED 2026-10-06 (maintainer request; old presets migrate to Custom RGB)
|
||||
D570,FIXED,Scaled crosshair (Crosshair size != 100%) showed a faint copy of the top bar's tip under the bottom bar (WRAP tile flags) —,FIXED 2026-10-06 (port only:
|
||||
D571,?,"Project icon restyled to the in-game sight red as pixel art (exe, window, favicon) —",CHANGED 2026-10-06 (maintainer request; make_icon.py; social preview unchanged)
|
||||
D572,?,Linux tarball glibc floor lowered from 2.38 to 2.31:,CHANGED 2026-10-07 (maintainer request; packaging only; X220 glibc 2.36 fresh install PASS)
|
||||
D407,CLOSED,Tanks cannot be boarded/exited on PC —,CLOSED (2026-09-28 bookkeeping:
|
||||
D408,FIXED,Game.SkipIntro skips the post-mission failure dossier:,FIXED 2026-09-30 (SkipIntro hook gated to first boot; live dossier check owed).
|
||||
D415,FIXED,"Shooting a light fixture does not ""kill"" its flickering light (Bunker, Caverns):",FIXED (2026-09-29 bookkeeping:
|
||||
D416,RESOLVED,"2-player split-screen crashes (issue #99, v0.3.0 4827bc8, fault addr 0xa43a0ed4, PC 0x14009e758):",RESOLVED in-harness (converter + D417 + D418 fix the 2P crash and split-screen flicker; 18/18 2P/3P/4P x 6-stage load runs clean; live pad play still owed).
|
||||
D417,RESOLVED,enum PROP unsigned on GCC:,"RESOLVED (src/bondconstants.h PROP__PORT_SIGNED = -1, D3/D142 precedent; Rule-2 review done 2026-10-06:"
|
||||
D418,RESOLVED,"Split-screen: fast3d safe-area crop stretched the last-set (half-height) viewport over the whole window, so each player's view filled the screen and the last-d…","RESOLVED (gfx_set_split_screen, port-only)."
|
||||
D419,RESOLVED,Split-screen input shared between players / P1 lost solo controls:,"RESOLVED (per-slot slotPlayer(), port-only; no current-player swaps from the poll thread; live-tested OK)."
|
||||
D420,FIXED,MP score / stats screen shows an odd big number (issue #99) —,FIXED 2026-10-01 (mp/virtual-seats); 2P/3P/4P watch-menu runs show 0s.
|
||||
D421,CLOSED,Gadget cycling (pad B / gadget key) disabled in 2+ player matches —,"CLOSED 2026-10-01: moot, no MP inventory holds a gadget (mp_weapon.c); hook reviewed + reverted."
|
||||
D422,FIXED,P2+ have no centred-aim mode in split-screen (single-slot aim state) —,FIXED in tree (commit ac92e0ab:
|
||||
D423,CLOSED,Unidentified black arrowhead in P2's split-screen view (stage 38) —,"CLOSED 2026-10-01: faithful (level shadow at a doorway top), maintainer-confirmed live."
|
||||
D424,FIXED,WeaponStats.RecoilSpeed/b44[] byte order:,"FIXED in tree (WS_B44, port-guarded); user-verified vs GEPD/1964 2026-09-29."
|
||||
D425,FIXED,Tank run-over death sound loops:,FIXED (2026-09-29 bookkeeping:
|
||||
D426,RESOLVED,Tank cannot be steered with the mouse out of aim mode (issue #116).,RESOLVED (user 2026-09-29:
|
||||
D427,FIXED,"Automatic fire cadence is frame-counted, ~2-3x faster than N64 at 60 fps (issue #114).","FIXED in tree (modulus scaled by ticks/frame; measured 6 ticks/shot at 30 and 60 cap); user-verified vs GEPD/1964 2026-09-29 (AK47 mag empties in ~3 s, Klobb normal)."
|
||||
D428,FIXED,No gun turn-sway with direct mouse look (issue #117).,FIXED in tree (2nd pass:
|
||||
D429,CLOSED,Tank full pass: aim-mode (RMB) mouse behaviour and tank sticking forward/back (follow-up to D426).,CLOSED faithful (user 2026-09-29:
|
||||
D430,FIXED,Shot light fixtures do not go dark:,FIXED in tree (D154-style words.w0/.w1 decode); user-verified vs GEPD/1964 on Caverns 2026-09-29.
|
||||
D431,FIXED,Light fixtures never reached lightFixtureBreak on PC:,FIXED in tree (fixture table records the image id; bg.c hit code looks it up; free-slot guard); user-verified vs GEPD/1964 2026-09-29.
|
||||
D432,FIXED,"Gunshot sounds ""off"" after D427:",FIXED in tree (gate offset rounded to odd at 1 tick/frame); measured shot/sound cadence identical to N64 rules; residual audible difference vs 1964 tracked as D433.
|
||||
D433,OPEN,"Slight audible difference in automatic-gun shot sound vs GEPD/1964 after cadence was matched (user by-ear, Caverns).",OPEN (not a timing issue; audio layer suspected).
|
||||
D434,FIXED,Raw ((u8*)&g_Textures[n])[0] & 0xf hit-type reads are wrong on PC; latent OOB once D431 returned real texture ids (found by sub-agent review).,"FIXED (.hitTexture at 3 sites, port-guarded); live check owed."
|
||||
D440,FIXED,"""Original N64"" / ""Port defaults"" one-press presets in the F10 overlay and front-end PC Options (+ -n64preset/-portpreset).",FIXED (port-only; headless-verified; user live accept owed).
|
||||
D441,PARTIAL,64-bit pointer-width sweep (part 1):,"PARTIAL (census + GE_HIGHARENA_BASE tier-1 build + 35 sites fixed + CI ratchet, 2026-10-03; tier-1 all-level sweep 21/21 PASS, 0 crashes, 2026-10-05; the above-4-GiB tier still owed)."
|
||||
D442,FIXED,Game.AllUnlocked reworked into a pure RAM/query-time override:,"FIXED; verified 2026-10-03 headless (mission select all 20 / only real progress when OFF, full cheat menu, eep byte-identical)."
|
||||
D443,FIXED,"D357 settings-display cleanup (mouse ×, whole-% deadzone and volume steps, FOV in horizontal degrees, MSAA 16×) plus a ""Restart game"" action (orderly D344 quit…",FIXED (port-only; reset probes pass; both-UI visual check and one Restart click owed; Linux relaunch path not compiled).
|
||||
D454,FIXED,Knife slash crashed on Windows:,FIXED (verified by maintainer live:
|
||||
D461,FIXED,Item-slot allocation overran into the next stage-bank block:,FIXED (maintainer-verified:
|
||||
D462,FIXED,vtxstore.c sized its two record tables with the N64 stride 0x14 for a 24-byte struct unk_09B7A0_struct_parent on PC (Vertex* unk00 widened); both are indexed a…,FIXED (build + solo -level_09 and 4P GE_STARTMP=4 smoke runs exit clean; no crash; by-eye owed in solo).
|
||||
D444,FIXED,Front-end PC Options:,FIXED (build-verified; human mouse test owed)
|
||||
D455,FIXED,Binary-first census of exe-static addresses truncated to 32 bits (D454 class) (2026-09-30).,FIXED/CLEAN (no further REAL site beyond D454)
|
||||
D447,FIXED,Pillarbox output mode (Video.AspectMode = Original) for the Original N64 preset; crop overscan no longer cancels Wide/Cinema.,"FIXED (build + 3 captures verified; Wide/Cinema, 16:9 Ratio, mouse and 1964 comparison owed to a human)"
|
||||
D445,FIXED,Crouched-rifle firing animation (crouched_rifle_firing_animation_groupA) never resolved on PC -> SIGSEGV in modelConstrainOrWrapAnimFrame when an MP player cro…,"FIXED — layout only (§D5 linker adjacency, D164 class):"
|
||||
D446,FIXED,Couch 2P watch-menu text (the MP pause page) differs by +-1-2 per channel between builds whose only difference is data layout (2026-09-29).,FIXED — port-only (fast3d):
|
||||
D448,PARTIAL,Split-screen pad seats:,"PARTIAL (committed 918ac5f9 on feat/mp-splitscreen-99, build + 2P smoke OK; plug/unplug behaviour not exercised at runtime)."
|
||||
D456,?,"Font-pointer / text-string-pointer slots retyped from s32 to real pointers (ABI/layout exception; front.c, options.c, language.c, bondview*, mpmenu.c, propobj.…",
|
||||
D457,?,64-bit ABI retypes part 2:,
|
||||
D453,?,"memp allocator API made 64-bit clean (D441 step 0; ABI/layout exception, §A1).",2026-09-30
|
||||
D451,FIXED,Automatic-fire gate at unsteady frame rates (D427 follow-up) and D173 residual.,"FIXED (RULE-2-SIGNOFF, maintainer 2026-09-30:"
|
||||
D463,FIXED,fast3d divided by zero (0xc0000094) importing a texture for a zero-area TextureRectangle on a tile with line = 0:,FIXED (port-layer guard in import_texture; 8 repeats of the crashing config + the 18-run sweep + solo -level_09 + menu boot all rc=0 with no crash; the guard fired in 3 of 8 repeats).
|
||||
D464,PARTIAL,"GE_MEMPREDZONE=1: 64-byte red-zone checker for the memp bump allocator (all banks), side table + checks at level unload / every 60 frames / orderly quit; re-in…","TOOL LANDED (local, feat/memp-redzone); MP sweep owed."
|
||||
D465,FIXED,#127 Dam/Caverns/Complex water slid side to side:,"FIXED 2026-10-01 (fix/d465-water-primlod, local); rate measured = N64; maintainer by-eye on Dam:"
|
||||
D466,FIXED,#125 Xenia's boss fight on Jungle triggers early:,"FIXED 2026-10-01 (fix/d466-drawdist-gameplay, local); RULE-2-SIGNOFF (port-feature leak fix; design-review approval); verified headless (room sets, on-screen counts), player-facing shot/autoaim/use/p…"
|
||||
D467,CLOSED,#130 Golden Gun empty click with each shot:,CLOSED: faithful by code + log (audited 2026-10-01; by-ear check of held-trigger behaviour vs 1964 is maintainer-owned)
|
||||
D468,FIXED,AI view clamp: under ultrawide (> 16:9) or Video.FovScale > 100 the AI's on-screen tests used the wider rendered frustum; they now keep the cartridge-widest vi…,"FIXED 2026-10-01 (feat/d468-ai-view-clamp, local); RULE-2-SIGNOFF (policy approved by the maintainer 2026-10-01); verified headless (clamp on/off switching + falsifier)."
|
||||
D469,FIXED,Gamepad presets + per-seat controller rebinding (ROADMAP 5a; supersedes outside PR #109):,"FIXED 2026-10-01 (merged into release/v0.4.1, local); preset 0 == old mapping proven (20,971,520 cases); live pad test owed (maintainer); Jinx 1.2/1.3 undefined (gap)"
|
||||
D470,FIXED,Audio master volume + output-device selection (ROADMAP 5a):,FIXED (live listening test owed)
|
||||
D471,FIXED,Controller-family button names (no glyphs) in the PC menus + player-index LEDs:,FIXED 2026-10-01 (local); headless captures for Xbox/PS/Nintendo; live PS/Nintendo pad check owed
|
||||
D472,FIXED,"F10 overlay polish: multi-pad ownership (audit G1-G3), widescreen pillarbox (D335b), red EXPERIMENTAL rows, unknown-ini-key warning.","FIXED 2026-10-01 (merged; port-only, no src/game)."
|
||||
D473,FIXED,"Uncached getenv(""GE_D75V"") on every G_VTX:",FIXED 2026-10-01 (local); Statue display-list CPU about -55% in steady windows
|
||||
D474,FIXED,fast3d micro-optimisations:,FIXED 2026-10-01 (local); output-identical by construction; measured gain on the dev machine within noise
|
||||
D475,OPEN,Statue/Cradle hitches diagnosed:,OPEN (diagnosed 2026-10-01):
|
||||
D476,FIXED,CI4 textures keyed on their own 16-entry palette bank instead of the whole 256-entry table (D217 hash):,FIXED 2026-10-02 (local); Statue sweep CI4 cache misses 189 -> 36
|
||||
D477,?,CI8 palette re-imports measured and ruled out as a CPU-side hitch cause:,MEASURED 2026-10-02 (local); premise of the D475/D476 follow-up refuted on the dev box; GPU/driver-side cost unmeasured
|
||||
D478,FIXED,"D441 read-side census re-run (319 warnings, 129 outside the chr.c D34 initialisers) triaged against the current tree:","FIXED 2026-10-02 (local; snprintf bound); census triage recorded, Stage B items listed"
|
||||
D479,FIXED,Split-screen GE_STARTMP harness hid the ammo counter:,"FIXED 2026-10-02 (local); harness-only, 2P frame shows the counter in both halves"
|
||||
D480,FIXED,"Shader pre-warm: every created shader pair (shader_id0, shader_id1) is remembered in $S/ge007.shaders and compiled at the first frame of the next session, so a…","FIXED 2026-10-02 (local, perf/shader-prewarm); output-identical (menu frame exact), 0 in-play compiles on the warm run"
|
||||
D481,FIXED,Low-end ~45/30 fps (#92):,"FIXED 2026-10-02 (local, perf/render-worker); X220 (HD 3000) 44-46 -> 60.0 fps, GE_RENDERINLINE=1 falsifier restores 45.9"
|
||||
D482,FIXED,Lighter first-launch defaults on Atom/Celeron-class GPUs (#92 follow-up):,"FIXED 2026-10-02 (local, perf/lowend-defaults); X220 forced-path:"
|
||||
D483,FIXED,#136: the PC crouch key stood Bond up when he switched to the sniper rifle; the N64 keeps the stance (the weapon's DISABLE_CROUCH flag blocks both crouch-down…,"FIXED 2026-10-02 (local, fix/d483-sniper); scripted crouch -> switch -> release keeps SQUAT"
|
||||
D484,FIXED,"#136: portScaleFovY's 20-degree floor raised the game's own zoom FOVs, capping sniper/camera magnification at 20 degrees instead of the N64's 7 (about a third…","FIXED 2026-10-02 (local, fix/d483-sniper, maintainer-approved 2026-10-02); full zoom renders at 7 degrees, normal views pixel-identical"
|
||||
D485,FIXED,"#136: sniper/camera zoom stepped once per game frame with no tick scaling, so at the port's 1 tick/frame it zoomed 2x faster in real time than the N64 at 2 tic…","FIXED 2026-10-02 (local, fix/d483-sniper); 15 -> 7 takes 16 frames at 60 fps (was 8) = the N64 at 30 fps"
|
||||
D486,FIXED,#126: explosions dropped in heavy firefights (turrets on Cradle/Egypt/Aztec):,"FIXED (a)(c)(d); drops + (b) faithful, closed."
|
||||
D487,FIXED,"PAL: the in-level ammo counter was not drawn (only a small dot where the ""7 / 93"" counter should be) —",FIXED; maintainer PAL play check 2026-10-02:
|
||||
D488,FIXED,PAL build reported the Brazilian MPAL TV type instead of PAL (osTvType) —,FIXED
|
||||
D489,FIXED,FPS dropped sharply while adjusting F10 watch sliders (music/FX) with a pad or arrow keys:,"FIXED 2026-10-02 (port-only UI layer, fix/d489-pad-slider-commit, merged into release/v0.4.1 2026-10-03); maintainer pad check passed 2026-10-03"
|
||||
D490,FIXED,Dam first-tower guard walked a small circle:,"FIXED 2026-10-02 (fix/d490-glass-hull, merged into release/v0.4.1 2026-10-03); maintainer by-eye Dam check passed 2026-10-02"
|
||||
D491,FIXED,"Struct reads across separately-declared adjacent globals (the D5/D215 class), found by the D24 overflow audit:","FIXED 2026-10-03 (fix/d491-adjacent-global-reads, local); builds clean, 900-frame -level_09 smoke clean; no by-eye effect expected beyond room-membership-driven behaviour"
|
||||
D492,CLOSED,Emulator .eep saves are not drop-in compatible:,CLOSED (converter shipped 2026-10-04:
|
||||
D493,FIXED,Pad rumble kept going after quitting the game:,"FIXED 2026-10-03, maintainer-verified in play (DualSense; Original-mode edges incl."
|
||||
D494,FIXED,"Sniper rifle: the PC crouch key now crouches/stands freely with a disable-crouch weapon (GEPD parity), never injecting C-down for it; supersedes D483's stance…","FIXED 2026-10-03, maintainer-verified in play"
|
||||
D495,FIXED,"Tank: with the default pad preset X changed the tank weapon and Y left it, because in-tank faces fell back to the fixed menu faces; PadMapCtx.tank routes Next/…","FIXED 2026-10-03, maintainer-verified in play"
|
||||
D496,FIXED,3P/4P split-screen: weapon-pickup messages garbled and oversized —,"FIXED 2026-10-03 (condensed 3+P text is faithful, maintainer-confirmed)"
|
||||
D497,FIXED,PC Options paging now matches the game's dossiers:,"FIXED 2026-10-03, maintainer-verified in play"
|
||||
D498,FIXED,Xbox 1.2 Christmas / 1.3 Frost / 1.4 Elektra gamepad presets:,"FIXED 2026-10-03, maintainer-verified with a pad"
|
||||
D499,FIXED,Trilinear texture filter option:,"FIXED 2026-10-03, maintainer-checked (no visible difference; kept)"
|
||||
D500,FIXED,"Aztec shuttle area: polygons ""fell apart""; the per-frame vtx pool (dyn.c, no bounds check, Aztec -mvtx40) could overrun into the half the render worker draws;…","FIXED 2026-10-03, maintainer-verified at the Aztec spot"
|
||||
D501,CLOSED,Ultrawide 16:9 pillarbox for cutscenes and the watch:,CLOSED (reverted 31ade91f)
|
||||
D502,FIXED,PC Options pad navigation:,"FIXED 2026-10-03, maintainer-verified with a pad"
|
||||
D503,PARTIAL,"Surface: near-full-fog silhouettes on 32-bit output (fog snap applied); the remaining Surface dish is the widescreen frustum, 4:3 matches N64 —",FIX LANDED (by-eye pass owed)
|
||||
D504,FIXED,F10 / PC Options menu:,FIXED (port-only; by-eye pass vs the PD port owed)
|
||||
D505,FIXED,Menu wording pass: 27 maintainer-approved text edits across both PC options UIs —,FIXED (port-only; by-eye pass owed)
|
||||
D506,FIXED,Menu slider values standardized:,FIXED (port-only; by-eye pass owed)
|
||||
D507,FIXED,Menu: one-line description of the focused option in both PC options UIs —,FIXED (by-eye pass owed)
|
||||
D508,FIXED,Forced aspect ratios:,FIXED (by-eye pass owed)
|
||||
D509,FIXED,Held frames after an aspect-ratio change:,FIXED (by-eye pass owed)
|
||||
D510,FIXED,F10 overlay + FPS counter the same on-screen size on the front end and in a level —,FIXED (live check owed)
|
||||
D511,OPEN,Four PD-parity settings:,DONE headless (live checks owed)
|
||||
D512,OPEN,Game.HudScale also scales the F10 overlay and FPS counter —,DONE headless (live check owed)
|
||||
D513,OPEN,"Input.ControlScheme: Modern / OG controls (#139, T7) —",DONE headless (pad pass owed)
|
||||
D514,OPEN,Emulator saves load directly:,DONE headless (by-eye mission select owed)
|
||||
D515,FIXED,Empty arch tag from a fresh build dir:,FIXED (headless-verified)
|
||||
D516,FIXED,"Control style Original shows the game's own N64 styles (1.1-2.4, per seat, saved to the Bond file) instead of the D498 presets —",FIXED (live pass 2026-10-04 incl.
|
||||
D517,FIXED,Holding left/right on a Bond settings row dropped the fps:,FIXED (live pass 2026-10-04)
|
||||
D518,FIXED,Pad layout preset was one global value:,FIXED (live pass 2026-10-04 incl.
|
||||
D519,FIXED,"One PC options UI: the file-select ""PC Options"" entry opens the F10 overlay; the MENU_PC_OPTIONS screen is deleted —",FIXED (headless-verified 2026-10-04; by-eye pass owed)
|
||||
D520,FIXED,"F10 overlay slider wedge, slider marker and dropdown arrow antialiased (they stepped in whole canvas pixels that grow with the window) —",FIXED (headless before/after capture; by-eye owed)
|
||||
D521,PARTIAL,A fixed-frame golden window that overlaps the intro flyby is platform-nondeterministic (cross-platform goldens); verify.sh's linux pixel-gate skip is driver-co…,PARTIAL (rule recorded 2026-10-04; the two residual bands CLOSED 2026-10-05 by D525's box re-round —
|
||||
D522,FIXED,The golden window's frame-900 stem is not settled gameplay on several levels —,FIXED 2026-10-05 (recipe-side re-base:
|
||||
D523,CLOSED,The golden recipe does not pin save state:,SUPERSEDED by D529 (2026-10-05) —
|
||||
D524,FIXED,The golden tooling was destructive and drift-prone:,FIXED 2026-10-05 (both pins idempotent —
|
||||
D525,FIXED,"The whole <level>/linux/ golden set was stale, not just on the six re-based levels —","FIXED 2026-10-05 (21-level box re-round, 63 frames, save pinned present; structural parity 21/21, Archives/Streets bands attributed to save state, not a renderer)"
|
||||
D526,CLOSED,P13 Dam-ending grate / edge-railing transparency is faithful N64 behaviour (port == 1964/GEPD); the per-triangle census probe is retained as a generic draw-sta…,"CLOSED — faithful, confirmed on 1964/GEPD (2026-10-05):"
|
||||
D527,OPEN,GE_PCDUMP written first:last (a colon where the dash belongs) silently dumps EVERY frame —,OPEN (harness documentation; a log-or-fail-closed fix is owed in the port layer)
|
||||
D528,OPEN,"GE_STARTMENU skips the EEPROM read, so front-end-only sessions never import the emulator save —",OPEN (harness documentation; the D514 imported-save test must run from a normal boot; a log-or-import fix is owed in the port layer)
|
||||
D529,FIXED,The golden gate depends on the save file's CONTENT —,FIXED 2026-10-05 (canonical save in-tree at tools_pc/golden/ge007.eep; verify.sh installs it like the ini pin; all 21 win sets re-captured under it —
|
||||
D530,?,The box (X220) linux golden re-round landed —,AMENDED BY D531 (2026-10-05) —
|
||||
D531,FIXED,capture_p7.sh's D529 adaptation was incomplete —,"FIXED 2026-10-05 (loop re-pins the canonical save per level + cd ""$ROOT""; fresh 21/21 box re-capture committed —"
|
||||
D532,FIXED,The three P12 U5 should-fixes applied —,FIXED 2026-10-05 (tooling-only:
|
||||
D533,FIXED,"The probe-doc drift that would have failed CI, plus three tracked-file accuracy/hygiene fixes (release-sweep spot-check) —",FIXED 2026-10-05 (GE-ENV-PROBES.md missing the GE_D526 family → gen_env_probes.py --check rc 1 = a CI-failing step; build-pc.sh local MSYS2 root in two messages; docs/fidelity.md og line; release-not…
|
||||
D534,FIXED,Pre-release review of the 2026-10-05 local batch —,FIXED 2026-10-05 (verify.sh/capture_p7.sh INT/TERM trap; release-notes linux-gate wording; dead security-and-fidelity-status.md links; future-dated 2026-10-07 labels normalised; D526 probe dangling-e…
|
||||
D540,FIXED,Fog rework (Deck playtest):,FIXED 2026-10-05 (port:
|
||||
D450,FIXED,"Unplugging and replugging a controller was not picked up (user, GameSir-G7 Pro; 2026-09-29).","FIXED — port-only: the render thread's gfx_sdl_handle_events drains the same SDL queue as video.c's host pump but had no case for SDL_CONTROLLERDEVICEADDED/REMOVED, so it usually swallowed them and i…"
|
||||
D436,FIXED,"Persistent (hipfire) crosshair, opt-in:",FIXED (shipped 2026-09-29; rule-2 sign-off:
|
||||
|
@@ -1,78 +0,0 @@
|
||||
# GitHub preview video — runbook
|
||||
|
||||
The README embeds `docs/media/goldeneye-gh-preview.mp4` (the gif of the same
|
||||
name is too heavy for GitHub; keep it only if someone wants a local copy).
|
||||
It's a ~32 s, 8-clip montage of gameplay cut from ShadowPlay session
|
||||
recordings.
|
||||
|
||||
## Tooling
|
||||
|
||||
`scratchpad/` is gitignored, so `make-preview.py` is a **local** tool (it
|
||||
hardcodes local video paths anyway) — this doc is the tracked artifact. If it
|
||||
goes missing, the commands below describe exactly what to rebuild. Run with
|
||||
plain `python`.
|
||||
Working files (per-clip mp4s, concat list, palette, `state.json`) go to
|
||||
`scratchpad/.preview-work/` — disposable, safe to wipe (but then re-run
|
||||
`build` after any edit).
|
||||
|
||||
```sh
|
||||
python scratchpad/make-preview.py list # montage index -> source session + time
|
||||
python scratchpad/make-preview.py shift <i> <±sec> # nudge a clip within its source video
|
||||
python scratchpad/make-preview.py set <i> <sec> # absolute source time
|
||||
python scratchpad/make-preview.py replace <i> <.10|.12|.18|.19> # new random spot in a session
|
||||
python scratchpad/make-preview.py regen --seed N # full random redo (same clips/lengths)
|
||||
python scratchpad/make-preview.py build # re-concat + re-render from state.json
|
||||
```
|
||||
|
||||
Every edit command rebuilds mp4 + gif automatically. A full rebuild takes
|
||||
~30–50 s; a single-clip shift is the same cost (concat dominates). Budget
|
||||
one command per step if running under a timeout — cut+concat and the two gif
|
||||
passes together fit in ~55 s.
|
||||
|
||||
## Source material
|
||||
|
||||
`<videos>\NVIDIA\Build-pc\new\` — ShadowPlay recordings
|
||||
(3072x1728 @ 60 fps, h264), no usable audio track (cut with `-an`). The four
|
||||
sessions and their tags are hardcoded in `SESSIONS` at the top of the script;
|
||||
add a new session there (tag, filename suffix, duration) to use it.
|
||||
|
||||
## Rules learned from review rounds
|
||||
|
||||
- **Never start a clip in the first ~60 s** of a session: desktop / boot /
|
||||
menus. (`MIN_START`)
|
||||
- **Bad zones** — source times that show non-gameplay; random picks avoid
|
||||
them and `replace` should too. Currently known (in `BAD_ZONES`):
|
||||
- `.10` @ ~395–425 s: file-select menu
|
||||
- `.10` @ ~1290–1420 s: watch close-up (appears twice in that session)
|
||||
Add new ones here whenever a review round finds another.
|
||||
- **Clip length** 4 s is the sweet spot; 5 s starts to drag, <3 s flickers.
|
||||
- **Same-session spacing**: two clips from one session ≥90 s apart, or they
|
||||
read as a repeat of the same scene.
|
||||
- When told "roll forward/back ~N seconds", use N ± a few s and prefer a
|
||||
high-motion moment in that window if you have keyframe stats (see below);
|
||||
otherwise a flat offset is fine.
|
||||
- If a pick lands on a menu/loading screen, step it forward again rather
|
||||
than picking a new random time — the user's "a bit later/earlier" almost
|
||||
always means the *same* scene, different part.
|
||||
|
||||
## Output spec (don't change casually)
|
||||
|
||||
- mp4: 1920x1080 @ 30 fps, libx264 crf 20, `+faststart`, no audio. ~25 MB for 32 s.
|
||||
- gif: 512 px wide, 12 fps, two-pass palette (`palettegen` then
|
||||
`paletteuse=dither=bayer:bayer_scale=3`). ~28 MB — local use only.
|
||||
|
||||
## Optional: motion data to find "cool" moments
|
||||
|
||||
`scratchpad/analyze-sessions.py` dumps per-keyframe YAVG stats for the
|
||||
session files; the diff between consecutive keyframes is a decent proxy for
|
||||
"something is happening". If you need to find the coolest moment in a time
|
||||
window, maximize it — but note plain random picks with spacing constraints
|
||||
have been preferred by the reviewer over signal-optimized cuts, so use this
|
||||
only when asked for "something cool".
|
||||
|
||||
## ffmpeg notes (Windows)
|
||||
|
||||
No system ffmpeg; the script falls back to the `imageio-ffmpeg` static
|
||||
binary (`pip install imageio-ffmpeg`). Pass paths as forward-slash or bare
|
||||
filenames with `cwd=` set — drive-letter colons and backslashes break
|
||||
filter-graph argument parsing.
|
||||
@@ -1,7 +1,5 @@
|
||||
# Release plan — "drop in your ROM and play"
|
||||
|
||||
> **Historical record — do not act on status claims in this file.** Part A shipped as the NTSC-U drop-in ROM flow (PR #84, issue #6). The remaining Part B pieces are tracked in the roadmap. Current open work and status: [`docs/ROADMAP.md`](../ROADMAP.md) (2026-09-29).
|
||||
|
||||
Status: proposal / not yet scheduled. Owner: TBD.
|
||||
|
||||
## Goal
|
||||
@@ -222,115 +220,25 @@ offer to create it).
|
||||
`docs/building.md` says it needs region-specific offsets; the code says
|
||||
otherwise. Generate `e` and `j` in CI and diff against a
|
||||
locally-ROM-generated copy to confirm before committing them.
|
||||
|
||||
2. **Does `d43_emit.py` really use `scripts/filelist.u.csv` for all three
|
||||
regions?** — *Resolved: yes, and that is a real bug.* Both emitters
|
||||
hard-code the NTSC filelist regardless of `REGION`: `FILELIST =
|
||||
"scripts/filelist.u.csv"` at `tools_pc/d43_emit.py:47` and
|
||||
`tools_pc/d69_emit.py:56`, so `pal-final`/`jpn-final` runs read NTSC byte
|
||||
offsets out of the wrong ROM — decompression garbage or `find_row()`
|
||||
misses. The per-region CSVs already exist in the repo with the identical
|
||||
shape (`rom_offset,size,asset_path,flag,flag`): `scripts/filelist.e.csv`
|
||||
(32,843 bytes) and `scripts/filelist.j.csv` (37,947 bytes), just with bare
|
||||
names instead of NTSC's `.bin`-suffixed ones — and `find_row()`
|
||||
(`d43_emit.py:89-93`) already tries multiple name variants, so it would
|
||||
work against any of the three CSVs if pointed at the right one. The fix is
|
||||
small: pick `scripts/filelist.{u,e,j}.csv` from `REGION` instead of
|
||||
hard-coding `.u`. **Remaining real unknown:** `d43_emit.py` also reads
|
||||
every `assets/**/modelFileHeader.inc.c` (512 files, e.g.
|
||||
`assets/obseg/chr/armourguard/modelFileHeader.inc.c:2`) to get NS/NT — arg
|
||||
4 and arg 8 of the `MODELFILEHEADER(...)` macro — which it uses in
|
||||
`build_nodes()` (`d43_emit.py`, ~lines 125-157) to compute the model root
|
||||
offset `R0 = 4*NS + 12*NT`. Those headers are committed decomp data, one
|
||||
file per model dir, with no region-suffixed variants; whether NS/NT
|
||||
actually differ between NTSC/PAL/JP ROMs for the same model cannot be
|
||||
verified from files in this repo alone (it would need an actual PAL or JP
|
||||
ROM to check). If they don't differ, the CSV fix above makes `d43`/`d69`
|
||||
region-correct on its own; if they do, that is a second, currently
|
||||
invisible bug.
|
||||
|
||||
3. **Where does Part B's converter write, so `pcmodels.c` / `pccg.c` find it?**
|
||||
— *Resolved.* Both loaders resolve the region from the ROM country byte at
|
||||
offset `0x3E` via the same `Country→region` map (`'E'`→`"ntsc-final"`,
|
||||
`'P'`→`"pal-final"`, `'J'`→`"jpn-final"`;
|
||||
`pcmodelsRegionForCountry()` at `port/src/pcmodels.c:66-74`, same pattern
|
||||
in `port/src/pccg.c:59-67`), then search the prefixes `$S/`, `$E/`, `./`
|
||||
in order (`pcmodels.c:79`; `pccg.c` similar) through `sysResolvePath()`
|
||||
(`port/src/system.c:207-259`: on Windows `$S/` → `./data` if it exists,
|
||||
else `<exedir>/data`; `$E/` → `<exedir>/`). So a native first-run
|
||||
converter in `port/src/pcconvert/` must write exactly, for region `R`:
|
||||
`data/pcmodels-R/pcmodels.bin` + `data/pcmodels-R/manifest.csv`
|
||||
(`pcmodels.c:100,109`) and `data/pccg-R/pccg.bin` +
|
||||
`data/pccg-R/manifest.csv` (`pccg.c:92,101`). The manifest is a CSV with
|
||||
header `name,offset,size`, decimal-parsed via `strtol`
|
||||
(`pcmodels.c:185-186`, `pccg.c:176-177`), row cap 1024 for pcmodels / 256
|
||||
for pccg.
|
||||
|
||||
4. **Launcher vs self-re-exec vs three shortcuts** — pick one UX.
|
||||
|
||||
5. **Splash UI for the first-run convert** — reuse the SDL window / a simple
|
||||
regions?** It hard-codes `FILELIST = "scripts/filelist.u.csv"` even when
|
||||
`REGION` is `pal-final`/`jpn-final`. Either model file offsets are
|
||||
US-identical across regions (plausible — models live in obseg which may not
|
||||
shift) or PAL/JP model conversion is currently untested. Confirm before
|
||||
promising PAL/JP support in the release.
|
||||
3. **Launcher vs self-re-exec vs three shortcuts** — pick one UX.
|
||||
4. **Splash UI for the first-run convert** — reuse the SDL window / a simple
|
||||
progress line, or a pre-SDL Win32 dialog? ~5 s is short enough that a log
|
||||
line + busy cursor may suffice.
|
||||
|
||||
6. **Non-Windows.** Part B's C converter is portable; the launcher and
|
||||
5. **Non-Windows.** Part B's C converter is portable; the launcher and
|
||||
bundling are Windows-only today. Out of scope but the C port keeps the
|
||||
door open.
|
||||
|
||||
7. **Signing.** Unsigned exes + a "reads your ROM" first-run step will trip
|
||||
6. **Signing.** Unsigned exes + a "reads your ROM" first-run step will trip
|
||||
SmartScreen. Not blocking, but worth a release-notes note.
|
||||
|
||||
8. **How much metadata does Part B's CMake codegen actually have to
|
||||
generate?** — *Resolved: far less than the original plan assumed.* The
|
||||
emitters read three kinds of committed metadata:
|
||||
(a) `scripts/filelist.u.csv` (and now `.e`/`.j` per question 2) — plain CSV,
|
||||
~33-41 KB each; readable from disk at runtime, no embedding needed.
|
||||
(b) `assets/obseg/file_resource_table.inc.c` (38,056 bytes, 884 lines) —
|
||||
used only to extract an ordered filename list via regex; a C port can read
|
||||
the already-compiled `file_resource_table[]` symbol directly at link time
|
||||
instead of parsing or embedding it.
|
||||
(c) `assets/**/modelFileHeader.inc.c`, 512 files — **this is the one real
|
||||
codegen artifact**: the C port needs a build-time-generated table of 512
|
||||
entries mapping model symbol → (NS, NT), the two args of the
|
||||
`MODELFILEHEADER(...)` macro that `d43_emit.py` actually uses. Everything
|
||||
else the scripts use — the N64_REC/PC_REC record-size tables, ADDR_OPS
|
||||
marker bytes, `d69_emit.py`'s `TILESIZES[11]` array — are small fixed
|
||||
constants that can just be hand-transcribed into the C port, not
|
||||
codegen'd. Bottom line: the CMake codegen step Part B calls for is
|
||||
narrowly the 512-entry nsnt table, not a general metadata-embedding
|
||||
system.
|
||||
|
||||
## Suggested milestone ordering
|
||||
|
||||
1. Part A (3-region matrix + pre-generated `romassets` + launcher) — days.
|
||||
2. Interim frozen `ge007-convert.exe` wired to first-run — days, optional.
|
||||
3. Part B C port of `d43`/`d69` + embedded metadata + output diffing — weeks.
|
||||
4. Drop the interim binary; single-binary drop-in experience complete.
|
||||
|
||||
## Part B follow-up backlog (was GitHub issue #85, folded here 2026-09-25)
|
||||
|
||||
Follow-up to #6, which shipped in v0.2.0 as an NTSC-U-only drop-in (PR #84). The issue was closed on GitHub and its scope is tracked here now. It has three independent workstreams.
|
||||
|
||||
### 1. PAL/JP asset pipeline repair (data + build)
|
||||
|
||||
`scripts/filelist.e.csv` and `filelist.j.csv` contain naming defects that make PAL/JP conversion impossible from this tree (D258: no PAL/JP PC build has ever been producible). The repair path has been verified but not applied:
|
||||
|
||||
- **PAL:** rename 31 model rows to the exact `file_resource_table.inc.c` symbols. Examples: `Pbridge_console` ×6 → `Pbridge_console1aZ…3bZ`, and `Pchrgrenadelauncher` → `PchrgrenadelaunchZ`. Greedy sequence alignment proves the mapping: 512×512 exact 1:1, offsets non-decreasing, and zero size anomalies against US.
|
||||
- **JP:** rename the mislabeled font row @3044276 from `fontZurichBold_fontchartable.bin` to `fontBankGothic_fontchartable.bin`. The contiguity invariant confirms it: the Gothic kerning table @3043600+676 ends exactly at this row, and the size matches the US Gothic chartable.
|
||||
- **Then:**
|
||||
- `gen_romassets.py e/j` must run clean without a ROM.
|
||||
- Commit `romassets_e.s` and `romassets_j.s`.
|
||||
- Replace the hardcoded `filelist.u.csv` in the d43/d69/d88 emitters with a region→suffix map.
|
||||
- Remove the NTSC-U-only gate in `prepare-assets.py`.
|
||||
- **Needs a PAL and/or JP ROM** for final byte-level verification of at least one region. Only NTSC is available locally.
|
||||
|
||||
### 2. Multi-region release layout
|
||||
|
||||
- Give each region its own OUTPUT_NAME (`ge007-ntsc-final` / `-pal-final` / `-jpn-final`) plus a default alias.
|
||||
- Add a region re-exec guard in `romdata.c` (~50 lines): if the country byte at 0x3E doesn't match the build, re-exec the sibling exe.
|
||||
- CI builds all 3 regions sequentially, and the bundle scripts package all 3 exes.
|
||||
|
||||
### 3. C port of the emitters (optional, removes PyInstaller)
|
||||
|
||||
Port the d43/d69/d88 emit logic to C. That removes the frozen-converter dependency entirely, because the converter becomes part of the engine binary. It's a 2–4 week task, and PD's `filemodel.c` is the closest analogue. It isn't required for PAL/JP: the frozen Python path works for all three regions once #1 lands.
|
||||
|
||||
**Acceptance:** a user with a PAL or JP ROM gets the same one-step drop-in experience as NTSC does today.
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
title: Documentation
|
||||
description: The technical documentation for the GoldenEye 007 PC port; the architecture, the N64-to-PC bug catalogue, and how the project is developed.
|
||||
date: '2026-10-05'
|
||||
modified_time: '2026-10-05'
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
The technical documentation for the port: the **architecture** and how the
|
||||
project is **developed**. (Building and running from source live on the
|
||||
separate [Building](building.md) page.)
|
||||
|
||||
## Architecture
|
||||
|
||||
- **[Internals](internals.md)** — the architecture: the software RSP, the
|
||||
port layer, the asset pipeline, and the phased plan. The single reference
|
||||
for what the port is and how it's put together.
|
||||
- **[Porting notes](porting-notes.md)** — the catalogue of N64→PC bugs: the
|
||||
recurring classes of defect and the ground-truth pattern each fix follows.
|
||||
|
||||
## Development
|
||||
|
||||
- **[Development process](dev-process.md)** — the investigation workflow:
|
||||
task budgets, file partitioning, the finding-log discipline, and the rules
|
||||
for what counts as a diagnosis versus a change.
|
||||
- **[Agentic development](dev/agentic-development.md)** — the case study:
|
||||
how the two-AI-agent setup ran, its timeline and numbers, and an honest
|
||||
read on what did and didn't work.
|
||||
- **[Security status](security.md)** — what the release packages install
|
||||
(and don't): no networking unless you opt in to the update check, no telemetry, no ROM, no system writes.
|
||||
- **[Fidelity status](fidelity.md)** — how faithfully the port reproduces
|
||||
the N64 game's logic, and what was audited to back that claim.
|
||||
|
Before Width: | Height: | Size: 3.4 KiB |
@@ -1,72 +0,0 @@
|
||||
---
|
||||
title: Fidelity status
|
||||
description: How faithfully the port reproduces the N64 game: unmodified decompiled logic, Makefile-mirrored region macros, one game-logic-adjacent change, and PRNG notes.
|
||||
date: '2026-10-05'
|
||||
modified_time: '2026-10-05'
|
||||
---
|
||||
|
||||
## Fidelity status
|
||||
|
||||
This page answers one question plainly: *how faithfully does the port
|
||||
actually reproduce the original N64 game's logic*?
|
||||
The audit was run end-to-end on 2026-09-15 and re-verified for the v0.3.0
|
||||
release bundles on 2026-09-18.
|
||||
|
||||
**v0.5.0 update pass, 2026-10-05:** two of the checks were re-run
|
||||
directly against the current tree — the region build macros were
|
||||
compared line-by-line against the original N64 Makefile's macro sets
|
||||
(all three regions match exactly), and the library file classification
|
||||
(which original files compile versus get PC-shimmed) was re-checked
|
||||
against the project's own ground-truth manifest (the CMake build
|
||||
compiles the libultra and libultrare audio sets, the gu matrix helpers,
|
||||
and `io/vitbl.c` only — no silent gaps, no silent duplicates). The
|
||||
full game-logic audit was not re-run in this pass; recent `src/game`
|
||||
edits on the release branch are finding-gated (for example the D519
|
||||
front-end options work, and a model-c scripted-camera clamp removal
|
||||
that returned those files to pure decompilation). The full engineering
|
||||
record behind each item is in the project's finding log
|
||||
([`docs/dev/findings.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/dev/findings.md)).
|
||||
|
||||
## The rule and what was checked
|
||||
|
||||
The port's non-negotiable rule is that decompiled game logic is never
|
||||
changed — only genuine N64-hardware dependencies get a PC-side
|
||||
replacement, in the `port/` layer. This was audited directly:
|
||||
|
||||
- **Region build macros** exactly mirror the original N64 Makefile's macro
|
||||
sets for all three regions — verified line-by-line (re-run 2026-10-05).
|
||||
- **The audio/library file classification** (which original files compile
|
||||
vs. get PC-shimmed) exactly matches the project's own ground-truth
|
||||
manifest — no silent gaps, no silent duplicates (re-run 2026-10-05).
|
||||
- **The one shipped game-logic-adjacent change** (a portal-culling
|
||||
float-precision fix, D271) is `#ifdef PORT`-gated, documented in the
|
||||
finding log, and moves *toward* matching N64 behavior, not away from it.
|
||||
Every other deviation from the decompiled source is one of the documented
|
||||
32→64-bit pointer-width ABI corrections described above.
|
||||
|
||||
## One issue, found and fixed before v0.3.0 shipped
|
||||
|
||||
The port's random-number generator (`port/src/random.c`) was documented
|
||||
as a bit-exact port of the N64's PRNG, but wasn't — a shift-operation
|
||||
helper didn't match the real MIPS64 instruction semantics it was meant to
|
||||
mirror. This was independently confirmed by hand-tracing the assembly
|
||||
against the C port, tracked as finding D284, and fixed before the release
|
||||
(verified clean across a full level sweep — all 20 missions plus the
|
||||
ending sequence — no crashes). It affected loot placement, AI behavior
|
||||
variance, and replay-state determinism from the very first random draw,
|
||||
in every release before v0.3.0. Because of the fix, RNG-derived output —
|
||||
including the save-file CRC — changed, which meant existing save files
|
||||
and any previously recorded replays no longer validate; v0.3.0's release
|
||||
notes describe the automatic save migration that handles this. We'd
|
||||
rather say this plainly than let a "verified bit-exact" claim stand
|
||||
uncorrected, both when it was wrong and now that it's fixed.
|
||||
|
||||
Everything else checked (the documented ABI/pointer-width exceptions used
|
||||
for the 32-to-64-bit transition, a sample of the finding log's own claimed
|
||||
fixes against the actual current code) matched what's documented.
|
||||
|
||||
---
|
||||
|
||||
*This page is kept current alongside future releases. Its source review is a
|
||||
manual, human-reviewed process today — see the project roadmap for plans to
|
||||
partially automate the mechanical parts of it.*
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
title: Framerate and pacing
|
||||
description: How the GoldenEye 007 PC port holds a steady 60 fps and keeps per-frame game timing correct on a fast CPU.
|
||||
date: '2026-10-05'
|
||||
modified_time: '2026-10-05'
|
||||
---
|
||||
|
||||
## Framerate and pacing
|
||||
|
||||
The port targets a **steady 60 fps** with VSync and an optional 30/60 frame
|
||||
cap, including on low-end GPUs.
|
||||
|
||||
The N64's game code assumed a **60 Hz vertical-interrupt tick** — it ran a
|
||||
few ticks per displayed frame. On a PC the CPU is far faster, so left alone
|
||||
the game logic would race ahead. The port handles this in two places:
|
||||
|
||||
- **Frame pacing** happens in `port/src/video.c` on the render thread (the
|
||||
software RSP thread), which paces the frame and does the swap and FPS
|
||||
accounting.
|
||||
- **Per-frame counters** in the game are gated with `portN64FrameStep()` so
|
||||
they advance at the *display* rate, not the CPU rate. Getting this wrong is
|
||||
a recurring class of subtle timing bug (a counter that ticks too fast, a
|
||||
stall that isn't real) — the [porting notes](porting-notes.md) track these
|
||||
(for example D13, D22 and D486).
|
||||
|
||||
On very weak integrated GPUs (Atom/Celeron class) the first launch writes
|
||||
lighter values for draw distance, LOD distance and anti-aliasing into
|
||||
`ge007.ini` so the game still holds 60 fps.
|
||||
|
||||
*More: [porting notes](porting-notes.md) — the timing-related findings (D13,
|
||||
D22, D486).*
|
||||
|
After Width: | Height: | Size: 317 KiB |
|
After Width: | Height: | Size: 229 KiB |
|
After Width: | Height: | Size: 531 KiB |
|
Before Width: | Height: | Size: 330 KiB |
|
Before Width: | Height: | Size: 668 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 621 KiB |
|
Before Width: | Height: | Size: 287 KiB |
|
After Width: | Height: | Size: 939 KiB |
|
Before Width: | Height: | Size: 284 KiB |
|
Before Width: | Height: | Size: 286 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 396 KiB |
|
Before Width: | Height: | Size: 817 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 131 KiB |
@@ -1,13 +0,0 @@
|
||||
new_name,original,video_session,snapshot_time
|
||||
shot-01.jpg,v040/dam0.png,v0.4.0 attract pass 2026.09.28,
|
||||
shot-03.jpg,v040/runway.png,v0.4.0 attract pass 2026.09.28,
|
||||
shot-04.jpg,v040/surface 1.png,v0.4.0 attract pass 2026.09.28,
|
||||
shot-09.jpg,v040/frigate 0.png,v0.4.0 attract pass 2026.09.28,
|
||||
shot-11.jpg,v040/surface 2.png,v0.4.0 attract pass 2026.09.28,
|
||||
shot-16.jpg,v040/statue 0.png,v0.4.0 attract pass 2026.09.28,
|
||||
shot-20.jpg,v040/archives 0.png,v0.4.0 attract pass 2026.09.28,
|
||||
shot-21.jpg,v040/cradle 0.png,v0.4.0 attract pass 2026.09.28,
|
||||
shot-24.jpg,v040/aztec 0.png,v0.4.0 attract pass 2026.09.28,
|
||||
shot-27.jpg,v040/control 0.png,v0.4.0 attract pass 2026.09.28,
|
||||
shot-28.jpg,v040/streets 0.png,v0.4.0 attract pass 2026.09.28,
|
||||
shot-29.jpg,v040/jungle.png,v0.4.0 attract pass 2026.09.28,
|
||||
|
|
Before Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 184 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 117 KiB |
@@ -1,267 +1,34 @@
|
||||
---
|
||||
title: GoldenEye 007 PC Port
|
||||
# v0.5.0: status bumped from v0.4.0. Description kept short (Bing's 160 limit).
|
||||
description: >-
|
||||
A native PC port of GoldenEye 007 (Nintendo 64, 1997), built from
|
||||
decompiled source with a software RSP. v0.5.0 for Windows, Linux and
|
||||
Steam Deck.
|
||||
# Freshness signals for jekyll-seo-tag (article:published_time / modified_time
|
||||
# and the WebPage JSON-LD dates). Keep these equal to the last real content
|
||||
# change of THIS page, not to the last release.
|
||||
date: '2026-10-05'
|
||||
modified_time: '2026-10-07'
|
||||
locale: en_US
|
||||
# The masthead is this page's title, so its first content h2 must NOT get the
|
||||
# amber page-title look (double header). See the .flat-title rule in
|
||||
# _includes/head-custom.html.
|
||||
flat_title: true
|
||||
description: A native PC port of the original Nintendo 64 GoldenEye 007, built from decompiled source — and a case study in AI-agent collaboration on a large low-level codebase.
|
||||
---
|
||||
|
||||
A native PC port of _GoldenEye 007_ (Rare, 1997, Nintendo 64), compiled from
|
||||
the [GoldenEye 007 decompilation](https://github.com/n64decomp/007): the
|
||||
original N64 game running from reconstructed source, not the Xbox 360
|
||||
remaster. The N64's graphics coprocessor (RSP) is emulated in software; every
|
||||
other hardware surface (video, audio, input, timers, save storage) is shimmed
|
||||
in a dedicated `port/` layer, following the architecture of the
|
||||
[Perfect Dark PC port](https://github.com/fgsfdsfgs/perfect_dark), the same
|
||||
Rare "Indy" engine family, one hardware generation apart.
|
||||
# GoldenEye 007 PC Port
|
||||
|
||||
[Status](#status) · [Download](#download) · [See it running](#see-it-running) · [Known issues](#known-issues) · [Documentation](#documentation)
|
||||
A work-in-progress **native PC port of the original 1997 Nintendo 64
|
||||
_GoldenEye 007_**, compiled from the game's
|
||||
[decompiled source](https://github.com/n64decomp/007) and running the N64's
|
||||
graphics coprocessor in software. It follows the architecture of the
|
||||
[Perfect Dark PC port](https://github.com/fgsfdsfgs/perfect_dark).
|
||||
|
||||
## Status
|
||||
It is also a **research project on AI-agent collaboration in a large,
|
||||
unfamiliar, low-level codebase**: two coding agents — a local open-weight model
|
||||
(Qwen 3.8) on a single RTX 5090, and Claude / Claude Code — driven by one person
|
||||
part-time through ~230 translation units of unmodified big-endian MIPS game
|
||||
code, handing work back and forth through shared written notes.
|
||||
|
||||
<div class="cblock" markdown="1">
|
||||
**Status:** Phase 2 of 4 (rendering); first alpha **v0.1.0** released. Runs the
|
||||
full single-player campaign — 19 of 21 missions completable start to finish in
|
||||
a full-campaign playtest, two levels still crash mid-mission. No audio yet; AI pacing
|
||||
and cutscenes are rough.
|
||||
|
||||
### v0.5.0 (2026-10-07) — split-screen multiplayer, one options menu, closer to the N64.
|
||||
|
||||
- **2–4 player split-screen multiplayer** on every multiplayer map, each
|
||||
player with their own pad, controls and aim settings.
|
||||
- **One options menu** for every setting, laid out like the Perfect Dark
|
||||
port's, with the game's own crosshair as the mouse pointer. Pick the game's
|
||||
own N64 control styles (1.1–2.4) per player, or the Xbox release's presets
|
||||
with per-player pad rebinding; PlayStation and Nintendo pads show their own
|
||||
button names.
|
||||
- **Closer to the N64:** fog and haze (Surface 2's ground fog, the sky at the
|
||||
horizon), automatic-weapon fire rate, sniper and camera zoom, Dam and
|
||||
Caverns water, the Jungle boss fight's timing, guard visibility in
|
||||
widescreen, and the Dam ending camera.
|
||||
- **Fewer crashes and glitches:** a multiplayer crash with the crouched rifle,
|
||||
rockets passing through the ground, and broken geometry around Aztec's
|
||||
shuttle.
|
||||
- **A steady 60 fps on low-end GPUs** (tested on an Intel HD 3000 laptop),
|
||||
with lighter first-launch defaults on Atom/Celeron-class graphics.
|
||||
- **Emulator saves load directly:** copy a 1964 or Project64 save in as
|
||||
`data/ge007.eep` and it is converted on first launch.
|
||||
- Also: more audio, video and control settings (master volume, audio device,
|
||||
trilinear filtering, draw distance up to 800%, PC-friendly crouch and
|
||||
look-ahead defaults), and controllers are picked up again when replugged.
|
||||
|
||||
</div>
|
||||
|
||||
What remains is a short list, under [Known issues](#known-issues).
|
||||
|
||||
**This is a pre-1.0 release, not a finished product.** v1.0 is the target
|
||||
for a polished, feature-complete build; expect missing features and the
|
||||
occasional breaking change until then. The [README's Roadmap
|
||||
section](https://github.com/jkdansereau/goldeneye-pc-port#roadmap) is the
|
||||
single tracker for what comes next.
|
||||
|
||||
<details id="known-issues">
|
||||
<summary>Known issues</summary>
|
||||
|
||||
<p>Full table in the <a href="https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/ROADMAP.md#known-issues">roadmap's known-issues section</a>; root causes in the <a href="https://github.com/jkdansereau/goldeneye-pc-port/tree/main/docs/dev">finding log</a>.</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>NTSC-U ROMs only in the release packages.</strong> PAL and JP ROMs convert, build and run from source, but release packages ship the US (NTSC-U) ROM. On the roadmap.</li>
|
||||
<li><strong>Changing the aspect ratio in a level can briefly glitch the gun/hand model</strong> (cosmetic, one-off). Change the ratio from the front-end PC Options, or accept it (D509).</li>
|
||||
<li><strong>No macOS or ARM builds yet.</strong> Today the release ships Windows, Linux and Steam Deck (keyboard, mouse and controller-button rebinding are all supported, defaults in the <a href="https://github.com/jkdansereau/goldeneye-pc-port#controls">README's Controls section</a>); macOS and ARM are on the roadmap.</li>
|
||||
<li><strong>Saves from v0.4.0 and earlier can hold fake unlocks from <code>All unlocked</code></strong> — <code>All unlocked</code> has not written your save since v0.5.0, but saves made while it was on in v0.4.0 and earlier can hold fake unlocks and are not repaired; back up <code>data/ge007.eep</code> if you used it on an older build (D442, D387).</li>
|
||||
<li><strong>The first frame of a level takes a little longer</strong> while its textures upload — a brief FPS-counter dip; nothing to fix (D475, D480).</li>
|
||||
<li><strong>In widescreen, far objects almost fully in fog can still show a faint distant building edge</strong> (4:3 matches the N64). Cosmetic (D503).</li>
|
||||
</ul>
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Release history</summary>
|
||||
|
||||
<p class="warn"><strong>Warning:</strong> always use the latest release — earlier builds are kept in the release history for reference only; they lack the features and fixes of newer versions, so don't install an older release.</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>2026-10-07 — v0.5.0</strong>: what it adds is the Status list above. <a href="https://github.com/jkdansereau/goldeneye-pc-port/releases/tag/v0.5.0">Release notes</a>.</li>
|
||||
<li><strong>2026-09-28 — v0.4.0</strong>: native widescreen, a complete aim system for mouse and controller, in-game key rebinding, crosshair customization, rumble-pak haptics, a rebuilt options overlay, and a broad fidelity-fix pass. <a href="https://github.com/jkdansereau/goldeneye-pc-port/releases/tag/v0.4.0">Release notes</a>.</li>
|
||||
<li><strong>2026-09-20 — v0.3.0</strong>: the first release with the complete campaign playable end to end at 60 fps on Windows, Linux, and Steam Deck. <a href="https://github.com/jkdansereau/goldeneye-pc-port/releases/tag/v0.3.0">Release notes</a>.</li>
|
||||
<li><strong>2026-09-04 → 2026-09-16 — v0.1.0 – v0.2.2</strong>: the alpha and beta cycle — build chain, software RSP, first rendered frames, front end, and per-level stabilization across the campaign.</li>
|
||||
</ul>
|
||||
|
||||
</details>
|
||||
|
||||
## Download
|
||||
|
||||
<div class="cblock" markdown="1">
|
||||
|
||||
| Platform | Bundle | Notes |
|
||||
|---|---|---|
|
||||
| **Windows** (x86_64) | [win64.zip](https://github.com/jkdansereau/goldeneye-pc-port/releases) | Engine + runtime DLLs + the one-time asset tool. |
|
||||
| **Linux** (x86_64) / **Steam Deck** | [linux tarball](https://github.com/jkdansereau/goldeneye-pc-port/releases) | SDL2 is bundled, so it runs as-is on any distro, and sideloads onto a Deck with nothing installed. |
|
||||
| macOS / ARM Linux | — | Roadmap: not shipped yet. |
|
||||
|
||||
**Bring your own ROM.** Both bundles contain no ROM and no game assets: you
|
||||
supply your own GoldenEye 007 N64 ROM (the
|
||||
[Requirements table](https://github.com/jkdansereau/goldeneye-pc-port#requirements)
|
||||
has the region filenames and SHA-1s). This release supports the NTSC-U (US)
|
||||
ROM; PAL and JP are on the roadmap. Then: unpack, drop the ROM in `data/`,
|
||||
and launch; the first run generates the derived assets automatically (no
|
||||
Python or other tooling needed). The full steps are in the
|
||||
[Quick start](https://github.com/jkdansereau/goldeneye-pc-port#quick-start);
|
||||
the packages carry no ROM or game assets, and run only with a ROM you
|
||||
already own. The binaries are not code-signed; each
|
||||
release artifact carries a GitHub build-provenance attestation you can
|
||||
verify with `gh attestation verify <file> --repo
|
||||
jkdansereau/goldeneye-pc-port`.
|
||||
|
||||
</div>
|
||||
|
||||
What the release actually installs is covered on [Security status](security.md);
|
||||
how faithfully it tracks the N64 game, on [Fidelity status](fidelity.md).
|
||||
|
||||
## See it running
|
||||
|
||||
<div class="viewer" id="viewer">
|
||||
<div class="vbar">
|
||||
<span class="dots"><i></i><i></i><i></i></span>
|
||||
<span class="lbl">viewer — in-engine captures, 3840×2160</span>
|
||||
<span class="idx" id="vIdx">1 / 14</span>
|
||||
</div>
|
||||
<div class="vstage">
|
||||
<img id="vImg" src="img/shots/feature-splitscreen.jpg" alt="GoldenEye 007 PC port — 2-player split-screen multiplayer, in-engine capture">
|
||||
<button class="vbtn prev" id="vPrev" aria-label="previous capture">‹</button>
|
||||
<button class="vbtn next" id="vNext" aria-label="next capture">›</button>
|
||||
</div>
|
||||
<div class="vcap"><span id="vCap">2-player split-screen — v0.5.0</span></div>
|
||||
</div>
|
||||
|
||||
## Play it, or take it apart
|
||||
|
||||
- **Try it yourself**: grab a bundle above, bring your own ROM, and play the
|
||||
campaign. It's a pre-1.0 build for exactly this: if something breaks, an
|
||||
[issue](https://github.com/jkdansereau/goldeneye-pc-port/issues) with what
|
||||
you were doing is genuinely useful.
|
||||
- **Read the code**: the game logic in `src/` is unmodified decompilation;
|
||||
every hardware surface (video, audio, input, save storage, and the
|
||||
software-RSP renderer) is shimmed in the MIT-licensed `port/` layer.
|
||||
[Internals](internals.md) is the map; [Porting notes](porting-notes.md) is
|
||||
the catalogue of N64→PC bug classes hit along the way, a good read even if
|
||||
you never touch the code.
|
||||
- **Mod it**: the port layer, build system and `tools_pc/` helpers are MIT
|
||||
licensed and yours to extend: new video options, input tweaks, your own
|
||||
asset sidecar. [CONTRIBUTING](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/CONTRIBUTING.md)
|
||||
has the ground rules that keep it faithful to the original game.
|
||||
- **AI disclosure**: development here used AI coding agents — Claude Code
|
||||
plus a local open-weight model on a single RTX 5090, directed by one person
|
||||
in their spare time. The project is as much a study of that process as it
|
||||
is a port; the write-up records what happened and takes no position for or
|
||||
against using LLMs on a project like this. See [the full
|
||||
write-up](dev/agentic-development.md) — the setup, timeline, handoff
|
||||
workflow, and an honest account of what did and didn't work.
|
||||
|
||||
## How it works
|
||||
|
||||
The R4300 game code's logic and control flow are the decompilation's, and
|
||||
remain the reference we preserve. The port's changes inside `src/` are few in
|
||||
kind and each is gated behind `#ifdef PORT`: mechanical 32→64-bit
|
||||
pointer-width fixes, a couple of approved timing fixes, and the opt-in
|
||||
*All unlocked* and *Skip intro* hooks. The N64's Reality Signal Processor
|
||||
(RSP), the graphics coprocessor that builds and executes each frame's
|
||||
display list, is emulated in software: the port interprets the graphics
|
||||
command stream (GBI) the game emits and translates it to OpenGL, bypassing
|
||||
the RDP entirely. Everything else that would touch N64 hardware (video,
|
||||
audio, input, timers, save storage) is shimmed in a small dedicated layer,
|
||||
following the architecture of the Perfect Dark PC port from the same Rare
|
||||
engine family. Full detail: [Internals](internals.md); the bug
|
||||
catalogue: [Porting notes](porting-notes.md).
|
||||
- **[Project repository and README](https://github.com/jkdansereau/goldeneye-pc-port)** — build instructions, requirements, status, how it compares to the Xbox 360 recompilation projects.
|
||||
|
||||
## Documentation
|
||||
|
||||
The technical docs live on the [Documentation](documentation.md) page — the
|
||||
architecture, the N64→PC bug catalogue, and how the project is developed.
|
||||
[Building](building.md) covers building and running from source.
|
||||
|
||||
<script>
|
||||
// console-viewer carousel: one media slot, cycle the captures. Vanilla JS.
|
||||
(function () {
|
||||
var slides = [
|
||||
{ src:"img/shots/feature-splitscreen.jpg", cap:"2-player split-screen — v0.5.0" },
|
||||
{ src:"img/shots/feature-options.jpg", cap:"PC options menu, custom crosshair colour — v0.5.0" },
|
||||
{ src:"img/shots/shot-01.jpg", cap:"Dam — v0.5.0 intro attract, 4K" },
|
||||
{ src:"img/shots/shot-03.jpg", cap:"Runway — v0.5.0 intro attract, 4K" },
|
||||
{ src:"img/shots/shot-04.jpg", cap:"Surface — v0.5.0 intro attract, 4K" },
|
||||
{ src:"img/shots/shot-09.jpg", cap:"Frigate — v0.5.0 intro attract, 4K" },
|
||||
{ src:"img/shots/shot-11.jpg", cap:"Surface 2 — v0.5.0 intro attract, 4K" },
|
||||
{ src:"img/shots/shot-16.jpg", cap:"Statue — v0.5.0 intro attract, 4K" },
|
||||
{ src:"img/shots/shot-20.jpg", cap:"Archives — v0.5.0 intro attract, 4K" },
|
||||
{ src:"img/shots/shot-21.jpg", cap:"Cradle — v0.5.0 intro attract, 4K" },
|
||||
{ src:"img/shots/shot-24.jpg", cap:"Aztec — v0.5.0 intro attract, 4K" },
|
||||
{ src:"img/shots/shot-27.jpg", cap:"Control — v0.5.0 intro attract, 4K" },
|
||||
{ src:"img/shots/shot-28.jpg", cap:"Streets — v0.5.0 intro attract, 4K" },
|
||||
{ src:"img/shots/shot-29.jpg", cap:"Jungle — v0.5.0 intro attract, 4K" }
|
||||
];
|
||||
var img = document.getElementById("vImg"),
|
||||
cap = document.getElementById("vCap"),
|
||||
idx = document.getElementById("vIdx"),
|
||||
i = 0;
|
||||
function show(n) {
|
||||
i = (n + slides.length) % slides.length;
|
||||
img.src = slides[i].src;
|
||||
img.alt = "GoldenEye 007 PC port — " + slides[i].cap +
|
||||
", in-engine capture, 3840×2160";
|
||||
cap.textContent = slides[i].cap;
|
||||
idx.textContent = (i + 1) + " / " + slides.length;
|
||||
}
|
||||
document.getElementById("vPrev").addEventListener("click", function () { show(i - 1); });
|
||||
document.getElementById("vNext").addEventListener("click", function () { show(i + 1); });
|
||||
document.addEventListener("keydown", function (e) {
|
||||
if (e.key === "ArrowLeft") show(i - 1);
|
||||
else if (e.key === "ArrowRight") show(i + 1);
|
||||
});
|
||||
show(0);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Software",
|
||||
"name": "GoldenEye 007 PC Port",
|
||||
"genericName": "Native PC port of a Nintendo 64 game",
|
||||
"softwareName": "GoldenEye 007 PC Port",
|
||||
"category": "GameCategory",
|
||||
"description": "A native PC build of GoldenEye 007 (Rare, 1997, Nintendo 64), compiled from the n64decomp/007 decompilation with the N64's RSP emulated in software. Ships no ROM and no game assets; you supply your own.",
|
||||
"softwareVersion": "0.5.0",
|
||||
"datePublished": "2026-10-05",
|
||||
"dateModified": "2026-10-07",
|
||||
"operatingSystem": ["Windows", "Linux", "Steam Deck"],
|
||||
"downloadUrl": "https://github.com/jkdansereau/goldeneye-pc-port/releases",
|
||||
"distributionType": "Software-Offline",
|
||||
"officialDistribution": {
|
||||
"@type": "SoftwareDistribution",
|
||||
"downloadUrl": "https://github.com/jkdansereau/goldeneye-pc-port/releases",
|
||||
"distributionLicense": "MIT"
|
||||
},
|
||||
"license": "https://opensource.org/licenses/MIT",
|
||||
"isFreeApplication": true,
|
||||
"url": "https://jkdansereau.github.io/goldeneye-pc-port/",
|
||||
"sameAs": "https://github.com/jkdansereau/goldeneye-pc-port",
|
||||
"publisher": {
|
||||
"@type": "Person",
|
||||
"name": "jkdansereau",
|
||||
"url": "https://github.com/jkdansereau"
|
||||
},
|
||||
"gallery": [
|
||||
"https://jkdansereau.github.io/goldeneye-pc-port/img/shots/shot-28.jpg",
|
||||
"https://jkdansereau.github.io/goldeneye-pc-port/img/shots/shot-01.jpg",
|
||||
"https://jkdansereau.github.io/goldeneye-pc-port/img/shots/shot-24.jpg"
|
||||
],
|
||||
"alternateName": "GoldenEye 007 (Nintendo 64, 1997) on the PC"
|
||||
}
|
||||
</script>
|
||||
- **[The two-agent development case study](dev/agentic-development.md)** — goal, setup, timeline, the handoff workflow, and an honest assessment of what did and didn't work.
|
||||
- **[Development process](dev-process.md)** — how work was scoped, partitioned, and budgeted across agents; the finding-log discipline.
|
||||
- **[Internals](internals.md)** — architecture, the software RSP-emulation approach, GoldenEye-vs-Perfect-Dark engine differences, the phased plan.
|
||||
- **[Porting notes](porting-notes.md)** — the recurring Nintendo 64 → PC bug classes hit during the port, with fixes.
|
||||
- **[Game-behavior reference](dev/game-behavior-reference.md)** — how the retail N64 game is meant to behave: combat/AI model, difficulty scaling, per-level objectives, timers, weapon data, and the original game's known quirks.
|
||||
- **[Building](building.md)** — full build and asset-extraction guide.
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
title: Input and aim
|
||||
description: How the GoldenEye 007 PC port maps keyboard, mouse and modern controllers onto the N64's controller, including mouse aim.
|
||||
date: '2026-10-05'
|
||||
modified_time: '2026-10-05'
|
||||
---
|
||||
|
||||
## Input and aim
|
||||
|
||||
`port/src/input.c` maps **SDL2 keyboard, mouse and gamepad** events into the
|
||||
N64's own controller structs, so the game's input code runs unmodified.
|
||||
|
||||
**Mouse aim** is modelled on the *GEPD* mouse-injector behaviour that the 1964
|
||||
"GoldenEye/Perfect Dark Edition" bundle used — an **independent
|
||||
reimplementation** of that behaviour (not derived code), credited to its
|
||||
author in the README. It comes in two styles, the N64's own aim feel or a
|
||||
centered FPS-style, with per-device sensitivity, smoothing and Y-inversion.
|
||||
|
||||
**Controllers:** modern pads get a dual-stick layout, deadzones,
|
||||
sensitivity, southpaw, trigger thresholds, Rumble-Pak vibration, and
|
||||
PlayStation/Nintendo button names. **Everything is rebindable** — keyboard,
|
||||
mouse and each controller — in the F10 options overlay. Split-screen needs one
|
||||
controller per extra player.
|
||||
|
||||
**Control style** offers **Ext** (this port's scheme) or **Original**, the
|
||||
game's own N64 styles 1.1–2.4, chosen per seat. New profiles start with
|
||||
**Look ahead** off (it fights mouse look) and **Crouch** on Toggle; the
|
||||
game's own Look up/down row was removed in favour of **Invert look** on the
|
||||
Mouse and Controller pages.
|
||||
|
||||
*More: [internals](internals.md) §7 (input & saves); the aim model's
|
||||
provenance is the D194 lineage in `port/src/input.c`.*
|
||||
@@ -1,18 +1,12 @@
|
||||
---
|
||||
title: Internals
|
||||
description: Architecture behind the PC port; the software RSP-emulation approach, GoldenEye-vs-Perfect-Dark engine differences, and the phased development plan.
|
||||
---
|
||||
|
||||
## Architecture & plan
|
||||
# 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
|
||||
> Phase 2 — for current status see the
|
||||
> [README](https://github.com/jkdansereau/goldeneye-pc-port#status) and
|
||||
> [`ROADMAP.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/ROADMAP.md);
|
||||
> for the blow-by-blow finding log see
|
||||
> [`dev/findings.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/dev/findings.md).
|
||||
> [`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
|
||||
@@ -28,18 +22,18 @@ platforms.
|
||||
|
||||
1. [Executive summary](#1-executive-summary)
|
||||
2. [What the PD port actually does (the reference architecture)](#2-what-the-pd-port-actually-does-the-reference-architecture)
|
||||
· [2.1 Emulate the RSP, bypass the RDP](#21-the-key-architectural-insight-emulate-the-rsp-bypass-the-rdp)
|
||||
— [2.1 Emulate the RSP, bypass the RDP](#21-the-key-architectural-insight-emulate-the-rsp-bypass-the-rdp)
|
||||
· [2.2 How the RSP is invoked](#22-how-the-rsp-is-invoked)
|
||||
· [2.3 Build system](#23-build-system)
|
||||
· [2.4 PD-port copy-candidate audit](#24-pd-port-copy-candidate-audit-session-2026-08-22)
|
||||
3. [GoldenEye decomp: what we're porting](#3-goldeneye-decomp-what-were-porting)
|
||||
· [3.1 Entry / main loop](#31-entry--main-loop)
|
||||
— [3.1 Entry / main loop](#31-entry--main-loop)
|
||||
· [3.2 The two libultra trees](#32-the-two-libultra-trees-srclibultra-and-srclibultrare)
|
||||
4. [N64 hardware surfaces that must be shimmed](#4-n64-hardware-surfaces-that-must-be-shimmed)
|
||||
5. [GE-specific graphics differences vs PD (the RSP work)](#5-ge-specific-graphics-differences-vs-pd-the-rsp-work)
|
||||
6. [Audio](#6-audio)
|
||||
7. [Input & saves](#7-input--saves)
|
||||
8. [Recommended plan](#8-recommended-plan); phases 0–4
|
||||
8. [Recommended plan](#8-recommended-plan) — phases 0–4
|
||||
9. [Open questions / risks](#9-open-questions--risks)
|
||||
10. [References](#10-references)
|
||||
|
||||
@@ -70,7 +64,7 @@ platforms.
|
||||
* **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
|
||||
file-backed EEPROM plus no-op PFS/motor shims — simpler than a full Memory
|
||||
Pak emulation.
|
||||
|
||||
---
|
||||
@@ -127,7 +121,7 @@ R4300 builds a Gfx display list
|
||||
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
|
||||
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`):
|
||||
@@ -162,7 +156,7 @@ The PC build is **CMake** (the N64 build stays Make + IDO). Dependencies:
|
||||
* `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/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`
|
||||
|
||||
@@ -176,38 +170,38 @@ for this project (see CONTRIBUTING.md): same Rare "Indy"/Bond engine family, and
|
||||
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
|
||||
**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/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
|
||||
**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
|
||||
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,
|
||||
- `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
|
||||
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
|
||||
- `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.
|
||||
`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,
|
||||
(`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
|
||||
**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
|
||||
@@ -221,19 +215,19 @@ D32 procedure).
|
||||
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`,
|
||||
* `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`,
|
||||
* `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*`
|
||||
* `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).
|
||||
* `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
|
||||
* **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.
|
||||
@@ -265,23 +259,23 @@ overlap.
|
||||
|
||||
What the PC build must do with each:
|
||||
|
||||
* **`src/libultra/audio/*.c`**; standard libaudio (AL). Runs on the CPU;
|
||||
* **`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
|
||||
* **`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`, ...).
|
||||
* **`src/libultra/gu/*.c`** — matrix helpers (`guMtxF`, `guOrtho`, ...).
|
||||
**Compile**.
|
||||
* **`src/libultrare/io/vitbl.c`**; defines `osViModeTable` (a data table),
|
||||
* **`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
|
||||
* **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,
|
||||
* **`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).
|
||||
|
||||
@@ -296,7 +290,7 @@ What the PC build must do with each:
|
||||
|
||||
| Subsystem | GE API used | Port strategy (from PD) |
|
||||
|-----------|-------------|--------------------------|
|
||||
| RSP (coproc 0) | `osSpTaskLoad/StartGo/Yield`, display lists | Software RSP (`fast3d`); **main work** |
|
||||
| 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 |
|
||||
@@ -312,32 +306,32 @@ What the PC build must do with each:
|
||||
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
|
||||
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
|
||||
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
|
||||
`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`,
|
||||
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
|
||||
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.
|
||||
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
|
||||
@@ -347,7 +341,7 @@ 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;
|
||||
> 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.
|
||||
|
||||
---
|
||||
@@ -355,7 +349,7 @@ sync/flush) is shared.
|
||||
## 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
|
||||
`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
|
||||
@@ -370,7 +364,7 @@ sync/flush) is shared.
|
||||
|
||||
* **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
|
||||
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
|
||||
@@ -382,84 +376,39 @@ sync/flush) is shared.
|
||||
rumble/mempak.
|
||||
This is simpler than a full Memory Pak/PFS emulation.
|
||||
|
||||
* **F10 settings overlay: mouse and scrolling** (`port/src/optionsoverlay.c`,
|
||||
`port/src/input.c`; D544, D314, D304):
|
||||
* *Cursor.* `inputSuspendForOverlay()` (`input.c`) drops relative-mouse
|
||||
grab and calls `mouseRequestCursor(1)`. With `Input.CrosshairCursor` on
|
||||
(default, D555) `inputApplyMouseRequests()` (host thread) then hides the OS
|
||||
cursor while the overlay is open and `drawCrosshairPointer()` in
|
||||
`optionsoverlay.c` draws the game's own `crosshairimage` sprite at the mouse,
|
||||
last in the overlay DL, at the game's on-screen size; the front end's own
|
||||
crosshair keeps tracking the mouse (D519) and the pointer is drawn exactly
|
||||
over it (`inputFrontEndCursorUiFrac()`), so only one shows; in a level it
|
||||
follows the OS mouse. With the option off the OS arrow shows over the box. One owner of the cursor state (no show/hide race).
|
||||
* *Hover vs selection.* `overlayHandleInputLocked()` maps the pointer to a
|
||||
visible row with `overlayRowAtY()` (follows the eased scroll position, so
|
||||
a click lands on the row it visually covers). Hover sets `s_hover` only
|
||||
while `s_mouseActive` (mouse moved or clicked); it is a focus cue, never
|
||||
the selection. The tip line (`helpVis` in the emit path) follows the
|
||||
hovered row while the mouse is in use, else the selected row. The
|
||||
wheel clears `s_mouseActive`.
|
||||
* *Clicks.* Left-click on a row selects it; headers, links and Back act on
|
||||
the whole row, otherwise only a click in the control span to the right of
|
||||
the name changes the setting (toggle/cycle, open a dropdown, start a
|
||||
binding capture, or set + drag a slider). With a dropdown open
|
||||
(`s_ddRow >= 0`) it owns the pointer: hover picks, click confirms, click
|
||||
outside or right-click cancels. Right-click elsewhere = Esc/back (closes a
|
||||
dropdown first, then one page, then the overlay). `s_rmbBlock` holds that
|
||||
off until release after a binding capture, so a right-click just bound
|
||||
does not also back out.
|
||||
* *Scrolling.* Long pages scroll as a window
|
||||
over the visible-row list (`s_visIdx`/`s_visN`, first displayed entry
|
||||
`s_scroll`, section header pinned). `overlayUpdateScroll()` moves the
|
||||
window only when the selection leaves it (D304). The wheel arrives on the
|
||||
host thread: `optionsOverlayScroll()` only queues notches in
|
||||
`s_wheelPending` (SDL atomic, D314) and `overlayApplyWheel()` applies them
|
||||
on the scheduler thread. The drawn position `s_scrollF` eases toward
|
||||
`s_scroll` each emit (snaps on page change via `s_scrollSnap`).
|
||||
* *Scrollbar (D556).* A page that overflows (same test as the "^ v" title
|
||||
marker) draws a thin track + thumb in the card's right margin
|
||||
(`overlayScrollbar()`: x = right-7..right-4, clear of the value text that
|
||||
ends at right-10); thumb size = visible/total rows, position from
|
||||
`s_scrollF`. Mouse: a press on the thumb drags it, a press on the track
|
||||
pages up/down; both call `overlayScrollTo()`, which sets `s_scroll` and
|
||||
moves `s_sel` into the window (like the wheel; otherwise
|
||||
`overlayUpdateScroll()` would snap back). A scrollbar press never reaches
|
||||
the row-click path.
|
||||
|
||||
---
|
||||
|
||||
## 8. Recommended plan
|
||||
|
||||
### Phase 0: Scaffolding (this change)
|
||||
### 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
|
||||
### 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)
|
||||
### 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);
|
||||
`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
|
||||
### 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. *(Done differently: GE compiles its real `src/sched.c` and shims its hardware leaf calls in `port/src/libultra.c`, which runs the software RSP inline — see the CMakeLists.txt `SRC_ENGINE` comment.)*
|
||||
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
|
||||
### 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
|
||||
@@ -467,7 +416,7 @@ sync/flush) is shared.
|
||||
* Port `input.c` (keyboard/mouse/gamepad).
|
||||
* Goal: playable with sound.
|
||||
|
||||
### Phase 4: Saves + polish
|
||||
### 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.
|
||||
@@ -476,21 +425,21 @@ sync/flush) is shared.
|
||||
|
||||
## 9. Open questions / risks
|
||||
|
||||
1. **`G_SETTEX` (low priority)**; appears unused (`gsSPUseTexture` is never
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 6.1 MiB |
|
Before Width: | Height: | Size: 16 MiB |