Read gfx_sp_tri1 against D233/D106/D271's existing guards. D233's
trivial-reject fix is ruled out as a direct cause -- GL clips well-formed
clip-space geometry correctly downstream of it. Found a new, previously
uncatalogued unguarded site: the G_CULL_BOTH backface-cull cross-product
divides raw vertex w with no epsilon guard, which can NaN/Inf near w=0
and silently skip both cull branches. Not yet confirmed against a live
repro. Added GE_D288 (cached-getenv, D250-safe) to catch the culprit
triangle's w/bbox at draw time on the next -level_20 run.
No game-logic change; port/fast3d/ diagnostic only, inert unless
GE_D288 is set.
Static trace confirms user's hypothesis architecturally: the Dam exit
script runs on a model-less BG driver chr, while the visible Bond is the
player's own third-person model teleported via chr_try_teleporting_to_pad
(CHR_BOND_CINEMA). Ties the abseil camera shake (M-143/M-144) and the
double-Bond image to one model-lifecycle mechanism; cross-refs D173/D292.
Closes the open question M-143 ended on: this is a genuine PC-only
divergence, not an original-game quirk. Since bondviewUpdatePlayerCollisionPositionFields's
smoothing-filter math is decomp-exact and already g_ClockTimer-scaled, the
bug must be upstream of the filter -- either the position warp itself is
wrong/larger on PC, or a call-ordering desync around the CAMERAMODE_INTRO
transition. Narrows the next step to a single probe: log raw field_488.pos
and field_3B8 (pre-filter) around the warp tick, instead of the open-ended
"needs N64 hardware" framing M-143 left off on.
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
Live Dam-abseil capture with the new lookatpad/gBondViewCutscene GE_D243CAM
probes (added this session) fires the gBondViewCutscene branch. Its pos2
read is g_CurrentPlayer->field_3C4/8/C, a leaky-integrator low-pass filter
of Bond's real position (bondviewUpdatePlayerCollisionPositionFields) that
is never reset when a scripted cutscene warps his position out from under
it -- the filter takes ~1s to catch up, and a fixed eye with a look-at
target sweeping through that catch-up is the reported shake. The filter's
own update is g_ClockTimer-scaled (frame-rate-correct by the project's
usual standard), so this looks decomp-faithful rather than a port-specific
timing bug -- open question is whether real N64 hardware shows the same
transient, which would make this not a port defect at all.
D146 stays fully decoupled (zero hits on a third consecutive live run).
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
Live Dam abseil cutscene captures (2 runs, both diagnostic probes on):
the visible camera shake reproduces every time, but the D146 stale-memory
burst fired on neither run -- the two symptoms M-107/M-109 treated as
related are actually independent. Added an env-gated GE_D243CAM probe to
bondviewFrozenCameraTick's pad-orbit CAMERAMODE_POSEND branch to check for
g_GlobalTimerDelta jitter; zero hits rules out that branch entirely. The
Dam cutscene uses one of the other two CAMERAMODE_POSEND sub-paths
(g_CameraLookAtBondPad or gBondViewCutscene), neither instrumented yet --
next session's concrete next step per docs/HANDOFF.md.
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
The M-140 fix writeup existed in two places -- an orphaned paragraph right
after the table row, and the pre-fix narrative in the tail ## D255 section
-- but neither ever updated the table row's own status cell, so
findings-index.csv (generated from the table) reported a real, fixed,
live-verified crash as still OPEN. Consolidated into one writeup in the
tail section (which findings-index.csv derives its verdict from per this
file's own ctx-opt convention) and fixed the row's status cell to match.
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
Confirmed correct level IDs (Streets=29, Egyptian=32, not the finding's
original level_20 guess). Source review clears both the unscaled-far-clip
and LOD-distance-multiplier hypotheses (bgfog.c/video.c both already scale
consistently). Narrowed to bg.c's portal/room-visibility BFS as the one
remaining suspect -- could be a real bug or just N64 behavior newly visible
on PC's sharp display; needs a live screenshot + location before touching
this decomp-verbatim system, not more static tracing.
Headless GE_PCDUMP long-sightline attempts on Streets/Statue/Egyptian did
not transfer from the D245 technique -- none of them have a scriptable path
to an open sightline without real level navigation.
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
Adds a GE_D245V env-gated probe dumping the water fan's raw S/T span each
call, and a GE_D245_FIXEDSHIFT env var (skyPortForceFanShift) that forces a
constant tc-shift k instead of the per-frame adaptive pick. Both are
diagnostic/opt-in only -- default behavior is unchanged (verify.sh
frigate/"surface 2" PASS with the env vars unset).
Headless GE_INPUTSCRIPT rotation on Frigate found k genuinely flips (kT
4->3) during ordinary view turning, which the original single-frame D227V
trace never caught -- new evidence for why the M-116 allowShift=TRUE fix
didn't hold up live. The fixed-k candidate is NOT the default; it needs a
live A/B (unset vs 3/4/5) to know if it actually looks better, not another
blind ship.
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
00 Agent campaign crashes at end of Control Center, then crashes on every
subsequent relaunch — points at corrupted persisted state (save/EEPROM),
not a one-off frame bug. Possibly-related audio degradation reported from
Streets onward, unconfirmed. Not investigated; needs the user's crash log
and save file before further work.
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
No discoverable in-UI way to quit (Deck/controller-only setups had only
window-close/Alt+F4). Adds a ROW_ACTION row kind (not config-backed, same
pattern as the existing __Resolution row) that triggers the identical
configSave()+exit(0) path SDL_QUIT already uses. Build-verified on Windows
and Linux; verify.sh dam PASS, no regression.
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
Same protocol: topics were compressions of their own §H entries (verified
by probe), statuses (the verdicts) kept verbatim. Skipped as already-thin:
D240-D243, D247 (one-liner + short status each).
§F table region: 226 KB -> 166 KB.
- D217: 7.5 KB status was a digest of the §H M-86→M-97 entries (each
'See full write-up: §H ...'); only the closing verdict was missing —
appended it (PR #47 + PR #49 merged, white residual → D228, M-105
stale-status correction).
- D271/D276: topics were compressions of their own §H entries; statuses
(the verdicts) kept verbatim.
- Skipped as already-thin/substantive: D220, D132 (compact audit summary),
D183, D179, D178, D182, D181.
The earlier edit replaced only D227's topic cell; its 11.9 KB status
survived. That status was a digest of the full write-ups, which exist:
§H '## D227' entries M-94/M-95/M-96/M-98/M-100/M-100b (in findings.md) and
M-99 (full entry in HANDOFF-ARCHIVE.md). Digest wording survives in git
history + snapshot.
Same protocol. Three sections ended before their final disposition — the
newer verdicts were appended verbatim to the section first (flagged):
- D206: M-77c user by-ear A/B = PASS (section still listed it as 'still owed')
- D207: M-77c MONITORING disposition
- D205: M-78 CLOSED (root cause was D206)
D176's defect-(b) summary (grey diagonal static, D183 0/166) has no section
home — kept as the row's substantive topic instead of a bare pointer.
Each cell checked line-by-line against its ## Dxx section(s); cells were
compressed summaries of the sections, so rows shrink to one-liner + pointer
and status keeps its verdict. Two cells carried facts NEWER than their
sections — appended verbatim to the sections first (flagged in-section):
- D221: human mouse-playtest confirmation (section still said 'eyeball owed')
- D227: M-100b human play-test confirmed (section ended 'still owed')
D228's status was the post-split FIX write-up, absent from its pre-fix
M-97 section — appended as a second ## D228 pass-section.
Exact pre-shrink wording survives in git history + notes/snapshot-pre-ctx-opt/.
Semantic coverage check of each §F row labeled D70–D121 against the §H
narrative (done directly, not delegated):
- D85, D86·D87, D88.4, D121: thin rows, fully covered by §H entries —
topic reworded as one-liner + 'see §H' pointer, status kept verbatim
- D117, D118: fat cells whose content (M-52 addenda; M-24 mouse-look
rework incl. D118b/c) is fully in §H — shrunk to one-liner + pointer
- D120: fat cell with NO section and no §H coverage (sole record of the
M-30 PointUsage fix) — pure verbatim move to a new ## D120 tail section,
row becomes a pointer; verified byte-identical vs pre-migration snapshot
- D75: LEFT AS-IS — its status cell holds a v0.2.0-pre playtest result not
present in §H; shrinking would lose data
- D119·D120: left as-is — sole record of D119 (§H mentions it only in passing)
Range rows (D70–D74, D89–D92, …) stay as-is per plan.
D76·D164, D165·D166, D180·D181 each point at existing ## Dxx sections that
fully cover the cell content (verified line-by-line; cells were compressed
summaries of the sections). Status cells kept verbatim. The remaining two
multi-label rows (D86·D87, D119·D120) are thin §H-era summary rows with no
sections — deferred to the D70–D121 §H cross-check.
Each label had a full writeup + a short 'Duplicate row' triage pass.
Keep the full writeup; append the short pass's genuinely-unique content
verbatim under a dated merge note (D233/D234: GRAPHICS-BACKLOG.md logging +
triage hypotheses; D152+: fully subsumed — noted as such). Both §F table
rows stay (cell 1 frozen) and both now point at the single merged section.
Nothing deleted: earlier text in git history + notes/snapshot-pre-ctx-opt/.
Cell 1 of the §F table is frozen, so four rows (D69·D78–D84, RC3·D167,
the D88.x range row, D118d) are invisible to findings-index.csv — LABEL_RE
can't parse their label format. Documented in the file header alongside
the lookup convention and a record of what the 2026-09-15 migration did
(including the one non-verbatim step: D250/D252/D255 derived statuses).
These three rows were fat topic with NO status cell (2-column rows). Topic
moved verbatim to new ## Dxx sections; table rows get a substantive one-liner
+ a status DERIVED from each entry's own verdict text (D250 FIXED / D252 OPEN
known-issue / D255 OPEN pending rule-#2 sign-off) — the derivation is flagged
in-section for auditability. CSV regenerated, label set unchanged (171); all
three topics verbatim-checked against the snapshot.
Pure moves following the 6208276d formatting repair: topic/status cells moved
verbatim to new ## Dxx sections; table rows now carry one-line summary +
pointer. CSV regenerated, label set unchanged (171); both sections
verbatim-checked against the snapshot.
Formatting fixes only — text preserved verbatim, no content added/removed:
- D211: the HUD ammo display '7 | 93' contained a literal unescaped pipe,
splitting its status cell in two; wrapped that token in backticks.
- D219: a call-chain used pipes as separators between separate code spans
(5 cells instead of 3); merged into one continuous code span — same text,
backtick positions moved.
- D255: row lacked the trailing pipe its neighbours have; added it so the
table is well-formed 3-column (status cell empty, as in D250/D252).
These were pre-existing CSV corruption (garbled/truncated status_raw), not
migration artifacts. CSV regenerated; label set unchanged (171).
The previous commit's (a)/(b) insertion landed at the line start, producing
' (a)## D233 — …' lines that no longer parse as headings. Moved the markers to
after the label: '## D233 (a) — …'. 117/117 migrated sections now have valid
headings; CSV check + label set still green.
Two table rows each carry the same label+topic with a status progression
(earlier vs updated pass). Both migrated verbatim in batch 6; heading markers
are formatting only. Flagged for Bucket B consolidation.
12 rows: topic/status cells moved verbatim to new '## Dxx' sections at file
tail; table rows now carry a one-line summary + pointer. CSV regenerated, label
set unchanged (171). Nothing deleted.
20 rows: topic/status cells moved verbatim to new '## Dxx' sections at file
tail; table rows now carry a one-line summary + pointer. CSV regenerated, label
set unchanged (171). Nothing deleted.
21 rows: topic/status cells moved verbatim to new '## Dxx' sections at file
tail; table rows now carry a one-line summary + pointer. CSV regenerated, label
set unchanged (171). Nothing deleted.
10 rows: topic/status cells moved verbatim to new '## Dxx' sections at file
tail; table rows now carry a one-line summary + pointer. CSV regenerated, label
set unchanged (171). Nothing deleted.
Also disambiguates the two '## D152+' headings from batch 2 as (a)/(b)
(formatting only; content untouched — flagged for Bucket B consolidation).
14 rows: topic/status cells moved verbatim to new '## Dxx' sections at file
tail; table rows now carry a one-line summary + pointer. CSV regenerated, label
set unchanged (171). Nothing deleted.
9 rows: topic/status cells moved verbatim to new '## Dxx' sections at file
tail; table rows now carry a one-line summary + pointer. CSV regenerated, label
set unchanged (171). Nothing deleted.
15 rows: topic/status cells moved verbatim to new '## Dxx' sections at file
tail; table rows now carry a one-line summary + pointer. CSV regenerated, label
set unchanged (171). Nothing deleted.
16 rows: topic/status cells moved verbatim to new '## Dxx' sections under a
provenance divider at file tail; table rows now carry a one-line summary +
pointer. CSV regenerated, label set unchanged (171). Nothing deleted.
Companion to the local split of docs/HANDOFF.md (gitignored rolling
working file — that half of the move is working-tree-only, backed up in
the pre-edit snapshot). Appends lines 137-5764 of the pre-split HANDOFF
(### ARCHIVE — M-140 onward, ~444 KB) after the existing M-2/M-3 brief,
with a divider documenting coverage and the newest-first ordering of
the moved block. Content verbatim; CRLF->LF normalised to match this
file and .gitattributes (eol=lf). Verified: live head + moved chunk
rejoin byte-identical to the original (CR-stripped); appended region
cmp-clean against the snapshot.
Archive intro corrected: it claimed 'through session M-11' but held only
M-2/M-3; the M-4-M-11 gap is pre-existing, noted as such.
AGENTS.md findings bullet + HANDOFF-ARCHIVE.md intro pointer now steer
cold-starting agents to docs/dev/findings-index.csv (grep this, then read
the specific ## Dxx entry) instead of linear-reading the ~100k-token §F
table. Base: release/v0.3.0 @ bf24bef8.
User clarified the black-geometry drop-out happened at normal ground
level during normal progression near the statue, not from the
elevated ramp with wide sightlines as the first write-up assumed.
Retracted the "widened FOV frustum from elevation" framing of the
room-visibility hypothesis; kept the underlying room-transition/
streaming-timing guess since it doesn't depend on elevation.
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
User report at Video.FovScale~130 + max draw distance/LOD: ground
geometry near an elevated wide-visibility vantage point drops out to
solid black, then recovers 10-30s later on its own. Doesn't cleanly
match D222 (NPC/object culling, FIXED), D249 (default-FOV far
geometry, doesn't self-recover), or D218 (fog/tint boundary, not
missing geometry) -- logged as its own finding with those three as
neighbors, not a confirmed duplicate of any. Leading unconfirmed
guess: a portal/room-visibility BFS not accounting for the widened
FOV frustum.
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
User corrections on the playtest batch: D289 (Dam computer-room
crash) is most likely the same computer-terminal SIGSEGV D255 already
fixed this session, not a separate mystery -- needs a re-test on the
post-fix build before treating as new. D292 (Bond sunk into floor on
death) is confirmed the same "Bond's world model positioned wrong"
family as D173/D148, not a separate bug -- cross-referenced both
directions so the split-across-two-files writeup (GRAPHICS-BACKLOG.md
D148 + findings.md D173) is easier to find as one thread covering
level-start floating, cutscene spin, and now death-pose sinking.
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
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
User reported a stray triangle during the Silo missile-shaft camera
pan (previously D171, parked since M-34 as "observed, not
investigated"). Reproduced headlessly, precisely bounded to frames
~562-741 of a bare -level_20 boot: a large, flat-shaded, untextured
brown triangle (uniform RGB(83,62,44)) grows then shrinks across the
pan, consistent with a real world-space triangle mis-transformed near
the camera plane rather than a HUD element or garbage vertex-color
read. Points at the D106/D233 near-plane/w-clip neighborhood in
port/fast3d/gfx_pc.cpp, not yet confirmed at the code level. Evidence
saved to docs/img/bugs/. Also noted: foreground `timeout N ...` launches
of the exe from this session's Bash tool produce zero GE_PCDUMP output;
backgrounding the launch works reliably -- worth remembering next time.
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
User reproduced the "stuck standoff" 3-4 times; traced to a hard
timeout in ai_22 (label 0x13, UsetuparkZ.c) that fires the execution
unconditionally after ~2.5 min if no other trigger fires first. User
waited it out and confirmed it resolves normally with dialogue -- not
a freeze, not a new bug. Documented so it isn't re-flagged.
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
D287: user hit a live freeze under debug.ps1 after ~15-20 min idle,
refocus-triggered, non-self-recovering (distinct from D286's single
fire-then-recover shape). Evidence preserved at
scratch/d287_freeze_gdb.txt (untracked, matches this repo's existing
scratch/ convention). Leading lead: gfxFrameMsgQ (32-slot scheduler
client queue) full while the VI-retrace pacemaker keeps ticking,
pointing at the main/render thread's consumer stalling rather than
the scheduler. Not root-caused; GE_D60 diagnostic already exists and
is the recommended next probe.
D252: user confirmed live the rainbow-hue bug still reproduces on
plain bullet-impact sparks, not just explosions -- a much cheaper
repro for the next GE_D252 capture.
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
sndHandleEvent's pool-exhaustion preempt-scan read/walked the shared
D_800243E4 active-ALSoundState list with no osSetIntMask lock, while
sndSetupSound (the list's insert path, reached from sndPlaySfx calls
across 15 game files) already locks it -- an asymmetric-locking gap,
the same D4/D147/D152 "N64 interrupts-off isn't a real lock on PC"
class. Wraps the scan in the identical lock/unlock pair sndSetupSound
already uses, unconditionally (no #ifdef PORT, matching that function's
own style -- osSetIntMask is genuine N64 API and the port's shim is a
process-wide recursive lock, safe to nest). Audited the rest of the
function: the scan is the only D_800243E4 touch point in it.
Build green; -level_09/-level_20 unregressed (1400+ frames, no crash,
no D152 lock-stuck warnings). Not yet gdb/live-confirmed against a
real reproduced crash -- a GE_INPUTSCRIPT-driven stress attempt this
session didn't visibly activate the scripted-input harness for a
reason not root-caused; the honest bar met here is "correct by D4/D152
precedent, build/sweep-clean," not "reproduced-then-fixed."
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
Cherry-picks the GE_D243 diagnostic (764a1f26, previously stuck on an
unmerged, un-build-verified branch) onto release/v0.3.0. The prior
session's "toolchain blocker" was this session's own TMP/TEMP-vs-Bash-tool
gotcha (documented in porting-notes.md this session), not a real
CMake/ninja issue -- now explained and worked around. Also fixes an
uncached getenv("GE_D243") per menu_init() call (D250's own rule: this
runs ~once/frame during front-end states) to a single cached check.
Build-verified; smoke-tested via a bare -level_33 boot (no crash, no
D243 lines, as expected since bare -level_XX skips the front end).
Still needs a real interactive playthrough to actually correlate
against the D146 stale-DL burst -- documented as the next step in
findings.md D243 (M-141), including an unconfirmed D173-connection
hypothesis raised by the user.
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
Menus read the same stick_x/stick_y channel as in-level gameplay, and
naturalPitchMode (SOLITARE-style, default-on) routes that channel to
the right stick for continuous look. Since front.c's menu nav shares
the channel, it silently inherited the right-stick routing, which is
unintuitive on a modern pad/Deck outside a running stage. The gamepad
poll already computed an unused padMenuMode flag for shoulder-button
gating; reused it to always source stick_x/stick_y from the left
stick outside RUN_STAGE, regardless of naturalPitchMode. Port-layer
only, inert during gameplay -- verified via -level_09/-level_20
unregressed; in-menu feel needs a human controller playtest.
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi