diff --git a/.github/release-notes.md b/.github/release-notes.md index 83c6655b..fe1f8b97 100644 --- a/.github/release-notes.md +++ b/.github/release-notes.md @@ -1,7 +1,7 @@ ## GoldenEye 007 PC Port

- ~32 s gameplay montage from live play sessions (no audio track)

@@ -10,15 +10,22 @@ > is completable end to end: the whole campaign has been playtested through > all 21 missions (Agent difficulty). It's an early public cut: all 21 solo > missions load and run crash-free, but the known issues below are real; -> feedback is very welcome. +> feedback is very welcome. **This is a pre-1.0 release, not a finished +> product** — expect rough edges and missing features until v1.0; see the +> [README's Roadmap section](https://github.com/jkdansereau/goldeneye-pc-port#roadmap) +> for where this is headed. ### What's new since v0.2.0 Small hotfix batch from user playtest feedback on v0.2.0: -- **Fixed: `All unlocked` + a fresh save produced silent music/SFX** (D259). - A brand-new save now seeds max volume the same way the N64 does, whether - or not `All unlocked` is on. +- **Partially fixed: `All unlocked` + a completely fresh EEPROM produced + silent music/SFX** (D259). A brand-new save slot now seeds max volume the + same way the N64 does. **However, user testing after this fix still found + audio (and occasionally right-mouse aim) break when `All unlocked` is + turned on *before* a save has ever been written** — see the Known Issues + note below; do not enable it on a fresh install until you've completed at + least one level normally. - **Mouse wheel weapon cycling now matches the on-screen wheel-menu order**: scroll up = previous weapon, scroll down = next (D260). - **Watch menu: holding a direction now auto-repeats** instead of crawling @@ -46,7 +53,8 @@ Small hotfix batch from user playtest feedback on v0.2.0: - **Steady 60 fps** in normal play (the software RSP runs off the presentation critical path); `Video.DisplayFPS` in the F10 overlay shows it. - **Audio**: in-level music and sound effects throughout (the alpha was - silent). A handful of tracks have wrong-sounding instruments (below). + silent). A previous wrong-sounding-instrument bug (bad bass synth on + Control/Cavern/Runway) is fixed as of v0.2.0. - **Mouse**: click-to-lock capture (click to grab, ESC to release) with a proportional GEPD-style aim mode; sensitivity / Y-inversion / aim-turn split tunable in `ge007.ini` or the F10 overlay. The legacy always-grab mode is @@ -66,15 +74,22 @@ Small hotfix batch from user playtest feedback on v0.2.0: before they become visible (Dam's alarms and wall switches being the tell); F10 → *Draw distance* / *LOD distance* set back to 100 restores the console-authentic look. MSAA now defaults to 4× instead of off. -- **All-unlocked toggle**: F10 → *All unlocked* makes all 21 solo levels - selectable at every difficulty from the first launch, adds 007 mode, and - fully populates the cheat menu. It is off by default (faithful N64 +- **All-unlocked toggle** *(experimental — see Known Issues before using)*: + F10 → *All unlocked* makes all 21 solo levels selectable at every + difficulty, adds 007 mode, and fully populates the cheat menu. It is off by + default (faithful N64 progression). No *active* cheats are enabled either way (weapons remain per-mission pickups, as on N64). - **Steam Deck first-run preset**: on SteamOS the first launch seeds Deck-friendly defaults (native 1280×800 fullscreen, VSync, MSAA 4, 150% - draw/LOD distance); an existing `ge007.ini` always wins. One more F10 row: - *No hit flash* (suppresses the damage-flash overlay). + draw/LOD distance); an existing `ge007.ini` always wins. **If you first + launch it in Desktop Mode (e.g. to add it via Steam) before ever running + it in Game Mode, the preset can be skipped** — an ini gets created before + Game Mode's Deck-specific environment is detected, and once an ini exists + it always wins over the preset on every later launch. If your resolution + isn't 1280×800 on first Game Mode boot, just set it manually: F10 → + *Resolution* (D283). One more F10 row: *No hit flash* (suppresses the + damage-flash overlay). - **Modern dual-stick controller layout** (the scheme used by the console re-releases): left stick move/strafe, right stick look, right trigger fire, left trigger aim, A/X use, B/Y crouch/cancel, **RB/LB cycle weapons**. @@ -106,14 +121,39 @@ Small hotfix batch from user playtest feedback on v0.2.0: - **Surface 1: the 2D billboard trees near the start render as a solid wall of tree texture** instead of discrete sprites (D236). Under active investigation; no fix in this release. -- **Steam Deck: one intermittent crash remains**: the v0.1.0-era Facility - crash is confirmed fixed on Deck hardware, but a SIGSEGV can occasionally - occur during heavy firefights (D255; seen on Bunker and Frigate). The - faulting-register capture in `ge007.crash.log` has pinned it: an object - record whose model reference is NULL while the record is still active; - i.e. something in a mid-firefight destruction leaves the record live one - tick too long. We're tracking down the exact destruction path; more Deck - playtime across levels is welcome; please report any Deck-specific faults. +- **No true widescreen support**: at 16:9 the game stretches the + 4:3-authored view (world geometry and HUD) rather than properly expanding + the horizontal field of view. The F10 *FOV scale %* slider is a manual, + imperfect workaround for the resulting distortion, not real widescreen. +- **No controller rebinding UI, and no macOS or ARM builds.** +- **Distant geometry can drop out on the biggest open levels** (Streets, + Egyptian) at default FOV — a culling/LOD issue that sometimes + self-corrects as you keep moving (D249). +- **Gunshot SFX can sound off during sustained/rapid fire**: cadence can + drift from the N64 original's rate, and PP7/AK47 fire can occasionally go + silent under heavy automatic fire near another looping sound (e.g. an + alarm klaxon) (D240/D241). +- **`All unlocked` is highly experimental — do not enable it until you have + at least one save written.** Complete a level normally first (e.g. Dam on + Agent), then turn the toggle on. Enabling it on a brand-new install with + no prior save can still cause silent audio and odd right-mouse-aim + behavior even after the D259 fix above. We recommend leaving it off unless + you specifically want the unlock goodies and are willing to accept an + experimental feature (D259/D257). +- **Linux / Steam Deck: an intermittent SIGSEGV remains, with a reliable + repro found this release** — Facility's computer terminals, the ones you + activate to open a door for level progression, consistently crash the + game on activation (D255; not Deck-hardware-specific, seen on Linux + generally). Combat-related crashes reported earlier on Bunker/Frigate may + be the same underlying bug (an object record whose model reference goes + NULL while the record is still active for one more tick) via a different + trigger. We're tracking down the exact cause using the terminal repro; + please report any other Deck/Linux-specific faults. +- **Steam Deck / gamepad: the right analog stick currently navigates the + main menu, file select, and mission-select map** — not the left stick. + This matches the N64 default but is unintuitive on a modern controller; + moving front-end menu navigation to the left stick is a planned QoL fix, + not done in this release. ### Downloads @@ -165,4 +205,9 @@ sha256sum -c goldeneye-pc-port--linux-x86_64.tar.gz.sha256 [GoldenEye 007 decompilation](https://github.com/n64decomp/007), architecture after the [Perfect Dark PC port](https://github.com/fgsfdsfgs/perfect_dark). Non-commercial fan preservation/research project; not affiliated with any -rights holder. +rights holder. **AI disclosure:** built through agentic AI coding (Claude +Code + a local open-weight model), directed by one person in their spare +time — as much a study of what agentic development gets wrong on a +game-sized codebase as it is a port. See the README's +[Background section](https://github.com/jkdansereau/goldeneye-pc-port#background) +for the full account. diff --git a/README.md b/README.md index 52e92201..93926484 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,20 @@ Linux bundle sideloads as-is) and runs the full campaign at a steady 60 fps with known rough edges ([Status](#status)). Free to download, build on and modify (you bring the ROM). -It's also a case study in AI-agent collaboration on a large, low-level -codebase: two coding agents, driven by one person part-time, porting ~230 -translation units of unmodified big-endian MIPS game code to a 64-bit desktop. -See [Background](#background). +**This is a pre-1.0 release, not a finished product.** v1.0 is the target +for a polished, feature-complete build; until then, expect rough edges, +missing features, and breaking changes between versions. See +[Status](#status) for what works today and [Roadmap](#roadmap) for where +this is headed. + +**AI disclosure:** development here was agentic - Claude Pro plus a local +open-weight model on a single RTX 5090, as of August–September 2026. This +project is as much a study of *that process* as it is a port: whether +current LLMs can carry a codebase like this, and what actually goes wrong +along the way. Judge the result for yourself. +I'm one person doing this in my spare time, not a team. See +[Background](#background) for the full setup, timeline, and an honest +account of what worked and what didn't. > [!IMPORTANT] > **You must supply your own GoldenEye 007 ROM.** This repository contains no @@ -94,8 +104,29 @@ Windows and Linux. (D251). - Surface 1's 2D billboard trees render as a solid wall of tree texture instead of discrete sprites (D236). Under active investigation. -- **Steam Deck:** one intermittent SIGSEGV remains in heavy firefights (D255); - current builds capture full faulting registers in `ge007.crash.log`. +- No true widescreen: 16:9 stretches the 4:3-authored view (world + HUD) + rather than properly expanding the horizontal FOV; the F10 *FOV scale %* + slider is a manual workaround, not real widescreen. +- Distant geometry can drop out on the biggest open levels (Streets, + Egyptian) at default FOV — a culling/LOD issue that sometimes + self-corrects as you keep moving (D249). +- Gunshot SFX can sound off during sustained/rapid fire: cadence can drift + from the N64 original's rate, and PP7/AK47 fire can occasionally go silent + under heavy automatic fire near another looping sound (D240/D241). +- **`All unlocked` is highly experimental — don't enable it until you have + at least one save written** (complete a level normally first, e.g. Dam on + Agent). Enabling it on a brand-new install with no prior save can still + cause silent audio and odd right-mouse-aim behavior (D257/D259/D281). +- **Linux / Steam Deck:** an intermittent SIGSEGV remains, with a reliable + repro found this release — Facility's computer terminals (the ones you + activate to open a door for level progression) consistently crash the + game on activation (D255; seen on Linux generally, not Deck-hardware + specific). Combat-related crashes reported earlier (Bunker, Frigate) may + be the same bug via a different trigger; current builds capture full + faulting registers in `ge007.crash.log`. +- Steam Deck / gamepad: front-end menu navigation (main menu, file select, + mission-select map) currently requires the right analog stick, not the + left (D282) — a planned QoL fix, not done in this release. - Assorted further cosmetic defects are tracked in [`docs/dev/GRAPHICS-BACKLOG.md`](docs/dev/GRAPHICS-BACKLOG.md). - No macOS or ARM support; no controller rebinding UI. @@ -112,7 +143,13 @@ derived assets), and add the executable as a non-Steam game. SDL2 is bundled, so installing. On SteamOS the first launch seeds `ge007.ini` with Deck-friendly defaults: native 1280×800 fullscreen, VSync, MSAA 4, and 150% draw/LOD distance (the authored N64 fade distances read short on the close-up panel); -everything is changeable in the options overlay and persists afterwards. The renderer is CPU-bound (software RSP); expect original N64-era +everything is changeable in the options overlay and persists afterwards. +**Do that first launch in Game Mode, not Desktop Mode** — an ini created by +an earlier Desktop Mode launch (e.g. while testing before adding it as a +Steam shortcut) permanently skips the Deck preset, since any existing ini +always wins over it (D283). If your resolution isn't 1280×800 on first +Game Mode boot, just set it manually: F10 → *Resolution*. The renderer is +CPU-bound (software RSP); expect original N64-era performance at 60 fps rather than more. This release was playtested on real Deck hardware; the v0.1.0-era Facility crash (D203) did not recur: its root cause was identified and fixed (D253), verified on the Deck; one intermittent @@ -125,6 +162,27 @@ options, **A** steps the selected option forward, **B** steps it back, and filtering cycle, sliders step in increments. With a keyboard attached the same overlay is `F10` + arrows/Enter. +## Roadmap + +No fixed timeline or committed feature list — this is spare-time work — but +directionally, on the way to v1.0: + +- Working through the [known issues](#status) above and the fuller list in + [`docs/dev/findings.md`](docs/dev/findings.md). +- **PAL and JP ROM support** ([issue #85](https://github.com/jkdansereau/goldeneye-pc-port/issues/85)); NTSC-U is the only supported region today. +- **Real widescreen** (properly expanding the field of view at 16:9, rather + than today's 4:3-stretch). +- **Controller rebinding UI**, and macOS/ARM builds. +- **LAN multiplayer**: reviving GoldenEye's original split-screen/deathmatch + netplay across multiple PCs on a local network. Genuinely under + consideration, but early and not started; no ETA. +- General polish: performance, remaining rendering/audio defects, save/config + robustness. + +Not currently planned: new game modes GE never shipped (e.g. co-op), online (non-LAN) +multiplayer, ray tracing. If any of these matter to you, open an issue — +it helps prioritize. + ## Beyond playing - **Tweak it**: `ge007.ini` and the F10 in-game overlay expose resolution, diff --git a/docs/dev/findings-index.csv b/docs/dev/findings-index.csv index ac33de7a..dd50b148 100644 --- a/docs/dev/findings-index.csv +++ b/docs/dev/findings-index.csv @@ -96,6 +96,9 @@ D270,?,Backfilled (M-132) — this label was never given its own row; only code D271,FIXED,"D236 wall, third pass —","FIXED-but-insufficient (src/game/bg.c #ifdef PORT, uncommitted) —" D276,OPEN,"D236 wall, eighth pass —","REFRAMED — oml=0xc81049d8/fmt=3 is the primary suspect now, not D269's CI8 pair; 0x0c184b50 unattributed; wall still open, see D236" D279,?,"Backfilled — this label was consumed by an undocumented partial session (2026-09-14 ~23:00) that left artifacts in scratch/ (d279_run.log, d279_imgmap_run.log,…",undocumented — artifacts only +D281,OPEN,"Game.AllUnlocked (D257) still breaks audio, and can also break right-mouse aim, when enabled before any save has ever been written (v0.2.1 user testing, 2026-0…","OPEN — user-reported regression, not root-caused; mitigation is user-facing guidance only (release notes:" +D282,OPEN,"QoL ask (v0.2.1 user testing, 2026-09-15):","OPEN — not designed; QoL, port-only." +D283,OPEN,"Steam Deck first-run preset (D-unnumbered in port/src/video.c/main.c, videoApplySteamOSDefaults()) did not apply for a user who added the game via Steam in Des…","OPEN — mechanism understood, root cause (Desktop-vs-Game-Mode STEAMOS env var difference) unverified on real hardware; user-facing workaround documented in README/release notes." D280,?,"D236 wall, ninth pass —","REFRAMED again — no fix; next steps in detail section, see D236" D275,?,"D236 wall, eighth pass —","done — probes live, uncommitted; see D276 for what they found" D274,OPEN,"D236 wall, seventh pass —","d267_slot_addr fix live (always-on); content/decode question CLOSED; mip-presence CLOSED; wall still open, see D236/D276" @@ -147,7 +150,7 @@ D251,FIXED,F10 options overlay QoL pass:,"FIXED (port-only, port/src/optionsover D252,?,"Particle effects cycle through a rainbow palette (M-124, user playtest of the v0.2.0-pre candidate).", D253,FIXED,Facility (and any level with a FORCECROUCH/LADDER linked special tile) SIGSEGV on Linux/Steam Deck —,FIXED — Deck-verified: D254,FIXED,Linux crash handler destroyed its own logs:,FIXED — field-verified. -D255,OPEN,"Facility firefight SIGSEGV on Steam Deck (v0.2.0-pre CI bundle, D253 fix confirmed working —","OPEN — case identified (NULL model on an active OBJ record; destroy-path ordering), reproducible on Deck in combat (Bunker + Frigate); fix pending rule-#2 sign-off." +D255,OPEN,"Facility firefight SIGSEGV on Steam Deck (v0.2.0-pre CI bundle, D253 fix confirmed working —",OPEN — case identified (NULL model on an active OBJ record; destroy-path ordering); reliable deterministic repro is Facility's door-opening computer terminals on activation (Linux/Steam Deck); earlie… D256,FIXED,Saves (and config) silently failed to persist on Linux whenever the game was not launched from the directory containing data/ —,FIXED (path half) — save-flow detail still owed from the user. D257,FIXED,New Game.AllUnlocked option (F10; default flipped OFF for v0.2.0 —,FIXED — verified by user playtest (PR #79 merged); default flipped OFF for v0.2.0 (D259). D259,FIXED,Game.AllUnlocked (D257) breaks audio on a fresh save:,FIXED in v0.2.1 — the deferred fix was applied: @@ -155,6 +158,6 @@ D260,FIXED,v0.2.1 (user request):,"FIXED — build green; one-line branch swap, D261,FIXED,v0.2.1 (user request:,FIXED — feel-check received; W/S sensitivity follow-up logged above. D262,FIXED,v0.2.1 (user report):,"FIXED (primary cause, D264) —" D264,FIXED,"D262's root cause (ABI/layout class, cf.","FIXED — build green, probes removed; visual confirmation pending." -D263,RESOLVED,v0.2.1 (user report):,RESOLVED (not a bug) — +D263,RESOLVED,v0.2.1 (user report):,"RESOLVED (not a bug), but refined (v0.2.1 further user testing, 2026-09-15) —" D265,?,"D236 investigation (port-layer, port/fast3d/gfx_pc.cpp):",PARKED — reverted to baseline; evidence + suspect list above. D258,OPEN,PAL/JP asset conversion is broken at the source-data level —,"OPEN — repair path verified, not applied (out of v0.2.0 scope)." diff --git a/docs/dev/findings.md b/docs/dev/findings.md index 64ffaae6..29738a44 100644 --- a/docs/dev/findings.md +++ b/docs/dev/findings.md @@ -484,6 +484,9 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D271 | **D236 wall, third pass — suspect #3 (draw density/culling) CONFIRMED as a real over-draw, and the D106 guard was the cause.** At Surface 1's boot camera, 11 portals around room 13 straddle the near plane every frame; their z==0 clip points project to ±1e20–1e26-scale FINITE screen coords. The original D106 guard (`lim=1e5`) mapped any such bound to a full-screen box — but on N64 there is no guard: those finite values flow into `bgRectIntersect` (pure comparisons), which either clamps to the parent box (portal covers view → keep) or yields an empty box (portal projects off-screen → **cull**). 9 of the 11 portals clamp empty on N64; PC was force-drawing those rooms. Fix: guard `lim` 1e5→1e38 (catches only NaN/inf, which this projection can't reach — max ~1e26); finite min>max still hits the original `degenerate` check (N64's own path, D106's real regression case unchanged). Verified: `d106Fallback` 396–781/600 ticks → **0** (Surface 1 + BUNKER1); roomsDrawn 17–18→10–13 at the boot view; per-portal oor-finite dumps confirm every culled portal's finite portion is far off-screen (N64 clamps identically); BUNKER1 room drops traced to the same N64-matching empty clamp. No crashes in long unattended runs. **USER RE-TEST (2026-09-14): NEGATIVE — trees still render as a big wall; user's read: "rather than the texture being mapped correctly"** → suspect #3 was a real over-draw but NOT the wall's root cause; fix kept for N64 fidelity. D236 thread continues — top suspect now per-card S/T / UV windowing (D265 note: all captured cards sampled the same u/v window) | FIXED-but-insufficient (`src/game/bg.c` `#ifdef PORT`, uncommitted) — probes live (`GE_D271`, extended `GE_D104`); wall still open, see D236 | | D276 | **D236 wall, eighth pass — the true painter of the visible weave, at confirmed weave pixels, is a fully-opaque IA8 blocky-noise texture sampled with a fixed, identical 2×-wrapped UV window and low fog — NOT either of D269's named CI8 classes.** Multi-point pixel attribution + per-vertex fog (D275's `GE_D270PIX` list extension) at 4 pixels picked directly off the reproduced weave screenshot found the D269 OPA/XLU classes absent at 2 of them; the dominant front-most class there (`oml` low bits coincide with the XLU class but `fmt=3`/IA, not CI8) decodes to blocky gray static, matches D267's already-dumped-but-dismissed "seq 113" texture, and reproduces D265's original "all cards sample u:0..64/v:0..16" observation to the exact texel — that lead was reframed away from prematurely in D269's second pass. A third class (`0x0c184b50`, CI8) is present everywhere with erratic z/w (`-0.04..0.99`) and high fog when it does land normally — flagged as the possible real "tree" layer, with its own likely occlusion/billboard bug, meaning D236 may have two separate root causes, not one. | REFRAMED — `oml=0xc81049d8`/`fmt=3` is the primary suspect now, not D269's CI8 pair; `0x0c184b50` unattributed; wall still open, see D236 | | D279 | **Backfilled — this label was consumed by an undocumented partial session (2026-09-14 ~23:00) that left artifacts in `scratch/` (`d279_run.log`, `d279_imgmap_run.log`, `d279_g110.png`, `d279_g220*.png`, `d279_gray_1500.png`) but no written conclusion; the logs carry no D279-tagged lines, so its exact probe/finding cannot be reconstructed from the tree.** Next available label after it: D280. | undocumented — artifacts only | +| D281 | **`Game.AllUnlocked` (D257) still breaks audio, and can also break right-mouse aim, when enabled before any save has ever been written (v0.2.1 user testing, 2026-09-15) — a distinct regression from D259, which only fixed the all-zero-slot volume case.** User's empirical workaround: complete a level normally first (e.g. Dam on Agent, which writes a real save), THEN enable `All unlocked`; enabling it on a completely fresh install (no save yet) still reproduces both symptoms. Not root-caused this session — D259's `geEepromPatchAllCheats` all-zero-slot seeding was verified against exactly the "fresh eep" case, so either that fix doesn't cover every code path a truly-first-launch toggle can hit (e.g. a race between the F10 toggle taking effect and the very first save-slot creation/read), or there's a second, independent mechanism. The right-mouse-aim symptom is new information with no prior lead — cross-ref D211/D194 (aim-mode sensitivity/mode family) for where a save-state-dependent aim regression could originate. | OPEN — user-reported regression, not root-caused; mitigation is user-facing guidance only (release notes: don't enable until a save exists, treat as experimental). | +| D282 | **QoL ask (v0.2.1 user testing, 2026-09-15): front-end menu navigation (main menu, file select, mission-select map) currently requires the RIGHT analog stick — matches N64 default control schemes but is unintuitive on a modern controller/Steam Deck, where the left stick is the expected primary-navigation input.** Distinct from the F10 options overlay, which is already left-stick/D-pad navigable (`port/src/optionsoverlay.c`). Scope: front-end menu input in `src/game/front.c` (or wherever N64 stick reads are gated by the active `controldef`) — likely wants a port-layer input remap for these menu contexts specifically, not a general control-scheme change. | OPEN — not designed; QoL, port-only. | +| D283 | **Steam Deck first-run preset (D-unnumbered in `port/src/video.c`/`main.c`, `videoApplySteamOSDefaults()`) did not apply for a user who added the game via Steam in Desktop Mode and switched to Game Mode afterward — resolution stayed at some non-native value instead of the Deck's native 1280×800 (v0.2.1 user testing, 2026-09-15).** Root mechanism, confirmed by code inspection: the preset only applies when `getenv("STEAMOS")` is truthy AND no `ge007.ini` exists yet (`main.c:126-129`, before `configLoad()`); once any ini exists, it always wins over the preset on every future launch (by design, so user F10 changes persist). **Leading hypothesis (not verified on real hardware in this environment):** SteamOS's Desktop Mode (a normal Plasma/X11 session) likely does not export the same `STEAMOS` env var that the Game Mode/gamescope session does, so a Desktop Mode test-launch (a natural step when adding a non-Steam game via its file browser) creates `ge007.ini` with ordinary Linux defaults *before* the user ever reaches Game Mode — permanently skipping the preset for that install, since the ini already exists on the first real Game Mode launch. This exact caveat was already known internally (`docs/HANDOFF.md`: "a Deck that already has an ini needs `-fresh` or F10 to pick the preset up") but was never surfaced in user-facing docs (README/release notes) — same class of gap as the stale D230 release-notes reference. **Workaround (lighter than `-fresh`, which also wipes the save):** F10 → Resolution → 1280×800 manually; takes seconds, no data lost. | OPEN — mechanism understood, root cause (Desktop-vs-Game-Mode `STEAMOS` env var difference) unverified on real hardware; user-facing workaround documented in README/release notes. | | D280 | **D236 wall, ninth pass — fog math VERIFIED N64-faithful (±2 truncation over all 2505 verts of `0xc81049d8`; D276 owed step #1 closed), the IA8 noise class VISUALLY confirmed as the weave's painter (`GE_D278C` collapse: band HF 14.8→2.9, no trees appear without it — first unconfounded test), full census at both confirmed weave points (only 3 real classes; two log entries are probe artifacts), D276's depth-race read INVERTED (`0x0c184b50` tree cards are in FRONT of the noise quad, z/w smaller=closer), `0xc8102078` ruled out as trees (terrain, 1.4M tris), and `0x0c184b50` shown to barely instantiate under deterministic `-level_36` boot (~4 tris vs 720 free-run) with CPU-baked bimodal fog 25/255. **No quick win for trees** — remaining work is CPU-side instantiation/room-streaming, not a port fix. | REFRAMED again — no fix; next steps in detail section, see D236 | | D275 | **D236 wall, eighth pass — probe upgrades:** `GE_D270PIX` extended from a single point to a semicolon-separated list (each line now tagged `pix=(x,y)` + D274's reliable `addr=`); `GE_D270KEEPOML` gained a "kept N tris" counter so a black A/B capture is diagnosable (tick-misaligned vs. a genuine render-interaction bug). Mechanical groundwork for D276; the class-isolation A/B itself is still owed. | done — probes live, uncommitted; see D276 for what they found | | D274 | **D236 wall, seventh pass — probe identity (`seq`) proven unreliable both mid-run (cache-hit staleness) and cross-run (shared runtime-arena address reuse); fixed with an unconditional per-slot address stamp.** Resolved the prior pass's open "content vs. decode" question as a bug in this session's own offline analysis (wrong palette byte order — native/little-endian, not big-endian), not a port bug: the port's existing CI palette decode, applied correctly, reproduces a plausible soft gradient on the real, address-confirmed wall-card texture. Confirmed mips are generated (`genmip=1`) and enabled (`tex_lod=1`) for that texture, with correct `GL_REPEAT` wrap — ruling out "mips absent" as the mechanism. Attempted class-isolation A/B was inconclusive (black frame). | `d267_slot_addr` fix live (always-on); content/decode question CLOSED; mip-presence CLOSED; wall still open, see D236/D276 | @@ -543,7 +546,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D252 | **Particle effects cycle through a rainbow palette (M-124, user playtest of the v0.2.0-pre candidate).** Shooting repeatedly makes the bullet-impact "sparks" slowly progress through the full hue wheel (~10 shots to sweep it); other lingering particle effects — smoke / explosion residue — show the same rainbow colouration instead of their intended grey/orange palette. **Mechanism context (verified this session):** particle tris carry no per-primitive material; their colour comes entirely from the ROM-resident state records `globalDL_0x078 … globalDL_0xa50` (`assets/oddtextures.c`, 15 of them) replayed per particle batch (`explosion.c:894`). Each record binds a **distinct** texture pair — `IMAGE_SMOKE_N` (IA, tile 0 @ TMEM 0) × `IMAGE_FIRE_N` (RGBA16, tile 1 @ TMEM 0x188) under `G_CC_INTERFERENCE` (TEXEL0·TEXEL1), and `explosionRenderPart` uses a fixed all-white default vertex (`explosion.c:215`) — so the intended per-stage colour is whatever those 15 fire/smoke pairs encode, and particles **age through the records** (`var_s2` 14→0 at `frame/flareanimspeed`), so a per-texture decode error surfaces exactly as a hue sweep over time / across shots. **Ruled out this session:** (a) the M-82/D172 tile-offset fix is correctly configured for this path — `gsDPSetTextureLOD(G_TL_TILE)` does NOT set the `G_TL_LOD` bit, so `rdp.tex_lod` stays false (`gfx_pc.cpp:2860`) and `gfx_lod_tile_offset` returns `i`, i.e. TEXEL1 genuinely samples tile 1; (b) the RGBA16/5551 texel decode in `import_texture_rgba16` is correct (big-endian, R=bits15-11 G=10-6 B=5-1 A=bit0). **Prime suspect: the width/height derivation in `import_texture_rgba16`** — `width = rdp.texture_tile[tile].line_size_bytes / 2`, `height = size_bytes / line_size_bytes`. The fire tiles are 16 px wide with a MASK_16 TMEM line (64 bytes), so `width` computes 32 (true: 16) and, with `size_bytes` = the LOADBLOCK's 16×14×2 = 448, `height` computes 7 (true: 14) — the GL texture would be built from the right pixels at the wrong dimensions, scrambling each of the 15 fire sprites into a different wrong-hued smear; that fits "slow rainbow progression" precisely (each aging stage = a different scrambled sprite). **Not yet confirmed** — needs one imported fire tile dumped and compared against the raw `IMAGE_FIRE_N` bytes (`GE_TEXDUMP`/`GE_PCDUMP`, or a targeted probe at the `upload_texture` call). **Per user instruction: documented only, NOT fixed for v0.2.0-pre** — ships as a known issue; pick up afterwards (cheap if the suspect holds: derive width/height from the LOADBLOCK's dtx/dlh rather than the tile line size, or special-case non-32-texel-wide RGBA16 tiles). D172/M-82 lineage. | | D253 | **Facility (and any level with a FORCECROUCH/LADDER linked special tile) SIGSEGV on Linux/Steam Deck — `stanCheckLinkedSpecialTile` stores to a garbage `outFlags` pointer (D203 root cause).** The B-callback typedef `standTileLocusCallback_B_t` (`bondtypes.h:573`) declares the middle three params as `f32 f32 f32`, so on x86-64 SysV the caller (`sub_GAME_7F0B1DDC`, `stan.c:2228`) passes the 6th arg (`record`) in **`%rdx`** (3rd integer slot; the floats take xmm0–2). But the actual implementation `stanCheckLinkedSpecialTile` was decompiled with an all-`s32` prototype, so it reads `outFlags` from **`%r9`** (6th integer slot) — a stale scratch value left by earlier callees. On the N64 (MIPS o32) both prototypes agree: everything flows through GPRs a0–a5 in order, 6th arg = a5 either way, so the original is fine and the decompilation looked consistent. The mismatch stays dormant until a tile actually hits the FORCECROUCH or LADDER branch and *stores* through `outFlags` → SEGV; that's why it was level-specific (Facility's opening crouch tile) and input-dependent, and why idle repros were negative. The sibling B-callback `stanLocusEdgeIsAboveY` already had the correct float prototype, which is why only this path was booby-trapped. **Fix (ABI-class, semantics-preserving):** declare the three middle params of `stanCheckLinkedSpecialTile` as `f32` in both the forward decl and definition (`stan.c:152`, `stan.c:2407`) to match the typedef; the body never uses them, and on N64 o32 f32 args occupy the same GPRs, so N64 codegen is unaffected. **Verified:** gdb run on WSL (gcc 15) — pre-fix, SIGSEGV at `stan.c:2441` with `r9=0x3` and valid `record=0x409fea00` in the caller frame; post-fix, the same walk route completes with no crash. Side benefit: the FORCECROUCH/LADDER branches were effectively dead-on-arrival on PC (any hit = crash); they now function. **Also observed post-fix (separate, WSLg-only):** a segfault *inside libgallium* during `gfx_opengl_upload_texture` (32×48 RGBA + mipmaps) while walking further — GL-driver-side under WSLg's Mesa; not reproducible on Windows; park as a new finding if it recurs on real hardware. | FIXED — Deck-verified: user got past the crouch tile with the re-cut bundle (a *new*, later crash is D255). | | D254 | **Linux crash handler destroyed its own logs: `ge007.crash.log` only ever contained the SIGABRT block, never the original SEGV.** The POSIX `crashHandler` (`port/src/crash.c`) finished by calling `sysFatalError()` → `abort()` → SIGABRT, which re-entered the *same* handler (SIGABRT was one of the installed signals); the second pass reopened the log with `"wb"` and overwrote the first pass's SEGV block — faulting PC, signal, everything. The Deck firefight crash (D255) came back as a bare `SIGNAL: 6` abort record for exactly this reason, forcing offline disassembly of the CI binary to recover what the log should have said. **Fix (port-only):** re-entrancy guard (`inCrashHandler`, second pass `_exit(128+sig)` immediately) + terminate directly with `_exit()` instead of `sysFatalError()` (the Windows path already terminated directly for the same reason) + the Linux block now dumps the faulting context: `FAULT ADDR` (`si_addr`) and the full general register set (Rax/Rcx/Rdx/Rsi/Rdi/R8–R11, Rsp/Rbp) from the `ucontext`, so a field crash now names the bad pointer on its face. **Field-verified 2026-09-13:** the D255 Deck crash came back as `SIGNAL: 11` + `FAULT ADDR: 0x10` + full register set — exactly the format designed here; it directly identified the NULL-model case (see D255). | FIXED — field-verified. | -| D255 | **Facility firefight SIGSEGV on Steam Deck (v0.2.0-pre CI bundle, D253 fix confirmed working — user got past the opening crouch tile).** Crash during gunplay; abort-unwind backtrace points at `sub_GAME_7F09BAC4` (`vtxstore.c:158`) ← `sub_GAME_7F09BBBC` (`:220`) ← `lvlManageMpGame` (`lv.c:1302`) ← `bossMainloop` (`boss.c:567`). Disassembling the exact CI binary at the fault PC (`0x200db135`) gives `mov 0x14(%rax),%edi; mov 0x10(%rdi),%rax`: the function walks the active PropRecord list keeping `type == 1` — which is **PROP_TYPE_OBJ, not CHR** (the decompiler cast to the union's first member and misnamed the field `ChrRecord.chrflags`; it is actually `ObjectRecord.model` at `0x14`) — then dereferences `model->obj`. Two port-relevant wrinkles: (a) the load is **32-bit zero-extended** because the decomp types the field through the `CHRFLAG` enum, so a model pointer ≥ 4 GB would truncate to garbage; (b) the loop skips records whose `model->obj` is NULL but *dereferences* `model` unconditionally, so an active object with a NULL/freed `model` faults — plausible in a firefight where destructibles (crates etc.) die. Not reproduced on WSL after equivalent play. **Register capture (D254 format, 2026-09-13):** two more Deck crashes — Bunker mid-firefight, then Frigate with ~3 NPCs alive; log: `SIGNAL: 11 / FAULT ADDR: 0x10 / Rax=0x701e5f54 Rdi=(nil) / PC 0x200db135`. `Rdi` = the record's model value = **exactly NULL** → case (b) confirmed: an active PROP_TYPE_OBJ record with a NULL `model`, dereferenced by the unconditional `mov 0x10(%rdi),%rax`; truncation (a) and stale/garbage pointers are ruled out. Faulting PC matches the recorded disassembly (`sub_GAME_7F09BAC4` = `vtxstore_fix_refs`). Grep-verified: nothing in game code ever writes NULL into `ObjectRecord.model` — `clear_model_obj()` nulls `model->obj` only, and `objInit`/spawn paths only activate a record once model ≠ NULL — so the NULL comes from either a fresh/uninitialized record slot being activated or a destroy-path ordering race (model freed/nulled while the record is still active this tick). **Fix constraint:** any guard lands in game code (`vtxstore.c`) → needs rule-#2 sign-off; `port/` cannot intercept this dereference. Next: (1) decision on whether a NULL-model skip in `vtxstore_fix_refs` qualifies as an ABI-class exception (semantics-preserving defensive guard, PD analogue check); (2) if yes, add it plus a `#ifdef PORT` trace at the deactivation/model-free sites to name the path that leaves the record model-less. | OPEN — case identified (NULL model on an active OBJ record; destroy-path ordering), reproducible on Deck in combat (Bunker + Frigate); fix pending rule-#2 sign-off. | +| D255 | **Facility firefight SIGSEGV on Steam Deck (v0.2.0-pre CI bundle, D253 fix confirmed working — user got past the opening crouch tile).** Crash during gunplay; abort-unwind backtrace points at `sub_GAME_7F09BAC4` (`vtxstore.c:158`) ← `sub_GAME_7F09BBBC` (`:220`) ← `lvlManageMpGame` (`lv.c:1302`) ← `bossMainloop` (`boss.c:567`). Disassembling the exact CI binary at the fault PC (`0x200db135`) gives `mov 0x14(%rax),%edi; mov 0x10(%rdi),%rax`: the function walks the active PropRecord list keeping `type == 1` — which is **PROP_TYPE_OBJ, not CHR** (the decompiler cast to the union's first member and misnamed the field `ChrRecord.chrflags`; it is actually `ObjectRecord.model` at `0x14`) — then dereferences `model->obj`. Two port-relevant wrinkles: (a) the load is **32-bit zero-extended** because the decomp types the field through the `CHRFLAG` enum, so a model pointer ≥ 4 GB would truncate to garbage; (b) the loop skips records whose `model->obj` is NULL but *dereferences* `model` unconditionally, so an active object with a NULL/freed `model` faults — plausible in a firefight where destructibles (crates etc.) die. Not reproduced on WSL after equivalent play. **Register capture (D254 format, 2026-09-13):** two more Deck crashes — Bunker mid-firefight, then Frigate with ~3 NPCs alive; log: `SIGNAL: 11 / FAULT ADDR: 0x10 / Rax=0x701e5f54 Rdi=(nil) / PC 0x200db135`. `Rdi` = the record's model value = **exactly NULL** → case (b) confirmed: an active PROP_TYPE_OBJ record with a NULL `model`, dereferenced by the unconditional `mov 0x10(%rdi),%rax`; truncation (a) and stale/garbage pointers are ruled out. Faulting PC matches the recorded disassembly (`sub_GAME_7F09BAC4` = `vtxstore_fix_refs`). Grep-verified: nothing in game code ever writes NULL into `ObjectRecord.model` — `clear_model_obj()` nulls `model->obj` only, and `objInit`/spawn paths only activate a record once model ≠ NULL — so the NULL comes from either a fresh/uninitialized record slot being activated or a destroy-path ordering race (model freed/nulled while the record is still active this tick). **Fix constraint:** any guard lands in game code (`vtxstore.c`) → needs rule-#2 sign-off; `port/` cannot intercept this dereference. Next: (1) decision on whether a NULL-model skip in `vtxstore_fix_refs` qualifies as an ABI-class exception (semantics-preserving defensive guard, PD analogue check); (2) if yes, add it plus a `#ifdef PORT` trace at the deactivation/model-free sites to name the path that leaves the record model-less. **Corrected, deterministic repro (v0.2.1 user testing, 2026-09-15, superseding the initial report): the trigger is Facility's computer terminals — the ones you ACTIVATE (interact with, not shoot/destroy) to open a door for level progression.** User confirms this is consistent/reliably reproducible on those specific terminals, on Steam Deck **and** Linux generally (not Deck-hardware-specific). This is a much stronger lead than the original "sometimes during firefights" — a terminal activation deactivating/freeing its associated object model while the record briefly stays active this tick is fully consistent with the destroy-path-ordering theory above, just via a scripted trigger instead of combat destruction, and gives a deterministic repro instead of a probabilistic one. **Next session should reproduce via terminal activation specifically**, not generic combat — much cheaper to instrument. | OPEN — case identified (NULL model on an active OBJ record; destroy-path ordering); reliable deterministic repro is Facility's door-opening computer terminals on activation (Linux/Steam Deck); earlier combat-related reports (Bunker, Frigate) may be the same mechanism via a different trigger; fix pending rule-#2 sign-off. | | D256 | **Saves (and config) silently failed to persist on Linux whenever the game was not launched from the directory containing `data/` — reported as "saves do not work at all" on Steam Deck.** `romdataInit` finds the ROM in `$S/` (CWD `data/`) *or* `$E/` (exe dir) *or* `./`, but `sysResolvePath`'s POSIX `$S` branch (used by `ge007.eep` and `ge007.ini`) only ever produced a CWD-relative path — its "fallback" was `./data/`, identical to the primary. Launch from any other CWD (Steam non-steam-game shortcut, terminal in `~`): ROM loads via the exe-dir candidate, game runs, but every EEPROM/config write hits `fopen(..., "wb")` on a nonexistent `./data/` and fails with a stderr-only "cannot write" — saves never persist, and **every launch also starts with default settings** (F10 changes don't stick), which presents as "draw distance/LOD is low" on levels like Dam where the user had raised `Video.DrawDistance` on another machine. The Windows branch already did CWD-else-exedir; the POSIX branch now mirrors it (`readlink("/proc/self/exe")`) and creates the directory best-effort so first-run writes don't fail. **Verified on WSL:** with exe+`data/` in `/tmp/fb2` and CWD=`/`, config/eeprom/ROM all resolve into `/tmp/fb2/data/` and a 25 s run completes clean; the pre-fix binary in the same layout fails to find the ROM at all. **Caveat:** if the user's Deck launch *was* from the bundle dir (CWD had `data/`), this fix does not explain their save failure — need the save-flow detail (does the memory-card screen appear? slot confirm? "saved" message?) before closing the report. Side note found while testing: a `data/` without the generated sidecars (`pccg-*/`, `pcmodels-*/` from `tools_pc/d69_emit.py`/`d43_emit.py`) starts fine but faults in `load_bg_file` (`bg.c:865`) on level load — the reserve functions warn "stage loads will fault" and return 0; expected degraded mode, not a new bug. | FIXED (path half) — save-flow detail still owed from the user. | | D257 | **New `Game.AllUnlocked` option (F10; default flipped OFF for v0.2.0 — see D259): skip N64 progression entirely — all 21 solo levels selectable at every difficulty, 007 mode available, and the cheat menu fully populated. Port-layer only; no game-code edits.** Three mechanisms, all in `port/`: (1) `main.c` seeds the two live `LEFTOVERDEBUG` RAM flags after `configLoad()` — `debug_enable_all_levels_flag` (all levels) and `debug_007_unlock_flag` (007 mode; both in `src/game/debugmenu_handler.c`, same class as the existing `GE_UNLOCK_ALL` getenv hook in the getter); (2) the eep shim (`libultra.c geEepromPatchAllCheats`) patches the save block at read time — special-casing `fileValidateSaves`'s exact pattern (block 4, 5×96 bytes) — setting all 20 progression-gated cheat-unlock bits per slot (`unlocked_cheats_1/2/3`; the cheat menu row on mode-select only renders when ≥1 is set, `front.c interface_menu06_modesel`) **and** filling every *empty* stage/difficulty completion-time field with the max time 0x3FF (existing player times preserved); both are then CRC'd per slot with the game's own `fileGenerateCRC` or `fileValidateSaves` would wipe the slots. (3) `optionsoverlay.c` row so users can toggle OFF for faithful progression. **Why the completion-time half is required (found by user repro):** when ANY single-player cheat is active, `g_AppendCheatSinglePlayer` makes mission-select (`get_highest_unlocked_difficulty_for_level`, front.c) accept only levels with status `STAGESTATUS_COMPLETED (3)`, not merely `UNLOCKED (1)` — AllUnlocked's debug-flag path returns 1, so enabling a cheat locked every level ("could only do Silo"). Reading the save as a fully-completed campaign satisfies both branches. Time bit-math mirrors `fileGet/SetSaveStageDifficultyTime` verbatim (10-bit fields, difficulties 0–2; 007 times are virtual). **Pitfall hit during implementation:** the first mirror of `save_data` used `times[80]` instead of `(SP_LEVEL_MAX-1)*4 = 76`, making `sizeof` 100≠96 so the block-read size check silently never matched — now enforced by a compile-time `ge_save_slot_size_check`. **Semantics:** unlocked cheats/completions persist to `ge007.eep` on any subsequent save write (self-consistent; mirrors N64 where completing a level permanently unlocks its cheat). No *active* cheats are enabled (invincibility/all-guns stay OFF); weapons remain per-mission pickups. **Verified:** fresh eep — all 5 slots validate `stored==calc` under `GE_SAVELOG=1`; user playtest: all levels selectable at each difficulty, 007 mode present, cheat menu populated, cheats toggle and work (time-modifying cheats untested by user), mission select intact with all cheats ON. | FIXED — verified by user playtest (PR #79 merged); default flipped OFF for v0.2.0 (D259). | | D259 | **`Game.AllUnlocked` (D257) breaks audio on a fresh save: with it ON, music and SFX load at volume 0 (silence). v0.2.0 ships with `AllUnlocked` default OFF.** User-observed during v0.2.0 artifact testing ("all unlocked seems to break sound"). **Mechanism:** a fresh `ge007.eep` is zero-filled, so every save slot is all-zero. On N64 such a slot fails `fileValidateSaves`' CRC check and is rewritten as `BLANKSAVEDATA` (`src/bondconstants.h:2148`), whose `music_vol`/`sfx_vol` are `0xFF` (max). With AllUnlocked ON, `geEepromPatchAllCheats` (`port/src/libultra.c`) patches the zeroed slots (cheat bits + completion times) and recomputes a **valid** per-slot CRC, so `fileValidateSaves` accepts them as-is and never calls `fileResetSave`. The volume bytes therefore stay 0, and `fileLoadSettingsForFolder` (`src/game/file2.c:1289-1290`) loads them verbatim: `set_mTrack2Vol((save->music_vol << 7) | (save->music_vol >> 1))` = 0 → silence. With AllUnlocked OFF the zeroed slots fail CRC and reset to `BLANKSAVEDATA`, which is why sound works in the default configuration. **Disposition for v0.2.0:** flip `portAllUnlocked` default to 0 (`port/src/video.c`; `SkipIntro` was already 0) — faithful N64 progression out of the box, F10 *All unlocked* still enables the goodie. The quirk then only affects users who deliberately toggle it ON with a fresh save (recoverable: raising the in-game volume writes a nonzero slot, after which the patch leaves volumes alone). **Deferred fix candidate (port-only, ~6 lines):** in `geEepromPatchAllCheats`, detect all-zero slots and seed `music_vol`/`sfx_vol = 0xFF` before the CRC recompute, emulating what `fileResetSave` would have done; real (nonzero) saves incl. a user-muted one stay untouched. | **FIXED in v0.2.1** — the deferred fix was applied: `geEepromPatchAllCheats` now detects all-zero slots and seeds `music_vol`/`sfx_vol = 0xFF` before the CRC recompute, emulating `fileResetSave`; real saves (incl. a deliberately muted one) stay untouched. **By-ear confirmed by the user** (v0.2.1 test pass: fresh save, AllUnlocked ON — audio plays correctly). | @@ -551,7 +554,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D261 | **v0.2.1 (user request: "fix watch menu sensitivity for wasd/controller buttons"): holding W/S (or a fully-pressed stick axis) on the watch inventory page crawled one item per tap — no repeat.** Root cause is the interaction of two existing behaviours: keyboard W/S map to `stickY = ±STICK_MAX (0x50)` and SDL pads peg at 0x80, both *outside* the smooth-scroll band (0x1f..0x45) that D118d intentionally left for the port — so a held press only ever hit the latched single-step path (`watch_stick_y_pressed_*`, one step per press edge, no repeat). On N64 the same stick level was the *fast-scroll* band (one step per frame), which D118d removed because digital sources peg there and it became runaway scroll. **Fix (port-only, `#ifdef PORT` in `src/game/options.c`):** a bounded auto-repeat helper `geWatchStickFastStep()` — while the stick is held past the smooth band (≥ 0x46 up / ≤ −0x45 down), step one extra item every `GE_WATCH_NAV_RATE` (6) frames after an initial `GE_WATCH_NAV_DELAY` (15)-frame hold; the first press still goes through the untouched latched path, so a tap behaves exactly as before, and the smooth band + D-pad/C-button edge-repeat are unchanged. Called from `game_options_inventory_navigation()` only. **Verified:** build green; headless smoke (`GE_INPUTSCRIPT` long-W-hold on the inventory page) runs clean. Feel-check (repeat rate) owed to the user — both constants are at the top of the helper for a one-number tune. **User feel-check (v0.2.1 test pass): repeat "feels fine for now", but W/S is still too sensitive overall — desired behaviour is tactile one-press-one-item with no risk of scrolling the whole list on an over-held key; candidate tweaks (not yet done): longer `GE_WATCH_NAV_DELAY`, slower `GE_WATCH_NAV_RATE`, or gating the auto-repeat off for keyboard taps entirely (repeat only for analogue stick). Logged, no code change. | FIXED — feel-check received; W/S sensitivity follow-up logged above. | | D262 | **v0.2.1 (user report): "3D models missing on watch menu screen item select" — the selected-item 3D preview does not appear on the watch inventory page.** User visually confirmed this session: no 3D model on the item-select page where one should be (default selection is *unarmed*, which has no model — a weapon must be scrolled to with S/DOWN). Headless repro (`GE_INPUTSCRIPT` pulse train + `GE_PCDUMP`, Dam `-level_33`): watch can't open during the intro freeze (`bondviewFrozenMoveBond` zeroes input while `g_CameraMode == CAMERAMODE_INTRO`; freeze ends ~frame 1400–1500 on Dam, and controller reads run faster than sim ticks, so scripts are timed by trial); once open + paged to INVENTORY (R_JPAD), the watch UI renders but the centre of the face is empty for every item. **Root cause found and fixed as D264** (`renderdata.flags == 0` in the watch subdraw pass — the cross-global ModelRenderData template read zeroes on PC; `subdraw()` then gated *every* geometry node, emitting exactly 1 word instead of the model's commands). Residual: with flags restored, the preview emits matrix/light-setup words (71 for item 5) but dump frames still show no visible centre geometry — a possible second layer (LOD/distance gating or fast3d rasterisation of the sub-DLs) remains unverified; user visual re-check pending. | FIXED (primary cause, D264) — pixel-level confirmation pending user run. | | D264 | **D262's root cause (ABI/layout class, cf. D140/D52): the watch item preview's `ModelRenderData` template is read across two adjacent globals that no longer sit adjacent on PC.** `set_enviro_fog_for_items_in_solo_watch_menu()` (`src/game/gunfire.c`) starts with `ModelRenderData renderdata = *((ModelRenderData *) (&D_80035D00));` — a 64-byte copy that on N64 spans `D_80035D00` (a zeroed u32, .bss) *and* `D_80035D04[] = {1, 3, 0…}` (.data): the effective template is the one documented in the comment above `D_80035D00` in `gun.c` — `basemtx=NULL, zbufferenabled=TRUE, flags=3, rest zero` (`CULLMODE_BOTH` is 0). The PC link places .bss and .data in separate sections, so the read is all zeros: **`flags==0`**, and every geometry emitter in the subdraw node dispatch gates on `renderdata->flags & 1/2` (e.g. `modelRenderNodeDl`) — so `subdraw()` emitted exactly one word (`gSPSegment`) per preview, no matter the item. Probes this session: entry gates all pass (hand has item, model header non-NULL), node tree present (~400 nodes, isLoaded=1), world-path geometry nodes carry valid built DLs — but in the watch pass `flags=0` on every node. **Fix (`#ifdef PORT`, values identical to the N64 ROM bytes):** explicit template — `ModelRenderData renderdata = {0}; renderdata.zbufferenabled = TRUE; renderdata.flags = 3;`. Verified at Gfx level: preview emission went 1 → 71 words (item 5) / 81 (mid-scroll) / 9 (item 47, knife-class). Note for follow-up: the 71-word dump is a repeating {matrix/segment/light} unit with no `gSPDisplayList` in the first 24 words — if the user's re-check still shows no visible model, next suspects are LOD/distance gating of the local watch-space matrices (`modelUpdateDistanceRelations`, cf. 7c48f664) or fast3d handling of the preview sub-DLs (segment 3 / render modes). | FIXED — build green, probes removed; visual confirmation pending. | -| D263 | **v0.2.1 (user report): Facility — "ouromov not shooting Trevelyan"; the scripted opening beat never triggers. RESOLVED: not a port bug — the beat is proximity/LOS-triggered exactly as on N64, and the full chain (incl. the execution shot) was verified firing end-to-end on PC.** The triage suspects (tick pacing, camera-mode gate, silent anim lookup) were all ruled out at runtime. **The beat, fully mapped** (static decode of `UsetuparkZ.c` — remember every DBYTE/TBYTE in the dump is bswap16/32'd relative to game interpretation): Trevelyan = guard chr 67 (body 9 = Trevelyan_006, pad 131) on ai_18: walks to pad 124, wanders, then loops a sees-Bond check; **only when Bond is in his vision** (range <10k, facing ±110°, fog, random gate ≈1/49/tick, LOS + *same stand tile* via `chrCanSeeBond`) does he jump to ai_19: runs at Bond, alarm + dialogue timers, sets an objective bit; BG list ai_41 (ID 0x1000, auto-started) polls that bit and spawns **Ourumov (body 7) at pad 88 with ai_23** plus a 6-soldier squad; ai_23 renumbers him to chrnum 78 and arms him → ai_22: runs to pad 123, waits for Trevelyan within 0x1400 of it, ~95 s dialogue cascade, then the **execution shot** (`HitChrWithItem` from 78 on 67). It is a multi-minute sequence that *starts* only when the player walks Bond west and down a floor into Trevelyan's room — Bond spawns 8900 units away on the upper floor with LOS blocked (probed directly: `stanTestLineUnobstructed` = obstructed, different stan tiles), identical geometry to N64. **Runtime verification** (`-level_34`, `#ifdef PORT` probes in the AI executor/vision path, all since removed): all 15 BG lists + guards start; Trevelyan alive on ai_18; every vision gate passes except the geometric ones at spawn distance; player movement confirmed working (note for future headless tests: under the port's default SOLITARE/natural-pitch scheme, walking is C-button/D-pad fed — W→C-up — and the analog stick carries pitch, so `GE_INPUTSCRIPT` SUP/SDOWN tokens do *not* walk Bond there; use UP/DOWN pulses). **Decisive test:** a temporary PORT-only teleport probe dropped Bond 65 units from Trevelyan at t≈12 s → seconds later: vision fires (chr 67 → ai_19), objective bit set, `SpawnChr body=7 pad=88` + squad, chrnum→78 on ai_23; ~2½ min later **`HitChrWithItem from_chr=78 target=67 part=8 item=6`** — Ourumov executes Trevelyan. Most likely explanation of the report: the tester never walked Bond into Trevelyan's sightline (or pre-D253 crash masked it). No game-code or port-code change required. **Kept from this session (test-harness only):** `port/src/input.c` — `GE_INPUTSCRIPT` analog-stick tokens were applied *after* the stick was already written out, so SUP/SDOWN/SLEFT/SRIGHT were silently discarded; the script override now runs before the clamp/write. | RESOLVED (not a bug) — beat verified firing end-to-end on PC incl. execution shot; **user playtest confirmed the sequence plays normally**; probes removed; input-script stick fix kept. | +| D263 | **v0.2.1 (user report): Facility — "ouromov not shooting Trevelyan"; the scripted opening beat never triggers. RESOLVED: not a port bug — the beat is proximity/LOS-triggered exactly as on N64, and the full chain (incl. the execution shot) was verified firing end-to-end on PC.** The triage suspects (tick pacing, camera-mode gate, silent anim lookup) were all ruled out at runtime. **The beat, fully mapped** (static decode of `UsetuparkZ.c` — remember every DBYTE/TBYTE in the dump is bswap16/32'd relative to game interpretation): Trevelyan = guard chr 67 (body 9 = Trevelyan_006, pad 131) on ai_18: walks to pad 124, wanders, then loops a sees-Bond check; **only when Bond is in his vision** (range <10k, facing ±110°, fog, random gate ≈1/49/tick, LOS + *same stand tile* via `chrCanSeeBond`) does he jump to ai_19: runs at Bond, alarm + dialogue timers, sets an objective bit; BG list ai_41 (ID 0x1000, auto-started) polls that bit and spawns **Ourumov (body 7) at pad 88 with ai_23** plus a 6-soldier squad; ai_23 renumbers him to chrnum 78 and arms him → ai_22: runs to pad 123, waits for Trevelyan within 0x1400 of it, ~95 s dialogue cascade, then the **execution shot** (`HitChrWithItem` from 78 on 67). It is a multi-minute sequence that *starts* only when the player walks Bond west and down a floor into Trevelyan's room — Bond spawns 8900 units away on the upper floor with LOS blocked (probed directly: `stanTestLineUnobstructed` = obstructed, different stan tiles), identical geometry to N64. **Runtime verification** (`-level_34`, `#ifdef PORT` probes in the AI executor/vision path, all since removed): all 15 BG lists + guards start; Trevelyan alive on ai_18; every vision gate passes except the geometric ones at spawn distance; player movement confirmed working (note for future headless tests: under the port's default SOLITARE/natural-pitch scheme, walking is C-button/D-pad fed — W→C-up — and the analog stick carries pitch, so `GE_INPUTSCRIPT` SUP/SDOWN tokens do *not* walk Bond there; use UP/DOWN pulses). **Decisive test:** a temporary PORT-only teleport probe dropped Bond 65 units from Trevelyan at t≈12 s → seconds later: vision fires (chr 67 → ai_19), objective bit set, `SpawnChr body=7 pad=88` + squad, chrnum→78 on ai_23; ~2½ min later **`HitChrWithItem from_chr=78 target=67 part=8 item=6`** — Ourumov executes Trevelyan. Most likely explanation of the report: the tester never walked Bond into Trevelyan's sightline (or pre-D253 crash masked it). No game-code or port-code change required. **Kept from this session (test-harness only):** `port/src/input.c` — `GE_INPUTSCRIPT` analog-stick tokens were applied *after* the stick was already written out, so SUP/SDOWN/SLEFT/SRIGHT were silently discarded; the script override now runs before the clamp/write. | RESOLVED (not a bug), but refined (v0.2.1 further user testing, 2026-09-15) — beat verified firing end-to-end on PC incl. execution shot; **user playtest confirmed the sequence plays normally.** **Refinement: the exact trigger behavior is significantly sensitive to Bond's precise location and does not fire 1:1 identically to the N64 original across different approach paths/positions** — consistent with the beat's own documented sensitivity (range/facing/LOS/same-stand-tile + a ≈1/49-per-tick random gate, see the mapped mechanism above), so this is likely the same finicky-trigger behavior rather than a new bug, but not confirmed byte-identical to N64 for every approach. **User confirms this does NOT block level progression** either way — logged for awareness, not tracked as an open defect. Probes removed; input-script stick fix kept. | | D265 | **D236 investigation (port-layer, `port/fast3d/gfx_pc.cpp`): the tree cards are IA8/IA4, not IA16 — and `import_texture_ia4` decoded the RDP's I2:A2 texel as I3:A1, inverting the alpha ramp. Fixed; awaiting user visual re-test.** The earlier sessions misidentified the format: a live tri-time tile census (probe in `gfx_sp_tri1`, since removed) shows level_36's tree-card classes are (a) **IA8, line=64B → 64×16** (texdump r107), drawn OPA/bilerp/1-cycle/AC_NONE — *genuinely fully opaque* (A nibble F on 1023/1024 texels; opaque on N64 too) — and (b) **IA4, line=64B → 128×16** (texdump r117), drawn ZMODE_XLU/point/2-cycle with m2a=CLR_MEM/m2b=1MA, i.e. texel-alpha blended. There are no 32-wide IA tiles at tri time; the prior "32×32 tile" reading was wrong, and the whole earlier IA16 byte-pair/I11:A5 debate (r107) was analyzing an IA8 image as byte-pairs. **The wall's root cause:** r117 decoded as I2:A2 is a strip of ~13 discrete conifer silhouettes (trunk+canopy shapes visible in the ASCII render, `scratch/r117_ia4_alpha.png`) with a 3-level alpha ramp {0: 95% of texels, 85, 170} — faint dithered distant trees on N64. But `import_texture_ia4` read each nibble as intensity=`part>>1` (3-bit, SCALE_3_8) + alpha=`part&1`: A2=1 canopy pixels became **fully opaque**, A2=2 edge dithering became **invisible** → opaque repeating tree silhouettes = the "wall". The bug is inherited verbatim from the PD port (`pd_port/port/fast3d/gfx_pc.cpp` has the identical I3:A1 read — latent there, fatal for GE's tree strip). **Fix (applied):** `intensity = part>>2`, `alpha = part&3`, both via new `SCALE_2_8`; this is the hardware-correct RDP IA4 layout. Also cleaned up: the ia16 importer comment now states plain I8:A8 (the byte-pair read *is* correct IA16; the old "r107 proof" was misidentified data), and the earlier part-1 dimension fix stays (harmless/correct for real IA16 tiles). **Caveats/owed:** (1) user visual re-test of Surface 1 trees — headless A/B was in flight when work halted; a cross-run region diff was inconclusive (camera drift between captures); (2) the decode change affects *every* IA4 tile game-wide — regression-sweep other levels for dithered/alpha IA4 art that now renders with correct 3-level alpha; (3) if a hard repeating band remains after the fix, it is the opaque IA8 strip (a), which matches hardware and D236 needs re-scoping. **OUTCOME (user re-test): NEGATIVE — trees still render as the wall with the I2:A2 decode applied; the change was fully reverted (`git checkout` of `port/fast3d/gfx_pc.cpp`, back to the original PD-derived importers) and work is parked.** The census/RM/texture data above stands as evidence for the next attempt: the decode was *necessary* (I2:A2 is the hardware layout and r117 is unambiguously an authored tree strip under it) but *not sufficient* — remaining suspects, in order: (i) fast3d's blending of these XLU/2-cycle cards (texel alpha reaching the GL blend at all — the `texture_edge`/`SHADER_OPT_TEXTURE_EDGE` path forces `a>0.19 ? 1 : discard`, which for a 3-level ramp quantizes to opaque/discard, i.e. still hard-edged), (ii) per-card UV windowing/placement (all captured cards sampled the same u:0..64/v:0..16 window — confirm against N64), (iii) fog/distance. Note for the next session: verify what the N64 actually shows in that region before more port-side changes (the opaque IA8 strip is faithful to hardware either way). | PARKED — reverted to baseline; evidence + suspect list above. | | D258 | **PAL/JP asset conversion is broken at the source-data level — `scripts/filelist.e.csv` and `filelist.j.csv` carry naming defects, so `gen_romassets.py e/j` (and the d43/d69/d88 emitters' `find_row`) cannot resolve PAL/JP builds. v0.2.0's drop-in ROM feature therefore ships NTSC-U only.** Discovered while scoping issue #6 (drop-in ROM). **PAL:** 31 of the 512 model rows in `filelist.e.csv` have truncated/renamed basenames (an extraction artifact — e.g. `Pbridge_console` ×6 where the resource table says `Pbridge_console1aZ…3bZ`, `Pchrgrenadelauncher` for `PchrgrenadelaunchZ`). **JP:** one font row is mislabeled — `fontBankGothic_fontchartable.bin` @3044276 is named `fontZurichBold_fontchartable.bin` (a duplicate label). Both were proven without a PAL/JP ROM: (a) greedy two-pointer sequence alignment of the 512-row `file_resource_table.inc.c` model order against each CSV's model rows gives an exact 1:1 match (481 by-name + 31 positional for PAL), offsets strictly non-decreasing, and zero size anomalies vs US (all within [0.5×, 2×]); (b) JP font row proven by the contiguity invariant — Gothic kerning @3043600+676 = 3044276 exactly hits the mislabeled row, whose size (8716) equals the US Gothic chartable, while Zurich's own chain is contiguous one row later. **Consequence:** no PAL or JP PC exe has ever been buildable from this tree (latent — only NTSC was ever exercised); the CSV `file` column is a label only (emitters look up by basename, never read the path), so the repair is a pure data fix: rename the 31 PAL model rows + 1 JP font row to the exact table symbols. **Scope decision (v0.2.0):** ship drop-in ROM support for NTSC-U only — `prepare-assets.py`/the frozen `ge007-convert` gate non-US ROMs with a clear message after SHA-1 region detection; the CSV repair + multi-region matrix + per-region exes are deferred to the Part B follow-up. | OPEN — repair path verified, not applied (out of v0.2.0 scope). | diff --git a/docs/index.md b/docs/index.md index a0b3a083..bf68ea4b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -19,11 +19,17 @@ same Rare "Indy" engine family, one hardware generation apart. **Status: v0.2.1.** The full single-player campaign runs at a steady 60 fps and is completable end to end; all 21 missions load and run clean on Windows -and Linux (one intermittent Steam Deck crash remains, listed below), and -audio (music + SFX) plays throughout. The known -rough edges (cutscenes that still glitch, a few music tracks that sound -wrong, a handful of cosmetic rendering defects) are listed plainly under -[Honest status](#honest-status). +and Linux (one intermittent Steam Deck/Linux crash remains, listed below), +and audio (music + SFX) plays throughout. The known rough edges (cutscenes +that still glitch, a handful of cosmetic rendering defects, missing features +like real widescreen) are listed plainly under [Honest status](#honest-status). + +**This is a pre-1.0 release, not a finished product** — v1.0 is the target +for a polished, feature-complete build; expect rough edges and missing +features until then. See the +[README's Roadmap section](https://github.com/jkdansereau/goldeneye-pc-port#roadmap) +for direction (PAL/JP support, real widescreen, LAN multiplayer under +consideration, and more).