Files
Syplheed-Reborn/docs/re/ship-placement-runtime-capture.md
MechaCat02 4efc0278b1 ship_capture: ingest the draw-logger format + quantify the static gaps
The correlator now accepts BOTH capture formats: the F10 ship-capture snapshot
and the draw-logger format (mission_draws.log / xenia_re_draws.log) via
parse_drawlog — group the ship shader's draws by vertex-buffer base, vcount =
size_words/stride_words, keep the first c0..c2 WVP. So a capital ship seen in a
normal instrumented run bakes without a dedicated F10 pass. correlate_capture
auto-detects the format. Shared normalize_wvp between both parsers.

Also add examples/ship_dump.rs (offline static-placement inspector) and record
the measured static-assembler gaps for e106 in the doc: bdy_01/bdy_02 overlap
(runtime port/starboard mirror at X=+-264), eng_02 and wep_02_01 never placed.
These confirm exact placement is runtime-only — nothing more to squeeze
statically; the capture-driven table is the only path. The draw logs present on
this box are the player fighter, so no capital-ship table can be baked offline.

7 ship_capture tests (adds draw-log parse + correlate); 77 formats-lib green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 20:57:08 +02:00

152 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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. **Bake plumbing landed** — the
correlator is now a tested library module and the viewer prefers a captured table;
only real capture data still needs baking in (needs the emulator + F10).
## 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 (now a library module)
The correlation math lives in **`sylpheed-formats::ship_capture`** (unit-tested with
synthetic captures): `parse_capture` → `CapturedDraw`s, `correlate(id, draws,
parts, ref_sub)` → a `ShipPlacement` (each part in the reference frame), and
`serialize_table`/`parse_table` for the checked-in text table.
Two capture formats are accepted (auto-detected): the F10 ship-capture snapshot
([`parse_capture`]) and the **draw-logger** format `mission_draws.log`/
`xenia_re_draws.log` (`parse_drawlog` — groups the ship shader `SHIP_VS_HASH`'s
draws by vertex-buffer `base`, `vcount = size_words/stride_words`). So a capital
ship seen in a normal instrumented run can be baked without a dedicated F10 pass.
`examples/correlate_capture.rs` is the CLI wrapper:
```
SYLPHEED_ISO=… cargo run --release --example correlate_capture -- \
<capture.log> <Stage_SNN> <ship_id> [ref_part_substr] [--emit]
```
Decodes the ship's base parts (for their vertex counts = the match key), correlates,
prints each part's ship-relative transform, and with `--emit` prints the `ship …`
block to paste into the placement table.
## Baked table + viewer preference
`crates/sylpheed-formats/data/ship_placements.txt` is the checked-in placement
table (embedded via `ship_capture::embedded_placement`). The viewer's
`build_ship_model` uses a ship's captured entry when present, else falls back to
the static `assemble_ship` (`external` toggle). **The table is currently empty** —
a real F10 capture log is needed to bake in `e106` (and others); paste the
`--emit` block into that file and rebuild.
## 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.
## What the static assembler gets wrong (measured, `e106` in Stage_S01)
Decoding the real container (`examples/ship_dump.rs`) shows the static
`assemble_ship` is **incomplete** — the capture is required to fix all of:
- **`bdy_01` / `bdy_02` overlap**: both get the *same* composite transform
(T = (116, 0, 857)); the real ship is a **port/starboard pair** at X = ∓264
(the mirror is applied at runtime, like the DeltaSaber fins in `saber_measured`).
- **`eng_02` and `wep_02_01` are never placed**: they aren't `rou_` hull nodes and
aren't in the external category list, so they drop out entirely.
- Only `brg_01` + `eng_01` get the approximate `GN_*`-frame treatment.
So even the *hull* tier is not fully correct, and no static signal supplies the
missing offsets (verified: the part vertex buffers are pure local coords). The
capture-driven table is the only path to correctness — there is nothing more to
squeeze statically.
> Offline status: the draw logs on this box are the **player fighter**, not a
> capital ship, so no capital-ship table can be baked here yet. Capture `e106`
> (Stage_S01, side-on) on the emulator box — F10 **or** just save
> `mission_draws.log` with the ship on screen — then `correlate_capture … --emit`.
## Next steps
1. ~~**Bake:** promote the correlator to a module + checked-in table + viewer
preference.~~ **DONE** (`ship_capture` module, `data/ship_placements.txt`,
`build_ship_model` prefers it). Remaining: run the captures below and paste the
`--emit` blocks in — needs the emulator (F10), can't be done offline.
2. **e106 + bridge:** re-capture `e106` (Stage_S01) framing the superstructure so
`brg_01` (culled last time) is included, then `--emit` → bake.
3. **Other ships:** one side-on F10 capture each for the cruiser (`e105`,
Stage_S02), ACROPOLIS (`f101`), TCAF cruiser/destroyer (`f105`/`f106`).
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).