emit_pad()'s widened-stan zero-fill assigned a 4-byte literal to an
8-byte bytearray slice. Python slice-assign with a short RHS silently
deletes the surplus bytes, so `out` shrank 4 B per pad+boundpad; once
len(out) dropped below a later leaf write's target offset, the write
clamped to empty-at-end and inserted there, drifting/truncating the
boundpad plink-name blob (Aztec pad33 "p138d2" -> "8d2") -> stanPackId
reject -> NULL stan -> door model=NULL -> crash propobj.c:13601.
Fix: 8 NUL bytes (only width-mismatched out[ write in the file).
All 21 Usetup*Z sidecars regenerated. Aztec (-level_28) now PASSES
(was C3 CRASH); Bunker1/Silo unregressed; d125_check.py 21/21 MATCH.
Sweep 12 -> 13/21.
Bunker2 (-level_27) still crashes: separate DOOR-tail linkedDoor
layout bug in d88_propdefs.py (C3r) -- linkedDoor is resolved from
linkedDoorOffset (read-before-write int id, D123 pattern). Folded
into the converter write-audit.
M-16 probes reverted; d125_check.py kept as the offline regression guard.
Generalisable quirk added to PORT-LEARNINGS.md §C.
WS4 level sweep driven to completion: 12/21 solo levels PASS
(load + render + no-crash). Full matrix in docs/LEVEL-STATUS.md,
reusable runner tools_pc/level_sweep.sh, WS6 checklist docs/LEVEL-PLAYTEST.md.
Fixes:
- D123 (tools_pc/d88_propdefs.py): crash class C1 on 6 levels
(Dam/Runway/Frigate/Statue/Streets/Cradle). D122 fallout - the widened
Vehichle/AircraftRecord.ailist slot was zeroed instead of carrying its
read-before-write integer AI-list id, so prop.c -> ailistFindById(0) ->
GAILIST_AIM_AT_BOND -> ai() ran a CHR aim list with a NULL chr ->
chrIsNotDeadOrShot(NULL). New OBJ_ID_WORDS arm. 4 levels -> PASS,
Runway falls through to C2. BUNKER1/Silo unregressed.
- D124 (port/src/gimgfixup.c): crash class C2 on Jungle. Compiled
explosion-DL pointer-sync keyed on an IMAGESEG marker that texLoad()
had already erased -> compiled globalDL_0xNNN arrays kept link-time
IMAGESEG words -> 0xABCDxxxx into fast3d on first explosion draw.
Latent on every level. Detect the slot from the compiled array instead.
Diagnosed, NOT fixed (write-ups in PCPortResearch.md SF):
- C2-GDL (D124-Facility addendum): Facility/Runway - model texture-pool
path yields a garbage tex->data; every model file loads at an odd
bank base (mempAllocBytesInBank does no alignment). Suspect
d43_emit.py/pcmodels sidecar texture-blob offsets (N64 8B vs PC 16B Gfx).
- C3/C6 (D125): Aztec/Bunker2/Surface2 - converted propDefs blob in RAM
doesn't match d88_propdefs.py offline output (garbage after record 0);
runtime sizepropdef() walk drifts ~100 records -> wrong door/modelid.
d88_propdefs.py convert_stream + sizepropdef() PORT strides verified
correct; suspect a dest-offset/pd_end bug in d88_emit.py propdefs emit.
Remaining: 9 crashes / 6 classes (C2-GDL, C3/C6, C2m Jungle G_MTX,
C5 Control BG portal, C4 Depot BG tile/room, C7 Surface1 sndSetupSound).
Priority order in docs/LEVEL-STATUS.md.
Also folds in the in-progress docs restructure (HANDOFF/AGENTS/CLAUDE,
PCPortResearch D121/D122 SF/SH entries, PLAN-linear-level-sweep,
HANDOFF-ARCHIVE, BACKLOG).
Root cause (HIGH confidence): pure variable-timestep frame pacing, not
PRNG/uninit state. osGetCount() is wall-clock on PC (libultra.c:88), and
frametiming.c waitForNextFrame advances game logic by however many 60Hz
ticks of real time elapsed per render -> two runs diverge 32-69% of
pixels by frame 200-440. random.c seeding verified deterministic and
correct; the PRNG stream only diverges as a victim of the pacing jitter.
GE_DETERM fixed-tick mode: assessed NOT narrow (frame-coupled virtual
clock deadlocks the boss.c retrace gate / pre-first-frame waits;
robust version redesigns VI retrace + pacemaker semantics). Deferred
with a full design in PCPortResearch.md F/D117 per AGENTS.md.
tools_pc/framediff.py: structural/tolerant frame compare (16x12 grid
mean-colour delta + non-clear-% swing + 16x16 aHash), --mask for the
animated HUD, --exact for a future deterministic build, --update to
refresh tools_pc/golden/. Reads .ppm and .png (built-in PNG decoder).
Validated: two nondeterministic re-runs pass structural, --exact
reports the real 32-69%, a wrong-frame pair fails.
No game-logic or port-layer C changes; no probes left in tree.
D115: THROWMTX/THROWPOS/THROWPREV in gunfire.c were raw byte offsets
((u8*)g_CurrentPlayer + handnum*sizeof(struct hand) + 0xAD8/0xB08/0xB48).
Both the stride (PC sizeof(struct hand) ~0x968 vs N64 0x3B8, D102) and the
base offset are N64-sized, so on x86-64 the address lands inside the
inline gait Model / bondhead-matrix region of struct player and
matrix_4x4_copy(THROWMTX,..) scribbles 64 bytes of live render state on
every casing-ejecting shot. The offsets are exactly
hands[handnum].throw_item_pos_related[_prev] and its translation row -
macros now use those field accessors under #ifdef PORT; N64 build
unchanged. Full survey in docs/AUDIT-M6-player-offsets.md (10 sites,
3 HIGH fixed here, 2 MED open incl. the likely 'weapon model doesn't
draw' cause).
D113 (investigation, no code change): the 'portal-BFS under-reach / void'
premise is stale - the BFS output is correct for BUNKER1's topology
(rooms 26/27 are behind PORTALFLAG_DISABLED closed-door portals, culled
identically on N64). The void is the closed-door models obj 153/154 not
rendering in their doorway -> D114 track.
D114 (investigation, no code change): joint-matrix chain + d43_emit.py
float emission verified clean post-D112; basemtx is a proper
right-handed +Y-up lookat, joint render_pos coherent and upright. All
plan hypotheses disproven. Residual = a horizontal mirror in the shared
port/fast3d viewport / MP-matrix path vs GE's left-handed screen
convention (also explains the open D75 mirrored-text / logo items).
Still open.
Plan + agent reports: docs/PLAN-M6-playable.md. §F: D113/D114/D115.
put_f32 reversed bytes doff+1..doff+4 instead of doff..doff+3, dropping
each float's MSB and pulling in a byte of the adjacent field. Corrupted
every f32 in converted model rodata (joint Origins, LOD distances, BSP
planes, bounding radii) for all sidecar-loaded models. Compiled-in
front-end models were unaffected, hence 'logo fine, every guard a line'.
render_pos[] joint matrices now coherent; BUNKER guards render as
humanoids and monitor screens draw content. Regen: python
tools_pc/d43_emit.py ntsc-final.
zbufClearCurrentPlayer (viewport.c) clears the N64 Z buffer with the
'point the colour image at the Z buffer and gDPFillRectangle it' idiom.
fast3d does not emulate that: gfx_dp_fill_rectangle bails when
color_image_address == z_buf_address assuming glClear already cleared
depth, but the frame-start clear_framebuffer(true,false) is colour-only.
fast3d expects G_CLEAR_DEPTH_EXT (0x44) for a real depth clear and GE
never emits it. Here the two addresses don't even match
(gDPSetDepthImage(z_buffer & ~0x3F) vs gDPSetColorImage(K0_TO_PHYSICAL))
so the packed-Z fill colour is scribbled onto the framebuffer instead.
Net: every in-level frame rendered against a stale/garbage depth buffer,
all ~160k room triangles failed the Z test, only skyRender's background
fill survived -> flat dark-blue screen + HUD.
PORT branch in zbufClearCurrentPlayer emits a bare G_CLEAR_DEPTH_EXT and
returns, skipping the N64 idiom (a no-op / harmful in fast3d anyway).
Verified -level_09: 90% flat sky-colour -> 2585 distinct colours, sky
16%; recognisable BUNKER1 (textured walls, storage racks, floor). 70s
crash-free. Adds GE_D104 room-render-pass probe in bg.c (gated, capped).
Remaining: dark-blue wedge (geometry gap) + brown band top third
(ceiling tex); then struct player weapon/offset pass; then D75(b).
osViSetMode hard-coded the fast3d native resolution to the visible
scanline count (pal ? 400 : 480) instead of the 320x240 CFB space GE
authors its GBI in. SCREEN_WIDTH/SCREEN_HEIGHT alias the native viewport;
RATIO_X came out 2.0 but RATIO_Y 1.0, so every gSPViewport/G_SETSCISSOR
was a 220px band (~46% of frame) shown in the lower half after invert_y.
gfx_current_native_aspect was wrong too (320/480).
Recover CFB height from vm->fldRegs[0].yScale (bufy*0x800/480, fr.c:417)
with a pal ? 272 : 240 fallback. comRegs.width already carried bufx.
Verified -level_09: GE_PCDUMP frames 46%/lower-band -> 91.7%/bbox
(0,20)-(639,459), correct letterbox + HUD placement. Reveals next layer:
room geometry (D85) and weapon model still don't draw (flat blue + HUD).
Session M-2: -level_09 boot wired, stan tile-name + intro-lang converter
fixes, two load-path crash fixes. BUNKER1 now renders ~2100 frames on
both direct-boot and attract before D90.
- prop.c/stan.c: GE_D88 env-gated diagnostic probes (proplvreset2 pad
walk, stanMatchTileName tile walk). Zero cost when unset.
- Verified: proplvreset2 now walks the full pads table correctly
(real plink strings, sane BUNKER1 coords); prop.c:1306 crash gone.
- D88.4 (new blocker): propDefs stream left un-byteswapped by the
converter -> setupDoor/modelLoad crash on garbage door->obj.
- D75: expanded with confirmed symptoms (Nintendo logo mispositioned,
gun-barrel Bond model missing, cast-roll character models absent).
D86 (resolved): modelInitRwData crashed on a bad ModelNode pointer during
room-triggered gameplay init. Root cause: initplayergaitobject.c did
`player_gait_object_header.RootNode = (int)&player_gait_hdr;` -- a
same-width pointer<->int round trip that's a no-op on N64 but truncates a
real 64-bit pointer on PC, dropping the executable's load-base high bits.
Fixed under #ifdef PORT by assigning the real pointer directly. Only
exercised once real gameplay starts (past the intro), which is why it was
never hit before D69 let the game reach stage load.
D87 (resolved): once D86 stopped blocking progress, an idle (no-input) run
reaches the front end's genuine attract-mode demo playback and crashes in
ramrom_replay_handler. Root cause: ramromfilestructure is a real
ROM-compiled asset (big-endian) loaded via romCopyAligned's raw byte copy
(D66) with no byteswap, so every multi-byte field reads scrambled and
drives wild pointer arithmetic in the block-buffer consumers. Fixed with a
#ifdef PORT ramromFixupEndian() called once after the load (D54 cseq-fixup
pattern).
Both root-caused with new env-gated TEMP probes (GE_D86, GE_D87), left in
place per the existing convention. D88 (per-level "Usetup*Z" stage-setup
file: zero PC porting done, struct-width + endian bugs stacked) is the new
next blocker, root-caused but intentionally not patched inline -- it's
D69-scale format-conversion work, logged as an open finding instead.
Also documents a fast, deterministic BUNKER1 repro (-level_09 command-line
arg, bypasses the front end/attract-mode entirely) and a correction to the
gdb guidance: attach mode (vs. launch-only) works well for watching a
global's write on an already-running process.
Docs: docs/PCPortResearch.md §F (D86/D87 resolved, D88 new), docs/HANDOFF.md
(session K), AGENTS.md summary line.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3xib6jjMV8o2iE4JfixAo
Verification of the D69 sidecars against a live run surfaced two more
narrow ABI layout bugs, both the same MIPS-BE/x86-LE class as D78/D79:
- D83: StandTileHeaderMid/StandTileHeaderTail bitfield packing. N64/MIPS
packs the first-declared bitfield into the high bits; x86 GCC packs it
into the low bits. tile->tail.hdrTail.pointCount (read pervasively for
tile-size navigation) silently read the wrong nibble on PC, producing
an out-of-range list_of_tilesizes index and an infinite loop in
stanBuildRoomData. Fixed under #ifdef PORT by declaring both structs'
fields in reverse order (verified via probe: byte-identical numeric
result to N64).
- D84: bgLoadRoomVtxData/bgLoadRoomPrimaryGdl/bgLoadRoomSecondaryGdl each
hand-roll the BG_SEG_TO_PTR segment fold instead of calling the macro,
and did it as real 64-bit pointer arithmetic -- the "+0xf1000000"
doesn't wrap at 32 bits the way the macro's (u32) cast does, so the
computed file offset came out ~4 GiB too large. Fixed under #ifdef PORT
by doing the fold as plain u32 math at all three sites, matching
BG_SEG_TO_PTR exactly.
With D83+D84, load_bg_file and stanBuildRoomData complete without fault
and the game proceeds into room streaming, where it hit a further crash
in bgBuildRoomVtxBounds: the room primary/secondary DL binaries decompress
correctly but texLoadFromGdl (the already-working model-GDL runtime
converter) doesn't produce valid GBI opcodes from their content (D85,
not yet root-caused -- likely a BG-specific GDL marker convention).
Added a PORT-only bounds check before every vtx[] dereference so a
garbage command stream can't segfault the process (produces an empty
bounding box for that batch instead) -- a crash-safety net, not a
geometry fix.
The original D69 bug (load_bg_file faulting instantly on stage load) is
now fully resolved and verified byte-for-byte against BUNKER1's stan
tiles. Two new, separate issues surfaced during verification remain open:
D85 (room geometry decodes wrong) and D86 (a new crash in modelInitRwData
during room-triggered model instantiation, likely unrelated to bg/stan --
see docs/HANDOFF.md). Debug probes GE_D69STAN/GE_D69BB added (stan.c/
bg.c, env-gated, zero cost when unset) and kept for the next session.
Docs: PCPortResearch.md D83-D86, HANDOFF.md and AGENTS.md status rewritten
to reflect current state.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3xib6jjMV8o2iE4JfixAo
Docs-of-record for the D69 stage-load milestone (docs/PCPortResearch.md
D78-D82): decoded bg/*.seg (header/room/envdata/portal tables) and
Tbg_*_stanZ (prefix offset-array + StandTile records) formats, plus two
struct-layout ABI fixes required before any byte-swap conversion can work:
- D78: StandTile's `u32 id:24` + non-bitfield `u8 room` packs into one
4-byte word on N64/MIPS-GCC (8-byte tile header) but never shares
storage on x86 GCC (10-byte header), silently misaligning every
mid/tail/points field read on PC regardless of endianness. `id` is
provably dead code (no reads/writes anywhere) so it's widened to a
3-byte array under #ifdef PORT, restoring the exact N64 stride.
- D79: bg_room_data's 3 `void *` offset fields are never dereferenced
(grep-verified, always used as plain u32 arithmetic) but silently grow
the 24-byte N64 room record to 40 bytes on x86-64. Declared `u32` under
#ifdef PORT instead -- size-preserving, no behavior change.
Both are narrow, individually-documented #ifdef PORT layout exceptions
per AGENTS.md non-negotiable #2 (same class as D53.1/D53.2/D66).
Adds docs/BRIEF-D69-stage-load.md (milestone brief) for reference.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3xib6jjMV8o2iE4JfixAo
Carry over the logo-pixel session's handoff state: D75-D77 recorded as
OPEN in PCPortResearch.md §F with candidate causes + verification steps,
prioritization guidance (D69 stays primary; render before audio unless a
shared root cause is found - Q1), and matching AGENTS.md phase-status line.
HANDOFF also drops the stale scratch-file list from Task 3 and points at
the relocated N64 reference screenshots.
D73 (root cause of old D72.3): GE's sinf/cosf build their double
constants through the du bit-union with big-endian word pairs {hi,lo};
on LE PC the .d read has the words swapped (rpi.d ~ 2^733), range
reduction overflows, and FTOFIX32 saturates to -32768 for every
sin/cos entry of guMtxF2L — so the Rareware logo's rotation matrix was
garbage and all its triangles projected off-screen. Fix: DVAL() macro
in src/libultra/gu/guint.h (PORT-only; #else branch is the original
expression) re-packing hi/lo into the correct LE double; sinf.c/cosf.c
.d reads replaced. N64 build untouched.
D74 (texture side, three port-layer bugs in fast3d):
- import_texture's fallback condition (rdp.tex_lod && tile >=
first+detail) overwrote valid gDPLoadBlock tmem data with
line*tile.height: letter mip chains 2744B -> 2048B (mips 1-5 dropped),
D_02005FF0 32x32 -> 32x3 (stale tile.height from gsDPSetTileSize).
Now falls back only when the tmem slot was never written.
- TextureCacheKey gains size_bytes so a truncated first import cannot
poison later full imports of the same address.
- VBO UV path now wraps by TILE size for WRAP sub-tiles (N64 semantics);
the logo's 20x3 tile at offset (11.5,29) was sampling row 0 instead
of rows 29-31. Per-vertex pre-wrap; known limits: half-texel edge
bleed, mirror sub-tiles unhandled.
Verified: clean runs reach frame ~2100 then hit the D69 load_bg_file
fault (unchanged — still the milestone blocker); PPM frame ~555 shows
the four gold RARE letters on the dark-blue plate (final pixel-perfect
check vs N64 footage left as a low-priority task). All env-gated probes
from this session (GE_DBGUV/DBGTRI/DBGTALL/DBGMAT/D74IMP/D74DUMP/
GE_DBGLOAD) stripped; previously committed TEMP D63 blocks + GE_D71LOG
remain on the HANDOFF strip list.
Docs: PCPortResearch.md §F D73/D74 entries, §G/§H status updated;
HANDOFF.md rewritten for Session I (D69 primary); AGENTS.md phase line.
- PCPortResearch.md: G status + H handoff now list D70-D72, the open D72.3
bug with its next diagnostic step, and the env-gated visual-debug tooling;
stale 'current frontier (post-D31)' note in the plan marked done
- AGENTS.md: phase status carries the D70-D72 summary; D69 stays the
milestone blocker
- HANDOFF.md: title reflects both threads
D59/D64: the 'external GL DLL' crash was an unbounded blood-RLE write
(sentinel die_blood_image_end lost its N64 section adjacency on PC)
clobbering the gun-barrel sub-DL at ptr_logo_and_walletbond_DL+0x200.
D60: osPiStartDma DMA-target validation (VirtualQuery-based) in libultra.c.
D61/D63: TEMP env-gated ROM-read / VTX-pool / menu probes (strip list in
HANDOFF Task 3). D62: OSMesgQueue/OSScMsg PC layout verification.
D65/D65b: enum HEADS/BODIES signed sentinels under PORT (PC GCC picks
unsigned underlying types; N64 semantics were signed).
D66: romCopyAligned + ramrom replay 64-bit pointer width (s32 truncation
of .bss targets > 4 GiB). D67: struct image_entry N64 layout
reconstruction (dataoffset:24 first, all-u32 bitfields, sizeof==8).
D68: Globalimagetable BE->LE fixup (port/src/gimgfixup.c): bswap the
IMAGESEG-marked Gfx w1 words in the 17 ROM-copied DLs + sImageTableEntry.
index in the 32 tables after texReset's romCopy; marker scan moved to
bytes 6..7 under PORT; compiled globalDL shadows synced post-texLoad for
explosion.c's g_ExplosionDisplayLists.
Result: game boots, plays intro music, and renders the entire ~3.5-minute
intro (logo -> gun barrel with Brosnan -> cast screen) at ~59 fps. New
blocker D69: load_bg_file faults on first stage load - BG-file headers are
N64 big-endian segment-0x0F offsets read LE on PC. Full analysis + strategy
in PCPortResearch.md §F/D69; HANDOFF rewritten for the D69 session.
- R1 RZ format: 512/512 files [0x11 0x72]+raw deflate; sum round8(C) = 1,277,088 B (exact D48 match)
- R2 romCopy = gated host memcpy; no 0x10C00000 limit; sidecar region needs only reservation+validity extension
- R3 file_resource_table is the single chokepoint; no direct model-symbol derefs outside table
- R4 obInit runs once (boss.c:179) before any model load; lazy one-shot patch safe
- R5 fit quantified: max round8(C)+8 = 0x4228; worst-case P_final = 0x16F74 vs 0x24C400 bank (25x); all dst!=0 buffers pass vs current constants (chainbound re-run)
- R6 reload hazard real + data-capable (PlegalpageZ example); one-line reset pending, closes it
- R7 per-region sidecars work (region build-time-fixed; EU=465 files); notes: no JP ROM candidate in romdataInit
- R8 emission spec complete; fresh C probe (exact build flags) re-verifies all 15 PC record sizes + promotion offsets — no drift; added explicit bswap16 rule for u16/s16 record scalars to HANDOFF Verified facts
- D48 finding in PCPortResearch.md §F: d43_convert.py does not emit bytes
(layout+remap validation only); Plan B = Python emit pass -> data/pcmodels-<region>/
RZ sidecars served via port-layer hw_address/rom_size table patch (no C
converter); Plan A kept as fallback with corrected staging guard (D_N64+D_PC<=avail)
- Moved all 23 d43_*.py investigation scripts from gitignored build-pc/ to tracked
tools_pc/ (spec is now versioned); d43_cover.py path ref updated; converter
re-run from new location: ALL CLEAN
- HANDOFF.md rewritten as two-session prompt: Session A = independent review of
Plan B (8 claims, each with falsification criterion -> finding D49), Session B =
execute (emit pass + round-trip validator, romdata plumbing, one-line hook,
pixel-assert soak); verified-facts converter spec preserved
- AGENTS.md phase status updated to D48 re-plan
- D38: implicit function declarations truncated pointer returns on x86-64;
added port/include/pc_protos.h prototype shim (398 decls, C-only, anchored
in port/shim/PR/ucode.h) + pc_netorder.c for ntohl/ntohs; build warning-clean.
- D39: Globalimagetable rebasing - per-symbol offsets + GIMG_OFF() in
image_bank.c (== N64 (u32)&sym incl. the 0x02000000 base); segment end
markers extended to the true 0x13F8 size (CSV entry truncated by 0x930).
- D40: g_ModelHitEntries pool sized for the 40-byte PC ModelHitEntry stride
(was a 12KB .bss overflow clobbering is_ramrom_flag -> demo-replay SIGSEGV).
- D41: release the GL context from the host main thread after videoInit so
the game scheduler thread can bind it (WGL single-thread currency; this
SDL2 build's MakeCurrent return value is an inverted ABI artifact).
- D42: rsp.c g_gfxTaskSettingsList XOR toggle -> explicit toggle on x86-64.
Boot now renders real frames ([NOTE] frame N rendered in ... us). Next
blocker D43 (model-file loading ABI mismatch) documented in PCPortResearch.md
section F/H; HANDOFF.md rewritten for it.
Record ROM-dump verification (No-Intro [!] byte-identical, CRCs re-checked)
in AGENTS.md phase status, PCPortResearch §H state, and HANDOFF current
state, so the next session starts from a verified foundation.
structured fields + x86-64 loop-stride bug; fix designed, handoff updated
- Finding D33 in docs/PCPortResearch.md: the .z64 stores multi-byte
structured fields in big-endian byte order (bit-packed streams raw);
per-field load-time transform rule (u32 bswap32 / u16 bswap16 / u8
identity) validated over all 173 animation entries; full blob layout
mapped; exact reproduction of the gdb-observed garbage values.
- Second root cause: expand_ani_table_entries iterates s32** (+8 bytes/iter
on x86-64 vs +4 on N64), rebasing only even-indexed entries (verified in
compiled binary). Fix design: romdataFixupAnimationData() in port/, one
call between romCopy and expand, s32* loop stride fix.
- docs/HANDOFF.md rewritten with the implementation task; AGENTS.md phase
status updated. tools_pc/disasm.py (BE ROM research tool) added.
- D31 (resolved): replace GE's gzip-1.2.4 inflate with real-zlib
decompressdata() in port/src/rzdecomp.c; exclude src/game/decompress.c +
zlib.c from the PC build. Root cause: struct huft is 8B on MIPS but 16B on
x86-64, overflowing load_resource's fixed 0x2100 buffer and clobbering its
frame/retaddr on the first file load. mainThread now boots past all language
files into bossInitMainthreadData().
- D32 (in progress): ROM-serialized structs with 32-bit pointer fields
misalign when read as 64-bit pointers. Part A done: ModelAnimation's
bitDescriptors/bitStream are now u32 (N64 layout preserved) with casts at the
two use sites in model.c. Part B (load-time rebase in expand_ani_table_entries)
is open — see docs §F/D32 + §H.
- Docs: D31 resolved, D32 recorded (A done / B open), Phase 1.5 added, new §H
handoff section. AGENTS.md: refined non-negotiable #2 to "game logic is
unmodified" with a narrow ABI/layout exception; refreshed phase status.
- Replace setjmp/longjmp green threads with real pthreads (8MB stacks);
PortQueue side table (mutex+condvar) for OSMesgQueue; dedicated tick
thread posts VI retrace + services timers (D24).
- Dual-mapped N64 DRAM: one 8MB store at V1 0x70000000 (s32-safe, all game
RAM symbols) and V2 0x80000000 (KSEG0 mirror for offset|0x80000000
rebuilds). Windows: CreateFileMappingW + MapViewOfFileEx; POSIX: memfd
+ MAP_FIXED. dram_syms.s pins cfb_16/_bssSegmentEnd/tlb block (D25).
- Address-width shims: PHYS_TO_K0 identity, OS_K0_TO_PHYSICAL = offset from
V1 base, OS_PHYSICAL_TO_K0 identity; fast3d seg_addr() remaps small
offsets into V2 (D26).
- ASLR off (-Wl,--disable-dynamicbase): absolute symbols referenced through
pointer-typed externs (.refptr + BASE reloc) must not rebase; startup
sanity check on image base 0x140000000 (D27).
- osPiStartDma posts its completion message on every path (fixes
romReceiveMesg deadlock) (D29).
- SEH-free crash handler: raw fault log first, validated manual EBP-chain
walk second (-fno-omit-frame-pointer), TerminateProcess; crashDumpThreads
via Toolhelp32 for heartbeat hang dumps (D30).
- PD fast3d integrated (port/fast3d/), replaces gfxstub.c; real src/sched.c
compiled; mainproc runs as a real pthread, host thread pumps SDL events.
- Docs: findings D24-D31 + Phase 2 status (section F/G).
Current state: boots through bossInitMainthreadData to langInit(), then
SIGSEGVs on the first file load — stack frame clobbered by a table of
truncated s32 pointers (D31, open). Next: attribute the write with
'set scheduler-locking on' + hardware watchpoint.
- scripts/gen_romassets.py: emit absolute cart-address asset symbols
(0x10000000 + rom_offset) from filelist.<r>.csv + the asset .s files,
in file_resource_table.inc.c order. US region (romassets_u.s).
- romdata.c: load/validate the GE-specific header (magic, name @0x20,
cart ID @0x3C, country @0x3E) and map the .z64 at cart base 0x10000000
so every &asset-symbol is a live host pointer.
- libultra.c: osPiStartDma/osPiRawStartDma now service reads as
bounds-checked memcpys from the mapped ROM (synchronous completion).
- system.c: implement sysResolvePath ($S data dir, $E exe dir).
- video.c: real SDL2 window + GL context + clear/swap frame loop with
FPS accounting; main.c runs a Phase 1 demo loop (ESC quits) until the
software RSP/scheduler can drive mainproc().
- CMakeLists: project(... ASM), wire romassets_<region>.s, drop
assetstubs.c (replaced by generated symbols); n64stubs.c keeps only
pure-RAM segment markers.
- docs: findings D18-D23 (header offsets, BE music table, .rz tracks,
scanner-prefixed manifest names, CMake ASM gotcha, absolute-symbol
approach).
Verified: baserom.u.z64 (12MB) passes validation and maps at 0x10000000;
window opens and renders until killed; asset offsets spot-checked against
ROM bytes (music bank seqCount=63 BE, first track offset 508).