Assets/Scripts/API/VidFile.cs:110-113 documents a case it handles without an
explanation:
/// Occasionally there is a null block which must be rejected.
I think I can explain it, and the check reproduces cleanly against ARENA2.
The finding
When a frame's RLE data fills the frame buffer exactly, the decode loop exits on the
pixel count without consuming the terminator byte — but the encoder wrote one anyway.
That leftover byte lands where the next block type is read, and reads as block type 0.
It was never a block.
The prediction this makes is falsifiable: every 0x00 at a block boundary should be
immediately preceded by a video block whose RLE ended exactly at buffer-full, and never
by anything else. Results:
| Corpus |
Files |
Null blocks |
Preceded by buffer-full frame |
Violations |
Daggerfall ARENA2 |
17 |
84 |
84 |
0 |
| Terminator: Future Shock + SkyNET |
23 |
1,245 |
1,245 |
0 |
In ARENA2 the null blocks are confined to two files — DAG2.VID (83) and ANIM0009.VID
(1) — while the other 15 have none at all, which is presumably why they read as
"occasional."
Why this is not a bug report
Rejecting 0x00 as a skippable block is byte-identical in effect to consuming it as a
stray terminator, so the current handling is correct and needs no change. The only
actionable detail is for anyone writing a stricter parser later:
Terminator emission is inconsistent. In ARENA2, 84 of the 101 video frames that end
exactly at buffer-full are followed by 0x00 — the other 17 are not. (Future Shock and
SkyNET: 1,245 of 1,294.) So a parser can neither require the trailing byte nor assume
it is absent; both forms are valid output from the original encoder.
Incidental corpus data on the Unknown header fields
I ran the same parse over both games' files. Offered as data points, not claims:
| Field |
VidFile.cs |
ARENA2 (17 files) |
Future Shock / SkyNET (23 files) |
Unknown1 |
// Always 512 |
512 in all 17 |
512 in all 23 — never varies anywhere, so undecidable from data |
Unknown2 |
undocumented |
14 in all 17 |
10 or 14. 10 correlates exactly with use of type-0x01 incremental blocks; 14 with row-offset-only encoding. Both occur within Future Shock alone, so not a version marker |
PlaybackRate |
hard throw unless 166 (VidFile.cs:250-251) |
166 in all 17 |
166 in all 23 — the assumption holds across both eras |
| Frame size |
expects 256x200 or 320x200 |
320x200 (16 files), 256x200 (DAG2.VID) |
also 320x186 in one game — only relevant if the decoder is ever pointed at those files |
The Unknown2 split is the one that might be worth something: whatever encoder feature
it tracks appears to have gone unused by the time Daggerfall shipped, since ARENA2 holds
14 uniformly.
Provenance
This came out of documenting the VID format as it shipped in The Terminator: Future
Shock (Dec 1995) and SkyNET (1996), which use the same format Daggerfall shipped with
roughly nine months later — same 15-byte header, same block types
(0x00/0x01/0x02/0x03/0x04/0x14/0x7C/0x7D), same RLE opcodes, same 6-bit palette, same
rate byte. VidFile.cs was my starting reference, and every assumption in it that I
could test against those earlier files held up.
As a cross-check in the other direction: the parser I built from that earlier corpus
reads all 17 ARENA2 VIDs with no desync, and decoded frame count equals header
FrameCount in every file.
Happy to share the verification script (it just instruments the RLE exit condition and
walks the block stream) if anyone wants to reproduce the table.
Assets/Scripts/API/VidFile.cs:110-113documents a case it handles without anexplanation:
I think I can explain it, and the check reproduces cleanly against ARENA2.
The finding
When a frame's RLE data fills the frame buffer exactly, the decode loop exits on the
pixel count without consuming the terminator byte — but the encoder wrote one anyway.
That leftover byte lands where the next block type is read, and reads as block type 0.
It was never a block.
The prediction this makes is falsifiable: every
0x00at a block boundary should beimmediately preceded by a video block whose RLE ended exactly at buffer-full, and never
by anything else. Results:
ARENA2In ARENA2 the null blocks are confined to two files —
DAG2.VID(83) andANIM0009.VID(1) — while the other 15 have none at all, which is presumably why they read as
"occasional."
Why this is not a bug report
Rejecting
0x00as a skippable block is byte-identical in effect to consuming it as astray terminator, so the current handling is correct and needs no change. The only
actionable detail is for anyone writing a stricter parser later:
Terminator emission is inconsistent. In ARENA2, 84 of the 101 video frames that end
exactly at buffer-full are followed by
0x00— the other 17 are not. (Future Shock andSkyNET: 1,245 of 1,294.) So a parser can neither require the trailing byte nor assume
it is absent; both forms are valid output from the original encoder.
Incidental corpus data on the
Unknownheader fieldsI ran the same parse over both games' files. Offered as data points, not claims:
VidFile.csUnknown1// Always 512Unknown2PlaybackRatethrowunless 166 (VidFile.cs:250-251)DAG2.VID)The
Unknown2split is the one that might be worth something: whatever encoder featureit tracks appears to have gone unused by the time Daggerfall shipped, since ARENA2 holds
14 uniformly.
Provenance
This came out of documenting the VID format as it shipped in The Terminator: Future
Shock (Dec 1995) and SkyNET (1996), which use the same format Daggerfall shipped with
roughly nine months later — same 15-byte header, same block types
(0x00/0x01/0x02/0x03/0x04/0x14/0x7C/0x7D), same RLE opcodes, same 6-bit palette, same
rate byte.
VidFile.cswas my starting reference, and every assumption in it that Icould test against those earlier files held up.
As a cross-check in the other direction: the parser I built from that earlier corpus
reads all 17 ARENA2 VIDs with no desync, and decoded frame count equals header
FrameCountin every file.Happy to share the verification script (it just instruments the RLE exit condition and
walks the block stream) if anyone wants to reproduce the table.