forked from Ports/goldeneye-pc-port
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).
This commit is contained in:
1 parent
a8b174a829
commit
9d118e0b13
20 files changed
+2058
-493
No files matched your search
@@ -4,7 +4,7 @@
|
||||
|
||||
- `n64decomp/007`: WIP decompilation of GoldenEye 007 (N64), byte-matches US/EU/JP ROMs.
|
||||
- Active work: **PC port** modelled on the Perfect Dark PC port (same Rare "Indy" engine family).
|
||||
- **Read `docs/PCPortResearch.md` first** — architecture, GE-specific RSP deltas, phased plan, and review findings (A1–D33 in §11; current handoff + plan in §H).
|
||||
- **`docs/PCPortResearch.md` is the reference** — architecture, GE-specific RSP deltas, phased plan (§1–§10), and the Dxx finding log (§F + §H, indexed at the top of §F). Read the section you need; don't linear-read it. Current task lives in `docs/HANDOFF.md`.
|
||||
- **Starting a fresh session?** Read `docs/HANDOFF.md` — the paste-ready brief with the immediate task, standing procedure, and environment gotchas.
|
||||
- **Dispatching subagents?** `docs/AGENT-WORKFLOW.md` — task budgets/deadlines, file partitioning, pre-flight, the standard brief template. Every investigation subagent reads `docs/PORT-LEARNINGS.md` (recurring bug classes) first and appends to it.
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `docs/PCPortResearch.md` | Research + plan + findings (A1–C2). Read first. |
|
||||
| `docs/PCPortResearch.md` | Research + plan (§1–§10) + Dxx finding log (§F/§H, indexed at top of §F). Reference, not a linear read. |
|
||||
| `CMakeLists.txt` | PC build (parallel to the N64 Makefile). Source list + `REGION_DEFS` live here. |
|
||||
| `port/src/` | Shims: `libultra.c` (OS API), `gesched.c` (scheduler), `n64stubs.c` (boot/TLB/FPU/rmon), `random.c` (PRNG ported verbatim from `random.s`), `ucode.c` (microcode segment markers), `main.c`, `video.c`, … |
|
||||
| `port/fast3d/` | Software RSP (adapted from the PD port). The main Phase 2 work. |
|
||||
@@ -41,7 +41,7 @@ Needs CMake + SDL2 + zlib + OpenGL (MSYS2 packages: see `build-pc.sh` header). R
|
||||
2. **Duplicates.** No symbol defined twice across the compiled set (watch `sp_*` stacks, `rmon*`, `os*` shims, segment markers).
|
||||
3. **Syntax.** Every touched file must parse; once the toolchain is installed, `./build-pc.sh` is the final word.
|
||||
|
||||
Run `/linkcheck` for this sweep. Record new findings in `docs/PCPortResearch.md` §11 style (next label after the last used).
|
||||
Run `/linkcheck` for this sweep. Record new findings in `docs/PCPortResearch.md` §F/§H style (next Dxx label after the last used) and add the label to the §F index.
|
||||
|
||||
## Phase status
|
||||
|
||||
@@ -49,6 +49,22 @@ Run `/linkcheck` for this sweep. Record new findings in `docs/PCPortResearch.md`
|
||||
- **Compile+link milestone:** all ~235 TUs compile and `ge007.x86_64.exe` links clean (`ninja ge007 -k 0`, 236/236). Findings D7–D17 in `docs/PCPortResearch.md` §11; asset stubs in `port/src/assetstubs.c`, fast3d no-ops in `port/src/gfxstub.c` (delete when real fast3d lands).
|
||||
- **Phase 1 (boot to window):** done — ROM loads/validates/maps at cart base 0x10000000 (`romdata.c`), SDL2 window + GL clear loop (`video.c`), absolute asset symbols from `scripts/gen_romassets.py` → `port/src/romassets_<r>.s` (findings D18–D23).
|
||||
- **Phase 1.5 (boot to first frame):** DONE — boot chain complete AND real frames render (`[NOTE] frame N rendered in … us`). D34–D42 closed the blockers on top of D31–D33: ANIM_DATA lvalues, music seq-table ABI+endianness, music-heap sizing, libaudio bank-tree re-layout, implicit-declaration prototype shim (D38), Globalimagetable rebasing + true segment size (D39), ModelHitEntry pool sizing (D40), cross-thread GL context release (D41), rsp task-settings toggle (D42). ROM dump verified against the No-Intro good dump (byte-identical, header CRCs re-checked — `tools_pc/romverify.c`).
|
||||
- **Phase 2 (rendering):** IN PROGRESS — fast3d integrated + real frames render. **Plan B EXECUTED (D50, committed):** all 512 NTSC model files converted offline by `tools_pc/d43_emit.py` → `data/pcmodels-ntsc-final/pcmodels.bin` + manifest.csv (single concatenated RZ image; regenerate per region with the script — sidecars are gitignored), served through the existing load path via a port-layer table patch (`port/src/pcmodels.c`, romdata cart-extension, one-shot `pcmodelsPatchTable()` + poolRemaining reset hook in `load_object_fill_header`); runtime C fixups for language-bank BE offset tables + font re-layout (D50.3/D50.4); legal-screen UB seed (D50.5); **D50.6 fixed the first model-render crash** — `texCopyGdls` copied only w0 of each 16-byte Gfx slot on PC (the union's trailing `long long` is 8 bytes), leaving stale mempool w1s in the compacted GDLs; byte-proven against PlegalpageZ, fixed with a full-slot copy under #ifdef PORT. Model GDLs now execute to completion (PlegalpageZ sub-DLs). **D51 RESOLVED** (font `pixeldata` fixup wrote the pointer at the wrong offsets — frame-5 `import_texture_i8` crash gone; GE opcode space decoded along the way, G_TEXRECT=0xE4/0xE5 non-F3DEX numbering), **D52 RESOLVED** (`osGetCount` now advances at the N64 RSP counter rate ≈ 46.5525 ticks/µs — fixed the non-deterministic post-frame-2 hang), **D53 RESOLVED** (model RW-data pool addressing: D53.1 `Model.datas` word-stride → `u32 *` under PORT + casts; D53.2 ModelSlot/AnimModelSlot re-laid on top of the PC struct Model — in-use@0x10 / pool@0x20, slot size = sizeof(Model) = 0xE8 — plus PORT-guarded heap-fallback bumps in model.c), **D54 RESOLVED** (cseq ALCMidiHdr BE header: port-layer `romdataFixupCseq` bswap32 of the first 17 u32s, called from music.c after each decompress), **D54b RESOLVED** (synth param slots sized for ALStartParamAlt = 0x28 in synthesizer.c alSynInit under PORT), **D55 RESOLVED** (RLE folder-menu background BE w/h header: bswap32 of the first word at `(u32 *)&unknown2` at cart-map time in romdataInit, guarded). Build GREEN; game boots, plays intro music, renders through logo/gun-barrel, instantiates Brosnan, and executes the gun-barrel hole DL clean to ENDDL. **D56 RESOLVED** (watch Model/RW-pool raw offsets: field-name Model access + static 0xC8-byte pool under PORT in bondview2.c), **D57 RESOLVED** (pointer-grown rwdata records overflowed N64-sized spare pools — Brosnan PC numRecords 153 > 140: capacities grown in initunk_005520.c + dynamic slot+pool fallback in model.c), **D58 RESOLVED** (gun-barrel DL: vertex-pointer K0 idiom rebuilt via `OS_K0_TO_PHYSICAL(…) | 0x80000000` in title.c + DL reserve 0x100→0x200 because `sizeof(Gfx)` is 16 on PC). **D59–D68 RESOLVED (committed):** the "GL DLL" crash was D64's unbounded blood-RLE write (sentinel section-adjacency lost on PC) clobbering the gun-barrel sub-DL; then D60–D62 (DMA target validation, OSMesgQueue layout), D65/D65b (HEADS/BODIES enum signed sentinels under PORT), D66 (romCopyAligned + ramrom replay 64-bit pointer width), D67 (struct image_entry N64 layout), **D68 (Globalimagetable BE→LE fixup** — `port/src/gimgfixup.c` bswaps IMAGESEG Gfx w1 words + sImageTableEntry.index after texReset's romCopy; compiled globalDL shadows synced for explosion.c). The game now renders the **entire intro** (logo → gun barrel w/ Brosnan → cast; ~frame 2100, ≈2 min wall-clock at the current ~20 fps clean-run rate). **D69 RESOLVED (committed):** `load_bg_file` no longer faults on stage load. BG-file headers/records are N64 big-endian segment-0x0F offsets with internal self-relative offsets; fixed via offline sidecar conversion (`tools_pc/d69_emit.py` → `data/pccg-<region>/pccg.bin` + manifest, same Plan-B pattern as D43/pcmodels, port-layer wiring in `port/src/pccg.c`) plus four ABI layout fixes surfaced along the way: D78 (`StandTile` id/room bitfield packing), D79 (`bg_room_data` pointer-width), D83 (`StandTileHeaderMid`/`Tail` bitfield packing — same class as D78, caused an infinite loop in `stanBuildRoomData` until fixed), D84 (hand-inlined `BG_SEG_TO_PTR`-style folds in room-DL loaders needed the same `(u32)`-cast 32-bit wrap the macro already has). **D85 OPEN, D86/D87 RESOLVED, D88 OPEN (current blocker) — found during/after D69 verification:** D85 — room primary/secondary DL binaries decompress correctly but `texLoadFromGdl` (the already-working model-GDL runtime converter) produces garbage from their content, not yet root-caused; safety-netted (bounds-checked in `bgBuildRoomVtxBounds` so it can't segfault, but geometry is wrong/empty). D86 (RESOLVED) — `modelInitRwData` crash was a single `(int)`-truncated pointer in `initplayergaitobject.c`'s embedded player gait-model init; fixed under `#ifdef PORT`. D87 (RESOLVED) — the front end's genuine attract-mode demo playback crashed on an unswapped big-endian `ramromfilestructure`; fixed with a `#ifdef PORT` byteswap fixup in `ramromreplay.c` (D54-cseq pattern).Line truncated
|
||||
- **Phase 3 (audio + input):** libaudio → SDL — largely copy-and-adapt from PD (`mixer.c` 722 lines vs our 31-line stub; `input.c` SDL_GameController backend; §2.4); decide the ASP strategy (C2: `aspMain*` dummies in `ucode.c`).
|
||||
- **Phase 2 (rendering):** IN PROGRESS. fast3d integrated; the **entire
|
||||
intro renders** (logos → gun barrel → cast). BUNKER1 (`-level_09`)
|
||||
**loads, renders recognisably** (textured rooms, skeletal guards) and
|
||||
**survives a firefight** crash-free. Now pivoted **breadth-first** —
|
||||
see `docs/PLAN-linear-level-sweep.md` (the plan of record): make
|
||||
boot→menu→level-select functional, then sweep all 21 solo levels for
|
||||
load+render+no-crash.
|
||||
- **Cross-level blocker:** D88.4 — `Usetup*Z` `propDefs` record stream
|
||||
not byteswapped by `tools_pc/d88_emit.py` (crashes in `setupDoor`).
|
||||
- **Cosmetic defects are parked** in `docs/GRAPHICS-BACKLOG.md`
|
||||
(D75 3D model transforms, D76 disclaimer, D77 audio, D114/D116 text
|
||||
X-mirror). Below crash/level work.
|
||||
- **Recurring sub-task:** ABI/layout fixes per the D32 procedure
|
||||
(§H) as new asset types are touched — the classes are catalogued in
|
||||
`docs/PORT-LEARNINGS.md`.
|
||||
- Full finding history: `docs/PCPortResearch.md` §F + §H (D31–D121,
|
||||
indexed at top of §F); session narrative in `docs/HANDOFF-ARCHIVE.md`.
|
||||
- **Phase 3 (audio + input):** input layer DONE (D118 — `port/src/input.c`, SDL keyboard/mouse/gamepad → N64 pad; polish bugs D118a/b open). Audio remains: libaudio → SDL, copy-and-adapt from PD (`mixer.c` 722 lines vs our 31-line stub; §2.4); decide the ASP strategy (C2: `aspMain*` dummies in `ucode.c`).
|
||||
- **Phase 4 (saves + polish):** file-backed EEPROM — build on PD's `fs.c` (file-backed save dir); PFS/motor already no-op stubs.
|
||||
@@ -6,13 +6,34 @@ checklists that were being skipped.
|
||||
|
||||
@AGENTS.md
|
||||
|
||||
## Doc load order — read deliberately, not everything
|
||||
|
||||
Context is scarce. Load by tier; do not blind-read whole files.
|
||||
|
||||
- **Tier 0 — always (auto-loaded):** `CLAUDE.md`, `AGENTS.md`. Nothing
|
||||
else auto-loads.
|
||||
- **Tier 1 — every session start, read fully (both are short):**
|
||||
`docs/HANDOFF.md` (current state + next task + environment),
|
||||
`docs/PORT-LEARNINGS.md` (recurring bug classes).
|
||||
- **Tier 2 — on demand only, do NOT read start-to-finish:**
|
||||
- `docs/PCPortResearch.md` §1–§10 — architecture / RSP deltas / phased
|
||||
plan. Read the section you need.
|
||||
- `docs/PCPortResearch.md` §F + §H — the Dxx finding log. **Jump to a
|
||||
specific `Dxx` via the index at the top of §F.** Never linear-read.
|
||||
- `docs/HANDOFF-ARCHIVE.md` — prior-session narrative (M-2…). Only when
|
||||
tracing the history of one fix.
|
||||
- `docs/AGENT-WORKFLOW.md` — before dispatching a subagent (also Tier 1
|
||||
if you may dispatch this session).
|
||||
- `docs/PLAN-linear-level-sweep.md` — the active plan of record.
|
||||
|
||||
## Session preflight — do this in your FIRST few tool calls, before acting
|
||||
|
||||
Having a concrete "next step" from the handoff is NOT license to skip this.
|
||||
|
||||
1. Read `docs/HANDOFF.md` — immediate task, standing procedure, environment gotchas.
|
||||
2. Read `docs/PORT-LEARNINGS.md` — recurring bug classes (you will re-derive
|
||||
bugs already catalogued here if you don't).
|
||||
1. Read the Tier 1 docs: `docs/HANDOFF.md` (immediate task, standing
|
||||
procedure, environment gotchas) and `docs/PORT-LEARNINGS.md`
|
||||
(recurring bug classes — you will re-derive catalogued bugs if you don't).
|
||||
2. Skim `docs/PLAN-linear-level-sweep.md` — the active plan of record.
|
||||
3. If you may dispatch a subagent this session, read `docs/AGENT-WORKFLOW.md`
|
||||
NOW — not at dispatch time.
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
All of these items should be completed once the main original game is playable from start to finish on PC with no crashing or major issues.
|
||||
|
||||
PC graphics settings - higher resolutions, anti aliasing, etc. Any rendering improvements but make them all optional, use Nightdive remasters or things like the Quake 1 remaster as a reference of offered features.
|
||||
|
||||
Co Op - find a way to implement local co op for the game on the singleplayer levels. The game already has local split screen multiplayer, but we'd want to extend netplay at least for LAN so that two PCs could run the port and play co op together.
|
||||
|
||||
Deathmatch - use the co op netplay to make deathmatch / regular multiplayer playable across 4 instances of the PC port on a local network
|
||||
@@ -0,0 +1,122 @@
|
||||
# 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`.
|
||||
@@ -0,0 +1,81 @@
|
||||
# BRIEF C2 — fast3d bad texture pointer on Facility + Jungle
|
||||
|
||||
Repo: `REPO_ROOT` (git, `master`, Windows;
|
||||
PowerShell + Bash).
|
||||
|
||||
## READ FIRST
|
||||
- `docs/HANDOFF.md` (top), `docs/PORT-LEARNINGS.md` (§C BE rodata, §D N64
|
||||
hardware idioms fast3d doesn't emulate).
|
||||
- `docs/PCPortResearch.md` §F entries **D69, D68, D39, D67, D107, D84,
|
||||
D58** (texture / segment-address history — jump via §F index).
|
||||
- `AGENTS.md` non-negotiables. `rsp/graphics/gmain.s` = GBI ground truth.
|
||||
- PD analogue: `PD_PORT_CHECKOUT` `port/fast3d/`
|
||||
texture import path — check first.
|
||||
|
||||
## SYMPTOM (WS4 sweep — `docs/LEVEL-STATUS.md`)
|
||||
Bare `./build-pc/ge007.x86_64.exe -level_XX`, early deterministic crash:
|
||||
|
||||
| Level | # | Site | Fault addr |
|
||||
|---|---|---|---|
|
||||
| Facility | 34 | `import_texture_i8` `port/fast3d/gfx_pc.cpp:821` | `0x72181ee8` (looks like an unresolved N64 segment addr) |
|
||||
| Jungle | 37 | `gfx_tex_normalize_source` `port/fast3d/gfx_pc.cpp:644` | `0xabcd0824` (sentinel / uninit value) |
|
||||
|
||||
fast3d is handed a garbage texture address. 7 levels render fine
|
||||
(Bunker1/Silo/Archives/Train/Caverns/Egypt/Cuba) so the common path is
|
||||
sound — Facility/Jungle pull in a texture asset or a GBI texture-image
|
||||
segment address the others don't.
|
||||
|
||||
## TASK
|
||||
Root-cause both (likely the same class) and apply the narrowest fix.
|
||||
Hypotheses:
|
||||
1. A per-level texture asset mis-converted / missing —
|
||||
`tools_pc/d69_emit.py` (pccg path) or `port/src/pccg.c`. `0xabcd....`
|
||||
is a classic uninit-slot sentinel — a texture slot that was never
|
||||
filled because the converter skipped a format/opcode.
|
||||
2. A GBI `gsDPSetTextureImage` segment address not resolved to a real
|
||||
pointer before `import_texture_*` (segment-fold class — D58/D84;
|
||||
`(u32)` 32-bit wrap on `BG_SEG_TO_PTR` / K0 folds). `0x72181ee8` is in
|
||||
the N64 segment-mapped range.
|
||||
3. A texture LOD / detail-tile path (D107) hit only by these levels.
|
||||
|
||||
## FILES YOU MAY TOUCH
|
||||
- `port/fast3d/gfx_pc.cpp` (narrow — a resolve/guard at the import site).
|
||||
- `tools_pc/d69_emit.py` (+ `tools_pc/d69_*` helpers), `port/src/pccg.c`,
|
||||
`port/src/romdata*.c` (segment resolution).
|
||||
- New probe files `tools_pc/c2_*` / env-gated `GE_C2` blocks.
|
||||
- `docs/PORT-LEARNINGS.md` (append), `docs/PCPortResearch.md` §F (next Dxx
|
||||
after the current §F-index max; add to index), `docs/LEVEL-STATUS.md`.
|
||||
- **NOT** `src/game/chr*.c` or `src/game/propobj.c` (other tracks).
|
||||
|
||||
## KNOWN-GOOD / RULED OUT
|
||||
- D121 pool injection, D122 propDef fix — committed, work.
|
||||
- 7 levels render clean — the base texture path is fine.
|
||||
- HUD/text X-mirror (D114/D116) — parked, do NOT re-trace (PORT-LEARNINGS §D2).
|
||||
|
||||
## PRE-FLIGHT
|
||||
- `export PATH="/c/msys64/mingw64/bin:$PATH" && ./build-pc.sh ntsc-final` — green.
|
||||
- Repro `./build-pc/ge007.x86_64.exe -level_34`. Crash log
|
||||
`ge007.crash.log`. `addr2line -e build-pc/ge007.x86_64.exe -f -C 0x<PC>`.
|
||||
- `GE_PCDUMP="80-260:40"` → `./ppm/`; `python tools_pc/pixcount.py`.
|
||||
- `timeout` does NOT kill on Windows: `... &`, `sleep 24`,
|
||||
`taskkill //F //IM ge007.x86_64.exe`.
|
||||
- Regen sidecars after 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`.
|
||||
|
||||
## BUDGET
|
||||
**~10 build→run→inspect cycles or ~75 min.** On expiry: revert probes,
|
||||
write up in §F with confidence rating + files touched.
|
||||
|
||||
## CONSTRAINTS
|
||||
No game-logic changes; ABI/layout/format / port-layer only. Prefer
|
||||
converter fix over runtime. Revert temp probes. Don't touch other tracks'
|
||||
uncommitted edits.
|
||||
|
||||
## REPORT
|
||||
1. Root cause + `file:line` evidence, why Facility/Jungle differ.
|
||||
2. Fix diff or why-not.
|
||||
3. Verification: `-level_34` + `-level_37` boot to ≥1 non-degenerate
|
||||
frame; `-level_09` + `-level_20` unregressed (`framediff.py`).
|
||||
4. Probes left in tree.
|
||||
5. Confidence.
|
||||
6. Quirk appended to `docs/PORT-LEARNINGS.md`.
|
||||
@@ -0,0 +1,95 @@
|
||||
# BRIEF C2-GDL — model/prop GDL runtime relocation misaligns dst (Facility, Runway)
|
||||
|
||||
Repo: `REPO_ROOT` (git `master`, Windows).
|
||||
|
||||
## READ FIRST
|
||||
- `docs/HANDOFF.md` top, `docs/PORT-LEARNINGS.md` **§B (16-byte PC Gfx vs
|
||||
8-byte N64 — this is the class)** + §D.
|
||||
- `docs/PCPortResearch.md` §F **D124** (the C2 split — this brief is the
|
||||
unfinished Facility half), **D80, D82, D83, D85, D95, D58, D50.6**
|
||||
(Gfx-stride / GDL history — jump via §F index).
|
||||
- `AGENTS.md` non-negotiables. `rsp/graphics/gmain.s` = GBI ground truth.
|
||||
- PD analogue: `PD_PORT_CHECKOUT` — its model-GDL /
|
||||
texture relocation path. Check before writing anything.
|
||||
|
||||
## SYMPTOM (WS4 re-sweep — `docs/LEVEL-STATUS.md`)
|
||||
Facility `-level_34` (fault 0x72181ee8) and Runway `-level_35` (fault
|
||||
0x721b8ee8) both crash in `import_texture_i8` (`port/fast3d/gfx_pc.cpp:821`)
|
||||
— fast3d fed a garbage `G_SETTIMG` w1.
|
||||
|
||||
Per the D124 investigation: the bad `G_SETTIMG` (`fmt=4 siz=2 w=0`,
|
||||
preceded by an unconsumed `0xba` GE tex-macro opcode) sits in a
|
||||
model/prop GDL relocated at runtime by `texLoadFromGdl()` via
|
||||
`sub_GAME_7F0762E0` (`objecthandler_2.c:82`) — NOT a room GDL, NOT the
|
||||
global bank. A `GE_C2` probe showed `texLoadFromGdl` writing to
|
||||
**non-16-byte-aligned `dst`** (`0x701eac01`, `0x701eb7d5`, …): the
|
||||
`replacementgdl` / `name`(=srcsize) offset arithmetic still mixes N64
|
||||
8-byte and PC 16-byte `Gfx` strides, so converted commands land mid-slot
|
||||
and a subsequent `G_SETTIMG` w1 reads as garbage.
|
||||
|
||||
12 levels PASS — they either don't hit this model-GDL path or hit it with
|
||||
a GDL whose misalignment happens to be benign.
|
||||
|
||||
## TASK
|
||||
Pin the exact stride/offset error in the model-GDL runtime relocation and
|
||||
fix it narrowly (`#ifdef PORT`, N64 line under `#else`, §F-documented).
|
||||
Likely spots:
|
||||
- `src/game/tex.c` `texLoadFromGdl()` — src/dst pointer advance per Gfx.
|
||||
- `src/game/objecthandler_2.c` `sub_GAME_7F0762E0` — `replacementgdl` /
|
||||
`srcsize` math, GDL buffer sizing.
|
||||
- `src/game/model.c` `modelNodeReplaceGdl` / rwdata GDL slot stride.
|
||||
- The `0xba` (`G_GEGBIMACRO_*` / GE tex-load macro) decode — fast3d may
|
||||
need to consume its extra word (cf. gmain.s).
|
||||
|
||||
Use the existing `GE_D86` / model-node probes and add a capped `GE_C2GDL`
|
||||
probe if needed.
|
||||
|
||||
## FILES YOU MAY TOUCH
|
||||
`src/game/tex.c`, `src/game/objecthandler_2.c`, `src/game/model.c`
|
||||
(narrow `#ifdef PORT`); `port/fast3d/gfx_pc.cpp` (only a guard / the 0xba
|
||||
consume, if that's the cause); new `tools_pc/c2gdl_*` probes;
|
||||
`docs/PORT-LEARNINGS.md`, `docs/PCPortResearch.md` §F (extend D124 or new
|
||||
Dxx), `docs/LEVEL-STATUS.md`.
|
||||
**NOT** `src/game/propobj.c` / `prop.c` / `tools_pc/d88*` (C3 agent owns
|
||||
those), **NOT** `port/src/gimgfixup.c` (D124-Jungle, done).
|
||||
|
||||
## KNOWN-GOOD / RULED OUT
|
||||
- D123 (C1), D124-Jungle (`gimgfixup.c`) — landed, work.
|
||||
- 12 levels PASS — the common GDL path is fine.
|
||||
- Global-bank / explosion-DL sync (D124) — separate, fixed.
|
||||
- HUD/text X-mirror (D114/D116) — parked, do NOT re-trace.
|
||||
|
||||
## PRE-FLIGHT
|
||||
- `export PATH="/c/msys64/mingw64/bin:$PATH" && ./build-pc.sh ntsc-final` — green.
|
||||
- Repro `./build-pc/ge007.x86_64.exe -level_34`. `ge007.crash.log`;
|
||||
`addr2line -e build-pc/ge007.x86_64.exe -f -C 0x<PC>`.
|
||||
- `GE_PCDUMP="80-260:40"` → `./ppm/`; `python tools_pc/pixcount.py`.
|
||||
- `timeout` does NOT kill on Windows: `... &`, `sleep 24`,
|
||||
`taskkill //F //IM ge007.x86_64.exe`.
|
||||
- gdb attach fast (`gdb -batch -x cmds.txt -p <winpid>`). HW watchpoint on
|
||||
the `dst` cursor in `texLoadFromGdl` catches the mis-advance.
|
||||
- Regen sidecars after any converter change (none expected here):
|
||||
`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`.
|
||||
|
||||
## BUDGET
|
||||
Structural / shared-infra → **~14 build→run→inspect cycles or ~2 h.** On
|
||||
expiry: revert temp probes, write up the stride spec + best hypothesis in
|
||||
§F with a confidence rating + files touched. A good half-solved write-up
|
||||
is a deliverable.
|
||||
|
||||
## CONSTRAINTS
|
||||
No game-logic / control-flow changes. ABI/layout/format only, `#ifdef
|
||||
PORT` with N64 line under `#else`, §F-documented. Revert your own probes.
|
||||
Don't touch the C3 agent's files.
|
||||
|
||||
## REPORT
|
||||
1. Root cause — `file:line`, the exact stride/offset bug, why 12 levels
|
||||
are unaffected.
|
||||
2. Fix diff or why-not.
|
||||
3. Verification — `-level_34` + `-level_35` boot to ≥1 non-degenerate
|
||||
frame (`pixcount.py` > a few %); `-level_09` + `-level_20` unregressed
|
||||
(`framediff.py`); re-check Jungle `-level_37` + Streets `-level_29`
|
||||
(may improve).
|
||||
4. Probes left in tree.
|
||||
5. Confidence rating.
|
||||
6. Generalisable quirk appended to `docs/PORT-LEARNINGS.md`.
|
||||
@@ -0,0 +1,62 @@
|
||||
# BRIEF C3+C6 — prop/door/item model-load crashes (Aztec, Bunker2, Surface2)
|
||||
|
||||
Dispatch AFTER C1 lands (shares `tools_pc/d88_propdefs.py` +
|
||||
`src/game/prop*.c` with C1 — do not run concurrently with C1).
|
||||
|
||||
Repo: `REPO_ROOT` (git `master`, Windows).
|
||||
|
||||
## READ FIRST
|
||||
- `docs/HANDOFF.md` top, `docs/PORT-LEARNINGS.md` §A + §C (esp. the D122
|
||||
"polymorphic-record converter with a forgotten type" note).
|
||||
- `docs/PCPortResearch.md` §F **D122, D120, D112, D53.2, D57, D67**
|
||||
(jump via index).
|
||||
- `docs/BRIEF-D122-per-level-modelload.md` — direct predecessor, same class.
|
||||
- `AGENTS.md` non-negotiables.
|
||||
|
||||
## SYMPTOM (WS4 sweep — `docs/LEVEL-STATUS.md`)
|
||||
| Level | # | Site | Note |
|
||||
|---|---|---|---|
|
||||
| Aztec | 28 | `propobj.c:13601` `door->model->obj->RootNode->Child->Child` | fault 0x10 |
|
||||
| Bunker2 | 27 | `propobj.c:13523` door displacement `linkedDoor` list walk | fault 0x8 |
|
||||
| Surface2 | 43 | `loadobjectmodel.c:393` `PitemZ_entries[modelid].header->RootNode` | fault 0x0 — direct D122 continuation |
|
||||
|
||||
All three: a prop/door/item model pointer or model-id from a `Usetup*Z`
|
||||
propDef record is wrong. D122 fixed the `[s16 obj][s16 pad]` half-swap for
|
||||
6 `inherits ObjectRecord` types; these levels emit a door subtype or
|
||||
another record type whose `model` / `linkedDoor` / `modelid` field is
|
||||
still converted wrong (or a placeholder-sized type:
|
||||
`VEHICLE`/`AIRCRAFT`/`TANK`/`AMMO`/`DEPOSIT_IN_ROOM` per
|
||||
`d88_propdefs.py`). Run `tools_pc/d88_propdef_scan.py` for the per-level
|
||||
type histogram; diff Aztec/Bunker2/Surface2 against the 7 passing levels.
|
||||
|
||||
## TASK
|
||||
Root-cause all three (likely one converter gap), extend
|
||||
`tools_pc/d88_propdefs.py` (+ `sizepropdef()` `#ifdef PORT` stride in
|
||||
`src/game/prop.c` to match, per the D122 pattern). Resolve any
|
||||
placeholder `PROPDEF_PC_BYTES` via `tools_pc/d88_layoutprobe.c`.
|
||||
|
||||
## FILES YOU MAY TOUCH
|
||||
`tools_pc/d88_propdefs.py`, `d88_emit.py`, `d88_layoutprobe.c`,
|
||||
`d88_propdef_scan.py`; `src/game/prop.c`, `src/game/propobj.c`,
|
||||
`src/game/loadobjectmodel.c` (narrow `#ifdef PORT`); new `tools_pc/c3_*`
|
||||
probes; `docs/PORT-LEARNINGS.md`, `docs/PCPortResearch.md` §F,
|
||||
`docs/LEVEL-STATUS.md`.
|
||||
|
||||
## KNOWN-GOOD / RULED OUT
|
||||
D121, D122 (committed `f2beae4b`), the 7 passing levels' prop/model load,
|
||||
C1's chr fix (landed first — rebase on it).
|
||||
|
||||
## PRE-FLIGHT / ENV / BUDGET / CONSTRAINTS / REPORT
|
||||
Same as `docs/BRIEF-C1-chr-airecord.md` (repro `-level_28` / `-level_27` /
|
||||
`-level_43`; `taskkill //F //IM ge007.x86_64.exe`; regen all 3 sidecars
|
||||
after converter change; `#ifdef PORT` with N64 line under `#else`; verify
|
||||
the 3 levels boot to a frame + `-level_09`/`-level_20` unregressed).
|
||||
Budget: **~12 cycles / ~90 min.**
|
||||
|
||||
## STILL OPEN AFTER THIS (singletons, own briefs later)
|
||||
- **C4** Depot 30 — `prop.c:902` `sp4C->room` after
|
||||
`walkTilesBetweenPoints_NoCallback` (BG tile/room table).
|
||||
- **C5** Control 23 — `bg.c:5723` `portal_pts->numPoints`
|
||||
(`g_BgPortals[].offset_portal` unresolved/BE), via `chrprop.c:62`.
|
||||
- **C7** Surface1 36 — `snd.c:653` `sndSetupSound` (audio subsystem is
|
||||
parked; may just need a narrow load-time guard until Phase 3).
|
||||
@@ -1,3 +1,6 @@
|
||||
> **RESOLVED — historical.** D69 is closed (see PCPortResearch.md §F/§H
|
||||
> D69, D78–D84). Kept for the bg/stan format spec. Not active work.
|
||||
|
||||
# Brief — M1 / D69: Stage (BG/stan) loading for PC
|
||||
|
||||
_Paste-ready milestone brief. Authoritative context: `AGENTS.md`,
|
||||
|
||||
@@ -0,0 +1,520 @@
|
||||
# HANDOFF ARCHIVE — session-by-session narrative
|
||||
|
||||
> **Reference only.** Current state, next task, and environment live in
|
||||
> `docs/HANDOFF.md`; per-finding detail in `docs/PCPortResearch.md` §F/§H
|
||||
> (indexed at the top of §F). This file is the frozen accumulation of
|
||||
> prior sessions' handoff briefs (through session M-11), kept so the
|
||||
> reasoning behind a fix is recoverable. Everything below is as-written
|
||||
> at the time and may be superseded.
|
||||
|
||||
---
|
||||
|
||||
# Handoff brief — GoldenEye 007 PC port (Phase 2: Session M-3 —
|
||||
# STAGE-LOAD → RENDER crash chain CLEAR; BUNKER1 renders, next is D75 model quality)
|
||||
|
||||
## PREFLIGHT — before touching anything (a concrete next-step below is NOT a reason to skip this)
|
||||
|
||||
1. `CLAUDE.md` (auto-loaded) + `AGENTS.md` non-negotiables.
|
||||
2. This file, top-to-bottom.
|
||||
3. `docs/PORT-LEARNINGS.md` — recurring bug classes.
|
||||
4. Dispatching a subagent? `docs/AGENT-WORKFLOW.md` FIRST. Every brief needs
|
||||
FILES / BUDGET / ON-EXPIRY / CONSTRAINTS / REPORT (see CLAUDE.md "Dispatch preflight").
|
||||
|
||||
_Paste-ready brief. Authoritative context: `AGENTS.md`,
|
||||
`docs/PCPortResearch.md` §F (D69, D78-D102), `docs/BRIEF-D69-stage-load.md`._
|
||||
|
||||
**NEW DIRECTION (2026-08-29): `docs/PLAN-linear-level-sweep.md`** — pivot
|
||||
to breadth-first: fix the boot→intro→menu→level-select path to
|
||||
*functional, not crashing* (cosmetics parked in `GRAPHICS-BACKLOG.md`),
|
||||
then sweep all 21 solo levels for load+render+no-crash. Start with WS1
|
||||
(auto-inject `memallocstringtable` args for bare `-level_XX`).
|
||||
|
||||
## READ THIS FIRST — crash chain CLEAR; viewport fixed; room geometry is next
|
||||
|
||||
`-level_09 -ml0 -me0 -mgfx100 -mvtx50 -mt700 -ma150` **boots BUNKER1
|
||||
and renders continuously with no fault** (60 s+, 5000+ VI posts at full
|
||||
framerate; attract mode also clean). The stage-load → first-frame →
|
||||
in-level crash chain (blocker since D69) is resolved.
|
||||
|
||||
**D103 (session M-4) fixed the "~46 %, lower half" symptom** — it was a
|
||||
single native-resolution/viewport bug (`osViSetMode` hard-coded height
|
||||
480 → `RATIO_Y` half of `RATIO_X`), not per-model. Frame is now full
|
||||
(91.7 %, correct letterbox + HUD placement). See §F "D103".
|
||||
|
||||
**D105 (session M-4) fixed room geometry** — `zbufClearCurrentPlayer`
|
||||
(`viewport.c`) used the N64 "fill the Z buffer as a colour image" idiom,
|
||||
which fast3d doesn't emulate, and nothing emitted `G_CLEAR_DEPTH_EXT`, so
|
||||
the depth buffer was never cleared and all ~160 k room triangles failed
|
||||
the Z test (only `skyRender`'s background fill survived). PORT branch now
|
||||
emits `G_CLEAR_DEPTH_EXT`. See §F "D104"/"D105". BUNKER1 now renders
|
||||
recognisably — textured walls, storage racks, floor (2585 colours, sky
|
||||
16 %), 70 s crash-free.
|
||||
|
||||
**D106 (session M-4)** — the big sky-void through doorways was the portal
|
||||
BFS dropping the next room: a portal straddling the camera near-plane
|
||||
projects z==0 clip points to ±1e20 screen coords, and on x86-64 that
|
||||
garbage came back `min>max` on one axis, slipping past the
|
||||
degenerate-box check that on N64 clamps it to full-screen. PORT guard in
|
||||
`sub_GAME_7F0B5864` treats non-finite / out-of-range bounds as
|
||||
full-screen. Visible rooms/frame 1–3 → 2–4. See §F "D106".
|
||||
|
||||
**D107 (session M-4)** — blurry ceilings/wall-panels were fast3d sampling
|
||||
GE's first mip (render tile 1), whose single-`G_LOADBLOCK` TMEM slot
|
||||
fast3d never registers, so `import_texture` fabricated a 16×16 crop of
|
||||
the base image and magnified it. `gfx_lod_tile_offset` now returns 0
|
||||
(base tile) when detail textures are off. BUNKER1 rooms render crisp.
|
||||
See §F "D107".
|
||||
|
||||
**Skeletal models — FIXED (session M-5, D112).** The "3D line" / missing
|
||||
characters were `tools_pc/d43_emit.py`'s `put_f32` reversing the wrong 4
|
||||
bytes (`src[doff+4:doff:-1]` → bytes doff+1..doff+4, dropping the MSB),
|
||||
corrupting every f32 in converted model rodata (joint `Origin`s, LOD
|
||||
distances, BSP planes). Compiled-in front-end models were unaffected
|
||||
(native LE), hence "logo fine, every guard broken". Fix: one line,
|
||||
`src[doff:doff+4][::-1]`; regen with `python tools_pc/d43_emit.py
|
||||
ntsc-final`. BUNKER guards now render as coherent humanoids, monitor
|
||||
screens draw content. See §F "D108–D112".
|
||||
|
||||
**"props emit no geometry" was a STALE premise** — props/doors emit
|
||||
complete leaf DLs (~15k/run, valid ptrs; fast3d transforms them). The
|
||||
storage-room void is NOT closed doors failing to render.
|
||||
|
||||
**Priority (user, M-11): get BUNKER1 — then every level — playable
|
||||
start-to-finish with no crashes. Fix crashes/hangs first; cosmetic
|
||||
rendering (D74/D75/D76/D114/D116) is logged in `docs/GRAPHICS-BACKLOG.md`
|
||||
and comes later.** D120 (blood-stain converter) is the nearest real
|
||||
converter gap; extending `d43_emit.py` for opcode-0x18 is a good
|
||||
subagent brief. After BUNKER1: walk the objective/exit path to a level
|
||||
transition, then the next stage.
|
||||
|
||||
**Real remaining work, in order (updated session M-8):**
|
||||
1. **Input layer (Phase 3)** — IN PROGRESS (M-8 agent, §F D118). `port/src/
|
||||
input.c` was a stub; only a minimal keyboard path in `libultra.c`
|
||||
(`contSnapshotFromKeyboard`) worked. No C-buttons/mouse-look/gamepad/
|
||||
rebinding. This is the top playability blocker — you cannot aim or
|
||||
properly move. Reference: `pd_port/port/src/input.c`.
|
||||
2. **Weapon model doesn't draw (AUDIT-M6 #5)** — NOT STARTED (M-8 agent
|
||||
hit the account session rate-limit before any work; tree untouched).
|
||||
Plan: instrument `gunfire.c` weapon setup (~600-661) + `field_87F`
|
||||
gating (~520/587-598) FIRST with a `GE_D119` printf — does setup even
|
||||
run? Then check `weaponModel.render_pos` = `dynAllocate`'d `rwmtx`
|
||||
lifetime vs the `gunRenderFirstPersonGunModels` (~1605) consumer (on
|
||||
N64 it aliased the persistent `hand->mtxlist`; D102 gave it own
|
||||
storage → arena may recycle). Also `weaponRwPool[192]` capacity vs
|
||||
`modelCalculateRwDataLen(mdlhdr)` on PC, and `flashvisptr` word-index
|
||||
assumption (~640). Narrow `#ifdef PORT` lifetime/sizing fix only — if
|
||||
it needs a behavioural arena change, STOP + write up. Direct `-level_09`
|
||||
boot may spawn Bond holstered — may need forced weapon state or the
|
||||
attract path to get the gun on screen. Gate to playability.
|
||||
3. **The HUD/text X-mirror (D116)** — DEPRIORITISED, parked in
|
||||
`docs/GRAPHICS-BACKLOG.md`. M-11 confirmed the ammo digits use the
|
||||
SAME `textrelated.c` path (no separate renderer — the M-8 premise was
|
||||
wrong) and re-hit the same contradiction: every stage verified
|
||||
non-mirrored, glyphs still flip. **Do not re-static-trace.** Next
|
||||
attempt needs a RenderDoc/apitrace capture or the asymmetric-1-texel
|
||||
experiment — nothing else. All cosmetic rendering issues (D74 dead
|
||||
wrap-block, D75/D76/D77) now tracked in `docs/GRAPHICS-BACKLOG.md`;
|
||||
they rank BELOW level-progression and crash work.
|
||||
4. **Rest of the `struct player` offset pass** — D115 fixed the HIGH
|
||||
`gunfire.c` THROW* bugs; #6 (watch-preview Model pool) + the broader
|
||||
audit remain (`docs/AUDIT-M6-player-offsets.md`).
|
||||
5. Add an f32 value spot-check to `d43_emit.py`'s verify pass (it only
|
||||
checks pointers/opcodes today — the D112 bug passed "ALL CHECKS PASSED").
|
||||
6. **GE_DETERM fixed-tick mode (§F D117)** — deferred as not-narrow; would
|
||||
unlock exact-diff regression testing + reproducible timing bugs. Design
|
||||
is written up. Worth doing once the above land.
|
||||
|
||||
**Session M-7 — see §F D116 + "D116 probe results".** Ran the glyph
|
||||
probe (overseer, after killing a subagent that was drifting toward a
|
||||
global texrect S-swap). Corrected finding: the HUD text mirror is a
|
||||
**per-quad texture-U flip that is NOT path-specific** — the ammo digits
|
||||
"83" are mirrored too (`ppm/frame_000320.ppm`), refuting M-7's earlier
|
||||
"proportional-font-specific" claim. Rect *positions* are correct; only
|
||||
texture content is X-flipped. Every fast3d stage probed clean (glyph
|
||||
bitmap in memory correct, texrect `ul.u=0→lr.u=max`, `import_texture_i8`
|
||||
linear, GL shader UV pass-through) — so the flip is in the GL
|
||||
vertex-buffer/draw layer OR a shared clip-space-X vs U desync on rect
|
||||
quads. **This re-opens D114's shared-mirror hypothesis** (now per-quad
|
||||
U/X, not screen-space) and plausibly also explains inverted guards /
|
||||
mislocated door props. NEXT: shader/vertex-buffer probe — dump per-vertex
|
||||
(x,u) for one glyph quad; render a 1-texel asymmetric test texture to see
|
||||
which axis inverts. `GE_D116`-gated probes left in `textrelated.c` +
|
||||
`gfx_pc.cpp` (zero-cost).
|
||||
|
||||
**Session M-8 wrap — state for next session.** Build GREEN at
|
||||
`587c6856`, tree clean. Committed: D116 part-3 writeup + `[D116/vbo]`
|
||||
probe (`5ff012b4`), `CLAUDE.md`/preflight (`fd25628a`), D117
|
||||
framediff+nondeterminism (`44d9ae98`), D118 input layer
|
||||
(`4ac11fe7`/`587c6856`), handoff reprioritisation. `GE_D116` and
|
||||
`GE_INPUTLOG` probes are in-tree, `getenv`-gated, zero-cost. Weapon-model
|
||||
(#5) agent never ran (rate-limit) — task untouched, plan in item 2 above.
|
||||
Pre-existing stash `stash@{0} "d59 probes WIP"` (~300 lines,
|
||||
gfx_pc/gfx_opengl/crash.c) is NOT from M-8 — provenance unknown, left as
|
||||
found. Next: human input playtest (tune `ge007.ini [Input] MouseAimSpeed`),
|
||||
then weapon-model #5.
|
||||
|
||||
**Session M-8 (continued) — D116 part 3 + input layer.** Killed the
|
||||
drifting D116 agent, ran the probe as overseer: `buf_vbo` (x,u) for
|
||||
glyph quads and the GL texture upload are both runtime-verified
|
||||
non-mirrored (x-left<->u=0), GL shader is `gl_Position=aVtxPos` +
|
||||
UV-passthrough — every stage clean, yet glyphs render X-flipped
|
||||
(contradiction, §F "D116 runtime probe part 3"). Deprioritised as
|
||||
cosmetic. `GE_D116` `[D116/vbo]` probe kept in `gfx_pc.cpp`. Then
|
||||
dispatched the **input-layer agent** (§F D118, in progress) — top
|
||||
playability blocker. `framediff.py` structural mode is the interim
|
||||
regression gate (`tools_pc/golden/` is a nondeterministic D115 capture).
|
||||
|
||||
**Session M-8 — see §F D117.** Visual-regression tooling. Root-caused the
|
||||
frame-to-frame nondeterminism: pure variable-timestep frame pacing
|
||||
(`osGetCount()` = wall clock on PC → `frametiming.c waitForNextFrame`
|
||||
advances logic a real-time-dependent number of 60 Hz ticks per render).
|
||||
PRNG seeding verified correct (`random.c`), not a source. A `GE_DETERM=1`
|
||||
fixed-tick mode was assessed **not narrow** (redesigns VI retrace/tick
|
||||
semantics, deadlock risk in load-screen loops) and deferred with a full
|
||||
design in §F D117 — NOT implemented. Added `tools_pc/framediff.py`
|
||||
(structural/tolerant: 16×12 grid mean-colour + non-clear-% + aHash,
|
||||
`--mask` for HUD, `--exact` for a future deterministic build, `--update`
|
||||
to refresh goldens). Validated against the D115 golden set. No C changes,
|
||||
no probes left in tree.
|
||||
|
||||
**Session M-10 — input playtest + guard-firefight crash chain.**
|
||||
|
||||
- **D119 — guard-attack crash FIXED** (`chraction.c`). Every prior
|
||||
`-level_09` run segfaulted ~frame 1200 the instant a BUNKER guard
|
||||
opened fire: `bondwalkItemGetAutomaticFiringRate` (`gun.c:1334`) ←
|
||||
`chrlvInitActAttack`. ~28 sites pun `weapons_held[]->chr` (really a
|
||||
`WeaponObjRecord*`) as `ChrRecord*` and read `.act_<x>.attack_item`,
|
||||
which on N64 aliases `WeaponObjRecord.weaponnum` (act union @0x2C + 84
|
||||
== 0x80). Pointer widening moves the act union to ~0x38 on PC → garbage
|
||||
negative item id → OOB `g_ItemStats` → crash. Fix: `PUN_ATTACK_ITEM()`
|
||||
macro reads `weaponnum` directly; `#else` branch is textually identical
|
||||
to the original → N64 build unchanged. Class-A (D53.2 type-pun) bug.
|
||||
- **D120 — blood-stain hang GUARDED, not fixed** (`chr.c:3322`). Next
|
||||
blocker, reachable only after D119: first guard bullet-hit →
|
||||
`chrCreateBloodStain` `PointUsage[]` negative-terminated chain walk
|
||||
cycles forever (VI thread keeps posting, logic thread spins, kernel
|
||||
heartbeat trips). Root cause: `tools_pc/d43_emit.py`'s opcode-0x18
|
||||
`ModelRoData_DisplayList_CollisionRecord` conversion is incomplete —
|
||||
6 pointer fields widen the struct and `PointUsage`/`CollisionVertices`
|
||||
sub-array endianness+stride aren't handled (only `CollisionRelatedNode`
|
||||
got the D43/D45 `u32`-vma treatment). **Interim:** `#ifdef PORT` caps
|
||||
both walk loops at `numVertices+8` iters + bounds-checks `index` → game
|
||||
survives, blood decals may be missing/wrong. **Real fix (next session,
|
||||
own subagent brief):** byte-spec the opcode-0x18 record + PointUsage +
|
||||
CollisionVertex sub-arrays vs a converted guard model, extend
|
||||
`d43_emit.py`. Same shape as D69/D88 converter work.
|
||||
- Result: `-level_09` now survives the guard firefight — 45 s+
|
||||
crash-free, frames past 1200 (was: hard segfault ~1200 every run).
|
||||
|
||||
**Session M-10 — input playtested (human).** Fixed in `port/src/input.c`:
|
||||
(a) mouse-look was a draining accumulator → flicks stayed "pressed" ~8
|
||||
frames after you stopped (stuck looking up/down) — now per-poll delta,
|
||||
no carryover; (b) horizontal mouse was routed to C-left/right = GE
|
||||
*sidestep*, and A/D drove analog stick-X = GE *turn* — swapped: mouse X
|
||||
→ stick-X turn (`MOUSE_TURN_GAIN 6.0`), A/D → C-left/right strafe, mouse
|
||||
Y → C-up/down look. Core aim/move now works.
|
||||
**Open input bugs (documented, deferred — fix in a later pass):**
|
||||
- **D118a — mouse yaw slower than pitch.** Horizontal turn (analog
|
||||
stick-X via `MOUSE_TURN_GAIN`) is visibly slower than vertical look
|
||||
(digital C-up/down). Different transfer curves (analog rate-limited vs
|
||||
digital full-press). Needs `MOUSE_TURN_GAIN` bump and/or a matched
|
||||
pitch path; ideally a single tunable `MouseAimSpeed`.
|
||||
- **D118b — mouse Y inverted.** Mouse up → looks down, mouse down →
|
||||
looks up (X unaffected). `aimDY`→C-button sign is backwards for GE's
|
||||
pitch convention (or SDL dy sign assumption wrong). Flip the
|
||||
`aimDY >=`/`<=` C-up/C-down assignment (or default `MouseInvertY`).
|
||||
One-line fix once confirmed against GE pitch sign.
|
||||
- Still TODO from D118: rebinding, gamepad hotplug, `ge007.ini` not yet
|
||||
created (config defaults are hardcoded).
|
||||
|
||||
**Session M-9 (Phase 3) — see §F D118.** SDL input layer implemented.
|
||||
`port/src/input.c` is now real: keyboard+mouse and SDL_GameController,
|
||||
mapped to GE's N64 pad (analog stick = move, C-buttons = aim, mouse-look
|
||||
bridged to digital C-buttons via a clamped accumulator, RMB/LT = aim
|
||||
mode, LMB/RT = fire). `libultra.c`'s SI section delegates to it (single
|
||||
source of controller state; still driven by `osContStartReadData`, no
|
||||
new frame hook). Config: `ge007.ini [Input]` MouseEnabled/MouseAimSpeed/
|
||||
MouseInvertY. Build GREEN, boots `-level_09` crash-free 35 s.
|
||||
**Owed: a human playtest** — live input is untested from the headless
|
||||
agent. Manual checklist: (1) WASD moves Bond, mouse turns/looks (tune
|
||||
`MouseAimSpeed` in ge007.ini if too fast/slow), LMB fires, RMB enters
|
||||
aim mode, Enter opens the pause menu; (2) plug an Xbox pad — left stick
|
||||
moves, right stick aims, triggers fire/aim; (3) `GE_INPUTLOG=1` prints
|
||||
each nonzero OSContPad poll. Rebinding + gamepad hotplug are TODO (§F
|
||||
D118).
|
||||
|
||||
**Session M-6 — see §F D113/D114/D115, `docs/PLAN-M6-playable.md`,
|
||||
`docs/AUDIT-M6-player-offsets.md`, `docs/AGENT-WORKFLOW.md`,
|
||||
`docs/PORT-LEARNINGS.md`.** D113: portal BFS is correct, not the void.
|
||||
D114: matrix chain + converter verified clean, residual = shared fast3d
|
||||
mirror (open). D115: player raw-offset audit + `gunfire.c` THROW* fix
|
||||
(uncommitted). Build green.
|
||||
|
||||
**This session's fixes (all committed, master):**
|
||||
|
||||
| # | commit | one-liner |
|
||||
|---|--------|-----------|
|
||||
| D93 | `164d7f99` | null-room (room 0) NULL-deref guards |
|
||||
| D85 | `493c9838` | `bgWidenRoomGdl` (8→16 `Gfx` + `bswap32`) + `bgSwapRoomVtx`; room DLs decode to real GBI; `bg.c:2448` size-cast bug |
|
||||
| D85 | `6f0208d6` | `ptr_texture_alloc_start` → real `struct texpool` storage (pool looked exhausted → ~630 room textures now resolve; cleared `Bad size for RGBA texture`) |
|
||||
| D94 | `63204a27` | `chrlvInitActAttack` `(s32)`-truncated anim-table index |
|
||||
| D95 | `f35eba91` `933ba52b` | 2× `g_GfxBuffers` (16-byte PC `Gfx`) + raise PC mempool ceiling `0x702F4400→0x70700000` (reclaim ~4 MB DRAM) so it doesn't OOM `MEMPOOL_STAGE` |
|
||||
| D96 | `d86ec483` | `PROPRECORD_STAN_ROOM_LEN` 4→8 (PORT) — prop room-list stack overflow (guards span ≥4 rooms → no terminator → smashed `chrpropsRenderPass` frame; *this* was the "runaway GDL append") |
|
||||
| D97 | `2fbcc556` | clamp negative `damagetype` (US-only OOB `g_DamageTypes[]` read) |
|
||||
| D98 | `000ed6af` | `initBONDdataforPlayer` allocate real PC `sizeof(struct player)` (hardcoded `0x2A80` under-alloc scribbled the master DL) |
|
||||
| D99 | `253caa23` | `Model.animflipfunc` `s32` fn-ptr truncation (flag + direct call, D92 pattern) |
|
||||
| D100 | `8eaad547` | `struct player.model` is an inline `struct Model`, not a `Model*` + 45 filler s32s (PC struct 0x2A8 ≫ 0xB8 hole → `animInit` overran); + dedicated `gaitRwData[]` |
|
||||
| D101 | `b2234f82` | `sub_GAME_7F06DB5C` stashed `ModelNode*`/`RenderPosView*` through an `s32` (the idiom its sibling was already fixed for) |
|
||||
| D102 | `1b078f6d` | 1P weapon `Model` + RW pool punned onto `struct hand` (PC `Model` 0xE8 ≫ N64 0xBC → `modelInit` aliased `datas` onto the pool); dedicated `weaponModel`/`weaponRwPool` |
|
||||
|
||||
**Recurring pattern this session:** the decomp pun-allocates `struct
|
||||
Model` (and `struct player`, `PropRecord`, `struct texpool`, …) into
|
||||
N64-sized holes / hardcoded byte counts. Every one is bigger on x86-64
|
||||
→ overrun → corruption. Fix = give it real inline storage or
|
||||
`sizeof()`-based alloc under `#ifdef PORT`. **Landmine still open**
|
||||
(§F D100): `struct player` / `struct hand` have raw hardcoded-offset
|
||||
accessors (`gunfire.c` `THROWMTX` at `+0xAD8`, …) that are NOT
|
||||
PORT-adjusted — already PC-wrong, a real `struct player` offset pass is
|
||||
owed before grenade/knife-throw code works.
|
||||
|
||||
- Build: `export PATH="/c/msys64/mingw64/bin:$PATH" && ./build-pc.sh ntsc-final`
|
||||
- Sidecar regen: `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`. `data/` is gitignored; if missing,
|
||||
`cp baserom.u.z64 data/ge007.ntsc-final.z64` first (§F "`data/`
|
||||
deletion + recovery").
|
||||
- Repro: `./build-pc/ge007.x86_64.exe -level_09 -ml0 -me0 -mgfx100
|
||||
-mvtx50 -mt700 -ma150` — `-m*` are BUNKER1's `boss.c` per-level sizes;
|
||||
a bare `-level_09` skips them and crashes early (TODO: auto-inject).
|
||||
|
||||
## Next: D75 — 3D model rendering quality
|
||||
|
||||
BUNKER1 renders but character/skeletal models are wrong (see the D75
|
||||
section further down, still accurate). `GE_PCDUMP` + `tools_pc/pixcount.py`
|
||||
to measure; the animated-model path (`animInit` + `struct player` raw
|
||||
offsets, `drawjointlist`, `modelApplyHeadRelations` head/body splice) is
|
||||
the prime suspect. `struct tex` headers are 24 B vs 16 B on PC — a
|
||||
separate open pool-pressure item; a real PC memory-budget pass is owed.
|
||||
|
||||
## What this session (M-2) fixed — all committed
|
||||
|
||||
1. **`-level_09` now works.** `osPiReadIo` was stubbed to 0, so the token
|
||||
string was always empty and N64 debug switches were ignored (only
|
||||
attract mode could reach a level). Now built from `argv[1..]`.
|
||||
2. **Sidecar tables patched in `obInit()`** instead of lazily at first
|
||||
model load — a direct stage boot loaded raw big-endian ROM before.
|
||||
3. **D88.5** — stan tile-name byte-swap in the converter (0/276 →
|
||||
273/273 name matches during pad setup).
|
||||
4. **D88.6** — intro CAMERA `lang1c` is a `u16` pair, not an `s32`
|
||||
(fixed the `langGet` NULL-bank crash).
|
||||
5. **D89** — `init_path_table_links` `[-3]` OOB → SIGILL fix; NULL-tile
|
||||
guard in `sub_GAME_7F0B0914`.
|
||||
6. **D90** — `stanTileDistanceRelated`'s 80-byte zero-fill was clobbering
|
||||
the caller's live stan-tile local (16B struct, 80B clear). The
|
||||
player's spawn stan was fine all along.
|
||||
7. **D91** — `(s32)&D_800442FC[portalnum]` truncation in bg portal cull.
|
||||
8. **D92** — `chrAllocate` `s32` param truncated the ailist pointer;
|
||||
`Model.unka0` `s32` field truncated a stored function pointer.
|
||||
|
||||
**Next steps for the resuming session (render milestone):**
|
||||
1. **D85** — root cause confirmed (8-byte N64 `Gfx` vs 16-byte PC `Gfx`
|
||||
in the unconverted per-room DL blob). Runtime widen+bswap fixup being
|
||||
implemented; verify with `GE_D69BB=1` (real GBI opcodes, not
|
||||
00/01/02/52), then `GE_PCDUMP` + `tools_pc/pixcount.py` for
|
||||
non-degenerate BUNKER1 geometry. See §F "D85 root cause CONFIRMED".
|
||||
Fallback if the widen stalls: soften the four `sysFatalError("Bad
|
||||
size…")` guards in `gfx_pc.cpp` to skip-with-warning.
|
||||
2. Then the front-end **render bugs (D75)**.
|
||||
|
||||
**D88.4 loose end (still open):** `PROPDEF_PC_BYTES` for `VEHICHLE`/
|
||||
`AIRCRAFT`/`TANK`/`AMMO`/`DEPOSIT_IN_ROOM` in `d88_propdefs.py` are
|
||||
placeholder guesses (BUNKER1 doesn't use them). Probe real sizes via
|
||||
`d88_layoutprobe.c` before loading levels that use those types.
|
||||
|
||||
## Known rendering bugs (D75 — still open, orthogonal to the D88 crash)
|
||||
|
||||
Even once the crash chain is cleared, the front end has **broken 3D model
|
||||
rendering** (user-confirmed this session):
|
||||
- Rareware logo: correct (fixed in D73/D74).
|
||||
- **Nintendo logo**: renders but **mispositioned**.
|
||||
- **Gun-barrel intro**: the **James Bond character model is missing
|
||||
entirely**.
|
||||
- **Intro credits / cast roll**: the per-character 3D models **do not
|
||||
appear at all** (names draw, models don't).
|
||||
Pattern: textures/text draw; **animated/skeletal character models never
|
||||
appear**; static 3D (logos) appears but with a bad transform. Leading
|
||||
hypothesis is D75(b) — the animated-model path (`animInit` + raw offsets
|
||||
into `struct player`, cf. D56) is broken independently of the D73 matrix
|
||||
sin/cos fix. Full triage plan in `PCPortResearch.md` §F D75.
|
||||
|
||||
The rest of this document (below) is the **last known-good, committed**
|
||||
status as of commit `8c9c6a2c` (D86+D87 resolved) — still accurate except
|
||||
D88 is now further along (D88.1–3 done/verified, D88.4 is the live crash).
|
||||
|
||||
## Where things stand
|
||||
|
||||
**D69 (the original "stage load faults" milestone blocker) remains
|
||||
RESOLVED.** `load_bg_file` (bg.c) doesn't fault on BUNKER1. Since then,
|
||||
two more crashes further down the load chain were found and fixed this
|
||||
session (D86, D87), and a third — the current blocker — was root-caused
|
||||
but not fixed (D88).
|
||||
|
||||
**D86 RESOLVED.** `modelInitRwData` crash (`model.c:6174`) was a single
|
||||
truncating pointer cast in the player's embedded gait/arm model:
|
||||
`src/game/initplayergaitobject.c:5` did
|
||||
`player_gait_object_header.RootNode = (int)&player_gait_hdr;` — a
|
||||
same-width no-op on N64 that truncates+zero-extends a real 64-bit pointer
|
||||
on PC. Fixed with a narrow `#ifdef PORT` branch assigning the pointer
|
||||
directly. Root-caused via a new node-walk trace (`GE_D86=1`, left in
|
||||
place, gated in `model.c`/`objecthandler_2.c`).
|
||||
|
||||
**D87 RESOLVED.** Once D86 stopped blocking progress, an idle (no-input)
|
||||
run eventually triggers the front-end's genuine attract-mode demo
|
||||
playback (`select_ramrom_to_play()` picks a random compiled-in demo —
|
||||
this is shipped retail behavior, not a debug feature) and crashed in
|
||||
`ramrom_replay_handler` (`ramromreplay.c`). Root cause: `ramromfilestructure`
|
||||
is a real ROM-compiled asset (big-endian, like everything else) loaded via
|
||||
`romCopyAligned()` — a raw byte copy by design (D66) — with **no
|
||||
byteswap**, so every multi-byte field read back scrambled (e.g. `size_cmds`
|
||||
2 → 33554432) and drove wild pointer arithmetic. Fixed with a `#ifdef PORT`
|
||||
`ramromFixupEndian()` called once after the load (same pattern as the D54
|
||||
cseq fixup). Not BUNKER1-specific — attract mode picks any of 7 demo
|
||||
locations at random, so don't rely on it for BUNKER1-specific testing (see
|
||||
`-level_09` below).
|
||||
|
||||
**D88 — SUPERSEDED, see "READ THIS FIRST" at top.** D88.1–D88.3 (header +
|
||||
sub-table width/endian conversion) are now done and verified; D88.4
|
||||
(`propDefs` byteswap) is the live blocker. The paragraph below is the
|
||||
original root-cause writeup, kept for context.
|
||||
|
||||
**D88 (original writeup) — root-caused.** Launch with
|
||||
`-level_09` (NTSC `LEVELID_BUNKER1 = 9`; `boss.c:199-339` decodes
|
||||
`-level_XX` into `g_StageNum`, bypassing the front end/attract-mode
|
||||
entirely — fast, deterministic BUNKER1 repro, crashes in well under a
|
||||
minute instead of waiting ~2 min for attract mode to maybe pick Bunker).
|
||||
Crash: `proplvreset2` (`prop.c:1306`) segfaults reading
|
||||
`g_CurrentSetup.pathwaypoints[i1].padID`. Root cause: the per-level
|
||||
`"Usetup<name>Z"` file (`prop.c:1267`, `struct stagesetup` in
|
||||
`bondtypes.h:4091`) is loaded as raw ROM bytes and has **zero PC porting
|
||||
work done on it** — unlike bg/stan (D69/D80-82) and models (D43/D50).
|
||||
Two compounding problems, not just one:
|
||||
1. The 10 top-level fields (`pathwaypoints`/`waypointgroups`/`intro`/
|
||||
`propDefs`/`patrolpaths`/`ailists`/`pads`/`boundpads`/`padnames`/
|
||||
`boundpadnames`) are declared as real pointers in the live C struct,
|
||||
so on PC they're 8 bytes each (an 80-byte header) instead of the
|
||||
file's real 4-byte-each (40-byte) N64 layout — same class as D79
|
||||
(`bg_room_data` pointer growth). Field 0 reads fine; everything after
|
||||
it is reading the wrong bytes entirely.
|
||||
2. The 4 meaningful bytes each field *does* store are big-endian (the
|
||||
code's own comment: "stores every internal reference as a byte offset
|
||||
from the start of the file") and nothing byte-swaps them — same class
|
||||
as D87.
|
||||
No PORT/byteswap handling exists anywhere in `prop.c` (confirmed by grep).
|
||||
**Not fixed this session** — this is D69-scale format-conversion work: a
|
||||
byte-accurate spec of the whole `Usetup*Z` format (top-level header +
|
||||
every nested sub-table: `waypoint`/`waygroup`/`PropDefHeaderRecord`/
|
||||
`PathRecord`/`AIListRecord`/`PadRecord`/`BoundPadRecord`/`pname`, each
|
||||
likely with its own internal offsets not yet audited) plus either an
|
||||
offline converter sidecar (preferred pattern per AGENTS.md, same shape as
|
||||
`tools_pc/d69_emit.py`) or a careful runtime fixup pass that parses the
|
||||
raw 40-byte N64-packed header by explicit byte offset, byte-swaps each
|
||||
field, and writes results into the PC-widened struct.
|
||||
|
||||
**Net effect vs. last session:** the game now runs substantially further
|
||||
— all the way through room-streaming setup, past the intro's model
|
||||
pipeline, and into per-level "Usetup" data — before hitting D88. The
|
||||
"loads without fault" acceptance bar is **still not met**, but the
|
||||
remaining blocker is now narrowly scoped and has a fast, deterministic
|
||||
repro (`-level_09`, no attract-mode wait, no depending on which random
|
||||
demo attract-mode picks).
|
||||
|
||||
## Recommended next steps, in order
|
||||
|
||||
1. **D88.** Get a byte-level spec of `Usetup*Z` (start from BUNKER1's
|
||||
file; `strResource` is built as `"U" + "sev" + "Z"`-style name in
|
||||
`prop.c:1253-1265` — check `setup_text_pointers[LEVELID_BUNKER1]` for
|
||||
the exact literal). Decide offline-converter vs. runtime-fixup (D69's
|
||||
bg/stan work is the template for the former; D54's cseq fixup is the
|
||||
template for the latter — given the struct-width mismatch on top of
|
||||
the byteswap, a runtime fixup that manually walks the *raw* 40-byte
|
||||
N64 header by hand (not through the live `stagesetup` struct) into a
|
||||
freshly-populated `g_CurrentSetup` is probably simpler than a full
|
||||
sidecar here, but verify against a raw ROM hex dump either way).
|
||||
2. Once BUNKER1 loads past `proplvreset2`, re-check for further crashes
|
||||
in the same vein (this session found 3 in a row — D86, D87, D88 — each
|
||||
only reachable after the previous one was fixed; expect more).
|
||||
3. Once BUNKER1 reaches a rendered frame with no fault, revisit **D85**
|
||||
(room primary/secondary GDL binaries decode to garbage via
|
||||
`texLoadFromGdl`) — use `GE_PCDUMP="<range>:10"` + `tools_pc/pixcount.py`
|
||||
to confirm non-black, non-degenerate content per the original D69
|
||||
acceptance bar. (This session's `GE_PCDUMP` captures around frame
|
||||
2100-2400 during attract-mode-driven "loading" were still on a HUD/menu
|
||||
screen, not real 3D geometry — don't read too much into pixel counts
|
||||
from before D88 is fixed.)
|
||||
4. Do NOT touch D75/D76/D77 (parked, lower priority, unrelated).
|
||||
|
||||
## Debug tooling added this session (kept, env-gated, zero cost when unset)
|
||||
|
||||
- `GE_D86=1` — node-walk trace in `modelInitRwData` (model.c) + a
|
||||
load-identity probe in `load_object_fill_header` (objecthandler_2.c).
|
||||
Resolved the D86 crash; left in place since the same
|
||||
load_object_fill_header/modelInitRwData pipeline could surface new
|
||||
edge cases as more of the game becomes reachable.
|
||||
- `GE_D87=1` — block-setup trace in `iterate_ramrom_entries_handle_camera_out`
|
||||
and consumer trace in `ramrom_replay_handler` (ramromreplay.c). Resolved
|
||||
the D87 crash; left in place as it's a rarely-exercised path (attract
|
||||
mode) worth having visibility into if it acts up again.
|
||||
- Pre-existing `GE_D69STAN=1`, `GE_D69BB=1`, `GE_D69=1` — unchanged, still
|
||||
useful for the bg/stan/D85 load path (see prior session's HANDOFF
|
||||
entries, preserved in git history, for exactly what each logs).
|
||||
|
||||
## New: fast, deterministic BUNKER1 repro (no attract-mode wait)
|
||||
|
||||
Launch with `-level_09` as a program argument
|
||||
(`./build-pc/ge007.x86_64.exe -level_09`) to skip the front end/attract
|
||||
mode and load BUNKER1 directly — `boss.c:199-339` decodes `-level_XX`
|
||||
(the two digit-chars are consumed as raw ASCII bytes:
|
||||
`g_StageNum = tokenFindLevel[0]*10 + tokenFindLevel[1] - 0x210`; NTSC
|
||||
`LEVELID_BUNKER1 = 9` → `"09"` since `'0'*10 + '9' - 0x210 = 9`). This is
|
||||
now the preferred way to test BUNKER1-specific load/render work — it's
|
||||
faster (crashes/completes in well under a minute vs. ~2+ min waiting on
|
||||
attract mode) and deterministic (not dependent on which of 7 random demo
|
||||
locations attract mode happens to pick).
|
||||
|
||||
## Environment / build
|
||||
|
||||
- `export PATH="/c/msys64/mingw64/bin:$PATH" && ./build-pc.sh ntsc-final`
|
||||
(~5 s). Build is GREEN with all D86/D87 work included.
|
||||
- Run from the **repo root**, not `build-pc/`.
|
||||
- Regenerate sidecars if missing: `python tools_pc/d69_emit.py ntsc-final`
|
||||
(bg/stan) and the D43/D50 model sidecar generator (pcmodels) — both
|
||||
gitignored, not checked in. (Both were already present in this dev
|
||||
environment this session.)
|
||||
- `GE_PCDUMP="<start>-<end>:<stride>"` + `tools_pc/pixcount.py` for frame
|
||||
captures once D88 is fixed and a frame actually renders real BUNKER1
|
||||
geometry.
|
||||
- Crash log: `ge007.crash.log` (repo root); symbolicate with
|
||||
`addr2line -e build-pc/ge007.x86_64.exe -f -C <pc>` (image base
|
||||
`0x140000000`).
|
||||
- gdb **launch** mode is too slow for timing-dependent bugs (unchanged
|
||||
guidance). **New this session:** gdb **attach** mode
|
||||
(`gdb -batch -x cmds.txt -p <winpid>`, `<winpid>` = 4th column of
|
||||
`ps -p <bashpid>`) works well and is fast for "watch a global for a
|
||||
legitimate vs. corrupted write" questions on an already-running,
|
||||
not-yet-crashed process — see `docs/PCPortResearch.md` §F environment
|
||||
reminders for the exact recipe used to root-cause D87.
|
||||
|
||||
## Non-negotiables (unchanged, see AGENTS.md)
|
||||
|
||||
1. N64 build files untouched.
|
||||
2. Game logic unmodified except narrow, documented `#ifdef PORT`
|
||||
ABI/layout exceptions (D86/D87 this session, both logged in
|
||||
`PCPortResearch.md` §F). D88 is explicitly **not** patched with a
|
||||
quick inline hack — it needs the same disciplined
|
||||
spec-then-convert/fixup treatment as D69, logged as an open finding
|
||||
instead per AGENTS.md's "stop and write it up" guidance for anything
|
||||
beyond a narrow, obviously-correct exception.
|
||||
3. Offline sidecar conversion preferred over runtime fixup for whole
|
||||
ROM-asset formats (D69/D80-82 pattern) — likely the right call for
|
||||
D88 too, though a careful runtime fixup is also plausible; decide
|
||||
after the byte-level spec work.
|
||||
@@ -1,3 +1,6 @@
|
||||
> **RESOLVED — historical.** D7 closed at the compile+link milestone.
|
||||
> Kept for the AI X-macro analysis. Not active work.
|
||||
|
||||
# HANDOFF — AI X-macro system (D7), the last build blocker
|
||||
|
||||
## Status
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
> **RESOLVED — historical.** D7/D8 closed at the compile+link milestone.
|
||||
> Kept for the IDO-vs-GCC preprocessor analysis. Not active work.
|
||||
|
||||
# HANDOFF — SWITCH/CALL macro port (D7/D8) — IN PROGRESS
|
||||
|
||||
## Status: BLOCKED on SWITCH expansion mangling (root cause now fully understood)
|
||||
|
||||
+152
-475
@@ -1,503 +1,180 @@
|
||||
# Handoff brief — GoldenEye 007 PC port (Phase 2: Session M-3 —
|
||||
# STAGE-LOAD → RENDER crash chain CLEAR; BUNKER1 renders, next is D75 model quality)
|
||||
# HANDOFF — GoldenEye 007 PC port (Phase 2, breadth-first level sweep)
|
||||
|
||||
## PREFLIGHT — before touching anything (a concrete next-step below is NOT a reason to skip this)
|
||||
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`.
|
||||
|
||||
1. `CLAUDE.md` (auto-loaded) + `AGENTS.md` non-negotiables.
|
||||
2. This file, top-to-bottom.
|
||||
3. `docs/PORT-LEARNINGS.md` — recurring bug classes.
|
||||
4. Dispatching a subagent? `docs/AGENT-WORKFLOW.md` FIRST. Every brief needs
|
||||
FILES / BUDGET / ON-EXPIRY / CONSTRAINTS / REPORT (see CLAUDE.md "Dispatch preflight").
|
||||
## Preflight (do not skip because you have a "next step")
|
||||
|
||||
_Paste-ready brief. Authoritative context: `AGENTS.md`,
|
||||
`docs/PCPortResearch.md` §F (D69, D78-D102), `docs/BRIEF-D69-stage-load.md`._
|
||||
1. `CLAUDE.md` (auto) + `AGENTS.md` non-negotiables.
|
||||
2. This file, top to bottom.
|
||||
3. `docs/PORT-LEARNINGS.md` — recurring bug classes (you WILL re-derive
|
||||
catalogued bugs otherwise).
|
||||
4. `docs/PLAN-linear-level-sweep.md` — the plan of record.
|
||||
5. Dispatching a subagent? `docs/AGENT-WORKFLOW.md` first — every brief
|
||||
needs FILES / BUDGET / ON-EXPIRY / CONSTRAINTS / REPORT.
|
||||
|
||||
## READ THIS FIRST — crash chain CLEAR; viewport fixed; room geometry is next
|
||||
## Direction (2026-08-29)
|
||||
|
||||
`-level_09 -ml0 -me0 -mgfx100 -mvtx50 -mt700 -ma150` **boots BUNKER1
|
||||
and renders continuously with no fault** (60 s+, 5000+ VI posts at full
|
||||
framerate; attract mode also clean). The stage-load → first-frame →
|
||||
in-level crash chain (blocker since D69) is resolved.
|
||||
**Breadth-first pivot.** Stop the depth-first BUNKER1 + graphics
|
||||
rabbit-holes. Instead:
|
||||
|
||||
**D103 (session M-4) fixed the "~46 %, lower half" symptom** — it was a
|
||||
single native-resolution/viewport bug (`osViSetMode` hard-coded height
|
||||
480 → `RATIO_Y` half of `RATIO_X`), not per-model. Frame is now full
|
||||
(91.7 %, correct letterbox + HUD placement). See §F "D103".
|
||||
1. Make boot → intro → menu → level-select **functional (not crashing)** —
|
||||
cosmetics parked in `docs/GRAPHICS-BACKLOG.md`.
|
||||
2. Sweep all **21 solo levels** for load + render + no-crash →
|
||||
`docs/LEVEL-STATUS.md`.
|
||||
|
||||
**D105 (session M-4) fixed room geometry** — `zbufClearCurrentPlayer`
|
||||
(`viewport.c`) used the N64 "fill the Z buffer as a colour image" idiom,
|
||||
which fast3d doesn't emulate, and nothing emitted `G_CLEAR_DEPTH_EXT`, so
|
||||
the depth buffer was never cleared and all ~160 k room triangles failed
|
||||
the Z test (only `skyRender`'s background fill survived). PORT branch now
|
||||
emits `G_CLEAR_DEPTH_EXT`. See §F "D104"/"D105". BUNKER1 now renders
|
||||
recognisably — textured walls, storage racks, floor (2585 colours, sky
|
||||
16 %), 70 s crash-free.
|
||||
|
||||
**D106 (session M-4)** — the big sky-void through doorways was the portal
|
||||
BFS dropping the next room: a portal straddling the camera near-plane
|
||||
projects z==0 clip points to ±1e20 screen coords, and on x86-64 that
|
||||
garbage came back `min>max` on one axis, slipping past the
|
||||
degenerate-box check that on N64 clamps it to full-screen. PORT guard in
|
||||
`sub_GAME_7F0B5864` treats non-finite / out-of-range bounds as
|
||||
full-screen. Visible rooms/frame 1–3 → 2–4. See §F "D106".
|
||||
|
||||
**D107 (session M-4)** — blurry ceilings/wall-panels were fast3d sampling
|
||||
GE's first mip (render tile 1), whose single-`G_LOADBLOCK` TMEM slot
|
||||
fast3d never registers, so `import_texture` fabricated a 16×16 crop of
|
||||
the base image and magnified it. `gfx_lod_tile_offset` now returns 0
|
||||
(base tile) when detail textures are off. BUNKER1 rooms render crisp.
|
||||
See §F "D107".
|
||||
|
||||
**Skeletal models — FIXED (session M-5, D112).** The "3D line" / missing
|
||||
characters were `tools_pc/d43_emit.py`'s `put_f32` reversing the wrong 4
|
||||
bytes (`src[doff+4:doff:-1]` → bytes doff+1..doff+4, dropping the MSB),
|
||||
corrupting every f32 in converted model rodata (joint `Origin`s, LOD
|
||||
distances, BSP planes). Compiled-in front-end models were unaffected
|
||||
(native LE), hence "logo fine, every guard broken". Fix: one line,
|
||||
`src[doff:doff+4][::-1]`; regen with `python tools_pc/d43_emit.py
|
||||
ntsc-final`. BUNKER guards now render as coherent humanoids, monitor
|
||||
screens draw content. See §F "D108–D112".
|
||||
|
||||
**"props emit no geometry" was a STALE premise** — props/doors emit
|
||||
complete leaf DLs (~15k/run, valid ptrs; fast3d transforms them). The
|
||||
storage-room void is NOT closed doors failing to render.
|
||||
|
||||
**Priority (user, M-11): get BUNKER1 — then every level — playable
|
||||
start-to-finish with no crashes. Fix crashes/hangs first; cosmetic
|
||||
rendering (D74/D75/D76/D114/D116) is logged in `docs/GRAPHICS-BACKLOG.md`
|
||||
and comes later.** D120 (blood-stain converter) is the nearest real
|
||||
converter gap; extending `d43_emit.py` for opcode-0x18 is a good
|
||||
subagent brief. After BUNKER1: walk the objective/exit path to a level
|
||||
transition, then the next stage.
|
||||
|
||||
**Real remaining work, in order (updated session M-8):**
|
||||
1. **Input layer (Phase 3)** — IN PROGRESS (M-8 agent, §F D118). `port/src/
|
||||
input.c` was a stub; only a minimal keyboard path in `libultra.c`
|
||||
(`contSnapshotFromKeyboard`) worked. No C-buttons/mouse-look/gamepad/
|
||||
rebinding. This is the top playability blocker — you cannot aim or
|
||||
properly move. Reference: `pd_port/port/src/input.c`.
|
||||
2. **Weapon model doesn't draw (AUDIT-M6 #5)** — NOT STARTED (M-8 agent
|
||||
hit the account session rate-limit before any work; tree untouched).
|
||||
Plan: instrument `gunfire.c` weapon setup (~600-661) + `field_87F`
|
||||
gating (~520/587-598) FIRST with a `GE_D119` printf — does setup even
|
||||
run? Then check `weaponModel.render_pos` = `dynAllocate`'d `rwmtx`
|
||||
lifetime vs the `gunRenderFirstPersonGunModels` (~1605) consumer (on
|
||||
N64 it aliased the persistent `hand->mtxlist`; D102 gave it own
|
||||
storage → arena may recycle). Also `weaponRwPool[192]` capacity vs
|
||||
`modelCalculateRwDataLen(mdlhdr)` on PC, and `flashvisptr` word-index
|
||||
assumption (~640). Narrow `#ifdef PORT` lifetime/sizing fix only — if
|
||||
it needs a behavioural arena change, STOP + write up. Direct `-level_09`
|
||||
boot may spawn Bond holstered — may need forced weapon state or the
|
||||
attract path to get the gun on screen. Gate to playability.
|
||||
3. **The HUD/text X-mirror (D116)** — DEPRIORITISED, parked in
|
||||
`docs/GRAPHICS-BACKLOG.md`. M-11 confirmed the ammo digits use the
|
||||
SAME `textrelated.c` path (no separate renderer — the M-8 premise was
|
||||
wrong) and re-hit the same contradiction: every stage verified
|
||||
non-mirrored, glyphs still flip. **Do not re-static-trace.** Next
|
||||
attempt needs a RenderDoc/apitrace capture or the asymmetric-1-texel
|
||||
experiment — nothing else. All cosmetic rendering issues (D74 dead
|
||||
wrap-block, D75/D76/D77) now tracked in `docs/GRAPHICS-BACKLOG.md`;
|
||||
they rank BELOW level-progression and crash work.
|
||||
4. **Rest of the `struct player` offset pass** — D115 fixed the HIGH
|
||||
`gunfire.c` THROW* bugs; #6 (watch-preview Model pool) + the broader
|
||||
audit remain (`docs/AUDIT-M6-player-offsets.md`).
|
||||
5. Add an f32 value spot-check to `d43_emit.py`'s verify pass (it only
|
||||
checks pointers/opcodes today — the D112 bug passed "ALL CHECKS PASSED").
|
||||
6. **GE_DETERM fixed-tick mode (§F D117)** — deferred as not-narrow; would
|
||||
unlock exact-diff regression testing + reproducible timing bugs. Design
|
||||
is written up. Worth doing once the above land.
|
||||
|
||||
**Session M-7 — see §F D116 + "D116 probe results".** Ran the glyph
|
||||
probe (overseer, after killing a subagent that was drifting toward a
|
||||
global texrect S-swap). Corrected finding: the HUD text mirror is a
|
||||
**per-quad texture-U flip that is NOT path-specific** — the ammo digits
|
||||
"83" are mirrored too (`ppm/frame_000320.ppm`), refuting M-7's earlier
|
||||
"proportional-font-specific" claim. Rect *positions* are correct; only
|
||||
texture content is X-flipped. Every fast3d stage probed clean (glyph
|
||||
bitmap in memory correct, texrect `ul.u=0→lr.u=max`, `import_texture_i8`
|
||||
linear, GL shader UV pass-through) — so the flip is in the GL
|
||||
vertex-buffer/draw layer OR a shared clip-space-X vs U desync on rect
|
||||
quads. **This re-opens D114's shared-mirror hypothesis** (now per-quad
|
||||
U/X, not screen-space) and plausibly also explains inverted guards /
|
||||
mislocated door props. NEXT: shader/vertex-buffer probe — dump per-vertex
|
||||
(x,u) for one glyph quad; render a 1-texel asymmetric test texture to see
|
||||
which axis inverts. `GE_D116`-gated probes left in `textrelated.c` +
|
||||
`gfx_pc.cpp` (zero-cost).
|
||||
|
||||
**Session M-8 wrap — state for next session.** Build GREEN at
|
||||
`587c6856`, tree clean. Committed: D116 part-3 writeup + `[D116/vbo]`
|
||||
probe (`5ff012b4`), `CLAUDE.md`/preflight (`fd25628a`), D117
|
||||
framediff+nondeterminism (`44d9ae98`), D118 input layer
|
||||
(`4ac11fe7`/`587c6856`), handoff reprioritisation. `GE_D116` and
|
||||
`GE_INPUTLOG` probes are in-tree, `getenv`-gated, zero-cost. Weapon-model
|
||||
(#5) agent never ran (rate-limit) — task untouched, plan in item 2 above.
|
||||
Pre-existing stash `stash@{0} "d59 probes WIP"` (~300 lines,
|
||||
gfx_pc/gfx_opengl/crash.c) is NOT from M-8 — provenance unknown, left as
|
||||
found. Next: human input playtest (tune `ge007.ini [Input] MouseAimSpeed`),
|
||||
then weapon-model #5.
|
||||
|
||||
**Session M-8 (continued) — D116 part 3 + input layer.** Killed the
|
||||
drifting D116 agent, ran the probe as overseer: `buf_vbo` (x,u) for
|
||||
glyph quads and the GL texture upload are both runtime-verified
|
||||
non-mirrored (x-left<->u=0), GL shader is `gl_Position=aVtxPos` +
|
||||
UV-passthrough — every stage clean, yet glyphs render X-flipped
|
||||
(contradiction, §F "D116 runtime probe part 3"). Deprioritised as
|
||||
cosmetic. `GE_D116` `[D116/vbo]` probe kept in `gfx_pc.cpp`. Then
|
||||
dispatched the **input-layer agent** (§F D118, in progress) — top
|
||||
playability blocker. `framediff.py` structural mode is the interim
|
||||
regression gate (`tools_pc/golden/` is a nondeterministic D115 capture).
|
||||
|
||||
**Session M-8 — see §F D117.** Visual-regression tooling. Root-caused the
|
||||
frame-to-frame nondeterminism: pure variable-timestep frame pacing
|
||||
(`osGetCount()` = wall clock on PC → `frametiming.c waitForNextFrame`
|
||||
advances logic a real-time-dependent number of 60 Hz ticks per render).
|
||||
PRNG seeding verified correct (`random.c`), not a source. A `GE_DETERM=1`
|
||||
fixed-tick mode was assessed **not narrow** (redesigns VI retrace/tick
|
||||
semantics, deadlock risk in load-screen loops) and deferred with a full
|
||||
design in §F D117 — NOT implemented. Added `tools_pc/framediff.py`
|
||||
(structural/tolerant: 16×12 grid mean-colour + non-clear-% + aHash,
|
||||
`--mask` for HUD, `--exact` for a future deterministic build, `--update`
|
||||
to refresh goldens). Validated against the D115 golden set. No C changes,
|
||||
no probes left in tree.
|
||||
|
||||
**Session M-10 — input playtest + guard-firefight crash chain.**
|
||||
|
||||
- **D119 — guard-attack crash FIXED** (`chraction.c`). Every prior
|
||||
`-level_09` run segfaulted ~frame 1200 the instant a BUNKER guard
|
||||
opened fire: `bondwalkItemGetAutomaticFiringRate` (`gun.c:1334`) ←
|
||||
`chrlvInitActAttack`. ~28 sites pun `weapons_held[]->chr` (really a
|
||||
`WeaponObjRecord*`) as `ChrRecord*` and read `.act_<x>.attack_item`,
|
||||
which on N64 aliases `WeaponObjRecord.weaponnum` (act union @0x2C + 84
|
||||
== 0x80). Pointer widening moves the act union to ~0x38 on PC → garbage
|
||||
negative item id → OOB `g_ItemStats` → crash. Fix: `PUN_ATTACK_ITEM()`
|
||||
macro reads `weaponnum` directly; `#else` branch is textually identical
|
||||
to the original → N64 build unchanged. Class-A (D53.2 type-pun) bug.
|
||||
- **D120 — blood-stain hang GUARDED, not fixed** (`chr.c:3322`). Next
|
||||
blocker, reachable only after D119: first guard bullet-hit →
|
||||
`chrCreateBloodStain` `PointUsage[]` negative-terminated chain walk
|
||||
cycles forever (VI thread keeps posting, logic thread spins, kernel
|
||||
heartbeat trips). Root cause: `tools_pc/d43_emit.py`'s opcode-0x18
|
||||
`ModelRoData_DisplayList_CollisionRecord` conversion is incomplete —
|
||||
6 pointer fields widen the struct and `PointUsage`/`CollisionVertices`
|
||||
sub-array endianness+stride aren't handled (only `CollisionRelatedNode`
|
||||
got the D43/D45 `u32`-vma treatment). **Interim:** `#ifdef PORT` caps
|
||||
both walk loops at `numVertices+8` iters + bounds-checks `index` → game
|
||||
survives, blood decals may be missing/wrong. **Real fix (next session,
|
||||
own subagent brief):** byte-spec the opcode-0x18 record + PointUsage +
|
||||
CollisionVertex sub-arrays vs a converted guard model, extend
|
||||
`d43_emit.py`. Same shape as D69/D88 converter work.
|
||||
- Result: `-level_09` now survives the guard firefight — 45 s+
|
||||
crash-free, frames past 1200 (was: hard segfault ~1200 every run).
|
||||
|
||||
**Session M-10 — input playtested (human).** Fixed in `port/src/input.c`:
|
||||
(a) mouse-look was a draining accumulator → flicks stayed "pressed" ~8
|
||||
frames after you stopped (stuck looking up/down) — now per-poll delta,
|
||||
no carryover; (b) horizontal mouse was routed to C-left/right = GE
|
||||
*sidestep*, and A/D drove analog stick-X = GE *turn* — swapped: mouse X
|
||||
→ stick-X turn (`MOUSE_TURN_GAIN 6.0`), A/D → C-left/right strafe, mouse
|
||||
Y → C-up/down look. Core aim/move now works.
|
||||
**Open input bugs (documented, deferred — fix in a later pass):**
|
||||
- **D118a — mouse yaw slower than pitch.** Horizontal turn (analog
|
||||
stick-X via `MOUSE_TURN_GAIN`) is visibly slower than vertical look
|
||||
(digital C-up/down). Different transfer curves (analog rate-limited vs
|
||||
digital full-press). Needs `MOUSE_TURN_GAIN` bump and/or a matched
|
||||
pitch path; ideally a single tunable `MouseAimSpeed`.
|
||||
- **D118b — mouse Y inverted.** Mouse up → looks down, mouse down →
|
||||
looks up (X unaffected). `aimDY`→C-button sign is backwards for GE's
|
||||
pitch convention (or SDL dy sign assumption wrong). Flip the
|
||||
`aimDY >=`/`<=` C-up/C-down assignment (or default `MouseInvertY`).
|
||||
One-line fix once confirmed against GE pitch sign.
|
||||
- Still TODO from D118: rebinding, gamepad hotplug, `ge007.ini` not yet
|
||||
created (config defaults are hardcoded).
|
||||
|
||||
**Session M-9 (Phase 3) — see §F D118.** SDL input layer implemented.
|
||||
`port/src/input.c` is now real: keyboard+mouse and SDL_GameController,
|
||||
mapped to GE's N64 pad (analog stick = move, C-buttons = aim, mouse-look
|
||||
bridged to digital C-buttons via a clamped accumulator, RMB/LT = aim
|
||||
mode, LMB/RT = fire). `libultra.c`'s SI section delegates to it (single
|
||||
source of controller state; still driven by `osContStartReadData`, no
|
||||
new frame hook). Config: `ge007.ini [Input]` MouseEnabled/MouseAimSpeed/
|
||||
MouseInvertY. Build GREEN, boots `-level_09` crash-free 35 s.
|
||||
**Owed: a human playtest** — live input is untested from the headless
|
||||
agent. Manual checklist: (1) WASD moves Bond, mouse turns/looks (tune
|
||||
`MouseAimSpeed` in ge007.ini if too fast/slow), LMB fires, RMB enters
|
||||
aim mode, Enter opens the pause menu; (2) plug an Xbox pad — left stick
|
||||
moves, right stick aims, triggers fire/aim; (3) `GE_INPUTLOG=1` prints
|
||||
each nonzero OSContPad poll. Rebinding + gamepad hotplug are TODO (§F
|
||||
D118).
|
||||
|
||||
**Session M-6 — see §F D113/D114/D115, `docs/PLAN-M6-playable.md`,
|
||||
`docs/AUDIT-M6-player-offsets.md`, `docs/AGENT-WORKFLOW.md`,
|
||||
`docs/PORT-LEARNINGS.md`.** D113: portal BFS is correct, not the void.
|
||||
D114: matrix chain + converter verified clean, residual = shared fast3d
|
||||
mirror (open). D115: player raw-offset audit + `gunfire.c` THROW* fix
|
||||
(uncommitted). Build green.
|
||||
|
||||
**This session's fixes (all committed, master):**
|
||||
|
||||
| # | commit | one-liner |
|
||||
|---|--------|-----------|
|
||||
| D93 | `164d7f99` | null-room (room 0) NULL-deref guards |
|
||||
| D85 | `493c9838` | `bgWidenRoomGdl` (8→16 `Gfx` + `bswap32`) + `bgSwapRoomVtx`; room DLs decode to real GBI; `bg.c:2448` size-cast bug |
|
||||
| D85 | `6f0208d6` | `ptr_texture_alloc_start` → real `struct texpool` storage (pool looked exhausted → ~630 room textures now resolve; cleared `Bad size for RGBA texture`) |
|
||||
| D94 | `63204a27` | `chrlvInitActAttack` `(s32)`-truncated anim-table index |
|
||||
| D95 | `f35eba91` `933ba52b` | 2× `g_GfxBuffers` (16-byte PC `Gfx`) + raise PC mempool ceiling `0x702F4400→0x70700000` (reclaim ~4 MB DRAM) so it doesn't OOM `MEMPOOL_STAGE` |
|
||||
| D96 | `d86ec483` | `PROPRECORD_STAN_ROOM_LEN` 4→8 (PORT) — prop room-list stack overflow (guards span ≥4 rooms → no terminator → smashed `chrpropsRenderPass` frame; *this* was the "runaway GDL append") |
|
||||
| D97 | `2fbcc556` | clamp negative `damagetype` (US-only OOB `g_DamageTypes[]` read) |
|
||||
| D98 | `000ed6af` | `initBONDdataforPlayer` allocate real PC `sizeof(struct player)` (hardcoded `0x2A80` under-alloc scribbled the master DL) |
|
||||
| D99 | `253caa23` | `Model.animflipfunc` `s32` fn-ptr truncation (flag + direct call, D92 pattern) |
|
||||
| D100 | `8eaad547` | `struct player.model` is an inline `struct Model`, not a `Model*` + 45 filler s32s (PC struct 0x2A8 ≫ 0xB8 hole → `animInit` overran); + dedicated `gaitRwData[]` |
|
||||
| D101 | `b2234f82` | `sub_GAME_7F06DB5C` stashed `ModelNode*`/`RenderPosView*` through an `s32` (the idiom its sibling was already fixed for) |
|
||||
| D102 | `1b078f6d` | 1P weapon `Model` + RW pool punned onto `struct hand` (PC `Model` 0xE8 ≫ N64 0xBC → `modelInit` aliased `datas` onto the pool); dedicated `weaponModel`/`weaponRwPool` |
|
||||
|
||||
**Recurring pattern this session:** the decomp pun-allocates `struct
|
||||
Model` (and `struct player`, `PropRecord`, `struct texpool`, …) into
|
||||
N64-sized holes / hardcoded byte counts. Every one is bigger on x86-64
|
||||
→ overrun → corruption. Fix = give it real inline storage or
|
||||
`sizeof()`-based alloc under `#ifdef PORT`. **Landmine still open**
|
||||
(§F D100): `struct player` / `struct hand` have raw hardcoded-offset
|
||||
accessors (`gunfire.c` `THROWMTX` at `+0xAD8`, …) that are NOT
|
||||
PORT-adjusted — already PC-wrong, a real `struct player` offset pass is
|
||||
owed before grenade/knife-throw code works.
|
||||
|
||||
- Build: `export PATH="/c/msys64/mingw64/bin:$PATH" && ./build-pc.sh ntsc-final`
|
||||
- Sidecar regen: `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`. `data/` is gitignored; if missing,
|
||||
`cp baserom.u.z64 data/ge007.ntsc-final.z64` first (§F "`data/`
|
||||
deletion + recovery").
|
||||
- Repro: `./build-pc/ge007.x86_64.exe -level_09 -ml0 -me0 -mgfx100
|
||||
-mvtx50 -mt700 -ma150` — `-m*` are BUNKER1's `boss.c` per-level sizes;
|
||||
a bare `-level_09` skips them and crashes early (TODO: auto-inject).
|
||||
|
||||
## Next: D75 — 3D model rendering quality
|
||||
|
||||
BUNKER1 renders but character/skeletal models are wrong (see the D75
|
||||
section further down, still accurate). `GE_PCDUMP` + `tools_pc/pixcount.py`
|
||||
to measure; the animated-model path (`animInit` + `struct player` raw
|
||||
offsets, `drawjointlist`, `modelApplyHeadRelations` head/body splice) is
|
||||
the prime suspect. `struct tex` headers are 24 B vs 16 B on PC — a
|
||||
separate open pool-pressure item; a real PC memory-budget pass is owed.
|
||||
|
||||
## What this session (M-2) fixed — all committed
|
||||
|
||||
1. **`-level_09` now works.** `osPiReadIo` was stubbed to 0, so the token
|
||||
string was always empty and N64 debug switches were ignored (only
|
||||
attract mode could reach a level). Now built from `argv[1..]`.
|
||||
2. **Sidecar tables patched in `obInit()`** instead of lazily at first
|
||||
model load — a direct stage boot loaded raw big-endian ROM before.
|
||||
3. **D88.5** — stan tile-name byte-swap in the converter (0/276 →
|
||||
273/273 name matches during pad setup).
|
||||
4. **D88.6** — intro CAMERA `lang1c` is a `u16` pair, not an `s32`
|
||||
(fixed the `langGet` NULL-bank crash).
|
||||
5. **D89** — `init_path_table_links` `[-3]` OOB → SIGILL fix; NULL-tile
|
||||
guard in `sub_GAME_7F0B0914`.
|
||||
6. **D90** — `stanTileDistanceRelated`'s 80-byte zero-fill was clobbering
|
||||
the caller's live stan-tile local (16B struct, 80B clear). The
|
||||
player's spawn stan was fine all along.
|
||||
7. **D91** — `(s32)&D_800442FC[portalnum]` truncation in bg portal cull.
|
||||
8. **D92** — `chrAllocate` `s32` param truncated the ailist pointer;
|
||||
`Model.unka0` `s32` field truncated a stored function pointer.
|
||||
|
||||
**Next steps for the resuming session (render milestone):**
|
||||
1. **D85** — root cause confirmed (8-byte N64 `Gfx` vs 16-byte PC `Gfx`
|
||||
in the unconverted per-room DL blob). Runtime widen+bswap fixup being
|
||||
implemented; verify with `GE_D69BB=1` (real GBI opcodes, not
|
||||
00/01/02/52), then `GE_PCDUMP` + `tools_pc/pixcount.py` for
|
||||
non-degenerate BUNKER1 geometry. See §F "D85 root cause CONFIRMED".
|
||||
Fallback if the widen stalls: soften the four `sysFatalError("Bad
|
||||
size…")` guards in `gfx_pc.cpp` to skip-with-warning.
|
||||
2. Then the front-end **render bugs (D75)**.
|
||||
|
||||
**D88.4 loose end (still open):** `PROPDEF_PC_BYTES` for `VEHICHLE`/
|
||||
`AIRCRAFT`/`TANK`/`AMMO`/`DEPOSIT_IN_ROOM` in `d88_propdefs.py` are
|
||||
placeholder guesses (BUNKER1 doesn't use them). Probe real sizes via
|
||||
`d88_layoutprobe.c` before loading levels that use those types.
|
||||
|
||||
## Known rendering bugs (D75 — still open, orthogonal to the D88 crash)
|
||||
|
||||
Even once the crash chain is cleared, the front end has **broken 3D model
|
||||
rendering** (user-confirmed this session):
|
||||
- Rareware logo: correct (fixed in D73/D74).
|
||||
- **Nintendo logo**: renders but **mispositioned**.
|
||||
- **Gun-barrel intro**: the **James Bond character model is missing
|
||||
entirely**.
|
||||
- **Intro credits / cast roll**: the per-character 3D models **do not
|
||||
appear at all** (names draw, models don't).
|
||||
Pattern: textures/text draw; **animated/skeletal character models never
|
||||
appear**; static 3D (logos) appears but with a bad transform. Leading
|
||||
hypothesis is D75(b) — the animated-model path (`animInit` + raw offsets
|
||||
into `struct player`, cf. D56) is broken independently of the D73 matrix
|
||||
sin/cos fix. Full triage plan in `PCPortResearch.md` §F D75.
|
||||
|
||||
The rest of this document (below) is the **last known-good, committed**
|
||||
status as of commit `8c9c6a2c` (D86+D87 resolved) — still accurate except
|
||||
D88 is now further along (D88.1–3 done/verified, D88.4 is the live crash).
|
||||
Full workstream breakdown (WS1–WS6) in `docs/PLAN-linear-level-sweep.md`.
|
||||
|
||||
## Where things stand
|
||||
|
||||
**D69 (the original "stage load faults" milestone blocker) remains
|
||||
RESOLVED.** `load_bg_file` (bg.c) doesn't fault on BUNKER1. Since then,
|
||||
two more crashes further down the load chain were found and fixed this
|
||||
session (D86, D87), and a third — the current blocker — was root-caused
|
||||
but not fixed (D88).
|
||||
- **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 through `f2beae4b` (M-12: D121 WS1 boot, D122 propDef fix).
|
||||
- **Input layer (Phase 3) landed and playtested** (D118, M-9/M-10).
|
||||
`port/src/input.c` is real: keyboard+mouse + SDL_GameController → N64
|
||||
pad. Core aim/move works. **Open polish bugs (deferred):** D118a mouse
|
||||
yaw slower than pitch; D118b mouse-Y inverted; rebinding / gamepad
|
||||
hotplug / `ge007.ini` generation still TODO.
|
||||
- **Recent fixes (M-10, `d1e93b76`):** D119 guard-attack crash
|
||||
(`weapons_held[]->chr` type-pun) fixed; D120 blood-stain hang guarded
|
||||
(not fixed — `d43_emit.py` opcode-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.
|
||||
|
||||
**D86 RESOLVED.** `modelInitRwData` crash (`model.c:6174`) was a single
|
||||
truncating pointer cast in the player's embedded gait/arm model:
|
||||
`src/game/initplayergaitobject.c:5` did
|
||||
`player_gait_object_header.RootNode = (int)&player_gait_hdr;` — a
|
||||
same-width no-op on N64 that truncates+zero-extends a real 64-bit pointer
|
||||
on PC. Fixed with a narrow `#ifdef PORT` branch assigning the pointer
|
||||
directly. Root-caused via a new node-walk trace (`GE_D86=1`, left in
|
||||
place, gated in `model.c`/`objecthandler_2.c`).
|
||||
## Done this session (M-12) — 2 commits
|
||||
|
||||
**D87 RESOLVED.** Once D86 stopped blocking progress, an idle (no-input)
|
||||
run eventually triggers the front-end's genuine attract-mode demo
|
||||
playback (`select_ramrom_to_play()` picks a random compiled-in demo —
|
||||
this is shipped retail behavior, not a debug feature) and crashed in
|
||||
`ramrom_replay_handler` (`ramromreplay.c`). Root cause: `ramromfilestructure`
|
||||
is a real ROM-compiled asset (big-endian, like everything else) loaded via
|
||||
`romCopyAligned()` — a raw byte copy by design (D66) — with **no
|
||||
byteswap**, so every multi-byte field read back scrambled (e.g. `size_cmds`
|
||||
2 → 33554432) and drove wild pointer arithmetic. Fixed with a `#ifdef PORT`
|
||||
`ramromFixupEndian()` called once after the load (same pattern as the D54
|
||||
cseq fixup). Not BUNKER1-specific — attract mode picks any of 7 demo
|
||||
locations at random, so don't rely on it for BUNKER1-specific testing (see
|
||||
`-level_09` below).
|
||||
- **WS1 / D121 (`02c12068`)** — bare `./build-pc/ge007.x86_64.exe
|
||||
-level_XX` auto-injects that stage's `memallocstringtable[]` `-m*` pool
|
||||
row (`#ifdef PORT` in `boss.c bossInitMainthreadData`). No manual `-m*`
|
||||
args any more. Gotchas recorded: setting `g_DebugAndUpdateStageFlag=1`
|
||||
is the WRONG fix (routes boot through the title-stage intro), and
|
||||
`tokenSetString` *replaces* the whole token buffer so the injected
|
||||
string must carry `-level_XX` forward. See §H D121.
|
||||
- **D88.4 was already resolved** (committed `ff563812`/`4ccc0d3c` weeks
|
||||
ago). The "OPEN — cross-level blocker / `setupDoor` crash" 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.py` had a per-type handler
|
||||
table + a generic fallback; 6 `inherits ObjectRecord` propDef types
|
||||
(47 TINTED_GLASS, 39 VEHICHLE, 40 AIRCRAFT, 45 TANK, 13 AUTOGUN, 20
|
||||
AMMO/MultiAmmoCrate) had **no handler** → fell to the generic
|
||||
word-granular `bswap32`, which swapped the `[s16 obj][s16 pad]` word as
|
||||
one u32 → model id `obj` in the wrong half → OOB `PitemZ_entries[]`
|
||||
deref → crash in `modelLoad` (`loadobjectmodel.c:393`) /
|
||||
`modelInitRwData` (`model.c:6249`). Fixed in the converter
|
||||
(`OBJ_TAIL_DESC` handler) + matching `sizepropdef()` `#ifdef PORT`
|
||||
stride. 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).
|
||||
|
||||
**D88 — SUPERSEDED, see "READ THIS FIRST" at top.** D88.1–D88.3 (header +
|
||||
sub-table width/endian conversion) are now done and verified; D88.4
|
||||
(`propDefs` byteswap) is the live blocker. The paragraph below is the
|
||||
original root-cause writeup, kept for context.
|
||||
**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.
|
||||
|
||||
**D88 (original writeup) — root-caused.** Launch with
|
||||
`-level_09` (NTSC `LEVELID_BUNKER1 = 9`; `boss.c:199-339` decodes
|
||||
`-level_XX` into `g_StageNum`, bypassing the front end/attract-mode
|
||||
entirely — fast, deterministic BUNKER1 repro, crashes in well under a
|
||||
minute instead of waiting ~2 min for attract mode to maybe pick Bunker).
|
||||
Crash: `proplvreset2` (`prop.c:1306`) segfaults reading
|
||||
`g_CurrentSetup.pathwaypoints[i1].padID`. Root cause: the per-level
|
||||
`"Usetup<name>Z"` file (`prop.c:1267`, `struct stagesetup` in
|
||||
`bondtypes.h:4091`) is loaded as raw ROM bytes and has **zero PC porting
|
||||
work done on it** — unlike bg/stan (D69/D80-82) and models (D43/D50).
|
||||
Two compounding problems, not just one:
|
||||
1. The 10 top-level fields (`pathwaypoints`/`waypointgroups`/`intro`/
|
||||
`propDefs`/`patrolpaths`/`ailists`/`pads`/`boundpads`/`padnames`/
|
||||
`boundpadnames`) are declared as real pointers in the live C struct,
|
||||
so on PC they're 8 bytes each (an 80-byte header) instead of the
|
||||
file's real 4-byte-each (40-byte) N64 layout — same class as D79
|
||||
(`bg_room_data` pointer growth). Field 0 reads fine; everything after
|
||||
it is reading the wrong bytes entirely.
|
||||
2. The 4 meaningful bytes each field *does* store are big-endian (the
|
||||
code's own comment: "stores every internal reference as a byte offset
|
||||
from the start of the file") and nothing byte-swaps them — same class
|
||||
as D87.
|
||||
No PORT/byteswap handling exists anywhere in `prop.c` (confirmed by grep).
|
||||
**Not fixed this session** — this is D69-scale format-conversion work: a
|
||||
byte-accurate spec of the whole `Usetup*Z` format (top-level header +
|
||||
every nested sub-table: `waypoint`/`waygroup`/`PropDefHeaderRecord`/
|
||||
`PathRecord`/`AIListRecord`/`PadRecord`/`BoundPadRecord`/`pname`, each
|
||||
likely with its own internal offsets not yet audited) plus either an
|
||||
offline converter sidecar (preferred pattern per AGENTS.md, same shape as
|
||||
`tools_pc/d69_emit.py`) or a careful runtime fixup pass that parses the
|
||||
raw 40-byte N64-packed header by explicit byte offset, byte-swaps each
|
||||
field, and writes results into the PC-widened struct.
|
||||
## Next task — WS4/WS5: drive the 21-level sweep
|
||||
|
||||
**Net effect vs. last session:** the game now runs substantially further
|
||||
— all the way through room-streaming setup, past the intro's model
|
||||
pipeline, and into per-level "Usetup" data — before hitting D88. The
|
||||
"loads without fault" acceptance bar is **still not met**, but the
|
||||
remaining blocker is now narrowly scoped and has a fast, deterministic
|
||||
repro (`-level_09`, no attract-mode wait, no depending on which random
|
||||
demo attract-mode picks).
|
||||
**WS4 matrix DONE** — `docs/LEVEL-STATUS.md`. **7 / 21 PASS** (Bunker1 09,
|
||||
Silo 20, Archives 24, Train 25, Caverns 39, Egypt 32, Cuba 54). 14 crashes
|
||||
in 7 classes C1–C7. Reusable 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).
|
||||
|
||||
## Recommended next steps, in order
|
||||
| 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) |
|
||||
|
||||
1. **D88.** Get a byte-level spec of `Usetup*Z` (start from BUNKER1's
|
||||
file; `strResource` is built as `"U" + "sev" + "Z"`-style name in
|
||||
`prop.c:1253-1265` — check `setup_text_pointers[LEVELID_BUNKER1]` for
|
||||
the exact literal). Decide offline-converter vs. runtime-fixup (D69's
|
||||
bg/stan work is the template for the former; D54's cseq fixup is the
|
||||
template for the latter — given the struct-width mismatch on top of
|
||||
the byteswap, a runtime fixup that manually walks the *raw* 40-byte
|
||||
N64 header by hand (not through the live `stagesetup` struct) into a
|
||||
freshly-populated `g_CurrentSetup` is probably simpler than a full
|
||||
sidecar here, but verify against a raw ROM hex dump either way).
|
||||
2. Once BUNKER1 loads past `proplvreset2`, re-check for further crashes
|
||||
in the same vein (this session found 3 in a row — D86, D87, D88 — each
|
||||
only reachable after the previous one was fixed; expect more).
|
||||
3. Once BUNKER1 reaches a rendered frame with no fault, revisit **D85**
|
||||
(room primary/secondary GDL binaries decode to garbage via
|
||||
`texLoadFromGdl`) — use `GE_PCDUMP="<range>:10"` + `tools_pc/pixcount.py`
|
||||
to confirm non-black, non-degenerate content per the original D69
|
||||
acceptance bar. (This session's `GE_PCDUMP` captures around frame
|
||||
2100-2400 during attract-mode-driven "loading" were still on a HUD/menu
|
||||
screen, not real 3D geometry — don't read too much into pixel counts
|
||||
from before D88 is fixed.)
|
||||
4. Do NOT touch D75/D76/D77 (parked, lower priority, unrelated).
|
||||
**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.
|
||||
|
||||
## Debug tooling added this session (kept, env-gated, zero cost when unset)
|
||||
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".
|
||||
|
||||
- `GE_D86=1` — node-walk trace in `modelInitRwData` (model.c) + a
|
||||
load-identity probe in `load_object_fill_header` (objecthandler_2.c).
|
||||
Resolved the D86 crash; left in place since the same
|
||||
load_object_fill_header/modelInitRwData pipeline could surface new
|
||||
edge cases as more of the game becomes reachable.
|
||||
- `GE_D87=1` — block-setup trace in `iterate_ramrom_entries_handle_camera_out`
|
||||
and consumer trace in `ramrom_replay_handler` (ramromreplay.c). Resolved
|
||||
the D87 crash; left in place as it's a rarely-exercised path (attract
|
||||
mode) worth having visibility into if it acts up again.
|
||||
- Pre-existing `GE_D69STAN=1`, `GE_D69BB=1`, `GE_D69=1` — unchanged, still
|
||||
useful for the bg/stan/D85 load path (see prior session's HANDOFF
|
||||
entries, preserved in git history, for exactly what each logs).
|
||||
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.
|
||||
|
||||
## New: fast, deterministic BUNKER1 repro (no attract-mode wait)
|
||||
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.
|
||||
|
||||
Launch with `-level_09` as a program argument
|
||||
(`./build-pc/ge007.x86_64.exe -level_09`) to skip the front end/attract
|
||||
mode and load BUNKER1 directly — `boss.c:199-339` decodes `-level_XX`
|
||||
(the two digit-chars are consumed as raw ASCII bytes:
|
||||
`g_StageNum = tokenFindLevel[0]*10 + tokenFindLevel[1] - 0x210`; NTSC
|
||||
`LEVELID_BUNKER1 = 9` → `"09"` since `'0'*10 + '9' - 0x210 = 9`). This is
|
||||
now the preferred way to test BUNKER1-specific load/render work — it's
|
||||
faster (crashes/completes in well under a minute vs. ~2+ min waiting on
|
||||
attract mode) and deterministic (not dependent on which of 7 random demo
|
||||
locations attract mode happens to pick).
|
||||
**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). Build is GREEN with all D86/D87 work included.
|
||||
- Run from the **repo root**, not `build-pc/`.
|
||||
- Regenerate sidecars if missing: `python tools_pc/d69_emit.py ntsc-final`
|
||||
(bg/stan) and the D43/D50 model sidecar generator (pcmodels) — both
|
||||
gitignored, not checked in. (Both were already present in this dev
|
||||
environment this session.)
|
||||
- `GE_PCDUMP="<start>-<end>:<stride>"` + `tools_pc/pixcount.py` for frame
|
||||
captures once D88 is fixed and a frame actually renders real BUNKER1
|
||||
geometry.
|
||||
- Crash log: `ge007.crash.log` (repo root); symbolicate with
|
||||
```sh
|
||||
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:337` decodes `-level_XX` as
|
||||
`d0*10 + d1 - 0x210`; LEVELID == the `-level_XX` number.
|
||||
- **Sidecar regen** (after any converter change; `data/` is gitignored):
|
||||
```sh
|
||||
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
|
||||
```
|
||||
If `data/` is missing: `cp baserom.u.z64 data/ge007.ntsc-final.z64`
|
||||
first (see §F "`data/` deletion + recovery").
|
||||
- **Frame capture:** `GE_PCDUMP="<start>-<end>:<stride>"` → `./ppm/`
|
||||
(gitignored). Analyse with `tools_pc/pixcount.py` (non-black content
|
||||
check) and `tools_pc/framediff.py <ppmdir>` (structural regression vs
|
||||
`tools_pc/golden/` — the port is **NOT frame-deterministic**, D117; use
|
||||
`--mask` for the HUD, not an exact compare). `tools_pc/ppm2bmp.py` to
|
||||
view.
|
||||
- **Crashes:** `ge007.crash.log` (repo root). Symbolicate
|
||||
`addr2line -e build-pc/ge007.x86_64.exe -f -C <pc>` (image base
|
||||
`0x140000000`).
|
||||
- gdb **launch** mode is too slow for timing-dependent bugs (unchanged
|
||||
guidance). **New this session:** gdb **attach** mode
|
||||
(`gdb -batch -x cmds.txt -p <winpid>`, `<winpid>` = 4th column of
|
||||
`ps -p <bashpid>`) works well and is fast for "watch a global for a
|
||||
legitimate vs. corrupted write" questions on an already-running,
|
||||
not-yet-crashed process — see `docs/PCPortResearch.md` §F environment
|
||||
reminders for the exact recipe used to root-cause D87.
|
||||
`0x140000000`). 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 of `ps -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 (unchanged, see AGENTS.md)
|
||||
## Non-negotiables (full list in AGENTS.md)
|
||||
|
||||
1. N64 build files untouched.
|
||||
1. N64 build files (`Makefile`, `tools/`, `rsp/`, `ld/`) untouched.
|
||||
2. Game logic unmodified except narrow, documented `#ifdef PORT`
|
||||
ABI/layout exceptions (D86/D87 this session, both logged in
|
||||
`PCPortResearch.md` §F). D88 is explicitly **not** patched with a
|
||||
quick inline hack — it needs the same disciplined
|
||||
spec-then-convert/fixup treatment as D69, logged as an open finding
|
||||
instead per AGENTS.md's "stop and write it up" guidance for anything
|
||||
beyond a narrow, obviously-correct exception.
|
||||
3. Offline sidecar conversion preferred over runtime fixup for whole
|
||||
ROM-asset formats (D69/D80-82 pattern) — likely the right call for
|
||||
D88 too, though a careful runtime fixup is also plausible; decide
|
||||
after the byte-level spec work.
|
||||
ABI/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.
|
||||
3. Prefer an **offline sidecar converter** (`tools_pc/d*_emit.py`,
|
||||
D43/D69/D88 pattern) over a runtime fixup for a whole ROM-asset format.
|
||||
@@ -0,0 +1,70 @@
|
||||
# LEVEL-PLAYTEST — WS6 human completion-validation checklist
|
||||
|
||||
Run **after** `docs/LEVEL-STATUS.md` is all-21 load+render PASS. This is
|
||||
the part that needs real input — a human plays each level start to finish
|
||||
and confirms it matches retail. Findings feed a WS5-style triage round
|
||||
(log each as a crash-class row or a new Dxx candidate).
|
||||
|
||||
## How to run one level
|
||||
|
||||
```sh
|
||||
export PATH="/c/msys64/mingw64/bin:$PATH" && ./build-pc.sh ntsc-final
|
||||
./build-pc/ge007.x86_64.exe -level_XX # bare — pools auto-injected (D121)
|
||||
```
|
||||
|
||||
Controls: `port/src/input.c` — keyboard+mouse or SDL gamepad. Known input
|
||||
polish gaps (do not re-log): D118a mouse yaw slower than pitch, D118b
|
||||
mouse-Y may feel inverted. Difficulty for a bare `-level_XX` boot defaults
|
||||
to Agent; use the menu path (mission select → difficulty) to test 00/007.
|
||||
|
||||
Objective status: `src/game/objective_status.c`
|
||||
(`get_status_of_objective()` `:161`); objective propDefs are
|
||||
`PROPDEF_OBJECTIVE_*` (`bondconstants.h:4332`).
|
||||
|
||||
## Per-level checklist (copy this block per level)
|
||||
|
||||
```
|
||||
### <Level> (-level_XX) difficulty tested: [ ] Agent [ ] Secret Agent [ ] 00 Agent
|
||||
|
||||
- [ ] Spawn point + start orientation match retail
|
||||
- [ ] Geometry / textures / lighting recognisable, no missing rooms
|
||||
- [ ] Guards spawn, path, react, take/deal damage, die correctly
|
||||
- [ ] Doors / lifts / switches / destructibles work
|
||||
- [ ] Pickups (ammo, armour, key items, gadgets) present and collectable
|
||||
- [ ] Each difficulty-gated objective reachable AND registers COMPLETE
|
||||
(objective_status)
|
||||
- [ ] Alarms / reinforcements / scripted events fire
|
||||
- [ ] Level exit trigger fires → MISSION COMPLETE (not a hang / wrong screen)
|
||||
- [ ] Auto-advance to next briefing works
|
||||
- [ ] Frame rate + timing acceptable
|
||||
- Notes / defects:
|
||||
```
|
||||
|
||||
## 21 solo levels (mission order — numbers = `-level_XX`)
|
||||
|
||||
| # | Level | -level | Briefing asset |
|
||||
|---|---|---|---|
|
||||
| 1 | Dam | 33 | UbriefdamZ |
|
||||
| 2 | Facility | 34 | UbriefarkZ |
|
||||
| 3 | Runway | 35 | UbriefrunZ |
|
||||
| 4 | Surface 1 | 36 | UbriefsevxZ |
|
||||
| 5 | Bunker 1 | 09 | UbriefsevbunkerZ |
|
||||
| 6 | Silo | 20 | UbriefsiloZ |
|
||||
| 7 | Frigate | 26 | UbriefdestZ |
|
||||
| 8 | Surface 2 | 43 | UbriefsevxbZ |
|
||||
| 9 | Bunker 2 | 27 | UbriefsevbZ |
|
||||
| 10 | Statue | 22 | UbriefstatueZ |
|
||||
| 11 | Archives | 24 | UbriefarchZ |
|
||||
| 12 | Streets | 29 | UbriefpeteZ |
|
||||
| 13 | Depot | 30 | UbriefdepoZ |
|
||||
| 14 | Train | 25 | UbrieftraZ |
|
||||
| 15 | Jungle | 37 | UbriefjunZ |
|
||||
| 16 | Control | 23 | UbriefcontrolZ |
|
||||
| 17 | Caverns | 39 | UbriefcaveZ |
|
||||
| 18 | Cradle | 41 | UbriefcradZ |
|
||||
| 19 | Aztec | 28 | UbriefaztZ |
|
||||
| 20 | Egypt | 32 | UbriefcrypZ |
|
||||
| 21 | Cuba | 54 | (post-Egypt unlock) |
|
||||
|
||||
(Mission table: `front.c:433`. Cuba = `LEVELID_CUBA`, not in the standard
|
||||
folder list — unlocked after Egypt.)
|
||||
@@ -0,0 +1,160 @@
|
||||
# LEVEL-STATUS — 21 solo-level load+render sweep (WS4)
|
||||
|
||||
Method: bare `./build-pc/ge007.x86_64.exe -level_XX` (per-level `-m*` pools
|
||||
auto-injected, D121), `GE_PCDUMP="80-260:40"`, ~24 s watchdog then
|
||||
`taskkill //F //IM ge007.x86_64.exe`, `ge007.crash.log` symbolicated with
|
||||
`addr2line -e build-pc/ge007.x86_64.exe -f -C <PC>` (image base
|
||||
0x140000000). "render %" = `tools_pc/pixcount.py` non-clear on the last
|
||||
captured frame. Binary at `f2beae4b` + this session's build.
|
||||
|
||||
Last full re-sweep: 2026-08-29, after D123 (C1) + D124-Jungle (C2) landed.
|
||||
Build = `f2beae4b` + `tools_pc/d88_propdefs.py` (D123) + `port/src/gimgfixup.c` (D124).
|
||||
|
||||
| Level | # | Status | Crash site | Class |
|
||||
|---|---|---|---|---|
|
||||
| Bunker1 | 09 | **PASS** 91.7% | — | — |
|
||||
| Silo | 20 | **PASS** 91.7% | — | — |
|
||||
| Archives | 24 | **PASS** 91.1% | — | — |
|
||||
| Train | 25 | **PASS** 87.0% | — | — |
|
||||
| Caverns | 39 | **PASS** 91.6% | — | — |
|
||||
| Egypt | 32 | **PASS** 90.9% | — | — |
|
||||
| Cuba | 54 | **PASS** 92.5% | — | — |
|
||||
| Dam | 33 | **PASS** 83.3% | — (C1 fixed, D123) | — |
|
||||
| Frigate | 26 | **PASS** 90.4% | — (C1 fixed, D123) | — |
|
||||
| Statue | 22 | **PASS** 80.1% | — (C1 fixed, D123) | — |
|
||||
| Streets | 29 | **PASS** 91.6% | — (C1 fixed, D123; no C2 crash this run — timing) | — |
|
||||
| Cradle | 41 | **PASS** 55.4% (low — partial render, no crash) | — (C1 fixed, D123) | — |
|
||||
| Runway | 35 | CRASH | `import_texture_i8` gfx_pc.cpp:821 (fault 0x721b8ee8) — same as Facility | **C2** |
|
||||
| Facility | 34 | CRASH | `import_texture_i8` gfx_pc.cpp:821 (fault 0x72181ee8) — model-GDL relocation align bug, D124, NOT fixed | **C2** |
|
||||
| Jungle | 37 | CRASH | `gfx_sp_matrix` gfx_pc.cpp:1046 (fault 0x401c68e0) — C2 texture crash fixed (D124), now explosion-DL `G_MTX` (D75/matrix family) | **C2m** |
|
||||
| Aztec | 28 | CRASH | `door7F054FB4` propobj.c:13601 (`door->model->obj->RootNode->Child->Child`) | **C3** |
|
||||
| Bunker2 | 27 | CRASH | `door7F054FB4` propobj.c:13523 (door displacement list walk) | **C3** |
|
||||
| Depot | 30 | CRASH | `sub_GAME_7F00324C` prop.c:902 (`sp4C->room` after `walkTilesBetweenPoints`) | **C4** |
|
||||
| Control | 23 | CRASH | `sub_GAME_7F0BA2D4` bg.c:5723 (`portal_pts->numPoints`, via chrprop.c:62) | **C5** |
|
||||
| Surface2 | 43 | CRASH | `modelLoad` loadobjectmodel.c:393 (`PitemZ_entries[modelid].header->RootNode`) | **C6** |
|
||||
| Surface1 | 36 | CRASH | `sndSetupSound` snd.c:653 (fault 0x56220001…, ascii-ish) | **C7** |
|
||||
|
||||
**12 / 21 PASS.** D123 cleared C1 on all 6 (Dam/Frigate/Statue/Cradle/Streets
|
||||
PASS; Runway falls through to C2). D124 cleared Jungle's texture crash but
|
||||
it now hits an explosion-DL matrix crash. 9 crashes remain in 6 classes.
|
||||
|
||||
Notes: Streets passed this run but the C1 agent saw a `gfx_sp_matrix`
|
||||
crash — likely the same explosion-DL `G_MTX` (C2m) as Jungle, timing-
|
||||
dependent (D117 nondeterminism). Cradle renders only 55% — watch for a
|
||||
partial-load issue when it gets a playtest. Frigate/Statue vary 68–90%
|
||||
run to run.
|
||||
|
||||
## Crash classes (most-impactful first)
|
||||
|
||||
### C1 — chr / AI-record pointer deref — FIXED (D123)
|
||||
**RESOLVED.** Root cause: `tools_pc/d88_propdefs.py` (D122's `OBJ_TAIL_DESC`)
|
||||
zeroed the widened `VehichleRecord/AircraftRecord.ailist` slot instead of
|
||||
carrying its pre-populated int AI-list id, so `prop.c:1764/1786` →
|
||||
`ailistFindById(0)` → `GAILIST_AIM_AT_BOND` → `ai()` ran a CHR aim list
|
||||
against `ChrEntityp==NULL`. Fixed via `OBJ_ID_WORDS`. See §F/§H D123.
|
||||
Dam/Frigate/Statue/Cradle now PASS; Runway/Streets fall through to C2.
|
||||
|
||||
<details><summary>original triage notes</summary>
|
||||
|
||||
`chrIsNotDeadOrShot(ChrRecord *self)` faults on `self->actiontype` with
|
||||
`self` bogus (Dam: Rdi=0x1401296a0 → an image address; fault addr 0x8).
|
||||
Reached from `chraidata.c:61` (`m_AimAtBond[]` AI-list region — backtrace
|
||||
frame is inside the global AI-list rodata blob, i.e. the return address
|
||||
was corrupted OR an AI-list entry is being called as a function pointer).
|
||||
Pointer-width / BE-rodata family (PORT-LEARNINGS §A / §C): a per-level
|
||||
`Usetup*Z` guard record (`GuardRecord` type 9 / `GuardAttributeRecord`
|
||||
type 18) or chr-spawn pointer not converted / not pointer-width-adjusted,
|
||||
OR the AI-list bytecode pointers in `chraidata.c` rodata are BE and get
|
||||
called raw. **Biggest single win — 6 levels.**
|
||||
|
||||
</details>
|
||||
|
||||
### C2 — fast3d bad texture pointer (D124) — SPLIT into two causes
|
||||
**Jungle (`0xabcd0824`) — FIXED (D124).** `gimgSyncCompiledGlobalDLs()`
|
||||
never copied the resolved texture pointers into the compiled
|
||||
`globalDL_0xNNN` explosion DLs (slot-detect keyed on a marker `texLoad()`
|
||||
had already erased); the arrays kept link-time `IMAGESEG` words and the
|
||||
first explosion-DL draw fed `0xABCDxxxx` into fast3d. Latent on every
|
||||
level. Fix: `port/src/gimgfixup.c` detects the slot from the compiled
|
||||
array. Jungle now renders ~300 frames then hits a *separate* explosion-DL
|
||||
`G_MTX` crash (`gfx_sp_matrix` gfx_pc.cpp:1046 — D75/matrix family).
|
||||
|
||||
**Facility (`0x72181ee8`) — NOT fixed, separate cause.** Bogus `G_SETTIMG`
|
||||
w1 in a model/prop GDL produced by the runtime `texLoadFromGdl()`
|
||||
relocation in `sub_GAME_7F0762E0` (`objecthandler_2.c:82`): its model-GDL
|
||||
output `dst` pointers are non-16-aligned (N64 8B vs PC 16B `Gfx` stride
|
||||
mix in the `replacementgdl`/`name` offset math). Open D80/D82/D83
|
||||
"model/room GDL runtime conversion unverified" area — shared infra, needs
|
||||
a dedicated pass. Files: `src/game/objecthandler_2.c`, `src/game/tex.c`
|
||||
(`texLoadFromGdl`), `src/game/model.c` (`modelNodeReplaceGdl`).
|
||||
|
||||
**M-14 update (partial, still NOT fixed):** crash cmd pinned —
|
||||
`G_SETTIMG w0=0xfd900000 w1=0x72181ee8` where `w1` is a garbage
|
||||
`tex->data` from the texture-pool path (`texLoadFromModelFileHeader` →
|
||||
`texLoad`), not solely the GDL command-stream copy. Every model file
|
||||
loads at an odd (`&15==1`) `objheader->Switches` base
|
||||
(`mempAllocBytesInBank` does no alignment) and `delta&15` != 0.
|
||||
Full write-up + next-step probe plan: PCPortResearch §F D124-Facility
|
||||
addendum. Likely fix site: the `d43_emit.py`/`pcmodels` sidecar's model
|
||||
texture-blob offsets (N64 8B-Gfx vs PC 16B-Gfx GDL extent).
|
||||
|
||||
### C3/C6 update (M-14, D125) — NOT a converter type-gap
|
||||
Root cause narrowed: the converted `propDefs` blob in RAM does **not** match
|
||||
`tools_pc/d88_propdefs.py`'s offline output (after record 0 it is
|
||||
zeros/garbage), so the runtime `sizepropdef()` walk drifts +101 records by
|
||||
the first door → `setupDoor`/`modelLoad` get the wrong `pdefIndex`/modelid.
|
||||
Histogram diff is clean (no type absent from passing levels). Suspect a
|
||||
destination-offset / `pd_end` / header-reloc bug in `tools_pc/d88_emit.py`'s
|
||||
propdefs emit path (retrofitted post-D122; docstring still says "opaque
|
||||
passthrough"). Not fixed — see §F/§H D125. **d88_propdefs.py `convert_stream`
|
||||
itself and `sizepropdef()` PORT strides are verified correct.**
|
||||
|
||||
### C3 — door model / ModelNode walk (2 levels: Aztec, Bunker2)
|
||||
`propobj.c:13601` derefs `door->model->obj->RootNode->Child->Child`;
|
||||
`:13523` walks the `linkedDoor` list. Fault addrs 0x10 / 0x8 → a null-ish
|
||||
base + field offset. Door prop model not loaded, or `door->model` /
|
||||
`->linkedDoor` pointer-width mis-set. Same family as D122 (propDef obj
|
||||
half-swap) — Aztec/Bunker2 emit a door subtype BUNKER1/Silo don't, or the
|
||||
door propDef `linkedDoor`/`model` field is pointer-width-sized wrong.
|
||||
Files: `src/game/propobj.c`, `tools_pc/d88_propdefs.py` (door handler),
|
||||
`src/game/prop.c`.
|
||||
|
||||
### C4 — prop tile-walk room deref (1: Depot)
|
||||
`prop.c:902` — `*arg1 = sp4C->room` where `sp4C` came back bad from
|
||||
`walkTilesBetweenPoints_NoCallback`. Likely a BG tile/room table not
|
||||
converted for Depot. Files: `src/game/prop.c`, `src/game/bg.c`, BG
|
||||
sidecar.
|
||||
|
||||
### C5 — bg portal points (1: Control)
|
||||
`bg.c:5723` — `portal_pts = g_BgPortals[idx].offset_portal; portal_pts->numPoints`.
|
||||
`offset_portal` unresolved / BE. Via `chrprop.c:62` (chr line-of-sight
|
||||
through portals). Files: `src/game/bg.c`, BG portal sidecar.
|
||||
|
||||
### C6 — modelLoad PitemZ header (1: Surface2)
|
||||
`loadobjectmodel.c:393` — `PitemZ_entries[modelid].header->RootNode`,
|
||||
`header` NULL / `modelid` OOB. Direct D122 continuation (prop/item model
|
||||
id from a propDef record). Fault addr 0x0. Files: `tools_pc/d88_propdefs.py`,
|
||||
`tools_pc/d43_emit.py`, `src/game/loadobjectmodel.c`.
|
||||
|
||||
### C7 — sndSetupSound (1: Surface1)
|
||||
`snd.c:653`. Audio is a parked subsystem (Phase 3) but this is a hard
|
||||
crash on load, not silence. May be dodge-able with a narrow guard until
|
||||
audio lands. Files: `src/snd.c`, `port/src/` audio stubs.
|
||||
|
||||
## Next (9 crashes, 6 classes — priority order)
|
||||
1. **C2** Runway + Facility — model-GDL relocation align bug in
|
||||
`objecthandler_2.c`/`texLoadFromGdl` (D80/D82/D83 area). Shared infra,
|
||||
2 levels + likely helps Jungle/Streets. Biggest win.
|
||||
2. **C3** Aztec + Bunker2 — door model / `linkedDoor`; brief
|
||||
`docs/BRIEF-C3-C6-prop-model.md` (dispatch after any `d88_propdefs.py`
|
||||
work settles).
|
||||
3. **C2m** Jungle (+ Streets intermittent) — explosion-DL `G_MTX`
|
||||
(`gfx_sp_matrix`), D75/matrix family.
|
||||
4. **C6** Surface2 — `PitemZ_entries` modelid (D122/D123 continuation).
|
||||
5. **C5** Control — BG portal `offset_portal` resolve.
|
||||
6. **C4** Depot — BG tile/room table.
|
||||
7. **C7** Surface1 — `sndSetupSound`; parked-audio, may just need a narrow
|
||||
load guard.
|
||||
|
||||
Re-run `tools_pc/level_sweep.sh` after each class fix; keep
|
||||
`-level_09`/`-level_20` green (`tools_pc/framediff.py`).
|
||||
+394
-2
@@ -790,6 +790,37 @@ duration. Asset symbol spot-checks against the ROM confirm correct offsets
|
||||
|
||||
### F. Phase 2 runtime findings (threads, DRAM, addresses)
|
||||
|
||||
**Dxx index — jump to a finding, do not linear-read this section.** §F
|
||||
covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
||||
|
||||
| Range | Topic | Status |
|
||||
|---|---|---|
|
||||
| D24–D30 | green threads / kernel / crash handler | resolved |
|
||||
| D31–D42 | boot ABI: ANIM_DATA, music seq-table, libaudio banks, Globalimagetable rebase, GL context, rsp toggle | resolved |
|
||||
| D43–D50 | model-file loading → offline sidecar "Plan B" (`d43_emit.py`); D50.6 texCopyGdls full-slot copy | resolved |
|
||||
| D51–D58 | font pixeldata, osGetCount tick rate, model RW pools, cseq BE header, synth slots, RLE menu bg, watch raw offsets, gun-barrel DL | resolved |
|
||||
| D59–D68 | intro render: blood-RLE clobber, DMA validate, OSMesgQueue, HEADS/BODIES sentinels, romCopy width, image_entry, Globalimagetable BE→LE | resolved |
|
||||
| D69 · D78–D84 | stage load (`load_bg_file`): bg/stan offline sidecar (`d69_emit.py`) + StandTile/bg_room_data ABI | resolved |
|
||||
| D70–D74 | intro-logo pixels: C-array bswap, UV path, sinf/cosf `DVAL()`, texture-import truncation | resolved |
|
||||
| D75 · D76 · D77 | front-end 3D model transforms · disclaimer screen · audio | OPEN (parked, `GRAPHICS-BACKLOG.md`) |
|
||||
| D85 | room primary/secondary DL → `texLoadFromGdl` garbage | OPEN (safety-netted; widen/pool fixes landed, geometry now renders) |
|
||||
| D86 · D87 | modelInitRwData truncated ptr · attract-demo BE `ramromfilestructure` | resolved |
|
||||
| D88.1–D88.3 · D88.5–D88.6 | `Usetup*Z` header + sub-table width/endian conversion (`d88_emit.py`) | resolved |
|
||||
| D88.4 | `propDefs` polymorphic record stream not byteswapped → `setupDoor` crash | **OPEN — cross-level blocker** |
|
||||
| D89–D92 | stage-load→frame: stan zero-fill overrun, portal address trunc, chr/AI spawn ptrs | resolved |
|
||||
| D93–D102 | struct-pun / hardcoded-size pass: player alloc, player.model inline Model, weapon Model pun, master-DL buffer | resolved (D95 pool bump partial) |
|
||||
| D103–D107 | BUNKER1 viewport height, depth-buffer clear (`G_CLEAR_DEPTH_EXT`), portal near-plane, LOD mip tile | resolved |
|
||||
| D108–D112 | skeletal models: `d43_emit.py put_f32` byte-reversal bug | resolved |
|
||||
| D113–D116 | portal BFS ok · matrix chain ok · player raw-offset audit + `gunfire.c` THROW* · HUD text X-mirror | D116 OPEN (parked — DO NOT re-static-trace) |
|
||||
| D117 | frame nondeterminism root-caused; `GE_DETERM` deferred; `framediff.py` structural | resolved (mode deferred) |
|
||||
| D118 | SDL input layer (`port/src/input.c`); D118a/b mouse polish open | resolved (polish open) |
|
||||
| D119 · D120 | guard-attack `weapons_held[]->chr` pun crash · blood-stain `PointUsage[]` chain hang (guarded, `d43_emit.py` opcode-0x18 gap) | D119 fixed · D120 guarded |
|
||||
| D121 | WS1 frictionless per-level boot: bare `-level_XX` injects its `memallocstringtable[]` `-m*` row (`#ifdef PORT` in `boss.c`) | resolved |
|
||||
| D122 | per-level prop/item model-load crash (Dam/Facility/Runway `modelLoad`/`modelInitRwData`): `d88_propdefs.py` had no handler for 6 ObjectRecord-derived propDef types (47/39/40/45/13/20) → generic arm half-swapped the `[s16 obj][s16 pad]` word → OOB `PitemZ_entries[]` | converter fixed; residual chr/fast3d crashes on those levels are separate |
|
||||
| D123 | crash class C1: `chrIsNotDeadOrShot` NULL deref on 6 levels (Dam/Runway/Frigate/Statue/Streets/Cradle). D122's `OBJ_TAIL_DESC` zeroed the widened `VehichleRecord/AircraftRecord.ailist` slot (w32), but `prop.c:1764/1786` reads a pre-populated int AI-list id there → `ailistFindById(0)` → `GAILIST_AIM_AT_BOND` → `ai()` runs a CHR aim list with `ChrEntityp==NULL` | converter fixed (`OBJ_ID_WORDS`); C1 cleared on all 6, residual crashes are fast3d (C2) |
|
||||
| D125 | crash classes C3+C6 (Aztec/Bunker2/Surface2): the converted `propDefs` blob in RAM ≠ `d88_propdefs.py` offline output — after record 0 it is zeros/garbage, so the `sizepropdef()` walk drifts (+101 records by the first door) and every `pdefIndex` lookup (`setupDoor`→`linkedDoor`, `modelLoad` modelid) hits the wrong record. NOT a forgotten-type gap (histogram diff clean). Suspect a destination-offset / `pd_end` / header-reloc bug in `tools_pc/d88_emit.py`'s propdefs emit, not in the converter | **not fixed** — investigation cut for usage reset; next: diff pccg.bin bytes vs `convert_stream`, trace `pd_start/pd_end/do` in d88_emit.py |
|
||||
| D124 | crash class C2: fast3d bad texture pointer. **Jungle** (`0xabcd0824`): `gimgSyncCompiledGlobalDLs()` slot-detect keyed on the post-fixup marker, which `texLoad()` had already overwritten → compiled `globalDL_0xNNN` explosion DLs kept link-time `IMAGESEG` words → latent on every level, tripped by the first explosion-DL draw. **Facility** (`0x72181ee8`): separate — model/prop GDL from the `texLoadFromGdl()`/`sub_GAME_7F0762E0` relocation path writes non-16-aligned `dst` (N64 8B vs PC 16B `Gfx` stride mix in `objecthandler_2.c`), open D80/D82/D83 area | Jungle fixed (`port/src/gimgfixup.c`); Facility diagnosed, not fixed |
|
||||
|
||||
Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS
|
||||
threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software
|
||||
RSP (`port/fast3d/`). The boot path now runs: ROM map → DRAM reserve → video
|
||||
@@ -2193,7 +2224,10 @@ word1=0x14 room-fileposition list), bg_room_data records carry more
|
||||
stanZ files go through stanDetermineEOF/stanLoadFile. TEMP D69 probe in
|
||||
ob.c (GE_D69) logs name/index/rom_size/hw_address per BG load.
|
||||
|
||||
### G. Phase 2 status (current)
|
||||
### G. Phase 2 status snapshot (HISTORICAL — frozen ~D74)
|
||||
|
||||
> Stale point-in-time snapshot. **Current status: `docs/HANDOFF.md`.**
|
||||
> Finding detail: §F index above + §H below.
|
||||
|
||||
Done through **D68**: PD fast3d integrated (`port/fast3d/`); GE's real
|
||||
`src/sched.c` + pthread kernel; dual-mapped DRAM; ROM mapped at cart base;
|
||||
@@ -2243,7 +2277,14 @@ stripped, but previously committed TEMP diagnostics (D63 blocks, GE_D71LOG,
|
||||
and the older D51–D66 leftovers) are still in the tree — strip list in
|
||||
HANDOFF Task 3. Build is GREEN.
|
||||
|
||||
### H. Handoff & plan (current session)
|
||||
### H. Finding log continued (D32 procedure, D70–D121)
|
||||
|
||||
> This section began as a per-session "handoff & plan" and grew into the
|
||||
> D70–D121 finding log. The **D32 repeatable fix procedure** below is
|
||||
> permanent reference; the rest is the finding archive (use the §F index).
|
||||
> **Current task and plan: `docs/HANDOFF.md` +
|
||||
> `docs/PLAN-linear-level-sweep.md`.** Session narrative:
|
||||
> `docs/HANDOFF-ARCHIVE.md`.
|
||||
|
||||
Full paste-ready brief: **docs/HANDOFF.md** (primary thread: D69 BG/stan
|
||||
stage loading — the milestone blocker; secondary: final pixel check of the
|
||||
@@ -3931,3 +3972,354 @@ module, as in D56, means unwind depth is limited — confirm callers by code-
|
||||
path analysis + behavior). Standalone probe compiles need `-std=c11` (without
|
||||
it `typedef s32 bool` in bondtypes.h breaks under gnu23) and pointer-difference
|
||||
arithmetic instead of offsetof (include/stddef.h is #if 0'd).
|
||||
|
||||
---
|
||||
|
||||
**D121 (session M-12, WS1) — frictionless per-level boot.** A bare
|
||||
`./build-pc/ge007.x86_64.exe -level_XX` with no `-m*` args booted every
|
||||
level with the default memory pools and OOM-crashed early in the load
|
||||
sequence; you had to hand-copy the per-level `-ml -me -mgfx -mvtx -mt -ma`
|
||||
row from `memallocstringtable[]` (`boss.c:101`) onto the command line.
|
||||
|
||||
*Root cause.* On N64 the per-stage auto-inject loop in `bossMainloop`
|
||||
(`boss.c` ~line 425: `tokenSetString(memallocstringtable[i].string)`)
|
||||
supplies that row, but it is gated on `g_DebugAndUpdateStageFlag`. With
|
||||
`-level_` present that flag is left to `rmonGetToken()` (`boss.c:178`),
|
||||
which the PC stub (`port/src/n64stubs.c`) returns 0 for, so the loop never
|
||||
runs.
|
||||
|
||||
*Rejected fix.* Setting `g_DebugAndUpdateStageFlag = 1` when `-level_` is
|
||||
present (the "mirror `boss.c:199-202`" idea in the old HANDOFF) is **not
|
||||
behaviour-preserving on PC**: it makes boot render the full title-stage
|
||||
intro (logos → gun-barrel → cast) instead of loading the level directly.
|
||||
The flag does more than pick the token row.
|
||||
|
||||
*Fix (`#ifdef PORT` in `bossInitMainthreadData`, after the flag's own
|
||||
`tokenSetString` block).* When `-level_` is present and no `-m` token was
|
||||
given, look up the stage's `memallocstringtable[]` row (same match the N64
|
||||
loop does) and `tokenSetString` it — **prefixed with `-level_XX` (and
|
||||
`-hard N` if present)**, because `tokenSetString` (`src/token.c:41`)
|
||||
`strcpy`s over the whole token buffer; without the prefix `bossMainloop`'s
|
||||
`tokenFind(1, "-level_")` returns NULL and it boots the title stage. No
|
||||
flag change, no control-flow change.
|
||||
|
||||
*Verified.* Bare `-level_09` now loads BUNKER1 directly (85–92 % non-clear
|
||||
frame content from frame 30, no intro), matching the old
|
||||
`-level_09 -ml0 -me0 -mgfx100 -mvtx50 -mt700 -ma150` repro. Residual
|
||||
BUNKER1 prop/`modelLoad` crash is intermittent (D117 nondeterminism +
|
||||
D88.4) and pre-existing — out of WS1 scope. HANDOFF repro line updated to
|
||||
drop the manual `-m*` list.
|
||||
|
||||
**D122 (session M-12) — per-level prop/item model-load crash: propDef
|
||||
converter missing handlers for ObjectRecord-derived record types.**
|
||||
`-level_33` (Dam), `-level_34` (Facility), `-level_35` (Runway) crashed
|
||||
deterministically before frame 1: `modelLoad` (`loadobjectmodel.c:393`,
|
||||
`PitemZ_entries[modelid].header->RootNode` with an OOB `modelid` — `Rax`
|
||||
held ASCII) and `modelInitRwData` (`model.c:6249`). BUNKER1 (`-level_09`)
|
||||
and Silo (`-level_20`) were fine.
|
||||
|
||||
*Root cause.* The D88.4 propDef stream converter (`tools_pc/d88_propdefs.py
|
||||
convert_record`) had per-type handlers for most record layouts but **four
|
||||
`inherits ObjectRecord` types fell through to the generic arm**: 47
|
||||
TINTED_GLASS, 39 VEHICHLE, 40 AIRCRAFT, 45 TANK (plus 13 AUTOGUN and 20
|
||||
AMMO/MultiAmmoCrate, exposed once the first four were fixed). The generic
|
||||
arm bswap32's N64 word 1 as a single 32-bit value, but for an
|
||||
ObjectRecord that word is `[s16 obj][s16 pad]` — it needs `_hh_word`
|
||||
(swap each half in place). Result: `obj` (the model id, read by
|
||||
`domakedefaultobj` at `prop.c:155` → `modelLoad`) landed in the wrong
|
||||
half → garbage/OOB index into `PitemZ_entries[]`. The generic arm also
|
||||
never widened the ObjectRecord's embedded pointer slots (prop*/model* 4→8
|
||||
B), so every tail field was mis-offset too. BUNKER1/Silo use none of
|
||||
these six types (verified with a per-level `PROPDEF_N64_WORDS` type
|
||||
histogram), which is why they were unaffected — the stream *walk* was
|
||||
self-consistent for all 21 levels (`convert_stream` validates region
|
||||
length), only the *field contents* of these records were wrong.
|
||||
|
||||
*Fix (converter only — `tools_pc/d88_propdefs.py`).* Added `OBJ_TAIL_DESC`
|
||||
= {type: (ptr_word_set, hh_word_set)} for types 47/39/40/45/13/20 and a
|
||||
handler that emits the real 144-byte PC ObjectRecord prefix
|
||||
(`_emit_object_prefix`, which does `obj` correctly) followed by the tail
|
||||
with pointer members widened 4→8 B and 8-aligned, `_hh_word` for u16-pair
|
||||
words (vehicle aioffset/aireturnlist; ammo-crate `slots[]`), `_bswap32`
|
||||
elsewhere. Updated `PROPDEF_PC_BYTES` (39: 176→208, 40: 180→208, 45:
|
||||
224→248; 47/13/20 already correct) and the matching
|
||||
`sizepropdef()` `#ifdef PORT` arms in `loadobjectmodel.c` (VEHICHLE
|
||||
44→52, AIRCRAFT 45→52, TANK 56→62 words; N64 values kept in the trailing
|
||||
comment / `#else` `#if 1` switch). No game-logic change. Regenerate:
|
||||
`python tools_pc/d88_emit.py ntsc-final --regen` (21/21, ALL CHECKS
|
||||
PASSED).
|
||||
|
||||
*Verified.* `-level_33/34/35` all get past the model-load chain now (no
|
||||
`loadobjectmodel.c:393` / `model.c:6249` crash). BUNKER1 unregressed
|
||||
(83–92 % non-clear frame content, frames 80–200). Silo unregressed
|
||||
(exercises the new type-20 handler — no crash).
|
||||
|
||||
*Residual (separate blockers, out of D122 scope — hand to the level
|
||||
sweep).* Dam + Runway now crash later in `chrIsNotDeadOrShot`
|
||||
(`chraction.c:4483`, `self` = an image rodata address — guard/chr setup,
|
||||
not propDef-related). Facility crashes in `import_texture_i8`
|
||||
(`gfx_pc.cpp:821`, bad texture pointer — fast3d). Neither is touched by
|
||||
this fix.
|
||||
|
||||
*Tail-layout confidence: medium* for 39/40/45 (VehichleRecord/
|
||||
AircraftRecord/TankRecord field offsets have "locs need confirming" notes
|
||||
in `bondtypes.h`; the pointer-word sets and total sizes are derived from
|
||||
the struct as written and may need a nudge if a level actually drives
|
||||
tank/vehicle motion). **High** for 47/13/20 (offsets are known) and for
|
||||
the crash fix itself (the `obj`-field half-swap is unambiguous).
|
||||
|
||||
---
|
||||
|
||||
**D123 (session M-13) — crash class C1: `chrIsNotDeadOrShot` NULL deref on
|
||||
6 levels (Dam 33, Runway 35, Frigate 26, Statue 22, Streets 29, Cradle
|
||||
41). Converter zeroed the vehicle/aircraft `ailist` id. FIXED
|
||||
(`tools_pc/d88_propdefs.py` only).**
|
||||
|
||||
*Symptom.* Bare `-level_33` etc. crashed before frame 1 in
|
||||
`chrIsNotDeadOrShot` (`chraction.c:4483`, `self->actiontype`), fault addr
|
||||
0x8 → `self ≈ NULL`. (The log's frame #1 `0x1401296a0` / `chraidata.c:61`
|
||||
was a stale stack-scan hit, not a real frame — misled the original
|
||||
triage.)
|
||||
|
||||
*Real backtrace (gdb):* `objTick` (`propobj.c:5504`) →
|
||||
`ai((PropDefHeaderRecord *)poTruck, PROP_TYPE_OBJ)` →
|
||||
`ai()` sees `Entityp->type == 39` (PROPDEF_VEHICHLE) so sets
|
||||
`VehichleEntityp`, `AiListp = VehichleEntityp->ailist`, `ChrEntityp =
|
||||
NULL`. `AiListp` resolved to `m_AimAtBond` (`GAILIST_AIM_AT_BOND` == id 0).
|
||||
Its first opcode is `AI_TRYFireOrAimAtTarget` →
|
||||
`actor_aim_at_actor(ChrEntityp=NULL,…)` → `chrIsNotDeadOrShot(NULL)`.
|
||||
|
||||
*Root cause.* `prop.c:1764` does `pdef_veh->ailist =
|
||||
ailistFindById(pdef_veh->ailist)` — it reads the **pre-populated integer
|
||||
AI-list id** out of the ROM record (VehichleRecord.ailist @ N64 0x80;
|
||||
Dam's is `0x040a` = 1034, a per-level list). But D122's `OBJ_TAIL_DESC`
|
||||
put word 32 in `ptr_word_set`, whose handler emits **8 zero bytes** for
|
||||
the widened 4→8B slot (correct for `path*`/`Sound*`, which `prop.c`
|
||||
overwrites with 0 — but wrong for `ailist`, read before overwrite).
|
||||
Result: `ailistFindById(0)` → `isGlobalAIListID(0)` true → global list id 0
|
||||
= `GAILIST_AIM_AT_BOND` → a vehicle runs a CHR-only aim list with a NULL
|
||||
chr. `AircraftRecord.ailist` (`prop.c:1786`) is identical. BUNKER1/Silo
|
||||
emit no vehicle/aircraft propDef → unaffected. All 6 crashing levels have
|
||||
a vehicle or aircraft prop.
|
||||
|
||||
*Fix (converter only).* New `OBJ_ID_WORDS = {39:{32}, 40:{32}}` in
|
||||
`tools_pc/d88_propdefs.py`; the `OBJ_TAIL_DESC` walk now emits
|
||||
`_bswap32(w)` into the low 4 bytes of the 8-aligned slot for id words
|
||||
(high 4 stay zero), so the u16/s32 id survives into the widened pointer
|
||||
field. Words 32 removed from the 39/40 `ptr_word_set`. Struct size
|
||||
unchanged → no `sizepropdef()` edit. TANK (45, `collision*`) and AUTOGUN
|
||||
(13, `unkC4/unkC8/beam`) left as ptr/zero — verified genuinely
|
||||
runtime-populated (`prop.c:1712` / `setupAutogun` `prop.c:694`). Regen:
|
||||
`python tools_pc/d88_emit.py ntsc-final --regen` (21/21 ALL CHECKS
|
||||
PASSED).
|
||||
|
||||
*Verified.* `chrIsNotDeadOrShot` crash gone on all 6. Dam 83%, Statue
|
||||
80%, Frigate 68–90%, Cradle 77% non-clear frames, no crash log.
|
||||
BUNKER1 (91.7%) + Silo (91.7%) unregressed. Runway and Streets now crash
|
||||
**later** in fast3d (`palette_to_rgba32`/`import_texture_i4`
|
||||
`gfx_pc.cpp:851`; `gfx_sp_matrix` `gfx_pc.cpp:1046`) — class C2/other
|
||||
track, not C1. Frigate/Statue/Cradle/Dam reach a frame cleanly.
|
||||
|
||||
*Confidence: high* — root cause reproduced in gdb, fix is a 1-field
|
||||
converter change matching the exact `prop.c` read pattern, and the id
|
||||
value (0x040a) matches the N64 `UsetupdamZ.c` Vehicle record tail.
|
||||
|
||||
**D124 (session M-13) — crash class C2: fast3d handed a garbage texture
|
||||
pointer on Facility (`-level_34`) and Jungle (`-level_37`).** Symptom:
|
||||
early deterministic crash — Facility `import_texture_i8` `gfx_pc.cpp:821`
|
||||
fault `0x72181ee8`; Jungle `gfx_tex_normalize_source` `gfx_pc.cpp:644`
|
||||
fault `0xabcd0824` (= `IMAGESEG(0x824)`). Two *different* root causes:
|
||||
|
||||
*Jungle — FIXED.* `gimgSyncCompiledGlobalDLs()` (`port/src/gimgfixup.c`,
|
||||
D68) is meant to copy the texture pointers that `texLoadFromDisplayList()`
|
||||
patched into the ROM copy of the Globalimagetable back into the compiled
|
||||
`globalDL_0xNNN` shadow arrays (`assets/oddtextures.c`) that `explosion.c`
|
||||
executes via `g_ExplosionDisplayLists[]`. Its slot-detect test was
|
||||
`p[6]==0xCD && p[7]==0xAB` — the *post-fixup marker* — but by the time it
|
||||
runs `texLoad()` has **already replaced every IMAGESEG w1 in the ROM copy
|
||||
with a real pointer**, so that test never matches and the sync copied
|
||||
*nothing*. The compiled arrays kept their link-time `IMAGESEG(id) =
|
||||
0xABCD0000|id` words; the first explosion/smoke/particle DL that
|
||||
Facility/Jungle render feeds `0xABCDxxxx` straight into fast3d
|
||||
`seg_addr()` (falls through to `return (void*)w1`) → `import_texture`
|
||||
deref. The 7 "working" levels are only working because nothing triggered
|
||||
an explosion-DL draw inside the ~22 s capture — **the bug was latent in
|
||||
every level.** *Fix:* detect the slot from the **compiled** array instead
|
||||
(`dst[j]` is a `G_SETTIMG` whose w1 is still `0xABCDxxxx`), then copy the
|
||||
ROM copy's resolved `*(u32*)(p+4)`. `port/src/gimgfixup.c` only; no
|
||||
converter/sidecar change. Verified: Jungle now renders ~300 frames
|
||||
(91.7% non-clear) before a *separate* downstream crash (`gfx_sp_matrix`
|
||||
`gfx_pc.cpp:1046`, bad G_MTX seg addr in the same explosion-DL stream —
|
||||
those DLs also carry unconverted `G_MTX` words; follow-up, D75/matrix
|
||||
family). BUNKER1 (83%) + Silo (91.7%) unregressed.
|
||||
|
||||
*Facility — NOT fixed (separate class).* Fault `0x72181ee8` is a bogus
|
||||
`G_SETTIMG` w1 (`fmt=4 siz=2 w=0`, prev cmd an unconsumed `0xba` GE
|
||||
tex-macro opcode) in a DL living in V1 DRAM — a **model/prop GDL from the
|
||||
runtime `texLoadFromGdl()` relocation path** in `sub_GAME_7F0762E0`
|
||||
(`objecthandler_2.c:82`), not a room GDL and not the global bank.
|
||||
`GE_C2` probe of `texLoadFromGdl` shows its model-GDL calls (second batch,
|
||||
`src≈0x706a****`) write to **non-16-aligned `dst`** (e.g.
|
||||
`0x701eac01`, `0x701eb7d5`) — the `replacementgdl`/`name`(=srcsize)
|
||||
offset arithmetic in `objecthandler_2.c` still mixes N64 8-byte and PC
|
||||
16-byte `Gfx` strides, so converted commands land mid-slot and a
|
||||
`G_SETTIMG` w1 reads as garbage. This is the open D80/D82/D83
|
||||
"model/room GDL runtime conversion not verified" area — shared infra
|
||||
touched by every level; out of C2 budget, left for a dedicated pass.
|
||||
BUNKER1/Silo/Jungle don't hit it because their visible prop models
|
||||
happen to convert cleanly; Facility's do not.
|
||||
|
||||
*Probes:* none left in tree — `GE_C2` scratch prints removed after
|
||||
root-cause. *Files touched:* `port/src/gimgfixup.c` (fix),
|
||||
`docs/PORT-LEARNINGS.md`, `docs/PCPortResearch.md`, `docs/LEVEL-STATUS.md`.
|
||||
*Confidence:* Jungle fix **high** (mechanism proven with a `[C2sync]`
|
||||
trace showing 32 slots going `abcd08xx -> 700e****`, verified render).
|
||||
Facility diagnosis **medium** (probe evidence strong; exact off-by in
|
||||
`objecthandler_2.c` not yet pinned).
|
||||
|
||||
**D125 (session M-14) — crash classes C3 + C6: the converted `propDefs`
|
||||
blob in RAM does not match `tools_pc/d88_propdefs.py`'s offline output;
|
||||
the `sizepropdef()` walk drifts, so every `pdefIndex`-keyed lookup
|
||||
(`setupDoor` → `linkedDoor`, `weaponAssignToHome`, `modelLoad(modelid)`)
|
||||
gets the wrong record.** NOT root-caused; investigation cut for a usage
|
||||
reset. Affects Aztec (`-level_28`, `propobj.c:13523/:13601`), Bunker2
|
||||
(`-level_27`, same `door7F054FB4`), Surface2 (`-level_43`,
|
||||
`loadobjectmodel.c:393` `PitemZ_entries[modelid].header`).
|
||||
|
||||
*Evidence.* Instrumented the `proplvreset2` propDef walk
|
||||
(`prop.c:1865`, `#if defined(PORT)` `getenv("GE_C3")` print of
|
||||
`pdefIndex / phead->type / sizepropdef(phead) / phead / g_CurrentSetup.propDefs`)
|
||||
and the `setupDoor` `linkedDoor` resolve (`prop.c:1206`). On `-level_27`
|
||||
(`UsetupsevbZ`, `setup_text_pointers[27]`):
|
||||
- `g_CurrentSetup.propDefs = 0x701ce814`.
|
||||
- Runtime walk: idx0 `type=35` (WATCH_MENU, correct), then idx1–4
|
||||
`type=0`, idx6 `type=112`, idx15 `type=26`, … — i.e. after the first
|
||||
record the blob is **zeros / garbage**, but `sizepropdef` still
|
||||
strides the phantom `type=35` at idx0/5/10 forward 16 B each.
|
||||
- By the first real DOOR the walk has counted **147** records where the
|
||||
offline converter (`convert_stream(UsetupsevbZ)`) puts it at **46**
|
||||
(verified: offline output = 346 records, DOOR at idx 46, self-
|
||||
consistent with `PROPDEF_PC_BYTES` == `sizepropdef()` PORT strides —
|
||||
every type cross-checked against `d88_layoutprobe` sizes, all match).
|
||||
- `setupDoor` then gets `arg2=147` for a stream-index-46 door;
|
||||
`linkedDoorOffset(-1) + 147 = 146` → `setupGetPtrToCommandByIndex(146)`
|
||||
returns a `type=0` record → `door->linkedDoor` garbage → fault
|
||||
walking the linked-door list in `door7F054FB4`. Surface2's C6 is the
|
||||
same mechanism one step earlier: a drifted `pdefIndex`/`modelid`.
|
||||
|
||||
*What is NOT the bug (ruled out).* (a) `d88_propdefs.py` `convert_stream`
|
||||
itself — self-test passes, offline walk of all 3 crashing levels'
|
||||
converted output is clean, DOOR/linkedDoorOffset values correct, stream
|
||||
tiles `[propDefs, intro)` exactly (`end=OK` for all 21). (b)
|
||||
`sizepropdef()` PORT strides — every `case` equals
|
||||
`PROPDEF_PC_BYTES[type]/4` and equals the compiler `sizeof` of the widened
|
||||
struct (checked via `d88_layoutprobe`). (c) DOOR converter layout —
|
||||
`linkedDoorOffset@144`, `linkedDoor@216`, `unkcc@224`, size 296 all match
|
||||
`d88_layoutprobe`; tail pointer-widening walk is correct end-to-end. (d)
|
||||
The per-type histogram diff (brief's suggested approach): the 3 crashing
|
||||
levels emit no propDef type absent from the 12 passing levels — TINTED_GLASS
|
||||
and AUTOGUN appear in passing Caverns/Control too. **This is not a
|
||||
forgotten-type converter gap like D122/D123.**
|
||||
|
||||
*Best hypothesis (confidence: mechanism high, exact fault medium).* The
|
||||
converted `propdefs_pc` bytes produced in `tools_pc/d88_emit.py`
|
||||
(`convert_propdefs`, line 320) are **not landing in the sidecar at the
|
||||
offset `g_CurrentSetup.propDefs` resolves to**, OR the `propDefs` header
|
||||
field is not being relocated to the converted blob's new offset, OR
|
||||
`pd_end` (the `'propdefs'` region end from the region-tiling at
|
||||
`d88_emit.py:315-318`) is computed wrong so only the first record is
|
||||
converted and the rest of the region is left as `bytearray(total_new)`
|
||||
zero-fill. The "idx0 correct, then zeros" signature points hard at a
|
||||
destination-offset / length bug in `d88_emit.py`'s propdefs emit
|
||||
(lines 266-267, 308-338, 471-476), not in `d88_propdefs.py`. Note
|
||||
`d88_emit.py`'s module docstring still describes propDefs as an "opaque
|
||||
passthrough blob" (stale post-D122) — the emit path was retrofitted and
|
||||
may not have been fully reconciled with the region-tiling / delta-reloc
|
||||
logic.
|
||||
|
||||
*Next step.* Dump the actual bytes at `data/pccg-ntsc-final/pccg.bin` for
|
||||
`UsetupsevbZ`'s propDefs region and compare to `convert_stream` output;
|
||||
add a `[D88emit]` print of `pd_start / pd_end / len(propdefs_pc) / do`
|
||||
(destination offset) in `d88_emit.py`; check whether the `propDefs`
|
||||
header word in the emitted file points at `do`. Likely a 1-region
|
||||
off-by-one in the sorted region list (intro vs propDefs ordering) or the
|
||||
converted blob written at the pre-growth offset.
|
||||
|
||||
*Probes:* **none left in tree** — the two `#if defined(PORT)`/`GE_C3`
|
||||
scratch prints in `src/game/prop.c` (lines ~1206 and ~1865) and the
|
||||
temp scan scripts `tools_pc/c3_scan.py` / `tools_pc/c3_doors.py` were all
|
||||
reverted/deleted. *Files touched:* only `docs/PCPortResearch.md` (this
|
||||
entry) + `docs/LEVEL-STATUS.md` (one line). No code, no converter, no
|
||||
sidecar change — **sidecars do NOT need regen**, tree builds clean at
|
||||
`f2beae4b` + M-13 uncommitted set.
|
||||
|
||||
**D124-Facility addendum (session M-14 — partial, NOT fixed, out of time).**
|
||||
Re-instrumented with a `GE_C2GDL` probe in `sub_GAME_7F0762E0`
|
||||
(`objecthandler_2.c`) + a bad-`G_SETTIMG` catcher in fast3d
|
||||
(`gfx_pc.cpp` G_SETTIMG case). Both probes **reverted** — tree is clean.
|
||||
Findings:
|
||||
|
||||
1. *Exact crash cmd.* `-level_34` faults in `import_texture_ia16`
|
||||
(`gfx_pc.cpp:757`, via `import_texture` :955) — NOT `import_texture_i8`
|
||||
as the old note said. The offending command in the relocated model GDL
|
||||
(dList base `0x7007b870`, cmd at `0x70080490`) is:
|
||||
`w0=0xfd900000` (`G_SETTIMG`, fmt=7/rgba? siz per bits, w=0),
|
||||
`w1=0x72181ee8`. `seg_addr(0x72181ee8)` returns it unchanged (top byte
|
||||
0x72 is not a segment) → deref of unmapped DRAM → AV. Preceding slot is
|
||||
`0xba 00 0e 02` (GE tex-load macro, w1=0) then a `00000000` slot.
|
||||
2. *`Gfx` stride confirmed.* `Gwords{ uintptr_t w0; uintptr_t w1; }` →
|
||||
on x86-64 `w0`@+0 (8B), `w1`@**+8** (8B), `sizeof(Gfx)==16`. The probe
|
||||
dump (8-byte granular) shows w0 at slot+0 and w1 at slot+8, i.e. the
|
||||
command is genuinely `{0xfd900000, 0x72181ee8}` — the converter wrote a
|
||||
real 16-byte slot, it is not a half-slot artefact.
|
||||
3. *`w1` is a bad `tex->data`.* This `G_SETTIMG` is emitted by
|
||||
`gDPSetTextureImage(gdl++, .., tex->data)` inside
|
||||
`texWriteLoadToTmemAddr` / `texWriteLoadToTmemZero` (`tex.c`). So the
|
||||
**texture-pool** entry `tex` returned by `texFindInPool()` has a
|
||||
garbage `.data` (should be a `0x05xxxxxx` seg-5 ref, resolved later by
|
||||
fast3d `segmentPointers[5]`). i.e. the fault is (at least partly) in
|
||||
the `texLoadFromModelFileHeader` → `texLoad` texture-pool path, driven
|
||||
off `objheader->Textures`, **not** solely the `texLoadFromGdl`
|
||||
command-stream copy. This partially contradicts the original
|
||||
D124-Facility hypothesis.
|
||||
4. *Alignment smoking gun.* `GE_C2GDL` probe shows **every** model file's
|
||||
`objheader->Switches` base has `((uintptr_t)Switches & 15) == 1`
|
||||
(odd!). `mempAllocBytesInBank` (`memp.c`) does **zero** alignment —
|
||||
`allocation = pool->pos; pool->pos += bytes;` — so a single earlier
|
||||
odd-sized bank alloc leaves the STAGE pool cursor permanently odd and
|
||||
every subsequent model file loads at an odd address. `delta`
|
||||
(`romremaining - pcremaining`, the scratch-relocation offset) is also
|
||||
not 16-aligned (`&15` = 3, 7, 11 across files). `sub_GAME_7F0762E0`
|
||||
then forms `dst = Switches + (replacementgdl & 0x00ffffff)` and
|
||||
`src = Switches + gdloff + delta`; observed `dst&15` = 5, 7, 9, 13.
|
||||
The N64 code assumes the file base is Gfx-aligned (8 on N64). The
|
||||
converted model GDLs the sidecar carries also have per-DL offsets that
|
||||
are only 4/8-aligned (`gdloff0 & 15` = 4, 8, 12), so even a 16-aligned
|
||||
base would not make every DL 16-aligned.
|
||||
- *Note:* an odd/misaligned base is self-consistent between the
|
||||
converter writer (`Switches+off`) and the fast3d reader (seg-5 =
|
||||
`Switches`, `+off`), so it does **not by itself** corrupt command
|
||||
*content* on x86 (unaligned `uintptr_t` loads are tolerated). The
|
||||
bad `tex->data` (#3) must come from an *offset* miscalc, likely the
|
||||
model texture-blob offset assuming N64-sized (8B-Gfx) GDL extent
|
||||
while the PC sidecar's GDLs are pre-expanded to 16B — the texture
|
||||
data then sits at a stale/too-large offset (`0x72181ee8` is ~0x1.9M
|
||||
past the bank end `~0x706a8000`), OR `texLoad` reads a `textures[i]`
|
||||
descriptor at a wrong stride off the odd `objheader->Textures` base
|
||||
(`(u8*)filedata + sizeof(ModelNode*)*numSwitches`, D43/D45 PORT line).
|
||||
- *Next step:* probe `texLoad` (`image.c`) — dump `textures[i]`
|
||||
`TextureID` / offset / resulting `tex->data` for `-level_34`, and
|
||||
dump `objheader->Textures`, `numtextures`, `numSwitches`, and the
|
||||
sidecar's texture-section layout. Compare against `d43_emit.py` /
|
||||
`pcmodels` sidecar builder: does it emit model texture blob offsets
|
||||
in N64 (8B-Gfx) or PC (16B-Gfx) GDL-extent terms? That converter is
|
||||
the likely fix site (offline sidecar per the D43/D69/D88 rule),
|
||||
with a fallback `#ifdef PORT` 16-align of the STAGE pool cursor /
|
||||
the model file base in `mempAllocBytesInBank` or
|
||||
`load_object_fill_header`.
|
||||
5. *Files touched this session:* none committed; both probe edits
|
||||
(`src/game/objecthandler_2.c`, `port/fast3d/gfx_pc.cpp`) reverted via
|
||||
`git checkout`. Docs only: this entry + `docs/LEVEL-STATUS.md`.
|
||||
*Confidence:* crash cmd + `Gfx` stride **high**; `tex->data`-is-the-bad-
|
||||
value **high**; root cause of *why* `tex->data` is bad **low–medium**
|
||||
(two candidate mechanisms, neither proven).
|
||||
@@ -0,0 +1,254 @@
|
||||
# PLAN — Linear pass: boot → menu → every solo level loads & renders
|
||||
|
||||
Paste-ready brief for a fresh session. Pivot from depth-first
|
||||
(BUNKER1 + graphics rabbit-holes) to **breadth-first**: make the normal
|
||||
player path work end-to-end (intro → credits → main menu → mission/level
|
||||
select), then confirm **all 21 solo levels load and render** without
|
||||
crashing. Turns the remaining work into a scoped list.
|
||||
|
||||
## Preflight (unchanged)
|
||||
|
||||
`CLAUDE.md` + `AGENTS.md` non-negotiables → `docs/HANDOFF.md` →
|
||||
`docs/PORT-LEARNINGS.md` → `docs/AGENT-WORKFLOW.md` before any subagent.
|
||||
|
||||
## Scope decisions (locked with the user)
|
||||
|
||||
- **Intro / credits / menus:** bar is *functional, not crashing*. Menus
|
||||
navigable start to finish; the intro is skippable with any button.
|
||||
Cosmetic defects are **out of scope** here — log them in
|
||||
`docs/GRAPHICS-BACKLOG.md`: gun-barrel Bond model missing (D75),
|
||||
Nintendo-logo transform (D75), mirrored glyphs (D114/D116), no audio
|
||||
(D77), partial disclaimer screen (D76).
|
||||
- **Text X-mirror (D114/D116):** stays parked. Menus are navigable with
|
||||
mirrored text.
|
||||
- **Level sweep depth:** *load + render + no immediate crash* only. A
|
||||
"soak" is pointless with no input (level triggers never fire). Play-to-
|
||||
completion validation is a **separate human step** (complex input),
|
||||
driven by a per-level checklist this plan produces (WS6).
|
||||
- **N64 reference captures:** deferred. Use PC golden frames +
|
||||
`tools_pc/framediff.py`; eyeball against memory/video for "matches
|
||||
original". `tools_pc/mupen64/` is available if a level looks wrong.
|
||||
|
||||
## Facts established (exploration, this session)
|
||||
|
||||
**Front end is a stage** — `LEVELID_TITLE` (0x5A). Logic in
|
||||
`src/game/front.c` (~8700 lines) + `src/game/title.c` (gun-barrel/logo
|
||||
3D) + `src/game/ramromreplay.c` (attract demos). State machine = `MENU_*`
|
||||
enum (`src/bondconstants.h:1831`), driven per frame by `menu_init()`
|
||||
(`front.c:8536`) through three switch tables (`update_/init_/interface_
|
||||
menuXX_*` at `front.c:8598/8637/8666`); rendered by
|
||||
`menu_jump_constructor_handler()` (`front.c:8718`). Transitions:
|
||||
`frontChangeMenu(menu, reload)` (`front.c:8513`). Boot entry:
|
||||
`initgamedata.c:9` sets `menu_update = MENU_LEGAL_SCREEN`.
|
||||
|
||||
**Intro states** (each: `init_` loads assets, `interface_` advances on
|
||||
timer OR any button, `constructor_` renders):
|
||||
`MENU_LEGAL_SCREEN` → `NINTENDO_LOGO` → `RAREWARE_LOGO` → `EYE_INTRO`
|
||||
(gun-barrel walk, "007", blood) → `GOLDENEYE_LOGO` → `DISPLAY_CAST`
|
||||
(credit roll) → idle → `select_ramrom_to_play()` (attract demo).
|
||||
Any button during intro sets `prev_keypresses` → logos short-circuit to
|
||||
`MENU_FILE_SELECT`, cast roll skipped. Gun-barrel sub-state machine is
|
||||
`gunbarrel_mode` (u8) in `title.c` (`insert_bond_eye_intro` `title.c:297`,
|
||||
`renderGunbarrelEyeIntroSequence` `title.c:608`).
|
||||
|
||||
**Menu → level launch:** mission select →
|
||||
`selected_stage = mission_folder_setup_entries[…].stage_id`
|
||||
(`front.c:3301`) → `MENU_DIFFICULTY` → `MENU_BRIEFING` /
|
||||
`MENU_007_OPTIONS` → `init_menu0B_runstage()` (`front.c:6900`) →
|
||||
`bossSetLoadedStage(selected_stage)` → `g_MainStageNum` → `bossMainloop`
|
||||
inner loop exits → `g_StageNum = g_MainStageNum` (`boss.c:647`) →
|
||||
`lvlStageLoad`. **Level → level always routes back through the front end**
|
||||
(`MENU_MISSION_COMPLETE`, `interface_menu0D_missioncomplete`
|
||||
`front.c:7119`; auto-advance scans `mission_folder_setup_entries` for the
|
||||
next `stage_id >= 0`). No direct in-stage jump.
|
||||
|
||||
**21 solo levels; LEVELID value == `-level_XX` number** (`boss.c:337`
|
||||
decode `d0*10 + d1 - 0x210`). Mission order + numbers:
|
||||
Dam 33, Facility 34, Runway 35, Surface1 36, Bunker1 09, Silo 20,
|
||||
Frigate 26, Surface2 43, Bunker2 27, Statue 22, Archives 24, Streets 29,
|
||||
Depot 30, Train 25, Jungle 37, Control 23, Caverns 39, Cradle 41,
|
||||
Aztec 28, Egypt 32, Cuba 54. Enum: `src/bondconstants.h:1633-1685`;
|
||||
mission table `mission_folder_setup_entries[]` `front.c:434-466`.
|
||||
|
||||
**Per-level memory args already have a table** — `memallocstringtable[]`
|
||||
(`boss.c:101-142`), one row/LEVELID (`-ml -me -mgfx -mvtx -mt -ma`). The
|
||||
auto-inject loop `boss.c:369-414` already selects the right row per stage
|
||||
load — but it is gated on `g_DebugAndUpdateStageFlag`, which the port
|
||||
only sets when `-level_` is **absent** (`boss.c:199-202`;
|
||||
`rmonGetToken()` → 0 on PC, `port/src/n64stubs.c:134`). So bare
|
||||
`-level_09` today uses default pools; hence the manual
|
||||
`-ml0 -me0 -mgfx100 -mvtx50 -mt700 -ma150` in the HANDOFF repro line.
|
||||
Documented TODO: `PCPortResearch.md` §H (D95 area) and `docs/HANDOFF.md`
|
||||
"Next task".
|
||||
|
||||
**D88.4 is a cross-level blocker.** Every level's `Usetup*Z` feeds the
|
||||
`propDefs` polymorphic record stream, which `tools_pc/d88_emit.py`
|
||||
deliberately leaves un-byteswapped. BUNKER1 crashes in `setupDoor`
|
||||
(`prop.c:971`) on a garbage BE `door->obj`. Any level hits the same class
|
||||
when its prop set is walked. Groundwork: `tools_pc/d88_propdefs.py`,
|
||||
`tools_pc/d88_layoutprobe.c` (machine `SZ`/`PTR` lines),
|
||||
`tools_pc/d88_propdef_scan.py` (per-level type histogram).
|
||||
Placeholder sizes still to resolve in `d88_propdefs.py`:
|
||||
`VEHICLE`/`AIRCRAFT`/`TANK`/`AMMO`/`DEPOSIT_IN_ROOM`.
|
||||
|
||||
**Tooling:** `GE_PCDUMP="a-b:step"` → `./ppm/` (`port/src/video.c:181`);
|
||||
`tools_pc/pixcount.py` (non-black pixel count / content check),
|
||||
`tools_pc/framediff.py` (structural regression vs `tools_pc/golden/` —
|
||||
the port is NOT frame-deterministic, D117), `tools_pc/ppm2bmp.py`.
|
||||
Crash log `ge007.crash.log`; symbolicate
|
||||
`addr2line -e build-pc/ge007.x86_64.exe -f -C <pc>` (image base
|
||||
0x140000000). Objectives: `propDefs` `PROPDEF_OBJECTIVE_*`
|
||||
(`bondconstants.h:4332`), status `src/game/objective_status.c`
|
||||
(`get_status_of_objective()` `:161`) — needed only for WS6.
|
||||
|
||||
## Workstreams
|
||||
|
||||
WS1 is a prerequisite for all. WS2 (`front.c`/`title.c`/`lv.c`/
|
||||
`ramromreplay.c`/`initmenus.c`/`port/`) and WS3 (`tools_pc/d88*` +
|
||||
narrow `prop.c`) are disjoint → parallelisable. WS4 depends on WS3.
|
||||
WS5 iterative. WS6 is the user's.
|
||||
|
||||
### WS1 — Frictionless per-level boot (small, first)
|
||||
|
||||
Goal: `./build-pc/ge007.x86_64.exe -level_XX` with no other args boots
|
||||
any level with its correct pools.
|
||||
|
||||
Change: in `src/boss.c` under `#ifdef PORT`, also set
|
||||
`g_DebugAndUpdateStageFlag = 1` when `-level_` **is** present (mirror of
|
||||
`boss.c:199-202`). The existing loop `boss.c:369-414` then supplies the
|
||||
right `memallocstringtable` row per stage; the generic preload at
|
||||
`boss.c:207-210` is harmless (per-loop `tokenSetString` at `:413`
|
||||
overrides). Debug-harness / ABI-class `#ifdef PORT` edit (same character
|
||||
as the `-level_` ASCII parse already in the file). Document as a new Dxx
|
||||
in `PCPortResearch.md` §F. Alt if boss.c is judged out of bounds:
|
||||
synthesize the string in the port token layer (`port/src/system.c`
|
||||
`sysGetTokenString`). Boss.c is simpler and preferred.
|
||||
|
||||
Also update `docs/HANDOFF.md` repro line; drop the manual `-m*` list once
|
||||
verified.
|
||||
|
||||
Verify: `-level_09` alone == today's full-arg behaviour (same frame,
|
||||
`framediff.py` green vs `tools_pc/golden/`).
|
||||
|
||||
### WS2 — Front-end walkthrough & crash repair (human playtest loop)
|
||||
|
||||
Goal: a human can go boot → (watch or skip intro) → file/folder select →
|
||||
mode select → mission select → difficulty → briefing → level launches,
|
||||
and after a level, mission-complete → next briefing, with **no crash /
|
||||
hang / dead-end**. Cosmetic wrongness acceptable and logged.
|
||||
|
||||
Method: same loop as the M-10 input playtest — overseer builds, human
|
||||
drives, defects come back as a list, overseer root-causes each (expect
|
||||
the pointer-width / BE-rodata / N64-Gfx-sizing classes in
|
||||
`PORT-LEARNINGS.md`), narrow `#ifdef PORT` fix, re-test.
|
||||
|
||||
Files in scope: `src/game/front.c`, `src/game/lv.c`
|
||||
(`interface_menu05_fileselect` is here), `src/game/title.c`,
|
||||
`src/game/initmenus.c`, `src/game/ramromreplay.c`, `port/src/*`. NOT
|
||||
`prop.c` / `tools_pc/d88*` (WS3).
|
||||
|
||||
Human checklist:
|
||||
1. Boot → legal → Nintendo → Rareware → gun-barrel → GE logo → cast roll
|
||||
→ (idle) attract demo. Note any crash/hang; cosmetic noted separately.
|
||||
2. Press a key during intro → confirm short-circuit to file select.
|
||||
3. File/folder select: create a folder, enter it; folder-delete sub-UI;
|
||||
30 s idle → back to legal screen.
|
||||
4. Mode select → Mission select: page through all 20 mission entries,
|
||||
each briefing text/photo loads without fault.
|
||||
5. Pick mission + Agent difficulty + briefing → Start → level loads.
|
||||
6. In-level: die / abort → mission-failed → menu. Complete a short level
|
||||
(or cheat) → mission-complete → auto-advance to next briefing.
|
||||
7. MP chain (lower priority): MP options → scenario/stage/char/handicap/
|
||||
teams → MP match loads.
|
||||
|
||||
Do NOT fix here (log/confirm in `GRAPHICS-BACKLOG.md`): D75, D76, D77,
|
||||
D114/D116.
|
||||
|
||||
Deliverable: front-end crash list + root causes + fixes;
|
||||
`GRAPHICS-BACKLOG.md` updated with confirmed cosmetic state.
|
||||
|
||||
### WS3 — D88.4 propDefs converter (cross-level blocker)
|
||||
|
||||
Goal: `Usetup*Z` `propDefs` converts correctly offline for all 21 levels
|
||||
so `setupDoor` / prop instantiation stops reading BE garbage.
|
||||
|
||||
Method: subagent brief, same shape as D69 / D88.1-3 (offline sidecar
|
||||
converter). Byte-spec every `PROPDEF_*` record type (~40, enum
|
||||
`bondconstants.h`), per-type field byteswap + pointer-width audit,
|
||||
extend `tools_pc/d88_emit.py`. Use `d88_propdefs.py` /
|
||||
`d88_layoutprobe.c` / `d88_propdef_scan.py`. Resolve the placeholder
|
||||
sizes (`VEHICLE`/`AIRCRAFT`/`TANK`/`AMMO`/`DEPOSIT_IN_ROOM`) via
|
||||
`d88_layoutprobe.c`.
|
||||
|
||||
Files: `tools_pc/d88_emit.py`, `tools_pc/d88_propdefs.py`,
|
||||
`tools_pc/d88_layoutprobe.c`, + narrow `#ifdef PORT` in `src/game/prop.c`
|
||||
if a struct-width mismatch surfaces (D79/D88 class).
|
||||
|
||||
Budget: structural — generous. On expiry: write up spec progress in
|
||||
§F/D88 with confidence rating.
|
||||
|
||||
Verify: `-level_09` walks past `setupDoor` to a rendered BUNKER1 frame
|
||||
with props; spot-check 2-3 other levels' prop walk.
|
||||
|
||||
### WS4 — Per-level load + render sweep
|
||||
|
||||
Goal: status matrix for all 21 solo levels — (a) `lvlStageLoad`
|
||||
completes without fault, (b) ≥1 non-degenerate frame renders, (c)
|
||||
survives ~300 frames / ~15 s with no input (immediate-crash check only).
|
||||
|
||||
Prereq: WS1 + WS3 landed.
|
||||
|
||||
Method: agent-driven, one level at a time (`-level_XX`). Each: launch
|
||||
`GE_PCDUMP="1-300:30"`, capture `ge007.crash.log`, run `pixcount.py`.
|
||||
Record PASS / CRASH@<site> / RENDERS-EMPTY / HANG. Symbolicate every
|
||||
crash (`addr2line`). Group failures by root-cause class.
|
||||
|
||||
Deliverable: `docs/LEVEL-STATUS.md` — table (level, number, load, render,
|
||||
crash site, notes), refreshed as WS5 closes items.
|
||||
|
||||
### WS5 — Per-level crash triage (iterative)
|
||||
|
||||
Goal: drive the WS4 matrix to "all 21 load + render".
|
||||
|
||||
Method: one focused investigation per distinct failure class (subagent
|
||||
brief per `AGENT-WORKFLOW.md` template; read `PORT-LEARNINGS.md` first).
|
||||
Expected classes: pointer-width struct growth in per-level assets,
|
||||
BE-rodata not converted, N64-Gfx/Vtx sizing, per-level pool exhaustion
|
||||
(`memallocstringtable` PC bumps, cf. D95). Fix = narrow `#ifdef PORT` or
|
||||
converter extension; document each in §F.
|
||||
|
||||
Verify per fix: target level's WS4 check → PASS *and* `-level_09`
|
||||
framediff stays green.
|
||||
|
||||
### WS6 — Human completion-validation pass (user-run, after WS4 green)
|
||||
|
||||
Goal: confirm each level is playable start to finish and matches the
|
||||
original — the part needing real input.
|
||||
|
||||
Deliverable produced for the user: `docs/LEVEL-PLAYTEST.md` — per-level
|
||||
checklist template: spawn point; each difficulty-gated objective
|
||||
(`objective_status.c`) reachable and registering COMPLETE; guards / props
|
||||
/ doors behave; level exit / mission-complete fires clean; timing +
|
||||
layout match retail. User fills it per level; findings feed a WS5-style
|
||||
triage round.
|
||||
|
||||
## Verification (overall)
|
||||
|
||||
- Per merged patch: `export PATH="/c/msys64/mingw64/bin:$PATH" &&
|
||||
./build-pc.sh ntsc-final` green, `/linkcheck` clean, single-frame
|
||||
`GE_PCDUMP` diff vs `tools_pc/golden/` via `framediff.py`.
|
||||
- WS1: `-level_09` alone == old manual-arg behaviour.
|
||||
- WS3: BUNKER1 renders props past `setupDoor`.
|
||||
- WS4/WS5: `docs/LEVEL-STATUS.md` all 21 rows = load+render PASS.
|
||||
- Regenerate 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`.
|
||||
|
||||
## Out of scope (tracked elsewhere — do not start here)
|
||||
|
||||
- D75 / D76 / D114 / D116 / D77 and all `docs/GRAPHICS-BACKLOG.md` items.
|
||||
- D118a/b input polish, rebinding, gamepad hotplug, `ge007.ini`.
|
||||
- Weapon-model-doesn't-draw (AUDIT-M6 #5), rest of the `struct player`
|
||||
offset audit.
|
||||
- Audio (Phase 3), saves/EEPROM (Phase 4), `docs/BACKLOG.md` (PC
|
||||
graphics options, co-op, netplay).
|
||||
@@ -53,6 +53,15 @@ through a converter or a runtime bswap fixup reads scrambled.
|
||||
(StandTile id/room, header mid/tail).
|
||||
- **Rule:** prefer an offline sidecar converter (D43, D69, D88 pattern,
|
||||
`tools_pc/d*_emit.py`) over a runtime fixup for a whole format.
|
||||
- A one-shot "sync patched values from the ROM copy into a compiled
|
||||
shadow array" pass must key its slot-detection on a field that is still
|
||||
intact *at sync time*. D124: `gimgSyncCompiledGlobalDLs` looked for the
|
||||
`0xABCDxxxx` IMAGESEG marker in the ROM copy — but `texLoad()` had
|
||||
already overwritten every one of those with a real pointer, so the sync
|
||||
silently copied nothing and the compiled `globalDL_0xNNN` explosion
|
||||
DLs kept their link-time markers (latent on *all* levels; only tripped
|
||||
when an explosion/smoke/particle DL is first drawn). Detect the slot
|
||||
from the *destination* (compiled) array, which still holds the marker.
|
||||
- A polymorphic-record converter with a per-type handler table + a
|
||||
"generic" fallback silently corrupts any type the table forgot: the
|
||||
fallback's word-granular bswap is wrong for sub-word fields. D122 —
|
||||
@@ -63,6 +72,18 @@ through a converter or a runtime bswap fixup reads scrambled.
|
||||
*every* `type` byte a level can emit and assert the handler set is
|
||||
total; `[u16|u16]` / `[s16|s16]` packed words need a half-swap
|
||||
(`_hh_word`), never a 32-bit swap.
|
||||
- **D123 corollary:** a pointer-width tail slot is **not always
|
||||
runtime-populated**. Some hold a small *integer id* in the ROM image
|
||||
that game code reads via that same field before overwriting it
|
||||
(`VehichleRecord/AircraftRecord.ailist`: `prop.c:1764/1786` does
|
||||
`x->ailist = ailistFindById(x->ailist)`). A converter that lumps every
|
||||
pointer-width tail slot into one "widen 4→8B, emit zeroes" bucket
|
||||
destroys the id → `ailistFindById(0)` silently returns global list 0
|
||||
(`GAILIST_AIM_AT_BOND`) → `ai()` runs a CHR aim list with a NULL chr →
|
||||
NULL deref in `chrIsNotDeadOrShot`. Before zeroing a widened slot,
|
||||
confirm the setup code writes it unconditionally; if it reads-then-writes,
|
||||
emit the value (`_bswap32` into the low 4 bytes, LE). Decide per field,
|
||||
not per type.
|
||||
|
||||
## C2. Port-layer / SDL shims
|
||||
|
||||
|
||||
@@ -112,9 +112,14 @@ void gimgSyncCompiledGlobalDLs(u8 *base)
|
||||
|
||||
while (p + 8 <= end && p[0] != (u8)G_ENDDL)
|
||||
{
|
||||
/* Post-fixup marker position: LE encoding of 0xABCDxxxx puts
|
||||
* CD AB at byte offsets 6..7. */
|
||||
if (p[6] == 0xCD && p[7] == 0xAB)
|
||||
/* D68/C2: texLoadFromDisplayList() has already replaced every
|
||||
* IMAGESEG w1 word in the ROM copy with a real texture pointer,
|
||||
* so the 0xABCDxxxx marker is gone from `p`. Identify the slots
|
||||
* that need syncing from the COMPILED array instead: a G_SETTIMG
|
||||
* command whose w1 is still an unresolved IMAGESEG marker
|
||||
* (0xABCDxxxx). Copy the ROM copy's resolved word into it. */
|
||||
if ((u8)(dst[j].words.w0 >> 24) == (u8)G_SETTIMG &&
|
||||
(dst[j].words.w1 >> 16) == 0xABCDu)
|
||||
{
|
||||
dst[j].words.w1 = *(u32 *)(p + 4);
|
||||
}
|
||||
|
||||
@@ -86,13 +86,15 @@ OBJ_PC_BYTES = 144
|
||||
# D122: ObjectRecord-derived types whose tail (N64 words >= 32) the converter
|
||||
# must lay out with pointer widening. {type: (ptr_word_set, hh_word_set)}.
|
||||
# 47 TintedGlassRecord tail: TintDist,CullDist,calculatedopacity,portalnum,unk90
|
||||
# 39 VehichleRecord ailist*@w32, [u16 aioffset|s16 aireturnlist]@w33, path*@w41, Sound*@w43
|
||||
# 40 AircraftRecord ailist*@w32, [u16|s16]@w33, path*@w43, Sound*@w44
|
||||
# 39 VehichleRecord ailist(id)@w32, [u16 aioffset|s16 aireturnlist]@w33, path*@w41, Sound*@w43
|
||||
# 40 AircraftRecord ailist(id)@w32, [u16|s16]@w33, path*@w43, Sound*@w44
|
||||
# NB w32 (ailist) is a pointer-width slot holding an INT id read by
|
||||
# prop.c before overwrite -> OBJ_ID_WORDS, not ptr_word_set (D123).
|
||||
# 45 TankRecord collision*@w32 (rest scalar; struct locs unconfirmed)
|
||||
OBJ_TAIL_DESC = {
|
||||
47: (frozenset(), frozenset()),
|
||||
39: (frozenset((32, 41, 43)), frozenset((33,))),
|
||||
40: (frozenset((32, 43, 44)), frozenset((33,))),
|
||||
39: (frozenset((41, 43)), frozenset((33,))),
|
||||
40: (frozenset((43, 44)), frozenset((33,))),
|
||||
45: (frozenset((32,)), frozenset()),
|
||||
# 13 AutogunRecord: unkC4*@w49 unkC8*@w50 beam*@w51 (is_active@w52 unkD4@w53)
|
||||
13: (frozenset((49, 50, 51)), frozenset()),
|
||||
@@ -100,6 +102,19 @@ OBJ_TAIL_DESC = {
|
||||
20: (frozenset(), frozenset(range(32, 45))),
|
||||
}
|
||||
|
||||
# D123: ObjectRecord-derived tail slots that are pointer-width on PC but hold a
|
||||
# pre-populated *integer AI-list id* in the ROM image which game code reads
|
||||
# before overwriting (prop.c:1764 `pdef_veh->ailist = ailistFindById(pdef_veh->ailist)`,
|
||||
# prop.c:1786 for aircraft). Unlike path*/Sound* (genuinely runtime-zeroed by
|
||||
# prop.c) these must NOT be zeroed by the converter — the id must survive into
|
||||
# the low 32 bits of the widened 8-byte slot. Was in ptr_word_set -> emitted as
|
||||
# 8 zero bytes -> ailistFindById(0) -> GAILIST_AIM_AT_BOND -> ai() ran a CHR aim
|
||||
# command with ChrEntityp==NULL -> NULL->actiontype crash in chrIsNotDeadOrShot.
|
||||
OBJ_ID_WORDS = {
|
||||
39: frozenset((32,)), # VehichleRecord.ailist @ N64 0x80
|
||||
40: frozenset((32,)), # AircraftRecord.ailist @ N64 0x80
|
||||
}
|
||||
|
||||
# Per-type PC struct size (bytes) the converter emits and sizepropdef must
|
||||
# return / 4. For ObjectRecord-derived types this is 144 + tail growth.
|
||||
PROPDEF_PC_BYTES = {
|
||||
@@ -302,10 +317,15 @@ def convert_record(src, so, type_byte):
|
||||
# tail with pointer members widened 4->8B and 8-aligned.
|
||||
_emit_object_prefix(out, src, so)
|
||||
ptr_words, hh_words = OBJ_TAIL_DESC[type_byte]
|
||||
id_words = OBJ_ID_WORDS.get(type_byte, frozenset())
|
||||
pc = OBJ_PC_BYTES
|
||||
for i in range(OBJ_N64_WORDS, n64w):
|
||||
w = src[so + 4 * i: so + 4 * i + 4]
|
||||
if i in ptr_words:
|
||||
if i in id_words: # D123: widened slot carrying an int id
|
||||
pc = (pc + 7) & ~7
|
||||
out[pc:pc + 4] = _bswap32(w) # id -> low 32 bits LE; high 32 = 0
|
||||
pc += 8
|
||||
elif i in ptr_words:
|
||||
pc = (pc + 7) & ~7
|
||||
pc += 8 # 8 zero bytes already present
|
||||
elif i in hh_words:
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")/.."
|
||||
LV="Dam:33 Facility:34 Runway:35 Surface1:36 Bunker1:09 Silo:20 Frigate:26 Surface2:43 Bunker2:27 Statue:22 Archives:24 Streets:29 Depot:30 Train:25 Jungle:37 Control:23 Caverns:39 Cradle:41 Aztec:28 Egypt:32 Cuba:54"
|
||||
OUT=/tmp/sweep_results
|
||||
mkdir -p /tmp/sweeplogs
|
||||
: > $OUT
|
||||
rm -f /tmp/sweeplogs/*.crash.log
|
||||
for entry in $LV; do
|
||||
name=${entry%%:*}; num=${entry##*:}
|
||||
rm -f ge007.crash.log; rm -rf ppm
|
||||
export PATH="/c/msys64/mingw64/bin:$PATH"; GE_PCDUMP="80-260:40" ./build-pc/ge007.x86_64.exe -level_$num > /tmp/sweeplogs/$name.log 2>&1 &
|
||||
pid=$!
|
||||
sleep 24
|
||||
taskkill //F //IM ge007.x86_64.exe > /dev/null 2>&1
|
||||
sleep 2
|
||||
status="UNKNOWN"
|
||||
if [ -f ge007.crash.log ]; then
|
||||
pc=$(grep -oE '0x[0-9a-fA-F]+' ge007.crash.log | head -1)
|
||||
sym=$(addr2line -e build-pc/ge007.x86_64.exe -f -C "$pc" 2>/dev/null | head -1)
|
||||
status="CRASH @ $sym ($pc)"
|
||||
cp ge007.crash.log /tmp/sweeplogs/$name.crash.log
|
||||
else
|
||||
last=$(ls ppm/*.ppm 2>/dev/null | tail -1)
|
||||
if [ -n "$last" ]; then
|
||||
pcnt=$(python tools_pc/pixcount.py "$last" 2>/dev/null | grep -oE 'non-clear [0-9]+ \([0-9.]+%\)')
|
||||
status="PASS $pcnt"
|
||||
else
|
||||
status="NO-FRAMES"
|
||||
fi
|
||||
fi
|
||||
echo "$name ($num): $status" | tee -a $OUT
|
||||
done
|
||||
echo "SWEEP DONE"
|
||||
Reference in new issue
Block a user