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

@ -45,7 +45,10 @@ descriptor residue. The recovered whole-game scalar economy/performance strip `5
bounded runtime landing surface too: representative descriptors import into bounded runtime landing surface too: representative descriptors import into
`RuntimeState.world_scalar_overrides` through stable normalized keys such as `RuntimeState.world_scalar_overrides` through stable normalized keys such as
`world.build_stations_cost`, `world.track_maintenance_cost`, `world.all_engine_speeds`, and `world.build_stations_cost`, `world.track_maintenance_cost`, `world.all_engine_speeds`, and
`world.hotel_revenue`. The grounded aggregate cargo-economics descriptors now have bounded `world.hotel_revenue`. The runtime-variable strip `39..54` now executes too through bounded
event-owned scalar maps on world/company/player/territory state, without widening save-native
reconstruction or adding a second packed executor. The grounded aggregate cargo-economics
descriptors now have bounded
runtime landing surfaces too: descriptor `105` `All Cargo Prices` plus descriptors `177..179` runtime landing surfaces too: descriptor `105` `All Cargo Prices` plus descriptors `177..179`
`All Cargo Production` / `All Factory Production` / `All Farm/Mine Production` import into `All Cargo Production` / `All Factory Production` / `All Farm/Mine Production` import into
event-owned cargo override state, and the grounded named cargo-production strip `180..229` now event-owned cargo override state, and the grounded named cargo-production strip `180..229` now
@ -90,8 +93,10 @@ scalar bands are now save-native too. Raw `.smp` inspection/export reconstructs
standalone save-slice imports can now lower the grounded lower locomotive availability and standalone save-slice imports can now lower the grounded lower locomotive availability and
locomotive-cost rows directly into `RuntimeState.named_locomotive_availability` and locomotive-cost rows directly into `RuntimeState.named_locomotive_availability` and
`RuntimeState.named_locomotive_cost` without needing overlay snapshots when the save carries enough `RuntimeState.named_locomotive_cost` without needing overlay snapshots when the save carries enough
catalog context; the unresolved lower tail and upper locomotive bands now stay on explicit parity catalog context, and the grounded executable lower prefix now extends through save-backed
instead of synthetic execution. The remaining recovered scalar world families execute too: locomotive id `61` (`Zephyr`); the unresolved lower tail and upper locomotive bands now stay on
explicit parity instead of synthetic execution. The remaining recovered scalar world families
execute too:
cargo-production slots `230..240` lower into `cargo_production_overrides`, and descriptor `453` cargo-production slots `230..240` lower into `cargo_production_overrides`, and descriptor `453`
lowers into lowers into
`world_restore.territory_access_cost`. Whole-game ordinary-condition breadth now aligns with those `world_restore.territory_access_cost`. Whole-game ordinary-condition breadth now aligns with those

View file

@ -360,8 +360,8 @@
"target_mask_bits": 8, "target_mask_bits": 8,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 40, "descriptor_id": 40,
@ -369,8 +369,8 @@
"target_mask_bits": 8, "target_mask_bits": 8,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 41, "descriptor_id": 41,
@ -378,8 +378,8 @@
"target_mask_bits": 8, "target_mask_bits": 8,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 42, "descriptor_id": 42,
@ -387,8 +387,8 @@
"target_mask_bits": 8, "target_mask_bits": 8,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 43, "descriptor_id": 43,
@ -396,8 +396,8 @@
"target_mask_bits": 1, "target_mask_bits": 1,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 44, "descriptor_id": 44,
@ -405,8 +405,8 @@
"target_mask_bits": 1, "target_mask_bits": 1,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 45, "descriptor_id": 45,
@ -414,8 +414,8 @@
"target_mask_bits": 1, "target_mask_bits": 1,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 46, "descriptor_id": 46,
@ -423,8 +423,8 @@
"target_mask_bits": 1, "target_mask_bits": 1,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 47, "descriptor_id": 47,
@ -432,8 +432,8 @@
"target_mask_bits": 2, "target_mask_bits": 2,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 48, "descriptor_id": 48,
@ -441,8 +441,8 @@
"target_mask_bits": 2, "target_mask_bits": 2,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 49, "descriptor_id": 49,
@ -450,8 +450,8 @@
"target_mask_bits": 2, "target_mask_bits": 2,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 50, "descriptor_id": 50,
@ -459,8 +459,8 @@
"target_mask_bits": 2, "target_mask_bits": 2,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 51, "descriptor_id": 51,
@ -468,8 +468,8 @@
"target_mask_bits": 4, "target_mask_bits": 4,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 52, "descriptor_id": 52,
@ -477,8 +477,8 @@
"target_mask_bits": 4, "target_mask_bits": 4,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 53, "descriptor_id": 53,
@ -486,8 +486,8 @@
"target_mask_bits": 4, "target_mask_bits": 4,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 54, "descriptor_id": 54,
@ -495,8 +495,8 @@
"target_mask_bits": 4, "target_mask_bits": 4,
"parameter_family": "runtime_variable_scalar", "parameter_family": "runtime_variable_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 55, "descriptor_id": 55,
@ -2696,30 +2696,30 @@
}, },
{ {
"descriptor_id": 299, "descriptor_id": 299,
"label": "Lower-Band Locomotive Availability Slot 59", "label": "GP 35 Availability",
"target_mask_bits": 11, "target_mask_bits": 11,
"parameter_family": "locomotive_availability_scalar", "parameter_family": "locomotive_availability_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 300, "descriptor_id": 300,
"label": "Lower-Band Locomotive Availability Slot 60", "label": "U1 Availability",
"target_mask_bits": 11, "target_mask_bits": 11,
"parameter_family": "locomotive_availability_scalar", "parameter_family": "locomotive_availability_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 301, "descriptor_id": 301,
"label": "Lower-Band Locomotive Availability Slot 61", "label": "Zephyr Availability",
"target_mask_bits": 11, "target_mask_bits": 11,
"parameter_family": "locomotive_availability_scalar", "parameter_family": "locomotive_availability_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 302, "descriptor_id": 302,
@ -3695,30 +3695,30 @@
}, },
{ {
"descriptor_id": 410, "descriptor_id": 410,
"label": "Lower-Band Locomotive Cost Slot 59", "label": "GP 35 Cost",
"target_mask_bits": 11, "target_mask_bits": 11,
"parameter_family": "locomotive_cost_scalar", "parameter_family": "locomotive_cost_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 411, "descriptor_id": 411,
"label": "Lower-Band Locomotive Cost Slot 60", "label": "U1 Cost",
"target_mask_bits": 11, "target_mask_bits": 11,
"parameter_family": "locomotive_cost_scalar", "parameter_family": "locomotive_cost_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 412, "descriptor_id": 412,
"label": "Lower-Band Locomotive Cost Slot 61", "label": "Zephyr Cost",
"target_mask_bits": 11, "target_mask_bits": 11,
"parameter_family": "locomotive_cost_scalar", "parameter_family": "locomotive_cost_scalar",
"runtime_key": null, "runtime_key": null,
"runtime_status": "evidence_blocked", "runtime_status": "executable",
"executable_in_runtime": false "executable_in_runtime": true
}, },
{ {
"descriptor_id": 413, "descriptor_id": 413,

View file

@ -4479,6 +4479,8 @@ mod tests {
let world_scalar_override_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join( let world_scalar_override_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
"../../fixtures/runtime/packed-event-world-scalar-override-save-slice-fixture.json", "../../fixtures/runtime/packed-event-world-scalar-override-save-slice-fixture.json",
); );
let runtime_variable_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../fixtures/runtime/packed-event-runtime-variable-overlay-fixture.json");
let cargo_economics_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")) let cargo_economics_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../fixtures/runtime/packed-event-cargo-economics-save-slice-fixture.json"); .join("../../fixtures/runtime/packed-event-cargo-economics-save-slice-fixture.json");
let cargo_economics_parity_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join( let cargo_economics_parity_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
@ -4595,6 +4597,8 @@ mod tests {
.expect("save-slice-backed executable world-scalar fixture should summarize"); .expect("save-slice-backed executable world-scalar fixture should summarize");
run_runtime_summarize_fixture(&world_scalar_override_fixture) run_runtime_summarize_fixture(&world_scalar_override_fixture)
.expect("save-slice-backed world-scalar override fixture should summarize"); .expect("save-slice-backed world-scalar override fixture should summarize");
run_runtime_summarize_fixture(&runtime_variable_overlay_fixture)
.expect("overlay-backed runtime-variable fixture should summarize");
run_runtime_summarize_fixture(&cargo_economics_fixture) run_runtime_summarize_fixture(&cargo_economics_fixture)
.expect("save-slice-backed cargo-economics fixture should summarize"); .expect("save-slice-backed cargo-economics fixture should summarize");
run_runtime_summarize_fixture(&cargo_economics_parity_fixture) run_runtime_summarize_fixture(&cargo_economics_parity_fixture)

View file

@ -196,6 +196,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -390,6 +394,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),

View file

@ -188,6 +188,14 @@ pub struct ExpectedRuntimeSummary {
#[serde(default)] #[serde(default)]
pub cargo_production_override_count: Option<usize>, pub cargo_production_override_count: Option<usize>,
#[serde(default)] #[serde(default)]
pub world_runtime_variable_count: Option<usize>,
#[serde(default)]
pub company_runtime_variable_owner_count: Option<usize>,
#[serde(default)]
pub player_runtime_variable_owner_count: Option<usize>,
#[serde(default)]
pub territory_runtime_variable_owner_count: Option<usize>,
#[serde(default)]
pub world_scalar_override_count: Option<usize>, pub world_scalar_override_count: Option<usize>,
#[serde(default)] #[serde(default)]
pub special_condition_count: Option<usize>, pub special_condition_count: Option<usize>,
@ -927,6 +935,38 @@ impl ExpectedRuntimeSummary {
)); ));
} }
} }
if let Some(count) = self.world_runtime_variable_count {
if actual.world_runtime_variable_count != count {
mismatches.push(format!(
"world_runtime_variable_count mismatch: expected {count}, got {}",
actual.world_runtime_variable_count
));
}
}
if let Some(count) = self.company_runtime_variable_owner_count {
if actual.company_runtime_variable_owner_count != count {
mismatches.push(format!(
"company_runtime_variable_owner_count mismatch: expected {count}, got {}",
actual.company_runtime_variable_owner_count
));
}
}
if let Some(count) = self.player_runtime_variable_owner_count {
if actual.player_runtime_variable_owner_count != count {
mismatches.push(format!(
"player_runtime_variable_owner_count mismatch: expected {count}, got {}",
actual.player_runtime_variable_owner_count
));
}
}
if let Some(count) = self.territory_runtime_variable_owner_count {
if actual.territory_runtime_variable_owner_count != count {
mismatches.push(format!(
"territory_runtime_variable_owner_count mismatch: expected {count}, got {}",
actual.territory_runtime_variable_owner_count
));
}
}
if let Some(count) = self.world_scalar_override_count { if let Some(count) = self.world_scalar_override_count {
if actual.world_scalar_override_count != count { if actual.world_scalar_override_count != count {
mismatches.push(format!( mismatches.push(format!(

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

View file

@ -116,6 +116,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),

View file

@ -491,6 +491,25 @@ pub enum RuntimeEffect {
slot: u32, slot: u32,
value: u32, value: u32,
}, },
SetWorldVariable {
index: u32,
value: i64,
},
SetCompanyVariable {
target: RuntimeCompanyTarget,
index: u32,
value: i64,
},
SetPlayerVariable {
target: RuntimePlayerTarget,
index: u32,
value: i64,
},
SetTerritoryVariable {
target: RuntimeTerritoryTarget,
index: u32,
value: i64,
},
SetWorldScalarOverride { SetWorldScalarOverride {
key: String, key: String,
value: i64, value: i64,
@ -882,6 +901,14 @@ pub struct RuntimeState {
#[serde(default)] #[serde(default)]
pub cargo_production_overrides: BTreeMap<u32, u32>, pub cargo_production_overrides: BTreeMap<u32, u32>,
#[serde(default)] #[serde(default)]
pub world_runtime_variables: BTreeMap<u32, i64>,
#[serde(default)]
pub company_runtime_variables: BTreeMap<u32, BTreeMap<u32, i64>>,
#[serde(default)]
pub player_runtime_variables: BTreeMap<u32, BTreeMap<u32, i64>>,
#[serde(default)]
pub territory_runtime_variables: BTreeMap<u32, BTreeMap<u32, i64>>,
#[serde(default)]
pub world_scalar_overrides: BTreeMap<String, i64>, pub world_scalar_overrides: BTreeMap<String, i64>,
#[serde(default)] #[serde(default)]
pub special_conditions: BTreeMap<String, u32>, pub special_conditions: BTreeMap<String, u32>,
@ -1039,7 +1066,6 @@ impl RuntimeState {
)); ));
} }
} }
let mut seen_territory_ids = BTreeSet::new(); let mut seen_territory_ids = BTreeSet::new();
let mut seen_territory_names = BTreeSet::new(); let mut seen_territory_names = BTreeSet::new();
for territory in &self.territories { for territory in &self.territories {
@ -1541,6 +1567,62 @@ impl RuntimeState {
)); ));
} }
} }
for index in self.world_runtime_variables.keys() {
if !(1..=4).contains(index) {
return Err(format!(
"world_runtime_variables contains out-of-range index {}",
index
));
}
}
for (company_id, vars) in &self.company_runtime_variables {
if !seen_company_ids.contains(company_id) {
return Err(format!(
"company_runtime_variables references unknown company_id {}",
company_id
));
}
for index in vars.keys() {
if !(1..=4).contains(index) {
return Err(format!(
"company_runtime_variables[{company_id}] contains out-of-range index {}",
index
));
}
}
}
for (player_id, vars) in &self.player_runtime_variables {
if !seen_player_ids.contains(player_id) {
return Err(format!(
"player_runtime_variables references unknown player_id {}",
player_id
));
}
for index in vars.keys() {
if !(1..=4).contains(index) {
return Err(format!(
"player_runtime_variables[{player_id}] contains out-of-range index {}",
index
));
}
}
}
for (territory_id, vars) in &self.territory_runtime_variables {
if !seen_territory_ids.contains(territory_id) {
return Err(format!(
"territory_runtime_variables references unknown territory_id {}",
territory_id
));
}
for index in vars.keys() {
if !(1..=4).contains(index) {
return Err(format!(
"territory_runtime_variables[{territory_id}] contains out-of-range index {}",
index
));
}
}
}
for key in self.world_scalar_overrides.keys() { for key in self.world_scalar_overrides.keys() {
if key.trim().is_empty() { if key.trim().is_empty() {
return Err("world_scalar_overrides contains an empty key".to_string()); return Err("world_scalar_overrides contains an empty key".to_string());
@ -1569,6 +1651,13 @@ fn validate_runtime_effect(
return Err("key must not be empty".to_string()); return Err("key must not be empty".to_string());
} }
} }
RuntimeEffect::SetWorldVariable { index, .. } => {
if !(1..=4).contains(index) {
return Err(format!(
"world runtime variable index {index} must be in 1..=4"
));
}
}
RuntimeEffect::SetWorldScalarOverride { key, .. } => { RuntimeEffect::SetWorldScalarOverride { key, .. } => {
if key.trim().is_empty() { if key.trim().is_empty() {
return Err("key must not be empty".to_string()); return Err("key must not be empty".to_string());
@ -1576,6 +1665,12 @@ fn validate_runtime_effect(
} }
RuntimeEffect::SetLimitedTrackBuildingAmount { .. } RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
| RuntimeEffect::SetEconomicStatusCode { .. } => {} | RuntimeEffect::SetEconomicStatusCode { .. } => {}
RuntimeEffect::SetCompanyVariable { target, index, .. } => {
validate_company_target(target, valid_company_ids)?;
if !(1..=4).contains(index) {
return Err(format!("runtime variable index {index} must be in 1..=4"));
}
}
RuntimeEffect::SetCompanyCash { target, .. } RuntimeEffect::SetCompanyCash { target, .. }
| RuntimeEffect::ConfiscateCompanyAssets { target } | RuntimeEffect::ConfiscateCompanyAssets { target }
| RuntimeEffect::DeactivateCompany { target } | RuntimeEffect::DeactivateCompany { target }
@ -1594,6 +1689,12 @@ fn validate_runtime_effect(
validate_company_target(target, valid_company_ids)?; validate_company_target(target, valid_company_ids)?;
validate_territory_target(territory, valid_territory_ids)?; validate_territory_target(territory, valid_territory_ids)?;
} }
RuntimeEffect::SetPlayerVariable { target, index, .. } => {
validate_player_target(target, valid_player_ids)?;
if !(1..=4).contains(index) {
return Err(format!("runtime variable index {index} must be in 1..=4"));
}
}
RuntimeEffect::SetPlayerCash { target, .. } RuntimeEffect::SetPlayerCash { target, .. }
| RuntimeEffect::DeactivatePlayer { target } => { | RuntimeEffect::DeactivatePlayer { target } => {
validate_player_target(target, valid_player_ids)?; validate_player_target(target, valid_player_ids)?;
@ -1669,6 +1770,12 @@ fn validate_runtime_effect(
return Err("slot must be in 1..=11".to_string()); return Err("slot must be in 1..=11".to_string());
} }
} }
RuntimeEffect::SetTerritoryVariable { target, index, .. } => {
validate_territory_target(target, valid_territory_ids)?;
if !(1..=4).contains(index) {
return Err(format!("runtime variable index {index} must be in 1..=4"));
}
}
RuntimeEffect::SetTerritoryAccessCost { .. } => {} RuntimeEffect::SetTerritoryAccessCost { .. } => {}
RuntimeEffect::SetSpecialCondition { label, .. } => { RuntimeEffect::SetSpecialCondition { label, .. } => {
if label.trim().is_empty() { if label.trim().is_empty() {
@ -1987,6 +2094,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -2057,6 +2168,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -2131,6 +2246,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -2215,6 +2334,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -2324,6 +2447,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -2385,6 +2512,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -2446,6 +2577,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -2524,6 +2659,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -2592,6 +2731,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -2664,6 +2807,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -2732,6 +2879,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -2806,6 +2957,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -2874,6 +3029,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -2942,6 +3101,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -3003,6 +3166,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -3074,6 +3241,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),

View file

@ -446,7 +446,7 @@ const KNOWN_CARGO_SLOT_DEFINITIONS: [KnownCargoSlotDefinition; 11] = [
}, },
]; ];
const GROUNDED_LOCOMOTIVE_PREFIX: [&str; 58] = [ const GROUNDED_LOCOMOTIVE_PREFIX: [&str; 61] = [
"2-D-2", "2-D-2",
"E-88", "E-88",
"Adler 2-2-2", "Adler 2-2-2",
@ -505,6 +505,9 @@ const GROUNDED_LOCOMOTIVE_PREFIX: [&str; 58] = [
"Trans-Euro", "Trans-Euro",
"V200", "V200",
"VL80T", "VL80T",
"GP 35",
"U1",
"Zephyr",
]; ];
const REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID: i32 = 435; const REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID: i32 = 435;
@ -4313,6 +4316,7 @@ fn derive_real_grouped_target_subject(
match row.target_mask_bits { match row.target_mask_bits {
Some(0x08) => Some(RealGroupedTargetSubject::WholeGame), Some(0x08) => Some(RealGroupedTargetSubject::WholeGame),
Some(0x01) => Some(RealGroupedTargetSubject::Company), Some(0x01) => Some(RealGroupedTargetSubject::Company),
Some(0x04) => Some(RealGroupedTargetSubject::Territory),
Some(0x02) => match compact_control Some(0x02) => match compact_control
.grouped_scope_checkboxes_0x7ff .grouped_scope_checkboxes_0x7ff
.get(row.group_index) .get(row.group_index)
@ -4401,6 +4405,16 @@ fn real_grouped_chairman_scope_name(ordinal: u8) -> &'static str {
} }
} }
fn runtime_variable_index(descriptor_id: u32) -> Option<u32> {
match descriptor_id {
39..=42 => Some(descriptor_id - 38),
43..=46 => Some(descriptor_id - 42),
47..=50 => Some(descriptor_id - 46),
51..=54 => Some(descriptor_id - 50),
_ => None,
}
}
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,
@ -4422,6 +4436,42 @@ fn decode_real_grouped_effect_action(
.copied()?; .copied()?;
let target_subject = derive_real_grouped_target_subject(row, compact_control); let target_subject = derive_real_grouped_target_subject(row, compact_control);
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.parameter_family == "runtime_variable_scalar"
&& row.row_shape == "scalar_assignment"
{
let index = runtime_variable_index(descriptor_metadata.descriptor_id)?;
return match target_subject {
Some(RealGroupedTargetSubject::WholeGame) => Some(RuntimeEffect::SetWorldVariable {
index,
value: i64::from(row.raw_scalar_value),
}),
Some(RealGroupedTargetSubject::Company) => Some(RuntimeEffect::SetCompanyVariable {
target: real_grouped_company_target(target_scope_ordinal)?,
index,
value: i64::from(row.raw_scalar_value),
}),
Some(RealGroupedTargetSubject::Player) => Some(RuntimeEffect::SetPlayerVariable {
target: real_grouped_player_target(target_scope_ordinal)?,
index,
value: i64::from(row.raw_scalar_value),
}),
Some(RealGroupedTargetSubject::Territory) => compact_control
.grouped_territory_selectors_0x80f
.get(row.group_index)
.copied()
.filter(|selector| *selector >= 0)
.map(|selector| RuntimeEffect::SetTerritoryVariable {
target: RuntimeTerritoryTarget::Ids {
ids: vec![selector as u32],
},
index,
value: i64::from(row.raw_scalar_value),
}),
_ => None,
};
}
if descriptor_metadata.executable_in_runtime if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.parameter_family == "company_governance_scalar" && descriptor_metadata.parameter_family == "company_governance_scalar"
&& row.row_shape == "scalar_assignment" && row.row_shape == "scalar_assignment"
@ -4858,6 +4908,7 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
| RuntimeChairmanTarget::Ids { .. } | RuntimeChairmanTarget::Ids { .. }
), ),
RuntimeEffect::SetWorldFlag { .. } RuntimeEffect::SetWorldFlag { .. }
| RuntimeEffect::SetWorldVariable { .. }
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. } | RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
| RuntimeEffect::SetEconomicStatusCode { .. } | RuntimeEffect::SetEconomicStatusCode { .. }
| RuntimeEffect::SetCompanyGovernanceScalar { .. } | RuntimeEffect::SetCompanyGovernanceScalar { .. }
@ -4879,7 +4930,8 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
| RuntimeEffect::ActivateEventRecord { .. } | RuntimeEffect::ActivateEventRecord { .. }
| RuntimeEffect::DeactivateEventRecord { .. } | RuntimeEffect::DeactivateEventRecord { .. }
| RuntimeEffect::RemoveEventRecord { .. } => true, | RuntimeEffect::RemoveEventRecord { .. } => true,
RuntimeEffect::SetPlayerCash { target, .. } => matches!( RuntimeEffect::SetPlayerCash { target, .. }
| RuntimeEffect::SetPlayerVariable { target, .. } => matches!(
target, target,
RuntimePlayerTarget::AllActive RuntimePlayerTarget::AllActive
| RuntimePlayerTarget::Ids { .. } | RuntimePlayerTarget::Ids { .. }
@ -4905,6 +4957,7 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
) )
} }
RuntimeEffect::SetCompanyCash { target, .. } RuntimeEffect::SetCompanyCash { target, .. }
| RuntimeEffect::SetCompanyVariable { target, .. }
| RuntimeEffect::AdjustCompanyCash { target, .. } | RuntimeEffect::AdjustCompanyCash { target, .. }
| RuntimeEffect::AdjustCompanyDebt { target, .. } => matches!( | RuntimeEffect::AdjustCompanyDebt { target, .. } => matches!(
target, target,
@ -4915,6 +4968,10 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
| RuntimeCompanyTarget::SelectedCompany | RuntimeCompanyTarget::SelectedCompany
| RuntimeCompanyTarget::ConditionTrueCompany | RuntimeCompanyTarget::ConditionTrueCompany
), ),
RuntimeEffect::SetTerritoryVariable { target, .. } => matches!(
target,
RuntimeTerritoryTarget::AllTerritories | RuntimeTerritoryTarget::Ids { .. }
),
RuntimeEffect::AppendEventRecord { record } => record RuntimeEffect::AppendEventRecord { record } => record
.effects .effects
.iter() .iter()
@ -11514,6 +11571,17 @@ mod tests {
assert_eq!(recovered_locomotive_availability_loco_id(457), None); assert_eq!(recovered_locomotive_availability_loco_id(457), None);
} }
#[test]
fn looks_up_extended_lower_band_locomotive_availability_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(301).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Zephyr Availability");
assert_eq!(metadata.parameter_family, "locomotive_availability_scalar");
assert_eq!(recovered_locomotive_availability_loco_id(301), Some(61));
assert!(metadata.executable_in_runtime);
}
#[test] #[test]
fn parses_recovered_locomotive_availability_row_with_structured_locomotive_id() { fn parses_recovered_locomotive_availability_row_with_structured_locomotive_id() {
let row_bytes = build_real_grouped_effect_row(RealGroupedEffectRowSpec { let row_bytes = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
@ -11568,6 +11636,17 @@ mod tests {
assert!(metadata.executable_in_runtime); assert!(metadata.executable_in_runtime);
} }
#[test]
fn looks_up_runtime_variable_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(43).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Company Variable 1");
assert_eq!(metadata.target_mask_bits, 0x01);
assert_eq!(metadata.parameter_family, "runtime_variable_scalar");
assert!(metadata.executable_in_runtime);
}
#[test] #[test]
fn looks_up_recovered_aggregate_cargo_production_descriptor_metadata() { fn looks_up_recovered_aggregate_cargo_production_descriptor_metadata() {
let metadata = let metadata =
@ -11616,6 +11695,17 @@ mod tests {
assert!(!metadata.executable_in_runtime); assert!(!metadata.executable_in_runtime);
} }
#[test]
fn looks_up_extended_lower_band_locomotive_cost_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(412).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Zephyr Cost");
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
assert_eq!(recovered_locomotive_cost_loco_id(412), Some(61));
assert!(metadata.executable_in_runtime);
}
#[test] #[test]
fn looks_up_recovered_territory_access_cost_descriptor_metadata() { fn looks_up_recovered_territory_access_cost_descriptor_metadata() {
let metadata = let metadata =

View file

@ -636,6 +636,52 @@ fn apply_runtime_effects(
RuntimeEffect::SetCargoProductionSlot { slot, value } => { RuntimeEffect::SetCargoProductionSlot { slot, value } => {
state.cargo_production_overrides.insert(*slot, *value); state.cargo_production_overrides.insert(*slot, *value);
} }
RuntimeEffect::SetWorldVariable { index, value } => {
state.world_runtime_variables.insert(*index, *value);
}
RuntimeEffect::SetCompanyVariable {
target,
index,
value,
} => {
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
for company_id in company_ids {
state
.company_runtime_variables
.entry(company_id)
.or_default()
.insert(*index, *value);
mutated_company_ids.insert(company_id);
}
}
RuntimeEffect::SetPlayerVariable {
target,
index,
value,
} => {
let player_ids = resolve_player_target_ids(state, target, condition_context)?;
for player_id in player_ids {
state
.player_runtime_variables
.entry(player_id)
.or_default()
.insert(*index, *value);
}
}
RuntimeEffect::SetTerritoryVariable {
target,
index,
value,
} => {
let territory_ids = resolve_territory_target_ids(state, target)?;
for territory_id in territory_ids {
state
.territory_runtime_variables
.entry(territory_id)
.or_default()
.insert(*index, *value);
}
}
RuntimeEffect::SetTerritoryAccessCost { value } => { RuntimeEffect::SetTerritoryAccessCost { value } => {
state.world_restore.territory_access_cost = Some(*value); state.world_restore.territory_access_cost = Some(*value);
} }
@ -1596,6 +1642,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -2008,6 +2058,128 @@ mod tests {
assert_eq!(result.service_events[0].applied_effect_count, 7); assert_eq!(result.service_events[0].applied_effect_count, 7);
} }
#[test]
fn applies_runtime_variable_effects() {
let mut state = RuntimeState {
companies: vec![
RuntimeCompany {
company_id: 1,
controller_kind: RuntimeCompanyControllerKind::Human,
current_cash: 10,
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: 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: 20,
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: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
},
],
players: vec![RuntimePlayer {
player_id: 9,
current_cash: 0,
active: true,
controller_kind: RuntimeCompanyControllerKind::Human,
}],
territories: vec![RuntimeTerritory {
territory_id: 7,
name: Some("North".to_string()),
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
event_runtime_records: vec![RuntimeEventRecord {
record_id: 14,
trigger_kind: 7,
active: true,
service_count: 0,
marks_collection_dirty: false,
one_shot: false,
has_fired: false,
conditions: Vec::new(),
effects: vec![
RuntimeEffect::SetWorldVariable {
index: 1,
value: -5,
},
RuntimeEffect::SetCompanyVariable {
target: RuntimeCompanyTarget::AllActive,
index: 2,
value: 17,
},
RuntimeEffect::SetPlayerVariable {
target: RuntimePlayerTarget::Ids { ids: vec![9] },
index: 3,
value: 99,
},
RuntimeEffect::SetTerritoryVariable {
target: RuntimeTerritoryTarget::Ids { ids: vec![7] },
index: 4,
value: 1234,
},
],
}],
..state()
};
let result = execute_step_command(
&mut state,
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
)
.expect("runtime variable effects should succeed");
assert_eq!(state.world_runtime_variables.get(&1), Some(&-5));
assert_eq!(
state
.company_runtime_variables
.get(&1)
.and_then(|vars| vars.get(&2)),
Some(&17)
);
assert_eq!(
state
.company_runtime_variables
.get(&2)
.and_then(|vars| vars.get(&2)),
Some(&17)
);
assert_eq!(
state
.player_runtime_variables
.get(&9)
.and_then(|vars| vars.get(&3)),
Some(&99)
);
assert_eq!(
state
.territory_runtime_variables
.get(&7)
.and_then(|vars| vars.get(&4)),
Some(&1234)
);
assert_eq!(result.service_events[0].applied_effect_count, 4);
}
#[test] #[test]
fn resolves_symbolic_company_targets() { fn resolves_symbolic_company_targets() {
let mut state = RuntimeState { let mut state = RuntimeState {

View file

@ -91,6 +91,10 @@ pub struct RuntimeSummary {
pub zero_named_locomotive_availability_count: usize, pub zero_named_locomotive_availability_count: usize,
pub named_locomotive_cost_count: usize, pub named_locomotive_cost_count: usize,
pub cargo_production_override_count: usize, pub cargo_production_override_count: usize,
pub world_runtime_variable_count: usize,
pub company_runtime_variable_owner_count: usize,
pub player_runtime_variable_owner_count: usize,
pub territory_runtime_variable_owner_count: usize,
pub world_scalar_override_count: usize, pub world_scalar_override_count: usize,
pub special_condition_count: usize, pub special_condition_count: usize,
pub enabled_special_condition_count: usize, pub enabled_special_condition_count: usize,
@ -707,6 +711,10 @@ impl RuntimeSummary {
.count(), .count(),
named_locomotive_cost_count: state.named_locomotive_cost.len(), named_locomotive_cost_count: state.named_locomotive_cost.len(),
cargo_production_override_count: state.cargo_production_overrides.len(), cargo_production_override_count: state.cargo_production_overrides.len(),
world_runtime_variable_count: state.world_runtime_variables.len(),
company_runtime_variable_owner_count: state.company_runtime_variables.len(),
player_runtime_variable_owner_count: state.player_runtime_variables.len(),
territory_runtime_variable_owner_count: state.territory_runtime_variables.len(),
world_scalar_override_count: state.world_scalar_overrides.len(), world_scalar_override_count: state.world_scalar_overrides.len(),
special_condition_count: state.special_conditions.len(), special_condition_count: state.special_conditions.len(),
enabled_special_condition_count: state enabled_special_condition_count: state
@ -747,9 +755,9 @@ mod tests {
use crate::{ use crate::{
CalendarPoint, RuntimeCompany, RuntimeCompanyControllerKind, CalendarPoint, RuntimeCompany, RuntimeCompanyControllerKind,
RuntimePackedEventCollectionSummary, RuntimePackedEventRecordSummary, RuntimePackedEventCollectionSummary, RuntimePackedEventRecordSummary, RuntimePlayer,
RuntimeSaveProfileState, RuntimeServiceState, RuntimeState, RuntimeTrackPieceCounts, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState, RuntimeTerritory,
RuntimeWorldRestoreState, RuntimeTrackPieceCounts, RuntimeWorldRestoreState,
}; };
use super::RuntimeSummary; use super::RuntimeSummary;
@ -941,6 +949,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -1064,6 +1076,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -1124,6 +1140,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -1176,6 +1196,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -1226,6 +1250,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::from([(1, 125), (2, 250)]), cargo_production_overrides: BTreeMap::from([(1, 125), (2, 250)]),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -1237,6 +1265,85 @@ mod tests {
assert_eq!(summary.world_restore_territory_access_cost, Some(750000)); assert_eq!(summary.world_restore_territory_access_cost, Some(750000));
} }
#[test]
fn counts_runtime_variable_surfaces() {
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: 0,
debt: 0,
credit_rating_score: None,
prime_rate: None,
active: true,
available_track_laying_capacity: None,
controller_kind: RuntimeCompanyControllerKind::Human,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
selected_company_id: None,
players: vec![RuntimePlayer {
player_id: 2,
current_cash: 0,
active: true,
controller_kind: RuntimeCompanyControllerKind::Human,
}],
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![RuntimeTerritory {
territory_id: 3,
name: Some("East".to_string()),
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
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(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::from([(1, 9)]),
company_runtime_variables: BTreeMap::from([(1, BTreeMap::from([(2, 11)]))]),
player_runtime_variables: BTreeMap::from([(2, BTreeMap::from([(3, 13)]))]),
territory_runtime_variables: BTreeMap::from([(3, BTreeMap::from([(4, 15)]))]),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
let summary = RuntimeSummary::from_state(&state);
assert_eq!(summary.world_runtime_variable_count, 1);
assert_eq!(summary.company_runtime_variable_owner_count, 1);
assert_eq!(summary.player_runtime_variable_owner_count, 1);
assert_eq!(summary.territory_runtime_variable_owner_count, 1);
}
#[test] #[test]
fn counts_world_frontier_buckets_separately() { fn counts_world_frontier_buckets_separately() {
let state = RuntimeState { let state = RuntimeState {
@ -1338,6 +1445,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -1428,6 +1539,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
@ -1514,6 +1629,10 @@ mod tests {
farm_mine_cargo_production_override: None, farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(), named_cargo_production_overrides: BTreeMap::new(),
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(), world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),

View file

@ -113,6 +113,9 @@ The highest-value next passes are now:
landing surface too: representative rows execute into `RuntimeState.world_scalar_overrides` landing surface too: representative rows execute into `RuntimeState.world_scalar_overrides`
through stable normalized keys such as `world.build_stations_cost` and through stable normalized keys such as `world.build_stations_cost` and
`world.track_maintenance_cost` `world.track_maintenance_cost`
- the runtime-variable strip `39..54` now executes too through bounded event-owned scalar maps on
world/company/player/territory state; these variables are runtime-owned only in the current
model and are not yet reconstructed from raw saves
- the grounded aggregate cargo-economics descriptors now execute too: descriptor `105` - the grounded aggregate cargo-economics descriptors now execute too: descriptor `105`
`All Cargo Prices` and descriptors `177..179` `All Cargo Production` / `All Factory Production` `All Cargo Prices` and descriptors `177..179` `All Cargo Production` / `All Factory Production`
/ `All Farm/Mine Production` land on bounded event-owned cargo override state, and the grounded / `All Farm/Mine Production` land on bounded event-owned cargo override state, and the grounded
@ -168,6 +171,8 @@ The highest-value next passes are now:
- recovered scalar locomotive availability and locomotive-cost descriptors now import through that - recovered scalar locomotive availability and locomotive-cost descriptors now import through that
save-native or embedded `RuntimeState.locomotive_catalog` context into the ordinary save-native or embedded `RuntimeState.locomotive_catalog` context into the ordinary
`named_locomotive_availability` and `named_locomotive_cost` runtime maps `named_locomotive_availability` and `named_locomotive_cost` runtime maps
- the grounded executable lower locomotive prefix now extends through save-backed locomotive id
`61` (`Zephyr`); the unresolved lower tail and upper locomotive bands stay on explicit parity
- cargo-production `230..240` and territory-access-cost `453` now execute too through minimal - cargo-production `230..240` and territory-access-cost `453` now execute too through minimal
world-side scalar landing surfaces: slot-indexed `cargo_production_overrides` and world-side scalar landing surfaces: slot-indexed `cargo_production_overrides` and
`world_restore.territory_access_cost` `world_restore.territory_access_cost`

View file

@ -75,6 +75,9 @@ Implemented today:
- the recovered whole-game scalar economy/performance strip `59..104` now has a bounded runtime - the recovered whole-game scalar economy/performance strip `59..104` now has a bounded runtime
landing surface too: representative descriptors import as `SetWorldScalarOverride` and land in landing surface too: representative descriptors import as `SetWorldScalarOverride` and land in
`RuntimeState.world_scalar_overrides` `RuntimeState.world_scalar_overrides`
- the runtime-variable strip `39..54` now imports and executes too through bounded event-owned
world/company/player/territory variable maps, so those descriptors no longer depend on generic
evidence parity or ad hoc fixture-only state
- the grounded aggregate cargo-economics descriptors now execute too: descriptor `105` - the grounded aggregate cargo-economics descriptors now execute too: descriptor `105`
`All Cargo Prices` and descriptors `177..179` `All Cargo Production` / `All Factory Production` `All Cargo Prices` and descriptors `177..179` `All Cargo Production` / `All Factory Production`
/ `All Farm/Mine Production` import through bounded cargo override surfaces, and the grounded / `All Farm/Mine Production` import through bounded cargo override surfaces, and the grounded
@ -130,7 +133,8 @@ Implemented today:
through `RuntimeState.locomotive_catalog` into `RuntimeState.named_locomotive_availability`; through `RuntimeState.locomotive_catalog` into `RuntimeState.named_locomotive_availability`;
raw `.smp` inspection/export now reconstructs the save-side locomotive row family and derives the raw `.smp` inspection/export now reconstructs the save-side locomotive row family and derives the
catalog directly into save-slice documents, so standalone save-slice imports can execute those catalog directly into save-slice documents, so standalone save-slice imports can execute those
rows whenever the save carries enough catalog entries rows whenever the save carries enough catalog entries, and the grounded executable lower prefix
now extends through save-backed locomotive id `61` (`Zephyr`)
- the grounded lower locomotive-cost band `352..409` now imports too through the same save-native - the grounded lower locomotive-cost band `352..409` now imports too through the same save-native
or embedded catalog into the event-owned `RuntimeState.named_locomotive_cost` map when its or embedded catalog into the event-owned `RuntimeState.named_locomotive_cost` map when its
scalar payloads are nonnegative; the unresolved lower tail and upper cost tail now stay on scalar payloads are nonnegative; the unresolved lower tail and upper cost tail now stay on

View file

@ -0,0 +1,87 @@
{
"format_version": 1,
"fixture_id": "packed-event-runtime-variable-overlay-fixture",
"source": {
"kind": "captured-runtime",
"description": "Fixture proving descriptors 39..54 import and execute through bounded world/company/player/territory runtime-variable surfaces."
},
"state_import_path": "packed-event-runtime-variable-overlay.json",
"commands": [
{
"kind": "service_trigger_kind",
"trigger_kind": 7
}
],
"expected_summary": {
"calendar_projection_source": "base-snapshot-preserved",
"calendar_projection_is_placeholder": false,
"company_count": 3,
"player_count": 2,
"territory_count": 2,
"packed_event_collection_present": true,
"packed_event_record_count": 4,
"packed_event_decoded_record_count": 4,
"packed_event_imported_runtime_record_count": 4,
"event_runtime_record_count": 4,
"world_runtime_variable_count": 1,
"company_runtime_variable_owner_count": 1,
"player_runtime_variable_owner_count": 1,
"territory_runtime_variable_owner_count": 1,
"total_event_record_service_count": 4,
"total_trigger_dispatch_count": 1
},
"expected_state_fragment": {
"world_runtime_variables": {
"1": 111
},
"company_runtime_variables": {
"1": {
"2": 222
}
},
"player_runtime_variables": {
"1": {
"3": 333
}
},
"territory_runtime_variables": {
"7": {
"4": 444
}
},
"packed_event_collection": {
"records": [
{
"import_outcome": "imported"
},
{
"import_outcome": "imported"
},
{
"import_outcome": "imported"
},
{
"import_outcome": "imported"
}
]
},
"event_runtime_records": [
{
"record_id": 71,
"service_count": 1
},
{
"record_id": 72,
"service_count": 1
},
{
"record_id": 73,
"service_count": 1
},
{
"record_id": 74,
"service_count": 1
}
]
}
}

View file

@ -0,0 +1,9 @@
{
"format_version": 1,
"import_id": "packed-event-runtime-variable-overlay",
"source": {
"description": "Overlay import combining company/player/territory runtime context with the runtime-variable descriptor sample."
},
"base_snapshot_path": "packed-event-territory-player-overlay-base-snapshot.json",
"save_slice_path": "packed-event-runtime-variable-save-slice.json"
}

View file

@ -0,0 +1,320 @@
{
"format_version": 1,
"save_slice_id": "packed-event-runtime-variable-save-slice",
"source": {
"description": "Tracked save-slice document proving runtime-variable descriptors 39..54 execute through bounded world/company/player/territory variable surfaces.",
"original_save_filename": "captured-runtime-variable.gms",
"original_save_sha256": "runtime-variable-sample-sha256",
"notes": [
"tracked as JSON save-slice document rather than raw .smp",
"uses overlay-backed company/player/territory context while leaving world variables save-slice-native"
]
},
"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,
"named_locomotive_availability_table": null,
"locomotive_catalog": null,
"cargo_catalog": null,
"company_roster": null,
"chairman_profile_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": 33280,
"records_tag_offset": 33536,
"close_tag_offset": 34560,
"packed_state_version": 1001,
"packed_state_version_hex": "0x000003e9",
"live_id_bound": 74,
"live_record_count": 4,
"live_entry_ids": [71, 72, 73, 74],
"decoded_record_count": 4,
"imported_runtime_record_count": 0,
"records": [
{
"record_index": 0,
"live_entry_id": 71,
"payload_offset": 33568,
"payload_len": 160,
"decode_status": "executable",
"payload_family": "real_packed_v1",
"trigger_kind": 7,
"one_shot": false,
"compact_control": {
"mode_byte_0x7ef": 7,
"primary_selector_0x7f0": 0,
"grouped_mode_0x7f4": 2,
"one_shot_header_0x7f5": 0,
"modifier_flag_0x7f9": 0,
"modifier_flag_0x7fa": 0,
"grouped_target_scope_ordinals_0x7fb": [0, 0, 0, 0],
"grouped_scope_checkboxes_0x7ff": [1, 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": 39,
"descriptor_label": "Game Variable 1",
"target_mask_bits": 8,
"parameter_family": "runtime_variable_scalar",
"opcode": 3,
"raw_scalar_value": 111,
"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": "scalar_assignment",
"semantic_family": "scalar_assignment",
"semantic_preview": "Set Game Variable 1 to 111",
"grouped_target_subject": "whole_game",
"grouped_target_scope": "whole_game",
"recovered_locomotive_id": null,
"locomotive_name": null,
"notes": [
"descriptor recovered from checked-in EventEffects semantic catalog"
]
}
],
"decoded_actions": [
{
"kind": "set_world_variable",
"index": 1,
"value": 111
}
],
"executable_import_ready": true,
"notes": [
"runtime variable world sample"
]
},
{
"record_index": 1,
"live_entry_id": 72,
"payload_offset": 33728,
"payload_len": 160,
"decode_status": "executable",
"payload_family": "real_packed_v1",
"trigger_kind": 7,
"one_shot": false,
"compact_control": {
"mode_byte_0x7ef": 7,
"primary_selector_0x7f0": 99,
"grouped_mode_0x7f4": 2,
"one_shot_header_0x7f5": 0,
"modifier_flag_0x7f9": 1,
"modifier_flag_0x7fa": 0,
"grouped_target_scope_ordinals_0x7fb": [1, 1, 1, 1],
"grouped_scope_checkboxes_0x7ff": [1, 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": 44,
"descriptor_label": "Company Variable 2",
"target_mask_bits": 1,
"parameter_family": "runtime_variable_scalar",
"opcode": 3,
"raw_scalar_value": 222,
"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": "scalar_assignment",
"semantic_family": "scalar_assignment",
"semantic_preview": "Set Company Variable 2 to 222",
"grouped_target_subject": "company",
"grouped_target_scope": "selected_company",
"recovered_locomotive_id": null,
"locomotive_name": null,
"notes": [
"descriptor recovered from checked-in EventEffects semantic catalog"
]
}
],
"decoded_actions": [
{
"kind": "set_company_variable",
"target": {
"kind": "selected_company"
},
"index": 2,
"value": 222
}
],
"executable_import_ready": true,
"notes": [
"runtime variable company sample"
]
},
{
"record_index": 2,
"live_entry_id": 73,
"payload_offset": 33888,
"payload_len": 160,
"decode_status": "executable",
"payload_family": "real_packed_v1",
"trigger_kind": 7,
"one_shot": false,
"compact_control": {
"mode_byte_0x7ef": 7,
"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": [1, 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": 49,
"descriptor_label": "Player Variable 3",
"target_mask_bits": 2,
"parameter_family": "runtime_variable_scalar",
"opcode": 3,
"raw_scalar_value": 333,
"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": "scalar_assignment",
"semantic_family": "scalar_assignment",
"semantic_preview": "Set Player Variable 3 to 333",
"grouped_target_subject": "player",
"grouped_target_scope": "selected_player",
"recovered_locomotive_id": null,
"locomotive_name": null,
"notes": [
"descriptor recovered from checked-in EventEffects semantic catalog"
]
}
],
"decoded_actions": [
{
"kind": "set_player_variable",
"target": {
"kind": "selected_player"
},
"index": 3,
"value": 333
}
],
"executable_import_ready": true,
"notes": [
"runtime variable player sample"
]
},
{
"record_index": 3,
"live_entry_id": 74,
"payload_offset": 34048,
"payload_len": 160,
"decode_status": "executable",
"payload_family": "real_packed_v1",
"trigger_kind": 7,
"one_shot": false,
"compact_control": {
"mode_byte_0x7ef": 7,
"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": [1, 0, 0, 0],
"summary_toggle_0x800": 1,
"grouped_territory_selectors_0x80f": [7, -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": 54,
"descriptor_label": "Territory Variable 4",
"target_mask_bits": 4,
"parameter_family": "runtime_variable_scalar",
"opcode": 3,
"raw_scalar_value": 444,
"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": "scalar_assignment",
"semantic_family": "scalar_assignment",
"semantic_preview": "Set Territory Variable 4 to 444",
"grouped_target_subject": "territory",
"grouped_target_scope": "named_territory",
"recovered_locomotive_id": null,
"locomotive_name": null,
"notes": [
"descriptor recovered from checked-in EventEffects semantic catalog"
]
}
],
"decoded_actions": [
{
"kind": "set_territory_variable",
"target": {
"kind": "ids",
"ids": [7]
},
"index": 4,
"value": 444
}
],
"executable_import_ready": true,
"notes": [
"runtime variable territory sample"
]
}
]
},
"notes": [
"runtime variable descriptor executable sample"
]
}
}

View file

@ -64,6 +64,9 @@ GROUNDED_LOCOMOTIVE_PREFIX = {
56: "Trans-Euro", 56: "Trans-Euro",
57: "V200", 57: "V200",
58: "VL80T", 58: "VL80T",
59: "GP 35",
60: "U1",
61: "Zephyr",
} }
@ -208,6 +211,8 @@ def classify(
runtime_status = "shell_owned" runtime_status = "shell_owned"
elif signature_byte_0x63 == 0 and signature_byte_0x64 == 0x8F: elif signature_byte_0x63 == 0 and signature_byte_0x64 == 0x8F:
parameter_family = "runtime_variable_scalar" parameter_family = "runtime_variable_scalar"
runtime_status = "executable"
executable_in_runtime = True
elif "Earthquake" in label or "Storm" in label: elif "Earthquake" in label or "Storm" in label:
parameter_family = "world_disaster_scalar" parameter_family = "world_disaster_scalar"