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.
pub fn new_with_plugins( seed: u64, scenario: Scenario, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>
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>
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}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 }3fn main() -> Result<(), Box<dyn std::error::Error>> {
4 let mut canwu = Canwu::demo(35)?;
5 let ids = Canwu::demo_ids();
6
7 canwu.act(
8 ids.commander,
9 SemanticAction::MoveArmy {
10 army: ids.army,
11 destination: ids.eastern_territory,
12 },
13 )?;
14 let arrival_events = canwu.advance(SimDuration::days(1))?;
15
16 let commander = canwu.observe(ids.commander, &ObserveRequest::default())?;
17 let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
18 println!("time: {}", canwu.time());
19 println!("arrival events: {}", arrival_events.len());
20 println!(
21 "commander knows location: {:?}",
22 commander.known_armies[0].known_location
23 );
24 println!(
25 "observer still believes location: {:?}",
26 observer.known_armies[0].known_location
27 );
28
29 canwu.advance(SimDuration::days(3))?;
30 let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
31 println!(
32 "observer after report: {:?}",
33 observer.known_armies[0].known_location
34 );
35 Ok(())
36}Sourcepub fn demo_ids() -> DemoIds
pub fn demo_ids() -> DemoIds
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
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::MoveArmy {
270 army: self.ids.army,
271 destination: self.ids.eastern_territory,
272 },
273 )
274 .at_time(due_at);
275 let request = CommandRequest::new(
276 CommandRequestId::new(1),
277 self.authority.revision(),
278 envelope,
279 );
280 let ingress = self.authority.enqueue_command(due_at, 0, request)?;
281
282 if self.verbose {
283 println!(
284 "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
285 ingress.ingress_id,
286 );
287 }
288
289 // This drains ingress due at the current canonical time only. The
290 // mid-quantum command remains queued until normal accumulation reaches
291 // its quantized minute; future scheduled work is never jumped to.
292 let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
293 self.canonical_boundaries += receipts.len();
294 self.refresh_presentation_from_authority();
295 self.presentation.render(self.presentation_time());
296 if self.verbose {
297 self.print_frame(GameSpeed::X1);
298 }
299 self.report_new_events();
300 Ok(())
301 }
302
303 fn refresh_presentation_from_authority(&mut self) {
304 self.presentation
305 .refresh_from_authority(&self.authority.world(), self.ids.army);
306 }
307
308 fn presentation_time(&self) -> PresentationTime {
309 PresentationTime {
310 canonical: self.authority.time(),
311 sub_minute: Duration::from_nanos(
312 u64::try_from(self.accumulated_sim_nanos)
313 .expect("the sub-minute accumulator must fit a Duration"),
314 ),
315 }
316 }
317
318 fn crossed_progress_milestone(&mut self, progress: Option<f64>) -> bool {
319 let Some(progress) = progress else {
320 return false;
321 };
322 let mut crossed = false;
323 while self.next_progress_milestone <= 100
324 && progress * 100.0 + f64::EPSILON >= f64::from(self.next_progress_milestone)
325 {
326 crossed = true;
327 self.next_progress_milestone += 25;
328 }
329 crossed
330 }
331
332 fn print_frame(&self, speed: GameSpeed) {
333 println!(
334 "frame={:>5} speed={:<6} wall={:>7.3}s visual_time={} canwu_time={} {}",
335 self.frame,
336 speed.label(),
337 self.wall_elapsed.as_secs_f64(),
338 self.presentation_time(),
339 self.authority.time(),
340 self.presentation.describe(),
341 );
342 }
343
344 fn report_new_events(&mut self) {
345 let events = self.authority.events();
346 if self.verbose {
347 for event in &events[self.reported_events..] {
348 println!(
349 "EVENT: at={} type={} {}",
350 event.timestamp,
351 event.kind.qualified_event_type(),
352 event.summary,
353 );
354 }
355 }
356 self.reported_events = events.len();
357 }
358
359 fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
360 Ok(RunOutcome {
361 profile: profile.name,
362 frames: self.frame,
363 time: self.authority.time(),
364 world: self.authority.world(),
365 events: self.authority.events().to_vec(),
366 commands: self.authority.commands().to_vec(),
367 authoritative_state_hash: self.authority.authoritative_state_hash()?,
368 checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
369 canonical_boundaries: self.canonical_boundaries,
370 })
371 }
372}
373
374struct RunOutcome {
375 profile: &'static str,
376 frames: u64,
377 time: SimTime,
378 world: WorldSnapshot,
379 events: Vec<SimEvent>,
380 commands: Vec<CommandRecord>,
381 authoritative_state_hash: String,
382 checkpoint_hash: String,
383 canonical_boundaries: usize,
384}
385
386fn run(profile: FrameProfile, verbose: bool) -> Result<RunOutcome, CanwuError> {
387 let mut host = GameHost::new(verbose)?;
388 if verbose {
389 println!("\n{} render loop", profile.name);
390 }
391
392 host.run_phase(SPEED_SCHEDULE[0], profile)?;
393 host.submit_player_command()?;
394 host.run_phase(SPEED_SCHEDULE[1], profile)?;
395 host.run_phase(SPEED_SCHEDULE[2], profile)?;
396
397 let time_before_pause = host.authority.time();
398 let remainder_before_pause = host.accumulated_sim_nanos;
399 let presentation_before_pause = host.presentation_time();
400 let frames_before_pause = host.frame;
401 assert!(
402 remainder_before_pause > 0,
403 "the pause fixture must preserve a fractional simulation minute"
404 );
405 host.run_phase(SPEED_SCHEDULE[3], profile)?;
406 assert!(
407 host.frame > frames_before_pause,
408 "pause must keep rendering"
409 );
410 assert_eq!(
411 host.authority.time(),
412 time_before_pause,
413 "pause must not advance Canwu"
414 );
415 assert_eq!(
416 host.accumulated_sim_nanos, remainder_before_pause,
417 "pause must not add desired simulation time"
418 );
419 assert_eq!(
420 host.presentation_time(),
421 presentation_before_pause,
422 "pause must freeze presentation time"
423 );
424
425 host.run_phase(SPEED_SCHEDULE[4], profile)?;
426 assert_eq!(
427 host.accumulated_sim_nanos, 0,
428 "the scripted schedule should end on a simulation-minute boundary"
429 );
430 host.outcome(profile)
431}
432
433fn std_duration(duration: SimDuration) -> Duration {
434 let minutes = u64::try_from(duration.as_minutes())
435 .expect("presentation interpolation requires a non-negative duration");
436 Duration::from_secs(
437 minutes
438 .checked_mul(60)
439 .expect("presentation duration must remain representable"),
440 )
441}
442
443fn assert_fps_independent(left: &RunOutcome, right: &RunOutcome) {
444 assert_ne!(
445 left.frames, right.frames,
446 "the render segmentations must differ"
447 );
448 assert_eq!(left.time, right.time, "Canwu time changed with render FPS");
449 assert_eq!(
450 left.world, right.world,
451 "world state changed with render FPS"
452 );
453 assert_eq!(left.events, right.events, "events changed with render FPS");
454 assert_eq!(
455 left.commands, right.commands,
456 "command ordering changed with render FPS"
457 );
458 assert_eq!(
459 left.authoritative_state_hash, right.authoritative_state_hash,
460 "authoritative state hash changed with render FPS"
461 );
462 assert_eq!(
463 left.checkpoint_hash, right.checkpoint_hash,
464 "checkpoint hash changed with render FPS"
465 );
466 assert_eq!(
467 left.canonical_boundaries, right.canonical_boundaries,
468 "canonical boundary count changed with render FPS"
469 );
470}
471
472fn assert_expected_outcome(outcome: &RunOutcome) {
473 let ids = Canwu::demo_ids();
474 let command_at = SimTime::from_minutes(1);
475 let arrival_at = SimTime::from_minutes(1_081);
476 let final_time = SimTime::from_minutes(1_082);
477
478 assert_eq!(outcome.time, final_time, "the scripted final time changed");
479 let army = outcome
480 .world
481 .army(ids.army)
482 .expect("the demo army must remain present");
483 assert_eq!(
484 army.location, ids.eastern_territory,
485 "the scheduled arrival must execute"
486 );
487 assert!(
488 army.transit.is_none(),
489 "the army must no longer be in authoritative transit"
490 );
491
492 assert_eq!(
493 outcome.commands.len(),
494 1,
495 "the move command must be accepted"
496 );
497 let command = &outcome.commands[0];
498 assert_eq!(command.accepted_at, command_at);
499 assert_eq!(command.envelope.expected_time, Some(command_at));
500 assert!(matches!(
501 &command.envelope.command,
502 Command::MoveArmy { army, destination }
503 if *army == ids.army && *destination == ids.eastern_territory
504 ));
505
506 let event_timeline = outcome
507 .events
508 .iter()
509 .map(|event| (event.kind.qualified_event_type(), event.timestamp))
510 .collect::<Vec<_>>();
511 assert_eq!(
512 event_timeline,
513 vec![
514 ("move_ordered".to_owned(), command_at),
515 ("army_arrived".to_owned(), arrival_at),
516 ("knowledge_updated".to_owned(), arrival_at),
517 ("report_dispatched".to_owned(), arrival_at),
518 ],
519 "the expected command and scheduled-arrival event timeline changed"
520 );
521 assert_eq!(
522 outcome.canonical_boundaries, 2,
523 "the command and arrival should settle at two canonical boundaries"
524 );
525}3fn main() -> Result<(), Box<dyn std::error::Error>> {
4 let mut canwu = Canwu::demo(35)?;
5 let ids = Canwu::demo_ids();
6
7 canwu.act(
8 ids.commander,
9 SemanticAction::MoveArmy {
10 army: ids.army,
11 destination: ids.eastern_territory,
12 },
13 )?;
14 let arrival_events = canwu.advance(SimDuration::days(1))?;
15
16 let commander = canwu.observe(ids.commander, &ObserveRequest::default())?;
17 let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
18 println!("time: {}", canwu.time());
19 println!("arrival events: {}", arrival_events.len());
20 println!(
21 "commander knows location: {:?}",
22 commander.known_armies[0].known_location
23 );
24 println!(
25 "observer still believes location: {:?}",
26 observer.known_armies[0].known_location
27 );
28
29 canwu.advance(SimDuration::days(3))?;
30 let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
31 println!(
32 "observer after report: {:?}",
33 observer.known_armies[0].known_location
34 );
35 Ok(())
36}Sourcepub const fn time(&self) -> SimTime
pub const fn time(&self) -> SimTime
Examples found in repository?
More examples
3fn main() -> Result<(), Box<dyn std::error::Error>> {
4 let mut canwu = Canwu::demo(35)?;
5 let ids = Canwu::demo_ids();
6
7 canwu.act(
8 ids.commander,
9 SemanticAction::MoveArmy {
10 army: ids.army,
11 destination: ids.eastern_territory,
12 },
13 )?;
14 let arrival_events = canwu.advance(SimDuration::days(1))?;
15
16 let commander = canwu.observe(ids.commander, &ObserveRequest::default())?;
17 let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
18 println!("time: {}", canwu.time());
19 println!("arrival events: {}", arrival_events.len());
20 println!(
21 "commander knows location: {:?}",
22 commander.known_armies[0].known_location
23 );
24 println!(
25 "observer still believes location: {:?}",
26 observer.known_armies[0].known_location
27 );
28
29 canwu.advance(SimDuration::days(3))?;
30 let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
31 println!(
32 "observer after report: {:?}",
33 observer.known_armies[0].known_location
34 );
35 Ok(())
36}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::MoveArmy {
270 army: self.ids.army,
271 destination: self.ids.eastern_territory,
272 },
273 )
274 .at_time(due_at);
275 let request = CommandRequest::new(
276 CommandRequestId::new(1),
277 self.authority.revision(),
278 envelope,
279 );
280 let ingress = self.authority.enqueue_command(due_at, 0, request)?;
281
282 if self.verbose {
283 println!(
284 "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
285 ingress.ingress_id,
286 );
287 }
288
289 // This drains ingress due at the current canonical time only. The
290 // mid-quantum command remains queued until normal accumulation reaches
291 // its quantized minute; future scheduled work is never jumped to.
292 let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
293 self.canonical_boundaries += receipts.len();
294 self.refresh_presentation_from_authority();
295 self.presentation.render(self.presentation_time());
296 if self.verbose {
297 self.print_frame(GameSpeed::X1);
298 }
299 self.report_new_events();
300 Ok(())
301 }
302
303 fn refresh_presentation_from_authority(&mut self) {
304 self.presentation
305 .refresh_from_authority(&self.authority.world(), self.ids.army);
306 }
307
308 fn presentation_time(&self) -> PresentationTime {
309 PresentationTime {
310 canonical: self.authority.time(),
311 sub_minute: Duration::from_nanos(
312 u64::try_from(self.accumulated_sim_nanos)
313 .expect("the sub-minute accumulator must fit a Duration"),
314 ),
315 }
316 }
317
318 fn crossed_progress_milestone(&mut self, progress: Option<f64>) -> bool {
319 let Some(progress) = progress else {
320 return false;
321 };
322 let mut crossed = false;
323 while self.next_progress_milestone <= 100
324 && progress * 100.0 + f64::EPSILON >= f64::from(self.next_progress_milestone)
325 {
326 crossed = true;
327 self.next_progress_milestone += 25;
328 }
329 crossed
330 }
331
332 fn print_frame(&self, speed: GameSpeed) {
333 println!(
334 "frame={:>5} speed={:<6} wall={:>7.3}s visual_time={} canwu_time={} {}",
335 self.frame,
336 speed.label(),
337 self.wall_elapsed.as_secs_f64(),
338 self.presentation_time(),
339 self.authority.time(),
340 self.presentation.describe(),
341 );
342 }
343
344 fn report_new_events(&mut self) {
345 let events = self.authority.events();
346 if self.verbose {
347 for event in &events[self.reported_events..] {
348 println!(
349 "EVENT: at={} type={} {}",
350 event.timestamp,
351 event.kind.qualified_event_type(),
352 event.summary,
353 );
354 }
355 }
356 self.reported_events = events.len();
357 }
358
359 fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
360 Ok(RunOutcome {
361 profile: profile.name,
362 frames: self.frame,
363 time: self.authority.time(),
364 world: self.authority.world(),
365 events: self.authority.events().to_vec(),
366 commands: self.authority.commands().to_vec(),
367 authoritative_state_hash: self.authority.authoritative_state_hash()?,
368 checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
369 canonical_boundaries: self.canonical_boundaries,
370 })
371 }
372}
373
374struct RunOutcome {
375 profile: &'static str,
376 frames: u64,
377 time: SimTime,
378 world: WorldSnapshot,
379 events: Vec<SimEvent>,
380 commands: Vec<CommandRecord>,
381 authoritative_state_hash: String,
382 checkpoint_hash: String,
383 canonical_boundaries: usize,
384}
385
386fn run(profile: FrameProfile, verbose: bool) -> Result<RunOutcome, CanwuError> {
387 let mut host = GameHost::new(verbose)?;
388 if verbose {
389 println!("\n{} render loop", profile.name);
390 }
391
392 host.run_phase(SPEED_SCHEDULE[0], profile)?;
393 host.submit_player_command()?;
394 host.run_phase(SPEED_SCHEDULE[1], profile)?;
395 host.run_phase(SPEED_SCHEDULE[2], profile)?;
396
397 let time_before_pause = host.authority.time();
398 let remainder_before_pause = host.accumulated_sim_nanos;
399 let presentation_before_pause = host.presentation_time();
400 let frames_before_pause = host.frame;
401 assert!(
402 remainder_before_pause > 0,
403 "the pause fixture must preserve a fractional simulation minute"
404 );
405 host.run_phase(SPEED_SCHEDULE[3], profile)?;
406 assert!(
407 host.frame > frames_before_pause,
408 "pause must keep rendering"
409 );
410 assert_eq!(
411 host.authority.time(),
412 time_before_pause,
413 "pause must not advance Canwu"
414 );
415 assert_eq!(
416 host.accumulated_sim_nanos, remainder_before_pause,
417 "pause must not add desired simulation time"
418 );
419 assert_eq!(
420 host.presentation_time(),
421 presentation_before_pause,
422 "pause must freeze presentation time"
423 );
424
425 host.run_phase(SPEED_SCHEDULE[4], profile)?;
426 assert_eq!(
427 host.accumulated_sim_nanos, 0,
428 "the scripted schedule should end on a simulation-minute boundary"
429 );
430 host.outcome(profile)
431}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::MoveArmy {
270 army: self.ids.army,
271 destination: self.ids.eastern_territory,
272 },
273 )
274 .at_time(due_at);
275 let request = CommandRequest::new(
276 CommandRequestId::new(1),
277 self.authority.revision(),
278 envelope,
279 );
280 let ingress = self.authority.enqueue_command(due_at, 0, request)?;
281
282 if self.verbose {
283 println!(
284 "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
285 ingress.ingress_id,
286 );
287 }
288
289 // This drains ingress due at the current canonical time only. The
290 // mid-quantum command remains queued until normal accumulation reaches
291 // its quantized minute; future scheduled work is never jumped to.
292 let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
293 self.canonical_boundaries += receipts.len();
294 self.refresh_presentation_from_authority();
295 self.presentation.render(self.presentation_time());
296 if self.verbose {
297 self.print_frame(GameSpeed::X1);
298 }
299 self.report_new_events();
300 Ok(())
301 }pub fn run_manifest_hash(&self) -> &str
Sourcepub fn checkpoint_hash(&self) -> &str
pub fn checkpoint_hash(&self) -> &str
Examples found in repository?
359 fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
360 Ok(RunOutcome {
361 profile: profile.name,
362 frames: self.frame,
363 time: self.authority.time(),
364 world: self.authority.world(),
365 events: self.authority.events().to_vec(),
366 commands: self.authority.commands().to_vec(),
367 authoritative_state_hash: self.authority.authoritative_state_hash()?,
368 checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
369 canonical_boundaries: self.canonical_boundaries,
370 })
371 }Examples found in repository?
359 fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
360 Ok(RunOutcome {
361 profile: profile.name,
362 frames: self.frame,
363 time: self.authority.time(),
364 world: self.authority.world(),
365 events: self.authority.events().to_vec(),
366 commands: self.authority.commands().to_vec(),
367 authoritative_state_hash: self.authority.authoritative_state_hash()?,
368 checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
369 canonical_boundaries: self.canonical_boundaries,
370 })
371 }Sourcepub fn world(&self) -> WorldSnapshot
pub fn world(&self) -> WorldSnapshot
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::MoveArmy {
270 army: self.ids.army,
271 destination: self.ids.eastern_territory,
272 },
273 )
274 .at_time(due_at);
275 let request = CommandRequest::new(
276 CommandRequestId::new(1),
277 self.authority.revision(),
278 envelope,
279 );
280 let ingress = self.authority.enqueue_command(due_at, 0, request)?;
281
282 if self.verbose {
283 println!(
284 "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
285 ingress.ingress_id,
286 );
287 }
288
289 // This drains ingress due at the current canonical time only. The
290 // mid-quantum command remains queued until normal accumulation reaches
291 // its quantized minute; future scheduled work is never jumped to.
292 let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
293 self.canonical_boundaries += receipts.len();
294 self.refresh_presentation_from_authority();
295 self.presentation.render(self.presentation_time());
296 if self.verbose {
297 self.print_frame(GameSpeed::X1);
298 }
299 self.report_new_events();
300 Ok(())
301 }
302
303 fn refresh_presentation_from_authority(&mut self) {
304 self.presentation
305 .refresh_from_authority(&self.authority.world(), self.ids.army);
306 }
307
308 fn presentation_time(&self) -> PresentationTime {
309 PresentationTime {
310 canonical: self.authority.time(),
311 sub_minute: Duration::from_nanos(
312 u64::try_from(self.accumulated_sim_nanos)
313 .expect("the sub-minute accumulator must fit a Duration"),
314 ),
315 }
316 }
317
318 fn crossed_progress_milestone(&mut self, progress: Option<f64>) -> bool {
319 let Some(progress) = progress else {
320 return false;
321 };
322 let mut crossed = false;
323 while self.next_progress_milestone <= 100
324 && progress * 100.0 + f64::EPSILON >= f64::from(self.next_progress_milestone)
325 {
326 crossed = true;
327 self.next_progress_milestone += 25;
328 }
329 crossed
330 }
331
332 fn print_frame(&self, speed: GameSpeed) {
333 println!(
334 "frame={:>5} speed={:<6} wall={:>7.3}s visual_time={} canwu_time={} {}",
335 self.frame,
336 speed.label(),
337 self.wall_elapsed.as_secs_f64(),
338 self.presentation_time(),
339 self.authority.time(),
340 self.presentation.describe(),
341 );
342 }
343
344 fn report_new_events(&mut self) {
345 let events = self.authority.events();
346 if self.verbose {
347 for event in &events[self.reported_events..] {
348 println!(
349 "EVENT: at={} type={} {}",
350 event.timestamp,
351 event.kind.qualified_event_type(),
352 event.summary,
353 );
354 }
355 }
356 self.reported_events = events.len();
357 }
358
359 fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
360 Ok(RunOutcome {
361 profile: profile.name,
362 frames: self.frame,
363 time: self.authority.time(),
364 world: self.authority.world(),
365 events: self.authority.events().to_vec(),
366 commands: self.authority.commands().to_vec(),
367 authoritative_state_hash: self.authority.authoritative_state_hash()?,
368 checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
369 canonical_boundaries: self.canonical_boundaries,
370 })
371 }pub fn knowledge(&self) -> &KnowledgeSnapshot
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::MoveArmy {
270 army: self.ids.army,
271 destination: self.ids.eastern_territory,
272 },
273 )
274 .at_time(due_at);
275 let request = CommandRequest::new(
276 CommandRequestId::new(1),
277 self.authority.revision(),
278 envelope,
279 );
280 let ingress = self.authority.enqueue_command(due_at, 0, request)?;
281
282 if self.verbose {
283 println!(
284 "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
285 ingress.ingress_id,
286 );
287 }
288
289 // This drains ingress due at the current canonical time only. The
290 // mid-quantum command remains queued until normal accumulation reaches
291 // its quantized minute; future scheduled work is never jumped to.
292 let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
293 self.canonical_boundaries += receipts.len();
294 self.refresh_presentation_from_authority();
295 self.presentation.render(self.presentation_time());
296 if self.verbose {
297 self.print_frame(GameSpeed::X1);
298 }
299 self.report_new_events();
300 Ok(())
301 }
302
303 fn refresh_presentation_from_authority(&mut self) {
304 self.presentation
305 .refresh_from_authority(&self.authority.world(), self.ids.army);
306 }
307
308 fn presentation_time(&self) -> PresentationTime {
309 PresentationTime {
310 canonical: self.authority.time(),
311 sub_minute: Duration::from_nanos(
312 u64::try_from(self.accumulated_sim_nanos)
313 .expect("the sub-minute accumulator must fit a Duration"),
314 ),
315 }
316 }
317
318 fn crossed_progress_milestone(&mut self, progress: Option<f64>) -> bool {
319 let Some(progress) = progress else {
320 return false;
321 };
322 let mut crossed = false;
323 while self.next_progress_milestone <= 100
324 && progress * 100.0 + f64::EPSILON >= f64::from(self.next_progress_milestone)
325 {
326 crossed = true;
327 self.next_progress_milestone += 25;
328 }
329 crossed
330 }
331
332 fn print_frame(&self, speed: GameSpeed) {
333 println!(
334 "frame={:>5} speed={:<6} wall={:>7.3}s visual_time={} canwu_time={} {}",
335 self.frame,
336 speed.label(),
337 self.wall_elapsed.as_secs_f64(),
338 self.presentation_time(),
339 self.authority.time(),
340 self.presentation.describe(),
341 );
342 }
343
344 fn report_new_events(&mut self) {
345 let events = self.authority.events();
346 if self.verbose {
347 for event in &events[self.reported_events..] {
348 println!(
349 "EVENT: at={} type={} {}",
350 event.timestamp,
351 event.kind.qualified_event_type(),
352 event.summary,
353 );
354 }
355 }
356 self.reported_events = events.len();
357 }
358
359 fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
360 Ok(RunOutcome {
361 profile: profile.name,
362 frames: self.frame,
363 time: self.authority.time(),
364 world: self.authority.world(),
365 events: self.authority.events().to_vec(),
366 commands: self.authority.commands().to_vec(),
367 authoritative_state_hash: self.authority.authoritative_state_hash()?,
368 checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
369 canonical_boundaries: self.canonical_boundaries,
370 })
371 }Sourcepub fn commands(&self) -> &[CommandRecord]
pub fn commands(&self) -> &[CommandRecord]
Examples found in repository?
359 fn outcome(self, profile: FrameProfile) -> Result<RunOutcome, CanwuError> {
360 Ok(RunOutcome {
361 profile: profile.name,
362 frames: self.frame,
363 time: self.authority.time(),
364 world: self.authority.world(),
365 events: self.authority.events().to_vec(),
366 commands: self.authority.commands().to_vec(),
367 authoritative_state_hash: self.authority.authoritative_state_hash()?,
368 checkpoint_hash: self.authority.checkpoint_hash().to_owned(),
369 canonical_boundaries: self.canonical_boundaries,
370 })
371 }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>
pub fn typed_domain_record<T: DomainRecordType>( &self, reference: &TypedDomainRecordRef<T>, ) -> Option<&DomainRecord>
pub fn domain_records(&self) -> impl Iterator<Item = &DomainRecord>
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>
pub fn replay_journal(&self) -> ReplayJournal
pub fn evidence_cursor(&self) -> Result<EvidenceCursor, CanwuError>
pub fn checkpoint(&self) -> Result<SimulationCheckpoint, 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}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::MoveArmy {
270 army: self.ids.army,
271 destination: self.ids.eastern_territory,
272 },
273 )
274 .at_time(due_at);
275 let request = CommandRequest::new(
276 CommandRequestId::new(1),
277 self.authority.revision(),
278 envelope,
279 );
280 let ingress = self.authority.enqueue_command(due_at, 0, request)?;
281
282 if self.verbose {
283 println!(
284 "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
285 ingress.ingress_id,
286 );
287 }
288
289 // This drains ingress due at the current canonical time only. The
290 // mid-quantum command remains queued until normal accumulation reaches
291 // its quantized minute; future scheduled work is never jumped to.
292 let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
293 self.canonical_boundaries += receipts.len();
294 self.refresh_presentation_from_authority();
295 self.presentation.render(self.presentation_time());
296 if self.verbose {
297 self.print_frame(GameSpeed::X1);
298 }
299 self.report_new_events();
300 Ok(())
301 }pub fn enqueue_plugin_ingress( &mut self, request: PluginIngressRequest, ) -> Result<IngressReceipt, CanwuError>
pub fn schedule_calendar_boundary( &mut self, due_at: SimTime, cadences: Vec<SystemCadence>, ) -> Result<IngressReceipt, CanwuError>
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::MoveArmy {
270 army: self.ids.army,
271 destination: self.ids.eastern_territory,
272 },
273 )
274 .at_time(due_at);
275 let request = CommandRequest::new(
276 CommandRequestId::new(1),
277 self.authority.revision(),
278 envelope,
279 );
280 let ingress = self.authority.enqueue_command(due_at, 0, request)?;
281
282 if self.verbose {
283 println!(
284 "INPUT: queued commander move as {:?}, captured_at={captured_at}, quantized_due_at={due_at}",
285 ingress.ingress_id,
286 );
287 }
288
289 // This drains ingress due at the current canonical time only. The
290 // mid-quantum command remains queued until normal accumulation reaches
291 // its quantized minute; future scheduled work is never jumped to.
292 let receipts = self.authority.advance_canonical(SimDuration::ZERO)?;
293 self.canonical_boundaries += receipts.len();
294 self.refresh_presentation_from_authority();
295 self.presentation.render(self.presentation_time());
296 if self.verbose {
297 self.print_frame(GameSpeed::X1);
298 }
299 self.report_new_events();
300 Ok(())
301 }pub fn step_canonical(&mut self) -> Result<Option<BoundaryReceipt>, CanwuError>
Sourcepub fn advance(
&mut self,
duration: SimDuration,
) -> Result<Vec<SimEvent>, CanwuError>
pub fn advance( &mut self, duration: SimDuration, ) -> Result<Vec<SimEvent>, CanwuError>
Examples found in repository?
3fn main() -> Result<(), Box<dyn std::error::Error>> {
4 let mut canwu = Canwu::demo(35)?;
5 let ids = Canwu::demo_ids();
6
7 canwu.act(
8 ids.commander,
9 SemanticAction::MoveArmy {
10 army: ids.army,
11 destination: ids.eastern_territory,
12 },
13 )?;
14 let arrival_events = canwu.advance(SimDuration::days(1))?;
15
16 let commander = canwu.observe(ids.commander, &ObserveRequest::default())?;
17 let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
18 println!("time: {}", canwu.time());
19 println!("arrival events: {}", arrival_events.len());
20 println!(
21 "commander knows location: {:?}",
22 commander.known_armies[0].known_location
23 );
24 println!(
25 "observer still believes location: {:?}",
26 observer.known_armies[0].known_location
27 );
28
29 canwu.advance(SimDuration::days(3))?;
30 let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
31 println!(
32 "observer after report: {:?}",
33 observer.known_armies[0].known_location
34 );
35 Ok(())
36}Sourcepub fn settle_boundary(
&mut self,
request: BoundaryRequest,
) -> Result<BoundaryReceipt, CanwuError>
pub fn settle_boundary( &mut self, request: BoundaryRequest, ) -> Result<BoundaryReceipt, CanwuError>
pub fn wait( &mut self, duration: SimDuration, ) -> Result<Vec<SimEvent>, CanwuError>
pub fn step(&mut self) -> Result<Vec<SimEvent>, CanwuError>
pub fn snapshot(&self) -> SimulationSnapshot
pub fn snapshot_json(&self) -> Result<String, CanwuError>
pub fn from_snapshot_json(json: &str) -> Result<Self, CanwuError>
pub fn from_snapshot_json_with_plugins( json: &str, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>
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>
pub fn replay( seed: u64, scenario: Scenario, commands: &[CommandRecord], final_time: SimTime, ) -> Result<Self, CanwuError>
pub fn replay_with_plugins( seed: u64, scenario: Scenario, plugins: &[&dyn SimulationPlugin], commands: &[CommandRecord], final_time: SimTime, ) -> Result<Self, CanwuError>
pub fn replay_with_boundaries( seed: u64, scenario: Scenario, plugins: &[&dyn SimulationPlugin], commands: &[CommandRecord], boundaries: &[BoundaryRecord], final_time: SimTime, ) -> Result<Self, CanwuError>
pub fn replay_with_run_manifest( seed: u64, scenario: Scenario, run_manifest: RunManifest, plugins: &[&dyn SimulationPlugin], commands: &[CommandRecord], boundaries: &[BoundaryRecord], final_time: SimTime, ) -> Result<Self, CanwuError>
pub fn replay_with_run_configuration( seed: u64, scenario: Scenario, run_manifest: RunManifest, run_configuration: RunConfiguration, plugins: &[&dyn SimulationPlugin], commands: &[CommandRecord], command_attempts: &[CommandAttemptRecord], boundaries: &[BoundaryRecord], final_time: SimTime, ) -> Result<Self, CanwuError>
pub fn replay_from_journal( scenario: Scenario, plugins: &[&dyn SimulationPlugin], journal: &ReplayJournal, ) -> Result<Self, CanwuError>
pub fn fork(&self) -> Self
pub fn diff(&self, other: &Self) -> WorldDiff
pub fn query(&self, query: &Query) -> QueryResult
pub fn query_as( &self, actor: PersonId, query: &Query, ) -> Result<QueryResult, CanwuError>
Sourcepub fn viewer_context(
&self,
actor: PersonId,
) -> Result<ViewerContext, CanwuError>
pub fn viewer_context( &self, actor: PersonId, ) -> Result<ViewerContext, CanwuError>
Builds an authorized viewer context from the persisted run policy.
Callers cannot select a stronger observation policy through an observation request; the run configuration is the input-control boundary for actor-relative versus research projections.
Sourcepub fn observe(
&self,
actor: PersonId,
request: &ObserveRequest,
) -> Result<AgentContext, CanwuError>
pub fn observe( &self, actor: PersonId, request: &ObserveRequest, ) -> Result<AgentContext, CanwuError>
Examples found in repository?
3fn main() -> Result<(), Box<dyn std::error::Error>> {
4 let mut canwu = Canwu::demo(35)?;
5 let ids = Canwu::demo_ids();
6
7 canwu.act(
8 ids.commander,
9 SemanticAction::MoveArmy {
10 army: ids.army,
11 destination: ids.eastern_territory,
12 },
13 )?;
14 let arrival_events = canwu.advance(SimDuration::days(1))?;
15
16 let commander = canwu.observe(ids.commander, &ObserveRequest::default())?;
17 let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
18 println!("time: {}", canwu.time());
19 println!("arrival events: {}", arrival_events.len());
20 println!(
21 "commander knows location: {:?}",
22 commander.known_armies[0].known_location
23 );
24 println!(
25 "observer still believes location: {:?}",
26 observer.known_armies[0].known_location
27 );
28
29 canwu.advance(SimDuration::days(3))?;
30 let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
31 println!(
32 "observer after report: {:?}",
33 observer.known_armies[0].known_location
34 );
35 Ok(())
36}Sourcepub fn observe_with_viewer(
&self,
viewer: &ViewerContext,
request: &ObserveRequest,
) -> Result<AgentContext, CanwuError>
pub fn observe_with_viewer( &self, viewer: &ViewerContext, request: &ObserveRequest, ) -> Result<AgentContext, CanwuError>
Projects the simulation for a previously authorized viewer context.
The context controls only the player-facing projection. Plugin system subscriptions and state read permissions remain enforced by the simulation runtime and are not widened by this method.
pub fn inspect( &self, actor: PersonId, entity: &EntityRef, detail: DetailLevel, ) -> Result<Inspection, CanwuError>
pub fn available_actions( &self, actor: PersonId, ) -> Result<Vec<AvailableAction>, CanwuError>
Sourcepub fn act(
&mut self,
actor: PersonId,
action: SemanticAction,
) -> Result<CommandReceipt, CanwuError>
pub fn act( &mut self, actor: PersonId, action: SemanticAction, ) -> Result<CommandReceipt, CanwuError>
Examples found in repository?
3fn main() -> Result<(), Box<dyn std::error::Error>> {
4 let mut canwu = Canwu::demo(35)?;
5 let ids = Canwu::demo_ids();
6
7 canwu.act(
8 ids.commander,
9 SemanticAction::MoveArmy {
10 army: ids.army,
11 destination: ids.eastern_territory,
12 },
13 )?;
14 let arrival_events = canwu.advance(SimDuration::days(1))?;
15
16 let commander = canwu.observe(ids.commander, &ObserveRequest::default())?;
17 let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
18 println!("time: {}", canwu.time());
19 println!("arrival events: {}", arrival_events.len());
20 println!(
21 "commander knows location: {:?}",
22 commander.known_armies[0].known_location
23 );
24 println!(
25 "observer still believes location: {:?}",
26 observer.known_armies[0].known_location
27 );
28
29 canwu.advance(SimDuration::days(3))?;
30 let observer = canwu.observe(ids.observer, &ObserveRequest::default())?;
31 println!(
32 "observer after report: {:?}",
33 observer.known_armies[0].known_location
34 );
35 Ok(())
36}