pub struct Canwu { /* private fields */ }Expand description
Main in-process API. All returned world values are detached snapshots.
Implementations§
Source§impl Canwu
impl Canwu
pub const fn version() -> &'static str
pub fn new(seed: u64, scenario: Scenario) -> Result<Self, CanwuError>
Sourcepub fn into_compacted(self) -> Result<CompactedCanwu, CanwuError>
pub fn into_compacted(self) -> Result<CompactedCanwu, CanwuError>
Enters the explicit compact-journal interface without discarding evidence.
Sourcepub fn new_with_plugins(
seed: u64,
scenario: Scenario,
plugins: &[&dyn SimulationPlugin],
) -> Result<Self, CanwuError>
pub fn new_with_plugins( seed: u64, scenario: Scenario, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>
Examples found in repository?
549fn main() -> Result<(), CanwuError> {
550 let central = CentralPlugin;
551 let treasury = TreasuryPlugin;
552 let county = CountyPlugin;
553 let plugins: [&dyn SimulationPlugin; 3] = [¢ral, &treasury, &county];
554 let mut canwu =
555 Canwu::new_with_plugins(11, Scenario::new(SimTime::EPOCH, Vec::new()), &plugins)?;
556
557 canwu.enqueue_plugin_ingress(canwu_api::PluginIngressRequest::new(
558 CENTRAL_PLUGIN,
559 ISSUE_INGRESS,
560 SimTime::EPOCH,
561 json!({"order_id": ORDER_ID}),
562 ))?;
563
564 let first = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
565 assert_eq!(first.generated_ingress.len(), 2);
566 assert!(canwu.typed_domain_record(&order_reference()).is_some());
567 assert!(canwu.typed_domain_record(&treasury_reference()).is_none());
568 assert!(canwu.typed_domain_record(&county_reference()).is_none());
569
570 let second = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
571 assert_eq!(second.record_change_count, 2);
572 assert!(canwu.typed_domain_record(&treasury_reference()).is_some());
573 assert!(canwu.typed_domain_record(&county_reference()).is_some());
574
575 let snapshot = canwu.snapshot_json()?;
576 let restored = Canwu::from_snapshot_json_with_plugins(&snapshot, &plugins)?;
577 let replayed = Canwu::replay_from_journal(&plugins, &canwu.replay_journal())?;
578 assert_eq!(restored.snapshot(), canwu.snapshot());
579 assert_eq!(replayed.snapshot(), canwu.snapshot());
580
581 println!(
582 "relief_order={} treasury_grain={} county_grain={} exact_replay=ok",
583 ORDER_ID, 600, 600
584 );
585 Ok(())
586}pub fn new_with_manifest( seed: u64, scenario: Scenario, run_manifest: RunManifest, ) -> Result<Self, CanwuError>
pub fn new_with_manifest_and_plugins( seed: u64, scenario: Scenario, run_manifest: RunManifest, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>
pub fn new_with_run_configuration( seed: u64, scenario: Scenario, run_manifest: RunManifest, run_configuration: RunConfiguration, ) -> Result<Self, CanwuError>
pub fn new_with_run_configuration_and_plugins( seed: u64, scenario: Scenario, run_manifest: RunManifest, run_configuration: RunConfiguration, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>
Sourcepub fn demo(seed: u64) -> Result<Self, CanwuError>
pub fn demo(seed: u64) -> Result<Self, CanwuError>
Deprecated compatibility scenario. New hosts should use an integration-owned scenario.
Examples found in repository?
More examples
88fn main() -> Result<(), CanwuError> {
89 let mut canwu = Canwu::demo(35)?;
90 let ids = Canwu::demo_ids();
91 canwu.register_plugin(&StancePlugin)?;
92 canwu.submit(CommandEnvelope::new(
93 Issuer::Actor(ids.commander),
94 Command::Plugin {
95 plugin: "example-stance".to_owned(),
96 command: "set_stance".to_owned(),
97 payload: json!({ "army": ids.army, "stance": "hold" }),
98 },
99 ))?;
100 Ok(())
101}11fn main() -> Result<(), Box<dyn std::error::Error>> {
12 let mut canwu = Canwu::demo(1918)?;
13 let ids = Canwu::demo_ids();
14
15 open_aid_request(&mut canwu, ids)?;
16 refresh_aid_options(&mut canwu, ids)?;
17 resolve_aid_request(&mut canwu)?;
18
19 let trace = canwu
20 .decision_trace(canwu_api::DecisionTraceId::new(1))
21 .expect("utility decision trace");
22 println!("{}", serde_json::to_string_pretty(trace)?);
23
24 verify_persistence_and_replay(&canwu)?;
25 println!("snapshot_restore=ok exact_replay=ok");
26 Ok(())
27}181 fn new(verbose: bool) -> Result<Self, CanwuError> {
182 let authority = Canwu::demo(35)?;
183 let ids = Canwu::demo_ids();
184 let presentation = PresentationState::from_world(&authority.world(), ids.army);
185 Ok(Self {
186 authority,
187 ids,
188 presentation,
189 accumulated_sim_nanos: 0,
190 wall_elapsed: Duration::ZERO,
191 frame: 0,
192 reported_events: 0,
193 next_progress_milestone: 25,
194 canonical_boundaries: 0,
195 verbose,
196 })
197 }146fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
147 let mut canwu = Canwu::demo(202)?;
148 canwu.register_plugin(&UncertaintyPlugin)?;
149 enqueue_ticket(
150 &mut canwu,
151 RANDOM_TICKET,
152 1,
153 DecisionControllerBinding::new(
154 "law-random-controller",
155 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
156 DecisionAuthority::Actor {
157 actor: Canwu::demo_ids().commander,
158 },
159 ),
160 )?;
161 let selection_at = SimTime::EPOCH + SimDuration::days(1);
162 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
163 canwu.step_canonical()?.expect("random source boundary");
164 canwu
165 .step_canonical()?
166 .expect("generated decision resolution boundary");
167 let trace = canwu
168 .decision_trace(canwu_api::DecisionTraceId::new(1))
169 .expect("random decision trace");
170 println!("random_trace={}", serde_json::to_string(trace)?);
171 Ok(())
172}
173
174fn llm_interface_branch() -> Result<(), Box<dyn std::error::Error>> {
175 let mut canwu = Canwu::demo(202)?;
176 let ticket_id = DecisionTicketId::new(2);
177 enqueue_ticket(
178 &mut canwu,
179 ticket_id,
180 10,
181 DecisionControllerBinding::new(
182 "law-llm-controller",
183 DecisionPolicyIdentity::new(DecisionPolicyKind::Llm, "strict-law-selector", "1"),
184 DecisionAuthority::Actor {
185 actor: Canwu::demo_ids().commander,
186 },
187 ),
188 )?;
189 let mut policy = QueuedLlmPolicy::new(
190 "strict-law-selector",
191 "1",
192 LlmModelIdentity {
193 provider: "not-connected".to_owned(),
194 model: "host-selected-model".to_owned(),
195 prompt_contract: "return one existing option_id and no new action".to_owned(),
196 },
197 );
198 let ticket = canwu.decision_ticket(ticket_id).expect("LLM ticket");
199 let request = policy.external_request(ticket);
200 println!("llm_request={}", serde_json::to_string(&request)?);
201
202 // A real host sends `request` to its model and parses a strict structured
203 // answer. This example supplies the same response object without making a
204 // network call.
205 policy.submit(
206 ticket_id,
207 ExternalDecisionResponse {
208 ticket_version: request.ticket_version,
209 option_id: "pass".to_owned(),
210 provider: "not-connected".to_owned(),
211 request_id: "example-response-1".to_owned(),
212 metadata: BTreeMap::new(),
213 },
214 )?;
215 let evaluation = canwu.drive_decision(
216 canwu.time(),
217 0,
218 DecisionRequestId::new(12),
219 None,
220 ticket_id,
221 &policy,
222 )?;
223 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
224 canwu.step_canonical()?.expect("LLM decision boundary");
225 Ok(())
226}Sourcepub fn demo_ids() -> DemoIds
pub fn demo_ids() -> DemoIds
IDs for the deprecated compatibility scenario.
Examples found in repository?
88fn main() -> Result<(), CanwuError> {
89 let mut canwu = Canwu::demo(35)?;
90 let ids = Canwu::demo_ids();
91 canwu.register_plugin(&StancePlugin)?;
92 canwu.submit(CommandEnvelope::new(
93 Issuer::Actor(ids.commander),
94 Command::Plugin {
95 plugin: "example-stance".to_owned(),
96 command: "set_stance".to_owned(),
97 payload: json!({ "army": ids.army, "stance": "hold" }),
98 },
99 ))?;
100 Ok(())
101}More examples
11fn main() -> Result<(), Box<dyn std::error::Error>> {
12 let mut canwu = Canwu::demo(1918)?;
13 let ids = Canwu::demo_ids();
14
15 open_aid_request(&mut canwu, ids)?;
16 refresh_aid_options(&mut canwu, ids)?;
17 resolve_aid_request(&mut canwu)?;
18
19 let trace = canwu
20 .decision_trace(canwu_api::DecisionTraceId::new(1))
21 .expect("utility decision trace");
22 println!("{}", serde_json::to_string_pretty(trace)?);
23
24 verify_persistence_and_replay(&canwu)?;
25 println!("snapshot_restore=ok exact_replay=ok");
26 Ok(())
27}181 fn new(verbose: bool) -> Result<Self, CanwuError> {
182 let authority = Canwu::demo(35)?;
183 let ids = Canwu::demo_ids();
184 let presentation = PresentationState::from_world(&authority.world(), ids.army);
185 Ok(Self {
186 authority,
187 ids,
188 presentation,
189 accumulated_sim_nanos: 0,
190 wall_elapsed: Duration::ZERO,
191 frame: 0,
192 reported_events: 0,
193 next_progress_milestone: 25,
194 canonical_boundaries: 0,
195 verbose,
196 })
197 }
198
199 fn run_phase(&mut self, phase: SpeedPhase, profile: FrameProfile) -> Result<(), CanwuError> {
200 let mut remaining_millis = phase.wall_millis;
201 let mut pattern_index = 0;
202 let mut first_frame = true;
203
204 while remaining_millis > 0 {
205 let frame_millis = profile.frame_millis[pattern_index % profile.frame_millis.len()]
206 .min(remaining_millis);
207 remaining_millis -= frame_millis;
208 self.render_frame(
209 Duration::from_millis(frame_millis),
210 phase.speed,
211 first_frame || remaining_millis == 0,
212 )?;
213 first_frame = false;
214 pattern_index += 1;
215 }
216 Ok(())
217 }
218
219 fn render_frame(
220 &mut self,
221 wall_dt: Duration,
222 speed: GameSpeed,
223 phase_edge: bool,
224 ) -> Result<(), CanwuError> {
225 self.frame += 1;
226 self.wall_elapsed += wall_dt;
227
228 let converted_wall_nanos = wall_dt.as_nanos() * BASE_SIM_SECONDS_PER_WALL_SECOND;
229 self.accumulated_sim_nanos += converted_wall_nanos * speed.multiplier();
230
231 let mut authority_changed = false;
232 while self.accumulated_sim_nanos >= SIMULATION_MINUTE_NANOS {
233 let receipts = self.authority.advance_canonical(SIMULATION_QUANTUM)?;
234 self.canonical_boundaries += receipts.len();
235 authority_changed |= !receipts.is_empty();
236 self.accumulated_sim_nanos -= SIMULATION_MINUTE_NANOS;
237 }
238
239 if authority_changed {
240 self.refresh_presentation_from_authority();
241 }
242
243 let progress = self.presentation.render(self.presentation_time());
244 let crossed_milestone = self.crossed_progress_milestone(progress);
245 let has_new_events = self.reported_events < self.authority.events().len();
246
247 if self.verbose && (phase_edge || crossed_milestone || has_new_events) {
248 self.print_frame(speed);
249 }
250 self.report_new_events();
251 Ok(())
252 }
253
254 /// This host never backdates mid-quantum input. It deterministically rounds
255 /// up to the next representable Canwu minute; another host may choose a
256 /// different policy, but it must apply the same policy across render FPS.
257 fn submit_player_command(&mut self) -> Result<(), CanwuError> {
258 let captured_at = self.presentation_time();
259 let due_at = if self.accumulated_sim_nanos == 0 {
260 self.authority.time()
261 } else {
262 self.authority
263 .time()
264 .checked_add(SIMULATION_QUANTUM)
265 .expect("the scripted command time must remain representable")
266 };
267 let envelope = CommandEnvelope::new(
268 Issuer::Actor(self.ids.commander),
269 Command::OrderMovement {
270 subject: EntityRef::Army(self.ids.army),
271 destination: self.ids.eastern_territory,
272 cargo: Vec::new(),
273 },
274 )
275 .at_time(due_at);
276 let request = CommandRequest::new(
277 CommandRequestId::new(1),
278 self.authority.revision(),
279 envelope,
280 );
281 let ingress = self.authority.enqueue_command(due_at, 0, request)?;
282
283 if self.verbose {
284 println!(
285 "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
286 ingress.ingress_id,
287 );
288 }
289
290 // This drains ingress due at the current canonical time only. The
291 // mid-quantum command remains queued until normal accumulation reaches
292 // its quantized minute; future scheduled work is never jumped to.
293 let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
294 self.canonical_boundaries += receipts.len();
295 self.refresh_presentation_from_authority();
296 self.presentation.render(self.presentation_time());
297 if self.verbose {
298 self.print_frame(GameSpeed::X1);
299 }
300 self.report_new_events();
301 Ok(())
302 }
303
304 fn refresh_presentation_from_authority(&mut self) {
305 self.presentation
306 .refresh_from_authority(&self.authority.world(), self.ids.army);
307 }
308
309 fn presentation_time(&self) -> PresentationTime {
310 PresentationTime {
311 canonical: self.authority.time(),
312 sub_minute: Duration::from_nanos(
313 u64::try_from(self.accumulated_sim_nanos)
314 .expect("the sub-minute accumulator must fit a Duration"),
315 ),
316 }
317 }
318
319 fn crossed_progress_milestone(&mut self, progress: Option<f64>) -> bool {
320 let Some(progress) = progress else {
321 return false;
322 };
323 let mut crossed = false;
324 while self.next_progress_milestone <= 100
325 && progress * 100.0 + f64::EPSILON >= f64::from(self.next_progress_milestone)
326 {
327 crossed = true;
328 self.next_progress_milestone += 25;
329 }
330 crossed
331 }
332
333 fn print_frame(&self, speed: GameSpeed) {
334 println!(
335 "frame={:>5} speed={:<6} wall={:>7.3}s visual_time={} canwu_time={} {}",
336 self.frame,
337 speed.label(),
338 self.wall_elapsed.as_secs_f64(),
339 self.presentation_time(),
340 self.authority.time(),
341 self.presentation.describe(),
342 );
343 }
344
345 fn report_new_events(&mut self) {
346 let events = self.authority.events();
347 if self.verbose {
348 for event in &events[self.reported_events..] {
349 println!(
350 "EVENT: at={} type={} {}",
351 event.timestamp,
352 event.kind.qualified_event_type(),
353 event.summary,
354 );
355 }
356 }
357 self.reported_events = events.len();
358 }
359
360 fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
361 Ok(RunOutcome {
362 profile: profile.name,
363 frames: self.frame,
364 time: self.authority.time(),
365 world: self.authority.world(),
366 events: self.authority.events().to_vec(),
367 commands: self.authority.commands().to_vec(),
368 authoritative_state_hash: self.authority.authoritative_state_hash()?,
369 checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
370 canonical_boundaries: self.canonical_boundaries,
371 })
372 }
373}
374
375struct RunOutcome {
376 profile: &'static str,
377 frames: u64,
378 time: SimTime,
379 world: WorldSnapshot,
380 events: Vec<SimEvent>,
381 commands: Vec<CommandRecord>,
382 authoritative_state_hash: String,
383 checkpoint_hash: String,
384 canonical_boundaries: usize,
385}
386
387fn run(profile: FrameProfile, verbose: bool) -> Result<RunOutcome, CanwuError> {
388 let mut host = GameHost::new(verbose)?;
389 if verbose {
390 println!("\n{} render loop", profile.name);
391 }
392
393 host.run_phase(SPEED_SCHEDULE[0], profile)?;
394 host.submit_player_command()?;
395 host.run_phase(SPEED_SCHEDULE[1], profile)?;
396 host.run_phase(SPEED_SCHEDULE[2], profile)?;
397
398 let time_before_pause = host.authority.time();
399 let remainder_before_pause = host.accumulated_sim_nanos;
400 let presentation_before_pause = host.presentation_time();
401 let frames_before_pause = host.frame;
402 assert!(
403 remainder_before_pause > 0,
404 "the pause fixture must preserve a fractional simulation minute"
405 );
406 host.run_phase(SPEED_SCHEDULE[3], profile)?;
407 assert!(
408 host.frame > frames_before_pause,
409 "pause must keep rendering"
410 );
411 assert_eq!(
412 host.authority.time(),
413 time_before_pause,
414 "pause must not advance Canwu"
415 );
416 assert_eq!(
417 host.accumulated_sim_nanos, remainder_before_pause,
418 "pause must not add desired simulation time"
419 );
420 assert_eq!(
421 host.presentation_time(),
422 presentation_before_pause,
423 "pause must freeze presentation time"
424 );
425
426 host.run_phase(SPEED_SCHEDULE[4], profile)?;
427 assert_eq!(
428 host.accumulated_sim_nanos, 0,
429 "the scripted schedule should end on a simulation-minute boundary"
430 );
431 host.outcome(profile)
432}
433
434fn std_duration(duration: SimDuration) -> Duration {
435 let minutes = u64::try_from(duration.as_minutes())
436 .expect("presentation interpolation requires a non-negative duration");
437 Duration::from_secs(
438 minutes
439 .checked_mul(60)
440 .expect("presentation duration must remain representable"),
441 )
442}
443
444fn assert_fps_independent(left: &RunOutcome, right: &RunOutcome) {
445 assert_ne!(
446 left.frames, right.frames,
447 "the render segmentations must differ"
448 );
449 assert_eq!(left.time, right.time, "Canwu time changed with render FPS");
450 assert_eq!(
451 left.world, right.world,
452 "world state changed with render FPS"
453 );
454 assert_eq!(left.events, right.events, "events changed with render FPS");
455 assert_eq!(
456 left.commands, right.commands,
457 "command ordering changed with render FPS"
458 );
459 assert_eq!(
460 left.authoritative_state_hash, right.authoritative_state_hash,
461 "authoritative state hash changed with render FPS"
462 );
463 assert_eq!(
464 left.checkpoint_hash, right.checkpoint_hash,
465 "checkpoint hash changed with render FPS"
466 );
467 assert_eq!(
468 left.canonical_boundaries, right.canonical_boundaries,
469 "canonical boundary count changed with render FPS"
470 );
471}
472
473fn assert_expected_outcome(outcome: &RunOutcome) {
474 let ids = Canwu::demo_ids();
475 let command_at = SimTime::from_minutes(1);
476 let arrival_at = SimTime::from_minutes(1_081);
477 let final_time = SimTime::from_minutes(1_082);
478
479 assert_eq!(outcome.time, final_time, "the scripted final time changed");
480 let army = outcome
481 .world
482 .army(ids.army)
483 .expect("the demo army must remain present");
484 assert_eq!(
485 army.location, ids.eastern_territory,
486 "the scheduled arrival must execute"
487 );
488 assert!(
489 army.transit.is_none(),
490 "the army must no longer be in authoritative transit"
491 );
492
493 assert_eq!(
494 outcome.commands.len(),
495 1,
496 "the move command must be accepted"
497 );
498 let command = &outcome.commands[0];
499 assert_eq!(command.accepted_at, command_at);
500 assert_eq!(command.envelope.expected_time, Some(command_at));
501 assert!(matches!(
502 &command.envelope.command,
503 Command::OrderMovement {
504 subject: EntityRef::Army(army),
505 destination,
506 cargo,
507 } if *army == ids.army
508 && *destination == ids.eastern_territory
509 && cargo.is_empty()
510 ));
511
512 let event_timeline = outcome
513 .events
514 .iter()
515 .map(|event| (event.kind.qualified_event_type(), event.timestamp))
516 .collect::<Vec<_>>();
517 assert_eq!(
518 event_timeline,
519 vec![
520 ("move_ordered".to_owned(), command_at),
521 ("army_arrived".to_owned(), arrival_at),
522 ("knowledge_updated".to_owned(), arrival_at),
523 ("report_dispatched".to_owned(), arrival_at),
524 ],
525 "the expected command and scheduled-arrival event timeline changed"
526 );
527 assert_eq!(
528 outcome.canonical_boundaries, 2,
529 "the command and arrival should settle at two canonical boundaries"
530 );
531}95fn enqueue_ticket(
96 canwu: &mut Canwu,
97 ticket_id: DecisionTicketId,
98 request_offset: u64,
99 controller: DecisionControllerBinding,
100) -> Result<(), CanwuError> {
101 let now = canwu.time();
102 let controller_id = controller.id.clone();
103 canwu.enqueue_decision(
104 now,
105 0,
106 DecisionIngressRequest::new(
107 DecisionRequestId::new(request_offset),
108 canwu.revision(),
109 DecisionMutation::RegisterController { controller },
110 ),
111 )?;
112 canwu.enqueue_decision(
113 now,
114 0,
115 DecisionIngressRequest::new(
116 DecisionRequestId::new(request_offset + 1),
117 canwu.revision(),
118 DecisionMutation::Open {
119 ticket: DecisionTicketDraft {
120 id: ticket_id,
121 definition: "example.law-passage".to_owned(),
122 decision_maker: EntityRef::Person(Canwu::demo_ids().commander),
123 assigned_controller: controller_id,
124 summary: "Will the proposed law pass?".to_owned(),
125 context: DecisionContext::new(
126 "example.law-passage.v1",
127 json!({
128 "supporting_seats": 72,
129 "opposing_seats": 28,
130 "public_pressure": "high"
131 }),
132 ),
133 options: vec![
134 DecisionOption::new("pass", "Pass the law"),
135 DecisionOption::new("fail", "Reject the law"),
136 ],
137 deadline: None,
138 },
139 },
140 ),
141 )?;
142 canwu.settle_boundary(canwu_api::BoundaryRequest::at(now))?;
143 Ok(())
144}
145
146fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
147 let mut canwu = Canwu::demo(202)?;
148 canwu.register_plugin(&UncertaintyPlugin)?;
149 enqueue_ticket(
150 &mut canwu,
151 RANDOM_TICKET,
152 1,
153 DecisionControllerBinding::new(
154 "law-random-controller",
155 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
156 DecisionAuthority::Actor {
157 actor: Canwu::demo_ids().commander,
158 },
159 ),
160 )?;
161 let selection_at = SimTime::EPOCH + SimDuration::days(1);
162 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
163 canwu.step_canonical()?.expect("random source boundary");
164 canwu
165 .step_canonical()?
166 .expect("generated decision resolution boundary");
167 let trace = canwu
168 .decision_trace(canwu_api::DecisionTraceId::new(1))
169 .expect("random decision trace");
170 println!("random_trace={}", serde_json::to_string(trace)?);
171 Ok(())
172}
173
174fn llm_interface_branch() -> Result<(), Box<dyn std::error::Error>> {
175 let mut canwu = Canwu::demo(202)?;
176 let ticket_id = DecisionTicketId::new(2);
177 enqueue_ticket(
178 &mut canwu,
179 ticket_id,
180 10,
181 DecisionControllerBinding::new(
182 "law-llm-controller",
183 DecisionPolicyIdentity::new(DecisionPolicyKind::Llm, "strict-law-selector", "1"),
184 DecisionAuthority::Actor {
185 actor: Canwu::demo_ids().commander,
186 },
187 ),
188 )?;
189 let mut policy = QueuedLlmPolicy::new(
190 "strict-law-selector",
191 "1",
192 LlmModelIdentity {
193 provider: "not-connected".to_owned(),
194 model: "host-selected-model".to_owned(),
195 prompt_contract: "return one existing option_id and no new action".to_owned(),
196 },
197 );
198 let ticket = canwu.decision_ticket(ticket_id).expect("LLM ticket");
199 let request = policy.external_request(ticket);
200 println!("llm_request={}", serde_json::to_string(&request)?);
201
202 // A real host sends `request` to its model and parses a strict structured
203 // answer. This example supplies the same response object without making a
204 // network call.
205 policy.submit(
206 ticket_id,
207 ExternalDecisionResponse {
208 ticket_version: request.ticket_version,
209 option_id: "pass".to_owned(),
210 provider: "not-connected".to_owned(),
211 request_id: "example-response-1".to_owned(),
212 metadata: BTreeMap::new(),
213 },
214 )?;
215 let evaluation = canwu.drive_decision(
216 canwu.time(),
217 0,
218 DecisionRequestId::new(12),
219 None,
220 ticket_id,
221 &policy,
222 )?;
223 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
224 canwu.step_canonical()?.expect("LLM decision boundary");
225 Ok(())
226}Sourcepub const fn time(&self) -> SimTime
pub const fn time(&self) -> SimTime
Examples found in repository?
More examples
95fn enqueue_ticket(
96 canwu: &mut Canwu,
97 ticket_id: DecisionTicketId,
98 request_offset: u64,
99 controller: DecisionControllerBinding,
100) -> Result<(), CanwuError> {
101 let now = canwu.time();
102 let controller_id = controller.id.clone();
103 canwu.enqueue_decision(
104 now,
105 0,
106 DecisionIngressRequest::new(
107 DecisionRequestId::new(request_offset),
108 canwu.revision(),
109 DecisionMutation::RegisterController { controller },
110 ),
111 )?;
112 canwu.enqueue_decision(
113 now,
114 0,
115 DecisionIngressRequest::new(
116 DecisionRequestId::new(request_offset + 1),
117 canwu.revision(),
118 DecisionMutation::Open {
119 ticket: DecisionTicketDraft {
120 id: ticket_id,
121 definition: "example.law-passage".to_owned(),
122 decision_maker: EntityRef::Person(Canwu::demo_ids().commander),
123 assigned_controller: controller_id,
124 summary: "Will the proposed law pass?".to_owned(),
125 context: DecisionContext::new(
126 "example.law-passage.v1",
127 json!({
128 "supporting_seats": 72,
129 "opposing_seats": 28,
130 "public_pressure": "high"
131 }),
132 ),
133 options: vec![
134 DecisionOption::new("pass", "Pass the law"),
135 DecisionOption::new("fail", "Reject the law"),
136 ],
137 deadline: None,
138 },
139 },
140 ),
141 )?;
142 canwu.settle_boundary(canwu_api::BoundaryRequest::at(now))?;
143 Ok(())
144}
145
146fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
147 let mut canwu = Canwu::demo(202)?;
148 canwu.register_plugin(&UncertaintyPlugin)?;
149 enqueue_ticket(
150 &mut canwu,
151 RANDOM_TICKET,
152 1,
153 DecisionControllerBinding::new(
154 "law-random-controller",
155 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
156 DecisionAuthority::Actor {
157 actor: Canwu::demo_ids().commander,
158 },
159 ),
160 )?;
161 let selection_at = SimTime::EPOCH + SimDuration::days(1);
162 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
163 canwu.step_canonical()?.expect("random source boundary");
164 canwu
165 .step_canonical()?
166 .expect("generated decision resolution boundary");
167 let trace = canwu
168 .decision_trace(canwu_api::DecisionTraceId::new(1))
169 .expect("random decision trace");
170 println!("random_trace={}", serde_json::to_string(trace)?);
171 Ok(())
172}
173
174fn llm_interface_branch() -> Result<(), Box<dyn std::error::Error>> {
175 let mut canwu = Canwu::demo(202)?;
176 let ticket_id = DecisionTicketId::new(2);
177 enqueue_ticket(
178 &mut canwu,
179 ticket_id,
180 10,
181 DecisionControllerBinding::new(
182 "law-llm-controller",
183 DecisionPolicyIdentity::new(DecisionPolicyKind::Llm, "strict-law-selector", "1"),
184 DecisionAuthority::Actor {
185 actor: Canwu::demo_ids().commander,
186 },
187 ),
188 )?;
189 let mut policy = QueuedLlmPolicy::new(
190 "strict-law-selector",
191 "1",
192 LlmModelIdentity {
193 provider: "not-connected".to_owned(),
194 model: "host-selected-model".to_owned(),
195 prompt_contract: "return one existing option_id and no new action".to_owned(),
196 },
197 );
198 let ticket = canwu.decision_ticket(ticket_id).expect("LLM ticket");
199 let request = policy.external_request(ticket);
200 println!("llm_request={}", serde_json::to_string(&request)?);
201
202 // A real host sends `request` to its model and parses a strict structured
203 // answer. This example supplies the same response object without making a
204 // network call.
205 policy.submit(
206 ticket_id,
207 ExternalDecisionResponse {
208 ticket_version: request.ticket_version,
209 option_id: "pass".to_owned(),
210 provider: "not-connected".to_owned(),
211 request_id: "example-response-1".to_owned(),
212 metadata: BTreeMap::new(),
213 },
214 )?;
215 let evaluation = canwu.drive_decision(
216 canwu.time(),
217 0,
218 DecisionRequestId::new(12),
219 None,
220 ticket_id,
221 &policy,
222 )?;
223 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
224 canwu.step_canonical()?.expect("LLM decision boundary");
225 Ok(())
226}257 fn submit_player_command(&mut self) -> Result<(), CanwuError> {
258 let captured_at = self.presentation_time();
259 let due_at = if self.accumulated_sim_nanos == 0 {
260 self.authority.time()
261 } else {
262 self.authority
263 .time()
264 .checked_add(SIMULATION_QUANTUM)
265 .expect("the scripted command time must remain representable")
266 };
267 let envelope = CommandEnvelope::new(
268 Issuer::Actor(self.ids.commander),
269 Command::OrderMovement {
270 subject: EntityRef::Army(self.ids.army),
271 destination: self.ids.eastern_territory,
272 cargo: Vec::new(),
273 },
274 )
275 .at_time(due_at);
276 let request = CommandRequest::new(
277 CommandRequestId::new(1),
278 self.authority.revision(),
279 envelope,
280 );
281 let ingress = self.authority.enqueue_command(due_at, 0, request)?;
282
283 if self.verbose {
284 println!(
285 "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
286 ingress.ingress_id,
287 );
288 }
289
290 // This drains ingress due at the current canonical time only. The
291 // mid-quantum command remains queued until normal accumulation reaches
292 // its quantized minute; future scheduled work is never jumped to.
293 let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
294 self.canonical_boundaries += receipts.len();
295 self.refresh_presentation_from_authority();
296 self.presentation.render(self.presentation_time());
297 if self.verbose {
298 self.print_frame(GameSpeed::X1);
299 }
300 self.report_new_events();
301 Ok(())
302 }
303
304 fn refresh_presentation_from_authority(&mut self) {
305 self.presentation
306 .refresh_from_authority(&self.authority.world(), self.ids.army);
307 }
308
309 fn presentation_time(&self) -> PresentationTime {
310 PresentationTime {
311 canonical: self.authority.time(),
312 sub_minute: Duration::from_nanos(
313 u64::try_from(self.accumulated_sim_nanos)
314 .expect("the sub-minute accumulator must fit a Duration"),
315 ),
316 }
317 }
318
319 fn crossed_progress_milestone(&mut self, progress: Option<f64>) -> bool {
320 let Some(progress) = progress else {
321 return false;
322 };
323 let mut crossed = false;
324 while self.next_progress_milestone <= 100
325 && progress * 100.0 + f64::EPSILON >= f64::from(self.next_progress_milestone)
326 {
327 crossed = true;
328 self.next_progress_milestone += 25;
329 }
330 crossed
331 }
332
333 fn print_frame(&self, speed: GameSpeed) {
334 println!(
335 "frame={:>5} speed={:<6} wall={:>7.3}s visual_time={} canwu_time={} {}",
336 self.frame,
337 speed.label(),
338 self.wall_elapsed.as_secs_f64(),
339 self.presentation_time(),
340 self.authority.time(),
341 self.presentation.describe(),
342 );
343 }
344
345 fn report_new_events(&mut self) {
346 let events = self.authority.events();
347 if self.verbose {
348 for event in &events[self.reported_events..] {
349 println!(
350 "EVENT: at={} type={} {}",
351 event.timestamp,
352 event.kind.qualified_event_type(),
353 event.summary,
354 );
355 }
356 }
357 self.reported_events = events.len();
358 }
359
360 fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
361 Ok(RunOutcome {
362 profile: profile.name,
363 frames: self.frame,
364 time: self.authority.time(),
365 world: self.authority.world(),
366 events: self.authority.events().to_vec(),
367 commands: self.authority.commands().to_vec(),
368 authoritative_state_hash: self.authority.authoritative_state_hash()?,
369 checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
370 canonical_boundaries: self.canonical_boundaries,
371 })
372 }
373}
374
375struct RunOutcome {
376 profile: &'static str,
377 frames: u64,
378 time: SimTime,
379 world: WorldSnapshot,
380 events: Vec<SimEvent>,
381 commands: Vec<CommandRecord>,
382 authoritative_state_hash: String,
383 checkpoint_hash: String,
384 canonical_boundaries: usize,
385}
386
387fn run(profile: FrameProfile, verbose: bool) -> Result<RunOutcome, CanwuError> {
388 let mut host = GameHost::new(verbose)?;
389 if verbose {
390 println!("\n{} render loop", profile.name);
391 }
392
393 host.run_phase(SPEED_SCHEDULE[0], profile)?;
394 host.submit_player_command()?;
395 host.run_phase(SPEED_SCHEDULE[1], profile)?;
396 host.run_phase(SPEED_SCHEDULE[2], profile)?;
397
398 let time_before_pause = host.authority.time();
399 let remainder_before_pause = host.accumulated_sim_nanos;
400 let presentation_before_pause = host.presentation_time();
401 let frames_before_pause = host.frame;
402 assert!(
403 remainder_before_pause > 0,
404 "the pause fixture must preserve a fractional simulation minute"
405 );
406 host.run_phase(SPEED_SCHEDULE[3], profile)?;
407 assert!(
408 host.frame > frames_before_pause,
409 "pause must keep rendering"
410 );
411 assert_eq!(
412 host.authority.time(),
413 time_before_pause,
414 "pause must not advance Canwu"
415 );
416 assert_eq!(
417 host.accumulated_sim_nanos, remainder_before_pause,
418 "pause must not add desired simulation time"
419 );
420 assert_eq!(
421 host.presentation_time(),
422 presentation_before_pause,
423 "pause must freeze presentation time"
424 );
425
426 host.run_phase(SPEED_SCHEDULE[4], profile)?;
427 assert_eq!(
428 host.accumulated_sim_nanos, 0,
429 "the scripted schedule should end on a simulation-minute boundary"
430 );
431 host.outcome(profile)
432}29fn open_aid_request(canwu: &mut Canwu, ids: DemoIds) -> Result<(), Box<dyn std::error::Error>> {
30 let now = canwu.time();
31 let controller = DecisionControllerBinding::new(
32 "warlord-b-ai",
33 DecisionPolicyIdentity::new(DecisionPolicyKind::Utility, "aid-utility", "1"),
34 DecisionAuthority::Actor {
35 actor: ids.commander,
36 },
37 )
38 .with_command_subject(EntityRef::Army(ids.army));
39
40 canwu.enqueue_decision(
41 now,
42 0,
43 DecisionIngressRequest::new(
44 DecisionRequestId::new(1),
45 canwu.revision(),
46 DecisionMutation::RegisterController { controller },
47 ),
48 )?;
49 canwu.enqueue_decision(
50 now,
51 0,
52 DecisionIngressRequest::new(
53 DecisionRequestId::new(2),
54 canwu.revision(),
55 DecisionMutation::Open {
56 ticket: DecisionTicketDraft {
57 id: DecisionTicketId::new(1),
58 definition: "beiyang.request-military-aid".to_owned(),
59 decision_maker: EntityRef::Person(ids.commander),
60 assigned_controller: "warlord-b-ai".to_owned(),
61 summary: "Neighboring warlord requests immediate military aid".to_owned(),
62 context: DecisionContext::new(
63 "beiyang.aid-request.v1",
64 json!({
65 "requester": "warlord-a",
66 "battle": "ongoing-front",
67 "common_enemy": true
68 }),
69 ),
70 options: vec![DecisionOption {
71 action: DecisionAction::None,
72 utility_inputs: BTreeMap::from([
73 ("home_defense".to_owned(), 80),
74 ("alliance".to_owned(), -40),
75 ]),
76 ..DecisionOption::new("decline", "Decline aid")
77 }],
78 deadline: Some(now + SimDuration::days(2)),
79 },
80 },
81 ),
82 )?;
83 canwu.step_canonical()?.expect("decision intake boundary");
84 Ok(())
85}
86
87fn refresh_aid_options(canwu: &mut Canwu, ids: DemoIds) -> Result<(), Box<dyn std::error::Error>> {
88 canwu.enqueue_decision(
89 canwu.time(),
90 0,
91 DecisionIngressRequest::new(
92 DecisionRequestId::new(3),
93 canwu.revision(),
94 DecisionMutation::ReplaceOptions {
95 ticket_id: DecisionTicketId::new(1),
96 expected_version: 1,
97 context: DecisionContext::new(
98 "beiyang.aid-request.v1",
99 json!({
100 "requester": "warlord-a",
101 "battle": "ongoing-front",
102 "common_enemy": true,
103 "route_confirmed": true
104 }),
105 ),
106 options: vec![
107 DecisionOption {
108 action: DecisionAction::None,
109 utility_inputs: BTreeMap::from([
110 ("home_defense".to_owned(), 80),
111 ("alliance".to_owned(), -40),
112 ]),
113 ..DecisionOption::new("decline", "Decline aid")
114 },
115 DecisionOption {
116 action: DecisionAction::Command {
117 command: serde_json::to_value(Command::OrderMovement {
118 subject: EntityRef::Army(ids.army),
119 destination: ids.eastern_territory,
120 cargo: Vec::new(),
121 })?,
122 },
123 utility_inputs: BTreeMap::from([
124 ("home_defense".to_owned(), -20),
125 ("alliance".to_owned(), 90),
126 ]),
127 ..DecisionOption::new("send-aid", "Send the neighboring army")
128 },
129 ],
130 },
131 ),
132 )?;
133 canwu
134 .step_canonical()?
135 .expect("dynamic option refresh boundary");
136 Ok(())
137}
138
139fn resolve_aid_request(canwu: &mut Canwu) -> Result<(), Box<dyn std::error::Error>> {
140 let policy = WeightedUtilityPolicy::new(
141 "aid-utility",
142 "1",
143 UtilityProfile {
144 weights: BTreeMap::from([("alliance".to_owned(), 3), ("home_defense".to_owned(), 1)]),
145 },
146 );
147 let evaluation = canwu.drive_decision(
148 canwu.time(),
149 0,
150 DecisionRequestId::new(4),
151 Some(CommandRequestId::new(1)),
152 DecisionTicketId::new(1),
153 &policy,
154 )?;
155 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
156 canwu
157 .step_canonical()?
158 .expect("decision resolution boundary");
159 Ok(())
160}pub const fn run_manifest(&self) -> &RunManifest
pub const fn run_configuration(&self) -> &RunConfigurationSnapshot
Sourcepub fn revision(&self) -> u64
pub fn revision(&self) -> u64
Returns the persisted authoritative transaction revision.
Accepted commands, persisted expected rejections, and completed settlement boundaries each advance it exactly once. Failed work, exact retries, bare clock movement, queued but unadmitted ingress, and plugin setup do not advance it; combine it with command expected-time guards.
Examples found in repository?
95fn enqueue_ticket(
96 canwu: &mut Canwu,
97 ticket_id: DecisionTicketId,
98 request_offset: u64,
99 controller: DecisionControllerBinding,
100) -> Result<(), CanwuError> {
101 let now = canwu.time();
102 let controller_id = controller.id.clone();
103 canwu.enqueue_decision(
104 now,
105 0,
106 DecisionIngressRequest::new(
107 DecisionRequestId::new(request_offset),
108 canwu.revision(),
109 DecisionMutation::RegisterController { controller },
110 ),
111 )?;
112 canwu.enqueue_decision(
113 now,
114 0,
115 DecisionIngressRequest::new(
116 DecisionRequestId::new(request_offset + 1),
117 canwu.revision(),
118 DecisionMutation::Open {
119 ticket: DecisionTicketDraft {
120 id: ticket_id,
121 definition: "example.law-passage".to_owned(),
122 decision_maker: EntityRef::Person(Canwu::demo_ids().commander),
123 assigned_controller: controller_id,
124 summary: "Will the proposed law pass?".to_owned(),
125 context: DecisionContext::new(
126 "example.law-passage.v1",
127 json!({
128 "supporting_seats": 72,
129 "opposing_seats": 28,
130 "public_pressure": "high"
131 }),
132 ),
133 options: vec![
134 DecisionOption::new("pass", "Pass the law"),
135 DecisionOption::new("fail", "Reject the law"),
136 ],
137 deadline: None,
138 },
139 },
140 ),
141 )?;
142 canwu.settle_boundary(canwu_api::BoundaryRequest::at(now))?;
143 Ok(())
144}More examples
257 fn submit_player_command(&mut self) -> Result<(), CanwuError> {
258 let captured_at = self.presentation_time();
259 let due_at = if self.accumulated_sim_nanos == 0 {
260 self.authority.time()
261 } else {
262 self.authority
263 .time()
264 .checked_add(SIMULATION_QUANTUM)
265 .expect("the scripted command time must remain representable")
266 };
267 let envelope = CommandEnvelope::new(
268 Issuer::Actor(self.ids.commander),
269 Command::OrderMovement {
270 subject: EntityRef::Army(self.ids.army),
271 destination: self.ids.eastern_territory,
272 cargo: Vec::new(),
273 },
274 )
275 .at_time(due_at);
276 let request = CommandRequest::new(
277 CommandRequestId::new(1),
278 self.authority.revision(),
279 envelope,
280 );
281 let ingress = self.authority.enqueue_command(due_at, 0, request)?;
282
283 if self.verbose {
284 println!(
285 "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
286 ingress.ingress_id,
287 );
288 }
289
290 // This drains ingress due at the current canonical time only. The
291 // mid-quantum command remains queued until normal accumulation reaches
292 // its quantized minute; future scheduled work is never jumped to.
293 let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
294 self.canonical_boundaries += receipts.len();
295 self.refresh_presentation_from_authority();
296 self.presentation.render(self.presentation_time());
297 if self.verbose {
298 self.print_frame(GameSpeed::X1);
299 }
300 self.report_new_events();
301 Ok(())
302 }29fn open_aid_request(canwu: &mut Canwu, ids: DemoIds) -> Result<(), Box<dyn std::error::Error>> {
30 let now = canwu.time();
31 let controller = DecisionControllerBinding::new(
32 "warlord-b-ai",
33 DecisionPolicyIdentity::new(DecisionPolicyKind::Utility, "aid-utility", "1"),
34 DecisionAuthority::Actor {
35 actor: ids.commander,
36 },
37 )
38 .with_command_subject(EntityRef::Army(ids.army));
39
40 canwu.enqueue_decision(
41 now,
42 0,
43 DecisionIngressRequest::new(
44 DecisionRequestId::new(1),
45 canwu.revision(),
46 DecisionMutation::RegisterController { controller },
47 ),
48 )?;
49 canwu.enqueue_decision(
50 now,
51 0,
52 DecisionIngressRequest::new(
53 DecisionRequestId::new(2),
54 canwu.revision(),
55 DecisionMutation::Open {
56 ticket: DecisionTicketDraft {
57 id: DecisionTicketId::new(1),
58 definition: "beiyang.request-military-aid".to_owned(),
59 decision_maker: EntityRef::Person(ids.commander),
60 assigned_controller: "warlord-b-ai".to_owned(),
61 summary: "Neighboring warlord requests immediate military aid".to_owned(),
62 context: DecisionContext::new(
63 "beiyang.aid-request.v1",
64 json!({
65 "requester": "warlord-a",
66 "battle": "ongoing-front",
67 "common_enemy": true
68 }),
69 ),
70 options: vec![DecisionOption {
71 action: DecisionAction::None,
72 utility_inputs: BTreeMap::from([
73 ("home_defense".to_owned(), 80),
74 ("alliance".to_owned(), -40),
75 ]),
76 ..DecisionOption::new("decline", "Decline aid")
77 }],
78 deadline: Some(now + SimDuration::days(2)),
79 },
80 },
81 ),
82 )?;
83 canwu.step_canonical()?.expect("decision intake boundary");
84 Ok(())
85}
86
87fn refresh_aid_options(canwu: &mut Canwu, ids: DemoIds) -> Result<(), Box<dyn std::error::Error>> {
88 canwu.enqueue_decision(
89 canwu.time(),
90 0,
91 DecisionIngressRequest::new(
92 DecisionRequestId::new(3),
93 canwu.revision(),
94 DecisionMutation::ReplaceOptions {
95 ticket_id: DecisionTicketId::new(1),
96 expected_version: 1,
97 context: DecisionContext::new(
98 "beiyang.aid-request.v1",
99 json!({
100 "requester": "warlord-a",
101 "battle": "ongoing-front",
102 "common_enemy": true,
103 "route_confirmed": true
104 }),
105 ),
106 options: vec![
107 DecisionOption {
108 action: DecisionAction::None,
109 utility_inputs: BTreeMap::from([
110 ("home_defense".to_owned(), 80),
111 ("alliance".to_owned(), -40),
112 ]),
113 ..DecisionOption::new("decline", "Decline aid")
114 },
115 DecisionOption {
116 action: DecisionAction::Command {
117 command: serde_json::to_value(Command::OrderMovement {
118 subject: EntityRef::Army(ids.army),
119 destination: ids.eastern_territory,
120 cargo: Vec::new(),
121 })?,
122 },
123 utility_inputs: BTreeMap::from([
124 ("home_defense".to_owned(), -20),
125 ("alliance".to_owned(), 90),
126 ]),
127 ..DecisionOption::new("send-aid", "Send the neighboring army")
128 },
129 ],
130 },
131 ),
132 )?;
133 canwu
134 .step_canonical()?
135 .expect("dynamic option refresh boundary");
136 Ok(())
137}pub fn run_manifest_hash(&self) -> &str
Sourcepub fn checkpoint_hash(&self) -> &str
pub fn checkpoint_hash(&self) -> &str
Examples found in repository?
360 fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
361 Ok(RunOutcome {
362 profile: profile.name,
363 frames: self.frame,
364 time: self.authority.time(),
365 world: self.authority.world(),
366 events: self.authority.events().to_vec(),
367 commands: self.authority.commands().to_vec(),
368 authoritative_state_hash: self.authority.authoritative_state_hash()?,
369 checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
370 canonical_boundaries: self.canonical_boundaries,
371 })
372 }Examples found in repository?
360 fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
361 Ok(RunOutcome {
362 profile: profile.name,
363 frames: self.frame,
364 time: self.authority.time(),
365 world: self.authority.world(),
366 events: self.authority.events().to_vec(),
367 commands: self.authority.commands().to_vec(),
368 authoritative_state_hash: self.authority.authoritative_state_hash()?,
369 checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
370 canonical_boundaries: self.canonical_boundaries,
371 })
372 }pub fn entities(&self) -> impl Iterator<Item = &EntityRef>
pub fn entity_exists(&self, entity: &EntityRef) -> bool
Sourcepub fn world(&self) -> WorldSnapshot
pub fn world(&self) -> WorldSnapshot
Deprecated detached format-5 compatibility projection.
Examples found in repository?
181 fn new(verbose: bool) -> Result<Self, CanwuError> {
182 let authority = Canwu::demo(35)?;
183 let ids = Canwu::demo_ids();
184 let presentation = PresentationState::from_world(&authority.world(), ids.army);
185 Ok(Self {
186 authority,
187 ids,
188 presentation,
189 accumulated_sim_nanos: 0,
190 wall_elapsed: Duration::ZERO,
191 frame: 0,
192 reported_events: 0,
193 next_progress_milestone: 25,
194 canonical_boundaries: 0,
195 verbose,
196 })
197 }
198
199 fn run_phase(&mut self, phase: SpeedPhase, profile: FrameProfile) -> Result<(), CanwuError> {
200 let mut remaining_millis = phase.wall_millis;
201 let mut pattern_index = 0;
202 let mut first_frame = true;
203
204 while remaining_millis > 0 {
205 let frame_millis = profile.frame_millis[pattern_index % profile.frame_millis.len()]
206 .min(remaining_millis);
207 remaining_millis -= frame_millis;
208 self.render_frame(
209 Duration::from_millis(frame_millis),
210 phase.speed,
211 first_frame || remaining_millis == 0,
212 )?;
213 first_frame = false;
214 pattern_index += 1;
215 }
216 Ok(())
217 }
218
219 fn render_frame(
220 &mut self,
221 wall_dt: Duration,
222 speed: GameSpeed,
223 phase_edge: bool,
224 ) -> Result<(), CanwuError> {
225 self.frame += 1;
226 self.wall_elapsed += wall_dt;
227
228 let converted_wall_nanos = wall_dt.as_nanos() * BASE_SIM_SECONDS_PER_WALL_SECOND;
229 self.accumulated_sim_nanos += converted_wall_nanos * speed.multiplier();
230
231 let mut authority_changed = false;
232 while self.accumulated_sim_nanos >= SIMULATION_MINUTE_NANOS {
233 let receipts = self.authority.advance_canonical(SIMULATION_QUANTUM)?;
234 self.canonical_boundaries += receipts.len();
235 authority_changed |= !receipts.is_empty();
236 self.accumulated_sim_nanos -= SIMULATION_MINUTE_NANOS;
237 }
238
239 if authority_changed {
240 self.refresh_presentation_from_authority();
241 }
242
243 let progress = self.presentation.render(self.presentation_time());
244 let crossed_milestone = self.crossed_progress_milestone(progress);
245 let has_new_events = self.reported_events < self.authority.events().len();
246
247 if self.verbose && (phase_edge || crossed_milestone || has_new_events) {
248 self.print_frame(speed);
249 }
250 self.report_new_events();
251 Ok(())
252 }
253
254 /// This host never backdates mid-quantum input. It deterministically rounds
255 /// up to the next representable Canwu minute; another host may choose a
256 /// different policy, but it must apply the same policy across render FPS.
257 fn submit_player_command(&mut self) -> Result<(), CanwuError> {
258 let captured_at = self.presentation_time();
259 let due_at = if self.accumulated_sim_nanos == 0 {
260 self.authority.time()
261 } else {
262 self.authority
263 .time()
264 .checked_add(SIMULATION_QUANTUM)
265 .expect("the scripted command time must remain representable")
266 };
267 let envelope = CommandEnvelope::new(
268 Issuer::Actor(self.ids.commander),
269 Command::OrderMovement {
270 subject: EntityRef::Army(self.ids.army),
271 destination: self.ids.eastern_territory,
272 cargo: Vec::new(),
273 },
274 )
275 .at_time(due_at);
276 let request = CommandRequest::new(
277 CommandRequestId::new(1),
278 self.authority.revision(),
279 envelope,
280 );
281 let ingress = self.authority.enqueue_command(due_at, 0, request)?;
282
283 if self.verbose {
284 println!(
285 "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
286 ingress.ingress_id,
287 );
288 }
289
290 // This drains ingress due at the current canonical time only. The
291 // mid-quantum command remains queued until normal accumulation reaches
292 // its quantized minute; future scheduled work is never jumped to.
293 let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
294 self.canonical_boundaries += receipts.len();
295 self.refresh_presentation_from_authority();
296 self.presentation.render(self.presentation_time());
297 if self.verbose {
298 self.print_frame(GameSpeed::X1);
299 }
300 self.report_new_events();
301 Ok(())
302 }
303
304 fn refresh_presentation_from_authority(&mut self) {
305 self.presentation
306 .refresh_from_authority(&self.authority.world(), self.ids.army);
307 }
308
309 fn presentation_time(&self) -> PresentationTime {
310 PresentationTime {
311 canonical: self.authority.time(),
312 sub_minute: Duration::from_nanos(
313 u64::try_from(self.accumulated_sim_nanos)
314 .expect("the sub-minute accumulator must fit a Duration"),
315 ),
316 }
317 }
318
319 fn crossed_progress_milestone(&mut self, progress: Option<f64>) -> bool {
320 let Some(progress) = progress else {
321 return false;
322 };
323 let mut crossed = false;
324 while self.next_progress_milestone <= 100
325 && progress * 100.0 + f64::EPSILON >= f64::from(self.next_progress_milestone)
326 {
327 crossed = true;
328 self.next_progress_milestone += 25;
329 }
330 crossed
331 }
332
333 fn print_frame(&self, speed: GameSpeed) {
334 println!(
335 "frame={:>5} speed={:<6} wall={:>7.3}s visual_time={} canwu_time={} {}",
336 self.frame,
337 speed.label(),
338 self.wall_elapsed.as_secs_f64(),
339 self.presentation_time(),
340 self.authority.time(),
341 self.presentation.describe(),
342 );
343 }
344
345 fn report_new_events(&mut self) {
346 let events = self.authority.events();
347 if self.verbose {
348 for event in &events[self.reported_events..] {
349 println!(
350 "EVENT: at={} type={} {}",
351 event.timestamp,
352 event.kind.qualified_event_type(),
353 event.summary,
354 );
355 }
356 }
357 self.reported_events = events.len();
358 }
359
360 fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
361 Ok(RunOutcome {
362 profile: profile.name,
363 frames: self.frame,
364 time: self.authority.time(),
365 world: self.authority.world(),
366 events: self.authority.events().to_vec(),
367 commands: self.authority.commands().to_vec(),
368 authoritative_state_hash: self.authority.authoritative_state_hash()?,
369 checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
370 canonical_boundaries: self.canonical_boundaries,
371 })
372 }Sourcepub fn knowledge(&self) -> &KnowledgeSnapshot
pub fn knowledge(&self) -> &KnowledgeSnapshot
Trusted host/admin access to the complete knowledge snapshot.
Do not expose this public API to player, agent, observer, or remote clients;
use Canwu::viewer or Canwu::viewer_for_actor instead.
Sourcepub fn events(&self) -> &[SimEvent]
pub fn events(&self) -> &[SimEvent]
Examples found in repository?
219 fn render_frame(
220 &mut self,
221 wall_dt: Duration,
222 speed: GameSpeed,
223 phase_edge: bool,
224 ) -> Result<(), CanwuError> {
225 self.frame += 1;
226 self.wall_elapsed += wall_dt;
227
228 let converted_wall_nanos = wall_dt.as_nanos() * BASE_SIM_SECONDS_PER_WALL_SECOND;
229 self.accumulated_sim_nanos += converted_wall_nanos * speed.multiplier();
230
231 let mut authority_changed = false;
232 while self.accumulated_sim_nanos >= SIMULATION_MINUTE_NANOS {
233 let receipts = self.authority.advance_canonical(SIMULATION_QUANTUM)?;
234 self.canonical_boundaries += receipts.len();
235 authority_changed |= !receipts.is_empty();
236 self.accumulated_sim_nanos -= SIMULATION_MINUTE_NANOS;
237 }
238
239 if authority_changed {
240 self.refresh_presentation_from_authority();
241 }
242
243 let progress = self.presentation.render(self.presentation_time());
244 let crossed_milestone = self.crossed_progress_milestone(progress);
245 let has_new_events = self.reported_events < self.authority.events().len();
246
247 if self.verbose && (phase_edge || crossed_milestone || has_new_events) {
248 self.print_frame(speed);
249 }
250 self.report_new_events();
251 Ok(())
252 }
253
254 /// This host never backdates mid-quantum input. It deterministically rounds
255 /// up to the next representable Canwu minute; another host may choose a
256 /// different policy, but it must apply the same policy across render FPS.
257 fn submit_player_command(&mut self) -> Result<(), CanwuError> {
258 let captured_at = self.presentation_time();
259 let due_at = if self.accumulated_sim_nanos == 0 {
260 self.authority.time()
261 } else {
262 self.authority
263 .time()
264 .checked_add(SIMULATION_QUANTUM)
265 .expect("the scripted command time must remain representable")
266 };
267 let envelope = CommandEnvelope::new(
268 Issuer::Actor(self.ids.commander),
269 Command::OrderMovement {
270 subject: EntityRef::Army(self.ids.army),
271 destination: self.ids.eastern_territory,
272 cargo: Vec::new(),
273 },
274 )
275 .at_time(due_at);
276 let request = CommandRequest::new(
277 CommandRequestId::new(1),
278 self.authority.revision(),
279 envelope,
280 );
281 let ingress = self.authority.enqueue_command(due_at, 0, request)?;
282
283 if self.verbose {
284 println!(
285 "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
286 ingress.ingress_id,
287 );
288 }
289
290 // This drains ingress due at the current canonical time only. The
291 // mid-quantum command remains queued until normal accumulation reaches
292 // its quantized minute; future scheduled work is never jumped to.
293 let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
294 self.canonical_boundaries += receipts.len();
295 self.refresh_presentation_from_authority();
296 self.presentation.render(self.presentation_time());
297 if self.verbose {
298 self.print_frame(GameSpeed::X1);
299 }
300 self.report_new_events();
301 Ok(())
302 }
303
304 fn refresh_presentation_from_authority(&mut self) {
305 self.presentation
306 .refresh_from_authority(&self.authority.world(), self.ids.army);
307 }
308
309 fn presentation_time(&self) -> PresentationTime {
310 PresentationTime {
311 canonical: self.authority.time(),
312 sub_minute: Duration::from_nanos(
313 u64::try_from(self.accumulated_sim_nanos)
314 .expect("the sub-minute accumulator must fit a Duration"),
315 ),
316 }
317 }
318
319 fn crossed_progress_milestone(&mut self, progress: Option<f64>) -> bool {
320 let Some(progress) = progress else {
321 return false;
322 };
323 let mut crossed = false;
324 while self.next_progress_milestone <= 100
325 && progress * 100.0 + f64::EPSILON >= f64::from(self.next_progress_milestone)
326 {
327 crossed = true;
328 self.next_progress_milestone += 25;
329 }
330 crossed
331 }
332
333 fn print_frame(&self, speed: GameSpeed) {
334 println!(
335 "frame={:>5} speed={:<6} wall={:>7.3}s visual_time={} canwu_time={} {}",
336 self.frame,
337 speed.label(),
338 self.wall_elapsed.as_secs_f64(),
339 self.presentation_time(),
340 self.authority.time(),
341 self.presentation.describe(),
342 );
343 }
344
345 fn report_new_events(&mut self) {
346 let events = self.authority.events();
347 if self.verbose {
348 for event in &events[self.reported_events..] {
349 println!(
350 "EVENT: at={} type={} {}",
351 event.timestamp,
352 event.kind.qualified_event_type(),
353 event.summary,
354 );
355 }
356 }
357 self.reported_events = events.len();
358 }
359
360 fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
361 Ok(RunOutcome {
362 profile: profile.name,
363 frames: self.frame,
364 time: self.authority.time(),
365 world: self.authority.world(),
366 events: self.authority.events().to_vec(),
367 commands: self.authority.commands().to_vec(),
368 authoritative_state_hash: self.authority.authoritative_state_hash()?,
369 checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
370 canonical_boundaries: self.canonical_boundaries,
371 })
372 }Sourcepub fn commands(&self) -> &[CommandRecord]
pub fn commands(&self) -> &[CommandRecord]
Examples found in repository?
360 fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
361 Ok(RunOutcome {
362 profile: profile.name,
363 frames: self.frame,
364 time: self.authority.time(),
365 world: self.authority.world(),
366 events: self.authority.events().to_vec(),
367 commands: self.authority.commands().to_vec(),
368 authoritative_state_hash: self.authority.authoritative_state_hash()?,
369 checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
370 canonical_boundaries: self.canonical_boundaries,
371 })
372 }Sourcepub fn boundaries(&self) -> &[BoundaryRecord]
pub fn boundaries(&self) -> &[BoundaryRecord]
pub fn command_attempts(&self) -> &[CommandAttemptRecord]
pub fn ingress_log(&self) -> &[IngressRecord]
pub fn domain_record( &self, reference: &DomainRecordRef, ) -> Option<&DomainRecord>
Sourcepub fn domain_record_version_evidence_exists(
&self,
reference: &DomainRecordVersionRef,
) -> bool
pub fn domain_record_version_evidence_exists( &self, reference: &DomainRecordVersionRef, ) -> bool
Returns whether an exact domain-record version exists in current or retained evidence.
Sourcepub fn evidence_exists(&self, reference: &EvidenceRef) -> bool
pub fn evidence_exists(&self, reference: &EvidenceRef) -> bool
Returns whether a generic evidence identity is retained or archived.
Sourcepub fn evidence_time(&self, reference: &EvidenceRef) -> Option<SimTime>
pub fn evidence_time(&self, reference: &EvidenceRef) -> Option<SimTime>
Returns when retained evidence first became authoritative.
Compacted identity-only receipts return None; load the archived
evidence body before making decisions that require temporal ordering.
Sourcepub fn domain_record_version(
&self,
reference: &DomainRecordVersionRef,
) -> Option<DomainRecord>
pub fn domain_record_version( &self, reference: &DomainRecordVersionRef, ) -> Option<DomainRecord>
Resolves the retained record body for one exact domain-record version.
A compacted archive receipt proves existence but does not expose the version body through this trusted-host query.
Sourcepub fn current_domain_record_version(
&self,
reference: &DomainRecordRef,
) -> Result<Option<DomainRecordVersionRef>, CanwuError>
pub fn current_domain_record_version( &self, reference: &DomainRecordRef, ) -> Result<Option<DomainRecordVersionRef>, CanwuError>
Returns the retained or archive-resolvable exact identity for the authoritative current record version. Missing provenance fails closed.
Sourcepub fn typed_domain_record<T: DomainRecordType>(
&self,
reference: &TypedDomainRecordRef<T>,
) -> Option<&DomainRecord>
pub fn typed_domain_record<T: DomainRecordType>( &self, reference: &TypedDomainRecordRef<T>, ) -> Option<&DomainRecord>
Examples found in repository?
549fn main() -> Result<(), CanwuError> {
550 let central = CentralPlugin;
551 let treasury = TreasuryPlugin;
552 let county = CountyPlugin;
553 let plugins: [&dyn SimulationPlugin; 3] = [¢ral, &treasury, &county];
554 let mut canwu =
555 Canwu::new_with_plugins(11, Scenario::new(SimTime::EPOCH, Vec::new()), &plugins)?;
556
557 canwu.enqueue_plugin_ingress(canwu_api::PluginIngressRequest::new(
558 CENTRAL_PLUGIN,
559 ISSUE_INGRESS,
560 SimTime::EPOCH,
561 json!({"order_id": ORDER_ID}),
562 ))?;
563
564 let first = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
565 assert_eq!(first.generated_ingress.len(), 2);
566 assert!(canwu.typed_domain_record(&order_reference()).is_some());
567 assert!(canwu.typed_domain_record(&treasury_reference()).is_none());
568 assert!(canwu.typed_domain_record(&county_reference()).is_none());
569
570 let second = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
571 assert_eq!(second.record_change_count, 2);
572 assert!(canwu.typed_domain_record(&treasury_reference()).is_some());
573 assert!(canwu.typed_domain_record(&county_reference()).is_some());
574
575 let snapshot = canwu.snapshot_json()?;
576 let restored = Canwu::from_snapshot_json_with_plugins(&snapshot, &plugins)?;
577 let replayed = Canwu::replay_from_journal(&plugins, &canwu.replay_journal())?;
578 assert_eq!(restored.snapshot(), canwu.snapshot());
579 assert_eq!(replayed.snapshot(), canwu.snapshot());
580
581 println!(
582 "relief_order={} treasury_grain={} county_grain={} exact_replay=ok",
583 ORDER_ID, 600, 600
584 );
585 Ok(())
586}pub fn domain_records(&self) -> impl Iterator<Item = &DomainRecord>
Sourcepub fn domain_record_page(
&self,
kind: &DomainRecordKind,
after: Option<&DomainRecordRef>,
limit: usize,
expected_revision: Option<u64>,
) -> Result<DomainRecordPage, CanwuError>
pub fn domain_record_page( &self, kind: &DomainRecordKind, after: Option<&DomainRecordRef>, limit: usize, expected_revision: Option<u64>, ) -> Result<DomainRecordPage, CanwuError>
Returns one trusted-host page bound to an authoritative revision.
Use the returned revision as expected_revision on subsequent pages.
Sourcepub fn decision_ticket(&self, id: DecisionTicketId) -> Option<&DecisionTicket>
pub fn decision_ticket(&self, id: DecisionTicketId) -> Option<&DecisionTicket>
Examples found in repository?
174fn llm_interface_branch() -> Result<(), Box<dyn std::error::Error>> {
175 let mut canwu = Canwu::demo(202)?;
176 let ticket_id = DecisionTicketId::new(2);
177 enqueue_ticket(
178 &mut canwu,
179 ticket_id,
180 10,
181 DecisionControllerBinding::new(
182 "law-llm-controller",
183 DecisionPolicyIdentity::new(DecisionPolicyKind::Llm, "strict-law-selector", "1"),
184 DecisionAuthority::Actor {
185 actor: Canwu::demo_ids().commander,
186 },
187 ),
188 )?;
189 let mut policy = QueuedLlmPolicy::new(
190 "strict-law-selector",
191 "1",
192 LlmModelIdentity {
193 provider: "not-connected".to_owned(),
194 model: "host-selected-model".to_owned(),
195 prompt_contract: "return one existing option_id and no new action".to_owned(),
196 },
197 );
198 let ticket = canwu.decision_ticket(ticket_id).expect("LLM ticket");
199 let request = policy.external_request(ticket);
200 println!("llm_request={}", serde_json::to_string(&request)?);
201
202 // A real host sends `request` to its model and parses a strict structured
203 // answer. This example supplies the same response object without making a
204 // network call.
205 policy.submit(
206 ticket_id,
207 ExternalDecisionResponse {
208 ticket_version: request.ticket_version,
209 option_id: "pass".to_owned(),
210 provider: "not-connected".to_owned(),
211 request_id: "example-response-1".to_owned(),
212 metadata: BTreeMap::new(),
213 },
214 )?;
215 let evaluation = canwu.drive_decision(
216 canwu.time(),
217 0,
218 DecisionRequestId::new(12),
219 None,
220 ticket_id,
221 &policy,
222 )?;
223 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
224 canwu.step_canonical()?.expect("LLM decision boundary");
225 Ok(())
226}pub fn decision_controller( &self, id: &str, ) -> Option<&DecisionControllerBinding>
Sourcepub fn decision_trace(&self, id: DecisionTraceId) -> Option<&DecisionTrace>
pub fn decision_trace(&self, id: DecisionTraceId) -> Option<&DecisionTrace>
Examples found in repository?
11fn main() -> Result<(), Box<dyn std::error::Error>> {
12 let mut canwu = Canwu::demo(1918)?;
13 let ids = Canwu::demo_ids();
14
15 open_aid_request(&mut canwu, ids)?;
16 refresh_aid_options(&mut canwu, ids)?;
17 resolve_aid_request(&mut canwu)?;
18
19 let trace = canwu
20 .decision_trace(canwu_api::DecisionTraceId::new(1))
21 .expect("utility decision trace");
22 println!("{}", serde_json::to_string_pretty(trace)?);
23
24 verify_persistence_and_replay(&canwu)?;
25 println!("snapshot_restore=ok exact_replay=ok");
26 Ok(())
27}More examples
146fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
147 let mut canwu = Canwu::demo(202)?;
148 canwu.register_plugin(&UncertaintyPlugin)?;
149 enqueue_ticket(
150 &mut canwu,
151 RANDOM_TICKET,
152 1,
153 DecisionControllerBinding::new(
154 "law-random-controller",
155 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
156 DecisionAuthority::Actor {
157 actor: Canwu::demo_ids().commander,
158 },
159 ),
160 )?;
161 let selection_at = SimTime::EPOCH + SimDuration::days(1);
162 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
163 canwu.step_canonical()?.expect("random source boundary");
164 canwu
165 .step_canonical()?
166 .expect("generated decision resolution boundary");
167 let trace = canwu
168 .decision_trace(canwu_api::DecisionTraceId::new(1))
169 .expect("random decision trace");
170 println!("random_trace={}", serde_json::to_string(trace)?);
171 Ok(())
172}pub fn decision_attempt( &self, id: DecisionRequestId, ) -> Option<&DecisionAttemptRecord>
pub fn decision_hot_state(&self) -> DecisionHotState
pub fn decision_history_location( &self, key: &DecisionHistoryKey, ) -> DecisionHistoryLocation
pub fn decision_history_location_with_provider( &self, key: &DecisionHistoryKey, provider: &dyn DecisionArchiveProvider, ) -> Result<DecisionHistoryLocation, CanwuError>
pub fn random_draws(&self) -> &[RandomDrawRecord]
pub fn boundary_head_hash(&self) -> Option<&str>
pub const fn schema(&self) -> &SchemaRegistry
pub fn plugin_descriptors(&self) -> impl Iterator<Item = &PluginDescriptor>
Sourcepub fn replay_journal(&self) -> ReplayJournal
pub fn replay_journal(&self) -> ReplayJournal
Examples found in repository?
More examples
549fn main() -> Result<(), CanwuError> {
550 let central = CentralPlugin;
551 let treasury = TreasuryPlugin;
552 let county = CountyPlugin;
553 let plugins: [&dyn SimulationPlugin; 3] = [¢ral, &treasury, &county];
554 let mut canwu =
555 Canwu::new_with_plugins(11, Scenario::new(SimTime::EPOCH, Vec::new()), &plugins)?;
556
557 canwu.enqueue_plugin_ingress(canwu_api::PluginIngressRequest::new(
558 CENTRAL_PLUGIN,
559 ISSUE_INGRESS,
560 SimTime::EPOCH,
561 json!({"order_id": ORDER_ID}),
562 ))?;
563
564 let first = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
565 assert_eq!(first.generated_ingress.len(), 2);
566 assert!(canwu.typed_domain_record(&order_reference()).is_some());
567 assert!(canwu.typed_domain_record(&treasury_reference()).is_none());
568 assert!(canwu.typed_domain_record(&county_reference()).is_none());
569
570 let second = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
571 assert_eq!(second.record_change_count, 2);
572 assert!(canwu.typed_domain_record(&treasury_reference()).is_some());
573 assert!(canwu.typed_domain_record(&county_reference()).is_some());
574
575 let snapshot = canwu.snapshot_json()?;
576 let restored = Canwu::from_snapshot_json_with_plugins(&snapshot, &plugins)?;
577 let replayed = Canwu::replay_from_journal(&plugins, &canwu.replay_journal())?;
578 assert_eq!(restored.snapshot(), canwu.snapshot());
579 assert_eq!(replayed.snapshot(), canwu.snapshot());
580
581 println!(
582 "relief_order={} treasury_grain={} county_grain={} exact_replay=ok",
583 ORDER_ID, 600, 600
584 );
585 Ok(())
586}pub fn outbox_entries(&self) -> Result<Vec<OutboxEntry>, CanwuError>
pub fn evidence_cursor(&self) -> Result<EvidenceCursor, CanwuError>
pub fn checkpoint(&self) -> Result<SimulationCheckpoint, CanwuError>
pub fn archive_reachability_manifest( &self, retained_checkpoints: &[SimulationCheckpoint], page_retention: &StatePageRetentionLedger, decision_provider: &dyn DecisionArchiveProvider, plugin_provider: &dyn PluginArchiveObjectProvider, ) -> Result<ArchiveReachabilityManifest, CanwuError>
pub fn journal_segment_since( &self, start: EvidenceCursor, ) -> Result<EvidenceJournalSegment, CanwuError>
pub fn checkpoint_journal(&self) -> Result<CheckpointJournal, CanwuError>
pub fn checkpoint_journal_json(&self) -> Result<String, CanwuError>
Sourcepub fn register_plugin<P: SimulationPlugin + ?Sized>(
&mut self,
plugin: &P,
) -> Result<(), CanwuError>
pub fn register_plugin<P: SimulationPlugin + ?Sized>( &mut self, plugin: &P, ) -> Result<(), CanwuError>
Examples found in repository?
More examples
88fn main() -> Result<(), CanwuError> {
89 let mut canwu = Canwu::demo(35)?;
90 let ids = Canwu::demo_ids();
91 canwu.register_plugin(&StancePlugin)?;
92 canwu.submit(CommandEnvelope::new(
93 Issuer::Actor(ids.commander),
94 Command::Plugin {
95 plugin: "example-stance".to_owned(),
96 command: "set_stance".to_owned(),
97 payload: json!({ "army": ids.army, "stance": "hold" }),
98 },
99 ))?;
100 Ok(())
101}146fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
147 let mut canwu = Canwu::demo(202)?;
148 canwu.register_plugin(&UncertaintyPlugin)?;
149 enqueue_ticket(
150 &mut canwu,
151 RANDOM_TICKET,
152 1,
153 DecisionControllerBinding::new(
154 "law-random-controller",
155 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
156 DecisionAuthority::Actor {
157 actor: Canwu::demo_ids().commander,
158 },
159 ),
160 )?;
161 let selection_at = SimTime::EPOCH + SimDuration::days(1);
162 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
163 canwu.step_canonical()?.expect("random source boundary");
164 canwu
165 .step_canonical()?
166 .expect("generated decision resolution boundary");
167 let trace = canwu
168 .decision_trace(canwu_api::DecisionTraceId::new(1))
169 .expect("random decision trace");
170 println!("random_trace={}", serde_json::to_string(trace)?);
171 Ok(())
172}Sourcepub fn set_plugin_archive_object_provider(
&mut self,
provider: Rc<dyn PluginArchiveObjectProvider>,
)
pub fn set_plugin_archive_object_provider( &mut self, provider: Rc<dyn PluginArchiveObjectProvider>, )
Attaches caller-owned package archive storage for authenticated cold history resolution during normal admissions, settlement, and queries.
Sourcepub fn plugin_archive_object(
&self,
namespace: &str,
object_id: &str,
) -> Result<Option<Vec<u8>>, CanwuError>
pub fn plugin_archive_object( &self, namespace: &str, object_id: &str, ) -> Result<Option<Vec<u8>>, CanwuError>
Loads one opaque package archive object from the attached provider. Package integrations authenticate the bytes against their committed archive roots before use.
Sourcepub fn submit(
&mut self,
command: CommandEnvelope,
) -> Result<CommandReceipt, CanwuError>
pub fn submit( &mut self, command: CommandEnvelope, ) -> Result<CommandReceipt, CanwuError>
Examples found in repository?
88fn main() -> Result<(), CanwuError> {
89 let mut canwu = Canwu::demo(35)?;
90 let ids = Canwu::demo_ids();
91 canwu.register_plugin(&StancePlugin)?;
92 canwu.submit(CommandEnvelope::new(
93 Issuer::Actor(ids.commander),
94 Command::Plugin {
95 plugin: "example-stance".to_owned(),
96 command: "set_stance".to_owned(),
97 payload: json!({ "army": ids.army, "stance": "hold" }),
98 },
99 ))?;
100 Ok(())
101}pub fn process_command( &mut self, request: CommandRequest, ) -> Result<CommandOutcome, CanwuError>
Sourcepub fn enqueue_command(
&mut self,
due_at: SimTime,
priority: i32,
request: CommandRequest,
) -> Result<IngressReceipt, CanwuError>
pub fn enqueue_command( &mut self, due_at: SimTime, priority: i32, request: CommandRequest, ) -> Result<IngressReceipt, CanwuError>
Examples found in repository?
257 fn submit_player_command(&mut self) -> Result<(), CanwuError> {
258 let captured_at = self.presentation_time();
259 let due_at = if self.accumulated_sim_nanos == 0 {
260 self.authority.time()
261 } else {
262 self.authority
263 .time()
264 .checked_add(SIMULATION_QUANTUM)
265 .expect("the scripted command time must remain representable")
266 };
267 let envelope = CommandEnvelope::new(
268 Issuer::Actor(self.ids.commander),
269 Command::OrderMovement {
270 subject: EntityRef::Army(self.ids.army),
271 destination: self.ids.eastern_territory,
272 cargo: Vec::new(),
273 },
274 )
275 .at_time(due_at);
276 let request = CommandRequest::new(
277 CommandRequestId::new(1),
278 self.authority.revision(),
279 envelope,
280 );
281 let ingress = self.authority.enqueue_command(due_at, 0, request)?;
282
283 if self.verbose {
284 println!(
285 "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
286 ingress.ingress_id,
287 );
288 }
289
290 // This drains ingress due at the current canonical time only. The
291 // mid-quantum command remains queued until normal accumulation reaches
292 // its quantized minute; future scheduled work is never jumped to.
293 let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
294 self.canonical_boundaries += receipts.len();
295 self.refresh_presentation_from_authority();
296 self.presentation.render(self.presentation_time());
297 if self.verbose {
298 self.print_frame(GameSpeed::X1);
299 }
300 self.report_new_events();
301 Ok(())
302 }Sourcepub fn enqueue_plugin_ingress(
&mut self,
request: PluginIngressRequest,
) -> Result<IngressReceipt, CanwuError>
pub fn enqueue_plugin_ingress( &mut self, request: PluginIngressRequest, ) -> Result<IngressReceipt, CanwuError>
Examples found in repository?
549fn main() -> Result<(), CanwuError> {
550 let central = CentralPlugin;
551 let treasury = TreasuryPlugin;
552 let county = CountyPlugin;
553 let plugins: [&dyn SimulationPlugin; 3] = [¢ral, &treasury, &county];
554 let mut canwu =
555 Canwu::new_with_plugins(11, Scenario::new(SimTime::EPOCH, Vec::new()), &plugins)?;
556
557 canwu.enqueue_plugin_ingress(canwu_api::PluginIngressRequest::new(
558 CENTRAL_PLUGIN,
559 ISSUE_INGRESS,
560 SimTime::EPOCH,
561 json!({"order_id": ORDER_ID}),
562 ))?;
563
564 let first = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
565 assert_eq!(first.generated_ingress.len(), 2);
566 assert!(canwu.typed_domain_record(&order_reference()).is_some());
567 assert!(canwu.typed_domain_record(&treasury_reference()).is_none());
568 assert!(canwu.typed_domain_record(&county_reference()).is_none());
569
570 let second = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
571 assert_eq!(second.record_change_count, 2);
572 assert!(canwu.typed_domain_record(&treasury_reference()).is_some());
573 assert!(canwu.typed_domain_record(&county_reference()).is_some());
574
575 let snapshot = canwu.snapshot_json()?;
576 let restored = Canwu::from_snapshot_json_with_plugins(&snapshot, &plugins)?;
577 let replayed = Canwu::replay_from_journal(&plugins, &canwu.replay_journal())?;
578 assert_eq!(restored.snapshot(), canwu.snapshot());
579 assert_eq!(replayed.snapshot(), canwu.snapshot());
580
581 println!(
582 "relief_order={} treasury_grain={} county_grain={} exact_replay=ok",
583 ORDER_ID, 600, 600
584 );
585 Ok(())
586}pub fn enqueue_permitted_plugin_ingress( &mut self, request: PluginIngressRequest, permit: &PluginIngressPermit, ) -> Result<IngressReceipt, CanwuError>
pub fn prepare_decision( &self, decision_request_id: DecisionRequestId, command_request_id: Option<CommandRequestId>, ticket_id: DecisionTicketId, policy: &dyn DecisionPolicy, ) -> Result<DecisionEvaluation, CanwuError>
pub fn prepare_decision_at( &self, due_at: SimTime, decision_request_id: DecisionRequestId, command_request_id: Option<CommandRequestId>, ticket_id: DecisionTicketId, policy: &dyn DecisionPolicy, ) -> Result<DecisionEvaluation, CanwuError>
Sourcepub fn enqueue_decision(
&mut self,
due_at: SimTime,
priority: i32,
request: DecisionIngressRequest,
) -> Result<IngressReceipt, CanwuError>
pub fn enqueue_decision( &mut self, due_at: SimTime, priority: i32, request: DecisionIngressRequest, ) -> Result<IngressReceipt, CanwuError>
Examples found in repository?
95fn enqueue_ticket(
96 canwu: &mut Canwu,
97 ticket_id: DecisionTicketId,
98 request_offset: u64,
99 controller: DecisionControllerBinding,
100) -> Result<(), CanwuError> {
101 let now = canwu.time();
102 let controller_id = controller.id.clone();
103 canwu.enqueue_decision(
104 now,
105 0,
106 DecisionIngressRequest::new(
107 DecisionRequestId::new(request_offset),
108 canwu.revision(),
109 DecisionMutation::RegisterController { controller },
110 ),
111 )?;
112 canwu.enqueue_decision(
113 now,
114 0,
115 DecisionIngressRequest::new(
116 DecisionRequestId::new(request_offset + 1),
117 canwu.revision(),
118 DecisionMutation::Open {
119 ticket: DecisionTicketDraft {
120 id: ticket_id,
121 definition: "example.law-passage".to_owned(),
122 decision_maker: EntityRef::Person(Canwu::demo_ids().commander),
123 assigned_controller: controller_id,
124 summary: "Will the proposed law pass?".to_owned(),
125 context: DecisionContext::new(
126 "example.law-passage.v1",
127 json!({
128 "supporting_seats": 72,
129 "opposing_seats": 28,
130 "public_pressure": "high"
131 }),
132 ),
133 options: vec![
134 DecisionOption::new("pass", "Pass the law"),
135 DecisionOption::new("fail", "Reject the law"),
136 ],
137 deadline: None,
138 },
139 },
140 ),
141 )?;
142 canwu.settle_boundary(canwu_api::BoundaryRequest::at(now))?;
143 Ok(())
144}More examples
29fn open_aid_request(canwu: &mut Canwu, ids: DemoIds) -> Result<(), Box<dyn std::error::Error>> {
30 let now = canwu.time();
31 let controller = DecisionControllerBinding::new(
32 "warlord-b-ai",
33 DecisionPolicyIdentity::new(DecisionPolicyKind::Utility, "aid-utility", "1"),
34 DecisionAuthority::Actor {
35 actor: ids.commander,
36 },
37 )
38 .with_command_subject(EntityRef::Army(ids.army));
39
40 canwu.enqueue_decision(
41 now,
42 0,
43 DecisionIngressRequest::new(
44 DecisionRequestId::new(1),
45 canwu.revision(),
46 DecisionMutation::RegisterController { controller },
47 ),
48 )?;
49 canwu.enqueue_decision(
50 now,
51 0,
52 DecisionIngressRequest::new(
53 DecisionRequestId::new(2),
54 canwu.revision(),
55 DecisionMutation::Open {
56 ticket: DecisionTicketDraft {
57 id: DecisionTicketId::new(1),
58 definition: "beiyang.request-military-aid".to_owned(),
59 decision_maker: EntityRef::Person(ids.commander),
60 assigned_controller: "warlord-b-ai".to_owned(),
61 summary: "Neighboring warlord requests immediate military aid".to_owned(),
62 context: DecisionContext::new(
63 "beiyang.aid-request.v1",
64 json!({
65 "requester": "warlord-a",
66 "battle": "ongoing-front",
67 "common_enemy": true
68 }),
69 ),
70 options: vec![DecisionOption {
71 action: DecisionAction::None,
72 utility_inputs: BTreeMap::from([
73 ("home_defense".to_owned(), 80),
74 ("alliance".to_owned(), -40),
75 ]),
76 ..DecisionOption::new("decline", "Decline aid")
77 }],
78 deadline: Some(now + SimDuration::days(2)),
79 },
80 },
81 ),
82 )?;
83 canwu.step_canonical()?.expect("decision intake boundary");
84 Ok(())
85}
86
87fn refresh_aid_options(canwu: &mut Canwu, ids: DemoIds) -> Result<(), Box<dyn std::error::Error>> {
88 canwu.enqueue_decision(
89 canwu.time(),
90 0,
91 DecisionIngressRequest::new(
92 DecisionRequestId::new(3),
93 canwu.revision(),
94 DecisionMutation::ReplaceOptions {
95 ticket_id: DecisionTicketId::new(1),
96 expected_version: 1,
97 context: DecisionContext::new(
98 "beiyang.aid-request.v1",
99 json!({
100 "requester": "warlord-a",
101 "battle": "ongoing-front",
102 "common_enemy": true,
103 "route_confirmed": true
104 }),
105 ),
106 options: vec![
107 DecisionOption {
108 action: DecisionAction::None,
109 utility_inputs: BTreeMap::from([
110 ("home_defense".to_owned(), 80),
111 ("alliance".to_owned(), -40),
112 ]),
113 ..DecisionOption::new("decline", "Decline aid")
114 },
115 DecisionOption {
116 action: DecisionAction::Command {
117 command: serde_json::to_value(Command::OrderMovement {
118 subject: EntityRef::Army(ids.army),
119 destination: ids.eastern_territory,
120 cargo: Vec::new(),
121 })?,
122 },
123 utility_inputs: BTreeMap::from([
124 ("home_defense".to_owned(), -20),
125 ("alliance".to_owned(), 90),
126 ]),
127 ..DecisionOption::new("send-aid", "Send the neighboring army")
128 },
129 ],
130 },
131 ),
132 )?;
133 canwu
134 .step_canonical()?
135 .expect("dynamic option refresh boundary");
136 Ok(())
137}Sourcepub fn drive_decision(
&mut self,
due_at: SimTime,
priority: i32,
decision_request_id: DecisionRequestId,
command_request_id: Option<CommandRequestId>,
ticket_id: DecisionTicketId,
policy: &dyn DecisionPolicy,
) -> Result<DecisionEvaluation, CanwuError>
pub fn drive_decision( &mut self, due_at: SimTime, priority: i32, decision_request_id: DecisionRequestId, command_request_id: Option<CommandRequestId>, ticket_id: DecisionTicketId, policy: &dyn DecisionPolicy, ) -> Result<DecisionEvaluation, CanwuError>
Examples found in repository?
139fn resolve_aid_request(canwu: &mut Canwu) -> Result<(), Box<dyn std::error::Error>> {
140 let policy = WeightedUtilityPolicy::new(
141 "aid-utility",
142 "1",
143 UtilityProfile {
144 weights: BTreeMap::from([("alliance".to_owned(), 3), ("home_defense".to_owned(), 1)]),
145 },
146 );
147 let evaluation = canwu.drive_decision(
148 canwu.time(),
149 0,
150 DecisionRequestId::new(4),
151 Some(CommandRequestId::new(1)),
152 DecisionTicketId::new(1),
153 &policy,
154 )?;
155 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
156 canwu
157 .step_canonical()?
158 .expect("decision resolution boundary");
159 Ok(())
160}More examples
174fn llm_interface_branch() -> Result<(), Box<dyn std::error::Error>> {
175 let mut canwu = Canwu::demo(202)?;
176 let ticket_id = DecisionTicketId::new(2);
177 enqueue_ticket(
178 &mut canwu,
179 ticket_id,
180 10,
181 DecisionControllerBinding::new(
182 "law-llm-controller",
183 DecisionPolicyIdentity::new(DecisionPolicyKind::Llm, "strict-law-selector", "1"),
184 DecisionAuthority::Actor {
185 actor: Canwu::demo_ids().commander,
186 },
187 ),
188 )?;
189 let mut policy = QueuedLlmPolicy::new(
190 "strict-law-selector",
191 "1",
192 LlmModelIdentity {
193 provider: "not-connected".to_owned(),
194 model: "host-selected-model".to_owned(),
195 prompt_contract: "return one existing option_id and no new action".to_owned(),
196 },
197 );
198 let ticket = canwu.decision_ticket(ticket_id).expect("LLM ticket");
199 let request = policy.external_request(ticket);
200 println!("llm_request={}", serde_json::to_string(&request)?);
201
202 // A real host sends `request` to its model and parses a strict structured
203 // answer. This example supplies the same response object without making a
204 // network call.
205 policy.submit(
206 ticket_id,
207 ExternalDecisionResponse {
208 ticket_version: request.ticket_version,
209 option_id: "pass".to_owned(),
210 provider: "not-connected".to_owned(),
211 request_id: "example-response-1".to_owned(),
212 metadata: BTreeMap::new(),
213 },
214 )?;
215 let evaluation = canwu.drive_decision(
216 canwu.time(),
217 0,
218 DecisionRequestId::new(12),
219 None,
220 ticket_id,
221 &policy,
222 )?;
223 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
224 canwu.step_canonical()?.expect("LLM decision boundary");
225 Ok(())
226}Sourcepub fn schedule_calendar_boundary(
&mut self,
due_at: SimTime,
cadences: Vec<SystemCadence>,
) -> Result<IngressReceipt, CanwuError>
pub fn schedule_calendar_boundary( &mut self, due_at: SimTime, cadences: Vec<SystemCadence>, ) -> Result<IngressReceipt, CanwuError>
Examples found in repository?
146fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
147 let mut canwu = Canwu::demo(202)?;
148 canwu.register_plugin(&UncertaintyPlugin)?;
149 enqueue_ticket(
150 &mut canwu,
151 RANDOM_TICKET,
152 1,
153 DecisionControllerBinding::new(
154 "law-random-controller",
155 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
156 DecisionAuthority::Actor {
157 actor: Canwu::demo_ids().commander,
158 },
159 ),
160 )?;
161 let selection_at = SimTime::EPOCH + SimDuration::days(1);
162 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
163 canwu.step_canonical()?.expect("random source boundary");
164 canwu
165 .step_canonical()?
166 .expect("generated decision resolution boundary");
167 let trace = canwu
168 .decision_trace(canwu_api::DecisionTraceId::new(1))
169 .expect("random decision trace");
170 println!("random_trace={}", serde_json::to_string(trace)?);
171 Ok(())
172}Sourcepub fn advance_canonical(
&mut self,
duration: SimDuration,
) -> Result<Vec<BoundaryReceipt>, CanwuError>
pub fn advance_canonical( &mut self, duration: SimDuration, ) -> Result<Vec<BoundaryReceipt>, CanwuError>
Examples found in repository?
219 fn render_frame(
220 &mut self,
221 wall_dt: Duration,
222 speed: GameSpeed,
223 phase_edge: bool,
224 ) -> Result<(), CanwuError> {
225 self.frame += 1;
226 self.wall_elapsed += wall_dt;
227
228 let converted_wall_nanos = wall_dt.as_nanos() * BASE_SIM_SECONDS_PER_WALL_SECOND;
229 self.accumulated_sim_nanos += converted_wall_nanos * speed.multiplier();
230
231 let mut authority_changed = false;
232 while self.accumulated_sim_nanos >= SIMULATION_MINUTE_NANOS {
233 let receipts = self.authority.advance_canonical(SIMULATION_QUANTUM)?;
234 self.canonical_boundaries += receipts.len();
235 authority_changed |= !receipts.is_empty();
236 self.accumulated_sim_nanos -= SIMULATION_MINUTE_NANOS;
237 }
238
239 if authority_changed {
240 self.refresh_presentation_from_authority();
241 }
242
243 let progress = self.presentation.render(self.presentation_time());
244 let crossed_milestone = self.crossed_progress_milestone(progress);
245 let has_new_events = self.reported_events < self.authority.events().len();
246
247 if self.verbose && (phase_edge || crossed_milestone || has_new_events) {
248 self.print_frame(speed);
249 }
250 self.report_new_events();
251 Ok(())
252 }
253
254 /// This host never backdates mid-quantum input. It deterministically rounds
255 /// up to the next representable Canwu minute; another host may choose a
256 /// different policy, but it must apply the same policy across render FPS.
257 fn submit_player_command(&mut self) -> Result<(), CanwuError> {
258 let captured_at = self.presentation_time();
259 let due_at = if self.accumulated_sim_nanos == 0 {
260 self.authority.time()
261 } else {
262 self.authority
263 .time()
264 .checked_add(SIMULATION_QUANTUM)
265 .expect("the scripted command time must remain representable")
266 };
267 let envelope = CommandEnvelope::new(
268 Issuer::Actor(self.ids.commander),
269 Command::OrderMovement {
270 subject: EntityRef::Army(self.ids.army),
271 destination: self.ids.eastern_territory,
272 cargo: Vec::new(),
273 },
274 )
275 .at_time(due_at);
276 let request = CommandRequest::new(
277 CommandRequestId::new(1),
278 self.authority.revision(),
279 envelope,
280 );
281 let ingress = self.authority.enqueue_command(due_at, 0, request)?;
282
283 if self.verbose {
284 println!(
285 "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
286 ingress.ingress_id,
287 );
288 }
289
290 // This drains ingress due at the current canonical time only. The
291 // mid-quantum command remains queued until normal accumulation reaches
292 // its quantized minute; future scheduled work is never jumped to.
293 let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
294 self.canonical_boundaries += receipts.len();
295 self.refresh_presentation_from_authority();
296 self.presentation.render(self.presentation_time());
297 if self.verbose {
298 self.print_frame(GameSpeed::X1);
299 }
300 self.report_new_events();
301 Ok(())
302 }Sourcepub fn step_canonical(&mut self) -> Result<Option<BoundaryReceipt>, CanwuError>
pub fn step_canonical(&mut self) -> Result<Option<BoundaryReceipt>, CanwuError>
Examples found in repository?
146fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
147 let mut canwu = Canwu::demo(202)?;
148 canwu.register_plugin(&UncertaintyPlugin)?;
149 enqueue_ticket(
150 &mut canwu,
151 RANDOM_TICKET,
152 1,
153 DecisionControllerBinding::new(
154 "law-random-controller",
155 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
156 DecisionAuthority::Actor {
157 actor: Canwu::demo_ids().commander,
158 },
159 ),
160 )?;
161 let selection_at = SimTime::EPOCH + SimDuration::days(1);
162 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
163 canwu.step_canonical()?.expect("random source boundary");
164 canwu
165 .step_canonical()?
166 .expect("generated decision resolution boundary");
167 let trace = canwu
168 .decision_trace(canwu_api::DecisionTraceId::new(1))
169 .expect("random decision trace");
170 println!("random_trace={}", serde_json::to_string(trace)?);
171 Ok(())
172}
173
174fn llm_interface_branch() -> Result<(), Box<dyn std::error::Error>> {
175 let mut canwu = Canwu::demo(202)?;
176 let ticket_id = DecisionTicketId::new(2);
177 enqueue_ticket(
178 &mut canwu,
179 ticket_id,
180 10,
181 DecisionControllerBinding::new(
182 "law-llm-controller",
183 DecisionPolicyIdentity::new(DecisionPolicyKind::Llm, "strict-law-selector", "1"),
184 DecisionAuthority::Actor {
185 actor: Canwu::demo_ids().commander,
186 },
187 ),
188 )?;
189 let mut policy = QueuedLlmPolicy::new(
190 "strict-law-selector",
191 "1",
192 LlmModelIdentity {
193 provider: "not-connected".to_owned(),
194 model: "host-selected-model".to_owned(),
195 prompt_contract: "return one existing option_id and no new action".to_owned(),
196 },
197 );
198 let ticket = canwu.decision_ticket(ticket_id).expect("LLM ticket");
199 let request = policy.external_request(ticket);
200 println!("llm_request={}", serde_json::to_string(&request)?);
201
202 // A real host sends `request` to its model and parses a strict structured
203 // answer. This example supplies the same response object without making a
204 // network call.
205 policy.submit(
206 ticket_id,
207 ExternalDecisionResponse {
208 ticket_version: request.ticket_version,
209 option_id: "pass".to_owned(),
210 provider: "not-connected".to_owned(),
211 request_id: "example-response-1".to_owned(),
212 metadata: BTreeMap::new(),
213 },
214 )?;
215 let evaluation = canwu.drive_decision(
216 canwu.time(),
217 0,
218 DecisionRequestId::new(12),
219 None,
220 ticket_id,
221 &policy,
222 )?;
223 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
224 canwu.step_canonical()?.expect("LLM decision boundary");
225 Ok(())
226}More examples
29fn open_aid_request(canwu: &mut Canwu, ids: DemoIds) -> Result<(), Box<dyn std::error::Error>> {
30 let now = canwu.time();
31 let controller = DecisionControllerBinding::new(
32 "warlord-b-ai",
33 DecisionPolicyIdentity::new(DecisionPolicyKind::Utility, "aid-utility", "1"),
34 DecisionAuthority::Actor {
35 actor: ids.commander,
36 },
37 )
38 .with_command_subject(EntityRef::Army(ids.army));
39
40 canwu.enqueue_decision(
41 now,
42 0,
43 DecisionIngressRequest::new(
44 DecisionRequestId::new(1),
45 canwu.revision(),
46 DecisionMutation::RegisterController { controller },
47 ),
48 )?;
49 canwu.enqueue_decision(
50 now,
51 0,
52 DecisionIngressRequest::new(
53 DecisionRequestId::new(2),
54 canwu.revision(),
55 DecisionMutation::Open {
56 ticket: DecisionTicketDraft {
57 id: DecisionTicketId::new(1),
58 definition: "beiyang.request-military-aid".to_owned(),
59 decision_maker: EntityRef::Person(ids.commander),
60 assigned_controller: "warlord-b-ai".to_owned(),
61 summary: "Neighboring warlord requests immediate military aid".to_owned(),
62 context: DecisionContext::new(
63 "beiyang.aid-request.v1",
64 json!({
65 "requester": "warlord-a",
66 "battle": "ongoing-front",
67 "common_enemy": true
68 }),
69 ),
70 options: vec![DecisionOption {
71 action: DecisionAction::None,
72 utility_inputs: BTreeMap::from([
73 ("home_defense".to_owned(), 80),
74 ("alliance".to_owned(), -40),
75 ]),
76 ..DecisionOption::new("decline", "Decline aid")
77 }],
78 deadline: Some(now + SimDuration::days(2)),
79 },
80 },
81 ),
82 )?;
83 canwu.step_canonical()?.expect("decision intake boundary");
84 Ok(())
85}
86
87fn refresh_aid_options(canwu: &mut Canwu, ids: DemoIds) -> Result<(), Box<dyn std::error::Error>> {
88 canwu.enqueue_decision(
89 canwu.time(),
90 0,
91 DecisionIngressRequest::new(
92 DecisionRequestId::new(3),
93 canwu.revision(),
94 DecisionMutation::ReplaceOptions {
95 ticket_id: DecisionTicketId::new(1),
96 expected_version: 1,
97 context: DecisionContext::new(
98 "beiyang.aid-request.v1",
99 json!({
100 "requester": "warlord-a",
101 "battle": "ongoing-front",
102 "common_enemy": true,
103 "route_confirmed": true
104 }),
105 ),
106 options: vec![
107 DecisionOption {
108 action: DecisionAction::None,
109 utility_inputs: BTreeMap::from([
110 ("home_defense".to_owned(), 80),
111 ("alliance".to_owned(), -40),
112 ]),
113 ..DecisionOption::new("decline", "Decline aid")
114 },
115 DecisionOption {
116 action: DecisionAction::Command {
117 command: serde_json::to_value(Command::OrderMovement {
118 subject: EntityRef::Army(ids.army),
119 destination: ids.eastern_territory,
120 cargo: Vec::new(),
121 })?,
122 },
123 utility_inputs: BTreeMap::from([
124 ("home_defense".to_owned(), -20),
125 ("alliance".to_owned(), 90),
126 ]),
127 ..DecisionOption::new("send-aid", "Send the neighboring army")
128 },
129 ],
130 },
131 ),
132 )?;
133 canwu
134 .step_canonical()?
135 .expect("dynamic option refresh boundary");
136 Ok(())
137}
138
139fn resolve_aid_request(canwu: &mut Canwu) -> Result<(), Box<dyn std::error::Error>> {
140 let policy = WeightedUtilityPolicy::new(
141 "aid-utility",
142 "1",
143 UtilityProfile {
144 weights: BTreeMap::from([("alliance".to_owned(), 3), ("home_defense".to_owned(), 1)]),
145 },
146 );
147 let evaluation = canwu.drive_decision(
148 canwu.time(),
149 0,
150 DecisionRequestId::new(4),
151 Some(CommandRequestId::new(1)),
152 DecisionTicketId::new(1),
153 &policy,
154 )?;
155 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
156 canwu
157 .step_canonical()?
158 .expect("decision resolution boundary");
159 Ok(())
160}pub fn advance( &mut self, duration: SimDuration, ) -> Result<Vec<SimEvent>, CanwuError>
Sourcepub fn settle_boundary(
&mut self,
request: BoundaryRequest,
) -> Result<BoundaryReceipt, CanwuError>
pub fn settle_boundary( &mut self, request: BoundaryRequest, ) -> Result<BoundaryReceipt, CanwuError>
Examples found in repository?
More examples
549fn main() -> Result<(), CanwuError> {
550 let central = CentralPlugin;
551 let treasury = TreasuryPlugin;
552 let county = CountyPlugin;
553 let plugins: [&dyn SimulationPlugin; 3] = [¢ral, &treasury, &county];
554 let mut canwu =
555 Canwu::new_with_plugins(11, Scenario::new(SimTime::EPOCH, Vec::new()), &plugins)?;
556
557 canwu.enqueue_plugin_ingress(canwu_api::PluginIngressRequest::new(
558 CENTRAL_PLUGIN,
559 ISSUE_INGRESS,
560 SimTime::EPOCH,
561 json!({"order_id": ORDER_ID}),
562 ))?;
563
564 let first = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
565 assert_eq!(first.generated_ingress.len(), 2);
566 assert!(canwu.typed_domain_record(&order_reference()).is_some());
567 assert!(canwu.typed_domain_record(&treasury_reference()).is_none());
568 assert!(canwu.typed_domain_record(&county_reference()).is_none());
569
570 let second = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
571 assert_eq!(second.record_change_count, 2);
572 assert!(canwu.typed_domain_record(&treasury_reference()).is_some());
573 assert!(canwu.typed_domain_record(&county_reference()).is_some());
574
575 let snapshot = canwu.snapshot_json()?;
576 let restored = Canwu::from_snapshot_json_with_plugins(&snapshot, &plugins)?;
577 let replayed = Canwu::replay_from_journal(&plugins, &canwu.replay_journal())?;
578 assert_eq!(restored.snapshot(), canwu.snapshot());
579 assert_eq!(replayed.snapshot(), canwu.snapshot());
580
581 println!(
582 "relief_order={} treasury_grain={} county_grain={} exact_replay=ok",
583 ORDER_ID, 600, 600
584 );
585 Ok(())
586}95fn enqueue_ticket(
96 canwu: &mut Canwu,
97 ticket_id: DecisionTicketId,
98 request_offset: u64,
99 controller: DecisionControllerBinding,
100) -> Result<(), CanwuError> {
101 let now = canwu.time();
102 let controller_id = controller.id.clone();
103 canwu.enqueue_decision(
104 now,
105 0,
106 DecisionIngressRequest::new(
107 DecisionRequestId::new(request_offset),
108 canwu.revision(),
109 DecisionMutation::RegisterController { controller },
110 ),
111 )?;
112 canwu.enqueue_decision(
113 now,
114 0,
115 DecisionIngressRequest::new(
116 DecisionRequestId::new(request_offset + 1),
117 canwu.revision(),
118 DecisionMutation::Open {
119 ticket: DecisionTicketDraft {
120 id: ticket_id,
121 definition: "example.law-passage".to_owned(),
122 decision_maker: EntityRef::Person(Canwu::demo_ids().commander),
123 assigned_controller: controller_id,
124 summary: "Will the proposed law pass?".to_owned(),
125 context: DecisionContext::new(
126 "example.law-passage.v1",
127 json!({
128 "supporting_seats": 72,
129 "opposing_seats": 28,
130 "public_pressure": "high"
131 }),
132 ),
133 options: vec![
134 DecisionOption::new("pass", "Pass the law"),
135 DecisionOption::new("fail", "Reject the law"),
136 ],
137 deadline: None,
138 },
139 },
140 ),
141 )?;
142 canwu.settle_boundary(canwu_api::BoundaryRequest::at(now))?;
143 Ok(())
144}pub fn wait( &mut self, duration: SimDuration, ) -> Result<Vec<SimEvent>, CanwuError>
pub fn step(&mut self) -> Result<Vec<SimEvent>, CanwuError>
Sourcepub fn snapshot(&self) -> SimulationSnapshot
pub fn snapshot(&self) -> SimulationSnapshot
Examples found in repository?
162fn verify_persistence_and_replay(canwu: &Canwu) -> Result<(), Box<dyn std::error::Error>> {
163 let snapshot = canwu.snapshot();
164 verify_snapshot_round_trip(&snapshot)?;
165 verify_journal_replay(canwu, &snapshot)
166}
167
168fn verify_snapshot_round_trip(
169 snapshot: &canwu_api::SimulationSnapshot,
170) -> Result<(), Box<dyn std::error::Error>> {
171 let restored = Canwu::from_snapshot_json(&serde_json::to_string(&snapshot)?)?;
172 assert_eq!(restored.snapshot(), *snapshot);
173 Ok(())
174}
175
176fn verify_journal_replay(
177 canwu: &Canwu,
178 snapshot: &canwu_api::SimulationSnapshot,
179) -> Result<(), Box<dyn std::error::Error>> {
180 let replayed = Canwu::replay_from_journal(&[], &canwu.replay_journal())?;
181 assert_eq!(replayed.snapshot(), *snapshot);
182 Ok(())
183}More examples
549fn main() -> Result<(), CanwuError> {
550 let central = CentralPlugin;
551 let treasury = TreasuryPlugin;
552 let county = CountyPlugin;
553 let plugins: [&dyn SimulationPlugin; 3] = [¢ral, &treasury, &county];
554 let mut canwu =
555 Canwu::new_with_plugins(11, Scenario::new(SimTime::EPOCH, Vec::new()), &plugins)?;
556
557 canwu.enqueue_plugin_ingress(canwu_api::PluginIngressRequest::new(
558 CENTRAL_PLUGIN,
559 ISSUE_INGRESS,
560 SimTime::EPOCH,
561 json!({"order_id": ORDER_ID}),
562 ))?;
563
564 let first = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
565 assert_eq!(first.generated_ingress.len(), 2);
566 assert!(canwu.typed_domain_record(&order_reference()).is_some());
567 assert!(canwu.typed_domain_record(&treasury_reference()).is_none());
568 assert!(canwu.typed_domain_record(&county_reference()).is_none());
569
570 let second = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
571 assert_eq!(second.record_change_count, 2);
572 assert!(canwu.typed_domain_record(&treasury_reference()).is_some());
573 assert!(canwu.typed_domain_record(&county_reference()).is_some());
574
575 let snapshot = canwu.snapshot_json()?;
576 let restored = Canwu::from_snapshot_json_with_plugins(&snapshot, &plugins)?;
577 let replayed = Canwu::replay_from_journal(&plugins, &canwu.replay_journal())?;
578 assert_eq!(restored.snapshot(), canwu.snapshot());
579 assert_eq!(replayed.snapshot(), canwu.snapshot());
580
581 println!(
582 "relief_order={} treasury_grain={} county_grain={} exact_replay=ok",
583 ORDER_ID, 600, 600
584 );
585 Ok(())
586}Sourcepub fn snapshot_json(&self) -> Result<String, CanwuError>
pub fn snapshot_json(&self) -> Result<String, CanwuError>
Examples found in repository?
549fn main() -> Result<(), CanwuError> {
550 let central = CentralPlugin;
551 let treasury = TreasuryPlugin;
552 let county = CountyPlugin;
553 let plugins: [&dyn SimulationPlugin; 3] = [¢ral, &treasury, &county];
554 let mut canwu =
555 Canwu::new_with_plugins(11, Scenario::new(SimTime::EPOCH, Vec::new()), &plugins)?;
556
557 canwu.enqueue_plugin_ingress(canwu_api::PluginIngressRequest::new(
558 CENTRAL_PLUGIN,
559 ISSUE_INGRESS,
560 SimTime::EPOCH,
561 json!({"order_id": ORDER_ID}),
562 ))?;
563
564 let first = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
565 assert_eq!(first.generated_ingress.len(), 2);
566 assert!(canwu.typed_domain_record(&order_reference()).is_some());
567 assert!(canwu.typed_domain_record(&treasury_reference()).is_none());
568 assert!(canwu.typed_domain_record(&county_reference()).is_none());
569
570 let second = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
571 assert_eq!(second.record_change_count, 2);
572 assert!(canwu.typed_domain_record(&treasury_reference()).is_some());
573 assert!(canwu.typed_domain_record(&county_reference()).is_some());
574
575 let snapshot = canwu.snapshot_json()?;
576 let restored = Canwu::from_snapshot_json_with_plugins(&snapshot, &plugins)?;
577 let replayed = Canwu::replay_from_journal(&plugins, &canwu.replay_journal())?;
578 assert_eq!(restored.snapshot(), canwu.snapshot());
579 assert_eq!(replayed.snapshot(), canwu.snapshot());
580
581 println!(
582 "relief_order={} treasury_grain={} county_grain={} exact_replay=ok",
583 ORDER_ID, 600, 600
584 );
585 Ok(())
586}Sourcepub fn from_snapshot_json(json: &str) -> Result<Self, CanwuError>
pub fn from_snapshot_json(json: &str) -> Result<Self, CanwuError>
Sourcepub fn from_snapshot_json_with_plugins(
json: &str,
plugins: &[&dyn SimulationPlugin],
) -> Result<Self, CanwuError>
pub fn from_snapshot_json_with_plugins( json: &str, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>
Examples found in repository?
549fn main() -> Result<(), CanwuError> {
550 let central = CentralPlugin;
551 let treasury = TreasuryPlugin;
552 let county = CountyPlugin;
553 let plugins: [&dyn SimulationPlugin; 3] = [¢ral, &treasury, &county];
554 let mut canwu =
555 Canwu::new_with_plugins(11, Scenario::new(SimTime::EPOCH, Vec::new()), &plugins)?;
556
557 canwu.enqueue_plugin_ingress(canwu_api::PluginIngressRequest::new(
558 CENTRAL_PLUGIN,
559 ISSUE_INGRESS,
560 SimTime::EPOCH,
561 json!({"order_id": ORDER_ID}),
562 ))?;
563
564 let first = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
565 assert_eq!(first.generated_ingress.len(), 2);
566 assert!(canwu.typed_domain_record(&order_reference()).is_some());
567 assert!(canwu.typed_domain_record(&treasury_reference()).is_none());
568 assert!(canwu.typed_domain_record(&county_reference()).is_none());
569
570 let second = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
571 assert_eq!(second.record_change_count, 2);
572 assert!(canwu.typed_domain_record(&treasury_reference()).is_some());
573 assert!(canwu.typed_domain_record(&county_reference()).is_some());
574
575 let snapshot = canwu.snapshot_json()?;
576 let restored = Canwu::from_snapshot_json_with_plugins(&snapshot, &plugins)?;
577 let replayed = Canwu::replay_from_journal(&plugins, &canwu.replay_journal())?;
578 assert_eq!(restored.snapshot(), canwu.snapshot());
579 assert_eq!(replayed.snapshot(), canwu.snapshot());
580
581 println!(
582 "relief_order={} treasury_grain={} county_grain={} exact_replay=ok",
583 ORDER_ID, 600, 600
584 );
585 Ok(())
586}pub fn from_checkpoint_and_journal( checkpoint: SimulationCheckpoint, segments: Vec<EvidenceJournalSegment>, ) -> Result<Self, CanwuError>
pub fn from_checkpoint_journal( bundle: CheckpointJournal, ) -> Result<Self, CanwuError>
pub fn from_checkpoint_journal_with_plugins( bundle: CheckpointJournal, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>
pub fn from_checkpoint_journal_json(json: &str) -> Result<Self, CanwuError>
pub fn from_checkpoint_journal_json_with_plugins( json: &str, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>
Sourcepub fn replay_from_journal(
plugins: &[&dyn SimulationPlugin],
journal: &ReplayJournal,
) -> Result<Self, CanwuError>
pub fn replay_from_journal( plugins: &[&dyn SimulationPlugin], journal: &ReplayJournal, ) -> Result<Self, CanwuError>
Examples found in repository?
More examples
549fn main() -> Result<(), CanwuError> {
550 let central = CentralPlugin;
551 let treasury = TreasuryPlugin;
552 let county = CountyPlugin;
553 let plugins: [&dyn SimulationPlugin; 3] = [¢ral, &treasury, &county];
554 let mut canwu =
555 Canwu::new_with_plugins(11, Scenario::new(SimTime::EPOCH, Vec::new()), &plugins)?;
556
557 canwu.enqueue_plugin_ingress(canwu_api::PluginIngressRequest::new(
558 CENTRAL_PLUGIN,
559 ISSUE_INGRESS,
560 SimTime::EPOCH,
561 json!({"order_id": ORDER_ID}),
562 ))?;
563
564 let first = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
565 assert_eq!(first.generated_ingress.len(), 2);
566 assert!(canwu.typed_domain_record(&order_reference()).is_some());
567 assert!(canwu.typed_domain_record(&treasury_reference()).is_none());
568 assert!(canwu.typed_domain_record(&county_reference()).is_none());
569
570 let second = canwu.settle_boundary(BoundaryRequest::at(SimTime::EPOCH))?;
571 assert_eq!(second.record_change_count, 2);
572 assert!(canwu.typed_domain_record(&treasury_reference()).is_some());
573 assert!(canwu.typed_domain_record(&county_reference()).is_some());
574
575 let snapshot = canwu.snapshot_json()?;
576 let restored = Canwu::from_snapshot_json_with_plugins(&snapshot, &plugins)?;
577 let replayed = Canwu::replay_from_journal(&plugins, &canwu.replay_journal())?;
578 assert_eq!(restored.snapshot(), canwu.snapshot());
579 assert_eq!(replayed.snapshot(), canwu.snapshot());
580
581 println!(
582 "relief_order={} treasury_grain={} county_grain={} exact_replay=ok",
583 ORDER_ID, 600, 600
584 );
585 Ok(())
586}Sourcepub fn replay_from_journal_with_archive_provider(
plugins: &[&dyn SimulationPlugin],
journal: &ReplayJournal,
archive_provider: Rc<dyn PluginArchiveObjectProvider>,
) -> Result<Self, CanwuError>
pub fn replay_from_journal_with_archive_provider( plugins: &[&dyn SimulationPlugin], journal: &ReplayJournal, archive_provider: Rc<dyn PluginArchiveObjectProvider>, ) -> Result<Self, CanwuError>
Replays with package archive storage attached before the first recorded boundary, so package-owned cold history participates in ordinary admission and settlement throughout replay.
pub fn replay_from_journal_json( plugins: &[&dyn SimulationPlugin], json: &str, ) -> Result<Self, CanwuError>
pub fn fork(&self) -> Self
Sourcepub fn admin_query_knowledge(
&self,
holder: KnowledgeHolderRef,
query: &KnowledgeQuery,
) -> Result<KnowledgeQueryResult, CanwuError>
pub fn admin_query_knowledge( &self, holder: KnowledgeHolderRef, query: &KnowledgeQuery, ) -> Result<KnowledgeQueryResult, CanwuError>
Trusted host/admin holder query. Player-facing callers must use a
restricted CanwuViewer.
Sourcepub fn viewer(&self) -> Result<CanwuViewer<'_>, CanwuError>
pub fn viewer(&self) -> Result<CanwuViewer<'_>, CanwuError>
Creates the restricted viewer dictated entirely by the persisted run policy and seat binding.
Sourcepub fn viewer_for_actor(
&self,
actor: PersonId,
) -> Result<CanwuViewer<'_>, CanwuError>
pub fn viewer_for_actor( &self, actor: PersonId, ) -> Result<CanwuViewer<'_>, CanwuError>
Character-seat and compatibility convenience. It never upgrades an institution, public, research, or developer policy to a person.