feat(viewer): stage thumbnail grid, per-model albedo, camera zoom fix
Route stage containers to stage_models (decode both single + stage paths, keep whichever yields more geometry) — fixes stages showing the dummy-root "black cube". Present a stage's sub-models as a thumbnail grid: each recentred and uniformly scaled to a fixed cell so a 1-unit prop and a 3000-unit structure are equally visible; skybox-plane / stray-anchor models (>20000u) are culled. Each sub-model is textured by matching its name to the container's `_col` albedo. Fix the orbit camera: zoom radius was hard-capped at 50u with fixed clip planes, trapping the camera inside multi-thousand-unit stages. Zoom limits and near/far planes now scale to the framed model. Adds docs/HANDOFF-stage-mesh-2026-07-12.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
94
docs/HANDOFF-stage-mesh-2026-07-12.md
Normal file
94
docs/HANDOFF-stage-mesh-2026-07-12.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# 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 <substr>` 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.
|
||||
Reference in New Issue
Block a user