A conserved quantity inside a pixel-trained world model

August 27, 2026 ยท View on GitHub

Code, run logs and paper source for the DreamerV3 study. A frozen world model trained only to predict pendulum video contains a scalar that its own transition map holds constant; the search returns nothing when the same model is trained with damping; and enforcing the recovered quantity during imagination lowers the model's rollout error.

Everything in runs/ is the output of the script named beside it in the table below. The figures in the paper are generated from those files and nothing else, so every number is checkable without a GPU.

Setup

git clone --recurse-submodules https://github.com/Zarand3r/world-model-invariants
cd world-model-invariants
uv sync

The DreamerV3 reference implementation is a submodule pinned at one upstream commit. The six checkpoints and three rendered datasets are too large for git and live at huggingface.co/Zarand3r/world-model-invariants; scripts/fetch_assets.py downloads them into runs/ and checks every file against a sha256 committed in docs/ASSETS.json, so a reproduction that starts from a download is still verifiable.

uv run python scripts/fetch_assets.py     # 359 MB; --skip-data to regenerate the .npz instead

Nothing below the figure regeneration needs those assets.

The probe bench

An interactive rig over the frozen models: steer the recovered invariant by hand, enforce it during imagination, and watch the predicted video. viz/README.md has the detail.

./viz/run.sh          # http://127.0.0.1:8130

Regenerating the figures (no GPU, seconds)

uv run python paper/make_figures.py   # reads runs/*.json, writes paper/figures/*.pdf
cd paper && tectonic -X compile main.tex

make_figures.py prints every number it plots. Compare its output against the paper.

Running the tests

uv run pytest tests/ -q                                    # 40 tests, ~6s
uv run pytest tests/ --ignore=tests/test_timing_convention.py   # 37 tests, no submodule needed

test_timing_convention.py checks our adapter against the reference DreamerV3 implementation, so it needs the submodule; one of its cases also needs runs/dreamer_ref_s3.pt and skips without it.

Reproducing from scratch (GPU, a few hours)

docs/REPRODUCE.md has the full procedure, including the pinned upstream commit of the DreamerV3 implementation. In outline:

uv run python scripts/make_pendulum_pixels.py               # render the dataset
uv run python scripts/make_pendulum_pixels.py --zeta 0.03 \
       --out runs/pendulum_pixels_damped.npz                # the dissipative arm
uv run python scripts/train_dreamer_pendulum.py --seed 3 --out runs/dreamer_ref_s3.pt
uv run python scripts/run_dreamer_extraction.py --ckpts runs/dreamer_ref_s3.pt --ld 12

Training is capped by wall clock rather than step count, so a re-trained model lands near but not exactly on the committed numbers. The released checkpoints do reproduce them exactly: on the hosted dreamer_ref_s{3,4,5}.pt, the command above returns |rho|_E of 0.973, 0.967 and 0.975, matching runs/dreamer_extraction_prereg_ld12.json to 1e-9.

What produced what

paper claimscriptrun logpre-registration
recovery at LD=12run_dreamer_extraction.py --ld 12dreamer_extraction_prereg_ld12.jsonD36
untrained nullrun_dreamer_extraction.py --untraineddreamer_untrained_null.jsongauge.decodability docstring
refusal on a damped modelrun_dreamer_refusal.pydreamer_refusal.jsondocs/DISSIPATIVE_PREREG.md
the edit, 20-draw nullrun_dreamer_edit.pydreamer_edit.jsondocs/S4_PREREG.md
extraction-dimension sweeprun_dreamer_ld_sweep.pydreamer_ld_sweep.jsonD36
where the energy livesrun_dreamer_residual_decomp.pydreamer_residual_decomp.jsonD37
what the correction acts onrun_dreamer_leverage.pydreamer_leverage.jsonD46
the same, on damped modelsrun_dreamer_leverage.py --ckpts runs/dreamer_damped_s*.ptdreamer_leverage_damped.jsonM26
flow-generation ablationrun_pairing_ablation.pypairing_ablation.jsonD47
leverage stability by horizonrun_leverage_stability.pyleverage_stability.jsonD48
edit compactnessrun_edit_compactness.pyedit_compactness.jsonD49
frequency weighting (unresolved)run_dreamer_nested_kappa.pydreamer_nested_kappa.jsonD38

Notes on reading the logs

  • Per-seed values, not just medians. Two results in this paper turn on seed-level disagreement that a median hides: the intervention's specificity holds on two models of three, and the low-variance energy claim inverts on one seed. Both are visible in the raw JSON.
  • The random-law arm is a distribution. dreamer_edit.json holds 20 draws per checkpoint under B_conservative_random. An earlier version drew one polynomial and reused it across models; the figures and the paper now report the null's median and each model's percentile inside it.
  • Two error metrics are not interchangeable. The intervention here is scored on pixel error against held-out video. The precursor study described in the paper's appendix scored trajectory error in a two-dimensional state space, so its percentages are not comparable with these.

Layout

latent_noether/   extraction machinery: PCA/effective-rank basis, polynomial invariants,
                  the joint f = B grad C fit, and the DreamerV3 adapter
scripts/          one script per experiment; each prints its own verdict
runs/             the JSON output of those scripts, as committed
paper/            LaTeX source, figure generator, generated figures
docs/             reproduction instructions and the pre-registrations
tests/            37 tests over the extraction machinery