Golden gate: Linux baselines, canonical save, gate hardening (D521-D532)

Linux reference-frame set for all 21 levels and the cross-platform golden
rule (D521); golden tooling made non-destructive (D524); the gate pins a
canonical in-tree save (D529) and the PRNG seed (D532); the capture tool's
per-level re-pin bug fixed and the Linux set re-captured under the
canonical save (D530, D531); no-arg verify.sh exits non-zero. README and
docs visual/consolidation passes; the 15 MB preview gif replaced by stills.

Refs: D521 D523 D524 D525 D526 D527 D528 D529 D530 D531 D532
This commit is contained in:
James Dansereau committed 2026-10-06 18:35:43 -04:00
1 parent aa5a1b81fe
commit 452fa1025a
203 files changed
+2035 -433984

No files matched your search

+124 -157
View File
@@ -1,10 +1,14 @@
# GoldenEye 007 PC Port — v<version> # GoldenEye 007 PC Port — v<version>
<p align="center"> <p align="center">
<img src="https://github.com/jkdansereau/goldeneye-pc-port/raw/v<version>/docs/media/goldeneye-gh-preview.gif" width="480" <img src="https://github.com/jkdansereau/goldeneye-pc-port/raw/v<version>/docs/img/shots/shot-28.jpg" width="340" alt="Streets, rendered by the port">
alt="~24 s gameplay montage from live play sessions, opening on the Runway tank (no audio track)"> <img src="https://github.com/jkdansereau/goldeneye-pc-port/raw/v<version>/docs/img/shots/shot-01.jpg" width="340" alt="Dam, rendered by the port">
</p> </p>
<!-- MAINTAINER: header stills are from the public capture set (docs/img/shots/).
The 15 MB gameplay-montage gif was dropped from the repo on the v0.5.0
docs media pass (C3); the project index now uses a still carousel. -->
Platforms: **Windows x86-64** and **Linux x86-64 (including Steam Deck)**. Platforms: **Windows x86-64** and **Linux x86-64 (including Steam Deck)**.
Region: **NTSC-U (US) only**. See [Known issues](#known-issues). Region: **NTSC-U (US) only**. See [Known issues](#known-issues).
@@ -12,186 +16,156 @@ Region: **NTSC-U (US) only**. See [Known issues](#known-issues).
build was played on (e.g. "Played on Windows and Steam Deck: campaign build was played on (e.g. "Played on Windows and Steam Deck: campaign
spot-checks, 2-4P split-screen, a controller-only session"). --> spot-checks, 2-4P split-screen, a controller-only session"). -->
The headline is **2–4 player split-screen multiplayer**, the largest feature The headline is **the settings interface, rebuilt**. Both options screens are
the N64 original had that this port was still missing. Alongside it come merged into one, laid out like the Perfect Dark PC port's menu, with plain-English
controller presets and rebinding, audio output options, a steady 60 fps on wording, real-unit sliders and a one-line description under whichever option you
low-end hardware, and a round of fidelity and stability fixes checked against select. Alongside it: the game's own N64 control styles as a per-seat preset,
the N64 game. emulator save files that load directly, a scalable HUD overlay, and a round of
fidelity fixes checked frame-by-frame against the N64 game.
--- ---
## New features ## New features
### 2–4 player split-screen multiplayer (#99) ### One options menu, PD-style layout (D504, D519)
- Local multiplayer on every MP map, with the N64's own setup flow, - The file-select **PC Options** entry now opens the same menu the F10 overlay
pause/watch menus and per-player HUD. shows — there is only one options UI, one place to learn (D519).
- **Who controls which player:** with one controller, keyboard/mouse is - Options are grouped into six sections in the Perfect Dark port's order
player 1 and the controller is player 2; with two or more controllers, the (Video, Sound, Controls, Game, …), with **checkboxes, sliders and dropdowns**
controllers take players 1–4. `Input.MPMode` in `ge007.ini` picks Auto instead of the old numbered rows (D504).
(default), PadsOnly or KbmP1. - **Display mode** is a dropdown; **Backspace** goes back, matching the rest of
- A controller unplugged and replugged mid-match keeps its player (thanks to the game's menus (D504 follow-up).
dolent, #122). Each controller's LED shows its player number.
### Controller presets and rebinding (D469, D498) ### Menu wording, units and help text (D505–D507)
F10 → Input → **Controller** (also in the front-end PC Options): - 27 maintainer-approved wording edits across both menus: option names say what
they do, not what the internal variable is called (D505).
- Sliders carry **real units** — percent, frames, seconds, pixels — and their
values are standardised across the two menus (D506).
- Selecting an option shows a **one-line description** of it in the same menu
(D507).
- **Layout preset:** the Xbox release's four styles: "1.1 Jinx" (the ### Control styles: the game's own N64 layouts (D513, D516, D518)
default, identical to before), "1.2 Christmas", "1.3 Frost" and
"1.4 Elektra" (they swap which stick moves, looks, strafes and turns), or
**Custom**.
- **Custom:** rebind every action per controller, up to two buttons each.
Tap B or Back to cancel a capture, hold Back to clear. Menus always keep
A/X accept and B/Y cancel, so you can't lock yourself out.
- Builds on ideas from italoarruda's #109 (credited as co-author).
- In the tank, the pad follows your layout too: the weapon button switches
the tank's weapon and the use button gets out (D495).
### PlayStation and Nintendo button names (D471) - **Control style** offers **Ext** (this port's scheme, unchanged) and
**Original**, which shows the game's own N64 styles 1.1–2.4 (D513, D516).
- The style is chosen **per seat** and saved with the Bond file, so each player
keeps their own (D518, D516).
- The Xbox-release presets (1.1 Jinx, 1.2 Christmas, 1.3 Frost, 1.4 Elektra) and
Custom rebinding from v0.4.x are unchanged. Preset names refer to the Xbox
release's control styles; this project is not affiliated with Microsoft or Rare.
- Here 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.
PlayStation and Switch controllers show their own button names in the ### Emulator save files load directly (D514)
menus (Cross/Circle, ZL/ZR, …). Xbox and Steam Deck text is unchanged.
### Audio output options (D470) - An emulator-format `ge007.eep` sitting next to the executable is read on first
launch, so a save from Project64, mupen or 1964 continues in the port without
conversion. (Live playtest owed; the converter `eep_convert.py` still does
explicit two-way conversion.)
- **Master volume**, applied after the music and FX volumes. ### HUD and overlay scaling (D510, D512)
- **Output device:** pick your audio device. A missing or unplugged device
falls back to the system default.
### Presets, filtering and the original look (D440, D447, D499) - The F10 overlay and the FPS counter are now the **same on-screen size** on the
front end and inside a level (D510).
- **Game.HudScale** scales the F10 overlay and FPS counter along with the in-game
HUD (D512).
- **Original N64 preset** (F10 → Graphics) sets every value the port changes ### New settings (D511)
back to the N64's; **Port defaults preset** restores the port's modern
defaults.
- **Aspect → Original** shows the exact 4:3 frame with bars (16:9 while the
game's own Ratio setting is 16:9).
- **Texture filter → Trilinear**, next to Bilinear: smooths distant
textures that the game has no mipmaps for.
### Options polish (D443, D472, D497, D502) - **Fullscreen mode**, **Center window**, **crosshair opacity** and
**crosshair colour by health** — the four settings the Perfect Dark port has
that this port was missing.
- **Restart game** row: applies restart-only settings such as anti-aliasing ### Overlay controls are antialiased (D520)
with one click.
- Clearer values: mouse sensitivity as ×, deadzone, vibration and volumes in
whole %, field of view in horizontal degrees, anti-aliasing up to 16×.
- Front-end PC Options pages like the game's own dossiers: PREVIOUS and
NEXT folder tabs, and B goes back a page. With a controller, the first
item is highlighted when you open a section and you return to where you
were when you leave it.
- The F10 overlay is pillarboxed in widescreen (instead of stretched),
experimental options are shown in red, an unknown `ge007.ini` key shows a
warning (#90), and with several controllers the one that opened the
overlay drives it.
### `All unlocked` no longer touches your save (D442) - Slider wedges, slider markers and dropdown arrows no longer step in whole
canvas pixels (which grew visibly at large window sizes); they are drawn
smooth at every size.
It's now a memory-only override: switch it off and your real progress is ### Watch menu keeps its own settings (D349, corrected)
back. Fake unlocks written by older builds are not repaired, so back up
`data/ge007.eep` if you used it before. - Closing the in-level **Watch** menu now saves the watch's own screen size and
ratio, and they no longer fight the front-end **Aspect ratio** setting. The
earlier note in the findings log was corrected.
--- ---
## Performance ## Fidelity and stability fixes
- **A steady 60 fps on low-end hardware (D481, #92).** Rendering ran on the - **Fog:** objects far in fog are hidden as on the N64 (fog snap); in widescreen
same thread that delivers the game's 60 Hz timing signal, so slower GPUs the wider view can still show a faint distant building edge (e.g. Surface's
regularly missed their frame slot and dropped to 30–45 fps. Rendering now dish from the start area) — 4:3 matches the N64 (D503).
runs on its own thread, like the N64's graphics chip. A 2011 laptop (Intel - **Aspect ratio:** forced ratios now letterbox/pillarbox without re-cropping the
HD 3000) went from ~45 to a steady 60 fps at every setting. play area (D508); the rare gun/hand model glitch after changing ratio
- **Lighter first-launch defaults on Atom/Celeron-class GPUs (D482, #92).** mid-level is largely fixed, a rare remainder is logged (D509).
On Intel HD 400–605 / UHD 600–605 class GPUs and software renderers, the - **FPS counter:** holding left/right on a Bond settings row no longer drops the
first launch lowers draw distance, LOD distance and anti-aliasing. Settings FPS counter (D517).
you've changed yourself are never touched. - **Build:** a fresh build directory no longer produces an untagged executable
- **No more in-play shader stutter (D480):** shaders the game has used are name (D515).
compiled at startup on the next launch. - **Tooling:** the asset converter's argument quoting and sidecar size check are
- **Render-thread CPU roughly halved in typical scenes (D473),** plus fewer tightened; the CI cache actions no longer carry stale ROM paths. The golden-frame
redundant texture lookups and palette re-imports (D474, D476). tooling is now 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
## Fidelity fixes (checked against the N64 game) of a hardcoded copy that had gone stale (D524). The gate's recipe was re-based
too: per-level windows that sit in settled gameplay rather than inside the intro
- **Draw distance no longer changes AI behaviour (D466, #125).** The flyby, and the save file pinned present (D522, D523).
extended draw distance (default 250%) let guards "see" you from beyond the - **Harness:** the debug capture/launch env vars are documented — `GE_PCDUMP` must be
N64's fog, so Xenia's fight on Jungle could start early. AI awareness now `first-last:step` (a colon where the dash belongs silently dumps every frame, D527),
uses the level's original distance; you can still see, shoot and use and a `GE_STARTMENU` boot skips the EEPROM import, so the imported-save test must run
everything that's drawn. from a normal boot (D528). A per-triangle draw-state census probe
- **Ultrawide and high field of view (D468):** the AI keeps the N64's widest (`GE_D526`/`GE_D526BOX`/`GE_D526MAX`) is available for transparency/texture triage
(16:9) view. Turn on *Guards see the wider view* if you prefer the old (D526; the P13 Dam-ending grate show-through it targeted was confirmed faithful N64
behaviour. behaviour on 1964/GEPD, so no change shipped).
- **Sniper rifle (#136):** the scope zooms all the way in again (it was
capped at about a third of the original magnification, D484); zoom speed
matches the N64 (it was about twice as fast at 60 fps, D485); the crouch
key now crouches and stands with it out, as it does in GEPD (D494).
- **Turrets and explosions at 60 fps (D486, #126):** turret fire rate,
tracers, explosion density and screen-shake length now match the N64.
Damage is unchanged. (Explosions that sometimes don't appear in heavy
firefights are the N64's own behaviour: it has six explosion slots.)
- **Weapons (#114–#118):** automatic-fire cadence follows the N64's rules at
any frame rate; grenade launcher recoil is back; the gun sways with mouse
look again; the tank turns with the mouse; the run-over death sound no
longer loops.
- **Light fixtures go dark when shot, with the right impact effects
(#119).**
- **Dam/Caverns water (D465, #127):** the water animation no longer slides
side to side, and it scrolls at the N64's rate.
- **Kneeling guards** use their crouched-rifle firing animation (D445,
thanks dolent, #120).
- **Dam:** the first-tower guard walks his whole patrol instead of a small
circle (D490).
- **The Rareware logo** in the intro renders correctly (D75).
- Props near some doorways no longer count as being in extra rooms, and
ejected casings spin as authored (D491).
---
## Stability & fixes
- **Knife-slash crash fixed (D454).**
- **64-bit memory-layout fixes** (D441, D453, D456, D457, D461, D462): two
item/vertex tables were sized with N64 strides, and several pointer fields
were truncated.
- A split-screen zero-size texture crash is guarded (D463); stale
anisotropic filtering on recycled textures is fixed (D446, thanks dolent,
#121).
- *Skip intro* no longer skips the failure dossier after a failed mission
(D408).
- Adjusting the music volume with a controller in F10 no longer drops the
frame rate (D489).
- Steam Deck: the Deck preset now also applies after a first launch in
Desktop Mode (D283).
- Aztec: the area by the shuttle with the four turrets no longer breaks
into stray triangles. The per-frame vertex buffer, sized for what the
N64 could see, could overflow with the port's wider view (D500).
- In 3- and 4-player games, weapon pickup messages no longer show stray
characters (D496).
- A controller no longer keeps vibrating after you quit mid-firefight
(D493).
- *Aspect → Original* no longer shows a one-pixel strip of flickering
pixels at the left and right edges (D493).
- Crash logs now include resolved stack frames, which makes reports far
easier to act on.
--- ---
## Known issues ## Known issues
- **NTSC-U (US) ROMs only.** PAL and JP ROMs convert, build and boot from | Issue | Impact | Workaround |
source on this release's code, but the release packages don't support them |---|---|---|
yet (#85). | 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 |
- **No macOS or ARM builds yet.** | 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 |
- The first frame of a level takes a little longer while its textures | No macOS or ARM builds | Platform | — |
upload: a brief dip in the FPS counter. | Saves from builds before v0.4.1 can hold fake unlocks from `All unlocked` | Save data | Not repaired automatically. Since v0.4.1 the option never writes the save; keep a backup of `data/ge007.eep` from before you used it |
- **`Skip intro` and `All unlocked` are still experimental.** | `Skip intro` and `All unlocked` are experimental | Opt-in options | Leave them off for a normal playthrough |
- Far objects almost hidden in fog can show as a faint outline with | The first frame of a level takes a little longer while its textures upload | Brief FPS-counter dip | None needed |
straight edges (e.g. the satellite dish seen from Surface's start area). | 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 N64's 16-bit colour hid it (D503).
- The full list, with workarounds, is the The full list, with workarounds, is the
[known-issues table](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/ROADMAP.md#known-issues). [known-issues table](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/ROADMAP.md#known-issues).
**Not yet playtested live** (merged, build- and review-verified, owed a play
session before this release is announced): the four new settings (D511),
`Game.HudScale` (D512), Control style Original on a pad (D513/D516 per seat),
emulator-save import (D514), and the D519 profile-chooser row / no-click-through
follow-ups.
<!-- MAINTAINER: if the play session did not cover all of the above, keep the
sentence "these were merged and review-verified but not playtested live"
in the body; never imply a full playtest. -->
**Golden-gate state for this release:** both platforms carry the full 21-level
reference-frame set (63 frames each) at the same stems, and each platform's gate
is green against its own goldens. The cross-platform spread (0.348-3.983% of
pixels over tol 2 on the 20 non-Cuba levels, structural tier 21/21 clean) is
informational only — it is not a cross-platform pixel-parity claim, and the
linux *pixel* gate did not run on the capture box (its GL probe fails closed
there; the linux crash gate did). The Dam ending cutscene has not been checked
by eye in a live session.
--- ---
## Thanks
Outside contributions merged for this release: dolent (PRs #120–#122), 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.
## Downloads ## Downloads
| File | Platform | | File | Platform |
@@ -232,13 +206,6 @@ sha256sum -c goldeneye-pc-port-<version>-win64.zip.sha256
sha256sum -c goldeneye-pc-port-<version>-linux-x86_64.tar.gz.sha256 sha256sum -c goldeneye-pc-port-<version>-linux-x86_64.tar.gz.sha256
``` ```
## Thanks
dolent (#120, #121, #122), italoarruda (#107, #109), MST246 (the #125
investigation and handoff used for the fix), JosephAHK (#133), MistaEcho
(#119 via #87), and everyone who filed #92, #114–#119, #125, #126, #127,
#129, #130 and #136.
## Source & docs ## Source & docs
<https://github.com/jkdansereau/goldeneye-pc-port>, built on the <https://github.com/jkdansereau/goldeneye-pc-port>, built on the
+5
View File
@@ -196,6 +196,11 @@ build-pc-cmd.bat
# v0.4.0 modern-options wave: per-landing sweep capture dirs (capture.sh output) # v0.4.0 modern-options wave: per-landing sweep capture dirs (capture.sh output)
tools_pc/sweep-captures/capture-*/ 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); # Stray full CPython install that landed in repo root (2026-09-28);
# referenced by no project tooling. Never commit. # referenced by no project tooling. Never commit.
/Python/ /Python/
+38
View File
@@ -17,6 +17,44 @@
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`. 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. 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`, 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 ## Critical files
| File | Role | | File | Role |
+8 -2
View File
@@ -43,6 +43,12 @@ 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. 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` 3. If you may dispatch a subagent this session, read `docs/dev-process.md`
NOW — not at dispatch time. 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 ## Dispatch preflight — EVERY Agent/Task spawn, no exceptions
@@ -76,7 +82,7 @@ Full template: `docs/dev-process.md` -> "Investigation-brief template".
## Local Qwen dispatch (cost-free, data-local worker) ## Local Qwen dispatch (cost-free, data-local worker)
A local Qwen3.8-27B is wired in via the `delegate-local` MCP server (see A local Qwen3.8-27B is wired in via the `delegate-local` MCP server (see
`C:\Users\james\Source\Repos\20260902-qwen38claudepair`). Use it as a `<repos>\20260902-qwen38claudepair`). Use it as a
*subagent whose compute runs on-machine* for scoped, checkable work — the lead *subagent whose compute runs on-machine* for scoped, checkable work — the lead
still owns judgment, planning, and the verification gate. still owns judgment, planning, and the verification gate.
@@ -93,7 +99,7 @@ still owns judgment, planning, and the verification gate.
proxy (`litellm/run.ps1`) + Unsloth Studio both up. proxy (`litellm/run.ps1`) + Unsloth Studio both up.
- **Corrected 2026-09-04:** the "worker's shell is `cmd.exe`, no grep/cat" - **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 claim that used to live here was WRONG — verified directly against the
harness's own `_run_bash` (`C:\Users\james\tools\claude-code-delegate-local`): 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 `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 (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 5 local agent `.md` files, caused a real dispatch (a multi-file
+13
View File
@@ -18,6 +18,19 @@ Third-party code vendored into the port layer keeps its own license:
libultraship / sm64-port lineage; see port/fast3d/LICENSE.txt. libultraship / sm64-port lineage; see port/fast3d/LICENSE.txt.
* port/fast3d/glad/ - generated OpenGL loader (glad), public domain / MIT. * 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: It does NOT cover:
* src/, include/, assets/, Makefile, tools/, rsp/, ld/, and the other * src/, include/, assets/, Makefile, tools/, rsp/, ld/, and the other
+73 -33
View File
@@ -9,9 +9,10 @@
and Steam Deck.** Native widescreen, a steady 60 fps, 2–4 player and Steam Deck.** Native widescreen, a steady 60 fps, 2–4 player
split-screen, mouse and keyboard or a modern controller, rebindable controls split-screen, mouse and keyboard or a modern controller, rebindable controls
and an in-game options menu. No and an in-game options menu. No
emulator: the original game's own code, rebuilt from the emulator, and not a binary recompilation: the game's own
[GoldenEye 007 decompilation](https://github.com/n64decomp/007), running from reconstructed C, rebuilt from the
your own ROM. [GoldenEye 007 decompilation](https://github.com/n64decomp/007), compiled
for your PC and running from your own ROM.
**[Download the latest release](https://github.com/jkdansereau/goldeneye-pc-port/releases)**, **[Download the latest release](https://github.com/jkdansereau/goldeneye-pc-port/releases)**,
then follow the [Quick start](#quick-start). then follow the [Quick start](#quick-start).
@@ -23,22 +24,23 @@ then follow the [Quick start](#quick-start).
> downloads contain no Nintendo code or assets, and no ROM. See > downloads contain no Nintendo code or assets, and no ROM. See
> [What's a ROM?](#whats-a-rom) and [Legal](#legal). > [What's a ROM?](#whats-a-rom) and [Legal](#legal).
**v0.4.1** is the current release. It is a **pre-1.0 release, not a **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 finished product**: the campaign and split-screen multiplayer play end to
end, but expect missing pieces (PAL/JP ROMs, macOS) and the occasional 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 breaking change between versions. v1.0 is the target for a feature-complete
build. build.
**AI disclosure:** this port was developed agentically, with Claude Code and a **AI disclosure:** this port was developed using AI coding agents (Claude
local open-weight model, directed by one person in their spare time. The 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 project is as much a study of that process as it is a port. See
[How it was made](#how-it-was-made). [How it was made](#how-it-was-made).
<p align="center"> <p align="center">
<img src="docs/media/goldeneye-gh-preview.gif" width="64%" <img src="docs/img/shots/shot-28.jpg" width="23%" alt="Streets, rendered by the port">
alt="~24 s gameplay montage from live play sessions"> <img src="docs/img/shots/shot-01.jpg" width="23%" alt="Dam, rendered by the port">
<br><em>All in-engine, running in the port: a ~24&nbsp;s gameplay montage <img src="docs/img/shots/shot-03.jpg" width="23%" alt="Runway, rendered by the port">
from live v0.4.0 play sessions, opening on the Runway tank · <img src="docs/img/shots/shot-24.jpg" width="23%" alt="Aztec, rendered by the port">
<br><em>All in-engine, running in the port ·
<a href="docs/index.md">12 level stills in the project index</a></em> <a href="docs/index.md">12 level stills in the project index</a></em>
</p> </p>
@@ -46,6 +48,14 @@ project is as much a study of that process as it is a port. See
## News ## News
- **2026-10-05** — **v0.5.0**: one options menu laid out like the Perfect Dark
port's, with plain-English wording, real-unit sliders and a one-line description
per option; the game's own N64 control styles as a per-seat preset; emulator save
files that load directly; a scalable HUD overlay; fullscreen mode, window centring
and crosshair settings; antialiased overlay controls; and a fidelity round checked
frame-by-frame against the N64 game (fog, aspect-ratio letterboxing, the Watch
menu's own settings). The reference-frame gate was re-based on both platforms
(21 levels, 63 frames each). [Release notes](https://github.com/jkdansereau/goldeneye-pc-port/releases/tag/v0.5.0).
- **v0.4.1**: 2–4 player split-screen multiplayer, controller presets and - **v0.4.1**: 2–4 player split-screen multiplayer, controller presets and
rebinding, PlayStation/Nintendo button names, master volume and audio rebinding, PlayStation/Nintendo button names, master volume and audio
device selection, a steady 60 fps on low-end GPUs, and fidelity fixes device selection, a steady 60 fps on low-end GPUs, and fidelity fixes
@@ -159,7 +169,8 @@ 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 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 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 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. 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).
**Split-screen:** pick Multiplayer from the main menu. With one controller, **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 keyboard/mouse is player 1 and the controller is player 2; with two or more
@@ -214,7 +225,7 @@ and the end-of-campaign credits load, render and run crash-free; the full
campaign was playtested end to end at Agent difficulty on Windows, Linux and campaign was playtested end to end at Agent difficulty on Windows, Linux and
real Steam Deck hardware for v0.4.0. Split-screen multiplayer was played live real Steam Deck hardware for v0.4.0. Split-screen multiplayer was played live
in 2P on every MP map and in 4P on Temple. in 2P on every MP map and in 4P on Temple.
<!-- MAINTAINER: after the v0.4.1 play session, add what it covered here. --> <!-- MAINTAINER: after the v0.5.0 play session, add what it covered here. -->
**Known issues:** see the **[known-issues table](docs/ROADMAP.md#known-issues)** **Known issues:** see the **[known-issues table](docs/ROADMAP.md#known-issues)**
(what you'll notice, impact, workarounds). The short version: NTSC-U ROMs (what you'll notice, impact, workarounds). The short version: NTSC-U ROMs
@@ -259,6 +270,12 @@ never writes to `%APPDATA%`, the registry or any system location.
| `data/ge007.shaders` | A list of the graphics shaders the game used, compiled at startup to avoid stutter. Safe to delete. | | `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. | | `ge007.crash.log` | Written in the folder you launched from if the game crashes. |
**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.
**`ge007.ini`** uses `[Section]` headers and `Key = value` lines. A few **`ge007.ini`** uses `[Section]` headers and `Key = value` lines. A few
common keys and their defaults: common keys and their defaults:
@@ -387,7 +404,7 @@ these matter to you, open an issue — it helps prioritize.
The port was built by two coding agents, directed by one person part-time. The port was built by two coding agents, directed by one person part-time.
A local open-weight model (`unsloth/Qwen3.8-27B-GGUF` on one RTX 5090, via the A local open-weight model (`unsloth/Qwen3.8-27B-GGUF` on one RTX 5090, via the
[pi](https://pi.dev/) agent) did the groundwork: build, boot chain, [pi](https://github.com/earendil-works/pi) agent) did the groundwork: build, boot chain,
software-RSP integration, asset pipeline, first frames. **Claude Code** software-RSP integration, asset pipeline, first frames. **Claude Code**
(Sonnet 5, Opus 5 for the hardest bugs) joined for the collaborative phase: (Sonnet 5, Opus 5 for the hardest bugs) joined for the collaborative phase:
the 21-level sweep, the ABI finding catalog, SDL input, front end. The two the 21-level sweep, the ABI finding catalog, SDL input, front end. The two
@@ -413,7 +430,7 @@ code with this one.
| | This project | The other well-known "GoldenEye on PC" recompilation projects (and their Steam Deck builds) | | | This project | The other well-known "GoldenEye on PC" recompilation projects (and their Steam Deck builds) |
|---|---|---| |---|---|---|
| **What it ports** | The original **Nintendo 64** game (1997) | The **Xbox 360 XBLA** HD remaster (built ~2007, never released) | | **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; no source-level understanding | | **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 |
| **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 | | **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) | | **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 | | **Status** | Pre-1.0 releases; full campaign and split-screen playable at 60 fps (see [Status](#status)) | Playable full game |
@@ -464,21 +481,25 @@ repo root, put your ROM in it as in [What's a ROM?](#whats-a-rom), and run
## How it works ## How it works
The game code in `src/` is the decompilation's, and its control flow is The game code in `src/` is the decompilation's, and its control flow is the
ground truth. Everything that would touch N64 hardware is redirected into reference we preserve. Everything that would touch N64 hardware is redirected into
`port/`: a **software RSP** (`port/fast3d/`, adapted from the Perfect Dark `port/`: a **software RSP** (`port/fast3d/`, adapted from the Perfect Dark
port) that interprets the GBI display list the game builds each frame and 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`, 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, 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; 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. and SDL2/OpenGL/filesystem backends for video, audio, input and storage.
Edits inside `src/` are rare, marked `#ifdef PORT`, and each one is Every port change inside `src/` is gated behind `#ifdef PORT` and
documented: mostly mechanical 64-bit fixes (a struct or pointer field that documented. They are few in kind, all narrow in scope: mechanical 32→64-bit
the 32→64-bit move misaligned or truncated), plus a few maintainer-approved pointer/struct fixes (the pointer-width transition touches many structs, so
timing fixes where per-frame N64 code ran too fast at the PC's 60 fps (for there are a lot of them), a few maintainer-approved timing fixes where
example sniper zoom and turret fire), and the opt-in *All unlocked* hook. Where it diverges from the Perfect Dark port: GoldenEye's N64 per-frame N64 code ran too fast at the PC's 60 fps (for example sniper zoom
serialized asset formats are converted offline by Python "sidecar" converters and turret fire), the opt-in *All unlocked* hook, and env-gated diagnostic
in `tools_pc/` rather than fixed up at load time. Full detail: 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 [`docs/internals.md`](docs/internals.md) and
[`docs/porting-notes.md`](docs/porting-notes.md). [`docs/porting-notes.md`](docs/porting-notes.md).
@@ -576,20 +597,39 @@ This port is a thin layer on a large amount of other people's work.
**Tools and models used to develop the port** **Tools and models used to develop the port**
- Qwen 3.8 (Alibaba Qwen team), run locally from the Development ran for most of its life on a single local model — **Unsloth's
[`unsloth/Qwen3.8-27B-GGUF`](https://huggingface.co/unsloth/Qwen3.8-27B-GGUF) Qwen 3.8 27B** — which did nearly all of the local (groundwork) work on its
repo — `UD-Q4_K_XL` as the primary quant, with other quants used along the own. In the final week that grew into a small menu, and the **current standard
way; [Unsloth](https://unsloth.ai) (the GGUF quantisation and Unsloth is NInfer + Strata**; tools are chosen per task, with local models carrying
Desktop) as the local model server. the high-volume work and hosted models the exception. The full record of the
- [pi](https://pi.dev/): the local coding-agent harness. models used, and the harnesses that drove them:
- [Claude / Claude Code](https://claude.com/claude-code) (Anthropic).
| 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).
## Legal ## Legal
This is a non-commercial fan preservation/research project, in the same This is a non-commercial fan preservation/research project, in the same
category as the many other N64 decompilation and native-port repositories on category as the many other N64 decompilation and native-port repositories on
GitHub. It is **not affiliated with, endorsed by, or sponsored by** Nintendo, GitHub. It is **not affiliated with, endorsed by, or sponsored by** Nintendo,
Rare, Microsoft, MGM, Danjaq, EON Productions, or any rights holder in Rare, Microsoft, Valve, MGM, Danjaq, EON Productions, or any rights holder in
GoldenEye or James Bond. "GoldenEye 007", "007", "James Bond" and related 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 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. game this port runs. No official logos, box art, or marketing assets are used.
@@ -635,4 +675,4 @@ is covered by [`NOTICE`](NOTICE), not by that license.
--- ---
*Last updated 2026-10-03 — v0.4.1 · <a href="https://github.com/jkdansereau/goldeneye-pc-port">GitHub</a> · <a href="https://github.com/jkdansereau/goldeneye-pc-port/releases">Releases</a>* *Last updated 2026-10-05 — 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>*
+15 -6
View File
@@ -26,6 +26,10 @@ local and remote branches, and known community forks.
row and delete it when the item lands. row and delete it when the item lands.
- Release scoping (which items go into which version) happens in the - Release scoping (which items go into which version) happens in the
release notes, not here. 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 - **Known issues** (the player-facing subset) live in the table below. The
README links to it and does not duplicate it. README links to it and does not duplicate it.
@@ -67,9 +71,11 @@ platform carries the same gate: a full 21-level sweep plus a campaign pass.
## Order of work (decided 2026-09-30) ## Order of work (decided 2026-09-30)
Work continues on `release/v0.4.1`, **to ship as v0.5.0 (named 2026-10-03, Work continues on `release/v0.4.1`, **shipped as v0.5.0 (named 2026-10-03,
maintainer)** — cut after the playtest session and the easy folds land cut staged 2026-10-05, maintainer)** — the docs, golden set (both platforms,
(push checklist: `dev/notes/PUSH-TASKS-v041.md`). There are two stages: 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** **Stage A: now → the pre-1.0 build**
1. Clear the §3 verification debt. 1. Clear the §3 verification debt.
@@ -134,7 +140,7 @@ below).
| Item | Status | Refs | Notes | | 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. | | 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. |
| Dam ending cutscene: Bond placement; railing transparency | open | D173, D292; maintainer 2026-10-02 | **Maintainer to re-check the end of Dam** (the last place a Bond offset was seen; the D173 level-start probe found no defect). Also seen in the Dam cutscene: an edge railing texture (a 2D element, maintainer believes) with odd transparency; possibly already fixed by the alpha pass. Capture a screenshot of each if still present. | | Dam ending cutscene: Bond placement; railing transparency | partial (railing/grate transparency confirmed faithful 2026-10-07; Bond-placement re-check pending) | D173, D292, D526; maintainer 2026-10-02 | **Railing/grate transparency CLOSED as faithful (D526):** the 2D edge-railing (grate) texture in the Dam ending shows "odd" transparency between its two anchors, confirmed identical on 1964/GEPD (port == N64) → the original 2D texture, not a port bug (D306/D291 class). **Remaining: re-check Bond placement at the end of Dam** (the D173 level-start probe found no defect; the last place a Bond offset was seen). |
| 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 (maintainer live pass):** the attract segment 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). | | 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 (maintainer live pass):** the attract segment 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). |
| Dam: first-tower guard walks a small circle (glass collision polygon) | done | D490 | **Fixed 2026-10-02, merged into `release/v0.4.1` 2026-10-03:** a stack overflow in `sub_GAME_7F03ECC0` (flat-bbox objects, i.e. glass) corrupted the collision polygon; `rem[8]` under `AVOID_UB`. Headless probe: the guard now walks his whole patrol loop. Maintainer by-eye check in Dam passed 2026-10-02. The porting-notes D24 audit ran 2026-10-03 and found D491 (below); no other overflow of this kind. | | Dam: first-tower guard walks a small circle (glass collision polygon) | done | D490 | **Fixed 2026-10-02, merged into `release/v0.4.1` 2026-10-03:** a stack overflow in `sub_GAME_7F03ECC0` (flat-bbox objects, i.e. glass) corrupted the collision polygon; `rem[8]` under `AVOID_UB`. Headless probe: the guard now walks his whole patrol loop. Maintainer by-eye check in Dam passed 2026-10-02. The porting-notes D24 audit ran 2026-10-03 and found D491 (below); no other overflow of this kind. |
| Struct reads across adjacent globals: portal bbox seeds put props in extra rooms; casing spin seeded from trigger state | done | D491 | **Fixed 2026-10-03, merged into `release/v0.4.1`:** `bg.c` / `gunfire.c` read three separate scalar globals as one coord3d; mingw reverses their order. `#ifdef PORT` reads the named globals. Builds clean, 900-frame `-level_09` smoke clean. Nothing by eye is expected. Delete this row once released. | | Struct reads across adjacent globals: portal bbox seeds put props in extra rooms; casing spin seeded from trigger state | done | D491 | **Fixed 2026-10-03, merged into `release/v0.4.1`:** `bg.c` / `gunfire.c` read three separate scalar globals as one coord3d; mingw reverses their order. `#ifdef PORT` reads the named globals. Builds clean, 900-frame `-level_09` smoke clean. Nothing by eye is expected. Delete this row once released. |
@@ -175,7 +181,9 @@ A single campaign pass with these on the checklist would clear most of them.
| 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; with the render worker a Lenovo X220 (HD 3000) went 44-46 → 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 there (`gpu=` vs `run=`), then a lighter renderer-keyed default (GPU) or fast3d per-batch cuts (CPU; local `PERF-HOTSPOTS.md`). | | 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; with the render worker a Lenovo X220 (HD 3000) went 44-46 → 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 there (`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 (2026-10-01) | **Diagnosed (D475, D477, D480):** no sustained load. The ~33 ms frames are the game's frame limiter skipping a retrace after a slightly late gfx task (`boss.c`; D13-class timing). CPU bursts were CI-palette re-imports (ruled out, D477; CI4 D476) and in-play shader compiles (fixed by D480). **Parked 2026-10-02:** a maintainer play session with the timeline probe (`probe/hitch-timeline`, never merge) felt no hitching. Reopen only if a visible hitch is reported (lever: pre-warm at level load). | | Statue/Cradle hitching (maintainer, 2026-09-30) | parked (not player-visible, 2026-10-02) | `GE_PERFSTAT` spike counters (2026-10-01) | **Diagnosed (D475, D477, D480):** no sustained load. The ~33 ms frames are the game's frame limiter skipping a retrace after a slightly late gfx task (`boss.c`; D13-class timing). CPU bursts were CI-palette re-imports (ruled out, D477; CI4 D476) and in-play shader compiles (fixed by D480). **Parked 2026-10-02:** a maintainer play session with the timeline probe (`probe/hitch-timeline`, never merge) 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. | | 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 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. || Linux golden baselines (P7) + two residual render bands | closed (2026-10-05 re-round) | D521/D525 | 21-level `<level>/linux/` set captured 2026-10-04 on a real-GL box (Mesa / Intel HD 3000), then **re-rounded 2026-10-05 (P7b, D525)**: all 21 levels, 63 frames, at the D522 windows with the save pinned present (the 2026-10-04 set had `data/ge007.eep` cleared, and D523 measures that save-file presence moves ~every level). Cross-platform framediff vs `win/` at the same stems: structural **21/21 clean** (worst cell mean 4.43, phash <= 6 except cradle at 40), exact **0.348-3.983%** over tol 2 on the 20 non-Cuba levels. **Both residual bands closed:** Archives 19.956-24.554% (cell means to 50.8) is now 3.590-3.859% (worst cell 2.31, phash 1) and Streets 1200's 16.0-148.4 cell-mean band is now 3.616-3.983% (worst cell 4.43, phash 0) — save state, not a renderer. Dam's 900 flyby offset is out of the set (Dam sits at 1500-1700, cross-platform 2.964-3.443%). Caveat: the box's own sweep is crash-detect only (`glxinfo -B` fails there, rc 255, so `verify.sh`'s probe fails closed); the cross-platform numbers are computed on Windows. |
## 5. Modern-remaster layer (port features beyond the N64) ## 5. Modern-remaster layer (port features beyond the N64)
@@ -274,11 +282,12 @@ and a stage loader.
| Bond seems to move faster on the port than on 1964 (live A/B) | closed (2026-10-04, maintainer) | — | Seen 2026-10-03 in a live one-pad A/B. Closed as an input-mapping difference, not game speed: the GEPD 1964 bundle is set up for mouse and keyboard and the port then forced 1.2 SOLITARE (D513/D516 now offer the game's own styles). A valid future A/B is the port on a pad against a standard emulator with plain pad input. | | Bond seems to move faster on the port than on 1964 (live A/B) | closed (2026-10-04, maintainer) | — | Seen 2026-10-03 in a live one-pad A/B. Closed as an input-mapping difference, not game speed: the GEPD 1964 bundle is set up for mouse and keyboard and the port then forced 1.2 SOLITARE (D513/D516 now offer the game's own styles). A valid future A/B is the port on a pad against a standard emulator with plain pad input. |
| 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. **Maintainer concern (2026-10-03):** aiming, PRNG guards and timed events may block open-loop scripts, and runs aren't deterministic (D117). Levers: Invincibility + Invisibility cheats, closed-loop waypoint steering, pinned seed (`GE_RSEED`), coverage routes. Spike: one Dam route x5, measure end-position spread, then go/no-go. | | 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. **Maintainer concern (2026-10-03):** aiming, PRNG guards and timed events may block open-loop scripts, and runs aren't deterministic (D117). Levers: Invincibility + Invisibility cheats, closed-loop waypoint steering, pinned seed (`GE_RSEED`), coverage routes. 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). | | 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 | **Windows done 2026-10-02:** all 21 levels re-based (`tools_pc/golden/<level>/win/`; pinned seed, START skips the intro, frames 900-1500; Cuba captures its ending); two-tier gate (structural + per-pixel with per-level limits). **Left:** Linux baselines (needs a real-GPU Linux box: `GE_PCDUMP` reads black on llvmpipe/WSLg), then the parity gate per platform. | | Golden baselines | partial | `tools_pc/verify.sh`, `tools_pc/golden/README.md`, D117/D522/D529/D531 | **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, the same way it pins the ini — D523's "presence" claim was refuted by the D529 A/B) 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):** the 2026-10-04 round cleared the save, the box re-rounded 2026-10-05 (D530 — but under the box-LOCAL save: `capture_p7.sh`'s per-level re-pin silently overwrote D529's canonical install, the D531 blocker), and D531 (2026-10-07) fixed the tool and re-captured all 21 levels under the canonical eep (47/63 frames moved). **Owed:** the pixel-gated linux verify — the box's WSLg GPU display is session-state-dependent (its 10-05 capture was GPU-backed; 10-07's session reads back black even with `DISPLAY=:0`, so `verify.sh`'s probe fails closed to crash-detect); run `verify.sh sweep` on the box when its GPU display is up and check the four D117 flaky levels' first stems (never-do 6: exclude/widen, don't re-capture). |
| Flicker scan (A/B/A frame alternation) | parked (2026-10-03: tooling, nothing blocked on it; spec in the local investigation report `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`. | | Flicker scan (A/B/A frame alternation) | parked (2026-10-03: tooling, nothing blocked on it; spec in the local investigation report `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. | | 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. | | 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, **in scope 2026-10-03 (maintainer)**: re-verify all scripted-camera cutscenes with the clamps compiled out (intro flyover, per-level endings incl. the Dam abseil), then delete both blocks in `model.c`. | | 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, **in scope 2026-10-03 (maintainer)**: re-verify all scripted-camera cutscenes with the clamps compiled out (intro flyover, per-level endings incl. the Dam abseil), then delete both blocks in `model.c`. |
| P12 U5 review of `tools_pc/**` (release-diff, B10) | partial | `docs/dev/notes/RELEASE-BLOCKERS-v050.md` §F, D531 | **2026-10-07 (local session):** U5 reviewed (the unit that owed the P7 merge). **Blocker found + fixed in-tree:** `capture_p7.sh`'s per-level re-pin restored the local save, so the D530 box re-round ran under the box-local save — D531 (fixed + re-captured). **Applied 2026-10-07 as D532 (tooling-only):** (1) `verify.sh`'s `pin_ini_640x480` now fails closed like `pin_eep` (the old `2>/dev/null || true` call sites ran the gate under an unpinned ini on write failure — a spurious-verdict source); (2) `GOLDEN_SEED` hard-pinned with a warning for a differing stray `GE_RSEED` (the old `:-` form let the env var re-seed the gate; capture's `eval grep` still picks up the same line, so capture and gate stay consistent); (3) `audiodebug.ps1`'s dead `-AB`/`-Old` switches removed with a one-line note. **Still open on this row:** the pixel-gated box verify in a WSLg GPU-display window (D531) — crash-detect only until then. Clean: `p7_report.py`, `eep_convert.py`, `abi_ratchet.py`, `docs_budget.py`, `abpad/`, the D258/D416/PII#7 tool diffs, probe/capture deletions (`scan_op12/13.py`, the 433k-line `sweep-captures/` PPM set). |
| Findings log and doc size | partial | `tools_pc/docs_budget.py` | The budget checker exists (per-file and tier-1 token budgets; `--toc FILE`). ROADMAP.md was trimmed under budget 2026-10-04. `findings.md` is still about 2 MB (~500k tokens), so agents must keep using the index. Nothing blocks; trim only if the budget check starts failing. | | Findings log and doc size | partial | `tools_pc/docs_budget.py` | The budget checker exists (per-file and tier-1 token budgets; `--toc FILE`). ROADMAP.md was trimmed under budget 2026-10-04. `findings.md` is still about 2 MB (~500k tokens), so agents must keep using the index. Nothing blocks; trim only if the budget check starts failing. |
| 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. | | 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: Web 1.0 phase 2 | decision | — | Phase 1 shipped. Phase 2 (centred serif page) is plan-only. |
+26 -14
View File
@@ -1,15 +1,15 @@
# GitHub Pages config. Site is served from the /docs folder on the default # GitHub Pages config. Site is served from the /docs folder on the default
# branch (Settings -> Pages -> Deploy from a branch -> main / docs). # branch (Settings -> Pages -> Deploy from a branch -> main / docs).
title: GoldenEye 007 PC Port title: GoldenEye 007 PC Port
# v0.2.1 (Bing SEO): keep this SHORT -- jekyll-seo-tag splices it into the # 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 # 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 # 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 # tagline on every page. Per-page meta/og descriptions come from each page's
# front matter `description:` (see index.md), not from here. # front matter `description:` (see index.md), not from here.
description: GoldenEye 007 (Nintendo 64, 1997) on the PC - decompiled, ported, and playable. 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 # No GitHub theme: the site uses a hand-rolled _layouts/default.html plus the
# design in _includes/head-custom.html (charcoal/off-white/amber, square and # design in _includes/head-custom.html (the dark "amber terminal" theme: a
# flat.) # near-black starfield, a single amber accent, mono chrome over a sans body).
show_downloads: false show_downloads: false
# Canonical site location - used by jekyll-seo-tag (canonical URLs, og: tags) # Canonical site location - used by jekyll-seo-tag (canonical URLs, og: tags)
@@ -19,7 +19,7 @@ baseurl: /goldeneye-pc-port
# Link-preview card image for jekyll-seo-tag (og:image / twitter:image). # Link-preview card image for jekyll-seo-tag (og:image / twitter:image).
# Path is relative to the site root; the plugin prepends url + baseurl. # Path is relative to the site root; the plugin prepends url + baseurl.
image: /img/attract-bunker1.png image: /img/shots/shot-28.jpg
# Both are on the GitHub Pages plugin whitelist - no Gemfile needed. # Both are on the GitHub Pages plugin whitelist - no Gemfile needed.
# jekyll-seo-tag: per-page <title>/description/canonical/OpenGraph via {% seo %} # jekyll-seo-tag: per-page <title>/description/canonical/OpenGraph via {% seo %}
@@ -35,35 +35,47 @@ plugins:
# instead, so they resolve from the published pages too. # instead, so they resolve from the published pages too.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# The site publishes a small curated set (see docs/dev/notes/DE-SLOP-DOCS-PLAN.md): # The site publishes a small curated set:
# index.md, building.md, internals.md, porting-notes.md, dev-process.md, # index.md, under-the-hood.md (the "under the hood" hub), building.md, plus
# dev/agentic-development.md # the four docs grouped behind that hub: internals.md, porting-notes.md,
# dev-process.md, dev/agentic-development.md, the five short topic notes
# grouped under it (the-software-rsp.md, the-asset-pipeline.md,
# framerate-and-pacing.md, input-and-aim.md, saves.md), and
# security-and-fidelity-status.md (linked from index.md's Known issues).
# Everything else under docs/ is working material - the finding log, level # Everything else under docs/ is working material - the finding log, level
# status, backlogs, plans, inherited decomp guides. It stays in the repo (and # status, backlogs, plans, session notes, inherited decomp guides. It stays in
# on GitHub) but is excluded from the site build so it never shows up as a # the repo (and on GitHub) but is excluded from the site build so it never
# published page or in sitemap.xml. # 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.
exclude: exclude:
- BACKLOG.md
- HANDOFF.md - HANDOFF.md
- ROADMAP.md - ROADMAP.md
- SetupGuide.md - SetupGuide.md
- StructureGuide.md
- StyleGuide.md - StyleGuide.md
- dev/ACCURACY-AUDIT-2026-09-20.md
- dev/AUDIO-PLAN.md - dev/AUDIO-PLAN.md
- dev/AUDIT-M6-player-offsets.md - dev/AUDIT-M6-player-offsets.md
- dev/CONTROLLER-INPUT-PLAN.md
- dev/CUT-CONTENT-BACKLOG.md
- dev/D176a-SKY-NOTES.md - dev/D176a-SKY-NOTES.md
- dev/D356-SETTINGS-REGROUP-PLAN.md
- dev/D357-SETTINGS-VALUES-PLAN.md
- dev/GE-ENV-PROBES.md - dev/GE-ENV-PROBES.md
- dev/GEPD-INPUT-PLAN.md
- dev/HANDOFF-ARCHIVE.md - dev/HANDOFF-ARCHIVE.md
- dev/LEVEL-OBJECTIVES.md - dev/LEVEL-OBJECTIVES.md
- dev/LEVEL-PLAYTEST.md - dev/LEVEL-PLAYTEST.md
- dev/LEVEL-STATUS.md - dev/LEVEL-STATUS.md
- dev/MODERN-PORT-FEATURES-BACKLOG.md - dev/notes
- dev/OPTIONS-MENU-PLAN.md - dev/OPTIONS-MENU-PLAN.md
- dev/preview-video.md
- dev/QOL-INVENTORY.md - dev/QOL-INVENTORY.md
- dev/RUMBLE-PLAN.md
- dev/TEXTURE-GLITCH-ANALYSIS.md - dev/TEXTURE-GLITCH-ANALYSIS.md
- dev/UNLOCKED-FPS-PLAN.md - dev/UNLOCKED-FPS-PLAN.md
- dev/VIEWMODEL-RESEARCH.md - dev/VIEWMODEL-RESEARCH.md
- dev/WIDESCREEN-FOV-PLAN.md - dev/WATCH-SETTINGS-PLAN.md
- dev/findings-index.csv - dev/findings-index.csv
- dev/findings.md - dev/findings.md
- dev/game-behavior-reference.md - dev/game-behavior-reference.md
+194 -189
View File
@@ -3,154 +3,166 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet"> <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">
<style> <style>
/* ---------------------------------------------------------------------- /* ----------------------------------------------------------------------
Hand-rolled site theme (replaces jekyll-theme-cayman, 2026-09-28). Site theme (replaces the 2026-09-28 "night desk" theme).
Look: committed charcoal/off-white/amber "night desk" — flat and square, Look: a dark, minimal "amber terminal" -- near-black space background
serif display type (Libre Baskerville) over a system sans body, thin (a faint starfield), a single amber accent (GoldenEye gold), a
amber rules between sections, text navigation separated by middots. console/mono identity for chrome (IBM Plex Mono) over a clean
No gradients, no shadows, no rounded corners, no theme switcher. 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.
--------------------------------------------------------------------- */ --------------------------------------------------------------------- */
:root { :root {
--bg: #1b1b1d; /* charcoal page */ --bg: #050607; /* true near-black */
--bg-soft: #232326; /* code / alt rows */ --bg-frame: #0c0d0f; /* the page frame */
--bg-deep: #141416; /* code blocks, masthead */ --bg-panel: #121316; /* code / cards / tables */
--ink: #e8e6e3; /* off-white body text */ --ink: #e6e4df; /* warm off-white */
--ink-soft: #b5b1a9; /* secondary text */ --ink-soft: #a9a7a0; /* secondary text */
--ink-mute: #8d897f; /* captions, footer */ --ink-mute: #70706a; /* captions, footer */
--line: #3a3a3e; /* 1px borders */ --line: #26282c; /* 1px borders */
--accent: #ffb454; /* amber/gold */ --line-2: #3a3d44; /* stronger borders */
--accent-hi: #ffd08a; /* link hover */ --accent: #e8a13a; /* GoldenEye gold (amber) */
--measure: 44rem; --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;
} }
html { background: var(--bg); } html { background: var(--bg); }
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
font-size: 16.5px;
line-height: 1.7;
color: var(--ink); color: var(--ink);
background: var(--bg); font-family: var(--sans);
font-size: 14px;
line-height: 1.62;
/* 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;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
} }
/* ---- Masthead -------------------------------------------------------- */ a {
.site-masthead {
background: var(--bg-deep);
border-bottom: 1px solid var(--accent);
}
.masthead-inner {
max-width: 52rem;
margin: 0 auto;
padding: 2.6rem 1.5rem 1.6rem;
text-align: center;
}
.masthead-title {
display: inline-block;
font-family: "Libre Baskerville", Georgia, "Times New Roman", serif;
font-weight: 700;
font-size: 2.1rem;
line-height: 1.25;
color: var(--ink);
text-decoration: none;
letter-spacing: 0.01em;
}
.masthead-title:hover { color: var(--accent); }
.masthead-tagline {
margin-top: 0.5rem;
color: var(--ink-mute);
font-size: 0.95rem;
}
.masthead-nav {
margin-top: 1.3rem;
font-size: 0.9rem;
}
.masthead-nav a {
color: var(--accent);
text-decoration: none;
padding: 0.1rem 0.15rem;
}
.masthead-nav a:hover { color: var(--accent-hi); text-decoration: underline; }
.nav-sep { color: var(--ink-mute); padding: 0 0.35rem; }
/* ---- Content ---------------------------------------------------------- */
.doc-content {
max-width: 52rem;
margin: 0 auto;
padding: 2.5rem 1.5rem 1rem;
}
.doc-content p,
.doc-content ul,
.doc-content ol,
.doc-content blockquote { max-width: var(--measure); }
.doc-content h1,
.doc-content h2,
.doc-content h3,
.doc-content h4 {
font-family: "Libre Baskerville", Georgia, "Times New Roman", serif;
font-weight: 700;
line-height: 1.35;
color: var(--ink);
margin-top: 2.4rem;
margin-bottom: 0.9rem;
}
.doc-content h1 { font-size: 1.75rem; }
.doc-content h2 { font-size: 1.35rem; }
.doc-content h3 { font-size: 1.1rem; }
.doc-content > h1:first-child { margin-top: 0.5rem; }
.doc-content a {
color: var(--accent); color: var(--accent);
text-decoration: underline; text-decoration: underline;
text-decoration-color: rgba(255, 180, 84, 0.45); text-decoration-color: rgba(232,161,58,.45);
text-underline-offset: 2px; text-underline-offset: 2px;
} }
.doc-content a:hover { a:hover { color: var(--accent-hi); text-decoration-color: currentColor; }
/* ---- 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 {
font-family: var(--mono);
font-weight: 700;
font-size: 1.7rem;
letter-spacing: .05em;
color: var(--ink);
}
.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 {
color: var(--accent);
text-decoration: none;
padding: 0 .35rem;
}
.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;
}
/* ---- 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); }
.doc-content h3 {
font-family: var(--mono);
font-size: .92rem;
color: var(--accent-hi); color: var(--accent-hi);
text-decoration-color: currentColor; 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 { .doc-content hr {
border: 0; border: 0; height: 1px; background: var(--line-2); margin: 2rem 0;
height: 1px;
background: var(--accent);
opacity: 0.5;
margin: 2.5rem 0;
} }
.doc-content blockquote { .doc-content blockquote {
color: var(--ink-soft); color: var(--ink-soft);
border-left: 3px solid var(--accent); border-left: 3px solid var(--accent);
padding: 0.2rem 0 0.2rem 1.1rem; padding: .2rem 0 .2rem 1rem;
margin-left: 0; margin-left: 0;
font-style: normal;
} }
/* Lead paragraph on doc pages (the italic summary under the H1) */ /* ---- code ---------------------------------------------------------- */
.doc-content > p > em:only-child {
font-style: normal;
color: var(--ink-soft);
font-size: 1.05rem;
}
/* ---- Code ------------------------------------------------------------ */
.doc-content code { .doc-content code {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-family: var(--mono);
font-size: 0.86em; font-size: .86em;
background: var(--bg-soft); background: var(--bg-panel);
border: 1px solid var(--line); border: 1px solid var(--line);
padding: 0.12em 0.4em; padding: .1em .4em;
color: var(--ink); color: var(--code-ink);
} }
.doc-content pre { .doc-content pre {
background: var(--bg-deep); background: var(--bg-panel);
border: 1px solid var(--line); border: 1px solid var(--line);
padding: 1rem 1.15rem; padding: 1rem 1.15rem;
max-width: var(--measure); max-width: var(--measure);
@@ -158,105 +170,98 @@
line-height: 1.55; line-height: 1.55;
} }
.doc-content pre code { .doc-content pre code {
background: transparent; background: transparent; border: 0; padding: 0;
border: 0; color: var(--code-ink); font-size: .84rem;
padding: 0;
color: var(--ink);
font-size: 0.84rem;
} }
/* ---- Tables ----------------------------------------------------------- */ /* ---- tables -------------------------------------------------------- */
.doc-content table { .doc-content table {
border-collapse: collapse; border-collapse: collapse; width: 100%;
width: 100%; max-width: var(--measure); margin: 1.5rem 0; font-size: .9rem;
max-width: var(--measure);
margin: 1.5rem 0;
font-size: 0.92rem;
} }
.doc-content table th, .doc-content th, .doc-content td {
.doc-content table td { border: 1px solid var(--line); padding: .5rem .7rem; text-align: left;
border: 1px solid var(--line);
padding: 0.5rem 0.75rem;
text-align: left;
} }
.doc-content table th { background: var(--bg-soft); font-weight: 600; } .doc-content th {
.doc-content table tr:nth-child(2n) { background: #1f1f22; } 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); }
/* ---- Images & gallery -------------------------------------------------- */ /* ---- images -------------------------------------------------------- */
.doc-content img { .doc-content img {
border: 1px solid var(--line); border: 1px solid var(--line); border-radius: 0; max-width: 100%;
border-radius: 0;
max-width: 100%;
} }
/* ---- details / summary (the "full gallery" collapse) --------------- */
.doc-content details { .doc-content details {
max-width: var(--measure); max-width: var(--measure);
border: 1px solid var(--line); border: 1px solid var(--line);
background: var(--bg-soft); background: var(--bg-panel);
padding: 0.6rem 0.9rem; padding: .6rem .9rem; margin: 1.5rem 0;
margin: 1.5rem 0; }
.doc-content summary {
cursor: pointer; color: var(--accent);
font-family: var(--mono); font-size: .85rem; letter-spacing: .03em;
} }
.doc-content summary { cursor: pointer; color: var(--accent); }
/* ---- Footer ------------------------------------------------------------- */ /* ---- console-viewer carousel (home page media) --------------------- */
.site-footer { .viewer {
max-width: 52rem; max-width: 38rem; margin: 1rem auto;
margin: 3rem auto 0; border: 1px solid var(--line-2);
padding: 1.5rem 1.5rem 2.5rem; background: var(--bg-panel);
border-top: 1px solid var(--line); font-family: var(--mono);
} }
.footer-inner { max-width: var(--measure); } .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;
}
/* ---- footer -------------------------------------------------------- */
.site-footer { text-align: center; padding: 1.2rem 1rem 1.6rem; }
.footer-stamp { .footer-stamp {
color: var(--ink-soft); font-family: var(--mono); font-size: .78rem;
font-size: 0.88rem; letter-spacing: .03em; color: var(--ink-soft);
margin: 0 0 0.8rem; margin: 0 0 .7rem;
} }
.footer-stamp a { color: var(--accent); text-decoration: none; } .footer-stamp a { color: var(--accent); text-decoration: none; }
.footer-stamp a:hover { color: var(--accent-hi); text-decoration: underline; } .footer-stamp a:hover { color: var(--accent-hi); text-decoration: underline; }
.footer-legal { .footer-legal {
color: var(--ink-mute); color: var(--ink-mute); font-size: .72rem; line-height: 1.55;
font-size: 0.8rem; margin: 0 auto; max-width: var(--measure);
line-height: 1.6;
margin: 0;
} }
@media (max-width: 42rem) { @media (max-width: 40rem) {
.masthead-title { font-size: 1.7rem; } .frame { margin: 8px; }
.masthead-inner { padding: 2rem 1rem 1.2rem; } .banner .t { font-size: 1.35rem; }
.doc-content { padding-top: 1.8rem; }
} }
/* ---- 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 code-block background) while it waits to render. */
.doc-content pre.mermaid,
.doc-content pre > code.language-mermaid {
background: transparent;
border: 0;
color: var(--ink);
padding: 0;
}
.doc-content pre.mermaid { text-align: center; overflow-x: auto; }
.doc-content pre.mermaid:not([data-processed]) { visibility: hidden; }
</style> </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.
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",
});
mermaid.run({ querySelector: "pre.mermaid" });
</script>
+26 -28
View File
@@ -3,46 +3,44 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="{{ '/img/attract-bunker1.png' | relative_url }}"> <link rel="icon" type="image/jpeg" href="{{ '/img/shots/shot-28.jpg' | relative_url }}">
{%- seo -%} {%- seo -%}
{%- include head-custom.html -%} {%- include head-custom.html -%}
</head> </head>
<body> <body>
<header class="site-masthead"> <div class="frame">
<div class="masthead-inner">
<a class="masthead-title" href="{{ '/' | relative_url }}">{{ site.title }}</a> <header class="banner">
<div class="masthead-tagline">{{ site.description }}</div> <div class="t">{{ site.title }}</div>
<nav class="masthead-nav"> <div class="tag">{{ site.description }}</div>
<a href="{{ '/' | relative_url }}">Home</a><span class="nav-sep">·</span>
<a href="{{ '/internals' | relative_url }}">Internals</a><span class="nav-sep">·</span>
<a href="{{ '/porting-notes' | relative_url }}">Porting notes</a><span class="nav-sep">·</span>
<a href="{{ '/building' | relative_url }}">Building</a><span class="nav-sep">·</span>
<a href="{{ '/dev-process' | relative_url }}">Dev process</a><span class="nav-sep">·</span>
<a href="{{ '/dev/agentic-development' | relative_url }}">Two-agent study</a>
</nav>
</div>
</header> </header>
<nav class="sitenav">
<a href="{{ '/' | relative_url }}">home</a><span class="sep">/</span>
<a href="{{ '/under-the-hood' | relative_url }}">under the hood</a><span class="sep">/</span>
<a href="{{ '/building' | relative_url }}">building</a>
</nav>
<div class="rule g"></div>
<main class="doc-content"> <main class="doc-content">
{{ content }} {{ content }}
</main> </main>
<div class="rule g"></div>
<footer class="site-footer"> <footer class="site-footer">
<div class="footer-inner"> <div class="footer-stamp">
<p class="footer-stamp"> last updated: 2026-10-07 &middot; v0.5.0
Last updated 2026-09-28 — v0.4.0 &nbsp;&middot;&nbsp; <a href="https://github.com/jkdansereau/goldeneye-pc-port">GitHub</a>
<span class="nav-sep">·</span> &nbsp;&middot;&nbsp; <a href="https://github.com/jkdansereau/goldeneye-pc-port/releases">releases</a>
<a href="https://github.com/jkdansereau/goldeneye-pc-port">GitHub</a> </div>
<span class="nav-sep">·</span> <div class="footer-legal">
<a href="https://github.com/jkdansereau/goldeneye-pc-port/releases">Releases</a> Non-commercial fan preservation / research project. No ROM or game assets are
</p> distributed; you supply a ROM you already own. Not affiliated with or endorsed
<p class="footer-legal"> by Nintendo, Rare, Microsoft, MGM, Danjaq, or EON Productions.
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.
</p>
</div> </div>
</footer> </footer>
</div>
</body> </body>
</html> </html>
+7
View File
@@ -185,6 +185,13 @@ justifies. Rules:
spend a cycle rebasing it**; re-derive only whatever part of its content 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 is still true as a fresh, small PR against current `main`. Merging stale
content back over newer information is a regression, not a save. 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) ## 7. The rule-2 sign-off procedure (genuine `src/game` behavior changes)
+2 -2
View File
@@ -38,7 +38,7 @@ More is already in place than "audio not started" suggests:
`osAiGetLength()` in `port/src/libultra.c:852-862` discard everything `osAiGetLength()` in `port/src/libultra.c:852-862` discard everything
(`osAiGetLength` returns 0), and `port/src/audio.c` never opens an SDL (`osAiGetLength` returns 0), and `port/src/audio.c` never opens an SDL
device. `main.c:141-142` already calls `audioInit()` / `mixerInit()`. device. `main.c:141-142` already calls `audioInit()` / `mixerInit()`.
- **The PD reference is local** at `C:\Users\james\Source\Repos\pd_port`, - **The PD reference is local** at `<repos>\pd_port`,
with the full audio stack: `port/src/audio.c` (75 lines, SDL device + 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 queue), `port/src/mixer.c` (~720 lines, software RSP-audio-ucode), and the
wiring trick in `include/PR/abi.h`. wiring trick in `include/PR/abi.h`.
@@ -201,4 +201,4 @@ M-50) → v0.4.0".
| `port/src/libultra.c:848-863` | `osAi*` shims — wire to audio.c (Phase 1) | | `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/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 | | `port/src/ucode.c` | Dummy `aspMain*` markers stay forever under CPU-bypass — document decision |
| `C:\Users\james\Source\Repos\pd_port\port\src\{audio,mixer}.c`, `include\PR\abi.h` | PD ground truth: device glue + DSP cores + the macro-swap pattern | | `<repos>\pd_port\port\src\{audio,mixer}.c`, `include\PR\abi.h` | PD ground truth: device glue + DSP cores + the macro-swap pattern |
+1 -1
View File
@@ -5,7 +5,7 @@ this session; verification in `docs/dev/findings.md` D356 (GE_WSPROBE_RESET
front + in-stage probes, regressions, build). Supersedes the "BOND FILE" front + in-stage probes, regressions, build). Supersedes the "BOND FILE"
section introduced in D353. Baselines: Turok PC port options (Nightdive, list section introduced in D353. Baselines: Turok PC port options (Nightdive, list
provided by the maintainer), the PD port's `port/src/optionsmenu.c` provided by the maintainer), the PD port's `port/src/optionsmenu.c`
(`C:\Users\james\Source\Repos\pd_port`), and plan §6 of (`<repos>\pd_port`), and plan §6 of
`docs/dev/OPTIONS-MENU-PLAN.md` (the original "align with Turok" note). `docs/dev/OPTIONS-MENU-PLAN.md` (the original "align with Turok" note).
## 1. Goal ## 1. Goal
+1 -1
View File
@@ -1,7 +1,7 @@
# D357 — Settings thresholds & values: align display units/defaults with the PD port + Turok PC # 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 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 (`C:\Users\james\Source\Repos\pd_port`, `port/src/optionsmenu.c` + checkout (`<repos>\pd_port`, `port/src/optionsmenu.c` +
`input.c`/`video.c` config registrations), the Turok PC (Nightdive) list from `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 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 user notes, not checked independently*), and the N64 watch (ground truth for
+17 -12
View File
@@ -1,5 +1,10 @@
# HANDOFF ARCHIVE — session-by-session narrative # HANDOFF ARCHIVE — session-by-session narrative
> **Local machine paths are genericised** in this archive: `<repo>` = this repo's
> working directory, `<repos>` = the local source-repos root, `<games>` / `<videos>`
> = the local games / video roots, `<python>` = the local CPython install, `<temp>`
> = a writable native temp dir. Session facts are unchanged.
> **Reference only.** Current state lives in the [README](../../README.md) > **Reference only.** Current state lives in the [README](../../README.md)
> "Status" section and `docs/dev/LEVEL-STATUS.md`; per-finding detail is in > "Status" section and `docs/dev/LEVEL-STATUS.md`; per-finding detail is in
> [`findings.md`](findings.md) §F/§H — look up via > [`findings.md`](findings.md) §F/§H — look up via
@@ -985,7 +990,7 @@ In KEEP-A test (sky quad only), the band shows (98,97,128) = fog color. This mea
### Environment & build ### Environment & build
- Build: `export PATH="/c/msys64/mingw64/bin:$PATH" && cmake --build build-pc -j 8` - Build: `export PATH="/c/msys64/mingw64/bin:$PATH" && cmake --build build-pc -j 8`
- Python: `"C:\Users\james\AppData\Local\Programs\Python\Python313\python.exe"` (has numpy+PIL) - Python: `"<python>\python.exe"` (has numpy+PIL)
- Run: from repo root, ROM in `./data/`. Level: `-level_36` - Run: from repo root, ROM in `./data/`. Level: `-level_36`
- Frame capture: F12 → `ppm/frame_NNN.ppm`; or `GE_PCDUMP=lo-hi:step` - Frame capture: F12 → `ppm/frame_NNN.ppm`; or `GE_PCDUMP=lo-hi:step`
- Input script: `GE_INPUTSCRIPT="120:START;..."` (M-99: CANNOT reach mouselook) - Input script: `GE_INPUTSCRIPT="120:START;..."` (M-99: CANNOT reach mouselook)
@@ -1081,7 +1086,7 @@ Facility = `-level_34` (stageId 34 = `LEVELID_FACILITY`; `boss.c` decodes `-leve
4. Diff behaviour vs a level whose opening beat DOES work (e.g. Dam `-level_33`) to isolate "Facility data" vs "general beat machinery". 4. Diff behaviour vs a level whose opening beat DOES work (e.g. Dam `-level_33`) to isolate "Facility data" vs "general beat machinery".
5. Any fix: port-only unless it's the documented ABI/layout class; new finding = D266+; append to `docs/porting-notes.md` if a new bug class emerges. 5. Any fix: port-only unless it's the documented ABI/layout class; new finding = D266+; append to `docs/porting-notes.md` if a new bug class emerges.
**Environment (unchanged from prior sessions):** build `export PATH="/c/msys64/mingw64/bin:$PATH" && cmake --build build-pc -j 8` (or `./build-pc.sh ntsc-final`); kill any running `ge007*` before linking. Exe `build-pc/ge007.x86_64.exe`, run from repo root (ROM in `./data/`). Headless: `GE_INPUTSCRIPT` (tokens A/B/Z/START/UP/DOWN/LEFT/RIGHT/CUP/CDOWN/CLEFT/CRIGHT/SDOWN/SNONE, `tick:TOKEN;…`, pulse = 6 reads), `GE_PCDUMP=lo-hi:step`, `GE_INPUTLOG=1`, F12 → `ppm/shot_NNN.ppm`. Python (PIL/numpy) at `/c/Users/james/AppData/Local/Programs/Python/Python313/python.exe`; **cast uint8 arrays to int32 before bit shifts**. **Environment (unchanged from prior sessions):** build `export PATH="/c/msys64/mingw64/bin:$PATH" && cmake --build build-pc -j 8` (or `./build-pc.sh ntsc-final`); kill any running `ge007*` before linking. Exe `build-pc/ge007.x86_64.exe`, run from repo root (ROM in `./data/`). Headless: `GE_INPUTSCRIPT` (tokens A/B/Z/START/UP/DOWN/LEFT/RIGHT/CUP/CDOWN/CLEFT/CRIGHT/SDOWN/SNONE, `tick:TOKEN;…`, pulse = 6 reads), `GE_PCDUMP=lo-hi:step`, `GE_INPUTLOG=1`, F12 → `ppm/shot_NNN.ppm`. Python (PIL/numpy) at `<python>`; **cast uint8 arrays to int32 before bit shifts**.
--- ---
@@ -1345,7 +1350,7 @@ will do a full manual playtest in lieu of the `verify.sh` sweep.
3. **Steam Deck test session (~1 hr, needs the hardware — who has the Deck?):** install tarball via USB, Facility walk+fire (D203 repro profile), gamepad check, `Video.DisplayFPS` on Dam + Streets, grab `ge007.crash.log` if anything faults. 3. **Steam Deck test session (~1 hr, needs the hardware — who has the Deck?):** install tarball via USB, Facility walk+fire (D203 repro profile), gamepad check, `Video.DisplayFPS` on Dam + Streets, grab `ge007.crash.log` if anything faults.
4. Tag `v0.2.0-pre`; publish pre-release with known issues: D243 (Dam end race), D230 (music quality), D245 (water seam), D246 (edge strips), D197 (face wrap on Silo), D75 (front-end models), the D251 4K ghost row, and "not yet verified on real Deck hardware". Flip to final after the user's playtest passes. 4. Tag `v0.2.0-pre`; publish pre-release with known issues: D243 (Dam end race), D230 (music quality), D245 (water seam), D246 (edge strips), D197 (face wrap on Silo), D75 (front-end models), the D251 4K ghost row, and "not yet verified on real Deck hardware". Flip to final after the user's playtest passes.
**Environment:** build as before (`/c/msys64/usr/bin/bash.exe -lc 'export PATH=/c/msys64/mingw64/bin:$PATH; cd /c/Users/james/Source/Repos/gh-fullhist && ./build-pc.sh ntsc-final'`). CI (`.github/workflows/ci.yml`) builds Linux per push — the `linux-build` job's artifact is the bundle source for the tarball if local Linux testing isn't available. **Environment:** build as before (`/c/msys64/usr/bin/bash.exe -lc 'export PATH=/c/msys64/mingw64/bin:$PATH; cd <repo> && ./build-pc.sh ntsc-final'`). CI (`.github/workflows/ci.yml`) builds Linux per push — the `linux-build` job's artifact is the bundle source for the tarball if local Linux testing isn't available.
## F10 overlay QoL session (2026-09-13, shipped in v0.2.0-pre) — D251; known bug: intermittent 4K-fullscreen "repeat of top entry" at panel bottom (MSAA×fullscreen driver suspect, unverified) ## F10 overlay QoL session (2026-09-13, shipped in v0.2.0-pre) — D251; known bug: intermittent 4K-fullscreen "repeat of top entry" at panel bottom (MSAA×fullscreen driver suspect, unverified)
@@ -1373,7 +1378,7 @@ will do a full manual playtest in lieu of the `verify.sh` sweep.
**Environment / state notes:** **Environment / state notes:**
- User's live `data/ge007.ini` already has `Fullscreen = 1`, `MSAA = 4` (backup at `/tmp/ge007.ini.bak` — I did not modify their ini). Monitor: single display 3072×1728, window fullscreen at 0,0. - User's live `data/ge007.ini` already has `Fullscreen = 1`, `MSAA = 4` (backup at `/tmp/ge007.ini.bak` — I did not modify their ini). Monitor: single display 3072×1728, window fullscreen at 0,0.
- `ppm/user4k.png` exists from a PowerShell `CopyFromScreen` capture but was taken with the overlay **closed** — low value; user asked to stop analyzing captures. - `ppm/user4k.png` exists from a PowerShell `CopyFromScreen` capture but was taken with the overlay **closed** — low value; user asked to stop analyzing captures.
- Build: `/c/msys64/usr/bin/bash.exe -lc 'export PATH=/c/msys64/mingw64/bin:$PATH; cd /c/Users/james/Source/Repos/gh-fullhist && ./build-pc.sh ntsc-final'`. Launch from repo root: `./build-pc/ge007.x86_64.exe &` (needs `./data/` ROM). - Build: `/c/msys64/usr/bin/bash.exe -lc 'export PATH=/c/msys64/mingw64/bin:$PATH; cd <repo> && ./build-pc.sh ntsc-final'`. Launch from repo root: `./build-pc/ge007.x86_64.exe &` (needs `./data/` ROM).
- Design doc for the overlay: `docs/dev/OPTIONS-MENU-PLAN.md` (approach C: port-layer overlay, zero src/ menu edits). - Design doc for the overlay: `docs/dev/OPTIONS-MENU-PLAN.md` (approach C: port-layer overlay, zero src/ menu edits).
## M-123 (2026-09-14) — D194 ① log mined + wrap-spike fixed; spazz NOT in our writes (they're rock-stable); next: live capture with the new residue/autoaim logging, then user A/B playtest ## M-123 (2026-09-14) — D194 ① log mined + wrap-spike fixed; spazz NOT in our writes (they're rock-stable); next: live capture with the new residue/autoaim logging, then user A/B playtest
@@ -1398,7 +1403,7 @@ will do a full manual playtest in lieu of the `verify.sh` sweep.
## M-122 (2026-09-13) — D194 GEPD-mirror aim IMPLEMENTED + user-confirmed "mechanically works"; three items left: ① crosshair/arm spazz glitch, ② aim-mode FPS drop (perf.ps1), ③ sensitivity tuning. Next session starts at ①. ## M-122 (2026-09-13) — D194 GEPD-mirror aim IMPLEMENTED + user-confirmed "mechanically works"; three items left: ① crosshair/arm spazz glitch, ② aim-mode FPS drop (perf.ps1), ③ sensitivity tuning. Next session starts at ①.
**The aim rewrite landed.** After four failed stick-synthesis models (absolute → dial → relative-angle → velocity-mapped stick; see M-121 + commits `8a8d066b`/`650b7f63`/`7f65ed15`), the session found and read **GEPD's actual source** (`C:\Users\james\Games\Emulators\Nintendo 64\1964_GEPD_Edition\1964\source.tar.xz` → `MouseInjectorPlugin/games/goldeneye.c`, extracted copy at `/c/Users/james/AppData/Local/Temp/gepd-src/`) and mirrored its model exactly. **User verdict: "mechanically it works... the aim mode works how we'd want."** Committed as `ef49d9e6` on `fix/d194-mouse-dt-decouple` (branch is up to date with main; PR #75/#76 already merged in). **The aim rewrite landed.** After four failed stick-synthesis models (absolute → dial → relative-angle → velocity-mapped stick; see M-121 + commits `8a8d066b`/`650b7f63`/`7f65ed15`), the session found and read **GEPD's actual source** (`<games>\Emulators\Nintendo 64\1964_GEPD_Edition\1964\source.tar.xz` → `MouseInjectorPlugin/games/goldeneye.c`, extracted copy at `<temp>/gepd-src/`) and mirrored its model exactly. **User verdict: "mechanically it works... the aim mode works how we'd want."** Committed as `ef49d9e6` on `fix/d194-mouse-dt-decouple` (branch is up to date with main; PR #75/#76 already merged in).
**The model (all in `port/src/input.c`, `aimGepdCompute()` ~line 1377):** mouse delta moves a crosshair POSITION accumulator (`s_gepdCrossX/Y`, clamped ±`GEPD_CROSSHAIR_LIMIT`=5.16); each tick it writes `crosshair_x/y_pos` + `gun_azimuth_angle/turning` (GEPD formulas, failsafe weapon offsets 0.15/0, RATIOFACTOR=1 for our 4:3, `GEPD_BASE_FOV`=90) and edge-scrolls the camera only past 72% toward the screen edge (`(ratio-0.72)*475*dt*(fov/90)`). **No look stick is emitted while aiming.** On aim release we stop writing and the game's own damping eases crosshair+arm back to centre; every non-aim tick adopts `crosshair_x/y_pos` so re-entry starts where the game left it (GEPD does the same). **The model (all in `port/src/input.c`, `aimGepdCompute()` ~line 1377):** mouse delta moves a crosshair POSITION accumulator (`s_gepdCrossX/Y`, clamped ±`GEPD_CROSSHAIR_LIMIT`=5.16); each tick it writes `crosshair_x/y_pos` + `gun_azimuth_angle/turning` (GEPD formulas, failsafe weapon offsets 0.15/0, RATIOFACTOR=1 for our 4:3, `GEPD_BASE_FOV`=90) and edge-scrolls the camera only past 72% toward the screen edge (`(ratio-0.72)*475*dt*(fov/90)`). **No look stick is emitted while aiming.** On aim release we stop writing and the game's own damping eases crosshair+arm back to centre; every non-aim tick adopts `crosshair_x/y_pos` so re-entry starts where the game left it (GEPD does the same).
@@ -1437,7 +1442,7 @@ will do a full manual playtest in lieu of the `verify.sh` sweep.
**RMB aim-mode (D194 continuation): two more attempts this session, both did not land, session ended mid-plan on the weekly usage limit.** **RMB aim-mode (D194 continuation): two more attempts this session, both did not land, session ended mid-plan on the weekly usage limit.**
- **Attempt 1 (small, safe): raised `AIM_MOVE_THRESH` 0.3->2.5px and lowered `Input.MouseAimSpeed` default 16->14 (matching the user's live ini, which had drifted to 50).** User's verdict: **"didn't actually fix anything"** — confirms the core problem isn't a magnitude/gain issue, it's the shape of the response. - **Attempt 1 (small, safe): raised `AIM_MOVE_THRESH` 0.3->2.5px and lowered `Input.MouseAimSpeed` default 16->14 (matching the user's live ini, which had drifted to 50).** User's verdict: **"didn't actually fix anything"** — confirms the core problem isn't a magnitude/gain issue, it's the shape of the response.
- **Real root cause found via code reading (still valid, not invalidated by what follows):** `bondviewProcessInput(s8 stick_x, s8 stick_y, ...)` in `src/game/bondview2.c` takes a **signed 8-bit stick** — the smallest representable nonzero aim-turn value is a hard jump to 10% speed (stick=61; `(stick-60)/10` is exactly 0 at stick<=60). No threshold/curve tuning on the port's synthesis side can produce a smooth ramp through an integer that can only be 60 or >=61. Confirmed via `pd_port` (`C:\Users\james\Source\Repos\pd_port`) reference: PD's own mouse-look (`src/game/player.c:3669`, `src/game/bondeyespy.c:945`) never synthesizes an N64 stick at all -- it's `theta += mdx * scale`, a continuous float angle accumulator, no floor, no int8 quantization anywhere. - **Real root cause found via code reading (still valid, not invalidated by what follows):** `bondviewProcessInput(s8 stick_x, s8 stick_y, ...)` in `src/game/bondview2.c` takes a **signed 8-bit stick** — the smallest representable nonzero aim-turn value is a hard jump to 10% speed (stick=61; `(stick-60)/10` is exactly 0 at stick<=60). No threshold/curve tuning on the port's synthesis side can produce a smooth ramp through an integer that can only be 60 or >=61. Confirmed via `pd_port` (`<repos>\pd_port`) reference: PD's own mouse-look (`src/game/player.c:3669`, `src/game/bondeyespy.c:945`) never synthesizes an N64 stick at all -- it's `theta += mdx * scale`, a continuous float angle accumulator, no floor, no int8 quantization anywhere.
- **User also reported a second, more specific symptom mid-investigation: aim resets to center the instant mouse movement pauses (even while still holding RMB), breaking precision tasks (shoot-the-lock, watch-laser-cuts-metal-in-train puzzles).** Traced `bondviewCurrentPlayerUpdateSpeedTheta`/`speedVertaUp`/`Down` (bondview2.c) in detail: confirmed this is a smoothed rate-based decel model (`g_CurrentPlayer->speedtheta` eases toward 0 over several ticks when input stops, does NOT hard-reset) -- so a literal snap-to-center was not found in that specific mechanism. Root cause of the reset symptom specifically was **not conclusively identified** before moving to the rewrite. - **User also reported a second, more specific symptom mid-investigation: aim resets to center the instant mouse movement pauses (even while still holding RMB), breaking precision tasks (shoot-the-lock, watch-laser-cuts-metal-in-train puzzles).** Traced `bondviewCurrentPlayerUpdateSpeedTheta`/`speedVertaUp`/`Down` (bondview2.c) in detail: confirmed this is a smoothed rate-based decel model (`g_CurrentPlayer->speedtheta` eases toward 0 over several ticks when input stops, does NOT hard-reset) -- so a literal snap-to-center was not found in that specific mechanism. Root cause of the reset symptom specifically was **not conclusively identified** before moving to the rewrite.
- **Attempt 2 (bigger, PD-style, approved by user before starting): bypassed the s8-stick synthesis entirely for aim-mode.** `port/src/input.c` computed a continuous -1..1 turn/vert speed (`inputGetContinuousAimSpeed()`, new function + header decl) instead of synthesizing `sx`/`sy`; `src/game/bondview2.c`'s aim-mode block (`#ifdef PORT`) read it directly, bypassing the `(stick-60)/10` conversion but leaving all downstream `speedtheta`/`speedverta` easing untouched. Removed the now-dead `Input.AimBand`/`Input.MouseAimCurve` knobs. **Compiled clean (verified both PORT and N64 `#ifdef` brace balance carefully), crash-free in a headless sanity check -- but broke live: "somethings broken... can't aim at all... it locks my input completely with the mouse."** Root cause of THAT breakage was not identified (no repro available headlessly; ran out of session before a careful live-debug pass). **Fully reverted** (`git checkout -- src/game/bondview2.c port/src/input.c port/include/input.h` back to the `db6eba2b` merge commit) — repo confirmed clean, PR #62's mouse work is back to its pre-session (working, if imperfectly-tuned) state. Live ini's `MouseAimSpeed` also manually restored 14->16 to match the reverted code default. - **Attempt 2 (bigger, PD-style, approved by user before starting): bypassed the s8-stick synthesis entirely for aim-mode.** `port/src/input.c` computed a continuous -1..1 turn/vert speed (`inputGetContinuousAimSpeed()`, new function + header decl) instead of synthesizing `sx`/`sy`; `src/game/bondview2.c`'s aim-mode block (`#ifdef PORT`) read it directly, bypassing the `(stick-60)/10` conversion but leaving all downstream `speedtheta`/`speedverta` easing untouched. Removed the now-dead `Input.AimBand`/`Input.MouseAimCurve` knobs. **Compiled clean (verified both PORT and N64 `#ifdef` brace balance carefully), crash-free in a headless sanity check -- but broke live: "somethings broken... can't aim at all... it locks my input completely with the mouse."** Root cause of THAT breakage was not identified (no repro available headlessly; ran out of session before a careful live-debug pass). **Fully reverted** (`git checkout -- src/game/bondview2.c port/src/input.c port/include/input.h` back to the `db6eba2b` merge commit) — repo confirmed clean, PR #62's mouse work is back to its pre-session (working, if imperfectly-tuned) state. Live ini's `MouseAimSpeed` also manually restored 14->16 to match the reverted code default.
@@ -1893,7 +1898,7 @@ current testing, folded in:
player's already-fixed 1P viewmodel isn't implicated. Narrows the player's already-fixed 1P viewmodel isn't implicated. Narrows the
search surface. search surface.
3. **D194 mouse feel — external corroboration, not a new mechanism.** 3. **D194 mouse feel — external corroboration, not a new mechanism.**
Investigated `C:\Users\james\Games\Emulators\Nintendo 64\1964_GEPD_Edition` Investigated `<games>\Emulators\Nintendo 64\1964_GEPD_Edition`
(the user's reference point for "feels better") — the 1964 emulator's (the user's reference point for "feels better") — the 1964 emulator's
bundled `Mouse_Injector.dll` (closed-source, no source available; the bundled `Mouse_Injector.dll` (closed-source, no source available; the
emulator's own `source.tar.xz` is just the stock 1964 core). Its exports emulator's own `source.tar.xz` is just the stock 1964 core). Its exports
@@ -2839,7 +2844,7 @@ except `bg.c`'s `GE_D63` lines which are not `#ifdef PORT`-guarded).
## Playtest bug reports (M-49 cont., 2026-09-04) — logged for future triage, NOT v0.1.0 blockers ## Playtest bug reports (M-49 cont., 2026-09-04) — logged for future triage, NOT v0.1.0 blockers
User playtested the **v0.1.0 win64 bundle** (`C:\Users\james\Games\goldeneye-pc-port-0.1.0-win64`, User playtested the **v0.1.0 win64 bundle** (`<games>\goldeneye-pc-port-0.1.0-win64`,
fresh save). Bugs filed in findings §F (D191–D197), all alpha-acceptable. fresh save). Bugs filed in findings §F (D191–D197), all alpha-acceptable.
NB: Cradle does **not** crash — it's the D193 scripted-sequence stall (an NB: Cradle does **not** crash — it's the D193 scripted-sequence stall (an
earlier note here wrongly said "Cradle also crashes"). earlier note here wrongly said "Cradle also crashes").
@@ -3303,7 +3308,7 @@ collision geometry. Full ranked next-steps in findings.md D205.
6. Build + smoke after probe removal: `./build-pc.sh ntsc-final`, then 6. Build + smoke after probe removal: `./build-pc.sh ntsc-final`, then
`-level_09` 60 s crash-free with `GE_AUDIOTRACE=1`. `-level_09` 60 s crash-free with `GE_AUDIOTRACE=1`.
### Environment (unchanged except noted): ### Environment (unchanged except noted):
- Build: `/c/msys64/usr/bin/bash.exe -lc 'cd /c/Users/james/Source/Repos/gh-fullhist && ./build-pc.sh ntsc-final'` (login shell for MINGW64 PATH; SDL2.dll at `/c/msys64/mingw64/bin`). - Build: `/c/msys64/usr/bin/bash.exe -lc 'cd <repo> && ./build-pc.sh ntsc-final'` (login shell for MINGW64 PATH; SDL2.dll at `/c/msys64/mingw64/bin`).
- Capture run: `GE_AUDIOTRACE=1 GE_INPUTSCRIPT="$(cat ../scratchpad/exact_run3_script.txt)" ./ge007.x86_64.exe -level_09` from `build-pc/`; kill with `taskkill //F //PID <pid>` (msys `kill` does not work). - Capture run: `GE_AUDIOTRACE=1 GE_INPUTSCRIPT="$(cat ../scratchpad/exact_run3_script.txt)" ./ge007.x86_64.exe -level_09` from `build-pc/`; kill with `taskkill //F //PID <pid>` (msys `kill` does not work).
- `mupen64/` (N64 emulator, user's A/B reference) at repo root — added to .gitignore this session. - `mupen64/` (N64 emulator, user's A/B reference) at repo root — added to .gitignore this session.
- `scratchpad/` is gitignored: banktest/, exact_match.py, fire scripts, match outputs are all local-only. To re-verify the bank exoneration: `scratchpad/banktest/harness.exe` (rebuild from harness.c against port/src/romdata.c if stale) then `python scratchpad/banktest/diff_bank.py`. - `scratchpad/` is gitignored: banktest/, exact_match.py, fire scripts, match outputs are all local-only. To re-verify the bank exoneration: `scratchpad/banktest/harness.exe` (rebuild from harness.c against port/src/romdata.c if stale) then `python scratchpad/banktest/diff_bank.py`.
@@ -3391,7 +3396,7 @@ archived user capture: `scratchpad/d202-m66/` (`check_dump.py`,
The `[EXPIRE]` line and the guard/expire code STAY (permanent, documented). The `[EXPIRE]` line and the guard/expire code STAY (permanent, documented).
Build note for this shell: toolchain at `/c/msys64/mingw64/bin`, and Build note for this shell: toolchain at `/c/msys64/mingw64/bin`, and
`cmake --build -j` hit "Cannot create temporary file in C:\\Windows\\" — `cmake --build -j` hit "Cannot create temporary file in C:\\Windows\\" —
`ninja -j1` with `TMP/TEMP=C:/Users/james/AppData/Local/Temp` works. `ninja -j1` with a writable native `TMP/TEMP` works.
## Done this session (M-66) — D202 ROOT CAUSE ESTABLISHED: the audible stuck door loop is sound 203 behaving exactly as ROM + ground-truth code specify — faithful N64 behaviour (original quirk), not a port bug. Disposition A/B/C pending user decision. ## Done this session (M-66) — D202 ROOT CAUSE ESTABLISHED: the audible stuck door loop is sound 203 behaving exactly as ROM + ground-truth code specify — faithful N64 behaviour (original quirk), not a port bug. Disposition A/B/C pending user decision.
@@ -4765,7 +4770,7 @@ does not.
Build locally: `cmake -S . -B build-linux -G Ninja -DROMID=ntsc-final Build locally: `cmake -S . -B build-linux -G Ninja -DROMID=ntsc-final
-DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14` then `ninja -C -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14` then `ninja -C
build-linux` (~20s on drvfs). WSL `Ubuntu` distro provisioned (toolchain + build-linux` (~20s on drvfs). WSL `Ubuntu` distro provisioned (toolchain +
SDL2/zlib/GL dev); repo at `/mnt/c/Users/james/Source/Repos/gh-fullhist` SDL2/zlib/GL dev); repo at `<repo>`
(fixed the malformed `/etc/wsl.conf` `[automount]` + `wsl --shutdown`). (fixed the malformed `/etc/wsl.conf` `[automount]` + `wsl --shutdown`).
`data/` has the ROM + sidecars already. `data/` has the ROM + sidecars already.
@@ -5074,7 +5079,7 @@ playtest handoff: `docs/dev/M-35-PLAYTEST-CHECKLIST.md`; WI-3 design:
(`d43_emit.py` / `d69_emit.py` / `d88_emit.py --regen`, all pass clean) → `./build-pc.sh (`d43_emit.py` / `d69_emit.py` / `d88_emit.py --regen`, all pass clean) → `./build-pc.sh
ntsc-final` links 242/242, `-level_09` runs crash-free past frame 1200. **No `assets/*.bin` ntsc-final` links 242/242, `-level_09` runs crash-free past frame 1200. **No `assets/*.bin`
extraction needed** — the committed `assets/` is self-sufficient; the ROM is runtime-only. extraction needed** — the committed `assets/` is self-sufficient; the ROM is runtime-only.
The old repo at `C:/Users/james/Source/Repos/007` has the ROM + a prior build. The old repo at `<repos>/007` has the ROM + a prior build.
- **D168 — VERIFIED.** Rebuilt with the flip fix, captured `-level_09` frame 320: - **D168 — VERIFIED.** Rebuilt with the flip fix, captured `-level_09` frame 320:
the exit arrow points up, ammo "7|93" reads normally bottom-right — matches the exit arrow points up, ammo "7|93" reads normally bottom-right — matches
+112 -76
View File
@@ -50,7 +50,7 @@ and forth** through shared written artifacts, directed by one human.
| | | | | |
|---|---| |---|---|
| 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. | | 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 | | 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 | | 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) | | Base | fork of the [GoldenEye 007 decompilation](https://github.com/n64decomp/007) (years of prior work by Larry Ficken ("kholdfuzion") and contributors) |
@@ -59,58 +59,32 @@ and forth** through shared written artifacts, directed by one human.
The port is the GoldenEye-specific porting work **on top of** those two 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. 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 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 handled well-scoped work, Sonnet 5 took what it stalled on, and Opus 5 the
reserved for the bugs that needed the most reasoning held at once; the same rest; the same "escalate when stuck" move applied at every level.
"escalate when stuck" move applied at every level.
## Timeline ## Timeline
All dates from this repository's own commit history (August–September 2026). All dates from this repository's own commit history (August–September 2026).
```mermaid ```text
gantt # phase timeline (from the port's own commit dates)
dateFormat YYYY-MM-DD Aug 16 day 0 fork + PC-port scaffolding
axisFormat %b %d Aug 20 day 4 full compile + link (~230 TUs); boot to window
title GoldenEye 007 PC port; from fork to v0.3.0 Aug 22 day 6 first rendered frames; offline asset-conversion pipeline
section Local model - Qwen 3.8 via pi Aug 24 day 8 entire intro renders (logos -> gun-barrel -> cast)
Fork + PC-port scaffolding :m1, 2026-08-16, 1d Aug 27 day 11 Claude joins (handoff workflow begins)
Full compile + link (~230 TUs) :m2, 2026-08-20, 1d Aug 29 day 13 21 solo levels load + render + unattended no-crash; SDL input layer
Boot to window (ROM map + SDL2) :m3, 2026-08-20, 1d Aug 30 day 14 front-end flow (menu -> briefing -> start)
OS shims, threads, fast3d integration :2026-08-21, 2d Aug 31 day 14 file-backed EEPROM saves
First rendered frames :m4, 2026-08-22, 1d Sep 02 audio mixer (music + SFX)
Offline asset-conversion pipeline :2026-08-22, 2d Sep 14 Steam Deck hardening + F10 overlay QoL
Entire intro renders (logos to cast) :m5, 2026-08-24, 1d Sep 18 final fix sweep (D309-D321) + release review
section Both agents - handoff workflow Sep 20 day 35 v0.3.0 released: full campaign at Agent difficulty, audio
Claude joins :milestone, 2026-08-27, 0d complete, bundles for Windows, Linux and Steam Deck
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
section Month one - front end to v0.3.0
Audio mixer (music + SFX) :2026-09-02, 7d
Steam Deck hardening + F10 overlay QoL :2026-09-14, 3d
Final fix sweep (D309-D321) + release review :2026-09-18, 3d
v0.3.0 released (full campaign playtested) :milestone, 2026-09-20, 0d
``` ```
- **Day 0** (16 Aug): repository forked, PC-port scaffolding added. Roughly **two weeks**, one person part-time, took the decompilation from
- **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.
- **Day 35** (20 Sep): **v0.3.0 released** — the full campaign playtested
end to end at Agent difficulty, audio complete, bundles for Windows,
Linux and Steam Deck.
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 "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 – 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 20 Sep) took it from there to a public release: the 21-level sweep to
@@ -134,12 +108,15 @@ issues documented rather than outstanding.
### Commit velocity ### Commit velocity
```mermaid ```text
xychart-beta # commits per week (week start)
title "Commits per week (week start)" 8/16 ## 20
x-axis ["8/16", "8/23", "8/30", "9/6", "9/13", "9/20"] 8/23 ########### 109
y-axis "commits" 0 --> 250 8/30 #################### 203
bar [20, 109, 203, 141, 220, 6] 9/6 ############## 141
9/13 ###################### 220
9/20 # 6 (release day only)
(each # ~ 10 commits)
``` ```
Weekly totals (the final week is release day only). The step up from the Weekly totals (the final week is release day only). The step up from the
@@ -150,10 +127,11 @@ at once.
### Who did what ### Who did what
```mermaid ```text
pie showData title "Commits by agent (raw count, snapshot through early September)" # commits by agent (raw count, snapshot through early September)
"Claude" : 161 Claude ################ 161 (72%)
"Local model (Qwen 3.8 / pi)" : 62 Local model (Qwen 3.8/pi) ###### 62 (28%)
(each # ~ 10 commits)
``` ```
(Phase A, the first 26 commits, to 24 Aug, was entirely the local model, (Phase A, the first 26 commits, to 24 Aug, was entirely the local model,
@@ -184,10 +162,11 @@ chain in relatively few, large commits, and the local model kept contributing
raw commits; the foundation is a heavier third of the project than its commit raw commits; the foundation is a heavier third of the project than its commit
share suggests; the developer's estimate is roughly: share suggests; the developer's estimate is roughly:
```mermaid ```text
pie showData title "Agent effort, milestone-weighted" # agent effort, milestone-weighted
"Claude" : 60 Claude ############ 60%
"Local model (Qwen 3.8 / pi)" : 40 Local model (Qwen 3.8/pi) ######## 40%
(each # ~ 5%)
``` ```
The local model's ~40% is front-loaded and foundational (the build, the boot The local model's ~40% is front-loaded and foundational (the build, the boot
@@ -201,21 +180,19 @@ result worth taking away.
This is the part worth paying attention to. This is the part worth paying attention to.
```mermaid ```text
flowchart TD # the handoff workflow
H["Human: direction, integration,<br/>build + playtest verification"]
C["Claude / Claude Code<br/>frontier, hosted"]
Q["Qwen 3.8 via pi<br/>open-weight, local RTX 5090"]
D[(Shared artifacts:<br/>HANDOFF.md · findings.md · porting-notes.md)]
H -->|scopes task, budget, files| C HUMAN
H -->|scopes task, budget, files| Q direction, integration, build + playtest verification
C <-->|reads / appends| D | scopes task, budget, files | scopes task, budget, files
Q <-->|reads / appends| D v v
C -.->|usage limit reached| Q CLAUDE (hosted) QWEN 3.8 / pi (local, RTX 5090)
Q -.->|hard structural bug| C | patch + write-up | patch + write-up
C -->|patch + write-up| H +--------------+ +--------------+
Q -->|patch + write-up| H v v
SHARED ARTIFACTS (both agents read + append)
HANDOFF.md . findings.md . porting-notes.md
``` ```
Both agents worked against the **same three written artifacts**, which is what Both agents worked against the **same three written artifacts**, which is what
@@ -255,8 +232,7 @@ Honest notes, for anyone weighing whether this transfers.
- 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 chain, OS shims, and the offline-converter architecture. None of it was
rewritten later. A 27B open model on one consumer GPU was genuinely productive rewritten later.
on this.
- **The written-artifact discipline made agent output compound.** An agent - **The written-artifact discipline made agent output compound.** An agent
starting cold late in the project was more effective than one early on, 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 because the accumulated notes were good. This is also what made the two
@@ -286,3 +262,63 @@ Honest notes, for anyone weighing whether this transfers.
**Still outstanding:** see **Still outstanding:** see
[`docs/ROADMAP.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/ROADMAP.md), [`docs/ROADMAP.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/ROADMAP.md),
the single tracker for open work. 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). Its
code output is still owed a code-quality audit in this doc.
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.
**Measurement owed.** Which of these was best on a given task — and each
model's rough cost-to-performance ratio — has not yet been measured properly;
attributing individual commits to the model that produced them is still a
manual pass. That includes a **code-quality audit of the GPT-6 (Sol) trial**.
All of it is pending and will be added here when it lands.
A concrete reason the commit-date route is unreliable: the `D350` commit
(`1303c0eb`, "expose watch-backed Bond settings in PC options and F10")
carries a **27 Sep** timestamp on both its author and committer dates, but
the session notes place that work on **4 Oct**. Because the two git dates
agree, this is **not** a rebase artifact (a rebase would have moved the
committer date while preserving the author date); it is a genuine divergence
between the recorded commit time and the working session, and resolving it
takes the session log, not the commit date. This is the class of edge case
the attribution pass has to walk by hand.
+12
View File
@@ -380,4 +380,16 @@ D517,FIXED,Holding left/right on a Bond settings row dropped the fps:,FIXED (liv
D518,FIXED,Pad layout preset was one global value:,FIXED (live pass 2026-10-04 incl. 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) 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) 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-07) —
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-07):"
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-07 (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-07) —
D531,FIXED,capture_p7.sh's D529 adaptation was incomplete —,"FIXED 2026-10-07 (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-07 (tooling-only:
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…" 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…"
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
380 D518
381 D519
382 D520
383 D521
384 D522
385 D523
386 D524
387 D525
388 D526
389 D527
390 D528
391 D529
392 D530
393 D531
394 D532
395 D450
+181 -4
View File
@@ -788,6 +788,18 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D518 | **Pad layout preset was one global value: setting another player's preset changed the main player's mapping too** — Port-only; D498 follow-up, maintainer report; full `## D518` entry at file tail | FIXED (live pass 2026-10-04 incl. split-screen 2P) | | D518 | **Pad layout preset was one global value: setting another player's preset changed the main player's mapping too** — Port-only; D498 follow-up, maintainer report; full `## D518` entry at file tail | FIXED (live pass 2026-10-04 incl. split-screen 2P) |
| D519 | **One PC options UI: the file-select "PC Options" entry opens the F10 overlay; the MENU_PC_OPTIONS screen is deleted** — Port-only (the one front.c entry call is kept); Bond-file rows + watch chooser re-keyed to file select; 4 probes retired, `GE_FILESELECT_OVERLAY` added; full `## D519` entry at the end of this file. | FIXED (headless-verified 2026-10-04; by-eye pass owed) | | D519 | **One PC options UI: the file-select "PC Options" entry opens the F10 overlay; the MENU_PC_OPTIONS screen is deleted** — Port-only (the one front.c entry call is kept); Bond-file rows + watch chooser re-keyed to file select; 4 probes retired, `GE_FILESELECT_OVERLAY` added; full `## D519` entry at the end of this file. | FIXED (headless-verified 2026-10-04; by-eye pass owed) |
| D520 | **F10 overlay slider wedge, slider marker and dropdown arrow antialiased (they stepped in whole canvas pixels that grow with the window)** — Port-only; D504 follow-up, P4k investigation; full `## D520` entry at file tail | FIXED (headless before/after capture; by-eye owed) | | D520 | **F10 overlay slider wedge, slider marker and dropdown arrow antialiased (they stepped in whole canvas pixels that grow with the window)** — Port-only; D504 follow-up, P4k investigation; full `## D520` entry at file tail | FIXED (headless before/after capture; by-eye owed) |
| D521 | **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-conditional** — Port-only (docs + verify.sh probe); D117 class, P7; full `## D521` entry at the end of this file | PARTIAL (rule recorded 2026-10-04; the two residual bands CLOSED 2026-10-05 by D525's box re-round — save state, not a renderer; cross-platform parity stays informational, the gate is per platform) |
| D522 | **The golden window's frame-900 stem is not settled gameplay on several levels — the win golden gate is not green (5/21 REGRESSION honest sequential, 7/21 concurrent)** — Golden gate; D117 follow-up; full `## D522` entry at file tail | FIXED 2026-10-05 (recipe-side re-base: per-level windows settled on every level; win gate 21/21 PASS honest sequential; no tol limit raised) |
| D523 | **The golden recipe does not pin save state: `verify.sh` pins the ini but never touches `data/ge007.eep`, and the goldens were captured with a save present** — Golden gate; D514/D522 follow-up; full `## D523` entry at file tail | SUPERSEDED by D529 (2026-10-07) — the frame depends on the save's *content*, not its presence (the presence claim was only ever measured on Archives); the gate now installs the canonical in-tree save `tools_pc/golden/ge007.eep` |
| D524 | **The golden tooling was destructive and drift-prone: `verify.sh`'s per-level ini/save pin snapshotted the previous level's game-written file (a sweep overwrites the user's own `data/ge007.ini`), and `capture_p7.sh` hardcoded the pre-D522 windows while deleting the save it captures with** — Golden tooling (U5 review of `tools_pc/**`); D522/D523 follow-up; full `## D524` entry at file tail | FIXED 2026-10-05 (both pins idempotent — first call snapshots the user's files, every level re-pins from that snapshot; `capture_p7.sh` reads the recipe out of `verify.sh` and requires the save present) |
| D525 | **The whole `<level>/linux/` golden set was stale, not just on the six re-based levels — re-rounding it closes D521's two residual render bands** — Golden gate (P7b, box); D521/D522/D523 follow-up; full `## D525` entry at file tail | 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 | **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-state diagnostic** — P13 Dam ending (B1 lead); full `## D526` entry at file tail | CLOSED — faithful, confirmed on 1964/GEPD (2026-10-07): the show-through is in both (port == N64), so no port bug, no fix (D306/D291 class); the per-triangle census probe (GE_D526/GE_D526BOX/GE_D526MAX) is retained as a generic diagnostic |
| D527 | **`GE_PCDUMP` written `first:last` (a colon where the dash belongs) silently dumps EVERY frame** — harness trap (`port/src/video.c`); full `## D527` entry at file tail | OPEN (harness documentation; a log-or-fail-closed fix is owed in the port layer) |
| D528 | **`GE_STARTMENU` skips the EEPROM read, so front-end-only sessions never import the emulator save** — harness trap; D514 follow-up; full `## D528` entry at file tail | 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 | **The golden gate depends on the save file's CONTENT — the D523 "presence, not which save" claim is refuted; the gate must pin a canonical save** — Golden gate; D523 follow-up (D525/D524); full `## D529` entry at file tail | FIXED 2026-10-07 (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 — three consecutive clean 21/21 sweeps are the valid green record; the no-arg `verify.sh` path now exits 1; the box linux re-round under it landed as D530) |
| D530 | **The box (X220) linux golden re-round landed — first cross-machine test of the four D117 flaky levels** — Golden gate (P7, box); D529 never-do 3/6 follow-up; full `## D530` entry at file tail | AMENDED BY D531 (2026-10-07) — the "canonical eep" provenance claim was wrong: the re-round ran under the box-LOCAL save (the `capture_p7.sh` per-level re-pin bug); the D531 re-capture under the true canonical eep supersedes this set |
| D531 | **`capture_p7.sh`'s D529 adaptation was incomplete — the per-level re-pin restored the local save, so the 2026-10-05 box re-round ran under the box-local save; fixed + re-captured under the canonical eep** — Golden gate (P7, box); D529/D530 follow-up, P12 U5 review finding; full `## D531` entry at file tail | FIXED 2026-10-07 (loop re-pins the canonical save per level + `cd "$ROOT"`; fresh 21/21 box re-capture committed — 47/63 frames differ from the box-local-save set; pixel-gated box verify owed for a window with the box's WSLg GPU display active — today's session is on the software fallback, black read-backs) |
| D532 | **The three P12 U5 should-fixes applied — the gate's ini pin now fails closed, the seed is hard-pinned (stray `GE_RSEED` warns), and `audiodebug.ps1`'s dead A/B switches are gone** — golden gate + tooling; D531/U5 follow-up; full `## D532` entry at file tail | FIXED 2026-10-07 (tooling-only: `pin_ini_640x480` fails closed like `pin_eep`; `GOLDEN_SEED` literal + warning, capture `eval` unaffected; `-AB`/`-Old`/`-OldMode` removed with a note) |
| D407 | **Tanks cannot be boarded/exited on PC — the v0.4.0 use/reload split (D378/D393) removed the B-button tap the engine's tank handlers in `bondview2.c` consume (user report 2026-09-28, Runway/Streets)** — full `## D407` entry at file tail | PARTIAL (fix landed in v0.4.0, port-only): present `GE_CONT_B` on the use (E / pad A) rising edge only while `g_PlayerIsInTank == 1` or `g_BondCanEnterTank != 0`; E keeps its D378 no-reload-fallback semantics elsewhere, N64 layout unaffected. D407(b) same cycle: front PC Options page-edge highlight clamp + mouse-wheel / W-S paging (wheel queue consumed on the menu, D223); its wheel mapping shipped inverted and was fixed to match W/S (wheel up = step up). D407(c): board-animation lockout, menu-accept B gate in tank states, in-tank aim routed through the legacy velocity stick + `Input.TankAimScale` knob. RESOLVED 2026-09-28: all port-only (src/game zero-diff), 10 TANKDBG probes stripped, release binary verified clean, user live tank drive signed off (board + exit OK, aim feel good). | | D407 | **Tanks cannot be boarded/exited on PC — the v0.4.0 use/reload split (D378/D393) removed the B-button tap the engine's tank handlers in `bondview2.c` consume (user report 2026-09-28, Runway/Streets)** — full `## D407` entry at file tail | PARTIAL (fix landed in v0.4.0, port-only): present `GE_CONT_B` on the use (E / pad A) rising edge only while `g_PlayerIsInTank == 1` or `g_BondCanEnterTank != 0`; E keeps its D378 no-reload-fallback semantics elsewhere, N64 layout unaffected. D407(b) same cycle: front PC Options page-edge highlight clamp + mouse-wheel / W-S paging (wheel queue consumed on the menu, D223); its wheel mapping shipped inverted and was fixed to match W/S (wheel up = step up). D407(c): board-animation lockout, menu-accept B gate in tank states, in-tank aim routed through the legacy velocity stick + `Input.TankAimScale` knob. RESOLVED 2026-09-28: all port-only (src/game zero-diff), 10 TANKDBG probes stripped, release binary verified clean, user live tank drive signed off (board + exit OK, aim feel good). |
| D408 | **`Game.SkipIntro` skips the post-mission failure dossier: with it on, entering a level then aborting (watch Z+A) or dying (KIA) returns straight to the menus — no REPORT / "Mission status: KILLED IN ACTION / ABORTED" screen** (user report 2026-09-28; save/AllUnlocked ruled out) — full `## D408` entry at file tail | OPEN (cosmetic, not root-caused past the handoff site). v0.4.0 decision: SkipIntro stays EXPERIMENTAL / not recommended for regular users — F10 row relabelled "Skip intro (EXPERIMENTAL)". | | D408 | **`Game.SkipIntro` skips the post-mission failure dossier: with it on, entering a level then aborting (watch Z+A) or dying (KIA) returns straight to the menus — no REPORT / "Mission status: KILLED IN ACTION / ABORTED" screen** (user report 2026-09-28; save/AllUnlocked ruled out) — full `## D408` entry at file tail | OPEN (cosmetic, not root-caused past the handoff site). v0.4.0 decision: SkipIntro stays EXPERIMENTAL / not recommended for regular users — F10 row relabelled "Skip intro (EXPERIMENTAL)". |
| D450 | **Unplugging and replugging a controller was not picked up (user, GameSir-G7 Pro; 2026-09-29).** — full `## D450` entry at file tail | 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 `inputRescanPads` never ran. Both pumps now post a request; the rescan runs at the top of `inputUpdate`, on the thread that reads the pads. | | D450 | **Unplugging and replugging a controller was not picked up (user, GameSir-G7 Pro; 2026-09-29).** — full `## D450` entry at file tail | 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 `inputRescanPads` never ran. Both pumps now post a request; the rescan runs at the top of `inputUpdate`, on the thread that reads the pads. |
@@ -5998,7 +6010,7 @@ probe scripts left.
prints `ALL CHECKS PASSED`, and `-level_09` / `-level_20` then segfault before prints `ALL CHECKS PASSED`, and `-level_09` / `-level_20` then segfault before
frame 1 (`EXCEPTION: 0xc0000005`, faulting return address is an ASCII frame 1 (`EXCEPTION: 0xc0000005`, faulting return address is an ASCII
model-name string). The pre-migration working sidecar model-name string). The pre-migration working sidecar
(`C:/Users/james/Source/Repos/007/data/pccg-ntsc-final/pccg.bin`, 3604378 B, (`<repos>/007/data/pccg-ntsc-final/pccg.bin`, 3604378 B,
2421-B manifest) drops into this tree and `-level_09` runs crash-free. 2421-B manifest) drops into this tree and `-level_09` runs crash-free.
**Isolation done (M-38).** **Isolation done (M-38).**
@@ -12871,7 +12883,7 @@ Verified: build clean (`build-pc`, MSYS2 MINGW64/ninja); `tools_pc/verify.sh bun
## D317 — Crash when toggling Game.AllUnlocked back and forth in-game (user report, v0.3.0 playtest build, 2026-09-18) — no dump left behind. ## D317 — Crash when toggling Game.AllUnlocked back and forth in-game (user report, v0.3.0 playtest build, 2026-09-18) — no dump left behind.
**Reported:** "Toggling unlock all back and forth in game causes a crash." Reproduced by the user on the current-tip playtest exe (the one at `C:\Users\james\Desktop\ge007-v0.3.0-playtest`, includes D310/D311 fixes). **Repro detail (user, 2026-09-18):** level = **Runway**. **No crash dump or log was left in that folder** — hard exit, so Windows Event Viewer is the only system-side artifact to check. **Reported:** "Toggling unlock all back and forth in game causes a crash." Reproduced by the user on the current-tip playtest exe (the Desktop `ge007-v0.3.0-playtest` folder, includes D310/D311 fixes). **Repro detail (user, 2026-09-18):** level = **Runway**. **No crash dump or log was left in that folder** — hard exit, so Windows Event Viewer is the only system-side artifact to check.
**Closed as a symptom of D316, not a real crash.** Evidence: **Closed as a symptom of D316, not a real crash.** Evidence:
- No WER report exists for the playtest exe; every `ge007` AppCrash in Windows Error Reporting traces to this repo's own `build-pc` dev runs, none after 12:00 that day, while the playtest session was ~13:15 — a real crash would have logged one. - No WER report exists for the playtest exe; every `ge007` AppCrash in Windows Error Reporting traces to this repo's own `build-pc` dev runs, none after 12:00 that day, while the playtest session was ~13:15 — a real crash would have logged one.
@@ -15453,7 +15465,7 @@ I logged all of this as **D397** (`docs/dev/findings.md`, index CSV, and the D18
**Status (2026-09-28, per `scratch/d397_fs/HANDOFF-D397.md`) — OPEN, re-scoped; no fix landed.** An in-tree CI→I import-routing block in `gfx_pc.cpp` `import_texture` was tried and is **ineffective** (the D116 stale-tile-metadata hypothesis is REFUTED: D161 already routes `fmt==CI && palette==G_TT_NONE` to `import_texture_i8`; the GE_TEXI logs show `palfmt=0` on all 299 rows) — the block was removed 2026-09-28. Established: the data path is 100% clean (299 row strips upload byte-exact vs `scratch/d397_fs/gunbarrel.pgm`); the artifact is static; MSAA, mips, RAND_NOISE, 5-bit quant and texture-cache aliasing are ruled out. Leading hypotheses: **H1** — the file-select custom-CC LERP (`gDPSetCombineLERP(TEXEL0, PRIMITIVE, ENV_ALPHA, ...)`) uses ENV_ALPHA 0x14 as the mix factor; the port feeds it as `20/255 ≈ 0.078` (`G_CCMUX_ENV_ALPHA` input, `gfx_pc.cpp`), so a black texel renders ≈0.92×prim ≈ the measured 27/28/17 black-region census — the N64 RDP 5-bit coefficient scaling (check `rsp/graphics/gmain.s` and the PD port's CC engine for the same combine id) is the root-cause question; **H2** — the 6-of-9-row comb is a second, separate row-strip-geometry defect (non-integer quad y + NEAREST, or the 256-tri vertex-ring wrap between the two tris of a row); **H3** — part of the darkened look is authentic N64 (G_TC_FILT 4-bit intensity dither, unimplemented here). A `GE_D397SH` env-gated shader-formula probe (in `gfx_opengl.cpp`, ~30 lines) stays in-tree until the root cause lands, then is removed. Next steps per the handoff: Q1 correlate which of the 20 compiled shaders is the gun-barrel row-strip one, Q4 settle the LERP-factor scaling against gmain.s/PD, Q5 native 640×480 FBO capture to re-measure the comb period. **Status (2026-09-28, per `scratch/d397_fs/HANDOFF-D397.md`) — OPEN, re-scoped; no fix landed.** An in-tree CI→I import-routing block in `gfx_pc.cpp` `import_texture` was tried and is **ineffective** (the D116 stale-tile-metadata hypothesis is REFUTED: D161 already routes `fmt==CI && palette==G_TT_NONE` to `import_texture_i8`; the GE_TEXI logs show `palfmt=0` on all 299 rows) — the block was removed 2026-09-28. Established: the data path is 100% clean (299 row strips upload byte-exact vs `scratch/d397_fs/gunbarrel.pgm`); the artifact is static; MSAA, mips, RAND_NOISE, 5-bit quant and texture-cache aliasing are ruled out. Leading hypotheses: **H1** — the file-select custom-CC LERP (`gDPSetCombineLERP(TEXEL0, PRIMITIVE, ENV_ALPHA, ...)`) uses ENV_ALPHA 0x14 as the mix factor; the port feeds it as `20/255 ≈ 0.078` (`G_CCMUX_ENV_ALPHA` input, `gfx_pc.cpp`), so a black texel renders ≈0.92×prim ≈ the measured 27/28/17 black-region census — the N64 RDP 5-bit coefficient scaling (check `rsp/graphics/gmain.s` and the PD port's CC engine for the same combine id) is the root-cause question; **H2** — the 6-of-9-row comb is a second, separate row-strip-geometry defect (non-integer quad y + NEAREST, or the 256-tri vertex-ring wrap between the two tris of a row); **H3** — part of the darkened look is authentic N64 (G_TC_FILT 4-bit intensity dither, unimplemented here). A `GE_D397SH` env-gated shader-formula probe (in `gfx_opengl.cpp`, ~30 lines) stays in-tree until the root cause lands, then is removed. Next steps per the handoff: Q1 correlate which of the 20 compiled shaders is the gun-barrel row-strip one, Q4 settle the LERP-factor scaling against gmain.s/PD, Q5 native 640×480 FBO capture to re-measure the comb period.
**Status (2026-09-27b, static read against the local `pd_port` checkout) — H1 REFUTED, H2 promoted to leading cause.** Cross-checked `G_CCMUX_ENV_ALPHA` handling against `C:\Users\james\Source\Repos\pd_port\port\fast3d\gfx_pc.cpp`: byte-identical formula (`tmp.r=tmp.g=tmp.b=rdp.env_color.a`, then `/255.0f` at the vertex-color pack site) — no special-cased coarser scaling in either port. There is no "5-bit RDP coefficient" quirk in the real color combiner; PRIM/ENV alpha are genuine 8-bit `gDPSetEnvColor` fields and the combiner's A/B/C/D math is full 8-bit on real hardware (the 5-bit-precision idea conflated the CC with an unrelated hardware unit). **H1 is very likely not the (or not the only) cause; the uniform darkening it predicts may simply be faithful/authored** — do not correct it without an N64/1964-GEPD reference frame. **Status (2026-09-27b, static read against the local `pd_port` checkout) — H1 REFUTED, H2 promoted to leading cause.** Cross-checked `G_CCMUX_ENV_ALPHA` handling against `<repos>\pd_port\port\fast3d\gfx_pc.cpp`: byte-identical formula (`tmp.r=tmp.g=tmp.b=rdp.env_color.a`, then `/255.0f` at the vertex-color pack site) — no special-cased coarser scaling in either port. There is no "5-bit RDP coefficient" quirk in the real color combiner; PRIM/ENV alpha are genuine 8-bit `gDPSetEnvColor` fields and the combiner's A/B/C/D math is full 8-bit on real hardware (the 5-bit-precision idea conflated the CC with an unrelated hardware unit). **H1 is very likely not the (or not the only) cause; the uniform darkening it predicts may simply be faithful/authored** — do not correct it without an N64/1964-GEPD reference frame.
Found a concrete geometric mechanism for **H2**, promoted to leading cause: `titleRenderFolderMenuBackgroundLines` (`src/game/title2.c:24`) emits `lry - uly == 3` (U10.2 units) per row — a **0.75-native-pixel-tall** rect, not 1.0 — via the universal N64 `((y+1)<<2)-1` idiom. That idiom is only exact on real hardware because the RDP's scan converter *floors* the fixed-point Y to an integer scanline before testing coverage. `gfx_draw_rectangle` (`gfx_pc.cpp:3443`) does not replicate that: it maps the U10.2 corners straight to continuous NDC floats and rasterizes with the GPU's ordinary continuous coverage rules. A sub-1-native-pixel-tall rect stacked 299 times (each offset exactly 1.0 native px from the last) does not reliably rasterize as "the one scanline the idiom intended" under continuous rasterization — which of the two neighbouring device rows a given source row's fractional edges land in drifts as the stack accumulates, worsened by any non-integer native→window scale (`gfx_adjust_viewport_or_scissor`'s `ratioY`). This is a rasterization property of the geometry, not stale state, so it naturally explains the first-render (non-re-entry) trigger that refuted D182's hypothesis for this instance. Full mechanism + tell for future occurrences: `docs/porting-notes.md` §D15. **Not yet done: a live capture to confirm the comb period tracks `ratioY`** (predicted: comb period should change if the window/render resolution changes, at fixed native resolution) — this is the concrete falsifiable test before attempting a fix. No code changed this pass (diagnosis only). Found a concrete geometric mechanism for **H2**, promoted to leading cause: `titleRenderFolderMenuBackgroundLines` (`src/game/title2.c:24`) emits `lry - uly == 3` (U10.2 units) per row — a **0.75-native-pixel-tall** rect, not 1.0 — via the universal N64 `((y+1)<<2)-1` idiom. That idiom is only exact on real hardware because the RDP's scan converter *floors* the fixed-point Y to an integer scanline before testing coverage. `gfx_draw_rectangle` (`gfx_pc.cpp:3443`) does not replicate that: it maps the U10.2 corners straight to continuous NDC floats and rasterizes with the GPU's ordinary continuous coverage rules. A sub-1-native-pixel-tall rect stacked 299 times (each offset exactly 1.0 native px from the last) does not reliably rasterize as "the one scanline the idiom intended" under continuous rasterization — which of the two neighbouring device rows a given source row's fractional edges land in drifts as the stack accumulates, worsened by any non-integer native→window scale (`gfx_adjust_viewport_or_scissor`'s `ratioY`). This is a rasterization property of the geometry, not stale state, so it naturally explains the first-render (non-re-entry) trigger that refuted D182's hypothesis for this instance. Full mechanism + tell for future occurrences: `docs/porting-notes.md` §D15. **Not yet done: a live capture to confirm the comb period tracks `ratioY`** (predicted: comb period should change if the window/render resolution changes, at fixed native resolution) — this is the concrete falsifiable test before attempting a fix. No code changed this pass (diagnosis only).
@@ -16524,7 +16536,7 @@ Whichever loop runs `SDL_PollEvent` first gets the event, and the render loop us
**Maintainer live-pad checklist.** (a) Fresh ini, no edits: Jinx 1.1 plays exactly as before (RT fire, LT/LB aim, A use, X reload, B gadget, Y next weapon, stick clicks crouch, Start pause; menus A/X accept, B/Y cancel). (b) F10 (Back) -> Input -> Controller...: Layout preset right -> Custom; seat/action rows appear. (c) Select Fire, press A: "Press button..." (the A press does not bind itself); press LB -> Primary: LB; fire with LB in a level, RT no longer fires. (d) Capture then tap B: cancelled, row unchanged, page does not close. Capture then hold B ~1 s: binds B. (e) Capture then tap Back: cancel; hold Back ~1 s: slot cleared. Esc and Delete work too. (f) Y on a row clears the slot without a modal. Start unbound -> still pauses (guard). (g) Reset to defaults restores Jinx 1.1 strings on all seats. (h) Controller-only: do (b)-(f) without touching the keyboard; never trapped. (i) 2P split-screen with two pads: set seat Pad 2 to a different Fire; confirm P1 and P2 differ; Pad 2 Y/B in menus still accept/cancel. With keyboard/mouse as P1 (one pad, MPMode Auto) the single pad uses the seat 0 table. (j) Unplug/replug a pad mid-match: bindings stay with the seat. (k) PrevWeapon (unbound by default): bind it and confirm it steps weapons backwards (untested live; reuses the wheel-back A+Z idiom). (l) Front-end PC Options -> Input -> Controller: same page; A starts capture, hold Back clears. (m) Quit and relaunch: the ini keeps `PadPreset` and the `Input.Pad*.` strings. **Maintainer live-pad checklist.** (a) Fresh ini, no edits: Jinx 1.1 plays exactly as before (RT fire, LT/LB aim, A use, X reload, B gadget, Y next weapon, stick clicks crouch, Start pause; menus A/X accept, B/Y cancel). (b) F10 (Back) -> Input -> Controller...: Layout preset right -> Custom; seat/action rows appear. (c) Select Fire, press A: "Press button..." (the A press does not bind itself); press LB -> Primary: LB; fire with LB in a level, RT no longer fires. (d) Capture then tap B: cancelled, row unchanged, page does not close. Capture then hold B ~1 s: binds B. (e) Capture then tap Back: cancel; hold Back ~1 s: slot cleared. Esc and Delete work too. (f) Y on a row clears the slot without a modal. Start unbound -> still pauses (guard). (g) Reset to defaults restores Jinx 1.1 strings on all seats. (h) Controller-only: do (b)-(f) without touching the keyboard; never trapped. (i) 2P split-screen with two pads: set seat Pad 2 to a different Fire; confirm P1 and P2 differ; Pad 2 Y/B in menus still accept/cancel. With keyboard/mouse as P1 (one pad, MPMode Auto) the single pad uses the seat 0 table. (j) Unplug/replug a pad mid-match: bindings stay with the seat. (k) PrevWeapon (unbound by default): bind it and confirm it steps weapons backwards (untested live; reuses the wheel-back A+Z idiom). (l) Front-end PC Options -> Input -> Controller: same page; A starts capture, hold Back clears. (m) Quit and relaunch: the ini keeps `PadPreset` and the `Input.Pad*.` strings.
**Credit.** The two-names-per-action SDL string format, the menu lockout guard idea and release-before-capture arming follow outside PR #109 (italoarruda); the commits carry `Co-authored-by: italoarruda`. **Credit.** The two-names-per-action SDL string format, the menu lockout guard idea and release-before-capture arming follow outside PR #109 (italoarruda); the commits carry a `Co-authored-by: italoarruda` trailer.
**Confidence.** High: preset 0 byte-equivalence (exhaustive over the mapping function), build, boot/level/overlay smoke, page render. Medium: pad-driven capture timing and the front-end page behaviour (no physical pad here). Low: PrevWeapon feel. **Confidence.** High: preset 0 byte-equivalence (exhaustive over the mapping function), build, boot/level/overlay smoke, page render. Medium: pad-driven capture timing and the front-end page behaviour (no physical pad here). Low: PrevWeapon feel.
@@ -17114,3 +17126,168 @@ keep: the next `./build-pc.sh` run reconfigures in place and picks up x86_64.
**Fix (`port/src/optionsoverlay.c`).** `drawWedge`: the solid body as runs of equal whole height plus one coverage-alpha top pixel per 1-px column; the marker and the dropdown arrow via `drawTriDownAA` (per-pixel area coverage from 4 sub-rows). Render state is set once per shape (`aaBegin`) and each piece is 2 commands (`aaPiece`, pieces under alpha 6 skipped), so the display-list size stays about where it was (D516 overflow history; `OV_BUF_CMDS` 16384). Applies in every Display mode (maintainer: the overlay is port UI, not part of the N64 picture). **Fix (`port/src/optionsoverlay.c`).** `drawWedge`: the solid body as runs of equal whole height plus one coverage-alpha top pixel per 1-px column; the marker and the dropdown arrow via `drawTriDownAA` (per-pixel area coverage from 4 sub-rows). Render state is set once per shape (`aaBegin`) and each piece is 2 commands (`aaPiece`, pieces under alpha 6 skipped), so the display-list size stays about where it was (D516 overflow history; `OV_BUF_CMDS` 16384). Applies in every Display mode (maintainer: the overlay is port UI, not part of the N64 picture).
**Verified.** Build rc=0, no warnings; `GE_OPTIONSOVERLAY=1 GE_OPTIONSOVERLAY_SECTION=__HdrAudio GE_PCDUMP=300-300` before/after: the ramps go from ~7 visible steps to a smooth ramp, markers/arrows softened; no DL overflow logged. **Owed:** maintainer by-eye at their window size. **Verified.** Build rc=0, no warnings; `GE_OPTIONSOVERLAY=1 GE_OPTIONSOVERLAY_SECTION=__HdrAudio GE_PCDUMP=300-300` before/after: the ramps go from ~7 visible steps to a smooth ramp, markers/arrows softened; no DL overflow logged. **Owed:** maintainer by-eye at their window size.
## D521: A fixed-frame golden window that overlaps the intro flyby is platform-nondeterministic (P7, cross-platform goldens)
**Problem (P7, 2026-10-04, Linux golden baselines).** The golden recipe was run verbatim on a real-GL Linux box (Mesa 23.0.3 / Intel HD 3000, X, 640x480 ini pin, `GE_INPUTSCRIPT="20:START"`, `GE_PCDUMP="900-1500:300"`, `GE_QUITFRAME=1502`) for all 21 levels and compared against `tools_pc/golden/<level>/win/`. At the settled stems (1200/1500; Cuba 600/900) the cross-platform delta is 1.142-6.958% of pixels over tol 2 -- worst non-outlier dam 1500 6.958%, worst grid-cell dmean 14.315 (surface1 1200), phash <= 3 -- but Dam's frame 900 measures 92.863% over tol 2 with phash 83, a completely different scene. The same-machine spread is what makes that number meaningful: repeat Linux runs of the same recipe differ by 0.115% (1200) and 0.219% (1500) at those stems (cap_1 vs cap_3, exact tol 2) but by 72.460% at 900, i.e. the 900 disagreement is the flyby still moving, not a renderer.
**Cause (D117 class, no new nondeterminism).** The intro flyby is wall-clock paced, and the capture harness steps *sim* frames, not wall-clock frames: `GE_PCDUMP` and `GE_QUITFRAME` both key off `frames` (`port/src/video.c:1406`, `++frames` in `videoEndFrame()`), which advances once per `portRenderGfxTask()` (`port/src/libultra.c:1256`), i.e. once per retrace actually consumed by `__scMain`. Retrace messages are dropped at the posting site when the game's queue is nearly full (`port/src/libultra.c:753`), so a box that cannot hold 60 fps stretches wall time *without* advancing `frames` (per-frame render times in cap_dam.log: frame 900 13009 us, 1200 15121 us, 1500 11808 us). On this box the flyby was still running at frame 900 -- it settles between frame ~830 and ~1130 run to run -- while the Windows golden run had it settled before 900. The scripted START press is not the difference: the pulse's six `GE_INPUTLOG` lines precede "frame 5 rendered" on both platforms, and `GE_INPUTSCRIPT_MS=1` variants (150/300/600 ms) change nothing.
**Rule (this is a documentation finding, not a code fix).** A cross-platform golden comparison is only meaningful at stems that are settled gameplay on *every* platform in the set. Either move the window past the slowest platform's flyby settle point, or report in-flyby stems as a scene offset and never gate on them. `GE_DETERM=1` is the right tool for Linux-vs-Linux regression runs only: it makes retrace generation call-sequenced (det_a vs det_b, exact tol 2: 4.654/5.562/5.331% at 900/1200/1500) but changes the frame -> sim mapping, so its 14.6-16.2% against the win/ goldens (det_a vs dam/win: 15.788/14.594/16.171%) is expected and must never be used for cross-platform comparison.
**Port-layer change (same sweep).** `tools_pc/verify.sh`'s linux pixcount/framediff skip (N6 / M-48, "GE_PCDUMP reads black") is a property of the GL *driver*, not of the platform. It is now driver-conditional: a `glxinfo -B` renderer probe sets `LINUX_SOFTWARE_GL`, and only llvmpipe/softpipe/swrast/dynrecomp skip the pixel gate; a real driver (Mesa iGPU/dGPU, NVIDIA) runs the same gate as Windows. `glxinfo` missing or unrecognised keeps the old conservative skip, so an unknown box behaves exactly as before. **Known limitation:** the probe whitelists by *blacklist* (`llvmpipe|softpipe|swrast|dynrecomp`), so a future software driver outside that list would enable the pixel gate and false-FAIL; the probe is not rewritten here.
**Verified (P7 sweep, 21 levels, one pass).** Structural framediff (cell means / coverage / phash) linux-vs-win: rc=0 on 19/21 levels; rc=1 on Dam (frame 900, the flyby) and Streets (frame 1200). Exact `--tol 2` over-tol2 %: 1.142-6.618% on the 17 levels with no outlier stem (bunker2 1.142-1.144%, facility 1.396-1.842%, surface1 1.494-2.779%, bunker1 1.710-2.341%, train 1.573-2.160%, control 2.125-2.688%; highest non-outlier surface2 5.592-6.170%, jungle 5.858-6.618%). Outliers, each classified: **Archives** 19.956-24.554% at all three stems, but the 16x12 grid map is confined to grid rows 0-2 (cell means up to 50.8; row 3 is 2.5-4.6, rows 4-11 <= 3.1) and the per-pixel histogram is a constant ~18-22 delta (counts at d=18,19,20,21,22) -- systematic, a candidate real rendering difference (sky/fog band), needs a same-sim-state capture pair before it is called a renderer bug. **Cuba** 10.705-26.350% with 16x12 cell means <= 11.1 and per-pixel deltas clustered at d 3-6 across the whole frame -- the dither/quantization noise floor, structural-clean. **Jungle** 5.858-6.618% (worst cell dmean 14.8-26.7). **Streets** 1200: 9.564% with a diagonal band of 16x12 cell means 16.0-148.4 across rows 0-4, cols 0-6 (a region delta, not noise); Streets 900/1500 are clean (4.738/5.040%) and show a separate residue in the lower-right cells (row 9 col 10: 20.9 at 900, 33.0 at 1500). **Dam** 900: 92.863% over tol 2, phash 83 -- the flyby offset.
**Open (docs/ROADMAP.md §4).** The Archives top-band and the Streets-1200 band, as the two candidates for a genuine cross-platform rendering difference; both need a same-sim-state capture pair (identical scripted input and a settled camera on both boxes) before either can be called a renderer bug.
## D522: The golden window's frame-900 stem is not settled gameplay on several levels — the win golden gate is not green (golden gate; D117 follow-up)
**FIXED 2026-10-05 — recipe-side re-base only (no game-code change, no tol limit raised).** The measurements below are kept as the record of why. `tools_pc/verify.sh`'s `golden_dump_for` now carries per-level windows: `900-1500:300` on the 14 levels where 900 is settled, `1000-1400:200` on Surface 2 / Streets / Depot / Cradle / Frigate, `1500-1700:100` on Dam. Chosen by measuring run-to-run agreement: at 900 those levels fail the per-pixel limit and Dam's 900 is still flyby; at the new stems three independent runs agree within tol 2 (frigate + the four: 1000/1200/1400 all 0.000% max-pair; Dam's 1200 agreed on two runs then diverged 5.761% on a third, so it moved to 1500-1700 — 1500/1700 0.000%, 1600 keeps ~40 moving pixels at 0.013%, inside the 1% limit). Full 21-level sweep after the re-base: **21/21 PASS** (`scratch/verify-rebase2.log`).
**Problem (2026-10-05 golden-gate pass, this tree).** A full 21-level sweep run with the recipe exactly as `tools_pc/golden/README.md` states it does not pass against the tracked `tools_pc/golden/<level>/win/` set. Honest sequential pass (one run at a time, `scratch/sweep-par.sh 1`, live `data/ge007.eep` present): **5/21 REGRESSION** — dam 6.542 % @900 (worst_cell 12.11), surface2 3.752 % @900 (limit 3.0), streets 1.001 % @1200, depot 1.267 % @900, cradle 1.754 % @900, all against a 1.0 % per-pixel limit; 16/21 PASS. The concurrent pass (`JOBS=7`, 21 levels in 1:56 wall) failed **7/21**: the same five plus runway 1.668 % @900 and frigate 4.484 % @900, both of which pass cleanly when run alone (worst_cell 0.616 / 0.109). Both numbers are recorded, not averaged; the difference is pacing (see below).
**Cause (D117 wall-clock pacing, not a render bug).** `GE_INPUTSCRIPT="20:START"` skips the intro flyby, but the flyby's own length is wall-clock paced: the scripted pad advances in real time, so which camera beat frame 900 lands on moves with the run's pacing. dam's 900 stem measured 1.598 / 2.689 / 3.424 / 6.542 / 7.240 % across five runs of the same recipe — the spread is the flyby beat, not noise. The unstable stem itself moves between passes: streets failed at 1500 in the concurrent pass and at 1200 in the sequential one. `tools_pc/golden/README.md`'s claim that "frames 900/1200/1500 are settled gameplay on every level" is contradicted.
**Also measured (D117 follow-up not closed).** `GE_DETERM=1` does not make the window reproducible: repeats differ 4.654 / 5.562 / 5.331 % with `GE_DETERM=1` against 0.115 / 0.219 % plain at the same stems — the switch makes the spread worse, not deterministic.
**Never do:** raise the tol limits to fit. That hides exactly the D117 class this gate exists to catch.
**Fix owed (recipe-side only; no game-code change):** either (a) move the capture window to stems settled on every level (1200–1800:300 is the measured candidate) and re-capture the 63 `win/` PNGs plus the box's linux set, or (b) keep the set and state the gate honestly (structural green + per-pixel marginal at 900) in `golden/README.md`, the ROADMAP row and the known-issues table. Driver: `scratch/sweep-par.sh` (gitignored; mirrors `verify.sh`'s ini pin, `GE_*` recipe, framediff flags and per-level limits, one cwd per level so runs can overlap — parallel verdicts are diagnosis only, the honest numbers come from the `JOBS=1` pass).
## D523: The golden recipe does not pin save state — `verify.sh` pins the ini but never touches `data/ge007.eep` (golden gate; D514/D522 follow-up) — **SUPERSEDED by D529 (2026-10-07): the frame depends on the save's CONTENT, not its presence; the gate now installs the canonical in-tree save**
**FIXED 2026-10-05 — `verify.sh` now pins the save file** (back up + require `data/ge007.eep`, mirroring the ini pin; it exits 2 with a message when a clone has none, rather than silently measuring a different scene), and `tools_pc/golden/README.md` states the pin. The measurements below are the record of why the gap mattered.
**Problem.** The goldens were captured with a save file present, and nothing in the recipe records that. `tools_pc/verify.sh` backs up and pins `data/ge007.ini` (`pin_ini_640x480` / `restore_ini` / the EXIT trap) but has no analogue for `data/ge007.eep`, and `tools_pc/golden/README.md`'s recipe list never mentions it. The game's save file changes the rendered scene, so the recipe reproduces the goldens only on a machine that happens to hold a matching save — and a fresh clone of this repo ships no `.eep` at all.
**Measured on this tree (2026-10-05, two honest sequential 21-level passes, same build, same ini pin).** With the live `data/ge007.eep` present: 5/21 REGRESSION. With no `.eep` at all: **19/21 REGRESSION** — Archives 21.557 % @900 (worst_cell 31.64), Silo 40.9 and Dam 116.6 worst_cell (framediff-level misses, not even per-pixel-comparable), and every remaining level 1.2–5.5 % against a 1.0 % limit. Only Bunker 2 (0.001) and Cuba (structural tier only) survive. So the save state, not the renderer, is what separates a passing gate run from a failing one.
**Box-side corroboration (P7, D521).** Archives cell means 0.0 → 27–31 from `.eep` presence alone, and dam's 900 phash 3 → 83: same cause, opposite direction, because the box's save differs from the win capture's. The box changed no golden and no `verify.sh` for it — correct, since the fix belongs to the recipe, not to either artifact.
**Fix owed (port tooling + docs only).** Pin the save file the way the ini is pinned: back up `data/ge007.eep`, install the capture-time save (or explicitly none) for golden runs, restore on exit, and state in `golden/README.md` which save state the set carries. `data/` already holds candidate capture-time saves (`ge007 - Copy.eep`, `ge007 copy.eep`, `d295_repo_eep_prior.bak`), all with different sha256 from the live one; which one the set was captured with is not recorded and has to be identified before pinning.
**Measured refinement (2026-10-05, same build, Archives as the discriminating level).** The variable that matters is **save-file presence, not which save**: Archives measures 0.175625 worst_cell with the live `ge007.eep`, with `ge007 - Copy.eep`, with `ge007 copy.eep`, and with `d295_repo_eep_prior.bak` (four different sha256s, all identical verdicts), and 21.557 % / worst_cell 31.64 with no `.eep` at all. So pinning "a save present" reproduces the set on Archives from any of these; pinning nothing does not. Dam cannot discriminate — its 900 stem spread (D522) dominates the save delta (measured 3.03 / 12.11 / 17.37 / 116.6 / 132.96 worst_cell across runs of the same recipe). Until that is done, no sweep verdict from a fresh clone is comparable to the committed goldens.
## D524: The golden tooling was destructive and drift-prone — `verify.sh`'s per-level ini/save pin snapshotted the previous level's game-written file, and `capture_p7.sh` hardcoded the pre-D522 windows while deleting the save it captures with (golden tooling; D522/D523 follow-up)
**FIXED 2026-10-05 — tooling only (no game-code change, no golden touched by this finding).** Two sites, both found in the U5 code review of `tools_pc/**` (paths / quoting / subprocess / destructive behaviour) that the v0.4.1 release audit owed.
**(1) `verify.sh` sweep overwrote the maintainer's own settings.** `pin_ini_640x480` and `pin_eep` are called once per level inside the sweep loop, and the game rewrites `data/ge007.ini` (and writes `data/ge007.eep`) after every run. So from level 2 onward the "backup" is the *previous level's game-written* ini, and the EXIT trap restores that: after one 21-level sweep the user's personal settings (`FovScale`, `DrawDistance`, MSAA, sound…) are replaced on disk by compiled-in defaults, with no way back, and the save restore hands back the second-to-last level's written save rather than the pre-sweep one. It also leaked one `mktemp -d` directory per level. Fix: both pins are idempotent — the FIRST call snapshots the user's ini/save, later calls only re-pin — and `pin_eep` re-installs that snapshot before every level, so each run starts from the same save state (D523 says presence, not which save, is what the frame depends on; the runs write the save, so re-installing is what makes the sweep reproducible).
**(2) `tools_pc/capture_p7.sh` carried the pre-D522 recipe and cleared the save.** It hardcoded `GE_PCDUMP="900-1500:300"` / `GE_QUITFRAME=1502` (stale on the six levels D522 re-based) and ran `rm -f data/ge007.eep` before every run — the D523 gap, and the reason the 2026-10-04 linux set had to be re-captured at all. It now reads the windows, scripts and seed out of `tools_pc/verify.sh` (`golden_dump_for` / `golden_script_for` / `GOLDEN_SEED`) so the capture tool and the gate cannot drift, computes `GE_QUITFRAME = last requested frame + 2`, and refuses to run without `data/ge007.eep` present.
**Never do:** keep the per-level backup semantics. A sweep that leaves the user's ini rewritten is a silent data-loss bug in a tool the maintainer runs against their own `data/` directory; "restore what the last run left" is never the intent.
## D525: The whole `<level>/linux/` golden set was stale, not only on the six re-based levels — re-rounding it closes D521's two residual render bands (golden gate; D521/D522/D523 follow-up)
**FIXED 2026-10-05 — recipe/asset side only (box re-round + merge; no game-code change, no tol limit raised).** The 13-frame order owed to the box was the wrong size. D523's evidence says save-file *presence* moves ~every level (Archives 21.557 % with no `.eep`, 0.175625 worst_cell with any of four saves), and P7's 2026-10-04 round ran with `data/ge007.eep` cleared while the win set carries a save present — so the entire 21-level linux set was stale, not just the six levels D522 moved. The box re-captured all 21 levels (63 frames, 21/21 rc=0, frames=3, 25-29 s each, ~10 min wall) at the windows `verify.sh` now carries, with the save pinned present, on the same real-GL box (Mesa 23.0.3 / Intel HD 3000, X on vt2, `DISPLAY=:0`, 640x480 ini pin, `GE_RSEED` pinned, per-level `GE_INPUTSCRIPT`). The base branch already carried 50 linux PNGs (the five re-based levels kept only their 1200 stem, Dam had none); after the re-round the set is 63, one frame per recipe stem on every level.
**Cross-platform measurement after the re-round (framediff linux-vs-win at the recipe stems, tol 2).** Structural tier: **21/21 PASS** — worst 16x12 cell mean 4.43 (streets), phash <= 6 on 20 levels, the one structural signal at the limit being **cradle phash 40** (threshold 40; cell means 1.17, so it is a perceptual-hash tie, not a region delta). Exact tier, % of pixels over tol 2: **0.348-3.983 %** on the 20 non-Cuba levels (facility 0.348-0.477, surface2 0.408-0.488, surface1 0.835-0.931, train 0.782-0.825, bunker1 0.951-1.063, statue 0.605-1.105, bunker2 1.142-1.143, depot 1.098-1.155, control 1.298-1.332, runway 1.340-1.396, caverns 1.462-1.473, silo 2.352-2.363, egypt 2.874-2.951, dam 2.964-3.443, frigate 1.282-3.181, cradle 3.063-3.337, jungle 3.127-3.851, aztec 3.704-3.830, archives 3.590-3.859, streets 3.616-3.983); Cuba 23.657-36.338 % is the recipe's structural-tier-only level (cutscene animation timing, `golden_tolpct_for cuba` = empty). The same spread measured 1.142-6.958 % on the stale set, so the re-round roughly halves the worst cross-platform deviation.
**D521's two open items are closed by this measurement, and neither is a renderer bug.** **Archives** measured 19.956-24.554 % with cell means up to 50.8 confined to grid rows 0-2 on the stale pair; it now measures 3.590-3.859 % with worst cell mean 2.31 and phash 1 — the band was the save-state difference D523 names, not a sky/fog rendering difference. **Streets** frame 1200 measured 9.564 % with a diagonal band of cell means 16.0-148.4; it now measures 3.616-3.983 % with worst cell mean 4.43 and phash 0. **Dam** frame 900's 92.863 % / phash 83 is the flyby offset (D521/D522) and is not in the set any more: Dam sits at 1500-1700, where the cross-platform delta is 2.964-3.443 %.
**Box-side caveat (this is why the box's own sweep is not a pixel gate).** `verify.sh`'s D521 renderer probe fails closed on this box: `glxinfo -B` exits 255 with "Error: unable to open display :0" from a non-login ssh shell even though the game's SDL GL context and `glReadPixels` readback work (the captured PNGs are 128-355 KB, not black). `LINUX_SOFTWARE_GL` therefore stays 1 and the box's sweep reports "pixcount/framediff skipped, crash-detect only". The cross-platform numbers above are computed on Windows against the merged linux PNGs, which is the only honest place to compute them from this box. **Never claim the linux pixel gate ran on this box** — the claim that holds is that the linux captures are structurally clean against the win set and that the linux *crash* gate ran on the box.
**Never do:** call the cross-platform spread a parity gate. The per-pixel limit (1 % / 3 % on jungle+surface2) is calibrated per platform against that platform's own run-to-run noise; 0.348-3.983 % across platforms is informational, and the gate each platform ships is its own captures against its own goldens.
## D526: 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-state diagnostic
**CLOSED as faithful 2026-10-07 (no port bug; no fix).** The P13 "Dam-ending grate show-through" (2nd POSENDS camera: the area under the grate Bond stands on shows through — the 2D edge-railing/grate texture's between-anchors region) was triaged as a suspected B1 port bug (the grate quad's texture stage taking an alpha/blend path it shouldn't — D195/D268 class). To bucket it, a per-triangle draw-state census probe was built (this D526) and smoke-tested. The 1964/GEPD reference of the same 2nd-POSENDS shot then showed **the port and the N64 look the same** — the show-through is in *both*. So it is the original game's 2D grate/railing texture rendering, reproduced faithfully: a known issue that **closes in the D306/D291 class** (a "bug" that 1964 shows is intended behaviour). There is nothing to fix in `port/fast3d/`.
**Probe (retained as a generic tool):** `d526_note_emit()` in `port/fast3d/gfx_pc.cpp`, folded into the D474 `tri_dbg()` gate (zero player cost when off). Env: `GE_D526="lo-hi"` (sim-frame window), `GE_D526BOX="x0 y0 x1 y1"` (NDC region, y-up; default whole screen), `GE_D526MAX=N` (cumulative per-triangle line cap, 400; the per-frame `D526F:` signature aggregate is uncapped). Emits to stderr: per-triangle NDC bbox, z/w, `gm`, `oml`, `omh`, combine mode, first-tile tmem/fmt/siz, prim + vertex alpha, use_alpha/modulate/invisible/2-cycle, plus a per-frame `D526F:` signature map. It is the only per-triangle *draw-state* census (D474/D75D/ZF/D303 are specialized) and is the right tool for the alpha/texture bug class (D195/D266/D268/D273).
**Static audit (no code change):** `palette_to_rgba32` (`gfx_pc.cpp:1044`) matches the game's ground truth — `texChannelsToPixels` (`src/game/image.c:1836`) packs RGBA5551 as `R<<11|G<<6|B<<1|A` (alpha = bit 0) and IA16 as intensity-hi/alpha-lo (the D228-fixed decode), so a global palette bit-flip is ruled out; blend state is stock and D268-verified. Consistent with the faithful result (the port's alpha/texture path is correct).
**Never do:** ship a `port/fast3d/` "fix" for the Dam-ending grate show-through — 1964/GEPD confirms it is faithful N64 behaviour (D306/D291 class). Do not drop the census probe without a finding that uses it; it is a maintained diagnostic.
## D527: `GE_PCDUMP` written `first:last` (a colon where the dash belongs) silently dumps EVERY frame (harness trap)
**OPEN (harness documentation; no code change yet).** `port/src/video.c:1357` parses `GE_PCDUMP` with `sscanf("%d-%d:%d")`. If the lo/hi pair is written `first:last` (a colon where the dash belongs), the `%d-%d` part fails to match, the fallback sets `hi=0x7fffffff` and `step=1`, and the dump writes **every** frame from `first` onward at ~921 KB each — a first launch in the 2026-10-05 session ate ~1.5 GB before it was noticed (the run went to ~1500 fps until the window was closed). The failure mode is silent: no error line, the dump just runs.
Fix owed (port-layer only): log a NOTE when the `sscanf` does not consume all three fields (e.g. "GE_PCDUMP 'x:y' is not 'first-last:step' — dumping every frame"), or fail closed (ignore the value) — either way the operator must see which window is active.
**Never do:** write `GE_PCDUMP` as `lo:hi` — use `first-last:step` (a dash between lo and hi, a colon before the step).
## D528: `GE_STARTMENU` skips the EEPROM read, so front-end-only sessions never import the emulator save (harness trap)
**OPEN (harness documentation).** A `GE_STARTMENU` session (front end up with no level load) never reaches the EEPROM read — no `eeprom:` log line; the front end reports `watchsettings: no valid Bond save in any folder` even with `data/ge007.eep` present. Consequence: the D514 imported-save test **cannot** be driven from a `GE_STARTMENU` boot; it must run from a **normal boot**. There it reproduces exactly: `eep: imported emulator save (6 regions converted, backup data\\ge007.eep.emulator.bak)` and `data/ge007.eep` md5 `3ca7cf84…` = the in-game-proven converted save (2026-10-05 session).
Decision owed: document it (a NOTE log line when `GE_STARTMENU` is active: "EEPROM import skipped") or make the front end perform the import — port-layer only; the N64 front end has no EEPROM at all, so this is pure port scaffolding.
**Never do:** drive the D514 imported-save test from a `GE_STARTMENU` boot — it must run from a normal boot, or the EEPROM import is skipped and the test cannot see the converted save.
---
## D529: The golden gate depends on the save file's CONTENT — the D523 "presence, not which save" claim is refuted; the gate must pin a canonical save (golden gate; refines D523; follow-up to D525/D524)
**2026-10-07 (pre-push session).** The D523 pin installed *whichever* local save existed ("presence, not which save — Archives measures worst_cell 0.175625 on each of the four candidate saves and 21.557% with none"). That claim had only ever been measured **on Archives**. The 2026-10-07 sweep refutes it: the local `data/ge007.eep` had been rewritten by the live Dam-ending playtest sessions (md5 `a41d7ab…`, mtime 2026-10-05 07:27), and the honest sequential sweep under it produced **6/21 REGRESSION** on a *byte-identical binary* (the 2026-10-04 run had been 21/21).
**The A/B (facility, window 900-1500:300):**
| save (2 KB each) | facility worst_cell |
|---|---|
| `ge007 copy.eep` (2026-09-02, the pre-live-sessions save) | **0.0–0.25 — PASS** |
| `ge007 - Copy.eep` (2026-09-16) | 0.0–0.25 — PASS |
| `build-pc/ge007.eep` (2026-09-28) | 0.25 (f2) / 0.25 (f3) — PASS |
| `../007/data/ge007.eep` (2026-09-15) | 0.25–0.5 — PASS |
| `gamefaqs_dexdrive_2001.eep` (external) | **177.47 (f2) / 177.22 (f3) — REGRESSION** |
| `data/ge007.eep` local (a41d7ab, the 2026-10-05 sessions) | **177.47 (f2) / 177.22 (f3) — REGRESSION** |
Two distinct classes in the 6-failure sweep, proven apart by the A/B:
1. **Save-content dependent (facility, streets, jungle, cradle):** same failure signature (177.2–177.5 at the 1200/1500 stems — a *different scene* in-window, not a jitter class) only on the two saves with that content (the 2001 dexdrive save and the 10-05 local save share it; 2001-vintage saves apparently hold state that routes facility's intro differently). The other four candidates pass. **This is what D529 fixes: the gate installs ONE canonical save, so "which save" can never vary.**
2. **D117 wall-clock flyby (dam@1500 4.9%, frigate@1000 3.2–8.3%, streets@1000 2.5–25.7%):** fails *identically on every save* — the window's first stem is inside the real-time-paced intro flyby, so the content at a given sim frame varies with machine load (D117, long-known, previously masked because the Oct-4 goldens happened to be captured the same way).
**Resolution (FIXED 2026-10-07, same session):**
- **Canonical save in-tree: `tools_pc/golden/ge007.eep`** (2 KB, = `ge007 copy.eep`, the stable pre-live-sessions save the Oct-4 gate was green on). In-tree = a fresh clone or CI can run the gate with zero external state, and the D523 "fresh clone has no save → exit 2" failure branch is gone for good.
- **`verify.sh` installs it like the ini pin** (snapshot the local `data/ge007.eep` if any → install the canonical → restore-or-remove on exit). The local playtest save is never the gate's input.
- **All 21 win golden sets re-captured under the canonical save** (`scratch/golden-recapture-win-v050.sh`; 21/21 rc=0). The first four flaky levels (dam/frigate/streets/jungle — the D117 flyby stems) were re-captured again **clean** (no concurrent processes) after the first interleaved capture.
- **Green protocol held (final, clean):** the first "double-green" pair ran `verify.sh` **with no arguments — which at the time only printed the usage and exited 0** (a no-op, not a sweep — that pair does not count) and was also interleaved with the in-flight re-capture. The valid record is **three consecutive clean full sweeps (`verify.sh sweep`) all 21/21 PASS** after the clean 4-level re-capture (`scratch/verify-v050-flaky3-run{1,2,3}.log`). **`verify.sh` with no arguments must never be cited as a green run.** CODE-PATCHED the same day (independent-reviewer follow-up): the no-arg path now prints the usage and **exits 1** with an explicit "never cite a no-arg run as a gate result" error — the failure class is closed in code, not just doc.
- **Linux side:** the box set was re-rounded 2026-10-05 under a *cleared* eep (D525) — a cleared eep is a *specific content state* (empty save), not the canonical one. The box re-round under `tools_pc/golden/ge007.eep` is owed in parallel on the release branch (off the critical path; the box pixel probe fails closed there anyway — D244 — so it is a second-surface check, not a gate).
**Why this is not a renderer drift:** the binary is byte-identical to the 2026-10-04 21/21 run; the only variable that changed is `data/ge007.eep`, and the A/B is save-keyed (same binary, four passing saves, one failing save). No renderer code changed.
**Never do:** (1) run the golden gate against a local playtest save — it is exactly this class of drift; always the in-tree canonical eep. (2) claim "the frame depends on save *presence*" again — it depends on *content*; the D523 claim survives only as the (superseded) measurement on Archives. (3) treat the linux 63-frame set as valid under the canonical eep before the box re-round lands — it is the D525 cleared-eep round, and **no linux number may be cited (e.g. in the README pass) until that re-round lands**. (4) cite `verify.sh` with **no arguments** as a green run — a sweep is `verify.sh sweep` (the no-arg path now exits 1, closing the trap in code). (5) run a re-capture concurrently with a sweep (contention moves the D117 flyby stems). (6) treat **same-session** sweeps as proof the four D117 flaky levels (dam/frigate/streets/jungle) are stable across wall-clock: the first-stem content is time-dependent and the pin was fixed to "now" — the cross-machine test is the box re-round; **if those four go red there, the fix is to exclude or widen the first-stem cell for those levels, NOT to re-capture again** (an independent reviewer's 2026-10-07 verification flagged this; same-session worst_cell swings were streets 8.38→4.41, jungle 1.42→3.87). (7) trust a `capture_p7.sh` round's provenance claim without checking WHICH save state the loop actually installed — the D524-era per-level re-pin restored the local save and silently overwrote D529's pre-loop canonical install (D531); the linux set is only "canonical" if the loop line says `cp "$CANON_EEP"`.
## D531: `capture_p7.sh`'s D529 adaptation was incomplete — the per-level re-pin restored the local save, so the 2026-10-05 box re-round ran under the box-local save (not the canonical one); fixed + re-captured (golden gate; D529/D530 follow-up; P12 U5 review finding)
**Blocker found in the 2026-10-07 P12 U5 review (local session).** `tools_pc/capture_p7.sh`'s per-level loop had `cp "$EEP_ORIG" "$EEP"` (introduced by D524 to make the tool non-destructive): it restored the pre-capture **local** save — or a 0-byte eep when `data/ge007.eep` did not exist — before every level. D529's adaptation (`8fbf8aa0`) installed the canonical save (`cp "$CANON_EEP" "$EEP"`) **once, before the loop** and added the canonical-missing guard, but left the D524-era re-pin line in place — so from level 1 the canonical install was silently overwritten and every capture ran under the local save state. The header comment ("the pinned save, restored before every level") asserted what the code did not do.
**Impact — D530's provenance claim is wrong:** the 2026-10-05 box re-round (`~/p7-reround.log`, "P7 re-round DONE") ran under the box-local save (`sha256 d9f306…`, 2 KB — distinct from the in-tree canonical `65247b…`), not the canonical eep. D530's / the golden README's / the commit message's "63 PNGs under the canonical eep" did not hold, and D530's first-stem table (measured in that re-round's report against the old D525 goldens — its numbers do check out against the report) is not canonical-save evidence. **The win set is unaffected** — `verify.sh`'s `pin_eep` re-installs the canonical save on every call (that code was right); the bug was capture-side only.
**Fix (this batch, tooling only):** the loop now runs `cp "$CANON_EEP" "$EEP"` per level (`EEP_ORIG` is kept for the exit-restore only), and the script now `cd "$ROOT"` — the loop's dump/golden paths are root-relative (`ppm/`, `tools_pc/golden/`), contradicting the header's "can be invoked from anywhere" claim.
**Re-capture (this batch):** a fresh 21-level box re-round under the canonical eep (21/21 rc=0, 63 frames), committed into `tools_pc/golden/<level>/linux/` replacing the D530 box-local-save set: **47/63 frames differ** from it (save content demonstrably moves the frames — D529's drift class), 16 byte-identical (save-independent scenes). **Note on the box's pixel capability:** the 10-05 re-round's p7_report diff table (1–26% over on real content) proves its WSLg GPU display was active then; today the box reads back **black even with `DISPLAY=:0`** (`glxinfo -B` under `:0` reports no usable renderer — the WSLg GPU display is session-state-dependent, and today's session is on the software fallback), so today's `verify.sh` runs (D530's crash-detect sweep and this batch's `~/p7-verify-pixel.log`) are legitimately crash-detect-only. The pixel-gated cross-machine verify is owed for a window in which the box's GPU display is up again.
**Pixel-gated cross-machine gate (the real D117 datum):** `verify.sh sweep` on the box against the new canonical goldens — **2026-10-07 attempt (log `~/p7-verify-pixel.log`): 21/21 PASS, 0 FAIL, crash-detect only** (the run's renderer probe correctly failed closed on today's software-fallback session, so it adds stability, not, pixel data); the pixel verdict — including the four flaky levels' first stems — is owed for a window in which the box's WSLg GPU display is active. If the four flaky levels (dam/frigate/streets/jungle) go red there, the never-do 6 decision stands: exclude or widen the first-stem cell, do NOT re-capture.
**U5 review notes from the same pass (non-blockers):** the three should-fixes below were applied 2026-10-07 as D532 (gate hardening: fail-closed ini pin, hard-pinned seed, dead A/B switches removed).
## D532: the three P12 U5 should-fixes applied — the golden gate's ini pin now fails closed, the seed is hard-pinned (a stray `GE_RSEED` warns instead of re-seeding), and `audiodebug.ps1`'s dead A/B switches are gone (golden gate + tooling; D531/U5 follow-up)
**2026-10-07 (local session).** Three tooling-only fixes, no game logic:
1. **`verify.sh` `pin_ini_640x480` failed OPEN** — the three call sites (`single`/`sweep`/`parity`) were `pin_ini_640x480 2>/dev/null || true`, so a failed ini write (missing `data/`, read-only tree, failed snapshot) ran the gate under an **unpinned ini** — the frame depends on a defaults-only 640x480 ini exactly like the save pin (D529), so that was a spurious-verdict source, not a skipped nicety. The pin now fails closed (snapshot and write both `error + exit 2`, matching `pin_eep`'s style) and the call sites are plain.
2. **`GOLDEN_SEED` hard-pinned** — it was `"${GE_RSEED:-0x0123456789abcdef}"`, so a stray `GE_RSEED` in the environment silently re-seeded the documented-as-pinned gate (frames shift → spurious verdicts). It is now the literal, with a warning when a differing env var is present (verified: `GE_RSEED=0xdead` warns, the pinned value is silent). `capture_p7.sh`'s `eval grep -m1 '^GOLDEN_SEED='` still picks up the same line, so capture and gate stay consistent.
3. **`audiodebug.ps1` dead switches removed** — `-AB`/`-Old` were documented no-ops (`GE_D204_OLD` no longer exists in the game); the switches, the A/B block, and the `-OldMode` plumbing are gone, with a one-line removal note (D532).
PS parse: 0 errors; `bash -n` clean; the warning path verified. **Owed (unchanged):** the pixel-gated box verify in a WSLg GPU-display window (D531).
## D530: The box (X220) linux golden re-round landed — first cross-machine test of the four D117 flaky levels (golden gate; D529 never-do 3/6 follow-up) — **AMENDED BY D531 (2026-10-07): the 10-05 re-round ran under the box-LOCAL save, not the canonical one (the capture_p7.sh bug); the true canonical re-capture is D531**
**2026-10-07 (local session).** The box re-round D529 owed (never-do 3: the linux set was the D525 *cleared*-eep round and no linux number could be cited until it was re-rounded under `tools_pc/golden/ge007.eep`) ran on X220 on 2026-10-05 (tree `442eb2ff`, worktree `~/gh-fullhist-wt-p7`, log `~/p7-reround.log` hit `P7 re-round DONE` 12:53 EDT). **21/21 levels rc=0** (the box self-gate is crash-detect only — the pixel probe fails closed on the box, D244). This session pulled the 63 PNGs (21 levels × 3 frames) and committed them into `tools_pc/golden/<level>/linux/`, replacing the D525 set: 51/63 differ from the D525 round; the 12/63 that are byte-identical are save-independent scenes — including **frigate's first stem (001000)**, a direct D117 stability signal. **AMENDED BY D531: that re-round ran under the box-local save (the `capture_p7.sh` bug), so the "canonical eep" provenance claim — and the first-stem table below, as canonical-save evidence — does not hold; the D531 re-capture supersedes this set.**
The `fail` rows in the box report are the expected cross-eep diffs against the *old* in-tree set, not regressions: structural tier is all `ok` (phash ≤ 3, cradle@40 the documented exception; Cuba 26.4% inside its documented 23.7–36.3% structural-only range).
**D117 flaky-four first-stem check (D529 never-do 6 — the first cross-machine test):**
| level (first stem) | worst_cell | phash |
|---|---|---|
| dam@1500 | 1.70375 | 0 |
| frigate@1000 | — (byte-identical to the D525 round) | 0 |
| streets@1000 | 1.775625 | 0 |
| jungle@900 | 5.115625 | 3 |
All four sit in the stable-levels' range (e.g. silo 0.41–0.84% over, worst_cell 0.41–0.42 is the quietest; archives 1.51–2.31 is noisier). **None went red → the never-do 6 decision closes with NO recipe change** (no first-stem cell excluded or widened on the linux side).
**Unblocked (D529 never-do 3):** the README/index v0.5.0 line "re-based on both platforms (21 levels, 63 frames each)" now stands on a canonical-eep linux set (D531's re-capture; the D530 set it first pointed at was the box-local-save round — see D531). **Fresh box sweep (crash-detect):** with the new goldens staged on the box, `verify.sh sweep` (2026-10-07) is **21/21 PASS** — but that run was crash-detect only (GE_PCDUMP reads black on the box's llvmpipe/WSLg, the D244 note), so it is a stability re-check, not a pixel datum. **AMENDED BY D531:** the "black frames" were **not** simply the no-DISPLAY llvmpipe path — the box's WSLg display is session-state-dependent: its 10-05 session was GPU-backed (the re-round's p7_report pixel table proves real frames), while today's reads back black even with `DISPLAY=:0` (software fallback; `glxinfo -B` reports no usable renderer). D531's pixel-gated attempt (21/21 crash-detect, `~/p7-verify-pixel.log`) is valid as a stability re-check; the pixel datum is owed for a GPU-display window.
+1 -1
View File
@@ -31,7 +31,7 @@ passes together fit in ~55 s.
## Source material ## Source material
`C:\Users\james\Videos\NVIDIA\Build-pc\new\` — ShadowPlay recordings `<videos>\NVIDIA\Build-pc\new\` — ShadowPlay recordings
(3072x1728 @ 60 fps, h264), no usable audio track (cut with `-an`). The four (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; 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. add a new session there (tag, filename suffix, duration) to use it.
+29
View File
@@ -0,0 +1,29 @@
---
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.
---
## 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).*
+120 -71
View File
@@ -1,12 +1,9 @@
--- ---
title: GoldenEye 007 PC Port title: GoldenEye 007 PC Port
# v0.4.0: status bumped from v0.3.0; Honest status replaced by the v0.4.0 # v0.5.0: status bumped from v0.4.0. Description kept short (Bing's 160 limit).
# release-notes known issues (rainbow/water/z-fight/cameras/trees/muzzle/
# stretched-widescreen/gunshot items all fixed; D335b F10-overlay stretch is
# the new widescreen residual). Description kept short (Bing's 160 limit).
description: >- description: >-
A native PC port of GoldenEye 007 (Nintendo 64, 1997), built from A native PC port of GoldenEye 007 (Nintendo 64, 1997), built from
decompiled source with a software RSP. v0.4.0 for Windows, Linux and decompiled source with a software RSP. v0.5.0 for Windows, Linux and
Steam Deck. Steam Deck.
--- ---
@@ -19,39 +16,41 @@ in a dedicated `port/` layer, following the architecture of the
[Perfect Dark PC port](https://github.com/fgsfdsfgs/perfect_dark), the same [Perfect Dark PC port](https://github.com/fgsfdsfgs/perfect_dark), the same
Rare "Indy" engine family, one hardware generation apart. Rare "Indy" engine family, one hardware generation apart.
[Download](#download) · [News](#news) · [See it running](#see-it-running) · [Honest status](#honest-status) · [Documentation](#documentation) [Download](#download) · [News](#news) · [See it running](#see-it-running) · [Known issues](#known-issues) · [Documentation](#documentation)
**Status: v0.4.0 (2026-09-28) — fully playable, with a small set of known **Status: v0.5.0 (2026-10-05) — fully playable, with a small set of known
caveats.** The full single-player campaign runs at a steady 60 fps and is caveats.** The single-player campaign and the 2–4 player split-screen
completable end to end (all 20 missions, plus the ending-credits sequence, multiplayer both run at a steady 60 fps. The single-player campaign is
playtested on Windows, Linux and Steam Deck). It is the most complete release completable end to end (all 20 missions plus the ending-credits sequence),
to date: **native widescreen** (on by default — undistorted world at your and the 2–4 player split-screen campaign is supported on every multiplayer
display's aspect, 4:3 menus pillarboxed), a **complete aim system** (N64 or map; both were playtested on Windows, Linux and Steam Deck. This release
centred PC style, for mouse *and* controller), **in-game key rebinding** with adds **one
a GEPD-style default layout, **crosshair customization**, **rumble-pak options menu** (laid out like the Perfect Dark port's, with plain-English
haptics** on supported gamepads, and a **rebuilt options overlay** with wording, real-unit sliders and a one-line description per option), the game's
fine-tuning rows (frame cap, MSAA, draw/LOD distance, HUD scale). The most own **N64 control styles as a per-seat preset**, **direct loading of
common defect classes from earlier releases — particle colour drift, water emulator save files**, a **scalable HUD overlay**, fullscreen and
seams, z-fighting, billboard trees, muzzle flashes, the front-end Nintendo window-centring controls, and a **fidelity round checked frame-by-frame
logo, gunshot SFX — are fixed in this version; what remains is a short list, against the N64 game** (fog, aspect-ratio letterboxing, the Watch menu's own
under [Honest status](#honest-status). settings). 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 **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 for a polished, feature-complete build; expect missing features and the
occasional breaking change until then. See the occasional breaking change until then. See the
[README's Roadmap section](https://github.com/jkdansereau/goldeneye-pc-port#roadmap) [README's Roadmap section](https://github.com/jkdansereau/goldeneye-pc-port#roadmap)
for direction (PAL/JP support, controller-button rebinding, LAN multiplayer for direction (PAL/JP ROM support, macOS and ARM builds, the remaining small
under consideration, and more). accuracy differences, and post-1.0 opt-in extras).
<p align="center">
<img src="media/goldeneye-gh-preview.gif" width="70%"
alt="~24 s gameplay montage from live play sessions">
<br><em>~24 s gameplay montage from live v0.4.0 play sessions (Runway tank,
Dam, Caverns, Aztec, Bunker&nbsp;2, Surface&nbsp;2), running in the port.</em>
</p>
## News ## News
- **2026-10-05** — **v0.5.0**: one options menu with plain-English wording,
real-unit sliders and a one-line description per option; the N64 control
styles as a per-seat preset; direct emulator-save loading; a scalable HUD
overlay; fullscreen and window-centring controls; and a fidelity round
checked frame-by-frame against the N64 game. The reference-frame gate was
re-based on both platforms (21 levels, 63 frames each).
[Release notes](https://github.com/jkdansereau/goldeneye-pc-port/releases/tag/v0.5.0) ·
[downloads](#download).
- **2026-09-28** — **v0.4.0**: native widescreen, a complete aim system for - **2026-09-28** — **v0.4.0**: native widescreen, a complete aim system for
mouse and controller, in-game key rebinding, crosshair customization, mouse and controller, in-game key rebinding, crosshair customization,
rumble-pak haptics, a rebuilt options overlay, and a broad fidelity-fix rumble-pak haptics, a rebuilt options overlay, and a broad fidelity-fix
@@ -85,10 +84,19 @@ without a ROM you already own.
## See it running ## See it running
<p align="center"> <div class="viewer" id="viewer">
<img src="img/shots/shot-28.jpg" width="45%" alt="Streets, rendered by the port (v0.4.0 intro attract)"> <div class="vbar">
<img src="img/shots/shot-01.jpg" width="45%" alt="Dam, rendered by the port (v0.4.0 intro attract)"> <span class="dots"><i></i><i></i><i></i></span>
</p> <span class="lbl">viewer &mdash; in-engine captures, 3072&times;1728</span>
<span class="idx" id="vIdx">1 / 12</span>
</div>
<div class="vstage">
<img id="vImg" src="img/shots/shot-01.jpg" alt="in-engine capture">
<button class="vbtn prev" id="vPrev" aria-label="previous capture">&#8249;</button>
<button class="vbtn next" id="vNext" aria-label="next capture">&#8250;</button>
</div>
<div class="vcap"><span id="vCap">Dam &mdash; v0.4.0 intro attract</span></div>
</div>
<details> <details>
<summary><strong>Full gallery</strong>: 12 in-engine captures from a v0.4.0 intro-attract pass (3072×1728 widescreen, Sep 2026)</summary> <summary><strong>Full gallery</strong>: 12 in-engine captures from a v0.4.0 intro-attract pass (3072×1728 widescreen, Sep 2026)</summary>
@@ -163,57 +171,98 @@ More captures may land here as playtesting continues.
licensed and yours to extend: new video options, input tweaks, your own 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) 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. has the ground rules that keep it faithful to the original game.
- **AI disclosure**: development here was agentic — Claude Pro plus a local - **AI disclosure**: development here used AI coding agents — Claude Code
open-weight model on a single RTX 5090, as of August–September 2026. This plus a local open-weight model on a single RTX 5090, directed by one person in their
project is as much a study of *that process* as it is a port: whether spare time. The project is as much a study of that process as it is a
current LLMs can carry a codebase like this, and what actually goes wrong port, and the write-up is written to let you judge the result for yourself;
along the way. Game codebases have historically been a rough fit for it records what happened and takes no position for or against using LLMs
LLMs — large, stateful, hardware-adjacent, unforgiving of a subtly wrong on a project like this. See [the full
memory layout — more so for older local models, so this is a useful data write-up](dev/agentic-development.md) — the setup, timeline, handoff
point on where that stands now, for better or worse. Judge the result for workflow, and an honest account of what did and didn't work.
yourself. I'm one person doing this in my spare time, not a team. See
[the full write-up](dev/agentic-development.md) for the setup, timeline,
handoff workflow, and an honest assessment of what did and didn't work.
## How it works ## How it works
The R4300 game code is compiled completely unmodified; the decompilation's The R4300 game code's logic and control flow are the decompilation's, and remain the
control flow is ground truth. The N64's Reality Signal Processor, the graphics reference we preserve. The port's changes inside `src/` are few in kind (mostly mechanical 32→64-bit pointer fixes, a couple of approved timing fixes, and the opt-in *All unlocked* hook), each gated behind `#ifdef PORT`. The N64's Reality Signal Processor, the graphics
coprocessor that builds and executes each frame's display list, is emulated in coprocessor that builds and executes each frame's display list, is emulated in
software: the port interprets the GBI stream the game emits and translates it 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 to OpenGL, bypassing the RDP entirely. Everything else that would touch N64
hardware (video, audio, input, timers, save storage) is shimmed in a small hardware (video, audio, input, timers, save storage) is shimmed in a small
dedicated layer, following the architecture of the Perfect Dark PC port from dedicated layer, following the architecture of the Perfect Dark PC port from
the same Rare engine family. Full detail: [Internals](internals.md); the bug the same Rare engine family. Full detail: [Internals](internals.md); the bug
catalogue: [Porting notes](porting-notes.md). catalogue: [Porting notes](porting-notes.md).
## Honest status ## Known issues
- On Facility, if gas leaks during Ourumov's monologue he can pause for up to Known defects and limitations as of v0.5.0, each with its finding-log
~10 s before resuming the scripted shootout — a latent race that exists in reference. The full list, with root causes and fix status: the
the N64 original too (where it softlocks permanently); the port detects and
auto-recovers it. (D318)
- With native widescreen on, the F10 options overlay stretches with the
window instead of pillarboxing like the front-end menus (legible; cosmetic;
the F10 *Native widescreen* toggle restores the old stretched frame
throughout). The world/HUD widescreen rendering itself is correct. (D335b)
- The front-end Rareware logo shows a subtle texture-filtering artifact
(the Nintendo logo and legal page are clean). Cosmetic only. (D75)
- **`All unlocked` is experimental**: the fresh-install corruption case is
fixed, but saves made while it is on are not guaranteed recoverable by
switching it off — complete a level normally first, and back up
`data/ge007.eep` before enabling it. (D387)
- No macOS or ARM support. Keyboard/mouse rebinding shipped in v0.4.0;
controller-button rebinding is not supported yet.
The full list, with root causes and fix status: the
[release notes](https://github.com/jkdansereau/goldeneye-pc-port/releases) [release notes](https://github.com/jkdansereau/goldeneye-pc-port/releases)
and the [finding log](https://github.com/jkdansereau/goldeneye-pc-port/tree/main/docs/dev). and the [finding log](https://github.com/jkdansereau/goldeneye-pc-port/tree/main/docs/dev).
- **Facility — gas leak during Ourumov's monologue (D318).** If gas leaks,
he can pause for up to ~10 s before the scripted shootout resumes. The
race exists in the N64 original too (where it softlocks permanently);
the port detects it and auto-recovers.
- **F10 options overlay under native widescreen (D335b).** The overlay
stretches with the window instead of pillarboxing like the front-end
menus. Legible, cosmetic only; the F10 *Native widescreen* toggle
restores the old stretched frame throughout. The world/HUD widescreen
rendering itself is correct.
- **Front-end Rareware logo (D75).** A subtle texture-filtering artifact;
the Nintendo logo and legal page are clean. Cosmetic only.
- **`All unlocked` is experimental (D387).** The fresh-install corruption
case is fixed, but saves made while it is on are not guaranteed
recoverable by switching it off — complete a level normally first, and
back up `data/ge007.eep` before enabling it.
- **No macOS or ARM builds yet** (both are on the
[roadmap](https://github.com/jkdansereau/goldeneye-pc-port#roadmap));
today the release ships Windows, Linux and Steam Deck. Keyboard, mouse
and controller-button rebinding are all supported (defaults in the
[README's Controls section](https://github.com/jkdansereau/goldeneye-pc-port#controls)).
What the release actually installs, and how faithfully it tracks the N64
game: [Security & fidelity status](security-and-fidelity-status.md).
## Documentation ## Documentation
- [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. The technical docs live under [under the
- [Development process](dev-process.md): how work was scoped, partitioned, and budgeted across agents; the finding-log discipline. hood](under-the-hood.md) — the architecture, the N64→PC bug catalogue, and
- [Internals](internals.md): architecture, the software RSP-emulation approach, GoldenEye-vs-Perfect-Dark engine differences, the phased plan. how the project is developed. [Building](building.md) covers building and
- [Porting notes](porting-notes.md): the recurring Nintendo 64 → PC bug classes hit during the port, with fixes. running from source.
- [Building](building.md): full build and asset-extraction guide.
<script>
// console-viewer carousel: one media slot, cycle the captures. Vanilla JS.
(function () {
var slides = [
{ src:"img/shots/shot-01.jpg", cap:"Dam &mdash; v0.4.0 intro attract" },
{ src:"img/shots/shot-03.jpg", cap:"Runway &mdash; v0.4.0 intro attract" },
{ src:"img/shots/shot-04.jpg", cap:"Surface &mdash; v0.4.0 intro attract" },
{ src:"img/shots/shot-09.jpg", cap:"Frigate &mdash; v0.4.0 intro attract" },
{ src:"img/shots/shot-11.jpg", cap:"Surface 2 &mdash; v0.4.0 intro attract" },
{ src:"img/shots/shot-16.jpg", cap:"Statue &mdash; v0.4.0 intro attract" },
{ src:"img/shots/shot-20.jpg", cap:"Archives &mdash; v0.4.0 intro attract" },
{ src:"img/shots/shot-21.jpg", cap:"Cradle &mdash; v0.4.0 intro attract" },
{ src:"img/shots/shot-24.jpg", cap:"Aztec &mdash; v0.4.0 intro attract" },
{ src:"img/shots/shot-27.jpg", cap:"Control &mdash; v0.4.0 intro attract" },
{ src:"img/shots/shot-28.jpg", cap:"Streets &mdash; v0.4.0 intro attract" },
{ src:"img/shots/shot-29.jpg", cap:"Jungle &mdash; v0.4.0 intro attract" }
];
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;
cap.innerHTML = 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>
+24
View File
@@ -0,0 +1,24 @@
---
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.
---
## 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.
*More: [internals](internals.md) §7 (input & saves); the aim model's
provenance is the D194 lineage in `port/src/input.c`.*
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 MiB

+4
View File
@@ -1441,6 +1441,10 @@ Some decomp functions write past a local array in edge cases. On the N64, IDO's
Also the read side: D496 (`generate_language_specific_text_for_weapon` strcat'd onto an uninitialised caller stack buffer for 3+ players; init under `AVOID_UB`). Also the read side: D496 (`generate_language_specific_text_for_weapon` strcat'd onto an uninitialised caller stack buffer for 3+ players; init under `AVOID_UB`).
## D25. Cross-platform goldens: a fixed frame stem is only comparable at settled gameplay (D521)
`GE_PCDUMP`/`GE_QUITFRAME` step the port's *sim* frame counter (`port/src/video.c:1406` `++frames`, one per `portRenderGfxTask()` at `port/src/libultra.c:1256`), and retrace messages are dropped at the posting site when the game's queue is nearly full (`port/src/libultra.c:753`), so a slow box stretches wall time without advancing `frames` -- frame stems are comparable, wall time is not. What is NOT comparable is any stem inside the intro flyby, whose settle point is wall-clock paced (D117): on a ~54 fps Linux box the flyby was still running at frame 900 (settles ~830-1130 run to run) while the Windows golden at 900 was settled, which reads as a 92.9% "different scene" delta if reported bare. Tells: over-tol2 % >30% at one stem and 1-7% at the next; phash Hamming in the 80s; a dense dump whose consecutive diffs sit at 80-95% and drop to 1-5% once settled. Never compare across platforms with `GE_DETERM=1` (call-sequenced retraces: stable run to run, different frame -> sim mapping). Before calling a residual band a renderer bug, classify it: a constant ~20 delta confined to a screen band (Archives' top 3 grid rows, all three stems) is systematic and needs a same-sim-state capture pair; a uniform d 3-6 over the whole frame with cell means <= 11 (Cuba) is the dither/quantization noise floor. **RESOLVED 2026-10-05 (D525):** both residual bands were *save state*, not renderer — the pair that closed them was a re-round of the whole linux set with `data/ge007.eep` pinned present (Archives 19.956-24.554% -> 3.590-3.859%, worst cell 50.8 -> 2.31; Streets 1200's 16.0-148.4 cell-mean band -> 3.616-3.983%, worst cell 4.43). The diagnostic tell that separates the two: re-capture one side with the save present and the band collapses to the noise floor, while a renderer defect survives identical sim state.
## E. Process / method notes ## E. Process / method notes
- **Never `exit()` while another thread may be inside the GL driver (D344).** - **Never `exit()` while another thread may be inside the GL driver (D344).**
+26
View File
@@ -0,0 +1,26 @@
---
title: Saves
description: How the GoldenEye 007 PC port backs the N64's EEPROM save with a plain file you can back up.
---
## Saves
GoldenEye's N64 save is **EEPROM-based** — the controller's EEPROM
(`osEeprom*`), not a Memory Pak. The port backs it with a **plain file**,
`data/ge007.eep` next to the executable, so your progress is a file you can
copy to back it up (or move to another machine).
A few details:
- The **PFS / Memory-Pak and motor (Rumble Pak) code** is only for accessory
detection and is stubbed to report "no accessory," so the game proceeds
without a Memory Pak.
- **Emulator saves can be imported** — copy a `GOLDENEYE-usa.eep` in as
`data/ge007.eep` (see the README's *Using an emulator save*).
- The save's **checksum is part of the file format**. The PRNG/CRC the save
depends on is kept byte-stable, because changing it would silently
invalidate existing saves — a porting-notes finding (D9).
- `-fresh` (a launch option) wipes the save and settings for a clean-slate run.
*More: [internals](internals.md) §7; the checksum finding is
[porting notes](porting-notes.md) D9.*
+1 -1
View File
@@ -4,7 +4,7 @@ description: What a release actually installs (no networking, no telemetry, no R
and how faithfully the port tracks the original N64 game's logic. and how faithfully the port tracks the original N64 game's logic.
--- ---
# Security & fidelity status ## Security & fidelity status
This page answers two questions plainly: *what does installing this put on This page answers two questions plainly: *what does installing this put on
your machine*, and *how faithfully does the port actually reproduce the your machine*, and *how faithfully does the port actually reproduce the
+31
View File
@@ -0,0 +1,31 @@
---
title: The asset pipeline
description: How the GoldenEye 007 PC port turns N64 ROM assets into the model and graphics files it loads at runtime.
---
## The asset pipeline
The N64 stored the game's assets (models, textures, fonts, level and cutscene
data) inside the ROM in on-hardware formats a PC can't use directly. The port
converts them into two "sidecar" files it loads at runtime:
- **`pcmodels`** — the 3D models (a PC layout of the N64's RZ model format).
- **`pccg`** — the graphics: textures, fonts and the rest of the ROM data.
Each is a single concatenated image plus a small `manifest.csv` (name, offset,
size) that the loader reads — `port/src/pcmodels.c` and `port/src/pccg.c`.
**Where they come from.** Two paths produce the same files:
- **A downloaded build** generates them from *your* ROM on first start, into
`data/pcmodels-<region>/` and `data/pccg-<region>/` next to the executable.
Delete them at any time — they are made again on the next start.
- **Building from source** runs the offline Python converters in `tools_pc/`
to produce the same files (see the [Building](building.md) guide, §4).
This is one of the deliberate differences from the Perfect Dark port, which
fixes up the N64 asset formats at load time: GoldenEye's serialized formats
are converted **offline** instead.
*More: [building](building.md) §2 and §4; the loaders are
`port/src/pcmodels.c` and `port/src/pccg.c`.*
+36
View File
@@ -0,0 +1,36 @@
---
title: The software RSP
description: How the GoldenEye 007 PC port emulates the N64's Reality Signal Processor (the graphics coprocessor) in software and bypasses the RDP.
---
## The software RSP
On the N64, the **Reality Signal Processor (RSP)** is the graphics
coprocessor. The R4300 CPU builds a list of graphics commands each frame
(the **GBI display list**); the RSP runs a "fast3d" microcode that turns
those commands into **RDP** (rasterizer) commands, which the RDP turns into
pixels.
The port does two things to that pipeline:
- **It emulates the RSP in software.** `port/fast3d/` (~7,000 lines of C++,
adapted from the Perfect Dark port) *is* the RSP: it interprets the GBI
display list the game builds and, instead of emitting RDP commands, emits
**OpenGL** calls directly.
- **It bypasses the RDP entirely** — there is no rasterizer to emulate,
because the software RSP talks straight to the GPU.
The game's own R4300 code runs natively and is unaware of the difference: it
builds display lists and "starts the RSP" exactly as it would on hardware. A
replacement scheduler (`pdsched.c`) catches the "start the RSP" call and runs
the software RSP on the render thread.
GoldenEye's custom commands are handled by the same interpreter: the packed
four-triangle `G_TRI4` command and the fade/blend **color-combiner** modes
(compiled on the fly from the N64's combiner equation to a GLSL shader by
`gfx_cc.cpp`). The N64's own RSP microcode (`rsp/graphics/gmain.s`) is not
run on PC but is the reference the software RSP's command decoding is checked
against.
*More: [internals](internals.md) §2 (the reference architecture) and §5
(GoldenEye-specific graphics work).*
+43
View File
@@ -0,0 +1,43 @@
---
title: Under the Hood
description: The technical documentation for the GoldenEye 007 PC port; the architecture, the N64-to-PC bug catalogue, and how the project is developed.
---
## Under the hood
The technical documentation for the port: how it works under the covers, the
catalogue of N64→PC bugs it had to fix, and how the project is developed.
(Building and running from source live on the separate
[Building](building.md) page.)
### How it works
- **[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.
### How it's developed
- **[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.
### Topic notes
Short, single-topic notes — the quick-orientation version of the longer docs
above:
- **[The software RSP](the-software-rsp.md)** — how the N64's graphics
coprocessor is emulated in software.
- **[The asset pipeline](the-asset-pipeline.md)** — how ROM assets become the
files the port loads at runtime.
- **[Framerate and pacing](framerate-and-pacing.md)** — the steady 60 fps,
frame pacing and per-frame timing.
- **[Input and aim](input-and-aim.md)** — controllers, keyboard, mouse aim
and rebinding.
- **[Saves](saves.md)** — the file-backed N64 save.
+122 -1
View File
@@ -1892,7 +1892,8 @@ static inline bool tri_dbg(void) {
const char* a = getenv("GE_D75D"); const char* a = getenv("GE_D75D");
const char* b = getenv("GE_ZF"); const char* b = getenv("GE_ZF");
const char* c = getenv("GE_D303"); const char* c = getenv("GE_D303");
s_tri_dbg = ((a && *a) || (b && *b) || (c && *c)) ? 1 : 0; const char* d = getenv("GE_D526");
s_tri_dbg = ((a && *a) || (b && *b) || (c && *c) || (d && *d)) ? 1 : 0;
} }
return s_tri_dbg != 0; return s_tri_dbg != 0;
} }
@@ -1978,6 +1979,125 @@ static void d75d_note_emit_z(uint32_t f, struct LoadedVertex* const* v_arr, int
d75d_env[0]=rdp.env_color.r; d75d_env[1]=rdp.env_color.g; d75d_env[2]=rdp.env_color.b; d75d_env[3]=rdp.env_color.a; d75d_env[0]=rdp.env_color.r; d75d_env[1]=rdp.env_color.g; d75d_env[2]=rdp.env_color.b; d75d_env[3]=rdp.env_color.a;
} }
/* D526 (docs/dev/findings.md §D526): per-triangle draw-state census
* probe, text-only. Retained as a generic transparency/texture triage
* tool (the D195/D266/D268/D273 bug class; the P13 Dam-ending grate it
* targeted, D526, was confirmed faithful N64 behaviour on 1964/GEPD,
* so no fix was needed). GE_D526="lo-hi" = sim-frame range (the same
* dash syntax as GE_D75D); optional GE_D526BOX="x0 y0 x1 y1" targets
* an NDC region (same syntax as GE_ZFBOX; default = on-screen);
* GE_D526MAX caps per-triangle lines (default 400). For every emitted
* triangle in range whose NDC bbox intersects the region, one line:
* frame, NDC bbox, z/w, geometry_mode, other_mode_l/h, combine word,
* sampled tile 0 (and 1 in 2-cycle) tmem/fmt/siz, first_tile_index,
* prim alpha, vertex alpha min/max, use_alpha/modulate/invisible.
* Plus one per-frame line aggregating the in-region tris by
* (comb, oml, omh, alpha) signature, so a suspect surface's signature
* stands out without enumerating every tri. Zero cost unless
* GE_D75D/GE_ZF/GE_D303/GE_D526 is set (folded into the D474 tri_dbg
* gate). */
static int d526_on = -1;
static int d526_lo = -1, d526_hi = 0x7FFFFFFF, d526_max = 400;
static uint32_t d526_lines = 0;
static float d526_box[4] = { -1.05f, -1.05f, 1.05f, 1.05f };
static int d526_box_on = 0;
#define D526_SIGS 32
static struct { uint64_t comb; uint32_t oml, omh; int alpha; uint32_t cnt; } d526_sigs[D526_SIGS];
static int d526_nsigs = 0;
static uint32_t d526_sig_frame = 0, d526_sig_total = 0;
static void d526_flush_sigs(uint32_t f) {
if (d526_nsigs == 0) { d526_sig_frame = f; return; }
fprintf(stderr, "D526F: f=%u inbox=%u sigs: ", f, d526_sig_total);
for (int i = 0; i < d526_nsigs; i++)
fprintf(stderr, "(comb=0x%016llx oml=0x%08x omh=0x%08x a=%d)x%u ",
(unsigned long long)d526_sigs[i].comb, d526_sigs[i].oml,
d526_sigs[i].omh, d526_sigs[i].alpha, d526_sigs[i].cnt);
fprintf(stderr, "\n");
d526_nsigs = 0; d526_sig_total = 0;
d526_sig_frame = f;
}
static void d526_atexit(void) { d526_flush_sigs(0xFFFFFFFFu); }
static void d526_init(void) {
if (d526_on >= 0) return;
d526_on = 0;
const char* e = getenv("GE_D526");
if (e && *e) {
d526_on = 1;
d526_lo = atoi(e);
const char* dash = strchr(e, '-');
if (dash) d526_hi = atoi(dash + 1);
}
if (d526_on) {
const char* m = getenv("GE_D526MAX");
if (m && *m) d526_max = atoi(m);
const char* b = getenv("GE_D526BOX");
if (b && *b && sscanf(b, "%f %f %f %f",
&d526_box[0], &d526_box[1], &d526_box[2], &d526_box[3]) == 4)
d526_box_on = 1;
atexit(d526_atexit);
}
}
static void d526_note_emit(uint32_t f, struct LoadedVertex* const* v_arr, int use_alpha, int use_modulate) {
d526_init();
if (!d526_on) return;
if (f < (uint32_t)d526_lo || f > (uint32_t)d526_hi) return;
if (f != d526_sig_frame) d526_flush_sigs(f);
float mnx = 1e9f, mxx = -1e9f, mny = 1e9f, mxy = -1e9f;
float zsum = 0.f, wsum = 0.f; int vmin = 255, vmax = -1, ok = 0;
for (int i = 0; i < 3; i++) {
float w = v_arr[i]->w;
if (w <= 0.f) continue;
float nx = v_arr[i]->x / w, ny = v_arr[i]->y / w;
if (nx < mnx) mnx = nx; if (nx > mxx) mxx = nx;
if (ny < mny) mny = ny; if (ny > mxy) mxy = ny;
zsum += v_arr[i]->z / w; wsum += w;
int a = v_arr[i]->color.a;
if (a < vmin) vmin = a; if (a > vmax) vmax = a;
ok = 1;
}
if (!ok) return;
if (d526_box_on)
if (mxx < d526_box[0] || mnx > d526_box[2] || mxy < d526_box[1] || mny > d526_box[3]) return;
else if (mxx < -1.05f || mnx > 1.05f || mxy < -1.05f || mny > 1.05f) return; // off-screen
d526_sig_total++;
int found = 0;
for (int i = 0; i < d526_nsigs; i++) {
if (d526_sigs[i].comb == rdp.combine_mode && d526_sigs[i].oml == rdp.other_mode_l &&
d526_sigs[i].omh == rdp.other_mode_h && d526_sigs[i].alpha == use_alpha) {
d526_sigs[i].cnt++; found = 1; break;
}
}
if (!found && d526_nsigs < D526_SIGS) {
d526_sigs[d526_nsigs].comb = rdp.combine_mode;
d526_sigs[d526_nsigs].oml = rdp.other_mode_l;
d526_sigs[d526_nsigs].omh = rdp.other_mode_h;
d526_sigs[d526_nsigs].alpha = use_alpha;
d526_sigs[d526_nsigs].cnt = 1;
d526_nsigs++;
}
if (d526_lines++ >= (uint32_t)d526_max) return;
const uint32_t fi = rdp.first_tile_index;
const uint32_t t0 = fi + gfx_lod_tile_offset(0);
const uint32_t omh = rdp.other_mode_h;
const int two = ((omh & (3U << G_MDSFT_CYCLETYPE)) == G_CYC_2CYCLE);
const uint32_t t1 = two ? fi + gfx_lod_tile_offset(1) : t0;
const int inv = (rdp.other_mode_l & (3u << 24)) == ((uint32_t)G_BL_0 << 24) &&
(rdp.other_mode_l & (3u << 20)) == ((uint32_t)G_BL_CLR_MEM << 20);
fprintf(stderr,
"D526: f=%u ndc=(%.3f,%.3f)-(%.3f,%.3f) zw=%.4f gm=0x%08x oml=0x%08x omh=0x%08x comb=0x%016llx fi=%u "
"tile0[tmem=%u fmt=%u siz=%u]",
f, mnx, mny, mxx, mxy, (wsum > 0.f ? zsum / wsum : -1.f),
rsp.geometry_mode, rdp.other_mode_l, omh,
(unsigned long long)rdp.combine_mode, fi,
rdp.texture_tile[t0].tmem, rdp.texture_tile[t0].fmt, rdp.texture_tile[t0].siz);
if (two)
fprintf(stderr, " tile1[tmem=%u fmt=%u siz=%u]",
rdp.texture_tile[t1].tmem, rdp.texture_tile[t1].fmt, rdp.texture_tile[t1].siz);
fprintf(stderr, " pa=%u va=[%d,%d] alpha=%d mod=%d inv=%d 2cyc=%d\n",
rdp.prim_color.a, vmin, vmax, use_alpha, use_modulate, inv, two);
}
static void gfx_sp_vertex(size_t n_vertices, size_t dest_index, const Vtx* vertices) { static void gfx_sp_vertex(size_t n_vertices, size_t dest_index, const Vtx* vertices) {
const size_t d75_di0 = dest_index; /* D75 probe: loop below mutates dest_index */ const size_t d75_di0 = dest_index; /* D75 probe: loop below mutates dest_index */
SUPPORT_CHECK(n_vertices <= MAX_VERTICES); SUPPORT_CHECK(n_vertices <= MAX_VERTICES);
@@ -2735,6 +2855,7 @@ static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bo
d75d_note_emit(videoGetFrameCount(), v_arr); // survived all rejection gates -> reaches GL d75d_note_emit(videoGetFrameCount(), v_arr); // survived all rejection gates -> reaches GL
d303_note_emit(videoGetFrameCount(), v_arr); // TEMP D303 d303_note_emit(videoGetFrameCount(), v_arr); // TEMP D303
zf_note_emit(videoGetFrameCount(), v_arr); // TEMP D306/D308 zf_note_emit(videoGetFrameCount(), v_arr); // TEMP D306/D308
d526_note_emit(videoGetFrameCount(), v_arr, use_alpha ? 1 : 0, use_modulate ? 1 : 0); // D526 census
d75d_note_emit_z(videoGetFrameCount(), v_arr, (used_textures[0] || used_textures[1]) ? 1 : 0, comb->used_textures[0] ? 1 : 0); d75d_note_emit_z(videoGetFrameCount(), v_arr, (used_textures[0] || used_textures[1]) ? 1 : 0, comb->used_textures[0] ? 1 : 0);
} }
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
+338
View File
@@ -0,0 +1,338 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
<https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Moe Ghoul>, 1 April 1989
Moe Ghoul, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
+5 -26
View File
@@ -13,7 +13,6 @@
.\tools_pc\audiodebug.ps1 -Fire # + scripted PPK fire pulses .\tools_pc\audiodebug.ps1 -Fire # + scripted PPK fire pulses
.\tools_pc\audiodebug.ps1 -Fire -Trace # + per-sndPlaySfx trace + index histogram .\tools_pc\audiodebug.ps1 -Fire -Trace # + per-sndPlaySfx trace + index histogram
.\tools_pc\audiodebug.ps1 -Fire -Trace -Dump # + raw mixed PCM to audiodump.raw .\tools_pc\audiodebug.ps1 -Fire -Trace -Dump # + raw mixed PCM to audiodump.raw
.\tools_pc\audiodebug.ps1 -AB -Fire # run BOTH modes, print before/after table
.\tools_pc\audiodebug.ps1 -Play # INTERACTIVE playtest, no timeout (D202) .\tools_pc\audiodebug.ps1 -Play # INTERACTIVE playtest, no timeout (D202)
.\tools_pc\audiodebug.ps1 -Soak # 5-minute stability soak .\tools_pc\audiodebug.ps1 -Soak # 5-minute stability soak
@@ -49,9 +48,8 @@ param(
[switch]$Dump, # GE_AUDIODUMP=1 -> audiodump.raw [switch]$Dump, # GE_AUDIODUMP=1 -> audiodump.raw
[switch]$VoiceDump, # GE_VOICEDUMP=1 -> voicedump.raw (per-voice, D202/M-67) [switch]$VoiceDump, # GE_VOICEDUMP=1 -> voicedump.raw (per-voice, D202/M-67)
[switch]$MixerTrace, # GE_MIXERTRACE=1 -- SLOW, see warning below [switch]$MixerTrace, # GE_MIXERTRACE=1 -- SLOW, see warning below
[switch]$Old, # GE_D204_OLD=1: pre-D204 behaviour # (the -AB/-Old A/B switches were removed 2026-10-07, D532: GE_D204_OLD no
# NOTE: GE_D204_OLD was removed from the game; -AB is now a no-op. # longer exists in the game, so they were documented no-ops)
[switch]$AB, # run twice (old, then new) and compare
# Plumbing # Plumbing
[switch]$NoBuild, [switch]$NoBuild,
@@ -121,7 +119,7 @@ if ($GameArgs) { $gameArgv += @($GameArgs | Where-Object { $_ }) }
# ----------------------------------------------------------- run one ------- # ----------------------------------------------------------- run one -------
function Invoke-AudioRun { function Invoke-AudioRun {
param([string]$Tag, [bool]$OldMode) param([string]$Tag)
$stdout = Join-Path $buildDir "audiodebug_$Tag.log" $stdout = Join-Path $buildDir "audiodebug_$Tag.log"
Remove-Item -ErrorAction SilentlyContinue $stdout, Remove-Item -ErrorAction SilentlyContinue $stdout,
@@ -131,14 +129,13 @@ function Invoke-AudioRun {
# Set the probes on THIS process; cmd.exe and the game both inherit them. # Set the probes on THIS process; cmd.exe and the game both inherit them.
$env:GE_D204 = "1" $env:GE_D204 = "1"
if ($OldMode) { $env:GE_D204_OLD = "1" } else { Remove-Item -EA SilentlyContinue Env:\GE_D204_OLD }
if ($Trace) { $env:GE_AUDIOTRACE = "1" } else { Remove-Item -EA SilentlyContinue Env:\GE_AUDIOTRACE } if ($Trace) { $env:GE_AUDIOTRACE = "1" } else { Remove-Item -EA SilentlyContinue Env:\GE_AUDIOTRACE }
if ($Dump) { $env:GE_AUDIODUMP = "1" } else { Remove-Item -EA SilentlyContinue Env:\GE_AUDIODUMP } if ($Dump) { $env:GE_AUDIODUMP = "1" } else { Remove-Item -EA SilentlyContinue Env:\GE_AUDIODUMP }
if ($VoiceDump) { $env:GE_VOICEDUMP = "1" } else { Remove-Item -EA SilentlyContinue Env:\GE_VOICEDUMP } if ($VoiceDump) { $env:GE_VOICEDUMP = "1" } else { Remove-Item -EA SilentlyContinue Env:\GE_VOICEDUMP }
if ($MixerTrace) { $env:GE_MIXERTRACE = "1" } else { Remove-Item -EA SilentlyContinue Env:\GE_MIXERTRACE } if ($MixerTrace) { $env:GE_MIXERTRACE = "1" } else { Remove-Item -EA SilentlyContinue Env:\GE_MIXERTRACE }
if ($inputScript) { $env:GE_INPUTSCRIPT = $inputScript } else { Remove-Item -EA SilentlyContinue Env:\GE_INPUTSCRIPT } if ($inputScript) { $env:GE_INPUTSCRIPT = $inputScript } else { Remove-Item -EA SilentlyContinue Env:\GE_INPUTSCRIPT }
$mode = if ($OldMode) { "GE_D204_OLD (pre-fix)" } else { "current build" } $mode = "current build"
$what = if ($Play) { "interactive - play until you close the window" } else { "$Seconds s" } $what = if ($Play) { "interactive - play until you close the window" } else { "$Seconds s" }
Write-Host "`n>>> run [$Tag] $mode - $what" -ForegroundColor Green Write-Host "`n>>> run [$Tag] $mode - $what" -ForegroundColor Green
if ($inputScript) { if ($inputScript) {
@@ -293,26 +290,8 @@ function Show-TraceSummary {
} }
# ------------------------------------------------------------------ go ----- # ------------------------------------------------------------------ go -----
if ($AB) { $log = Invoke-AudioRun -Tag "run"
$oldLog = Invoke-AudioRun -Tag "old" -OldMode $true
$newLog = Invoke-AudioRun -Tag "new" -OldMode $false
$a = Get-AudioSummary -LogPath $oldLog -Tag "old"
$b = Get-AudioSummary -LogPath $newLog -Tag "new"
Show-AudioVerdict $a
Show-AudioVerdict $b
Write-Host "`n===================== A/B =====================" -ForegroundColor Yellow
Write-Host (" {0,-24} {1,12} {2,12}" -f "", "GE_D204_OLD", "current")
Write-Host (" {0,-24} {1,12:N3} {2,12:N3}" -f "rt (min, steady)", $a.RtMin, $b.RtMin)
Write-Host (" {0,-24} {1,12} {2,12}" -f "queue min", $a.QMin, $b.QMin)
Write-Host (" {0,-24} {1,12} {2,12}" -f "queue max", $a.QMax, $b.QMax)
Write-Host (" {0,-24} {1,12} {2,12}" -f "dropped blocks", $a.Drop, $b.Drop)
Write-Host (" {0,-24} {1,12} {2,12}" -f "largest block", $a.MaxBlock, $b.MaxBlock)
Write-Host "`n Both runs used the SAME binary (GE_D204_OLD switches behaviour at" -ForegroundColor DarkGray
Write-Host " runtime), so this is free of build-to-build variance." -ForegroundColor DarkGray
} else {
$log = Invoke-AudioRun -Tag "run" -OldMode ([bool]$Old)
Show-AudioVerdict (Get-AudioSummary -LogPath $log -Tag "run") Show-AudioVerdict (Get-AudioSummary -LogPath $log -Tag "run")
}
if ($Trace) { Show-TraceSummary } if ($Trace) { Show-TraceSummary }
Write-Host "`nlogs: build-pc\audiodebug_*.log" -ForegroundColor DarkGray Write-Host "`nlogs: build-pc\audiodebug_*.log" -ForegroundColor DarkGray
+99
View File
@@ -0,0 +1,99 @@
#!/bin/bash
# P7 golden capture on Linux (mirrors tools_pc/verify.sh's golden recipe).
#
# Usage: capture_p7.sh [-b BUILD_DIR] <level_name>:<num> [<level_name>:<num> ...]
# BUILD_DIR build dir holding ge007.x86_64, relative to the repo root
# (default: build-linux). The repo root is the parent of the
# directory this script lives in, so the script can be invoked
# from anywhere; no machine-specific path is baked in.
# Writes only inside the repo: <repo>/data/ge007.ini + ge007.eep (the
# pinned 640x480 ini and the canonical save, re-pinned before every level;
# the local save is only snapshotted for the exit restore),
# <repo>/ppm (the game's dump dir, removed after each level), and
# <repo>/tools_pc/golden/<level>/linux/.
# Per-level run logs go to <repo>/cap_<level>.log: scratch, never tracked.
# DISPLAY must be inherited from the caller -- the capture needs a real GL
# context, and there is deliberately no default display/XAUTHORITY here.
#
# Recipe: the per-level windows/scripts/seed are READ OUT OF tools_pc/verify.sh
# (golden_dump_for / golden_script_for / GOLDEN_SEED) so this tool and the gate
# cannot drift apart -- see tools_pc/golden/README.md for what the windows are
# and why (D522). GE_QUITFRAME = last requested frame + 2 (orderly quit, D344).
#
# D523 (SUPERSEDED by D529): the win/ set was captured with a save file, and
# the frame depends on the save's CONTENT (D529's A/B: facility 177.47 on the
# 2026-10-05 playtest save, 0.0-0.25 on the four other candidates). This tool
# installs the CANONICAL save (tools_pc/golden/ge007.eep, in-tree; verify.sh
# does the same for the gate) rather than trusting a local eep -- a local
# playtest save is exactly this class of drift. (History: the P7 round of
# 2026-10-04 ran with a cleared eep; that set was re-captured 2026-10-05, D525.)
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
BUILD_DIR=build-linux
if [ "${1:-}" = -b ]; then
[ -n "${2:-}" ] || { echo "error: -b needs a BUILD_DIR argument"; exit 2; }
BUILD_DIR="$2"; shift 2
fi
[ "$#" -gt 0 ] || { echo "error: no <level_name>:<num> specs given"; exit 2; }
EXE="$ROOT/$BUILD_DIR/ge007.x86_64"
[ -x "$EXE" ] || { echo "error: no executable at $EXE (pass -b BUILD_DIR)"; exit 2; }
[ -n "${DISPLAY:-}" ] || { echo "error: DISPLAY is unset; the capture needs a real GL context (no default provided)"; exit 2; }
VSH="$ROOT/tools_pc/verify.sh"
[ -f "$VSH" ] || { echo "error: no tools_pc/verify.sh under $ROOT (the recipe lives there)"; exit 2; }
CANON_EEP="$ROOT/tools_pc/golden/ge007.eep"
[ -f "$CANON_EEP" ] || { echo "error: canonical golden save missing: $CANON_EEP (D529)"; exit 2; }
cd "$ROOT" # the loop's dump/golden paths are root-relative (ppm/, tools_pc/golden/)
EEP="$ROOT/data/ge007.eep"
EEP_ORIG=$(mktemp); EEP_HAD=0
[ -f "$EEP" ] && { EEP_HAD=1; cp "$EEP" "$EEP_ORIG"; }
cp "$CANON_EEP" "$EEP"
INI="$ROOT/data/ge007.ini"
INI_ORIG=""
[ -f "$INI" ] && { INI_ORIG=$(mktemp); cp "$INI" "$INI_ORIG"; }
cleanup() {
[ -n "$INI_ORIG" ] && cp "$INI_ORIG" "$INI" 2>/dev/null
if [ "$EEP_HAD" = 1 ]; then cp "$EEP_ORIG" "$EEP" 2>/dev/null; else rm -f "$EEP" 2>/dev/null; fi
rm -f "$INI_ORIG" "$EEP_ORIG" 2>/dev/null
}
trap cleanup EXIT INT TERM
# recipe comes from the tracked gate tool, not from this file
eval "$(grep -m1 '^GOLDEN_DUMP=' "$VSH")"
eval "$(grep -m1 '^golden_dump_for()' "$VSH")"
eval "$(grep -m1 '^golden_script_for()' "$VSH")"
eval "$(grep -m1 '^GOLDEN_SEED=' "$VSH")"
for spec in "$@"; do
name="${spec%%:*}"; num="${spec##*:}"
[ "$name" != "$num" ] || { echo "error: bad spec $spec"; exit 2; }
dump=$(golden_dump_for "$name")
script=$(golden_script_for "$name")
IFS='-:' read -r lo hi step <<< "$dump"
last=$(( lo + step * ((hi - lo) / step) ))
qf=$(( last + 2 ))
printf '[Window]\nWidth = 640\nHeight = 480\n' > "$INI"
# D531: re-pin the CANONICAL save before every level (the frame depends on
# the save's CONTENT, D529). The D524-era re-pin restored $EEP_ORIG (the
# local pre-capture save, or an empty file when none existed), so the
# pre-loop canonical install below was silently overwritten from level 1:
# the 2026-10-05 box re-round ran under the box's local save, not the
# canonical one. EEP_ORIG is kept only for the exit restore.
cp "$CANON_EEP" "$EEP"
rm -f ge007.crash.log
rm -rf ppm
t0=$(date +%s)
( export GE_PCDUMP="$dump" GE_QUITFRAME="$qf" GE_INPUTSCRIPT="$script" \
GE_RSEED="$GOLDEN_SEED"; timeout 300 "$EXE" -level_"$num" ) > "cap_$name.log" 2>&1
rc=$?
n=0
if [ -d ppm ]; then
mkdir -p "tools_pc/golden/$name/linux"
for f in ppm/*.ppm; do
stem=$(basename "$f" .ppm)
python3 -c "from PIL import Image; Image.open('$f').save('tools_pc/golden/$name/linux/$stem.png')"
n=$((n+1))
done
rm -rf ppm
fi
t1=$(date +%s)
echo "== $name rc=$rc frames=$n secs=$((t1-t0)) window=$dump"
done
+3 -1
View File
@@ -52,7 +52,9 @@ def file_generate_crc(data: bytes, buggy: bool):
return c1 & 0xFFFFFFFF, c2 & 0xFFFFFFFF return c1 & 0xFFFFFFFF, c2 & 0xFFFFFFFF
def main(): def main():
src = sys.argv[1] if len(sys.argv) > 1 else r"C:\Users\james\Downloads\ge-issue87-artifacts\ge007.eep" if len(sys.argv) < 2:
sys.exit("usage: d295_crc.py <ge007.eep> [out.eep]")
src = sys.argv[1]
dst = sys.argv[2] if len(sys.argv) > 2 else None dst = sys.argv[2] if len(sys.argv) > 2 else None
eep = bytearray(open(src, "rb").read()) eep = bytearray(open(src, "rb").read())
assert len(eep) == 2048, len(eep) assert len(eep) == 2048, len(eep)
+96 -4
View File
@@ -2,17 +2,37 @@
`<level>/<platform>/frame_NNNNNN.png`: reference `GE_PCDUMP` captures for `<level>/<platform>/frame_NNNNNN.png`: reference `GE_PCDUMP` captures for
`tools_pc/verify.sh`, one folder per solo level (all 21, `cuba` = the ending) `tools_pc/verify.sh`, one folder per solo level (all 21, `cuba` = the ending)
and per platform (`win` today; `linux` to come). and per platform (`win`, and `linux` — 21 levels x 3 frames on each, captured
2026-10-05 at the same stems).
**Recipe** (2026-10-02 re-base; `verify.sh` applies all of it): **Recipe** (2026-10-02 re-base; `verify.sh` applies all of it):
- `data/ge007.ini` pinned to ONLY `[Window] Width = 640, Height = 480` (every - `data/ge007.ini` pinned to ONLY `[Window] Width = 640, Height = 480` (every
other option at its compiled-in default; verify.sh backs yours up). other option at its compiled-in default; verify.sh backs yours up).
- `GE_RSEED=0x0123456789abcdef` (pinned PRNG). - `GE_RSEED=0x0123456789abcdef` (pinned PRNG).
- `GE_INPUTSCRIPT="20:START"` (skips the intro flyby; the scripted pad is - `GE_INPUTSCRIPT="20:START"` (skips the intro flyby; the scripted pad is
the only input) and `GE_PCDUMP="900-1500:300"`: frames 900/1200/1500 are the only input). Window per level, exactly as `verify.sh`'s `golden_dump_for`
settled gameplay on every level. **Cuba** instead: no input states it (D522 re-base 2026-10-05 — the 900 stem is NOT settled gameplay on
every level: it fails the 1% limit on Surface 2 / Streets / Depot / Cradle and
is still inside Dam's flyby):
`900-1500:300` (900/1200/1500) on the 14 levels where 900 is settled,
`1000-1400:200` (1000/1200/1400) on Surface 2 / Streets / Depot / Cradle /
Frigate, `1500-1700:100` (1500/1600/1700) on Dam, and **Cuba** with no input
(`1:SNONE`) and `300-900:300` (the ending cutscene; it returns to the boot (`1:SNONE`) and `300-900:300` (the ending cutscene; it returns to the boot
screens before frame 1200). screens before frame 1200). `framediff` walks the CANDIDATE frames, so a
sweep's `--dump` must hit the golden stems exactly — never widen a stride.
- The gate INSTALLS THE CANONICAL SAVE `tools_pc/golden/ge007.eep` (2 KB, in-tree;
D523, **superseded by D529**): the frame depends on the save's **content**, not
its presence — D523's "presence, not which save" claim (worst_cell 0.175625 on
each of the four candidate saves, 21.557% with none) was only ever measured on
Archives, and the 2026-10-07 A/B refutes it (facility 177.47 scene-level on the
2026-10-05 playtest save, 0.0-0.25 on every other candidate on disk). `verify.sh`
and `capture_p7.sh` install the canonical eep the same way they pin the ini
(snapshot the local file if any, install the canonical one, restore-or-remove on
exit), so the gate is reproducible on a fresh clone and in CI. A local playtest
save is **never** the gate's input. The linux 63-frame set was re-captured
on the box under the canonical eep 2026-10-07 (D531; the 2026-10-05
re-round, D530, turned out to have run under the box-LOCAL save — the
`capture_p7.sh` per-level re-pin bug, fixed in D531) and is valid.
- Every run ends via `GE_QUITFRAME` (orderly quit; never a hard kill, D344). - Every run ends via `GE_QUITFRAME` (orderly quit; never a hard kill, D344).
**Gate** (two tiers): the structural framediff (cell means, coverage, **Gate** (two tiers): the structural framediff (cell means, coverage,
@@ -21,12 +41,45 @@ with a per-level limit: 1% of pixels, 3% for Jungle and Surface 2, Cuba
structural only. Measured run to run: <= 0.57% on 18 levels, Jungle 1.75%, structural only. Measured run to run: <= 0.57% on 18 levels, Jungle 1.75%,
Surface 2 2.33%, Cuba 36%; a global texture-filter switch measured 22%. Surface 2 2.33%, Cuba 36%; a global texture-filter switch measured 22%.
**Cross-platform rule (D521, P7):** a frame stem is comparable across
platforms only if it is settled gameplay on *every* platform in the set.
The intro flyby is wall-clock paced (D117): a box that cannot hold 60 fps
stretches wall time without advancing the sim-frame counter, so the flyby
settles at a different frame per box. On the Linux capture box (Mesa /
Intel HD 3000; the golden stems rendered in 11.8-15.1 ms) frame 900 is still
inside the flyby (it settles between ~830 and ~1130 run to run) while the
win/ golden at 900 is settled -- linux/900 vs win/900 is a scene offset
(92.863% over tol 2, phash 83 on Dam), NOT a renderer defect. After the 2026-10-05
re-round both platforms sit at the same stems and the cross-platform spread is
0.348-3.983% over tol 2 (structural tier 21/21 clean, phash <= 6 except cradle
at 40; Cuba 23.7-36.3% is structural-tier-only) — informational only. The gate
each platform ships is its own captures against its own goldens; never call the
cross-platform spread a parity gate. 1200/1500 are
settled on both and measure the real driver delta. `GE_DETERM=1` is for
Linux-vs-Linux regression runs only (it changes the frame -> sim mapping;
against the win/ goldens it measures 15.788% at 900, 14.594% at 1200 and
16.171% at 1500, which is expected and must never be read as a delta).
Regenerate one level after a deliberate visual change: run Regenerate one level after a deliberate visual change: run
`tools_pc/verify.sh <level>` twice to confirm the new look is stable, then `tools_pc/verify.sh <level>` twice to confirm the new look is stable, then
capture with the recipe above and replace `<level>/<platform>/*.png`. capture with the recipe above and replace `<level>/<platform>/*.png`.
## History ## History
- **D531 (2026-10-07):** `capture_p7.sh`'s D529 adaptation was incomplete —
the per-level re-pin restored the pre-capture LOCAL save (D524-era line),
silently overwriting D529's pre-loop canonical install, so the 2026-10-05
box re-round (D530) ran under the box-local save, not the canonical one.
Fixed (loop now re-pins the canonical save per level; `cd "$ROOT"` added)
and re-captured: fresh 21/21 box round, 63 frames committed, replacing the
D530 set (47/63 frames differ — save content demonstrably moves the frames;
16 byte-identical save-independent scenes).
- **D530 (2026-10-07, AMENDED BY D531):** the linux set was re-rounded on the
box (X220, tree `442eb2ff`; `~/p7-reround.log` hit `P7 re-round DONE`
2026-10-05 12:53 EDT, 21/21 rc=0) and committed, replacing the D525
cleared-eep set (51/63 frames differ; 12 save-independent scenes byte-
identical). The "canonical eep" provenance claim was wrong — see D531.
- **D168 (2026-09-01):** the `GE_PCDUMP` PPM writer emitted `glReadPixels` rows - **D168 (2026-09-01):** the `GE_PCDUMP` PPM writer emitted `glReadPixels` rows
unreversed, so every capture — and this baseline set — was vertically unreversed, so every capture — and this baseline set — was vertically
flipped. Writer fixed (`port/fast3d/gfx_opengl.cpp`); this set regenerated flipped. Writer fixed (`port/fast3d/gfx_opengl.cpp`); this set regenerated
@@ -51,3 +104,42 @@ capture with the recipe above and replace `<level>/<platform>/*.png`.
`verify.sh` no longer hard-kills (taskkill of every `ge007.x86_64.exe`, `verify.sh` no longer hard-kills (taskkill of every `ge007.x86_64.exe`,
including a maintainer's game); it quits via `GE_QUITFRAME` and only stops including a maintainer's game); it quits via `GE_QUITFRAME` and only stops
its own PID as a watchdog fallback. its own PID as a watchdog fallback.
- **2026-10-04 (P7):** first `<level>/linux/` set captured (21 levels, the
recipe above verbatim) on a real-GL Linux box; `verify.sh`'s linux pixcount/
framediff skip is now driver-conditional (a `glxinfo` renderer probe, D521)
-- llvmpipe/softpipe/swrast still skip, a real driver runs the full pixel
gate. Structural framediff linux-vs-win: 19/21 clean; Dam (frame 900, the
flyby) and Streets (frame 1200, a diagonal region delta) fail. Exact over-
tol2: 1.1-6.6% on 17 levels; Archives 20-24.6% (a constant ~20 delta over
the top 3 grid rows only), Cuba 10.7-26.4% (uniform d 3-6, cell means <=
11: dither noise floor), Jungle 5.9-6.6%, Streets 1200 9.6%. See D521.
- **2026-10-05 (D522/D523 re-base):** the win set's window moved on six levels —
Surface 2 / Streets / Depot / Cradle / Frigate to `1000-1400:200`, Dam to
`1500-1700:100`. Measured, same build, honest sequential passes: at 900 those
levels fail the per-pixel limit (Dam 6.542% @900 worst_cell 12.11, Surface 2
3.752%, Streets 1.001%, Depot 1.267%, Cradle 1.754%, Frigate 8.391% on a later
run) and Dam's 900 is still flyby (run-to-run 3.6-3.8% over tol 2, maxchan 168);
Dam's 1200 looked settled on two runs (0.000%) then diverged 5.761% on a third,
so it moved to 1500-1700 (1500/1700 0.000% across three runs, 1600 keeps ~40
moving pixels at 0.013%). At the new stems three independent runs agree within
tol 2. `verify.sh` now pins the save file (D523). **The `<level>/linux/` set was
stale everywhere**, not only on those six levels: P7's 2026-10-04 round ran with
`data/ge007.eep` cleared, and D523 measures that save-file *presence* moves ~every
level (Archives 21.557% with none, 0.175625 worst_cell with any of four saves).
- **2026-10-05 (P7b, linux re-round):** the box re-captured all 21 levels (63 frames,
21/21 rc=0, frames=3, 25-29 s each) at the windows `verify.sh` carries, with the
save pinned present, on the same real-GL box (Mesa 23.0.3 / Intel HD 3000). The
set is now one frame per recipe stem on every level, both platforms. Cross-platform
framediff (linux vs win, tol 2): **structural 21/21 PASS** (worst cell mean 4.43
on streets; phash <= 6 except cradle at 40, a hash tie with cell means 1.17), exact
**0.348-3.983%** on the 20 non-Cuba levels (Cuba 23.7-36.3%, structural tier only)
— about half the stale set's 1.142-6.958%. **D521's two residual bands are closed
by this measurement:** Archives 19.956-24.554% (cell means to 50.8, rows 0-2) is
now 3.590-3.859% (worst cell 2.31, phash 1) and Streets 1200's 16.0-148.4 cell-mean
band is now 3.616-3.983% (worst cell 4.43, phash 0) — both were the save-state
difference, not a renderer. Dam's 900 (92.863%, phash 83) is out of the set; at
1500-1700 Dam measures 2.964-3.443%. **Caveat:** the box's own sweep is crash-detect
only — `glxinfo -B` fails there (rc 255, "unable to open display :0" from a non-login
ssh shell) so `verify.sh`'s fail-closed probe keeps the pixcount/framediff skip even
though the game's readback works; the cross-platform numbers are computed on Windows
against the merged linux PNGs. See D525.
Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 KiB

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 KiB

After

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 333 KiB

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 KiB

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 KiB

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 KiB

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Loaded 100 of 203 files, more files were not shown because too many files have changed in this diff. Show more