re: runtime-capture pipeline for exact capital-ship part placement
Static external-part placement (ship::assemble_ship) is only approximate — a GN_* frame is the mount pivot, and the real offset lives in a runtime detail rig. So capture ground truth from Canary instead. Finding: the guest vertex buffer holds LOCAL coords (byte-identical to the .xpr), so capital-ship parts are placed entirely in the vertex shader. The per-part transform is in the VS float constants — c0..c2 are the WorldViewProjection rows; normalising each by its norm (= projection x/y/z scales, sy/sx = 16:9) yields the rigid WorldView (rows orthonormal, det +1). Expressing every part relative to a reference part cancels the camera: rel_p = WV_ref⁻¹·WV_p, a pure ship-space rigid transform. - examples/correlate_capture.rs: parse xenia_ship_capture.log, decode the ship's parts, match by vertex count, recover each part's ship-relative placement. - docs/re/ship-placement-runtime-capture.md: full method + the capture protocol (Canary branch capture-ship-placement, F10 hotkey) + validated e106 result. Validated on e106 (ADAN Destroyer, Stage_S01): all 7 drawn parts matched by vertex count; bdy_01/bdy_02 recovered as a PORT/STARBOARD pair (X = ∓264) that static had overlapping; engines correctly aft; extent 872×670×2181. Only brg_01 (bridge) missing — culled at that camera angle, needs a bridge-facing capture. HANDOFF: next = re-capture bridge, bake a per-ship placement table the viewer prefers over assemble_ship, then capture the cruiser / ACROPOLIS / TCAF ships. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ Promote to a prose `structures/…md` file when a format needs behavioural notes
|
||||
| IXUD subtitle | 🟡 | `sylpheed-formats/src/ixud.rs` | timed cues; **movie↔track link unknown** (dynamic item) |
|
||||
| Fonts (ttf/otf/ttc) | ✅ | `sylpheed-formats/src/font.rs` | standard OpenType, parsed via ttf-parser |
|
||||
| XBG7 mesh | 🟡/❔ | `sylpheed-formats/src/mesh.rs` + `tests/mesh_disc.rs` ([xbg7](structures/xbg7-mesh.md)) | weapons/props: declaration-driven variable stride (36 models), GPU-confirmed. **Stage containers: 5662 sub-models across 22 stages** via content-anchored grouped pools (`stage_models`). Quantized hero bodies (DeltaSaber `f004`) still declined |
|
||||
| Capital-ship part placement | 🟡 | `sylpheed-formats/src/ship.rs` (static) + [runtime capture](ship-placement-runtime-capture.md) | hull placement static-exact; external parts approximate statically. **Runtime capture** (Canary F10 → VS-constant WorldView) gives ground truth — validated on `e106` destroyer; not yet baked into the viewer |
|
||||
|
||||
## Functions / code paths
|
||||
|
||||
|
||||
105
docs/re/ship-placement-runtime-capture.md
Normal file
105
docs/re/ship-placement-runtime-capture.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# 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_<id>`, `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 -- \
|
||||
<capture.log> <Stage_SNN> <ship_id> [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).
|
||||
Reference in New Issue
Block a user