Parse tfrag topology: vertex info, strips, indices and triangles
Turns tfrag positions into drawable geometry. Every unpack in a patch's VIF lists is now classified by format *and* destination VU address, using the VU header at the head of the common list. That table is what tells the strips from the indices from the parent arrays: they are all V4_8 and only the address distinguishes them. Strip indices address the vertex-info array, not the position array. Several vertex-info entries can share a position, so an index resolves through vertex_info[i].vertex / 2 to reach a position. Vertex-info also carries UVs and the LOD parent link. A strip run of n vertices is a GS triangle strip of n - 2 triangles with alternating winding. The vertex count is biased by -128 when negative, and the first record of a list is always biased even when positive -- real patches open with -116 or -124, never a small positive number. tri_count lives in the patch header and cannot be derived from the lists without walking them, so reproducing it is a strong check: all 27 levels, 18,102 patches, 809,879 triangles, every patch matching its header, every triangle index inside its LOD's vertex-info range. Four wrong turns got there, each of which failed quietly rather than loudly: reading the element size from the immediate instead of the command, 2-byte payload padding instead of 4, ignoring the inline payload of STROW/STCOL/STMASK/MPG/DIRECT, and banking strips by arrival order instead of by LOD. Every one produced parseable, plausible geometry. Only the header's own triangle count caught them. Still open: ad-gif texture bindings, per-vertex RGBA and light records, and the LOD switch and morph rules.
This commit is contained in:
@@ -313,7 +313,52 @@ mismatch means the walk desynchronised, so `tfrag_positions` returns `None`
|
||||
rather than partial geometry. Across all 27 levels: 18,102 patches,
|
||||
821,439 vertices, zero mismatches.
|
||||
|
||||
### Topology: strips, indices and triangles
|
||||
|
||||
Also settled. `parse_tfrag` walks each list and classifies every unpack by
|
||||
format *and* destination VU address, using the VU header at the head of the
|
||||
common list (5 V4_16 vectors, unsigned, to address 0). That table is what
|
||||
separates the strips from the indices from the parent arrays — they are all
|
||||
V4_8, and only the address tells them apart.
|
||||
|
||||
| `(vn, vl)` | Meaning |
|
||||
|---|---|
|
||||
| (3, 1), unsigned, addr 0 | the VU header itself |
|
||||
| (3, 0) | ad-gifs, 5 quadwords each |
|
||||
| (2, 1) | positions, V3_16 |
|
||||
| (3, 1) | vertex info: UV, LOD parent, own position |
|
||||
| (3, 2) | strips (signed) and indices (unsigned) |
|
||||
|
||||
**Strip indices address the vertex-info array, not the position array.** Several
|
||||
vertex-info entries can share a position, so a strip index resolves through
|
||||
`vertex_info[i].vertex / 2` to reach a position.
|
||||
|
||||
A strip record is four bytes: a vertex count, a packet flag and an ad-gif
|
||||
offset. A negative count is biased by −128, and *the first record of a list is
|
||||
always biased* even when its count is positive — a real patch opens with −116 or
|
||||
−124, never a small positive number. A zero count ends the list. A run of `n`
|
||||
vertices is a GS triangle strip of `n − 2` triangles with alternating winding.
|
||||
|
||||
**Bank the LODs by index, not by arrival.** The lists are walked in transfer
|
||||
order but the strip and index arrays have to be stored by *which LOD they
|
||||
belong to*. Appending them as they are found fills the arrays LOD-2-first,
|
||||
which looks fine until the triangle counts come out low.
|
||||
|
||||
### Verification
|
||||
|
||||
`tri_count` is stored in the patch header and cannot be derived from the lists
|
||||
without walking them, so reproducing it is a strong check. All 27 levels:
|
||||
**18,102 patches, 809,879 triangles, every patch matching its header**, with
|
||||
every triangle index inside that LOD's vertex-info range and every ad-gif index
|
||||
inside the ad-gif array.
|
||||
|
||||
Getting there took four wrong turns, each of which failed quietly: reading the
|
||||
element size from the immediate, 2-byte payload padding, ignoring non-unpack
|
||||
payloads, and banking strips by arrival order. Every one produced parseable,
|
||||
plausible geometry; only the header's own triangle count caught them.
|
||||
|
||||
### Still open
|
||||
|
||||
Strips and triangle indices, the RGBA vertex-info arrays, texture binding, and
|
||||
the LOD/morph/cull rules in `TfragProc`. Positions alone are not drawable.
|
||||
The ad-gif texture bindings (which texture each primitive samples), the RGBA
|
||||
per-vertex colours and light records, and the LOD switch and morph rules in
|
||||
`TfragProc`. Positions and topology are drawable but not yet textured.
|
||||
|
||||
Reference in New Issue
Block a user