D112: fix d43_emit put_f32 byte-swap off-by-one (skeletal models collapsed)

put_f32 reversed bytes doff+1..doff+4 instead of doff..doff+3, dropping
each float's MSB and pulling in a byte of the adjacent field. Corrupted
every f32 in converted model rodata (joint Origins, LOD distances, BSP
planes, bounding radii) for all sidecar-loaded models. Compiled-in
front-end models were unaffected, hence 'logo fine, every guard a line'.

render_pos[] joint matrices now coherent; BUNKER guards render as
humanoids and monitor screens draw content. Regen: python
tools_pc/d43_emit.py ntsc-final.
This commit is contained in:
James Dansereau committed 2026-08-29 09:21:12 -04:00
1 parent cb73ea0985
commit 3a02ded079
3 files changed
+66 -19

No files matched your search

+25 -18
View File
@@ -40,27 +40,34 @@ 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".
**Storage-room sky void — root-caused (session M-4), it is the
prop/model track:** the void is *closed doors* — portals 25/26 are
correctly `PORTALFLAG_DISABLED`, and `chrpropsRenderPass` runs (`n=3..10`
props/room) but emits almost no geometry, so the door models that should
fill those openings never draw. Portal visibility itself is healthy
post-D106 (room 18 → 8 rooms deep). Fixing prop/door rendering unblocks
this AND the missing weapon.
**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.
**Real remaining work, in order:**
1. **Prop / character model rendering** — `chrpropsRenderPass` enumerates
props but emits no geometry; door models (storage void), weapon model,
and skeletal characters all ride this. Overlaps the `struct player`
offset pass and D75(b). Start here — highest visual + playability
leverage. `GE_D96` probe in `chrprop.c`.
2. **`struct player` offset pass** — weapon model doesn't draw; gate to
1. **Portal-BFS under-reach in the storage area** — per-frame visible
room count drops to 1–2 in rooms 27–29 (`GE_D104`/probes now removed).
`projmatrix` + `screensize` verified valid; the 200→3 `g_RoomLoadBudget`
drop is intentional N64 FP-cam behaviour. Likely `bgQueuePortalTraversal`
/ `bgDetermineVisibleRooms` portal-projection. This is the void.
2. **Character pose / orientation polish** — post-D112 a BUNKER guard
renders but appears inverted; suspect a matrix-handedness / lookat
sign item in the joint base matrix. Also the mispositioned static
crate prop (top-left in the control room).
3. **`struct player` offset pass** — weapon model doesn't draw; gate to
playability (landmine below + D102).
3. **D75(b)** — animated/skeletal character models (see §F "D75" and
"Known rendering bugs"). All three intro-model failures traced to the
game `matrixmath.c` → `modelUpdateMatrices`/`process_02_position` →
raw `render_pos` `i*0x40` re-pack path (NOT the D73 sin/cos path);
static prop = mispositioned, animated `setup_chr_instance` = missing.
4. Add an f32 value spot-check to `d43_emit.py`'s verify pass (it only
checks pointers/opcodes today — this bug passed "ALL CHECKS PASSED").
**This session's fixes (all committed, master):**
+40
View File
@@ -3401,6 +3401,46 @@ table via `bgSwapRoomVtx` or the point-index blob length); front-end
text draws mirrored. Next: prop/door model rendering (unblocks the void
AND the missing weapon), then D75(b) skeletal models.
**D108–D112 (session M-5) — the "props emit no geometry" premise was
stale; the real bug was a converter byte-swap off-by-one, FIXED.**
Re-probing `chrpropsRenderPass` / `chrpropRender` / `modelRenderNodeDl`
(`GE_D96*`) showed props *do* emit complete leaf DLs now (~15k
`nodeDl` EMITs/run, valid `gdl`/`vtx`/`BaseAddr`; fast3d transforms
~300–600 prop verts/frame with sane `w`). Two separate residual bugs
were isolated: (1) in the BUNKER storage area the per-frame visible-room
count drops to 1–2 while `cameramode` goes 1→0 and `g_RoomLoadBudget`
200→3 — but the 200→3 is *intentional* N64 behaviour
(`bgRoomVisibilityRelated`: 0xC8 for intro/swirl cams, 3 for FP) and
`currentPlayerGetProjectionMatrix()` + `g_CurrentPlayer->screensize`
were both verified valid, so this is a portal-BFS under-reach, still
open. (2) **Skeletal characters rendered as a "3D line" / not at all**
(also visible in attract mode): `drawjointlist` → `subcalcmatrices` →
`modelUpdateMatrices` → `process_02_position` → `modelBuildGroupMatrices`
produced `render_pos[]` joint matrices with **sane rotations but garbage
/ 1e27 / NaN positions**. `basemtx` (`camGetWorldToScreenMtxf()`) was a
clean orthonormal lookat; joint *rotations* from the anim bitstream were
fine; but `group->Origin` (`ModelRoData_GroupRecord.Origin`, a
`coord3d`) read as garbage for every joint whose bytes weren't
zero — while the adjacent `JointID`/`MatrixIDs` (u16/s16) read fine.
Root cause: **`put_f32` in `tools_pc/d43_emit.py` had an off-by-one in
its BE→LE byte reversal** — `buf[o:o+4] = src[doff+4:doff:-1]` yields
bytes `doff+1..doff+4` (dropping the value's MSB, pulling in one byte of
the next field) instead of `doff..doff+3`. This corrupted *every* f32
field in converted model rodata (joint `Origin`s, LOD near/far, BSP
planes, bounding radii) for every sidecar-loaded model; the compiled-in
front-end intro models (native-LE C structs, not run through the
converter) were unaffected, which is why the Rareware logo looked right
but every in-level character collapsed. Fix: `src[doff:doff+4][::-1]`.
After regen (`python tools_pc/d43_emit.py ntsc-final`), all 20
`render_pos` entries of a BUNKER guard are coherent (~±60 units around
the body), monitor screens draw their content, and character models
render as recognisable humanoids (still some pose/orientation polish
owed — a guard appears inverted — a separate matrix-handedness item).
`d43_emit.py`'s verify pass only checks pointers/opcodes, not float
values — a float spot-check is worth adding. Probes `GE_D96GATE /
D96DL / D108 / D109 / D110 / D111 / D112 / GE_TRICNT` were all removed
after the fix.
**`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
+1 -1
View File
@@ -490,7 +490,7 @@ def process(name):
def put_u16(o, v): buf[o:o + 2] = struct.pack("<H", v & 0xFFFF)
def put_s16(o, v): buf[o:o + 2] = struct.pack("<H", v & 0xFFFF) # bytes identical to <h
def put_u32(o, v): buf[o:o + 4] = struct.pack("<I", v & 0xFFFFFFFF)
def put_f32(o, doff): buf[o:o + 4] = src[doff + 4:doff:-1] # bswap32 (4 bytes!)
def put_f32(o, doff): buf[o:o + 4] = src[doff:doff + 4][::-1] # bswap32 (BE->LE)
def put_ptr(o, old_off):
if not old_off:
return