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`
|
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
|
`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
|
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
|
engine without a parallel packed executor. The first grounded condition-side unlock now exists for
|
||||||
blocked until condition evaluation is grounded, and mixed supported/unsupported real rows stay
|
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
|
parity-only. The PE32 hook remains useful as capture and integration tooling, but it is no longer
|
||||||
the main execution milestone.
|
the main execution milestone.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4440,14 +4440,20 @@ mod tests {
|
||||||
.join("../../fixtures/runtime/packed-event-selective-import-save-slice-fixture.json");
|
.join("../../fixtures/runtime/packed-event-selective-import-save-slice-fixture.json");
|
||||||
let overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
let overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
.join("../../fixtures/runtime/packed-event-selective-import-overlay-fixture.json");
|
.join("../../fixtures/runtime/packed-event-selective-import-overlay-fixture.json");
|
||||||
let symbolic_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
let symbolic_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||||
.join("../../fixtures/runtime/packed-event-symbolic-company-scope-overlay-fixture.json");
|
"../../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"))
|
let deactivate_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
.join("../../fixtures/runtime/packed-event-deactivate-company-overlay-fixture.json");
|
.join("../../fixtures/runtime/packed-event-deactivate-company-overlay-fixture.json");
|
||||||
let track_capacity_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
let track_capacity_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
.join("../../fixtures/runtime/packed-event-track-capacity-overlay-fixture.json");
|
.join("../../fixtures/runtime/packed-event-track-capacity-overlay-fixture.json");
|
||||||
let mixed_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
let mixed_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||||
.join("../../fixtures/runtime/packed-event-mixed-company-descriptor-overlay-fixture.json");
|
"../../fixtures/runtime/packed-event-mixed-company-descriptor-overlay-fixture.json",
|
||||||
|
);
|
||||||
|
|
||||||
run_runtime_summarize_fixture(&parity_fixture)
|
run_runtime_summarize_fixture(&parity_fixture)
|
||||||
.expect("save-slice-backed parity fixture should summarize");
|
.expect("save-slice-backed parity fixture should summarize");
|
||||||
|
|
@ -4457,6 +4463,8 @@ mod tests {
|
||||||
.expect("overlay-backed selective-import fixture should summarize");
|
.expect("overlay-backed selective-import fixture should summarize");
|
||||||
run_runtime_summarize_fixture(&symbolic_overlay_fixture)
|
run_runtime_summarize_fixture(&symbolic_overlay_fixture)
|
||||||
.expect("overlay-backed symbolic-target fixture should summarize");
|
.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)
|
run_runtime_summarize_fixture(&deactivate_overlay_fixture)
|
||||||
.expect("overlay-backed deactivate-company fixture should summarize");
|
.expect("overlay-backed deactivate-company fixture should summarize");
|
||||||
run_runtime_summarize_fixture(&track_capacity_overlay_fixture)
|
run_runtime_summarize_fixture(&track_capacity_overlay_fixture)
|
||||||
|
|
|
||||||
|
|
@ -388,6 +388,7 @@ mod tests {
|
||||||
text_bands: vec![],
|
text_bands: vec![],
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: vec![],
|
standalone_condition_rows: vec![],
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![],
|
grouped_effect_rows: vec![],
|
||||||
decoded_actions: vec![rrt_runtime::RuntimeEffect::AdjustCompanyCash {
|
decoded_actions: vec![rrt_runtime::RuntimeEffect::AdjustCompanyCash {
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,12 @@ pub struct ExpectedRuntimeSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_blocked_missing_condition_context_count: Option<usize>,
|
pub packed_event_blocked_missing_condition_context_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[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>,
|
pub packed_event_blocked_missing_compact_control_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_blocked_unmapped_real_descriptor_count: Option<usize>,
|
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 let Some(count) = self.packed_event_blocked_missing_compact_control_count {
|
||||||
if actual.packed_event_blocked_missing_compact_control_count != count {
|
if actual.packed_event_blocked_missing_compact_control_count != count {
|
||||||
mismatches.push(format!(
|
mismatches.push(format!(
|
||||||
|
|
|
||||||
|
|
@ -247,14 +247,12 @@ impl CompanyFinanceState {
|
||||||
self.current_dividend_per_share = new_rate.clamp(0.0, self.board_dividend_ceiling);
|
self.current_dividend_per_share = new_rate.clamp(0.0, self.board_dividend_ceiling);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_recent_metric(
|
pub fn read_recent_metric(&self, metric: AnnualReportMetric, years_ago: usize) -> Option<f64> {
|
||||||
&self,
|
|
||||||
metric: AnnualReportMetric,
|
|
||||||
years_ago: usize,
|
|
||||||
) -> Option<f64> {
|
|
||||||
match metric {
|
match metric {
|
||||||
AnnualReportMetric::FuelCost if years_ago == 0 => Some(self.current_fuel_cost as f64),
|
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
|
AnnualReportMetric::NetProfits => self
|
||||||
.recent_net_profits
|
.recent_net_profits
|
||||||
.get(years_ago)
|
.get(years_ago)
|
||||||
|
|
@ -278,11 +276,7 @@ impl CompanyFinanceState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_recent_metric_window(
|
pub fn read_recent_metric_window(&self, metric: AnnualReportMetric, years: usize) -> Vec<f64> {
|
||||||
&self,
|
|
||||||
metric: AnnualReportMetric,
|
|
||||||
years: usize,
|
|
||||||
) -> Vec<f64> {
|
|
||||||
(0..years)
|
(0..years)
|
||||||
.filter_map(|years_ago| self.read_recent_metric(metric, years_ago))
|
.filter_map(|years_ago| self.read_recent_metric(metric, years_ago))
|
||||||
.collect()
|
.collect()
|
||||||
|
|
@ -457,7 +451,10 @@ fn should_bankrupt_deep_distress(
|
||||||
&& company.current_cash < -300_000
|
&& company.current_cash < -300_000
|
||||||
&& company.years_since_founding >= 3
|
&& company.years_since_founding >= 3
|
||||||
&& company.years_since_last_bankruptcy >= 5
|
&& 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(
|
fn issue_bond_evaluation(
|
||||||
|
|
@ -552,9 +549,8 @@ fn issue_stock_evaluation(
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut tranche =
|
let mut tranche = ((company.outstanding_share_count / 10) / CompanyFinanceState::SHARE_LOT)
|
||||||
((company.outstanding_share_count / 10) / CompanyFinanceState::SHARE_LOT)
|
* CompanyFinanceState::SHARE_LOT;
|
||||||
* CompanyFinanceState::SHARE_LOT;
|
|
||||||
tranche = tranche.max(2_000);
|
tranche = tranche.max(2_000);
|
||||||
while tranche >= CompanyFinanceState::SHARE_LOT
|
while tranche >= CompanyFinanceState::SHARE_LOT
|
||||||
&& company.support_adjusted_share_price * tranche as f64 > 55_000.0
|
&& 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::persistence::{load_runtime_snapshot_document, validate_runtime_snapshot_document};
|
||||||
use crate::{
|
use crate::{
|
||||||
CalendarPoint, RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect,
|
CalendarPoint, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
||||||
RuntimeEventRecord, RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||||
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary,
|
||||||
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventRecordSummary,
|
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
||||||
RuntimePackedEventTextBandSummary, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
|
RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary,
|
||||||
RuntimeWorldRestoreState, SmpLoadedPackedEventRecordSummary,
|
RuntimePackedEventTextBandSummary, RuntimePlayerConditionTestScope, RuntimeSaveProfileState,
|
||||||
|
RuntimeServiceState, RuntimeState, RuntimeWorldRestoreState,
|
||||||
|
SmpLoadedPackedEventNegativeSentinelScopeSummary, SmpLoadedPackedEventRecordSummary,
|
||||||
SmpLoadedPackedEventTextBandSummary, SmpLoadedSaveSlice,
|
SmpLoadedPackedEventTextBandSummary, SmpLoadedSaveSlice,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -112,6 +114,9 @@ enum CompanyTargetImportBlocker {
|
||||||
MissingSelectionContext,
|
MissingSelectionContext,
|
||||||
MissingCompanyRoleContext,
|
MissingCompanyRoleContext,
|
||||||
MissingConditionContext,
|
MissingConditionContext,
|
||||||
|
CompanyConditionScopeDisabled,
|
||||||
|
PlayerConditionScope,
|
||||||
|
TerritoryConditionScope,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImportCompanyContext {
|
impl ImportCompanyContext {
|
||||||
|
|
@ -592,6 +597,8 @@ fn runtime_packed_event_record_summary_from_smp(
|
||||||
company_context: &ImportCompanyContext,
|
company_context: &ImportCompanyContext,
|
||||||
imported: bool,
|
imported: bool,
|
||||||
) -> RuntimePackedEventRecordSummary {
|
) -> RuntimePackedEventRecordSummary {
|
||||||
|
let lowered_decoded_actions =
|
||||||
|
lowered_record_decoded_actions(record).unwrap_or_else(|_| record.decoded_actions.clone());
|
||||||
RuntimePackedEventRecordSummary {
|
RuntimePackedEventRecordSummary {
|
||||||
record_index: record.record_index,
|
record_index: record.record_index,
|
||||||
live_entry_id: record.live_entry_id,
|
live_entry_id: record.live_entry_id,
|
||||||
|
|
@ -618,6 +625,10 @@ fn runtime_packed_event_record_summary_from_smp(
|
||||||
.iter()
|
.iter()
|
||||||
.map(runtime_packed_event_condition_row_summary_from_smp)
|
.map(runtime_packed_event_condition_row_summary_from_smp)
|
||||||
.collect(),
|
.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_row_counts: record.grouped_effect_row_counts.clone(),
|
||||||
grouped_effect_rows: record
|
grouped_effect_rows: record
|
||||||
.grouped_effect_rows
|
.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)
|
.map(runtime_packed_event_grouped_effect_row_summary_from_smp)
|
||||||
.collect(),
|
.collect(),
|
||||||
grouped_company_targets: classify_real_grouped_company_targets(record),
|
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,
|
executable_import_ready: record.executable_import_ready,
|
||||||
import_outcome: Some(determine_packed_event_import_outcome(
|
import_outcome: Some(determine_packed_event_import_outcome(
|
||||||
record,
|
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(
|
fn runtime_packed_event_compact_control_summary_from_smp(
|
||||||
control: &crate::SmpLoadedPackedEventCompactControlSummary,
|
control: &crate::SmpLoadedPackedEventCompactControlSummary,
|
||||||
) -> RuntimePackedEventCompactControlSummary {
|
) -> RuntimePackedEventCompactControlSummary {
|
||||||
|
|
@ -710,15 +732,20 @@ fn smp_packed_record_to_runtime_event_record(
|
||||||
if record.decode_status == "unsupported_framing" {
|
if record.decode_status == "unsupported_framing" {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if record.payload_family == "real_packed_v1" && !record.executable_import_ready {
|
if record.payload_family == "real_packed_v1" {
|
||||||
return None;
|
if record.compact_control.is_none() || !record.executable_import_ready {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let effects =
|
let lowered_effects = match lowered_record_decoded_actions(record) {
|
||||||
match smp_runtime_effects_to_runtime_effects(&record.decoded_actions, company_context) {
|
Ok(effects) => effects,
|
||||||
Ok(effects) => effects,
|
Err(_) => return None,
|
||||||
Err(_) => return None,
|
};
|
||||||
};
|
let effects = match smp_runtime_effects_to_runtime_effects(&lowered_effects, company_context) {
|
||||||
|
Ok(effects) => effects,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
Some((|| {
|
Some((|| {
|
||||||
let trigger_kind = record.trigger_kind.ok_or_else(|| {
|
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(
|
fn smp_runtime_effects_to_runtime_effects(
|
||||||
effects: &[RuntimeEffect],
|
effects: &[RuntimeEffect],
|
||||||
company_context: &ImportCompanyContext,
|
company_context: &ImportCompanyContext,
|
||||||
|
|
@ -912,6 +1093,18 @@ fn company_target_import_error_message(
|
||||||
Some(CompanyTargetImportBlocker::MissingConditionContext) => {
|
Some(CompanyTargetImportBlocker::MissingConditionContext) => {
|
||||||
"packed company effect requires condition-relative context".to_string()
|
"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(),
|
None => "packed company effect is importable".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -934,6 +1127,9 @@ fn determine_packed_event_import_outcome(
|
||||||
if !record.executable_import_ready {
|
if !record.executable_import_ready {
|
||||||
return "blocked_unmapped_real_descriptor".to_string();
|
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)
|
if let Some(blocker) = packed_record_company_target_import_blocker(record, company_context)
|
||||||
{
|
{
|
||||||
return company_target_import_outcome(blocker).to_string();
|
return company_target_import_outcome(blocker).to_string();
|
||||||
|
|
@ -950,8 +1146,11 @@ fn packed_record_company_target_import_blocker(
|
||||||
record: &SmpLoadedPackedEventRecordSummary,
|
record: &SmpLoadedPackedEventRecordSummary,
|
||||||
company_context: &ImportCompanyContext,
|
company_context: &ImportCompanyContext,
|
||||||
) -> Option<CompanyTargetImportBlocker> {
|
) -> Option<CompanyTargetImportBlocker> {
|
||||||
record
|
let lowered_effects = match lowered_record_decoded_actions(record) {
|
||||||
.decoded_actions
|
Ok(effects) => effects,
|
||||||
|
Err(blocker) => return Some(blocker),
|
||||||
|
};
|
||||||
|
lowered_effects
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|effect| runtime_effect_company_target_import_blocker(effect, company_context))
|
.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"
|
"blocked_missing_company_role_context"
|
||||||
}
|
}
|
||||||
CompanyTargetImportBlocker::MissingConditionContext => "blocked_missing_condition_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(),
|
text_bands: packed_text_bands(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: vec![],
|
standalone_condition_rows: vec![],
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![],
|
grouped_effect_rows: vec![],
|
||||||
decoded_actions: vec![effect],
|
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> {
|
fn real_grouped_rows() -> Vec<crate::SmpLoadedPackedEventGroupedEffectRowSummary> {
|
||||||
vec![crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
vec![crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
group_index: 0,
|
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 {
|
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
group_index: 0,
|
group_index: 0,
|
||||||
row_index: 0,
|
row_index: 0,
|
||||||
|
|
@ -1470,9 +1707,7 @@ mod tests {
|
||||||
value_word_0x16: 0,
|
value_word_0x16: 0,
|
||||||
row_shape: "scalar_assignment".to_string(),
|
row_shape: "scalar_assignment".to_string(),
|
||||||
semantic_family: Some("scalar_assignment".to_string()),
|
semantic_family: Some("scalar_assignment".to_string()),
|
||||||
semantic_preview: Some(format!(
|
semantic_preview: Some(format!("Set Company Track Pieces Buildable to {value}")),
|
||||||
"Set Company Track Pieces Buildable to {value}"
|
|
||||||
)),
|
|
||||||
locomotive_name: None,
|
locomotive_name: None,
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
}
|
}
|
||||||
|
|
@ -1758,6 +1993,7 @@ mod tests {
|
||||||
text_bands: Vec::new(),
|
text_bands: Vec::new(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: Vec::new(),
|
standalone_condition_rows: Vec::new(),
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
|
|
@ -1779,6 +2015,7 @@ mod tests {
|
||||||
text_bands: Vec::new(),
|
text_bands: Vec::new(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: Vec::new(),
|
standalone_condition_rows: Vec::new(),
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
|
|
@ -1800,6 +2037,7 @@ mod tests {
|
||||||
text_bands: Vec::new(),
|
text_bands: Vec::new(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: Vec::new(),
|
standalone_condition_rows: Vec::new(),
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
|
|
@ -2011,6 +2249,7 @@ mod tests {
|
||||||
text_bands: packed_text_bands(),
|
text_bands: packed_text_bands(),
|
||||||
standalone_condition_row_count: 1,
|
standalone_condition_row_count: 1,
|
||||||
standalone_condition_rows: vec![],
|
standalone_condition_rows: vec![],
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 1, 0, 0],
|
grouped_effect_row_counts: vec![0, 1, 0, 0],
|
||||||
grouped_effect_rows: vec![],
|
grouped_effect_rows: vec![],
|
||||||
decoded_actions: vec![
|
decoded_actions: vec![
|
||||||
|
|
@ -2121,6 +2360,7 @@ mod tests {
|
||||||
text_bands: packed_text_bands(),
|
text_bands: packed_text_bands(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: vec![],
|
standalone_condition_rows: vec![],
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![],
|
grouped_effect_rows: vec![],
|
||||||
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
|
|
@ -2395,6 +2635,9 @@ mod tests {
|
||||||
text_bands: packed_text_bands(),
|
text_bands: packed_text_bands(),
|
||||||
standalone_condition_row_count: 1,
|
standalone_condition_row_count: 1,
|
||||||
standalone_condition_rows: real_condition_rows(),
|
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_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: real_grouped_rows(),
|
grouped_effect_rows: real_grouped_rows(),
|
||||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||||
|
|
@ -2443,8 +2686,272 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 {
|
let save_slice = SmpLoadedSaveSlice {
|
||||||
file_extension_hint: Some("gms".to_string()),
|
file_extension_hint: Some("gms".to_string()),
|
||||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||||
|
|
@ -2485,6 +2992,7 @@ mod tests {
|
||||||
text_bands: packed_text_bands(),
|
text_bands: packed_text_bands(),
|
||||||
standalone_condition_row_count: 1,
|
standalone_condition_row_count: 1,
|
||||||
standalone_condition_rows: real_condition_rows(),
|
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_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: real_grouped_rows(),
|
grouped_effect_rows: real_grouped_rows(),
|
||||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||||
|
|
@ -2500,7 +3008,7 @@ mod tests {
|
||||||
|
|
||||||
let import = project_save_slice_to_runtime_state_import(
|
let import = project_save_slice_to_runtime_state_import(
|
||||||
&save_slice,
|
&save_slice,
|
||||||
"packed-events-real-descriptor-frontier",
|
"negative-sentinel-player-scope",
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.expect("save slice should project");
|
.expect("save slice should project");
|
||||||
|
|
@ -2511,17 +3019,82 @@ mod tests {
|
||||||
.state
|
.state
|
||||||
.packed_event_collection
|
.packed_event_collection
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|summary| summary.records[0].compact_control.as_ref())
|
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
|
||||||
.map(|control| control.mode_byte_0x7ef),
|
Some("blocked_player_condition_scope")
|
||||||
Some(6)
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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!(
|
assert_eq!(
|
||||||
import
|
import
|
||||||
.state
|
.state
|
||||||
.packed_event_collection
|
.packed_event_collection
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
|
.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(),
|
text_bands: packed_text_bands(),
|
||||||
standalone_condition_row_count: 1,
|
standalone_condition_row_count: 1,
|
||||||
standalone_condition_rows: real_condition_rows(),
|
standalone_condition_rows: real_condition_rows(),
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: real_grouped_rows(),
|
grouped_effect_rows: real_grouped_rows(),
|
||||||
decoded_actions: vec![],
|
decoded_actions: vec![],
|
||||||
|
|
@ -2674,6 +3248,7 @@ mod tests {
|
||||||
text_bands: packed_text_bands(),
|
text_bands: packed_text_bands(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: vec![],
|
standalone_condition_rows: vec![],
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
grouped_effect_rows: vec![crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
group_index: 0,
|
group_index: 0,
|
||||||
|
|
@ -2806,6 +3381,7 @@ mod tests {
|
||||||
text_bands: packed_text_bands(),
|
text_bands: packed_text_bands(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: vec![],
|
standalone_condition_rows: vec![],
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![real_deactivate_company_row(true)],
|
grouped_effect_rows: vec![real_deactivate_company_row(true)],
|
||||||
decoded_actions: vec![RuntimeEffect::DeactivateCompany {
|
decoded_actions: vec![RuntimeEffect::DeactivateCompany {
|
||||||
|
|
@ -2890,6 +3466,7 @@ mod tests {
|
||||||
text_bands: packed_text_bands(),
|
text_bands: packed_text_bands(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: vec![],
|
standalone_condition_rows: vec![],
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![real_deactivate_company_row(false)],
|
grouped_effect_rows: vec![real_deactivate_company_row(false)],
|
||||||
decoded_actions: vec![],
|
decoded_actions: vec![],
|
||||||
|
|
@ -2983,6 +3560,7 @@ mod tests {
|
||||||
text_bands: packed_text_bands(),
|
text_bands: packed_text_bands(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: vec![],
|
standalone_condition_rows: vec![],
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![real_track_capacity_row(18)],
|
grouped_effect_rows: vec![real_track_capacity_row(18)],
|
||||||
decoded_actions: vec![RuntimeEffect::SetCompanyTrackLayingCapacity {
|
decoded_actions: vec![RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||||
|
|
@ -3081,6 +3659,7 @@ mod tests {
|
||||||
text_bands: packed_text_bands(),
|
text_bands: packed_text_bands(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: vec![],
|
standalone_condition_rows: vec![],
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![1, 1, 0, 0],
|
grouped_effect_row_counts: vec![1, 1, 0, 0],
|
||||||
grouped_effect_rows: vec![
|
grouped_effect_rows: vec![
|
||||||
real_track_capacity_row(18),
|
real_track_capacity_row(18),
|
||||||
|
|
@ -3198,6 +3777,7 @@ mod tests {
|
||||||
text_bands: packed_text_bands(),
|
text_bands: packed_text_bands(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: vec![],
|
standalone_condition_rows: vec![],
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![],
|
grouped_effect_rows: vec![],
|
||||||
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
|
|
@ -3356,6 +3936,7 @@ mod tests {
|
||||||
text_bands: packed_text_bands(),
|
text_bands: packed_text_bands(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: vec![],
|
standalone_condition_rows: vec![],
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![],
|
grouped_effect_rows: vec![],
|
||||||
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
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,
|
extract_pk4_entry_bytes, extract_pk4_entry_file, inspect_pk4_bytes, inspect_pk4_file,
|
||||||
};
|
};
|
||||||
pub use runtime::{
|
pub use runtime::{
|
||||||
RuntimeCompany, RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord,
|
RuntimeCompany, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
||||||
RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||||
RuntimePackedEventCompactControlSummary,
|
RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary,
|
||||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
||||||
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, RuntimeSaveProfileState,
|
RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary,
|
||||||
|
RuntimePackedEventTextBandSummary, RuntimePlayerConditionTestScope, RuntimeSaveProfileState,
|
||||||
RuntimeServiceState, RuntimeState, RuntimeWorldRestoreState,
|
RuntimeServiceState, RuntimeState, RuntimeWorldRestoreState,
|
||||||
};
|
};
|
||||||
pub use smp::{
|
pub use smp::{
|
||||||
|
|
@ -48,8 +49,8 @@ pub use smp::{
|
||||||
SmpClassicRehydrateProfileProbe, SmpContainerProfile, SmpEarlyContentProbe,
|
SmpClassicRehydrateProfileProbe, SmpContainerProfile, SmpEarlyContentProbe,
|
||||||
SmpHeaderVariantProbe, SmpInspectionReport, SmpKnownTagHit,
|
SmpHeaderVariantProbe, SmpInspectionReport, SmpKnownTagHit,
|
||||||
SmpLoadedCandidateAvailabilityTable, SmpLoadedEventRuntimeCollectionSummary,
|
SmpLoadedCandidateAvailabilityTable, SmpLoadedEventRuntimeCollectionSummary,
|
||||||
SmpLoadedPackedEventCompactControlSummary,
|
SmpLoadedPackedEventCompactControlSummary, SmpLoadedPackedEventConditionRowSummary,
|
||||||
SmpLoadedPackedEventConditionRowSummary, SmpLoadedPackedEventGroupedEffectRowSummary,
|
SmpLoadedPackedEventGroupedEffectRowSummary, SmpLoadedPackedEventNegativeSentinelScopeSummary,
|
||||||
SmpLoadedPackedEventRecordSummary, SmpLoadedPackedEventTextBandSummary, SmpLoadedProfile,
|
SmpLoadedPackedEventRecordSummary, SmpLoadedPackedEventTextBandSummary, SmpLoadedProfile,
|
||||||
SmpLoadedSaveSlice, SmpLoadedSpecialConditionsTable, SmpLocomotivePolicyFieldObservation,
|
SmpLoadedSaveSlice, SmpLoadedSpecialConditionsTable, SmpLocomotivePolicyFieldObservation,
|
||||||
SmpLocomotivePolicyFloatAlignmentCandidate, SmpLocomotivePolicyNeighborhoodProbe,
|
SmpLocomotivePolicyFloatAlignmentCandidate, SmpLocomotivePolicyNeighborhoodProbe,
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,28 @@ pub enum RuntimeCompanyTarget {
|
||||||
Ids { ids: Vec<u32> },
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum RuntimeEffect {
|
pub enum RuntimeEffect {
|
||||||
|
|
@ -167,6 +189,8 @@ pub struct RuntimePackedEventRecordSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub standalone_condition_rows: Vec<RuntimePackedEventConditionRowSummary>,
|
pub standalone_condition_rows: Vec<RuntimePackedEventConditionRowSummary>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub negative_sentinel_scope: Option<RuntimePackedEventNegativeSentinelScopeSummary>,
|
||||||
|
#[serde(default)]
|
||||||
pub grouped_effect_row_counts: Vec<usize>,
|
pub grouped_effect_row_counts: Vec<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub grouped_effect_rows: Vec<RuntimePackedEventGroupedEffectRowSummary>,
|
pub grouped_effect_rows: Vec<RuntimePackedEventGroupedEffectRowSummary>,
|
||||||
|
|
@ -182,6 +206,15 @@ pub struct RuntimePackedEventRecordSummary {
|
||||||
pub notes: Vec<String>,
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct RuntimePackedEventCompactControlSummary {
|
pub struct RuntimePackedEventCompactControlSummary {
|
||||||
pub mode_byte_0x7ef: u8,
|
pub mode_byte_0x7ef: u8,
|
||||||
|
|
@ -994,6 +1027,7 @@ mod tests {
|
||||||
text_bands: Vec::new(),
|
text_bands: Vec::new(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: Vec::new(),
|
standalone_condition_rows: Vec::new(),
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
grouped_company_targets: Vec::new(),
|
grouped_company_targets: Vec::new(),
|
||||||
|
|
@ -1017,6 +1051,7 @@ mod tests {
|
||||||
text_bands: Vec::new(),
|
text_bands: Vec::new(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: Vec::new(),
|
standalone_condition_rows: Vec::new(),
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
grouped_company_targets: Vec::new(),
|
grouped_company_targets: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@ use std::path::Path;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, Sha256};
|
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;
|
pub const SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION: u32 = 0x03ec;
|
||||||
const PREAMBLE_U32_WORD_COUNT: usize = 16;
|
const PREAMBLE_U32_WORD_COUNT: usize = 16;
|
||||||
|
|
@ -1312,6 +1315,8 @@ pub struct SmpLoadedPackedEventRecordSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub standalone_condition_rows: Vec<SmpLoadedPackedEventConditionRowSummary>,
|
pub standalone_condition_rows: Vec<SmpLoadedPackedEventConditionRowSummary>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub negative_sentinel_scope: Option<SmpLoadedPackedEventNegativeSentinelScopeSummary>,
|
||||||
|
#[serde(default)]
|
||||||
pub grouped_effect_row_counts: Vec<usize>,
|
pub grouped_effect_row_counts: Vec<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub grouped_effect_rows: Vec<SmpLoadedPackedEventGroupedEffectRowSummary>,
|
pub grouped_effect_rows: Vec<SmpLoadedPackedEventGroupedEffectRowSummary>,
|
||||||
|
|
@ -1323,6 +1328,15 @@ pub struct SmpLoadedPackedEventRecordSummary {
|
||||||
pub notes: Vec<String>,
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct SmpLoadedPackedEventCompactControlSummary {
|
pub struct SmpLoadedPackedEventCompactControlSummary {
|
||||||
pub mode_byte_0x7ef: u8,
|
pub mode_byte_0x7ef: u8,
|
||||||
|
|
@ -1836,6 +1850,7 @@ fn parse_synthetic_event_runtime_record_summary(
|
||||||
text_bands,
|
text_bands,
|
||||||
standalone_condition_row_count,
|
standalone_condition_row_count,
|
||||||
standalone_condition_rows: Vec::new(),
|
standalone_condition_rows: Vec::new(),
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts,
|
grouped_effect_row_counts,
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
decoded_actions,
|
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
|
let decoded_actions = compact_control
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|control| decode_real_grouped_effect_actions(&grouped_effect_rows, control))
|
.map(|control| decode_real_grouped_effect_actions(&grouped_effect_rows, control))
|
||||||
|
|
@ -1968,6 +1986,7 @@ fn parse_real_event_runtime_record_summary(
|
||||||
text_bands,
|
text_bands,
|
||||||
standalone_condition_row_count,
|
standalone_condition_row_count,
|
||||||
standalone_condition_rows,
|
standalone_condition_rows,
|
||||||
|
negative_sentinel_scope,
|
||||||
grouped_effect_row_counts,
|
grouped_effect_row_counts,
|
||||||
grouped_effect_rows,
|
grouped_effect_rows,
|
||||||
decoded_actions,
|
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(
|
fn parse_real_grouped_effect_row_summary(
|
||||||
row_bytes: &[u8],
|
row_bytes: &[u8],
|
||||||
group_index: usize,
|
group_index: usize,
|
||||||
|
|
@ -2484,6 +2546,7 @@ fn build_unsupported_event_runtime_record_summaries(
|
||||||
text_bands: Vec::new(),
|
text_bands: Vec::new(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: Vec::new(),
|
standalone_condition_rows: Vec::new(),
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
decoded_actions: 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].text_bands[0].preview, "Alpha");
|
||||||
assert_eq!(summary.records[0].standalone_condition_row_count, 0);
|
assert_eq!(summary.records[0].standalone_condition_row_count, 0);
|
||||||
assert_eq!(summary.records[0].standalone_condition_rows.len(), 0);
|
assert_eq!(summary.records[0].standalone_condition_rows.len(), 0);
|
||||||
|
assert!(summary.records[0].negative_sentinel_scope.is_none());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
summary.records[0].grouped_effect_row_counts,
|
summary.records[0].grouped_effect_row_counts,
|
||||||
vec![0, 0, 0, 0]
|
vec![0, 0, 0, 0]
|
||||||
|
|
@ -7682,6 +7746,20 @@ mod tests {
|
||||||
.as_deref(),
|
.as_deref(),
|
||||||
Some("AutoPlant")
|
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.len(), 1);
|
||||||
assert_eq!(summary.records[0].grouped_effect_rows[0].opcode, 8);
|
assert_eq!(summary.records[0].grouped_effect_rows[0].opcode, 8);
|
||||||
assert_eq!(
|
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]
|
#[test]
|
||||||
fn classifies_real_grouped_row_semantic_families() {
|
fn classifies_real_grouped_row_semantic_families() {
|
||||||
let grouped_rows = vec![
|
let grouped_rows = vec![
|
||||||
|
|
|
||||||
|
|
@ -495,8 +495,7 @@ fn resolve_company_target_ids(
|
||||||
.companies
|
.companies
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|company| {
|
.filter(|company| {
|
||||||
company.active
|
company.active && company.controller_kind == RuntimeCompanyControllerKind::Human
|
||||||
&& company.controller_kind == RuntimeCompanyControllerKind::Human
|
|
||||||
})
|
})
|
||||||
.map(|company| company.company_id)
|
.map(|company| company.company_id)
|
||||||
.collect())
|
.collect())
|
||||||
|
|
@ -532,8 +531,10 @@ fn resolve_company_target_ids(
|
||||||
{
|
{
|
||||||
Ok(vec![selected_company_id])
|
Ok(vec![selected_company_id])
|
||||||
} else {
|
} else {
|
||||||
Err("target requires selected_company_id to reference an active company"
|
Err(
|
||||||
.to_string())
|
"target requires selected_company_id to reference an active company"
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeCompanyTarget::ConditionTrueCompany => {
|
RuntimeCompanyTarget::ConditionTrueCompany => {
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,9 @@ pub struct RuntimeSummary {
|
||||||
pub packed_event_blocked_missing_selection_context_count: usize,
|
pub packed_event_blocked_missing_selection_context_count: usize,
|
||||||
pub packed_event_blocked_missing_company_role_context_count: usize,
|
pub packed_event_blocked_missing_company_role_context_count: usize,
|
||||||
pub packed_event_blocked_missing_condition_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_missing_compact_control_count: usize,
|
||||||
pub packed_event_blocked_unmapped_real_descriptor_count: usize,
|
pub packed_event_blocked_unmapped_real_descriptor_count: usize,
|
||||||
pub packed_event_blocked_structural_only_count: usize,
|
pub packed_event_blocked_structural_only_count: usize,
|
||||||
|
|
@ -123,7 +126,11 @@ impl RuntimeSummary {
|
||||||
.clone(),
|
.clone(),
|
||||||
metadata_count: state.metadata.len(),
|
metadata_count: state.metadata.len(),
|
||||||
company_count: state.companies.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_collection_present: state.packed_event_collection.is_some(),
|
||||||
packed_event_record_count: state
|
packed_event_record_count: state
|
||||||
.packed_event_collection
|
.packed_event_collection
|
||||||
|
|
@ -218,6 +225,48 @@ impl RuntimeSummary {
|
||||||
.count()
|
.count()
|
||||||
})
|
})
|
||||||
.unwrap_or(0),
|
.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_blocked_missing_compact_control_count: state
|
||||||
.packed_event_collection
|
.packed_event_collection
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -333,10 +382,10 @@ mod tests {
|
||||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||||
packed_state_version: 0x3e9,
|
packed_state_version: 0x3e9,
|
||||||
packed_state_version_hex: "0x000003e9".to_string(),
|
packed_state_version_hex: "0x000003e9".to_string(),
|
||||||
live_id_bound: 7,
|
live_id_bound: 11,
|
||||||
live_record_count: 2,
|
live_record_count: 5,
|
||||||
live_entry_ids: vec![3, 7],
|
live_entry_ids: vec![3, 7, 9, 10, 11],
|
||||||
decoded_record_count: 2,
|
decoded_record_count: 5,
|
||||||
imported_runtime_record_count: 0,
|
imported_runtime_record_count: 0,
|
||||||
records: vec![
|
records: vec![
|
||||||
RuntimePackedEventRecordSummary {
|
RuntimePackedEventRecordSummary {
|
||||||
|
|
@ -354,6 +403,7 @@ mod tests {
|
||||||
text_bands: Vec::new(),
|
text_bands: Vec::new(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: Vec::new(),
|
standalone_condition_rows: Vec::new(),
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
grouped_company_targets: Vec::new(),
|
grouped_company_targets: Vec::new(),
|
||||||
|
|
@ -377,6 +427,7 @@ mod tests {
|
||||||
text_bands: Vec::new(),
|
text_bands: Vec::new(),
|
||||||
standalone_condition_row_count: 0,
|
standalone_condition_row_count: 0,
|
||||||
standalone_condition_rows: Vec::new(),
|
standalone_condition_rows: Vec::new(),
|
||||||
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
grouped_company_targets: Vec::new(),
|
grouped_company_targets: Vec::new(),
|
||||||
|
|
@ -385,6 +436,80 @@ mod tests {
|
||||||
import_outcome: Some("blocked_missing_company_context".to_string()),
|
import_outcome: Some("blocked_missing_company_context".to_string()),
|
||||||
notes: Vec::new(),
|
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(),
|
event_runtime_records: Vec::new(),
|
||||||
|
|
@ -394,13 +519,40 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
let summary = RuntimeSummary::from_state(&state);
|
let summary = RuntimeSummary::from_state(&state);
|
||||||
assert_eq!(summary.packed_event_blocked_missing_compact_control_count, 1);
|
assert_eq!(
|
||||||
assert_eq!(summary.packed_event_blocked_unmapped_real_descriptor_count, 0);
|
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_structural_only_count, 0);
|
||||||
assert_eq!(summary.packed_event_blocked_missing_company_context_count, 1);
|
assert_eq!(
|
||||||
assert_eq!(summary.packed_event_blocked_missing_selection_context_count, 0);
|
summary.packed_event_blocked_missing_company_context_count,
|
||||||
assert_eq!(summary.packed_event_blocked_missing_company_role_context_count, 0);
|
1
|
||||||
assert_eq!(summary.packed_event_blocked_missing_condition_context_count, 0);
|
);
|
||||||
|
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]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,9 @@ The highest-value next passes are now:
|
||||||
descriptor `13` `Deactivate Company`, and descriptor `16` `Company Track Pieces Buildable`
|
descriptor `13` `Deactivate Company`, and descriptor `16` `Company Track Pieces Buildable`
|
||||||
- widen real packed-event executable coverage descriptor by descriptor after identity, target mask,
|
- 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
|
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
|
- the first grounded condition-side unlock now exists for negative-sentinel `raw_condition_id = -1`
|
||||||
grounded runtime semantics, and keep mixed supported/unsupported real rows parity-only
|
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,
|
- 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
|
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
|
- 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
|
- 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`,
|
summaries, and three recovered executable company-scoped families: descriptor `2` = `Company Cash`,
|
||||||
descriptor `13` = `Deactivate Company`, and descriptor `16` = `Company Track Pieces Buildable`
|
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
|
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
|
ordinary nonnegative condition-id semantics plus runtime ownership for the still-blocked player and
|
||||||
condition-relative execution for the still-blocked symbolic scopes, not another persistence
|
territory scope families, alongside broader real grouped-descriptor coverage beyond the current
|
||||||
scaffold pass.
|
company-scoped batch.
|
||||||
|
|
||||||
## Why This Boundary
|
## Why This Boundary
|
||||||
|
|
||||||
|
|
@ -232,8 +236,8 @@ Current status:
|
||||||
raw `.smp` binaries
|
raw `.smp` binaries
|
||||||
- overlay-backed captured-runtime inputs now provide enough runtime company context for symbolic
|
- 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
|
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,
|
- the remaining gap is wider real grouped-descriptor semantic coverage plus ordinary condition-id
|
||||||
not first-pass captured-runtime plumbing
|
evaluation and player/territory runtime ownership, not first-pass captured-runtime plumbing
|
||||||
|
|
||||||
### Milestone 4: Domain Expansion
|
### 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_imported_runtime_record_count": 0,
|
||||||
"packed_event_parity_only_record_count": 1,
|
"packed_event_parity_only_record_count": 1,
|
||||||
"packed_event_unsupported_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_missing_compact_control_count": 0,
|
||||||
"packed_event_blocked_unmapped_real_descriptor_count": 0,
|
"packed_event_blocked_unmapped_real_descriptor_count": 0,
|
||||||
"packed_event_blocked_structural_only_count": 0,
|
"packed_event_blocked_structural_only_count": 0,
|
||||||
|
|
@ -52,11 +53,17 @@
|
||||||
"payload_family": "real_packed_v1",
|
"payload_family": "real_packed_v1",
|
||||||
"trigger_kind": 6,
|
"trigger_kind": 6,
|
||||||
"one_shot": true,
|
"one_shot": true,
|
||||||
"import_outcome": "blocked_missing_condition_context",
|
"import_outcome": "blocked_territory_condition_scope",
|
||||||
"compact_control": {
|
"compact_control": {
|
||||||
"primary_selector_0x7f0": 99,
|
"primary_selector_0x7f0": 99,
|
||||||
"grouped_target_scope_ordinals_0x7fb": [0, 1, 2, 3]
|
"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": [
|
"grouped_company_targets": [
|
||||||
{
|
{
|
||||||
"kind": "condition_true_company"
|
"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_row_counts": [1, 0, 0, 0],
|
||||||
"grouped_effect_rows": [
|
"grouped_effect_rows": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue