Skip to main content

Canwu

Struct Canwu 

Source
pub struct Canwu { /* private fields */ }
Expand description

Main in-process API. All returned world values are detached snapshots.

Implementations§

Source§

impl Canwu

Source

pub const fn version() -> &'static str

Source

pub fn new(seed: u64, scenario: Scenario) -> Result<Self, CanwuError>

Source

pub fn into_compacted(self) -> Result<CompactedCanwu, CanwuError>

Enters the explicit compact-journal interface without discarding evidence.

Source

pub fn new_with_plugins( seed: u64, scenario: Scenario, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>

Examples found in repository?
examples/governance_transition.rs (line 555)
549fn main() -> Result<(), CanwuError> {
550    let central = CentralPlugin;
551    let treasury = TreasuryPlugin;
552    let county = CountyPlugin;
553    let plugins: [&dyn SimulationPlugin; 3] = [&central, &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}
Source

pub fn new_with_manifest( seed: u64, scenario: Scenario, run_manifest: RunManifest, ) -> Result<Self, CanwuError>

Source

pub fn new_with_manifest_and_plugins( seed: u64, scenario: Scenario, run_manifest: RunManifest, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>

Source

pub fn new_with_run_configuration( seed: u64, scenario: Scenario, run_manifest: RunManifest, run_configuration: RunConfiguration, ) -> Result<Self, CanwuError>

Source

pub fn new_with_run_configuration_and_plugins( seed: u64, scenario: Scenario, run_manifest: RunManifest, run_configuration: RunConfiguration, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>

Source

pub fn demo(seed: u64) -> Result<Self, CanwuError>

Deprecated compatibility scenario. New hosts should use an integration-owned scenario.

Examples found in repository?
examples/phased_boundary.rs (line 150)
149fn main() -> Result<(), CanwuError> {
150    let mut canwu = Canwu::demo(35)?;
151    canwu.register_plugin(&SupplyPlugin)?;
152    canwu.register_plugin(&DemandPlugin)?;
153
154    let receipt = canwu
155        .settle_boundary(BoundaryRequest::at(canwu.time()).with_cadence(SystemCadence::Daily))?;
156    assert_eq!(receipt.allocations[0].granted, 6);
157    assert_eq!(canwu.boundaries()[0].emissions.len(), 2);
158    Ok(())
159}
More examples
Hide additional examples
examples/plugin.rs (line 89)
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}
examples/decision_ticket.rs (line 12)
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}
examples/continuous_game_loop.rs (line 182)
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    }
examples/uncertainty_resolution.rs (line 149)
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}
Source

pub fn demo_ids() -> DemoIds

IDs for the deprecated compatibility scenario.

Examples found in repository?
examples/plugin.rs (line 90)
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
Hide additional examples
examples/decision_ticket.rs (line 13)
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}
examples/continuous_game_loop.rs (line 183)
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}
examples/uncertainty_resolution.rs (line 123)
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}
Source

pub const fn time(&self) -> SimTime

Examples found in repository?
examples/phased_boundary.rs (line 155)
149fn main() -> Result<(), CanwuError> {
150    let mut canwu = Canwu::demo(35)?;
151    canwu.register_plugin(&SupplyPlugin)?;
152    canwu.register_plugin(&DemandPlugin)?;
153
154    let receipt = canwu
155        .settle_boundary(BoundaryRequest::at(canwu.time()).with_cadence(SystemCadence::Daily))?;
156    assert_eq!(receipt.allocations[0].granted, 6);
157    assert_eq!(canwu.boundaries()[0].emissions.len(), 2);
158    Ok(())
159}
More examples
Hide additional examples
examples/continuous_game_loop.rs (line 260)
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}
examples/uncertainty_resolution.rs (line 102)
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}
examples/decision_ticket.rs (line 30)
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}
Source

pub const fn run_manifest(&self) -> &RunManifest

Source

pub const fn run_configuration(&self) -> &RunConfigurationSnapshot

Source

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?
examples/continuous_game_loop.rs (line 278)
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
Hide additional examples
examples/uncertainty_resolution.rs (line 109)
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}
examples/decision_ticket.rs (line 45)
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}
Source

pub fn run_manifest_hash(&self) -> &str

Source

pub fn checkpoint_hash(&self) -> &str

Examples found in repository?
examples/continuous_game_loop.rs (line 369)
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    }
Source

pub fn authoritative_state_hash(&self) -> Result<String, CanwuError>

Examples found in repository?
examples/continuous_game_loop.rs (line 368)
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    }
Source

pub fn entities(&self) -> impl Iterator<Item = &EntityRef>

Source

pub fn entity_exists(&self, entity: &EntityRef) -> bool

Source

pub fn world(&self) -> WorldSnapshot

