An atomic game · built with the generalized Atomic Intrinsic Integration method

Inside the Encoder

Modern video codecs do not “compress frames”. They spend a budget. Every single decision inside H.264, HEVC, AV1 and VVC is the same comparison — is this picture improvement worth what it costs in bits? — made a few million times a second. This game makes you make those decisions, under a budget, against a real encoder running in this page.

The sentence most people get wrong

“Higher bitrate means better quality, and the codec decides how good it looks.” Both halves are wrong in an interesting way. Quality per bit is set almost entirely by decisions the encoder makes — which parts of the picture to predict, from where, at what block size, and how much precision to destroy — and the video standard does not specify a single one of them. A standard defines only what a decoder must do with a bitstream. Two encoders claiming the same standard, at the same bitrate, can differ by an enormous visible margin, because one of them searched harder.

What is actually in this page

Not an animation of a codec. A codec. These levels run a real, small, block-based hybrid encoder written in the page: a separable 8×8 DCT, a deadzone quantizer on H.264's logarithmic step ladder, an Exp-Golomb run/level entropy coder from the CAVLC family, intra prediction from reconstructed neighbours, rate-constrained full-search motion compensation from reconstructed references, and a rate–distortion mode decision. Every number you will see — every bit count, every decibel — was produced by actually doing the work. Nothing is a stand-in formula tuned to make the graph move.

That matters here more than it usually does, because you are about to calibrate your intuition on these readouts.

The five things every block-based codec does

StepWhat it doesWhy it exists
PredictGuess the block from pixels already decoded — neighbours in this frame (intra) or a patch of an earlier frame (inter)Most of a picture is a repeat of something already sent
SubtractKeep only the residual: the part the guess got wrongYou only have to transmit your surprise
Transform8×8 DCT on the residualConcentrates the energy into a few coefficients
QuantizeDivide by a step and round; small coefficients become exactly zeroThe only lossy step, and the one that creates the compression
Entropy codeShort codes for common values, long ones for rareLossless packing of what is left

The decoder runs steps 5→1 backwards and is nearly brainless: it has no choices to make. All the intelligence, and all the variation between products, lives in the encoder's search. One consequence shapes everything else in this game: the encoder must predict from the decoded picture, not the original, because that is all the decoder will have. Any error it leaves behind is inherited by every frame that follows.

How to play

Seven levels, each introducing exactly one new decision. Each level states its goal and what it costs you to be wrong — a bit budget, a limited number of commits, or an irreversible choice. The tag at the top of each level tells you how much help you are getting:

scaffold present every readout is live while you move the lever · scaffold degraded readouts only after you commit · scaffold inverted you must predict the result before you see it · scaffold absent you are on your own

The help is removed on purpose and in that order. With the readouts permanently on, you can beat every level by trial and error and learn nothing — which is exactly the failure this game's design method was written to prevent. The last level gives you no readouts at all until the frame is already sent.


Level 1 of 7 · new decision: how much precision to destroy scaffold present

The quantizer — where the loss happens

One frame, no prediction at all: just transform, quantize, count bits. The quantizer divides every DCT coefficient by a step and rounds. Anything smaller than two-thirds of a step becomes exactly zero and costs almost nothing to send. QP sets that step, and the step doubles every six QP — so QP is a logarithmic dial, which is why the quality curve has a cliff in it.

Before you touch anything. Moving from QP 24 to QP 36 roughly halves the file. What does that cost in quality?

Original frame
What the decoder gets back
Error heat map — where the quality went. Detail dies first; flat sky survives.
One 8×8 block's coefficients. Dark cells were zeroed; top-left is the block's average brightness.
QP 26 · step —
—bits for the frame
—dB quality (PSNR)
—coefficients zeroed
3commits left
Goal. Send this frame in 7,000 bits or fewer while keeping quality at 33.5 dB or better.
What it costs to be wrong. Three commits. Run out and the level is lost — restart it and try again with what you learned.

Level 2 of 7 · new decision: what to throw the precision away from scaffold inverted

The transform — why the DCT comes first

Quantizing is just rounding. You could round the pixels directly and skip the transform entirely. Both paths below use the same quantizer and the same entropy coder, and the page searches for a step size that makes them spend the same number of bits. Only one thing differs: whether the rounding happens on pixels or on DCT coefficients.

Same bits, same coder, same quantizer. How big is the gap?

Rounding the DCT coefficients — —
Rounding the pixels — —
10000 bits

Commit a prediction first — the answer is hidden until you do.

A natural picture is mostly smooth, so in the DCT domain nearly all its energy lands in a handful of low-frequency coefficients and the rest is near zero — free to send. In the pixel domain every one of the 64 values is equally important, so an equal budget buys you 64 coarse numbers instead of 6 good ones. This is energy compaction, and it is the whole reason a transform is in the pipeline.

Level 3 of 7 · new decision: what to predict from scaffold present

Prediction — send the surprise, not the picture

So far every block has been coded from scratch. Real codecs almost never do that. They guess the block first — from the decoded pixels directly above and left of it, or from the same place in the previous frame — and then transmit only the residual: guess minus truth. A good guess makes the residual nearly flat, and a flat residual quantizes to almost nothing.

Five blocks are marked on the frame. They do not all want the same guess: one sits on a smooth gradient, one on a hard vertical edge, one on a moving object. Pick a mode for each.

Click a marked block to work on it. Green = already committed.
The previous decoded frame — all the encoder may predict from.
Truth
The guess
Residual — flat grey is free
What comes back
—bits for this block
—residual energy left
0bits spent of 330
0/5blocks committed
Goal. Commit all five blocks for 330 bits or fewer in total. Coding all five from scratch costs over 400 — there is no lazy way through.
What it costs to be wrong. A committed block is final — you cannot go back and recode it, exactly as a real encoder cannot. Overspend and the level is lost.

Level 4 of 7 · new decision: where in the past frame to look scaffold present

Motion — the surprise is somewhere else

The camera is panning and three objects are moving. Predicting a block from the same place in the previous frame is then a bad guess, but predicting it from the place that content came from is an excellent one. That offset is a motion vector, and the encoder must transmit it.

Drag the two sliders until the residual goes flat grey. Watch both bars: the orange residual bar falls as your aim improves, and the purple motion bar grows as the vector gets further from zero. The vector is not free.

Current frame. The marked block is the one you are coding.
Previous decoded frame. The box is where your vector is pointing.
Truth
Fetched from the past
Residual
Stacked cost of this block: residual bits plus motion vector bits.
Total cost = residual + motion vector.
0 px
0 px
—prediction error (SAD)
—residual bits
—vector bits
0bits spent of 230
0/4blocks committed
Goal. Code all four blocks for 230 bits or fewer. Standing still on all four costs over 600. One of the four is already predicted perfectly without moving — find out what a vector is worth there.
What it costs to be wrong. Commits are final.

Level 5 of 7 · new decision: how finely to carve the picture scaffold degraded

Partitioning — where one vector is not enough

Up to now every block had one vector. Real pictures are not that tidy: a 16×16 macroblock lying across the edge of a moving object contains two different motions, and no single vector describes both. So the encoder may split it into four 8×8 partitions, each with its own vector — four times the accuracy, four times the vectors to transmit.

Sixty macroblocks. Click the ones you think need splitting. Your live readouts are gone: you get the totals only after you press Encode, so you have to reason about where the motion disagrees instead of watching a number.

Click a macroblock to split it (shown divided into four). Click again to merge.
you split this one the encoder's own choice, revealed after you commit
—total bits
—of those, vector bits
—dB quality
0macroblocks split
3encodes left
Goal. Beat split-nothing on bits and on quality at the same time. Splitting everything will not do it.
What it costs to be wrong. Three encodes. Each one burns a try whether it helps or not.

