feat(xbg7): read the per-sub-mesh vertex declarations (XBG7_SUBMESH_DECLS, off by default)
desc_dump shows each index marker is followed by its OWN element triples: n201_01 declares strides 24, 24, 24 and 28 (the last sub-mesh has a fourth element), which matches the runtime capture's stride=28 on that draw and the four distinct vertex shaders. parse_vertex_decl read the first declaration for the whole pool. all_vertex_decls reads one per marker; anchor_grouped_meshes uses each sub-mesh's own stride for the pool walk, the pivot validation and the read. debug_grouped_report follows the same setting so the diagnostic cannot blame the wrong gate — at n201_01's capture-proven pool start it now reports "pad 0: ACCEPTED" instead of a NaN position. With XBG7_SUBMESH_DECLS=1: resources that never decode 85 -> 47, resources decoding in no container 63 -> 30, capture oracles unchanged (93/93 index runs, 42/42 index counts), consistency unchanged at 96. Off by default because selection has not caught up: the three n201_0x copies then settle on one pool (twin_pairs_do_not_share_a_buffer fails), production still picks a start 4 bytes before sub-mesh #1 rather than the proven one even though the proven start validates and is unclaimed, and four newly decoded ptc_pack .dat composites carry degenerate triangles. Format truth is settled; choosing among candidates is the remaining work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
This commit is contained in:
@@ -1602,3 +1602,55 @@ S09/S25 `n205`, S15 `n207_208`). 58 of the 63 live in exactly one container.
|
||||
Flying stage 16 did **not** get the `e901` wings drawn (the boss appears later in
|
||||
the mission), which is the next lesson: choosing the mission puts a container in
|
||||
memory, but the unit still has to be **on screen** for a draw to exist.
|
||||
|
||||
### 🔎 The descriptor carries a declaration PER SUB-MESH — worth 38 misses, held behind a knob (2026-08-13)
|
||||
|
||||
Following the `n201` mixed-stride finding: the descriptor was dumped around every
|
||||
index marker (`examples/desc_dump.rs`), and the layout is unambiguous — **each
|
||||
marker is followed by its own element triples**, terminated by
|
||||
`off=0x00FF0000 / code=0xFFFFFFFF`:
|
||||
|
||||
| `n201_01` marker | verts / indices | elements (offset, code) | stride |
|
||||
|---|---|---|---|
|
||||
| `@0x428` | 777 / 4464 | 0 `2A23B9`, 12 `1A2360`, 20 `182886` | 24 |
|
||||
| `@0x900` | 869 / 4464 | 0 `2A23B9`, 12 `1A2360`, 20 `2C235F` | 24 |
|
||||
| `@0xDD8` | 192 / 576 | 0 `2A23B9`, 12 `1A2360`, 20 `182886` | 24 |
|
||||
| `@0x12B0` | 869 / 4464 | 0 `2A23B9`, 12 `1A2360`, 20 `182886`, **24 `2C235F`** | **28** |
|
||||
|
||||
That is exactly the capture's `stride=28` on the fourth draw, and it explains the
|
||||
four distinct vertex-shader hashes. `parse_vertex_decl` read the **first**
|
||||
declaration and applied it to the whole pool; `all_vertex_decls` now reads one per
|
||||
marker, and `anchor_grouped_meshes` uses each sub-mesh's own stride for the vertex
|
||||
pool walk, the pivot validation and the read.
|
||||
|
||||
**Measured with `XBG7_SUBMESH_DECLS=1`:**
|
||||
|
||||
| | default | per-sub-mesh declarations |
|
||||
|---|---|---|
|
||||
| resources that never decode (disc-wide) | 85 | **47** |
|
||||
| resources that decode in **no** container | 63 | **30** |
|
||||
| capture oracles (`Stage_S02`) | 93/93 runs, 42/42 index counts | **unchanged** |
|
||||
| cross-container minority decodes | 96 | 96 |
|
||||
| decoded index runs with a degenerate triangle | 1 | 5 |
|
||||
|
||||
And `debug_grouped_report` at `n201_01`'s **capture-proven** pool start now reads
|
||||
`pad 0: ACCEPTED`, where it used to read `position component NaN` — the block the
|
||||
engine draws from is finally acceptable to the decoder.
|
||||
|
||||
**Why it is off by default.** Selection has not caught up:
|
||||
|
||||
* the three `n201_0x` copies all settle on ONE pool, so
|
||||
`tests/mesh_consistency_disc.rs::twin_pairs_do_not_share_a_buffer` fails — a twin
|
||||
collapse is the one thing this project has decided never to ship;
|
||||
* production still picks a pool start 4 bytes before sub-mesh **#1** (`0x32BEFF0`)
|
||||
instead of the proven `0x32BA71C`, even though that start **is** in the candidate
|
||||
list, validates at pad 0, and is claimed by nobody in the final output — so the
|
||||
remaining defect is in *choosing* among candidates, not in the format reading;
|
||||
* four newly decoded `ptc_pack` `.dat` composites carry degenerate triangles
|
||||
(`f202_break`, `e108_break`, `f106_break`, `eff_s901_e04`) — new coverage of
|
||||
imperfect quality rather than a regression, but not clean either.
|
||||
|
||||
So the format question is **settled** (and capture-confirmed), the coverage win is
|
||||
real and measured, and what stands between the two is the same
|
||||
selection/distinct-assignment machinery that the pad work already improved once.
|
||||
That is the next step, with `n201`'s proven offsets as the acceptance test.
|
||||
|
||||
Reference in New Issue
Block a user