ship: STATIC assembly is now EXACT — node-TRS off-by-one cracked via the capture

The runtime capture served its true purpose: as the answer key that exposed the
static encoding. The XBG7 node joint table (node+0x44) holds NINE keyframe
channels [TX TY TZ RY RX RZ SX SY SZ] behind EIGHT pointer slots shifted one
track forward: channel k+1 = f64@(ptr[k]+8), and channel 0 -- TX, which no
pointer names -- sits at ptr[0]-0x48 (tracks are 0x50-byte records, value at
+8). The old 8-slot read took TY as X, RY as Y (usually 0 -- why both hulls
stacked on the centreline) and never saw TX at all (the +-264 hull offsets).
Euler order is Ry(ch3)*Rx(ch4)*Rz(ch5) with angles applied directly, pinned by
the captured engine nacelle Rx(-15)*Rz(30) decomposition. mesh.rs gains
read_trs9/node_rotation; scene_world_nodes and node_transforms both fixed
(saber fin overrides unaffected).

assemble_ship rewritten fully static and exact:
- every composite-node instance placed (alias duplicates collapsed, real
  instances kept) including cross-id turret mounts (rou_e303_wep_01_root x2)
- the e_rou_<id>_eng cluster rig mounts at GN_Engine_01 (two mirrored nacelles
  +-131 with -+30-degree cant + centre engine) -- world = frame o rig_local
- brg/sld at their exact GN frames (frames were always exact)
- shared-geometry twin pairs at +-TX: X-reflect the instance whose offset
  opposes the geometry's dominant side (viewer reverses winding on det<0)
- squeezed node names resolve (wep02 -> wep_02_01)

Ground-truth gate: ship::tests::static_assembly_matches_runtime_capture asserts
static == the baked e106 capture for all 8 parts (T<1.0, R<0.02) plus both
nacelles, both turrets, and the starboard-hull mirror. Viewer now uses pure
static assembly; ship_capture + the baked table remain as the verification
oracle. Renders show a coherent, bilaterally-symmetric destroyer.

81 formats-lib + all disc tests green; viewer builds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-26 19:00:49 +02:00
parent 015e49ac8a
commit 737b61242e
5 changed files with 433 additions and 81 deletions

View File

@@ -1,10 +1,20 @@
# Capital-ship part placement — runtime capture (ground truth)
**Status:** **`e106` ADAN Destroyer FULLY BAKED (2026-07-26)** — all 8 parts
(incl. the bridge both earlier captures missed, and the runtime-mirrored
starboard hull) recovered from a second F10 capture and checked in to
`data/ship_placements.txt`; the viewer renders it via the captured table.
Remaining ships (e105, f101, f105/f106, turrets) need one capture each.
**Status:**✅ **STATIC ASSEMBLY IS EXACT — no captures needed anymore
(2026-07-26).** The capture became the oracle that cracked the static encoding:
the node joint tables are **9 keyframe channels `[TX TY TZ RY RX RZ SX SY SZ]`
with the 8 table pointers shifted one track forward** (channel 0 = TX sits at
`ptr[0]0x48`; the old 8-slot read took TY as X, never saw TX — why hulls
stacked on the centreline). Euler order `Ry·Rx·Rz`, angles direct
(`mesh.rs::read_trs9` / `node_rotation`). `assemble_ship` now places every
composite-node instance (cross-id turrets ×2), mounts the `e_rou_<id>_eng`
cluster rig at `GN_Engine_01` (two mirrored nacelles + centre engine), puts
`brg`/`sld` at their exact GN frames, and X-reflects the shared-geometry twin
whose lateral offset opposes the geometry's dominant side.
`ship::tests::static_assembly_matches_runtime_capture` asserts static ==
captured for all 8 e106 parts (T < 1.0, R < 0.02) + both nacelles + both
turrets + the hull mirror. The viewer uses pure static assembly; the baked
table + correlator remain as the verification oracle only.
## Problem