Performance work D473-D477; scaffolding batches 3-4

Uncached getenv per vertex batch (D473), fast3d micro-optimisations (D474),
hitch diagnosis (D475), CI4 palette-bank cache key (D476), spike counters,
CI8 palette churn measurement (D477). Dead diagnostic probes removed.

Refs: D473 D474 D475 D476 D477 GE_PERFSTAT
This commit is contained in:
James Dansereau committed 2026-10-06 18:35:41 -04:00
1 parent b735190fd9
commit 88bcef32a3
17 files changed
+229 -835

No files matched your search

+8 -8
View File
@@ -50,13 +50,13 @@ Two classes:
| `GE_WRAPFIX` | `port/fast3d/gfx_pc.cpp:3166` (`gfx_set_wrap_fix`) | RC3 test override for the `Video.WrapFix` D74/D167 wrap-block path (`atoi != 0` wins over the ini). | **live** (RC3 not finished) |
| `GE_SAVELOG` | `src/game/file.c:46`, `src/game/file2.c:16` (`SAVELOG` macro), `src/boss.c:749`, `src/game/objective_status.c:297,343` | Traces the campaign save/unlock chain (objective completion → `end_of_mission_briefing` → `fileWriteSave` → EEPROM). Used to confirm D157. `g_savelogObjOnce` gates the per-criterion dump to the `bossReturnTitleStage` call. | **live** (strip once a few more level boundaries are playtested) |
| `GE_D160` | `src/boss.c:744`, `src/game/bondview2.c:791`, `src/aicommands.def:10055,10181,10236,10242,10247` (all `#ifdef PORT`) | Dam exit-cutscene (D148/D160) diagnostic: trace points in `bossReturnTitleStage`, `bondviewSetCameraMode`, and AI cmds `EndLevel`/`exit_level`/`CameraLookAtBondFromPad`/`CameraSwitch`. **Investigation in progress** — user owes a `GE_D160=1` Dam run. | **live** |
| `GE_D243` | `src/game/front.c` (`menu_init`, cached `s_d243`) | Per-menu-transition trace with a local per-tick frame counter (D243 stale-DL-burst screen identification). Counter is NOT numerically identical to the port's internal frame count — cross-reference by elapsed offset. | **live** (D243) |
| `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_D243` | `src/game/front.c` (`menu_init`, cached `s_d243`) | Per-menu-transition trace with a local per-tick frame counter (D243 stale-DL-burst screen identification). Counter is NOT numerically identical to the port's internal frame count — cross-reference by elapsed offset. | **dead** (D243 FIXED) — removed 2026-10-01 (scaffolding strip batch 3, branch `chore/strip-d243-probes`); kept as a tombstone — the generator flags it GONE |
| `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. | **dead** (D243 FIXED) — removed 2026-10-01 (scaffolding strip batch 3, branch `chore/strip-d243-probes`); kept as a tombstone — the generator flags it GONE |
| `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. | **dead** (D243 FIXED) — removed 2026-10-01 (scaffolding strip batch 3, branch `chore/strip-d243-probes`); kept as a tombstone — the generator flags it GONE |
| `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. | **dead** (D243 FIXED) — removed 2026-10-01 (scaffolding strip batch 3, branch `chore/strip-d243-probes`); kept as a tombstone — the generator flags it GONE |
| `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=<chrnum>` 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_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. | **dead** (D243 FIXED) — removed 2026-10-01 (scaffolding strip batch 3, branch `chore/strip-d243-probes`); kept as a tombstone — the generator flags it GONE |
| `GE_D243SWIRL` | `src/game/bondview2.c:10480` (`playerTick`, cached `d243SwirlTarget`) | D243 follow-up probe: `GE_D243SWIRL=<chrnum>` 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. | **dead** (D243 FIXED) — removed 2026-10-01 (scaffolding strip batch 3, branch `chore/strip-d243-probes`); kept as a tombstone — the generator flags it GONE |
| `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=<n>` 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) |
@@ -84,8 +84,8 @@ Two classes:
| `GE_D235_NEXT=<stage id>` | `src/boss.c` (with `GE_D235_RELOAD`) | **TEMP**: re-enter a *different* stage after the abort (level A → title → level B), used for D336 (Runway → Frigate). `GE_D85TEX=1` now also logs each stage's texture-pool size and address range (`src/game/initmttex.c`). | **TEMP** |
| `GE_D320R="tick:action;..."` | `port/src/d320repro.c` (hooked in `src/game/lv.c` per-tick PORT block) | **TEMP** forced-repro harness keyed on `g_GlobalTimer`. Actions: `roster`, `jump=<chr>,list=<hexid>,off=<n>`, `bit=<hex>`, `trace=<chr>`, `rename=<chr>,<num>`, `seed=<hi>,<lo>` (D320); `explode=<type>,<dist>` (explosionCreate ahead of Bond) and `cheat=gl|rl|inv|<id>` (game cheat activation) for D252 headless explosion/debris captures. | **dead** (D320 fixed via D329) — removed 2026-09-30 (ROADMAP §7 strip pass, branch `chore/strip-temp-probes`); tombstone — the generator flags it GONE; `port/src/d320repro.c` + its `lv.c` hook deleted |
| `GE_AIMLOG=1` (+ `GE_INPUTSCRIPT` tokens `MDX<n>` `MDY<n>` `RHOLD` `LHOLD` `RREL`) | `port/src/input.c` | D337 aim-model harness: scripted sustained mouse deltas (treated as a grabbed mouse) and aim holds, plus a per-poll line with aim state, active device, the game's drawn crosshair x, `crosshair_x/y_pos` and camera angles. Measures range, edge-scroll and jitter headlessly. | **live** |
| `GE_PERFSTAT=1` | `port/fast3d/gfx_pc.cpp` (`gfx_run`/`gfx_end_frame`), `port/fast3d/gfx_opengl.cpp` (start/end_frame), `src/boss.c` (main loop) | #92/D339 perf probe: per-300-frame averages of render phases (pre / display-list / post / swap+pace), tris and draw batches per frame, game-thread busy time per scheduler message, and a GL_TIME_ELAPSED GPU timer (meaningful only when GPU-bound). | **live** |
| `GE_D75V` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_vertex`, end of the copy/transform loop; cached env check) | D75 (Nintendo logo) per-batch vertex probe: for each `G_VTX` batch in frames `lo..hi`, logs frame#, source pointer, n_vertices, original dest_index, raw ob[0..2] of the first vertex, min/max of the transformed x/y/z/w across the batch, and the MP translation row. Used to prove the transform is correct (M-195) and that every batch encodes v0=0 (slot-overwrite hypothesis). Value form `"lo-hi"` (e.g. `300-900` = the logo screen); unset = off. | **live** (D75) |
| `GE_PERFSTAT=1` | `port/fast3d/gfx_pc.cpp` (`gfx_run`/`gfx_end_frame`), `port/fast3d/gfx_opengl.cpp` (start/end_frame), `src/boss.c` (main loop) | #92/D339 perf probe: per-300-frame averages of render phases (pre / display-list / post / swap+pace), tris and draw batches per frame, game-thread busy time per scheduler message, and a GL_TIME_ELAPSED GPU timer (meaningful only when GPU-bound). 2026-10-01: also prints `PERFSTAT spikes: max=..ms at frame N (dl= run=) >20ms= >33ms=` per 300-frame window (hitches hide in the averages). | **live** |
| `GE_D75V` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_vertex`, end of the copy/transform loop; cached env check) | D75 (Nintendo logo) per-batch vertex probe: for each `G_VTX` batch in frames `lo..hi`, logs frame#, source pointer, n_vertices, original dest_index, raw ob[0..2] of the first vertex, min/max of the transformed x/y/z/w across the batch, and the MP translation row. Used to prove the transform is correct (M-195) and that every batch encodes v0=0 (slot-overwrite hypothesis). Value form `"lo-hi"` (e.g. `300-900` = the logo screen); unset = off. **D473 (2026-10-01): the cache was broken (re-read on every G_VTX when unset); now a real one-shot cache.** | **live** (D75) |
| `GE_D75D` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_vertex` G_VTX case + `gfx_sp_tri1` rejection/emit gates; cached env check) | D75 (Nintendo logo) per-frame draw-path probe (M-196): for frames `lo..hi`, logs one line/frame with the V/T token stream (`seq`), max triangle vertex index, cumulative vertices loaded, and per-triangle accounting — trivial-reject / backface-cull counts, how many tris reach GL, how many of those are on-screen (NDC bbox ⊂ [−1,1]²), invisible-flag count, vertex-color min/max, first oml word, depth z/w range + NaN count, texture-sampler presence (`utex` = GL shader, `cutex` = combiner), combine mode, prim/env color. Used to REFUTE the slot-overwrite hypothesis (DL is streamed, max index 15) and narrow the bug to a texture-modulate material rendering black. M-197 added logo-mode `lg_*` fields (sampled only on tris with the logo combine word — the frame-level `utex`/`cutex` are first-tri-wins and were the source of the "combiner claims 0" false anomaly): `lg_utex`/`lg_tex` + tile-0 descriptor + texture buffer address/id/size for the logo material. Value form `"lo-hi"` (logo window ≈ `244-320` on a bare front-end boot); unset = off. Companion one-shot `D75DCC:` log in `gfx_generate_cc` fires when `combine_mode==0x009ffe4f19ffe4f1` to decode the logo's combine slots. | **live** (D75) |
| `GE_PDMOUSEAIM` | `port/src/input.c:487` (`pdMouseAimEnabled`) | Force on/off the PD-style mouse-aim model (D194 lineage); unset = default (on in N64 aim mode unless legacy GEPD aim is selected). | **live** |
+7 -2
View File
@@ -58,7 +58,7 @@ D169,FIXED,Front-end mouse pointer can't reach the outer cells of the mission/le
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.",FIXED (2026-09-29 bookkeeping:
D177,FIXED,Ladders non-functional —,FIXED (M-36) — high confidence; interactive climb test owed
D177,FIXED,Ladders non-functional —,"FIXED (M-36) — high confidence; interactive climb test owed Owed climb test considered cleared by the 2026-09-28 v0.4.0 campaign sign-off (ROADMAP §3 bookkeeping, 2026-10-01); reopen if seen."
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:"
@@ -90,7 +90,7 @@ D108-D112,RESOLVED,skeletal models: d43_emit.py put_f32 byte-reversal bug,resolv
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,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
D150,FIXED,watch OBJECTIVES / BRIEF page crash:,"FIXED (src/str.c #ifdef PORT); interactive re-verify pending Owed re-verify considered cleared by the 2026-09-28 v0.4.0 campaign sign-off (ROADMAP §3 bookkeeping, 2026-10-01); reopen if seen."
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:
@@ -332,4 +332,9 @@ D469,FIXED,Gamepad presets + per-seat controller rebinding (ROADMAP 5a; supersed
D470,FIXED,Audio master volume + output-device selection (ROADMAP 5a):,FIXED (live listening test owed)
D471,FIXED,Controller-family button names (no glyphs) in the PC menus + player-index LEDs:,FIXED 2026-10-01 (local); headless captures for Xbox/PS/Nintendo; live PS/Nintendo pad check owed
D472,FIXED,"F10 overlay polish: multi-pad ownership (audit G1-G3), widescreen pillarbox (D335b), red EXPERIMENTAL rows, unknown-ini-key warning.","FIXED 2026-10-01 (merged; port-only, no src/game)."
D473,FIXED,"Uncached getenv(""GE_D75V"") on every G_VTX:",FIXED 2026-10-01 (local); Statue display-list CPU about -55% in steady windows
D474,FIXED,fast3d micro-optimisations:,FIXED 2026-10-01 (local); output-identical by construction; measured gain on the dev machine within noise
D475,OPEN,Statue/Cradle hitches diagnosed:,OPEN (diagnosed 2026-10-01):
D476,FIXED,CI4 textures keyed on their own 16-entry palette bank instead of the whole 256-entry table (D217 hash):,FIXED 2026-10-02 (local); Statue sweep CI4 cache misses 189 -> 36
D477,?,CI8 palette re-imports measured and ruled out as a CPU-side hitch cause:,MEASURED 2026-10-02 (local); premise of the D475/D476 follow-up refuted on the dev box; GPU/driver-side cost unmeasured
D450,FIXED,"Unplugging and replugging a controller was not picked up (user, GameSir-G7 Pro; 2026-09-29).","FIXED — port-only: the render thread's gfx_sdl_handle_events drains the same SDL queue as video.c's host pump but had no case for SDL_CONTROLLERDEVICEADDED/REMOVED, so it usually swallowed them and i…"
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
58 D178
59 D175
60 D176
61 D177
62 D172
63 D174
64 D183
90 D113-D116
91 D117
92 D118
93 D150
94 D260-D265
95 D266-D269
96 D270
332 D470
333 D471
334 D472
335 D473
336 D474
337 D475
338 D476
339 D477
340 D450
+59 -5
View File
@@ -443,7 +443,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| 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 | 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 |
| 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 Owed climb test considered cleared by the 2026-09-28 v0.4.0 campaign sign-off (ROADMAP §3 bookkeeping, 2026-10-01); reopen if seen. |
| 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 |
@@ -489,7 +489,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| 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) | **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 |
| 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 Owed re-verify considered cleared by the 2026-09-28 v0.4.0 campaign sign-off (ROADMAP §3 bookkeeping, 2026-10-01); reopen if seen. |
| 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 |
@@ -522,7 +522,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D140 | **Pause-menu crash (M-23 open → M-27 fixed).** — full `## D140` entry at file tail | FIXED (`src/game/bondview.h`, `src/game/bondview2.c`; `#ifdef PORT`). Exposed the next watch-render crash → **D141**. `-level_09`/`-level_20` unregressed (3/3). porting-notes.md §A. |
| D118d | **Watch-menu list over-scrolls with keyboard (M-27, user bug report).** `game_options_inventory_navigation` / `sub_GAME_7F0A611C` (`options.c`) have a "slam the stick" fast-scroll: a raw *level* check `joyGetStickY(PLAYER_1) < -0x46` / `>= 0x47` that steps `watch_inventory_cursor_pos` by 1 **every frame** it's held. An N64 stick rarely sustains past ~0x46 on-axis so a human taps it; keyboard W/S (and SDL pads pegged at 0x80) sit there every frame → one keypress skips several items (breaks item selection needed to finish the game). Fix: `#ifdef PORT` macro `GE_WATCH_STICK_FAST{UP,DOWN}` → `0` for the port — stick list-nav then goes only through the latched single-step path (`watch_stick_y_pressed_*`, one step per press, existing game mechanism) and the -0x1e..-0x45 smooth-scroll band; D-pad/C-buttons keep edge-repeat. N64 `#else` = verbatim. | FIXED (`src/game/options.c`, `#ifdef PORT`). Build green, `-level_09` unregressed. **Feel to be confirmed in playtest** (probe couldn't reach the INVENTORY watch page headlessly). D118 input family. Latent sibling: `front.c` menu nav uses `joyGetStick*InRange` level checks the same way. |
| D141 | **Watch-menu item-model crash (M-27), exposed by D140.** — full `## D141` entry at file tail | FIXED (`src/game/gunfire.c`, `#ifdef PORT`). Watch page now renders (weapon page verified — mirrored text D114/D116, dark weapon model D75, both parked cosmetic). `-level_09`/`-level_20` unregressed. |
| 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. |
| 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. Owed re-verify considered cleared by the 2026-09-28 v0.4.0 campaign sign-off (ROADMAP §3 bookkeeping, 2026-10-01); reopen if seen. |
| 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 | **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 —… |
@@ -566,7 +566,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D260 | **v0.2.1 (user request): mouse-wheel weapon-select direction swapped.** — full `## D260` entry at file tail | FIXED — build green; one-line branch swap, trivially reviewable. |
| D261 | **v0.2.1 (user request: "fix watch menu sensitivity for wasd/controller buttons"): holding W/S (or a fully-pressed stick axis) on the watch inventory page…** — full `## D261` entry at file tail | FIXED — feel-check received; W/S sensitivity follow-up logged above. |
| D262 | **v0.2.1 (user report): "3D models missing on watch menu screen item select" — the selected-item 3D preview does not appear on the watch inventory page.** — full `## D262` entry at file tail | FIXED (primary cause, D264) — pixel-level confirmation pending user run. |
| D264 | **D262's root cause (ABI/layout class, cf. D140/D52): the watch item preview's `ModelRenderData` template is read across two adjacent globals that no longer sit…** — full `## D264` entry at file tail | FIXED — build green, probes removed; visual confirmation pending. |
| D264 | **D262's root cause (ABI/layout class, cf. D140/D52): the watch item preview's `ModelRenderData` template is read across two adjacent globals that no longer sit…** — full `## D264` entry at file tail | FIXED — build green, probes removed; visual confirmation pending. Covered by the 2026-09-28 v0.4.0 campaign sign-off (ROADMAP §3 bookkeeping, 2026-10-01). |
| D263 | **v0.2.1 (user report): Facility — "Ourumov not shooting Trevelyan"; the scripted opening beat never triggers. RESOLVED: not a port bug — the beat is…** — full `## D263` entry at file tail | RESOLVED (not a bug), refined (v0.2.1 further user testing, 2026-09-15). The D318 re-report (2026-09-18) turned out to be a **different** failure mode (derailment race → softlock, see D318 RESOLVED) — D263's proximity-trigger mechanism map stands. |
| D265 | **D236 investigation (port-layer, `port/fast3d/gfx_pc.cpp`): the tree cards are IA8/IA4, not IA16 — and `import_texture_ia4` decoded the RDP's I2:A2 texel as…** — full `## D265` entry at file tail | CLOSED (2026-09-26 bookkeeping: sub-pass of D236, which closed in M-198). Earlier: PARKED — reverted to baseline; evidence + suspect list above. |
| D258 | **PAL/JP asset conversion is broken at the source-data level — `scripts/filelist.e.csv` and `filelist.j.csv` carry naming defects, so `gen_romassets.py e/j` (and…** — full `## D258` entry at file tail | DEFERRED (user call 2026-09-28: v0.4.0 ships NTSC-only, README-disclosed; the verified repair path is post-v0.4 backlog). Earlier: OPEN — repair path verified, not applied (out of v0.2.0 scope). |
@@ -653,7 +653,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| 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 | **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. |
| 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. Accepted in use: the maintainer used the F10 overlay extensively on 2026-10-01 (D469/D470/D472 testing) with no complaint about row styling. |
| 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. |
| D372 | **Low-end perf preset (v0.4.0 modern options wave M5, 2026-09-27).** full `## D372` entry at file tail | FIXED (headless-verified; awaiting user live accept): one-row `Video.LowEndMode` in F10 VIDEO; ON writes FpsCap 30 + MSAA x1 (restart), OFF restores compiled defaults (60/2). Registered key persists through orderly exit. |
@@ -740,6 +740,11 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D470 | **Audio master volume + output-device selection (ROADMAP 5a):** `Audio.MasterVolume` (0..100, scratch-copy gain on the final mix, default 100 = byte-identical) and `Audio.Device` (name, default system device; live reopen on the audio-producer thread, fallback on missing/removed device). | FIXED (live listening test owed) |
| D471 | **Controller-family button names (no glyphs) in the PC menus + player-index LEDs: PlayStation/Nintendo pads see their own button names in the F10/front-end help lines and the pad-binding column; Xbox/Steam Deck/unknown keep the old text exactly** — full `## D471` entry at file tail | FIXED 2026-10-01 (local); headless captures for Xbox/PS/Nintendo; live PS/Nintendo pad check owed |
| D472 | **F10 overlay polish: multi-pad ownership (audit G1-G3), widescreen pillarbox (D335b), red EXPERIMENTAL rows, unknown-ini-key warning.** — full `## D472` entry at file tail | FIXED 2026-10-01 (merged; port-only, no src/game). Headless-verified pillarbox/red rows/warning at 1280x720; multi-pad ownership by code reading only, live check owed. |
| D473 | **Uncached `getenv("GE_D75V")` on every G_VTX: the probe's `d75v_lo == -1` meant both "not read yet" and "disabled", so with the env unset `gfx_sp_vertex` re-ran getenv on every vertex batch (~55-59% of render-thread CPU by sampling; D302/D250 class)** — full `## D473` entry at file tail | FIXED 2026-10-01 (local); Statue display-list CPU about -55% in steady windows |
| D474 | **fast3d micro-optimisations: unchanged `set_tile`/`set_tile_size` no longer dirty the textures (each dirty = cache lookup + `gfx_flush()` batch break); per-triangle/per-vertex debug hooks (D75D/ZF/D303) behind one cached gate** — full `## D474` entry at file tail | FIXED 2026-10-01 (local); output-identical by construction; measured gain on the dev machine within noise |
| D475 | **Statue/Cradle hitches diagnosed: low-CPU ~33 ms frames are the game's own frame limiter skipping a retrace (`boss.c` tick-interval check) after a 13-16 ms gfx task; CPU-side bursts are CI-palette re-imports (D217 keys CI textures on palette content), not first-sight loads. Room-texture pre-warm was built but gives ~no gain (kept on unmerged branch `perf/prewarm`, default off)** — full `## D475` entry at file tail | OPEN (diagnosed 2026-10-01): next levers = palette-as-texture/LRU for CI re-imports; frame-limiter interplay needs a design look |
| D476 | **CI4 textures keyed on their own 16-entry palette bank instead of the whole 256-entry table (D217 hash): unrelated TLUT loads no longer re-import unchanged CI4 textures** — full `## D476` entry at file tail | FIXED 2026-10-02 (local); Statue sweep CI4 cache misses 189 -> 36 |
| D477 | **CI8 palette re-imports measured and ruled out as a CPU-side hitch cause: decode+upload averages ~0.008 ms/frame (worst frame 0.44 ms); the cache already holds the animated palettes (1314 misses, 10 re-misses); one CI8 texture is drawn with several palettes per frame, so an in-place update path would thrash** — full `## D477` entry at file tail | MEASURED 2026-10-02 (local); premise of the D475/D476 follow-up refuted on the dev box; GPU/driver-side cost unmeasured |
| D407 | **Tanks cannot be boarded/exited on PC — the v0.4.0 use/reload split (D378/D393) removed the B-button tap the engine's tank handlers in `bondview2.c` consume (user report 2026-09-28, Runway/Streets)** — full `## D407` entry at file tail | PARTIAL (fix landed in v0.4.0, port-only): present `GE_CONT_B` on the use (E / pad A) rising edge only while `g_PlayerIsInTank == 1` or `g_BondCanEnterTank != 0`; E keeps its D378 no-reload-fallback semantics elsewhere, N64 layout unaffected. D407(b) same cycle: front PC Options page-edge highlight clamp + mouse-wheel / W-S paging (wheel queue consumed on the menu, D223); its wheel mapping shipped inverted and was fixed to match W/S (wheel up = step up). D407(c): board-animation lockout, menu-accept B gate in tank states, in-tank aim routed through the legacy velocity stick + `Input.TankAimScale` knob. RESOLVED 2026-09-28: all port-only (src/game zero-diff), 10 TANKDBG probes stripped, release binary verified clean, user live tank drive signed off (board + exit OK, aim feel good). |
| D408 | **`Game.SkipIntro` skips the post-mission failure dossier: with it on, entering a level then aborting (watch Z+A) or dying (KIA) returns straight to the menus — no REPORT / "Mission status: KILLED IN ACTION / ABORTED" screen** (user report 2026-09-28; save/AllUnlocked ruled out) — full `## D408` entry at file tail | OPEN (cosmetic, not root-caused past the handoff site). v0.4.0 decision: SkipIntro stays EXPERIMENTAL / not recommended for regular users — F10 row relabelled "Skip intro (EXPERIMENTAL)". |
| D450 | **Unplugging and replugging a controller was not picked up (user, GameSir-G7 Pro; 2026-09-29).** — full `## D450` entry at file tail | FIXED — port-only: the render thread's `gfx_sdl_handle_events` drains the same SDL queue as video.c's host pump but had no case for `SDL_CONTROLLERDEVICEADDED/REMOVED`, so it usually swallowed them and `inputRescanPads` never ran. Both pumps now post a request; the rescan runs at the top of `inputUpdate`, on the thread that reads the pads. |
@@ -11718,6 +11723,8 @@ Archived capture: `ge007_d243_live.log`.
D173 (level-start floating), D292 (death sinking), and the Cradle spin (also under D148) are cross-referenced as the same "third-person world model transform/lifecycle" family (M-145, and the M-160/M-164/M-165 addendum on D173/D292's own entries). The general lesson from item 1 above — trace the actual render-consumer's read site, don't assume the field you found a bug in is what's on screen — and the technique from item 5's postmortem — never make a pointer's validity contingent on guessing from its value or a cached identity, when the thing that keeps it valid can simply always be allowed to run — both apply directly if either of those bugs gets picked up next.
**2026-10-01: diagnostics stripped (ROADMAP §7 scaffolding strip, batch 3, branch `chore/strip-d243-probes`).** D243 is FIXED (M-189/M-190); the investigation scaffolding is removed, behaviour unchanged. Removed: `GE_D243M` and its plumbing (`d243mEnabled`/`d243mProbeActive`/`d243mGetFrameCounter`, `D243M:` CREATE/REMOVE/tick/draw/f488write/firinganim lines in `bondview2.c`; chrdraw census in both `drawjointlist` variants of `objecthandler.c`; animrestart in `chraction.c`; playanim in `chrai.c`; setanim_caller/setanimspeed/setanimplayspeed/tickstart/postadv/biganim and the clamp log lines in `model.c`; prebefore/onscreen in `chr.c`), `GE_D243X2` (prop->pos save/restore) and `GE_D243X4` (render_pos freeze) in `chr.c`, `GE_D243CAM` (3 sites) and `GE_D243SWIRL` in `bondview2.c`, and the `GE_D243` menu-transition trace + `d243MenuName` in `front.c`. (`GE_D243X3` was already gone.) Kept (the shipped fix and its supports): the `sizeof(Model)` buffer-layout fix in `bondview2.c` (M-189); the unconditional `field_488.pos` write + teleport-epoch filter re-seed (M-190: `d243NotifyTeleport`/`d243GetTeleportEpoch`, the call in `chrai.c` `AI_TRYTeleportingChrToPad`); `gameScriptedCameraActive()` and its two users (`model.c` clamps, `port/src/input.c` mouse-look gate); the M-183/M-185 playspeed/endframe clamps in `model.c` (Rule-2 retention sign-off, M-192) minus their log lines. Left alone: `GE_DEBUGMENU` in `src/boss.c` (a debug-menu enabler whose comment cites D243; not a D243 probe).
## D244 — Self-hosted CI runner (`corpyvt6agent01`) link failure: `undefined reference to 'objectiveGetStatus_WEAK'` (2026-09-12, first-ever build attempt on the…
**Self-hosted CI runner (`corpyvt6agent01`) link failure: `undefined reference to 'objectiveGetStatus_WEAK'` (2026-09-12, first-ever build attempt on the reinstalled box, run `34707798707`).** `src/game/objective_status.c:162` uses `#pragma weak objectiveGetStatus_WEAK = get_status_of_objective` (a decomp ground-truth alias, referenced 2-arg from `chrai.c:2538` — comment calls it "horrible hack to get ai matching"). The box's `g++`/`ld` reported as `x86_64-w64-mingw32/16.1.0`; a minimal local repro of the identical `#pragma weak` cross-TU function alias **links cleanly** on this session's own machine (`mingw-w64-x86_64-gcc 16.2.0-3`, `binutils 2.47-3`). Not a game-logic bug (the alias is unmodified decomp code, ground truth) and not a CMakeLists/link-flag issue (no `--gc-sections`/LTO in play) — the leading, unverified explanation is the self-hosted box's MSYS2 packages predate a PE/COFF weak-external fix or behavior change versus current MSYS2, consistent with rollout step 2's earlier finding that the box's mingw64 package set was stale/incomplete at initial setup.
@@ -16385,3 +16392,50 @@ Whichever loop runs `SDL_PollEvent` first gets the event, and the render loop us
**Unknown ini keys.** `config.c` counts keys that `applyKV` rejects during `configLoad` (`configUnknownKeyCount/First`); the F10 root page shows an amber "N UNKNOWN INI KEY(S) - SEE LOG" in the title line. Non-blocking, no behaviour change. Legacy migrated keys (e.g. old `Input.AimStyle`) also count, once, until the ini self-cleans on exit.
**Verified (Windows, 1280x720, MouseEnabled=0, `GE_OPTIONSOVERLAY=1`, GE_PCDUMP frame 250, rc=0, no crash log):** root page pillarboxed with the warning (`scratch/f472/root.png`); Gameplay page with red Skip intro / All unlocked rows (`scratch/f472/gp250.png`). **Not verifiable headless:** multi-pad ownership. Maintainer live checks: with 2 pads, P2 Back opens F10, P2 navigates/adjusts/B-backs/Start-closes while P1 Back/Start does nothing; detach the owner mid-menu, the lowest remaining pad takes over; keyboard F10 + pad 1 still drives; solo single-pad unchanged; pillarboxed mouse hover/click lands on the drawn row at 16:9 and 21:9; PS pad in seat 2 shows PS names in the help line when it owns the overlay.
## D473 — Uncached `getenv("GE_D75V")` per G_VTX (low-end perf, #92 / D339)
**Found by** the 2026-10-01 CPU profile (in-process all-thread sampler `GE_PERFSAMP`, branch `perf/profile-hotspots`, local note `docs/dev/notes/PERF-HOTSPOTS.md`): ~55-59% of render-thread samples landed in `getenv` called from `gfx_sp_vertex`. The D75 vertex probe (`port/fast3d/gfx_pc.cpp`, `GE_D75V="lo-hi"`) cached its range in `d75v_lo`, but used `-1` both as "uncached" and as "disabled", so with the env unset (always, for players) the `if (d75v_lo < 0)` re-ran `getenv` on every G_VTX (170-520 per frame). Same class as D302/D250 (porting-notes D302); the registry wrongly described it as a cached check.
**Fix:** a separate `d75v_init` flag; the probe's behaviour when set is unchanged, so output is identical. **Measured** (`GE_PERFSTAT`, Statue `-level_22`, maintainer settings 1600x900 MSAA 8x DD 400, same scripted sweep, RTX 5090): steady windows with matching triangle counts, display-list time 0.99 -> 0.46 ms and 1.31 -> 0.55 ms per frame; total `run` 5.6 -> 1.8-2.0 ms. Profile context: whole-process CPU is only ~1.5-2 ms/frame on this machine after the fix, so on the N3060 / HD 400 (#92) the remaining bottleneck is probably GPU/driver-side; next step there is a `GE_PERFSTAT` run on that hardware (gpu= vs run= vs interval=). Further CPU proposals (texture-bind lookups, redundant state changes, debug hooks per triangle, combiner lookup) are ranked in the local note.
## D474 — fast3d micro-optimisations from the 2026-10-01 profile (low-end, #92)
**Changes (`port/fast3d/gfx_pc.cpp`, output-identical by construction):** (1) `gfx_dp_set_tile` compares the post-normalisation tile fields with the stored ones and only sets `rdp.textures_changed[]` when something differs; `gfx_dp_set_tile_size` returns early when uls/ult/lrs/lrt are unchanged. A dirty flag costs a texture-cache lookup and forces `gfx_flush()` (a batch break, i.e. an extra draw call) on the next textured triangle. Texture/TLUT loads and G_TEXTURE still dirty the textures through their own paths, so a real change is never missed. Narrow stored fields compare conservatively (out-of-range args count as changed). (2) A single cached `tri_dbg()` gate (non-empty `GE_D75D`, `GE_ZF` or `GE_D303`, the same semantics as each hook's own lazy init) now guards the per-triangle emit/reject hooks and the per-G_VTX note, which previously each cost a call plus a frame-counter read even when disabled.
**Measured** (`GE_PERFSTAT`, Statue, the same sweep and settings as D473): display-list time in comparable windows 0.46 -> 0.42 and 0.55 -> 0.54 ms (one window 1.57 -> 0.80), batches 255 -> 254. On this machine the gain is within run-to-run noise: redundant tile setters rarely broke batches in this scene. Kept because it is free and the batch/lookup savings scale with weaker CPUs and per-draw driver cost. Frame 1350 dump checked by eye: normal textured rendering.
## D475 -- low-CPU spikes explained; texture pre-warm (Video.TexturePrewarm) built, little to gain
**Game thread (GE_GTPERF).** On Statue (-level_22, 1550-frame view sweep) no scheduler message ever took more than 3 ms of game-thread work. The ~33 ms low-CPU frames (render CPU 1-2 ms) are retraces skipped by the game's own frame limiter (`boss.c` `mainTickElapsed < MAIN_LOOP_TICK_INTERVAL`): every gap line reads "1 skipped by tick-interval, 0 by pendingGfx" and the previous gfx task had run ~13-16 ms (includes the vsync/present wait). A frame that finishes a little late misses the tick threshold and the next DL is built one retrace later. Not a loading/AI/audio cost.
**Texture bursts (GE_PWMISS).** After frame 150, 894 of 1002 cache misses were repeats of an already-imported address: the same CI8 texture and the same palette address with a different palette *content hash* (51 distinct hashes for one texture within ~8 frames from frame 587). D217 keys CI textures on palette content, so a palette the game rewrites in place (lighting/effect tint) re-decodes and re-uploads the texture every change. Only ~108 misses per 1000 frames were true first-sight. That is the real source of the 60-130-import bursts.
**Pre-warm (built, default OFF).** `gfx_prewarm_walk()` (port/fast3d/gfx_pc.cpp) is a reduced DL interpreter run on the game thread from `bgRoomCalcBB` (one `#ifdef PORT` hook in src/game/bg.c, while the room is transiently loaded); it executes only texture-state commands and records import jobs (snapshot of tile/loaded-texture/palette state, deduped); the render thread replays them via `import_texture` at the start of the next `gfx_run`. Statue: 27 rooms, 756 jobs (638 dup), +378 cache entries, walk 0.8 ms, replay 49 ms at level load. In-game misses 1419 (off) vs 1271 (on) over 1500 frames; spikes >20 ms 6 vs 6, >33 ms 4 vs 3, i.e. no real gain. Not verified output-identical (no frame diff run; Cradle not run), so `Video.TexturePrewarm` defaults to 0.
**Next lever.** Stop keying/decoding per palette content: upload CI index + palette as textures and look up in the shader (or cache by palette with a small LRU), so a palette change costs a 1 KB update instead of a full re-import.
**Lead note (2026-10-01):** the pre-warm code is NOT merged (no measurable gain, never frame-diffed); it stays on branch `perf/prewarm` (`d625dc7e`) with probes `GE_GTPERF` / `GE_PWMISS` for future work. Two real levers remain: (1) CI palette re-import churn (upload index + palette separately and look up in the shader, or cache per palette with an LRU); (2) the frame-limiter skip: a gfx task that finishes slightly late makes `mainTickElapsed < MAIN_LOOP_TICK_INTERVAL` drop the next retrace, a timing interplay between the port's wall-clock `osGetCount` and the VI pacing (porting-notes D13 class); needs a design look before any change (game-loop pacing is game logic, so Rule-2 applies if `boss.c` behaviour changes).
**Frame-limiter design look (2026-10-02, lead):** `boss.c` builds a frame on a VI retrace only if >= 387,937 counts (~8.3 ms) passed since the previous frame's start (`copy_of_osgetcount_value_1`, set in `updateFrameCounters`). A clock-beat cause (free-running 60.000 Hz port VI vs monitor vsync) was tested and refuted: a static `-level_09` view for 3700 frames with `GE_GTPERF=3` logged 9 skips, all in the first 314 frames (load/intro), none in the remaining ~57 s. So the skip only follows a long previous gfx task, i.e. it is a consequence of the CPU bursts (CI palette re-imports on Statue), not an independent pacing bug. Decision: no pacing change and no Rule-2 item; fixing the palette churn should remove most of these skips.
## D476 — CI4 texture cache keyed on its own palette bank (palette re-import churn, D475 follow-up)
**Cause:** D217 keys CI textures on an FNV hash of the whole 256-entry TLUT table (`rdp.palette`). A CI4 texture decodes only its 16-entry bank (`rdp.palette + palette_index * 16`, `import_texture_ci4`), so a TLUT load into any other part of the table changed the key and forced a re-decode and re-upload of an unchanged CI4 texture.
**Fix (`port/fast3d/gfx_pc.cpp`, port-only, output-identical):** for `siz == G_IM_SIZ_4b` the key's palette hash covers only that bank's 32 bytes; CI8 (reads all 256 entries) keeps the whole-table hash. Two keys can only match when the decoded texels would be identical.
**Measured** (`GE_PWMISS` probe build, Statue sweep at the maintainer's settings, misses after frame 150): CI4 189 -> 36; total 1472 -> 1101 (the CI8 count also moved, which is run-to-run variation; its keys are unchanged).
**Not fixed here:** the bulk is CI8 textures in the arena (e.g. `0x701a2920`) whose palettes the game animates (70-155 distinct palettes over the sweep, only 48 eviction re-misses in total), so no cache can absorb them. That needs a GPU palette lookup (index texture + palette texture, with filtering done in the shader) or an in-place `glTexSubImage2D` update path; see ROADMAP §4.
## D477 — CI8 palette re-imports measured: not a CPU-side hitch cause (D475/D476 follow-up premise refuted)
**Question:** D475/D476 named CI8 textures with game-animated palettes as the main remaining source of the Statue/Cradle hitches and proposed a GPU palette lookup or an in-place `glTexSubImage2D` update path.
**Probe:** `GE_CI8PROF=1` on branch `probe/ci8-churn` (TEMP, never merge): per-CI8-bind counters in `import_texture` (binds, misses, distinct palette hashes, re-misses of an already-seen palette, same-frame binds with a different palette) and timers around the miss path (decode vs `upload_texture`), plus the worst CI8 frame per 300-frame window.
**Run:** Statue `-level_22`, maintainer's ini (1600x900, MSAA 8x, `TextureFilter = 2`, DD 400, FOV 135, vsync), `GE_INPUTSCRIPT="200:SLEFT;1000:SNONE;1050:SRIGHT;1800:SNONE;1850:SUP;2100:SLEFT;2400:SNONE"`, `GE_QUITFRAME=2420`, with `GE_PERFSTAT=1`; RTX 5090 dev box; rc=0, ini/eep restored. One run.
**Results:**
- CI8 miss path costs ~0.008 ms/frame on average in the busiest window (frames 300-900, ~1.9 misses/frame); the worst single frame spent 0.44 ms on CI8 imports. Non-CI8 misses are smaller still. CPU-side, this cannot make a gfx task ~15 ms late.
- The cache absorbs the animation: 1314 CI8 misses over 153 textures, only 10 re-misses of a palette already seen (the top texture `0x701a2920`, 32x48: 137 misses, 134 palettes, 3 re-misses). Misses fall to 0 after ~frame 1800.
- The same CI8 texture is bound with different palettes within one frame in most frames (`0x70155ac0`: 1902 of 2400 frames; `0x701a2920`: 8166 same-frame palette switches). An in-place single-texture update path would therefore re-upload every frame; **do not build it**.
- Spikes: 3 frames > 33 ms in 2420 (frames 3, 459, 762), each with render-thread `dl`/`run` 0.2-0.6 ms; none coincides with the worst CI8 frame (577).
**Not measured:** driver-deferred work (first-draw allocation of a new texture, `glGenerateMipmap` forced by the three-point filter on every upload) shows up neither in these CPU timers nor in the averaged GPU timer. The D475 association between the bursts and palette re-imports was temporal, not a measured cost.
**Conclusion:** GPU palette lookup / in-place update are not justified by this data. Next step if the hitches still matter: a per-frame timeline (game-thread build + limiter decision, render-thread misses/new textures/shader compiles, per-frame GPU timestamps, swap time) over >=5 runs, PresentMon for what reaches the screen, and a disable-falsifier per suspect. Low-end hardware may weigh these costs differently (a `GE_PERFSTAT` run there is still owed, ROADMAP §4).