Commit Graph
146 Commits
Author SHA1 Message Date
James Dansereau 7af146a1d0 D329: fix 60 fps guard anim pin (D318/D320 root cause) - tick-granularity divergence
The D318/D320 guard freeze is not a platform-invariant lock. modelTickAnim
folds numticks into one modelSetAnimFrame2WithChrStuff call, which only
advances framea/frameb when floor(frame) crosses an integer. At 1 tick/frame
(port at 60 fps) and attack speed 0.5-0.8, a pair left equal by the aim-hold
endframe clamp is re-emitted stale after the endframe is raised, so the
model pins forever. At >=2 ticks/frame (N64's normal regime) it cannot.

- model.c (#ifdef PORT, Rule-2 sign-off): when the loop did not run and the
  current endframe no longer blocks the next frame, re-derive frameb as
  modelSetAnimFrame does. GE_D329=0 disables, GE_D329LOG=1 logs triggers.
- Verified: lockstep clk=1 no longer pins (Facility ai_19, Control ai_9,
  WSL + native Windows, watchdog off); 0 triggers at clk=2; user playtest
  confirms N64-matching Ourumov timing. Bunker1 golden failure predates it
  (identical with the fix off).
- d318bprobe (TEMP, env-gated): attack-state probe + GE_D318B_CLK lockstep.
- d320repro: fix jump=<chr>,... parser (documented form never parsed).
- findings D329 + D318/D320/D321 corrections, porting-notes D13.
2026-09-23 09:53:10 -04:00
James Dansereau 5910350e8e D328: mod support is viable — GE's file table is in the ROM; D327's 'ruled out' corrected
Reviewed the PD PC port fork at perfect-dark-dabs-mod-dabs-mod (MIT, same
license as this tree; AGENTS.md already names the PD port a standing reference)
and verified its approach against our own ROM.

Architectural flip: PD does not load a modded ROM. modimport.c states our exact
constraint ('the port cannot run the patched ROM - it runs decomp code and reads
the ROM at fixed offsets') and instead diffs the patched ROM against stock,
extracting what differs into a mod directory its loader already reads. Mod
support is an offline importer plus a file-override layer, not a loader rewrite.

geconvert.c parses a GoldenEye US ROM directly and gives the constants:
DATA_ROM 0x21990, DATA_VRAM 0x80020d90, FILES_AT 0x252c4, 12-byte records with
size by differencing adjacent addresses - exactly obInit.

Verified here: 1172 magic at 0x21990, raw-inflate yields a 247120-byte data
segment (the same inflate port/src/rzdecomp.c already wraps), table at 0x252c4
with 727 rows, and 681 exact offset+size matches against filelist.u.csv with 0
mismatches.

Then reimplemented discovery with NO hardcoded offsets (scan for 1172 in the
first 4 MB, inflate, scan for a >=500-row run counting from 0) and validated on
two real mods by content:
- Random Eye: its own table found at seg 0x219f0 / table 0x25304; 704/726 files
  byte-identical to vanilla at the extracted offsets, vs 0/803 at vanilla
  offsets in D326. The 22 remainder are fully accounted for: 2 real changes
  (UsetupcaveZ, UsetuptraZ), 1 NULLFILE differencing artifact, 19 zero-size
  unused bg slots.
- Goldfinger 64 (24 MB): table read correctly, 19/726 identical as expected of a
  total conversion.

D327's scans were run over uncompressed ROM bytes, so its mechanism guess was
right and its conclusion wrong; amended here (section heading, inference
paragraph, status line and index row). Also noted: PD's rompatch.c is an MIT
VCDIFF/BPS/IPS decoder, which removes the GPLv2 xdelta3 constraint D325 flagged.

Remaining work is the mod-directory override layer (we have no analogue of PD's
romfile.moddir); rule-2 limit for added levels unchanged. No code changed.
2026-09-22 23:15:50 -04:00
James Dansereau bbc32957e1 D325-D327: ROM-mod (xdelta) compatibility investigation + settled plan
Docs-only. Records the mod-compatibility investigation so it is not re-derived
when mod support is picked up. No code changed.

Measured two shipped community mods against a vanilla NTSC-U ROM:
- Goldfinger 64 v1.0 (12.8 MB xdelta): ROM 12 -> 24 MB, 797/803 vanilla
  filelist.u.csv slices differ.
- Random Eye v1.1 (286 KB xdelta): ROM size byte-identical, yet 96.5% of bytes
  and 803/803 slices differ; content preserved but shifted (+7216/+7392, one
  file -229888).
Both die at tools_pc/d43_emit.py:273 with a zlib error on a slice taken at a
hardcoded vanilla offset. Cause: the file table is compiled in (ob.c ->
file_resource_table.inc.c -> romassets_<region>.s) and the converters reuse the
same vanilla offsets. ROM size is not the constraint; ROM code is never
executed, so ASM hacks are inert regardless.

D325 - Goldfinger 64 probe + mod taxonomy.
D326 - Random Eye repacks too, so D325's hypothesised in-place tier is empty.
D327 - plan settled: reading the table from the ROM is ruled out by measurement
  (hw_address absent in all four encodings tested, 0 hits for the
  segment-relative form; two structural fileentry scans found nothing);
  content-addressed offset remapping with gap inference is the viable route,
  since obInit differencing proves files are contiguous.

Also recorded: GE maps its 0x7F000000 game segment directly from ROM,
uncompressed, uniquely signature-findable at ROM 0x34b30.

The 1964GEPD fork reviewed in D327 is GPL-2.0 against this MIT tree; no code
was copied or vendored, facts re-derived from our own ROM and decomp.
2026-09-22 22:51:21 -04:00
James Dansereau 97c7e38845 D320: resolve sweep via forced repro — ai_19/ai_9 pins confirmed, ai_12 clean; watchdog generalized (table-driven + uncapped repeat rescue); D318 N64-manifestation corrected (unverified; 1964 deliberate-timing A/B armed)
Also commits the prior session's carried-uncommitted set: D294 room-pool
fix (sqrt + 2x cap, user-verified state), D324 stdlib-shim prototypes,
D323 explicit-LOD census + tools_pc tooling, GRAPHICS-BACKLOG row.
texdump*/ probe artifacts gitignored.
2026-09-22 22:30:32 -04:00
James Dansereau eaa137d6a9 D306/D308: investigate z-fighting — depth precision == N64, real fighting confirmed; GE_ZF flicker probe in-tree 2026-09-21 23:53:39 -04:00
James Dansereau 2efbbef964 D303: investigate extra long muzzle flash — pipeline + data ruled out; GE_D303 capture probe in-tree 2026-09-21 23:41:05 -04:00
James Dansereau 61766e8aef D307: investigate CCTV facing-backwards — no port-side bug found; GE_D307 diagnostic in-tree
setupCctv orientation path traced end-to-end and every input verified
N64-faithful against the ROM for Bunker1: PcctvZ (not GcameraZ) switch
data bit-for-bit, boundpads match the pccg sidecar once the legitimate
load-time room-scale (pad->pos *= get_room_data_float2(), ~1.8542) is
applied, record config words incl. unkCC match; all matrix/atan2 helpers
are byte-identical src/game code. PC orientation values are what the N64
computes; if the symptom genuinely differs from N64 the remaining suspect
is fast3d's handling of the non-orthonormal lens matrix (unk84 row 0
scaled 0.1x). Blocked on user repro detail.

Also: plan/README/findings refreshed for M-197 (D75 fixed, user-confirmed)
and M-198 (D236 closed); D252 retired; findings index regenerated.
2026-09-21 23:26:18 -04:00
James Dansereau 425237f93f D236 (M-198): fix Surface 1/2 tree cards; root cause was a DETAIL tile binding
GE's texLoadFromGdl picks a handler per G_NOOP texture reference from w0 & 7.
TEXTURETYPE_LOD/MIPMAP (texHandleType0/Type2) issue ONE load block at TMEM 0,
so tiles 0..n are LOD levels of that one image. TEXTURETYPE_DETAIL
(texHandleType1, src/game/tex.c) issues TWO: the detail texture at TMEM 0 and
the real base image at TMEM texGetSizeInBytes(detail,0) on tiles 1+.

gfx_lod_tile_offset folded every G_TL_LOD draw to tile 0 (D107 -- correct for
the first two, wrong for DETAIL), so the port drew the detail texture and never
imported the base image at all. All four Surface 1 treeline cards
(tex1198-tex1201, RGBA5551 64x17 cut-outs) are DETAIL bindings sharing one
detail texture, tex2465: an opaque 32x32 IA8 noise tile. That is the same
texture D267/D276 measured at 1023/1024 texels opaque and called the mystery
"noise painter" -- hence 20+ passes chasing decode, alpha, mip and geometry
theories for what was a binding bug.

Fix (port layer only): under G_TL_LOD, if tile fi+1 differs from tile fi in
BOTH TMEM address and fmt/siz, sample tile 1. Asymmetric on purpose -- LOD
levels of one image always share a format, so a mip chain can never trip it,
and a detail pair that happened to share a format just keeps the old
behaviour. Config Video.DetailBaseTile, default on.

Verified: Surface 1 idle spawn goes from an opaque tiled noise wall to discrete
snow-covered pines with ground/snow unchanged; Surface 2 shows a discrete night
treeline; Depot renders clean; bunker1 golden framediff is bit-identical with
the fix on and off. Offline scan of every level's bg: DETAIL bindings exist
only in sevx (84), depo (39), sevb (4), sev (4), so every other level is
provably untouched. User play-test confirmed.

Also refuted and removed pass 25's fix direction: cut-out mip suppression
changed nothing, and its alpha census showed the wall is painted by a fully
opaque texture -- retiring the mip / 1-bit-alpha / XLU-smear branch. The
GE_D236P25 probe is gone with it.

tools_pc/gen_env_probes.py: stop counting a "GE_FOO[" log-line TAG in the doc
as a documented env var. GE_TEXDUMP_ADDR prints "GE_TEXA[...]", which is not
the name of any getenv(), so the check reported a permanent phantom GONE.

Generalised as docs/porting-notes.md D12; full trail in findings.md D236 pass
26. Carries the pending D75/M-197 sidecar-converter fix and the env-gated
D236/D75 probes, which the doc gates require to land alongside their rows.
2026-09-21 22:48:10 -04:00
James Dansereau 7bb43260de D322: log issue #87 long-session audio degradation + ship GE_D322 pool-telemetry probe
User report (v0.3.0 full campaign): audio progressively degrades from Silo;
by Caverns/Cradle the OST is inaudible and SFX come and go; restarting the
game restores it. Logged as D322 with static triage: level teardown audit
clean, mixer stateless per frame, D202's covered leak class is one shape
only (ownerless count==-1 loops) validated single-level only, and D305
already records a pool/list accounting desync. Ranked hypotheses:
voice-pool exhaustion/counter drift > queue starvation > evtq saturation.

Probe: GE_D322=1 prints sfx=n/8 (SFX soft cap), evtq=n/64, pv free/lame/alloc
(24 physical synth voices) + pipeline depth/drops every 5 s of wall clock,
read with GE_D204. sndD322PoolSummary() walks the ALSynth lists under the
D285 lock; NB drvr is a pointer field (first build faulted on &drvr).
Smoke-tested 40 s headless -level_09: pools coherent, no crash.

Also adds the missing D321 row to the findings index (entry existed at file
tail with no table row, so it was absent from the CSV too).
2026-09-20 23:10:57 -04:00
James Dansereau b3b0ff74db v0.3.0 release review: watchdog re-arm, rule-2 sign-offs, GPL source removal, repo hygiene
Per the 2026-09-19 adversarial pre-ship review (findings.md M-192):

- d318watchdog.c: one-shot per OCCURRENCE, not per process -- re-arm after
  600 ticks of signature absence so a Facility replay in the same session
  (AllUnlocked) is still covered.
- model.c: RULE-2-SIGNOFF records for the retained M-183/M-185 and D311
  defensive clamps (user-approved retention as the stability-maximizing
  option; stale 'Threshold: 50.0' comment corrected to 10.0f; both marked
  post-release removal candidates pending full cutscene re-verification).
- reference/mouse-injector/: removed from the public repo (GPLv2 source +
  prebuilt discord-rpc.lib in an MIT project; never compiled here).
  Provenance stub README remains; path gitignored for local re-vendoring.
  AGENTS.md / GEPD-INPUT-PLAN.md / optionsoverlay.c refs updated.
- scratch/: untracked + gitignored (19 session logs/probe sources were
  committed in the release window).
- findings-index.csv: D317 (and 5 other stale rows) now CLOSED -- added
  CLOSED to gen_findings_index.py's verdict vocabulary.
- docs: D310 perf gauge (no change warranted), GE_DEBUG_UNLOCKALL noted in
  security-and-fidelity-status.md, release notes gain the missing D318
  watchdog bullet + FOV-clamp clarification.

Build verified: ./build-pc.sh ntsc-final clean; gen_findings_index.py
--check and gen_env_probes.py --check pass.
2026-09-20 10:51:12 -04:00
James Dansereau cc169874ce D318: port-side watchdog + timeline probe for the Facility ai_22 softlock; D320: D318-class sweep of all level AI scripts; gfx_pc GE_D116 printf format fix
- D318 (RESOLVED): as-authored latent race — gas bit mid-monologue derails
  c78's pre-aim hold into a permanent ACT_ATTACK anim pin
  (actor_fire_or_aim_at_target_update moves only the endframe, never
  re-inits; N64-identical). NOT a port bug. Port-side watchdog
  port/src/d318watchdog.c (hooked in lv.c after g_GlobalTimer += ) detects
  the pinned state and re-inits the attack on confirm; GE_D318T=1 timeline
  diagnostic included. random.c gains an env-gated probe (GE_RANDPROBE) for
  bit-exact PRNG verification. Scratch evidence (dis/seqcmp scripts, addr
  dumps, session handoff) parked per house practice.
- D320 (OPEN): swept every level's AI scripts for the same aim-hold ->
  _update shape: 17 lists in 8 levels. Facility ai_19 and Control ai_9 are
  HIGH (non-self-healing, same as ai_22); Depot ai_12 MED-HIGH (objective
  bit gated behind a stop-check). Bond-combat loops likely safe (sustained
  fire stays inside the shoot window by design; exercised every playtest).
  Classified as-authored latent race(s), faithfully reproduced. Probe-verify
  path + generalize-the-watchdog recommendation documented in findings.md.
- gfx_pc.cpp: GE_D116 debug printf used %f for uint32_t args and %d for
  unsigned tile fields; casts added, build warning-free.

Full ntsc-final configure+compile+link PASS. Accuracy sweep write-up in
docs/dev/ACCURACY-AUDIT-2026-09-20.md (PRNG bit-exact; A1/shift32/wrapv/
PORT-hygiene/Gfx-Vtx/osVirtualToPhysical sweeps all clean or already fixed).
2026-09-19 00:38:14 -04:00
James Dansereau 276d0124dc D312/D313: fix bgBuildRoomVtxBounds G_VTX vertex-count misread on PC (Gfx_le dma.par bit shift) — n=16 batches collapsed to single-point bounds, failing the per-batch AABB pretest in bgTestBulletHitBackground; projectiles passed through those walls and bullets showed no impacts. One-line #ifdef PORT fix (recover params byte from words.w0 bits 16-23); N64 path untouched. User-repro verified (Runaway: mines collide, bullet impacts land on wall plane). 2026-09-18 18:12:14 -04:00
James Dansereau 340d6a595f D316/D317: fix F10 overlay mouse click mapping + row hit-band; D319: FpsCap 30/60 toggle
D316: optionsOverlayHandleInput() mapped clicks with a naive window-size
scale that ignored the safe-area crop, and overlayRowAtY()'s hit-band was
asymmetric (biased downward) -- together these could land a click on the
row below the one clicked. Fixed by adding gfx_get_ui_screen_rect() (reuses
the real forward transform from gfx_adjust_viewport_or_scissor) to invert
clicks correctly, and centering the row hit-band.

D317 (reported as a crash toggling Game.AllUnlocked): confirmed instant
close, not a hang -- closed as a symptom of D316 (the hit-band bias let a
click on "All unlocked" fall through to the adjacent "Quit to desktop" row).

D314 (F10 flicker on main menu): mechanism still unpinned; landed an
env-gated diagnostic probe (GE_D314=1) for a future repro run. Confirmed
still present after the D316 fix (different bug class) -- deprioritized
per user call.

D315 (menu scaling main-menu vs in-game): disclosed as a known issue,
deferred to v0.3.1.

D319: Video.FpsCap's free 0-360 slider was mostly a dead zone (D186: sim
tick ceiling caps it at 60/50, and values under 30 already got force-
uncapped) -- replaced with a plain 30/60 cycle (ROW_FPSCAP, mirrors the
existing ROW_MSAA pattern); default changed to 60 so a fresh config shows
a reachable value. Uncapped stays valid at the config level for old inis
but is no longer offered in the menu.
2026-09-18 16:47:48 -04:00
James Dansereau c67479866c D310 v2: scope PRINT sizing fix to global AI lists; D311: close D156 guard hole
D310 (RULE-2-SIGNOFF, user-confirmed fixed): the uniform 1-byte AI_PRINT
size broke level-local lists, which embed a NUL-terminated string after
the PRINT byte (Caverns Trevelyan ran circles with glitched anims).
chraiitemsize is back to byte-identical decomp; new PORT-only
d310ItemSize() applies the 1-byte size only for lists confirmed global
via chraiGetAIListID, used at ai()'s AI_PRINT step and chraiGoToLabel's
label scan. Local-list behavior unchanged from the original.

D311 (new, guarded): live freeze in Caverns diagnosed via GDB — Bond's
own anim tick spun ~3.1e9 iterations in modelSetAnimFrame2WithChrStuff:
animframe1 poisoned to -1.67e31 upstream, and the D156 guard validated
only frameb, so 'frameb = framea' passed both garbage values through and
floorFloatToInt saturated them to far-apart ints. Extend the guard to
validate both framea/frameb (snap bad one to the valid other, else 0)
and heal model->animframe1/2 before the fallback re-reads them. Same
file/pattern as the accepted D156 guards, #ifdef PORT only. Upstream
poison source (oldspeed=-1.75e18 at transition start; timespeed=0,
unkb0=0) recorded open in the D311 entry.

Post-fix Caverns playtest: guards dash-pause-resume, Trevelyan pathing
normal, Caverns->intro clean. Probe log: global PRINTs size=1/global=1
(hit once each), local PRINTs NUL-scanned/valid landings, zero
LABEL-NOT-FOUND, zero D156 fires, zero watchdog freezes.
2026-09-18 13:18:16 -04:00
James Dansereau 0c700852e2 D310 (RULE-2-SIGNOFF): size AI_PRINT records at their true 1-byte width in chraiitemsize (#ifdef PORT) — fixes D309 spin + Caverns standing-still guards 2026-09-18 12:55:06 -04:00
James Dansereau ede16415f2 docs: D309 diagnosed — Caverns->intro complete freeze, phantom GotoNext(lblZero) via AI_PRINT NUL-scan mis-size 2026-09-18 12:25:33 -04:00
James Dansereau 444b172cb8 docs: v0.3.0 release prep — D251 closed, security page published, notes cleanup
- release-notes: drop the internal 'carried forward' meta paragraph; move the
  D251 bottom-row duplication out of Known Issues into the F10 fix bullet
  (no longer occurring per user live playtest of the D304 build)
- findings: close D251's last symptom; mark D304 live F10 playtest done;
  de-stale D266/D271 'uncommitted' cells (landed in 532092c7); regen index CSV
- README: link the security & fidelity status page
- docs/security-and-fidelity-status.md: publish — re-verified for v0.3.0 on
  2026-09-18 (bundle contents, no-networking source check, asset converter);
  D271 now landed; D284 RNG history reworded as found-and-fixed pre-v0.3.0

Verified: build-pc.sh ntsc-final clean (243/243); tools_pc/verify.sh sweep
21/21 PASS on this tree (scratch/verify_sweep_v030_final.json).
2026-09-18 10:54:00 -04:00
James Dansereau 416396e504 D302/D304/D225/D164/D252/D305: perf regression, F10 menu overhaul, small fixes pass
- D302: fix a reintroduced D250-class perf regression -- 4 uncached getenv()
  calls added by D236 diagnostic passes 10/12/14/16 were running on every
  triangle draw in gfx_sp_tri1; cached with the established pattern.
- D304: F10 mouse-sensitivity menu overhaul -- widened Input.AimModeSens'
  clamp to match Input.MouseTurnSpeed, then collapsed both plus the
  aim/turn link toggle into a single Input.MouseSensitivity control per
  user request (avoids decoupling into a confusing state). Fixed two real
  overlay bugs found along the way: overlayUpdateScroll() bottom-anchored
  the clicked row on every click (not just when off-screen), and
  overlayRowAtY()'s hit-test scanned rows past the last one actually drawn,
  letting a click near a scroll boundary silently hit the wrong row
  (reproduced as "All unlocked" triggering "Quit to desktop" instead).
- D225: GE_DEBUG_UNLOCKALL=1 dev-tooling toggle (file2.c), default off.
- D164/D76: docs-only correction -- the legal-screen one-line fix was
  already committed 8dfc578f4; findings.md just never caught up.
- D252: root-caused and fixed a 6+-session-old rainbow-particle bug --
  explosionRenderPart() wrote 5 vertices into a 4-vertex allocation,
  corrupting the next particle/effect's data in the shared vertex pool.
  User-confirmed live: "Rainbow went away."
- D305: Steam Deck SIGSEGV recurrence past D285's own fix -- sndHandleEvent's
  preempt-scan dereferenced its list-walk pointer before checking it for
  NULL. Added the missing guard. User-verified crash-free on the Deck
  across multiple sessions, including toggling Game.AllUnlocked off/on.

D236 pass-16 diagnostic (GE_D236RAW, bg.c) carried along from a prior
session's in-progress investigation; unchanged, env-gated, zero-cost unset.
2026-09-18 09:56:23 -04:00
James Dansereau e7c76cf8eb D236 pass 13: live capture demotes draw-order theory, redirects to tree-card fog
User played live with GE_D236ORDER=1, walked toward Surface1's tree line;
wall bug still reproduced. Tree-card count reached 121 tris (vs 2-4 in
deterministic boot), and draw order flips favorably at high counts (tree
drawn last, as pass 12's theory needed) -- but the bug persisted anyway in
that same zone, demoting draw-order as the dominant cause.

Redirects to a lead D280 flagged but never closed: the tree class's own
fog computation (bimodal 25/255 per vertex, never verified against N64,
unlike the noise class's fog which D280 already confirmed correct). Not
attempted this pass -- next session's starting point.

Not v0.3.0-blocking, parked again per standing user priority.
2026-09-18 02:36:49 -04:00
James Dansereau db823d0261 D243 M-189/M-190: fix Bond cutscene root cause, camera tracking, mouse-lock; D236 pass 12 diagnostics
D243/D173/D292 (Bond cutscene family) -- root cause found and fixed:
bondview2.c's cutscene body-model buffer layout reserved space using a
hardcoded 32-bit sizeof(Model) literal that never accounted for 64-bit
pointer widening (real size 232 vs reserved ~188 bytes), so the model's
own animation-data buffer overlapped its tail fields (playspeed/animrate/
unkac/unkb0/unkb4), corrupting them on every cutscene body-model creation.
Fixed by computing the real sizeof(Model), matching the identical idiom
already used one line above for ModelFileHeader. User-confirmed live.

Follow-up camera-tracking fix: replaced the old shake-workaround freeze
(which also blocked legitimate per-shot tracking) with a write-every-tick
scheme plus an instant re-seed of the look-at filter's accumulator at each
teleport, eliminating the old catch-up shake without freezing anything.

Second bug found in the same review: mouse-look was never gated during
scripted/no-control camera states (an already-documented but never
implemented safety gate in port/src/input.c's hipDirectCompute). Fixed by
adding the missing check, reusing gameScriptedCameraActive() (renamed from
D243's d243mCutsceneActive, since it's no longer D243-specific -- also
used by model.c's M-183/M-185 clamps, which were separately found to be
dead code for real players (gated on the diagnostic env var, not just
camera mode) and fixed to be unconditionally active.

D236 (Surface tree wall) pass 12: new mechanism found (both competing
texture classes are Z_UPD=0 decal-mode draws, so on-screen precedence is
decided by draw order, not depth) and a headless capture finally landed on
the actual bugged view -- but that view has zero tree-class triangles
present, so the mechanism couldn't be tested there. Two new env-gated
diagnostic probes (GE_D236ORDER, GE_D236ZFIX) left in tree for the next
pass; not a fix, not v0.3.0-blocking.

All findings.md summary-table rows touched this session cross-checked
against their full entries and corrected where stale (D219/D252/D246/D247
had been fixed in a prior session but their index rows still said
OPEN/NOT-A-BUG).
2026-09-18 02:30:17 -04:00
James Dansereau 814ecf5fce D243 M-183/M-185: playspeed/endframe clamp workarounds for Bond animation issue
Added two #ifdef PORT workarounds in modelTickAnim() to address D243's
Bond position/animation issue during scripted camera modes:

M-183: Clamp playspeed > 10.0 to 1.0 (prevents rapid advancement ~97 frames/tick)
M-185: Clamp corrupted endframe values (< 0.0 or > 1000.0) to 100.0

Both are env-gated on GE_D243M and apply only during scripted camera modes.
The clamp fixes prevent the rapid animation advancement but Bond's animation
still 'repeats' — he can't complete his animation during the Dam abseil cutscene.

Root cause analysis: playspeed/endframe corruption is consistent with the
D100/D140 struct layout/punning family (32→64-bit pointer widening causes
fields to be read from wrong memory locations). Playspeed observed at 388.82
(expected ~1.0), endframe as huge negative values like -604462909807314587353088.0.

Updated findings.md §F table entry for D243 to reflect partial fix status.
2026-09-17 23:10:59 -04:00
James Dansereau 4ccab22ce2 D243: ship permanent fix for Dam abseil camera shake (M-170)
Convert the GE_D243X3 experiment into an always-on fix by removing
the env-gating. The fix freezes field_488.pos (the camera's look-at
target derived from the model's animated-skeleton transform) during
POSEND camera mode, re-baselining only when a legitimate shot-change
teleport fires (detected via the d243TeleportEpoch counter incremented
by d243NotifyTeleport() in chrai.c's AI_TRYTeleportingChrToPad handler).

This eliminates the camera shake while allowing legitimate inter-shot
repositioning. The fix is decomp-faithful - it targets the camera's
reaction to the animation cycle rather than suppressing Bond's own
animation, which was found to be playing correctly.

Also includes:
- GE_DEBUGMENU diagnostic in boss.c (prior session)
- GE_D243SWIRL probe in bondview2.c (prior session)
- M-178 finding documentation (rapid animation advancement analysis)
- D243 biganim probe in model.c (for future investigation)
2026-09-17 22:38:52 -04:00
James Dansereau ee4ea96258 docs(D300): correct stale status -- user already live-confirmed hipfire fix
findings.md and the index still said "pending manual playtest" / "pending
build-verify", but HANDOFF.md records the user played it live this session
and confirmed better feel. Regenerated the index via gen_findings_index.py.
2026-09-17 19:30:38 -04:00
James Dansereau 2fe0e1c83e fix(D301): NULL-guard fileGetIsCheatUnlocked() against unvalidated saves (GitHub #87)
Closes the unguarded save deref at its single choke point instead of
patching each call site (front.c mode-select, file.c briefing check).
Build-verified, crash-free smoke test; live confirmation against the
exact #87 repro still owed (see finding for detail).
2026-09-17 19:30:05 -04:00
James Dansereau 53219c18ae D243 M-167: camera shake mechanism confirmed live -- first positive result
M-163's live playtest smoothed prop->pos completely (log-verified) but
the visible camera shake was unchanged -- the camera's look-at filter
doesn't read prop->pos at all. It reads field_488.pos, a separate
variable computed from render_pos (the model's own animated-skeleton
transform), independent of prop->pos even though both happened to show
the same period-4 cycling symptom through a probe that only logged one
of them.

Retargeted the suppression at field_488.pos's actual write site
(bondview2.c:10495-10497, GE_D243X3-gated, freezes it while POSEND is
active). User's live playtest: "yep camera shake is gone now" -- the
first positive result across the whole D243 investigation.

Not a real fix -- GE_D243X3 unconditionally freezes state rather than
correcting why the model's skeleton animation cycles in the first
place. User also reports Bond's own rendered body still visibly
duplicates/glitches, expected since this only freezes the camera-feed
copy, not the render_pos draw-path read -- likely the same underlying
mechanism observed through a different consumer, not yet isolated.

porting-notes.md D11 gets the generalized lesson: confirm the actual
consumer reads the field you fixed before spending a live-test cycle
on it -- two fields can show the same symptom from one shared root
cause while being genuinely independent data paths.
2026-09-17 13:06:57 -04:00
James Dansereau d9c7705654 D243 M-162..M-166: corrected decisive experiment + deep root-cause tracing
Two third-party reviews caught a real flaw in the earlier M-161 negative
experiment: it restored prop->pos AFTER chrTick() returned, but
render_pos/field_488.pos are computed INSIDE that same call -- so M-161's
"no change" result never actually tested M-160's overwrite hypothesis.
Verified this directly against the capture data (a clean period-4
same-frame before/after-chrTick offset proves the lane-advance happens
entirely inside chrTick) before accepting the review.

M-162: corrected save/restore placement (chr.c), Z-only. Live playtest
confirmed the mechanism -- Z went smooth in the log -- but the visible
glitch persisted because that capture's chr cycled on Y instead.

M-163: widened the experiment to the whole prop->pos vector. Built and
smoke-tested; not yet live-playtested (exe was busy/user stepped away) --
this is the ready-to-test build for the next session.

M-164/M-165/M-166 (static tracing, no build needed): chrUpdateAnim reads
the model's root-joint position back into prop->pos after advancing
whatever animation is attached (getsuboffset) -- not an arbitrary
overwrite. The cutscene's teleport (AI_TRYTeleportingChrToPad) re-anchors
the root joint but never touches animation state; the real rappel
animation commands target a separate invisible driver chr (M-145), not
the visible one. Traced the Dam exit script (UsetupdamZ.c) directly and
found no animation-set command for Bond anywhere in it -- his head
animation gets forced back to an idle/walk loop by
bondviewFrozenMoveBond's own tick, but the *phase* within that loop is
inherited, not reset. This raises a real possibility that the mechanism
is decomp-faithful and the actual PC/N64 divergence is upstream of the
cutscene code entirely (which animation phase Bond happens to be on when
the cutscene grabs him). Extended D243M: onscreen with aidx=/animframe=
(reusing GE_D193A's reverse-resolve technique) so the next live capture
can test this directly.

Docs updated throughout (findings.md, findings-index.csv,
GE-ENV-PROBES.md); HANDOFF.md refreshed with full session state (local,
gitignored).
2026-09-17 12:49:19 -04:00
James Dansereau fec396f53b D300: hipfire direct camera look (WI-1); D243 M-159/160/161 investigation
D300: new hipDirectCompute() in port/src/input.c writes vv_theta/vv_verta
directly for hipfire mouse-look, mirroring GEPD's linear px->degree model
and the existing D194 aim-mode pattern -- replaces the N64 stick-curve
routing that saturated at ~13 px/poll (issue #89). Ported GEPD's
dead/watch/pause safety gates. Input.MouseDirectLook=1 default, escape
hatch to the legacy path. User-confirmed better feel in a live playtest.

D243: added a permanent onscreen-visibility probe (D243M: onscreen,
chr.c) to test whether chrTick's posIsOnScreen gate explains the
abseil cutscene's frozen render position (M-159). A live capture
refuted that and found the real mechanism instead: chrTick overwrites
prop->pos with a stale value via its own chrUpdateAnim/modelTickAnim
animation-position logic, racing the AI script's scripted write
(M-160). A decisive throwaway experiment neutralizing that overwrite
produced no visible change in a live playtest (M-161) -- the mechanism
is real but isn't the dominant visible cause. Experiment reverted;
D243 parked pending a new lead after ~15 session-passes without a fix.

Docs: findings.md/findings-index.csv/porting-notes.md/GE-ENV-PROBES.md
updated throughout.
2026-09-17 10:56:41 -04:00
James Dansereau 7b405abc63 fix(D299): Game.SkipIntro crash selecting most save slots
fileValidateSaves() -- the only place saves[] is populated from EEPROM
-- is called exclusively from the legal screen's init, which
Game.SkipIntro's boot-to-file-select shortcut skips entirely. saves[]
was left all-zero, so fileGetSaveForFoldernum() returned NULL for
every folder except whichever one a zeroed save_data happened to
decode to, crashing file-select's unguarded fileGetIsCheatUnlocked()
for the rest. Call fileValidateSaves() from the shortcut too
(port-layer completeness fix, zero game-logic change).

Live-verified by user: all 4 save slots now selectable with
SkipIntro=1, intro still correctly skipped.
2026-09-17 09:43:44 -04:00
James Dansereau f70284b5be D219/D252 M-158: fix §F table status cells (index CSV is generator-derived — update the source row, not the CSV) 2026-09-17 08:50:33 -04:00
James Dansereau 30dcbea085 D219/D252 M-158: rainbow bug not reproducible in 6 identical dev-box runs; classified flaky/environment-dependent; probes left live for next occurrence 2026-09-16 23:15:45 -04:00
James Dansereau 23da815dda D236 pass 10: refute "nondeterministic instantiation", it's room occlusion
Per the rule-2-clarification pass-10 brief in findings.md, applied the new
three-bucket taxonomy to D280's leading theory that the 0x0c184b50
discrete-tree-card render-mode class instantiates inconsistently across
runs of the deterministic -level_36 boot (~4 tris vs ~720).

Added a small env-gated diagnostic (GE_D236RM, port/fast3d/gfx_pc.cpp
gfx_sp_tri1) counting that class's per-frame triangle count, heartbeat-
logged every 300 frames even at zero. Three independent -level_36
launches (one with sustained GE_INPUTSCRIPT camera rotation) produced
byte-identical per-frame counts, falsifying the "run-to-run dependent"
premise outright. Cross-referencing with the existing GE_D104 room-census
probe in the same run shows the count drops to zero at the exact frame
the player's curRoom transitions from 12 (17-20 rooms drawn, an outdoor
vista) to 18 (1-5 rooms, an enclosed indoor space) via the boot's own
scripted intro camera — ordinary portal/room occlusion, not a bug. None
of buckets (a)/(b)/(c) apply; no src/game or port/ fix made or warranted.

The original "wall not discrete sprites" symptom is untouched by this
pass — narrowed the real next step to getting a camera into room 12
facing the tree line directly, in docs/dev/findings.md.

Added the generalisable "deterministic boot flag != deterministic camera
path" quirk to porting-notes.md §E. findings-index.csv regenerated.
2026-09-16 17:58:01 -04:00
James Dansereau 2a1f1b2472 findings: D243 M-154 crash-fix note + D298 corrected (full HUD, suspected debug-launch artifact)
- D243: M-154 fix paragraph — probe-side AV at the abseil CREATE
  (render_pos = 0xFF tombstone until instcalcmatrices runs during the
  draw pass); guarded; transition frame now logged.
- D298: user corrected the symptom — the FULL HUD is missing, not just
  the aim-mode crosshair; suspected debug-launch artifact; per explicit
  instruction, do not raise until seen in a regular release-targeted
  build. Entry re-scoped and parked accordingly.
2026-09-16 16:57:40 -04:00
James Dansereau 190141ff53 docs: D230 status was stale OPEN despite M-121 fix in the same entry
The §F summary table row and the entry's own inline Status line still
said "OPEN -- static-analysis pass done (M-108)" even though the same
paragraph documents the RAW16-wavetable byte-swap fix landed at M-121
(v0.2.0). Left a future session likely to re-investigate an already-fixed
bug. Corrected both to FIXED, noting the still-owed by-ear pass.
2026-09-16 16:17:53 -04:00
James Dansereau 5071185f8c findings: D295 M-148/M-149 + D297 filed/closed (M-150); porting-notes D8/D9
- D295: full M-148 root-cause/repro record (difficultytext overflow ->
  high-bit byte -> langGetJpnCharPixels NULL deref; OOB theory retracted;
  pre-D284 save fingerprint identified), verdict FIXED (M-149).
- D297: new entry — v0.3.0 wipes all pre-fix saves (D284 side effect);
  scope verified (5 slots only, block-0 is a factory seal), FIXED (M-150).
- D284: correct the false 'savegames are unaffected' claim.
- porting-notes: D8 instance (difficultytext[4]); new D9 — changing a
  PRNG/CRC implementation invalidates persisted derived state; sweep
  rewrites data/ge007.eep gotcha in E.
2026-09-16 15:28:44 -04:00
James Dansereau 2c19689c62 findings: D243 M-144 — user confirms real N64 does NOT show the abseil shake
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
2026-09-16 00:17:08 -04:00
James Dansereau 91ba14d2e6 findings: D243 M-143 — name the shake mechanism (unreset position-smoothing filter)
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
2026-09-16 00:13:30 -04:00
James Dansereau 24fb22ca80 D243: live playthrough decouples shake from D146; add GE_D243CAM probe
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
2026-09-15 23:44:07 -04:00
James Dansereau 8c28453517 findings: fix D255's stale OPEN status (was FIXED+verified, never updated)
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
2026-09-15 23:25:23 -04:00
James Dansereau e978e526be findings: D249 static review rules out two suspects, narrows to portal BFS
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
2026-09-15 23:20:44 -04:00
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 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 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