Commit Graph
70 Commits
Author SHA1 Message Date
James Dansereau 710d0b12b5 docs: correct stale 'opaque passthrough' propDefs comment in d88_emit.py (D122/D123/D125) 2026-08-29 16:39:58 -04:00
James Dansereau 9d118e0b13 M-13: 21-level sweep matrix + D123 (C1 fix) + D124 (C2 Jungle fix)
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).
2026-08-29 16:36:18 -04:00
James Dansereau a8b174a829 D122: fix propDef obj half-swap for ObjectRecord-derived types
tools_pc/d88_propdefs.py had a per-type handler table plus a generic
fallback. Six `inherits ObjectRecord` propDef types (47 TINTED_GLASS,
39 VEHICHLE, 40 AIRCRAFT, 45 TANK, then 13 AUTOGUN, 20 AMMO/MultiAmmoCrate)
had no handler and fell through to the generic arm, which bswap32's N64
word 1. For an ObjectRecord that word is [s16 obj][s16 pad] and needs a
half-swap, not a word-swap - so the model id `obj` landed in the wrong
16 bits, giving a garbage/OOB index into PitemZ_entries[] and a crash in
modelLoad (loadobjectmodel.c:393) / modelInitRwData (model.c:6249).

BUNKER1/Silo were unaffected because neither emits any of the six types.

Fix (converter + matching walk stride, no game logic):
- d88_propdefs.py: OBJ_TAIL_DESC handler - real 144B PC ObjectRecord
  prefix + typed tail (pointers widened 4->8B/8-aligned, [u16|u16] words
  half-swapped). PROPDEF_PC_BYTES 39/40 176/180->208, 45 224->248.
