The tie control region is not a VIF or GIF tag stream; and vert_ofs is mis-named
Went after the GS's GIF packets in the tie AD-GIF region. Got a negative result,
a new universal invariant, and a retraction of my own first probe, which reached
the right answer for the wrong reason twice.
THE NEGATIVE RESULT
Tried the leading `5 * shader_count` quadwords of each packet (the
`bfc_distance` field's stated meaning) as VIF tags -- command in bits 0-6, size
in 7-14, mark in 15 -- and as GIF tags -- type in the top byte of word 1, length
in bits 0-2 of word 0. Both uniform. Over 13,042 records: 10,443 distinct values
of word 0, no GIF tag type above a count of 2. That is the control result rule 4
asks for, and it says stop.
THE RETRACTION
The first probe scanned `bfc_distance` quadwords, which for most records
overruns `vert_ofs` and runs into the vertex data. Pooling a control region with
vertex positions -- two populations with different structure -- gives a uniform
answer, so "not a tag stream" came out right from a measurement that was
measuring the wrong bytes. That is exactly the rule 3a failure, and I only caught
it because the hex dump I took next looked structured while the histogram said
random.
Re-measuring quadword 0 alone, on its own population, gives the same uniformity.
The conclusion survives; the reasoning did not, and both are recorded.
THE PARTITION, AND A NEW INVARIANT
The hex dump showed the packet layout directly:
q[0 .. vert_ofs) control 3..8 quadwords, peak at 5
q[vert_ofs .. +vert_size) vertices
q[vert_ofs+vert_size ..] scissor
and `scissor_ofs == vert_ofs + vert_size` holds on **15,219 of 15,219** records
across all 27 levels, including every one whose strip_count is non-zero. That is a
derived quantity stored explicitly, and it holds everywhere -- which is the check
that confirms the three regions really are laid out the way the existing packet
length formula assumed. It is now a hard check in invariants_hold(), which is
something the previous four could not be.
The fixture in tie.rs failed the moment I added it: it had vert_ofs 4 and
vert_size 0x20 with scissor_ofs 0x30, which is 4 + 0x24. The synthetic data was
never constrained by a relation that did not exist yet. Fixed to 0x24.
vert_ofs IS MIS-NAMED
It is not a vertex offset. It is the *length* of the leading control region, and
the vertex data begins at that many quadwords into the packet. The field name
stays because the on-disc order is fixed and everything already written about this
record says vert_ofs, but the doc now states what the value is and gives the
distribution.
STILL UNKNOWN
What the control region contains. Not standard DMA tags; possibly pre-merged GS
register writes in another framing, or per-class state rather than per-primitive
draw data. Recorded as an open question rather than guessed at.
23 + 19 + 58 + 156 + 22 = 278 passing with the disc present (disc binary 35.67s),
clippy clean.
This commit is contained in:
@@ -252,15 +252,33 @@ pub struct TieRecord {
|
||||
pub control_count: u8,
|
||||
/// `+0x07`: 1..5 on disc, never above `control_count`.
|
||||
pub control_size: u8,
|
||||
/// `+0x08`: 3..7 on disc, never above `vert_size`.
|
||||
/// `+0x08`: **quadwords of control data before the vertices**, 3..8 on disc
|
||||
/// (`3`: 5 records, `4`: 3,364, `5`: 4,571, `6`: 3,469, `7`: 1,632,
|
||||
/// `8`: 1 of 15,219). Peak at 5.
|
||||
///
|
||||
/// **Not a vertex offset.** The name is the one thing about this record that
|
||||
/// is wrong: the value is the *length* of the leading region, not where the
|
||||
/// vertices start within some larger array. The vertex data begins at this
|
||||
/// many quadwords into the packet. Renamed in intent, not in the field name,
|
||||
/// because the on-disc order is fixed and `vert_ofs` is what every existing
|
||||
/// test and doc says.
|
||||
pub vert_ofs: u8,
|
||||
/// `+0x09`: 4..60 on disc.
|
||||
/// `+0x09`: quadwords of vertex data; 4..60 on disc.
|
||||
pub vert_size: u8,
|
||||
/// `+0x0a`: equals `vert_size` on every record measured.
|
||||
pub color_ofs: u8,
|
||||
/// `+0x0b`: zero on every record measured.
|
||||
pub color_count: u8,
|
||||
/// `+0x0c`: 7..67 on disc, never below `scissor_size`.
|
||||
/// `+0x0c`: quadword offset of the scissor data — and **exactly
|
||||
/// `vert_ofs + vert_size`, on 15,219 of 15,219 records across all 27
|
||||
/// levels**, including every record whose `strip_count` is non-zero.
|
||||
///
|
||||
/// It carries no information the other two fields do not. It is the end of
|
||||
/// the vertex region, stored twice. The universality is itself the useful
|
||||
/// part: it is the check that showed the packet really does partition as
|
||||
/// control / vertices / scissor, since a derived quantity only lands on the
|
||||
/// same value everywhere if those three regions are laid out the way the
|
||||
/// length formula already assumed.
|
||||
pub scissor_ofs: u8,
|
||||
/// `+0x0d`: 1..5 on disc.
|
||||
pub scissor_size: u8,
|
||||
@@ -329,6 +347,10 @@ impl TieRecord {
|
||||
&& self.vert_ofs <= self.vert_size
|
||||
&& self.scissor_size <= self.scissor_ofs
|
||||
&& self.control_size <= self.control_count
|
||||
// Universal on 15,219 of 15,219 records, so unlike the first four --
|
||||
// which are the large-partition rule plus three smaller ones -- this
|
||||
// one can be a hard check. See `scissor_ofs`.
|
||||
&& u16::from(self.scissor_ofs) == u16::from(self.vert_ofs) + u16::from(self.vert_size)
|
||||
}
|
||||
|
||||
/// Quadwords of packet data: `vert_ofs + vert_size + scissor_size`.
|
||||
@@ -557,7 +579,7 @@ mod tests {
|
||||
7, 2, // control_count, control_size
|
||||
4, 0x20, // vert_ofs, vert_size
|
||||
0x20, 0, // color_ofs (= vert_size), color_count
|
||||
0x30, 0x20, // scissor_ofs, scissor_size
|
||||
0x24, 0x20, // scissor_ofs (= vert_ofs + vert_size), scissor_size
|
||||
0, 0, // strip_count, strip_vertex_count
|
||||
]);
|
||||
}
|
||||
@@ -580,6 +602,8 @@ mod tests {
|
||||
let second = TieRecord::parse(records[1]).unwrap();
|
||||
assert_eq!(second.shader_count, 1);
|
||||
assert_eq!(second.bfc_distance, 5);
|
||||
// scissor_ofs is vert_ofs + vert_size: 4 + 0x20 = 0x24.
|
||||
assert_eq!(second.scissor_ofs, 0x24);
|
||||
assert!(second.invariants_hold());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user