Commit Graph
1036 Commits
Author SHA1 Message Date
James Dansereau b74d2738fd backlog: D335b F10 overlay pillarbox (polish) 2026-09-23 15:40:54 -04:00
James Dansereau 8e07bced9d D334/D335: 16:9 smoke sweep over 16 more levels — all render, no crashes, no slabs 2026-09-23 15:40:09 -04:00
James Dansereau 89a9875542 README: drop the fixed gun-barrel widescreen note (D335a) 2026-09-23 15:29:57 -04:00
James Dansereau 9232ea180f D335: wrap end credits; fix D335a gun-barrel bar leak (full-canvas scissor under the pillarbox)
Verified at 16:9: -level_54 ending subtitles/cast roll keep their shape;
gun-barrel backdrop now clips at the 4:3 edge. Emitted only for windows
wider than 4:3. Win + Linux builds clean.
2026-09-23 15:29:48 -04:00
James Dansereau 66b33f7f45 D226: design refinement — HUD scale needs a fast3d span transform (boxes + text + icons), not per-glyph scaling 2026-09-23 15:00:38 -04:00
James Dansereau 91bd0f84b7 D237: F10 overlay category headers (DISPLAY/VIEW/MOUSE/CONTROLLER/GAME) + SkipIntro, Show FPS, pad invert/deadzone/trigger rows
New non-selectable ROW_HEADER kind: navigation, wheel and clicks step over
headers. Wheel scroll already existed. Port-only; Win + Linux builds clean;
overlay capture verified.
2026-09-23 14:59:37 -04:00
James Dansereau 7a541da877 plan: widescreen done (D334/D335) 2026-09-23 14:57:07 -04:00
James Dansereau ae1b39ff25 README: native widescreen replaces the stretched-4:3 known issue and roadmap entry (D334/D335) 2026-09-23 14:56:50 -04:00
James Dansereau f62bceaf88 D335: native widescreen, HUD + front end (Option B Phase 3)
HUD elements now keep 4:3 proportions via fast3d's EXT aspect modes (the PD
pattern): watch, gauge bars, messages/title card, upper text and countdown
CENTER; ammo RIGHT (right hand) / LEFT (left hand). The front end is
pillarboxed (whole menu_jump_constructor_handler CENTER) and the menu
pointer is mapped into the pillarbox. port/include/hudaspect.h emits only
when native widescreen is on and the window is wider than 4:3, so 4:3 and
native-off stay byte-identical. Rule-2 approved.

Verified with 16:9/21:9 captures (HUD, watch, title card, legal, file
select, gun barrel); golden 3/3; Win + Linux builds. Gun-barrel circle
leaks into the bars while sliding in -> GRAPHICS-BACKLOG D335a.
2026-09-23 14:56:29 -04:00
James Dansereau d5d9a36a6f D334: native widescreen, world side (Option B Phase 2) — Video.NativeWidescreen
Generalise GE's own N64 16:9 mode (aspect = vp_ratio*0.75*16/9, the TV
pre-squash) to the real window aspect: the port's canvas->window stretch is
that TV, so the world renders undistorted at any aspect (Hor+, vertical FOV
kept). All of GE's 16:9 special cases generalised under #ifdef PORT (camera
aspect NTSC+EU, crosshair sprite width, rocket viewmodel offset); Rule-2
approved. Phase-4 vertical FOV boost is skipped while native is on (would
double-count); room-pool aspect term kept. Default on; F10 row.

4:3 identity (golden 3/3); native 16:9 centre == 4:3 frame (diff 4.3 vs
12.4 stretched); 21:9 clean on Bunker/Facility/Dam; Win + Linux builds.
HUD still stretched (Phase 3 next).
2026-09-23 14:44:46 -04:00
James Dansereau 639a112b9b D333 (#104): Input.AimStyle — N64 / PD damp / centred FPS-style aim on RMB
One F10 selector ("RMB aim style") instead of stacking toggles. CENTRED
routes RMB-held mouse motion to hipDirectCompute (FOV-scaled) and emits no
aim stick, so the game's aim integrator keeps the crosshair centred: the
behaviour the reporter found on Q (game L button), now on RMB. PD DAMP is
D332 (PR #96); legacy Input.PdMouseAim=1 migrates to it. Default N64 =
unchanged. Port-only.

Windows + Linux builds clean; bunker1 golden 3/3. Feel check owed.
2026-09-23 14:34:24 -04:00
Dan Turner 05e5525db9 input: fix reticule jitter in aim mode — adopt the PD mouse-aim model (D332, PR #96)
Opt-in Input.PdMouseAim (F10 "PD mouse aim"): in aim mode the port only
accumulates the mouse position and the game's own crosshair integrator is
driven by it with PD's mouse damp (0.01), instead of GEPD's per-poll
overwrite fighting the weapon's ~0.8 per-tick damp (write*damp^k jitter).
Hook: #ifdef PORT block in sub_GAME_7F067FBC (aim-mode branch only),
identity when off; Rule-2 sign-off given by the maintainer.

Integrated locally from PR #96 with review fixes: finding renumbered
D337 -> D332; GE_PDMOUSEAIM lookup cached (GE_ENVSTR; it ran every poll
and aim tick); "live env" comments corrected. Windows build clean; bunker1
golden 3/3. Feel A/B owed.
2026-09-23 14:29:27 -04:00
James Dansereau 3a7c5d06c5 plan: fix D235 commit ref after amend 2026-09-23 13:34:24 -04:00
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