Commit Graph
192 Commits
Author SHA1 Message Date
James Dansereau 7412a9818d sky.c: D245 headless probe + opt-in fixed-shift A/B candidate
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
2026-09-15 23:06:47 -04:00
James Dansereau e108d6bf2e findings: log D295, campaign-halting Control Center crash (GitHub #87)
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
2026-09-15 22:52:11 -04:00
James Dansereau a3637c40e1 optionsoverlay: add D293 quit-to-desktop row to the F10 overlay
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
2026-09-15 22:49:42 -04:00
James Dansereau 44ea596411 findings: validation fixes — D252 derived status now reflects M-141 (both hypotheses refuted, root cause open); normalize D221/D227 append flags to 'appended 2026-09-15 ctx-opt' 2026-09-15 22:24:48 -04:00
James Dansereau 093e428639 findings.md: has-section dedup, rows 20-27 (D280 D275 D274 D273 D272 D284 D285 D286)
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.
2026-09-15 21:51:22 -04:00
James Dansereau e9bb415488 findings.md: has-section dedup, rows 16-19 (D217 D271 D276 + skips)
- 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.
2026-09-15 21:50:34 -04:00
James Dansereau 2a84927e3f findings.md: finish D227 shrink (status cell was missed in the batch-2 pass)
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.
2026-09-15 21:49:31 -04:00
James Dansereau e85175639c findings.md: has-section dedup, rows 11-15 (D206 D207 D205 D176 D177)
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.
2026-09-15 21:48:28 -04:00
James Dansereau 45427eaf0e findings.md: has-section dedup, rows 1-10 (D159 D161 D186 D200 D221 D227 D229 D228 D204 D202)
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/.
2026-09-15 21:45:46 -04:00
James Dansereau 00a877cc53 findings.md: D70–D121 §H cross-check — shrink covered rows to pointers, migrate D120
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.
2026-09-15 21:41:50 -04:00
James Dansereau 14eb1f8330 findings.md: shrink the 3 multi-label rows with per-sublabel sections to pointers
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.
2026-09-15 21:40:23 -04:00
James Dansereau e2b8ec3432 findings.md: consolidate the (a)/(b) duplicate-label sections (D152+, D233, D234)
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/.
2026-09-15 21:39:38 -04:00
James Dansereau 8687846bee findings.md: header documents the ctx-opt migration + CSV lookup path + 4 known index gaps
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).
2026-09-15 21:39:33 -04:00
James Dansereau 9c9d4a0997 docs(findings): migrate D250/D252/D255 to tail sections (ctx-opt P1, manual)
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.
2026-09-15 21:30:27 -04:00
James Dansereau 9a8f1c9162 docs(findings): migrate D211/D219 to tail sections (ctx-opt P1, structurally-odd follow-up)
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.
2026-09-15 21:29:31 -04:00
James Dansereau 6208276d2b docs(findings): repair pre-existing table formatting in D211/D219/D255 (NOT a content edit)
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).
2026-09-15 21:29:20 -04:00
James Dansereau dd2e95e229 docs(findings): repair D233/D234 disambiguation markers (were inserted before '##')
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.
2026-09-15 21:24:21 -04:00
James Dansereau 1726221030 docs(findings): disambiguate duplicate D233/D234 section headings (a)/(b)
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.
2026-09-15 21:23:47 -04:00
James Dansereau 60b1e4ff61 docs(findings): migrate fat §F rows D271-D294 to tail sections (ctx-opt P1, batch 8/8)
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.
2026-09-15 21:23:27 -04:00
James Dansereau 63b4d79ce6 docs(findings): migrate fat §F rows D244-D270 to tail sections (ctx-opt P1, batch 7/8)
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.
2026-09-15 21:23:27 -04:00
James Dansereau ce4ec5a387 docs(findings): migrate fat §F rows D218-D243 to tail sections (ctx-opt P1, batch 6/8)
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.
2026-09-15 21:23:26 -04:00
James Dansereau 5446cc295b docs(findings): migrate fat §F rows D201-D216 to tail sections (ctx-opt P1, batch 5/8)
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.
2026-09-15 21:23:20 -04:00
James Dansereau 4ea303e25e docs(findings): migrate fat §F rows D184-D199 to tail sections (ctx-opt P1, batch 4/8)
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.
2026-09-15 21:23:19 -04:00
James Dansereau 2d50dd2319 docs(findings): migrate fat §F rows D169-D181 to tail sections (ctx-opt P1, batch 3/8)
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.
2026-09-15 21:22:48 -04:00
James Dansereau 415abb4438 docs(findings): migrate fat §F rows D152-D168 to tail sections (ctx-opt P1, batch 2/8)
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.
2026-09-15 21:22:47 -04:00
James Dansereau cd6f0b5e83 docs(findings): migrate fat §F rows D123-D151 to tail sections (ctx-opt P1, batch 1/8)
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.
2026-09-15 21:22:39 -04:00
James Dansereau bf24bef8a9 docs(D294): correct trigger condition - ground level, not elevated vantage
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
2026-09-15 20:38:43 -04:00
James Dansereau a001f1c137 docs(D294): new finding - Statue ground briefly renders black near the elevated CCCP-monument ramp, self-recovers
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
2026-09-15 20:37:28 -04:00
James Dansereau b6ed6b1d36 docs: fold D289 into D255 (likely same terminal crash), consolidate D173/D292 as one Bond-positioning thread
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
2026-09-15 20:35:52 -04:00
James Dansereau 4c2643c239 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
2026-09-15 20:33:07 -04:00
James Dansereau d475884456 docs(D288): promote D171 (Silo stray triangle) with user-reproduced, visually-documented repro
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
2026-09-15 20:27:54 -04:00
James Dansereau a2b028737b docs: fix Ourumov spelling in D263 quoted report title
Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
2026-09-15 20:19:28 -04:00
James Dansereau 791bf6b7c0 docs(D263): confirm Ourumov/Trevelyan standoff timeout fallback is by design, not a bug
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
2026-09-15 20:17:17 -04:00
James Dansereau 86fdcc1db6 docs(D287): new finding - real non-recovering freeze after long idle + refocus; D252 confirmed on bullet-impact sparks too
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
2026-09-15 20:00:43 -04:00
James Dansereau 07b25f4b45 fix(D285): lock sndHandleEvent's preempt-scan against sndSetupSound's writer
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
2026-09-15 19:35:22 -04:00
James Dansereau 556f4434f3 D243: port GE_D243 front-end MENU_* trace onto release/v0.3.0, build-verify, fix uncached getenv
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
2026-09-15 19:30:58 -04:00
James Dansereau bb1e658ea5 fix(D282): front-end menu nav always uses left stick, matching F10-overlay convention
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
2026-09-15 19:28:03 -04:00
James Dansereau 4b1d9bc21c docs(D252): refute width/height suspect via authored asset data; add GE_D252 diag probe
The prior "prime suspect" (import_texture_rgba16 width/height derivation)
assumed line_size_bytes comes from the tile's MASK_16 wrap period, but
assets/oddtextures.c's actual gsDPSetTile for the fire render tile sets
line=4 explicitly, giving the correct 16x14 dimensions -- matching the
pre-existing is_fire_shape==16x14 filter already hardcoded into the
GE_TEXDUMP dumper. Neither named D252 hypothesis (width/height, GEVR's
byte-group-order alternative) survives; root cause stays open. Adds an
env-gated GE_D252 diagnostic (dims + raw texel dump) for whoever next
gets an interactive session to trigger the actual fire draw.

Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
2026-09-15 19:25:15 -04:00
James Dansereau 65728e9f0e docs(D286): new finding - user-confirmed real freeze on Caverns, Steam Deck
Caught during the same live Deck session used to fix D255: the game's
own hang watchdog (portHeartbeatCheck) fired once, no frame rendered
for ~8s at frames=46404, dumped all-thread state, then rendering
resumed and the session continued to a clean close - not a crash, a
self-recovered stall. User confirmed by direct observation this was a
real, visible freeze (not a Deck-side system hiccup), happening early
in a Caverns playthrough, not tied to an explosion or terminal (rules
out D255/D285 as the cause).

