Format reference · .zrb

A movie in palettes and blocks

Step through an original Smacker clip, see which blocks survive between frames, and follow the bytes behind Total Annihilation’s cinematics.

Container
Smacker 2 · SMK2
Fixed header
104 bytes · little-endian
Video unit
4 × 4 indexed pixels
Stock cadence
33.33 ms per frame

A familiar extension, a Smacker movie

The five inspected Total Annihilation movies, Data/1.zrb through Data/5.zrb, are ordinary Smacker 2 files beginning with SMK2. The .zrb extension adds no outer container. Each packet can update the palette, carry audio, and describe what to draw or retain in the next video frame.

Start with a simple picture: a rectangle moving across a dark background. Each new frame writes the blocks needed to move or recolor the rectangle, while most of the picture stays in place.

Original interactive example

A rectangle in motion

Moving rectangle · 64 × 32 stored pixels
Frame 0: green rectangle, 16 × 12 pixels, at (4, 8)

Stored extent · each block is 4 × 4 pixels.

Block instructions · 16 × 8 blocks
Frame 0: 128 solid writes and 0 retained blocks

Solid write Retain previous indices

Solid writes
128 / 128
Retained blocks
0 / 128
Palette chunk
16 bytes

Packet 0 · offset 202 · 48 bytes · mask 0x01

The first packet writes the rectangle and its background.

Enable JavaScript to play or scrub this original clip. Download the SMK2 (.zrb) · Decoded frame data

An original 970-byte SMK2 movie: a 16 × 12 rectangle moves across a 64 × 32 picture and back, changing from green to amber to teal and back to green. Press Play to watch all 16 silent frames at the authored 125 ms interval, or step through them. The block-instruction view shows which parts of the picture each packet writes; uncheck Show block instructions to hide it.

The rectangle moves four pixels per frame: one column of 4 × 4 blocks. Most steps need just six solid writes—three blocks at its leading edge and three to restore the background behind it. At a color change, 15 writes repaint the rectangle and clear its trailing edge. All other blocks retain their previous pixel indices.

This clip keeps its palette fixed and changes the rectangle’s color by writing different indices. A palette update can also change colors without rewriting any pixels; the palette example below shows that separate behavior.

The instruction map marks type 3 solid writes with bright squares and type 2 retained blocks with dark squares and dashes. The views come from decoding the downloadable binary; the browser plays generated SVG frames. Both examples on this page are silent, have no ring packet, and use one block per instruction. They do not exercise two-color or full-color blocks.

Find the packet boundaries

All scalar fields are little-endian. The physical packet count is the logical frame count plus one if the ring flag is set. After the fixed header, read the packet-size table, content-mask table, video trees, and then packets in sequence.

104-byte header
  → physical packet count × 4-byte size words
  → physical packet count × 1-byte content masks
  → video-tree section (header gives its byte length)
  → packet 0 → packet 1 → … → optional ring packet

For each size word, clear the low two bits to obtain the packet length. Bit 0 marks a keyframe. The purpose of bit 1 remains unknown; neither bit changes sequential decoding. The ring packet is indexed but is not one of the frames in play-once playback.

Content-mask bitChunk presentPosition inside packet
0PaletteFirst, if present.
1–7Audio tracks 0–6After the palette, in ascending track order.
No separate bitVideo bitstreamAfter all declared palette and audio chunks.

Byte layouts

Fixed header

These offsets are file-relative. The fixed header ends at byte 104 (0x68).

Scroll table horizontally to see all fields →

OffsetBytesTypeFieldMeaning
0x004ASCIISignatureSMK2; SMK4 is another video revision.
0x04, 0x084 eachu32Width, heightStored pixel dimensions.
0x0C4u32Frame countLogical frames to play.
0x104i32Frame intervalPositive: milliseconds. Negative: magnitude × 10 microseconds.
0x144u32FlagsBit 0: ring packet. Bit 1: alternate black rows. Bit 2: repeated rows. Either display mode doubles height.
0x1828u32[7]Audio sizesLargest decoded packet size for each track.
0x344u32Tree bytesEncoded video-tree section length.
0x3816u32[4]Tree allocation hintsMap, color-pair, full-color-pair, block-type order.
0x4828u32[7]Audio descriptionsRate, presence, codec, width, and channels.
0x644u32ReservedReserved word.

Audio track description

Each description is a u32 at 0x48 + 4 × track, for tracks 0 through 6.

BitsMeaning
0–23Sample rate.
26 or 27Alternative perceptual codec selection.
28Stereo if set.
29Sixteen-bit samples if set; otherwise unsigned eight-bit samples.
30Track present.
31Compressed audio.

The palette example’s file boundaries

The separate palette example uses example.zrb, a 990-byte signal sweep. This file has 16 logical and physical packets. Its first size word is 53 (0x35): keyframe bit 0 is set, and clearing the low two bits gives 52 bytes.

