From d7b9371afbd14d7cb87281931b2fd28155725a22 Mon Sep 17 00:00:00 2001
From: James Dansereau <106631954+jkdansereau@users.noreply.github.com>
Date: Tue, 6 Oct 2026 18:35:44 -0400
Subject: [PATCH] Docs: public copy, site presentation, release-gate review
(D533, D534)
Public-copy language pass, homepage restructure and Documentation rename,
404 page, dead-anchor and permalink fixes, release-gate sign-off, and the
pre-release review fixes of the 2026-10-05 batch (probe-doc drift,
build-path hygiene, verify.sh and capture safety).
Refs: D533 D534
---
.github/SECURITY.md | 2 +-
.github/release-notes.md | 67 ++--
AGENTS.md | 38 ++-
README.md | 21 +-
build-pc.sh | 4 +-
docs/404.html | 20 ++
docs/ROADMAP.md | 33 +-
docs/_config.yml | 28 +-
docs/_includes/head-custom.html | 133 +++++++-
docs/_layouts/default.html | 31 +-
docs/building.md | 5 +-
docs/dev/GE-ENV-PROBES.md | 1 +
docs/dev/HANDOFF-ARCHIVE.md | 217 ++++++++++++-
docs/dev/agentic-development.md | 23 +-
docs/dev/findings-index.csv | 14 +-
docs/dev/findings.md | 121 +++++--
docs/{under-the-hood.md => documentation.md} | 36 +--
docs/fidelity.md | 72 +++++
docs/framerate-and-pacing.md | 2 +
docs/index.md | 322 +++++++++----------
docs/input-and-aim.md | 2 +
docs/internals.md | 2 +-
docs/porting-notes.md | 6 +-
docs/saves.md | 2 +
docs/security-and-fidelity-status.md | 115 -------
docs/security.md | 87 +++++
docs/the-asset-pipeline.md | 2 +
docs/the-software-rsp.md | 2 +
port/fast3d/gfx_pc.cpp | 4 +-
tools_pc/audiodebug.ps1 | 2 +-
tools_pc/capture_p7.sh | 6 +-
tools_pc/golden/README.md | 8 +-
tools_pc/verify.sh | 22 +-
33 files changed, 982 insertions(+), 468 deletions(-)
create mode 100644 docs/404.html
rename docs/{under-the-hood.md => documentation.md} (50%)
create mode 100644 docs/fidelity.md
delete mode 100644 docs/security-and-fidelity-status.md
create mode 100644 docs/security.md
diff --git a/.github/SECURITY.md b/.github/SECURITY.md
index 192f0203..6e6b6d2e 100644
--- a/.github/SECURITY.md
+++ b/.github/SECURITY.md
@@ -12,7 +12,7 @@ surface is:
A full walkthrough of what a release actually installs on your machine, and
what a from-source build pulls in, is in
-[`docs/security-and-fidelity-status.md`](../docs/security-and-fidelity-status.md).
+[`docs/security.md`](../docs/security.md).
### Why the binaries are unsigned
diff --git a/.github/release-notes.md b/.github/release-notes.md
index 5a87b3ee..f9562a6a 100644
--- a/.github/release-notes.md
+++ b/.github/release-notes.md
@@ -16,12 +16,12 @@ Region: **NTSC-U (US) only**. See [Known issues](#known-issues).
build was played on (e.g. "Played on Windows and Steam Deck: campaign
spot-checks, 2-4P split-screen, a controller-only session"). -->
-The headline is **the settings interface, rebuilt**. Both options screens are
-merged into one, laid out like the Perfect Dark PC port's menu, with plain-English
-wording, real-unit sliders and a one-line description under whichever option you
-select. Alongside it: the game's own N64 control styles as a per-seat preset,
-emulator save files that load directly, a scalable HUD overlay, and a round of
-fidelity fixes checked frame-by-frame against the N64 game.
+The headline is **one real options system**: both options
+screens merged into a single menu, laid out like the Perfect Dark PC port's,
+with the game's own N64 control styles as a per-seat preset, emulator save
+files that load directly, and a scalable HUD overlay. A round of fidelity
+fixes, checked frame-by-frame against the N64 game, rounds out the feature
+list.
---
@@ -37,15 +37,6 @@ fidelity fixes checked frame-by-frame against the N64 game.
- **Display mode** is a dropdown; **Backspace** goes back, matching the rest of
the game's menus (D504 follow-up).
-### Menu wording, units and help text (D505–D507)
-
-- 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).
-
### Control styles: the game's own N64 layouts (D513, D516, D518)
- **Control style** offers **Ext** (this port's scheme, unchanged) and
@@ -79,18 +70,6 @@ fidelity fixes checked frame-by-frame against the N64 game.
**crosshair colour by health** — the four settings the Perfect Dark port has
that this port was missing.
-### Overlay controls are antialiased (D520)
-
-- 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.
-
-### Watch menu keeps its own settings (D349, corrected)
-
-- 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.
-
---
## Fidelity and stability fixes
@@ -101,13 +80,19 @@ fidelity fixes checked frame-by-frame against the N64 game.
- **Aspect ratio:** forced ratios now letterbox/pillarbox without re-cropping the
play area (D508); the rare gun/hand model glitch after changing ratio
mid-level is largely fixed, a rare remainder is logged (D509).
+- **Watch menu:** 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 (D349).
+- **Overlay controls:** 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 (D520).
- **FPS counter:** holding left/right on a Bond settings row no longer drops the
FPS counter (D517).
- **Build:** a fresh build directory no longer produces an untagged executable
name (D515).
- **Tooling:** the asset converter's argument quoting and sidecar size check are
- tightened; the CI cache actions no longer carry stale ROM paths. The golden-frame
- tooling is now non-destructive and drift-free: a verification sweep used to
+ tightened; the CI cache actions no longer carry stale ROM paths. The
+ reference-frame 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
@@ -124,6 +109,16 @@ fidelity fixes checked frame-by-frame against the N64 game.
---
+## The small stuff
+
+27 menu-wording edits across both menus (option names say what they do, not
+what the internal variable is called, D505); sliders carrying **real units** —
+percent, frames, seconds, pixels — standardised across the two menus (D506);
+a **one-line description** of the selected option in the same menu (D507);
+and menu spelling standardised to one convention across both menus.
+
+---
+
## Known issues
| Issue | Impact | Workaround |
@@ -148,10 +143,16 @@ follow-ups.
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
+**Reference-frame gate for this release:** both platforms carry the full 21-level
+reference-frame set (63 frames each) at the same stems. The Windows gate is green
+against its own goldens (21/21, three consecutive clean sweeps); on Linux the
+crash gate is green and the pixel verdict is still owed (see below). The recipe pins the run for you: the save file's
+CONTENT is pinned (the gate installs its own canonical save per level, the same
+way it pins the display ini — D529) and the PRNG seed is hard-pinned (a stray
+`GE_RSEED` in your environment warns and is ignored rather than re-seeding the
+gate — D532), so a gate run and a capture run see the same starting state. The
+cross-platform spread measured on the shipped pairs (0.380-5.120% 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
diff --git a/AGENTS.md b/AGENTS.md
index 40fca429..a7f6d5ff 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -9,6 +9,33 @@
- **Current status:** the README "Status" section (`docs/dev/LEVEL-STATUS.md` is a historical per-level sweep record). Current task + environment: `docs/HANDOFF.md` (a rolling local working file — may be absent in a fresh clone; fall back to the README "Status" section).
- **Dispatching subagents?** `docs/dev-process.md` — task budgets/deadlines, file partitioning, pre-flight, the standard brief template. Every investigation subagent reads `docs/porting-notes.md` first and appends to it.
+## Public site pages (`docs/`) — presentation layer, not a source of truth
+
+The repo's homepage and sub-pages are published by GitHub Pages; the local
+preview is `node scratch/build_preview.mjs site` → `scratch/_site/` (served
+with `node scratch/serve_preview.mjs 8777`). The site is a **presentation
+layer over the reference docs, kept in a separate area, and allowed to go
+stale** — never let it poison a work item:
+
+- **Pure site pages** — `docs/index.md`, `documentation.md`,
+ `the-software-rsp.md`, `the-asset-pipeline.md`, `framerate-and-pacing.md`,
+ `input-and-aim.md`, `saves.md`, `security.md`, `fidelity.md` —
+ summarise the ROADMAP / README / finding log / release notes. **Do not
+ consult them as work information, cross-check against them, or treat a
+ mismatch with `docs/ROADMAP.md` or the finding log as a bug** (that is
+ the expected, independent-staleness case). Update them only when the task
+ says so, and source every fact from the ROADMAP / README / finding log —
+ never the other way round. The footer "last updated" date in
+ `docs/_layouts/default.html` is the same class of site-only detail.
+- **Shared files** — `docs/internals.md`, `porting-notes.md`,
+ `building.md`, `dev-process.md`, `dev/agentic-development.md` are
+ authoritative working docs that the site publishes verbatim; edit them
+ normally as part of dev work and the site picks up the change for free.
+
+Keep the split intact: no working notes migrate into pure site pages, and
+never edit a shared doc merely to make the site read better — if the site is
+stale, fix the site page (or the fact upstream, as its own task).
+
## Non-negotiables
1. **N64 build untouched.** `Makefile`, `tools/`, `rsp/`, `ld/` belong to the N64 build. Never modify them for the PC port.
@@ -45,8 +72,10 @@ mostly agent-authored.
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.
+ only bulk imagery in the tree is `tools_pc/golden/**/*.png`
+ (63 frames × 2 platforms, 126 total, nested per level at
+ `tools_pc/golden//{win,linux}/`), 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
@@ -83,8 +112,9 @@ Needs CMake + SDL2 + zlib + OpenGL, and must run from the MSYS2 MINGW64 shell
1. **`Cannot create temporary file in C:\Windows\: Permission denied`** at the
link step. The PE toolchain (ninja → cmd → gcc/ld) needs a writable
TMP/TEMP; the msys→native env conversion drops it in non-login shells
- (agent harnesses; the `C:/msys64` tree here was built for `D:/M/msys64`,
- so its conversion is unreliable). `build-pc.sh` now **self-heals**: it
+ (agent harnesses; the `C:/msys64` tree here was built for a relocated MSYS2
+ root — `` in the placeholder legend — so its path conversion is
+ unreliable). `build-pc.sh` now **self-heals**: it
probes a native child's TMP (via a file — piped `cmd.exe` stdout is
unreliable under msys console emulation) and, if broken, re-runs
cmake+build under PowerShell with `TMP`/`TEMP` set natively (the
diff --git a/README.md b/README.md
index 108b532f..dfcb658f 100644
--- a/README.md
+++ b/README.md
@@ -48,14 +48,17 @@ project is as much a study of that process as it is a port. See
## 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).
+- **2026-10-05** — **v0.5.0**: one real options system: a
+ single menu (laid out like the Perfect Dark port's) covering every setting,
+ the game's own N64 control styles as a per-seat preset, and your emulator
+ saves loading directly; a scalable HUD overlay; fullscreen, window-centring
+ and crosshair settings; 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). Plus the small stuff: a menu wording, units and
+ help-text pass, standardised menu spelling, and antialiased overlay
+ controls.
+ [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
rebinding, PlayStation/Nintendo button names, master volume and audio
device selection, a steady 60 fps on low-end GPUs, and fidelity fixes
@@ -239,7 +242,7 @@ only in the release packages, and no macOS/ARM builds yet.
What a release installs (no networking, no telemetry, no ROM or game assets)
and how faithfully the port tracks the original game's logic:
-[Security & fidelity status](docs/security-and-fidelity-status.md).
+[Security](docs/security.md) and [fidelity](docs/fidelity.md) status.
The binaries are not code-signed; from v0.5.0 on each release artifact also
carries a GitHub build-provenance attestation you can check with
`gh attestation verify --repo jkdansereau/goldeneye-pc-port`
diff --git a/build-pc.sh b/build-pc.sh
index b5c149cc..5172aced 100644
--- a/build-pc.sh
+++ b/build-pc.sh
@@ -45,7 +45,7 @@ BUILD_DIR="${BUILD_DIR:-build-pc}"
# MSYS2 install). Whatever `cmake`/`gcc` happen to be first on PATH is not
# trustworthy: Git Bash's /mingw64 has no toolchain, and a pip-installed
# cmake in a Python Scripts dir can shadow the MSYS2 one. GE_MSYS2_ROOT
-# (e.g. D:\M\msys64) overrides the search.
+# (e.g. C:\msys64) overrides the search.
_ge_find_mingw_bin() {
local c root
root="${GE_MSYS2_ROOT:-}"
@@ -67,7 +67,7 @@ _ge_find_mingw_bin() {
if [ -n "${MSYSTEM:-}" ] && [ -z "${GE_PC_BUILD_VIA_NATIVE:-}" ]; then
_mingw_bin="$(_ge_find_mingw_bin)" || {
echo "ERROR: could not find the MSYS2 MinGW64 toolchain (mingw64/bin with cmake.exe + gcc.exe)." >&2
- echo "Install it (see docs/building.md) or point GE_MSYS2_ROOT at the MSYS2 root, e.g. GE_MSYS2_ROOT='D:\M\msys64'." >&2
+ echo "Install it (see docs/building.md) or point GE_MSYS2_ROOT at the MSYS2 root, e.g. GE_MSYS2_ROOT='C:\msys64'." >&2
exit 1
}
# Failure mode 3 (AGENTS.md): the toolchain's DLL dir must be on PATH,
diff --git a/docs/404.html b/docs/404.html
new file mode 100644
index 00000000..c8b3f422
--- /dev/null
+++ b/docs/404.html
@@ -0,0 +1,20 @@
+---
+title: 404
+description: Page not found.
+layout: default
+permalink: /404.html
+---
+
+## 404
+
+This page doesn't exist. The site's real pages are linked from the
+nav: [home]({{ '/' | relative_url }}) ·
+[documentation]({{ '/documentation' | relative_url }}) ·
+[building]({{ '/building' | relative_url }}).
+
+*Note for anyone following a moved link: the old "under the hood" page now
+lives at [documentation]({{ '/documentation' | relative_url }}).*
+
+
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index 99f1d0ec..42ed6205 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -140,15 +140,13 @@ below).
| 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. |
-| 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). |
+| Dam ending cutscene: Bond placement; railing transparency | partial (railing/grate transparency confirmed faithful 2026-10-05; 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). |
-| 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. |
| Sniper rifle: zoom level and speed vs 1964 | verify | #136, D484, D485, D494 | Crouch: **D494** (2026-10-03) makes the PC crouch key crouch/stand freely with the sniper, as GEPD does (superseding D483's freeze); maintainer-verified. Zoom level (D484, 20 -> 7 degrees) and speed (D485, tick-scaled, Rule-2) fixed 2026-10-02. **Owed:** 1964 A/B of zoom level and speed; close #136 after it. |
| Crash on save slots 1/3/4, plus a host BSOD (fltmgr.sys) | decided | #94 | Needs a repro. Two maintainer retest requests (v0.3.0, v0.4.0) got no reply. Likely D299/D301 (save-slot crash, fixed) and D344 (BSOD on exit, fixed). **Decided 2026-10-01: close as stale when the release ships**, pointing at D299/D301/D344; no GitHub activity before then. |
| Authored mip chains not sampled for explicit-LOD textures | post-1.0 | D323 | Distant textures use driver mips instead of Rare's hand-authored chain (heaviest on Statue/Aztec). Parked as a fidelity enhancement. **Decided 2026-10-01:** after 1.0, together with the LOD-fraction approximation as one "real RDP LOD" project. |
| Approximations in the software RSP | post-1.0 | `gfx_pc.cpp` `G_CCMUX_LOD_FRACTION`; D323 | The LOD-fraction CC input is an eyeballed approximation, bundled with D323 as one "real RDP LOD" project after 1.0 (2026-09-30 triage). **WrapFix settled 2026-10-02:** the maintainer checked the Depot building ceilings against 1964 with the default `Video.WrapFix = 0` and they look right, so the default stays off (the opt-in knob remains; RC3/D167). |
-| Latent 64-bit ABI hazards | partial | D441, #107, #108; AUDIT-M6 `struct player`/`struct hand` raw offsets (porting-notes §A1) | **State (2026-10-03, D441):** complete census (local `scratch/d441/`; many compiler hits are intentional `(s32)&ANIM_DATA_*` offsets, D34); 17 files widened to `uintptr_t` under `#ifdef PORT`; 35 sign-extension sites fixed; CI ratchet `tools_pc/abi_ratchet.py` (baseline 389); arena windows centralised (`port/include/portaddr.h`); tier-1 high-arena build (`GE_HIGHARENA_BASE=0x90000000`) runs the menu and `-level_09` pixel-identical to default. D478: nothing live on the current layout; the rest is harmless today but would bite on macOS/ARM/ASLR. **Sweep (2026-10-05):** tier-1 all-level sweep done — 21/21 PASS, 0 crashes, worst over-tol 2.38 % Jungle / 1.38 % Surface 2 (both under their 3 % limit), all 1 %-limit levels <= 0.24 %; no fix batches, ratchet unchanged (389); recipe + caveats in the D441 findings entry. **Owed:** tier 2 (above 4 GiB; u32 slots widened); store-to-u32 vs arithmetic-only tag per site (`portHostToN64` under macOS port_addr); `mema.c` widening (D453); `initanitable.c` heap slots; GE_DETERM wall-clock timers (D117 follow-up, blocks using determinism mode for frame gates). **#108:** widening the `sndPlaySfx` bound disables the guard everywhere; prefer a range check. Detail: findings D441, D457, D478. |
+| Latent 64-bit ABI hazards | partial | D441, #107, #108; AUDIT-M6 `struct player`/`struct hand` raw offsets (porting-notes §A1) | **State (2026-10-03, D441):** complete census (local `scratch/d441/`; many compiler hits are intentional `(s32)&ANIM_DATA_*` offsets, D34); 17 files widened to `uintptr_t` under `#ifdef PORT`; 35 sign-extension sites fixed; CI ratchet `tools_pc/abi_ratchet.py` (baseline 389); arena windows centralised (`port/include/portaddr.h`); tier-1 high-arena build (`GE_HIGHARENA_BASE=0x90000000`) runs the menu and `-level_09` pixel-identical to default. D478: nothing live on the current layout; the rest is harmless today but would bite on macOS/ARM/ASLR. **Sweep (2026-10-05):** tier-1 all-level sweep done, 21/21 PASS, no fix batches, ratchet unchanged (389) — recipe + caveats in the D441 entry. **Owed:** tier 2 (above 4 GiB; u32 slots widened); store-to-u32 vs arithmetic-only tag per site (`portHostToN64` under macOS port_addr); `mema.c` widening (D453); `initanitable.c` heap slots; GE_DETERM wall-clock timers (D117 follow-up, blocks using determinism mode for frame gates). **#108:** widening the `sndPlaySfx` bound disables the guard everywhere; prefer a range check. Detail: findings D441, D457, D478. |
## 3. Verification debt (fixed; a live check is owed)
@@ -179,11 +177,9 @@ A single campaign pass with these on the checklist would clear most of them.
| macOS (Intel + Apple Silicon) / ARM64 Linux | open (target 0.9.0/1.0, maintainer 2026-10-02) | PRs #88, #95; #101 | Outside drafts, parked for `main` until after 1.0. **Address model decided (2026-09-30): port_addr (#95)**, a `portN64ToHost`/`PORT_N64PTR` chokepoint with a `PORT_ADDR_STRICT` validator. The `macos` integration branch is on origin with #88 (merged), #132 and #95 (retargeted, open); nothing merges to `main` before the post-1.0 pass. **Open against `macos`:** #131 (self-hosted sweep scaffold, never run on a Mac; needs #95 first). **On the next rebase onto `main`:** take #131's `selfhosted.yml`; drop #95's `crouched_rifle` hunk (our #120/D445 is correct); relabel #95's D29x/D405-D421 labels. Needs the §2 ABI-hazard sweep; the maintainer tests firsthand before merging. |
| Platform parity gate (Linux co-equal with Windows) | open | — | **Decided 2026-09-30:** yes. Every released platform carries the same gate: a 21-level sweep plus a campaign pass. Needs the golden baselines on both platforms first (§7). The last known Linux crash (D190) was closed as not-a-bug. |
| Low-end GPUs (~30 fps on Celeron/HD 400) | fixed, verify on N3060 class | D339, D481, D482, #92 | **D481 (2026-10-02):** fast3d ran inline on the scheduler thread, delaying retrace delivery so the tick gate skipped frames; 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 | **Diagnosed (D475, D477, D480):** no sustained load — the ~33 ms frames are the frame limiter skipping a retrace after a late gfx task (`boss.c`, D13-class timing); CPU bursts were CI-palette re-imports (ruled out, D477) and in-play shader compiles (fixed, D480). **Parked 2026-10-02:** a play session with the timeline probe felt no hitching. Reopen only if a visible hitch is reported (lever: pre-warm at level load). |
| Steam Deck preset skipped when first launched from Desktop Mode | verify | D283 | Fixed on `release/v0.4.1`: the Deck is detected by DMI hardware id (Valve Jupiter/Galileo; `STEAMOS` as a second signal) and the preset applies once per ini, only while Fullscreen/Maximized are untouched. Only a Windows build and a Linux `-fsyntax-only` check ran. **Owed: one run on a real Deck** (Desktop-Mode-created ini, then Game Mode; expect fullscreen 1280x800 and the log line `video: Steam Deck (DMI Valve ...)`). The Known-issues row goes when this ships. |
-| Windows 7 | open | #98 | **Re-checked 2026-10-02: likely feasible around 1.0.** The exe imports no Windows 8+ functions and the bundled SDL2 2.32.10 still supports Windows 7. The real blocker is the first-run converter `ge007-convert.exe` (PyInstaller, Python 3.9+): the native C converter (#6 Part B) removes it, or a separate Python 3.8 build. Work: one Windows 7 VM test, then manual per-release checks (CI cannot test it). Supersedes the earlier 'won't-fix' call. || Linux golden baselines (P7) + two residual render bands | closed (2026-10-05 re-round) | D521/D525 | 21-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. |
-
-
+| 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. |
## 5. Modern-remaster layer (port features beyond the N64)
@@ -208,9 +204,6 @@ Background research lives in the maintainer's local notes (gitignored):
| Item | Status | Refs | Notes |
|---|---|---|---|
-| `All unlocked` rework: RAM override instead of save patching | done | D442, D387, D259 | **Landed 2026-09-30 (D442):** query-time hooks in `file2.c`, RULE-2-SIGNOFF recorded; the EEPROM patch and D387 merge are removed. **Verified 2026-10-03 (headless):** ON = all 20 levels and cheats unlocked; OFF = only real progress; `ge007.eep` byte-identical. Delete this row once released. |
-| Settings units and values cleanup | done | D357, D443 | **Landed 2026-09-30 (D443):** mouse ×, whole-% deadzone/volume/vibration, FOV in horizontal degrees, MSAA 16×; verified 2026-10-03 (both UIs show the same values). FPS presets above 60 wait for the frame-rate-above-60 work. Delete this row once released. |
-| Restart button in the settings menus | done | D443 | **Landed 2026-09-30 (D443):** a "Restart game" row (orderly D344 quit, then relaunch). **Owed:** click it once on Windows (no BSOD); the Linux relaunch path has never been compiled. |
| F10 / PC Options menu: PD-style dialog refactor + watch-consolidation review | verify | D504-D512, `MENU-PD-ALIGNMENT-PLAN.md` (maintainer's local notes) | **Built 2026-10-04 (D504); follow-ups merged into `release/v0.4.1`:** D505/D506/D507 (wording, real-unit sliders, descriptions); D508 forced aspect ratios (16:9 / 21:9); D509 2-frame hold after an aspect change (rare gun glitch = known issue); D510 overlay + FPS counter same size front end vs level; D512 `Game.HudScale` scales the F10 overlay. **Owed (live pass, `docs/dev/notes/MENU-LIVE-CHECKLIST.md`, launches A/B/C):** look-and-feel vs PD; mouse, keyboard and pad in a level and on the front end; HudScale 75/100/150; 21:9; display-mode round trip across a restart; split-screen 2P with the overlay open. **Gaps:** background blur (post-1.0, §5c); per-seat control scheme is #139 / D513. Watch consolidation: not cheaply feasible (parked as T5). |
| Defaults/presets consolidation: one `Display mode` switch | verify | D504, D440, `MENU-PD-ALIGNMENT-PLAN.md` §5 (local notes) | **Landed 2026-10-04 (D504):** the derived `__DisplayMode` row shows Modern / Original N64 / Custom (no ini key); F10 dropdown + left/right stepping, front end steps too; one reset row per dialog covering exactly its keys; editing a non-bundled key does not flip Display mode. **Decided 2026-10-03: tri-state**, flipping to Custom when the user edits a bundled key. **Owed (live):** display-mode round trip across a restart; read of the regrouped pages and wording (MENU-LIVE-CHECKLIST.md A1/A2/A4). |
| Control style Original shows the game's own N64 styles | verify | #139 follow-up, D516, D517 | **Landed 2026-10-04 (D516, merged @ 91595597):** "Controller style" row (Original only; the game's own 1.1-2.4 per seat, saved to the Bond file; front end reads "(in a mission)"); D498 presets hidden in Original. **D517:** hold-repeat on any Bond row dropped fps (EEPROM rewrite per detent); now staged like sliders. Live passes 2026-10-04 (maintainer): single player + split-screen 2P with different styles per seat. **Owed:** the 2.x second-pad pass (real second controller). |
@@ -220,9 +213,7 @@ Background research lives in the maintainer's local notes (gitignored):
| Gamepad presets: duplicate-binding warning, Xbox stick behaviours | open (after v0.5.0; documented in the README) | D469, D498, D518, D394 | D469 (pad action table, Jinx 1.1 default, Custom per-seat bindings), **D498** (Xbox 1.2 Christmas / 1.3 Frost / 1.4 Elektra) and the per-seat preset (**D518**) are done and maintainer-verified with pads. Not done (maintainer 2026-10-04: skip in game for v0.5.0, document only): a warning when two actions share a button; the Xbox "left stick aims the crosshair" and "D-pad copies the left stick" behaviours (D394). |
| Pad button names on a Nintendo pad | verify | D471; local note `PAD-MENU-AUDIT.md` | Audit done 2026-10-01 (no controller-only trap; D472 fixed G1-G3). D471 family button names + player LEDs: **PS5 DualSense verified 2026-10-03.** **Owed:** a Nintendo (Switch) pad check; the maintainer has no such pad, so this waits for one or for a user report. |
| Mute and auto-pause on focus loss; PNG screenshots to `screenshots/` | parked | D231, PR #56 (closed unmerged), `docs/dev/notes/parked/0001-QoL-*.patch` (local) | Deprioritised by the maintainer on 2026-09-25. F12 today is the dev PPM dump. |
-| FPS tanks while adjusting F10 music volume with a controller | done | D489 | Maintainer, 2026-10-02 (seen on PAL; general, also on v0.4.0). Cause: every pad/arrow slider step saved to the EEPROM (3 joy-poll handshakes + file rewrite on the game thread). Fixed: steps stage, save once on release; merged into `release/v0.4.1` 2026-10-03. Maintainer pad check passed 2026-10-03. Delete this row once released. |
| Frame rate above 60 (120/144/uncapped with interpolation) | decision | [`dev/UNLOCKED-FPS-PLAN.md`](dev/UNLOCKED-FPS-PLAN.md) | The menu cap is {30, 60} (the ini accepts 0 = uncapped, but the sim ticks at the VI rate). The plan's Phase 0 is resolved by D248 (the default was a port bug; it is a real 60 fps) and its Phase 1 audit and Phase 2 slice scope are written (landed 2026-09-30), but the header still reads parked. Next step is a standing sim/render-rate measurement, not the old 30 fps investigation. Must stay compatible with netplay determinism. |
-| Swapping saves with emulators | done (D492) | D492; `tools_pc/eep_convert.py`; README "Using an emulator save" | **Implemented 2026-10-04:** `tools_pc/eep_convert.py` (`n64-to-pc`, `pc-to-n64`, `verify [--as n64|pc]`, auto-detect), bundled as `tools/eep_convert.py` in both release packages; README section added. **Why (D492):** the port stores checksum words and `options` little-endian, so an unconverted 1964 save fails validation and all five slots are wiped; converting (swap `options`, recompute the checksum pair) was proven in a headless A/B. On-disk format unchanged. Verified byte-identical and round-trip. |
| PD Extended Options parity: settings that need new dev work | done (reviewed 2026-10-04; two rows deferred) | `MENU-PARITY-SONNET-PLAN.md` §3 (local notes); pd_port `port/src/optionsmenu.c` @ 32a1cb9 | **Reviewed 2026-10-04 (maintainer decisions; local notes `PD-PARITY-DESIGN-REVIEW.md`, `WATCH-SCREEN-RATIO-DESIGN.md`).** **Done:** D511 fullscreen mode, Center window, crosshair opacity and color by health (live check owed, MENU-LIVE-CHECKLIST.md A4/A5/B3). **Watch Screen size/Ratio:** stay on the watch, no PC rows; the game saves watch settings on watch close (D349 correction) and they don't fight Aspect ratio (README). **Deferred past v0.5.0:** mouse lock mode (Auto/Always/Off; aim lock itself = `Input.AimMode`, D337); per-seat controller device select (needs a stable pad id; MP input layer). **Closed:** edge deadzone = `Input.AimRange` (D338); crosshair sway N/A (GE has none; gun sway is D428); use key reloads, no toggle (Ext keeps D378's GEPD split, Control style Original gives the N64 behaviour); per-seat options/binds (pad binds already per seat, D469/D518; keyboard/mouse is one device); no Detail textures toggle (`Video.DetailBaseTile` is the D236 fix); N/A confirmed: HUD centering (automatic, D335b), glare, overexposure, GE64 muzzle flashes, MP death music, radial menu speed, language filter, crosshair speed. **Other rows:** analog movement/stick scale/crouch → #139, T7; tickrate/framerate limit → frame rate above 60. |
| One PC options UI: front-end "PC Options" opens the F10 overlay over the folder graphic | verify | D519, D343, D504, D510, D512 | **Landed 2026-10-04 (D519, branch `feat/one-options-ui`):** the file-select "PC Options" entry opens the F10 overlay over file select; the `MENU_PC_OPTIONS` screen and four old probes are gone; Bond-file rows and the watch chooser are keyed to file select. Headless checks pass (D519). **Owed:** by-eye mouse, keyboard and pad pass on file select; profile chooser + Controller style with a real profile; footer text overlaps the Copy/Erase bar. |
| Emulator saves load directly in the game (no tool for import) | verify | D514, D492; `port/src/eepimport.c` called from `geEepromLoad`; D297 `legacycrc.c` same pattern | **Landed 2026-10-04 (D514, merged into `release/v0.4.1`).** Copying a 1964/Project64 `.eep` in as `data/ge007.eep` works: on first launch each region that validates in emulator format and not port format is converted in place (same rules as `eep_convert.py`); regions invalid in both are left to the game's wipe; port saves untouched; the original is backed up to `ge007.eep.emulator.bak`. Export stays with `eep_convert.py`. Headless verify 6/6 byte-identical to the in-game-proven reference. **Owed:** by-eye mission select after importing a real 1964 save. |
@@ -277,20 +268,22 @@ and a stage loader.
| Item | Status | Refs | Notes |
|---|---|---|---|
-| Security review follow-ups | done | maintainer's local security review | All items done on `release/v0.4.1`: the Low hardening items, the Medium item (self-hosted workflow input handling), actions pinned by SHA, `pyinstaller` pinned to 6.22.3, and the ROM no longer stored in `actions/cache` by `selfhosted.yml` (staged from the runner's `_rom-seed` dir only). Dependabot bumps (#134, #135) landed as SHA pins. |
-| 1964-vs-port comparison harness | partial | `tools_pc/abpad/` | **2026-10-02/03:** one millisecond timeline drives 1964 (virtual XInput pad) and the port (`GE_INPUTSCRIPT_MS`), both windows captured on one clock with side-by-side sheets; verified through intro skip, file select and folder open. Not frame-identical (1964 has no seed pin or frame dump). Menu scripting dropped for `live_ab.ps1` (position-only, one real pad, `GE_PAD_BACKGROUND=1`). **Left:** in-level timelines (e.g. the D486 turret check); the two sides use different save files. |
-| 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. |
+| 1964-vs-port comparison harness | partial | `tools_pc/abpad/` | One millisecond timeline drives 1964 (virtual XInput pad) and the port (`GE_INPUTSCRIPT_MS`); both windows captured on one clock with side-by-side sheets, verified through intro skip, file select and folder open. Not frame-identical (1964 has no seed pin or frame dump). **Left:** in-level timelines (e.g. the D486 turret check); the two sides use different save files. |
+| Scripted-playthrough harness | post-1.0 (maintainer 2026-10-04: not a good use of current LLMs, revisit with newer models) | `GE_INPUTSCRIPT`, D117 | Input script + `GE_PCDUMP` + crash-log check for unattended regression runs. Levers: Invincibility + Invisibility cheats, closed-loop waypoint steering, pinned seed. Spike: one Dam route x5, measure end-position spread, then go/no-go. |
| Cheap worker for mechanical, build-checked tasks | open | `delegate-local` MCP; memory of past Qwen issues | Birdman64's fast worker model wrote 44% of its commits while the lead planned and reviewed. Ours: pattern-applied edits (D441 fix batches, scaffolding strips, small tools), each gated by a clean build + a specific check + the lead's diff review, in the worker's own worktree. Local Qwen for edit-only tasks (it fabricates on reading tasks), or a hosted fast model via `delegate_to_provider` (the maintainer's cost call). |
-| Golden baselines | partial | `tools_pc/verify.sh`, `tools_pc/golden/README.md`, D117/D522/D529/D531 | **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). |
+| 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-05) fixed the tool and re-captured all 21 levels under the canonical eep (47/63 frames moved). **Cross-platform spread recomputed 2026-10-05** from the committed pairs (`framediff.py` win-vs-linux at the stems, tol 2): exact **0.380–5.120 %** on the 20 non-Cuba levels (silo's 900 stem is the outlier: cell mean 34.3, phash 10; streets 3.223–4.566), structural **21/21 clean**, max phash 40 (cradle, at threshold); Cuba 1.357–12.663 % is the structural-tier-only level. The D525 figures above predate the D531 linux re-capture. **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`. |
| 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 --repo jkdansereau/goldeneye-pc-port`; documented in SECURITY.md and the README. **Owed:** the v0.5.0 tag build is the first real run; confirm the step passes and verification succeeds on a downloaded asset, then delete this row. |
| Self-hosted CI runner link failure | open | D244 | Needs a `pacman -Syu` on the runner (maintainer-only access). Release tag builds use GitHub runners, so it doesn't block a release. |
| Remove temporary scaffolding before a release | partial | [`dev/GE-ENV-PROBES.md`](dev/GE-ENV-PROBES.md), D302 | Passes 2026-09-30 to 2026-10-03 removed the closed-finding probe suites (registry in `GE-ENV-PROBES.md`, `gen_env_probes.py --check` passing); TEMP probes `GE_TESTSTYLE` / `GE_D516` removed 2026-10-04. **Left:** D104 (kept while D294 is in verify); the M-183/M-185 clamps, **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. |
+| P12 U5 review of `tools_pc/**` (release-diff, B10) | partial | D531/D532/D533 | Reviewed 2026-10-05 (the unit that owed the P7 merge). **Blocker found + fixed (D531):** `capture_p7.sh`'s per-level re-pin restored the local save, so the D530 box re-round ran under the box-local save — fixed + re-captured under the canonical eep (47/63 frames moved). **Should-fixes applied as D532 (tooling-only):** `verify.sh`'s `pin_ini_640x480` fails closed like `pin_eep`; `GOLDEN_SEED` hard-pinned with a warning for a stray `GE_RSEED`; `audiodebug.ps1`'s dead `-AB`/`-Old` switches removed. **Still open:** the pixel-gated box verify in a WSLg GPU-display window (D531) — crash-detect only until then. |
+| Findings log and doc size | partial | `tools_pc/docs_budget.py` | Budget checker (per-file and tier-1 token budgets; `--toc FILE`). `findings.md` is still about 2 MB (~500k tokens), so agents must keep using the index. Nothing blocks; trim only when a budget check fails. |
| Branch and PR housekeeping | open | — | 30 landed local branches pruned 2026-09-30 (tips in `prune-branches-2026-09-30.txt`, notes archive). **On hold by the maintainer (do not prune yet):** probe-only `d243-verify-local`, `investigate/d243-cutscene-race`, `park/d236-probes-m139`. Local `main` and `release/v0.4.1` are deliberately not pushed. PR #62 (mouse dt-decouple) stays held until mouse feel is fundamentally better. |
| Docs site: Web 1.0 phase 2 | decision | — | Phase 1 shipped. Phase 2 (centred serif page) is plan-only. |
+| Docs site: press/SEO pass (2026-10-05) | partial | `docs/` site pages | Landed on the pure site pages + presentation layer: real `
` masthead on every page, `color-scheme`/`theme-color`/`max-image-preview`/`rel=me` in the layout, ink-mute >=4.5:1, focus-visible + selection, 15px body, mobile padding; `date`/`modified_time` front matter on the pure pages. Home-page additions (elevator-pitch pull-quote, Overview fact table, press/coverage section) were **removed on review 2026-10-05** — each restated the tagline/intro or duplicated a section below. The five short topic notes (the-software-rsp, the-asset-pipeline, framerate-and-pacing, input-and-aim, saves) were dropped from the published set (the PD port documents the same subjects; files stay in the repo as working material). **Open:** the same front matter on the *shared* docs (`internals.md`, `porting-notes.md`, `building.md`, `dev-process.md`, `dev/agentic-development.md`) is deliberately untouched — authoritative working docs, not edited for site polish. |
+| Agentic-development deep dive (doc) | queued — not yet (2026-10-05) | `docs/dev/agentic-development.md` | Write-up owed for the public case study: per-agent commit estimates / work estimates (the manual attribution pass over the session log, incl. the D350-style commit-date divergences), a code-quality audit of the trial models (GPT-6 (Sol) et al.), and the process feedback / findings. Deferred by the maintainer; pick it up when scheduled — nothing here blocks. |
+| Docs site: split Security & fidelity -> two pages + v0.5.0 update pass (2026-10-05) | done (local, unpushed) | `docs/security.md`, `docs/fidelity.md` (was `security-and-fidelity-status.md`) | Split on review (the homepage pointer sat near Known issues and read as if there were security *issues*); both pages cross-linked and linked from the hub and index.md's Download section. Update pass 2026-10-05 against the `release/v0.4.1` tree: no network includes/symbols/HTTP literals in compiled `src/`+`port/`; no registry or library-loading calls; converter imports no network module; bundle manifests re-checked (allowlist + ROM/oversized-blob hard-fail); region macros re-compared vs the Makefile (all three regions); library classification re-checked (CMake globs libultra+libultrare audio, gu, `io/vitbl.c` only). Full game-logic audit not re-run; recent `src/game` edits are finding-gated. |
+| Docs site: final sanity sweep + anchor fixes (2026-10-05) | done (local, unpushed) | `docs/porting-notes.md` (3 TOC hrefs), `docs/404.html` (permalink) | Audit of the 10-page published set: one h1 per page, no heading-level skips, all 42 in-page anchors resolve, titles/descriptions unique and <=160 chars, og:image/canonical present, JSON-LD parses, no local paths/usernames, served == built. Three `porting-notes.md` TOC hrefs carried a double hyphen where kramdown emits one (headings whose punctuation leaves a single separator: `(32->64):`, `D114/D116):`, `"off":`) — dead on the live GitHub Pages site, corrected at source against the live ids. `docs/404.html` needed `permalink: /404.html` (else canonical/og:url read `/404.html.html`). Preview-builder slug rule fixed to kramdown: strip punctuation, map **each** whitespace char to `-` (so ` / ` -> `--`), no run-collapsing; headings with inline markup now get ids too. |
## Community forks (not ours; reference only)
diff --git a/docs/_config.yml b/docs/_config.yml
index e3c655bf..52a699d7 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -36,19 +36,29 @@ plugins:
# ---------------------------------------------------------------------------
# The site publishes a small curated set:
-# index.md, under-the-hood.md (the "under the hood" hub), building.md, plus
+# index.md, documentation.md (the documentation hub), building.md, plus
+# 404.html (themed not-found page),
# the four docs grouped behind that hub: internals.md, porting-notes.md,
-# dev-process.md, dev/agentic-development.md, 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
-# status, backlogs, plans, session notes, inherited decomp guides. It stays in
-# the repo (and on GitHub) but is excluded from the site build so it never
-# shows up as a published page or in sitemap.xml.
+# dev-process.md, dev/agentic-development.md, and
+# security-and-fidelity-status.md (split on review 2026-10-05 into
+# security.md + fidelity.md, linked from index.md's Download section and
+# the hub).
+# The five short topic notes (the-software-rsp.md, the-asset-pipeline.md,
+# framerate-and-pacing.md, input-and-aim.md, saves.md) were dropped from the
+# site on review 2026-10-05: the Perfect Dark port documents the same
+# subjects and nothing here was significantly new; they stay in the repo as
+# working material. Everything else under docs/ is working material - the
+# finding log, level status, backlogs, plans, session notes, inherited
+# decomp guides. It stays in the repo (and on GitHub) but is excluded from
+# the site build so it never shows up as a published page or in sitemap.xml.
# NOTE: keep this list in sync as dev/ working docs are added; a new unlisted
# .md under docs/ is silently published to the site + sitemap.xml.
exclude:
+ - the-software-rsp.md
+ - the-asset-pipeline.md
+ - framerate-and-pacing.md
+ - input-and-aim.md
+ - saves.md
- HANDOFF.md
- ROADMAP.md
- SetupGuide.md
diff --git a/docs/_includes/head-custom.html b/docs/_includes/head-custom.html
index 2bef12bf..9b7c82cb 100644
--- a/docs/_includes/head-custom.html
+++ b/docs/_includes/head-custom.html
@@ -21,7 +21,9 @@
--bg-panel: #121316; /* code / cards / tables */
--ink: #e6e4df; /* warm off-white */
--ink-soft: #a9a7a0; /* secondary text */
- --ink-mute: #70706a; /* captions, footer */
+ --ink-mute: #8a8a83; /* captions, footer (>=4.5:1 on --bg-frame:
+ the old #70706a measured 3.9:1 and failed
+ WCAG AA on the tagline + legal line) */
--line: #26282c; /* 1px borders */
--line-2: #3a3d44; /* stronger borders */
--accent: #e8a13a; /* GoldenEye gold (amber) */
@@ -40,8 +42,9 @@
margin: 0;
color: var(--ink);
font-family: var(--sans);
- font-size: 14px;
- line-height: 1.62;
+ font-size: 15px; /* 14px read as console chrome, but the
+ body is prose people actually read */
+ line-height: 1.6;
/* faint starfield (the old-school "space" tile, softened) */
background-color: var(--bg);
background-image:
@@ -63,6 +66,14 @@
}
a:hover { color: var(--accent-hi); text-decoration-color: currentColor; }
+ /* keyboard focus + selection: the amber thread again, so a focused
+ control is visible on the near-black ground (a11y / keyboard users) */
+ :focus-visible {
+ outline: 2px solid var(--accent-hi);
+ outline-offset: 2px;
+ }
+ ::selection { background: var(--accent); color: #0b0b0d; }
+
/* ---- the centred "page" frame -------------------------------------- */
.frame {
max-width: 50rem;
@@ -75,11 +86,13 @@
/* ---- banner -------------------------------------------------------- */
.banner { text-align: center; padding: 2.4rem 1rem 1.4rem; }
.banner .t {
+ margin: 0; /* it is an
now (see the layout) */
font-family: var(--mono);
font-weight: 700;
font-size: 1.7rem;
letter-spacing: .05em;
- color: var(--ink);
+ color: var(--accent); /* brand name in goldeneye gold -- the amber
+ thread (masthead -> nav -> rule -> page title) */
}
.banner .tag { color: var(--ink-mute); font-size: .82rem; margin-top: .55rem; }
@@ -131,6 +144,23 @@
border-bottom: 1px solid var(--line-2);
}
.doc-content h2::before { content: "\25aa "; color: var(--accent); }
+ /* Page-title look: the 2026-09 SEO pass demoted page headings to h2
+ (the masthead owns h1; no duplicate page titles) -- see the SEO
+ conventions note in scratch/pr-seo-revise-prompt.md. The first
+ content h2 of each page gets the h1 look; presentation only, the
+ heading levels stay per-convention. (On the home page the masthead
+ itself is the title, so its first h2 -- Status -- leads as a section.) */
+ /* .flat-title (front matter on the home page): the masthead IS the
+ page title there, so the first h2 must read as a plain section --
+ otherwise masthead-h1 + amber first-h2 stack as a double header. */
+ .doc-content:not(.flat-title) > h2:first-of-type {
+ font-size: 1.5rem;
+ font-weight: 700;
+ color: var(--accent);
+ margin: .2rem 0 1rem;
+ padding-bottom: 0;
+ border-bottom: 0;
+ }
.doc-content h3 {
font-family: var(--mono);
font-size: .92rem;
@@ -189,21 +219,88 @@
}
.doc-content tr:nth-child(2n) td { background: var(--zebra); }
+ /* ---- console blocks ------------------------------------------------ */
+ /* Status / News / Download / Known issues panels (home page). Same
+ amber-terminal language as the code blocks above, but the content
+ keeps its semantic list/table markup (SEO + screen readers).
+
+ Site formatting rules (2026-10-05 PR pass + 2026-09 SEO conventions)
+ — keep these:
+ - Heading LEVELS follow the SEO pass: the page heading is h2 (the
+ masthead owns h1; no duplicate page titles), sections are h2,
+ h3 labels a real sub-topic only (e.g. the current-release line
+ under Status). The first content h2 gets the amber "page-title"
+ look via CSS only (.doc-content > h2:first-of-type) -- never by
+ promoting the markup to h1.
+ - h2 = the section, outside the panel.
+ - Directives ("always use the latest release") are .callouts inside
+ panels, or a .warn line (amber rule, ink text) where no panel
+ exists; never headings — headings label topics,
+ never emphasis.
+ - List intros ("what a player will notice…") stay plain paragraphs.
+ - Dated list-item leads ("2026-10-05 — v0.5.0") stay leads
+ inside the
, not headings.
+ - Headings must be single source lines: marked ends an ATX heading at
+ the line break, so a wrapped "### …" silently becomes a heading plus
+ a stray paragraph (Jekyll/other renderers differ — don't rely on
+ multi-line headings at all). */
+ .doc-content .cblock {
+ background: var(--bg-panel);
+ border: 1px solid var(--line);
+ padding: 1.1rem 1.3rem .9rem;
+ margin: 1.6rem 0;
+ }
+
+ /* Expandable panels (, e.g. the release history folded under
+ Status): same panel language as .cblock; the summary line is the
+ amber handle. The content is in the DOM either way -- collapsing is
+ presentation, not hiding from search. */
+ .doc-content details {
+ background: var(--bg-panel);
+ border: 1px solid var(--line);
+ padding: .3rem 1.3rem .8rem;
+ margin: 1.6rem 0;
+ }
+ .doc-content summary {
+ font-family: var(--mono);
+ font-size: .9rem;
+ font-weight: 600;
+ color: var(--accent);
+ letter-spacing: .03em;
+ cursor: pointer;
+ }
+ .cblock h3 { margin-top: 0; color: var(--ink); }
+ .cblock p, .cblock ul, details p, details ul {
+ font-family: var(--mono);
+ font-size: .88rem;
+ line-height: 1.6;
+ }
+ .cblock li, details li { margin: .5rem 0; }
+ .cblock li::marker, details li::marker { color: var(--accent); }
+ .cblock strong, details strong { color: var(--accent); font-weight: 600; }
+ .cblock code, details code { background: transparent; border: 0; padding: 0; }
+ .doc-content .callout {
+ border-left: 3px solid var(--accent);
+ padding: .15rem 0 .15rem .8rem;
+ margin: 0 0 1rem;
+ }
+ .doc-content .callout p { color: var(--ink-soft); }
+
+ /* Inline directive marker -- "Warning:" leading a sentence, amber like
+ the rest of the accent thread, no panel around it. */
+ .doc-content details .warn {
+ border-left: 3px solid var(--accent);
+ padding: .1rem 0 .1rem .7rem;
+ margin: .2rem 0 .9rem;
+ font-weight: 600;
+ }
+ .cblock table { background: transparent; margin: 1rem 0 .4rem; }
+ .cblock > p:last-child { margin-bottom: 0; }
+
/* ---- images -------------------------------------------------------- */
.doc-content img {
border: 1px solid var(--line); border-radius: 0; max-width: 100%;
- }
-
- /* ---- details / summary (the "full gallery" collapse) --------------- */
- .doc-content details {
- max-width: var(--measure);
- border: 1px solid var(--line);
- background: var(--bg-panel);
- padding: .6rem .9rem; margin: 1.5rem 0;
- }
- .doc-content summary {
- cursor: pointer; color: var(--accent);
- font-family: var(--mono); font-size: .85rem; letter-spacing: .03em;
+ height: auto; /* no squashed stills when a width= is set */
}
/* ---- console-viewer carousel (home page media) --------------------- */
@@ -261,7 +358,11 @@
@media (max-width: 40rem) {
.frame { margin: 8px; }
+ .banner { padding: 1.4rem .8rem 1rem; }
.banner .t { font-size: 1.35rem; }
+ .doc-content { padding: 1.1rem 1rem .5rem; }
+ .doc-content table { font-size: .82rem; }
+ .doc-content pre { padding: .7rem .8rem; }
}
diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html
index 2bbc8775..79acc088 100644
--- a/docs/_layouts/default.html
+++ b/docs/_layouts/default.html
@@ -3,26 +3,44 @@
+
+
+
+
+
+
+
{%- seo -%}
{%- include head-custom.html -%}
+
-
{{ site.title }}
-
{{ site.description }}
+
{{ site.title }}
+
{{ site.description }}
-
-
+
Dam — v0.4.0 intro attract
-
-Full gallery: 12 in-engine captures from a v0.4.0 intro-attract pass (3072×1728 widescreen, Sep 2026)
-
-
-
-
-
Dam
-
-
-
-
Runway
-
-
-
-
Surface
-
-
-
-
Frigate
-
-
-
-
Surface 2
-
-
-
-
Statue
-
-
-
-
Archives
-
-
-
-
Cradle
-
-
-
-
Aztec
-
-
-
-
Control
-
-
-
-
Streets
-
-
-
-
Jungle
-
-
-
-
-More captures may land here as playtesting continues.
-
## Play it, or take it apart
- **Try it yourself**: grab a bundle above, bring your own ROM, and play the
- campaign. It's an early cut for exactly this: if something breaks, an
+ campaign. It's a pre-1.0 build for exactly this: if something breaks, an
[issue](https://github.com/jkdansereau/goldeneye-pc-port/issues) with what
you were doing is genuinely useful.
- **Read the code**: the game logic in `src/` is unmodified decompilation;
@@ -172,80 +155,51 @@ More captures may land here as playtesting continues.
asset sidecar. [CONTRIBUTING](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/CONTRIBUTING.md)
has the ground rules that keep it faithful to the original game.
- **AI disclosure**: development here used AI coding agents — Claude Code
- plus a local open-weight model on a single RTX 5090, directed by one person in their
- spare time. The project is as much a study of that process as it is a
- port, and the write-up is written to let you judge the result for yourself;
- it records what happened and takes no position for or against using LLMs
- on a project like this. See [the full
+ plus a local open-weight model on a single RTX 5090, directed by one person
+ in their spare time. The project is as much a study of that process as it
+ is a port; the write-up records what happened and takes no position for or
+ against using LLMs on a project like this. See [the full
write-up](dev/agentic-development.md) — the setup, timeline, handoff
workflow, and an honest account of what did and didn't work.
## How it works
-The R4300 game code's logic and control flow are the decompilation's, and remain the
-reference we preserve. The port's changes inside `src/` are few in kind (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
-software: the port interprets the graphics command stream (GBI) the game emits and translates it
-to OpenGL, bypassing the RDP entirely. Everything else that would touch N64
-hardware (video, audio, input, timers, save storage) is shimmed in a small
-dedicated layer, following the architecture of the Perfect Dark PC port from
-the same Rare engine family. Full detail: [Internals](internals.md); the bug
+The R4300 game code's logic and control flow are the decompilation's, and
+remain the reference we preserve. The port's changes inside `src/` are few in
+kind and each is gated behind `#ifdef PORT`: mechanical 32→64-bit
+pointer-width fixes, a couple of approved timing fixes, and the opt-in
+*All unlocked* and *Skip intro* hooks. The N64's Reality Signal Processor
+(RSP), the graphics coprocessor that builds and executes each frame's
+display list, is emulated in software: the port interprets the graphics
+command stream (GBI) the game emits and translates it to OpenGL, bypassing
+the RDP entirely. Everything else that would touch N64 hardware (video,
+audio, input, timers, save storage) is shimmed in a small dedicated layer,
+following the architecture of the Perfect Dark PC port from the same Rare
+engine family. Full detail: [Internals](internals.md); the bug
catalogue: [Porting notes](porting-notes.md).
-## Known issues
-
-Known defects and limitations as of v0.5.0, each with its finding-log
-reference. The full list, with root causes and fix status: the
-[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).
-
-- **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
-The technical docs live under [under the
-hood](under-the-hood.md) — the architecture, the N64→PC bug catalogue, and
-how the project is developed. [Building](building.md) covers building and
-running from source.
+The technical docs live on the [Documentation](documentation.md) page — the
+architecture, the N64→PC bug catalogue, and how the project is developed.
+[Building](building.md) covers building and running from source.
+
+
diff --git a/docs/input-and-aim.md b/docs/input-and-aim.md
index af056086..60ccd252 100644
--- a/docs/input-and-aim.md
+++ b/docs/input-and-aim.md
@@ -1,6 +1,8 @@
---
title: Input and aim
description: How the GoldenEye 007 PC port maps keyboard, mouse and modern controllers onto the N64's controller, including mouse aim.
+date: '2026-10-05'
+modified_time: '2026-10-05'
---
## Input and aim
diff --git a/docs/internals.md b/docs/internals.md
index 816750cc..48d24bc9 100644
--- a/docs/internals.md
+++ b/docs/internals.md
@@ -3,7 +3,7 @@ title: Internals
description: Architecture behind the PC port; the software RSP-emulation approach, GoldenEye-vs-Perfect-Dark engine differences, and the phased development plan.
---
-## GoldenEye 007 PC Port: architecture & plan
+## Architecture & plan
> This began as the pre-implementation research note and is kept as the
> architecture reference. Sections 1–9 describe the design; section 10 lists
diff --git a/docs/porting-notes.md b/docs/porting-notes.md
index 3eb820d0..390e04ff 100644
--- a/docs/porting-notes.md
+++ b/docs/porting-notes.md
@@ -19,14 +19,14 @@ good that you are looking at one of these.
## Contents
-- [A. Pointer-width struct growth (32→64); the dominant class](#a-pointer-width-struct-growth-3264--the-dominant-class)
+- [A. Pointer-width struct growth (32→64); the dominant class](#a-pointer-width-struct-growth-3264-the-dominant-class)
- [B. 16-byte PC `Gfx` / `Vtx` vs 8-byte N64](#b-16-byte-pc-gfx--vtx-vs-8-byte-n64)
- [C. Big-endian rodata / ROM data read on little-endian PC](#c-big-endian-rodata--rom-data-read-on-little-endian-pc)
- [C2. Port-layer / SDL shims](#c2-port-layer--sdl-shims)
- [D. N64 hardware idioms fast3d does not emulate](#d-n64-hardware-idioms-fast3d-does-not-emulate)
-- [D2. The HUD/model "X-mirror"; RESOLVED](#d2-the-hudmodel-x-mirror-d114d116--resolved-it-was-an-upside-down-capture)
+- [D2. The HUD/model "X-mirror"; RESOLVED](#d2-the-hudmodel-x-mirror-d114d116-resolved-it-was-an-upside-down-capture)
- [D3. GCC/mingw makes an all-non-negative `enum` UNSIGNED](#d3-gccmingw-makes-an-all-non-negative-enum-unsigned)
-- [D4. N64 "interrupts off" must be a real lock on PC](#d4-n64-interrupts-off-is-not-free-on-pc--it-must-be-a-real-lock)
+- [D4. N64 "interrupts off" must be a real lock on PC](#d4-n64-interrupts-off-is-not-free-on-pc-it-must-be-a-real-lock)
- [D5. Loop bounds that assume linker adjacency of two globals](#d5-loop-bounds-that-assume-linker-adjacency-of-two-file-scope-globals)
- [D12. Collapsing an LOD binding to tile 0 breaks a DETAIL binding](#d12-collapse-an-lod-binding-to-tile-0-is-wrong-for-a-two-texture-detail-binding-d236)
- [D13. Per-render accumulation is not associative (1 vs 2–3 ticks/frame)](#d13-per-render-accumulation-is-not-associative-the-ports-1-tickframe-can-reach-states-the-n64s-23-ticksframe-never-did-d329)
diff --git a/docs/saves.md b/docs/saves.md
index 7d485111..b8510d73 100644
--- a/docs/saves.md
+++ b/docs/saves.md
@@ -1,6 +1,8 @@
---
title: Saves
description: How the GoldenEye 007 PC port backs the N64's EEPROM save with a plain file you can back up.
+date: '2026-10-05'
+modified_time: '2026-10-05'
---
## Saves
diff --git a/docs/security-and-fidelity-status.md b/docs/security-and-fidelity-status.md
deleted file mode 100644
index 786fc1e3..00000000
--- a/docs/security-and-fidelity-status.md
+++ /dev/null
@@ -1,115 +0,0 @@
----
-title: Security & fidelity status
-description: What a release actually installs (no networking, no telemetry, no ROM or assets)
-and how faithfully the port tracks the original N64 game's logic.
----
-
-## Security & fidelity status
-
-This page answers two questions plainly: *what does installing this put on
-your machine*, and *how faithfully does the port actually reproduce the
-original N64 game's logic*. Both were reviewed end-to-end on 2026-09-15 and
-re-verified for the v0.3.0 release bundles on 2026-09-18 (bundle contents,
-no-networking source check, and the asset converter). The no-networking source
-check was repeated against the `release/v0.4.1` tree on 2026-09-30 (no socket,
-HTTP or name-resolution calls in `src/` or `port/`; neither the engine nor the
-bundled `SDL2.dll` imports a networking DLL; no registry calls in the port's
-own code); the bundle contents were not re-checked then. The full engineering
-record behind each item is in the project's finding log
-([`docs/dev/findings.md`](dev/findings.md)).
-
-## What a release actually installs
-
-Each release (`goldeneye-pc-port-*-win64.zip` / `-linux-x86_64.tar.gz`)
-contains, and only contains: the game engine executable, a fixed set of
-standard runtime libraries (SDL2, zlib, and the MinGW/Linux runtime — no
-third-party downloads), the one-time ROM-to-PC asset converter, a README,
-and license texts. The build scripts hard-fail if a ROM image or an
-oversized blob ends up in the bundle. **No ROM, no game assets (textures,
-audio, models, levels, text) are ever shipped** — you supply your own
-legally-owned copy.
-
-At runtime, the engine:
-
-- has **no networking of any kind** (no sockets, no HTTP, checked directly
- against the source),
-- has **no telemetry, crash reporting, or analytics** of any kind,
-- **never touches the Windows registry** and **never requests elevated
- permissions**,
-- writes save data, config, and derived ROM assets only inside its own
- folder — never to `%APPDATA%`, `Program Files`, or any system location.
-
-The one-time asset converter (`ge007-convert` / `prepare-assets.py`) — the
-part of the bundle most likely to draw suspicion, since it's the thing that
-reads your ROM and writes new files — has no network access at all,
-verifies your ROM against a fixed table of known retail GoldenEye 007
-hashes before touching it (an unrecognized file is rejected outright), and
-writes only inside the bundle folder. Its full source ships right next to
-the compiled version in every release if you'd like to read it yourself. The
-engine starts it automatically on the first run, when the two derived asset
-folders (`data/pcmodels-*`, `data/pccg-*`) are missing, passing it the ROM it
-found and the output folder; after that first run it is not launched again.
-
-See [`SECURITY.md`](../.github/SECURITY.md) for why the binaries are
-unsigned and what that means in practice (a SmartScreen prompt on first
-run, and why some antivirus engines flag the converter specifically).
-
-## Building from source
-
-Cloning and building pulls in only standard, well-known packages: MSYS2
-`pacman` packages on Windows, `apt` packages on Linux — no `npm`, no
-`cargo`, nothing downloaded at CMake configure time, no git submodules, and
-no prebuilt binaries checked into the repository. The CI build freezes the release's asset-converter tool with `pyinstaller`,
-which is pinned to an exact version, and every third-party GitHub Action the
-workflows use is pinned to a full commit SHA (both from the release after
-v0.4.0; earlier releases installed an unpinned `pyinstaller`). What
-PyInstaller packages is entirely in-repo, stdlib-only Python, so a release is
-reproducible from a diff of this repository.
-
-One development-only backdoor exists in source builds: the `GE_DEBUG_UNLOCKALL`
-environment variable (`src/game/file2.c`) pre-unlocks all cheat options at boot.
-It is off unless explicitly set, changes no shipped behavior, and is documented
-here so the env-var surface stays fully accounted for.
-
-## Fidelity to the original N64 game
-
-The port's non-negotiable rule is that decompiled game logic is never
-changed — only genuine N64-hardware dependencies get a PC-side
-replacement, in the `port/` layer. This was audited directly:
-
-- **Region build macros** exactly mirror the original N64 Makefile's macro
- sets for all three regions — verified line-by-line.
-- **The audio/library file classification** (which original files compile
- vs. get PC-shimmed) exactly matches the project's own ground-truth
- manifest — no silent gaps, no silent duplicates.
-- **The one shipped game-logic-adjacent change** (a portal-culling
- float-precision fix, D271) is `#ifdef PORT`-gated, documented in the
- finding log, and moves *toward* matching N64 behavior, not away from it.
- Every other deviation from the decompiled source is one of the documented
- 32→64-bit pointer-width ABI corrections described above.
-
-**One issue, found and fixed before v0.3.0 shipped**: the port's
-random-number generator (`port/src/random.c`) was documented as a bit-exact
-port of the N64's PRNG, but wasn't — a shift-operation helper didn't match
-the real MIPS64 instruction semantics it was meant to mirror. This was
-independently confirmed by hand-tracing the assembly against the C port,
-tracked as finding D284, and fixed before the release (verified clean
-across a full level sweep — all 20 missions plus the ending sequence —
-no crashes). It affected loot placement, AI
-behavior variance, and replay-state determinism from the very first random
-draw, in every release before v0.3.0. Because of the fix, RNG-derived
-output — including the save-file CRC — changed, which meant existing save
-files and any previously recorded replays no longer validate; v0.3.0's
-release notes describe the automatic save migration that handles this. We'd
-rather say this plainly than let a "verified bit-exact" claim stand
-uncorrected, both when it was wrong and now that it's fixed.
-
-Everything else checked (the documented ABI/pointer-width exceptions used
-for the 32-to-64-bit transition, a sample of the finding log's own claimed
-fixes against the actual current code) matched what's documented.
-
----
-
-*This page is kept current alongside future releases. Its source review is a
-manual, human-reviewed process today — see the project roadmap for plans to
-partially automate the mechanical parts of it.*
diff --git a/docs/security.md b/docs/security.md
new file mode 100644
index 00000000..cd040941
--- /dev/null
+++ b/docs/security.md
@@ -0,0 +1,87 @@
+---
+title: Security status
+description: What a GoldenEye PC port release installs and doesn't: no networking, no telemetry, no ROM, no system writes — re-verified against the v0.5.0 source.
+date: '2026-10-05'
+modified_time: '2026-10-05'
+---
+
+## Security status
+
+This page answers one question plainly: *what does installing this put on
+your machine*? The review was done
+end-to-end on 2026-09-15 and re-verified for the v0.3.0 release bundles on
+2026-09-18 (bundle contents, no-networking source check, and the asset
+converter). The no-networking source check was repeated against the
+`release/v0.4.1` tree on 2026-09-30 (no socket, HTTP or name-resolution
+calls in `src/` or `port/`; neither the engine nor the bundled `SDL2.dll`
+imports a networking DLL; no registry calls in the port's own code); the
+bundle contents were not re-checked then.
+
+**v0.5.0 update pass, 2026-10-05:** the source checks were re-run against
+the current tree — no socket/network includes, no network symbols and no
+HTTP string literals in the compiled `src/` and `port/` sets; no registry
+or library-loading calls in either; the converter's Python sources
+(`prepare-assets.py` and its emit-script imports) import no network
+module. The bundle contents were re-checked against the packaging
+manifests (`tools_pc/bundle-win.sh` / `bundle-linux.sh` — the scripts CI
+runs): an explicit allowlist of engine, runtime DLLs, SDL2, licenses and
+converter tools, with a hard-fail guard against a ROM image or oversized
+blob sneaking in. The v0.5.0 artifacts themselves were not re-unpacked
+locally; they are built by the same scripts. The full engineering record
+behind each item is in the project's finding log
+([`docs/dev/findings.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/dev/findings.md)).
+
+## What a release actually installs
+
+Each release (`goldeneye-pc-port-*-win64.zip` / `-linux-x86_64.tar.gz`)
+contains, and only contains: the game engine executable, a fixed set of
+standard runtime libraries (SDL2, zlib, and the MinGW/Linux runtime — no
+third-party downloads), the one-time ROM-to-PC asset converter, a README,
+and license texts. The build scripts hard-fail if a ROM image or an
+oversized blob ends up in the bundle. **No ROM, no game assets (textures,
+audio, models, levels, text) are ever shipped** — you supply your own
+legally-owned copy.
+
+At runtime, the engine:
+
+- has **no networking of any kind** (no sockets, no HTTP, checked directly
+ against the source),
+- has **no telemetry, crash reporting, or analytics** of any kind,
+- **never touches the Windows registry** and **never requests elevated
+ permissions**,
+- writes save data, config, and derived ROM assets only inside its own
+ folder — never to `%APPDATA%`, `Program Files`, or any system location.
+
+The one-time asset converter (`ge007-convert` / `prepare-assets.py`) — the
+part of the bundle most likely to draw suspicion, since it's the thing that
+reads your ROM and writes new files — has no network access at all,
+verifies your ROM against a fixed table of known retail GoldenEye 007
+hashes before touching it (an unrecognized file is rejected outright), and
+writes only inside the bundle folder. Its full source ships right next to
+the compiled version in every release if you'd like to read it yourself.
+The engine starts it automatically on the first run, when the two derived
+asset folders (`data/pcmodels-*`, `data/pccg-*`) are missing, passing it
+the ROM it found and the output folder; after that first run it is not
+launched again.
+
+See [`SECURITY.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/.github/SECURITY.md) for why the binaries are
+unsigned and what that means in practice (a SmartScreen prompt on first
+run, and why some antivirus engines flag the converter specifically).
+
+## Building from source
+
+Cloning and building pulls in only standard, well-known packages: MSYS2
+`pacman` packages on Windows, `apt` packages on Linux — no `npm`, no
+`cargo`, nothing downloaded at CMake configure time, no git submodules, and
+no prebuilt binaries checked into the repository. The CI build freezes the
+release's asset-converter tool with `pyinstaller`, which is pinned to an
+exact version, and every third-party GitHub Action the workflows use is
+pinned to a full commit SHA (both from the release after v0.4.0; earlier
+releases installed an unpinned `pyinstaller`). What PyInstaller packages
+is in-repo, stdlib-only Python — a release is reproducible from this
+repository alone.
+
+One development-only backdoor exists in source builds: the `GE_DEBUG_UNLOCKALL`
+environment variable (`src/game/file2.c`) pre-unlocks all cheat options at boot.
+It is off unless explicitly set, changes no shipped behavior, and is documented
+here so the env-var surface stays fully accounted for.
diff --git a/docs/the-asset-pipeline.md b/docs/the-asset-pipeline.md
index c30569ee..76d19056 100644
--- a/docs/the-asset-pipeline.md
+++ b/docs/the-asset-pipeline.md
@@ -1,6 +1,8 @@
---
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.
+date: '2026-10-05'
+modified_time: '2026-10-05'
---
## The asset pipeline
diff --git a/docs/the-software-rsp.md b/docs/the-software-rsp.md
index 1f04c8c3..72516c38 100644
--- a/docs/the-software-rsp.md
+++ b/docs/the-software-rsp.md
@@ -1,6 +1,8 @@
---
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.
+date: '2026-10-05'
+modified_time: '2026-10-05'
---
## The software RSP
diff --git a/port/fast3d/gfx_pc.cpp b/port/fast3d/gfx_pc.cpp
index a4d738cb..ff94d711 100644
--- a/port/fast3d/gfx_pc.cpp
+++ b/port/fast3d/gfx_pc.cpp
@@ -2057,9 +2057,9 @@ static void d526_note_emit(uint32_t f, struct LoadedVertex* const* v_arr, int us
ok = 1;
}
if (!ok) return;
- if (d526_box_on)
+ 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
+ } 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++) {
diff --git a/tools_pc/audiodebug.ps1 b/tools_pc/audiodebug.ps1
index 4728c5bf..bfb93053 100644
--- a/tools_pc/audiodebug.ps1
+++ b/tools_pc/audiodebug.ps1
@@ -48,7 +48,7 @@ param(
[switch]$Dump, # GE_AUDIODUMP=1 -> audiodump.raw
[switch]$VoiceDump, # GE_VOICEDUMP=1 -> voicedump.raw (per-voice, D202/M-67)
[switch]$MixerTrace, # GE_MIXERTRACE=1 -- SLOW, see warning below
- # (the -AB/-Old A/B switches were removed 2026-10-07, D532: GE_D204_OLD no
+ # (the -AB/-Old A/B switches were removed 2026-10-05, D532: GE_D204_OLD no
# longer exists in the game, so they were documented no-ops)
# Plumbing
diff --git a/tools_pc/capture_p7.sh b/tools_pc/capture_p7.sh
index 6b637a40..17222bd5 100755
--- a/tools_pc/capture_p7.sh
+++ b/tools_pc/capture_p7.sh
@@ -54,7 +54,11 @@ cleanup() {
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
+# D534: restore on EXIT only; INT/TERM just exit (which fires EXIT), so an
+# interrupted capture cannot continue past the restore with the snapshot gone.
+trap cleanup EXIT
+trap 'exit 130' INT
+trap 'exit 143' TERM
# recipe comes from the tracked gate tool, not from this file
eval "$(grep -m1 '^GOLDEN_DUMP=' "$VSH")"
diff --git a/tools_pc/golden/README.md b/tools_pc/golden/README.md
index 625f0661..b21d0793 100644
--- a/tools_pc/golden/README.md
+++ b/tools_pc/golden/README.md
@@ -24,13 +24,13 @@ and per platform (`win`, and `linux` — 21 levels x 3 frames on each, captured
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
+ Archives, and the 2026-10-05 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
+ on the box under the canonical eep 2026-10-05 (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).
@@ -66,7 +66,7 @@ capture with the recipe above and replace `//*.png`.
## History
-- **D531 (2026-10-07):** `capture_p7.sh`'s D529 adaptation was incomplete —
+- **D531 (2026-10-05):** `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.
@@ -74,7 +74,7 @@ capture with the recipe above and replace `//*.png`.
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
+- **D530 (2026-10-05, 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-
diff --git a/tools_pc/verify.sh b/tools_pc/verify.sh
index d50cb43f..4109cea6 100644
--- a/tools_pc/verify.sh
+++ b/tools_pc/verify.sh
@@ -112,7 +112,7 @@ while [ $# -gt 0 ]; do
--script) SCRIPT="$2"; USER_SCRIPT=1; shift 2 ;;
--against) AGAINST="$2"; shift 2 ;;
--json) JSON=1; shift ;;
- -h|--help) sed -n '2,32p' "$0"; exit 0 ;;
+ -h|--help) sed -n '2,34p' "$0"; exit 0 ;;
*) POSARGS+=("$1"); shift ;;
esac
done
@@ -121,12 +121,12 @@ set -- "${POSARGS[@]:-}"
case "${1:-}" in
sweep) MODE=sweep; shift; SWEEP_SUBSET="${*:-}" ;;
parity) MODE=parity; LEVEL_ARG="${2:-}"; shift 2 2>/dev/null || true ;;
- -h) sed -n '2,32p' "$0"; exit 0 ;;
+ -h) sed -n '2,34p' "$0"; exit 0 ;;
"")
# D529 never-do 4: a no-arg run must NEVER look like a green verdict. It
- # prints the usage and exits NON-zero (2026-10-07: a no-arg pair was
+ # prints the usage and exits NON-zero (2026-10-05: a no-arg pair was
# cited as a "double-green" 21/21 because it exited 0).
- sed -n '2,32p' "$0"
+ sed -n '2,34p' "$0"
echo "error: no mode given -- a sweep is 'verify.sh sweep', one level is 'verify.sh ' (never cite a no-arg run as a gate result; D529)" >&2
exit 1 ;;
*) MODE=single; LEVEL_ARG="$1" ;;
@@ -257,14 +257,21 @@ pin_ini_640x480() {
restore_ini() {
if [ -n "$INI_BAK" ]; then cp "$INI_BAK" "$INI" && rm -f "$INI_BAK"
elif [ "$INI_CREATED" = 1 ]; then rm -f "$INI"; fi
+ INI_BAK=""; INI_CREATED=0
}
-trap 'restore_ini; restore_eep; kill_ours' EXIT INT TERM
+# D534: restore on EXIT only; INT/TERM just exit (which fires EXIT). A combined
+# `EXIT INT TERM` trap restored on Ctrl-C and then let the sweep CONTINUE --
+# the next level's pin re-installed the canonical save with the snapshot
+# already deleted, so the user's own ge007.eep was left replaced on disk.
+trap 'restore_ini; restore_eep; kill_ours' EXIT
+trap 'exit 130' INT
+trap 'exit 143' TERM
# --- save pin (D523, SUPERSEDED by D529): the goldens were captured with a
-# save file PRESENT. D529 (2026-10-07 A/B) proved the frame depends on the
+# save file PRESENT. D529 (2026-10-05 A/B) proved the frame depends on the
# save's CONTENT, not just its presence -- D523's "presence, not which
# save" claim had only been measured on Archives (worst_cell 0.175625 on
-# each of the four candidate saves, 21.557% with none), and the 2026-10-07
+# each of the four candidate saves, 21.557% with none), and the 2026-10-05
# sweep (facility 177.47 scene-level on the local playtest save vs 0.0-0.25
# on every other candidate on disk) refutes it. The gate now INSTALLS THE
# CANONICAL SAVE (tools_pc/golden/ge007.eep, 2 KB, in-tree so a fresh
@@ -291,6 +298,7 @@ restore_eep() {
[ -n "$EEP_PIN_BAK" ] || return 0
if [ "$EEP_PIN_HAD" = 1 ]; then cp "$EEP_PIN_BAK" "$EEP_PIN"; else rm -f "$EEP_PIN"; fi
rm -f "$EEP_PIN_BAK"
+ EEP_PIN_BAK=""; EEP_PIN_HAD=0
}
# --- run one level -> sets globals: R_STATUS R_FRAMES R_SYM R_BRIEF -------