replayDeterministic replay
Time, scheduled work, and random draws run in a fixed order. Replaying a run's journal rebuilds it, and the checkpoint hash confirms the result is identical.
open source · 0.13.0 · pre-1.0
参伍引擎
Canwu is a headless simulation engine written in Rust. Your code sends it commands; it advances time, records what happened and why, and tracks what each character knows. Rendering, UI, and historical content stay in your game, research tool, or agent system.
starter.rsuse canwu_api::{Canwu, CommandRequest, CommandRequestId, EntityRef, Issuer, SimDuration};
use canwu_reference_world::{
MovementCommand, ReferenceWorldPlugin, demo_scenario, order_movement,
snapshot as reference_snapshot,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (scenario, ids) = demo_scenario()?;
let plugin = ReferenceWorldPlugin;
let mut canwu = Canwu::new_with_plugins(35, scenario, &[&plugin])?;
let envelope = order_movement(
Issuer::Actor(ids.commander),
&MovementCommand {
subject: EntityRef::Army(ids.army),
destination: ids.eastern_territory,
cargo: Vec::new(),
},
)?
.at_time(canwu.time());
canwu.enqueue_command(
canwu.time(),
0,
CommandRequest::new(CommandRequestId::new(1), canwu.revision(), envelope),
)?;
canwu.advance_canonical(SimDuration::hours(19))?;
let saved = canwu.snapshot_json()?;
let loaded = Canwu::from_snapshot_json_with_plugins(&saved, &[&plugin])?;
let fork = loaded.fork();
let journal = canwu.replay_journal();
let replayed = Canwu::replay_from_journal(&[&plugin], &journal)?;
assert_eq!(loaded.checkpoint_hash(), canwu.checkpoint_hash());
assert_eq!(fork.checkpoint_hash(), canwu.checkpoint_hash());
assert_eq!(replayed.checkpoint_hash(), canwu.checkpoint_hash());
println!(
"army_location={} checkpoint={}",
reference_snapshot(&replayed)?
.army(ids.army)
.expect("demo army exists")
.location,
replayed.checkpoint_hash()
);
Ok(())
}Architecture
Public API boundary · code above this line uses only canwu-api
Engine internals · reached only through canwu-api
Owns rendering, input, the real-time clock, accounts, and historical content. It depends on canwu-api, registers the plugins it needs, submits commands, and reads the results.
canwu-debugDesktop debug client (egui) built on the public API and the reference integrationsDomain extensions add reusable rules as simulation plugins built on canwu-api. Register only the ones you need, or write your own. Reference content packs supply data, and reference integrations combine the pieces into small worlds you can run and copy.
canwu-resourceResource accounts, reservations, transfers, and fulfillmentcanwu-productionProduction processes, facilities, work orders, and outputcanwu-movementMovement orders, route legs, and capacity poolscanwu-militaryForces, operations, combat, and occupationcanwu-societySocial diffusion between population groupscanwu-cultureCulture authoring, compilation, and lifecyclecanwu-lawLegal procedures, versioned law, and applicability (experimental)canwu-technologyTechnology from evidence to adoption and diffusioncanwu-fiscalFiscal institutions, procedures, and assessmentscanwu-informationDocuments, copies, access, and interpretationcanwu-correspondenceLetters, addresses, carriers, and interceptioncanwu-history-researchOptional research assessments for technology runscanwu-ming-fiscalVersioned, source-cited Ming fiscal datacanwu-economy-reference-contentEconomy model cards and cited datacanwu-military-reference-contentSynthetic (non-historical) military rulesets for the reference integrationcanwu-reference-worldSmall world with armies and territories, used by the starter examplecanwu-economy-referenceGrain loop with production, transport, and military supplycanwu-force-supply-referenceMilitary supply as a second resource consumercanwu-military-referenceThe military extension running in the reference worldcanwu-ming-fiscal-referenceHongwu, Wanli, and Hongguang fiscal scenarioscanwu-apiThe engine crate your application depends on; extension crates build on it too. Its Canwu type creates and advances a run, accepts commands, returns detached snapshots and actor-relative views, and saves, forks, and replays runs. It re-exports the types you need from the crates below.
Canwu::new_with_pluginsCreate a run from a scenario and a plugin listenqueue_commandQueue a command for a simulation timeadvance_canonicalAdvance time and settle everything that falls dueviewer_for_actorRead the world as one actor knows itevents · explainList what happened and ask whysnapshot_json · fork · replay_from_journalSave, branch, and replay a runcanwu-simHolds the authoritative state. It queues incoming commands, runs scheduled work, and settles due work in boundaries: one all-or-nothing pass at one simulation time, run in 14 fixed phases. If anything fails, the whole boundary rolls back. It also records the evidence and hashes used for saves and replay. Applications reach it only through canwu-api.
canwu-simRuntime, scheduling, settlement, plugins, persistence, hashing, and replayData types that plugins and applications reach through canwu-api: what happened and why, what each actor knows, pending decisions, and route and transport records. The runtime itself uses the event, knowledge, and decision types.
canwu-eventEvents with their cause, audience, and evidence referencescanwu-knowledgeWhat each actor knows, with source, confidence, and agecanwu-decisionDecision tickets, controllers, and policiescanwu-routingRoute planning where travel times change over timecanwu-transportTransport runs, bookings, handoffs, and capacity poolsTyped IDs, deterministic random numbers, schema metadata, and simulation time arithmetic. Every other crate builds on these two.
canwu-coreStable IDs, deterministic random numbers, and schema primitivescanwu-timeSimulation time and checked duration arithmeticStep 1 of 7
Your client creates a typed command. In the starter example, the reference world wraps a MovementCommand into a command envelope.
let envelope = order_movement(Issuer::Actor(commander), &command)?;Step 2 of 7
enqueue_command puts the request in the ingress queue for its due time and returns a receipt. Resending the identical request returns the same receipt; reusing its ID with different content fails. The world has not changed yet.
canwu.enqueue_command(due_at, priority, CommandRequest::new(id, revision, envelope))?;Step 3 of 7
advance_canonical moves simulation time forward. Each batch of work that falls due is settled in one boundary.
canwu.advance_canonical(SimDuration::hours(19))?;Step 4 of 7
The engine checks the issuer's authority, the revision the command was based on, and its expected time. A rejected command is recorded with its reason. Plugin systems then run in 14 fixed phases and propose changes, which commit together or roll back together.
The 14 phases →Step 5 of 7
Committed changes produce events that carry their cause and audience. Characters' knowledge and reports are updated, and the replay journal gains one more boundary.
Step 6 of 7
A player or agent reads through viewer_for_actor and sees only what that actor knows. Trusted host tools can also list events() and ask explain() why something happened.
let viewer = canwu.viewer_for_actor(actor)?;Reading state safely →Step 7 of 7
snapshot_json saves the run, and replay_journal returns its recorded inputs. replay_from_journal rebuilds the run from those inputs; equal checkpoint hashes show the replay is exact.
let replayed = Canwu::replay_from_journal(&[&plugin], &canwu.replay_journal())?;
assert_eq!(replayed.checkpoint_hash(), canwu.checkpoint_hash());Save, replay, and fork →Guarantees
replayTime, scheduled work, and random draws run in a fixed order. Replaying a run's journal rebuilds it, and the checkpoint hash confirms the result is identical.
knowledgeThe true state of the world is kept apart from what each character knows. Each observation records its source, confidence, and age, so a character can act on a report that is late or wrong.
commandsClients change the world only by submitting commands, and read it through snapshots, events, or a character's view. A game, a research notebook, and an AI agent use the same calls.
Install
[dependencies]
canwu-api = "=0.13.0"Five-minute check
git clone https://github.com/PeiyuanQi/canwu
cd canwu
cargo run -p canwu-reference-world --example starterarmy_location=3 checkpoint=dd9796a606983d02eafd3495df11b20bb8423cec33f96c0eaddc24e44e4e2de1AI agents
viewer_for_actor(actor)viewer.query_knowledge(query)viewer.visible_changes_since(time)viewer.evaluation_traces(subject, after)enqueue_command(...)advance_canonical(...)actor-scoped// 1. Read what this character knows
let viewer = canwu.viewer_for_actor(actor)?;
let knowledge = viewer.query_knowledge(&query)?;
// 2. Decide, then submit a typed command
canwu.enqueue_command(time, priority, request)?;
canwu.advance_canonical(duration)?;
// 3. See what changed from this character's point of view
let changes = canwu.viewer_for_actor(actor)?.visible_changes_since(since);Use cases
Run the simulation behind any map or renderer.
Run repeatable scenarios, branch alternative histories, and compare the results.
Give several agents different knowledge, authority, and commands.
Build timelines, maps, and classroom tools on replayable state.
Contribute