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).
6.2 KiB
BRIEF C1 — chrIsNotDeadOrShot crash on 6 levels (blocks the level sweep)
Repo: REPO_ROOT (git, branch master, Windows;
PowerShell + Bash tool).
READ FIRST
docs/HANDOFF.md(top) — state, environment, repro line.docs/PORT-LEARNINGS.md— §A (pointer-width struct growth) and §C (BE rodata on LE) are the prime suspects. Also §A D119 (weapons_held[]->chrtype-pun) — closest prior instance.docs/PCPortResearch.md§F entries D119, D115, D101, D102, D56, D57 (jump via the index — do NOT linear-read).docs/AUDIT-M6-player-offsets.md— the raw-offset landmine class.AGENTS.mdnon-negotiables (#2: narrow#ifdef PORTABI/layout only, N64 line kept under#else, logged in §F).
SYMPTOM (WS4 sweep, this session — docs/LEVEL-STATUS.md)
./build-pc/ge007.x86_64.exe -level_XX (bare — pools auto-injected, D121),
crashes early, before frame 1, deterministically, at:
chrIsNotDeadOrShot src/game/chraction.c:4483 s8 currentaction = self->actiontype;
EXCEPTION 0xc0000005, FAULT ADDR 0x8 (Dam). Crash self = the same
value as backtrace frame #1 = 0x1401296a0 — an address inside the
compiled binary's rodata, in the chraidata.c global-AI-list blob
(m_AimAtBond[] / m_*[] at chraidata.c:61+). So a ChrRecord *self
is pointing at AI-list bytecode rodata — a pointer swap / pun / wrong
struct-offset read, not mere corruption.
Affected: Dam 33, Runway 35, Frigate 26, Statue 22, Streets 29, Cradle 41 (6). NOT Bunker1 09 / Silo 20 / Archives 24 / Train 25 / Caverns 39 / Egypt 32 / Cuba 54 (those 7 PASS, render ~91%).
TASK
One question: why is a ChrRecord* actually an AI-list rodata pointer
on these 6 levels, and what is the narrow fix?
Leading hypotheses:
- A per-level
Usetup*Zguard record (GuardRecordtype 9 /GuardAttributeRecordtype 18, or the chr-spawn record) has a pointer ors16/s16packed field converted wrongly — or not at all — bytools_pc/d88_propdefs.py(cf. D122: the "generic" fallback arm word-swaps sub-word fields). Enumerate every propDeftypebyte the 6 crashing levels emit vs the 7 passing ones (tools_pc/d88_propdef_scan.pyhistogram) — the delta type is the suspect. The 7 pass / 6 fail split is the strongest lead. - The AI-list bytecode pointers in
chraidata.c.rodata(SetChrAiList, list-table entries) are BE and consumed raw — a chr getsaiListset to a byteswapped pointer, then a subsequent tick treats it as / derefs it as aChrRecord*. Checkchraifunc.c/chraidata.clist dispatch and anyAiListRecord-style table. ChrRecord/GuardRecordstruct is pointer-width-grown on x86-64 and a per-level record is read from ROM bytes at N64 offsets (D56/D57 class) —self->actiontypeat the wrong byte.
Determine which. Prefer an offline converter extension
(d88_propdefs.py / d88_emit.py, D43/D88 pattern) over a runtime fixup;
else a narrow #ifdef PORT ABI edit.
FILES YOU MAY TOUCH
src/game/chraction.c,src/game/chr.c,src/game/chraidata.c,src/game/chraifunc.c(narrow#ifdef PORTonly).tools_pc/d88_propdefs.py,tools_pc/d88_emit.py,tools_pc/d88_layoutprobe.c,tools_pc/d88_propdef_scan.py.- New probe files
tools_pc/c1_*.py/ env-gatedGE_C1blocks. docs/PORT-LEARNINGS.md(append),docs/PCPortResearch.md§F (write up as the next Dxx after D122 — check the §F index for the current max — add to the index),docs/LEVEL-STATUS.md(flip the rows you fix).- NOT
port/fast3d/*orsrc/game/propobj.c(other tracks).
KNOWN-GOOD / RULED OUT — do not re-investigate
- D121 per-level pool injection — works, committed.
- D122 propDef
objhalf-swap — committedf2beae4b; your bug is a different record type/field (BUNKER1/Silo unaffected). - BUNKER1 + Silo + Archives + Train + Caverns + Egypt + Cuba chr/AI setup — works. Use them as the "good" control for the propDef-type diff.
- The intermittent BUNKER1
loadobjectmodel.c:393crash — D117 nondeterminism, unrelated.
PRE-FLIGHT ATTACHED
./build-pc.sh ntsc-finalgreen (export PATH="/c/msys64/mingw64/bin:$PATH"first).- Sidecars current for
f2beae4b. Regen 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 - Repro:
./build-pc/ge007.x86_64.exe -level_33(Dam). Crash logge007.crash.logat repo root. Symbolicate:addr2line -e build-pc/ge007.x86_64.exe -f -C 0x<PC>(PATH first; image base 0x140000000, PC in log is absolute). - Frame capture
GE_PCDUMP="80-260:40"→./ppm/;python tools_pc/pixcount.py ppm/<last>.ppm. timeoutdoes NOT kill the game on Windows. Launch... &,sleep 24,taskkill //F //IM ge007.x86_64.exe.- gdb attach is fast: game running via
nohup ... &, thengdb -batch -x cmds.txt -p <winpid>(winpid = 4th col ofps -p <bashpid>). Hardware watchpoint on the chr'saiListfield, or on the badself, catches the corrupting write. - PD analogue: check
PD_PORT_CHECKOUTchr / guard setup + its propdef converter before writing anything new.
BUDGET
Structural → ~12 build→run→inspect cycles or ~90 min. On expiry: revert temp probes (leave only committed env-gated ones), write up spec progress + best hypothesis in §F with an explicit confidence rating, list every file you touched. A good half-solved write-up is a deliverable.
CONSTRAINTS
No game-logic / control-flow changes. ABI/layout/format only, each
#ifdef PORT with the N64 line verbatim under #else, each documented in
§F. Prefer converter extension over runtime fixup. Revert your own temp
probes before reporting. Do not touch other tracks' uncommitted edits
(none expected).
REPORT
- Root cause —
file:lineevidence, which hypothesis, why the 6 differ from the 7 passing levels. - Fix — the diff, or why-not if unresolved.
- Verification —
-level_33/35/26/22/29/41each boot to ≥1 non-degenerate frame (pixcount.py> a few %), AND-level_09+-level_20unregressed (tools_pc/framediff.py). - Probes left in the tree (env-gated, capped).
- Confidence rating.
- Generalisable quirk appended to
docs/PORT-LEARNINGS.md.