# Handoff — XBG7 stage-container meshes, unified layout, mesh renderer (2026-07-12) ## Summary This session extended the XBG7 mesh work from single weapons/props to the big **stage containers**, fixed a long-standing weapon decode artifact, added a headless mesh renderer for self-verification, and reworked the viewer's stage presentation. Companion Xenia-Canary GPU instrumentation (the `log_draws` draw-logger used as the ground-truth oracle) is on the `instrument-current` branch of the Canary fork. ## What landed (Reborn — `feature/ipfb-idxd-parser`) ### 1. Stage containers decode — `Xbg7Model::stage_models` `hidden/resource3d/Stage_S*.xpr` are **collections of up to ~450 enemy/prop sub-models**, not single meshes. Each resource is a block `[12-byte header][index buffer][vertex buffer]`; the index count is the descriptor's `(index_bytes, index_count)` marker and the vertex count is the `u32` 32 bytes before it. The blocks are **scattered among the container's texture data with no stored offset**, so each is located by **content**: one `O(file)` pass per stride finds vertex-buffer starts (unit NORMAL at vertex +12 whose previous stride slot isn't — a block boundary), then each resource is pinned to the candidate where its indices validate and produce non-degenerate, well-connected triangles. Fast (≤ ~4 s on a 70 MB stage), unambiguous. - **Coverage: ~4993 sub-models across the 22 stages** (content-anchored). - A **connectivity gate** (mean triangle edge ≤ 0.28× bbox diagonal) rejects mis-anchored blocks that would render as spiky messes. ### 2. Weapon layout fix (unified with stages) Weapons use the **same** `[12-byte header][index][vertex]` block layout. The old decoder read the index buffer 12 bytes too early → 2 leading degenerate triangles (the recurring "stray white triangle" artifact) + a lost tail of 6 real indices. Now the header is skipped; vertex offsets are unchanged, so coverage stays 36/166 and the triangle lists are correct. Verified on wep_00/03/04 via the renderer. ### 3. Headless mesh renderer — `sylpheed-cli mesh {info,render}` A software rasterizer (orthographic, z-buffered, two-sided shading) that writes a PNG. Lets anyone (including the assistant) verify recovered geometry without the GUI. `--only ` filters sub-models; `--yaw/--pitch/--size/--row` control the view. Stage containers render as a normalised thumbnail grid. ### 4. Viewer UX - Stage files show a **thumbnail grid**: each sub-model recentred + uniformly scaled to a fixed cell (a 1-unit prop and a 3000-unit structure are equally visible), skybox-plane / stray-anchor models (>20000 u) culled. - Stage sub-models are **textured** by matching name → `_col` albedo. - **Camera fixed**: zoom was hard-capped at 50 u (camera trapped inside big stages); now zoom limits + clip planes scale to the framed model. - Dispatch decodes both single-model and stage paths and keeps whichever yields more geometry (fixes stages previously showing the dummy-root "black cube"). ## State / how to verify ```bash # formats + disc tests (needs the extracted disc) SYLPHEED_RES3D=/path/to/hidden/resource3d \ cargo test -p sylpheed-formats --test mesh_disc -- --ignored --nocapture # render any model / stage to a PNG cargo run --release -p sylpheed-cli -- mesh render \ .../resource3d/Stage_S10.xpr /tmp/s10.png --size 1000 cargo run --release -p sylpheed-cli -- mesh render \ .../resource3d/rou_f001_wep_00.xpr /tmp/wep.png # viewer cargo run -p sylpheed-viewer # File → Open Directory → the extracted disc ``` ## Known limitations / next steps - **Multi-submesh main bodies with separate vertex pools** still mis-decode (e.g. `Stage_S10` `e005`: a coherent fighter overlaid with wrong spike triangles). `e003` works only because its two submeshes *share* one pool. Same unsolved class as the **DeltaSaber hero body** (`f004`, still declined). Fix needs per-submesh vertex-base RE. The LODs of these bodies (e.g. `e005_l`) decode cleanly. - **The per-resource data offset is not stored** — blocks are found by content. Reversing the container's allocation order would give exact offsets + 100% coverage but is a larger effort. - **Texture colour correctness** remains the known "unverified" dynamic-RE item (channel order / sRGB) for both 2D textures and model albedos. - The viewer's stage decode runs on the main thread (~10 s on a 70 MB stage in a debug build); move off-thread if the load hitch matters. ## Canary oracle (`instrument-current` on the Xenia-Canary fork) `command_processor.cc::LogDrawForRE` (cvar `log_draws`) dumps each distinct draw's primitive type, index buffer, vertex declaration, and sample vertex positions to `xenia_re_draws.log`. Used to confirm the XBG7 layout (triangle list, f32×3 position + f16×4 normal + f16×2 uv). Build with the memory-safe flags (`CC=clang-19 CXX=clang++-19 CMAKE_BUILD_PARALLEL_LEVEL=3 … -j 3`); run one emulator process at a time.