re: the format layer is NOT exonerated -- assemble_ship draws a non-renderable volume
Did the visual the backlog kept naming as the next step, and it overturns the
conclusion. Rendering e106 from the static assembly and from the baked runtime
capture: the capture has 8 placements, the static assembly 11 -- the same 8 plus
e303_wep_01 x2 and a second e106_eng_01. In the render the destroyer sits inside
a white slab that dwarfs it.
That slab is e303_wep_01: 172 verts, 110 tris, bounds X[-1000,600] Y[-1050,1050]
Z[-2400,2400] = 1600 x 2100 x 4800. A real e106 turret (wep_02_01) is 269x179x417
with 772 tris; the bridge is 105x76x305. So it is not a turret -- it reads as a
collision/trigger volume, and whatever it is the game does not draw it: the e106
capture contains no e303_wep_01 at all.
Why it was missed: assemble_ship treats every rou_* composite node as drawable,
the cross-id mount is documented as intended, and the test asserts
count("e303_wep_01") == 2. The capture's silence was explained as vbase dedup,
but dedup would show one instance, not zero -- and the test walks capture parts
looking them up in the static output, so EXTRA static placements cannot fail it.
Same shape as the earlier include_external gap: a test that can only fail one way.
Scope stated carefully: sweeping 335 ships for "ship-scale span with <400 tris"
flags 20 ships and 58 placements over 28 resources, but ONLY the e106 case is
proven (render + capture absence + geometry). Others may be legitimately large
low-poly parts and need the same three checks.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -150,3 +150,61 @@ comparison is no longer merely the next step — it is the only remaining one.**
|
||||
Render `e106` in the viewer beside `ship_render`'s output of the same
|
||||
`assemble_ship` result; if they agree, the bug is in neither and the original
|
||||
report needs re-grounding against a specific ship and a specific expectation.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ The format layer is NOT exonerated — `assemble_ship` draws a non-renderable volume
|
||||
|
||||
**Found 2026-08-11 by finally doing the visual**, which the notes above kept
|
||||
naming as the next step. It overturns their conclusion.
|
||||
|
||||
Render `e106` from the static assembly and from the baked runtime capture and
|
||||
compare — `ship_render` does both:
|
||||
|
||||
| | placements | parts |
|
||||
|---|---|---|
|
||||
| runtime capture (ground truth) | **8** | `bdy_01…04`, `brg_01`, `eng_01`, `eng_02`, `wep_02_01` |
|
||||
| `assemble_ship(--static)` | **11** | the same 8, **plus `e303_wep_01` ×2** and a second `e106_eng_01` |
|
||||
|
||||
The render makes it obvious: the destroyer sits inside a white slab that dwarfs
|
||||
it ([capture](captures/e106-static-assembly-volume-bug.png)). That slab is
|
||||
`e303_wep_01`, and its own geometry is:
|
||||
|
||||
```
|
||||
e303_wep_01 172 verts, 110 tris bounds X[-1000, 600] Y[-1050, 1050] Z[-2400, 2400] 1600 x 2100 x 4800
|
||||
e106_wep_02_01 1002 verts, 772 tris 269 x 179 x 417 ← what a real e106 turret looks like
|
||||
e106_brg_01 202 verts, 202 tris 105 x 76 x 305
|
||||
```
|
||||
|
||||
**110 triangles, perfectly round axis-aligned bounds, and bigger than the ship it
|
||||
is mounted on.** That is not a turret. 🟡 It reads as a collision / trigger
|
||||
volume, and ✅ whatever it is, **the game does not draw it**: the runtime capture
|
||||
of e106 contains no `e303_wep_01` at all.
|
||||
|
||||
### Why this was missed
|
||||
|
||||
`assemble_ship` treats **every** `rou_*` node in the composite as a drawable
|
||||
part, and the doc comment states the cross-id mount as intended behaviour —
|
||||
`"INCLUDING repeated instances and cross-id turret mounts (rou_e303_wep_01_root
|
||||
×2 on the e106 hull)"` — with
|
||||
`ship::tests::static_assembly_matches_runtime_capture` asserting
|
||||
`count("e303_wep_01") == 2`. The absence from the capture was explained away as
|
||||
vbase dedup, but **dedup would show one instance, not zero**.
|
||||
|
||||
The test cannot catch it either: it walks the capture's parts and looks each up
|
||||
in the static output, so **extra** static placements are invisible to it. That is
|
||||
the same shape of gap as the earlier `include_external` hypothesis — a test that
|
||||
can only fail one way.
|
||||
|
||||
### Scope, stated carefully
|
||||
|
||||
Sweeping all 335 assembled ships for the signature *ship-scale span with under
|
||||
400 triangles* flags **20 ships and 58 placements** over 28 distinct resources
|
||||
(`e005_ant_*`, `f001_ant_*`, `f002_bdy_*`, `f301_barrel`, `f303_body`,
|
||||
`e303_wep_01`, …). **Only the `e106`/`e303_wep_01` case is proven** — by render,
|
||||
by capture absence, and by geometry. Some of the others may be legitimately large
|
||||
low-poly parts, and each needs the same three checks before being called a bug.
|
||||
|
||||
**Next:** decide the rule that separates drawable parts from volumes (candidates:
|
||||
the node-name suffix, a descriptor flag, or the triangle-density heuristic), then
|
||||
re-point `static_assembly_matches_runtime_capture` so it also fails on extras.
|
||||
|
||||
BIN
docs/re/captures/e106-static-assembly-volume-bug.png
Normal file
BIN
docs/re/captures/e106-static-assembly-volume-bug.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Reference in New Issue
Block a user