diff --git a/.github/release-notes.md b/.github/release-notes.md index 78af7545..7e1b9924 100644 --- a/.github/release-notes.md +++ b/.github/release-notes.md @@ -2,7 +2,7 @@

~32 s gameplay montage from live play sessions (no audio track) + alt="~12 s gameplay montage from live v0.3.0 play sessions (no audio track)">

> The full single-player campaign runs at a steady 60 fps with audio (music + @@ -64,6 +64,15 @@ Center on 00 Agent difficulty, which would then crash on every subsequent relaunch until the save was reset (D295); and a crash selecting most save slots with `Skip Intro` enabled (D299). +- **The elusive complete freeze is root-caused and fixed.** Players had hit + a total, non-self-recovering lockup (most recently when returning from + Caverns to the intro screen) that was real but never reproduced on demand. + It turned out to be a latent defect in the original game's own AI command + stream — a record type the engine mis-measures, which our level-teardown + timing can trigger and the N64 never presents. Fixed with a minimal change + under the project's game-code exception process (D309/D310); a second, + unrelated freeze mechanism found during the same playtest is now guarded + as well (D311). - **A NULL-pointer guard was added** around a save-data lookup that two players hit with an identical crash address (GitHub #87, D301) — this should close that crash, though we weren't able to reproduce the exact @@ -139,6 +148,16 @@ (the wrong direction) on some levels, seen on Bunker — this is the gameplay security-camera object, not the player's own view (D307). Not investigated for this release. +- **A rare, self-clearing visual quirk**: on an occasional animation + transition, Bond's model can briefly render out of place. It has no effect + on gameplay or your save, and it clears on its own or by re-entering the + level; a safeguard added this release guarantees it can never affect game + stability (D311). +- **Two collision oddities found in final playtesting**: throwable items + (mines, grenades) can occasionally clip through walls in certain spots + (D312), and bullet impacts don't appear on some surfaces — doors and + windows are unaffected (D313). Both are queued for the next release; if + you hit either, an issue with the level and spot helps a lot. ### Downloads diff --git a/.gitignore b/.gitignore index df95ccef..31d0f566 100644 --- a/.gitignore +++ b/.gitignore @@ -178,3 +178,7 @@ gdb-hang.txt # frozen asset converter (built by CI via PyInstaller; local freezes land here) tools_pc/dist/prepare-assets/ge007-convert* build-pyinst/ + +# Local working junk (frame captures, timerun state, PPM dumps) - moved here +# from repo root / scratch on 2026-09-18; never commit. +/local/ diff --git a/README.md b/README.md index 450225e4..d6bcaf12 100644 --- a/README.md +++ b/README.md @@ -39,9 +39,9 @@ account of what worked and what didn't.

~32 s gameplay montage from live play sessions -
All in-engine, running in the port, a ~32 s gameplay montage loop - from the v0.2.0 playtest. + alt="~12 s gameplay montage from live play sessions"> +
All in-engine, running in the port — a ~12 s gameplay montage + from live v0.3.0 play sessions.

## Download @@ -112,6 +112,13 @@ filtering, FOV, sensitivity); Windows and Linux, including Steam Deck. - In-level security camera props (not the player's own view) can occasionally end up facing backwards on some levels, seen on Bunker (D307). +- Rarely, Bond's model can briefly render out of place after an animation + transition — purely visual: no effect on gameplay or saves, and it clears + on its own or by re-entering the level (D311). +- 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). - Some front-end 3D models are off: the spinning Nintendo logo renders as two white blobs and the Rareware logo's texture filtering looks wrong (D75). - Surface 1's 2D billboard trees render as a solid wall of tree texture @@ -432,11 +439,11 @@ This port is a thin layer on a large amount of other people's work. **Tools and models used to develop the port** -- Qwen 3.8 (Alibaba Qwen team), run locally as the +- Qwen 3.8 (Alibaba Qwen team), run locally from the [`unsloth/Qwen3.8-27B-GGUF`](https://huggingface.co/unsloth/Qwen3.8-27B-GGUF) - `UD-Q4_K_XL` quant; - [Unsloth](https://unsloth.ai) (the GGUF quantisation and Unsloth Desktop, - used as the local model server). + repo — `UD-Q4_K_XL` as the primary quant, with other quants used along the + way; [Unsloth](https://unsloth.ai) (the GGUF quantisation and Unsloth + Desktop) as the local model server. - [pi](https://pi.dev/): the local coding-agent harness. - [Claude / Claude Code](https://claude.com/claude-code) (Anthropic). diff --git a/docs/dev/GE-ENV-PROBES.md b/docs/dev/GE-ENV-PROBES.md index aaf2aea2..64531647 100644 --- a/docs/dev/GE-ENV-PROBES.md +++ b/docs/dev/GE-ENV-PROBES.md @@ -3,7 +3,7 @@ Every `getenv("GE_…")` in the tree. The table prose is hand-curated; **`tools_pc/gen_env_probes.py`** re-greps the live sites and reports drift (NEW / GONE vars + a fresh file:line map) — run it before trusting the -File:line cells. Last reconciled 2026-09-04 (M-51). All are **env-gated**: unset = zero behavior +File:line cells. Last reconciled 2026-09-20 (v0.3.0 release review). All are **env-gated**: unset = zero behavior change, so they are safe to leave in a release build (they only cost a `getenv` on the guarded path). "Dead" below means *the finding it was built for is closed* — the probe is strip-candidate scaffolding, not that it does @@ -24,6 +24,8 @@ Two classes: | `GE_INPUTLOG` | `port/src/input.c:329`, `port/src/config.c:53` (`configGetInputLog`) | Logs computed pad state (buttons + stick) each poll when non-zero. Also `[Debug] InputLog` ini. Also traces D165 `menuptr est/tgt/eff/stick`. | **live** | | `GE_STARTMENU` (+ `GE_STARTMENU_PAGE`, `GE_STARTMENU_DIFF`) | `src/game/lv.c:398-402` (`#ifdef PORT`) | Boot straight into a front-end menu id (13=MISSION_COMPLETE, 10=BRIEFING, 7=MISSION_SELECT, 12=MISSION_FAILED, 6=MODE_SELECT); `_PAGE`=folder row (def 1=Dam), `_DIFF`=0..3. Crash-test a screen fast. | **live** | | `GE_UNLOCK_ALL` | `src/game/debugmenu_handler.c:1098` (`get_debug_enable_all_levels_flag`, `#ifdef PORT`) | Mission-select shows every solo level (playtest jump-to-any-level aid). Cached in a `static int c`. | **live** | +| `GE_DEBUG_UNLOCKALL` | `src/game/file2.c:404` (`#ifdef PORT`) | Forces the "all levels unlocked" flag TRUE regardless of the shipped save — same effect as `GE_UNLOCK_ALL` but at the file2 unlock-query site. Cached in a `static int`. | **live** (playtest aid) | +| `GE_DEBUGMENU` | `src/boss.c:566` (`#ifdef PORT`) | Makes the L+R debug menu respond to live controller input (C-up+C-down open/close, stick + buttons read from pad 0) instead of the `#ifndef DEBUGMENU` path; unset reproduces that path exactly. Cached in a `static int`. | **live** (playtest aid) | | `GE_OPTIONSOVERLAY` | `port/src/optionsoverlay.c:231` | Force-enable the F10 port-layer options overlay (D184 / PR #10) regardless of ini. `atoi != 0`. | **live** | | `GE_D204` | `port/src/audio.c` (`audioSetNextBuffer`) | **Audio-health monitor (D204).** One line per 5 s of wall clock: `rt=` produced-audio/wall ratio (1.000 healthy; below 1 = the 30 Hz audio retrace is starving and the device is padding with silence), `q=` SDL queue depth / `queueLimit` (pinning at the limit with `drop=` climbing = overproduction, the "eventually no audio" state), `drop=` blocks discarded, `max=` largest block vs its `g_MaxFrameSize*4` allocation. **Deliberately cheap (~30 clock reads/s) — safe to leave on for a full playtest, unlike `GE_MIXERTRACE`.** The instrument of record for D202. | **live** | | `GE_D204_OLD` | `port/src/audio.c` (`d204OldMode`) | Restores pre-D204 behaviour (`osAiGetLength()` reports the whole SDL queue depth; `queueLimit` back to 8192) so before/after can be A/B'd in **one binary**, without build-to-build variance. Temporary — remove when D204 closes. | **live** (D204 A/B) | @@ -47,8 +49,9 @@ Two classes: | `GE_D243CAM` | `src/game/bondview2.c` (`bondviewFrozenCameraTick`, three `CAMERAMODE_POSEND` branches) | Dumps eye/look-at positions (+theta/verta on the `gBondViewCutscene` branch, +dt/angle/pad on the orbit branch) per call. **Found M-143**: the `gBondViewCutscene` branch fires and its look-at (`field_3C4/8/C`) sweeps through a filter catch-up after the cutscene warp — the shake mechanism. | **live** (D243) | | `GE_D243M` | `src/game/bondview2.c` (`solo_char_load`, `bondviewRemovePlayerBody`, `playerTick`; cached `d243mEnabled`) + `src/game/objecthandler.c` (`drawjointlist`, both region variants) + `src/game/chr.c` (`chrTick`, `after_position_update:`) | M-151/M-154/M-159 probe for the Dam-abseil "frozen Bonds + shake": `D243M: CREATE/REMOVE` at both `bodyModel` sites; `D243M: tick` (idx/model/**propptr/chrptr**/cam modes/`prop->pos`/`field_488.pos`/`field_3B8`/`field_3C4-8-C`/**rp** = render_pos pointer, **never dereferenced** — M-154 fix 2: tick-time `render_pos` holds arbitrary stale slot contents until the first draw pass, so the pre-view root translation is logged by `chrdraw` only) while the model exists or POSEND/INTRO is active; `D243M: draw` per on-screen player render submission; `D243M: chrdraw` — M-154 per-frame model-draw census, one line per CHR render submission (Model* + raw root translation) while a scripted camera mode (POSEND/INTRO/SWIRL/FADESWIRL) is active; a second Bond instance = extra lines with *distinct* model pointers at the same frame (same model is drawn twice/frame — two passes — dedupe by (frame, model)); `D243M: onscreen` (M-159/M-162/M-163/M-166, `chr.c`) — one line per `chrTick` call while the probe is active: `vis=` `posIsOnScreen`'s result (M-159's frustum hypothesis, REFUTED by M-160) + the live `prop->pos` + `actiontype=`/`anim=` (M-160/M-164: which of `chrTick`'s branches fired, the attached-animation pointer) + `aidx=`/`animframe=` (M-166: the animation reverse-resolved to its `animation_table_ptrs1` index, GE_D193A's technique, + its current playback phase — tests whether the visible chr's animation *phase* at cutscene-entry, not just its identity, is what differs from N64) + `x2active=`/`x2raw=` (M-162/M-163, gated on `GE_D243X2`: whether the decisive save/restore experiment ran this tick, and the raw pre-restore position it overrode — the whole `prop->pos` vector as of M-163, not just Z). Silent during FPS play, silent on the `GE_D243X2` fields unless that env var is also set. Note: `field_3B8` x/z read at 10× world units in the smoke capture — not plain world position. | **live** (D243) | | `GE_D243X2` | `src/game/chr.c` (`chrTick`, gated additionally on `d243mProbeActive()`) | **Not a probe — a decisive behavioral experiment, off by default, NOT a fix.** M-162/M-163: saves `prop->pos` right before `chrTick`'s position-update dispatch (`chr.c:2541-2617`) runs, restores it at `after_position_update:` — before the render-matrix computation later in the same tick can see the cycled value (unlike M-161, which restored too late to have any effect). Tests whether `chrTick`'s own animation-position write (M-160/M-164) is what produces the Dam-abseil shake. M-162 was Z-only (confirmed the mechanism on that axis, but the visible glitch persisted because that capture's chr cycled on Y instead); M-163 widened it to the whole vector. **M-163's live result: NEGATIVE for the visible shake** — `prop->pos` genuinely went smooth (log-verified) but the shake was unchanged, because the camera's look-at filter reads a *different* variable (`field_488.pos`, see `GE_D243X3`) that this experiment never touches. Kept in the tree as a real, useful (if ultimately not the visible-symptom fix) data point — always pair with `GE_D243M=1` so the `x2active=`/`x2raw=` fields on `D243M: onscreen` show whether/what it overrode. Do not ship. | **live, test-only** (D243) | -| `GE_D243X3` | `src/game/bondview2.c` (`playerTick`, the `field_488.pos` write site, gated on `g_CameraMode == CAMERAMODE_POSEND`) | **Not a probe — the experiment that actually confirmed the mechanism, off by default, NOT a fix.** M-167: skips the `field_488.pos.x/y/z` assignment (`bondview2.c:10495-10497`) entirely while POSEND is active, freezing it at whatever value it held the instant suppression engaged. `field_488.pos` — not `prop->pos` — is what the cutscene's leaky-integrator look-at filter (M-143, `field_3B8`/`field_3C4-8-C`) actually reads; it comes from `render_pos` (the model's animated-skeleton readback), an independent data path from `prop->pos`. **Live-confirmed positive: user — "yep camera shake is gone now."** Logs a companion `D243M: f488write frame=... idx=... x3active=... x3skip=... epoch=... f488pos=x,y,z` line (always-on when `GE_D243M=1`, independent of whether `GE_D243X3` itself is set) so the freeze is log-verifiable. **M-169: no longer freezes forever** — re-baselines (`x3skip=0`, write allowed through) exactly on the tick a legitimate shot-change teleport fires (`d243GetTeleportEpoch()`, bumped by `chrai.c`'s `AI_TRYTeleportingChrToPad` right after its real `setsuboffset()` call), addressing the M-168 postmortem's "camera looks the wrong way for later shots" incompleteness — untested live yet, next session's first move. Revert once a real root-cause fix replaces it; do not ship as-is. | **live, test-only** (D243) | +| `GE_D243X3` | `src/game/bondview2.c` (`playerTick`, the `field_488.pos` write site, gated on `g_CameraMode == CAMERAMODE_POSEND`) | **Not a probe — the experiment that actually confirmed the mechanism, off by default, NOT a fix.** M-167: skips the `field_488.pos.x/y/z` assignment (`bondview2.c:10495-10497`) entirely while POSEND is active, freezing it at whatever value it held the instant suppression engaged. `field_488.pos` — not `prop->pos` — is what the cutscene's leaky-integrator look-at filter (M-143, `field_3B8`/`field_3C4-8-C`) actually reads; it comes from `render_pos` (the model's animated-skeleton readback), an independent data path from `prop->pos`. **Live-confirmed positive: user — "yep camera shake is gone now."** Logs a companion `D243M: f488write frame=... idx=... x3active=... x3skip=... epoch=... f488pos=x,y,z` line (always-on when `GE_D243M=1`, independent of whether `GE_D243X3` itself is set) so the freeze is log-verifiable. **M-169: no longer freezes forever** — re-baselines (`x3skip=0`, write allowed through) exactly on the tick a legitimate shot-change teleport fires (`d243GetTeleportEpoch()`, bumped by `chrai.c`'s `AI_TRYTeleportingChrToPad` right after its real `setsuboffset()` call), addressing the M-168 postmortem's "camera looks the wrong way for later shots" incompleteness — untested live yet, next session's first move. **Superseded: D243 shipped its permanent fix (`4ccab22c`, M-170), which removed the X3 suppression block from `playerTick` — no live site remains.** Kept here as a tombstone; the generator flags it GONE. | **dead** (D243 closed — probe removed) | | `GE_D243X4` | `src/game/chr.c` (`chrTick`, the `render_pos` snapshot/restore, gated on `d243mProbeActive()`) | **Not a probe — the M-168 experiment applying X3's technique to the actual draw-path consumer, off by default, NOT a fix.** Snapshots `model->render_pos[]` after a 2-tick warm-up once POSEND engages, then holds it steady by copying the cached contents back over each subsequent tick's freshly-computed buffer — `subcalcmatrices()` always still runs first (never skipped; the first two attempts skipped it and crashed on a reused-pointer stale `-1` tombstone, see findings.md D243 M-168). Addresses Bond's own rendered-body duplication (the consumer X3 never touched). Crash-free, stable, but same forever-freeze incompleteness as X3 pre-M-169. **M-169: same teleport-epoch re-baseline as X3** — the warm-up counter now also resets (re-snapshots) whenever `d243GetTeleportEpoch()` changes, not only on the active/inactive edge. Untested live yet. Revert once a real root-cause fix replaces it; do not ship as-is. | **live, test-only** (D243) | +| `GE_D243SWIRL` | `src/game/bondview2.c:10480` (`playerTick`, cached `d243SwirlTarget`) | D243 follow-up probe: `GE_D243SWIRL=` logs a per-tick line for that chr while the swirl/fade camera path is active (tick counter + state), to verify post-fix behavior of the swirl transition. Inert unless set to a non-negative chr number. | **live** (D243 verification) | | `GE_D193` | `src/game/frametiming.c` (`waitForNextFrame`, `#ifdef PORT`) | Per-wall-second line: `render=` rendered frames, `sim=` sim ticks delivered (Σ of the `deltaFrames`/`g_ClockTimer` fed downstream), `clamped=` frames whose catch-up hit `FRAMETIMING_PORT_MAX_CATCHUP`, `maxraw=` largest pre-clamp delta. Healthy steady state = `render=30 sim=60 clamped=0 maxraw=2` (measured M-80, all levels idle). Use to check whether the wall-clock timer under-advances under real AI load (D193). Cheap; safe for a full playtest. | **live** (D193) | | `GE_D193A` | `src/game/chr.c` (`chrTick` → `d193aSample`, plus `d193aMoved` in `chrUpdateAnim`; `#ifdef PORT`) | Per-wall-second per-ticked-chr locomotion telemetry (D193/D209): `chr=` chrnum, `act=` ACT_TYPE, `speed=` world-units travelled/s, `gtd=` g_GlobalTimerDelta, `tier=` `act_gopos.unk59` SPEED tier (0 walk / 1 run / 2 sprint; `-1` when not ACT_GOPOS), `gspd=` `act_gopos.speed` (**a turn rate, not a travel rate** — 0 means running straight), `typ=` PROP_TYPE (3=CHR, 6=VIEWER), `tick=`/`move=`/`frz=` chrTick entries vs `chrUpdateAnim` calls vs freeze-gated (move « tick = the chr is being visibility-culled out of its own locomotion; `move=0` at high `speed` = WAYMODE_MAGIC), `hid=` CHRFLAG_HIDDEN, then the root-motion scalars `scale` / `xlscale` / `animrate` / `playspeed` / `mspeed` (model->speed) / `endframe` / `anim` ptr / `aidx=` that pointer reverse-resolved to its `animation_table_ptrs1` index (40 walking, 41 sprinting, 42 running, 107 walking_unarmed — map via `src/game/initanitable.c`). `GE_D193A_CHR=` restricts to one chrnum. **This probe found D209**: Cradle showed `tier=1` (RUN) with `aidx=40/107` (walk anims) on every sample. | **live** (D193/D209) | | `GE_D193B` | `src/game/model.c` (`sub_GAME_7F06D3F4`, `#ifdef PORT`) | Rate-limited (1/97) raw anim root-motion decode trace (D193): `joint=` / `flip=` / `base=` mtx channel index / `frame=` / `tmp=` decoded s16 translation triple from the anim bitstream / `angle=` / `anim` ptr / `bd=`+`bs=` the D32 u32 bitDescriptors/bitStream addresses / `njoints`. M-80 Facility: `base=0`, `angle=0` **always** (root-motion facing delta never non-zero — top lead), `tmp` ≈ (±small, ~1086 hip-height, ±small). Compare `tmp`/`angle` magnitudes against a clean N64 decomp build of the same anim+frame. | **live** (D193) | @@ -61,6 +64,13 @@ Two classes: | `GE_D116` | `port/fast3d/gfx_pc.cpp:1710,2362`, `src/game/textrelated.c:265,541` (cached `ge_d116`) | HUD/text "X-mirror" (D114/D116) vbo/uv trace. **D114/D116 CLOSED — NOT A BUG (M-33/D168): the captures were upside-down, not mirrored.** Probe is now dead scaffolding — strip on the next cleanup pass. | dead (D114/D116 closed) | | `GE_D288` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_tri1`, right after the D233 trivial-reject guard; cached `ge_d288`) | M-152 diag for the Silo intro stray triangle: logs any triangle whose clip-space NDC bbox covers >15% of the screen or goes non-finite, with each vertex's `w` and `any_behind_camera`. **Investigation in progress** — narrowed to two candidates (an unguarded `w`-division NaN leak in the backface-cull test just below, or the D106/D271 portal-BFS family), not yet root-caused; needs a live/headless `-level_20` run to disambiguate. | **live** (D288) | | `GE_D236RM` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_tri1`, unconditional getenv, no state kept when unset) | M-155/pass-10 diag for D236: per-frame (`num_dls`) triangle count of the `0x0c184b50` render-mode class (`other_mode_l` exact match), heartbeat-logged every 300 frames even at zero. **REFRAMED D280's "run-to-run dependent instantiation" as ordinary room-based occlusion**, not a CPU-side gating bug — see `## D236` pass-10 writeup. | **live** (D236 OPEN, kept for future passes) | +| `GE_D236ORDER` | `port/fast3d/gfx_pc.cpp:1844` (`gfx_sp_tri1`, cached `s_d236order`) | D236 pass-12 diag: per-DL sequence log of the tree-class triangles (DL index, screen-space depth, noise min/max census) to test the draw-order hypothesis. Zero cost unset; remove once D236 concludes. | **live** (D236 OPEN) | +| `GE_D236ALPHA` | `port/fast3d/gfx_pc.cpp:1896` (`gfx_sp_tri1`, cached `s_d236alpha`) | D236 pass-14 diag: for the `0x0c184b50` tree-class render mode, logs the combine/alpha state of up to 4000 matching triangles (deduped on combine change) so the captured draw can be cross-checked against the source. Zero cost unset; remove once D236 concludes. | **live** (D236 OPEN) | +| `GE_D236ZFIX` | `port/fast3d/gfx_pc.cpp:1970` (`gfx_sp_tri1`, cached `s_d236zfix`) | D236 pass-12 test override: forces `depth_update=true` for the `0x0c184b50` tree-class render mode — a cheap mechanism test before deciding what a real fix looks like. Off by default; remove once D236 concludes. | **live, test-only** (D236 OPEN) | +| `GE_D236RAW` | `src/game/bg.c:2437` (`#ifdef PORT`) | D236 pass-16 diag: one-shot census of the raw vertex table's alpha-byte distribution (5 buckets + min/max), camera-independent, to check whether any vertex in the whole table exceeds the low ceiling. Zero cost unset; remove once D236 concludes. | **live** (D236 OPEN) | +| `GE_D245V` | `src/game/sky.c:962` (`skyPortRenderPoly`-adjacent, `#ifdef PORT`) | D245 water-seam diag: per-call min/max of the recomputed S/T texture-coordinate ranges for the water fan vertices (rate-limited), to check whether the seam tracks a tc-shift boundary. Inert unset. | **live** (D245 OPEN) | +| `GE_D245_FIXEDSHIFT` | `src/game/sky.c:946` (`#ifdef PORT`) | D245 test override: pins the water tc shift `k` to the given integer (0..SKY_TC_MAX_SHIFT) instead of the computed per-vertex value, to A/B the allowShift=TRUE behavior. Off by default (unset = current behavior). | **live, test-only** (D245 OPEN) | +| `GE_D252` | `port/fast3d/gfx_pc.cpp:733` (`import_texture`, cached `s_d252`) | D252 rainbow-particle diag: one-shot per (address,size) import log of the tile state (tmem/fmt/siz/addr/size/line) for small textures so a suspect particle tile can be checked against its source bytes by hand. Remove once D252 is resolved. | **live** (D252 OPEN — non-reproducible) | ## Dxx diagnostic probes — finding closed unless noted @@ -92,6 +102,14 @@ Two classes: | `GE_D104` | `src/game/bg.c:654` (rate-limited, `d104c`) | D104 depth-clear trace | dead (D104 closed) | | `GE_D71LOG` | `port/fast3d/gfx_pc.cpp:645` | D71 fast3d trace | dead (D71 closed) | | `GE_D75` | `src/game/title.c` `sub_GAME_7F007F30` (capped 8) | D75 Bug 2 gun-barrel model probe: logs `chrModelInstance`/`gunModelInstance` ptr+obj+numMatrices, `render_pos`, `renderData.mtxlist`, `g_GfxMemPos`, `osVirtualToPhysical(render_pos)` | **LIVE** (D75 Bug 2 OPEN — see §F "D75 Bug 2 — RUNTIME PROBE") | +| `GE_D305` | `src/snd.c:420` (`sndHandleEvent` desync branch, `#ifdef PORT`) | D305: logs the sound-preemption desync state when it is detected (after the NULL-guard fix made the crash itself impossible) — confirms whether `Game.AllUnlocked` correlates or was coincidental, and whether it recurs. Inert unset. | **live** (D305 monitoring) | +| `GE_D309` | `src/game/chrai.c:810,3424`, `src/game/chraction.c:10139` (all `#ifdef PORT`) | D309 freeze probe: logs AI label-not-found restarts (`chrai.c`) and the run-to-bond persistent-list step-on path (`chraction.c`, capped 400 lines) — the diagnostics used to root-cause the elusive complete freeze (D309/D310). Inert unset. | **live** (D309/D310 fixed — kept for recurrence) | +| `GE_D314` | `port/src/optionsoverlay.c:681` (cached `s_d314Enabled`) | D314 F10-flicker probe: logs the overlay's viGetY / visible-row count / scroll / selection each poll. Inert unset; strip candidate once D314 is triaged. | **live** (D314 pending triage) | +| `GE_D318R` | `port/src/random.c:69,147` (`#ifdef PORT`) | PRNG golden probe (D284/D318): logs the first 4000 gameplay-PRNG draws with tick + caller return address, and every `randomSeed` set — verifies the bit-exact port of `random.s` and which call sites consume the stream. Inert unset. | **live** (D284 verification) | +| `GE_RSEED` | `port/src/random.c:134` (`#ifdef PORT`) | Test-only boot-seed override: `GE_RSEED=` pins `g_randomSeed` at first seed instead of the wall-clock-derived value — for determinism and N64-parity tests. Inert unset. | **live, test-only** | +| `GE_RSEED_LV` | `port/src/d318watchdog.c:232` (`#ifdef PORT`) | One-shot level-start seed log: logs `g_randomSeed` at the first tick after level start (optionally armed for a specific tick via `@`) — ties a saved/loaded game's PRNG state to a level for D318-class repro. Inert unset. | **live** (D318 verification) | +| `GE_D318T` | `port/src/d318watchdog.c:270` (`#ifdef PORT`) | D318 watchdog timeline probe: per-tick log of the Facility ai_22 softlock signature window (c78 list/offset/attack state, c67 health) while the watchdog watches. Inert unset. | **live** (D318 verification) | +| `GE_D318W` | `port/src/d318watchdog.c:136` (`#ifdef PORT`) | **Not a probe — the D318 port-side watchdog itself, ON by default.** Detects the Facility ai_22 hold-pose softlock signature (a latent N64 race, see findings.md D318) for 600 consecutive ticks and re-seeds c78's attack via the game's own fresh-attack entry point. `GE_D318W=0` opts out; logs `D318W:` lines when it acts. **Permanent fix, not strip-candidate.** | **live** (D318 fix — permanent) | ## Notes diff --git a/docs/dev/agentic-development.md b/docs/dev/agentic-development.md index d06f1c7e..caf1254f 100644 --- a/docs/dev/agentic-development.md +++ b/docs/dev/agentic-development.md @@ -68,7 +68,7 @@ All dates from this repository's own commit history (August 2026). gantt dateFormat YYYY-MM-DD axisFormat %b %d - title GoldenEye 007 PC port; from fork to playable front end + title GoldenEye 007 PC port; from fork to v0.3.0 section Local model - Qwen 3.8 via pi Fork + PC-port scaffolding :m1, 2026-08-16, 1d Full compile + link (~230 TUs) :m2, 2026-08-20, 1d @@ -84,6 +84,10 @@ gantt SDL input layer (kbd/mouse/gamepad) :2026-08-29, 1d Front-end flow (menu to briefing to start) :m7, 2026-08-30, 1d File-backed EEPROM saves :2026-08-31, 1d + section Month one - front end to v0.3.0 + Audio mixer (music + SFX) :2026-09-02, 7d + Steam Deck hardening + F10 overlay QoL :2026-09-14, 3d + v0.3.0 released (full campaign playtested) :milestone, 2026-09-18, 0d ``` - **Day 0** (16 Aug): repository forked, PC-port scaffolding added. @@ -97,50 +101,59 @@ gantt unattended play window without crashing.** - **Day 14** (30–31 Aug): front end playable end to end (menu → mission select → difficulty → briefing → start); file-backed saves. +- **Day 33** (18 Sep): **v0.3.0 released** — the full campaign playtested + end to end at Agent difficulty, audio complete, bundles for Windows, + Linux and Steam Deck. So roughly **two weeks**, one person part-time, to take a decompilation from "builds an N64 ROM" to "boots on desktop, renders every solo level, playable -through the front end into the early game"; with audio and a set of cosmetic -issues still outstanding. +through the front end into the early game". The rest of month one (29 Aug – +18 Sep) took it from there to a public release: the 21-level sweep to +full-campaign completion, the audio mixer, input and Steam Deck polish, +widescreen FOV scaling, and the v0.3.0 bundles — with a set of cosmetic +issues documented rather than outstanding. ## By the numbers | | | |---|---| -| Calendar time | 26 days (17 Aug – 11 Sep 2026), one person part-time | -| Commits on the port | ~430 | -| Root-caused bugs logged | `D1`–`D226` in [`findings.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/dev/findings.md), 116 still-listed entries (some labels later merged or withdrawn) | -| Handoff sessions | 89+ (`M-2` … `M-89`, and counting) | -| Game-source files given `#ifdef PORT` ABI edits | 67 files, ~305 blocks | -| New port-layer / tooling files | ~114 | -| Port layer | ~21,900 lines C/C++ (`port/`) | -| PC asset-conversion tooling | ~7,050 lines Python (`tools_pc/`) | -| Outcome | intro + all 21 solo missions render and are crash-free, front end playable, in-level SFX + menu/intro music play; in-level music and full-campaign completion re-confirmation still owed | +| Calendar time | 34 days (16 Aug – 18 Sep 2026), one person part-time | +| Commits on the port | ~690 | +| Root-caused bugs logged | `D1`–`D311` in [`findings.md`](https://github.com/jkdansereau/goldeneye-pc-port/blob/main/docs/dev/findings.md), 190 tracked entries in the index (some labels later merged or withdrawn) | +| Handoff sessions | 190+ (`M-2` … `M-191`, and counting) | +| Game-source files given `#ifdef PORT` ABI edits | 71 files, ~385 blocks | +| New port-layer / tooling files | ~119 | +| Port layer | ~24,450 lines C/C++ (`port/`) | +| PC asset-conversion tooling | ~7,180 lines Python (`tools_pc/`) | +| Outcome | **v0.3.0 released**: full campaign playable end to end (all 21 missions playtested at Agent difficulty) at a steady 60 fps, full audio (music + SFX), file-backed saves; Windows, Linux and Steam Deck; known cosmetic defects documented in the finding log | ### Commit velocity ```mermaid xychart-beta title "Commits per day" - x-axis ["8/16", "8/17", "8/20", "8/21", "8/22", "8/23", "8/24", "8/27", "8/28", "8/29", "8/30", "8/31", "9/1"] - y-axis "commits" 0 --> 60 - bar [1, 2, 2, 6, 9, 2, 4, 7, 57, 39, 39, 46, 9] + x-axis ["8/16", "8/17", "8/20", "8/21", "8/22", "8/23", "8/24", "8/27", "8/28", "8/29", "8/30", "8/31", "9/1", "9/2", "9/3", "9/4", "9/5", "9/6", "9/7", "9/8", "9/9", "9/10", "9/11", "9/12", "9/13", "9/14", "9/15", "9/16", "9/17", "9/18"] + y-axis "commits" 0 --> 90 + bar [1, 2, 2, 6, 9, 2, 4, 7, 57, 39, 39, 46, 11, 52, 39, 8, 8, 2, 8, 34, 17, 30, 20, 30, 57, 4, 84, 38, 22, 13] ``` -Days with no commits are omitted. The 8/25–8/26 gap is between phases; the -step up from 8/28 onward is the collaborative phase in full swing, including -the parallel multi-agent "bursts" used near the end. +Days with no commits are omitted (18–19 and 25–26 Aug). The step up from +8/28 onward is the collaborative phase in full swing, including the parallel +multi-agent "bursts"; the 9/15 spike is the Steam Deck playtest-feedback +batch landing at once. ### Who did what ```mermaid -pie showData title "Commits by agent (raw count)" +pie showData title "Commits by agent (raw count, snapshot through early September)" "Claude" : 161 "Local model (Qwen 3.8 / pi)" : 62 ``` (Phase A, the first 26 commits, to 24 Aug, was entirely the local model, -solo; from 27 Aug on the two agents worked in parallel.) +solo; from 27 Aug on the two agents worked in parallel. The raw tally was +not re-run for the final third of the project; treat the milestone-weighted +estimate below as the better number.) | Milestone / workstream | Primary agent | Weight | |---|---|---| @@ -208,9 +221,11 @@ let them substitute for each other: usage limit mid-problem, the local model picked the task up from the HANDOFF state and continued; when the local model hit a bug that needed deeper structural reasoning, it wrote up where it was and Claude took over. -2. **`findings.md`**; the chronological finding log. 162 numbered entries, - each a root cause with `file:line` evidence and the fix. New agents (either - model) are pointed at the relevant entries before they start. +2. **`findings.md`**; the chronological finding log. 311 numbered `D` + entries by v0.3.0 (190 tracked in the index; some labels merged or + withdrawn), each a root cause with `file:line` evidence and the fix. New + agents (either model) are pointed at the relevant entries before they + start. 3. **`porting-notes.md`**; the append-only "recurring bug classes" file. The single highest-leverage artifact: it stopped both models from re-deriving the same class of N64→PC bug over and over. @@ -262,7 +277,8 @@ 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** (parked below crash/level work): audio is not -implemented; several front-end 3D transforms and some text rendering are -wrong. See +**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). diff --git a/docs/index.md b/docs/index.md index bf68ea4b..f920a944 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,10 +1,11 @@ --- title: GoldenEye 007 PC Port -# v0.2.1 (Bing SEO round 2): re-shortened to 133 chars (was 161, 1 over -# Bing's 160 limit) and bumped the stale v0.2.0 mention. +# v0.3.0: status bumped from v0.2.1; Honest-status bullets synced with the +# README's v0.3.0 known issues (D255/D282/F10-dup/overscan items removed as +# fixed). Description kept at 133 chars (Bing's 160 limit). description: >- A native PC port of the 1997 N64 classic, built from decompiled source with - a software RSP. v0.2.1 for Windows, Linux and Steam Deck. + a software RSP. v0.3.0 for Windows, Linux and Steam Deck. ---