Execute recovered world scalar event descriptors

This commit is contained in:
Jan Petykiewicz 2026-04-16 11:39:59 -07:00
commit 13c7268b0d
23 changed files with 675 additions and 98 deletions

View file

@ -51,23 +51,25 @@ recovered locomotives-page `real_packed_v1` record that lands in the explicit
`blocked_unmapped_world_descriptor` bucket. The next recovered descriptor band is now partially `blocked_unmapped_world_descriptor` bucket. The next recovered descriptor band is now partially
executable too: descriptors `454..456` (`All Steam/Diesel/Electric Locos Avail.`) now lower executable too: descriptors `454..456` (`All Steam/Diesel/Electric Locos Avail.`) now lower
through checked-in metadata into keyed `world_flags`, while the wider locomotive availability/cost through checked-in metadata into keyed `world_flags`, while the wider locomotive availability/cost
scalar bands are now split more cleanly: the boolean `0/1` availability subset can import through scalar bands are now split more cleanly: the recovered locomotive availability bands can import as
an overlay-backed `RuntimeState.locomotive_catalog` into full scalar overrides through an overlay-backed `RuntimeState.locomotive_catalog` into
`RuntimeState.named_locomotive_availability`, while non-boolean availability payloads still remain `RuntimeState.named_locomotive_availability`, while save-slice-only imports of those rows still
parity-only. The runtime still carries the save-owned named locomotive availability table directly block explicitly when catalog context is missing. The runtime still carries the save-owned named
too: locomotive availability table directly too:
checked-in save-slice documents can populate `RuntimeState.named_locomotive_availability`, and checked-in save-slice documents can populate `RuntimeState.named_locomotive_availability`, and
imported runtime effects can mutate that map through the ordinary event-service path without imported runtime effects can mutate that map through the ordinary event-service path without
needing full Trainbuy or live-locomotive parity. A parallel event-owned named locomotive cost map needing full Trainbuy or live-locomotive parity. A parallel event-owned named locomotive cost map
now exists too: recovered locomotive-cost descriptors from bands `352..451` and `475..500` can now exists too: recovered locomotive-cost descriptors from bands `352..451` and `475..500` can
import through the same overlay-backed locomotive catalog into import through the same overlay-backed locomotive catalog into
`RuntimeState.named_locomotive_cost`, while cargo-production and territory-access-cost rows remain `RuntimeState.named_locomotive_cost`, and the remaining recovered scalar world families now execute
metadata-rich parity-only families until a later slice grounds an honest landing surface. Explicit too: cargo-production slots `230..240` lower into `cargo_production_overrides`, and descriptor
unmapped world-condition and world-descriptor frontier buckets still remain where current `453` lowers into `world_restore.territory_access_cost`. Explicit unmapped world-condition and
checked-in metadata stops. Shell purchase-flow, Trainbuy refresh, cached locomotive-rating world-descriptor frontier buckets still remain where current checked-in metadata stops, with the
recomputation, and selected-profile parity remain out of scope. Mixed supported/unsupported real main scalar residue now being missing-catalog locomotive rows rather than unknown world-side
rows still stay parity-only. The PE32 hook remains useful as capture and integration tooling, but families. Shell purchase-flow, Trainbuy refresh, cached locomotive-rating recomputation, and
it is no longer the main execution milestone. selected-profile parity remain out of scope. Mixed supported/unsupported real rows still stay
parity-only. The PE32 hook remains useful as capture and integration tooling, but it is no longer
the main execution milestone.
## Project Docs ## Project Docs

View file

@ -4468,6 +4468,9 @@ mod tests {
let scalar_band_parity_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join( let scalar_band_parity_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
"../../fixtures/runtime/packed-event-world-scalar-band-parity-save-slice-fixture.json", "../../fixtures/runtime/packed-event-world-scalar-band-parity-save-slice-fixture.json",
); );
let world_scalar_executable_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
"../../fixtures/runtime/packed-event-world-scalar-executable-save-slice-fixture.json",
);
run_runtime_summarize_fixture(&parity_fixture) run_runtime_summarize_fixture(&parity_fixture)
.expect("save-slice-backed parity fixture should summarize"); .expect("save-slice-backed parity fixture should summarize");
@ -4496,6 +4499,8 @@ mod tests {
.expect("overlay-backed locomotive cost fixture should summarize"); .expect("overlay-backed locomotive cost fixture should summarize");
run_runtime_summarize_fixture(&scalar_band_parity_fixture) run_runtime_summarize_fixture(&scalar_band_parity_fixture)
.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)
.expect("save-slice-backed executable world-scalar fixture should summarize");
} }
#[test] #[test]

View file

@ -186,6 +186,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}, },
@ -359,6 +360,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}, },

View file

@ -58,6 +58,8 @@ pub struct ExpectedRuntimeSummary {
#[serde(default)] #[serde(default)]
pub world_restore_economic_status_code: Option<i32>, pub world_restore_economic_status_code: Option<i32>,
#[serde(default)] #[serde(default)]
pub world_restore_territory_access_cost: Option<u32>,
#[serde(default)]
pub world_restore_absolute_counter_restore_kind: Option<String>, pub world_restore_absolute_counter_restore_kind: Option<String>,
#[serde(default)] #[serde(default)]
pub world_restore_absolute_counter_adjustment_context: Option<String>, pub world_restore_absolute_counter_adjustment_context: Option<String>,
@ -160,6 +162,8 @@ pub struct ExpectedRuntimeSummary {
#[serde(default)] #[serde(default)]
pub named_locomotive_cost_count: Option<usize>, pub named_locomotive_cost_count: Option<usize>,
#[serde(default)] #[serde(default)]
pub cargo_production_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>,
@ -373,6 +377,14 @@ impl ExpectedRuntimeSummary {
)); ));
} }
} }
if let Some(value) = self.world_restore_territory_access_cost {
if actual.world_restore_territory_access_cost != Some(value) {
mismatches.push(format!(
"world_restore_territory_access_cost mismatch: expected {value}, got {:?}",
actual.world_restore_territory_access_cost
));
}
}
if let Some(kind) = &self.world_restore_absolute_counter_restore_kind { if let Some(kind) = &self.world_restore_absolute_counter_restore_kind {
if actual.world_restore_absolute_counter_restore_kind.as_ref() != Some(kind) { if actual.world_restore_absolute_counter_restore_kind.as_ref() != Some(kind) {
mismatches.push(format!( mismatches.push(format!(
@ -785,6 +797,14 @@ impl ExpectedRuntimeSummary {
)); ));
} }
} }
if let Some(count) = self.cargo_production_override_count {
if actual.cargo_production_override_count != count {
mismatches.push(format!(
"cargo_production_override_count mismatch: expected {count}, got {}",
actual.cargo_production_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

@ -96,6 +96,7 @@ struct SaveSliceProjection {
candidate_availability: BTreeMap<String, u32>, candidate_availability: BTreeMap<String, u32>,
named_locomotive_availability: BTreeMap<String, u32>, named_locomotive_availability: BTreeMap<String, u32>,
named_locomotive_cost: BTreeMap<String, u32>, named_locomotive_cost: BTreeMap<String, u32>,
cargo_production_overrides: BTreeMap<u32, u32>,
special_conditions: BTreeMap<String, u32>, special_conditions: BTreeMap<String, u32>,
} }
@ -251,6 +252,7 @@ pub fn project_save_slice_to_runtime_state_import(
candidate_availability: projection.candidate_availability, candidate_availability: projection.candidate_availability,
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,
special_conditions: projection.special_conditions, special_conditions: projection.special_conditions,
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -293,7 +295,10 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
calendar: base_state.calendar, calendar: base_state.calendar,
world_flags, world_flags,
save_profile: projection.save_profile, save_profile: projection.save_profile,
world_restore: projection.world_restore, world_restore: RuntimeWorldRestoreState {
territory_access_cost: base_state.world_restore.territory_access_cost,
..projection.world_restore
},
metadata, metadata,
companies: base_state.companies.clone(), companies: base_state.companies.clone(),
selected_company_id: base_state.selected_company_id, selected_company_id: base_state.selected_company_id,
@ -311,6 +316,7 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
candidate_availability: projection.candidate_availability, candidate_availability: projection.candidate_availability,
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(),
special_conditions: projection.special_conditions, special_conditions: projection.special_conditions,
service_state: base_state.service_state.clone(), service_state: base_state.service_state.clone(),
}; };
@ -554,6 +560,7 @@ fn project_save_slice_components(
ai_ignore_territories_at_startup_enabled: special_condition_enabled(34), ai_ignore_territories_at_startup_enabled: special_condition_enabled(34),
limited_track_building_amount: None, limited_track_building_amount: None,
economic_status_code: None, economic_status_code: None,
territory_access_cost: None,
absolute_counter_restore_kind: Some( absolute_counter_restore_kind: Some(
"mode-adjusted-selected-year-lane".to_string(), "mode-adjusted-selected-year-lane".to_string(),
), ),
@ -640,6 +647,7 @@ fn project_save_slice_components(
} }
let named_locomotive_cost = BTreeMap::new(); let named_locomotive_cost = BTreeMap::new();
let cargo_production_overrides = BTreeMap::new();
for (index, note) in save_slice.notes.iter().enumerate() { for (index, note) in save_slice.notes.iter().enumerate() {
metadata.insert(format!("save_slice.note.{index}"), note.clone()); metadata.insert(format!("save_slice.note.{index}"), note.clone());
@ -655,6 +663,7 @@ fn project_save_slice_components(
candidate_availability, candidate_availability,
named_locomotive_availability, named_locomotive_availability,
named_locomotive_cost, named_locomotive_cost,
cargo_production_overrides,
special_conditions, special_conditions,
}) })
} }
@ -978,6 +987,14 @@ fn lower_contextual_real_grouped_effects(
let mut effects = Vec::with_capacity(record.grouped_effect_rows.len()); let mut effects = Vec::with_capacity(record.grouped_effect_rows.len());
for row in &record.grouped_effect_rows { for row in &record.grouped_effect_rows {
if let Some(effect) = lower_contextual_cargo_production_effect(row)? {
effects.push(effect);
continue;
}
if let Some(effect) = lower_contextual_territory_access_cost_effect(row)? {
effects.push(effect);
continue;
}
if let Some(effect) = lower_contextual_locomotive_cost_effect(row, company_context)? { if let Some(effect) = lower_contextual_locomotive_cost_effect(row, company_context)? {
effects.push(effect); effects.push(effect);
continue; continue;
@ -1011,10 +1028,8 @@ fn lower_contextual_locomotive_availability_effect(
if row.row_shape != "scalar_assignment" { if row.row_shape != "scalar_assignment" {
return Ok(None); return Ok(None);
} }
let value = match row.raw_scalar_value { let Some(value) = u32::try_from(row.raw_scalar_value).ok() else {
0 => false, return Ok(None);
1 => true,
_ => return Ok(None),
}; };
let Some(locomotive_id) = row.recovered_locomotive_id else { let Some(locomotive_id) = row.recovered_locomotive_id else {
return Ok(None); return Ok(None);
@ -1026,12 +1041,48 @@ fn lower_contextual_locomotive_availability_effect(
else { else {
return Err(ImportBlocker::MissingLocomotiveCatalogContext); return Err(ImportBlocker::MissingLocomotiveCatalogContext);
}; };
Ok(Some(RuntimeEffect::SetNamedLocomotiveAvailability { Ok(Some(RuntimeEffect::SetNamedLocomotiveAvailabilityValue {
name, name,
value, value,
})) }))
} }
fn lower_contextual_cargo_production_effect(
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
) -> Result<Option<RuntimeEffect>, ImportBlocker> {
if row.parameter_family.as_deref() != Some("cargo_production_scalar") {
return Ok(None);
}
if row.row_shape != "scalar_assignment" {
return Ok(None);
}
let Some(value) = u32::try_from(row.raw_scalar_value).ok() else {
return Ok(None);
};
let Some(slot) = row.descriptor_id.checked_sub(229) else {
return Ok(None);
};
if !(1..=11).contains(&slot) {
return Ok(None);
}
Ok(Some(RuntimeEffect::SetCargoProductionSlot { slot, value }))
}
fn lower_contextual_territory_access_cost_effect(
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
) -> Result<Option<RuntimeEffect>, ImportBlocker> {
if row.parameter_family.as_deref() != Some("territory_access_cost_scalar") {
return Ok(None);
}
if row.row_shape != "scalar_assignment" {
return Ok(None);
}
let Some(value) = u32::try_from(row.raw_scalar_value).ok() else {
return Ok(None);
};
Ok(Some(RuntimeEffect::SetTerritoryAccessCost { value }))
}
fn lower_contextual_locomotive_cost_effect( fn lower_contextual_locomotive_cost_effect(
row: &SmpLoadedPackedEventGroupedEffectRowSummary, row: &SmpLoadedPackedEventGroupedEffectRowSummary,
company_context: &ImportRuntimeContext, company_context: &ImportRuntimeContext,
@ -1288,12 +1339,27 @@ fn lower_condition_targets_in_effect(
value: *value, value: *value,
} }
} }
RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name, value } => {
RuntimeEffect::SetNamedLocomotiveAvailabilityValue {
name: name.clone(),
value: *value,
}
}
RuntimeEffect::SetNamedLocomotiveCost { name, value } => { RuntimeEffect::SetNamedLocomotiveCost { name, value } => {
RuntimeEffect::SetNamedLocomotiveCost { RuntimeEffect::SetNamedLocomotiveCost {
name: name.clone(), name: name.clone(),
value: *value, value: *value,
} }
} }
RuntimeEffect::SetCargoProductionSlot { slot, value } => {
RuntimeEffect::SetCargoProductionSlot {
slot: *slot,
value: *value,
}
}
RuntimeEffect::SetTerritoryAccessCost { value } => {
RuntimeEffect::SetTerritoryAccessCost { value: *value }
}
RuntimeEffect::SetSpecialCondition { label, value } => RuntimeEffect::SetSpecialCondition { RuntimeEffect::SetSpecialCondition { label, value } => RuntimeEffect::SetSpecialCondition {
label: label.clone(), label: label.clone(),
value: *value, value: *value,
@ -1729,12 +1795,27 @@ fn smp_runtime_effect_to_runtime_effect(
value: *value, value: *value,
}) })
} }
RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name, value } => {
Ok(RuntimeEffect::SetNamedLocomotiveAvailabilityValue {
name: name.clone(),
value: *value,
})
}
RuntimeEffect::SetNamedLocomotiveCost { name, value } => { RuntimeEffect::SetNamedLocomotiveCost { name, value } => {
Ok(RuntimeEffect::SetNamedLocomotiveCost { Ok(RuntimeEffect::SetNamedLocomotiveCost {
name: name.clone(), name: name.clone(),
value: *value, value: *value,
}) })
} }
RuntimeEffect::SetCargoProductionSlot { slot, value } => {
Ok(RuntimeEffect::SetCargoProductionSlot {
slot: *slot,
value: *value,
})
}
RuntimeEffect::SetTerritoryAccessCost { value } => {
Ok(RuntimeEffect::SetTerritoryAccessCost { value: *value })
}
RuntimeEffect::SetSpecialCondition { label, value } => { RuntimeEffect::SetSpecialCondition { label, value } => {
Ok(RuntimeEffect::SetSpecialCondition { Ok(RuntimeEffect::SetSpecialCondition {
label: label.clone(), label: label.clone(),
@ -2296,7 +2377,10 @@ fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool {
| RuntimeEffect::DeactivatePlayer { .. } | RuntimeEffect::DeactivatePlayer { .. }
| RuntimeEffect::SetCandidateAvailability { .. } | RuntimeEffect::SetCandidateAvailability { .. }
| RuntimeEffect::SetNamedLocomotiveAvailability { .. } | RuntimeEffect::SetNamedLocomotiveAvailability { .. }
| RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. }
| RuntimeEffect::SetNamedLocomotiveCost { .. } | RuntimeEffect::SetNamedLocomotiveCost { .. }
| RuntimeEffect::SetCargoProductionSlot { .. }
| RuntimeEffect::SetTerritoryAccessCost { .. }
| RuntimeEffect::SetSpecialCondition { .. } | RuntimeEffect::SetSpecialCondition { .. }
| RuntimeEffect::ActivateEventRecord { .. } | RuntimeEffect::ActivateEventRecord { .. }
| RuntimeEffect::DeactivateEventRecord { .. } | RuntimeEffect::DeactivateEventRecord { .. }
@ -2375,7 +2459,10 @@ fn runtime_effect_company_target_import_blocker(
| RuntimeEffect::SetEconomicStatusCode { .. } | RuntimeEffect::SetEconomicStatusCode { .. }
| RuntimeEffect::SetCandidateAvailability { .. } | RuntimeEffect::SetCandidateAvailability { .. }
| RuntimeEffect::SetNamedLocomotiveAvailability { .. } | RuntimeEffect::SetNamedLocomotiveAvailability { .. }
| RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. }
| RuntimeEffect::SetNamedLocomotiveCost { .. } | RuntimeEffect::SetNamedLocomotiveCost { .. }
| RuntimeEffect::SetCargoProductionSlot { .. }
| RuntimeEffect::SetTerritoryAccessCost { .. }
| RuntimeEffect::SetSpecialCondition { .. } | RuntimeEffect::SetSpecialCondition { .. }
| RuntimeEffect::ActivateEventRecord { .. } | RuntimeEffect::ActivateEventRecord { .. }
| RuntimeEffect::DeactivateEventRecord { .. } | RuntimeEffect::DeactivateEventRecord { .. }
@ -2717,6 +2804,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
} }
@ -4882,7 +4970,7 @@ mod tests {
} }
#[test] #[test]
fn leaves_recovered_locomotive_availability_rows_blocked_unmapped_world_descriptor() { fn blocks_scalar_locomotive_availability_rows_without_catalog_context() {
let save_slice = SmpLoadedSaveSlice { let save_slice = SmpLoadedSaveSlice {
file_extension_hint: Some("gms".to_string()), file_extension_hint: Some("gms".to_string()),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
@ -4953,7 +5041,7 @@ mod tests {
executable_import_ready: false, executable_import_ready: false,
notes: vec![ notes: vec![
"decoded from grounded real 0x4e9a row framing".to_string(), "decoded from grounded real 0x4e9a row framing".to_string(),
"recovered locomotive availability descriptor family remains parity-only" "scalar locomotive availability rows still need catalog context"
.to_string(), .to_string(),
], ],
}], }],
@ -4975,7 +5063,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_missing_locomotive_catalog_context")
); );
} }
@ -5056,7 +5144,7 @@ mod tests {
} }
#[test] #[test]
fn overlays_boolean_locomotive_availability_rows_into_named_availability_effects() { fn overlays_scalar_locomotive_availability_rows_into_named_availability_effects() {
let base_state = RuntimeState { let base_state = RuntimeState {
calendar: CalendarPoint { calendar: CalendarPoint {
year: 1845, year: 1845,
@ -5094,6 +5182,7 @@ mod tests {
("Locomotive 112".to_string(), 1), ("Locomotive 112".to_string(), 1),
]), ]),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -5141,14 +5230,14 @@ mod tests {
negative_sentinel_scope: None, negative_sentinel_scope: None,
grouped_effect_row_counts: vec![2, 0, 0, 0], grouped_effect_row_counts: vec![2, 0, 0, 0],
grouped_effect_rows: vec![ grouped_effect_rows: vec![
real_locomotive_availability_row(250, 1), real_locomotive_availability_row(250, 42),
real_locomotive_availability_row(457, 0), real_locomotive_availability_row(457, 7),
], ],
decoded_conditions: Vec::new(), decoded_conditions: Vec::new(),
decoded_actions: vec![], decoded_actions: vec![],
executable_import_ready: false, executable_import_ready: false,
notes: vec![ notes: vec![
"boolean locomotive availability rows use overlay catalog context" "scalar locomotive availability rows use overlay catalog context"
.to_string(), .to_string(),
], ],
}], }],
@ -5185,14 +5274,14 @@ mod tests {
.state .state
.named_locomotive_availability .named_locomotive_availability
.get("Locomotive 10"), .get("Locomotive 10"),
Some(&1) Some(&42)
); );
assert_eq!( assert_eq!(
import import
.state .state
.named_locomotive_availability .named_locomotive_availability
.get("Locomotive 112"), .get("Locomotive 112"),
Some(&0) Some(&7)
); );
} }
@ -5385,6 +5474,7 @@ mod tests {
("Locomotive 1".to_string(), 100000), ("Locomotive 1".to_string(), 100000),
("Locomotive 101".to_string(), 200000), ("Locomotive 101".to_string(), 200000),
]), ]),
cargo_production_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -5554,7 +5644,7 @@ mod tests {
} }
#[test] #[test]
fn keeps_recovered_cargo_production_rows_parity_only() { fn imports_recovered_cargo_production_rows_into_runtime_records() {
let save_slice = SmpLoadedSaveSlice { let save_slice = SmpLoadedSaveSlice {
file_extension_hint: Some("gms".to_string()), file_extension_hint: Some("gms".to_string()),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
@ -5602,32 +5692,42 @@ mod tests {
decoded_conditions: Vec::new(), decoded_conditions: Vec::new(),
decoded_actions: vec![], decoded_actions: vec![],
executable_import_ready: false, executable_import_ready: false,
notes: vec!["cargo production rows remain metadata-only".to_string()], notes: vec![
"cargo production rows now import through world overrides".to_string(),
],
}], }],
}), }),
notes: vec![], notes: vec![],
}; };
let import = project_save_slice_to_runtime_state_import( let mut import = project_save_slice_to_runtime_state_import(
&save_slice, &save_slice,
"packed-events-cargo-production-frontier", "packed-events-cargo-production-frontier",
None, None,
) )
.expect("save slice should project"); .expect("save slice should project");
assert!(import.state.event_runtime_records.is_empty()); assert_eq!(import.state.event_runtime_records.len(), 1);
assert_eq!( assert_eq!(
import import
.state .state
.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("imported")
); );
execute_step_command(
&mut import.state,
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
)
.expect("cargo production runtime record should run");
assert_eq!(import.state.cargo_production_overrides.get(&1), Some(&125));
} }
#[test] #[test]
fn keeps_recovered_territory_access_cost_rows_parity_only() { fn imports_recovered_territory_access_cost_rows_into_runtime_records() {
let save_slice = SmpLoadedSaveSlice { let save_slice = SmpLoadedSaveSlice {
file_extension_hint: Some("gms".to_string()), file_extension_hint: Some("gms".to_string()),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
@ -5675,27 +5775,40 @@ mod tests {
decoded_conditions: Vec::new(), decoded_conditions: Vec::new(),
decoded_actions: vec![], decoded_actions: vec![],
executable_import_ready: false, executable_import_ready: false,
notes: vec!["territory access cost rows remain metadata-only".to_string()], notes: vec![
"territory access cost rows now import through world restore".to_string(),
],
}], }],
}), }),
notes: vec![], notes: vec![],
}; };
let import = project_save_slice_to_runtime_state_import( let mut import = project_save_slice_to_runtime_state_import(
&save_slice, &save_slice,
"packed-events-territory-access-cost-frontier", "packed-events-territory-access-cost-frontier",
None, None,
) )
.expect("save slice should project"); .expect("save slice should project");
assert!(import.state.event_runtime_records.is_empty()); assert_eq!(import.state.event_runtime_records.len(), 1);
assert_eq!( assert_eq!(
import import
.state .state
.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("imported")
);
execute_step_command(
&mut import.state,
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
)
.expect("territory access cost runtime record should run");
assert_eq!(
import.state.world_restore.territory_access_cost,
Some(750000)
); );
} }
@ -5736,6 +5849,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -8596,6 +8710,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState { service_state: RuntimeServiceState {
periodic_boundary_calls: 9, periodic_boundary_calls: 9,
@ -8766,6 +8881,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}, },

