ecsly_replay 0.1.0-dev.3
ecsly_replay: ^0.1.0-dev.3 copied to clipboard
Record and seek replay logs for ecsly worlds using PersistentId-keyed snapshots and checkpoints.
ecsly_replay #
Record simulation history for an ecsly world and seek forward or backward to any tick or opaque event key. Built for inspector time bars and harness tooling where agent commands are not deterministic but recorded outcomes are.
Install #
This package releases from this repo's pipeline; its first pub.dev release is a one-time manual bootstrap (see docs/release.mdx). Until then, depend on the packages via git:
dependencies:
ecsly_replay:
git:
url: https://github.com/Arenukvern/ecsly.git
path: plugins/ecsly_replay
ecsly_serialization:
git:
url: https://github.com/Arenukvern/ecsly.git
path: plugins/ecsly_serialization
After that: dart pub add ecsly_replay ecsly_serialization.
Design #
- Identity: PersistentId
and WorldSnapshot
from
ecsly_serialization— no parallel id scheme. - Log: append-only
ReplayEntry { tick, seq, kind, payload, eventKey? }ordered by(tick, seq)with aneventKey → positionindex. - Commands: each entry carries a full serialized outcome snapshot; replay applies it via reconcile restore (no inverse deltas, no re-running systems).
- Inputs: optional second kind for deterministic games (host supplies
InputReplayer). - Checkpoints: serialized world snapshots on a cadence; invalidated when history is truncated.
- Seek: nearest checkpoint at or before the target, then replay forward.
Checkpoints reconcile into the live world by default (transient entities
survive with
TransientPolicy.keep). UseTransientPolicy.despawnto drop non-persistent entities after each seek.CheckpointRestoreMode.spawnis the explicit oracle. Useseek,seekToTick,seekToEventKey, orseekTo. - No Flutter UI in this package.
Plugin #
ReplayPlugin installs the whole record-and-seek state into a world in one
call — log, checkpoint store, recorder, and a player bound to that world,
reachable from systems and tooling as ReplayStateResource:
world.addPlugin(ReplayPlugin(
header: ReplayLogHeader(
formatVersion: replayLogFormatVersion,
fingerprints: ReplayFingerprints(
buildFingerprint: myBuildId,
schemaFingerprint: 'schema-$schemaVersion',
),
),
));
final state = world.getResource<ReplayStateResource>();
state.recorder.recordCommand(
tick: tick,
outcome: captureWorldSnapshot(world),
eventKey: harnessEncodedKey,
);
state.recorder.maybeCheckpoint(world, tick);
state.player.seekToEventKey(harnessEncodedKey);
The standalone classes below remain the API for hosts that wire the pieces themselves (multiple worlds, external logs, custom players).
Manual wiring #
import 'package:ecsly_replay/ecsly_replay.dart';
import 'package:ecsly_serialization/ecsly_serialization.dart';
final log = ReplayLog(
header: ReplayLogHeader(
formatVersion: replayLogFormatVersion,
fingerprints: ReplayFingerprints(
buildFingerprint: myBuildId,
schemaFingerprint: 'schema-$schemaVersion',
),
),
);
final checkpoints = CheckpointStore(cadence: 30);
final recorder = ReplayRecorder(log: log, checkpoints: checkpoints);
// After each command:
recorder.recordCommand(
tick: tick,
outcome: captureWorldSnapshot(world),
eventKey: harnessEncodedKey,
);
recorder.maybeCheckpoint(world, tick);
final player = ReplayPlayer(world: world, log: log, checkpoints: checkpoints);
player.seekToEventKey(harnessEncodedKey);
See DX_FAQ.md for usage notes and DESIGN_FAQ.md for rationale.
Dependencies #
ecsly(core runtime)ecsly_serialization(snapshots, reconcile restore)
What was borrowed #
Concepts (not code paths) from internal ecs_replay_tools: log ordering rules,
checkpoint cadence/invalidation, cold-vs-hot determinism checks, and build/schema
fingerprints in the log header.