From 93591a79defb7cdee86a862aaf1e5b073855f088 Mon Sep 17 00:00:00 2001 From: ReaperOfVeriod Date: Tue, 13 Oct 2026 05:04:25 +0200 Subject: [PATCH] Verify the tfrag record framing against RAM: two pointer fields, morphed vertices The tfrags block header -- table_offset 0x40, tfrag_count 833, lod_distance 32.0 -- hits once in the level-0 savestate at RAM 0x848000, so disc offset 0 maps to RAM 0x848000 and the block can be compared position for position. Result 1: exactly two fields per record are rewritten. Comparing all 833 64-byte table records field by field: records compared 833 u32 fields differing 1,665 = 833 x 2, and no others their offsets +0x10 and +0x38, on every record relocation delta 8,683,584 = 0x848000, on 833 of them So +0x10 and +0x38 are block-relative pointers and the loader adds the block's RAM base to each. Every other byte of all 833 records is verbatim. Six fields differ by 0xF80000 and 0x1F00000 instead -- three records pointing into a different buffer, recorded rather than averaged. This names two fields the disc-side work had left as `data` and an unnamed word. Result 2: behind the pointers, about 8.6% of bytes are rewritten. Following record 0's +0x10 to disc 0xd040 / RAM 0x855040 and comparing 16 KiB: 14,977 bytes identical, 1,407 differ = 8.6%. The differences spread across many field positions rather than concentrating, which is what morphed POSITIONS look like -- topology, UVs, counts and command lists stay verbatim while a fraction of the position bytes move. 8.6% is about half of a 16-byte vertex's six position bytes, consistent with roughly half the vertices in this patch mid-morph at the saved camera distance. Not established: that the morphed bytes match resolve_vertex's formula. That needs mapping RAM vertices to disc vertex indices and solving for the weight, which is the next step -- but the framing is now known well enough to attempt it, and the two pointer fields are confirmed rather than inferred. This also answers the "is morphing real?" question that made the earlier tfrags probe fail to match RAM: the tfrags block is not verbatim in RAM, because both its table pointers are relocated and its vertex positions are morphed. --- docs/plan/pcsx2-verification.md | 39 +++++++++++++++++++++++++++++++++ docs/roadmap.md | 19 +++++++++++----- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/docs/plan/pcsx2-verification.md b/docs/plan/pcsx2-verification.md index a2abd57..ad1238c 100644 --- a/docs/plan/pcsx2-verification.md +++ b/docs/plan/pcsx2-verification.md @@ -823,3 +823,42 @@ Two lessons, both already written down and both re-learned: - the control for this one is trivial and was not run: read the predicted RAM address and compare it to the disc, which takes one command and would have shown the verbatim copy immediately. + +## tfrag: two relocated pointer fields per table record, and morphed vertices behind them + +Checking the tfrag record framing against RAM. The tfrags block header is +`table_offset 0x40, tfrag_count 833, lod_distance 32.0`, and that 8-byte prefix +hits once in the level-0 savestate: **RAM `0x848000`**. So the block is at a +known place, and disc offset 0 maps to RAM `0x848000`. + +**Result 1 -- exactly two fields per record are rewritten.** Comparing all 833 +64-byte table records, field by field: + + records compared 833 + u32 fields differing 1,665 = 833 x 2, and no others + their offsets +0x10 and +0x38, on every record + relocation delta 8,683,584 = 0x848000, on 833 of them + +So `+0x10` and `+0x38` are **block-relative pointers**, and the loader adds the +block's RAM base to each. Every other byte of all 833 records is verbatim. The +remaining six fields differ by 0xF80000 and 0x1F00000 rather than 0x848000 -- +three records pointing into a different buffer, recorded rather than averaged. + +This names two fields the disc-side work had left as `data` and an unnamed word. + +**Result 2 -- behind the pointers, about 8.6% of bytes are rewritten.** Following +record 0's `+0x10` to disc `0xd040` / RAM `0x855040` and comparing 16 KiB: + + identical bytes 14,977 of 16,384 + differing 1,407 = 8.6% + +The differences are spread across many field positions rather than concentrated, +which is what morphed **positions** look like: the topology, UVs, counts and +command lists stay verbatim while a fraction of the position bytes move. 8.6% is +about half of a 16-byte vertex's 6 position bytes, i.e. consistent with roughly +half the vertices in this patch being mid-morph at the saved camera distance. + +**Not established:** that the morphed bytes match `resolve_vertex`'s formula. That +needs mapping RAM vertices to disc vertex indices and solving for the weight, which +is the next step -- but the framing is now known well enough to attempt it, and +the two pointer fields are confirmed rather than inferred. diff --git a/docs/roadmap.md b/docs/roadmap.md index f530b9e..3175d75 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -245,11 +245,20 @@ Phase 1 hands over the extents; this phase works out what each one contains. vertices'** midpoint, not toward separate morph targets — there are no morph targets in this format, which is what the old "morph targets unrecovered" note wrongly implied. -- [ ] What tfrag is **not** yet verified against is the original: the LOD switch - distances, morph weight and collapse edge are derived from the data (tiers - classified from the four V4_8 parent arrays, weight running zero to one - across each band), but have not been compared against PCSX2 the way the - lighting pass was. That needs a frame at a known view distance. +- [x] The tfrag **table record has exactly two relocated pointer fields**, at + `+0x10` and `+0x38`, verified against PCSX2 across all **833** records: the + loader adds the block's RAM base (`0x848000`) to each, every other byte of + every record is verbatim, and 1,665 fields differ where 1,666 were possible. + This names two fields previously left as `data` and an unnamed word. +- [~] Behind those pointers, **8.6% of bytes are rewritten** (14,977 of 16,384 + identical), spread across many field positions rather than concentrated -- + consistent with morphed positions with topology and UVs left verbatim. +- [ ] What tfrag is **not** yet verified is that the morphed bytes match + `resolve_vertex`'s formula. The LOD switch distances, morph weight and + collapse edge are derived from the data (tiers classified from the four V4_8 + parent arrays, weight running zero to one across each band); the framing is + now known well enough to map RAM vertices to disc indices and solve for the + weight, which is the next step. - [x] `tie` — **fully chained.** `resolve_tie_class` walks both indirections: the class table is in `core_index.bin`, and the entry's `offset_in_asset_wad` addresses `core_data.bin` (not `core_data.wad`). All **1,864** tie classes