Expose the shrub body as 8-byte records, bounded per class

Turns the differential finding into code. Each shrub body record is 6
varying bytes plus 2 constant ones; body_records() hands them back raw and
body_is_well_formed() checks the trailing pair.

ShrubHeader::region now takes an explicit span_end. It has to: without it
the data slice runs to the end of the lump, so the last class in each level
absorbs whatever follows shrub and a per-class check ends up measuring its
neighbours. That is the same unbounded-span mistake as before, caught this
time by a failing assertion rather than by reading the docs afterwards.

The disc test asserts well-formedness only for the 334 of 358 classes whose
span ends at the next class. The last class in each level has no successor,
so its span contains the 690 KB region after shrub and its body is not
wholly records -- that is the open question, and asserting either way would
be wrong. Shrub::span_end_is_a_successor records which case a span is.

Records are summed only across bounded bodies. Summing the last class too
reproduces the "3.5 million records" figure retracted two commits ago, to
within rounding -- which is a useful check on how easy that mistake is to
repeat by accident.

8,938 records across the 334 bounded bodies; 16 denormal reserved words;
358 headers naming their own class.
This commit is contained in:
2026-10-11 03:26:06 +02:00
parent ed7e568231
commit 69b81af3b2
2 changed files with 160 additions and 14 deletions
+59 -3
View File
@@ -1646,6 +1646,9 @@ fn shrub_headers_name_their_own_class_on_every_level() {
let mut absent = 0usize;
let (mut min_size, mut max_size) = (f32::MAX, 0.0f32);
let mut denormals = 0usize;
let mut bounded_records = 0usize;
let mut bounded_bodies = 0usize;
let mut last_bodies = 0usize;
for n in 0..27 {
let dir = data.join(format!("level{n}"));
@@ -1657,12 +1660,63 @@ fn shrub_headers_name_their_own_class_on_every_level() {
};
let h = gc_formats::level_core::LevelCoreHeader::parse(&index).expect("core header");
let range = h.shrub_classes;
// Class spans tile between consecutive geometry offsets, with the last
// one bounded by the start of core_data's constant trailer. Without
// this the last class absorbs the trailer and the body check measures
// its neighbours.
let trailer = gc_formats::regions::CORE_DATA_TRAILER_BYTES;
let mut offsets: Vec<usize> = Vec::new();
for i in 0..range.count.max(0) as usize {
let at = range.offset.max(0) as usize + i * SHRUB_CLASS_ENTRY_BYTES;
let e = ShrubClassEntry::parse(&index[at..at + SHRUB_CLASS_ENTRY_BYTES])
.unwrap_or_else(|e| panic!("level{n}: shrub class {i}: {e}"));
match ShrubHeader::region(&core, e.base.offset_in_asset_wad, e.base.o_class) {
Ok(Some(s)) => {
if e.base.offset_in_asset_wad > 0 {
offsets.push(e.base.offset_in_asset_wad as usize);
}
}
offsets.sort_unstable();
let mut span_end_of: std::collections::HashMap<usize, usize> =
std::collections::HashMap::new();
for (i, &o) in offsets.iter().enumerate() {
let end = offsets.get(i + 1).copied().unwrap_or(core.len() - trailer);
span_end_of.insert(o, end);
}
for i in 0..range.count.max(0) as usize {
let at = range.offset.max(0) as usize + i * SHRUB_CLASS_ENTRY_BYTES;
let e = ShrubClassEntry::parse(&index[at..at + SHRUB_CLASS_ENTRY_BYTES])
.unwrap_or_else(|e| panic!("level{n}: shrub class {i}: {e}"));
let (span_end, has_successor) = match span_end_of
.get(&(e.base.offset_in_asset_wad.max(0) as usize))
{
Some(&e2)
if offsets.last() != Some(&(e.base.offset_in_asset_wad.max(0) as usize)) =>
{
(e2, true)
}
_ => (core.len(), false),
};
match ShrubHeader::region(&core, e.base.offset_in_asset_wad, e.base.o_class, span_end) {
Ok(Some(mut s)) => {
s.span_end_is_a_successor = has_successor;
// Only a class whose span ends at the *next* class has a
// bounded body. The last class's span runs to the trailer
// and contains the 690 KB region that follows shrub, so its
// body is not wholly records -- that is the open question,
// not a defect, and asserting either way would be wrong.
if s.span_end_is_a_successor {
assert!(
s.body_is_well_formed(),
"level{n} class {i}: bounded shrub body is not whole 8-byte records"
);
bounded_bodies += 1;
}
last_bodies += 1;
// Count records only where the span is actually bounded.
// Summing the unbounded last class too reproduces the
// retracted "3.5 million records" figure.
if s.span_end_is_a_successor {
bounded_records += s.body_record_count();
}
assert_eq!(
s.header.o_class,
e.base.o_class.max(0) as u32,
@@ -1709,7 +1763,9 @@ fn shrub_headers_name_their_own_class_on_every_level() {
assert!(spans > 340, "only {spans} shrub spans with geometry");
eprintln!(
"shrub: {spans} headers naming their own class, {absent} without geometry; \
size field {min_size}..{max_size}; {denormals} denormal reserved words"
{bounded_bodies}/{last_bodies} class bodies bounded by a successor, \
{bounded_records} 8-byte records in bounded bodies, \
{denormals} denormal reserved words"
);
}