File offsetLengthContents
0104 bytesFixed header: SMK2, 64 × 32, 16 frames, interval 125, flags 4.
10464 bytesSixteen packet-size words.
16816 bytesContent masks; packets 0 and 8 have mask 0x01.
18418 bytesVideo trees; only the block-type tree is present.
20252 bytesPacket 0: 16 palette bytes and 36 video/padding bytes.
59064 bytesPacket 8: 16 palette bytes and 48 video/padding bytes.
990End of file.

The palette clip’s decoded frame data records every packet’s offset and length. The first packet writes all 128 blocks. Moving the scan column requires 12 solid writes per frame; frame 8 instead retains all 128 blocks and updates the palette. Packet length depends on the encoded symbols, so fewer pixel writes do not necessarily mean a smaller packet.

Time and display extent

A positive interval of 125 means 125 milliseconds per frame. A negative interval of −3333, used by all five inspected movies, means 3333 × 10 µs = 33.33 ms. Each example’s 16 frames at 125 ms occupy two seconds at authored speed.

Stored height and display height are separate. Repeating each stored row and inserting an alternate black row both double the display height, but produce different pictures. Nanolathe’s format reader reports the display extent; it does not render these display modes. The palette example’s Double rows control illustrates repeated rows, selected by its header flag 4. The moving rectangle uses flags 0, so stored and display dimensions are both 64 × 32.

Bits, Huffman trees, and caches

Bits are consumed least-significant first within each byte. The first bit of a multi-bit integer is also its least-significant bit. Tree topology is prefix ordered: 0 introduces a leaf; 1 introduces the zero branch followed by the one branch.

A byte-valued tree starts with a presence bit. An absent tree yields zero. A present tree stores eight bits at each leaf and finishes with a zero delimiter after the complete topology. A constant leaf requires no traversal bits. The terminating delimiter is established by asset parsing and authored fixtures; the public description omits it.

The video section contains four word-valued trees, in this order:

  1. Map words: sixteen two-color selectors.
  2. Color-pair words: two palette indices.
  3. Full-color-pair words: two palette indices for full-color blocks.
  4. Block-type words: operation, run length, and solid color.

Each word tree starts with a presence bit. A present word tree contains low-byte and high-byte trees, three literal 16-bit cache-marker values, its topology, and a final zero delimiter. Decode each topology leaf through the low-byte tree first, then the high-byte tree. A leaf matching a marker references one of three cache slots, initially zero.

Each word tree keeps its own three most recent distinct-in-succession outputs. Resolve a cache reference before updating the cache. If the value equals the newest entry, leave the cache unchanged; otherwise shift the entries and install the new value. Reset all caches before every video frame. Cache markers do not need a physical leaf if unreferenced.

The tree section may have unused trailing bytes. Its declared byte length, not the end of the final topology, locates the first packet.

A palette update can repaint retained pixels

A retained block keeps its palette indices, not a frozen RGB color. If the palette changes, the displayed picture can change even when every block retains its pixels.

Palette comparison

A new color, without new pixel indices

Decoded color · 64 × 32 stored pixels
Frame 7: original green waveform with an amber scan column

Stored extent · each block is 4 × 4 pixels.

Block instructions · 16 × 8 blocks
Frame 7: 12 solid writes and 116 retained blocks

Solid write Retain previous indices

Solid writes
12 / 128
Retained blocks
116 / 128
Palette chunk
0 bytes

Packet 7 · offset 542 · 48 bytes · mask 0x00

Twelve solid writes move the scan column; the other 116 blocks retain their previous indices.

Enable JavaScript to compare the palette change. Download the SMK2 (.zrb) · Decoded frame data

Compare frames 7 and 8 of an original signal-sweep clip. The waveform turns from green to red while the instruction map changes to 128 retained blocks and zero solid writes. Only the palette changes. This separate 990-byte SMK2 download has 16 frames and a repeated-row display flag; Double rows illustrates its requested 64 × 64 display extent.

Palette operations fill entries starting at index zero. Keep an immutable copy of the previous palette for copy operations. For control byte c:

ConditionOperationExtra bytes
Bit 7 setRetain the next (c & 127) + 1 entries in place.None.
Bit 7 clear, bit 6 setCopy (c & 63) + 1 entries from the previous palette to the output cursor.One source index.
Both high bits clearWrite one literal color; c is red.Green, then blue.

Literal components are six-bit values. Expand each with (v << 2) | (v >> 4). The order is red, green, blue, established by an authored pure-red reference; the public description’s component labels are inverted. Palette colors are opaque, including index zero.

Stop at 256 entries or chunk exhaustion. Bytes beyond the declared palette span belong to the next chunk, even if not all palette entries were visited. Overlapping copies still read from the old palette, never from entries just written.

