Files
goldeneye-pc-port/docs/dev/GE-ENV-PROBES.md
T
James Dansereau f242992360 D245: water tiled at the wrong size + s16 sky/water vertices; D242 closed
The IsWater 32x32 CI8 image (drawn through an RGBA16 tile, D229) was
imported with its mip chain as 32x43 and normalised as 16 wide, so the
pattern ran at 2x and wrapped every 43 rows instead of the N64's 32:
fold steps made it jump. Import the base level only and use CI8
geometry in the tri path. Sky/water fans now go through a port-only
G_FLOATVTX_EXT (float clip positions + S/T) instead of s16 Vtx, removing
the tc shift and wScale quantisation jitter. User-verified vs 1964;
golden 3/3; Win + Linux build. A/B: GE_D245_OLDVTX / GE_D245_OLDTEX.
2026-09-26 18:56:52 -04:00

43 KiB
Raw Blame History

GE_* environment probes — consolidated list

Every getenv("GE_…") in the tree. The table prose is hand-curated; tools_pc/gen_env_probes.py re-greps the live sites and reports drift (NEW / GONE vars + a fresh file:line map) — run it before trusting the File:line cells. Last reconciled 2026-09-21 (GE_D235 added, D235 fix wired; GE_D288 refined per M-155 follow-up; GE_D75V added for the Nintendo-logo vertex-slot investigation, M-195; GE_D322 pool-telemetry probe added; GE_D75D + D75DCC added for the M-196 draw-path investigation that refuted slot-overwrite; GE_TEXDUMP_ADDR added + GE_D75D gained logo-mode lg_* fields for the M-197 root cause). All are env-gated: unset = zero behavior change, so they are safe to leave in a release build (they only cost a getenv on the guarded path). "Dead" below means the finding it was built for is closed — the probe is strip-candidate scaffolding, not that it does anything harmful.

Two classes:

  • Infra / live tooling — deliberately kept, used by the playtest harness (tools_pc/debug.ps1, playtest.sh, level_sweep.sh) or by an investigation still in progress.
  • Dxx diagnostic — added to chase one finding; most are now closed.

Infra / live tooling

Env var File:line What it does Status
GE_PCDUMP port/fast3d/gfx_opengl.cpp:1079 (is_pcdump), gfx_opengl.cpp:700, port/src/video.c:188, port/src/config.c:46 (configGetFrameDump) Per-frame framebuffer PPM dump to $PCDUMP dir (verification-ritual golden captures, tools_pc/framediff.py/pixcount.py). Also settable via [Debug] FrameDump ini (env wins). M-33/D168: the writer now emits rows top-to-bottom — captures before that fix are vertically flipped. live
GE_INPUTSCRIPT port/src/input.c:306 Headless scripted controller-0 input: "<frame>:<tok>,…;…" (SUP/SDOWN/SLEFT/SRIGHT/SNONE sustain, buttons pulse; ZHOLD/ZREL sustain fire, D207 M-201). Sole input source when set. live
GE_INPUTLOG port/src/input.c:329, port/src/config.c:53 (configGetInputLog) Logs computed pad state (buttons + stick) each poll when non-zero. Also [Debug] InputLog ini. Also traces D165 menuptr est/tgt/eff/stick. live
GE_STARTMENU (+ GE_STARTMENU_PAGE, GE_STARTMENU_DIFF) src/game/lv.c:398-402 (#ifdef PORT) Boot straight into a front-end menu id (13=MISSION_COMPLETE, 10=BRIEFING, 7=MISSION_SELECT, 12=MISSION_FAILED, 6=MODE_SELECT); _PAGE=folder row (def 1=Dam), _DIFF=0..3. Crash-test a screen fast. live
GE_UNLOCK_ALL src/game/debugmenu_handler.c:1098 (get_debug_enable_all_levels_flag, #ifdef PORT) Mission-select shows every solo level (playtest jump-to-any-level aid). Cached in a static int c. live
GE_DEBUG_UNLOCKALL src/game/file2.c:404 (#ifdef PORT) Forces the "all levels unlocked" flag TRUE regardless of the shipped save — same effect as GE_UNLOCK_ALL but at the file2 unlock-query site. Cached in a static int. live (playtest aid)
GE_DEBUGMENU src/boss.c:566 (#ifdef PORT) Makes the L+R debug menu respond to live controller input (C-up+C-down open/close, stick + buttons read from pad 0) instead of the #ifndef DEBUGMENU path; unset reproduces that path exactly. Cached in a static int. live (playtest aid)
GE_OPTIONSOVERLAY port/src/optionsoverlay.c:231 Force-enable the F10 port-layer options overlay (D184 / PR #10) regardless of ini. atoi != 0. live
GE_D204 port/src/audio.c (audioSetNextBuffer) Audio-health monitor (D204). One line per 5 s of wall clock: rt= produced-audio/wall ratio (1.000 healthy; below 1 = the 30 Hz audio retrace is starving and the device is padding with silence), q= SDL queue depth / queueLimit (pinning at the limit with drop= climbing = overproduction, the "eventually no audio" state), drop= blocks discarded, max= largest block vs its g_MaxFrameSize*4 allocation. Deliberately cheap (~30 clock reads/s) — safe to leave on for a full playtest, unlike GE_MIXERTRACE. The instrument of record for D202. live
GE_D204_OLD port/src/audio.c (d204OldMode) Restores pre-D204 behaviour (osAiGetLength() reports the whole SDL queue depth; queueLimit back to 8192) so before/after can be A/B'd in one binary, without build-to-build variance. Temporary — remove when D204 closes. live (D204 A/B)
GE_AUDIOTRACE src/snd.c (sndPlaySfx, sndDeactivate), src/game/propobj.c (door sounds), src/libultra/audio/load.c (alLoadParam) Per-sndPlaySfx resolution trace to audiotrace.log: soundIndex → ALSound*/keyMap/wavetable/base/len/book, the returned ALSoundState*, every sndDeactivate, and wavetable bind events. M-65 additions: [VOICE+]/[VOICE-] pairing every voice acquire/release with sound/state/flags (NB: written from two threads to one unbuffered FILE* — occasional torn lines, parse defensively), [SLOTWRITE] door slot ownership, [RETRIGGER-POST], [WAVELOOP] (per-sound ALADPCMloop presence), [DOORSND] every door sound state change incl. an ORPHAN-both-slots-busy verdict. M-66b addition: [EXPIRE] — posted whenever the port-side ownerless-infinite-loop expiration is scheduled (sound/state/delay/fade); this line is permanent (part of the D202 option-C fix), unlike the rest of the probe set which is removable on close. M-67/M-69 additions: [WIRE] t=µs filter=… <- table=… (src/libultra/audio/load.c, every wavetable→decoder bind, µs-timestamped) and [VOICEMAP] (src/libultra/audio/synthesizer.c, full PVoice→decoder/resampler/env-state address map at synth init — this is how offline voicedump slot addresses tie to WIRE filter addresses). M-68 additions: [EVT] t=µs type=N state=P + [STOP-EVT] … deltaUs=N (event types per src/snd.h: 1=PLAY, 2=STOP, 8=VOL, 64=DECAY, 128=END). M-71 additions: [VOL] t=µs state=… rawVol=… playing=… (src/snd.c, per-VOL-event value before the slot-volume law) and [DISTVOL] pos=(…) player=(…) dist=… vol=… (src/game/propobj.c, the distance-attenuation funnel sub_GAME_7F053894 — this is how an SFX's final volume is computed from hit position). NB: lines from the multiple file handles interleave — occasional torn/corrupt lines, parse defensively. dumppos= gives the exact byte offset into audiodump.raw (quantised to the 2880-byte audio block). Low cost. live (D202/D205)
GE_PULLTRACE src/libultra/audio/load.c (alAdpcmPull) [PASTEND] assertion: fires if a voice's f->sample exceeds its wave's total samples while nOver == 0 (i.e. an unstopped voice keeps decoding past the end instead of zero-filling). Rate-limited to 40 lines. M-65: 0 hits — exhausted voices go silent correctly. live (D202)
GE_DMEMWIPE port/src/mixer.c (aClearBufferImpl) Zeroes the whole software DMEM at the frame boundary (detected as alMainBusPull's clear of AL_MAIN_L_OUT, addr 1088). Tests whether audio state survives across frames in DMEM. M-65: no-op on output — ruled out. live (D202)
GE_NOWET port/src/mixer.c (aSetVolumeImpl) Mutes the reverb send (wetamt = 0), isolating the reverb delay lines as a possible source of persistent signal. M-65: drone unchanged — ruled out (NB aPoleFilterImpl is still an unimplemented no-op, D199). live (D202)
GE_BANKDUMP port/src/romdata.c BANKSRC lines: the source ROM-layout bank tree per soundIndex (instrument → sound → envelope pointers + decayTime). Used to prove sound 203's decay=-1 is genuine ROM data, not a conversion bug. One-shot at load. live (D202)
GE_FORCEALARM src/game/propobj.c (handle_alarm_gas_timer_calldamage, #ifdef PORT) D207 diag (temporary). Forces the in-game alarm klaxon (alarmActivate) on a frame counter — on ~2–10 s, off, on again — and emits a per-frame [D207] f=… active=… ptr_alarm_sfx=… playstate=… locked=… line to audiotrace.log. Repro harness for the "alarm SFX overrides other audio" report when a level's own alarm can't be triggered headlessly. Remove when D207 closes. live (D207) — temporary
GE_KEYMAPDUMP port/src/romdata.c KEYMAP + ENVSRC lines: raw source bytes of every ALKeyMap / ALEnvelope in the ROM-layout blob, for byte-wise comparison against runtime values. One-shot at load. live (D202)
GE_AUDIODUMP port/src/audio.c (audioSetNextBuffer) Raw-dumps every mixed s16/stereo/22050 Hz buffer to audiodump.raw in the CWD for offline waveform inspection. Length ÷ 88200 = seconds of audio produced. live (D202)
GE_VOICEDUMP port/src/mixer.c (aEnvMixerImpl) Dumps each voice's resampled mono stream (post-resample/pre-envelope, i.e. the env-mixer input) to voicedump.raw as records of [u32 stateAddr][u32 nSamples][u64 us][s16 × n]. The µs stamp is when the block was MIXED — records are rendered ahead, so a record's samples END at its stamp (M-69: treating stamps as start offsets misplaces content by up to one ~33 ms block). stateAddr = the env-mixer state address from [VOICEMAP]. Used with scratchpad/exact_match.py for full-corpus per-voice sample verification. live (D202)
GE_MIXERTRACE port/src/mixer.c, src/audi.c (amDmaCallback), src/libultra/audio/load.c (_decodeChunk, alLoadParam) Per-opcode mixer trace to mixertrace.log (DMEM addresses, ADPCM books, decoded frames, [DMAREQ]/[DMAHIT]/[DMAMISS]/[BINDTABLE]). WARNING: unbuffered per-opcode fprintf, ~25 MB/min. It slows the process enough to starve the audio thread — M-63 measured "audio at 13 % of real time" that was entirely this probe's own artifact. NEVER use it for timing-sensitive measurement; use GE_D204. Grep it, never read it whole. live (D202) — timing-unsafe
GE_TEXPITCH port/fast3d/gfx_pc.cpp:924 (gfx_tex_pitch_fix, cached) =0 disables the D183 defensive de-stride in import_texture (default on). RC2/RC3 texture-shear A/B knob. live (RC3 not finished)
GE_TEXRAW port/fast3d/gfx_pc.cpp:1079 With GE_TEXDUMP, also dumps the raw pre-import source bytes (texdump/rNNN_f…_s…_WxH.bin) so a decode bug can be told from a source-data bug offline (D183). live (RC3 not finished)
GE_TEXDUMP_ADDR port/fast3d/gfx_pc.cpp (import_texture, cached env check) D75/M-197: with value <hex>, dumps the raw source bytes of every import from that exact address (bypassing the 400-dump cap) to texdump/aNNNNN_WxH.bin + a GE_TEXA[…] log line with fmt/siz/size/min/max/nonzero byte stats. Distinguished "never loaded" from "loaded zeros" for the Nintendo-logo texture buffer (root-caused to the D50 sidecar converter dropping embedded blobs). live (D75)
GE_D219RAW src/game/image.c (texReadUncompressed/texChannelsToPixels, #ifdef PORT) Diagnostic-only, print-only (no behavior change): logs the runtime texture decompressor's bitstream header (texnum/format/width/height/compmethod) for every non-zlib wide-pixel image, plus a dump of the first row of decoded texels exactly as stored in the pool (post-PORT_PIXEL16, i.e. byte-swapped for the 16-bit family) for RGBA16/RGB15/IA16. No texnum filter — lets any suspect texture be cross-referenced against the offline ROM census (scratch/d219_census.txt) without narrowing by hand. live (D219, closed — kept for future runtime-decoder triage)
GE_WRAPFIX port/fast3d/gfx_pc.cpp:3166 (gfx_set_wrap_fix) RC3 test override for the Video.WrapFix D74/D167 wrap-block path (atoi != 0 wins over the ini). live (RC3 not finished)
GE_SAVELOG src/game/file.c:46, src/game/file2.c:16 (SAVELOG macro), src/boss.c:749, src/game/objective_status.c:297,343 Traces the campaign save/unlock chain (objective completion → end_of_mission_briefing → fileWriteSave → EEPROM). Used to confirm D157. g_savelogObjOnce gates the per-criterion dump to the bossReturnTitleStage call. live (strip once a few more level boundaries are playtested)
GE_D160 src/boss.c:744, src/game/bondview2.c:791, src/aicommands.def:10055,10181,10236,10242,10247 (all #ifdef PORT) Dam exit-cutscene (D148/D160) diagnostic: trace points in bossReturnTitleStage, bondviewSetCameraMode, and AI cmds EndLevel/exit_level/CameraLookAtBondFromPad/CameraSwitch. Investigation in progress — user owes a GE_D160=1 Dam run. live
GE_D243 src/game/front.c (menu_init, cached s_d243) Per-menu-transition trace with a local per-tick frame counter (D243 stale-DL-burst screen identification). Counter is NOT numerically identical to the port's internal frame count — cross-reference by elapsed offset. live (D243)
GE_D243CAM src/game/bondview2.c (bondviewFrozenCameraTick, three CAMERAMODE_POSEND branches) Dumps eye/look-at positions (+theta/verta on the gBondViewCutscene branch, +dt/angle/pad on the orbit branch) per call. Found M-143: the gBondViewCutscene branch fires and its look-at (field_3C4/8/C) sweeps through a filter catch-up after the cutscene warp — the shake mechanism. live (D243)
GE_D243M src/game/bondview2.c (solo_char_load, bondviewRemovePlayerBody, playerTick; cached d243mEnabled) + src/game/objecthandler.c (drawjointlist, both region variants) + src/game/chr.c (chrTick, after_position_update:) M-151/M-154/M-159 probe for the Dam-abseil "frozen Bonds + shake": D243M: CREATE/REMOVE at both bodyModel sites; D243M: tick (idx/model/propptr/chrptr/cam modes/prop->pos/field_488.pos/field_3B8/field_3C4-8-C/rp = render_pos pointer, never dereferenced — M-154 fix 2: tick-time render_pos holds arbitrary stale slot contents until the first draw pass, so the pre-view root translation is logged by chrdraw only) while the model exists or POSEND/INTRO is active; D243M: draw per on-screen player render submission; D243M: chrdraw — M-154 per-frame model-draw census, one line per CHR render submission (Model* + raw root translation) while a scripted camera mode (POSEND/INTRO/SWIRL/FADESWIRL) is active; a second Bond instance = extra lines with distinct model pointers at the same frame (same model is drawn twice/frame — two passes — dedupe by (frame, model)); D243M: onscreen (M-159/M-162/M-163/M-166, chr.c) — one line per chrTick call while the probe is active: vis= posIsOnScreen's result (M-159's frustum hypothesis, REFUTED by M-160) + the live prop->pos + actiontype=/anim= (M-160/M-164: which of chrTick's branches fired, the attached-animation pointer) + aidx=/animframe= (M-166: the animation reverse-resolved to its animation_table_ptrs1 index, GE_D193A's technique, + its current playback phase — tests whether the visible chr's animation phase at cutscene-entry, not just its identity, is what differs from N64) + x2active=/x2raw= (M-162/M-163, gated on GE_D243X2: whether the decisive save/restore experiment ran this tick, and the raw pre-restore position it overrode — the whole prop->pos vector as of M-163, not just Z). Silent during FPS play, silent on the GE_D243X2 fields unless that env var is also set. Note: field_3B8 x/z read at 10× world units in the smoke capture — not plain world position. live (D243)
GE_D243X2 src/game/chr.c (chrTick, gated additionally on d243mProbeActive()) Not a probe — a decisive behavioral experiment, off by default, NOT a fix. M-162/M-163: saves prop->pos right before chrTick's position-update dispatch (chr.c:2541-2617) runs, restores it at after_position_update: — before the render-matrix computation later in the same tick can see the cycled value (unlike M-161, which restored too late to have any effect). Tests whether chrTick's own animation-position write (M-160/M-164) is what produces the Dam-abseil shake. M-162 was Z-only (confirmed the mechanism on that axis, but the visible glitch persisted because that capture's chr cycled on Y instead); M-163 widened it to the whole vector. M-163's live result: NEGATIVE for the visible shake — prop->pos genuinely went smooth (log-verified) but the shake was unchanged, because the camera's look-at filter reads a different variable (field_488.pos, see GE_D243X3) that this experiment never touches. Kept in the tree as a real, useful (if ultimately not the visible-symptom fix) data point — always pair with GE_D243M=1 so the x2active=/x2raw= fields on D243M: onscreen show whether/what it overrode. Do not ship. live, test-only (D243)
GE_D243X3 src/game/bondview2.c (playerTick, the field_488.pos write site, gated on g_CameraMode == CAMERAMODE_POSEND) Not a probe — the experiment that actually confirmed the mechanism, off by default, NOT a fix. M-167: skips the field_488.pos.x/y/z assignment (bondview2.c:10495-10497) entirely while POSEND is active, freezing it at whatever value it held the instant suppression engaged. field_488.pos — not prop->pos — is what the cutscene's leaky-integrator look-at filter (M-143, field_3B8/field_3C4-8-C) actually reads; it comes from render_pos (the model's animated-skeleton readback), an independent data path from prop->pos. Live-confirmed positive: user — "yep camera shake is gone now." Logs a companion D243M: f488write frame=... idx=... x3active=... x3skip=... epoch=... f488pos=x,y,z line (always-on when GE_D243M=1, independent of whether GE_D243X3 itself is set) so the freeze is log-verifiable. M-169: no longer freezes forever — re-baselines (x3skip=0, write allowed through) exactly on the tick a legitimate shot-change teleport fires (d243GetTeleportEpoch(), bumped by chrai.c's AI_TRYTeleportingChrToPad right after its real setsuboffset() call), addressing the M-168 postmortem's "camera looks the wrong way for later shots" incompleteness — untested live yet, next session's first move. Superseded: D243 shipped its permanent fix (4ccab22c, M-170), which removed the X3 suppression block from playerTick — no live site remains. Kept here as a tombstone; the generator flags it GONE. dead (D243 closed — probe removed)
GE_D243X4 src/game/chr.c (chrTick, the render_pos snapshot/restore, gated on d243mProbeActive()) Not a probe — the M-168 experiment applying X3's technique to the actual draw-path consumer, off by default, NOT a fix. Snapshots model->render_pos[] after a 2-tick warm-up once POSEND engages, then holds it steady by copying the cached contents back over each subsequent tick's freshly-computed buffer — subcalcmatrices() always still runs first (never skipped; the first two attempts skipped it and crashed on a reused-pointer stale -1 tombstone, see findings.md D243 M-168). Addresses Bond's own rendered-body duplication (the consumer X3 never touched). Crash-free, stable, but same forever-freeze incompleteness as X3 pre-M-169. M-169: same teleport-epoch re-baseline as X3 — the warm-up counter now also resets (re-snapshots) whenever d243GetTeleportEpoch() changes, not only on the active/inactive edge. Untested live yet. Revert once a real root-cause fix replaces it; do not ship as-is. live, test-only (D243)
GE_D243SWIRL src/game/bondview2.c:10480 (playerTick, cached d243SwirlTarget) D243 follow-up probe: GE_D243SWIRL=<chrnum> logs a per-tick line for that chr while the swirl/fade camera path is active (tick counter + state), to verify post-fix behavior of the swirl transition. Inert unless set to a non-negative chr number. live (D243 verification)
GE_D193 src/game/frametiming.c (waitForNextFrame, #ifdef PORT) Per-wall-second line: render= rendered frames, sim= sim ticks delivered (Σ of the deltaFrames/g_ClockTimer fed downstream), clamped= frames whose catch-up hit FRAMETIMING_PORT_MAX_CATCHUP, maxraw= largest pre-clamp delta. Healthy steady state = render=30 sim=60 clamped=0 maxraw=2 (measured M-80, all levels idle). Use to check whether the wall-clock timer under-advances under real AI load (D193). Cheap; safe for a full playtest. live (D193)
GE_D193A src/game/chr.c (chrTick → d193aSample, plus d193aMoved in chrUpdateAnim; #ifdef PORT) Per-wall-second per-ticked-chr locomotion telemetry (D193/D209): chr= chrnum, act= ACT_TYPE, speed= world-units travelled/s, gtd= g_GlobalTimerDelta, tier= act_gopos.unk59 SPEED tier (0 walk / 1 run / 2 sprint; -1 when not ACT_GOPOS), gspd= act_gopos.speed (a turn rate, not a travel rate — 0 means running straight), typ= PROP_TYPE (3=CHR, 6=VIEWER), tick=/move=/frz= chrTick entries vs chrUpdateAnim calls vs freeze-gated (move « tick = the chr is being visibility-culled out of its own locomotion; move=0 at high speed = WAYMODE_MAGIC), hid= CHRFLAG_HIDDEN, then the root-motion scalars scale / xlscale / animrate / playspeed / mspeed (model->speed) / endframe / anim ptr / aidx= that pointer reverse-resolved to its animation_table_ptrs1 index (40 walking, 41 sprinting, 42 running, 107 walking_unarmed — map via src/game/initanitable.c). GE_D193A_CHR=<n> restricts to one chrnum. This probe found D209: Cradle showed tier=1 (RUN) with aidx=40/107 (walk anims) on every sample. live (D193/D209)
GE_D193B src/game/model.c (sub_GAME_7F06D3F4, #ifdef PORT) Rate-limited (1/97) raw anim root-motion decode trace (D193): joint= / flip= / base= mtx channel index / frame= / tmp= decoded s16 translation triple from the anim bitstream / angle= / anim ptr / bd=+bs= the D32 u32 bitDescriptors/bitStream addresses / njoints. M-80 Facility: base=0, angle=0 always (root-motion facing delta never non-zero — top lead), tmp ≈ (±small, ~1086 hip-height, ±small). Compare tmp/angle magnitudes against a clean N64 decomp build of the same anim+frame. live (D193)
GE_D250 src/boss.c (bossMainloop, wraps lvlRender(), #ifdef PORT) Per-wall-second line: [D250] lvlRender avg=Nus n=M — average + count of lvlRender() (DL-building: culling/animation/scene traversal) calls that wall-second, isolated from gfx_run()'s software-RSP interpretation cost (already covered by the existing "frame rendered in Nus" line). Added M-120 to chase D250 (fps stuck ~45-50 on Dam, ruled resolution/MSAA/vsync/build-flags out already) — found lvlRender itself is cheap (~0.5-1.5ms avg), so it is NOT the missing ~10-15ms/frame either; the still-unexplained cost must be elsewhere in the per-retrace-message loop (scheduler/thread round-trip, audio, or something not yet isolated by a targeted timer). Cheap; safe for a full playtest. live (D250)
GE_D172 port/fast3d/gfx_pc.cpp (gfx_sp_tri1 — SETCOMBINE dedup log + non-LOD multitex tri dump; #ifdef PORT) Particle magenta/cyan colour (D172). Logs each distinct SETCOMBINE + active cycletype, and — for a 2-cycle tri that consumes TEXEL1 with tex_lod=0 and a distinct 2nd tile — the tile fast3d samples for each texunit vs what the DL configured. Found D172: 40/40 particle tris configured tile 1 @ tmem 392 (fire) but sampled tile 0 (smoke) → smoke*smoke. FIXED (gfx_lod_tile_offset return rdp.tex_lod ? 0 : i). Probe kept inert. live (D172 FIXED, probe kept)
GE_D157 port/fast3d/gfx_pc.cpp (gfx_sp_vertex + import_texture; #ifdef PORT) D219/D252's "rainbow particle" family — bullet-impact spark on Bunker1 door confirmed by screenshot to cycle magenta/green/cyan (M-157). Three sub-probes: original exact-color-match log (g_BulletSparkColors[] byte match → geometry_mode/LIGHTING + shaded RGB); D157T texture-identity-keyed vertex full-path log (tile 0 holds a loaded texture ≤16 KiB in frame window [90,240] → raw cn[], post-shade RGBA, LIGHTING, geometry_mode, combine_mode, tile-0 tmem/fmt/siz/addr/size/line, uv; cap 400 + summary/200); D157I import census (small textures imported in the window, deduped per source address → addr/fmt/siz/size/tile/tmem). M-158: bug did NOT reproduce in 6 identical dev-box runs — probes left live to capture the full RGBA path on the next occurrence (user Steam Deck run with GE_D157=1). M-201: F12 opens a ~90-frame full-detail D157TB burst; continuous logging cut to a 2000-line baseline + 500 colour-match hits (the uncapped white match ran live play at 4–6 fps). TEMP (D252 fixed M-201; remove after release)
GE_DVM src/game/gunfire.c (gunUpdateAndFire gate dump + gunRenderFirstPersonGunModels render dump; #ifdef PORT) 1P weapon viewmodel (D215). [DVM] line: the field_87F show/hide gate terms + Gun_hand_without_item sub-terms + copy_of_body_obj_header RootNode/Switches/numRecords. [DVMr] line: 87F + cmds_emitted = gfx commands the weapon subdraw() produced (healthy ≈ 150; the D215 bug showed 1). Rate-limited ~1/s per hand. Found + verified D215 (renderdata.flags read 0 → all weapon DL nodes gated off). Probe kept inert. live (D215 FIXED, probe kept)
GE_DTEX port/fast3d/gfx_pc.cpp:1001 Per-import_texture param log (dims / line / size / lod / gen_mipmaps). Used to root-cause RC2 and D159. Kept as an inert diagnostic. live (RC2/RC3 not finished)
GE_TEXDUMP port/fast3d/gfx_pc.cpp:1020, port/fast3d/gfx_opengl.cpp:700 PPM dump of every uploaded texture + a fmt/siz/palfmt/palidx/pal[0..3] line. Root-caused D161 (Depot ceiling). Kept as an inert diagnostic. live (RC3 not finished)
GE_D116 port/fast3d/gfx_pc.cpp:1710,2362, src/game/textrelated.c:265,541 (cached ge_d116) HUD/text "X-mirror" (D114/D116) vbo/uv trace. D114/D116 CLOSED — NOT A BUG (M-33/D168): the captures were upside-down, not mirrored. Probe is now dead scaffolding — strip on the next cleanup pass. dead (D114/D116 closed)
GE_D288 port/fast3d/gfx_pc.cpp (gfx_sp_tri1, right after the D233 trivial-reject guard; cached ge_d288) M-152 diag for the Silo intro stray triangle: frame-gated to num_dls 550–755 (M-155), now skips any_behind_camera==1 triangles (M-155 follow-up — 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. Logs any triangle whose clip-space NDC bbox covers >50% of the screen or goes non-finite, with each vertex's w. Bare -level_20 re-run: 1965 sane hits in the window, still zero 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, live capture is next. live (D288)
GE_D245V src/game/sky.c:962 (skyPortRenderPoly-adjacent, #ifdef PORT) D245 water-seam diag: per-call min/max of the recomputed S/T texture-coordinate ranges for the water fan vertices (rate-limited), to check whether the seam tracks a tc-shift boundary. Inert unset. live (D245 OPEN)
GE_D245_FIXEDSHIFT src/game/sky.c:946 (#ifdef PORT) D245 test override: pins the water tc shift k to the given integer (0..SKY_TC_MAX_SHIFT) instead of the computed per-vertex value, to A/B the allowShift=TRUE behavior. Off by default (unset = current behavior). live, test-only (D245 OPEN)
GE_D252 port/fast3d/gfx_pc.cpp:733 (import_texture, cached s_d252) D252 rainbow-particle diag: one-shot per (address,size) import log of the tile state (tmem/fmt/siz/addr/size/line) for small textures so a suspect particle tile can be checked against its source bytes by hand. Remove once D252 is resolved. live (D252 OPEN — non-reproducible)
GE_D252POOL src/game/image.c (texLoad), src/game/initmttex.c, port/src/gimgfixup.c TEMP (M-201): one line per new texture load (pool main/other, free bytes) + POOL-FULL events + stage pool range; D252SYNC: N slots re-synced, M changed per stage load (the D252 fix at work: M>0 from the 2nd stage on). Load-time only, no fps cost. TEMP (D252 fixed; remove after release)
GE_D245_OLDVTX=1 / GE_D245_OLDTEX=1 src/game/sky.c (skyPortRenderPoly) / port/fast3d/gfx_pc.cpp (CI8 import + tri-path dims) A/B (M-201): restore the pre-fix s16 sky/water vertex path / the pre-fix 32x43, 16-wide water texture geometry. TEMP (D245 fixed; remove post-release)
GE_D207S src/game/gunfire.c (player shot-sound block, #ifdef PORT) TEMP (M-201): one line per player shot-sound decision: `D207S t=<g_GlobalTimer> hand item rate -> H1 H2
GE_D306C="f0 f1 [wmax]" port/fast3d/gfx_pc.cpp (after zf_note_emit) TEMP (M-201): depth-state census of every emitted triangle in frames f0..f1 (GL depth mode, geometry_mode, other_mode_l, tile-0 texture, NDC centroid, z/w range, min w); optional wmax keeps near geometry only. Cap 60000 lines. TEMP (D306 closed; remove post-release)
GE_INTROCAM=<n> src/game/bondview_r.c (#ifdef PORT, intro camera pick) TEMP/test-only: force the level intro's first camera index (Dam truck shot = 5). The PRNG draw is still consumed. Logs D306: intro camera index when set (or with GE_D306C). TEMP (useful repro tool)
GE_STARTWEAPON=<ITEM_IDS> src/game/bondview_r.c (#ifdef PORT, after the start-inventory setup) TEMP/test-only (M-201, D303): adds the item + 400 rounds of its ammo and makes it the starting right-hand weapon (M16 = 13). Pair with GE_INPUTSCRIPT ZHOLD. TEMP (useful repro tool)
GE_D235 src/boss.c (per-stage while (!done) block, after memaReset; #ifdef PORT) D235 evidence probe: logs how many GL-texture-cache entries the per-stage videoResetTextureCache() call purges at each level load. A cold boot into a level should log 0; a death/restart (or any transition from a level that uploaded textures) logs >0 — direct evidence of the stale-entry mechanism. The clear itself is not gated on this var (it's the fix, see below); the var only controls the log line. live (D235)
GE_D235_OFF src/boss.c (same block) TEMP A/B switch: skips the per-stage videoResetTextureCache(), i.e. reverts the D235 fix for one run. For comparing a restart with and without the fix. TEMP (D235; remove when D235 closes)
GE_D235_RELOAD=<frames> src/boss.c (same block + bossMainloop retrace branch) TEMP reload harness: once the title stage loads after the first level (e.g. after a scripted watch Abort), wait <frames> retraces, then re-enter that level via bossSetLoadedStage(), the front end's own mission-start call. A headless death/restart without the pointer-menu walk. Pair with GE_INPUTSCRIPT="800:START;1200:A;1300:RIGHT;1400:A" (abort) on -level_XX. TEMP (D235; remove when D235 closes)
GE_D235_NEXT=<stage id> src/boss.c (with GE_D235_RELOAD) TEMP: re-enter a different stage after the abort (level A → title → level B), used for D336 (Runway → Frigate). GE_D85TEX=1 now also logs each stage's texture-pool size and address range (src/game/initmttex.c). TEMP
GE_D320R="tick:action;..." port/src/d320repro.c (hooked in src/game/lv.c per-tick PORT block) TEMP forced-repro harness keyed on g_GlobalTimer. Actions: roster, jump=<chr>,list=<hexid>,off=<n>, bit=<hex>, trace=<chr>, rename=<chr>,<num>, seed=<hi>,<lo> (D320); explode=<type>,<dist> (explosionCreate ahead of Bond) and `cheat=gl rl
GE_AIMLOG=1 (+ GE_INPUTSCRIPT tokens MDX<n> MDY<n> RHOLD LHOLD RREL) port/src/input.c D337 aim-model harness: scripted sustained mouse deltas (treated as a grabbed mouse) and aim holds, plus a per-poll line with aim state, active device, the game's drawn crosshair x, crosshair_x/y_pos and camera angles. Measures range, edge-scroll and jitter headlessly. live
GE_PERFSTAT=1 port/fast3d/gfx_pc.cpp (gfx_run/gfx_end_frame), port/fast3d/gfx_opengl.cpp (start/end_frame), src/boss.c (main loop) #92/D339 perf probe: per-300-frame averages of render phases (pre / display-list / post / swap+pace), tris and draw batches per frame, game-thread busy time per scheduler message, and a GL_TIME_ELAPSED GPU timer (meaningful only when GPU-bound). live
GE_D75V port/fast3d/gfx_pc.cpp (gfx_sp_vertex, end of the copy/transform loop; cached env check) D75 (Nintendo logo) per-batch vertex probe: for each G_VTX batch in frames lo..hi, logs frame#, source pointer, n_vertices, original dest_index, raw ob[0..2] of the first vertex, min/max of the transformed x/y/z/w across the batch, and the MP translation row. Used to prove the transform is correct (M-195) and that every batch encodes v0=0 (slot-overwrite hypothesis). Value form "lo-hi" (e.g. 300-900 = the logo screen); unset = off. live (D75)
GE_D75D port/fast3d/gfx_pc.cpp (gfx_sp_vertex G_VTX case + gfx_sp_tri1 rejection/emit gates; cached env check) D75 (Nintendo logo) per-frame draw-path probe (M-196): for frames lo..hi, logs one line/frame with the V/T token stream (seq), max triangle vertex index, cumulative vertices loaded, and per-triangle accounting — trivial-reject / backface-cull counts, how many tris reach GL, how many of those are on-screen (NDC bbox ⊂ [−1,1]²), invisible-flag count, vertex-color min/max, first oml word, depth z/w range + NaN count, texture-sampler presence (utex = GL shader, cutex = combiner), combine mode, prim/env color. Used to REFUTE the slot-overwrite hypothesis (DL is streamed, max index 15) and narrow the bug to a texture-modulate material rendering black. M-197 added logo-mode lg_* fields (sampled only on tris with the logo combine word — the frame-level utex/cutex are first-tri-wins and were the source of the "combiner claims 0" false anomaly): lg_utex/lg_tex + tile-0 descriptor + texture buffer address/id/size for the logo material. Value form "lo-hi" (logo window ≈ 244-320 on a bare front-end boot); unset = off. Companion one-shot D75DCC: log in gfx_generate_cc fires when combine_mode==0x009ffe4f19ffe4f1 to decode the logo's combine slots. live (D75)

Dxx diagnostic probes — finding closed unless noted

Env var File:line Finding / what it logged Status
GE_D51 port/src/libultra.c:496, src/game/model.c:125,244,544 msgQ 32-slot overflow watch / ModelSlot layout dead (D51 closed)
GE_D54 — (all blocks stripped M-32, commit 49ce620a) music seq-table ABI / endianness (ALMidiHdr) dead (D54 closed) — fully removed (kept here as a tombstone; the generator flags it GONE)
GE_D154 src/game/bg.c:3418,3447,3632 (capped 64 calls) bg room-GDL call trace (room / gdlidx / vtxoff / op / raw hdr words) dead (D154 closed)
GE_D227V src/game/sky.c (skyPortRenderPoly, #ifdef PORT) Sky/water per-vertex dump: S/T/w/1/w, screen xy, the chosen tc shift k, the baked tc, and whether the tile capture succeeded. Found D227: one sky quad spans ~30,000 texels in S and T but Vtx.tc is S10.5 in an s16 (+/-1024 texels), so 7 of its 8 texture coordinates overflowed and wrapped -- the starburst/"two sky processes" seam. Root cause was a unit error (unk20/unk24 are already S10.5, not texels; M-82's *32 was spurious). FIXED M-100b. Probe kept inert. live (D227 FIXED, probe kept; useful for D229 water)
GE_D176 src/game/bgfog.c:459,471, src/game/sky.c:325,381 (all #ifdef PORT) D176 sky/fog env-match + sky-vert trace (Path B, PR #18) live (D176(a)/(b) OPEN)
GE_D178 src/game/front.c:6586,6594 (#ifdef PORT) briefing-data u16 decode trace (blank-objectives, D143/D178) dead (D178 closed)
GE_D56 src/game/model.c:227,830 watch Model raw-offset reads dead (D56 closed)
GE_D60 port/src/libultra.c:521,735 gfx frame msgQ delivery trace dead (D60 closed)
GE_D61 port/src/libultra.c:795 (s_d61opened) opens a one-shot log file dead (D61 closed)
GE_D62 port/src/libultra.c:477 osRecvMesg trace dead (D62 closed)
GE_D63 remaining: src/game/bg.c:2525,2908,2953, front.c:1410,1416,7814,7889,8083,8091,8099,8366, image.c:2453, language.c:431, model.c:4340, title.c:607 gun-barrel sub-DL clobber hunt (D63/D64/D65/D66 — the labels drift within the blocks). Stripped M-30 (d0789358, gfx_pc.cpp trail). Stripped M-32 (49ce620a): memp.c, bg.c:2880 (incl. the bare non-PORT d63bgprimarycount static + entry log), blood_animation.c, dyn.c, rsp.c. Remaining blocks are all #ifdef PORT + getenv-guarded and inert — strip candidates for a later pass. dead (D63 closed) — partially removed
GE_D69 src/game/ob.c:165,227 stage-load (D69) object trace dead (D69 closed)
GE_D69BB src/game/bg.c:2460,2479,3020 D69 bg-binary layout trace dead
GE_D69STAN src/game/stan.c:266,276 D69 stan-tile trace dead
GE_D85DUMP src/game/bg.c:2510 D85 bgWidenRoomGdl 8→16B dump dead (D85 closed)
GE_D85TEX src/game/image.c:2479, src/game/tex.c:859,1050 D85 texpool-full events (Depot analysis) dead (D85 closed) — was useful for §3
GE_D217TEX src/game/image.c:234 (texInflateZlib) D217: logs texnum/format/numcolours/palette[0..1] straight off the decode bitstream (before any arena/pointer math) — the decode-correctness check that proved the affected texture (grip, texnum=1608) decodes byte-identically across levels, ruling out a decode/ROM-data bug. live (D217 open — investigation instrument)
GE_D229 port/fast3d/gfx_pc.cpp (gfx_sp_tri dual-texunit probe + import_texture) D229: per-draw dump of both texunits' tile/tmem/fmt/siz/UV state for 2-cycle draws, plus a one-shot confirm line when the RGBA16-over-CI8 import remap fires (the fix path). Inert by default. live (D229 fixed — probe kept for the Surface 2 / sweep verification)
GE_D86 src/game/model.c:6295, src/game/objecthandler_2.c:143 D86 model rwdata trace dead (D86 closed)
GE_D87 src/game/ramromreplay.c:296,363,381 D87 ramromfilestructure endianness dead (D87 closed)
GE_D88 src/game/prop.c:1367, src/game/stan.c:3090 D88 Usetup*Z propDef stream dead (D88 family closed; D88.4 resolved)
GE_D90 remaining: src/game/bondview2.c:2146, src/game/prop.c:1391 (bondview_r.c blocks stripped M-32, 49ce620a) D90 bondview / prop NULL trace dead (D90 closed) — partially removed
GE_D96 src/game/chrprop.c:436,507 (cached probe) D96 chrprop trace dead (D96 closed)
GE_D104 src/game/bg.c:654 (rate-limited, d104c) D104 depth-clear trace dead (D104 closed)
GE_D71LOG port/fast3d/gfx_pc.cpp:645 D71 fast3d trace dead (D71 closed)
GE_D75 src/game/title.c sub_GAME_7F007F30 (capped 8) D75 Bug 2 gun-barrel model probe: logs chrModelInstance/gunModelInstance ptr+obj+numMatrices, render_pos, renderData.mtxlist, g_GfxMemPos, osVirtualToPhysical(render_pos) LIVE (D75 Bug 2 OPEN — see §F "D75 Bug 2 — RUNTIME PROBE")
GE_D305 src/snd.c:420 (sndHandleEvent desync branch, #ifdef PORT) D305: logs the sound-preemption desync state when it is detected (after the NULL-guard fix made the crash itself impossible) — confirms whether Game.AllUnlocked correlates or was coincidental, and whether it recurs. Inert unset. live (D305 monitoring)
GE_D309 src/game/chrai.c:810,3424, src/game/chraction.c:10139 (all #ifdef PORT) D309 freeze probe: logs AI label-not-found restarts (chrai.c) and the run-to-bond persistent-list step-on path (chraction.c, capped 400 lines) — the diagnostics used to root-cause the elusive complete freeze (D309/D310). Inert unset. live (D309/D310 fixed — kept for recurrence)
GE_D314 port/src/optionsoverlay.c:681 (cached s_d314Enabled) D314 F10-flicker probe: logs the overlay's viGetY / visible-row count / scroll / selection each poll. Inert unset; strip candidate once D314 is triaged. live (D314 pending triage)
GE_D318R port/src/random.c:69,147 (#ifdef PORT) PRNG golden probe (D284/D318): logs the first 4000 gameplay-PRNG draws with tick + caller return address, and every randomSeed set — verifies the bit-exact port of random.s and which call sites consume the stream. Inert unset. live (D284 verification)
GE_RSEED port/src/random.c:134 (#ifdef PORT) Test-only boot-seed override: GE_RSEED=<hex64> pins g_randomSeed at first seed instead of the wall-clock-derived value — for determinism and N64-parity tests. Inert unset. live, test-only
GE_RSEED_LV port/src/d318watchdog.c:232 (#ifdef PORT) One-shot level-start seed log: logs g_randomSeed at the first tick after level start (optionally armed for a specific tick via <hex>@<tick>) — ties a saved/loaded game's PRNG state to a level for D318-class repro. Inert unset. live (D318 verification)
GE_D318T port/src/d318watchdog.c:270 (#ifdef PORT) D318 watchdog timeline probe: per-tick log of the Facility ai_22 softlock signature window (c78 list/offset/attack state, c67 health) while the watchdog watches. Inert unset. live (D318 verification)
GE_D322 port/src/audio.c (audioSetNextBuffer), src/snd.c (sndD322PoolSummary) D322 pool-telemetry probe: one line per 5 s of wall clock with the three pool layers a long session can exhaust — sfx=count/max (8-voice SFX soft cap), evtq=n/64 (event queue occupancy; full = silent post drops, M-71), pv free/lame/alloc (24 physical synth voices shared by SFX + music; -1s before sndInit), plus the D204 pipeline fields. Cheap (~30 reads/s), campaign-safe; read together with GE_D204=1. live (D322 capture)
GE_D318W port/src/d318watchdog.c:136 (#ifdef PORT) Not a probe — the D318 port-side watchdog itself, ON by default. Detects the Facility ai_22 hold-pose softlock signature (a latent N64 race, see findings.md D318) for 600 consecutive ticks and re-seeds c78's attack via the game's own fresh-attack entry point. GE_D318W=0 opts out; logs D318W: lines when it acts. Permanent fix, not strip-candidate. live (D318 fix — permanent)

Notes

  • The bare non-#ifdef PORT GE_D63 block in bgRenderRoomPrimary (d63bgprimarycount static + entry log, was ~bg.c:2876/2880) was removed M-32 (49ce620a). Every remaining GE_D63 block is #ifdef PORT / #if defined(PORT) wrapped and inert.
  • [Debug] ini keys (Debug.FrameDump, Debug.InputLog) mirror GE_PCDUMP / GE_INPUTLOG as a fallback (env var wins) — M-26, port/src/config.c.
  • Not GE_-prefixed but same category: GE_DETERM is implemented but experimental — not yet achieving its determinism goal (fixed-tick mode, §F D117, M-52 four-pass writeup); GE_DETERM_TRACE (M-52 4th pass) adds instrumented clock-event logging when used alongside it, for diffing two runs to localize remaining divergence. Both env-gated, default off, zero cost when unset.