Execute runtime variable event descriptors

This commit is contained in:
Jan Petykiewicz 2026-04-17 08:18:34 -07:00
commit d3790c2ae3
17 changed files with 1300 additions and 79 deletions

View file

@ -292,6 +292,10 @@ pub fn project_save_slice_to_runtime_state_import(
farm_mine_cargo_production_override: projection.farm_mine_cargo_production_override,
named_cargo_production_overrides: projection.named_cargo_production_overrides,
cargo_production_overrides: projection.cargo_production_overrides,
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: projection.world_scalar_overrides,
special_conditions: projection.special_conditions,
service_state: RuntimeServiceState::default(),
@ -386,6 +390,10 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
farm_mine_cargo_production_override: base_state.farm_mine_cargo_production_override,
named_cargo_production_overrides: base_state.named_cargo_production_overrides.clone(),
cargo_production_overrides: base_state.cargo_production_overrides.clone(),
world_runtime_variables: base_state.world_runtime_variables.clone(),
company_runtime_variables: base_state.company_runtime_variables.clone(),
player_runtime_variables: base_state.player_runtime_variables.clone(),
territory_runtime_variables: base_state.territory_runtime_variables.clone(),
world_scalar_overrides: base_state.world_scalar_overrides.clone(),
special_conditions: projection.special_conditions,
service_state: base_state.service_state.clone(),
@ -1362,6 +1370,10 @@ fn lower_contextual_real_grouped_effects(
effects.push(effect);
continue;
}
if let Some(effect) = lower_contextual_runtime_variable_effect(row)? {
effects.push(effect);
continue;
}
if let Some(effect) = lower_contextual_cargo_production_effect(row)? {
effects.push(effect);
continue;
@ -1436,6 +1448,25 @@ fn lower_contextual_world_scalar_override_effect(
}))
}
fn lower_contextual_runtime_variable_effect(
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
) -> Result<Option<RuntimeEffect>, ImportBlocker> {
if row.parameter_family.as_deref() != Some("runtime_variable_scalar") {
return Ok(None);
}
if row.row_shape != "scalar_assignment" {
return Ok(None);
}
let value = i64::from(row.raw_scalar_value);
Ok(match row.descriptor_id {
39..=42 => Some(RuntimeEffect::SetWorldVariable {
index: row.descriptor_id - 38,
value,
}),
_ => None,
})
}
fn lower_contextual_locomotive_availability_effect(
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
company_context: &ImportRuntimeContext,
@ -1682,12 +1713,28 @@ fn lower_condition_targets_in_effect(
value: *value,
}
}
RuntimeEffect::SetWorldVariable { index, value } => RuntimeEffect::SetWorldVariable {
index: *index,
value: *value,
},
RuntimeEffect::SetLimitedTrackBuildingAmount { value } => {
RuntimeEffect::SetLimitedTrackBuildingAmount { value: *value }
}
RuntimeEffect::SetEconomicStatusCode { value } => {
RuntimeEffect::SetEconomicStatusCode { value: *value }
}
RuntimeEffect::SetCompanyVariable {
target,
index,
value,
} => RuntimeEffect::SetCompanyVariable {
target: lower_condition_true_company_target_in_company_target(
target,
lowered_company_target,
)?,
index: *index,
value: *value,
},
RuntimeEffect::SetCompanyCash { target, value } => RuntimeEffect::SetCompanyCash {
target: lower_condition_true_company_target_in_company_target(
target,
@ -1695,6 +1742,18 @@ fn lower_condition_targets_in_effect(
)?,
value: *value,
},
RuntimeEffect::SetPlayerVariable {
target,
index,
value,
} => RuntimeEffect::SetPlayerVariable {
target: lower_condition_true_player_target_in_player_target(
target,
lowered_player_target,
)?,
index: *index,
value: *value,
},
RuntimeEffect::SetPlayerCash { target, value } => RuntimeEffect::SetPlayerCash {
target: lower_condition_true_player_target_in_player_target(
target,
@ -1823,6 +1882,15 @@ fn lower_condition_targets_in_effect(
value: *value,
}
}
RuntimeEffect::SetTerritoryVariable {
target,
index,
value,
} => RuntimeEffect::SetTerritoryVariable {
target: target.clone(),
index: *index,
value: *value,
},
RuntimeEffect::SetCargoProductionOverride { target, value } => {
RuntimeEffect::SetCargoProductionOverride {
target: target.clone(),
@ -2220,12 +2288,35 @@ fn smp_runtime_effect_to_runtime_effect(
key: key.clone(),
value: *value,
}),
RuntimeEffect::SetWorldVariable { index, value } => Ok(RuntimeEffect::SetWorldVariable {
index: *index,
value: *value,
}),
RuntimeEffect::SetLimitedTrackBuildingAmount { value } => {
Ok(RuntimeEffect::SetLimitedTrackBuildingAmount { value: *value })
}
RuntimeEffect::SetEconomicStatusCode { value } => {
Ok(RuntimeEffect::SetEconomicStatusCode { value: *value })
}
RuntimeEffect::SetCompanyVariable {
target,
index,
value,
} => {
if company_target_allowed_for_import(
target,
company_context,
allow_condition_true_company,
) {
Ok(RuntimeEffect::SetCompanyVariable {
target: target.clone(),
index: *index,
value: *value,
})
} else {
Err(company_target_import_error_message(target, company_context))
}
}
RuntimeEffect::SetCompanyCash { target, value } => {
if company_target_allowed_for_import(
target,
@ -2240,6 +2331,25 @@ fn smp_runtime_effect_to_runtime_effect(
Err(company_target_import_error_message(target, company_context))
}
}
RuntimeEffect::SetPlayerVariable {
target,
index,
value,
} => {
if player_target_allowed_for_import(
target,
company_context,
allow_condition_true_player,
) {
Ok(RuntimeEffect::SetPlayerVariable {
target: target.clone(),
index: *index,
value: *value,
})
} else {
Err(player_target_import_error_message(target, company_context))
}
}
RuntimeEffect::SetPlayerCash { target, value } => {
if player_target_allowed_for_import(
target,
@ -2254,6 +2364,21 @@ fn smp_runtime_effect_to_runtime_effect(
Err(player_target_import_error_message(target, company_context))
}
}
RuntimeEffect::SetTerritoryVariable {
target,
index,
value,
} => {
if territory_target_import_blocker(target, company_context).is_none() {
Ok(RuntimeEffect::SetTerritoryVariable {
target: target.clone(),
index: *index,
value: *value,
})
} else {
Err("packed effect requires territory runtime context".to_string())
}
}
RuntimeEffect::SetChairmanCash { target, value } => {
if chairman_target_import_blocker(target, company_context).is_none() {
Ok(RuntimeEffect::SetChairmanCash {
@ -2998,6 +3123,7 @@ fn real_grouped_row_is_unsupported_executable_descriptor_variant(
56 | 57 => row.row_shape != "scalar_assignment",
_ => match row.parameter_family.as_deref() {
Some("world_scalar_override") => row.row_shape != "scalar_assignment",
Some("runtime_variable_scalar") => row.row_shape != "scalar_assignment",
Some("locomotive_availability_scalar") => {
!(row.row_shape == "scalar_assignment" && row.raw_scalar_value >= 0)
}
@ -3190,6 +3316,7 @@ fn real_grouped_row_is_unsupported_retire_train_scope(
fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool {
match effect {
RuntimeEffect::SetCompanyCash { target, .. }
| RuntimeEffect::SetCompanyVariable { target, .. }
| RuntimeEffect::SetCompanyGovernanceScalar { target, .. }
| RuntimeEffect::SetCompanyTerritoryAccess { target, .. }
| RuntimeEffect::ConfiscateCompanyAssets { target }
@ -3208,10 +3335,12 @@ fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool {
.iter()
.any(runtime_effect_uses_condition_true_company),
RuntimeEffect::SetWorldFlag { .. }
| RuntimeEffect::SetWorldVariable { .. }
| RuntimeEffect::SetWorldScalarOverride { .. }
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
| RuntimeEffect::SetEconomicStatusCode { .. }
| RuntimeEffect::SetPlayerCash { .. }
| RuntimeEffect::SetPlayerVariable { .. }
| RuntimeEffect::SetChairmanCash { .. }
| RuntimeEffect::DeactivatePlayer { .. }
| RuntimeEffect::DeactivateChairman { .. }
@ -3222,6 +3351,7 @@ fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool {
| RuntimeEffect::SetCargoPriceOverride { .. }
| RuntimeEffect::SetCargoProductionOverride { .. }
| RuntimeEffect::SetCargoProductionSlot { .. }
| RuntimeEffect::SetTerritoryVariable { .. }
| RuntimeEffect::SetTerritoryAccessCost { .. }
| RuntimeEffect::SetSpecialCondition { .. }
| RuntimeEffect::ActivateEventRecord { .. }
@ -3232,7 +3362,8 @@ fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool {
fn runtime_effect_uses_condition_true_player(effect: &RuntimeEffect) -> bool {
match effect {
RuntimeEffect::SetPlayerCash { target, .. } => {
RuntimeEffect::SetPlayerCash { target, .. }
| RuntimeEffect::SetPlayerVariable { target, .. } => {
matches!(target, RuntimePlayerTarget::ConditionTruePlayer)
}
RuntimeEffect::DeactivatePlayer { target } => {
@ -3274,6 +3405,7 @@ fn runtime_effect_company_target_import_blocker(
) -> Option<ImportBlocker> {
match effect {
RuntimeEffect::SetCompanyCash { target, .. }
| RuntimeEffect::SetCompanyVariable { target, .. }
| RuntimeEffect::SetCompanyGovernanceScalar { target, .. }
| RuntimeEffect::SetCompanyTerritoryAccess { target, .. }
| RuntimeEffect::ConfiscateCompanyAssets { target }
@ -3293,9 +3425,13 @@ fn runtime_effect_company_target_import_blocker(
}
}
RuntimeEffect::SetPlayerCash { target, .. }
| RuntimeEffect::SetPlayerVariable { target, .. }
| RuntimeEffect::DeactivatePlayer { target } => {
player_target_import_blocker(target, company_context)
}
RuntimeEffect::SetTerritoryVariable { target, .. } => {
territory_target_import_blocker(target, company_context)
}
RuntimeEffect::SetChairmanCash { target, .. }
| RuntimeEffect::DeactivateChairman { target } => {
chairman_target_import_blocker(target, company_context)
@ -3324,6 +3460,7 @@ fn runtime_effect_company_target_import_blocker(
runtime_effect_company_target_import_blocker(nested, company_context)
}),
RuntimeEffect::SetWorldFlag { .. }
| RuntimeEffect::SetWorldVariable { .. }
| RuntimeEffect::SetWorldScalarOverride { .. }
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
| RuntimeEffect::SetEconomicStatusCode { .. }
@ -3689,6 +3826,10 @@ mod tests {
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
@ -4245,6 +4386,9 @@ mod tests {
56 => Some("Trans-Euro"),
57 => Some("V200"),
58 => Some("VL80T"),
59 => Some("GP 35"),
60 => Some("U1"),
61 => Some("Zephyr"),
_ => None,
}
}
@ -4361,6 +4505,9 @@ mod tests {
56 => Some("Trans-Euro"),
57 => Some("V200"),
58 => Some("VL80T"),
59 => Some("GP 35"),
60 => Some("U1"),
61 => Some("Zephyr"),
_ => None,
}
}
@ -4471,6 +4618,9 @@ mod tests {
55 => "Trans-Euro",
56 => "V200",
57 => "VL80T",
58 => "GP 35",
59 => "U1",
60 => "Zephyr",
_ => return format!("Locomotive {}", index + 1),
}
.to_string()
@ -7125,7 +7275,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: Some(save_named_locomotive_table(58)),
named_locomotive_availability_table: Some(save_named_locomotive_table(61)),
locomotive_catalog: None,
cargo_catalog: None,
company_roster: None,
@ -7165,7 +7315,7 @@ mod tests {
grouped_effect_row_counts: vec![2, 0, 0, 0],
grouped_effect_rows: vec![
real_locomotive_availability_row(250, 42),
real_locomotive_availability_row(298, 7),
real_locomotive_availability_row(301, 7),
],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
@ -7186,7 +7336,7 @@ mod tests {
)
.expect("save slice should project");
assert_eq!(import.state.locomotive_catalog.len(), 58);
assert_eq!(import.state.locomotive_catalog.len(), 61);
assert_eq!(import.state.event_runtime_records.len(), 1);
assert_eq!(
import
@ -7211,7 +7361,7 @@ mod tests {
Some(&42)
);
assert_eq!(
import.state.named_locomotive_availability.get("VL80T"),
import.state.named_locomotive_availability.get("Zephyr"),
Some(&7)
);
}
@ -7242,8 +7392,8 @@ mod tests {
name: "Big Boy 4-8-8-4".to_string(),
},
crate::RuntimeLocomotiveCatalogEntry {
locomotive_id: 58,
name: "VL80T".to_string(),
locomotive_id: 61,
name: "Zephyr".to_string(),
},
],
cargo_catalog: Vec::new(),
@ -7255,7 +7405,7 @@ mod tests {
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::from([
("Big Boy 4-8-8-4".to_string(), 0),
("VL80T".to_string(), 1),
("Zephyr".to_string(), 1),
]),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
@ -7265,6 +7415,10 @@ mod tests {
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
@ -7318,7 +7472,7 @@ mod tests {
grouped_effect_row_counts: vec![2, 0, 0, 0],
grouped_effect_rows: vec![
real_locomotive_availability_row(250, 42),
real_locomotive_availability_row(298, 7),
real_locomotive_availability_row(301, 7),
],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
@ -7364,7 +7518,7 @@ mod tests {
Some(&42)
);
assert_eq!(
import.state.named_locomotive_availability.get("VL80T"),
import.state.named_locomotive_availability.get("Zephyr"),
Some(&7)
);
}
@ -7538,7 +7692,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: Some(save_named_locomotive_table(58)),
named_locomotive_availability_table: Some(save_named_locomotive_table(61)),
locomotive_catalog: None,
cargo_catalog: None,
company_roster: None,
@ -7578,7 +7732,7 @@ mod tests {
grouped_effect_row_counts: vec![2, 0, 0, 0],
grouped_effect_rows: vec![
real_locomotive_cost_row(352, 250000),
real_locomotive_cost_row(409, 325000),
real_locomotive_cost_row(412, 325000),
],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
@ -7598,7 +7752,7 @@ mod tests {
)
.expect("save slice should project");
assert_eq!(import.state.locomotive_catalog.len(), 58);
assert_eq!(import.state.locomotive_catalog.len(), 61);
assert_eq!(import.state.event_runtime_records.len(), 1);
assert_eq!(
import
@ -7620,7 +7774,7 @@ mod tests {
Some(&250000)
);
assert_eq!(
import.state.named_locomotive_cost.get("VL80T"),
import.state.named_locomotive_cost.get("Zephyr"),
Some(&325000)
);
}
@ -7651,8 +7805,8 @@ mod tests {
name: "2-D-2".to_string(),
},
crate::RuntimeLocomotiveCatalogEntry {
locomotive_id: 58,
name: "VL80T".to_string(),
locomotive_id: 61,
name: "Zephyr".to_string(),
},
],
cargo_catalog: Vec::new(),
@ -7665,7 +7819,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::from([
("2-D-2".to_string(), 100000),
("VL80T".to_string(), 200000),
("Zephyr".to_string(), 200000),
]),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
@ -7674,6 +7828,10 @@ mod tests {
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
@ -7727,7 +7885,7 @@ mod tests {
grouped_effect_row_counts: vec![2, 0, 0, 0],
grouped_effect_rows: vec![
real_locomotive_cost_row(352, 250000),
real_locomotive_cost_row(409, 325000),
real_locomotive_cost_row(412, 325000),
],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
@ -7769,7 +7927,7 @@ mod tests {
Some(&250000)
);
assert_eq!(
import.state.named_locomotive_cost.get("VL80T"),
import.state.named_locomotive_cost.get("Zephyr"),
Some(&325000)
);
}
@ -8427,6 +8585,10 @@ mod tests {
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
@ -10710,6 +10872,10 @@ mod tests {
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
@ -10890,6 +11056,10 @@ mod tests {
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
@ -12585,6 +12755,10 @@ mod tests {
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState {
@ -12776,6 +12950,10 @@ mod tests {
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),