Implement whole-game packed event conditions
This commit is contained in:
parent
e9c8bfbb9c
commit
cc54a00e25
16 changed files with 1184 additions and 42 deletions
|
|
@ -118,10 +118,14 @@ pub struct ExpectedRuntimeSummary {
|
|||
#[serde(default)]
|
||||
pub packed_event_blocked_unmapped_ordinary_condition_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub packed_event_blocked_unmapped_world_condition_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub packed_event_blocked_missing_compact_control_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub packed_event_blocked_unmapped_real_descriptor_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub packed_event_blocked_unmapped_world_descriptor_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub packed_event_blocked_territory_access_variant_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub packed_event_blocked_territory_access_scope_count: Option<usize>,
|
||||
|
|
@ -601,6 +605,14 @@ impl ExpectedRuntimeSummary {
|
|||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.packed_event_blocked_unmapped_world_condition_count {
|
||||
if actual.packed_event_blocked_unmapped_world_condition_count != count {
|
||||
mismatches.push(format!(
|
||||
"packed_event_blocked_unmapped_world_condition_count mismatch: expected {count}, got {}",
|
||||
actual.packed_event_blocked_unmapped_world_condition_count
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.packed_event_blocked_missing_compact_control_count {
|
||||
if actual.packed_event_blocked_missing_compact_control_count != count {
|
||||
mismatches.push(format!(
|
||||
|
|
@ -617,6 +629,14 @@ impl ExpectedRuntimeSummary {
|
|||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.packed_event_blocked_unmapped_world_descriptor_count {
|
||||
if actual.packed_event_blocked_unmapped_world_descriptor_count != count {
|
||||
mismatches.push(format!(
|
||||
"packed_event_blocked_unmapped_world_descriptor_count mismatch: expected {count}, got {}",
|
||||
actual.packed_event_blocked_unmapped_world_descriptor_count
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.packed_event_blocked_territory_access_variant_count {
|
||||
if actual.packed_event_blocked_territory_access_variant_count != count {
|
||||
mismatches.push(format!(
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ enum ImportBlocker {
|
|||
MissingTerritoryContext,
|
||||
NamedTerritoryBinding,
|
||||
UnmappedOrdinaryCondition,
|
||||
UnmappedWorldCondition,
|
||||
MissingTrainContext,
|
||||
MissingTrainTerritoryContext,
|
||||
}
|
||||
|
|
@ -923,7 +924,11 @@ fn packed_record_condition_scope_import_blocker(
|
|||
.count();
|
||||
if ordinary_condition_row_count != 0 {
|
||||
if ordinary_condition_row_count != record.decoded_conditions.len() {
|
||||
return Some(ImportBlocker::UnmappedOrdinaryCondition);
|
||||
return Some(if record_has_world_state_condition_rows(record) {
|
||||
ImportBlocker::UnmappedWorldCondition
|
||||
} else {
|
||||
ImportBlocker::UnmappedOrdinaryCondition
|
||||
});
|
||||
}
|
||||
if (!company_context.has_territory_context)
|
||||
&& (record
|
||||
|
|
@ -1205,6 +1210,30 @@ fn lower_condition_targets_in_condition(
|
|||
comparator: *comparator,
|
||||
value: *value,
|
||||
},
|
||||
RuntimeCondition::SpecialConditionThreshold {
|
||||
label,
|
||||
comparator,
|
||||
value,
|
||||
} => RuntimeCondition::SpecialConditionThreshold {
|
||||
label: label.clone(),
|
||||
comparator: *comparator,
|
||||
value: *value,
|
||||
},
|
||||
RuntimeCondition::CandidateAvailabilityThreshold {
|
||||
name,
|
||||
comparator,
|
||||
value,
|
||||
} => RuntimeCondition::CandidateAvailabilityThreshold {
|
||||
name: name.clone(),
|
||||
comparator: *comparator,
|
||||
value: *value,
|
||||
},
|
||||
RuntimeCondition::EconomicStatusCodeThreshold { comparator, value } => {
|
||||
RuntimeCondition::EconomicStatusCodeThreshold {
|
||||
comparator: *comparator,
|
||||
value: *value,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1281,7 +1310,10 @@ fn condition_uses_condition_true_company(condition: &RuntimeCondition) -> bool {
|
|||
| RuntimeCondition::CompanyTerritoryNumericThreshold { target, .. } => {
|
||||
matches!(target, RuntimeCompanyTarget::ConditionTrueCompany)
|
||||
}
|
||||
RuntimeCondition::TerritoryNumericThreshold { .. } => false,
|
||||
RuntimeCondition::TerritoryNumericThreshold { .. }
|
||||
| RuntimeCondition::SpecialConditionThreshold { .. }
|
||||
| RuntimeCondition::CandidateAvailabilityThreshold { .. }
|
||||
| RuntimeCondition::EconomicStatusCodeThreshold { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1351,8 +1383,11 @@ fn smp_runtime_effect_to_runtime_effect(
|
|||
territory,
|
||||
value,
|
||||
} => {
|
||||
if !company_target_allowed_for_import(target, company_context, allow_condition_true_company)
|
||||
{
|
||||
if !company_target_allowed_for_import(
|
||||
target,
|
||||
company_context,
|
||||
allow_condition_true_company,
|
||||
) {
|
||||
Err(company_target_import_error_message(target, company_context))
|
||||
} else if territory_target_import_blocker(territory, company_context).is_some() {
|
||||
Err("packed effect requires territory runtime context".to_string())
|
||||
|
|
@ -1657,6 +1692,9 @@ fn company_target_import_error_message(
|
|||
Some(ImportBlocker::UnmappedOrdinaryCondition) => {
|
||||
"packed ordinary condition is not yet mapped".to_string()
|
||||
}
|
||||
Some(ImportBlocker::UnmappedWorldCondition) => {
|
||||
"packed whole-game condition is not yet mapped".to_string()
|
||||
}
|
||||
Some(ImportBlocker::MissingTrainContext) => {
|
||||
"packed effect requires runtime train context".to_string()
|
||||
}
|
||||
|
|
@ -1788,12 +1826,23 @@ fn determine_packed_event_import_outcome(
|
|||
{
|
||||
return "blocked_retire_train_variant".to_string();
|
||||
}
|
||||
if record
|
||||
.grouped_effect_rows
|
||||
.iter()
|
||||
.any(real_grouped_row_is_world_state_family)
|
||||
{
|
||||
return "blocked_unmapped_world_descriptor".to_string();
|
||||
}
|
||||
return if record
|
||||
.standalone_condition_rows
|
||||
.iter()
|
||||
.any(|row| row.raw_condition_id >= 0)
|
||||
{
|
||||
"blocked_unmapped_ordinary_condition".to_string()
|
||||
if record_has_world_state_condition_rows(record) {
|
||||
"blocked_unmapped_world_condition".to_string()
|
||||
} else {
|
||||
"blocked_unmapped_ordinary_condition".to_string()
|
||||
}
|
||||
} else {
|
||||
"blocked_unmapped_real_descriptor".to_string()
|
||||
};
|
||||
|
|
@ -1814,6 +1863,58 @@ fn determine_packed_event_import_outcome(
|
|||
"blocked_unsupported_decode".to_string()
|
||||
}
|
||||
|
||||
fn record_has_world_state_condition_rows(record: &SmpLoadedPackedEventRecordSummary) -> bool {
|
||||
record
|
||||
.decoded_conditions
|
||||
.iter()
|
||||
.any(runtime_condition_is_world_state)
|
||||
|| record
|
||||
.standalone_condition_rows
|
||||
.iter()
|
||||
.any(ordinary_condition_row_is_world_state_family)
|
||||
}
|
||||
|
||||
fn runtime_condition_is_world_state(condition: &RuntimeCondition) -> bool {
|
||||
matches!(
|
||||
condition,
|
||||
RuntimeCondition::SpecialConditionThreshold { .. }
|
||||
| RuntimeCondition::CandidateAvailabilityThreshold { .. }
|
||||
| RuntimeCondition::EconomicStatusCodeThreshold { .. }
|
||||
)
|
||||
}
|
||||
|
||||
fn ordinary_condition_row_is_world_state_family(
|
||||
row: &SmpLoadedPackedEventConditionRowSummary,
|
||||
) -> bool {
|
||||
row.metric.as_deref().is_some_and(|metric| {
|
||||
metric.contains("Special Condition")
|
||||
|| metric.contains("Candidate Availability")
|
||||
|| metric.contains("Economic Status")
|
||||
|| metric.contains("World Flag")
|
||||
}) || row
|
||||
.semantic_family
|
||||
.as_deref()
|
||||
.is_some_and(|family| family == "world_state_threshold" || family == "world_flag_equals")
|
||||
}
|
||||
|
||||
fn real_grouped_row_is_world_state_family(
|
||||
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
||||
) -> bool {
|
||||
row.target_mask_bits == Some(0x08)
|
||||
|| row.parameter_family.as_deref().is_some_and(|family| {
|
||||
family.starts_with("whole_game_")
|
||||
|| family.starts_with("special_condition")
|
||||
|| family.starts_with("candidate_availability")
|
||||
|| family.starts_with("world_flag")
|
||||
})
|
||||
|| row.descriptor_label.as_deref().is_some_and(|label| {
|
||||
label.contains("Special Condition")
|
||||
|| label.contains("Candidate Availability")
|
||||
|| label.contains("World Flag")
|
||||
|| label == "Economic Status"
|
||||
})
|
||||
}
|
||||
|
||||
fn packed_record_company_target_import_blocker(
|
||||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
company_context: &ImportRuntimeContext,
|
||||
|
|
@ -1858,6 +1959,9 @@ fn runtime_condition_company_target_import_blocker(
|
|||
target, territory, ..
|
||||
} => company_target_import_blocker(target, company_context)
|
||||
.or_else(|| territory_target_import_blocker(territory, company_context)),
|
||||
RuntimeCondition::SpecialConditionThreshold { .. }
|
||||
| RuntimeCondition::CandidateAvailabilityThreshold { .. }
|
||||
| RuntimeCondition::EconomicStatusCodeThreshold { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1896,6 +2000,7 @@ fn company_target_import_outcome(blocker: ImportBlocker) -> &'static str {
|
|||
ImportBlocker::MissingTerritoryContext => "blocked_missing_territory_context",
|
||||
ImportBlocker::NamedTerritoryBinding => "blocked_named_territory_binding",
|
||||
ImportBlocker::UnmappedOrdinaryCondition => "blocked_unmapped_ordinary_condition",
|
||||
ImportBlocker::UnmappedWorldCondition => "blocked_unmapped_world_condition",
|
||||
ImportBlocker::MissingTrainContext => "blocked_missing_train_context",
|
||||
ImportBlocker::MissingTrainTerritoryContext => "blocked_missing_train_territory_context",
|
||||
}
|
||||
|
|
@ -4621,8 +4726,10 @@ mod tests {
|
|||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![real_territory_access_row(
|
||||
true,
|
||||
vec!["territory access row is missing company or territory scope"
|
||||
.to_string()],
|
||||
vec![
|
||||
"territory access row is missing company or territory scope"
|
||||
.to_string(),
|
||||
],
|
||||
)],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
|
|
|
|||
|
|
@ -39,14 +39,13 @@ pub use runtime::{
|
|||
RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCompanyTerritoryAccess,
|
||||
RuntimeCompanyTerritoryTrackPieceCount, RuntimeCondition, RuntimeConditionComparator,
|
||||
RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||
RuntimePackedEventCollectionSummary,
|
||||
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
||||
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
|
||||
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, RuntimePlayer,
|
||||
RuntimePlayerConditionTestScope, RuntimePlayerTarget, RuntimeSaveProfileState,
|
||||
RuntimeServiceState, RuntimeState, RuntimeTerritory, RuntimeTerritoryMetric,
|
||||
RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts, RuntimeTrain,
|
||||
RuntimeWorldRestoreState,
|
||||
RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary,
|
||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
||||
RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary,
|
||||
RuntimePackedEventTextBandSummary, RuntimePlayer, RuntimePlayerConditionTestScope,
|
||||
RuntimePlayerTarget, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
|
||||
RuntimeTerritory, RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric,
|
||||
RuntimeTrackPieceCounts, RuntimeTrain, RuntimeWorldRestoreState,
|
||||
};
|
||||
pub use smp::{
|
||||
SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAlignedRuntimeRuleBandLane,
|
||||
|
|
|
|||
|
|
@ -228,6 +228,20 @@ pub enum RuntimeCondition {
|
|||
comparator: RuntimeConditionComparator,
|
||||
value: i64,
|
||||
},
|
||||
SpecialConditionThreshold {
|
||||
label: String,
|
||||
comparator: RuntimeConditionComparator,
|
||||
value: i64,
|
||||
},
|
||||
CandidateAvailabilityThreshold {
|
||||
name: String,
|
||||
comparator: RuntimeConditionComparator,
|
||||
value: i64,
|
||||
},
|
||||
EconomicStatusCodeThreshold {
|
||||
comparator: RuntimeConditionComparator,
|
||||
value: i64,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
@ -1235,6 +1249,21 @@ fn validate_runtime_condition(
|
|||
validate_company_target(target, valid_company_ids)?;
|
||||
validate_territory_target(territory, valid_territory_ids)
|
||||
}
|
||||
RuntimeCondition::SpecialConditionThreshold { label, .. } => {
|
||||
if label.trim().is_empty() {
|
||||
Err("label must not be empty".to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
RuntimeCondition::CandidateAvailabilityThreshold { name, .. } => {
|
||||
if name.trim().is_empty() {
|
||||
Err("name must not be empty".to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
RuntimeCondition::EconomicStatusCodeThreshold { .. } => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2126,9 +2126,8 @@ fn parse_real_event_runtime_record_summary(
|
|||
&& row.raw_scalar_value != 0
|
||||
&& (!company_target_present || !territory_target_present)
|
||||
{
|
||||
row.notes.push(
|
||||
"territory access row is missing company or territory scope".to_string(),
|
||||
);
|
||||
row.notes
|
||||
.push("territory access row is missing company or territory scope".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2927,18 +2926,20 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
|
|||
),
|
||||
RuntimeEffect::SetCompanyTerritoryAccess {
|
||||
target, territory, ..
|
||||
} => matches!(
|
||||
target,
|
||||
RuntimeCompanyTarget::AllActive
|
||||
| RuntimeCompanyTarget::Ids { .. }
|
||||
| RuntimeCompanyTarget::HumanCompanies
|
||||
| RuntimeCompanyTarget::AiCompanies
|
||||
| RuntimeCompanyTarget::SelectedCompany
|
||||
| RuntimeCompanyTarget::ConditionTrueCompany
|
||||
) && matches!(
|
||||
territory,
|
||||
RuntimeTerritoryTarget::AllTerritories | RuntimeTerritoryTarget::Ids { .. }
|
||||
),
|
||||
} => {
|
||||
matches!(
|
||||
target,
|
||||
RuntimeCompanyTarget::AllActive
|
||||
| RuntimeCompanyTarget::Ids { .. }
|
||||
| RuntimeCompanyTarget::HumanCompanies
|
||||
| RuntimeCompanyTarget::AiCompanies
|
||||
| RuntimeCompanyTarget::SelectedCompany
|
||||
| RuntimeCompanyTarget::ConditionTrueCompany
|
||||
) && matches!(
|
||||
territory,
|
||||
RuntimeTerritoryTarget::AllTerritories | RuntimeTerritoryTarget::Ids { .. }
|
||||
)
|
||||
}
|
||||
RuntimeEffect::SetCompanyCash { target, .. }
|
||||
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
||||
| RuntimeEffect::AdjustCompanyDebt { target, .. } => matches!(
|
||||
|
|
@ -2961,7 +2962,10 @@ fn runtime_condition_supported_for_save_import(condition: &RuntimeCondition) ->
|
|||
match condition {
|
||||
RuntimeCondition::CompanyNumericThreshold { .. }
|
||||
| RuntimeCondition::TerritoryNumericThreshold { .. }
|
||||
| RuntimeCondition::CompanyTerritoryNumericThreshold { .. } => true,
|
||||
| RuntimeCondition::CompanyTerritoryNumericThreshold { .. }
|
||||
| RuntimeCondition::SpecialConditionThreshold { .. }
|
||||
| RuntimeCondition::CandidateAvailabilityThreshold { .. }
|
||||
| RuntimeCondition::EconomicStatusCodeThreshold { .. } => true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -651,6 +651,46 @@ fn evaluate_record_conditions(
|
|||
return Ok(None);
|
||||
}
|
||||
}
|
||||
RuntimeCondition::SpecialConditionThreshold {
|
||||
label,
|
||||
comparator,
|
||||
value,
|
||||
} => {
|
||||
let actual = state
|
||||
.special_conditions
|
||||
.get(label)
|
||||
.copied()
|
||||
.map(i64::from)
|
||||
.unwrap_or(0);
|
||||
if !compare_condition_value(actual, *comparator, *value) {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
RuntimeCondition::CandidateAvailabilityThreshold {
|
||||
name,
|
||||
comparator,
|
||||
value,
|
||||
} => {
|
||||
let actual = state
|
||||
.candidate_availability
|
||||
.get(name)
|
||||
.copied()
|
||||
.map(i64::from)
|
||||
.unwrap_or(0);
|
||||
if !compare_condition_value(actual, *comparator, *value) {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
RuntimeCondition::EconomicStatusCodeThreshold { comparator, value } => {
|
||||
let actual = state
|
||||
.world_restore
|
||||
.economic_status_code
|
||||
.map(i64::from)
|
||||
.unwrap_or(0);
|
||||
if !compare_condition_value(actual, *comparator, *value) {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1039,7 +1079,8 @@ fn set_company_territory_access_pairs(
|
|||
}
|
||||
} else {
|
||||
access_entries.retain(|entry| {
|
||||
!(company_ids.contains(&entry.company_id) && territory_ids.contains(&entry.territory_id))
|
||||
!(company_ids.contains(&entry.company_id)
|
||||
&& territory_ids.contains(&entry.territory_id))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1785,6 +1826,78 @@ mod tests {
|
|||
assert!(error.contains("condition-evaluation context"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluates_world_state_conditions_before_effects_run() {
|
||||
let mut state = RuntimeState {
|
||||
world_restore: RuntimeWorldRestoreState {
|
||||
economic_status_code: Some(3),
|
||||
..RuntimeWorldRestoreState::default()
|
||||
},
|
||||
candidate_availability: BTreeMap::from([(String::from("Mogul"), 2)]),
|
||||
special_conditions: BTreeMap::from([(String::from("Use Wartime Cargos"), 1)]),
|
||||
event_runtime_records: vec![
|
||||
RuntimeEventRecord {
|
||||
record_id: 23,
|
||||
trigger_kind: 7,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: vec![
|
||||
RuntimeCondition::SpecialConditionThreshold {
|
||||
label: "Use Wartime Cargos".to_string(),
|
||||
comparator: RuntimeConditionComparator::Ge,
|
||||
value: 1,
|
||||
},
|
||||
RuntimeCondition::CandidateAvailabilityThreshold {
|
||||
name: "Mogul".to_string(),
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 2,
|
||||
},
|
||||
RuntimeCondition::EconomicStatusCodeThreshold {
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||
key: "world_condition_passed".to_string(),
|
||||
value: true,
|
||||
}],
|
||||
},
|
||||
RuntimeEventRecord {
|
||||
record_id: 24,
|
||||
trigger_kind: 7,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: vec![RuntimeCondition::SpecialConditionThreshold {
|
||||
label: "Disable Cargo Economy".to_string(),
|
||||
comparator: RuntimeConditionComparator::Gt,
|
||||
value: 0,
|
||||
}],
|
||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||
key: "world_condition_failed".to_string(),
|
||||
value: true,
|
||||
}],
|
||||
},
|
||||
],
|
||||
..state()
|
||||
};
|
||||
|
||||
let result = execute_step_command(
|
||||
&mut state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||
)
|
||||
.expect("world-state conditions should evaluate successfully");
|
||||
|
||||
assert_eq!(result.service_events[0].serviced_record_ids, vec![23]);
|
||||
assert_eq!(state.world_flags.get("world_condition_passed"), Some(&true));
|
||||
assert_eq!(state.world_flags.get("world_condition_failed"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_shot_record_only_fires_once() {
|
||||
let mut state = RuntimeState {
|
||||
|
|
|
|||
|
|
@ -56,8 +56,10 @@ pub struct RuntimeSummary {
|
|||
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,
|
||||
|
|
@ -393,6 +395,20 @@ impl RuntimeSummary {
|
|||
.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()
|
||||
|
|
@ -421,6 +437,20 @@ impl RuntimeSummary {
|
|||
.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()
|
||||
|
|
@ -843,4 +873,107 @@ mod tests {
|
|||
assert_eq!(summary.company_count, 2);
|
||||
assert_eq!(summary.active_company_count, 1);
|
||||
}
|
||||
|
||||
#[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(),
|
||||
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(),
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue