rrt/crates/rrt-runtime/src/summary.rs

1202 lines
52 KiB
Rust

use serde::{Deserialize, Serialize};
use crate::{CalendarPoint, RuntimeState};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeSummary {
pub calendar: CalendarPoint,
pub calendar_projection_source: Option<String>,
pub calendar_projection_is_placeholder: bool,
pub world_flag_count: usize,
pub world_restore_selected_year_profile_lane: Option<u8>,
pub world_restore_campaign_scenario_enabled: Option<bool>,
pub world_restore_sandbox_enabled: Option<bool>,
pub world_restore_seed_tuple_written_from_raw_lane: Option<bool>,
pub world_restore_absolute_counter_requires_shell_context: Option<bool>,
pub world_restore_absolute_counter_reconstructible_from_save: Option<bool>,
pub world_restore_disable_cargo_economy_special_condition_slot: Option<u8>,
pub world_restore_disable_cargo_economy_special_condition_reconstructible_from_save:
Option<bool>,
pub world_restore_disable_cargo_economy_special_condition_write_side_grounded: Option<bool>,
pub world_restore_disable_cargo_economy_special_condition_enabled: Option<bool>,
pub world_restore_use_bio_accelerator_cars_enabled: Option<bool>,
pub world_restore_use_wartime_cargos_enabled: Option<bool>,
pub world_restore_disable_train_crashes_enabled: Option<bool>,
pub world_restore_disable_train_crashes_and_breakdowns_enabled: Option<bool>,
pub world_restore_ai_ignore_territories_at_startup_enabled: Option<bool>,
pub world_restore_limited_track_building_amount: Option<i32>,
pub world_restore_economic_status_code: Option<i32>,
pub world_restore_absolute_counter_restore_kind: Option<String>,
pub world_restore_absolute_counter_adjustment_context: Option<String>,
pub metadata_count: usize,
pub company_count: usize,
pub active_company_count: usize,
pub player_count: usize,
pub train_count: usize,
pub active_train_count: usize,
pub retired_train_count: usize,
pub locomotive_catalog_count: usize,
pub territory_count: usize,
pub company_territory_track_count: usize,
pub packed_event_collection_present: bool,
pub packed_event_record_count: usize,
pub packed_event_decoded_record_count: usize,
pub packed_event_imported_runtime_record_count: usize,
pub packed_event_parity_only_record_count: usize,
pub packed_event_unsupported_record_count: usize,
pub packed_event_blocked_missing_company_context_count: usize,
pub packed_event_blocked_missing_selection_context_count: usize,
pub packed_event_blocked_missing_company_role_context_count: usize,
pub packed_event_blocked_missing_player_context_count: usize,
pub packed_event_blocked_missing_player_selection_context_count: usize,
pub packed_event_blocked_missing_player_role_context_count: usize,
pub packed_event_blocked_missing_condition_context_count: usize,
pub packed_event_blocked_missing_player_condition_context_count: usize,
pub packed_event_blocked_company_condition_scope_disabled_count: usize,
pub packed_event_blocked_player_condition_scope_count: usize,
pub packed_event_blocked_territory_condition_scope_count: usize,
pub packed_event_blocked_missing_territory_context_count: usize,
pub packed_event_blocked_named_territory_binding_count: usize,
pub packed_event_blocked_unmapped_ordinary_condition_count: usize,
pub packed_event_blocked_unmapped_world_condition_count: usize,
pub packed_event_blocked_missing_compact_control_count: usize,
pub packed_event_blocked_unmapped_real_descriptor_count: usize,
pub packed_event_blocked_unmapped_world_descriptor_count: usize,
pub packed_event_blocked_territory_access_variant_count: usize,
pub packed_event_blocked_territory_access_scope_count: usize,
pub packed_event_blocked_missing_train_context_count: usize,
pub packed_event_blocked_missing_train_territory_context_count: usize,
pub packed_event_blocked_missing_locomotive_catalog_context_count: usize,
pub packed_event_blocked_confiscation_variant_count: usize,
pub packed_event_blocked_retire_train_variant_count: usize,
pub packed_event_blocked_retire_train_scope_count: usize,
pub packed_event_blocked_structural_only_count: usize,
pub event_runtime_record_count: usize,
pub candidate_availability_count: usize,
pub zero_candidate_availability_count: usize,
pub named_locomotive_availability_count: usize,
pub zero_named_locomotive_availability_count: usize,
pub named_locomotive_cost_count: usize,
pub special_condition_count: usize,
pub enabled_special_condition_count: usize,
pub save_profile_kind: Option<String>,
pub save_profile_family: Option<String>,
pub save_profile_map_path: Option<String>,
pub save_profile_display_name: Option<String>,
pub save_profile_selected_year_profile_lane: Option<u8>,
pub save_profile_sandbox_enabled: Option<bool>,
pub save_profile_campaign_scenario_enabled: Option<bool>,
pub save_profile_staged_profile_copy_on_restore: Option<bool>,
pub total_event_record_service_count: u64,
pub periodic_boundary_call_count: u64,
pub total_trigger_dispatch_count: u64,
pub dirty_rerun_count: u64,
pub total_company_cash: i64,
}
impl RuntimeSummary {
pub fn from_state(state: &RuntimeState) -> Self {
Self {
calendar: state.calendar,
calendar_projection_source: state.metadata.get("save_slice.calendar_source").cloned(),
calendar_projection_is_placeholder: state
.metadata
.get("save_slice.calendar_source")
.is_some_and(|value| value == "default-1830-placeholder"),
world_flag_count: state.world_flags.len(),
world_restore_selected_year_profile_lane: state
.world_restore
.selected_year_profile_lane,
world_restore_campaign_scenario_enabled: state.world_restore.campaign_scenario_enabled,
world_restore_sandbox_enabled: state.world_restore.sandbox_enabled,
world_restore_seed_tuple_written_from_raw_lane: state
.world_restore
.seed_tuple_written_from_raw_lane,
world_restore_absolute_counter_requires_shell_context: state
.world_restore
.absolute_counter_requires_shell_context,
world_restore_absolute_counter_reconstructible_from_save: state
.world_restore
.absolute_counter_reconstructible_from_save,
world_restore_disable_cargo_economy_special_condition_slot: state
.world_restore
.disable_cargo_economy_special_condition_slot,
world_restore_disable_cargo_economy_special_condition_reconstructible_from_save: state
.world_restore
.disable_cargo_economy_special_condition_reconstructible_from_save,
world_restore_disable_cargo_economy_special_condition_write_side_grounded: state
.world_restore
.disable_cargo_economy_special_condition_write_side_grounded,
world_restore_disable_cargo_economy_special_condition_enabled: state
.world_restore
.disable_cargo_economy_special_condition_enabled,
world_restore_use_bio_accelerator_cars_enabled: state
.world_restore
.use_bio_accelerator_cars_enabled,
world_restore_use_wartime_cargos_enabled: state
.world_restore
.use_wartime_cargos_enabled,
world_restore_disable_train_crashes_enabled: state
.world_restore
.disable_train_crashes_enabled,
world_restore_disable_train_crashes_and_breakdowns_enabled: state
.world_restore
.disable_train_crashes_and_breakdowns_enabled,
world_restore_ai_ignore_territories_at_startup_enabled: state
.world_restore
.ai_ignore_territories_at_startup_enabled,
world_restore_limited_track_building_amount: state
.world_restore
.limited_track_building_amount,
world_restore_economic_status_code: state.world_restore.economic_status_code,
world_restore_absolute_counter_restore_kind: state
.world_restore
.absolute_counter_restore_kind
.clone(),
world_restore_absolute_counter_adjustment_context: state
.world_restore
.absolute_counter_adjustment_context
.clone(),
metadata_count: state.metadata.len(),
company_count: state.companies.len(),
active_company_count: state
.companies
.iter()
.filter(|company| company.active)
.count(),
player_count: state.players.len(),
train_count: state.trains.len(),
active_train_count: state.trains.iter().filter(|train| train.active).count(),
retired_train_count: state.trains.iter().filter(|train| train.retired).count(),
locomotive_catalog_count: state.locomotive_catalog.len(),
territory_count: state.territories.len(),
company_territory_track_count: state.company_territory_track_piece_counts.len(),
packed_event_collection_present: state.packed_event_collection.is_some(),
packed_event_record_count: state
.packed_event_collection
.as_ref()
.map(|summary| summary.live_record_count)
.unwrap_or(0),
packed_event_decoded_record_count: state
.packed_event_collection
.as_ref()
.map(|summary| summary.decoded_record_count)
.unwrap_or(0),
packed_event_imported_runtime_record_count: state
.packed_event_collection
.as_ref()
.map(|summary| summary.imported_runtime_record_count)
.unwrap_or(0),
packed_event_parity_only_record_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| record.decode_status == "parity_only")
.count()
})
.unwrap_or(0),
packed_event_unsupported_record_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| record.decode_status == "unsupported_framing")
.count()
})
.unwrap_or(0),
packed_event_blocked_missing_company_context_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_missing_company_context")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_missing_selection_context_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_missing_selection_context")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_missing_company_role_context_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_missing_company_role_context")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_missing_player_context_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_missing_player_context")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_missing_player_selection_context_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_missing_player_selection_context")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_missing_player_role_context_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_missing_player_role_context")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_missing_condition_context_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_missing_condition_context")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_missing_player_condition_context_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_missing_player_condition_context")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_company_condition_scope_disabled_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_company_condition_scope_disabled")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_player_condition_scope_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_player_condition_scope")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_territory_condition_scope_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_territory_condition_scope")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_missing_territory_context_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_missing_territory_context")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_named_territory_binding_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_named_territory_binding")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_unmapped_ordinary_condition_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_unmapped_ordinary_condition")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_unmapped_world_condition_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_unmapped_world_condition")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_missing_compact_control_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_missing_compact_control")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_unmapped_real_descriptor_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_unmapped_real_descriptor")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_unmapped_world_descriptor_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_unmapped_world_descriptor")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_territory_access_variant_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_territory_access_variant")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_territory_access_scope_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_territory_access_scope")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_missing_train_context_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_missing_train_context")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_missing_train_territory_context_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_missing_train_territory_context")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_missing_locomotive_catalog_context_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_missing_locomotive_catalog_context")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_confiscation_variant_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref() == Some("blocked_confiscation_variant")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_retire_train_variant_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref() == Some("blocked_retire_train_variant")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_retire_train_scope_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref() == Some("blocked_retire_train_scope")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_structural_only_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref() == Some("blocked_structural_only")
})
.count()
})
.unwrap_or(0),
event_runtime_record_count: state.event_runtime_records.len(),
candidate_availability_count: state.candidate_availability.len(),
zero_candidate_availability_count: state
.candidate_availability
.values()
.filter(|value| **value == 0)
.count(),
named_locomotive_availability_count: state.named_locomotive_availability.len(),
zero_named_locomotive_availability_count: state
.named_locomotive_availability
.values()
.filter(|value| **value == 0)
.count(),
named_locomotive_cost_count: state.named_locomotive_cost.len(),
special_condition_count: state.special_conditions.len(),
enabled_special_condition_count: state
.special_conditions
.values()
.filter(|value| **value != 0)
.count(),
save_profile_kind: state.save_profile.profile_kind.clone(),
save_profile_family: state.save_profile.profile_family.clone(),
save_profile_map_path: state.save_profile.map_path.clone(),
save_profile_display_name: state.save_profile.display_name.clone(),
save_profile_selected_year_profile_lane: state.save_profile.selected_year_profile_lane,
save_profile_sandbox_enabled: state.save_profile.sandbox_enabled,
save_profile_campaign_scenario_enabled: state.save_profile.campaign_scenario_enabled,
save_profile_staged_profile_copy_on_restore: state
.save_profile
.staged_profile_copy_on_restore,
total_event_record_service_count: state.service_state.total_event_record_services,
periodic_boundary_call_count: state.service_state.periodic_boundary_calls,
total_trigger_dispatch_count: state
.service_state
.trigger_dispatch_counts
.values()
.sum(),
dirty_rerun_count: state.service_state.dirty_rerun_count,
total_company_cash: state
.companies
.iter()
.map(|company| company.current_cash)
.sum(),
}
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use crate::{
CalendarPoint, RuntimeCompany, RuntimeCompanyControllerKind,
RuntimePackedEventCollectionSummary, RuntimePackedEventRecordSummary,
RuntimeSaveProfileState, RuntimeServiceState, RuntimeState, RuntimeTrackPieceCounts,
RuntimeWorldRestoreState,
};
use super::RuntimeSummary;
#[test]
fn counts_structural_only_and_missing_context_frontiers() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: Vec::new(),
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
trains: Vec::new(),
locomotive_catalog: vec![
crate::RuntimeLocomotiveCatalogEntry {
locomotive_id: 10,
name: "Locomotive 10".to_string(),
},
crate::RuntimeLocomotiveCatalogEntry {
locomotive_id: 112,
name: "Locomotive 112".to_string(),
},
],
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
mechanism_family: "classic-save-rehydrate-v1".to_string(),
mechanism_confidence: "grounded".to_string(),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
packed_state_version: 0x3e9,
packed_state_version_hex: "0x000003e9".to_string(),
live_id_bound: 11,
live_record_count: 5,
live_entry_ids: vec![3, 7, 9, 10, 11],
decoded_record_count: 5,
imported_runtime_record_count: 0,
records: vec![
RuntimePackedEventRecordSummary {
record_index: 0,
live_entry_id: 3,
payload_offset: Some(0x7202),
payload_len: Some(96),
decode_status: "parity_only".to_string(),
payload_family: "real_packed_v1".to_string(),
trigger_kind: None,
active: None,
marks_collection_dirty: None,
one_shot: None,
compact_control: None,
text_bands: Vec::new(),
standalone_condition_row_count: 0,
standalone_condition_rows: Vec::new(),
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![0, 0, 0, 0],
grouped_effect_rows: Vec::new(),
grouped_company_targets: Vec::new(),
decoded_conditions: Vec::new(),
decoded_actions: Vec::new(),
executable_import_ready: false,
import_outcome: Some("blocked_missing_compact_control".to_string()),
notes: Vec::new(),
},
RuntimePackedEventRecordSummary {
record_index: 1,
live_entry_id: 7,
payload_offset: Some(0x7262),
payload_len: Some(48),
decode_status: "parity_only".to_string(),
payload_family: "synthetic_harness".to_string(),
trigger_kind: Some(7),
active: Some(true),
marks_collection_dirty: Some(false),
one_shot: Some(false),
compact_control: None,
text_bands: Vec::new(),
standalone_condition_row_count: 0,
standalone_condition_rows: Vec::new(),
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![0, 0, 0, 0],
grouped_effect_rows: Vec::new(),
grouped_company_targets: Vec::new(),
decoded_conditions: Vec::new(),
decoded_actions: Vec::new(),
executable_import_ready: false,
import_outcome: Some("blocked_missing_company_context".to_string()),
notes: Vec::new(),
},
RuntimePackedEventRecordSummary {
record_index: 2,
live_entry_id: 9,
payload_offset: Some(0x7292),
payload_len: Some(48),
decode_status: "parity_only".to_string(),
payload_family: "real_packed_v1".to_string(),
trigger_kind: Some(7),
active: None,
marks_collection_dirty: None,
one_shot: None,
compact_control: None,
text_bands: Vec::new(),
standalone_condition_row_count: 0,
standalone_condition_rows: Vec::new(),
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![0, 0, 0, 0],
grouped_effect_rows: Vec::new(),
grouped_company_targets: Vec::new(),
decoded_conditions: Vec::new(),
decoded_actions: Vec::new(),
executable_import_ready: false,
import_outcome: Some(
"blocked_company_condition_scope_disabled".to_string(),
),
notes: Vec::new(),
},
RuntimePackedEventRecordSummary {
record_index: 3,
live_entry_id: 10,
payload_offset: Some(0x72c2),
payload_len: Some(48),
decode_status: "parity_only".to_string(),
payload_family: "real_packed_v1".to_string(),
trigger_kind: Some(7),
active: None,
marks_collection_dirty: None,
one_shot: None,
compact_control: None,
text_bands: Vec::new(),
standalone_condition_row_count: 0,
standalone_condition_rows: Vec::new(),
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![0, 0, 0, 0],
grouped_effect_rows: Vec::new(),
grouped_company_targets: Vec::new(),
decoded_conditions: Vec::new(),
decoded_actions: Vec::new(),
executable_import_ready: false,
import_outcome: Some("blocked_player_condition_scope".to_string()),
notes: Vec::new(),
},
RuntimePackedEventRecordSummary {
record_index: 4,
live_entry_id: 11,
payload_offset: Some(0x72f2),
payload_len: Some(48),
decode_status: "parity_only".to_string(),
payload_family: "real_packed_v1".to_string(),
trigger_kind: Some(7),
active: None,
marks_collection_dirty: None,
one_shot: None,
compact_control: None,
text_bands: Vec::new(),
standalone_condition_row_count: 0,
standalone_condition_rows: Vec::new(),
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![0, 0, 0, 0],
grouped_effect_rows: Vec::new(),
grouped_company_targets: Vec::new(),
decoded_conditions: Vec::new(),
decoded_actions: Vec::new(),
executable_import_ready: false,
import_outcome: Some("blocked_territory_condition_scope".to_string()),
notes: Vec::new(),
},
],
}),
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
let summary = RuntimeSummary::from_state(&state);
assert_eq!(
summary.packed_event_blocked_missing_compact_control_count,
1
);
assert_eq!(
summary.packed_event_blocked_unmapped_real_descriptor_count,
0
);
assert_eq!(summary.packed_event_blocked_structural_only_count, 0);
assert_eq!(
summary.packed_event_blocked_missing_company_context_count,
1
);
assert_eq!(
summary.packed_event_blocked_missing_selection_context_count,
0
);
assert_eq!(
summary.packed_event_blocked_missing_company_role_context_count,
0
);
assert_eq!(
summary.packed_event_blocked_missing_condition_context_count,
0
);
assert_eq!(
summary.packed_event_blocked_company_condition_scope_disabled_count,
1
);
assert_eq!(summary.packed_event_blocked_player_condition_scope_count, 1);
assert_eq!(
summary.packed_event_blocked_territory_condition_scope_count,
1
);
}
#[test]
fn counts_active_companies_separately_from_total_companies() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![
RuntimeCompany {
company_id: 1,
current_cash: 10,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
controller_kind: RuntimeCompanyControllerKind::Human,
},
RuntimeCompany {
company_id: 2,
current_cash: 20,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: false,
available_track_laying_capacity: Some(7),
controller_kind: RuntimeCompanyControllerKind::Ai,
},
],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
trains: Vec::new(),
locomotive_catalog: vec![
crate::RuntimeLocomotiveCatalogEntry {
locomotive_id: 10,
name: "Locomotive 10".to_string(),
},
crate::RuntimeLocomotiveCatalogEntry {
locomotive_id: 112,
name: "Locomotive 112".to_string(),
},
],
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
let summary = RuntimeSummary::from_state(&state);
assert_eq!(summary.company_count, 2);
assert_eq!(summary.active_company_count, 1);
}
#[test]
fn counts_named_locomotive_availability_entries_and_zero_values() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: Vec::new(),
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
trains: Vec::new(),
locomotive_catalog: vec![
crate::RuntimeLocomotiveCatalogEntry {
locomotive_id: 10,
name: "Locomotive 10".to_string(),
},
crate::RuntimeLocomotiveCatalogEntry {
locomotive_id: 112,
name: "Locomotive 112".to_string(),
},
],
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::from([
("Big Boy".to_string(), 0),
("GP7".to_string(), 1),
("Mikado".to_string(), 0),
]),
named_locomotive_cost: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
let summary = RuntimeSummary::from_state(&state);
assert_eq!(summary.locomotive_catalog_count, 2);
assert_eq!(summary.named_locomotive_availability_count, 3);
assert_eq!(summary.zero_named_locomotive_availability_count, 2);
assert_eq!(summary.named_locomotive_cost_count, 0);
}
#[test]
fn counts_named_locomotive_cost_entries() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: Vec::new(),
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::from([
("Big Boy".to_string(), 250000),
("GP7".to_string(), 175000),
]),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
let summary = RuntimeSummary::from_state(&state);
assert_eq!(summary.named_locomotive_cost_count, 2);
}
#[test]
fn counts_world_frontier_buckets_separately() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: Vec::new(),
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
mechanism_family: "classic-save-rehydrate-v1".to_string(),
mechanism_confidence: "grounded".to_string(),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
packed_state_version: 0x3e9,
packed_state_version_hex: "0x000003e9".to_string(),
live_id_bound: 2,
live_record_count: 2,
live_entry_ids: vec![21, 22],
decoded_record_count: 2,
imported_runtime_record_count: 0,
records: vec![
RuntimePackedEventRecordSummary {
record_index: 0,
live_entry_id: 21,
payload_offset: Some(0x7202),
payload_len: Some(96),
decode_status: "parity_only".to_string(),
payload_family: "real_packed_v1".to_string(),
trigger_kind: Some(7),
active: None,
marks_collection_dirty: None,
one_shot: None,
compact_control: None,
text_bands: Vec::new(),
standalone_condition_row_count: 0,
standalone_condition_rows: Vec::new(),
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![1, 0, 0, 0],
grouped_effect_rows: Vec::new(),
grouped_company_targets: Vec::new(),
decoded_conditions: Vec::new(),
decoded_actions: Vec::new(),
executable_import_ready: false,
import_outcome: Some("blocked_unmapped_world_descriptor".to_string()),
notes: Vec::new(),
},
RuntimePackedEventRecordSummary {
record_index: 1,
live_entry_id: 22,
payload_offset: Some(0x7242),
payload_len: Some(96),
decode_status: "parity_only".to_string(),
payload_family: "real_packed_v1".to_string(),
trigger_kind: Some(7),
active: None,
marks_collection_dirty: None,
one_shot: None,
compact_control: None,
text_bands: Vec::new(),
standalone_condition_row_count: 1,
standalone_condition_rows: Vec::new(),
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![0, 0, 0, 0],
grouped_effect_rows: Vec::new(),
grouped_company_targets: Vec::new(),
decoded_conditions: Vec::new(),
decoded_actions: Vec::new(),
executable_import_ready: false,
import_outcome: Some("blocked_unmapped_world_condition".to_string()),
notes: Vec::new(),
},
],
}),
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
let summary = RuntimeSummary::from_state(&state);
assert_eq!(
summary.packed_event_blocked_unmapped_world_descriptor_count,
1
);
assert_eq!(
summary.packed_event_blocked_unmapped_world_condition_count,
1
);
}
#[test]
fn counts_missing_locomotive_catalog_context_frontier() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: Vec::new(),
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
mechanism_family: "classic-save-rehydrate-v1".to_string(),
mechanism_confidence: "grounded".to_string(),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
packed_state_version: 0x3e9,
packed_state_version_hex: "0x000003e9".to_string(),
live_id_bound: 1,
live_record_count: 1,
live_entry_ids: vec![1],
decoded_record_count: 1,
imported_runtime_record_count: 0,
records: vec![RuntimePackedEventRecordSummary {
record_index: 0,
live_entry_id: 1,
payload_offset: Some(0x7202),
payload_len: Some(96),
decode_status: "parity_only".to_string(),
payload_family: "real_packed_v1".to_string(),
trigger_kind: Some(7),
active: None,
marks_collection_dirty: None,
one_shot: None,
compact_control: None,
text_bands: Vec::new(),
standalone_condition_row_count: 0,
standalone_condition_rows: Vec::new(),
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![1, 0, 0, 0],
grouped_effect_rows: Vec::new(),
grouped_company_targets: Vec::new(),
decoded_conditions: Vec::new(),
decoded_actions: Vec::new(),
executable_import_ready: false,
import_outcome: Some("blocked_missing_locomotive_catalog_context".to_string()),
notes: Vec::new(),
}],
}),
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
let summary = RuntimeSummary::from_state(&state);
assert_eq!(
summary.packed_event_blocked_missing_locomotive_catalog_context_count,
1
);
}
}