D132: fix propDef union-index slots for LINK/SWITCH/LOCK_DOOR/SAFE_ITEM

Types 14/19/38/44 declare their Index{1,2,3} fields as a union with a
pointer, so on PC each is an 8B/8-aligned slot (Index1@8, Index2@16,
...) and the record ends in a *next the proplvreset2 walk writes. The
converter was laying the N64 tight-4-byte index words at PC bytes 4/8/12,
putting Index1 where the compiler reads Index2 and leaving Index2 zero.
Non-crashing but silently broke switch-doors, dual weapons, padlocked
doors, and safe/safe-item links.

- tools_pc/d88_propdefs.py: PROPDEF_PC_BYTES 14/19->32, 44->40; handler
  emits Index{k} into low 4B of the 8-aligned slot at PC 8+8*k.
- loadobjectmodel.c sizepropdef() PORT: LINK/SWITCH/LOCK_DOOR->8,
  SAFE_ITEM->10, TAG stays 6.

Verified: -level_09 framediff 3/3 PASS; -level_20 identical to a
freshly-built pre-D132 baseline (frame_000320 delta is pre-existing
stale-golden/Silo slowdown, not a regression); Archives/Streets/Dam
load+render+no-crash.
This commit is contained in:
James Dansereau committed 2026-08-29 23:16:16 -04:00
1 parent e5f4290bc6
commit 5e9ea50add
3 files changed
+36 -11

No files matched your search

+17 -1
View File
@@ -4817,7 +4817,23 @@ Depot, sevb, sevx.
`PROPDEF_PC_BYTES == real compiler sizeof`. D122's totality note flagged
`[u16|u16]` half-swap but not the `union{ptr; s32}` index-slot case.
### Proposed fix (NOT applied — ABI/layout only, `#ifdef PORT`)
### APPLIED (M-20, commit pending) — ABI/layout only, `#ifdef PORT`
Fix below applied verbatim. `tools_pc/d88_propdefs.py`: `PROPDEF_PC_BYTES`
`14/19 -> 32`, `44 -> 40`; the `(14,19,38,44)` handler now emits each
`Index{k}` into the low 4 B of the 8-aligned slot at PC `8 + 8*k`
(`nidx = {14:2,19:2,38:2,44:3}`). `loadobjectmodel.c sizepropdef()` PORT
switch: `LINK/SWITCH/LOCK_DOOR -> return 8`, `SAFE_ITEM -> return 10`,
`TAG` stays `6`. Regen chain run. Verified: `-level_09` framediff 3/3
PASS; `-level_20` behaviour byte-identical to a freshly-built pre-D132
baseline (the `frame_000320` phash delta is pre-existing stale-golden /
Silo slowdown, NOT a regression — confirmed by building the baseline);
Archives (`-level_25`, SWITCH+SAFE_ITEM), Streets (`-level_29`, 20x
LOCK_DOOR), Dam (`-level_33`, SWITCH+LOCK_DOOR) all load + render +
no-crash. Sweep runs this pass were heavily flaky (level_09 itself
0-framed on a loaded machine) — pure D117/watchdog noise, no crashes.
### Original proposed fix (as written pre-apply)
`tools_pc/d88_propdefs.py` — `PROPDEF_PC_BYTES`: `14: 24 -> 32`,
`19: 24 -> 32`, `44: 24 -> 40` (38 stays 32). Replace the
+7 -3
View File
@@ -78,13 +78,17 @@ s32 sizepropdef(PropDefHeaderRecord *pdef)
case PROPDEF_OBJECTIVE_ENTER_ROOM:
case PROPDEF_WATCH_MENU_OBJECTIVE_TEXT:
case PROPDEF_OBJECTIVE_DEPOSIT_OBJECT_IN_ROOM: return 6;
case PROPDEF_TAG: return 6;
case PROPDEF_CAMERAPOS: return 7;
/* D132: LINK / SWITCH / LOCK_DOOR declare their Index{1,2} fields as a
* union with a pointer, so each is an 8B/8-aligned slot on PC, and the
* record ends in a *next the proplvreset2 walk writes -> PC sizeof 32
* (8 words). SAFE_ITEM has 3 such unions + *next -> sizeof 40 (10). */
case PROPDEF_LINK:
case PROPDEF_SWITCH:
case PROPDEF_TAG:
case PROPDEF_SAFE_ITEM: return 6;
case PROPDEF_CAMERAPOS: return 7;
case PROPDEF_GUARD:
case PROPDEF_LOCK_DOOR: return 8;
case PROPDEF_SAFE_ITEM: return 10;
case PROPDEF_RENAME: return 12;
case PROPDEF_PROP:
case PROPDEF_ALARM:
+12 -7
View File
@@ -129,9 +129,9 @@ PROPDEF_PC_BYTES = {
10: 288, # MonitorObjRecord
11: 664, # MultiMonitorObjRecord
13: 248, # AutogunRecord
14: 24, # LinkRecord (3w + next* grown, 8-aligned)
14: 32, # LinkRecord (D132: union{ptr;s32} Index1@8 Index2@16 next*@24)
18: 12, # GuardAttributeRecord
19: 24, # SwitchRecord = LinkRecord
19: 32, # SwitchRecord = LinkRecord (D132)
20: 200, # MultiAmmoCrateRecord
21: 152, # BodyArmourRecord
22: 24, # TagObjectRecord
@@ -148,7 +148,7 @@ PROPDEF_PC_BYTES = {
38: 32, # LockDoorRecord
39: 208, # VehichleRecord (D122: 144 prefix + widened tail)
40: 208, # AircraftRecord (D122)
44: 24, # SafeObjectRecord fragment (5w + next*), refine on use
44: 40, # SafeObjectRecord (D132: 3x union{ptr;s32} Index1@8 Index2@16 Index3@24 next*@32)
45: 248, # TankRecord (D122: 144 prefix + 23-word scalar tail + collision*)
46: 28, # CutsceneRecord
47: 168, # TintedGlassRecord
@@ -286,11 +286,16 @@ def convert_record(src, so, type_byte):
return bytes(out) # renobj* @40 zero
if type_byte in (14, 19, 38, 44): # LINK / SWITCH / LOCK_DOOR / SAFE_ITEM
# D132: each Index{1,2,3} field shares a union with a pointer, so on
# PC it lives in the LOW 4 bytes of an 8B/8-aligned slot at PC offset
# 8 + 8*(N-1); the record ends in a *next the setup walk writes. The
# N64 image packs the indices as tight 4-byte words; emitting them
# there put Index1 where the compiler reads Index2.
nidx = {14: 2, 19: 2, 38: 2, 44: 3}[type_byte]
out[0:4] = _hdr_word(src[so:so + 4])
# remaining serialized words: Index1/Index2 are s32 in file (zero or id);
# trailing next* is not serialized.
for i in range(1, n64w):
out[4 * i:4 * i + 4] = _bswap32(src[so + 4 * i: so + 4 * i + 4])
for k in range(nidx):
w = src[so + 4 * (k + 1): so + 4 * (k + 1) + 4]
out[8 + 8 * k: 12 + 8 * k] = _bswap32(w) # low 4B, LE; high 4B = 0
return bytes(out)
if type_byte == 18: # SetGuardAttribute: hdr + s32 chrnum + [s16 unk8][s8 unkA][s8 GrenadeProb]