forked from Ports/goldeneye-pc-port
M-23: WS6 Facility playtest — D135/D137/D138/D139 (firefight, aim, freeze, unload)
First real human WS6 playthrough. Each crash is a subsystem the 21-level
boot sweep never exercises.
- D135 (propobj.c) bgTestHitOnObj: unported N64 GBI parser — raw 8-byte-Gfx
byte/word indices desync on the PC 16-byte Gfx stream, walk off the DL.
Ported #ifdef PORT to gdl->words.w0/.w1 shifts (mirrors pd_port bg.c:3635).
Sub-fix: objHit:9717 div-by-0 — texnum recovery not portable, force -1.
- D137 (gunfire.c) gunDrawSight: s32 sp54 holds a Gfx*; 4-byte slot splices
adjacent stack into the pointer -> wild DL write on right-mouse aim.
#ifdef PORT: Gfx *sp54.
- D138 (snd.c) sndCreatePostEvent: parked audio never drains evtq->allocList,
per-objTick positional-sound posts stall the main thread ("1 room from the
end" freeze). #ifdef PORT no-op until audio lands (D77).
- D139 (cleanup_objects.c) cleanupObjects: read the propDef type via
(u8)obj[0] — correct only big-endian (type = low byte of the header word).
On LE that's extrascale -> walk never hits PROPDEF_END -> runs off the blob
-> frees garbage -> stage-unload crash. #ifdef PORT uses pdef->type.
UNVERIFIED (fast teardown test hit D140 first).
Open: D140 pause-menu watch-model NULL matrix; D118c aim-mode mouse-down
-> crouch. New harness tools_pc/repro_gdb.sh + build-pc/d136_cmds.txt.
-level_09 unregressed (91.65%). Docs: PCPortResearch §F D135-D140,
PORT-LEARNINGS §B/§C, HANDOFF M-23 + M-24 next-task.
This commit is contained in:
1 parent
78c0f72157
commit
73820a0c24
9 files changed
+383
-18
No files matched your search
+111
-14
@@ -36,8 +36,12 @@ Full workstream breakdown (WS1–WS6) in `docs/PLAN-linear-level-sweep.md`.
|
||||
- **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.
|
||||
yaw (analog) vs pitch (digital) mismatch; D118b mouse-Y inverted;
|
||||
**D118c — in manual-aim mode, mouse-down → crouch** (mouse Y emits
|
||||
C-down, which game logic maps to crouch/zoom in `insightaimmode`;
|
||||
clean fix = the deferred analog-aim `#ifdef PORT` hook, §F D118);
|
||||
rebinding / gamepad hotplug / `ge007.ini` generation still TODO.
|
||||
Weapon switch on kbd = A button (`Space`/`Z`/`E`) — no dedicated key.
|
||||
- **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).
|
||||
@@ -150,7 +154,108 @@ Full workstream breakdown (WS1–WS6) in `docs/PLAN-linear-level-sweep.md`.
|
||||
later pass. Bug class D24-implications + `osYieldThread` watch item added
|
||||
to PORT-LEARNINGS §E.
|
||||
|
||||
## Next task
|
||||
## Done this session (M-23) — WS6 Facility playthrough: D135/D137/D138/D139 FIXED, D136/D140/D118c open
|
||||
|
||||
First real human WS6 playtest. User speed-ran Dam OK, then hit a chain of
|
||||
crashes on Facility (`-level_34`) — each one a subsystem the 21-level sweep
|
||||
never exercises (no weapon fire, no stage unload, no pause menu). Fixed four,
|
||||
two new open, Facility now near-complete. **All uncommitted** (folds into the
|
||||
docs-restructure commit set with D121/D122/D126/D128/D130/D131/D134).
|
||||
|
||||
- **D135 (FIXED)** `src/game/propobj.c` — firefight crash. `bgTestHitOnObj`
|
||||
(bullet-ray vs object triangle geometry, via `propobjFindHit` on every
|
||||
shot that resolves on an object model) is an **unported N64 GBI parser**:
|
||||
raw 8-byte-`Gfx` byte/word indices desync on the PC 16-byte `Gfx` stream →
|
||||
walks off the DL → AV `0x709ae02a`. Ported `#ifdef PORT` to `gdl->words.w0/
|
||||
.w1` shifts (mirrors PD `pd_port/src/game/bg.c:3635`). Second fault after
|
||||
the parser fix: `objHit` `propobj.c:9717` `% impact_sounds->thing2_len` int
|
||||
div-by-0 — the ported texnum-recovery (`*(s16*)(phys(w1)-8)`) returned a
|
||||
bogus `texturenum` → OOB `g_HitTypeSounds[]`. Fixed: PORT texnum branch
|
||||
always `-1` (`isnd_default`, safe; generic impact sound/decal, park w/ D77).
|
||||
- **D137 (FIXED)** `src/game/gunfire.c` — right-mouse (raise crosshair) crash.
|
||||
`gunDrawSight` `s32 sp54` holds a `Gfx*` the whole fn (`texSelect`/
|
||||
`display_image_at_position` take `Gfx**`); 4-byte slot → `*(Gfx**)&sp54`
|
||||
splices adjacent stack → wild DL write in `texSetRenderMode`. `#ifdef PORT`:
|
||||
`sp54` is `Gfx *`. §A.
|
||||
- **D138 (FIXED)** `src/snd.c` — the "1 room from the end" *freeze* (not a
|
||||
crash — kernel-heartbeat watchdog). `sndCreatePostEvent` →
|
||||
`alEvtqPostEvent` (`libultra/audio/event.c:110`) walks `evtq->allocList`;
|
||||
parked audio thread never drains it, `chrobjSndCreatePostEvent` posts per
|
||||
objTick per ambient-sound object, Facility is dense with machinery → the
|
||||
list walk stalls the main thread. `#ifdef PORT`: `sndCreatePostEvent` is a
|
||||
no-op until audio lands (D77). No gameplay effect.
|
||||
- **D139 (FIXED, UNVERIFIED)** `src/game/cleanup_objects.c` — stage-unload
|
||||
crash (`lvlUnloadStageTextData` → `cleanupObjects` → `objFree`, `obj->prop`
|
||||
= packed-float garbage). Root cause: `cleanupObjects` walks propDefs with
|
||||
`(u8)obj[0]` for the type, which works only because the header word
|
||||
`[u16 extrascale][u8 state][u8 type]` is **big-endian** (type = low byte).
|
||||
On LE the low byte is `extrascale` → the walk never matches `PROPDEF_END`,
|
||||
runs off the blob, dispatches the switch on garbage → `objFreePermanently`
|
||||
on non-objects → crash. `#ifdef PORT`: use `((PropDefHeaderRecord*)obj)->
|
||||
type` (offset 3) like every other consumer (`sizepropdef`, proplvreset).
|
||||
**Not confirmed** — when the user hit pause to trigger a fast teardown
|
||||
test, the pause menu itself crashed first (D140). `-level_09` unregressed
|
||||
(91.65%). §F **D139**, PORT-LEARNINGS §C (BE header byte on LE).
|
||||
- **D140 (OPEN)** — **pause menu crashes.** `maybe_mp_interface` →
|
||||
`bondviewRenderWatch` (`bondview2.c:8604`) → `bondviewTransformManyPos-
|
||||
ToViewMatrix(g_CurrentPlayer->field_23C = NULL, objheader->numMatrices=9)`
|
||||
→ `matrix_4x4_copy(src=0x0)` AV. The watch model's per-node view-matrix
|
||||
cache (`struct player.field_23C`) is never allocated (or `field_23C` is at
|
||||
the wrong PC offset — `struct player` layout, `docs/AUDIT-M6-player-
|
||||
offsets.md`). Blocks the WS6 pause-menu / objective-status checks and the
|
||||
watch gadgets. D75 3D-model-transform family. Files: `src/game/bondview2.c`,
|
||||
`src/bondtypes.h` (`struct player`).
|
||||
- **D118c (OPEN, low pri)** — in manual-aim mode, mouse-down → **crouch**.
|
||||
Mouse Y emits C-down; `bondview2.c:5351` maps C-down in `insightaimmode`
|
||||
to `crouchDown`/zoom as well as aim pitch. No input-layer-only fix; needs
|
||||
the deferred analog-aim `#ifdef PORT` hook (§F D118). Documented, parked.
|
||||
- **New harness** `tools_pc/repro_gdb.sh <XX>` — nohup-launch a level, attach
|
||||
gdb, dump full `bt full` + locals on the first SIGSEGV/SIGFPE, rolling
|
||||
frame dump to `ppm/`. Needed because `ge007.crash.log`'s own backtrace is
|
||||
`#01 = null` (native unwinder fails). Also `build-pc/d136_cmds.txt` — a
|
||||
gdb cmd file that traces every `objFree` call (`type`/`obj`/`prop`/`model`)
|
||||
then catches the fault; the "last line before FAULT" localises D136-class
|
||||
walk-off bugs. **Attach the winpid via `ps -W | grep ge007.x86_64 |
|
||||
awk '{print $4}'`** — `repro_gdb.sh`'s own `ps -p $!` derivation is wrong.
|
||||
- **Follow-up (not done):** the BG room-geometry hit-test in `bg.c`
|
||||
(`~3373-3646`, walks the D85-widened `ptr_expanded_mapping_info`) is the
|
||||
identical unported GBI parser to D135 — latent, fires on shooting
|
||||
walls/floor. Same mechanical port. §F D135 + PORT-LEARNINGS §B.
|
||||
- **Controls** (for the new session's own playtesting): kbd — WASD/arrows
|
||||
move, `A`/`D` sidestep, mouse X turn (analog), mouse Y look (digital
|
||||
C-button), LMB/LCtrl fire, RMB/LShift aim, `Space`/`Z`/`E` = A (action AND
|
||||
weapon-switch — GE overloads it), `X`/`R`/`F` = B (reload), `Q` = L,
|
||||
`Enter`/`Tab` = Start, `Esc` quit. No dedicated weapon key.
|
||||
|
||||
## Next task (M-24 — Opus 5)
|
||||
|
||||
**Immediate:** confirm **D139** — launch `-level_34`, play a few seconds,
|
||||
then die or pause→Abort Mission (avoid the watch/pause-menu render until
|
||||
D140 is fixed — dying is cleaner). Clean drop to the debrief/next screen =
|
||||
D139 confirmed. Then re-run Dam (`-level_33`) end-to-end — its M-22 crash
|
||||
was the same `objFreeEmbedmentOrProjectile` site, should be D139.
|
||||
|
||||
**Then, in rough priority:**
|
||||
1. **D140** — pause-menu / watch-model NULL (`field_23C`). Blocks WS6
|
||||
objective-status checks. Start: is `field_23C` never-allocated or a
|
||||
wrong `struct player` offset? gdb watchpoint / check `AUDIT-M6-player-
|
||||
offsets.md`. Likely D75 family (watch is a 3D model).
|
||||
2. **Re-run `tools_pc/level_sweep.sh`** — D135/D138 are cross-level (every
|
||||
level with shootable props / ambient machinery). Sweep is boot+capture
|
||||
only so it won't show combat regressions, but confirm no *boot* breakage
|
||||
and refresh `docs/LEVEL-STATUS.md`.
|
||||
3. **Resume WS6** `docs/LEVEL-PLAYTEST.md` — Facility first (finish the
|
||||
objective checklist now that it's completable), then down the mission
|
||||
list. Each new crash is one subsystem deeper; expect more `#ifdef PORT`
|
||||
ABI fixes (D122/D126/D132/D135/D137/D139 pattern).
|
||||
4. bg.c hit-test sibling port (follow-up above) — do it before a level
|
||||
where wall-shooting matters (most).
|
||||
|
||||
**Verification per fix:** target level boots to ≥1 non-degenerate frame
|
||||
(`tools_pc/pixcount.py`), AND `-level_09` + `-level_20` unregressed
|
||||
(`tools_pc/framediff.py`). Regen sidecars after any converter change.
|
||||
|
||||
<details><summary>M-22 and earlier "Next task" (superseded — §H authoritative)</summary>
|
||||
|
||||
-1. **D134 DONE (M-22)** — the frame-2 boot hang that every session since
|
||||
M-13 wrote off as "sweep flakiness / D117 / machine load" was a **real,
|
||||
@@ -160,21 +265,11 @@ Full workstream breakdown (WS1–WS6) in `docs/PLAN-linear-level-sweep.md`.
|
||||
permanent stall. Fix in `port/src/libultra.c` (`portPostEventForce` +
|
||||
2 reserved slots in `portPostVIEvent`). `-level_09` 6/6 boots to frame 600,
|
||||
0 heartbeats (pre-fix 1/3). §F/§H **D134**, PORT-LEARNINGS §E.
|
||||
**Consequence: re-run the full 21-level sweep** — several NO-FRAMES /
|
||||
"hang" rows in `docs/LEVEL-STATUS.md` (incl. the M-20 Facility/Jungle
|
||||
re-verify failures) are probably this and may now pass. `level_sweep.sh`
|
||||
is also hardened this session (45 s watchdog, lockdir serialization,
|
||||
stray-process kill, one retry on NO-FRAMES).
|
||||
|
||||
0. **D132 DONE (M-21, commit `fa296b17`)** — propDef union-index slots for
|
||||
types 14/19/38/44 (LINK/SWITCH/LOCK_DOOR/SAFE_ITEM) now emitted at the
|
||||
right PC offsets (`d88_propdefs.py` + `loadobjectmodel.c sizepropdef`
|
||||
PORT). Verified `-level_09` framediff 3/3, `-level_20` == pre-D132
|
||||
baseline (no regression), Archives/Streets/Dam load+render+no-crash.
|
||||
**Now hand the user `docs/LEVEL-PLAYTEST.md` for WS6** (linked props —
|
||||
switch-doors, dual weapons, locked doors, safes — need real input to
|
||||
validate). Also cheap: harden `level_sweep.sh` (watchdog 24→45 s,
|
||||
serialize) — sweep flakiness was severe this pass.
|
||||
PORT).
|
||||
|
||||
1. **`-level_09` (BUNKER1) boot crash — DISPROVEN as a regression** (M-20,
|
||||
coordinator: 3 runs / 1800 frames crash-free on `1fc3cff6`; re-confirmed
|
||||
@@ -205,6 +300,8 @@ After that: all 21 load+render → hand `docs/LEVEL-PLAYTEST.md` to the
|
||||
user for the WS6 completion pass (real input, per-level objective
|
||||
checklist).
|
||||
|
||||
</details>
|
||||
|
||||
**Regen gotcha:** `d69_emit.py` rewrites `data/pccg-<r>/pccg.bin` from
|
||||
scratch with only the 52 bg/stan rows — you MUST follow it with
|
||||
`d88_emit.py --regen` to re-add the 21 `Usetup*Z` rows, or every level
|
||||
|
||||
+27
-1
@@ -814,7 +814,8 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
||||
| 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) |
|
||||
| D118 | SDL input layer (`port/src/input.c`); D118a/b/c mouse polish open (D118c: aim-mode mouse-down → crouch, digital C-button overload — needs the deferred analog-aim hook) | resolved (polish open) |
|
||||
| D137 | right-mouse (aim-sight) crash: `gunDrawSight` (`gunfire.c:6235`) `s32 sp54` holds a `Gfx*` the whole function (`sp54 = *gdl`; `texSelect(&sp54,…)`/`display_image_at_position(&sp54,…)` take `Gfx**`). As N64 `s32` it is 4 B, so `*(Gfx**)&sp54` reads 4 B of the adjacent stack float as the pointer high word → `_g = 0x03e4fca0_70081220` → wild DL write in `texSetRenderMode` (`othermodemicrocode.c:177`) the moment the crosshair raises. §A. | FIXED (`src/game/gunfire.c` `#ifdef PORT` — `sp54` is `Gfx *`) |
|
||||
| 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 |
|
||||
@@ -825,6 +826,10 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
||||
| D131 | crash class C2m (Jungle `-level_37`): fast3d `gfx_sp_matrix` AV on a wild matrix pointer `0x401c68e0` (`gfx_pc.cpp:1046`) ~frame 300, when the first explosion/smoke prop renders. `explosionRenderPropSmoke` builds `gSPMatrix(gdl++, osVirtualToPhysical((void*)&dword_CODE_bss_8007A100), …MODELVIEW)` (+ `applyRoomMatrixToDisplayList`). `osVirtualToPhysical()` is a `u32`-returning shim (`libultra.c:1207`) → truncates the compiled `.bss` symbol's `0x1_00000000` module high word → w1 = `0x40xxxxxx` → `seg_addr()` returns it raw. Same class as D94 (chraction 32-bit ptr truncation), but in a GBI DL word. The projection matrix in the same DL is fine (`get_BONDdata_field_10E0()` is a runtime `0x70xxxxxx` ptr, fits u32); the `gSPDisplayList(&globalDL_0xNNN)` refs are fine (port `Gwords.w1` is 64-bit `uintptr_t`, no macro truncation). ~30 `osVirtualToPhysical(<compiled matrix/vtx symbol>)` sites exist (`explosion.c`, `glass*.c`, `blood_animation.c`, `bondview2.c`) — all latent until that effect first draws. | resolved — `seg_addr()` restores the module high word for any fallthrough w1 in `[0x40000000, 0x70000000)` (module fixed-based at `0x140000000`, no ASLR; DRAM/KSEG0/segmented/phys all handled earlier). Jungle renders to frame 2400+ clean; `-level_20`/`-level_24` unregressed. **18→19/21.** (`-level_09` has a separate pre-existing boot crash, see below.) |
|
||||
| D133 | intro render triage (M-20): the "intro renders mostly black" item in the M-18/M-19 handoff is **NOT a regression** — it is the D75/D76 parked-cosmetic steady state. M-17 (`9ec6121e`), whose handoff claimed "the entire intro renders — logos → gun barrel → cast", was built in a scratch worktree and captured with the same `GE_PCDUMP="20-900:20"` window: coverage is **pixel-identical** to HEAD `0b5f5d1a` (legal screen 6677 non-clear px / 2.17% on both; centred-logo frames ~7% on both; near-black between). 2D/text/texture layers draw; animated character-model layers (Nintendo-logo transform, gun-barrel Bond, cast models) never appear = D75 exactly. No cheap regression to fix; parked. Secondary: Facility `-level_34` + Jungle `-level_37` re-verify FAILED this pass (boot crash / frame-2 hang) — flagged in LEVEL-STATUS for a clean-machine re-check, not investigated (scope). | not a regression — parked (D75/D76, `GRAPHICS-BACKLOG.md`) |
|
||||
| D134 | **Frame-2 boot hang / sweep "NO-FRAMES" flakiness — root-caused and fixed (M-22).** `osSpTaskStartGo` (port/src/libultra.c) posted the SP/DP task-done events with `OS_MESG_NOBLOCK` into the scheduler's 8-slot `interruptQ`, which the 60 Hz VI pacemaker thread also fills with `VIDEO_MSG`. A gfx task runs the whole frame synchronously on the sched thread (fast3d); the first two frames take 30-80 ms, so the pacemaker queues several retraces meanwhile. Once the queue is full the RSP-done post is **silently dropped** -> `__scMain` never clears `sc->curRSPTask` -> the client never gets `OS_SC_DONE_MSG` -> `bossMainloop`'s `pendingGfx` never clears -> permanent stall (log: `frame 2 rendered`, then `kernel heartbeat ... frames=2`, retrace queue pinned at `valid=8/8 ret=-1`). Reproduced 2 of 3 `-level_09` boots on an idle machine, i.e. most of what M-13..M-21 wrote off as D117 nondeterminism / machine load. `OS_MESG_BLOCK` is NOT the fix (the done event is posted from the sched thread, the queue's only consumer -> self-deadlock): `portPostEventForce()` drops the OLDEST message (always a stale retrace) and retries, and `portPostVIEvent` now reserves 2 slots. Verified: 6/6 `-level_09` boots reach frame 600 with 0 heartbeats (pre-fix 1/3). | FIXED (`port/src/libultra.c`) |
|
||||
| D135 | **Firefight crash — `bgTestHitOnObj` (`propobj.c:8446`) is an unported N64 GBI parser (M-22, WS6 playtest).** The object bullet-hit test walks a model's display list command stream with hardcoded 8-byte-N64-`Gfx` byte/word indices (`*(s8*)gdl`, `((u32*)gdl)[1]`, `((u8*)gdl)[5..7]`, the G_TRI4 nibble reads, the backward `G_SETTIMG` scan). On PC the model GDL is the 16-byte `Gfx` layout (`d43_emit.py`, `{u64 w0; u64 w1}`), so every access reads the wrong bytes → the walk desyncs on the first `G_VTX`/`G_TRI1` → `v->coord.x` deref off the end of the DL → AV (`0x709ae02a`, a near-miss past a live DRAM ptr = the §B signature). Triggered by `propobjFindHit` every time a shot ray resolves a hit on an object model (guards' dropped weapons, attached objects, shootable props) — invisible to `level_sweep.sh` (no weapon fire). Two sibling faults seen the same session (`texSelect`/`texSetRenderMode` wild `gdl`, `objFreeEmbedmentOrProjectile` `prop->obj`) are likely the same corrupted-object cascade. PD `bgTestHitOnObj` (`pd_port/src/game/bg.c:3635`) is the fully-ported ground truth. | FIXED (`src/game/propobj.c`, `#ifdef PORT` — reads `gdl->words.w0/.w1` shifts). **Second fault, same walker:** after the parser fix the Facility firefight then hit `0xc0000094` (int div-by-zero) at `objHit` `propobj.c:9717` `randomGetNext() % impact_sounds->thing2_len` — the ported texnum-recovery (`*(s16*)(phys(w1) − 8)`) returned a bogus positive `texturenum` (`(u32)` cast truncated a 64-bit `words.w1`; the N64 "texnum 8 bytes before the texture data" layout isn't guaranteed for the port's converted/replaced GDLs) → `g_HitTypeSounds[texnum & 0xf]` picked an entry with `thing2_len == 0`. Fixed: the PORT texnum branch now always returns `-1` (→ `g_HitTypeSounds[0]` = `isnd_default`, `thing2_len == 1`, safe). `texturenum` only flavours the bullet-impact sound/decal, so generic-hit is an acceptable degradation (park with D77). **Follow-up:** the BG room-geometry sibling `sub_GAME_…` (`bg.c:~3373-3646`, walks the D85-widened `ptr_expanded_mapping_info`) has the identical unported parser pattern — latent, fires on shooting walls/floor; not yet ported. |
|
||||
| D136 | **Stage-unload crash — `objFree` `obj->prop` reads float garbage (M-22, WS6 Facility playthrough).** After D135 cleared the firefight crashes the level is completable; then on teardown `lvlUnloadStageTextData` → `cleanupObjects` (`cleanup_objects.c:42`) → `objFreePermanently` → `objFree` (`propobj.c:994`) → `objFreeEmbedmentOrProjectile(obj->prop)` faults at `:888` (`prop->obj` deref). Real gdb `bt`: `obj` = `0x701bd730` (valid) but `obj->prop` = `0x3e567400405c30c0` — two packed `f32` (~0.21, ~3.44), i.e. a `coord3d` read as a pointer → `ObjectRecord.prop` (N64 `/*0x10*/`, after `inherits PropDefHeaderRecord` + `s16 obj; s16 pad; u32 flags; u32 flags2;`) lands at the wrong PC offset for this object's propDef type, OR the record is undersized and setup's `->prop` store never happened / was clobbered. Same family as D122/D132 (`tools_pc/d88_propdefs.py` per-type record sizing vs the compiled `ObjectRecord`/subtype layout). Type not yet identified — needs a gdb run with a breakpoint on `objFree` to read `obj->type` (`cleanupObjects` frees WEAPON/AMMO/MAGAZINE/COLLECTABLE/MONITOR/RACK/AUTOGUN/HAT/ARMOUR/GAS_RELEASING/VEHICHLE/AIRCRAFT/GLASS/SAFE/TANK/TINTED_GLASS via this path). Mission completes; crash is before the next briefing loads. | ROOT-CAUSED → **D139** (it was NOT a layout bug — `cleanupObjects` read the type from the wrong byte on LE and walked off the propDef blob). |
|
||||
| D139 | **Stage-unload crash root cause + fix (M-23).** `cleanupObjects` (`cleanup_objects.c`) walks `g_CurrentSetup.propDefs` with `(u8)obj[0]` as the record type — correct only because the header word `[u16 extrascale][u8 state][u8 type]` is **big-endian** on N64, so `type` is the low byte. On little-endian PC the low byte is `extrascale`, so `(u8)obj[0]` is never `PROPDEF_END` → the `while` runs off the end of the blob, and the `switch` dispatches on garbage → `objFreePermanently()` on records that aren't `ObjectRecord`s → `obj->prop`/`obj->model` read adjacent float data (`0x3e567400405c30c0`) → AV in `objFreeEmbedmentOrProjectile` (`propobj.c:888`). `sizepropdef()` and every other propDef consumer already use `pdef->type` (struct member, offset 3) — only this one spot used the raw byte cast. Not a `d88_propdefs.py` layout issue at all. | FIXED (`src/game/cleanup_objects.c`, `#ifdef PORT` — `CLEANUP_PDTYPE(o)` = `((PropDefHeaderRecord*)o)->type`). **UNVERIFIED** — the fast teardown test (pause→abort) hit D140 first; `-level_09` unregressed. PORT-LEARNINGS §C. |
|
||||
| D140 | **Pause-menu crash (M-23, OPEN).** `maybe_mp_interface` → `bondviewRenderWatch` (`bondview2.c:8604`) → `bondviewTransformManyPosToViewMatrix(g_CurrentPlayer->field_23C, objheader->numMatrices)` with `field_23C == NULL` (`arg1 == 9`) → `matrix_4x4_copy(src=0x0, …)` (`matrixmath.c:130`) AV. `struct player.field_23C` is the watch model's per-node view-matrix cache — never allocated on PC, or `field_23C` is at the wrong `struct player` offset (`docs/AUDIT-M6-player-offsets.md`). Blocks the WS6 pause-menu / `objective_status` checks + watch gadgets. D75 3D-model-transform family. Files: `src/game/bondview2.c` (`bondviewRenderWatch` ~8540-8608), `src/bondtypes.h` (`struct player`). | OPEN |
|
||||
| D130 | crash class C2 (Facility `-level_34`, Runway `-level_35`): `import_texture_i8` AV on a wild texture pointer (`0x72181ee8`), baked into the HUD glyph DL by `gDPLoadTextureBlock(gdl, curchar->pixeldata, …)` when the level-title string ("Chemical Warfare Facility #2") renders `#`/`"`. Root cause: `romdataFixupFont` (`port/src/romdata.c`) converts the N64 24B fontchar array to the PC 32B array **in place**, forward-field, backward-glyph. PC−N64 stride = 8, so for glyphs 0/1/2 `d` overlaps `s` by less than the 24B read span and an early field write clobbers a later field's source mid-`for k<5` loop → glyph 1's `width` = `bswap(index)` = `0x01000000`, glyph 2's `pixeldata` = `pcPixOff+(index−pixStart)` ≈ `0x020002e8` → `+= font_base` → wild → fast3d AV. On N64 the array is read-only rodata, never re-laid-out, so no bug there. NOT the model-GDL relocation (`sub_GAME_7F0762E0` / `texLoadFromGdl`) the D124-Facility addendum suspected — `gdl` there is still a segmented `0x05xxxxxx` value so the `& 0x00ffffff` masks are correct, and `texLoadFromGdl` never copies a `G_SETTIMG` for these levels. | resolved — stage all 6 N64 fields in a local `f[6]` before writing any. Facility + Runway now PASS (16→18/21). Probes reverted. |
|
||||
|
||||
Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS
|
||||
@@ -3912,6 +3917,27 @@ TODO: (1) human playtest + tune `MouseAimSpeed`; (2) key rebinding;
|
||||
(3) gamepad hotplug (opened only at init today); (4) optional analog-aim
|
||||
`#ifdef PORT` hook in bondview.c for true mouse-look.
|
||||
|
||||
*Open polish bugs (from WS6 playtest, M-22):*
|
||||
- **D118a** — mouse yaw (analog, `stick_x += aimDX`) vs pitch (digital
|
||||
C-up/C-down pulse) feel very different; pitch is steppy/slow.
|
||||
- **D118b** — mouse-Y may feel inverted (toggle: `ge007.ini [Input]
|
||||
MouseInvertY`).
|
||||
- **D118c** — **in manual-aim mode (right-mouse), moving the mouse down to
|
||||
look down makes Bond crouch.** Root cause is the same digital bridge: mouse
|
||||
Y emits `GE_CONT_D` (C-down), and `bondview2.c` `MoveData` maps
|
||||
`C-down` in `insightaimmode` to `crouchDown` (`bondview2.c:5351`) / zoom
|
||||
(`:5340`, DISABLE_CROUCH weapons) as well as free-aim pitch. The C-button
|
||||
is genuinely overloaded by mode in game logic — the only clean fix is the
|
||||
deferred analog-aim `#ifdef PORT` hook (TODO 4), which would drive pitch
|
||||
without touching C-down. Low priority (aim-mode look-down is rare in
|
||||
normal play). No cheap input-layer-only fix — suppressing C-down in aim
|
||||
mode would break intentional crouch/zoom.
|
||||
- **D137 (FIXED)** — right-mouse *crash* (not this crouch bug):
|
||||
`gunDrawSight` `s32 sp54` truncated a `Gfx*`; see §F D137.
|
||||
- Weapon switch on kbd/mouse = the A button (`Space`/`Z`/`E`), same as
|
||||
action/use (GE overloads it). No dedicated key; mouse-wheel cycle is a
|
||||
candidate quick-win.
|
||||
|
||||
**`data/` deletion + recovery (session M-3).** `git worktree remove
|
||||
--force` on an agent worktree that had a directory *junction*
|
||||
`worktree/data → main/data` followed the junction and deleted the real
|
||||
|
||||
@@ -102,6 +102,22 @@ expressed in N64 `Gfx`/`Vtx` units is **half-size** on PC.
|
||||
- Instances: D50.6 (texCopyGdls copied only w0 of each 16-byte slot),
|
||||
D58 (DL reserve 0x100→0x200), D85 (`bgWidenRoomGdl` 8→16 + bswap),
|
||||
D95 (2× master-DL buffer + raised mempool ceiling).
|
||||
- **D135 corollary — an unported GBI *parser* (not just a buffer size).** Code
|
||||
that walks a DL command stream with raw byte/word indices into an 8-byte N64
|
||||
`Gfx` (`*(s8*)gdl` for the cmd, `((u32*)gdl)[1]` for w1, `((u8*)gdl)[5..7]`
|
||||
for `G_TRI1` vtx indices, `((u32*)gdl)[0/1]` nibble reads for `G_TRI4`) reads
|
||||
the wrong bytes of the PC 16-byte `{u64 w0; u64 w1}` slot and desyncs on the
|
||||
first command → walks off the DL → wild deref. The N64 32-bit words survive
|
||||
in the LOW dword of each 64-bit field, so the mechanical port is: `u32 w0 =
|
||||
(u32)gdl->words.w0; u32 w1 = (u32)gdl->words.w1;` then replace every raw
|
||||
access with the equivalent shift/mask on w0/w1 (BE byte `i` of a word →
|
||||
`(word >> (8*(3-i))) & 0xff`). `gdl++` (advances by `sizeof(Gfx)`) is already
|
||||
correct. Watch `(s32)ptr` truncation in vtx-base math and `x | 0x80000000`
|
||||
KSEG0 folds (identity on PC — just drop the OR, and guard segmented w1).
|
||||
Instances: `bgTestHitOnObj` (`propobj.c`, FIXED); `bg.c` room-geometry
|
||||
hit-test `sub_GAME_…` (`~3373-3646`, still latent — shooting walls).
|
||||
PD ground truth: `pd_port` uses `gdl->dma.cmd` + `GFX_W0_BYTE(i)`/`GFX_W1_BYTE(i)`
|
||||
macros (`3-i` / `11-i` on 64-bit LE).
|
||||
- **Also bites RAW hardcoded struct-stride writes, not just Gfx/Vtx.**
|
||||
D128: `sub_GAME_7F0B37EC` did `((u8*)g_BgPortals)[(portal<<3)+6] |= 2`
|
||||
— the N64 `bg_portal_data_entry` is 8B (`ptr@0, cr1@4..cb2@7`); on PC
|
||||
@@ -129,6 +145,17 @@ 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.
|
||||
- **D139 — `(u8)word` / `(s8)word` to grab "the first field" of a struct
|
||||
reads the WRONG byte on LE.** N64 code that does `(u8)obj[0]` (obj a
|
||||
`u32*`) to read a byte-3 field of a big-endian header word — e.g. the
|
||||
propDef type in `[u16 extrascale][u8 state][u8 type]` — gets the low byte,
|
||||
which is the *last* BE field. On LE it's `extrascale`. `cleanupObjects`
|
||||
did this for its walk-termination + type dispatch → never saw
|
||||
`PROPDEF_END` → ran off the blob → freed garbage → crash on stage unload.
|
||||
Fix: use the struct member (`pdef->type`) like every other consumer.
|
||||
Grep for `(u8)*`/`(s8)*`/`& 0xff` on a `u32`/`s32` that's really a
|
||||
serialized multi-field word — especially GBI-word and header-word reads
|
||||
that were never routed through a converter or `#ifdef PORT`.
|
||||
- **Python slice-assignment width is a silent corruptor** (D125):
|
||||
`out[a:a+8] = b"\x00\x00\x00\x00"` on a `bytearray` does NOT raise — it
|
||||
deletes `(8 - len(rhs))` bytes, shrinking `out` and shifting everything
|
||||
|
||||
@@ -7,15 +7,29 @@
|
||||
#include "loadobjectmodel.h"
|
||||
|
||||
|
||||
#ifdef PORT
|
||||
/* D139: the N64 `(u8)obj[0]` extracts the propDef type because the header word
|
||||
* `[u16 extrascale][u8 state][u8 type]` is big-endian, so `type` is the low
|
||||
* byte. On little-endian PC the low byte is `extrascale`, so this walk never
|
||||
* matched PROPDEF_END (ran off the propDefs blob) and dispatched the switch on
|
||||
* garbage -> objFreePermanently() on non-ObjectRecords -> `obj->prop` reads
|
||||
* float data -> crash in objFreeEmbedmentOrProjectile on stage unload. Every
|
||||
* other propDef consumer (sizepropdef, the proplvreset walks) already uses
|
||||
* `pdef->type` (struct member, offset 3); match them. */
|
||||
#define CLEANUP_PDTYPE(o) (((PropDefHeaderRecord *)(o))->type)
|
||||
#else
|
||||
#define CLEANUP_PDTYPE(o) ((u8)(o)[0])
|
||||
#endif
|
||||
|
||||
void cleanupObjects(s32 stage)
|
||||
{
|
||||
u32 *obj = (u32)g_CurrentSetup.propDefs;
|
||||
|
||||
if (obj)
|
||||
{
|
||||
while ((u8)obj[0] != PROPDEF_END)
|
||||
while (CLEANUP_PDTYPE(obj) != PROPDEF_END)
|
||||
{
|
||||
switch ((u8)obj[0])
|
||||
switch (CLEANUP_PDTYPE(obj))
|
||||
{
|
||||
case PROPDEF_DOOR:
|
||||
case PROPDEF_PROP:
|
||||
@@ -75,3 +89,5 @@ void cleanupObjects(s32 stage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef CLEANUP_PDTYPE
|
||||
@@ -6234,13 +6234,27 @@ void gunSetSightVisible(s32 reason, bool visible)
|
||||
|
||||
void gunDrawSight(s32 *gdl) {
|
||||
|
||||
#ifdef PORT
|
||||
/* D137: sp54 holds a Gfx* the whole time (`sp54 = *gdl`, then passed as
|
||||
* `&sp54` to texSelect/display_image_at_position which take Gfx**). As an
|
||||
* N64 `s32` it is 4 bytes, so `*(Gfx**)&sp54` reads 4 bytes of adjacent
|
||||
* stack as the pointer high word → wild `gdl` write in texSetRenderMode
|
||||
* when the player raises the crosshair (right-mouse aim). §A. */
|
||||
Gfx *sp54;
|
||||
#else
|
||||
s32 sp54;
|
||||
#endif
|
||||
f32 xypos[2];
|
||||
f32 halfedxy[2];
|
||||
|
||||
if ((g_CurrentPlayer->gunsightmode == 0) && (g_CurrentPlayer->mpmenuon == FALSE)) {
|
||||
#ifdef PORT
|
||||
sp54 = *(Gfx **)gdl;
|
||||
texSelect(&sp54, crosshairimage, 4, 0, 0);
|
||||
#else
|
||||
sp54 = *gdl;
|
||||
texSelect(&sp54, crosshairimage, 4, 0, 0);
|
||||
#endif
|
||||
|
||||
xypos[0] = g_CurrentPlayer->crosshair_angle.f[0];
|
||||
xypos[1] = g_CurrentPlayer->crosshair_angle.f[1];
|
||||
@@ -6254,7 +6268,11 @@ void gunDrawSight(s32 *gdl) {
|
||||
halfedxy[1] = halfedxy[1] * g_GunSightAspectRatio;
|
||||
#endif
|
||||
display_image_at_position(&sp54, &xypos, &halfedxy, 0x20, 0x20, 0, 0, 1, 0xFF, 0xFF, 0xFF, 0x6E, (crosshairimage->level > 0), 0);
|
||||
#ifdef PORT
|
||||
*(Gfx **)gdl = sp54;
|
||||
#else
|
||||
*gdl = sp54;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+81
-1
@@ -8481,7 +8481,19 @@ bool bgTestHitOnObj(coord3d *arg0, coord3d *arg1, coord3d *arg2, Gfx *gdl, Gfx *
|
||||
|
||||
while (1)
|
||||
{
|
||||
#ifdef PORT
|
||||
/* D135: GE object bullet-hit GBI parser. On PC the model GDL is the
|
||||
* 16-byte Gfx layout (tools_pc/d43_emit.py) with the two N64 32-bit
|
||||
* words preserved in the low dwords of words.w0 / words.w1. Every raw
|
||||
* byte/word index below assumed an 8-byte N64 slot, so it desynced on
|
||||
* the first G_VTX/G_TRI1 and walked off the DL (§B, PORT-LEARNINGS).
|
||||
* Read the words instead; mirrors PD bgTestHitOnObj (pd_port bg.c). */
|
||||
u32 w0 = (u32) gdl->words.w0;
|
||||
u32 w1 = (u32) gdl->words.w1;
|
||||
op = (s8) (w0 >> 24);
|
||||
#else
|
||||
op = *((s8 *) gdl);
|
||||
#endif
|
||||
if (op == (s8)G_ENDDL)
|
||||
{
|
||||
if (gdl2 == 0)
|
||||
@@ -8501,9 +8513,15 @@ bool bgTestHitOnObj(coord3d *arg0, coord3d *arg1, coord3d *arg2, Gfx *gdl, Gfx *
|
||||
|
||||
if (op == G_VTX)
|
||||
{
|
||||
#ifdef PORT
|
||||
op = (w0 >> 16) & 0xf;
|
||||
padC = w1 & 0x00ffffff;
|
||||
vtxbase = (Vertex *) ((uintptr_t) vertices + (uintptr_t) padC - (op << 4));
|
||||
#else
|
||||
op = ((u8 *) gdl)[1] & 0xf;
|
||||
padC = ((u32 *) gdl)[1] & 0x00ffffff;
|
||||
vtxbase = (Vertex *) ((((s32) vertices) + padC) - (op << 4));
|
||||
#endif
|
||||
gdl++;
|
||||
|
||||
continue;
|
||||
@@ -8514,9 +8532,15 @@ bool bgTestHitOnObj(coord3d *arg0, coord3d *arg1, coord3d *arg2, Gfx *gdl, Gfx *
|
||||
bboxMin = D_8003204C;
|
||||
bboxMax = D_80032058;
|
||||
|
||||
#ifdef PORT
|
||||
idx[0] = ((w1 >> 16) & 0xff) / 10;
|
||||
idx[1] = ((w1 >> 8) & 0xff) / 10;
|
||||
idx[2] = (w1 & 0xff) / 10;
|
||||
#else
|
||||
idx[0] = ((u8 *) gdl)[5] / 10;
|
||||
idx[1] = ((u8 *) gdl)[6] / 10;
|
||||
idx[2] = ((u8 *) gdl)[7] / 10;
|
||||
#endif
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
@@ -8560,6 +8584,28 @@ bool bgTestHitOnObj(coord3d *arg0, coord3d *arg1, coord3d *arg2, Gfx *gdl, Gfx *
|
||||
dy = (f32) (((s32) hitbuf.hitpos.y) - ((s32) arg0->y));
|
||||
dz = (f32) (((s32) hitbuf.hitpos.z) - ((s32) arg0->z));
|
||||
tcmd = gdl;
|
||||
#ifdef PORT
|
||||
if (((u8) ((u32) gdl->words.w0 >> 24) != G_SETTIMG) && (cmdStart < gdl))
|
||||
{
|
||||
do
|
||||
{
|
||||
tcmd--;
|
||||
if ((u8) ((u32) tcmd->words.w0 >> 24) == G_SETTIMG)
|
||||
{
|
||||
break;
|
||||
}
|
||||
} while (cmdStart < tcmd);
|
||||
}
|
||||
|
||||
/* N64 recovers the texture number from *(s16*)(phys(w1) - 8).
|
||||
* That layout is not guaranteed for the port's converted
|
||||
* model textures / runtime-replaced collision GDLs, and a
|
||||
* bogus value here indexes g_HitTypeSounds[] OOB (div-by-0
|
||||
* at :9717). texturenum only flavours the bullet-impact
|
||||
* sound/decal, so fall back to the generic hit (-1). */
|
||||
texnum = -1;
|
||||
(void) tcmd;
|
||||
#else
|
||||
if (((*((u8 *) gdl)) != 253) && (cmdStart < gdl))
|
||||
{
|
||||
do
|
||||
@@ -8583,6 +8629,7 @@ bool bgTestHitOnObj(coord3d *arg0, coord3d *arg1, coord3d *arg2, Gfx *gdl, Gfx *
|
||||
padC = ((u32 *) tcmd)[1] - 8;
|
||||
texnum = *((u16 *) (padC | 0x80000000));
|
||||
}
|
||||
#endif
|
||||
|
||||
d = ((dx * dx) + (dy * dy)) + (dz * dz);
|
||||
|
||||
@@ -8614,6 +8661,32 @@ bool bgTestHitOnObj(coord3d *arg0, coord3d *arg1, coord3d *arg2, Gfx *gdl, Gfx *
|
||||
bboxMin2 = D_80032070;
|
||||
bboxMax2 = D_8003207C;
|
||||
|
||||
#ifdef PORT
|
||||
if (s2 == 0)
|
||||
{
|
||||
idx2[0] = w1 & 0xf;
|
||||
idx2[1] = (w1 & 0xff) >> 4;
|
||||
idx2[2] = w0 & 0xf;
|
||||
}
|
||||
else if (s2 == 1)
|
||||
{
|
||||
idx2[0] = (w1 >> 8) & 0xf;
|
||||
idx2[1] = (w1 & 0xffff) >> 12;
|
||||
idx2[2] = (w0 & 0xff) >> 4;
|
||||
}
|
||||
else if (s2 == 2)
|
||||
{
|
||||
idx2[0] = (w1 >> 16) & 0xf;
|
||||
idx2[1] = ((w1 >> 16) & 0xff) >> 4;
|
||||
idx2[2] = (w0 >> 8) & 0xf;
|
||||
}
|
||||
else
|
||||
{
|
||||
idx2[0] = (w1 >> 24) & 0xf;
|
||||
idx2[1] = w1 >> 28;
|
||||
idx2[2] = (w0 & 0xffff) >> 12;
|
||||
}
|
||||
#else
|
||||
if (s2 == 0)
|
||||
{
|
||||
idx2[0] = ((u32 *) gdl)[1] & 0xf;
|
||||
@@ -8638,6 +8711,7 @@ bool bgTestHitOnObj(coord3d *arg0, coord3d *arg1, coord3d *arg2, Gfx *gdl, Gfx *
|
||||
idx2[1] = ((u32 *) gdl)[1] >> 28;
|
||||
idx2[2] = ((u32) ((u16 *) gdl)[1]) >> 12;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
@@ -8681,7 +8755,12 @@ bool bgTestHitOnObj(coord3d *arg0, coord3d *arg1, coord3d *arg2, Gfx *gdl, Gfx *
|
||||
dy = (f32) (((s32) hitbuf.hitpos.y) - ((s32) arg0->y));
|
||||
dz = (f32) (((s32) hitbuf.hitpos.z) - ((s32) arg0->z));
|
||||
tcmd = gdl;
|
||||
|
||||
#ifdef PORT
|
||||
/* see the G_TRI1 note above — texturenum recovery from
|
||||
* the G_SETTIMG w1 is not portable; use generic hit. */
|
||||
texnum = -1;
|
||||
(void) tcmd;
|
||||
#else
|
||||
if (((*((u8 *) gdl)) != G_SETTIMG) && (cmdStart < gdl))
|
||||
{
|
||||
do
|
||||
@@ -8704,6 +8783,7 @@ bool bgTestHitOnObj(coord3d *arg0, coord3d *arg1, coord3d *arg2, Gfx *gdl, Gfx *
|
||||
padC = ((u32 *) tcmd)[1] - 8;
|
||||
texnum = *((u16 *) (padC | 0x80000000));
|
||||
}
|
||||
#endif
|
||||
|
||||
d = ((dx * dx) + (dy * dy)) + (dz * dz);
|
||||
|
||||
|
||||
@@ -1037,6 +1037,18 @@ void sndCreatePostEvent(ALSoundState *state, s16 eventType, s32 arg2)
|
||||
{
|
||||
ALSndpEvent evt;
|
||||
|
||||
#ifdef PORT
|
||||
/* D138: libaudio is Phase-3 parked. The audio thread never drains
|
||||
* g_sndPlayerPtr->evtq->allocList, so every positional-sound tick
|
||||
* (chrobjSndCreatePostEvent, called per objTick for each ambient-sound
|
||||
* object) appends an item and alEvtqPostEvent walks the whole list to
|
||||
* insert it -> O(n^2). Facility has enough machinery ambience that after
|
||||
* a few thousand frames one insert takes seconds and the kernel-heartbeat
|
||||
* watchdog trips ("no frame rendered"). Skip event posting until audio
|
||||
* lands (D77); no gameplay effect. */
|
||||
(void) evt; (void) eventType; (void) arg2; (void) state;
|
||||
return;
|
||||
#else
|
||||
evt.common.type = eventType;
|
||||
evt.common.state = state;
|
||||
evt.unks32.val8 = arg2;
|
||||
@@ -1045,6 +1057,7 @@ void sndCreatePostEvent(ALSoundState *state, s16 eventType, s32 arg2)
|
||||
{
|
||||
alEvtqPostEvent(&g_sndPlayerPtr->evtq, (ALEvent *)&evt, 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
# Playtest launcher for docs/LEVEL-PLAYTEST.md (WS6 human validation).
|
||||
# Usage: ./tools_pc/playtest.sh <XX> [difficulty-note]
|
||||
# e.g. ./tools_pc/playtest.sh 33 "Agent"
|
||||
# Launches the game bare (-level_XX, pools auto-injected per D121), tees all
|
||||
# output to build-pc/playtest_L<XX>_<HHMMSS>.log, and reports crash status
|
||||
# on exit. Human plays start-to-finish; close the window when done.
|
||||
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
LEVEL="${1:?usage: playtest.sh <XX> [difficulty]}"
|
||||
DIFF="${2:-Agent}"
|
||||
export PATH="/c/msys64/mingw64/bin:$PATH"
|
||||
|
||||
if [ ! -x build-pc/ge007.x86_64.exe ]; then
|
||||
echo "binary missing — building..."
|
||||
./build-pc.sh ntsc-final
|
||||
fi
|
||||
|
||||
LOG="build-pc/playtest_L${LEVEL}_$(date +%H%M%S).log"
|
||||
echo "== playtest level ${LEVEL} (difficulty: ${DIFF}) =="
|
||||
echo "== log: ${LOG}"
|
||||
echo "== checklist: docs/LEVEL-PLAYTEST.md — spawn, geometry, guards,"
|
||||
echo "== doors/pickups/objectives, alarms, exit -> MISSION COMPLETE, auto-advance"
|
||||
|
||||
set +e
|
||||
./build-pc/ge007.x86_64.exe "-level_${LEVEL}" 2>&1 | tee "$LOG"
|
||||
RC=${PIPESTATUS[0]}
|
||||
set -e
|
||||
|
||||
echo "==============================="
|
||||
if grep -q "FATAL: EXCEPTION" "$LOG"; then
|
||||
echo "RESULT: CRASH (see log)"
|
||||
grep -A3 "FATAL: EXCEPTION" "$LOG" | head -8
|
||||
elif grep -qi "mission complete\|MISSION COMPLETE" "$LOG"; then
|
||||
echo "RESULT: MISSION COMPLETE seen in log"
|
||||
else
|
||||
echo "RESULT: exited rc=${RC} (no crash marker; check log + your notes)"
|
||||
fi
|
||||
echo "log: ${LOG}"
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# Repro harness: launch a level under gdb-attach, dump a full backtrace on the
|
||||
# first access violation (0xc0000005 / SIGSEGV). Human plays; close nothing --
|
||||
# gdb prints the trace and detaches when it faults.
|
||||
#
|
||||
# Usage: ./tools_pc/repro_gdb.sh <XX> e.g. ./tools_pc/repro_gdb.sh 34
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
export PATH="/c/msys64/mingw64/bin:$PATH"
|
||||
LEVEL="${1:?usage: repro_gdb.sh <XX>}"
|
||||
STAMP=$(date +%H%M%S)
|
||||
LOG="build-pc/repro_L${LEVEL}_${STAMP}.log"
|
||||
GDBLOG="build-pc/repro_L${LEVEL}_${STAMP}.gdb.txt"
|
||||
export GE_PCDUMP="1-99999:30" # rolling frame dump -> ppm/ (last few = pre-crash)
|
||||
|
||||
cat > "build-pc/repro_cmds_${STAMP}.txt" <<'EOF'
|
||||
set pagination off
|
||||
set width 0
|
||||
set confirm off
|
||||
handle SIGSEGV stop print nopass
|
||||
continue
|
||||
echo \n\n=================== FAULT CAUGHT ===================\n
|
||||
bt
|
||||
echo \n--- frame 0 ---\n
|
||||
frame 0
|
||||
info args
|
||||
info locals
|
||||
echo \n--- full backtrace ---\n
|
||||
bt full
|
||||
echo \n--- registers ---\n
|
||||
info registers rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11
|
||||
echo \n===================================================\n
|
||||
detach
|
||||
quit
|
||||
EOF
|
||||
|
||||
echo "== launching -level_${LEVEL} (nohup); play a firefight to trigger the crash =="
|
||||
nohup ./build-pc/ge007.x86_64.exe "-level_${LEVEL}" > "$LOG" 2>&1 &
|
||||
BASHPID=$!
|
||||
sleep 6
|
||||
# winpid = 4th column of ps -p <bashpid>
|
||||
WINPID=$(ps -p "$BASHPID" | awk 'NR==2{print $4}')
|
||||
echo "== bash pid=$BASHPID winpid=$WINPID attaching gdb =="
|
||||
echo "== game log: $LOG"
|
||||
echo "== gdb log: $GDBLOG"
|
||||
gdb -batch -x "build-pc/repro_cmds_${STAMP}.txt" -p "$WINPID" 2>&1 | tee "$GDBLOG"
|
||||
echo "== done. crash log: ge007.crash.log ; pre-crash frames: ppm/ =="
|
||||
Reference in new issue
Block a user