Import locomotive availability descriptors with overlay context
This commit is contained in:
parent
8c7ff335cb
commit
87108f357b
21 changed files with 1154 additions and 27 deletions
21
README.md
21
README.md
|
|
@ -51,15 +51,18 @@ recovered locomotives-page `real_packed_v1` record that lands in the explicit
|
||||||
`blocked_unmapped_world_descriptor` bucket. The next recovered descriptor band is now partially
|
`blocked_unmapped_world_descriptor` bucket. The next recovered descriptor band is now partially
|
||||||
executable too: descriptors `454..456` (`All Steam/Diesel/Electric Locos Avail.`) now lower
|
executable too: descriptors `454..456` (`All Steam/Diesel/Electric Locos Avail.`) now lower
|
||||||
through checked-in metadata into keyed `world_flags`, while the wider locomotive availability/cost
|
through checked-in metadata into keyed `world_flags`, while the wider locomotive availability/cost
|
||||||
scalar bands remain recovered-but-parity-only until per-locomotive identity is grounded. The
|
scalar bands are now split more cleanly: the boolean `0/1` availability subset can import through
|
||||||
runtime now carries the save-owned named locomotive availability table directly too: checked-in
|
an overlay-backed `RuntimeState.locomotive_catalog` into
|
||||||
save-slice documents can populate `RuntimeState.named_locomotive_availability`, and imported
|
`RuntimeState.named_locomotive_availability`, while non-boolean availability payloads plus the
|
||||||
runtime effects can mutate that map through the ordinary event-service path without needing full
|
locomotive-cost/cargo-production/territory-access-cost families remain recovered-but-parity-only.
|
||||||
Trainbuy or live-locomotive parity. Explicit unmapped world-condition and world-descriptor
|
The runtime still carries the save-owned named locomotive availability table directly too:
|
||||||
frontier buckets still remain where current checked-in metadata stops. Shell purchase-flow and
|
checked-in save-slice documents can populate `RuntimeState.named_locomotive_availability`, and
|
||||||
selected-profile parity remain out of scope. Mixed supported/unsupported real rows still stay
|
imported runtime effects can mutate that map through the ordinary event-service path without
|
||||||
parity-only. The PE32 hook remains useful as capture and integration tooling, but it is no longer
|
needing full Trainbuy or live-locomotive parity. Explicit unmapped world-condition and
|
||||||
the main execution milestone.
|
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
|
## Project Docs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4457,6 +4457,12 @@ mod tests {
|
||||||
let named_locomotive_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
let named_locomotive_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||||
"../../fixtures/runtime/packed-event-named-locomotive-availability-save-slice-fixture.json",
|
"../../fixtures/runtime/packed-event-named-locomotive-availability-save-slice-fixture.json",
|
||||||
);
|
);
|
||||||
|
let missing_catalog_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||||
|
"../../fixtures/runtime/packed-event-locomotive-availability-missing-catalog-save-slice-fixture.json",
|
||||||
|
);
|
||||||
|
let overlay_locomotive_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||||
|
"../../fixtures/runtime/packed-event-locomotive-availability-overlay-fixture.json",
|
||||||
|
);
|
||||||
|
|
||||||
run_runtime_summarize_fixture(&parity_fixture)
|
run_runtime_summarize_fixture(&parity_fixture)
|
||||||
.expect("save-slice-backed parity fixture should summarize");
|
.expect("save-slice-backed parity fixture should summarize");
|
||||||
|
|
@ -4476,6 +4482,11 @@ mod tests {
|
||||||
.expect("overlay-backed mixed real-row fixture should summarize");
|
.expect("overlay-backed mixed real-row fixture should summarize");
|
||||||
run_runtime_summarize_fixture(&named_locomotive_fixture)
|
run_runtime_summarize_fixture(&named_locomotive_fixture)
|
||||||
.expect("save-slice-backed named locomotive availability fixture should summarize");
|
.expect("save-slice-backed named locomotive availability fixture should summarize");
|
||||||
|
run_runtime_summarize_fixture(&missing_catalog_fixture).expect(
|
||||||
|
"save-slice-backed locomotive availability missing-catalog fixture should summarize",
|
||||||
|
);
|
||||||
|
run_runtime_summarize_fixture(&overlay_locomotive_fixture)
|
||||||
|
.expect("overlay-backed locomotive availability fixture should summarize");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -348,6 +349,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,8 @@ pub struct ExpectedRuntimeSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub retired_train_count: Option<usize>,
|
pub retired_train_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub locomotive_catalog_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
pub territory_count: Option<usize>,
|
pub territory_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub company_territory_track_count: Option<usize>,
|
pub company_territory_track_count: Option<usize>,
|
||||||
|
|
@ -136,6 +138,8 @@ pub struct ExpectedRuntimeSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_blocked_missing_train_territory_context_count: Option<usize>,
|
pub packed_event_blocked_missing_train_territory_context_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub packed_event_blocked_missing_locomotive_catalog_context_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
pub packed_event_blocked_confiscation_variant_count: Option<usize>,
|
pub packed_event_blocked_confiscation_variant_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_blocked_retire_train_variant_count: Option<usize>,
|
pub packed_event_blocked_retire_train_variant_count: Option<usize>,
|
||||||
|
|
@ -443,6 +447,14 @@ impl ExpectedRuntimeSummary {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(count) = self.locomotive_catalog_count {
|
||||||
|
if actual.locomotive_catalog_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"locomotive_catalog_count mismatch: expected {count}, got {}",
|
||||||
|
actual.locomotive_catalog_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(count) = self.territory_count {
|
if let Some(count) = self.territory_count {
|
||||||
if actual.territory_count != count {
|
if actual.territory_count != count {
|
||||||
mismatches.push(format!(
|
mismatches.push(format!(
|
||||||
|
|
@ -683,6 +695,14 @@ impl ExpectedRuntimeSummary {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(count) = self.packed_event_blocked_missing_locomotive_catalog_context_count {
|
||||||
|
if actual.packed_event_blocked_missing_locomotive_catalog_context_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_blocked_missing_locomotive_catalog_context_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_blocked_missing_locomotive_catalog_context_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(count) = self.packed_event_blocked_confiscation_variant_count {
|
if let Some(count) = self.packed_event_blocked_confiscation_variant_count {
|
||||||
if actual.packed_event_blocked_confiscation_variant_count != count {
|
if actual.packed_event_blocked_confiscation_variant_count != count {
|
||||||
mismatches.push(format!(
|
mismatches.push(format!(
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ struct ImportRuntimeContext {
|
||||||
territory_name_to_id: BTreeMap<String, u32>,
|
territory_name_to_id: BTreeMap<String, u32>,
|
||||||
has_train_context: bool,
|
has_train_context: bool,
|
||||||
has_train_territory_context: bool,
|
has_train_territory_context: bool,
|
||||||
|
locomotive_catalog_names_by_id: BTreeMap<u32, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
@ -136,6 +137,7 @@ enum ImportBlocker {
|
||||||
UnmappedWorldCondition,
|
UnmappedWorldCondition,
|
||||||
MissingTrainContext,
|
MissingTrainContext,
|
||||||
MissingTrainTerritoryContext,
|
MissingTrainTerritoryContext,
|
||||||
|
MissingLocomotiveCatalogContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImportRuntimeContext {
|
impl ImportRuntimeContext {
|
||||||
|
|
@ -152,6 +154,7 @@ impl ImportRuntimeContext {
|
||||||
territory_name_to_id: BTreeMap::new(),
|
territory_name_to_id: BTreeMap::new(),
|
||||||
has_train_context: false,
|
has_train_context: false,
|
||||||
has_train_territory_context: false,
|
has_train_territory_context: false,
|
||||||
|
locomotive_catalog_names_by_id: BTreeMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,6 +202,11 @@ impl ImportRuntimeContext {
|
||||||
.trains
|
.trains
|
||||||
.iter()
|
.iter()
|
||||||
.any(|train| train.territory_id.is_some()),
|
.any(|train| train.territory_id.is_some()),
|
||||||
|
locomotive_catalog_names_by_id: state
|
||||||
|
.locomotive_catalog
|
||||||
|
.iter()
|
||||||
|
.map(|entry| (entry.locomotive_id, entry.name.clone()))
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -233,6 +241,7 @@ pub fn project_save_slice_to_runtime_state_import(
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -289,6 +298,7 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
|
||||||
players: base_state.players.clone(),
|
players: base_state.players.clone(),
|
||||||
selected_player_id: base_state.selected_player_id,
|
selected_player_id: base_state.selected_player_id,
|
||||||
trains: base_state.trains.clone(),
|
trains: base_state.trains.clone(),
|
||||||
|
locomotive_catalog: base_state.locomotive_catalog.clone(),
|
||||||
territories: base_state.territories.clone(),
|
territories: base_state.territories.clone(),
|
||||||
company_territory_track_piece_counts: base_state
|
company_territory_track_piece_counts: base_state
|
||||||
.company_territory_track_piece_counts
|
.company_territory_track_piece_counts
|
||||||
|
|
@ -839,6 +849,7 @@ fn runtime_packed_event_grouped_effect_row_summary_from_smp(
|
||||||
row_shape: row.row_shape.clone(),
|
row_shape: row.row_shape.clone(),
|
||||||
semantic_family: row.semantic_family.clone(),
|
semantic_family: row.semantic_family.clone(),
|
||||||
semantic_preview: row.semantic_preview.clone(),
|
semantic_preview: row.semantic_preview.clone(),
|
||||||
|
recovered_locomotive_id: row.recovered_locomotive_id,
|
||||||
locomotive_name: row.locomotive_name.clone(),
|
locomotive_name: row.locomotive_name.clone(),
|
||||||
notes: row.notes.clone(),
|
notes: row.notes.clone(),
|
||||||
}
|
}
|
||||||
|
|
@ -851,11 +862,9 @@ fn smp_packed_record_to_runtime_event_record(
|
||||||
if record.decode_status == "unsupported_framing" {
|
if record.decode_status == "unsupported_framing" {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if record.payload_family == "real_packed_v1" {
|
if record.payload_family == "real_packed_v1" && record.compact_control.is_none() {
|
||||||
if record.compact_control.is_none() || !record.executable_import_ready {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let lowered_conditions = match lowered_record_decoded_conditions(record, company_context) {
|
let lowered_conditions = match lowered_record_decoded_conditions(record, company_context) {
|
||||||
Ok(conditions) => conditions,
|
Ok(conditions) => conditions,
|
||||||
|
|
@ -934,8 +943,14 @@ fn lowered_record_decoded_actions(
|
||||||
|
|
||||||
let lowered_company_target = lowered_condition_true_company_target(record)?;
|
let lowered_company_target = lowered_condition_true_company_target(record)?;
|
||||||
let lowered_player_target = lowered_condition_true_player_target(record)?;
|
let lowered_player_target = lowered_condition_true_player_target(record)?;
|
||||||
record
|
let base_effects = if record.payload_family != "real_packed_v1"
|
||||||
.decoded_actions
|
|| record.decoded_actions.len() == record.grouped_effect_rows.len()
|
||||||
|
{
|
||||||
|
record.decoded_actions.clone()
|
||||||
|
} else {
|
||||||
|
lower_contextual_real_grouped_effects(record, company_context)?
|
||||||
|
};
|
||||||
|
base_effects
|
||||||
.iter()
|
.iter()
|
||||||
.map(|effect| {
|
.map(|effect| {
|
||||||
lower_condition_targets_in_effect(
|
lower_condition_targets_in_effect(
|
||||||
|
|
@ -947,6 +962,66 @@ fn lowered_record_decoded_actions(
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn lower_contextual_real_grouped_effects(
|
||||||
|
record: &SmpLoadedPackedEventRecordSummary,
|
||||||
|
company_context: &ImportRuntimeContext,
|
||||||
|
) -> Result<Vec<RuntimeEffect>, ImportBlocker> {
|
||||||
|
if record.payload_family != "real_packed_v1" || record.compact_control.is_none() {
|
||||||
|
return Err(ImportBlocker::UnmappedWorldCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut effects = Vec::with_capacity(record.grouped_effect_rows.len());
|
||||||
|
for row in &record.grouped_effect_rows {
|
||||||
|
if let Some(effect) = lower_contextual_locomotive_availability_effect(row, company_context)?
|
||||||
|
{
|
||||||
|
effects.push(effect);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return Err(if real_grouped_row_is_world_state_family(row) {
|
||||||
|
ImportBlocker::UnmappedWorldCondition
|
||||||
|
} else {
|
||||||
|
ImportBlocker::UnmappedOrdinaryCondition
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if effects.is_empty() {
|
||||||
|
return Err(ImportBlocker::UnmappedWorldCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(effects)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lower_contextual_locomotive_availability_effect(
|
||||||
|
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
||||||
|
company_context: &ImportRuntimeContext,
|
||||||
|
) -> Result<Option<RuntimeEffect>, ImportBlocker> {
|
||||||
|
if row.parameter_family.as_deref() != Some("locomotive_availability_scalar") {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
if row.row_shape != "scalar_assignment" {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let value = match row.raw_scalar_value {
|
||||||
|
0 => false,
|
||||||
|
1 => true,
|
||||||
|
_ => return Ok(None),
|
||||||
|
};
|
||||||
|
let Some(locomotive_id) = row.recovered_locomotive_id else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let Some(name) = company_context
|
||||||
|
.locomotive_catalog_names_by_id
|
||||||
|
.get(&locomotive_id)
|
||||||
|
.cloned()
|
||||||
|
else {
|
||||||
|
return Err(ImportBlocker::MissingLocomotiveCatalogContext);
|
||||||
|
};
|
||||||
|
Ok(Some(RuntimeEffect::SetNamedLocomotiveAvailability {
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
fn packed_record_condition_scope_import_blocker(
|
fn packed_record_condition_scope_import_blocker(
|
||||||
record: &SmpLoadedPackedEventRecordSummary,
|
record: &SmpLoadedPackedEventRecordSummary,
|
||||||
company_context: &ImportRuntimeContext,
|
company_context: &ImportRuntimeContext,
|
||||||
|
|
@ -1781,6 +1856,9 @@ fn company_target_import_error_message(
|
||||||
Some(ImportBlocker::MissingTrainTerritoryContext) => {
|
Some(ImportBlocker::MissingTrainTerritoryContext) => {
|
||||||
"packed train effect requires runtime train territory context".to_string()
|
"packed train effect requires runtime train territory context".to_string()
|
||||||
}
|
}
|
||||||
|
Some(ImportBlocker::MissingLocomotiveCatalogContext) => {
|
||||||
|
"packed locomotive availability row requires locomotive catalog context".to_string()
|
||||||
|
}
|
||||||
Some(ImportBlocker::MissingPlayerContext)
|
Some(ImportBlocker::MissingPlayerContext)
|
||||||
| Some(ImportBlocker::MissingPlayerSelectionContext)
|
| Some(ImportBlocker::MissingPlayerSelectionContext)
|
||||||
| Some(ImportBlocker::MissingPlayerRoleContext)
|
| Some(ImportBlocker::MissingPlayerRoleContext)
|
||||||
|
|
@ -1871,6 +1949,11 @@ fn determine_packed_event_import_outcome(
|
||||||
return "blocked_missing_compact_control".to_string();
|
return "blocked_missing_compact_control".to_string();
|
||||||
}
|
}
|
||||||
if !record.executable_import_ready {
|
if !record.executable_import_ready {
|
||||||
|
if let Err(blocker) = lowered_record_decoded_actions(record, company_context) {
|
||||||
|
if blocker == ImportBlocker::MissingLocomotiveCatalogContext {
|
||||||
|
return company_target_import_outcome(blocker).to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
if record
|
if record
|
||||||
.grouped_effect_rows
|
.grouped_effect_rows
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -2085,6 +2168,9 @@ fn company_target_import_outcome(blocker: ImportBlocker) -> &'static str {
|
||||||
ImportBlocker::UnmappedWorldCondition => "blocked_unmapped_world_condition",
|
ImportBlocker::UnmappedWorldCondition => "blocked_unmapped_world_condition",
|
||||||
ImportBlocker::MissingTrainContext => "blocked_missing_train_context",
|
ImportBlocker::MissingTrainContext => "blocked_missing_train_context",
|
||||||
ImportBlocker::MissingTrainTerritoryContext => "blocked_missing_train_territory_context",
|
ImportBlocker::MissingTrainTerritoryContext => "blocked_missing_train_territory_context",
|
||||||
|
ImportBlocker::MissingLocomotiveCatalogContext => {
|
||||||
|
"blocked_missing_locomotive_catalog_context"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2571,6 +2657,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -2719,6 +2806,7 @@ mod tests {
|
||||||
row_shape: "multivalue_scalar".to_string(),
|
row_shape: "multivalue_scalar".to_string(),
|
||||||
semantic_family: Some("multivalue_scalar".to_string()),
|
semantic_family: Some("multivalue_scalar".to_string()),
|
||||||
semantic_preview: Some("Set Company Cash to 7 with aux [2, 3, 24, 36]".to_string()),
|
semantic_preview: Some("Set Company Cash to 7 with aux [2, 3, 24, 36]".to_string()),
|
||||||
|
recovered_locomotive_id: None,
|
||||||
locomotive_name: Some("Mikado".to_string()),
|
locomotive_name: Some("Mikado".to_string()),
|
||||||
notes: vec!["grouped effect row carries locomotive-name side string".to_string()],
|
notes: vec!["grouped effect row carries locomotive-name side string".to_string()],
|
||||||
}]
|
}]
|
||||||
|
|
@ -2748,6 +2836,7 @@ mod tests {
|
||||||
"Set Deactivate Company to {}",
|
"Set Deactivate Company to {}",
|
||||||
if enabled { "TRUE" } else { "FALSE" }
|
if enabled { "TRUE" } else { "FALSE" }
|
||||||
)),
|
)),
|
||||||
|
recovered_locomotive_id: None,
|
||||||
locomotive_name: None,
|
locomotive_name: None,
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
}
|
}
|
||||||
|
|
@ -2772,6 +2861,7 @@ mod tests {
|
||||||
row_shape: "scalar_assignment".to_string(),
|
row_shape: "scalar_assignment".to_string(),
|
||||||
semantic_family: Some("scalar_assignment".to_string()),
|
semantic_family: Some("scalar_assignment".to_string()),
|
||||||
semantic_preview: Some(format!("Set Company Track Pieces Buildable to {value}")),
|
semantic_preview: Some(format!("Set Company Track Pieces Buildable to {value}")),
|
||||||
|
recovered_locomotive_id: None,
|
||||||
locomotive_name: None,
|
locomotive_name: None,
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
}
|
}
|
||||||
|
|
@ -2801,6 +2891,7 @@ mod tests {
|
||||||
"Set Deactivate Player to {}",
|
"Set Deactivate Player to {}",
|
||||||
if enabled { "TRUE" } else { "FALSE" }
|
if enabled { "TRUE" } else { "FALSE" }
|
||||||
)),
|
)),
|
||||||
|
recovered_locomotive_id: None,
|
||||||
locomotive_name: None,
|
locomotive_name: None,
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
}
|
}
|
||||||
|
|
@ -2831,6 +2922,7 @@ mod tests {
|
||||||
"Set Territory - Allow All to {}",
|
"Set Territory - Allow All to {}",
|
||||||
if enabled { "TRUE" } else { "FALSE" }
|
if enabled { "TRUE" } else { "FALSE" }
|
||||||
)),
|
)),
|
||||||
|
recovered_locomotive_id: None,
|
||||||
locomotive_name: None,
|
locomotive_name: None,
|
||||||
notes,
|
notes,
|
||||||
}
|
}
|
||||||
|
|
@ -2855,6 +2947,7 @@ mod tests {
|
||||||
row_shape: "scalar_assignment".to_string(),
|
row_shape: "scalar_assignment".to_string(),
|
||||||
semantic_family: Some("scalar_assignment".to_string()),
|
semantic_family: Some("scalar_assignment".to_string()),
|
||||||
semantic_preview: Some(format!("Set Economic Status to {value}")),
|
semantic_preview: Some(format!("Set Economic Status to {value}")),
|
||||||
|
recovered_locomotive_id: None,
|
||||||
locomotive_name: None,
|
locomotive_name: None,
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
}
|
}
|
||||||
|
|
@ -2881,6 +2974,7 @@ mod tests {
|
||||||
row_shape: "scalar_assignment".to_string(),
|
row_shape: "scalar_assignment".to_string(),
|
||||||
semantic_family: Some("scalar_assignment".to_string()),
|
semantic_family: Some("scalar_assignment".to_string()),
|
||||||
semantic_preview: Some(format!("Set Limited Track Building Amount to {value}")),
|
semantic_preview: Some(format!("Set Limited Track Building Amount to {value}")),
|
||||||
|
recovered_locomotive_id: None,
|
||||||
locomotive_name: None,
|
locomotive_name: None,
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
}
|
}
|
||||||
|
|
@ -2907,6 +3001,7 @@ mod tests {
|
||||||
row_shape: "scalar_assignment".to_string(),
|
row_shape: "scalar_assignment".to_string(),
|
||||||
semantic_family: Some("scalar_assignment".to_string()),
|
semantic_family: Some("scalar_assignment".to_string()),
|
||||||
semantic_preview: Some(format!("Set Use Wartime Cargos to {value}")),
|
semantic_preview: Some(format!("Set Use Wartime Cargos to {value}")),
|
||||||
|
recovered_locomotive_id: None,
|
||||||
locomotive_name: None,
|
locomotive_name: None,
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
}
|
}
|
||||||
|
|
@ -2933,6 +3028,71 @@ mod tests {
|
||||||
row_shape: "scalar_assignment".to_string(),
|
row_shape: "scalar_assignment".to_string(),
|
||||||
semantic_family: Some("scalar_assignment".to_string()),
|
semantic_family: Some("scalar_assignment".to_string()),
|
||||||
semantic_preview: Some(format!("Set Turbo Diesel Availability to {value}")),
|
semantic_preview: Some(format!("Set Turbo Diesel Availability to {value}")),
|
||||||
|
recovered_locomotive_id: None,
|
||||||
|
locomotive_name: None,
|
||||||
|
notes: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn real_locomotive_availability_row(
|
||||||
|
descriptor_id: u32,
|
||||||
|
value: i32,
|
||||||
|
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
|
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
|
group_index: 0,
|
||||||
|
row_index: 0,
|
||||||
|
descriptor_id,
|
||||||
|
descriptor_label: Some("Unknown Loco Available".to_string()),
|
||||||
|
target_mask_bits: Some(0x08),
|
||||||
|
parameter_family: Some("locomotive_availability_scalar".to_string()),
|
||||||
|
opcode: 3,
|
||||||
|
raw_scalar_value: value,
|
||||||
|
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".to_string(),
|
||||||
|
semantic_family: Some("scalar_assignment".to_string()),
|
||||||
|
semantic_preview: Some(format!("Set Unknown Loco Available to {value}")),
|
||||||
|
recovered_locomotive_id: match descriptor_id {
|
||||||
|
241..=351 => Some(descriptor_id - 240),
|
||||||
|
457..=474 => Some(descriptor_id - 345),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
locomotive_name: None,
|
||||||
|
notes: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn real_locomotive_cost_row(
|
||||||
|
descriptor_id: u32,
|
||||||
|
value: i32,
|
||||||
|
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
|
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
|
group_index: 0,
|
||||||
|
row_index: 0,
|
||||||
|
descriptor_id,
|
||||||
|
descriptor_label: Some("Unknown Loco Cost".to_string()),
|
||||||
|
target_mask_bits: Some(0x08),
|
||||||
|
parameter_family: Some("locomotive_cost_scalar".to_string()),
|
||||||
|
opcode: 3,
|
||||||
|
raw_scalar_value: value,
|
||||||
|
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".to_string(),
|
||||||
|
semantic_family: Some("scalar_assignment".to_string()),
|
||||||
|
semantic_preview: Some(format!("Set Unknown Loco Cost to {value}")),
|
||||||
|
recovered_locomotive_id: match descriptor_id {
|
||||||
|
352..=451 => Some(descriptor_id - 351),
|
||||||
|
475..=500 => Some(descriptor_id - 374),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
locomotive_name: None,
|
locomotive_name: None,
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
}
|
}
|
||||||
|
|
@ -2964,6 +3124,7 @@ mod tests {
|
||||||
"Set {label} to {}",
|
"Set {label} to {}",
|
||||||
if enabled { "TRUE" } else { "FALSE" }
|
if enabled { "TRUE" } else { "FALSE" }
|
||||||
)),
|
)),
|
||||||
|
recovered_locomotive_id: None,
|
||||||
locomotive_name: None,
|
locomotive_name: None,
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
}
|
}
|
||||||
|
|
@ -2993,6 +3154,7 @@ mod tests {
|
||||||
"Set Confiscate All to {}",
|
"Set Confiscate All to {}",
|
||||||
if enabled { "TRUE" } else { "FALSE" }
|
if enabled { "TRUE" } else { "FALSE" }
|
||||||
)),
|
)),
|
||||||
|
recovered_locomotive_id: None,
|
||||||
locomotive_name: None,
|
locomotive_name: None,
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
}
|
}
|
||||||
|
|
@ -3024,6 +3186,7 @@ mod tests {
|
||||||
"Set Retire Train to {}",
|
"Set Retire Train to {}",
|
||||||
if enabled { "TRUE" } else { "FALSE" }
|
if enabled { "TRUE" } else { "FALSE" }
|
||||||
)),
|
)),
|
||||||
|
recovered_locomotive_id: None,
|
||||||
locomotive_name: locomotive_name.map(ToString::to_string),
|
locomotive_name: locomotive_name.map(ToString::to_string),
|
||||||
notes,
|
notes,
|
||||||
}
|
}
|
||||||
|
|
@ -3048,6 +3211,7 @@ mod tests {
|
||||||
row_shape: "bool_toggle".to_string(),
|
row_shape: "bool_toggle".to_string(),
|
||||||
semantic_family: Some("bool_toggle".to_string()),
|
semantic_family: Some("bool_toggle".to_string()),
|
||||||
semantic_preview: Some("Set Confiscate All to FALSE".to_string()),
|
semantic_preview: Some("Set Confiscate All to FALSE".to_string()),
|
||||||
|
recovered_locomotive_id: None,
|
||||||
locomotive_name: None,
|
locomotive_name: None,
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
}
|
}
|
||||||
|
|
@ -4667,6 +4831,7 @@ mod tests {
|
||||||
row_shape: "scalar_assignment".to_string(),
|
row_shape: "scalar_assignment".to_string(),
|
||||||
semantic_family: Some("scalar_assignment".to_string()),
|
semantic_family: Some("scalar_assignment".to_string()),
|
||||||
semantic_preview: Some("Set Unknown Loco Available to 42".to_string()),
|
semantic_preview: Some("Set Unknown Loco Available to 42".to_string()),
|
||||||
|
recovered_locomotive_id: Some(10),
|
||||||
locomotive_name: None,
|
locomotive_name: None,
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
}],
|
}],
|
||||||
|
|
@ -4701,6 +4866,295 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn blocks_boolean_locomotive_availability_rows_without_catalog_context() {
|
||||||
|
let save_slice = SmpLoadedSaveSlice {
|
||||||
|
file_extension_hint: Some("gms".to_string()),
|
||||||
|
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||||
|
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: 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: 32,
|
||||||
|
live_record_count: 1,
|
||||||
|
live_entry_ids: vec![32],
|
||||||
|
decoded_record_count: 1,
|
||||||
|
imported_runtime_record_count: 0,
|
||||||
|
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||||
|
record_index: 0,
|
||||||
|
live_entry_id: 32,
|
||||||
|
payload_offset: Some(0x7202),
|
||||||
|
payload_len: Some(96),
|
||||||
|
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![1, 0, 0, 0],
|
||||||
|
grouped_effect_rows: vec![real_locomotive_availability_row(250, 1)],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
|
decoded_actions: vec![],
|
||||||
|
executable_import_ready: false,
|
||||||
|
notes: vec![
|
||||||
|
"boolean locomotive availability row still needs catalog context"
|
||||||
|
.to_string(),
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
notes: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let import = project_save_slice_to_runtime_state_import(
|
||||||
|
&save_slice,
|
||||||
|
"packed-events-locomotive-availability-missing-catalog",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("save slice should project");
|
||||||
|
|
||||||
|
assert!(import.state.event_runtime_records.is_empty());
|
||||||
|
assert_eq!(
|
||||||
|
import
|
||||||
|
.state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
|
||||||
|
Some("blocked_missing_locomotive_catalog_context")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn overlays_boolean_locomotive_availability_rows_into_named_availability_effects() {
|
||||||
|
let base_state = RuntimeState {
|
||||||
|
calendar: CalendarPoint {
|
||||||
|
year: 1845,
|
||||||
|
month_slot: 2,
|
||||||
|
phase_slot: 1,
|
||||||
|
tick_slot: 3,
|
||||||
|
},
|
||||||
|
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(),
|
||||||
|
locomotive_catalog: vec![
|
||||||
|
crate::RuntimeLocomotiveCatalogEntry {
|
||||||
|
locomotive_id: 10,
|
||||||
|
name: "Locomotive 10".to_string(),
|
||||||
|
},
|
||||||
|
crate::RuntimeLocomotiveCatalogEntry {
|
||||||
|
locomotive_id: 112,
|
||||||
|
name: "Locomotive 112".to_string(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
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([
|
||||||
|
("Locomotive 10".to_string(), 0),
|
||||||
|
("Locomotive 112".to_string(), 1),
|
||||||
|
]),
|
||||||
|
special_conditions: BTreeMap::new(),
|
||||||
|
service_state: RuntimeServiceState::default(),
|
||||||
|
};
|
||||||
|
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: 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, 1),
|
||||||
|
real_locomotive_availability_row(457, 0),
|
||||||
|
],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
|
decoded_actions: vec![],
|
||||||
|
executable_import_ready: false,
|
||||||
|
notes: vec![
|
||||||
|
"boolean locomotive availability rows use overlay catalog context"
|
||||||
|
.to_string(),
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
notes: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut import = project_save_slice_overlay_to_runtime_state_import(
|
||||||
|
&base_state,
|
||||||
|
&save_slice,
|
||||||
|
"overlay-locomotive-availability",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("overlay import should project");
|
||||||
|
|
||||||
|
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("overlay-imported locomotive availability record should run");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
import
|
||||||
|
.state
|
||||||
|
.named_locomotive_availability
|
||||||
|
.get("Locomotive 10"),
|
||||||
|
Some(&1)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
import
|
||||||
|
.state
|
||||||
|
.named_locomotive_availability
|
||||||
|
.get("Locomotive 112"),
|
||||||
|
Some(&0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keeps_recovered_locomotive_cost_rows_parity_only() {
|
||||||
|
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: 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: 34,
|
||||||
|
live_record_count: 1,
|
||||||
|
live_entry_ids: vec![34],
|
||||||
|
decoded_record_count: 1,
|
||||||
|
imported_runtime_record_count: 0,
|
||||||
|
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||||
|
record_index: 0,
|
||||||
|
live_entry_id: 34,
|
||||||
|
payload_offset: Some(0x7202),
|
||||||
|
payload_len: Some(96),
|
||||||
|
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![1, 0, 0, 0],
|
||||||
|
grouped_effect_rows: vec![real_locomotive_cost_row(352, 250000)],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
|
decoded_actions: vec![],
|
||||||
|
executable_import_ready: false,
|
||||||
|
notes: vec!["locomotive cost rows remain metadata-only".to_string()],
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
notes: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let import = project_save_slice_to_runtime_state_import(
|
||||||
|
&save_slice,
|
||||||
|
"packed-events-locomotive-cost-frontier",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("save slice should project");
|
||||||
|
|
||||||
|
assert!(import.state.event_runtime_records.is_empty());
|
||||||
|
assert_eq!(
|
||||||
|
import
|
||||||
|
.state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
|
||||||
|
Some("blocked_unmapped_world_descriptor")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn overlays_real_company_cash_descriptor_into_executable_runtime_record() {
|
fn overlays_real_company_cash_descriptor_into_executable_runtime_record() {
|
||||||
let base_state = RuntimeState {
|
let base_state = RuntimeState {
|
||||||
|
|
@ -4729,6 +5183,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -4813,6 +5268,7 @@ mod tests {
|
||||||
semantic_preview: Some(
|
semantic_preview: Some(
|
||||||
"Set Company Cash to 250 with aux [2, 3, 24, 36]".to_string(),
|
"Set Company Cash to 250 with aux [2, 3, 24, 36]".to_string(),
|
||||||
),
|
),
|
||||||
|
recovered_locomotive_id: None,
|
||||||
locomotive_name: Some("Mikado".to_string()),
|
locomotive_name: Some("Mikado".to_string()),
|
||||||
notes: vec![
|
notes: vec![
|
||||||
"grouped effect row carries locomotive-name side string".to_string(),
|
"grouped effect row carries locomotive-name side string".to_string(),
|
||||||
|
|
@ -6353,6 +6809,7 @@ mod tests {
|
||||||
row_shape: "scalar_assignment".to_string(),
|
row_shape: "scalar_assignment".to_string(),
|
||||||
semantic_family: Some("scalar_assignment".to_string()),
|
semantic_family: Some("scalar_assignment".to_string()),
|
||||||
semantic_preview: Some("Set Turbo Diesel Availability to 1".to_string()),
|
semantic_preview: Some("Set Turbo Diesel Availability to 1".to_string()),
|
||||||
|
recovered_locomotive_id: None,
|
||||||
locomotive_name: None,
|
locomotive_name: None,
|
||||||
notes: vec!["checked-in whole-game grouped-effect sample".to_string()],
|
notes: vec!["checked-in whole-game grouped-effect sample".to_string()],
|
||||||
}],
|
}],
|
||||||
|
|
@ -7575,6 +8032,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -7753,6 +8211,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ pub use runtime::{
|
||||||
RuntimeCompany, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
RuntimeCompany, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
||||||
RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCompanyTerritoryAccess,
|
RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCompanyTerritoryAccess,
|
||||||
RuntimeCompanyTerritoryTrackPieceCount, RuntimeCondition, RuntimeConditionComparator,
|
RuntimeCompanyTerritoryTrackPieceCount, RuntimeCondition, RuntimeConditionComparator,
|
||||||
RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate, RuntimeLocomotiveCatalogEntry,
|
||||||
RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary,
|
RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary,
|
||||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
||||||
RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary,
|
RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary,
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,12 @@ pub struct RuntimeTrain {
|
||||||
pub retired: bool,
|
pub retired: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct RuntimeLocomotiveCatalogEntry {
|
||||||
|
pub locomotive_id: u32,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum RuntimeCompanyTarget {
|
pub enum RuntimeCompanyTarget {
|
||||||
|
|
@ -512,6 +518,8 @@ pub struct RuntimePackedEventGroupedEffectRowSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub semantic_preview: Option<String>,
|
pub semantic_preview: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub recovered_locomotive_id: Option<u32>,
|
||||||
|
#[serde(default)]
|
||||||
pub locomotive_name: Option<String>,
|
pub locomotive_name: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub notes: Vec<String>,
|
pub notes: Vec<String>,
|
||||||
|
|
@ -629,6 +637,8 @@ pub struct RuntimeState {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub trains: Vec<RuntimeTrain>,
|
pub trains: Vec<RuntimeTrain>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub locomotive_catalog: Vec<RuntimeLocomotiveCatalogEntry>,
|
||||||
|
#[serde(default)]
|
||||||
pub territories: Vec<RuntimeTerritory>,
|
pub territories: Vec<RuntimeTerritory>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub company_territory_track_piece_counts: Vec<RuntimeCompanyTerritoryTrackPieceCount>,
|
pub company_territory_track_piece_counts: Vec<RuntimeCompanyTerritoryTrackPieceCount>,
|
||||||
|
|
@ -756,6 +766,28 @@ impl RuntimeState {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut seen_locomotive_ids = BTreeSet::new();
|
||||||
|
let mut seen_locomotive_names = BTreeSet::new();
|
||||||
|
for entry in &self.locomotive_catalog {
|
||||||
|
if !seen_locomotive_ids.insert(entry.locomotive_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"duplicate locomotive_catalog.locomotive_id {}",
|
||||||
|
entry.locomotive_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if entry.name.trim().is_empty() {
|
||||||
|
return Err(format!(
|
||||||
|
"locomotive_catalog entry {} has an empty name",
|
||||||
|
entry.locomotive_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !seen_locomotive_names.insert(entry.name.clone()) {
|
||||||
|
return Err(format!(
|
||||||
|
"duplicate locomotive_catalog.name {:?}",
|
||||||
|
entry.name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
for entry in &self.company_territory_track_piece_counts {
|
for entry in &self.company_territory_track_piece_counts {
|
||||||
if !seen_company_ids.contains(&entry.company_id) {
|
if !seen_company_ids.contains(&entry.company_id) {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
|
|
@ -1419,6 +1451,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -1475,6 +1508,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -1517,6 +1551,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -1572,6 +1607,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -1627,6 +1663,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -1733,6 +1770,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -1775,6 +1813,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -1834,6 +1873,7 @@ mod tests {
|
||||||
retired: false,
|
retired: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -1883,6 +1923,7 @@ mod tests {
|
||||||
active: true,
|
active: true,
|
||||||
retired: false,
|
retired: false,
|
||||||
}],
|
}],
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -1932,6 +1973,7 @@ mod tests {
|
||||||
active: true,
|
active: true,
|
||||||
retired: false,
|
retired: false,
|
||||||
}],
|
}],
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: vec![RuntimeTerritory {
|
territories: vec![RuntimeTerritory {
|
||||||
territory_id: 1,
|
territory_id: 1,
|
||||||
name: Some("Appalachia".to_string()),
|
name: Some("Appalachia".to_string()),
|
||||||
|
|
@ -1985,6 +2027,7 @@ mod tests {
|
||||||
active: true,
|
active: true,
|
||||||
retired: true,
|
retired: true,
|
||||||
}],
|
}],
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -2027,6 +2070,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: vec![RuntimeTerritory {
|
territories: vec![RuntimeTerritory {
|
||||||
territory_id: 7,
|
territory_id: 7,
|
||||||
name: Some("Appalachia".to_string()),
|
name: Some("Appalachia".to_string()),
|
||||||
|
|
@ -2082,6 +2126,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: vec![RuntimeTerritory {
|
territories: vec![RuntimeTerritory {
|
||||||
territory_id: 7,
|
territory_id: 7,
|
||||||
name: Some("Appalachia".to_string()),
|
name: Some("Appalachia".to_string()),
|
||||||
|
|
@ -2131,6 +2176,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: vec![RuntimeTerritory {
|
territories: vec![RuntimeTerritory {
|
||||||
territory_id: 7,
|
territory_id: 7,
|
||||||
name: Some("Appalachia".to_string()),
|
name: Some("Appalachia".to_string()),
|
||||||
|
|
|
||||||
|
|
@ -1674,6 +1674,8 @@ pub struct SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub semantic_preview: Option<String>,
|
pub semantic_preview: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub recovered_locomotive_id: Option<u32>,
|
||||||
|
#[serde(default)]
|
||||||
pub locomotive_name: Option<String>,
|
pub locomotive_name: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub notes: Vec<String>,
|
pub notes: Vec<String>,
|
||||||
|
|
@ -2709,6 +2711,7 @@ fn parse_real_grouped_effect_row_summary(
|
||||||
value_word_0x14,
|
value_word_0x14,
|
||||||
value_word_0x16,
|
value_word_0x16,
|
||||||
)),
|
)),
|
||||||
|
recovered_locomotive_id: recovered_locomotive_availability_loco_id(descriptor_id),
|
||||||
locomotive_name,
|
locomotive_name,
|
||||||
notes,
|
notes,
|
||||||
})
|
})
|
||||||
|
|
@ -9252,6 +9255,43 @@ mod tests {
|
||||||
assert!(!metadata.executable_in_runtime);
|
assert!(!metadata.executable_in_runtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn looks_up_upper_band_recovered_locomotive_availability_descriptor_metadata() {
|
||||||
|
let metadata =
|
||||||
|
real_grouped_effect_descriptor_metadata(457).expect("descriptor metadata should exist");
|
||||||
|
|
||||||
|
assert_eq!(metadata.label, "Unknown Loco Available");
|
||||||
|
assert_eq!(metadata.target_mask_bits, 0x08);
|
||||||
|
assert_eq!(metadata.parameter_family, "locomotive_availability_scalar");
|
||||||
|
assert_eq!(recovered_locomotive_availability_loco_id(457), Some(112));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_recovered_locomotive_availability_row_with_structured_locomotive_id() {
|
||||||
|
let row_bytes = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
||||||
|
descriptor_id: 250,
|
||||||
|
raw_scalar_value: 1,
|
||||||
|
opcode: 3,
|
||||||
|
value_byte_0x09: 0,
|
||||||
|
value_dword_0x0d: 0,
|
||||||
|
value_byte_0x11: 0,
|
||||||
|
value_byte_0x12: 0,
|
||||||
|
value_word_0x14: 0,
|
||||||
|
value_word_0x16: 0,
|
||||||
|
locomotive_name: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let row = parse_real_grouped_effect_row_summary(&row_bytes, 0, 0, None)
|
||||||
|
.expect("row should parse");
|
||||||
|
|
||||||
|
assert_eq!(row.descriptor_id, 250);
|
||||||
|
assert_eq!(row.recovered_locomotive_id, Some(10));
|
||||||
|
assert_eq!(
|
||||||
|
row.parameter_family.as_deref(),
|
||||||
|
Some("locomotive_availability_scalar")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn looks_up_recovered_locomotive_policy_descriptor_metadata() {
|
fn looks_up_recovered_locomotive_policy_descriptor_metadata() {
|
||||||
let metadata =
|
let metadata =
|
||||||
|
|
|
||||||
|
|
@ -1157,6 +1157,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ pub struct RuntimeSummary {
|
||||||
pub train_count: usize,
|
pub train_count: usize,
|
||||||
pub active_train_count: usize,
|
pub active_train_count: usize,
|
||||||
pub retired_train_count: usize,
|
pub retired_train_count: usize,
|
||||||
|
pub locomotive_catalog_count: usize,
|
||||||
pub territory_count: usize,
|
pub territory_count: usize,
|
||||||
pub company_territory_track_count: usize,
|
pub company_territory_track_count: usize,
|
||||||
pub packed_event_collection_present: bool,
|
pub packed_event_collection_present: bool,
|
||||||
|
|
@ -65,6 +66,7 @@ pub struct RuntimeSummary {
|
||||||
pub packed_event_blocked_territory_access_scope_count: usize,
|
pub packed_event_blocked_territory_access_scope_count: usize,
|
||||||
pub packed_event_blocked_missing_train_context_count: usize,
|
pub packed_event_blocked_missing_train_context_count: usize,
|
||||||
pub packed_event_blocked_missing_train_territory_context_count: usize,
|
pub packed_event_blocked_missing_train_territory_context_count: usize,
|
||||||
|
pub packed_event_blocked_missing_locomotive_catalog_context_count: usize,
|
||||||
pub packed_event_blocked_confiscation_variant_count: usize,
|
pub packed_event_blocked_confiscation_variant_count: usize,
|
||||||
pub packed_event_blocked_retire_train_variant_count: usize,
|
pub packed_event_blocked_retire_train_variant_count: usize,
|
||||||
pub packed_event_blocked_retire_train_scope_count: usize,
|
pub packed_event_blocked_retire_train_scope_count: usize,
|
||||||
|
|
@ -165,6 +167,7 @@ impl RuntimeSummary {
|
||||||
train_count: state.trains.len(),
|
train_count: state.trains.len(),
|
||||||
active_train_count: state.trains.iter().filter(|train| train.active).count(),
|
active_train_count: state.trains.iter().filter(|train| train.active).count(),
|
||||||
retired_train_count: state.trains.iter().filter(|train| train.retired).count(),
|
retired_train_count: state.trains.iter().filter(|train| train.retired).count(),
|
||||||
|
locomotive_catalog_count: state.locomotive_catalog.len(),
|
||||||
territory_count: state.territories.len(),
|
territory_count: state.territories.len(),
|
||||||
company_territory_track_count: state.company_territory_track_piece_counts.len(),
|
company_territory_track_count: state.company_territory_track_piece_counts.len(),
|
||||||
packed_event_collection_present: state.packed_event_collection.is_some(),
|
packed_event_collection_present: state.packed_event_collection.is_some(),
|
||||||
|
|
@ -513,6 +516,20 @@ impl RuntimeSummary {
|
||||||
.count()
|
.count()
|
||||||
})
|
})
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
|
packed_event_blocked_missing_locomotive_catalog_context_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.import_outcome.as_deref()
|
||||||
|
== Some("blocked_missing_locomotive_catalog_context")
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
packed_event_blocked_confiscation_variant_count: state
|
packed_event_blocked_confiscation_variant_count: state
|
||||||
.packed_event_collection
|
.packed_event_collection
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -642,6 +659,16 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: vec![
|
||||||
|
crate::RuntimeLocomotiveCatalogEntry {
|
||||||
|
locomotive_id: 10,
|
||||||
|
name: "Locomotive 10".to_string(),
|
||||||
|
},
|
||||||
|
crate::RuntimeLocomotiveCatalogEntry {
|
||||||
|
locomotive_id: 112,
|
||||||
|
name: "Locomotive 112".to_string(),
|
||||||
|
},
|
||||||
|
],
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -872,6 +899,16 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: vec![
|
||||||
|
crate::RuntimeLocomotiveCatalogEntry {
|
||||||
|
locomotive_id: 10,
|
||||||
|
name: "Locomotive 10".to_string(),
|
||||||
|
},
|
||||||
|
crate::RuntimeLocomotiveCatalogEntry {
|
||||||
|
locomotive_id: 112,
|
||||||
|
name: "Locomotive 112".to_string(),
|
||||||
|
},
|
||||||
|
],
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -906,6 +943,16 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: vec![
|
||||||
|
crate::RuntimeLocomotiveCatalogEntry {
|
||||||
|
locomotive_id: 10,
|
||||||
|
name: "Locomotive 10".to_string(),
|
||||||
|
},
|
||||||
|
crate::RuntimeLocomotiveCatalogEntry {
|
||||||
|
locomotive_id: 112,
|
||||||
|
name: "Locomotive 112".to_string(),
|
||||||
|
},
|
||||||
|
],
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -922,6 +969,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
let summary = RuntimeSummary::from_state(&state);
|
let summary = RuntimeSummary::from_state(&state);
|
||||||
|
assert_eq!(summary.locomotive_catalog_count, 2);
|
||||||
assert_eq!(summary.named_locomotive_availability_count, 3);
|
assert_eq!(summary.named_locomotive_availability_count, 3);
|
||||||
assert_eq!(summary.zero_named_locomotive_availability_count, 2);
|
assert_eq!(summary.zero_named_locomotive_availability_count, 2);
|
||||||
}
|
}
|
||||||
|
|
@ -944,6 +992,7 @@ mod tests {
|
||||||
players: Vec::new(),
|
players: Vec::new(),
|
||||||
selected_player_id: None,
|
selected_player_id: None,
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
company_territory_access: Vec::new(),
|
company_territory_access: Vec::new(),
|
||||||
|
|
@ -1029,4 +1078,78 @@ mod tests {
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn counts_missing_locomotive_catalog_context_frontier() {
|
||||||
|
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(),
|
||||||
|
locomotive_catalog: Vec::new(),
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
|
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
||||||
|
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()),
|
||||||
|
packed_state_version: 0x3e9,
|
||||||
|
packed_state_version_hex: "0x000003e9".to_string(),
|
||||||
|
live_id_bound: 1,
|
||||||
|
live_record_count: 1,
|
||||||
|
live_entry_ids: vec![1],
|
||||||
|
decoded_record_count: 1,
|
||||||
|
imported_runtime_record_count: 0,
|
||||||
|
records: vec![RuntimePackedEventRecordSummary {
|
||||||
|
record_index: 0,
|
||||||
|
live_entry_id: 1,
|
||||||
|
payload_offset: Some(0x7202),
|
||||||
|
payload_len: Some(96),
|
||||||
|
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: None,
|
||||||
|
compact_control: None,
|
||||||
|
text_bands: Vec::new(),
|
||||||
|
standalone_condition_row_count: 0,
|
||||||
|
standalone_condition_rows: Vec::new(),
|
||||||
|
negative_sentinel_scope: None,
|
||||||
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
|
grouped_effect_rows: Vec::new(),
|
||||||
|
grouped_company_targets: Vec::new(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
|
decoded_actions: Vec::new(),
|
||||||
|
executable_import_ready: false,
|
||||||
|
import_outcome: Some("blocked_missing_locomotive_catalog_context".to_string()),
|
||||||
|
notes: Vec::new(),
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
event_runtime_records: Vec::new(),
|
||||||
|
candidate_availability: BTreeMap::new(),
|
||||||
|
named_locomotive_availability: BTreeMap::new(),
|
||||||
|
special_conditions: BTreeMap::new(),
|
||||||
|
service_state: RuntimeServiceState::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let summary = RuntimeSummary::from_state(&state);
|
||||||
|
assert_eq!(
|
||||||
|
summary.packed_event_blocked_missing_locomotive_catalog_context_count,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,10 +127,10 @@ The highest-value next passes are now:
|
||||||
descriptors `454..456` (`All Steam/Diesel/Electric Locos Avail.`) now lower through checked-in
|
descriptors `454..456` (`All Steam/Diesel/Electric Locos Avail.`) now lower through checked-in
|
||||||
metadata into keyed `world_flags`, while the wider locomotive availability/cost scalar bands
|
metadata into keyed `world_flags`, while the wider locomotive availability/cost scalar bands
|
||||||
remain recovered-but-parity-only until per-locomotive identity is grounded
|
remain recovered-but-parity-only until per-locomotive identity is grounded
|
||||||
- the runtime now also carries the save-owned named locomotive availability table directly:
|
- the runtime now also carries both the save-owned named locomotive availability table and an
|
||||||
checked-in save-slice documents can populate `RuntimeState.named_locomotive_availability`, and
|
overlay-backed locomotive catalog context: checked-in save-slice documents can populate
|
||||||
imported runtime effects can mutate that map through the ordinary event-service path without
|
`RuntimeState.named_locomotive_availability`, and boolean `0/1` availability descriptors can
|
||||||
needing full live locomotive-pool parity
|
lower through `RuntimeState.locomotive_catalog` into the same ordinary event-service path
|
||||||
- keep in mind that the current local `.gms` corpus still exports with no packed event collection,
|
- keep in mind that the current local `.gms` corpus still exports with no packed event collection,
|
||||||
so real descriptor mapping needs to stay plumbing-first until better captures exist
|
so real descriptor mapping needs to stay plumbing-first until better captures exist
|
||||||
- use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution
|
- use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution
|
||||||
|
|
|
||||||
|
|
@ -86,10 +86,14 @@ Implemented today:
|
||||||
save-slice documents can carry the persisted `[world+0x66b6]` name table into
|
save-slice documents can carry the persisted `[world+0x66b6]` name table into
|
||||||
`RuntimeState.named_locomotive_availability`, and imported runtime effects can mutate that map
|
`RuntimeState.named_locomotive_availability`, and imported runtime effects can mutate that map
|
||||||
through the ordinary event-service path without requiring Trainbuy or live locomotive-pool parity
|
through the ordinary event-service path without requiring Trainbuy or live locomotive-pool parity
|
||||||
|
- the boolean `0/1` subset of the recovered locomotives-page availability bands can now import
|
||||||
|
through an overlay-backed `RuntimeState.locomotive_catalog`; non-boolean availability payloads
|
||||||
|
and the adjacent locomotive-cost/cargo-production/access-cost families remain parity-only
|
||||||
|
|
||||||
That means the next implementation work is breadth, not bootstrap. The recommended next slice is
|
That means the next implementation work is breadth, not bootstrap. The recommended next slice is
|
||||||
broader real grouped-descriptor and ordinary condition-id coverage beyond the current access,
|
broader real grouped-descriptor and ordinary condition-id coverage beyond the current access,
|
||||||
whole-game toggle, train, player, numeric-threshold, and named locomotive availability batches.
|
whole-game toggle, train, player, numeric-threshold, named locomotive availability, and
|
||||||
|
overlay-resolved locomotive availability batches.
|
||||||
Richer runtime ownership should still be added only where a later descriptor or condition family
|
Richer runtime ownership should still be added only where a later descriptor or condition family
|
||||||
needs more than the current event-owned roster.
|
needs more than the current event-owned roster.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-locomotive-availability-missing-catalog-save-slice-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture backed by a tracked save-slice document that leaves a boolean locomotive availability row blocked until overlay-backed catalog context is supplied."
|
||||||
|
},
|
||||||
|
"state_save_slice_path": "packed-event-locomotive-availability-missing-catalog-save-slice.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "step_count",
|
||||||
|
"steps": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar": {
|
||||||
|
"year": 1830,
|
||||||
|
"month_slot": 0,
|
||||||
|
"phase_slot": 0,
|
||||||
|
"tick_slot": 1
|
||||||
|
},
|
||||||
|
"calendar_projection_is_placeholder": true,
|
||||||
|
"locomotive_catalog_count": 0,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 0,
|
||||||
|
"packed_event_parity_only_record_count": 1,
|
||||||
|
"packed_event_blocked_missing_locomotive_catalog_context_count": 1,
|
||||||
|
"event_runtime_record_count": 0,
|
||||||
|
"named_locomotive_availability_count": 0
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"packed_event_collection": {
|
||||||
|
"live_entry_ids": [32],
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "real_packed_v1",
|
||||||
|
"import_outcome": "blocked_missing_locomotive_catalog_context",
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"descriptor_id": 250,
|
||||||
|
"recovered_locomotive_id": 10,
|
||||||
|
"semantic_preview": "Set Unknown Loco Available to 1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-locomotive-availability-missing-catalog-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document proving boolean locomotive availability rows stay parity-only without overlay-backed locomotive catalog context.",
|
||||||
|
"original_save_filename": "captured-locomotive-availability-missing-catalog.gms",
|
||||||
|
"original_save_sha256": "locomotive-availability-missing-catalog-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"locks the explicit missing locomotive catalog frontier for boolean availability rows"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"save_slice": {
|
||||||
|
"file_extension_hint": "gms",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"trailer_family": null,
|
||||||
|
"bridge_family": null,
|
||||||
|
"profile": null,
|
||||||
|
"candidate_availability_table": null,
|
||||||
|
"named_locomotive_availability_table": null,
|
||||||
|
"special_conditions_table": null,
|
||||||
|
"event_runtime_collection": {
|
||||||
|
"source_kind": "packed-event-runtime-collection",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"metadata_tag_offset": 28928,
|
||||||
|
"records_tag_offset": 29184,
|
||||||
|
"close_tag_offset": 29696,
|
||||||
|
"packed_state_version": 1001,
|
||||||
|
"packed_state_version_hex": "0x000003e9",
|
||||||
|
"live_id_bound": 32,
|
||||||
|
"live_record_count": 1,
|
||||||
|
"live_entry_ids": [32],
|
||||||
|
"decoded_record_count": 1,
|
||||||
|
"imported_runtime_record_count": 0,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 32,
|
||||||
|
"payload_offset": 29186,
|
||||||
|
"payload_len": 96,
|
||||||
|
"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": [1, 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": 1,
|
||||||
|
"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 1",
|
||||||
|
"recovered_locomotive_id": 10,
|
||||||
|
"locomotive_name": null,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_actions": [],
|
||||||
|
"executable_import_ready": false,
|
||||||
|
"notes": [
|
||||||
|
"boolean locomotive availability row still requires overlay-backed locomotive catalog context"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"locomotive availability catalog blocker sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"snapshot_id": "packed-event-locomotive-availability-overlay-base-snapshot",
|
||||||
|
"source": {
|
||||||
|
"description": "Base runtime snapshot supplying locomotive catalog context for descriptor-driven named locomotive availability import."
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"calendar": {
|
||||||
|
"year": 1835,
|
||||||
|
"month_slot": 1,
|
||||||
|
"phase_slot": 2,
|
||||||
|
"tick_slot": 4
|
||||||
|
},
|
||||||
|
"world_flags": {
|
||||||
|
"base.only": true
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"base.note": "preserve locomotive catalog context"
|
||||||
|
},
|
||||||
|
"locomotive_catalog": [
|
||||||
|
{
|
||||||
|
"locomotive_id": 10,
|
||||||
|
"name": "Locomotive 10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locomotive_id": 112,
|
||||||
|
"name": "Locomotive 112"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"named_locomotive_availability": {
|
||||||
|
"Locomotive 10": 0,
|
||||||
|
"Locomotive 112": 1
|
||||||
|
},
|
||||||
|
"event_runtime_records": [],
|
||||||
|
"candidate_availability": {},
|
||||||
|
"special_conditions": {},
|
||||||
|
"service_state": {
|
||||||
|
"periodic_boundary_calls": 0,
|
||||||
|
"trigger_dispatch_counts": {},
|
||||||
|
"total_event_record_services": 0,
|
||||||
|
"dirty_rerun_count": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-locomotive-availability-overlay-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture backed by an overlay import document so boolean locomotive availability descriptors execute against captured catalog context."
|
||||||
|
},
|
||||||
|
"state_import_path": "packed-event-locomotive-availability-overlay.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar": {
|
||||||
|
"year": 1835,
|
||||||
|
"month_slot": 1,
|
||||||
|
"phase_slot": 2,
|
||||||
|
"tick_slot": 4
|
||||||
|
},
|
||||||
|
"calendar_projection_is_placeholder": false,
|
||||||
|
"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,
|
||||||
|
"packed_event_parity_only_record_count": 1,
|
||||||
|
"event_runtime_record_count": 1,
|
||||||
|
"named_locomotive_availability_count": 2,
|
||||||
|
"zero_named_locomotive_availability_count": 1,
|
||||||
|
"total_event_record_service_count": 1,
|
||||||
|
"total_trigger_dispatch_count": 1
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"metadata": {
|
||||||
|
"base.note": "preserve locomotive catalog context",
|
||||||
|
"save_slice.import_projection": "overlay-runtime-restore-v1"
|
||||||
|
},
|
||||||
|
"named_locomotive_availability": {
|
||||||
|
"Locomotive 10": 1,
|
||||||
|
"Locomotive 112": 0
|
||||||
|
},
|
||||||
|
"packed_event_collection": {
|
||||||
|
"live_entry_ids": [33],
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "real_packed_v1",
|
||||||
|
"import_outcome": "imported",
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"descriptor_id": 250,
|
||||||
|
"recovered_locomotive_id": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"descriptor_id": 457,
|
||||||
|
"recovered_locomotive_id": 112
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event_runtime_records": [
|
||||||
|
{
|
||||||
|
"record_id": 33,
|
||||||
|
"service_count": 1,
|
||||||
|
"effects": [
|
||||||
|
{
|
||||||
|
"kind": "set_named_locomotive_availability",
|
||||||
|
"name": "Locomotive 10",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "set_named_locomotive_availability",
|
||||||
|
"name": "Locomotive 112",
|
||||||
|
"value": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-locomotive-availability-overlay-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document proving boolean locomotive availability descriptors can import through overlay-backed catalog context.",
|
||||||
|
"original_save_filename": "captured-locomotive-availability-overlay.gms",
|
||||||
|
"original_save_sha256": "locomotive-availability-overlay-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"uses synthetic catalog names to prove descriptor-to-id lowering plus overlay-backed resolution"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"save_slice": {
|
||||||
|
"file_extension_hint": "gms",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"trailer_family": null,
|
||||||
|
"bridge_family": null,
|
||||||
|
"profile": null,
|
||||||
|
"candidate_availability_table": null,
|
||||||
|
"named_locomotive_availability_table": null,
|
||||||
|
"special_conditions_table": null,
|
||||||
|
"event_runtime_collection": {
|
||||||
|
"source_kind": "packed-event-runtime-collection",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"metadata_tag_offset": 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": 0,
|
||||||
|
"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": 1,
|
||||||
|
"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 1",
|
||||||
|
"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": 0,
|
||||||
|
"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 0",
|
||||||
|
"recovered_locomotive_id": 112,
|
||||||
|
"locomotive_name": null,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_actions": [],
|
||||||
|
"executable_import_ready": false,
|
||||||
|
"notes": [
|
||||||
|
"boolean locomotive availability rows use overlay-backed catalog context"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"overlay-backed descriptor-driven named locomotive availability sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"import_id": "packed-event-locomotive-availability-overlay",
|
||||||
|
"source": {
|
||||||
|
"description": "Overlay import that combines a captured base snapshot with boolean locomotive availability descriptors.",
|
||||||
|
"notes": [
|
||||||
|
"used to upgrade descriptor-driven named locomotive availability rows through overlay-backed catalog context"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"base_snapshot_path": "packed-event-locomotive-availability-overlay-base-snapshot.json",
|
||||||
|
"save_slice_path": "packed-event-locomotive-availability-overlay-save-slice.json"
|
||||||
|
}
|
||||||
|
|
@ -57,6 +57,7 @@
|
||||||
"parameter_family": "locomotive_availability_scalar",
|
"parameter_family": "locomotive_availability_scalar",
|
||||||
"semantic_family": "scalar_assignment",
|
"semantic_family": "scalar_assignment",
|
||||||
"semantic_preview": "Set Unknown Loco Available to 42",
|
"semantic_preview": "Set Unknown Loco Available to 42",
|
||||||
|
"recovered_locomotive_id": 10,
|
||||||
"row_shape": "scalar_assignment"
|
"row_shape": "scalar_assignment"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
"original_save_sha256": "parity-sample-sha256",
|
"original_save_sha256": "parity-sample-sha256",
|
||||||
"notes": [
|
"notes": [
|
||||||
"tracked as JSON save-slice document rather than raw .smp",
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
"preserves one recovered-but-unmapped locomotive policy row and one semantically decoded-but-parity-only row"
|
"preserves one recovered-but-unmapped locomotive availability row and one semantically decoded-but-parity-only row"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"save_slice": {
|
"save_slice": {
|
||||||
|
|
@ -80,9 +80,10 @@
|
||||||
"row_shape": "scalar_assignment",
|
"row_shape": "scalar_assignment",
|
||||||
"semantic_family": "scalar_assignment",
|
"semantic_family": "scalar_assignment",
|
||||||
"semantic_preview": "Set Unknown Loco Available to 42",
|
"semantic_preview": "Set Unknown Loco Available to 42",
|
||||||
|
"recovered_locomotive_id": 10,
|
||||||
"locomotive_name": null,
|
"locomotive_name": null,
|
||||||
"notes": [
|
"notes": [
|
||||||
"recovered locomotive availability descriptor family remains parity-only until per-locomotive identity is grounded"
|
"recovered locomotive availability descriptor family remains parity-only until the scalar payload is in the grounded boolean subset"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -90,7 +91,7 @@
|
||||||
"executable_import_ready": false,
|
"executable_import_ready": false,
|
||||||
"notes": [
|
"notes": [
|
||||||
"decoded from grounded real 0x4e9a row framing",
|
"decoded from grounded real 0x4e9a row framing",
|
||||||
"recovered locomotives-page descriptor band is now checked in, but this scalar family still has no executable runtime landing surface"
|
"recovered locomotives-page descriptor band is now checked in, but this scalar family still needs overlay-backed locomotive catalog context and a grounded boolean scalar payload"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue