- Input.AimMode 0=N64 (D332 PD-integration, default) / 1=Centred (PC); legacy
AimStyle/PdMouseAim migrated; hidden Input.AimLegacyGepd fallback.
- Mouse aim routing follows RMB/Aim, Q/L (the game's second aim button), and
insightaimmode (Toggle aim, pad): removes the accidental Q 'FPS aim'.
- The PD game hook only feeds the mouse while the mouse is the active aim
device, so pad/keyboard stick aim is not swallowed.
- Harness: GE_INPUTSCRIPT MDX/MDY/RHOLD/LHOLD/RREL + GE_AIMLOG; measured old
vs new: same screen range and edge-scroll, jitter equal headlessly.
Golden 3/3; Win + Linux builds.
bheadAdjustAnimation read player->field_5C0 / animFlipFlag, which on N64
are raw-offset aliases of the inline player.model (model.animframe1 /
model.gunhand). Since D100 made model a real inline struct on PC they were
separate, never-written fields: Bond's head-bob start frame came from
uninitialised player-struct bytes (2e32 etc.), poisoning his model anim
(D156) and wrecking the scene. The D294 2x room pool only moved the player
struct onto dirtier memory. Read/write the real model fields.
Found with gdb hardware watchpoints (field never written). Headless repro
Runway -> Frigate -> turn now clean, D156 0; golden 3/3; Win + Linux.
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.
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.
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).
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.
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.
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.
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).
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.
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).
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.
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).
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).
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.
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.
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.
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.
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.
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).
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.
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).
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)
- 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.
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.
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.
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.
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).
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).