# Capital-ship part placement — runtime capture (ground truth) **Status:** pipeline working end-to-end for one ship (`e106` ADAN Destroyer); correlator recovers exact ship-relative transforms. Not yet baked into the viewer. ## Problem Capital ships ship as **split XBG7 parts** (hull bodies, bridge, engines, turrets) in `hidden/resource3d/Stage_SNN.xpr`. Reconstructing the whole ship needs each part's placement. Static reverse-engineering of the composite scene graph (`e_rou_`, `GN_*` frames) got the **hull right but external parts only approximately** — a `GN_*` frame is the mount *pivot*, and the real outboard / rotational offset lives in a detail sub-rig or in runtime code not recoverable statically. See `sylpheed-formats::ship::assemble_ship` (the static best-effort). ## Finding The **guest vertex buffer holds LOCAL coordinates** — byte-identical to the `.xpr` (verified: `e106_bdy_04`'s first vertex `(-20.0000,153.1539,45.9997)` matches the decode exactly). So parts are placed **entirely in the vertex shader**; the buffer carries no placement. (Contrast: static *stage* geometry is pre-transformed into world space and byte-matches the `.xpr` at file offsets — that's a different case.) The per-part transform is in the **vertex-shader float constants**. For the ship shader (`vs=0xC7F781F4C1D58054`), constants `c0..c2` are the **WorldViewProjection** matrix rows: - Their norms are `(sx, sy, 1)` = the projection x/y scales; `sy/sx ≈ 1.78 = 16:9` aspect. Dividing each row by its norm gives the rigid **WorldView** (verified: normalized rows orthonormal to 1e-4, `det = +1`). - The camera View cancels when expressing every part relative to a reference part: `rel_p = WV_ref⁻¹ · WV_p` = `(Rᵀ_ref·R_p , Rᵀ_ref·(T_p − T_ref))` — a pure ship-space rigid transform. Applying `rel_p` to part `p`'s local geometry assembles the ship exactly, in the reference part's frame. (`c4..c14` also vary per part — likely a normal matrix / a second WVP variant / WorldView split; not needed, `c0..c2` suffice.) ## Capture instrumentation Branch **`capture-ship-placement`** in the `xenia-canary-native` worktree (off the `instrument-current`-descended native branch, which already has GPU `log_draws`). - **F10** in the emulator window → `xe::gpu::RequestShipCaptureFrame()`. - Dumps the next ≤8000 draws (de-duped by vertex-buffer address) to **`xenia_ship_capture.log`** in the binary's dir (`build/bin/Linux/Release/`). Per draw: ``` DRAW vbase=0x… stride=… vcount=… indices=… prim=… vs=0x… pos: (x,y,z) … up to 64 LOCAL vertex positions (== .xpr) vsconst base=N: c0=(x,y,z,w) c1=… … first 48 VS float4 constants ``` - Files: `src/xenia/gpu/command_processor.{cc,h}` (`CaptureShipDrawForRE`, `RequestShipCaptureFrame`), `src/xenia/app/emulator_window.cc` (F10 key). - Run: `run-canary-native.sh` (HW Vulkan, interactive). Play into the mission, frame the ship side-on, press F10. ## Correlator `sylpheed-formats/examples/correlate_capture.rs`: ``` SYLPHEED_ISO=… cargo run --release --example correlate_capture -- \ [ref_part_substr] ``` Parses the log, decodes the ship's base parts, matches each part to a draw by **vertex count** (unique per part), recovers WorldView from `c0..c2`, and prints each part's ship-relative transform (reference-part frame) + assembled extent. ## Validated result — `e106` ADAN Destroyer (Mission 1 / Stage_S01, side view) Vertex-count → part map (all matched exactly): bdy_01/02 = 1261, bdy_03 = 815, bdy_04 = 1633, eng_01 = 583, eng_02 = 491, wep_02_01 = 1002, brg_01 = 202. Ship-relative placement (reference = `bdy_04`, aft hull): | part | T (ship-relative) | note | |---|---|---| | bdy_04 | (0, 0, 0) | reference, aft hull | | eng_01 | (131, −133, −132) | **aft** (−Z), starboard | | eng_02 | (0, −49, −140) | aft, centre | | bdy_01 | (**−264**, −150, 1165) | **port** hull, forward | | bdy_02 | (**+264**, −150, 1165) | **starboard** hull, forward | | bdy_03 | (0, −35, 1076) | forward keel/spine | | wep_02_01 | (0, 49, 1009) | forward weapon | Assembled extent **872 × 670 × 2181** (a coherent ~2181-long destroyer). **Key wins vs static:** `bdy_01`/`bdy_02` are a **port/starboard pair** (X = ∓264), which static had overlapping at one point; engines land correctly aft; the ship is not the 1894-tall tower the static Y put out. **Missing:** `brg_01` (bridge, vcount 202) was **culled/occluded** at that camera angle (0 draws) — needs a second capture framing the superstructure. ## Next steps 1. **Bridge:** re-capture with the bridge visible → fills `brg_01`. 2. **Bake:** promote the correlator to a module; emit a per-ship placement table (`ship_id → [(part, R, T)]`) as a checked-in data file; have the viewer's `build_ship_model` prefer the captured table over `assemble_ship` when present. 3. **Other ships:** same capture for the cruiser (`e105`, Stage_S02), ACROPOLIS (`f101`), TCAF cruiser/destroyer (`f105`/`f106`). One side-on F10 each. 4. **Turrets:** the shared `e3NN`/`e4NN` gun models appear as their own draws in the capture too — correlate them to place turrets (static never resolved these).