Level 6 of 7 · new decision: the price of a bit scaffold inverted

J = D + λR — the one rule under all of it

Quantizer, prediction mode, vector, partition: four separate levers, and you have been choosing each by feel. An encoder does not. It scores every candidate with one number —

J = D + λ·R   distortion, plus λ times the bits

— and keeps the lowest. λ is the exchange rate between squared error and bits. Raise it and bits get expensive, so the encoder starts refusing to pay for detail and falls back on cheaper modes. Lower it and it will buy anything. Changing λ does not change the quantizer at all; it changes what the encoder is willing to spend.

At a fixed QP, you multiply the price of a bit by twenty. Which mode takes over the frame?

Rate-distortion plane: bits spent against quality obtained, with your committed operating points.
Every encode you commit lands here. The green box is the target. Up is better, left is cheaper — you want the top-left.
Shown only after you commit — not yet encoded
QP 30
×1.00 of the textbook value
—bits
—dB
—mode mix
5encodes left
Goal. Land a point inside the green box: 2,800 bits or fewer at 38.0 dB or better. Your starting point misses it by about 400 bits, and no amount of λ alone will ever reach it — λ is not a quality dial.
What it costs to be wrong. Five encodes, and the picture stays hidden until each one is spent.

Level 7 of 7 · everything at once scaffold absent

Ship the shot — rate control, frame types, and a channel that will not wait

Twenty-four frames, going out over a link that delivers a fixed 3,200 bits per frame interval and no more. The decoder plays from a buffer. Spend more than arrives and the buffer drains; empty it and playback stalls — the failure every real rate controller exists to prevent.

You choose, one frame at a time and with no take-backs:

No live readouts. You find out what a frame cost after it is sent. Somewhere in this shot the camera cuts to a different scene; you will not be told when.

Before each frame: how expensive will it be?

A correct call earns you 600 bits of buffer. A wrong one costs nothing but the information.

Per-frame bit cost and the decoder buffer level across the shot.
Orange bars: bits spent per frame. Green line: buffer level — if it touches the floor, playback stalls.
Frame 0 — what you are about to send
Last decoded frame — what the viewer actually saw
QP 30
10,000buffer (stall at 0)
—last frame cost
—dB of that frame
0/2frames below 34 dB
3delay budget left
—average dB so far (need 36.6)
Goal. Get all 24 frames out with the buffer never reaching zero, no more than two frames below 34 dB, and an average of 36.6 dB or better across the shot. There is no separate bit cap — the channel is the budget. Note what that implies: a quantizer setting that is right for the easy frames is wrong for the hard ones, and finding that out frame by frame is the entire job of a rate controller.
What it costs to be wrong. Everything is final. A frame coded badly is also the reference the next frame predicts from, so its error is inherited — the bits you save now are borrowed, not earned. Empty the buffer and the run is over.


Debrief

What you now know, and what killed the thing you believed

What people believeWhat actually happened
More bitrate means better quality. Only along one encoder's curve. In level 2 two encodes spent identical bits and differed by about twenty decibels, because one of them transformed first. The bitrate is an input; quality per bit is the output of a search.
The codec decides how good it looks. The standard defines only the decoder. Every choice you made in levels 3–6 — mode, vector, partition, λ — is unspecified by H.264, HEVC or AV1 and is where encoders actually differ. This is why two “H.264 1080p 5 Mbps” files can look nothing alike, and why a slower preset looks better at the same bitrate: it searches more of exactly these decisions.
Each frame is a picture. Most frames are corrections. A P frame here costs roughly a third of an I frame, and the great majority of its macroblocks ended up as Skip — literally “the previous frame, shifted; I have nothing to add”.
Motion vectors are free. Level 4's flat-sky block: a perfect vector reduced the residual by almost nothing and the vector itself cost more than it saved. This is why encoders minimise SAD + λ·(vector bits) rather than SAD, and why a “more accurate” motion search can make a file bigger.
Smaller blocks are more accurate, so split more. Splitting everything in level 5 cost more bits and barely moved quality, because four vectors describing one single motion is three wasted vectors. Splitting pays only where motion genuinely disagrees inside a block — object boundaries. Every partition choice is side information someone has to transmit.
Lower the quantizer to make the file smaller. That is one of two dials and it is the blunt one. λ changes what the encoder is willing to buy without changing precision at all. In level 6 neither dial alone reached the target box.
Saving bits on a frame is free if it looks acceptable. It is a loan. Every later P frame predicts from your decoded output, so its error propagates until the next I frame pays it off — which you felt directly as drift in level 7.