Not root-caused - no distinguishing log line precedes it. Logged as
open with no repro trigger identified rather than guessed at further.

Also fixes a cosmetic units bug spotted while reading the watchdog
code: the heartbeat log printed a raw microsecond delta with a "ms"
label (port/src/libultra.c, no /1000 conversion) - now divided
correctly. No behavior change, log output only.
2026-09-15 18:55:26 -04:00
James Dansereau 7ef0395a3f fix(D255): correct 32-bit pointer truncation in vtxstore_fix_refs
Root-caused via a live Steam Deck session over SSH: sub_GAME_7F09BAC4
(vtxstore_fix_refs) read an active OBJ record's Model* through
((Model*)((ChrRecord*)prop->chr)->chrflags) - a union pun onto
ChrRecord.chrflags, which is typed CHRFLAG, a 4-byte enum. On 32-bit
N64 this is bit-exact (the enum and a pointer are both 4 bytes, so
casting the enum's value to Model* reproduces the real stored bytes).
On 64-bit PC, Model* is 8 bytes but the enum read is still only 4 -
the cast reads just the pointer's low 32 bits as an int and
zero-extends it back to a 64-bit address, discarding the real high
bits. This is the D3x pointer-width ABI class from AGENTS.md - a
struct field misread across the 32-to-64-bit pointer-width transition.

This was actually flagged as suspect (a) in the very first D255
investigation, then wrongly deprioritized in favor of a "destroy-path
race" theory once a register capture happened to show Rdi=(nil) and
looked like it confirmed a different, unrelated NULL-model theory. It
didn't: (nil) is exactly what a 32-bit-truncated read produces
whenever the real pointer's low 32 bits happen to be zero - a
coincidence that pointed two sessions' worth of investigation at the
wrong field. Proof: reading the same record through the correctly
64-bit-typed ObjectRecord.model field (this session's diagnostic
probe) was always a valid, non-NULL pointer - the object's model was
never actually missing.

Fix: read the pointer through ObjectRecord.model (the same union
memory, correctly typed) instead of through the mistyped
ChrRecord.chrflags alias, at both use sites in the function. Identical
bytes and identical result on N64; correct on 64-bit PC.

Verified live on the user's own Steam Deck hardware over SSH, running
their own prepared ROM assets: held through the entire Facility level
including repeated terminal destruction (the most consistent prior
repro) and through Caverns' radio-objective terminals (the second
confirmed level), zero crashes. Windows rebuild + full 21-level
verify.sh sweep also clean.

