bfc_distance is not the control region's length; the AD-GIF is not in the packet
Follows 9bb04cc. The control region is now characterised, and the second half of
the answer is a correction to a field doc that has been wrong since the record
was first parsed.
WHAT THE CONTROL REGION IS
Per-record, not shared state. 12,294 distinct blobs across 13,042 records, mixed
within 2,410 of 3,549 classes, and different between LOD 0 and LOD 1 on all 500
classes that have both. It varies per primitive.
Partitioned by its own length, it is not uniform in character, and the pooled
count hid that:
vert_ofs 48 bytes (3 qw) 5 distinct blobs
vert_ofs 64 bytes (4 qw) 3,175
vert_ofs 80 bytes (5 qw) 4,309
vert_ofs 96 bytes (6 qw) 3,281
vert_ofs 112 bytes (7 qw) 1,523
vert_ofs 128 bytes (8 qw) 1
The shortest and longest are effectively constant -- five blobs, and one -- while
everything between is per-record unique. That is a shape, not a noise floor, and
it is invisible unless the measurement is partitioned by length.
THE CORRECTION
bfc_distance == 5 * shader_count holds on every record, and the field doc has
always read "quadwords of ad-gif data" -- which read as a packet-relative length
would make it the control region's size.
It cannot be that. bfc_distance is 5..20 quadwords; vert_ofs, the control region,
is 3..8.
Pooled, they overlap on 9,315 of 15,219 records -- 61%, a number describing
nothing. Partitioned by shader_count, as AGENTS 3a requires, it is exact:
shader_count 1: 2,772 of 8,676 exceed (32%)
shader_count 2: 4,649 of 4,649 exceed (100%)
shader_count 3: 1,729 of 1,729 exceed (100%)
shader_count 4: 165 of 165 exceed (100%)
Without exception for two or more shaders. For one shader it exceeds exactly when
vert_ofs is below 5, which is that 32%.
**The AD-GIF is not stored in the tie packet.** The packet is control, vertices
and scissor, in that order. bfc_distance is a per-record quantity tied to the
shader count whose meaning is still not established, and the "quadwords of ad-gif
data" reading of it is wrong. The doc now says so and says why, rather than
carrying a plausible description of a field whose meaning nobody has established.
This is the fourth retraction today and the second today found by partitioning
rather than by a test failing. The pooled number was not merely less precise than
the partitioned ones -- it pointed the other way, because 61% looks like noise and
100% looks like a boundary. Had I accepted the pooled figure I would have written
"sometimes" and moved on.
59 disc tests, 278 total across 23 + 19 + 59 + 156 + 22 with the disc present
(disc binary 35.31s), clippy clean.
This commit is contained in:
@@ -6321,3 +6321,96 @@ fn level_table_has_twenty_seven_keys_with_twenty_one_absent() {
|
||||
assert_eq!(ids, expected, "the level-id set moved");
|
||||
assert_eq!(*ids.last().unwrap(), 30, "the highest level id is 30");
|
||||
}
|
||||
|
||||
/// `bfc_distance` is not the length of the control region, partitioned by
|
||||
/// `shader_count`.
|
||||
///
|
||||
/// `bfc_distance == 5 * shader_count` holds universally, but reading it as
|
||||
/// "quadwords of AD-GIF data" makes it the length of the leading region -- and it
|
||||
/// cannot be. `bfc_distance` runs 5..20 quadwords and `vert_ofs` only 3..8.
|
||||
///
|
||||
/// Pooled, the two overlap on 9,315 of 15,219 records and that number describes
|
||||
/// nothing. Partitioned by `shader_count` it is exact: for two or more shaders
|
||||
/// `bfc_distance` exceeds `vert_ofs` on **every** record, and for one shader on
|
||||
/// 32%, which is just the share whose `vert_ofs` is below 5.
|
||||
///
|
||||
/// The AD-GIF is therefore not stored in the tie packet. `bfc_distance` is a
|
||||
/// per-record quantity relating to the shader count, and the packet holds control,
|
||||
/// vertices and scissor in that order.
|
||||
#[test]
|
||||
fn tie_bfc_distance_is_not_the_control_region_length() {
|
||||
require_disc!();
|
||||
let data = std::env::var_os("GC_EXTRACTED")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| {
|
||||
let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
|
||||
PathBuf::from(home).join("Documents/gc-extracted")
|
||||
});
|
||||
if !data.exists() {
|
||||
eprintln!("skipped: unpack the disc first");
|
||||
return;
|
||||
}
|
||||
use gc_formats::level_core::{ClassEntry, LevelCoreHeader};
|
||||
use gc_formats::tie::{parse_tie_block, TieClassHeader, TieRecord};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
let mut by_sc: BTreeMap<u8, (usize, usize)> = BTreeMap::new();
|
||||
let mut total = 0usize;
|
||||
|
||||
for lv in 0..27usize {
|
||||
let (Ok(idx), Ok(core)) = (
|
||||
std::fs::read(data.join(format!("level{lv}/core_index.bin"))),
|
||||
std::fs::read(data.join(format!("level{lv}/core_data.bin"))),
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
let t = LevelCoreHeader::parse(&idx).expect("hdr").tie_classes;
|
||||
for c in 0..t.count as usize {
|
||||
let o = t.offset as usize + c * 0x20;
|
||||
let Ok(e) = ClassEntry::parse(&idx[o..o + 0x20]) else {
|
||||
continue;
|
||||
};
|
||||
let base = e.offset_in_asset_wad as usize;
|
||||
if base == 0 || base + 0x48 > core.len() {
|
||||
continue;
|
||||
}
|
||||
let Ok(hdr) = TieClassHeader::parse(&core[base..base + 0x48]) else {
|
||||
continue;
|
||||
};
|
||||
for lod in 1..=2usize {
|
||||
let off = hdr.packets[lod];
|
||||
if off <= 0 {
|
||||
continue;
|
||||
}
|
||||
let s = base + off as usize;
|
||||
let Some(blk) = parse_tie_block(&core, s) else {
|
||||
continue;
|
||||
};
|
||||
for r in blk.header.records(&core[s..]) {
|
||||
let Ok(r) = TieRecord::parse(r) else { continue };
|
||||
total += 1;
|
||||
let e = by_sc.entry(r.shader_count).or_insert((0, 0));
|
||||
e.0 += 1;
|
||||
e.1 += usize::from(r.bfc_distance > r.vert_ofs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(total > 15_000, "only {total} tie records read");
|
||||
eprintln!("bfc_distance > vert_ofs, by shader_count: {by_sc:?}");
|
||||
for (sc, (n, over)) in &by_sc {
|
||||
if *sc >= 2 {
|
||||
assert_eq!(
|
||||
*n, *over,
|
||||
"shader_count {sc}: expected every record to exceed, got {over}/{n}"
|
||||
);
|
||||
}
|
||||
}
|
||||
// One shader: exceeds exactly when vert_ofs < 5, i.e. control < 5 quadwords.
|
||||
let (n1, over1) = by_sc[&1];
|
||||
assert!(
|
||||
over1 < n1,
|
||||
"shader_count 1 should not exceed on every record"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -246,7 +246,19 @@ pub struct TieRecord {
|
||||
pub pointer: u32,
|
||||
/// `+0x04`: AD-GIF (material) blocks this packet references; 1..4 on disc.
|
||||
pub shader_count: u8,
|
||||
/// `+0x05`: quadwords of ad-gif data. Equals `5 * shader_count`.
|
||||
/// `+0x05`: equals `5 * shader_count` on every record measured.
|
||||
///
|
||||
/// **This is not the length of the control region**, despite what the field
|
||||
/// is usually described as. It runs 5..20 quadwords and the control region is
|
||||
/// `vert_ofs`, 3..8, so for two or more shaders the value exceeds the region
|
||||
/// on *every* record -- 9,315 of 15,219 pooled, which is a number describing
|
||||
/// nothing; partitioned by `shader_count` it is 100% for 2, 3 and 4 and 32%
|
||||
/// for 1, which is exactly the share whose `vert_ofs` is below 5.
|
||||
///
|
||||
/// So the AD-GIF is **not stored in the tie packet**. What this value means
|
||||
/// is not established; the `5 * shader_count` relation is solid and the
|
||||
/// "quadwords of ad-gif data" reading of it is not. See
|
||||
/// `tie_bfc_distance_is_not_the_control_region_length`.
|
||||
pub bfc_distance: u8,
|
||||
/// `+0x06`: 4..18 on disc.
|
||||
pub control_count: u8,
|
||||
|
||||
@@ -1807,6 +1807,45 @@ the packet. The field name is left alone because the on-disc order is fixed and
|
||||
everything already written about this record says `vert_ofs`, but the doc now
|
||||
says what the value is.
|
||||
|
||||
**Still not known:** what the control region actually contains. It is not
|
||||
standard VIF or GIF DMA tags. It may be pre-merged GS register writes in a
|
||||
different framing, or per-class state rather than per-primitive draw data.
|
||||
**What the control region is:** per-record, not shared. 12,294 distinct blobs
|
||||
across 13,042 records, mixed within 2,410 of 3,549 classes, and different between
|
||||
LOD 0 and LOD 1 on all 500 classes that have both. It varies per primitive.
|
||||
|
||||
Partitioned by its own length it is *not* uniform in character -- the pooled
|
||||
count hid this:
|
||||
|
||||
vert_ofs 48 bytes (3 qw) 5 distinct blobs
|
||||
vert_ofs 64 bytes (4 qw) 3,175
|
||||
vert_ofs 80 bytes (5 qw) 4,309
|
||||
vert_ofs 96 bytes (6 qw) 3,281
|
||||
vert_ofs 112 bytes (7 qw) 1,523
|
||||
vert_ofs 128 bytes (8 qw) 1
|
||||
|
||||
The shortest and longest are effectively constant -- five blobs and one -- and
|
||||
everything in between is per-record unique. That is a shape, not a noise floor.
|
||||
|
||||
## bfc_distance is not the control region's length
|
||||
|
||||
The other half of the answer. `bfc_distance == 5 * shader_count` holds on every
|
||||
record and has always been documented as "quadwords of ad-gif data" -- which, read
|
||||
as a length within the packet, would make it the control region's size.
|
||||
|
||||
It cannot be. `bfc_distance` is 5..20 quadwords; `vert_ofs`, the control region,
|
||||
is 3..8.
|
||||
|
||||
Pooled, the two overlap on 9,315 of 15,219 records (61%) -- a number describing
|
||||
nothing. Partitioned by `shader_count`, as rule 3a requires, it is exact:
|
||||
|
||||
shader_count 1: 2,772 of 8,676 exceed (32%)
|
||||
shader_count 2: 4,649 of 4,649 exceed (100%)
|
||||
shader_count 3: 1,729 of 1,729 exceed (100%)
|
||||
shader_count 4: 165 of 165 exceed (100%)
|
||||
|
||||
For two or more shaders it exceeds on every record, without exception. For one
|
||||
shader it exceeds exactly when `vert_ofs < 5`, which is the 32%.
|
||||
|
||||
**So the AD-GIF is not stored in the tie packet.** The packet is control,
|
||||
vertices, scissor. `bfc_distance` is a per-record quantity relating to the shader
|
||||
count whose meaning is not established, and the "quadwords of ad-gif data" reading
|
||||
of it is wrong. The field doc said so in terms before this measurement; it does
|
||||
not now.
|
||||
|
||||
+3
-1
@@ -747,7 +747,9 @@ lookup, and it is the main thing inherited from ReRAC.
|
||||
the GS's draw packets are not stored there in standard DMA form. What the
|
||||
probe did establish is the packet partition (control / vertices / scissor)
|
||||
and a universal invariant, `scissor_ofs == vert_ofs + vert_size`, on 15,219
|
||||
of 15,219 records. See `docs/plan/level-assets.md`.
|
||||
of 15,219 records. **`bfc_distance` is also not the control region's length** --
|
||||
100% of records exceed it for 2+ shaders, 32% for one -- so the AD-GIF is not
|
||||
stored in the tie packet at all. See `docs/plan/level-assets.md`.
|
||||
|
||||
**Gate:** CPU and GPU paths agree on every vertex of a reference level — the
|
||||
same identity check ReRAC runs.
|
||||
|
||||
Reference in New Issue
Block a user