diff --git a/README.md b/README.md index 3c21ee8..755e0d4 100644 --- a/README.md +++ b/README.md @@ -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 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 -scalar bands are now split more cleanly: the boolean `0/1` availability subset can import through -an overlay-backed `RuntimeState.locomotive_catalog` into -`RuntimeState.named_locomotive_availability`, while non-boolean availability payloads still remain -parity-only. The runtime still carries the save-owned named locomotive availability table directly -too: +scalar bands are now split more cleanly: the recovered locomotive availability bands can import as +full scalar overrides through an overlay-backed `RuntimeState.locomotive_catalog` into +`RuntimeState.named_locomotive_availability`, while save-slice-only imports of those rows still +block explicitly when catalog context is missing. The runtime still carries the save-owned named +locomotive availability table directly too: 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 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 import through the same overlay-backed locomotive catalog into -`RuntimeState.named_locomotive_cost`, while cargo-production and territory-access-cost rows remain -metadata-rich parity-only families until a later slice grounds an honest landing surface. Explicit -unmapped world-condition and world-descriptor frontier buckets still remain where current -checked-in metadata stops. Shell purchase-flow, Trainbuy refresh, cached locomotive-rating -recomputation, and 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. +`RuntimeState.named_locomotive_cost`, and the remaining recovered scalar world families now execute +too: cargo-production slots `230..240` lower into `cargo_production_overrides`, and descriptor +`453` lowers into `world_restore.territory_access_cost`. Explicit unmapped world-condition and +world-descriptor frontier buckets still remain where current checked-in metadata stops, with the +main scalar residue now being missing-catalog locomotive rows rather than unknown world-side +families. Shell purchase-flow, Trainbuy refresh, cached locomotive-rating recomputation, and +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 diff --git a/crates/rrt-cli/src/main.rs b/crates/rrt-cli/src/main.rs index 21a5b59..e91f81e 100644 --- a/crates/rrt-cli/src/main.rs +++ b/crates/rrt-cli/src/main.rs @@ -4468,6 +4468,9 @@ mod tests { let scalar_band_parity_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join( "../../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) .expect("save-slice-backed parity fixture should summarize"); @@ -4496,6 +4499,8 @@ mod tests { .expect("overlay-backed locomotive cost fixture should summarize"); run_runtime_summarize_fixture(&scalar_band_parity_fixture) .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] diff --git a/crates/rrt-fixtures/src/load.rs b/crates/rrt-fixtures/src/load.rs index 08cc132..ad4020f 100644 --- a/crates/rrt-fixtures/src/load.rs +++ b/crates/rrt-fixtures/src/load.rs @@ -186,6 +186,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }, @@ -359,6 +360,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }, diff --git a/crates/rrt-fixtures/src/schema.rs b/crates/rrt-fixtures/src/schema.rs index 4d8c978..a29ce67 100644 --- a/crates/rrt-fixtures/src/schema.rs +++ b/crates/rrt-fixtures/src/schema.rs @@ -58,6 +58,8 @@ pub struct ExpectedRuntimeSummary { #[serde(default)] pub world_restore_economic_status_code: Option, #[serde(default)] + pub world_restore_territory_access_cost: Option, + #[serde(default)] pub world_restore_absolute_counter_restore_kind: Option, #[serde(default)] pub world_restore_absolute_counter_adjustment_context: Option, @@ -160,6 +162,8 @@ pub struct ExpectedRuntimeSummary { #[serde(default)] pub named_locomotive_cost_count: Option, #[serde(default)] + pub cargo_production_override_count: Option, + #[serde(default)] pub special_condition_count: Option, #[serde(default)] pub enabled_special_condition_count: Option, @@ -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 actual.world_restore_absolute_counter_restore_kind.as_ref() != Some(kind) { 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 actual.special_condition_count != count { mismatches.push(format!( diff --git a/crates/rrt-runtime/src/import.rs b/crates/rrt-runtime/src/import.rs index c5faf2f..1f5385f 100644 --- a/crates/rrt-runtime/src/import.rs +++ b/crates/rrt-runtime/src/import.rs @@ -96,6 +96,7 @@ struct SaveSliceProjection { candidate_availability: BTreeMap, named_locomotive_availability: BTreeMap, named_locomotive_cost: BTreeMap, + cargo_production_overrides: BTreeMap, special_conditions: BTreeMap, } @@ -251,6 +252,7 @@ pub fn project_save_slice_to_runtime_state_import( candidate_availability: projection.candidate_availability, named_locomotive_availability: projection.named_locomotive_availability, named_locomotive_cost: projection.named_locomotive_cost, + cargo_production_overrides: projection.cargo_production_overrides, special_conditions: projection.special_conditions, service_state: RuntimeServiceState::default(), }; @@ -293,7 +295,10 @@ pub fn project_save_slice_overlay_to_runtime_state_import( calendar: base_state.calendar, world_flags, 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, companies: base_state.companies.clone(), 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, named_locomotive_availability: projection.named_locomotive_availability, named_locomotive_cost: base_state.named_locomotive_cost.clone(), + cargo_production_overrides: base_state.cargo_production_overrides.clone(), special_conditions: projection.special_conditions, 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), limited_track_building_amount: None, economic_status_code: None, + territory_access_cost: None, absolute_counter_restore_kind: Some( "mode-adjusted-selected-year-lane".to_string(), ), @@ -640,6 +647,7 @@ fn project_save_slice_components( } let named_locomotive_cost = BTreeMap::new(); + let cargo_production_overrides = BTreeMap::new(); for (index, note) in save_slice.notes.iter().enumerate() { metadata.insert(format!("save_slice.note.{index}"), note.clone()); @@ -655,6 +663,7 @@ fn project_save_slice_components( candidate_availability, named_locomotive_availability, named_locomotive_cost, + cargo_production_overrides, special_conditions, }) } @@ -978,6 +987,14 @@ fn lower_contextual_real_grouped_effects( let mut effects = Vec::with_capacity(record.grouped_effect_rows.len()); 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)? { effects.push(effect); continue; @@ -1011,10 +1028,8 @@ fn lower_contextual_locomotive_availability_effect( if row.row_shape != "scalar_assignment" { return Ok(None); } - let value = match row.raw_scalar_value { - 0 => false, - 1 => true, - _ => return Ok(None), + let Some(value) = u32::try_from(row.raw_scalar_value).ok() else { + return Ok(None); }; let Some(locomotive_id) = row.recovered_locomotive_id else { return Ok(None); @@ -1026,12 +1041,48 @@ fn lower_contextual_locomotive_availability_effect( else { return Err(ImportBlocker::MissingLocomotiveCatalogContext); }; - Ok(Some(RuntimeEffect::SetNamedLocomotiveAvailability { + Ok(Some(RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name, value, })) } +fn lower_contextual_cargo_production_effect( + row: &SmpLoadedPackedEventGroupedEffectRowSummary, +) -> Result, 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, 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( row: &SmpLoadedPackedEventGroupedEffectRowSummary, company_context: &ImportRuntimeContext, @@ -1288,12 +1339,27 @@ fn lower_condition_targets_in_effect( value: *value, } } + RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name, value } => { + RuntimeEffect::SetNamedLocomotiveAvailabilityValue { + name: name.clone(), + value: *value, + } + } RuntimeEffect::SetNamedLocomotiveCost { name, value } => { RuntimeEffect::SetNamedLocomotiveCost { name: name.clone(), 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 { label: label.clone(), value: *value, @@ -1729,12 +1795,27 @@ fn smp_runtime_effect_to_runtime_effect( value: *value, }) } + RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name, value } => { + Ok(RuntimeEffect::SetNamedLocomotiveAvailabilityValue { + name: name.clone(), + value: *value, + }) + } RuntimeEffect::SetNamedLocomotiveCost { name, value } => { Ok(RuntimeEffect::SetNamedLocomotiveCost { name: name.clone(), 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 } => { Ok(RuntimeEffect::SetSpecialCondition { label: label.clone(), @@ -2296,7 +2377,10 @@ fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool { | RuntimeEffect::DeactivatePlayer { .. } | RuntimeEffect::SetCandidateAvailability { .. } | RuntimeEffect::SetNamedLocomotiveAvailability { .. } + | RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. } | RuntimeEffect::SetNamedLocomotiveCost { .. } + | RuntimeEffect::SetCargoProductionSlot { .. } + | RuntimeEffect::SetTerritoryAccessCost { .. } | RuntimeEffect::SetSpecialCondition { .. } | RuntimeEffect::ActivateEventRecord { .. } | RuntimeEffect::DeactivateEventRecord { .. } @@ -2375,7 +2459,10 @@ fn runtime_effect_company_target_import_blocker( | RuntimeEffect::SetEconomicStatusCode { .. } | RuntimeEffect::SetCandidateAvailability { .. } | RuntimeEffect::SetNamedLocomotiveAvailability { .. } + | RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. } | RuntimeEffect::SetNamedLocomotiveCost { .. } + | RuntimeEffect::SetCargoProductionSlot { .. } + | RuntimeEffect::SetTerritoryAccessCost { .. } | RuntimeEffect::SetSpecialCondition { .. } | RuntimeEffect::ActivateEventRecord { .. } | RuntimeEffect::DeactivateEventRecord { .. } @@ -2717,6 +2804,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), } @@ -4882,7 +4970,7 @@ mod tests { } #[test] - fn leaves_recovered_locomotive_availability_rows_blocked_unmapped_world_descriptor() { + fn blocks_scalar_locomotive_availability_rows_without_catalog_context() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), @@ -4953,7 +5041,7 @@ mod tests { executable_import_ready: false, notes: vec![ "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(), ], }], @@ -4975,7 +5063,7 @@ mod tests { .packed_event_collection .as_ref() .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] - fn overlays_boolean_locomotive_availability_rows_into_named_availability_effects() { + fn overlays_scalar_locomotive_availability_rows_into_named_availability_effects() { let base_state = RuntimeState { calendar: CalendarPoint { year: 1845, @@ -5094,6 +5182,7 @@ mod tests { ("Locomotive 112".to_string(), 1), ]), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -5141,14 +5230,14 @@ mod tests { negative_sentinel_scope: None, grouped_effect_row_counts: vec![2, 0, 0, 0], grouped_effect_rows: vec![ - real_locomotive_availability_row(250, 1), - real_locomotive_availability_row(457, 0), + real_locomotive_availability_row(250, 42), + real_locomotive_availability_row(457, 7), ], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec![ - "boolean locomotive availability rows use overlay catalog context" + "scalar locomotive availability rows use overlay catalog context" .to_string(), ], }], @@ -5185,14 +5274,14 @@ mod tests { .state .named_locomotive_availability .get("Locomotive 10"), - Some(&1) + Some(&42) ); assert_eq!( import .state .named_locomotive_availability .get("Locomotive 112"), - Some(&0) + Some(&7) ); } @@ -5385,6 +5474,7 @@ mod tests { ("Locomotive 1".to_string(), 100000), ("Locomotive 101".to_string(), 200000), ]), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -5554,7 +5644,7 @@ mod tests { } #[test] - fn keeps_recovered_cargo_production_rows_parity_only() { + fn imports_recovered_cargo_production_rows_into_runtime_records() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), @@ -5602,32 +5692,42 @@ mod tests { decoded_conditions: Vec::new(), decoded_actions: vec![], 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![], }; - let import = project_save_slice_to_runtime_state_import( + let mut import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-cargo-production-frontier", None, ) .expect("save slice should project"); - assert!(import.state.event_runtime_records.is_empty()); + assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import .state .packed_event_collection .as_ref() .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] - fn keeps_recovered_territory_access_cost_rows_parity_only() { + fn imports_recovered_territory_access_cost_rows_into_runtime_records() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), @@ -5675,27 +5775,40 @@ mod tests { decoded_conditions: Vec::new(), decoded_actions: vec![], 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![], }; - let import = project_save_slice_to_runtime_state_import( + let mut import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-territory-access-cost-frontier", None, ) .expect("save slice should project"); - assert!(import.state.event_runtime_records.is_empty()); + assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import .state .packed_event_collection .as_ref() .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(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -8596,6 +8710,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState { periodic_boundary_calls: 9, @@ -8766,6 +8881,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }, diff --git a/crates/rrt-runtime/src/persistence.rs b/crates/rrt-runtime/src/persistence.rs index 7ce4e26..6d0b68e 100644 --- a/crates/rrt-runtime/src/persistence.rs +++ b/crates/rrt-runtime/src/persistence.rs @@ -106,6 +106,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }, diff --git a/crates/rrt-runtime/src/runtime.rs b/crates/rrt-runtime/src/runtime.rs index 4305182..b9d964f 100644 --- a/crates/rrt-runtime/src/runtime.rs +++ b/crates/rrt-runtime/src/runtime.rs @@ -317,10 +317,21 @@ pub enum RuntimeEffect { name: String, value: bool, }, + SetNamedLocomotiveAvailabilityValue { + name: String, + value: u32, + }, SetNamedLocomotiveCost { name: String, value: u32, }, + SetCargoProductionSlot { + slot: u32, + value: u32, + }, + SetTerritoryAccessCost { + value: u32, + }, SetSpecialCondition { label: String, value: u32, @@ -614,6 +625,8 @@ pub struct RuntimeWorldRestoreState { #[serde(default)] pub economic_status_code: Option, #[serde(default)] + pub territory_access_cost: Option, + #[serde(default)] pub absolute_counter_restore_kind: Option, #[serde(default)] pub absolute_counter_adjustment_context: Option, @@ -659,6 +672,8 @@ pub struct RuntimeState { #[serde(default)] pub named_locomotive_cost: BTreeMap, #[serde(default)] + pub cargo_production_overrides: BTreeMap, + #[serde(default)] pub special_conditions: BTreeMap, #[serde(default)] pub service_state: RuntimeServiceState, @@ -1173,6 +1188,14 @@ impl RuntimeState { 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() { if key.trim().is_empty() { 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()); } } + RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name, .. } => { + if name.trim().is_empty() { + return Err("name must not be empty".to_string()); + } + } RuntimeEffect::SetNamedLocomotiveCost { name, .. } => { if name.trim().is_empty() { 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, .. } => { if label.trim().is_empty() { return Err("label must not be empty".to_string()); @@ -1476,6 +1510,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1512,6 +1547,7 @@ mod tests { ai_ignore_territories_at_startup_enabled: Some(false), limited_track_building_amount: None, economic_status_code: None, + territory_access_cost: None, absolute_counter_restore_kind: Some( "mode-adjusted-selected-year-lane".to_string(), ), @@ -1534,6 +1570,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1591,6 +1628,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1658,6 +1696,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1756,6 +1795,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1800,6 +1840,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1844,6 +1885,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1905,6 +1947,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1956,6 +1999,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -2011,6 +2055,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -2062,6 +2107,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -2119,6 +2165,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -2170,6 +2217,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -2221,6 +2269,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; diff --git a/crates/rrt-runtime/src/smp.rs b/crates/rrt-runtime/src/smp.rs index e0f9d69..e088e01 100644 --- a/crates/rrt-runtime/src/smp.rs +++ b/crates/rrt-runtime/src/smp.rs @@ -2838,7 +2838,7 @@ fn recovered_cargo_production_descriptor_metadata( target_mask_bits: 0x08, parameter_family: "cargo_production_scalar", 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, parameter_family: "territory_access_cost_scalar", 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 && descriptor_metadata.parameter_family == "world_flag_toggle" && row.row_shape == "bool_toggle" @@ -3489,7 +3511,10 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool { | RuntimeEffect::SetEconomicStatusCode { .. } | RuntimeEffect::SetCandidateAvailability { .. } | RuntimeEffect::SetNamedLocomotiveAvailability { .. } + | RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. } | RuntimeEffect::SetNamedLocomotiveCost { .. } + | RuntimeEffect::SetCargoProductionSlot { .. } + | RuntimeEffect::SetTerritoryAccessCost { .. } | RuntimeEffect::SetSpecialCondition { .. } | RuntimeEffect::ConfiscateCompanyAssets { .. } | RuntimeEffect::DeactivateCompany { .. } @@ -9345,7 +9370,7 @@ mod tests { assert_eq!(metadata.target_mask_bits, 0x08); assert_eq!(metadata.parameter_family, "cargo_production_scalar"); assert_eq!(metadata.runtime_key, None); - assert!(!metadata.executable_in_runtime); + assert!(metadata.executable_in_runtime); } #[test] @@ -9381,7 +9406,7 @@ mod tests { assert_eq!(metadata.target_mask_bits, 0x08); assert_eq!(metadata.parameter_family, "territory_access_cost_scalar"); assert_eq!(metadata.runtime_key, None); - assert!(!metadata.executable_in_runtime); + assert!(metadata.executable_in_runtime); } #[test] diff --git a/crates/rrt-runtime/src/step.rs b/crates/rrt-runtime/src/step.rs index d95d6e5..d50b75c 100644 --- a/crates/rrt-runtime/src/step.rs +++ b/crates/rrt-runtime/src/step.rs @@ -495,9 +495,20 @@ fn apply_runtime_effects( .named_locomotive_availability .insert(name.clone(), u32::from(*value)); } + RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name, value } => { + state + .named_locomotive_availability + .insert(name.clone(), *value); + } RuntimeEffect::SetNamedLocomotiveCost { name, 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 } => { state.special_conditions.insert(label.clone(), *value); } @@ -1169,6 +1180,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), } @@ -1457,6 +1469,80 @@ mod tests { 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] fn resolves_symbolic_company_targets() { let mut state = RuntimeState { diff --git a/crates/rrt-runtime/src/summary.rs b/crates/rrt-runtime/src/summary.rs index 3121766..a4d1cc6 100644 --- a/crates/rrt-runtime/src/summary.rs +++ b/crates/rrt-runtime/src/summary.rs @@ -26,6 +26,7 @@ pub struct RuntimeSummary { pub world_restore_ai_ignore_territories_at_startup_enabled: Option, pub world_restore_limited_track_building_amount: Option, pub world_restore_economic_status_code: Option, + pub world_restore_territory_access_cost: Option, pub world_restore_absolute_counter_restore_kind: Option, pub world_restore_absolute_counter_adjustment_context: Option, pub metadata_count: usize, @@ -77,6 +78,7 @@ pub struct RuntimeSummary { pub named_locomotive_availability_count: usize, pub zero_named_locomotive_availability_count: usize, pub named_locomotive_cost_count: usize, + pub cargo_production_override_count: usize, pub special_condition_count: usize, pub enabled_special_condition_count: usize, pub save_profile_kind: Option, @@ -149,6 +151,7 @@ impl RuntimeSummary { .world_restore .limited_track_building_amount, 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 @@ -597,6 +600,7 @@ impl RuntimeSummary { .filter(|value| **value == 0) .count(), 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(), enabled_special_condition_count: state .special_conditions @@ -820,6 +824,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -920,6 +925,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -969,6 +975,7 @@ mod tests { ("Mikado".to_string(), 0), ]), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1010,6 +1017,7 @@ mod tests { ("Big Boy".to_string(), 250000), ("GP7".to_string(), 175000), ]), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1019,6 +1027,47 @@ mod tests { 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] fn counts_world_frontier_buckets_separately() { let state = RuntimeState { @@ -1110,6 +1159,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1189,6 +1239,7 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + cargo_production_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; diff --git a/docs/README.md b/docs/README.md index 3d5f8a3..01530fe 100644 --- a/docs/README.md +++ b/docs/README.md @@ -126,16 +126,18 @@ The highest-value next passes are now: - 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 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 - rows for the remaining cargo-production, locomotive-cost, and territory-access-cost families + split cleanly between executable scalar availability/cost rows and the remaining world-side + scalar families - 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 - `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 - 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 - event-owned `RuntimeState.named_locomotive_cost` map through the ordinary runtime path, while - cargo-production and territory-access-cost rows remain metadata-rich parity-only families + event-owned `RuntimeState.named_locomotive_cost` map through the ordinary runtime path +- 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, 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 diff --git a/docs/runtime-rehost-plan.md b/docs/runtime-rehost-plan.md index 21edd16..712abe4 100644 --- a/docs/runtime-rehost-plan.md +++ b/docs/runtime-rehost-plan.md @@ -86,21 +86,22 @@ Implemented today: save-slice documents can carry the persisted `[world+0x66b6]` name table into `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 -- the boolean `0/1` subset of the recovered locomotives-page availability bands can now import - through an overlay-backed `RuntimeState.locomotive_catalog`; non-boolean availability payloads - still remain parity-only, but the adjacent locomotive-cost bands `352..451` and `475..500` now - import too through the same overlay-backed catalog into the event-owned - `RuntimeState.named_locomotive_cost` map when their scalar payloads are nonnegative -- cargo-production `230..240` and territory-access-cost `453` rows now remain as the primary - recovered scalar-band parity families: their labels, target masks, and slot identities are - checked in, but execution for those families is still deferred until a grounded landing surface - exists +- the recovered locomotives-page availability bands can now import as full scalar overrides + through an overlay-backed `RuntimeState.locomotive_catalog` into + `RuntimeState.named_locomotive_availability`; save-slice-only imports of those rows now fail on + the explicit `blocked_missing_locomotive_catalog_context` frontier rather than a generic + unmapped-world bucket +- the adjacent locomotive-cost bands `352..451` and `475..500` now import too through the same + overlay-backed catalog into the event-owned `RuntimeState.named_locomotive_cost` map when their + scalar payloads are nonnegative +- 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 -honest landing surfaces for one or more of the remaining recovered scalar families, plus broader -real grouped-descriptor and ordinary condition-id coverage beyond the current access, whole-game -toggle, train, player, numeric-threshold, named locomotive availability, and named locomotive cost -batches. +broader real grouped-descriptor and ordinary condition-id coverage beyond the current access, +whole-game toggle, train, player, numeric-threshold, named locomotive availability, named +locomotive cost, and world scalar override batches. Richer runtime ownership should still be added only where a later descriptor or condition family needs more than the current event-owned roster. diff --git a/fixtures/runtime/packed-event-locomotive-availability-missing-catalog-save-slice-fixture.json b/fixtures/runtime/packed-event-locomotive-availability-missing-catalog-save-slice-fixture.json index 1512457..1c95445 100644 --- a/fixtures/runtime/packed-event-locomotive-availability-missing-catalog-save-slice-fixture.json +++ b/fixtures/runtime/packed-event-locomotive-availability-missing-catalog-save-slice-fixture.json @@ -3,7 +3,7 @@ "fixture_id": "packed-event-locomotive-availability-missing-catalog-save-slice-fixture", "source": { "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", "commands": [ @@ -42,7 +42,7 @@ { "descriptor_id": 250, "recovered_locomotive_id": 10, - "semantic_preview": "Set Unknown Loco Available to 1" + "semantic_preview": "Set Unknown Loco Available to 42" } ] } diff --git a/fixtures/runtime/packed-event-locomotive-availability-missing-catalog-save-slice.json b/fixtures/runtime/packed-event-locomotive-availability-missing-catalog-save-slice.json index 91cbfdd..313dd55 100644 --- a/fixtures/runtime/packed-event-locomotive-availability-missing-catalog-save-slice.json +++ b/fixtures/runtime/packed-event-locomotive-availability-missing-catalog-save-slice.json @@ -2,12 +2,12 @@ "format_version": 1, "save_slice_id": "packed-event-locomotive-availability-missing-catalog-save-slice", "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_sha256": "locomotive-availability-missing-catalog-sample-sha256", "notes": [ "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": { @@ -71,7 +71,7 @@ "target_mask_bits": 8, "parameter_family": "locomotive_availability_scalar", "opcode": 3, - "raw_scalar_value": 1, + "raw_scalar_value": 42, "value_byte_0x09": 0, "value_dword_0x0d": 0, "value_byte_0x11": 0, @@ -80,7 +80,7 @@ "value_word_0x16": 0, "row_shape": "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, "locomotive_name": null, "notes": [] @@ -89,7 +89,7 @@ "decoded_actions": [], "executable_import_ready": false, "notes": [ - "boolean locomotive availability row still requires overlay-backed locomotive catalog context" + "scalar locomotive availability row still requires overlay-backed locomotive catalog context" ] } ] diff --git a/fixtures/runtime/packed-event-locomotive-availability-overlay-fixture.json b/fixtures/runtime/packed-event-locomotive-availability-overlay-fixture.json index ac34090..839cf7c 100644 --- a/fixtures/runtime/packed-event-locomotive-availability-overlay-fixture.json +++ b/fixtures/runtime/packed-event-locomotive-availability-overlay-fixture.json @@ -3,7 +3,7 @@ "fixture_id": "packed-event-locomotive-availability-overlay-fixture", "source": { "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", "commands": [ @@ -28,7 +28,7 @@ "packed_event_parity_only_record_count": 1, "event_runtime_record_count": 1, "named_locomotive_availability_count": 2, - "zero_named_locomotive_availability_count": 1, + "zero_named_locomotive_availability_count": 0, "total_event_record_service_count": 1, "total_trigger_dispatch_count": 1 }, @@ -38,8 +38,8 @@ "save_slice.import_projection": "overlay-runtime-restore-v1" }, "named_locomotive_availability": { - "Locomotive 10": 1, - "Locomotive 112": 0 + "Locomotive 10": 42, + "Locomotive 112": 7 }, "packed_event_collection": { "live_entry_ids": [33], @@ -67,14 +67,14 @@ "service_count": 1, "effects": [ { - "kind": "set_named_locomotive_availability", + "kind": "set_named_locomotive_availability_value", "name": "Locomotive 10", - "value": true + "value": 42 }, { - "kind": "set_named_locomotive_availability", + "kind": "set_named_locomotive_availability_value", "name": "Locomotive 112", - "value": false + "value": 7 } ] } diff --git a/fixtures/runtime/packed-event-locomotive-availability-overlay-save-slice.json b/fixtures/runtime/packed-event-locomotive-availability-overlay-save-slice.json index e0e1222..c108da1 100644 --- a/fixtures/runtime/packed-event-locomotive-availability-overlay-save-slice.json +++ b/fixtures/runtime/packed-event-locomotive-availability-overlay-save-slice.json @@ -2,12 +2,12 @@ "format_version": 1, "save_slice_id": "packed-event-locomotive-availability-overlay-save-slice", "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_sha256": "locomotive-availability-overlay-sample-sha256", "notes": [ "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": { @@ -71,7 +71,7 @@ "target_mask_bits": 8, "parameter_family": "locomotive_availability_scalar", "opcode": 3, - "raw_scalar_value": 1, + "raw_scalar_value": 42, "value_byte_0x09": 0, "value_dword_0x0d": 0, "value_byte_0x11": 0, @@ -80,7 +80,7 @@ "value_word_0x16": 0, "row_shape": "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, "locomotive_name": null, "notes": [] @@ -93,7 +93,7 @@ "target_mask_bits": 8, "parameter_family": "locomotive_availability_scalar", "opcode": 3, - "raw_scalar_value": 0, + "raw_scalar_value": 7, "value_byte_0x09": 0, "value_dword_0x0d": 0, "value_byte_0x11": 0, @@ -102,7 +102,7 @@ "value_word_0x16": 0, "row_shape": "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, "locomotive_name": null, "notes": [] @@ -111,7 +111,7 @@ "decoded_actions": [], "executable_import_ready": false, "notes": [ - "boolean locomotive availability rows use overlay-backed catalog context" + "scalar locomotive availability rows use overlay-backed catalog context" ] } ] diff --git a/fixtures/runtime/packed-event-locomotive-availability-overlay.json b/fixtures/runtime/packed-event-locomotive-availability-overlay.json index 1ca9728..d09185c 100644 --- a/fixtures/runtime/packed-event-locomotive-availability-overlay.json +++ b/fixtures/runtime/packed-event-locomotive-availability-overlay.json @@ -2,7 +2,7 @@ "format_version": 1, "import_id": "packed-event-locomotive-availability-overlay", "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": [ "used to upgrade descriptor-driven named locomotive availability rows through overlay-backed catalog context" ] diff --git a/fixtures/runtime/packed-event-parity-save-slice-fixture.json b/fixtures/runtime/packed-event-parity-save-slice-fixture.json index fa82915..d4b684a 100644 --- a/fixtures/runtime/packed-event-parity-save-slice-fixture.json +++ b/fixtures/runtime/packed-event-parity-save-slice-fixture.json @@ -26,11 +26,12 @@ "packed_event_imported_runtime_record_count": 1, "packed_event_parity_only_record_count": 2, "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_territory_condition_scope_count": 0, "packed_event_blocked_missing_compact_control_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, "event_runtime_record_count": 1, "total_company_cash": 0 @@ -48,7 +49,7 @@ { "decode_status": "parity_only", "payload_family": "real_packed_v1", - "import_outcome": "blocked_unmapped_world_descriptor", + "import_outcome": "blocked_missing_locomotive_catalog_context", "grouped_effect_rows": [ { "descriptor_id": 250, diff --git a/fixtures/runtime/packed-event-parity-save-slice.json b/fixtures/runtime/packed-event-parity-save-slice.json index 9509abd..80c0413 100644 --- a/fixtures/runtime/packed-event-parity-save-slice.json +++ b/fixtures/runtime/packed-event-parity-save-slice.json @@ -7,7 +7,7 @@ "original_save_sha256": "parity-sample-sha256", "notes": [ "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": { @@ -83,7 +83,7 @@ "recovered_locomotive_id": 10, "locomotive_name": null, "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, "notes": [ "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" ] }, { diff --git a/fixtures/runtime/packed-event-world-scalar-band-parity-save-slice-fixture.json b/fixtures/runtime/packed-event-world-scalar-band-parity-save-slice-fixture.json index af49076..5c03e47 100644 --- a/fixtures/runtime/packed-event-world-scalar-band-parity-save-slice-fixture.json +++ b/fixtures/runtime/packed-event-world-scalar-band-parity-save-slice-fixture.json @@ -3,7 +3,7 @@ "fixture_id": "packed-event-world-scalar-band-parity-save-slice-fixture", "source": { "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", "commands": [ @@ -23,7 +23,7 @@ "packed_event_collection_present": true, "packed_event_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_unsupported_record_count": 0, "packed_event_blocked_missing_locomotive_catalog_context_count": 1, @@ -31,9 +31,10 @@ "packed_event_blocked_territory_condition_scope_count": 0, "packed_event_blocked_missing_compact_control_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, - "event_runtime_record_count": 0, + "event_runtime_record_count": 2, + "cargo_production_override_count": 0, "total_company_cash": 0 }, "expected_state_fragment": { @@ -46,7 +47,7 @@ { "decode_status": "parity_only", "payload_family": "real_packed_v1", - "import_outcome": "blocked_unmapped_world_descriptor", + "import_outcome": "imported", "grouped_effect_rows": [ { "descriptor_id": 230, @@ -79,7 +80,7 @@ { "decode_status": "parity_only", "payload_family": "real_packed_v1", - "import_outcome": "blocked_unmapped_world_descriptor", + "import_outcome": "imported", "grouped_effect_rows": [ { "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 + } + ] + } + ] } } diff --git a/fixtures/runtime/packed-event-world-scalar-band-parity-save-slice.json b/fixtures/runtime/packed-event-world-scalar-band-parity-save-slice.json index c0e6154..bb7b615 100644 --- a/fixtures/runtime/packed-event-world-scalar-band-parity-save-slice.json +++ b/fixtures/runtime/packed-event-world-scalar-band-parity-save-slice.json @@ -2,12 +2,12 @@ "format_version": 1, "save_slice_id": "packed-event-world-scalar-band-parity-save-slice", "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_sha256": "world-scalar-band-parity-sample-sha256", "notes": [ "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": { @@ -89,7 +89,7 @@ "decoded_actions": [], "executable_import_ready": false, "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": [], "executable_import_ready": false, "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" ] } ] diff --git a/fixtures/runtime/packed-event-world-scalar-executable-save-slice-fixture.json b/fixtures/runtime/packed-event-world-scalar-executable-save-slice-fixture.json new file mode 100644 index 0000000..24e3227 --- /dev/null +++ b/fixtures/runtime/packed-event-world-scalar-executable-save-slice-fixture.json @@ -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 + } + ] + } + ] + } +} diff --git a/fixtures/runtime/packed-event-world-scalar-executable-save-slice.json b/fixtures/runtime/packed-event-world-scalar-executable-save-slice.json new file mode 100644 index 0000000..314d57a --- /dev/null +++ b/fixtures/runtime/packed-event-world-scalar-executable-save-slice.json @@ -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" + ] + } +}