diff --git a/docs/dev/GRAPHICS-BACKLOG.md b/docs/dev/GRAPHICS-BACKLOG.md index 91f91fc3..6f1a2bf4 100644 --- a/docs/dev/GRAPHICS-BACKLOG.md +++ b/docs/dev/GRAPHICS-BACKLOG.md @@ -14,7 +14,7 @@ once the game is playable start-to-finish without crashes. | **D76** | 2D disclaimer screen only partially drawn. | §F D76. | | ~~**D77**~~ | ~~No audible music on PC.~~ | **RESOLVED (M-104, PR #42) — in-level music now plays.** Root cause was a D6-class missing `return` in `sub_GAME_7F0C0BF0` (`mp_music.c`) that zeroed the seqp volume on the only in-level trigger path; menu/intro music bypassed it and was always fine. The remaining audio-*quality* gap (wrong-sounding instruments on some tracks — elevator named specifically — plus occasional garbling) is tracked as **D230**. §F D77/D230. | | ~~**D116 (HUD)**~~ | ~~proportional-font strings + ammo counter mirrored~~ | **CLOSED — NOT A BUG (M-33, §F D168).** Same capture-orientation artifact as D114. | -| **D148** | Level-end cinematics don't play. Dam: hitting the exit trigger should show Bond rappelling down the dam (the bungee jump); instead it cuts straight to the mission-complete report. **Broader (M-49 full playthrough): cutscenes are buggy in many places across the game**, not just the Dam ending — the user reports frequent cutscene glitches (skipped, wrong camera, misplaced/hovering actors, timing). Treat D148/D160 as the tracking entry for the whole cutscene-reliability problem, not just Dam. | Scripted cutscene / cinematic-camera + Bond rappel anim. Found M-27 during the first full Dam→Facility playthrough. Progression is unaffected — the report and next briefing load fine. The widespread nature (M-49) points at a systemic cause: the propDef/`sizepropdef` stride desync (D122/D132 family) that misreads `CutsceneRecord` lookups, and/or D75 anim-model + D173 actor-position bugs showing up wherever a cutscene puts a character on screen, and/or the D155/D156/D193 timing family driving cutscene camera/anim pacing. **M-31 (§F D160):** static-traced the Dam end ailists (`ai_24`→`ai_17`, `camera_switch`→`CutsceneRecord` type-46 lookup). Diagnostic `GE_D160=1` shipped (`c95713f5`) — user runs Dam to the exit with it set; the trace pins whether the cutscene AI runs, and whether the propDef-index walk (D122/D132 family) hands `camera_switch` a bogus cutscene record. Leading hypothesis: propDef `sizepropdef` stride desync (medium confidence), not D75 anim-model. **M-87 user playtest:** Bond floating/hover cutscene glitch (D173) still present; new specific instance — **Bond spins in circles in some cutscenes**, named example the end-of-Cradle Trevelyan-falls cutscene. Likely the same actor-position/anim-root cause manifesting as bad rotation instead of bad height. | +| **D148** | Level-end cinematics glitch. Dam: the Bond rappelling/bungee cutscene plays but the camera violently shakes and Bond's own model appears to duplicate/glitch. **Cutscenes are buggy in many places across the game** (D173 level-start floating, D292 death-sinking, Cradle spin) — treat D148/D243 as the tracking entry for the whole cutscene-reliability problem, not just Dam. | **2026-09-17 (D243 M-107 through M-168, ~18 session-passes): the mechanism is now understood in real depth, and the camera-shake half is fixed (diagnostically, live-confirmed).** Full history + code citations: `docs/dev/findings.md` `## D243` (long, multi-pass — look up via `findings-index.csv`, read the M-159 through M-168 paragraphs for the current understanding, don't linear-read). Short version for planning purposes: **(1) Root mechanism** — the cutscene teleports the *visible* Bond chr between shot positions correctly (`AI_TRYTeleportingChrToPad`, `chrai.c:4064`), but the actual rappel *animation* commands run on a separate, invisible driver chr (M-145) — the visible chr never gets told to play it, so it just keeps ticking whatever animation it already had equipped before the cutscene grabbed it. Every tick, that stale animation's own root motion gets read back into the chr's render position (`chrUpdateAnim`→`getsuboffset`, `model.c:684`), producing a small repeating cycle that reads as "shaking"/"glitching" on screen. Whether this exact mechanism is itself decomp-faithful (with the real PC/N64 divergence being *which* animation phase Bond happens to inherit at the cutscene boundary, not a code-path bug) is still an open question (M-165/M-166) — not yet answered. **(2) Two independent render paths, not one bug** — the camera's look-at filter reads `field_488.pos` (derived from `render_pos`, the model's animated-skeleton transform); the model's own on-screen draw reads `render_pos` directly (`objecthandler.c`'s `drawjointlist`). Fixing one does not fix the other; two live-test cycles were spent learning this the hard way (M-162/M-163 → M-167). **(3) Camera shake: FIXED at the mechanism level, live-confirmed by the user** ("camera shake is gone now") by freezing `field_488.pos` during the cutscene (`GE_D243X3`, `bondview2.c:10495`) — diagnostic-only, not shippable as-is (see next). **(4) Bond's own model duplication: mechanism-level fix attempted (`GE_D243X4`, `chr.c`, freezing `render_pos`'s contents) — stable after fixing two crashes (model-pointer-reuse hazard, see `porting-notes.md`'s two D11 updates), but freezing unconditionally also breaks the cutscene's *legitimate* per-shot camera/model repositioning** (the camera now looks the wrong direction for later shots; Bond still looks stuck) — not a fix yet. **Next steps for whoever picks this up** (none attempted yet): (a) re-baseline the `GE_D243X3`/`GE_D243X4` freeze on a position-delta threshold (a jump much larger than the observed intra-shot cycling amplitude, already characterized in the M-153/M-160 captures, is very likely a real shot-change teleport, not cycling) instead of freezing forever; or (b) hook the freeze/re-baseline directly to `AI_TRYTeleportingChrToPad` firing, the precise, unambiguous signal for "a legitimate reposition just happened"; (c) separately, find the actual N64-vs-PC divergence point per M-165/M-166 (log `aidx=`/`animframe=`, already wired into `D243M: onscreen`) to determine whether a real, permanent fix is even a `port/`-layer timing correction or needs a rule-2 sign-off for a genuine behavioral difference — this determines which of (a)/(b) is worth turning into a real fix vs. a stopgap. | | ~~**D149**~~ | ~~Front-end MISSION COMPLETE / mode-select 3D models (dossier, wallets) render as garbled full-screen geometry or not at all.~~ | **CLOSED — user-verified fixed (M-87 playtest).** No longer reproduces; presumably fixed as a side effect of other front-end/D75-family work since M-49. D75 family. §F D146. | | **D170** | Named-NPC "flee" scripted behavior looks wrong on PC: on Silo, Ourumov breaks into a full run the instant he alerts ("Ourumov: Kill him!") rather than the N64 walk/retreat; developer reports Trevelyan behaves the same way on Cradle. **M-49 full playthrough: escalated — Cradle (`-level_51`) is "bugged out completely" from Trevelyan's behavior; the final level's scripted progression breaks. Now folded into / superseded by §F D193 (NPC AI locomotion too slow).** | **Observed, not investigated** (M-34, from a Silo playtest capture; user bug report — no fix wanted yet). Could be a genuine ailist / `chrTick` divergence, OR the D155/D156 wall-clock `deltaFrames` locomotion-anim speed-up (a catch-up spike playing the walk cycle at 2–3×). **Verify first:** capture the same beat against N64 reference footage before assuming it diverges — Ourumov may be scripted to flee. If it's real, suspect the frametiming clamp (§F D155/D156) or the Cradle-family AI path (§F D123). | | **D171** | Silo missile-shaft: a large triangle sweeps across the frame while panning down the shaft. **Promoted to `docs/dev/findings.md` §F D288 (M-141)** with a user-reproduced, precisely-bounded, visually-documented repro (frames ~562-741 of a bare `-level_20` boot) — read that entry for the current state, not this row. | See D288. | diff --git a/docs/dev/findings.md b/docs/dev/findings.md index f181abb6..ad3a68bc 100644 --- a/docs/dev/findings.md +++ b/docs/dev/findings.md @@ -10977,6 +10977,32 @@ Cross-referencing M-145's already-established architecture: `AI_TRYTeleportingCh **Status (from §F table):** **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: the `GE_D243` trace above was BUILT (statically, i.e. written) on branch `investigate/d243-cutscene-race` (`764a1f26`, pushed, not merged) — `src/game/front.c` only, `#ifdef PORT`, logs the target `MENU_*` name + a local per-tick counter on every `menu_init()` state transition. NOT build-verified: two independent sessions (the dispatched subagent, then this session re-verifying in a fresh worktree) both hit the same reproducible failure — `cmake`'s C-compiler ABI check fails with "Cannot create temporary file in C:\Windows\: Permission denied" when gcc is invoked *through* CMake/ninja, even though a bare `gcc -c` invocation from the same shell, same session, succeeds and correctly resolves its temp dir to `%TEMP%`. This is a genuine, reproducible environment issue distinct from the diff's content (static review of the diff found nothing wrong: enum cases all verified to exist in `bondconstants.h`, brace/`#ifdef` balance checked, follows the existing `GE_D160`/`GE_D178` env-gated trace idiom used elsewhere in the same file) — worth a session with a working toolchain investigating the CMake/ninja-vs-bare-gcc environment discrepancy itself as its own item, separate from D243. Next: build `investigate/d243-cutscene-race` on a session where `./build-pc.sh` actually completes, then run the `-level_33` Dam repro with `GE_D243=1` (and the existing `GE_D160`/`GE_PCDUMP` mechanisms) to correlate the trace against the D146 burst's frame range. **M-141: the M-120 toolchain blocker is EXPLAINED and worked around, GE_D243 is now build-verified.** The "Cannot create temporary file in C:\Windows\" failure isn't specific to CMake/ninja vs. bare `gcc -c` — it's this Claude Code session's `Bash` tool shell (Git-for-Windows Bash, not a real MSYS2 MINGW64 window) not exposing a usable `TMP`/`TEMP` to child processes the way an interactive MSYS2 shell does; a bare `gcc -c` from the same shell happened to not hit the temp-file path M-120 tested, which is why it looked clean while the full build wasn't. Fix: explicitly `export TMP=... TEMP=...` (Windows-style path) alongside the already-known `PATH=/c/msys64/mingw64/bin:/c/msys64/usr/bin:$PATH` fix, both before invoking `./build-pc.sh`/`ninja`/`cmake` — now also recorded in `porting-notes.md` §E as a general session-start gotcha, not just a D243-specific note. Cherry-picked the `GE_D243` trace commit (`764a1f26`) cleanly onto `release/v0.3.0` (branch `investigate/d243-fresh`), fixed one thing the original diff got wrong per this project's own D250 rule (`menu_init()` runs ~once/frame during front-end states — the diff's three `getenv("GE_D243")` calls were uncached; now a single cached `static int s_d243` check), and confirmed it builds clean. **Smoke-tested, not fully exercised:** a bare `-level_33` boot runs 1100+ frames with zero crashes and (correctly) zero `D243:` log lines — bare `-level_XX` skips the front end entirely (per this doc's own M-24-era note), so `menu_init()` never runs; this only proves the probe doesn't regress normal boot, not that it captures anything useful yet. **Still blocked on the same thing M-107/M-109/M-120 all were: an actual interactive playthrough** (main menu → Dam → abseil cutscene → mission-complete → whatever screen the D146 burst fires on, ~1600-3900 frames later) with both `GE_D243=1` and `GE_D160=1` set, which needs a human with input, not available in this session. **Possible connection to D173 worth checking in that same playthrough** (per user, M-141): D173 (floating/spinning Bond puppet in cutscenes, e.g. Cradle/Trevelyan) and D243 (stale-memory DL read some seconds after a cutscene-driven `bossReturnTitleStage`) both sit in the scripted-cutscene/level-transition neighborhood and both ultimately touch "Bond's (or a puppet's) in-world model" — no code-level link found this session (D173's own M-106 pass already ruled out the two most likely struct-aliasing sites, and D243's mechanism looks like a stale/freed buffer read in a *different* subsystem, front-end/menu memory rather than a chr/puppet transform), so treat this as an unconfirmed hypothesis to keep in mind while capturing the next live trace, not an established shared root cause. **Steam Deck playtest confirmation, same M-141 session** — user independently reported "cutscene at end of dam level is badly glitched" without prompting, matching this row's own "camera violently shakes" symptom exactly. Second independent confirmation this is real and reproducible, not a one-off; still needs the interactive `GE_D243=1`+`GE_D160=1` capture this row already calls for. +--- + +## D243 — CONSOLIDATED NEXT STEPS (as of M-168, 2026-09-17) + +This entry is long and multi-pass (M-107 through M-168). This block is the single place to read for "what's actually known and what to do next" — everything else above is the reasoning trail that got here, kept for provenance, not required reading to continue the work. + +### What's established (high confidence, code-verified) + +1. **Two independent render-consumer paths exist for "where is this chr's third-person body rendered," and they can diverge.** `prop->pos` (game-logic position, written by AI scripts) is NOT what gets rendered. What gets rendered is `render_pos` (the model's animated-skeleton transform, recomputed every tick by `subcalcmatrices`/`instcalcmatrices`, `model.c:2453`) and `field_488.pos` (derived from `render_pos`, read by the cutscene camera's look-at filter, `bondview2.c:10495`). A probe or fix that only touches `prop->pos` will not affect what's on screen — this cost two live-playtest cycles to learn (M-162/M-163 → M-167). +2. **The camera shake's mechanism:** `chrTick`'s normal per-chr animation-locomotion dispatch (`chr.c:2541-2617`, the `ACT_PATROL`/`ACT_ANIM`/`ACT_STAND`/else branches) has no cutscene-mode awareness and keeps running throughout the scripted sequence. `chrUpdateAnim` (`chr.c:2398`) advances whatever animation is currently attached and reads the model's resulting root-joint position back into the render path (`getsuboffset`, `model.c:684`) every tick — a small, repeating root-motion cycle from that stale animation is what shows up as "shaking." +3. **Why the animation is stale:** the cutscene's `AI_TRYTeleportingChrToPad` (`chrai.c:4064`) correctly moves the *visible* Bond chr's position between shots, but the actual rappel animation commands (`guard_play_animation`) run on a separate, invisible driver chr (M-145) — the visible chr is never told to play the cutscene animation, so it just keeps ticking whichever animation it had equipped before the cutscene grabbed control. +4. **Camera shake: diagnostically FIXED, live-confirmed.** Freezing `field_488.pos` during POSEND (`GE_D243X3`, `bondview2.c:10495`) stops the shake completely — user-confirmed. Not shippable as-is (see below). +5. **Bond's model duplication: diagnostically addressed but incomplete.** The same freeze technique applied to `render_pos` (`GE_D243X4`, `chr.c`) is now crash-free (two real crashes fixed — see `porting-notes.md`'s two D11 updates for the postmortem: model pointers get reused by the allocator across different logical model instances, so any validity check based on a pointer's value or cached identity can be fooled) but **freezing unconditionally also suppresses the cutscene's legitimate per-shot repositioning** (each `AI_TRYTeleportingChrToPad` call is a real, intentional camera/model cut to a new shot) — so the camera now looks the wrong direction for later shots and Bond still looks stuck. +6. **Whether the root mechanism (item 2/3) is itself decomp-faithful is still OPEN.** `ai_24` (the level-exit trigger) contains no animation-set command for Bond (verified directly against `assets/obseg/setup/UsetupdamZ.c`); `bondviewFrozenMoveBond`'s own tick forces Bond's animation *identity* back to an idle/walk loop deterministically, but the *phase* within that loop is inherited from real-time-dependent prior gameplay, not reset to a fixed value — this phase is the most likely PC/N64 divergence point, not yet confirmed. + +### What to actually try next (none of these attempted yet) + +- **(a) Delta-threshold re-baseline.** Instead of freezing `field_488.pos`/`render_pos` forever once POSEND engages, re-baseline the frozen snapshot whenever the raw (pre-freeze) value jumps by more than some threshold clearly larger than the intra-shot cycling amplitude (already characterized in the M-153/M-160/M-163 captures — the cycling stays within a few hundred to ~1500 units; a real shot-change teleport should be much larger or otherwise distinguishable). Cheapest option, no new hooks needed, but the threshold is a guess that could misfire. +- **(b) Hook the real signal directly.** Re-baseline the freeze exactly when `AI_TRYTeleportingChrToPad` (`chrai.c:4064`) fires for this chr, rather than inferring it from position deltas. More precise, needs a small cross-file signal (a flag or callback) from `chrai.c` to the freeze logic in `chr.c`/`bondview2.c`. +- **(c) Find the actual root-cause divergence (item 6 above).** Log the animation's actual frame/index via the `aidx=`/`animframe=` fields already wired into `D243M: onscreen` (M-166) during a live capture, and determine whether the resulting phase is something that could plausibly also happen on N64 (in which case this is decomp-faithful and (a)/(b) above — or a bucket-(b) port-layer timing fix — are the right permanent answer) or whether it's clearly PC-specific (which would point at a different, more specific root cause still to be found). This determines whether (a)/(b) above are building toward a real fix or just a better-tuned workaround. +- **Do (c) before shipping (a) or (b) as a real fix** — per rule #2's diagnosis-before-fix discipline, and because the answer to (c) changes which of (a)/(b) is actually correct behavior to converge on. + +### Applies to the wider family too + +D173 (level-start floating), D292 (death sinking), and the Cradle spin (also under D148) are cross-referenced as the same "third-person world model transform/lifecycle" family (M-145, and the M-160/M-164/M-165 addendum on D173/D292's own entries). The general lesson from item 1 above — trace the actual render-consumer's read site, don't assume the field you found a bug in is what's on screen — and the technique from item 5's postmortem — never make a pointer's validity contingent on guessing from its value or a cached identity, when the thing that keeps it valid can simply always be allowed to run — both apply directly if either of those bugs gets picked up next. + ## D244 — Self-hosted CI runner (`corpyvt6agent01`) link failure: `undefined reference to 'objectiveGetStatus_WEAK'` (2026-09-12, first-ever build attempt on the… **Self-hosted CI runner (`corpyvt6agent01`) link failure: `undefined reference to 'objectiveGetStatus_WEAK'` (2026-09-12, first-ever build attempt on the reinstalled box, run `34707798707`).** `src/game/objective_status.c:162` uses `#pragma weak objectiveGetStatus_WEAK = get_status_of_objective` (a decomp ground-truth alias, referenced 2-arg from `chrai.c:2538` — comment calls it "horrible hack to get ai matching"). The box's `g++`/`ld` reported as `x86_64-w64-mingw32/16.1.0`; a minimal local repro of the identical `#pragma weak` cross-TU function alias **links cleanly** on this session's own machine (`mingw-w64-x86_64-gcc 16.2.0-3`, `binutils 2.47-3`). Not a game-logic bug (the alias is unmodified decomp code, ground truth) and not a CMakeLists/link-flag issue (no `--gc-sections`/LTO in play) — the leading, unverified explanation is the self-hosted box's MSYS2 packages predate a PE/COFF weak-external fix or behavior change versus current MSYS2, consistent with rollout step 2's earlier finding that the box's mingw64 package set was stale/incomplete at initial setup.