View file

@ -106,6 +106,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}, },

View file

@ -317,10 +317,21 @@ pub enum RuntimeEffect {
name: String, name: String,
value: bool, value: bool,
}, },
SetNamedLocomotiveAvailabilityValue {
name: String,
value: u32,
},
SetNamedLocomotiveCost { SetNamedLocomotiveCost {
name: String, name: String,
value: u32, value: u32,
}, },
SetCargoProductionSlot {
slot: u32,
value: u32,
},
SetTerritoryAccessCost {
value: u32,
},
SetSpecialCondition { SetSpecialCondition {
label: String, label: String,
value: u32, value: u32,
@ -614,6 +625,8 @@ pub struct RuntimeWorldRestoreState {
#[serde(default)] #[serde(default)]
pub economic_status_code: Option<i32>, pub economic_status_code: Option<i32>,
#[serde(default)] #[serde(default)]
pub territory_access_cost: Option<u32>,
#[serde(default)]
pub absolute_counter_restore_kind: Option<String>, pub absolute_counter_restore_kind: Option<String>,
#[serde(default)] #[serde(default)]
pub absolute_counter_adjustment_context: Option<String>, pub absolute_counter_adjustment_context: Option<String>,
@ -659,6 +672,8 @@ pub struct RuntimeState {
#[serde(default)] #[serde(default)]
pub named_locomotive_cost: BTreeMap<String, u32>, pub named_locomotive_cost: BTreeMap<String, u32>,
#[serde(default)] #[serde(default)]
pub cargo_production_overrides: BTreeMap<u32, u32>,
#[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,
@ -1173,6 +1188,14 @@ impl RuntimeState {
return Err("named_locomotive_cost contains an empty key".to_string()); return Err("named_locomotive_cost contains an empty key".to_string());
} }
} }
for slot in self.cargo_production_overrides.keys() {
if !(1..=11).contains(slot) {
return Err(format!(
"cargo_production_overrides contains out-of-range slot {}",
slot
));
}
}
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());
@ -1249,11 +1272,22 @@ fn validate_runtime_effect(
return Err("name must not be empty".to_string()); return Err("name must not be empty".to_string());
} }
} }
RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name, .. } => {
if name.trim().is_empty() {
return Err("name must not be empty".to_string());
}
}
RuntimeEffect::SetNamedLocomotiveCost { name, .. } => { RuntimeEffect::SetNamedLocomotiveCost { name, .. } => {
if name.trim().is_empty() { if name.trim().is_empty() {
return Err("name must not be empty".to_string()); return Err("name must not be empty".to_string());
} }
} }
RuntimeEffect::SetCargoProductionSlot { slot, .. } => {
if !(1..=11).contains(slot) {
return Err("slot must be in 1..=11".to_string());
}
}
RuntimeEffect::SetTerritoryAccessCost { .. } => {}
RuntimeEffect::SetSpecialCondition { label, .. } => { RuntimeEffect::SetSpecialCondition { label, .. } => {
if label.trim().is_empty() { if label.trim().is_empty() {
return Err("label must not be empty".to_string()); return Err("label must not be empty".to_string());
@ -1476,6 +1510,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1512,6 +1547,7 @@ mod tests {
ai_ignore_territories_at_startup_enabled: Some(false), ai_ignore_territories_at_startup_enabled: Some(false),
limited_track_building_amount: None, limited_track_building_amount: None,
economic_status_code: None, economic_status_code: None,
territory_access_cost: None,
absolute_counter_restore_kind: Some( absolute_counter_restore_kind: Some(
"mode-adjusted-selected-year-lane".to_string(), "mode-adjusted-selected-year-lane".to_string(),
), ),
@ -1534,6 +1570,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1591,6 +1628,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1658,6 +1696,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1756,6 +1795,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1800,6 +1840,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1844,6 +1885,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1905,6 +1947,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1956,6 +1999,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2011,6 +2055,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2062,6 +2107,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2119,6 +2165,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2170,6 +2217,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -2221,6 +2269,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };

View file

@ -2838,7 +2838,7 @@ fn recovered_cargo_production_descriptor_metadata(
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "cargo_production_scalar", parameter_family: "cargo_production_scalar",
runtime_key: None, runtime_key: None,
executable_in_runtime: false, executable_in_runtime: true,
} }
}) })
} }
@ -2951,7 +2951,7 @@ fn recovered_territory_access_cost_descriptor_metadata(
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "territory_access_cost_scalar", parameter_family: "territory_access_cost_scalar",
runtime_key: None, runtime_key: None,
executable_in_runtime: false, executable_in_runtime: true,
}) })
} }
@ -3241,6 +3241,28 @@ fn decode_real_grouped_effect_action(
}); });
} }
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.parameter_family == "cargo_production_scalar"
&& row.row_shape == "scalar_assignment"
&& row.raw_scalar_value >= 0
{
let slot = descriptor_metadata.descriptor_id.checked_sub(229)?;
return Some(RuntimeEffect::SetCargoProductionSlot {
slot,
value: row.raw_scalar_value as u32,
});
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.parameter_family == "territory_access_cost_scalar"
&& row.row_shape == "scalar_assignment"
&& row.raw_scalar_value >= 0
{
return Some(RuntimeEffect::SetTerritoryAccessCost {
value: row.raw_scalar_value as u32,
});
}
if descriptor_metadata.executable_in_runtime if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.parameter_family == "world_flag_toggle" && descriptor_metadata.parameter_family == "world_flag_toggle"
&& row.row_shape == "bool_toggle" && row.row_shape == "bool_toggle"
@ -3489,7 +3511,10 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
| RuntimeEffect::SetEconomicStatusCode { .. } | RuntimeEffect::SetEconomicStatusCode { .. }
| RuntimeEffect::SetCandidateAvailability { .. } | RuntimeEffect::SetCandidateAvailability { .. }
| RuntimeEffect::SetNamedLocomotiveAvailability { .. } | RuntimeEffect::SetNamedLocomotiveAvailability { .. }
| RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. }
| RuntimeEffect::SetNamedLocomotiveCost { .. } | RuntimeEffect::SetNamedLocomotiveCost { .. }
| RuntimeEffect::SetCargoProductionSlot { .. }
| RuntimeEffect::SetTerritoryAccessCost { .. }
| RuntimeEffect::SetSpecialCondition { .. } | RuntimeEffect::SetSpecialCondition { .. }
| RuntimeEffect::ConfiscateCompanyAssets { .. } | RuntimeEffect::ConfiscateCompanyAssets { .. }
| RuntimeEffect::DeactivateCompany { .. } | RuntimeEffect::DeactivateCompany { .. }
@ -9345,7 +9370,7 @@ mod tests {
assert_eq!(metadata.target_mask_bits, 0x08); assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "cargo_production_scalar"); assert_eq!(metadata.parameter_family, "cargo_production_scalar");
assert_eq!(metadata.runtime_key, None); assert_eq!(metadata.runtime_key, None);
assert!(!metadata.executable_in_runtime); assert!(metadata.executable_in_runtime);
} }
#[test] #[test]
@ -9381,7 +9406,7 @@ mod tests {
assert_eq!(metadata.target_mask_bits, 0x08); assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "territory_access_cost_scalar"); assert_eq!(metadata.parameter_family, "territory_access_cost_scalar");
assert_eq!(metadata.runtime_key, None); assert_eq!(metadata.runtime_key, None);
assert!(!metadata.executable_in_runtime); assert!(metadata.executable_in_runtime);
} }
#[test] #[test]

View file

@ -495,9 +495,20 @@ fn apply_runtime_effects(
.named_locomotive_availability .named_locomotive_availability
.insert(name.clone(), u32::from(*value)); .insert(name.clone(), u32::from(*value));
} }
RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name, value } => {
state
.named_locomotive_availability
.insert(name.clone(), *value);
}
RuntimeEffect::SetNamedLocomotiveCost { name, value } => { RuntimeEffect::SetNamedLocomotiveCost { name, value } => {
state.named_locomotive_cost.insert(name.clone(), *value); state.named_locomotive_cost.insert(name.clone(), *value);
} }
RuntimeEffect::SetCargoProductionSlot { slot, value } => {
state.cargo_production_overrides.insert(*slot, *value);
}
RuntimeEffect::SetTerritoryAccessCost { value } => {
state.world_restore.territory_access_cost = Some(*value);
}
RuntimeEffect::SetSpecialCondition { label, value } => { RuntimeEffect::SetSpecialCondition { label, value } => {
state.special_conditions.insert(label.clone(), *value); state.special_conditions.insert(label.clone(), *value);
} }
@ -1169,6 +1180,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
} }
@ -1457,6 +1469,80 @@ mod tests {
assert_eq!(result.service_events[0].applied_effect_count, 2); assert_eq!(result.service_events[0].applied_effect_count, 2);
} }
#[test]
fn applies_scalar_named_locomotive_availability_effects() {
let mut state = RuntimeState {
event_runtime_records: vec![RuntimeEventRecord {
record_id: 12,
trigger_kind: 7,
active: true,
service_count: 0,
marks_collection_dirty: false,
one_shot: false,
has_fired: false,
conditions: Vec::new(),
effects: vec![
RuntimeEffect::SetNamedLocomotiveAvailabilityValue {
name: "Big Boy".to_string(),
value: 42,
},
RuntimeEffect::SetNamedLocomotiveAvailabilityValue {
name: "GP7".to_string(),
value: 7,
},
],
}],
..state()
};
let result = execute_step_command(
&mut state,
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
)
.expect("scalar named locomotive availability effects should succeed");
assert_eq!(
state.named_locomotive_availability.get("Big Boy"),
Some(&42)
);
assert_eq!(state.named_locomotive_availability.get("GP7"), Some(&7));
assert_eq!(result.service_events[0].applied_effect_count, 2);
}
#[test]
fn applies_world_scalar_override_effects() {
let mut state = RuntimeState {
event_runtime_records: vec![RuntimeEventRecord {
record_id: 13,
trigger_kind: 7,
active: true,
service_count: 0,
marks_collection_dirty: false,
one_shot: false,
has_fired: false,
conditions: Vec::new(),
effects: vec![
RuntimeEffect::SetCargoProductionSlot {
slot: 1,
value: 125,
},
RuntimeEffect::SetTerritoryAccessCost { value: 750000 },
],
}],
..state()
};
let result = execute_step_command(
&mut state,
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
)
.expect("world scalar override effects should succeed");
assert_eq!(state.cargo_production_overrides.get(&1), Some(&125));
assert_eq!(state.world_restore.territory_access_cost, Some(750000));
assert_eq!(result.service_events[0].applied_effect_count, 2);
}
#[test] #[test]
fn resolves_symbolic_company_targets() { fn resolves_symbolic_company_targets() {
let mut state = RuntimeState { let mut state = RuntimeState {

View file

@ -26,6 +26,7 @@ pub struct RuntimeSummary {
pub world_restore_ai_ignore_territories_at_startup_enabled: Option<bool>, pub world_restore_ai_ignore_territories_at_startup_enabled: Option<bool>,
pub world_restore_limited_track_building_amount: Option<i32>, pub world_restore_limited_track_building_amount: Option<i32>,
pub world_restore_economic_status_code: Option<i32>, pub world_restore_economic_status_code: Option<i32>,
pub world_restore_territory_access_cost: Option<u32>,
pub world_restore_absolute_counter_restore_kind: Option<String>, pub world_restore_absolute_counter_restore_kind: Option<String>,
pub world_restore_absolute_counter_adjustment_context: Option<String>, pub world_restore_absolute_counter_adjustment_context: Option<String>,
pub metadata_count: usize, pub metadata_count: usize,
@ -77,6 +78,7 @@ pub struct RuntimeSummary {
pub named_locomotive_availability_count: usize, pub named_locomotive_availability_count: usize,
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 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>,
@ -149,6 +151,7 @@ impl RuntimeSummary {
.world_restore .world_restore
.limited_track_building_amount, .limited_track_building_amount,
world_restore_economic_status_code: state.world_restore.economic_status_code, world_restore_economic_status_code: state.world_restore.economic_status_code,
world_restore_territory_access_cost: state.world_restore.territory_access_cost,
world_restore_absolute_counter_restore_kind: state world_restore_absolute_counter_restore_kind: state
.world_restore .world_restore
.absolute_counter_restore_kind .absolute_counter_restore_kind
@ -597,6 +600,7 @@ impl RuntimeSummary {
.filter(|value| **value == 0) .filter(|value| **value == 0)
.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(),
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
@ -820,6 +824,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -920,6 +925,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -969,6 +975,7 @@ mod tests {
("Mikado".to_string(), 0), ("Mikado".to_string(), 0),
]), ]),
named_locomotive_cost: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1010,6 +1017,7 @@ mod tests {
("Big Boy".to_string(), 250000), ("Big Boy".to_string(), 250000),
("GP7".to_string(), 175000), ("GP7".to_string(), 175000),
]), ]),
cargo_production_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1019,6 +1027,47 @@ mod tests {
assert_eq!(summary.named_locomotive_cost_count, 2); assert_eq!(summary.named_locomotive_cost_count, 2);
} }
#[test]
fn counts_world_scalar_override_surfaces() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState {
territory_access_cost: Some(750000),
..RuntimeWorldRestoreState::default()
},
metadata: BTreeMap::new(),
companies: Vec::new(),
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::from([(1, 125), (2, 250)]),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
let summary = RuntimeSummary::from_state(&state);
assert_eq!(summary.cargo_production_override_count, 2);
assert_eq!(summary.world_restore_territory_access_cost, Some(750000));
}
#[test] #[test]
fn counts_world_frontier_buckets_separately() { fn counts_world_frontier_buckets_separately() {
let state = RuntimeState { let state = RuntimeState {
@ -1110,6 +1159,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };
@ -1189,6 +1239,7 @@ mod tests {
candidate_availability: BTreeMap::new(), candidate_availability: BTreeMap::new(),
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(),
special_conditions: BTreeMap::new(), special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(), service_state: RuntimeServiceState::default(),
}; };

View file

@ -126,16 +126,18 @@ The highest-value next passes are now:
- the next recovered locomotives-page descriptor batch is partially executable too: - the next recovered locomotives-page descriptor batch is partially executable too:
descriptors `454..456` (`All Steam/Diesel/Electric Locos Avail.`) now lower through checked-in descriptors `454..456` (`All Steam/Diesel/Electric Locos Avail.`) now lower through checked-in
metadata into keyed `world_flags`, while the wider locomotive availability/cost scalar bands now metadata into keyed `world_flags`, while the wider locomotive availability/cost scalar bands now
split cleanly between executable boolean availability rows and recovered metadata-rich parity split cleanly between executable scalar availability/cost rows and the remaining world-side
rows for the remaining cargo-production, locomotive-cost, and territory-access-cost families scalar families
- the runtime now also carries both the save-owned named locomotive availability table and an - the runtime now also carries both the save-owned named locomotive availability table and an
overlay-backed locomotive catalog context: checked-in save-slice documents can populate overlay-backed locomotive catalog context: checked-in save-slice documents can populate
`RuntimeState.named_locomotive_availability`, and boolean `0/1` availability descriptors can `RuntimeState.named_locomotive_availability`, and recovered scalar availability descriptors can
lower through `RuntimeState.locomotive_catalog` into the same ordinary event-service path lower through `RuntimeState.locomotive_catalog` into the same ordinary event-service path
- that same overlay-backed locomotive catalog now unlocks the recovered locomotive-cost bands too: - that same overlay-backed locomotive catalog now unlocks the recovered locomotive-cost bands too:
nonnegative scalar rows from descriptors `352..451` and `475..500` can lower into the new nonnegative scalar rows from descriptors `352..451` and `475..500` can lower into the new
event-owned `RuntimeState.named_locomotive_cost` map through the ordinary runtime path, while event-owned `RuntimeState.named_locomotive_cost` map through the ordinary runtime path
cargo-production and territory-access-cost rows remain metadata-rich parity-only families - cargo-production `230..240` and territory-access-cost `453` now execute too through minimal
world-side scalar landing surfaces: slot-indexed `cargo_production_overrides` and
`world_restore.territory_access_cost`
- keep in mind that the current local `.gms` corpus still exports with no packed event collection, - keep in mind that the current local `.gms` corpus still exports with no packed event collection,
so real descriptor mapping needs to stay plumbing-first until better captures exist so real descriptor mapping needs to stay plumbing-first until better captures exist
- use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution - use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution

View file

@ -86,21 +86,22 @@ Implemented today:
save-slice documents can carry the persisted `[world+0x66b6]` name table into save-slice documents can carry the persisted `[world+0x66b6]` name table into
`RuntimeState.named_locomotive_availability`, and imported runtime effects can mutate that map `RuntimeState.named_locomotive_availability`, and imported runtime effects can mutate that map
through the ordinary event-service path without requiring Trainbuy or live locomotive-pool parity through the ordinary event-service path without requiring Trainbuy or live locomotive-pool parity
- the boolean `0/1` subset of the recovered locomotives-page availability bands can now import - the recovered locomotives-page availability bands can now import as full scalar overrides
through an overlay-backed `RuntimeState.locomotive_catalog`; non-boolean availability payloads through an overlay-backed `RuntimeState.locomotive_catalog` into
still remain parity-only, but the adjacent locomotive-cost bands `352..451` and `475..500` now `RuntimeState.named_locomotive_availability`; save-slice-only imports of those rows now fail on
import too through the same overlay-backed catalog into the event-owned the explicit `blocked_missing_locomotive_catalog_context` frontier rather than a generic
`RuntimeState.named_locomotive_cost` map when their scalar payloads are nonnegative unmapped-world bucket
- cargo-production `230..240` and territory-access-cost `453` rows now remain as the primary - the adjacent locomotive-cost bands `352..451` and `475..500` now import too through the same
recovered scalar-band parity families: their labels, target masks, and slot identities are overlay-backed catalog into the event-owned `RuntimeState.named_locomotive_cost` map when their
checked in, but execution for those families is still deferred until a grounded landing surface scalar payloads are nonnegative
exists - the remaining recovered scalar world families now execute as well: cargo-production `230..240`
rows lower into slot-indexed `cargo_production_overrides`, and territory-access-cost descriptor
`453` lowers into `world_restore.territory_access_cost`
That means the next implementation work is breadth, not bootstrap. The recommended next slice is That means the next implementation work is breadth, not bootstrap. The recommended next slice is
honest landing surfaces for one or more of the remaining recovered scalar families, plus broader broader real grouped-descriptor and ordinary condition-id coverage beyond the current access,
real grouped-descriptor and ordinary condition-id coverage beyond the current access, whole-game whole-game toggle, train, player, numeric-threshold, named locomotive availability, named
toggle, train, player, numeric-threshold, named locomotive availability, and named locomotive cost locomotive cost, and world scalar override batches.
batches.
Richer runtime ownership should still be added only where a later descriptor or condition family Richer runtime ownership should still be added only where a later descriptor or condition family
needs more than the current event-owned roster. needs more than the current event-owned roster.

View file

@ -3,7 +3,7 @@
"fixture_id": "packed-event-locomotive-availability-missing-catalog-save-slice-fixture", "fixture_id": "packed-event-locomotive-availability-missing-catalog-save-slice-fixture",
"source": { "source": {
"kind": "captured-runtime", "kind": "captured-runtime",
"description": "Fixture backed by a tracked save-slice document that leaves a boolean locomotive availability row blocked until overlay-backed catalog context is supplied." "description": "Fixture backed by a tracked save-slice document that leaves a scalar locomotive availability row blocked until overlay-backed catalog context is supplied."
}, },
"state_save_slice_path": "packed-event-locomotive-availability-missing-catalog-save-slice.json", "state_save_slice_path": "packed-event-locomotive-availability-missing-catalog-save-slice.json",
"commands": [ "commands": [
@ -42,7 +42,7 @@
{ {
"descriptor_id": 250, "descriptor_id": 250,
"recovered_locomotive_id": 10, "recovered_locomotive_id": 10,
"semantic_preview": "Set Unknown Loco Available to 1" "semantic_preview": "Set Unknown Loco Available to 42"
} }
] ]
} }

View file

@ -2,12 +2,12 @@
"format_version": 1, "format_version": 1,
"save_slice_id": "packed-event-locomotive-availability-missing-catalog-save-slice", "save_slice_id": "packed-event-locomotive-availability-missing-catalog-save-slice",
"source": { "source": {
"description": "Tracked save-slice document proving boolean locomotive availability rows stay parity-only without overlay-backed locomotive catalog context.", "description": "Tracked save-slice document proving scalar locomotive availability rows stay parity-only without overlay-backed locomotive catalog context.",
"original_save_filename": "captured-locomotive-availability-missing-catalog.gms", "original_save_filename": "captured-locomotive-availability-missing-catalog.gms",
"original_save_sha256": "locomotive-availability-missing-catalog-sample-sha256", "original_save_sha256": "locomotive-availability-missing-catalog-sample-sha256",
"notes": [ "notes": [
"tracked as JSON save-slice document rather than raw .smp", "tracked as JSON save-slice document rather than raw .smp",
"locks the explicit missing locomotive catalog frontier for boolean availability rows" "locks the explicit missing locomotive catalog frontier for scalar availability rows"
] ]
}, },
"save_slice": { "save_slice": {
@ -71,7 +71,7 @@
"target_mask_bits": 8, "target_mask_bits": 8,
"parameter_family": "locomotive_availability_scalar", "parameter_family": "locomotive_availability_scalar",
"opcode": 3, "opcode": 3,
"raw_scalar_value": 1, "raw_scalar_value": 42,
"value_byte_0x09": 0, "value_byte_0x09": 0,
"value_dword_0x0d": 0, "value_dword_0x0d": 0,
"value_byte_0x11": 0, "value_byte_0x11": 0,
@ -80,7 +80,7 @@
"value_word_0x16": 0, "value_word_0x16": 0,
"row_shape": "scalar_assignment", "row_shape": "scalar_assignment",
"semantic_family": "scalar_assignment", "semantic_family": "scalar_assignment",
"semantic_preview": "Set Unknown Loco Available to 1", "semantic_preview": "Set Unknown Loco Available to 42",
"recovered_locomotive_id": 10, "recovered_locomotive_id": 10,
"locomotive_name": null, "locomotive_name": null,
"notes": [] "notes": []
@ -89,7 +89,7 @@
"decoded_actions": [], "decoded_actions": [],
"executable_import_ready": false, "executable_import_ready": false,
"notes": [ "notes": [
"boolean locomotive availability row still requires overlay-backed locomotive catalog context" "scalar locomotive availability row still requires overlay-backed locomotive catalog context"
] ]
} }
] ]

View file

@ -3,7 +3,7 @@
"fixture_id": "packed-event-locomotive-availability-overlay-fixture", "fixture_id": "packed-event-locomotive-availability-overlay-fixture",
"source": { "source": {
"kind": "captured-runtime", "kind": "captured-runtime",
"description": "Fixture backed by an overlay import document so boolean locomotive availability descriptors execute against captured catalog context." "description": "Fixture backed by an overlay import document so scalar locomotive availability descriptors execute against captured catalog context."
}, },
"state_import_path": "packed-event-locomotive-availability-overlay.json", "state_import_path": "packed-event-locomotive-availability-overlay.json",
"commands": [ "commands": [
@ -28,7 +28,7 @@
"packed_event_parity_only_record_count": 1, "packed_event_parity_only_record_count": 1,
"event_runtime_record_count": 1, "event_runtime_record_count": 1,
"named_locomotive_availability_count": 2, "named_locomotive_availability_count": 2,
"zero_named_locomotive_availability_count": 1, "zero_named_locomotive_availability_count": 0,
"total_event_record_service_count": 1, "total_event_record_service_count": 1,
"total_trigger_dispatch_count": 1 "total_trigger_dispatch_count": 1
}, },
@ -38,8 +38,8 @@
"save_slice.import_projection": "overlay-runtime-restore-v1" "save_slice.import_projection": "overlay-runtime-restore-v1"
}, },
"named_locomotive_availability": { "named_locomotive_availability": {
"Locomotive 10": 1, "Locomotive 10": 42,
"Locomotive 112": 0 "Locomotive 112": 7
}, },
"packed_event_collection": { "packed_event_collection": {
"live_entry_ids": [33], "live_entry_ids": [33],
@ -67,14 +67,14 @@
"service_count": 1, "service_count": 1,
"effects": [ "effects": [
{ {
"kind": "set_named_locomotive_availability", "kind": "set_named_locomotive_availability_value",
"name": "Locomotive 10", "name": "Locomotive 10",
"value": true "value": 42
}, },
{ {
"kind": "set_named_locomotive_availability", "kind": "set_named_locomotive_availability_value",
"name": "Locomotive 112", "name": "Locomotive 112",
"value": false "value": 7
} }
] ]
} }

View file

@ -2,12 +2,12 @@
"format_version": 1, "format_version": 1,
"save_slice_id": "packed-event-locomotive-availability-overlay-save-slice", "save_slice_id": "packed-event-locomotive-availability-overlay-save-slice",
"source": { "source": {
"description": "Tracked save-slice document proving boolean locomotive availability descriptors can import through overlay-backed catalog context.", "description": "Tracked save-slice document proving scalar locomotive availability descriptors can import through overlay-backed catalog context.",
"original_save_filename": "captured-locomotive-availability-overlay.gms", "original_save_filename": "captured-locomotive-availability-overlay.gms",
"original_save_sha256": "locomotive-availability-overlay-sample-sha256", "original_save_sha256": "locomotive-availability-overlay-sample-sha256",
"notes": [ "notes": [
"tracked as JSON save-slice document rather than raw .smp", "tracked as JSON save-slice document rather than raw .smp",
"uses synthetic catalog names to prove descriptor-to-id lowering plus overlay-backed resolution" "uses synthetic catalog names to prove descriptor-to-id lowering plus overlay-backed scalar resolution"
] ]
}, },
"save_slice": { "save_slice": {
@ -71,7 +71,7 @@
"target_mask_bits": 8, "target_mask_bits": 8,
"parameter_family": "locomotive_availability_scalar", "parameter_family": "locomotive_availability_scalar",
"opcode": 3, "opcode": 3,
"raw_scalar_value": 1, "raw_scalar_value": 42,
"value_byte_0x09": 0, "value_byte_0x09": 0,
"value_dword_0x0d": 0, "value_dword_0x0d": 0,
"value_byte_0x11": 0, "value_byte_0x11": 0,
@ -80,7 +80,7 @@
"value_word_0x16": 0, "value_word_0x16": 0,
"row_shape": "scalar_assignment", "row_shape": "scalar_assignment",
"semantic_family": "scalar_assignment", "semantic_family": "scalar_assignment",
"semantic_preview": "Set Unknown Loco Available to 1", "semantic_preview": "Set Unknown Loco Available to 42",
"recovered_locomotive_id": 10, "recovered_locomotive_id": 10,
"locomotive_name": null, "locomotive_name": null,
"notes": [] "notes": []
@ -93,7 +93,7 @@
"target_mask_bits": 8, "target_mask_bits": 8,
"parameter_family": "locomotive_availability_scalar", "parameter_family": "locomotive_availability_scalar",
"opcode": 3, "opcode": 3,
"raw_scalar_value": 0, "raw_scalar_value": 7,
"value_byte_0x09": 0, "value_byte_0x09": 0,
"value_dword_0x0d": 0, "value_dword_0x0d": 0,
"value_byte_0x11": 0, "value_byte_0x11": 0,
@ -102,7 +102,7 @@
"value_word_0x16": 0, "value_word_0x16": 0,
"row_shape": "scalar_assignment", "row_shape": "scalar_assignment",
"semantic_family": "scalar_assignment", "semantic_family": "scalar_assignment",
"semantic_preview": "Set Unknown Loco Available to 0", "semantic_preview": "Set Unknown Loco Available to 7",
"recovered_locomotive_id": 112, "recovered_locomotive_id": 112,
"locomotive_name": null, "locomotive_name": null,
"notes": [] "notes": []
@ -111,7 +111,7 @@
"decoded_actions": [], "decoded_actions": [],
"executable_import_ready": false, "executable_import_ready": false,
"notes": [ "notes": [
"boolean locomotive availability rows use overlay-backed catalog context" "scalar locomotive availability rows use overlay-backed catalog context"
] ]
} }
] ]

View file

@ -2,7 +2,7 @@
"format_version": 1, "format_version": 1,
"import_id": "packed-event-locomotive-availability-overlay", "import_id": "packed-event-locomotive-availability-overlay",
"source": { "source": {
"description": "Overlay import that combines a captured base snapshot with boolean locomotive availability descriptors.", "description": "Overlay import that combines a captured base snapshot with scalar locomotive availability descriptors.",
"notes": [ "notes": [
"used to upgrade descriptor-driven named locomotive availability rows through overlay-backed catalog context" "used to upgrade descriptor-driven named locomotive availability rows through overlay-backed catalog context"
] ]

View file

@ -26,11 +26,12 @@
"packed_event_imported_runtime_record_count": 1, "packed_event_imported_runtime_record_count": 1,
"packed_event_parity_only_record_count": 2, "packed_event_parity_only_record_count": 2,
"packed_event_unsupported_record_count": 0, "packed_event_unsupported_record_count": 0,
"packed_event_blocked_missing_locomotive_catalog_context_count": 1,
"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_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": 1, "event_runtime_record_count": 1,
"total_company_cash": 0 "total_company_cash": 0
@ -48,7 +49,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_missing_locomotive_catalog_context",
"grouped_effect_rows": [ "grouped_effect_rows": [
{ {
"descriptor_id": 250, "descriptor_id": 250,

View file

@ -7,7 +7,7 @@
"original_save_sha256": "parity-sample-sha256", "original_save_sha256": "parity-sample-sha256",
"notes": [ "notes": [
"tracked as JSON save-slice document rather than raw .smp", "tracked as JSON save-slice document rather than raw .smp",
"preserves one recovered-but-unmapped locomotive availability row and one semantically decoded-but-parity-only row" "preserves one recovered scalar locomotive-availability row that still needs overlay-backed catalog context and one semantically decoded imported row"
] ]
}, },
"save_slice": { "save_slice": {
@ -83,7 +83,7 @@
"recovered_locomotive_id": 10, "recovered_locomotive_id": 10,
"locomotive_name": null, "locomotive_name": null,
"notes": [ "notes": [
"recovered locomotive availability descriptor family remains parity-only until the scalar payload is in the grounded boolean subset" "recovered locomotive availability descriptor family now supports scalar payloads, but standalone save-slice import still needs overlay-backed locomotive catalog context"
] ]
} }
], ],
@ -91,7 +91,7 @@
"executable_import_ready": false, "executable_import_ready": false,
"notes": [ "notes": [
"decoded from grounded real 0x4e9a row framing", "decoded from grounded real 0x4e9a row framing",
"recovered locomotives-page descriptor band is now checked in, but this scalar family still needs overlay-backed locomotive catalog context and a grounded boolean scalar payload" "recovered locomotives-page descriptor band is now checked in, and this scalar family can import through named locomotive availability overrides once overlay-backed locomotive catalog context is present"
] ]
}, },
{ {

View file

@ -3,7 +3,7 @@
"fixture_id": "packed-event-world-scalar-band-parity-save-slice-fixture", "fixture_id": "packed-event-world-scalar-band-parity-save-slice-fixture",
"source": { "source": {
"kind": "captured-runtime", "kind": "captured-runtime",
"description": "Fixture backed by a tracked save-slice document that pins recovered scalar-band world descriptors as parity-only." "description": "Fixture backed by a tracked save-slice document that mixes executable scalar world descriptors with one remaining missing-catalog frontier."
}, },
"state_save_slice_path": "packed-event-world-scalar-band-parity-save-slice.json", "state_save_slice_path": "packed-event-world-scalar-band-parity-save-slice.json",
"commands": [ "commands": [
@ -23,7 +23,7 @@
"packed_event_collection_present": true, "packed_event_collection_present": true,
"packed_event_record_count": 3, "packed_event_record_count": 3,
"packed_event_decoded_record_count": 3, "packed_event_decoded_record_count": 3,
"packed_event_imported_runtime_record_count": 0, "packed_event_imported_runtime_record_count": 2,
"packed_event_parity_only_record_count": 3, "packed_event_parity_only_record_count": 3,
"packed_event_unsupported_record_count": 0, "packed_event_unsupported_record_count": 0,
"packed_event_blocked_missing_locomotive_catalog_context_count": 1, "packed_event_blocked_missing_locomotive_catalog_context_count": 1,
@ -31,9 +31,10 @@
"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_unmapped_real_descriptor_count": 0, "packed_event_blocked_unmapped_real_descriptor_count": 0,
"packed_event_blocked_unmapped_world_descriptor_count": 2, "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": 0, "event_runtime_record_count": 2,
"cargo_production_override_count": 0,
"total_company_cash": 0 "total_company_cash": 0
}, },
"expected_state_fragment": { "expected_state_fragment": {
@ -46,7 +47,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": "imported",
"grouped_effect_rows": [ "grouped_effect_rows": [
{ {
"descriptor_id": 230, "descriptor_id": 230,
@ -79,7 +80,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": "imported",
"grouped_effect_rows": [ "grouped_effect_rows": [
{ {
"descriptor_id": 453, "descriptor_id": 453,
@ -93,6 +94,27 @@
] ]
} }
] ]
},
"event_runtime_records": [
{
"record_id": 11,
"effects": [
{
"kind": "set_cargo_production_slot",
"slot": 1,
"value": 125
} }
]
},
{
"record_id": 13,
"effects": [
{
"kind": "set_territory_access_cost",
"value": 750000
}
]
}
]
} }
} }

View file

@ -2,12 +2,12 @@
"format_version": 1, "format_version": 1,
"save_slice_id": "packed-event-world-scalar-band-parity-save-slice", "save_slice_id": "packed-event-world-scalar-band-parity-save-slice",
"source": { "source": {
"description": "Tracked save-slice document pinning recovered locomotives-page scalar families that are still parity-only.", "description": "Tracked save-slice document pinning recovered locomotives-page scalar families across executable and missing-context boundaries.",
"original_save_filename": "captured-world-scalar-band-parity.gms", "original_save_filename": "captured-world-scalar-band-parity.gms",
"original_save_sha256": "world-scalar-band-parity-sample-sha256", "original_save_sha256": "world-scalar-band-parity-sample-sha256",
"notes": [ "notes": [
"tracked as JSON save-slice document rather than raw .smp", "tracked as JSON save-slice document rather than raw .smp",
"covers recovered cargo production, locomotive cost, and territory access cost families without claiming executable runtime semantics" "covers recovered cargo production, locomotive cost, and territory access cost families with one remaining missing-catalog frontier"
] ]
}, },
"save_slice": { "save_slice": {
@ -89,7 +89,7 @@
"decoded_actions": [], "decoded_actions": [],
"executable_import_ready": false, "executable_import_ready": false,
"notes": [ "notes": [
"recovered cargo production metadata is now checked in, but no runtime landing surface exists yet" "recovered cargo production metadata now imports through world overrides"
] ]
}, },
{ {
@ -201,7 +201,7 @@
"decoded_actions": [], "decoded_actions": [],
"executable_import_ready": false, "executable_import_ready": false,
"notes": [ "notes": [
"recovered territory access cost metadata is now checked in, but execution is still deferred" "recovered territory access cost metadata now imports through world restore"
] ]
} }
] ]

View file

@ -0,0 +1,71 @@
{
"format_version": 1,
"fixture_id": "packed-event-world-scalar-executable-save-slice-fixture",
"source": {
"kind": "captured-runtime",
"description": "Fixture backed by a tracked save-slice document proving recovered cargo-production and territory-access-cost rows execute through world scalar runtime surfaces."
},
"state_save_slice_path": "packed-event-world-scalar-executable-save-slice.json",
"commands": [
{
"kind": "service_trigger_kind",
"trigger_kind": 7
}
],
"expected_summary": {
"calendar": {
"year": 1830,
"month_slot": 0,
"phase_slot": 0,
"tick_slot": 0
},
"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,
"packed_event_parity_only_record_count": 1,
"packed_event_blocked_unmapped_world_descriptor_count": 0,
"event_runtime_record_count": 1,
"cargo_production_override_count": 1,
"world_restore_territory_access_cost": 750000,
"total_event_record_service_count": 1,
"total_trigger_dispatch_count": 1
},
"expected_state_fragment": {
"metadata": {
"save_slice.import_projection": "partial-runtime-restore-v1"
},
"cargo_production_overrides": {
"1": 125
},
"world_restore": {
"territory_access_cost": 750000
},
"packed_event_collection": {
"live_entry_ids": [41],
"records": [
{
"import_outcome": "imported"
}
]
},
"event_runtime_records": [
{
"record_id": 41,
"service_count": 1,
"effects": [
{
"kind": "set_cargo_production_slot",
"slot": 1,
"value": 125
},
{
"kind": "set_territory_access_cost",
"value": 750000
}
]
}
]
}
}

View file

@ -0,0 +1,123 @@
{
"format_version": 1,
"save_slice_id": "packed-event-world-scalar-executable-save-slice",
"source": {
"description": "Tracked save-slice document proving recovered cargo-production and territory-access-cost rows import through world scalar runtime surfaces.",
"original_save_filename": "captured-world-scalar-executable.gms",
"original_save_sha256": "world-scalar-executable-sample-sha256",
"notes": [
"tracked as JSON save-slice document rather than raw .smp",
"uses a save-slice-backed proof path because cargo-production and territory-access-cost do not require overlay-backed runtime context"
]
},
"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,
"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": 30976,
"records_tag_offset": 31232,
"close_tag_offset": 31744,
"packed_state_version": 1001,
"packed_state_version_hex": "0x000003e9",
"live_id_bound": 41,
"live_record_count": 1,
"live_entry_ids": [41],
"decoded_record_count": 1,
"imported_runtime_record_count": 0,
"records": [
{
"record_index": 0,
"live_entry_id": 41,
"payload_offset": 31264,
"payload_len": 112,
"decode_status": "parity_only",
"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": [2, 0, 0, 0],
"grouped_effect_rows": [
{
"group_index": 0,
"row_index": 0,
"descriptor_id": 230,
"descriptor_label": "Cargo Production Slot 1",
"target_mask_bits": 8,
"parameter_family": "cargo_production_scalar",
"opcode": 3,
"raw_scalar_value": 125,
"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 Cargo Production Slot 1 to 125",
"recovered_locomotive_id": null,
"locomotive_name": null,
"notes": []
},
{
"group_index": 0,
"row_index": 1,
"descriptor_id": 453,
"descriptor_label": "Territory Access Cost",
"target_mask_bits": 8,
"parameter_family": "territory_access_cost_scalar",
"opcode": 3,
"raw_scalar_value": 750000,
"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 Access Cost to 750000",
"recovered_locomotive_id": null,
"locomotive_name": null,
"notes": []
}
],
"decoded_actions": [],
"executable_import_ready": false,
"notes": [
"world scalar override record imports without overlay-backed runtime context"
]
}
]
},
"notes": [
"world scalar executable sample"
]
}
}