diff --git a/assets/entropy.svg b/assets/entropy.svg
new file mode 100644
index 0000000..cf0c11c
--- /dev/null
+++ b/assets/entropy.svg
@@ -0,0 +1,126 @@
+
diff --git a/engine/generate_entropy_svg.py b/engine/generate_entropy_svg.py
new file mode 100644
index 0000000..c053295
--- /dev/null
+++ b/engine/generate_entropy_svg.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3
+"""Generate assets/entropy.svg — a SMIL-animated entropy-reversal field.
+
+120 particles loop from a seeded chaos cloud into a (2,5) torus-knot
+lattice, hold, and scatter back. State (bits sorted by visitors, chaos
+generation) comes from state/demon.json. Stdlib only; deterministic for
+a given generation so regenerated output is diffable.
+"""
+
+import hashlib
+import json
+import math
+import os
+import sys
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+STATE_PATH = os.path.join(ROOT, "state", "demon.json")
+OUT_PATH = os.path.join(ROOT, "assets", "entropy.svg")
+
+WIDTH, HEIGHT = 880, 260
+MARGIN = 20
+N_PARTICLES = 120
+KNOT_R, KNOT_r = 150, 58
+DUR = "11s"
+KEY_TIMES = "0;0.64;0.82;1"
+KEY_SPLINES = "0.25 0.1 0.25 1;0 0 1 1;0.55 0 0.45 1"
+
+BG = "#010409"
+BORDER = "#21262d"
+CYAN = "#4dd4e8"
+DEEP = "#1d6f7d"
+GREEN = "#7ee2a8"
+MUTED = "#8b949e"
+
+
+def seeded_unit(index, generation, channel):
+ """Deterministic float in [0, 1) from particle index + generation."""
+ digest = hashlib.sha256(f"{index}:{generation}:{channel}".encode()).digest()
+ return int.from_bytes(digest[:8], "big") / 2**64
+
+
+def knot_points(n):
+ """(2,5) torus knot sampled at n points, scaled/centered to the canvas."""
+ raw = []
+ for i in range(n):
+ t = 2 * math.pi * i / n
+ rad = KNOT_R + KNOT_r * math.cos(5 * t)
+ raw.append((rad * math.cos(2 * t), 0.62 * rad * math.sin(2 * t)))
+ xs, ys = [p[0] for p in raw], [p[1] for p in raw]
+ span_x, span_y = max(xs) - min(xs), max(ys) - min(ys)
+ scale = min((WIDTH - 2 * MARGIN) / span_x, (HEIGHT - 2 * MARGIN) / span_y)
+ cx, cy = (max(xs) + min(xs)) / 2, (max(ys) + min(ys)) / 2
+ return [
+ (WIDTH / 2 + (x - cx) * scale, HEIGHT / 2 + (y - cy) * scale)
+ for x, y in raw
+ ]
+
+
+def main():
+ with open(STATE_PATH) as fh:
+ state = json.load(fh)
+ bits = int(state.get("bits_sorted", 0))
+ generation = int(state.get("generation", 0))
+ entropy = max(12.7, 100 - 0.0002 * bits)
+ s_label = f"{entropy:.1f}"
+
+ lattice = knot_points(N_PARTICLES)
+ parts = [
+ f'")
+
+ svg = "\n".join(parts) + "\n"
+ os.makedirs(os.path.dirname(OUT_PATH), exist_ok=True)
+ with open(OUT_PATH, "w") as fh:
+ fh.write(svg)
+ size = os.path.getsize(OUT_PATH)
+ print(f"wrote {OUT_PATH} ({size} bytes, S={s_label}, bits={bits}, gen={generation})")
+ if size > 200 * 1024:
+ print("error: SVG exceeds 200 KB budget", file=sys.stderr)
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/state/demon.json b/state/demon.json
new file mode 100644
index 0000000..fe021d0
--- /dev/null
+++ b/state/demon.json
@@ -0,0 +1 @@
+{ "bits_sorted": 0, "generation": 0, "demons": 0, "last_sorted_at": null }