Files
goldeneye-pc-port/docs/dev/V0.4.0-PLAN.md
T
James Dansereau 2ba72eb75f docs: v0.4.0 plan/README/backlog/porting-notes refresh for the modern-options wave
- V0.4.0-PLAN: 2026-09-27 refresh (M-wave state, D393-D397 session
  additions, M7 deferral decision).
- README: known-issues + options documentation for the landed
  crosshair/controller/options work.
- GRAPHICS-BACKLOG / porting-notes: D392/D397 and crosshair entries.
- .gitignore: per-landing sweep capture dirs.
2026-09-27 22:04:07 -04:00

516 lines
43 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<texnum>.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.