- loadobjectmodel.c sizepropdef() #ifdef PORT arm: VEHICHLE/AIRCRAFT
  44/45->52, TANK 56->62 words (N64 values in the #else sizeof switch).

Verified: -level_33/34/35 clear the model-load chain (they now crash
further in, in unrelated chr/fast3d code - separate blockers);
-level_09 BUNKER1 and -level_20 Silo unregressed. Sidecars regenerated
(d88_emit.py --regen, 21/21). Full write-up: PCPortResearch.md D122
(uncommitted, rides with the in-progress docs restructure).
2026-08-29 15:26:22 -04:00
James Dansereau ffc3112037 D117: framediff.py visual-regression tool; root-cause frame nondeterminism
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.
2026-08-29 10:49:15 -04:00
James Dansereau 42eacc4aae tools_pc: D115 golden frame baseline for regression diffing (-level_09 BUNKER1) 2026-08-29 10:38:23 -04:00
James Dansereau 3a02ded079 D112: fix d43_emit put_f32 byte-swap off-by-one (skeletal models collapsed)
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.
2026-08-29 09:21:12 -04:00
James Dansereau 2f3bc74417 D88.6: intro CAMERA lang1c is a u16 pair, not an s32
SetupIntroCamera.lang1c is union { u16 lang_index[2]; u32 lang_ptr; }
and bondview_r.c reads lang1c.lang_index[1]. The intro converter
bswap32'd the whole word, which swaps element 0 and element 1 -- the
consumer then read the wrong language slot id, indexed g_LangBanks out
of range and crashed in langGet on a NULL bank. Byte-swap each u16 of
lang1c in place instead; lang20 stays a genuine s32 bswap32. Requires
d88_emit.py --regen.
2026-08-28 09:58:22 -04:00
James Dansereau 5dbe8a48c2 D88.5: byte-swap the stan tile id-hi half in the converter
stanMatchTileName reads a tile's packed name id through a StandTilePoint
alias: (u16)tile->x == stanIdHi and *((u8*)&tile->y) == stanIdLo. D78
left the id/room word as a verbatim byte array ("id is provably dead"),
which missed this aliased scalar read -- on little-endian PC the u16
load of the big-endian id-hi bytes came back byte-swapped, so every
name lookup during pad setup missed (0/276). Swap bytes 0-1 of the
id/room word in d69_emit's stan converter; idLo (byte 2) and room
(byte 3) stay put. Verified: 273/273 matches, pads now get real stan
tiles. Requires sidecar regen (d69_emit.py + d88_emit.py --regen).
2026-08-28 09:58:14 -04:00
James Dansereau 695f42d383 D88.4: emit converted propDefs into the Usetup*Z sidecar
d88_emit.py runs d88_propdefs.convert_stream over the propDefs region and
feeds its growth into the cumulative delta so intro + every later table
shifts correctly.
2026-08-28 09:16:13 -04:00
James Dansereau f2281ed661 D88.4: propDefs record format spec + N64->PC converter (d88_propdefs.py)
The propDefs stream is a flat s32[]; each record's serialized N64 word count
is fixed across all 21 levels (verified: the getools C sources tile every
propDefs region byte-for-byte against the ROM). Every pointer member is 0 in
the file, so records containing pointers grow 4->8B per pointer on PC.

d88_propdefs.py rewrites each record to its native PC struct layout: header
word (u16+u8+u8) and _mkword half-pairs swapped independently, scalars
bswap32'd, pointer slots widened to 8 zero bytes, runtime areas zero-filled.
d88_layoutprobe.c (expanded) is the compiler-verified sizeof/offsetof source
for PROPDEF_PC_BYTES. Converts + walk-verifies all 21 levels clean.
2026-08-28 09:16:05 -04:00
James Dansereau f333a713fd D88.4: d88_emit.py --regen flag + PC layout probe for propDef records
--regen drops all Usetup*Z rows (manifest + bytes) and rebuilds the sidecar
from the surviving d69 rows before re-converting, for clean converter
iteration. d88_layoutprobe.c prints sizeof/offsetof for every propDef record
struct against the real port include chain.
2026-08-28 09:04:46 -04:00
James Dansereau fbfa2f8231 D88.4: add propDefs region scanner (analysis helper, WIP)
Read-only tool to enumerate PROPDEF_* record types per Usetup*Z file.
Walk currently drifts on the sizeof()-derived record types (DOOR/GUARD/
PROP/etc) because their exact N64 word sizes aren't yet confirmed -- that
confirmation is the core remaining D88.4 spec task.
2026-08-28 08:33:31 -04:00
James Dansereau cca7d0bfad D88.2: Usetup*Z stage-setup offline converter (tools_pc/d88_emit.py)
Converts the 21 per-level Usetup*Z stage-setup files to PC layout,
appending rows to the same pccg.bin/manifest sidecar as d69_emit.py.
Delta-relocates the 8 pointer-widened sub-tables (header 40->80B,
waypoint/waygroup/PathRecord/AIListRecord/PadRecord/BoundPadRecord/pname)
and byte-swaps the s32 ID/offset fields. propDefs left as an opaque
relocated passthrough for now (D88.4).
2026-08-28 08:30:56 -04:00
James DansereauandClaude Sonnet 5 2acb7f6a42 D69.2: bg/stan offline converter (tools_pc/d69_emit.py)
Implements the D80-D81 format spec: size-preserving bswap of bg .seg
header/room/envdata tables, growth+relocation of the portal_data_entry
table (8B N64 -> 16B PC, offset_portal stays a real dereferenced pointer)
and its point-data blob (relocated by portal_delta, content size
unchanged), and the Tbg_*_stanZ prefix room-offset array (4B N64 -> 8B PC
slots, matching StanPrefixRecord's PC-compiled pointer alignment) plus
per-tile bswap of mid/tail halves and point coordinates. Round-trip
validated against BUNKER1 (bg/bg_sev_all_p.seg, Tbg_sev_all_p_stanZ):
header offsets, MaxNumRooms walk, portal targets/connectedRoom bytes, and
all 1066 stan tiles match the N64 source exactly.

Output: data/pccg-<region>/pccg.bin + manifest.csv (gitignored, same shape
as pcmodels.bin/D43). Regenerate: python tools_pc/d69_emit.py ntsc-final.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3xib6jjMV8o2iE4JfixAo
2026-08-27 21:02:02 -04:00
James Dansereau 0010270f1b Repo cleanup: reference footage to docs/reference/, ignore local artifacts
- Move the two N64 reference screenshots out of the repo root into
  docs/reference/ (n64-footage-2026-08-24.jpg, n64-footage-gun-barrel.png);
  HANDOFF.md now points there.
- Gitignore screenshots/ (session captures) and tools_pc/mupen64/ (local
  Mupen64 Plus checkout used for N64 reference footage; 8 MB of binaries).
- Commit tools_pc/mtxtest.c, the standalone fast3d matrix-convention
  harness that HANDOFF/D72.3 docs reference as kept-for-reference.
- Refresh PORTING.md: status was still 'scaffolding / building once
  implementation lands'; now reflects Phase 2 in progress and points at
  docs/HANDOFF.md for current session state.
2026-08-27 19:27:05 -04:00
James Dansereau 8f631462f3 PC port: phase 2 — D59-D68 milestone: full intro renders; blocker is D69 (BG-file BE headers)
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.
2026-08-24 03:27:29 -04:00
James Dansereau 330b2bae46 PC port: phase 2 — Plan B executed (D50): offline model sidecars + port plumbing; texCopyGdls w1 fix
- tools_pc/d43_emit.py: full emit pass, 512/512 round-trip validated;
  data/pcmodels-<region>/pcmodels.bin + manifest.csv (gitignored, regenerate)
- port/src/pcmodels.c(+h): cart-extension reservation, sidecar copy, one-shot
  file_resource_table patch; romdata.c/h extended
- objecthandler_2.c: #ifdef PORT hook — pcmodelsPatchTable() + poolRemaining=0
  reset (closes D48.3/R6 reload hazard)
- language.c/textrelated.c: runtime C fixups for lang-bank BE offset tables
  (romdataFixupLangBank) and font re-layout (romdataFontPcSize/FixupFont)
- front.c: legal-screen uninitialized-pointer UB seed (N64 survived by luck)
- tex.c (D50.6): texCopyGdls copied only w0 of each 16B Gfx slot on PC —
  compaction scratch kept stale mempool w1s -> garbage G_SETTIMG -> OOB in
  import_texture_rgba16. Full-slot copy under #ifdef PORT; byte-proven vs
  PlegalpageZ sidecar (w0s match, w1s stale). Model GDLs now execute.
2026-08-23 10:22:03 -04:00
James Dansereau 73ea03734d PC port: phase 2 — D48 review: re-plan D43 as offline pre-conversion (Plan B)
- 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
2026-08-22 18:43:04 -04:00
James Dansereau bd56cff073 PC port: phase 1.5 — ROM dump verification groundwork
No-Intro 'GoldenEye 007 (U) [!].z64' placed in data/ and verified
byte-identical (MD5 70c52588...) to the dev ROM copies
(data/ge007.ntsc-final.z64, baserom.u.z64). Header magic/title/country
check out; embedded CRCs DCBC50D1/09FD1AA3 re-computed and matched via
tools/n64cksum.c's CIC-6102 routine.

Adds tools_pc/romverify.c: one-shot integrity check (size, GE header
magic at 0x00, title, country byte, CRC1/CRC2) so any future ROM swap
is detectable in seconds. Fingerprint + provenance recorded in
docs/HANDOFF.md; gitignore the built romverify.exe.
2026-08-21 20:29:03 -04:00
James Dansereau 8482ba3ed1 PC port: Phase 1.5 — root-cause D32 Part B (D33): ROM big-endian
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.
2026-08-21 20:18:38 -04:00