The transferable rules

  1. Every decision is J = D + λR. Mode, vector, partition, quantizer, frame type — all of it is one comparison, repeated millions of times a second. If you only remember one thing, remember that an encoder is a search algorithm with a price list.
  2. Transmit surprise, never content. Predict as well as you can, then send only the error. Everything else in the pipeline exists to make the error cheap.
  3. Side information is information. Vectors, modes and split flags are paid for in the same currency as picture detail, which is why more flexibility is not automatically better.
  4. The encoder must live inside the decoder's head. It predicts from the reconstruction, never the original. That single constraint produces drift, error propagation, and the need for I frames.
  5. Rate control is a buffer problem, not an average. Hitting a target bitrate on average while emptying the buffer at the scene cut is still a stall.
  6. Newer standards are bigger menus, not smaller files. HEVC and AV1 add partition shapes, prediction modes and transform sizes — more candidates for the same J comparison. The gain is real and it is paid for in encoding time, which is why presets exist.

What this model leaves out

The fidelity contract, stated plainly, because a caricature presented as the truth would quietly miscalibrate you.

Simulated honestly: the separable 8×8 DCT-II; a deadzone quantizer on H.264's logarithmic step ladder (the step doubles every 6 QP); an Exp-Golomb run/level entropy coder of the CAVLC family; intra DC, vertical and horizontal prediction from genuinely reconstructed neighbours; integer-pel rate-constrained full-search motion estimation against reconstructed references; bidirectional prediction by averaging two references; macroblock partitioning; Skip; the J = D + λR mode decision with λ = 0.85·2(QP−12)/3; a leaky-bucket decoder buffer. Error propagation and drift are not scripted — they emerge, because the references really are the reconstructions.

Not here at all: colour — this is luma only, and chroma is roughly another 25% of the bits and its own prediction machinery. CABAC, whose context-adaptive arithmetic coding is worth 10–15% over the CAVLC-style coder used here. Sub-pixel motion at half and quarter resolution with interpolation filters, worth a great deal more than you might guess. Deblocking and SAO filters, which is why your reconstructions look blockier than real ones at the same bitrate. Multiple reference frames, weighted prediction, intra modes beyond three of the nine in H.264, transform sizes other than 8×8, trellis quantization, adaptive quantization by perceptual importance, and rate–distortion optimised quantization. Everything the last fifteen years added in HEVC, VP9, AV1 and VVC: quadtree and multi-type-tree partitioning, 35-to-67 intra directions, compound and warped motion, transform skip, film grain synthesis. Real sequences, real camera noise, and real perceptual metrics — PSNR is used here because it is honest arithmetic on the signal, but it is a poor model of what an eye objects to, and no serious codec comparison relies on it alone. And the frames are 160×96 synthetic images with exact integer motion, which makes motion estimation far easier than it is in life.

What survives all of that is the shape of the system, and the shape is what you came for.

Where to go next

Built with the generalized Atomic Intrinsic Integration method — modelled on the structured methodology for designing games for conceptual understanding by Echeverría, Barrios, Nussbaum, Améstica & Leclerc, generalized from physics to arbitrary systems. One self-contained file, no network, no tracking, no storage. All imagery is generated procedurally from a fixed seed, so your run is identical to everybody else's.