Deprecated detached format-5 compatibility projection.

Examples found in repository?
examples/continuous_game_loop.rs (line 184)
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    }
Source

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.

Source

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.

Source

pub fn pending_transition_manifests( &self, ) -> impl Iterator<Item = &PendingTransitionManifest>

Trusted host access to the registered transition manifests whose ready boundary has not settled, in manifest-ID order. Settled manifests leave their audit on the boundary record and receipt.

Source

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.

Source

pub fn events(&self) -> &[SimEvent]

Examples found in repository?
examples/continuous_game_loop.rs (line 245)
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    }
Source

pub fn commands(&self) -> &[CommandRecord]

Examples found in repository?
examples/continuous_game_loop.rs (line 367)
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    }
Source

pub fn boundaries(&self) -> &[BoundaryRecord]

Examples found in repository?
examples/phased_boundary.rs (line 157)
149fn main() -> Result<(), CanwuError> {
150    let mut canwu = Canwu::demo(35)?;
151    canwu.register_plugin(&SupplyPlugin)?;
152    canwu.register_plugin(&DemandPlugin)?;
153
154    let receipt = canwu
155        .settle_boundary(BoundaryRequest::at(canwu.time()).with_cadence(SystemCadence::Daily))?;
156    assert_eq!(receipt.allocations[0].granted, 6);
157    assert_eq!(canwu.boundaries()[0].emissions.len(), 2);
158    Ok(())
159}
Source

pub fn command_attempts(&self) -> &[CommandAttemptRecord]

Source

pub fn ingress_log(&self) -> &[IngressRecord]

Source

pub fn domain_record( &self, reference: &DomainRecordRef, ) -> Option<&DomainRecord>

Source

pub fn domain_record_version_evidence_exists( &self, reference: &DomainRecordVersionRef, ) -> bool

Returns whether an exact domain-record version exists in current or retained evidence.

Source

pub fn evidence_exists(&self, reference: &EvidenceRef) -> bool

Returns whether a generic evidence identity is retained or archived.

Source

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.

Source

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.

Source

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.

Source

pub fn typed_domain_record<T: DomainRecordType>( &self, reference: &TypedDomainRecordRef<T>, ) -> Option<&DomainRecord>

Examples found in repository?
examples/governance_transition.rs (line 566)
549fn main() -> Result<(), CanwuError> {
550    let central = CentralPlugin;
551    let treasury = TreasuryPlugin;
552    let county = CountyPlugin;
553    let plugins: [&dyn SimulationPlugin; 3] = [&central, &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}
Source

pub fn domain_records(&self) -> impl Iterator<Item = &DomainRecord>

Source

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.

Source

pub fn decision_ticket(&self, id: DecisionTicketId) -> Option<&DecisionTicket>

Examples found in repository?
examples/uncertainty_resolution.rs (line 200)
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}
Source

pub fn decision_controller( &self, id: &str, ) -> Option<&DecisionControllerBinding>

Source

pub fn decision_trace(&self, id: DecisionTraceId) -> Option<&DecisionTrace>

Examples found in repository?
examples/decision_ticket.rs (line 20)
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
Hide additional examples
examples/uncertainty_resolution.rs (line 170)
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}
Source

pub fn decision_attempt( &self, id: DecisionRequestId, ) -> Option<&DecisionAttemptRecord>

Source

pub fn decision_hot_state(&self) -> DecisionHotState

Source

pub fn decision_history_location( &self, key: &DecisionHistoryKey, ) -> DecisionHistoryLocation

Source

pub fn decision_history_location_with_provider( &self, key: &DecisionHistoryKey, provider: &dyn DecisionArchiveProvider, ) -> Result<DecisionHistoryLocation, CanwuError>

Source

pub fn random_draws(&self) -> &[RandomDrawRecord]

Source

pub fn boundary_head_hash(&self) -> Option<&str>

Source

pub const fn schema(&self) -> &SchemaRegistry

Source

pub fn plugin_descriptors(&self) -> impl Iterator<Item = &PluginDescriptor>

Source

pub fn replay_journal(&self) -> ReplayJournal

Examples found in repository?
examples/decision_ticket.rs (line 181)
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
Hide additional examples
examples/governance_transition.rs (line 577)
549fn main() -> Result<(), CanwuError> {
550    let central = CentralPlugin;
551    let treasury = TreasuryPlugin;
552    let county = CountyPlugin;
553    let plugins: [&dyn SimulationPlugin; 3] = [&central, &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}
Source

pub fn outbox_entries(&self) -> Result<Vec<OutboxEntry>, CanwuError>

Source

pub fn evidence_cursor(&self) -> Result<EvidenceCursor, CanwuError>

Source

pub fn checkpoint(&self) -> Result<SimulationCheckpoint, CanwuError>

Source

pub fn archive_reachability_manifest( &self, retained_checkpoints: &[SimulationCheckpoint], page_retention: &StatePageRetentionLedger, decision_provider: &dyn DecisionArchiveProvider, plugin_provider: &dyn PluginArchiveObjectProvider, ) -> Result<ArchiveReachabilityManifest, CanwuError>

Source

pub fn journal_segment_since( &self, start: EvidenceCursor, ) -> Result<EvidenceJournalSegment, CanwuError>

Source

pub fn checkpoint_journal(&self) -> Result<CheckpointJournal, CanwuError>

Source

pub fn checkpoint_journal_json(&self) -> Result<String, CanwuError>

Source

pub fn register_plugin<P: SimulationPlugin + ?Sized>( &mut self, plugin: &P, ) -> Result<(), CanwuError>

Examples found in repository?
examples/phased_boundary.rs (line 151)
149fn main() -> Result<(), CanwuError> {
150    let mut canwu = Canwu::demo(35)?;
151    canwu.register_plugin(&SupplyPlugin)?;
152    canwu.register_plugin(&DemandPlugin)?;
153
154    let receipt = canwu
155        .settle_boundary(BoundaryRequest::at(canwu.time()).with_cadence(SystemCadence::Daily))?;
156    assert_eq!(receipt.allocations[0].granted, 6);
157    assert_eq!(canwu.boundaries()[0].emissions.len(), 2);
158    Ok(())
159}
More examples
Hide additional examples
examples/plugin.rs (line 91)
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}
examples/uncertainty_resolution.rs (line 150)
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}
Source

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.

Source

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.

Source

pub fn submit( &mut self, command: CommandEnvelope, ) -> Result<CommandReceipt, CanwuError>

Examples found in repository?
examples/plugin.rs (lines 92-99)
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}
Source

pub fn process_command( &mut self, request: CommandRequest, ) -> Result<CommandOutcome, CanwuError>

Source

pub fn enqueue_command( &mut self, due_at: SimTime, priority: i32, request: CommandRequest, ) -> Result<IngressReceipt, CanwuError>

Examples found in repository?
examples/continuous_game_loop.rs (line 281)
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    }
Source

pub fn enqueue_plugin_ingress( &mut self, request: PluginIngressRequest, ) -> Result<IngressReceipt, CanwuError>

Examples found in repository?
examples/governance_transition.rs (lines 557-562)
549fn main() -> Result<(), CanwuError> {
550    let central = CentralPlugin;
551    let treasury = TreasuryPlugin;
552    let county = CountyPlugin;
553    let plugins: [&dyn SimulationPlugin; 3] = [&central, &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}
Source

pub fn enqueue_permitted_plugin_ingress( &mut self, request: PluginIngressRequest, permit: &PluginIngressPermit, ) -> Result<IngressReceipt, CanwuError>

Source

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.

Source

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.

Source

pub fn prepare_decision( &self, decision_request_id: DecisionRequestId, command_request_id: Option<CommandRequestId>, ticket_id: DecisionTicketId, policy: &dyn DecisionPolicy, ) -> Result<DecisionEvaluation, CanwuError>

Source

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>

Source

pub fn enqueue_decision( &mut self, due_at: SimTime, priority: i32, request: DecisionIngressRequest, ) -> Result<IngressReceipt, CanwuError>

Examples found in repository?
examples/uncertainty_resolution.rs (lines 104-112)
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
Hide additional examples
examples/decision_ticket.rs (lines 40-48)
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}
Source

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?
examples/decision_ticket.rs (lines 148-155)
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
Hide additional examples
examples/uncertainty_resolution.rs (lines 217-224)
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}
Source

pub fn schedule_calendar_boundary( &mut self, due_at: SimTime, cadences: Vec<SystemCadence>, ) -> Result<IngressReceipt, CanwuError>

Examples found in repository?
examples/uncertainty_resolution.rs (line 164)
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}
Source

pub fn advance_canonical( &mut self, duration: SimDuration, ) -> Result<Vec<BoundaryReceipt>, CanwuError>

Examples found in repository?
examples/continuous_game_loop.rs (line 233)
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    }
Source

pub fn step_canonical(&mut self) -> Result<Option<BoundaryReceipt>, CanwuError>

Examples found in repository?
examples/uncertainty_resolution.rs (line 165)
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
Hide additional examples
examples/decision_ticket.rs (line 84)
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}
Source

pub fn advance( &mut self, duration: SimDuration, ) -> Result<Vec<SimEvent>, CanwuError>

Source

pub fn settle_boundary( &mut self, request: BoundaryRequest, ) -> Result<BoundaryReceipt, CanwuError>

