Demos
A demo is recorded by the server, never by a client. The authoritative world already consumes exactly one PlayerInput per pawn per tick; the recorder writes that input down as it is applied, plus a full world snapshot every N ticks. A replay re-runs the real Universe.Tick over the recorded inputs — the same movement code, the same physics, the same map — so it reproduces movement and collisions bit for bit, rather than replaying a network stream the way Source’s client .dem does. A solo session’s server demo is the client’s demo: the embedded server records it.
Recording
Section titled “Recording”From the server console (an embedded server’s console or a dedicated server’s stdin; the verb is authority-gated like say):
| Command | What it does |
|---|---|
demo record [name] | Starts recording at the next tick boundary. Without a name the file is <map>-<yyyyMMdd-HHmmss>.dh-demo under the engine’s user data directory, demos\. A name may be bare (demo record jumps) or a full path. |
demo stop | Closes the file. A recording also closes on its own when the map switches, and when the server shuts down. |
demo status | Where the recording is writing, how many ticks and checkpoints it holds, and when the next checkpoint lands. |
server.demo.checkpointTicks (default 300, five seconds at 60 Hz) sets how often a full checkpoint is written. Every checkpoint is a seek index entry and a determinism check for the replay; more of them cost file size, not tick time.
A recording can start mid-session. It opens with a join for every live pawn carrying its full movement state — position, look, velocity, the whole CharacterMotion — and the first checkpoint, so the replay rebuilds those pawns exactly as they stood rather than from a spawn point. Logic entities (doors, plates) are not restored from the checkpoint: a replay always installs the map fresh, so a demo started while a door is mid-swing diverges at its opening checkpoint, and says so. Start the recording at map load for a clean run.
What the file holds
Section titled “What the file holds”.dh-demo is a versioned, little-endian chunk stream: four magic bytes (DHDM), a format version, then byte kind, int length, payload chunks until an end chunk. A file cut off mid-write reads as far as its last whole chunk and is reported as truncated.
| Chunk | Contents |
|---|---|
| Header | Map name and barcode, the pallet closure hash, engine version and git sha, tick rate, checkpoint interval, the start tick, and the WorldSettings snapshot every client receives (movement values, time scale, surface grip overrides). |
| Join | Tick, player slot, wire id, display name, and the pawn’s full movement state. |
| Leave | Tick and player slot. |
| Input | Tick, player slot, and the PlayerInput that tick was simulated with — read off the pawn after the server applied it, so a starved tick records the held or neutral input that was actually run, never what arrived on the wire. |
| Checkpoint | Tick and the same full-world snapshot a late joiner is sent (SnapshotCodec), so the replay checks itself against exactly what players saw. |
A record’s tick is the tick it applies before: inputs at tick T are the ones tick T simulates, a checkpoint at tick T is the boundary after tick T−1. The reader (DemoReader) loads the whole file and indexes the checkpoints; FindCheckpoint(tick) answers the latest checkpoint at or before a tick.
Replaying
Section titled “Replaying”DigitalHeaven.Engine.Host --demo <file> [--until <tick>] [--demoLog positions,ground,contacts]The replay is headless: no window, no socket, no renderer. It boots the dedicated server composition — the real game assembly, the real map install, the real tick — on a loopback transport nobody connects to, switches to the demo’s map if the boot map differs, applies the recorded world rules to its preference store, spawns the recorded pawns, and ticks as fast as the machine runs. After every tick that carries a checkpoint it compares the live world to it entity by entity — position, look, flags, eye height, rotation — with exact float equality. Map objects are matched by their authored index and pawns by wire id, so a demo recorded after a map switch still lines up against a fresh world.
The first mismatch is nondeterminism (or a different build, or a different pallet — the closure hash is checked and warned about) and is reported with its tick:
DIVERGED tick 900 PlayerPawn@7 position: recorded (3.25, 0, -1.5), replayed (3.2500002, 0, -1.5) delta (2.3841858E-07, 0, 0)demo: replayed 900 tick(s) to 1020 in 0.4s, checked 4 checkpoint(s): DIVERGED at tick 900 ...The process exits 0 when every checkpoint matched and 3 on a divergence; --until <tick> stops early. --demoLog adds per-tick lines to stdout: positions (position and velocity per pawn), ground (grounded flag, ground normal, ticks since grounded), contacts (the mover’s contact planes from IPhysicsWorld.CollideMover, and a census of every physics body at the start via DescribeBodies so a plane can be related to the body that produced it).
Determinism
Section titled “Determinism”Everything in the tick is a pure function of the recorded state on one machine: fixed dt, no wall-clock reads, no randomness. The two places the server does reach for something outside the tick are recorded around rather than reproduced: spawn placement on a multi-spawn map draws from the server’s own Random, so the demo carries the chosen pose in the join record; and the world’s per-session rate limiters read a stopwatch, which never touches the simulation. The slope limit is the one value that crosses the wire in a different unit than it is stored (a cosine on the wire, degrees in the preference), so the replay searches for the degree value whose cosine is bit-identical to the recorded threshold rather than trusting acos to land on it.
What is deliberately not carried: per-player movement and look overrides (players.<target>.*), and the state of logic entities at a mid-session start. A demo that depends on either diverges at its opening checkpoint and says so.