Files
goldeneye-pc-port/docs/BRIEF-C1-chr-airecord.md
T
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

123 lines
6.2 KiB
Markdown

# 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[]->chr` type-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.md` non-negotiables (#2: narrow `#ifdef PORT` ABI/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:
1. A per-level `Usetup*Z` guard record (`GuardRecord` type 9 /
`GuardAttributeRecord` type 18, or the chr-spawn record) has a pointer
or `s16/s16` packed field converted wrongly — or not at all — by
`tools_pc/d88_propdefs.py` (cf. D122: the "generic" fallback arm
word-swaps sub-word fields). Enumerate every propDef `type` byte the 6
crashing levels emit vs the 7 passing ones
(`tools_pc/d88_propdef_scan.py` histogram) — the delta type is the
suspect. The 7 pass / 6 fail split is the strongest lead.
2. The AI-list bytecode pointers in `chraidata.c` `.rodata`
(`SetChrAiList`, list-table entries) are BE and consumed raw — a chr
gets `aiList` set to a byteswapped pointer, then a subsequent tick
treats it as / derefs it as a `ChrRecord*`. Check `chraifunc.c` /
`chraidata.c` list dispatch and any `AiListRecord`-style table.
3. `ChrRecord` / `GuardRecord` struct is pointer-width-grown on x86-64
and a per-level record is read from ROM bytes at N64 offsets (D56/D57
class) — `self->actiontype` at 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 PORT` only).
- `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-gated `GE_C1` blocks.
- `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/*` or `src/game/propobj.c` (other tracks).
## KNOWN-GOOD / RULED OUT — do not re-investigate
- D121 per-level pool injection — works, committed.
- D122 propDef `obj` half-swap — committed `f2beae4b`; 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:393` crash — D117
nondeterminism, unrelated.
## PRE-FLIGHT ATTACHED
- `./build-pc.sh ntsc-final` green (`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 log
`ge007.crash.log` at 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`.
- **`timeout` does NOT kill the game on Windows.** Launch `... &`,
`sleep 24`, `taskkill //F //IM ge007.x86_64.exe`.
- gdb attach is fast: game running via `nohup ... &`, then
`gdb -batch -x cmds.txt -p <winpid>` (winpid = 4th col of `ps -p <bashpid>`).
Hardware watchpoint on the chr's `aiList` field, or on the bad `self`,
catches the corrupting write.
- PD analogue: check `PD_PORT_CHECKOUT` chr / 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
1. Root cause — `file:line` evidence, which hypothesis, why the 6 differ
from the 7 passing levels.
2. Fix — the diff, or why-not if unresolved.
3. Verification — `-level_33/35/26/22/29/41` each boot to ≥1
non-degenerate frame (`pixcount.py` > a few %), AND `-level_09` +
`-level_20` unregressed (`tools_pc/framediff.py`).
4. Probes left in the tree (env-gated, capped).
5. Confidence rating.
6. Generalisable quirk appended to `docs/PORT-LEARNINGS.md`.