Commit Graph
973 Commits
Author SHA1 Message Date
James Dansereau c728c42e81 golden: move bunker1 window to 640-1120:240 (frame 400 sat on the nondeterministic intro camera; 3 captures agree) 2026-09-23 13:32:20 -04:00
James Dansereau 971c2d9f62 D281: AllUnlocked on a fresh install now yields the game's own fresh-slot layout
D259 seeded only the volumes on all-zero slots. Because the AllUnlocked read
patch re-CRCs every slot, a fresh eep survived validation as five folder-1
slots with options 0: sight-on-screen off (no crosshair while aiming = the
"RMB aim broken" report) plus auto-aim/look-ahead/ammo off, and the game
reset/rebuilt the duplicates mid-validate. All-zero slots now become what
fileValidateSaves + fileBuildWriteNewSave would build (folder saves from
BLANKSAVEDATA via the game's own setters, leftovers free).

Verified: fresh eep + AllUnlocked + SAVELOG -> 00/21/42/63/80, all CRC OK,
no repair writes. Golden 3/3 (x2 reruns); Windows + Linux builds clean.
2026-09-23 13:27:28 -04:00
James Dansereau 27a560cd0b plan: reconcile v0.4.0 bucket states with the findings index (D75/D195/D197/D235/D288/D290/D294, getenv sweep) 2026-09-23 13:19:19 -04:00
James Dansereau d9d7cd3586 perf: cache getenv in hot-path probes (post-D302 sweep)
Swept all 178 getenv sites; 97 were uncached. The load-time/one-shot ones
stay as-is. The 25 on per-frame/per-node/per-string/audio-thread paths
(langGet, modelGetNodeRwData, modelRenderNodeGundl, skyRender and its poly
path, chrpropRender, frozen-camera tick, amDmaCallback, alRaw16Pull,
_decodeChunk, __CSPHandleMIDIMsg, sndHandleEvent, audioSetNextBuffer,
configGetInputLog/configGetFrameDump) now go through GE_ENVFLAG/GE_ENVSTR
(port/include/envflag.h): one getenv per site per process, exact since the
env is fixed. All sites are inside PORT probe blocks; no game logic touched.

Windows + Linux builds clean (only pre-existing, D324-audited warnings);
bunker1 golden 3/3; GE_INPUTLOG still gates on (12 lines with scripted input).
2026-09-23 13:18:32 -04:00
James Dansereau 94dff8116f D331: shell casings now render (D264 pattern, third site)
The casing draw read g_DefaultCasingModelRenderData (u32[15] {0,1,3,..})
as the wider PC ModelRenderData: flags landed on a 0, so subdraw() drew
nothing and the read overran the array. Use the explicit template.

Verified: PP7 casing ejects and tumbles on native Windows (none before);
bunker1 golden 3/3; Windows + Linux builds clean.
2026-09-23 13:13:14 -04:00
James Dansereau a5804d10b8 D290: fix missing controller model on the watch Control Options page (D264 pattern, second site)
watchRenderController built its ModelRenderData from a raw 64-byte read at
D_80035D04+0x3c, which on N64 is the {0,1,3,...} template spanning into
watchControllerButtonBases. On PC the struct is wider and the globals aren't
at those offsets, so flags read 0 and subdraw() drew nothing. Use the
explicit template, as D264 did for the item preview. Also fix the three
pointer-width/raw-Gfx sites on the same path that now execute: two u32
render_pos casts (fatal under Linux PIE), a (s32) pointer arg, and the
dual-controller branch's hand-encoded G_MTX word (now gSPMatrix).

Verified on native Windows (headless watch walk + capture); bunker1 golden
3/3; Windows and Linux builds clean. D291: headless check noted (needs play).
2026-09-23 13:07:31 -04:00
James Dansereau 30cf76b812 D288: re-check on current build — does not reproduce (dense Silo-intro capture + colour scan); D271 A/B ruled out as the fixer 2026-09-23 12:58:21 -04:00
James Dansereau 2a6bc2bb4a D235: headless reload verification; TEMP GE_D235_RELOAD / GE_D235_OFF harness
Scripted watch Abort on -level_09 -> title stage -> GE_D235_RELOAD re-enters
the level via bossSetLoadedStage() (the front end's mission-start call)
after a front-end dwell. With the fix: stale entries purged at every
transition; the reloaded level renders cleanly. A/B with GE_D235_OFF (reset
skipped): 588-788 stale entries survive yet all textures stay correct, for
both an identical and a divergent (intro skipped) first-load order. So the
reported trigger isn't captured headlessly on Bunker 1; the fix stays
correct by construction. D235 stays OPEN pending a real die -> mission
select -> restart playtest (A/B with GE_D235_OFF).

Both switches are TEMP, env-gated, inert when unset; bunker1 golden 3/3.
2026-09-23 11:29:34 -04:00
James Dansereau c1b5b6e519 D330 (#103): mouse no longer drives the stick inside the in-level watch
Auto-Aim / Look Ahead "wouldn't stay on": in the watch, current_menu is
still RUN_STAGE and hipDirectCompute declines on its !watch gate, so mouse
motion fell through to the legacy stick fallback (~3 counts/px/poll). A
~15 px flick crossed the watch's +/-0x2E threshold, which on a selected
option picks OFF/ON (game_option_toggle_input) and otherwise flips pages.
The mouse now contributes nothing while the watch is open or paused;
keyboard and pad drive the watch as before. Save/load round trip ruled out.

Build clean; bunker1 golden 3/3. Mouse feel-check owed (needs real motion).
2026-09-23 11:09:52 -04:00
James Dansereau f89311e9e3 golden: re-baseline bunker1 at 400-880:240 (post-D248 60 fps); defaults-only ini pin
The 2026-09-09 set was captured at 30 fps (2 sim ticks/frame). Since D248
the port renders at 60 fps (1 tick/frame), so frames 200/320/440 landed in
the intro cutscene (nondeterministic camera, D117) and the golden failed on
every build, with or without D329. Moved the window to 400-880:240 (the same
sim ticks the original set sampled, now in settled gameplay); two independent
native-Windows captures agree 3/3.

verify.sh's ini pin rewrote only Width/Height, which kept personal
FovScale/DrawDistance/MSAA settings and failed the gate. It now writes a
defaults-only 640x480 ini (and pins/removes one when none exists).
2026-09-23 11:03:21 -04:00
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 35483f9892 plan: track mod support and HD texture packs as researched post-1.0 items
Both investigated 2026-09-22 against the MIT PD port fork; neither is v0.4.0
work. Recorded here rather than only in the gitignored notes backlog so the
branch carries the conclusions.

Mod support (D325-D328): community mods repack the ROM, so vanilla offsets are
useless; the fix is an offline importer plus a mod-directory override layer, not
a loader rewrite. GE's file table is in the ROM at 0x252c4 inside the
1172-compressed data segment at 0x21990 (681 exact matches vs filelist, 0
mismatches; hardcode-free discovery reads Random Eye's own table to 704/726
byte-identical). Remaining effort is the mod-dir override layer; added levels
stay blocked by rule 2.

HD texture packs (issue #100): PD's texpack architecture studied - identity, not
decoding, is the problem. Our gfx_pc.cpp is the same lineage and GE_TEXDUMP is
already most of a dumper. Step 1 (address -> texturenum registry) is the only
novel design decision and is a src/game touch needing #ifdef PORT.
2026-09-22 23:20:09 -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 12490b95e6 plan: mark D195/D197 stale in front-end batch (both closed per findings) 2026-09-21 23:54:18 -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 0503d33564 D236: strip the investigation probes now that the finding is closed
D236 was root-caused and fixed in the previous commit, so its diagnostic
scaffolding comes out:

  GE_D236RM / GE_D236ORDER / GE_D236ALPHA / GE_D236ZFIX  gfx_sp_tri1
  GE_D236RAW                                             bg.c room load
  GE_D236BT                                              chr.c + model.c
  port_d236bt_dump()                                     host stack backtrace
  g_d236_last_vtx_seg / _raw                             G_VTX trackers

Also drops the <windows.h> include that only the backtrace dumper needed, and
rewords the one D288 comment that cross-referenced GE_D236RM. src/game/bg.c,
chr.c and model.c are back to their pre-probe state -- no game-code diff from
D236 remains. The pass-26 fix in gfx_lod_tile_offset is untouched.

Doc rows removed from GE-ENV-PROBES.md; gen_env_probes.py --check and
gen_findings_index.py --check both pass. Build clean. The Surface 1 spawn
capture is unchanged across the strip (settled frame 560 within threshold;
the intro-pan frames never match run-to-run, D117).
2026-09-21 22:53:04 -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 327e6fe1a7 D75 (M-196): refute slot-overwrite hypothesis; narrow logo bug to texture-modulate render path
New env-gated GE_D75D probe (gfx_pc.cpp) captures per-frame V/T token stream
+ per-triangle rejection/emission accounting for the front-end logo window.

Findings (bare front-end boot, logo = frames ~244+, nv=93/nt=1018):
- Slot-overwrite (M-195's leading hypothesis) REFUTED: the DL is streamed
  (V TTT...VTTT interleaved), max triangle vertex index = 15, so no
  cross-batch vertex clobbering can occur.
- Rejection gates are not the cause: of 1018 tris/frame, trivial-reject=0,
  backface-cull ~520 (normal half), ~500 reach GL, all on-screen (NDC in
  [-1,1]^2). Not the invisible flag; vertex colors [0,255]; depth normal.
- PPM analysis: logo window renders 0-33 content px/frame (the 'tiny
  fragment'), vs title quads at 8.36% coverage.
- Narrowed cause: combine mode 0x009ffe4f19ffe4f1 parses to
  rgb a=TEXEL0 c=SHADE (texture-modulate); gfx_generate_cc sets
  used_textures[0]=1, but the material still renders black-on-black. Next
  step: trace where the TEXEL0 sampler is lost between combiner build and
  draw (shader_id0 -> GL program / prg[tm] / texture upload).

Port-layer fast3d only; no rule-2 sign-off needed. Probes env-gated, inert
without GE_D75D (verified: 0 lines in a normal run).
2026-09-21 06:00:28 -04:00
James Dansereau d4440759cd Merge main into release/v0.4.0 (docs: D322 log + mission-count fix) 2026-09-21 05:26:42 -04:00
James Dansereau 5ea053667f docs: correct solo-mission count 21 -> 20 (level_54 'Cuba' is the ending-credits sequence, not a mission)
The campaign has 20 playable missions (SP_LEVEL_MAX // 20 in
bondconstants.h; mission_folder_setup_entries in front.c has exactly 20
entries). The old '21' counted -level_54 (LEVELID_CUBA), which the front
end jumps to after the final mission for the extended cast display +
credits scroll (do_extended_cast_display / bondviewRenderCredits, D129) -
it is not a selectable mission.

- README, docs/index.md, .github/release-notes.md,
  docs/security-and-fidelity-status.md: 21 -> 20 in current-status claims
- AGENTS.md phase status: same correction
- docs/dev/LEVEL-STATUS.md + LEVEL-PLAYTEST.md: relabel the level list;
  counting note explains historical '21/21 PASS' sweep results = 21 stage
  boots (20 missions + ending sequence)
- social-preview card: stat line 21 -> 20, PNG regenerated (1280x640)
2026-09-21 05:23:11 -04:00
James Dansereau aebcf178e6 D75 (M-195): GE_D75V per-batch vertex probe + findings/probes doc updates
- gfx_sp_vertex probe (env GE_D75V=lo-hi): logs per G_VTX batch the
  transformed x/y/z/w min/max, raw first-vertex ob, dest_index, and the
  MP translation row. Proves the logo transform is correct and that every
  batch encodes v0=0 (slot-overwrite hypothesis).
- videoGetFrameCount() accessor for frame-gated probes.
- findings.md: M-195 entry in the D75 section (emission + transform
  eliminated; G_VTX v0=0 overwrite is the leading suspect).
- GE-ENV-PROBES.md: register GE_D75V, bump reconciled line.
2026-09-21 05:14:57 -04:00
James Dansereau bb508e5ce4 D235: wire videoResetTextureCache into per-stage reset (stale GL texture cache); D311: arm speed-poison choke-point probes
D235 (wrong textures after dying/restarting a level) -- mechanism found
+ fix (M-193). M-108's refutation of the game-side CI-cache theory stands,
but the port has its own TextureCacheMap (port/fast3d/gfx_pc.cpp) keyed by
(source address, palette addrs, fmt, siz, size_bytes, palette_hash) with NO
content check. Stage-texture source addresses live in MEMPOOL_STAGE, which
the per-stage while(!done) block in boss.c resets on every transition
(death/restart included). After the reset, entries keyed into that arena are
stale; if the texpool places a different image at an address that held
another one in the previous run of the level, the lookup hits and returns
the old pixels -- 'wrong textures' only on the reload path, never on a cold
boot. The N64 has no such cache (RSP re-reads DRAM every frame), so this is
a pure port-side defect: videoResetTextureCache() (port/src/video.c) existed
with zero callers and is now called in that block after memaReset
(#ifdef PORT). Re-uploads are pixel-identical; cost is one-time during the
loading screen. GE_D235=1 logs the purged entry count per transition
(cold boot = 0, death/restart > 0) as mechanism evidence; new
gfx_texture_cache_count() accessor backs it. Full 21-level golden sweep:
21/21 PASS (pixel-identical). Live death/restart confirmation is a playtest
item.

D311 (upstream poison source, M-194): every write of model->speed funnels
through modelSetAnimSpeed (startframe<=0 path) or modelSetAnimation2 -- both
now log 'D311: ... ANOMALY' when the incoming value or the resident speed is
non-finite/|v|>1e4 (#ifdef PORT, unconditional-on-fire like the D156
guards; silent in healthy play). The line captures in/resident/startframe/
oldspeed/newspeed/timespeed + model ptr so the next Caverns occurrence pins
the injecting call. Static pass: sub_GAME_7F06FE90 callers all pass
arg2=16.0f (denominator safe); the old->new lerp only circulates values;
bheadAdjustAnimation is the one path where a live player-velocity
corruption reaches model->speed.
2026-09-20 23:46:43 -04:00
James Dansereau 8d3bd5732a D320/D288: extend GE_D318T with generic pin detector; refine GE_D288 probe; cache bg.c hot-path getenvs
D320 (Phase 1 item 1): d318TimelineTick (GE_D318T=1) now also runs a
generic D318-class pin DETECTOR for the sweep's flagged lists (Facility
ai_19, Control ai_9, Depot ai_12): any chr in ACT_ATTACK with a live-fire
attacktype (no TARGET_AIM_ONLY bit) whose modelGetAnimFrame is byte-stable
for 600 ticks logs one 'D320T: PIN?' line with AI-list id/offset/atk/ent/
mot/unk54, repeating every further 600 stable ticks. Detection only -- the
recovery watchdog stays hardcoded to ai_22 by design (D320's explicit
instruction: generalize only after a second list actually freezes in play).
Legit states excluded/self-clearing: monologue pre-aims keep the AIM_ONLY
bit; working fire cycles re-init through chrlvTickAttackCommon so their
frame does not stay byte-stable for 10 s. Smoke: 90 s Facility run, zero
false positives, existing D318T timeline unaffected.

D288 (Phase 1 item 3): per M-155's revised next step, the GE_D288 probe
now skips any_behind_camera==1 triangles (the x/w divide is meaningless for
w<=0 -- that subset was pure false-lead noise) and logs the render-mode
word (oml) so a live capture can ID the culprit asset against level tables.
Re-ran bare -level_20: 1965 sane hits in the 550-755 window, still zero
hits for the target color RGB(83,62,44) -- consistent with M-155's working
theory that the bare boot's scripted camera never reaches the bug state;
headless probing is at its limit here, live capture is next.

bg.c getenv caching (D302-class sweep follow-up): GE_D104 (per room pass,
per frame), GE_D63 primary+secondary render logs (per room, per frame), and
the two GE_D154 inner-GDL-walk sites (per bullet hit) now use the cached
static pattern. All #ifdef PORT blocks; zero behavior change when unset.
2026-09-20 23:27:22 -04:00
James Dansereau 56ac2ab870 Phase 0 hygiene: remove GE_OBJT probes (D321 closed; M-192 item 7)
The D321 stage-flag writer probe (chrSetStageFlags/chrUnsetStageFlags in
chraction.c, GAS_RELEASING destruction timestamp in propobj.c) did its job
(D321 trigger chain mapped + closed); all three blocks were #ifdef PORT
diagnostics with no N64-build or runtime effect when unset. Dropped the
GE-ENV-PROBES.md row too (drift check passes).

getenv hot-path sweep (D302 follow-up, HANDOFF M-192): function-level scan
of all 171 GE_* getenv sites found no uncached calls at frame/triangle rate
-- the only candidates are snd.c's event-rate probe guards (all #ifdef PORT)
and bossReturnTitleStage (once per title return). fast3d has zero raw
'if (getenv(' sites.
2026-09-20 23:10:57 -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 2d9e673129 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:09:03 -04:00
James Dansereau fbc48899ec social-preview: bump card to v0.3.0 (status line; stats refreshed to 199 findings, ~24k C/C++, ~7k Python) + regenerate PNG 2026-09-20 18:07:09 -04:00
James Dansereau 470c239502 docs(agentic-development): velocity chart daily -> single weekly-totals chart (6 bars) 2026-09-20 18:00:19 -04:00
James Dansereau c8309307b9 docs(agentic-development): simplify commits-velocity chart to weekly totals (6 bars) - daily x-axis labels overlapped in narrow renderers 2026-09-20 18:00:09 -04:00
James Dansereau 77ffbbdd64 docs(agentic-development): handoff-workflow flowchart LR -> TD so GitHub doesn't scale the wide diagram down to illegible size 2026-09-20 17:55:54 -04:00
James Dansereau 5d23d2bdfe docs(agentic-development): split commits-per-day chart into August/September halves - 32 x-axis labels overlapped in the rendered mermaid 2026-09-20 17:55:21 -04:00
James Dansereau 4f403ed6d5 docs: deep v0.3.0 accuracy pass — README Background stats (36 days/16 Aug-20 Sep, ~835 commits, D1-D321/199 findings); agentic-dev timeframe 'August' -> 'August-September', gantt gains the 9/18-9/20 final fix sweep + release review bar, findings count 311 -> 321 2026-09-20 17:52:59 -04:00
James Dansereau e153028e07 docs(agentic-development): v0.3.0 release date 9/18 -> 9/20; refresh milestone stats (36 days, ~835 commits, D1-D321/199 entries, M-192, velocity chart through 9/20) 2026-09-20 17:37:25 -04:00
James Dansereau 4827bc868a release-notes/readme: cutscene fix covers all cutscenes (not just Dam); drop never-shipped locked-camera bullet; merge hipfire + F10 sensitivity into one mouse/input bullet v0.3.0 2026-09-20 17:35:16 -04:00
James Dansereau 92dbd015b5 Register GE_OBJT in the env-probe index (CI drift check) 2026-09-20 17:24:16 -04:00
James Dansereau 75e5902717 D321: map the D318 trigger chain (tanks -> chr 254 gas cascade -> combat bit, ~3.5s authored delay; derail same-tick; PC faithful to N64) + GE_OBJT probe extensions (AI-list ID, GAS prop timestamps) 2026-09-20 17:21:24 -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 63315030f8 docs: v0.3.0 release prep — final review pass
- GE-ENV-PROBES.md reconciled (18 new vars documented, GE_D243X3 tombstoned
  after the M-170 permanent fix removed it) — CI gen_env_probes.py --check passes
- release notes gif alt text: ~32 s -> ~12 s (matches README/index captions)
- agentic-development.md stats regenerated for the month-one milestone
- docs/index.md status bumped to v0.3.0, honest-status synced with README
- .gitignore: local/ working-junk dir
2026-09-20 10:09:33 -04:00
James Dansereau 448444ed38 D320 second pass: re-verify mechanism + high-risk rows against source; settle AI command-ID mapping
Review of the D320 entry itself. All mechanism claims confirmed against
source (chrHasStoppedOrPatroling ACT_ATTACK exclusion; update fn only moves
endframe via chrlvAttackActionRelated, never modelSetAnimation; ai_9/ai_12
structures re-read in full). Two precision corrections:
- ai_19 row: the ONLY progression out of the label 0x34 loop is own-flag
  bit 0x01 -> list 0x1504; the stop-check path re-aims at 0x08 (loops).
  Nothing in the list sets that bit (level's only chr_bitfield_set_on(,0x01)
  targets chr 0x43).
- ai_12 row: the update lands mid-hold by construction (aim cmd jumps to it
  on success); the 180-range-flag wait between is unreachable on this path.

Also settled the AI command-ID mapping for future reference: bondaicommands.h
IDs == cmdbuilder enum values from aicommands.def order (naive CMDNAME count
is off by 2; cross-checked on 4 known commands). if_guard_bitfield_is_set_on
= 0x96 = AI_IFMyFlags2Has (executing guard's OWN flags2); the per-chr family
(chr_bitfield_*) is 0x97-0x99.

Also audited d318watchdog.c in this pass: chraiGetAIListID and
sub_GAME_7F025560 signatures match; call form mirrors chraction.c:7396;
dead c78 can never satisfy the signature (ACT_ATTACK vs ACT_DIE/ACT_DEAD
disjoint); g_randomSeed is u64 as written. No changes needed.
2026-09-19 09:01:01 -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 69cc9790ac README: credit f1zz1ec0ke for LLM consultation (v0.3.0) 2026-09-18 13:27:32 -04:00
James Dansereau ce05ae7187 Park D309/D310/D311 session evidence logs + referenced diagnostics in scratch/
- Probe logs preserved as evidence: pre-fix standing-still guard lines,
  v1 Trevelyan regression (local-list embedded PRINT strings), clean
  post-fix Caverns run (global PRINTs size=1/global=1 hit once each incl.
  the D309 spin record; local PRINTs NUL-scanned with valid landings; zero
  LABEL-NOT-FOUND / D156 / watchdog lines), D311 freeze watchdog+stack dump.
- D309/D310 diagnostic files referenced by findings.md: frozen thread
  stacks, live m_RunToBondPersistent byte dump, AI-list decoders (original
  NUL-scan + fixed global-only sizing), command name table.
2026-09-18 13:22:50 -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