ship: engine placement CONFIRMED by 43-instance capture; exhaust markers added

capture_verify example: clusters every part's ship-relative transform across
the new multi-snapshot, multi-instance F10 captures (5 snapshots, 43 e106
instances, all angles). Verdict: the dominant clusters match the static
assembly to ~1 unit on EVERY part — including BOTH engine nacelles at
(+-131, -133, -131) — so the "engines inside the hull" appearance is the
game's own placement: the engine geometry sits recessed in the aft hull, and
the visible "thrusters" in-game are exhaust FX drawn at the GN_Jet/GN_SJet
frames (Z ~ -570, past the stern).

To close that perception gap the viewer now draws simple exhaust cones at the
game's own jet frames (ship::exhaust_frames; part of the external-parts
toggle). The jet frames flip Z, so the cone apex is authored at +Z and lands
trailing aft — verified in the offline render.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-26 19:44:12 +02:00
parent 31f2637e6c
commit ea32e77e45
4 changed files with 285 additions and 0 deletions

View File

@@ -376,6 +376,29 @@ pub fn assemble_ship(bytes: &[u8], id: &str, include_external: bool) -> Vec<Scen
placed
}
/// The ship's exhaust mount frames (`GN_Jet_*` / `GN_SJet_*`), world-space, from
/// the primary composite. These are where the game renders the engine exhaust
/// FX — the visible "thrusters". The engine GEOMETRY itself sits recessed in
/// the hull (capture-verified across 43 e106 instances: the game draws
/// `eng_01`/`eng_02` exactly where [`assemble_ship`] puts them); without the
/// FX the assembled ship reads engine-less, so a viewer can draw exhaust
/// markers at these frames.
pub fn exhaust_frames(bytes: &[u8], id: &str) -> Vec<ScenePart> {
let names = xbg7_resource_names(bytes);
let scenes: Vec<Vec<ScenePart>> = composites_for(&names, id)
.into_iter()
.filter(|c| !c.ends_with("_joint"))
.map(|c| scene_world_nodes(bytes, c))
.collect();
let Some(nodes) = scenes.into_iter().max_by_key(|n| n.len()) else {
return Vec::new();
};
nodes
.into_iter()
.filter(|n| n.resource.starts_with("GN_Jet") || n.resource.starts_with("GN_SJet"))
.collect()
}
/// Detect shared-geometry port/starboard twin RESOURCES placed at ±TX and mark
/// the dominant-side instance as an X-reflection (negate the matrix X column).
/// Twins are `<stem>_01`/`<stem>_02` pairs with equal vertex data; the reflected