Vizuara AI Labs · World Models

We reproduced Dreamer 4
on CoinRun

A world model trained entirely from scratch on data we generated ourselves — then we open-sourced the whole recipe, including the five things we had to fix to make it work.

40.41
Tokenizer PSNR
32.19
End-to-end FVD
144
Frames, no collapse
~$150
One GPU

The model dreaming

Every clip below is generated frame by frame by the neural network — no game engine. Each one is given just 4 real frames of context, then hallucinates the next 144 frames on its own. Notice it keeps each level's art style — night ice, desert, pastel — and never dissolves.

These are unedited samples from the held-out test split — levels the model never trained on.

How we built it

The long version — every design decision, every number, and every thing that broke. Roughly a fifteen-minute read.

1 · What a world model actually is

A world model is a neural network that has learned to be a game engine. You give it the current frame and a button press; it gives you the next frame. Do that repeatedly — feeding each prediction back in as the new input — and you get a video you can steer. There is no physics code, no collision detection, no sprite sheet. Every pixel is a prediction.

World model architecture: tokenizer, dynamics, decoder
Fig 1 — The three parts of a world model. A tokenizer compresses each frame into 32 continuous latents, an action-conditioned transformer predicts the next set given what you pressed, and the decoder turns them back into pixels. The loop is what makes it a simulator, not a video model.

The technical name for running the model forward like this is a rollout, and it is where the difficulty lives. One-step prediction is easy: the model always sees a real frame as context during training. In a rollout it sees its own output, which contains small errors, which push it slightly off the distribution it was trained on, where its predictions get slightly worse. That feedback loop is called compounding error, and it is the standard way world models fail — not with a crash, but by slowly dissolving into mush.

Compounding error in autoregressive rollouts
Fig 2 — Four real frames of context, then 144 frames where the model feeds on itself. Coherence at the end is the real test, not one-step accuracy.

2 · Attempt one, and why it failed

Our first world model followed Genie (DeepMind, 2024), built on the open GenieRedux reimplementation. It trained. It produced things that looked vaguely like a platformer. It was also close to unusable, for two reasons that turned out to be structural rather than bad luck.

The controls did nothing. Genie's headline idea is that it learns its action vocabulary without labels — it watches unlabelled video and infers a small set of discrete actions. That is elegant, and it is what lets Genie train on internet video. But at our scale the model simply learned to ignore the action channel. We measured this with ΔPSNR — generate once with the true action, once with a different action, and see how much the futures differ. We got approximately zero. Pressing a key changed nothing.

It ran at about one frame per second. Genie's dynamics model is MaskGIT, which fills in a frame's tokens over roughly 25 refinement passes. Twenty-five forward passes per frame is not a tuning problem you can optimise away; getting to real time would have been a separate research project in distillation.

Two structural problems, not two bugs. That is what sent us looking for a different method.

3 · Choosing Dreamer 4

We switched to Dreamer 4 (Hafner, Yan & Lillicrap, 2025), using the Open Dreamer JAX implementation by Diego Marti Monso, Francesco Sacco and Edward Hu. It differs from Genie at essentially every layer, and each difference maps onto one of our problems.

Genie versus Dreamer 4
Fig 3 — Discrete codes and guessed actions, versus continuous latents and real actions.

Continuous latents instead of discrete codes. Genie's tokenizer is a VQ-VAE: each frame becomes a grid of integers drawn from a codebook of 1,024. Dreamer 4's tokenizer has no codebook at all — it is a masked autoencoder that projects to a low-dimensional bottleneck and squeezes it through a tanh. Nothing is quantised, so nothing is lost to rounding.

Real actions instead of inferred ones. The dynamics model takes the actual action as an input embedding. This sounds like a step backwards — it means you need action-labelled data, which is exactly what Genie was designed to avoid. But we generate our own data, so we know every action for free. Giving up Genie's core advantage cost us nothing and bought us control.

Flow matching with shortcut forcing instead of MaskGIT. This is the one that solves speed, and it deserves its own section.

4 · The data engine

Our first dataset was 122,000 frames. The recipe we were now following assumes roughly ten million. We had been training on about one percent of the intended data — which, in hindsight, explains more of attempt one's weakness than the architecture did.

Data generation pipeline
Fig 4 — procgen rollouts to ArrayRecord shards. Every frame is ours; nothing is scraped.

We rebuilt it around OpenAI's procgen CoinRun (MIT licensed), fanned out across many CPU containers on Modal. Ten thousand episodes survived the length filter out of about 31,000 attempted, chunked into 160-frame records: 9,600,000 training frames, plus 480,000 each for validation and test. Every record carries the frames, the action taken at each step, and the reward.

Two details matter more than they look. First, we generate rather than scrape — there is no copyright question anywhere in this project, and we get perfect action labels as a side effect. Second, the loader does reward-biased sampling: half the training windows are deliberately chosen to contain a moment where the agent reached a coin. Without that, most random 64-frame windows of a random policy are just aimless wandering.

5 · Stage one — the tokenizer

The tokenizer is trained first and then frozen. Its only job is to compress a frame into latents and reconstruct it faithfully. Everything downstream inherits its ceiling: the dynamics model can never produce a sharper frame than the tokenizer can decode.

The one real judgement call here was how many latents per frame. Open Dreamer's default is 512, but that number is sized for Minecraft at 360×640, which is about 920 patches per frame. CoinRun at 64×64 with patch size 8 has only 64 patches. Using 512 latents would have been an eightfold expansion rather than a compression, and would have inflated the dynamics model too, since its token count scales with the number of latents. We used 32 — a 24× compression, and divisible by the dynamics model's packing factor of 2.

Tokenizer PSNR versus published baselines
Fig 5 — Measured reconstruction quality. 10,000 steps, 1×H100, 95 minutes, about $6.

It reached PSNR 40.41, with LPIPS at 0.0015 — perceptually near-lossless. For reference, GenieRedux reports 38.25 on CoinRun and the Genie paper reports 35.7. Those are not perfectly like-for-like comparisons, but it is comfortably in the right range, and the reconstructions show crisp platforms, sprites and distinct level art styles.

6 · Stage two — dynamics and shortcut forcing

The dynamics model is the world model proper: a block-causal transformer that takes the latents of past frames plus the action, and predicts the next frame's latents. Ours is about 1.57 billion parameters.

It is trained with flow matching — instead of classifying discrete tokens, it learns a velocity field that transports noise into data. Sampling normally means integrating that field in many small steps, which is slow. Shortcut models (Frans, Hafner, Levine & Abbeel) fix this by conditioning the network on the step size as well as the noise level, and training a self-consistency rule:

One step of size 2d must land where two steps of size d land.
Shortcut models: many small steps versus few large steps
Fig 6 — Same path, far fewer steps. This is what makes real-time generation possible.

The consequence is that few-step generation is learned inside the same training run. There is no separate distillation phase, which is precisely the project we would have had to undertake with Genie. The schedule is two-phase: pure flow matching first, so the model learns the field, then the bootstrap objective is switched on so it learns to take big jumps along it.

Two-phase training schedule
Fig 7 — The two-phase schedule. For the first 40,000 steps the model learns only the flow field. After bootstrap_start it also learns to take big steps — that second phase is what buys few-step, real-time-capable generation.

We ran 80,000 steps with the switch at 40,000. That is a rescaling of the published 200k/100k schedule, and it was forced by a hard constraint: Modal caps a single job at 24 hours, and we measured about 1.05 iterations per second. At 200,000 steps we would have hit the wall at roughly step 90,000 — before ever reaching the bootstrap phase, and therefore without the one capability we changed architectures to get. Halving the schedule preserved the phase structure within the budget.

Measured training losses across both phases
Fig 8 — Our actual logged losses. boot_mse is exactly zero until step 40,000, then appears and falls as the shortcut objective engages.

7 · Five things that broke

Open Dreamer's write-up describes CoinRun as its single-GPU starting point. In practice that path does not run end to end as shipped. We hit five distinct breakages, all found by reading source rather than guessing:

  • Their CoinRun generator crashes. It passes serialization_format="pickle" to a ShardWriter whose constructor takes no such argument.
  • Writer and reader disagree. That writer emits msgpack; the CoinRun reader calls pickle.loads. The pickle path was lost in a refactor. We wrote records in the schema the reader actually parses.
  • No CoinRun tokenization script. Theirs hardcodes the Minecraft transform. We routed around it — the dynamics trainer supports raw video and encodes on the fly.
  • The dynamics trainer asserts the Minecraft action space. CoinRun is 0 binary / 16 categorical, so it fails immediately. The model itself is generic; the assert was a stale guard.
  • The evaluation script cannot write its own videos. It calls imageio's pyav plugin, which their dependencies do not include.

Alongside those, the infrastructure produced its own lessons. The one worth repeating: putting jax[cuda12] on an nvidia/cuda base image makes JAX fail to find cuSPARSE and silently fall back to CPU. It does not error. It just runs about a hundred times slower. We caught it only because the smoke test asserted on jax.devices() instead of trusting that a GPU was requested.

8 · Measuring it honestly

We evaluated on the held-out test split: 64 rollouts, each given 4 real context frames and asked to generate 144 more. FVD compares distributions of video clips, and the useful thing is that it can be decomposed.

FVD decomposition
Fig 9 — Splitting the error between the tokenizer and the dynamics model.

Of the 32.19 end-to-end score, roughly half comes from the tokenizer's reconstruction loss and half from the dynamics model's prediction error. Neither component is the sole bottleneck — which tells us where effort would go next: higher resolution improves the first term, more dynamics training or scale improves the second.

Visually, the rollouts hold together for the full 144 frames. Each level keeps its own art style, geometry stays valid, and nothing dissolves. They do not match the ground truth frame for frame — after 144 autoregressive steps from four real frames they are dreaming a plausible CoinRun level rather than replaying the recorded one. For a world model that is the expected and correct behaviour; the question is whether it stays coherent, and it does.

One thing we have not yet measured is controllability. Everything above shows the model simulates CoinRun well. It does not yet prove that pressing right makes the character go right — that requires the action-swap test, and until we run it we are not claiming the control problem is solved. It is the single most important number still outstanding.

Results

Measured on the held-out test split: 64 rollouts, each generating 144 frames from 4 real context frames — scored by FVD as 576 sixteen-frame clips.

Tokenizer reconstructionPSNR
Genie paper (DeepMind)35.70
GenieRedux (INSAIT, CVPR'25)38.25
Ours40.41
FVD decomposition
Where the error comes from — roughly half tokenizer, half dynamics.
FVD componentScoreMeaning
original → tokenizer recon16.59compression ceiling
recon → prediction23.29dynamics only
original → prediction32.19end to end
Being precise: the Genie paper reports FVD 54.8 on its own curated platformer set. Ours is not a like-for-like comparison — different data, resolution and protocol — so we are not claiming to beat it. We report the decomposition so the numbers can be checked against ours directly.

Five things we had to fix

The reference implementation's CoinRun path does not run end to end as shipped. Each of these cost us real time. All five fixes are in our harness, written up line by line in the failure log.

FIX 01

Generator crashes

The CoinRun data generator passes a keyword its own writer does not accept.

FIX 02

Writer ≠ reader

The writer emits msgpack; the CoinRun reader expects pickle. The write path was lost in a refactor.

FIX 03

No CoinRun tokenization

The tokenize script is Minecraft-only. We route around it by encoding on the fly during training.

FIX 04

Hardcoded action space

Training asserts the Minecraft action space. The model is generic — the assert was a stale guard.

FIX 05

Missing video backend

Evaluation writes MP4 via a plugin that is not in the dependency list, so it fails at the very last step.

What we are not claiming

This is not the first open-source world model. Cosmos, Oasis, Muse, DIAMOND, GenieRedux and the Dreamer 4 implementation we built on are all open. We are standing on that work, not replacing it.

Control is not yet measured. These rollouts use ground-truth actions. They show the model simulates the world well; they do not yet prove that pressing a key steers it. That test is next, and we will publish the number either way.

It is 64×64 CoinRun. Sharp for the resolution, but small and stylised. The visual ceiling here is the game's own art, not the model.

Credits & prior work

This project stands on other people's work. The method, the implementation we built on, the environment, and the tooling are all theirs — our contribution is the CoinRun reproduction, the fixes, and the measurements.

Open Dreamer — the implementation this whole project is built on.

Diego Marti Monso, Francesco Sacco and Edward Hu (next-state/open-dreamer), with contributions from Dere-Wah. Their JAX/Flax implementation of Dreamer 4 — the configs, the training scripts, the architecture — is what we ran. We reproduced their CoinRun setting; we did not invent it.

@misc{marti2026opendreamer,
  title  = {How to Train a Frontier-level World Model},
  author = {Marti Monso, Diego and Sacco, Francesco and Hu, Edward},
  month  = {jul}, year = {2026}, publisher = {Zenodo},
  doi    = {10.5281/zenodo.21475232},
  url    = {https://next-state.github.io/open-dreamer/}
}
METHOD

Dreamer 4

Danijar Hafner, Wilson Yan, Timothy Lillicrap.
arXiv:2509.24527 — the causal tokenizer and action-conditioned dynamics we trained.

KEY TECHNIQUE

Shortcut models

Kevin Frans, Danijar Hafner, Sergey Levine, Pieter Abbeel. ICLR 2025.
arXiv:2410.12557 — conditioning the flow on step size, which is what makes few-step generation possible.

THE ORIGINAL IDEA

Genie

Jake Bruce et al., Google DeepMind. ICML 2024.
arXiv:2402.15391 — generative interactive environments, and the tokenizer PSNR we compare against.

BASELINE

GenieRedux

Nedko Savov, Naser Kazemi, Mohammad Mahdi, Danda Pani Paudel, Xi Wang, Luc Van Gool (INSAIT). CVPR 2025.
insait-institute/GenieRedux — our first build, and the 38.25 PSNR reference.

ENVIRONMENT

Procgen / CoinRun

Karl Cobbe and collaborators, OpenAI. MIT licensed.
openai/procgen — every frame of our dataset was rendered by it.

INFRASTRUCTURE

Tooling

Modal (GPUs), JAX & Flax, Optax, Grain, ArrayRecord, ott-jax (optimal transport), the Muon optimizer, LPIPS and FVD for evaluation, and Remotion for these videos.

Licensing — please read before you clone. Open Dreamer currently ships an explicit all rights reserved notice: “No license or permission is granted … without the prior written consent of the copyright holders,” with a note that it is provisional and a formal licence is expected. Out of respect for that, our repository contains none of their code and no weights derived from itopen-dreamer/ is gitignored, and you clone it yourself under their terms.

What we release, under MIT, is our work: the Modal harness, the procgen data engine, all five fixes, the figures, the rollouts and the full failure log. We have asked the authors about licensing and will expand the release if and when one permits it.

Reproduce it

Our whole harness is one file plus configs, MIT licensed and on GitHub. Two clones, a Modal account, and roughly $150 on a single GPU takes you from nothing to a dreaming world model.

# our harness — MIT
git clone https://github.com/RajatDandekar/dreamer4-coinrun.git
cd dreamer4-coinrun

# the implementation we trained — their licence applies, see below
git clone https://github.com/next-state/open-dreamer.git
pip install modal && modal setup

modal run --detach modal_dreamer.py::gen_data                                   # 9.6M frames, ~$15
modal run --detach modal_dreamer.py::train_tokenizer --steps 10000 --batch 32   # ~95 min, ~$6
modal run --detach modal_dreamer.py::latent_stats
modal run --detach modal_dreamer.py::pipeline                                   # dynamics + eval

Two things that will bite you. Use an H200, not an H100 — batch 16 OOMs by 26 GiB. And stay on a single GPU: multi-GPU deadlocked on NCCL rendezvous for us while looking perfectly healthy at $36/hr.

Dynamics and eval are bundled into one pipeline job because Modal caps a single job at 24 hours — that cap is also why the schedule is 80k/40k rather than the published 200k/100k.

In the repo

procgen data engineModal training harness all 5 fixes64 rollouts figures + training logsfull failure log

Deliberately not in the repo

Open Dreamer's codetrained weights

Cite this

If this was useful, please cite the reproduction — and the work underneath it. The Open Dreamer entry is in credits above.

@misc{vizuara2026dreamer4coinrun,
  title  = {Training a Dreamer 4 World Model from Scratch on CoinRun},
  author = {{Vizuara AI Labs}},
  year   = {2026},
  url    = {https://github.com/RajatDandekar/dreamer4-coinrun}
}

The method is Dreamer 4 (Hafner, Yan & Lillicrap) and shortcut models (Frans, Hafner, Levine & Abbeel). Please cite those too.