diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1c0d8950..04d3af43 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -7,9 +7,8 @@ body: value: | Thanks for filing a report. First, please check: - - The port is **Phase 2 of 4** — rendering is incomplete and many - cosmetic defects are already tracked in - [`docs/dev/GRAPHICS-BACKLOG.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/dev/GRAPHICS-BACKLOG.md). + - Known open issues (rendering, audio, platform) are tracked in + [`docs/ROADMAP.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/ROADMAP.md). Search open issues and that file before filing. - Only the **Windows / MSYS2 MINGW64** build is tested. Linux and macOS are best-effort — build breaks there are expected. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 2edadabd..2eb0a3bb 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -4,7 +4,7 @@ contact_links: url: https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/building.md about: Read this first for build/extraction problems — most are covered here. - name: Known rendering defects - url: https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/dev/GRAPHICS-BACKLOG.md + url: https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/ROADMAP.md about: Cosmetic / rendering issues already known and tracked. Check before filing. - name: Security vulnerability url: https://github.com/jkdansereau/goldeneye-pc-port/security/advisories/new diff --git a/.github/labels.yml b/.github/labels.yml index 7f260a01..1a15f5b4 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -10,7 +10,7 @@ - name: rendering color: 5319e7 - description: Graphics / fast3d / RSP output defect (see docs/dev/GRAPHICS-BACKLOG.md) + description: Graphics / fast3d / RSP output defect (see docs/ROADMAP.md) - name: audio color: 1d76db diff --git a/.github/release-notes.md b/.github/release-notes.md index 14b1c015..5b4ba7cc 100644 --- a/.github/release-notes.md +++ b/.github/release-notes.md @@ -6,7 +6,7 @@

Platforms: **Windows x86-64** and **Linux x86-64 (including Steam Deck)**. -Region: **NTSC-U (US) only** — see [Things to know](#things-to-know). +Region: **NTSC-U (US) only** — see [Caveats](#caveats). v0.4.0 is a large feature and fidelity release. The full campaign runs at a steady 60 fps with music and SFX throughout, and has been playtested end to @@ -24,7 +24,7 @@ The world now renders natively at your display's aspect ratio instead of stretched 4:3: geometry is undistorted (wider view, same vertical FOV), the in-level HUD keeps its shape and anchors to the screen edges, and the 4:3 front-end menus and ending-credits sequence are pillarboxed. Turn it off -with the **`Native widescreen`** toggle (F10 → Display) to restore the old +with the **`Native widescreen`** toggle (F10 → Graphics) to restore the old stretched frame. (D334/D335) ### A true, stable 60 fps @@ -56,19 +56,18 @@ frame cadence. Fixed at the root; the game now holds a rock-stable 60 fps ### Rebuilt options (F10 overlay + front-end PC Options) - Both options screens are reorganized into functional sections — **Input, - Gameplay, HUD, Graphics, Audio, Display** — each with its own *Reset to + Gameplay, HUD, Graphics, Audio, Video** — each with its own *Reset to defaults*; per-profile settings scoping; and the old "save file" wording is now **profile**. (D353–D356) - The **F10 in-game overlay** was refreshed GE-style: category headers, full controller support (value-adjust with hold-to-repeat), fixed pointer/ keyboard navigation, and new rows: **Show FPS**, **Skip intro - (experimental)**, pad - **Invert look / Southpaw / Deadzone / Trigger threshold**. (D237/D345– - D347/D360–D370) + (EXPERIMENTAL)**, plus pad-only **Invert look / Southpaw / Deadzone / + Trigger threshold**. (D237/D345–D347/D360–D370) - The front end gained a **PC Options screen** beside the file-select bar, and save-file **Copy/Erase** moved into the bottom bar. Long sections - (Input) page within the screen -- a "Page 1/2" marker plus a bottom hint - on every page -- so every row stays on screen and stays reachable; the + (Input) page within the screen — a "Page 1/2" marker plus a bottom hint + on every page — so every row stays on screen and stays reachable; the mouse wheel and W/S also page the list, and the selection clamps at page edges. (D343/D406/D407) - Watch-only settings — **Auto-aim**, **Look ahead** and the rest — are now @@ -79,10 +78,10 @@ frame cadence. Fixed at the root; the game now holds a rock-stable 60 fps ### Crosshair customization *(defaults leave the N64 crosshair untouched)* -**Show crosshair** (on/off), **Crosshair colour** (the original red, -or green / blue / yellow / cyan / magenta / white, or custom RGB), **Crosshair size** -(default 100% of the original drawing), and **Crosshair style** (Original / -Thin cross). (D373/D379/D381/D382) +**Show crosshair** (on/off), **Crosshair colour** (default *Original +(red)*, or Green / Red / Blue / Yellow / Cyan / Magenta / White, or custom +RGB), **Crosshair size** (default 100% of the original drawing), and +**Crosshair style** (Original / Thin cross). (D373/D379/D381/D382) ### In-game key rebinding + new default layout @@ -97,7 +96,7 @@ ones migrate automatically. (D371/D374/D383/D385) - **Sane pad defaults**: **A = use/interact, X = reload, Y = weapon cycle** (v0.3.0 mapped two buttons to crouch and had no in-game use button). (D393) - Crouch uses the engine's real crouch input, with a **`Crouch mode`** - toggle (hold / latched). (D375) + toggle (Hold / Toggle). (D375) - Xbox-variant controller parity, **Southpaw**, and per-stick **deadzone** + **trigger threshold** rows. (D237/D394) - **The N64 Rumble Pak now drives real gamepad haptics** (Steam Deck @@ -199,36 +198,51 @@ distances). (D402/D283) ## Options reference -All settings available in v0.4.0, in both options screens (front-end -**PC Options** and the in-game **F10 overlay**). Defaults in parentheses. +Every setting in v0.4.0's options screens (front-end **PC Options** and the +in-game **F10 overlay**), with the current default. | Setting | Section | Default | |---|---|---| -| `Native widescreen` | Display | On | -| `Frame rate cap` | Display | 60 (30 / 60) | -| `Show FPS` | Display | Off | -| `MSAA` | Display | 2× (1/2/4/8, clamped to driver max) | | `Aim style` | Input | N64 (N64 / Centred (PC)) | -| `Aim range` | Input | PC (shown while Centred) | -| `Mouse horizontal/vertical sensitivity` | Input | calibrated midpoint | -| `Invert look (mouse)` / `Invert look (controller)` / `Southpaw` | Input | Off | -| `X / Y axis look sensitivity (controller)` | Input | 100% (native) | -| `Look smoothing (controller)` | Input | 0 | +| `Aim range` | Input | PC (shown while Centred is selected) | +| `Mouse horizontal sensitivity` / `Mouse vertical sensitivity` | Input | 100% (calibrated midpoint) | +| `Invert look (mouse)` / `Invert look (controller)` | Input | Off | +| `Southpaw` | Input | Off | +| `X axis look sensitivity (controller)` / `Y axis look sensitivity (controller)` | Input | 100% (native) | +| `Look smoothing (controller)` | Input | 0 (off) | | `Tank aim speed` | Input | 100% (`Input.TankAimScale`) | -| `Deadzone (left/right stick)` | Input | 70% | +| `Deadzone (left stick)` / `Deadzone (right stick)` | Input | 70% | | `Trigger threshold` | Input | 23% | | `Vibration` | Input | 50% | -| `Crouch mode` | Input | Hold | -| `Bindings…` (Movement / Actions editors) | Input | GEPD-style preset | +| `Crouch mode` | Input | Hold (Hold / Toggle) | +| `Bindings…` (`Movement…` / `Actions…`) | Input | GEPD-style preset | | `Auto-aim` / `Look ahead` | Gameplay | Off (watch-backed) | -| `Skip intro` / `No hit flash` | Gameplay | Off | +| `Skip intro (EXPERIMENTAL)` / `No hit flash` | Gameplay | Off | | `All unlocked (EXPERIMENTAL)` | Gameplay | Off | +| `Sight on screen` / `Ammo on screen` | HUD | On (watch-backed) | | `HUD scale` | HUD | 100% (75–150%) | -| `Show crosshair` / `Crosshair colour` / `Crosshair size` / `Crosshair style` | HUD | N64-intact (size 100%) | +| `Show crosshair` | HUD | On | +| `Crosshair colour` | HUD | Original (red) | +| `Crosshair size` | HUD | 100% | +| `Crosshair style` | HUD | Original (Original / Thin cross) | +| `Native widescreen` | Graphics | On | +| `Widescreen auto FOV` | Graphics | On | +| `Crop overscan` | Graphics | On | +| `Anti-aliasing` | Graphics | 2× (1/2/4/8, clamped to driver max) | +| `Texture filter` | Graphics | Bilinear (Nearest / Bilinear / 3-Point) | +| `Anisotropic filtering` | Graphics | 4× (1–16) | +| `FOV scale` | Graphics | 100% | +| `Draw distance` / `LOD distance` | Graphics | 250% (100–400%) | +| `Music volume` / `FX volume` | Audio | 100% (watch-backed) | +| `Fullscreen` | Video | Off (windowed) | +| `Resolution` | Video | Auto windowed preset | +| `VSync` | Video | On | +| `Frame rate cap` | Video | 60 (30 / 60) | +| `Show FPS` | Video | Off | --- -## Things to know +## Caveats - **NTSC-U (US) ROMs only.** PAL and JP ROMs are not supported in this version; asset repair for those regions is on the post-release roadmap. diff --git a/.github/social-preview.html b/.github/social-preview.html index 2050c42c..3aa0c427 100644 --- a/.github/social-preview.html +++ b/.github/social-preview.html @@ -124,12 +124,12 @@
REPORT:
-
Status: RELEASED v0.3.0 — WINDOWS · LINUX · STEAM DECK
+
Status: RELEASED v0.4.0 — WINDOWS · LINUX · STEAM DECK
Statistics:All 20 solo missions — full campaign completed in playtesting ~230 game-code units, compiled unmodified - ~24k lines C/C++  ·  ~7k Python (port layer + tools) - 199 findings root-caused  ·  two coding agents, one human, part-time + ~25k lines C/C++  ·  ~14k Python (port layer + tools) + 286 findings root-caused  ·  two coding agents, one human, part-time
diff --git a/.github/social-preview.png b/.github/social-preview.png index 60ae6e0e..d9ebd2c9 100644 Binary files a/.github/social-preview.png and b/.github/social-preview.png differ diff --git a/AGENTS.md b/AGENTS.md index 36ca548b..770746df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,8 @@ - `n64decomp/007`: WIP decompilation of GoldenEye 007 (N64), byte-matches US/EU/JP ROMs. - Active work: **PC port** modelled on the Perfect Dark PC port (same Rare "Indy" engine family). - **Reference docs:** `docs/internals.md` — architecture, GE-specific RSP deltas, phased plan (§1–§10). `docs/dev/findings.md` — the `Dxx` finding log (§F + §H). **Look up findings via `docs/dev/findings-index.csv` (label, one-liner, status; regenerate with `tools_pc/gen_findings_index.py`), then read only the specific `## Dxx` entry (multi-pass labels like D202/D176(a) have large sections — grep within the section or read with offset/limit, don't slurp it whole). Never linear-read.** `docs/porting-notes.md` — the recurring N64→PC bug classes (dense; skim the headers, read what's relevant). -- **Current status:** the README "Status" section, and `docs/dev/LEVEL-STATUS.md` for the per-level sweep. Current task + environment: `docs/HANDOFF.md` (a rolling local working file — may be absent in a fresh clone; fall back to the README "Status" section). +- **Open work / known issues:** `docs/ROADMAP.md` — the single tracker; add new items there, never start a new backlog/roadmap file. +- **Current status:** the README "Status" section (`docs/dev/LEVEL-STATUS.md` is a historical per-level sweep record). Current task + environment: `docs/HANDOFF.md` (a rolling local working file — may be absent in a fresh clone; fall back to the README "Status" section). - **Dispatching subagents?** `docs/dev-process.md` — task budgets/deadlines, file partitioning, pre-flight, the standard brief template. Every investigation subagent reads `docs/porting-notes.md` first and appends to it. ## Non-negotiables @@ -23,7 +24,7 @@ | `docs/internals.md` | Architecture + RSP deltas + phased plan (§1–§10). Reference, not a linear read. | | `docs/dev/findings.md` | The `Dxx` finding log (§F/§H); lookups via `docs/dev/findings-index.csv`. | | `CMakeLists.txt` | PC build (parallel to the N64 Makefile). Source list + `REGION_DEFS` live here. | -| `port/src/` | Shims: `libultra.c` (OS API), `gesched.c` (scheduler), `n64stubs.c` (boot/TLB/FPU/rmon), `random.c` (PRNG ported verbatim from `random.s`), `ucode.c` (microcode segment markers), `main.c`, `video.c`, … | +| `port/src/` | Shims: `libultra.c` (OS API; also shims the scheduler's hardware leaf calls and runs the software RSP inline — the game's real `src/sched.c` is compiled), `n64stubs.c` (boot/TLB/FPU/rmon), `random.c` (PRNG ported verbatim from `random.s`), `ucode.c` (microcode segment markers), `main.c`, `video.c`, … | | `port/fast3d/` | Software RSP (adapted from the PD port). The main Phase 2 work. | | `rsp/graphics/gmain.s` | GE's RSP ucode — ground truth for GBI/CC/RM. | | `reference/mouse-injector/README.md` | **Stub only** — the vendored GEPD-Edition Mouse Injector source (GPLv2) was removed from the public repo 2026-09-19 (license hygiene: GPL code + prebuilt binary in an MIT project; it was never compiled here). The stub records provenance + how to re-vendor locally (gitignored). The ported mouse-aim model lives in `port/src/input.c` (D194 lineage); design record: `docs/dev/GEPD-INPUT-PLAN.md`. | @@ -94,25 +95,14 @@ Needs CMake + SDL2 + zlib + OpenGL, and must run from the MSYS2 MINGW64 shell ## Verification ritual (after any build-affecting change) -1. **Undefined symbols.** Every symbol referenced by the compiled set (see `CMakeLists.txt`: `SRC_GAME`, `SRC_ENGINE`, `SRC_LIBAUDIO`, `SRC_LIBULTRARE_AUDIO`, `SRC_LIBULTRARE_DATA`, `SRC_GU`, `SRC_PORT*`) must be defined exactly once in the compiled set or in `port/`. Symbols that live in EXCLUDED files (`libultra/io/*`, `libultrare/io/*` except `vitbl.c`, `libultra/os/*`, `libultrare/os/*`, `sched.c`, `rmon.c`, `vi.c`, `src/*.s`) must be provided by `port/src/libultra.c`, `gesched.c`, `n64stubs.c`, `random.c`, or `ucode.c`. +1. **Undefined symbols.** Every symbol referenced by the compiled set (see `CMakeLists.txt`: `SRC_GAME`, `SRC_ENGINE`, `SRC_LIBAUDIO`, `SRC_LIBULTRARE_AUDIO`, `SRC_LIBULTRARE_DATA`, `SRC_GU`, `SRC_PORT*`) must be defined exactly once in the compiled set or in `port/`. Symbols that live in EXCLUDED files (`libultra/io/*`, `libultrare/io/*` except `vitbl.c`, `libultra/os/*`, `libultrare/os/*`, `sched.c`, `rmon.c`, `vi.c`, `src/*.s`) must be provided by `port/src/libultra.c`, `n64stubs.c`, `random.c`, or `ucode.c`. 2. **Duplicates.** No symbol defined twice across the compiled set (watch `sp_*` stacks, `rmon*`, `os*` shims, segment markers). 3. **Syntax.** Every touched file must parse; `./build-pc.sh` is the final word. Run `/linkcheck` for this sweep. Record new findings in `docs/dev/findings.md` §F/§H style (next `Dxx` label after the last used) and add the label to the §F index. -## Phase status (summary — see README + `docs/dev/findings.md` for detail) +## Phase status -- **Phase 0–1.5:** done. Build system, boot chain, OS-shim layer, fast3d - integration, first frames, full intro rendering. -- **Phase 2 (rendering):** in progress. All 20 solo missions (plus the - ending-credits sequence, `-level_54`) load + render + survive an - unattended window; front end (menu → mission select → briefing → - start) is functional; file-backed EEPROM saves work. Cosmetic defects are - parked in `docs/dev/GRAPHICS-BACKLOG.md`. -- **Phase 3 (audio + input):** input layer done (`port/src/input.c`); polish - bugs open (D118* mouse-look residuals; interactive feel-checks owed). Audio - mixer done (libaudio → SDL software mixer, D198–D201); D204 tempo drift - fixed + measured; D202 stuck door loop root-caused with a port-side - expiration (M-66b) awaiting by-ear verification. -- **Phase 4 (saves + polish):** file-backed EEPROM done; widescreen, config, - rebinding UI outstanding. +Do not keep a status summary here — it goes stale. Open work, known issues +and decisions owed: **`docs/ROADMAP.md`** (the single tracker). Current +release state: the README "Status" section. diff --git a/CLAUDE.md b/CLAUDE.md index 4d4f106d..a0952eb8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,6 +16,8 @@ Context is scarce. Load by tier; do not blind-read whole files. `docs/HANDOFF.md` (current state + next task + environment — a rolling local working file; may be absent in a fresh clone, in which case read the README "Status" section instead) — read fully; + `docs/ROADMAP.md` (the single tracker of open work — read the section + relevant to the task); `docs/porting-notes.md` (recurring bug classes) — skim the section headers, read the classes relevant to the task. - **Tier 2 — on demand only, do NOT read start-to-finish:** diff --git a/README.md b/README.md index bfa84066..4fc9442d 100644 --- a/README.md +++ b/README.md @@ -131,32 +131,12 @@ graphics, audio; frame cap, MSAA, filtering, FOV, sensitivity, key rebinding, crosshair, vibration); Windows and Linux, including Steam Deck. -**Known issues:** +**Known issues:** see the **[known-issues table](docs/ROADMAP.md#known-issues)** +(what you'll notice, impact, workarounds). The short version: no multiplayer +yet, NTSC-U ROMs only, no macOS/ARM builds, and a couple of cosmetic defects. -- On Facility, if gas leaks during Ourumov's monologue he can pause for up to - ~10 s before resuming the scripted shootout — a latent race that exists in - the N64 original too (where it softlocks permanently); the port detects and - auto-recovers it (D318). -- With native widescreen on, the F10 options overlay stretches with the - window instead of pillarboxing like the front-end menus (legible; cosmetic; - the F10 *Native widescreen* toggle restores the old stretched frame - throughout). The world/HUD widescreen rendering itself is correct (D335b). -- The Rareware front-end logo shows a subtle texture-filtering artifact - (Nintendo logo and legal page are clean). Cosmetic only (D75). -- This release ships NTSC (US) assets; PAL/JP ROMs are not supported in this - version (D258). -- **`All unlocked` is experimental: back up `data/ge007.eep` before enabling - it.** Any save while ON, even a profile-settings change, can permanently - write artificial cheat unlocks and completion times into the EEPROM; - switching OFF does not undo them (D387). Do not use it on a save whose - original progression you need to preserve. -- Assorted further cosmetic defects are tracked in - [`docs/dev/GRAPHICS-BACKLOG.md`](docs/dev/GRAPHICS-BACKLOG.md). -- No macOS or ARM support. Keyboard/mouse rebinding shipped in v0.4.0; - controller-button rebinding is not supported yet. - -Root causes and fix status for every item: the [release notes](https://github.com/jkdansereau/goldeneye-pc-port/releases) -and the finding log in [`docs/dev/findings.md`](docs/dev/findings.md). +> **Before enabling `All unlocked`, back up `data/ge007.eep`.** Saving with it +> on can permanently write fake unlocks into your save (D387). ### Steam Deck @@ -191,23 +171,23 @@ 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: +The goal is a **feature-complete, faithful** port: every mode and region the +N64 cartridge ships, behaving 1:1 with the original. The full list of open +items, and which ones are waiting on a decision, lives in one place: +[`docs/ROADMAP.md`](docs/ROADMAP.md). In brief: -- 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. -- **Controller (pad) button rebinding UI** (keyboard/mouse rebinding shipped - in v0.4.0), 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. +- **Split-screen multiplayer** (2–4 players), the largest missing N64 feature + ([#99](https://github.com/jkdansereau/goldeneye-pc-port/issues/99)). +- **PAL and JP ROM support**. NTSC-U is the only supported region today. +- The remaining small accuracy differences (audio timbre, a few cosmetic + rendering details). +- **macOS/ARM builds** and controller-button rebinding. -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. +This is spare-time work, so there's no timeline. Opt-in extras beyond the N64 +game (LAN play, bots, HD textures) come after feature-complete, if at all. +Not currently planned: co-op or other modes GE never shipped, online +multiplayer, ray tracing. If any of these matter to you, open an issue — it +helps prioritize. --- @@ -387,9 +367,9 @@ The R4300 game code in `src/` is compiled completely unmodified; the decompilation's control flow is ground truth. Everything that would touch N64 hardware is redirected into `port/`: a **software RSP** (`port/fast3d/`, adapted from the Perfect Dark port) that interprets the GBI display list the -game builds each frame and emits OpenGL, bypassing the RDP; a scheduler -replacement (`port/src/gesched.c`) that drives it directly; single-threaded -libultra OS shims (`port/src/libultra.c`); and SDL2/OpenGL/filesystem backends +game builds each frame and emits OpenGL, bypassing the RDP; libultra OS shims +(`port/src/libultra.c`, game threads as real host threads) that let the +game's own scheduler run and drive the software RSP inline; and SDL2/OpenGL/filesystem backends for video, audio, input and storage. The 32→64-bit transition forces a small, cataloged class of mechanical ABI-only edits to ROM-serialized structs (pointer-width reconciliation); these change no behavior and are documented diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md new file mode 100644 index 00000000..770b7a9d --- /dev/null +++ b/docs/ROADMAP.md @@ -0,0 +1,263 @@ +# Roadmap and open items + +**This is the single tracker for high-level work on the port.** If an item +is not here, it is either done or not planned. The README's *Known issues* +and *Roadmap* sections are short summaries of this file. Root-cause detail +for each `Dxx` lives in [`dev/findings.md`](dev/findings.md) (find it via +[`dev/findings-index.csv`](dev/findings-index.csv); that index is generated, +first-line-only, and can lag the entry tail, so read the entry). + +Last full triage: 2026-09-29. Sources: the findings log, all GitHub issues, +PRs and releases, every local backlog/plan/roadmap note, `HANDOFF.md`, all +local and remote branches, and known community forks. + +## How to use this file + +- **One row per item.** Link the `Dxx` and/or GitHub `#issue`. Keep detail + in the finding, not here. +- **Status vocabulary:** `open` · `partial` · `verify` (fixed, a live check + is owed) · `parked` (work exists on a branch or patch, not merged) · + `decision` (blocked on a maintainer call). +- **When an item closes, delete its row.** The history is kept in the + finding and in git. Do not strike rows through. Struck rows are what made + the old backlogs unreadable. +- **New work goes here first.** Do not start a new backlog, roadmap or + plan-of-record file. A design doc for one item is fine; link it from the + row and delete it when the item lands. +- Release scoping (which items go into which version) happens in the + release notes, not here. +- **Known issues** (the player-facing subset) live in the table below. The + README links to it and does not duplicate it. + +## Goal and definition of done + +**Feature complete and faithful to the N64 original.** Every mode and +region the cartridge ships should work and behave 1:1 with the N64, +checked against an accurate emulator (1964/GEPD) or real hardware. On top +of that sits a **modern-remaster layer** (the bar set by Nightdive's +Quake/Turok remasters, the XBLA remaster and GE+/Dab's mod). It must never +change game logic by default, and it must always be possible to get back +to the original look and feel (see the *Original N64 preset* in §5). + +The port is **feature complete** when all of these hold: + +1. Every solo mission (all 20 plus the credits) is completable at every + difficulty, on Windows and Linux. +2. No crash on any level under normal play, on either platform. +3. Audio is complete and faithful: music, positional SFX, menu and weapon + sounds. +4. Cutscenes play correctly (camera, actor positions, timing). +5. AI locomotion and pacing match the N64. +6. Input feels like GoldenEye on a controller, and like a modern FPS on + mouse and keyboard. +7. Rendering has no visible defect versus 1964/GEPD at default settings, + apart from the port's deliberate modern defaults. +8. **Multiplayer (2–4 player split-screen) works.** It is the largest item + left (§1). +9. **PAL and JP ROMs work** (§1, pending a decision). + +**Exit gate:** a full two-platform campaign playthrough at more than one +difficulty, plus a split-screen session, on a build with the §3 +verification debt cleared. + +Where things stand (2026-09-29): 1–7 hold for NTSC-U, with the small +accuracy gaps listed in §2. 8 and 9 are open. + +## Known issues + +What a player will notice in the current release. **This table is the +single list:** the README links here instead of keeping its own copy. When +an issue is fixed and released, delete its row. When a new player-visible +issue is confirmed, add a row here (and its tracking row in the sections +below). + +| Issue | Impact | Workaround | Tracking | +|---|---|---|---| +| Multiplayer isn't available yet | Missing feature | — | #99, §1 | +| PAL and JP ROMs aren't supported | NTSC-U only | Use an NTSC-U ROM | D258, §1 | +| **`All unlocked` can permanently write fake unlocks into your save** | Save corruption | Back up `data/ge007.eep` before enabling it; don't use it on a save you care about | D387, §5a | +| `Game.SkipIntro` also skips the post-mission failure dossier | Minor | Leave SkipIntro off | D408, §2 | +| F10 overlay stretches in native widescreen (menus pillarbox correctly) | Cosmetic | — | D335b, §5a | +| Rareware logo shows a subtle texture-filtering artifact | Cosmetic | — | D75, §2 | +| Steam Deck: a first launch in Desktop Mode skips the Deck preset | Settings | First launch in Game Mode, or delete `ge007.ini` and relaunch | D283, §4 | +| ~30 fps on very low-end GPUs (e.g. Intel HD 400) | Performance | Set `Video.LowEndMode=1` in `ge007.ini` | D339, #92, §4 | +| No macOS or ARM builds | Platform | — | #88, #95, #101, §4 | +| Controller buttons can't be rebound in-game (keyboard/mouse can) | Missing feature | — | #109, §5a | + +--- + +## 1. Feature completeness vs the N64 cartridge + +| Item | Status | Refs | Notes | +|---|---|---|---| +| **2–4 player split-screen multiplayer** | parked | #99, D416–D423, branch `feat/mp-splitscreen-99` | The largest missing N64 feature. 2P has been live-tested (M&K + pad, respawn, 16:9 HUD). Before merge: Rule-2 review of `PROP__PORT_SIGNED` (D417) and a golden gate run on the maintainer's console. Open on the branch: D420 (stats screen shows a large number), D421 (gadget cycling off; needs a Rule-2 game-thread hook), D422 (no centred aim for P2+), D423 (black arrowhead in P2 view), and D428's sway accumulator is player-0-only. Untested: 3P/4P, pads-only mode, pause/watch/F10 during a match, MP audio, most stages, and the full N64 MP setup screens (scenarios, handicaps, teams). Needs per-pad seats (§5). An outside restore exists for reference: birdturtle `feature/local-mp-input`. | +| **PAL and JP ROM support** | decision | D258, D75 (pal/jpn sidecar regen), #85 | Conversion is broken at the source-data level (filelist naming). A repair path has been verified but not applied. Needs PAL/JP ROMs to test. #85 was closed as "retired for now", not won't-do, so public users currently have no tracker. Reopen it or file a fresh one when work starts. | +| Drop-in ROM converter, remaining parts | decision | #6, [`dev/release-dropin-rom-plan.md`](dev/release-dropin-rom-plan.md) | NTSC-U drop-in shipped. Left: per-region output names and re-exec guard (with PAL/JP), native C emitters to replace the PyInstaller converter. | +| Cut content (TCRF weapons/items) | decision | [`dev/CUT-CONTENT-BACKLOG.md`](dev/CUT-CONTENT-BACKLOG.md) | Not part of the shipped game. Tier 0 already works via the debug/cheat menus. Tier 1 (config-toggle grants) is small and port-only. Tier 2/3 (placing props, re-enabling unused MP maps) need game data changes and are out of scope for this repo. | + +## 2. Accuracy (behaviour differs from the N64) + +| Item | Status | Refs | Notes | +|---|---|---|---| +| Auto-fire gunshot sound slightly differs from 1964/GEPD | open | D433, #114 | Cadence now matches the N64 rules exactly. The residual is timbre (resampler / release envelope suspected). Needs a precise by-ear description. | +| SFX voice cap and long-session audio decay | partial | D322, #87 | The Windows periodic garble is fixed. Still open: is the 8-voice cap faithful, and the progressive decay that #87 reported (not reproduced here). The owed GitHub housekeeping is in `docs/dev/notes/ISSUE-87-FOLLOWUP.md` (local). | +| Auto-fire gate at unstable 40–55 fps | partial | D427 | Exact at a steady 60 or 30 fps. At fluctuating rates it can flip between 1× and 2×, because `field_88C` is never rescaled. | +| Third-person Bond offset residual | partial | D173, D292 | Mostly fixed. A small drift remains about 1 time in 10. | +| Statue corner flicker at max FOV | partial | D294 | Room-pool residual after the black-ground fix. | +| Crash on save slots 1/3/4, plus a host BSOD (fltmgr.sys) | open | #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). Close as stale if there's still no reply, and reproduce locally rather than asking the reporter again. | +| SkipIntro skips the post-mission failure dossier | open | D408, D216 | Only affects the experimental `Game.SkipIntro` option. The audio-break half of D216 is also unrooted. | +| Front-end: Rareware logo filtering, cast-roll models | partial | D75 | Cosmetic. The other front-end logos are fixed. The cast-roll character models have never been verified. | +| Authored mip chains not sampled for explicit-LOD textures | parked | D323 | Distant textures use driver mips instead of Rare's hand-authored chain (heaviest on Statue/Aztec). Parked as a fidelity enhancement. | +| Approximations in the software RSP | open | `gfx_pc.cpp` `G_CCMUX_LOD_FRACTION`; `Video.WrapFix` (§F row `RC3 · D167`, D74 family) | The LOD-fraction CC input is an eyeballed approximation. The non-power-of-two wrap fix is still opt-in (default off), with unknown visible impact. Both need a comparison against 1964. | +| Latent 64-bit ABI hazards | open | #107, #108; ~396 `-Wpointer-to-int-cast` sites (count from a local review note; re-count with a `-Wpointer-to-int-cast` build); AUDIT-M6 `struct player`/`struct hand` raw offsets (porting-notes §A1) | Harmless today, because the arena sits low in the address space. They would bite on macOS/ARM/ASLR. The fixes fall under the AGENTS.md ABI/layout exception. **#108 concern:** widening the `sndPlaySfx` bound disables the guard on every platform; prefer a range check. Check #107 against D430/D431/D434, which touch the same `lightFindVertexBaseForTri` region. A read-only provenance pass over the 396 sites is a good delegate task. | +| Controller preset parity (Xbox "Jinx" 1.1, 1.2/1.3 selector) | partial | D394 | The gadget category and pad-initiated binding capture are still open. | + +## 3. Verification debt (fixed; a live check is owed) + +A single campaign pass with these on the checklist would clear most of them. + +| Item | Refs | Check | +|---|---|---| +| Light-fixture hit-type reads (impact sound + sparks) | D434, #119 | Shoot fixtures on Bunker and Caverns. | +| v0.4.1 batch (unreleased; fixes live on `release/v0.4.1`) | D424–D432; #114 fire rate, #115 tank-crush audio loop, #116 tank movement, #117 sway, #118 GL recoil, #119 light fixtures | All user-verified against GEPD/1964 on 2026-09-29. Close the issues when it ships. | +| FOV-scale edge culling | D222 | Max FOV, pan across NPCs at the screen edge. | +| Water pulse, Surface 2 | D229 | Frigate is verified. Only the Surface 2 by-eye check is owed. | +| Linux save/config path when launched outside the game dir | D256 | Save-flow check on Linux. | +| Settings/F10 batch | D356, D358, D361, D363, D374, D406 | Live accept of each item in the F10 overlay. (D377–D379 residuals were waived.) | +| Controller feel | CONTROLLER-INPUT-PLAN Wave A, D204 | Per-stick deadzone/smoothing feel on a pad and on Deck; D204 tempo by ear; `Input.RumbleScale=0` silences rumble. | +| Widescreen HUD at 21:9 | D334/D335 | One ultrawide HUD playthrough. | +| Older "owed" notes likely cleared by the 2026-09-28 campaign sign-off | D150, D177, D264, D369, D371, D372 | Tick off in findings (bookkeeping only). | + +## 4. Platform + +| Item | Status | Refs | Notes | +|---|---|---|---| +| macOS (Intel + Apple Silicon) / ARM64 Linux | open | PRs #88, #95; #101 | Outside drafts. The maintainer tests firsthand before merging. Needs the ABI-hazard sweep in §2. | +| Linux as a co-equal platform | decision | — | Does Linux carry the same sign-off gate as Windows (a full sweep of all 21 levels)? The last known Linux crash (D190) was closed as not-a-bug. | +| Low-end GPUs (~30 fps on Celeron/HD 400) | open | D339, #92 | Needs `GE_PERFSTAT` data from that class of hardware. The D372 low-end preset is the mitigation. | +| Steam Deck preset skipped when first launched from Desktop Mode | open | D283 | Workaround is documented in the README. Make the fix not depend on the env var. | +| Windows 7 | decision | #98 | Recommend won't-fix (toolchain). | + +## 5. Modern-remaster layer (port features beyond the N64) + +**The bar:** feature parity with a Nightdive-style remaster and with +GE+/Dab's mod where it's practical. Shipped so far: native widescreen and +ultrawide, FOV, MSAA/anisotropic/texture-filter options (including N64 +3-point), draw and LOD distance, VSync and a frame cap, show-FPS, +keyboard/mouse rebinding with a GEPD-style layout, pad deadzone, +sensitivity, smoothing, southpaw and triggers, rumble, crosshair and HUD +customisation, HUD scale, music/FX volume, no-hit-flash, SkipIntro +(experimental), All unlocked (unsafe, see D387), and the F10 overlay. + +The modern defaults (native widescreen, overscan crop, 250% draw/LOD +distance, bilinear filtering) are **deliberate**. They are not a fidelity +defect. + +Background research lives in the maintainer's local notes (gitignored): +`GEPORT-REFERENCE-DEEPDIVE.md`, `MODERN-PORT-FEATURES-BACKLOG.md`, +`MODERN-OPTIONS-PLAN.md` and `PD-LEGACY-SURVEY.md` under `docs/dev/notes/`. + +### 5a. Before 1.0 + +| Item | Status | Refs | Notes | +|---|---|---|---| +| **"Original N64" preset toggle** | open | — | One switch that snaps every value the port has changed back to N64 values: draw/LOD distance, culling, texture filter (3-point), 4:3, no overscan crop, stock FOV, N64 aim. It's cheap, because each is already a config key. This is the port's answer to the XBLA/Nightdive "original vs enhanced" switch. | +| `All unlocked` writes fake unlocks into the real save | open | D387 | **The only data-integrity bug.** Fix it or gate the option harder. Until then, the README warns users to back up the EEPROM. | +| Settings units and values cleanup | decision | D357 ([plan](dev/D357-SETTINGS-VALUES-PLAN.md)) | Awaiting sign-off. Its "Now" column is partly stale: the deadzone is already split L/R, and draw/LOD defaults are 250, not 150. Covers FOV in degrees, sensitivity shown as ×, MSAA 16×, filter naming, volume step and the FPS preset grid. | +| Gamepad button rebinding UI | open | PR #109, D394 | Keyboard/mouse rebinding has shipped. Review #109 under the outside-PR policy. | +| Per-pad tuning (device index, hot-plug seats) | open | — | Needed for split-screen anyway. | +| Full-gamepad menu navigation audit, Xbox/PS button glyphs | open | — | Menus are only partly audited for pad-only use. There are no glyphs yet. | +| 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. | +| F10 overlay polish | open | D335b, #90 | Pillarbox the overlay in widescreen; colour experimental rows red; warn on unknown `ge007.ini` keys (promised in #90; today they are only logged). | +| Master volume, audio output device select | open | — | | +| 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 premise predates the D248 real-60 fix, so re-measure first. Must stay compatible with netplay determinism. | + +### 5b. GE+ / Dab's-mod gameplay options (opt-in; touch game code) + +| Item | Status | Refs | Notes | +|---|---|---|---| +| Disable knockback / hitstun, damage sound, endless death cam | decision | hooks at `chraction.c:2233`, `gunfire.c:3334`, `bondview2.c:8354` | Needs one batched Rule-2 sign-off. | +| Bodies stay where they fell | decision | — | | +| Port-native third-person camera | decision | #110 | The 007 Plus ROM patch itself is out of scope. Decide whether a port-native TP camera is in or out. | + +### 5c. Enhanced visuals (optional; after feature-complete) + +The XBLA/Nightdive-style "new look". None of it has started in this repo. +An outside fork has a large Original/Enhanced/Remaster render-mode stack +(see *Community forks* below). Adopting any of it is its own decision and +needs Rule-2 review. + +| Item | Status | Notes | +|---|---|---| +| Post-process chain: FXAA/SMAA, bloom, depth of field, colour grade/LUT, CRT filter, brightness/gamma, colourblind filters, SSAO | decision | Build the framebuffer pass once. Every effect defaults OFF. | +| Render scale / supersampling, integer scaling, "authentic 320×240" mode | open | | +| Dynamic/coloured lighting, dynamic shadows, water reflections | decision | Large. Consult the PD port first. #105 is about the fork's version of reflections, not ours. | +| HD texture packs: loader + texnum dumper; model packs | decision | #100. There is a staged design. Step 1 (texture identity registry) touches `src/game`, so it needs Rule-2 framing. | +| Animation smoothing (keyframe slerp) | open | | + +### 5d. Extras (nice to have) + +In-game console · demo/replay record and playback (doubles as the +determinism harness, §7) · photo mode · benchmark mode · subtitles · HUD +opacity and a flash-intensity slider · a screen-shake slider that covers +every shake source (D181 only scales explosions) · F11 video recording · +in-game patch notes and update check · mod loader (parked: branches +`land/modloader-*`, D413/D414; only the content tier M1/C3′ is viable here) +and a stage loader. + +## 6. Multiplayer beyond the N64 + +| Item | Status | Refs | Notes | +|---|---|---|---| +| LAN netplay | decision | — | Depends on split-screen. Determinism groundwork is D117 (not achieved). | +| MP bots | decision | #111 | Not N64 (GoldenEye's MP is human-only; Simulants are Perfect Dark). birdturtle `simulants` is an outside prototype. | +| Co-op campaign | decision | — | Not N64. thepont `feature/coop` is an outside prototype. Listed under Not planned unless this changes. | +| Cheats beyond the N64 cheat menu | decision | #113 | Only in scope as an opt-in toggle. | + +## 7. Tooling, infrastructure, security + +| Item | Status | Refs | Notes | +|---|---|---|---| +| Security review follow-ups | open | maintainer's local security review | Four open hardening items: one Medium (self-hosted CI workflow input handling), the rest Low (local-input only). Details are kept out of this public file. Also: pin Actions by SHA, pin `pyinstaller`, take the Dependabot action bumps (#1/#2). | +| Deterministic 1964-vs-port comparison harness | open | — | Would replace by-eye/by-ear fidelity checks. Highest-leverage tooling item. | +| Scripted-playthrough harness | open | `GE_INPUTSCRIPT` | Input script + `GE_PCDUMP` + crash-log check, for unattended regression runs. The tool exists; the harness doesn't. | +| Golden baselines | open | `tools_pc/verify.sh`, D117 | bunker1 has been noisy since M-84, so the per-patch gate is unreliable. Re-base it, extend to all 21 levels × both platforms, and finish `GE_DETERM`. | +| 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. | +| `build-pc.sh` TMP self-heal fails in-script | open | AGENTS.md | A standalone `.ps1` workaround is documented. | +| Remove temporary scaffolding before a release | open | [`dev/GE-ENV-PROBES.md`](dev/GE-ENV-PROBES.md), D302 | Probes: D207S, D157, D252POOL, D245V, D236 (RAW/RM/ORDER/ALPHA/ZFIX/BT), D288, D309, D318B/D320 repro, `GE_OBJT`. Tools: `tools_pc/scan_op12.py`, `scan_op13.py`. Also demote the D318 watchdog to detect-and-log (D329 removed the cause), remove the M-183/M-185 clamps after a full cutscene re-check, and grep hot paths for `getenv` (D302 repeat offender). | +| `crash.c` backtraces | open | — | The EBP walk dies at frame 1. Switch to `CaptureStackBackTrace`/`StackWalk64`. | +| `romdata.c` VirtualAlloc fallback hardening | open | D179 tail | Minor. | +| Findings log and doc size | open | — | `findings.md` is about 2 MB and growing. It needs the deferred porting-notes TOC and docs-budget script (context cost for every session). | +| Branch and PR housekeeping | open | — | Push local `main` (5cccbcef, post-v0.4.0 bookkeeping). Prune the ~20 squash-merged branches: keep `feat/mp-splitscreen-99`, `land/modloader-*` and `research/cut-content-backlog`, and first confirm the single unique commits on `feat/unlocked-fps-phase1-audit` and `docs/m138-security-disclosure` landed. 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. | + +## Community forks (not ours; reference only) + +| Fork | What it has | Relevance | +|---|---|---| +| thepont/goldeneye-pc-port `feature/graphics-final-stack`, `fix/original-water-fidelity`, `feature/coop` | ORIGINAL/ENHANCED/REMASTER render mode, lighting, post-FX, bump/shadow/DoF, water reflections, vector HUD font, 2P co-op | Source of #105. Heavy `src/game` edits. **Their D323–D326 and "D329" labels clash with ours**, so relabel anything ported. | +| birdturtle `feature/local-mp-input`, `simulants` | Local MP restore; first MP bot | Cross-check for #99 and #111. | +| kaziema `vita` | PS Vita port | Platform reference. | +| mattymattmattmatt/goldeneye-pcvr-port `vr/stereo-headaim` | VR stereo + head aim | Platform reference. | + +## Decisions owed (maintainer) + +1. **PAL/JP:** pursue it (needs those ROMs; also unblocks drop-in Part B), or stay NTSC-only? +2. **Split-screen:** approve the D417 Rule-2 change, then merge `feat/mp-splitscreen-99`. +3. **D387:** fix `All unlocked`'s save leak, or restrict the option? +4. **D357:** sign off the settings units/values plan (after refreshing its stale table). +5. **GE+ gameplay options (§5b)** and a port-native third-person camera: in or out? +6. **Enhanced visuals (§5c):** build our own, adopt parts of thepont's stack, or skip? +7. **Beyond-N64 multiplayer (§6):** netplay, bots, co-op, cheats. In scope after feature-complete, or not? +8. **Linux co-equal status** and **release cadence / tag policy**. +9. **Outside PRs:** #107 and #108 (with the guard concern), #109, #88/#95. Review order. +10. **D433 gunshot timbre, D323 authored mips:** worth chasing, or accept? + +## Not planned + +Online (non-LAN) multiplayer, matchmaking, leaderboards, anti-cheat, +cross-play · Steamworks, achievements, cloud saves (no storefront) · ray +tracing, HDR, frame generation, stereo 3D · remake-scope assets (new +models, music or voice) · new movement mechanics (jump, roll, melee) · the +007 Plus ROM patch as such (#110) · co-op (unless decision 7 changes it) · +Windows 7 (#98, pending decision). diff --git a/docs/_config.yml b/docs/_config.yml index 14895403..0d959a57 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -45,23 +45,18 @@ plugins: exclude: - BACKLOG.md - HANDOFF.md + - ROADMAP.md - SetupGuide.md - StructureGuide.md - StyleGuide.md - dev/AUDIO-PLAN.md - dev/AUDIT-M6-player-offsets.md - - dev/D172-PARTICLE-COLOR-RESEARCH.md - dev/D176a-SKY-NOTES.md - dev/GE-ENV-PROBES.md - - dev/GRAPHICS-BACKLOG.md - dev/HANDOFF-ARCHIVE.md - dev/LEVEL-OBJECTIVES.md - dev/LEVEL-PLAYTEST.md - dev/LEVEL-STATUS.md - - dev/LINUX-PORT.md - - dev/M-35-PLAYTEST-CHECKLIST.md - - dev/M-35-QOL-REVIEW.md - - dev/M-36-TRIAGE.md - dev/MODERN-PORT-FEATURES-BACKLOG.md - dev/OPTIONS-MENU-PLAN.md - dev/QOL-INVENTORY.md diff --git a/docs/dev/AUDIO-PLAN.md b/docs/dev/AUDIO-PLAN.md index 8b6d8853..80ab96ec 100644 --- a/docs/dev/AUDIO-PLAN.md +++ b/docs/dev/AUDIO-PLAN.md @@ -1,5 +1,7 @@ # AUDIO-PLAN — Phase 3 / ROADMAP B3 (libaudio → SDL) +> **Historical record — do not act on status claims in this file.** The audio track shipped (D198–D204); the "gaps" and TODOs described below are closed. Kept for the architecture rationale. Current open work and status: [`docs/ROADMAP.md`](../ROADMAP.md) (2026-09-29). + Status: plan of record for the audio track, 2026-09-05. Companion to `ROADMAP-1.0.md` §B3 ("audio (Phase 3)", "audio track parallel from M-50 → v0.4.0"). Independent of B1/B2 — can start immediately. diff --git a/docs/dev/D172-PARTICLE-COLOR-RESEARCH.md b/docs/dev/D172-PARTICLE-COLOR-RESEARCH.md deleted file mode 100644 index 541410df..00000000 --- a/docs/dev/D172-PARTICLE-COLOR-RESEARCH.md +++ /dev/null @@ -1,170 +0,0 @@ -# D172 — particle/flares wrong color (magenta/cyan blood): ROOT CAUSE FOUND + FIXED (M-82) - -Symptom (user-reported, GRAPHICS-BACKLOG line 31): bullet impacts / flares / blood spatter -render magenta/cyan instead of dark red. - -## TL;DR (M-82) - -**Root cause:** `gfx_lod_tile_offset()` (`gfx_pc.cpp`) returns `0` unconditionally on -the `!gfx_detail_textures_enabled` path (a D107 blurry-ceiling fix — and -`port/src/video.c:225` sets that flag `false` at startup). Particle draws use -`G_CC_INTERFERENCE` (`TEXEL0 * TEXEL1`) with tile 0 = IA8 smoke @ TMEM 0 and -tile 1 = RGBA16 fire @ TMEM 0x188 — but with the offset pinned to 0, **texunit 1 -samples the smoke texture too**. `smoke * smoke` through the rest of the combine -= the magenta/cyan. Confirmed by the `GE_D172` probe on a user Silo firefight: -40/40 particle tris logged `cfg1[tile=1 tmem=392 fmt=RGBA]` but -`SAMPLED1=tile0(tmem=0 fmt=IA)`. - -**Fix (`gfx_pc.cpp` `gfx_lod_tile_offset`):** `return rdp.tex_lod ? 0 : i;` — -fold to the base tile only when LOD is actually active (D107's mip case, -`tex_lod=1`, unchanged); a genuine non-LOD 2-texture combine now gets tile `i`. -`bunker1` verify PASS (the D107 repro level). Committed on PR #34. **Owed: an -in-game eyeball that blood/sparks now read dark red.** - -The 0xB9 / cycle-type false trails and the probe design are kept below for the -record. - ---- - -Original note (superseded): Static audit done; one strong root-cause candidate -with a cheap in-game verification. **No code changed.** - -## 1. How particles get their color state (mechanism) - -Particle triangles (`explosion.c`, `gSPVertex` + `G_TRI_FILL`/`G_TRI_SHADE_TXTR`) carry -**no per-primitive material command**. Their CC/tile state comes from replaying ROM-resident -state records via `gSPDisplayList(gdl++, g_ExplosionDisplayLists[var_s2])` -(`explosion.c:894`, 15-record loop, one record + its particles per iteration). - -- Records: `globalDL_0x078 … globalDL_0x9a8`, a 17-entry table at image-bank offset - 0x28 (`g_pc_gimg_off_globalDL_*`), bank base cart 0x1029D160 (see `image_bank.c:322`). -- Each record is also scanned at load by `texLoadFromDisplayList` (`src/game/image.c:475`) - for its `G_SETTIMG` + ABCD marker word — that part works (textures upload fine). - -## 2. Record layout (decoded from ROM) - -``` -+0x00 E7 C3 FF FE G_RDPPIPESYNC -+0x08 B9 00 03 1D ??? <-- UNHANDLED by fast3d (see §4) -+0x10 second word of the ??? command -+0x18 FC ... G_SETCOMBINE w0 (rgb c0 | alpha c0 <<16) -+0x20 FF ... G_SETCOMBINE w1 (rgb c1 | alpha c1 <<16) -+0x28 FD ... G_SETTIMG w0 (timg pointer) -+0x30 .. 50 50 50 50 G_SETTIMG w1 (ABCD marker) -``` - -Dominant particle combine mode (14 of 17 records), decoded with the standard libultra -bit layout: - -- cycle 0 RGB: `TEXEL0, 0, TEXEL1, COMB_A` -- cycle 0 ALP: `TEXEL0A, COMB_A, SHADE, COMB_A` -- cycle 1 RGB: `COMBINED, 0, SHADE, COMB_A` ← **2-cycle** modulate -- cycle 1 ALP: same as cycle 0 - -## 3. fast3d audit (what is right) - -- `G_SETCOMBINE` bit extraction (`gfx_pc.cpp:2786`) matches the standard libultra layout - exactly (verified field-by-field against the ROM words). Not the bug. -- Store/decode of `rdp.combine_mode` (`gfx_dp_set_combine_mode` vs `gfx_generate_cc` - key shifts) are consistent. Not the bug. -- The generated GLSL combiner implements the RDP formula with the usual special cases; - `SHADER_COMBINED` correctly resolves to the previous cycle's running value. - -## 4. The 0xB9 root-cause candidate is WITHDRAWN (M-82, 2026-09-08) - -The earlier audit claimed fast3d has "no case for opcode 0xB9" and that the D146 -`default:` guard therefore aborts every particle sub-DL. **That is wrong.** - -This build does **not** define `F3DEX_GBI_2` (verified: no `-DF3DEX_GBI_2` in -`CMakeLists.txt`, no `#define` in `include/PR/gbi.h` or anywhere in tree). In the -non-F3DEX2 branch of `gbi.h`, `G_SETOTHERMODE_L = G_IMMFIRST-6 = -71`, and -`(uint8_t)(-71) == 0xB9`. So `gfx_pc.cpp:2731` `case (uint8_t)G_SETOTHERMODE_L:` -**is** `case 0xB9:`. - -Decode of the record word `w0 = 0xB900031D`, `w1 = `: -`gfx_sp_set_other_mode(C0(8,8)=3, C0(0,8)=29, w1)` → sets `other_mode_l` bits -3..31. `sft=3 == G_MDSFT_RENDERMODE`, `len=29` — this is exactly -`gsDPSetRenderMode(w1)`. `w1` values (`0x0C184B50`, `0x00504B50`, …) are -render-mode / blender words. fast3d applies it correctly. - -So the particle sub-DL runs to completion on PC: `E7` RDPPIPESYNC (handled), -`B9` SetRenderMode (handled), `FC` SETCOMBINE (handled), `FD` SETTIMG (handled). -**No D146 abort. No stale-state fallthrough.** The wrong-colour bug is elsewhere. - -## 5. Cycle-type is ALSO ruled out — the real record, and where the bug is (M-82) - -The actual particle records are in **`assets/oddtextures.c`** -(`globalDL_0x078` … , 17 of them). One record verbatim: - -```c -gsDPPipeSync(), -gsDPSetCycleType(G_CYC_2CYCLE), // <-- record DOES set 2-cycle -gsDPSetRenderMode(G_RM_PASS, G_RM_ZB_CLD_SURF2), -gsDPSetTextureLOD(G_TL_TILE), -gsDPSetCombineMode(G_CC_INTERFERENCE, G_CC_MODULATEIA2), -gsSPTexture(..., G_TX_RENDERTILE, G_ON), - // tile 0: IMAGE_SMOKE_0 IA16 image -> loaded/rendered as IA8, 56x56 - // tile 1: IMAGE_FIRE_0 RGBA16, 16x14, loaded to tmem 0x188 -gsDPSetTextureLUT(G_TT_NONE), -gsDPPipeSync(), -gsSPEndDisplayList(), -``` - -So the record **explicitly sets `G_CYC_2CYCLE`** (opcode `0xBA` = -`G_SETOTHERMODE_H` in this build), and fast3d handles `0xBA` correctly: -`gfx_pc.cpp:2734` → `gfx_sp_set_other_mode(52, 2, w1<<32)` sets `other_mode_h` -cycletype = 2CYC. **Confirmed by probe (M-82):** the `GE_D172=1` SETCOMBINE -probe logged the particle combine `w0=fc26a004` (= `G_CC_INTERFERENCE`/ -`G_CC_MODULATEIA2`) with **`cycletype=1 (2CYC)`** every time. The cycle-type -hypothesis is dead too. - -**The bug is the `G_CC_INTERFERENCE` two-tile combine / TEXEL1 path.** -`G_CC_INTERFERENCE` cycle-0 = `TEXEL0 * TEXEL1`; the record binds **two -different tiles of two different formats** (tile 0 IA8 smoke, tile 1 RGBA16 -fire at tmem 0x188). Magenta/cyan = a TEXEL1 sampling/decode fault: wrong tmem -offset for tile 1, RGBA16 (5551) channel/endian misread, or fast3d treating -tile 1 as an LOD mip of tile 0 under `G_TL_TILE`. This needs a visual -render-iterate pass (headless can't see particle colour) — see §6. - -### Probes shipped (M-82, `#ifdef PORT`, `GE_D172=1`, inert when unset) - -- `gfx_pc.cpp` G_SETCOMBINE case — logs each distinct combine + active - cycletype (dedup, 64 max). Confirmed the particle combine `fc26a004` runs - at `cycletype=2CYC`. -- `gfx_pc.cpp` `gfx_sp_tri1` (after the texunit loop) — whenever a **2-cycle - triangle actually consumes TEXEL1**, dumps `combine_mode` + both texunits' - `tile / tmem / fmt / siz`. This is the decisive probe: a Silo close-range - guard kill with `GE_D172=1` should log - `T0[... fmt=3(IA) siz=1(8b)] T1[tmem=392 fmt=0(RGBA) siz=2(16b)]`. - If `T1.tmem != 392`, or `T1.fmt/siz` are wrong, the TEXEL1 bind is the bug. - If they're all correct, the fault is in `import_texture_rgba16` for the - second texunit or the `G_CC_INTERFERENCE` CC generation — pair with - `GE_DTEX=1` / `GE_TEXRAW=1` to dump the fire tile's bytes. - -## 6. Fix path (next session — needs a display) - -1. Get a `-level_XX` capture with visible particle spray (Silo guard kill at - close range per the original report, or any wall-impact spark). Note the - colour. -2. Env-gated probe in `gfx_pc.cpp` `import_texture` / the TEXEL1 bind path: - for tile 1 when combine == the particle key, log format/size/tmem-addr/ - line_size and the first few decoded texels. Compare against the RGBA16 - `IMAGE_FIRE_0` bytes in the extracted asset. -3. Likely fixes, in order of suspicion: (a) tile-1 tmem address 0x188 not - honoured → TEXEL1 reads tile-0 bytes; (b) RGBA16→RGBA32 decode for the - second texunit; (c) `G_TL_TILE` LOD path picking the wrong tile for TEXEL1. -4. Re-decode `G_CC_INTERFERENCE` / `G_CC_MODULATEIA2` as fast3d generates them - (`gfx_cc.cpp`) and confirm the GLSL matches `TEXEL0*TEXEL1` then - `COMBINED*SHADE`. - -## Files - -- `src/game/explosion.c` — record table (165), replay loop (894) -- `src/game/image_bank.c:322`, `src/game/image.c:475` — record offsets / load-time scan -- `port/fast3d/gfx_pc.cpp` — SP walker cases (~2600–2930), D146 default (2920), - SETCOMBINE decode (2786), combine store (2270), cc generation (312+), 2CYCLE flag (1523) -- `rsp/graphics/gmain.s` — N64 ucode ground truth for 0xB9 (dispatch table at dmem 0xbc; - ginit.s missing from repo) - -Note (M-82): 0xB9 is `G_SETOTHERMODE_L` in this non-F3DEX2 build and is already -handled by fast3d — `gmain.s` / `ginit.s` are NOT needed to resolve D172. diff --git a/docs/dev/D176a-SKY-NOTES.md b/docs/dev/D176a-SKY-NOTES.md index 2e728c89..e02044e7 100644 --- a/docs/dev/D176a-SKY-NOTES.md +++ b/docs/dev/D176a-SKY-NOTES.md @@ -1,6 +1,6 @@ # D176(a) Sky — consolidated research notes (read-only) -Companion to `GRAPHICS-BACKLOG.md` D176(a) and findings.md M-37/M-47. +Companion to the (since retired) `GRAPHICS-BACKLOG.md` D176(a) and findings.md M-37/M-47. Purpose: make the next implementation session mechanical. No code changed by this doc. diff --git a/docs/dev/F10-GE-STYLE-PLAN.md b/docs/dev/F10-GE-STYLE-PLAN.md deleted file mode 100644 index ac14238e..00000000 --- a/docs/dev/F10-GE-STYLE-PLAN.md +++ /dev/null @@ -1,129 +0,0 @@ -# F10 visual refresh — GoldenEye dossier styling (proposal) - -Status: **superseded by D363 after user playtest** — the dossier/paper -concept below was implemented as D362, then replaced with a dark -watch-green/Bank Gothic version. This is the historical proposal for a -port-layer F10 overlay proposal, not a replacement for GE's pause watch or the -front-end PC Options screen. Reference surfaces: `src/game/front.c` -`constructor_menu15_cheat`/`frontSetupMenuBackground`, the existing -`port/src/frontoptions.c` cheat-list-inspired options screen, and GE's in-stage -watch. Current overlay: `port/src/optionsoverlay.c` `optionsOverlayEmit`, -`overlayRowAtY`, `sliderBarSpan`, `maxVisibleRows`. - -## 1. Direction and boundaries - -Make F10 read as an **MI6 dossier laid over the game scene**, not a generic -SDL debug panel: dark translucent scrim, warm paper/ivory content card, -black Zurich-style menu text, red for active options/slider fills and an -occasional muted gold title/accent. The front-end PC Options already uses -GE's actual 3D folder and Zurich text; use it as the visual reference, not -as a display-list dependency. In-stage F10 must remain an inexpensive flat -2D overlay: no loading a 3D wallet model, new ROM assets, front-end init -functions or `src/game` modifications. Preserve the current two-level -category -> settings navigation and the shared settings/commit API. - -The initial port-only implementation lives in `optionsOverlayEmit()` and -uses one shared `OvLayout` for both 320x240 stage and 440x330 front canvases. -Headless captures live under `scratch/d362-test/` (not distributed). Manual -visual review on a bright stage and controller/mouse feel-check remain owed. - -## 2. Concrete visual hierarchy - -- **Backdrop/card:** keep an adjustable translucent dim over the scene; - replace the opaque blue-black rectangle with a near-opaque ivory panel - (subtle border and one offset shadow). Do not mimic the actual folder - texture unless a later, measured prototype proves the simple card - insufficient. Preserve legible contrast against both dark and bright - levels, including safe-area crop and widescreen pillarboxes. -- **Masthead:** small gold/black `MI6 / OPTIONS` or `PC OPTIONS` heading, - fine black rule (a restrained `CLASSIFIED` stamp can be added later if - measured text fits) rather than a blue title/instruction block. Keep the - category name on the **same - page above its first setting**, especially INPUT over Mouse sensitivity. -- **Root:** numbered `1. Input` ... `5. Video`, GE's dossier/menu numbering - and title case as on `frontoptions.c`; a narrow translucent dark selection - box behind the focused item. Category order and labels stay unchanged in - the shared row table; casing is presentation-only. -- **Section:** a compact `PREVIOUS`/Back tab, title and optional - `(Profile N)` annotation, then two columns: label at left, value at right. - Unselected values in black, active/toggled values in GE's dark red - (`0xA00000FF` is the cheat-screen reference). Slider tracks are thin - charcoal with a red fill and an obvious knob; show the numerical value - outside the bar. A mixed-scope `(per profile)` marker must remain legible - without colliding with the value column; measure it and, if necessary, - use a small second line rather than silently removing the distinction. -- **Actions:** Reset retains its two-press/three-second confirmation and - displays `CONFIRM` in red only while armed. Quit remains visually distinct - from resets (and retains its existing activation semantics). Back/Close - affordances should match the actual click targets, not become decoration. -- **Footer:** one quiet, short line for mouse/arrows/controller, with an - explicit `ESC: Back / F10: Close` cue. No newly invented glyphs or fonts - that may be missing from the game's text bank. FPS readout is independent. - -Suggested layout sketch (not pixel positions): - -``` - MI6 / PC OPTIONS CLOSE - ----------------------------------------------- - PREVIOUS GRAPHICS - Anti-aliasing 2x - Texture filter Bilinear - Draw distance [------|----] 50/100 - ... - Reset to defaults [ENTER] - ESC Back / F10 Close -``` - -## 3. Implementation boundaries - -1. Extract a port-only layout helper for fonts, palette, - panel rectangles and spacing; preserve `rows[]`, `rowAdjust`, persistence, - file scoping, reset logic and input bindings. Use GE's loaded Zurich Bold - font for body *only after* validating it exists on both stage and front - contexts; Bank Gothic can remain a fallback/header font. Text clipping - must use the whole viewport as `textRender` expects. -2. Replace only the fill/text passes in `optionsOverlayEmit()` first. Emit - fills before text as currently, reset GBI state explicitly and keep - `OV_BUF_CMDS`/DL size bounds. Avoid mutable texture uploads or new assets. -3. One shared geometry calculation must drive panel/card positions, - `maxVisibleRows`, row Y, slider-bar span, close/back hitboxes, and - `overlayRowAtY`. In particular, don't change visual row spacing without - updating hit-testing (D304/D316), and don't use naive OS-window pixel - mapping instead of `gfx_get_ui_screen_rect` (D316/D335). When a page - scrolls, keep its section title visible; a clipped row cannot be clicked. -4. Use measured text widths to reserve space for values, `Confirm`, - `(Profile N)` and restart markers. At 320x240, allow scrolling/short - contextual hints, not overlapping text or tiny glyphs. At 440x330 and - ultrawide, centre or constrain the card rather than stretching the - columns across the entire viewport. -5. Make any GE-like UI sound effects opt-in to a *separate* follow-up: F10 - runs in controller polling, and D361 prohibits game save/audio calls - from that thread. A cosmetic refresh must not reintroduce that lockup. - -## 4. Acceptance gates - -- Compare screenshots with the existing front Options/cheat dossier and the - in-stage watch; user reviews the live palette/layout before closing the task. -- Native 320x240, front-end 440x330, windowed 4:3, 16:9 and 21:9, - fullscreen, safe-area crop on/off: INPUT title visible; no text clipping, - unreadable values, wrong hitboxes, phantom/duplicated bottom rows or - overflow in the display list. Check graphics's longest labels and AUDIO's - per-profile markers with and without a valid file. -- Mouse click/drag/wheel, arrows and D-pad/stick, ESC back, F10/Start/X - close, focused/armed reset, and quit action all behave **exactly** as - before. F10 Audio on file select still commits via D361's game-thread - queue; test both fresh and existing EEPROM without altering the user's - save (backup/restore for probes). -- With overlay closed, append no panel DL and retain existing golden frame - hashes (unless FPS display is explicitly enabled). Run `./build-pc.sh - ntsc-final`, link/duplicate-symbol checks, front-end + stage smoke, - `GE_WSPROBE_F10FRONT` and targeted live playtest. Log the result in - `docs/dev/findings.md` after implementation, not in advance. - -## 5. Explicit non-goals - -No fullscreen 3D folder in gameplay, changes to the N64 watch, new config -keys, row wording/value changes, save-format changes, controller remapping, -new persistent assets, or `src/game` behavioral edits. The front-end PC -Options screen is a **visual reference**, not part of the first styling -patch; harmonizing its tiny decorative details is a later optional pass. diff --git a/docs/dev/GRAPHICS-BACKLOG.md b/docs/dev/GRAPHICS-BACKLOG.md deleted file mode 100644 index c629fae5..00000000 --- a/docs/dev/GRAPHICS-BACKLOG.md +++ /dev/null @@ -1,51 +0,0 @@ -# GRAPHICS-BACKLOG — known cosmetic / rendering issues - -Deferred below level-progression and crash work (user priority, M-11). -Minor graphical glitches are acceptable in the current state. Fix these -once the game is playable start-to-finish without crashes. - -## Open - -| ID | Symptom | Status / notes | -|----|---------|----------------| -| ~~**D114/D116**~~ | ~~HUD text + ammo digits render each glyph individually X-mirrored~~ | **CLOSED — NOT A BUG (M-33, §F D168).** The `GE_PCDUMP` PPM writer never reversed `glReadPixels` rows, so every capture was upside-down; "mirrored" was a misread of inverted asymmetric content. The game renders correctly on hardware (developer-confirmed). Writer fixed (`gfx_opengl.cpp`). "Every stage verified non-mirrored" was correct — there was no mirror. | -| ~~**D74 wrap-block**~~ | ~~`gfx_pc.cpp:1546/1551` PORT block is dead code (guard `cms & G_TX_WRAP` always false; indexes per-texunit arrays with the vertex index → OOB if reached).~~ | **RESOLVED (M-30, RC3/D167).** The in-vertex-loop wrap block was reworked and hoisted to a per-texunit pre-wrap window (`gfx_pc.cpp` ~1574, now `cms`-correct and indexed by texunit not vertex), behind the `Video.WrapFix` knob (default OFF). No longer dead code or OOB. See §F "RC3 · D167". | -| **D392** | In the opening attract-mode Train demo, a soldier consistently looks more hunched on PC than in 1964/GEPD; his hand intersects his body at the wrong position. User comparison: `docs/img/animation-glitch-pc-port.png` and `docs/img/animation-glitch-1964gepd.png`. | **FIXED — user visually verified (D392).** Train's normal opening (`-level_25`) has a central firing guard (`chr=5`) whose torso/shoulder aim offsets reached `aimupback=-0.855`, `shoulder=-0.606,+0.868` radians by tick 100. `chrlvUpdateAimendbackShoulders` used N64 `((f32*)arg1)[12/13/16/17]` offsets into a firing table whose first member widens from a 4-byte to an 8-byte pointer union on PC; it read *other* floats and bent the torso/arms to wrong angles. `#ifdef PORT` now reads `max_up/max_down/free_arm_frac_up/free_arm_frac_down` by name; with the same short Train intro probe the torso offset stays 0, shoulders ~0.028/+0.014 at tick 100. User's original comparison: `docs/img/animation-glitch-pc-port.png` vs `docs/img/animation-glitch-1964gepd.png`. The user subsequently confirmed the soldier's pose looked fixed in-game; this session's image tool itself cannot display PNGs. No ramrom demo override needed; ordinary `-level_25` reproduces. | -| **D75** | ~~Nintendo logo mispositioned; gun-barrel Bond model absent~~; cast-roll character models — status unknown. (Rareware logo OK.) | **Nintendo logo + GoldenEye logo + legal page FIXED (M-197, user-confirmed live 2026-09-21).** Root cause: the D50 sidecar converter (`tools_pc/d43_emit.py`) reserved + remapped the model's embedded texture-blob span but never copied the pixel bytes into the emitted RZ image → TEXEL0 sampled a zeroed buffer → the texture-modulate logo rendered black (M-195/M-196 had proven emission/transform/combiner all correct). Same latent bug hit `PgoldeneyelogoZ` + `PlegalpageZ`. Fix = tooling-only (blob raw-copy + round-trip check); ntsc sidecars regenerated; logo window now renders 23k px (7.59%) vs 0–33 pre-fix. Gun-barrel Bond renders correctly (M-33). Cast roll still unverified — recheck with `GE_PCDUMP="1400-2200:25"`. `findings.md` §F D75 (M-195/M-196/M-197 updates) + M-197 at file tail. | -| **D76** | 2D disclaimer screen only partially drawn. | §F D76. | -| ~~**D77**~~ | ~~No audible music on PC.~~ | **RESOLVED (M-104, PR #42) — in-level music now plays.** Root cause was a D6-class missing `return` in `sub_GAME_7F0C0BF0` (`mp_music.c`) that zeroed the seqp volume on the only in-level trigger path; menu/intro music bypassed it and was always fine. The remaining audio-*quality* gap (wrong-sounding instruments on some tracks — elevator named specifically — plus occasional garbling) is tracked as **D230**. §F D77/D230. | -| ~~**D116 (HUD)**~~ | ~~proportional-font strings + ammo counter mirrored~~ | **CLOSED — NOT A BUG (M-33, §F D168).** Same capture-orientation artifact as D114. | -| **D148** | Level-end cinematics glitch. Dam: the Bond rappelling/bungee cutscene plays but the camera violently shakes and Bond's own model appears to duplicate/glitch. **Cutscenes are buggy in many places across the game** (D173 level-start floating, D292 death-sinking, Cradle spin) — treat D148/D243 as the tracking entry for the whole cutscene-reliability problem, not just Dam. | **2026-09-17 (D243 M-107 through M-168, ~18 session-passes): the mechanism is now understood in real depth, and the camera-shake half is fixed (diagnostically, live-confirmed).** Full history + code citations: `docs/dev/findings.md` `## D243` (long, multi-pass — look up via `findings-index.csv`, read the M-159 through M-168 paragraphs for the current understanding, don't linear-read). Short version for planning purposes: **(1) Root mechanism** — the cutscene teleports the *visible* Bond chr between shot positions correctly (`AI_TRYTeleportingChrToPad`, `chrai.c:4064`), but the actual rappel *animation* commands run on a separate, invisible driver chr (M-145) — the visible chr never gets told to play it, so it just keeps ticking whatever animation it already had equipped before the cutscene grabbed it. Every tick, that stale animation's own root motion gets read back into the chr's render position (`chrUpdateAnim`→`getsuboffset`, `model.c:684`), producing a small repeating cycle that reads as "shaking"/"glitching" on screen. Whether this exact mechanism is itself decomp-faithful (with the real PC/N64 divergence being *which* animation phase Bond happens to inherit at the cutscene boundary, not a code-path bug) is still an open question (M-165/M-166) — not yet answered. **(2) Two independent render paths, not one bug** — the camera's look-at filter reads `field_488.pos` (derived from `render_pos`, the model's animated-skeleton transform); the model's own on-screen draw reads `render_pos` directly (`objecthandler.c`'s `drawjointlist`). Fixing one does not fix the other; two live-test cycles were spent learning this the hard way (M-162/M-163 → M-167). **(3) Camera shake: FIXED at the mechanism level, live-confirmed by the user** ("camera shake is gone now") by freezing `field_488.pos` during the cutscene (`GE_D243X3`, `bondview2.c:10495`) — diagnostic-only, not shippable as-is (see next). **(4) Bond's own model duplication: mechanism-level fix attempted (`GE_D243X4`, `chr.c`, freezing `render_pos`'s contents) — stable after fixing two crashes (model-pointer-reuse hazard, see `porting-notes.md`'s two D11 updates), but freezing unconditionally also breaks the cutscene's *legitimate* per-shot camera/model repositioning** (the camera now looks the wrong direction for later shots; Bond still looks stuck) — not a fix yet. **Next steps for whoever picks this up** (none attempted yet): (a) re-baseline the `GE_D243X3`/`GE_D243X4` freeze on a position-delta threshold (a jump much larger than the observed intra-shot cycling amplitude, already characterized in the M-153/M-160 captures, is very likely a real shot-change teleport, not cycling) instead of freezing forever; or (b) hook the freeze/re-baseline directly to `AI_TRYTeleportingChrToPad` firing, the precise, unambiguous signal for "a legitimate reposition just happened"; (c) separately, find the actual N64-vs-PC divergence point per M-165/M-166 (log `aidx=`/`animframe=`, already wired into `D243M: onscreen`) to determine whether a real, permanent fix is even a `port/`-layer timing correction or needs a rule-2 sign-off for a genuine behavioral difference — this determines which of (a)/(b) is worth turning into a real fix vs. a stopgap. | -| ~~**D149**~~ | ~~Front-end MISSION COMPLETE / mode-select 3D models (dossier, wallets) render as garbled full-screen geometry or not at all.~~ | **CLOSED — user-verified fixed (M-87 playtest).** No longer reproduces; presumably fixed as a side effect of other front-end/D75-family work since M-49. D75 family. §F D146. | -| **D170** | Named-NPC "flee" scripted behavior looks wrong on PC: on Silo, Ourumov breaks into a full run the instant he alerts ("Ourumov: Kill him!") rather than the N64 walk/retreat; developer reports Trevelyan behaves the same way on Cradle. **M-49 full playthrough: escalated — Cradle (`-level_51`) is "bugged out completely" from Trevelyan's behavior; the final level's scripted progression breaks. Now folded into / superseded by §F D193 (NPC AI locomotion too slow).** | **Observed, not investigated** (M-34, from a Silo playtest capture; user bug report — no fix wanted yet). Could be a genuine ailist / `chrTick` divergence, OR the D155/D156 wall-clock `deltaFrames` locomotion-anim speed-up (a catch-up spike playing the walk cycle at 2–3×). **Verify first:** capture the same beat against N64 reference footage before assuming it diverges — Ourumov may be scripted to flee. If it's real, suspect the frametiming clamp (§F D155/D156) or the Cradle-family AI path (§F D123). | -| **D171** | Silo missile-shaft: a large triangle sweeps across the frame while panning down the shaft. **Promoted to `docs/dev/findings.md` §F D288 (M-141)** with a user-reproduced, precisely-bounded, visually-documented repro (frames ~562-741 of a bare `-level_20` boot) — read that entry for the current state, not this row. | See D288. | -| **D172** | Bullet-impact / blood particle spray renders magenta or cyan instead of dark red (visible on guards hit at close range on Silo). | **M-82: ROOT CAUSE FOUND + FIXED (PR #34).** `GE_D172` probe on a user Silo firefight: 40/40 particle tris configure tile 1 = RGBA16 fire @ tmem 392 but fast3d **samples tile 0 (smoke) for both texunits** — `gfx_lod_tile_offset()` returns 0 unconditionally (D107 blurry-ceiling fix; `video.c:225` disables `gfx_detail_textures_enabled`). `G_CC_INTERFERENCE` = `smoke*smoke` → magenta/cyan. Fix: `return rdp.tex_lod ? 0 : i` — only fold to base tile for real LOD/mip draws. (0xB9 + cycle-type were both false trails, ruled out earlier in M-82.) **Owed: in-game eyeball — blood should be dark red.** Detail: `D172-PARTICLE-COLOR-RESEARCH.md`. | -| ~~**D178**~~ | ~~Objectives not listed correctly on the pre-mission briefing screen (the mission overview shown before starting a level).~~ | **CLOSED — user-verified fixed (M-87 playtest): "main menu text is good now."** No longer reproduces. M-36 triage below kept for reference. **M-36 triage: the objective lines are BLANK, not wrong.** `GE_STARTMENU=10` Dam briefing renders the header fine ("OHMSS / Agent: James Bond / Mission 1: Arkangelsk / Part i: Dam / PRIMARY OBJECTIVES:") then just the bullet "a." with no text and no b/c/d. → **D143/D151 blank-text family** (`langGet()` → NULL → silent blank), *not* a propDef walk / difficulty decode. **Family B** in `docs/dev/M-36-TRIAGE.md` — find which lang bank the front-end briefing flow fails to load. Re-confirm the exact shape in a *real* briefing (the `GE_STARTMENU` boot skips real save state). Close relatives: D151 (watch objective text blank — type-35/23 `text` slot u16→u32 fix, M-28; interactive re-verify was still pending), D143 (briefing/objective text blank via `langGet` NULL), D157 (objective difficulty byte offset — could make difficulty-gated objectives appear/disappear wrongly). The pre-start briefing is a *different surface* from the in-level watch, so D151's fix may not cover it. **Next:** ask user which level + what exactly shows vs expected; compare against `docs/dev/LEVEL-OBJECTIVES.md` (regenerated post-D157); if lines are blank → D143/D151 family (`langGet` NULL guard = silent blank), if wrong objectives listed → propDef type-23 record walk / difficulty decode. | -| **D175** | In-game stutter / brief hang during normal play — observed opening a door on Runway (mission 3, `-level_35`) and also on Surface. No backtrace captured (gdb attach missed the window; it self-recovered). | **Observed, not investigated** (user QA report). Matches known transient-hang classes rather than looking new: D155 (post-slow-load catch-up spiral — clamped), D156 (anim NaN loop — guarded, prints one-shot stderr diag if it fires), D134 (SP/DP task-done event swallowed — fixed M-22, Runway was an original victim), D147/D152 (audio `s_imLock` steal = ~2 s hiccup). Door-open specifically = new room first visible → one-off texture-import/frame spike (likely benign). **Next:** reproduce under `tools_pc/debug.ps1` with the box idle; check console for the D156 stderr diag; if it recurs, attach gdb mid-hang and pattern-match `bt` (import_texture = benign slow frame; model.c:3131 spin = D156; pthread_mutex_lock on s_imLock = D147/D152). | -| **D176** | Surface exterior renders wrong (two co-located defects). **(a)** Sky is solid **black** — the skybox / sky gradient does not draw at all (reference: warm sunset cloud gradient). **(b)** The perimeter **tree-line backdrop renders as grey TV-static / comb interlacing** instead of snow-laden pines — the geometry is present and roughly the right silhouette but every tree texture is scrambled noise. Ground, tunnel and the dish/bunker building draw fine. | **Observed, screenshots committed** (M-35, user QA; `docs/img/bugs/d176-surface-exterior-our-build.png` vs `-reference.png`). Level is the shared Surface exterior — user reached it Dam→Surface on Agent so this is **Surface (i) `-level_36`** (confirm it also affects `-level_43`). **(a)** black sky: **M-36 partial (findings.md "D176(a)").** Env data is correct (`Sky.Clouds=1`, `CloudRGB=240,120,30`, `SkyImageId=0`); `skyRender` runs the cloud path and classifies corners correctly (switch case 12), but `skyRenderTri`/`skyRenderFull` emit nothing — not even the `env->RGB` fill shows. Not root-caused: next is probing `sub_GAME_7F097388` projected coords, verifying `skywaterimages[0]` resolves on PC (`image_bank.c` GIMG offset `0xFB4`), and a fast3d sky-tri trace. `GE_D176=1` probe shipped. **M-37: ROOT-CAUSED** — `skyRenderTri`/`skyRenderFull` build geometry *exclusively* as `G_RDPHALF_*` immediates, which `gfx_pc.cpp` deliberately no-ops (identical no-op in the PD port); fix = decode the RDPHALF tri-raster stream in fast3d (~1 session, genuine new code). **M-42: `-level_22` (Statue) confirmed a clean headless repro** — bare boot, frame ~360, upper half pure black; use it to verify the decoder. **M-47 + read-only consolidation:** Path B draft exists (`scratchpad/sky.patch`, `skyPortRenderPoly` PORT hook — geometry+texture reach GL, two unit defects open) and the full RDPHALF wire format is now decoded (S15.1 half-packing, 8-component perspective-interpolated vertex vectors); next session is mechanical per `docs/dev/D176a-SKY-NOTES.md`. `-level_36`/`-43` intro cams face down (not a sky test without `GE_INPUTSCRIPT`). **M-82: PR #18 rebased onto main + Defect-1 fix applied** — cloud tile is `s_skywaterimages[0]` = 64×64 IA8; the patch's `tc = S·32` scale is correct (matches GE's `width<<5` idiom, `glass2.c:678`), the bug was `(s16)` overflow on the ±20k-texel horizon S/T; fix = per-primitive phase-fold to the 64-texel period (`sky.c` `skyPortRenderPoly`, `#ifdef PORT`). Builds clean, no crash (bunker1/statue/surface1 verify PASS). **Unverified visually** — owed: one `-level_22` screenshot (see `D176a-SKY-NOTES.md` "verification ask"). Defect 2 (horizon-band coverage / straddling tris) still open → adaptive tessellation. **(b)** tree/rock static: **M-36 triage — NOT the D159 family.** `texSwapAltRowBytes` is a full `#ifdef PORT` no-op (`image.c:2199`), it cannot cause this. Real cause: `port/fast3d/gfx_pc.cpp` `import_texture_*` assume `line_size_bytes == full_image_line_size_bytes` (`SUPPORT_CHECK`; rgba16's is commented out *"trips ... with a garbage size"* `:658`) → non-PoT / sub-rect tiles get a flat linear read → diagonal row shear = grey static. **Family A** in `docs/dev/M-36-TRIAGE.md`. Fix: honour `line_size_bytes` as the real source pitch. Same defect as D182(2). | -| ~~**D233**~~ | ~~All levels (intermittent): near doors — especially doors you can't see through to the inside — the indoor level geometry vanishes and you can see straight through it into the room, while world objects (computer terminals, furniture) in that space still draw.~~ | **FIXED + VISUALLY VERIFIED (M-107 fix, M-108 verify).** `port/fast3d/gfx_pc.cpp` `gfx_sp_tri1` was trivial-rejecting visible triangles that had a behind-camera (`w<0`) vertex. Status cell here was stale at "not investigated" — corrected M-119. §F D233. | -| ~~**D234**~~ | ~~Dam: the guard towers along the actual dam structure are invisible except for the first one.~~ | **FIXED + VISUALLY VERIFIED (M-108).** Same underlying mechanism as D233. Status cell here was stale at "not investigated" — corrected M-119. §F D234. | -| **D235** | Wrong textures load after dying/restarting a level — clarified: not Facility-specific, seen on multiple levels; trigger is death/restart (level reload), not a general build regression. | **Observed, not investigated** (M-106, user QA report, clarified same session). This is a state-leak on level re-entry, not a one-level texture-decode bug — prime suspect is the texture pool / CI-cache (D217/D228 neighbourhood) not fully resetting between a level teardown and reload (stale arena base, a palette-cache entry keyed by a reused address, or a texpool index not rewound). Needs: confirm it does NOT repro on a fresh level boot (only after death/restart); which surfaces; a screenshot. Next: instrument `texInitPool`/the CI-cache clear path for what does/doesn't reset on reload vs cold boot. | -| **D236** | Surface 1: the 2D billboard trees that appear at the start of the level and around the map don't draw as discrete tree sprites — they render as a wall/sheet of tree texture instead of the proper 2D tree objects. | **FIXED (M-198, pass 26) — awaiting user play-test sign-off.** Root cause was a texture-BINDING bug, not decode/alpha/mip/geometry: GE binds the four Surface 1 treeline cards (`tex1198`-`tex1201`, RGBA5551 64x17 cut-outs) as `TEXTURETYPE_DETAIL`, where `texHandleType1` (`tex.c`) loads the *detail* texture at TMEM 0 and the real base image at TMEM `texGetSizeInBytes(detail,0)` on tiles 1+. fast3d's `gfx_lod_tile_offset` folded every `G_TL_LOD` draw to tile 0 (D107, correct for TEXTURETYPE_LOD/MIPMAP), so the port drew the shared detail texture -- `tex2465`, an opaque 32x32 IA8 noise tile, which is exactly the 'noise painter' D267/D276 measured at 1023/1024 texels opaque -- and never imported the cards at all. Fix: when a tile pair differs in both TMEM address and `fmt`/`siz` under `G_TL_LOD`, sample the base image (tile 1). `Video.DetailBaseTile`, default on. Live-verified on Surface 1 (discrete snow-covered pines) and Surface 2; Depot clean; bunker1 golden bit-identical with the fix on/off; `type=1` bindings exist only in sevx/depo/sevb/sev, so every other level is provably untouched. Full trail: `findings.md` D236 pass 26; generalised as `porting-notes.md` §D12. -| **D249** | Streets and Egyptian (the two biggest/most-open levels) show culling problems on far-away geometry at *default* FOV — distant geometry drops/pops incorrectly. Also a QoL ask: improve LOD in general, but gate it behind a toggle so N64-accurate ("old school") LOD stays selectable. | **Observed, not investigated** (M-119, user QA report). Distinct from D222 (high-FOV near-edge culling) and D218 (fog/far-clip tint). Two candidate mechanisms: (a) portal/room-visibility cutting off too aggressively at range on large multi-room layouts (D233/D234 neighbourhood — check for a far-plane/depth-range sibling to D233's w<0 trivial-reject fix), or (b) GE's own LOD/detail-swap system not ported faithfully (mip-tile / simple-model substitution at distance, `gfx_lod_tile_offset` family). First checks: `GE_PCDUMP` a long sightline on Streets/Egyptian where geometry visibly pops; determine which of (a)/(b) is firing before designing the fix or the requested toggle. §F D249. | -| **D217** | 1P weapon viewmodel + character-model CI textures render with a wrong palette on many levels / multiple weapons — PP7 grip + hand render flat green on Facility & Depot, "weird or missing" gun textures elsewhere; Bond's watch face too. Surfaced by D215 (viewmodel now draws). Clean on Bunker1. | **Defect ① FIXED (M-90), pending human playtest eyeball — full detail in `findings.md` §H "D217 — M-90" and §F D217.** Root cause: weapon texpool arena bases land %8==4 on PC (memp bump drift from pointer bloat); `texAlignIndices` absolute 8-byte row padding then shifts every palette 4 bytes early while model-DL `G_LOADTLUT` reads the N64 offset (grip count=1 load lands on next tenant's header slot → `0x1A00` green; larger loads read a 2-entry zero pad — the M-86 tell). Fix: `texInitPool` rounds the base up to 8 under `#ifdef PORT` (no-op on N64); 21/21 sweep PASS. Two defects: ① **visible green = misaligned texpool arena** (fixed as above). ② **CI texture-cache collision** (no palette-content key) — proven, **fix landed on branch `fix/d217-model-texture-palette` `f6fc0399`** (palette FNV-1a hash in the CI cache key), owes a 21-level `verify.sh sweep`; does not fix ①. | -| **D174** | "No blood effect" — user reports no visible blood when shooting guards / into surfaces. | **User QA report; likely D120, unverified.** Decals: the D120 `PointUsage[]` converter fix (M-30) was never interactively verified and only takes effect after a full sidecar regen (`d43 && d69 && d88 --regen`) — check whether the playtest build had regenerated sidecars before concluding the fix failed. Spray: D172 observed the particle spray *drawing* (wrong color, magenta/cyan) on Silo — if the user sees no spray either, that's new info (different weapon/range, or the D172 decode is worse than "wrong color"). First step: BUNKER1 firefight with regenerated sidecars; if decals still absent, instrument the `chr.c:3328` decal walk. | -| **D173** | Third-person Bond model (the player-representing figure shown at level start) spawns too high — floats well above the ground "a lot of the time" while the actual player spawn is correct. Same symptom on the Cuba end credits: JB stands ~6 ft in the air above Natalya. | **Confirmed still present across the full 21-level playthrough (M-49).** **Observed, not investigated** (user QA report). D75 animated-model family but a *position* defect, not absence — M-33 confirmed the gun-barrel Bond renders fine upright. Suspects: (a) intro/credits puppet chr spawn position vs the model's base transform; (b) anim root-motion accumulation (`modelSetAnimFrame2WithChrStuff`, D156 area) offsetting the rendered joints above the body origin; (c) a `coord3d`/packed-float decode in the intro camera/model setup. "Player spawning is OK" localises it to the render-side model, not `g_CurrentPlayer` state. | -| **D182** | SELECT FILE screen degrades after navigating away and back (main menu → file select, or backing out of file select and re-entering). On the fresh screen the 007 gun-barrel spiral / Bond silhouette renders behind the file boxes; after a round-trip **the Bond image stops drawing entirely** and the whole background gains the **comb / interlacing** artifact (horizontal line scramble). File boxes, text and the Copy/Erase icons still draw. | **Observed, screenshot committed** (M-35, user QA; `docs/img/bugs/d182-file-select-backout-20260902.png`). Two symptoms, likely linked: **(1)** background 3D image stops rendering after re-entry → a front-end model/DL whose `render_pos` / segment binding is only valid on first construction (D149 / D75 `constructor_menuXX` family — the dossier/wallet models have the same "renders once" behavior). **(2)** interlacing = **M-36 triage: Family A**, the `import_texture_*` `line` vs `full_image_line` shear (see D176(b) / `docs/dev/M-36-TRIAGE.md`) — **not** the D159 `texSwapAltRowBytes` path (that is a full PORT no-op). Open question: why only *after* a round-trip — first import may land `line == width`, re-import may not, or the tile descriptor is stale on re-entry. Repro: `GE_STARTMENU` to file-select, back out with ESC/B, re-enter; `GE_DTEX` on the spiral texture across both visits. Shares defect (2) with D176(b). **2026-09-27 (D397):** user re-reported the comb/interlace on the file-select background AND on the **intro gun-barrel** — the latter is a *first-time* render, so the defect is persistent (not only after a round-trip), refuting the "stale descriptor on re-entry" hypothesis. D159 (odd-row swap) is no-op'd and D183 pitch shear is disproven for level_36 + a no-op for the 299×440×1 row-strip. Next step is a live `GE_PCDUMP` capture + ROM ground-truth decode of the gun-barrel RLE image to separate ground-truth dithered content from a GL sampling/texture-cache artifact. See findings.md D397. | -| **D197** | Character face/head textures wrap wrong — the face texture repeats around the side of the head "and continues weird" instead of clamping to the front. Seen on **Silo** (M-49 playtest), intermittent ("sometimes"). Reads as **`G_TX_CLAMP` not honoured** for the character-skin tile in fast3d (→ `GL_REPEAT`, so the face UV region tiles around the skull), or wrong `shifts`/`shiftt` / LOD-tile selection on the head texture, or the D167 non-PoT wrap-period issue (head textures are often non-PoT) hitting skinned models. | **Observed, not investigated** (M-49, user bug report — needs a capture + which chr/level). Distinct from D176(b)/D182(2) (those are *bg wall* pitch shear). Related: RC3/D167 (`Video.WrapFix`, non-PoT wrap period) + the `texWriteTileLods` `shifts=shiftt=lod` note in findings §F "D176(b) → 4. candidate 2". First checks: does `gfx_pc.cpp` map N64 `cms/cmt` `G_TX_CLAMP` → `GL_CLAMP_TO_EDGE` for character DLs; is the head tile getting the base tile vs an LOD tile (D107); try `Video.WrapFix=1` on a repro. | -| **D195** | Transparency / alpha-blended surfaces render wrong — reported on **Control** (Severnaya bunker basement — Natalya's console room; heavy on glass partitions and the translucent projected/holographic wall displays). Exact failure mode not yet pinned by the reporter (M-49 playtest): candidates are alpha-blended geometry drawing fully opaque, translucent panels drawing invisible, or draw-order/sort so translucent surfaces occlude what's behind. | **Observed, not investigated** (M-49, user bug report — needs a `GE_PCDUMP` capture + which surfaces). fast3d render-mode / combiner path. Same neighborhood as D161 (CI8 tile + `G_TT_NONE` → garbage), D172 (particle-sprite channel order), D176(b) (texture pitch shear) — but this is a *blend/alpha* defect, not a texture-decode one. Note D128 already touched Control's `specialportalarray` (portal struct stride) — check whether the affected surfaces are portal-adjacent. First checks: does `gfx_pc.cpp` honour `G_RM_*` XLU render modes + `G_AC_THRESHOLD`/`G_CC` alpha for these DLs; is `gDPSetRenderMode` `w1` being parsed (cf. findings §F "zbuf-clear w1 scribble"); is Z-write-vs-blend ordering right for XLU surfaces. Get the reporter to say: opaque-where-should-be-clear, invisible, or wrong edges? | -| **D252** | Particle effects cycle through a rainbow palette — bullet-impact sparks and lingering smoke/explosion residue drift through the full hue wheel over time (~10 shots to sweep it) instead of holding their intended grey/orange palette (M-124, user playtest). Full mechanism + suspect in findings §F **D252**: colour comes from 15 distinct `IMAGE_SMOKE_N`×`IMAGE_FIRE_N` (RGBA16) pairs the particles age through; prime suspect is `import_texture_rgba16` deriving width/height from the tile line size (32×7 instead of 16×14 for the fire tiles), scrambling each stage's sprite into a different wrong hue. | **Documented only — not fixed for v0.2.0-pre** (user instruction). Confirm with one dumped fire tile vs raw `IMAGE_FIRE_N` bytes, then derive width/height from the LOADBLOCK dtx/dlh. D172/M-82 lineage. | -| **D323** | Distant geometry on 15 levels samples driver-generated mips from the finest texture level; N64 samples Rare's hand-authored mip chain for explicit-LOD textures bound under `G_TL_LOD` (heaviest: Statue 378 binds, Aztec 806). Overwhelmingly CI8/IA8 — subtle fidelity gap, no reported artifact; the only RGBA5551 cut-outs are the D236 set (already handled). | **OPEN / parked** — mechanism confirmed, census in `tools_pc/bg_lod_audit.py`, fix path documented in findings §F **D323** (upload GL mip chains for TEXTLOD-on bindings). Not worth doing speculatively; revisit only if a playtest flags mushy distant detail. | -| **D335a** | **RESOLVED 2026-09-23.** Gun-barrel backdrop (Bond's spotlight) leaked into the pillarbox bars while sliding in from off-canvas. Fix: `menu_jump_constructor_handler` emits a full-canvas `gDPSetScissor` right after the CENTER wrap (only when wider than 4:3), which fast3d squeezes to the pillarbox, so off-canvas draws clip at the 4:3 edge as the N64 RDP clips at the screen edge. Verified with a 16:9 barrel capture. | -| **D335b** | Native widescreen: the F10 options overlay still stretches with the window (legible, pre-existing look). Pillarboxing it needs `optionsoverlay.c`'s click mapping (`gfx_get_ui_screen_rect`) remapped into the centred region in step. Polish, post-playtest. | -| **#105** | Dam: uncovered water-reflection pixels show the procedural cyan fill instead of the stage sky (residual after the water/wall-reflection work; visible boundary between reflected geometry and sky in the settled Enhanced/Remaster capture). | Backlogged 2026-09-28 from [issue #105](https://github.com/jkdansereau/goldeneye-pc-port/issues/105); post-v0.4.0 graphics queue. Note: the issue's "D329" cross-ref is the issue author's own numbering — our D329 is the anim-pinning fix. | -| **1P weapon viewmodel** | **RESOLVED (M-83, D215).** In-level first-person weapon models drew nothing. Root cause: `gunRenderFirstPersonGunModels` built its `ModelRenderData` via `*(ModelRenderData*)&D_80035CC0` — a reinterpret across two separately-declared adjacent globals that only lands right on the N64 linker + 4-byte pointers; on x86-64 `renderdata.flags` read 0, gating every weapon DL node off in `modelRenderNodeGundl`. All 3 ranked suspects in VIEWMODEL-RESEARCH.md were ruled out first by the `GE_DVM=1` probe. Fixed `#ifdef PORT` (explicit field init). `subdraw` 1→153 gfx cmds; owed one BUNKER1 eyeball. | - -## Resolved (for reference) - -- D70–D74: intro-logo pixels (Rareware logo renders correctly). -- D103: viewport / native-resolution half-frame. -- D105: room geometry Z-buffer never cleared (`G_CLEAR_DEPTH_EXT`). -- D106: sky-void through doorways (portal near-plane min>max). -- D107: blurry ceilings/wall-panels (LOD mip tile fabrication). -- D112: skeletal models were "3D lines" (`d43_emit.py put_f32` byte order). diff --git a/docs/dev/LEVEL-PLAYTEST.md b/docs/dev/LEVEL-PLAYTEST.md index 4753d680..e39464e3 100644 --- a/docs/dev/LEVEL-PLAYTEST.md +++ b/docs/dev/LEVEL-PLAYTEST.md @@ -1,5 +1,7 @@ # LEVEL-PLAYTEST — WS6 human completion-validation checklist +> **Historical record — do not act on status claims in this file.** This checklist predates the v0.4.0 input model (D332–D337) and the campaign sign-off; use it as a template only. Current open work and status: [`docs/ROADMAP.md`](../ROADMAP.md) (2026-09-29). + Run **after** `docs/dev/LEVEL-STATUS.md` is all-21 load+render PASS. This is the part that needs real input — a human plays each level start to finish and confirms it matches retail. Findings feed a WS5-style triage round diff --git a/docs/dev/LEVEL-STATUS.md b/docs/dev/LEVEL-STATUS.md index bb217a57..1cd1a87e 100644 --- a/docs/dev/LEVEL-STATUS.md +++ b/docs/dev/LEVEL-STATUS.md @@ -1,5 +1,7 @@ # LEVEL-STATUS — level load+render sweep (WS4) — 20 solo missions + ending sequence +> **Historical record — do not act on status claims in this file.** The per-level sweep below predates the 2026-09-28 full-campaign sign-off; its open items (re-playthrough gate, M-49 completability, Cuba crash) are resolved. Current open work and status: [`docs/ROADMAP.md`](../ROADMAP.md) (2026-09-29). + **Counting note:** the campaign is **20 playable missions** (`SP_LEVEL_MAX // 20` in `bondconstants.h`; `mission_folder_setup_entries` in `front.c` has exactly 20 entries). The sweep's 21st boot target, "Cuba" (`-level_54`, @@ -73,7 +75,7 @@ cause. Only remaining crash: 900. This is the **known D129 residual**: the bare `-level_54` boot reaches the cast/credits scroll referencing a text bank the real front-end flow would have loaded. Not a real-flow blocker; Cuba loads + - renders 300+ frames fine. Parked with D76 (`docs/dev/GRAPHICS-BACKLOG.md`). + renders 300+ frames fine. Parked with D76 (since fixed as D164). | Level | # | Status | Crash site | Class | |---|---|---|---|---| diff --git a/docs/dev/LINUX-PORT.md b/docs/dev/LINUX-PORT.md deleted file mode 100644 index 9586411f..00000000 --- a/docs/dev/LINUX-PORT.md +++ /dev/null @@ -1,91 +0,0 @@ -# Linux port — status & gap analysis - -Session M-38 (2026-09-02). Goal-2 on the roadmap (after the Windows alpha / -release artifact). This is a scoping + first-patch pass; **nothing here has run -on a Linux machine yet.** - -## Method - -Read `build-pc.sh`, `CMakeLists.txt`, `docs/building.md`, and grepped -`port/src`, `port/fast3d`, `port/include` for Windows-only headers / Win32 APIs -/ unguarded `_WIN32` branches. A local Qwen `triage` pass seeded the hunt; every -claim below was re-checked against the real files (the 27B was wrong on two of -three headline items — see "Ruled out"). - -## What is already Linux-ready (verified by reading) - -- **`port/include/platform.h`** — sets `PLATFORM_LINUX` / `PLATFORM_MACOS` / - `PLATFORM_WINDOWS` cleanly. -- **`CMakeLists.txt`** — `find_package(SDL2 REQUIRED)`, `find_package(ZLIB - REQUIRED)`, and `else()` branches already present for the non-Windows case: - `GL_LIBRARY = GL` (l.291), `EXTRA_LIBRARIES = stdc++ m dl pthread` (l.304), - `elseif(UNIX)` platform block (l.86). `WIN32_EXECUTABLE` and the MinGW zlib - fallback are correctly `if(WIN32)` / `if(MINGW)` guarded. -- **`build-pc.sh`** — already documents the Linux apt deps - (`cmake libsdl2-dev zlib1g-dev libgl1-mesa-dev`) and is a plain bash script, - no MSYS-isms in the executable body. -- **`port/src/system.c`** — `sysSleep()` has a `nanosleep()` `#else` branch - (l.76-82); `` is `PLATFORM_WINDOWS`-guarded. -- **`port/src/libultra.c`** — the OS-thread kernel has a real pthreads branch - next to the Win32 `_beginthreadex` one. -- **`port/src/crash.c`** — `MessageBoxW` / `SetThreadPriority` / `__debugbreak` - all `_WIN32`-guarded, with `PLATFORM_LINUX` counterparts. -- **`port/src/video.c`** — `GE_MKDIR` has both `_mkdir` and `mkdir(p,0777)`. -- No `__declspec`, `#pragma comment`, or hardcoded `\`-path / drive-letter - usage anywhere in `port/`. - -## Ruled out (Qwen triage false positives) - -- ~~`system.c:75` `Sleep()` has no POSIX branch~~ — it does (`nanosleep`). -- ~~CMake needs `else()` branches for zlib/pthread on Linux~~ — already there. - -## The one real code gap — FIXED this session (verification owed) - -**`port/src/romdata.c`** — the fixed-address cart map (`0x10000000`, so absolute -ROM-asset symbols are live host pointers) had a **Windows-only** implementation -(`VirtualAlloc`); the `#else` branch was a stub that logged "not implemented on -this platform" and fell straight to the heap-copy fallback. In heap-copy mode -anything that dereferences a cart address *directly* (not via -`romdataGetRom()` / the PI shims) reads wrong memory — so Linux would run, if at -all, in the same degraded mode Windows only hits when `VirtualAlloc` loses the -address race. - -**Change (M-38):** -- Factored the post-map work (memcpy + `pcmodelsLoadSidecars` + - `pccgLoadSidecars` + D55 RLE-header fixup) into `romdataFinishCartMap()`, - shared by both paths so they cannot drift. -- POSIX `#else` now does an anonymous `mmap((void*)CART_BASE, len, - PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED_NOREPLACE, -1, 0)`. - `MAP_FIXED_NOREPLACE` (Linux 4.17+) fails rather than clobbering an existing - mapping; where the macro is absent the plain hint is advisory and the - `at == CART_BASE` check catches a relocated result. On any failure it - `munmap`s and falls through to the identical heap-copy path as before. -- `romdataDestroy()` `munmap`s on POSIX (tracked length in `mappedLen`). - -**Owed verification (needs a Linux box):** -1. Compiles under GCC on Linux (`` resolves; no decomp-include-path - shadowing surprise). -2. `mmap` actually lands at `0x10000000` on a stock ASLR Linux — `0x10000000` - (256 MB) is low but usually free; if the kernel routinely refuses it, - consider linking the binary no-PIE or reserving the range via a linker - script so the loader keeps it clear. -3. `-level_09` boots crash-free with the mapped path taken (check the log says - "mapped at 0x10000000 (cart base)", not "using heap copy"). -4. Whether the heap-copy fallback is actually survivable on Linux if step 2 - fails — audit for direct `0x1xxxxxxx` derefs outside the PI shims. - -## Remaining Linux work after the romdata patch lands - -| # | Item | Size | Note | -|---|------|------|------| -| 1 | Run the Ubuntu **`validate`** CI job → upgrade it to a real **compile** job | S | `.github/workflows/ci.yml` already configures on Ubuntu; flipping it to `cmake --build` surfaces the real GCC-strictness gaps cheaply | -| 2 | First actual Linux runtime bring-up (`-level_09`) | M | expect a short tail of unguarded calls / struct-layout asserts GCC-on-Linux vs MinGW | -| 3 | `docs/building.md` — promote Linux from "untested" to "supported" once 1+2 pass | S | | -| 4 | SDL2 / GL context creation on X11 + Wayland | M | fast3d is GL 3.3 core; PD port runs on Linux so the path is known-good | -| 5 | Package: an AppImage or tarball equivalent of `bundle-win.sh` | M | reuse `prepare-assets.py` verbatim — it is already OS-agnostic | - -## Recommended next step - -Land the romdata patch behind CI (item 1) — turn the Ubuntu `validate` job into -a compile job in the same PR so the "does it build on Linux" question is -answered by the bots, then do the runtime bring-up on a real machine. diff --git a/docs/dev/M-35-PLAYTEST-CHECKLIST.md b/docs/dev/M-35-PLAYTEST-CHECKLIST.md deleted file mode 100644 index 959eb150..00000000 --- a/docs/dev/M-35-PLAYTEST-CHECKLIST.md +++ /dev/null @@ -1,89 +0,0 @@ -# M-35 QoL playtest checklist - -Everything on this branch is config-gated to prior behaviour, so the -**first** thing to confirm is that nothing changed with a default -`ge007.ini`. Then flip the new keys and check the feel. - -Edit `data/ge007.ini` while the game is closed. - ---- - -## 0. Regression (default config) — do first - -- [ ] Delete `data/ge007.ini`, launch, quit. It rewrites with new keys: - `[Input] MouseCaptureMode = 0`, `[Game] ScreenShakeIntensity = 1`. - Existing keys keep their values. -- [ ] Play a level normally (mouse grabbed on focus, as before). Mouse look, - aim (RMB), menus — all exactly as the last build. **No behaviour change - expected at defaults.** -- [ ] Alt-tab away and back — cursor frees / re-grabs (unchanged). - -## 1. Click-to-lock capture — `Input.MouseCaptureMode = 1` - -- [ ] Launch. At the main menu the **OS cursor is visible and free**; moving - the mouse moves the game crosshair 1:1 (see item 3). -- [ ] Click in the window during a level → cursor hides, mouse-look engages. -- [ ] Press **ESC** during a level → cursor reappears, mouse-look stops, - you can move the pointer off the window. (ESC does *not* quit.) -- [ ] Click back in → re-locks. First click only re-locks — it must **not** - fire the weapon. -- [ ] Open the pause/watch (Start), then a front-end menu → cursor frees - automatically. Return to the stage → re-locks without a click. -- [ ] Alt-tab out mid-level → cursor frees; alt-tab back → stays free until - you click (Quake behaviour). -- **Correct feel:** exactly like Quake/Nightdive remasters — click to play, - ESC to get your cursor back. -- **Tune:** none — it's a mode toggle. - -## 2. Aim sensitivity — B3 - -- [ ] With `Input.MouseCaptureMode` either value, hold RMB (aim mode) and - make small aiming corrections. Default is now `MouseAimSpeed = 16` - (was 25). -- **Correct feel:** you can settle the sight on a distant guard's head - without overshooting; a fast flick still crosses the screen. -- **Tune:** `Input.MouseAimSpeed` (1–100+). Up = faster. If hipfire turning - now feels faster than aiming, raise `MouseAimSpeed` or lower - `Input.MouseTurnSpeed` (default 100). `Input.AimBand` (5–40, default 20) - widens the usable stick range past the aim gate — raise it if aim feels - capped/notchy at the extremes. -- [ ] Check the D166 hipfire pitch (mouse up/down without RMB): still - proportional taps, consistent with the yaw. Tune - `Input.HipfirePitchSpeed` (default 100). - -## 3. Menu pointer 1:1 — needs `Input.MouseCaptureMode = 1` - -- [ ] On the mission-select grid, move the OS cursor to each of the four - **corner** level tiles — the game crosshair must reach all of them - (this is the D169 case; absolute mapping should make it exact now). -- [ ] File-select, mode-select, difficulty-select, watch menu — crosshair - tracks the OS cursor with no drift, no lag, no acceleration. -- [ ] Keyboard nav (arrows / stick) still works on every screen. -- **Correct feel:** the crosshair sits exactly under the OS pointer, like a - normal PC menu. -- **Tune:** `Input.MenuPointerSpeed` (default 100) still scales the legacy - relative path; with capture mode the absolute path ignores it. If tracking - feels off, check the window isn't a weird aspect ratio (the map assumes the - render fills the window). -- **Note:** in legacy mode (`MouseCaptureMode = 0`) the menu still uses the - D165/D169 relative estimator — 1:1 menu tracking requires capture mode. - -## 4. Screen shake — `Game.ScreenShakeIntensity` - -- [ ] Default `1` — explosions shake the screen exactly as before. -- [ ] Set `0` — no shake on explosions / big hits. -- [ ] Set `2`–`3` — exaggerated. -- **Correct:** `1` is indistinguishable from the current build. - ---- - -## Single most important thing to test first - -**Item 0** — confirm a default `ge007.ini` produces a build that plays -identically to `def0e0ac`. If that holds, everything else is opt-in polish. - -## Known not-done (parked, see OPTIONS-MENU-PLAN.md) - -- No in-game options **menu** yet — only the config file + the expanded - `config.c` API and the `Game.ScreenShakeIntensity` hook. The menu is a - port-layer overlay design, documented, not built. diff --git a/docs/dev/M-35-QOL-REVIEW.md b/docs/dev/M-35-QOL-REVIEW.md deleted file mode 100644 index 9fca6e24..00000000 --- a/docs/dev/M-35-QOL-REVIEW.md +++ /dev/null @@ -1,132 +0,0 @@ -# M-35 — Native-PC input + options-menu QoL review - -Branch: `qol/native-pc-input-menu` (cut from `def0e0ac`). Port-layer only -except one documented route-(b) `#ifdef PORT` line in `src/fr.c` (D181). -Every new behaviour is config-gated with the default reproducing prior -behaviour; `tools_pc/golden/` headless captures are unaffected at defaults -(no mouse in a headless run; `Game.ScreenShakeIntensity` default 1.0 is a -no-op multiply). - -Findings: **D180** (input pass), **D181** (screen-shake hook). See -`docs/dev/findings.md` §F. - ---- - -## WI-1 — In-game mouse: Quake-style click-to-lock - -**Status: landed, feel-check owed.** - -### What changed - -| File:area | Change | -|---|---| -| `port/src/input.c` — new statics `mouseCaptureMode` / `captureArmed` / `windowFocused` | Capture-mode state. `mouseCaptureMode` registered as `Input.MouseCaptureMode` (0/1, **default 0**). | -| `port/src/input.c` `inputInit` | In capture mode, start **un**grabbed (`SDL_SetRelativeMouseMode(FALSE)`), wait for a click. Legacy mode unchanged (grab on start). | -| `port/src/input.c` `applyGrab()` / `reconcileGrab(menuMode)` | `applyGrab` is the old `inputSetMouseGrab` body (SDL relative-mode toggle + delta drain). `reconcileGrab` computes the wanted grab state: legacy → `windowFocused`; capture → `captureArmed && windowFocused && !menuMode`. Called once per controller-0 poll and from every notify hook. | -| `port/src/input.c` `inputSetMouseGrab(on)` | Now just records `windowFocused` and calls `reconcileGrab` — so focus loss/gain works in both modes without special-casing. | -| `port/src/input.c` `inputNotifyClick()` | Capture mode + focused → `captureArmed = 1` + regrab. No-op otherwise (game sees the click). | -| `port/src/input.c` `inputReleaseCapture()` | Capture mode + grabbed → `captureArmed = 0`, release, return 1 (caller swallows the key). Returns 0 otherwise (ESC falls through to the N64 B button, D145). | -| `port/src/input.c` `inputComputePad` idx 0 | Calls `reconcileGrab(menuMode)`; when capture-mode + free + in a stage, zeroes the local `mb` so mouse buttons never reach the game (no phantom fire from the re-lock click). | -| `port/src/video.c` event pump | `SDL_MOUSEBUTTONDOWN` → `inputNotifyClick()`. `SDLK_ESCAPE` keydown → `inputReleaseCapture()` (before the existing fall-through). | -| `port/include/input.h` | Prototypes for the three new hooks. | - -### Config keys - -| Key | Range | Default | Effect | -|---|---|---|---| -| `Input.MouseCaptureMode` | 0–1 | **0** | 0 = legacy (cursor grabbed whenever the window is focused). 1 = click-to-lock: free cursor until you click the window; ESC / focus-loss / opening a menu frees it; entering a stage while "armed" re-locks. | -| `Input.MouseAimSpeed` | 1–500 | **16** (was 25) | Aim-mode (RMB / LShift) sensitivity %. B3: still overshot at 25. | - -Controller input is completely independent of all of the above — the pad -branch in `inputComputePad` is untouched, and `reconcileGrab` only ever -toggles `SDL_SetRelativeMouseMode`. - -### D166 / hipfire-pitch interaction - -The D166 hipfire pitch pulse is unchanged. It only runs when the cursor is -grabbed (mouse deltas are zero while free), so click-to-lock does not -perturb it. The M-24 aim-band (`61 + gain` into the `60 + aimBand` ceiling) -is unchanged; only the `mouseAimSpeed` scalar feeding `gx`/`gy` moved. - -### Owed feel-checks (playtest checklist items 1–4) - -Headless runs cannot move the mouse, so the capture ergonomics, the new -`MouseAimSpeed = 16` default, and the D166 pulse consistency under the new -model are all inferred, not observed. - ---- - -## WI-2 — Menu pointer: absolute cursor tracking - -**Status: landed for capture mode, feel-check owed. Route (a), port-only.** - -### What changed - -`port/src/input.c`, the `menuMode` pointer branch (the D165/D169 -P-controller). Added an **absolute-position** target source: when -`mouseCaptureMode && !mouseGrabbed` (i.e. a real free OS cursor over a -front-end screen), read `SDL_GetMouseState` + `SDL_GetWindowSize` of the -mouse-focus window, normalise to `[0,1]²`, and map straight onto the live -virtual front-end rect (`loH..hiH × loV..hiV`, already derived from -`getPlayer_c_screen*` for D169). That becomes `menuTgt{H,V}` directly -(no accumulation, no drift). The P-controller/estimator below is unchanged -and simply chases the target, so the game's `cursor_h_pos/v_pos` lands -exactly where the OS pointer is. - -The relative-delta path (legacy `MenuPointerMode`, or capture mode while -grabbed) is **unchanged** — `haveAbs` gates only the new assignment. - -### Why this is enough for 1:1 - -The D169 fix already made the clamp rect track the real 440×330 field. The -only thing missing for true 1:1 was a non-drifting target: a free OS cursor -*is* an absolute device, so mapping its window fraction onto the rect is -exact. No route-(b) hook needed. - -### Keyboard nav parity - -Untouched — the menu branch only ever adds to `sx/sy`; D-pad / stick keys -still feed the same axes. The D118d `front.c` latent sibling was **not** -touched (no evidence the absolute path exposes it; the stick values emitted -are small P-controller corrections, never a slammed ±80). - -### Caveat - -Absolute tracking is only wired for `MouseCaptureMode = 1`. In legacy mode -(default) the menu still uses the D165/D169 relative estimator. If -click-to-lock becomes the default later, WI-2 is 1:1 for free; until then -menu 1:1 requires `Input.MouseCaptureMode = 1`. - ---- - -## WI-3 — In-game video/options menu - -**Status: config-system foundation landed; menu surface = design doc only.** -See `docs/dev/OPTIONS-MENU-PLAN.md`. - -### Landed - -- `configRegisterFloat` / `configRegisterUInt` + clamps (`port/src/config.c`, - `port/include/config.h`). `configSave` rewritten to emit all four option - kinds section-grouped in one pass. -- `Game.ScreenShakeIntensity` (0.0–10.0, default 1.0) — the first - route-(b) hook, D181, proving the pattern the menu will lean on. - -### Not landed (budget) — precise resume point in the design doc - -- The menu tables / handlers themselves. GE's menu system (`front.c` / - `initmenus.c` / `mainmenu.c`) differs enough from PD's `optionsmenu.c` - that it is an adaptation, not a copy. Design doc covers the entry point, - the handler shape, and the minimum option set. - ---- - -## Verification evidence - -| Check | Result | -|---|---| -| `./build-pc.sh ntsc-final` | green, 241/241 | -| `-level_09` framediff (200/320/440) | 3/3 within threshold; nonclear Δ 0.00–0.01pp (91.6%); phash 0/1/5 = documented D117 intro-pan noise | -| `GE_STARTMENU=7` boot | crash-free, no `ge007.crash.log` | -| golden dumps at defaults | unaffected (no headless mouse; shake scale 1.0 = no-op) | -| linkcheck | see PR body | diff --git a/docs/dev/M-36-TRIAGE.md b/docs/dev/M-36-TRIAGE.md deleted file mode 100644 index 977d02fb..00000000 --- a/docs/dev/M-36-TRIAGE.md +++ /dev/null @@ -1,94 +0,0 @@ -# M-36 — graphics / QA bug triage sweep - -Branch `qol/native-pc-input-menu` (local). Goal: sort the open -`GRAPHICS-BACKLOG.md` + QA-report bugs into **root-cause families** so a -fix hits several at once, and correct one mis-attribution. - -Method: headless `GE_PCDUMP` captures (`-level_36` Surface, `GE_STARTMENU=10` -briefing) + static read of the implicated port code. Frames in -`scratchpad/` this session; not committed (auto window size = 1957×1468, -can't framediff — visual only). - ---- - -## Headline findings - -### 1. The "comb interlacing" is NOT the D159 family - -`GRAPHICS-BACKLOG.md` attributes D176(b) and D182(2) to `texSwapAltRowBytes` -"recurring on re-import". **That function is a full `#ifdef PORT` no-op** — -`src/game/image.c:2199` `return;` before any work. It cannot be the cause of -anything. The mis-attribution is now corrected in the backlog. - -The real defect: `port/fast3d/gfx_pc.cpp` `import_texture_*` assume -`full_image_line_size_bytes == line_size_bytes` (explicit `SUPPORT_CHECK` -on rgba32/ia4/ia8/ia16/…; the rgba16 one is commented out with -*"this trips in some places with a garbage size in -full_image_line_size_bytes"*, `gfx_pc.cpp:658`). When a tile's `line` -(SETTILE pitch) differs from the source image row width — non-power-of-two -textures, or a sub-rect tile load — the importer does a flat linear read and -every row walks off by a few bytes → **diagonal shear that reads as grey -static**. Matches the Surface cliff/tree walls (`-level_36` frame 300–900: -structured rock texture tiling but each tile sheared to noise) and the -file-select spiral after re-entry. - -Open question: why does D182 only scramble **after** navigating away and -back? Either the first import happens to have `line == width` and the -re-import doesn't, or the tile descriptor is stale on the second visit. - -### 2. Briefing objectives are BLANK, not wrong - -`GE_STARTMENU=10` briefing (Dam): header renders perfectly ("OHMSS / Agent: -James Bond / Mission 1: Arkangelsk / Part i: Dam / PRIMARY OBJECTIVES:"). -Under it: just the bullet letter **"a."** with **no objective text**, and no -b/c/d lines. So D178 is the **D143/D151 blank-text family** (`langGet()` -returns NULL for the objective string slot → text silently blank), *not* a -propDef walk-desync or difficulty-decode problem. Fix = find which lang bank -the front-end briefing flow fails to load/map. - -Caveat: this repro used the synthetic `GE_STARTMENU` boot (skips real -save/unlock state). The user's D178 report is from a live playthrough, and -D143 independently documents blank briefing text, so it is a real defect — -but re-confirm the exact "a." + blank shape in a real briefing. - ---- - -## Family table - -| Family | Bugs | Root cause (hypothesis) | Files | ROI | -|---|---|---|---|---| -| **A — fast3d texture line/pitch shear** | D176(b) Surface walls; D182(2) file-select bg | **HYPOTHESIS DISPROVEN (D183, `4b71435d`).** 0/166 texture loads on `-level_36` are strided; the wall texture is a 32×32 IA8 whose ROM bytes are genuinely a noise field, decoded correctly. A golden-safe de-stride + `GE_TEXRAW`/`GE_DTEX` diagnostics landed anyway (latent hardening). **D176(b)/D182(2) still OPEN** — next: ROM ground-truth decode of the wall texnum (`TEXTURE-GLITCH-ANALYSIS.md` §7) → likely a tiling-density / UV-scale / `shifts`/`shiftt`-LOD issue (RC3/D167 family) or a wrong-texture bind, not a decode bug. | `port/fast3d/gfx_pc.cpp` | was HIGH, **now needs re-scoping** | -| **B — front-end string bank not loaded** | D178 briefing objectives blank; D143 briefing text blank | **FIXED (D178, `cd1ed574`).** Not a bank-load problem — the `Ubrief*Z` segment is a raw 48-byte BE ROM image loaded with no converter; all 24 `u16` (string ids + difficulty gates) read byte-swapped on LE → `langGet()` NULL → blank, and `>=` difficulty gate always-false → objectives vanish. `romdataFixupBriefing()` swaps in place. Verified: all 4 Dam objectives render at 00 Agent with correct gating. **D143 was a symptom of this** — annotate it superseded. D151 (watch objective text) still owed a re-verify. | `port/src/romdata.c`, `src/game/front.c` (`#ifdef PORT`) | **DONE** | -| **C — D75 front-end / cutscene 3D models** | D75 Nintendo logo; D148 Dam rappel cutscene; D149 MISSION COMPLETE / mode-select models; D182(1) file-select Bond image "renders once"; D173 3rd-person Bond floats; 1P weapon viewmodel absent | mixed: compiled sub-DL corruption (D144/D146), model transform/geometry, `render_pos` arena lifetime, constructor-runs-once | `port/fast3d/gfx_pc.cpp`, `title.c`, `bondview*.c`, model path | **LOW** per fix, deep. **DEFER** (agreed COA). | -| **D — blood / particle** | D172 spray magenta/cyan; D174 no blood decals | D172: CC-mode / texture-format decode in the fast3d sprite path. D174: likely D120 `PointUsage[]` — **only takes effect after a full sidecar regen** (`d43 && d69 && d88 --regen`); verify that first. | `blood_animation.c`, fast3d sprite path; `tools_pc/d43_emit.py` | **MEDIUM**. D174 may already be fixed — regen + BUNKER1 firefight to confirm before touching code. | -| **E — transient hang / anim speed / clip** | D175 door-open stutter (Runway/Surface); D170 NPC flee runs not walks; D171 Silo grey-triangle sweep | D175: matches D155/D156/D134/D147 known classes (benign texture-import spike most likely). D170: D155/D156 wall-clock `deltaFrames` anim speed-up, or just scripted. D171: near-plane clip fan (D106 family). | frametiming clamp, `chrTick`/AI, portal near-plane | **LOW–MED** — needs interactive repro + N64 reference footage. **DEFER**. | -| **F — misc cosmetic** | D74 dead wrap-block (latent, harmless); D76 disclaimer partial (D164 fix owed an eyeball); D77 audio (Phase 3); **D176(a) black sky** | D176(a): sky DL not emitting / wrong transform — **separate from D176(b)**, own investigation (could be C-family or a sky-pass issue). | various | **LOW**, opportunistic. | - ---- - -## Results (2 subagents, integrated + verified on branch HEAD) - -- **Family B → FIXED** (`cd1ed574`). Byte-swapped raw briefing segment. Also - explains D143. High confidence. -- **Family A → hypothesis disproven** (`4b71435d`, D183). D176(b) is not a - texture decode bug. Golden-safe de-stride + diagnostics shipped for the - latent case. **D176(b) / D182(2) remain open and need re-scoping** — start - from the ROM ground-truth decode of the wall texnum; suspect - tiling-density / UV / per-LOD `shifts`/`shiftt` (RC3/D167 family) or a - wrong-texture bind. - -Harness note: both agent worktrees were created from `def0e0ac` (the M-35 -branch point, ~5 commits stale — the M-31 stale-worktree bug again). No harm -this round: the integrator re-applied both patches onto branch HEAD and -verified there. Check `git worktree list` HEADs on the next burst. - -## Next - -1. **D176(b) re-scope** — offline decode the Surface wall texnum from ROM - (`TEXTURE-GLITCH-ANALYSIS.md` §7). Structured rock in ROM → wrong bind or - UV/tiling; noise in ROM → not our bug (or a shift-LOD density issue). -2. **D176(a)** black sky — untouched, separate investigation. -3. **D182** — re-test the file-select round-trip now that `GE_TEXPITCH` / - `GE_TEXRAW` exist; the "renders once" half (D182-1) is Family C. -4. Reassess C/D/E with the user. D174 (blood) is the cheapest — just needs a - sidecar regen + BUNKER1 firefight to confirm the D120 fix. diff --git a/docs/dev/M7-ALLUNLOCKED-PLAN.md b/docs/dev/M7-ALLUNLOCKED-PLAN.md deleted file mode 100644 index 3c78d17e..00000000 --- a/docs/dev/M7-ALLUNLOCKED-PLAN.md +++ /dev/null @@ -1,45 +0,0 @@ -# M7 — Reversible AllUnlocked: investigation and fix plan - -**v0.4.0 release decision (2026-09-28, user):** ship AllUnlocked as a -**loudly-documented, EXPERIMENTAL, irreversible** option for this release rather -than hiding/disabling it or landing a safe architecture. The toggle stays (default -OFF); the in-game row is now labelled **"All unlocked (EXPERIMENTAL)"** with the ON -value displayed as **"ON - UNSAFE"** in both the F10 overlay and the front PC -Options screen, and the README Known-issues bullet already tells users to back up -`data/ge007.eep` before enabling it. A proper fix (Candidate A port-only shadow or -Candidate B non-mutating virtual query) remains the v0.5.0 work item. - -Status: **investigation, NO FIX APPROVED OR LANDED**. Root cause/repro: `docs/dev/findings.md` D387; original feature: D257, fresh-save repair: D281, old-CRC migration: D297. This plan supersedes the M7 "no-code proof" in `docs/dev/notes/MODERN-OPTIONS-PLAN.md`. Follow `AGENTS.md` rule 2 and `docs/dev-process.md` §7 before any `src/game` edit. - -## User-visible contract to settle - -Recommended: OFF (after restart, as presently documented) must display only genuinely earned mission progress/cheats; ON may *temporarily* expose every level, difficulty, 007 mode and cheat, without modifying durable progress. Achievements **earned while ON without an active cheat** should survive OFF, including difficulty inheritance, timed cheats and profile operations. Enabling/disabling must never silently delete existing genuine save records. Do not auto-repair an EEPROM already containing baked fake unlocks: the bytes lack provenance. The user has not explicitly signed off on a game-code alternative. - -## Confirmed facts (source + isolated data) - -- `port/src/libultra.c:geEepromRW` first migrates legacy CRCs on the five-slot block-4 read, then `geEepromPatchAllCheats` fills unlocked-cheat bits and every zero completion time with 0x3FF and re-CRCs a **temporary copy**. `fileValidateSaves` (`src/game/file2.c:527`) accepts that copy into `saves[]`. `fileWriteSave` (`file2.c:86`) writes the entire 96-byte live slot, not only the setting/progress that changed. -- Private data under `AppData/Local/Temp/ge-m7-20260927/`: the older `data/ge007 copy.eep` has two genuine times and only cheat `04 00 00` in active folder 0 (slot 4). ON or OFF boot without a save yields identical EEPROM bytes after the same legacy CRC migration. ON plus existing `GE_WSPROBE_FRONT=600` performs an ordinary Music-volume save to slot 4: cheats become `ff ff 0f`, 58 empty times become 0x3FF, two real times remain. OFF + restart validates the written CRC and retains all fakes. Full repro/logs: D387. Current `data/ge007.ini` and `data/ge007.eep` were never opened for write; copies and SHA-256s in that private test directory. -- `main.c` seeds debug unlock flags only at boot; an in-session OFF toggle leaves those RAM flags set until restart. `Game.AllUnlocked` defaults OFF; the relevant UI currently gives no explicit restart warning. -- More than a write-mask problem: a successful mission runs `end_of_mission_briefing` (`src/game/file.c:41–68`) → `fileUnlockStageInFolderAtDifficulty` (`file2.c:828`) → `fileOverwriteSaveSlotWithNewSave` (`file2.c:794`), **rotating the profile to a free EEPROM slot** and freeing the old slot. `fileCheckSaveStageDifficultyTime` only updates if the real time beats the synthetic 0x3FF. Genuine lower-difficulty completions and long (>1023-second) runs can also have legitimate 0x3FF times; value alone is not provenance. Fake cheat bits cause `fileGetIsCheatUnlocked` to return true, so `end_of_mission_briefing` skips a genuinely earned timed-cheat award. A port-only sanitizer keyed by physical slot or by `time==0x3FF` is incorrect. -- The current user EEPROM already has two nonfree slots with 60/60 max times and all cheat bits, consistent with an earlier write-back. Two separate older EEPROM backups exist in `data/`, but replacing or auto-merging them would risk later real progress; do not touch or auto-repair originals. - -## Architecture decision before code - -**Candidate A — port-only EEPROM shadow/merge:** keep authoritative *raw* progress per logical profile outside patched `saves[]` and translate every write to durable real-only state. Must cover direct settings writes, profile rotation, copy/delete/reset, earned times (including 0x3FF), timed cheat grants currently suppressed by fake reads, five-slot CRCs, and old-CRC migration. Physical-slot-only interception demonstrably fails. A read/write-only adapter may have insufficient information to recognize some genuine completions or cheat awards; prove a full event/provenance model in an isolated prototype before promising this path. No new sidecar that can be lost/desynced without a recovery design. - -**Candidate B — non-mutating virtual query:** retire `geEepromPatchAllCheats`, keep raw `saves[]` and EEPROM intact, and make the *opt-in* UI/gameplay availability queries report AllUnlocked without changing saved bits/times. Likely small PORT-gated touch points: `fileGetIsCheatUnlocked` (`src/game/file2.c:391`, existing D225 developer-only query override), `fileIsStageUnlockedAtDifficulty` (`file2.c:681`; cheat-enabled mission selection requires `STAGESTATUS_COMPLETED`, not merely the debug-flag `UNLOCKED`), and the raw earned-cheat check in `end_of_mission_briefing` (`src/game/file.c:41–68`), which must still record real cheat awards while virtual availability is ON. Audit every caller and the Rare/PD analogue first. A live-only toggle may require restoring debug flags safely; simplest contract is **takes effect on restart** and an explicit UI label. This alters opt-in `src/game` behavior and is **not** the ABI/layout exception: no implementation or implicit sign-off. If port-only is ruled out, prepare the exact file/line, reference, reason, scope and precedent required by `docs/dev-process.md` §7; obtain explicit user approval and document the grant in a new RULE-2-SIGNOFF finding before edits. - -**Release fallback:** if neither safe architecture is ready, do not present the current toggle as a reversible enhancement. Get an explicit release decision on hiding/disabling it vs shipping a loudly irreversible option; default-off alone does not protect users who toggle it on. Keep existing saves intact. - -## Test matrix (private files ONLY) - -1. Capture baseline hashes of copied current ini/EEPROM; separate valid older-progress, fully fresh zero EEPROM, already-contaminated, legacy-CRC, and mixed/multiple-folder fixtures. Do not copy old saves back into `data/`. -2. No-write ON boot then OFF restart: raw five slots unchanged except *explicit* D297 migration; correct UI availability in both states. -3. ON settings writes via `GE_WSPROBE_FRONT` (normal slot), in-stage F10 watch writes, and save-file chooser/reset: only real settings/intentional reset persist; CRC valid; no synthetic completions/cheats in raw file. -4. Real mission completion at agent and higher difficulty (including inherited lower difficulty), fast timed-cheat award, slow/capped 0x3FF completion, and cheat-enabled mission selection. Verify actual new earned records after OFF restart, no artificial ones. Build an isolated gameplay harness if manual replay is impractical; do not use a settings-write test as a substitute for mission completion. -5. Save-slot rotation (`fileOverwriteSaveSlotWithNewSave`), profile copy/delete, all four folders plus free slot, and reopen across process restart. Confirm all five CRCs via `GE_SAVELOG=1` and raw bit-level diff; compare copied baseline profile identity rather than just physical slot index. -6. Default-OFF NTSC/PAL/JP builds and post-landing 21-level default-render comparison; config/EEPROM originals hash-verified **after** all tests. Human test of ON/OFF UI text and behavior. Existing contaminated saves need an explicit *separate*, opt-in recovery plan; never silently rewrite them. - -## Other wave item - -M5 remains under user review: FPS cap and MSAA are independently exposed already; dropping the two-setting Low-end preset is a separate decision, not a dependency of this M7 investigation. diff --git a/docs/dev/TEXTURE-GLITCH-ANALYSIS.md b/docs/dev/TEXTURE-GLITCH-ANALYSIS.md index 82ae0677..56dfd5b7 100644 --- a/docs/dev/TEXTURE-GLITCH-ANALYSIS.md +++ b/docs/dev/TEXTURE-GLITCH-ANALYSIS.md @@ -1,5 +1,7 @@ # Texture Glitch Analysis — PC Port +> **Historical record — do not act on status claims in this file.** Only the status table is current; the diagnosis from §1 onward is from 2026-08-29 and includes retracted fixes (RC4) — never apply them. Current open work and status: [`docs/ROADMAP.md`](../ROADMAP.md) (2026-09-29). + **Original diagnosis:** 2026-08-29 (read-only) · **Status table refreshed:** 2026-08-31 (M-31) Companion artifacts: `` (outside repo). diff --git a/docs/dev/V0.4.0-PLAN.md b/docs/dev/V0.4.0-PLAN.md deleted file mode 100644 index 135ac554..00000000 --- a/docs/dev/V0.4.0-PLAN.md +++ /dev/null @@ -1,515 +0,0 @@ -# v0.4.0 Plan — local working document (NOT pushed; update as items move) - -**2026-09-27 user release decision:** defer M7 / AllUnlocked save-safety work -for v0.4.0 (D387); do not ask for a live-save test or treat the feature as -safe. Before shipping, hide/disable the unsafe toggle or explicitly agree -on a release limitation — default OFF alone does not prevent a user from -turning it ON. The user will do a full campaign playtest *instead of* the -21-level automated sweep; record the campaign result as the release gate, -not as a sweep PASS. **Playtest interruption D393/D394:** physical-pad -A use / X reload / Y weapon cycle was user-tested. After the user supplied -the StrategyWiki Xbox 1.1 Jinx mapping, D394 moved crouch to either stick -click, LB to aim, B to a port-side owned-gadget cycle, and retired LB/RB -weapon cycling. Build linked; user physically confirmed the new button roles work, including -stick-click crouch, LB aim, RB no weapon cycling and B gadget cycling (the -N64 port has no separate gadget HUD icon). **D395:** both controller-accessible -binding menus now refuse pad-initiated keyboard/mouse capture and allow B to -cancel an active modal, pending pad-only retest; this is not gamepad rebinding. -**D396:** F10 B now backs/closes, A selects/toggles without changing sliders; -left/right adjusts sliders. NTSC linked; physical pad retest owed. Inspect B's item category/order during campaign play if multiple mission -items are owned: the current ID-range filter includes some mission objects. -RB's remastered-graphics toggle cannot be replicated with N64 assets, and -Back remains the PC options shortcut. - -Created 2026-09-21 from a fresh triage of `findings-index.csv`, -`GRAPHICS-BACKLOG.md`, `docs/BACKLOG.md`, `ROADMAP-1.0.md` (M-139 phase order), -`QOL-INVENTORY.md`, and the 2026-09-20 HANDOFF. - -**Release goal (user-set):** fix everything that still makes the port not 1:1 -with the N64 original; *then* improvements outside the OG game, plus stability. -Sequencing follows M-139's R → Q → F order, split into four phases below. - -## Baseline - -- **v0.3.0 is shipped** (tag on origin at `4827bc86`); `main` is 7 docs-only - commits ahead. Branch `release/v0.4.0` off `main` when Phase 1 starts. -- All 20 solo missions (plus the ending-credits sequence) load/render/crash-free; campaign completable end-to-end - (Agent, playtested). Steady 60 fps. Audio done. Saves done. -- Known-issue list = README "Known issues" section (D303, D245, D308/ - D306, D307, D311, D318 disclosure, widescreen-stretch, D249, - D240/D241, D257/D259/D281 AllUnlocked caveat). ~~D252~~ retired (M-188), - ~~D75~~ fixed (M-197, user-confirmed), ~~D236~~ fixed (M-198, pass 26 — - awaiting play-test sign-off). - -## Triage of every open item (as of this triage) - -### Bucket A — Fidelity (port diverges from N64 behavior/appearance/sound) - -| ID | Item | State / notes | -|----|------|---------------| -| D320 | 17 aim-hold AI lists in 8 levels carry the as-authored D318 softlock race; Facility `ai_19`, Control `ai_9`, Depot `ai_12` high/med-high | Static sweep done; **live probe-verification owed** (GE_D318T-style timeline on the 3 lists). If one freezes: generalize `d318watchdog.c` (parameterize list/offset/chr/entityid), not per-list copies. N64 softlocks permanently — watchdog stays a port-side recovery, disclosed like D318. | -| D252 | Rainbow particles still intermittent on both platforms after the vertex-buffer fix | Second cause unrooted; prime suspect `import_texture_rgba16` fire-tile width/height from tile line size (32×7 vs 16×14). README known issue. | -| D245 | Water seam between two patterns, moves with view (REOPENED) | M-142 probe: tc-shift k flips during ordinary rotation; opt-in `GE_D245_FIXEDSHIFT` candidate build-verified, needs A/B vs N64 reference before making it default. | -| D303 | Wrong extra long muzzle flash (M16 + others) | **Investigated — pipeline + data ruled out.** Flash path (`gunUpdateAndFire` → `rwmtx[1]/[2]`, `dorottex` DLPRIMARY arms) is byte-identical game code; M16's `MuzzleFlashExtension`=6.0 is the legitimate max stat; Gm16Z flash-node data byte-faithful through d43. Headless runs can't fire weapons (guards don't shoot an idle player), so a TEMP `GE_D303=1` probe in `gfx_pc.cpp` logs every very-tall-thin emitted triangle with full RDP state — **blocked on a live firing capture** (playtest brief item 17). | -| D306/D308 | Z-fighting: Dam intro/truck wheels + general/broader instances | **Investigated — real fighting confirmed, port-vs-faithful open.** Depth precision is equivalent to N64 (24-bit both sides; GE's RSP writes full 16.16 post-transform coords, not s16), so level-authored coincident geometry is the prime suspect and likely fights on N64 too. TEMP `GE_ZF=1` flicker probe in `gfx_pc.cpp` (recurrence-filtered; `GE_ZFBOX` region filter) shows sustained oscillation (counts ~1000) in the Dam-intro truck region. Closing it as faithful needs an N64 reference of the same spot; a PC-only mitigation (float depth) would diverge from N64 and needs a rule-2-style decision. | -| D307 | In-level security camera props face backwards on some levels (Bunker) | **Investigated 2026-09-21 — no port-side bug found.** Full orientation path traced (`setupCctv` → per-frame `RotY(unkC8)×unk84`); every input verified N64-faithful against the ROM (PcctvZ switch data, boundpads incl. the legitimate load-time room-scale ×1.8542, record config words); all math helpers are byte-identical `src/game` code. Remaining suspect if it genuinely differs from N64: fast3d handling of the non-orthonormal lens matrix. **Blocked on user repro detail** (which camera/level + screenshot + ideally N64 compare). `GE_D307=1` diagnostic kept in-tree. | -| D236 | Surface 1 billboard trees render as a solid wall of texture | **FIXED (M-198, pass 26, `425237f9`):** root cause was a texture-BINDING bug — `TEXTURETYPE_DETAIL` cards drawn from the detail tile (opaque IA8 noise) instead of the base image; `gfx_lod_tile_offset` now samples tile 1 when the pair differs (`Video.DetailBaseTile`, default on). Live-verified Surface 1/2 + Depot; other levels provably untouched. Awaiting user play-test sign-off; probes stripped (`0503d335`). | -| D235 | Wrong textures after death/restart (level reload), multiple levels | **Headless verification 2026-09-23 (`2a6bc2bb`)**: fix clean on reload; bug NOT reproduced even with the fix off. Needs the real die → mission select → restart A/B (`GE_D235_OFF=1`). | -| D288 | Silo intro: huge flat untextured triangle sweeps the frame (frames ~562–741 of bare `-level_20`) | **Does NOT reproduce on the current build (2026-09-23, `30cf76b8`)**; D271 A/B ruled out as the fixer. Close if the Silo playtest does not see it. | -| D294 | Statue: CCCP monument ground renders solid black, self-recovers in 10–30 s | **FIXED pending user playtest** (2026-09-22). Note: its "3× pool corrupts anim" symptom was D336, so the 2× cap can be revisited. | -| D291 | Dam: texture glitches when the truck drives past | Not investigated. | -| D249 | Distant geometry drops out on Streets/Egyptian at default FOV | M-142 ruled out far-clip + LOD-multiplier; narrowed to portal/room-visibility BFS. **First check overlap with D271's portal-culling fix** (branch state, see Phase 0). Needs live screenshot+location. | -| D195 | Control: transparency/alpha-blended surfaces render wrong (glass partitions, holographic displays) | **FIXED** (index: gfx_sp_vertex fix) — plan row was stale (2026-09-23 reconcile). | -| D197 | Character face textures wrap around the head instead of clamping (Silo, intermittent) | **CLOSED** (user playtest v0.2.0-pre) — plan row was stale (2026-09-23 reconcile). | -| D182(2)/D176(b) | File-select background degrades after round-trip (comb/interlacing); Surface tree-line static — shared "texture pitch/shear" defect | M-36 Family A; `import_texture_*` line-size assumption. Distinct from the disproven D183 hypothesis. | -| D176(a) | Sky: RDPHALF tri-raster decode; horizon-band coverage (Defect 2) still open | Path B draft in `scratchpad/sky.patch`, phase-fold fix applied, **never visually verified** — one `-level_22` screenshot owed. Branch `land/d176a-sky-perspective-scroll` exists (forgejo remote) — audit + port to main. | -| D75 | Front-end logos: Nintendo logo degenerate (tiny orbiting fragment); Rareware filtering wrong | **FIXED (M-197, user-confirmed live 2026-09-21)**: D50 sidecar converter dropped embedded texture blobs. Remaining: pal/jpn sidecar regen on ROM-holding machines. | -| D290 | Watch in-game "controller" section shows no control-pad graphic | **FIXED 2026-09-23 (`a5804d10`)**: D264-pattern template read in `watchRenderController` + 3 pointer-width/raw-Gfx sites. Sibling sweep also found + fixed **D331** (shell casings never rendered, `94dff811`). | -| D174/D172 | Blood: decals never interactively verified (D120 needs sidecar regen); spray color fixed but owed an eyeball | BUNKER1 firefight with regenerated sidecars (`d43 && d69 && d88 --regen`). | -| D240/D241 | Gunshot SFX cadence drifts; PP7/AK47 goes silent under heavy fire near looping sounds | D241 high-confidence = D207 reproduced (check D207 entry status first — index row is stale/ambiguous). Fold into the D207 voice-cap family. | -| D322 | Long-session audio degradation (issue #87, v0.3.0 full campaign): from Silo onward audio worsens; by Caverns/Cradle OST inaudible + SFX "come and go"; restart cures → session-accumulating state | OPEN — static triage done (teardown audit clean, mixer stateless, D202 leak class covered only for ownerless `count==-1` loops and only validated single-level; D305 already records a pool/list desync). Ranked: voice-pool exhaustion/counter drift > queue starvation > evtq saturation. `GE_D322` pool-telemetry probe shipped 2026-09-21; needs a campaign capture with `GE_D322=1 GE_D204=1`. | -| D311 (residual) | Upstream anim-poison source still open (freeze class guarded) | **Likely root-caused by D336** (head-bob alias read); headless Caverns clean at 2× pool. Close after one live Caverns combat session. | -| D258 | PAL/JP asset conversion broken at the source-data level | Repair path verified, not applied. Region fidelity — NTSC-only users unaffected, but it blocks "faithful in every region" and any JP/PAL ROM use. | -| D160 (residual) | Cutscene reliability PARTIAL (D243 family mostly fixed via M-189/M-190) | Residual = whatever the live GE_D160/GE_D243 campaign pass shows. | - -### Bucket B — Stability (crashes/freezes/hangs; not N64-parity per se) - -| ID | Item | State / notes | -|----|------|---------------| -| D289 | Dam computer-room crash (Deck, one occurrence, never repro'd) | Needs a human re-test on a current build (D255+). If it never recurs → close as untracked. | -| D287 | Windows: real, non-self-recovering freeze after ~15–20 min idle + click-back | One confirmed occurrence; `gfxFrameMsgQ` full is a lead. Consider crash-dump wiring on Windows for the next hit. | -| D286 | Deck: ~8 s self-recovering frame freeze on Caverns | One confirmed occurrence, no repro trigger. | -| D175 | Brief stutter/hang (door-open on Runway/Surface) | Matches known transient-hang classes; reproduce under `tools_pc/debug.ps1` idle-box before believing it's new. | -| D285 (residual) | Audio-thread race fix was PARTIAL | Re-check the thread-lock state vs D305's NULL guard coverage. | -| D281 | AllUnlocked breaks audio + RMB aim when enabled with zero saves written | User-facing mitigation shipped; proper fix = make the feature robust or gate it harder (see Phase 3 decision). | -| — | getenv caching in hot paths (**DONE 2026-09-23, `d9d7cd35`**) (post-release cleanup, HANDOFF M-192) | D302 fixed one instance; sweep for stragglers. Cheap perf/stability hygiene. | -| D117 | Frame nondeterminism (GE_DETERM attempted, not achieved) | Parked as a research item; do not block the release on it. | - -### Bucket C — Improvements / QoL (outside OG game; after fidelity) - -| ID | Item | Notes | -|----|------|-------| -| Widescreen | Currently **stretched 4:3**, not native — README discloses this | **DONE 2026-09-23 (D334 world `d5d9a36a`, D335 HUD/front end `f62bceaf`)**: native at any aspect, default on, F10 toggle; Rule-2 approved. Owed: user HUD playtest at 16:9/21:9; D335a cosmetic. | -| D237 | F10 overlay: mouse-wheel scroll, more settings, category grouping | **DONE 2026-09-23 (`91bd0f84`)**: F10 category headers + 5 more rows (wheel scroll already existed). | -| D314/D315 | F10 flicker (user-deprioritized) + main-vs-in-game menu scaling | D315 deferred from v0.3.0; bigger design question — lands naturally with the overlay work. | -| D238 | Mouse sensitivity rework (master sens, RMB aim sens, Y-gain parity) | Partly superseded: aim modes folded (D337, N64 default via PD integration; Centred (PC) opt-in), Aim range PC/N64 (D338). Remaining: sensitivity feel tuning. | -| D239 | Click-to-lock parity: delete always-grab mode 0 + config key | Small; closes D192 by deletion. | -| GEPD controls (M3) | **IMPLEMENTED, targeted playtest owed (D378/D380/D383–D386).** One effective keyboard/mouse layout, no `Input.Layout` selector: Ctrl crouches without aim; E interacts without reloading; R reloads without interacting. Input → Bindings → Movement/Actions edits two Primary/Secondary keyboard-or-Mouse 1–5 slots in both UIs (D388). Mouse 3 (right) is Aim default; wheel still cycles weapons. Menu arrows/Enter/Escape and mouse clicks remain fixed. See `MODERN-OPTIONS-PLAN.md` M3; earlier preset/B-button text there is historical. | -| Modern controller presets (D394) | **1.1 Jinx button roles physically confirmed; precise gadget category still open**: A action, X reload, B cycle owned gadgets, Y cycle weapons, L3/R3 crouch, RT fire, LT/LB aim, D-pad movement, Start pause. RB updated-graphics and Back scores are XBLA remaster-specific: no HD assets/score view in the N64 port; reserve RB, keep Back→PC options. **Planned** named selector: 1.1 Jinx (L move+strafe, R look+turn), 1.2 Christmas (L move+turn, R look+strafe), 1.3 Frost (L look+strafe, R move+turn), 1.4 Elektra (L look+turn, R move+strafe). Face/trigger roles remain fixed across schemes. Add an `Input.ControllerPreset` enum with default 1.1, F10/front-options selector, preserve per-pad inversion/deadzone, separate keyboard `NaturalPitch` from the pad selector, adapt both stick axes and D-pad per scheme, and verify in watch/menu/tank and physical controller before labeling each selectable. Do not treat these four presets as already implemented. | -| Modern options wave (parity set, 2026-09-27) | **In progress, not a single opt-in Modern page (D388).** M1: Input → Bindings and Gameplay → HUD, shared by F10/front; M2: Original red crosshair plus White/custom RGB, style, size and visibility implemented (D379/D381/D382); M3: single effective layout and rebinding (above). **M5: redundant Low-end toggle removed (D388); Frame rate cap and Anti-aliasing remain independently adjustable, stored values preserved on migration.** M4 GE+ knockback/hitstun/damage-sound/death-cam toggles parked for v0.5.0, not v0.4.0 blockers. **M7 BLOCKED (D387):** AllUnlocked ON can persist synthetic cheats/times after any profile-settings save; OFF does not remove them. Do not run on live saves; a safe design and multi-profile fixture tests are owed. PNG screenshot upgrade dropped from this wave; dev PPM dump remains. The M1–M7 detailed notes retain their historical design record in `MODERN-OPTIONS-PLAN.md`. | -| D226 | Player-adjustable HUD scale (ammo counter, pickup text, dialogue) | **DONE 2026-09-24 (`30720354`)**: Game.HudScale 75–150% (user-verified), fast3d rect scaling about anchors, dialogue/bottom width-capped. | -| Controller rebinding UI (D395) | **Not implemented.** The two Input → Bindings → Movement/Actions pages edit keyboard/mouse slots only. Pad-only users could previously enter a capture modal and get stuck; pad-triggered capture is now disabled in both UIs and B cancels a capture started by keyboard/mouse (NTSC build linked; physical pad-only retest owed). Controller presets (D394) are separate from arbitrary pad rebinding; design an explicit controller-actions page, persistence/migration and capture/cancel semantics before enabling pad capture. | Post-v0.4 scope decision; no promise that keyboard binding slots can hold pad buttons. | -| Rumble (D224) | Wire `osMotor*` → `SDL_GameControllerRumble` (PD precedent exists) | Deprioritized M-87; pick up if gamepad support is in scope for v0.4.0 — user call. | -| Per-pad tuning / hot-plug | PD incremental hot-plug + remembered seats; deadzone/scale/rumble per pad | Medium; real-controller users. | -| Window polish | HiDpi, center window, maximize/default-fullscreen | Small P-class wins. | -| Unlocked FPS | Plan parked; premise changed (D248/D250 fixed — default is now a true 60) | Phase 0 re-measurement before any work; motion interpolation stays out. | - -### Bucket D — Infra / housekeeping - -- **D244** self-hosted CI runner link failure — fix before the release tag so - CI can build win+linux bundles (or confirm GitHub runners suffice). -- Post-release v0.3.0 cleanup (HANDOFF): remove GE_OBJT probes - (`chraction.c` chrSet/UnsetStageFlags, `propobj.c` GAS line); M-183/M-185 - clamp removal pending full-cutscene re-verification; D309 probe decision - (keep for v0.4.0 debugging or remove). -- Working-tree archaeology: ~~uncommitted D236 second-pass state~~ **RESOLVED - before this plan was written — `532092c7`/`6807555c` (Sep 15) already landed - the D266 IA4/texedge decode + D271 portal-culling fixes on main and dropped - the D267–D280 probes.** Consequence: Phase 1 item 9 (D249) can proceed with - the D271 fix in-tree; the D236 tree-wall work continues from D276's oml/fmt=3 - lead on top of the landed decode fixes. - -## Session additions (2026-09-23/24, local) - -- **D336** Frigate "everything breaks but the gun" (uninitialised head-bob alias fields, D100 follow-up): FIXED, user-confirmed. -- **D337/D338** aim modes folded + Aim range option: user-approved ("Seems good"). -- **D339** #92 low-end perf: `GE_PERFSTAT` probe; CPU budget measured (~2 ms/frame), GPU the likely bottleneck; needs target hardware. -- **#105** (Enhanced/Remaster water reflection) refers to a `feature/graphics-final-stack` branch that isn't on origin or local; not v0.4.0 scope. Its "D329" label collides with ours, so watch for that if that work merges. - -## Phases - -### Phase 0 — Hygiene (½–1 session; do before any fidelity work) -1. Branch `release/v0.4.0` off `main`. -2. ~~Resolve the uncommitted D236/D271 tree state~~ already landed (`532092c7`). -3. Remove GE_OBJT probes + playtest-folder `.bat`; decide D309 probe fate; - getenv-cache sweep. -4. Fix/confirm CI runner (D244) so release tooling works end-to-end. -5. Carry-over verifications from the v0.3.0 handoff that were owed: - Runway AllUnlocked toggle re-test, menu-click spot check (D316/D317 both - closed — just confirm no regression on the new tree). - -### Release gate (independent of the phases below) - -**D322 (long-session audio degradation, issue #87)** is a campaign-breaking -defect in shipped v0.3.0 and does not wait for the phase ladder: the -`GE_D322` probe is already built and smoke-tested — request the campaign -capture (`GE_D322=1 GE_D204=1`) from the reporter now, and run its decision -rules as soon as the log lands. If the capture implicates a port-side leak, -the fix slots into Phase 2; if it's a rule-2 behavioral difference, it gets -its own sign-off item. - -### Phase 1 — Fidelity wave (the release's core) -Ordered by (crash risk → visibility → effort); each item ends with its own -verification (headless where possible, human eyeball/ear as merge gate): - -1. **D320 live verification + watchdog generalization** — the only open - item that can hard-lock progression on 8 levels; probes already exist. -2. **D289 re-test** (crash class; one human pass, then close or chase). -3. **Batch "one-off geometry/texture anomalies": D288 + D294 + D291** — - all are single-location, bounded, visually documented; likely share a - fast3d import/culling root cause. One investigation thread. -4. ~~**D252 rainbow particles second cause**~~ — **retired (M-188):** the - user's symptom closed as a duplicate of the D219 fix (live retest clean); - decode + dimensions proven correct (M-141/M-156). Re-open only on a - fireball-rainbow re-report. -5. **D245 water seam** — candidate fix exists; A/B against N64 reference, - then flip default (A1-class fidelity call). -6. **D303 + D306/D308 z-fighting family** — small visual parity items; batch - per commit cadence. (~~D307 camera flip~~ investigated 2026-09-21: no - port-side bug found, all inputs verified N64-faithful — blocked on user - repro detail; see Bucket A row.) D303 + D306/D308 both investigated - 2026-07-19: no port-side bug found in either (pipeline/data/precision all - verified N64-faithful); capture probes `GE_D303` and `GE_ZF` are in-tree — - both now blocked on user-side captures (firing repro / N64 reference). - Nothing further to do agent-side until those land. -7. ~~**D236 tree wall**~~ — **done (M-198, pass 26):** `TEXTURETYPE_DETAIL` - tile-binding fix in fast3d (`Video.DetailBaseTile`); live-verified, - probes stripped. Only the user play-test sign-off remains. -8. ~~**D235 texture reload leak**~~ — **done (M-193):** port-side - `TextureCacheMap` now cleared on every stage transition via - `videoResetTextureCache()` in boss.c; 21/21 sweep pixel-identical; live - death/restart confirm is a playtest item. -9. **D249 culling** — *after* the D271 overlap check; live screenshot+location - first, BFS instrumentation second. -10. **Audio parity: D322 long-session degradation first** (issue #87 — the - only audio report that breaks a full campaign; `GE_D322` probe already - shipped, get the campaign capture, then apply the decision rules in the - finding), **then D240/D241** (fold into D207 family) + verify D230's bass - fix by ear on the elevator track. -11. **Front-end fidelity batch: D290 watch pad graphic, D182(2)/ - D176(b) pitch-shear defect, D176(a) sky Defect 2** (verify the patch - visually first — it's been unverified for weeks). (~~D75 logos~~ fixed - M-197; ~~D195 Control alpha~~ FIXED per findings; ~~D197 face wrap~~ - CLOSED at v0.2.0-pre playtest — both were stale in this list.) -12. **D174/D172 blood verification** (regen sidecars, BUNKER1 firefight). -13. **D258 PAL/JP asset conversion repair** — apply the verified repair path; - re-run the region sweep. Decide scope: ship JP/PAL-accurate or document - NTSC-only for v0.4.0 (user call). -14. **D311 upstream anim-poison source** — close out the last open thread of - the freeze class so the known-issue bullet can be dropped. - -Exit criteria: every README "Known issues" bullet either fixed or explicitly -re-disclosed with a v0.5.0 pointer; no new fidelity regressions (21-level -sweep after each landing). - -### Phase 2 — Stability wave -1. **D287/D286 freeze investigation** — wire Windows crash dumps (or an - in-process fault log) so the next occurrence is diagnosable; pattern-match - against `gfxFrameMsgQ` lead and the D309 teardown-race class. -2. **D175 stutter** — idle-box repro under `tools_pc/debug.ps1`; likely a - known transient-hang class, confirm and close or chase. -3. **D285 residual** — re-audit audio thread-locking vs D305's guard. -4. **D281 AllUnlocked no-save path** — make it robust (root-cause the - sndHandleEvent/aim interaction) or gate the toggle until a save exists. - User call on which. -5. Long-idle soak test (both platforms) as the stability acceptance bar. - -### Phase 3 — Improvements / QoL (only after Phases 1–2 are green) -Order by user-visible value (2026-09-27 refresh — several rows since DONE, marked): -1. ~~**Native widescreen**~~ — **DONE 2026-09-23** (D334/D335; playtest sign-off owed). -2. ~~**F10 overlay wave: D237 + D314/D315**~~ — D237 **DONE 2026-09-23**; D314/D315 still open (user-deprioritized; lands with further overlay work). -3. **Input feel: D238 sensitivity rework, D239 delete always-grab, window - HiDpi/center; rumble + per-pad tuning if gamepad is in scope (user call).** -4. ~~**D226 HUD scale**~~ — **DONE 2026-09-24**. -5. Unlocked FPS: re-measure Phase 0 against the post-D248/D250 baseline and - decide whether v0.4.0 or v0.5.0. -6. **Modern options wave — substantially implemented, NOT release-complete** - (D377–D389). Five functional roots; Input → Bindings → Movement/Actions - and Gameplay → HUD work in both menus. One effective PC layout with - dedicated E/R/Ctrl; two bind slots including Mouse 1–5; crosshair - visibility, Original/White/colour, style and size. Redundant Low-end - preset retired, but FPS cap/MSAA remain. M4 GE+ toggles deferred to - v0.5.0, M6 PNG screenshot removed from this release. **M7 blocked by - persisted fake save progress (D387); do not test with a live save**. - Physical acceptance, M7 release decision, 21-level visual gate and - documentation/commit reconciliation are still open. Earlier "one - opt-in section/default-off/preservation proof" estimates were superseded; - the original research remains in `MODERN-OPTIONS-PLAN.md`. - -### Phase 4 — Release ritual -1. `./build-pc.sh ntsc-final` clean; `/linkcheck`; full 21-level - `tools_pc/verify.sh sweep --json`. -2. Full campaign playtest sign-off (user), incl. the D320-probe levels and a - water level + Surface + Silo (the Phase 1 visual fixes). -3. Refresh README Known Issues, `.github/release-notes.md`, findings index; - regenerate `agentic-development.md` stats from git (still owed since - v0.3.0 prep). -4. Push `release/v0.4.0` → tag `v0.4.0` → CI draft bundles → validate both - artifacts → fast-forward main. - -## Explicitly out of scope for v0.4.0 -- macOS/ARM, LAN multiplayer. -- Motion interpolation / sim-rate changes (UNLOCKED-FPS non-goals). -- D117 frame determinism (research-parked). -- **Mod support and HD texture packs — both post-1.0, both researched; see below.** - -### Post-1.0, researched and tracked (do not start in v0.4.0) - -Both were investigated 2026-09-22 against the Perfect Dark PC port fork -`perfect-dark-dabs-mod-dabs-mod` (**MIT**, Ryan Dwyer — same license as this -tree; `AGENTS.md` already names the PD port a standing reference, so code may be -adapted with attribution). Fuller sequencing notes live in the local -`docs/dev/notes/MODERN-PORT-FEATURES-BACKLOG.md` §5/§6, which is -gitignored — the -durable record is `docs/dev/findings.md` and the summary here. The -drop-in mod support design itself is expanded in the local -`docs/dev/notes/MOD-LOADER-PLAN.md` (2026-09-27: C1–C4 components, v1 -scope, acceptance bar). - -**Mod support (ROM patches) — viable, medium–large.** Findings **D325–D328**. -- Community mods *repack* the ROM, so vanilla offsets are useless: Goldfinger 64 - (12→24 MB, 797/803 slices differ) and even the content-neutral Random Eye - randomizer (identical ROM size, yet 803/803 slices differ — content shifted - +7392). Both die in `tools_pc/d43_emit.py:273` on a vanilla-offset zlib slice. -- **The fix is not a loader rewrite.** PD's `port/src/modimport.c` states our own - constraint verbatim and instead diffs the patched ROM against stock, extracting - what differs into a **mod directory** its loader already reads. The game keeps - running its stock ROM at fixed compiled offsets. -- GE's file table is in the ROM after all (D327 said otherwise; corrected by - D328): 1172-compressed data segment at ROM `0x21990` — one raw inflate, which - `port/src/rzdecomp.c` already wraps — table at `0x252c4`, 727 records of 12 - bytes, pointers `0x80020d90`-based, size by differencing adjacent addresses, - exactly `obInit` (`src/game/ob.c:126`). Verified: **681 exact offset+size - matches vs `filelist.u.csv`, 0 mismatches**; discovery needs **no hardcoded - offsets** and reads Random Eye's own table to **704/726 files byte-identical**. -- Remaining work: extraction in C (proven), patch decoding (PD's `rompatch.c` is - an MIT VCDIFF/BPS/IPS decoder — **removes the GPLv2 xdelta3 constraint**), - converters taking the extracted table, and the real effort: a - **mod-directory override layer** — `port/src/romdata.c` reads the ROM only and - has no analogue of PD's `romfile.moddir` file slots. -- **Hard limit:** mods that *add levels* need new stage-table entries in - `src/game` — rule 2, not the ABI/layout exception. Either a sign-off or scope - down to the existing 20 slots. ASM/code mods stay impossible (ROM code is never - executed here). - -**HD texture packs — medium, well-scaffolded. Answers issue #100** ("High -Resolution Textures", open, `enhancement` — a user asking whether HD textures can -be used with the port). Architecture studied from PD's -`port/include/texpack.h` + `port/fast3d/gfx_pc.cpp`; no finding written yet, so -this entry and backlog §6 are the record. -- **Identity is the whole problem, not decoding.** The renderer only ever sees - the pointer `gDPSetTextureImage` was handed, so PD keeps an address → - texture-number registry populated where `texLoad()` runs and invalidates it - when the pool drops (a stale entry names the *wrong* texture). Then three-tier - matching: texture number → texel checksum (for textures embedded in model - files) → per-record. Packs are `textures/.png`; decode happens on a - worker so loading does not hitch; the hook is inside `import_texture`, gated so - it costs one lookup when off. -- Most scaffolding exists here: our `gfx_pc.cpp` is the same lineage - (`gfx_texture_cache`, `:92,579`), `g_Textures[].texturenum` exists - (`src/game/chr.c:3894`), and **`GE_TEXDUMP` already dumps textures** - (`port/fast3d/gfx_pc.cpp:1205`) — it just names them by sequence - (`r000_f3_s1_64x64.bin`) rather than texture number, so dumps cannot round-trip. - Renaming that is the single change turning the probe into a pack dumper. A PNG - decoder is the one new dependency, and zlib is already required. -- Step 1 (the identity registry) is the only novel design decision, and it - records identity where textures load — a `src/game` touch needing `#ifdef PORT` - plus a §F note. Settle that at design time, not mid-implementation. - -## Session log (agent-driven, 2026-09-20 evening) - -**Phase 0 — done except user-side items:** -- `release/v0.4.0` cut off `main` (which already carried the D266/D271 fixes, - `532092c7` — the "uncommitted tree state" item was stale; plan updated). -- D322 probe work committed (`7bb43260`); GE_OBJT probes removed (D321 closed, - M-192 item 7, `56ac2ab8`); D309 probe **kept** (inert, cheap, recurrence - value for the D309/D310 freeze class). -- getenv hot-path sweep (D302 follow-up): function-level scan of all GE_* - sites found no frame/triangle-rate stragglers in fast3d; cached four - per-frame/per-hit guards in `src/game/bg.c` (GE_D104 room pass, GE_D63 - primary+secondary render logs, GE_D154 inner GDL walk) — all `#ifdef PORT`, - mechanical. -- D244 (self-hosted CI runner): needs `pacman -Syu` on the box itself — - **user-side action**, not locally fixable; fallback (`#ifdef PORT` thin - wrapper for the `#pragma weak` alias) only if the toolchain update fails. -- 21-level sweep gate: running on the new tree (see `scratchpad/sweeps/`). -- Carry-over D316/D317 + Runway AllUnlocked re-tests are interactive → folded - into the big playtest brief below. - -**Phase 1 — in flight:** -- Item 1 (D320): generic pin-signature detector added to `d318TimelineTick` - (`GE_D318T=1` now covers any chr: ACT_ATTACK + live-fire attacktype + - byte-stable animframe for 10 s → one `D320T: PIN?` line per chr, repeating - every 10 s while pinned). Detection only — the recovery watchdog stays - hardcoded to ai_22 per D320's own instruction until a second list freezes. - Live verification of Facility ai_19 / Control ai_9 / Depot ai_12 beats = - playtest work (see brief). -- Item 3 (D288/D294/D291 batch): D288 probe refined per M-155 follow-up - (any_behind_camera==0 only — the w<0 bbox math was a false lead; oml word - now logged for asset ID). D294: `GE_D104` room-visibility trace confirmed - sufficient (curRoom/roomsDrawn/budget + per-room load state every 10 s); - needs live Statue repro at FovScale≈130 + max draw distance. D291: still - needs live Dam truck details — lowest value of the three. -- Item 13 (D258 PAL/JP): **deferred pending user scope call** (ship - multi-region or document NTSC-only for v0.4.0) — no PAL/JP ROMs available - here to verify a blind CSV rename against. -- Item 4 (D235 wrong textures after death/restart): **mechanism found + fix - applied (M-193).** M-108's refutation of the game-side CI-cache theory - stands, but the *port* has its own `TextureCacheMap` (source-address keyed, - no content check) that was never invalidated on stage transitions — - `videoResetTextureCache()` existed with zero callers. Now wired into the - per-stage reset block in boss.c (`#ifdef PORT`, after `memaReset`). - Pixel-identical re-uploads; golden sweep running to confirm. Live - death/restart confirmation + a `GE_D235` >0 capture are playtest items. -- D311 upstream poison source (M-194): static pass done — all - `sub_GAME_7F06FE90` callers pass arg2=16.0f (denominator safe); the - old→new lerp only circulates values; `bheadAdjustAnimation` is the one - path where a live player-velocity corruption reaches `model->speed`. Both - speed-write choke points (`modelSetAnimSpeed`, `modelSetAnimation2`) now - log `D311: … ANOMALY` on |v|>1e4/NaN — next Caverns occurrence is a - one-line diagnosis. No env var (D156-guard pattern). -- Item 4 (D252 rainbow particles): **effectively closed — no agent work - left.** Re-read the finding this session: M-141 refuted the width/height - suspect (line=4 → correct 16×14, cross-confirmed by the GE_TEXDUMP - is_fire_shape filter), M-156 proved the ROM bytes decode to a clean - white-hot→ember progression, and **M-188 closed the user's actual symptom - (lingering residue sprites) as a duplicate of the D219 fix** — user - retested live: "seems fine now." Only a *fireball* rainbow re-report would - re-open it. Plan item 4 retired. -- Item 10 (D207 alarm klaxon): **blocked on user repro info, not agent- - workable.** M-77b implemented the designed last-resort LOOPED-preemption - pass + `[D207-DROP]`/`[D207-YIELD]` counters, BUNKER1 forced-alarm soak - showed zero drops / zero yields (pass 1 always finds a victim), and the - change was reverted. The finding now asks for: which level, which - weapon/situation, and a `GE_AUDIOTRACE=1` capture (or clip) of the failing - alarm — then decide between heavy-level 8/8 starvation (re-apply the - reverted pass), an alarm-stop-path leak, or pure perception (klaxon mix - volume vs N64). Added to the playtest brief as a low-effort ask. -- Full 21-level golden sweep: **21/21 PASS** on the final tree (`bb508e5c`), - confirming D235 fix + D311 probe + all Phase 0 changes are pixel-identical. -- **D75 (Nintendo logo) investigation — M-195, no root cause yet but two - suspects eliminated.** (a) Fresh `GE_D63` run: all 23 logo Primary GDLs - emitted every frame, zero unknown-opcode warnings → emission + DL - consumption fine; PC GDLs byte-faithful to N64 source. (b) New `GE_D75V` - per-batch vertex probe: transformed coords match the wordmark geometry - exactly (MP translation matches the lookat) → basematrix / f32→s32 / - matrix-list suspects eliminated. **Leading hypothesis:** every `G_VTX` - batch encodes v0=0, so multi-batch nodes overwrite shared vertex slots - 0..n−1 (only the last batch survives; higher-indexed triangles read stale - slots) — consistent with the single tiny orbiting fragment observed. Next: - ROM-dump one node's GDL stream and cross-reference `G_TRI4` index ranges - against per-batch counts; fix would be port-layer fast3d slot management. - Probe code (`gfx_pc.cpp`, `videoGetFrameCount()` in video.c/h) uncommitted - at end of session — commit with the doc updates. -- **D307 (CCTV facing backwards) investigated — no port-side bug found.** - Env-gated `GE_D307=1` probe in `setupCctv` dumps every input + derived - orientation per record. Bunker1 (`UsetupsevbunkerZ`) decode vs live: model - switch offset = `PcctvZ` (NOT `GcameraZ`, that's the gun) sw[0] - bit-for-bit; boundpads match the pccg sidecar once the legitimate load-time - room-scale (`pad->pos *= get_room_data_float2()`, ≈1.8542 here) is applied; - record config words (incl. `unkCC`) match; all matrix/atan2 helpers are - byte-identical `src/game` code. Conclusion: PC orientation values == what - the N64 computes; if the symptom is real vs N64, the remaining suspect is - fast3d's handling of the non-orthonormal lens matrix (`unk84` row 0 scaled - 0.1×). Blocked on user repro detail (which camera/level + screenshot). - Findings entry updated; probe kept in-tree (D60/D88 pattern). -- **D303 (extra long muzzle flash) investigated — no port-side bug found yet,** - blocked on live capture. Mapped the full flash pipeline (`gunUpdateAndFire` - builds `rwmtx[1]` = yaw×scale×flashext and `rwmtx[2]` from `Switches[2]`; the - flare arms are `dorottex` DLPRIMARY nodes) — all byte-identical game code, - matrix helpers verified index-for-index. Ruled out: weapon stats (M16's - flashext=6.0 is the legitimate max, sniperrifle matches; the ext≥3 ⊆ - CAN_DUAL_WIELD overlap is design coincidence) and model conversion (Gm16Z - DLPRIMARY records + vertex blocks byte-faithful per d43 round-trip). Added a - TEMP `GE_D303=1` probe in `gfx_pc.cpp` (cached-getenv; logs very-tall-thin - emitted triangles with blend/oml/comb/prim/env/w) — 75 s unattended runs on - Bunker1/Dam/Depot show only static scene geometry, so the artifact needs a - live firing repro. Next: user fires the M16 under `GE_D303=1` and shares the - log lines; the spurious quad's RDP state will identify its source. -- **D306/D308 (z-fighting family) investigated — real fighting confirmed, cause - framed.** Verified GE's RSP writes post-transform x/y/z/w as full 32-bit 16.16 - (not s16 8.8), so the port's float pipeline + `GL_DEPTH24_STENCIL8` matches N64 - depth precision — no port-side precision deficit exists. Built a TEMP - `GE_ZF=1` flicker probe in `gfx_pc.cpp` (96×54 screen-cell grid, per-cell depth - history, requires non-monotonic a-b-a oscillation within GE_ZFTOL recurring ≥5× - at the same cell; `GE_ZFBOX` region filter) — earlier center-pair and simple - flicker variants saturated on tessellation noise. Dam (`-level_33`) unattended: - sustained hits in the lower-left truck region with per-cell oscillation counts to - ~970 → D306's "transparent areas" are real depth-test alternation. Prime suspect - is level-authored coincident geometry (fights on N64 too); confirming port-only - needs an N64 reference of the same spot, and a mitigation (e.g. float depth) - would diverge from N64 — parked pending that. -- **Plan/doc refresh:** D75 (M-197) and D236 (M-198) marked fixed throughout - (Bucket A rows, Phase 1 items 6/7/11, playtest brief); findings index - regenerated; README Known Issues to match. - -### Big playtest brief (for the user, when the tree is final) - -Build: `./build-pc.sh ntsc-final`. Set **`GE_D322=1 GE_D204=1`** for the whole -campaign (D322 pool telemetry — the release gate). For the three D320 beats, -add **`GE_D318T=1`** on: Facility (the captive-scientists monologue/execution -beat), Control (the ai_9 guard beat), Depot (the execution beat) — watch for -`D320T: PIN?` lines; a frozen run keeps printing every 10 s. - -Checklist beyond the normal play: -1. **D322**: full campaign, share `ge007.log` after (decision rules in the - finding). -2. **D316/D317 regression confirm**: click menu rows at a few window sizes, - esp. low rows (crop overscan bars) with SafeAreaCrop on/off; toggle - Game.AllUnlocked back and forth in-game. -3. **D289**: Dam computer room — interact with the terminals (was a one-off - crash, likely D255-class, already fixed — confirm it stays gone). -4. **D294**: Statue, set FovScale ≈130 + max DrawDistance/LodDistance, walk - past the CCCP monument at ground level; if the black-ground window hits, - note the time (a `GE_D104=1` run there is the follow-up capture). -5. **D291**: Dam — watch for texture glitches as the truck drives past; - screenshot + which surface. -6. **D175**: door-open on Runway/Surface — note any stutter/hang. -7. **D287**: leave a level idle+unfocused 15–20 min, click back (the - non-self-recovering freeze; one prior occurrence). -8. **D320 beats** as above with `GE_D318T=1`. -9. Audio by-ear: elevator track bass (D230 verify), PP7/AK47 under heavy fire - near looping sounds (D240/D241). -10. **D235**: on any level, die and restart (or reload a save) in the same - session, then look around — textures should be correct (no swapped/wrong - stage textures). Run once with `GE_D235=1` and share the log if anything - looks off (a `>0` count means a stale-cache hit slipped through). -11. **D288 (Silo)**: play Silo normally; if you see the wrong-color target / - geometry anomaly, set `GE_D288=1 GE_PCDUMP=1` for a 30–60 s run around it - and share log + captures (the probe logs the offending oml word for asset - ID). -12. **D311 (Caverns)**: play Caverns ~2 min of normal combat; if the anim- - speed freeze/poison recurs, the log will now carry a one-line - `D311: … ANOMALY` naming the corrupting call site — share it. -13. **D207 (alarm klaxon)**: if gunfire goes quiet during an alarm, note the - level + weapon + situation; ideally re-run that spot with - `GE_AUDIOTRACE=1` and share the log (or a short clip). -14. **D75 (front-end logos)**: confirm the Nintendo/Rareware logos + legal - page still render correctly (fixed M-197; user-confirmed once already). -15. **D236 (Surface trees)**: Surface 1/2 — confirm the treeline renders as - discrete tree sprites, not a wall (fixed M-198; probes stripped). -16. **D307 (security cameras)**: if any in-level CCTV camera looks like it's - facing backwards, note which level + which camera (screenshot) — the - orientation math is verified N64-faithful, so a concrete repro is what's - needed to keep chasing it. -17. **D303 (extra long muzzle flash)**: run with `GE_D303=1` in your terminal, - fire the M16 (and any other gun that shows the extra upward flash) until - you see it, then share the `D303:` log lines from around that moment — - each line carries the RDP state of a candidate quad and will identify the - spurious draw call's source. -18. **AllUnlocked preservation (M7) BLOCKED — D387.** Do not run this - against a real save: even changing a profile setting while ON persists - synthetic cheats and 0x3FF completion times; OFF requires a restart and - does not undo already-written fakes. First define safe earned-progress - semantics, fix in `port/`, and verify on private copies with settings - writes, mission completion, OFF restart and multi-slot CRC checks. - M4 modern toggles are parked to v0.5.0. - -## Conventions reminder -- One findings entry per investigation; the index tail moves constantly — - it ended at **D370** as of 2026-09-27 (the F10 drag entry), so new - labels start **D371+** (the old "after D322" note is long stale; always - confirm the tail of `findings-index.csv` before filing). -- Fidelity fixes: port-layer or documented A1/ABI exception only; genuine - behavioral differences need the rule-2 sign-off (`docs/dev-process.md`). -- Every subagent reads `docs/porting-notes.md` first and appends to it. diff --git a/docs/dev/VIEWMODEL-RESEARCH.md b/docs/dev/VIEWMODEL-RESEARCH.md index 9da65fb1..2d81234e 100644 --- a/docs/dev/VIEWMODEL-RESEARCH.md +++ b/docs/dev/VIEWMODEL-RESEARCH.md @@ -8,7 +8,7 @@ > `#ifdef PORT` in `src/game/gunfire.c`. See findings.md D215. This doc is kept > for the ruled-out analysis only. -Companion to `GRAPHICS-BACKLOG.md` ("1P weapon viewmodel") and findings D115 +Companion to the (since retired) `GRAPHICS-BACKLOG.md` ("1P weapon viewmodel") and findings D115 item #5 / AUDIT-M6 #5. Goal: narrow the five audit suspects to a ranked, probe-ready list. No code changed by this doc. diff --git a/docs/dev/agentic-development.md b/docs/dev/agentic-development.md index 6bba6d5c..03d31662 100644 --- a/docs/dev/agentic-development.md +++ b/docs/dev/agentic-development.md @@ -283,8 +283,6 @@ Honest notes, for anyone weighing whether this transfers. to Opus 5. Each tier earned its place on the problems the tier below it couldn't close. -**Still outstanding** (the v0.3.0 known-issues list): cosmetic rendering -defects (particle colours, Surface 1 billboard trees, front-end logos, water -seam), stretched-not-native widescreen, PAL/JP support, a controller -rebinding UI, and macOS/ARM builds. See the README's Status section and -[`GRAPHICS-BACKLOG.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/dev/GRAPHICS-BACKLOG.md). +**Still outstanding:** see +[`docs/ROADMAP.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/ROADMAP.md), +the single tracker for open work. diff --git a/docs/dev/findings-index.csv b/docs/dev/findings-index.csv index 4e1c35b0..83dd7a21 100644 --- a/docs/dev/findings-index.csv +++ b/docs/dev/findings-index.csv @@ -13,7 +13,7 @@ D159,FIXED,"front-end wallet-Bond photo ""interlaced""/combed (RC1 / D149) —", D161,FIXED,Depot (-level_30) ceiling = bright-blue speckle + radial rays (B2).,FIXED (port/fast3d/gfx_pc.cpp) D165 · D166,FIXED,"Input-layer polish (M-31, port-only):",FIXED D180 · D181,FIXED,Native-PC input pass (QoL run):,FIXED (2026-09-26 bookkeeping: -D186,OPEN,"Video.FpsCap throttles the sim, not just presentation (M-39 diag, M-42 clamp).","OPEN (2026-09-26, user direction:" +D186,CLOSED,"Video.FpsCap throttles the sim, not just presentation (M-39 diag, M-42 clamp).",CLOSED (2026-09-28 bookkeeping: D187,FIXED,"HUD ammo-type/weapon icon vanished at -O2 (M-46 root-cause, M-47 fix + verify).","FIXED (src/game/gunfire.c, #ifdef AVOID_UB); PR #19" D188,FIXED,Linux -O2 boot crash:,"FIXED (src/libultrare/libc/xprintf.c, src/sprintf.c, #ifdef PORT); PR #22" D189,FIXED,Linux -fstack-protector aborts on the decomp's latent stack-buffer overruns (M-48) —,"FIXED (CMakeLists.txt, src/game/stan.c #ifdef PORT); PR #23" @@ -31,7 +31,7 @@ D192,CLOSED,Front-end mission/level-select grid pointer still can't reach the ou D221,FIXED,SELECT FILE mouse hit-box offset —,"FIXED (M-91) — explicit {min,max} coord2d pairs under #ifdef PORT; human mouse-playtest confirmed." D222,FIXED,High Video.FovScale breaks culling —,"FIXED, awaiting visual verification (M-121)." D223,FIXED,QoL ask: mouse-wheel weapon cycling should be directional like a normal PC shooter —,"FIXED (M-105, port/src/input.c) —…" -D224,PARTIAL,"QoL gap (M-87, from a PD-PC-port survey):","LANDED via D401 (2026-09-27, re-promoted from DEPRIORITIZED)." +D224,CLOSED,"QoL gap (M-87, from a PD-PC-port survey):",CLOSED (2026-09-28 bookkeeping: D225,FIXED,"Dev-tooling QoL (M-87, user ask):","FIXED (2026-09-18) — GE_DEBUG_UNLOCKALL=1 short-circuits fileGetIsCheatUnlocked() (src/game/file2.c, #ifdef PORT, cached getenv) to always return TRUE; default off, no save mutation." D226,FIXED,"QoL ask: player-adjustable HUD scale for the ammo counter, bottom-left pickup/item status text, and top-of-screen dialogue/subtitle text.","FIXED (2026-09-24): Game.HudScale 75–150% (user-verified, capped at 150) via fast3d G_HUDSCALE_EXT rect scaling about per-element anchors; dialogue/bottom messages width-capped." D227,FIXED,Sky renders as two independently-moving halves —,"FIXED (M-100b, PR #43) —" @@ -47,7 +47,7 @@ D203,CLOSED,"Steam Deck (SteamOS, x86_64 Linux) v0.1.0 release bundle:",CLOSED D201,FIXED,"src/libultra/audio/bnkf.c bank/instrument/sound/wavetable relocation offsets were s32, truncating the real 64-bit base pointer.",FIXED (src/libultra/audio/bnkf.c) — D202,FIXED,"Silenced PPK ""slap"" + broader real-time mixer corruption (M-52→M-67).","FIXED (2026-09-26, user by-ear:" D206,FIXED,PC played every SFX one bank slot too high —,"FIXED (M-76, src/snd.c:1001, #ifdef PORT) —" -D207,FIXED,"Alarm klaxon starves combat SFX and doesn't recover after it stops (surfaced by D206, M-77).",MONITORING — user 2026-09-26: +D207,CLOSED,"Alarm klaxon starves combat SFX and doesn't recover after it stops (surfaced by D206, M-77).",CLOSED (2026-09-28 bookkeeping: D208,FIXED,Player weapon-fire sound goes permanently silent mid-firefight (all other SFX unaffected) —,FIXED + user playtest-verified (M-78d) — D209,FIXED,Every AI character in the game is locked to a *walk* animation regardless of the speed the ailist commands —,FIXED + user-playtest-verified (M-81). D210,FIXED,chrToPatrol() leaves act_patrol.lastvisible60 uninitialised on PC —,"FIXED (chraction.c:3872, #ifdef PORT → self->act_patrol.lastvisible60 = -1;, N64 line under #else).…" @@ -57,12 +57,12 @@ D168,FIXED,GE_PCDUMP / F12 PPM captures were vertically flipped —,FIXED (port/ D169,FIXED,Front-end mouse pointer can't reach the outer cells of the mission/level-select grid —,"FIXED (M-33, port/src/input.c, port-only, no #ifdef PORT)." D178,FIXED,Pre-mission briefing screen:,FIXED (M-36) — high confidence D175,CLOSED,In-game stutter / brief hang during normal play —,"CLOSED (v0.4.0 campaign sign-off 2026-09-28, user:" -D176,FIXED,"Surface exterior renders wrong (-level_36), two independent defects.","(a) scroll-speed FIXED (fidelity pass; HIGH confidence on root cause, MODERATE on N64 parity); M-93 playtest + M-98 Defect-2 starburst screenshot in the sections." +D176,FIXED,"Surface exterior renders wrong (-level_36), two independent defects.",FIXED (2026-09-29 bookkeeping: D177,FIXED,Ladders non-functional —,FIXED (M-36) — high confidence; interactive climb test owed D172,FIXED,"Bullet-impact / blood / spark particles render magenta or cyan instead of dark red (M-82, ROOT CAUSE FOUND + FIXED).",FIXED (port/fast3d/gfx_pc.cpp gfx_lod_tile_offset)… D174,CLOSED,"""No blood effect"" (user QA report).","CLOSED — faithful (2026-09-26, user side-by-side on 1964:" D183,CLOSED,"M-36 Family A (""texture line/pitch shear"") is DISPROVEN for the Surface repro (-level_36).","CLOSED (2026-09-26, user-approved:" -D173,FIXED,Third-person Bond model spawns too high —,"MOSTLY FIXED as a side effect of D243 (2026-09-18, user-confirmed)." +D173,PARTIAL,Third-person Bond model spawns too high —,PARTIAL (2026-09-29 bookkeeping: D179,FIXED,Packaged build crashes after the logos —,"Diagnosed, not fixed —" D180,FIXED,"Native-PC input pass (M-XX QoL run, qol/native-pc-input-menu, port-only).",FIXED (2026-09-26 bookkeeping: D181,FIXED,Game.ScreenShakeIntensity —,FIXED (2026-09-26 bookkeeping: @@ -74,13 +74,13 @@ D214,FIXED,"Keyboard rebinding — [Input.Bind] config section (M-83, port-only, D215,FIXED,1P weapon viewmodel never drew in-level —,"FIXED (src/game/gunfire.c, #ifdef PORT)." D216,FIXED,"Game.SkipIntro — boot straight to the file-select menu (M-83, port + one #ifdef PORT game-code line, PD parity, Phase 4 quick win).",FIXED (2026-09-26 bookkeeping: D217,FIXED,1P weapon viewmodel + 3P Bond model textures garbled —,FIXED + merged (PR #47 + PR #49); white residual split out as D228. -D218,FIXED,Wide FOV exposes the per-level draw-distance / fog boundary —,FIXED (2026-09-26 reclassified: +D218,FIXED,Wide FOV exposes the per-level draw-distance / fog boundary —,FIXED (2026-09-26 reclassified; 2026-09-29 default corrected: D219,RESOLVED,"PPK explosions render purple on Dam (M-87, user QA, ""not 3D but noting"").","RESOLVED (M-115, 2026-09-12) for the original PPK-purple symptom." D220,CLOSED,"1P muzzle flash position shifts upward at higher Video.FovScale (M-87, user QA, Jungle/M16).","CLOSED — merged into D303 (2026-09-26, user:" D85,CLOSED,Room primary/secondary DL → texLoadFromGdl garbage.,"CLOSED (2026-09-26, user-approved:" D86 · D87,RESOLVED,modelInitRwData truncated ptr · attract-demo BE ramromfilestructure.,resolved D88.4,RESOLVED,propDefs polymorphic record stream not byteswapped → setupDoor crash.,resolved (d88_propdefs.py); layout audit M-20 → D132 -D132,PROPOSED,D88 propDefs layout audit (M-20):,proposed fix (not applied) — +D132,CLOSED,D88 propDefs layout audit (M-20):,CLOSED (2026-09-28 bookkeeping: D157,FIXED,"Campaign never unlocks the next level (M-30, user bug —","FIXED (src/bondtypes.h, #ifdef PORT —…" D160,FIXED,Dam level-end cutscene (Bond off the dam / bungee) never plays —,"FIXED (user, 2026-09-26:" D89-D92,RESOLVED,"stage-load→frame: stan zero-fill overrun, portal address trunc, chr/AI spawn ptrs",resolved @@ -88,13 +88,13 @@ D93-D102,RESOLVED,struct-pun / hardcoded-size pass:,resolved (D95 pool bump part D103-D107,RESOLVED,"BUNKER1 viewport height, depth-buffer clear (G_CLEAR_DEPTH_EXT), portal near-plane, LOD mip tile",resolved D108-D112,RESOLVED,skeletal models: d43_emit.py put_f32 byte-reversal bug,resolved D113-D116,FIXED,portal BFS ok · matrix chain ok · player raw-offset audit + gunfire.c THROW* · HUD text X-mirror,"D114/D116 CLOSED — NOT A BUG (M-33, see D168):" -D117,OPEN,"Frame nondeterminism root-caused; GE_DETERM attempted, not achieved.","OPEN (mode attempted, not achieved)" +D117,CLOSED,"Frame nondeterminism root-caused; GE_DETERM attempted, not achieved.",CLOSED (2026-09-28 bookkeeping: D118,RESOLVED,"SDL input layer; M-24 mouse-look rework (mode-aware map, real INI); D118b/c FIXED.",resolved (D118a residual) D150,FIXED,watch OBJECTIVES / BRIEF page crash:,FIXED (src/str.c #ifdef PORT); interactive re-verify pending D260-D265,CLOSED,"v0.2.1 hotfix batch: D260 wheel direction swap (fixed) · D261 watch-menu nav auto-repeat (fixed, feel-check owed) · D262 watch item-preview model missing → roo…",CLOSED (2026-09-26 bookkeeping: D266-D269,CLOSED,"D236/D265 wall, second pass:",CLOSED (2026-09-26 bookkeeping: D270,CLOSED,Backfilled (M-132) — this label was never given its own row; only code comments and the HANDOFF archive documented it.,CLOSED (2026-09-26 bookkeeping: -D271,FIXED,"D236 wall, third pass —",FIXED-but-insufficient (src/game/bg.c #ifdef PORT) — +D271,CLOSED,"D236 wall, third pass —",CLOSED (2026-09-29 bookkeeping: D276,CLOSED,"D236 wall, eighth pass —",CLOSED (2026-09-26 bookkeeping: D279,CLOSED,Backfilled — this label was consumed by an undocumented partial session (2026-09-14 ~23:00) that left artifacts in scratch/ (d279_run.log… —,CLOSED (2026-09-26 bookkeeping: D281,FIXED,"Game.AllUnlocked (D257) still breaks audio, and can also break right-mouse aim, when enabled before any save has ever been written (v0.2.1 user testing… —","FIXED (2026-09-23, port/src/libultra.c):" @@ -123,7 +123,7 @@ D139,FIXED,Stage-unload crash root cause + fix (M-23).,"FIXED (src/game/cleanup_ D140,FIXED,Pause-menu crash (M-23 open → M-27 fixed).,"FIXED (src/game/bondview.h, src/game/bondview2.c; #ifdef PORT)." D141,FIXED,"Watch-menu item-model crash (M-27), exposed by D140.","FIXED (src/game/gunfire.c, #ifdef PORT)." D152,FIXED,"Mission-failed / objective-failed → permanent black screen (M-28, user bug report —","FIXED (user, 2026-09-26:" -D154,?,"bg.c room-geometry ray/hit-test GBI parser port (M-28, WRITTEN / UNVERIFIED / UNCOMMITTED).",M-30 re-audit + fixes (still playtest-gated for final verify). +D154,CLOSED,"bg.c room-geometry ray/hit-test GBI parser port (M-28, WRITTEN / UNVERIFIED / UNCOMMITTED).",CLOSED (2026-09-28 bookkeeping: D156,FIXED,"Facility outro-cutscene ""hang"" —","FIXED (user, 2026-09-26:" D155,FIXED,"Facility outro-cutscene end → ""hang"" = unclamped deltaFrames spiral (M-29, user bug report).","FIXED (src/game/frametiming.c, #ifdef PORT —…" D153,RESOLVED,"-level_09 frame ~900–1400 0xc0000005 = D117/D134 load flakiness, NOT a bug (M-28).",NOT A BUG — closed. Run anchors on a quiet machine. @@ -145,7 +145,7 @@ D245,FIXED,IsWater water: two competing patterns with a visible seam that moves D246,FIXED,"Tiny pixel strips at the left and right edges of the screen, ""like overscan"" —","FIXED (2026-09-18 session, port-only, Video.SafeAreaCrop)." D247,FIXED,Black bars top and bottom of screen —,"FIXED (2026-09-18 session, port-only, Video.SafeAreaCrop)." D248,FIXED,"Port runs a stable 30 FPS instead of 60 FPS game-wide (M-117, user asked to verify FPS after the D209 timing-keystone fixes).","FIXED, root-caused to a genuine pointer-width ABI bug in src/sched.c (compiled for the PC build, not excluded)." -D249,OPEN,"LOD/culling breaks down on large open levels (Streets, Egyptian named specifically) at *default* FOV —","OPEN — M-142 static review ruled out the unscaled-far-clip and LOD-multiplier suspects (both already correct); CLOSED (v0.4.0 campaign sign-off 2026-09-28, user:" +D249,CLOSED,"LOD/culling breaks down on large open levels (Streets, Egyptian named specifically) at *default* FOV —",CLOSED (2026-09-28 bookkeeping: D250,FIXED,"FPS is inconsistent/low on many levels after 60 Hz shipped (M-119, user QA; D248 follow-on).","FIXED — per-call getenv() cost was the bottleneck; rock-steady 60-61 fps after, user re-tested 1440p/4K clean." D251,FIXED,F10 options overlay QoL pass:,"FIXED (port-only, port/src/optionsoverlay.c)." D252,FIXED,"Particle effects cycle through a rainbow palette (M-124, user playtest of the v0.2.0-pre candidate).","FIXED (M-201, 2026-09-26, user-verified):" @@ -161,7 +161,7 @@ D262,FIXED,v0.2.1 (user report):,"FIXED (primary cause, D264) —" D264,FIXED,"D262's root cause (ABI/layout class, cf.","FIXED — build green, probes removed; visual confirmation pending." D263,RESOLVED,v0.2.1 (user report):,"RESOLVED (not a bug), refined (v0.2.1 further user testing, 2026-09-15)." D265,CLOSED,"D236 investigation (port-layer, port/fast3d/gfx_pc.cpp):",CLOSED (2026-09-26 bookkeeping: -D258,PROPOSED,PAL/JP asset conversion is broken at the source-data level —,DEFERRED (user call 2026-09-28: +D258,DEFERRED,PAL/JP asset conversion is broken at the source-data level —,DEFERRED (user call 2026-09-28: D284,FIXED,port/src/random.c's MIPS64 dsll32/dsrl32 shift helpers were backwards —,"FIXED, 21-level sweep clean, no crashes —" D285,FIXED,Steam Deck SIGSEGV during D255 live-testing = SEPARATE bug:,"FIXED (user, 2026-09-26:" D286,CLOSED,"User-confirmed real ~8s frame freeze on Caverns, Steam Deck —","CLOSED (2026-09-26, user-approved:" @@ -172,7 +172,7 @@ D290,FIXED,"Watch's in-game ""controller"" section shows no control-pad graphic D291,CLOSED,"Dam: texture glitches when the truck drives past (M-141, Steam Deck user playtest).","CLOSED — duplicate of D306, faithful (M-201, 2026-09-26:" D292,FIXED,Death animations are glitched —,"MOSTLY FIXED as a side effect of D243 (2026-09-18, user-confirmed alongside D173) —" D293,FIXED,"QoL ask: no discoverable way to quit the game from the UI (M-141, Steam Deck user playtest —","FIXED (M-141) — ""Quit to desktop"" row added to the F10 overlay; build-verified Win+Linux, not yet live-confirmed.…" -D294,FIXED,"Statue: ground geometry near the large CCCP/hammer-and-sickle monument briefly renders solid black over a wide area, then self-recovers 10-30 seconds later… —","FIXED, user playtest confirmed (2026-09-25:" +D294,PARTIAL,"Statue: ground geometry near the large CCCP/hammer-and-sickle monument briefly renders solid black over a wide area, then self-recovers 10-30 seconds later… —",PARTIAL (2026-09-29 bookkeeping: D295,FIXED,"Campaign-halting crash at the end of Control Center on 00 Agent difficulty, then a crash on every subsequent relaunch/resume (GitHub #87, user campaign playtes…",FIXED (M-149) — M-148 root-caused + reproduced on HEAD; fix landed: D296,CLOSED,"Control Center, 00 Agent:","CLOSED (2026-09-26, user-approved:" D297,FIXED,v0.3.0 silently wipes ALL pre-fix player saves on first launch (D284 side effect; found during D295/M-148).,FIXED (M-150) — migration shim landed: @@ -225,7 +225,7 @@ D344,FIXED,Host PC bugchecks (BSOD 0x119 VIDEO_SCHEDULER_INTERNAL_ERROR) during D345,FIXED,"Front-end crosshair snaps away from the pointer on screen/page changes (file select → mode select setCursorPOSforMode(0), front.c:2516; every MENU_PC_OPTIONS p…","Port-only, user-verified (parts a–g):" D346,CLOSED,PC-settings wording pass:,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest. D347,FIXED,F10 overlay controller:,"FIXED (port-only, optionsoverlay.c):" -D348,?,"File-select ""PC Options"" label:",SUPERSEDED by D398 (glyph replaced with the IMG_DOT texture; no new art). +D348,CLOSED,"File-select ""PC Options"" label:",CLOSED (2026-09-28 bookkeeping: D349,CLOSED,GE watch settings are not normally saved to the Bond file:,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest. D350,CLOSED,Watch-backed PC options:,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest. D351,FIXED,Existing D346 value/unit rows had positional initializers shifted into hidePtr:,"FIXED (port-only): designated row initializers restore unit, deadzone divisor and hide-if-auto fields; build clean, headless smoke clean." @@ -243,8 +243,8 @@ D363,FIXED,F10 return to a dark overlay with the N64 watch's green/Bank Gothic v D364,FIXED,Settings UI feedback:,FIXED (user mostly accepted; D365 follows up on annotation size and click targets). D365,CLOSED,F10 annotation hierarchy and visible mouse change targets (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest. D366,CLOSED,Input calibration label:,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest. -D367,?,F10 full-width control boxes misaligned with their contents (2026-09-27).,SUPERSEDED by D368 after user playtest: -D368,?,Simplify F10 click targets:,"INPUT RULE KEPT; tinted strips user-rejected, removed in D369." +D367,CLOSED,F10 full-width control boxes misaligned with their contents (2026-09-27).,CLOSED (2026-09-28 bookkeeping: +D368,CLOSED,Simplify F10 click targets:,CLOSED (2026-09-28 bookkeeping: D369,FIXED,Remove F10 row boxes/strips entirely (2026-09-27).,FIXED (awaiting live acceptance): D370,CLOSED,F10 distance/volume drags hurt frame rate (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest. D371,FIXED,"GEPD key-layout preset + reload/crouch bindings, v0.4.0 modern options wave M3 (2026-09-27).",FIXED (headless-verified; awaiting user live accept): @@ -253,13 +253,13 @@ D373,FIXED,"In-game crosshair on/off + colour (v0.4.0 modern options wave M2, 20 D374,FIXED,"GEPD becomes the default key layout; preset layering fix (v0.4.0 M3 playtest feedback, 2026-09-27).",FIXED (headless + user live-tested): D375,CLOSED,Crouch wired to the wrong pad bit:,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest. D376,CLOSED,Crouch follow-up + modern-options spot check (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest. -D377,FIXED,GEPD independent crouch (2026-09-27).,FIXED (scripted stance verified; user says live crouch was good; pad/hold-vs-toggle specifics owed): -D378,FIXED,GEPD use/reload separation (2026-09-27).,FIXED (headless edges tested; user says E/R seemed good; targeted door/partial-mag cases owed): -D379,FIXED,Crosshair colour: authored texture is red (2026-09-27).,FIXED (cyan frame capture; live yellow/blue recheck owed): +D377,FIXED,GEPD independent crouch (2026-09-27).,FIXED (2026-09-28 bookkeeping: +D378,FIXED,GEPD use/reload separation (2026-09-27).,FIXED (2026-09-28 bookkeeping: +D379,FIXED,Crosshair colour: authored texture is red (2026-09-27).,FIXED (2026-09-28 bookkeeping: D380,FIXED,One PC keyboard layout; PD-style rebind design (2026-09-27).,FIXED (follow-up D383): D381,CLOSED,"RULE-2-SIGNOFF: opt-in RGB, size and Beta crosshair (2026-09-27).",CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest. D382,CLOSED,Crosshair menu placement and slider hitches (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest. -D383,PARTIAL,PD-style keyboard rebinding in both options UIs (2026-09-27).,PARTIAL (2026-09-28). +D383,CLOSED,PD-style keyboard rebinding in both options UIs (2026-09-27).,CLOSED (2026-09-28 bookkeeping: D384,CLOSED,Front PC Options key-bind navigation feedback + two-slot UI (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest. D385,CLOSED,Real mouse 1–5 action rebinding in the two-slot editor (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest. D386,CLOSED,Correct Aim default from SDL middle (Mouse 2) to right (Mouse 3) (2026-09-27).,CLOSED (2026-09-28). Shipped in v0.4.0; accepted in the user's final review / campaign playtest. @@ -274,14 +274,26 @@ D394,PARTIAL,Xbox 1.1 Jinx controller parity —,PARTIAL (2026-09-28). D395,RESOLVED,Controller trapped in keyboard/mouse-only binding capture —,"RESOLVED (v0.4.0 campaign sign-off 2026-09-28, user:" D396,RESOLVED,"F10 pad B/A used as slider decrement/increment, not back/select —","RESOLVED (v0.4.0 campaign sign-off 2026-09-28, user:" D397,RESOLVED,SELECT FILE background + intro gun-barrel show an interlaced/comb look —,RESOLVED (2026-09-28): -D398,FIXED,PC Options label: D348's procedural slider glyph replaced with an existing mainfolderimages texture (2026-09-27).,"SUPERSEDED by D400 (icon removed, text-only label; purple-hover bug moot; the D400 textMeasure arg-swap bug was found and fixed there)." -D399,PARTIAL,File-select bottom row (SELECT FILE text + Copy/Erase + PC Options dot/label) shifted 15px left as a block (user sign-off 2026-09-27).,"LANDED (port-only, D343 class):" -D400,PARTIAL,"File-select ""PC Options"" label:","LANDED (port-only, frontoptions.c):" +D398,CLOSED,PC Options label: D348's procedural slider glyph replaced with an existing mainfolderimages texture (2026-09-27).,"SUPERSEDED by D400 (icon removed, text-only label; purple-hover bug moot; the D400 textMeasure arg-swap bug was found and fixed there)." +D399,CLOSED,File-select bottom row (SELECT FILE text + Copy/Erase + PC Options dot/label) shifted 15px left as a block (user sign-off 2026-09-27).,CLOSED (2026-09-28 bookkeeping: +D400,CLOSED,"File-select ""PC Options"" label:",CLOSED (2026-09-28 bookkeeping: D401,CLOSED,Rumble Pak → real gamepad haptics (re-promotes D224/M-87):,CLOSED (2026-09-28). Rumble shipped in v0.4.0 (D401); Steam Deck real-pad validation: D402,RESOLVED,First clean Linux (glibc / SteamOS) build of release/v0.4.0 broke in two port-layer include-resolution spots MinGW tolerated (bondconstants.h first-include cyc…,RESOLVED (Linux/Ubuntu-24.04 build → glibc-2.41-compatible binary for the Steam Deck): D403,RESOLVED,Deck v0.4.0-pre test:,"RESOLVED (2026-09-27, user-confirmed on the Deck:" D404,RESOLVED,"""Aim style: Centred (PC)"" (Input.AimMode=1, D333/D337) has no effect when aiming with a gamepad; the row's own doc comment over-promises (""applies to every aim…","RESOLVED (2026-09-28, port-layer only):" D405,CLOSED,"fast3d/OpenGL: MSAA above the driver max black-screens; two glBindFramebuffer calls pass an index where a GL name is required (PR #106, italoarruda, 2026-09-25…","CLOSED (2026-09-28). Merged from PR #106 (code file only; security-reviewed clean - port-layer GL state fixes, no external input, no allocations)." D406,RESOLVED,front-end PC Options screen:,"RESOLVED (2026-09-28, port-layer only, Steam Deck-validated):" -D407,PARTIAL,Tanks cannot be boarded/exited on PC —,"PARTIAL (fix landed in v0.4.0, port-only):" +D407,CLOSED,Tanks cannot be boarded/exited on PC —,CLOSED (2026-09-28 bookkeeping: D408,OPEN,Game.SkipIntro skips the post-mission failure dossier:,"OPEN (cosmetic, not root-caused past the handoff site)." +D415,FIXED,"Shooting a light fixture does not ""kill"" its flickering light (Bunker, Caverns):",FIXED (2026-09-29 bookkeeping: +D424,FIXED,WeaponStats.RecoilSpeed/b44[] byte order:,"FIXED in tree (WS_B44, port-guarded); user-verified vs GEPD/1964 2026-09-29." +D425,FIXED,Tank run-over death sound loops:,FIXED (2026-09-29 bookkeeping: +D426,RESOLVED,Tank cannot be steered with the mouse out of aim mode (issue #116).,RESOLVED (user 2026-09-29: +D427,FIXED,"Automatic fire cadence is frame-counted, ~2-3x faster than N64 at 60 fps (issue #114).","FIXED in tree (modulus scaled by ticks/frame; measured 6 ticks/shot at 30 and 60 cap); user-verified vs GEPD/1964 2026-09-29 (AK47 mag empties in ~3 s, Klobb normal)." +D428,FIXED,No gun turn-sway with direct mouse look (issue #117).,FIXED in tree (2nd pass: +D429,CLOSED,Tank full pass: aim-mode (RMB) mouse behaviour and tank sticking forward/back (follow-up to D426).,CLOSED faithful (user 2026-09-29: +D430,FIXED,Shot light fixtures do not go dark:,FIXED in tree (D154-style words.w0/.w1 decode); user-verified vs GEPD/1964 on Caverns 2026-09-29. +D431,FIXED,Light fixtures never reached lightFixtureBreak on PC:,FIXED in tree (fixture table records the image id; bg.c hit code looks it up; free-slot guard); user-verified vs GEPD/1964 2026-09-29. +D432,FIXED,"Gunshot sounds ""off"" after D427:",FIXED in tree (gate offset rounded to odd at 1 tick/frame); measured shot/sound cadence identical to N64 rules; residual audible difference vs 1964 tracked as D433. +D433,OPEN,"Slight audible difference in automatic-gun shot sound vs GEPD/1964 after cadence was matched (user by-ear, Caverns).",OPEN (not a timing issue; audio layer suspected). +D434,FIXED,Raw ((u8*)&g_Textures[n])[0] & 0xf hit-type reads are wrong on PC; latent OOB once D431 returned real texture ids (found by sub-agent review).,"FIXED (.hitTexture at 3 sites, port-guarded); live check owed." diff --git a/docs/dev/findings.md b/docs/dev/findings.md index f23ee5ee..d6f6dcdc 100644 --- a/docs/dev/findings.md +++ b/docs/dev/findings.md @@ -397,7 +397,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D161 | Depot (`-level_30`) ceiling = bright-blue speckle + radial rays (B2). A CI8 tile drawn with `gsDPSetTextureLUT(G_TT_NONE)` was decoded against the stale `rdp.palette` → garbage. Fix: `#ifdef`-free narrow route CI→I when `palette_fmt == G_TT_NONE` in `gfx_pc.cpp import_texture()`. | FIXED (`port/fast3d/gfx_pc.cpp`) | | D165 · D166 | **Input-layer polish (M-31, port-only)**: cursor P-controller + hipfire C-pulse. — full `## D165` / `## D166` entries at file tail | FIXED | | D180 · D181 | **Native-PC input pass (QoL run)**: click-to-lock capture + ScreenShakeIntensity hook. — full `## D180` / `## D181` entries at file tail | FIXED (2026-09-26 bookkeeping: shipped; user reports it feels good in v0.4.0 play). Earlier: LANDED, config-gated, feel-checks owed | -| D186 | **`Video.FpsCap` throttles the sim, not just presentation (M-39 diag, M-42 clamp).** The frame-pacing `sysSleep`+busy-wait in `sync_framerate_with_timer` (`port/fast3d/gfx_sdl2.cpp`) runs inline on the scheduler thread (`osSpTaskStartGo`→`gfx_run`→`swap_buffers_begin`), so a low cap blocks VI-retrace delivery and drags every game thread down to the cap rate (no N64 analogue — RSP/RDP are separate silicon). **Landed:** `0 < FpsCap < 30` → treated as `0` (uncapped) + warning, at both `videoInit` (normalises a bad `ge007.ini` for the next `configSave`) and `gfx_sdl_set_target_fps` (covers the F10-overlay live path); caps ≥ 30 unchanged. Verified: default `FpsCap=0` framediff 3/3 golden-identical; `FpsCap=10` now runs at the uncapped rate; `FpsCap=60` still paces. **Owed:** move pacing off the scheduler thread so any cap only drops presented frames. See §F "D186" + porting-notes.md §E. | OPEN (2026-09-26, user direction: target an "original" framerate/timing mode, with 60 fps + options on top; pacing must stop throttling the sim). Earlier: CLAMP LANDED (`port/fast3d/gfx_sdl2.cpp`, `port/src/video.c`); real fix owed | +| D186 | **`Video.FpsCap` throttles the sim, not just presentation (M-39 diag, M-42 clamp).** The frame-pacing `sysSleep`+busy-wait in `sync_framerate_with_timer` (`port/fast3d/gfx_sdl2.cpp`) runs inline on the scheduler thread (`osSpTaskStartGo`→`gfx_run`→`swap_buffers_begin`), so a low cap blocks VI-retrace delivery and drags every game thread down to the cap rate (no N64 analogue — RSP/RDP are separate silicon). **Landed:** `0 < FpsCap < 30` → treated as `0` (uncapped) + warning, at both `videoInit` (normalises a bad `ge007.ini` for the next `configSave`) and `gfx_sdl_set_target_fps` (covers the F10-overlay live path); caps ≥ 30 unchanged. Verified: default `FpsCap=0` framediff 3/3 golden-identical; `FpsCap=10` now runs at the uncapped rate; `FpsCap=60` still paces. **Owed:** move pacing off the scheduler thread so any cap only drops presented frames. See §F "D186" + porting-notes.md §E. | **CLOSED (2026-09-28 bookkeeping: the user-visible symptom is guarded — caps below 30 are forced to uncapped at load and on set, the menus expose only 30/60, and v0.4.0 holds 60 fps. Moving frame pacing off the scheduler thread and an "original timing" mode are roadmap feature work, not an open bug).** Earlier: OPEN (2026-09-26, user direction: target an "original" framerate/timing mode, with 60 fps + options on top; pacing must stop throttling the sim). Earlier: CLAMP LANDED (`port/fast3d/gfx_sdl2.cpp`, `port/src/video.c`); real fix owed | | D187 | **HUD ammo-type/weapon icon vanished at `-O2` (M-46 root-cause, M-47 fix + verify).** — full `## D187` entry at file tail | FIXED (`src/game/gunfire.c`, `#ifdef AVOID_UB`); PR #19 | | D188 | **Linux `-O2` boot crash: `_Printf` passes `&args` of a by-value `va_list` param (M-48).** — full `## D188` entry at file tail | FIXED (`src/libultrare/libc/xprintf.c`, `src/sprintf.c`, `#ifdef PORT`); PR #22 | | D189 | **Linux `-fstack-protector` aborts on the decomp's latent stack-buffer overruns (M-48) — FIXED.** — full `## D189` entry at file tail | FIXED (`CMakeLists.txt`, `src/game/stan.c` `#ifdef PORT`); PR #23 | @@ -415,7 +415,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D221 | **SELECT FILE mouse hit-box offset — compiler stack-layout dependency, not coordinate mismatch (M-87 QA / M-91 fix).** — full `## D221` entry at file tail | **FIXED (M-91)** — explicit `{min,max}` `coord2d` pairs under `#ifdef PORT`; human mouse-playtest confirmed. Distinct from D192 (mode-0 clamp range, still open). | | D222 | **High `Video.FovScale` breaks culling — NPCs and some world objects stop rendering near the screen edges (M-87, user QA).** — full `## D222` entry at file tail | **FIXED, awaiting visual verification (M-121).** Root cause traced one step further than the original write-up… | | D223 | **QoL ask: mouse-wheel weapon cycling should be directional like a normal PC shooter — wheel up = next weapon, wheel down = previous — instead of both directions…** — full `## D223` entry at file tail | **FIXED (M-105, `port/src/input.c`) —… | -| D224 | **QoL gap (M-87, from a PD-PC-port survey): the N64 Rumble Pak is fully dropped instead of modernized.** — full `## D224` entry at file tail | **LANDED via D401 (2026-09-27, re-promoted from DEPRIORITIZED).** Rumble Pak now drives real SDL gamepad haptics (`SDL_GameControllerRumble`) via the port-layer `inputRumble*` API + `CONT_CARD_ON`/`PFS_ERR_DEVICE` gating; single global `Input.RumbleScale` (0..1) config + one `Rumble strength` slider row. Real-pad playtest **done 2026-09-28 (Steam Deck: rumble works and is validated; user: include in v0.4.0)**. Gamepad-only —… | +| D224 | **QoL gap (M-87, from a PD-PC-port survey): the N64 Rumble Pak is fully dropped instead of modernized.** — full `## D224` entry at file tail | **CLOSED (2026-09-28 bookkeeping: rumble landed via D401 and shipped in v0.4.0).** Earlier: **LANDED via D401 (2026-09-27, re-promoted from DEPRIORITIZED).** Rumble Pak now drives real SDL gamepad haptics (`SDL_GameControllerRumble`) via the port-layer `inputRumble*` API + `CONT_CARD_ON`/`PFS_ERR_DEVICE` gating; single global `Input.RumbleScale` (0..1) config + one `Rumble strength` slider row. Real-pad playtest **done 2026-09-28 (Steam Deck: rumble works and is validated; user: include in v0.4.0)**. Gamepad-only —… | | D225 | **Dev-tooling QoL (M-87, user ask): a debug-build/env toggle that unlocks all missions + all cheats, for testing.** — full `## D225` entry at file tail | **FIXED (2026-09-18)** — `GE_DEBUG_UNLOCKALL=1` short-circuits `fileGetIsCheatUnlocked()` (`src/game/file2.c`, `#ifdef PORT`, cached getenv) to always return TRUE; default off, no save mutation. Build-verified; headless boot with `GE_STARTMENU=7` (mission select) crash-free. | | D226 | **QoL ask: player-adjustable HUD scale for the ammo counter, bottom-left pickup/item status text, and top-of-screen dialogue/subtitle text.** — full `## D226` entry at file tail | **FIXED (2026-09-24)**: Game.HudScale 75–150% (user-verified, capped at 150) via fast3d G_HUDSCALE_EXT rect scaling about per-element anchors; dialogue/bottom messages width-capped. | | D227 | **Sky renders as two independently-moving halves — "two sky processes" (M-93 QA; root cause M-100b: unit error, `unk20`/`unk24` are S10.5 not texels).** — full `## D227` entries (M-94→M-100b) at file tail | **FIXED (M-100b, PR #43)** — also fixed "scrolls too fast" (M-96) + over-tiling; human play-test confirmed. Full history in the sections. | @@ -431,7 +431,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D201 | **`src/libultra/audio/bnkf.c` bank/instrument/sound/wavetable relocation offsets were `s32`, truncating the real 64-bit base pointer.** — full `## D201` entry at file tail | FIXED (`src/libultra/audio/bnkf.c`) — part of the Phase-3 mixer landing, not yet build/runtime-verified in isolation | | D202 | **Silenced PPK "slap" + broader real-time mixer corruption (M-52→M-67).** — full `## D202` entry at file tail | FIXED (2026-09-26, user by-ear: silenced PPK sounds good, no slap or lingering loop; M-66b expiry + later audio fixes). Earlier: OPEN — by-ear check owed (2026-09-26): fire the silenced PPK a lot, listen for a slap/crackle or a lingering loop. Earlier: ROOT CAUSE ESTABLISHED (M-66); Disposition C implemented + measured (M-66b) — stuck loop is sound 203, a faithful N64 quirk; PC-side expiration fades it. M-67 static analysis exhausted. Full trail in the section. | | D206 | **PC played every SFX one bank slot too high — pointer-width layout shift in `struct ALInstrumentAlt_s` (M-76).** Root cause of D205 (3)/(4) + D202's "slap". — full `## D206` entry at file tail | **FIXED (M-76, `src/snd.c:1001`, `#ifdef PORT`)** — ABI/layout-only (D3x class); user by-ear A/B vs N64 = PASS (M-77c) → D202 closed. | -| D207 | **Alarm klaxon starves combat SFX and doesn't recover after it stops (surfaced by D206, M-77).** — full `## D207` entry at file tail | MONITORING — user 2026-09-26: "fixed possibly"; M-201 live captures showed no drops (see D241). Earlier: MONITORING (M-77c: user reports the alarm is acceptable in play after D206; no fix shipped) — voice-starvation hypothesis did NOT hold up (M-77b); designed pass reverted. | +| D207 | **Alarm klaxon starves combat SFX and doesn't recover after it stops (surfaced by D206, M-77).** — full `## D207` entry at file tail | **CLOSED (2026-09-28 bookkeeping: no drops in the M-201 live captures; sibling D241 closed as the D207 family in the v0.4.0 campaign sign-off. Re-open with a capture if drop-outs recur).** Earlier: MONITORING — user 2026-09-26: "fixed possibly"; M-201 live captures showed no drops (see D241). Earlier: MONITORING (M-77c: user reports the alarm is acceptable in play after D206; no fix shipped) — voice-starvation hypothesis did NOT hold up (M-77b); designed pass reverted. | | D208 | **Player weapon-fire sound goes permanently silent mid-firefight (all other SFX unaffected) — `struct` field `bondview.h:200 s32 field_A48` is used as a second…** — full `## D208` entry at file tail | FIXED + user playtest-verified (M-78d) — Bunker firefight: player gunshots persist through the whole engagement and survive the klaxon stopping. Committed 7601b9db (PR #29, merged). | | D209 | **Every AI character in the game is locked to a *walk* animation regardless of the speed the ailist commands — `act_ubytes.padding[45]`, a raw-byte alias into…** — full `## D209` entry at file tail | **FIXED + user-playtest-verified (M-81).** `chraction.c:3665`, `#ifdef PORT` → `get_sound_at_range(self, self->act_gopos.unk59, ...)`; the N64 line kept verbatim under `#else`.… | | D210 | **`chrToPatrol()` leaves `act_patrol.lastvisible60` uninitialised on PC — a raw-byte union alias (`act_init.padding[0x13]`) that shifts when `act_patrol` widens…** — full `## D210` entry at file tail | **FIXED (`chraction.c:3872`, `#ifdef PORT` → `self->act_patrol.lastvisible60 = -1;`, N64 line under `#else`).… | @@ -442,12 +442,12 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D169 | **Front-end mouse pointer can't reach the outer cells of the mission/level-select grid — only an inner ~3×3 selectable (M-33, developer bug report).** — full `## D169` entry at file tail | **FIXED (M-33, `port/src/input.c`, port-only, no `#ifdef PORT`).** The `menuMode` pointer branch now derives its clamp bounds from the live virtual screen —… | | D178 | **Pre-mission briefing screen: objectives blank / missing, briefing pages blank (also the D143 "briefing text blank" side effect) — FIXED (M-36).** — full `## D178` entry at file tail | **FIXED (M-36)** — high confidence | | D175 | **In-game stutter / brief hang during normal play** — full `## D175` entry at file tail | CLOSED (v0.4.0 campaign sign-off 2026-09-28, user: no stutter over the full campaign -- fixed). Earlier: OPEN (user, 2026-09-26: still happens occasionally; keep). Earlier: OPEN — observed, not investigated. **Steam Deck (Linux, 2026-09-27/28): occasional frame-timing hitches at low CPU/GPU (~15–30 %) — a frame-pacing/present profile, not a throughput bottleneck (see entry).** | -| D176 | **Surface exterior renders wrong (`-level_36`), two independent defects.** (a) black sky = RDPHALF no-op, root-caused M-37; scroll-speed FIXED (perspective-correct tc in `skyPortRenderPoly`). (b) cliff/rock walls = grey diagonal static — NOT a decode bug (D183: 0/166 loads strided); re-scope from ROM ground truth of the wall texnum (M-37 inconclusive, leaning tiling-density). — full `## D176(a)` entries at file tail; see also GRAPHICS-BACKLOG.md D176 | **(a) scroll-speed FIXED** (fidelity pass; HIGH confidence on root cause, MODERATE on N64 parity); M-93 playtest + M-98 Defect-2 starburst screenshot in the sections. **(b) open.** | +| D176 | **Surface exterior renders wrong (`-level_36`), two independent defects.** (a) black sky = RDPHALF no-op, root-caused M-37; scroll-speed FIXED (perspective-correct tc in `skyPortRenderPoly`). (b) cliff/rock walls = grey diagonal static — NOT a decode bug (D183: 0/166 loads strided); re-scope from ROM ground truth of the wall texnum (M-37 inconclusive, leaning tiling-density). — full `## D176(a)` entries at file tail; see also GRAPHICS-BACKLOG.md D176 | FIXED (2026-09-29 bookkeeping: (a) sky via D227, human play-test confirmed; (b) Surface perimeter tree/rock wall via D236, closed M-198; GRAPHICS-BACKLOG row closed 2026-09-28). Earlier: **(a) scroll-speed FIXED** (fidelity pass; HIGH confidence on root cause, MODERATE on N64 parity); M-93 playtest + M-98 Defect-2 starburst screenshot in the sections. **(b) open.** | | D177 | **Ladders non-functional — progression blocker (user QA) — FIXED (M-36): `count`/`rooms` land in the high half of a widened pointer.** — full `## D177` entry at file tail | **FIXED (M-36)** — high confidence; interactive climb test owed | | D172 | **Bullet-impact / blood / spark particles render magenta or cyan instead of dark red (M-82, ROOT CAUSE FOUND + FIXED).** — full `## D172` entry at file tail | **FIXED (`port/fast3d/gfx_pc.cpp` `gfx_lod_tile_offset`)… | | D174 | **"No blood effect" (user QA report).** — full `## D174` entry at file tail | CLOSED — faithful (2026-09-26, user side-by-side on 1964: blood matches N64). Earlier: Blood renders (user, 2026-09-26); OPEN only for an N64-accuracy comparison on 1964. Earlier: OPEN — observed; first step = BUNKER1 firefight with regenerated sidecars | | D183 | **M-36 Family A ("texture line/pitch shear") is DISPROVEN for the Surface repro (`-level_36`).** The importers' pitch assumption is real but never fires there; the Surface cliff "grey static" is not a shear. Full evidence + what the walls actually are: §F "D183" below. A defensive, provably-no-op de-stride landed in `import_texture()` anyway (covers the `gfx_dp_load_tile` case the `SUPPORT_CHECK`s assert against), plus `GE_DTEX` STRIDED marker + `GE_TEXRAW` raw-source dump. | CLOSED (2026-09-26, user-approved: disproven hypothesis; the Surface sky issue it served was fixed under D176). Earlier: Hypothesis DISPROVEN + diagnostics shipped; D176(b)/D182(2) still OPEN | -| D173 | **Third-person Bond model spawns too high** — full `## D173` entry at file tail | **MOSTLY FIXED as a side effect of D243 (2026-09-18, user-confirmed).** User: "9/10 times he's in the right place now, if he's off it's only by a small amount, no more floating in midair or glitching out spinning like before." The M-160/M-165 shared-mechanism hypothesis (D243's animation-state/position desync class) holds for this bug too, confirmed by outcome even though the exact code path was never separately traced for D173 specifically. Small positional-offset residual remains, not investigated further — no more full floating/spin. | +| D173 | **Third-person Bond model spawns too high** — full `## D173` entry at file tail | PARTIAL (2026-09-29 bookkeeping: mostly fixed as a side effect of D243, user-confirmed; a small ~1-in-10 offset residual remains, tracked in docs/ROADMAP.md §2). Earlier: **MOSTLY FIXED as a side effect of D243 (2026-09-18, user-confirmed).** User: "9/10 times he's in the right place now, if he's off it's only by a small amount, no more floating in midair or glitching out spinning like before." The M-160/M-165 shared-mechanism hypothesis (D243's animation-state/position desync class) holds for this bug too, confirmed by outcome even though the exact code path was never separately traced for D173 specifically. Small positional-offset residual remains, not investigated further — no more full floating/spin. | | D179 | **Packaged build crashes after the logos — the D43/D69 model + bg sidecars are ROM-derived and absent from any build that has no ROM (M-34, alpha-release QA).** — full `## D179` entry at file tail | Diagnosed, not fixed — release packaging gap + latent `romdata.c` fallback | | D180 | **Native-PC input pass (M-XX QoL run, `qol/native-pc-input-menu`, port-only).** — full `## D180` entry at file tail | FIXED (2026-09-26 bookkeeping: shipped; user reports it feels good in v0.4.0 play). Earlier: LANDED, port-only, no `#ifdef PORT`.… | | D181 | **`Game.ScreenShakeIntensity` — first route-(b) `src/` gameplay-cosmetic hook (M-XX QoL run).** — full `## D181` entry at file tail | FIXED (2026-09-26 bookkeeping: shipped; user reports it feels good in v0.4.0 play). Earlier: **Menu row pulled (v0.2.1, user testing).** The hook is correct on its own narrow terms —… | @@ -459,7 +459,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D215 | **1P weapon viewmodel never drew in-level — `gunRenderFirstPersonGunModels` builds its `ModelRenderData` from a reinterpret across separately-declared adjacent…** — full `## D215` entry at file tail | **FIXED (`src/game/gunfire.c`, `#ifdef PORT`).** Build clean.… | | D216 | **`Game.SkipIntro` — boot straight to the file-select menu (M-83, port + one `#ifdef PORT` game-code line, PD parity, Phase 4 quick win).** — full `## D216` entry at file tail | FIXED (2026-09-26 bookkeeping: shipped; user reports it feels good in v0.4.0 play). Earlier: **LANDED + VERIFIED this session.** Build clean.… | | D217 | **1P weapon viewmodel + 3P Bond model textures garbled — wrong/stale palette on model-DL CI textures (M-84).** Two defects: ① texpool arena misalignment (the green), ② CI texture-cache collision. — full `## D217` entries (M-86→M-97) at file tail | **FIXED + merged** (PR #47 + PR #49); white residual split out as D228. | -| D218 | **Wide FOV exposes the per-level draw-distance / fog boundary — "blue artifacting" down long peripheral sightlines (M-84, user QA on the D211 FOV slider).** — full `## D218` entry at file tail | FIXED (2026-09-26 reclassified: the proposed fix shipped as Video.DrawDistance + DrawDistanceAutoFov, default 150). Earlier: **Logged (M-84), fix proposed + parked.** Plan… | +| D218 | **Wide FOV exposes the per-level draw-distance / fog boundary — "blue artifacting" down long peripheral sightlines (M-84, user QA on the D211 FOV slider).** — full `## D218` entry at file tail | FIXED (2026-09-26 reclassified; 2026-09-29 default corrected: the proposed fix shipped as Video.DrawDistance + DrawDistanceAutoFov, default 250 — `port/src/video.c:68`). The broader LOD/detail-distance ask is a separate feature in docs/ROADMAP.md §5. Earlier: FIXED (2026-09-26 reclassified: the proposed fix shipped as Video.DrawDistance + DrawDistanceAutoFov, default 150). Earlier: **Logged (M-84), fix proposed + parked.** Plan… | | D219 | **PPK explosions render purple on Dam (M-87, user QA, "not 3D but noting").** — full `## D219` entry at file tail | **RESOLVED (M-115, 2026-09-12)** for the original PPK-purple symptom. **The adjacent bullet-spark-rainbow investigation logged under this label (M-157/M-158) is also FIXED (2026-09-18 session)** — `glass2.c`'s `bullet_spark_render` read the spark's RGBA colour via a hardcoded raw byte offset instead of the named `s_bullet_spark` struct fields, landing in the wrong memory on 64-bit PC; fixed by reading the named fields. User live-confirmed. D252's duplicate "lingering particle residue" report closes as the same bug (M-188). | **M-111 (2026-09-12, live session, 6 build/relaunch cycles): G_LIGHTING lead RULED OUT; texture-cache-collision lead RULED OUT; ROOT CAUSE FOUND — R/B channel swap in the decoded fire particle texture data itself.** Live-captured 4 rounds against a real user playtest (not scripted) with the user confirming explosions were still blue/purple in every capture: @@ -478,7 +478,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D86 · D87 | **modelInitRwData truncated ptr · attract-demo BE `ramromfilestructure`.** — see §H (D86, D87) | resolved | | D88.1–D88.3 · D88.5–D88.6 | `Usetup*Z` header + sub-table width/endian conversion (`d88_emit.py`) | resolved | | D88.4 | **`propDefs` polymorphic record stream not byteswapped → `setupDoor` crash.** — see §H (D88.4) + `## D132` | resolved (`d88_propdefs.py`); layout audit M-20 → **D132** | -| D132 | D88 propDefs layout audit (M-20): converter cursor confirmed vs real PC struct layout for all types the 21 levels emit. DOOR/OBJECT-prefix/VEHICHLE/AIRCRAFT/TANK/AUTOGUN/AMMO/TINTED_GLASS/objective sub-records all MATCH. **Divergence found:** types 14 LINK / 19 SWITCH / 38 LOCK_DOOR / 44 SAFE_ITEM — each has `s32 IndexN` fields sharing a union with a pointer (`LinkRecord.first`/`Index1`), so on PC the field sits in an 8-byte, 8-aligned union slot, but the converter emits it at N64 tight-4-byte-word offsets → `pdef->Index1` reads `Index2`'s value, `Index2` reads 0 → switch-doors / dual-weapons / locked-doors / safes silently fail their validity guard (non-crash; guard failure also prevents the under-sized-record `->next` overflow). | proposed fix (not applied) — see below | +| D132 | D88 propDefs layout audit (M-20): converter cursor confirmed vs real PC struct layout for all types the 21 levels emit. DOOR/OBJECT-prefix/VEHICHLE/AIRCRAFT/TANK/AUTOGUN/AMMO/TINTED_GLASS/objective sub-records all MATCH. **Divergence found:** types 14 LINK / 19 SWITCH / 38 LOCK_DOOR / 44 SAFE_ITEM — each has `s32 IndexN` fields sharing a union with a pointer (`LinkRecord.first`/`Index1`), so on PC the field sits in an 8-byte, 8-aligned union slot, but the converter emits it at N64 tight-4-byte-word offsets → `pdef->Index1` reads `Index2`'s value, `Index2` reads 0 → switch-doors / dual-weapons / locked-doors / safes silently fail their validity guard (non-crash; guard failure also prevents the under-sized-record `->next` overflow). | **CLOSED (2026-09-28 bookkeeping: fix applied — `tools_pc/d88_propdefs.py` carries the D132 union-slot layouts for types 14/19/38/44; post-fix verification recorded in the `## D132` entry).** Earlier: proposed fix (not applied) — see below | | D157 | **Campaign never unlocks the next level (M-30, user bug — "complete Dam on Agent, Facility stays locked, no save").** — full `## D157` entry at file tail | FIXED (`src/bondtypes.h`, `#ifdef PORT` —… | | D160 | **Dam level-end cutscene (Bond off the dam / bungee) never plays — cuts straight to the MISSION COMPLETE report (M-30/M-31, user bug D148).** — full `## D160` entry at file tail | FIXED (user, 2026-09-26: the Dam ending cutscene plays since v0.3.0). Earlier: DIAGNOSTIC SHIPPED, not root-caused (`c95713f5`, `#ifdef PORT` + `GE_D160=1`)… | | D161 | **Depot (`-level_30`) ceiling renders as bright-blue speckle + radial "light-ray" streaks converging to a point instead of a dark corrugated roof (M-30/M-31…** — full `## D161` entry at file tail | FIXED (`port/fast3d/gfx_pc.cpp` `import_texture`, `port/fast3d/gfx_opengl.cpp` diag). porting-notes.md §D. | @@ -487,13 +487,13 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D103–D107 | BUNKER1 viewport height, depth-buffer clear (`G_CLEAR_DEPTH_EXT`), portal near-plane, LOD mip tile | resolved | | D108–D112 | skeletal models: `d43_emit.py put_f32` byte-reversal bug | resolved | | D113–D116 | portal BFS ok · matrix chain ok · player raw-offset audit + `gunfire.c` THROW* · HUD text X-mirror | **D114/D116 CLOSED — NOT A BUG (M-33, see D168): the "X-mirror" was an upside-down `GE_PCDUMP` capture misread as mirrored. PPM writer fixed; the game renders correctly on hardware.** | -| D117 | **Frame nondeterminism root-caused; `GE_DETERM` attempted, not achieved.** — see §H (D117 M-8 root cause + M-52 addenda) | OPEN (mode attempted, not achieved) | +| D117 | **Frame nondeterminism root-caused; `GE_DETERM` attempted, not achieved.** — see §H (D117 M-8 root cause + M-52 addenda) | **CLOSED (2026-09-28 bookkeeping: won't-fix research item — the golden gate compares settled frames and no longer needs determinism; `GE_DETERM` left as-is).** Earlier: OPEN (mode attempted, not achieved) | | D118 | **SDL input layer; M-24 mouse-look rework (mode-aware map, real INI); D118b/c FIXED.** — see §H (D118 + M-24 entries) | resolved (D118a residual) | | D150 | watch OBJECTIVES / BRIEF page crash: `strcat(buf, langGet(id))` with `langGet` → NULL (unloaded bank) → NULL deref; `str.c` str* builtins elide a plain param NULL check, so guard via asm-laundered `GE_IS_NULL` | FIXED (`src/str.c` `#ifdef PORT`); interactive re-verify pending | | D260–D265 | v0.2.1 hotfix batch: D260 wheel direction swap (fixed) · D261 watch-menu nav auto-repeat (fixed, feel-check owed) · D262 watch item-preview model missing → root-caused to D264 (fixed at Gfx level, visual check pending) · D263 Facility Ourumov/Trevelyan beat — **resolved: not a bug**, proximity/LOS-triggered as on N64; full chain incl. execution shot verified firing on PC (teleport probe + user playtest) · D264 watch preview renderdata.flags zeroed by cross-global template read on PC (ABI/layout class; fixed) · D236/D265 Surface 1 tree "wall": IA16 importer dimensions transposed (fixed); decode attempt reverted after raw-byte proof that byte-pair is the authored image; root cause = `import_texture_ia4` decoding RDP I2:A2 as I3:A1, inverting the tree strip's alpha ramp → opaque repeating silhouettes; IA4 I3:A1→I2:A2 decode fix applied and user re-tested — **still a wall; reverted, parked** with evidence + suspect list in D265 | CLOSED (2026-09-26 bookkeeping: v0.2.1 hotfix batch shipped; D265 is a D236 sub-pass, closed M-198). Earlier: partial — see rows | | D266–D269 | D236/D265 wall, second pass: **the wall is CI8 32×32, not IA** (D269) — band-gated probe finds two draw classes per room stream (OPA oml=0xc4112078 + XLU oml=0xc41049d8), both BILERP/TEXTLOD/2-cycle/**FOG-on**; cards are soft gradient blobs, stripe period ≈ one card per visible room. D266: re-applied the reverted I2:A2 IA4 decode + replaced the hardcoded 0.19 edge quantize with AC-aware handling (census shows zero IA4 draws at boot). D267: IA8 dump infra — the three IA8 images are noise/mast sprites, not the wall. D268: force-alpha A/B proves the blend pipeline works. Ranked suspects now: fog range/application (D218 family) > TEXTLOD/mip sampling > draw density/culling > palette | CLOSED (2026-09-26 bookkeeping: sub-pass of D236, which closed in M-198). Earlier: OPEN (the wall itself) — probes live in tree (`GE_D266`/`GE_D266W`/`GE_D267`/`GE_D268_FORCEA`); the D266 decode + D271 portal-culling fixes **landed** (`532092c7`, M-139 R2 extraction) | | D270 | **Backfilled (M-132) — this label was never given its own row; only code comments and the HANDOFF archive documented it.** — full `## D270` entry at file tail | CLOSED (2026-09-26 bookkeeping: sub-pass of D236, which closed in M-198). Earlier: dead-ended into D271/D272; infrastructure kept in tree | -| D271 | **D236 wall, third pass — suspect #3 (draw density/culling) CONFIRMED as a real over-draw; the D106 guard was the cause (M-129).** — full `## D271` entry at file tail | FIXED-but-insufficient (`src/game/bg.c` `#ifdef PORT`) — **landed** `532092c7` (M-139 R2 extraction, probes stripped); wall still open, see D236 | +| D271 | **D236 wall, third pass — suspect #3 (draw density/culling) CONFIRMED as a real over-draw; the D106 guard was the cause (M-129).** — full `## D271` entry at file tail | CLOSED (2026-09-29 bookkeeping: superseded — the Surface 1 tree wall was fixed by D236, closed M-198; D271's portal-culling change stays landed). Earlier: FIXED-but-insufficient (`src/game/bg.c` `#ifdef PORT`) — **landed** `532092c7` (M-139 R2 extraction, probes stripped); wall still open, see D236 | | D276 | **D236 wall, eighth pass — the true painter of the visible weave is a fully-opaque IA8 blocky-noise texture, not either of D269's named CI8 classes (M-134).** — full `## D276` entry at file tail | CLOSED (2026-09-26 bookkeeping: sub-pass of D236, which closed in M-198). Earlier: 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`…** — full `## D279` entry at file tail | CLOSED (2026-09-26 bookkeeping: sub-pass of D236, which closed in M-198). Earlier: 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…** — full `## D281` entry at file tail | FIXED (2026-09-23, port/src/libultra.c): the AllUnlocked patch now reproduces the game's fresh-slot result (folder saves with DEFAULT_OPTIONS + one free slot) instead of five folder-1 slots with options 0 (no aim sight). Verified by fresh-eep SAVELOG; audio half **confirmed 2026-09-28 (campaign, user: good)**. v0.4.0 decision (user-delegated, 2026-09-28): keep the shipped mitigation + README disclosure; the zero-saves root cause (sndHandleEvent/aim interaction) is v0.5.0. | @@ -525,7 +525,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D150 | **Watch objective/briefing page crash (M-28).** — full `## D150` entry at file tail | FIXED (`src/str.c`, `#ifdef PORT`). Build green (`ntsc-final`); `-level_09` 600+ frames @ 91.67% unregressed. Watch-page repro is interactive — user to re-verify. porting-notes.md §C. | | D152 | **Mission-failed / objective-failed → permanent black screen (M-28, user bug report — LIVE process inspected, not yet fixed).** — full `## D152` entry at file tail | FIXED (user, 2026-09-26: no black screen after mission/objective failure). Earlier: MITIGATED (`port/src/libultra.c`, M-28, `#ifdef PORT`/port-only).… | | D152+ | **D152 addendum — static audit + narrow fix (M-31).** — full `## D152+` entry at file tail | FIXED (`src/snd.c`, `port/src/libultra.c`, `#ifdef PORT`). porting-notes.md §D4. | -| D154 | **`bg.c` room-geometry ray/hit-test GBI parser port (M-28, WRITTEN / UNVERIFIED / UNCOMMITTED).** — full `## D154` entry at file tail | **M-30 re-audit + fixes (still playtest-gated for final verify).** Cross-checked the ported G_TRI1 + all 12 G_TRI4 nibble recoveries twice against the N64 `#else` and the raw BE-word bit derivation —… | +| D154 | **`bg.c` room-geometry ray/hit-test GBI parser port (M-28, WRITTEN / UNVERIFIED / UNCOMMITTED).** — full `## D154` entry at file tail | **CLOSED (2026-09-28 bookkeeping: playtest gate satisfied — full v0.4.0 campaign played with no background-hit/raycast anomalies; ABI re-audited again under D313).** Earlier: **M-30 re-audit + fixes (still playtest-gated for final verify).** Cross-checked the ported G_TRI1 + all 12 G_TRI4 nibble recoveries twice against the N64 `#else` and the raw BE-word bit derivation —… | | D156 | **Facility outro-cutscene "hang" — the actual fix (M-29, user bug report, 2nd occurrence after D155).** — full `## D156` entry at file tail | FIXED (user, 2026-09-26: no outro hang seen). Earlier: GUARDED (`src/game/model.c`, `#ifdef PORT`)… | | D155 | **Facility outro-cutscene end → "hang" = unclamped `deltaFrames` spiral (M-29, user bug report).** — full `## D155` entry at file tail | FIXED (`src/game/frametiming.c`, `#ifdef PORT` —… | | D153 | **`-level_09` frame ~900–1400 `0xc0000005` = D117/D134 load flakiness, NOT a bug (M-28).** — full `## D153` entry at file tail | NOT A BUG — closed. Run anchors on a quiet machine. | @@ -552,7 +552,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D246 | **Tiny pixel strips at the left and right edges of the screen, "like overscan" — look like raw pixels, not 3D graphics (M-116, user QA report).** — full `## D246` entry at file tail | **FIXED (2026-09-18 session, port-only, `Video.SafeAreaCrop`).** Measured precisely (exactly 1.0 logical unit missing on both edges, every resolution); underlying clip-precision root cause not isolated, but `gfx_adjust_viewport_or_scissor` now trims a fixed 1-unit margin from both edges — same toggle/mechanism as D247. Live user playtest confirmed clean edges. | | D247 | **Black bars top and bottom of screen — possible aspect-ratio defect (M-116, user QA report).** — full `## D247` entry at file tail | **FIXED (2026-09-18 session, port-only, `Video.SafeAreaCrop`).** Root cause: faithful, unmodified N64 TV-safe-area letterboxing that real CRT overscan used to hide; this port showed the full VI frame. Per user request, given a real fix (not left as "correct original behaviour"): `gfx_adjust_viewport_or_scissor` now remaps against the active gameplay viewport bounds so it fills the window edge-to-edge. New `Video.SafeAreaCrop` toggle (default on). Live user playtest confirmed clean edges. | | D248 | **Port runs a stable 30 FPS instead of 60 FPS game-wide (M-117, user asked to verify FPS after the D209 timing-keystone fixes).** — full `## D248` entry at file tail | **FIXED, root-caused to a genuine pointer-width ABI bug in `src/sched.c` (compiled for the PC build, not excluded).** `osScAddClient()` (`src/sched.c:187-199`) stashes a per-client "present every retrace vs.… | -| D249 | **LOD/culling breaks down on large open levels (Streets, Egyptian named specifically) at *default* FOV — far-away geometry has visible culling issues (M-119…** — full `## D249` entry at file tail | OPEN — M-142 static review ruled out the unscaled-far-clip and LOD-multiplier suspects (both already correct); CLOSED (v0.4.0 campaign sign-off 2026-09-28, user: no distant-geometry drop-out seen on Streets/Egyptian over the full campaign -- fixed); narrowed to the portal/room-visibility BFS, needs a live screenshot+location, not more code reading.… | +| D249 | **LOD/culling breaks down on large open levels (Streets, Egyptian named specifically) at *default* FOV — far-away geometry has visible culling issues (M-119…** — full `## D249` entry at file tail | **CLOSED (2026-09-28 bookkeeping: the v0.4.0 campaign sign-off already recorded below closed it — no distant-geometry drop-out on Streets/Egyptian over the full campaign; lead status corrected).** Earlier: OPEN — M-142 static review ruled out the unscaled-far-clip and LOD-multiplier suspects (both already correct); CLOSED (v0.4.0 campaign sign-off 2026-09-28, user: no distant-geometry drop-out seen on Streets/Egyptian over the full campaign -- fixed); narrowed to the portal/room-visibility BFS, needs a live screenshot+location, not more code reading.… | | D250 | **FPS is inconsistent/low on many levels after 60 Hz shipped (M-119, user QA; D248 follow-on).** — full `## D250` entry at file tail | FIXED — per-call `getenv()` cost was the bottleneck; rock-steady 60-61 fps after, user re-tested 1440p/4K clean. | | D251 | ****F10 options overlay QoL pass:** — full `## D251` entry at file tail | **FIXED (port-only, `port/src/optionsoverlay.c`).** (a) The panel now computes how many rows fit the *actual* current 2D height (`maxVisibleRows()` from `viGetY()`) and scrolls… The last open symptom — the bottom row deterministically duplicating the selected item (v0.2.0-pre playtest) — is resolved by D304's `overlayUpdateScroll()` minimal-scroll rewrite; user-confirmed no longer occurring (2026-09-18). | | D252 | **Particle effects cycle through a rainbow palette (M-124, user playtest of the v0.2.0-pre candidate).** — full `## D252` entry at file tail | **FIXED (M-201, 2026-09-26, user-verified): port-side D68 shim `gimgSyncCompiledGlobalDLs` only synced the compiled explosion DLs on the first stage; every later stage kept the title stage's freed texture pointers. Earlier history: PARTIALLY FIXED — a real, distinct manifestation was found and fixed, but the bug family is not fully closed, and is intermittent on BOTH platforms, not Deck-specific (corrected 2026-09-18).** `explosionRenderPart()`'s 4-vs-5-vertex overrun (`src/game/explosion.c`) is fixed and stays in. Initial testing suggested "fixed on Windows, recurs on Deck," but further user testing found it also recurs intermittently on Windows (same Jungle repro, not always reproducing) and is level-dependent (same repro method doesn't trigger it on every level) — not a clean platform split. Repro method (`Game.AllUnlocked` + cheats for an immediate grenade launcher) is flagged as a possible, unconfirmed factor. The unconfirmed secondary lead from this session (stale TMEM-slot `src_fmt` CI8 reroute, `port/fast3d/gfx_pc.cpp` ~line 1250) remains the best-named candidate for the residual. Not re-investigated further this session. | @@ -580,7 +580,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D291 | **Dam: texture glitches when the truck drives past (M-141, Steam Deck user playtest).** — full `## D291` entry at file tail | CLOSED — duplicate of D306, faithful (M-201, 2026-09-26: user-captured, Dam intro camera 5 only). Earlier: OPEN — observed, not investigated. Needs a live repro with the truck's exact position/timing and, ideally, a screenshot. | | D292 | **Death animations are glitched — Bond is often sunk halfway into the floor (M-141, Steam Deck user playtest).** — full `## D292` entry at file tail | **MOSTLY FIXED as a side effect of D243 (2026-09-18, user-confirmed alongside D173) — same fix, same outcome: right 9/10 times, small residual offset only when off, no more full sinking/glitching.** See D173 for the user's exact confirmation wording; not separately re-tested, reported together. | | D293 | **QoL ask: no discoverable way to quit the game from the UI (M-141, Steam Deck user playtest — "couldn't see a way to exit the game. Maybe build this into the…** — full `## D293` entry at file tail | FIXED (M-141) — "Quit to desktop" row added to the F10 overlay; build-verified Win+Linux, not yet live-confirmed.… | -| D294 | **Statue: ground geometry near the large CCCP/hammer-and-sickle monument briefly renders solid black over a wide area, then self-recovers 10-30 seconds later…** — full `## D294` entry at file tail | **FIXED, user playtest confirmed (2026-09-25: not reproduced)** — root cause: room-model POOL exhaustion (authored `-ma220`=220 KB < ~250 KB monument-plaza demand at FovScale 130 + max DD/Lod); `memaAlloc` NULL → silent skip → undrawn room shows the night-sky clear colour. Stochastic (wall-clock seed → load order). Fix: `portRoomPoolScale()` (video.c, identity at N64-faithful settings) applied in `bossMainloop`; verified headless (faithful→exact authored size; user settings→0 failures; `GE_ROOMPOOL=0.25`→exhaustion on demand). Playtest: main patch gone; residual corner flicker traced to full-level residency (487 KB = 2.2× authored) starving the 2× pool; the 3×/linear fix REGRESSED the renderer on the menu-driven path (D156 NaN anim state, not a bank overflow) → reverted to sqrt + 2.0 cap (ma=450560). Residual open; probes in place for next capture. High-DD horizon edge on night levels = setting-inherent, documented only.… | +| D294 | **Statue: ground geometry near the large CCCP/hammer-and-sickle monument briefly renders solid black over a wide area, then self-recovers 10-30 seconds later…** — full `## D294` entry at file tail | PARTIAL (2026-09-29 bookkeeping: main symptom FIXED — room-model pool; user playtests 2026-09-22/25 no longer reproduce the black ground; the residual corner flicker at max FOV reported 2026-09-22 was never re-checked, tracked in docs/ROADMAP.md §2). Earlier: **FIXED, user playtest confirmed (2026-09-25: not reproduced)** — root cause: room-model POOL exhaustion (authored `-ma220`=220 KB < ~250 KB monument-plaza demand at FovScale 130 + max DD/Lod); `memaAlloc` NULL → silent skip → undrawn room shows the night-sky clear colour. Stochastic (wall-clock seed → load order). Fix: `portRoomPoolScale()` (video.c, identity at N64-faithful settings) applied in `bossMainloop`; verified headless (faithful→exact authored size; user settings→0 failures; `GE_ROOMPOOL=0.25`→exhaustion on demand). Playtest: main patch gone; residual corner flicker traced to full-level residency (487 KB = 2.2× authored) starving the 2× pool; the 3×/linear fix REGRESSED the renderer on the menu-driven path (D156 NaN anim state, not a bank overflow) → reverted to sqrt + 2.0 cap (ma=450560). Residual open; probes in place for next capture. High-DD horizon edge on night levels = setting-inherent, documented only.… | | D295 | **Campaign-halting crash at the end of Control Center on 00 Agent difficulty, then a crash on every subsequent relaunch/resume (GitHub #87, user campaign playtest).** — full `## D295` entry at file tail | FIXED (M-149) — M-148 root-caused + reproduced on HEAD; fix landed: port-side JPN-cache seed (`port/src/main.c`) + `#ifdef PORT` buffer widen in `front.c` (D8 class). Verified: previously-crashing input now clean, 21/21 sweep PASS. Companion save-wipe regression → D297. History — M-148 fully root-caused + **reproduced on current HEAD**: `difficultytext[4]` (`front.c:2616`) overflow — `strcpy("00 Agent")`/`strcat("\n")` spills onto `textpos.p[0]` (y, ≥0x80 in normal geometries) → high-bit byte → `langGetJpnCharPixels` NULL deref (`language.c:320`, `g_JpnCacheCacheItems` NULL outside JPN mode — likewise unallocated on N64 non-JP builds, crash potential is decomp-native). The user's save is a *valid pre-D284* save (all 5 slot CRCs bit-exact under the old PRNG); post-fix builds wipe it at validation, which is why the unmodified artifact no longer triggers it — NOT a fix. No OOB exists (M-147 guess retracted: 007 difficulty has no stored times). Fix options flagged for sign-off (port-side JPN-cache init preferred; `difficultytext` enlarge = D8 class). Side finding: v0.3.0 silently wipes all pre-fix saves on first launch (D284 side effect — own D entry owed). Separate still-open issue found: unconditional `[EVTQ-DROP]` audio-pool-exhaustion logging, `src/libultra/audio/event.c:92`.… | | D296 | **Control Center, 00 Agent: noticeable stutter, user's own read is "maybe from the drone guns" (M-146, same D295 investigation session).** — full `## D296` entry at file tail | CLOSED (2026-09-26, user-approved: stutter seen only under gdb (debug.ps1); D302, fixed two days later, removed a per-triangle uncached getenv costing ~15 fps; reopen if seen on a normal build). Earlier: OPEN — single live report, un-triaged; observed only under a gdb-attached debug-harness launch (`tools_pc/debug.ps1`), not the packaged release artifact — user flagged that harness overhead has caused false stutter before. Needs a plain-exe / real-release repro check before any perf tracing.… | | D297 | **v0.3.0 silently wipes ALL pre-fix player saves on first launch (D284 side effect; found during D295/M-148).** — full `## D297` entry at file tail | FIXED (M-150) — migration shim landed: `port/src/legacycrc.c` (isolated, deliberately-wrong pre-D284 PRNG, reviewer-verified bit-exact against `0de99b4d^`) + eeprom-shim read-path hook in `libultra.c` (block 4 only; persists pristine+migrated bytes before the read-time-only AllUnlocked patch). Verified: user's #87 artifact → `D297-migrated 5`, all slots OK, idempotent on relaunch; native post-fix save untouched; corrupt slot left to the game's own wipe path; fresh eep untouched; 21/21 sweep PASS. Block-0 header needed no migration (factory seal, verified). | @@ -633,7 +633,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D345 | **Front-end crosshair snaps away from the pointer on screen/page changes (file select → mode select `setCursorPOSforMode(0)`, front.c:2516; every `MENU_PC_OPTIONS` page hop), and the options screen's keyboard navigation was missing/mis-modelled (2026-09-27).** — full `## D345` entry at file tail | Port-only, user-verified (parts a–g): (a) `input.c` re-assert window on menu change; (b) `cursorToItem` skips its snap while the pointer is live; (d) W/S emit stick in menus under NaturalPitch; (e/f) F10-style discrete row stepping + arrow left/right value adjust replace raw drift (first pass shipped broken, fixed); (g) screen-placed cursor parks in the left gutter, off the row text. Game-side one-liner deferred: physical-controller pass was good, no Rule-2 sign-off sought. | | D346 | **PC-settings wording pass: option labels/values aligned to Nightdive/Turok + PD-port conventions (title-case On/Off, "Frame rate cap", "Anti-aliasing"/"None", units in values not labels, self-explanatory auto-FOV toggles); VIEW section merged into VIDEO (2026-09-27).** — full `## D346` entry at file tail | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. | | D347 | **F10 overlay controller: left/right did nothing (only A/Y adjusted values) and no input had hold-to-repeat (2026-09-27).** — full `## D347` entry at file tail | FIXED (port-only, `optionsoverlay.c`): D-pad left/right + stick-X wired into value adjust (A/X/B/Y kept); 18/4-frame hold-to-repeat on adjust and up/down nav, matching the options screen's D345(e)/(f) cadence. User validated physical-controller pass. | -| D348 | **File-select "PC Options" label: no settings icon exists in the game art (mainfolderimages = copy/del/select-arrow/X/check/dot only) -- a mini slider glyph is drawn instead (2026-09-27).** — full `## D348` entry at file tail | SUPERSEDED by D398 (glyph replaced with the IMG_DOT texture; no new art). PCDUMP pixel-verified when shipped (135 knob px vs 9 paper). | +| D348 | **File-select "PC Options" label: no settings icon exists in the game art (mainfolderimages = copy/del/select-arrow/X/check/dot only) -- a mini slider glyph is drawn instead (2026-09-27).** — full `## D348` entry at file tail | **CLOSED (2026-09-28 bookkeeping: superseded by D398).** Earlier: SUPERSEDED by D398 (glyph replaced with the IMG_DOT texture; no new art). PCDUMP pixel-verified when shipped (135 knob px vs 9 paper). | | D349 | **GE watch settings are not normally saved to the Bond file: erase is the only caller of the settings serializer, and stage start reloads the file's old values.** — full `## D349` entry at file tail | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. | | D350 | **Watch-backed PC options: shared front/F10 Bond-file rows, explicit chooser, game-thread F10 bridge and field-scoped PC-only persistence.** — full `## D350` entry at file tail | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. | | D351 | **Existing D346 value/unit rows had positional initializers shifted into `hidePtr`: %/x rows hidden or formatted without units.** — full `## D351` entry at file tail | FIXED (port-only): designated row initializers restore unit, deadzone divisor and hide-if-auto fields; build clean, headless smoke clean. Manual settings visual check owed. | @@ -651,8 +651,8 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D364 | **Settings UI feedback: replace P legend; F10 slider click band misses drawn bar; input defaults visually off-centre (2026-09-27).** — full `## D364` entry at file tail | FIXED (user mostly accepted; D365 follows up on annotation size and click targets). | | D365 | **F10 annotation hierarchy and visible mouse change targets (2026-09-27).** — full `## D365` entry at file tail | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. | | D366 | **Input calibration label: call 50 the default, not original (2026-09-27).** — full `## D366` entry at file tail | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. | -| D367 | **F10 full-width control boxes misaligned with their contents (2026-09-27).** — full `## D367` entry at file tail | SUPERSEDED by D368 after user playtest: per-value boxes remained confusing. | -| D368 | **Simplify F10 click targets: setting name selects, the rest changes (2026-09-27).** — full `## D368` entry at file tail | INPUT RULE KEPT; tinted strips user-rejected, removed in D369. | +| D367 | **F10 full-width control boxes misaligned with their contents (2026-09-27).** — full `## D367` entry at file tail | **CLOSED (2026-09-28 bookkeeping: superseded by D368).** Earlier: SUPERSEDED by D368 after user playtest: per-value boxes remained confusing. | +| D368 | **Simplify F10 click targets: setting name selects, the rest changes (2026-09-27).** — full `## D368` entry at file tail | **CLOSED (2026-09-28 bookkeeping: superseded by D369 (input rule kept)).** Earlier: INPUT RULE KEPT; tinted strips user-rejected, removed in D369. | | D369 | **Remove F10 row boxes/strips entirely (2026-09-27).** — full `## D369` entry at file tail | FIXED (awaiting live acceptance): no row backgrounds, control rectangles or hover paint; selected setting distinguished by brighter text, slider tracks remain. Input rule from D368 unchanged. | | D370 | **F10 distance/volume drags hurt frame rate (2026-09-27).** — full `## D370` entry at file tail | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. (user: fixed.) | | D371 | **GEPD key-layout preset + reload/crouch bindings, v0.4.0 modern options wave M3 (2026-09-27).** full `## D371` entry at file tail | FIXED (headless-verified; awaiting user live accept): `Input.Layout` (0=FPS, 1=GEPD) + `Input.CrouchMode` (hold/toggle) in F10 INPUT; GEPD preset acts as per-key effective default under `[Input.Bind]` overrides; new IA_RELOAD/IA_CROUCH emit B (unbound by default). MODERN section dropped per D356 Turok-standard rule (rows land in functional INPUT). Boot/ini-round-trip/F10-page verified; in-game feel-check owed. | @@ -661,13 +661,13 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D374 | **GEPD becomes the default key layout; preset layering fix (v0.4.0 M3 playtest feedback, 2026-09-27).** full `## D374` entry at file tail | FIXED (headless + user live-tested): GEPD playtest found Ctrl fired the gun (stale persisted [Input.Bind] Fire=Left Ctrl masked the preset) and the FPS default made no sense for a mouse FPS. D374: GEPD is now Input.Layout=0 (the default; N64 layout = 1); only EXPLICIT per-key overrides beat the preset. One-off ini flip: pre-D374 `Layout = 1` (ex-GEPD) is now 0. | | D375 | **Crouch wired to the wrong pad bit: B is not the crouch input (v0.4.0 M3 playtest, 2026-09-27).** full `## D375` entry at file tail | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. | | D376 | **Crouch follow-up + modern-options spot check (2026-09-27).** full `## D376` entry at file tail | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. | -| D377 | **GEPD independent crouch (2026-09-27).** full `## D377` entry at file tail | FIXED (scripted stance verified; user says live crouch was good; pad/hold-vs-toggle specifics owed): Ctrl/B/Y now drive game stance from the port with no aim requirement. | -| D378 | **GEPD use/reload separation (2026-09-27).** full `## D378` entry at file tail | FIXED (headless edges tested; user says E/R seemed good; targeted door/partial-mag cases owed): R reload only, E interaction only in gameplay, menu/watch/tank retains B. | -| D379 | **Crosshair colour: authored texture is red (2026-09-27).** full `## D379` entry at file tail | FIXED (cyan frame capture; live yellow/blue recheck owed): nonwhite tints use alpha silhouette instead of multiplying red RGB; default unchanged. | +| D377 | **GEPD independent crouch (2026-09-27).** full `## D377` entry at file tail | **FIXED (2026-09-28 bookkeeping: residual live-check notes waived by the user; shipped in v0.4.0, no reports).** Earlier: FIXED (scripted stance verified; user says live crouch was good; pad/hold-vs-toggle specifics owed): Ctrl/B/Y now drive game stance from the port with no aim requirement. | +| D378 | **GEPD use/reload separation (2026-09-27).** full `## D378` entry at file tail | **FIXED (2026-09-28 bookkeeping: residual live-check notes waived by the user; shipped in v0.4.0, no reports).** Earlier: FIXED (headless edges tested; user says E/R seemed good; targeted door/partial-mag cases owed): R reload only, E interaction only in gameplay, menu/watch/tank retains B. | +| D379 | **Crosshair colour: authored texture is red (2026-09-27).** full `## D379` entry at file tail | **FIXED (2026-09-28 bookkeeping: residual live-check notes waived by the user; shipped in v0.4.0, no reports).** Earlier: FIXED (cyan frame capture; live yellow/blue recheck owed): nonwhite tints use alpha silhouette instead of multiplying red RGB; default unchanged. | | D380 | **One PC keyboard layout; PD-style rebind design (2026-09-27).** full `## D380` entry at file tail | FIXED (follow-up D383): one PC layout and migration remain; keyboard capture UI is implemented separately, with live acceptance owed. | | D381 | **RULE-2-SIGNOFF: opt-in RGB, size and Beta crosshair (2026-09-27).** full `## D381` entry at file tail | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. | | D382 | **Crosshair menu placement and slider hitches (2026-09-27).** full `## D382` entry at file tail | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. | -| D383 | **PD-style keyboard rebinding in both options UIs (2026-09-27).** full `## D383` entry at file tail | **PARTIAL (2026-09-28).** Keyboard/mouse rebinding shipped + accepted in v0.4.0; pad-rebinding UI is the v0.5.0 controller-options wave (CONTROLLER-INPUT-PLAN). | +| D383 | **PD-style keyboard rebinding in both options UIs (2026-09-27).** full `## D383` entry at file tail | **CLOSED (2026-09-28 bookkeeping: keyboard/mouse rebinding shipped + accepted in v0.4.0; pad rebinding is tracked by the v0.5.0 controller wave (D394, PR #109)).** Earlier: **PARTIAL (2026-09-28).** Keyboard/mouse rebinding shipped + accepted in v0.4.0; pad-rebinding UI is the v0.5.0 controller-options wave (CONTROLLER-INPUT-PLAN). | | D384 | **Front PC Options key-bind navigation feedback + two-slot UI (2026-09-27).** full `## D384` entry at file tail | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. | | D385 | **Real mouse 1–5 action rebinding in the two-slot editor (2026-09-27).** full `## D385` entry at file tail | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. | | D386 | **Correct Aim default from SDL middle (Mouse 2) to right (Mouse 3) (2026-09-27).** full `## D386` entry at file tail | **CLOSED (2026-09-28).** Shipped in v0.4.0; accepted in the user's final review / campaign playtest. | @@ -683,16 +683,28 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D396 | **F10 pad B/A used as slider decrement/increment, not back/select** — B/Y fed Left and A/X fed Right on every row. | RESOLVED (v0.4.0 campaign sign-off 2026-09-28, user: F10 B/A/slider behaviour confirmed on the physical pad). B backs one F10 page, closes at root; A/X accept/toggle on edge but never shift a slider; D-pad/stick left/right still adjust. | | D397 | **SELECT FILE background + intro gun-barrel show an interlaced/comb look** — D182(2) family, but present on FIRST display, not only after re-entry (2026-09-27). Full `## D397` entry at file tail. | **RESOLVED (2026-09-28):** root cause = **H2** (confirmed by a 3-scale PCDUMP test — the comb period is fixed at ≈3 *native* rows across 640/1024/1280, i.e. window-period = 3×scale: the `(<<2)-1` 0.75-native-pixel row-strips rely on RDP floor-based scanline stepping that `gfx_draw_rectangle`'s continuous-float rasterization did not replicate). **Fix (port-layer, surgical):** `gfx_draw_rectangle` now floors the top edge to its scanline and the bottom edge to the last covered scanline (the RDP scanline range `[top,bot+1]`) before rasterizing — a no-op for whole-pixel rects, so only sub-pixel strips change; §D15 in `docs/porting-notes.md` documents the mechanism. H1 (ENV_ALPHA 5-bit CC scaling) REFUTED by a byte-identical PD-port cross-check (the darkening it predicted is faithful, not a bug). `GE_D397SH`/`D397SHM` probes removed. Verified by playtest: gun-barrel + file-select gradient render clean, comb gone. | | D398 | **PC Options label: D348's procedural slider glyph replaced with an existing mainfolderimages texture (2026-09-27).** Full `## D398` entry at file tail. | SUPERSEDED by D400 (icon removed, text-only label; purple-hover bug moot; the D400 `textMeasure` arg-swap bug was found and fixed there). | -| D399 | **File-select bottom row (SELECT FILE text + Copy/Erase + PC Options dot/label) shifted 15px left as a block (user sign-off 2026-09-27).** Full `## D399` entry at file tail. | LANDED (port-only, D343 class): `selectpos.f[0]` 110→95 (new `#ifdef PORT` in front.c), Copy 200/222→185/207, Erase 282/304→267/289 (D343 PORT block), `LABEL_X` 358→343 (`frontoptions.c`); hit bounds recompute from the shifted positions. Clean build, PCDUMP frame 195 no overlaps; by-eye check **done 2026-09-28 (user: good) -- RESOLVED**. | -| D400 | **File-select "PC Options" label: icon removed, text-only; fixed a swapped textMeasure() arg bug and a 7px vertical misalignment vs Copy/Erase (user sign-off 2026-09-27).** Full `## D400` entry at file tail. | LANDED (port-only, `frontoptions.c`): D398's dot icon dropped on user instruction ("most straightforward design"); label now a plain `textRender` word, GAP 28 after Erase. Two measurement bugs fixed: (1) `textMeasure(textheight, textwidth)` args passed swapped -> label 35px above the bar, hot band transposed; (2) bare-label height measures 0 (vs 14 for newline-terminated Copy/Erase) -> 7px lower tops; fixed by restoring the `kLabelNL` height measure. PCDUMP frame 195: tops aligned 279.8 vs 279.3, 31px clearance from Erase; by-eye check **done 2026-09-28 (user: good) -- RESOLVED**. | +| D399 | **File-select bottom row (SELECT FILE text + Copy/Erase + PC Options dot/label) shifted 15px left as a block (user sign-off 2026-09-27).** Full `## D399` entry at file tail. | **CLOSED (2026-09-28 bookkeeping: landed with user sign-off, shipped in v0.4.0).** Earlier: LANDED (port-only, D343 class): `selectpos.f[0]` 110→95 (new `#ifdef PORT` in front.c), Copy 200/222→185/207, Erase 282/304→267/289 (D343 PORT block), `LABEL_X` 358→343 (`frontoptions.c`); hit bounds recompute from the shifted positions. Clean build, PCDUMP frame 195 no overlaps; by-eye check **done 2026-09-28 (user: good) -- RESOLVED**. | +| D400 | **File-select "PC Options" label: icon removed, text-only; fixed a swapped textMeasure() arg bug and a 7px vertical misalignment vs Copy/Erase (user sign-off 2026-09-27).** Full `## D400` entry at file tail. | **CLOSED (2026-09-28 bookkeeping: landed with user sign-off, shipped in v0.4.0).** Earlier: LANDED (port-only, `frontoptions.c`): D398's dot icon dropped on user instruction ("most straightforward design"); label now a plain `textRender` word, GAP 28 after Erase. Two measurement bugs fixed: (1) `textMeasure(textheight, textwidth)` args passed swapped -> label 35px above the bar, hot band transposed; (2) bare-label height measures 0 (vs 14 for newline-terminated Copy/Erase) -> 7px lower tops; fixed by restoring the `kLabelNL` height measure. PCDUMP frame 195: tops aligned 279.8 vs 279.3, 31px clearance from Erase; by-eye check **done 2026-09-28 (user: good) -- RESOLVED**. | | D401 | **Rumble Pak → real gamepad haptics (re-promotes D224/M-87): `CONT_CARD_ON` gate + `osPfs*`/`osMotor*` shims rewired to `SDL_GameControllerRumble`, single global `Rumble strength` slider on `Input.RumbleScale` (2026-09-27/28).** Full `## D401` entry at file tail. | **CLOSED (2026-09-28).** Rumble shipped in v0.4.0 (D401); Steam Deck real-pad validation: works, user confirmed for release. | | D402 | **First clean Linux (glibc / SteamOS) build of `release/v0.4.0` broke in two port-layer include-resolution spots MinGW tolerated (`bondconstants.h` first-include cycle; `video.c` `_SHIFTL`); fixed in `port/shim/` only, no `src/` edits, no rule-2 (2026-09-27).** Full `## D402` entry at file tail. | RESOLVED (Linux/Ubuntu-24.04 build → glibc-2.41-compatible binary for the Steam Deck): three `port/shim/` fixes — `PR/ucode.h` + `bondconstants.h` defer `pc_protos.h`/`bondtypes.h` during `bondconstants.h`'s own `ultra64.h` pass (breaks the `bondconstants.h`→`ultra64.h`→`pc_protos.h`→`bondtypes.h`→`bondconstants.h` re-entry); `PR/gbi.h` defines `_SHIFTL`/`_SHIFTR` when absent (the `gbi.h` macros in `video.c` lost their `mbi.h` definitions on Linux). Builds + links clean on Ubuntu-24.04; bundled `libSDL2` with `rpath=$ORIGIN`; pushed to the Deck. Shim files uncommitted at log time. | | D403 | **Deck v0.4.0-pre test: Nintendo logo + copyright/legal page render broken, broader graphics "not reflecting new updates" — stale 0.3.0-era sidecars seeded into the bundle (2026-09-27).** Full `## D403` entry at file tail. | RESOLVED (2026-09-27, user-confirmed on the Deck: a stale-sidecar **test artifact**, never a code regression): the Deck bundle was seeded with `pcmodels`/`pccg` sidecars from the `0.3.0-local` install (data dated 2026-09-15), which predate the D75/M-197 embedded-blob raw-pixel fix (commit `425237f9`, 2026-09-21) and the D303 muzzle-vertex converter fix (2026-09-26) → the `P*` front-end logo/legal textures come out broken while the in-exe fast3d fixes (D397 gun-barrel/file-select) render correctly. Disambiguation performed (2026-09-27): `data/pcmodels-ntsc-final` + `data/pccg-ntsc-final` regenerated from the Python-source converter (`prepare-assets.py` + the current `d43/d69/d88_emit.py`, `ALL CHECKS PASSED`) on the NTSC ROM and pushed to the Deck (sha256-verified), preserving the user's `ge007.eep`/`ge007.ini`. **RESOLVED (user-confirmed on the Deck, 2026-09-27): the front-end logo / copyright / legal page now render correctly — confirming this was the stale-sidecar test artifact, not a code regression.** | | 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). | -| D407 | **Tanks cannot be boarded/exited on PC — the v0.4.0 use/reload split (D378/D393) removed the B-button tap the engine's tank handlers in `bondview2.c` consume (user report 2026-09-28, Runway/Streets)** — full `## D407` entry at file tail | PARTIAL (fix landed in v0.4.0, port-only): present `GE_CONT_B` on the use (E / pad A) rising edge only while `g_PlayerIsInTank == 1` or `g_BondCanEnterTank != 0`; E keeps its D378 no-reload-fallback semantics elsewhere, N64 layout unaffected. D407(b) same cycle: front PC Options page-edge highlight clamp + mouse-wheel / W-S paging (wheel queue consumed on the menu, D223); its wheel mapping shipped inverted and was fixed to match W/S (wheel up = step up). D407(c): board-animation lockout, menu-accept B gate in tank states, in-tank aim routed through the legacy velocity stick + `Input.TankAimScale` knob. RESOLVED 2026-09-28: all port-only (src/game zero-diff), 10 TANKDBG probes stripped, release binary verified clean, user live tank drive signed off (board + exit OK, aim feel good). | +| D407 | **Tanks cannot be boarded/exited on PC — the v0.4.0 use/reload split (D378/D393) removed the B-button tap the engine's tank handlers in `bondview2.c` consume (user report 2026-09-28, Runway/Streets)** — full `## D407` entry at file tail | **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 | 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)". | +| 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. | +| D424 | **`WeaponStats.RecoilSpeed`/`b44[]` byte order: no grenade-launcher recoil, shortened recoil on every gun (issue #118).** — full `## D424` entry at file tail | FIXED in tree (`WS_B44`, port-guarded); user-verified vs GEPD/1964 2026-09-29. | +| D425 | **Tank run-over death sound loops: `(chrflags << 7) >= 0` is always true on unsigned GCC enum (issue #115).** — full `## D425` entry at file tail | FIXED (2026-09-29 bookkeeping: use-site `(s32)` cast; user-verified vs GEPD/1964 2026-09-29, #115). Earlier: FIXED in tree (use-site `(s32)` cast); live check owed. | +| D426 | **Tank cannot be steered with the mouse out of aim mode (issue #116).** — full `## D426` entry at file tail | RESOLVED (user 2026-09-29: tank controls now match GEPD/1964; see D429). | +| D427 | **Automatic fire cadence is frame-counted, ~2-3x faster than N64 at 60 fps (issue #114).** — full `## D427` entry at file tail | FIXED in tree (modulus scaled by ticks/frame; measured 6 ticks/shot at 30 and 60 cap); user-verified vs GEPD/1964 2026-09-29 (AK47 mag empties in ~3 s, Klobb normal). | +| D428 | **No gun turn-sway with direct mouse look (issue #117).** — full `## D428` entry at file tail | FIXED in tree (2nd pass: exact inverse of the game's yaw integration, clamp fov/60); user-confirmed feels right 2026-09-29. | +| D429 | **Tank full pass: aim-mode (RMB) mouse behaviour and tank sticking forward/back (follow-up to D426).** — full `## D429` entry at file tail | CLOSED faithful (user 2026-09-29: the "sticking" was drive inertia; the original also drops driving input while the mouse aims; PC now matches GEPD/1964). | +| D430 | **Shot light fixtures do not go dark: `lightfixture.c` `extract_vertex_indices_from_triangle` is an unported N64 GBI parser (issue #119 / D415).** — full `## D430` entry at file tail | FIXED in tree (D154-style words.w0/.w1 decode); user-verified vs GEPD/1964 on Caverns 2026-09-29. | +| D431 | **Light fixtures never reached `lightFixtureBreak` on PC: bullet-hit texnum is stubbed to -1 (D154), so `check_if_imageID_is_light` is always false (issue #119; user: Cavern lights do not take damage).** — full `## D431` entry at file tail | FIXED in tree (fixture table records the image id; bg.c hit code looks it up; free-slot guard); user-verified vs GEPD/1964 2026-09-29. | +| D432 | **Gunshot sounds "off" after D427: sound-trigger gate (`SoundTriggerRate`) is per-tick on PC but frame-granular on the N64 (user: Cavern, Mac-10 run).** — full `## D432` entry at file tail | FIXED in tree (gate offset rounded to odd at 1 tick/frame); measured shot/sound cadence identical to N64 rules; residual audible difference vs 1964 tracked as D433. | +| D433 | **Slight audible difference in automatic-gun shot sound vs GEPD/1964 after cadence was matched (user by-ear, Caverns).** — full `## D433` entry at file tail | OPEN (not a timing issue; audio layer suspected). | +| D434 | **Raw `((u8*)&g_Textures[n])[0] & 0xf` hit-type reads are wrong on PC; latent OOB once D431 returned real texture ids (found by sub-agent review).** — full `## D434` entry at file tail | FIXED (`.hitTexture` at 3 sites, port-guarded); live check owed. | Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS @@ -10478,7 +10490,7 @@ crash class C2 (Facility `-level_34`, Runway `-level_35`): `import_texture_i8` A **`bg.c` room-geometry ray/hit-test GBI parser port (M-28, WRITTEN / UNVERIFIED / UNCOMMITTED).** `bgTestRayIntersectionInRoom` (`bg.c:3331`, called by `bgTestBulletHitBackground` on every shot that resolves against background geometry) is the D135 sibling flagged since M-23: raw `((u8*)gdl)[k]` / `((u32*)gdl)[i]` byte/word indexing into what is now the 16-byte PC `Gfx` room DL (D85 `bgWidenRoomGdl` + `texLoadFromGdl`) → desync → OOB `vtxbase[idx]` / walk-off AV the first time you shoot a wall or floor. Ported `#ifdef PORT` (N64 verbatim under `#else`): `vtxoff`/`vtxbase`/`op` via the `.dma` view (matches the already-ported `bgBuildRoomVtxBounds`); G_TRI1 indices from `(w1>>16/8/0)&0xff /10`; the 12 G_TRI4 nibble extractions from `(u32)gdl->words.w0/.w1` (each mapped from the N64 byte/halfword layout — table in the code comment, cross-checked twice); both `texturenum` recoveries → `-1` (the KSEG0 `*(u16*)(w1-8)` deref is invalid for the port's converted GDLs, same as D135; texnum only picks the impact decal/sound, parked D77). Builds clean. **Verification blocked** — a no-input `GE_PCDUMP` capture never fires a bullet so it never calls this function; needs a real firefight into a wall on an idle machine (this session's box was thrashed → D153/driver crash on every run). -**Status (from §F table):** **M-30 re-audit + fixes (still playtest-gated for final verify).** Cross-checked the ported G_TRI1 + all 12 G_TRI4 nibble recoveries twice against the N64 `#else` and the raw BE-word bit derivation — **G_TRI1 and G_TRI4 are correct**. **Two bugs fixed:** (1) `vtxoff` — the port read `gdl->dma.par & 0xf`, but the PC `Gdma_le` shim (`port/shim/PR/gbi.h`) maps `.par` to bits 0-23 of word0 (the packed *length*, always 16-aligned) not the N64 byte-1 params field at bits 16-23, so vtxoff was silently forced to 0 (breaks any G_VTX batch with `v0 != 0`, i.e. multi-batch rooms → wrong `vtxbase[idx]` → OOB). Now `((u32)gdl->words.w0 >> 16) & 0xf`. (2) **Sibling ported** — `bgTestBulletHitBackground` tail (`bg.c:~3841`, the post-hit G_SETTILE back-scan for `tileformat`/`tilesize`) was itself an unported raw `((u8*)gdl)[0]`/`[1]` + 64-bit `words.w0 << 11 >> 30` parser over the same widened DL; wrapped `#ifdef PORT` (opcode from bits 24-31, byte1 from bits 16-23, `(u32)`-forced 32-bit width for the tilesize shift). **Diagnostic:** `GE_D154=1` → `osSyncPrintf` of the parsed cmd/vtx-index stream (call header + per-TRI1/TRI4 `idx`) for the first 64 invocations, for the user/integrator to eyeball. Build green (`ntsc-final`, 242/242). Runtime unverifiable in a worktree (no `data/`); still needs a firefight-into-wall on an idle machine + `-level_09` framediff. Confidence: HIGH on the mechanical correctness (bit-for-bit vs N64), MEDIUM that nothing else in the walk still bites. porting-notes.md §B (D135 corollary). +**Status (2026-09-28 bookkeeping):** **CLOSED** — playtest gate satisfied — full v0.4.0 campaign played with no background-hit/raycast anomalies; ABI re-audited again under D313. Previous: **Status (from §F table):** **M-30 re-audit + fixes (still playtest-gated for final verify).** Cross-checked the ported G_TRI1 + all 12 G_TRI4 nibble recoveries twice against the N64 `#else` and the raw BE-word bit derivation — **G_TRI1 and G_TRI4 are correct**. **Two bugs fixed:** (1) `vtxoff` — the port read `gdl->dma.par & 0xf`, but the PC `Gdma_le` shim (`port/shim/PR/gbi.h`) maps `.par` to bits 0-23 of word0 (the packed *length*, always 16-aligned) not the N64 byte-1 params field at bits 16-23, so vtxoff was silently forced to 0 (breaks any G_VTX batch with `v0 != 0`, i.e. multi-batch rooms → wrong `vtxbase[idx]` → OOB). Now `((u32)gdl->words.w0 >> 16) & 0xf`. (2) **Sibling ported** — `bgTestBulletHitBackground` tail (`bg.c:~3841`, the post-hit G_SETTILE back-scan for `tileformat`/`tilesize`) was itself an unported raw `((u8*)gdl)[0]`/`[1]` + 64-bit `words.w0 << 11 >> 30` parser over the same widened DL; wrapped `#ifdef PORT` (opcode from bits 24-31, byte1 from bits 16-23, `(u32)`-forced 32-bit width for the tilesize shift). **Diagnostic:** `GE_D154=1` → `osSyncPrintf` of the parsed cmd/vtx-index stream (call header + per-TRI1/TRI4 `idx`) for the first 64 invocations, for the user/integrator to eyeball. Build green (`ntsc-final`, 242/242). Runtime unverifiable in a worktree (no `data/`); still needs a firefight-into-wall on an idle machine + `-level_09` framediff. Confidence: HIGH on the mechanical correctness (bit-for-bit vs N64), MEDIUM that nothing else in the walk still bites. porting-notes.md §B (D135 corollary). ## D156 — Facility outro-cutscene "hang" — the actual fix (M-29, user bug report, 2nd occurrence after D155). @@ -10791,7 +10803,7 @@ silence check owed. See `## D401` (file tail) for the full record. **QoL gap (M-87, from a PD-PC-port survey): the N64 Rumble Pak is fully dropped instead of modernized.** GE genuinely has a rumble subsystem (`src/motor.c`, `src/joy.c`) but the port's `osMotorInit/Start/Stop` (`port/src/libultra.c:1220-1223`) are all hard stubs — "no accessories on the PC," `osMotorStart`/`osMotorStop` return `-1` unconditionally, `osPfsFindFile`-family returns `PFS_ERR_NOPACK`. So every in-game rumble event (weapon recoil, explosions, etc.) computes and calls into a dead path. **PD's PC port wires the equivalent straight through to real gamepad rumble:** `inputRumbleSupported(idx)` checks `SDL_GameControllerHasRumble`/`SDL_JoystickIsHaptic` (with a Windows fallback: some pads report no haptics but rumble anyway), `inputRumble(idx, strength, time)` calls `SDL_GameControllerRumble()` scaled by a per-pad `RumbleScale` config value (options-menu slider, `Input.PadN.RumbleScale`), and `osPfsFindFile` reports a "Rumble Pak" present exactly when a real pad supports rumble (`libultra.c` — same "pretend the N64 accessory is there" pattern GE's port already uses for `osMemSize`/Expansion Pak, just applied to a real capability instead of faked). Cross-ref `QOL-INVENTORY.md` (folded into the existing "per-pad tuning" row). -**Status (from §F table):** **DEPRIORITIZED (M-87, user call).** Gamepad-only — irrelevant to mouse/keyboard, which is the primary target for this next release; no keyboard/mouse play is affected by NOT doing this. Root cause + fix still valid whenever gamepad support becomes a priority (no `src/` game-logic touch: GE's `motor.c`/`joy.c` call sites already exist and already call the `osMotor*` shims; the fix is entirely in `port/src/libultra.c` + `input.c` — add `inputRumbleSupported`/`inputRumble` per the PD shape, wire `osMotorStart`/`osMotorStop` to them, add `Input.PadN.RumbleScale` config). **M-87 PD-legacy-survey addendum (`docs/dev/notes/PD-LEGACY-SURVEY.md` candidate 1, high confidence) — the D224 fix as originally stated is NOT sufficient by itself.** Two more upstream blockers sit before the stubbed `osMotorInit/Start/Stop` ever gets reached: (1) `port/src/libultra.c:1096-1098` sets `g_contStatus[i].status = 0` for every pad — `CONT_CARD_ON` is never set, and `src/joy.c:184` gates the *entire* rumble-pak init path on that bit, so `joyRumblePakInit()` never even calls `osPfsInit`. (2) `src/joy.c:186-190` only attempts `osMotorInit` if `osPfsInit`'s return is `PFS_ERR_ID_FATAL` or `PFS_ERR_DEVICE` — but the port's stub (`libultra.c:1214`) returns `PFS_ERR_NOPACK`, so motor init is never attempted even once (1) is fixed. PD's exact trick (`libultra.c:213-227,349-351`): set `CONT_CARD_ON` for connected pads, and make `osPfsInitPak()` return `PFS_ERR_DEVICE` specifically when the pad supports rumble. **Whenever D224 gets implemented, both of these must land alongside the `SDL_GameControllerRumble` wiring** or the game's state machine parks at `RUMBLEPAKINITSTATE_NOT_READY` forever and nothing audibly changes despite looking wired up correctly. +**Status (2026-09-28 bookkeeping):** **CLOSED** — rumble landed via D401 and shipped in v0.4.0. Previous: **Status (from §F table):** **DEPRIORITIZED (M-87, user call).** Gamepad-only — irrelevant to mouse/keyboard, which is the primary target for this next release; no keyboard/mouse play is affected by NOT doing this. Root cause + fix still valid whenever gamepad support becomes a priority (no `src/` game-logic touch: GE's `motor.c`/`joy.c` call sites already exist and already call the `osMotor*` shims; the fix is entirely in `port/src/libultra.c` + `input.c` — add `inputRumbleSupported`/`inputRumble` per the PD shape, wire `osMotorStart`/`osMotorStop` to them, add `Input.PadN.RumbleScale` config). **M-87 PD-legacy-survey addendum (`docs/dev/notes/PD-LEGACY-SURVEY.md` candidate 1, high confidence) — the D224 fix as originally stated is NOT sufficient by itself.** Two more upstream blockers sit before the stubbed `osMotorInit/Start/Stop` ever gets reached: (1) `port/src/libultra.c:1096-1098` sets `g_contStatus[i].status = 0` for every pad — `CONT_CARD_ON` is never set, and `src/joy.c:184` gates the *entire* rumble-pak init path on that bit, so `joyRumblePakInit()` never even calls `osPfsInit`. (2) `src/joy.c:186-190` only attempts `osMotorInit` if `osPfsInit`'s return is `PFS_ERR_ID_FATAL` or `PFS_ERR_DEVICE` — but the port's stub (`libultra.c:1214`) returns `PFS_ERR_NOPACK`, so motor init is never attempted even once (1) is fixed. PD's exact trick (`libultra.c:213-227,349-351`): set `CONT_CARD_ON` for connected pads, and make `osPfsInitPak()` return `PFS_ERR_DEVICE` specifically when the pad supports rumble. **Whenever D224 gets implemented, both of these must land alongside the `SDL_GameControllerRumble` wiring** or the game's state machine parks at `RUMBLEPAKINITSTATE_NOT_READY` forever and nothing audibly changes despite looking wired up correctly. ## D225 — Dev-tooling QoL (M-87, user ask): a debug-build/env toggle that unlocks all missions + all cheats, for testing. @@ -11727,7 +11739,7 @@ Superseded history below (the original OPEN investigation, root cause not found **LOD/culling breaks down on large open levels (Streets, Egyptian named specifically) at *default* FOV — far-away geometry has visible culling issues (M-119, user QA report).** Distinct from **D222** (which is specifically high `Video.FovScale` breaking *near-screen-edge* culling on NPCs/objects — not implicated here, default FOV) and from **D218** (the fog/far-clip *tint* boundary at wide FOV — also not the same symptom). This report is at the stock FOV, on the game's two biggest/most-open outdoor-scale levels, affecting *far* geometry specifically — points at either (a) a portal/room-visibility BFS cutting off too aggressively at range on large multi-room layouts (same neighbourhood as D233/D234's near-camera portal-cull bugs — worth checking whether D233's `gfx_sp_tri1` w<0 trivial-reject fix, which only guarded *behind-camera* vertices, has a sibling far-plane/depth-range case), or (b) GE's own N64 LOD/detail-swap system (mip-tile / simple-model substitution at distance — see D218's still-open question of "does GE have a separate geometry/object LOD-swap distinct from `Visibility.FarFog`") not being ported faithfully, popping or dropping geometry instead of swapping detail level. **Second, separate ask bundled in the same report:** the user would like LOD **improved** in general (presumably distance-based detail transitions look rougher/more abrupt on PC than remembered) but explicitly wants this **gated behind a toggle** so the original N64-accurate LOD behaviour stays selectable ("in case you want old school graphics") — i.e. this is a QoL feature request layered on top of a correctness bug, not one fix. -**Status (from §F table):** OPEN — static review this session ruled out two of the three suspects; the remaining one needs a live repro, not more code reading. **Level IDs confirmed:** Streets = `-level_29` (not `-level_20` as originally guessed — that number was Egyptian's neighbourhood; Egyptian itself is `-level_32`, `verify.sh`'s alias `egypt`). Attempted a headless `GE_PCDUMP` long-sightline capture (same technique as D245's scripted-turn probe) on both — **abandoned as a bad test**: Streets spawns directly behind a barricade/wall (no open sightline at all from the reachable start), and Statue's (`-level_22`) relevant open vista is well past the spawn point, behind navigation this session has no reliable scripted way to reach (unlike D245's in-place camera turn, walking through a level's actual layout via blind `GE_INPUTSCRIPT` button-mashing risks getting stuck on geometry/AI and wouldn't reliably land at the right spot even if it didn't). **This one genuinely needs a live human capture** (screenshot + exact location) before further diagnosis — logged here rather than burning more session time on scripted navigation guesses. +**Status (2026-09-28 bookkeeping):** **CLOSED** — the v0.4.0 campaign sign-off already recorded below closed it — no distant-geometry drop-out on Streets/Egyptian over the full campaign; lead status corrected. Previous: **Status (from §F table):** OPEN — static review this session ruled out two of the three suspects; the remaining one needs a live repro, not more code reading. **Level IDs confirmed:** Streets = `-level_29` (not `-level_20` as originally guessed — that number was Egyptian's neighbourhood; Egyptian itself is `-level_32`, `verify.sh`'s alias `egypt`). Attempted a headless `GE_PCDUMP` long-sightline capture (same technique as D245's scripted-turn probe) on both — **abandoned as a bad test**: Streets spawns directly behind a barricade/wall (no open sightline at all from the reachable start), and Statue's (`-level_22`) relevant open vista is well past the spawn point, behind navigation this session has no reliable scripted way to reach (unlike D245's in-place camera turn, walking through a level's actual layout via blind `GE_INPUTSCRIPT` button-mashing risks getting stuck on geometry/AI and wouldn't reliably land at the right spot even if it didn't). **This one genuinely needs a live human capture** (screenshot + exact location) before further diagnosis — logged here rather than burning more session time on scripted navigation guesses. **Two of the three original suspects ruled out by source review (no live access needed for this part):** (1) **Not an unscaled far-clip plane.** Checked whether `Video.DrawDistance`'s fog-distance scaling (D218) forgot to extend the actual projection matrix's far-clip plane by the same factor, which would hard-cull geometry the fog says should still be visible — it doesn't: `fogLoadCurrentEnvironment` (`bgfog.c:311-345`) computes one `scaledFarFog` value and feeds it to *both* `viSetZRange`'s far argument (which becomes `g_ViBackData->zfar`, the actual `guPerspectiveF` far plane in `fr.c:736`) *and* the fog-blend-distance math (`g_CurFogDetails.scaled_far_fog_dist`) a few lines later — they can't drift apart. (2) **Not the LOD-distance multiplier.** `portLodDistanceMultiplier()` (`port/src/video.c:196`) is a plain `100.0f/pct` scale with `pct` clamped to `[25,400]`, applied to `modelUpdateDistanceRelations`'s per-model LOD-swap distance (`model.c:1962`) — at the port's own default (`cfgLodDistance=150`), this makes `distance` *smaller* (multiplier ≈0.667), which per the function's own documented sense keeps *more* detail visible farther out, not less. Both mechanisms are self-consistent, already more generous than stock N64, and not a plausible source of "geometry drops/pops" at default settings. **Remaining live suspect, unconfirmed:** GE's own screen-space portal/room-visibility BFS (`bgDetermineVisibleRooms`/`bgQueuePortalTraversal`/`bgRectIntersect`, `bg.c`) — a decomp-verbatim system, not distance-based per se but screen-space-rect-based (a portal's *projected* bounding box vs. the parent room's, `bgRectIntersect` at `bg.c:1812`). Its **near-camera** degenerate case (a portal straddling z=0 producing huge/NaN screen coords) was already found and fixed as D106/revised-by-D271 (`bg.c:1724-1751`, `#ifdef PORT` guard) — but there is no equivalent guard, and no evidence of one being needed, for the opposite **far-camera** case: a portal at long range legitimately projects to a small (but numerically fine, non-degenerate) screen rect, and if that rect fails `bgRectIntersect` against the parent/screen box, the room is correctly culled per N64's own algorithm. That could be either (a) a genuine mismatch this port introduces somewhere in the projection chain that N64 didn't have, or (b) N64's own always-present behavior that's simply more visible on PC's sharp, high-res display than it ever was through a blurry N64-era CRT/composite signal — the same "reveal, not regression" pattern several other findings in this project have hit (cf. D227's tc-overflow, invisible until a related fix made textures correct). **Cannot distinguish (a) from (b), or find a concrete bug to fix, without a screenshot + exact in-level location from a live playthrough** — do not touch `bg.c`'s portal BFS without that evidence; it's large, sensitive, decomp-verbatim game logic (AGENTS.md non-negotiable #2), not a narrow ABI-class case like D106/D271's near-plane guard was. @@ -11906,6 +11918,8 @@ Superseded history below (the original OPEN investigation, root cause not found **Status:** OPEN; still needs the playtest item (Dam: watch the truck pass, F12 screenshot if glitched, note whether it's the wheels, i.e. D306). +**Current status (2026-09-29 bookkeeping):** the Status line above is historical; the authoritative verdict is the §F row — CLOSED — duplicate of D306, faithful (M-201, 2026-09-26: user-captured, Dam intro camera 5 only). Earlier: OPEN — observed, not investigated. Needs a live repro with the truck's exact position/timing and, ideally, a screenshot. + **2026-09-26 (M-201): merged into D306.** Captured by the user: intro-only (Dam level intro/attract, truck over the walkway camera), wheels drawing through the body; reproduces at FovScale 100. Tracked under D306 from here. @@ -12160,6 +12174,8 @@ This is a **perfect, smooth white-hot → yellow → orange → deep-red → fad **Status:** OPEN — single live report, un-triaged, un-reproduced outside the debug harness. **Do not start perf-tracing `port/fast3d/` or AI code over this until step (a)/(b) above rule out harness overhead** — that's the cheap, disqualifying check and it hasn't been run yet. +**Current status (2026-09-29 bookkeeping):** the Status line above is historical; the authoritative verdict is the §F row — CLOSED (2026-09-26, user-approved: stutter seen only under gdb (debug.ps1); D302, fixed two days later, removed a per-triangle uncached getenv costing ~15 fps; reopen if seen on a normal build). Earlier: OPEN — single live report, un-triaged; observed only und… + ## D297 - v0.3.0 silently wipes ALL pre-fix player saves on first launch (D284 side effect; release-blocker, found during D295/M-148). **What:** every save written by a v0.2.x build fails `fileValidateSaves` CRC on any post-D284 build and is reset to factory (`BAD -> RESET (progress wiped)`, confirmed live via `GE_SAVELOG=1` on the user's #87 artifact — all five slots). Players upgrading to v0.3.0 lose their entire campaign with no warning. @@ -12190,6 +12206,8 @@ This is a **perfect, smooth white-hot → yellow → orange → deep-red → fad **Status:** **OPEN — parked per user instruction: suspected debug-launch artifact; do not raise until seen in a regular release-targeted build. Not yet investigated.** +**Current status (2026-09-29 bookkeeping):** the Status line above is historical; the authoritative verdict is the §F row — CLOSED (2026-09-26, user: HUD is fine now). Earlier: s — regression reported 2026-09-16 during v0.3.0 work (build ce8427cf); user says "don't fix, just for context"; not yet investigated — full `## D298` entry at file tail + ## D299 — Game.SkipIntro crashed selecting any save slot except one (found + fixed this session, 2026-09-17) **Symptom (user, live repro):** with `Game.SkipIntro=1` (D216), booting straight to file-select and selecting any of the 4 file slots except one crashed the game (`EXCEPTION: 0xc0000005`, fault addr `0x10`). Reproduced on both a clean `CMAKE_BUILD_TYPE=Release` rebuild and the normal `RelWithDebInfo` dev build, and with a completely fresh (no `data/ge007.eep`) save — ruling out stale/incompatible save data. With `SkipIntro=0` (normal intro/legal-screen boot), all 4 slots worked with no crash. @@ -12344,6 +12362,8 @@ Adversarial pre-ship review of the whole v0.3.0 window (147 commits since v0.2.2 **Status:** OPEN — investigated (pipeline + data ruled out), GE_D303 capture probe in-tree, blocked on a live firing repro. +**Current status (2026-09-29 bookkeeping):** the Status line above is historical; the authoritative verdict is the §F row — FIXED (M-201, 2026-09-26): d43_emit sized op-22 star-flash vertex arrays as count, not count*4 arms; regenerated sidecars render a clean star. Earlier: OPEN — CONFIRMED PORT BUG (2026-09-26, user vs 1964: M16/AR33 muzzle flash shoots up and draws much higher o… + **M-191 (2026-09-18, user QA on the v0.3.0 pre-release build): REOPENING the "main fireball" thread — the fresh report matches exactly the condition M-188 said to watch for.** User describes smoke/explosion effects as now looking correct overall, EXCEPT a "weird rainbow confetti looking sprite" that still draws overlaid on top of otherwise-correct smoke/explosion particles, sometimes. M-188 explicitly closed only the "lingering particle residue" manifestation (which turned out to be `s_bullet_spark`/D219, unrelated code) and left this exact scenario — a rainbow artifact riding on the actual explosion/smoke effect itself — open, with the instruction "re-open only if a fireball (not residue/embers) rainbow report comes in again." This report fits that description, not the residue/embers one. **Not yet investigated further this session** — the last concrete lead (before M-188's tangent into D219) was: trace forward from `import_texture_rgba16`'s `upload_texture()` call (`gfx_pc.cpp:718`) through the GL texture object and the `G_CC_INTERFERENCE` (TEXEL0=smoke IA × TEXEL1=fire RGBA16) combiner, since the source ROM data itself was already proven correct by hand-decoding all 14 fire-stage textures (M-156, see above) — the bug is downstream of decode, in upload/cache/combiner. A live `GE_PCDUMP` + `GE_D252=1` capture during an actual reproduction (shoot something that smokes/explodes) is still the next concrete step, unchanged from M-156's own "next, concrete" recommendation. **Status: REOPENED (was incorrectly treated as fully covered by the D219 closure) — OPEN, root cause still unconfirmed for this specific manifestation.** @@ -12406,7 +12426,7 @@ Adversarial pre-ship review of the whole v0.3.0 window (147 commits since v0.2.2 **User report (2026-09-18):** crash on Cavern, early in the level, on the local v0.3.0 pre-release build this session deployed to the Deck (`~/Downloads/goldeneye-pc-port-0.3.0-deck-playtest/`). User's own hypothesis: likely related to `Game.AllUnlocked` (the F10 "unlock everything" toggle, D257), which had been previously enabled on that session/save. -**Fetched `ge007.crash.log` via SSH from the Deck (192.168.69.18) and symbolized it against the exact deployed binary** (confirmed identical build via matching ELF BuildID `d6ec0681d2e82a656c7a099f66264d6bfa4480b7` between the Deck's copy and this session's local `build-linux/ge007.x86_64` — the SHA256 differs only because `bundle`-style `patchelf --set-rpath` modifies the file after linking, not because the code differs): +**Fetched `ge007.crash.log` via SSH from the Deck (the LAN address) and symbolized it against the exact deployed binary** (confirmed identical build via matching ELF BuildID `d6ec0681d2e82a656c7a099f66264d6bfa4480b7` between the Deck's copy and this session's local `build-linux/ge007.x86_64` — the SHA256 differs only because `bundle`-style `patchelf --set-rpath` modifies the file after linking, not because the code differs): ``` SIGNAL: 11 FAULT ADDR: 0x62 @@ -12449,6 +12469,8 @@ do { **Status:** PARTIAL — real fix landed and confirmed (Windows), residual known and deferred past v0.3.0 by explicit user decision, not by default/oversight. +**Current status (2026-09-29 bookkeeping):** the Status line above is historical; the authoritative verdict is the §F row — FIXED (defensive guard) — `sndHandleEvent`'s preempt-scan (`src/snd.c`) dereferenced its list-walk pointer before ever checking it for NULL (a do-while always runs its body once); D285's `osSetIntMask` lock only prevents another thread from emptying the list m… + **Correction, same day: the residual is NOT Deck/Linux-specific — it's intermittent on Windows too.** User follow-up testing: "The rainbow explosion particles is still intermittent on PC sometimes, I can reproduce on Jungle for example but it does not always occur." Repro method: `Game.AllUnlocked` + cheats to get the grenade launcher immediately, rather than earning it through normal play. **Also reports the same repro method does NOT reproduce the bug on every level** — i.e. it's both run-to-run intermittent on a single level AND level-dependent, not just platform-dependent as the previous paragraph assumed. This walks back the "Windows-fixed/Deck-residual" framing from the paragraph above — the correct framing is: **the `explosionRenderPart` 4-vs-5-vertex fix is real and eliminated one genuine, deterministic contributing bug, but the rainbow symptom itself was never fully platform-isolated to begin with, and a second, still-unidentified mechanism can fire intermittently on any platform.** The user notes `Game.AllUnlocked`+cheat-immediate-grenade-launcher as the repro method used, flagged as a possible (not confirmed) contributing factor — matches this session's own prior TMEM-slot-reuse lead's inherent timing-dependence (a scene reached quickly via cheats, before the game's own normal object/texture allocation order would occur, could plausibly produce a different TMEM allocation pattern than reaching the same scene through normal play). Not confirmed causal; logged as a correlation worth checking in a future live-capture session, same as the AllUnlocked/D305 correlation was. **Status, corrected:** PARTIAL — `explosionRenderPart` fix is real, verified, and stays in. The rainbow symptom is intermittent and not cleanly platform-isolated (seen on both Windows and Steam Deck); the secondary mechanism remains unidentified. Public docs (release notes, README) should describe this as "still occasionally intermittent, on any platform," not "fixed on Windows, only recurs on Deck." @@ -12461,6 +12483,8 @@ do { **Status:** OPEN — confirmed as real z-fighting by probe; port-vs-faithful question open (see D308). +**Current status (2026-09-29 bookkeeping):** the Status line above is historical; the authoritative verdict is the §F row — CLOSED — faithful, confirmed on 1964 (M-201): all PC-specific leads refuted (FOV, depth precision, depth state, near plane, wheel transforms); it is the authored truck geometry seen from intro camera 5 under the truck. Absorbs D291. Earlier GE_ZF "real fightin… + **2026-09-26 (v0.4.0, M-201): D291 is this same bug; FOV ruled out.** User captured the D291 "Dam truck" glitch with F12: it happens only on the Dam level intro (also the attract demo), in the shot where the truck drives over the low walkway camera (~frames 280–440 from `-level_33` boot). Wheels and tyre treads draw through the mudguards/body. It reproduces at **FovScale 100** (user re-test, `scratch/d291_shots/fov100_full.png`) as well as at 150 + native widescreen (`shot_000..003.png`), so the port's wider cutscene FOV is not what exposes it. Headless A/B at default vs user settings: `scratch/d291_AB.png` (runs not frame-locked; `GE_ZF` hit its 600-line cap in both). Occlusion-state path checked in passing: fast3d honours `G_ZBUFFER`/`G_ZS_PRIM`/`Z_CMP`/`Z_UPD` per triangle (`gfx_pc.cpp` depth_mode), so no obvious state gap. **Still gated on an N64/emulator reference of this exact shot** (porting-notes D12): if the N64 shows clean wheels there, the fight is PC-specific and the next suspect is the vehicle model's per-part transforms at extreme close range; if it shows the same, close as faithful. **2026-09-26 (M-201, user: "investigate the PC-specific leads"): every PC-specific lead refuted; the artifact is the authored truck geometry seen from under the truck.** @@ -12492,6 +12516,8 @@ do { **Status:** OPEN (investigated — no port-side bug found; awaiting repro detail). +**Current status (2026-09-29 bookkeeping):** the Status line above is historical; the authoritative verdict is the §F row — FIXED (M-201, 2026-09-26, user-verified): renamed duplicate member `CCTVRecord.pad`→`lookpad` made setupCctv read the MOUNT pad as the look-at pad; prop.c reads lookpad on PC. Earlier: OPEN — CONFIRMED PORT BUG (2026-09-26, user vs 1964: ALL Bunker 1 security… + **M-201 (2026-09-26, v0.4.0): ROOT CAUSE FOUND + FIXED — a renamed duplicate struct member re-pointed `setupCctv`'s look-at pad to the mount pad. User-verified.** - **Confirmed port bug first:** user side-by-side with 1964: every Bunker 1 camera faces backwards on PC, correct on N64. @@ -12519,6 +12545,8 @@ do { **Status:** OPEN — real z-fighting confirmed by probe; port-vs-faithful blocked on an N64 reference of the same spot. +**Current status (2026-09-29 bookkeeping):** the Status line above is historical; the authoritative verdict is the §F row — CLOSED — faithful (2026-09-26, user: Statue ground z-fighting reproduces on 1964 GEPD bundle). Earlier: OPEN (user, 2026-09-26: possibly faithful; validate Statue ground z-fighting on 1964 vs port). Earlier: OPEN — investigated: PC depth precision == N64 (24-b… + ## D309 — Complete non-self-recovering freeze (Windows) returning from Caverns to the intro screen — boss thread spins forever in `ai()` on `m_RunToBondPersistent` via a phantom `GotoNext(lblZero)` (user report + live GDB diagnosis, 2026-09-18). **Repro (user):** played through Caverns, then returned to the intro screen (Bond's intro circles); game locked up completely, no self-recovery. Process left alive for live diagnosis (PID 35936 at capture time). @@ -12601,6 +12629,8 @@ This confirms the M-160/M-164/M-165 hypothesis (`## D173`'s own consolidated-nex **Status:** GUARDED (freeze class neutralized; upstream poison source OPEN — M-194 probe armed). **RULE-2-SIGNOFF (2026-09-19, release review — M-192 item 2):** user approved retention as the stability-maximizing option; classified as a defensive guard against non-finite state in the same class as the accepted D156 guards — it fires only on poison values that indicate an already-broken state and snaps to a valid frame rather than altering any legitimate animation. Previously disclosed in-thread as a D156-guard extension following the already-accepted D156 precedent in this same file; revert is still two small blocks. Cross-refs: D156 (original guard + Facility outro hang), D309/D310 (ruled out for this freeze — no AI involvement). +**Current status (2026-09-29 bookkeeping):** the Status line above is historical; the authoritative verdict is the §F row — FIXED — root cause D336 (`bheadAdjustAnimation` read never-written placeholder fields); the `#ifdef PORT` guard is retained as a safety net. 2026-09-25 user playtest incl. ~2 min of Caverns combat: 0 D311 ANOMALY / D156 lines in a ~46-min session. + **2026-09-24 — likely root cause found in D336.** D311's own static pass named `bheadAdjustAnimation` as the one path where a corrupted value reaches `model->speed`. D336 showed that path computing Bond's head-bob start frame from a never-written alias field (`field_5C0` = N64 `model.animframe1`), i.e. uninitialised player-struct bytes, whose garbage depends on memory layout (room-pool size, previous level). That matches D311's intermittent `oldspeed=-1.75e18`-at-transition signature. After the D336 fix: headless Caverns (`-level_39`, user settings incl. the 2× room pool `ma=512000`, scripted movement/turning, ~80 s) gave **0 D156 guards, 0 D311 anomaly lines, no crash**. **Close after one live Caverns combat session (~2 min) stays D156/D311-silent.** **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). @@ -12642,6 +12672,8 @@ This confirms the M-160/M-164/M-165 hypothesis (`## D173`'s own consolidated-nex **Status:** OPEN — reported only. Explicitly not for v0.3.0; queued for v0.3.1. Cross-refs: D312 (possible common wall class), D172 (impact-effect path, fixed). +**Current status (2026-09-29 bookkeeping):** the Status line above is historical; the authoritative verdict is the §F row — FIXED — same root cause as D312, merged diagnosis; same one-line fix. Doors/windows use the separate working `bgTestHitOnObj` path, hence the "surface-dependent" appearance. + ## D314 — F10 menu items flicker randomly/intermittently (reported on the main-menu context, user report, v0.3.0 playtest build, 2026-09-18). **Reported:** "F10 menu glitches on main menu, the menu items flicker randomly/intermittently." **Repro detail (user, 2026-09-18):** it happens when F10 opens the settings menu while on the **file-select / level-select screen**, and also when the settings menu is open in-game and the user is then **returned to file-select/level-select** (i.e. the overlay outlives a context switch). The playtest build is the current-tip exe (includes D310/D311 fixes). So this is specifically an overlay-vs-mission-select-screen interaction, not the plain title screen. @@ -12666,6 +12698,8 @@ This confirms the M-160/M-164/M-165 hypothesis (`## D173`'s own consolidated-nex **Status:** OPEN, likely fixed (D287 + wheel race), pending the user check above. +**Current status (2026-09-29 bookkeeping):** the Status line above is historical; the authoritative verdict is the §F row — FIXED (user playtest 2026-09-26) — M-200 wheel-queue race fix + D287 window-thread cursor calls. Earlier: confirmed still present post-D316 fix (2026-09-18). Diagnostic probe (`GE_D314=1`) landed in `optionsOverlayHandleInput()` if revisited. + **2026-09-26 (v0.4.0, M-201): user-confirmed.** F10 on file-select/mission-select with mouse move/click/wheel, and in-level → abort with F10 open: no flicker. Closed by the M-200 wheel-queue fix plus D287 (cursor/relative-mode calls moved to the window thread). @@ -12684,6 +12718,8 @@ This confirms the M-160/M-164/M-165 hypothesis (`## D173`'s own consolidated-nex **Status:** OPEN — disclosed as a known issue, deferred to v0.3.1. Lower urgency than D314/D316/D317. +**Current status (2026-09-29 bookkeeping):** the Status line above is historical; the authoritative verdict is the §F row — CLOSED (2026-09-26, user: menu scaling looks fine; likely resolved by the v0.4.0 widescreen/HUD-scale work D334/D335/D226). Earlier: OPEN — disclosed as a known issue, deferred to v0.3.1 (bigger design question, not a mechanical fix). + ## D316 — Mouse-clicking some menu items glitches and lands on the item below instead (user report, v0.3.0 playtest build, 2026-09-18). **Reported:** "On some menu items when you try to click with mouse, it glitches and forces you onto the item below." **Repro detail (user, 2026-09-18):** possible on any item, but specifically noticed on the **crop overscan bars** setting; not currently reproducible on demand — intermittent. @@ -12952,6 +12988,8 @@ The same device at the same moment drains at exactly real time through DirectSou **Status:** OPEN / parked (GRAPHICS-BACKLOG). If a playtest ever flags mushy distant detail in one of the 15 levels above (Statue and Aztec are the heaviest), the fix path is: in fast3d's texture upload, when the binding class has TEXTLOD on and `tex->maxlod > 1`, feed `glTexImage2D` levels 0..N-1 from the decoded explicit images instead of `glGenerateMipmap`. Audit tool: `tools_pc/bg_lod_audit.py` (also documents the GE OMH field-write encoding, which no other tool captures). +**Current status (2026-09-29 bookkeeping):** the Status line above is historical; the authoritative verdict is the §F row — CLOSED — parked as a fidelity enhancement, not a bug (2026-09-26, user-approved: no reported artifact). Earlier: OPEN / parked — mechanism confirmed in code; census (`tools_pc/bg_lod_audit.py`) shows 15 levels with explicit-LOD textures bound under TEXTLOD-on… + ## D324 — GE's stub declares nothing, so TUs that include it get NO CRT prototypes: implicit `int f()` declarations silently corrupt non-int returns (atof garbage in every float ini parse; D38 class) **Context.** Found while verifying the D294 fix: the new `GE_ROOMPOOL` env hook in `port/src/video.c` called `atof()`, and the parsed value was deterministic-per-call-site garbage (observed 15279232 / 7087232 / 6817888 / 14288992 across runs) instead of the env string's value. The scale override was silently ignored on every run until prototypes were added. @@ -14873,7 +14911,7 @@ both menus' labels and sizing is still owed. ## D383 — PD-style keyboard rebind capture in both options UIs (2026-09-27) -**Status: PARTIAL (built and isolated automated checks; live keyboard-menu +**Status (2026-09-28 bookkeeping):** **CLOSED** — keyboard/mouse rebinding shipped + accepted in v0.4.0; pad rebinding is tracked by the v0.5.0 controller wave (D394, PR #109). Previous: **Status: PARTIAL (built and isolated automated checks; live keyboard-menu acceptance and the release golden gate owed).** User explicitly requested PD-style keyboard rebinding after the D380 single-PC-layout migration. PD analogue: `../pd_port/port/src/optionsmenu.c:1694–1885` offers a per-action @@ -15179,6 +15217,8 @@ I logged all of this as **D397** (`docs/dev/findings.md`, index CSV, and the D18 **Status:** OPEN, re-scoped. New user observation; no code change this session (diagnosis below; a blind fix is not safe without the ground-truth capture that is blocked here). +**Current status (2026-09-29 bookkeeping):** the Status line above is historical; the authoritative verdict is the §F row — RESOLVED (2026-09-28): root cause = H2 (confirmed by a 3-scale PCDUMP test — the comb period is fixed at ≈3 *native* rows across 640/1024/1280, i.e. window-period = 3×scale: the `(<<2)-1` 0.75-native-pixel row-strips rely on RDP floor-based scanline stepping t… + **User report:** the SELECT FILE background image (the 007 gun-barrel / Bond-silhouette backdrop behind the file boxes) and the intro gun-barrel both have a "weird interlaced glitchy look" — a horizontal comb / line-scramble. This extends the long-standing D182(2) "comb / interlacing" symptom and adds a **second, independent instance** (the intro gun-barrel) that D182 never covered. Critically, the intro gun-barrel is a **first-time render** (not a back-out / re-entry), so the interlace is present on first display, not only after a round-trip. That refutes D182's "only after re-entry / stale tile descriptor" hypothesis for this symptom: it is a *persistent* defect on these large 1:1 grayscale front-end images. **What was ruled out this session (static read, no live run):** @@ -15513,7 +15553,7 @@ included; D281 keeps the shipped mitigation + disclosure, zero-saves root cause **Verification:** NTSC build + link clean; zero `TANKDBG` strings in the release binary (re-verified after probe strip, 2026-09-28); 40 s front-end boot steady ~60 fps with zero errors. **Live sign-off (user, 2026-09-28):** first tank drive — board + exit work, no double-trigger against the tank prop, and the in-tank aim feel is good at `TankAimScale = 100` ("felt fine, glad to have the setting"). -**Status:** RESOLVED (2026-09-28) — board-ordering fix + board-animation lockout + menu-accept B gate + tank aim routing + `Input.TankAimScale`, all port-only (`src/game` zero-diff), user-signed off on a live tank drive; probes stripped, release binary verified clean. +**Status (2026-09-28 bookkeeping):** **CLOSED** — tank board/exit accepted by the user; shipped in v0.4.0. Previous: **Status:** RESOLVED (2026-09-28) — board-ordering fix + board-animation lockout + menu-accept B gate + tank aim routing + `Input.TankAimScale`, all port-only (`src/game` zero-diff), user-signed off on a live tank drive; probes stripped, release binary verified clean. ## D408 — `Game.SkipIntro` skips the post-mission failure dossier: failed/aborted missions return straight to the menus with no REPORT screen (user report 2026-09-28, during v0.4.0 wrap-up playtest) @@ -15524,3 +15564,102 @@ included; D281 keeps the shipped mitigation + disclosure, zero-saves root cause **v0.4.0 decision (user, 09-28):** `Game.SkipIntro` stays **EXPERIMENTAL — not recommended for regular users yet**, alongside its older unrooted audio-break report (D216 status note). The F10 row is relabelled "Skip intro (EXPERIMENTAL)" (`port/src/optionsoverlay.c`, mirroring the D387 AllUnlocked labelling precedent); the config var, default (off), and `lv.c` hook are unchanged. **Status:** OPEN — cosmetic (no crash, no data loss; the mission still records as failed/aborted and the menus are fully functional). Root-causing needs a side-by-side of front-end state (`current_menu`, `g_CurrentStageToLoad`, `mission_failed_or_aborted`, `g_isBondKIA`) on the fail return with SkipIntro off vs on — candidate for v0.5.0 alongside the D216 audio half. + + +## D415 — Shooting a light fixture does not "kill" its flickering light (Bunker, Caverns) (community report, 2026-09-28) + +**Report (issue #87, comment from the issue's reporter, 2026-09-28, after an +00 Agent playthrough of v0.4.0):** "shooting the lights fixtures in bunker and +caverns does not actually 'kill' the flicker light." Reported as one of "a few +little things"; the reporter asked whether it is already known before filing +separate tickets. It was not: no prior finding or backlog row covers it. + +**Not yet established:** whether the N64 original (1964/GEPD) kills the +flicker on those specific fixtures, i.e. whether this is a port bug at all; +which fixtures (flickering vs. static lights) and levels are affected; whether +the fixture's texture/vertex darkening happens and only the flicker persists, +or nothing changes. + +**Starting points (unverified leads, not a diagnosis):** the break path is +`lightFixtureBreak()` (`src/game/lightfixture.c:407`) → +`darken_triangle_in_room()` / `darken_vertex_in_room()`, which edit room vertex +colours located via `lightFindVertexBaseForTri()` (`:176`, the same cast #107 +touches; s32-safe on Windows/Linux because the DRAM arena is pinned at +0x70000000). The flicker itself may be driven by a separate per-frame +room/vertex light animation that rewrites the colours after the darkening; if +so, check whether the fixture table's per-room state (`light_fixture_table`, +`redarken_lights_in_room()`) is honoured on PC. Also rule out fast3d vertex +caching of room geometry, which would hide a vertex-colour edit. + +**Next:** confirm the N64 behaviour on 1964 (Bunker 1 and Caverns, shoot a +flickering fixture), then repro on PC. + +**Status:** OPEN (logged, not investigated). Now tracked as GitHub issue #119; the reporter's statement that the N64 stops the flicker is accepted as the baseline (a 1964 check is a bonus, not a gate). + +**Current status (2026-09-29 bookkeeping):** the Status line above is historical; the authoritative verdict is the §F row — 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. + + +## D424 — `WeaponStats.RecoilSpeed`/`b44[]` byte order: no recoil animation on the grenade launcher, shortened recoil on every gun (issue #118) + +**Root cause:** `b44[4]` aliases the four bytes of the big-endian `RecoilSpeed` word (`gun.h`). Byte 0 is the MSB on N64 but the LSB on little-endian PC, so `gunfire.c` read rise/fall/retrigger/offset reversed. Grenade launcher `0x081000FF` (8,16,0,-1) read as (-1,0,16,8): `field_890 >= sp1A4+sp1A0` was true at once, so the recoil phase never ran. KF7/Klobb 4+12 -> 6+0 ticks, PP7 4+8 -> 0+3. +**Fix (port, `#ifdef PORT`, use-site, semantics-preserving):** `WS_B44(ws,i)` accessor in `gun.h` reads `b44[3-i]`; all `gunfire.c` reads switched. **Rule-2 note:** endianness rather than pointer widening, so it needs sign-off as an ABI-class exception. +**Rule-2:** user sign-off 2026-09-29 ("if justified and accurate"). Accuracy check: a standalone gcc test of the real union layout with the GL (`0x081000FF`) and KF7 (`0x040C0006`) words gives (-1,0,16,8)/(6,0,12,4) through the old read and exactly the N64 (8,16,0,-1)/(4,12,0,6) through `WS_B44`. +**Status:** FIXED; user-verified vs GEPD/1964 2026-09-29 (GL kick, PP7 recoil). + +## D425 — Tank run-over death sound loops: `(chrflags << 7) >= 0` sign-bit latch is always true on GCC (issue #115) + +`CHRFLAG` contains `0x80000000`, so GCC makes the enum unsigned; `bondview2.c` tank-crush latch (`CHRFLAG_01000000` << 7 = sign bit) never blocked, so `CRUSHED_YELL_SFX`/`TANK_CRUSH_MAN_SFX` retrigger every tick while an ACT_DIE chr sits in the tank bounds. **Fix:** `(s32)chrflags << 7` (use-site cast; a `-1` sentinel would risk widening the enum to 64-bit). Cross-tag porting-notes D3. **Status:** FIXED; user-verified 2026-09-29 (Runway run-over plays the crush yell once). + +## D426 — Tank cannot be steered with the mouse out of aim mode (issue #116) + +Tank hull turns from stick X (`analogTurn`, `bondview2.c` ~5166); port hipfire mouse used `hipDirectCompute` (direct `vv_theta` write, ignored/overwritten in tank). **Fix (`port/src/input.c`):** in tank, hipfire mouse X emits stick turn (x `Input.TankAimScale`), mouse Y dropped (stick Y is drive). Pad CENTRED direct path in tank not audited. **Status:** FIXED; user-verified 2026-09-29 (non-aim tank mouse turning good; aim behaviour matches the original, D429). + +## D427 — Automatic fire cadence is frame-counted, ~2-3x faster than N64 at 60 fps (issue #114) + +`gunfire.c` `field_88C += 1` per rendered frame gates `field_88C % AutomaticFiringRate`; N64 renders 2-3 ticks/frame, PC 60 fps gives 1 (D13 class; D240 lineage). Faithful decomp, platform-dependent -> **needs rule-2 sign-off** before any `src/game` edit. `FpsCap 30` roughly halves the gap (untested). **Fix (rule-2, user go-ahead 2026-09-29; `gunfire.c`, `#ifdef PORT`):** the automatic-fire gate `88C % AutomaticFiringRate` now uses `rate * (g_ClockTimer >= 2 ? 1 : 2)`, so it spans the same number of TICKS as a console 2-tick frame. Only that gate changes. **Rejected first attempt:** slowing `field_88C` itself (advance per 2 ticks) made the counter sit on the same value for two frames, so every `88C == 0` / `%` gate fired twice (measured `1 5 1 5` tick gaps = no slowdown, and it would have double-fired pistols). **Measured** (`-level_09`, AK47 via `GE_STARTWEAPON=8`, held fire, one shot per line of a temporary probe, since removed): before = 3 ticks/shot at 60 fps; after = 6 ticks/shot at FpsCap 60 and FpsCap 30 alike (N64 at 2 ticks/frame = 6). Semi-autos are recoil-time (tick-scaled) bound and unchanged. +**Status:** FIXED; user-verified vs GEPD/1964 2026-09-29 (AK47 empties in ~3 s, Klobb normal). **Known limit (sub-agent review, 2026-09-29):** `field_88C` is never rescaled, so at mixed 1/2/3 ticks-per-frame pacing (unstable ~40-55 fps) the modulus flips 2x<->1x and can produce an early extra shot or jittery cadence; steady 60 (or 30) fps is exact. No stall and no same-frame double fire. Headless measurement was steady-state only. + +## D428 — No gun turn-sway with direct mouse look (issue #117) + +Gun turn lag = `weapon_theta_displacement = -1.75*speedtheta` (`gun.c` `gunSetBondWeaponSway`). Direct-look paths (D238 hipDirectCompute, centred, PD aim) write the camera and leave `speedtheta`~0. Writing `speedtheta` would also move the camera; needs a display-only route or design decision. **Fix (rule-2, user go-ahead; `#ifdef PORT`):** `hipDirectCompute` records the yaw degrees it applied; hipfire feeds them to an atomic accumulator (`swayFeedTheta`, `port/src/input.c`); `bondview2.c` adds `portGunSwayTheta(g_GlobalTimerDelta)` to the `speedtheta` value passed to `gunSetBondWeaponSway` (player 0 only). The speed uses the game's own convention for deriving speed from an angle change (`bondview2.c` speedverta: delta / (2*dt), clamp +/-0.7). Camera untouched; vertical sway already worked (the game derives `speedverta` from the real `vv_verta` change). Aim mode not fed (sway-free while aiming). Pad centred-direct path not fed. Verified the hook fires (`GE_INPUTLOG` `gunsway` lines, ~0.13 speed at 8 px/poll); amount is by-feel, tune the 2*dt divisor if needed. +**Status:** FIXED; user-confirmed feels right 2026-09-29 (second pass, see below; the formula in the Fix paragraph above is the superseded first pass). **Split-screen note:** the accumulator is global and drained only by player 0; revisit when #99 (mouse on a non-zero slot) merges. + +## D429 — Tank full pass: aim-mode (RMB) mouse behaviour and tank sticking forward/back (user playtest 2026-09-29, follow-up to D426) + +After D426, non-aim mouse turning in the tank is good. Still open: (1) right-mouse aim in the tank is "wonky"; (2) the tank can seemingly get stuck driving forward or backward. Candidates (unverified): the aim-held legacy velocity-stick path (`input.c` `tankAimStick`) feeding stick Y while the game treats stick Y as drive/turret pitch depending on `insightaimmode`; digital C-up/C-down (W/S) interplay in aim mode; latched button state across the aim toggle. Needs a dedicated pass against GEPD/1964 tank behaviour, then the input layer/QoL. +**Status:** CLOSED faithful (user 2026-09-29: tank controls now match GEPD/1964; the "sticking" was drive inertia and the original also drops driving input while the mouse aims). + +## D430 — Shot light fixtures do not darken: `extract_vertex_indices_from_triangle` reads the N64 8-byte Gfx layout from the 16-byte PC Gfx (issue #119, the D415 report) + +**Root cause (code reading; D154/D135/D312 class, porting-notes B):** `lightFixtureBreak` -> `darken_triangle_in_room` / `extract_vertex_coords_from_triangle` all get their three vertex indices from `extract_vertex_indices_from_triangle` (`lightfixture.c`). Every case reads the display-list command as raw N64 storage -- `gfx->tri.tri.v[k]` for G_TRI1, `((u32*)gfx)[1]`, `((u8*)gfx)[7]`, `((u16*)gfx)[3]` ... for the four G_TRI4 triangles. The room DL on PC is the widened 16-byte Gfx (D85), where the two N64 words live in the low dwords of `words.w0`/`words.w1`, so all reads returned garbage (effectively index 0). A shot fixture therefore darkened vertex 0 of its vertex batch instead of its own vertices (and the glass-shard positions and "neighbouring triangle" search used the same wrong vertices). The sibling parser in `bg.c` (bullet hit test) was fixed for this in D154; `lightfixture.c` was missed. +**Fix (`#ifdef PORT`, no logic change):** decode from `words.w0`/`w1` with exactly D154's nibble mapping (case 0 = G_TRI1 bytes 5-7 / 10; cases 1-4 = G_TRI4 s2 0..3). The original N64 behaviour of ignoring the G_VTX v0 offset is kept. Each of the 15 index expressions was checked against the original raw-byte access. **No headless proof** (needs a shot at a fixture); verification is the Bunker/Caverns playtest. +**Note:** this explains "shooting the fixture does not kill the light". I found no flicker mechanism in the game code (the light system only darkens vertex colours >>2 and spawns glass), so if a flicker persists after this fix it is a separate effect. +**Status:** FIXED; user-verified vs GEPD/1964 (Caverns) 2026-09-29. + +## D431 — Shot light fixtures never break on PC: the bullet-hit path returns texnum -1, so `check_if_imageID_is_light` is never true (issue #119, user-confirmed on Caverns) + +`chrprop.c` calls `lightFixtureBreak()` only when `check_if_imageID_is_light(bghit.texturenum)`. On the N64 `bg.c`'s room hit-test recovers `texnum` by dereferencing 8 bytes before the SETTIMG texture data in KSEG0; on PC that is invalid for the converted room GDLs, so D154 stubbed it to `-1` (both sites, G_TRI1 and G_TRI4 loops). Result: `lightFixtureBreak` is never called for any shot (the `GE_D430` probe logged zero `break` lines during a Caverns session where the user shot lights). D430 (index parser) was a second, independent bug behind it. +**Fix (`#ifdef PORT`, no logic change):** `tex.c` already knows the image id when it opens a fixture entry; it now stores it in the entry's unused `RESERVED` slot (`lightFixtureSetTexnum`); the two `bg.c` sites return `lightFixtureTexnumForGfx(gdl, roomnum)` (same room/DL-range test `lightFixtureBreak` uses) instead of -1. Non-light textures still return -1 (impact-sound flavour unchanged, D77 parked). +**Consequence found by sub-agent review (D434):** real image ids for light hits now flow into the `g_Textures[]` consumers in `chrprop.c`/`propobj.c`, which read a raw byte 0 as the hit type (wrong on PC) -- fixed in D434. +**Status:** FIXED; user-verified vs GEPD/1964 (Caverns) 2026-09-29. + +## D432 — Gunshot sound cadence drifted from the shots after D427 (user: sounds "off", Caverns, Mac-10-class gun) + +`gunfire.c` re-triggers the shot sound when `field_A4C < g_GlobalTimer`, with `field_A4C = SoundTriggerRate + g_GlobalTimer`. The gate is evaluated once per gun-tick call (per rendered frame; each frame carries `g_ClockTimer` ticks of `g_GlobalTimer`). On the N64 (2 ticks/frame) it is only evaluated every second tick, so the next sound lands on the first frame boundary after rate+1 ticks (AK47 rate 4 -> 6). At 1 tick/frame the port opened it after exactly rate+1 (5). D427 moved the shots to 6 ticks, so shots (6) and sounds (5) drifted. **Fix:** at `g_ClockTimer < 2` add `| 1` to the rate (rate 4 -> 5, 3 -> 3, 5 -> 5, 11 -> 11), which opens the gate on the same even boundary the console used; unchanged at >= 2 ticks/frame. +**Status:** FIXED; cadence measured identical to the N64 rules (AK47 6/6, Klobb 6/12, weapon 9 4/6; zero SKIPs). Residual slight audible difference tracked as D433. + +## D428 second pass (2026-09-29) + +Probe (`GE_D428`) showed the hook firing with the input clamped at 0.7 on most fast turns. The game's natural stick turn sets `speedtheta = (stick/70)^2 * fov/60` with NO ramp (max ~1.0) and integrates `vv_theta += speedtheta*dt*3.5`, so the exact inverse for a mouse turn is `deg / (3.5*dt)` clamped to `fov/60`. The first pass used the vertical axis' `delta/(2*dt)` clamped to 0.7: too weak at full speed, too strong at slow speed. Switched; user reports a slight difference turning fast, which this addresses. + +## D433 — Automatic-gun shot sound still audibly differs slightly from GEPD/1964 (user by-ear, Caverns, 2026-09-29) + +**Measured (headless, `-level_09`, held fire, temporary shot + `GE_D207S` probes, since removed):** after D427/D432 the shot and sound-start cadence match the console's rules exactly -- AK47 shots 6 ticks / sounds 6 ticks, offset 0 on every shot; Klobb shots 6 / sounds 12 (SoundTriggerRate 11, same gate as N64); weapon 9 shots 4 / sounds 6; zero SKIPs. So the residual difference is NOT fire/sound cadence. +**Suspects (unverified):** (1) the port's RSP-audio HLE (`port/src/mixer.c`): resampler interpolation, envelope/`aEnvMixer` handling, reverb; (2) how a superseded shot sound is stopped (`sndDeactivate` -> release envelope) versus the emulator's plugin; (3) the reference itself -- 1964's audio plugin is also HLE and is not a ground-truth for timbre. **Next:** ask for a description (pitch / tail cut / loudness / reverb / overlap, which gun) or a capture of both; then compare mixer resampling and release handling to libaudio's N64 behaviour. +**Status:** OPEN. + +## D434 — Raw `((u8*)&g_Textures[n])[0] & 0xf` hit-type reads are wrong on PC (found by sub-agent review of D431; latent OOB in `g_HitTypeSounds[13]`) + +Under PORT `struct image_entry` puts `dataoffset:24` in bits 0-23 (D67), so byte 0 is the LOW byte of the data offset. On the N64 (big-endian) byte 0 is the MSB of word 0, the hit type. `chrprop.c` (impact_sounds ~1154, the explosion skip test ~1187) and `propobj.c` (~9685) used the raw byte, so on PC they indexed `g_HitTypeSounds[]` with an arbitrary nibble 0..15 (table has 13 entries; 13-15 read past it) and mis-tested the 5/6 spark skip. Harmless while `bghit.texturenum` was always -1 (D154); D431 now returns real ids for light fixtures, activating the path. (The Caverns playtest passed, so the light images' nibbles were presumably in range: luck, not design.) The comment in `image.h` claiming the N64 does the same was wrong and is corrected. +**Fix (`#ifdef PORT`, layout-driven, semantics-preserving):** use the `.hitTexture` bitfield at the three sites, as `chr.c` already does. **General rule (for porting-notes):** un-stubbing a `-1` sentinel (D154/D135/D431) re-activates every dormant consumer of the value -- grep all readers and check each for raw-layout reads and table-bounds assumptions first. +**Status:** FIXED; built. Live check owed (shoot light fixtures across levels; impact sound/sparks on lights). diff --git a/docs/dev/release-dropin-rom-plan.md b/docs/dev/release-dropin-rom-plan.md index 9f4b9c67..1781b080 100644 --- a/docs/dev/release-dropin-rom-plan.md +++ b/docs/dev/release-dropin-rom-plan.md @@ -1,5 +1,7 @@ # Release plan — "drop in your ROM and play" +> **Historical record — do not act on status claims in this file.** Part A shipped as the NTSC-U drop-in ROM flow (PR #84, issue #6). The remaining Part B pieces are tracked in the roadmap. Current open work and status: [`docs/ROADMAP.md`](../ROADMAP.md) (2026-09-29). + Status: proposal / not yet scheduled. Owner: TBD. ## Goal diff --git a/docs/internals.md b/docs/internals.md index 6d7666a7..816750cc 100644 --- a/docs/internals.md +++ b/docs/internals.md @@ -10,7 +10,7 @@ description: Architecture behind the PC port; the software RSP-emulation approac > external references. The phased plan in section 8 is largely done through > Phase 2; for current status see the > [README](https://github.com/jkdansereau/goldeneye-pc-port#status) and -> [`dev/LEVEL-STATUS.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/dev/LEVEL-STATUS.md); +> [`ROADMAP.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/ROADMAP.md); > for the blow-by-blow finding log see > [`dev/findings.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/dev/findings.md). @@ -411,7 +411,7 @@ sync/flush) is shared. * Bring in the PD `fast3d` (gfx_pc / gfx_opengl / gfx_cc / gfx_sdl2). * Verify the custom CC/RM modes against `gmain.s`; add a `G_SETTEX` decode path only if it turns out to be used (see §5; it appears unused). -* Port `pdsched.c` (GE variant) to drive the software RSP. +* Port `pdsched.c` (GE variant) to drive the software RSP. *(Done differently: GE compiles its real `src/sched.c` and shims its hardware leaf calls in `port/src/libultra.c`, which runs the software RSP inline — see the CMakeLists.txt `SRC_ENGINE` comment.)* * Goal: render the title screen / first level. ### Phase 3: Audio + input diff --git a/docs/porting-notes.md b/docs/porting-notes.md index 05598fdd..d8b5eadf 100644 --- a/docs/porting-notes.md +++ b/docs/porting-notes.md @@ -1346,6 +1346,10 @@ darkening from a nonzero `ENV_ALPHA` LERP factor is very likely faithful (possibly authored/original-dev-intentional tinting), not a bug — do not "fix" it without an N64/1964-GEPD reference screenshot proving otherwise. +## D17. Un-stubbing a `-1` sentinel re-activates every dormant consumer of the value (D431/D434) + +A port stub that forces a value to `-1` (D154 `texnum`, D135) can hide latent layout bugs in its other readers. When restoring a real value, grep ALL readers of the field first and check each for raw-layout reads and table-bounds assumptions. Instance: D431 made light-fixture hits return real image ids, which reached `chrprop.c`/`propobj.c` reading `((u8*)&g_Textures[n])[0] & 0xf` as a hit type -- on PC byte 0 is the low byte of `dataoffset` (LE bitfield layout, D67), so it indexed `g_HitTypeSounds[13]` with 0..15. Use the `.hitTexture`/`.hitSound` bitfields (as `chr.c` does). + ## E. Process / method notes - **Never `exit()` while another thread may be inside the GL driver (D344).** diff --git a/port/src/input.c b/port/src/input.c index 08ecc0d0..e14c427a 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -505,6 +505,8 @@ static int aimGepdAccumulate(double dxPx, double dyLook); static void aimGepdEdgeScroll(void); static int aimGepdCompute(double dxPx, double dyLook); static int hipDirectCompute(double dxPx, double dyLook); +static double s_hipThetaDegOut; /* D428: yaw degrees the last hipDirectCompute applied */ +static void swayFeedTheta(double deg); static int padDirectCompute(int dx, int dy); /* D404: pad twin of the above */ /* D194: bondview2's "look-ahead" pitch centreing (docentreupdown) arms during * hip-fire walking whenever the pitch strays from the horizon target, and -- @@ -1914,14 +1916,21 @@ unsigned inputComputePad(int idx, signed char *stick_x, signed char *stick_y) * unrecognised" and fast motion bang-bang). Falls through to * the legacy stick path below when it declines (disabled, no * player, or a safety gate is closed). */ - if (mouseDirectLook && hipDirectCompute(hipEdx, hipDyLook)) { + /* D426 (#116): in a tank the hull turns from stick X (analogTurn) and stick Y + * is drive, so the direct camera write is ignored/overwritten -- emit + * mouse X as stick turn (scaled by Input.TankAimScale) and drop Y. */ + int inTankHip = (g_PlayerIsInTank == 1); + if (!inTankHip && mouseDirectLook && hipDirectCompute(hipEdx, hipDyLook)) { + swayFeedTheta(s_hipThetaDegOut); /* D428: hipfire gun turn-lag */ /* Pitch handled inside hipDirectCompute too; nothing left * to do for yaw/pitch this poll. Digital pitch-pulse * (naturalPitchMode==0) still applies below only in the * legacy path, so skip both branches here. */ } else { - sx += (int)(hipEdx * hipSens * MOUSE_TURN_GAIN); - if (naturalPitchMode) { + sx += (int)(hipEdx * hipSens * MOUSE_TURN_GAIN * (inTankHip ? tankAimScale / 100.0 : 1.0)); + if (inTankHip) { + /* no pitch/drive from the mouse in a tank */ + } else if (naturalPitchMode) { /* D194/D238: SOLITARE gives hipfire pitch the same * continuous analog stick treatment as yaw -- same * formula as the sx line above, so X and Y are, by @@ -2584,6 +2593,42 @@ static int aimGepdCompute(double dxPx, double dyLook) * stick path); 0 to fall back (disabled, no player, or a safety gate is * closed -- e.g. mid-death or mid-cutscene, matching GEPD's !dead/!watch). */ +/* D428 (#117): the gun's turn-lag ("sway") is driven by player->speedtheta + * (gunSetBondWeaponSway, bondview2.c), which the game derives from STICK + * deflection. Direct mouse look writes vv_theta instead and leaves speedtheta + * at ~0, so the gun stayed glued to the view. Accumulate the yaw degrees the + * direct path applied (poll thread) and hand the game thread an equivalent + * turn speed once per tick: the exact inverse of the game's own yaw + * integration (see portGunSwayTheta). Hipfire only; display-side. */ +static SDL_atomic_t s_swayThetaMdeg; + +static void swayFeedTheta(double deg) +{ + SDL_AtomicAdd(&s_swayThetaMdeg, (int)lround(deg * 1000.0)); +} + +float portGunSwayTheta(float dt) +{ + int m = SDL_AtomicSet(&s_swayThetaMdeg, 0); + if (dt <= 0.0f) + return 0.0f; + /* Exact inverse of the game's own yaw integration (bondview2.c: + * vv_theta += speedtheta * dt * 3.5) and of its natural-turn speed, which is + * set straight from the stick with no ramp: speedtheta = (stick/70)^2 * fov/60, + * i.e. |speedtheta| <= fov/60 (~1.0). So a mouse turn of X degrees/tick gives + * the same gun lag the stick turning at that speed did on the N64. (An earlier + * version used the vertical axis' delta/(2*dt) clamped to 0.7: too weak at full + * speed, too strong at slow speed.) */ + f32 fov = viGetFovY(); + double lim = (fov > 0.0f) ? (double)fov / 60.0 : 1.0; + double v = (m / 1000.0) / (3.5 * (double)dt); + if (v > lim) v = lim; + if (v < -lim) v = -lim; + if (m != 0 && configGetInputLog()) + sysLogPrintf(LOG_NOTE, "GE_INPUTLOG gunsway theta=%.1f deg speed=%.3f", m / 1000.0, v); + return (float)v; +} + static int hipDirectCompute(double dxPx, double dyLook) { struct player *p = g_CurrentPlayer; @@ -2619,7 +2664,8 @@ static int hipDirectCompute(double dxPx, double dyLook) /* dyLook already carries MouseInvertY + MouseYScale (applied by the * caller before dt-scaling, same as every other consumer of dyLook) -- * do not re-apply either here. */ - p->vv_theta += (f32) (dxPx * 0.1 * sens * scale); + s_hipThetaDegOut = dxPx * 0.1 * sens * scale; + p->vv_theta += (f32) s_hipThetaDegOut; p->vv_verta -= (f32) (dyLook * 0.1 * sens * scale); if (p->vv_verta > 90.0f) p->vv_verta = 90.0f; if (p->vv_verta < -90.0f) p->vv_verta = -90.0f; diff --git a/src/game/bg.c b/src/game/bg.c index 2bed832a..c5ac6f70 100644 --- a/src/game/bg.c +++ b/src/game/bg.c @@ -7,6 +7,7 @@ #include #include #include "bg.h" +#include "lightfixture.h" #include "bondview.h" #include "chr.h" #include "debug_camera.h" @@ -3603,7 +3604,8 @@ bool bgTestRayIntersectionInRoom(coord3d *from, coord3d *to, coord3d *dir, RoomV else { #ifdef PORT - texnum = -1; /* D154: KSEG0 texnum deref invalid for converted GDLs (cf. D135) */ + /* D154: KSEG0 texnum deref invalid for converted GDLs (cf. D135). D431 (#119): light fixtures are the one texture class the game acts on, so recover their image id from the fixture table. */ + texnum = lightFixtureTexnumForGfx(gdl, roomnum); #else temp.word = ((u32 *) tcmd)[1] - 8; texnum = *((u16 *) (temp.word | 0x80000000)); @@ -3788,7 +3790,8 @@ bool bgTestRayIntersectionInRoom(coord3d *from, coord3d *to, coord3d *dir, RoomV else { #ifdef PORT - texnum = -1; /* D154: KSEG0 texnum deref invalid for converted GDLs (cf. D135) */ + /* D154: KSEG0 texnum deref invalid for converted GDLs (cf. D135). D431 (#119): light fixtures are the one texture class the game acts on, so recover their image id from the fixture table. */ + texnum = lightFixtureTexnumForGfx(gdl, roomnum); #else temp.word = ((u32 *) tcmd)[1] - 8; texnum = *((u16 *) (temp.word | 0x80000000)); diff --git a/src/game/bondview2.c b/src/game/bondview2.c index f565b948..b00c81aa 100644 --- a/src/game/bondview2.c +++ b/src/game/bondview2.c @@ -7722,6 +7722,16 @@ void MoveBond(s8 stick_x, s8 stick_y, u16 buttons, u16 oldbuttons) f32 weapon_speed_verta; sp14C_temp = g_CurrentPlayer->speedtheta; +#ifdef PORT + { + /* D428 (#117): direct mouse look leaves speedtheta ~0; add the + * equivalent yaw speed so the gun lags turns again (player 0 owns + * the mouse). Defined in port/src/input.c. */ + extern float portGunSwayTheta(float dt); + if (get_cur_playernum() == 0) + sp14C_temp += portGunSwayTheta(g_GlobalTimerDelta); + } +#endif weapon_speed_verta = (g_CurrentPlayer->speedverta / 0.7f) + (g_CurrentPlayer->field_A4 / CHR_OBJ_MAXSPEED); @@ -7865,10 +7875,19 @@ void MoveBond(s8 stick_x, s8 stick_y, u16 buttons, u16 oldbuttons) if (sp6C->actiontype == ACT_DIE) { #if defined(VERSION_US) + /* D425 (#115): CHRFLAG is unsigned on GCC (has 0x80000000); N64 tests the sign bit */ +#ifdef PORT + if (((s32)sp6C->chrflags << 7) >= 0) +#else if ((sp6C->chrflags << 7) >= 0) #endif +#endif #if defined(VERSION_JP) || defined(VERSION_EU) +#ifdef PORT + if (((s32)sp6C->chrflags << 7) >= 0 && lvlGetControlsLockedFlag() == 0) +#else if ((sp6C->chrflags << 7) >= 0 && lvlGetControlsLockedFlag() == 0) +#endif #endif { sp6C->chrflags |= CHRFLAG_01000000; diff --git a/src/game/chrprop.c b/src/game/chrprop.c index eb504362..99cfec0d 100644 --- a/src/game/chrprop.c +++ b/src/game/chrprop.c @@ -1151,7 +1151,13 @@ void chraiDefaultWeaponFireHandler(s32 hand) } else { +#ifdef PORT + /* D434: byte 0 of the PORT image_entry is the low byte of dataoffset, not the + * hit type (N64: byte 0 = MSB = hit type). Use the bitfield (cf. chr.c). */ + impact_sounds = g_HitTypeSounds[g_Textures[bghit.texturenum].hitTexture]; +#else impact_sounds = g_HitTypeSounds[((u8 *) (&g_Textures[bghit.texturenum]))[0] & 0xf]; +#endif } if (createSpark) @@ -1184,7 +1190,12 @@ void chraiDefaultWeaponFireHandler(s32 hand) { recall_joy2_hits_edit_flag(shotdata.weapon, finalpos, besttexture); +#ifdef PORT + /* D434: see the impact_sounds read above (raw byte 0 != hit type on PC). */ + if ((g_Textures[besttexture].hitTexture != 5) && (g_Textures[besttexture].hitTexture != 6)) +#else if (((0xf & ((u8 *) g_Textures)[besttexture * 8]) != 5) && ((((u8 *) g_Textures)[besttexture * 8] & 0xf) != 6)) +#endif { rooms[0] = bestroom; rooms[1] = 255; diff --git a/src/game/gun.h b/src/game/gun.h index 1ce99c43..a897c7e6 100644 --- a/src/game/gun.h +++ b/src/game/gun.h @@ -319,6 +319,14 @@ s32 bondwalkItemHasAmmo(ITEM_IDS item); void gunDrawSight(s32 *gdl); +/* D424 (#118): b44 aliases the four bytes of the big-endian RecoilSpeed word; + * byte 0 is the MSB on the N64 but the LSB on little-endian PC. */ +#ifdef PORT +#define WS_B44(ws, i) ((ws)->b44[3 - (i)]) +#else +#define WS_B44(ws, i) ((ws)->b44[i]) +#endif + WeaponStats *get_ptr_item_statistics(ITEM_IDS item); ITEM_IDS getCurrentPlayerWeaponId(GUNHAND hand); diff --git a/src/game/gunfire.c b/src/game/gunfire.c index d21ff3bc..792a2d3b 100644 --- a/src/game/gunfire.c +++ b/src/game/gunfire.c @@ -3222,7 +3222,20 @@ void gunTickHandState(enum GUNHAND hand, s32 triggerOn) && (currentPlayerGetIsAiming() == 0) && (((s32) handptr->volley % 3) != 0))) { + #ifdef PORT + /* D427 (#114): field_88C counts RENDERED frames and the + * automatic-fire cadence is 88C % AutomaticFiringRate. The N64 + * renders ~2 ticks per frame; the port at 60 fps gives 1, so + * every automatic gun fired 2-3x faster in wall time. Scale the + * modulus so it spans the same number of TICKS the console's + * 2-tick frames did (identical to N64 at >= 2 ticks/frame). + * Only this gate changes: 88C itself and every other 88C==0 + * "first frame" gate stay per-frame, and animation time + * (field_890) stays tick-scaled. */ + if (((s32) handptr->field_88C % (bondwalkItemGetAutomaticFiringRate(var_s1) * ((g_ClockTimer >= 2) ? 1 : 2))) == 0) +#else if (((s32) handptr->field_88C % bondwalkItemGetAutomaticFiringRate(var_s1)) == 0) +#endif { if ((getPlayerCount() == 1) || ((checkGamePaused() == 0) && (g_CurrentPlayer->mpmenuon == 0))) { @@ -3302,7 +3315,19 @@ void gunTickHandState(enum GUNHAND hand, s32 triggerOn) if ((g_CurrentPlayer->hands[1 - hand].field_A50 != g_GlobalTimer) && (handptr->field_A4C < g_GlobalTimer)) { +#ifdef PORT + /* D432 (#114 follow-up): this gate compares against g_GlobalTimer + * every gun tick. On the N64 (2 ticks/frame) it is only evaluated + * every second tick, so the next sound lands on the first frame + * boundary after rate+1 ticks: rate 4 -> 6, rate 3 -> 4. At 1 + * tick/frame the port let it fire after exactly rate+1 (5 for the + * AK47), which, once D427 moved the shots to 6 ticks, made shot + * and sound drift apart ("off"). Round the offset to odd (rate|1) + * so the gate opens on the same even boundary the console used. */ + handptr->field_A4C = ((s32) bondwalkItemGetSoundTriggerRate(var_s1) | ((g_ClockTimer >= 2) ? 0 : 1)) + g_GlobalTimer; +#else handptr->field_A4C = bondwalkItemGetSoundTriggerRate(var_s1) + g_GlobalTimer; +#endif sp1B4 = 1; } } @@ -3412,21 +3437,21 @@ void gunTickHandState(enum GUNHAND hand, s32 triggerOn) weapon_stats = get_ptr_item_statistics(var_s1); #if defined(VERSION_US) - sp1A4 = weapon_stats->b44[0]; - sp1A0 = weapon_stats->b44[1]; + sp1A4 = WS_B44(weapon_stats, 0); + sp1A0 = WS_B44(weapon_stats, 1); #endif #if defined(VERSION_JP) - sp1A4 = weapon_stats->b44[0]; - sp1A0 = weapon_stats->b44[1]; - stat_2 = weapon_stats->b44[2]; - stat_3 = weapon_stats->b44[3]; + sp1A4 = WS_B44(weapon_stats, 0); + sp1A0 = WS_B44(weapon_stats, 1); + stat_2 = WS_B44(weapon_stats, 2); + stat_3 = WS_B44(weapon_stats, 3); stat_4 = weapon_stats->SingleFiringRate; #endif #if defined(VERSION_EU) - sp1A4 = ((s32)weapon_stats->b44[0] * 50) / 60; - sp1A0 = ((s32)weapon_stats->b44[1] * 50) / 60; - stat_2 = ((s32)weapon_stats->b44[2] * 50) / 60; - stat_3 = ((s32)weapon_stats->b44[3] * 50) / 60; + sp1A4 = ((s32)WS_B44(weapon_stats, 0) * 50) / 60; + sp1A0 = ((s32)WS_B44(weapon_stats, 1) * 50) / 60; + stat_2 = ((s32)WS_B44(weapon_stats, 2) * 50) / 60; + stat_3 = ((s32)WS_B44(weapon_stats, 3) * 50) / 60; stat_4 = weapon_stats->SingleFiringRate * 50 / 60; #endif @@ -3456,19 +3481,19 @@ void gunTickHandState(enum GUNHAND hand, s32 triggerOn) && (handptr->weapon_hold_time != 0) #if defined(VERSION_US) - && (handptr->field_890 >= weapon_stats->b44[2]) + && (handptr->field_890 >= WS_B44(weapon_stats, 2)) #endif #if defined(VERSION_JP) || defined(VERSION_EU) && (handptr->field_890 >= stat_2) #endif - && (weapon_stats->b44[3] >= 0) + && (WS_B44(weapon_stats, 3) >= 0) #if defined(VERSION_US) // HACK: registers are swapped // addu a1, v1, a0 - && (handptr->field_890 + weapon_stats->b44[3] < (0,sp1A4) + sp1A0) - && (handptr->field_890 + weapon_stats->b44[3] >= (s32)weapon_stats->b44[2]) + && (handptr->field_890 + WS_B44(weapon_stats, 3) < (0,sp1A4) + sp1A0) + && (handptr->field_890 + WS_B44(weapon_stats, 3) >= (s32)WS_B44(weapon_stats, 2)) #endif #if defined(VERSION_JP) || defined(VERSION_EU) && (handptr->field_890 + stat_3 < sp1A4 + sp1A0) @@ -3480,7 +3505,7 @@ void gunTickHandState(enum GUNHAND hand, s32 triggerOn) handptr->field_890 = 0; handptr->field_88C = 0; #if defined(VERSION_US) - handptr->field_8A8 = weapon_stats->b44[3]; + handptr->field_8A8 = WS_B44(weapon_stats, 3); #endif #if defined(VERSION_JP) || defined(VERSION_EU) handptr->field_8A8 = stat_3; diff --git a/src/game/image.h b/src/game/image.h index 3e1dc685..30e0a8ee 100644 --- a/src/game/image.h +++ b/src/game/image.h @@ -61,10 +61,11 @@ struct tex { * u32 makes GCC pack the struct into exactly two words on both targets, and * putting dataoffset first satisfies the raw word read. The IMAGE() macro * initializer order is adjusted to match under PORT (see image.c). - * NOTE: chrprop.c/propobj.c also read `((u8*)&entry)[0] & 0xf` as a hit - * type; with this layout that yields the low nibble of dataoffset, which is - * what the N64 binary does too (the .hitSound bitfield is used by the other - * hit-sound paths in gunfire.c). */ + * NOTE (D434): chrprop.c/propobj.c read `((u8*)&entry)[0] & 0xf` as the hit + * type. On the N64 (big-endian) byte 0 is the MSB of word 0, i.e. the hit + * type; with this LE layout byte 0 is the LOW byte of dataoffset, so that raw + * read is wrong on PC (out of range of g_HitTypeSounds[13] for nibbles >= 13). + * Those sites now use the .hitTexture bitfield under PORT (as chr.c does). */ struct image_entry { u32 dataoffset : 24; // runtime: cumulative start offset within the images segment (image_entries_load converts sizes -> offsets) diff --git a/src/game/lightfixture.c b/src/game/lightfixture.c index b75779e1..59d92cbd 100644 --- a/src/game/lightfixture.c +++ b/src/game/lightfixture.c @@ -141,6 +141,39 @@ void lightFixtureEntryEnd(Gfx *DL) } +#ifdef PORT +/* D431 (#119): tex.c calls this right after lightFixtureEntryBegin() with the + * fixture texture's image id. */ +void lightFixtureSetTexnum(s32 texnum) +{ + if (current_light_fixture_slot != LIGHTFIXTURE_TABLE_MAX) + { + light_fixture_table[current_light_fixture_slot].RESERVED = (s16) texnum; + } +} + +/* Returns the light texture's image id when `gfx` lies in a fixture entry of + * `room_index`, else -1. Same room/range test lightFixtureBreak uses. */ +s32 lightFixtureTexnumForGfx(Gfx *gfx, s32 room_index) +{ + s32 i; + + for (i = 0; i < LIGHTFIXTURE_TABLE_MAX; i++) + { + /* room_index 0 marks a FREE slot whose DL range may still dangle into a + * freed room (see clear_light_fixturetable_in_room); never match it. */ + if (light_fixture_table[i].room_index == 0) { continue; } + if (room_index != light_fixture_table[i].room_index) { continue; } + if (gfx < light_fixture_table[i].ptr_start_pertinent_DL) { continue; } + if (gfx >= light_fixture_table[i].ptr_end_pertinent_DL) { continue; } + return light_fixture_table[i].RESERVED; + } + + return -1; +} +#endif + + bool check_if_imageID_is_light(s32 imageID) { if ((imageID == IMAGE_WALL_LAMP) || @@ -195,6 +228,49 @@ Vtx *lightFindVertexBaseForTri(Gfx *gfx, s32 room_index) void extract_vertex_indices_from_triangle(Gfx* gfx, u32 tri_type, s32* idx1, s32* idx2, s32* idx3) { +#ifdef PORT + /* D430 (#119): this is an unported N64 GBI parser -- raw ((u8*)gfx)[k] / + * ((u32*)gfx)[i] / gfx->tri.tri.v[] indexing assumes the 8-byte N64 Gfx, but + * the room DL is the 16-byte PC Gfx (D85 bgWidenRoomGdl). Same class + fix as + * D154 (bg.c bgTestBulletHitBackground): recover the two N64 32-bit words from + * the low dwords of words.w0/.w1. On PC every read returned garbage (all-zero + * indices), so a shot fixture darkened vertex 0 of its batch instead of its own + * vertices. Semantics otherwise unchanged (no vtxoff, as on the N64). + * tri_type 0 = G_TRI1; 1..4 = the four triangles of a G_TRI4. */ + { + const u32 gw0 = (u32) gfx->words.w0; + const u32 gw1 = (u32) gfx->words.w1; + switch (tri_type) + { + case 0: + *idx1 = (s32)((gw1 >> 16) & 0xff) / 10; + *idx2 = (s32)((gw1 >> 8) & 0xff) / 10; + *idx3 = (s32)( gw1 & 0xff) / 10; + break; + case 1: + *idx1 = gw1 & 0xf; + *idx2 = (gw1 >> 4) & 0xf; + *idx3 = gw0 & 0xf; + break; + case 2: + *idx1 = (gw1 >> 8) & 0xf; + *idx2 = (gw1 >> 12) & 0xf; + *idx3 = (gw0 >> 4) & 0xf; + break; + case 3: + *idx1 = (gw1 >> 16) & 0xf; + *idx2 = (gw1 >> 20) & 0xf; + *idx3 = (gw0 >> 8) & 0xf; + break; + case 4: + *idx1 = (gw1 >> 24) & 0xf; + *idx2 = (gw1 >> 28) & 0xf; + *idx3 = (gw0 >> 12) & 0xf; + break; + } + return; + } +#endif switch (tri_type) { case 0: diff --git a/src/game/lightfixture.h b/src/game/lightfixture.h index a5884e6e..602752fd 100644 --- a/src/game/lightfixture.h +++ b/src/game/lightfixture.h @@ -19,5 +19,12 @@ struct s_darkened_light { void lightFixtureEntryEnd(Gfx *param_1); bool check_if_imageID_is_light(s32 imageID); void lightFixtureEntryBegin(Gfx *DL); +#ifdef PORT +/* D431 (#119): image id of a fixture's texture (stored in the otherwise-unused + * RESERVED slot) and the lookup the bullet-hit code uses in place of the KSEG0 + * texnum recovery that is invalid for the port's converted room GDLs. */ +void lightFixtureSetTexnum(s32 texnum); +s32 lightFixtureTexnumForGfx(Gfx *gfx, s32 room_index); +#endif #endif diff --git a/src/game/propobj.c b/src/game/propobj.c index 1c38e18b..2cc589e4 100644 --- a/src/game/propobj.c +++ b/src/game/propobj.c @@ -9682,7 +9682,12 @@ void objHit(ShotData *shotdata, BulletHit *hit) } else { +#ifdef PORT + /* D434: raw byte 0 of the PORT image_entry is dataoffset's low byte, not the hit type. */ + impact_sounds = g_HitTypeSounds[g_Textures[texturenum].hitTexture]; +#else impact_sounds = g_HitTypeSounds[((u8 *)&g_Textures[texturenum])[0] & 0x0f]; +#endif } thing2_index = randomGetNext() % impact_sounds->thing2_len; diff --git a/src/game/tex.c b/src/game/tex.c index eefc6857..5ae9f858 100644 --- a/src/game/tex.c +++ b/src/game/tex.c @@ -997,6 +997,9 @@ s32 texLoadFromGdl(Gfx *src, s32 srcsize, Gfx *dst, void *texpool) if (check_if_imageID_is_light(texnum)) { lightFixtureEntryBegin(out); +#ifdef PORT + lightFixtureSetTexnum(texnum); /* D431 */ +#endif lightPending = TRUE; } } diff --git a/tools_pc/gen_findings_index.py b/tools_pc/gen_findings_index.py index 6df61941..445f7d18 100644 --- a/tools_pc/gen_findings_index.py +++ b/tools_pc/gen_findings_index.py @@ -54,7 +54,8 @@ def one_liner(cell: str, limit: int = 160) -> str: LEAD_RE = re.compile( r"^\**\s*(OPEN|RESOLVED|FIXED|PROPOSED|PARTIAL|LANDED|CLAMP|DISPROVEN" - r"|ROOT-CAUSED|GUARDED|ANALYZED|DIAGNOSTIC SHIPPED|NOT A BUG|CLOSED)", + r"|ROOT-CAUSED|GUARDED|ANALYZED|DIAGNOSTIC SHIPPED|NOT A BUG|CLOSED" + r"|DEFERRED|SUPERSEDED)", re.I, ) @@ -74,6 +75,7 @@ def bucket(status_cell: str) -> str: "ANALYZED": "PARTIAL", "DIAGNOSTIC SHIPPED": "PARTIAL", "NOT A BUG": "RESOLVED", + "SUPERSEDED": "CLOSED", }.get(tok, tok) for name, rx in STATUS_BUCKETS: if rx.search(status_cell):