Run periodic boundary service on year rollover

This commit is contained in:
Jan Petykiewicz 2026-04-18 06:07:44 -07:00
commit ef2c317b6b
4 changed files with 97 additions and 8 deletions

View file

@ -122,10 +122,15 @@ pub fn execute_step_command(
let mut boundary_events = Vec::new();
let mut service_events = Vec::new();
let steps_executed = match command {
StepCommand::AdvanceTo { calendar } => {
advance_to_target_calendar_point(state, *calendar, &mut boundary_events)?
StepCommand::AdvanceTo { calendar } => advance_to_target_calendar_point(
state,
*calendar,
&mut boundary_events,
&mut service_events,
)?,
StepCommand::StepCount { steps } => {
step_count(state, *steps, &mut boundary_events, &mut service_events)?
}
StepCommand::StepCount { steps } => step_count(state, *steps, &mut boundary_events),
StepCommand::ServiceTriggerKind { trigger_kind } => {
service_trigger_kind(state, *trigger_kind, &mut service_events)?;
0
@ -151,6 +156,7 @@ fn advance_to_target_calendar_point(
state: &mut RuntimeState,
target: crate::CalendarPoint,
boundary_events: &mut Vec<BoundaryEvent>,
service_events: &mut Vec<ServiceEvent>,
) -> Result<u64, String> {
target.validate()?;
if target < state.calendar {
@ -162,7 +168,7 @@ fn advance_to_target_calendar_point(
let mut steps = 0_u64;
while state.calendar < target {
step_once(state, boundary_events);
step_once(state, boundary_events, service_events)?;
steps += 1;
}
Ok(steps)
@ -172,14 +178,19 @@ fn step_count(
state: &mut RuntimeState,
steps: u32,
boundary_events: &mut Vec<BoundaryEvent>,
) -> u64 {
service_events: &mut Vec<ServiceEvent>,
) -> Result<u64, String> {
for _ in 0..steps {
step_once(state, boundary_events);
step_once(state, boundary_events, service_events)?;
}
steps.into()
Ok(steps.into())
}
fn step_once(state: &mut RuntimeState, boundary_events: &mut Vec<BoundaryEvent>) {
fn step_once(
state: &mut RuntimeState,
boundary_events: &mut Vec<BoundaryEvent>,
service_events: &mut Vec<ServiceEvent>,
) -> Result<(), String> {
let boundary = state.calendar.step_forward();
if boundary != BoundaryEventKind::Tick {
boundary_events.push(BoundaryEvent {
@ -187,6 +198,10 @@ fn step_once(state: &mut RuntimeState, boundary_events: &mut Vec<BoundaryEvent>)
calendar: state.calendar,
});
}
if boundary == BoundaryEventKind::YearRollover {
service_periodic_boundary(state, service_events)?;
}
Ok(())
}
fn boundary_kind_label(boundary: BoundaryEventKind) -> &'static str {
@ -2909,6 +2924,70 @@ mod tests {
assert_eq!(state.calendar.tick_slot, 5);
}
#[test]
fn year_rollover_step_runs_periodic_boundary_services() {
let mut state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: crate::MONTH_SLOTS_PER_YEAR - 1,
phase_slot: crate::PHASE_SLOTS_PER_MONTH - 1,
tick_slot: crate::TICKS_PER_PHASE - 1,
},
event_runtime_records: vec![RuntimeEventRecord {
record_id: 77,
trigger_kind: 1,
active: true,
service_count: 0,
marks_collection_dirty: false,
one_shot: false,
has_fired: false,
conditions: Vec::new(),
effects: vec![RuntimeEffect::SetWorldFlag {
key: "world.periodic_rollover_service_fired".to_string(),
value: true,
}],
}],
..state()
};
let result = execute_step_command(&mut state, &StepCommand::StepCount { steps: 1 })
.expect("year rollover step should run periodic boundary services");
assert_eq!(result.steps_executed, 1);
assert_eq!(
result.boundary_events,
vec![BoundaryEvent {
kind: "year_rollover".to_string(),
calendar: CalendarPoint {
year: 1831,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
}]
);
assert_eq!(state.service_state.periodic_boundary_calls, 1);
assert_eq!(state.service_state.total_event_record_services, 1);
assert_eq!(
state
.world_flags
.get("world.periodic_rollover_service_fired"),
Some(&true)
);
assert!(
result
.service_events
.iter()
.any(|event| event.trigger_kind == Some(1))
);
assert!(
result
.service_events
.iter()
.any(|event| event.kind == "annual_finance_policy")
);
}
#[test]
fn rejects_backward_target() {
let mut state = state();