Broaden chairman governance event coverage
This commit is contained in:
parent
86cf89b26c
commit
f8350a48c5
18 changed files with 1939 additions and 13 deletions
|
|
@ -4495,6 +4495,14 @@ mod tests {
|
|||
let chairman_scope_parity_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"../../fixtures/runtime/packed-event-chairman-scope-parity-save-slice-fixture.json",
|
||||
);
|
||||
let chairman_condition_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../fixtures/runtime/packed-event-chairman-condition-overlay-fixture.json");
|
||||
let company_governance_condition_overlay_fixture = PathBuf::from(env!(
|
||||
"CARGO_MANIFEST_DIR"
|
||||
))
|
||||
.join(
|
||||
"../../fixtures/runtime/packed-event-company-governance-condition-overlay-fixture.json",
|
||||
);
|
||||
|
||||
run_runtime_summarize_fixture(&parity_fixture)
|
||||
.expect("save-slice-backed parity fixture should summarize");
|
||||
|
|
@ -4544,6 +4552,10 @@ mod tests {
|
|||
.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");
|
||||
run_runtime_summarize_fixture(&chairman_condition_overlay_fixture)
|
||||
.expect("overlay-backed chairman condition fixture should summarize");
|
||||
run_runtime_summarize_fixture(&company_governance_condition_overlay_fixture)
|
||||
.expect("overlay-backed company governance condition fixture should summarize");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -351,6 +351,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
players: Vec::new(),
|
||||
|
|
|
|||
|
|
@ -78,6 +78,12 @@ pub struct ExpectedRuntimeSummary {
|
|||
#[serde(default)]
|
||||
pub selected_chairman_profile_id: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub linked_chairman_company_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub company_takeover_cooldown_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub company_merger_cooldown_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub train_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub active_train_count: Option<usize>,
|
||||
|
|
@ -473,6 +479,30 @@ impl ExpectedRuntimeSummary {
|
|||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.linked_chairman_company_count {
|
||||
if actual.linked_chairman_company_count != count {
|
||||
mismatches.push(format!(
|
||||
"linked_chairman_company_count mismatch: expected {count}, got {}",
|
||||
actual.linked_chairman_company_count
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.company_takeover_cooldown_count {
|
||||
if actual.company_takeover_cooldown_count != count {
|
||||
mismatches.push(format!(
|
||||
"company_takeover_cooldown_count mismatch: expected {count}, got {}",
|
||||
actual.company_takeover_cooldown_count
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.company_merger_cooldown_count {
|
||||
if actual.company_merger_cooldown_count != count {
|
||||
mismatches.push(format!(
|
||||
"company_merger_cooldown_count mismatch: expected {count}, got {}",
|
||||
actual.company_merger_cooldown_count
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.train_count {
|
||||
if actual.train_count != count {
|
||||
mismatches.push(format!(
|
||||
|
|
|
|||
|
|
@ -3335,6 +3335,16 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn selected_chairman_negative_sentinel_scope()
|
||||
-> crate::SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
||||
crate::SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
||||
company_test_scope: RuntimeCompanyConditionTestScope::Disabled,
|
||||
player_test_scope: RuntimePlayerConditionTestScope::SelectedPlayerOnly,
|
||||
territory_scope_selector_is_0x63: false,
|
||||
source_row_indexes: vec![0],
|
||||
}
|
||||
}
|
||||
|
||||
fn real_grouped_rows() -> Vec<crate::SmpLoadedPackedEventGroupedEffectRowSummary> {
|
||||
vec![crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index: 0,
|
||||
|
|
@ -4814,6 +4824,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
crate::RuntimeCompany {
|
||||
company_id: 2,
|
||||
|
|
@ -4825,6 +4841,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
],
|
||||
selected_company_id: Some(1),
|
||||
|
|
@ -5023,6 +5045,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
crate::RuntimeCompany {
|
||||
company_id: 2,
|
||||
|
|
@ -5034,6 +5062,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
crate::RuntimeCompany {
|
||||
company_id: 3,
|
||||
|
|
@ -5045,6 +5079,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
],
|
||||
selected_company_id: Some(3),
|
||||
|
|
@ -6638,6 +6678,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
players: Vec::new(),
|
||||
|
|
@ -6799,6 +6845,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
territories: vec![crate::RuntimeTerritory {
|
||||
|
|
@ -7102,6 +7154,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
..state()
|
||||
|
|
@ -7500,6 +7558,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
..state()
|
||||
|
|
@ -8804,6 +8868,375 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlays_selected_chairman_conditions_into_imported_runtime_records() {
|
||||
let base_state = RuntimeState {
|
||||
calendar: CalendarPoint {
|
||||
year: 1840,
|
||||
month_slot: 1,
|
||||
phase_slot: 2,
|
||||
tick_slot: 3,
|
||||
},
|
||||
world_flags: BTreeMap::new(),
|
||||
save_profile: RuntimeSaveProfileState::default(),
|
||||
world_restore: RuntimeWorldRestoreState::default(),
|
||||
metadata: BTreeMap::new(),
|
||||
companies: vec![crate::RuntimeCompany {
|
||||
company_id: 1,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: Some(1),
|
||||
book_value_per_share: 2620,
|
||||
investor_confidence: 37,
|
||||
management_attitude: 58,
|
||||
takeover_cooldown_year: Some(1839),
|
||||
merger_cooldown_year: Some(1838),
|
||||
}],
|
||||
selected_company_id: Some(1),
|
||||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
chairman_profiles: vec![
|
||||
crate::RuntimeChairmanProfile {
|
||||
profile_id: 1,
|
||||
name: "Chairman One".to_string(),
|
||||
active: true,
|
||||
current_cash: 500,
|
||||
linked_company_id: Some(1),
|
||||
company_holdings: BTreeMap::new(),
|
||||
holdings_value_total: 700,
|
||||
net_worth_total: 1200,
|
||||
purchasing_power_total: 1500,
|
||||
},
|
||||
crate::RuntimeChairmanProfile {
|
||||
profile_id: 2,
|
||||
name: "Chairman Two".to_string(),
|
||||
active: true,
|
||||
current_cash: 200,
|
||||
linked_company_id: None,
|
||||
company_holdings: BTreeMap::new(),
|
||||
holdings_value_total: 400,
|
||||
net_worth_total: 600,
|
||||
purchasing_power_total: 800,
|
||||
},
|
||||
],
|
||||
selected_chairman_profile_id: Some(1),
|
||||
trains: Vec::new(),
|
||||
locomotive_catalog: Vec::new(),
|
||||
cargo_catalog: Vec::new(),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
company_territory_access: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
named_locomotive_availability: BTreeMap::new(),
|
||||
named_locomotive_cost: BTreeMap::new(),
|
||||
cargo_production_overrides: BTreeMap::new(),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState::default(),
|
||||
};
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
trailer_family: None,
|
||||
bridge_family: None,
|
||||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
cargo_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
metadata_tag_offset: 0x7100,
|
||||
records_tag_offset: 0x7200,
|
||||
close_tag_offset: 0x7600,
|
||||
packed_state_version: 0x3e9,
|
||||
packed_state_version_hex: "0x000003e9".to_string(),
|
||||
live_id_bound: 71,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![71],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 71,
|
||||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(136),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "real_packed_v1".to_string(),
|
||||
trigger_kind: Some(6),
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: Some(false),
|
||||
compact_control: Some(real_compact_control()),
|
||||
text_bands: vec![],
|
||||
standalone_condition_row_count: 1,
|
||||
standalone_condition_rows: vec![
|
||||
crate::SmpLoadedPackedEventConditionRowSummary {
|
||||
row_index: 0,
|
||||
raw_condition_id: 2218,
|
||||
subtype: 4,
|
||||
flag_bytes: {
|
||||
let mut bytes = vec![0; 25];
|
||||
bytes[0..4].copy_from_slice(&500_i32.to_le_bytes());
|
||||
bytes
|
||||
},
|
||||
candidate_name: None,
|
||||
comparator: Some("eq".to_string()),
|
||||
metric: Some("Player Cash".to_string()),
|
||||
semantic_family: Some("numeric_threshold".to_string()),
|
||||
semantic_preview: Some("Test Player Cash == 500".to_string()),
|
||||
recovered_cargo_slot: None,
|
||||
recovered_cargo_class: None,
|
||||
requires_candidate_name_binding: false,
|
||||
notes: vec![],
|
||||
},
|
||||
],
|
||||
negative_sentinel_scope: Some(selected_chairman_negative_sentinel_scope()),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![real_world_flag_row(
|
||||
110,
|
||||
"Disable Stock Buying and Selling",
|
||||
true,
|
||||
)],
|
||||
decoded_conditions: vec![RuntimeCondition::ChairmanNumericThreshold {
|
||||
target: RuntimeChairmanTarget::SelectedChairman,
|
||||
metric: crate::RuntimeChairmanMetric::CurrentCash,
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 500,
|
||||
}],
|
||||
decoded_actions: vec![RuntimeEffect::SetWorldFlag {
|
||||
key: "world.chairman_condition_imported".to_string(),
|
||||
value: true,
|
||||
}],
|
||||
executable_import_ready: true,
|
||||
notes: vec!["chairman metric condition gates a world-side effect".to_string()],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let mut import = project_save_slice_overlay_to_runtime_state_import(
|
||||
&base_state,
|
||||
&save_slice,
|
||||
"chairman-condition-overlay",
|
||||
None,
|
||||
)
|
||||
.expect("overlay import should project");
|
||||
|
||||
assert_eq!(import.state.event_runtime_records.len(), 1);
|
||||
assert_eq!(
|
||||
import.state.event_runtime_records[0].conditions,
|
||||
vec![RuntimeCondition::ChairmanNumericThreshold {
|
||||
target: RuntimeChairmanTarget::SelectedChairman,
|
||||
metric: crate::RuntimeChairmanMetric::CurrentCash,
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 500,
|
||||
}]
|
||||
);
|
||||
|
||||
crate::execute_step_command(
|
||||
&mut import.state,
|
||||
&crate::StepCommand::ServiceTriggerKind { trigger_kind: 6 },
|
||||
)
|
||||
.expect("chairman-gated trigger should execute");
|
||||
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
.world_flags
|
||||
.get("world.chairman_condition_imported"),
|
||||
Some(&true)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlays_book_value_conditions_into_imported_runtime_records() {
|
||||
let base_state = RuntimeState {
|
||||
calendar: CalendarPoint {
|
||||
year: 1840,
|
||||
month_slot: 1,
|
||||
phase_slot: 2,
|
||||
tick_slot: 3,
|
||||
},
|
||||
world_flags: BTreeMap::new(),
|
||||
save_profile: RuntimeSaveProfileState::default(),
|
||||
world_restore: RuntimeWorldRestoreState::default(),
|
||||
metadata: BTreeMap::new(),
|
||||
companies: vec![crate::RuntimeCompany {
|
||||
company_id: 1,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 2620,
|
||||
investor_confidence: 37,
|
||||
management_attitude: 58,
|
||||
takeover_cooldown_year: Some(1839),
|
||||
merger_cooldown_year: Some(1838),
|
||||
}],
|
||||
selected_company_id: Some(1),
|
||||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
chairman_profiles: Vec::new(),
|
||||
selected_chairman_profile_id: None,
|
||||
trains: Vec::new(),
|
||||
locomotive_catalog: Vec::new(),
|
||||
cargo_catalog: Vec::new(),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
company_territory_access: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
named_locomotive_availability: BTreeMap::new(),
|
||||
named_locomotive_cost: BTreeMap::new(),
|
||||
cargo_production_overrides: BTreeMap::new(),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState::default(),
|
||||
};
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
trailer_family: None,
|
||||
bridge_family: None,
|
||||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
cargo_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
metadata_tag_offset: 0x7100,
|
||||
records_tag_offset: 0x7200,
|
||||
close_tag_offset: 0x7600,
|
||||
packed_state_version: 0x3e9,
|
||||
packed_state_version_hex: "0x000003e9".to_string(),
|
||||
live_id_bound: 72,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![72],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 72,
|
||||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(136),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "real_packed_v1".to_string(),
|
||||
trigger_kind: Some(7),
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: Some(false),
|
||||
compact_control: Some(real_compact_control()),
|
||||
text_bands: vec![],
|
||||
standalone_condition_row_count: 1,
|
||||
standalone_condition_rows: vec![
|
||||
crate::SmpLoadedPackedEventConditionRowSummary {
|
||||
row_index: 0,
|
||||
raw_condition_id: 2620,
|
||||
subtype: 4,
|
||||
flag_bytes: {
|
||||
let mut bytes = vec![0; 25];
|
||||
bytes[0..4].copy_from_slice(&2620_i32.to_le_bytes());
|
||||
bytes
|
||||
},
|
||||
candidate_name: None,
|
||||
comparator: Some("eq".to_string()),
|
||||
metric: Some("Book Value Per Share".to_string()),
|
||||
semantic_family: Some("numeric_threshold".to_string()),
|
||||
semantic_preview: Some("Test Book Value Per Share == 2620".to_string()),
|
||||
recovered_cargo_slot: None,
|
||||
recovered_cargo_class: None,
|
||||
requires_candidate_name_binding: false,
|
||||
notes: vec![],
|
||||
},
|
||||
],
|
||||
negative_sentinel_scope: Some(company_negative_sentinel_scope(
|
||||
RuntimeCompanyConditionTestScope::SelectedCompanyOnly,
|
||||
)),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![real_world_flag_row(
|
||||
110,
|
||||
"Disable Stock Buying and Selling",
|
||||
true,
|
||||
)],
|
||||
decoded_conditions: vec![RuntimeCondition::CompanyNumericThreshold {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
metric: crate::RuntimeCompanyMetric::BookValuePerShare,
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 2620,
|
||||
}],
|
||||
decoded_actions: vec![RuntimeEffect::SetWorldFlag {
|
||||
key: "world.book_value_condition_imported".to_string(),
|
||||
value: true,
|
||||
}],
|
||||
executable_import_ready: true,
|
||||
notes: vec![
|
||||
"book value per share condition gates a world-side effect".to_string(),
|
||||
],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let mut import = project_save_slice_overlay_to_runtime_state_import(
|
||||
&base_state,
|
||||
&save_slice,
|
||||
"company-book-value-condition-overlay",
|
||||
None,
|
||||
)
|
||||
.expect("overlay import should project");
|
||||
|
||||
assert_eq!(import.state.event_runtime_records.len(), 1);
|
||||
assert_eq!(
|
||||
import.state.event_runtime_records[0].conditions,
|
||||
vec![RuntimeCondition::CompanyNumericThreshold {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
metric: crate::RuntimeCompanyMetric::BookValuePerShare,
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 2620,
|
||||
}]
|
||||
);
|
||||
|
||||
crate::execute_step_command(
|
||||
&mut import.state,
|
||||
&crate::StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||
)
|
||||
.expect("company-governance trigger should execute");
|
||||
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
.world_flags
|
||||
.get("world.book_value_condition_imported"),
|
||||
Some(&true)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlays_recovered_world_toggle_batch_into_executable_runtime_record() {
|
||||
let base_state = state();
|
||||
|
|
@ -9058,6 +9491,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
crate::RuntimeCompany {
|
||||
company_id: 7,
|
||||
|
|
@ -9069,6 +9508,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
],
|
||||
selected_company_id: Some(42),
|
||||
|
|
@ -9272,6 +9717,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
..state()
|
||||
|
|
@ -9377,6 +9828,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
crate::RuntimeCompany {
|
||||
company_id: 7,
|
||||
|
|
@ -9388,6 +9845,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
],
|
||||
selected_company_id: Some(42),
|
||||
|
|
@ -9533,6 +9996,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
trains: vec![
|
||||
|
|
@ -9767,6 +10236,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
trains: vec![RuntimeTrain {
|
||||
|
|
@ -9886,6 +10361,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
..state()
|
||||
|
|
@ -10004,6 +10485,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
players: Vec::new(),
|
||||
|
|
@ -10190,6 +10677,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
players: Vec::new(),
|
||||
|
|
|
|||
|
|
@ -33,6 +33,18 @@ pub struct RuntimeCompany {
|
|||
#[serde(default)]
|
||||
pub controller_kind: RuntimeCompanyControllerKind,
|
||||
#[serde(default)]
|
||||
pub linked_chairman_profile_id: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub book_value_per_share: i64,
|
||||
#[serde(default)]
|
||||
pub investor_confidence: i64,
|
||||
#[serde(default)]
|
||||
pub management_attitude: i64,
|
||||
#[serde(default)]
|
||||
pub takeover_cooldown_year: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub merger_cooldown_year: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub track_piece_counts: RuntimeTrackPieceCounts,
|
||||
}
|
||||
|
||||
|
|
@ -235,6 +247,9 @@ pub enum RuntimeCompanyMetric {
|
|||
TotalDebt,
|
||||
CreditRating,
|
||||
PrimeRate,
|
||||
BookValuePerShare,
|
||||
InvestorConfidence,
|
||||
ManagementAttitude,
|
||||
TrackPiecesTotal,
|
||||
TrackPiecesSingle,
|
||||
TrackPiecesDouble,
|
||||
|
|
@ -918,6 +933,46 @@ impl RuntimeState {
|
|||
));
|
||||
}
|
||||
}
|
||||
for company in &self.companies {
|
||||
if let Some(linked_chairman_profile_id) = company.linked_chairman_profile_id {
|
||||
let linked_profile = self
|
||||
.chairman_profiles
|
||||
.iter()
|
||||
.find(|profile| profile.profile_id == linked_chairman_profile_id)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"company {} references unknown linked_chairman_profile_id {}",
|
||||
company.company_id, linked_chairman_profile_id
|
||||
)
|
||||
})?;
|
||||
if linked_profile.linked_company_id != Some(company.company_id) {
|
||||
return Err(format!(
|
||||
"company {} linked_chairman_profile_id {} must point back through linked_company_id",
|
||||
company.company_id, linked_chairman_profile_id
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
for chairman in &self.chairman_profiles {
|
||||
if let Some(linked_company_id) = chairman.linked_company_id {
|
||||
let linked_company = self
|
||||
.companies
|
||||
.iter()
|
||||
.find(|company| company.company_id == linked_company_id)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"chairman_profile {} references unknown linked_company_id {}",
|
||||
chairman.profile_id, linked_company_id
|
||||
)
|
||||
})?;
|
||||
if linked_company.linked_chairman_profile_id != Some(chairman.profile_id) {
|
||||
return Err(format!(
|
||||
"chairman_profile {} linked_company_id {} must point back through linked_chairman_profile_id",
|
||||
chairman.profile_id, linked_company_id
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(selected_player_id) = self.selected_player_id {
|
||||
if !seen_player_ids.contains(&selected_player_id) {
|
||||
return Err(format!(
|
||||
|
|
@ -1772,6 +1827,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
},
|
||||
RuntimeCompany {
|
||||
|
|
@ -1783,6 +1844,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
},
|
||||
],
|
||||
|
|
@ -1895,6 +1962,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
|
|
@ -1956,6 +2029,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
|
|
@ -2129,6 +2208,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_company_id: Some(2),
|
||||
|
|
@ -2177,6 +2262,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: false,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_company_id: Some(1),
|
||||
|
|
@ -2225,6 +2316,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
|
|
@ -2290,6 +2387,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
|
|
@ -2345,6 +2448,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
|
|
@ -2404,6 +2513,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
|
|
@ -2459,6 +2574,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
|
|
@ -2520,6 +2641,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
|
|
@ -2575,6 +2702,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
|
|
@ -2607,4 +2740,122 @@ mod tests {
|
|||
|
||||
assert!(state.validate().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_company_with_unknown_linked_chairman_profile() {
|
||||
let state = RuntimeState {
|
||||
calendar: CalendarPoint {
|
||||
year: 1830,
|
||||
month_slot: 0,
|
||||
phase_slot: 0,
|
||||
tick_slot: 0,
|
||||
},
|
||||
world_flags: BTreeMap::new(),
|
||||
save_profile: RuntimeSaveProfileState::default(),
|
||||
world_restore: RuntimeWorldRestoreState::default(),
|
||||
metadata: BTreeMap::new(),
|
||||
companies: vec![RuntimeCompany {
|
||||
company_id: 1,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: Some(9),
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
chairman_profiles: Vec::new(),
|
||||
selected_chairman_profile_id: None,
|
||||
trains: Vec::new(),
|
||||
locomotive_catalog: Vec::new(),
|
||||
cargo_catalog: Vec::new(),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
company_territory_access: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
named_locomotive_availability: BTreeMap::new(),
|
||||
named_locomotive_cost: BTreeMap::new(),
|
||||
cargo_production_overrides: BTreeMap::new(),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState::default(),
|
||||
};
|
||||
|
||||
assert!(state.validate().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_mismatched_company_chairman_back_links() {
|
||||
let state = RuntimeState {
|
||||
calendar: CalendarPoint {
|
||||
year: 1830,
|
||||
month_slot: 0,
|
||||
phase_slot: 0,
|
||||
tick_slot: 0,
|
||||
},
|
||||
world_flags: BTreeMap::new(),
|
||||
save_profile: RuntimeSaveProfileState::default(),
|
||||
world_restore: RuntimeWorldRestoreState::default(),
|
||||
metadata: BTreeMap::new(),
|
||||
companies: vec![RuntimeCompany {
|
||||
company_id: 1,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: Some(1),
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
chairman_profiles: vec![RuntimeChairmanProfile {
|
||||
profile_id: 1,
|
||||
name: "Chairman One".to_string(),
|
||||
active: true,
|
||||
current_cash: 0,
|
||||
linked_company_id: None,
|
||||
company_holdings: BTreeMap::new(),
|
||||
holdings_value_total: 0,
|
||||
net_worth_total: 0,
|
||||
purchasing_power_total: 0,
|
||||
}],
|
||||
selected_chairman_profile_id: None,
|
||||
trains: Vec::new(),
|
||||
locomotive_catalog: Vec::new(),
|
||||
cargo_catalog: Vec::new(),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
company_territory_access: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
named_locomotive_availability: BTreeMap::new(),
|
||||
named_locomotive_cost: BTreeMap::new(),
|
||||
cargo_production_overrides: BTreeMap::new(),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState::default(),
|
||||
};
|
||||
|
||||
assert!(state.validate().is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@ use serde::{Deserialize, Serialize};
|
|||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::{
|
||||
RuntimeCargoClass, RuntimeChairmanTarget, RuntimeCompanyConditionTestScope,
|
||||
RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition, RuntimeConditionComparator,
|
||||
RuntimeEffect, RuntimeEventRecordTemplate, RuntimePlayerConditionTestScope,
|
||||
RuntimePlayerTarget, RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric,
|
||||
RuntimeCargoClass, RuntimeChairmanMetric, RuntimeChairmanTarget,
|
||||
RuntimeCompanyConditionTestScope, RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition,
|
||||
RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecordTemplate,
|
||||
RuntimePlayerConditionTestScope, RuntimePlayerTarget, RuntimeTerritoryMetric,
|
||||
RuntimeTerritoryTarget, RuntimeTrackMetric,
|
||||
};
|
||||
|
||||
pub const SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION: u32 = 0x03ec;
|
||||
|
|
@ -232,6 +233,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum RealOrdinaryConditionMetric {
|
||||
Company(RuntimeCompanyMetric),
|
||||
Chairman(RuntimeChairmanMetric),
|
||||
Territory(RuntimeTerritoryMetric),
|
||||
CompanyTerritory(RuntimeTrackMetric),
|
||||
}
|
||||
|
|
@ -344,6 +346,11 @@ const KNOWN_CARGO_SLOT_DEFINITIONS: [KnownCargoSlotDefinition; 11] = [
|
|||
];
|
||||
|
||||
const REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID: i32 = 435;
|
||||
const REAL_CHAIRMAN_CASH_CONDITION_ID: i32 = 2218;
|
||||
const REAL_CHAIRMAN_HOLDINGS_TOTAL_CONDITION_ID: i32 = 2239;
|
||||
const REAL_CHAIRMAN_NET_WORTH_CONDITION_ID: i32 = 2240;
|
||||
const REAL_CHAIRMAN_PURCHASING_POWER_CONDITION_ID: i32 = 1247;
|
||||
const REAL_BOOK_VALUE_PER_SHARE_CONDITION_ID: i32 = 2620;
|
||||
const REAL_CARGO_PRODUCTION_CONDITION_TEMPLATE_ID: i32 = 200;
|
||||
const REAL_NAMED_LOCOMOTIVE_AVAILABILITY_CONDITION_ID: i32 = 2422;
|
||||
const REAL_NAMED_LOCOMOTIVE_COST_CONDITION_ID: i32 = 2423;
|
||||
|
|
@ -354,7 +361,7 @@ const REAL_OTHER_CARGO_PRODUCTION_TOTAL_CONDITION_ID: i32 = 2421;
|
|||
const REAL_LIMITED_TRACK_BUILDING_AMOUNT_CONDITION_ID: i32 = 2547;
|
||||
const REAL_TERRITORY_ACCESS_COST_CONDITION_ID: i32 = 1516;
|
||||
|
||||
const REAL_ORDINARY_CONDITION_METADATA: [RealOrdinaryConditionMetadata; 33] = [
|
||||
const REAL_ORDINARY_CONDITION_METADATA: [RealOrdinaryConditionMetadata; 38] = [
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 1802,
|
||||
label: "Current Cash",
|
||||
|
|
@ -362,6 +369,34 @@ const REAL_ORDINARY_CONDITION_METADATA: [RealOrdinaryConditionMetadata; 33] = [
|
|||
RuntimeCompanyMetric::CurrentCash,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_CHAIRMAN_CASH_CONDITION_ID,
|
||||
label: "Player Cash",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(
|
||||
RuntimeChairmanMetric::CurrentCash,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_CHAIRMAN_HOLDINGS_TOTAL_CONDITION_ID,
|
||||
label: "Player Stock Value",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(
|
||||
RuntimeChairmanMetric::HoldingsValueTotal,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_CHAIRMAN_NET_WORTH_CONDITION_ID,
|
||||
label: "Player Net Worth",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(
|
||||
RuntimeChairmanMetric::NetWorthTotal,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_CHAIRMAN_PURCHASING_POWER_CONDITION_ID,
|
||||
label: "Purchasing Power",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(
|
||||
RuntimeChairmanMetric::PurchasingPowerTotal,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 951,
|
||||
label: "Total Debt",
|
||||
|
|
@ -383,6 +418,13 @@ const REAL_ORDINARY_CONDITION_METADATA: [RealOrdinaryConditionMetadata; 33] = [
|
|||
RuntimeCompanyMetric::PrimeRate,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_BOOK_VALUE_PER_SHARE_CONDITION_ID,
|
||||
label: "Book Value Per Share",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
||||
RuntimeCompanyMetric::BookValuePerShare,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2293,
|
||||
label: "Company Track Pieces",
|
||||
|
|
@ -3258,6 +3300,18 @@ fn decode_real_condition_row(
|
|||
value,
|
||||
})
|
||||
}
|
||||
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(metric)) => {
|
||||
negative_sentinel_scope.and_then(|scope| {
|
||||
real_condition_chairman_target(scope).map(|target| {
|
||||
RuntimeCondition::ChairmanNumericThreshold {
|
||||
target,
|
||||
metric,
|
||||
comparator,
|
||||
value,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(metric)) => {
|
||||
negative_sentinel_scope
|
||||
.filter(|scope| scope.territory_scope_selector_is_0x63)
|
||||
|
|
@ -3369,6 +3423,20 @@ fn decode_world_flag_condition(
|
|||
})
|
||||
}
|
||||
|
||||
fn real_condition_chairman_target(
|
||||
scope: &SmpLoadedPackedEventNegativeSentinelScopeSummary,
|
||||
) -> Option<RuntimeChairmanTarget> {
|
||||
match scope.player_test_scope {
|
||||
RuntimePlayerConditionTestScope::AllPlayers => Some(RuntimeChairmanTarget::AllActive),
|
||||
RuntimePlayerConditionTestScope::SelectedPlayerOnly => {
|
||||
Some(RuntimeChairmanTarget::SelectedChairman)
|
||||
}
|
||||
RuntimePlayerConditionTestScope::Disabled
|
||||
| RuntimePlayerConditionTestScope::AiPlayersOnly
|
||||
| RuntimePlayerConditionTestScope::HumanPlayersOnly => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn real_grouped_effect_descriptor_metadata(
|
||||
descriptor_id: u32,
|
||||
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
||||
|
|
@ -10397,6 +10465,102 @@ mod tests {
|
|||
assert_eq!(access_cost.label, "Access Rights Cost:");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_up_checked_in_chairman_and_governance_condition_metadata() {
|
||||
let chairman_cash = real_ordinary_condition_metadata(REAL_CHAIRMAN_CASH_CONDITION_ID)
|
||||
.expect("chairman cash condition metadata should exist");
|
||||
assert_eq!(chairman_cash.label, "Player Cash");
|
||||
|
||||
let holdings = real_ordinary_condition_metadata(REAL_CHAIRMAN_HOLDINGS_TOTAL_CONDITION_ID)
|
||||
.expect("chairman holdings condition metadata should exist");
|
||||
assert_eq!(holdings.label, "Player Stock Value");
|
||||
|
||||
let net_worth = real_ordinary_condition_metadata(REAL_CHAIRMAN_NET_WORTH_CONDITION_ID)
|
||||
.expect("chairman net worth condition metadata should exist");
|
||||
assert_eq!(net_worth.label, "Player Net Worth");
|
||||
|
||||
let purchasing_power =
|
||||
real_ordinary_condition_metadata(REAL_CHAIRMAN_PURCHASING_POWER_CONDITION_ID)
|
||||
.expect("chairman purchasing-power condition metadata should exist");
|
||||
assert_eq!(purchasing_power.label, "Purchasing Power");
|
||||
|
||||
let book_value = real_ordinary_condition_metadata(REAL_BOOK_VALUE_PER_SHARE_CONDITION_ID)
|
||||
.expect("book value condition metadata should exist");
|
||||
assert_eq!(book_value.label, "Book Value Per Share");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decodes_chairman_cash_condition_from_selected_player_scope() {
|
||||
let row = SmpLoadedPackedEventConditionRowSummary {
|
||||
row_index: 0,
|
||||
raw_condition_id: REAL_CHAIRMAN_CASH_CONDITION_ID,
|
||||
subtype: 4,
|
||||
flag_bytes: {
|
||||
let mut bytes = vec![0; 25];
|
||||
bytes[0..4].copy_from_slice(&500_i32.to_le_bytes());
|
||||
bytes
|
||||
},
|
||||
candidate_name: None,
|
||||
comparator: Some("eq".to_string()),
|
||||
metric: Some("Player Cash".to_string()),
|
||||
semantic_family: Some("numeric_threshold".to_string()),
|
||||
semantic_preview: Some("Test Player Cash == 500".to_string()),
|
||||
recovered_cargo_slot: None,
|
||||
recovered_cargo_class: None,
|
||||
requires_candidate_name_binding: false,
|
||||
notes: vec![],
|
||||
};
|
||||
let negative_scope = SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
||||
company_test_scope: RuntimeCompanyConditionTestScope::Disabled,
|
||||
player_test_scope: RuntimePlayerConditionTestScope::SelectedPlayerOnly,
|
||||
territory_scope_selector_is_0x63: false,
|
||||
source_row_indexes: vec![0],
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
decode_real_condition_row(&row, Some(&negative_scope)),
|
||||
Some(RuntimeCondition::ChairmanNumericThreshold {
|
||||
target: RuntimeChairmanTarget::SelectedChairman,
|
||||
metric: RuntimeChairmanMetric::CurrentCash,
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 500,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decodes_book_value_per_share_condition_to_company_metric() {
|
||||
let row = SmpLoadedPackedEventConditionRowSummary {
|
||||
row_index: 0,
|
||||
raw_condition_id: REAL_BOOK_VALUE_PER_SHARE_CONDITION_ID,
|
||||
subtype: 4,
|
||||
flag_bytes: {
|
||||
let mut bytes = vec![0; 25];
|
||||
bytes[0..4].copy_from_slice(&2620_i32.to_le_bytes());
|
||||
bytes
|
||||
},
|
||||
candidate_name: None,
|
||||
comparator: Some("eq".to_string()),
|
||||
metric: Some("Book Value Per Share".to_string()),
|
||||
semantic_family: Some("numeric_threshold".to_string()),
|
||||
semantic_preview: Some("Test Book Value Per Share == 2620".to_string()),
|
||||
recovered_cargo_slot: None,
|
||||
recovered_cargo_class: None,
|
||||
requires_candidate_name_binding: false,
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
decode_real_condition_row(&row, None),
|
||||
Some(RuntimeCondition::CompanyNumericThreshold {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
metric: RuntimeCompanyMetric::BookValuePerShare,
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 2620,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_up_checked_in_world_flag_descriptor_metadata() {
|
||||
let metadata =
|
||||
|
|
|
|||
|
|
@ -405,6 +405,14 @@ fn apply_runtime_effects(
|
|||
state.selected_chairman_profile_id = None;
|
||||
}
|
||||
if let Some(linked_company_id) = linked_company_id {
|
||||
if let Some(company) = state
|
||||
.companies
|
||||
.iter_mut()
|
||||
.find(|company| company.company_id == linked_company_id)
|
||||
{
|
||||
company.linked_chairman_profile_id = None;
|
||||
mutated_company_ids.insert(linked_company_id);
|
||||
}
|
||||
for other in &mut state.chairman_profiles {
|
||||
if other.profile_id != profile_id
|
||||
&& other.linked_company_id == Some(linked_company_id)
|
||||
|
|
@ -1222,6 +1230,9 @@ fn company_metric_value(company: &crate::RuntimeCompany, metric: RuntimeCompanyM
|
|||
RuntimeCompanyMetric::TotalDebt => company.debt as i64,
|
||||
RuntimeCompanyMetric::CreditRating => company.credit_rating_score.unwrap_or(0),
|
||||
RuntimeCompanyMetric::PrimeRate => company.prime_rate.unwrap_or(0),
|
||||
RuntimeCompanyMetric::BookValuePerShare => company.book_value_per_share,
|
||||
RuntimeCompanyMetric::InvestorConfidence => company.investor_confidence,
|
||||
RuntimeCompanyMetric::ManagementAttitude => company.management_attitude,
|
||||
RuntimeCompanyMetric::TrackPiecesTotal => i64::from(company.track_piece_counts.total),
|
||||
RuntimeCompanyMetric::TrackPiecesSingle => i64::from(company.track_piece_counts.single),
|
||||
RuntimeCompanyMetric::TrackPiecesDouble => i64::from(company.track_piece_counts.double),
|
||||
|
|
@ -1402,10 +1413,11 @@ mod tests {
|
|||
|
||||
use super::*;
|
||||
use crate::{
|
||||
CalendarPoint, RuntimeCompany, RuntimeCompanyControllerKind, RuntimeCompanyTarget,
|
||||
RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate, RuntimePlayer,
|
||||
RuntimeSaveProfileState, RuntimeServiceState, RuntimeTerritory, RuntimeTerritoryTarget,
|
||||
RuntimeTrackPieceCounts, RuntimeTrain, RuntimeWorldRestoreState,
|
||||
CalendarPoint, RuntimeChairmanMetric, RuntimeChairmanProfile, RuntimeChairmanTarget,
|
||||
RuntimeCompany, RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeCondition,
|
||||
RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||
RuntimePlayer, RuntimeSaveProfileState, RuntimeServiceState, RuntimeTerritory,
|
||||
RuntimeTerritoryTarget, RuntimeTrackPieceCounts, RuntimeTrain, RuntimeWorldRestoreState,
|
||||
};
|
||||
|
||||
fn state() -> RuntimeState {
|
||||
|
|
@ -1430,6 +1442,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
players: Vec::new(),
|
||||
|
|
@ -1613,6 +1631,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 2,
|
||||
|
|
@ -1624,6 +1648,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
],
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
|
|
@ -1824,6 +1854,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 2,
|
||||
|
|
@ -1835,6 +1871,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
],
|
||||
selected_company_id: Some(1),
|
||||
|
|
@ -1970,6 +2012,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 2,
|
||||
|
|
@ -1981,6 +2029,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: false,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 3,
|
||||
|
|
@ -1992,6 +2046,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
],
|
||||
event_runtime_records: vec![
|
||||
|
|
@ -2068,6 +2128,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: Some(8),
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: Some(1),
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
|
|
@ -2148,6 +2214,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 2,
|
||||
|
|
@ -2159,6 +2231,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
],
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
|
|
@ -2203,6 +2281,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 2,
|
||||
|
|
@ -2214,6 +2298,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
],
|
||||
territories: vec![
|
||||
|
|
@ -2588,6 +2678,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 30,
|
||||
|
|
@ -2982,6 +3078,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 2,
|
||||
|
|
@ -2993,6 +3095,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
],
|
||||
selected_company_id: Some(1),
|
||||
|
|
@ -3114,4 +3222,280 @@ mod tests {
|
|||
assert!(!state.trains[1].retired);
|
||||
assert!(!state.trains[2].retired);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_chairman_cash_supports_all_active_target() {
|
||||
let mut state = RuntimeState {
|
||||
chairman_profiles: vec![
|
||||
RuntimeChairmanProfile {
|
||||
profile_id: 1,
|
||||
name: "Chairman One".to_string(),
|
||||
active: true,
|
||||
current_cash: 10,
|
||||
linked_company_id: None,
|
||||
company_holdings: BTreeMap::new(),
|
||||
holdings_value_total: 0,
|
||||
net_worth_total: 0,
|
||||
purchasing_power_total: 0,
|
||||
},
|
||||
RuntimeChairmanProfile {
|
||||
profile_id: 2,
|
||||
name: "Chairman Two".to_string(),
|
||||
active: true,
|
||||
current_cash: 20,
|
||||
linked_company_id: None,
|
||||
company_holdings: BTreeMap::new(),
|
||||
holdings_value_total: 0,
|
||||
net_worth_total: 0,
|
||||
purchasing_power_total: 0,
|
||||
},
|
||||
RuntimeChairmanProfile {
|
||||
profile_id: 3,
|
||||
name: "Chairman Three".to_string(),
|
||||
active: false,
|
||||
current_cash: 30,
|
||||
linked_company_id: None,
|
||||
company_holdings: BTreeMap::new(),
|
||||
holdings_value_total: 0,
|
||||
net_worth_total: 0,
|
||||
purchasing_power_total: 0,
|
||||
},
|
||||
],
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 93,
|
||||
trigger_kind: 6,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::SetChairmanCash {
|
||||
target: RuntimeChairmanTarget::AllActive,
|
||||
value: 77,
|
||||
}],
|
||||
}],
|
||||
..state()
|
||||
};
|
||||
|
||||
execute_step_command(
|
||||
&mut state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 6 },
|
||||
)
|
||||
.expect("all-active chairman cash effect should succeed");
|
||||
|
||||
assert_eq!(state.chairman_profiles[0].current_cash, 77);
|
||||
assert_eq!(state.chairman_profiles[1].current_cash, 77);
|
||||
assert_eq!(state.chairman_profiles[2].current_cash, 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deactivate_chairman_clears_selected_and_company_links_for_ids_target() {
|
||||
let mut state = RuntimeState {
|
||||
companies: vec![
|
||||
RuntimeCompany {
|
||||
company_id: 1,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: Some(1),
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 2,
|
||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||
current_cash: 80,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: Some(2),
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
},
|
||||
],
|
||||
chairman_profiles: vec![
|
||||
RuntimeChairmanProfile {
|
||||
profile_id: 1,
|
||||
name: "Chairman One".to_string(),
|
||||
active: true,
|
||||
current_cash: 10,
|
||||
linked_company_id: Some(1),
|
||||
company_holdings: BTreeMap::new(),
|
||||
holdings_value_total: 0,
|
||||
net_worth_total: 0,
|
||||
purchasing_power_total: 0,
|
||||
},
|
||||
RuntimeChairmanProfile {
|
||||
profile_id: 2,
|
||||
name: "Chairman Two".to_string(),
|
||||
active: true,
|
||||
current_cash: 20,
|
||||
linked_company_id: Some(2),
|
||||
company_holdings: BTreeMap::new(),
|
||||
holdings_value_total: 0,
|
||||
net_worth_total: 0,
|
||||
purchasing_power_total: 0,
|
||||
},
|
||||
],
|
||||
selected_chairman_profile_id: Some(2),
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 94,
|
||||
trigger_kind: 6,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::DeactivateChairman {
|
||||
target: RuntimeChairmanTarget::Ids { ids: vec![2] },
|
||||
}],
|
||||
}],
|
||||
..state()
|
||||
};
|
||||
|
||||
execute_step_command(
|
||||
&mut state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 6 },
|
||||
)
|
||||
.expect("ids-target chairman deactivation should succeed");
|
||||
|
||||
assert!(state.chairman_profiles[0].active);
|
||||
assert!(!state.chairman_profiles[1].active);
|
||||
assert_eq!(state.chairman_profiles[1].linked_company_id, None);
|
||||
assert_eq!(state.selected_chairman_profile_id, None);
|
||||
assert_eq!(state.companies[0].linked_chairman_profile_id, Some(1));
|
||||
assert_eq!(state.companies[1].linked_chairman_profile_id, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn company_governance_metric_conditions_gate_execution() {
|
||||
let mut state = RuntimeState {
|
||||
companies: vec![RuntimeCompany {
|
||||
company_id: 1,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 2620,
|
||||
investor_confidence: 37,
|
||||
management_attitude: 58,
|
||||
takeover_cooldown_year: Some(1844),
|
||||
merger_cooldown_year: Some(1845),
|
||||
}],
|
||||
selected_company_id: Some(1),
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 95,
|
||||
trigger_kind: 6,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: vec![RuntimeCondition::CompanyNumericThreshold {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
metric: crate::RuntimeCompanyMetric::BookValuePerShare,
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 2620,
|
||||
}],
|
||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||
key: "world.book_value_gate_passed".to_string(),
|
||||
value: true,
|
||||
}],
|
||||
}],
|
||||
..state()
|
||||
};
|
||||
|
||||
execute_step_command(
|
||||
&mut state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 6 },
|
||||
)
|
||||
.expect("book-value company condition should gate execution");
|
||||
|
||||
assert_eq!(
|
||||
state.world_flags.get("world.book_value_gate_passed"),
|
||||
Some(&true)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chairman_metric_conditions_support_all_active_target() {
|
||||
let mut state = RuntimeState {
|
||||
chairman_profiles: vec![
|
||||
RuntimeChairmanProfile {
|
||||
profile_id: 1,
|
||||
name: "Chairman One".to_string(),
|
||||
active: true,
|
||||
current_cash: 20,
|
||||
linked_company_id: None,
|
||||
company_holdings: BTreeMap::new(),
|
||||
holdings_value_total: 500,
|
||||
net_worth_total: 700,
|
||||
purchasing_power_total: 900,
|
||||
},
|
||||
RuntimeChairmanProfile {
|
||||
profile_id: 2,
|
||||
name: "Chairman Two".to_string(),
|
||||
active: true,
|
||||
current_cash: 30,
|
||||
linked_company_id: None,
|
||||
company_holdings: BTreeMap::new(),
|
||||
holdings_value_total: 600,
|
||||
net_worth_total: 800,
|
||||
purchasing_power_total: 1000,
|
||||
},
|
||||
],
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 96,
|
||||
trigger_kind: 6,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: vec![RuntimeCondition::ChairmanNumericThreshold {
|
||||
target: RuntimeChairmanTarget::AllActive,
|
||||
metric: RuntimeChairmanMetric::PurchasingPowerTotal,
|
||||
comparator: RuntimeConditionComparator::Ge,
|
||||
value: 900,
|
||||
}],
|
||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||
key: "world.chairman_gate_passed".to_string(),
|
||||
value: true,
|
||||
}],
|
||||
}],
|
||||
..state()
|
||||
};
|
||||
|
||||
execute_step_command(
|
||||
&mut state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 6 },
|
||||
)
|
||||
.expect("all-active chairman condition should gate execution");
|
||||
|
||||
assert_eq!(
|
||||
state.world_flags.get("world.chairman_gate_passed"),
|
||||
Some(&true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ pub struct RuntimeSummary {
|
|||
pub chairman_profile_count: usize,
|
||||
pub active_chairman_profile_count: usize,
|
||||
pub selected_chairman_profile_id: Option<u32>,
|
||||
pub linked_chairman_company_count: usize,
|
||||
pub company_takeover_cooldown_count: usize,
|
||||
pub company_merger_cooldown_count: usize,
|
||||
pub train_count: usize,
|
||||
pub active_train_count: usize,
|
||||
pub retired_train_count: usize,
|
||||
|
|
@ -181,6 +184,21 @@ impl RuntimeSummary {
|
|||
.filter(|profile| profile.active)
|
||||
.count(),
|
||||
selected_chairman_profile_id: state.selected_chairman_profile_id,
|
||||
linked_chairman_company_count: state
|
||||
.companies
|
||||
.iter()
|
||||
.filter(|company| company.linked_chairman_profile_id.is_some())
|
||||
.count(),
|
||||
company_takeover_cooldown_count: state
|
||||
.companies
|
||||
.iter()
|
||||
.filter(|company| company.takeover_cooldown_year.is_some())
|
||||
.count(),
|
||||
company_merger_cooldown_count: state
|
||||
.companies
|
||||
.iter()
|
||||
.filter(|company| company.merger_cooldown_year.is_some())
|
||||
.count(),
|
||||
train_count: state.trains.len(),
|
||||
active_train_count: state.trains.iter().filter(|train| train.active).count(),
|
||||
retired_train_count: state.trains.iter().filter(|train| train.retired).count(),
|
||||
|
|
@ -934,6 +952,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
},
|
||||
RuntimeCompany {
|
||||
|
|
@ -945,6 +969,12 @@ mod tests {
|
|||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: false,
|
||||
available_track_laying_capacity: Some(7),
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||
},
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue