Add tfrag LOD selection, morph weights and parent linkage
The block header's float at +0x08 gives L and the switch distances are [D0, D1, D2] = [6L, 4L, 2L] world units -- 192/128/64 on every level measured. The distance test uses the patch's bounding sphere truncated to integers against trunc(D * 1024), not the sphere's centre, so a patch straddling a threshold takes the coarser mode. Six modes result: LOD 2, LOD 1 collapse, LOD 1 morph, LOD 0 collapse, LOD 0 morph, LOD 0. A morph weight runs 0 to 1 across its tier's band. Past the far edge an entry is not blended but replaced by its first parent -- a collapse. The tested property is that distance never moves back to a finer mode, swept across the whole range, because that is what keeps terrain from popping as the camera moves. The four V4_8 parent arrays are classified by VU address and an unrecognised address is now an error, which is a real check: all 18,102 patches still parse. Every recorded parent resolves to real vertex info and real positions, and no common entry claims a parent. That is as far as it goes, and the limit is a finding rather than a rounding error. The addresses are not unique: on level 0 patch 1, unk_indices_2_lod_01_addr == parent_indices_lod_0_addr == 291, with unk_06 and unk_0a both zero, so nothing but unpacking order separates them. Across all 27 levels 57,782 of 374,349 LOD entries resolve, and 316,548 unresolved LOD-0 entries against 28,739 resolved is far too lopsided to be genuine -- either the arrays are misassigned where addresses collide, or RAC2's positions_lod_0_count means something other than RAC1's. Only 3,274 of 18,102 patches carry enough V4_8 bytes to cover positions_lod_01_count + positions_lod_0_count + extras. The vertex-info tiers themselves are confirmed: the addresses at +9 and +11 match the observed V4_16 unpacks and the three tier counts sum to vert_count on every patch, so the header layout is right and it is specifically the parent arrays that are open. Ruled out so far: address-only classification (collides) and one-parent-per-position (fails on 15,000 patches). Not yet tried: ordering by VU address instead of by declaration, and using unk_06/unk_0a to suppress an empty tier before falling through. Written up in plan/level-assets.md.
This commit is contained in:
@@ -435,10 +435,54 @@ CLUT remap still yields a well-formed image of the wrong thing. Two layers:
|
||||
That tool is worth keeping: it is the cheapest way to tell a decode that is
|
||||
merely well-formed from one that is right.
|
||||
|
||||
### LOD selection
|
||||
|
||||
Mostly settled. The block header's float at +0x08 gives `L`, and the switch
|
||||
distances are `[D0, D1, D2] = [6L, 4L, 2L]` world units; on every level `L` is
|
||||
32.0, so `[192, 128, 64]`. The distance test compares the patch's bounding
|
||||
sphere, truncated to integers, against `trunc(D * 1024)`. Six modes result —
|
||||
LOD 2, LOD 1 collapse, LOD 1 morph, LOD 0 collapse, LOD 0 morph, LOD 0 — and
|
||||
the *sphere* is what is tested, not the centre, so a patch straddling a
|
||||
threshold takes the coarser mode. That is what stops geometry popping as the
|
||||
camera moves, and it is worth testing for directly: sweeping the depth must
|
||||
never move back to a finer mode.
|
||||
|
||||
A morph weight runs 0 to 1 across its tier's band: LOD-01 between D1 and D0,
|
||||
LOD-0 between D2 and D1. Past the far edge of the band an entry is not morphed
|
||||
but *replaced* by its first parent — a collapse, not a blend.
|
||||
|
||||
### What is still unresolved: the parent-index arrays
|
||||
|
||||
The four V4_8 parent arrays (`parent_indices_lod01`, `unk_indices_2_lod01`,
|
||||
`parent_indices_lod0`, `unk_indices_2_lod0`) are classified by VU address, but
|
||||
**the addresses are not unique**. On level 0 patch 1,
|
||||
`unk_indices_2_lod_01_addr == parent_indices_lod_0_addr == 291`, and the VU
|
||||
header's `unk_06` and `unk_0a` are both zero — there are no extra entries at
|
||||
all, so nothing but the order the unpacks appear in distinguishes the two.
|
||||
|
||||
The consequence is measurable. Across all 27 levels, of 886,841 vertex-info
|
||||
entries, 516,492 are common and 57,782 LOD entries resolve to a parent. But
|
||||
316,548 LOD-0 entries have no recorded parent against only 28,739 that do, and
|
||||
only 3,274 of 18,102 patches carry enough V4_8 bytes to cover
|
||||
`positions_lod_01_count + positions_lod_0_count + extras`. The asymmetry is too
|
||||
large to be genuine, so one of two things is true: the parent arrays are being
|
||||
misassigned where addresses collide, or RAC2's VU header means something
|
||||
different by `positions_lod_0_count` than RAC1's does.
|
||||
|
||||
The vertex-info tiers themselves *are* confirmed — the addresses at +9 and +11
|
||||
do match the observed V4_16 unpacks, and the three tier counts sum to
|
||||
`vert_count` on every patch. So the header layout is right; it is specifically
|
||||
the parent arrays that are unresolved.
|
||||
|
||||
Ruled out for now: address-only classification (collides), and assuming the
|
||||
arrays are one entry per position (fails on 15,000 patches). Not yet tried:
|
||||
ordering by VU address rather than by declaration, and using `unk_06`/`unk_0a`
|
||||
to suppress an empty tier before falling through to the next candidate.
|
||||
|
||||
### Still open
|
||||
|
||||
The LOD switch and morph rules in `TfragProc`, and the GS register values the
|
||||
level load builds from the texture entry (TBP0/TBP1 and so on) for a renderer
|
||||
that emulates the GS rather than resolving addresses itself. A tfrag can now be
|
||||
turned into positioned, UV-mapped, textured triangles; what is missing is
|
||||
choosing which LOD to draw.
|
||||
The parent arrays above, and the GS register values the level load builds from
|
||||
the texture entry (TBP0/TBP1 and so on) for a renderer that emulates the GS
|
||||
rather than resolving addresses itself. A tfrag can be turned into positioned,
|
||||
UV-mapped, textured triangles and told which LOD to draw; morphing cannot be
|
||||
completed until the parent arrays resolve.
|
||||
|
||||
Reference in New Issue
Block a user