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
| Step | What it does | Why it exists |
|---|---|---|
| Predict | Guess 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 |
| Subtract | Keep only the residual: the part the guess got wrong | You only have to transmit your surprise |
| Transform | 8×8 DCT on the residual | Concentrates the energy into a few coefficients |
| Quantize | Divide by a step and round; small coefficients become exactly zero | The only lossy step, and the one that creates the compression |
| Entropy code | Short codes for common values, long ones for rare | Lossless 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.
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.
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.
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.
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.
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.
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:
- I — code it from scratch. Expensive, but it owes nothing to the past and lets a viewer join here.
- P — predict from the previous decoded frame. Usually several times cheaper.
- B — predict from the previous and the next frame, averaged. Cheaper still, but the encoder must code the future frame first, so it commits two frames at once and adds a frame of delay. You have three frames of delay for the whole shot and no more.
- QP — the quantizer, as in level 1.
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.
A correct call earns you 600 bits of buffer. A wrong one costs nothing but the information.
Debrief
What you now know, and what killed the thing you believed
| What people believe | What 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
- 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. - 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.
- 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.
- 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.
- 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.
- Newer standards are bigger menus, not smaller files.
HEVC and AV1 add partition shapes, prediction modes and transform sizes —
more candidates for the same
Jcomparison. 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
- Run
ffmpeg -i in.mp4 -c:v libx264 -crf 23 -preset veryslow out.mp4against-preset ultrafastat the same CRF. Same standard, same quantizer, very different sizes: that difference is entirely the search you just did by hand. ffprobe -show_frameswill printpict_typeandpkt_sizeper frame. The spikes are I frames and scene cuts — level 7, on real video.- CRF is not a bitrate. It is approximately a fixed λ: a constant price for a bit, letting the bitrate go wherever the content needs. That is why it holds quality steady and why its file sizes are unpredictable.
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.