Make locomotive context save-native
This commit is contained in:
parent
13c7268b0d
commit
b060c42fa2
18 changed files with 1240 additions and 83 deletions
33
README.md
33
README.md
|
|
@ -51,25 +51,20 @@ 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 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`, 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.
|
||||
scalar bands are now save-native too. Raw `.smp` inspection/export reconstructs the persisted
|
||||
`[world+0x66b6]` locomotive name table and derives a minimal `RuntimeState.locomotive_catalog`, so
|
||||
standalone save-slice imports can now lower recovered locomotive availability and locomotive-cost
|
||||
rows directly into `RuntimeState.named_locomotive_availability` and
|
||||
`RuntimeState.named_locomotive_cost` without needing overlay snapshots when the save carries enough
|
||||
catalog context. The remaining recovered scalar world families 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, and
|
||||
`blocked_missing_locomotive_catalog_context` is now reserved for intentionally incomplete save-side
|
||||
catalog context instead of the normal save-slice path. 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
|
||||
|
||||
|
|
|
|||
|
|
@ -4460,9 +4460,14 @@ mod tests {
|
|||
let missing_catalog_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"../../fixtures/runtime/packed-event-locomotive-availability-missing-catalog-save-slice-fixture.json",
|
||||
);
|
||||
let save_locomotive_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"../../fixtures/runtime/packed-event-locomotive-availability-save-slice-fixture.json",
|
||||
);
|
||||
let overlay_locomotive_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"../../fixtures/runtime/packed-event-locomotive-availability-overlay-fixture.json",
|
||||
);
|
||||
let save_locomotive_cost_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../fixtures/runtime/packed-event-locomotive-cost-save-slice-fixture.json");
|
||||
let overlay_locomotive_cost_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../fixtures/runtime/packed-event-locomotive-cost-overlay-fixture.json");
|
||||
let scalar_band_parity_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
|
|
@ -4493,8 +4498,13 @@ mod tests {
|
|||
run_runtime_summarize_fixture(&missing_catalog_fixture).expect(
|
||||
"save-slice-backed locomotive availability missing-catalog fixture should summarize",
|
||||
);
|
||||
run_runtime_summarize_fixture(&save_locomotive_fixture).expect(
|
||||
"save-slice-backed locomotive availability descriptor fixture should summarize",
|
||||
);
|
||||
run_runtime_summarize_fixture(&overlay_locomotive_fixture)
|
||||
.expect("overlay-backed locomotive availability fixture should summarize");
|
||||
run_runtime_summarize_fixture(&save_locomotive_cost_fixture)
|
||||
.expect("save-slice-backed locomotive cost fixture should summarize");
|
||||
run_runtime_summarize_fixture(&overlay_locomotive_cost_fixture)
|
||||
.expect("overlay-backed locomotive cost fixture should summarize");
|
||||
run_runtime_summarize_fixture(&scalar_band_parity_fixture)
|
||||
|
|
@ -4526,6 +4536,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: None,
|
||||
notes: vec!["exported for test".to_string()],
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: None,
|
||||
notes: vec![],
|
||||
|
|
@ -381,6 +382,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(
|
||||
rrt_runtime::SmpLoadedEventRuntimeCollectionSummary {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::persistence::{load_runtime_snapshot_document, validate_runtime_snapsh
|
|||
use crate::{
|
||||
CalendarPoint, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
||||
RuntimeCompanyTarget, RuntimeCondition, RuntimeEffect, RuntimeEventRecord,
|
||||
RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
||||
RuntimeEventRecordTemplate, RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary,
|
||||
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
||||
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
|
||||
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary,
|
||||
|
|
@ -95,6 +95,7 @@ struct SaveSliceProjection {
|
|||
event_runtime_records: Vec<RuntimeEventRecord>,
|
||||
candidate_availability: BTreeMap<String, u32>,
|
||||
named_locomotive_availability: BTreeMap<String, u32>,
|
||||
locomotive_catalog: Option<Vec<RuntimeLocomotiveCatalogEntry>>,
|
||||
named_locomotive_cost: BTreeMap<String, u32>,
|
||||
cargo_production_overrides: BTreeMap<u32, u32>,
|
||||
special_conditions: BTreeMap<String, u32>,
|
||||
|
|
@ -243,7 +244,7 @@ pub fn project_save_slice_to_runtime_state_import(
|
|||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
trains: Vec::new(),
|
||||
locomotive_catalog: Vec::new(),
|
||||
locomotive_catalog: projection.locomotive_catalog.unwrap_or_default(),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
company_territory_access: Vec::new(),
|
||||
|
|
@ -305,7 +306,9 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
|
|||
players: base_state.players.clone(),
|
||||
selected_player_id: base_state.selected_player_id,
|
||||
trains: base_state.trains.clone(),
|
||||
locomotive_catalog: base_state.locomotive_catalog.clone(),
|
||||
locomotive_catalog: projection
|
||||
.locomotive_catalog
|
||||
.unwrap_or_else(|| base_state.locomotive_catalog.clone()),
|
||||
territories: base_state.territories.clone(),
|
||||
company_territory_track_piece_counts: base_state
|
||||
.company_territory_track_piece_counts
|
||||
|
|
@ -351,6 +354,11 @@ fn project_save_slice_components(
|
|||
"save_slice.named_locomotive_availability_present".to_string(),
|
||||
save_slice.named_locomotive_availability_table.is_some(),
|
||||
);
|
||||
world_flags.insert(
|
||||
"save_slice.locomotive_catalog_present".to_string(),
|
||||
save_slice.locomotive_catalog.is_some()
|
||||
|| 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(),
|
||||
|
|
@ -441,31 +449,6 @@ fn project_save_slice_components(
|
|||
metadata.insert("save_slice.bridge_family".to_string(), family.clone());
|
||||
}
|
||||
|
||||
let (packed_event_collection, event_runtime_records) =
|
||||
project_packed_event_collection(save_slice, company_context)?;
|
||||
if let Some(summary) = &save_slice.event_runtime_collection {
|
||||
metadata.insert(
|
||||
"save_slice.event_runtime_collection_source_kind".to_string(),
|
||||
summary.source_kind.clone(),
|
||||
);
|
||||
metadata.insert(
|
||||
"save_slice.event_runtime_collection_version_hex".to_string(),
|
||||
summary.packed_state_version_hex.clone(),
|
||||
);
|
||||
metadata.insert(
|
||||
"save_slice.event_runtime_collection_record_count".to_string(),
|
||||
summary.live_record_count.to_string(),
|
||||
);
|
||||
metadata.insert(
|
||||
"save_slice.event_runtime_collection_decoded_record_count".to_string(),
|
||||
summary.decoded_record_count.to_string(),
|
||||
);
|
||||
metadata.insert(
|
||||
"save_slice.event_runtime_collection_imported_runtime_record_count".to_string(),
|
||||
event_runtime_records.len().to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let save_profile = if let Some(profile) = &save_slice.profile {
|
||||
metadata.insert(
|
||||
"save_slice.profile_kind".to_string(),
|
||||
|
|
@ -645,10 +628,105 @@ fn project_save_slice_components(
|
|||
named_locomotive_availability.insert(entry.text.clone(), entry.availability_dword);
|
||||
}
|
||||
}
|
||||
let locomotive_catalog = if let Some(catalog) = &save_slice.locomotive_catalog {
|
||||
metadata.insert(
|
||||
"save_slice.locomotive_catalog_source_kind".to_string(),
|
||||
catalog.source_kind.clone(),
|
||||
);
|
||||
metadata.insert(
|
||||
"save_slice.locomotive_catalog_semantic_family".to_string(),
|
||||
catalog.semantic_family.clone(),
|
||||
);
|
||||
metadata.insert(
|
||||
"save_slice.locomotive_catalog_entry_count".to_string(),
|
||||
catalog.observed_entry_count.to_string(),
|
||||
);
|
||||
if let Some(entries_offset) = catalog.entries_offset {
|
||||
metadata.insert(
|
||||
"save_slice.locomotive_catalog_entries_offset".to_string(),
|
||||
entries_offset.to_string(),
|
||||
);
|
||||
}
|
||||
Some(
|
||||
catalog
|
||||
.entries
|
||||
.iter()
|
||||
.map(|entry| RuntimeLocomotiveCatalogEntry {
|
||||
locomotive_id: entry.locomotive_id,
|
||||
name: entry.name.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
} else if let Some(table) = &save_slice.named_locomotive_availability_table {
|
||||
metadata.insert(
|
||||
"save_slice.locomotive_catalog_source_kind".to_string(),
|
||||
"derived-from-named-locomotive-availability-table".to_string(),
|
||||
);
|
||||
metadata.insert(
|
||||
"save_slice.locomotive_catalog_semantic_family".to_string(),
|
||||
"scenario-save-derived-locomotive-catalog".to_string(),
|
||||
);
|
||||
metadata.insert(
|
||||
"save_slice.locomotive_catalog_entry_count".to_string(),
|
||||
table.observed_entry_count.to_string(),
|
||||
);
|
||||
if let Some(entries_offset) = table.entries_offset {
|
||||
metadata.insert(
|
||||
"save_slice.locomotive_catalog_entries_offset".to_string(),
|
||||
entries_offset.to_string(),
|
||||
);
|
||||
}
|
||||
Some(
|
||||
table
|
||||
.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, entry)| RuntimeLocomotiveCatalogEntry {
|
||||
locomotive_id: (index + 1) as u32,
|
||||
name: entry.text.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let named_locomotive_cost = BTreeMap::new();
|
||||
let cargo_production_overrides = BTreeMap::new();
|
||||
|
||||
let mut packed_event_context = company_context.clone();
|
||||
if let Some(catalog) = &locomotive_catalog {
|
||||
packed_event_context.locomotive_catalog_names_by_id = catalog
|
||||
.iter()
|
||||
.map(|entry| (entry.locomotive_id, entry.name.clone()))
|
||||
.collect();
|
||||
}
|
||||
|
||||
let (packed_event_collection, event_runtime_records) =
|
||||
project_packed_event_collection(save_slice, &packed_event_context)?;
|
||||
if let Some(summary) = &save_slice.event_runtime_collection {
|
||||
metadata.insert(
|
||||
"save_slice.event_runtime_collection_source_kind".to_string(),
|
||||
summary.source_kind.clone(),
|
||||
);
|
||||
metadata.insert(
|
||||
"save_slice.event_runtime_collection_version_hex".to_string(),
|
||||
summary.packed_state_version_hex.clone(),
|
||||
);
|
||||
metadata.insert(
|
||||
"save_slice.event_runtime_collection_record_count".to_string(),
|
||||
summary.live_record_count.to_string(),
|
||||
);
|
||||
metadata.insert(
|
||||
"save_slice.event_runtime_collection_decoded_record_count".to_string(),
|
||||
summary.decoded_record_count.to_string(),
|
||||
);
|
||||
metadata.insert(
|
||||
"save_slice.event_runtime_collection_imported_runtime_record_count".to_string(),
|
||||
event_runtime_records.len().to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
for (index, note) in save_slice.notes.iter().enumerate() {
|
||||
metadata.insert(format!("save_slice.note.{index}"), note.clone());
|
||||
}
|
||||
|
|
@ -662,6 +740,7 @@ fn project_save_slice_components(
|
|||
event_runtime_records,
|
||||
candidate_availability,
|
||||
named_locomotive_availability,
|
||||
locomotive_catalog,
|
||||
named_locomotive_cost,
|
||||
cargo_production_overrides,
|
||||
special_conditions,
|
||||
|
|
@ -3242,6 +3321,32 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn save_named_locomotive_table(
|
||||
count: usize,
|
||||
) -> crate::SmpLoadedNamedLocomotiveAvailabilityTable {
|
||||
crate::SmpLoadedNamedLocomotiveAvailabilityTable {
|
||||
source_kind: "runtime-save-direct-serializer".to_string(),
|
||||
semantic_family: "scenario-named-locomotive-availability-table".to_string(),
|
||||
header_offset: None,
|
||||
entries_offset: Some(0x7c78),
|
||||
entries_end_offset: Some(0x7c78 + count * 0x41),
|
||||
observed_entry_count: count,
|
||||
zero_availability_count: 0,
|
||||
zero_availability_names: vec![],
|
||||
entries: (0..count)
|
||||
.map(|index| crate::SmpRt3105SaveNameTableEntry {
|
||||
index,
|
||||
offset: 0x7c78 + index * 0x41,
|
||||
text: format!("Locomotive {}", index + 1),
|
||||
availability_dword: 1,
|
||||
availability_dword_hex: "0x00000001".to_string(),
|
||||
trailer_word: 1,
|
||||
trailer_word_hex: "0x00000001".to_string(),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn real_cargo_production_row(
|
||||
descriptor_id: u32,
|
||||
value: i32,
|
||||
|
|
@ -3498,6 +3603,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: None,
|
||||
notes: vec![],
|
||||
|
|
@ -3537,6 +3643,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: None,
|
||||
notes: vec![],
|
||||
|
|
@ -3650,6 +3757,7 @@ mod tests {
|
|||
],
|
||||
},
|
||||
),
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: Some(crate::SmpLoadedSpecialConditionsTable {
|
||||
source_kind: "save-fixed-special-conditions-range".to_string(),
|
||||
table_offset: 0x0d64,
|
||||
|
|
@ -3903,6 +4011,11 @@ mod tests {
|
|||
import.state.named_locomotive_availability.get("GP7"),
|
||||
Some(&1)
|
||||
);
|
||||
assert_eq!(import.state.locomotive_catalog.len(), 2);
|
||||
assert_eq!(import.state.locomotive_catalog[0].locomotive_id, 1);
|
||||
assert_eq!(import.state.locomotive_catalog[0].name, "Big Boy");
|
||||
assert_eq!(import.state.locomotive_catalog[1].locomotive_id, 2);
|
||||
assert_eq!(import.state.locomotive_catalog[1].name, "GP7");
|
||||
assert_eq!(
|
||||
import.state.special_conditions.get("Disable Cargo Economy"),
|
||||
Some(&0)
|
||||
|
|
@ -3923,6 +4036,14 @@ mod tests {
|
|||
.map(String::as_str),
|
||||
Some("2")
|
||||
);
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
.metadata
|
||||
.get("save_slice.locomotive_catalog_source_kind")
|
||||
.map(String::as_str),
|
||||
Some("derived-from-named-locomotive-availability-table")
|
||||
);
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
|
|
@ -3961,6 +4082,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -4075,6 +4197,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -4167,6 +4290,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -4277,6 +4401,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -4360,6 +4485,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -4493,6 +4619,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -4734,6 +4861,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -4810,6 +4938,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -4908,6 +5037,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -4981,6 +5111,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -5079,6 +5210,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -5143,6 +5275,108 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imports_scalar_locomotive_availability_rows_with_save_derived_catalog_context() {
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
trailer_family: None,
|
||||
bridge_family: None,
|
||||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: Some(save_named_locomotive_table(112)),
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
metadata_tag_offset: 0x7100,
|
||||
records_tag_offset: 0x7200,
|
||||
close_tag_offset: 0x7600,
|
||||
packed_state_version: 0x3e9,
|
||||
packed_state_version_hex: "0x000003e9".to_string(),
|
||||
live_id_bound: 33,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![33],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 33,
|
||||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(120),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "real_packed_v1".to_string(),
|
||||
trigger_kind: Some(7),
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: Some(false),
|
||||
compact_control: Some(real_compact_control()),
|
||||
text_bands: vec![],
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![2, 0, 0, 0],
|
||||
grouped_effect_rows: vec![
|
||||
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![
|
||||
"scalar locomotive availability rows use save-derived catalog context"
|
||||
.to_string(),
|
||||
],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let mut import = project_save_slice_to_runtime_state_import(
|
||||
&save_slice,
|
||||
"save-derived-locomotive-availability",
|
||||
None,
|
||||
)
|
||||
.expect("save slice should project");
|
||||
|
||||
assert_eq!(import.state.locomotive_catalog.len(), 112);
|
||||
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("imported")
|
||||
);
|
||||
|
||||
execute_step_command(
|
||||
&mut import.state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||
)
|
||||
.expect("save-derived locomotive availability record should run");
|
||||
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
.named_locomotive_availability
|
||||
.get("Locomotive 10"),
|
||||
Some(&42)
|
||||
);
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
.named_locomotive_availability
|
||||
.get("Locomotive 112"),
|
||||
Some(&7)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlays_scalar_locomotive_availability_rows_into_named_availability_effects() {
|
||||
let base_state = RuntimeState {
|
||||
|
|
@ -5196,6 +5430,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -5297,6 +5532,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -5372,6 +5608,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -5435,6 +5672,101 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imports_scalar_locomotive_cost_rows_with_save_derived_catalog_context() {
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
trailer_family: None,
|
||||
bridge_family: None,
|
||||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: Some(save_named_locomotive_table(112)),
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
metadata_tag_offset: 0x7100,
|
||||
records_tag_offset: 0x7200,
|
||||
close_tag_offset: 0x7600,
|
||||
packed_state_version: 0x3e9,
|
||||
packed_state_version_hex: "0x000003e9".to_string(),
|
||||
live_id_bound: 41,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![41],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 41,
|
||||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(120),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "real_packed_v1".to_string(),
|
||||
trigger_kind: Some(7),
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: Some(false),
|
||||
compact_control: Some(real_compact_control()),
|
||||
text_bands: vec![],
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![2, 0, 0, 0],
|
||||
grouped_effect_rows: vec![
|
||||
real_locomotive_cost_row(352, 250000),
|
||||
real_locomotive_cost_row(475, 325000),
|
||||
],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec![
|
||||
"scalar locomotive cost rows use save-derived catalog context".to_string(),
|
||||
],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let mut import = project_save_slice_to_runtime_state_import(
|
||||
&save_slice,
|
||||
"save-derived-locomotive-cost",
|
||||
None,
|
||||
)
|
||||
.expect("save slice should project");
|
||||
|
||||
assert_eq!(import.state.locomotive_catalog.len(), 112);
|
||||
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("imported")
|
||||
);
|
||||
|
||||
execute_step_command(
|
||||
&mut import.state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||
)
|
||||
.expect("save-derived locomotive cost record should run");
|
||||
|
||||
assert_eq!(
|
||||
import.state.named_locomotive_cost.get("Locomotive 1"),
|
||||
Some(&250000)
|
||||
);
|
||||
assert_eq!(
|
||||
import.state.named_locomotive_cost.get("Locomotive 101"),
|
||||
Some(&325000)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlays_scalar_locomotive_cost_rows_into_named_cost_effects() {
|
||||
let base_state = RuntimeState {
|
||||
|
|
@ -5488,6 +5820,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -5582,6 +5915,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -5655,6 +5989,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -5738,6 +6073,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -5863,6 +6199,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -6007,6 +6344,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -6110,6 +6448,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -6194,6 +6533,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -6299,6 +6639,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -6404,6 +6745,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -6499,6 +6841,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -6590,6 +6933,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -6689,6 +7033,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -6779,6 +7124,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -6851,6 +7197,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -6928,6 +7275,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -7010,6 +7358,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -7092,6 +7441,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -7190,6 +7540,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -7279,6 +7630,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -7394,6 +7746,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -7519,6 +7872,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -7628,6 +7982,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -7800,6 +8155,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -7892,6 +8248,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -7980,6 +8337,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -8133,6 +8491,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -8275,6 +8634,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -8364,6 +8724,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -8480,6 +8841,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -8584,6 +8946,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -8729,6 +9092,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
@ -8903,6 +9267,7 @@ mod tests {
|
|||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: None,
|
||||
locomotive_catalog: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
|
|
|
|||
|
|
@ -1008,6 +1008,23 @@ pub struct SmpRt3105SaveNameTableProbe {
|
|||
pub evidence: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpRt3105SaveNamedLocomotiveAvailabilityProbe {
|
||||
pub profile_family: String,
|
||||
pub source_kind: String,
|
||||
pub semantic_family: String,
|
||||
pub semantic_alignment: Vec<String>,
|
||||
pub entries_offset: usize,
|
||||
pub entry_stride: usize,
|
||||
pub entry_stride_hex: String,
|
||||
pub observed_entry_count: usize,
|
||||
pub zero_availability_count: usize,
|
||||
pub zero_availability_names: Vec<String>,
|
||||
pub entries_end_offset: usize,
|
||||
pub entries: Vec<SmpRt3105SaveNameTableEntry>,
|
||||
pub evidence: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpRt3105SaveNameTableEntry {
|
||||
pub index: usize,
|
||||
|
|
@ -1521,6 +1538,22 @@ pub struct SmpLoadedNamedLocomotiveAvailabilityTable {
|
|||
pub entries: Vec<SmpRt3105SaveNameTableEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpLoadedLocomotiveCatalogEntry {
|
||||
pub locomotive_id: u32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpLoadedLocomotiveCatalog {
|
||||
pub source_kind: String,
|
||||
pub semantic_family: String,
|
||||
#[serde(default)]
|
||||
pub entries_offset: Option<usize>,
|
||||
pub observed_entry_count: usize,
|
||||
pub entries: Vec<SmpLoadedLocomotiveCatalogEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpLoadedSpecialConditionsTable {
|
||||
pub source_kind: String,
|
||||
|
|
@ -1694,6 +1727,8 @@ pub struct SmpLoadedSaveSlice {
|
|||
pub profile: Option<SmpLoadedProfile>,
|
||||
pub candidate_availability_table: Option<SmpLoadedCandidateAvailabilityTable>,
|
||||
pub named_locomotive_availability_table: Option<SmpLoadedNamedLocomotiveAvailabilityTable>,
|
||||
#[serde(default)]
|
||||
pub locomotive_catalog: Option<SmpLoadedLocomotiveCatalog>,
|
||||
pub special_conditions_table: Option<SmpLoadedSpecialConditionsTable>,
|
||||
pub event_runtime_collection: Option<SmpLoadedEventRuntimeCollectionSummary>,
|
||||
pub notes: Vec<String>,
|
||||
|
|
@ -1728,6 +1763,8 @@ pub struct SmpInspectionReport {
|
|||
pub rt3_105_post_span_bridge_probe: Option<SmpRt3105PostSpanBridgeProbe>,
|
||||
pub rt3_105_save_bridge_payload_probe: Option<SmpRt3105SaveBridgePayloadProbe>,
|
||||
pub rt3_105_save_name_table_probe: Option<SmpRt3105SaveNameTableProbe>,
|
||||
pub rt3_105_save_named_locomotive_availability_probe:
|
||||
Option<SmpRt3105SaveNamedLocomotiveAvailabilityProbe>,
|
||||
pub special_conditions_probe: Option<SmpSpecialConditionsProbe>,
|
||||
pub smp_aligned_runtime_rule_band_probe: Option<SmpAlignedRuntimeRuleBandProbe>,
|
||||
pub post_special_conditions_scalar_probe: Option<SmpPostSpecialConditionsScalarProbe>,
|
||||
|
|
@ -1839,6 +1876,23 @@ pub fn load_save_slice_from_report(
|
|||
entries: probe.entries.clone(),
|
||||
}
|
||||
});
|
||||
let named_locomotive_availability_table = report
|
||||
.rt3_105_save_named_locomotive_availability_probe
|
||||
.as_ref()
|
||||
.map(|probe| SmpLoadedNamedLocomotiveAvailabilityTable {
|
||||
source_kind: probe.source_kind.clone(),
|
||||
semantic_family: probe.semantic_family.clone(),
|
||||
header_offset: None,
|
||||
entries_offset: Some(probe.entries_offset),
|
||||
entries_end_offset: Some(probe.entries_end_offset),
|
||||
observed_entry_count: probe.observed_entry_count,
|
||||
zero_availability_count: probe.zero_availability_count,
|
||||
zero_availability_names: probe.zero_availability_names.clone(),
|
||||
entries: probe.entries.clone(),
|
||||
});
|
||||
let locomotive_catalog = named_locomotive_availability_table
|
||||
.as_ref()
|
||||
.and_then(derive_locomotive_catalog_from_named_availability_table);
|
||||
let special_conditions_table =
|
||||
report
|
||||
.special_conditions_probe
|
||||
|
|
@ -1861,13 +1915,40 @@ pub fn load_save_slice_from_report(
|
|||
bridge_family: summary.bridge_family.clone(),
|
||||
profile,
|
||||
candidate_availability_table,
|
||||
named_locomotive_availability_table: None,
|
||||
named_locomotive_availability_table,
|
||||
locomotive_catalog,
|
||||
special_conditions_table,
|
||||
event_runtime_collection: report.event_runtime_collection_summary.clone(),
|
||||
notes: summary.notes.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn derive_locomotive_catalog_from_named_availability_table(
|
||||
table: &SmpLoadedNamedLocomotiveAvailabilityTable,
|
||||
) -> Option<SmpLoadedLocomotiveCatalog> {
|
||||
if table.entries.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let entries = table
|
||||
.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, entry)| SmpLoadedLocomotiveCatalogEntry {
|
||||
locomotive_id: (index + 1) as u32,
|
||||
name: entry.text.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Some(SmpLoadedLocomotiveCatalog {
|
||||
source_kind: format!("{}-ordinal-catalog", table.source_kind),
|
||||
semantic_family: "scenario-save-derived-locomotive-catalog".to_string(),
|
||||
entries_offset: table.entries_offset,
|
||||
observed_entry_count: entries.len(),
|
||||
entries,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_event_runtime_collection_summary(
|
||||
bytes: &[u8],
|
||||
container_profile: Option<&SmpContainerProfile>,
|
||||
|
|
@ -3691,6 +3772,13 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
|
|||
container_profile.as_ref(),
|
||||
rt3_105_save_bridge_payload_probe.as_ref(),
|
||||
);
|
||||
let rt3_105_save_named_locomotive_availability_probe =
|
||||
parse_rt3_105_save_named_locomotive_availability_probe(
|
||||
bytes,
|
||||
file_extension_hint.as_deref(),
|
||||
container_profile.as_ref(),
|
||||
rt3_105_packed_profile_probe.as_ref(),
|
||||
);
|
||||
let special_conditions_probe = parse_special_conditions_probe(
|
||||
bytes,
|
||||
file_extension_hint.as_deref(),
|
||||
|
|
@ -3825,6 +3913,7 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
|
|||
rt3_105_post_span_bridge_probe,
|
||||
rt3_105_save_bridge_payload_probe,
|
||||
rt3_105_save_name_table_probe,
|
||||
rt3_105_save_named_locomotive_availability_probe,
|
||||
special_conditions_probe,
|
||||
smp_aligned_runtime_rule_band_probe,
|
||||
post_special_conditions_scalar_probe,
|
||||
|
|
@ -3870,6 +3959,8 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
|
|||
.to_string(),
|
||||
"The RT3 1.05 candidate-availability table probe decodes the fixed-width trailing name table from either the common-save bridge payload or the fixed 0x6a70..0x73c0 source range when that header validates."
|
||||
.to_string(),
|
||||
"The RT3 1.05 save-side named locomotive availability probe scans the post-profile save region for the grounded fixed-width locomotive-name-plus-dword row family when that run is present."
|
||||
.to_string(),
|
||||
"The post-special-conditions scalar probe captures the fixed 0x0df4..0x0f30 dword window immediately after the hidden sentinel slot, splits it into the aligned-band overlap prefix and the later tail, and records the live-object offset alignment of that tail without claiming a byte-for-byte mirror."
|
||||
.to_string(),
|
||||
"The classic rehydrate-profile probe recognizes the grounded 0x32dc -> 0x3714 -> 0x3715 progress-id sequence and captures the exact 0x108-byte block between the latter two ids when that pattern appears."
|
||||
|
|
@ -5414,6 +5505,148 @@ fn parse_rt3_105_save_name_table_probe(
|
|||
})
|
||||
}
|
||||
|
||||
const RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE: usize = 0x41;
|
||||
const RT3_105_SAVE_NAMED_LOCOMOTIVE_MIN_ENTRY_COUNT: usize = 8;
|
||||
const RT3_105_SAVE_NAMED_LOCOMOTIVE_MAX_SEARCH_SPAN: usize = 0x4000;
|
||||
|
||||
fn parse_rt3_105_save_named_locomotive_availability_probe(
|
||||
bytes: &[u8],
|
||||
file_extension_hint: Option<&str>,
|
||||
container_profile: Option<&SmpContainerProfile>,
|
||||
packed_profile_probe: Option<&SmpRt3105PackedProfileProbe>,
|
||||
) -> Option<SmpRt3105SaveNamedLocomotiveAvailabilityProbe> {
|
||||
let packed_profile_probe = packed_profile_probe?;
|
||||
let extension = file_extension_hint.unwrap_or("");
|
||||
let profile_family = container_profile
|
||||
.map(|profile| profile.profile_family.clone())
|
||||
.unwrap_or_else(|| packed_profile_probe.profile_family.clone());
|
||||
if !matches!(extension, "gms" | "gmx") || !profile_family.contains("save-container") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let search_start = packed_profile_probe
|
||||
.packed_profile_offset
|
||||
.checked_add(packed_profile_probe.packed_profile_len)?;
|
||||
let search_end = search_start
|
||||
.checked_add(RT3_105_SAVE_NAMED_LOCOMOTIVE_MAX_SEARCH_SPAN)
|
||||
.map(|end| end.min(bytes.len()))
|
||||
.unwrap_or(bytes.len());
|
||||
if search_end <= search_start + RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut best_start = None;
|
||||
let mut best_entries = Vec::new();
|
||||
for candidate_start in search_start..search_end {
|
||||
let entries = parse_direct_named_locomotive_entries(bytes, candidate_start, search_end);
|
||||
if entries.len() > best_entries.len() {
|
||||
best_entries = entries;
|
||||
best_start = Some(candidate_start);
|
||||
}
|
||||
}
|
||||
|
||||
if best_entries.len() < RT3_105_SAVE_NAMED_LOCOMOTIVE_MIN_ENTRY_COUNT {
|
||||
return None;
|
||||
}
|
||||
|
||||
let entries_offset = best_start?;
|
||||
let entries_end_offset = entries_offset
|
||||
.checked_add(best_entries.len() * RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE)?;
|
||||
let zero_availability_names = best_entries
|
||||
.iter()
|
||||
.filter(|entry| entry.availability_dword == 0)
|
||||
.map(|entry| entry.text.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let zero_availability_count = zero_availability_names.len();
|
||||
let source_kind = match extension {
|
||||
"gms" => "save-direct-locomotive-row-run",
|
||||
"gmx" => "sandbox-direct-locomotive-row-run",
|
||||
_ => "direct-locomotive-row-run",
|
||||
}
|
||||
.to_string();
|
||||
|
||||
let observed_entry_count = best_entries.len();
|
||||
|
||||
Some(SmpRt3105SaveNamedLocomotiveAvailabilityProbe {
|
||||
profile_family,
|
||||
source_kind,
|
||||
semantic_family: "scenario-named-locomotive-availability-table".to_string(),
|
||||
semantic_alignment: vec![
|
||||
"Matches the grounded `.smp` save-side locomotive-name-plus-dword row family restored into scenario state [world+0x66b6].".to_string(),
|
||||
"Entry layout is one availability dword at +0x00 followed by one fixed-width locomotive name buffer at +0x04..+0x40.".to_string(),
|
||||
"The recovered row order is treated conservatively as the live locomotive ordinal order later used by locomotives-page descriptor lowering.".to_string(),
|
||||
],
|
||||
entries_offset,
|
||||
entry_stride: RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE,
|
||||
entry_stride_hex: format!("0x{:x}", RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE),
|
||||
observed_entry_count,
|
||||
zero_availability_count,
|
||||
zero_availability_names,
|
||||
entries_end_offset,
|
||||
entries: best_entries,
|
||||
evidence: vec![
|
||||
format!("search span 0x{search_start:08x}..0x{search_end:08x}"),
|
||||
format!("entries offset 0x{entries_offset:08x}"),
|
||||
format!(
|
||||
"entry stride 0x{:x}",
|
||||
RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE
|
||||
),
|
||||
format!("observed entry count {observed_entry_count}"),
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_direct_named_locomotive_entries(
|
||||
bytes: &[u8],
|
||||
start_offset: usize,
|
||||
search_end: usize,
|
||||
) -> Vec<SmpRt3105SaveNameTableEntry> {
|
||||
let mut entries = Vec::new();
|
||||
let mut offset = start_offset;
|
||||
while offset + RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE <= bytes.len() && offset < search_end
|
||||
{
|
||||
let record = &bytes[offset..offset + RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE];
|
||||
let Some(nul_index) = record[4..].iter().position(|byte| *byte == 0) else {
|
||||
break;
|
||||
};
|
||||
let name_bytes = &record[4..4 + nul_index];
|
||||
if name_bytes.is_empty() {
|
||||
break;
|
||||
}
|
||||
let Ok(text) = std::str::from_utf8(name_bytes) else {
|
||||
break;
|
||||
};
|
||||
if !is_probable_named_locomotive_label(text) {
|
||||
break;
|
||||
}
|
||||
if record[4 + nul_index + 1..].iter().any(|byte| *byte != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
let availability_dword = u32::from_le_bytes([record[0], record[1], record[2], record[3]]);
|
||||
entries.push(SmpRt3105SaveNameTableEntry {
|
||||
index: entries.len(),
|
||||
offset,
|
||||
text: text.to_string(),
|
||||
availability_dword,
|
||||
availability_dword_hex: format!("0x{availability_dword:08x}"),
|
||||
trailer_word: availability_dword,
|
||||
trailer_word_hex: format!("0x{availability_dword:08x}"),
|
||||
});
|
||||
offset += RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE;
|
||||
}
|
||||
entries
|
||||
}
|
||||
|
||||
fn is_probable_named_locomotive_label(text: &str) -> bool {
|
||||
if text.is_empty() || text.len() > 40 {
|
||||
return false;
|
||||
}
|
||||
text.bytes().all(|byte| {
|
||||
byte.is_ascii_alphanumeric() || matches!(byte, b' ' | b'-' | b'/' | b'(' | b')' | b'.')
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_special_conditions_probe(
|
||||
bytes: &[u8],
|
||||
file_extension_hint: Option<&str>,
|
||||
|
|
@ -10296,6 +10529,40 @@ mod tests {
|
|||
],
|
||||
evidence: vec![],
|
||||
};
|
||||
let named_locomotive_table = SmpRt3105SaveNamedLocomotiveAvailabilityProbe {
|
||||
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||
source_kind: "save-direct-locomotive-row-run".to_string(),
|
||||
semantic_family: "scenario-named-locomotive-availability-table".to_string(),
|
||||
semantic_alignment: vec![],
|
||||
entries_offset: 0x7c78,
|
||||
entry_stride: RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE,
|
||||
entry_stride_hex: format!("0x{:x}", RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE),
|
||||
observed_entry_count: 2,
|
||||
zero_availability_count: 1,
|
||||
zero_availability_names: vec!["Big Boy".to_string()],
|
||||
entries_end_offset: 0x7c78 + 2 * RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE,
|
||||
entries: vec![
|
||||
SmpRt3105SaveNameTableEntry {
|
||||
index: 0,
|
||||
offset: 0x7c78,
|
||||
text: "Big Boy".to_string(),
|
||||
availability_dword: 0,
|
||||
availability_dword_hex: "0x00000000".to_string(),
|
||||
trailer_word: 0,
|
||||
trailer_word_hex: "0x00000000".to_string(),
|
||||
},
|
||||
SmpRt3105SaveNameTableEntry {
|
||||
index: 1,
|
||||
offset: 0x7cb9,
|
||||
text: "GP7".to_string(),
|
||||
availability_dword: 1,
|
||||
availability_dword_hex: "0x00000001".to_string(),
|
||||
trailer_word: 1,
|
||||
trailer_word_hex: "0x00000001".to_string(),
|
||||
},
|
||||
],
|
||||
evidence: vec![],
|
||||
};
|
||||
let bridge = SmpRt3105PostSpanBridgeProbe {
|
||||
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||
bridge_family: "rt3-105-save-post-span-bridge-v1".to_string(),
|
||||
|
|
@ -10315,6 +10582,8 @@ mod tests {
|
|||
};
|
||||
report.rt3_105_packed_profile_probe = Some(packed_profile.clone());
|
||||
report.rt3_105_save_name_table_probe = Some(name_table.clone());
|
||||
report.rt3_105_save_named_locomotive_availability_probe =
|
||||
Some(named_locomotive_table.clone());
|
||||
report.save_load_summary = build_save_load_summary(
|
||||
Some("gms"),
|
||||
Some(&SmpContainerProfile {
|
||||
|
|
@ -10347,6 +10616,33 @@ mod tests {
|
|||
.text,
|
||||
"Uranium Mine"
|
||||
);
|
||||
assert_eq!(
|
||||
slice
|
||||
.named_locomotive_availability_table
|
||||
.as_ref()
|
||||
.expect("named locomotive availability table")
|
||||
.entries[1]
|
||||
.text,
|
||||
"GP7"
|
||||
);
|
||||
assert_eq!(
|
||||
slice
|
||||
.locomotive_catalog
|
||||
.as_ref()
|
||||
.expect("derived locomotive catalog")
|
||||
.entries[0]
|
||||
.name,
|
||||
"Big Boy"
|
||||
);
|
||||
assert_eq!(
|
||||
slice
|
||||
.locomotive_catalog
|
||||
.as_ref()
|
||||
.expect("derived locomotive catalog")
|
||||
.entries[1]
|
||||
.locomotive_id,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -10797,6 +11093,87 @@ mod tests {
|
|||
assert_eq!(probe.footer_progress_word_1, 0x3714);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_rt3_105_save_named_locomotive_availability_probe() {
|
||||
let mut bytes = vec![0u8; 0x9000];
|
||||
let packed_profile_offset = 0x73c0usize;
|
||||
let packed_profile_len = 0x108usize;
|
||||
let entries_offset = 0x7c78usize;
|
||||
let names = [
|
||||
("Eight Wheeler 4-4-0", 1u32),
|
||||
("EP-2 Bipolar", 1u32),
|
||||
("ET22", 1u32),
|
||||
("F3", 0u32),
|
||||
("Fairlie 0-6-6-0", 1u32),
|
||||
("Firefly 2-2-2", 0u32),
|
||||
("FP45", 0u32),
|
||||
("Ge 6/6 Crocodile", 1u32),
|
||||
("GG1", 0u32),
|
||||
("GP7", 1u32),
|
||||
];
|
||||
|
||||
for (index, (name, value)) in names.iter().enumerate() {
|
||||
let offset = entries_offset + index * RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE;
|
||||
bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
|
||||
bytes[offset + 4..offset + 4 + name.len()].copy_from_slice(name.as_bytes());
|
||||
}
|
||||
|
||||
let probe = parse_rt3_105_save_named_locomotive_availability_probe(
|
||||
&bytes,
|
||||
Some("gms"),
|
||||
Some(&SmpContainerProfile {
|
||||
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||
profile_evidence: vec![],
|
||||
is_known_profile: true,
|
||||
}),
|
||||
Some(&SmpRt3105PackedProfileProbe {
|
||||
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||
packed_profile_offset,
|
||||
packed_profile_len,
|
||||
packed_profile_len_hex: "0x108".to_string(),
|
||||
packed_profile_block: SmpRt3105PackedProfileBlock {
|
||||
relative_len: packed_profile_len,
|
||||
relative_len_hex: "0x108".to_string(),
|
||||
leading_word_0: 3,
|
||||
leading_word_0_hex: "0x00000003".to_string(),
|
||||
trailing_zero_word_count_after_leading_word: 2,
|
||||
header_flag_word_3: 1,
|
||||
header_flag_word_3_hex: "0x00000001".to_string(),
|
||||
map_path_offset: 0x10,
|
||||
map_path: Some("Alternate USA.gmp".to_string()),
|
||||
display_name_offset: 0x43,
|
||||
display_name: Some("Alternate USA".to_string()),
|
||||
profile_byte_0x77: 0x07,
|
||||
profile_byte_0x77_hex: "0x07".to_string(),
|
||||
profile_byte_0x82: 0x4d,
|
||||
profile_byte_0x82_hex: "0x4d".to_string(),
|
||||
profile_byte_0x97: 0,
|
||||
profile_byte_0x97_hex: "0x00".to_string(),
|
||||
profile_byte_0xc5: 0,
|
||||
profile_byte_0xc5_hex: "0x00".to_string(),
|
||||
stable_nonzero_words: vec![],
|
||||
},
|
||||
ascii_runs: vec![],
|
||||
}),
|
||||
)
|
||||
.expect("save-side locomotive table probe should parse");
|
||||
|
||||
assert_eq!(probe.source_kind, "save-direct-locomotive-row-run");
|
||||
assert_eq!(
|
||||
probe.semantic_family,
|
||||
"scenario-named-locomotive-availability-table"
|
||||
);
|
||||
assert_eq!(probe.entries_offset, entries_offset);
|
||||
assert_eq!(
|
||||
probe.entry_stride,
|
||||
RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE
|
||||
);
|
||||
assert_eq!(probe.observed_entry_count, names.len());
|
||||
assert_eq!(probe.zero_availability_count, 4);
|
||||
assert_eq!(probe.entries[0].text, "Eight Wheeler 4-4-0");
|
||||
assert_eq!(probe.entries[9].text, "GP7");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_rt3_105_alt_save_container_profile() {
|
||||
let shared_header = SmpSharedHeader {
|
||||
|
|
|
|||
|
|
@ -128,13 +128,13 @@ The highest-value next passes are now:
|
|||
metadata into keyed `world_flags`, while the wider locomotive availability/cost scalar bands now
|
||||
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 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
|
||||
- raw `.smp` inspection/export now reconstructs the persisted save-side named locomotive table and
|
||||
derives a minimal locomotive catalog from its row order, so save-slice documents can carry both
|
||||
`RuntimeState.named_locomotive_availability` and the catalog context needed for descriptor
|
||||
lowering
|
||||
- recovered scalar locomotive availability and locomotive-cost descriptors now import through that
|
||||
save-native or embedded `RuntimeState.locomotive_catalog` context into the ordinary
|
||||
`named_locomotive_availability` and `named_locomotive_cost` runtime maps
|
||||
- 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`
|
||||
|
|
|
|||
|
|
@ -87,13 +87,13 @@ Implemented today:
|
|||
`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 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
|
||||
through `RuntimeState.locomotive_catalog` into `RuntimeState.named_locomotive_availability`;
|
||||
raw `.smp` inspection/export now reconstructs the save-side locomotive row family and derives the
|
||||
catalog directly into save-slice documents, so standalone save-slice imports can execute those
|
||||
rows whenever the save carries enough catalog entries
|
||||
- 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
|
||||
save-native or embedded 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`
|
||||
|
|
|
|||
|
|
@ -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 scalar 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 save-derived or embedded catalog context is supplied."
|
||||
},
|
||||
"state_save_slice_path": "packed-event-locomotive-availability-missing-catalog-save-slice.json",
|
||||
"commands": [
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"format_version": 1,
|
||||
"save_slice_id": "packed-event-locomotive-availability-missing-catalog-save-slice",
|
||||
"source": {
|
||||
"description": "Tracked save-slice document proving scalar 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 when the save slice lacks enough locomotive catalog context.",
|
||||
"original_save_filename": "captured-locomotive-availability-missing-catalog.gms",
|
||||
"original_save_sha256": "locomotive-availability-missing-catalog-sample-sha256",
|
||||
"notes": [
|
||||
|
|
@ -89,7 +89,7 @@
|
|||
"decoded_actions": [],
|
||||
"executable_import_ready": false,
|
||||
"notes": [
|
||||
"scalar locomotive availability row still requires overlay-backed locomotive catalog context"
|
||||
"scalar locomotive availability row still requires locomotive catalog context that this save slice does not carry"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"fixture_id": "packed-event-locomotive-availability-save-slice-fixture",
|
||||
"source": {
|
||||
"kind": "captured-runtime",
|
||||
"description": "Fixture backed by a tracked save-slice document that imports recovered scalar locomotive availability descriptors through embedded locomotive catalog context."
|
||||
},
|
||||
"state_save_slice_path": "packed-event-locomotive-availability-save-slice.json",
|
||||
"commands": [
|
||||
{
|
||||
"kind": "service_trigger_kind",
|
||||
"trigger_kind": 7
|
||||
}
|
||||
],
|
||||
"expected_summary": {
|
||||
"calendar_projection_is_placeholder": true,
|
||||
"locomotive_catalog_count": 2,
|
||||
"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": 2,
|
||||
"zero_named_locomotive_availability_count": 0,
|
||||
"total_event_record_service_count": 1,
|
||||
"total_trigger_dispatch_count": 1,
|
||||
"dirty_rerun_count": 0
|
||||
},
|
||||
"expected_state_fragment": {
|
||||
"metadata": {
|
||||
"save_slice.locomotive_catalog_source_kind": "save-direct-locomotive-row-run-ordinal-catalog"
|
||||
},
|
||||
"named_locomotive_availability": {
|
||||
"Locomotive 10": 42,
|
||||
"Locomotive 112": 7
|
||||
},
|
||||
"packed_event_collection": {
|
||||
"live_entry_ids": [33],
|
||||
"records": [
|
||||
{
|
||||
"decode_status": "parity_only",
|
||||
"import_outcome": "imported"
|
||||
}
|
||||
]
|
||||
},
|
||||
"event_runtime_records": [
|
||||
{
|
||||
"record_id": 33,
|
||||
"service_count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"save_slice_id": "packed-event-locomotive-availability-save-slice",
|
||||
"source": {
|
||||
"description": "Tracked save-slice document proving recovered scalar locomotive availability descriptors import through embedded save-native locomotive catalog context.",
|
||||
"original_save_filename": "captured-locomotive-availability-save-slice.gms",
|
||||
"original_save_sha256": "locomotive-availability-save-slice-sample-sha256",
|
||||
"notes": [
|
||||
"tracked as JSON save-slice document rather than raw .smp",
|
||||
"uses embedded save-native locomotive catalog entries to prove standalone save-slice execution for lower-band and upper-band locomotive availability descriptors"
|
||||
]
|
||||
},
|
||||
"save_slice": {
|
||||
"file_extension_hint": "gms",
|
||||
"container_profile_family": "rt3-classic-save-container-v1",
|
||||
"mechanism_family": "classic-save-rehydrate-v1",
|
||||
"mechanism_confidence": "grounded",
|
||||
"trailer_family": null,
|
||||
"bridge_family": null,
|
||||
"profile": null,
|
||||
"candidate_availability_table": null,
|
||||
"named_locomotive_availability_table": null,
|
||||
"locomotive_catalog": {
|
||||
"source_kind": "save-direct-locomotive-row-run-ordinal-catalog",
|
||||
"semantic_family": "scenario-save-derived-locomotive-catalog",
|
||||
"entries_offset": 31864,
|
||||
"observed_entry_count": 2,
|
||||
"entries": [
|
||||
{ "locomotive_id": 10, "name": "Locomotive 10" },
|
||||
{ "locomotive_id": 112, "name": "Locomotive 112" }
|
||||
]
|
||||
},
|
||||
"special_conditions_table": null,
|
||||
"event_runtime_collection": {
|
||||
"source_kind": "packed-event-runtime-collection",
|
||||
"mechanism_family": "classic-save-rehydrate-v1",
|
||||
"mechanism_confidence": "grounded",
|
||||
"container_profile_family": "rt3-classic-save-container-v1",
|
||||
"metadata_tag_offset": 28928,
|
||||
"records_tag_offset": 29184,
|
||||
"close_tag_offset": 29696,
|
||||
"packed_state_version": 1001,
|
||||
"packed_state_version_hex": "0x000003e9",
|
||||
"live_id_bound": 33,
|
||||
"live_record_count": 1,
|
||||
"live_entry_ids": [33],
|
||||
"decoded_record_count": 1,
|
||||
"imported_runtime_record_count": 1,
|
||||
"records": [
|
||||
{
|
||||
"record_index": 0,
|
||||
"live_entry_id": 33,
|
||||
"payload_offset": 29186,
|
||||
"payload_len": 120,
|
||||
"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": 250,
|
||||
"descriptor_label": "Unknown Loco Available",
|
||||
"target_mask_bits": 8,
|
||||
"parameter_family": "locomotive_availability_scalar",
|
||||
"opcode": 3,
|
||||
"raw_scalar_value": 42,
|
||||
"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 Unknown Loco Available to 42",
|
||||
"recovered_locomotive_id": 10,
|
||||
"locomotive_name": null,
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"group_index": 0,
|
||||
"row_index": 1,
|
||||
"descriptor_id": 457,
|
||||
"descriptor_label": "Unknown Loco Available",
|
||||
"target_mask_bits": 8,
|
||||
"parameter_family": "locomotive_availability_scalar",
|
||||
"opcode": 3,
|
||||
"raw_scalar_value": 7,
|
||||
"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 Unknown Loco Available to 7",
|
||||
"recovered_locomotive_id": 112,
|
||||
"locomotive_name": null,
|
||||
"notes": []
|
||||
}
|
||||
],
|
||||
"decoded_actions": [],
|
||||
"executable_import_ready": false,
|
||||
"notes": [
|
||||
"scalar locomotive availability rows use save-native catalog context"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"notes": [
|
||||
"save-slice-backed locomotive availability effect sample"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"fixture_id": "packed-event-locomotive-cost-save-slice-fixture",
|
||||
"source": {
|
||||
"kind": "captured-runtime",
|
||||
"description": "Fixture backed by a tracked save-slice document that imports recovered scalar locomotive cost descriptors through embedded locomotive catalog context."
|
||||
},
|
||||
"state_save_slice_path": "packed-event-locomotive-cost-save-slice.json",
|
||||
"commands": [
|
||||
{
|
||||
"kind": "service_trigger_kind",
|
||||
"trigger_kind": 7
|
||||
}
|
||||
],
|
||||
"expected_summary": {
|
||||
"calendar_projection_is_placeholder": true,
|
||||
"locomotive_catalog_count": 2,
|
||||
"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_cost_count": 2,
|
||||
"total_event_record_service_count": 1,
|
||||
"total_trigger_dispatch_count": 1,
|
||||
"dirty_rerun_count": 0
|
||||
},
|
||||
"expected_state_fragment": {
|
||||
"metadata": {
|
||||
"save_slice.locomotive_catalog_source_kind": "save-direct-locomotive-row-run-ordinal-catalog"
|
||||
},
|
||||
"named_locomotive_cost": {
|
||||
"Locomotive 1": 250000,
|
||||
"Locomotive 101": 325000
|
||||
},
|
||||
"packed_event_collection": {
|
||||
"live_entry_ids": [41],
|
||||
"records": [
|
||||
{
|
||||
"decode_status": "parity_only",
|
||||
"import_outcome": "imported"
|
||||
}
|
||||
]
|
||||
},
|
||||
"event_runtime_records": [
|
||||
{
|
||||
"record_id": 41,
|
||||
"service_count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
150
fixtures/runtime/packed-event-locomotive-cost-save-slice.json
Normal file
150
fixtures/runtime/packed-event-locomotive-cost-save-slice.json
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"save_slice_id": "packed-event-locomotive-cost-save-slice",
|
||||
"source": {
|
||||
"description": "Tracked save-slice document proving recovered scalar locomotive cost descriptors import through embedded save-native locomotive catalog context.",
|
||||
"original_save_filename": "captured-locomotive-cost-save-slice.gms",
|
||||
"original_save_sha256": "locomotive-cost-save-slice-sample-sha256",
|
||||
"notes": [
|
||||
"tracked as JSON save-slice document rather than raw .smp",
|
||||
"uses embedded save-native locomotive catalog entries to prove standalone save-slice execution for lower-band and upper-band locomotive cost descriptors"
|
||||
]
|
||||
},
|
||||
"save_slice": {
|
||||
"file_extension_hint": "gms",
|
||||
"container_profile_family": "rt3-classic-save-container-v1",
|
||||
"mechanism_family": "classic-save-rehydrate-v1",
|
||||
"mechanism_confidence": "grounded",
|
||||
"trailer_family": null,
|
||||
"bridge_family": null,
|
||||
"profile": null,
|
||||
"candidate_availability_table": null,
|
||||
"named_locomotive_availability_table": null,
|
||||
"locomotive_catalog": {
|
||||
"source_kind": "save-direct-locomotive-row-run-ordinal-catalog",
|
||||
"semantic_family": "scenario-save-derived-locomotive-catalog",
|
||||
"entries_offset": 31864,
|
||||
"observed_entry_count": 2,
|
||||
"entries": [
|
||||
{ "locomotive_id": 1, "name": "Locomotive 1" },
|
||||
{ "locomotive_id": 101, "name": "Locomotive 101" }
|
||||
]
|
||||
},
|
||||
"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": 29952,
|
||||
"records_tag_offset": 30208,
|
||||
"close_tag_offset": 30976,
|
||||
"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": 1,
|
||||
"records": [
|
||||
{
|
||||
"record_index": 0,
|
||||
"live_entry_id": 41,
|
||||
"payload_offset": 30240,
|
||||
"payload_len": 120,
|
||||
"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": [],
|
||||
"negative_sentinel_scope": null,
|
||||
"grouped_effect_row_counts": [2, 0, 0, 0],
|
||||
"grouped_effect_rows": [
|
||||
{
|
||||
"group_index": 0,
|
||||
"row_index": 0,
|
||||
"descriptor_id": 352,
|
||||
"descriptor_label": "Locomotive 1 Cost",
|
||||
"target_mask_bits": 8,
|
||||
"parameter_family": "locomotive_cost_scalar",
|
||||
"opcode": 3,
|
||||
"raw_scalar_value": 250000,
|
||||
"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 Locomotive 1 Cost to 250000",
|
||||
"recovered_locomotive_id": 1,
|
||||
"locomotive_name": null,
|
||||
"notes": [
|
||||
"locomotive cost descriptor maps to live locomotive id 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group_index": 0,
|
||||
"row_index": 1,
|
||||
"descriptor_id": 475,
|
||||
"descriptor_label": "Locomotive 101 Cost",
|
||||
"target_mask_bits": 8,
|
||||
"parameter_family": "locomotive_cost_scalar",
|
||||
"opcode": 3,
|
||||
"raw_scalar_value": 325000,
|
||||
"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 Locomotive 101 Cost to 325000",
|
||||
"recovered_locomotive_id": 101,
|
||||
"locomotive_name": null,
|
||||
"notes": [
|
||||
"locomotive cost descriptor maps to live locomotive id 101"
|
||||
]
|
||||
}
|
||||
],
|
||||
"decoded_conditions": [],
|
||||
"decoded_actions": [
|
||||
{
|
||||
"kind": "set_named_locomotive_cost",
|
||||
"name": "Locomotive 1",
|
||||
"value": 250000
|
||||
},
|
||||
{
|
||||
"kind": "set_named_locomotive_cost",
|
||||
"name": "Locomotive 101",
|
||||
"value": 325000
|
||||
}
|
||||
],
|
||||
"executable_import_ready": false,
|
||||
"notes": [
|
||||
"scalar locomotive cost rows use save-native catalog context"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"notes": [
|
||||
"save-slice-backed locomotive cost effect sample"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -20,20 +20,21 @@
|
|||
"tick_slot": 1
|
||||
},
|
||||
"calendar_projection_is_placeholder": true,
|
||||
"locomotive_catalog_count": 10,
|
||||
"packed_event_collection_present": true,
|
||||
"packed_event_record_count": 2,
|
||||
"packed_event_decoded_record_count": 2,
|
||||
"packed_event_imported_runtime_record_count": 1,
|
||||
"packed_event_imported_runtime_record_count": 2,
|
||||
"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_locomotive_catalog_context_count": 0,
|
||||
"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": 0,
|
||||
"packed_event_blocked_structural_only_count": 0,
|
||||
"event_runtime_record_count": 1,
|
||||
"event_runtime_record_count": 2,
|
||||
"total_company_cash": 0
|
||||
},
|
||||
"expected_state_fragment": {
|
||||
|
|
@ -49,7 +50,7 @@
|
|||
{
|
||||
"decode_status": "parity_only",
|
||||
"payload_family": "real_packed_v1",
|
||||
"import_outcome": "blocked_missing_locomotive_catalog_context",
|
||||
"import_outcome": "imported",
|
||||
"grouped_effect_rows": [
|
||||
{
|
||||
"descriptor_id": 250,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"original_save_sha256": "parity-sample-sha256",
|
||||
"notes": [
|
||||
"tracked as JSON save-slice document rather than raw .smp",
|
||||
"preserves one recovered scalar locomotive-availability row that still needs overlay-backed catalog context and one semantically decoded imported row"
|
||||
"preserves one recovered scalar locomotive-availability row that now imports through save-native locomotive catalog context and one semantically decoded imported row"
|
||||
]
|
||||
},
|
||||
"save_slice": {
|
||||
|
|
@ -19,6 +19,24 @@
|
|||
"bridge_family": null,
|
||||
"profile": null,
|
||||
"candidate_availability_table": null,
|
||||
"locomotive_catalog": {
|
||||
"source_kind": "save-direct-locomotive-row-run-ordinal-catalog",
|
||||
"semantic_family": "scenario-save-derived-locomotive-catalog",
|
||||
"entries_offset": 31864,
|
||||
"observed_entry_count": 10,
|
||||
"entries": [
|
||||
{ "locomotive_id": 1, "name": "Locomotive 1" },
|
||||
{ "locomotive_id": 2, "name": "Locomotive 2" },
|
||||
{ "locomotive_id": 3, "name": "Locomotive 3" },
|
||||
{ "locomotive_id": 4, "name": "Locomotive 4" },
|
||||
{ "locomotive_id": 5, "name": "Locomotive 5" },
|
||||
{ "locomotive_id": 6, "name": "Locomotive 6" },
|
||||
{ "locomotive_id": 7, "name": "Locomotive 7" },
|
||||
{ "locomotive_id": 8, "name": "Locomotive 8" },
|
||||
{ "locomotive_id": 9, "name": "Locomotive 9" },
|
||||
{ "locomotive_id": 10, "name": "Locomotive 10" }
|
||||
]
|
||||
},
|
||||
"special_conditions_table": null,
|
||||
"event_runtime_collection": {
|
||||
"source_kind": "packed-event-runtime-collection",
|
||||
|
|
@ -34,7 +52,7 @@
|
|||
"live_record_count": 2,
|
||||
"live_entry_ids": [3, 5],
|
||||
"decoded_record_count": 2,
|
||||
"imported_runtime_record_count": 1,
|
||||
"imported_runtime_record_count": 2,
|
||||
"records": [
|
||||
{
|
||||
"record_index": 0,
|
||||
|
|
@ -83,7 +101,7 @@
|
|||
"recovered_locomotive_id": 10,
|
||||
"locomotive_name": null,
|
||||
"notes": [
|
||||
"recovered locomotive availability descriptor family now supports scalar payloads, but standalone save-slice import still needs overlay-backed locomotive catalog context"
|
||||
"recovered locomotive availability descriptor family now imports through save-native locomotive catalog context"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
@ -91,7 +109,7 @@
|
|||
"executable_import_ready": false,
|
||||
"notes": [
|
||||
"decoded from grounded real 0x4e9a row framing",
|
||||
"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"
|
||||
"recovered locomotives-page descriptor band is now checked in, and this scalar family now imports through named locomotive availability overrides when the save slice carries locomotive catalog context"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
},
|
||||
"calendar_projection_source": "base-snapshot-preserved",
|
||||
"calendar_projection_is_placeholder": false,
|
||||
"world_flag_count": 8,
|
||||
"world_flag_count": 9,
|
||||
"company_count": 1,
|
||||
"packed_event_collection_present": true,
|
||||
"packed_event_record_count": 2,
|
||||
|
|
|
|||
|
|
@ -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 mixes executable scalar world descriptors with one remaining missing-catalog frontier."
|
||||
"description": "Fixture backed by a tracked save-slice document that mixes executable scalar world descriptors with one remaining intentionally unsupported scalar frontier."
|
||||
},
|
||||
"state_save_slice_path": "packed-event-world-scalar-band-parity-save-slice.json",
|
||||
"commands": [
|
||||
|
|
@ -26,12 +26,12 @@
|
|||
"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,
|
||||
"packed_event_blocked_missing_locomotive_catalog_context_count": 0,
|
||||
"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": 0,
|
||||
"packed_event_blocked_unmapped_world_descriptor_count": 1,
|
||||
"packed_event_blocked_structural_only_count": 0,
|
||||
"event_runtime_record_count": 2,
|
||||
"cargo_production_override_count": 0,
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
{
|
||||
"decode_status": "parity_only",
|
||||
"payload_family": "real_packed_v1",
|
||||
"import_outcome": "blocked_missing_locomotive_catalog_context",
|
||||
"import_outcome": "blocked_unmapped_world_descriptor",
|
||||
"grouped_effect_rows": [
|
||||
{
|
||||
"descriptor_id": 352,
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
"target_mask_bits": 8,
|
||||
"parameter_family": "locomotive_cost_scalar",
|
||||
"semantic_family": "scalar_assignment",
|
||||
"semantic_preview": "Set Locomotive 1 Cost to 250000",
|
||||
"semantic_preview": "Set Locomotive 1 Cost to -250000",
|
||||
"recovered_locomotive_id": 1,
|
||||
"row_shape": "scalar_assignment"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"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 with one remaining missing-catalog frontier"
|
||||
"covers recovered cargo production, locomotive cost, and territory access cost families with one remaining intentionally unsupported scalar frontier"
|
||||
]
|
||||
},
|
||||
"save_slice": {
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"live_record_count": 3,
|
||||
"live_entry_ids": [11, 12, 13],
|
||||
"decoded_record_count": 3,
|
||||
"imported_runtime_record_count": 0,
|
||||
"imported_runtime_record_count": 2,
|
||||
"records": [
|
||||
{
|
||||
"record_index": 0,
|
||||
|
|
@ -126,7 +126,7 @@
|
|||
"target_mask_bits": 8,
|
||||
"parameter_family": "locomotive_cost_scalar",
|
||||
"opcode": 3,
|
||||
"raw_scalar_value": 250000,
|
||||
"raw_scalar_value": -250000,
|
||||
"value_byte_0x09": 0,
|
||||
"value_dword_0x0d": 0,
|
||||
"value_byte_0x11": 0,
|
||||
|
|
@ -135,7 +135,7 @@
|
|||
"value_word_0x16": 0,
|
||||
"row_shape": "scalar_assignment",
|
||||
"semantic_family": "scalar_assignment",
|
||||
"semantic_preview": "Set Locomotive 1 Cost to 250000",
|
||||
"semantic_preview": "Set Locomotive 1 Cost to -250000",
|
||||
"recovered_locomotive_id": 1,
|
||||
"locomotive_name": null,
|
||||
"notes": [
|
||||
|
|
@ -146,7 +146,7 @@
|
|||
"decoded_actions": [],
|
||||
"executable_import_ready": false,
|
||||
"notes": [
|
||||
"recovered locomotive cost metadata is now checked in, but scalar rows still need overlay-backed locomotive catalog context to import"
|
||||
"negative locomotive cost payload remains intentionally unsupported even though the descriptor family is recovered"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue