Route planning and delivery execution
This page follows a letter from Wuxi to Beijing. You will run a routed delivery, see how canwu-routing picks a route from what one observer knows, and see how canwu-transport and canwu-movement record the journey leg by leg, including failures and reroutes.
Your application supplies the network and timetable data. canwu-routing and canwu-transport are plain Rust libraries that compute and check values; your plugins store those values as domain records and change them through canonical ingress. All of their types are re-exported from canwu-api.
Run it
Section titled “Run it”The correspondence example plans two deliveries from the same sender, one to a neighbor in Wuxi and one to Beijing:
cargo run -p canwu-correspondence --example routed_correspondenceWuxi local delivery: 1 leg(s), arrival minute 30 wuxi/hub -> wuxi/delivery/recipient via HorseWuxi to Beijing: 2 leg(s), arrival minute 3060 wuxi/hub -> beijing/station via Rail beijing/station -> beijing/delivery/recipient via HorseThe sender’s network is defined in examples/support/mod.rs. The local letter takes one 30-minute horse leg. For Beijing the router compares a direct train (departs at minute 60, two days), a train through Nanjing (three days), and two final-mile horse legs (two and three hours). It picks the direct train and the two-hour leg: 60 + 2,880 + 120 = minute 3,060. Routed correspondence from Wuxi walks through that example in full. This page explains the planning and execution contracts underneath it.
Planning and execution
Section titled “Planning and execution”“How does a letter get from Wuxi to Beijing?” is two questions:
- Planning: given the network, timetables, risk, and capacity this observer knows about, which route arrives earliest within the limits?
- Execution: has the letter departed, reached a relay, changed hands, been rerouted after a flood, and reached the recipient?
canwu-routing answers the first and canwu-transport records the second. The recipient’s knowledge changes only when the information extension completes the delivery attempt.
View diagram source
flowchart LR
Snap["PlanningSnapshot<br/>what the observer knows"] --> Plan["plan_route()<br/>RoutePlan"]
Plan --> Install["TransportExecution<br/>initial ItineraryRevision"]
Install --> Book["CapacityBooking<br/>(optional)"]
Book --> Legs["Legs: start, arrive<br/>Handoff at relays"]
Legs -->|"leg fails"| Replan["ReplanPending"]
Replan -->|"new snapshot"| Reroute["Successor<br/>ItineraryRevision"]
Reroute --> Legs
Legs -->|"final leg arrives"| Done["ArrivalPending<br/>or Settled"]
Plan a route
Section titled “Plan a route”let request = RoutingRequest { origin: RoutingNodeRef::new("wuxi/hub"), destination: RoutingNodeRef::new("beijing/delivery/recipient"), departure_at: SimTime::EPOCH + SimDuration::hours(1), policy: RoutingPolicy { allowed_modes: [TransferMode::Rail, TransferMode::Horse] .into_iter() .collect(), ..RoutingPolicy::default() },};
let plan = plan_route(&planning_snapshot, &request)?;A PlanningSnapshot is the network as one observer knows it at one moment. It records the observer, observed_at, knowledge_cut, topology_version, an optional timetable_version, and an optional valid_until expiry. Someone with a current railway timetable and someone who has only heard a courier rumor get different plans, and both plans are valid for what they knew. Two helpers build snapshots:
canwu_reference_world::planning_snapshot_from_worldbuilds one from the reference world’s territories and routes.canwu_correspondence::planning_snapshot_from_holder_knowledgebuilds one from exactly what one holder’s ledger asserts; see Plan from a holder’s knowledge.
The resulting RoutePlan lists legs, each with a mode and planned departure and arrival, plus estimated_arrival_at and a digest. A nearby recipient may need one short leg; Beijing may need several relays, stations, and custody transfers.
Modes and timetables
Section titled “Modes and timetables”Rail, air, telegraph, and relay stations share one algorithm; the differences live in network data:
- Rail:
TransferMode::Rail, station endpoints, and departure slots. A 1900 network and a 1940 network are two content packages with different timetables. - Air:
TransferMode::Air, airports, and flight slots. - Telegraph and other signals:
TransferMode::Signal. Office hours, latency, risk, and interception come from data or your own extension’s policy. - Courier networks: relay-station endpoints,
HorseorFootconnections, and custody handoffs between legs.
Each connection has a TraversalModel: Fixed for a constant duration, Departures for scheduled slots, and Piecewise for durations that change over time. The default algorithm, FifoDijkstraV1, assumes that leaving later never means arriving earlier. When your time-dependent data breaks that rule, set algorithm: RoutingAlgorithm::BoundedLabelCorrectingV1 in the policy.
Limits and caching
Section titled “Limits and caching”RoutingPolicy also sets max_expanded_nodes, max_transfers, max_risk_per_mille, and max_arrival_at. Failures such as NoKnownRoute and ExpansionBudgetExceeded are deterministic results, so replay reproduces them.
RoutingCache stores plans keyed by the snapshot digest and the request, including its policy. It is a host-side cache: you can drop it at any time and rebuild it, and replay works without it.
ETA and delivery deadline
Section titled “ETA and delivery deadline”RoutePlan.estimated_arrival_at is an estimate for execution. The delivery attempt’s due_at is the deadline in the information lifecycle. When the letter will arrive late, the system records a failure or creates a new attempt; the original deadline stays as it was.
Rerouting and retrying are different operations:
- A reroute creates a new immutable
ItineraryRevisionfor the same delivery attempt. - A delivery retry creates a new delivery attempt, which may use a new plan and mode.
Record the journey
Section titled “Record the journey”After planning, the host creates a TransportExecution, installs the plan as its first itinerary revision, and links it to the delivery attempt it must complete:
let mut execution = TransportExecution::new( TransportExecutionId(7), Some(delivery_attempt_version.clone()),);execution.install_initial_itinerary(initial_revision)?;execution.begin_saga( delivery_attempt_version, delivery_completion_operation_key( TransportExecutionId(7), ItineraryRevisionId(1), 1, ),)?;delivery_attempt_version is the exact domain-record version (DomainRecordVersionRef) of the delivery attempt, and initial_revision is an ItineraryRevision holding the RoutePlan with reason ItineraryRevisionReason::Initial. begin_saga moves the execution to Executing.
Then each leg runs in order:
start_current_leg(at)departs, andcomplete_current_leg(at, endpoint)arrives. Each leg records its actual times.- At a relay,
record_handoffrecords who hands custody to whom before the next leg starts. - When a flood, war, bad weather, or a line outage blocks the route, a domain system calls
fail_current_leg(reason, at). The execution entersReplanPending. A new planning snapshot produces a successor revision, installed withreroute(revision, at), andrecord_handoffrecords custody passing from the failed leg to the new revision’s first leg. The router only plans; your systems decide that the disaster happened. - When the final leg arrives, the execution enters
ArrivalPending.completion_request()returns aDeliveryCompletionRequestwith a stable operation key and evidence. The host submits it through canonical ingress to complete the delivery attempt in the information extension. Submitting the same key twice completes the attempt once.
For execution 7, itinerary revision 1, and attempt version 1, the key is transport/7/revision/1/delivery-completion/attempt-version/1. An execution without a delivery attempt ends with settle_arrival(at, endpoint) in state Settled.
People, seizures, and external conditions
Section titled “People, seizures, and external conditions”These records cover common strategy-game movement:
MovementSubjectRole::PersonsGroupmoves a persons group: several people travelling as one subject, usually identified by an application domain record. Like cargo, it carries a positive quantity, here the head count;requires_quantity()returnstruefor both roles.HandoffKind::Seizure { by }records a seizure handoff: someone outside the itinerary, such as bandits or a hostile garrison, takes custody. Planned handoffs useHandoffKind::Planned. A seizure handoff follows the same leg rules as a planned handoff.- A terminal seizure names the failed current leg as both ends, so custody leaves the itinerary. An execution allows one terminal seizure. After it, the execution accepts no more handoffs, leg failures, reroutes, arrivals, successful settlement, or bookings, and only its owner can close it, as failed or cancelled.
ItineraryRevisionReason::ExternalCondition { record, version, kind }explains a reroute by citing an application-owned condition record, such as a flood or border-closure record, at an exact positive version, with an application label inkind. It is validated on the initial itinerary and on every reroute.
Transport records these facts. Whether a seizure or hazard happened, and who had the authority, is decided by your application systems.
Movement lifecycle and capacity pools
Section titled “Movement lifecycle and capacity pools”canwu-transport holds the records. The movement lifecycle extension canwu-movement runs them through one MovementPlugin: your host sends orders with the tracked command apply_movement_operation_v1, application systems report incidents through movement_incident_v1, and the plugin settles each leg at its due time, allocates capacity pools, and publishes holder-relative reports. The plugin draws no random numbers; your systems decide whether a hazard happens. How it runs on the design page gives the full rules for orders, legs, incidents, bookings, and reports.
Capacity bookings
Section titled “Capacity bookings”Trains, aircraft, relay horses, and telegraph lines can all be booked with CapacityBooking, a persistent record for a time window. Its status is one of Requested, Confirmed, Consumed, Released, Expired, Cancelled, or Failed. A booking can be confirmed, failed, or cancelled before its window opens, and consumed only inside the window.
Movement executions can also draw on capacity pools; see Capacity pools. The correspondence extension currently offers only CorrespondenceCapacityAdmission::Unconstrained, so correspondence legs run without capacity limits.
Further reading
Section titled “Further reading”- Architecture overview
- Routing and transport proposal, with the ownership boundary and the M1–M3 milestones