In frame 8 of the palette example, palette index 2 changes from (45, 59, 24) to (57, 23, 17) in six-bit components. That is RGB (182, 239, 97)(231, 93, 69) after expansion. Every pixel index is retained, but the waveform turns from green to red. The 16-byte chunk writes four RGB literals, retains the remaining 252 entries, and has one padding byte.

Four block operations

Visit 4 × 4 blocks from left to right, then top to bottom. Clip partial edge blocks to stored dimensions. Decode a word from the type tree: its low two bits select the operation, bits 2–7 select a run length, and the high byte supplies a solid color.

TypeOperationDecode for each block in the run
0Two-colorA color-pair word, then a map word. Successive low-to-high map bits select the low-byte color for 0 and high-byte color for 1, in pixel row order.
1Full-colorTwo full-color-pair words per row, for four rows. The first word supplies columns 2 and 3; the second supplies 0 and 1, low byte then high byte in each pair.
2RetainKeep the previous frame’s pixel indices.
3SolidFill with the type word’s high byte.

Run indices 0–58 mean index plus one blocks. Indices 59–63 mean 128, 256, 512, 1024, and 2048 blocks respectively. Repeat the operation for that many consecutive blocks; additional color and map words are decoded for each block that needs them.

For example, 0x0303 means a one-block solid fill with palette index 3. 0x0002 retains one block. Those are two of the five type-tree values used in both downloadable clips.

Audio restarts its predictors per packet

Every audio chunk starts with its inclusive u32 length. Uncompressed chunks then contain PCM: unsigned bytes or signed little-endian 16-bit words, with stereo channels interleaved left then right.

Compressed chunks add a u32 decoded source-byte count, followed by an independent bitstream. The first three bits specify data presence, stereo, and sixteen-bit samples. Width and channel flags must agree with the track description; an absent-data chunk yields no samples.

For Huffman DPCM, build one byte tree per byte in an interleaved sample frame: one for eight-bit mono, two for eight-bit stereo or sixteen-bit mono, and four for sixteen-bit stereo.

  1. Read initial channel samples in reverse channel order. For sixteen-bit samples, read the high byte before the low byte. Output those initial samples in ordinary channel order.
  2. Decode following deltas in ordinary channel order, low-byte tree then high-byte tree for sixteen-bit data.
  3. Assemble a complete delta word before adding it to the previous sample, modulo 65536 for sixteen-bit or modulo 256 for eight-bit data. Low-byte overflow carries into the high byte.
  4. Stop at the declared decoded source-byte count, including the initial samples. Restart the predictors in the next packet.

Nanolathe expands unsigned bytes to signed16 with (sample − 128) × 256 and preserves sixteen-bit values. It does not resample or remix channels. The WAV guide includes original playable PCM examples for comparing sample widths.

The five inspected cinematics

These are observations of the supplied corpus, not format limits. All five movies store interval −3333 and one compressed 22,050 Hz, stereo, unsigned-eight-bit audio track. Reading that track as mono sixteen-bit audio would misinterpret its samples.

MovieStored pixelsDisplay pixelsLogical frames
1640 × 240640 × 480599
2 · Intro640 × 240640 × 4804,058
3640 × 240640 × 480889
4640 × 240640 × 4801,841
5640 × 304640 × 3044,020

The pinned research records a black-box comparison against FFmpeg 8.1.1: all 11,407 RGB24 frames match frame MD5s, and each complete signed16 soundtrack matches its SHA256. The separate whole-track audio pass matches the same reference. An independently authored sixteen-bit stereo stream also matches (255, −32768), (257, 32767), (259, 32766), covering channel-base order and low-byte carry beyond the stock eight-bit corpus. No FFmpeg library is linked or required at runtime.

Reader support and open questions

Borrowed input must remain immutable. Frame pixels and per-packet audio may be reused on the next call. A whole-track audio pass walks indexed packets through the shared audio decoder without advancing video or decoding its blocks.

The shell’s movie sequencing belongs to [08 R-OOS-01 §4]. This guide owns the file bytes; it does not define a device or playback timing loop.

Sources and example files

Adapted from the owning ZRB research at the pinned sidebar revision. The complete source snapshot preserves evidence labels, public-format references, corrections, and implementation boundaries.

The moving rectangle, its decoded packet data, the palette-change clip, its decoded packet data, and all SVG frame views are original assets generated by scripts/zrb-example.py. No retail movie frames or audio are included. The generator decodes both binaries to check the rectangle’s motion, its changes of palette index, and the signal sweep’s palette-only transition, then produces the previews from that decoded data. Its reader covers these fixtures’ solid/retain subset, not general Smacker decoding.

python3 scripts/zrb-example.py --check
# Optional authoring check; requires the FFmpeg command-line tool:
python3 scripts/zrb-example.py --check --verify-ffmpeg

The optional check compares all 32 stored RGB24 frames across both clips, byte for byte, against FFmpeg. The normal website checks use only Python’s standard library and do not require FFmpeg, an engine checkout, or game data.