Add chairman packed event runtime support
This commit is contained in:
parent
377de45631
commit
86cf89b26c
23 changed files with 1431 additions and 41 deletions
|
|
@ -18,6 +18,9 @@ selected-company and controller-role context through overlay imports, and real d
|
||||||
execute through the ordinary runtime path, and descriptors `1` `Player Cash` and `14`
|
execute through the ordinary runtime path, and descriptors `1` `Player Cash` and `14`
|
||||||
`Deactivate Player` now join that batch through the same service engine. Synthetic packed records
|
`Deactivate Player` now join that batch through the same service engine. Synthetic packed records
|
||||||
still exercise the same runtime without a parallel packed executor. The first grounded
|
still exercise the same runtime without a parallel packed executor. The first grounded
|
||||||
|
chairman-profile runtime slice now exists too: overlay-backed selected-chairman context plus the
|
||||||
|
hidden grouped target-subject lane let those same real descriptors `1` and `14` execute on
|
||||||
|
selected-chairman scope, while wider chairman target scopes remain explicit parity. The first grounded
|
||||||
condition-side unlock now exists for negative-sentinel `raw_condition_id = -1` company scopes, and
|
condition-side unlock now exists for negative-sentinel `raw_condition_id = -1` company scopes, and
|
||||||
the first ordinary nonnegative condition batch now executes too: numeric-threshold company
|
the first ordinary nonnegative condition batch now executes too: numeric-threshold company
|
||||||
finance, company track, aggregate territory track, and company-territory track rows can import
|
finance, company track, aggregate territory track, and company-territory track rows can import
|
||||||
|
|
|
||||||
|
|
@ -4485,6 +4485,16 @@ mod tests {
|
||||||
);
|
);
|
||||||
let cargo_catalog_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
let cargo_catalog_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
.join("../../fixtures/runtime/packed-event-cargo-catalog-save-slice-fixture.json");
|
.join("../../fixtures/runtime/packed-event-cargo-catalog-save-slice-fixture.json");
|
||||||
|
let chairman_cash_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("../../fixtures/runtime/packed-event-chairman-cash-overlay-fixture.json");
|
||||||
|
let deactivate_chairman_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("../../fixtures/runtime/packed-event-deactivate-chairman-overlay-fixture.json");
|
||||||
|
let missing_chairman_context_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||||
|
"../../fixtures/runtime/packed-event-chairman-missing-context-save-slice-fixture.json",
|
||||||
|
);
|
||||||
|
let chairman_scope_parity_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||||
|
"../../fixtures/runtime/packed-event-chairman-scope-parity-save-slice-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");
|
||||||
|
|
@ -4526,6 +4536,14 @@ mod tests {
|
||||||
.expect("save-slice-backed parity world-scalar condition fixture should summarize");
|
.expect("save-slice-backed parity world-scalar condition fixture should summarize");
|
||||||
run_runtime_summarize_fixture(&cargo_catalog_fixture)
|
run_runtime_summarize_fixture(&cargo_catalog_fixture)
|
||||||
.expect("save-slice-backed cargo catalog fixture should summarize");
|
.expect("save-slice-backed cargo catalog fixture should summarize");
|
||||||
|
run_runtime_summarize_fixture(&chairman_cash_overlay_fixture)
|
||||||
|
.expect("overlay-backed chairman-cash fixture should summarize");
|
||||||
|
run_runtime_summarize_fixture(&deactivate_chairman_overlay_fixture)
|
||||||
|
.expect("overlay-backed deactivate-chairman fixture should summarize");
|
||||||
|
run_runtime_summarize_fixture(&missing_chairman_context_fixture)
|
||||||
|
.expect("save-slice-backed chairman missing-context fixture should summarize");
|
||||||
|
run_runtime_summarize_fixture(&chairman_scope_parity_fixture)
|
||||||
|
.expect("save-slice-backed chairman scope parity fixture should summarize");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -353,6 +355,8 @@ mod tests {
|
||||||
selected_company_id: Some(42),
|
selected_company_id: Some(42),
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,12 @@ pub struct ExpectedRuntimeSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub player_count: Option<usize>,
|
pub player_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub chairman_profile_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub active_chairman_profile_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub selected_chairman_profile_id: Option<u32>,
|
||||||
|
#[serde(default)]
|
||||||
pub train_count: Option<usize>,
|
pub train_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub active_train_count: Option<usize>,
|
pub active_train_count: Option<usize>,
|
||||||
|
|
@ -110,6 +116,10 @@ pub struct ExpectedRuntimeSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_blocked_missing_player_role_context_count: Option<usize>,
|
pub packed_event_blocked_missing_player_role_context_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub packed_event_blocked_missing_chairman_context_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub packed_event_blocked_chairman_target_scope_count: Option<usize>,
|
||||||
|
#[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_missing_player_condition_context_count: Option<usize>,
|
pub packed_event_blocked_missing_player_condition_context_count: Option<usize>,
|
||||||
|
|
@ -439,6 +449,30 @@ impl ExpectedRuntimeSummary {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(count) = self.chairman_profile_count {
|
||||||
|
if actual.chairman_profile_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"chairman_profile_count mismatch: expected {count}, got {}",
|
||||||
|
actual.chairman_profile_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(count) = self.active_chairman_profile_count {
|
||||||
|
if actual.active_chairman_profile_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"active_chairman_profile_count mismatch: expected {count}, got {}",
|
||||||
|
actual.active_chairman_profile_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(selected_id) = self.selected_chairman_profile_id {
|
||||||
|
if actual.selected_chairman_profile_id != Some(selected_id) {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"selected_chairman_profile_id mismatch: expected {selected_id:?}, got {:?}",
|
||||||
|
actual.selected_chairman_profile_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(count) = self.train_count {
|
if let Some(count) = self.train_count {
|
||||||
if actual.train_count != count {
|
if actual.train_count != count {
|
||||||
mismatches.push(format!(
|
mismatches.push(format!(
|
||||||
|
|
@ -591,6 +625,22 @@ impl ExpectedRuntimeSummary {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(count) = self.packed_event_blocked_missing_chairman_context_count {
|
||||||
|
if actual.packed_event_blocked_missing_chairman_context_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_blocked_missing_chairman_context_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_blocked_missing_chairman_context_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(count) = self.packed_event_blocked_chairman_target_scope_count {
|
||||||
|
if actual.packed_event_blocked_chairman_target_scope_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_blocked_chairman_target_scope_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_blocked_chairman_target_scope_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(count) = self.packed_event_blocked_missing_condition_context_count {
|
if let Some(count) = self.packed_event_blocked_missing_condition_context_count {
|
||||||
if actual.packed_event_blocked_missing_condition_context_count != count {
|
if actual.packed_event_blocked_missing_condition_context_count != count {
|
||||||
mismatches.push(format!(
|
mismatches.push(format!(
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,18 @@ 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, RuntimeCargoCatalogEntry, RuntimeCompanyConditionTestScope,
|
CalendarPoint, RuntimeCargoCatalogEntry, RuntimeChairmanTarget,
|
||||||
RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeCondition, RuntimeEffect,
|
RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind, RuntimeCompanyTarget,
|
||||||
RuntimeEventRecord, RuntimeEventRecordTemplate, RuntimeLocomotiveCatalogEntry,
|
RuntimeCondition, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||||
RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary,
|
RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary,
|
||||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
||||||
RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary,
|
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
|
||||||
RuntimePackedEventTextBandSummary, RuntimePlayerConditionTestScope, RuntimePlayerTarget,
|
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary,
|
||||||
RuntimeSaveProfileState, RuntimeServiceState, RuntimeState, RuntimeTerritoryTarget,
|
RuntimePlayerConditionTestScope, RuntimePlayerTarget, RuntimeSaveProfileState,
|
||||||
RuntimeWorldRestoreState, SmpLoadedPackedEventConditionRowSummary,
|
RuntimeServiceState, RuntimeState, RuntimeTerritoryTarget, RuntimeWorldRestoreState,
|
||||||
SmpLoadedPackedEventGroupedEffectRowSummary, SmpLoadedPackedEventNegativeSentinelScopeSummary,
|
SmpLoadedPackedEventConditionRowSummary, SmpLoadedPackedEventGroupedEffectRowSummary,
|
||||||
SmpLoadedPackedEventRecordSummary, SmpLoadedPackedEventTextBandSummary, SmpLoadedSaveSlice,
|
SmpLoadedPackedEventNegativeSentinelScopeSummary, SmpLoadedPackedEventRecordSummary,
|
||||||
|
SmpLoadedPackedEventTextBandSummary, SmpLoadedSaveSlice,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const STATE_DUMP_FORMAT_VERSION: u32 = 1;
|
pub const STATE_DUMP_FORMAT_VERSION: u32 = 1;
|
||||||
|
|
@ -116,6 +117,8 @@ struct ImportRuntimeContext {
|
||||||
known_player_ids: BTreeSet<u32>,
|
known_player_ids: BTreeSet<u32>,
|
||||||
selected_player_id: Option<u32>,
|
selected_player_id: Option<u32>,
|
||||||
has_complete_player_controller_context: bool,
|
has_complete_player_controller_context: bool,
|
||||||
|
known_chairman_profile_ids: BTreeSet<u32>,
|
||||||
|
selected_chairman_profile_id: Option<u32>,
|
||||||
known_territory_ids: BTreeSet<u32>,
|
known_territory_ids: BTreeSet<u32>,
|
||||||
has_territory_context: bool,
|
has_territory_context: bool,
|
||||||
territory_name_to_id: BTreeMap<String, u32>,
|
territory_name_to_id: BTreeMap<String, u32>,
|
||||||
|
|
@ -132,6 +135,8 @@ enum ImportBlocker {
|
||||||
MissingPlayerContext,
|
MissingPlayerContext,
|
||||||
MissingPlayerSelectionContext,
|
MissingPlayerSelectionContext,
|
||||||
MissingPlayerRoleContext,
|
MissingPlayerRoleContext,
|
||||||
|
MissingChairmanContext,
|
||||||
|
ChairmanTargetScope,
|
||||||
MissingConditionContext,
|
MissingConditionContext,
|
||||||
MissingPlayerConditionContext,
|
MissingPlayerConditionContext,
|
||||||
CompanyConditionScopeDisabled,
|
CompanyConditionScopeDisabled,
|
||||||
|
|
@ -153,6 +158,8 @@ impl ImportRuntimeContext {
|
||||||
known_player_ids: BTreeSet::new(),
|
known_player_ids: BTreeSet::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
has_complete_player_controller_context: false,
|
has_complete_player_controller_context: false,
|
||||||
|
known_chairman_profile_ids: BTreeSet::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
known_territory_ids: BTreeSet::new(),
|
known_territory_ids: BTreeSet::new(),
|
||||||
has_territory_context: false,
|
has_territory_context: false,
|
||||||
territory_name_to_id: BTreeMap::new(),
|
territory_name_to_id: BTreeMap::new(),
|
||||||
|
|
@ -185,6 +192,12 @@ impl ImportRuntimeContext {
|
||||||
.players
|
.players
|
||||||
.iter()
|
.iter()
|
||||||
.all(|player| player.controller_kind != RuntimeCompanyControllerKind::Unknown),
|
.all(|player| player.controller_kind != RuntimeCompanyControllerKind::Unknown),
|
||||||
|
known_chairman_profile_ids: state
|
||||||
|
.chairman_profiles
|
||||||
|
.iter()
|
||||||
|
.map(|profile| profile.profile_id)
|
||||||
|
.collect(),
|
||||||
|
selected_chairman_profile_id: state.selected_chairman_profile_id,
|
||||||
known_territory_ids: state
|
known_territory_ids: state
|
||||||
.territories
|
.territories
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -244,6 +257,8 @@ pub fn project_save_slice_to_runtime_state_import(
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: projection.locomotive_catalog.unwrap_or_default(),
|
locomotive_catalog: projection.locomotive_catalog.unwrap_or_default(),
|
||||||
cargo_catalog: projection.cargo_catalog.unwrap_or_default(),
|
cargo_catalog: projection.cargo_catalog.unwrap_or_default(),
|
||||||
|
|
@ -307,6 +322,8 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
|
||||||
selected_company_id: base_state.selected_company_id,
|
selected_company_id: base_state.selected_company_id,
|
||||||
players: base_state.players.clone(),
|
players: base_state.players.clone(),
|
||||||
selected_player_id: base_state.selected_player_id,
|
selected_player_id: base_state.selected_player_id,
|
||||||
|
chairman_profiles: base_state.chairman_profiles.clone(),
|
||||||
|
selected_chairman_profile_id: base_state.selected_chairman_profile_id,
|
||||||
trains: base_state.trains.clone(),
|
trains: base_state.trains.clone(),
|
||||||
locomotive_catalog: projection
|
locomotive_catalog: projection
|
||||||
.locomotive_catalog
|
.locomotive_catalog
|
||||||
|
|
@ -1007,6 +1024,7 @@ fn runtime_packed_event_grouped_effect_row_summary_from_smp(
|
||||||
descriptor_label: row.descriptor_label.clone(),
|
descriptor_label: row.descriptor_label.clone(),
|
||||||
target_mask_bits: row.target_mask_bits,
|
target_mask_bits: row.target_mask_bits,
|
||||||
parameter_family: row.parameter_family.clone(),
|
parameter_family: row.parameter_family.clone(),
|
||||||
|
grouped_target_subject: row.grouped_target_subject.clone(),
|
||||||
opcode: row.opcode,
|
opcode: row.opcode,
|
||||||
raw_scalar_value: row.raw_scalar_value,
|
raw_scalar_value: row.raw_scalar_value,
|
||||||
value_byte_0x09: row.value_byte_0x09,
|
value_byte_0x09: row.value_byte_0x09,
|
||||||
|
|
@ -1157,6 +1175,9 @@ fn lower_contextual_real_grouped_effects(
|
||||||
|
|
||||||
let mut effects = Vec::with_capacity(record.grouped_effect_rows.len());
|
let mut effects = Vec::with_capacity(record.grouped_effect_rows.len());
|
||||||
for row in &record.grouped_effect_rows {
|
for row in &record.grouped_effect_rows {
|
||||||
|
if real_grouped_row_is_unsupported_chairman_target_scope(row) {
|
||||||
|
return Err(ImportBlocker::ChairmanTargetScope);
|
||||||
|
}
|
||||||
if let Some(effect) = lower_contextual_cargo_production_effect(row)? {
|
if let Some(effect) = lower_contextual_cargo_production_effect(row)? {
|
||||||
effects.push(effect);
|
effects.push(effect);
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -1425,12 +1446,19 @@ fn lower_condition_targets_in_effect(
|
||||||
)?,
|
)?,
|
||||||
value: *value,
|
value: *value,
|
||||||
},
|
},
|
||||||
|
RuntimeEffect::SetChairmanCash { target, value } => RuntimeEffect::SetChairmanCash {
|
||||||
|
target: target.clone(),
|
||||||
|
value: *value,
|
||||||
|
},
|
||||||
RuntimeEffect::DeactivatePlayer { target } => RuntimeEffect::DeactivatePlayer {
|
RuntimeEffect::DeactivatePlayer { target } => RuntimeEffect::DeactivatePlayer {
|
||||||
target: lower_condition_true_player_target_in_player_target(
|
target: lower_condition_true_player_target_in_player_target(
|
||||||
target,
|
target,
|
||||||
lowered_player_target,
|
lowered_player_target,
|
||||||
)?,
|
)?,
|
||||||
},
|
},
|
||||||
|
RuntimeEffect::DeactivateChairman { target } => RuntimeEffect::DeactivateChairman {
|
||||||
|
target: target.clone(),
|
||||||
|
},
|
||||||
RuntimeEffect::SetCompanyTerritoryAccess {
|
RuntimeEffect::SetCompanyTerritoryAccess {
|
||||||
target,
|
target,
|
||||||
territory,
|
territory,
|
||||||
|
|
@ -1590,6 +1618,17 @@ fn lower_condition_targets_in_condition(
|
||||||
comparator: *comparator,
|
comparator: *comparator,
|
||||||
value: *value,
|
value: *value,
|
||||||
},
|
},
|
||||||
|
RuntimeCondition::ChairmanNumericThreshold {
|
||||||
|
target,
|
||||||
|
metric,
|
||||||
|
comparator,
|
||||||
|
value,
|
||||||
|
} => RuntimeCondition::ChairmanNumericThreshold {
|
||||||
|
target: target.clone(),
|
||||||
|
metric: *metric,
|
||||||
|
comparator: *comparator,
|
||||||
|
value: *value,
|
||||||
|
},
|
||||||
RuntimeCondition::TerritoryNumericThreshold {
|
RuntimeCondition::TerritoryNumericThreshold {
|
||||||
target,
|
target,
|
||||||
metric,
|
metric,
|
||||||
|
|
@ -1786,6 +1825,7 @@ fn condition_uses_condition_true_company(condition: &RuntimeCondition) -> bool {
|
||||||
| RuntimeCondition::CompanyTerritoryNumericThreshold { target, .. } => {
|
| RuntimeCondition::CompanyTerritoryNumericThreshold { target, .. } => {
|
||||||
matches!(target, RuntimeCompanyTarget::ConditionTrueCompany)
|
matches!(target, RuntimeCompanyTarget::ConditionTrueCompany)
|
||||||
}
|
}
|
||||||
|
RuntimeCondition::ChairmanNumericThreshold { .. } => false,
|
||||||
RuntimeCondition::TerritoryNumericThreshold { .. }
|
RuntimeCondition::TerritoryNumericThreshold { .. }
|
||||||
| RuntimeCondition::SpecialConditionThreshold { .. }
|
| RuntimeCondition::SpecialConditionThreshold { .. }
|
||||||
| RuntimeCondition::CandidateAvailabilityThreshold { .. }
|
| RuntimeCondition::CandidateAvailabilityThreshold { .. }
|
||||||
|
|
@ -1803,6 +1843,40 @@ fn condition_uses_condition_true_company(condition: &RuntimeCondition) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn chairman_target_import_blocker(
|
||||||
|
target: &RuntimeChairmanTarget,
|
||||||
|
company_context: &ImportRuntimeContext,
|
||||||
|
) -> Option<ImportBlocker> {
|
||||||
|
match target {
|
||||||
|
RuntimeChairmanTarget::AllActive => {
|
||||||
|
if company_context.known_chairman_profile_ids.is_empty() {
|
||||||
|
Some(ImportBlocker::MissingChairmanContext)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RuntimeChairmanTarget::SelectedChairman => {
|
||||||
|
if company_context.selected_chairman_profile_id.is_some() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ImportBlocker::MissingChairmanContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RuntimeChairmanTarget::Ids { ids } => {
|
||||||
|
if company_context.known_chairman_profile_ids.is_empty() {
|
||||||
|
Some(ImportBlocker::MissingChairmanContext)
|
||||||
|
} else if ids
|
||||||
|
.iter()
|
||||||
|
.all(|id| company_context.known_chairman_profile_ids.contains(id))
|
||||||
|
{
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ImportBlocker::MissingChairmanContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn smp_runtime_effects_to_runtime_effects(
|
fn smp_runtime_effects_to_runtime_effects(
|
||||||
effects: &[RuntimeEffect],
|
effects: &[RuntimeEffect],
|
||||||
company_context: &ImportRuntimeContext,
|
company_context: &ImportRuntimeContext,
|
||||||
|
|
@ -1867,6 +1941,16 @@ fn smp_runtime_effect_to_runtime_effect(
|
||||||
Err(player_target_import_error_message(target, company_context))
|
Err(player_target_import_error_message(target, company_context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RuntimeEffect::SetChairmanCash { target, value } => {
|
||||||
|
if chairman_target_import_blocker(target, company_context).is_none() {
|
||||||
|
Ok(RuntimeEffect::SetChairmanCash {
|
||||||
|
target: target.clone(),
|
||||||
|
value: *value,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err("packed effect requires chairman runtime context".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
RuntimeEffect::DeactivatePlayer { target } => {
|
RuntimeEffect::DeactivatePlayer { target } => {
|
||||||
if player_target_allowed_for_import(
|
if player_target_allowed_for_import(
|
||||||
target,
|
target,
|
||||||
|
|
@ -1880,6 +1964,15 @@ fn smp_runtime_effect_to_runtime_effect(
|
||||||
Err(player_target_import_error_message(target, company_context))
|
Err(player_target_import_error_message(target, company_context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RuntimeEffect::DeactivateChairman { target } => {
|
||||||
|
if chairman_target_import_blocker(target, company_context).is_none() {
|
||||||
|
Ok(RuntimeEffect::DeactivateChairman {
|
||||||
|
target: target.clone(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err("packed effect requires chairman runtime context".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
RuntimeEffect::SetCompanyTerritoryAccess {
|
RuntimeEffect::SetCompanyTerritoryAccess {
|
||||||
target,
|
target,
|
||||||
territory,
|
territory,
|
||||||
|
|
@ -2236,6 +2329,8 @@ fn company_target_import_error_message(
|
||||||
Some(ImportBlocker::MissingPlayerContext)
|
Some(ImportBlocker::MissingPlayerContext)
|
||||||
| Some(ImportBlocker::MissingPlayerSelectionContext)
|
| Some(ImportBlocker::MissingPlayerSelectionContext)
|
||||||
| Some(ImportBlocker::MissingPlayerRoleContext)
|
| Some(ImportBlocker::MissingPlayerRoleContext)
|
||||||
|
| Some(ImportBlocker::MissingChairmanContext)
|
||||||
|
| Some(ImportBlocker::ChairmanTargetScope)
|
||||||
| Some(ImportBlocker::MissingPlayerConditionContext) => {
|
| Some(ImportBlocker::MissingPlayerConditionContext) => {
|
||||||
"packed company effect is blocked by non-company import context".to_string()
|
"packed company effect is blocked by non-company import context".to_string()
|
||||||
}
|
}
|
||||||
|
|
@ -2324,10 +2419,22 @@ fn determine_packed_event_import_outcome(
|
||||||
}
|
}
|
||||||
if !record.executable_import_ready {
|
if !record.executable_import_ready {
|
||||||
if let Err(blocker) = lowered_record_decoded_actions(record, company_context) {
|
if let Err(blocker) = lowered_record_decoded_actions(record, company_context) {
|
||||||
if blocker == ImportBlocker::MissingLocomotiveCatalogContext {
|
if matches!(
|
||||||
|
blocker,
|
||||||
|
ImportBlocker::MissingLocomotiveCatalogContext
|
||||||
|
| ImportBlocker::MissingChairmanContext
|
||||||
|
| ImportBlocker::ChairmanTargetScope
|
||||||
|
) {
|
||||||
return company_target_import_outcome(blocker).to_string();
|
return company_target_import_outcome(blocker).to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if record
|
||||||
|
.grouped_effect_rows
|
||||||
|
.iter()
|
||||||
|
.any(real_grouped_row_is_unsupported_chairman_target_scope)
|
||||||
|
{
|
||||||
|
return "blocked_chairman_target_scope".to_string();
|
||||||
|
}
|
||||||
if record
|
if record
|
||||||
.grouped_effect_rows
|
.grouped_effect_rows
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -2507,6 +2614,9 @@ fn runtime_condition_company_target_import_blocker(
|
||||||
RuntimeCondition::CompanyNumericThreshold { target, .. } => {
|
RuntimeCondition::CompanyNumericThreshold { target, .. } => {
|
||||||
company_target_import_blocker(target, company_context)
|
company_target_import_blocker(target, company_context)
|
||||||
}
|
}
|
||||||
|
RuntimeCondition::ChairmanNumericThreshold { target, .. } => {
|
||||||
|
chairman_target_import_blocker(target, company_context)
|
||||||
|
}
|
||||||
RuntimeCondition::TerritoryNumericThreshold { target, .. } => {
|
RuntimeCondition::TerritoryNumericThreshold { target, .. } => {
|
||||||
territory_target_import_blocker(target, company_context)
|
territory_target_import_blocker(target, company_context)
|
||||||
}
|
}
|
||||||
|
|
@ -2559,6 +2669,8 @@ fn company_target_import_outcome(blocker: ImportBlocker) -> &'static str {
|
||||||
ImportBlocker::MissingPlayerContext => "blocked_missing_player_context",
|
ImportBlocker::MissingPlayerContext => "blocked_missing_player_context",
|
||||||
ImportBlocker::MissingPlayerSelectionContext => "blocked_missing_player_selection_context",
|
ImportBlocker::MissingPlayerSelectionContext => "blocked_missing_player_selection_context",
|
||||||
ImportBlocker::MissingPlayerRoleContext => "blocked_missing_player_role_context",
|
ImportBlocker::MissingPlayerRoleContext => "blocked_missing_player_role_context",
|
||||||
|
ImportBlocker::MissingChairmanContext => "blocked_missing_chairman_context",
|
||||||
|
ImportBlocker::ChairmanTargetScope => "blocked_chairman_target_scope",
|
||||||
ImportBlocker::MissingConditionContext => "blocked_missing_condition_context",
|
ImportBlocker::MissingConditionContext => "blocked_missing_condition_context",
|
||||||
ImportBlocker::MissingPlayerConditionContext => "blocked_missing_player_condition_context",
|
ImportBlocker::MissingPlayerConditionContext => "blocked_missing_player_condition_context",
|
||||||
ImportBlocker::CompanyConditionScopeDisabled => "blocked_company_condition_scope_disabled",
|
ImportBlocker::CompanyConditionScopeDisabled => "blocked_company_condition_scope_disabled",
|
||||||
|
|
@ -2585,6 +2697,17 @@ fn real_grouped_row_is_unsupported_territory_access_variant(
|
||||||
row.descriptor_id == 3 && !(row.row_shape == "bool_toggle" && row.raw_scalar_value != 0)
|
row.descriptor_id == 3 && !(row.row_shape == "bool_toggle" && row.raw_scalar_value != 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn real_grouped_row_is_unsupported_chairman_target_scope(
|
||||||
|
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
||||||
|
) -> bool {
|
||||||
|
matches!(row.grouped_target_subject.as_deref(), Some("chairman"))
|
||||||
|
&& matches!(row.descriptor_id, 1 | 14)
|
||||||
|
&& row
|
||||||
|
.notes
|
||||||
|
.iter()
|
||||||
|
.any(|note| note == "chairman row requires selected-chairman scope")
|
||||||
|
}
|
||||||
|
|
||||||
fn real_grouped_row_is_unsupported_territory_access_scope(
|
fn real_grouped_row_is_unsupported_territory_access_scope(
|
||||||
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
|
@ -2644,7 +2767,9 @@ fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool {
|
||||||
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
|
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
|
||||||
| RuntimeEffect::SetEconomicStatusCode { .. }
|
| RuntimeEffect::SetEconomicStatusCode { .. }
|
||||||
| RuntimeEffect::SetPlayerCash { .. }
|
| RuntimeEffect::SetPlayerCash { .. }
|
||||||
|
| RuntimeEffect::SetChairmanCash { .. }
|
||||||
| RuntimeEffect::DeactivatePlayer { .. }
|
| RuntimeEffect::DeactivatePlayer { .. }
|
||||||
|
| RuntimeEffect::DeactivateChairman { .. }
|
||||||
| RuntimeEffect::SetCandidateAvailability { .. }
|
| RuntimeEffect::SetCandidateAvailability { .. }
|
||||||
| RuntimeEffect::SetNamedLocomotiveAvailability { .. }
|
| RuntimeEffect::SetNamedLocomotiveAvailability { .. }
|
||||||
| RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. }
|
| RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. }
|
||||||
|
|
@ -2666,6 +2791,7 @@ fn runtime_effect_uses_condition_true_player(effect: &RuntimeEffect) -> bool {
|
||||||
RuntimeEffect::DeactivatePlayer { target } => {
|
RuntimeEffect::DeactivatePlayer { target } => {
|
||||||
matches!(target, RuntimePlayerTarget::ConditionTruePlayer)
|
matches!(target, RuntimePlayerTarget::ConditionTruePlayer)
|
||||||
}
|
}
|
||||||
|
RuntimeEffect::SetChairmanCash { .. } | RuntimeEffect::DeactivateChairman { .. } => false,
|
||||||
RuntimeEffect::AppendEventRecord { record } => record
|
RuntimeEffect::AppendEventRecord { record } => record
|
||||||
.effects
|
.effects
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -2701,6 +2827,10 @@ fn runtime_effect_company_target_import_blocker(
|
||||||
| RuntimeEffect::DeactivatePlayer { target } => {
|
| RuntimeEffect::DeactivatePlayer { target } => {
|
||||||
player_target_import_blocker(target, company_context)
|
player_target_import_blocker(target, company_context)
|
||||||
}
|
}
|
||||||
|
RuntimeEffect::SetChairmanCash { target, .. }
|
||||||
|
| RuntimeEffect::DeactivateChairman { target } => {
|
||||||
|
chairman_target_import_blocker(target, company_context)
|
||||||
|
}
|
||||||
RuntimeEffect::RetireTrains {
|
RuntimeEffect::RetireTrains {
|
||||||
company_target,
|
company_target,
|
||||||
territory_target,
|
territory_target,
|
||||||
|
|
@ -3067,6 +3197,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -3211,6 +3343,7 @@ mod tests {
|
||||||
descriptor_label: Some("Company Cash".to_string()),
|
descriptor_label: Some("Company Cash".to_string()),
|
||||||
target_mask_bits: Some(0x01),
|
target_mask_bits: Some(0x01),
|
||||||
parameter_family: Some("company_finance_scalar".to_string()),
|
parameter_family: Some("company_finance_scalar".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 8,
|
opcode: 8,
|
||||||
raw_scalar_value: 7,
|
raw_scalar_value: 7,
|
||||||
value_byte_0x09: 1,
|
value_byte_0x09: 1,
|
||||||
|
|
@ -3240,6 +3373,7 @@ mod tests {
|
||||||
descriptor_label: Some("Deactivate Company".to_string()),
|
descriptor_label: Some("Deactivate Company".to_string()),
|
||||||
target_mask_bits: Some(0x01),
|
target_mask_bits: Some(0x01),
|
||||||
parameter_family: Some("company_lifecycle_toggle".to_string()),
|
parameter_family: Some("company_lifecycle_toggle".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 1,
|
opcode: 1,
|
||||||
raw_scalar_value: if enabled { 1 } else { 0 },
|
raw_scalar_value: if enabled { 1 } else { 0 },
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -3270,6 +3404,7 @@ mod tests {
|
||||||
descriptor_label: Some("Company Track Pieces Buildable".to_string()),
|
descriptor_label: Some("Company Track Pieces Buildable".to_string()),
|
||||||
target_mask_bits: Some(0x01),
|
target_mask_bits: Some(0x01),
|
||||||
parameter_family: Some("company_build_limit_scalar".to_string()),
|
parameter_family: Some("company_build_limit_scalar".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 3,
|
opcode: 3,
|
||||||
raw_scalar_value: value,
|
raw_scalar_value: value,
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -3299,6 +3434,7 @@ mod tests {
|
||||||
descriptor_label: Some("Deactivate Player".to_string()),
|
descriptor_label: Some("Deactivate Player".to_string()),
|
||||||
target_mask_bits: Some(0x02),
|
target_mask_bits: Some(0x02),
|
||||||
parameter_family: Some("player_lifecycle_toggle".to_string()),
|
parameter_family: Some("player_lifecycle_toggle".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 1,
|
opcode: 1,
|
||||||
raw_scalar_value: if enabled { 1 } else { 0 },
|
raw_scalar_value: if enabled { 1 } else { 0 },
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -3332,6 +3468,7 @@ mod tests {
|
||||||
descriptor_label: Some("Territory - Allow All".to_string()),
|
descriptor_label: Some("Territory - Allow All".to_string()),
|
||||||
target_mask_bits: Some(0x05),
|
target_mask_bits: Some(0x05),
|
||||||
parameter_family: Some("territory_access_toggle".to_string()),
|
parameter_family: Some("territory_access_toggle".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 1,
|
opcode: 1,
|
||||||
raw_scalar_value: if enabled { 1 } else { 0 },
|
raw_scalar_value: if enabled { 1 } else { 0 },
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -3362,6 +3499,7 @@ mod tests {
|
||||||
descriptor_label: Some("Economic Status".to_string()),
|
descriptor_label: Some("Economic Status".to_string()),
|
||||||
target_mask_bits: Some(0x08),
|
target_mask_bits: Some(0x08),
|
||||||
parameter_family: Some("whole_game_state_enum".to_string()),
|
parameter_family: Some("whole_game_state_enum".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 3,
|
opcode: 3,
|
||||||
raw_scalar_value: value,
|
raw_scalar_value: value,
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -3391,6 +3529,7 @@ mod tests {
|
||||||
descriptor_label: Some("Limited Track Building Amount".to_string()),
|
descriptor_label: Some("Limited Track Building Amount".to_string()),
|
||||||
target_mask_bits: Some(0x08),
|
target_mask_bits: Some(0x08),
|
||||||
parameter_family: Some("world_track_build_limit_scalar".to_string()),
|
parameter_family: Some("world_track_build_limit_scalar".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 3,
|
opcode: 3,
|
||||||
raw_scalar_value: value,
|
raw_scalar_value: value,
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -3420,6 +3559,7 @@ mod tests {
|
||||||
descriptor_label: Some("Use Wartime Cargos".to_string()),
|
descriptor_label: Some("Use Wartime Cargos".to_string()),
|
||||||
target_mask_bits: Some(0x08),
|
target_mask_bits: Some(0x08),
|
||||||
parameter_family: Some("special_condition_scalar".to_string()),
|
parameter_family: Some("special_condition_scalar".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 3,
|
opcode: 3,
|
||||||
raw_scalar_value: value,
|
raw_scalar_value: value,
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -3449,6 +3589,7 @@ mod tests {
|
||||||
descriptor_label: Some("Turbo Diesel Availability".to_string()),
|
descriptor_label: Some("Turbo Diesel Availability".to_string()),
|
||||||
target_mask_bits: Some(0x08),
|
target_mask_bits: Some(0x08),
|
||||||
parameter_family: Some("candidate_availability_scalar".to_string()),
|
parameter_family: Some("candidate_availability_scalar".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 3,
|
opcode: 3,
|
||||||
raw_scalar_value: value,
|
raw_scalar_value: value,
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -3479,6 +3620,7 @@ mod tests {
|
||||||
descriptor_label: Some("Unknown Loco Available".to_string()),
|
descriptor_label: Some("Unknown Loco Available".to_string()),
|
||||||
target_mask_bits: Some(0x08),
|
target_mask_bits: Some(0x08),
|
||||||
parameter_family: Some("locomotive_availability_scalar".to_string()),
|
parameter_family: Some("locomotive_availability_scalar".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 3,
|
opcode: 3,
|
||||||
raw_scalar_value: value,
|
raw_scalar_value: value,
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -3521,6 +3663,7 @@ mod tests {
|
||||||
descriptor_label: Some(descriptor_label.clone()),
|
descriptor_label: Some(descriptor_label.clone()),
|
||||||
target_mask_bits: Some(0x08),
|
target_mask_bits: Some(0x08),
|
||||||
parameter_family: Some("locomotive_cost_scalar".to_string()),
|
parameter_family: Some("locomotive_cost_scalar".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 3,
|
opcode: 3,
|
||||||
raw_scalar_value: value,
|
raw_scalar_value: value,
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -3616,6 +3759,7 @@ mod tests {
|
||||||
descriptor_label: Some(descriptor_label.clone()),
|
descriptor_label: Some(descriptor_label.clone()),
|
||||||
target_mask_bits: Some(0x08),
|
target_mask_bits: Some(0x08),
|
||||||
parameter_family: Some("cargo_production_scalar".to_string()),
|
parameter_family: Some("cargo_production_scalar".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 3,
|
opcode: 3,
|
||||||
raw_scalar_value: value,
|
raw_scalar_value: value,
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -3645,6 +3789,7 @@ mod tests {
|
||||||
descriptor_label: Some("Territory Access Cost".to_string()),
|
descriptor_label: Some("Territory Access Cost".to_string()),
|
||||||
target_mask_bits: Some(0x08),
|
target_mask_bits: Some(0x08),
|
||||||
parameter_family: Some("territory_access_cost_scalar".to_string()),
|
parameter_family: Some("territory_access_cost_scalar".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 3,
|
opcode: 3,
|
||||||
raw_scalar_value: value,
|
raw_scalar_value: value,
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -3676,6 +3821,7 @@ mod tests {
|
||||||
descriptor_label: Some(label.to_string()),
|
descriptor_label: Some(label.to_string()),
|
||||||
target_mask_bits: Some(0x08),
|
target_mask_bits: Some(0x08),
|
||||||
parameter_family: Some("world_flag_toggle".to_string()),
|
parameter_family: Some("world_flag_toggle".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 0,
|
opcode: 0,
|
||||||
raw_scalar_value: if enabled { 1 } else { 0 },
|
raw_scalar_value: if enabled { 1 } else { 0 },
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -3708,6 +3854,7 @@ mod tests {
|
||||||
descriptor_label: Some("Confiscate All".to_string()),
|
descriptor_label: Some("Confiscate All".to_string()),
|
||||||
target_mask_bits: Some(0x01),
|
target_mask_bits: Some(0x01),
|
||||||
parameter_family: Some("company_confiscation_variant".to_string()),
|
parameter_family: Some("company_confiscation_variant".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 1,
|
opcode: 1,
|
||||||
raw_scalar_value: if enabled { 1 } else { 0 },
|
raw_scalar_value: if enabled { 1 } else { 0 },
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -3742,6 +3889,7 @@ mod tests {
|
||||||
descriptor_label: Some("Retire Train".to_string()),
|
descriptor_label: Some("Retire Train".to_string()),
|
||||||
target_mask_bits: Some(0x0d),
|
target_mask_bits: Some(0x0d),
|
||||||
parameter_family: Some("company_or_territory_asset_toggle".to_string()),
|
parameter_family: Some("company_or_territory_asset_toggle".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 1,
|
opcode: 1,
|
||||||
raw_scalar_value: if enabled { 1 } else { 0 },
|
raw_scalar_value: if enabled { 1 } else { 0 },
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -3772,6 +3920,7 @@ mod tests {
|
||||||
descriptor_label: Some("Confiscate All".to_string()),
|
descriptor_label: Some("Confiscate All".to_string()),
|
||||||
target_mask_bits: Some(0x01),
|
target_mask_bits: Some(0x01),
|
||||||
parameter_family: Some("company_confiscation_variant".to_string()),
|
parameter_family: Some("company_confiscation_variant".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 1,
|
opcode: 1,
|
||||||
raw_scalar_value: 0,
|
raw_scalar_value: 0,
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -5451,6 +5600,7 @@ mod tests {
|
||||||
descriptor_label: Some("Unknown Loco Available".to_string()),
|
descriptor_label: Some("Unknown Loco Available".to_string()),
|
||||||
target_mask_bits: Some(0x08),
|
target_mask_bits: Some(0x08),
|
||||||
parameter_family: Some("locomotive_availability_scalar".to_string()),
|
parameter_family: Some("locomotive_availability_scalar".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 3,
|
opcode: 3,
|
||||||
raw_scalar_value: 42,
|
raw_scalar_value: 42,
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -5697,6 +5847,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: vec![
|
locomotive_catalog: vec![
|
||||||
crate::RuntimeLocomotiveCatalogEntry {
|
crate::RuntimeLocomotiveCatalogEntry {
|
||||||
|
|
@ -6092,6 +6244,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: vec![
|
locomotive_catalog: vec![
|
||||||
crate::RuntimeLocomotiveCatalogEntry {
|
crate::RuntimeLocomotiveCatalogEntry {
|
||||||
|
|
@ -6488,6 +6642,8 @@ mod tests {
|
||||||
selected_company_id: Some(42),
|
selected_company_id: Some(42),
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -6566,6 +6722,7 @@ mod tests {
|
||||||
descriptor_label: Some("Company Cash".to_string()),
|
descriptor_label: Some("Company Cash".to_string()),
|
||||||
target_mask_bits: Some(0x01),
|
target_mask_bits: Some(0x01),
|
||||||
parameter_family: Some("company_finance_scalar".to_string()),
|
parameter_family: Some("company_finance_scalar".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 8,
|
opcode: 8,
|
||||||
raw_scalar_value: 250,
|
raw_scalar_value: 250,
|
||||||
value_byte_0x09: 1,
|
value_byte_0x09: 1,
|
||||||
|
|
@ -8145,6 +8302,7 @@ mod tests {
|
||||||
descriptor_label: Some("Turbo Diesel Availability".to_string()),
|
descriptor_label: Some("Turbo Diesel Availability".to_string()),
|
||||||
target_mask_bits: Some(0x08),
|
target_mask_bits: Some(0x08),
|
||||||
parameter_family: Some("candidate_availability_scalar".to_string()),
|
parameter_family: Some("candidate_availability_scalar".to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode: 3,
|
opcode: 3,
|
||||||
raw_scalar_value: 1,
|
raw_scalar_value: 1,
|
||||||
value_byte_0x09: 0,
|
value_byte_0x09: 0,
|
||||||
|
|
@ -9850,6 +10008,8 @@ mod tests {
|
||||||
selected_company_id: Some(42),
|
selected_company_id: Some(42),
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -10034,6 +10194,8 @@ mod tests {
|
||||||
selected_company_id: Some(42),
|
selected_company_id: Some(42),
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,8 @@ 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::{
|
||||||
RuntimeCargoCatalogEntry, RuntimeCargoClass, RuntimeCompany, RuntimeCompanyConditionTestScope,
|
RuntimeCargoCatalogEntry, RuntimeCargoClass, RuntimeChairmanMetric, RuntimeChairmanProfile,
|
||||||
|
RuntimeChairmanTarget, RuntimeCompany, RuntimeCompanyConditionTestScope,
|
||||||
RuntimeCompanyControllerKind, RuntimeCompanyMetric, RuntimeCompanyTarget,
|
RuntimeCompanyControllerKind, RuntimeCompanyMetric, RuntimeCompanyTarget,
|
||||||
RuntimeCompanyTerritoryAccess, RuntimeCompanyTerritoryTrackPieceCount, RuntimeCondition,
|
RuntimeCompanyTerritoryAccess, RuntimeCompanyTerritoryTrackPieceCount, RuntimeCondition,
|
||||||
RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,30 @@ pub struct RuntimePlayer {
|
||||||
pub controller_kind: RuntimeCompanyControllerKind,
|
pub controller_kind: RuntimeCompanyControllerKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn runtime_chairman_profile_default_active() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct RuntimeChairmanProfile {
|
||||||
|
pub profile_id: u32,
|
||||||
|
pub name: String,
|
||||||
|
#[serde(default = "runtime_chairman_profile_default_active")]
|
||||||
|
pub active: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub current_cash: i64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub linked_company_id: Option<u32>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub company_holdings: BTreeMap<u32, u32>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub holdings_value_total: i64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub net_worth_total: i64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub purchasing_power_total: i64,
|
||||||
|
}
|
||||||
|
|
||||||
fn runtime_train_default_active() -> bool {
|
fn runtime_train_default_active() -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
@ -156,6 +180,14 @@ pub enum RuntimePlayerTarget {
|
||||||
Ids { ids: Vec<u32> },
|
Ids { ids: Vec<u32> },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
|
pub enum RuntimeChairmanTarget {
|
||||||
|
AllActive,
|
||||||
|
SelectedChairman,
|
||||||
|
Ids { ids: Vec<u32> },
|
||||||
|
}
|
||||||
|
|
||||||
#[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 RuntimeTerritoryTarget {
|
pub enum RuntimeTerritoryTarget {
|
||||||
|
|
@ -211,6 +243,15 @@ pub enum RuntimeCompanyMetric {
|
||||||
TrackPiecesNonElectric,
|
TrackPiecesNonElectric,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum RuntimeChairmanMetric {
|
||||||
|
CurrentCash,
|
||||||
|
HoldingsValueTotal,
|
||||||
|
NetWorthTotal,
|
||||||
|
PurchasingPowerTotal,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum RuntimeTerritoryMetric {
|
pub enum RuntimeTerritoryMetric {
|
||||||
|
|
@ -242,6 +283,12 @@ pub enum RuntimeCondition {
|
||||||
comparator: RuntimeConditionComparator,
|
comparator: RuntimeConditionComparator,
|
||||||
value: i64,
|
value: i64,
|
||||||
},
|
},
|
||||||
|
ChairmanNumericThreshold {
|
||||||
|
target: RuntimeChairmanTarget,
|
||||||
|
metric: RuntimeChairmanMetric,
|
||||||
|
comparator: RuntimeConditionComparator,
|
||||||
|
value: i64,
|
||||||
|
},
|
||||||
TerritoryNumericThreshold {
|
TerritoryNumericThreshold {
|
||||||
target: RuntimeTerritoryTarget,
|
target: RuntimeTerritoryTarget,
|
||||||
metric: RuntimeTerritoryMetric,
|
metric: RuntimeTerritoryMetric,
|
||||||
|
|
@ -336,9 +383,16 @@ pub enum RuntimeEffect {
|
||||||
target: RuntimePlayerTarget,
|
target: RuntimePlayerTarget,
|
||||||
value: i64,
|
value: i64,
|
||||||
},
|
},
|
||||||
|
SetChairmanCash {
|
||||||
|
target: RuntimeChairmanTarget,
|
||||||
|
value: i64,
|
||||||
|
},
|
||||||
DeactivatePlayer {
|
DeactivatePlayer {
|
||||||
target: RuntimePlayerTarget,
|
target: RuntimePlayerTarget,
|
||||||
},
|
},
|
||||||
|
DeactivateChairman {
|
||||||
|
target: RuntimeChairmanTarget,
|
||||||
|
},
|
||||||
SetCompanyTerritoryAccess {
|
SetCompanyTerritoryAccess {
|
||||||
target: RuntimeCompanyTarget,
|
target: RuntimeCompanyTarget,
|
||||||
territory: RuntimeTerritoryTarget,
|
territory: RuntimeTerritoryTarget,
|
||||||
|
|
@ -590,6 +644,8 @@ pub struct RuntimePackedEventGroupedEffectRowSummary {
|
||||||
pub target_mask_bits: Option<u8>,
|
pub target_mask_bits: Option<u8>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub parameter_family: Option<String>,
|
pub parameter_family: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub grouped_target_subject: Option<String>,
|
||||||
pub opcode: u8,
|
pub opcode: u8,
|
||||||
pub raw_scalar_value: i32,
|
pub raw_scalar_value: i32,
|
||||||
pub value_byte_0x09: u8,
|
pub value_byte_0x09: u8,
|
||||||
|
|
@ -733,6 +789,10 @@ pub struct RuntimeState {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub selected_player_id: Option<u32>,
|
pub selected_player_id: Option<u32>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub chairman_profiles: Vec<RuntimeChairmanProfile>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub selected_chairman_profile_id: Option<u32>,
|
||||||
|
#[serde(default)]
|
||||||
pub trains: Vec<RuntimeTrain>,
|
pub trains: Vec<RuntimeTrain>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub locomotive_catalog: Vec<RuntimeLocomotiveCatalogEntry>,
|
pub locomotive_catalog: Vec<RuntimeLocomotiveCatalogEntry>,
|
||||||
|
|
@ -801,6 +861,63 @@ impl RuntimeState {
|
||||||
active_player_ids.insert(player.player_id);
|
active_player_ids.insert(player.player_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut seen_chairman_profile_ids = BTreeSet::new();
|
||||||
|
let mut seen_chairman_names = BTreeSet::new();
|
||||||
|
let mut active_chairman_profile_ids = BTreeSet::new();
|
||||||
|
for chairman in &self.chairman_profiles {
|
||||||
|
if !seen_chairman_profile_ids.insert(chairman.profile_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"duplicate chairman_profile.profile_id {}",
|
||||||
|
chairman.profile_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if chairman.name.trim().is_empty() {
|
||||||
|
return Err(format!(
|
||||||
|
"chairman_profile {} has an empty name",
|
||||||
|
chairman.profile_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !seen_chairman_names.insert(chairman.name.clone()) {
|
||||||
|
return Err(format!(
|
||||||
|
"duplicate chairman_profile.name {:?}",
|
||||||
|
chairman.name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if chairman.active {
|
||||||
|
active_chairman_profile_ids.insert(chairman.profile_id);
|
||||||
|
}
|
||||||
|
if let Some(linked_company_id) = chairman.linked_company_id {
|
||||||
|
if !seen_company_ids.contains(&linked_company_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"chairman_profile {} references unknown linked_company_id {}",
|
||||||
|
chairman.profile_id, linked_company_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for company_id in chairman.company_holdings.keys() {
|
||||||
|
if !seen_company_ids.contains(company_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"chairman_profile {} references unknown holdings company_id {}",
|
||||||
|
chairman.profile_id, company_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(selected_chairman_profile_id) = self.selected_chairman_profile_id {
|
||||||
|
if !seen_chairman_profile_ids.contains(&selected_chairman_profile_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"selected_chairman_profile_id {} does not reference a live chairman profile",
|
||||||
|
selected_chairman_profile_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !active_chairman_profile_ids.contains(&selected_chairman_profile_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"selected_chairman_profile_id {} must reference an active chairman profile",
|
||||||
|
selected_chairman_profile_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(selected_player_id) = self.selected_player_id {
|
if let Some(selected_player_id) = self.selected_player_id {
|
||||||
if !seen_player_ids.contains(&selected_player_id) {
|
if !seen_player_ids.contains(&selected_player_id) {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
|
|
@ -956,19 +1073,25 @@ impl RuntimeState {
|
||||||
return Err(format!("duplicate record_id {}", record.record_id));
|
return Err(format!("duplicate record_id {}", record.record_id));
|
||||||
}
|
}
|
||||||
for (condition_index, condition) in record.conditions.iter().enumerate() {
|
for (condition_index, condition) in record.conditions.iter().enumerate() {
|
||||||
validate_runtime_condition(condition, &seen_company_ids, &seen_territory_ids)
|
validate_runtime_condition(
|
||||||
.map_err(|err| {
|
condition,
|
||||||
|
&seen_company_ids,
|
||||||
|
&seen_chairman_profile_ids,
|
||||||
|
&seen_territory_ids,
|
||||||
|
)
|
||||||
|
.map_err(|err| {
|
||||||
format!(
|
format!(
|
||||||
"event_runtime_records[record_id={}].conditions[{condition_index}] {err}",
|
"event_runtime_records[record_id={}].conditions[{condition_index}] {err}",
|
||||||
record.record_id
|
record.record_id
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
for (effect_index, effect) in record.effects.iter().enumerate() {
|
for (effect_index, effect) in record.effects.iter().enumerate() {
|
||||||
validate_runtime_effect(
|
validate_runtime_effect(
|
||||||
effect,
|
effect,
|
||||||
&seen_company_ids,
|
&seen_company_ids,
|
||||||
&seen_player_ids,
|
&seen_player_ids,
|
||||||
|
&seen_chairman_profile_ids,
|
||||||
&seen_territory_ids,
|
&seen_territory_ids,
|
||||||
)
|
)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
|
|
@ -1315,6 +1438,7 @@ fn validate_runtime_effect(
|
||||||
effect: &RuntimeEffect,
|
effect: &RuntimeEffect,
|
||||||
valid_company_ids: &BTreeSet<u32>,
|
valid_company_ids: &BTreeSet<u32>,
|
||||||
valid_player_ids: &BTreeSet<u32>,
|
valid_player_ids: &BTreeSet<u32>,
|
||||||
|
valid_chairman_profile_ids: &BTreeSet<u32>,
|
||||||
valid_territory_ids: &BTreeSet<u32>,
|
valid_territory_ids: &BTreeSet<u32>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
match effect {
|
match effect {
|
||||||
|
|
@ -1343,6 +1467,10 @@ fn validate_runtime_effect(
|
||||||
| RuntimeEffect::DeactivatePlayer { target } => {
|
| RuntimeEffect::DeactivatePlayer { target } => {
|
||||||
validate_player_target(target, valid_player_ids)?;
|
validate_player_target(target, valid_player_ids)?;
|
||||||
}
|
}
|
||||||
|
RuntimeEffect::SetChairmanCash { target, .. }
|
||||||
|
| RuntimeEffect::DeactivateChairman { target } => {
|
||||||
|
validate_chairman_target(target, valid_chairman_profile_ids)?;
|
||||||
|
}
|
||||||
RuntimeEffect::RetireTrains {
|
RuntimeEffect::RetireTrains {
|
||||||
company_target,
|
company_target,
|
||||||
territory_target,
|
territory_target,
|
||||||
|
|
@ -1403,6 +1531,7 @@ fn validate_runtime_effect(
|
||||||
record,
|
record,
|
||||||
valid_company_ids,
|
valid_company_ids,
|
||||||
valid_player_ids,
|
valid_player_ids,
|
||||||
|
valid_chairman_profile_ids,
|
||||||
valid_territory_ids,
|
valid_territory_ids,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
@ -1418,23 +1547,29 @@ fn validate_event_record_template(
|
||||||
record: &RuntimeEventRecordTemplate,
|
record: &RuntimeEventRecordTemplate,
|
||||||
valid_company_ids: &BTreeSet<u32>,
|
valid_company_ids: &BTreeSet<u32>,
|
||||||
valid_player_ids: &BTreeSet<u32>,
|
valid_player_ids: &BTreeSet<u32>,
|
||||||
|
valid_chairman_profile_ids: &BTreeSet<u32>,
|
||||||
valid_territory_ids: &BTreeSet<u32>,
|
valid_territory_ids: &BTreeSet<u32>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
for (condition_index, condition) in record.conditions.iter().enumerate() {
|
for (condition_index, condition) in record.conditions.iter().enumerate() {
|
||||||
validate_runtime_condition(condition, valid_company_ids, valid_territory_ids).map_err(
|
validate_runtime_condition(
|
||||||
|err| {
|
condition,
|
||||||
format!(
|
valid_company_ids,
|
||||||
"template record_id={}.conditions[{condition_index}] {err}",
|
valid_chairman_profile_ids,
|
||||||
record.record_id
|
valid_territory_ids,
|
||||||
)
|
)
|
||||||
},
|
.map_err(|err| {
|
||||||
)?;
|
format!(
|
||||||
|
"template record_id={}.conditions[{condition_index}] {err}",
|
||||||
|
record.record_id
|
||||||
|
)
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
for (effect_index, effect) in record.effects.iter().enumerate() {
|
for (effect_index, effect) in record.effects.iter().enumerate() {
|
||||||
validate_runtime_effect(
|
validate_runtime_effect(
|
||||||
effect,
|
effect,
|
||||||
valid_company_ids,
|
valid_company_ids,
|
||||||
valid_player_ids,
|
valid_player_ids,
|
||||||
|
valid_chairman_profile_ids,
|
||||||
valid_territory_ids,
|
valid_territory_ids,
|
||||||
)
|
)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
|
|
@ -1451,12 +1586,16 @@ fn validate_event_record_template(
|
||||||
fn validate_runtime_condition(
|
fn validate_runtime_condition(
|
||||||
condition: &RuntimeCondition,
|
condition: &RuntimeCondition,
|
||||||
valid_company_ids: &BTreeSet<u32>,
|
valid_company_ids: &BTreeSet<u32>,
|
||||||
|
valid_chairman_profile_ids: &BTreeSet<u32>,
|
||||||
valid_territory_ids: &BTreeSet<u32>,
|
valid_territory_ids: &BTreeSet<u32>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
match condition {
|
match condition {
|
||||||
RuntimeCondition::CompanyNumericThreshold { target, .. } => {
|
RuntimeCondition::CompanyNumericThreshold { target, .. } => {
|
||||||
validate_company_target(target, valid_company_ids)
|
validate_company_target(target, valid_company_ids)
|
||||||
}
|
}
|
||||||
|
RuntimeCondition::ChairmanNumericThreshold { target, .. } => {
|
||||||
|
validate_chairman_target(target, valid_chairman_profile_ids)
|
||||||
|
}
|
||||||
RuntimeCondition::TerritoryNumericThreshold { target, .. } => {
|
RuntimeCondition::TerritoryNumericThreshold { target, .. } => {
|
||||||
validate_territory_target(target, valid_territory_ids)
|
validate_territory_target(target, valid_territory_ids)
|
||||||
}
|
}
|
||||||
|
|
@ -1562,6 +1701,28 @@ fn validate_player_target(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_chairman_target(
|
||||||
|
target: &RuntimeChairmanTarget,
|
||||||
|
valid_chairman_profile_ids: &BTreeSet<u32>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
match target {
|
||||||
|
RuntimeChairmanTarget::AllActive | RuntimeChairmanTarget::SelectedChairman => Ok(()),
|
||||||
|
RuntimeChairmanTarget::Ids { ids } => {
|
||||||
|
if ids.is_empty() {
|
||||||
|
return Err("target ids must not be empty".to_string());
|
||||||
|
}
|
||||||
|
for profile_id in ids {
|
||||||
|
if !valid_chairman_profile_ids.contains(profile_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"target references unknown chairman profile_id {profile_id}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_territory_target(
|
fn validate_territory_target(
|
||||||
target: &RuntimeTerritoryTarget,
|
target: &RuntimeTerritoryTarget,
|
||||||
valid_territory_ids: &BTreeSet<u32>,
|
valid_territory_ids: &BTreeSet<u32>,
|
||||||
|
|
@ -1628,6 +1789,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -1689,6 +1852,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -1735,6 +1900,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -1794,6 +1961,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -1853,6 +2022,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -1963,6 +2134,8 @@ mod tests {
|
||||||
selected_company_id: Some(2),
|
selected_company_id: Some(2),
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -2009,6 +2182,8 @@ mod tests {
|
||||||
selected_company_id: Some(1),
|
selected_company_id: Some(1),
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -2055,6 +2230,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: vec![
|
trains: vec![
|
||||||
RuntimeTrain {
|
RuntimeTrain {
|
||||||
train_id: 7,
|
train_id: 7,
|
||||||
|
|
@ -2118,6 +2295,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: vec![RuntimeTrain {
|
trains: vec![RuntimeTrain {
|
||||||
train_id: 7,
|
train_id: 7,
|
||||||
owner_company_id: 2,
|
owner_company_id: 2,
|
||||||
|
|
@ -2171,6 +2350,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: vec![RuntimeTrain {
|
trains: vec![RuntimeTrain {
|
||||||
train_id: 7,
|
train_id: 7,
|
||||||
owner_company_id: 1,
|
owner_company_id: 1,
|
||||||
|
|
@ -2228,6 +2409,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: vec![RuntimeTrain {
|
trains: vec![RuntimeTrain {
|
||||||
train_id: 7,
|
train_id: 7,
|
||||||
owner_company_id: 1,
|
owner_company_id: 1,
|
||||||
|
|
@ -2281,6 +2464,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -2340,6 +2525,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -2393,6 +2580,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
RuntimeCargoClass, RuntimeCompanyConditionTestScope, RuntimeCompanyMetric,
|
RuntimeCargoClass, RuntimeChairmanTarget, RuntimeCompanyConditionTestScope,
|
||||||
RuntimeCompanyTarget, RuntimeCondition, RuntimeConditionComparator, RuntimeEffect,
|
RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition, RuntimeConditionComparator,
|
||||||
RuntimeEventRecordTemplate, RuntimePlayerConditionTestScope, RuntimePlayerTarget,
|
RuntimeEffect, RuntimeEventRecordTemplate, RuntimePlayerConditionTestScope,
|
||||||
RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric,
|
RuntimePlayerTarget, RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION: u32 = 0x03ec;
|
pub const SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION: u32 = 0x03ec;
|
||||||
|
|
@ -1876,6 +1876,8 @@ pub struct SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
pub target_mask_bits: Option<u8>,
|
pub target_mask_bits: Option<u8>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub parameter_family: Option<String>,
|
pub parameter_family: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub grouped_target_subject: Option<String>,
|
||||||
pub opcode: u8,
|
pub opcode: u8,
|
||||||
pub raw_scalar_value: i32,
|
pub raw_scalar_value: i32,
|
||||||
pub value_byte_0x09: u8,
|
pub value_byte_0x09: u8,
|
||||||
|
|
@ -1901,6 +1903,15 @@ pub struct SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
pub notes: Vec<String>,
|
pub notes: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum RealGroupedTargetSubject {
|
||||||
|
Company,
|
||||||
|
Player,
|
||||||
|
Chairman,
|
||||||
|
Territory,
|
||||||
|
WholeGame,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct SmpLoadedSaveSlice {
|
pub struct SmpLoadedSaveSlice {
|
||||||
pub file_extension_hint: Option<String>,
|
pub file_extension_hint: Option<String>,
|
||||||
|
|
@ -2590,12 +2601,20 @@ fn parse_real_event_runtime_record_summary(
|
||||||
}
|
}
|
||||||
if let Some(control) = compact_control.as_ref() {
|
if let Some(control) = compact_control.as_ref() {
|
||||||
for row in &mut grouped_effect_rows {
|
for row in &mut grouped_effect_rows {
|
||||||
|
row.grouped_target_subject = derive_real_grouped_target_subject(row, control)
|
||||||
|
.map(real_grouped_target_subject_name)
|
||||||
|
.map(str::to_string);
|
||||||
let company_target_present = control
|
let company_target_present = control
|
||||||
.grouped_target_scope_ordinals_0x7fb
|
.grouped_target_scope_ordinals_0x7fb
|
||||||
.get(row.group_index)
|
.get(row.group_index)
|
||||||
.copied()
|
.copied()
|
||||||
.and_then(real_grouped_company_target)
|
.and_then(real_grouped_company_target)
|
||||||
.is_some();
|
.is_some();
|
||||||
|
let chairman_target_present = control
|
||||||
|
.grouped_target_scope_ordinals_0x7fb
|
||||||
|
.get(row.group_index)
|
||||||
|
.copied()
|
||||||
|
.is_some_and(real_grouped_chairman_target_supported_in_runtime);
|
||||||
let territory_target_present = control
|
let territory_target_present = control
|
||||||
.grouped_territory_selectors_0x80f
|
.grouped_territory_selectors_0x80f
|
||||||
.get(row.group_index)
|
.get(row.group_index)
|
||||||
|
|
@ -2617,6 +2636,14 @@ fn parse_real_event_runtime_record_summary(
|
||||||
row.notes
|
row.notes
|
||||||
.push("territory access row is missing company or territory scope".to_string());
|
.push("territory access row is missing company or territory scope".to_string());
|
||||||
}
|
}
|
||||||
|
if matches!(
|
||||||
|
derive_real_grouped_target_subject(row, control),
|
||||||
|
Some(RealGroupedTargetSubject::Chairman)
|
||||||
|
) && !chairman_target_present
|
||||||
|
{
|
||||||
|
row.notes
|
||||||
|
.push("chairman row requires selected-chairman scope".to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3174,6 +3201,7 @@ fn parse_real_grouped_effect_row_summary(
|
||||||
descriptor_label: descriptor_metadata.map(|metadata| metadata.label.to_string()),
|
descriptor_label: descriptor_metadata.map(|metadata| metadata.label.to_string()),
|
||||||
target_mask_bits: descriptor_metadata.map(|metadata| metadata.target_mask_bits),
|
target_mask_bits: descriptor_metadata.map(|metadata| metadata.target_mask_bits),
|
||||||
parameter_family: descriptor_metadata.map(|metadata| metadata.parameter_family.to_string()),
|
parameter_family: descriptor_metadata.map(|metadata| metadata.parameter_family.to_string()),
|
||||||
|
grouped_target_subject: None,
|
||||||
opcode,
|
opcode,
|
||||||
raw_scalar_value,
|
raw_scalar_value,
|
||||||
value_byte_0x09,
|
value_byte_0x09,
|
||||||
|
|
@ -3665,6 +3693,44 @@ fn runtime_world_flag_key_from_label(label: &str) -> String {
|
||||||
key
|
key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn derive_real_grouped_target_subject(
|
||||||
|
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
||||||
|
compact_control: &SmpLoadedPackedEventCompactControlSummary,
|
||||||
|
) -> Option<RealGroupedTargetSubject> {
|
||||||
|
match row.target_mask_bits {
|
||||||
|
Some(0x08) => Some(RealGroupedTargetSubject::WholeGame),
|
||||||
|
Some(0x01) => Some(RealGroupedTargetSubject::Company),
|
||||||
|
Some(0x02) => match compact_control
|
||||||
|
.grouped_scope_checkboxes_0x7ff
|
||||||
|
.get(row.group_index)
|
||||||
|
.copied()
|
||||||
|
{
|
||||||
|
Some(2) => Some(RealGroupedTargetSubject::Chairman),
|
||||||
|
_ => Some(RealGroupedTargetSubject::Player),
|
||||||
|
},
|
||||||
|
_ if row.descriptor_id == 3 => Some(RealGroupedTargetSubject::Territory),
|
||||||
|
_ if row.descriptor_id == 15
|
||||||
|
&& compact_control
|
||||||
|
.grouped_territory_selectors_0x80f
|
||||||
|
.get(row.group_index)
|
||||||
|
.is_some_and(|selector| *selector >= 0) =>
|
||||||
|
{
|
||||||
|
Some(RealGroupedTargetSubject::Territory)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn real_grouped_target_subject_name(subject: RealGroupedTargetSubject) -> &'static str {
|
||||||
|
match subject {
|
||||||
|
RealGroupedTargetSubject::Company => "company",
|
||||||
|
RealGroupedTargetSubject::Player => "player",
|
||||||
|
RealGroupedTargetSubject::Chairman => "chairman",
|
||||||
|
RealGroupedTargetSubject::Territory => "territory",
|
||||||
|
RealGroupedTargetSubject::WholeGame => "whole_game",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn decode_real_grouped_effect_actions(
|
fn decode_real_grouped_effect_actions(
|
||||||
grouped_effect_rows: &[SmpLoadedPackedEventGroupedEffectRowSummary],
|
grouped_effect_rows: &[SmpLoadedPackedEventGroupedEffectRowSummary],
|
||||||
compact_control: &SmpLoadedPackedEventCompactControlSummary,
|
compact_control: &SmpLoadedPackedEventCompactControlSummary,
|
||||||
|
|
@ -3684,17 +3750,23 @@ fn decode_real_grouped_effect_action(
|
||||||
.grouped_target_scope_ordinals_0x7fb
|
.grouped_target_scope_ordinals_0x7fb
|
||||||
.get(row.group_index)
|
.get(row.group_index)
|
||||||
.copied()?;
|
.copied()?;
|
||||||
|
let target_subject = derive_real_grouped_target_subject(row, compact_control);
|
||||||
|
|
||||||
if descriptor_metadata.executable_in_runtime
|
if descriptor_metadata.executable_in_runtime
|
||||||
&& descriptor_metadata.descriptor_id == 1
|
&& descriptor_metadata.descriptor_id == 1
|
||||||
&& row.opcode == 8
|
&& row.opcode == 8
|
||||||
&& row.row_shape == "multivalue_scalar"
|
&& row.row_shape == "multivalue_scalar"
|
||||||
{
|
{
|
||||||
let target = real_grouped_player_target(target_scope_ordinal)?;
|
return match target_subject {
|
||||||
return Some(RuntimeEffect::SetPlayerCash {
|
Some(RealGroupedTargetSubject::Chairman) => Some(RuntimeEffect::SetChairmanCash {
|
||||||
target,
|
target: real_grouped_chairman_target(target_scope_ordinal)?,
|
||||||
value: i64::from(row.raw_scalar_value),
|
value: i64::from(row.raw_scalar_value),
|
||||||
});
|
}),
|
||||||
|
_ => Some(RuntimeEffect::SetPlayerCash {
|
||||||
|
target: real_grouped_player_target(target_scope_ordinal)?,
|
||||||
|
value: i64::from(row.raw_scalar_value),
|
||||||
|
}),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if descriptor_metadata.executable_in_runtime
|
if descriptor_metadata.executable_in_runtime
|
||||||
|
|
@ -3823,8 +3895,14 @@ fn decode_real_grouped_effect_action(
|
||||||
&& row.row_shape == "bool_toggle"
|
&& row.row_shape == "bool_toggle"
|
||||||
&& row.raw_scalar_value != 0
|
&& row.raw_scalar_value != 0
|
||||||
{
|
{
|
||||||
let target = real_grouped_player_target(target_scope_ordinal)?;
|
return match target_subject {
|
||||||
return Some(RuntimeEffect::DeactivatePlayer { target });
|
Some(RealGroupedTargetSubject::Chairman) => Some(RuntimeEffect::DeactivateChairman {
|
||||||
|
target: real_grouped_chairman_target(target_scope_ordinal)?,
|
||||||
|
}),
|
||||||
|
_ => Some(RuntimeEffect::DeactivatePlayer {
|
||||||
|
target: real_grouped_player_target(target_scope_ordinal)?,
|
||||||
|
}),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if descriptor_metadata.executable_in_runtime
|
if descriptor_metadata.executable_in_runtime
|
||||||
|
|
@ -3886,6 +3964,17 @@ fn real_grouped_player_target(ordinal: u8) -> Option<RuntimePlayerTarget> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn real_grouped_chairman_target(ordinal: u8) -> Option<RuntimeChairmanTarget> {
|
||||||
|
match ordinal {
|
||||||
|
1 => Some(RuntimeChairmanTarget::SelectedChairman),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn real_grouped_chairman_target_supported_in_runtime(ordinal: u8) -> bool {
|
||||||
|
real_grouped_chairman_target(ordinal).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_synthetic_packed_event_action(bytes: &[u8], cursor: &mut usize) -> Option<RuntimeEffect> {
|
fn parse_synthetic_packed_event_action(bytes: &[u8], cursor: &mut usize) -> Option<RuntimeEffect> {
|
||||||
let opcode = read_u8_at(bytes, *cursor)?;
|
let opcode = read_u8_at(bytes, *cursor)?;
|
||||||
*cursor += 1;
|
*cursor += 1;
|
||||||
|
|
@ -4033,6 +4122,13 @@ fn parse_optional_u16_len_prefixed_string(
|
||||||
|
|
||||||
fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
|
fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
|
||||||
match effect {
|
match effect {
|
||||||
|
RuntimeEffect::SetChairmanCash { target, .. }
|
||||||
|
| RuntimeEffect::DeactivateChairman { target } => matches!(
|
||||||
|
target,
|
||||||
|
RuntimeChairmanTarget::AllActive
|
||||||
|
| RuntimeChairmanTarget::SelectedChairman
|
||||||
|
| RuntimeChairmanTarget::Ids { .. }
|
||||||
|
),
|
||||||
RuntimeEffect::SetWorldFlag { .. }
|
RuntimeEffect::SetWorldFlag { .. }
|
||||||
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
|
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
|
||||||
| RuntimeEffect::SetEconomicStatusCode { .. }
|
| RuntimeEffect::SetEconomicStatusCode { .. }
|
||||||
|
|
@ -4097,6 +4193,7 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
|
||||||
fn runtime_condition_supported_for_save_import(condition: &RuntimeCondition) -> bool {
|
fn runtime_condition_supported_for_save_import(condition: &RuntimeCondition) -> bool {
|
||||||
match condition {
|
match condition {
|
||||||
RuntimeCondition::CompanyNumericThreshold { .. }
|
RuntimeCondition::CompanyNumericThreshold { .. }
|
||||||
|
| RuntimeCondition::ChairmanNumericThreshold { .. }
|
||||||
| RuntimeCondition::TerritoryNumericThreshold { .. }
|
| RuntimeCondition::TerritoryNumericThreshold { .. }
|
||||||
| RuntimeCondition::CompanyTerritoryNumericThreshold { .. }
|
| RuntimeCondition::CompanyTerritoryNumericThreshold { .. }
|
||||||
| RuntimeCondition::SpecialConditionThreshold { .. }
|
| RuntimeCondition::SpecialConditionThreshold { .. }
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ use std::collections::BTreeSet;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
RuntimeCargoClass, RuntimeCompanyControllerKind, RuntimeCompanyMetric, RuntimeCompanyTarget,
|
RuntimeCargoClass, RuntimeChairmanMetric, RuntimeChairmanTarget, RuntimeCompanyControllerKind,
|
||||||
RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecordTemplate,
|
RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition, RuntimeConditionComparator,
|
||||||
RuntimePlayerTarget, RuntimeState, RuntimeSummary, RuntimeTerritoryMetric,
|
RuntimeEffect, RuntimeEventRecordTemplate, RuntimePlayerTarget, RuntimeState, RuntimeSummary,
|
||||||
RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
||||||
calendar::BoundaryEventKind,
|
calendar::BoundaryEventKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -87,6 +87,8 @@ struct AppliedEffectsSummary {
|
||||||
struct ResolvedConditionContext {
|
struct ResolvedConditionContext {
|
||||||
matching_company_ids: BTreeSet<u32>,
|
matching_company_ids: BTreeSet<u32>,
|
||||||
matching_player_ids: BTreeSet<u32>,
|
matching_player_ids: BTreeSet<u32>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
matching_chairman_profile_ids: BTreeSet<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute_step_command(
|
pub fn execute_step_command(
|
||||||
|
|
@ -346,6 +348,21 @@ fn apply_runtime_effects(
|
||||||
mutated_player_ids.insert(player_id);
|
mutated_player_ids.insert(player_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RuntimeEffect::SetChairmanCash { target, value } => {
|
||||||
|
let profile_ids = resolve_chairman_target_ids(state, target, condition_context)?;
|
||||||
|
for profile_id in profile_ids {
|
||||||
|
let chairman = state
|
||||||
|
.chairman_profiles
|
||||||
|
.iter_mut()
|
||||||
|
.find(|profile| profile.profile_id == profile_id)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"missing chairman profile_id {profile_id} while applying cash effect"
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
chairman.current_cash = *value;
|
||||||
|
}
|
||||||
|
}
|
||||||
RuntimeEffect::DeactivatePlayer { target } => {
|
RuntimeEffect::DeactivatePlayer { target } => {
|
||||||
let player_ids = resolve_player_target_ids(state, target, condition_context)?;
|
let player_ids = resolve_player_target_ids(state, target, condition_context)?;
|
||||||
for player_id in player_ids {
|
for player_id in player_ids {
|
||||||
|
|
@ -365,6 +382,39 @@ fn apply_runtime_effects(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RuntimeEffect::DeactivateChairman { target } => {
|
||||||
|
let profile_ids = resolve_chairman_target_ids(state, target, condition_context)?;
|
||||||
|
for profile_id in profile_ids.iter().copied() {
|
||||||
|
let linked_company_id = state
|
||||||
|
.chairman_profiles
|
||||||
|
.iter()
|
||||||
|
.find(|profile| profile.profile_id == profile_id)
|
||||||
|
.and_then(|profile| profile.linked_company_id);
|
||||||
|
let chairman = state
|
||||||
|
.chairman_profiles
|
||||||
|
.iter_mut()
|
||||||
|
.find(|profile| profile.profile_id == profile_id)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"missing chairman profile_id {profile_id} while applying deactivate effect"
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
chairman.active = false;
|
||||||
|
chairman.linked_company_id = None;
|
||||||
|
if state.selected_chairman_profile_id == Some(profile_id) {
|
||||||
|
state.selected_chairman_profile_id = None;
|
||||||
|
}
|
||||||
|
if let Some(linked_company_id) = linked_company_id {
|
||||||
|
for other in &mut state.chairman_profiles {
|
||||||
|
if other.profile_id != profile_id
|
||||||
|
&& other.linked_company_id == Some(linked_company_id)
|
||||||
|
{
|
||||||
|
other.linked_company_id = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
RuntimeEffect::SetCompanyTerritoryAccess {
|
RuntimeEffect::SetCompanyTerritoryAccess {
|
||||||
target,
|
target,
|
||||||
territory,
|
territory,
|
||||||
|
|
@ -607,6 +657,7 @@ fn evaluate_record_conditions(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut company_matches: Option<BTreeSet<u32>> = None;
|
let mut company_matches: Option<BTreeSet<u32>> = None;
|
||||||
|
let mut chairman_matches: Option<BTreeSet<u32>> = None;
|
||||||
|
|
||||||
for condition in conditions {
|
for condition in conditions {
|
||||||
match condition {
|
match condition {
|
||||||
|
|
@ -657,6 +708,41 @@ fn evaluate_record_conditions(
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RuntimeCondition::ChairmanNumericThreshold {
|
||||||
|
target,
|
||||||
|
metric,
|
||||||
|
comparator,
|
||||||
|
value,
|
||||||
|
} => {
|
||||||
|
let resolved = resolve_chairman_target_ids(
|
||||||
|
state,
|
||||||
|
target,
|
||||||
|
&ResolvedConditionContext::default(),
|
||||||
|
)?;
|
||||||
|
let matching = resolved
|
||||||
|
.into_iter()
|
||||||
|
.filter(|profile_id| {
|
||||||
|
state
|
||||||
|
.chairman_profiles
|
||||||
|
.iter()
|
||||||
|
.find(|profile| profile.profile_id == *profile_id)
|
||||||
|
.is_some_and(|profile| {
|
||||||
|
compare_condition_value(
|
||||||
|
chairman_metric_value(profile, *metric),
|
||||||
|
*comparator,
|
||||||
|
*value,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
if matching.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
intersect_chairman_matches(&mut chairman_matches, matching);
|
||||||
|
if chairman_matches.as_ref().is_some_and(BTreeSet::is_empty) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
RuntimeCondition::CompanyTerritoryNumericThreshold {
|
RuntimeCondition::CompanyTerritoryNumericThreshold {
|
||||||
target,
|
target,
|
||||||
territory,
|
territory,
|
||||||
|
|
@ -840,6 +926,7 @@ fn evaluate_record_conditions(
|
||||||
Ok(Some(ResolvedConditionContext {
|
Ok(Some(ResolvedConditionContext {
|
||||||
matching_company_ids: company_matches.unwrap_or_default(),
|
matching_company_ids: company_matches.unwrap_or_default(),
|
||||||
matching_player_ids: BTreeSet::new(),
|
matching_player_ids: BTreeSet::new(),
|
||||||
|
matching_chairman_profile_ids: chairman_matches.unwrap_or_default(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -854,6 +941,17 @@ fn intersect_company_matches(company_matches: &mut Option<BTreeSet<u32>>, next:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn intersect_chairman_matches(chairman_matches: &mut Option<BTreeSet<u32>>, next: BTreeSet<u32>) {
|
||||||
|
match chairman_matches {
|
||||||
|
Some(existing) => {
|
||||||
|
existing.retain(|profile_id| next.contains(profile_id));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
*chairman_matches = Some(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_company_target_ids(
|
fn resolve_company_target_ids(
|
||||||
state: &RuntimeState,
|
state: &RuntimeState,
|
||||||
target: &RuntimeCompanyTarget,
|
target: &RuntimeCompanyTarget,
|
||||||
|
|
@ -1043,6 +1141,53 @@ fn resolve_player_target_ids(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_chairman_target_ids(
|
||||||
|
state: &RuntimeState,
|
||||||
|
target: &RuntimeChairmanTarget,
|
||||||
|
_condition_context: &ResolvedConditionContext,
|
||||||
|
) -> Result<Vec<u32>, String> {
|
||||||
|
match target {
|
||||||
|
RuntimeChairmanTarget::AllActive => Ok(state
|
||||||
|
.chairman_profiles
|
||||||
|
.iter()
|
||||||
|
.filter(|profile| profile.active)
|
||||||
|
.map(|profile| profile.profile_id)
|
||||||
|
.collect()),
|
||||||
|
RuntimeChairmanTarget::Ids { ids } => {
|
||||||
|
let known_ids = state
|
||||||
|
.chairman_profiles
|
||||||
|
.iter()
|
||||||
|
.map(|profile| profile.profile_id)
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
for profile_id in ids {
|
||||||
|
if !known_ids.contains(profile_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"target references unknown chairman profile_id {profile_id}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ids.clone())
|
||||||
|
}
|
||||||
|
RuntimeChairmanTarget::SelectedChairman => {
|
||||||
|
let selected_profile_id = state.selected_chairman_profile_id.ok_or_else(|| {
|
||||||
|
"target requires selected_chairman_profile_id context".to_string()
|
||||||
|
})?;
|
||||||
|
if state
|
||||||
|
.chairman_profiles
|
||||||
|
.iter()
|
||||||
|
.any(|profile| profile.profile_id == selected_profile_id && profile.active)
|
||||||
|
{
|
||||||
|
Ok(vec![selected_profile_id])
|
||||||
|
} else {
|
||||||
|
Err(
|
||||||
|
"target requires selected_chairman_profile_id to reference an active chairman profile"
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_territory_target_ids(
|
fn resolve_territory_target_ids(
|
||||||
state: &RuntimeState,
|
state: &RuntimeState,
|
||||||
target: &RuntimeTerritoryTarget,
|
target: &RuntimeTerritoryTarget,
|
||||||
|
|
@ -1090,6 +1235,18 @@ fn company_metric_value(company: &crate::RuntimeCompany, metric: RuntimeCompanyM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn chairman_metric_value(
|
||||||
|
profile: &crate::RuntimeChairmanProfile,
|
||||||
|
metric: RuntimeChairmanMetric,
|
||||||
|
) -> i64 {
|
||||||
|
match metric {
|
||||||
|
RuntimeChairmanMetric::CurrentCash => profile.current_cash,
|
||||||
|
RuntimeChairmanMetric::HoldingsValueTotal => profile.holdings_value_total,
|
||||||
|
RuntimeChairmanMetric::NetWorthTotal => profile.net_worth_total,
|
||||||
|
RuntimeChairmanMetric::PurchasingPowerTotal => profile.purchasing_power_total,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn territory_metric_value(
|
fn territory_metric_value(
|
||||||
state: &RuntimeState,
|
state: &RuntimeState,
|
||||||
territory_ids: &[u32],
|
territory_ids: &[u32],
|
||||||
|
|
@ -1277,6 +1434,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,9 @@ pub struct RuntimeSummary {
|
||||||
pub company_count: usize,
|
pub company_count: usize,
|
||||||
pub active_company_count: usize,
|
pub active_company_count: usize,
|
||||||
pub player_count: usize,
|
pub player_count: usize,
|
||||||
|
pub chairman_profile_count: usize,
|
||||||
|
pub active_chairman_profile_count: usize,
|
||||||
|
pub selected_chairman_profile_id: Option<u32>,
|
||||||
pub train_count: usize,
|
pub train_count: usize,
|
||||||
pub active_train_count: usize,
|
pub active_train_count: usize,
|
||||||
pub retired_train_count: usize,
|
pub retired_train_count: usize,
|
||||||
|
|
@ -52,6 +55,8 @@ pub struct RuntimeSummary {
|
||||||
pub packed_event_blocked_missing_player_context_count: usize,
|
pub packed_event_blocked_missing_player_context_count: usize,
|
||||||
pub packed_event_blocked_missing_player_selection_context_count: usize,
|
pub packed_event_blocked_missing_player_selection_context_count: usize,
|
||||||
pub packed_event_blocked_missing_player_role_context_count: usize,
|
pub packed_event_blocked_missing_player_role_context_count: usize,
|
||||||
|
pub packed_event_blocked_missing_chairman_context_count: usize,
|
||||||
|
pub packed_event_blocked_chairman_target_scope_count: usize,
|
||||||
pub packed_event_blocked_missing_condition_context_count: usize,
|
pub packed_event_blocked_missing_condition_context_count: usize,
|
||||||
pub packed_event_blocked_missing_player_condition_context_count: usize,
|
pub packed_event_blocked_missing_player_condition_context_count: usize,
|
||||||
pub packed_event_blocked_company_condition_scope_disabled_count: usize,
|
pub packed_event_blocked_company_condition_scope_disabled_count: usize,
|
||||||
|
|
@ -169,6 +174,13 @@ impl RuntimeSummary {
|
||||||
.filter(|company| company.active)
|
.filter(|company| company.active)
|
||||||
.count(),
|
.count(),
|
||||||
player_count: state.players.len(),
|
player_count: state.players.len(),
|
||||||
|
chairman_profile_count: state.chairman_profiles.len(),
|
||||||
|
active_chairman_profile_count: state
|
||||||
|
.chairman_profiles
|
||||||
|
.iter()
|
||||||
|
.filter(|profile| profile.active)
|
||||||
|
.count(),
|
||||||
|
selected_chairman_profile_id: state.selected_chairman_profile_id,
|
||||||
train_count: state.trains.len(),
|
train_count: state.trains.len(),
|
||||||
active_train_count: state.trains.iter().filter(|train| train.active).count(),
|
active_train_count: state.trains.iter().filter(|train| train.active).count(),
|
||||||
retired_train_count: state.trains.iter().filter(|train| train.retired).count(),
|
retired_train_count: state.trains.iter().filter(|train| train.retired).count(),
|
||||||
|
|
@ -298,6 +310,34 @@ impl RuntimeSummary {
|
||||||
.count()
|
.count()
|
||||||
})
|
})
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
|
packed_event_blocked_missing_chairman_context_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.import_outcome.as_deref()
|
||||||
|
== Some("blocked_missing_chairman_context")
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
|
packed_event_blocked_chairman_target_scope_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.import_outcome.as_deref()
|
||||||
|
== Some("blocked_chairman_target_scope")
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
packed_event_blocked_missing_condition_context_count: state
|
packed_event_blocked_missing_condition_context_count: state
|
||||||
.packed_event_collection
|
.packed_event_collection
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -666,6 +706,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: vec![
|
locomotive_catalog: vec![
|
||||||
crate::RuntimeLocomotiveCatalogEntry {
|
crate::RuntimeLocomotiveCatalogEntry {
|
||||||
|
|
@ -909,6 +951,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: vec![
|
locomotive_catalog: vec![
|
||||||
crate::RuntimeLocomotiveCatalogEntry {
|
crate::RuntimeLocomotiveCatalogEntry {
|
||||||
|
|
@ -956,6 +1000,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: vec![
|
locomotive_catalog: vec![
|
||||||
crate::RuntimeLocomotiveCatalogEntry {
|
crate::RuntimeLocomotiveCatalogEntry {
|
||||||
|
|
@ -1009,6 +1055,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -1053,6 +1101,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -1092,6 +1142,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
@ -1200,6 +1252,8 @@ mod tests {
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::new(),
|
||||||
|
selected_chairman_profile_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
locomotive_catalog: Vec::new(),
|
locomotive_catalog: Vec::new(),
|
||||||
cargo_catalog: Vec::new(),
|
cargo_catalog: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,9 @@ The highest-value next passes are now:
|
||||||
- descriptors `1` `Player Cash` and `14` `Deactivate Player` now join that executable real batch
|
- descriptors `1` `Player Cash` and `14` `Deactivate Player` now join that executable real batch
|
||||||
through the same ordinary runtime path, backed by the minimal player runtime and overlay-import
|
through the same ordinary runtime path, backed by the minimal player runtime and overlay-import
|
||||||
context
|
context
|
||||||
|
- the first chairman-targeted real grouped rows now execute too through that same path when the
|
||||||
|
hidden grouped target-subject lane resolves to selected-chairman scope; broader chairman target
|
||||||
|
scopes stay parity-only under `blocked_chairman_target_scope`
|
||||||
- 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
|
||||||
- the first grounded condition-side unlock now exists for negative-sentinel `raw_condition_id = -1`
|
- the first grounded condition-side unlock now exists for negative-sentinel `raw_condition_id = -1`
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,11 @@ Implemented today:
|
||||||
names, a minimal player runtime now carries selected-player and role context, and real descriptor
|
names, a minimal player runtime now carries selected-player and role context, and real descriptor
|
||||||
`1` = `Player Cash` and descriptor `14` = `Deactivate Player` now import and execute through the
|
`1` = `Player Cash` and descriptor `14` = `Deactivate Player` now import and execute through the
|
||||||
ordinary runtime path
|
ordinary runtime path
|
||||||
|
- a first-class chairman-profile runtime now exists too, with overlay-backed selected-chairman
|
||||||
|
context and the first chairman-targeted grouped-effect subset: the same real descriptors
|
||||||
|
`1` = `Player Cash` and `14` = `Deactivate Player` now also import and execute through the
|
||||||
|
hidden grouped target-subject lane when it resolves to selected-chairman scope, while broader
|
||||||
|
chairman target scopes remain explicit parity on `blocked_chairman_target_scope`
|
||||||
- a minimal event-owned train surface and an opaque economic-status lane now exist in runtime
|
- a minimal event-owned train surface and an opaque economic-status lane now exist in runtime
|
||||||
state, and real descriptors `8` = `Economic Status`, `9` = `Confiscate All`, and `15` =
|
state, and real descriptors `8` = `Economic Status`, `9` = `Confiscate All`, and `15` =
|
||||||
`Retire Train` now import and execute through the ordinary runtime path when overlay context
|
`Retire Train` now import and execute through the ordinary runtime path when overlay context
|
||||||
|
|
@ -111,8 +116,8 @@ Implemented today:
|
||||||
|
|
||||||
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 and ordinary condition-id coverage beyond the current access,
|
broader real grouped-descriptor and ordinary condition-id coverage beyond the current access,
|
||||||
whole-game toggle, train, player, numeric-threshold, named locomotive availability, named
|
whole-game toggle, train, player, chairman selected-scope, numeric-threshold, named locomotive
|
||||||
locomotive cost, world scalar override, and world-scalar condition batches.
|
availability, named locomotive cost, world scalar override, and world-scalar condition batches.
|
||||||
Richer runtime ownership should still be added only where a later descriptor or condition family
|
Richer runtime ownership should still be added only where a later descriptor or condition family
|
||||||
needs more than the current event-owned roster.
|
needs more than the current event-owned roster.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-chairman-cash-overlay-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture proving descriptor 1 imports and executes on selected-chairman scope."
|
||||||
|
},
|
||||||
|
"state_import_path": "packed-event-chairman-cash-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": 2,
|
||||||
|
"chairman_profile_count": 2,
|
||||||
|
"active_chairman_profile_count": 2,
|
||||||
|
"selected_chairman_profile_id": 1,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 1,
|
||||||
|
"event_runtime_record_count": 1,
|
||||||
|
"total_event_record_service_count": 1,
|
||||||
|
"total_trigger_dispatch_count": 1
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"chairman_profiles": [
|
||||||
|
{
|
||||||
|
"profile_id": 1,
|
||||||
|
"current_cash": 999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": 2,
|
||||||
|
"current_cash": 250
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packed_event_collection": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "imported",
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "set_chairman_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "selected_chairman"
|
||||||
|
},
|
||||||
|
"value": 999
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"grouped_target_subject": "chairman"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
fixtures/runtime/packed-event-chairman-cash-overlay.json
Normal file
9
fixtures/runtime/packed-event-chairman-cash-overlay.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"import_id": "packed-event-chairman-cash-overlay",
|
||||||
|
"source": {
|
||||||
|
"description": "Overlay import combining chairman runtime context with the real chairman-targeted cash descriptor sample."
|
||||||
|
},
|
||||||
|
"base_snapshot_path": "packed-event-chairman-overlay-base-snapshot.json",
|
||||||
|
"save_slice_path": "packed-event-chairman-cash-save-slice.json"
|
||||||
|
}
|
||||||
110
fixtures/runtime/packed-event-chairman-cash-save-slice.json
Normal file
110
fixtures/runtime/packed-event-chairman-cash-save-slice.json
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-chairman-cash-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document with a chairman-targeted Player Cash row using the hidden grouped target-subject lane.",
|
||||||
|
"original_save_filename": "captured-chairman-cash.gms",
|
||||||
|
"original_save_sha256": "chairman-cash-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"proves selected-chairman descriptor import through the normal runtime 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": 61,
|
||||||
|
"live_record_count": 1,
|
||||||
|
"live_entry_ids": [61],
|
||||||
|
"decoded_record_count": 1,
|
||||||
|
"imported_runtime_record_count": 1,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 61,
|
||||||
|
"payload_offset": 29296,
|
||||||
|
"payload_len": 140,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "real_packed_v1",
|
||||||
|
"trigger_kind": 6,
|
||||||
|
"one_shot": false,
|
||||||
|
"compact_control": {
|
||||||
|
"mode_byte_0x7ef": 6,
|
||||||
|
"primary_selector_0x7f0": 12,
|
||||||
|
"grouped_mode_0x7f4": 2,
|
||||||
|
"one_shot_header_0x7f5": 0,
|
||||||
|
"modifier_flag_0x7f9": 0,
|
||||||
|
"modifier_flag_0x7fa": 0,
|
||||||
|
"grouped_target_scope_ordinals_0x7fb": [1, 1, 1, 1],
|
||||||
|
"grouped_scope_checkboxes_0x7ff": [2, 0, 0, 0],
|
||||||
|
"summary_toggle_0x800": 1,
|
||||||
|
"grouped_territory_selectors_0x80f": [-1, -1, -1, -1]
|
||||||
|
},
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 0,
|
||||||
|
"standalone_condition_rows": [],
|
||||||
|
"grouped_effect_row_counts": [1, 0, 0, 0],
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"group_index": 0,
|
||||||
|
"row_index": 0,
|
||||||
|
"descriptor_id": 1,
|
||||||
|
"descriptor_label": "Player Cash",
|
||||||
|
"target_mask_bits": 2,
|
||||||
|
"parameter_family": "player_cash_scalar",
|
||||||
|
"grouped_target_subject": "chairman",
|
||||||
|
"opcode": 8,
|
||||||
|
"raw_scalar_value": 999,
|
||||||
|
"value_byte_0x09": 0,
|
||||||
|
"value_dword_0x0d": 0,
|
||||||
|
"value_byte_0x11": 0,
|
||||||
|
"value_byte_0x12": 0,
|
||||||
|
"value_word_0x14": 0,
|
||||||
|
"value_word_0x16": 0,
|
||||||
|
"row_shape": "multivalue_scalar",
|
||||||
|
"semantic_family": "multivalue_scalar",
|
||||||
|
"semantic_preview": "Set Player Cash to 999 with aux [0, 0, 0, 0]",
|
||||||
|
"locomotive_name": null,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_conditions": [],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "set_chairman_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "selected_chairman"
|
||||||
|
},
|
||||||
|
"value": 999
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": true,
|
||||||
|
"notes": [
|
||||||
|
"decoded from grounded real 0x4e9a row framing",
|
||||||
|
"hidden grouped target-subject lane resolves descriptor 1 to selected chairman scope"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"real chairman-targeted cash descriptor sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-chairman-missing-context-save-slice-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture proving chairman-targeted rows stay parity-only without runtime chairman context."
|
||||||
|
},
|
||||||
|
"state_import_path": "packed-event-chairman-cash-save-slice.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar_projection_source": "default-1830-placeholder",
|
||||||
|
"calendar_projection_is_placeholder": true,
|
||||||
|
"chairman_profile_count": 0,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 0,
|
||||||
|
"packed_event_blocked_missing_chairman_context_count": 1,
|
||||||
|
"event_runtime_record_count": 0
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"packed_event_collection": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "blocked_missing_chairman_context",
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"grouped_target_subject": "chairman"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"snapshot_id": "packed-event-chairman-overlay-base-snapshot",
|
||||||
|
"source": {
|
||||||
|
"description": "Base runtime snapshot supplying selected-chairman context for chairman-targeted packed-event overlays."
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"calendar": {
|
||||||
|
"year": 1840,
|
||||||
|
"month_slot": 1,
|
||||||
|
"phase_slot": 2,
|
||||||
|
"tick_slot": 3
|
||||||
|
},
|
||||||
|
"world_flags": {
|
||||||
|
"base.only": true
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"base.note": "chairman overlay context"
|
||||||
|
},
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"company_id": 1,
|
||||||
|
"current_cash": 150,
|
||||||
|
"debt": 80,
|
||||||
|
"credit_rating_score": 650,
|
||||||
|
"prime_rate": 5,
|
||||||
|
"controller_kind": "human",
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 20,
|
||||||
|
"single": 5,
|
||||||
|
"double": 8,
|
||||||
|
"transition": 1,
|
||||||
|
"electric": 3,
|
||||||
|
"non_electric": 17
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 2,
|
||||||
|
"current_cash": 90,
|
||||||
|
"debt": 40,
|
||||||
|
"credit_rating_score": 480,
|
||||||
|
"prime_rate": 6,
|
||||||
|
"controller_kind": "ai",
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 8,
|
||||||
|
"single": 2,
|
||||||
|
"double": 2,
|
||||||
|
"transition": 0,
|
||||||
|
"electric": 1,
|
||||||
|
"non_electric": 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selected_company_id": 1,
|
||||||
|
"players": [],
|
||||||
|
"selected_player_id": null,
|
||||||
|
"chairman_profiles": [
|
||||||
|
{
|
||||||
|
"profile_id": 1,
|
||||||
|
"name": "Chairman One",
|
||||||
|
"active": true,
|
||||||
|
"current_cash": 500,
|
||||||
|
"linked_company_id": 1,
|
||||||
|
"company_holdings": {
|
||||||
|
"1": 1000
|
||||||
|
},
|
||||||
|
"holdings_value_total": 700,
|
||||||
|
"net_worth_total": 1200,
|
||||||
|
"purchasing_power_total": 1500
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": 2,
|
||||||
|
"name": "Chairman Two",
|
||||||
|
"active": true,
|
||||||
|
"current_cash": 250,
|
||||||
|
"linked_company_id": 2,
|
||||||
|
"company_holdings": {
|
||||||
|
"2": 900
|
||||||
|
},
|
||||||
|
"holdings_value_total": 600,
|
||||||
|
"net_worth_total": 900,
|
||||||
|
"purchasing_power_total": 1100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selected_chairman_profile_id": 1,
|
||||||
|
"event_runtime_records": [],
|
||||||
|
"candidate_availability": {},
|
||||||
|
"special_conditions": {},
|
||||||
|
"service_state": {
|
||||||
|
"periodic_boundary_calls": 0,
|
||||||
|
"trigger_dispatch_counts": {},
|
||||||
|
"total_event_record_services": 0,
|
||||||
|
"dirty_rerun_count": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-chairman-scope-parity-save-slice-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture proving unsupported chairman scopes stay parity-only under an explicit blocker."
|
||||||
|
},
|
||||||
|
"state_import_path": "packed-event-chairman-scope-parity-save-slice.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar_projection_source": "default-1830-placeholder",
|
||||||
|
"calendar_projection_is_placeholder": true,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 0,
|
||||||
|
"packed_event_blocked_chairman_target_scope_count": 1,
|
||||||
|
"event_runtime_record_count": 0
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"packed_event_collection": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "blocked_chairman_target_scope",
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"grouped_target_subject": "chairman"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-chairman-scope-parity-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document with a chairman-targeted row on an unsupported non-selected scope.",
|
||||||
|
"original_save_filename": "captured-chairman-scope-parity.gms",
|
||||||
|
"original_save_sha256": "chairman-scope-parity-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"pins the selected-chairman-only execution boundary"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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": 63,
|
||||||
|
"live_record_count": 1,
|
||||||
|
"live_entry_ids": [63],
|
||||||
|
"decoded_record_count": 1,
|
||||||
|
"imported_runtime_record_count": 0,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 63,
|
||||||
|
"payload_offset": 29296,
|
||||||
|
"payload_len": 140,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "real_packed_v1",
|
||||||
|
"trigger_kind": 6,
|
||||||
|
"one_shot": false,
|
||||||
|
"compact_control": {
|
||||||
|
"mode_byte_0x7ef": 6,
|
||||||
|
"primary_selector_0x7f0": 12,
|
||||||
|
"grouped_mode_0x7f4": 2,
|
||||||
|
"one_shot_header_0x7f5": 0,
|
||||||
|
"modifier_flag_0x7f9": 0,
|
||||||
|
"modifier_flag_0x7fa": 0,
|
||||||
|
"grouped_target_scope_ordinals_0x7fb": [0, 1, 1, 1],
|
||||||
|
"grouped_scope_checkboxes_0x7ff": [2, 0, 0, 0],
|
||||||
|
"summary_toggle_0x800": 1,
|
||||||
|
"grouped_territory_selectors_0x80f": [-1, -1, -1, -1]
|
||||||
|
},
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 0,
|
||||||
|
"standalone_condition_rows": [],
|
||||||
|
"grouped_effect_row_counts": [1, 0, 0, 0],
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"group_index": 0,
|
||||||
|
"row_index": 0,
|
||||||
|
"descriptor_id": 1,
|
||||||
|
"descriptor_label": "Player Cash",
|
||||||
|
"target_mask_bits": 2,
|
||||||
|
"parameter_family": "player_cash_scalar",
|
||||||
|
"grouped_target_subject": "chairman",
|
||||||
|
"opcode": 8,
|
||||||
|
"raw_scalar_value": 700,
|
||||||
|
"value_byte_0x09": 0,
|
||||||
|
"value_dword_0x0d": 0,
|
||||||
|
"value_byte_0x11": 0,
|
||||||
|
"value_byte_0x12": 0,
|
||||||
|
"value_word_0x14": 0,
|
||||||
|
"value_word_0x16": 0,
|
||||||
|
"row_shape": "multivalue_scalar",
|
||||||
|
"semantic_family": "multivalue_scalar",
|
||||||
|
"semantic_preview": "Set Player Cash to 700 with aux [0, 0, 0, 0]",
|
||||||
|
"locomotive_name": null,
|
||||||
|
"notes": [
|
||||||
|
"chairman row requires selected-chairman scope"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_conditions": [],
|
||||||
|
"decoded_actions": [],
|
||||||
|
"executable_import_ready": false,
|
||||||
|
"notes": [
|
||||||
|
"decoded from grounded real 0x4e9a row framing",
|
||||||
|
"selected-chairman scope is the only grounded chairman-target subset in this slice"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"real chairman-targeted scope parity sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-deactivate-chairman-overlay-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture proving descriptor 14 imports and executes on selected-chairman scope."
|
||||||
|
},
|
||||||
|
"state_import_path": "packed-event-deactivate-chairman-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": 2,
|
||||||
|
"chairman_profile_count": 2,
|
||||||
|
"active_chairman_profile_count": 1,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 1,
|
||||||
|
"event_runtime_record_count": 1,
|
||||||
|
"total_event_record_service_count": 1,
|
||||||
|
"total_trigger_dispatch_count": 1
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"selected_chairman_profile_id": null,
|
||||||
|
"chairman_profiles": [
|
||||||
|
{
|
||||||
|
"profile_id": 1,
|
||||||
|
"active": false,
|
||||||
|
"linked_company_id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": 2,
|
||||||
|
"active": true,
|
||||||
|
"linked_company_id": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packed_event_collection": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "imported",
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "deactivate_chairman",
|
||||||
|
"target": {
|
||||||
|
"kind": "selected_chairman"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"grouped_target_subject": "chairman"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"import_id": "packed-event-deactivate-chairman-overlay",
|
||||||
|
"source": {
|
||||||
|
"description": "Overlay import combining chairman runtime context with the real chairman-targeted deactivation descriptor sample."
|
||||||
|
},
|
||||||
|
"base_snapshot_path": "packed-event-chairman-overlay-base-snapshot.json",
|
||||||
|
"save_slice_path": "packed-event-deactivate-chairman-save-slice.json"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-deactivate-chairman-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document with a chairman-targeted Deactivate Player row using the hidden grouped target-subject lane.",
|
||||||
|
"original_save_filename": "captured-deactivate-chairman.gms",
|
||||||
|
"original_save_sha256": "deactivate-chairman-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"proves selected-chairman lifecycle import through the normal runtime 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": 62,
|
||||||
|
"live_record_count": 1,
|
||||||
|
"live_entry_ids": [62],
|
||||||
|
"decoded_record_count": 1,
|
||||||
|
"imported_runtime_record_count": 1,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 62,
|
||||||
|
"payload_offset": 29296,
|
||||||
|
"payload_len": 140,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "real_packed_v1",
|
||||||
|
"trigger_kind": 6,
|
||||||
|
"one_shot": false,
|
||||||
|
"compact_control": {
|
||||||
|
"mode_byte_0x7ef": 6,
|
||||||
|
"primary_selector_0x7f0": 12,
|
||||||
|
"grouped_mode_0x7f4": 2,
|
||||||
|
"one_shot_header_0x7f5": 0,
|
||||||
|
"modifier_flag_0x7f9": 0,
|
||||||
|
"modifier_flag_0x7fa": 0,
|
||||||
|
"grouped_target_scope_ordinals_0x7fb": [1, 1, 1, 1],
|
||||||
|
"grouped_scope_checkboxes_0x7ff": [2, 0, 0, 0],
|
||||||
|
"summary_toggle_0x800": 1,
|
||||||
|
"grouped_territory_selectors_0x80f": [-1, -1, -1, -1]
|
||||||
|
},
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 0,
|
||||||
|
"standalone_condition_rows": [],
|
||||||
|
"grouped_effect_row_counts": [1, 0, 0, 0],
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"group_index": 0,
|
||||||
|
"row_index": 0,
|
||||||
|
"descriptor_id": 14,
|
||||||
|
"descriptor_label": "Deactivate Player",
|
||||||
|
"target_mask_bits": 2,
|
||||||
|
"parameter_family": "player_lifecycle_toggle",
|
||||||
|
"grouped_target_subject": "chairman",
|
||||||
|
"opcode": 1,
|
||||||
|
"raw_scalar_value": 1,
|
||||||
|
"value_byte_0x09": 0,
|
||||||
|
"value_dword_0x0d": 0,
|
||||||
|
"value_byte_0x11": 0,
|
||||||
|
"value_byte_0x12": 0,
|
||||||
|
"value_word_0x14": 0,
|
||||||
|
"value_word_0x16": 0,
|
||||||
|
"row_shape": "bool_toggle",
|
||||||
|
"semantic_family": "bool_toggle",
|
||||||
|
"semantic_preview": "Set Deactivate Player to TRUE",
|
||||||
|
"locomotive_name": null,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_conditions": [],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "deactivate_chairman",
|
||||||
|
"target": {
|
||||||
|
"kind": "selected_chairman"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": true,
|
||||||
|
"notes": [
|
||||||
|
"decoded from grounded real 0x4e9a row framing",
|
||||||
|
"hidden grouped target-subject lane resolves descriptor 14 to selected chairman scope"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"real chairman-targeted lifecycle descriptor sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue