Pangram verdict · v3.3
We believe that this text is a mix of AI and human-written content.
AI likelihood · overall
MixedArticle text · 1,066 words · 6 segments analyzed
Retro GamingComputer ScienceVideogamesVideo Codec7 min readJul 30, 2026--I like porting DOS-era games to the Atari ST — a home computer launched in 1985 and mostly obsolete by 1995, the year Westwood’s Command & Conquer arrived. When I started a port of that RTS classic, cutscenes were not on the critical path. The game core came first. Experienced players often skip the videos anyway, and it was not obvious an ST port even needed them.Command & Conquer’s iconic 1995 intro video, rendered in 16 colors using the STV codec. CRT filter for aura.What was obvious: Westwood’s video format (VQA, for Vector Quantized Animation) was never going to work out of the box on a low-end ST. Designed for the VGA graphics of its day, VQA videos are 320×200 pixels, 256 colors, and run at 15 frames per second. The ST, on the other hand, supports only 16 colors and uses a more complicated video memory layout. I wrote more about that while porting DOOM to the ST. It can’t display VQA videos directly; they’d have to be converted using a process called “chunky-to-planar” (c2p). Achieving 15 fps would be out of reach for an 8 MHz Atari ST, so I did the wise thing and decided not to embark on such a foolish endeavor.Except I’m really bad at resisting temptations. The idea of FMV on an Atari ST kept creeping into my thoughts until I finally gave in.
That is how STV started — less a product requirement than an excuse to squeeze an old CPU the way hackers used to.Borrow what fits, redesign what doesn’tI studied Westwood’s VQA and kept the useful idea: a codebook of tiles, with frames mostly sending indices into that dictionary.
Beyond that, the ST wanted different geometry.VQA’s small tiles (4×2) looked like a poor fit. Two observations pushed me toward 8×8.From earlier experiments I already knew that eight horizontal pixels are a natural unit on this machine. The 68000 has an instruction called `movep` that can update those eight pixels as four bytes, without the bit twiddling a naive planar write would need. Height eight made sense out of practical considerations: a 200-pixel column holds 25 tiles, which fits in a single 32-bit skip mask. Fitting things into a single register is good if you want performance on old hardware.Each codebook entry is 32 bytes, ready to be copied directly into video RAM using the Motorola 68000’s famous **movep** instruction. Encoding happens on a modern host; the Atari’s job is simply to throw these tiles on screen as fast as possible.How a frame is encodedGory details. Jump ahead if you prefer the narrative.Let’s start with the main idea: A frame is drawn column by column. For each column the stream carries a 32-bit skip mask, then 16-bit codebook indices only for the tiles that change, compared to the previous frame:column 0 column 1 … column 39┌───────────┐ ┌───────────┐ ┌───────────┐│ skip mask │ │ skip mask │ … │ skip mask ││ (uint32) │ │ (uint32) │ │ (uint32) │├───────────┤ ├───────────┤ ├───────────┤│ idx,… │ │ idx,… │ … │ idx,… ││ (uint16; │ │ (uint16; │ │ (uint16; ││ changed │ │ changed │ │ changed ││ only) │ │ only) │ │ only) │└───────────┘ └───────────┘ └───────────┘Fullscreen video has no spare cycles for copying a whole framebuffer every frame. So the player uses ping-pong buffering: two screens — front and back — flipped on vertical blank. A skipped tile keeps what is already in the back buffer, which is not the previous frame but the one before that — frame N−2. The encoder needs to take this into account but the player can work very efficiently.I didn’t know at first what the real bottleneck would be. At the bit rates I used, disk streaming turned out not to be an issue — I wasn’t streaming from floppy, after all. What mattered was drawing tiles as cheaply as possible. The hot path is mostly moving memory, and bandwidth is tight: avoid redundant reads and writes, and keep as much as you can in registers. It’s one of those times where smart algorithms lose to dumb assembly code well-adapted to the hardware.Press enter or click to view image in full sizeBlock artefacts, normally something we avoid. They originate from assembling the picture from codebook of tiles, some of which match better than others.A codebook that adaptsA static codebook would not survive a cutscene. Faces move, lighting shifts, logos slam onto the screen — the dictionary has to learn new tiles as the clip unfolds. I knew a dynamic codebook was necessary. By default it holds 2048 tiles (conveniently, exactly 64 KB at 32 bytes per tile).
What I did not know was the update budget: how many of those entries could the player afford to replace each frame?On the ST, updating the codebook is relatively inexpensive. Copying a few tiles into RAM each frame is negligible compared to the cost of rendering the video itself. As a result, the limit of about 32 updated tiles per frame was chosen less to protect CPU time, and more to control compression: each replacement uses up bitstream bandwidth, so the encoder must prioritize updates for the greatest quality.
In practice, even higher rates — up to 128 updated tiles per frame — are possible, and videos remain smooth.The intro video, the 16-color palette, and the codebook in a single visualization. Notice the palette changes and how tiles are replaced.Audio: stop transcoding on the targetSound went through a similar reality check. While porting C&C I first tried Westwood’s ADPCM-compressed audio. Decoding it on the ST was possible, but it maxed out the low-end machines.The easier path won: 12.5 kHz, 8-bit mono PCM, played straight through the Atari STE’s DMA sound hardware with no transcoding on the target. The encoder does the work; the player mostly shoves samples into a DMA buffer and lets the hardware run. In practice, there’s also volume adaption and mixing of samples playing at the same time, but the CPU load is quite low.Keep the player dumb, make the encoder smartThe format and the player have to stay dead simple.
That does not mean the encoder has to be. Most of the interesting engineering lives on the host side, where you can afford YUV math, frequency-domain metrics, and codebook policy that would be absurd at 8 MHz.A few of the tricks:Work in YUV, not RGB.