The op-22 DisplayListPrimaryRecord count is the number of star-flash
arms; dorottex (and PD's modelRenderNodeStarGunfire) read count*4
vertices. d43_emit/d43_convert sized the array as count vertices, so
3/4 of every muzzle star came from neighbouring sidecar bytes and drew
as long spikes (M16 "shooting up"). Size it 16*4*count. Emitter
round-trip checks all pass; M16 renders a clean star headless.
Also TEMP GE_STARTWEAPON (bondview_r.c) for weapon repros. Re-run
tools_pc/d43_emit.py to regenerate data/pcmodels-*.
CCTVRecord's own s32 `pad` (the look-at pad) shadows the inherited
ObjectRecord `pad` (mount pad) under IDO. The PC struct renamed it
`lookpad` to satisfy GCC, which silently re-pointed setupCctv's reads at
the mount pad, so every camera's look vector was its lens offset and the
lens faced the wall. Read `lookpad` on PC (#ifdef PORT, A1 class);
matches PD's setupCreateCctv (lookatpadnum). User-verified vs 1964,
golden gate 3/3. Porting-notes gains the duplicate-member rename trap.
TEMP GE_D207S (gunfire.c, #ifdef PORT, read-only) logs every player
shot-sound decision (H1/H2/SKIP). Four user captures incl. a 7-weapon
stress test at 60 fps: 0 SKIP, 0 voice drops, audio production at rate.
The one captured drop-out ran under GE_AUDIOTRACE, whose audio-path file
I/O is a likely cause. Still open as intermittent.
optionsoverlay.c called atoi and lround through implicit int declarations
(GE's stub stdlib.h/math.h shadow the CRT, D324 class); atoi joins the
other K&R decls in port/shim/stdlib.h, lround gets a local prototype.
Windows + Linux builds clean of both warnings.
GE_INPUTSCRIPT gains ZHOLD/ZREL (sustained fire) for automatic-weapon
audio repros. D207/D241: headless burst-fire at 60 vs 30 fps with the
alarm forced logged zero voice drops at both caps; owed a real-play
capture. Golden gate 3/3.
D291 (user-captured) is D306: Dam intro camera 5, truck over the walkway
camera; reproduces at FovScale 100. All PC-specific leads refuted with
evidence: FOV, depth precision/DrawDistance, depth state (every truck
triangle is opaque z-tested), near-plane handling, wheel transforms. The
image is a depth-correct view of the authored model from under the truck.
GE_ZF's earlier "real fighting" was a +-255 z quantisation artefact.
TEMP tools: GE_INTROCAM=<n> pins the level intro's first camera
(bondview_r.c, #ifdef PORT, PRNG draw still consumed); GE_D306C depth-state
census in gfx_pc.cpp. Golden gate 3/3.
gimgSyncCompiledGlobalDLs (D68 shim) only copied texLoad's resolved
texture pointers into the compiled globalDL shadows while a slot still
held the 0xABCD IMAGESEG marker, i.e. only for the first stage (title).
Every later stage kept pointers into the title's freed texture pool, so
explosion smoke/fire/debris rendered as rainbow garbage once the stage
overwrote that memory (Facility: past the lockers). Learn the slots once,
re-sync them every stage. User-verified; golden gate 3/3.
Also: TEMP GE_D252POOL texpool/sync probe; GE_D157 F12 burst and caps
(uncapped colour match made live play 4-6 fps). D314 F10 flicker
user-confirmed fixed. Findings M-201, porting-notes C2.
- D252 (M-200): the 'rainbow confetti' is the explosion debris
(explosionInitFlyingParticles). Statically clean for colour; 4 headless
repros including the user's grenade-launcher trigger are all clean. Still OPEN.
- d320repro (TEMP): new explode= and cheat= actions for headless explosion
captures; documented in GE-ENV-PROBES.
- D314: optionsOverlayScroll (host thread) rebuilt s_visIdx/s_visN and wrote
s_sel while the scheduler thread draws the overlay. Wheel notches are now
queued atomically and applied in HandleInput (D287 pattern). Emit and
HandleInput share the scheduler thread. Headless file/mission select is
pixel-stable; likely fixed together with D287, needs a user check.
- D291: truck AI mapped (ai_9 / 0x040a, path 7); headless walk did not reach it.
input.c called SDL_SetRelativeMouseMode / SDL_ShowCursor from the scheduler
thread (every controller poll) and from the host thread's focus/click
handling at the same time. SDL's mouse API is not thread-safe; on Windows
relative mode starts/stops the raw-input thread, and clicking back into the
window after being unfocused is exactly when both threads flip it - the D287
freeze trigger. Callers now record the wanted state; inputApplyMouseRequests()
applies it on the host thread from videoPumpEvents(). Queue-deadlock leads
ruled out statically. Win+Linux build clean, golden 3/3; freeze re-test owed.
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.
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.
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 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.
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).