Implement runtime variable event conditions

This commit is contained in:
Jan Petykiewicz 2026-04-17 08:50:35 -07:00
commit bd9e1421a1
15 changed files with 1442 additions and 29 deletions

View file

@ -46,7 +46,8 @@ bounded runtime landing surface too: representative descriptors import into
`RuntimeState.world_scalar_overrides` through stable normalized keys such as
`world.build_stations_cost`, `world.track_maintenance_cost`, `world.all_engine_speeds`, and
`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
event-owned scalar maps on world/company/player/territory state, and the matching ordinary
condition strip now gates records through those same maps too, 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`
@ -54,7 +55,8 @@ runtime landing surfaces too: descriptor `105` `All Cargo Prices` plus descripto
event-owned cargo override state, and the grounded named cargo-production strip `180..229` now
imports into named cargo production overrides too. The named cargo-price strip `106..176`
remains explicit `blocked_evidence_blocked_descriptor` parity until descriptor ordering is pinned
more strongly. The first grounded
more strongly. The add-building strip `503..519` is now explicitly classified as recovered
shell-owned descriptor parity rather than generic unresolved residue. The first grounded
condition-side unlock now exists for negative-sentinel `raw_condition_id = -1` company scopes, and
the first ordinary nonnegative condition batch now executes too: numeric-threshold company
finance, company track, aggregate territory track, and company-territory track rows can import

View file

