Unlock negative-sentinel company condition scopes
This commit is contained in:
parent
780e739daa
commit
087ebf1097
18 changed files with 1315 additions and 79 deletions
|
|
@ -4440,14 +4440,20 @@ mod tests {
|
|||
.join("../../fixtures/runtime/packed-event-selective-import-save-slice-fixture.json");
|
||||
let overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../fixtures/runtime/packed-event-selective-import-overlay-fixture.json");
|
||||
let symbolic_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../fixtures/runtime/packed-event-symbolic-company-scope-overlay-fixture.json");
|
||||
let symbolic_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"../../fixtures/runtime/packed-event-symbolic-company-scope-overlay-fixture.json",
|
||||
);
|
||||
let negative_company_scope_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join(
|
||||
"../../fixtures/runtime/packed-event-negative-company-scope-overlay-fixture.json",
|
||||
);
|
||||
let deactivate_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../fixtures/runtime/packed-event-deactivate-company-overlay-fixture.json");
|
||||
let track_capacity_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../fixtures/runtime/packed-event-track-capacity-overlay-fixture.json");
|
||||
let mixed_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../fixtures/runtime/packed-event-mixed-company-descriptor-overlay-fixture.json");
|
||||
let mixed_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"../../fixtures/runtime/packed-event-mixed-company-descriptor-overlay-fixture.json",
|
||||
);
|
||||
|
||||
run_runtime_summarize_fixture(&parity_fixture)
|
||||
.expect("save-slice-backed parity fixture should summarize");
|
||||
|
|
@ -4457,6 +4463,8 @@ mod tests {
|
|||
.expect("overlay-backed selective-import fixture should summarize");
|
||||
run_runtime_summarize_fixture(&symbolic_overlay_fixture)
|
||||
.expect("overlay-backed symbolic-target fixture should summarize");
|
||||
run_runtime_summarize_fixture(&negative_company_scope_overlay_fixture)
|
||||
.expect("overlay-backed negative-sentinel company-scope fixture should summarize");
|
||||
run_runtime_summarize_fixture(&deactivate_overlay_fixture)
|
||||
.expect("overlay-backed deactivate-company fixture should summarize");
|
||||
run_runtime_summarize_fixture(&track_capacity_overlay_fixture)
|
||||
|
|
|
|||
|
|
@ -388,6 +388,7 @@ mod tests {
|
|||
text_bands: vec![],
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_actions: vec![rrt_runtime::RuntimeEffect::AdjustCompanyCash {
|
||||
|
|
|
|||
|
|
@ -84,6 +84,12 @@ pub struct ExpectedRuntimeSummary {
|
|||
#[serde(default)]
|
||||
pub packed_event_blocked_missing_condition_context_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub packed_event_blocked_company_condition_scope_disabled_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub packed_event_blocked_player_condition_scope_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub packed_event_blocked_territory_condition_scope_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>,
|
||||
|
|
@ -417,6 +423,30 @@ impl ExpectedRuntimeSummary {
|
|||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.packed_event_blocked_company_condition_scope_disabled_count {
|
||||
if actual.packed_event_blocked_company_condition_scope_disabled_count != count {
|
||||
mismatches.push(format!(
|
||||
"packed_event_blocked_company_condition_scope_disabled_count mismatch: expected {count}, got {}",
|
||||
actual.packed_event_blocked_company_condition_scope_disabled_count
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.packed_event_blocked_player_condition_scope_count {
|
||||
if actual.packed_event_blocked_player_condition_scope_count != count {
|
||||
mismatches.push(format!(
|
||||
"packed_event_blocked_player_condition_scope_count mismatch: expected {count}, got {}",
|
||||
actual.packed_event_blocked_player_condition_scope_count
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.packed_event_blocked_territory_condition_scope_count {
|
||||
if actual.packed_event_blocked_territory_condition_scope_count != count {
|
||||
mismatches.push(format!(
|
||||
"packed_event_blocked_territory_condition_scope_count mismatch: expected {count}, got {}",
|
||||
actual.packed_event_blocked_territory_condition_scope_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!(
|
||||
|
|
|
|||
|
|
@ -247,14 +247,12 @@ impl CompanyFinanceState {
|
|||
self.current_dividend_per_share = new_rate.clamp(0.0, self.board_dividend_ceiling);
|
||||
}
|
||||
|
||||
pub fn read_recent_metric(
|
||||
&self,
|
||||
metric: AnnualReportMetric,
|
||||
years_ago: usize,
|
||||
) -> Option<f64> {
|
||||
pub fn read_recent_metric(&self, metric: AnnualReportMetric, years_ago: usize) -> Option<f64> {
|
||||
match metric {
|
||||
AnnualReportMetric::FuelCost if years_ago == 0 => Some(self.current_fuel_cost as f64),
|
||||
AnnualReportMetric::BookValuePerShare if years_ago == 0 => Some(self.book_value_per_share),
|
||||
AnnualReportMetric::BookValuePerShare if years_ago == 0 => {
|
||||
Some(self.book_value_per_share)
|
||||
}
|
||||
AnnualReportMetric::NetProfits => self
|
||||
.recent_net_profits
|
||||
.get(years_ago)
|
||||
|
|
@ -278,11 +276,7 @@ impl CompanyFinanceState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn read_recent_metric_window(
|
||||
&self,
|
||||
metric: AnnualReportMetric,
|
||||
years: usize,
|
||||
) -> Vec<f64> {
|
||||
pub fn read_recent_metric_window(&self, metric: AnnualReportMetric, years: usize) -> Vec<f64> {
|
||||
(0..years)
|
||||
.filter_map(|years_ago| self.read_recent_metric(metric, years_ago))
|
||||
.collect()
|
||||
|
|
@ -457,7 +451,10 @@ fn should_bankrupt_deep_distress(
|
|||
&& company.current_cash < -300_000
|
||||
&& company.years_since_founding >= 3
|
||||
&& company.years_since_last_bankruptcy >= 5
|
||||
&& company.recent_net_profits.iter().all(|profit| *profit <= -20_000)
|
||||
&& company
|
||||
.recent_net_profits
|
||||
.iter()
|
||||
.all(|profit| *profit <= -20_000)
|
||||
}
|
||||
|
||||
fn issue_bond_evaluation(
|
||||
|
|
@ -552,9 +549,8 @@ fn issue_stock_evaluation(
|
|||
return None;
|
||||
}
|
||||
|
||||
let mut tranche =
|
||||
((company.outstanding_share_count / 10) / CompanyFinanceState::SHARE_LOT)
|
||||
* CompanyFinanceState::SHARE_LOT;
|
||||
let mut tranche = ((company.outstanding_share_count / 10) / CompanyFinanceState::SHARE_LOT)
|
||||
* CompanyFinanceState::SHARE_LOT;
|
||||
tranche = tranche.max(2_000);
|
||||
while tranche >= CompanyFinanceState::SHARE_LOT
|
||||
&& company.support_adjusted_share_price * tranche as f64 > 55_000.0
|
||||
|
|
|
|||
|
|
@ -5,12 +5,14 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::persistence::{load_runtime_snapshot_document, validate_runtime_snapshot_document};
|
||||
use crate::{
|
||||
CalendarPoint, RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect,
|
||||
RuntimeEventRecord, RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
||||
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
||||
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventRecordSummary,
|
||||
RuntimePackedEventTextBandSummary, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
|
||||
RuntimeWorldRestoreState, SmpLoadedPackedEventRecordSummary,
|
||||
CalendarPoint, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
||||
RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||
RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary,
|
||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
||||
RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary,
|
||||
RuntimePackedEventTextBandSummary, RuntimePlayerConditionTestScope, RuntimeSaveProfileState,
|
||||
RuntimeServiceState, RuntimeState, RuntimeWorldRestoreState,
|
||||
SmpLoadedPackedEventNegativeSentinelScopeSummary, SmpLoadedPackedEventRecordSummary,
|
||||
SmpLoadedPackedEventTextBandSummary, SmpLoadedSaveSlice,
|
||||
};
|
||||
|
||||
|
|
@ -112,6 +114,9 @@ enum CompanyTargetImportBlocker {
|
|||
MissingSelectionContext,
|
||||
MissingCompanyRoleContext,
|
||||
MissingConditionContext,
|
||||
CompanyConditionScopeDisabled,
|
||||
PlayerConditionScope,
|
||||
TerritoryConditionScope,
|
||||
}
|
||||
|
||||
impl ImportCompanyContext {
|
||||
|
|
@ -592,6 +597,8 @@ fn runtime_packed_event_record_summary_from_smp(
|
|||
company_context: &ImportCompanyContext,
|
||||
imported: bool,
|
||||
) -> RuntimePackedEventRecordSummary {
|
||||
let lowered_decoded_actions =
|
||||
lowered_record_decoded_actions(record).unwrap_or_else(|_| record.decoded_actions.clone());
|
||||
RuntimePackedEventRecordSummary {
|
||||
record_index: record.record_index,
|
||||
live_entry_id: record.live_entry_id,
|
||||
|
|
@ -618,6 +625,10 @@ fn runtime_packed_event_record_summary_from_smp(
|
|||
.iter()
|
||||
.map(runtime_packed_event_condition_row_summary_from_smp)
|
||||
.collect(),
|
||||
negative_sentinel_scope: record
|
||||
.negative_sentinel_scope
|
||||
.as_ref()
|
||||
.map(runtime_packed_event_negative_sentinel_scope_summary_from_smp),
|
||||
grouped_effect_row_counts: record.grouped_effect_row_counts.clone(),
|
||||
grouped_effect_rows: record
|
||||
.grouped_effect_rows
|
||||
|
|
@ -625,7 +636,7 @@ fn runtime_packed_event_record_summary_from_smp(
|
|||
.map(runtime_packed_event_grouped_effect_row_summary_from_smp)
|
||||
.collect(),
|
||||
grouped_company_targets: classify_real_grouped_company_targets(record),
|
||||
decoded_actions: record.decoded_actions.clone(),
|
||||
decoded_actions: lowered_decoded_actions,
|
||||
executable_import_ready: record.executable_import_ready,
|
||||
import_outcome: Some(determine_packed_event_import_outcome(
|
||||
record,
|
||||
|
|
@ -636,6 +647,17 @@ fn runtime_packed_event_record_summary_from_smp(
|
|||
}
|
||||
}
|
||||
|
||||
fn runtime_packed_event_negative_sentinel_scope_summary_from_smp(
|
||||
scope: &SmpLoadedPackedEventNegativeSentinelScopeSummary,
|
||||
) -> RuntimePackedEventNegativeSentinelScopeSummary {
|
||||
RuntimePackedEventNegativeSentinelScopeSummary {
|
||||
company_test_scope: scope.company_test_scope,
|
||||
player_test_scope: scope.player_test_scope,
|
||||
territory_scope_selector_is_0x63: scope.territory_scope_selector_is_0x63,
|
||||
source_row_indexes: scope.source_row_indexes.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn runtime_packed_event_compact_control_summary_from_smp(
|
||||
control: &crate::SmpLoadedPackedEventCompactControlSummary,
|
||||
) -> RuntimePackedEventCompactControlSummary {
|
||||
|
|
@ -710,15 +732,20 @@ fn smp_packed_record_to_runtime_event_record(
|
|||
if record.decode_status == "unsupported_framing" {
|
||||
return None;
|
||||
}
|
||||
if record.payload_family == "real_packed_v1" && !record.executable_import_ready {
|
||||
return None;
|
||||
if record.payload_family == "real_packed_v1" {
|
||||
if record.compact_control.is_none() || !record.executable_import_ready {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let effects =
|
||||
match smp_runtime_effects_to_runtime_effects(&record.decoded_actions, company_context) {
|
||||
Ok(effects) => effects,
|
||||
Err(_) => return None,
|
||||
};
|
||||
let lowered_effects = match lowered_record_decoded_actions(record) {
|
||||
Ok(effects) => effects,
|
||||
Err(_) => return None,
|
||||
};
|
||||
let effects = match smp_runtime_effects_to_runtime_effects(&lowered_effects, company_context) {
|
||||
Ok(effects) => effects,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
Some((|| {
|
||||
let trigger_kind = record.trigger_kind.ok_or_else(|| {
|
||||
|
|
@ -742,6 +769,160 @@ fn smp_packed_record_to_runtime_event_record(
|
|||
})())
|
||||
}
|
||||
|
||||
fn lowered_record_decoded_actions(
|
||||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
) -> Result<Vec<RuntimeEffect>, CompanyTargetImportBlocker> {
|
||||
if let Some(blocker) = packed_record_condition_scope_import_blocker(record) {
|
||||
return Err(blocker);
|
||||
}
|
||||
|
||||
let Some(lowered_target) = lowered_condition_true_company_target(record) else {
|
||||
return Ok(record.decoded_actions.clone());
|
||||
};
|
||||
Ok(record
|
||||
.decoded_actions
|
||||
.iter()
|
||||
.map(|effect| lower_condition_true_company_target_in_effect(effect, &lowered_target))
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn packed_record_condition_scope_import_blocker(
|
||||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
) -> Option<CompanyTargetImportBlocker> {
|
||||
if record.standalone_condition_rows.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let negative_sentinel_row_count = record
|
||||
.standalone_condition_rows
|
||||
.iter()
|
||||
.filter(|row| row.raw_condition_id == -1)
|
||||
.count();
|
||||
if negative_sentinel_row_count == 0 {
|
||||
return Some(CompanyTargetImportBlocker::MissingConditionContext);
|
||||
}
|
||||
if negative_sentinel_row_count != record.standalone_condition_rows.len() {
|
||||
return Some(CompanyTargetImportBlocker::MissingConditionContext);
|
||||
}
|
||||
|
||||
let Some(scope) = record.negative_sentinel_scope.as_ref() else {
|
||||
return Some(CompanyTargetImportBlocker::MissingConditionContext);
|
||||
};
|
||||
if scope.player_test_scope != RuntimePlayerConditionTestScope::Disabled {
|
||||
return Some(CompanyTargetImportBlocker::PlayerConditionScope);
|
||||
}
|
||||
if scope.territory_scope_selector_is_0x63 {
|
||||
return Some(CompanyTargetImportBlocker::TerritoryConditionScope);
|
||||
}
|
||||
if scope.company_test_scope == RuntimeCompanyConditionTestScope::Disabled {
|
||||
return Some(CompanyTargetImportBlocker::CompanyConditionScopeDisabled);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn lowered_condition_true_company_target(
|
||||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
) -> Option<RuntimeCompanyTarget> {
|
||||
let scope = record.negative_sentinel_scope.as_ref()?;
|
||||
match scope.company_test_scope {
|
||||
RuntimeCompanyConditionTestScope::Disabled => None,
|
||||
RuntimeCompanyConditionTestScope::AllCompanies => Some(RuntimeCompanyTarget::AllActive),
|
||||
RuntimeCompanyConditionTestScope::SelectedCompanyOnly => {
|
||||
Some(RuntimeCompanyTarget::SelectedCompany)
|
||||
}
|
||||
RuntimeCompanyConditionTestScope::AiCompaniesOnly => {
|
||||
Some(RuntimeCompanyTarget::AiCompanies)
|
||||
}
|
||||
RuntimeCompanyConditionTestScope::HumanCompaniesOnly => {
|
||||
Some(RuntimeCompanyTarget::HumanCompanies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_condition_true_company_target_in_effect(
|
||||
effect: &RuntimeEffect,
|
||||
lowered_target: &RuntimeCompanyTarget,
|
||||
) -> RuntimeEffect {
|
||||
match effect {
|
||||
RuntimeEffect::SetWorldFlag { key, value } => RuntimeEffect::SetWorldFlag {
|
||||
key: key.clone(),
|
||||
value: *value,
|
||||
},
|
||||
RuntimeEffect::SetCompanyCash { target, value } => RuntimeEffect::SetCompanyCash {
|
||||
target: lower_condition_true_company_target_in_company_target(target, lowered_target),
|
||||
value: *value,
|
||||
},
|
||||
RuntimeEffect::DeactivateCompany { target } => RuntimeEffect::DeactivateCompany {
|
||||
target: lower_condition_true_company_target_in_company_target(target, lowered_target),
|
||||
},
|
||||
RuntimeEffect::SetCompanyTrackLayingCapacity { target, value } => {
|
||||
RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||
target: lower_condition_true_company_target_in_company_target(
|
||||
target,
|
||||
lowered_target,
|
||||
),
|
||||
value: *value,
|
||||
}
|
||||
}
|
||||
RuntimeEffect::AdjustCompanyCash { target, delta } => RuntimeEffect::AdjustCompanyCash {
|
||||
target: lower_condition_true_company_target_in_company_target(target, lowered_target),
|
||||
delta: *delta,
|
||||
},
|
||||
RuntimeEffect::AdjustCompanyDebt { target, delta } => RuntimeEffect::AdjustCompanyDebt {
|
||||
target: lower_condition_true_company_target_in_company_target(target, lowered_target),
|
||||
delta: *delta,
|
||||
},
|
||||
RuntimeEffect::SetCandidateAvailability { name, value } => {
|
||||
RuntimeEffect::SetCandidateAvailability {
|
||||
name: name.clone(),
|
||||
value: *value,
|
||||
}
|
||||
}
|
||||
RuntimeEffect::SetSpecialCondition { label, value } => RuntimeEffect::SetSpecialCondition {
|
||||
label: label.clone(),
|
||||
value: *value,
|
||||
},
|
||||
RuntimeEffect::AppendEventRecord { record } => RuntimeEffect::AppendEventRecord {
|
||||
record: Box::new(RuntimeEventRecordTemplate {
|
||||
record_id: record.record_id,
|
||||
trigger_kind: record.trigger_kind,
|
||||
active: record.active,
|
||||
marks_collection_dirty: record.marks_collection_dirty,
|
||||
one_shot: record.one_shot,
|
||||
effects: record
|
||||
.effects
|
||||
.iter()
|
||||
.map(|nested| {
|
||||
lower_condition_true_company_target_in_effect(nested, lowered_target)
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
},
|
||||
RuntimeEffect::ActivateEventRecord { record_id } => RuntimeEffect::ActivateEventRecord {
|
||||
record_id: *record_id,
|
||||
},
|
||||
RuntimeEffect::DeactivateEventRecord { record_id } => {
|
||||
RuntimeEffect::DeactivateEventRecord {
|
||||
record_id: *record_id,
|
||||
}
|
||||
}
|
||||
RuntimeEffect::RemoveEventRecord { record_id } => RuntimeEffect::RemoveEventRecord {
|
||||
record_id: *record_id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_condition_true_company_target_in_company_target(
|
||||
target: &RuntimeCompanyTarget,
|
||||
lowered_target: &RuntimeCompanyTarget,
|
||||
) -> RuntimeCompanyTarget {
|
||||
match target {
|
||||
RuntimeCompanyTarget::ConditionTrueCompany => lowered_target.clone(),
|
||||
_ => target.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn smp_runtime_effects_to_runtime_effects(
|
||||
effects: &[RuntimeEffect],
|
||||
company_context: &ImportCompanyContext,
|
||||
|
|
@ -912,6 +1093,18 @@ fn company_target_import_error_message(
|
|||
Some(CompanyTargetImportBlocker::MissingConditionContext) => {
|
||||
"packed company effect requires condition-relative context".to_string()
|
||||
}
|
||||
Some(CompanyTargetImportBlocker::CompanyConditionScopeDisabled) => {
|
||||
"packed company effect disables company-side negative-sentinel condition scope"
|
||||
.to_string()
|
||||
}
|
||||
Some(CompanyTargetImportBlocker::PlayerConditionScope) => {
|
||||
"packed company effect requires player runtime ownership for negative-sentinel scope"
|
||||
.to_string()
|
||||
}
|
||||
Some(CompanyTargetImportBlocker::TerritoryConditionScope) => {
|
||||
"packed company effect requires territory runtime ownership for negative-sentinel scope"
|
||||
.to_string()
|
||||
}
|
||||
None => "packed company effect is importable".to_string(),
|
||||
}
|
||||
}
|
||||
|
|
@ -934,6 +1127,9 @@ fn determine_packed_event_import_outcome(
|
|||
if !record.executable_import_ready {
|
||||
return "blocked_unmapped_real_descriptor".to_string();
|
||||
}
|
||||
if let Some(blocker) = packed_record_condition_scope_import_blocker(record) {
|
||||
return company_target_import_outcome(blocker).to_string();
|
||||
}
|
||||
if let Some(blocker) = packed_record_company_target_import_blocker(record, company_context)
|
||||
{
|
||||
return company_target_import_outcome(blocker).to_string();
|
||||
|
|
@ -950,8 +1146,11 @@ fn packed_record_company_target_import_blocker(
|
|||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
company_context: &ImportCompanyContext,
|
||||
) -> Option<CompanyTargetImportBlocker> {
|
||||
record
|
||||
.decoded_actions
|
||||
let lowered_effects = match lowered_record_decoded_actions(record) {
|
||||
Ok(effects) => effects,
|
||||
Err(blocker) => return Some(blocker),
|
||||
};
|
||||
lowered_effects
|
||||
.iter()
|
||||
.find_map(|effect| runtime_effect_company_target_import_blocker(effect, company_context))
|
||||
}
|
||||
|
|
@ -1022,6 +1221,11 @@ fn company_target_import_outcome(blocker: CompanyTargetImportBlocker) -> &'stati
|
|||
"blocked_missing_company_role_context"
|
||||
}
|
||||
CompanyTargetImportBlocker::MissingConditionContext => "blocked_missing_condition_context",
|
||||
CompanyTargetImportBlocker::CompanyConditionScopeDisabled => {
|
||||
"blocked_company_condition_scope_disabled"
|
||||
}
|
||||
CompanyTargetImportBlocker::PlayerConditionScope => "blocked_player_condition_scope",
|
||||
CompanyTargetImportBlocker::TerritoryConditionScope => "blocked_territory_condition_scope",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1393,6 +1597,7 @@ mod tests {
|
|||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_actions: vec![effect],
|
||||
|
|
@ -1401,6 +1606,36 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn company_negative_sentinel_scope(
|
||||
company_test_scope: RuntimeCompanyConditionTestScope,
|
||||
) -> crate::SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
||||
crate::SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
||||
company_test_scope,
|
||||
player_test_scope: RuntimePlayerConditionTestScope::Disabled,
|
||||
territory_scope_selector_is_0x63: false,
|
||||
source_row_indexes: vec![0],
|
||||
}
|
||||
}
|
||||
|
||||
fn territory_negative_sentinel_scope() -> crate::SmpLoadedPackedEventNegativeSentinelScopeSummary
|
||||
{
|
||||
crate::SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
||||
company_test_scope: RuntimeCompanyConditionTestScope::AllCompanies,
|
||||
player_test_scope: RuntimePlayerConditionTestScope::Disabled,
|
||||
territory_scope_selector_is_0x63: true,
|
||||
source_row_indexes: vec![0],
|
||||
}
|
||||
}
|
||||
|
||||
fn player_negative_sentinel_scope() -> crate::SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
||||
crate::SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
||||
company_test_scope: RuntimeCompanyConditionTestScope::AllCompanies,
|
||||
player_test_scope: RuntimePlayerConditionTestScope::AllPlayers,
|
||||
territory_scope_selector_is_0x63: false,
|
||||
source_row_indexes: vec![0],
|
||||
}
|
||||
}
|
||||
|
||||
fn real_grouped_rows() -> Vec<crate::SmpLoadedPackedEventGroupedEffectRowSummary> {
|
||||
vec![crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index: 0,
|
||||
|
|
@ -1425,7 +1660,9 @@ mod tests {
|
|||
}]
|
||||
}
|
||||
|
||||
fn real_deactivate_company_row(enabled: bool) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
fn real_deactivate_company_row(
|
||||
enabled: bool,
|
||||
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index: 0,
|
||||
row_index: 0,
|
||||
|
|
@ -1470,9 +1707,7 @@ mod tests {
|
|||
value_word_0x16: 0,
|
||||
row_shape: "scalar_assignment".to_string(),
|
||||
semantic_family: Some("scalar_assignment".to_string()),
|
||||
semantic_preview: Some(format!(
|
||||
"Set Company Track Pieces Buildable to {value}"
|
||||
)),
|
||||
semantic_preview: Some(format!("Set Company Track Pieces Buildable to {value}")),
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
|
|
@ -1758,6 +1993,7 @@ mod tests {
|
|||
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(),
|
||||
decoded_actions: Vec::new(),
|
||||
|
|
@ -1779,6 +2015,7 @@ mod tests {
|
|||
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(),
|
||||
decoded_actions: Vec::new(),
|
||||
|
|
@ -1800,6 +2037,7 @@ mod tests {
|
|||
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(),
|
||||
decoded_actions: Vec::new(),
|
||||
|
|
@ -2011,6 +2249,7 @@ mod tests {
|
|||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 1,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 1, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_actions: vec![
|
||||
|
|
@ -2121,6 +2360,7 @@ mod tests {
|
|||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
|
|
@ -2395,6 +2635,9 @@ mod tests {
|
|||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 1,
|
||||
standalone_condition_rows: real_condition_rows(),
|
||||
negative_sentinel_scope: Some(company_negative_sentinel_scope(
|
||||
RuntimeCompanyConditionTestScope::AllCompanies,
|
||||
)),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
|
|
@ -2443,8 +2686,272 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn leaves_real_records_with_condition_relative_company_scope_blocked_missing_condition_context()
|
||||
{
|
||||
fn lowers_negative_sentinel_company_scopes_into_runtime_company_targets() {
|
||||
let base_state = RuntimeState {
|
||||
companies: vec![
|
||||
crate::RuntimeCompany {
|
||||
company_id: 1,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 100,
|
||||
debt: 10,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
crate::RuntimeCompany {
|
||||
company_id: 2,
|
||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||
current_cash: 50,
|
||||
debt: 20,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
crate::RuntimeCompany {
|
||||
company_id: 3,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 70,
|
||||
debt: 30,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
],
|
||||
selected_company_id: Some(3),
|
||||
..state()
|
||||
};
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
trailer_family: None,
|
||||
bridge_family: None,
|
||||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
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()),
|
||||
metadata_tag_offset: 0x7100,
|
||||
records_tag_offset: 0x7200,
|
||||
close_tag_offset: 0x7600,
|
||||
packed_state_version: 0x3e9,
|
||||
packed_state_version_hex: "0x000003e9".to_string(),
|
||||
live_id_bound: 11,
|
||||
live_record_count: 5,
|
||||
live_entry_ids: vec![7, 8, 9, 10, 11],
|
||||
decoded_record_count: 5,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![
|
||||
crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 7,
|
||||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(133),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "real_packed_v1".to_string(),
|
||||
trigger_kind: Some(6),
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: Some(true),
|
||||
compact_control: Some(real_compact_control()),
|
||||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 1,
|
||||
standalone_condition_rows: real_condition_rows(),
|
||||
negative_sentinel_scope: Some(company_negative_sentinel_scope(
|
||||
RuntimeCompanyConditionTestScope::AllCompanies,
|
||||
)),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 7,
|
||||
}],
|
||||
executable_import_ready: true,
|
||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||
},
|
||||
crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 1,
|
||||
live_entry_id: 8,
|
||||
payload_offset: Some(0x7282),
|
||||
payload_len: Some(133),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "real_packed_v1".to_string(),
|
||||
trigger_kind: Some(6),
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: Some(true),
|
||||
compact_control: Some(real_compact_control()),
|
||||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 1,
|
||||
standalone_condition_rows: real_condition_rows(),
|
||||
negative_sentinel_scope: Some(company_negative_sentinel_scope(
|
||||
RuntimeCompanyConditionTestScope::SelectedCompanyOnly,
|
||||
)),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 8,
|
||||
}],
|
||||
executable_import_ready: true,
|
||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||
},
|
||||
crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 2,
|
||||
live_entry_id: 9,
|
||||
payload_offset: Some(0x7302),
|
||||
payload_len: Some(133),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "real_packed_v1".to_string(),
|
||||
trigger_kind: Some(6),
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: Some(true),
|
||||
compact_control: Some(real_compact_control()),
|
||||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 1,
|
||||
standalone_condition_rows: real_condition_rows(),
|
||||
negative_sentinel_scope: Some(company_negative_sentinel_scope(
|
||||
RuntimeCompanyConditionTestScope::AiCompaniesOnly,
|
||||
)),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 9,
|
||||
}],
|
||||
executable_import_ready: true,
|
||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||
},
|
||||
crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 3,
|
||||
live_entry_id: 10,
|
||||
payload_offset: Some(0x7382),
|
||||
payload_len: Some(133),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "real_packed_v1".to_string(),
|
||||
trigger_kind: Some(6),
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: Some(true),
|
||||
compact_control: Some(real_compact_control()),
|
||||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 1,
|
||||
standalone_condition_rows: real_condition_rows(),
|
||||
negative_sentinel_scope: Some(company_negative_sentinel_scope(
|
||||
RuntimeCompanyConditionTestScope::HumanCompaniesOnly,
|
||||
)),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 10,
|
||||
}],
|
||||
executable_import_ready: true,
|
||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||
},
|
||||
crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 4,
|
||||
live_entry_id: 11,
|
||||
payload_offset: Some(0x7402),
|
||||
payload_len: Some(133),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "real_packed_v1".to_string(),
|
||||
trigger_kind: Some(6),
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: Some(true),
|
||||
compact_control: Some(real_compact_control()),
|
||||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 1,
|
||||
standalone_condition_rows: real_condition_rows(),
|
||||
negative_sentinel_scope: Some(company_negative_sentinel_scope(
|
||||
RuntimeCompanyConditionTestScope::Disabled,
|
||||
)),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 11,
|
||||
}],
|
||||
executable_import_ready: true,
|
||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||
},
|
||||
],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let import = project_save_slice_overlay_to_runtime_state_import(
|
||||
&base_state,
|
||||
&save_slice,
|
||||
"packed-events-real-descriptor-frontier",
|
||||
None,
|
||||
)
|
||||
.expect("save slice should project");
|
||||
|
||||
assert_eq!(import.state.event_runtime_records.len(), 4);
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
.packed_event_collection
|
||||
.as_ref()
|
||||
.and_then(|summary| summary.records[0].compact_control.as_ref())
|
||||
.map(|control| control.mode_byte_0x7ef),
|
||||
Some(6)
|
||||
);
|
||||
let effects = import
|
||||
.state
|
||||
.event_runtime_records
|
||||
.iter()
|
||||
.map(|record| record.effects[0].clone())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
effects,
|
||||
vec![
|
||||
RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::AllActive,
|
||||
value: 7,
|
||||
},
|
||||
RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
value: 8,
|
||||
},
|
||||
RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::AiCompanies,
|
||||
value: 9,
|
||||
},
|
||||
RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::HumanCompanies,
|
||||
value: 10,
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
.packed_event_collection
|
||||
.as_ref()
|
||||
.map(|summary| {
|
||||
summary
|
||||
.records
|
||||
.iter()
|
||||
.map(|record| record.import_outcome.clone())
|
||||
.collect::<Vec<_>>()
|
||||
}),
|
||||
Some(vec![
|
||||
Some("imported".to_string()),
|
||||
Some("imported".to_string()),
|
||||
Some("imported".to_string()),
|
||||
Some("imported".to_string()),
|
||||
Some("blocked_company_condition_scope_disabled".to_string()),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocks_negative_sentinel_player_scope_until_player_runtime_exists() {
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
|
|
@ -2485,6 +2992,7 @@ mod tests {
|
|||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 1,
|
||||
standalone_condition_rows: real_condition_rows(),
|
||||
negative_sentinel_scope: Some(player_negative_sentinel_scope()),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
|
|
@ -2500,7 +3008,7 @@ mod tests {
|
|||
|
||||
let import = project_save_slice_to_runtime_state_import(
|
||||
&save_slice,
|
||||
"packed-events-real-descriptor-frontier",
|
||||
"negative-sentinel-player-scope",
|
||||
None,
|
||||
)
|
||||
.expect("save slice should project");
|
||||
|
|
@ -2511,17 +3019,82 @@ mod tests {
|
|||
.state
|
||||
.packed_event_collection
|
||||
.as_ref()
|
||||
.and_then(|summary| summary.records[0].compact_control.as_ref())
|
||||
.map(|control| control.mode_byte_0x7ef),
|
||||
Some(6)
|
||||
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
|
||||
Some("blocked_player_condition_scope")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocks_negative_sentinel_territory_scope_until_territory_runtime_exists() {
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
trailer_family: None,
|
||||
bridge_family: None,
|
||||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
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()),
|
||||
metadata_tag_offset: 0x7100,
|
||||
records_tag_offset: 0x7200,
|
||||
close_tag_offset: 0x7600,
|
||||
packed_state_version: 0x3e9,
|
||||
packed_state_version_hex: "0x000003e9".to_string(),
|
||||
live_id_bound: 7,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![7],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 7,
|
||||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(133),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "real_packed_v1".to_string(),
|
||||
trigger_kind: Some(6),
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: Some(true),
|
||||
compact_control: Some(real_compact_control()),
|
||||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 1,
|
||||
standalone_condition_rows: real_condition_rows(),
|
||||
negative_sentinel_scope: Some(territory_negative_sentinel_scope()),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 7,
|
||||
}],
|
||||
executable_import_ready: true,
|
||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let import = project_save_slice_to_runtime_state_import(
|
||||
&save_slice,
|
||||
"negative-sentinel-territory-scope",
|
||||
None,
|
||||
)
|
||||
.expect("save slice should project");
|
||||
|
||||
assert!(import.state.event_runtime_records.is_empty());
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
.packed_event_collection
|
||||
.as_ref()
|
||||
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
|
||||
Some("blocked_missing_condition_context")
|
||||
Some("blocked_territory_condition_scope")
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -2567,6 +3140,7 @@ mod tests {
|
|||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 1,
|
||||
standalone_condition_rows: real_condition_rows(),
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_actions: vec![],
|
||||
|
|
@ -2674,6 +3248,7 @@ mod tests {
|
|||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index: 0,
|
||||
|
|
@ -2806,6 +3381,7 @@ mod tests {
|
|||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![real_deactivate_company_row(true)],
|
||||
decoded_actions: vec![RuntimeEffect::DeactivateCompany {
|
||||
|
|
@ -2890,6 +3466,7 @@ mod tests {
|
|||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![real_deactivate_company_row(false)],
|
||||
decoded_actions: vec![],
|
||||
|
|
@ -2983,6 +3560,7 @@ mod tests {
|
|||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![real_track_capacity_row(18)],
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||
|
|
@ -3081,6 +3659,7 @@ mod tests {
|
|||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![1, 1, 0, 0],
|
||||
grouped_effect_rows: vec![
|
||||
real_track_capacity_row(18),
|
||||
|
|
@ -3198,6 +3777,7 @@ mod tests {
|
|||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
|
|
@ -3356,6 +3936,7 @@ mod tests {
|
|||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
|
|
|
|||
|
|
@ -35,11 +35,12 @@ pub use pk4::{
|
|||
extract_pk4_entry_bytes, extract_pk4_entry_file, inspect_pk4_bytes, inspect_pk4_file,
|
||||
};
|
||||
pub use runtime::{
|
||||
RuntimeCompany, RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord,
|
||||
RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
||||
RuntimePackedEventCompactControlSummary,
|
||||
RuntimeCompany, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
||||
RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||
RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary,
|
||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
||||
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, RuntimeSaveProfileState,
|
||||
RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary,
|
||||
RuntimePackedEventTextBandSummary, RuntimePlayerConditionTestScope, RuntimeSaveProfileState,
|
||||
RuntimeServiceState, RuntimeState, RuntimeWorldRestoreState,
|
||||
};
|
||||
pub use smp::{
|
||||
|
|
@ -48,8 +49,8 @@ pub use smp::{
|
|||
SmpClassicRehydrateProfileProbe, SmpContainerProfile, SmpEarlyContentProbe,
|
||||
SmpHeaderVariantProbe, SmpInspectionReport, SmpKnownTagHit,
|
||||
SmpLoadedCandidateAvailabilityTable, SmpLoadedEventRuntimeCollectionSummary,
|
||||
SmpLoadedPackedEventCompactControlSummary,
|
||||
SmpLoadedPackedEventConditionRowSummary, SmpLoadedPackedEventGroupedEffectRowSummary,
|
||||
SmpLoadedPackedEventCompactControlSummary, SmpLoadedPackedEventConditionRowSummary,
|
||||
SmpLoadedPackedEventGroupedEffectRowSummary, SmpLoadedPackedEventNegativeSentinelScopeSummary,
|
||||
SmpLoadedPackedEventRecordSummary, SmpLoadedPackedEventTextBandSummary, SmpLoadedProfile,
|
||||
SmpLoadedSaveSlice, SmpLoadedSpecialConditionsTable, SmpLocomotivePolicyFieldObservation,
|
||||
SmpLocomotivePolicyFloatAlignmentCandidate, SmpLocomotivePolicyNeighborhoodProbe,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,28 @@ pub enum RuntimeCompanyTarget {
|
|||
Ids { ids: Vec<u32> },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RuntimeCompanyConditionTestScope {
|
||||
#[default]
|
||||
Disabled,
|
||||
AllCompanies,
|
||||
SelectedCompanyOnly,
|
||||
AiCompaniesOnly,
|
||||
HumanCompaniesOnly,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RuntimePlayerConditionTestScope {
|
||||
#[default]
|
||||
Disabled,
|
||||
AllPlayers,
|
||||
SelectedPlayerOnly,
|
||||
AiPlayersOnly,
|
||||
HumanPlayersOnly,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum RuntimeEffect {
|
||||
|
|
@ -167,6 +189,8 @@ pub struct RuntimePackedEventRecordSummary {
|
|||
#[serde(default)]
|
||||
pub standalone_condition_rows: Vec<RuntimePackedEventConditionRowSummary>,
|
||||
#[serde(default)]
|
||||
pub negative_sentinel_scope: Option<RuntimePackedEventNegativeSentinelScopeSummary>,
|
||||
#[serde(default)]
|
||||
pub grouped_effect_row_counts: Vec<usize>,
|
||||
#[serde(default)]
|
||||
pub grouped_effect_rows: Vec<RuntimePackedEventGroupedEffectRowSummary>,
|
||||
|
|
@ -182,6 +206,15 @@ pub struct RuntimePackedEventRecordSummary {
|
|||
pub notes: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RuntimePackedEventNegativeSentinelScopeSummary {
|
||||
pub company_test_scope: RuntimeCompanyConditionTestScope,
|
||||
pub player_test_scope: RuntimePlayerConditionTestScope,
|
||||
pub territory_scope_selector_is_0x63: bool,
|
||||
#[serde(default)]
|
||||
pub source_row_indexes: Vec<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RuntimePackedEventCompactControlSummary {
|
||||
pub mode_byte_0x7ef: u8,
|
||||
|
|
@ -994,6 +1027,7 @@ mod tests {
|
|||
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(),
|
||||
|
|
@ -1017,6 +1051,7 @@ mod tests {
|
|||
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(),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ use std::path::Path;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::{RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecordTemplate};
|
||||
use crate::{
|
||||
RuntimeCompanyConditionTestScope, RuntimeCompanyTarget, RuntimeEffect,
|
||||
RuntimeEventRecordTemplate, RuntimePlayerConditionTestScope,
|
||||
};
|
||||
|
||||
pub const SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION: u32 = 0x03ec;
|
||||
const PREAMBLE_U32_WORD_COUNT: usize = 16;
|
||||
|
|
@ -1312,6 +1315,8 @@ pub struct SmpLoadedPackedEventRecordSummary {
|
|||
#[serde(default)]
|
||||
pub standalone_condition_rows: Vec<SmpLoadedPackedEventConditionRowSummary>,
|
||||
#[serde(default)]
|
||||
pub negative_sentinel_scope: Option<SmpLoadedPackedEventNegativeSentinelScopeSummary>,
|
||||
#[serde(default)]
|
||||
pub grouped_effect_row_counts: Vec<usize>,
|
||||
#[serde(default)]
|
||||
pub grouped_effect_rows: Vec<SmpLoadedPackedEventGroupedEffectRowSummary>,
|
||||
|
|
@ -1323,6 +1328,15 @@ pub struct SmpLoadedPackedEventRecordSummary {
|
|||
pub notes: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
||||
pub company_test_scope: RuntimeCompanyConditionTestScope,
|
||||
pub player_test_scope: RuntimePlayerConditionTestScope,
|
||||
pub territory_scope_selector_is_0x63: bool,
|
||||
#[serde(default)]
|
||||
pub source_row_indexes: Vec<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpLoadedPackedEventCompactControlSummary {
|
||||
pub mode_byte_0x7ef: u8,
|
||||
|
|
@ -1836,6 +1850,7 @@ fn parse_synthetic_event_runtime_record_summary(
|
|||
text_bands,
|
||||
standalone_condition_row_count,
|
||||
standalone_condition_rows: Vec::new(),
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts,
|
||||
grouped_effect_rows: Vec::new(),
|
||||
decoded_actions,
|
||||
|
|
@ -1940,6 +1955,9 @@ fn parse_real_event_runtime_record_summary(
|
|||
}
|
||||
}
|
||||
|
||||
let negative_sentinel_scope = compact_control.as_ref().and_then(|control| {
|
||||
derive_negative_sentinel_scope_summary(&standalone_condition_rows, control)
|
||||
});
|
||||
let decoded_actions = compact_control
|
||||
.as_ref()
|
||||
.map(|control| decode_real_grouped_effect_actions(&grouped_effect_rows, control))
|
||||
|
|
@ -1968,6 +1986,7 @@ fn parse_real_event_runtime_record_summary(
|
|||
text_bands,
|
||||
standalone_condition_row_count,
|
||||
standalone_condition_rows,
|
||||
negative_sentinel_scope,
|
||||
grouped_effect_row_counts,
|
||||
grouped_effect_rows,
|
||||
decoded_actions,
|
||||
|
|
@ -2074,6 +2093,49 @@ fn parse_real_condition_row_summary(
|
|||
})
|
||||
}
|
||||
|
||||
fn derive_negative_sentinel_scope_summary(
|
||||
rows: &[SmpLoadedPackedEventConditionRowSummary],
|
||||
control: &SmpLoadedPackedEventCompactControlSummary,
|
||||
) -> Option<SmpLoadedPackedEventNegativeSentinelScopeSummary> {
|
||||
let source_row_indexes = rows
|
||||
.iter()
|
||||
.filter(|row| row.raw_condition_id == -1)
|
||||
.map(|row| row.row_index)
|
||||
.collect::<Vec<_>>();
|
||||
if source_row_indexes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
||||
company_test_scope: decode_company_condition_test_scope(control.modifier_flag_0x7f9)?,
|
||||
player_test_scope: decode_player_condition_test_scope(control.modifier_flag_0x7fa)?,
|
||||
territory_scope_selector_is_0x63: control.primary_selector_0x7f0 == 0x63,
|
||||
source_row_indexes,
|
||||
})
|
||||
}
|
||||
|
||||
fn decode_company_condition_test_scope(value: u8) -> Option<RuntimeCompanyConditionTestScope> {
|
||||
match value {
|
||||
0 => Some(RuntimeCompanyConditionTestScope::Disabled),
|
||||
1 => Some(RuntimeCompanyConditionTestScope::AllCompanies),
|
||||
2 => Some(RuntimeCompanyConditionTestScope::SelectedCompanyOnly),
|
||||
3 => Some(RuntimeCompanyConditionTestScope::AiCompaniesOnly),
|
||||
4 => Some(RuntimeCompanyConditionTestScope::HumanCompaniesOnly),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_player_condition_test_scope(value: u8) -> Option<RuntimePlayerConditionTestScope> {
|
||||
match value {
|
||||
0 => Some(RuntimePlayerConditionTestScope::Disabled),
|
||||
1 => Some(RuntimePlayerConditionTestScope::AllPlayers),
|
||||
2 => Some(RuntimePlayerConditionTestScope::SelectedPlayerOnly),
|
||||
3 => Some(RuntimePlayerConditionTestScope::AiPlayersOnly),
|
||||
4 => Some(RuntimePlayerConditionTestScope::HumanPlayersOnly),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_real_grouped_effect_row_summary(
|
||||
row_bytes: &[u8],
|
||||
group_index: usize,
|
||||
|
|
@ -2484,6 +2546,7 @@ fn build_unsupported_event_runtime_record_summaries(
|
|||
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(),
|
||||
decoded_actions: Vec::new(),
|
||||
|
|
@ -7603,6 +7666,7 @@ mod tests {
|
|||
assert_eq!(summary.records[0].text_bands[0].preview, "Alpha");
|
||||
assert_eq!(summary.records[0].standalone_condition_row_count, 0);
|
||||
assert_eq!(summary.records[0].standalone_condition_rows.len(), 0);
|
||||
assert!(summary.records[0].negative_sentinel_scope.is_none());
|
||||
assert_eq!(
|
||||
summary.records[0].grouped_effect_row_counts,
|
||||
vec![0, 0, 0, 0]
|
||||
|
|
@ -7682,6 +7746,20 @@ mod tests {
|
|||
.as_deref(),
|
||||
Some("AutoPlant")
|
||||
);
|
||||
let negative_sentinel_scope = summary.records[0]
|
||||
.negative_sentinel_scope
|
||||
.as_ref()
|
||||
.expect("negative-sentinel scope summary should decode");
|
||||
assert_eq!(
|
||||
negative_sentinel_scope.company_test_scope,
|
||||
RuntimeCompanyConditionTestScope::SelectedCompanyOnly
|
||||
);
|
||||
assert_eq!(
|
||||
negative_sentinel_scope.player_test_scope,
|
||||
RuntimePlayerConditionTestScope::AiPlayersOnly
|
||||
);
|
||||
assert!(!negative_sentinel_scope.territory_scope_selector_is_0x63);
|
||||
assert_eq!(negative_sentinel_scope.source_row_indexes, vec![0]);
|
||||
assert_eq!(summary.records[0].grouped_effect_rows.len(), 1);
|
||||
assert_eq!(summary.records[0].grouped_effect_rows[0].opcode, 8);
|
||||
assert_eq!(
|
||||
|
|
@ -7725,6 +7803,63 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decodes_negative_sentinel_scope_modifiers_and_territory_marker() {
|
||||
for (value, expected) in [
|
||||
(0, RuntimeCompanyConditionTestScope::Disabled),
|
||||
(1, RuntimeCompanyConditionTestScope::AllCompanies),
|
||||
(2, RuntimeCompanyConditionTestScope::SelectedCompanyOnly),
|
||||
(3, RuntimeCompanyConditionTestScope::AiCompaniesOnly),
|
||||
(4, RuntimeCompanyConditionTestScope::HumanCompaniesOnly),
|
||||
] {
|
||||
assert_eq!(decode_company_condition_test_scope(value), Some(expected));
|
||||
}
|
||||
for (value, expected) in [
|
||||
(0, RuntimePlayerConditionTestScope::Disabled),
|
||||
(1, RuntimePlayerConditionTestScope::AllPlayers),
|
||||
(2, RuntimePlayerConditionTestScope::SelectedPlayerOnly),
|
||||
(3, RuntimePlayerConditionTestScope::AiPlayersOnly),
|
||||
(4, RuntimePlayerConditionTestScope::HumanPlayersOnly),
|
||||
] {
|
||||
assert_eq!(decode_player_condition_test_scope(value), Some(expected));
|
||||
}
|
||||
|
||||
let rows = vec![SmpLoadedPackedEventConditionRowSummary {
|
||||
row_index: 0,
|
||||
raw_condition_id: -1,
|
||||
subtype: 4,
|
||||
flag_bytes: vec![0x30; 25],
|
||||
candidate_name: Some("AutoPlant".to_string()),
|
||||
notes: vec![],
|
||||
}];
|
||||
let summary = derive_negative_sentinel_scope_summary(
|
||||
&rows,
|
||||
&SmpLoadedPackedEventCompactControlSummary {
|
||||
mode_byte_0x7ef: 6,
|
||||
primary_selector_0x7f0: 0x63,
|
||||
grouped_mode_0x7f4: 2,
|
||||
one_shot_header_0x7f5: 1,
|
||||
modifier_flag_0x7f9: 4,
|
||||
modifier_flag_0x7fa: 2,
|
||||
grouped_target_scope_ordinals_0x7fb: vec![0, 1, 2, 3],
|
||||
grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0],
|
||||
summary_toggle_0x800: 1,
|
||||
grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1],
|
||||
},
|
||||
)
|
||||
.expect("negative sentinel summary should derive");
|
||||
assert_eq!(
|
||||
summary.company_test_scope,
|
||||
RuntimeCompanyConditionTestScope::HumanCompaniesOnly
|
||||
);
|
||||
assert_eq!(
|
||||
summary.player_test_scope,
|
||||
RuntimePlayerConditionTestScope::SelectedPlayerOnly
|
||||
);
|
||||
assert!(summary.territory_scope_selector_is_0x63);
|
||||
assert_eq!(summary.source_row_indexes, vec![0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_real_grouped_row_semantic_families() {
|
||||
let grouped_rows = vec![
|
||||
|
|
|
|||
|
|
@ -495,8 +495,7 @@ fn resolve_company_target_ids(
|
|||
.companies
|
||||
.iter()
|
||||
.filter(|company| {
|
||||
company.active
|
||||
&& company.controller_kind == RuntimeCompanyControllerKind::Human
|
||||
company.active && company.controller_kind == RuntimeCompanyControllerKind::Human
|
||||
})
|
||||
.map(|company| company.company_id)
|
||||
.collect())
|
||||
|
|
@ -532,8 +531,10 @@ fn resolve_company_target_ids(
|
|||
{
|
||||
Ok(vec![selected_company_id])
|
||||
} else {
|
||||
Err("target requires selected_company_id to reference an active company"
|
||||
.to_string())
|
||||
Err(
|
||||
"target requires selected_company_id to reference an active company"
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
RuntimeCompanyTarget::ConditionTrueCompany => {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ pub struct RuntimeSummary {
|
|||
pub packed_event_blocked_missing_selection_context_count: usize,
|
||||
pub packed_event_blocked_missing_company_role_context_count: usize,
|
||||
pub packed_event_blocked_missing_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_compact_control_count: usize,
|
||||
pub packed_event_blocked_unmapped_real_descriptor_count: usize,
|
||||
pub packed_event_blocked_structural_only_count: usize,
|
||||
|
|
@ -123,7 +126,11 @@ impl RuntimeSummary {
|
|||
.clone(),
|
||||
metadata_count: state.metadata.len(),
|
||||
company_count: state.companies.len(),
|
||||
active_company_count: state.companies.iter().filter(|company| company.active).count(),
|
||||
active_company_count: state
|
||||
.companies
|
||||
.iter()
|
||||
.filter(|company| company.active)
|
||||
.count(),
|
||||
packed_event_collection_present: state.packed_event_collection.is_some(),
|
||||
packed_event_record_count: state
|
||||
.packed_event_collection
|
||||
|
|
@ -218,6 +225,48 @@ impl RuntimeSummary {
|
|||
.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_compact_control_count: state
|
||||
.packed_event_collection
|
||||
.as_ref()
|
||||
|
|
@ -333,10 +382,10 @@ mod tests {
|
|||
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: 7,
|
||||
live_record_count: 2,
|
||||
live_entry_ids: vec![3, 7],
|
||||
decoded_record_count: 2,
|
||||
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 {
|
||||
|
|
@ -354,6 +403,7 @@ mod tests {
|
|||
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(),
|
||||
|
|
@ -377,6 +427,7 @@ mod tests {
|
|||
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(),
|
||||
|
|
@ -385,6 +436,80 @@ mod tests {
|
|||
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_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_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_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
import_outcome: Some("blocked_territory_condition_scope".to_string()),
|
||||
notes: Vec::new(),
|
||||
},
|
||||
],
|
||||
}),
|
||||
event_runtime_records: Vec::new(),
|
||||
|
|
@ -394,13 +519,40 @@ mod tests {
|
|||
};
|
||||
|
||||
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_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_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]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue