mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-23 15:52:42 +02:00
docs: log Steam Deck playtest feedback batch (D289-D293 new, cross-refs to D173/D219/D243/D246)
User completed a Dam-through-Runway Steam Deck playtest and reported 9 items. Four corroborate already-open findings (D173 third-person Bond floating at level start, D246 fullscreen edge strips -- Deck's own default preset sets MSAA=4, exactly this row's leading suspect, D219 purple/blue particle colors widened to blood decals, D243 Dam end-cutscene camera glitch -- second independent confirmation). Five are new: D289 (Dam computer-room crash, non-reproducing), D290 (watch controller-page missing pad graphic), D291 (Dam truck texture glitches), D292 (death-pose Bond sunk into floor, possibly D173- adjacent), D293 (no way to quit the game from the UI -- confirmed in code, SDL_QUIT via window-close/Alt+F4 is the only path; a QoL gap on Deck/controller-only setups). Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
This commit is contained in:
1 parent
d475884456
commit
4c2643c239
2 files changed
+14
-4
No files matched your search
@@ -166,3 +166,8 @@ D285,FIXED,"A real Steam Deck SIGSEGV caught during D255 live-testing (v0.2.1 pu
|
||||
D286,OPEN,"User-confirmed real (not system-side) ~8s frame freeze on Caverns, Steam Deck, caught during the same live session as D255/D285 —","OPEN — one confirmed occurrence, no repro trigger identified, not root-caused; distinct from D255/D285."
|
||||
D287,OPEN,"User-confirmed real, non-self-recovering freeze on Windows (M-141, 2026-09-15/16) after ~15-20 minutes with no input, triggered by clicking back into the windo…","OPEN — one confirmed occurrence, real (not self-recovering), not root-caused; gfxFrameMsgQ full is a lead, not a confirmed cause."
|
||||
D288,OPEN,"Silo intro: a huge, flat-shaded, untextured brown/tan triangle sweeps across the frame during the camera pan down the missile shaft (M-141, user report —","OPEN — visually confirmed and precisely bounded (frames ~562-741 of a bare -level_20 boot), root cause not yet identified; promoted from the long-parked GRAPHICS-BACKLOG.md D171 entry with real evide…"
|
||||
D289,OPEN,"Dam: a crash in the computer room, not reproducible on a second attempt (M-141, Steam Deck user playtest).","OPEN — single non-reproducing occurrence, no evidence captured (no crash log, no repro)."
|
||||
D290,OPEN,"Watch's in-game ""controller"" section shows no control-pad graphic (M-141, Steam Deck user playtest).","OPEN — observed, not investigated."
|
||||
D291,OPEN,"Dam: texture glitches when the truck drives past (M-141, Steam Deck user playtest).","OPEN — observed, not investigated."
|
||||
D292,OPEN,Death animations are glitched —,"OPEN — observed, not investigated."
|
||||
D293,OPEN,"QoL ask: no discoverable way to quit the game from the UI (M-141, Steam Deck user playtest —","OPEN — not investigated, low complexity."
|
||||
|
@@ -435,7 +435,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
||||
| D172 | **Bullet-impact / blood / spark particles render magenta or cyan instead of dark red (M-82, ROOT CAUSE FOUND + FIXED).** Particle billboards (`explosion.c` `explosionRenderPart`, `gSPVertex`+`gSP2Triangles`) take their CC/tile state from ROM state records replayed just before them (`g_ExplosionDisplayLists[]` = `assets/oddtextures.c` `globalDL_0x078..`). The dominant record sets `gsDPSetCycleType(G_CYC_2CYCLE)` + `gsDPSetTextureLOD(G_TL_TILE)` + `gsDPSetCombineMode(G_CC_INTERFERENCE, G_CC_MODULATEIA2)` and binds **two** tiles: tile 0 = IA8 "smoke" @ TMEM 0, tile 1 = RGBA16 "fire" @ TMEM 0x188. `G_CC_INTERFERENCE` cycle-0 = `TEXEL0 * TEXEL1`. **fast3d's `gfx_lod_tile_offset()` returns `0` unconditionally** on the `!gfx_detail_textures_enabled` branch — a D107 fix for GE's mip textures (whole chain loaded in one LOADBLOCK at TMEM 0; `port/src/video.c:225` sets the flag `false`). So texunit 1's sampled tile = `first_tile_index + 0` = tile 0 → **TEXEL1 samples the smoke texture, not fire** → `smoke * smoke` → wrong colour. Two earlier M-82 candidates were false trails: (1) "0xB9 unhandled → D146 abort" — 0xB9 is `G_SETOTHERMODE_L` in this non-F3DEX2 build, handled; zero D146 aborts in a sustained-fire run. (2) "records never set 2-cycle" — they do (`0xBA`), and fast3d applies it (probe-confirmed `cycletype=2CYC`). **Probe evidence (`GE_D172=1`, user Silo firefight, `d172_silo.log`):** 40/40 particle tris → `cfg1[tile=1 tmem=392 fmt=RGBA siz=16b]` but `SAMPLED1 = tile0 (tmem=0 fmt=IA)`, `tex_lod=0`. | **FIXED (`port/fast3d/gfx_pc.cpp` `gfx_lod_tile_offset`): `return rdp.tex_lod ? 0 : i;`** — fold to the base tile only when LOD is actually active. D107's mip case (`tex_lod=1`, blurry ceilings) unchanged; a genuine non-LOD 2-texture combine now samples tile `i`. `bunker1` verify PASS (the D107 repro level), no framediff regression. PR #34. **Owed: in-game eyeball — blood/sparks should read dark red.** Env-gated `GE_D172` probes left in tree (`#ifdef PORT`, inert). |
|
||||
| D174 | **"No blood effect" (user QA report).** Likely the unverified D120 decal fix: `d43_emit.py` now emits the opcode-0x18 `PointUsage[]` chain, but it was never interactively verified and requires a full sidecar regen to take effect (`debug.ps1` does not regen). Spray path is D172 (draws, wrong colour) — total absence would be new. See `GRAPHICS-BACKLOG.md` D174. | 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. | Hypothesis DISPROVEN + diagnostics shipped; D176(b)/D182(2) still OPEN |
|
||||
| D173 | **Third-person Bond model spawns too high** (user QA report): the player-representing figure at level start floats well above the ground "a lot of the time" while the actual player spawn is correct; same on the Cuba end credits — JB ~6 ft in the air above Natalya. D75 animated-model family but a *position* defect, not absence (M-33: gun-barrel Bond renders fine). Suspects: intro/credits puppet chr spawn position vs model base transform; anim root-motion accumulation (`modelSetAnimFrame2WithChrStuff`, cf. D156); packed-float `coord3d` decode in intro setup. "Player spawning OK" localises it to the render-side model, not `g_CurrentPlayer`. See `GRAPHICS-BACKLOG.md` D173. | OPEN — observed, not investigated. **M-87 user playtest: still present** — "Bond floating in the sky / cutscene glitch thing still exists." Related, newly reported: **in some cutscenes Bond spins in circles** (named instance: end of Cradle, when Trevelyan falls) — likely the same actor-position/anim-root class rather than a new bug; logged under D148/D160 (`GRAPHICS-BACKLOG.md`) since it's cutscene-triggered. **M-106 investigation (static only — no build/runtime access in this worktree, see note below):** checked the two most D209-shaped candidates for a puppet Y-position aliasing bug and RULED OUT both. (1) `chrSpawnAtPad`/`chrSpawnAtCoord`/`chrAdjustPosForSpawn` (`src/game/chraction.c:10653-10794`) — the puppet-spawn path a cutscene `guard_play_animation`-style AI cmd would use. `PadRecord.stan` (`src/bondtypes.h:1744-1751`) is a ROM-serialized *pointer* field (`StandTile *stan` at offset 0x28) — exactly the tail-pointer shape D122/D126/D132 warn about — and `tools_pc/d88_emit.py`'s `emit_pad()` zeroes it on conversion with a comment claiming it's "dead-on-load, always set before use." **Verified this claim is TRUE, not a converter bug**: every `PadRecord` literal in the N64 source asset tables (checked `assets/obseg/setup/UsetupdamZ.c:37-51`) initialises `stan` to the literal `0` — the ROM binary never carries a live pointer there on N64 either, so zeroing it on PC is byte-identical behaviour, not a regression. `chrAdjustPosForSpawn` receiving a NULL seed `StandTile*` is therefore the original game's normal case, not a port defect. (2) The anim-root-motion Y-accumulation chain (`modelTickAnim`→`modelSetAnimFrame2WithChrStuff`, `model.c:3060+`, `pos24 = pos34 + rotate(pos)` at `model.c:3322`) was already exhaustively audited for exactly this class of bug during D193/D209 (M-80/M-81) and found self-consistent, with the `padding[]`-union sweep that found D209/D210 explicitly noting `act_anim`/`act_dead` (the union arms active during a played-animation/dead puppet) are "layout-stable" (`porting-notes.md` §A, D210 entry) — i.e. that sweep already covers the cutscene-puppet actiontype, and it came back clean. So the two most probable D209-style aliasing sites for this specific symptom are both cold. **Did not find the actual root cause.** Did not get to: the *separate* Cuba end-credits case (`front.c` `init_menu18_displaycast`/`constructor_menu18_displaycast`, `front.c:7821-8320`+ — a **different**, single-actor "cast roster" display model shared by the intro roster and the credits roster, using `getsuboffset`/`subcalcpos`/`cast_rootpos_smoothed` root-motion smoothing, not the in-level chr path at all) — spot-checked `getsuboffset` (`model.c:684-700`) and it is `#MATCH`-verified clean, but did not trace the smoothing math or confirm this is even the code path the "JB above Natalya" report hits (that report implies two actors on screen at once, which this single-`cast_model` code doesn't obviously support — may be a distinct, not-yet-located two-actor cutscene-puppet path). **Confidence: LOW-MEDIUM that this is the D209-style struct-aliasing family at all** — the two most likely aliasing sites check out clean, which argues *against* the shared-root-cause theory for D173 specifically (unlike D160, where the analogous check also came back clean but for a different reason — no pointer fields to alias in the first place). Next investigator: get a live Cradle (`-level_41`) capture of Trevelyan's chr during the death sequence with a position-logging probe (none shipped yet — would need a new `#ifdef PORT` env-gated trace, not added this session per the no-build-verification constraint), and separately locate the actual two-actor Cuba credits code path (grep for wherever `intro_char_table`/cast display is invoked with two simultaneous models, if it exists — not found this session). **M-141: no new lead this session** (see D243's M-141 entry) — checked whether D243's stale-memory front-end/menu bug could share a mechanism with this puppet-position bug; no code-level link found, kept as an unconfirmed hypothesis to watch for during the next live cutscene capture, not a new suspect to chase on its own. |
|
||||
| D173 | **Third-person Bond model spawns too high** (user QA report): the player-representing figure at level start floats well above the ground "a lot of the time" while the actual player spawn is correct; same on the Cuba end credits — JB ~6 ft in the air above Natalya. D75 animated-model family but a *position* defect, not absence (M-33: gun-barrel Bond renders fine). Suspects: intro/credits puppet chr spawn position vs model base transform; anim root-motion accumulation (`modelSetAnimFrame2WithChrStuff`, cf. D156); packed-float `coord3d` decode in intro setup. "Player spawning OK" localises it to the render-side model, not `g_CurrentPlayer`. See `GRAPHICS-BACKLOG.md` D173. | OPEN — observed, not investigated. **M-87 user playtest: still present** — "Bond floating in the sky / cutscene glitch thing still exists." Related, newly reported: **in some cutscenes Bond spins in circles** (named instance: end of Cradle, when Trevelyan falls) — likely the same actor-position/anim-root class rather than a new bug; logged under D148/D160 (`GRAPHICS-BACKLOG.md`) since it's cutscene-triggered. **M-106 investigation (static only — no build/runtime access in this worktree, see note below):** checked the two most D209-shaped candidates for a puppet Y-position aliasing bug and RULED OUT both. (1) `chrSpawnAtPad`/`chrSpawnAtCoord`/`chrAdjustPosForSpawn` (`src/game/chraction.c:10653-10794`) — the puppet-spawn path a cutscene `guard_play_animation`-style AI cmd would use. `PadRecord.stan` (`src/bondtypes.h:1744-1751`) is a ROM-serialized *pointer* field (`StandTile *stan` at offset 0x28) — exactly the tail-pointer shape D122/D126/D132 warn about — and `tools_pc/d88_emit.py`'s `emit_pad()` zeroes it on conversion with a comment claiming it's "dead-on-load, always set before use." **Verified this claim is TRUE, not a converter bug**: every `PadRecord` literal in the N64 source asset tables (checked `assets/obseg/setup/UsetupdamZ.c:37-51`) initialises `stan` to the literal `0` — the ROM binary never carries a live pointer there on N64 either, so zeroing it on PC is byte-identical behaviour, not a regression. `chrAdjustPosForSpawn` receiving a NULL seed `StandTile*` is therefore the original game's normal case, not a port defect. (2) The anim-root-motion Y-accumulation chain (`modelTickAnim`→`modelSetAnimFrame2WithChrStuff`, `model.c:3060+`, `pos24 = pos34 + rotate(pos)` at `model.c:3322`) was already exhaustively audited for exactly this class of bug during D193/D209 (M-80/M-81) and found self-consistent, with the `padding[]`-union sweep that found D209/D210 explicitly noting `act_anim`/`act_dead` (the union arms active during a played-animation/dead puppet) are "layout-stable" (`porting-notes.md` §A, D210 entry) — i.e. that sweep already covers the cutscene-puppet actiontype, and it came back clean. So the two most probable D209-style aliasing sites for this specific symptom are both cold. **Did not find the actual root cause.** Did not get to: the *separate* Cuba end-credits case (`front.c` `init_menu18_displaycast`/`constructor_menu18_displaycast`, `front.c:7821-8320`+ — a **different**, single-actor "cast roster" display model shared by the intro roster and the credits roster, using `getsuboffset`/`subcalcpos`/`cast_rootpos_smoothed` root-motion smoothing, not the in-level chr path at all) — spot-checked `getsuboffset` (`model.c:684-700`) and it is `#MATCH`-verified clean, but did not trace the smoothing math or confirm this is even the code path the "JB above Natalya" report hits (that report implies two actors on screen at once, which this single-`cast_model` code doesn't obviously support — may be a distinct, not-yet-located two-actor cutscene-puppet path). **Confidence: LOW-MEDIUM that this is the D209-style struct-aliasing family at all** — the two most likely aliasing sites check out clean, which argues *against* the shared-root-cause theory for D173 specifically (unlike D160, where the analogous check also came back clean but for a different reason — no pointer fields to alias in the first place). Next investigator: get a live Cradle (`-level_41`) capture of Trevelyan's chr during the death sequence with a position-logging probe (none shipped yet — would need a new `#ifdef PORT` env-gated trace, not added this session per the no-build-verification constraint), and separately locate the actual two-actor Cuba credits code path (grep for wherever `intro_char_table`/cast display is invoked with two simultaneous models, if it exists — not found this session). **M-141: no new lead this session** (see D243's M-141 entry) — checked whether D243's stale-memory front-end/menu bug could share a mechanism with this puppet-position bug; no code-level link found, kept as an unconfirmed hypothesis to watch for during the next live cutscene capture, not a new suspect to chase on its own. **M-141: Steam Deck playtest confirmation** — user reports Bond floating at level start on both Dam andLine truncated
|
||||
| D179 | **Packaged build crashes after the logos — the D43/D69 model + bg sidecars are ROM-derived and absent from any build that has no ROM (M-34, alpha-release QA).** The first `goldeneye-pc-port-*-win64.zip` from CI (bundle-win.sh) shows the Rare/Nintendo logos (compiled-in `assets/rarewarelogo.c`) then AVs at `0x6b157a88`/`0x70157a88` — symbolised: `modelPromoteNodeOffsetsToPointers` (`model.c`) ← `load_object_fill_header` (`objecthandler_2.c`) ← first prop/item model load. Root cause is **not** a code regression: the port loads PC-layout model geometry from `data/pcmodels-<region>/{pcmodels.bin,manifest.csv}` and stage bg/stan from `data/pccg-<region>/{pccg.bin,manifest.csv}`, both produced offline by `tools_pc/d43_emit.py` / `d69_emit.py` from the ROM (see `port/src/pcmodels.c` / `pccg.c`; `pcmodelsReserveSize` logs *"pcmodels.bin not found — model loads will fail"* and returns 0). CI has no ROM so it never runs the emit scripts, and the two `data/` dirs are (correctly) gitignored ROM-derived game data — **cannot be committed or shipped** (converted Nintendo/Rare geometry, DLs, collision, stan nav; = distributing assets). A local build with those dirs present runs clean (verified M-34: 1741 frames, `romdataInit ... mapped at 0x10000000`, `pcmodels: 512 sidecars`, `pccg: 73 sidecars`, no crash — so `0x10000000` is *not* the problem here). The `docs/building.md` sidecar-gen step was also missing entirely. **Fix = release-side, backlogged** (`docs/BACKLOG.md` → "Alpha release"): bundle the emit scripts + their committed inputs as a pure-stdlib-Python asset-prep tool the user runs once against their own ROM (~5 MB output, no MIPS toolchain). Secondary/latent: the fixed-address `VirtualAlloc((LPVOID)0x10000000)` in `romdata.c` has no working fallback (`"using heap copy — direct ROM reads will fail"` then limps into the same crash) — didn't bite M-34 but will on a machine where something occupies `0x10000000`; harden separately (reserve earliest in `main`, or retry low bases and derive all segment math from the base obtained). | Diagnosed, not fixed — release packaging gap + latent `romdata.c` fallback |
|
||||
| D180 | **Native-PC input pass (M-XX QoL run, `qol/native-pc-input-menu`, port-only).** Three parts, all config-gated with default = prior behaviour: **(WI-1) `Input.MouseCaptureMode`** (0 = legacy always-grab; 1 = Quake-style click-to-lock) in `port/src/input.c` + `port/src/video.c` — in mode 1 the OS cursor is free until a click lands in the game window (`SDL_MOUSEBUTTONDOWN` → `inputNotifyClick`), and ESC / focus-loss / entering any front-end menu (`current_menu != MENU_RUN_STAGE`) frees it again; re-entering a stage while still "armed" re-locks so unpausing needs no click. `reconcileGrab()` runs once per controller-0 poll. Mouse buttons are suppressed from the game while the cursor is free in-stage (no phantom fire). Controller paths untouched. **(WI-2) absolute-cursor menu tracking** — when capture mode is on and the cursor is free in a menu, the D165/D169 pointer P-controller takes its target from the real cursor's absolute window position mapped onto the live virtual front-end rect (`getPlayer_c_screen*`), giving true 1:1 tracking instead of the relative-delta estimator. Relative path unchanged for legacy/grabbed. **(B3) `Input.MouseAimSpeed` default 25 → 16** (aim mode still overshot at 25 per the backlog). | LANDED, port-only, no `#ifdef PORT`. Build 241/241, `-level_09` framediff 3/3 within threshold (unregressed), `GE_STARTMENU` menu boot crash-free. **Feel-checks owed** (headless can't drive the mouse): click-to-lock ergonomics, absolute menu tracking, the new aim-speed default. |
|
||||
| D181 | **`Game.ScreenShakeIntensity` — first route-(b) `src/` gameplay-cosmetic hook (M-XX QoL run).** `src/fr.c viShake()` gains a single `#ifdef PORT` line — `intensity *= portScreenShakeScale;` (extern `f32`, defined + `configRegisterFloat`'d 0.0–10.0 in `port/src/video.c`) — before the existing clamp. Default `1.0f` ⇒ exact no-op, every headless golden dump byte-unaffected; `0.0` disables explosion/effect screen shake, up to `10.0` exaggerates it. N64 build (no `-DPORT`) keeps the original line verbatim. Precedent per `docs/BACKLOG.md` "Fun features → screen-shake intensity slider" and AGENTS.md #2's documented-exception path. Same policy class as a future FOV hook. | **Menu row pulled (v0.2.1, user testing).** The hook is correct on its own narrow terms — `viShake()` is called only from `explosion.c`, so the slider does exactly what it says (scales explosion/effect shake) — but that's a much narrower scope than the "Screen shake" label implies to a player: it does not touch the always-on walking head-bob or any getting-shot reaction, so a user swinging it from 0 to max during ordinary play sees nothing and reasonably calls it broken. Removed from `port/src/optionsoverlay.c`'s row table until it covers all screen-shake/view-bob sources, not just explosions; config var (`portScreenShakeScale`) and the `fr.c` hook are untouched. Build green. |
|
||||
@@ -448,7 +448,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
||||
| D216 | **`Game.SkipIntro` — boot straight to the file-select menu (M-83, port + one `#ifdef PORT` game-code line, PD parity, Phase 4 quick win).** New `s32 portSkipIntro` (`port/src/video.c`, `configRegisterInt("Game.SkipIntro", 0, 1)`), read once in `src/game/lv.c` at `LEVELID_TITLE` stage load — in the `else` of the existing `GE_STARTMENU` `#ifdef PORT` block. When set it does what every intro stage's own `!is_first_time_on_main_menu` branch does (`front.c`): `is_first_time_on_main_menu = FALSE; prev_keypresses = TRUE; maybe_is_in_menu = TRUE; menu_update = MENU_FILE_SELECT;` — i.e. it reuses the game's own post-intro route rather than inventing one, so the legal screen + Nintendo/Rare/GoldenEye logo attract loop is skipped. `GE_STARTMENU` still wins when both are set. Default 0 ⇒ untouched. | **LANDED + VERIFIED this session.** Build clean. Headless: default boot frame 90 = the TWYCROSS classification screen (10% non-black), `SkipIntro=1` frame 90 = the **SELECT FILE menu** (folders/photos/cursor all render, 89% non-black), no crash across repeated boots. Screenshots captured + eyeballed. **Owed:** nothing critical — a human boot to confirm the menu is fully interactive (cursor moves, a save slot opens). **REGRESSED (v0.2.1 user testing, 2026-09-15): user reports enabling `Skip intro` breaks audio.** Not root-caused this session — leading suspect is that the intro/attract stages perform some audio-subsystem init (title/attract music kickoff, or a first-touch of the mixer) that `portSkipIntro`'s jump to `MENU_FILE_SELECT` bypasses. **Mitigation applied:** the `Game.SkipIntro` row was pulled from the F10 options overlay (`port/src/optionsoverlay.c`) so players can no longer enable it from the menu; the config var, default (0/off), and `lv.c` hook are untouched (so an existing `ge007.ini` with it already set would still trigger the bug — worth a follow-up hard-disable if that turns out to matter). Do not re-add the menu row without root-causing the audio interaction first. |
|
||||
| D217 | **1P weapon viewmodel + third-person Bond model textures render garbled — wrong/stale palette on model-DL CI textures (M-84, user QA; surfaced by D215 now that the viewmodel draws).** BUNKER1/Facility: the PP7 viewmodel has yellow+blue scrambled texels and a solid **green** patch by the hammer; Bond's in-game **watch face** shows the same green mismatch; user reports the **third-person Bond world model** does this "sometimes" too. Signature matches the **D161 family** — a CI-format tile drawn without the expected TLUT state, so fast3d's `import_texture` indexes a **stale `rdp.palette`**. D161 fixed the explicit `G_TT_NONE` case; this looks like model DLs not emitting `gDPLoadTLUT` through the port path, or the palette segment not resolving for weapon/character geometry. **Not a D215 regression** — D215 only un-gated the DL. | **FIXED (M-90/M-92/M-97; PR #47 texpool alignment + PR #49 CI-cache palette key) — the green-texture defect is fixed and merged, confirmed by two independent human playtests. The white missing-texture residual (AK47 / NPC-held world-model weapons) is split out as D228.** Status cell had gone stale at OPEN; corrected M-105. History: root cause characterised M-86; headless repro: `-level_34` FACILITY ~frame 300–400, PP7 **grip = flat green block** + green ring at the barrel joint + green-cast hand; barrel/slide upper correct black. Also green on `-level_30` DEPOT. **Clean on `-level_09` BUNKER1** (A/B control — same PP7, identical DL). **Two separate defects found:** ① **Wrong palette bound (the visible green — upstream of fast3d).** The grip is a CI4 solid-fill of palette index 0. Green `(24,65,0)` == RGBA5551 `0x1A00` == `rdp.palette[0]` at grip-import time. The grip's `count=1` model `G_LOADTLUT` is fed **different source bytes per level** for the same tile: BUNKER `00 01`→`PD_BE16`→`0x0001` (black ✓), FACILITY `1a 00`→`0x1A00` (green ✗). fast3d byteswaps + stores faithfully (proven correct by BUNKER) — the wrong bytes *arrive* from the model-DL / segment-address path. Strong tell: every *larger* model TLUT has `raw[0]==raw[1]==0x0000` with real colour from index 2 ⇒ GE's model-TLUT payload likely carries a **4-byte (2-entry) leading zero pad**, OR the port's model-TLUT source pointer is **4 bytes / 2 entries too low** (then the `count=1` grip load reads the tail of the preceding CI blob — BUNKER lucky, FACILITY not; fits "widespread across levels + weapons"). Ruled out this session: not tail-overflow (index 0 is in-range; matches M-85 `GE_D217Z`), not the fast3d byteswap. **Next:** dump the grip TLUT `G_SETTIMG` target vs the ROM/`pcmodels` sidecar PP7-grip material bytes; confirm the 4-byte offset vs N64; fix in model-DL segment resolution or `tools_pc/d43_emit.py` blob placement (sidecar regen). ② **CI texture-cache collision (proven, real, separate; fix landed).** `gfx_texture_cache_lookup` (`gfx_pc.cpp:522`) keyed CI textures on `{addr, palette_addrs[0/1], palidx, size_bytes}` with **no palette-content check**; GE reissues `gDPLoadTLUT` from repeated scratch-arena addresses with different content, so a later material could take a stale HIT and sample another material's decoded palette. **Fix on branch `fix/d217-model-texture-palette` (commit `f6fc0399`):** FNV-1a hash of the 512-byte `rdp.palette` computed only in `gfx_dp_load_tlut()`, added to the CI cache key (`port/fast3d/gfx_pc.{cpp,h}`, fast3d-internal, no `#ifdef`). Cheap, cannot worsen a decode; **owes a full 21-level `verify.sh sweep`** before merge (touches every palettized world texture's import path — cache-churn risk only). Does **not** fix defect ①. Screenshots: session scratchpad `d217/`, `fac_gun_crop.png`. Cross-ref `docs/dev/GRAPHICS-BACKLOG.md`. **User playtest matrix (M-87, raw notes — not yet triaged):** DAM/PPK: watch still green; AK47 viewmodel has white (missing-texture) patches on the magazine + barrel. FACILITY: PPK garbled **green AND blue**; 3rd-person Bond world model shows greenness on level load-in; AK47 has "weird blue / glitched" textures. JUNGLE: M16 viewmodel is orange in spots. Confirms multi-weapon (PP7/AK47/M16), multi-color (green/blue/white/orange) — consistent with the upstream model-TLUT-source theory (different trailing garbage per weapon/material) rather than one single fixed offset. **Scope clarified (M-87):** not just Bond's 1P viewmodel / 3P world model — **NPC-held weapon models (other characters' guns in third person) show the same glitched-color/white-missing texture symptom.** So the defect is in the shared weapon-model-DL/TLUT path used by *any* character holding that weapon, not something specific to Bond's model or the 1P viewmodel rig — widens the likely fix surface (weapon model loading in general) beyond "Bond's gun." **M-87 clarification:** not uniform across levels — severity and glitch *type* both vary per level (some levels show it lightly, some not at all in a given session, and the specific wrong-color/missing pLine truncated
|
||||
| D218 | **Wide FOV exposes the per-level draw-distance / fog boundary — "blue artifacting" down long peripheral sightlines (M-84, user QA on the D211 FOV slider).** BUNKER→**Dam main tunnel** at `FovScale` ~150: geometry / backdrop past the level's fog-end distance shows at the widened frustum edges (blue = Dam's fog tint). Not a render bug — `fogLoadCurrentEnvironment` (`bgfog.c:305`) does `viSetZRange(Visibility.BlendMultiplier, Visibility.FarFog)`, so **`Visibility.FarFog` is both the far clip plane and the fog-saturation distance**; levels tune it for the stock ~60° FOV. | **Logged (M-84), fix proposed + parked.** Plan: `Video.DrawDistance` (percent, 100–400, default 100 ⇒ no-op) `#ifdef PORT` multiplier on `FarFog` at `bgfog.c:305` (+ the direct `arg0->Visibility.FarFog` reads just below, so fog stays consistent) plus `Video.DrawDistanceAutoFov` (0/1, default 1 — couples draw distance to `FovScale` unless `DrawDistance` is set explicitly). Clamp effective multiplier ≲2.5× (small znear ⇒ far-field z-fighting risk). **M-87: user confirms still occurring** at high FOV, and — reversing the earlier "not implemented by user request" — **now wants `Video.DrawDistance` implemented as a real QoL feature**, plus scope it to cover **LOD/detail-distance in general**, not just the fog/far-clip boundary this row root-caused. Open question for that broader ask: does GE have a *separate* geometry/object LOD-swap or draw-culling distance mechanism (distinct from `Visibility.FarFog`) worth exposing too, or is fog-distance the only meaningful "how far can you see" knob in this engine? Check for a per-object visibility-radius / simple-model-swap system before assuming `FarFog` is the whole story. **FIXED as designed, awaiting visual verification (M-121).** Implemented exactly the parked plan: `f32 portDrawDistanceMultiplier(void)` (`port/src/video.c`) — an explicit `Video.DrawDistance != 100` wins outright, otherwise `Video.DrawDistanceAutoFov` (default on) couples the multiplier 1:1 to `Video.FovScale`, clamped to `[1.0, 2.5]` (never shrinks draw distance; the 2.5x ceiling is the finding's own far-field z-fighting caution). Applied in `fogLoadCurrentEnvironment` (`bgfog.c`) to a new local (`scaledFarFog`) used at *both* the `viSetZRange` far-clip call and the `g_CurFogDetails.scaled_far_fog_dist` fog-blend read just below it, so the clip plane and the fog math stay consistent per the original plan's own note — the level's `EnvironmentRecord` itself is never mutated (it's shared/reloaded, not a per-frame copy). Both new config keys added to the F10 overlay (`Video.DrawDistance` slider, `Video.DrawDistanceAutoFov` toggle) alongside the existing `FovScale` row. Identity at both defaults (`DrawDistance=100`, `FovScale=100` ⇒ `portFovScale==1.0f`) by construction. **Broader LOD-swap question from M-87 ANSWERED — yes, and now exposed too (same session, M-121 continued).** `src/game/model.c` has a real, distinct geometry-LOD system: `MODELNODE_OPCODE_LOD` nodes whose children are shown/hidden per-frame by `modelUpdateDistanceRelations()` testing a view-space `distance` (scaled by `getPlayer_c_lodscalez()`, itself now FOV-consistent per this session's D222 fix) against each node's `MinDistance`/`MaxDistance` — a genuine detail-swap mechanism, separate from the fog-visibility-fade this row already fixed. It even has an existing ground-truth multiplier hook, `g_ModelDistanceScale`, already used transiently by the game itself (`chr.c`'s 0.3125f close-up override, `front.c`/`title.c`'s menu-model `modelSetDistanceDisabled`). Rather than write to that shared global (would fight those existing overrides), added a second, composed multiplier at the same call site: `f32 portLodDistanceMultiplier(void)` (`port/src/video.c`), driven by new `Video.LodDistance` (percent, 25-400, default 100) and `Video.LodDistanceAutoFov` (**default OFF**, unlike `DrawDistanceAutoFov` — this is meant as a standalone performance lever the user can pull *down*, not something that silently adds cost as FovScale widens) — `distance *= portLodDistanceMultiplier()`, inverse-scaled so `LodDistance=200` keeps full detail twice as far (more cost) and `LodDistance=50` drops to the lower LOD twice as soon (less cost). Both new keys in the F10 overlay alongside `DrawDistance`. Identity at `LodDistance=100`. Rebuilt clean, crash-free at `LodDistance=40` (aggressive) on Dam and at defaults on bunker1. **M-121 live playtest, iterated to a working state.** Two real issues found and fixed while testing live on Dam with the user: (1) **regression** — the D222 fix (above) correctly widened `c_lodscalez` for the screen-edge cull planes, but that same value is *also* read directly by `propobj.c`'s `chrobjFogVisRangeRelated()`/`sub_GAME_7F054C58()` (the character/prop fog-visibility-fade cutoff) — uncompensated, this made guards fade/disappear *sooner* at wide FOV than before D222, the opposite of the intent. Fixed by dividing that same Line truncated
|
||||
| D219 | **PPK explosions render purple on Dam (M-87, user QA, "not 3D but noting").** Effect-color defect, not a texture-decode one — likely the D172 particle-color family (`gfx_lod_tile_offset` / `G_CC_INTERFERENCE` channel-order class) recurring on a different particle type/weapon than the D172 fix covered, or a fresh instance of the same class. | **RESOLVED (M-115, 2026-09-12)** — byte-order fix in `src/game/image.c` scoped by pixel format (16-bit family bswap16-at-store, 32-bit identity); user playtest-confirmed. Superseded history below, which began: **OPEN — observed, not investigated.** Next: `GE_PCDUMP`/`GE_TEXDUMP` a Dam PPK explosion; check whether it's the same `gfx_lod_tile_offset` tile-sampling defect D172 fixed for blood, on an explosion DL D172's fix didn't touch. **Status: re-checked at runtime M-90 — mechanism covered by the D172 fix on current `main`; close pending a human by-eye pass (folds into PR #47 playtest).** **M-89 triage (local-Qwen, static analysis only, not yet runtime-confirmed):** the explosion DLs (`assets/oddtextures.c` `globalDL_0x078/0x120/0x1c8/0x270…`) use the same `G_CYC_2CYCLE`+`G_CC_INTERFERENCE,G_CC_MODULATEIA2`+`G_TL_TILE`, tile0=IA smoke/tile1=RGBA fire mechanism as the already-fixed D172 blood/spark records — no separate/unfixed code path found in `explosion.c` or the asset DLs. If still purple after D172's fix (which the user's Dam report postdates — needs re-confirming on current `main`), the next suspect is the GLSL combiner implementation of `G_CC_INTERFERENCE`+`G_CC_MODULATEIA2` for cycle 2 specifically (`gfx_generate_cc`, `port/fast3d/gfx_pc.cpp:312-494`), not the tile-offset fix. **Next, concrete:** run Dam (`-level_33`) with `GE_D172=1` while triggering a PPK/grenade explosion and check for "BUG" lines in the log (silence = D172's fix already covers this DL and the row can likely close on a re-test; needs a scripted-fire repro, not yet built). **M-90 runtime re-check DONE (scripted-fire repro built with `GE_INPUTSCRIPT`): the D172 fix demonstrably covers Dam's particle DLs — row can close on a human by-eye pass.** Ran `-level_33` headless with `GE_D172=1`, 64 scripted Z-pulses (`GE_INPUTSCRIPT="200:Z;215:Z;..."`, ~15 controller-reads apart) and a `GE_PCDUMP` window over the same span. Result: **40/40 particle multitex tris log `SAMPLED1=tile1 (tmem=392)`** — the correct RGBA fire tile, i.e. exactly the post-D172-fix behaviour (pre-fix these read `SAMPLED1 = tile0 (tmem=0 fmt=IA)`, the smoke texture); **zero** "BUG"/unknown-GBI/D146-abort lines; **0** purple/magenta pixels (`r>120, b>120, g<90, r-g>50, b-g>50`) across all 28 captured frames. Caveat: the user's M-87 "purple" report may predate the D172 fix on their binary (playtests run on whatever build was current); this re-check proves the sampling path is correct on current `main`, which is as far as a headless pass can take it — a final by-eye look at a live Dam PPK firefight is the honest closer (cheap for the user, folds into the D217 PR #47 playtest). **M-108 — REOPENED, the by-eye closer FAILED.** User played a live Dam (`-level_33`) PPK firefight on the current `main` build (post-D233/D234 fixes, same session as D243) and confirmed explosions are **still blue/purple**, directly contradicting M-90's clean scripted-repro result (40/40 correct-tile samples, 0 purple pixels, 28 frames). This means the M-90 `GE_INPUTSCRIPT` scripted-fire repro was very likely **not exercising the same explosion instance/DL the live firefight hits** — plausible gaps: a different explosion variant (grenade vs. bullet-impact spark vs. enemy-death vs. PPK-specific muzzle/hit effect) using a DL the scripted test's Z-pulses didn't trigger, or a state/lighting/prim-color condition (M-89's alternate suspect: the `G_CC_INTERFERENCE`+`G_CC_MODULATEIA2` cycle-2 GLSL combiner implementation, `gfx_generate_cc`, `port/fast3d/gfx_pc.cpp:312-494`) that only manifests under conditions the synthetic repro didn't reproduce. **Do not trust the M-90 all-clear for this finding going forward** — it proved one particle path clean, not that Dam's live-fire purple is fixed. Next: a live `GE_PCDUMP`/`GE_D172=1` capture during an actual purple explosion in real play (not a scripted approximation) is now the only way forward — need to pin down exactly what triggers it (PPK gunshots hitting a surface/enemy? a thrown/tossed explosive? which). **User has now confirmed (2026-09-12) this is NOT Dam-exclusive — game-wide, not just suspected.** Treat as confirmed scope, not pending a second-level check. **User wants this actively fixed, not just triaged** — next session should prioritize a live `GE_PCDUMP`/`GE_D172=1` capture during a real explosion (any level) over further static analysis, since the M-89/M-90 static passes are exhausted on this finding without producing a fix. **New HIGH-interest lead (M-110): `explosion.c` never clears `G_LIGHTING` before drawing any of the 15 particle DLs** (`globalDL_0x078` through `0Line truncated
|
||||
| D219 | **PPK explosions render purple on Dam (M-87, user QA, "not 3D but noting").** Effect-color defect, not a texture-decode one — likely the D172 particle-color family (`gfx_lod_tile_offset` / `G_CC_INTERFERENCE` channel-order class) recurring on a different particle type/weapon than the D172 fix covered, or a fresh instance of the same class. | **RESOLVED (M-115, 2026-09-12)** — byte-order fix in `src/game/image.c` scoped by pixel format (16-bit family bswap16-at-store, 32-bit identity); user playtest-confirmed. Superseded history below, which began: **OPEN — observed, not investigated.** Next: `GE_PCDUMP`/`GE_TEXDUMP` a Dam PPK explosion; check whether it's the same `gfx_lod_tile_offset` tile-sampling defect D172 fixed for blood, on an explosion DL D172's fix didn't touch. **Status: re-checked at runtime M-90 — mechanism covered by the D172 fix on current `main`; close pending a human by-eye pass (folds into PR #47 playtest).** **M-89 triage (local-Qwen, static analysis only, not yet runtime-confirmed):** the explosion DLs (`assets/oddtextures.c` `globalDL_0x078/0x120/0x1c8/0x270…`) use the same `G_CYC_2CYCLE`+`G_CC_INTERFERENCE,G_CC_MODULATEIA2`+`G_TL_TILE`, tile0=IA smoke/tile1=RGBA fire mechanism as the already-fixed D172 blood/spark records — no separate/unfixed code path found in `explosion.c` or the asset DLs. If still purple after D172's fix (which the user's Dam report postdates — needs re-confirming on current `main`), the next suspect is the GLSL combiner implementation of `G_CC_INTERFERENCE`+`G_CC_MODULATEIA2` for cycle 2 specifically (`gfx_generate_cc`, `port/fast3d/gfx_pc.cpp:312-494`), not the tile-offset fix. **Next, concrete:** run Dam (`-level_33`) with `GE_D172=1` while triggering a PPK/grenade explosion and check for "BUG" lines in the log (silence = D172's fix already covers this DL and the row can likely close on a re-test; needs a scripted-fire repro, not yet built). **M-90 runtime re-check DONE (scripted-fire repro built with `GE_INPUTSCRIPT`): the D172 fix demonstrably covers Dam's particle DLs — row can close on a human by-eye pass.** Ran `-level_33` headless with `GE_D172=1`, 64 scripted Z-pulses (`GE_INPUTSCRIPT="200:Z;215:Z;..."`, ~15 controller-reads apart) and a `GE_PCDUMP` window over the same span. Result: **40/40 particle multitex tris log `SAMPLED1=tile1 (tmem=392)`** — the correct RGBA fire tile, i.e. exactly the post-D172-fix behaviour (pre-fix these read `SAMPLED1 = tile0 (tmem=0 fmt=IA)`, the smoke texture); **zero** "BUG"/unknown-GBI/D146-abort lines; **0** purple/magenta pixels (`r>120, b>120, g<90, r-g>50, b-g>50`) across all 28 captured frames. Caveat: the user's M-87 "purple" report may predate the D172 fix on their binary (playtests run on whatever build was current); this re-check proves the sampling path is correct on current `main`, which is as far as a headless pass can take it — a final by-eye look at a live Dam PPK firefight is the honest closer (cheap for the user, folds into the D217 PR #47 playtest). **M-108 — REOPENED, the by-eye closer FAILED.** User played a live Dam (`-level_33`) PPK firefight on the current `main` build (post-D233/D234 fixes, same session as D243) and confirmed explosions are **still blue/purple**, directly contradicting M-90's clean scripted-repro result (40/40 correct-tile samples, 0 purple pixels, 28 frames). This means the M-90 `GE_INPUTSCRIPT` scripted-fire repro was very likely **not exercising the same explosion instance/DL the live firefight hits** — plausible gaps: a different explosion variant (grenade vs. bullet-impact spark vs. enemy-death vs. PPK-specific muzzle/hit effect) using a DL the scripted test's Z-pulses didn't trigger, or a state/lighting/prim-color condition (M-89's alternate suspect: the `G_CC_INTERFERENCE`+`G_CC_MODULATEIA2` cycle-2 GLSL combiner implementation, `gfx_generate_cc`, `port/fast3d/gfx_pc.cpp:312-494`) that only manifests under conditions the synthetic repro didn't reproduce. **Do not trust the M-90 all-clear for this finding going forward** — it proved one particle path clean, not that Dam's live-fire purple is fixed. Next: a live `GE_PCDUMP`/`GE_D172=1` capture during an actual purple explosion in real play (not a scripted approximation) is now the only way forward — need to pin down exactly what triggers it (PPK gunshots hitting a surface/enemy? a thrown/tossed explosive? which). **User has now confirmed (2026-09-12) this is NOT Dam-exclusive — game-wide, not just suspected.** Treat as confirmed scope, not pending a second-level check. **User wants this actively fixed, not just triaged** — next session should prioritize a live `GE_PCDUMP`/`GE_D172=1` capture during a real explosion (any level) over further static analysis, since the M-89/M-90 static passes are exhausted on this finding without producing a fix. **New HIGH-interest lead (M-110): `explosion.c` never clears `G_LIGHTING` before drawing any of the 15 particle DLs** (`globalDL_0x078` through `0Line truncated
|
||||
|
||||
**M-111 (2026-09-12, live session, 6 build/relaunch cycles): G_LIGHTING lead RULED OUT; texture-cache-collision lead RULED OUT; ROOT CAUSE FOUND — R/B channel swap in the decoded fire particle texture data itself.** Live-captured 4 rounds against a real user playtest (not scripted) with the user confirming explosions were still blue/purple in every capture:
|
||||
1. **G_LIGHTING probe fired 5000+ times across a full playtest with explosions triggered, 100% `LIGHTING=off`, 0 `D219 SUSPECT` hits.** (First attempt used the original 40-hit cap from the D172 probe, which silently exhausted on ordinary bullet-impact particles before any explosion — fixed to exempt the lit case from the cap and add an always-on periodic counter, confirming the code path really was being hit throughout, not just early. See `port/fast3d/gfx_pc.cpp` `gfx_sp_tri1`'s D172/D219 probe block.) **G_LIGHTING is conclusively not the mechanism.**
|
||||
@@ -535,9 +535,9 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
||||
| D240 | **Player gunshot SFX timing/cadence reportedly does not match the N64 original's rate (M-106, user QA report).** Static trace only (no ROM/build/display in this worktree). Weapon fire re-triggers its SFX through `bondwalkItemGetSoundTriggerRate()`/`bondwalkItemGetSound()`, gated on `g_GlobalTimer` (`src/game/gunfire.c:3246-3286`: `handptr->field_A4C = bondwalkItemGetSoundTriggerRate(var_s1) + g_GlobalTimer`), alternating between two per-hand `ALSoundState` slots (`audioHandle`/`field_A48`, same block) so a fast-firing weapon's report can overlap itself once before being retriggered. `g_GlobalTimer` advances by `g_ClockTimer = speedgraphframes` every tick (`src/game/lv.c:1023,1037`), and `speedgraphframes` is set from `deltaFrames` in `updateFrameCounters()` (`src/game/frametiming.c:64-76`) — the exact tick-delta plumbing already investigated (and partly fixed) under **D155** (catch-up-spiral clamp, same file), **D186** (`Video.FpsCap` sim-coupling clamp), **D193/D209** (AI-locomotion timing keystone — D209 the actual root cause, a 64-bit union-aliasing bug, now fixed and merged), and **D204** (audio pipeline running ~2% below real time forever, fixed by padding silence at the DAC). Since D209 and D204 both merged before this report, the SFX-cadence complaint is either (a) a genuinely distinct, audio-specific residual downstream of those fixes — e.g. a mismatch between the sim-tick-gated *trigger* (above) and the software mixer's own playback clock (`port/src/audio.c:93,155,221-222` — the synth produces samples at a measured ~21600/s against the 22050 Hz output device, the same gap D204 had to compensate for elsewhere) that specifically desyncs how *often* a shot's sample starts rather than the absolute wall-clock rate D204 measured, or (b) a stale pre-D209/pre-D204 build report. Per-weapon rate constants (`AutomaticFiringRate`/`SingleFiringRate`/`SoundTriggerRate`, `src/game/gun.h:60-82`, values in `assets/obseg/gun/*/gunWeaponStat.inc.c`) are plain compiled C literals, not ROM-serialized data, so no pointer-width/ABI reconciliation issue applies here. | **OPEN — not root-caused; static call-site tracing only, no runtime verification possible in this environment.** Confidence LOW-MEDIUM that this is a *new* bug distinct from the D155/D186/D193/D204/D209 timing-keystone family (all of which gate the identical `deltaFrames→speedgraphframes→g_ClockTimer→g_GlobalTimer` chain this SFX trigger consumes). Raise confidence by: (1) confirming the user's build/commit postdates the D209 merge; (2) a `GE_AUDIOTRACE`-style capture logging the tick (`g_GlobalTimer`) and/or wall-clock timestamp of consecutive same-weapon fire-SFX requests during sustained auto-fire (AK47/Klobb, `AutomaticFiringRate=0x03`), diffed against the weapon's rate constant and against N64 reference footage's frame-count between shots. |
|
||||
| D241 | **Player gunshot SFX goes silent under some conditions — reported on Bunker with the alarm sounding, "or some other factor" — but only for PP7 and AK47, Klobb unaffected (M-106, user QA report, concrete repro).** This is very likely **D207 finally reproduced.** D207 (M-77) root-caused that GE's SFX player has a hard **8-voice cap** (`MUSIC_SFX_SEQ_MAYBE_MAX_SOUNDS`, `src/music.c:76`) and that D206's fix made the alarm klaxon (`ALARM3_SFX`=163, triggered from `handle_alarm_gas_timer_calldamage()`, `src/game/propobj.c:14457`) a genuine **infinite ADPCM loop** that now legitimately occupies one of those 8 voices for the alarm's whole duration; the manual preemption scan (`snd.c:~347-392`) refuses to steal any *owned* LOOPED voice (the M-65 carve-out only exempts *ownerless* loops), so once 8/8 voices are held, a new one-shot request is silently dropped via `sndDisposeSound` (`snd.c:391`). D207's own M-77b session built a `GE_FORCEALARM=1` BUNKER1 harness and could **not** reproduce audible drops (`[D207-DROP]=0`), and explicitly left as "owed": *"needs a real repro from the user: which level, which weapon/situation."* This report supplies exactly that (Bunker, alarm). **Investigated why PP7/AK47 and not Klobb, inconclusive at the code level:** Klobb (`ITEM_SKORPION` — confirmed GE's internal name for the Klobb via `src/game/player.c:576`, `skorpion_stats` in `assets/obseg/gun/skorpion/gunWeaponStat.inc.c`) and AK47 (`ak47_stats`, `assets/obseg/gun/ak47/gunWeaponStat.inc.c`) share the **same** `AutomaticFiringRate=0x03` (fast auto), while PP7 (`wppk_stats`, `assets/obseg/gun/wppk/gunWeaponStat.inc.c`) is semi-auto only (`AutomaticFiringRate=0xFF`) — so a naive "fires faster ⇒ more self-inflicted voice contention" theory does not cleanly explain grouping PP7 with AK47 against Klobb. All three weapons' fire SFX (`GUN_B1_MGUN3_3_SFX`=Klobb, `GUN_B2_HEAVY_SFX`=PP7, `GUN_B4_BOLTACTION_SFX`=AK47 — `src/bondconstants.h:2277-2280`) go through the identical one-shot, priority-0x40 `sndPlaySfx` path (`gunfire.c:3246-3286`); no per-weapon distinction exists in the trigger or priority *code*. A real distinction, if one exists, most likely lives in the **sample bank data itself** (per-sample envelope/decay length — a longer-tailed sample stays `PLAYING` and stealable/held longer, changing its odds in the preemption scan) — that data is ROM-derived binary content, not inspectable without the extracted ROM (`./data/`, absent in this worktree). Confirmed the voice-allocation/preemption code is unmodified `src/snd.c` game logic, not a `port/` mixer bug: `port/src/mixer.c`/`port/src/audio.c` implement DSP synthesis and device output only (per D199), with no SDL `Mix_*`-style channel-priority layer in the port to have introduced a new bug. Per AGENTS.md non-negotiable #2, any real fix belongs in D207's already-designed (reverted, M-77b) last-resort LOOPED-preemption pass in `snd.c`, not in `port/`. | **OPEN — not newly root-caused; high confidence this is D207 reproduced, not an independent bug.** Recommend folding this repro into D207 (or closing D241 as a duplicate once D207 is reopened) rather than tracking separately. Next: re-run M-77's `GE_FORCEALARM=1` harness on `-level_09` with scripted PP7 and AK47 fire (not just generic fire) against Klobb as a control, watching `g_sndAllocatedVoicesCount` / the (currently reverted) `[D207-DROP]` counters to see whether the 8-cap is actually hit for two of the three weapons and not the third — or whether it is really the sample-envelope-length distinction above. No runtime verification possible in this environment (no ROM/display). |
|
||||
| D242 | **Player-death sequence UX regression vs N64 original: PC requires an LMB click to proceed to the mission-failed/file-select screen; user recalls N64 auto-plays 3 death-camera angles then transitions with no input needed (M-106, user QA report).** **The 3-angle auto-replay and its no-input auto-transition both exist, unmodified, in the decomp — confirmed, not a design difference removed for the port.** `bondviewSetCameraMode(CAMERAMODE_DEATH_CAM_SP)` (`src/game/bondview2.c:901-1010`) calls `pickDeathCameraAngles()` (`bondview2.c:641-782`, picks a fresh random valid camera angle each call) to stage each replay; when it returns 0 — no further valid angle found — the code calls `bossRunTitleStage()` directly, and the source comment at `bondview2.c:1006-1007` literally reads *"...or when the three death replays are finished."* The 3-cycle count is driven by `bondviewAdvanceCameraMode()` (`bondview2.c:1041-1078`): `CAMERAMODE_DEATH_CAM_SP` → `CAMERAMODE_DEATH_CAM_MP` → `camera_mode++`, looping back to `DEATH_CAM_SP` while `camera_mode < CAMERAMODE_SWIRL` (three iterations). Each individual replay's own advance is driven purely by animation/timer state (`bondview2.c:1467-1490`): the `g_CameraAfterCinema` flag that `bondviewAdvanceCameraMode()` acts on is set automatically once `modelGetAnimFrame(bodyModel) >= modelGetAnimEndFrame(bodyModel)`, or after a `camera_transition_timer >= 180.0f` tick-timeout fallback when there is no body model — **no button check in that branch; fully automatic.** **The button-press the user is hitting is a real, intentional N64 skip-ahead shortcut, not a port-added gate:** the same code block (`bondview2.c:1483-1489`) *also* fires the identical advance immediately on `(buttons & ~oldbuttons & (CONT_A | B_BUTTON | Z_TRIG | START_BUTTON))` whenever `bonddead && redbloodfinished && deathanimfinished` — i.e. on real N64 hardware, pressing A/B/Z/Start at any point during a death replay skips straight to the end, by design. **On the port, LMB (mouse fire) is mapped to `GE_CONT_G`, which is Z_TRIG** (`port/src/input.c:664-665`: `(mb & SDL_BUTTON(SDL_BUTTON_LEFT)) ... button \|= GE_CONT_G`; binding table `input.c:351`: `{"Z",GE_CONT_G}`) — exactly one of the four original skip buttons. So an LMB click during the death sequence is GE's own original cutscene-skip input, faithfully wired through the port's control remap, not a port-invented "click to continue" prompt. **What this investigation could not determine** (no build/runtime access in this worktree): whether the *automatic*, no-input path actually completes on PC in a reasonable time. `camera_transition_timer` accumulates via `g_GlobalTimerDelta` (`bondview2.c:1470,1493`), which is set from the same `g_ClockTimer`/`speedgraphframes`/`deltaFrames` chain flagged in D240 above and investigated under D155/D186/D193/D204/D209 — if any residual in that chain stalls or mis-scales specifically in this state, the auto-advance could take far longer on PC than on N64 (or effectively never complete before the player gives up and clicks), which would make an LMB click *feel* mandatory even though the ground-truth logic never requires one. | **NOT A PORT-LOGIC BUG as far as static analysis can tell.** Confidence HIGH that the LMB-click skip path is faithful original behavior (Z_TRIG-mapped, exists unmodified in `bondview2.c`), not a defect to remove. Confidence LOW (untested) on whether the no-click automatic path is actually healthy on PC. Needs a human playtest: die, then **deliberately touch no input** during the death sequence and time whether/how long until the mission-failed screen appears on its own. If it never arrives without a click, the real bug is a `g_GlobalTimerDelta`/`camera_transition_timer` stall in the D240 clock family — cross-reference D240 — not a wrongly-added input gate, so there is still no `#ifdef PORT` skip-gate to remove per AGENTS.md #2. No runtime verification possible in this environment (no ROM/display). |
|
||||
| D243 | **Dam end-of-level cutscene (the abseil/bungee sequence, D148/D160's subject): the camera violently shakes/"spazzes" during each of the scripted camera-angle cuts (M-107, live `GE_D160=1` trace, two interactive `-level_33` runs + one fresh-save normal-progression run).** **First, corrects D160's own premise: the cutscene is NOT skipped on the current build.** Both debug-loaded runs show exactly the AI-script-scripted sequence firing: `bondviewSetCameraMode(7)` (`CAMERAMODE_POSEND`, the cutscene camera-lock state) called 2-3 times, matching `ai_17`'s three `camera_switch` commands in `UsetupdamZ.c`'s abseil ailist (`assets/obseg/setup/UsetupdamZ.c:2656`+), followed by `bossReturnTitleStage`. So D160's original (b)/(c) hypotheses (AI trigger never fires / puppet renders invisibly) are **refuted on this build** — the mechanism runs; what's broken is what it renders. **Root cause candidate, directly observed:** right in the camera-cut window, fast3d's D146 catch-all (`port/fast3d/gfx_pc.cpp:3037`, "unknown GBI opcode … ending DL") fired a tight burst — same 4 memory addresses repeating every frame for ~10 frames, then clean — with payload bytes `0xfafbfbfcfcfbfdfc`/`0xfcfafbfbfafbfafb`: a **freed/uninitialized-memory fill pattern**, not real GBI commands. This reads as a stale-pointer display-list read, not a decode bug — some segment/scene buffer backing the outgoing camera shot's geometry got freed or recycled while a still-in-flight display list (queued ahead in the port's double-buffered render pipeline — see D134's "pendingGfx" note, `port/src/libultra.c:733-746`, on how many frames of latency the sched round-trip can carry) still referenced it. **Independent evidence of a race, not a fixed-order bug:** the two `-level_33` debug-load runs show the camera_switch(7) calls landing in a *different order relative to* `bossReturnTitleStage` between runs (run 1: three switches, then the stage-return call; run 2: two switches, the stage-return call, *then* a third switch arriving after) — and the D146 corruption burst appeared in run 1 but not run 2 at all, non-deterministically. That pattern (symptom present/absent, call order shuffling) is the signature of a genuine timing race between the AI-list's cutscene commands and the level-exit/report-transition code, not a deterministic logic bug. **User-confirmed on real (non-debug-boot) play, not just the debug harness:** a fresh-save normal playthrough into Dam still hit the camera glitch on this same cutscene — ruling out "artifact of the `-level_33` debug boot" for this specific symptom (unlike the secondary "no report screen, straight to file-select" symptom seen only on the debug-loaded runs, which did NOT reproduce on the fresh-save run and is therefore most likely a `-level_N` debug-boot artifact per the D190 precedent, not filed as its own bug here). | **OPEN — M-109 re-analysis of the same session's raw logs corrects the timing/pattern claims above; the D134/pendingGfx race hypothesis is likely wrong.** Confidence MEDIUM on the correction, LOW-MEDIUM on the new lead. Re-reading `data/playtest_d160.log`/`_run2.log` at the `g_framesRendered` level (not just grepping for `D146`) shows the D146 burst fires **~1600-3900 frames (tens of real seconds) *after*** `bossReturnTitleStage`/the `camera_switch(7)` calls, not during them, in **both** runs — "right in the camera-cut window" and "run 1 only" are both wrong as stated above. This also weakens the D134/`pendingGfx` citation: `osSpTaskStartGo` runs `gfx_run()` synchronously and inline (`port/src/libultra.c:1232-1258`), so nothing from the cutscene's own DL submission is still in flight by the time any teardown code runs — the port doesn't have the async-RSP race the hypothesis above assumes. **New lead:** whatever screen renders in that multi-thousand-frame gap after mission-complete is the more likely site — probably `MENU_MISSION_COMPLETE` (`front.c:7267`, `constructor_menu0D_missioncomplete`) or an adjacent briefing/file-select screen in that chain, same general class as D75 (front-end model absence) / D141 (menu bad-pointer bug), not a cutscene-render race. Of the 4 repeating fault addresses, 3 read as literal zero words and only 1 matches the `0xfafbfbfc…` pattern, arguing against a deliberate poison-fill (none found anywhere in `port/`) and for either genuinely-unwritten memory or a decode landing inside unrelated valid data. Next: an env-gated `front.c` menu-transition trace (`MENU_*` id + `g_framesRendered` on every transition after `bossReturnTitleStage`) plus a `GE_PCDUMP` window over frames ~4500-8000 (not the camera-cut window) to name the exact screen/segment. Cross-ref D160 (this supersedes its stale "cutscene skipped" framing with a "cutscene renders corrupted" one, now further corrected to "corruption isn't in the cutscene render at all") and D173 (Cradle's Bond-floating/spinning cutscene bug — same subsystem family, unconfirmed shared root cause). **M-120: theLine truncated
|
||||
| D243 | **Dam end-of-level cutscene (the abseil/bungee sequence, D148/D160's subject): the camera violently shakes/"spazzes" during each of the scripted camera-angle cuts (M-107, live `GE_D160=1` trace, two interactive `-level_33` runs + one fresh-save normal-progression run).** **First, corrects D160's own premise: the cutscene is NOT skipped on the current build.** Both debug-loaded runs show exactly the AI-script-scripted sequence firing: `bondviewSetCameraMode(7)` (`CAMERAMODE_POSEND`, the cutscene camera-lock state) called 2-3 times, matching `ai_17`'s three `camera_switch` commands in `UsetupdamZ.c`'s abseil ailist (`assets/obseg/setup/UsetupdamZ.c:2656`+), followed by `bossReturnTitleStage`. So D160's original (b)/(c) hypotheses (AI trigger never fires / puppet renders invisibly) are **refuted on this build** — the mechanism runs; what's broken is what it renders. **Root cause candidate, directly observed:** right in the camera-cut window, fast3d's D146 catch-all (`port/fast3d/gfx_pc.cpp:3037`, "unknown GBI opcode … ending DL") fired a tight burst — same 4 memory addresses repeating every frame for ~10 frames, then clean — with payload bytes `0xfafbfbfcfcfbfdfc`/`0xfcfafbfbfafbfafb`: a **freed/uninitialized-memory fill pattern**, not real GBI commands. This reads as a stale-pointer display-list read, not a decode bug — some segment/scene buffer backing the outgoing camera shot's geometry got freed or recycled while a still-in-flight display list (queued ahead in the port's double-buffered render pipeline — see D134's "pendingGfx" note, `port/src/libultra.c:733-746`, on how many frames of latency the sched round-trip can carry) still referenced it. **Independent evidence of a race, not a fixed-order bug:** the two `-level_33` debug-load runs show the camera_switch(7) calls landing in a *different order relative to* `bossReturnTitleStage` between runs (run 1: three switches, then the stage-return call; run 2: two switches, the stage-return call, *then* a third switch arriving after) — and the D146 corruption burst appeared in run 1 but not run 2 at all, non-deterministically. That pattern (symptom present/absent, call order shuffling) is the signature of a genuine timing race between the AI-list's cutscene commands and the level-exit/report-transition code, not a deterministic logic bug. **User-confirmed on real (non-debug-boot) play, not just the debug harness:** a fresh-save normal playthrough into Dam still hit the camera glitch on this same cutscene — ruling out "artifact of the `-level_33` debug boot" for this specific symptom (unlike the secondary "no report screen, straight to file-select" symptom seen only on the debug-loaded runs, which did NOT reproduce on the fresh-save run and is therefore most likely a `-level_N` debug-boot artifact per the D190 precedent, not filed as its own bug here). | **OPEN — M-109 re-analysis of the same session's raw logs corrects the timing/pattern claims above; the D134/pendingGfx race hypothesis is likely wrong.** Confidence MEDIUM on the correction, LOW-MEDIUM on the new lead. Re-reading `data/playtest_d160.log`/`_run2.log` at the `g_framesRendered` level (not just grepping for `D146`) shows the D146 burst fires **~1600-3900 frames (tens of real seconds) *after*** `bossReturnTitleStage`/the `camera_switch(7)` calls, not during them, in **both** runs — "right in the camera-cut window" and "run 1 only" are both wrong as stated above. This also weakens the D134/`pendingGfx` citation: `osSpTaskStartGo` runs `gfx_run()` synchronously and inline (`port/src/libultra.c:1232-1258`), so nothing from the cutscene's own DL submission is still in flight by the time any teardown code runs — the port doesn't have the async-RSP race the hypothesis above assumes. **New lead:** whatever screen renders in that multi-thousand-frame gap after mission-complete is the more likely site — probably `MENU_MISSION_COMPLETE` (`front.c:7267`, `constructor_menu0D_missioncomplete`) or an adjacent briefing/file-select screen in that chain, same general class as D75 (front-end model absence) / D141 (menu bad-pointer bug), not a cutscene-render race. Of the 4 repeating fault addresses, 3 read as literal zero words and only 1 matches the `0xfafbfbfc…` pattern, arguing against a deliberate poison-fill (none found anywhere in `port/`) and for either genuinely-unwritten memory or a decode landing inside unrelated valid data. Next: an env-gated `front.c` menu-transition trace (`MENU_*` id + `g_framesRendered` on every transition after `bossReturnTitleStage`) plus a `GE_PCDUMP` window over frames ~4500-8000 (not the camera-cut window) to name the exact screen/segment. Cross-ref D160 (this supersedes its stale "cutscene skipped" framing with a "cutscene renders corrupted" one, now further corrected to "corruption isn't in the cutscene render at all") and D173 (Cradle's Bond-floating/spinning cutscene bug — same subsystem family, unconfirmed shared root cause). **M-120: theLine truncated
|
||||
| D245 | **IsWater water: two competing patterns with a visible seam that moves as the player looks around (mouse/aim) — "looks almost exactly like skybox" (M-116, user QA report, post-D229-fix build).** The symptom is D227's exact signature class (s16 `Vtx.tc` overflow → rasteriser interpolates between wrapped coordinates → two differently-wrong screen regions + a seam at the wrap boundary; on the sky quad the split ran on the 0–3 diagonal). Water draws through the same port path: `src/game/sky.c` water block (~905–956) — `texSelect(&skywaterimages[WaterImageId])` + `sub_GAME_7F09343C` (two-texunit cross-fade; both tiles re-declared RGBA/16b @ tmem 0, the draw D229 fixed) then a `skyRenderTri` fan over `sp274[]` under `skyPortBeginFan(sp274, s1, FALSE)` — and **the `FALSE` is a documented, deliberate tolerance of exactly this overflow**: the comment above `skyPortBeginFan` (sky.c ~1738) records that the IsWater quad is excluded from D227's tile-shift safety valve because both texunits read the same vertex tc, so a tc rescale would need matched shifts on BOTH tiles and "shifting both makes them sample identically, which flattens the cross-fade"; the 2026-09 decision was that "the water quad keeps the plain 1:1 tc and simply tolerates the overflow on its horizon vertex, exactly as it did before D227." The mechanism is real: `unk20/unk24` are S10.5 units reaching tens of thousands near the horizon; the fold rebases to a multiple of `SKY_TC_WRAP` (2048) but with k=0 an s16 tc holds only ±32767, so any wider span wraps. It was invisible before D229 because every water texel was green mottle garbage anyway; with correct blue texels the wrapped region now reads as a visibly different pattern + seam, and it tracks the view because the fan's S/T depend on camera orientation. (On N64 this draw is a verbatim RDP `G_TRI_SHADE_TXTR` coefficient block with no s16 tc — the overflow constraint is a port artifact of re-emitting as `gSPVertex` batches, same class as D227.) | **OPEN — REOPENED (M-116 live playtest, same session): the `allowShift=TRUE` mechanistic fix below did NOT resolve the symptom.** User report on Frigate post-fix: water still "glitches, kinda goes back and forth between two graphics too quickly," and still "changes depending on your view unlike the sky" (i.e. the sky, using the identical `skyPortBeginFan`/`skyPortEmitTileShift` mechanism post-D227, no longer has this problem, but water still does after getting the same treatment). This means the `allowShift=FALSE→TRUE` change either isn't the actual fix, or there's a second mechanism specific to water's two-texunit cross-fade that the tc-shift alone doesn't address — most likely candidate: the shift only rescales the *shared* vertex tc uniformly, but the tile-0-vs-tile-1 sample-point difference is set by each tile's own `uls`/`ult` (RDP tile-space, not vertex-tc-space) — if per-frame `k` changes granularity of the *quantized* shared coordinate before each tile's independent `uls`/`ult` subtraction, the two tiles' effective sampled texel could drift relative to each other frame-to-frame in a way the static `GE_D227V` trace (single frame, single camera angle) never exercised. Needs a fresh live investigation, not a repeat of the same static-trace approach — a scripted or live multi-frame capture while rotating view, watching both tiles' *decoded* texel positions (not just vertex tc) frame over frame. Do not re-apply the same `allowShift=TRUE` reasoning without new evidence; the superseded mechanism write-up below is unconfirmed-by-playtest and may be wrong. The code change (`allowShift=TRUE` on the water quad) is left in place — it's not shown to be actively harmful, and reverting it isn't obviously better than leaving it, pending the fresh investigation. **Superseded analysis (unconfirmed by playtest, kept for reference):** ~~FIXED (port-layer, `src/game/sky.c`, `#ifdef PORT`) — confirmed the `allowShift=FALSE` tolerance was the cause, and the "flattens the cross-fade" objection was wrong.~~ Changed the water quad's call from `skyPortBeginFan(sp274, s1, FALSE)` to `..., TRUE)`, so it now gets D227's tc-shift safety valve like every other sky fan. Why the old objection doesn't hold: `skyPortEmitTileShift` replays **both** tiles' captured `SETTILE` commands with the identical `k`, so both texunits' decoded S/T are rescaled by the same `2^k` *before* each tile's own (unshifted, tile-native) `uls`/`ult` offset is applied by the RDP — the offset between tile 0 and tile 1's sample points (what drives the cross-fade) is preserved, not flattened, because the shift and the offset act at different stages. **Verified mechanistically:** `GE_D227V=1` on Frigate (`-level_26`) shows a water fan vertex spanning S=-166..299317, T=-6451..288410 in S10.5 units — tens of thousands of texels, the same order of magnitude as D227's pre-fix sky overflow — now getting `kS=4 kT=4` (previously forced to 0), decoding to small in-range tc valuesLine truncated
|
||||
| D246 | **Tiny pixel strips at the left and right edges of the screen, "like overscan" — look like raw pixels, not 3D graphics (M-116, user QA report).** Uninvestigated. The port renders at native 640×480 (`GE_NATIVE_W/H`; `gfx_current_native_aspect` set in `port/src/video.c:253,570`) and scales to the window on present, so the leading candidate is a scaling/blit rounding artifact: non-integer scale factors leaving 1–2 columns of uncleared backbuffer content at the edges (or an off-by-one in the blit rect). Secondary candidates: an RDP fill-rect/scissor off-by-one in a game DL or the F10 overlay hook (`gfx_run()` seam, D184), or GL edge filtering on the scaled texture. | **OPEN — one candidate ruled out (M-116 follow-up), root cause not found.** Step (1) from the original plan is done: a `GE_PCDUMP` capture (Frigate, native 640×483 buffer, both against the existing save and a fresh EEPROM) shows clean, continuous scene content in the leftmost/rightmost columns — no dead/black/garbage strip at either edge. That means the defect is **not** baked into the native render itself (rules out an RDP fill-rect/scissor off-by-one in a game DL, and rules out D227/D245-style tc issues) — it must be introduced later, specifically in the SDL-window presentation path that `GE_PCDUMP` bypasses entirely (`gfx_run()` reads back the game's own render target directly, never the actual window backbuffer the user sees). Re-examined `gfx_adjust_viewport_or_scissor` (`gfx_pc.cpp:2174`): its floor/ceil rounding is already deliberately anti-gap (comment: "prevents sub-pixel gaps at viewport edges"), so a plain rounding bug there is a weaker lead than it looked; more likely candidates now are the `game_renders_to_framebuffer`-path MSAA resolve `glBlitFramebuffer` (`gfx_opengl.cpp:1252`, only exercised when `Video.MSAA>1` or the window size actually differs from the game's target size mid-frame — a path `GE_PCDUMP`'s headless capture doesn't exercise the same way a live resizable window does) or a genuine sub-pixel/DPI scaling remainder specific to the user's actual window dimensions. **Next, still needs the user (no live window access in this environment):** (1) confirm current `Video.MSAA` setting and window size/aspect at the time the strips were seen; (2) if `MSAA>1`, retest with `Video.MSAA=1` to see if the strips disappear (isolates the resolve-blit path); (3) report whether the strip width changes with window size (fixed 1px = blit-rect off-by-one; scales with window = scale-factor remainder). Cross-ref D247 (separate, now closed — don't conflate the two; D247's letterbox is confirmed-intended and top/bottom only, D246 is left/right and still unexplained). |
|
||||
| D246 | **Tiny pixel strips at the left and right edges of the screen, "like overscan" — look like raw pixels, not 3D graphics (M-116, user QA report).** Uninvestigated. The port renders at native 640×480 (`GE_NATIVE_W/H`; `gfx_current_native_aspect` set in `port/src/video.c:253,570`) and scales to the window on present, so the leading candidate is a scaling/blit rounding artifact: non-integer scale factors leaving 1–2 columns of uncleared backbuffer content at the edges (or an off-by-one in the blit rect). Secondary candidates: an RDP fill-rect/scissor off-by-one in a game DL or the F10 overlay hook (`gfx_run()` seam, D184), or GL edge filtering on the scaled texture. | **OPEN — one candidate ruled out (M-116 follow-up), root cause not found.** Step (1) from the original plan is done: a `GE_PCDUMP` capture (Frigate, native 640×483 buffer, both against the existing save and a fresh EEPROM) shows clean, continuous scene content in the leftmost/rightmost columns — no dead/black/garbage strip at either edge. That means the defect is **not** baked into the native render itself (rules out an RDP fill-rect/scissor off-by-one in a game DL, and rules out D227/D245-style tc issues) — it must be introduced later, specifically in the SDL-window presentation path that `GE_PCDUMP` bypasses entirely (`gfx_run()` reads back the game's own render target directly, never the actual window backbuffer the user sees). Re-examined `gfx_adjust_viewport_or_scissor` (`gfx_pc.cpp:2174`): its floor/ceil rounding is already deliberately anti-gap (comment: "prevents sub-pixel gaps at viewport edges"), so a plain rounding bug there is a weaker lead than it looked; more likely candidates now are the `game_renders_to_framebuffer`-path MSAA resolve `glBlitFramebuffer` (`gfx_opengl.cpp:1252`, only exercised when `Video.MSAA>1` or the window size actually differs from the game's target size mid-frame — a path `GE_PCDUMP`'s headless capture doesn't exercise the same way a live resizable window does) or a genuine sub-pixel/DPI scaling remainder specific to the user's actual window dimensions. **Next, still needs the user (no live window access in this environment):** (1) confirm current `Video.MSAA` setting and window size/aspect at the time the strips were seen; (2) if `MSAA>1`, retest with `Video.MSAA=1` to see if the strips disappear (isolates the resolve-blit path); (3) report whether the strip width changes with window size (fixed 1px = blit-rect off-by-one; scales with window = scale-factor remainder). Cross-ref D247 (separate, now closed — don't conflate the two; D247's letterbox is confirmed-intended and top/bottom only, D246 is left/right and still unexplained). **M-141: Steam Deck playtest confirmation, new platform data point** — user reports "some artifacts left and right of screen, only a sliver... like the fullscreen isn't quite going to full screen" specifically in **Full Screen mode on Steam Deck**. Same symptom description as the original M-116 report (Windows). Checked: the Deck's own first-run preset (`videoApplySteamOSDefaults()`, `port/src/video.c:255`) sets `cfgMSAA = 4` by default — exactly the condition this row already flagged as the leading remaining suspect (the MSAA-resolve `glBlitFramebuffer` path, step 2 of the still-open bisection ask). Next: have the user set `Video.MSAA=1` via F10 and retest on the Deck specifically — if the strips vanish, this row's root cause is confirmed without needing any more code tracing. |
|
||||
| D247 | **Black bars top and bottom of screen — possible aspect-ratio defect (M-116, user QA report).** Uninvestigated. Context: the port currently renders native 4:3 (640×480); widescreen is a *parked proposal* (`docs/dev/WIDESCREEN-FOV-PLAN.md` — PD-parity native 16:9 render, needs rule-#2 sign-off for its game-code HUD wave), so if the user's window/display is wider than 4:3 and the present path letterboxes to preserve aspect, the bars are current intended behaviour rather than a bug. If they appear in a 4:3 window too, it is a real defect (framebuffer sized wrong vs window, `videoCreateFramebuffer(w,h,upscale,autoresize)` → `gfx_create_framebuffer`). | **NOT A BUG — confirmed faithful, unmodified N64 decomp behaviour, no code change made.** `GE_PCDUMP` captures (both against the pre-existing save and a fresh-EEPROM run, to rule out a stale `Screen` option from earlier test sessions) show the exact same inset on Frigate: real scene content occupies rows ~20–459 of a 640×483 capture (the 483 vs 480 is a separate, harmless 3-row capture-tool rounding quirk, not investigated further), i.e. roughly 220/240 of the vertical field — and `src/game/fr.h:34` defines `VIEWPORT_HEIGHT_DEFAULT_NTSC 220` (240 for the full field), consumed unmodified by `bondviewGetCurrentPlayerViewportHeight()` (`bondview2.c:7858`) as the **default in-game viewport height whenever `PLAYER_OPTION_SCREEN` is `SCREEN_SIZE_FULLSCREEN`** (the decomp's own default value, `options.c:128`) — `VIEWPORT_HEIGHT_FULLSCREEN` (304, no inset) is a red herring name; it's only reachable via the `cameraBufferToggle` branch, not normal solo play. This is GoldenEye 007's well-documented original TV-safe-area letterboxing, baked into ground-truth decomp constants with no `#ifdef PORT` anywhere near it — the N64 game genuinely doesn't fill the full field during normal gameplay. Ruled out a stale/leftover EEPROM `Screen=Wide/Cinema` setting from earlier QoL-testing sessions as an alternative explanation: a fresh EEPROM (default options) shows the identical inset. **No fix needed; this row closes.** If the user's window shows noticeably *thicker* bars than this ~8–9%-per-edge margin, or bars appear on the *sides* instead of top/bottom, that would be a genuinely different (and real) symptom worth reopening under a new label — but "thin bars top and bottom, present at any window size" is correct, original behaviour. Cross-ref D226 (separate, already-tracked HUD-scale QoL ask — unrelated mechanism, don't conflate). |
|
||||
| D248 | **Port runs a stable 30 FPS instead of 60 FPS game-wide (M-117, user asked to verify FPS after the D209 timing-keystone fixes).** The port's pacemaker (`port/src/libultra.c` `portTickThread`) posts a VI-retrace event at a true 60Hz (NTSC) cadence, so the halving had to be somewhere downstream of that. | **FIXED, root-caused to a genuine pointer-width ABI bug in `src/sched.c` (compiled for the PC build, not excluded).** `osScAddClient()` (`src/sched.c:187-199`) stashes a per-client "present every retrace vs. every other retrace" flag by writing into the *next array slot's* `.next` field (`c[1].next = next`) rather than adding a real struct member — `gfxClient` (`src/init.c:246`) is registered with `next=NULL` (send every retrace → 60Hz) and the audio client `g_AudioClient[0]` (`src/audi.c:441`) with a nonzero sentinel `1` (send every other retrace → 30Hz, its intended cadence). `__scHandleRetrace` (`src/sched.c:334`) reads that flag back with a hardcoded byte offset, `*((s32*)client + 2)` (2×`sizeof(s32)` = 8 bytes). On the N64 (32-bit pointers) `sizeof(OSScClient)` is exactly 8 bytes, so offset 8 correctly lands on `client[1].next` — matching the write. On this 64-bit port `sizeof(OSScClient)` is 16 bytes, so the same hardcoded 8-byte offset instead reads the low 32 bits of `client->msgQ` itself (always a real, non-NULL pointer) — the gfx client's flag reads as permanently "nonzero" and silently falls onto the audio client's every-other-frame path, halving the game's real render/tick cadence to 30Hz although the retrace pacemaker itself was always ticking at the correct 60Hz. Same D122/D126/D132/D209 bug class (32→64-bit pointer-width struct-size growth desyncing a hand-rolled offset), just in the scheduler instead of a ROM-serialized struct. **Fix (`#ifdef PORT`, N64 line kept verbatim under `#else`):** read the real field by indexing the struct (`client[1].next == 0`) instead of hardcoding a byte offset — this naturally scales with pointer width on either platform and is the direct decompilation of what `osScAddClient`'s write side already does. **Verified:** `Video.DisplayFPS=1` overlay (`port/src/optionsoverlay.c`, D213) read a rock-stable "30 FPS" on `-level_09` (BUNKER1) pre-fix and "60 FPS" post-fix, same build/scene, screenshots captured via `GE_PCDUMP`. Rebuilt clean (`ninja -j1`, pre-existing unrelated warnings only); `-level_09` and `-level_20` both ran 12-30s post-fix with no crash (`ge007.crash.log` absent). No game-code files touched beyond this one mechanical read (`src/sched.c` is compiled for the PC target per `CMakeLists.txt`'s `SRC_ENGINE` list, not one of the excluded os/io files) and the audio client's own flag/path is completely untouched, so `#ifdef PORT`-gated audio pacing (D204 tempo work) is unaffected. **Note for `docs/dev/UNLOCKED-FPS-PLAN.md`:** this settles that plan's Phase 0 open question — the ~30fps default was a real port bug, not original N64 behavior or an artifact of retrace-rate tracking sim speed; the plan's default-preset baseline should be re-measured at the corrected 60fps before Phase 1 pacing work resumes. Not yet done: a full `verify.sh sweep` across all 21 levels (only `-level_09`/`-level_20` spot-checked this session) and a live human playtest for feel (input latency, animation smoothness, any level-specific perf regression now that render work happens twice as often per second). |
|
||||
| D249 | **LOD/culling breaks down on large open levels (Streets, Egyptian named specifically) at *default* FOV — far-away geometry has visible culling issues (M-119, user QA report).** Distinct from **D222** (which is specifically high `Video.FovScale` breaking *near-screen-edge* culling on NPCs/objects — not implicated here, default FOV) and from **D218** (the fog/far-clip *tint* boundary at wide FOV — also not the same symptom). This report is at the stock FOV, on the game's two biggest/most-open outdoor-scale levels, affecting *far* geometry specifically — points at either (a) a portal/room-visibility BFS cutting off too aggressively at range on large multi-room layouts (same neighbourhood as D233/D234's near-camera portal-cull bugs — worth checking whether D233's `gfx_sp_tri1` w<0 trivial-reject fix, which only guarded *behind-camera* vertices, has a sibling far-plane/depth-range case), or (b) GE's own N64 LOD/detail-swap system (mip-tile / simple-model substitution at distance — see D218's still-open question of "does GE have a separate geometry/object LOD-swap distinct from `Visibility.FarFog`") not being ported faithfully, popping or dropping geometry instead of swapping detail level. **Second, separate ask bundled in the same report:** the user would like LOD **improved** in general (presumably distance-based detail transitions look rougher/more abrupt on PC than remembered) but explicitly wants this **gated behind a toggle** so the original N64-accurate LOD behaviour stays selectable ("in case you want old school graphics") — i.e. this is a QoL feature request layered on top of a correctness bug, not one fix. | OPEN — not investigated. Next: reproduce on Streets (`-level_20`) and Egyptian (`-level_08`, confirm level id) at default `Video.FovScale`; `GE_PCDUMP` a long sightline where geometry visibly drops/pops, cross-referencing whatever governs GE's LOD-tile selection (`gfx_lod_tile_offset` family, already touched by D172/D219) and the portal/room-visibility walk (D233/D234 neighbourhood) to determine which of (a)/(b) is actually firing before designing a fix or the requested toggle. |
|
||||
@@ -564,6 +564,11 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
||||
| D286 | **User-confirmed real (not system-side) ~8s frame freeze on Caverns, Steam Deck, caught during the same live session as D255/D285 — self-recovered, not a crash.** The game's own hang watchdog (`portHeartbeatCheck`) fired once (no frame for ~8s, `frames=46404`), dumped all-thread state via `crashDumpThreads`, then rendering resumed normally and the session continued to a clean close. Not tied to an explosion/terminal (rules out D255/D285). Not root-caused — no distinguishing log line precedes it; leading unconfirmed guess is a heavy room/asset stream-in stall (Caverns is a large level, cross-ref D250's FPS lineage, though the symptom shape differs). Also fixed in passing: the heartbeat log printed a raw microsecond value with a `ms` label (cosmetic, `port/src/libultra.c`). Full body below. | OPEN — one confirmed occurrence, no repro trigger identified, not root-caused; distinct from D255/D285. Do not chase a repro without a better lead. |
|
||||
| D287 | **User-confirmed real, non-self-recovering freeze on Windows (M-141, 2026-09-15/16) after ~15-20 minutes with no input, triggered by clicking back into the window.** Caught live under `tools_pc/debug.ps1` (gdb-attached), evidence preserved at `scratch/d287_freeze_gdb.txt`. Last real `frame N rendered` log line: frame 17616. The heartbeat (`portHeartbeatCheck`) then fired 3 times with a **growing** stall (8001ms → 13017ms → 18021ms, `frames=17616` unchanged all 3 times) and then stopped logging entirely, while the VI-retrace pacemaker (`D51 vi post #N`) kept incrementing at its normal ~60 Hz rate for well over 100 more real seconds until the user closed the window — i.e. the pacemaker/OS-timer side is alive, the sim/render side is genuinely wedged, not the D286 shape (which self-recovered after one dump). **Different from D286**: no explosion/level-streaming context (idle + refocus, not combat), and the growing-then-silent heartbeat pattern (vs. D286's single fire-then-recover) points at a different mechanism. **Leading lead, not yet confirmed:** at freeze time, `gfxFrameMsgQ` (`src/init.c`, the scheduler's 32-slot client completion queue, registered via `osScAddClient`) shows `valid=32/32` — completely full — while the VI-retrace queue (8-slot) is not (`valid=1/8`). A full *client* queue with the producer (scheduler) still ticking implies the **consumer** (the main thread's `osRecvMesg(&gfxFrameMsgQ, ...)` in the frame loop) stopped draining it — consistent with the main/render thread blocking on something else (a lock, a GL call, a Windows message-pump stall) during or after a long-idle refocus, not with the scheduler itself stalling. Not yet identified what that something is. A pre-existing, currently-unused diagnostic (`GE_D60`, `port/src/libultra.c:630+`) already watches `gfxFrameMsgQ` receives and looks like the right next probe to enable. **Next, concrete:** reproduce with `GE_D60=1` (and ideally `GE_INPUTLOG=1`) running under `debug.ps1`, leave the window idle+unfocused 15-20 min, refocus, and once frozen `bt full` the actual game/render thread (not just the auto-dump's generic `WaitForMultipleObjects` frames for `shedThread`/`audioThread`) to find what it's blocked on. Cross-ref D134's superficially-similar-sounding-but-different mechanism (a different, 8-slot queue, already fixed) — do not assume the same fix applies here without confirming the actual blocked call site. | OPEN — one confirmed occurrence, real (not self-recovering), not root-caused; `gfxFrameMsgQ` full is a lead, not a confirmed cause. |
|
||||
| D288 | **Silo intro: a huge, flat-shaded, untextured brown/tan triangle sweeps across the frame during the camera pan down the missile shaft (M-141, user report — "weird triangle drawn on silo intro randomly when the camera pans around the rocket"; previously catalogued as `GRAPHICS-BACKLOG.md` D171, "observed, not investigated" since M-34, never given a full findings.md entry until now).** Reproduced headlessly this session: `GE_PCDUMP=1-2000:10 ./build-pc/ge007.x86_64.exe -level_20` (run backgrounded, not foregrounded — see the environment note below), captured to `./ppm/`. Visual sweep (contact-sheet montages, `docs/img/bugs/D288-silo-stray-triangle-sheet.png` covers frames 571-751; `-closeup.png` is a full-res crop of frame 691) shows: **clean at frames 481-561** (no triangle), **first appears between frame ~562-601**, grows continuously in screen size through frame ~711 (by which point it covers roughly a third of the frame), then **shrinks and is gone by ~731-741** as the scene cuts to the first-person shaft-corridor view. Sampled pixel color is **exactly uniform** across the whole triangle (`RGB(83,62,44)` / `#533E2C` at two different sample points) — no gradient, no texture detail — i.e. this is a flat-shaded (no per-vertex color variance, no texture bound) single triangle, not garbage/noisy vertex-color data. **Reading of the evidence:** the grow-then-shrink-then-vanish arc across ~15 real seconds of camera motion is the classic signature of a genuine 3D triangle in world space that the camera flies close to/through during the scripted pan — not a 2D HUD/UI element (which would stay roughly fixed in screen size) and not obviously a `D146`-class freed-memory read (which reads as noisy garbage bytes, not a clean uniform flat color). The flat uniform color argues this is a REAL, correctly-colored asset triangle whose position/transform is wrong, which points at the same neighborhood as D106/D233 (near-plane / w-clip mishandling in `port/fast3d/gfx_pc.cpp`) rather than a game-code or converter data bug — but this is a read of the visual evidence, not a code-level confirmation; no probe has been run yet. **Not yet identified:** which model/prop this triangle belongs to (nothing in `UsetupsiloZ.c` was grepped for a matching solid-brown material this session), and no `port/fast3d/` tracing was done to catch the actual triangle at draw time. **Environment note (new, useful beyond this bug):** launching `ge007.x86_64.exe` in the **foreground** under this session's Bash tool with `timeout N ...` produced **zero** `ppm/` output despite `GE_PCDUMP` being set and verified exported — the `ppm/` directory was never even created (`GE_MKDIR` never ran or `frames`-gated capture never fired). Backgrounding the same invocation (`... &`, `sleep N`, `kill`) worked immediately and reliably. Not root-caused (didn't block this investigation), but worth remembering for the next session that needs `GE_PCDUMP` from this same tool. **Next, concrete:** (1) grep `assets/obseg/setup/UsetupsiloZ.c` / the missile-shaft model's material table for a solid `RGB(83,62,44)`-ish prim/vertex color to identify the source asset; (2) add a temporary `#ifdef PORT` probe in `gfx_sp_tri1` logging any triangle whose post-transform screen-space bounding box exceeds some fraction of the viewport together with each vertex's `w`, gated to `-level_20` frames ~560-740, to catch the culprit triangle's `w` sign/magnitude at the moment it blows up — same style as the existing `GE_D271`/`GE_D104` probes; (3) cross-check against D233's `w<0` trivial-reject fix and D106/D271's near-plane portal-bound clamp to see if this is a third site with the same underlying class of bug, or a genuinely new mechanism. | OPEN — visually confirmed and precisely bounded (frames ~562-741 of a bare `-level_20` boot), root cause not yet identified; promoted from the long-parked `GRAPHICS-BACKLOG.md` D171 entry with real evidence. |
|
||||
| D289 | **Dam: a crash in the computer room, not reproducible on a second attempt (M-141, Steam Deck user playtest).** No crash log or further detail captured — the session moved on before it could be investigated live. Could be any of several already-open Dam-adjacent crash classes (D255-family list-race timing, a stray D146-class stale-DL read, or something new) — genuinely unknown without a crash log or a repeat occurrence. | OPEN — single non-reproducing occurrence, no evidence captured (no crash log, no repro). If it recurs, get `ge007.crash.log` (or a gdb `bt full` if running under `debug.ps1`) and the exact in-room action/trigger. |
|
||||
| D290 | **Watch's in-game "controller" section shows no control-pad graphic (M-141, Steam Deck user playtest).** Likely a missing/failed texture or model bind on one of the watch's equipment-display pages — candidate area `src/game/bondview2.c` (watch display) alongside the front-end options/controller-config screens, not yet located precisely. | OPEN — observed, not investigated. Needs a live repro (open the watch, navigate to the controller-config page) plus a screenshot for comparison against N64 reference. |
|
||||
| D291 | **Dam: texture glitches when the truck drives past (M-141, Steam Deck user playtest).** No further detail captured (which surface/texture, timing, one-shot vs. every pass). Could be a moving-vehicle-specific instance of a known texture-cache/TMEM-residency class (cf. D124's stale-DL-marker mechanism, a different object family) or something new. | OPEN — observed, not investigated. Needs a live repro with the truck's exact position/timing and, ideally, a screenshot. |
|
||||
| D292 | **Death animations are glitched — Bond is often sunk halfway into the floor (M-141, Steam Deck user playtest).** Sounds adjacent to D173's puppet-position family (a render-side model offset bug, not a position/collision bug — matches "player spawn/position is correct, the model isn't") but not confirmed to share a mechanism; could equally be a death-pose/ragdoll-specific animation or ground-height issue distinct from D173's spawn-time case. | OPEN — observed, not investigated. Cross-ref D173 when someone next investigates either — worth checking together, not confirmed related. |
|
||||
| D293 | **QoL ask: no discoverable way to quit the game from the UI (M-141, Steam Deck user playtest — "couldn't see a way to exit the game. Maybe build this into the settings/options menu.").** Confirmed in code: the only quit path is `SDL_QUIT` (`port/src/video.c:515`, the OS window-close event / Alt+F4) — there is no Escape-key handler and no "Quit" entry in the front-end menus or the F10 options overlay. Fine on a desktop with visible window chrome; a real gap on Steam Deck / controller-only / fullscreen setups with no easy way to reach the OS window controls. | OPEN — not investigated, low complexity. Candidate fix: a "Quit to Desktop" row in the F10 overlay (`port/src/optionsoverlay.c`) and/or an Escape-key binding in the front-end menu context (`port/src/input.c`), calling the same path `SDL_QUIT` already triggers. Cheap, port-only, good QoL-batch candidate. |
|
||||
|
||||
Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS
|
||||
threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software
|
||||
|
||||
Reference in new issue
Block a user