diff --git a/README.md b/README.md index 28398bd..7b3e3fb 100644 --- a/README.md +++ b/README.md @@ -51,11 +51,15 @@ 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 remain recovered-but-parity-only until per-locomotive identity is grounded. Explicit -unmapped world-condition and world-descriptor frontier buckets still remain where current -checked-in metadata stops. Shell purchase-flow 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. +scalar bands remain recovered-but-parity-only until per-locomotive identity is grounded. The +runtime now 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. Explicit unmapped world-condition and world-descriptor +frontier buckets still remain where current checked-in metadata stops. Shell purchase-flow 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 dfdd47a..c7f534e 100644 --- a/crates/rrt-cli/src/main.rs +++ b/crates/rrt-cli/src/main.rs @@ -4454,6 +4454,9 @@ mod tests { let mixed_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join( "../../fixtures/runtime/packed-event-mixed-company-descriptor-overlay-fixture.json", ); + let named_locomotive_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join( + "../../fixtures/runtime/packed-event-named-locomotive-availability-save-slice-fixture.json", + ); run_runtime_summarize_fixture(&parity_fixture) .expect("save-slice-backed parity fixture should summarize"); @@ -4471,6 +4474,8 @@ mod tests { .expect("overlay-backed track-capacity fixture should summarize"); run_runtime_summarize_fixture(&mixed_overlay_fixture) .expect("overlay-backed mixed real-row fixture should summarize"); + run_runtime_summarize_fixture(&named_locomotive_fixture) + .expect("save-slice-backed named locomotive availability fixture should summarize"); } #[test] @@ -4495,6 +4500,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: None, notes: vec!["exported for test".to_string()], diff --git a/crates/rrt-fixtures/src/load.rs b/crates/rrt-fixtures/src/load.rs index 80b1bf5..f7664ab 100644 --- a/crates/rrt-fixtures/src/load.rs +++ b/crates/rrt-fixtures/src/load.rs @@ -183,6 +183,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }, @@ -259,6 +260,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: None, notes: vec![], @@ -352,6 +354,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }, @@ -371,6 +374,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some( rrt_runtime::SmpLoadedEventRuntimeCollectionSummary { diff --git a/crates/rrt-fixtures/src/schema.rs b/crates/rrt-fixtures/src/schema.rs index 9c98557..2039e51 100644 --- a/crates/rrt-fixtures/src/schema.rs +++ b/crates/rrt-fixtures/src/schema.rs @@ -150,6 +150,10 @@ pub struct ExpectedRuntimeSummary { #[serde(default)] pub zero_candidate_availability_count: Option, #[serde(default)] + pub named_locomotive_availability_count: Option, + #[serde(default)] + pub zero_named_locomotive_availability_count: Option, + #[serde(default)] pub special_condition_count: Option, #[serde(default)] pub enabled_special_condition_count: Option, @@ -735,6 +739,22 @@ impl ExpectedRuntimeSummary { )); } } + if let Some(count) = self.named_locomotive_availability_count { + if actual.named_locomotive_availability_count != count { + mismatches.push(format!( + "named_locomotive_availability_count mismatch: expected {count}, got {}", + actual.named_locomotive_availability_count + )); + } + } + if let Some(count) = self.zero_named_locomotive_availability_count { + if actual.zero_named_locomotive_availability_count != count { + mismatches.push(format!( + "zero_named_locomotive_availability_count mismatch: expected {count}, got {}", + actual.zero_named_locomotive_availability_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 8b1efad..df29bbc 100644 --- a/crates/rrt-runtime/src/import.rs +++ b/crates/rrt-runtime/src/import.rs @@ -94,6 +94,7 @@ struct SaveSliceProjection { packed_event_collection: Option, event_runtime_records: Vec, candidate_availability: BTreeMap, + named_locomotive_availability: BTreeMap, special_conditions: BTreeMap, } @@ -238,6 +239,7 @@ pub fn project_save_slice_to_runtime_state_import( packed_event_collection: projection.packed_event_collection, event_runtime_records: projection.event_runtime_records, candidate_availability: projection.candidate_availability, + named_locomotive_availability: projection.named_locomotive_availability, special_conditions: projection.special_conditions, service_state: RuntimeServiceState::default(), }; @@ -295,6 +297,7 @@ pub fn project_save_slice_overlay_to_runtime_state_import( packed_event_collection: projection.packed_event_collection, event_runtime_records: projection.event_runtime_records, candidate_availability: projection.candidate_availability, + named_locomotive_availability: projection.named_locomotive_availability, special_conditions: projection.special_conditions, service_state: base_state.service_state.clone(), }; @@ -325,6 +328,10 @@ fn project_save_slice_components( "save_slice.special_conditions_present".to_string(), save_slice.special_conditions_table.is_some(), ); + world_flags.insert( + "save_slice.named_locomotive_availability_present".to_string(), + save_slice.named_locomotive_availability_table.is_some(), + ); world_flags.insert( "save_slice.event_runtime_collection_present".to_string(), save_slice.event_runtime_collection.is_some(), @@ -590,6 +597,35 @@ fn project_save_slice_components( } } + let mut named_locomotive_availability = BTreeMap::new(); + if let Some(table) = &save_slice.named_locomotive_availability_table { + metadata.insert( + "save_slice.named_locomotive_availability_source_kind".to_string(), + table.source_kind.clone(), + ); + metadata.insert( + "save_slice.named_locomotive_availability_semantic_family".to_string(), + table.semantic_family.clone(), + ); + metadata.insert( + "save_slice.named_locomotive_availability_entry_count".to_string(), + table.observed_entry_count.to_string(), + ); + metadata.insert( + "save_slice.named_locomotive_availability_zero_count".to_string(), + table.zero_availability_count.to_string(), + ); + if let Some(header_offset) = table.header_offset { + metadata.insert( + "save_slice.named_locomotive_availability_header_offset".to_string(), + header_offset.to_string(), + ); + } + for entry in &table.entries { + named_locomotive_availability.insert(entry.text.clone(), entry.availability_dword); + } + } + for (index, note) in save_slice.notes.iter().enumerate() { metadata.insert(format!("save_slice.note.{index}"), note.clone()); } @@ -602,6 +638,7 @@ fn project_save_slice_components( packed_event_collection, event_runtime_records, candidate_availability, + named_locomotive_availability, special_conditions, }) } @@ -1133,6 +1170,12 @@ fn lower_condition_targets_in_effect( value: *value, } } + RuntimeEffect::SetNamedLocomotiveAvailability { name, value } => { + RuntimeEffect::SetNamedLocomotiveAvailability { + name: name.clone(), + value: *value, + } + } RuntimeEffect::SetSpecialCondition { label, value } => RuntimeEffect::SetSpecialCondition { label: label.clone(), value: *value, @@ -1562,6 +1605,12 @@ fn smp_runtime_effect_to_runtime_effect( value: *value, }) } + RuntimeEffect::SetNamedLocomotiveAvailability { name, value } => { + Ok(RuntimeEffect::SetNamedLocomotiveAvailability { + name: name.clone(), + value: *value, + }) + } RuntimeEffect::SetSpecialCondition { label, value } => { Ok(RuntimeEffect::SetSpecialCondition { label: label.clone(), @@ -2111,6 +2160,7 @@ fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool { | RuntimeEffect::SetPlayerCash { .. } | RuntimeEffect::DeactivatePlayer { .. } | RuntimeEffect::SetCandidateAvailability { .. } + | RuntimeEffect::SetNamedLocomotiveAvailability { .. } | RuntimeEffect::SetSpecialCondition { .. } | RuntimeEffect::ActivateEventRecord { .. } | RuntimeEffect::DeactivateEventRecord { .. } @@ -2188,6 +2238,7 @@ fn runtime_effect_company_target_import_blocker( | RuntimeEffect::SetLimitedTrackBuildingAmount { .. } | RuntimeEffect::SetEconomicStatusCode { .. } | RuntimeEffect::SetCandidateAvailability { .. } + | RuntimeEffect::SetNamedLocomotiveAvailability { .. } | RuntimeEffect::SetSpecialCondition { .. } | RuntimeEffect::ActivateEventRecord { .. } | RuntimeEffect::DeactivateEventRecord { .. } @@ -2526,6 +2577,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), } @@ -3080,6 +3132,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: None, notes: vec![], @@ -3118,6 +3171,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: None, notes: vec![], @@ -3199,6 +3253,38 @@ mod tests { }, ], }), + named_locomotive_availability_table: Some( + crate::SmpLoadedNamedLocomotiveAvailabilityTable { + source_kind: "runtime-save-direct-serializer".to_string(), + semantic_family: "scenario-named-locomotive-availability-table".to_string(), + header_offset: None, + entries_offset: None, + entries_end_offset: None, + observed_entry_count: 2, + zero_availability_count: 1, + zero_availability_names: vec!["Big Boy".to_string()], + entries: vec![ + crate::SmpRt3105SaveNameTableEntry { + index: 0, + offset: 0, + text: "Big Boy".to_string(), + availability_dword: 0, + availability_dword_hex: "0x00000000".to_string(), + trailer_word: 0, + trailer_word_hex: "0x00000000".to_string(), + }, + crate::SmpRt3105SaveNameTableEntry { + index: 1, + offset: 0x41, + text: "GP7".to_string(), + availability_dword: 1, + availability_dword_hex: "0x00000001".to_string(), + trailer_word: 1, + trailer_word_hex: "0x00000001".to_string(), + }, + ], + }, + ), special_conditions_table: Some(crate::SmpLoadedSpecialConditionsTable { source_kind: "save-fixed-special-conditions-range".to_string(), table_offset: 0x0d64, @@ -3444,10 +3530,34 @@ mod tests { import.state.candidate_availability.get("Uranium Mine"), Some(&0) ); + assert_eq!( + import.state.named_locomotive_availability.get("Big Boy"), + Some(&0) + ); + assert_eq!( + import.state.named_locomotive_availability.get("GP7"), + Some(&1) + ); assert_eq!( import.state.special_conditions.get("Disable Cargo Economy"), Some(&0) ); + assert_eq!( + import + .state + .metadata + .get("save_slice.named_locomotive_availability_source_kind") + .map(String::as_str), + Some("runtime-save-direct-serializer") + ); + assert_eq!( + import + .state + .metadata + .get("save_slice.named_locomotive_availability_entry_count") + .map(String::as_str), + Some("2") + ); assert_eq!( import .state @@ -3485,6 +3595,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -3598,6 +3709,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -3689,6 +3801,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -3798,6 +3911,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -3880,6 +3994,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -4012,6 +4127,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -4252,6 +4368,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -4327,6 +4444,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -4424,6 +4542,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -4496,6 +4615,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -4615,6 +4735,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -4627,6 +4748,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -4769,6 +4891,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -4871,6 +4994,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -4954,6 +5078,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -5058,6 +5183,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -5162,6 +5288,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -5256,6 +5383,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -5346,6 +5474,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -5444,6 +5573,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -5533,6 +5663,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -5604,6 +5735,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -5680,6 +5812,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -5761,6 +5894,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -5842,6 +5976,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -5939,6 +6074,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -6027,6 +6163,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -6141,6 +6278,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -6264,6 +6402,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -6372,6 +6511,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -6543,6 +6683,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -6634,6 +6775,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -6721,6 +6863,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -6873,6 +7016,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -7014,6 +7158,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -7102,6 +7247,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -7217,6 +7363,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -7320,6 +7467,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -7443,6 +7591,7 @@ mod tests { effects: vec![], }], candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState { periodic_boundary_calls: 9, @@ -7460,6 +7609,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), @@ -7609,6 +7759,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }, @@ -7629,6 +7780,7 @@ mod tests { bridge_family: None, profile: None, candidate_availability_table: None, + named_locomotive_availability_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), diff --git a/crates/rrt-runtime/src/lib.rs b/crates/rrt-runtime/src/lib.rs index 77cc7c2..57678e3 100644 --- a/crates/rrt-runtime/src/lib.rs +++ b/crates/rrt-runtime/src/lib.rs @@ -53,10 +53,11 @@ pub use smp::{ SmpClassicRehydrateProfileProbe, SmpContainerProfile, SmpEarlyContentProbe, SmpHeaderVariantProbe, SmpInspectionReport, SmpKnownTagHit, SmpLoadedCandidateAvailabilityTable, SmpLoadedEventRuntimeCollectionSummary, - SmpLoadedPackedEventCompactControlSummary, SmpLoadedPackedEventConditionRowSummary, - SmpLoadedPackedEventGroupedEffectRowSummary, SmpLoadedPackedEventNegativeSentinelScopeSummary, - SmpLoadedPackedEventRecordSummary, SmpLoadedPackedEventTextBandSummary, SmpLoadedProfile, - SmpLoadedSaveSlice, SmpLoadedSpecialConditionsTable, SmpLocomotivePolicyFieldObservation, + SmpLoadedNamedLocomotiveAvailabilityTable, SmpLoadedPackedEventCompactControlSummary, + SmpLoadedPackedEventConditionRowSummary, SmpLoadedPackedEventGroupedEffectRowSummary, + SmpLoadedPackedEventNegativeSentinelScopeSummary, SmpLoadedPackedEventRecordSummary, + SmpLoadedPackedEventTextBandSummary, SmpLoadedProfile, SmpLoadedSaveSlice, + SmpLoadedSpecialConditionsTable, SmpLocomotivePolicyFieldObservation, SmpLocomotivePolicyFloatAlignmentCandidate, SmpLocomotivePolicyNeighborhoodProbe, SmpPackedProfileWordLane, SmpPostSpecialConditionsScalarLane, SmpPostSpecialConditionsScalarProbe, SmpPostTextFieldNeighborhoodProbe, diff --git a/crates/rrt-runtime/src/persistence.rs b/crates/rrt-runtime/src/persistence.rs index 3baea3b..c61ee0f 100644 --- a/crates/rrt-runtime/src/persistence.rs +++ b/crates/rrt-runtime/src/persistence.rs @@ -103,6 +103,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: 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 71cc2cc..238d31e 100644 --- a/crates/rrt-runtime/src/runtime.rs +++ b/crates/rrt-runtime/src/runtime.rs @@ -307,6 +307,10 @@ pub enum RuntimeEffect { name: String, value: u32, }, + SetNamedLocomotiveAvailability { + name: String, + value: bool, + }, SetSpecialCondition { label: String, value: u32, @@ -637,6 +641,8 @@ pub struct RuntimeState { #[serde(default)] pub candidate_availability: BTreeMap, #[serde(default)] + pub named_locomotive_availability: BTreeMap, + #[serde(default)] pub special_conditions: BTreeMap, #[serde(default)] pub service_state: RuntimeServiceState, @@ -1119,6 +1125,11 @@ impl RuntimeState { return Err("candidate_availability contains an empty key".to_string()); } } + for key in self.named_locomotive_availability.keys() { + if key.trim().is_empty() { + return Err("named_locomotive_availability contains an empty key".to_string()); + } + } for key in self.special_conditions.keys() { if key.trim().is_empty() { return Err("special_conditions contains an empty key".to_string()); @@ -1190,6 +1201,11 @@ fn validate_runtime_effect( return Err("name must not be empty".to_string()); } } + RuntimeEffect::SetNamedLocomotiveAvailability { name, .. } => { + if name.trim().is_empty() { + return Err("name must not be empty".to_string()); + } + } RuntimeEffect::SetSpecialCondition { label, .. } => { if label.trim().is_empty() { return Err("label must not be empty".to_string()); @@ -1409,6 +1425,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1464,6 +1481,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1518,6 +1536,7 @@ mod tests { }], }], candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1582,6 +1601,7 @@ mod tests { }], }], candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1677,6 +1697,7 @@ mod tests { }), event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1718,6 +1739,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1759,6 +1781,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1817,6 +1840,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1865,6 +1889,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1917,6 +1942,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -1965,6 +1991,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -2019,6 +2046,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -2067,6 +2095,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -2115,6 +2144,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: 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 dbf65c9..f6a6001 100644 --- a/crates/rrt-runtime/src/smp.rs +++ b/crates/rrt-runtime/src/smp.rs @@ -1503,6 +1503,22 @@ pub struct SmpLoadedCandidateAvailabilityTable { pub entries: Vec, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SmpLoadedNamedLocomotiveAvailabilityTable { + pub source_kind: String, + pub semantic_family: String, + #[serde(default)] + pub header_offset: Option, + #[serde(default)] + pub entries_offset: Option, + #[serde(default)] + pub entries_end_offset: Option, + pub observed_entry_count: usize, + pub zero_availability_count: usize, + pub zero_availability_names: Vec, + pub entries: Vec, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SmpLoadedSpecialConditionsTable { pub source_kind: String, @@ -1673,6 +1689,7 @@ pub struct SmpLoadedSaveSlice { pub bridge_family: Option, pub profile: Option, pub candidate_availability_table: Option, + pub named_locomotive_availability_table: Option, pub special_conditions_table: Option, pub event_runtime_collection: Option, pub notes: Vec, @@ -1840,6 +1857,7 @@ pub fn load_save_slice_from_report( bridge_family: summary.bridge_family.clone(), profile, candidate_availability_table, + named_locomotive_availability_table: None, special_conditions_table, event_runtime_collection: report.event_runtime_collection_summary.clone(), notes: summary.notes.clone(), @@ -2659,6 +2677,11 @@ fn parse_real_grouped_effect_row_summary( if descriptor_metadata.is_none() { notes.push("descriptor id not yet recovered in the checked-in effect table".to_string()); } + if let Some(loco_id) = recovered_locomotive_availability_loco_id(descriptor_id) { + notes.push(format!( + "locomotive availability descriptor maps to live locomotive id {loco_id}" + )); + } Some(SmpLoadedPackedEventGroupedEffectRowSummary { group_index, @@ -2836,6 +2859,16 @@ fn recovered_locomotive_availability_descriptor_metadata( }) } +fn recovered_locomotive_availability_loco_id(descriptor_id: u32) -> Option { + if (241..=351).contains(&descriptor_id) { + return Some(descriptor_id - 240); + } + if (457..=474).contains(&descriptor_id) { + return Some(descriptor_id - 345); + } + None +} + fn recovered_locomotive_cost_descriptor_metadata( descriptor_id: u32, ) -> Option { @@ -3409,6 +3442,7 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool { | RuntimeEffect::SetLimitedTrackBuildingAmount { .. } | RuntimeEffect::SetEconomicStatusCode { .. } | RuntimeEffect::SetCandidateAvailability { .. } + | RuntimeEffect::SetNamedLocomotiveAvailability { .. } | RuntimeEffect::SetSpecialCondition { .. } | RuntimeEffect::ConfiscateCompanyAssets { .. } | RuntimeEffect::DeactivateCompany { .. } diff --git a/crates/rrt-runtime/src/step.rs b/crates/rrt-runtime/src/step.rs index 419864e..522fd78 100644 --- a/crates/rrt-runtime/src/step.rs +++ b/crates/rrt-runtime/src/step.rs @@ -490,6 +490,11 @@ fn apply_runtime_effects( RuntimeEffect::SetCandidateAvailability { name, value } => { state.candidate_availability.insert(name.clone(), *value); } + RuntimeEffect::SetNamedLocomotiveAvailability { name, value } => { + state + .named_locomotive_availability + .insert(name.clone(), u32::from(*value)); + } RuntimeEffect::SetSpecialCondition { label, value } => { state.special_conditions.insert(label.clone(), *value); } @@ -1158,6 +1163,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), } @@ -1372,6 +1378,43 @@ mod tests { assert_eq!(result.service_events[0].mutated_company_ids, vec![2]); } + #[test] + fn applies_named_locomotive_availability_effects() { + let mut state = RuntimeState { + event_runtime_records: vec![RuntimeEventRecord { + record_id: 10, + trigger_kind: 7, + active: true, + service_count: 0, + marks_collection_dirty: false, + one_shot: false, + has_fired: false, + conditions: Vec::new(), + effects: vec![ + RuntimeEffect::SetNamedLocomotiveAvailability { + name: "Big Boy".to_string(), + value: false, + }, + RuntimeEffect::SetNamedLocomotiveAvailability { + name: "GP7".to_string(), + value: true, + }, + ], + }], + ..state() + }; + + let result = execute_step_command( + &mut state, + &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, + ) + .expect("named locomotive availability effects should succeed"); + + assert_eq!(state.named_locomotive_availability.get("Big Boy"), Some(&0)); + assert_eq!(state.named_locomotive_availability.get("GP7"), Some(&1)); + 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 b2d79ee..c025d9b 100644 --- a/crates/rrt-runtime/src/summary.rs +++ b/crates/rrt-runtime/src/summary.rs @@ -72,6 +72,8 @@ pub struct RuntimeSummary { pub event_runtime_record_count: usize, pub candidate_availability_count: usize, pub zero_candidate_availability_count: usize, + pub named_locomotive_availability_count: usize, + pub zero_named_locomotive_availability_count: usize, pub special_condition_count: usize, pub enabled_special_condition_count: usize, pub save_profile_kind: Option, @@ -570,6 +572,12 @@ impl RuntimeSummary { .values() .filter(|value| **value == 0) .count(), + named_locomotive_availability_count: state.named_locomotive_availability.len(), + zero_named_locomotive_availability_count: state + .named_locomotive_availability + .values() + .filter(|value| **value == 0) + .count(), special_condition_count: state.special_conditions.len(), enabled_special_condition_count: state .special_conditions @@ -781,6 +789,7 @@ mod tests { }), event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -869,6 +878,7 @@ mod tests { packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; @@ -878,6 +888,44 @@ mod tests { assert_eq!(summary.active_company_count, 1); } + #[test] + fn counts_named_locomotive_availability_entries_and_zero_values() { + let state = RuntimeState { + calendar: CalendarPoint { + year: 1830, + month_slot: 0, + phase_slot: 0, + tick_slot: 0, + }, + world_flags: BTreeMap::new(), + save_profile: RuntimeSaveProfileState::default(), + world_restore: RuntimeWorldRestoreState::default(), + metadata: BTreeMap::new(), + companies: Vec::new(), + selected_company_id: None, + players: Vec::new(), + selected_player_id: None, + trains: 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::from([ + ("Big Boy".to_string(), 0), + ("GP7".to_string(), 1), + ("Mikado".to_string(), 0), + ]), + special_conditions: BTreeMap::new(), + service_state: RuntimeServiceState::default(), + }; + + let summary = RuntimeSummary::from_state(&state); + assert_eq!(summary.named_locomotive_availability_count, 3); + assert_eq!(summary.zero_named_locomotive_availability_count, 2); + } + #[test] fn counts_world_frontier_buckets_separately() { let state = RuntimeState { @@ -966,6 +1014,7 @@ mod tests { }), event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), + named_locomotive_availability: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; diff --git a/docs/README.md b/docs/README.md index ab24937..ed8b35d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -127,6 +127,10 @@ The highest-value next passes are now: 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 remain recovered-but-parity-only until per-locomotive identity is grounded +- the runtime now also carries the save-owned named locomotive availability table directly: + 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 live locomotive-pool parity - 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 3dc97ce..237aae1 100644 --- a/docs/runtime-rehost-plan.md +++ b/docs/runtime-rehost-plan.md @@ -82,12 +82,16 @@ Implemented today: descriptors `230..240`, `241..351`, `352..451`, `453`, and `457..500` now carry recovered world-side scalar metadata, while descriptors `454..456` (`All Steam/Diesel/Electric Locos Avail.`) now execute as keyed `world_flags` +- a first-class named locomotive availability runtime surface now exists too: + 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 That means the next implementation work is breadth, not bootstrap. The recommended next slice is broader real grouped-descriptor and ordinary condition-id coverage beyond the current access, -whole-game toggle, train, player, and numeric-threshold batches. Richer runtime ownership should -still be added only where a later descriptor or condition family needs more than the current -event-owned roster. +whole-game toggle, train, player, numeric-threshold, and named locomotive availability batches. +Richer runtime ownership should still be added only where a later descriptor or condition family +needs more than the current event-owned roster. ## Why This Boundary diff --git a/fixtures/runtime/packed-event-named-locomotive-availability-save-slice-fixture.json b/fixtures/runtime/packed-event-named-locomotive-availability-save-slice-fixture.json new file mode 100644 index 0000000..7db5411 --- /dev/null +++ b/fixtures/runtime/packed-event-named-locomotive-availability-save-slice-fixture.json @@ -0,0 +1,60 @@ +{ + "format_version": 1, + "fixture_id": "packed-event-named-locomotive-availability-save-slice-fixture", + "source": { + "kind": "captured-runtime", + "description": "Fixture backed by a tracked save-slice document that imports the named locomotive availability table and mutates it through an imported packed-event record." + }, + "state_save_slice_path": "packed-event-named-locomotive-availability-save-slice.json", + "commands": [ + { + "kind": "service_trigger_kind", + "trigger_kind": 7 + } + ], + "expected_summary": { + "calendar_projection_is_placeholder": true, + "packed_event_collection_present": true, + "packed_event_record_count": 1, + "packed_event_decoded_record_count": 1, + "packed_event_imported_runtime_record_count": 1, + "event_runtime_record_count": 1, + "named_locomotive_availability_count": 3, + "zero_named_locomotive_availability_count": 2, + "total_event_record_service_count": 1, + "total_trigger_dispatch_count": 1, + "dirty_rerun_count": 0 + }, + "expected_state_fragment": { + "world_flags": { + "save_slice.named_locomotive_availability_present": true + }, + "metadata": { + "save_slice.named_locomotive_availability_source_kind": "runtime-save-direct-serializer", + "save_slice.named_locomotive_availability_entry_count": "2", + "save_slice.named_locomotive_availability_zero_count": "1" + }, + "named_locomotive_availability": { + "Big Boy": 0, + "GP7": 0, + "Mikado": 1 + }, + "packed_event_collection": { + "live_entry_ids": [ + 17 + ], + "records": [ + { + "decode_status": "executable", + "import_outcome": "imported" + } + ] + }, + "event_runtime_records": [ + { + "record_id": 17, + "service_count": 1 + } + ] + } +} diff --git a/fixtures/runtime/packed-event-named-locomotive-availability-save-slice.json b/fixtures/runtime/packed-event-named-locomotive-availability-save-slice.json new file mode 100644 index 0000000..014fb00 --- /dev/null +++ b/fixtures/runtime/packed-event-named-locomotive-availability-save-slice.json @@ -0,0 +1,117 @@ +{ + "format_version": 1, + "save_slice_id": "packed-event-named-locomotive-availability-save-slice", + "source": { + "description": "Tracked save-slice document proving named locomotive availability survives save import and can be mutated by imported packed events.", + "original_save_filename": "captured-named-locomotive-availability.gms", + "original_save_sha256": "named-locomotive-availability-sample-sha256", + "notes": [ + "tracked as JSON save-slice document rather than raw .smp", + "locks the named locomotive availability import surface without guessing per-descriptor locomotive names" + ] + }, + "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": { + "source_kind": "runtime-save-direct-serializer", + "semantic_family": "scenario-named-locomotive-availability-table", + "header_offset": null, + "entries_offset": null, + "entries_end_offset": null, + "observed_entry_count": 2, + "zero_availability_count": 1, + "zero_availability_names": [ + "GP7" + ], + "entries": [ + { + "index": 0, + "offset": 0, + "text": "Big Boy", + "availability_dword": 1, + "availability_dword_hex": "0x00000001", + "trailer_word": 1, + "trailer_word_hex": "0x00000001" + }, + { + "index": 1, + "offset": 65, + "text": "GP7", + "availability_dword": 0, + "availability_dword_hex": "0x00000000", + "trailer_word": 0, + "trailer_word_hex": "0x00000000" + } + ] + }, + "special_conditions_table": null, + "event_runtime_collection": { + "source_kind": "packed-event-runtime-collection", + "mechanism_family": "classic-save-rehydrate-v1", + "mechanism_confidence": "grounded", + "container_profile_family": "rt3-classic-save-container-v1", + "metadata_tag_offset": 28928, + "records_tag_offset": 29184, + "close_tag_offset": 29440, + "packed_state_version": 1001, + "packed_state_version_hex": "0x000003e9", + "live_id_bound": 17, + "live_record_count": 1, + "live_entry_ids": [ + 17 + ], + "decoded_record_count": 1, + "imported_runtime_record_count": 1, + "records": [ + { + "record_index": 0, + "live_entry_id": 17, + "payload_offset": 29186, + "payload_len": 64, + "decode_status": "executable", + "payload_family": "synthetic_harness", + "trigger_kind": 7, + "active": true, + "marks_collection_dirty": false, + "one_shot": false, + "text_bands": [], + "standalone_condition_row_count": 0, + "standalone_condition_rows": [], + "grouped_effect_row_counts": [ + 0, + 0, + 0, + 0 + ], + "grouped_effect_rows": [], + "decoded_actions": [ + { + "kind": "set_named_locomotive_availability", + "name": "Big Boy", + "value": false + }, + { + "kind": "set_named_locomotive_availability", + "name": "Mikado", + "value": true + } + ], + "executable_import_ready": true, + "notes": [ + "fixture packed-event record" + ] + } + ] + }, + "notes": [ + "named locomotive availability import surface" + ] + } +} diff --git a/fixtures/runtime/packed-event-selective-import-overlay-fixture.json b/fixtures/runtime/packed-event-selective-import-overlay-fixture.json index a95f769..1e869d1 100644 --- a/fixtures/runtime/packed-event-selective-import-overlay-fixture.json +++ b/fixtures/runtime/packed-event-selective-import-overlay-fixture.json @@ -21,7 +21,7 @@ }, "calendar_projection_source": "base-snapshot-preserved", "calendar_projection_is_placeholder": false, - "world_flag_count": 7, + "world_flag_count": 8, "company_count": 1, "packed_event_collection_present": true, "packed_event_record_count": 2,