v0.5.0 playtest fold-in: fog and near-plane clip, crash fixes, polish (D540-D564)

Deck/Windows playtest fixes: fog rework (D540) and RSP-style CPU near-plane
clip with per-vertex N64 fog (D543), with fog recomputed on near-plane-clipped vertices as the RSP does,
so the haze around the player stays steady (D553); colour-combiner clamp for banded walls
(D548); rocket and thrown-item stack overflows (D545, D549); menu
double-trigger mitigation (D541); Dam ending first-shot camera swivel
restored by removing an old port look-at workaround (D552); settings standardisation (D546), live
All unlocked (D547), F10 mouse (D544); static window title and the project
mark (the in-game crosshair shape, site gold) as window/taskbar icon and
favicon (D550). Reference frames re-based
on Windows, Linux and Steam Deck (A/B-confirmed); capture_p7.sh runs on
Windows. Opt-in update check, off by default (D551: one HTTPS request to
GitHub per launch when enabled; winhttp.dll loaded only then; the tag CI
job checks the tag against GE007_VERSION). Release notes, ROADMAP,
internals and security docs updated; tree-hygiene pass.

Refs: D540 D541 D543 D544 D545 D546 D547 D548 D549 D550 D551 D552 D553 D554 D555 D556 D557 D558 D559 D560 D561 D562 D563 D564
This commit is contained in:
James Dansereau committed 2026-10-06 18:35:44 -04:00
1 parent d7b9371afb
commit 4b710728bf
243 files changed
+2905 -611

No files matched your search

