From 180fc27b04b3c529e1f7c5553437ef3039fbc176 Mon Sep 17 00:00:00 2001 From: ReaperOfVeriod Date: Tue, 13 Oct 2026 04:30:45 +0200 Subject: [PATCH] Locate the tie texcoord field at i16[4]/i16[5], and rule out the (6,7) artifact Scoring each adjacent i16 slot pair of the 16-byte tie vertex for 12-bit fixedness over 36,501 level-0 vertices locates the fields: (i16[0], i16[1]) 1.1% not a texcoord (i16[2], i16[3]) 4.8% not a texcoord (i16[4], i16[5]) 73.6% <- the texcoords (i16[6], i16[7]) 76.6% <- the near-constant 4096/0 pair (6,7) scores HIGHER and is not a texcoord. It is the near-constant 4096, 0 pair: 4096 is 1.0 in 12-bit fixed and 0 is zero, so both are trivially multiples of 4096. That is the constant artifact again -- a fixed pair would score 100% anywhere. The field is (4,5), with (0,1) and (2,3) ruled out at 1.1% and 4.8%. The first four slots span the full i16 range with 10,803 to 13,421 distinct values each, so they are neither texcoords nor small fields. The vertex base is confirmed to be vert_ofs alone. Adding the shader or control region makes it worse -- vert_ofs only 73.6% bfc + vert_ofs 69.8% bfc + control_count*control_size + vert_ofs 66.7% -- so the 73.6% residual is not a mislocated base. Worth recording because vert_ofs (3..7) numerically overlaps the shader region (5*shader_count, 5..20 quadwords), which made a combined base look plausible; measurement says the simple one is right. The test asserts (4,5) above 60% and (0,1)/(2,3) below 5%/15%, and names the (6,7) artifact in its failure message so the higher-scoring pair cannot be mistaken for the answer later. --- crates/gc-extract/tests/disc.rs | 76 +++++++++++++++++++++++++++++++ docs/formats/core-data-regions.md | 27 +++++++++-- docs/roadmap.md | 16 +++++-- 3 files changed, 110 insertions(+), 9 deletions(-) diff --git a/crates/gc-extract/tests/disc.rs b/crates/gc-extract/tests/disc.rs index 2b8fe4f..d497430 100644 --- a/crates/gc-extract/tests/disc.rs +++ b/crates/gc-extract/tests/disc.rs @@ -3433,6 +3433,82 @@ fn tie_vertex_element_is_16_bytes_with_fixed_texcoords() { } } + // Which i16 slot pair is the texcoord? Scored per pair on level 0. + let core0 = std::fs::read(data.join("level0/core_data.bin")).expect("core_data"); + let idx0 = std::fs::read(data.join("level0/core_index.bin")).expect("core_index"); + let t0 = LevelCoreHeader::parse(&idx0).expect("hdr").tie_classes; + let (mut pair_hits, mut pair_tot) = ([0usize; 4], 0usize); + for c in 0..t0.count as usize { + let o = t0.offset as usize + c * 0x20; + let e = ClassEntry::parse(&idx0[o..o + 0x20]).expect("entry"); + let base = e.offset_in_asset_wad as usize; + if base == 0 || base + 0x48 > core0.len() { + continue; + } + let Ok(hdr) = TieClassHeader::parse(&core0[base..base + 0x48]) else { + continue; + }; + for lod in [1usize, 2] { + let off = hdr.packets[lod] as usize; + if off == 0 { + continue; + } + let s = base + off; + let Some(blk) = parse_tie_block(&core0, s) else { + continue; + }; + let rr: Vec<&[u8]> = blk.header.records(&core0[s..]).collect(); + for (ri, rec) in rr.iter().enumerate() { + let Ok(tr) = TieRecord::parse(rec) else { + continue; + }; + let start = s + 16 + ri * 16 + tr.pointer as usize + tr.vert_ofs as usize * 16; + for v in 0..tr.vert_size as usize { + let q = start + v * 16; + if q + 16 > core0.len() { + break; + } + pair_tot += 1; + for p in 0..4usize { + let a = + i16::from_le_bytes(core0[q + p * 4..q + p * 4 + 2].try_into().unwrap()); + let b = i16::from_le_bytes( + core0[q + p * 4 + 2..q + p * 4 + 4].try_into().unwrap(), + ); + if a % 4096 == 0 && b % 4096 == 0 { + pair_hits[p] += 1 + } + } + } + } + } + } + // (4,5) is the texcoord field. (6,7) scores *higher* only because it is the + // near-constant 4096/0 pair, which is trivially a pair of multiples of 4096 + // -- the same artifact a constant would produce anywhere. + assert!(pair_tot > 30_000, "only {pair_tot} level-0 vertices scored"); + let r = |p: usize| pair_hits[p] as f64 / pair_tot as f64; + assert!(r(2) > 0.60, "texcoord pair (4,5) fell to {:.3}", r(2)); + assert!( + r(0) < 0.05, + "pair (0,1) looks like a texcoord at {:.3}", + r(0) + ); + assert!( + r(1) < 0.15, + "pair (2,3) looks like a texcoord at {:.3}", + r(1) + ); + + eprintln!( + "tie vertex slot pairs, level 0 (n={pair_tot}): (0,1) {:.1}% (2,3) {:.1}% \ + (4,5) {:.1}% <- texcoords (6,7) {:.1}% <- the constant 4096/0 pair", + r(0) * 100.0, + r(1) * 100.0, + r(2) * 100.0, + r(3) * 100.0 + ); + assert!(verts > 90_000, "only {verts} tie vertices reached"); let r16 = fixed16 as f64 / verts as f64; let r8 = fixed8 as f64 / verts as f64; diff --git a/docs/formats/core-data-regions.md b/docs/formats/core-data-regions.md index 74c570f..209fe91 100644 --- a/docs/formats/core-data-regions.md +++ b/docs/formats/core-data-regions.md @@ -1607,12 +1607,29 @@ texcoord pair to be 12-bit fixed -- both words multiples of 4,096, which is how Sixteen wins by a factor of 1.7 over the nearest alternative, so it is the element size. -**Observed layout**, 8 x `i16`: +**Observed layout**, 8 x `i16`. Scoring each adjacent slot pair for 12-bit +fixedness over 36,501 level-0 vertices locates the fields: - i16[0..3] 10,803 to 13,421 distinct values, spanning the full i16 range - i16[4], [5] the texcoords; both multiples of 4,096 on 82.5% of vertices - i16[6] reads 4,096 on 85.8% - i16[7] reads 0 on 84.0% + (i16[0], i16[1]) 1.1% not a texcoord + (i16[2], i16[3]) 4.8% not a texcoord + (i16[4], i16[5]) 73.6% <- the texcoords + (i16[6], i16[7]) 76.6% <- the near-constant 4096/0 pair + +**`(6,7)` scores higher and is not a texcoord.** It is the near-constant +`4096, 0` pair -- `4096` is 1.0 in 12-bit fixed and `0` is zero, so *both* are +trivially multiples of 4,096. That is the constant artifact again: a fixed pair +would score 100% anywhere. The field is `(4,5)` at 73.6%, with `(0,1)` and `(2,3)` +ruled out at 1.1% and 4.8%. + +The first four slots span the full `i16` range with 10,803 to 13,421 distinct +values each, so they are neither texcoords nor small fields. + +**The vertex base is `vert_ofs` alone.** Adding the shader or control region to +it makes things *worse*, so the 73.6% residual is not a mislocated base: + + vert_ofs only 73.6% + bfc + vert_ofs 69.8% + bfc + control_count*control_size + vert_ofs 66.7% **Not established:** what any of the eight fields *means*, and why the texcoord rate is 82.5% rather than 100%. The residual varies with record shape -- records diff --git a/docs/roadmap.md b/docs/roadmap.md index 387c2ad..b2fe1b7 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -260,10 +260,18 @@ Phase 1 hands over the extents; this phase works out what each one contains. - [x] The tie **vertex element is 16 bytes**, measured over 95,612 vertices on levels 0-2 and settled by comparison: a 12-bit-fixed texcoord hit rate of **82.5%** at 16 bytes against 47.8% at 8 and 44.8% at 12. -- [ ] The tie **vertex fields are not named.** All eight `i16` slots span the full - range; the texcoord pair is 12-bit fixed on 82.5%, not 100%, and the - residual tracks record shape (60% to 89%), so something per-packet varies - that this reading does not capture. +- [x] The tie **texcoord field is located**: `i16[4]`/`i16[5]` of the 16-byte + element, scoring 73.6% for 12-bit fixedness over 36,501 level-0 vertices, + against 1.1% for `(0,1)` and 4.8% for `(2,3)`. The `(6,7)` pair scores + *higher* at 76.6% and is **not** a texcoord -- it is the near-constant + `4096, 0` pair, which is trivially two multiples of 4,096. +- [ ] The remaining tie vertex **fields are not named.** `i16[0..3]` span the full + `i16` range with 10,803-13,421 distinct values each, so they are neither + texcoords nor small fields; they are most likely position plus a packed + word, but that is not established. The texcoord rate is 73.6% rather than + 100% and the vertex base is confirmed to be `vert_ofs` alone (adding the + shader or control region makes it worse), so the residual is not a + mislocated base. - [~] `shrub` — class table done (48-byte entries, not 32; reading shrub at 32 still gives 231 sane-looking entries of 358, which is the hazard). Geometry header parsed and checked field by field on all 358 spans: