Close EventEffects semantic descriptor frontier

This commit is contained in:
Jan Petykiewicz 2026-04-16 20:20:41 -07:00
commit 51a7bbd756
18 changed files with 5432 additions and 175 deletions

View file

@ -31,12 +31,21 @@ company-targeted and chairman-targeted descriptor and condition batches can exec
save-slice fixtures without overlay snapshots when that context is present; raw `.gms` inspection save-slice fixtures without overlay snapshots when that context is present; raw `.gms` inspection
still does not reconstruct those company/chairman collections automatically. A checked-in still does not reconstruct those company/chairman collections automatically. A checked-in
`EventEffects` export now exists too in `EventEffects` export now exists too in
`artifacts/exports/rt3-1.06/event-effects-table.json`, and the first recovered governance `artifacts/exports/rt3-1.06/event-effects-table.json`, and a checked-in semantic closure layer now
descriptor tranche now imports through the generic company-governance scalar effect surface: exists beside it in `artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json`. Recovered
descriptor rows now land on explicit semantic frontier buckets such as
`blocked_shell_owned_descriptor`, `blocked_evidence_blocked_descriptor`, and
`blocked_variant_or_scope_blocked_descriptor` instead of generic anonymous descriptor residue. The
first recovered governance descriptor tranche now imports through the generic
company-governance scalar effect surface:
descriptor `56` `Credit Rating` and descriptor `57` `Prime Rate` execute from ordinary real packed descriptor `56` `Credit Rating` and descriptor `57` `Prime Rate` execute from ordinary real packed
rows, while adjacent recovered finance/control-transfer descriptors such as `55` `Stock Prices` rows, while adjacent recovered finance/control-transfer descriptors such as `55` `Stock Prices`
and `58` `Merger Premium` now land on explicit shell-owned parity instead of anonymous unmapped and `58` `Merger Premium` now land on explicit shell-owned parity instead of anonymous unmapped
descriptor residue. The first grounded descriptor residue. The recovered whole-game scalar economy/performance strip `59..104` now has a
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 first grounded
condition-side unlock now exists for negative-sentinel `raw_condition_id = -1` company scopes, and condition-side unlock now exists for negative-sentinel `raw_condition_id = -1` company scopes, and
the first ordinary nonnegative condition batch now executes too: numeric-threshold company the first ordinary nonnegative condition batch now executes too: numeric-threshold company
finance, company track, aggregate territory track, and company-territory track rows can import finance, company track, aggregate territory track, and company-territory track rows can import
@ -82,9 +91,8 @@ same world-scalar runtime surfaces too: named locomotive availability thresholds
locomotive cost thresholds, named cargo-production slot thresholds, aggregate cargo-production locomotive cost thresholds, named cargo-production slot thresholds, aggregate cargo-production
thresholds, factory/farm-mine/other cargo-production thresholds, limited-track-building-amount thresholds, factory/farm-mine/other cargo-production thresholds, limited-track-building-amount
thresholds, and territory-access-cost thresholds all gate imported runtime records through the thresholds, and territory-access-cost thresholds all gate imported runtime records through the
same service path. Explicit same service path. Explicit unmapped world-condition frontier buckets still remain where current
unmapped world-condition and world-descriptor checked-in metadata stops, and
frontier buckets still remain where current checked-in metadata stops, and
`blocked_missing_locomotive_catalog_context` is now reserved for intentionally incomplete save-side `blocked_missing_locomotive_catalog_context` is now reserved for intentionally incomplete save-side
catalog context instead of the normal save-slice path. Cargo slot identity and class metadata are catalog context instead of the normal save-slice path. Cargo slot identity and class metadata are
now save-native too: the recipe-book probe lowers into `RuntimeState.cargo_catalog`, so save-slice now save-native too: the recipe-book probe lowers into `RuntimeState.cargo_catalog`, so save-slice

File diff suppressed because it is too large Load diff

View file

@ -4476,6 +4476,9 @@ mod tests {
let world_scalar_executable_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join( let world_scalar_executable_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
"../../fixtures/runtime/packed-event-world-scalar-executable-save-slice-fixture.json", "../../fixtures/runtime/packed-event-world-scalar-executable-save-slice-fixture.json",
); );
let world_scalar_override_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
"../../fixtures/runtime/packed-event-world-scalar-override-save-slice-fixture.json",
);
let world_scalar_condition_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join( let world_scalar_condition_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
"../../fixtures/runtime/packed-event-world-scalar-condition-save-slice-fixture.json", "../../fixtures/runtime/packed-event-world-scalar-condition-save-slice-fixture.json",
); );
@ -4585,6 +4588,8 @@ mod tests {
.expect("save-slice-backed recovered scalar-band parity fixture should summarize"); .expect("save-slice-backed recovered scalar-band parity fixture should summarize");
run_runtime_summarize_fixture(&world_scalar_executable_fixture) run_runtime_summarize_fixture(&world_scalar_executable_fixture)
.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)
.expect("save-slice-backed world-scalar override fixture should summarize");
run_runtime_summarize_fixture(&world_scalar_condition_fixture) run_runtime_summarize_fixture(&world_scalar_condition_fixture)
.expect("save-slice-backed executable world-scalar condition fixture should summarize"); .expect("save-slice-backed executable world-scalar condition fixture should summarize");
run_runtime_summarize_fixture(&world_scalar_condition_parity_fixture) run_runtime_summarize_fixture(&world_scalar_condition_parity_fixture)

View file

@ -190,6 +190,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}, },
@ -377,6 +378,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_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

@ -148,6 +148,10 @@ pub struct ExpectedRuntimeSummary {
#[serde(default)] #[serde(default)]
pub packed_event_blocked_shell_owned_descriptor_count: Option<usize>, pub packed_event_blocked_shell_owned_descriptor_count: Option<usize>,
#[serde(default)] #[serde(default)]
pub packed_event_blocked_evidence_blocked_descriptor_count: Option<usize>,
#[serde(default)]
pub packed_event_blocked_variant_or_scope_blocked_descriptor_count: Option<usize>,
#[serde(default)]
pub packed_event_blocked_unmapped_real_descriptor_count: Option<usize>, pub packed_event_blocked_unmapped_real_descriptor_count: Option<usize>,
#[serde(default)] #[serde(default)]
pub packed_event_blocked_unmapped_world_descriptor_count: Option<usize>, pub packed_event_blocked_unmapped_world_descriptor_count: Option<usize>,
@ -184,6 +188,8 @@ 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_scalar_override_count: Option<usize>,
#[serde(default)]
pub special_condition_count: Option<usize>, pub special_condition_count: Option<usize>,
#[serde(default)] #[serde(default)]
pub enabled_special_condition_count: Option<usize>, pub enabled_special_condition_count: Option<usize>,
@ -761,6 +767,22 @@ impl ExpectedRuntimeSummary {
)); ));
} }
} }
if let Some(count) = self.packed_event_blocked_evidence_blocked_descriptor_count {
if actual.packed_event_blocked_evidence_blocked_descriptor_count != count {
mismatches.push(format!(
"packed_event_blocked_evidence_blocked_descriptor_count mismatch: expected {count}, got {}",
actual.packed_event_blocked_evidence_blocked_descriptor_count
));
}
}
if let Some(count) = self.packed_event_blocked_variant_or_scope_blocked_descriptor_count {
if actual.packed_event_blocked_variant_or_scope_blocked_descriptor_count != count {
mismatches.push(format!(
"packed_event_blocked_variant_or_scope_blocked_descriptor_count mismatch: expected {count}, got {}",
actual.packed_event_blocked_variant_or_scope_blocked_descriptor_count
));
}
}
if let Some(count) = self.packed_event_blocked_unmapped_real_descriptor_count { if let Some(count) = self.packed_event_blocked_unmapped_real_descriptor_count {
if actual.packed_event_blocked_unmapped_real_descriptor_count != count { if actual.packed_event_blocked_unmapped_real_descriptor_count != count {
mismatches.push(format!( mismatches.push(format!(
@ -905,6 +927,14 @@ impl ExpectedRuntimeSummary {
)); ));
} }
} }
if let Some(count) = self.world_scalar_override_count {
if actual.world_scalar_override_count != count {
mismatches.push(format!(
"world_scalar_override_count mismatch: expected {count}, got {}",
actual.world_scalar_override_count
));
}
}
if let Some(count) = self.special_condition_count { if let Some(count) = self.special_condition_count {
if actual.special_condition_count != count { if actual.special_condition_count != count {
mismatches.push(format!( mismatches.push(format!(

View file

@ -31,6 +31,7 @@ pub const REQUIRED_EXPORTS: &[&str] = &[
"artifacts/exports/rt3-1.06/pending-template-store-record-kinds.csv", "artifacts/exports/rt3-1.06/pending-template-store-record-kinds.csv",
"artifacts/exports/rt3-1.06/pending-template-store-management.md", "artifacts/exports/rt3-1.06/pending-template-store-management.md",
"artifacts/exports/rt3-1.06/event-effects-table.json", "artifacts/exports/rt3-1.06/event-effects-table.json",
"artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json",
]; ];
pub const REQUIRED_ATLAS_HEADINGS: &[&str] = &[ pub const REQUIRED_ATLAS_HEADINGS: &[&str] = &[

View file

@ -106,6 +106,7 @@ struct SaveSliceProjection {
cargo_catalog: Option<Vec<RuntimeCargoCatalogEntry>>, cargo_catalog: Option<Vec<RuntimeCargoCatalogEntry>>,
named_locomotive_cost: BTreeMap<String, u32>, named_locomotive_cost: BTreeMap<String, u32>,
cargo_production_overrides: BTreeMap<u32, u32>, cargo_production_overrides: BTreeMap<u32, u32>,
world_scalar_overrides: BTreeMap<String, i64>,
special_conditions: BTreeMap<String, u32>, special_conditions: BTreeMap<String, u32>,
} }
@ -277,6 +278,7 @@ pub fn project_save_slice_to_runtime_state_import(
named_locomotive_availability: projection.named_locomotive_availability, named_locomotive_availability: projection.named_locomotive_availability,
named_locomotive_cost: projection.named_locomotive_cost, named_locomotive_cost: projection.named_locomotive_cost,
cargo_production_overrides: projection.cargo_production_overrides, cargo_production_overrides: projection.cargo_production_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(),
}; };
@ -364,6 +366,7 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
named_locomotive_availability: projection.named_locomotive_availability, named_locomotive_availability: projection.named_locomotive_availability,
named_locomotive_cost: base_state.named_locomotive_cost.clone(), named_locomotive_cost: base_state.named_locomotive_cost.clone(),
cargo_production_overrides: base_state.cargo_production_overrides.clone(), cargo_production_overrides: base_state.cargo_production_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(),
}; };
@ -872,6 +875,7 @@ fn project_save_slice_components(
let named_locomotive_cost = BTreeMap::new(); let named_locomotive_cost = BTreeMap::new();
let cargo_production_overrides = BTreeMap::new(); let cargo_production_overrides = BTreeMap::new();
let world_scalar_overrides = BTreeMap::new();
let mut packed_event_context = company_context.clone(); let mut packed_event_context = company_context.clone();
if has_company_projection { if has_company_projection {
@ -948,6 +952,7 @@ fn project_save_slice_components(
cargo_catalog, cargo_catalog,
named_locomotive_cost, named_locomotive_cost,
cargo_production_overrides, cargo_production_overrides,
world_scalar_overrides,
special_conditions, special_conditions,
}) })
} }
@ -1316,6 +1321,10 @@ fn lower_contextual_real_grouped_effects(
if real_grouped_row_is_unsupported_chairman_target_scope(row) { if real_grouped_row_is_unsupported_chairman_target_scope(row) {
return Err(ImportBlocker::ChairmanTargetScope); return Err(ImportBlocker::ChairmanTargetScope);
} }
if let Some(effect) = lower_contextual_world_scalar_override_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;
@ -1347,6 +1356,28 @@ fn lower_contextual_real_grouped_effects(
Ok(effects) Ok(effects)
} }
fn lower_contextual_world_scalar_override_effect(
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
) -> Result<Option<RuntimeEffect>, ImportBlocker> {
if row.parameter_family.as_deref() != Some("world_scalar_override") {
return Ok(None);
}
if row.row_shape != "scalar_assignment" {
return Ok(None);
}
let Some(key) = row
.descriptor_label
.as_deref()
.map(crate::smp::runtime_world_scalar_key_from_label)
else {
return Ok(None);
};
Ok(Some(RuntimeEffect::SetWorldScalarOverride {
key,
value: i64::from(row.raw_scalar_value),
}))
}
fn lower_contextual_locomotive_availability_effect( fn lower_contextual_locomotive_availability_effect(
row: &SmpLoadedPackedEventGroupedEffectRowSummary, row: &SmpLoadedPackedEventGroupedEffectRowSummary,
company_context: &ImportRuntimeContext, company_context: &ImportRuntimeContext,
@ -1564,6 +1595,12 @@ fn lower_condition_targets_in_effect(
key: key.clone(), key: key.clone(),
value: *value, value: *value,
}, },
RuntimeEffect::SetWorldScalarOverride { key, value } => {
RuntimeEffect::SetWorldScalarOverride {
key: key.clone(),
value: *value,
}
}
RuntimeEffect::SetLimitedTrackBuildingAmount { value } => { RuntimeEffect::SetLimitedTrackBuildingAmount { value } => {
RuntimeEffect::SetLimitedTrackBuildingAmount { value: *value } RuntimeEffect::SetLimitedTrackBuildingAmount { value: *value }
} }
@ -2346,6 +2383,12 @@ fn smp_runtime_effect_to_runtime_effect(
value: *value, value: *value,
}) })
} }
RuntimeEffect::SetWorldScalarOverride { key, value } => {
Ok(RuntimeEffect::SetWorldScalarOverride {
key: key.clone(),
value: *value,
})
}
RuntimeEffect::SetCargoProductionSlot { slot, value } => { RuntimeEffect::SetCargoProductionSlot { slot, value } => {
Ok(RuntimeEffect::SetCargoProductionSlot { Ok(RuntimeEffect::SetCargoProductionSlot {
slot: *slot, slot: *slot,
@ -2690,6 +2733,27 @@ fn determine_packed_event_import_outcome(
{ {
return "blocked_shell_owned_descriptor".to_string(); return "blocked_shell_owned_descriptor".to_string();
} }
if record
.grouped_effect_rows
.iter()
.any(real_grouped_row_is_evidence_blocked_descriptor_family)
{
return "blocked_evidence_blocked_descriptor".to_string();
}
if record
.grouped_effect_rows
.iter()
.any(real_grouped_row_is_variant_or_scope_blocked_descriptor_family)
{
return "blocked_variant_or_scope_blocked_descriptor".to_string();
}
if record
.grouped_effect_rows
.iter()
.any(real_grouped_row_is_unsupported_executable_descriptor_variant)
{
return "blocked_variant_or_scope_blocked_descriptor".to_string();
}
if record if record
.grouped_effect_rows .grouped_effect_rows
.iter() .iter()
@ -2788,9 +2852,47 @@ fn real_grouped_row_is_world_state_family(
fn real_grouped_row_is_shell_owned_descriptor_family( fn real_grouped_row_is_shell_owned_descriptor_family(
row: &SmpLoadedPackedEventGroupedEffectRowSummary, row: &SmpLoadedPackedEventGroupedEffectRowSummary,
) -> bool {
real_grouped_row_has_runtime_status(row, "shell_owned")
}
fn real_grouped_row_is_evidence_blocked_descriptor_family(
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
) -> bool {
real_grouped_row_has_runtime_status(row, "evidence_blocked")
}
fn real_grouped_row_is_variant_or_scope_blocked_descriptor_family(
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
) -> bool {
real_grouped_row_has_runtime_status(row, "variant_or_scope_blocked")
}
fn real_grouped_row_has_runtime_status(
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
status: &str,
) -> bool { ) -> bool {
crate::smp::grouped_effect_descriptor_runtime_status_name(row.descriptor_id) crate::smp::grouped_effect_descriptor_runtime_status_name(row.descriptor_id)
.is_some_and(|status| status == "shell_owned") .is_some_and(|runtime_status| runtime_status == status)
}
fn real_grouped_row_is_unsupported_executable_descriptor_variant(
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
) -> bool {
if !real_grouped_row_has_runtime_status(row, "executable") {
return false;
}
match row.descriptor_id {
1 => !(row.opcode == 8 && row.row_shape == "multivalue_scalar"),
2 => !(row.opcode == 8 && row.row_shape == "multivalue_scalar"),
8 | 108 | 109 | 122 => row.row_shape != "scalar_assignment",
13 | 14 => !(row.row_shape == "bool_toggle" && row.raw_scalar_value != 0),
56 | 57 => row.row_shape != "scalar_assignment",
_ => {
row.parameter_family.as_deref() == Some("world_scalar_override")
&& row.row_shape != "scalar_assignment"
}
}
} }
fn packed_record_company_target_import_blocker( fn packed_record_company_target_import_blocker(
@ -2980,6 +3082,7 @@ 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::SetWorldScalarOverride { .. }
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. } | RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
| RuntimeEffect::SetEconomicStatusCode { .. } | RuntimeEffect::SetEconomicStatusCode { .. }
| RuntimeEffect::SetPlayerCash { .. } | RuntimeEffect::SetPlayerCash { .. }
@ -3093,6 +3196,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::SetWorldScalarOverride { .. }
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. } | RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
| RuntimeEffect::SetEconomicStatusCode { .. } | RuntimeEffect::SetEconomicStatusCode { .. }
| RuntimeEffect::SetCandidateAvailability { .. } | RuntimeEffect::SetCandidateAvailability { .. }
@ -3449,6 +3553,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
} }
@ -6634,6 +6739,7 @@ mod tests {
]), ]),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -7039,6 +7145,7 @@ mod tests {
("Locomotive 101".to_string(), 200000), ("Locomotive 101".to_string(), 200000),
]), ]),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -7211,7 +7318,7 @@ mod tests {
.packed_event_collection .packed_event_collection
.as_ref() .as_ref()
.and_then(|summary| summary.records[0].import_outcome.as_deref()), .and_then(|summary| summary.records[0].import_outcome.as_deref()),
Some("blocked_unmapped_world_descriptor") Some("blocked_evidence_blocked_descriptor")
); );
} }
@ -7439,6 +7546,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -8207,7 +8315,7 @@ mod tests {
.packed_event_collection .packed_event_collection
.as_ref() .as_ref()
.and_then(|summary| summary.records[0].import_outcome.as_deref()), .and_then(|summary| summary.records[0].import_outcome.as_deref()),
Some("blocked_unmapped_real_descriptor") Some("blocked_variant_or_scope_blocked_descriptor")
); );
} }
@ -8295,7 +8403,7 @@ mod tests {
.packed_event_collection .packed_event_collection
.as_ref() .as_ref()
.and_then(|summary| summary.records[0].import_outcome.as_deref()), .and_then(|summary| summary.records[0].import_outcome.as_deref()),
Some("blocked_unmapped_real_descriptor") Some("blocked_variant_or_scope_blocked_descriptor")
); );
} }
@ -9713,6 +9821,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -9886,6 +9995,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -11574,6 +11684,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState { service_state: RuntimeServiceState {
periodic_boundary_calls: 9, periodic_boundary_calls: 9,
@ -11758,6 +11869,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_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

@ -110,6 +110,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_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

@ -467,6 +467,10 @@ pub enum RuntimeEffect {
slot: u32, slot: u32,
value: u32, value: u32,
}, },
SetWorldScalarOverride {
key: String,
value: i64,
},
SetTerritoryAccessCost { SetTerritoryAccessCost {
value: u32, value: u32,
}, },
@ -842,6 +846,8 @@ 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_scalar_overrides: BTreeMap<String, i64>,
#[serde(default)]
pub special_conditions: BTreeMap<String, u32>, pub special_conditions: BTreeMap<String, u32>,
#[serde(default)] #[serde(default)]
pub service_state: RuntimeServiceState, pub service_state: RuntimeServiceState,
@ -1489,6 +1495,11 @@ impl RuntimeState {
)); ));
} }
} }
for key in self.world_scalar_overrides.keys() {
if key.trim().is_empty() {
return Err("world_scalar_overrides contains an empty key".to_string());
}
}
for key in self.special_conditions.keys() { for key in self.special_conditions.keys() {
if key.trim().is_empty() { if key.trim().is_empty() {
return Err("special_conditions contains an empty key".to_string()); return Err("special_conditions contains an empty key".to_string());
@ -1512,6 +1523,11 @@ fn validate_runtime_effect(
return Err("key must not be empty".to_string()); return Err("key must not be empty".to_string());
} }
} }
RuntimeEffect::SetWorldScalarOverride { key, .. } => {
if key.trim().is_empty() {
return Err("key must not be empty".to_string());
}
}
RuntimeEffect::SetLimitedTrackBuildingAmount { .. } RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
| RuntimeEffect::SetEconomicStatusCode { .. } => {} | RuntimeEffect::SetEconomicStatusCode { .. } => {}
RuntimeEffect::SetCompanyCash { target, .. } RuntimeEffect::SetCompanyCash { target, .. }
@ -1901,6 +1917,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1964,6 +1981,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2031,6 +2049,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2108,6 +2127,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2210,6 +2230,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2264,6 +2285,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2318,6 +2340,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2389,6 +2412,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2450,6 +2474,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2515,6 +2540,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2576,6 +2602,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2643,6 +2670,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2704,6 +2732,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2765,6 +2794,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2819,6 +2849,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2883,6 +2914,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_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

@ -141,20 +141,19 @@ enum RealGroupedEffectRuntimeStatus {
} }
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Debug, Clone, PartialEq, Deserialize)]
struct CheckedInEventEffectsTableArtifact { struct CheckedInEventEffectsSemanticCatalogArtifact {
descriptors: Vec<CheckedInEventEffectDescriptorRow>, descriptors: Vec<CheckedInEventEffectSemanticRow>,
} }
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Debug, Clone, PartialEq, Deserialize)]
struct CheckedInEventEffectDescriptorRow { struct CheckedInEventEffectSemanticRow {
descriptor_id: u32, descriptor_id: u32,
selector_order: f32,
target_mask_bits: u8,
label_id: u32,
label: String, label: String,
signature_byte_0x63: u8, target_mask_bits: u8,
signature_byte_0x64: u8, parameter_family: String,
signature_hex_0x63_0x6d: String, runtime_key: Option<String>,
runtime_status: String,
executable_in_runtime: bool,
} }
const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetadata; 12] = [ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetadata; 12] = [
@ -281,10 +280,10 @@ fn checked_in_event_effect_descriptor_rows()
-> &'static BTreeMap<u32, RealGroupedEffectDescriptorMetadata> { -> &'static BTreeMap<u32, RealGroupedEffectDescriptorMetadata> {
static ROWS: OnceLock<BTreeMap<u32, RealGroupedEffectDescriptorMetadata>> = OnceLock::new(); static ROWS: OnceLock<BTreeMap<u32, RealGroupedEffectDescriptorMetadata>> = OnceLock::new();
ROWS.get_or_init(|| { ROWS.get_or_init(|| {
let artifact: CheckedInEventEffectsTableArtifact = serde_json::from_str(include_str!( let artifact: CheckedInEventEffectsSemanticCatalogArtifact = serde_json::from_str(
"../../../artifacts/exports/rt3-1.06/event-effects-table.json" include_str!("../../../artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json"),
)) )
.expect("checked-in event-effects artifact should parse"); .expect("checked-in event-effects semantic catalog should parse");
artifact artifact
.descriptors .descriptors
.into_iter() .into_iter()
@ -299,14 +298,20 @@ fn checked_in_event_effect_descriptor_rows()
} }
fn checked_in_event_effect_descriptor_metadata( fn checked_in_event_effect_descriptor_metadata(
row: CheckedInEventEffectDescriptorRow, row: CheckedInEventEffectSemanticRow,
) -> RealGroupedEffectDescriptorMetadata { ) -> RealGroupedEffectDescriptorMetadata {
let label = Box::leak(row.label.clone().into_boxed_str()) as &'static str; let label = Box::leak(row.label.clone().into_boxed_str()) as &'static str;
let (parameter_family, runtime_key, runtime_status, executable_in_runtime) = let parameter_family = Box::leak(row.parameter_family.into_boxed_str()) as &'static str;
classify_checked_in_event_effect_descriptor(&row, label); let runtime_key = row
debug_assert!(!row.signature_hex_0x63_0x6d.is_empty()); .runtime_key
debug_assert!(row.label_id <= u16::MAX as u32); .map(|key| Box::leak(key.into_boxed_str()) as &'static str);
let _ = row.selector_order; let runtime_status = match row.runtime_status.as_str() {
"executable" => RealGroupedEffectRuntimeStatus::Executable,
"shell_owned" => RealGroupedEffectRuntimeStatus::ShellOwned,
"evidence_blocked" => RealGroupedEffectRuntimeStatus::EvidenceBlocked,
"variant_or_scope_blocked" => RealGroupedEffectRuntimeStatus::VariantOrScopeBlocked,
other => panic!("unknown checked-in event-effect runtime status {other}"),
};
RealGroupedEffectDescriptorMetadata { RealGroupedEffectDescriptorMetadata {
descriptor_id: row.descriptor_id, descriptor_id: row.descriptor_id,
label, label,
@ -314,141 +319,10 @@ fn checked_in_event_effect_descriptor_metadata(
parameter_family, parameter_family,
runtime_key, runtime_key,
runtime_status, runtime_status,
executable_in_runtime, executable_in_runtime: row.executable_in_runtime,
} }
} }
fn classify_checked_in_event_effect_descriptor(
row: &CheckedInEventEffectDescriptorRow,
label: &'static str,
) -> (
&'static str,
Option<&'static str>,
RealGroupedEffectRuntimeStatus,
bool,
) {
let descriptor_id = row.descriptor_id;
match descriptor_id {
4..=7 => {
return (
"scenario_outcome_shell_action",
None,
RealGroupedEffectRuntimeStatus::ShellOwned,
false,
);
}
10 | 11 | 12 | 23 => {
return (
"company_confiscation_variant",
None,
RealGroupedEffectRuntimeStatus::VariantOrScopeBlocked,
false,
);
}
17..=21 => {
return (
"company_or_territory_destruction_variant",
None,
RealGroupedEffectRuntimeStatus::ShellOwned,
false,
);
}
24 => {
return (
"control_transfer_shell_action",
None,
RealGroupedEffectRuntimeStatus::ShellOwned,
false,
);
}
56 => {
return (
"company_governance_scalar",
None,
RealGroupedEffectRuntimeStatus::Executable,
true,
);
}
57 => {
return (
"company_governance_scalar",
None,
RealGroupedEffectRuntimeStatus::Executable,
true,
);
}
58 => {
return (
"company_finance_shell_scalar",
None,
RealGroupedEffectRuntimeStatus::ShellOwned,
false,
);
}
_ => {}
}
if row.signature_byte_0x63 == 0 && row.signature_byte_0x64 == 0x8f {
return (
"runtime_variable_scalar",
None,
RealGroupedEffectRuntimeStatus::EvidenceBlocked,
false,
);
}
if row.target_mask_bits == 0x0b && label == "Stock Prices" {
return (
"company_finance_shell_scalar",
None,
RealGroupedEffectRuntimeStatus::ShellOwned,
false,
);
}
if label.contains("Cost")
|| label.contains("Revenue")
|| label.contains("Speed")
|| label.contains("Reliability")
|| label.contains("Acceleration")
|| label.contains("Pulling Power")
|| label.contains("Usage Rate")
|| label.contains("Maintenance")
{
return (
"world_scalar_economy_or_locomotive",
None,
RealGroupedEffectRuntimeStatus::EvidenceBlocked,
false,
);
}
if label.contains("Earthquake") || label.contains("Storm") {
return (
"world_disaster_scalar",
None,
RealGroupedEffectRuntimeStatus::EvidenceBlocked,
false,
);
}
if label.contains("Add Building") {
return (
"world_building_spawn",
None,
RealGroupedEffectRuntimeStatus::ShellOwned,
false,
);
}
(
"recovered_effect_table_descriptor",
None,
RealGroupedEffectRuntimeStatus::EvidenceBlocked,
false,
)
}
pub(crate) fn grouped_effect_descriptor_runtime_status_name( pub(crate) fn grouped_effect_descriptor_runtime_status_name(
descriptor_id: u32, descriptor_id: u32,
) -> Option<&'static str> { ) -> Option<&'static str> {
@ -4101,7 +3975,27 @@ fn runtime_world_flag_key(
}) })
} }
fn runtime_world_flag_key_from_label(label: &str) -> String { pub(crate) fn runtime_world_flag_key_from_label(label: &str) -> String {
normalize_runtime_world_key(label)
}
fn runtime_world_scalar_key(
descriptor_metadata: RealGroupedEffectDescriptorMetadata,
) -> Option<String> {
descriptor_metadata
.runtime_key
.map(str::to_string)
.or_else(|| {
(descriptor_metadata.parameter_family == "world_scalar_override")
.then(|| normalize_runtime_world_key(descriptor_metadata.label))
})
}
pub(crate) fn runtime_world_scalar_key_from_label(label: &str) -> String {
normalize_runtime_world_key(label)
}
fn normalize_runtime_world_key(label: &str) -> String {
let mut key = String::with_capacity(label.len() + 6); let mut key = String::with_capacity(label.len() + 6);
key.push_str("world."); key.push_str("world.");
let mut last_was_underscore = false; let mut last_was_underscore = false;
@ -4140,6 +4034,9 @@ fn derive_real_grouped_target_subject(
if row.parameter_family.as_deref() == Some("company_governance_scalar") { if row.parameter_family.as_deref() == Some("company_governance_scalar") {
return Some(RealGroupedTargetSubject::Company); return Some(RealGroupedTargetSubject::Company);
} }
if row.parameter_family.as_deref() == Some("world_scalar_override") {
return Some(RealGroupedTargetSubject::WholeGame);
}
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),
@ -4353,6 +4250,16 @@ fn decode_real_grouped_effect_action(
}); });
} }
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.parameter_family == "world_scalar_override"
&& row.row_shape == "scalar_assignment"
{
return Some(RuntimeEffect::SetWorldScalarOverride {
key: runtime_world_scalar_key(descriptor_metadata)?,
value: i64::from(row.raw_scalar_value),
});
}
if descriptor_metadata.executable_in_runtime if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.parameter_family == "cargo_production_scalar" && descriptor_metadata.parameter_family == "cargo_production_scalar"
&& row.row_shape == "scalar_assignment" && row.row_shape == "scalar_assignment"
@ -4657,6 +4564,7 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
| RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. } | RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. }
| RuntimeEffect::SetNamedLocomotiveCost { .. } | RuntimeEffect::SetNamedLocomotiveCost { .. }
| RuntimeEffect::SetCargoProductionSlot { .. } | RuntimeEffect::SetCargoProductionSlot { .. }
| RuntimeEffect::SetWorldScalarOverride { .. }
| RuntimeEffect::SetTerritoryAccessCost { .. } | RuntimeEffect::SetTerritoryAccessCost { .. }
| RuntimeEffect::SetSpecialCondition { .. } | RuntimeEffect::SetSpecialCondition { .. }
| RuntimeEffect::ConfiscateCompanyAssets { .. } | RuntimeEffect::ConfiscateCompanyAssets { .. }

View file

@ -313,6 +313,9 @@ fn apply_runtime_effects(
RuntimeEffect::SetWorldFlag { key, value } => { RuntimeEffect::SetWorldFlag { key, value } => {
state.world_flags.insert(key.clone(), *value); state.world_flags.insert(key.clone(), *value);
} }
RuntimeEffect::SetWorldScalarOverride { key, value } => {
state.world_scalar_overrides.insert(key.clone(), *value);
}
RuntimeEffect::SetLimitedTrackBuildingAmount { value } => { RuntimeEffect::SetLimitedTrackBuildingAmount { value } => {
state.world_restore.limited_track_building_amount = Some(*value); state.world_restore.limited_track_building_amount = Some(*value);
} }
@ -1559,6 +1562,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
} }
@ -1912,6 +1916,10 @@ mod tests {
has_fired: false, has_fired: false,
conditions: Vec::new(), conditions: Vec::new(),
effects: vec![ effects: vec![
RuntimeEffect::SetWorldScalarOverride {
key: "world.build_stations_cost".to_string(),
value: 350000,
},
RuntimeEffect::SetCargoProductionSlot { RuntimeEffect::SetCargoProductionSlot {
slot: 1, slot: 1,
value: 125, value: 125,
@ -1928,9 +1936,15 @@ mod tests {
) )
.expect("world scalar override effects should succeed"); .expect("world scalar override effects should succeed");
assert_eq!(
state
.world_scalar_overrides
.get("world.build_stations_cost"),
Some(&350000)
);
assert_eq!(state.cargo_production_overrides.get(&1), Some(&125)); assert_eq!(state.cargo_production_overrides.get(&1), Some(&125));
assert_eq!(state.world_restore.territory_access_cost, Some(750000)); assert_eq!(state.world_restore.territory_access_cost, Some(750000));
assert_eq!(result.service_events[0].applied_effect_count, 2); assert_eq!(result.service_events[0].applied_effect_count, 3);
} }
#[test] #[test]

View file

@ -71,6 +71,8 @@ pub struct RuntimeSummary {
pub packed_event_blocked_unmapped_world_condition_count: usize, pub packed_event_blocked_unmapped_world_condition_count: usize,
pub packed_event_blocked_missing_compact_control_count: usize, pub packed_event_blocked_missing_compact_control_count: usize,
pub packed_event_blocked_shell_owned_descriptor_count: usize, pub packed_event_blocked_shell_owned_descriptor_count: usize,
pub packed_event_blocked_evidence_blocked_descriptor_count: usize,
pub packed_event_blocked_variant_or_scope_blocked_descriptor_count: usize,
pub packed_event_blocked_unmapped_real_descriptor_count: usize, pub packed_event_blocked_unmapped_real_descriptor_count: usize,
pub packed_event_blocked_unmapped_world_descriptor_count: usize, pub packed_event_blocked_unmapped_world_descriptor_count: usize,
pub packed_event_blocked_territory_access_variant_count: usize, pub packed_event_blocked_territory_access_variant_count: usize,
@ -89,6 +91,7 @@ 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_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,
pub save_profile_kind: Option<String>, pub save_profile_kind: Option<String>,
@ -511,6 +514,34 @@ impl RuntimeSummary {
.count() .count()
}) })
.unwrap_or(0), .unwrap_or(0),
packed_event_blocked_evidence_blocked_descriptor_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_evidence_blocked_descriptor")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_variant_or_scope_blocked_descriptor_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_variant_or_scope_blocked_descriptor")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_unmapped_real_descriptor_count: state packed_event_blocked_unmapped_real_descriptor_count: state
.packed_event_collection .packed_event_collection
.as_ref() .as_ref()
@ -676,6 +707,7 @@ 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_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
.special_conditions .special_conditions
@ -903,6 +935,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1019,6 +1052,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1072,6 +1106,7 @@ mod tests {
]), ]),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1117,6 +1152,7 @@ mod tests {
("GP7".to_string(), 175000), ("GP7".to_string(), 175000),
]), ]),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1160,6 +1196,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::from([(1, 125), (2, 250)]), cargo_production_overrides: BTreeMap::from([(1, 125), (2, 250)]),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1265,6 +1302,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1348,6 +1386,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1427,6 +1466,7 @@ mod tests {
named_locomotive_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(), cargo_production_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

@ -98,12 +98,21 @@ The highest-value next passes are now:
can execute from standalone save-slice fixtures without overlay snapshots when that context is can execute from standalone save-slice fixtures without overlay snapshots when that context is
present; raw `.gms` inspection/export still does not reconstruct those company/chairman surfaces present; raw `.gms` inspection/export still does not reconstruct those company/chairman surfaces
- a checked-in `EventEffects` export now exists at - a checked-in `EventEffects` export now exists at
`artifacts/exports/rt3-1.06/event-effects-table.json`, and the first recovered governance `artifacts/exports/rt3-1.06/event-effects-table.json`, and a checked-in semantic closure layer
descriptor tranche now executes through the generic company-governance scalar effect surface: now exists at `artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json`
descriptor `56` `Credit Rating` and descriptor `57` `Prime Rate` - recovered descriptor rows now land on explicit semantic frontier buckets such as
`blocked_shell_owned_descriptor`, `blocked_evidence_blocked_descriptor`, and
`blocked_variant_or_scope_blocked_descriptor` instead of generic unmapped-descriptor residue
- the first recovered governance descriptor tranche now executes through the generic
company-governance scalar effect surface: descriptor `56` `Credit Rating` and descriptor `57`
`Prime Rate`
- adjacent recovered finance/control-transfer descriptors such as `55` `Stock Prices` and `58` - adjacent recovered finance/control-transfer descriptors such as `55` `Stock Prices` and `58`
`Merger Premium` now land on explicit shell-owned descriptor parity instead of generic unmapped `Merger Premium` now land on explicit shell-owned descriptor parity instead of generic unmapped
descriptor residue descriptor residue
- the recovered whole-game scalar economy/performance strip `59..104` now has a bounded runtime
landing surface too: representative rows execute into `RuntimeState.world_scalar_overrides`
through stable normalized keys such as `world.build_stations_cost` and
`world.track_maintenance_cost`
- widen real packed-event executable coverage descriptor by descriptor after identity, target mask, - widen real packed-event executable coverage descriptor by descriptor after identity, target mask,
and normalized effect semantics are all grounded, not just after row framing is parsed and normalized effect semantics are all grounded, not just after row framing is parsed
- the first grounded condition-side unlock now exists for negative-sentinel `raw_condition_id = -1` - the first grounded condition-side unlock now exists for negative-sentinel `raw_condition_id = -1`
@ -203,6 +212,14 @@ python3 tools/py/extract_event_effects.py \
artifacts/exports/rt3-1.06/event-effects-table.json artifacts/exports/rt3-1.06/event-effects-table.json
``` ```
Regenerate the checked-in `EventEffects` semantic catalog with:
```bash
python3 tools/py/build_event_effect_semantic_catalog.py \
artifacts/exports/rt3-1.06/event-effects-table.json \
artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json
```
That default export now walks two roots: That default export now walks two roots:
- `entry:0x005a313b` - `entry:0x005a313b`

View file

@ -61,13 +61,20 @@ Implemented today:
without overlay snapshots when the checked-in documents include that context, while raw `.gms` without overlay snapshots when the checked-in documents include that context, while raw `.gms`
inspection/export still leaves those company/chairman surfaces absent inspection/export still leaves those company/chairman surfaces absent
- a checked-in `EventEffects` export now exists too at - a checked-in `EventEffects` export now exists too at
`artifacts/exports/rt3-1.06/event-effects-table.json`, and the first recovered `artifacts/exports/rt3-1.06/event-effects-table.json`, and a checked-in semantic closure layer
company-governance descriptor tranche now executes through the generic now exists at `artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json`
- recovered descriptor rows now classify into explicit semantic frontier buckets such as
`blocked_shell_owned_descriptor`, `blocked_evidence_blocked_descriptor`, and
`blocked_variant_or_scope_blocked_descriptor` instead of generic unmapped-descriptor buckets
- the first recovered company-governance descriptor tranche now executes through the generic
`SetCompanyGovernanceScalar` surface: descriptor `56` `Credit Rating` and descriptor `57` `SetCompanyGovernanceScalar` surface: descriptor `56` `Credit Rating` and descriptor `57`
`Prime Rate` now import through ordinary company target lowering `Prime Rate` now import through ordinary company target lowering
- adjacent recovered finance/control-transfer descriptors such as `55` `Stock Prices` and `58` - adjacent recovered finance/control-transfer descriptors such as `55` `Stock Prices` and `58`
`Merger Premium` now land on explicit shell-owned descriptor parity instead of generic unmapped `Merger Premium` now land on explicit shell-owned descriptor parity instead of generic unmapped
descriptor buckets descriptor buckets
- 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
`RuntimeState.world_scalar_overrides`
- a minimal event-owned train surface and an opaque economic-status lane now exist in runtime - a minimal event-owned train surface and an opaque economic-status lane now exist in runtime
state, and real descriptors `8` = `Economic Status`, `9` = `Confiscate All`, and `15` = state, and real descriptors `8` = `Economic Status`, `9` = `Confiscate All`, and `15` =
`Retire Train` now import and execute through the ordinary runtime path when overlay context `Retire Train` now import and execute through the ordinary runtime path when overlay context
@ -138,11 +145,11 @@ Implemented today:
That means the next implementation work is still breadth, not bootstrap. The current descriptor That means the next implementation work is still breadth, not bootstrap. The current descriptor
frontier is no longer anonymous id recovery; it is the remaining recovered-but-nonexecutable frontier is no longer anonymous id recovery; it is the remaining recovered-but-nonexecutable
families from the checked-in effect table, especially broader company/world scalar bands and the families from the checked-in semantic catalog, especially cargo-price, add-building, and other
shell-owned finance/control-transfer rows that still need final classification or bounded runtime descriptor clusters that now have explicit shell-owned or evidence-blocked status but not yet a
landing surfaces. Raw save reconstruction for company/chairman context is still a later tranche bounded executable landing surface. Raw save reconstruction for company/chairman context is still a
once stronger evidence exists. Richer runtime ownership should still be added only where a later later tranche once stronger evidence exists. Richer runtime ownership should still be added only
descriptor or condition family needs more than the current event-owned roster. where a later descriptor or condition family needs more than the current event-owned roster.
## Why This Boundary ## Why This Boundary

View file

@ -31,8 +31,9 @@
"packed_event_blocked_missing_condition_context_count": 0, "packed_event_blocked_missing_condition_context_count": 0,
"packed_event_blocked_territory_condition_scope_count": 0, "packed_event_blocked_territory_condition_scope_count": 0,
"packed_event_blocked_missing_compact_control_count": 0, "packed_event_blocked_missing_compact_control_count": 0,
"packed_event_blocked_evidence_blocked_descriptor_count": 1,
"packed_event_blocked_unmapped_real_descriptor_count": 0, "packed_event_blocked_unmapped_real_descriptor_count": 0,
"packed_event_blocked_unmapped_world_descriptor_count": 1, "packed_event_blocked_unmapped_world_descriptor_count": 0,
"packed_event_blocked_structural_only_count": 0, "packed_event_blocked_structural_only_count": 0,
"event_runtime_record_count": 2, "event_runtime_record_count": 2,
"cargo_production_override_count": 0, "cargo_production_override_count": 0,
@ -67,7 +68,7 @@
{ {
"decode_status": "parity_only", "decode_status": "parity_only",
"payload_family": "real_packed_v1", "payload_family": "real_packed_v1",
"import_outcome": "blocked_unmapped_world_descriptor", "import_outcome": "blocked_evidence_blocked_descriptor",
"grouped_effect_rows": [ "grouped_effect_rows": [
{ {
"descriptor_id": 352, "descriptor_id": 352,

View file

@ -0,0 +1,70 @@
{
"format_version": 1,
"fixture_id": "packed-event-world-scalar-override-save-slice-fixture",
"source": {
"kind": "captured-runtime",
"description": "Fixture proving representative 59..104 world scalar descriptors execute through the bounded world_scalar_overrides runtime surface."
},
"state_save_slice_path": "packed-event-world-scalar-override-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_imported_runtime_record_count": 1,
"event_runtime_record_count": 1,
"world_scalar_override_count": 4,
"total_event_record_service_count": 1,
"total_trigger_dispatch_count": 1
},
"expected_state_fragment": {
"world_scalar_overrides": {
"world.build_stations_cost": 350000,
"world.track_maintenance_cost": 22000,
"world.all_engine_speeds": 115,
"world.hotel_revenue": 175
},
"packed_event_collection": {
"records": [
{
"import_outcome": "imported"
}
]
},
"event_runtime_records": [
{
"record_id": 61,
"service_count": 1,
"effects": [
{
"kind": "set_world_scalar_override",
"key": "world.build_stations_cost",
"value": 350000
},
{
"kind": "set_world_scalar_override",
"key": "world.track_maintenance_cost",
"value": 22000
},
{
"kind": "set_world_scalar_override",
"key": "world.all_engine_speeds",
"value": 115
},
{
"kind": "set_world_scalar_override",
"key": "world.hotel_revenue",
"value": 175
}
]
}
]
}
}

View file

@ -0,0 +1,198 @@
{
"format_version": 1,
"save_slice_id": "packed-event-world-scalar-override-save-slice",
"source": {
"description": "Tracked save-slice document proving the checked-in scalar economy/performance descriptor band imports into world_scalar_overrides.",
"original_save_filename": "captured-world-scalar-override.gms",
"original_save_sha256": "world-scalar-override-sample-sha256",
"notes": [
"tracked as JSON save-slice document rather than raw .smp",
"pins representative executable descriptors from the recovered 59..104 economy/performance band"
]
},
"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,
"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": 31360,
"records_tag_offset": 31616,
"close_tag_offset": 32128,
"packed_state_version": 1001,
"packed_state_version_hex": "0x000003e9",
"live_id_bound": 61,
"live_record_count": 1,
"live_entry_ids": [61],
"decoded_record_count": 1,
"imported_runtime_record_count": 0,
"records": [
{
"record_index": 0,
"live_entry_id": 61,
"payload_offset": 31648,
"payload_len": 176,
"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": [4, 0, 0, 0],
"grouped_effect_rows": [
{
"group_index": 0,
"row_index": 0,
"descriptor_id": 59,
"descriptor_label": "Build Stations Cost",
"target_mask_bits": 15,
"parameter_family": "world_scalar_override",
"opcode": 3,
"raw_scalar_value": 350000,
"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 Build Stations Cost to 350000",
"recovered_locomotive_id": null,
"locomotive_name": null,
"notes": [
"descriptor recovered from checked-in EventEffects semantic catalog"
]
},
{
"group_index": 0,
"row_index": 1,
"descriptor_id": 66,
"descriptor_label": "Track Maintenance Cost",
"target_mask_bits": 11,
"parameter_family": "world_scalar_override",
"opcode": 3,
"raw_scalar_value": 22000,
"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 Track Maintenance Cost to 22000",
"recovered_locomotive_id": null,
"locomotive_name": null,
"notes": [
"descriptor recovered from checked-in EventEffects semantic catalog"
]
},
{
"group_index": 0,
"row_index": 2,
"descriptor_id": 81,
"descriptor_label": "All Engine Speeds",
"target_mask_bits": 15,
"parameter_family": "world_scalar_override",
"opcode": 3,
"raw_scalar_value": 115,
"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 All Engine Speeds to 115",
"recovered_locomotive_id": null,
"locomotive_name": null,
"notes": [
"descriptor recovered from checked-in EventEffects semantic catalog"
]
},
{
"group_index": 0,
"row_index": 3,
"descriptor_id": 102,
"descriptor_label": "Hotel Revenue",
"target_mask_bits": 15,
"parameter_family": "world_scalar_override",
"opcode": 3,
"raw_scalar_value": 175,
"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 Hotel Revenue to 175",
"recovered_locomotive_id": null,
"locomotive_name": null,
"notes": [
"descriptor recovered from checked-in EventEffects semantic catalog"
]
}
],
"decoded_actions": [
{
"kind": "set_world_scalar_override",
"key": "world.build_stations_cost",
"value": 350000
},
{
"kind": "set_world_scalar_override",
"key": "world.track_maintenance_cost",
"value": 22000
},
{
"kind": "set_world_scalar_override",
"key": "world.all_engine_speeds",
"value": 115
},
{
"kind": "set_world_scalar_override",
"key": "world.hotel_revenue",
"value": 175
}
],
"executable_import_ready": true,
"notes": [
"representative whole-game scalar economy/performance descriptors execute through world_scalar_overrides"
]
}
]
},
"notes": [
"world scalar override executable sample"
]
}
}

View file

@ -0,0 +1,124 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
from pathlib import Path
def normalize_world_scalar_key(label: str) -> str:
parts: list[str] = []
current = []
for ch in label:
if ch.isalnum():
current.append(ch.lower())
elif current:
parts.append("".join(current))
current = []
if current:
parts.append("".join(current))
return "world." + "_".join(parts)
def classify(row: dict[str, object]) -> dict[str, object]:
descriptor_id = int(row["descriptor_id"])
label = str(row["label"])
signature_byte_0x63 = int(row["signature_byte_0x63"])
signature_byte_0x64 = int(row["signature_byte_0x64"])
parameter_family = "recovered_effect_table_descriptor"
runtime_key = None
runtime_status = "evidence_blocked"
executable_in_runtime = False
if 4 <= descriptor_id <= 7:
parameter_family = "scenario_outcome_shell_action"
runtime_status = "shell_owned"
elif descriptor_id in {10, 11, 12, 23}:
parameter_family = "company_confiscation_variant"
runtime_status = "variant_or_scope_blocked"
elif 17 <= descriptor_id <= 21:
parameter_family = "company_or_territory_destruction_variant"
runtime_status = "shell_owned"
elif descriptor_id == 24:
parameter_family = "control_transfer_shell_action"
runtime_status = "shell_owned"
elif descriptor_id in {55, 58}:
parameter_family = "company_finance_shell_scalar"
runtime_status = "shell_owned"
elif descriptor_id in {56, 57}:
parameter_family = "company_governance_scalar"
runtime_status = "executable"
executable_in_runtime = True
elif 59 <= descriptor_id <= 104:
parameter_family = "world_scalar_override"
runtime_key = normalize_world_scalar_key(label)
runtime_status = "executable"
executable_in_runtime = True
elif 105 <= descriptor_id <= 176:
parameter_family = "cargo_price_scalar"
elif 177 <= descriptor_id <= 240:
parameter_family = "cargo_production_scalar"
elif 241 <= descriptor_id <= 351 or 457 <= descriptor_id <= 474:
parameter_family = "locomotive_availability_scalar"
elif 352 <= descriptor_id <= 451 or 475 <= descriptor_id <= 502:
parameter_family = "locomotive_cost_scalar"
elif descriptor_id == 453:
parameter_family = "territory_access_cost_scalar"
runtime_status = "executable"
executable_in_runtime = True
elif descriptor_id == 454:
parameter_family = "world_flag_toggle"
runtime_key = "world.all_steam_locos_available"
runtime_status = "executable"
executable_in_runtime = True
elif descriptor_id == 455:
parameter_family = "world_flag_toggle"
runtime_key = "world.all_diesel_locos_available"
runtime_status = "executable"
executable_in_runtime = True
elif descriptor_id == 456:
parameter_family = "world_flag_toggle"
runtime_key = "world.all_electric_locos_available"
runtime_status = "executable"
executable_in_runtime = True
elif 503 <= descriptor_id <= 519:
parameter_family = "world_building_spawn"
runtime_status = "shell_owned"
elif signature_byte_0x63 == 0 and signature_byte_0x64 == 0x8F:
parameter_family = "runtime_variable_scalar"
elif "Earthquake" in label or "Storm" in label:
parameter_family = "world_disaster_scalar"
return {
"descriptor_id": descriptor_id,
"label": label,
"target_mask_bits": int(row["target_mask_bits"]),
"parameter_family": parameter_family,
"runtime_key": runtime_key,
"runtime_status": runtime_status,
"executable_in_runtime": executable_in_runtime,
}
def main() -> None:
parser = argparse.ArgumentParser(
description="Build a checked-in semantic catalog over the raw RT3 EventEffects table."
)
parser.add_argument("raw_table", type=Path)
parser.add_argument("out", type=Path)
args = parser.parse_args()
raw_artifact = json.loads(args.raw_table.read_text(encoding="utf-8"))
descriptors = [classify(row) for row in raw_artifact["descriptors"]]
artifact = {
"descriptor_count": len(descriptors),
"raw_table_binary_sha256": raw_artifact.get("binary_sha256"),
"semantic_catalog_version": 1,
"descriptors": descriptors,
}
args.out.write_text(json.dumps(artifact, indent=2) + "\n", encoding="utf-8")
if __name__ == "__main__":
main()