# RE backlog Open items that are *not* being worked right now. Each entry says what is wrong or unknown, what evidence exists, and what the first step would be. Move an item into `INDEX.md` (with a `structures/…md` or a parser + test) once it is actually settled. --- ## Capital ships assemble wrong in the viewer **Reported:** 2026-07-30, by the user. **Status:** πŸ”Ž **diagnosed 2026-08-10 β€” the format layer is exonerated.** Runtime captures of three classes (`f105`, `e105`, `e106`) at controlled range reproduce `assemble_ship` to ≀0.43 units in translation and to 0.000 in rotation for every part that does not move; see [`ship-placement-capture-generalisation.md`](ship-placement-capture-generalisation.md) Β§4. So look at **the viewer**: first that it passes `include_external = true` (`iso_loader.rs:4012` β€” with `false` an e106 loses its bridge and both nacelles, 5 parts instead of 11), then its own transform stack. One real format-side bug was found on the way and is **fixed**: index-less parts (`e105_brg`) never matched their `GN_Bridge_01` hardpoint, so 34 (stage, ship) entries β€” `e102`, `e104`, `e105` across Stages 02–29 β€” assembled without a bridge. The other apparent exception (`e105_eng_01` rotation) was an aggregation artefact and is 0.000. The original report and its reasoning follow. The reborn viewer builds capital ships from the split XBG7 parts via `sylpheed-formats::ship::assemble_ship`, and they come out **wrong** β€” parts in the wrong place / wrong orientation. **Why this is a real finding and not a known limitation:** the RE write-up [`ship-placement-runtime-capture.md`](ship-placement-runtime-capture.md) declares static assembly βœ… **exact** as of 2026-07-26 β€” 9-channel joint tables `[TX TY TZ RY RX RZ SX SY SZ]`, Euler `RyΒ·RxΒ·Rz`, with `ship::tests::static_assembly_matches_runtime_capture` asserting static == runtime capture (T < 1.0, R < 0.02). So either the viewer is not using that path, or the claim generalises worse than the test suggests. **The likely gap:** that test is **one ship** β€” the `e106` destroyer, 8 parts plus two nacelles, two turrets and the hull mirror. Nothing pins the other classes. Rules that were derived from `e106` and could easily be `e106`-specific: - the engine cluster rig mounted at `GN_Engine_01` (two mirrored nacelles + centre); - "X-reflect the shared-geometry twin whose lateral offset opposes the geometry's dominant side" β€” a heuristic, not a decoded flag; - cross-id turret instancing (Γ—2). **First step (the oracle already exists):** re-run the runtime capture on a *different* capital ship and diff static vs captured, exactly as `e106` was done β€” F10 in the `capture-ship-placement` build of `xenia-canary-native` dumps the ship shader's `c0..c2` WorldViewProjection rows per part; `WV_ref⁻¹ Β· WV_p` is the ship-space rigid transform, which is ground truth. Pick a class whose rig differs from `e106` (different engine count, a ship with no `sld`, a carrier). Then extend `static_assembly_matches_runtime_capture` into a per-ship table so a regression in one class cannot hide behind `e106` passing. **Also worth ruling out first, cheaply:** that the viewer's own transform stack (scale, handedness, node-instance recursion) is not re-breaking a correct assembly β€” compare the viewer's placement against `assemble_ship`'s output directly before blaming the format layer. --- ## Viewer: `include_external` is already on β€” that hypothesis is dead **Checked 2026-08-11.** The item above names "first that it passes `include_external = true` (`iso_loader.rs:4012`)" as the cheap first step. It does: `ShipBrowser::show_external` defaults to `true` (`iso_loader.rs:643`), the checkbox reads it (`ui.rs:1593`) and it is threaded through `RequestShipRender` β†’ `build_ship_model` β†’ `assemble_ship` unchanged (`ui.rs:1689`, `iso_loader.rs:4012`). So a ship rendered by the viewer is the full external assembly, not the bare hull. The viewer also does not have a transform stack of its own to blame: it bakes `ScenePart::apply` straight into the vertices and rotates normals by the same `p.m` (`iso_loader.rs:4030-4062`), so its placement is `assemble_ship`'s output by construction. What remains unexcluded, in order of cheapness: the mirror handling (`det < 0` reverses triangle winding only β€” a reflected part keeps its reflected geometry), `Xbg7Model::models_named` resolving the wrong sub-model when a resource name repeats, and the exhaust cones. **Next step is a visual**: the diagnosis has run out of things it can settle by reading, so the viewer needs to be run against a known-good class (`e106`) and its render compared with `ship_render`'s. --- ## Viewer: the duplicate-resource-name hypothesis is dead too **Checked 2026-08-11.** The diagnosis above left three candidates for why capital ships assemble wrong in the viewer: mirror handling, `Xbg7Model::models_named` resolving the wrong sub-model when a resource name repeats, and the exhaust cones. The second is now **refuted**, and comprehensively. `build_ship_model` resolves each placement with `base.iter().find(|m| m.name == p.resource)` (`iso_loader.rs:4041`) β€” first match wins β€” so a repeated resource name inside a container would silently draw the wrong geometry. It cannot happen: decoding **every** XBG7 resource in **all 22 stage containers** gives **4 603 resources and zero repeated names**. ``` Stage_S01 62/62 Stage_S07 323/323 Stage_S13 290/290 Stage_S25 351/351 Stage_S02 304/304 Stage_S08 388/388 Stage_S14 22/22 Stage_S26 318/318 Stage_S03 214/214 Stage_S09 316/316 Stage_S15 386/386 Stage_S27 321/321 Stage_S04 179/179 Stage_S10 7/7 Stage_S16 65/65 Stage_S28 118/118 Stage_S05 92/92 Stage_S11 157/157 Stage_S24 162/162 Stage_S29 386/386 Stage_S06 266/266 Stage_S12 376/376 ``` Per-ship it is tighter still: `e106` wants 9 distinct names and decodes exactly 9 models for 11 placements; `e105` 9 for 9; `f105` 5 for 6. Every placement resolves to the one model it names. **So two of the three candidates are gone** (this one and `include_external`), leaving **mirror handling** and **the exhaust cones** β€” and the still-untried visual comparison, which remains the right next step. --- ## Viewer: mirror handling and the exhaust cones are cleared too β€” the static avenue is exhausted **Checked 2026-08-11.** Both remaining candidates were tested across every ship on the disc, and neither shows the reported signature. **Mirror handling.** The concern was that `ScenePart::apply` bakes `RΒ·(SΒ·v)+T` while the viewer takes its winding-flip decision from `det(m)` alone and rotates normals by `m` alone β€” both ignoring `s`. A mirror encoded as a *negative scale* would then reflect geometry without flipping winding, drawing the part inside-out. It never happens: across **1 485 assembled parts** in all 22 containers there are **22 mirrored parts, every one with `det(m) < 0`**, and **zero** parts with a negative scale or a non-uniform one. `apply_twin_mirrors` writes the reflection into `m` (negating its X column), so the viewer's flip always fires, and ignoring `s` for normals is harmless because `s` is always uniform. **Exhaust cones.** These are the one piece of geometry the viewer *invents* β€” a cone at each `GN_Jet`/`GN_SJet` frame, because the real engine geometry is recessed and the game draws FX there instead. If they landed wrongly they would read exactly as "a part in the wrong place". Across **335 assembled ships, 192 of which have exhaust frames, not one cone sits outside its hull's bounding box** (tolerance 10 % of the axis span). **Caveat, stated rather than glossed:** "inside the hull box" does not prove a cone is *right* β€” orientation and size are untested, and a cone could be wrong while still inside. What it does rule out is the reported symptom for that part. So every mechanism this diagnosis proposed is now eliminated: `include_external`, duplicate resource names, mirror handling, and cones-in-the-wrong-place. The format and assembly layers pass every static test available, and **the visual 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 β€” but the cause is a MIS-DECODE, not a stray 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.** ### CORRECTION (same day, one iteration later): it is not a volume β€” it is a bad decode The first reading of this was that `e303_wep_01` is a collision/trigger volume the assembler wrongly draws. **That is wrong, and the evidence that settles it is decoding the same resource from every container that holds it:** ``` Stage_S01 172 verts 110 tris X[-24.5, 24.5] Y[0.0, 23.4] Z[-20.8, 20.8] ← 49 Γ— 23 Γ— 42, a turret Stage_S02 172 verts 110 tris X[-1000, 600] Y[Β±1050] Z[Β±2400] ← 1600 Γ— 2100 Γ— 4800 Stage_S03… 172 verts 110 tris 49 Γ— 23 Γ— 42 (correct) Stage_S08 … 1600 Γ— 2100 Γ— 4800 Stage_S26 … 1600 Γ— 2100 Γ— 4800 ``` Same resource, same vertex and triangle count, **decoding correctly in eleven containers and wrongly in exactly three** (`Stage_S02`, `S08`, `S26`). So: - the **placement is legitimate** β€” `e303_wep_01` is a small shared turret, cross-mounted on `e101` and `e106`, and at its true size it is unremarkable; - the original author's explanation of the capture's silence (**vbase dedup**) stands, and my "dedup would show one, not zero" objection does not survive: with the correct decode the turret is small, ordinary geometry; - **the defect is in the mesh decoder**, which resolved this resource's vertex data differently in three containers. The render and the symptom are real; the cause named in the first version of this entry was not. ### The part that matters more than this one resource **The decoder can produce wrong geometry without declining.** The [XBG7 audit](structures/xbg7-mesh.md) counted 814 resources it *refuses* β€” a visible, honest failure. This is the other kind: `e303_wep_01` decodes "fine" in `Stage_S02` and is silently 100Γ— too large. Screening for the signature (bounds that are exact multiples of 50 with a span over 1000) flags 22–32 models in each of `S02`, `S03`, `S08`, `S26`, `S27` β€” **but that screen also catches legitimate `e_rou_*` composite proxies**, so it is a candidate list, not a count of bugs. **Next:** diff the anchor scan's chosen `vb0` for `e303_wep_01` between `Stage_S01` (correct) and `Stage_S02` (wrong) β€” same resource, two outcomes, so the divergence is directly observable β€” then use whatever distinguishes them to add a post-decode sanity check, so a silent 100Γ— mis-decode becomes a decline. ### 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. **Still true, and independent of the correction above:** `static_assembly_matches_runtime_capture` walks the capture's parts and looks each up in the static output, so **extra static placements can never fail it**. That is worth fixing regardless β€” it is the same one-way-test shape as the earlier `include_external` hypothesis. Also unchanged: only **two** cross-id placements exist fleet-wide (`e303_wep_01` on `e101` Γ—24 and `e106` Γ—36, across 335 assembled ships), so cross-id mounting is a narrow, real feature rather than a systemic guess.