Examples found in repository?
examples/phased_boundary.rs (line 155)
149fn main() -> Result<(), CanwuError> {
150    let mut canwu = Canwu::demo(35)?;
151    canwu.register_plugin(&SupplyPlugin)?;
152    canwu.register_plugin(&DemandPlugin)?;
153
154    let receipt = canwu
155        .settle_boundary(BoundaryRequest::at(canwu.time()).with_cadence(SystemCadence::Daily))?;
156    assert_eq!(receipt.allocations[0].granted, 6);
157    assert_eq!(canwu.boundaries()[0].emissions.len(), 2);
158    Ok(())
159}
More examples
Hide additional examples
examples/governance_transition.rs (line 564)
549fn main() -> Result<(), CanwuError> {
550    let central = CentralPlugin;
551    let treasury = TreasuryPlugin;
552    let county = CountyPlugin;
553    let plugins: [&dyn SimulationPlugin; 3] = [&central, &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}
examples/uncertainty_resolution.rs (line 144)
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}
Source

pub fn wait( &mut self, duration: SimDuration, ) -> Result<Vec<SimEvent>, CanwuError>

Source

pub fn step(&mut self) -> Result<Vec<SimEvent>, CanwuError>

Source

pub fn snapshot(&self) -> SimulationSnapshot

Examples found in repository?
examples/decision_ticket.rs (line 164)
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
Hide additional examples
examples/governance_transition.rs (line 578)
549fn main() -> Result<(), CanwuError> {
550    let central = CentralPlugin;
551    let treasury = TreasuryPlugin;
552    let county = CountyPlugin;
553    let plugins: [&dyn SimulationPlugin; 3] = [&central, &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}
Source

pub fn snapshot_json(&self) -> Result<String, CanwuError>

Examples found in repository?
examples/governance_transition.rs (line 575)
549fn main() -> Result<(), CanwuError> {
550    let central = CentralPlugin;
551    let treasury = TreasuryPlugin;
552    let county = CountyPlugin;
553    let plugins: [&dyn SimulationPlugin; 3] = [&central, &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}
Source

pub fn from_snapshot_json(json: &str) -> Result<Self, CanwuError>

Examples found in repository?
examples/decision_ticket.rs (line 172)
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}
Source

pub fn from_snapshot_json_with_plugins( json: &str, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>

Examples found in repository?
examples/governance_transition.rs (line 576)
549fn main() -> Result<(), CanwuError> {
550    let central = CentralPlugin;
551    let treasury = TreasuryPlugin;
552    let county = CountyPlugin;
553    let plugins: [&dyn SimulationPlugin; 3] = [&central, &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}
Source

pub fn from_checkpoint_and_journal( checkpoint: SimulationCheckpoint, segments: Vec<EvidenceJournalSegment>, ) -> Result<Self, CanwuError>

Source

pub fn from_checkpoint_journal( bundle: CheckpointJournal, ) -> Result<Self, CanwuError>

Source

pub fn from_checkpoint_journal_with_plugins( bundle: CheckpointJournal, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>

Source

pub fn from_checkpoint_journal_json(json: &str) -> Result<Self, CanwuError>

Source

pub fn from_checkpoint_journal_json_with_plugins( json: &str, plugins: &[&dyn SimulationPlugin], ) -> Result<Self, CanwuError>

Source

pub fn replay_from_journal( plugins: &[&dyn SimulationPlugin], journal: &ReplayJournal, ) -> Result<Self, CanwuError>

Examples found in repository?
examples/decision_ticket.rs (line 181)
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
Hide additional examples
examples/governance_transition.rs (line 577)
549fn main() -> Result<(), CanwuError> {
550    let central = CentralPlugin;
551    let treasury = TreasuryPlugin;
552    let county = CountyPlugin;
553    let plugins: [&dyn SimulationPlugin; 3] = [&central, &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}
Source

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.

Source

pub fn replay_from_journal_json( plugins: &[&dyn SimulationPlugin], json: &str, ) -> Result<Self, CanwuError>

Source

pub fn fork(&self) -> Self

Source

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.

Source

pub fn viewer(&self) -> Result<CanwuViewer<'_>, CanwuError>

Creates the restricted viewer dictated entirely by the persisted run policy and seat binding.

Source

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.

Source

pub fn viewer_context( &self, actor: PersonId, ) -> Result<ViewerContext, CanwuError>

Source

pub fn explain(&self, request: &ExplanationRequest) -> Explanation

Trait Implementations§

Auto Trait Implementations§

§

impl !RefUnwindSafe for Canwu

§

impl !Send for Canwu

§

impl !Sync for Canwu

§

impl !UnwindSafe for Canwu

§

impl Freeze for Canwu

§

impl Unpin for Canwu

§

impl UnsafeUnpin for Canwu

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.