@ -4532,7 +4532,7 @@
},
{
"descriptor_id": 503,
"label": "Unknown Add Building",
"label": "Add Building Slot 1",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4541,7 +4541,7 @@
},
{
"descriptor_id": 504,
"label": "Unknown Add Building",
"label": "Add Building Slot 2",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4550,7 +4550,7 @@
},
{
"descriptor_id": 505,
"label": "Unknown Add Building",
"label": "Add Building Slot 3",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4559,7 +4559,7 @@
},
{
"descriptor_id": 506,
"label": "Unknown Add Building",
"label": "Add Building Slot 4",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4568,7 +4568,7 @@
},
{
"descriptor_id": 507,
"label": "Unknown Add Building",
"label": "Add Building Slot 5",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4577,7 +4577,7 @@
},
{
"descriptor_id": 508,
"label": "Unknown Add Building",
"label": "Add Building Slot 6",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4586,7 +4586,7 @@
},
{
"descriptor_id": 509,
"label": "Unknown Add Building",
"label": "Add Building Slot 7",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4595,7 +4595,7 @@
},
{
"descriptor_id": 510,
"label": "Unknown Add Building",
"label": "Add Building Slot 8",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4604,7 +4604,7 @@
},
{
"descriptor_id": 511,
"label": "Unknown Add Building",
"label": "Add Building Slot 9",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4613,7 +4613,7 @@
},
{
"descriptor_id": 512,
"label": "Unknown Add Building",
"label": "Add Building Slot 10",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4622,7 +4622,7 @@
},
{
"descriptor_id": 513,
"label": "Unknown Add Building",
"label": "Add Building Slot 11",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4631,7 +4631,7 @@
},
{
"descriptor_id": 514,
"label": "Unknown Add Building",
"label": "Add Building Slot 12",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4640,7 +4640,7 @@
},
{
"descriptor_id": 515,
"label": "Unknown Add Building",
"label": "Add Building Slot 13",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4649,7 +4649,7 @@
},
{
"descriptor_id": 516,
"label": "Unknown Add Building",
"label": "Add Building Slot 14",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4658,7 +4658,7 @@
},
{
"descriptor_id": 517,
"label": "Unknown Add Building",
"label": "Add Building Slot 15",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4667,7 +4667,7 @@
},
{
"descriptor_id": 518,
"label": "Unknown Add Building",
"label": "Add Building Slot 16",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,
@ -4676,7 +4676,7 @@
},
{
"descriptor_id": 519,
"label": "Unknown Add Building",
"label": "Add Building Slot 17",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"runtime_key": null,

View file

@ -4481,11 +4481,17 @@ mod tests {
);
let runtime_variable_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../fixtures/runtime/packed-event-runtime-variable-overlay-fixture.json");
let runtime_variable_condition_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join(
"../../fixtures/runtime/packed-event-runtime-variable-condition-overlay-fixture.json",
);
let cargo_economics_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../fixtures/runtime/packed-event-cargo-economics-save-slice-fixture.json");
let cargo_economics_parity_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
"../../fixtures/runtime/packed-event-cargo-economics-parity-save-slice-fixture.json",
);
let add_building_shell_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../fixtures/runtime/packed-event-add-building-shell-save-slice-fixture.json");
let world_scalar_condition_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
"../../fixtures/runtime/packed-event-world-scalar-condition-save-slice-fixture.json",
);
@ -4599,10 +4605,14 @@ mod tests {
.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(&runtime_variable_condition_overlay_fixture)
.expect("overlay-backed runtime-variable condition fixture should summarize");
run_runtime_summarize_fixture(&cargo_economics_fixture)
.expect("save-slice-backed cargo-economics fixture should summarize");
run_runtime_summarize_fixture(&cargo_economics_parity_fixture)
.expect("save-slice-backed cargo-economics parity fixture should summarize");
run_runtime_summarize_fixture(&add_building_shell_fixture)
.expect("save-slice-backed add-building shell fixture should summarize");
run_runtime_summarize_fixture(&world_scalar_condition_fixture)
.expect("save-slice-backed executable world-scalar condition fixture should summarize");
run_runtime_summarize_fixture(&world_scalar_condition_parity_fixture)

View file

@ -1302,6 +1302,7 @@ fn lowered_record_decoded_conditions(
}
let lowered_company_target = lowered_condition_true_company_target(record)?;
let lowered_player_target = lowered_condition_true_player_target(record)?;
let ordinary_rows = record
.standalone_condition_rows
.iter()
@ -1313,6 +1314,7 @@ fn lowered_record_decoded_conditions(
condition,
row,
lowered_company_target.as_ref(),
lowered_player_target.as_ref(),
company_context,
)
})
@ -1949,9 +1951,19 @@ fn lower_condition_targets_in_condition(
condition: &RuntimeCondition,
row: &SmpLoadedPackedEventConditionRowSummary,
lowered_company_target: Option<&RuntimeCompanyTarget>,
lowered_player_target: Option<&RuntimePlayerTarget>,
company_context: &ImportRuntimeContext,
) -> Result<RuntimeCondition, ImportBlocker> {
Ok(match condition {
RuntimeCondition::WorldVariableThreshold {
index,
comparator,
value,
} => RuntimeCondition::WorldVariableThreshold {
index: *index,
comparator: *comparator,
value: *value,
},
RuntimeCondition::CompanyNumericThreshold {
target,
metric,
@ -1966,6 +1978,20 @@ fn lower_condition_targets_in_condition(
comparator: *comparator,
value: *value,
},
RuntimeCondition::CompanyVariableThreshold {
target,
index,
comparator,
value,
} => RuntimeCondition::CompanyVariableThreshold {
target: lower_condition_true_company_target_in_company_target(
target,
lowered_company_target,
)?,
index: *index,
comparator: *comparator,
value: *value,
},
RuntimeCondition::ChairmanNumericThreshold {
target,
metric,
@ -1977,6 +2003,20 @@ fn lower_condition_targets_in_condition(
comparator: *comparator,
value: *value,
},
RuntimeCondition::PlayerVariableThreshold {
target,
index,
comparator,
value,
} => RuntimeCondition::PlayerVariableThreshold {
target: lower_condition_true_player_target_in_player_target(
target,
lowered_player_target,
)?,
index: *index,
comparator: *comparator,
value: *value,
},
RuntimeCondition::TerritoryNumericThreshold {
target,
metric,
@ -1988,6 +2028,17 @@ fn lower_condition_targets_in_condition(
comparator: *comparator,
value: *value,
},
RuntimeCondition::TerritoryVariableThreshold {
target,
index,
comparator,
value,
} => RuntimeCondition::TerritoryVariableThreshold {
target: lower_territory_target_in_condition(target, row, company_context)?,
index: *index,
comparator: *comparator,
value: *value,
},
RuntimeCondition::CompanyTerritoryNumericThreshold {
target,
territory,
@ -2187,11 +2238,17 @@ fn record_uses_condition_true_player(record: &SmpLoadedPackedEventRecordSummary)
fn condition_uses_condition_true_company(condition: &RuntimeCondition) -> bool {
match condition {
RuntimeCondition::CompanyNumericThreshold { target, .. }
| RuntimeCondition::CompanyVariableThreshold { target, .. }
| RuntimeCondition::CompanyTerritoryNumericThreshold { target, .. } => {
matches!(target, RuntimeCompanyTarget::ConditionTrueCompany)
}
RuntimeCondition::PlayerVariableThreshold { target, .. } => {
matches!(target, RuntimePlayerTarget::ConditionTruePlayer)
}
RuntimeCondition::ChairmanNumericThreshold { .. } => false,
RuntimeCondition::TerritoryNumericThreshold { .. }
| RuntimeCondition::TerritoryVariableThreshold { .. }
| RuntimeCondition::WorldVariableThreshold { .. }
| RuntimeCondition::SpecialConditionThreshold { .. }
| RuntimeCondition::CandidateAvailabilityThreshold { .. }
| RuntimeCondition::NamedLocomotiveAvailabilityThreshold { .. }
@ -2719,6 +2776,7 @@ fn conditions_provide_company_context(conditions: &[RuntimeCondition]) -> bool {
matches!(
condition,
RuntimeCondition::CompanyNumericThreshold { .. }
| RuntimeCondition::CompanyVariableThreshold { .. }
| RuntimeCondition::CompanyTerritoryNumericThreshold { .. }
)
})
@ -3026,7 +3084,8 @@ fn record_has_world_state_condition_rows(record: &SmpLoadedPackedEventRecordSumm
fn runtime_condition_is_world_state(condition: &RuntimeCondition) -> bool {
matches!(
condition,
RuntimeCondition::SpecialConditionThreshold { .. }
RuntimeCondition::WorldVariableThreshold { .. }
| RuntimeCondition::SpecialConditionThreshold { .. }
| RuntimeCondition::CandidateAvailabilityThreshold { .. }
| RuntimeCondition::NamedLocomotiveAvailabilityThreshold { .. }
| RuntimeCondition::NamedLocomotiveCostThreshold { .. }
@ -3180,15 +3239,25 @@ fn runtime_condition_company_target_import_blocker(
company_context: &ImportRuntimeContext,
) -> Option<ImportBlocker> {
match condition {
RuntimeCondition::WorldVariableThreshold { .. } => None,
RuntimeCondition::CompanyNumericThreshold { target, .. } => {
company_target_import_blocker(target, company_context)
}
RuntimeCondition::CompanyVariableThreshold { target, .. } => {
company_target_import_blocker(target, company_context)
}
RuntimeCondition::PlayerVariableThreshold { target, .. } => {
player_target_import_blocker(target, company_context)
}
RuntimeCondition::ChairmanNumericThreshold { target, .. } => {
chairman_target_import_blocker(target, company_context)
}
RuntimeCondition::TerritoryNumericThreshold { target, .. } => {
territory_target_import_blocker(target, company_context)
}
RuntimeCondition::TerritoryVariableThreshold { target, .. } => {
territory_target_import_blocker(target, company_context)
}
RuntimeCondition::CompanyTerritoryNumericThreshold {
target, territory, ..
} => company_target_import_blocker(target, company_context)

View file

@ -311,24 +311,47 @@ pub enum RuntimeTrackMetric {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum RuntimeCondition {
WorldVariableThreshold {
index: u32,
comparator: RuntimeConditionComparator,
value: i64,
},
CompanyNumericThreshold {
target: RuntimeCompanyTarget,
metric: RuntimeCompanyMetric,
comparator: RuntimeConditionComparator,
value: i64,
},
CompanyVariableThreshold {
target: RuntimeCompanyTarget,
index: u32,
comparator: RuntimeConditionComparator,
value: i64,
},
ChairmanNumericThreshold {
target: RuntimeChairmanTarget,
metric: RuntimeChairmanMetric,
comparator: RuntimeConditionComparator,
value: i64,
},
PlayerVariableThreshold {
target: RuntimePlayerTarget,
index: u32,
comparator: RuntimeConditionComparator,
value: i64,
},
TerritoryNumericThreshold {
target: RuntimeTerritoryTarget,
metric: RuntimeTerritoryMetric,
comparator: RuntimeConditionComparator,
value: i64,
},
TerritoryVariableThreshold {
target: RuntimeTerritoryTarget,
index: u32,
comparator: RuntimeConditionComparator,
value: i64,
},
CompanyTerritoryNumericThreshold {
target: RuntimeCompanyTarget,
territory: RuntimeTerritoryTarget,
@ -1209,6 +1232,7 @@ impl RuntimeState {
validate_runtime_condition(
condition,
&seen_company_ids,
&seen_player_ids,
&seen_chairman_profile_ids,
&seen_territory_ids,
)
@ -1810,6 +1834,7 @@ fn validate_event_record_template(
validate_runtime_condition(
condition,
valid_company_ids,
valid_player_ids,
valid_chairman_profile_ids,
valid_territory_ids,
)
@ -1842,19 +1867,51 @@ fn validate_event_record_template(
fn validate_runtime_condition(
condition: &RuntimeCondition,
valid_company_ids: &BTreeSet<u32>,
valid_player_ids: &BTreeSet<u32>,
valid_chairman_profile_ids: &BTreeSet<u32>,
valid_territory_ids: &BTreeSet<u32>,
) -> Result<(), String> {
match condition {
RuntimeCondition::WorldVariableThreshold { index, .. } => {
if !(1..=4).contains(index) {
Err("index must be in 1..=4".to_string())
} else {
Ok(())
}
}
RuntimeCondition::CompanyNumericThreshold { target, .. } => {
validate_company_target(target, valid_company_ids)
}
RuntimeCondition::CompanyVariableThreshold { target, index, .. } => {
validate_company_target(target, valid_company_ids)?;
if !(1..=4).contains(index) {
Err("index must be in 1..=4".to_string())
} else {
Ok(())
}
}
RuntimeCondition::ChairmanNumericThreshold { target, .. } => {
validate_chairman_target(target, valid_chairman_profile_ids)
}
RuntimeCondition::PlayerVariableThreshold { target, index, .. } => {
validate_player_target(target, valid_player_ids)?;
if !(1..=4).contains(index) {
Err("index must be in 1..=4".to_string())
} else {
Ok(())
}
}
RuntimeCondition::TerritoryNumericThreshold { target, .. } => {
validate_territory_target(target, valid_territory_ids)
}
RuntimeCondition::TerritoryVariableThreshold { target, index, .. } => {
validate_territory_target(target, valid_territory_ids)?;
if !(1..=4).contains(index) {
Err("index must be in 1..=4".to_string())
} else {
Ok(())
}
}
RuntimeCondition::CompanyTerritoryNumericThreshold {
target, territory, ..
} => {

View file

@ -333,9 +333,13 @@ pub(crate) fn grouped_effect_descriptor_runtime_status_name(
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RealOrdinaryConditionMetric {
WorldVariable(u32),
Company(RuntimeCompanyMetric),
CompanyVariable(u32),
PlayerVariable(u32),
Chairman(RuntimeChairmanMetric),
Territory(RuntimeTerritoryMetric),
TerritoryVariable(u32),
CompanyTerritory(RuntimeTrackMetric),
}
@ -511,6 +515,22 @@ const GROUNDED_LOCOMOTIVE_PREFIX: [&str; 61] = [
];
const REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID: i32 = 435;
const REAL_WORLD_VARIABLE_1_CONDITION_ID: i32 = 2241;
const REAL_WORLD_VARIABLE_2_CONDITION_ID: i32 = 2242;
const REAL_WORLD_VARIABLE_3_CONDITION_ID: i32 = 2243;
const REAL_WORLD_VARIABLE_4_CONDITION_ID: i32 = 2244;
const REAL_COMPANY_VARIABLE_1_CONDITION_ID: i32 = 2245;
const REAL_COMPANY_VARIABLE_2_CONDITION_ID: i32 = 2246;
const REAL_COMPANY_VARIABLE_3_CONDITION_ID: i32 = 2247;
const REAL_COMPANY_VARIABLE_4_CONDITION_ID: i32 = 2248;
const REAL_PLAYER_VARIABLE_1_CONDITION_ID: i32 = 2249;
const REAL_PLAYER_VARIABLE_2_CONDITION_ID: i32 = 2250;
const REAL_PLAYER_VARIABLE_3_CONDITION_ID: i32 = 2251;
const REAL_PLAYER_VARIABLE_4_CONDITION_ID: i32 = 2252;
const REAL_TERRITORY_VARIABLE_1_CONDITION_ID: i32 = 2253;
const REAL_TERRITORY_VARIABLE_2_CONDITION_ID: i32 = 2254;
const REAL_TERRITORY_VARIABLE_3_CONDITION_ID: i32 = 2255;
const REAL_TERRITORY_VARIABLE_4_CONDITION_ID: i32 = 2256;
const REAL_CHAIRMAN_CASH_CONDITION_ID: i32 = 2218;
const REAL_CHAIRMAN_HOLDINGS_TOTAL_CONDITION_ID: i32 = 2239;
const REAL_CHAIRMAN_NET_WORTH_CONDITION_ID: i32 = 2240;
@ -530,7 +550,87 @@ const REAL_OTHER_CARGO_PRODUCTION_TOTAL_CONDITION_ID: i32 = 2421;
const REAL_LIMITED_TRACK_BUILDING_AMOUNT_CONDITION_ID: i32 = 2547;
const REAL_TERRITORY_ACCESS_COST_CONDITION_ID: i32 = 1516;
const REAL_ORDINARY_CONDITION_METADATA: [RealOrdinaryConditionMetadata; 40] = [
const REAL_ORDINARY_CONDITION_METADATA: [RealOrdinaryConditionMetadata; 56] = [
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_WORLD_VARIABLE_1_CONDITION_ID,
label: "Game Variable 1",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(1)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_WORLD_VARIABLE_2_CONDITION_ID,
label: "Game Variable 2",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(2)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_WORLD_VARIABLE_3_CONDITION_ID,
label: "Game Variable 3",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(3)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_WORLD_VARIABLE_4_CONDITION_ID,
label: "Game Variable 4",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(4)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_COMPANY_VARIABLE_1_CONDITION_ID,
label: "Company Variable 1",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(1)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_COMPANY_VARIABLE_2_CONDITION_ID,
label: "Company Variable 2",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(2)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_COMPANY_VARIABLE_3_CONDITION_ID,
label: "Company Variable 3",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(3)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_COMPANY_VARIABLE_4_CONDITION_ID,
label: "Company Variable 4",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(4)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_PLAYER_VARIABLE_1_CONDITION_ID,
label: "Player Variable 1",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(1)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_PLAYER_VARIABLE_2_CONDITION_ID,
label: "Player Variable 2",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(2)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_PLAYER_VARIABLE_3_CONDITION_ID,
label: "Player Variable 3",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(3)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_PLAYER_VARIABLE_4_CONDITION_ID,
label: "Player Variable 4",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(4)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_TERRITORY_VARIABLE_1_CONDITION_ID,
label: "Territory Variable 1",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(1)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_TERRITORY_VARIABLE_2_CONDITION_ID,
label: "Territory Variable 2",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(2)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_TERRITORY_VARIABLE_3_CONDITION_ID,
label: "Territory Variable 3",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(3)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_TERRITORY_VARIABLE_4_CONDITION_ID,
label: "Territory Variable 4",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(4)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 1802,
label: "Current Cash",
@ -3580,6 +3680,13 @@ fn decode_real_condition_row(
let comparator = decode_real_condition_comparator(row.subtype)?;
let value = decode_real_condition_threshold(&row.flag_bytes)?;
match metadata.kind {
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(index)) => {
Some(RuntimeCondition::WorldVariableThreshold {
index,
comparator,
value,
})
}
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(metric)) => {
Some(RuntimeCondition::CompanyNumericThreshold {
target: RuntimeCompanyTarget::ConditionTrueCompany,
@ -3588,6 +3695,26 @@ fn decode_real_condition_row(
value,
})
}
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(index)) => {
Some(RuntimeCondition::CompanyVariableThreshold {
target: RuntimeCompanyTarget::ConditionTrueCompany,
index,
comparator,
value,
})
}
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(index)) => {
negative_sentinel_scope.and_then(|scope| {
real_condition_player_target(scope).map(|target| {
RuntimeCondition::PlayerVariableThreshold {
target,
index,
comparator,
value,
}
})
})
}
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(metric)) => {
negative_sentinel_scope.and_then(|scope| {
real_condition_chairman_target(scope).map(|target| {
@ -3600,6 +3727,16 @@ fn decode_real_condition_row(
})
})
}
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(
index,
)) => negative_sentinel_scope
.filter(|scope| scope.territory_scope_selector_is_0x63)
.map(|_| RuntimeCondition::TerritoryVariableThreshold {
target: RuntimeTerritoryTarget::AllTerritories,
index,
comparator,
value,
}),
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(metric)) => {
negative_sentinel_scope
.filter(|scope| scope.territory_scope_selector_is_0x63)
@ -3727,6 +3864,22 @@ fn real_condition_chairman_target(
}
}
fn real_condition_player_target(
scope: &SmpLoadedPackedEventNegativeSentinelScopeSummary,
) -> Option<RuntimePlayerTarget> {
match scope.player_test_scope {
RuntimePlayerConditionTestScope::AllPlayers => Some(RuntimePlayerTarget::AllActive),
RuntimePlayerConditionTestScope::SelectedPlayerOnly => {
Some(RuntimePlayerTarget::SelectedPlayer)
}
RuntimePlayerConditionTestScope::AiPlayersOnly => Some(RuntimePlayerTarget::AiPlayers),
RuntimePlayerConditionTestScope::HumanPlayersOnly => {
Some(RuntimePlayerTarget::HumanPlayers)
}
RuntimePlayerConditionTestScope::Disabled => None,
}
}
fn real_grouped_effect_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
@ -4981,9 +5134,13 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
fn runtime_condition_supported_for_save_import(condition: &RuntimeCondition) -> bool {
match condition {
RuntimeCondition::CompanyNumericThreshold { .. }
RuntimeCondition::WorldVariableThreshold { .. }
| RuntimeCondition::CompanyNumericThreshold { .. }
| RuntimeCondition::CompanyVariableThreshold { .. }
| RuntimeCondition::PlayerVariableThreshold { .. }
| RuntimeCondition::ChairmanNumericThreshold { .. }
| RuntimeCondition::TerritoryNumericThreshold { .. }
| RuntimeCondition::TerritoryVariableThreshold { .. }
| RuntimeCondition::CompanyTerritoryNumericThreshold { .. }
| RuntimeCondition::SpecialConditionThreshold { .. }
| RuntimeCondition::CandidateAvailabilityThreshold { .. }
@ -11188,6 +11345,14 @@ mod tests {
#[test]
fn looks_up_checked_in_chairman_and_governance_condition_metadata() {
let world_variable = real_ordinary_condition_metadata(REAL_WORLD_VARIABLE_1_CONDITION_ID)
.expect("world-variable condition metadata should exist");
assert_eq!(world_variable.label, "Game Variable 1");
let player_variable = real_ordinary_condition_metadata(REAL_PLAYER_VARIABLE_3_CONDITION_ID)
.expect("player-variable condition metadata should exist");
assert_eq!(player_variable.label, "Player Variable 3");
let chairman_cash = real_ordinary_condition_metadata(REAL_CHAIRMAN_CASH_CONDITION_ID)
.expect("chairman cash condition metadata should exist");
assert_eq!(chairman_cash.label, "Player Cash");
@ -11228,6 +11393,116 @@ mod tests {
assert_eq!(book_value.label, "Book Value Per Share");
}
#[test]
fn decodes_world_variable_condition() {
let row = SmpLoadedPackedEventConditionRowSummary {
row_index: 0,
raw_condition_id: REAL_WORLD_VARIABLE_1_CONDITION_ID,
subtype: 4,
flag_bytes: {
let mut bytes = vec![0; 25];
bytes[0..4].copy_from_slice(&111_i32.to_le_bytes());
bytes
},
candidate_name: None,
comparator: Some("eq".to_string()),
metric: Some("Game Variable 1".to_string()),
semantic_family: Some("numeric_threshold".to_string()),
semantic_preview: Some("Test Game Variable 1 == 111".to_string()),
recovered_cargo_slot: None,
recovered_cargo_class: None,
requires_candidate_name_binding: false,
notes: vec![],
};
assert_eq!(
decode_real_condition_row(&row, None),
Some(RuntimeCondition::WorldVariableThreshold {
index: 1,
comparator: RuntimeConditionComparator::Eq,
value: 111,
})
);
}
#[test]
fn decodes_player_variable_condition_from_selected_player_scope() {
let row = SmpLoadedPackedEventConditionRowSummary {
row_index: 0,
raw_condition_id: REAL_PLAYER_VARIABLE_3_CONDITION_ID,
subtype: 4,
flag_bytes: {
let mut bytes = vec![0; 25];
bytes[0..4].copy_from_slice(&333_i32.to_le_bytes());
bytes
},
candidate_name: None,
comparator: Some("eq".to_string()),
metric: Some("Player Variable 3".to_string()),
semantic_family: Some("numeric_threshold".to_string()),
semantic_preview: Some("Test Player Variable 3 == 333".to_string()),
recovered_cargo_slot: None,
recovered_cargo_class: None,
requires_candidate_name_binding: false,
notes: vec![],
};
let negative_scope = SmpLoadedPackedEventNegativeSentinelScopeSummary {
company_test_scope: RuntimeCompanyConditionTestScope::Disabled,
player_test_scope: RuntimePlayerConditionTestScope::SelectedPlayerOnly,
territory_scope_selector_is_0x63: false,
source_row_indexes: vec![0],
};
assert_eq!(
decode_real_condition_row(&row, Some(&negative_scope)),
Some(RuntimeCondition::PlayerVariableThreshold {
target: RuntimePlayerTarget::SelectedPlayer,
index: 3,
comparator: RuntimeConditionComparator::Eq,
value: 333,
})
);
}
#[test]
fn decodes_territory_variable_condition_with_world_territory_scope() {
let row = SmpLoadedPackedEventConditionRowSummary {
row_index: 0,
raw_condition_id: REAL_TERRITORY_VARIABLE_4_CONDITION_ID,
subtype: 4,
flag_bytes: {
let mut bytes = vec![0; 25];
bytes[0..4].copy_from_slice(&444_i32.to_le_bytes());
bytes
},
candidate_name: None,
comparator: Some("eq".to_string()),
metric: Some("Territory Variable 4".to_string()),
semantic_family: Some("numeric_threshold".to_string()),
semantic_preview: Some("Test Territory Variable 4 == 444".to_string()),
recovered_cargo_slot: None,
recovered_cargo_class: None,
requires_candidate_name_binding: false,
notes: vec![],
};
let negative_scope = SmpLoadedPackedEventNegativeSentinelScopeSummary {
company_test_scope: RuntimeCompanyConditionTestScope::Disabled,
player_test_scope: RuntimePlayerConditionTestScope::Disabled,
territory_scope_selector_is_0x63: true,
source_row_indexes: vec![0],
};
assert_eq!(
decode_real_condition_row(&row, Some(&negative_scope)),
Some(RuntimeCondition::TerritoryVariableThreshold {
target: RuntimeTerritoryTarget::AllTerritories,
index: 4,
comparator: RuntimeConditionComparator::Eq,
value: 444,
})
);
}
#[test]
fn decodes_chairman_cash_condition_from_selected_player_scope() {
let row = SmpLoadedPackedEventConditionRowSummary {

View file

@ -782,10 +782,25 @@ fn evaluate_record_conditions(
}
let mut company_matches: Option<BTreeSet<u32>> = None;
let mut player_matches: Option<BTreeSet<u32>> = None;
let mut chairman_matches: Option<BTreeSet<u32>> = None;
for condition in conditions {
match condition {
RuntimeCondition::WorldVariableThreshold {
index,
comparator,
value,
} => {
let actual = state
.world_runtime_variables
.get(index)
.copied()
.unwrap_or(0);
if !compare_condition_value(actual, *comparator, *value) {
return Ok(None);
}
}
RuntimeCondition::CompanyNumericThreshold {
target,
metric,
@ -821,6 +836,37 @@ fn evaluate_record_conditions(
return Ok(None);
}
}
RuntimeCondition::CompanyVariableThreshold {
target,
index,
comparator,
value,
} => {
let resolved = resolve_company_target_ids(
state,
target,
&ResolvedConditionContext::default(),
)?;
let matching = resolved
.into_iter()
.filter(|company_id| {
let actual = state
.company_runtime_variables
.get(company_id)
.and_then(|vars| vars.get(index))
.copied()
.unwrap_or(0);
compare_condition_value(actual, *comparator, *value)
})
.collect::<BTreeSet<_>>();
if matching.is_empty() {
return Ok(None);
}
intersect_company_matches(&mut company_matches, matching);
if company_matches.as_ref().is_some_and(BTreeSet::is_empty) {
return Ok(None);
}
}
RuntimeCondition::TerritoryNumericThreshold {
target,
metric,
@ -833,6 +879,56 @@ fn evaluate_record_conditions(
return Ok(None);
}
}
RuntimeCondition::TerritoryVariableThreshold {
target,
index,
comparator,
value,
} => {
let territory_ids = resolve_territory_target_ids(state, target)?;
let actual = territory_ids
.iter()
.map(|territory_id| {
state
.territory_runtime_variables
.get(territory_id)
.and_then(|vars| vars.get(index))
.copied()
.unwrap_or(0)
})
.sum::<i64>();
if !compare_condition_value(actual, *comparator, *value) {
return Ok(None);
}
}
RuntimeCondition::PlayerVariableThreshold {
target,
index,
comparator,
value,
} => {
let resolved =
resolve_player_target_ids(state, target, &ResolvedConditionContext::default())?;
let matching = resolved
.into_iter()
.filter(|player_id| {
let actual = state
.player_runtime_variables
.get(player_id)
.and_then(|vars| vars.get(index))
.copied()
.unwrap_or(0);
compare_condition_value(actual, *comparator, *value)
})
.collect::<BTreeSet<_>>();
if matching.is_empty() {
return Ok(None);
}
intersect_player_matches(&mut player_matches, matching);
if player_matches.as_ref().is_some_and(BTreeSet::is_empty) {
return Ok(None);
}
}
RuntimeCondition::ChairmanNumericThreshold {
target,
metric,
@ -1050,7 +1146,7 @@ fn evaluate_record_conditions(
Ok(Some(ResolvedConditionContext {
matching_company_ids: company_matches.unwrap_or_default(),
matching_player_ids: BTreeSet::new(),
matching_player_ids: player_matches.unwrap_or_default(),
matching_chairman_profile_ids: chairman_matches.unwrap_or_default(),
}))
}
@ -1066,6 +1162,17 @@ fn intersect_company_matches(company_matches: &mut Option<BTreeSet<u32>>, next:
}
}
fn intersect_player_matches(player_matches: &mut Option<BTreeSet<u32>>, next: BTreeSet<u32>) {
match player_matches {
Some(existing) => {
existing.retain(|player_id| next.contains(player_id));
}
None => {
*player_matches = Some(next);
}
}
}
fn intersect_chairman_matches(chairman_matches: &mut Option<BTreeSet<u32>>, next: BTreeSet<u32>) {
match chairman_matches {
Some(existing) => {
@ -1586,8 +1693,9 @@ mod tests {
RuntimeChairmanMetric, RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany,
RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeCondition,
RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
RuntimePlayer, RuntimeSaveProfileState, RuntimeServiceState, RuntimeTerritory,
RuntimeTerritoryTarget, RuntimeTrackPieceCounts, RuntimeTrain, RuntimeWorldRestoreState,
RuntimePlayer, RuntimePlayerTarget, RuntimeSaveProfileState, RuntimeServiceState,
RuntimeTerritory, RuntimeTerritoryTarget, RuntimeTrackPieceCounts, RuntimeTrain,
RuntimeWorldRestoreState,
};
fn state() -> RuntimeState {
@ -2965,6 +3073,122 @@ mod tests {
assert_eq!(state.world_flags.get("world_scalar_condition_failed"), None);
}
#[test]
fn evaluates_runtime_variable_conditions_before_effects_run() {
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,
}],
selected_company_id: Some(1),
players: vec![RuntimePlayer {
player_id: 1,
current_cash: 50,
active: true,
controller_kind: RuntimeCompanyControllerKind::Human,
}],
selected_player_id: Some(1),
territories: vec![RuntimeTerritory {
territory_id: 7,
name: Some("North".to_string()),
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
world_runtime_variables: BTreeMap::from([(1, 111)]),
company_runtime_variables: BTreeMap::from([(1, BTreeMap::from([(2, 222)]))]),
player_runtime_variables: BTreeMap::from([(1, BTreeMap::from([(3, 333)]))]),
territory_runtime_variables: BTreeMap::from([(7, BTreeMap::from([(4, 444)]))]),
event_runtime_records: vec![
RuntimeEventRecord {
record_id: 27,
trigger_kind: 7,
active: true,
service_count: 0,
marks_collection_dirty: false,
one_shot: false,
has_fired: false,
conditions: vec![
RuntimeCondition::WorldVariableThreshold {
index: 1,
comparator: RuntimeConditionComparator::Eq,
value: 111,
},
RuntimeCondition::CompanyVariableThreshold {
target: RuntimeCompanyTarget::SelectedCompany,
index: 2,
comparator: RuntimeConditionComparator::Eq,
value: 222,
},
RuntimeCondition::PlayerVariableThreshold {
target: RuntimePlayerTarget::SelectedPlayer,
index: 3,
comparator: RuntimeConditionComparator::Eq,
value: 333,
},
RuntimeCondition::TerritoryVariableThreshold {
target: RuntimeTerritoryTarget::Ids { ids: vec![7] },
index: 4,
comparator: RuntimeConditionComparator::Eq,
value: 444,
},
],
effects: vec![RuntimeEffect::SetWorldFlag {
key: "runtime_variable_condition_passed".to_string(),
value: true,
}],
},
RuntimeEventRecord {
record_id: 28,
trigger_kind: 7,
active: true,
service_count: 0,
marks_collection_dirty: false,
one_shot: false,
has_fired: false,
conditions: vec![RuntimeCondition::PlayerVariableThreshold {
target: RuntimePlayerTarget::SelectedPlayer,
index: 4,
comparator: RuntimeConditionComparator::Gt,
value: 0,
}],
effects: vec![RuntimeEffect::SetWorldFlag {
key: "runtime_variable_condition_failed".to_string(),
value: true,
}],
},
],
..state()
};
let result = execute_step_command(
&mut state,
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
)
.expect("runtime-variable conditions should evaluate successfully");
assert_eq!(result.service_events[0].serviced_record_ids, vec![27]);
assert_eq!(
state.world_flags.get("runtime_variable_condition_passed"),
Some(&true)
);
assert_eq!(
state.world_flags.get("runtime_variable_condition_failed"),
None
);
}
#[test]
fn one_shot_record_only_fires_once() {
let mut state = RuntimeState {

View file

@ -114,14 +114,17 @@ The highest-value next passes are now:
through stable normalized keys such as `world.build_stations_cost` and
`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
world/company/player/territory state, and the matching ordinary-condition strip now gates
imported records through those same runtime-owned variable maps; these variables are still
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`
`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
named cargo-production strip `180..229` now lands on named cargo production overrides too
- the named cargo-price strip `106..176` remains explicit
`blocked_evidence_blocked_descriptor` parity until descriptor ordering is pinned more strongly
- the add-building strip `503..519` is now explicitly classified as recovered shell-owned parity,
with tracked fixture coverage, instead of generic unresolved descriptor residue
- widen real packed-event executable coverage descriptor by descriptor after identity, target mask,
and normalized effect semantics are all grounded, not just after row framing is parsed
- the first grounded condition-side unlock now exists for negative-sentinel `raw_condition_id = -1`

View file

@ -76,14 +76,17 @@ Implemented today:
landing surface too: representative descriptors import as `SetWorldScalarOverride` and land in
`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
world/company/player/territory variable maps, and the matching ordinary-condition strip now
gates runtime records through those same maps too, so those descriptors and conditions no longer
depend on generic evidence parity or ad hoc fixture-only state
- 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 Farm/Mine Production` import through bounded cargo override surfaces, and the grounded
named cargo-production strip `180..229` now imports through named cargo production overrides too
- the named cargo-price strip `106..176` now sits on explicit
`blocked_evidence_blocked_descriptor` parity instead of generic unmapped-descriptor frontier
- the add-building strip `503..519` is now explicitly classified as recovered shell-owned parity
with tracked fixture coverage, not generic unresolved descriptor residue
- a minimal event-owned train surface and an opaque economic-status lane now exist in runtime
state, and real descriptors `8` = `Economic Status`, `9` = `Confiscate All`, and `15` =
`Retire Train` now import and execute through the ordinary runtime path when overlay context

View file

@ -0,0 +1,37 @@
{
"format_version": 1,
"fixture_id": "packed-event-add-building-shell-save-slice-fixture",
"source": {
"kind": "captured-runtime",
"description": "Fixture pinning the explicit shell-owned descriptor frontier for recovered add-building rows."
},
"state_save_slice_path": "packed-event-add-building-shell-save-slice.json",
"commands": [
{
"kind": "service_trigger_kind",
"trigger_kind": 7
}
],
"expected_summary": {
"calendar_projection_source": "default-1830-placeholder",
"calendar_projection_is_placeholder": true,
"packed_event_collection_present": true,
"packed_event_record_count": 1,
"packed_event_decoded_record_count": 1,
"packed_event_parity_only_record_count": 1,
"packed_event_blocked_shell_owned_descriptor_count": 1,
"event_runtime_record_count": 0,
"total_event_record_service_count": 0,
"total_trigger_dispatch_count": 1
},
"expected_state_fragment": {
"packed_event_collection": {
"records": [
{
"import_outcome": "blocked_shell_owned_descriptor"
}
]
},
"event_runtime_records": []
}
}

View file

@ -0,0 +1,110 @@
{
"format_version": 1,
"save_slice_id": "packed-event-add-building-shell-save-slice",
"source": {
"description": "Tracked save-slice document pinning the recovered add-building strip on the explicit shell-owned descriptor frontier.",
"original_save_filename": "captured-add-building-shell.gms",
"original_save_sha256": "add-building-shell-sample-sha256",
"notes": [
"tracked as JSON save-slice document rather than raw .smp",
"pins descriptor 503 as recovered shell-owned parity instead of unresolved residue"
]
},
"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": 28928,
"records_tag_offset": 29184,
"close_tag_offset": 29696,
"packed_state_version": 1001,
"packed_state_version_hex": "0x000003e9",
"live_id_bound": 75,
"live_record_count": 1,
"live_entry_ids": [75],
"decoded_record_count": 1,
"imported_runtime_record_count": 0,
"records": [
{
"record_index": 0,
"live_entry_id": 75,
"payload_offset": 29186,
"payload_len": 120,
"decode_status": "parity_only",
"payload_family": "real_packed_v1",
"trigger_kind": 7,
"active": null,
"marks_collection_dirty": null,
"one_shot": false,
"compact_control": {
"mode_byte_0x7ef": 6,
"primary_selector_0x7f0": 0,
"grouped_mode_0x7f4": 2,
"one_shot_header_0x7f5": 1,
"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": [],
"negative_sentinel_scope": null,
"grouped_effect_row_counts": [1, 0, 0, 0],
"grouped_effect_rows": [
{
"group_index": 0,
"row_index": 0,
"descriptor_id": 503,
"descriptor_label": "Add Building Slot 1",
"target_mask_bits": 8,
"parameter_family": "world_building_spawn",
"opcode": 3,
"raw_scalar_value": 1,
"value_byte_0x09": 0,
"value_dword_0x0d": 0,
"value_byte_0x11": 0,
"value_byte_0x12": 0,
"value_word_0x14": 0,
"value_word_0x16": 0,
"row_shape": "scalar_assignment",
"semantic_family": "scalar_assignment",
"semantic_preview": "Set Add Building Slot 1 to 1",
"locomotive_name": null,
"notes": [
"descriptor recovered in the checked-in effect table as shell_owned parity"
]
}
],
"decoded_conditions": [],
"decoded_actions": [],
"executable_import_ready": false,
"notes": [
"add-building descriptor is recovered but remains shell-owned parity"
]
}
]
},
"notes": [
"recovered add-building descriptor sample"
]
}
}

View file

@ -0,0 +1,134 @@
{
"format_version": 1,
"fixture_id": "packed-event-runtime-variable-condition-overlay-fixture",
"source": {
"kind": "captured-runtime",
"description": "Fixture proving runtime-variable ordinary conditions gate imported records through bounded world/company/player/territory variable surfaces."
},
"state_import_path": "packed-event-runtime-variable-condition-overlay.json",
"commands": [
{
"kind": "service_trigger_kind",
"trigger_kind": 7
},
{
"kind": "service_trigger_kind",
"trigger_kind": 8
}
],
"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": 5,
"packed_event_decoded_record_count": 5,
"packed_event_imported_runtime_record_count": 5,
"event_runtime_record_count": 5,
"world_runtime_variable_count": 2,
"company_runtime_variable_owner_count": 1,
"player_runtime_variable_owner_count": 1,
"territory_runtime_variable_owner_count": 1,
"total_event_record_service_count": 5,
"total_trigger_dispatch_count": 2
},
"expected_state_fragment": {
"world_runtime_variables": {
"1": 111,
"2": 211
},
"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"
},
{
"import_outcome": "imported",
"decoded_conditions": [
{
"kind": "world_variable_threshold",
"index": 1,
"comparator": "eq",
"value": 111
},
{
"kind": "company_variable_threshold",
"target": {
"kind": "selected_company"
},
"index": 2,
"comparator": "eq",
"value": 222
},
{
"kind": "player_variable_threshold",
"target": {
"kind": "selected_player"
},
"index": 3,
"comparator": "eq",
"value": 333
},
{
"kind": "territory_variable_threshold",
"target": {
"kind": "all_territories"
},
"index": 4,
"comparator": "eq",
"value": 444
}
]
}
]
},
"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
},
{
"record_id": 75,
"service_count": 1
}
]
}
}

View file

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

View file

@ -0,0 +1,479 @@
{
"format_version": 1,
"save_slice_id": "packed-event-runtime-variable-condition-save-slice",
"source": {
"description": "Tracked save-slice document proving runtime-variable ordinary conditions gate a packed world-variable effect.",
"original_save_filename": "captured-runtime-variable-condition.gms",
"original_save_sha256": "runtime-variable-condition-sample-sha256",
"notes": [
"tracked as JSON save-slice document rather than raw .smp",
"uses overlay-backed company/player/territory context while proving world/company/player/territory runtime-variable conditions together"
]
},
"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": 34944,
"packed_state_version": 1001,
"packed_state_version_hex": "0x000003e9",
"live_id_bound": 75,
"live_record_count": 5,
"live_entry_ids": [71, 72, 73, 74, 75],
"decoded_record_count": 5,
"imported_runtime_record_count": 5,
"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"
]
},
{
"record_index": 4,
"live_entry_id": 75,
"payload_offset": 34208,
"payload_len": 224,
"decode_status": "executable",
"payload_family": "real_packed_v1",
"trigger_kind": 8,
"one_shot": false,
"compact_control": {
"mode_byte_0x7ef": 6,
"primary_selector_0x7f0": 99,
"grouped_mode_0x7f4": 2,
"one_shot_header_0x7f5": 0,
"modifier_flag_0x7f9": 2,
"modifier_flag_0x7fa": 2,
"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": 4,
"standalone_condition_rows": [
{
"row_index": 0,
"raw_condition_id": 2241,
"subtype": 4,
"flag_bytes": [111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"candidate_name": null,
"comparator": "eq",
"metric": "Game Variable 1",
"semantic_family": "numeric_threshold",
"semantic_preview": "Test Game Variable 1 == 111",
"requires_candidate_name_binding": false,
"notes": []
},
{
"row_index": 1,
"raw_condition_id": 2246,
"subtype": 4,
"flag_bytes": [222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"candidate_name": null,
"comparator": "eq",
"metric": "Company Variable 2",
"semantic_family": "numeric_threshold",
"semantic_preview": "Test Company Variable 2 == 222",
"requires_candidate_name_binding": false,
"notes": []
},
{
"row_index": 2,
"raw_condition_id": 2251,
"subtype": 4,
"flag_bytes": [77, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"candidate_name": null,
"comparator": "eq",
"metric": "Player Variable 3",
"semantic_family": "numeric_threshold",
"semantic_preview": "Test Player Variable 3 == 333",
"requires_candidate_name_binding": false,
"notes": []
},
{
"row_index": 3,
"raw_condition_id": 2256,
"subtype": 4,
"flag_bytes": [188, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"candidate_name": null,
"comparator": "eq",
"metric": "Territory Variable 4",
"semantic_family": "numeric_threshold",
"semantic_preview": "Test Territory Variable 4 == 444",
"requires_candidate_name_binding": false,
"notes": []
}
],
"negative_sentinel_scope": {
"company_test_scope": "selected_company_only",
"player_test_scope": "selected_player_only",
"territory_scope_selector_is_0x63": true,
"source_row_indexes": [0]
},
"grouped_effect_row_counts": [1, 0, 0, 0],
"grouped_effect_rows": [
{
"group_index": 0,
"row_index": 0,
"descriptor_id": 40,
"descriptor_label": "Game Variable 2",
"target_mask_bits": 8,
"parameter_family": "runtime_variable_scalar",
"opcode": 3,
"raw_scalar_value": 211,
"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 2 to 211",
"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_conditions": [
{
"kind": "world_variable_threshold",
"index": 1,
"comparator": "eq",
"value": 111
},
{
"kind": "company_variable_threshold",
"target": {
"kind": "condition_true_company"
},
"index": 2,
"comparator": "eq",
"value": 222
},
{
"kind": "player_variable_threshold",
"target": {
"kind": "selected_player"
},
"index": 3,
"comparator": "eq",
"value": 333
},
{
"kind": "territory_variable_threshold",
"target": {
"kind": "all_territories"
},
"index": 4,
"comparator": "eq",
"value": 444
}
],
"decoded_actions": [
{
"kind": "set_world_variable",
"index": 2,
"value": 211
}
],
"executable_import_ready": true,
"notes": [
"runtime variable conditions gate a world-variable effect"
]
}
]
},
"notes": [
"runtime variable condition executable sample"
]
}
}

View file

@ -208,6 +208,7 @@ def classify(
executable_in_runtime = True
elif 503 <= descriptor_id <= 519:
parameter_family = "world_building_spawn"
label = f"Add Building Slot {descriptor_id - 502}"
runtime_status = "shell_owned"
elif signature_byte_0x63 == 0 and signature_byte_0x64 == 0x8F:
parameter_family = "runtime_variable_scalar"