The separate audio-thread crash (D285, caught during this same live
session) is unrelated and unaffected by this fix.
2026-09-15 18:42:08 -04:00
James Dansereau 49f18ad1e6 docs(D285): new finding - audio-thread SIGSEGV, separate from D255
Caught a real crash on the user's Steam Deck during D255 live-test
setup: a fresh ge007.crash.log from the published v0.2.1 binary,
inside the user's active testing window. Symbolized directly on-device
with addr2line against the crashing binary's own debug symbols.

FAULT ADDR 0x62 inside sndHandleEvent (src/snd.c:366), on the audio
thread (amMain/portThreadWrapper) - not D255's 0x10/vtxstore_fix_refs
on the render thread. Different bug, different subsystem, likely
conflated with D255 in reports since both surface during the same
explosion/combat testing.

Root cause (static, code-read only): sndHandleEvent's pool-exhaustion
preempt-scan walks the shared D_800243E4 ALSoundState list with no
osSetIntMask lock anywhere in the function, while the list's insert
path (sndSetupSound, reached from 15 different game files' direct
sndPlaySfx calls) does lock it - an asymmetric-locking gap matching
the established D4/D147/D152 "N64 interrupts-off isn't a real lock on
PC" race class. Fits the repro pattern (explosion SFX bursts = high
thread-contention window).

Not fixed - flagged two open questions before it should be: whether
the missing lock is a genuine decomp-fidelity gap (N64 assembly may
have had it) vs a legitimate new PC-only lock, and it needs gdb
confirmation on a live crash, not just this static read.
2026-09-15 18:12:30 -04:00
James Dansereau edcfb1f163 docs(D255): consolidate real-Deck evidence - destroy-via-explosion, not activation
Further Steam Deck testing on the published v0.2.1 release reconciles
the two competing D255 repro theories: the trigger is an object dying
via explosion, not terminal activation. Confirmed on four levels:

- Facility: every terminal crashes consistently when destroyed
- Caverns: terminals near the radio objective crash when they explode
- Archives: a similar crash when a guard's grenade detonates (no
  terminal involved at all - narrows the real trigger to "object dies
  via explosion damage" in general)
- Frigate: negative control - shooting a room of terminals there did
  not reproduce it, so it isn't simply "any destructible OBJ record
  exploding" either

Updated the D255 finding with a static lead for the next session (the
objFree destroy path never explicitly nulls ObjectRecord.model, so the
NULL likely comes from a freed-slot-reuse or list-modify-during-iterate
race rather than a direct field write - untested, needs a live Deck
capture with the GE_D255 probes already in tree). Updated
.github/release-notes.md's D255 bullet to match.
2026-09-15 17:58:25 -04:00
James Dansereau b322b55a70 docs(D255): log user's real-hardware Deck report (destroy-trigger, not just activation)
User reproduced on real Steam Deck hardware, v0.2.1 published release:
Facility crashes consistently when terminals are DESTROYED (shot), not
only on activation as the M-139 "corrected" framing concluded. Frigate
crashed once in the same pass, specifics not recorded.

