Retract the uniformity claim everywhere it still stood, including the API docs

The record-length retraction had been applied to the tests and to the section
that discovered the problem, but the same claim was still standing in four other
places -- including the rustdoc for has_uniform_record_length(), which is what a
caller would actually read:

  crates/gc-formats/src/chunks.rs:206   "Measured: true for 1,509 of 1,509 chunks"
  docs/roadmap.md:448                    "uniform record length (1,509 of 1,509)"
  docs/formats/core-data-regions.md      a whole section titled
                                         "Every chunk has a uniform record length"

All three now say 257 of 1,509, and the method's rustdoc explains WHY the old
number was an artefact -- that resolve sliced at the first offset delta, so every
record came out the same length by construction and the predicate was confirming
the parser back to itself.

That section also contained a withdrawal, and the withdrawal was wrong:

  "This withdraws the previous commit's variable-length type. That came from
   taking minlen/maxlen over all of a type's records at once, pooling across
   chunks; rule 3a's pooled claim, third time this session."

The pooling rule fired correctly and was overruled by a measurement with no
independent content. The per-chunk check that should have caught the pooled
signal was itself reporting the parser's own slicing, so it agreed with the wrong
answer for the same reason the original claim did. Two errors stacked: a real
signal dismissed by a rule that had been right twice already, and a predicate
that could not have told the difference.

This is the third time this session that a test was asserting the parser's
behaviour rather than the disc's, and the third time it was the only thing
standing between a claim and a retraction. has_uniform_record_length is the
clearest instance: a predicate named after a property of the data, returning a
confident answer about the code.

245 tests, clippy clean.
This commit is contained in:
2026-10-13 08:18:20 +02:00
parent 2d3880b624
commit 8ccc3ddda4
3 changed files with 40 additions and 14 deletions
+14 -3
View File
@@ -203,10 +203,21 @@ impl<'a> ResolvedChunk<'a> {
/// True when every record in this chunk has the same length. /// True when every record in this chunk has the same length.
/// ///
/// **Measured: true for 1,509 of 1,509 chunks** across all 27 levels. Record /// **Measured: true for 257 of 1,509 chunks** across all 27 levels. Record
/// length varies *between* chunks of the same type -- one type runs 76 to 104 /// length varies *between* chunks of the same type -- one type runs 76 to 104
/// bytes -- but never within one. An earlier claim that some type had /// bytes -- and, on 1,252 chunks, *within* one as well.
/// variable-length records was pooling across chunks; it is withdrawn. ///
/// This method used to report 1,509 of 1,509, and that was an artefact of
/// `resolve`: it sliced every record at the chunk's first offset delta, so
/// every record came out the same length by construction, and this predicate
/// was confirming the parser back to itself. `resolve` now bounds each record
/// by its successor's offset, which is the only coherent reading -- a record
/// of the nominal stride would overlap the next one whenever the delta is
/// smaller, and an explicit offset table cannot express an overlap.
///
/// So the earlier withdrawal, "an earlier claim that some type had
/// variable-length records was pooling across chunks", was also wrong. The
/// records are variable-length and were never anything else.
pub fn has_uniform_record_length(&self) -> bool { pub fn has_uniform_record_length(&self) -> bool {
let mut it = self.records.iter().filter(|r| !r.is_empty()); let mut it = self.records.iter().filter(|r| !r.is_empty());
let Some(first) = it.next().map(|r| r.len()) else { let Some(first) = it.next().map(|r| r.len()) else {
+20 -7
View File
@@ -1429,15 +1429,28 @@ fits and `None` otherwise, so the 247 unexplained chunks are distinguishable
from the 1,262 explained ones and nothing skips a leading byte on the strength of from the 1,262 explained ones and nothing skips a leading byte on the strength of
a rule that does not apply. a rule that does not apply.
### Every chunk has a uniform record length ### RETRACTED: "every chunk has a uniform record length"
**1,509 of 1,509 chunks.** Length varies between chunks of the same type -- one **Claimed: 1,509 of 1,509 chunks. True: 257 of 1,509.**
type runs 76 to 104 bytes -- but never within one chunk.
This **withdraws the previous commit's "variable-length type"**. That came from Length varies between chunks of the same type -- one type runs 76 to 104 bytes --
taking `minlen`/`maxlen` over all of a type's records at once, pooling across and on 1,252 chunks it varies *within* one as well.
chunks; rule 3a's pooled claim, third time this session. Within any one chunk the
length is fixed. **Both halves of that old entry were wrong, and both were mine.**
"Never within one chunk" was `resolve` slicing every record at the chunk's first
offset delta. Every record then came out the same length by construction, so the
predicate was confirming the parser back to itself.
And this section's *withdrawal* of "variable-length type" was wrong too. It read
the pooled observation as a partitioning artifact -- `minlen`/`maxlen` taken across
all of a type's records at once. Rule 3a is right about pooling in general, and it
was applied to a real signal here: the type genuinely has variable-length records,
and the per-chunk check that would have caught it was itself reporting the
parser's own slicing.
Two errors stacked. The pooling rule fired correctly and was overruled by a
measurement that had no independent content.
## The first TWO bytes encode the record's own index ## The first TWO bytes encode the record's own index
+6 -4
View File
@@ -444,10 +444,12 @@ Phase 1 hands over the extents; this phase works out what each one contains.
chunks, 34,491 records.** Moby class IDs enrich at 9,493 hits against chunks, 34,491 records.** Moby class IDs enrich at 9,493 hits against
246.1 expected with contiguous per-type arrays -- but the arrays land in a 246.1 expected with contiguous per-type arrays -- but the arrays land in a
different place for each type, so no single pooled scan finds them. different place for each type, so no single pooled scan finds them.
- [x] **Chunk record framing is closed.** Every chunk has a **uniform record - [x] **Chunk record framing is closed**, but the uniformity claim in it is
length** (1,509 of 1,509) -- length varies *between* chunks of a type, never **retracted**: only **257 of 1,509** chunks have a uniform record length, and
within one, which withdrew an earlier "variable-length type" claim that had 1,252 are variable within the chunk. "1,509 of 1,509" was `resolve` slicing
been pooled across chunks. The record's leading byte is a **per-chunk phase at the chunk's first offset delta and the predicate confirming the parser
back to itself. An earlier withdrawal ("variable-length was a pooled
artifact") was wrong too -- the records were always variable. The record's leading byte is a **per-chunk phase
counter** (`bucket_rule`, 1,262 of 1,509 chunks) and on **1,235** of them counter** (`bucket_rule`, 1,262 of 1,509 chunks) and on **1,235** of them
*both* leading bytes encode the record's own index in mixed radix *both* leading bytes encode the record's own index in mixed radix
(`index_rule`), so the leading bytes carry no per-record information. (`index_rule`), so the leading bytes carry no per-record information.