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 }148fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
149 let mut canwu = Canwu::demo(202)?;
150 canwu.register_plugin(&UncertaintyPlugin)?;
151 enqueue_ticket(
152 &mut canwu,
153 RANDOM_TICKET,
154 1,
155 DecisionControllerBinding::new(
156 "law-random-controller",
157 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
158 DecisionAuthority::Actor {
159 actor: Canwu::demo_ids().commander,
160 },
161 ),
162 )?;
163 let selection_at = SimTime::EPOCH + SimDuration::days(1);
164 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
165 canwu.step_canonical()?.expect("random source boundary");
166 canwu
167 .step_canonical()?
168 .expect("generated decision resolution boundary");
169 let trace = canwu
170 .decision_trace(canwu_api::DecisionTraceId::new(1))
171 .expect("random decision trace");
172 println!("random_trace={}", serde_json::to_string(trace)?);
173 Ok(())
174}
175
176fn llm_interface_branch() -> Result<(), Box<dyn std::error::Error>> {
177 let mut canwu = Canwu::demo(202)?;
178 let ticket_id = DecisionTicketId::new(2);
179 enqueue_ticket(
180 &mut canwu,
181 ticket_id,
182 10,
183 DecisionControllerBinding::new(
184 "law-llm-controller",
185 DecisionPolicyIdentity::new(DecisionPolicyKind::Llm, "strict-law-selector", "1"),
186 DecisionAuthority::Actor {
187 actor: Canwu::demo_ids().commander,
188 },
189 ),
190 )?;
191 let mut policy = QueuedLlmPolicy::new(
192 "strict-law-selector",
193 "1",
194 LlmModelIdentity {
195 provider: "not-connected".to_owned(),
196 model: "host-selected-model".to_owned(),
197 prompt_contract: "return one existing option_id and no new action".to_owned(),
198 },
199 );
200 let ticket = canwu.decision_ticket(ticket_id).expect("LLM ticket");
201 let request = policy.external_request(ticket);
202 println!("llm_request={}", serde_json::to_string(&request)?);
203
204 // A real host sends `request` to its model and parses a strict structured
205 // answer. This example supplies the same response object without making a
206 // network call.
207 policy.submit(
208 ticket_id,
209 ExternalDecisionResponse {
210 ticket_version: request.ticket_version,
211 option_id: "pass".to_owned(),
212 provider: "not-connected".to_owned(),
213 request_id: "example-response-1".to_owned(),
214 metadata: BTreeMap::new(),
215 },
216 )?;
217 let evaluation = canwu.drive_decision(
218 canwu.time(),
219 0,
220 DecisionRequestId::new(12),
221 None,
222 ticket_id,
223 &policy,
224 )?;
225 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
226 canwu.step_canonical()?.expect("LLM decision boundary");
227 Ok(())
228}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}96fn enqueue_ticket(
97 canwu: &mut Canwu,
98 ticket_id: DecisionTicketId,
99 request_offset: u64,
100 controller: DecisionControllerBinding,
101) -> Result<(), CanwuError> {
102 let now = canwu.time();
103 let controller_id = controller.id.clone();
104 canwu.enqueue_decision(
105 now,
106 0,
107 DecisionIngressRequest::new(
108 DecisionRequestId::new(request_offset),
109 canwu.revision(),
110 DecisionMutation::RegisterController { controller },
111 ),
112 )?;
113 canwu.enqueue_decision(
114 now,
115 0,
116 DecisionIngressRequest::new(
117 DecisionRequestId::new(request_offset + 1),
118 canwu.revision(),
119 DecisionMutation::Open {
120 ticket: DecisionTicketDraft {
121 id: ticket_id,
122 definition: "example.law-passage".to_owned(),
123 decision_maker: EntityRef::Person(Canwu::demo_ids().commander),
124 assigned_controller: controller_id,
125 summary: "Will the proposed law pass?".to_owned(),
126 context: DecisionContext::new(
127 "example.law-passage.v1",
128 json!({
129 "supporting_seats": 72,
130 "opposing_seats": 28,
131 "public_pressure": "high"
132 }),
133 ),
134 options: vec![
135 DecisionOption::new("pass", "Pass the law"),
136 DecisionOption::new("fail", "Reject the law"),
137 ],
138 deadline: None,
139 parent_ticket: None,
140 },
141 },
142 ),
143 )?;
144 canwu.settle_boundary(canwu_api::BoundaryRequest::at(now))?;
145 Ok(())
146}
147
148fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
149 let mut canwu = Canwu::demo(202)?;
150 canwu.register_plugin(&UncertaintyPlugin)?;
151 enqueue_ticket(
152 &mut canwu,
153 RANDOM_TICKET,
154 1,
155 DecisionControllerBinding::new(
156 "law-random-controller",
157 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
158 DecisionAuthority::Actor {
159 actor: Canwu::demo_ids().commander,
160 },
161 ),
162 )?;
163 let selection_at = SimTime::EPOCH + SimDuration::days(1);
164 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
165 canwu.step_canonical()?.expect("random source boundary");
166 canwu
167 .step_canonical()?
168 .expect("generated decision resolution boundary");
169 let trace = canwu
170 .decision_trace(canwu_api::DecisionTraceId::new(1))
171 .expect("random decision trace");
172 println!("random_trace={}", serde_json::to_string(trace)?);
173 Ok(())
174}
175
176fn llm_interface_branch() -> Result<(), Box<dyn std::error::Error>> {
177 let mut canwu = Canwu::demo(202)?;
178 let ticket_id = DecisionTicketId::new(2);
179 enqueue_ticket(
180 &mut canwu,
181 ticket_id,
182 10,
183 DecisionControllerBinding::new(
184 "law-llm-controller",
185 DecisionPolicyIdentity::new(DecisionPolicyKind::Llm, "strict-law-selector", "1"),
186 DecisionAuthority::Actor {
187 actor: Canwu::demo_ids().commander,
188 },
189 ),
190 )?;
191 let mut policy = QueuedLlmPolicy::new(
192 "strict-law-selector",
193 "1",
194 LlmModelIdentity {
195 provider: "not-connected".to_owned(),
196 model: "host-selected-model".to_owned(),
197 prompt_contract: "return one existing option_id and no new action".to_owned(),
198 },
199 );
200 let ticket = canwu.decision_ticket(ticket_id).expect("LLM ticket");
201 let request = policy.external_request(ticket);
202 println!("llm_request={}", serde_json::to_string(&request)?);
203
204 // A real host sends `request` to its model and parses a strict structured
205 // answer. This example supplies the same response object without making a
206 // network call.
207 policy.submit(
208 ticket_id,
209 ExternalDecisionResponse {
210 ticket_version: request.ticket_version,
211 option_id: "pass".to_owned(),
212 provider: "not-connected".to_owned(),
213 request_id: "example-response-1".to_owned(),
214 metadata: BTreeMap::new(),
215 },
216 )?;
217 let evaluation = canwu.drive_decision(
218 canwu.time(),
219 0,
220 DecisionRequestId::new(12),
221 None,
222 ticket_id,
223 &policy,
224 )?;
225 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
226 canwu.step_canonical()?.expect("LLM decision boundary");
227 Ok(())
228}Sourcepub const fn time(&self) -> SimTime
pub const fn time(&self) -> SimTime
Examples found in repository?
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 }
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}96fn enqueue_ticket(
97 canwu: &mut Canwu,
98 ticket_id: DecisionTicketId,
99 request_offset: u64,
100 controller: DecisionControllerBinding,
101) -> Result<(), CanwuError> {
102 let now = canwu.time();
103 let controller_id = controller.id.clone();
104 canwu.enqueue_decision(
105 now,
106 0,
107 DecisionIngressRequest::new(
108 DecisionRequestId::new(request_offset),
109 canwu.revision(),
110 DecisionMutation::RegisterController { controller },
111 ),
112 )?;
113 canwu.enqueue_decision(
114 now,
115 0,
116 DecisionIngressRequest::new(
117 DecisionRequestId::new(request_offset + 1),
118 canwu.revision(),
119 DecisionMutation::Open {
120 ticket: DecisionTicketDraft {
121 id: ticket_id,
122 definition: "example.law-passage".to_owned(),
123 decision_maker: EntityRef::Person(Canwu::demo_ids().commander),
124 assigned_controller: controller_id,
125 summary: "Will the proposed law pass?".to_owned(),
126 context: DecisionContext::new(
127 "example.law-passage.v1",
128 json!({
129 "supporting_seats": 72,
130 "opposing_seats": 28,
131 "public_pressure": "high"
132 }),
133 ),
134 options: vec![
135 DecisionOption::new("pass", "Pass the law"),
136 DecisionOption::new("fail", "Reject the law"),
137 ],
138 deadline: None,
139 parent_ticket: None,
140 },
141 },
142 ),
143 )?;
144 canwu.settle_boundary(canwu_api::BoundaryRequest::at(now))?;
145 Ok(())
146}
147
148fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
149 let mut canwu = Canwu::demo(202)?;
150 canwu.register_plugin(&UncertaintyPlugin)?;
151 enqueue_ticket(
152 &mut canwu,
153 RANDOM_TICKET,
154 1,
155 DecisionControllerBinding::new(
156 "law-random-controller",
157 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
158 DecisionAuthority::Actor {
159 actor: Canwu::demo_ids().commander,
160 },
161 ),
162 )?;
163 let selection_at = SimTime::EPOCH + SimDuration::days(1);
164 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
165 canwu.step_canonical()?.expect("random source boundary");
166 canwu
167 .step_canonical()?
168 .expect("generated decision resolution boundary");
169 let trace = canwu
170 .decision_trace(canwu_api::DecisionTraceId::new(1))
171 .expect("random decision trace");
172 println!("random_trace={}", serde_json::to_string(trace)?);
173 Ok(())
174}
175
176fn llm_interface_branch() -> Result<(), Box<dyn std::error::Error>> {
177 let mut canwu = Canwu::demo(202)?;
178 let ticket_id = DecisionTicketId::new(2);
179 enqueue_ticket(
180 &mut canwu,
181 ticket_id,
182 10,
183 DecisionControllerBinding::new(
184 "law-llm-controller",
185 DecisionPolicyIdentity::new(DecisionPolicyKind::Llm, "strict-law-selector", "1"),
186 DecisionAuthority::Actor {
187 actor: Canwu::demo_ids().commander,
188 },
189 ),
190 )?;
191 let mut policy = QueuedLlmPolicy::new(
192 "strict-law-selector",
193 "1",
194 LlmModelIdentity {
195 provider: "not-connected".to_owned(),
196 model: "host-selected-model".to_owned(),
197 prompt_contract: "return one existing option_id and no new action".to_owned(),
198 },
199 );
200 let ticket = canwu.decision_ticket(ticket_id).expect("LLM ticket");
201 let request = policy.external_request(ticket);
202 println!("llm_request={}", serde_json::to_string(&request)?);
203
204 // A real host sends `request` to its model and parses a strict structured
205 // answer. This example supplies the same response object without making a
206 // network call.
207 policy.submit(
208 ticket_id,
209 ExternalDecisionResponse {
210 ticket_version: request.ticket_version,
211 option_id: "pass".to_owned(),
212 provider: "not-connected".to_owned(),
213 request_id: "example-response-1".to_owned(),
214 metadata: BTreeMap::new(),
215 },
216 )?;
217 let evaluation = canwu.drive_decision(
218 canwu.time(),
219 0,
220 DecisionRequestId::new(12),
221 None,
222 ticket_id,
223 &policy,
224 )?;
225 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
226 canwu.step_canonical()?.expect("LLM decision boundary");
227 Ok(())
228}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 parent_ticket: None,
80 },
81 },
82 ),
83 )?;
84 canwu.step_canonical()?.expect("decision intake boundary");
85 Ok(())
86}
87
88fn refresh_aid_options(canwu: &mut Canwu, ids: DemoIds) -> Result<(), Box<dyn std::error::Error>> {
89 canwu.enqueue_decision(
90 canwu.time(),
91 0,
92 DecisionIngressRequest::new(
93 DecisionRequestId::new(3),
94 canwu.revision(),
95 DecisionMutation::ReplaceOptions {
96 ticket_id: DecisionTicketId::new(1),
97 expected_version: 1,
98 context: DecisionContext::new(
99 "beiyang.aid-request.v1",
100 json!({
101 "requester": "warlord-a",
102 "battle": "ongoing-front",
103 "common_enemy": true,
104 "route_confirmed": true
105 }),
106 ),
107 options: vec![
108 DecisionOption {
109 action: DecisionAction::None,
110 utility_inputs: BTreeMap::from([
111 ("home_defense".to_owned(), 80),
112 ("alliance".to_owned(), -40),
113 ]),
114 ..DecisionOption::new("decline", "Decline aid")
115 },
116 DecisionOption {
117 action: DecisionAction::Command {
118 command: serde_json::to_value(Command::OrderMovement {
119 subject: EntityRef::Army(ids.army),
120 destination: ids.eastern_territory,
121 cargo: Vec::new(),
122 })?,
123 },
124 utility_inputs: BTreeMap::from([
125 ("home_defense".to_owned(), -20),
126 ("alliance".to_owned(), 90),
127 ]),
128 ..DecisionOption::new("send-aid", "Send the neighboring army")
129 },
130 ],
131 },
132 ),
133 )?;
134 canwu
135 .step_canonical()?
136 .expect("dynamic option refresh boundary");
137 Ok(())
138}
139
140fn resolve_aid_request(canwu: &mut Canwu) -> Result<(), Box<dyn std::error::Error>> {
141 let policy = WeightedUtilityPolicy::new(
142 "aid-utility",
143 "1",
144 UtilityProfile {
145 weights: BTreeMap::from([("alliance".to_owned(), 3), ("home_defense".to_owned(), 1)]),
146 },
147 );
148 let evaluation = canwu.drive_decision(
149 canwu.time(),
150 0,
151 DecisionRequestId::new(4),
152 Some(CommandRequestId::new(1)),
153 DecisionTicketId::new(1),
154 &policy,
155 )?;
156 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
157 canwu
158 .step_canonical()?
159 .expect("decision resolution boundary");
160 Ok(())
161}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?
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 }More examples
96fn enqueue_ticket(
97 canwu: &mut Canwu,
98 ticket_id: DecisionTicketId,
99 request_offset: u64,
100 controller: DecisionControllerBinding,
101) -> Result<(), CanwuError> {
102 let now = canwu.time();
103 let controller_id = controller.id.clone();
104 canwu.enqueue_decision(
105 now,
106 0,
107 DecisionIngressRequest::new(
108 DecisionRequestId::new(request_offset),
109 canwu.revision(),
110 DecisionMutation::RegisterController { controller },
111 ),
112 )?;
113 canwu.enqueue_decision(
114 now,
115 0,
116 DecisionIngressRequest::new(
117 DecisionRequestId::new(request_offset + 1),
118 canwu.revision(),
119 DecisionMutation::Open {
120 ticket: DecisionTicketDraft {
121 id: ticket_id,
122 definition: "example.law-passage".to_owned(),
123 decision_maker: EntityRef::Person(Canwu::demo_ids().commander),
124 assigned_controller: controller_id,
125 summary: "Will the proposed law pass?".to_owned(),
126 context: DecisionContext::new(
127 "example.law-passage.v1",
128 json!({
129 "supporting_seats": 72,
130 "opposing_seats": 28,
131 "public_pressure": "high"
132 }),
133 ),
134 options: vec![
135 DecisionOption::new("pass", "Pass the law"),
136 DecisionOption::new("fail", "Reject the law"),
137 ],
138 deadline: None,
139 parent_ticket: None,
140 },
141 },
142 ),
143 )?;
144 canwu.settle_boundary(canwu_api::BoundaryRequest::at(now))?;
145 Ok(())
146}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 parent_ticket: None,
80 },
81 },
82 ),
83 )?;
84 canwu.step_canonical()?.expect("decision intake boundary");
85 Ok(())
86}
87
88fn refresh_aid_options(canwu: &mut Canwu, ids: DemoIds) -> Result<(), Box<dyn std::error::Error>> {
89 canwu.enqueue_decision(
90 canwu.time(),
91 0,
92 DecisionIngressRequest::new(
93 DecisionRequestId::new(3),
94 canwu.revision(),
95 DecisionMutation::ReplaceOptions {
96 ticket_id: DecisionTicketId::new(1),
97 expected_version: 1,
98 context: DecisionContext::new(
99 "beiyang.aid-request.v1",
100 json!({
101 "requester": "warlord-a",
102 "battle": "ongoing-front",
103 "common_enemy": true,
104 "route_confirmed": true
105 }),
106 ),
107 options: vec![
108 DecisionOption {
109 action: DecisionAction::None,
110 utility_inputs: BTreeMap::from([
111 ("home_defense".to_owned(), 80),
112 ("alliance".to_owned(), -40),
113 ]),
114 ..DecisionOption::new("decline", "Decline aid")
115 },
116 DecisionOption {
117 action: DecisionAction::Command {
118 command: serde_json::to_value(Command::OrderMovement {
119 subject: EntityRef::Army(ids.army),
120 destination: ids.eastern_territory,
121 cargo: Vec::new(),
122 })?,
123 },
124 utility_inputs: BTreeMap::from([
125 ("home_defense".to_owned(), -20),
126 ("alliance".to_owned(), 90),
127 ]),
128 ..DecisionOption::new("send-aid", "Send the neighboring army")
129 },
130 ],
131 },
132 ),
133 )?;
134 canwu
135 .step_canonical()?
136 .expect("dynamic option refresh boundary");
137 Ok(())
138}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 person_availability(
&self,
person: PersonId,
) -> Option<&PersonAvailability>
pub fn person_availability( &self, person: PersonId, ) -> Option<&PersonAvailability>
Trusted host access to a person’s committed life and custody state.
None means no change has been committed: the person is alive and free.
Sourcepub fn person_availabilities(
&self,
) -> impl Iterator<Item = (&PersonId, &PersonAvailability)>
pub fn person_availabilities( &self, ) -> impl Iterator<Item = (&PersonId, &PersonAvailability)>
Trusted host access to every committed person availability, in person-ID order. Persons without an entry are alive and free.
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?
176fn llm_interface_branch() -> Result<(), Box<dyn std::error::Error>> {
177 let mut canwu = Canwu::demo(202)?;
178 let ticket_id = DecisionTicketId::new(2);
179 enqueue_ticket(
180 &mut canwu,
181 ticket_id,
182 10,
183 DecisionControllerBinding::new(
184 "law-llm-controller",
185 DecisionPolicyIdentity::new(DecisionPolicyKind::Llm, "strict-law-selector", "1"),
186 DecisionAuthority::Actor {
187 actor: Canwu::demo_ids().commander,
188 },
189 ),
190 )?;
191 let mut policy = QueuedLlmPolicy::new(
192 "strict-law-selector",
193 "1",
194 LlmModelIdentity {
195 provider: "not-connected".to_owned(),
196 model: "host-selected-model".to_owned(),
197 prompt_contract: "return one existing option_id and no new action".to_owned(),
198 },
199 );
200 let ticket = canwu.decision_ticket(ticket_id).expect("LLM ticket");
201 let request = policy.external_request(ticket);
202 println!("llm_request={}", serde_json::to_string(&request)?);
203
204 // A real host sends `request` to its model and parses a strict structured
205 // answer. This example supplies the same response object without making a
206 // network call.
207 policy.submit(
208 ticket_id,
209 ExternalDecisionResponse {
210 ticket_version: request.ticket_version,
211 option_id: "pass".to_owned(),
212 provider: "not-connected".to_owned(),
213 request_id: "example-response-1".to_owned(),
214 metadata: BTreeMap::new(),
215 },
216 )?;
217 let evaluation = canwu.drive_decision(
218 canwu.time(),
219 0,
220 DecisionRequestId::new(12),
221 None,
222 ticket_id,
223 &policy,
224 )?;
225 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
226 canwu.step_canonical()?.expect("LLM decision boundary");
227 Ok(())
228}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
148fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
149 let mut canwu = Canwu::demo(202)?;
150 canwu.register_plugin(&UncertaintyPlugin)?;
151 enqueue_ticket(
152 &mut canwu,
153 RANDOM_TICKET,
154 1,
155 DecisionControllerBinding::new(
156 "law-random-controller",
157 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
158 DecisionAuthority::Actor {
159 actor: Canwu::demo_ids().commander,
160 },
161 ),
162 )?;
163 let selection_at = SimTime::EPOCH + SimDuration::days(1);
164 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
165 canwu.step_canonical()?.expect("random source boundary");
166 canwu
167 .step_canonical()?
168 .expect("generated decision resolution boundary");
169 let trace = canwu
170 .decision_trace(canwu_api::DecisionTraceId::new(1))
171 .expect("random decision trace");
172 println!("random_trace={}", serde_json::to_string(trace)?);
173 Ok(())
174}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}148fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
149 let mut canwu = Canwu::demo(202)?;
150 canwu.register_plugin(&UncertaintyPlugin)?;
151 enqueue_ticket(
152 &mut canwu,
153 RANDOM_TICKET,
154 1,
155 DecisionControllerBinding::new(
156 "law-random-controller",
157 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
158 DecisionAuthority::Actor {
159 actor: Canwu::demo_ids().commander,
160 },
161 ),
162 )?;
163 let selection_at = SimTime::EPOCH + SimDuration::days(1);
164 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
165 canwu.step_canonical()?.expect("random source boundary");
166 canwu
167 .step_canonical()?
168 .expect("generated decision resolution boundary");
169 let trace = canwu
170 .decision_trace(canwu_api::DecisionTraceId::new(1))
171 .expect("random decision trace");
172 println!("random_trace={}", serde_json::to_string(trace)?);
173 Ok(())
174}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>
Sourcepub fn cancel_plugin_ingress(
&mut self,
ingress_id: IngressId,
reason: impl Into<String>,
) -> Result<IngressReceipt, CanwuError>
pub fn cancel_plugin_ingress( &mut self, ingress_id: IngressId, reason: impl Into<String>, ) -> Result<IngressReceipt, CanwuError>
Withdraws a still-pending plugin ingress item that the host enqueued
with Self::enqueue_plugin_ingress, strictly before its due time.
Due, admitted, archived, or already cancelled items fail with
ErrorCode::LateIngress; items of internal packet types or items a
plugin scheduled inside the engine fail with
ErrorCode::InvalidAuthority; unknown IDs fail with
ErrorCode::EvidenceUnavailable; non-plugin targets and reasons that
are empty, untrimmed, or longer than
MAX_INGRESS_CANCELLATION_REASON_BYTES fail with
ErrorCode::InvalidPayload; declared read-only runs fail with
ErrorCode::InteractionReadOnly. The returned receipt names the
terminal IngressPayload::PluginCancellation journal record. The
withdrawn item is never admitted, never settles, and nothing is rolled
back; snapshots, checkpoint journals, and exact replay preserve the
cancellation.
Sourcepub fn cancel_permitted_plugin_ingress(
&mut self,
ingress_id: IngressId,
permit: &PluginIngressPermit,
reason: impl Into<String>,
) -> Result<IngressReceipt, CanwuError>
pub fn cancel_permitted_plugin_ingress( &mut self, ingress_id: IngressId, permit: &PluginIngressPermit, reason: impl Into<String>, ) -> Result<IngressReceipt, CanwuError>
Withdraws a still-pending item of an internal packet type through the
owning plugin’s opaque registration permit. The permit covers
host-enqueued items of that exact type and items the same plugin
scheduled inside the engine; timing rules match
Self::cancel_plugin_ingress.
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?
96fn enqueue_ticket(
97 canwu: &mut Canwu,
98 ticket_id: DecisionTicketId,
99 request_offset: u64,
100 controller: DecisionControllerBinding,
101) -> Result<(), CanwuError> {
102 let now = canwu.time();
103 let controller_id = controller.id.clone();
104 canwu.enqueue_decision(
105 now,
106 0,
107 DecisionIngressRequest::new(
108 DecisionRequestId::new(request_offset),
109 canwu.revision(),
110 DecisionMutation::RegisterController { controller },
111 ),
112 )?;
113 canwu.enqueue_decision(
114 now,
115 0,
116 DecisionIngressRequest::new(
117 DecisionRequestId::new(request_offset + 1),
118 canwu.revision(),
119 DecisionMutation::Open {
120 ticket: DecisionTicketDraft {
121 id: ticket_id,
122 definition: "example.law-passage".to_owned(),
123 decision_maker: EntityRef::Person(Canwu::demo_ids().commander),
124 assigned_controller: controller_id,
125 summary: "Will the proposed law pass?".to_owned(),
126 context: DecisionContext::new(
127 "example.law-passage.v1",
128 json!({
129 "supporting_seats": 72,
130 "opposing_seats": 28,
131 "public_pressure": "high"
132 }),
133 ),
134 options: vec![
135 DecisionOption::new("pass", "Pass the law"),
136 DecisionOption::new("fail", "Reject the law"),
137 ],
138 deadline: None,
139 parent_ticket: None,
140 },
141 },
142 ),
143 )?;
144 canwu.settle_boundary(canwu_api::BoundaryRequest::at(now))?;
145 Ok(())
146}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 parent_ticket: None,
80 },
81 },
82 ),
83 )?;
84 canwu.step_canonical()?.expect("decision intake boundary");
85 Ok(())
86}
87
88fn refresh_aid_options(canwu: &mut Canwu, ids: DemoIds) -> Result<(), Box<dyn std::error::Error>> {
89 canwu.enqueue_decision(
90 canwu.time(),
91 0,
92 DecisionIngressRequest::new(
93 DecisionRequestId::new(3),
94 canwu.revision(),
95 DecisionMutation::ReplaceOptions {
96 ticket_id: DecisionTicketId::new(1),
97 expected_version: 1,
98 context: DecisionContext::new(
99 "beiyang.aid-request.v1",
100 json!({
101 "requester": "warlord-a",
102 "battle": "ongoing-front",
103 "common_enemy": true,
104 "route_confirmed": true
105 }),
106 ),
107 options: vec![
108 DecisionOption {
109 action: DecisionAction::None,
110 utility_inputs: BTreeMap::from([
111 ("home_defense".to_owned(), 80),
112 ("alliance".to_owned(), -40),
113 ]),
114 ..DecisionOption::new("decline", "Decline aid")
115 },
116 DecisionOption {
117 action: DecisionAction::Command {
118 command: serde_json::to_value(Command::OrderMovement {
119 subject: EntityRef::Army(ids.army),
120 destination: ids.eastern_territory,
121 cargo: Vec::new(),
122 })?,
123 },
124 utility_inputs: BTreeMap::from([
125 ("home_defense".to_owned(), -20),
126 ("alliance".to_owned(), 90),
127 ]),
128 ..DecisionOption::new("send-aid", "Send the neighboring army")
129 },
130 ],
131 },
132 ),
133 )?;
134 canwu
135 .step_canonical()?
136 .expect("dynamic option refresh boundary");
137 Ok(())
138}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?
140fn resolve_aid_request(canwu: &mut Canwu) -> Result<(), Box<dyn std::error::Error>> {
141 let policy = WeightedUtilityPolicy::new(
142 "aid-utility",
143 "1",
144 UtilityProfile {
145 weights: BTreeMap::from([("alliance".to_owned(), 3), ("home_defense".to_owned(), 1)]),
146 },
147 );
148 let evaluation = canwu.drive_decision(
149 canwu.time(),
150 0,
151 DecisionRequestId::new(4),
152 Some(CommandRequestId::new(1)),
153 DecisionTicketId::new(1),
154 &policy,
155 )?;
156 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
157 canwu
158 .step_canonical()?
159 .expect("decision resolution boundary");
160 Ok(())
161}More examples
176fn llm_interface_branch() -> Result<(), Box<dyn std::error::Error>> {
177 let mut canwu = Canwu::demo(202)?;
178 let ticket_id = DecisionTicketId::new(2);
179 enqueue_ticket(
180 &mut canwu,
181 ticket_id,
182 10,
183 DecisionControllerBinding::new(
184 "law-llm-controller",
185 DecisionPolicyIdentity::new(DecisionPolicyKind::Llm, "strict-law-selector", "1"),
186 DecisionAuthority::Actor {
187 actor: Canwu::demo_ids().commander,
188 },
189 ),
190 )?;
191 let mut policy = QueuedLlmPolicy::new(
192 "strict-law-selector",
193 "1",
194 LlmModelIdentity {
195 provider: "not-connected".to_owned(),
196 model: "host-selected-model".to_owned(),
197 prompt_contract: "return one existing option_id and no new action".to_owned(),
198 },
199 );
200 let ticket = canwu.decision_ticket(ticket_id).expect("LLM ticket");
201 let request = policy.external_request(ticket);
202 println!("llm_request={}", serde_json::to_string(&request)?);
203
204 // A real host sends `request` to its model and parses a strict structured
205 // answer. This example supplies the same response object without making a
206 // network call.
207 policy.submit(
208 ticket_id,
209 ExternalDecisionResponse {
210 ticket_version: request.ticket_version,
211 option_id: "pass".to_owned(),
212 provider: "not-connected".to_owned(),
213 request_id: "example-response-1".to_owned(),
214 metadata: BTreeMap::new(),
215 },
216 )?;
217 let evaluation = canwu.drive_decision(
218 canwu.time(),
219 0,
220 DecisionRequestId::new(12),
221 None,
222 ticket_id,
223 &policy,
224 )?;
225 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
226 canwu.step_canonical()?.expect("LLM decision boundary");
227 Ok(())
228}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?
148fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
149 let mut canwu = Canwu::demo(202)?;
150 canwu.register_plugin(&UncertaintyPlugin)?;
151 enqueue_ticket(
152 &mut canwu,
153 RANDOM_TICKET,
154 1,
155 DecisionControllerBinding::new(
156 "law-random-controller",
157 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
158 DecisionAuthority::Actor {
159 actor: Canwu::demo_ids().commander,
160 },
161 ),
162 )?;
163 let selection_at = SimTime::EPOCH + SimDuration::days(1);
164 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
165 canwu.step_canonical()?.expect("random source boundary");
166 canwu
167 .step_canonical()?
168 .expect("generated decision resolution boundary");
169 let trace = canwu
170 .decision_trace(canwu_api::DecisionTraceId::new(1))
171 .expect("random decision trace");
172 println!("random_trace={}", serde_json::to_string(trace)?);
173 Ok(())
174}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?
148fn random_branch() -> Result<(), Box<dyn std::error::Error>> {
149 let mut canwu = Canwu::demo(202)?;
150 canwu.register_plugin(&UncertaintyPlugin)?;
151 enqueue_ticket(
152 &mut canwu,
153 RANDOM_TICKET,
154 1,
155 DecisionControllerBinding::new(
156 "law-random-controller",
157 DecisionPolicyIdentity::new(DecisionPolicyKind::Random, "weighted-random", "1"),
158 DecisionAuthority::Actor {
159 actor: Canwu::demo_ids().commander,
160 },
161 ),
162 )?;
163 let selection_at = SimTime::EPOCH + SimDuration::days(1);
164 canwu.schedule_calendar_boundary(selection_at, vec![SystemCadence::Daily])?;
165 canwu.step_canonical()?.expect("random source boundary");
166 canwu
167 .step_canonical()?
168 .expect("generated decision resolution boundary");
169 let trace = canwu
170 .decision_trace(canwu_api::DecisionTraceId::new(1))
171 .expect("random decision trace");
172 println!("random_trace={}", serde_json::to_string(trace)?);
173 Ok(())
174}
175
176fn llm_interface_branch() -> Result<(), Box<dyn std::error::Error>> {
177 let mut canwu = Canwu::demo(202)?;
178 let ticket_id = DecisionTicketId::new(2);
179 enqueue_ticket(
180 &mut canwu,
181 ticket_id,
182 10,
183 DecisionControllerBinding::new(
184 "law-llm-controller",
185 DecisionPolicyIdentity::new(DecisionPolicyKind::Llm, "strict-law-selector", "1"),
186 DecisionAuthority::Actor {
187 actor: Canwu::demo_ids().commander,
188 },
189 ),
190 )?;
191 let mut policy = QueuedLlmPolicy::new(
192 "strict-law-selector",
193 "1",
194 LlmModelIdentity {
195 provider: "not-connected".to_owned(),
196 model: "host-selected-model".to_owned(),
197 prompt_contract: "return one existing option_id and no new action".to_owned(),
198 },
199 );
200 let ticket = canwu.decision_ticket(ticket_id).expect("LLM ticket");
201 let request = policy.external_request(ticket);
202 println!("llm_request={}", serde_json::to_string(&request)?);
203
204 // A real host sends `request` to its model and parses a strict structured
205 // answer. This example supplies the same response object without making a
206 // network call.
207 policy.submit(
208 ticket_id,
209 ExternalDecisionResponse {
210 ticket_version: request.ticket_version,
211 option_id: "pass".to_owned(),
212 provider: "not-connected".to_owned(),
213 request_id: "example-response-1".to_owned(),
214 metadata: BTreeMap::new(),
215 },
216 )?;
217 let evaluation = canwu.drive_decision(
218 canwu.time(),
219 0,
220 DecisionRequestId::new(12),
221 None,
222 ticket_id,
223 &policy,
224 )?;
225 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
226 canwu.step_canonical()?.expect("LLM decision boundary");
227 Ok(())
228}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 parent_ticket: None,
80 },
81 },
82 ),
83 )?;
84 canwu.step_canonical()?.expect("decision intake boundary");
85 Ok(())
86}
87
88fn refresh_aid_options(canwu: &mut Canwu, ids: DemoIds) -> Result<(), Box<dyn std::error::Error>> {
89 canwu.enqueue_decision(
90 canwu.time(),
91 0,
92 DecisionIngressRequest::new(
93 DecisionRequestId::new(3),
94 canwu.revision(),
95 DecisionMutation::ReplaceOptions {
96 ticket_id: DecisionTicketId::new(1),
97 expected_version: 1,
98 context: DecisionContext::new(
99 "beiyang.aid-request.v1",
100 json!({
101 "requester": "warlord-a",
102 "battle": "ongoing-front",
103 "common_enemy": true,
104 "route_confirmed": true
105 }),
106 ),
107 options: vec![
108 DecisionOption {
109 action: DecisionAction::None,
110 utility_inputs: BTreeMap::from([
111 ("home_defense".to_owned(), 80),
112 ("alliance".to_owned(), -40),
113 ]),
114 ..DecisionOption::new("decline", "Decline aid")
115 },
116 DecisionOption {
117 action: DecisionAction::Command {
118 command: serde_json::to_value(Command::OrderMovement {
119 subject: EntityRef::Army(ids.army),
120 destination: ids.eastern_territory,
121 cargo: Vec::new(),
122 })?,
123 },
124 utility_inputs: BTreeMap::from([
125 ("home_defense".to_owned(), -20),
126 ("alliance".to_owned(), 90),
127 ]),
128 ..DecisionOption::new("send-aid", "Send the neighboring army")
129 },
130 ],
131 },
132 ),
133 )?;
134 canwu
135 .step_canonical()?
136 .expect("dynamic option refresh boundary");
137 Ok(())
138}
139
140fn resolve_aid_request(canwu: &mut Canwu) -> Result<(), Box<dyn std::error::Error>> {
141 let policy = WeightedUtilityPolicy::new(
142 "aid-utility",
143 "1",
144 UtilityProfile {
145 weights: BTreeMap::from([("alliance".to_owned(), 3), ("home_defense".to_owned(), 1)]),
146 },
147 );
148 let evaluation = canwu.drive_decision(
149 canwu.time(),
150 0,
151 DecisionRequestId::new(4),
152 Some(CommandRequestId::new(1)),
153 DecisionTicketId::new(1),
154 &policy,
155 )?;
156 assert!(matches!(evaluation, DecisionEvaluation::Prepared(_)));
157 canwu
158 .step_canonical()?
159 .expect("decision resolution boundary");
160 Ok(())
161}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}96fn enqueue_ticket(
97 canwu: &mut Canwu,
98 ticket_id: DecisionTicketId,
99 request_offset: u64,
100 controller: DecisionControllerBinding,
101) -> Result<(), CanwuError> {
102 let now = canwu.time();
103 let controller_id = controller.id.clone();
104 canwu.enqueue_decision(
105 now,
106 0,
107 DecisionIngressRequest::new(
108 DecisionRequestId::new(request_offset),
109 canwu.revision(),
110 DecisionMutation::RegisterController { controller },
111 ),
112 )?;
113 canwu.enqueue_decision(
114 now,
115 0,
116 DecisionIngressRequest::new(
117 DecisionRequestId::new(request_offset + 1),
118 canwu.revision(),
119 DecisionMutation::Open {
120 ticket: DecisionTicketDraft {
121 id: ticket_id,
122 definition: "example.law-passage".to_owned(),
123 decision_maker: EntityRef::Person(Canwu::demo_ids().commander),
124 assigned_controller: controller_id,
125 summary: "Will the proposed law pass?".to_owned(),
126 context: DecisionContext::new(
127 "example.law-passage.v1",
128 json!({
129 "supporting_seats": 72,
130 "opposing_seats": 28,
131 "public_pressure": "high"
132 }),
133 ),
134 options: vec![
135 DecisionOption::new("pass", "Pass the law"),
136 DecisionOption::new("fail", "Reject the law"),
137 ],
138 deadline: None,
139 parent_ticket: None,
140 },
141 },
142 ),
143 )?;
144 canwu.settle_boundary(canwu_api::BoundaryRequest::at(now))?;
145 Ok(())
146}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?
163fn verify_persistence_and_replay(canwu: &Canwu) -> Result<(), Box<dyn std::error::Error>> {
164 let snapshot = canwu.snapshot();
165 verify_snapshot_round_trip(&snapshot)?;
166 verify_journal_replay(canwu, &snapshot)
167}
168
169fn verify_snapshot_round_trip(
170 snapshot: &canwu_api::SimulationSnapshot,
171) -> Result<(), Box<dyn std::error::Error>> {
172 let restored = Canwu::from_snapshot_json(&serde_json::to_string(&snapshot)?)?;
173 assert_eq!(restored.snapshot(), *snapshot);
174 Ok(())
175}
176
177fn verify_journal_replay(
178 canwu: &Canwu,
179 snapshot: &canwu_api::SimulationSnapshot,
180) -> Result<(), Box<dyn std::error::Error>> {
181 let replayed = Canwu::replay_from_journal(&[], &canwu.replay_journal())?;
182 assert_eq!(replayed.snapshot(), *snapshot);
183 Ok(())
184}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.