fix(xbg7): the index run was one element late for 575 sub-meshes
Extending the capture comparison from index COUNTS to index VALUES (`examples/capture_index_bytes.rs`, using the batch offsets the new ib logging gives) showed 76 of 93 Stage_S02 index runs identical to the GPU's and 17 differing — every difference a shift by exactly one element, on buffers whose index data sits at pad 2. `anchor_pool_mesh` returned the FIRST pad that validated, and pad 0 is tried first with the looser winding gate (0.70 vs 0.85). Read at pad 0, a pad-2 block yields [true[1], true[2], …, garbage]: every index in range, the pool covered, the positions right, the winding often just above 0.70 — so it validated, and every triangle was mis-wired. Nothing count-based could see it. The signature is decidable without the capture: a shifted run wires arbitrary vertices, so triangles come out degenerate. 282 of 283 correctly anchored Stage_S02 blocks have zero degenerate triangles, while the shifted readings carry 1–2 156. So score every validating pad by (degenerate triangles, then winding) and keep the best. `XBG7_PAD_FIRST_MATCH=1` restores the old behaviour. captured index runs identical: 76/93 -> 93/93 (2 025 elements) decoded runs with a degenerate triangle: 579 -> 16 (disc-wide) sub-meshes whose index run changed: 575 of 8 850 resources decoded / vertex anchors / consistency: unchanged (6 209 / same vb / 89) Locked in by tests/mesh_disc.rs::decoded_index_runs_have_almost_no_degenerate_triangles. Suite green with --include-ignored apart from the pre-existing known-failing cross-container consistency test (the 24-vertex bounding-box class). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
This commit is contained in:
@@ -1396,3 +1396,55 @@ vs our 246)"*. Not a disagreement: `21` was the first of two batches, and
|
||||
Mixing the index range into the de-dup key (same commit) makes every batch
|
||||
appear. **Any conclusion drawn from a pre-2026-08-13 capture's `indices=` value,
|
||||
or from `vbase − ibase`, is about one batch and not about the block.**
|
||||
|
||||
### ✅ FIXED, and it is the biggest silent defect found so far: the index run was one element late (2026-08-13)
|
||||
|
||||
Comparing captured index VALUES (not just counts) against our decode turned the
|
||||
layout check above into a byte-level oracle — `examples/capture_index_bytes.rs`
|
||||
lines each draw batch up against `GameMesh::indices` at the batch's own offset.
|
||||
Result on `Stage_S02`: **76 of 93 index runs identical, 17 differing — and every
|
||||
difference was a shift by exactly one element**, on buffers whose real index data
|
||||
sits at **pad 2**.
|
||||
|
||||
Cause: `anchor_pool_mesh` took the **first** pad that validated, and pad 0 is
|
||||
tried first *with the looser gate* (`XBG7_PAD0_CONSISTENCY` 0.70, against 0.85 for
|
||||
pad ≥ 1). For a pad-2 block, reading at pad 0 yields `[true[1], true[2], …,
|
||||
garbage]` — every index still in range, the pool still covered, the positions
|
||||
untouched, and the winding often just above 0.70. So it validated, and every
|
||||
triangle came out mis-wired.
|
||||
|
||||
**The signature is decidable offline** (`examples/index_pad_check.rs`): a shifted
|
||||
run wires arbitrary vertices, so triangles come out **degenerate** (a repeated
|
||||
index). In `Stage_S02`, 32 resources read at pad 0 with 1–2 156 degenerate
|
||||
triangles and winding 0.63–0.76, while the same blocks at pad 2 give **zero**
|
||||
degenerate triangles and winding 0.98–1.00. And degeneracy is near-perfectly
|
||||
clean as an invariant: **282 of 283** correctly anchored blocks in that container
|
||||
have zero degenerate triangles.
|
||||
|
||||
**Fix:** score every validating pad by `(degenerate triangles, then winding)` and
|
||||
keep the best, instead of returning the first. Revert knob
|
||||
`XBG7_PAD_FIRST_MATCH=1` restores the old behaviour, which is how the before/after
|
||||
below was measured.
|
||||
|
||||
| measurement | first-match (old) | scored (new) |
|
||||
|---|---|---|
|
||||
| captured index runs identical to ours (`Stage_S02`, 2 025 elements) | 76 / 93 | **93 / 93** |
|
||||
| decoded sub-meshes whose index run holds a degenerate triangle (disc-wide) | **579** | **16** |
|
||||
| sub-meshes whose index run changed | — | **575** of 8 850 |
|
||||
| resources decoded · vertex anchors · cross-container minority decodes | 6 209 · — · 89 | **unchanged** (6 209 · identical `vb` · 89) |
|
||||
|
||||
So 575 sub-meshes — 6.5 % of the disc's geometry — were being decoded with
|
||||
mis-wired triangles under a completely correct-looking decode: right resource,
|
||||
right buffer, right vertex count, right coverage. **No count-based metric could
|
||||
see it**; only the captured index values, and then the degeneracy signature they
|
||||
pointed at. Locked in by
|
||||
`tests/mesh_disc.rs::decoded_index_runs_have_almost_no_degenerate_triangles`.
|
||||
|
||||
Honest limits: of the moved runs, **92** had a pad-0 reading with no degenerate
|
||||
triangle and moved on the winding tie-break alone (83 such cases existed before
|
||||
the fix, so the tie-break newly decides 9) — weaker evidence than the degeneracy
|
||||
signature, and unverified by the capture. The **16** remaining degenerate runs are
|
||||
the grouped `.dat` break composites in `ptc_pack` (`f102`/`f104`/`e107`, whose
|
||||
marker lists are documented not to map onto the stored blocks), `e201_bdy_03_m`
|
||||
(2 containers) and `_rou_f402_dead` (9) — each already suspect on other grounds,
|
||||
and now the concrete next targets.
|
||||
|
||||
Reference in New Issue
Block a user