Unlock negative-sentinel company condition scopes
This commit is contained in:
parent
780e739daa
commit
087ebf1097
18 changed files with 1315 additions and 79 deletions
|
|
@ -16,8 +16,9 @@ overlay-import, compact-control, and symbolic company-target workflows. The runt
|
|||
selected-company and controller-role context through overlay imports, and real descriptors `2`
|
||||
`Company Cash`, `13` `Deactivate Company`, and `16` `Company Track Pieces Buildable` now parse and
|
||||
execute through the ordinary runtime path. Synthetic packed records still exercise the same service
|
||||
engine without a parallel packed executor. Condition-relative company scopes remain explicitly
|
||||
blocked until condition evaluation is grounded, and mixed supported/unsupported real rows stay
|
||||
engine without a parallel packed executor. The first grounded condition-side unlock now exists for
|
||||
negative-sentinel `raw_condition_id = -1` company scopes, while ordinary condition-id semantics and
|
||||
player/territory runtime ownership remain blocked. Mixed supported/unsupported real rows still stay
|
||||
parity-only. The PE32 hook remains useful as capture and integration tooling, but it is no longer
|
||||
the main execution milestone.
|
||||
|
||||
|
|
|
|||
|
|
@ -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,8 +549,7 @@ fn issue_stock_evaluation(
|
|||
return None;
|
||||
}
|
||||
|
||||
let mut tranche =
|
||||
((company.outstanding_share_count / 10) / 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
|
||||
|
|
|
|||
|
|
@ -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,12 +732,17 @@ 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 {
|
||||
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) {
|
||||
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,
|
||||
};
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -83,8 +83,9 @@ The highest-value next passes are now:
|
|||
descriptor `13` `Deactivate Company`, and descriptor `16` `Company Track Pieces Buildable`
|
||||
- widen real packed-event executable coverage descriptor by descriptor after identity, target mask,
|
||||
and normalized effect semantics are all grounded, not just after row framing is parsed
|
||||
- leave condition-relative company scopes explicit and blocked until condition evaluation has
|
||||
grounded runtime semantics, and keep mixed supported/unsupported real rows parity-only
|
||||
- the first grounded condition-side unlock now exists for negative-sentinel `raw_condition_id = -1`
|
||||
company scopes; broader ordinary condition-id evaluation and player/territory runtime ownership
|
||||
are the remaining condition frontier, and mixed supported/unsupported real rows stay parity-only
|
||||
- keep in mind that the current local `.gms` corpus still exports with no packed event collection,
|
||||
so real descriptor mapping needs to stay plumbing-first until better captures exist
|
||||
- use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution
|
||||
|
|
|
|||
|
|
@ -31,11 +31,15 @@ Implemented today:
|
|||
- real `0x4e9a` grouped rows now carry checked-in descriptor metadata, semantic family/preview
|
||||
summaries, and three recovered executable company-scoped families: descriptor `2` = `Company Cash`,
|
||||
descriptor `13` = `Deactivate Company`, and descriptor `16` = `Company Track Pieces Buildable`
|
||||
- the first grounded condition-side unlock now exists for real packed rows: negative-sentinel
|
||||
`raw_condition_id = -1` company scope lowers `condition_true_company` into normalized company
|
||||
targets during import, while player and territory scope variants remain parity-visible and
|
||||
explicitly blocked
|
||||
|
||||
That means the next implementation work is breadth, not bootstrap. The recommended next slice is
|
||||
broader real grouped-descriptor coverage beyond the current company-scoped batch, plus
|
||||
condition-relative execution for the still-blocked symbolic scopes, not another persistence
|
||||
scaffold pass.
|
||||
ordinary nonnegative condition-id semantics plus runtime ownership for the still-blocked player and
|
||||
territory scope families, alongside broader real grouped-descriptor coverage beyond the current
|
||||
company-scoped batch.
|
||||
|
||||
## Why This Boundary
|
||||
|
||||
|
|
@ -232,8 +236,8 @@ Current status:
|
|||
raw `.smp` binaries
|
||||
- overlay-backed captured-runtime inputs now provide enough runtime company context for symbolic
|
||||
selected-company and controller-role scopes without inventing company state from save bytes alone
|
||||
- the remaining gap is wider real grouped-descriptor semantic coverage plus condition evaluation,
|
||||
not first-pass captured-runtime plumbing
|
||||
- the remaining gap is wider real grouped-descriptor semantic coverage plus ordinary condition-id
|
||||
evaluation and player/territory runtime ownership, not first-pass captured-runtime plumbing
|
||||
|
||||
### Milestone 4: Domain Expansion
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"fixture_id": "packed-event-negative-company-scope-overlay-fixture",
|
||||
"source": {
|
||||
"kind": "captured-runtime",
|
||||
"description": "Fixture backed by an overlay import document so the first real negative-sentinel company-scope row executes against selected-company context."
|
||||
},
|
||||
"state_import_path": "packed-event-negative-company-scope-overlay.json",
|
||||
"commands": [
|
||||
{
|
||||
"kind": "service_trigger_kind",
|
||||
"trigger_kind": 6
|
||||
}
|
||||
],
|
||||
"expected_summary": {
|
||||
"calendar_projection_source": "base-snapshot-preserved",
|
||||
"calendar_projection_is_placeholder": false,
|
||||
"company_count": 3,
|
||||
"packed_event_collection_present": true,
|
||||
"packed_event_record_count": 1,
|
||||
"packed_event_decoded_record_count": 1,
|
||||
"packed_event_imported_runtime_record_count": 1,
|
||||
"packed_event_parity_only_record_count": 1,
|
||||
"packed_event_unsupported_record_count": 0,
|
||||
"packed_event_blocked_missing_condition_context_count": 0,
|
||||
"packed_event_blocked_company_condition_scope_disabled_count": 0,
|
||||
"packed_event_blocked_player_condition_scope_count": 0,
|
||||
"packed_event_blocked_territory_condition_scope_count": 0,
|
||||
"event_runtime_record_count": 1,
|
||||
"total_event_record_service_count": 1,
|
||||
"total_trigger_dispatch_count": 1,
|
||||
"dirty_rerun_count": 0,
|
||||
"total_company_cash": 400
|
||||
},
|
||||
"expected_state_fragment": {
|
||||
"selected_company_id": 3,
|
||||
"companies": [
|
||||
{
|
||||
"company_id": 1,
|
||||
"controller_kind": "human",
|
||||
"current_cash": 100,
|
||||
"debt": 10
|
||||
},
|
||||
{
|
||||
"company_id": 2,
|
||||
"controller_kind": "ai",
|
||||
"current_cash": 50,
|
||||
"debt": 20
|
||||
},
|
||||
{
|
||||
"company_id": 3,
|
||||
"controller_kind": "human",
|
||||
"current_cash": 250,
|
||||
"debt": 30
|
||||
}
|
||||
],
|
||||
"packed_event_collection": {
|
||||
"live_entry_ids": [9],
|
||||
"records": [
|
||||
{
|
||||
"import_outcome": "imported",
|
||||
"negative_sentinel_scope": {
|
||||
"company_test_scope": "selected_company_only",
|
||||
"player_test_scope": "disabled",
|
||||
"territory_scope_selector_is_0x63": false,
|
||||
"source_row_indexes": [0]
|
||||
},
|
||||
"decoded_actions": [
|
||||
{
|
||||
"kind": "set_company_cash",
|
||||
"target": {
|
||||
"kind": "selected_company"
|
||||
},
|
||||
"value": 250
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"event_runtime_records": [
|
||||
{
|
||||
"record_id": 9,
|
||||
"service_count": 1,
|
||||
"effects": [
|
||||
{
|
||||
"kind": "set_company_cash",
|
||||
"target": {
|
||||
"kind": "selected_company"
|
||||
},
|
||||
"value": 250
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"import_id": "packed-event-negative-company-scope-overlay",
|
||||
"source": {
|
||||
"description": "Overlay import that combines selected-company snapshot context with a real negative-sentinel company-scope packed row.",
|
||||
"notes": [
|
||||
"used to prove that the first real negative-sentinel company scope imports through the ordinary runtime path"
|
||||
]
|
||||
},
|
||||
"base_snapshot_path": "packed-event-symbolic-company-scope-overlay-base-snapshot.json",
|
||||
"save_slice_path": "packed-event-negative-company-scope-save-slice.json"
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"save_slice_id": "packed-event-negative-company-scope-save-slice",
|
||||
"source": {
|
||||
"description": "Tracked save-slice document with a real packed Company Cash row unlocked by negative-sentinel company scope.",
|
||||
"original_save_filename": "captured-negative-company-scope.gms",
|
||||
"original_save_sha256": "negative-company-scope-sample-sha256",
|
||||
"notes": [
|
||||
"tracked as JSON save-slice document rather than raw .smp",
|
||||
"proves the first executable real negative-sentinel company-scope path"
|
||||
]
|
||||
},
|
||||
"save_slice": {
|
||||
"file_extension_hint": "gms",
|
||||
"container_profile_family": "rt3-classic-save-container-v1",
|
||||
"mechanism_family": "classic-save-rehydrate-v1",
|
||||
"mechanism_confidence": "grounded",
|
||||
"trailer_family": null,
|
||||
"bridge_family": null,
|
||||
"profile": null,
|
||||
"candidate_availability_table": null,
|
||||
"special_conditions_table": null,
|
||||
"event_runtime_collection": {
|
||||
"source_kind": "packed-event-runtime-collection",
|
||||
"mechanism_family": "classic-save-rehydrate-v1",
|
||||
"mechanism_confidence": "grounded",
|
||||
"container_profile_family": "rt3-classic-save-container-v1",
|
||||
"metadata_tag_offset": 28928,
|
||||
"records_tag_offset": 29184,
|
||||
"close_tag_offset": 29696,
|
||||
"packed_state_version": 1001,
|
||||
"packed_state_version_hex": "0x000003e9",
|
||||
"live_id_bound": 9,
|
||||
"live_record_count": 1,
|
||||
"live_entry_ids": [9],
|
||||
"decoded_record_count": 1,
|
||||
"imported_runtime_record_count": 1,
|
||||
"records": [
|
||||
{
|
||||
"record_index": 0,
|
||||
"live_entry_id": 9,
|
||||
"payload_offset": 29290,
|
||||
"payload_len": 109,
|
||||
"decode_status": "parity_only",
|
||||
"payload_family": "real_packed_v1",
|
||||
"trigger_kind": 6,
|
||||
"one_shot": false,
|
||||
"compact_control": {
|
||||
"mode_byte_0x7ef": 6,
|
||||
"primary_selector_0x7f0": 42,
|
||||
"grouped_mode_0x7f4": 2,
|
||||
"one_shot_header_0x7f5": 0,
|
||||
"modifier_flag_0x7f9": 2,
|
||||
"modifier_flag_0x7fa": 0,
|
||||
"grouped_target_scope_ordinals_0x7fb": [0, 1, 2, 3],
|
||||
"grouped_scope_checkboxes_0x7ff": [1, 0, 1, 0],
|
||||
"summary_toggle_0x800": 1,
|
||||
"grouped_territory_selectors_0x80f": [-1, 10, -1, 22]
|
||||
},
|
||||
"text_bands": [
|
||||
{
|
||||
"label": "primary_text_band",
|
||||
"packed_len": 8,
|
||||
"present": true,
|
||||
"preview": "Resolve!"
|
||||
},
|
||||
{
|
||||
"label": "secondary_text_band_0",
|
||||
"packed_len": 0,
|
||||
"present": false,
|
||||
"preview": ""
|
||||
},
|
||||
{
|
||||
"label": "secondary_text_band_1",
|
||||
"packed_len": 0,
|
||||
"present": false,
|
||||
"preview": ""
|
||||
},
|
||||
{
|
||||
"label": "secondary_text_band_2",
|
||||
"packed_len": 0,
|
||||
"present": false,
|
||||
"preview": ""
|
||||
},
|
||||
{
|
||||
"label": "secondary_text_band_3",
|
||||
"packed_len": 0,
|
||||
"present": false,
|
||||
"preview": ""
|
||||
},
|
||||
{
|
||||
"label": "secondary_text_band_4",
|
||||
"packed_len": 0,
|
||||
"present": false,
|
||||
"preview": ""
|
||||
}
|
||||
],
|
||||
"standalone_condition_row_count": 1,
|
||||
"standalone_condition_rows": [
|
||||
{
|
||||
"row_index": 0,
|
||||
"raw_condition_id": -1,
|
||||
"subtype": 4,
|
||||
"flag_bytes": [
|
||||
48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
|
||||
58, 59, 60, 61, 62, 63, 64, 65, 66, 67,
|
||||
68, 69, 70, 71, 72
|
||||
],
|
||||
"candidate_name": "AutoPlant",
|
||||
"notes": [
|
||||
"negative sentinel-style condition row id",
|
||||
"condition row carries candidate-name side string"
|
||||
]
|
||||
}
|
||||
],
|
||||
"negative_sentinel_scope": {
|
||||
"company_test_scope": "selected_company_only",
|
||||
"player_test_scope": "disabled",
|
||||
"territory_scope_selector_is_0x63": false,
|
||||
"source_row_indexes": [0]
|
||||
},
|
||||
"grouped_effect_row_counts": [1, 0, 0, 0],
|
||||
"grouped_effect_rows": [
|
||||
{
|
||||
"group_index": 0,
|
||||
"row_index": 0,
|
||||
"descriptor_id": 2,
|
||||
"descriptor_label": "Company Cash",
|
||||
"target_mask_bits": 1,
|
||||
"parameter_family": "company_finance_scalar",
|
||||
"opcode": 8,
|
||||
"raw_scalar_value": 250,
|
||||
"value_byte_0x09": 1,
|
||||
"value_dword_0x0d": 12,
|
||||
"value_byte_0x11": 2,
|
||||
"value_byte_0x12": 3,
|
||||
"value_word_0x14": 24,
|
||||
"value_word_0x16": 36,
|
||||
"row_shape": "multivalue_scalar",
|
||||
"semantic_family": "multivalue_scalar",
|
||||
"semantic_preview": "Set Company Cash to 250 with aux [2, 3, 24, 36]",
|
||||
"locomotive_name": "Mikado",
|
||||
"notes": [
|
||||
"grouped effect row carries locomotive-name side string"
|
||||
]
|
||||
}
|
||||
],
|
||||
"decoded_actions": [
|
||||
{
|
||||
"kind": "set_company_cash",
|
||||
"target": {
|
||||
"kind": "condition_true_company"
|
||||
},
|
||||
"value": 250
|
||||
}
|
||||
],
|
||||
"executable_import_ready": true,
|
||||
"notes": [
|
||||
"decoded from grounded real 0x4e9a row framing",
|
||||
"negative-sentinel company scope lowers the condition-relative target at import time"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"notes": [
|
||||
"real negative-sentinel company-scope sample"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,8 @@
|
|||
"packed_event_imported_runtime_record_count": 0,
|
||||
"packed_event_parity_only_record_count": 1,
|
||||
"packed_event_unsupported_record_count": 1,
|
||||
"packed_event_blocked_missing_condition_context_count": 1,
|
||||
"packed_event_blocked_missing_condition_context_count": 0,
|
||||
"packed_event_blocked_territory_condition_scope_count": 1,
|
||||
"packed_event_blocked_missing_compact_control_count": 0,
|
||||
"packed_event_blocked_unmapped_real_descriptor_count": 0,
|
||||
"packed_event_blocked_structural_only_count": 0,
|
||||
|
|
@ -52,11 +53,17 @@
|
|||
"payload_family": "real_packed_v1",
|
||||
"trigger_kind": 6,
|
||||
"one_shot": true,
|
||||
"import_outcome": "blocked_missing_condition_context",
|
||||
"import_outcome": "blocked_territory_condition_scope",
|
||||
"compact_control": {
|
||||
"primary_selector_0x7f0": 99,
|
||||
"grouped_target_scope_ordinals_0x7fb": [0, 1, 2, 3]
|
||||
},
|
||||
"negative_sentinel_scope": {
|
||||
"company_test_scope": "all_companies",
|
||||
"player_test_scope": "disabled",
|
||||
"territory_scope_selector_is_0x63": true,
|
||||
"source_row_indexes": [0]
|
||||
},
|
||||
"grouped_company_targets": [
|
||||
{
|
||||
"kind": "condition_true_company"
|
||||
|
|
|
|||
|
|
@ -127,6 +127,12 @@
|
|||
]
|
||||
}
|
||||
],
|
||||
"negative_sentinel_scope": {
|
||||
"company_test_scope": "all_companies",
|
||||
"player_test_scope": "disabled",
|
||||
"territory_scope_selector_is_0x63": true,
|
||||
"source_row_indexes": [0]
|
||||
},
|
||||
"grouped_effect_row_counts": [1, 0, 0, 0],
|
||||
"grouped_effect_rows": [
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue