Add named locomotive availability runtime surface
This commit is contained in:
parent
c04c19ac82
commit
8c7ff335cb
16 changed files with 542 additions and 13 deletions
14
README.md
14
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()],
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -150,6 +150,10 @@ pub struct ExpectedRuntimeSummary {
|
|||
#[serde(default)]
|
||||
pub zero_candidate_availability_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub named_locomotive_availability_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub zero_named_locomotive_availability_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub special_condition_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub enabled_special_condition_count: Option<usize>,
|
||||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ struct SaveSliceProjection {
|
|||
packed_event_collection: Option<RuntimePackedEventCollectionSummary>,
|
||||
event_runtime_records: Vec<RuntimeEventRecord>,
|
||||
candidate_availability: BTreeMap<String, u32>,
|
||||
named_locomotive_availability: BTreeMap<String, u32>,
|
||||
special_conditions: BTreeMap<String, u32>,
|
||||
}
|
||||
|
||||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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<String, u32>,
|
||||
#[serde(default)]
|
||||
pub named_locomotive_availability: BTreeMap<String, u32>,
|
||||
#[serde(default)]
|
||||
pub special_conditions: BTreeMap<String, u32>,
|
||||
#[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(),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1503,6 +1503,22 @@ pub struct SmpLoadedCandidateAvailabilityTable {
|
|||
pub entries: Vec<SmpRt3105SaveNameTableEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpLoadedNamedLocomotiveAvailabilityTable {
|
||||
pub source_kind: String,
|
||||
pub semantic_family: String,
|
||||
#[serde(default)]
|
||||
pub header_offset: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub entries_offset: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub entries_end_offset: Option<usize>,
|
||||
pub observed_entry_count: usize,
|
||||
pub zero_availability_count: usize,
|
||||
pub zero_availability_names: Vec<String>,
|
||||
pub entries: Vec<SmpRt3105SaveNameTableEntry>,
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
pub profile: Option<SmpLoadedProfile>,
|
||||
pub candidate_availability_table: Option<SmpLoadedCandidateAvailabilityTable>,
|
||||
pub named_locomotive_availability_table: Option<SmpLoadedNamedLocomotiveAvailabilityTable>,
|
||||
pub special_conditions_table: Option<SmpLoadedSpecialConditionsTable>,
|
||||
pub event_runtime_collection: Option<SmpLoadedEventRuntimeCollectionSummary>,
|
||||
pub notes: Vec<String>,
|
||||
|
|
@ -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<u32> {
|
||||
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<RealGroupedEffectDescriptorMetadata> {
|
||||
|
|
@ -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 { .. }
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
|
|
@ -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(),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue