Four simultaneous drain monitors on the same USB interface: WASAPI (512 and
2048 samples) fell to 81-88% drain for ~30-50 s every ~317 s and lost 1.4% of
playback, while DirectSound stayed at exact real time. Each WASAPI shortfall
overflowed the audio queue and dropped blocks (the periodic garbled bursts).
audioInit() now hints directsound,wasapi; SDL_AUDIODRIVER still overrides.
Golden gate 3/3; 60 s GE_D204 run clean after load.
A no-game SDL drain monitor (new tools_pc/sdl_drain_monitor.c, opens SDL
exactly like port/src/audio.c) shows the maintainer's Focusrite USB interface
draining at 88-95% for ~15 s every ~312 s, matching the in-game overflow
bursts' period. Level loads, combat, the SDL pull pattern and a
pacemaker-vs-vsync beat were ruled out. Remaining D322 items: the 8-voice SFX
cap (D207/D241) and #87's progressive decay. porting-notes: measure the host
device before chasing periodic audio glitches.
D311 FIXED (root cause D336; 0 anomaly lines incl. Caverns combat), D288
CLOSED, D294 and D330 user-confirmed, D235 still unreproduced. D322: pools
healthy (leak hypothesis refuted); SFX soft-cap saturation in firefights
(D207/D241) plus a new periodic SDL output-queue overflow every ~314 s that
starts at the title screen, so it should reproduce headless.
Pins data/ge007.ini to a defaults-only 640x480 window (restored on any exit),
captures Bunker 1 at 640-1120:240 and runs framediff.py against the golden
baseline. --level/--dump give a capture-only smoke run (exit 0 = frames
captured and no crash). Full framediff output goes to scratch/.
New findings not covered by TCRF's articles (full detail in local
backlog/cut-content/RESEARCH.md):
- The shipped game's M16 pickup text still reads 'an AR33 assault rifle'
(LpropobjE PROPOBJ_STR_2A, propobj.c:10471); unused 'US AR33' strings and
PROP_CHRM16 comments survive too.
- Klobb rename scar verified: GUN_STR_6B is the only out-of-alphabetical-order
weapon name in LgunE.c, exactly as TCRF's prerelease page describes.
- cartrifle + wristdart: full pickup/prop resources, no ITEM_ enum, no weapon
stats — cut weapons missing from TCRF's unused-items list (wrist dart = the
AMMO_DARTS hardware; decomp comments it '(BETA)').
- All four Bonds mapped: CUFF_/BOND_ Connery/Dalton/Moore enums + the
actor-switch code in bondview2.c with every branch flattened to Brosnan;
no former-actor models exist in the ROM (80 C*Z chr models enumerated).
- Cuba (LlenE.c) is the only orphan level with recoverable text: cut Natalya
dialogue, title card, alternate credits cast.
- Orphan-level geometry confirmed present in ROM (obseg.h segment decls);
what's missing is mission setup, not geometry.
- Music enum vs Notes-page GS index aligned slot-for-slot through 0x2C with
four local mismatches past it (49/52/54/59) pending in-game audition.
Ammo (per-hand corner), bottom messages/title card (bottom-left) and top
dialogue (text origin) scale with crisp glyphs; dialogue and bottom
messages are width-capped because their line breaks are authored. 100%
emits nothing (golden 3/3). Captures verified at 150/200%; Win + Linux.
Env-gated per-frame timing: render phases, tris/batches, game-thread busy,
GL_TIME_ELAPSED GPU timer. Measured ~2.1 ms/frame total CPU on the heaviest
levels here; the GPU timer is only meaningful when GPU-bound. Target-hardware
numbers needed before a low-end preset. Golden 3/3; Win + Linux builds.
Scales the mouse accumulator's screen mapping and edge-scroll threshold; aim
entry adopts through the game's screen mapping so it never jumps. Measured:
PC 98%/72%, N64 65%/49%. Golden 3/3; Win + Linux builds.
- 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.