+6 -1
View File
@@ -1,7 +1,12 @@
# Security Policy
This project is a non-commercial, fan-made research port. It has no server
component, no networking of any kind, and no telemetry. Each release ships
component and no telemetry, and no networking unless you turn on the opt-in
update check (`Game.CheckUpdates`, off by default), which makes one HTTPS
request to GitHub's releases API per launch (GitHub sees that request and
your IP, as with any web request). On Windows it loads the system
`winhttp.dll` from System32 only then; on Linux/Steam Deck it runs the
system `curl`. It never downloads or installs anything. Each release ships
prebuilt Windows and Linux binaries, but never a ROM or any game asset — you
supply those yourself from a copy you legally own. The realistic security
surface is:
+70 -23
View File
@@ -12,9 +12,8 @@
Platforms: **Windows x86-64** and **Linux x86-64 (including Steam Deck)**.
Region: **NTSC-U (US) only**. See [Known issues](#known-issues).
<!-- MAINTAINER: after the play session, add one line here saying what this
build was played on (e.g. "Played on Windows and Steam Deck: campaign
spot-checks, 2-4P split-screen, a controller-only session"). -->
Played on Windows and Steam Deck: campaign spot-checks, 2-4 player
split-screen and controller-only sessions.
The headline is **one real options system**: both options
screens merged into a single menu, laid out like the Perfect Dark PC port's,
@@ -74,6 +73,31 @@ list.
## Fidelity and stability fixes
- **Dam ending camera:** the first shot of the ending cutscene swivels up onto
Bond again, as on the N64; an old port workaround snapped it straight onto
him (D552).
- **Fog rework (Surface 2, from Deck playtest):** ground fog no longer "chunks"
away tile by tile, and the sky fades into fog toward the horizon as on the
N64 (D540). Large near-camera triangles are now clipped against the near
plane the way the N64's RSP does, with per-vertex fog recomputed on the
clipped edge, so the haze around Bond stays steady as he moves instead of
brightening and darkening (D543, D553; compared against hardware-level
emulation). A new **Fog distance** setting (100% = N64) is independent of
Draw distance, and Draw and Fog distance now apply live instead of on the
next level load.
- **Banded walls near explosions:** in Aztec's dark corridor, walls near rocket
fire turned into black/white/yellow/blue bands (also seen on the Deck); fixed
by clamping colour-combiner inputs to the range the N64 can produce (D548).
Build-verified; a live Aztec re-check is owed.
- **Rocket and thrown-item crashes/clipping:** a player-fired rocket could pass
through the ground when fired near Bond's feet (a stack-layout difference
from the N64, D545), and the same class of bug is fixed in thrown grenades,
knives and objects (D549).
- **Menu double-trigger (Steam Deck):** one A press could advance two menu
screens, and one B could back out two overlay pages. A short release
hold-off on menu A/B/Start and a single back action per press are now in
place. This is a mitigation: the exact cause is unconfirmed (the Deck
re-check passed) (D541).
- **Fog:** objects far in fog are hidden as on the N64 (fog snap); in widescreen
the wider view can still show a faint distant building edge (e.g. Surface's
dish from the start area) — 4:3 matches the N64 (D503).
@@ -117,6 +141,40 @@ 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.
- **Settings standardised (D546):** Draw and LOD distance default to 2.0x
(N64 = 1.0x), stick deadzone to 25%, and the mouse sensitivity maximum is 4x.
**FOV** is now vertical degrees, 30-90, with 60 = the N64 view. The old
"Guard AI uses full wide view" row is now **Gameplay view area**
(Original / Extended). Untouched old values migrate once; existing
`ge007.ini` files still load.
- **Optional update check (D551):** a new **Check for updates** option in Game,
**off by default**. When on, it makes one HTTPS request to GitHub per launch
and shows an "Update available" row linking to the releases page.
- **Settings menu wording pass (D554):** clearer tips that wrap to two lines
instead of being cut off, one `Key: Action` style for the control hints,
consistent casing and names (Original / Extended, "Original layout").
*Skip intro* and *All unlocked* are no longer marked experimental.
The game's own *Look up/down* option is gone from the menu; use *Invert look*
on the Mouse and Controller pages (D564).
- **Crosshair mouse pointer and scrollbar in the settings menu (D555, D556):**
the game's own crosshair is the pointer while F10 / PC Options is open (Mouse →
Crosshair pointer to turn it off), long pages get a draggable scrollbar, and
tips are kept only where a setting needs explaining. Long tips are no longer
cut off inside a level (D558).
- **New defaults (D556, D557):** Crouch mode defaults to Toggle (new installs and
Reset to defaults), and new profiles start with Look ahead off, which fights
mouse look on PC; an existing save's untouched empty folders (no progress,
factory options) get the same when first played (D559). Played or
customised profiles and existing settings are not changed.
- **All unlocked** now applies live from the options menu, with no restart
(D547).
- **F10 overlay mouse:** the OS cursor is always shown (no more flicker on the
main menu), right-click goes back, and the tip line follows the hovered row
(D544).
- **Window:** the title bar is now static (no per-second "NN fps" refresh; the
FPS readout is still in the F10 overlay), and the window and taskbar icon is a
new project mark instead of a game still (D550). Live icon/title check owed.
---
## Known issues
@@ -127,36 +185,25 @@ and menu spelling standardised to one convention across both menus.
| Changing aspect ratio inside a level can briefly glitch the gun/hand model, rarely | Cosmetic, one-off | Change the ratio from the front-end PC Options, or accept it |
| No macOS or ARM builds | Platform | — |
| Saves from builds before v0.4.1 can hold fake unlocks from `All unlocked` | Save data | Not repaired automatically. Since v0.4.1 the option never writes the save; keep a backup of `data/ge007.eep` from before you used it |
| `Skip intro` and `All unlocked` are experimental | Opt-in options | Leave them off for a normal playthrough |
| The first frame of a level takes a little longer while its textures upload | Brief FPS-counter dip | None needed |
| Far objects almost fully in fog are now hidden as on the N64, except in widescreen where a faint distant building edge can still show (e.g. Surface's dish from the start area); 4:3 matches the N64 | Cosmetic | Higher Draw/LOD distance shows more |
The full list, with workarounds, is the
[known-issues table](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/ROADMAP.md#known-issues).
**Not yet playtested live** (merged, build- and review-verified, owed a play
session before this release is announced): the four new settings (D511),
`Game.HudScale` (D512), Control style Original on a pad (D513/D516 per seat),
emulator-save import (D514), and the D519 profile-chooser row / no-click-through
follow-ups.
<!-- MAINTAINER: if the play session did not cover all of the above, keep the
sentence "these were merged and review-verified but not playtested live"
in the body; never imply a full playtest. -->
**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
**Reference-frame gate for this release:** Windows, Linux and Steam Deck each
carry the full 21-level reference-frame set (63 frames each) at the same stems,
re-captured for this release's fog and near-plane changes and confirmed by two
independent capture passes per platform. The full pixel gate is green on all
three against their own goldens on the final build: Windows 21/21, Linux
(Intel HD 3000) 21/21 and Steam Deck 21/21. The recipe pins the run for you: the save file's
CONTENT is pinned (the gate installs its own canonical save per level, the same
way it pins the display ini — D529) and the PRNG seed is hard-pinned (a stray
`GE_RSEED` in your environment warns and is ignored rather than re-seeding the
gate — D532), so a gate run and a capture run see the same starting state. The
cross-platform spread 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
by eye in a live session.
cross-platform spread on the shipped sets (0.188-4.553% of pixels over tol 2 on
the 20 non-Cuba levels) is informational only — it is not a cross-platform
pixel-parity claim.
---
+8 -10
View File
@@ -55,11 +55,9 @@
.tab span { writing-mode:vertical-rl; text-orientation:mixed;
font-family:'Arimo',sans-serif; font-weight:700; font-size:15px;
letter-spacing:4px; color:#3b3b33; transform:rotate(180deg); }
.reticle { position:absolute; right:34px; top:246px; width:44px; height:44px; z-index:5; }
.reticle::before, .reticle::after { content:""; position:absolute; background:#c62828; }
.reticle::before { left:50%; top:-6px; width:3px; height:56px; margin-left:-1.5px; }
.reticle::after { top:50%; left:-6px; height:3px; width:56px; margin-top:-1.5px; }
.reticle i { position:absolute; inset:4px; border:3px solid #c62828; border-radius:50%; }
/* the project mark: in-game default crosshair proportions (ring outer r 18,
bars reach ~1.43x r with an open centre ~0.24x r); see tools_pc/make_icon.py */
.reticle { position:absolute; right:30px; top:242px; width:52px; height:52px; z-index:5; }
/* the parchment card */
.card { position:absolute; left:40px; top:40px; right:96px; bottom:40px;
@@ -124,12 +122,12 @@
<div class="rep">REPORT:</div>
<div class="rows">
<div class="statusline">Status: <span class="status">RELEASED v0.4.0 &mdash; WINDOWS &middot; LINUX &middot; STEAM DECK</span></div>
<div class="statusline">Status: <span class="status">RELEASED v0.5.0 &mdash; WINDOWS &middot; LINUX &middot; STEAM DECK</span></div>
<div class="stats">
<span class="k">Statistics:</span><span>All 20 solo missions &mdash; full campaign completed in playtesting</span>
<span></span><span>~230 game-code units, compiled unmodified</span>
<span></span><span>~25k lines C/C++ &nbsp;&middot;&nbsp; ~14k Python (port layer + tools)</span>
<span></span><span>286 findings root-caused &nbsp;&middot;&nbsp; two coding agents, one human, part-time</span>
<span></span><span>~210 game-code units, compiled unmodified</span>
<span></span><span>~34k lines C/C++ &nbsp;&middot;&nbsp; ~10k Python (port layer + tools)</span>
<span></span><span>407 findings logged &nbsp;&middot;&nbsp; two coding agents, one human, part-time</span>
</div>
</div>
@@ -142,6 +140,6 @@
<div class="tab"><span>NEXT</span></div>
<div class="tab"><span>PREVIOUS</span></div>
</div>
<div class="reticle"><i></i></div>
<svg class="reticle" viewBox="-26 -26 52 52" aria-hidden="true"><g fill="none" stroke="#c62828" stroke-width="3"><circle r="16.5"/><path d="M0-26V-4.5M0 4.5V26M-26 0H-4.5M4.5 0H26"/></g></svg>
</body>
</html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 597 KiB

After

Width:  |  Height:  |  Size: 596 KiB

+9
View File
@@ -404,6 +404,15 @@ jobs:
with:
persist-credentials: false
# The pushed tag must match the version baked into the exe (the update
# check compares against it); bump GE007_VERSION in CMakeLists.txt first.
- name: Check tag matches GE007_VERSION
run: |
ver=$(sed -n 's/^set(GE007_VERSION "\([^"]*\)").*/\1/p' CMakeLists.txt)
if [ -z "$ver" ] || [ "${GITHUB_REF_NAME}" != "v${ver}" ]; then
echo "tag ${GITHUB_REF_NAME} != v${ver} (CMakeLists.txt GE007_VERSION)"; exit 1
fi
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: goldeneye-pc-port-win64
+1 -1
View File
@@ -1,7 +1,7 @@
# Self-hosted runtime (ROM-gated)
#
# Runs the build + the 21-level headless sweep (tools_pc/verify.sh) on the
# self-hosted runner "corpyvt6agent01", the one machine that has a legal ROM.
# self-hosted runner, the one machine that has a legal ROM.
# This is the job class ci.yml explicitly does NOT do: CI is build-only, no
# ROM, so runtime checks live here. Setup + rollout plan (gitignored, local):
# docs/dev/notes/SELFHOSTED-RUNNER-PLAN.md
+3 -2
View File
@@ -73,8 +73,9 @@ mostly agent-authored.
bulk ROM-derived captures: capture output dirs are gitignored
(`tools_pc/sweep-captures/cap-*/`, `tools_pc/sweep-captures/**/*.ppm`); the
only bulk imagery in the tree is `tools_pc/golden/**/*.png`
(63 frames × 2 platforms, 126 total, nested per level at
`tools_pc/golden/<level>/{win,linux}/`), which exists because `verify.sh`
(63 frames × 3 platforms, 189 total, nested per level at
`tools_pc/golden/<level>/{win,linux,deck}/`; the `deck` set added
2026-10-05 with maintainer approval), which exists because `verify.sh`
consumes it. Downloads ship no game content at all.
5. **Batch, don't spam.** Accumulate approved doc/tree fixes across a session
into ONE commit; no per-item branches.
+6
View File
@@ -149,6 +149,7 @@ endif()
# Provenance, printed by --version and at startup (port/src/main.c): a build
# keeps pointing at this repository no matter who compiles or renames it.
set(VERSION_CODENAME "larkspur")
set(GE007_VERSION "0.5.0") # bump on every release
set(ORIGIN_URL "https://github.com/jkdansereau/goldeneye-pc-port")
configure_file(
@@ -600,6 +601,11 @@ target_link_libraries(ge007 ${LIBS})
set_target_properties(ge007 PROPERTIES OUTPUT_NAME "${BIN_NAME}")
if(WIN32)
set_target_properties(ge007 PROPERTIES WIN32_EXECUTABLE TRUE)
# Explorer/taskbar icon: the original crosshair mark (cmake/icon/ge007.rc,
# a static resource script; the ge007.ico it references is generated by
# tools_pc/make_icon.py). Compiled by the resource compiler; ge007.ico is
# resolved relative to this .rc file.
target_sources(ge007 PRIVATE cmake/icon/ge007.rc)
endif()
message(STATUS "Output binary: ${BIN_NAME}")
+10 -3
View File
@@ -1,5 +1,7 @@
# GoldenEye 007 PC Port
<p align="center"><img src="docs/img/icon/ge007-icon.png" width="96" alt="project mark: a generic version of the game's default aim cross -- a circle with N/S/E/W lines crossing its edge (original generated art, see tools_pc/make_icon.py -- no game content)"></p>
[![CI](https://github.com/jkdansereau/goldeneye-pc-port/actions/workflows/ci.yml/badge.svg)](https://github.com/jkdansereau/goldeneye-pc-port/actions/workflows/ci.yml)
[![Latest release](https://img.shields.io/github/v/release/jkdansereau/goldeneye-pc-port?include_prereleases&label=download)](https://github.com/jkdansereau/goldeneye-pc-port/releases)
![Platforms](https://img.shields.io/badge/platforms-Windows%20%7C%20Linux%20%7C%20Steam%20Deck-blue)
@@ -218,7 +220,8 @@ Select again) closes. With a keyboard, use `F10` and the arrow keys/Enter.
saved automatically.
- **Faithful N64 progression by default.** Opt-in extras: *Skip intro* and
*All unlocked* (every level, 007 mode and the full cheat menu, without
touching your save; both experimental).
touching your save), and *Check for updates* (off
by default; one request to GitHub per launch when on, see Security).
- **Saves in a plain file** next to the game, so backing up is a file copy.
## Status
@@ -240,7 +243,7 @@ only in the release packages, and no macOS/ARM builds yet.
> those are not repaired; if you used it on an older build, keep a backup of
> `data/ge007.eep`.
What a release installs (no networking, no telemetry, no ROM or game assets)
What a release installs (no networking unless you turn on the opt-in update check, which is off by default and makes one HTTPS request to GitHub per launch; no telemetry, no ROM or game assets)
and how faithfully the port tracks the original game's logic:
[Security](docs/security.md) and [fidelity](docs/fidelity.md) status.
The binaries are not code-signed; from v0.5.0 on each release artifact also
@@ -651,7 +654,7 @@ release, is useless without a ROM you supply.
Textures, audio, models, level data and in-game text are extracted from a ROM
*you already own*, on *your* machine, the first time you start the game. The
port has no network access and never uploads anything.
port has no network access unless you turn on the opt-in *Check for updates* option (`Game.CheckUpdates`, off by default). When on, it makes one HTTPS request per launch to GitHub's releases API to look for a newer release (GitHub sees the request and your IP, as with any web request), never downloads or installs anything, and never uploads anything.
### This repository
@@ -660,6 +663,10 @@ The repository is a fork of the public
its contents (see [`NOTICE`](NOTICE) for what that includes). The few
port-specific edits inside the decompiled code are marked `#ifdef PORT`.
The project mark / favicon is original *generated* art -- a generic version
of the game's default aim cross (a circle with N/S/E/W lines crossing its
edge), no game screenshots or logos in it.
### Your part
- Only use a ROM that you dumped from a cartridge you own.
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+4
View File
@@ -0,0 +1,4 @@
/* Windows executable icon (Explorer / taskbar).
* Original crosshair mark generated by tools_pc/make_icon.py (cmake/icon/
* ge007.ico) -- same art as the site favicon (docs/img/icon/). */
IDI_ICON1 ICON "ge007.ico"
+23 -20
View File
@@ -120,7 +120,6 @@ below).
| Changing Aspect ratio in a level (mostly Fill window to Original or a forced ratio) can briefly glitch the gun / hand model, rarely | Cosmetic, one-off | Change the ratio from the front-end PC Options, or accept it | D509 (frame hold fixed the common case; the rare remainder is logged, not investigated) |
| No macOS or ARM builds | Platform | — | #88, #95, #101, §4 |
| Saves from builds before v0.4.1 can hold fake unlocks from `All unlocked` | Save data | Not repaired automatically. Since v0.4.1 the option never writes the save; keep a backup of `data/ge007.eep` from before you used it | D442, D387 |
| `Skip intro` and `All unlocked` are experimental | Opt-in options | Leave them off for a normal playthrough | D408, D442 |
| The first frame of a level takes a little longer while its textures upload | Brief FPS-counter dip | None needed | D475, D480 |
| Far objects almost fully in fog are now hidden as on the N64, except in widescreen, where the wider view can still show a faint distant building edge (e.g. Surface's dish from the start area). 4:3 matches the N64 | Cosmetic | None; higher Draw/LOD distance shows more | D503, §5c |
@@ -140,13 +139,14 @@ 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-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 ending cutscene | done (2026-10-06) | D526, D552 | Railing/grate transparency faithful (D526); first-shot swivel restored (D552, maintainer-verified). |
| Title-screen attract demo runs too fast; F10 box rescales while it plays | backlogged (after v0.5.0, maintainer 2026-10-04) | D87 (ramrom file struct); timing: D13, D248, D427, D451, D486 | **Reported 2026-10-04 (live pass):** attract plays visibly too fast; not investigated. Lead: it is a ramrom demo replay whose recordings carry a per-frame tick count, which the port's 60 fps / 1-tick pacing may override. Rule out PC causes, then compare with 1964. Same pass: the F10 box briefly rescaled during the demo (not reproduced; candidate fix = overlay reads fast3d's native viewport, parked locally). |
| Sniper rifle: zoom level and speed vs 1964 | verify | #136, D484, D485, D494 | Crouch: **D494** (2026-10-03) makes the PC crouch key crouch/stand freely with the sniper, as GEPD does (superseding D483's freeze); maintainer-verified. Zoom level (D484, 20 -> 7 degrees) and speed (D485, tick-scaled, Rule-2) fixed 2026-10-02. **Owed:** 1964 A/B of zoom level and speed; close #136 after it. |
| Crash on save slots 1/3/4, plus a host BSOD (fltmgr.sys) | decided | #94 | Needs a repro. Two maintainer retest requests (v0.3.0, v0.4.0) got no reply. Likely D299/D301 (save-slot crash, fixed) and D344 (BSOD on exit, fixed). **Decided 2026-10-01: close as stale when the release ships**, pointing at D299/D301/D344; no GitHub activity before then. |
| Authored mip chains not sampled for explicit-LOD textures | post-1.0 | D323 | Distant textures use driver mips instead of Rare's hand-authored chain (heaviest on Statue/Aztec). Parked as a fidelity enhancement. **Decided 2026-10-01:** after 1.0, together with the LOD-fraction approximation as one "real RDP LOD" project. |
| Approximations in the software RSP | post-1.0 | `gfx_pc.cpp` `G_CCMUX_LOD_FRACTION`; D323 | The LOD-fraction CC input is an eyeballed approximation, bundled with D323 as one "real RDP LOD" project after 1.0 (2026-09-30 triage). **WrapFix settled 2026-10-02:** the maintainer checked the Depot building ceilings against 1964 with the default `Video.WrapFix = 0` and they look right, so the default stays off (the opt-in knob remains; RC3/D167). |
| Latent 64-bit ABI hazards | partial | D441, #107, #108; AUDIT-M6 `struct player`/`struct hand` raw offsets (porting-notes §A1) | **State (2026-10-03, D441):** complete census (local `scratch/d441/`; many 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. |
| Latent 64-bit ABI hazards | partial | D441, #107, #108; AUDIT-M6 `struct player`/`struct hand` raw offsets (porting-notes §A1) | **State (2026-10-03, D441):** complete census (local `scratch/d441/`; many hits are intentional `(s32)&ANIM_DATA_*` offsets, D34); 17 files widened to `uintptr_t` under `#ifdef PORT`; 35 sign-extension sites fixed; CI ratchet `tools_pc/abi_ratchet.py` (baseline 389); arena windows centralised (`port/include/portaddr.h`); tier-1 high-arena build (`GE_HIGHARENA_BASE=0x90000000`) runs the menu and `-level_09` pixel-identical to default. D478: nothing live on the current layout; the rest would bite only on macOS/ARM/ASLR. **Sweep (2026-10-05):** tier-1 all-level sweep done, 21/21 PASS, no fix batches, ratchet unchanged (389), recipe in the D441 entry. **Owed:** tier 2 (above 4 GiB; u32 slots widened); store-to-u32 vs arithmetic-only tag per site (`portHostToN64` under macOS port_addr); `mema.c` widening (D453); `initanitable.c` heap slots; GE_DETERM wall-clock timers (D117 follow-up, blocks using determinism mode for frame gates). **#108:** widening the `sndPlaySfx` bound disables the guard everywhere; prefer a range check. |
| Port-memory vertex/pool bounds hardening | post-1.0 (decided; not a v0.5.0 blocker) | D545, D549 | D545 (rockets through floor, fixed) left one residual: ~1 dropped rocket in ~60 spawns (Archives, akimbo), consistent with the room-pool hypothesis (`bgBuildRoomVtxBounds` allocation failing leaves no bounds) but unproven; D549 (same stack-overflow class in grenade/knife/object throws) is fixed. Both shipped. Proposed hardening: a port-memory bounds table validating vtx/pool pointers against the known arena windows. |
## 3. Verification debt (fixed; a live check is owed)
@@ -174,9 +174,9 @@ A single campaign pass with these on the checklist would clear most of them.
| Item | Status | Refs | Notes |
|---|---|---|---|
| macOS (Intel + Apple Silicon) / ARM64 Linux | open (target 0.9.0/1.0, maintainer 2026-10-02) | PRs #88, #95; #101 | Outside drafts, parked 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. |
| macOS (Intel + Apple Silicon) / ARM64 Linux | open (target 0.9.0/1.0, maintainer 2026-10-02) | PRs #88, #95; #101 | Outside drafts, parked until after 1.0. **Address model decided (2026-09-30): port_addr (#95)**, a `portN64ToHost`/`PORT_N64PTR` chokepoint with a `PORT_ADDR_STRICT` validator. The `macos` integration branch is on origin with #88 (merged), #132 and #95 (open); nothing merges to `main` before the post-1.0 pass. **Open against `macos`:** #131 (self-hosted sweep scaffold, never run on a Mac; needs #95 first). **On the next rebase onto `main`:** take #131's `selfhosted.yml`; drop #95's `crouched_rifle` hunk (our #120/D445 is correct); relabel #95's D29x/D405-D421 labels. Needs the §2 ABI-hazard sweep; the maintainer tests firsthand before merging. |
| Platform parity gate (Linux co-equal with Windows) | open | — | **Decided 2026-09-30:** yes. Every released platform carries the same gate: a 21-level sweep plus a campaign pass. Needs the golden baselines on both platforms first (§7). The last known Linux crash (D190) was closed as not-a-bug. |
| Low-end GPUs (~30 fps on Celeron/HD 400) | fixed, verify on N3060 class | D339, D481, D482, #92 | **D481 (2026-10-02):** fast3d ran inline on the scheduler thread, delaying retrace delivery so the tick gate skipped frames; with the render worker a Lenovo X220 (HD 3000) went 44-46 → 60.0 fps. **D482:** Atom/Celeron-class GPUs (HD 400-605, UHD 600/605, software renderers) get DrawDistance/LodDistance 100 and MSAA 1 once, where still at defaults. **D473:** uncached per-batch getenv fixed. Direction: real optimizations, not a preset. **Next only if N3060-class hardware still drops:** one `GE_PERFSTAT=1` run there (`gpu=` vs `run=`), then a lighter renderer-keyed default (GPU) or fast3d per-batch cuts (CPU; local `PERF-HOTSPOTS.md`). |
| Low-end GPUs (~30 fps on Celeron/HD 400) | fixed, verify on N3060 class | D339, D481, D482, #92 | **D481 (2026-10-02):** fast3d ran inline on the scheduler thread, delaying retrace delivery so the tick gate skipped frames; the render worker took a Lenovo X220 (HD 3000) from 44-46 to 60.0 fps. **D482:** Atom/Celeron-class GPUs (HD 400-605, UHD 600/605, software renderers) get DrawDistance/LodDistance 100 and MSAA 1 once, where still at defaults. **D473:** uncached per-batch getenv fixed. Direction: real optimizations, not a preset. **Next only if N3060-class hardware still drops:** one `GE_PERFSTAT=1` run (`gpu=` vs `run=`), then a lighter renderer-keyed default (GPU) or fast3d per-batch cuts (CPU; local `PERF-HOTSPOTS.md`). |
| Statue/Cradle hitching (maintainer, 2026-09-30) | parked (not player-visible, 2026-10-02) | `GE_PERFSTAT` spike counters | **Diagnosed (D475, D477, D480):** no sustained load — the ~33 ms frames are the frame limiter skipping a retrace after a late gfx task (`boss.c`, D13-class timing); CPU bursts were CI-palette re-imports (ruled out, D477) and in-play shader compiles (fixed, D480). **Parked 2026-10-02:** a play session with the timeline probe felt no hitching. Reopen only if a visible hitch is reported (lever: pre-warm at level load). |
| Steam Deck preset skipped when first launched from Desktop Mode | verify | D283 | Fixed on `release/v0.4.1`: the Deck is detected by DMI hardware id (Valve Jupiter/Galileo; `STEAMOS` as a second signal) and the preset applies once per ini, only while Fullscreen/Maximized are untouched. Only a Windows build and a Linux `-fsyntax-only` check ran. **Owed: one run on a real Deck** (Desktop-Mode-created ini, then Game Mode; expect fullscreen 1280x800 and the log line `video: Steam Deck (DMI Valve ...)`). The Known-issues row goes when this ships. |
| Windows 7 | open | #98 | **Re-checked 2026-10-02: likely feasible around 1.0.** The exe imports no Windows 8+ functions and the bundled SDL2 2.32.10 still supports Windows 7. The real blocker is the first-run converter `ge007-convert.exe` (PyInstaller, Python 3.9+): the native C converter (#6 Part B) removes it, or a separate Python 3.8 build. Work: one Windows 7 VM test, then manual per-release checks (CI cannot test it). Supersedes the earlier 'won't-fix' call. |
@@ -189,8 +189,8 @@ ultrawide, FOV, MSAA/anisotropic/texture-filter options (including N64
3-point), draw and LOD distance, VSync and a frame cap, show-FPS,
keyboard/mouse rebinding with a GEPD-style layout, pad deadzone,
sensitivity, smoothing, southpaw and triggers, rumble, crosshair and HUD
customisation, HUD scale, music/FX volume, no-hit-flash, SkipIntro
(experimental), All unlocked (experimental, see D387), and the F10 overlay.
customisation, HUD scale, music/FX volume, no-hit-flash, SkipIntro,
All unlocked (see D387, D442), and the F10 overlay.
The modern defaults (native widescreen, overscan crop, 250% draw/LOD
distance, bilinear filtering) are **deliberate**. They are not a fidelity
@@ -204,20 +204,23 @@ Background research lives in the maintainer's local notes (gitignored):
| Item | Status | Refs | Notes |
|---|---|---|---|
| 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). |
| Log file (next release) | open | maintainer 2026-10-06 | Write `ge007.log` next to the ini (console output is lost on a normal launch). Crosshair pointer done in v0.5.0 (D555). |
| F10 / PC Options menu: PD-style dialog refactor + watch-consolidation review | verify | D504-D512, `MENU-PD-ALIGNMENT-PLAN.md` (maintainer's local notes) | **Built 2026-10-04 (D504); follow-ups merged:** D505/D506/D507 (wording, real-unit sliders, descriptions); D508 forced aspect ratios (16:9 / 21:9); D509 2-frame hold after an aspect change (rare gun glitch = known issue); D510 overlay + FPS counter same size front end vs level; D512 `Game.HudScale` scales the F10 overlay. **Owed (live pass, `docs/dev/notes/MENU-LIVE-CHECKLIST.md`, launches A/B/C):** look-and-feel vs PD; mouse, keyboard and pad in a level and on the front end; HudScale 75/100/150; 21:9; display-mode round trip across a restart; split-screen 2P with the overlay open. **Gaps:** background blur (post-1.0, §5c); per-seat control scheme is #139 / D513. Watch consolidation: not cheaply feasible (parked as T5). |
| Defaults/presets consolidation: one `Display mode` switch | verify | D504, D440, `MENU-PD-ALIGNMENT-PLAN.md` §5 (local notes) | **Landed 2026-10-04 (D504):** the derived `__DisplayMode` row shows Modern / Original N64 / Custom (no ini key); F10 dropdown + left/right stepping, front end steps too; one reset row per dialog covering exactly its keys; editing a non-bundled key does not flip Display mode. **Decided 2026-10-03: tri-state**, flipping to Custom when the user edits a bundled key. **Owed (live):** display-mode round trip across a restart; read of the regrouped pages and wording (MENU-LIVE-CHECKLIST.md A1/A2/A4). |
| Control style Original shows the game's own N64 styles | verify | #139 follow-up, D516, D517 | **Landed 2026-10-04 (D516, merged @ 91595597):** "Controller style" row (Original only; the game's own 1.1-2.4 per seat, saved to the Bond file; front end reads "(in a mission)"); D498 presets hidden in Original. **D517:** hold-repeat on any Bond row dropped fps (EEPROM rewrite per detent); now staged like sliders. Live passes 2026-10-04 (maintainer): single player + split-screen 2P with different styles per seat. **Owed:** the 2.x second-pad pass (real second controller). |
| Pad layout preset per player (setting P2's no longer moves P1) | verify | D518, D498 follow-up | **Landed 2026-10-04 (D518, merged @ 91595597):** `Input.PadPreset` kept as seat 1's key + new `Input.PadPreset2/3/4`; the F10/front-end preset row follows the Select-player seat. Headless: GE_PADMAPTEST PASS; **live passes 2026-10-04** (P2 change leaves P1 alone; split-screen: each seat ran its own preset). |
| Pad layout preset per player (setting P2's no longer moves P1) | verify | D518, D498 follow-up | **Landed 2026-10-04 (D518):** `Input.PadPreset` kept as seat 1's key + new `Input.PadPreset2/3/4`; the F10/front-end preset row follows the Select-player seat. Headless GE_PADMAPTEST PASS; **live passes 2026-10-04** (P2 change leaves P1 alone; split-screen: each seat ran its own preset). |
| Control scheme selectable: Ext / Original control style | verify | #139, D513, D498, D166 | **Landed 2026-10-04 (D513, merged into `release/v0.4.1`).** `Input.ControlScheme` (row "Control style", Ext default / Original) on the Controller page, both UIs. Original leaves the game's own style alone; D498 presets set layout and stick swap only in Original; 2.x runs faithfully; global in v1, per seat with the Select Player work. **Owed:** pad pass in both modes, watch-selected 1.1-1.4, split-screen 2P with different styles, 2.x with a second pad. |
| Outside PRs #107–#109, #122–#124 | decided | #122 hot-plugged pads never reopen, #123 persistent crosshair (`gunfire.c`), #124 compact health/armour bars (`bondview2.c`); #107/#108/#109 (italoarruda) | **Decided 2026-10-01 (local `PR-REVIEW-*.md`):** **#120/#121/#122** merged locally (#122 = D450); close on push. **#123**: accept-with-changes, deferred (Original N64 preset, relabel D436, Rule-2 line, 2P + sniper-zoom check). **#124**: deferred (ignores HudScale, overlaps status text, ultrawide anchoring, split-screen untested). **#133**: duplicates D472; close as superseded on push. **#109** (pad rebinding): decline as-is; supersede with our pad presets (§5a), crediting the author (Co-authored-by). **#107**: folded into D441; close on push. **#108**: belongs with `macos`; ask for a range check. |
| Outside PRs #107–#109, #122–#124 | decided | #122 hot-plugged pads never reopen, #123 persistent crosshair (`gunfire.c`), #124 compact health/armour bars (`bondview2.c`); #107/#108/#109 (italoarruda) | **Decided 2026-10-01:** **#120/#121/#122** merged locally (#122 = D450); close on push. **#123**: accept-with-changes, deferred (Original N64 preset, relabel D436, Rule-2 line, 2P + sniper-zoom check). **#124**: deferred (ignores HudScale, overlaps status text, ultrawide anchoring, split-screen untested). **#133**: duplicates D472; close as superseded on push. **#109** (pad rebinding): decline as-is; supersede with our pad presets (§5a), crediting the author (Co-authored-by). **#107**: folded into D441; close on push. **#108**: belongs with `macos`; ask for a range check. |
| Gamepad presets: duplicate-binding warning, Xbox stick behaviours | open (after v0.5.0; documented in the README) | D469, D498, D518, D394 | D469 (pad action table, Jinx 1.1 default, Custom per-seat bindings), **D498** (Xbox 1.2 Christmas / 1.3 Frost / 1.4 Elektra) and the per-seat preset (**D518**) are done and maintainer-verified with pads. Not done (maintainer 2026-10-04: skip in game for v0.5.0, document only): a warning when two actions share a button; the Xbox "left stick aims the crosshair" and "D-pad copies the left stick" behaviours (D394). |
| Pad button names on a Nintendo pad | verify | D471; local note `PAD-MENU-AUDIT.md` | Audit done 2026-10-01 (no controller-only trap; D472 fixed G1-G3). D471 family button names + player LEDs: **PS5 DualSense verified 2026-10-03.** **Owed:** a Nintendo (Switch) pad check; the maintainer has no such pad, so this waits for one or for a user report. |
| Mute and auto-pause on focus loss; PNG screenshots to `screenshots/` | parked | D231, PR #56 (closed unmerged), `docs/dev/notes/parked/0001-QoL-*.patch` (local) | Deprioritised by the maintainer on 2026-09-25. F12 today is the dev PPM dump. |
| Frame rate above 60 (120/144/uncapped with interpolation) | decision | [`dev/UNLOCKED-FPS-PLAN.md`](dev/UNLOCKED-FPS-PLAN.md) | The menu cap is {30, 60} (the ini accepts 0 = uncapped, but the sim ticks at the VI rate). The plan's Phase 0 is resolved by D248 (the default was a port bug; it is a real 60 fps) and its Phase 1 audit and Phase 2 slice scope are written (landed 2026-09-30), but the header still reads parked. Next step is a standing sim/render-rate measurement, not the old 30 fps investigation. Must stay compatible with netplay determinism. |
| PD Extended Options parity: settings that need new dev work | done (reviewed 2026-10-04; two rows deferred) | `MENU-PARITY-SONNET-PLAN.md` §3 (local notes); pd_port `port/src/optionsmenu.c` @ 32a1cb9 | **Reviewed 2026-10-04 (maintainer decisions; local notes `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. |
| PD Extended Options parity: settings that need new dev work | done (reviewed 2026-10-04; two rows deferred) | `MENU-PARITY-SONNET-PLAN.md` §3 (local notes); pd_port `port/src/optionsmenu.c` @ 32a1cb9 | **Reviewed 2026-10-04 (maintainer decisions; local notes).** **Done:** D511 fullscreen mode, Center window, crosshair opacity and color by health (live check owed, MENU-LIVE-CHECKLIST.md A4/A5/B3). **Watch Screen size/Ratio:** stay on the watch, no PC rows; the game saves watch settings on watch close (D349 correction) and they don't fight Aspect ratio (README). **Deferred past v0.5.0:** mouse lock mode (Auto/Always/Off; aim lock itself = `Input.AimMode`, D337); per-seat controller device select (needs a stable pad id; MP input layer). **Closed:** edge deadzone = `Input.AimRange` (D338); crosshair sway N/A (GE has none; gun sway is D428); use key reloads, no toggle (Ext keeps D378's GEPD split, Control style Original gives the N64 behaviour); per-seat options/binds (pad binds already per seat, D469/D518; keyboard/mouse is one device); no Detail textures toggle (`Video.DetailBaseTile` is the D236 fix); N/A confirmed: HUD centering (D335b), glare, overexposure, GE64 muzzle flashes, MP death music, radial menu speed, language filter, crosshair speed. **Other rows:** analog movement/stick scale/crouch → #139, T7; tickrate/framerate limit → frame rate above 60. |
| One PC options UI: front-end "PC Options" opens the F10 overlay over the folder graphic | verify | D519, D343, D504, D510, D512 | **Landed 2026-10-04 (D519):** the file-select "PC Options" entry opens the F10 overlay over file select; the `MENU_PC_OPTIONS` screen and four old probes are gone; Bond-file rows and the watch chooser are keyed to file select. Headless checks pass. **Owed:** by-eye mouse, keyboard and pad pass on file select; profile chooser + Controller style with a real profile; footer text overlaps the Copy/Erase bar. |
| Emulator saves load directly in the game (no tool for import) | verify | D514, D492; `port/src/eepimport.c` called from `geEepromLoad`; D297 `legacycrc.c` same pattern | **Landed 2026-10-04 (D514).** Copying a 1964/Project64 `.eep` in as `data/ge007.eep` works: on first launch each region that validates in emulator format and not port format is converted in place (same rules as `eep_convert.py`); regions invalid in both are left to the game's wipe; port saves untouched; the original is backed up to `ge007.eep.emulator.bak`. Export stays with `eep_convert.py`. Headless verify 6/6 byte-identical to the reference. **Owed:** by-eye mission select after importing a real 1964 save. |
| DexDrive / GameFAQs `.n64` saves | idea (easy, post-1.0) | D514; sample `scratch/eepinv/gamefaqs_dexdrive_2001.eep` (local) | **Noted 2026-10-04.** Old GameFAQs GoldenEye saves are DexDrive Controller Pak dumps (`123-456-STD` header, then a 32 KB pak) whose note (game code `3BADD1E5`, 2 pages) holds the 512-byte cartridge EEPROM. Extracting it and zero-padding to 2048 bytes gives a valid emulator-format save (TenebrusoM sample: all 6 CRCs OK). Support = walk the note table, pull the note, pad, hand to the D514 importer (or an `eep_convert.py dexdrive-to-pc` subcommand). Not tested in game. |
| Title bar and window icon (no FPS counter; static "GoldenEye 007" title + GE-crosshair window icon) | done (v0.5.0 polish) | maintainer request 2026-10-17; D550; `port/src/video.c`, `port/fast3d/gfx_sdl2.cpp`, `cmake/icon/ge007.rc`; icon art = `docs/img/icon/ge007-icon.png` (`tools_pc/make_icon.py`) | **DONE 2026-10-17 (D550):** static title bar (live FPS stays on F10), `SDL_SetWindowIcon` from a generated 32x32 header, exe icon via `ge007.rc`. Original generated mark, no game imagery. Build-verified; maintainer live window/taskbar check owed. |
| Opt-in update check (`Game.CheckUpdates`, off by default) | verify | D551; `port/src/updatecheck.c` | **Landed 2026-10-06:** Game-page toggle; one HTTPS GET per launch to `/releases/latest` (no pre-releases); Windows loads `winhttp.dll` at runtime only. Live-verified (Win/Linux/Deck), security-reviewed. |
### 5b. GE+ / Dab's-mod gameplay options (opt-in; touch game code)
@@ -271,19 +274,19 @@ and a stage loader.
| 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-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`. |
| Golden baselines | partial | `tools_pc/verify.sh`, `tools_pc/golden/README.md`, D117/D522/D529/D531 | **2026-10-06: all three sets (win/linux/deck) re-based for D543/D546**, A/B-confirmed; history in the golden README. **Windows gate GREEN (2026-10-05 re-base):** 21/21 PASS honest sequential. The recipe pins the save CONTENT (D529: `verify.sh` installs the canonical in-tree save `tools_pc/golden/ge007.eep` per level) and per-level windows — `900-1500:300` on 14 levels, `1000-1400:200` on Surface 2/Streets/Depot/Cradle/Frigate, `1500-1700:100` on Dam — because the 900 stem sits inside the wall-clock-paced intro flyby (D117; dam measured 1.6–7.2 % run to run). No tol limit raised. **Linux set (D525 → D530 → D531):** D530's re-round ran under the box-local save (`capture_p7.sh`'s re-pin overwrote D529's canonical install); D531 fixed the tool and re-captured all 21 levels under the canonical eep (47/63 frames moved). **Cross-platform spread recomputed 2026-10-05** from the committed pairs (`framediff.py` win-vs-linux at the stems, tol 2): exact **0.380–5.120 %** on the 20 non-Cuba levels (silo's 900 stem is the outlier: cell mean 34.3, phash 10), structural **21/21 clean**, max phash 40 (cradle, at threshold); Cuba 1.357–12.663 % is the structural-tier-only level. **Pixel gate green on all three, final v0.5.0 build (2026-10-06):** win 21/21; linux 21/21 on the X220 (`DISPLAY=:1` makes the GL probe see the real GPU; one Jungle flyby-jitter miss passed twice solo); deck 21/21 (needs `XAUTHORITY` for the probe over ssh). |
| Flicker scan (A/B/A frame alternation) | parked (2026-10-03: tooling, nothing blocked on it; local spec `scratch/birdman64-rows-investigation.md` §4) | `GE_PCDUMP`, `GE_INPUTSCRIPT` | Dump **every** frame of a scripted run at low resolution and flag frame n when it differs from n-1 but matches n-2. For any old-vs-new pixel diff outside the golden gate, run a same-commit control first. Reference: Birdman64 `tools/flicker_scan.py`. |
| Release build-provenance attestation | verify | `.github/workflows/ci.yml` release job; `.github/SECURITY.md`; README Status section | **Implemented 2026-10-04:** SHA-pinned `actions/attest-build-provenance` attests `dist/*.zip[.sha256]` + `dist/*.tar.gz[.sha256]` in the tag-only release job (`id-token: write` + `attestations: write` on that job only). Backs SECURITY.md's "unsigned, but reproducible" stance with `gh attestation verify <file> --repo jkdansereau/goldeneye-pc-port`; documented in SECURITY.md and the README. **Owed:** the v0.5.0 tag build is the first real run; confirm the step passes and verification succeeds on a downloaded asset, then delete this row. |
| Self-hosted CI runner link failure | open | D244 | Needs a `pacman -Syu` on the runner (maintainer-only access). Release tag builds use GitHub runners, so it doesn't block a release. |
| Remove temporary scaffolding before a release | partial | [`dev/GE-ENV-PROBES.md`](dev/GE-ENV-PROBES.md), D302 | Passes 2026-09-30 to 2026-10-03 removed the closed-finding probe suites (registry in `GE-ENV-PROBES.md`, `gen_env_probes.py --check` passing); TEMP probes `GE_TESTSTYLE` / `GE_D516` removed 2026-10-04. **Left:** D104 (kept while D294 is in verify); the M-183/M-185 clamps, **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 | 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. |
| P12 U5 review of `tools_pc/**` (release-diff, B10) | partial | D531/D532/D533 | Reviewed 2026-10-05 (the unit that owed the P7 merge). **Blocker found + fixed (D531):** `capture_p7.sh`'s per-level re-pin restored the local save (see Golden baselines). **Should-fixes applied as D532 (tooling-only):** `verify.sh`'s `pin_ini_640x480` fails closed like `pin_eep`; `GOLDEN_SEED` hard-pinned with a warning for a stray `GE_RSEED`; `audiodebug.ps1`'s dead `-AB`/`-Old` switches removed. Box pixel verify done 2026-10-06 (see Golden baselines). |
| Findings log and doc size | partial | `tools_pc/docs_budget.py` | Budget checker (per-file and tier-1 token budgets; `--toc FILE`). `findings.md` is still about 2 MB (~500k tokens), so agents must keep using the index. Nothing blocks; trim only when a budget check fails. |
| Branch and PR housekeeping | open | — | 30 landed local branches pruned 2026-09-30 (tips in `prune-branches-2026-09-30.txt`, notes archive). **On hold by the maintainer (do not prune yet):** probe-only `d243-verify-local`, `investigate/d243-cutscene-race`, `park/d236-probes-m139`. Local `main` and `release/v0.4.1` are deliberately not pushed. PR #62 (mouse dt-decouple) stays held until mouse feel is fundamentally better. |
| Docs site: Web 1.0 phase 2 | decision | — | Phase 1 shipped. Phase 2 (centred serif page) is plan-only. |
| Docs site: press/SEO pass (2026-10-05) | partial | `docs/` site pages | Landed on the pure site pages + presentation layer: real `<h1>` 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: press/SEO pass (2026-10-05) | partial | `docs/` site pages | Landed on the pure site pages + layout: real `<h1>` masthead, `color-scheme`/`theme-color`/`max-image-preview`/`rel=me`, ink-mute >=4.5:1, focus-visible + selection, 15px body, mobile padding; `date`/`modified_time` front matter on the pure pages. Home-page additions (pull-quote, Overview fact table, press section) **removed on review 2026-10-05** as duplicates. The five short topic notes (the-software-rsp, the-asset-pipeline, framerate-and-pacing, input-and-aim, saves) were dropped from the published set (files stay as working material). **Open:** the same front matter on the *shared* docs is deliberately untouched (authoritative working docs, not edited for site polish). |
| Agentic-development deep dive (doc) | queued — not yet (2026-10-05) | `docs/dev/agentic-development.md` | Write-up owed for the public case study: per-agent commit/work estimates (manual attribution pass over the session log, incl. D350-style commit-date divergences), a code-quality audit of the trial models (GPT-6 (Sol) et al.), and process feedback / findings. Deferred by the maintainer; nothing here blocks. |
| Docs site: split Security & fidelity -> two pages + v0.5.0 update pass (2026-10-05) | done (local, unpushed) | `docs/security.md`, `docs/fidelity.md` (was `security-and-fidelity-status.md`) | Split on review (the homepage pointer sat near Known issues and read as if there were security *issues*); both pages cross-linked and linked from the hub and index.md's Download section. Update pass 2026-10-05 against the `release/v0.4.1` tree: no network includes/symbols/HTTP literals in compiled `src/`+`port/`; no registry or library-loading calls; converter imports no network module; bundle manifests re-checked (allowlist + ROM/oversized-blob hard-fail); region macros re-compared vs the Makefile (all three regions); library classification re-checked (CMake globs libultra+libultrare audio, gu, `io/vitbl.c` only). Full game-logic audit not re-run; recent `src/game` edits are finding-gated. |
| Docs site: final sanity sweep + anchor fixes (2026-10-05) | done (local, unpushed) | `docs/porting-notes.md` (3 TOC hrefs), `docs/404.html` (permalink) | Audit of the 10-page published set: one h1 per page, no heading-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. |
| Docs site: final sanity sweep + anchor fixes (2026-10-05) | done (local, unpushed) | `docs/porting-notes.md` (3 TOC hrefs), `docs/404.html` (permalink) | Audit of the 10-page published set (one h1 per page, no heading skips, in-page anchors resolve, unique titles/descriptions, og:image/canonical, JSON-LD, no local paths, served == built). Three `porting-notes.md` TOC hrefs had a double hyphen where kramdown emits one: corrected at source against the live ids. `docs/404.html` got `permalink: /404.html`. Preview-builder slug rule fixed to kramdown's. |
## Community forks (not ours; reference only)
+4 -1
View File
@@ -11,7 +11,10 @@
large image preview in search cards, which is what makes the
in-engine stills legible in a coverage roundup. -->
<meta name="robots" content="max-image-preview:large">
<link rel="icon" type="image/jpeg" href="{{ '/img/shots/shot-28.jpg' | relative_url }}">
<!-- Favicon: the project's own crosshair mark (tools_pc/make_icon.py),
not a game still -- original art, no derivative-work exposure. -->
<link rel="icon" type="image/png" href="{{ '/img/icon/ge007-icon.png' | relative_url }}">
<link rel="apple-touch-icon" sizes="512x512" href="{{ '/img/icon/ge007-icon.png' | relative_url }}">
<!-- Entity disambiguation: the maintainer behind every page. -->
<link rel="me" href="https://github.com/jkdansereau">
{%- seo -%}
+8
View File
@@ -98,6 +98,12 @@ Two classes:
| `GE_PADMAPTEST` | `port/src/input.c` (`inputInit` -> `padMapSelfTest`) | D469: boot-time self-test of the gamepad action table. Proves preset 0 == the legacy hard-wired mapping (20,971,520 synthetic cases), logs the per-source mask table, a Custom remap, per-seat independence, the Start guard and the binding API; restores config afterwards (nothing persisted); the game then continues. Cached (`GE_ENVFLAG`). | **live** (harness) |
| `GE_D466LOG=<n>` | `port/src/drawdistgameplay.c` (`portD466FrameLog`, called from `bgDetermineVisibleRooms`) | D466 dev probe: every `<n>` calls (per player view) logs `D466 f=.. active= ext= auth= subset= hext= hauth= chrOn= chrGp= seen= hgame=` to stderr: extended vs authored room-set counts + hashes, ONSCREEN chrs vs gameplay-on-screen chrs, `HAS_BEEN_ON_SCREEN` count, and a chr-state hash. Cached. | **live** (D466 dev probe) |
| `GE_D468_SCALE=<0.05..1>` | `port/src/drawdistgameplay.c` (`portD468UpdateClamp`) | D468 dev falsifier: multiplies the AI-view-clamp fractions (and so turns the clamp on even at 16:9 / FOV 100), so a headless `GE_D466LOG` run can show the authored room set and gameplay on-screen chrs shrinking. Cached; out-of-range values ignored. | **live** (D468 dev probe) |
| `GE_SHADEPERSP=1` | `port/fast3d/gfx_opengl.cpp` (`gfx_opengl_init`, shader build) | D540 A/B switch: perspective-correct shade-colour interpolation instead of the default RDP-style screen-affine (`noperspective`) one (the sky horizon fade). Read once at GL init. | **live** (D540 A/B) |
| `GE_FOGPIXEL=1` | `port/fast3d/gfx_opengl.cpp` (`gfx_opengl_init`), `port/fast3d/gfx_pc.cpp` (`gfx_sp_vertex`) | D543 A/B switch: the D540 exact per-pixel fog (fog * w, divided by w in the shader) instead of the default per-vertex RSP fog (honest negative-w divide, CPU near clip lerps it). Read once at GL init. | **live** (D543 A/B) |
| `GE_FOGVERTEX` | (removed) | **removed** -- D540's per-vertex-fog opt-in; per-vertex fog is the D543 default, the opt-out is `GE_FOGPIXEL=1`; tombstone kept. | **removed** |
| `GE_NEARCLIP=0` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_tri1`) | D543 A/B switch: disable the CPU RSP-style near-plane clip (default on) and leave near-plane clipping to the GPU. Cached on first triangle. | **live** (D543 A/B) |
| `GE_NEARCLIPFOG=recompute` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_tri1`) | D543 A/B switch: recompute the per-vertex RSP fog value of near-clip-created vertices from their new z/w (as `gfx_sp_vertex`) instead of lerping it (default). Cached on first triangle. | **live** (D543 A/B) |
| `GE_FOGSNAP=1` | `port/fast3d/gfx_opengl.cpp` (fragment shader build) | D540 A/B switch: re-enables the D503 full-fog snap (fog >= 247/255 -> full), off by default since D540 (its hard edge swept across the ground in chunks). Cached on first shader build. | **live** (D540 A/B) |
| `GE_PADFAMILY=xbox\|ps\|nintendo` | `port/src/input.c` (`inputPadFamily`) | D471 dev override: force the controller family used for button names in the PC menus (headless screenshots without the hardware). Cached. | **live** (D471 dev knob) |
| `GE_D466_FORCE=1` | `port/src/drawdistgameplay.c` (`portD466Active`) | D466: run the authored-distance split even at DrawDistance 100% (identity test: authored set must equal extended). Cached. | **live** (D466 test hook) |
| `GE_D466_OFF=1` | `port/src/drawdistgameplay.c` (`portD466Active`) | D466 kill switch: disable the gameplay/render split (every gameplay reader falls back to the extended-range state, i.e. the pre-fix #125 behaviour). For A/B only. Cached. | **live** (D466 kill switch) |
@@ -112,6 +118,8 @@ Two classes:
| `GE_OPTIONSOVERLAY_SECTION` | `port/src/optionsoverlay.c:989` | Isolated screenshot/probe of a nested F10 page by row key (opens that header; never a menu mode). | **live** (diagnostic) |
| `GE_OPTIONSOVERLAY_SELECTBACK` | `port/src/optionsoverlay.c:995` | Screenshot-only: pins the selection to the Back row when the overlay auto-opens, to check it against a normally selected setting on the same page. | **live** (diagnostic) |
| `GE_OPTIONSOVERLAY_DROPDOWN` | `port/src/optionsoverlay.c:1195` | Screenshot-only: with the overlay auto-opened on a page, opens the dropdown popup of the named row key (e.g. `Video.TextureFilter`, `__Resolution`) and focuses it. | **live** (diagnostic) |
| `GE_OPTIONSOVERLAY_ATFRAME` | `port/src/optionsoverlay.c:3362` | Re-applies the GE_OPTIONSOVERLAY / _SECTION / _SELECT request at emit frame n so it survives a level load (in-level screenshots, D558). Read once. | **live** (diagnostic) |
| `GE_OPTIONSOVERLAY_SELECT` | `port/src/optionsoverlay.c:1221` | Screenshot-only: selects the named row key on the auto-opened page (e.g. `Game.NoHitFlash`) so its tip shows (D558). | **live** (diagnostic) |
| `GE_OPTIONTREEPROBE` | `port/src/optionsoverlay.c:888` | D388: opt-in in-memory walk of the real F10 page tree; writes no settings, binds or profile bytes; restores UI state afterward. | **live** (diagnostic) |
| `GE_PRESETPROBE` | `port/src/optionsoverlay.c:957` (`presetProbe`) | D440: one-shot at overlay init — drives the "Original N64" and "Port defaults" preset rows through the real arm -> confirm activation and checks the "Active" state; logs `GE_PRESETPROBE: PASS/FAIL`. **Writes the live config** (persisted on exit) — use a throwaway data dir. | **live** (diagnostic) |
| `GE_OVPOINTERMAP` | `port/src/optionsoverlay.c:1726` | D391 pointer-map diagnostic: on LMB-down (and every 120 samples) logs SDL mouse coords vs GL drawable pixel coords + the hover row, to separate hit-band/scroll errors from coordinate-space mismatch. | **live** (diagnostic) |
+1 -1
View File
@@ -6242,7 +6242,7 @@ Local-only commits on top of `442eb2ff`: `aa19b354` (site SEO/redundancy),
## Environment
- Preview: `:8777` (PID may be stale after a reboot — `node
scratch/build_preview.mjs site && node scratch/serve_preview.mjs 8777`).
- Box ssh: `ssh the test box` (Windows→box works; box has no sshd —
- Box ssh: `ssh` to the X220 box (Windows→box works; box has no sshd —
everything rides the same session's stdin/stdout; pushes via
`cat f | ssh ... 'cat > dest'` or tar pipes).
- 3 parked worktrees (ci/d441/modpatch) untouched.
+27 -2
View File
@@ -40,7 +40,7 @@ D230,FIXED,In-level music quality:,"FIXED (M-121, v0.2.0) —"
D231,CLOSED,"QoL batch: mute-on-focus-loss + screenshot-hotkey productization (M-105, roadmap Tier 3, port-only).","CLOSED — PARKED/DEPRIORITIZED (user, 2026-09-25)."
D233,FIXED,All levels (intermittent):,"FIXED (port/fast3d/gfx_pc.cpp, gfx_sp_tri1) —…"
D234,FIXED,"Dam: guard towers along the dam structure invisible except the first one (M-106, user QA report).",VISUALLY VERIFIED FIXED (M-108) —…
D244,OPEN,Self-hosted CI runner (corpyvt6agent01) link failure:,"OPEN, not yet fixed. Next…"
D244,OPEN,Self-hosted CI runner link failure:,"OPEN, not yet fixed. Next…"
D228,FIXED,AK47 white missing-texture patches after D217's texpool fix —,"FIXED (port/fast3d/gfx_pc.cpp, palette_to_rgba32) —"
D204,FIXED,PC audio ran ~2 % below real time permanently (AI feedback loop's 3 ms setpoint vs PC jitter).,"FIXED (port/src/audio.c, port/src/libultra.c, port/include/audio.h) —"
D203,CLOSED,"Steam Deck (SteamOS, x86_64 Linux) v0.1.0 release bundle:",CLOSED — root cause + fix in D253.
@@ -283,11 +283,35 @@ D403,RESOLVED,Deck v0.4.0-pre test:,"RESOLVED (2026-09-27, user-confirmed on the
D404,RESOLVED,"""Aim style: Centred (PC)"" (Input.AimMode=1, D333/D337) has no effect when aiming with a gamepad; the row's own doc comment over-promises (""applies to every aim…","RESOLVED (2026-09-28, port-layer only):"
D405,CLOSED,"fast3d/OpenGL: MSAA above the driver max black-screens; two glBindFramebuffer calls pass an index where a GL name is required (PR #106, italoarruda, 2026-09-25…","CLOSED (2026-09-28). Merged from PR #106 (code file only; security-reviewed clean - port-layer GL state fixes, no external input, no allocations)."
D406,RESOLVED,front-end PC Options screen:,"RESOLVED (2026-09-28, port-layer only, Steam Deck-validated):"
D541,PARTIAL,One A press advanced two menu screens on the Steam Deck (Desktop Mode) —,"PARTIAL 2026-10-05 (mitigation landed, port-only:"
D542,OPEN,"Stray polygons left of Bond's gun, frequent on foggy levels (Streets, Surface 2) —",OPEN 2026-10-05 (triage:
D543,FIXED,Surface 2: lit area following Bond brighter than 1964 —,"FIXED 2026-10-06 (CPU RSP-style near clip in gfx_sp_tri1 + per-vertex fog default; build-verified, by-eye vs 1964 at next playtest)"
D544,FIXED,OS cursor flickers with F10 open on the main menu —,"FIXED 2026-10-05 (port-only, input.c; build-verified, maintainer mouse check owed)"
D545,FIXED,Player rockets can pass through the ground when fired near Bond's feet —,"FIXED 2026-10-06 (AVOID_UB array, gun.c; maintainer Archives preset A/B:"
D546,FIXED,PC settings standardization:,FIXED 2026-10-05 (port-only; build + maintainer check owed; golden re-base batched with D543)
D547,FIXED,Game.AllUnlocked applies live (no restart) —,"FIXED 2026-10-05 (port-only, main.c/optionsoverlay.c; build-verified, maintainer mission-select check owed)"
D548,FIXED,Walls near rocket fire/explosions banded black/white/yellow/blue (Aztec dark corridor; also seen on Deck) —,FIXED 2026-10-06 (gfx_opengl.cpp input clamp; root fix = D543 CPU near clip; maintainer Aztec re-check owed)
D549,FIXED,Thrown grenade/knife/object:,"FIXED 2026-10-06 (AVOID_UB coord3d, gun.c x3; build-verified)"
D550,FIXED,"Static title bar (no more per-second ""NN fps"" refresh) + project mark as favicon / window / taskbar icon —","FIXED 2026-10-17 (port + tooling only, video.c/gfx_sdl2.cpp/make_icon.py; ninja-command-verified + full build 249/249; maintainer live window/taskbar check owed)"
D551,?,"Opt-in update check (Game.CheckUpdates, default off):",ADDED 2026-10-06 (port layer only; live-verified Windows/Linux/Deck + F10 UI capture; security review fixes applied)
D552,FIXED,Dam ending cutscene: the first camera shot no longer swivels up onto Bond —,FIXED 2026-10-06 (port-only addition removed; code path back to the decomp; maintainer-verified live)
D553,FIXED,Surface 2: the haze around Bond brightened/darkened as he moved —,FIXED 2026-10-06 (fast3d:
D554,OPEN,F10 settings menu: wording/casing pass + two-line tips —,DONE 2026-10-06 (port/src/optionsoverlay.c; maintainer by-eye check owed)
D555,OPEN,Crosshair mouse pointer over the F10 / PC Options overlay —,"DONE 2026-10-06 (port/src/input.c, optionsoverlay.c, tools_pc/make_icon.py; maintainer by-eye check owed)"
D556,OPEN,"F10 feedback pass (tips trimmed, conditional-row order, wording fixes) + mouse scrollbar —","DONE 2026-10-06 (port/src/optionsoverlay.c; build only, by-eye check owed)"
D557,FIXED,"New/blank profiles start with Look ahead OFF on PC (Rule-2 sign-off, maintainer 2026-10-06) —",FIXED 2026-10-06 (src/game/file2.h DEFAULT_OPTIONS under #ifdef PORT; existing saves untouched; win gate 21/21)
D558,FIXED,F10 overlay tips cut off in a level (not on the front end) —,FIXED 2026-10-06 (optionsoverlay.c textRenderWide; verified by in-level capture)
D559,FIXED,"Look ahead off for untouched existing folders (maintainer-approved, port layer only) —",FIXED 2026-10-06 (watchsettings.c migrateUntouchedLookAhead)
D560,FIXED,F10 copy pass 3 (maintainer line review) —,FIXED 2026-10-06 (optionsoverlay.c tips/labels/value names; display text only)
D561,FIXED,"F10: ""Menu pointer"" row removed (front-end-only legacy option; confused players) —","FIXED 2026-10-06 (row/tip/names removed; Input.MenuPointerMode ini key, Direct default and the legacy code path kept for one release)"
D562,FIXED,F10 crosshair pointer hidden while a pad/keyboard drives the menu —,FIXED 2026-10-06 (drawCrosshairPointer draws only while s_mouseActive)
D563,FIXED,F10 root hub: Left/Right no longer open pages; held nav inputs latched on open/page change —,FIXED 2026-10-06
D564,FIXED,"F10: game's own ""Look up/down"" row removed (dead for mouse and centred pad, stacked with Invert look); value pinned to factory; tip polish —","FIXED 2026-10-06 (port only; Mouse / Controller ""Invert look"" are the inversion controls)"
D407,CLOSED,Tanks cannot be boarded/exited on PC —,CLOSED (2026-09-28 bookkeeping:
D408,FIXED,Game.SkipIntro skips the post-mission failure dossier:,FIXED 2026-09-30 (SkipIntro hook gated to first boot; live dossier check owed).
D415,FIXED,"Shooting a light fixture does not ""kill"" its flickering light (Bunker, Caverns):",FIXED (2026-09-29 bookkeeping:
D416,RESOLVED,"2-player split-screen crashes (issue #99, v0.3.0 4827bc8, fault addr 0xa43a0ed4, PC 0x14009e758):",RESOLVED in-harness (converter + D417 + D418 fix the 2P crash and split-screen flicker; 18/18 2P/3P/4P x 6-stage load runs clean; live pad play still owed).
D417,RESOLVED,enum PROP unsigned on GCC:,"RESOLVED (src/bondconstants.h PROP__PORT_SIGNED = -1, D3/D142 precedent; Rule-2 review owed)."
D417,RESOLVED,enum PROP unsigned on GCC:,"RESOLVED (src/bondconstants.h PROP__PORT_SIGNED = -1, D3/D142 precedent; Rule-2 review done 2026-10-06:"
D418,RESOLVED,"Split-screen: fast3d safe-area crop stretched the last-set (half-height) viewport over the whole window, so each player's view filled the screen and the last-d…","RESOLVED (gfx_set_split_screen, port-only)."
D419,RESOLVED,Split-screen input shared between players / P1 lost solo controls:,"RESOLVED (per-slot slotPlayer(), port-only; no current-player swaps from the poll thread; live-tested OK)."
D420,FIXED,MP score / stats screen shows an odd big number (issue #99) —,FIXED 2026-10-01 (mp/virtual-seats); 2P/3P/4P watch-menu runs show 0s.
@@ -394,4 +418,5 @@ D531,FIXED,capture_p7.sh's D529 adaptation was incomplete —,"FIXED 2026-10-05
D532,FIXED,The three P12 U5 should-fixes applied —,FIXED 2026-10-05 (tooling-only:
D533,FIXED,"The probe-doc drift that would have failed CI, plus three tracked-file accuracy/hygiene fixes (release-sweep spot-check) —",FIXED 2026-10-05 (GE-ENV-PROBES.md missing the GE_D526 family → gen_env_probes.py --check rc 1 = a CI-failing step; build-pc.sh local MSYS2 root in two messages; docs/fidelity.md og line; release-not…
D534,FIXED,Pre-release review of the 2026-10-05 local batch —,FIXED 2026-10-05 (verify.sh/capture_p7.sh INT/TERM trap; release-notes linux-gate wording; dead security-and-fidelity-status.md links; future-dated 2026-10-07 labels normalised; D526 probe dangling-e…
D540,FIXED,Fog rework (Deck playtest):,FIXED 2026-10-05 (port:
D450,FIXED,"Unplugging and replugging a controller was not picked up (user, GameSir-G7 Pro; 2026-09-29).","FIXED — port-only: the render thread's gfx_sdl_handle_events drains the same SDL queue as video.c's host pump but had no case for SDL_CONTROLLERDEVICEADDED/REMOVED, so it usually swallowed them and i…"
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
40 D231
41 D233
42 D234
43 D244
44 D228
45 D204
46 D203
283 D404
284 D405
285 D406
286 D541
287 D542
288 D543
289 D544
290 D545
291 D546
292 D547
293 D548
294 D549
295 D550
296 D551
297 D552
298 D553
299 D554
300 D555
301 D556
302 D557
303 D558
304 D559
305 D560
306 D561
307 D562
308 D563
309 D564
310 D407
311 D408
312 D415
313 D416
314 D417
315 D418
316 D419
317 D420
418 D532
419 D533
420 D534
421 D540
422 D450
+834 -5
View File
@@ -424,7 +424,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D231 | **QoL batch: mute-on-focus-loss + screenshot-hotkey productization (M-105, roadmap Tier 3, port-only).** `Audio.MuteOnFocusLoss` (drop mixed blocks while the window is unfocused; pause-on-focus-loss deferred because of the D204-family AI feedback loop) plus `Video.ScreenshotKey`/`Video.ScreenshotDir` (scancode-based key, timestamped `screenshots/` PPMs). Implemented M-105 as PR #56 (`port/src/audio.c`, `port/src/video.c`, `port/include/audio.h`), never merged. | **CLOSED — PARKED/DEPRIORITIZED (user, 2026-09-25).** PR #56 closed and the `feat/qol-mute-screenshot` branch was deleted from GitHub. The code is in no release branch; it's kept locally only (local branch `feat/qol-mute-screenshot`, commit `3f6e8d98`, plus a patch in the gitignored `docs/dev/notes/parked/`). If it's revived: rebase onto the current release branch and playtest alt-tab mid-level plus the custom screenshot key/dir. |
| D233 | **All levels (intermittent): indoor level geometry invisible near doors — you can see through walls/floors into rooms you can't see into, while world objects…** — full `## D233` entry at file tail | **FIXED (port/fast3d/gfx_pc.cpp, `gfx_sp_tri1`) —… |
| D234 | **Dam: guard towers along the dam structure invisible except the first one (M-106, user QA report).** — full `## D234` entry at file tail | **VISUALLY VERIFIED FIXED (M-108) —… |
| D244 | **Self-hosted CI runner (`corpyvt6agent01`) link failure: `undefined reference to 'objectiveGetStatus_WEAK'` (2026-09-12, first-ever build attempt on the…** — full `## D244` entry at file tail | **OPEN, not yet fixed.** Next… |
| D244 | **Self-hosted CI runner link failure: `undefined reference to 'objectiveGetStatus_WEAK'` (2026-09-12, first-ever build attempt on the…** — full `## D244` entry at file tail | **OPEN, not yet fixed.** Next… |
| D228 | **AK47 white missing-texture patches after D217's texpool fix — separate failure mode (M-97, `-level_34` FACILITY).** — full `## D228` entries at file tail | **FIXED (`port/fast3d/gfx_pc.cpp`, `palette_to_rgba32`)** — byte-order bug in the G_TT_IA16 branch; user-verified live. Fix write-up: second `## D228` section. |
| D204 | **PC audio ran ~2 % below real time permanently (AI feedback loop's 3 ms setpoint vs PC jitter).** — full `## D204` entry at file tail | FIXED (`port/src/audio.c`, `port/src/libultra.c`, `port/include/audio.h`) — measured; a by-ear pass on a real playthrough still owed |
| D203 | **Steam Deck (SteamOS, x86_64 Linux) v0.1.0 release bundle: Facility (`-level_34`) crashes "after loading in as James Bond" (user bug report).** — full `## D203` entry at file tail | CLOSED — root cause + fix in D253. |
@@ -691,11 +691,35 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D404 | **"Aim style: Centred (PC)" (`Input.AimMode=1`, D333/D337) has no effect when aiming with a gamepad; the row's own doc comment over-promises ("applies to every aim input … pad trigger") but the centred path is mouse-only (Steam Deck, 2026-09-27).** Full `## D404` entry at file tail. | RESOLVED (2026-09-28, port-layer only): the toggle now genuinely works on a pad. First logged as a communication bug and closed by relabel ("Aim style (mouse)"), then reopened on user challenge the same day: the pad path emits stick *deflection* into the game's crosshair integrator, so the pad was actually always in the **N64** model (a flick travels the crosshair; edge-scroll + spring-back only made a hold *feel* direct), and the "stick has no crosshair to travel" rationale was wrong. Fix: new `padDirectCompute()` in `input.c` (the pad twin of `hipDirectCompute`'s CENTRED branch -- same dead/watch/pause/cutscene gates, minus the mouse-grab gate; revised same day to the N64 path's verbatim quadratic curve -- `sign(v)\u00b7clamp(defl/70,\u00b11)\u00b2 \u00b7 (fov/60) \u00b7 3.5 deg/poll` (bondview2.c canNaturalTurn/canNaturalPitch), full-stick parity with the N64 path, pending retest on the refreshed build); the natural-pitch pad branch routes CENTRED through it and keeps N64's classic deflection behaviour (also the default, so no pad default changes). Labels reverted to device-neutral ("Aim style" / "Aim range"). **Steam Deck (2026-09-27/28): "centered aim is broken on deck too" -- fixed for real; the first-pass linear gain was measured as a dramatic sensitivity loss (2026-09-28) and replaced with the N64 path's verbatim quadratic curve (fov/60 base, 3.5 deg/poll top rate); retest on the refreshed build: user "good". RESOLVED.** |
| D405 | **fast3d/OpenGL: MSAA above the driver max black-screens; two `glBindFramebuffer` calls pass an index where a GL name is required** (PR #106, italoarruda, 2026-09-25; label rebased from the PR's D323, which collides with our D323 LOD finding) — full `## D405` entry at file tail | **CLOSED (2026-09-28).** Merged from PR #106 (code file only; security-reviewed clean - port-layer GL state fixes, no external input, no allocations). `resolve_msaa_color_buffer` clamps `msaa_level` to `GL_MAX_SAMPLES`; the resolve/copy paths bind `current_framebuffer ? framebuffers[i].fbo : 0` (the default framebuffer is GL name 0). Both Windows (opengl32) and Linux use this backend. Post-merge sweep 21/21 PASS + MSAA=16 boot check render-verified; link ritual clean. |
| D406 | **front-end PC Options screen: long sections (INPUT, 17 visible rows) run off the bottom of the 440x330 paper, and rows past the silent 14-row page cap (Crouch mode, Reset to defaults, Bindings…) are unreachable from the front screen** (user report 2026-09-28, v0.4.0 pre-push review) — full `## D406` entry at file tail | RESOLVED (2026-09-28, port-layer only, Steam Deck-validated): in-section pagination in `frontoptions.c` (11 rows/page; the vertical stepper crosses page boundaries both ways; dim 'Page p/N' marker + bottom hint on every page; slider bars start past long labels; F10 overlay untouched). D406b/c/d same-cycle refinements (label/track overlap, hint visibility/wording, hint position). |
| D541 | **One A press advanced two menu screens on the Steam Deck (Desktop Mode) — cause unconfirmed (Steam desktop layout A=Enter or a pad reseat)** — Deck playtest; full `## D541` entry at file tail | PARTIAL 2026-10-05 (mitigation landed, port-only: 70 ms menu release hold-off in `input.c` + overlay accept; one back per 150 ms) — mitigation, cause unconfirmed; Deck re-test owed |
| D542 | **Stray polygons left of Bond's gun, frequent on foggy levels (Streets, Surface 2)** — D540-build playtest; full `## D542` entry at file tail | OPEN 2026-10-05 (triage: GE_SHADEPERSP / GE_FOGVERTEX / rc1 A/B, then 1964) |
| D543 | **Surface 2: lit area following Bond brighter than 1964** — full `## D543` entry at file tail | FIXED 2026-10-06 (CPU RSP-style near clip in gfx_sp_tri1 + per-vertex fog default; build-verified, by-eye vs 1964 at next playtest) |
| D544 | **OS cursor flickers with F10 open on the main menu** — show/hide requests race (inputSuspendForOverlay vs the D519 front-end pointer); full `## D544` entry at file tail | FIXED 2026-10-05 (port-only, input.c; build-verified, maintainer mouse check owed) |
| D545 | **Player rockets can pass through the ground when fired near Bond's feet** — `gunInitProjectileFromPlayer` passes a single `s32` as a 20-entry room out-array (N64 overflow absorbed by `pad_a`; D490 / porting-notes §D24 class); full `## D545` entry at file tail | FIXED 2026-10-06 (AVOID_UB array, gun.c; maintainer Archives preset A/B: 1 drop in ~60 at wide view, residual = room-pool hypothesis, hardening backlogged) |
| D546 | **PC settings standardization: round defaults, vertical-degree FOV, "Gameplay view area" wording** — draw/LOD 2.0x, deadzone 25 %, mouse sens max 4x, FOV 30–90° in 5° steps (60 = N64, PD "Vert FOV" model), one-time ini migration; full `## D546` entry at file tail | FIXED 2026-10-05 (port-only; build + maintainer check owed; golden re-base batched with D543) |
| D547 | **Game.AllUnlocked applies live (no restart)** — F10 row seeds/clears the RAM unlock flags it owns; full `## D547` entry at file tail | FIXED 2026-10-05 (port-only, main.c/optionsoverlay.c; build-verified, maintainer mission-select check owed) |
| D548 | **Walls near rocket fire/explosions banded black/white/yellow/blue (Aztec dark corridor; also seen on Deck)** — D540 `noperspective` combiner inputs extrapolated past 1.0 by GPU near-clipping, then the CC wrap emulation wrapped them; full `## D548` entry at file tail | FIXED 2026-10-06 (gfx_opengl.cpp input clamp; root fix = D543 CPU near clip; maintainer Aztec re-check owed) |
| D549 | **Thrown grenade/knife/object: `s32 sp94` receives a 12-byte `coord3d` from `bullet_path_from_screen_center`** — D24 class (N64 overflow hit a dead slot, PC can hit live locals); full `## D549` entry at file tail | FIXED 2026-10-06 (AVOID_UB coord3d, gun.c x3; build-verified) |
| D550 | **Static title bar (no more per-second "NN fps" refresh) + project mark as favicon / window / taskbar icon** — original generated crosshair art (no game content); full `## D550` entry at file tail | FIXED 2026-10-17 (port + tooling only, video.c/gfx_sdl2.cpp/make_icon.py; ninja-command-verified + full build 249/249; maintainer live window/taskbar check owed) |
| D551 | **Opt-in update check (`Game.CheckUpdates`, default off): one HTTPS GET per launch to GitHub `/releases/latest`; `GE007_VERSION` added** — full `## D551` entry at file tail | ADDED 2026-10-06 (port layer only; live-verified Windows/Linux/Deck + F10 UI capture; security review fixes applied) |
| D552 | **Dam ending cutscene: the first camera shot no longer swivels up onto Bond — the D243 M-190 port-only look-at filter re-seed snapped it onto him** — full `## D552` entry at file tail | FIXED 2026-10-06 (port-only addition removed; code path back to the decomp; maintainer-verified live) |
| D553 | **Surface 2: the haze around Bond brightened/darkened as he moved — near-plane-clipped vertices lerped fog from a behind-the-eye corner that flips 0/255** — full `## D553` entry at file tail | FIXED 2026-10-06 (fast3d: fog recomputed on clip-created vertices by default, as the RSP; `GE_NEARCLIPFOG=lerp` = old A/B) |
| D554 | **F10 settings menu: wording/casing pass + two-line tips** — display text only, config keys unchanged; tips wrap instead of ".." — full `## D554` entry at file tail | DONE 2026-10-06 (port/src/optionsoverlay.c; maintainer by-eye check owed) |
| D555 | **Crosshair mouse pointer over the F10 / PC Options overlay** — the game's own crosshair sprite is drawn at the mouse, last in the overlay display list (OS cursor hidden; reworked 2026-10-06 from an SDL colour cursor); opt-out `Input.CrosshairCursor` — full `## D555` entry at file tail | DONE 2026-10-06 (port/src/input.c, optionsoverlay.c, tools_pc/make_icon.py; maintainer by-eye check owed) |
| D556 | **F10 feedback pass (tips trimmed, conditional-row order, wording fixes) + mouse scrollbar** — standard PC settings lose their tips, Controller page keeps Control style first (Profile chooser now sits above the first profile row, not row 1), No hit flash / Skip intro tips corrected, thin draggable scrollbar on overflowing pages — full `## D556` entry at file tail | DONE 2026-10-06 (port/src/optionsoverlay.c; build only, by-eye check owed) |
| D557 | **New/blank profiles start with Look ahead OFF on PC (Rule-2 sign-off, maintainer 2026-10-06)** — full `## D557` entry at file tail | FIXED 2026-10-06 (`src/game/file2.h` DEFAULT_OPTIONS under `#ifdef PORT`; existing saves untouched; win gate 21/21) |
| D558 | **F10 overlay tips cut off in a level (not on the front end)** — full `## D558` entry at file tail | FIXED 2026-10-06 (`optionsoverlay.c` `textRenderWide`; verified by in-level capture) |
| D559 | **Look ahead off for untouched existing folders (maintainer-approved, port layer only)** — full `## D559` entry at file tail | FIXED 2026-10-06 (`watchsettings.c` `migrateUntouchedLookAhead`) |
| D560 | **F10 copy pass 3 (maintainer line review)** — full `## D560` entry at file tail | FIXED 2026-10-06 (`optionsoverlay.c` tips/labels/value names; display text only) |
| D561 | **F10: "Menu pointer" row removed (front-end-only legacy option; confused players)** — full `## D561` entry at file tail | FIXED 2026-10-06 (row/tip/names removed; `Input.MenuPointerMode` ini key, Direct default and the legacy code path kept for one release) |
| D562 | **F10 crosshair pointer hidden while a pad/keyboard drives the menu** — full `## D562` entry at file tail | FIXED 2026-10-06 (`drawCrosshairPointer` draws only while `s_mouseActive`) |
| D563 | **F10 root hub: Left/Right no longer open pages; held nav inputs latched on open/page change** — full `## D563` entry at file tail | FIXED 2026-10-06 |
| D564 | **F10: game's own "Look up/down" row removed (dead for mouse and centred pad, stacked with Invert look); value pinned to factory; tip polish** — full `## D564` entry at file tail | FIXED 2026-10-06 (port only; Mouse / Controller "Invert look" are the inversion controls) |
| D407 | **Tanks cannot be boarded/exited on PC — the v0.4.0 use/reload split (D378/D393) removed the B-button tap the engine's tank handlers in `bondview2.c` consume (user report 2026-09-28, Runway/Streets)** — full `## D407` entry at file tail | **CLOSED (2026-09-28 bookkeeping: tank board/exit accepted by the user; shipped in v0.4.0).** Earlier: PARTIAL (fix landed in v0.4.0, port-only): present `GE_CONT_B` on the use (E / pad A) rising edge only while `g_PlayerIsInTank == 1` or `g_BondCanEnterTank != 0`; E keeps its D378 no-reload-fallback semantics elsewhere, N64 layout unaffected. D407(b) same cycle: front PC Options page-edge highlight clamp + mouse-wheel / W-S paging (wheel queue consumed on the menu, D223); its wheel mapping shipped inverted and was fixed to match W/S (wheel up = step up). D407(c): board-animation lockout, menu-accept B gate in tank states, in-tank aim routed through the legacy velocity stick + `Input.TankAimScale` knob. RESOLVED 2026-09-28: all port-only (src/game zero-diff), 10 TANKDBG probes stripped, release binary verified clean, user live tank drive signed off (board + exit OK, aim feel good). |
| D408 | **`Game.SkipIntro` skips the post-mission failure dossier: with it on, entering a level then aborting (watch Z+A) or dying (KIA) returns straight to the menus — no REPORT / "Mission status: KILLED IN ACTION / ABORTED" screen** (user report 2026-09-28; save/AllUnlocked ruled out) — full `## D408` entry at file tail | FIXED 2026-09-30 (SkipIntro hook gated to first boot; live dossier check owed). |
| D415 | **Shooting a light fixture does not "kill" its flickering light (Bunker, Caverns): the fixture takes the hit but the flicker keeps going (community report, issue #87 comment by the reporter, 2026-09-28, 00 Agent playthrough of v0.4.0).** — full `## D415` entry at file tail | FIXED (2026-09-29 bookkeeping: root-caused and fixed as D430 + D431, user-verified vs GEPD/1964 on Caverns 2026-09-29; #119). Earlier: ROOT-CAUSED as D430 (see there); fix in tree, live check owed. |
| D416 | **2-player split-screen crashes (issue #99, v0.3.0 `4827bc8`, fault addr 0xa43a0ed4, PC 0x14009e758): MP setup files `Ump_setup*Z` were never converted N64→PC** — full `## D416` entry at file tail | RESOLVED in-harness (converter + D417 + D418 fix the 2P crash and split-screen flicker; 18/18 2P/3P/4P x 6-stage load runs clean; live pad play still owed). Also D417, D418.
| D417 | **`enum PROP` unsigned on GCC: `prop >= 0` passed for `getPropForHeldItem()`'s -1 (fist) -> `modelLoad(-1)` null deref in any 2P+ stage (player.c `sub_GAME_7F09B398`)** — full `## D417` entry at file tail | RESOLVED (src/bondconstants.h `PROP__PORT_SIGNED = -1`, D3/D142 precedent; **Rule-2 review owed**). |
| D417 | **`enum PROP` unsigned on GCC: `prop >= 0` passed for `getPropForHeldItem()`'s -1 (fist) -> `modelLoad(-1)` null deref in any 2P+ stage (player.c `sub_GAME_7F09B398`)** — full `## D417` entry at file tail | RESOLVED (src/bondconstants.h `PROP__PORT_SIGNED = -1`, D3/D142 precedent; Rule-2 review done 2026-10-06: compiler-ABI class, no behaviour change). |
| D418 | **Split-screen: fast3d safe-area crop stretched the last-set (half-height) viewport over the whole window, so each player's view filled the screen and the last-drawn (shuffled) player won -> alternating full-screen flicker** — full `## D418` entry at file tail | RESOLVED (`gfx_set_split_screen`, port-only). |
| D419 | **Split-screen input shared between players / P1 lost solo controls: poll thread used g_CurrentPlayer for per-slot aim/stance/style/use** — full `## D419` entry at file tail | RESOLVED (per-slot `slotPlayer()`, port-only; no current-player swaps from the poll thread; live-tested OK). |
| D420 | **MP score / stats screen shows an odd big number (issue #99)** — `char rankbuffer[4]` overrun ("Rank: 1st") clobbered the adjacent `scores[]` (D8 class); widened under `#ifdef PORT` — full `## D420` entry at file tail | FIXED 2026-10-01 (`mp/virtual-seats`); 2P/3P/4P watch-menu runs show 0s. |
@@ -802,6 +826,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D532 | **The three P12 U5 should-fixes applied — the gate's ini pin now fails closed, the seed is hard-pinned (stray `GE_RSEED` warns), and `audiodebug.ps1`'s dead A/B switches are gone** — golden gate + tooling; D531/U5 follow-up; full `## D532` entry at file tail | FIXED 2026-10-05 (tooling-only: `pin_ini_640x480` fails closed like `pin_eep`; `GOLDEN_SEED` literal + warning, capture `eval` unaffected; `-AB`/`-Old`/`-OldMode` removed with a note) |
| D533 | **The probe-doc drift that would have failed CI, plus three tracked-file accuracy/hygiene fixes (release-sweep spot-check)** — release gate (P12 U2/U6/U7 spot-checks); full `## D533` entry at file tail | FIXED 2026-10-05 (`GE-ENV-PROBES.md` missing the `GE_D526` family → `gen_env_probes.py --check` rc 1 = a CI-failing step; `build-pc.sh` local MSYS2 root in two messages; `docs/fidelity.md` og line; release-notes gate paragraph quoted a pre-D531 spread) |
| D534 | **Pre-release review of the 2026-10-05 local batch — Ctrl-C in the golden gate could leave the user's save replaced, plus doc accuracy fixes** — release gate; full `## D534` entry at file tail | FIXED 2026-10-05 (`verify.sh`/`capture_p7.sh` INT/TERM trap; release-notes linux-gate wording; dead `security-and-fidelity-status.md` links; future-dated `2026-10-07` labels normalised; D526 probe dangling-else; verify.sh usage range) |
| D540 | **Fog rework (Deck playtest): ground fog chunked (D503 snap) and cycled light/dark at Bond's feet (behind-camera vertex fog), sky horizon fade squeezed (perspective shade), fog tied to Draw distance** — full `## D540` entry at file tail | FIXED 2026-10-05 (port: exact per-pixel fog, snap off, noperspective shade, new `Video.FogDistance` default 1.0x = N64 (far clip = min(Fog, Draw) x authored), Draw/Fog distance live; maintainer A/B vs 1964 OK; goldens re-based win/linux/deck); Deck re-check owed |
| D407 | **Tanks cannot be boarded/exited on PC — the v0.4.0 use/reload split (D378/D393) removed the B-button tap the engine's tank handlers in `bondview2.c` consume (user report 2026-09-28, Runway/Streets)** — full `## D407` entry at file tail | PARTIAL (fix landed in v0.4.0, port-only): present `GE_CONT_B` on the use (E / pad A) rising edge only while `g_PlayerIsInTank == 1` or `g_BondCanEnterTank != 0`; E keeps its D378 no-reload-fallback semantics elsewhere, N64 layout unaffected. D407(b) same cycle: front PC Options page-edge highlight clamp + mouse-wheel / W-S paging (wheel queue consumed on the menu, D223); its wheel mapping shipped inverted and was fixed to match W/S (wheel up = step up). D407(c): board-animation lockout, menu-accept B gate in tank states, in-tank aim routed through the legacy velocity stick + `Input.TankAimScale` knob. RESOLVED 2026-09-28: all port-only (src/game zero-diff), 10 TANKDBG probes stripped, release binary verified clean, user live tank drive signed off (board + exit OK, aim feel good). |
| D408 | **`Game.SkipIntro` skips the post-mission failure dossier: with it on, entering a level then aborting (watch Z+A) or dying (KIA) returns straight to the menus — no REPORT / "Mission status: KILLED IN ACTION / ABORTED" screen** (user report 2026-09-28; save/AllUnlocked ruled out) — full `## D408` entry at file tail | OPEN (cosmetic, not root-caused past the handoff site). v0.4.0 decision: SkipIntro stays EXPERIMENTAL / not recommended for regular users — F10 row relabelled "Skip intro (EXPERIMENTAL)". |
| D450 | **Unplugging and replugging a controller was not picked up (user, GameSir-G7 Pro; 2026-09-29).** — full `## D450` entry at file tail | FIXED — port-only: the render thread's `gfx_sdl_handle_events` drains the same SDL queue as video.c's host pump but had no case for `SDL_CONTROLLERDEVICEADDED/REMOVED`, so it usually swallowed them and `inputRescanPads` never ran. Both pumps now post a request; the rescan runs at the top of `inputUpdate`, on the thread that reads the pads. |
@@ -11794,9 +11819,9 @@ D173 (level-start floating), D292 (death sinking), and the Cradle spin (also und
**2026-10-01: diagnostics stripped (ROADMAP §7 scaffolding strip, batch 3, branch `chore/strip-d243-probes`).** D243 is FIXED (M-189/M-190); the investigation scaffolding is removed, behaviour unchanged. Removed: `GE_D243M` and its plumbing (`d243mEnabled`/`d243mProbeActive`/`d243mGetFrameCounter`, `D243M:` CREATE/REMOVE/tick/draw/f488write/firinganim lines in `bondview2.c`; chrdraw census in both `drawjointlist` variants of `objecthandler.c`; animrestart in `chraction.c`; playanim in `chrai.c`; setanim_caller/setanimspeed/setanimplayspeed/tickstart/postadv/biganim and the clamp log lines in `model.c`; prebefore/onscreen in `chr.c`), `GE_D243X2` (prop->pos save/restore) and `GE_D243X4` (render_pos freeze) in `chr.c`, `GE_D243CAM` (3 sites) and `GE_D243SWIRL` in `bondview2.c`, and the `GE_D243` menu-transition trace + `d243MenuName` in `front.c`. (`GE_D243X3` was already gone.) Kept (the shipped fix and its supports): the `sizeof(Model)` buffer-layout fix in `bondview2.c` (M-189); the unconditional `field_488.pos` write + teleport-epoch filter re-seed (M-190: `d243NotifyTeleport`/`d243GetTeleportEpoch`, the call in `chrai.c` `AI_TRYTeleportingChrToPad`); `gameScriptedCameraActive()` and its two users (`model.c` clamps, `port/src/input.c` mouse-look gate); the M-183/M-185 playspeed/endframe clamps in `model.c` (Rule-2 retention sign-off, M-192) minus their log lines. Left alone: `GE_DEBUGMENU` in `src/boss.c` (a debug-menu enabler whose comment cites D243; not a D243 probe).
## D244 — Self-hosted CI runner (`corpyvt6agent01`) link failure: `undefined reference to 'objectiveGetStatus_WEAK'` (2026-09-12, first-ever build attempt on the…
## D244 — Self-hosted CI runner link failure: `undefined reference to 'objectiveGetStatus_WEAK'` (2026-09-12, first-ever build attempt on the…
**Self-hosted CI runner (`corpyvt6agent01`) link failure: `undefined reference to 'objectiveGetStatus_WEAK'` (2026-09-12, first-ever build attempt on the reinstalled box, run `34707798707`).** `src/game/objective_status.c:162` uses `#pragma weak objectiveGetStatus_WEAK = get_status_of_objective` (a decomp ground-truth alias, referenced 2-arg from `chrai.c:2538` — comment calls it "horrible hack to get ai matching"). The box's `g++`/`ld` reported as `x86_64-w64-mingw32/16.1.0`; a minimal local repro of the identical `#pragma weak` cross-TU function alias **links cleanly** on this session's own machine (`mingw-w64-x86_64-gcc 16.2.0-3`, `binutils 2.47-3`). Not a game-logic bug (the alias is unmodified decomp code, ground truth) and not a CMakeLists/link-flag issue (no `--gc-sections`/LTO in play) — the leading, unverified explanation is the self-hosted box's MSYS2 packages predate a PE/COFF weak-external fix or behavior change versus current MSYS2, consistent with rollout step 2's earlier finding that the box's mingw64 package set was stale/incomplete at initial setup.
**Self-hosted CI runner link failure: `undefined reference to 'objectiveGetStatus_WEAK'` (2026-09-12, first-ever build attempt on the reinstalled box, run `34707798707`).** `src/game/objective_status.c:162` uses `#pragma weak objectiveGetStatus_WEAK = get_status_of_objective` (a decomp ground-truth alias, referenced 2-arg from `chrai.c:2538` — comment calls it "horrible hack to get ai matching"). The box's `g++`/`ld` reported as `x86_64-w64-mingw32/16.1.0`; a minimal local repro of the identical `#pragma weak` cross-TU function alias **links cleanly** on this session's own machine (`mingw-w64-x86_64-gcc 16.2.0-3`, `binutils 2.47-3`). Not a game-logic bug (the alias is unmodified decomp code, ground truth) and not a CMakeLists/link-flag issue (no `--gc-sections`/LTO in play) — the leading, unverified explanation is the self-hosted box's MSYS2 packages predate a PE/COFF weak-external fix or behavior change versus current MSYS2, consistent with rollout step 2's earlier finding that the box's mingw64 package set was stale/incomplete at initial setup.
**Status (from §F table):** **OPEN, not yet fixed.** Next: on the box, `pacman -Syu` (or at minimum `pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-binutils`) to bring the toolchain current, then re-dispatch `selfhosted.yml`. If updating doesn't clear it, the fallback is a narrow `#ifdef PORT` replacement of the alias with an explicit thin wrapper function in `objective_status.c` (ABI-preserving, no logic change) — but try the toolchain update first since the local repro suggests this is a version/environment gap, not a fundamental PE/COFF limitation.
@@ -12744,7 +12769,7 @@ This confirms the M-160/M-164/M-165 hypothesis (`## D173`'s own consolidated-nex
1. *Change:* `src/game/chrai.c`, `chraiitemsize()`, `case AI_PRINT:` — replace the NUL-scan with `return 1;` (the record's true on-disk size), under `#ifdef PORT`; the `#else` arm is byte-identical to the original decomp.
2. *Proof of divergence:* mechanism verified live (gdb symbol + per-tick probe lines above, exact offsets/sizes/landing bytes). Intended-behavior references: (a) the list source itself — PRINT sits between two real commands and is a no-op by construction; (b) user's direct N64 experience — guards do not stand frozen in Caverns on real hardware (same standard as M-144's user comparison that settled D243). Honest caveat recorded at request time: the ROM *code* would mis-skip on N64 too if reached — this is a latent original defect, and the fix restores the list author's intent rather than provable N64 behavior.
3. *Why not `port/`:* no N64 hardware dependency to shim — the sizer function's own logic operating on game data is the defect; nothing upstream (ABI/layout §A1 suspects, port-layer) is involved. *Why not the ABI exception:* nothing pointer-width-driven; the 32-bit original has identical code and data.
4. *Same-engine precedent:* Perfect Dark removed `PRINT` from the AI language entirely and **reassigned slot 0xAD** to a real command (`aiChrCopyProperties`, `pd_port/src/game/chraicommands.c:8159`) — Rare themselves retired this broken record class in the next title on the same engine.
4. *Same-engine precedent (CORRECTED 2026-10-05, 3g review):* the original text here claimed Perfect Dark removed `PRINT` and reassigned slot 0xAD to `aiChrCopyProperties`; that was wrong (`aiChrCopyProperties` is PD `@cmd 0173`, and PD's 16-bit opcodes are numbered independently of GE's). PD **kept** PRINT (`CMD_PRINT 0x00b5`) with the same NUL-scan sizer (`chraiGetCommandLength`, `pd_port/src/game/chrai.c:796`), and its global lists carry the strings inline (`pd_port/src/game/gailists.c`, e.g. `dprint 'd','y','e','n','g',0` = GE's commented-out `PRINT("dyeng")`). So Rare fixed the *data* in the next title, not the sizer. The v2 split still follows the real encoding: `chraidata.c` is in the byte-matching N64 link (`ld/game.data.ld.inc`), so the cartridge's global lists genuinely hold 1-byte PRINTs (the `PRINT(STRING)` macro drops its argument), while all 398 level-list PRINTs in the US setups carry cmd + string + NUL (static scan through the real `chraiitemsize`, 21 setups, 0 mismatches). An equivalent data-side alternative (PD style: emit the two strings in `chraidata.c` under `#ifdef PORT` via `debug_log` chars, drop `d310ItemSize` and the per-label-jump list lookup) is noted as a possible post-release cleanup, not needed for correctness.
**Grant (2026-09-18, in-thread):** "I want to proceed with your implementation plan here to fix this bug." (User's reply to the full §7 write-up; asked only whether a fresh session was needed — answered no, granted in this session.)
@@ -12785,6 +12810,8 @@ This confirms the M-160/M-164/M-165 hypothesis (`## D173`'s own consolidated-nex
**2026-09-25 playtest (user, Windows, one ~46-min session Silo → Caverns, `GE_D322=1 GE_D204=1`, build `7baf4900`):** Caverns combat ~2 min, plus Silo onwards: zero `D311: … ANOMALY` lines (the probe logs via `osSyncPrintf` into the same log, and D318W lines from the same path prove it is live). Combined with D336 (upstream poison source) this closes D311. Guard stays (Rule-2 sign-off 2026-09-19).
**2026-10-06 (3f behaviour audit, maintainer asked to revisit "AI hacks"):** keep as-is. The guard acts only on frame values that are non-finite or beyond +/-1e6, which no legitimate animation reaches, so it cannot alter a real animation; it stops a hang that already-corrupt state would cause. The corruption source is fixed (D336, ABI), so on a healthy run it never fires; cost is two compares per anim tick. Not an AI/behaviour workaround.
## D312 — Throwable items (mines, grenades, etc.) can clip through walls in certain spots (user report, v0.3.0 pre-release playtest, 2026-09-18) — FIXED: `bgBuildRoomVtxBounds` mis-reads the G_VTX vertex count on PC (Gfx_le `dma.par` bit-layout shift), collapsing whole batches of wall geometry out of the collision query (diagnosed + fixed, 2026-09-18).
**Reported:** user's last-minute v0.3.0 playtest: thrown/placed throwable items — mines, grenades, "etc." — can pass through wall geometry in certain spots rather than colliding with it. Spot-dependent ("certain spots"), not universal.
@@ -15859,6 +15886,8 @@ flickering fixture), then repro on PC.
**Fix:** `PROP__PORT_SIGNED = -1` sentinel under `#ifdef PORT` at the head of `enum PROP` (`src/bondconstants.h`), following the `SP_LEVEL__PORT_SIGNED` precedent. `PROP_ALARM1` stays 0, sizeof unchanged, no stored value changes. Also corrects `drop_inventory` (`propid >= 0`). **This is a `src/game`-header edit: Rule-2 review owed.**
**Rule-2 review (2026-10-06, 3f behaviour audit):** not a behavioural change. IDO gives `enum PROP` a signed int type, so the decomp's `prop >= 0` test rejected -1 on the N64; GCC's unsigned choice for an all-non-negative enum is compiler-ABI drift (porting-notes §D3, D142, `SP_LEVEL__PORT_SIGNED` precedent). The unused sentinel restores IDO's signedness: no value, size or logic change, N64 semantics exact. Closed as ABI class; no sign-off needed.
**Status:** RESOLVED.
## D418 — Split-screen alternating full-screen views: safe-area crop remapped sub-rect viewports (issue #99)
@@ -17376,3 +17405,803 @@ in as calendar dates; every commit is dated 2026-10-05) normalised to
`if`, so with no `GE_D526BOX` the off-screen filter never ran) — braced;
`verify.sh`'s usage print (`sed -n '2,32p'`) cut the `--json`/`--against`
lines — now `2,34p`; an unbalanced bold in the ROADMAP Golden-baselines row.
## D540: Fog rework from the Deck playtest — ground fog "chunked" and cycled light/dark around Bond, sky horizon fade squeezed, fog tied to Draw distance; new Fog distance slider, Draw/Fog distance apply live (2026-10-05)
**Reports (v0.5.0-rc, maintainer, Deck + Windows A/B against 1964/GEPD on
Surface 2):** ground fog "blocky — updates one piece of the ground at a time /
chunks away" as Bond walks; the ground around Bond's feet cycled bright/dark
as he moved or turned; the sky's clouds stayed bright down to the horizon
where 1964 fades them into fog; no way to set fog independently ("fog, draw
and LOD distance all seem independent"); Draw distance only applied on the
next level load.
**Causes (port renderer, `port/fast3d/`):**
1. *Chunking* — the D503 full-fog snap (fog >= 247/255 -> full). Its hard 97%
edge followed the per-vertex fog's triangle-shaped iso-lines and swept
across the ground tile by tile. Maintainer A/B with the snap disabled:
smooth. (The rc1 per-vertex form of the snap was worse.)
2. *Light/dark cycling at Bond's feet* — per-vertex fog for triangle corners
behind the camera. The old code forced `winv = +32767` for `w < 0`
(sign flip -> no-fog/full-fog jumps); even with the RSP's honest
negative-w divide (full fog), that corner's value is blended into the
visible ground after GPU near-clipping, so the area at the player's feet
changed with whichever corners sat behind the camera.
3. *Sky horizon fade* — the RDP interpolates shade colour affinely in screen
space; the GPU default is perspective-correct. On a sky triangle running
from overhead to the horizon that kept the bright cloud colour almost to
the horizon and squeezed the authored fade into a thin band.
4. *Fog vs Draw distance* — D218 scaled the far clip and kept the authored
per-mille fog positions, so fog moved out with Draw distance (2.5x
default): fogged levels were not N64 at the defaults. Both were computed
only at level load (`fogLoadCurrentEnvironment`).
**Fixes:**
- Fog is exact per pixel: the vertex sends `z*fog_mul + w*fog_offset`
(fog x w, linear in clip space, clips correctly) and the fragment shader
divides by `gl_FragCoord.w`. Same fog function, colour and positions as the
RSP; corners behind the camera no longer contribute. Differs from the N64
only in the in-between shading of large triangles (the N64 is per-vertex).
- D503 snap off by default (`GE_FOGSNAP=1` restores it).
- Shade colour (`vInputN`) is `noperspective` (desktop GLSL >= 1.30; GLSL ES
keeps perspective) — RDP-style screen-affine Gouraud; textures stay
perspective-correct.
- `Video.FogDistance` (new, 50..800 %, default 100 = N64; F10 Graphics
"Fog distance", both display presets 100): `bgfog.c` (`#ifdef PORT`, the
D218 site) now scales the level's far clip/fog by
`min(FogDistance, DrawDistance)` instead of DrawDistance alone, and keeps
the game's own per-mille fog positions and intensity math unchanged (the
D218 mechanism, driven by fog). At Fog 1.0x that is the authored far: exact
N64 fog at any Draw distance (Cradle: Draw 2.5x vs 1.0x 0.05% px, the
distant truss a ghost as on 1964). Draw distance still scales prop draw
distance (`propobj.c`), the room pool and the D466 split within that clip.
**Rejected first cut (same evening):** keep the far clip at Draw x and
convert the per-mille fog positions to distances and back. Two defects:
(1) the game reads the two fractions as LINEAR distance for culling but as
DEPTH-BUFFER per-mille for the prop/chr fade (`sp20`/`sp1C` ->
`fogGetPropDistColor`), and feeding both the linear rescale faded props and
characters out (Train attract: no crates, no Bond); (2) with the clip far
out, the fog sits within a few per-mille of 1000, so the s32 rounding
pulled the fog visibly in (Cradle truss fogged out), and the NDC conversion
assumed `viSetZRange`'s first argument (`BlendMultiplier`) is the
projection near plane -- Fog 2.0x fogged the walkway at Bond's feet. Lesson:
extend the clip only as far as the fog needs and leave the game's fog math
alone.
- Live apply: `fogSetRenderFogColor` re-runs `fogLoadCurrentEnvironment` on
the game thread when either multiplier changed (pointer reset per level).
- A/B switches (registered): `GE_FOGVERTEX=1` (per-vertex RSP fog),
`GE_SHADEPERSP=1` (perspective shade), `GE_FOGSNAP=1`.
**Verified:** maintainer side-by-side vs 1964/GEPD on Surface 2 at Bond's
start and while moving: ground fog smooth, no cycling at Bond's feet, horizon
fade matches; "everything else looks solid". Maintainer 1964 stills of the
Frigate and Cradle starts: Frigate's sea is dark navy (the new build; the old
goldens' teal was the 2.5x fog), Cradle's distant truss is a faint ghost
(matched by the final clip design above). Runway/Surface 1/Dam/Jungle
headless captures inspected during the per-pixel step. Goldens re-based on
all three platforms the same evening (win 21/21 confirming pass; linux on the
X220; first `deck` set, see tools_pc/golden/README.md). **Owed:** maintainer
re-check on the Deck (fog walk on Surface 2; Fog/Draw sliders live). A remaining "slight scale or rotation" in
the sky clouds vs 1964 was attributed by the maintainer to 1964's non-4:3
window aspect; re-check only against a correct-aspect reference (simple64 /
ParaLLEl-RDP) before opening a row.
## D541: one A press advanced two menu screens on the Steam Deck (Desktop Mode) — two OR'd input sources for one press (Deck playtest, 2026-10-05)
**Report:** v0.5.0-rc on the Deck, launched in Desktop Mode — A "clicks forward
too fast" on file select, level select and the F10 / PC Options overlay
(maintainer: a double trigger from single quick presses).
**Cause: UNCONFIRMED.** The maintainer had switched Steam to controller
(gamepad) mode in Desktop Mode, which should disable the desktop key mapping
below, so that mapping is only one candidate; the other is a Steam virtual-pad
re-enumeration (`inputOpenPads` reseat) mid-press (not read). Tracer
(per-source A edges + pad add/remove) deferred by the maintainer. Candidate 1: outside a
Steam-launched game, Steam applies its Desktop Configuration to the Deck's
controls (A types Enter, B types Escape) while SDL also reads the raw pad. The
menu accept is an OR of pad A/X, Enter and the left mouse button
(`input.c` front-end mapping; overlay `accept`), so the two sources of one
press, a few ms out of step, gave a 1→0→1 level = two edges. In the overlay
Escape (key event → `s_backPending`) and the pad-B edge are separate back
paths, so one B could back out two pages. Ruled out (Sonnet triage, high
confidence): the `joyPoll` (scheduler thread) / `joyConsumeSamples` (game
thread) ring in `src/joy.c` — a clean edge lands in exactly one consume
window; the full-ring overwrite path can only drop a press, not repeat one.
**Fix (port-only, menus only):** a ~70 ms release hold-off on menu A/B/Start
in `inputComputePad` (applied before the GE_INPUTSCRIPT harness) and on the
overlay's `accept`; one back action per 150 ms across Esc and pad B. Press
edges are untouched, gameplay is unaffected. Covers candidate 1 and any
multi-source press; may not cover a reseat. **Owed:** Deck re-test in Desktop
Mode (and Game Mode) — one press = one screen; B backs out one page.
## D542: stray polygons drawn to the left of Bond's gun, frequent on foggy levels (Streets, Surface 2) — OPEN (Deck/Windows playtest of the D540 build, 2026-10-05)
**Report:** with the D540 fog build, polygons "randomly draw to the left of
Bond's gun", very often, especially on foggy levels (Streets, Surface 2).
**Not yet triaged.** Candidates: (a) a D540 regression in near-camera geometry
(the viewmodel and nearby room triangles: `noperspective` shade, or the
per-pixel fog's `fog * w` path); (b) the pre-existing Streets "1-frame polygon
explosion near the body armour / grenade launcher" from the same playtest (its
1964 check is still owed), now seen more often. **First step (cheap A/B, no
code):** same spot with `GE_SHADEPERSP=1`, then `GE_FOGVERTEX=1`, then the rc1
binary (Deck: `rc1-backup/ge007.x86_64`); whichever makes it vanish names the
layer. A 1964 look at the same spot settles faithful vs port.
## D543: Surface 2 — the lit area that follows Bond looks brighter than on 1964 — OPEN (2026-10-05)
**Report:** after D540, the ground around Bond on Surface 2 reads as a bright
light following him; 1964/GEPD shows it dimmer. Maintainer wants the
accurate result, not a tuned one.
**Leading hypothesis:** D540's exact per-pixel fog leaves the near ground
unfogged, while the N64 (and 1964) shade per vertex and the RSP near-clips in
clip space, so the near-plane edge of a big ground triangle inherits fog lerped
from a corner behind the camera — a haze at the player's feet. D540 dropped
per-vertex fog on the GPU because GPU clipping of noperspective/RSP values
made that area cycle light/dark. **Accurate fix to evaluate:** RSP-style
near-plane clipping in fast3d (clip fogged triangles against the near plane on
the CPU, lerping per-vertex fog in clip space like the RSP), then per-vertex
fog screen-affine — N64 behaviour without the cycling; compare against 1964
at Bond's start on Surface 2. Second hypothesis to rule out: GE's own near-fog
record (`Visibility.Nfd` / `g_NearFogValuesP`, bgfog.c). `GE_FOGVERTEX=1`
shows the per-vertex look today (with the cycling).
**Implementation (2026-10-06):** CPU near-plane clip added in `gfx_sp_tri1` (`port/fast3d/gfx_pc.cpp`, D543 comment block before the emit loop): after the reject/cull gates, triangles are Sutherland-Hodgman clipped against z+w>eps in clip space (3 or 4 verts, fanned to 1-2 triangles), new vertices lerp x/y/z/w/u/v/colour/fog_n linearly (stack storage, not `rsp.loaded_vertices`); fully-behind triangles are dropped. Skipped for rects, `G_NO_CLIPPING_EXT`, `GE_NEARCLIP=0`. `GE_NEARCLIPFOG=recompute` re-derives the per-vertex fog byte from the new z/w instead of lerping it. Not built or run by the implementer; maintainer A/B owed on Surface 2 at Bond's start vs 1964 (default / `GE_FOGVERTEX=1` / `GE_NEARCLIPFOG=recompute` / `GE_NEARCLIP=0`). Confidence it compiles: high (read-checked); correct: medium until A/B.
**Decision (2026-10-06, lead, from code + maintainer feedback; no further
A/B by maintainer choice):** per-vertex RSP fog is the default again, on top
of the CPU near clip, with the clip-created vertex fog **lerped** (not
recomputed). Reasoning: the maintainer's report is "1964 is dimmer around
Bond"; the D540 per-pixel fog is zero at the feet (bright), and the recompute
rule gives zero at the near plane too (bright), while the RSP model (per-vertex
fog, behind-camera corner = full fog via the honest negative-w divide, lerped
into the near-plane vertex) leaves a haze at the feet -- the dimmer look. The
D540 reason for dropping per-vertex fog (light/dark cycling) was GPU
near-clipping of behind-camera corners, which the CPU clip removes. The fog
varying now shares the shade qualifier (`noperspective`): the RDP interpolates
fog screen-affine like shade. Switches: `GE_FOGPIXEL=1` = D540 per-pixel fog
(replaces `GE_FOGVERTEX`, tombstoned), `GE_NEARCLIP=0`, `GE_NEARCLIPFOG=recompute`.
Build-verified; by-eye check rides on the golden re-base contact sheets and
the next playtest (Surface 2 at the start vs 1964). Fogged-level goldens
change (re-base batched with D546).
## D544: OS mouse cursor flickers with the F10 overlay open on the main menu — FIXED 2026-10-05 (maintainer report, same day)
**Report:** "sometimes the mouse cursor flickers when the F10 menu is open on
the main menu".
**Cause (code read, not yet run):** two requests race every overlay tick.
`inputSuspendForOverlay()` (`port/src/input.c` ~3155, called from
`overlayHandleInputLocked`, `optionsoverlay.c` ~2300) always calls
`mouseRequestCursor(1)` (show the OS cursor); on the front end the D519 path
(`input.c` ~2174: `overlayFrontEndCrosshair(); mouseRequestCursor(0);`)
hides it because the game crosshair is the pointer there. Whichever request is
applied last on the event thread (`inputApplyMouseRequests`) wins, so the
cursor toggles. In a level only the show request runs (no flicker).
**Proposed fix (port-only, ~3 lines):** in `inputSuspendForOverlay`, skip
`mouseRequestCursor(1)` when the D519 front-end pointer is active
(`mouseEnabled && current_menu != GE_MENU_RUN_STAGE && current_menu !=
GE_MENU_INVALID`), so only one owner sets the cursor per context. Verify:
F10 on the main menu with a mouse -- one pointer (the crosshair), no flicker;
F10 in a level -- OS cursor visible as before.
**Fix (2026-10-05/06, revised after the maintainer check):** the first cut
(skip the show request while the D519 pointer is active) stopped the flicker,
but the maintainer wants the pointer drawn OVER the overlay for mouse users.
Final: `inputSuspendForOverlay` always shows the OS cursor, and the D519
front-end path no longer hides it (the game crosshair keeps tracking the
mouse underneath the box); one owner, so no race. Same pass, F10 mouse QoL
(`optionsoverlay.c`): right-click = Esc/back (closes a dropdown, then one
page, then the overlay; replaces the old right-click "cycle value back";
blocked until release after a binding capture so a bound right-click does
not also back out), and the tip line follows the hovered row while the mouse
is in use. Maintainer check owed. Mouse scrollbar: backlog (post-release QoL).
## D545: player rockets can pass through the ground when fired near Bond's feet — FIXED 2026-10-05 (maintainer report; 3e triage)
**Report:** with the (dual) rocket launcher on Aztec and most maps, aiming down
near Bond's feet while running sometimes sends the rocket through the ground
or a solid object.
**Cause (static, high confidence it is a real defect):**
`gunInitProjectileFromPlayer` (`src/game/gun.c`) declares `s32 sp54;` and
passes `&sp54` to `bgFindRoomsAlongSegment(..., &sp54, &sp50, 0x14)`, which
writes up to 0x14 traversed room numbers there. On the N64 the 19 extra slots
landed in the decomp's `u8 pad_a[0x4c]` (exactly 19 more `s32`); GCC lays the
frame out differently, so on PC the writes can hit live locals (`pos`, `tile`,
`usedfallback`) -- a spawn position/tile corrupted to just under the floor
gives a rocket that never meets the ground. Only the player launch path does
this; NPC rockets and the per-tick flight (`handles_projectile_motion`, full
`roomNums[]` buffers) are clean. Same class as D490 (porting-notes §D24).
**Fix:** `s32 sp54[0x14]` under `#ifdef AVOID_UB` (array passed directly);
the `#else` arm keeps the original. No reads change. Build-verified.
**Verify (maintainer):** Aztec, dual rocket launchers, aim at Bond's feet
while running; if it still happens, the same shot on 1964/GEPD decides
faithful vs port.
**Residual suspects from the triage (not proven to fire):** (a) the
`bgBuildRoomVtxBounds` safety-net `continue` (bg.c) leaves a skipped batch
with impossible bounds = a silent collision hole (worth a counter if the
report persists); (b) projectile room lists only grow via portal crossings,
so a stale `unkCC` skips world tests (faithful logic). Object/chr collision
(`projectileTestObjectCollisionRecursive`) was not traced.
**Maintainer re-test (2026-10-06):** near Bond mostly fixed; some rockets
(akimbo) still pass through **walls and floor** (not doors), e.g. Archives.
Second static triage: no ABI/UB defect in the object/door path (door-only
suspect `sub_GAME_7F041400` `bestedge > 0` set aside, since it isn't doors). Leading
hypothesis, port-caused: the room pool starves (D294 documents mema starving
on PC when Draw/LOD/FOV widen the resident room set; D294's pool bump only
partly covers 2.0x), and `bgBuildRoomVtxBounds`'s `memaAlloc` failing leaves
`vtx_batch_bounds` NULL, so `bgTestBulletHitBackground` returns no hit for
EVERY wall/floor of a room that still renders. Test (no diagnostics, by
maintainer choice): same Archives shots under Modern vs the Original N64
preset (level restarted). If confirmed, the proposed fix allocates the bounds table
from port memory instead of the room pool (a few KB per room, freed with the
room; no game-logic change). Status stays PARTIAL until that test.
**Preset A/B result (maintainer, 2026-10-06, Archives, akimbo rockets):** fine
under Modern, Original N64 and custom high FOV; one rocket dropped in the whole
session (roughly 1 in 60), most likely under Modern or high FOV. D545 fixed the
main case. The single residual is consistent with the wide-view room-pool
hypothesis but not proven; the port-memory bounds table stays a proposed
hardening (backlog), not a release item.
## D546: PC settings standardization -- round defaults, vertical FOV, view-area wording — FIXED 2026-10-05 (maintainer-approved table)
**Ask:** N64 value = 1.0x (Original N64 preset); PC defaults on round steps
that every slider lands on; wording that reads as a setting, not a warning.
**Changes (port-only):**
- `Video.DrawDistance` / `Video.LodDistance` default 250 -> **200** (2.0x):
initializers, `kVideoPresets` port column, reset table; D482 low-end lowering
now keys on 200. Fog stays 1.0x.
- Stick deadzones 7000 (23 %, off the 300 step) -> **7500 (25 %)**
(`STICK_DEADZONE`, reset table).
- Mouse horizontal/vertical sensitivity UI max 3x -> **4x**; controller look
stays 2x (PD port: stick scale 0..2.0x; PD mouse 0..30 at default 2.5).
- **FOV:** shown as **vertical degrees, 30..90 in 5° steps, 60 = N64** in the
middle (PD's "Vert FOV" model: vertical degrees, default 60, range 15..170).
Horizontal degrees (D443/D357) moved with the window aspect (N64 = 75° at
4:3, 91.5° at 16:9) and never landed round. Storage stays
`Video.FovScale` % but is now a float so each step (100/12 %) is exact;
old integer inis load unchanged and snap to the next mark on the first step.
- `Game.AIWideView` (D468) row relabelled **"Gameplay view area"**, values
**Original / Extended**, tip "Screen area game logic counts as in view"
(the old "Guard AI uses full wide view" read as a vision cheat; the setting
picks which screen area on-screen gameplay tests use). Ini key unchanged.
- **Migration:** one-time, untouched values only (`Video.DefaultsRev`,
`Input.DefaultsRev`, D482 "considered" pattern): draw/LOD 250 -> 200,
deadzones 7000 -> 7500. Runs before D482.
**Goldens:** captures use the defaults, so draw/LOD 2.0x changes frames; the
re-base is batched with D543 (one round after the fog design is final).
## D547: Game.AllUnlocked applies live — FIXED 2026-10-05 (maintainer ask)
The file2.c query-time hooks (D442) already read `portAllUnlocked` live; only
the two RAM flags `debug_enable_all_levels_flag` / `debug_007_unlock_flag`
were boot-seeded. `portAllUnlockedApply()` (`port/src/main.c`) now runs at
boot and from `rowSetCommit` (F10 change and section reset): On sets each flag
that is clear and remembers it; Off clears only flags it set, so GE_UNLOCK_ALL
/ debug-menu unlocks survive. Row values are plain Off/On ("(restart)"
dropped). LEFTOVERDEBUG builds only, as before. Maintainer check owed: on at
file select -> mission select shows all levels + 007; off -> back to the save's
progress (screens refresh on re-entry).
## D548: walls near rocket fire banded black/white/yellow/blue in dark areas (Aztec; also seen on the Deck) — FIXED 2026-10-06 (maintainer screenshots, A/B)
**Report:** in Aztec's dark corridor, walls beside Bond near rocket fire and
explosions turned into posterised white/yellow/blue/black bands following the
texture detail. `GE_SHADEPERSP=1` (perspective shade) did not reproduce it.
**Cause:** D540 made every combiner input varying (`vInputN`) `noperspective`.
When the GPU clips a big near-camera triangle, those screen-affine values can
be extrapolated past 1.0; the GLideN64-style CC wrap emulation
(`WRAP(texel, -1.01, 1.01)` / `(-0.51, 1.51)`, gfx_opengl.cpp) then wrapped the
out-of-range result, per channel -> the bands.
**Fix:** the fragment shader clamps each combiner input to 0..1 into a local
(`cInputN`) before the combine. N64 shade/prim/env are 8-bit, so this only
removes values the hardware cannot produce. Root fix: D543's CPU near-plane
clip (no GPU near-clipping of affine attributes). Maintainer Aztec re-check owed.
## D549: thrown grenade / knife / object -- `s32 sp94` receives a 12-byte coord3d — FIXED 2026-10-06 (D545 follow-up triage)
`generate_player_thrown_grenade`, `generate_player_thrown_knife` and
`generate_player_thrown_object` (`src/game/gun.c`) declare `s32 sp94` and pass
`&sp94` to `bullet_path_from_screen_center(coord3d *, ...)`, which writes x/y/z.
On the N64 (sp94 at 0x94) the extra 8 bytes ended at `spA0_a` on a slot that is
dead at that point (the mine path's `if (wor == NULL)` after the call proves
`wor` was not hit there); with GCC's layout they can hit live locals. Fix:
`struct coord3d sp94` under `#ifdef AVOID_UB`, original under `#else`; nothing
else reads sp94. Same class as D545 / D490 (porting-notes §D24). Possibly
related to older "thrown items through walls" reports; not demonstrated.
## D550: static title bar (no per-second "NN fps" refresh) + project mark as favicon / window / taskbar icon — FIXED 2026-10-06 (v0.5.0 finalize, maintainer request 2026-10-06)
Two small surface items, both port/tooling-only (zero `src/game` diff, no
goldens):
**1. Static title bar.** `videoPumpEvents` (`port/src/video.c`) refreshed the
window title to `"GoldenEye 007 - NN fps"` about once a second (the
`wmAPI->set_window_title` block). Removed; the title is now set once at
window creation. The live-FPS readout is still available on the F10 overlay.
**2. Project mark in place of the game-still favicon.** The favicon / link
card / window icon used in-engine stills (shot-28.jpg et al.) — derivative
game content in every browser tab and the taskbar. Replaced with an ORIGINAL
generated mark: a generic version of the game's *default aim crosshair*
(circle + N/S/E/W lines crossing its edge), GE red `#c62828` on near-black
`#050607` with a faint glow — a generic functional shape, no logo art,
wordmark or screenshot (tree-hygiene rule 4). One generator,
`tools_pc/make_icon.py` (Pillow, deterministic; `--check` for CI staleness),
emits:
- `docs/img/icon/ge007-icon.png` (512, site favicon + README icon),
- `cmake/icon/ge007.ico` (16–256 multi-size, PNG-compressed entries; 16/24/32
use a chunkier stroke variant so the mark survives tiny sizes),
- `port/fast3d/ge007_icon.h` (32×32 RGBA, plain R,G,B,A per pixel).
Wiring: `cmake/icon/ge007.rc` (static; the exe icon) via `target_sources` in
the CMakeLists WIN32 branch; `gfx_sdl2.cpp` builds an `SDL_Surface` against
explicit `0x00ff00ff`-style masks (portable, no endianness assumption) and
`SDL_SetWindowIcon`s it after the GL context; `docs/_layouts/default.html`
favicon + apple-touch-icon; `README.md` gets the mark above the badges. The
jekyll-seo `og:image` link-preview card keeps `shot-28.jpg` (a still is fine
there — it is a published page asset, not a tab/favicon) and
`scratch/build_preview.mjs` was restored to the original image constant.
**Implementation bugs found only at build verification (both mine):**
(a) the first `make_icon.py` emitted the C array with *flat* cells per row
(no per-row braces): all 1024 `{r,g,b,a}` cells then sit at the TOP level of
the `[32][32][4]` initializer — 1024 > first dim 32 → "too many
initializers". Both gcc AND clang reject it, correctly: it is genuinely
ill-formed C, not a toolchain bug (braced/nested forms of the same data
compile cleanly in both). (b) `gfx_sdl2.cpp` packed the channels unshifted
(`p[1] & 0x0000ff00` is always 0); masked only by (a) never having compiled.
**Verification-trap note:** in the mixed msys/native agent shell, `g++ ...
| head` reports the PIPE's rc (always 0), so earlier "compiles fine" claims
were false while the TU actually failed. Verified properly: `ninja -t
commands` exact command via cmd.exe with a sane `PATH`/`TMP` (rc 0, only the
pre-existing `gfx_pre_swap_hook` warning) + full `./build-pc.sh ntsc-final`
(249/249 link). Window/taskbar icon + static title owed a maintainer live
check.
**Mark re-proportioned 2026-10-06 (maintainer: "right direction, not accurate
to the in-game crosshair").** Measured off an in-game capture of the default
reticle: the four bars do NOT meet in the centre (open gap ~0.24x the ring's
outer radius) and reach ~1.43x the outer radius (the first cut crossed the
centre and reached ~1.27x). `make_icon.py` now draws eight bar segments from
the gap to the reach (512 master: ring r 150, reach 214, gap 36, stroke 24;
small variant: r 140, reach 200, gap 46, stroke 40 so the centre stays open at
16 px). All three outputs regenerated (deterministic re-run identical). The
`.github/social-preview.html` card's CSS reticle (same old proportions) was
replaced with an inline SVG at the new proportions and the PNG re-rendered.
Still a generic geometric reticle (circle + four bars), no game art copied.
Full `./build-pc.sh ntsc-final` rc 0 with the regenerated header.
**Re-drawn 2026-10-06 (gold).** The colour is now site gold `#e8a13a` (was
GE red `#c62828`), glow included; the proportions were re-measured from the
game's own default crosshair sprite (thin ring about 0.11x the ring radius,
bars about 0.09x the radius, reach about 1.45x the radius, open centre gap
about 0.30x the radius). There is a chunkier small-size variant (16/24/32 px
.ico slots + the 32 px window icon). The `.github/social-preview.html`
card's reticle stays red on purpose — the gold disappears on its manila
background. **Centring fix (same day):** shapes are now drawn symmetric about the exact centre (the line between the two middle supersample pixels; even bar widths, inclusive boxes `[c-r, c+r-1]`), which removes a half-pixel right/down shift at 48/24/16 px; `make_icon.py` (and `--check`) now fail if any emitted size is not pixel-exact left-right and top-bottom symmetric.
## D551: opt-in update check (`Game.CheckUpdates`, default off) -- ADDED 2026-10-06 (v0.5.0 finalize)
**What.** `port/src/updatecheck.c` (+ `updatecheck.h`). `updateCheckStart()` is
the only network entry point; its first line is `if (!cfgCheckUpdates) return;`,
so with the ini value 0 there is no thread, no DNS and no library load. It is
called once from `main.c` right after `configLoad()`. When on: one detached
thread makes a single HTTPS GET to `api.github.com/repos/<repo>/releases/latest`
(5 s timeouts, fixed User-Agent, nothing else sent), parses `tag_name`, and
compares it with the new `GE007_VERSION` (CMake `set(GE007_VERSION "0.5.0")`,
bump on every release; `versioninfo.h.in`). Failure is silent (one log line).
`/releases/latest` excludes drafts and pre-releases on purpose (maintainer
decision).
**Windows.** No link-time winhttp dependency: `winhttp.dll` is `LoadLibraryW`'d
inside the opt-in path, nine functions resolved with `GetProcAddress`,
`FreeLibrary` at the end, so the exe's import table never lists it. Linux runs
`curl` via `popen` (URL single-quoted); no curl = no result.
**UI.** Game page toggle "Check for updates" (+ reset-table entry, tip text) and
an action row "Update available: open page" shown only once a newer tag is
known (`SDL_OpenURL` on the releases page). CI: the tag-only `release` job now
fails if the tag differs from `v` + `GE007_VERSION`.
**Status.** Port layer only, no game logic touched. Build-verified; exe
imports no winhttp (objdump). **Live-verified 2026-10-06 (Windows, isolated
work dir, orderly GE_QUITFRAME quit):** Off -> no update-check activity;
On -> real HTTPS GET, `update check: up to date (latest v0.4.0, running
v0.5.0)`; On with a temporary GE007_VERSION 0.3.0 build -> `v0.4.0 is
available (running v0.3.0)`. **Linux + Steam Deck (2026-10-06, system
curl):** off -> no activity, on -> "up to date". **UI:** scripted F10 run
(GE_OVNAV) on the 0.3.0 build: the "Check for updates" toggle and the
"Update available: open page" row render at the bottom of the Game page
within the column widths, and the row appears live while the page is open
(visibility is rebuilt every emit). **Security review (2026-10-06):**
SHIP-WITH-FIXES, fixes applied: winhttp.dll loaded with
`LOAD_LIBRARY_SEARCH_SYSTEM32` (no DLL planting from the exe folder);
curl run as `curl -q --proto =https` (ignores ~/.curlrc, HTTPS only);
tag restricted to `[0-9A-Za-z._-]` before it reaches the log;
`snprintf` instead of a 32-byte `memcpy`; help text says the change
applies from the next launch; security docs name the curl process, the
System32 load, and that GitHub sees the request and the IP. Confirmed
fine: gate ordering (configLoad before updateCheckStart), constant URLs
(no injection), TLS validation left at system defaults, bounded parsing,
SDL_OpenURL with a constant URL, CI tag check uses the env var quoted.
Owed: nothing beyond pressing the row once in a live session (opens the
browser).
## D552: Dam ending cutscene — first camera shot no longer swivelled up onto Bond (D243 M-190 re-seed removed) — FIXED 2026-10-06
**Report (maintainer playtest 2026-10-06, vs 1964/GEPD):** in the Dam ending
(the run to the dam edge / bungee), the first camera shot should swivel up
onto Bond, ending pointed at his front right; on the port it started
already aimed.
**Cause.** A port-only addition from D243 (M-190, 2026-09-18) in the
player-chr tick (`src/game/bondview2.c`, `#ifdef PORT`): on every
shot-change teleport during `CAMERAMODE_POSEND` it re-seeded the look-at
filter (`field_3B8` accumulator and `field_3C4/3C8/3CC`) to Bond's new
position, so the camera snapped onto him. The decomp's own leaky integrator
(`bondviewUpdatePlayerCollisionPositionFields`) instead converges over about
20 ticks from the stale pre-teleport position — that convergence is the
swivel (D243 M-143 logged the same sweep, pos2.y -22832 -> +691, and read it
as shake). D243's actual shake cause was the `sizeof(Model)` buffer overlap
(M-189), fixed independently, so neither M-170's freeze nor M-190's re-seed
was still needed.
**Fix.** Removed the re-seed block, its teleport-epoch locals, the epoch
counter (`d243NotifyTeleport` / `d243GetTeleportEpoch`) and the notify call in
`src/game/chrai.c` (`AI_TRYTeleportingChrToPad`) — all `#ifdef PORT`
additions. The path is now the decomp, unmodified (the unconditional
`field_488.pos` write is original code). No Rule-2 sign-off needed: this
removes a port deviation. **Verified:** build rc 0; maintainer replayed the
Dam ending live — swivel present, no shake.
## D553: Surface 2 haze around Bond popping dark/bright as he moves — near-clip fog now recomputed (RSP behaviour) — FIXED 2026-10-06
**Report (maintainer playtest 2026-10-06):** after D543 the lit area around
Bond on Surface 2 was dimmer (good) but changed with his position: dark,
step forward bright, step again dark. 1964 (HLE) shows a similar pulse;
the maintainer then compared against hardware-level emulation
(RetroArch Mupen64Plus-Next, cxd4 LLE RSP + angrylion LLE RDP): there the
light around Bond is static, apart from the game's own small shading
artefacts. So the pulse is a port deviation.
**Cause.** D543's CPU near-plane clip (`port/fast3d/gfx_pc.cpp`) gave each
clip-created vertex a fog value LERPED from its two parents. One parent is
behind the eye; `gfx_sp_vertex` computes its fog as `z * (1/w) * mul + off`,
which jumps between the 0 and 255 clamps as that corner's w crosses 0. The
near-edge fog therefore depended on where a big ground triangle's corners
fell relative to Bond, and flipped as he walked. The RSP's clipper runs
new vertices through its vertex-finish code (gmain.s clip routine ends in
the same finish call), i.e. fog is recomputed from the new vertex's own
clip-space position; at the near plane that is a constant.
**Fix.** The existing `GE_NEARCLIPFOG=recompute` path is now the default
(`GE_NEARCLIPFOG=lerp` keeps the old behaviour for A/B). Port layer only.
**Measured:** a scripted Surface 2 walk (`GE_INPUTSCRIPT="20:START;1400:UP"`,
`GE_RSEED` pinned) gives the same ground brightness in both modes (mean 86.8
vs 86.5 of 255; D543's "brighter feet" concern for recompute does not show),
step-to-step change <2/255 in both — that path does not trigger the pulse,
so the by-eye check where the maintainer saw it is the real test. Goldens:
see the D553 note in tools_pc/golden/README.md if any set moved.
## D554: F10 settings menu wording/casing pass + two-line tips — DONE 2026-10-06
**What.** Display text only in `port/src/optionsoverlay.c` (config keys, the
kRowHelp lookup keys and every value array's order/count are untouched; values
are stored as indices, never as display text). No `src/game` change.
**Style rules.** American spelling. Page titles and hub entries Title Case
(hub actions now "Quit to Desktop", "Restart Game"); row labels and value
names sentence case ("3-point"); parenthesised tags lower case ("(per
profile)", "(no profile)", "(restart)"); tips sentence case, no trailing
period, plain third-person English, never repeating the label; control hints
are `Key: Action` pairs, three spaces between pairs, `/` between alternative
keys with no spaces ("A/Enter: Select", "B/Esc/F10: Close"); slashes inside
labels have no spaces ("Pause/start"). "Original" = the stock N64 behaviour
wherever a player picks it; "N64" only names hardware; "PC" is never a value;
"Extended" is the opposite of Original. "deg" kept for FOV (the font has no
degree sign).
**Maintainer decisions applied.** Aim style / Aim range values are now
Original / Centered and Original / Extended. The "(EXPERIMENTAL)" tag is
removed from Skip intro and All unlocked, with the red-ink path
(`strstr(label,"EXPERIMENTAL")`, `PD_EXP_*`) deleted so nothing depends on the
tag. "Controller style" is now "Original layout". Binding action names: the
pad row "Use / action" is renamed to match the keyboard's "Use/back". "Ext"
is now "Extended" (Control scheme), "Velocity (legacy)" is "Velocity (old)",
"(in a mission)" is "Not in a mission" (the value shows when no live player
exists), the ini warning reads "n unknown settings in the ini, see the log".
All 44 changed tips follow the plan's table; a tip was added for
`Video.CrosshairHide`.
**Code facts confirmed.** Esc closes the hub: `optionsOverlayBack()` sets
`s_backPending`, and the update loop calls `optionsOverlayToggle()` when no
page is open (so the hub hint is "B/Esc/F10: Close"). "Show crosshair"
(`Video.CrosshairHide`, portCrosshairHide) is a port-level override:
`gunDrawSight` returns early when it is set, regardless of "Sight on screen"
(`Bond.Sight`, the game's own per-profile sight-visible reason). The tip says
exactly that.
**Wrap.** Tips no longer go through a 64-byte buffer with ".." chopping.
`tipWrap()` measures with the overlay's own `bodyWidth()` against
`cardW - 8`; if too wide it splits at the last space that fits and the second
centred line is drawn in the hint panel. The panel was already sized for it
(`hintLines*12+2`; 2 tip + 2 key-hint lines = 50 <= `OV_HINT_H` 53), so no
layout constant changed; the key hints move down by the actual tip line count.
A line 2 that still overflows is chopped with ".." (cannot happen for current
tips). `tipSelfCheck()` runs once on the first emit that has fonts and logs
a warning by key for any tip needing more than 2 lines at HUD 150 (budget
189), plus one summary line.
**Review fixes (same day, from the lead's HUD 150 screenshots).**
(1) Control hints clipped at HUD 150. Every hint line is now a `{full, compact}`
pair (`kHint[]`); the compact form is used only when the full one exceeds the
card budget (189 at HUD 150), then the Xbox letters as a last resort. Compact
forms: "A: Select L/R: Change", "B: Back F10: Close", "A: Bind L/R: Slot
Y: Clear", "Keys/mouse only L/R: Slot", "Enter: Bind B: Back", "Press a
button Hold B: Bind B", "Tap B: Cancel Hold BACK: Clear", "Press key or
mouse 1-5", "B: Cancel Del: Clear"; the hub lines already fit. The one-time
check now also logs any hint whose full form (Xbox names) exceeds 296 or whose
compact form exceeds 189.
(2) Game page tip showed only "Reverse makes pushing up look" at HUD 150.
Root cause was not the page: `drawBody` passed `textRender` the raw canvas
width/height as its clip limits, but `textRender` clips in unscaled glyph space
while the string is drawn through the 78% span scale about its own origin.
A line that starts right of centre and is wide enough (here a 187-unit
one-line tip, which `tipWrap` correctly kept on one line) ran past 213 in
unscaled space and lost its tail. `drawBody` now maps the limits back into
unscaled space (`x + (ovW - x) * 100 / 78`, same for y). This affects every
overlay string, none for the worse.
**Owed.** By-eye contact sheet of every page at HUD 100 and 150. Not done:
the slider-bar shrink at HUD 150 (labels still ellipsise there).
## D555: crosshair mouse pointer over the F10 / PC Options overlay (maintainer ask)
**Ask.** The red in-game crosshair should be on top of the F10 / PC Options box
on the main menu, i.e. the mouse pointer there should be the crosshair. Until
now (D519) the game's own crosshair tracked the mouse UNDER the overlay and the
OS arrow was shown on top (D544).
**Fix (port layer only; no src/game change).** One mechanism: while the overlay
is open and the OS cursor is shown, the OS cursor image is a colour cursor
shaped like the project crosshair, hotspot at the centre. The OS composites
the cursor above the game frame, so it is always over the overlay box.
- Art: `tools_pc/make_icon.py` `_cursor()` / `make_cursor_header()` emit
`port/fast3d/ge007_cursor.h` (32x32 and 48x48 RGBA, transparent background,
same proportions as `_crosshair`, red #c62828 with a 1px dark outline;
row-braced arrays like `ge007_icon.h`; deterministic, run twice = same bytes).
- `port/src/input.c`: `crosshairCursorBuild()` packs the pixels against explicit
masks, shifting each channel into its lane before masking (the D550 bug), picks
48 px when the drawable is >= 1.5x the window size (high DPI), else 32.
`crosshairCursorApply()` does SDL_SetCursor(crosshair) / SDL_SetCursor(
SDL_GetDefaultCursor()). Both run from `inputApplyMouseRequests()`, the
host/event thread (videoPumpEvents), next to the SDL_ShowCursor application
(D287) -- never the game thread. Condition: `Input.CrosshairCursor` on, the
applied cursor state is "shown", and `optionsOverlayIsOpen()`; otherwise the
default arrow is restored (overlay closed, focus lost). The cursor object is
built lazily once and kept for the process lifetime.
- The game's under-overlay crosshair (D519, `overlayFrontEndCrosshair`) is
unchanged. It tracks the same window position, so outside the box it sits
directly beneath the pointer and the pointer covers it; whether it visibly
doubles (slight lag halo) needs a by-eye check. If so, the follow-up is to
skip `overlayFrontEndCrosshair()` while the custom cursor is active.
- Setting: `Input.CrosshairCursor` (default 1), F10 Mouse page row "Crosshair
pointer" (toggle), tip "Shows the red crosshair as the mouse pointer in this
menu", in the reset-defaults table.
**Verification.** Build only (ntsc-final rc=0); visual check owed to the
maintainer.
**Reworked 2026-10-06 (maintainer feedback: the front-end crosshair showed
underneath as a duplicate, and the SDL cursor was neither the game's graphic nor
its size).** The custom SDL colour cursor is REMOVED (`crosshairCursorBuild/Apply`,
`port/fast3d/ge007_cursor.h`, `make_icon.py` `_cursor`/`make_cursor_header`; the
other make_icon outputs are unchanged). New mechanism, port layer only:
- `optionsoverlay.c` `drawCrosshairPointer()` runs last in `overlayEmitLocked()`
(after the card, outside the centred-aspect block) and draws the game's own
sprite `crosshairimage` (image_bank.c; 32x32, loaded from the user's ROM
at runtime -- nothing baked into the tree) with the same
`texSelect` + `display_image_at_position` as `frontDrawCursor` (white, alpha
220), at the OS mouse position mapped through `gfx_get_ui_screen_rect`.
- Position/size, FRONT END: the first cut parked `cursor_h_pos/cursor_v_pos` at
-1000, but the game's own front-end tick clamps the cursor back into bounds
before `frontDrawCursor`, so a copy appeared at the clamp corner (found in a
headless capture). Park removed. Now D519's `overlayFrontEndCrosshair()` keeps
the game cursor tracking the mouse (option on or off) and
`drawCrosshairPointer()` draws the pointer exactly over it: `input.c`
`inputFrontEndCursorUiFrac()` is the inverse of D519's window->canvas mapping
(x = (cursor_h_pos - screenleft)/screenwidth, then the D335 pillarbox
x*vis + (1-vis)/2 when native widescreen; y likewise), giving fractions of the
UI screen rect; canvas px = frac * (W,H); half-extent = (image->width/2)/viGetX()
* W * vis and (image->height/2)/viGetY() * H, i.e. frontDrawCursor's size. The
sprite lands on the one underneath, so one crosshair is visible.
- IN LEVEL (no front-end cursor): position from `SDL_GetMouseState` through
`gfx_get_ui_screen_rect`; half-extent `16/ovScale()` x `(4/3)/portNativeAspect()`
(gunDrawSight's 16.0 and widescreen x fix). It does NOT follow the Video.Crosshair
colour/size/opacity options (UI pointer: plain sprite, front-end opacity).
- OS cursor: `inputApplyMouseRequests()` (host thread) hides it while the option is
on and the overlay is open; restores on close / option off.
- Option off: previous behaviour (OS arrow over the overlay, D519 crosshair under it).
- Also: `Game.NoHitFlash` tip is now "Hides the white flash when you take damage".
Headless captures (1280x720, front end, overlay open; frame 1100): AspectMode=1, AspectMode=1 + NativeWidescreen=1 (the front end stays 4:3 pillarboxed), AspectMode=0 (stretched, the pointer stretches with the game, as its own cursor does): exactly one crosshair each. Live mouse feel owed to the maintainer.
## D556: F10 feedback pass (tips trimmed, conditional-row order, wording fixes) + mouse scrollbar
Port layer only (`port/src/optionsoverlay.c`); no game-code edits. Maintainer playtest feedback on the D554/D555 menu.
- **Tips.** A tip is kept only where the setting is port/GE specific or not obvious. Removed (no tip): `Video.FullscreenMode`, `__CenterWindow`, `Video.CrosshairAlpha`, `Video.MSAA`, `Video.VSync`, `Video.FpsCap`, `Video.TextureFilter`, `Video.Anisotropy`, `Game.ScreenShakeIntensity`, `Audio.MasterVolume`, `Input.MouseSensitivity`, `Input.MouseYScale`, `Input.MouseInvertY`, `Input.MouseSmoothing`, `Input.MouseRawInput`, `Input.PadLookInvertY`, `Input.PadDeadzoneL/R`, `Input.PadLookSensX/Y`, `Input.PadLookSmooth`, `Input.PadTriggerPct`, `Input.RumbleScale`, `Bond.AimControl`, `Bond.Ammo`, `Input.CrouchMode`, `__UpdateOpen`. An absent tip leaves its tip area empty (see the hint-panel fix below). Rewritten: AspectMode, WidescreenAuto, SafeAreaCrop, CrosshairCursor, Look up/down, NoHitFlash, SkipIntro, CheckUpdates. Anti-aliasing's "higher costs speed" tip is gone with the rest of the standard settings.
- **Conditional-row order.** Rule: a row whose visibility depends on another row sits directly below it. Audited every page: Fullscreen -> Fullscreen mode / Center window, Aim style -> Aim range, Crosshair color -> Red/Green/Blue, Layout preset -> pad binds, Check for updates -> update link were already right. One violation: on file select the D519 profile chooser was injected as row 1 of any page with profile-backed rows, so choosing Original put "Profile" above "Control style" on the Controller page. It is now injected directly above the first profile-backed row (Controller: Control style, Profile, Original layout; Game page unchanged). Grouping moves (display order only): Widescreen auto FOV now follows Native widescreen; Mouse Smoothing/Raw input sit with the other mouse-feel rows, before Aim style.
- **Look up/down "Reverse/Upright"** is GoldenEye's own N64 wording (`assets/obseg/text/LoptionsE.c` OPTION_STR_1B/1C, used by `src/game/options.c`), so it is kept; the tip says Reverse inverts like Invert look. PD's port has no such string.
- **No hit flash.** Code: `currentPlayerSetFadeColour` in `src/game/bondview2.c` (`portNoHitFlash`, D232) zeroes any non-black fade. The two non-black fades are the damage envelope in bondviewPlayerTickDamageAndHealth (WHITE 0xFF, alpha scaled by health) and the red (150,0,0) overlay after the death blood. The old "red flash" tip was wrong for the common case; the tip now says "damage flash and the red tint on death".
- **Skip intro.** D408 (post-mission failure screen skipped) is FIXED (boot-only gate in `src/game/lv.c`); the claim is removed. The option only boots to file select.
- **Mouse scrollbar.** `overlayScrollbar()` computes a 3-unit track in the card's right margin (x = right-7..right-4; value text ends at right-10, so no overlap at HUD 100/150) over the body rows, only while the page overflows (same test as the "^ v" marker); thumb size = visible/total rows, position from the eased `s_scrollF`. Drawn after the row fills in the dim/bright green palette. Input (in `overlayHandleInputLocked`, before the row-click path, using the same ox/oy mapping): press on the thumb drags it, press on the track pages up/down by one window; both go through `overlayScrollTo()`, which sets `s_scroll` and pulls `s_sel` into the window (as the wheel moves the selection, since `overlayUpdateScroll` would otherwise snap back). A scrollbar press never reaches the row click.
- **Hint-panel regression fix (lead review).** First cut drew the control-hint lines only while `!s_mouseActive`, and the OS cursor merely resting over the window sets `s_mouseActive`, so hints vanished per page depending on pointer position (and would flicker in play). Now the control hints are always drawn on every page, and a section page reserves a fixed two-line tip area (`tipArea` = 24 units) above them so a row without a tip leaves it empty and the hints never move (2 + 2 lines = 50 <= OV_HINT_H 53); the hub has no tips and reserves none. Compact-hint fallback unchanged.
- **Crouch mode default -> Toggle** (maintainer approved): `crouchMode` initial value 1 in `port/src/input.c` (the configRegisterInt default) and the F10 reset table entry `Input.CrouchMode` = 1 in `optionsoverlay.c`. Existing ini values are kept; no DefaultsRev migration.
- **Look ahead OFF for new profiles: NOT done.** New files are built from the `BLANKSAVEDATA` macro (`DEFAULT_OPTIONS`) at nine sites inside `src/game/file2.c` (fileResetSave, fileBuildWriteNewSave, fileDeleteSaveForFolder, fileClearSavefileForFolder, ...), mostly called from within that same file, so there is no port-side creation hook (a linker wrap would miss the intra-file calls, and detecting "new" by save contents would risk flipping existing profiles). It needs either a `#ifdef PORT` edit of `DEFAULT_OPTIONS` (Option B, src/game, needs rule-2 sign-off) or an agreed new hook; left for the maintainer.
**Verification.** Build only (ntsc-final rc=0); no game launch. By-eye check owed (tips, Controller page order, scrollbar and hint panel at HUD 100 and 150).
## D557: new/blank profiles start with Look ahead OFF on PC — RULE-2 SIGN-OFF (maintainer, 2026-10-06) — FIXED 2026-10-06
**Decision.** Defaults review against the Perfect Dark PC port
(`docs/dev/notes/DEFAULTS-VS-PD.md`, local): Look ahead (per-profile
`OPTION_LOOKAHEAD`) is the one N64 default that is clearly hostile on PC —
its pitch auto-centring on slopes fights mouse and right-stick look; PD's
port keeps the equivalent on only under `PLATFORM_N64`. Maintainer approved
turning it OFF for new profiles, with the explicit Rule-2 sign-off this
`src/game` edit needs (it is a deliberate PC default, not an ABI/layout
fix).
**Why src/game.** New profiles are built from `BLANKSAVEDATA`
(`src/bondconstants.h`, using `DEFAULT_OPTIONS`) at nine sites inside
`src/game/file2.c` (new save, erase/clear, copy-to-blank, ...); there is no
port-side "profile created" hook, and inferring "new" from save contents
risks flipping existing profiles. So the change is one macro:
`src/game/file2.h` `DEFAULT_OPTIONS` drops `OPTION_LOOKAHEAD` under
`#ifdef PORT` (`#else` keeps the original for the byte-matching N64 build).
The port's F10 "Reset to defaults" mirror (`kBlankSave`,
`port/src/watchsettings.c`) is built from the same macro, so it follows.
**Scope.** Existing profiles keep their stored option bits — no migration.
Only profiles initialised from `BLANKSAVEDATA` afterwards (new/erased/cleared)
start with Look ahead off; players can still turn it on per profile. Same
batch: `Input.CrouchMode` default -> Toggle (D556; ini default, new installs
and Reset to defaults only). **Verified:** build rc 0; Windows gate sweep
21/21 PASS (the canonical golden save's profiles keep their bits).
## D558: F10 overlay tips cut off in a level, fine on the front end — FIXED 2026-10-06
**Symptom.** At 1280x720, AspectMode=1, in a level, long Game-page tips ("Hides the
white flash when you take dama", "Reverse inverts up and down, like Invert lo") were
cut off; the front end showed every tip whole.
**Root cause (not the aspect/HUD geometry).** Card geometry is identical in both
contexts (the overlay lays out on its own canvas: `ovW()` x `ovH()`, card/tip budget
from the same `overlayLayout()`; tip wrap, centring and clip all use it).
The cut is in the game's `textRenderGlyph` (`src/game/textrelated.c:241`): it drops any
glyph whose UNSCALED x is `> viGetX()` (or y `> viGetY()`) -- the game's 2D canvas, 320x240
in a level, 440x330 on the front end. `drawBody` draws through the 78% span scale about
the string start, so the x the game tests is the start plus the unscaled advance (drawn
width / 0.78): a tip starting at x=46 died at 320 (214 drawn units in), on the front end
the 440 limit hid it. Right-aligned values and, at HUD 75 (ovW 427), more text were exposed
to the same limit.
**Fix (port layer only).** `port/src/optionsoverlay.c` `textRenderWide()` widens the
canvas the game tests (`viSetXY`, restored right after) for the duration of each
`textRender` call, to the overlay's unscaled layout extent (`ovW()*100/OV_TEXT_PCT`).
`tipSelfCheck` already used the narrowest reachable budget (HUD 150 -> card 197, av 189);
unchanged, still 0 over 2 lines.
**Test hooks.** `GE_OPTIONSOVERLAY_ATFRAME=<n>` re-applies the GE_OPTIONSOVERLAY /
_SECTION request at emit n (survives level load; read once, cached);
`GE_OPTIONSOVERLAY_SELECT=<row key>` selects a row on the opened page.
**Verified** by in-level captures (`-level_09`, GE_INPUTSCRIPT="20:START", ATFRAME=200):
before: NoHitFlash and Bond.Look tips clipped at the same screen x; after: whole and
centred at HudScale 100 (AspectMode 1 and 0) and wrapped to two centred lines at HudScale 150.
## D559: Look ahead off for untouched existing folders — FIXED 2026-10-06 (maintainer-approved, port layer only)
D557 only changes folders initialised from now on. `port/src/watchsettings.c`
`migrateUntouchedLookAhead()` handles existing ones with no further `src/game` edit:
when a stage is starting (first game-thread tick with `stageActive()`, before the stage
load's `fileLoadSettingsForFolder` re-reads the save; also applied live if the load already
ran) and `selected_folder_num` is a valid save with options EXACTLY the old factory default
`AUTOAIM|SIGHTONSCREEN|LOOKAHEAD|DISPLAYAMMO` (0x3a, control-type bits 0), `flag_007 == 0`,
no unlocked cheats and every `times[]` byte zero (no stage completed at any difficulty),
it clears `OPTION_LOOKAHEAD` and persists with `fileWriteSave` (same path as the F10
per-profile rows), logging one INFO line. Folders with any progress or any other option bit
are never touched. Once per stage activation. **Verified** with a temporary forced-state
hook (removed): untouched folder fires and logs; with progress it does not.
## D560: F10 copy pass 3 (maintainer line review)
Display text only (`port/src/optionsoverlay.c`); config keys, value-array order and count unchanged.
Every tip is now a complete sentence ending in a period. Changes: Display mode tip "Modern, original or custom graphics presets."; Aspect ratio tip shown only while Fill window is selected (`optionsRowHelp` checks the value; Fill window = `videoOutputAspect()` 0, the output rect is the whole window); tips removed for Native widescreen, Crop overscan, Draw/Fog/LOD distance and Look up/down; Explosion shake tip restored ("Sets how strongly explosions shake the screen.", it scales `viShake()` in `src/fr.c` via `portScreenShakeScale`); Control style, Layout presets (both the Extended `Input.PadPreset` row and the Original `Bond.Control` row now carry the label "Layout presets"), Auto-aim, Gameplay view area (D468), No hit flash, Skip intro ("Skips the intro and opens the file select screen." -- `lv.c` boots to MENU_FILE_SELECT, D408), Check for updates reworded.
Southpaw: swaps only the fire and aim TRIGGER actions (`input.c` `padMapPure`, `c->southpaw` G<->R swap), not the sticks (the stick swap belongs to the 1.3 Frost / 1.4 Elektra presets, D498); tip "Swaps the fire and aim triggers."
Look up/down values renamed Inverted/Normal (index 0 = the game's "Reverse", 1 = "Upright"); mapping from the game's own option strings and the options-screen preview (`options.c` ~3103: value 1 shows up for D-pad up). Note a fresh profile (value 0) therefore reads "Inverted"; compare against 1964 if in doubt.
Two tips were shortened to keep `tipSelfCheck` at 0 over 2 lines at HUD 150 (Control style: "Choose the port's modern controls or the original game's."; Gameplay view area: "...widescreen and any field of view."; layout presets drops "port"). Menu pointer row kept; analysis in the session report (Input.MenuPointerMode, D165, default Direct).
**Verified** by in-level captures (`-level_09`, 1280x720, AspectMode 0/1, HudScale 100): all whole, centred, punctuated; Gameplay view area wraps to two lines at HUD 100.
## D561: F10 "Menu pointer" row removed — FIXED 2026-10-06 (maintainer request)
`Input.MenuPointerMode` (Direct = 1:1 mouse position, default; Velocity (old) =
D165's mouse-as-analog-stick nudge) only affects the GAME'S front-end menu
cursor: it is read solely under `menuMode` (`port/src/input.c`, "not in a
stage"), never in gameplay, never by the F10 overlay (which handles the mouse
itself), never on pad/Deck paths. Maintainer: an option only a legacy user would
want "will only confuse and cast doubt". Removed the F10 row, its tip and its
value names (`port/src/optionsoverlay.c`); kept the ini key (default Direct, and
its Reset-to-defaults entry) and the Velocity code path so a hand-set ini still
works for this release. Follow-up (next release): delete the Velocity path and
`Input.MenuPointerSpeed`. Verified: build rc 0; in-level Mouse page capture at
1280x720 shows the row gone; tip/hint checks 0 over.
## D562: F10 crosshair pointer hidden while a pad/keyboard drives the menu
Maintainer: the pointer "just hovers over and blocks UI elements" when a controller drives the menu.
`drawCrosshairPointer()` (`port/src/optionsoverlay.c`) now returns early unless `s_mouseActive`
(the existing overlay state: set by mouse move/click, cleared by pad/D-pad/stick/keyboard nav,
accept, wheel and on open), so the pointer reappears on the next mouse movement. No second tracker.
The OS cursor stays hidden while the crosshair pointer mode is on (`inputApplyMouseRequests`
hides it whenever `cfgCrosshairCursor && optionsOverlayIsOpen()`), so no arrow appears.
Front end: the game's own D519 cursor (`overlayFrontEndCrosshair`, `input.c`) is left as is: it sits
UNDER the overlay card (the card does not let it block rows) and is clamped by the game each tick, so
there is no clean port-side park (the -1000 attempt is not repeated). Build rc 0.
## D563: F10 root hub: Left/Right no longer open pages; held nav inputs latched
Maintainer: holding the stick/D-pad/arrow on the root menu could enter a page (e.g. PC Options) and
keep acting inside it. (a) On the root hub (`s_section < 0`) header/link and action rows now activate
only on A / Enter (or a click); Left/Right/stick/D-pad/arrows do nothing there. Pages keep Left/Right
value changes. (b) Held-input latch: `s_latchArm` is set when the overlay opens (closed-state branch
of `overlayHandleInputLocked`, covers F10 and the front-end PC Options entry) and on every page
enter/back (`overlayOpenHeader`, `overlayBackOne`); the next poll snapshots held up/down/left/right/
accept/back and each stays masked (`s_latchMask`) until released once, so auto-repeat cannot fire
from a latched input. Verified by code reading + build rc 0 only: the overlay reads SDL pad/keyboard
state directly, which GE_INPUTSCRIPT does not drive, so a headless held-input repro is not possible; maintainer
to check front end (file select -> PC Options) and in-level F10 by hand.
## D564: F10 "Look up/down" removed; game value pinned to factory; tip polish — FIXED 2026-10-06 (maintainer decision)
**Report:** maintainer: changing Look up/down (in-level F10 and front end) had
no visible effect. **Trace (code, file:line in the D562/D563 session report):**
the row wrote the game's `OPTION_INVERTLOOK` (`set_cur_player_look_vertical_inverted`),
read only at `src/game/bondview2.c:4938` (`invertPitch`) on the game's own
stick/C-button pitch path. The port's mouse look (D300 `hipDirectCompute`,
aim models) and centred-aim pad look (`padDirectCompute`) write `vv_verta`
directly and never consult it; only pad look in the N64 aim style passes
through it, where it stacks with Controller "Invert look". Perfect Dark's port
instead routes mouse and pad through its single "Reverse Pitch" option; GE's
port already has per-device Invert look rows (the usual PC model), so the
maintainer chose removal.
**Fix (port only):** removed the `Bond.Look` row and its value names
(`port/src/optionsoverlay.c`); `port/src/watchsettings.c` pins the game's
value to its factory default 0 each stage tick (blank profiles load 0:
`DEFAULT_OPTIONS` has no `OPTION_INVERTLOOK`, `src/game/file2.c:1357`), so
every default profile behaves exactly as before and a value saved by an older
build or set in the in-game watch menu cannot silently flip pad pitch. Mouse /
Controller "Invert look" are the only inversion controls. Same pass, tip
polish: All unlocked "Unlocks all levels, 007 mode and every cheat.", Show
crosshair, FOV and Aim style tips rewritten as plain sentences. Verified: both
TUs compile (link was blocked by a running game; full build + re-cut follow).
+1 -1
View File
@@ -28,6 +28,6 @@ separate [Building](building.md) page.)
how the two-AI-agent setup ran, its timeline and numbers, and an honest
read on what did and didn't work.
- **[Security status](security.md)** — what the release packages install
(and don't): no networking, no telemetry, no ROM, no system writes.
(and don't): no networking unless you opt in to the update check, no telemetry, no ROM, no system writes.
- **[Fidelity status](fidelity.md)** — how faithfully the port reproduces
the N64 game's logic, and what was audited to back that claim.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 910 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 939 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

After

Width:  |  Height:  |  Size: 396 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 893 KiB

After

Width:  |  Height:  |  Size: 817 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

+1 -1
View File
@@ -72,7 +72,7 @@ single tracker for what comes next.
<li><strong>NTSC-U ROMs only in the release packages.</strong> PAL and JP ROMs convert, build and run from source, but release packages ship the US (NTSC-U) ROM. On the roadmap.</li>
<li><strong>Changing the aspect ratio in a level can briefly glitch the gun/hand model</strong> (cosmetic, one-off). Change the ratio from the front-end PC Options, or accept it (D509).</li>
<li><strong>No macOS or ARM builds yet.</strong> Today the release ships Windows, Linux and Steam Deck (keyboard, mouse and controller-button rebinding are all supported, defaults in the <a href="https://github.com/jkdansereau/goldeneye-pc-port#controls">README's Controls section</a>); macOS and ARM are on the roadmap.</li>
<li><strong><code>Skip intro</code> and <code>All unlocked</code> are experimental</strong> — leave them off for a normal playthrough. <code>All unlocked</code> has not written your save since v0.4.1, but saves made while it was on in builds before v0.4.1 can hold fake unlocks and are not repaired; back up <code>data/ge007.eep</code> if you used it on an older build (D442, D387).</li>
<li><strong>Saves from builds before v0.4.1 can hold fake unlocks from <code>All unlocked</code></strong> — <code>All unlocked</code> has not written your save since v0.4.1, but saves made while it was on in builds before v0.4.1 can hold fake unlocks and are not repaired; back up <code>data/ge007.eep</code> if you used it on an older build (D442, D387).</li>
<li><strong>The first frame of a level takes a little longer</strong> while its textures upload — a brief FPS-counter dip; nothing to fix (D475, D480).</li>
<li><strong>In widescreen, far objects almost fully in fog can still show a faint distant building edge</strong> (4:3 matches the N64). Cosmetic (D503).</li>
</ul>
+45
View File
@@ -382,6 +382,51 @@ sync/flush) is shared.
rumble/mempak.
This is simpler than a full Memory Pak/PFS emulation.
* **F10 settings overlay: mouse and scrolling** (`port/src/optionsoverlay.c`,
`port/src/input.c`; D544, D314, D304):
* *Cursor.* `inputSuspendForOverlay()` (`input.c`) drops relative-mouse
grab and calls `mouseRequestCursor(1)`. With `Input.CrosshairCursor` on
(default, D555) `inputApplyMouseRequests()` (host thread) then hides the OS
cursor while the overlay is open and `drawCrosshairPointer()` in
`optionsoverlay.c` draws the game's own `crosshairimage` sprite at the mouse,
last in the overlay DL, at the game's on-screen size; the front end's own
crosshair keeps tracking the mouse (D519) and the pointer is drawn exactly
over it (`inputFrontEndCursorUiFrac()`), so only one shows; in a level it
follows the OS mouse. With the option off the OS arrow shows over the box. One owner of the cursor state (no show/hide race).
* *Hover vs selection.* `overlayHandleInputLocked()` maps the pointer to a
visible row with `overlayRowAtY()` (follows the eased scroll position, so
a click lands on the row it visually covers). Hover sets `s_hover` only
while `s_mouseActive` (mouse moved or clicked); it is a focus cue, never
the selection. The tip line (`helpVis` in the emit path) follows the
hovered row while the mouse is in use, else the selected row. The
wheel clears `s_mouseActive`.
* *Clicks.* Left-click on a row selects it; headers, links and Back act on
the whole row, otherwise only a click in the control span to the right of
the name changes the setting (toggle/cycle, open a dropdown, start a
binding capture, or set + drag a slider). With a dropdown open
(`s_ddRow >= 0`) it owns the pointer: hover picks, click confirms, click
outside or right-click cancels. Right-click elsewhere = Esc/back (closes a
dropdown first, then one page, then the overlay). `s_rmbBlock` holds that
off until release after a binding capture, so a right-click just bound
does not also back out.
* *Scrolling.* Long pages scroll as a window
over the visible-row list (`s_visIdx`/`s_visN`, first displayed entry
`s_scroll`, section header pinned). `overlayUpdateScroll()` moves the
window only when the selection leaves it (D304). The wheel arrives on the
host thread: `optionsOverlayScroll()` only queues notches in
`s_wheelPending` (SDL atomic, D314) and `overlayApplyWheel()` applies them
on the scheduler thread. The drawn position `s_scrollF` eases toward
`s_scroll` each emit (snaps on page change via `s_scrollSnap`).
* *Scrollbar (D556).* A page that overflows (same test as the "^ v" title
marker) draws a thin track + thumb in the card's right margin
(`overlayScrollbar()`: x = right-7..right-4, clear of the value text that
ends at right-10); thumb size = visible/total rows, position from
`s_scrollF`. Mouse: a press on the thumb drags it, a press on the track
pages up/down; both call `overlayScrollTo()`, which sets `s_scroll` and
moves `s_sel` into the window (like the wheel; otherwise
`overlayUpdateScroll()` would snap back). A scrollbar press never reaches
the row-click path.
---
## 8. Recommended plan
+13 -4
View File
@@ -1,6 +1,6 @@
---
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.
description: What a GoldenEye PC port release installs and doesn't: no networking unless you opt in to the update check, no telemetry, no ROM, no system writes — re-verified against the v0.5.0 source.
date: '2026-10-05'
modified_time: '2026-10-05'
---
@@ -20,7 +20,9 @@ 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
or library-loading calls in either (since D551 the one exception is the
opt-in update check: a System32 `winhttp.dll` load on Windows or a `curl`
process on Linux, only when `Game.CheckUpdates` is on); 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
@@ -44,8 +46,15 @@ legally-owned copy.
At runtime, the engine:
- has **no networking of any kind** (no sockets, no HTTP, checked directly
against the source),
- has **no networking unless you turn on the opt-in update check**
(`Game.CheckUpdates`, off by default): then it makes one HTTPS request to
GitHub's releases API per launch (a fixed User-Agent; GitHub sees the
request and your IP, as with any web request) and never downloads or
installs anything. On Windows it loads the system `winhttp.dll` from
System32 at runtime only in that case (it is not in the import table); on
Linux/Steam Deck it runs the system `curl`. With the setting off there
are no sockets and no HTTP (D551; checked against the source and run live
on Windows, Linux and Steam Deck),
- has **no telemetry, crash reporting, or analytics** of any kind,
- **never touches the Windows registry** and **never requests elevated
permissions**,
+46
View File
@@ -0,0 +1,46 @@
/* Generated by tools_pc/make_icon.py -- do not edit by hand.
* The project mark (a generic version of the GE default aim
* crosshair) as a 32x32 RGBA array, row-major, plain R,G,B,A
* byte order per pixel; used by gfx_sdl2.cpp for
* SDL_SetWindowIcon. Regenerate: python tools_pc/make_icon.py */
#ifndef GE007_ICON_H
#define GE007_ICON_H
#include <stdint.h>
#define GE007_ICON_SIZE 32
static const uint8_t ge007_icon_rgba[GE007_ICON_SIZE][GE007_ICON_SIZE][4] = {
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 6, 7, 7,255},{ 6, 7, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 2, 4, 6,255},{ 2, 4, 6,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 6, 6, 7,255},{ 17, 14, 10,255},{ 17, 14, 10,255},{ 6, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 7, 7,255},{ 2, 4, 6,255},{ 16, 14, 10,255},{211,147, 53,255},{211,147, 53,255},{ 16, 14, 10,255},{ 2, 4, 6,255},{ 5, 7, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 8, 7, 7,255},{ 5, 5, 7,255},{ 21, 17, 11,255},{226,157, 57,255},{226,157, 57,255},{ 21, 17, 11,255},{ 5, 5, 7,255},{ 8, 7, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 8, 7, 8,255},{ 9, 8, 8,255},{ 13, 11, 9,255},{ 10, 9, 8,255},{ 17, 14, 9,255},{222,154, 56,255},{222,154, 56,255},{ 17, 14, 9,255},{ 10, 9, 8,255},{ 13, 11, 9,255},{ 9, 8, 8,255},{ 8, 7, 8,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 6, 7, 7,255},{ 9, 8, 8,255},{ 10, 9, 8,255},{ 10, 9, 8,255},{ 0, 1, 5,255},{ 0, 0, 3,255},{ 29, 22, 12,255},{224,156, 56,255},{224,156, 56,255},{ 29, 22, 12,255},{ 0, 0, 3,255},{ 0, 1, 5,255},{ 10, 9, 8,255},{ 10, 9, 8,255},{ 9, 8, 8,255},{ 6, 7, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 6, 7, 7,255},{ 9, 8, 8,255},{ 12, 10, 9,255},{ 6, 6, 7,255},{ 3, 3, 6,255},{ 70, 50, 22,255},{130, 91, 35,255},{216,150, 54,255},{233,162, 58,255},{233,162, 58,255},{216,150, 54,255},{130, 91, 35,255},{ 70, 50, 22,255},{ 3, 3, 6,255},{ 6, 6, 7,255},{ 12, 10, 9,255},{ 9, 8, 8,255},{ 6, 7, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 6, 7, 7,255},{ 9, 8, 8,255},{ 10, 9, 8,255},{ 1, 2, 6,255},{ 20, 15, 10,255},{162,113, 42,255},{249,173, 62,255},{255,184, 66,255},{226,157, 57,255},{231,160, 58,255},{231,160, 58,255},{226,157, 57,255},{255,184, 66,255},{249,173, 62,255},{162,113, 42,255},{ 20, 15, 10,255},{ 1, 2, 6,255},{ 10, 9, 8,255},{ 9, 8, 8,255},{ 6, 7, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 9, 8, 8,255},{ 12, 10, 9,255},{ 5, 4, 6,255},{ 74, 52, 23,255},{222,154, 56,255},{251,174, 62,255},{218,151, 55,255},{112, 78, 31,255},{ 35, 25, 14,255},{223,155, 56,255},{223,155, 56,255},{ 35, 25, 14,255},{112, 78, 31,255},{218,151, 55,255},{251,174, 62,255},{222,154, 56,255},{ 74, 52, 23,255},{ 5, 4, 6,255},{ 12, 10, 9,255},{ 9, 8, 8,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 8, 7, 8,255},{ 11, 9, 8,255},{ 6, 6, 7,255},{ 17, 13, 10,255},{223,155, 56,255},{255,180, 64,255},{164,114, 43,255},{ 20, 15, 10,255},{ 0, 0, 3,255},{ 16, 13, 10,255},{222,155, 56,255},{222,155, 56,255},{ 16, 13, 10,255},{ 0, 0, 3,255},{ 20, 15, 10,255},{164,114, 43,255},{255,180, 64,255},{223,155, 56,255},{ 17, 13, 10,255},{ 6, 6, 7,255},{ 11, 9, 8,255},{ 8, 7, 8,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 9, 8, 8,255},{ 10, 9, 8,255},{ 3, 3, 6,255},{161,112, 42,255},{251,174, 62,255},{165,115, 43,255},{ 8, 6, 7,255},{ 5, 5, 7,255},{ 10, 9, 8,255},{ 21, 17, 10,255},{226,157, 57,255},{226,157, 57,255},{ 21, 17, 10,255},{ 10, 9, 8,255},{ 5, 5, 7,255},{ 8, 6, 7,255},{165,115, 43,255},{251,174, 62,255},{161,112, 42,255},{ 3, 3, 6,255},{ 10, 9, 8,255},{ 9, 8, 8,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 6, 7, 7,255},{ 8, 7, 7,255},{ 13, 11, 9,255},{ 3, 3, 5,255},{ 70, 50, 22,255},{248,173, 62,255},{218,152, 55,255},{ 17, 13, 10,255},{ 6, 6, 7,255},{ 14, 11, 9,255},{ 6, 6, 7,255},{ 19, 16, 10,255},{212,147, 53,255},{212,147, 53,255},{ 19, 16, 10,255},{ 6, 6, 7,255},{ 14, 11, 9,255},{ 6, 6, 7,255},{ 17, 13, 10,255},{218,152, 55,255},{248,173, 62,255},{ 70, 50, 22,255},{ 3, 3, 5,255},{ 13, 11, 9,255},{ 8, 7, 7,255},{ 6, 7, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 2, 4, 6,255},{ 5, 5, 7,255},{ 10, 9, 8,255},{ 0, 0, 3,255},{130, 91, 35,255},{254,184, 65,255},{112, 79, 31,255},{ 0, 0, 4,255},{ 10, 9, 8,255},{ 6, 6, 7,255},{ 9, 8, 8,255},{ 10, 8, 8,255},{ 20, 16, 11,255},{ 20, 16, 11,255},{ 10, 8, 8,255},{ 9, 8, 8,255},{ 6, 6, 7,255},{ 10, 9, 8,255},{ 0, 0, 4,255},{112, 79, 31,255},{254,184, 65,255},{130, 91, 35,255},{ 0, 0, 3,255},{ 10, 9, 8,255},{ 5, 5, 7,255},{ 2, 4, 6,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 6, 6, 7,255},{ 16, 13, 9,255},{ 21, 16, 11,255},{ 17, 14, 10,255},{ 30, 22, 12,255},{216,150, 54,255},{225,157, 57,255},{ 34, 26, 13,255},{ 17, 13, 9,255},{ 21, 17, 11,255},{ 20, 15, 10,255},{ 10, 8, 8,255},{ 9, 8, 8,255},{ 6, 6, 7,255},{ 6, 6, 7,255},{ 9, 8, 8,255},{ 10, 8, 8,255},{ 20, 15, 10,255},{ 21, 17, 11,255},{ 17, 13, 9,255},{ 34, 26, 13,255},{225,157, 57,255},{216,150, 54,255},{ 30, 22, 12,255},{ 17, 14, 10,255},{ 21, 16, 11,255},{ 16, 13, 9,255},{ 6, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 6, 7, 7,255},{ 2, 4, 6,255},{ 17, 14, 10,255},{212,147, 53,255},{226,157, 57,255},{222,154, 56,255},{224,156, 56,255},{233,162, 58,255},{231,160, 58,255},{223,155, 56,255},{223,154, 56,255},{226,157, 57,255},{212,147, 53,255},{ 21, 16, 11,255},{ 6, 6, 7,255},{ 11, 10, 8,255},{ 11, 10, 8,255},{ 6, 6, 7,255},{ 21, 16, 11,255},{212,147, 53,255},{226,157, 57,255},{223,154, 56,255},{223,155, 56,255},{231,160, 58,255},{233,162, 58,255},{224,156, 56,255},{222,154, 56,255},{226,157, 57,255},{212,147, 53,255},{ 17, 14, 10,255},{ 2, 4, 6,255},{ 6, 7, 7,255}},
{{ 6, 7, 7,255},{ 2, 4, 6,255},{ 17, 14, 10,255},{212,147, 53,255},{226,157, 57,255},{222,154, 56,255},{224,156, 56,255},{233,162, 58,255},{231,160, 58,255},{223,155, 56,255},{223,154, 56,255},{226,157, 57,255},{212,147, 53,255},{ 21, 16, 11,255},{ 6, 6, 7,255},{ 11, 10, 8,255},{ 11, 10, 8,255},{ 6, 6, 7,255},{ 21, 16, 11,255},{212,147, 53,255},{226,157, 57,255},{223,154, 56,255},{223,155, 56,255},{231,160, 58,255},{233,162, 58,255},{224,156, 56,255},{222,154, 56,255},{226,157, 57,255},{212,147, 53,255},{ 17, 14, 10,255},{ 2, 4, 6,255},{ 6, 7, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 6, 6, 7,255},{ 16, 13, 9,255},{ 21, 16, 11,255},{ 17, 14, 10,255},{ 30, 22, 12,255},{216,150, 54,255},{225,157, 57,255},{ 34, 26, 13,255},{ 17, 13, 9,255},{ 21, 17, 11,255},{ 20, 15, 10,255},{ 10, 8, 8,255},{ 9, 8, 8,255},{ 6, 6, 7,255},{ 6, 6, 7,255},{ 9, 8, 8,255},{ 10, 8, 8,255},{ 20, 15, 10,255},{ 21, 17, 11,255},{ 17, 13, 9,255},{ 34, 26, 13,255},{225,157, 57,255},{216,150, 54,255},{ 30, 22, 12,255},{ 17, 14, 10,255},{ 21, 16, 11,255},{ 16, 13, 9,255},{ 6, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 2, 4, 6,255},{ 5, 5, 7,255},{ 10, 9, 8,255},{ 0, 0, 3,255},{130, 91, 35,255},{254,184, 65,255},{112, 79, 31,255},{ 0, 0, 4,255},{ 10, 9, 8,255},{ 6, 6, 7,255},{ 9, 8, 8,255},{ 10, 8, 8,255},{ 20, 16, 11,255},{ 20, 16, 11,255},{ 10, 8, 8,255},{ 9, 8, 8,255},{ 6, 6, 7,255},{ 10, 9, 8,255},{ 0, 0, 4,255},{112, 79, 31,255},{254,184, 65,255},{130, 91, 35,255},{ 0, 0, 3,255},{ 10, 9, 8,255},{ 5, 5, 7,255},{ 2, 4, 6,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 6, 7, 7,255},{ 8, 7, 7,255},{ 13, 11, 9,255},{ 3, 3, 5,255},{ 70, 50, 22,255},{248,173, 62,255},{218,152, 55,255},{ 17, 13, 10,255},{ 6, 6, 7,255},{ 14, 11, 9,255},{ 6, 6, 7,255},{ 19, 16, 10,255},{212,147, 53,255},{212,147, 53,255},{ 19, 16, 10,255},{ 6, 6, 7,255},{ 14, 11, 9,255},{ 6, 6, 7,255},{ 17, 13, 10,255},{218,152, 55,255},{248,173, 62,255},{ 70, 50, 22,255},{ 3, 3, 5,255},{ 13, 11, 9,255},{ 8, 7, 7,255},{ 6, 7, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 9, 8, 8,255},{ 10, 9, 8,255},{ 3, 3, 6,255},{161,112, 42,255},{251,174, 62,255},{165,115, 43,255},{ 8, 6, 7,255},{ 5, 5, 7,255},{ 10, 9, 8,255},{ 21, 17, 10,255},{226,157, 57,255},{226,157, 57,255},{ 21, 17, 10,255},{ 10, 9, 8,255},{ 5, 5, 7,255},{ 8, 6, 7,255},{165,115, 43,255},{251,174, 62,255},{161,112, 42,255},{ 3, 3, 6,255},{ 10, 9, 8,255},{ 9, 8, 8,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 8, 7, 8,255},{ 11, 9, 8,255},{ 6, 6, 7,255},{ 17, 13, 10,255},{223,155, 56,255},{255,180, 64,255},{164,114, 43,255},{ 20, 15, 10,255},{ 0, 0, 3,255},{ 16, 13, 10,255},{222,155, 56,255},{222,155, 56,255},{ 16, 13, 10,255},{ 0, 0, 3,255},{ 20, 15, 10,255},{164,114, 43,255},{255,180, 64,255},{223,155, 56,255},{ 17, 13, 10,255},{ 6, 6, 7,255},{ 11, 9, 8,255},{ 8, 7, 8,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 9, 8, 8,255},{ 12, 10, 9,255},{ 5, 4, 6,255},{ 74, 52, 23,255},{222,154, 56,255},{251,174, 62,255},{218,151, 55,255},{112, 78, 31,255},{ 35, 25, 14,255},{223,155, 56,255},{223,155, 56,255},{ 35, 25, 14,255},{112, 78, 31,255},{218,151, 55,255},{251,174, 62,255},{222,154, 56,255},{ 74, 52, 23,255},{ 5, 4, 6,255},{ 12, 10, 9,255},{ 9, 8, 8,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 6, 7, 7,255},{ 9, 8, 8,255},{ 10, 9, 8,255},{ 1, 2, 6,255},{ 20, 15, 10,255},{162,113, 42,255},{249,173, 62,255},{255,184, 66,255},{226,157, 57,255},{231,160, 58,255},{231,160, 58,255},{226,157, 57,255},{255,184, 66,255},{249,173, 62,255},{162,113, 42,255},{ 20, 15, 10,255},{ 1, 2, 6,255},{ 10, 9, 8,255},{ 9, 8, 8,255},{ 6, 7, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 6, 7, 7,255},{ 9, 8, 8,255},{ 12, 10, 9,255},{ 6, 6, 7,255},{ 3, 3, 6,255},{ 70, 50, 22,255},{130, 91, 35,255},{216,150, 54,255},{233,162, 58,255},{233,162, 58,255},{216,150, 54,255},{130, 91, 35,255},{ 70, 50, 22,255},{ 3, 3, 6,255},{ 6, 6, 7,255},{ 12, 10, 9,255},{ 9, 8, 8,255},{ 6, 7, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 6, 7, 7,255},{ 9, 8, 8,255},{ 10, 9, 8,255},{ 10, 9, 8,255},{ 0, 1, 5,255},{ 0, 0, 3,255},{ 29, 22, 12,255},{224,156, 56,255},{224,156, 56,255},{ 29, 22, 12,255},{ 0, 0, 3,255},{ 0, 1, 5,255},{ 10, 9, 8,255},{ 10, 9, 8,255},{ 9, 8, 8,255},{ 6, 7, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 8, 7, 8,255},{ 9, 8, 8,255},{ 13, 11, 9,255},{ 10, 9, 8,255},{ 17, 14, 9,255},{222,154, 56,255},{222,154, 56,255},{ 17, 14, 9,255},{ 10, 9, 8,255},{ 13, 11, 9,255},{ 9, 8, 8,255},{ 8, 7, 8,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 8, 7, 7,255},{ 5, 5, 7,255},{ 21, 17, 11,255},{226,157, 57,255},{226,157, 57,255},{ 21, 17, 11,255},{ 5, 5, 7,255},{ 8, 7, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 7, 7,255},{ 2, 4, 6,255},{ 16, 14, 10,255},{211,147, 53,255},{211,147, 53,255},{ 16, 14, 10,255},{ 2, 4, 6,255},{ 5, 7, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 6, 6, 7,255},{ 17, 14, 10,255},{ 17, 14, 10,255},{ 6, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 2, 4, 6,255},{ 2, 4, 6,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}},
{{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 6, 7, 7,255},{ 6, 7, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255},{ 5, 6, 7,255}}
};
#endif /* GE007_ICON_H */
+60 -14
View File
@@ -63,6 +63,8 @@ static int gl_glsl_version = 130;
static char gl_glsl_version_str[16] = "130";
static GLenum gl_mirror_clamp = GL_MIRROR_CLAMP_TO_EDGE;
static bool gl_es = false;
extern int gfx_rdp_affine; /* D540: defined in gfx_pc.cpp */
extern int gfx_fog_vertex; /* D540: defined in gfx_pc.cpp */
static bool gl_core_profile = false;
static int gfx_opengl_get_max_texture_size() {
@@ -149,13 +151,13 @@ static const char* shader_item_to_str(uint32_t item, bool with_alpha, bool only_
case SHADER_1:
return with_alpha ? "vec4(1.0, 1.0, 1.0, 1.0)" : "vec3(1.0, 1.0, 1.0)";
case SHADER_INPUT_1:
return with_alpha || !inputs_have_alpha ? "vInput1" : "vInput1.rgb";
return with_alpha || !inputs_have_alpha ? "cInput1" : "cInput1.rgb";
case SHADER_INPUT_2:
return with_alpha || !inputs_have_alpha ? "vInput2" : "vInput2.rgb";
return with_alpha || !inputs_have_alpha ? "cInput2" : "cInput2.rgb";
case SHADER_INPUT_3:
return with_alpha || !inputs_have_alpha ? "vInput3" : "vInput3.rgb";
return with_alpha || !inputs_have_alpha ? "cInput3" : "cInput3.rgb";
case SHADER_INPUT_4:
return with_alpha || !inputs_have_alpha ? "vInput4" : "vInput4.rgb";
return with_alpha || !inputs_have_alpha ? "cInput4" : "cInput4.rgb";
case SHADER_TEXEL0:
return with_alpha ? "texVal0" : "texVal0.rgb";
case SHADER_TEXEL0A:
@@ -181,13 +183,13 @@ static const char* shader_item_to_str(uint32_t item, bool with_alpha, bool only_
case SHADER_1:
return "1.0";
case SHADER_INPUT_1:
return "vInput1.a";
return "cInput1.a";
case SHADER_INPUT_2:
return "vInput2.a";
return "cInput2.a";
case SHADER_INPUT_3:
return "vInput3.a";
return "cInput3.a";
case SHADER_INPUT_4:
return "vInput4.a";
return "cInput4.a";
case SHADER_TEXEL0:
return "texVal0.a";
case SHADER_TEXEL0A:
@@ -277,9 +279,16 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad
}
}
}
/* D540: RDP-style screen-affine shade colour (see gfx_opengl_init).
* D543: fog is the per-vertex RSP value by default, which the RDP also
* interpolates screen-affine (it rides in shade alpha), so it shares the
* shade qualifier. GE_FOGPIXEL=1 (D540 per-pixel) needs a perspective
* attribute: it carries fog * w, divided by w in the shader. */
const char *shadeq = gfx_rdp_affine ? "noperspective " : "";
const char *fogq = gfx_fog_vertex ? shadeq : "";
if (cc_features.opt_fog) {
append_line(vs_buf, &vs_len, "INPUT vec4 aFog;");
append_line(vs_buf, &vs_len, "OUTPUT vec4 vFog;");
vs_len += sprintf(vs_buf + vs_len, "%sOUTPUT vec4 vFog;\n", fogq);
num_floats += 4;
}
@@ -291,7 +300,7 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad
for (int i = 0; i < cc_features.num_inputs; i++) {
vs_len += sprintf(vs_buf + vs_len, "INPUT vec%d aInput%d;\n", cc_features.opt_alpha ? 4 : 3, i + 1);
vs_len += sprintf(vs_buf + vs_len, "OUTPUT vec%d vInput%d;\n", cc_features.opt_alpha ? 4 : 3, i + 1);
vs_len += sprintf(vs_buf + vs_len, "%sOUTPUT vec%d vInput%d;\n", shadeq, cc_features.opt_alpha ? 4 : 3, i + 1);
num_floats += cc_features.opt_alpha ? 4 : 3;
}
@@ -360,13 +369,13 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad
}
}
if (cc_features.opt_fog) {
append_line(fs_buf, &fs_len, "INPUT vec4 vFog;");
fs_len += sprintf(fs_buf + fs_len, "%sINPUT vec4 vFog;\n", fogq);
}
if (cc_features.opt_grayscale) {
append_line(fs_buf, &fs_len, "INPUT vec4 vGrayscaleColor;");
}
for (int i = 0; i < cc_features.num_inputs; i++) {
fs_len += sprintf(fs_buf + fs_len, "INPUT vec%d vInput%d;\n", cc_features.opt_alpha ? 4 : 3, i + 1);
fs_len += sprintf(fs_buf + fs_len, "%sINPUT vec%d vInput%d;\n", shadeq, cc_features.opt_alpha ? 4 : 3, i + 1);
}
if (cc_features.used_textures[0]) {
@@ -441,6 +450,15 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad
}
append_line(fs_buf, &fs_len, "void main() {");
/* D548: combiner inputs are 8-bit on the N64 (shade, prim, env), so never
* above 1.0. Under the D540 noperspective shade, GPU clipping of a big
* near-camera triangle can extrapolate them past 1.0; the CC wrap
* emulation below then turned walls into black/white/yellow/blue bands
* (Aztec, near rocket fire). Clamp to the hardware range first. */
for (int i = 0; i < cc_features.num_inputs; i++) {
fs_len += sprintf(fs_buf + fs_len, " vec%d cInput%d = clamp(vInput%d, 0.0, 1.0);\n",
cc_features.opt_alpha ? 4 : 3, i + 1, i + 1);
}
for (int i = 0; i < 2; i++) {
if (cc_features.used_textures[i]) {
@@ -503,10 +521,22 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad
append_line(fs_buf, &fs_len, " texel = clamp(texel, 0.0, 1.0);");
// TODO discard if alpha is 0?
if (cc_features.opt_fog) {
/* D543 default: vFog.a is the per-vertex RSP value, used as-is
* (N64). GE_FOGPIXEL=1 (D540): vFog.a = fog * w, divided by w here =
* exact per-pixel fog from the pixel's own depth. */
append_line(fs_buf, &fs_len, gfx_fog_vertex ? " float fogA = clamp(vFog.a, 0.0, 1.0);"
: " float fogA = clamp(vFog.a * gl_FragCoord.w, 0.0, 1.0);");
/* D540: the D503 snap is OFF by default -- its hard 97% edge swept
* across the ground in tile-shaped chunks as the player walked
* (Deck playtest; maintainer-confirmed with it off). GE_FOGSNAP=1
* restores it. */
static int fogSnap = -1;
if (fogSnap < 0) { const char *e = getenv("GE_FOGSNAP"); fogSnap = (e && e[0] == '1'); }
if (fogSnap) append_line(fs_buf, &fs_len, " if (fogA >= 247.0 / 255.0) fogA = 1.0;");
if (cc_features.opt_alpha) {
append_line(fs_buf, &fs_len, " texel = vec4(mix(texel.rgb, vFog.rgb, vFog.a), texel.a);");
append_line(fs_buf, &fs_len, " texel = vec4(mix(texel.rgb, vFog.rgb, fogA), texel.a);");
} else {
append_line(fs_buf, &fs_len, " texel = mix(texel, vFog.rgb, vFog.a);");
append_line(fs_buf, &fs_len, " texel = mix(texel, vFog.rgb, fogA);");
}
}
@@ -1103,6 +1133,22 @@ static void gfx_opengl_init(void) {
}
sysLogPrintf(LOG_NOTE, "GL: using GLSL version %s", gl_glsl_version_str);
/* D540: the RDP interpolates shade colour affinely in screen space; the
* GPU default is perspective-correct, which squeezed the sky's horizon
* fade into a thin band. noperspective needs desktop GLSL 1.30+ (not
* GLSL ES). GE_SHADEPERSP=1 restores perspective-correct shade (A/B).
* Fog (D543): per-vertex RSP fog by default, as the N64. Its old
* light/dark cycling at the player's feet came from GPU near-clipping of
* behind-camera corners; gfx_sp_tri1 now near-clips on the CPU like the
* RSP. GE_FOGPIXEL=1 = the D540 exact per-pixel fog (A/B). */
{
const char *sp = getenv("GE_SHADEPERSP"), *fp = getenv("GE_FOGPIXEL");
gfx_rdp_affine = (!gl_es && gl_glsl_version >= 130 && !(sp && sp[0] == '1')) ? 1 : 0;
gfx_fog_vertex = (fp && fp[0] == '1') ? 0 : 1; /* D543: per-vertex RSP fog by default */
}
sysLogPrintf(LOG_NOTE, "GL: shade %s, fog %s", gfx_rdp_affine ? "affine (RDP)" : "perspective",
gfx_fog_vertex ? "per-vertex" : "per-pixel");
glGenBuffers(1, &opengl_vbo);
glBindBuffer(GL_ARRAY_BUFFER, opengl_vbo);
+124 -15
View File
@@ -81,12 +81,19 @@ struct NormalColor {
};
};
/* D540: set by the GL backend at init. gfx_rdp_affine: RDP-style
* screen-affine shade colour. gfx_fog_vertex: 1 = per-vertex RSP fog
* (default, D543), 0 = the D540 exact per-pixel fog (GE_FOGPIXEL=1). */
int gfx_rdp_affine = 0;
int gfx_fog_vertex = 0;
struct LoadedVertex {
float x, y, z, w;
float u, v;
struct RGBA color;
uint8_t fog;
uint8_t clip_rej;
float fog_n; /* D540: fog sent to the GPU (0..255): the RSP value (default), or fog * w (GE_FOGPIXEL) */
};
static struct {
@@ -2261,19 +2268,27 @@ static void gfx_sp_vertex(size_t n_vertices, size_t dest_index, const Vtx* verti
}
float winv = 1.0f / w;
if (winv < 0.0f) {
/* D540: the RSP divides by a negative w as-is: a vertex behind
* the camera gets z/w at/beyond the far end of the range (full
* fog), continuously. The old override (winv < 0 -> +32767)
* flipped the sign, so a corner crossing behind the player
* jumped no-fog <-> full-fog and big ground triangles popped
* one at a time (Deck playtest). */
if (!gfx_fog_vertex && winv < 0.0f) {
winv = std::numeric_limits<int16_t>::max();
}
float fog_z = z * winv * rsp.fog_mul + rsp.fog_offset;
d->fog = clampf(fog_z, 0.f, 255.f);
/* D503: the N64's 16-bit framebuffer hid objects ~97% fogged;
* 32-bit output reveals them as silhouettes. Snap to full fog. */
if (d->fog >= 247.f) {
d->fog = 255.f;
}
/* D543 default: the per-vertex RSP value (as the N64; D543's CPU
* near clip lerps it for clip-created vertices like the RSP).
* GE_FOGPIXEL=1 (D540): the GPU gets fog * w (z*mul + w*off,
* linear in clip space) and the shader divides by w -- exact
* per-pixel fog. The D503 full-fog snap is opt-in (GE_FOGSNAP=1). */
d->fog_n = gfx_fog_vertex ? d->fog : d->z * rsp.fog_mul + d->w * rsp.fog_offset;
} else {
d->fog = rdp.fog_color.a;
d->fog_n = gfx_fog_vertex ? d->fog : d->fog * d->w;
}
d->color.a = vcn->a; // can be required for SHADE_ALPHA even if fog is enabled
@@ -2858,14 +2873,106 @@ static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bo
d526_note_emit(videoGetFrameCount(), v_arr, use_alpha ? 1 : 0, use_modulate ? 1 : 0); // D526 census
d75d_note_emit_z(videoGetFrameCount(), v_arr, (used_textures[0] || used_textures[1]) ? 1 : 0, comb->used_textures[0] ? 1 : 0);
}
/* D543: RSP-style near-plane clipping on the CPU. The GPU clips the
* triangle itself against the near plane, but it interpolates the
* noperspective shade colour and the fog value in screen space (D548
* bands, D540 light/dark cycling, D543 bright lit area at Bond's feet),
* so a big ground triangle that crosses the camera plane extrapolates
* them. The RSP instead clips in clip space and lerps every vertex
* attribute linearly there (t = dA / (dA - dB)); do the same, so GL
* receives only fully-in-front triangles and never clips. Skipped for
* rects, G_NO_CLIPPING_EXT, and GE_NEARCLIP=0 (A/B). Fog on a vertex
* the clip creates is re-derived from its own z/w (as gfx_sp_vertex
* does), like the RSP, whose clipper runs new vertices through the same
* vertex-finish code: at the near plane that is a constant, so the haze
* at the player's feet stays static (D553; maintainer compared against
* LLE mupen64plus, cxd4 + angrylion). GE_NEARCLIPFOG=lerp restores the
* old interpolation from the parents (whose behind-the-eye corner flips
* between 0 and 255 fog as w crosses 0 -> the dark/bright popping) for
* A/B. The new vertices live on the stack: rsp.loaded_vertices is
* game-visible. */
static int nearclip_on = -1, nearclip_fog_recompute = -1;
if (nearclip_on < 0) {
const char* e = getenv("GE_NEARCLIP");
nearclip_on = !(e && e[0] == '0');
const char* f = getenv("GE_NEARCLIPFOG");
nearclip_fog_recompute = (f && strcmp(f, "lerp") == 0) ? 0 : 1; /* D553: recompute by default */
}
struct LoadedVertex* emit_v[6] = { v1, v2, v3, NULL, NULL, NULL };
int n_out_tris = 1;
struct LoadedVertex nc_vtx[2];
if (nearclip_on && !is_rect && (rsp.extra_geometry_mode & G_NO_CLIPPING_EXT) == 0) {
const float NEAR_EPS = 1e-4f;
float d[3];
int n_in = 0;
for (int i = 0; i < 3; i++) {
float z = v_arr[i]->z, w = v_arr[i]->w;
d[i] = v_arr[i]->z + v_arr[i]->w - NEAR_EPS;
if (d[i] > 0.0f) n_in++;
}
if (n_in == 0) {
return; // entirely behind the near plane
}
if (n_in < 3) {
struct LoadedVertex* poly[4];
int n_poly = 0, n_new = 0;
for (int i = 0; i < 3; i++) {
int j = (i + 1) % 3;
struct LoadedVertex* a = v_arr[i];
struct LoadedVertex* b = v_arr[j];
if (d[i] > 0.0f) poly[n_poly++] = a;
if ((d[i] > 0.0f) != (d[j] > 0.0f)) {
float t = d[i] / (d[i] - d[j]);
struct LoadedVertex* o = &nc_vtx[n_new++];
o->x = a->x + (b->x - a->x) * t;
o->y = a->y + (b->y - a->y) * t;
o->z = a->z + (b->z - a->z) * t;
o->w = a->w + (b->w - a->w) * t;
o->u = a->u + (b->u - a->u) * t;
o->v = a->v + (b->v - a->v) * t;
o->color.r = (uint8_t)clampf(floorf(a->color.r + (b->color.r - a->color.r) * t + 0.5f), 0.f, 255.f);
o->color.g = (uint8_t)clampf(floorf(a->color.g + (b->color.g - a->color.g) * t + 0.5f), 0.f, 255.f);
o->color.b = (uint8_t)clampf(floorf(a->color.b + (b->color.b - a->color.b) * t + 0.5f), 0.f, 255.f);
o->color.a = (uint8_t)clampf(floorf(a->color.a + (b->color.a - a->color.a) * t + 0.5f), 0.f, 255.f);
o->clip_rej = 0;
o->fog_n = a->fog_n + (b->fog_n - a->fog_n) * t;
if (nearclip_fog_recompute) {
if (rsp.geometry_mode & G_FOG) {
float fw = o->w;
if (fabsf(fw) < 0.001f) fw = 0.001f;
float winv = 1.0f / fw;
if (!gfx_fog_vertex && winv < 0.0f) winv = std::numeric_limits<int16_t>::max();
o->fog = clampf(o->z * winv * rsp.fog_mul + rsp.fog_offset, 0.f, 255.f);
} else {
o->fog = a->fog;
}
} else {
o->fog = (uint8_t)clampf(floorf(a->fog + ((float)b->fog - (float)a->fog) * t + 0.5f), 0.f, 255.f);
}
if (gfx_fog_vertex) o->fog_n = o->fog; // as gfx_sp_vertex
poly[n_poly++] = o;
}
}
// n_poly is 3 or 4 here (one or two vertices inside)
emit_v[0] = poly[0]; emit_v[1] = poly[1]; emit_v[2] = poly[2];
if (n_poly == 4) {
emit_v[3] = poly[0]; emit_v[4] = poly[2]; emit_v[5] = poly[3];
n_out_tris = 2;
}
}
}
const int n_emit_verts = n_out_tris * 3;
if (buf_vbo_num_tris + n_out_tris > MAX_BUFFERED) {
gfx_flush(); // buf_vbo holds MAX_BUFFERED triangles; make room for 1-2
}
for (int i = 0; i < n_emit_verts; i++) {
struct LoadedVertex* vi = emit_v[i];
float z = vi->z, w = vi->w;
if (clip_parameters.z_is_from_0_to_1) {
z = (z + w) / 2.0f;
}
buf_vbo[buf_vbo_len++] = v_arr[i]->x;
buf_vbo[buf_vbo_len++] = clip_parameters.invert_y ? -v_arr[i]->y : v_arr[i]->y;
buf_vbo[buf_vbo_len++] = vi->x;
buf_vbo[buf_vbo_len++] = clip_parameters.invert_y ? -vi->y : vi->y;
buf_vbo[buf_vbo_len++] = z;
buf_vbo[buf_vbo_len++] = w;
@@ -2877,8 +2984,8 @@ static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bo
// TODO: fix this; for now just ignore smaller mips
const uint32_t tile = gfx_lod_tile_offset(t);
float u = v_arr[i]->u / 32.0f;
float v = v_arr[i]->v / 32.0f;
float u = vi->u / 32.0f;
float v = vi->v / 32.0f;
int shifts = rdp.texture_tile[rdp.first_tile_index + tile].shifts;
int shiftt = rdp.texture_tile[rdp.first_tile_index + tile].shiftt;
@@ -2955,7 +3062,7 @@ static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bo
buf_vbo[buf_vbo_len++] = rdp.fog_color.r / 255.0f;
buf_vbo[buf_vbo_len++] = rdp.fog_color.g / 255.0f;
buf_vbo[buf_vbo_len++] = rdp.fog_color.b / 255.0f;
buf_vbo[buf_vbo_len++] = v_arr[i]->fog / 255.0f; // fog factor
buf_vbo[buf_vbo_len++] = vi->fog_n / 255.0f; // D540: see LoadedVertex.fog_n
}
if (use_grayscale) {
@@ -2976,10 +3083,10 @@ static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bo
color = &rdp.prim_color;
break;
case G_CCMUX_SHADE:
color = &v_arr[i]->color;
color = &vi->color;
break;
case G_CCMUX_SHADE_ALPHA:
tmp.r = tmp.g = tmp.b = v_arr[i]->color.a;
tmp.r = tmp.g = tmp.b = vi->color.a;
color = &tmp;
break;
case G_CCMUX_ENVIRONMENT:
@@ -3032,7 +3139,8 @@ static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bo
}
}
if (++buf_vbo_num_tris == MAX_BUFFERED) {
buf_vbo_num_tris += n_out_tris;
if (buf_vbo_num_tris >= MAX_BUFFERED) {
gfx_flush();
}
}
@@ -3943,6 +4051,7 @@ static void gfx_sp_vertex_float(size_t n_vertices, size_t dest_index, const Port
d->z = z;
d->w = w;
d->fog = rdp.fog_color.a;
d->fog_n = gfx_fog_vertex ? d->fog : d->fog * w;
}
}
+27
View File
@@ -8,6 +8,7 @@
#include "gfx_window_manager_api.h"
#include "gfx_screen_config.h"
#include "input.h"
#include "ge007_icon.h"
extern "C" void videoRequestQuit(const char *why); // port/src/video.c (D344)
extern "C" void inputRequestRescan(void); // port/src/input.c (D450)
@@ -228,6 +229,32 @@ static void gfx_sdl_init(const struct GfxWindowInitSettings *set) {
SDL_GL_MakeCurrent(wnd, ctx);
SDL_GL_SetSwapInterval(1);
// D550: window / taskbar icon = the project mark (generated by
// tools_pc/make_icon.py into ge007_icon.h; R,G,B,A byte order per
// pixel, portable -- packed here against the surface's masks).
{
const Uint32 rmask = 0x000000ff, gmask = 0x0000ff00,
bmask = 0x00ff0000, amask = 0xff000000;
SDL_Surface *icon = SDL_CreateRGBSurface(
0, GE007_ICON_SIZE, GE007_ICON_SIZE, 32, rmask, gmask, bmask, amask);
if (icon) {
for (int y = 0; y < GE007_ICON_SIZE; ++y) {
for (int x = 0; x < GE007_ICON_SIZE; ++x) {
const uint8_t *p = ge007_icon_rgba[y][x];
// Shift each channel into its mask's byte lane before
// the & (an unshifted p[1] & 0x0000ff00 is always 0):
const Uint32 v = ((Uint32)p[0] & rmask) | ((Uint32)(p[1] << 8) & gmask) |
((Uint32)(p[2] << 16) & bmask) | ((Uint32)(p[3] << 24) & amask);
*(Uint32 *)((Uint8 *)icon->pixels + y * icon->pitch + x * 4) = v;
}
}
SDL_SetWindowIcon(wnd, icon);
SDL_FreeSurface(icon);
} else {
sysLogPrintf(LOG_NOTE, "SDL: could not create the window icon surface: %s", SDL_GetError());
}
}
SDL_ShowWindow(wnd);
qpc_freq = SDL_GetPerformanceFrequency();
+2
View File
@@ -59,6 +59,8 @@ int inputReleaseCapture(void);
/* F10 options overlay: force the OS cursor free + visible while the overlay
* owns the mouse. Safe to call every poll. */
void inputSuspendForOverlay(void);
int inputFrontEndCursorUiFrac(double *fx, double *fy, double *xscale); /* D555 */
int inputCrosshairCursorOn(void); /* D555: Input.CrosshairCursor (overlay draws the game crosshair as the pointer) */
/* Raw gamepad state for controller idx (the F10 options overlay's gamepad
* navigation). Unaffected by the overlay's pad-swallow, which happens at our
+15
View File
@@ -0,0 +1,15 @@
#ifndef PORT_UPDATECHECK_H
#define PORT_UPDATECHECK_H
/*
* Opt-in "check for updates" (Game.CheckUpdates, default OFF).
*
* updateCheckStart() does nothing at all unless the ini value is 1 -- no
* thread, no DNS, no library init. When enabled it spawns one detached
* background thread that makes a single HTTPS GET to GitHub's releases API.
*/
void updateCheckStart(void); /* call once, after configLoad() */
const char *updateCheckAvailable(void); /* newer tag (e.g. "vX.Y.Z") or NULL */
void updateCheckOpenReleases(void); /* SDL_OpenURL on the releases page */
#endif
+1
View File
@@ -4,6 +4,7 @@
/* Generated by CMake from versioninfo.h.in — do not edit. */
#define GE007_VERSION_HASH "@VERSION_HASH@"
#define GE007_VERSION "@GE007_VERSION@"
#define GE007_VERSION_CODENAME "@VERSION_CODENAME@"
#define GE007_ORIGIN_URL "@ORIGIN_URL@"
#define GE007_TARGET_PLATFORM "@TARGET_PLATFORM@"
+72 -7
View File
@@ -122,7 +122,7 @@ extern void gunRequestHandWeaponChange(enum GUNHAND hand, s32 nextWeapon, s32 cy
#define GE_CONT_F 0x0001 /* C-right */
#define MAX_PADS 4
#define STICK_DEADZONE 7000
#define STICK_DEADZONE 7500 /* 25 %; D546 (was 7000 = 23 %) */
#define STICK_MAX 80
/* Front-end pointer mode: GE's menu cursor ("crosshair") is stick-driven
@@ -791,6 +791,14 @@ static void mouseRequestCursor(int show)
SDL_AtomicUnlock(&s_mouseReqLock);
}
/* D555 (reworked 2026-10-06): Input.CrosshairCursor -- while the F10 overlay /
* PC Options is open the OS cursor is HIDDEN and optionsoverlay.c draws the
* game's own crosshair sprite at the mouse position, last in its display list
* (so it is above the card). Off: the OS arrow over the overlay, as before. */
static int cfgCrosshairCursor = 1;
int inputCrosshairCursorOn(void) { return cfgCrosshairCursor; }
static int s_cursorShown = -1; /* host thread only: last SDL_ShowCursor state applied */
/* Host thread only (videoPumpEvents, inputInit). */
void inputApplyMouseRequests(void)
{
@@ -806,9 +814,15 @@ void inputApplyMouseRequests(void)
SDL_GetRelativeMouseState(NULL, NULL); /* drain the accumulated jump */
}
}
if (cur >= 0 && cur != s_appCursor) {
s_appCursor = cur;
SDL_ShowCursor(cur ? SDL_ENABLE : SDL_DISABLE);
if (cur >= 0) s_appCursor = cur;
if (s_appCursor >= 0) {
/* D555: with the crosshair pointer on, the overlay draws its own
* pointer, so the OS cursor stays hidden while it is open. */
const int show = s_appCursor == 1 && !(cfgCrosshairCursor && optionsOverlayIsOpen());
if (show != s_cursorShown) {
s_cursorShown = show;
SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE);
}
}
}
@@ -1029,6 +1043,7 @@ static int padSouthpaw = 0; /* 1 = swap fire (G) / grenade (R)
static int padLookSmooth = 0; /* right-stick look low-pass strength, 0-10 (0 = off, 10 = max) */
static int padTriggerPct = 25; /* trigger press point, % of travel (~30*256) */
static int padLookInvertY = 0; /* 1 = invert right-stick (look) Y */
static int inputDefaultsRev = 0; /* D546: 1 once the deadzone default migration ran */
/* Per-pad look-smoothing EMA state (Wave A item 5), one X/Y pair per pad. Reset
* on hot-unplug (inputRescanPads) so a smoothed value can't stick after the
* stick snaps back; the low-pass otherwise recentres it toward 0 on its own. */
@@ -1516,7 +1531,7 @@ static void inputMigrateBinds(void); /* D380; after configLoad */
static void inputBindingProbe(void); /* D383/D384; opt-in, restores ini state */
static int bindsVersion = 0; /* D380/D386: versioned migration of effective ini binds */
static int crouchMode = 0; /* 0 = hold, 1 = toggle (latched crouch input) */
static int crouchMode = 1; /* 0 = hold, 1 = toggle (latched crouch input); D556: toggle by default (existing ini values kept) */
static void padMapSelfTest(void);
int inputInit(void)
@@ -1553,6 +1568,14 @@ int inputInit(void)
* Runs before any configSave, so the saved L/R values are always valid. */
if (padDeadzoneL == PAD_DZ_UNSET) padDeadzoneL = padDeadzone;
if (padDeadzoneR == PAD_DZ_UNSET) padDeadzoneR = padDeadzone;
/* D546: one-time move of the untouched old deadzone default (7000 = 23 %)
* to the standardized 25 % (7500); a value the player set is kept. */
if (inputDefaultsRev < 1) {
if (padDeadzone == 7000) padDeadzone = STICK_DEADZONE;
if (padDeadzoneL == 7000) padDeadzoneL = STICK_DEADZONE;
if (padDeadzoneR == 7000) padDeadzoneR = STICK_DEADZONE;
inputDefaultsRev = 1;
}
if (getenv("GE_BINDPROBE")) inputBindingProbe();
sysLogPrintf(LOG_INFO, "input: PC bindings, crouch mode %s",
@@ -2172,8 +2195,9 @@ static unsigned inputComputePadSlot(int idx, signed char *stick_x, signed char *
}
optionsOverlayHandleInput();
if (mouseEnabled && current_menu != GE_MENU_RUN_STAGE && current_menu != GE_MENU_INVALID) {
overlayFrontEndCrosshair(); /* D519: crosshair stays the pointer over the overlay */
mouseRequestCursor(0); /* ...so hide the OS cursor (one pointer, not two) */
overlayFrontEndCrosshair(); /* D519: the game cursor tracks the mouse (D555: the overlay pointer is drawn exactly over it) */
/* D544: the OS cursor stays visible (inputSuspendForOverlay) -- the crosshair
* draws under the overlay box, so it alone could not point at F10 rows. */
}
if (stick_x) *stick_x = 0;
if (stick_y) *stick_y = 0;
@@ -2897,6 +2921,23 @@ static unsigned inputComputePadSlot(int idx, signed char *stick_x, signed char *
}
}
/* D541: front-end release hold-off. One physical press can reach the menu
* through two OR'd sources a few ms apart -- on a Steam Deck in Desktop
* Mode, Steam's desktop layout types Enter for A (Escape for B) while SDL
* also reads the raw pad -- and a gap between them is a second edge, so
* one A press advanced two screens. A menu button only counts as released
* after ~70 ms up; press edges are untouched. Menus only. */
if (idx >= 0 && idx < MAX_PADS &&
current_menu != GE_MENU_RUN_STAGE && current_menu != GE_MENU_INVALID) {
static Uint32 s_menuBtnDownAt[MAX_PADS][3];
static const u16 kHoldBits[3] = { GE_CONT_A, GE_CONT_B, GE_CONT_START };
Uint32 now = SDL_GetTicks();
for (int k = 0; k < 3; k++) {
if (button & kHoldBits[k]) s_menuBtnDownAt[idx][k] = now;
else if (s_menuBtnDownAt[idx][k] && now - s_menuBtnDownAt[idx][k] < 70) button |= kHoldBits[k];
}
}
if (idx == 0 && scriptIsActive(0)) {
/* D263: apply BEFORE the stick is written out -- scripted stick
* tokens (SUP/SDOWN/SLEFT/SRIGHT) used to be discarded. */
@@ -3134,6 +3175,25 @@ static void overlayFrontEndCrosshair(void)
cursor_v_pos = (float)(v < loV ? loV : v > hiV ? hiV : v);
}
/* D555 rework: INVERSE of overlayFrontEndCrosshair's mapping. Where the game's
* front-end cursor (cursor_h_pos/cursor_v_pos, which frontDrawCursor draws at)
* lands, as fractions of the UI screen rect (the rect the overlay's full-canvas
* DL maps to), plus the x scale of the front-end canvas inside it (D335
* native-widescreen pillarbox: vis, else 1). 0 = not available. */
int inputFrontEndCursorUiFrac(double *fx, double *fy, double *xscale)
{
double sw = getPlayer_c_screenwidth(), sh = getPlayer_c_screenheight();
double sl = getPlayer_c_screenleft(), st = getPlayer_c_screentop();
if (!(sw > 200.0 && sw < 2000.0 && sh > 150.0 && sh < 2000.0)) return 0;
double x = ((double)cursor_h_pos - sl) / sw, y = ((double)cursor_v_pos - st) / sh;
double vis = 1.0;
extern f32 portNativeAspect(void);
f32 na = portNativeAspect();
if (na > (4.0f / 3.0f)) { vis = (4.0 / 3.0) / (double)na; x = x * vis + (1.0 - vis) * 0.5; }
*fx = x; *fy = y; *xscale = vis;
return 1;
}
void inputSuspendForOverlay(void)
{
if (mouseGrabbed) {
@@ -3141,6 +3201,9 @@ void inputSuspendForOverlay(void)
mouseRequestRelative(0);
mouseDX = mouseDY = 0.0;
}
/* D544: the OS cursor is always shown while the overlay is open, so the
* pointer draws OVER the menu (maintainer). The front-end path no longer
* hides it, which also removes the show/hide race that flickered. */
mouseRequestCursor(1);
}
@@ -3834,6 +3897,7 @@ PD_CONSTRUCTOR static void inputConfigInit(void)
}
configRegisterInt("Input.MPMode", &mpInputMode, 0, 2); /* D416: 0 Auto, 1 PadsOnly, 2 KbmP1 */
configRegisterInt("Input.MouseEnabled", &mouseEnabled, 0, 1);
configRegisterInt("Input.CrosshairCursor", &cfgCrosshairCursor, 0, 1); /* D555 */
configRegisterInt("Input.MouseAimSpeed", &mouseAimSpeed, 1, 500);
configRegisterInt("Input.PdMouseAim", &pdMouseAim, 0, 1); /* legacy -> AimStyle 1 */
configRegisterInt("Input.AimStyle", &aimStyleLegacy, 0, 2); /* D333 legacy -> AimMode */
@@ -3876,6 +3940,7 @@ PD_CONSTRUCTOR static void inputConfigInit(void)
configRegisterInt("Input.PadDeadzone", &padDeadzone, 0, 30000); /* legacy; Wave A migrates to L/R */
configRegisterInt("Input.PadDeadzoneL", &padDeadzoneL, 0, 30000); /* Wave A: left (movement) stick */
configRegisterInt("Input.PadDeadzoneR", &padDeadzoneR, 0, 30000); /* Wave A: right (look) stick */
configRegisterInt("Input.DefaultsRev", &inputDefaultsRev, 0, 1); /* D546 */
configRegisterInt("Input.PadLookSensX", &padLookSensX, 25, 200); /* Wave A: look horizontal sensitivity (%) */
configRegisterInt("Input.PadLookSensY", &padLookSensY, 25, 200); /* Wave A: look vertical (pitch) sensitivity (%) */
configRegisterInt("Input.PadSouthpaw", &padSouthpaw, 0, 1); /* Wave A: swap fire/grenade triggers */
+30 -13
View File
@@ -24,6 +24,7 @@
#include "platform.h"
#include "system.h"
#include "config.h"
#include "updatecheck.h"
#include "fs.h"
#include "romdata.h"
#include "dram.h"
@@ -95,6 +96,33 @@ static void portAtExit(void)
}
}
/* D257/D442: Game.AllUnlocked applies live. Called at boot and on every F10
* row change. On seeds the game's own RAM unlock flags (mission select offers
* every solo level at every difficulty plus 007 mode); Off clears only the
* flags this setting seeded, so GE_UNLOCK_ALL / debug-menu unlocks survive.
* Port-layer memory writes only (AGENTS rule 2); the cheat menu and COMPLETED
* checks come from D442's query-time hooks in file2.c, which read
* portAllUnlocked live. The debug flags exist only with LEFTOVERDEBUG (not
* PAL); the file2.c hooks cover All unlocked everywhere. */
void portAllUnlockedApply(void)
{
#if defined(LEFTOVERDEBUG)
extern s32 portAllUnlocked; /* port/src/video.c */
extern s32 debug_enable_all_levels_flag; /* src/game/debugmenu_handler.c */
extern s32 debug_007_unlock_flag; /* ditto */
static int s_seededLevels, s_seeded007;
if (portAllUnlocked) {
if (!debug_enable_all_levels_flag) { debug_enable_all_levels_flag = 1; s_seededLevels = 1; }
if (!debug_007_unlock_flag) { debug_007_unlock_flag = 1; s_seeded007 = 1; }
} else {
if (s_seededLevels) { debug_enable_all_levels_flag = 0; s_seededLevels = 0; }
if (s_seeded007) { debug_007_unlock_flag = 0; s_seeded007 = 0; }
}
sysLogPrintf(LOG_INFO, "all-unlocked: Game.AllUnlocked=%d (levels flag %d, 007 flag %d)",
(int)portAllUnlocked, (int)debug_enable_all_levels_flag, (int)debug_007_unlock_flag);
#endif
}
int main(int argc, char **argv)
{
sysSetArgs(argc, argv);
@@ -134,6 +162,7 @@ int main(int argc, char **argv)
* skips it for good. */
configLoad();
videoApplySteamDeckPreset();
updateCheckStart(); /* opt-in (Game.CheckUpdates, default off); no-op otherwise */
atexit(portAtExit); /* persist config + window geometry on clean exit */
/* 1a. D257: Game.AllUnlocked (default OFF; F10 'All unlocked' enables)
@@ -151,19 +180,7 @@ int main(int argc, char **argv)
* COMPLETED check come from the query-time hooks in file2.c
* (fileGetIsCheatUnlocked, fileIsStageUnlockedAtDifficulty); the
* save is never patched. */
#if defined(LEFTOVERDEBUG) /* the debug unlock flags only exist with LEFTOVERDEBUG (not PAL); D442's query-time hooks cover All unlocked everywhere */
{
extern s32 portAllUnlocked; /* port/src/video.c */
extern s32 debug_enable_all_levels_flag; /* src/game/debugmenu_handler.c */
extern s32 debug_007_unlock_flag; /* ditto */
if (portAllUnlocked) {
debug_enable_all_levels_flag = 1;
debug_007_unlock_flag = 1;
sysLogPrintf(LOG_INFO, "all-unlocked: RAM unlock flags seeded "
"(Game.AllUnlocked=1)");
}
}
#endif
portAllUnlockedApply();
/* 2. Load the ROM and map segments. */
if (romdataInit() != 0) {
+539 -221
View File
File diff suppressed because it is too large. Load diff
+221
View File
@@ -0,0 +1,221 @@
/*
* Opt-in update check. See port/include/updatecheck.h.
*
* Off by default (Game.CheckUpdates = 0): the ONLY entry point that can
* reach the network, updateCheckStart(), returns before creating a thread
* when the setting is 0. When on: one HTTPS GET per launch, on a detached
* thread, ~5 s timeout, silent on any failure (one log line). Windows uses
* WinHTTP, LoadLibrary'd at runtime (never linked); elsewhere `curl` is run
* through popen() (skipped if missing).
* No telemetry: the request carries only a fixed User-Agent.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#endif
#include <SDL.h>
#include "platform.h"
#include "system.h"
#include "config.h"
#include "versioninfo.h"
#include "updatecheck.h"
#define UC_HOST "api.github.com"
/* /releases/latest deliberately excludes drafts and pre-releases (maintainer
* decision): a pre-release never prompts anyone to update. */
#define UC_PATH "/repos/jkdansereau/goldeneye-pc-port/releases/latest"
#define UC_URL "https://" UC_HOST UC_PATH
#define UC_RELEASES "https://github.com/jkdansereau/goldeneye-pc-port/releases"
#define UC_UA "ge007-pc-port"
#define UC_BODY_MAX (64 * 1024)
static int cfgCheckUpdates = 0; /* Game.CheckUpdates, default OFF */
static SDL_atomic_t s_ready; /* 1 once s_tag is published */
static char s_tag[32];
PD_CONSTRUCTOR static void updateCheckConfigInit(void)
{
configRegisterInt("Game.CheckUpdates", &cfgCheckUpdates, 0, 1);
}
/* Parse "vMAJOR.MINOR.PATCH" (missing parts = 0, suffix ignored). */
static int parseVer(const char *s, int v[3])
{
v[0] = v[1] = v[2] = 0;
if (*s == 'v' || *s == 'V') s++;
if (*s < '0' || *s > '9') return 0;
for (int i = 0; i < 3; i++) {
if (*s < '0' || *s > '9') break;
v[i] = (int)strtol(s, (char **)&s, 10);
if (*s != '.') break;
s++;
}
return 1;
}
static int versionNewer(const char *tag, const char *running)
{
int a[3], b[3];
if (!parseVer(tag, a) || !parseVer(running, b)) return 0;
for (int i = 0; i < 3; i++) {
if (a[i] != b[i]) return a[i] > b[i];
}
return 0;
}
/* Minimal scan for "tag_name": "<value>". */
static int extractTag(const char *body, char *out, size_t n)
{
const char *p = strstr(body, "\"tag_name\"");
if (!p) return 0;
p += 10;
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
if (*p != ':') return 0;
p++;
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
if (*p != '"') return 0;
p++;
size_t i = 0;
/* Tag characters only (no control/ANSI bytes reach the log). */
while (*p && *p != '"' && i + 1 < n) {
char c = *p++;
if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
c == '.' || c == '_' || c == '-')) return 0;
out[i++] = c;
}
if (*p != '"' || i == 0) return 0;
out[i] = 0;
return 1;
}
#ifdef _WIN32
/* winhttp.dll is loaded at runtime, only here (after the Game.CheckUpdates
* gate), so the exe's import table never lists it and nothing networking-
* related is mapped into the process while the setting is off. */
typedef LPVOID HINT_;
typedef HINT_ (WINAPI *PFN_Open)(LPCWSTR, DWORD, LPCWSTR, LPCWSTR, DWORD);
typedef int (WINAPI *PFN_SetTimeouts)(HINT_, int, int, int, int);
typedef HINT_ (WINAPI *PFN_Connect)(HINT_, LPCWSTR, WORD, DWORD);
typedef HINT_ (WINAPI *PFN_OpenRequest)(HINT_, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR *, DWORD);
typedef int (WINAPI *PFN_Send)(HINT_, LPCWSTR, DWORD, LPVOID, DWORD, DWORD, DWORD_PTR);
typedef int (WINAPI *PFN_Recv)(HINT_, LPVOID);
typedef int (WINAPI *PFN_Query)(HINT_, DWORD, LPCWSTR, LPVOID, LPDWORD, LPDWORD);
typedef int (WINAPI *PFN_Read)(HINT_, LPVOID, DWORD, LPDWORD);
typedef int (WINAPI *PFN_Close)(HINT_);
#define UC_WH_ACCESS_DEFAULT_PROXY 0
#define UC_WH_FLAG_SECURE 0x00800000
#define UC_WH_QUERY_STATUS_CODE 19
#define UC_WH_QUERY_FLAG_NUMBER 0x20000000
#endif
/* Fetch the response body into buf (NUL-terminated). Returns 1 on success. */
static int fetchLatest(char *buf, size_t cap)
{
size_t len = 0;
#ifdef _WIN32
int ok = 0;
/* System32 only: never a winhttp.dll planted next to the exe (D551 review). */
#ifndef LOAD_LIBRARY_SEARCH_SYSTEM32
#define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800
#endif
HMODULE dll = LoadLibraryExW(L"winhttp.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
if (!dll) return 0;
PFN_Open pOpen = (PFN_Open)(void *)GetProcAddress(dll, "WinHttpOpen");
PFN_SetTimeouts pTimeout = (PFN_SetTimeouts)(void *)GetProcAddress(dll, "WinHttpSetTimeouts");
PFN_Connect pConnect = (PFN_Connect)(void *)GetProcAddress(dll, "WinHttpConnect");
PFN_OpenRequest pOpenReq = (PFN_OpenRequest)(void *)GetProcAddress(dll, "WinHttpOpenRequest");
PFN_Send pSend = (PFN_Send)(void *)GetProcAddress(dll, "WinHttpSendRequest");
PFN_Recv pRecv = (PFN_Recv)(void *)GetProcAddress(dll, "WinHttpReceiveResponse");
PFN_Query pQuery = (PFN_Query)(void *)GetProcAddress(dll, "WinHttpQueryHeaders");
PFN_Read pRead = (PFN_Read)(void *)GetProcAddress(dll, "WinHttpReadData");
PFN_Close pClose = (PFN_Close)(void *)GetProcAddress(dll, "WinHttpCloseHandle");
if (!pOpen || !pTimeout || !pConnect || !pOpenReq || !pSend || !pRecv ||
!pQuery || !pRead || !pClose) {
FreeLibrary(dll);
return 0;
}
HINT_ ses = pOpen(L"" UC_UA, UC_WH_ACCESS_DEFAULT_PROXY, NULL, NULL, 0);
if (!ses) { FreeLibrary(dll); return 0; }
/* resolve, connect, send, receive: 5 s each */
pTimeout(ses, 5000, 5000, 5000, 5000);
HINT_ con = pConnect(ses, L"" UC_HOST, 443, 0);
HINT_ req = con ? pOpenReq(con, L"GET", L"" UC_PATH, NULL, NULL, NULL,
UC_WH_FLAG_SECURE) : NULL;
if (req &&
pSend(req, L"Accept: application/vnd.github+json\r\n", (DWORD)-1L, NULL, 0, 0, 0) &&
pRecv(req, NULL)) {
DWORD status = 0, sz = sizeof(status);
if (pQuery(req, UC_WH_QUERY_STATUS_CODE | UC_WH_QUERY_FLAG_NUMBER, NULL,
&status, &sz, NULL) && status == 200) {
for (;;) {
DWORD got = 0;
if (len + 1 >= cap) break;
if (!pRead(req, buf + len, (DWORD)(cap - 1 - len), &got) || got == 0)
break;
len += got;
}
ok = len > 0;
}
}
if (req) pClose(req);
if (con) pClose(con);
pClose(ses);
FreeLibrary(dll);
buf[len] = 0;
return ok;
#else
FILE *f = popen("curl -q -fsS --proto =https --max-time 5 -A '" UC_UA "' '" UC_URL "' 2>/dev/null", "r");
if (!f) return 0;
size_t got;
while (len + 1 < cap && (got = fread(buf + len, 1, cap - 1 - len, f)) > 0) len += got;
int rc = pclose(f); /* nonzero if curl is missing (127) or failed */
buf[len] = 0;
return rc == 0 && len > 0;
#endif
}
static int updateCheckThread(void *unused)
{
(void)unused;
static char body[UC_BODY_MAX]; /* the thread runs at most once per launch */
char tag[32];
if (fetchLatest(body, UC_BODY_MAX) && extractTag(body, tag, sizeof(tag))) {
if (versionNewer(tag, GE007_VERSION)) {
snprintf(s_tag, sizeof(s_tag), "%s", tag);
SDL_AtomicSet(&s_ready, 1); /* full barrier: s_tag visible first */
sysLogPrintf(LOG_INFO, "update check: %s is available (running v%s); %s",
tag, GE007_VERSION, UC_RELEASES);
} else {
sysLogPrintf(LOG_INFO, "update check: up to date (latest %s, running v%s)",
tag, GE007_VERSION);
}
} else {
sysLogPrintf(LOG_INFO, "update check: no result (offline or unavailable)");
}
return 0;
}
void updateCheckStart(void)
{
if (!cfgCheckUpdates) return; /* OFF by default: no thread, no network, no init */
SDL_Thread *t = SDL_CreateThread(updateCheckThread, "ge007-updatecheck", NULL);
if (t) SDL_DetachThread(t);
else sysLogPrintf(LOG_INFO, "update check: could not start thread");
}
const char *updateCheckAvailable(void)
{
return SDL_AtomicGet(&s_ready) ? s_tag : NULL;
}
void updateCheckOpenReleases(void)
{
if (SDL_OpenURL(UC_RELEASES) != 0)
sysLogPrintf(LOG_INFO, "update check: could not open browser: %s", SDL_GetError());
}
+46 -26
View File
@@ -63,13 +63,14 @@ static int cfgTexFilter = 1; /* 0 = nearest, 1 = bilinear (default), 2 = N
static int cfgFixMipTex = 1; /* RC2: clip mip-contaminated texture uploads to base height */
static int cfgDetailBaseTile = 1; /* D236: TEXTURETYPE_DETAIL -> sample the base image, not the detail tile */
static int cfgWrapFix = 0; /* D74 sub-tile UV pre-wrap + RC3/D167 non-PoT mask-period wrap (opt-in; GE_WRAPFIX env overrides) */
static int cfgFovScale = 100; /* D211: percent of the original vertical FOV; 100 = unchanged (byte-identical) */
static float cfgFovScale = 100.0f; /* D211: percent of the original vertical FOV; 100 = unchanged (byte-identical). D546: float so the 5-degree UI steps are exact */
static int cfgWidescreenAuto = 1; /* WIDESCREEN-FOV-PLAN Phase 4: auto-scale vertical FOV by window aspect ratio; on by default, no-op at 4:3 */
static int cfgNativeWidescreen = 1; /* D334 (WIDESCREEN-FOV-PLAN Phase 2): project the world at the real window aspect (Hor+); no-op at 4:3 */
static int cfgHudScale = 100; /* D226: HUD text/ammo scale %, 100 = original (no emission) */
static int cfgDrawDistance = 250; /* % of authored far clip; 250% is the UI's 50/100 midpoint */
static int cfgDrawDistance = 200; /* % of authored far clip; D546: 2.0x default (was 2.5x) */
static int cfgFogDistance = 100; /* D540: % of the authored fog distances; 100 = N64 */
static int cfgDrawDistanceAutoFov = 0; /* legacy ini option, no longer exposed in the menu */
static int cfgLodDistance = 250; /* % of authored geometry LOD distance; 50/100 in the UI */
static int cfgLodDistance = 200; /* % of authored geometry LOD distance; D546: 2.0x default (was 2.5x) */
static int cfgLodDistanceAutoFov = 0; /* legacy ini option, no longer exposed in the menu */
static int cfgAniso = 4; /* D212: anisotropic filtering samples; 4 = the value fast3d already applied (no visual delta at default) */
static int cfgSafeAreaCrop = 1; /* crop the N64 TV-overscan safe-area margin (visible as black top/bottom bars on PC) instead of showing it; on by default */
@@ -78,6 +79,7 @@ static int cfgFullscreen = 0; /* 0 = windowed, 1 = borderless fullscreen
static int cfgFullscreenMode = 0; /* D511: fullscreen flavour, 0 = borderless desktop, 1 = exclusive */
static int cfgDeckPresetApplied = 0; /* D283: 1 once the Steam Deck preset has been considered */
static int cfgLowEndConsidered = 0; /* D482: 1 once the low-end GPU defaults have been considered */
static int cfgDefaultsRev = 0; /* D546: 1 once the 2.5x -> 2.0x draw/LOD default migration ran */
/*
* [Window] persistence. W/H = 0 -> auto (gfx_sdl2 fits a 4:3 window into ~85%
@@ -208,14 +210,14 @@ void portCrosshairPreview(s32 *r, s32 *g, s32 *b)
}
/* D257: Game.AllUnlocked — everything-unlocked goodie, OFF by default
* (faithful N64 progression: levels unlock as you complete them). Consumed
* once at startup by main.c, which sets the game's own RAM unlock flags
* (faithful N64 progression: levels unlock as you complete them). Applied
* by portAllUnlockedApply (main.c) at startup and on every F10 change: it
* sets the game's own RAM unlock flags
* (debug_enable_all_levels_flag / debug_007_unlock_flag in
* src/game/debugmenu_handler.c, live because the PC build defines
* LEFTOVERDEBUG) — port-layer memory writes only, no game-code edits.
* 1 = every solo level selectable at every difficulty plus 007 mode from
* the first launch. F10 'All unlocked' row toggles it; takes effect next
* run. Cheats are unlocked at query time via fileGetIsCheatUnlocked
* the first launch. F10 'All unlocked' row toggles it live. Cheats are unlocked at query time via fileGetIsCheatUnlocked
* (src/game/file2.c, D442) -- the save file is never patched. */
s32 portAllUnlocked = 0;
@@ -398,6 +400,15 @@ f32 portDrawDistanceMultiplier(void)
return mult;
}
/* D540: Video.FogDistance -- multiplier on the level's authored fog start/end
* DISTANCES, independent of Video.DrawDistance (which moves the far clip).
* bgfog.c caps the fog at the far clip, so the fog always hides the edge of
* the drawn world. 1.0f (100) = the N64's own fog. */
f32 portFogDistanceMultiplier(void)
{
return (f32)cfgFogDistance / 100.0f;
}
/* D249: Video.LodDistance -- multiplier on the *distance* term
* modelUpdateDistanceRelations() (src/game/model.c) tests against each LOD
* node's MinDistance/MaxDistance, composed on top of the game's own
@@ -531,11 +542,12 @@ PD_CONSTRUCTOR static void videoConfigInit(void)
configRegisterInt("Video.FixMipTextures", &cfgFixMipTex, 0, 1);
configRegisterInt("Video.DetailBaseTile", &cfgDetailBaseTile, 0, 1);
configRegisterInt("Video.WrapFix", &cfgWrapFix, 0, 1);
configRegisterInt("Video.FovScale", &cfgFovScale, 50, 150);
configRegisterFloat("Video.FovScale", &cfgFovScale, 50.f, 150.f);
configRegisterInt("Video.WidescreenAuto", &cfgWidescreenAuto, 0, 1);
configRegisterInt("Video.NativeWidescreen", &cfgNativeWidescreen, 0, 1); /* D334 */
configRegisterInt("Game.HudScale", &cfgHudScale, 75, 150); /* D226: capped at 150 (user: little benefit above) */
configRegisterInt("Video.DrawDistance", &cfgDrawDistance, 100, 800);
configRegisterInt("Video.FogDistance", &cfgFogDistance, 50, 800); /* D540 */
configRegisterInt("Video.DrawDistanceAutoFov", &cfgDrawDistanceAutoFov, 0, 1);
configRegisterInt("Video.LodDistance", &cfgLodDistance, 25, 800);
configRegisterInt("Video.LodDistanceAutoFov", &cfgLodDistanceAutoFov, 0, 1);
@@ -551,6 +563,7 @@ PD_CONSTRUCTOR static void videoConfigInit(void)
configRegisterInt("Window.Maximized", &cfgWinMax, 0, 1);
configRegisterInt("Video.DeckPresetApplied", &cfgDeckPresetApplied, 0, 1); /* D283 */
configRegisterInt("Video.LowEndConsidered", &cfgLowEndConsidered, 0, 1); /* D482 */
configRegisterInt("Video.DefaultsRev", &cfgDefaultsRev, 0, 1); /* D546 */
}
/* D283: Steam Deck preset. Called from main() right AFTER configLoad(), so
@@ -686,6 +699,20 @@ static int videoIsLowEndRenderer(const char *r, const char **why)
return 0;
}
/* D546: one-time move of untouched old defaults to the standardized ones
* (draw/LOD distance 2.5x -> 2.0x). A value the player changed is kept; same
* "considered" flag pattern as D482. */
static void videoMigrateDefaults(void)
{
int changed = 0;
if (cfgDefaultsRev >= 1) return;
if (cfgDrawDistance == 250) { cfgDrawDistance = 200; changed++; }
if (cfgLodDistance == 250) { cfgLodDistance = 200; changed++; }
sysLogPrintf(LOG_INFO, "video: defaults rev 1 (D546): %d value(s) moved to 2.0x", changed);
cfgDefaultsRev = 1;
configSave();
}
static void videoApplyLowEndDefaults(void)
{
const char *r = gfx_opengl_renderer_string();
@@ -702,8 +729,8 @@ static void videoApplyLowEndDefaults(void)
}
if (low) {
int changed = 0;
if (cfgDrawDistance == 250) { cfgDrawDistance = 100; changed++; }
if (cfgLodDistance == 250) { cfgLodDistance = 100; changed++; }
if (cfgDrawDistance == 200) { cfgDrawDistance = 100; changed++; } /* D546: the default is 2.0x */
if (cfgLodDistance == 200) { cfgLodDistance = 100; changed++; }
if (cfgMSAA == 2) { cfgMSAA = 1; gfx_msaa_level = 1; changed++; }
sysLogPrintf(LOG_INFO, "video: low-end renderer \"%s\" (%s); lowered %d default(s): DrawDistance=%d LodDistance=%d MSAA=%d",
r, why, changed, cfgDrawDistance, cfgLodDistance, cfgMSAA);
@@ -734,8 +761,9 @@ static const struct { const char *key; double n64, port; } kVideoPresets[] = {
{ "Video.WidescreenAuto", 0, 1 }, /* stock vertical FOV at any window aspect */
{ "Video.SafeAreaCrop", 0, 1 }, /* show the VI frame as output, borders included */
{ "Video.AspectMode", 1, 0 }, /* D447: exact console aspect, bars around it */
{ "Video.DrawDistance", 100, 250 }, /* authored far clip / fog (D218) */
{ "Video.LodDistance", 100, 250 }, /* authored LOD switch distances (D249) */
{ "Video.DrawDistance", 100, 200 }, /* authored far clip (D218); D546 2.0x */
{ "Video.FogDistance", 100, 100 }, /* D540: N64 fog in both presets */
{ "Video.LodDistance", 100, 200 }, /* authored LOD switch distances (D249); D546 2.0x */
{ "Video.DrawDistanceAutoFov", 0, 0 }, /* legacy coupling: off = identity */
{ "Video.LodDistanceAutoFov", 0, 0 },
{ "Video.FovScale", 100, 100 }, /* stock FOV (D211) */
@@ -794,7 +822,7 @@ static SDL_atomic_t liveCfgDirty;
* plain float the game re-reads each frame; anisotropy goes to fast3d. */
static void videoApplyImageOptions(void)
{
portFovScale = (f32)cfgFovScale / 100.0f;
portFovScale = cfgFovScale / 100.0f;
gfx_set_anisotropy_level(cfgAniso);
gfx_set_safe_area_crop(cfgSafeAreaCrop);
}
@@ -1017,6 +1045,7 @@ int videoInit(void)
};
gfx_init(&set);
videoMigrateDefaults(); /* D546: before D482, which compares against the new defaults */
videoApplyLowEndDefaults(); /* D482: needs GL_RENDERER; before the first frame */
/* VSync + optional fps cap; fast3d paces the window itself. */
@@ -1190,11 +1219,11 @@ void videoStartFrame(void)
if (dirty & VCFG_VSYNC) wmAPI->set_swap_interval(cfgVSync ? 1 : 0);
if (dirty & VCFG_FPS) gfx_set_target_fps(cfgFpsCap);
if (dirty & VCFG_FILTER) videoApplyTexFilter();
if (dirty & VCFG_FOV) portFovScale = (f32)cfgFovScale / 100.0f;
if (dirty & VCFG_FOV) portFovScale = cfgFovScale / 100.0f;
if (dirty & VCFG_ANISO) gfx_set_anisotropy_level(cfgAniso);
if (dirty & VCFG_CROP) gfx_set_safe_area_crop(cfgSafeAreaCrop);
sysLogPrintf(LOG_INFO, "video: live config applied mask=%02x "
"(vsync=%d fpscap=%d texfilter=%d fov=%d aniso=%d)",
"(vsync=%d fpscap=%d texfilter=%d fov=%.1f aniso=%d)",
dirty, cfgVSync, cfgFpsCap, cfgTexFilter, cfgFovScale, cfgAniso);
}
@@ -1299,17 +1328,8 @@ void videoPumpEvents(void)
}
}
/* Refresh the window title with the live FPS about once a second. */
if (wmAPI && wmAPI->set_window_title) {
static double lastTitle = 0.0;
double now = wmAPI->get_time();
if (now - lastTitle >= 1.0) {
lastTitle = now;
char title[64];
snprintf(title, sizeof(title), "GoldenEye 007 - %.0f fps", vidAvgFPS);
wmAPI->set_window_title(title);
}
}
/* D550: static title bar -- the "GoldenEye 007 - NN fps" per-second
* refresh used to live here (maintainer request 2026-10-17). */
/* D287: apply anything the events above (click-to-lock, focus) queued. */
inputApplyMouseRequests();
+49 -1
View File
@@ -28,6 +28,7 @@ extern u16 get_mTrack2Vol(void);
extern void fileBuildWriteNewSave(u32 folder);
extern void set_cur_player_look_vertical_inverted(u32 value);
extern void cur_player_set_aim_control(u32 value);
extern void cur_player_set_lookahead(u32 value);
extern void cur_player_set_sight_onscreen_control(u32 value);
extern void cur_player_set_ammo_onscreen_setting(u32 value);
@@ -532,6 +533,32 @@ static void applyValue(enum WatchSettingField field, int value)
}
}
/* D559 (maintainer-approved, port layer only): D557 turned Look ahead off in
* DEFAULT_OPTIONS, but folders created before it hold the OLD factory options.
* When a folder is started to play (the stage becomes active; the stage load's
* fileLoadSettingsForFolder then reads the saved options), clear OPTION_LOOKAHEAD
* iff the folder is untouched: no stage time at any difficulty (times[] all
* zero), no 007 flag, no unlocked cheats, and options EXACTLY the old factory
* default with control-type bits 0. Any progress or any other option bit
* (including a deliberately-set Look ahead on a played folder) is never touched.
* Persists through fileWriteSave, as saveFrontField does. Game thread only.
* Returns 1 when it changed the save. */
#define OLD_FACTORY_OPTIONS (OPTION_AUTOAIM | OPTION_SIGHTONSCREEN | OPTION_LOOKAHEAD | OPTION_DISPLAYAMMO)
static int migrateUntouchedLookAhead(int folder)
{
save_data *save = validSave(folder);
if (!save || save->options != OLD_FACTORY_OPTIONS) return 0;
if (save->flag_007 || save->unlocked_cheats_1 || save->unlocked_cheats_2 || save->unlocked_cheats_3) return 0;
for (size_t i = 0; i < sizeof(save->times); i++)
if (save->times[i]) return 0;
if (!fileGamePakProbe()) return 0;
save->options = (u16)(save->options & ~OPTION_LOOKAHEAD);
fileWriteSave(save);
sysLogPrintf(LOG_INFO, "watchsettings: Bond file %d is untouched and has the old factory options; Look ahead set off (D559)", folder + 1);
return 1;
}
static int wsLookAheadChecked = 0; /* once per stage activation */
void watchSettingsGameTick(void)
{
/* Drain F10 front-end commits on the game thread. This MUST run before
@@ -566,7 +593,9 @@ void watchSettingsGameTick(void)
{
static int wsResetProbePhase = 0;
static int wsProbeFrontTicks = 0, wsProbeStageTicks = 0;
if (getenv("GE_WSPROBE_RESET")) {
static int wsResetProbeOn = -1; /* cached: this runs every tick (D250/D302 class) */
if (wsResetProbeOn < 0) wsResetProbeOn = getenv("GE_WSPROBE_RESET") != NULL;
if (wsResetProbeOn) {
if (!stageActive()) {
if (wsResetProbePhase == 0 && ++wsProbeFrontTicks == 120) {
optionsResetProbePrepare();
@@ -594,8 +623,22 @@ void watchSettingsGameTick(void)
* during a level/menu transition. Each command's folder is re-checked
* against selected_folder_num at apply time, so a stale cross-file edit
* is still dropped, never misapplied. */
/* D564: the game's own "Look up/down" option is not exposed (the port's
* Mouse / Controller "Invert look" rows are the inversion controls). Pin it
* to its factory value (0, as a blank profile has it) so a value saved by an
* older build or set via the in-game watch menu cannot silently flip the
* pad stick on the game's pitch path. RAM only; one int compare per tick. */
if (stageActive() && g_CurrentPlayer && get_cur_player_look_vertical_inverted() != 0)
set_cur_player_look_vertical_inverted(0);
if (!stageActive() || !g_CurrentPlayer) {
wsStageWasActive = 0;
if (!stageActive()) wsLookAheadChecked = 0;
else if (!wsLookAheadChecked) { /* D559: stage starting, before the stage load reads the save */
wsLookAheadChecked = 1;
if (migrateUntouchedLookAhead(selected_folder_num) && getPlayerCount() == 1)
cur_player_set_lookahead(0); /* harmless if the stage load re-reads the save after this */
}
/* D354: one-shot fallback -- if not a single folder holds a valid
* save (wiped/corrupt/fresh eeprom), build the game's own blank save for
* file 1 (game thread, so the EEPROM write is on the owning thread)
@@ -649,6 +692,11 @@ void watchSettingsGameTick(void)
* init_watch re-applies the saved music value to the X track; resync the
* BGM (track 1) to the same live value once per activation so the player's
* music setting survives level starts, not just live F10 edits. */
if (!wsLookAheadChecked) { /* D559: no tick saw the stage before its load: fix the live setting too */
wsLookAheadChecked = 1;
if (migrateUntouchedLookAhead(selected_folder_num) && getPlayerCount() == 1)
cur_player_set_lookahead(0);
}
if (!wsStageWasActive && musicTrack1GetVolume() != get_mTrack2Vol())
musicTrack1ApplySeqpVol(get_mTrack2Vol());
wsStageWasActive = 1;
+38 -2
View File
@@ -344,6 +344,15 @@ int portFogPositionVisibleGameplay(float *pos3, float range)
#endif
#ifdef PORT
/* D540: Video.FogDistance helpers (port rendering option, like D218's draw
* distance; game logic unchanged -- identity at the defaults). */
extern f32 portDrawDistanceMultiplier(void);
extern f32 portFogDistanceMultiplier(void);
static EnvironmentRecord *s_portFogEnv = NULL; /* last env loaded, for live re-apply */
static f32 s_portFogDD, s_portFogFM; /* multipliers it was built with */
#endif
/**
* Address 0x7F0BA758.
*/
@@ -379,8 +388,17 @@ void fogLoadCurrentEnvironment(EnvironmentRecord *arg0)
* a per-frame copy. Identity at DrawDistance=100/DrawDistanceAutoFov's
* FovScale=100 default. */
{
extern f32 portDrawDistanceMultiplier(void);
scaledFarFog = arg0->Visibility.FarFog * portDrawDistanceMultiplier();
/* D540: the far clip follows FOG distance, capped by Draw distance:
* beyond the fog's end everything is solid fog, so a further clip
* shows nothing -- but it pushed the authored per-mille fog positions
* so close to 1000 that the s32 rounding pulled the fog visibly in
* (Cradle's distant truss fogged out vs 1964 at Draw 2.5x; exact at
* Draw 1.0x). At Fog 1.0x the clip is the authored far: exact N64 at
* any Draw distance. Draw distance still scales prop draw distance
* (propobj.c) and the room pool within that clip. */
f32 clipMult = portFogDistanceMultiplier();
if (clipMult > portDrawDistanceMultiplier()) clipMult = portDrawDistanceMultiplier();
scaledFarFog = arg0->Visibility.FarFog * clipMult;
}
viSetZRange(arg0->Visibility.BlendMultiplier, scaledFarFog);
#else
@@ -425,6 +443,12 @@ void fogLoadCurrentEnvironment(EnvironmentRecord *arg0)
g_CurrentEnvironment.DifferenceFromFarIntensity = (s32) arg0->Fog.DifferenceFromFarIntensity;
g_CurrentEnvironment.FarIntensity = arg0->Fog.FarIntensity;
#ifdef PORT
/* D540: remember the env + multipliers for the live re-apply below. */
s_portFogEnv = arg0;
s_portFogDD = portDrawDistanceMultiplier();
s_portFogFM = portFogDistanceMultiplier();
#endif
g_CurrentEnvironment.Red = arg0->Sky.Red;
g_CurrentEnvironment.Green = arg0->Sky.Green;
g_CurrentEnvironment.Blue = arg0->Sky.Blue;
@@ -527,6 +551,7 @@ void fogLoadLevelEnvironment(s32 level_id, s32 arg1)
g_PortAuthoredScaledFarFogIntensity = FLT_MAX;
g_PortAuthoredScaledDifferenceFromFarFogIntensity = 0.0f;
g_PortAuthoredFarFog = 10000.0f;
s_portFogEnv = NULL; /* D540: no live re-apply until this level's env loads */
#endif
if (arg1)
@@ -690,6 +715,17 @@ Gfx *fogSetRenderFogColor(Gfx *gdl, s32 arg1)
return gdl;
}
#ifdef PORT
/* D540: Draw distance / Fog distance apply live (they used to wait for
* the next level load): rebuild the fog state from the same environment
* on the game thread when either multiplier changed. */
if (s_portFogEnv != NULL &&
(portDrawDistanceMultiplier() != s_portFogDD || portFogDistanceMultiplier() != s_portFogFM))
{
fogLoadCurrentEnvironment(s_portFogEnv);
}
#endif
// note: both branches are exactly the same.
// maybe one has debug code?
if (arg1)
+6 -63
View File
@@ -352,15 +352,6 @@ int gameScriptedCameraActive(void)
(g_CameraMode == CAMERAMODE_SWIRL) || (g_CameraMode == CAMERAMODE_FADESWIRL);
}
/* D243 M-169: a monotonic epoch counter bumped by chrai.c's
* AI_TRYTeleportingChrToPad case right after the decomp call that
* legitimately re-anchors a chr's model root joint via setsuboffset() for a
* real cutscene shot-change. playerTick() polls it to re-seed the camera
* look-at filter exactly on a shot-change (M-190). Global, not per-chr:
* sufficient for this cutscene's scope. */
static u32 g_d243TeleportEpoch = 0;
void d243NotifyTeleport(void) { g_d243TeleportEpoch++; }
u32 d243GetTeleportEpoch(void) { return g_d243TeleportEpoch; }
#endif
void solo_char_load(void)
@@ -10629,64 +10620,16 @@ s32 playerTick(PropRecord *prop)
RenderPosView *rp = g_playerPointers[index]->bodyModel->render_pos;
matrix_4x4_multiply_homogeneous(currentPlayerGetViewToWorldMtxf(), (Mtxf *) rp, (Mtxf *) mtx);
#ifdef PORT
/* D243 M-170 (superseded by M-190 below): the original "fix"
* unconditionally froze field_488.pos during POSEND, only
* letting a write through on the tick a shot-change teleport
* fired. That killed the shake but also killed legitimate
* camera tracking of Bond's real per-tick motion within a
* shot -- the M-189 buffer-overlap fix that landed this same
* session removed the actual cause of the shake (corrupted
* render-skeleton data), so freezing is no longer needed and
* was actively wrong (user-reported: "camera angles do not
* follow bond"). */
/* D243 M-190: write field_488.pos every tick unconditionally
* (real tracking restored), and instead re-seed the look-at
* filter's leaky-integrator accumulator (field_3B8, read by
* bondviewUpdatePlayerCollisionPositionFields) the instant a
* legitimate shot-change teleport fires, rather than letting
* it slowly converge over ~20 ticks (M-143's original shake
* mechanism, confirmed PC-only vs N64 in M-144). Detected via
* the same d243TeleportEpoch counter M-170 already used
* (bumped by d243NotifyTeleport() in chrai.c's
* AI_TRYTeleportingChrToPad handler) -- only the response to
* an epoch change is different now: reset the filter to the
* new position instead of freezing the input to it. */
extern u32 d243GetTeleportEpoch(void);
static u32 s_d243LastEpoch = 0;
static int s_d243HaveEpoch = 0;
u32 epoch;
int justTeleported;
epoch = d243GetTeleportEpoch();
justTeleported = (!s_d243HaveEpoch) || (epoch != s_d243LastEpoch);
s_d243LastEpoch = epoch;
s_d243HaveEpoch = 1;
/* D243 history (M-170 freeze, M-190 teleport re-seed) removed
* 2026-10-06 (D552): the shake's real cause was the M-189
* sizeof(Model) buffer overlap; the remaining ~20-tick filter
* convergence after a shot-change teleport is the original
* camera swivel onto Bond (Dam ending), so the write below
* and the look-at filter are the decomp's, unmodified. */
#endif
g_playerPointers[index]->field_488.pos.x = mtx[12] + (mtx[4] * 7.0f);
g_playerPointers[index]->field_488.pos.y = mtx[13] + (mtx[5] * 7.0f);
g_playerPointers[index]->field_488.pos.z = mtx[14] + (mtx[6] * 7.0f);
#ifdef PORT
if ((g_CameraMode == CAMERAMODE_POSEND) && justTeleported)
{
f32 fx = g_playerPointers[index]->field_488.pos.x;
f32 fy = g_playerPointers[index]->field_488.pos.y;
f32 fz = g_playerPointers[index]->field_488.pos.z;
/* Re-seed so the filter's steady state (field_3C4/8/C =
* field_3B8 * FACTOR_2) already equals the new position,
* instead of a fresh ~20-tick geometric decay toward it
* (bondviewUpdatePlayerCollisionPositionFields, the
* FACTOR_1/FACTOR_2 pair). field_3B8 is the PRE-scale
* accumulator, so it must be seeded at pos/FACTOR_2, not
* pos itself -- field_3C4/8/C (what the camera actually
* reads) are set directly to the real position so it's
* correct even before the next tick's filter update. */
g_playerPointers[index]->field_3B8.f[0] = fx / S7F081478_FACTOR_2;
g_playerPointers[index]->field_3B8.f[1] = fy / S7F081478_FACTOR_2;
g_playerPointers[index]->field_3B8.f[2] = fz / S7F081478_FACTOR_2;
g_playerPointers[index]->field_3C4 = fx;
g_playerPointers[index]->field_3C8 = fy;
g_playerPointers[index]->field_3CC = fz;
}
#endif
}
return ret;
-14
View File
@@ -4177,20 +4177,6 @@ void ai(PropDefHeaderRecord *Entityp, PROP_TYPE EntityType)
chr->chrflags = chr->chrflags | CHRFLAG_INIT;
setsubroty(chr->model, FacingDirection);
setsuboffset(chr->model, &pos);
#ifdef PORT
/* D243 M-169: signal the real shot-change moment
* to the X3/X4 render-pos freeze experiments --
* see the d243NotifyTeleport() definition
* (bondview2.c) for the full rationale. This is
* the exact decomp call (setsuboffset, line
* above, unmodified) that a legitimate cutscene
* teleport already performs; the notify is a
* side-channel observer only, no logic change. */
{
extern void d243NotifyTeleport(void);
d243NotifyTeleport();
}
#endif
chrDetectRooms(chr);
if (chr->prop == g_CurrentPlayer->prop)
{
+8
View File
@@ -20,7 +20,15 @@
#define OPTION_CONTROLTYPE 0x0700
#define OPTION_SCREENCINEMA 0x0800
#ifdef PORT
/* D557 (Rule-2 sign-off: maintainer 2026-10-06): new/blank profiles start with
* Look ahead OFF on PC -- its pitch auto-centring fights mouse and right-stick
* look (PD's PC port likewise keeps it on only for N64). Existing saves keep
* their stored option bits; only BLANKSAVEDATA-initialised profiles change. */
#define DEFAULT_OPTIONS (OPTION_AUTOAIM | OPTION_SIGHTONSCREEN | OPTION_DISPLAYAMMO)
#else
#define DEFAULT_OPTIONS (OPTION_AUTOAIM | OPTION_SIGHTONSCREEN | OPTION_LOOKAHEAD | OPTION_DISPLAYAMMO)
#endif
extern ChrRecord *g_CurModelChr;
+28
View File
@@ -1728,7 +1728,13 @@ void gunInitProjectileFromPlayer(ObjectRecord *obj, coord3d *targetpos, Mtxf *ar
u8 rooms[2];
s32 pad_rooms;
u8 pad_a[0x4c];
#ifdef AVOID_UB
s32 sp54[0x14]; /* D545: bgFindRoomsAlongSegment below writes up to 0x14 room
numbers here; on N64 the overflow landed in `pad_a`
(0x4c = 19 more s32), on PC it could hit live locals */
#else
s32 sp54;
#endif
s32 sp50;
s32 pad_sp;
@@ -1788,7 +1794,11 @@ void gunInitProjectileFromPlayer(ObjectRecord *obj, coord3d *targetpos, Mtxf *ar
rooms[0] = bondviewGetCurrentPlayersRoom();
rooms[1] = 0xff;
#ifdef AVOID_UB
bgFindRoomsAlongSegment(bondviewGetCurrentPlayersPosition3(), &pos, rooms, obj->projectile->unkCC, sp54, &sp50, 0x14);
#else
bgFindRoomsAlongSegment(bondviewGetCurrentPlayersPosition3(), &pos, rooms, obj->projectile->unkCC, &sp54, &sp50, 0x14);
#endif
}
}
@@ -1807,7 +1817,13 @@ void generate_player_thrown_grenade(s32 hand)
Mtxf spA0_a;
struct WeaponObjRecord *wor;
s32 new_prop_type;
#ifdef AVOID_UB
struct coord3d sp94; /* D549: bullet_path_from_screen_center writes a coord3d here;
on N64 the 8-byte overflow hit a dead stack slot, on PC it
can hit live locals (wor/new_prop_type/...) */
#else
s32 sp94; // sp148
#endif
struct coord3d base_speed_vec; // sp136
struct PropRecord* player_prop; // sp132
struct coord3d *bondprevpos; // sp128
@@ -1898,7 +1914,13 @@ void generate_player_thrown_knife(s32 hand)
Mtxf spA0_a;
s32 padding;
s32 new_prop_type;
#ifdef AVOID_UB
struct coord3d sp94; /* D549: bullet_path_from_screen_center writes a coord3d here;
on N64 the 8-byte overflow hit a dead stack slot, on PC it
can hit live locals (wor/new_prop_type/...) */
#else
s32 sp94;
#endif
struct coord3d base_speed_vec;
Mtxf sp40_f;
struct PropRecord* player_prop;
@@ -1988,7 +2010,13 @@ void generate_player_thrown_object(s32 hand)
Mtxf spA0_a;
struct WeaponObjRecord *wor;
s32 new_prop_type;
#ifdef AVOID_UB
struct coord3d sp94; /* D549: bullet_path_from_screen_center writes a coord3d here;
on N64 the 8-byte overflow hit a dead stack slot, on PC it
can hit live locals (wor/new_prop_type/...) */
#else
s32 sp94; // sp148
#endif
struct coord3d base_speed_vec; // sp136
struct PropRecord* player_prop; // sp132
struct coord3d *bondprevpos; // sp128
+112 -51
View File
@@ -1,20 +1,41 @@
#!/bin/bash
# P7 golden capture on Linux (mirrors tools_pc/verify.sh's golden recipe).
#
# Usage: capture_p7.sh [-b BUILD_DIR] <level_name>:<num> [<level_name>:<num> ...]
# Usage: capture_p7.sh [-b BUILD_DIR] [-p PLATFORM] [-j N] <level_name>:<num> [...]
# BUILD_DIR build dir holding ge007.x86_64, relative to the repo root
# (default: build-linux). The repo root is the parent of the
# directory this script lives in, so the script can be invoked
# from anywhere; no machine-specific path is baked in.
# Writes only inside the repo: <repo>/data/ge007.ini + ge007.eep (the
# pinned 640x480 ini and the canonical save, re-pinned before every level;
# the local save is only snapshotted for the exit restore),
# <repo>/ppm (the game's dump dir, removed after each level), and
# <repo>/tools_pc/golden/<level>/linux/.
# Per-level run logs go to <repo>/cap_<level>.log: scratch, never tracked.
# PLATFORM golden set to write: tools_pc/golden/<level>/<PLATFORM>/
# (default: linux; `deck` = Steam Deck set, same exe/recipe).
# -j N run up to N levels concurrently (default 1); result lines
# print in the original level order. Each game quits via
# GE_QUITFRAME (the only kill is `timeout`'s own-child watchdog).
# PIXEL RESULTS ASSUME EACH INSTANCE HOLDS 60 fps (D117: the
# intro flyby is wall-clock paced): use -j 2-3 only on a strong
# GPU with nothing else loading it (no local LLM loaded); use
# -j 1 on weak boxes (Steam Deck, Intel HD 3000, ...).
# Isolation: every level runs with CWD = a fresh temp work dir holding its
# OWN data/ (copy of ge007.<romid>.z64 + pccg-<romid>/ + pcmodels-<romid>/ +
# the canonical save tools_pc/golden/ge007.eep + the pinned 640x480 ini) and
# its own ppm/ (the game resolves data/ from its CWD first). The repo's
# data/ is only READ, never modified; the work dir is removed after each
# level. ROMID comes from <BUILD_DIR>/CMakeCache.txt, else the single
# data/ge007.*.z64 present, else ntsc-final (override: GE_ROMID).
# Writes only: <repo>/tools_pc/golden/<level>/<PLATFORM>/ and the per-level
# run logs <repo>/cap_<level>.log (scratch, never tracked).
# Windows: run from Git Bash/MSYS2 with -b build-pc -p win (no DISPLAY needed;
# the .exe resolves from <BUILD_DIR>/ge007.x86_64).
# Runs on a stripped copy of the repo on the device (Steam Deck Desktop
# Mode: DISPLAY=:0 bash, python3 + PIL, no compiler): put the prebuilt binary
# at <BUILD_DIR>/ge007.x86_64 and data/ + tools_pc/{verify.sh,golden/ge007.eep}
# in the copy.
# DISPLAY must be inherited from the caller -- the capture needs a real GL
# context, and there is deliberately no default display/XAUTHORITY here.
#
# GE_FAKE_DECK=0 on every run: on a Steam Deck the D283 preset would force
# 1280x800 fullscreen over the pinned 640x480 ini (harmless elsewhere).
#
# Recipe: the per-level windows/scripts/seed are READ OUT OF tools_pc/verify.sh
# (golden_dump_for / golden_script_for / GOLDEN_SEED) so this tool and the gate
# cannot drift apart -- see tools_pc/golden/README.md for what the windows are
@@ -28,37 +49,43 @@
# playtest save is exactly this class of drift. (History: the P7 round of
# 2026-10-04 ran with a cleared eep; that set was re-captured 2026-10-05, D525.)
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
BUILD_DIR=build-linux
if [ "${1:-}" = -b ]; then
[ -n "${2:-}" ] || { echo "error: -b needs a BUILD_DIR argument"; exit 2; }
BUILD_DIR="$2"; shift 2
fi
BUILD_DIR=build-linux; PLATFORM=linux; JOBS=1
while getopts "b:p:j:" opt; do
case "$opt" in
b) BUILD_DIR="$OPTARG" ;;
p) PLATFORM="$OPTARG" ;;
j) JOBS="$OPTARG" ;;
*) echo "usage: capture_p7.sh [-b BUILD_DIR] [-p PLATFORM] [-j N] <level_name>:<num> ..."; exit 2 ;;
esac
done
shift $((OPTIND - 1))
case "$JOBS" in ''|*[!0-9]*|0) echo "error: -j needs a positive integer (got '$JOBS')"; exit 2 ;; esac
case "$PLATFORM" in ''|*[!A-Za-z0-9_-]*) echo "error: bad PLATFORM '$PLATFORM'"; exit 2 ;; esac
[ "$#" -gt 0 ] || { echo "error: no <level_name>:<num> specs given"; exit 2; }
EXE="$ROOT/$BUILD_DIR/ge007.x86_64"
[ -x "$EXE" ] || { echo "error: no executable at $EXE (pass -b BUILD_DIR)"; exit 2; }
[ -n "${DISPLAY:-}" ] || { echo "error: DISPLAY is unset; the capture needs a real GL context (no default provided)"; exit 2; }
case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*) ;; # Windows (Git Bash / MSYS2): the desktop is the GL context; no DISPLAY
*) [ -n "${DISPLAY:-}" ] || { echo "error: DISPLAY is unset; the capture needs a real GL context (no default provided)"; exit 2; } ;;
esac
PY=""; for c in python3 python; do "$c" -c "import PIL" >/dev/null 2>&1 && { PY=$c; break; }; done
[ -n "$PY" ] || { echo "error: no python with Pillow (PIL) on PATH (needed to write the PNGs)"; exit 2; }
VSH="$ROOT/tools_pc/verify.sh"
[ -f "$VSH" ] || { echo "error: no tools_pc/verify.sh under $ROOT (the recipe lives there)"; exit 2; }
CANON_EEP="$ROOT/tools_pc/golden/ge007.eep"
[ -f "$CANON_EEP" ] || { echo "error: canonical golden save missing: $CANON_EEP (D529)"; exit 2; }
cd "$ROOT" # the loop's dump/golden paths are root-relative (ppm/, tools_pc/golden/)
EEP="$ROOT/data/ge007.eep"
EEP_ORIG=$(mktemp); EEP_HAD=0
[ -f "$EEP" ] && { EEP_HAD=1; cp "$EEP" "$EEP_ORIG"; }
cp "$CANON_EEP" "$EEP"
INI="$ROOT/data/ge007.ini"
INI_ORIG=""
[ -f "$INI" ] && { INI_ORIG=$(mktemp); cp "$INI" "$INI_ORIG"; }
cleanup() {
[ -n "$INI_ORIG" ] && cp "$INI_ORIG" "$INI" 2>/dev/null
if [ "$EEP_HAD" = 1 ]; then cp "$EEP_ORIG" "$EEP" 2>/dev/null; else rm -f "$EEP" 2>/dev/null; fi
rm -f "$INI_ORIG" "$EEP_ORIG" 2>/dev/null
}
# 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
cd "$ROOT" # golden paths are root-relative (tools_pc/golden/)
ROMID="${GE_ROMID:-$(sed -n 's/^ROMID:[A-Za-z]*=//p' "$ROOT/$BUILD_DIR/CMakeCache.txt" 2>/dev/null | head -n1)}"
if [ -z "$ROMID" ]; then
roms=$(ls "$ROOT"/data/ge007.*.z64 2>/dev/null)
if [ "$(printf '%s\n' "$roms" | grep -c .)" = 1 ]; then
ROMID=$(basename "$roms" .z64); ROMID="${ROMID#ge007.}"
else ROMID=ntsc-final; fi
fi
for need in "ge007.$ROMID.z64" "pccg-$ROMID" "pcmodels-$ROMID"; do
[ -e "$ROOT/data/$need" ] || { echo "error: $ROOT/data/$need missing (needed for the per-run work dir; set GE_ROMID to pick another region)"; exit 2; }
done
# recipe comes from the tracked gate tool, not from this file
eval "$(grep -m1 '^GOLDEN_DUMP=' "$VSH")"
@@ -66,38 +93,72 @@ eval "$(grep -m1 '^golden_dump_for()' "$VSH")"
eval "$(grep -m1 '^golden_script_for()' "$VSH")"
eval "$(grep -m1 '^GOLDEN_SEED=' "$VSH")"
for spec in "$@"; do
name="${spec%%:*}"; num="${spec##*:}"
[ "$name" != "$num" ] || { echo "error: bad spec $spec"; exit 2; }
TMPROOT=$(mktemp -d)
trap 'kill $(jobs -rp) 2>/dev/null; rm -rf "$TMPROOT"' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
# capture_one NAME NUM -> prints the result line on stdout
capture_one() {
local name="$1" num="$2" dump script lo hi step last qf w t0 t1 rc n f stem
dump=$(golden_dump_for "$name")
script=$(golden_script_for "$name")
IFS='-:' read -r lo hi step <<< "$dump"
last=$(( lo + step * ((hi - lo) / step) ))
qf=$(( last + 2 ))
printf '[Window]\nWidth = 640\nHeight = 480\n' > "$INI"
# D531: re-pin the CANONICAL save before every level (the frame depends on
# the save's CONTENT, D529). The D524-era re-pin restored $EEP_ORIG (the
# local pre-capture save, or an empty file when none existed), so the
# pre-loop canonical install below was silently overwritten from level 1:
# the 2026-10-05 box re-round ran under the box's local save, not the
# canonical one. EEP_ORIG is kept only for the exit restore.
cp "$CANON_EEP" "$EEP"
rm -f ge007.crash.log
rm -rf ppm
w="$TMPROOT/$name"; rm -rf "$w"; mkdir -p "$w/data"
# D531/D529: every level gets the CANONICAL save (the frame depends on its
# content) and the pinned defaults-only 640x480 ini -- in its OWN data/.
cp "$ROOT/data/ge007.$ROMID.z64" "$w/data/" &&
cp -r "$ROOT/data/pccg-$ROMID" "$ROOT/data/pcmodels-$ROMID" "$w/data/" &&
cp "$CANON_EEP" "$w/data/ge007.eep" &&
printf '[Window]\nWidth = 640\nHeight = 480\n' > "$w/data/ge007.ini" ||
{ echo "== $name rc=setup-failed frames=0 secs=0 window=$dump"; return; }
t0=$(date +%s)
( export GE_PCDUMP="$dump" GE_QUITFRAME="$qf" GE_INPUTSCRIPT="$script" \
GE_RSEED="$GOLDEN_SEED"; timeout 300 "$EXE" -level_"$num" ) > "cap_$name.log" 2>&1
( cd "$w" && export GE_PCDUMP="$dump" GE_QUITFRAME="$qf" GE_INPUTSCRIPT="$script" \
GE_RSEED="$GOLDEN_SEED" GE_FAKE_DECK=0; timeout 300 "$EXE" -level_"$num" ) > "$ROOT/cap_$name.log" 2>&1
rc=$?
n=0
if [ -d ppm ]; then
mkdir -p "tools_pc/golden/$name/linux"
for f in ppm/*.ppm; do
if [ -d "$w/ppm" ]; then
mkdir -p "$ROOT/tools_pc/golden/$name/$PLATFORM"
for f in "$w"/ppm/*.ppm; do
[ -f "$f" ] || continue
stem=$(basename "$f" .ppm)
python3 -c "from PIL import Image; Image.open('$f').save('tools_pc/golden/$name/linux/$stem.png')"
"$PY" -c "import sys; from PIL import Image; Image.open(sys.argv[1]).save(sys.argv[2])" \
"$f" "$ROOT/tools_pc/golden/$name/$PLATFORM/$stem.png"
n=$((n+1))
done
rm -rf ppm
fi
rm -rf "$w"
t1=$(date +%s)
echo "== $name rc=$rc frames=$n secs=$((t1-t0)) window=$dump"
}
# validate every spec up front, then run up to JOBS at once; results are
# replayed in the original order
i=0
for spec in "$@"; do
name="${spec%%:*}"; num="${spec##*:}"
[ "$name" != "$num" ] || { echo "error: bad spec $spec"; exit 2; }
done
nxt=0
flush_done() {
local blocking="${1:-0}"
while [ "$nxt" -lt "$i" ] || { [ "$blocking" = 1 ] && [ "$nxt" -lt "$total" ]; }; do
if [ ! -f "$TMPROOT/res.$nxt" ]; then
[ "$blocking" = 1 ] && { sleep 0.2; continue; }
return
fi
cat "$TMPROOT/res.$nxt"; nxt=$((nxt + 1))
done
}
total=$#
for spec in "$@"; do
name="${spec%%:*}"; num="${spec##*:}"
while [ "$(jobs -rp | wc -l)" -ge "$JOBS" ]; do sleep 0.2; flush_done; done
( capture_one "$name" "$num" > "$TMPROOT/res.$i.tmp"; mv "$TMPROOT/res.$i.tmp" "$TMPROOT/res.$i" ) &
i=$((i + 1))
flush_done
done
flush_done 1
wait
+1 -1
View File
@@ -27,7 +27,7 @@ BUDGETS = {
"docs/HANDOFF.md": 3000,
"docs/ROADMAP.md": 12000,
"docs/porting-notes.md": 60000,
"docs/dev/findings.md": 600000,
"docs/dev/findings.md": 650000,
"docs/dev/findings-index.csv": 40000,
}
+86 -5
View File
@@ -3,11 +3,13 @@
`<level>/<platform>/frame_NNNNNN.png`: reference `GE_PCDUMP` captures for
`tools_pc/verify.sh`, one folder per solo level (all 21, `cuba` = the ending)
and per platform (`win`, and `linux` — 21 levels x 3 frames on each, captured
2026-10-05 at the same stems).
2026-10-05 at the same stems; `deck` = the Steam Deck set, captured with
`capture_p7.sh -p deck`, per-level pixel limits = the linux ones for now).
**Recipe** (2026-10-02 re-base; `verify.sh` applies all of it):
- `data/ge007.ini` pinned to ONLY `[Window] Width = 640, Height = 480` (every
other option at its compiled-in default; verify.sh backs yours up).
other option at its compiled-in default). Each run gets its own copy in a
temp work dir (see Isolation below); your `data/ge007.ini` is never touched.
- `GE_RSEED=0x0123456789abcdef` (pinned PRNG).
- `GE_INPUTSCRIPT="20:START"` (skips the intro flyby; the scripted pad is
the only input). Window per level, exactly as `verify.sh`'s `golden_dump_for`
@@ -26,14 +28,27 @@ and per platform (`win`, and `linux` — 21 levels x 3 frames on each, captured
each of the four candidate saves, 21.557% with none) was only ever measured on
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
and `capture_p7.sh` install the canonical eep into each run's own work dir
(the repo's `data/` is never modified), 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-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).
- **Isolation + parallelism:** `verify.sh` and `capture_p7.sh` run every level with
CWD = a fresh temp work dir holding its own `data/` (ROM, `pccg-<romid>/`,
`pcmodels-<romid>/`, the canonical save, the pinned ini) and its own `ppm/`; the
repo's `data/` is only read (the old pin/restore of `data/ge007.ini`/`.eep` is
gone). That makes levels safe to run concurrently:
`tools_pc/verify.sh sweep -j 3` / `tools_pc/capture_p7.sh -j 2 ...`. Pixel
results assume each instance holds 60 fps (D117): `-j 2-3` only on a strong GPU
with nothing else loading it (no local LLM loaded), `-j 1` on weak boxes.
Measured 2026-10-05 with a busy GPU (an LLM resident at ~100%): Surface 1's
frame 900 failed at `-j 2` and `-j 3` and passed at `-j 1`.
- Windows set: from Git Bash, `bash tools_pc/capture_p7.sh -b build-pc -p win -j 3 <level>:<num> ...`
(no DISPLAY on Windows; `-j 1` if the GPU is loaded).
- Deck set: on the device (Desktop Mode, `DISPLAY=:0`, prebuilt binary at
`<build>/ge007.x86_64`): `bash tools_pc/capture_p7.sh -b <build> -p deck <level>:<num> ...`.
**Gate** (two tiers): the structural framediff (cell means, coverage,
phash) catches gross breakage; then a per-pixel `--exact --tol 2` check
@@ -143,3 +158,69 @@ capture with the recipe above and replace `<level>/<platform>/*.png`.
ssh shell) so `verify.sh`'s fail-closed probe keeps the pixcount/framediff skip even
though the game's readback works; the cross-platform numbers are computed on Windows
against the merged linux PNGs. See D525.
- **2026-10-05 (isolated + parallel capture, `deck` platform):** `verify.sh` and
`capture_p7.sh` now run each level in its own temp work dir (own `data/` copy,
pinned ini, canonical save, `ppm/`) instead of pinning/restoring the repo's
`data/`; added `-j N` (concurrent levels, verdicts in level order) and a `deck`
platform (`verify.sh --platform deck`, `capture_p7.sh -p deck`).
- **2026-10-05 (D540 re-base #2, win only):** final fog design (far clip scales
by min(Fog, Draw distance); the game's fog math untouched, so fogged levels
render the N64's own fog at defaults). Re-captured whole levels: aztec, cradle,
dam, egypt, jungle, statue, surface1, streets (24 frames, win). Side-by-sides:
fog only (Cradle's distant truss is a faint ghost, Frigate's sea unchanged);
props, characters, gun and HUD present, no overlays. Pass-A vs pass-B
(over tol 2): <=0.30% (aztec 0.19, egypt 0.26, statue 0.30), cradle/dam/jungle/
surface1 <=0.003%; streets 0.75-2.1% (timer digits jitter; promoted from the
pass whose state the confirming runs reproduce). Confirming pass 21/21 PASS
(silo failed once with worst_cell 127 and passed on the single re-run; streets
failed twice before its re-promotion, then passed twice). The `<level>/linux/`
set is stale on the re-captured levels until the box re-captures.
- **2026-10-05 (D540, linux re-base + first `deck` set):** same final fog code,
captured concurrently on the two boxes. **linux** (X220, `-j 1`, rebuilt from
the working tree): pass A against the existing set; 13 levels already within
limit and were left; re-captured aztec, cradle, egypt, statue, surface1, dam,
jungle (A/B 0.00-0.45%, dam 0.000%, jungle 1.7% under its 3% limit; dam/jungle
differ from the old set by the fog change, 8.4% / 37.9%). streets/silo A/B run
0.3-3.0% on the box (left as captured earlier; they match their goldens).
**deck** (Steam Deck, Desktop Mode, the X220-built binary + bundled SDL2,
`capture_p7.sh -p deck -j 2`): all 21 levels, A/B <= 0.04% except jungle
1.65% and statue 0.44%; `-j 3` failed its validation on the Deck (archives
4.96%, frigate 7-11%), `-j 2` agreed (<= 0.031%), 286 s per 21-level pass.
Both tools now set `GE_FAKE_DECK=0` on every run: on a Deck the D283 preset
otherwise forces 1280x800 fullscreen over the pinned ini. Also on 2026-10-05,
before the final fog design, a first D540 win re-base (17 levels) was
superseded by re-base #2 above; the levels it alone touched pass re-base #2's
confirming pass. **Run sweeps with the GPU otherwise idle** (no local LLM
loaded): 60 fps pacing is part of the gate (D117).
- **2026-10-06 (v0.5.0 re-base, linux + deck):** D543 (CPU near-plane clip +
per-vertex N64 fog) and D546 (Draw/LOD 2.0x default) re-captured on both
boxes concurrently from one X220-built binary (tree `21c5b5c5`), canonical
eep md5-checked on each box. Two full passes each (X220 `-j 1` 551 s/pass,
Deck `-j 2` 286 s/pass, every run rc 0). Pass A vs B over tol 2: deck 63/63
within limit; linux 60/63 — Dam drifted 5.2-6.0% between A and B (Bond's
settle position after the wall-clock-paced flyby, D117), so two Dam-only
runs C/D were taken: C vs D <= 0.022% and C vs the previous golden <= 0.023%,
so C was promoted (A and B were the outliers). Change vs the previous set is
concentrated where fog/draw distance bites: Statue ~26%, Surface 2 20-27%,
Streets 19-20%, Surface 1 ~13%, Depot ~10%, Cradle ~5%; indoor levels
<= 0.4%. Cradle and Frigate compared by eye against 1964/GEPD stills: match.
**win (same day, same tree):** `capture_p7.sh` now runs on Windows too
(Git Bash; `-b build-pc -p win`, no DISPLAY; picks the python that has
Pillow). Two passes at `-j 3` on the dev box (190 s each, every run rc 0,
a local LLM resident on the GPU): 62/63 A/B-stable; Streets 1400 jittered
1.5% (timer digits), two Streets-only `-j 1` runs C/D sat within 0.81% of
pass B, so B was promoted for Streets, A elsewhere. Deltas vs the previous
win set mirror linux/deck (Statue ~26%, Surface 2 ~27%, Streets ~20%,
Surface 1 ~13%, Depot ~10%, Cradle ~5%). Cross-platform spread on the new
sets, over tol 2, non-Cuba: win-linux 0.406-4.553%, win-deck 0.188-3.264%,
linux-deck 0.281-3.828% (informational, not a parity gate).
- **2026-10-06 (D553, near-clip fog recomputed):** Windows sweep after the
change flagged surface1/depot/cradle; re-captured surface1/depot/statue/
cradle on all three platforms, two passes each. Only **cradle** really
moved (~4.9% over tol 2, A/B <= 0.12% on every platform; the near walkway
is very slightly lighter, still in line with the 1964 still) and was
re-based x3. depot/surface1 "regressions" were `-j 3` pacing flakes with a
local LLM on the GPU: captured alone, depot is pixel-identical to its
golden and surface1/statue are within 0.5% -- those sets were left as-is.
The Deck must be in Desktop Mode for captures (Game Mode = Wayland/
gamescope: every run times out with 0 frames).
Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 347 KiB

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 KiB

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 KiB

After

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 KiB

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 KiB

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 KiB

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

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