Logging as a dated, evidence-stamped addition rather than overwriting
the existing activation-repro entry - both triggers are plausible
routes into the same underlying destroy-path-ordering race and are not
necessarily contradictory; neither has been cross-verified against the
other yet. Next Linux/Deck session should test both triggers with
GE_D255=1 before revising the "reliable repro" framing again.

Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
2026-09-15 17:35:51 -04:00
James Dansereau 126fe75d7f diag(D255): env-gated NULL-model probe for the Facility terminal crash
Adds a live-repro diagnostic for D255 (Facility door-opening terminal
SIGSEGV, Linux/Steam-Deck-only): vtxstore.c's vtxstore_fix_refs
(sub_GAME_7F09BAC4) skips-and-logs instead of dereferencing when it
finds a NULL model on an active PROP_TYPE_OBJ record, logging the
record's identity/state; propobj.c's objFree logs every free call so
the two logs can be correlated by prop/obj pointer. Both gated behind
GE_D255 (off by default, getenv-cached per the D250 lesson) - zero
behavior change unless set.

Attempted a live Windows repro this session (5 terminal activations
across 3 direct -level_34 launches): zero probe hits, zero crashes.
Combined with the existing WSL-negative result, this is now two
negatives against one Linux/Steam-Deck positive - the bug does not
reproduce on Windows, likely a genuine allocator/timing difference.
Landing the probes anyway since they're inert by default and are the
fastest path to a real trace whenever a Linux/Deck session is
available; no more Windows time should go toward a live repro here.

Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
2026-09-15 17:12:35 -04:00
James Dansereau 0de99b4db4 fix(prng): correct MIPS64 dsll32/dsrl32 semantics in port/src/random.c (D284)
random.c's dsll32/dsrl32 helpers modelled the real MIPS64 dsll32/dsrl32
instructions as ordinary 32-bit shift-by-n operations, masked to 32
bits. Real MIPS64 dsll32/dsrl32 shift the FULL 64-bit register by
n+32 with no masking - they exist specifically to encode a 32-63 shift
amount in a 5-bit immediate field. The bug was bit-exact-looking but
wrong: it produced a deterministic, self-consistent stream that was
never the N64's actual stream, from the very first draw.

Surfaced by M-138's security/decomp-fidelity review (a forked review
agent's numeric trace + a hand trace of random.s both found the same
divergence); fix decided over disclose-as-known-issue per M-139b.

Fix: dsll32(x,n) = x << (n+32), dsrl32(x,n) = x >> (n+32), no masking.
No other line changes - every call site (randomGetNext,
randomGetNextFrom, chrObjRandomGetNext) already mirrors random.s/
chrObjRandom.s line-by-line through these two helpers plus plain <</>>
(which already matched dsll/dsrl correctly).

Verified against a standalone C reproduction outside the game (first
five draws from random.s's real initial seed diverge completely
between the buggy and corrected helpers, confirming the fix actually
changes the output stream). 242/242 clean build; full 21-level
verify.sh sweep PASS, no crashes.

This silently changes every RNG-derived sequence (loot, AI variance,
anything seeded from randomGetNext/randomGetNextFrom) from the first
draw in every build going forward. Savegames are unaffected (seed
isn't save state); existing recorded replays will diverge against a
post-fix build. Called out in v0.3.0 release notes per M-139c.

Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
2026-09-15 16:40:19 -04:00
James Dansereau 532092c732 fix(render): land D266 IA4/texedge decode + D271 portal-culling fixes (M-139 R2)
R2 extraction from the parked D236 investigation tree
(park/d236-probes-m139): pulls out only the two verified, behavior-real
fixes and drops every D267-D280 debug probe (census counters, PPM
dumpers, per-tri/per-portal logging) that had accumulated alongside them.

- D266: import_texture_ia4 decodes RDP IA4 as I2:A2 (was I3:A1, an
  inherited PD assumption); gfx_opengl.cpp's CVG_X_ALPHA "texedge" path
  now keys off the real alphacompare field (dither/threshold/none)
  instead of a hardcoded 0.19 cutoff, and gfx_pc.cpp excludes AC_DECAL
  tiles from the texedge path entirely (it never discards on real
  hardware).
- D271: the D106 near-plane portal-straddling guard's `lim` constant
  widens from 1e5 to 1e38 so finite out-of-range bounds flow through to
  bgRectIntersect and clamp/cull exactly as they do on N64, instead of
  being force-mapped to full-screen (was drawing whole rooms N64 culls).

Checked D271 against D249 (distant-geometry drop-out on Streets/Egyptian)
before landing per M-139's overlap concern: no overlap, different code
paths (near-plane portal guard vs. far-plane/LOD symptom); D249 stays
open.

Verified: 242/242 clean build; full 21-level verify.sh sweep clean (one
dam NO-FRAMES on the sweep pass reproduced PASS on an immediate solo
re-run - a load-sensitivity flake, not a regression).

The D267-D280 probe code and the rest of the D236 tree-wall
investigation stay parked on park/d236-probes-m139 for whenever that
thread resumes (still no root cause for the tree wall itself - see
D280's negative outcome).

Also documents a build-environment gotcha hit this session: this Bash
tool's shell reports MINGW64 (uname/$MSYSTEM) but is actually
Git-for-Windows Bash, missing cmake/ninja/gcc from PATH until
/c/msys64/mingw64/bin and /c/msys64/usr/bin are prepended explicitly
(docs/porting-notes.md SS E).

Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
2026-09-15 16:34:28 -04:00
James Dansereau e7fee0d7a4 docs: v0.2.1 known-issues sweep, pre-1.0/roadmap framing, AI disclosure
Batched pass across README/release-notes/Pages (one commit instead of the
scattered per-fix pushes earlier this session, per new commit-cadence
preference):

- New known issues surfaced that weren't documented anywhere: no real
  widescreen (16:9 stretches 4:3 rather than expanding FOV), no controller
  rebinding UI / macOS-ARM builds, culling/LOD dropout on Streets/Egyptian
  (D249), gunshot SFX oddities under sustained fire (D240/D241).
- Fixed a stale claim (in three places) that a wrong-sounding-instrument bug
  was still open; it was fixed in v0.2.0 (D230).
- Softened the D259 "All unlocked" fix to reflect reality: still breaks
  audio/aim if enabled before any save exists; flagged experimental
  (new finding D281).
- Corrected D255's repro to the precise, deterministic trigger found this
  session (Facility's door-opening computer terminals, Linux-wide not
  Deck-specific) instead of the vaguer original combat-crash description.
- Logged D282 (front-end menus need right stick not left on gamepad) and
  D283 (Steam Deck first-run preset skipped by an early Desktop Mode
  launch) with user-facing workarounds.
- Refined D263 (Ourumov/Trevelyan) internally with a position-sensitivity
  caveat; no public bullet since it doesn't block progression.
- Added pre-1.0 framing and a new README Roadmap section (known-issues
  cleanup, PAL/JP, real widescreen, rebinding, macOS/ARM, LAN multiplayer
  explicitly "under consideration, no ETA", plus an explicit "not currently
  planned" list) across README/release-notes/Pages.
- Added an AI-development disclosure (agentic development as a genuine
  study of the process, not just a port) across the same three docs.
- Pinned the release-notes hero GIF to the v0.2.1 tag instead of v0.2.0.

Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
2026-09-15 12:53:57 -04:00
James Dansereau 1e11e1a5fd v0.2.1 polish: pull Game.ScreenShakeIntensity from the F10 menu (D181)
User testing found the slider "basically useless" -- viShake() only scales
explosion/effect shake (explosion.c), never the always-on walking head-bob
or a getting-shot reaction, which is what "Screen shake" implies to a
player. Removed the menu row until it covers all screen-shake/view-bob
sources, not just explosions. Config var + fr.c hook untouched.

Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
2026-09-15 09:28:06 -04:00
James Dansereau 99f4f38e1c v0.2.1 polish: pull Game.SkipIntro from the F10 menu (breaks audio)
User spot-check of the v0.2.1 draft found that enabling Skip intro breaks
audio. It was already off by default (D216); removing the menu row so
players can't enable it until the audio interaction is root-caused. The
config var and lv.c hook are untouched (dead unless an existing ini already
has it set).

Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
2026-09-15 09:18:40 -04:00
James Dansereau 572ab7bf5b docs: findings/backlog through D280 (v0.2.1 batch + D236 tree-wall passes 2-9)
Records the v0.2.1 hotfix batch (D259-D264, this commit's code) and the
ongoing D236 Surface-1 tree-wall investigation (D265-D280): still open as of
D280 -- fog math verified N64-faithful, the noise class confirmed as the
weave's painter, the portal-culling over-draw fix (D271) real but
insufficient, no quick win identified. The D266-D280 probe code and the
D266 IA4 decode / portal-culling fixes themselves stay uncommitted pending
the game-wide regression sweep GRAPHICS-BACKLOG.md flags.

Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
2026-09-15 08:21:56 -04:00