- level_sweep.sh: frames rendered then frozen with process alive -> STALLED (froze at N/M frames) instead of a false PASS (the Silo -level_20 fly-down / D117-D134 load case). Retried once like NO-FRAMES. - input.c: Input.MouseRawInput=1 (default 0) sets SDL relative system-scale + warp hints off in inputInit -> no OS pointer accel on aim. - docs: M-26-QOL-REVIEW.md + HANDOFF section.
31 KiB
HANDOFF — GoldenEye 007 PC port (Phase 2, breadth-first level sweep)
Tier 1 doc. Current state + next task + environment. Finding history is
docs/PCPortResearch.md §F/§H (indexed at top of §F); session narrative
is docs/HANDOFF-ARCHIVE.md.
Preflight (do not skip because you have a "next step")
CLAUDE.md(auto) +AGENTS.mdnon-negotiables.- This file, top to bottom.
docs/PORT-LEARNINGS.md— recurring bug classes (you WILL re-derive catalogued bugs otherwise).docs/PLAN-linear-level-sweep.md— the plan of record.- Dispatching a subagent?
docs/AGENT-WORKFLOW.mdfirst — every brief needs FILES / BUDGET / ON-EXPIRY / CONSTRAINTS / REPORT.
Direction (2026-08-29)
Breadth-first pivot. Stop the depth-first BUNKER1 + graphics rabbit-holes. Instead:
- Make boot → intro → menu → level-select functional (not crashing) —
cosmetics parked in
docs/GRAPHICS-BACKLOG.md. - Sweep all 21 solo levels for load + render + no-crash →
docs/LEVEL-STATUS.md.
Full workstream breakdown (WS1–WS6) in docs/PLAN-linear-level-sweep.md.
Where things stand
- BUNKER1 (
-level_09) is playable-ish. Loads, renders recognisably (textured rooms, storage racks, floor; skeletal guards render as humanoids), and survives a guard firefight — 45 s+ crash-free (D103–D120). Silo (-level_20) also loads + renders clean. Committed throughf2beae4b(M-12: D121 WS1 boot, D122 propDef fix). - Input layer (Phase 3) landed and playtested (D118, M-9/M-10).
port/src/input.cis real: keyboard+mouse + SDL_GameController → N64 pad. Core aim/move works. M-24 mouse-look rework (port/src/input.c, port-only): mode-aware map — aim mode (RMB) drives the analog stick past ±60 and emits no C-buttons; hipfire keeps digital C-up/C-down pitch. D118b (mouse-Y inverted) and D118c (aim + mouse-down → crouch) FIXED; D118a residual — hipfire pitch still digital vs analog yaw (minor).config.cINI load/save now implemented →ge007.iniis written on first run and re-read;[Input]MouseEnabled/MouseAimSpeed(50) /MouseTurnSpeed(100) /MouseInvertY(0) are live-tunable. Rebinding / gamepad hotplug still TODO. Weapon switch on kbd = A button (Space/Z/E) — no dedicated key. - Recent fixes (M-10,
d1e93b76): D119 guard-attack crash (weapons_held[]->chrtype-pun) fixed; D120 blood-stain hang guarded (not fixed —d43_emit.pyopcode-0x18 converter gap). - Cosmetic backlog (parked,
docs/GRAPHICS-BACKLOG.md): D75 front-end 3D model transforms (Nintendo logo misplaced, gun-barrel Bond absent, cast models absent), D76 disclaimer screen partial, D77 no audio, D114/D116 HUD/text X-mirror (DO NOT re-static-trace — see PORT-LEARNINGS §D2), D74 dead wrap-block.
Done this session (M-12) — 2 commits
- WS1 / D121 (
02c12068) — bare./build-pc/ge007.x86_64.exe -level_XXauto-injects that stage'smemallocstringtable[]-m*pool row (#ifdef PORTinboss.c bossInitMainthreadData). No manual-m*args any more. Gotchas recorded: settingg_DebugAndUpdateStageFlag=1is the WRONG fix (routes boot through the title-stage intro), andtokenSetStringreplaces the whole token buffer so the injected string must carry-level_XXforward. See §H D121. - D88.4 was already resolved (committed
ff563812/4ccc0d3cweeks ago). The "OPEN — cross-level blocker /setupDoorcrash" note in the old §F index / plan was stale.d88_emit.py --regen= 21/21 pass. - D122 (
f2beae4b) — the real cross-level blocker was prop/item model loading.tools_pc/d88_propdefs.pyhad a per-type handler table + a generic fallback; 6inherits ObjectRecordpropDef types (47 TINTED_GLASS, 39 VEHICHLE, 40 AIRCRAFT, 45 TANK, 13 AUTOGUN, 20 AMMO/MultiAmmoCrate) had no handler → fell to the generic word-granularbswap32, which swapped the[s16 obj][s16 pad]word as one u32 → model idobjin the wrong half → OOBPitemZ_entries[]deref → crash inmodelLoad(loadobjectmodel.c:393) /modelInitRwData(model.c:6249). Fixed in the converter (OBJ_TAIL_DESChandler) + matchingsizepropdef()#ifdef PORTstride. BUNKER1/Silo were never affected (they don't emit those types). Full write-up: §F/§H D122. Confidence: crash fix high; types 39/40/45 tail layout medium (structs have "locs unconfirmed" notes — nudge if a level actually drives a tank/vehicle).
Uncommitted docs (tangled with the in-progress big docs restructure —
HANDOFF.md, AGENTS.md, CLAUDE.md): the D121 + D122 entries in
docs/PCPortResearch.md §F/§H and this file's edits. Commit or fold in.
Done this session (M-17) — 4 commits, 13→18/21 PASS
- D126 (
e851e2ab) — objective sub-recordscriteria_picture(30),criteria_roomentered(32),criteria_deposit(33),setup_objective_text(35) each end in aT *nextlist pointer thatset_parent_cur_obj_*/setup_briefing_text_entry_parentwrite while walking the setup stream. Widens 4→8B and 8-aligns at offset 16 on PC → struct is 24B/6w (N64 16/20); the old N64-sized emit meant the runtime 8-byte->nextstore clobbered the next propdef record's header → walk desync → command indices drifted ~100 →setupDoorlinkedDoorresolution landed on the wrong record. Fixedd88_propdefs.py(PROPDEF_PC_BYTES[30/32/33/35]=24+ typed handler) +loadobjectmodel.csizepropdefPORT returns 6. **Cleared C3r Bunker2- C4 Depot + C6 Surface2** in one change.
- D127 (
1129f63d) —sndPlaySfxguards a bogusALSound*(the converted libaudio bank has fewer/rearrangedsoundArrayslots than N64; audio is Phase-3 parked). Cleared C7 Surface1. - D128 (
749feb9f) —sub_GAME_7F0B37ECspecial-portal marker used the hardcoded N64 8-bytebg_portal_data_entrystride /controlbytes1@6; on PC the struct is 16B withcontrolbytes1@10, so|= 2scribbled into the middle of a portal'soffset_portalpointer →bg.c:5723crash on any level with aspecialportalarrayentry.#ifdef PORTuses the struct accessor. Cleared C5 Control. - D129 (
<this commit>) —langGetbounds-checks the slot id and resolved bank pointer. Bare-level_XXboots that reach the cast/credits text path (Cuba,bondviewRenderCredits, D76 area) reference banks the menu flow never loaded. Reduces the Cuba bare-boot end-credits crash; Cuba itself loads + renders 300+ frames fine and the credits work through the real front-end flow.
Done this session (M-18) — 1 fix, 18→20/21 PASS
- D130 (
port/src/romdata.c, uncommitted) — Facility-level_34+ Runway-level_35C2 crash (import_texture_i8AV on wild ptr0x72181ee8). Root cause was NOT the model-GDL relocation the D124-Facility addendum /BRIEF-C2gdl-model-reloc.mdsuspected — disproved:texLoadFromGdlnever copies aG_SETTIMGon these levels,texWriteLoadToTmem*is never called, andgdlinsub_GAME_7F0762E0is still a segmented0x05xxxxxxvalue so its& 0x00ffffffmasks are correct. Real cause:romdataFixupFontcorrupts BankGothic/ZurichBold glyph indices 0/1/2 — the in-place N64 24B → PC 32Bfontcharrelayout aliasesdst/srcfor low glyph indices (stride grew 8B < the 24B field read span), so a field write clobbers a later field's source mid-loop (glyph 1width=bswap(index), glyph 2pixeldata≈0x020002e8→+= font_base→ wild). Facility's title "Chemical Warfare Facility #2" renders#→gDPLoadTextureBlock(gdl, curchar->pixeldata=wild, …)→ fast3d AV. Fix: stage all 6 N64 fields in a localf[6]before writing. §F/§H D130; PORT-LEARNINGS §A. Verified: -level_34 0/~14, -level_35 0/4; -level_09/20/24 unregressed.
Done this session (M-19) — D131 (Jungle), D63 scaffolding removal
- D131 (
port/fast3d/gfx_pc.cpp) — Jungle-level_37C2m crash.explosionRenderPropSmokepasses a compiled.bssmatrix symbol (&dword_CODE_bss_8007A100) throughosVirtualToPhysical()— au32-returning shim — intogSPMatrix, truncating the0x1_00000000module high word → w1 =0x401c68e0→ wild deref ingfx_sp_matrixwhen the first explosion draws (~frame 300). NOTgimgfixup/ the model-GDL path (that premise was already dead per D130). Fix:seg_addrrestores the module high word for a fallthrough w1 in[0x40000000, 0x70000000). Covers ~30 latentosVirtualToPhysical(<compiled matrix/vtx symbol>)sites (explosion/glass/blood/bondview2). Verified: Jungle renders to frame 1500–2400+ crash-free;-level_20/-level_24unregressed. §F/§H D131, PORT-LEARNINGS §A. - D63 scaffolding removed — dead TEMP D63 probe calls stripped from
port/src/libultra.c(watchdog thread + activity ring + slot watchers) and the#ifdef PORTprobe blocks inblood_animation.c,dyn.c,front.c,rsp.c,title.c. All inert (env-gated / no side effects); build links clean.port/fast3d/gfx_pc.cppD63 trail code left for a later pass. Bug class D24-implications +osYieldThreadwatch item added to PORT-LEARNINGS §E.
Done this session (M-23) — WS6 Facility playthrough: D135/D137/D138/D139 FIXED, D136/D140/D118c open
First real human WS6 playtest. User speed-ran Dam OK, then hit a chain of
crashes on Facility (-level_34) — each one a subsystem the 21-level sweep
never exercises (no weapon fire, no stage unload, no pause menu). Fixed four,
two new open, Facility now near-complete. Committed ec51d9c5 (the
older D121/D122/D126/D128/D130/D131 write-ups are still uncommitted, tangled
in the docs-restructure — fold those separately).
- D135 (FIXED)
src/game/propobj.c— firefight crash.bgTestHitOnObj(bullet-ray vs object triangle geometry, viapropobjFindHiton every shot that resolves on an object model) is an unported N64 GBI parser: raw 8-byte-Gfxbyte/word indices desync on the PC 16-byteGfxstream → walks off the DL → AV0x709ae02a. Ported#ifdef PORTtogdl->words.w0/ .w1shifts (mirrors PDpd_port/src/game/bg.c:3635). Second fault after the parser fix:objHitpropobj.c:9717% impact_sounds->thing2_lenint div-by-0 — the ported texnum-recovery (*(s16*)(phys(w1)-8)) returned a bogustexturenum→ OOBg_HitTypeSounds[]. Fixed: PORT texnum branch always-1(isnd_default, safe; generic impact sound/decal, park w/ D77). - D137 (FIXED)
src/game/gunfire.c— right-mouse (raise crosshair) crash.gunDrawSights32 sp54holds aGfx*the whole fn (texSelect/display_image_at_positiontakeGfx**); 4-byte slot →*(Gfx**)&sp54splices adjacent stack → wild DL write intexSetRenderMode.#ifdef PORT:sp54isGfx *. §A. - D138 (FIXED)
src/snd.c— the "1 room from the end" freeze (not a crash — kernel-heartbeat watchdog).sndCreatePostEvent→alEvtqPostEvent(libultra/audio/event.c:110) walksevtq->allocList; parked audio thread never drains it,chrobjSndCreatePostEventposts per objTick per ambient-sound object, Facility is dense with machinery → the list walk stalls the main thread.#ifdef PORT:sndCreatePostEventis a no-op until audio lands (D77). No gameplay effect. - D139 (FIXED, UNVERIFIED)
src/game/cleanup_objects.c— stage-unload crash (lvlUnloadStageTextData→cleanupObjects→objFree,obj->prop= packed-float garbage). Root cause:cleanupObjectswalks propDefs with(u8)obj[0]for the type, which works only because the header word[u16 extrascale][u8 state][u8 type]is big-endian (type = low byte). On LE the low byte isextrascale→ the walk never matchesPROPDEF_END, runs off the blob, dispatches the switch on garbage →objFreePermanentlyon non-objects → crash.#ifdef PORT: use((PropDefHeaderRecord*)obj)-> type(offset 3) like every other consumer (sizepropdef, proplvreset). Not confirmed — when the user hit pause to trigger a fast teardown test, the pause menu itself crashed first (D140).-level_09unregressed (91.65%). §F D139, PORT-LEARNINGS §C (BE header byte on LE). - D140 (OPEN) — pause menu crashes.
maybe_mp_interface→bondviewRenderWatch(bondview2.c:8604) →bondviewTransformManyPos- ToViewMatrix(g_CurrentPlayer->field_23C = NULL, objheader->numMatrices=9)→matrix_4x4_copy(src=0x0)AV. The watch model's per-node view-matrix cache (struct player.field_23C) is never allocated (orfield_23Cis at the wrong PC offset —struct playerlayout,docs/AUDIT-M6-player- offsets.md). Blocks the WS6 pause-menu / objective-status checks and the watch gadgets. D75 3D-model-transform family. Files:src/game/bondview2.c,src/bondtypes.h(struct player). - D118c (OPEN, low pri) — in manual-aim mode, mouse-down → crouch.
Mouse Y emits C-down;
bondview2.c:5351maps C-down ininsightaimmodetocrouchDown/zoom as well as aim pitch. No input-layer-only fix; needs the deferred analog-aim#ifdef PORThook (§F D118). Documented, parked. - New harness
tools_pc/repro_gdb.sh <XX>— nohup-launch a level, attach gdb, dump fullbt full+ locals on the first SIGSEGV/SIGFPE, rolling frame dump toppm/. Needed becausege007.crash.log's own backtrace is#01 = null(native unwinder fails). Alsobuild-pc/d136_cmds.txt— a gdb cmd file that traces everyobjFreecall (type/obj/prop/model) then catches the fault; the "last line before FAULT" localises D136-class walk-off bugs. Attach the winpid viaps -W | grep ge007.x86_64 | awk '{print $4}'—repro_gdb.sh's ownps -p $!derivation is wrong. - Follow-up (not done): the BG room-geometry hit-test in
bg.c(~3373-3646, walks the D85-widenedptr_expanded_mapping_info) is the identical unported GBI parser to D135 — latent, fires on shooting walls/floor. Same mechanical port. §F D135 + PORT-LEARNINGS §B. - Controls (for the new session's own playtesting): kbd — WASD/arrows
move,
A/Dsidestep, mouse X turn (analog), mouse Y look (digital C-button), LMB/LCtrl fire, RMB/LShift aim,Space/Z/E= A (action AND weapon-switch — GE overloads it),X/R/F= B (reload),Q= L,Enter/Tab= Start,Escquit. No dedicated weapon key.
Done this session (M-24) — mouse-look rework + real INI parser + playtest QoL
Pre-playtest QoL, port-layer only, no src/ / game-logic change.
Full review sheet: docs/M-24-QOL-REVIEW.md (rationale, per-change
risk, verification, what to check by playing). Committed f3ec5170,
65ed0315, 33506aee.
port/src/input.cmouse-look rework. ReadbondviewProcessInput/MoveData: GE aim is mode-dependent — hipfire yaw = analog stick-X, pitch = digital C-up/C-down (stick-Y = move, no pitch); aim mode (R held) yaw+pitch = analog stick past ±60, and C-up/C-down there = crouch/lean/ zoom, not aim. New map keyed on our own RMB/LShift (hold-to-aim proxy): aim mode pushesstick_x/yinto the 61..80 band and emits no C-buttons; hipfire keeps digital C-pitch. GE's native pitch is inverted (C-up→look down) — hidden so mouse-down looks down;MouseInvertYflips.- D118b FIXED (mouse-Y inversion), D118c FIXED (aim+down→crouch —
no
src/hook needed, aim look no longer emits C-down). - D118a residual: hipfire pitch digital vs analog yaw. Minor (precise
vertical aim is an aim-mode activity). Full fix = the deferred analog
#ifdef PORTbondview.chook.
- D118b FIXED (mouse-Y inversion), D118c FIXED (aim+down→crouch —
no
port/src/config.c— INI load/save implemented (was a stub that only logged TODO). Parses$S/ge007.ini([Section]blocks,Key = value,#/;comments), clamps ints to registered bounds, writes defaults on first run, rewrites on clean exit. Unblocks live tuning of the mouse knobs + all future video options. Note: constructor-registered options present atconfigLoad()time (main.c:52) are captured; anything registered later would miss the first save.- New knob
Input.MouseTurnSpeed(hipfire yaw %, default 100), split fromMouseAimSpeed(aim-mode %, default 50) since they feed different game curves. - Verified:
-level_09boots crash-free to 900+ frames @ 91.65% coverage (unregressed);-level_20crash-free;ge007.iniwritten then re-read with no unknown-key warnings. Build green (ntsc-final). port/src/video.cQoL: F12 takes a screenshot (ppm/shot_NNN.ppm, dumped on the render thread via the existinggfx_opengl_dump_bound_fbo); window focus loss frees the mouse (inputSetMouseGrab(0)onSDL_WINDOWEVENT_FOCUS_LOST, re-grab on gain) so alt-tab works.inputSetMouseGrab()also zeroes the aim delta and suspends mouse reads while released.- Alt-Enter fullscreen toggle already existed (
gfx_sdl2.cpp:299). - Gamepad hotplug —
SDL_CONTROLLERDEVICEADDED/REMOVED→inputRescanPads()(close all + re-open). Caveat: the game latchesinputConnectedMask()atosContInit(boot), so a pad added later merges into controller 0 for play but won't appear as a separate channel — plug it before launch for multi-pad. - Mouse-wheel = weapon cycle — a notch queues a 2-poll A-button press;
GE's default scheme (
invButtons = A_BUTTON,bondview2.c:5162/5326) cycles the weapon forward on a bare A edge. Both wheel directions cycle forward (no clean backward input without the A+fire combo). - Window title shows live FPS (
wmAPI->set_window_title, ~1 Hz). - Still TODO (not started): key rebinding, on-screen (in-render) FPS overlay, in-game options menu.
Done this session (M-25) — 3 commits, port-layer QoL
Free-roam QoL pass, port/ only, zero src/ / game-logic change. Full
review sheet: docs/M-25-QOL-REVIEW.md. All defaults reproduce prior
behavior exactly.
9ebed821—[Video]ge007.ini knobs.Video.VSync/FpsCap/MSAA(1/2/4/8) /TextureFilter(0 nearest, 1 bilinear) /Fullscreen, wired invideo.c videoInit. Plus config auto-migration:config.cnow rewritesge007.inionce when the build registers a key the file never had (so a new section actually appears) — an up-to-date file with a user comment is left untouched.5a7a1035—tools_pc/playtest.shtakes a level name (playtest.sh bunker2) or number,--listprints the 21-level table, and a crash now auto-runsaddr2lineon the faulting PCs.863f436b—[Input]ge007.ini knobs.MouseYScale,MouseSmoothing(0 = off = today),PadDeadzone(7000),PadTriggerPct(23),PadLookInvertY.- Verified per commit: build green,
-level_09crash-free to frame 620 @ 91.6% (unregressed),-level_20crash-free. - Silo
-level_20capture freeze — D117/D134 load sensitivity, NOT a real bug and not an M-25 regression. Full-length captures froze at ~frame 320 (silo→Bond camera descent) twice, but only on a machine still loaded from the sweep (the pre-M-25 baseline froze the same way). On an idle machine the user drove Silo live past ~1800 frames, VI pacemaker healthy, no stall. Seedocs/M-25-QOL-REVIEW.md.
Done this session (M-26) — port-layer QoL batch (A/B/C/E)
Low-risk QoL, port/ only, zero src/ change. All defaults reproduce
prior behaviour. Full review: docs/M-26-QOL-REVIEW.md.
- A — save-on-exit +
[Window]persistence.atexit(portAtExit)inmain.c(videoSaveWindowState()+configSave()), fires on every clean quit (exit(0)invideoPumpEvents);abort()paths skip it. New[Window]ini section (Width/Height/X/Y/Maximized, sentinels = old behaviour) invideo.c;Video.Fullscreennow round-trips. - B —
level_sweep.shSTALLED verdict. Frames rendered then froze with the process still alive →STALLED (froze at N/M frames)instead of a bogus PASS (the Silo case). Retried once like NO-FRAMES. - C — raw mouse input.
Input.MouseRawInput=1(default 0) sets the SDL relative-scale / warp hints off ininputInit— no OS pointer accel. - E —
--help/--versioninmain.cbeforecrashInit(): build id, usage, the 21-level-level_XXtable. - Verified: build green (
ntsc-final);-level_09+-level_20boot crash-free 6/6 GE_PCDUMP frames (unregressed);ge007.inigains a populated[Window]block on exit.
Next task (M-24 — Opus 5)
Scope call (user, M-23): D139 (stage unload) + D140 (pause menu / watch)
are the same not-yet-built front-end/transition layer — level exit →
debrief → next briefing, pause, the watch. Bare -level_XX skips all of it
and the sweep never touches it. Do not build a throwaway exit path just
to verify D139 — its fix is unambiguous (LE reads the wrong header byte) and
committed; it'll be exercised for free once that layer is built. Both go in
a parked "front-end / transitions / pause / watch" bucket for one
dedicated session later (see docs/PLAN-linear-level-sweep.md WS2).
M-24 = keep doing in-level playtesting. In-level crashes (D135/D137/D138
type — pointer-width / ABI / GBI-parser bugs) are the real work and show up
during normal -level_XX play.
- Resume WS6
docs/LEVEL-PLAYTEST.md, bare-level_XX. Do the in-level ~80% (spawn, geometry, guards, doors, lifts, switches, pickups, each difficulty-gated objective registers COMPLETE viaobjective_status, alarms/reinforcements). Defer the exit-trigger / auto-advance / pause / watch checks — those need the parked front-end bucket. Facility first (it's playable start→~end now), then down the mission list. - Each new in-level crash:
tools_pc/repro_gdb.sh <XX>, get the realbt full, expect another#ifdef PORTABI/layout fix (D122/D126/D132/D135/D137/D139 pattern). Log as the next Dxx. - Re-run
tools_pc/level_sweep.shonce — D135/D138 touch shared code paths; sweep is boot-only so it only proves no boot regression. Refreshdocs/LEVEL-STATUS.md. bg.chit-test sibling port (§F D135 follow-up — identical unported GBI parser to D135,~3373-3646) — do it before a level where wall-shooting is heavy (i.e. soon).
Parked bucket — front-end / transitions (one focused session):
- D139 verify (stage unload /
cleanupObjects) - D140 (pause menu →
bondviewRenderWatch→ NULLfield_23Cmatrix) - level exit → MISSION COMPLETE → debrief → auto-advance to next briefing
- main menu / mission-select / difficulty-select playtest (WS2)
- the watch (objectives page, gadgets) — needed for the WS6 exit/objective checks, D75 3D-model-transform family
Verification per fix: target level boots to ≥1 non-degenerate frame
(tools_pc/pixcount.py), AND -level_09 + -level_20 unregressed
(tools_pc/framediff.py). Regen sidecars after any converter change.
M-22 and earlier "Next task" (superseded — §H authoritative)
-1. D134 DONE (M-22) — the frame-2 boot hang that every session since
M-13 wrote off as "sweep flakiness / D117 / machine load" was a real,
fixed bug: the SP/DP task-done event was posted OS_MESG_NOBLOCK into
the sched interruptQ that the 60 Hz VI pacemaker also fills, so a slow
synchronous fast3d frame let a retrace backlog swallow the done event →
permanent stall. Fix in port/src/libultra.c (portPostEventForce +
2 reserved slots in portPostVIEvent). -level_09 6/6 boots to frame 600,
0 heartbeats (pre-fix 1/3). §F/§H D134, PORT-LEARNINGS §E.
-
D132 DONE (M-21, commit
fa296b17) — propDef union-index slots for types 14/19/38/44 (LINK/SWITCH/LOCK_DOOR/SAFE_ITEM) now emitted at the right PC offsets (d88_propdefs.py+loadobjectmodel.c sizepropdefPORT). -
-level_09(BUNKER1) boot crash — DISPROVEN as a regression (M-20, coordinator: 3 runs / 1800 frames crash-free on1fc3cff6; re-confirmed M-20 this pass: 690+ frames at 91.7% coverage). Was sweep flakiness / D117 nondeterminism under machine load. Do not investigate. -
Intro "renders mostly black" is NOT a regression — it is the known D75/D76 parked-cosmetic steady state (M-20 / D133). Verified by building M-17 (
9ec6121e, whose handoff claimed "the entire intro renders") in a scratch worktree and capturing the sameGE_PCDUMPwindow: coverage is pixel-identical to HEAD (legal screen 6677 non-clear px = 2.17% both builds; logo-ish frames ~7%; black gaps in between). The 2D/text layers draw; the animated character-model layers (Nintendo-logo transform, gun-barrel Bond, cast models) never appear — exactly D75. The M-17 "entire intro renders" handoff line was aspirational, not a measured state. Parked below level/crash work (docs/GRAPHICS-BACKLOG.md). Silo (#3, fly-down hang) is the same class or D117 nondeterminism — not a fresh regression. -
Facility
-level_34+ Jungle-level_37re-verify FAILED this pass (M-20, machine lightly loaded). Facility: boot crashframes=0, PC0x1400c3b77(addr2line unresolved). Jungle: renders frames 1–2 then kernel-heartbeat hang ("no frame rendered",frames=2). Both were claimed PASS in M-18/M-19 (D130/D131). Needs a clean-machine re-verify before deciding regression vs. flakiness — seedocs/LEVEL-STATUS.md.-level_09/-level_20still PASS, so the capture harness is sound.
After that: all 21 load+render → hand docs/LEVEL-PLAYTEST.md to the
user for the WS6 completion pass (real input, per-level objective
checklist).
Regen gotcha: d69_emit.py rewrites data/pccg-<r>/pccg.bin from
scratch with only the 52 bg/stan rows — you MUST follow it with
d88_emit.py --regen to re-add the 21 Usetup*Z rows, or every level
falls back to the raw N64 setup and crashes identically in
proplvreset2. Always run the full d43 && d69 && d88 --regen chain.
Sweep note: level_sweep.sh was flaky this session (spurious NO-FRAMES on
known-good levels under machine load / the 24 s watchdog + D117
nondeterminism). Verify individual levels with a 35–45 s window and
GE_PCDUMP="60-500:40" when a sweep row looks wrong.
Sweep runner: tools_pc/level_sweep.sh (bare -level_XX, GE_PCDUMP="80-260:40",
24 s watchdog, taskkill //F //IM ge007.x86_64.exe; export PATH=".../mingw64/bin:$PATH"
before addr2line — the script's own symbolication no-op'd without it).
The M-13 matrix below is partially superseded by M-14 (see §H D125 for the current 12/21 status) — treat §H as authoritative.
| Class | Levels | Site |
|---|---|---|
| C1 | Dam 33, Runway 35, Frigate 26, Statue 22, Streets 29, Cradle 41 | chrIsNotDeadOrShot chraction.c:4483 — self = AI-list rodata ptr |
| C2 | Facility 34, Jungle 37 | import_texture_i8/gfx_tex_normalize_source — bad tex ptr |
| C3 | Aztec 28, Bunker2 27 | propobj.c door model / linkedDoor walk |
| C4 | Depot 30 | prop.c:902 sp4C->room after walkTilesBetweenPoints |
| C5 | Control 23 | bg.c:5723 portal_pts->numPoints |
| C6 | Surface2 43 | loadobjectmodel.c:393 PitemZ_entries[modelid].header (D122 cont.) |
| C7 | Surface1 36 | snd.c:653 sndSetupSound (audio — parked subsystem) |
Done M-13: C1 fixed (D123, tools_pc/d88_propdefs.py — the C1
crash was D122 fallout: widened Vehichle/AircraftRecord.ailist slot
zeroed instead of carrying its read-before-write int id). C2 split:
Jungle's texture crash fixed (D124, port/src/gimgfixup.c — compiled
explosion-DL sync keyed on an already-erased marker); Facility+Runway
diagnosed (model-GDL relocation misaligns dst, objecthandler_2.c /
texLoadFromGdl, D80/D82/D83 area) — NOT fixed.
Full re-sweep after both: 12 / 21 PASS (docs/LEVEL-STATUS.md).
9 crashes remain in 6 classes: C2 Runway+Facility (model-GDL align —
biggest), C3 Aztec+Bunker2 (docs/BRIEF-C3-C6-prop-model.md),
C2m Jungle (explosion-DL G_MTX), C6 Surface2 (PitemZ modelid),
C5 Control (BG portal), C4 Depot (BG tile/room), C7 Surface1
(sndSetupSound). Priority order + files in docs/LEVEL-STATUS.md "Next".
Uncommitted M-13 edits: tools_pc/d88_propdefs.py, port/src/gimgfixup.c,
§F/§H D123+D124, docs/LEVEL-STATUS.md, docs/LEVEL-PLAYTEST.md,
docs/PORT-LEARNINGS.md, tools_pc/level_sweep.sh, the BRIEF-C* files,
this file. Fold into the docs-restructure commit set.
Re-run tools_pc/level_sweep.sh after each fix; keep -level_09 +
-level_20 green (framediff.py). All-21-PASS → hand the user
docs/LEVEL-PLAYTEST.md for WS6.
Verification per fix: target level boots to ≥1 non-degenerate frame
(tools_pc/pixcount.py > a few %), AND -level_09 + -level_20
unregressed (tools_pc/framediff.py). Regen sidecars after any converter
change: python tools_pc/d43_emit.py ntsc-final && python tools_pc/d69_emit.py ntsc-final && python tools_pc/d88_emit.py ntsc-final --regen.
Parked (do NOT start here): WS2 front-end menu playtest (needs a
human driving; cosmetics D75/D76/D77/D114/D116 all out of scope —
GRAPHICS-BACKLOG.md). Audio (Phase 3), saves (Phase 4).
Environment / build
export PATH="/c/msys64/mingw64/bin:$PATH" && ./build-pc.sh ntsc-final # ~5 s
Run from the repo root, not build-pc/.
- Repro (BUNKER1, skips attract mode):
./build-pc/ge007.x86_64.exe -level_09(per-level-m*pool sizes are now auto-injected — D121/WS1; pass them by hand only to override).boss.c:337decodes-level_XXasd0*10 + d1 - 0x210; LEVELID == the-level_XXnumber. - Sidecar regen (after any converter change;
data/is gitignored):Ifpython tools_pc/d43_emit.py ntsc-final && \ python tools_pc/d69_emit.py ntsc-final && \ python tools_pc/d88_emit.py ntsc-final --regendata/is missing:cp baserom.u.z64 data/ge007.ntsc-final.z64first (see §F "data/deletion + recovery"). - Frame capture:
GE_PCDUMP="<start>-<end>:<stride>"→./ppm/(gitignored). Analyse withtools_pc/pixcount.py(non-black content check) andtools_pc/framediff.py <ppmdir>(structural regression vstools_pc/golden/— the port is NOT frame-deterministic, D117; use--maskfor the HUD, not an exact compare).tools_pc/ppm2bmp.pyto view. - Crashes:
ge007.crash.log(repo root). Symbolicateaddr2line -e build-pc/ge007.x86_64.exe -f -C <pc>(image base0x140000000). Frames past the true chain may be stale (D56). - gdb: launch mode is too slow for timing-dependent faults. Attach
mode is fast:
gdb -batch -x cmds.txt -p <winpid>(<winpid>= 4th column ofps -p <bashpid>; game must already be running, e.g.nohup … &). A hardware watchpoint on a global catches a bad write in < 1 min. - Standalone probe compiles need
-std=c11.
Non-negotiables (full list in AGENTS.md)
- N64 build files (
Makefile,tools/,rsp/,ld/) untouched. - Game logic unmodified except narrow, documented
#ifdef PORTABI/layout/format exceptions — each logged as a Dxx in §F/§H with the N64 line kept verbatim under#else. Anything beyond a narrow, obviously-correct exception: stop and write it up with a confidence rating, don't hack it in. - Prefer an offline sidecar converter (
tools_pc/d*_emit.py, D43/D69/D88 pattern) over a runtime fixup for a whole ROM-asset format.