Census locomotive tail blockers across local saves
This commit is contained in:
parent
61472bf72d
commit
cbfe0a8df9
16 changed files with 2022 additions and 319 deletions
|
|
@ -14,6 +14,11 @@ pub(super) fn parse_scan_command(
|
|||
root_path: root_path.into(),
|
||||
})
|
||||
}
|
||||
[subcommand, root_path] if subcommand == "scan-locomotive-catalog-tail" => {
|
||||
Ok(ScanCommand::ScanLocomotiveCatalogTail {
|
||||
root_path: root_path.into(),
|
||||
})
|
||||
}
|
||||
[subcommand, root_path] if subcommand == "scan-special-conditions" => {
|
||||
Ok(ScanCommand::ScanSpecialConditions {
|
||||
root_path: root_path.into(),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use crate::app::command::ScanCommand;
|
||||
use crate::app::runtime_scan::{
|
||||
scan_aligned_runtime_rule_band, scan_candidate_table_headers, scan_candidate_table_named_runs,
|
||||
scan_post_special_conditions_scalars, scan_post_special_conditions_tail,
|
||||
scan_recipe_book_lines, scan_special_conditions,
|
||||
scan_locomotive_catalog_tail, scan_post_special_conditions_scalars,
|
||||
scan_post_special_conditions_tail, scan_recipe_book_lines, scan_special_conditions,
|
||||
};
|
||||
|
||||
pub(super) fn dispatch_scan(command: ScanCommand) -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
|
@ -13,6 +13,9 @@ pub(super) fn dispatch_scan(command: ScanCommand) -> Result<(), Box<dyn std::err
|
|||
ScanCommand::ScanCandidateTableNamedRuns { root_path } => {
|
||||
scan_candidate_table_named_runs(&root_path)
|
||||
}
|
||||
ScanCommand::ScanLocomotiveCatalogTail { root_path } => {
|
||||
scan_locomotive_catalog_tail(&root_path)
|
||||
}
|
||||
ScanCommand::ScanSpecialConditions { root_path } => scan_special_conditions(&root_path),
|
||||
ScanCommand::ScanAlignedRuntimeRuleBand { root_path } => {
|
||||
scan_aligned_runtime_rule_band(&root_path)
|
||||
|
|
|
|||
|
|
@ -447,7 +447,8 @@ fn build_named_run_aggregates(
|
|||
find_named_run_by_names(&sample.port_runs, "Port00", "Port00", 1),
|
||||
find_named_run_by_names(&sample.warehouse_runs, "Warehouse00", "Warehouse00", 1),
|
||||
) {
|
||||
let row_pair_key = format!("{}/{}", port00_run.start_index, warehouse00_run.start_index);
|
||||
let row_pair_key =
|
||||
format!("{}/{}", port00_run.start_index, warehouse00_run.start_index);
|
||||
*aggregates
|
||||
.port00_warehouse00_row_pair_map_counts
|
||||
.entry(row_pair_key.clone())
|
||||
|
|
|
|||
569
crates/rrt-cli/src/app/runtime_scan/locomotive_tail.rs
Normal file
569
crates/rrt-cli/src/app/runtime_scan/locomotive_tail.rs
Normal file
|
|
@ -0,0 +1,569 @@
|
|||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use rrt_runtime::inspect::smp::save_load::{
|
||||
SmpLoadedLocomotiveCatalogEntry, SmpLoadedSaveSlice, load_save_slice_file,
|
||||
};
|
||||
|
||||
const EXTRA_SHIPPED_ENGINE_TYPE_NAMES: [&str; 5] =
|
||||
["242 A1", "Class 460", "Class A1", "Class P8", "Class QJ"];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct RuntimeLocomotiveCatalogTailScanSample {
|
||||
path: String,
|
||||
container_profile_family: Option<String>,
|
||||
mechanism_family: String,
|
||||
map_path: Option<String>,
|
||||
display_name: Option<String>,
|
||||
named_locomotive_table_entry_count: Option<usize>,
|
||||
locomotive_catalog_entries: Vec<SmpLoadedLocomotiveCatalogEntry>,
|
||||
descriptor_rows: Vec<RuntimeLocomotiveDescriptorRowHit>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct RuntimeLocomotiveDescriptorRowHit {
|
||||
descriptor_id: u32,
|
||||
descriptor_label: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
|
||||
pub(crate) struct RuntimeObservedLocomotiveCatalogEntry {
|
||||
pub(crate) locomotive_id: u32,
|
||||
pub(crate) name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub(crate) struct RuntimeLocomotiveCatalogTailScanSampleReport {
|
||||
pub(crate) path: String,
|
||||
pub(crate) container_profile_family: Option<String>,
|
||||
pub(crate) mechanism_family: String,
|
||||
pub(crate) map_path: Option<String>,
|
||||
pub(crate) display_name: Option<String>,
|
||||
pub(crate) named_locomotive_table_entry_count: Option<usize>,
|
||||
pub(crate) locomotive_catalog_entry_count: usize,
|
||||
pub(crate) tail_entries: Vec<RuntimeObservedLocomotiveCatalogEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub(crate) struct RuntimeLocomotiveCatalogTailCluster {
|
||||
pub(crate) entry_count: usize,
|
||||
pub(crate) tail_entries: Vec<RuntimeObservedLocomotiveCatalogEntry>,
|
||||
pub(crate) file_count: usize,
|
||||
pub(crate) sample_paths: Vec<String>,
|
||||
pub(crate) map_paths: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub(crate) struct RuntimeLocomotiveExtraNameCoverageSummary {
|
||||
pub(crate) name: String,
|
||||
pub(crate) observed_in_catalog_file_count: usize,
|
||||
pub(crate) observed_ordinals: Vec<u32>,
|
||||
pub(crate) sample_paths: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub(crate) struct RuntimeLocomotiveDescriptorBandHitSummary {
|
||||
pub(crate) row_count: usize,
|
||||
pub(crate) file_count: usize,
|
||||
pub(crate) descriptor_ids_present: Vec<u32>,
|
||||
pub(crate) descriptor_labels_present: Vec<String>,
|
||||
pub(crate) carrier_paths: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub(crate) struct RuntimeLocomotiveCatalogTailCensusReport {
|
||||
pub(crate) root_path: String,
|
||||
pub(crate) file_count: usize,
|
||||
pub(crate) files_with_named_locomotive_table_count: usize,
|
||||
pub(crate) files_with_locomotive_catalog_count: usize,
|
||||
pub(crate) files_with_packed_event_collection_count: usize,
|
||||
pub(crate) stable_prefix_length: usize,
|
||||
pub(crate) stable_prefix_last_name: Option<String>,
|
||||
pub(crate) tail_cluster_count: usize,
|
||||
pub(crate) tail_clusters: Vec<RuntimeLocomotiveCatalogTailCluster>,
|
||||
pub(crate) extra_shipped_name_coverage: Vec<RuntimeLocomotiveExtraNameCoverageSummary>,
|
||||
pub(crate) descriptor_452_hits: RuntimeLocomotiveDescriptorBandHitSummary,
|
||||
pub(crate) upper_availability_band_hits: RuntimeLocomotiveDescriptorBandHitSummary,
|
||||
pub(crate) upper_cost_band_hits: RuntimeLocomotiveDescriptorBandHitSummary,
|
||||
pub(crate) skipped_file_count: usize,
|
||||
pub(crate) samples: Vec<RuntimeLocomotiveCatalogTailScanSampleReport>,
|
||||
}
|
||||
|
||||
pub(crate) fn scan_locomotive_catalog_tail(
|
||||
root_path: &Path,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut candidate_paths = Vec::new();
|
||||
collect_locomotive_tail_input_paths(root_path, &mut candidate_paths)?;
|
||||
|
||||
let file_count = candidate_paths.len();
|
||||
let mut samples = Vec::new();
|
||||
let mut skipped_file_count = 0usize;
|
||||
for path in candidate_paths {
|
||||
match load_locomotive_catalog_tail_scan_sample(&path) {
|
||||
Ok(sample) => samples.push(sample),
|
||||
Err(_) => skipped_file_count += 1,
|
||||
}
|
||||
}
|
||||
|
||||
let files_with_named_locomotive_table_count = samples
|
||||
.iter()
|
||||
.filter(|sample| sample.named_locomotive_table_entry_count.is_some())
|
||||
.count();
|
||||
let files_with_locomotive_catalog_count = samples
|
||||
.iter()
|
||||
.filter(|sample| !sample.locomotive_catalog_entries.is_empty())
|
||||
.count();
|
||||
let files_with_packed_event_collection_count = samples
|
||||
.iter()
|
||||
.filter(|sample| !sample.descriptor_rows.is_empty())
|
||||
.count();
|
||||
let stable_prefix_length = stable_catalog_prefix_length(&samples);
|
||||
let stable_prefix_last_name = stable_prefix_last_name(&samples, stable_prefix_length);
|
||||
let tail_clusters = build_tail_clusters(&samples, stable_prefix_length);
|
||||
let extra_shipped_name_coverage = build_extra_shipped_name_coverage(&samples);
|
||||
let descriptor_452_hits = build_descriptor_band_hit_summary(&samples, 452..=452);
|
||||
let upper_availability_band_hits = build_descriptor_band_hit_summary(&samples, 457..=474);
|
||||
let upper_cost_band_hits = build_descriptor_band_hit_summary(&samples, 475..=502);
|
||||
let sample_reports = build_sample_reports(&samples, stable_prefix_length);
|
||||
|
||||
let report = RuntimeLocomotiveCatalogTailCensusReport {
|
||||
root_path: root_path.display().to_string(),
|
||||
file_count,
|
||||
files_with_named_locomotive_table_count,
|
||||
files_with_locomotive_catalog_count,
|
||||
files_with_packed_event_collection_count,
|
||||
stable_prefix_length,
|
||||
stable_prefix_last_name,
|
||||
tail_cluster_count: tail_clusters.len(),
|
||||
tail_clusters,
|
||||
extra_shipped_name_coverage,
|
||||
descriptor_452_hits,
|
||||
upper_availability_band_hits,
|
||||
upper_cost_band_hits,
|
||||
skipped_file_count,
|
||||
samples: sample_reports,
|
||||
};
|
||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_sample_reports(
|
||||
samples: &[RuntimeLocomotiveCatalogTailScanSample],
|
||||
stable_prefix_length: usize,
|
||||
) -> Vec<RuntimeLocomotiveCatalogTailScanSampleReport> {
|
||||
let mut reports = samples
|
||||
.iter()
|
||||
.map(|sample| RuntimeLocomotiveCatalogTailScanSampleReport {
|
||||
path: sample.path.clone(),
|
||||
container_profile_family: sample.container_profile_family.clone(),
|
||||
mechanism_family: sample.mechanism_family.clone(),
|
||||
map_path: sample.map_path.clone(),
|
||||
display_name: sample.display_name.clone(),
|
||||
named_locomotive_table_entry_count: sample.named_locomotive_table_entry_count,
|
||||
locomotive_catalog_entry_count: sample.locomotive_catalog_entries.len(),
|
||||
tail_entries: tail_entries(sample, stable_prefix_length),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
reports.sort_by(|left, right| left.path.cmp(&right.path));
|
||||
reports
|
||||
}
|
||||
|
||||
fn stable_prefix_last_name(
|
||||
samples: &[RuntimeLocomotiveCatalogTailScanSample],
|
||||
stable_prefix_length: usize,
|
||||
) -> Option<String> {
|
||||
(stable_prefix_length != 0).then(|| {
|
||||
samples
|
||||
.iter()
|
||||
.find_map(|sample| {
|
||||
sample
|
||||
.locomotive_catalog_entries
|
||||
.get(stable_prefix_length - 1)
|
||||
})
|
||||
.map(|entry| entry.name.clone())
|
||||
.expect("stable prefix entry should exist")
|
||||
})
|
||||
}
|
||||
|
||||
fn stable_catalog_prefix_length(samples: &[RuntimeLocomotiveCatalogTailScanSample]) -> usize {
|
||||
let catalog_samples = samples
|
||||
.iter()
|
||||
.filter(|sample| !sample.locomotive_catalog_entries.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
if catalog_samples.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let min_count = catalog_samples
|
||||
.iter()
|
||||
.map(|sample| sample.locomotive_catalog_entries.len())
|
||||
.min()
|
||||
.unwrap_or(0);
|
||||
|
||||
let mut stable_prefix_length = 0usize;
|
||||
for index in 0..min_count {
|
||||
let first_name = &catalog_samples[0].locomotive_catalog_entries[index].name;
|
||||
if catalog_samples
|
||||
.iter()
|
||||
.all(|sample| sample.locomotive_catalog_entries[index].name == *first_name)
|
||||
{
|
||||
stable_prefix_length += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
stable_prefix_length
|
||||
}
|
||||
|
||||
fn build_tail_clusters(
|
||||
samples: &[RuntimeLocomotiveCatalogTailScanSample],
|
||||
stable_prefix_length: usize,
|
||||
) -> Vec<RuntimeLocomotiveCatalogTailCluster> {
|
||||
let mut grouped = BTreeMap::<
|
||||
Vec<RuntimeObservedLocomotiveCatalogEntry>,
|
||||
Vec<&RuntimeLocomotiveCatalogTailScanSample>,
|
||||
>::new();
|
||||
for sample in samples
|
||||
.iter()
|
||||
.filter(|sample| !sample.locomotive_catalog_entries.is_empty())
|
||||
{
|
||||
grouped
|
||||
.entry(tail_entries(sample, stable_prefix_length))
|
||||
.or_default()
|
||||
.push(sample);
|
||||
}
|
||||
|
||||
let mut clusters = grouped
|
||||
.into_iter()
|
||||
.map(|(tail_entries, samples)| {
|
||||
let mut sample_paths = samples
|
||||
.iter()
|
||||
.map(|sample| sample.path.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let mut map_paths = samples
|
||||
.iter()
|
||||
.filter_map(|sample| sample.map_path.clone())
|
||||
.collect::<BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
sample_paths.sort();
|
||||
map_paths.sort();
|
||||
RuntimeLocomotiveCatalogTailCluster {
|
||||
entry_count: stable_prefix_length + tail_entries.len(),
|
||||
tail_entries,
|
||||
file_count: samples.len(),
|
||||
sample_paths,
|
||||
map_paths,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
clusters.sort_by(|left, right| {
|
||||
left.tail_entries
|
||||
.cmp(&right.tail_entries)
|
||||
.then(left.entry_count.cmp(&right.entry_count))
|
||||
});
|
||||
clusters
|
||||
}
|
||||
|
||||
fn tail_entries(
|
||||
sample: &RuntimeLocomotiveCatalogTailScanSample,
|
||||
stable_prefix_length: usize,
|
||||
) -> Vec<RuntimeObservedLocomotiveCatalogEntry> {
|
||||
sample
|
||||
.locomotive_catalog_entries
|
||||
.iter()
|
||||
.skip(stable_prefix_length)
|
||||
.map(|entry| RuntimeObservedLocomotiveCatalogEntry {
|
||||
locomotive_id: entry.locomotive_id,
|
||||
name: entry.name.clone(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn build_extra_shipped_name_coverage(
|
||||
samples: &[RuntimeLocomotiveCatalogTailScanSample],
|
||||
) -> Vec<RuntimeLocomotiveExtraNameCoverageSummary> {
|
||||
EXTRA_SHIPPED_ENGINE_TYPE_NAMES
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
let mut observed_ordinals = BTreeSet::new();
|
||||
let mut sample_paths = Vec::new();
|
||||
for sample in samples {
|
||||
let matching_ordinals = sample
|
||||
.locomotive_catalog_entries
|
||||
.iter()
|
||||
.filter(|entry| entry.name == name)
|
||||
.map(|entry| entry.locomotive_id)
|
||||
.collect::<Vec<_>>();
|
||||
if matching_ordinals.is_empty() {
|
||||
continue;
|
||||
}
|
||||
observed_ordinals.extend(matching_ordinals);
|
||||
sample_paths.push(sample.path.clone());
|
||||
}
|
||||
sample_paths.sort();
|
||||
RuntimeLocomotiveExtraNameCoverageSummary {
|
||||
name: name.to_string(),
|
||||
observed_in_catalog_file_count: sample_paths.len(),
|
||||
observed_ordinals: observed_ordinals.into_iter().collect(),
|
||||
sample_paths,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn build_descriptor_band_hit_summary(
|
||||
samples: &[RuntimeLocomotiveCatalogTailScanSample],
|
||||
descriptor_ids: std::ops::RangeInclusive<u32>,
|
||||
) -> RuntimeLocomotiveDescriptorBandHitSummary {
|
||||
let mut row_count = 0usize;
|
||||
let mut carrier_paths = Vec::new();
|
||||
let mut descriptor_ids_present = BTreeSet::new();
|
||||
let mut descriptor_labels_present = BTreeSet::new();
|
||||
|
||||
for sample in samples {
|
||||
let matching_rows = sample
|
||||
.descriptor_rows
|
||||
.iter()
|
||||
.filter(|row| descriptor_ids.contains(&row.descriptor_id))
|
||||
.collect::<Vec<_>>();
|
||||
if matching_rows.is_empty() {
|
||||
continue;
|
||||
}
|
||||
row_count += matching_rows.len();
|
||||
carrier_paths.push(sample.path.clone());
|
||||
for row in matching_rows {
|
||||
descriptor_ids_present.insert(row.descriptor_id);
|
||||
if let Some(label) = &row.descriptor_label {
|
||||
descriptor_labels_present.insert(label.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
carrier_paths.sort();
|
||||
RuntimeLocomotiveDescriptorBandHitSummary {
|
||||
row_count,
|
||||
file_count: carrier_paths.len(),
|
||||
descriptor_ids_present: descriptor_ids_present.into_iter().collect(),
|
||||
descriptor_labels_present: descriptor_labels_present.into_iter().collect(),
|
||||
carrier_paths,
|
||||
}
|
||||
}
|
||||
|
||||
fn load_locomotive_catalog_tail_scan_sample(
|
||||
smp_path: &Path,
|
||||
) -> Result<RuntimeLocomotiveCatalogTailScanSample, Box<dyn std::error::Error>> {
|
||||
let save_slice = load_save_slice_file(smp_path)?;
|
||||
Ok(build_locomotive_catalog_tail_scan_sample(
|
||||
smp_path,
|
||||
&save_slice,
|
||||
))
|
||||
}
|
||||
|
||||
fn build_locomotive_catalog_tail_scan_sample(
|
||||
smp_path: &Path,
|
||||
save_slice: &SmpLoadedSaveSlice,
|
||||
) -> RuntimeLocomotiveCatalogTailScanSample {
|
||||
RuntimeLocomotiveCatalogTailScanSample {
|
||||
path: smp_path.display().to_string(),
|
||||
container_profile_family: save_slice.container_profile_family.clone(),
|
||||
mechanism_family: save_slice.mechanism_family.clone(),
|
||||
map_path: save_slice
|
||||
.profile
|
||||
.as_ref()
|
||||
.and_then(|profile| profile.map_path.clone()),
|
||||
display_name: save_slice
|
||||
.profile
|
||||
.as_ref()
|
||||
.and_then(|profile| profile.display_name.clone()),
|
||||
named_locomotive_table_entry_count: save_slice
|
||||
.named_locomotive_availability_table
|
||||
.as_ref()
|
||||
.map(|table| table.observed_entry_count),
|
||||
locomotive_catalog_entries: save_slice
|
||||
.locomotive_catalog
|
||||
.as_ref()
|
||||
.map(|catalog| catalog.entries.clone())
|
||||
.unwrap_or_default(),
|
||||
descriptor_rows: save_slice
|
||||
.event_runtime_collection
|
||||
.as_ref()
|
||||
.map(|summary| {
|
||||
summary
|
||||
.records
|
||||
.iter()
|
||||
.flat_map(|record| {
|
||||
record.grouped_effect_rows.iter().map(|row| {
|
||||
RuntimeLocomotiveDescriptorRowHit {
|
||||
descriptor_id: row.descriptor_id,
|
||||
descriptor_label: row.descriptor_label.clone(),
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_locomotive_tail_input_paths(
|
||||
root_path: &Path,
|
||||
out: &mut Vec<PathBuf>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let metadata = match fs::symlink_metadata(root_path) {
|
||||
Ok(metadata) => metadata,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => return Ok(()),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
if metadata.file_type().is_symlink() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if root_path.is_file() {
|
||||
if path_is_locomotive_tail_input(root_path) {
|
||||
out.push(root_path.to_path_buf());
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let entries = match fs::read_dir(root_path) {
|
||||
Ok(entries) => entries,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => return Ok(()),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
for entry in entries {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
collect_locomotive_tail_input_paths(&path, out)?;
|
||||
continue;
|
||||
}
|
||||
if path_is_locomotive_tail_input(&path) {
|
||||
out.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn path_is_locomotive_tail_input(path: &Path) -> bool {
|
||||
path.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.is_some_and(|ext| matches!(ext.to_ascii_lowercase().as_str(), "gms" | "gmx" | "smp"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn sample(
|
||||
path: &str,
|
||||
names: &[&str],
|
||||
descriptor_ids: &[u32],
|
||||
) -> RuntimeLocomotiveCatalogTailScanSample {
|
||||
RuntimeLocomotiveCatalogTailScanSample {
|
||||
path: path.to_string(),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
map_path: Some(format!("{path}.gmp")),
|
||||
display_name: Some(path.to_string()),
|
||||
named_locomotive_table_entry_count: Some(names.len()),
|
||||
locomotive_catalog_entries: names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, name)| SmpLoadedLocomotiveCatalogEntry {
|
||||
locomotive_id: (index + 1) as u32,
|
||||
name: (*name).to_string(),
|
||||
})
|
||||
.collect(),
|
||||
descriptor_rows: descriptor_ids
|
||||
.iter()
|
||||
.map(|descriptor_id| RuntimeLocomotiveDescriptorRowHit {
|
||||
descriptor_id: *descriptor_id,
|
||||
descriptor_label: Some(format!("Descriptor {descriptor_id}")),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn computes_stable_prefix_length_across_catalog_samples() {
|
||||
let samples = vec![
|
||||
sample("a", &["Big Boy", "VL80T", "242 A1"], &[]),
|
||||
sample("b", &["Big Boy", "VL80T", "GP 35"], &[]),
|
||||
];
|
||||
|
||||
assert_eq!(stable_catalog_prefix_length(&samples), 2);
|
||||
assert_eq!(
|
||||
stable_prefix_last_name(&samples, 2),
|
||||
Some("VL80T".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn groups_tail_clusters_by_exact_tail_sequence() {
|
||||
let samples = vec![
|
||||
sample("a", &["Big Boy", "VL80T", "242 A1", "Class 460"], &[]),
|
||||
sample("b", &["Big Boy", "VL80T", "242 A1", "Class 460"], &[]),
|
||||
sample("c", &["Big Boy", "VL80T", "GP 35", "U1"], &[]),
|
||||
];
|
||||
|
||||
let clusters = build_tail_clusters(&samples, 2);
|
||||
|
||||
assert_eq!(clusters.len(), 2);
|
||||
let counts = clusters
|
||||
.iter()
|
||||
.map(|cluster| cluster.file_count)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(counts, vec![2, 1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn summarizes_extra_shipped_name_coverage() {
|
||||
let samples = vec![
|
||||
sample("a", &["Big Boy", "242 A1", "Class 460"], &[]),
|
||||
sample("b", &["Big Boy", "Class QJ"], &[]),
|
||||
];
|
||||
|
||||
let coverage = build_extra_shipped_name_coverage(&samples);
|
||||
|
||||
assert_eq!(coverage[0].name, "242 A1");
|
||||
assert_eq!(coverage[0].observed_in_catalog_file_count, 1);
|
||||
assert_eq!(coverage[0].observed_ordinals, vec![2]);
|
||||
assert_eq!(coverage[4].name, "Class QJ");
|
||||
assert_eq!(coverage[4].observed_in_catalog_file_count, 1);
|
||||
assert_eq!(coverage[4].observed_ordinals, vec![2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn summarizes_descriptor_band_hits() {
|
||||
let samples = vec![
|
||||
sample("a", &["Big Boy"], &[452, 457, 475]),
|
||||
sample("b", &["Big Boy"], &[457, 458]),
|
||||
];
|
||||
|
||||
let descriptor_452_hits = build_descriptor_band_hit_summary(&samples, 452..=452);
|
||||
let upper_availability_hits = build_descriptor_band_hit_summary(&samples, 457..=474);
|
||||
|
||||
assert_eq!(descriptor_452_hits.row_count, 1);
|
||||
assert_eq!(descriptor_452_hits.file_count, 1);
|
||||
assert_eq!(upper_availability_hits.row_count, 3);
|
||||
assert_eq!(upper_availability_hits.file_count, 2);
|
||||
assert_eq!(
|
||||
upper_availability_hits.descriptor_ids_present,
|
||||
vec![457, 458]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_gmx_inputs_in_locomotive_tail_scan() {
|
||||
assert!(path_is_locomotive_tail_input(Path::new("save.gms")));
|
||||
assert!(path_is_locomotive_tail_input(Path::new("sandbox.gmx")));
|
||||
assert!(path_is_locomotive_tail_input(Path::new("fixture.smp")));
|
||||
assert!(!path_is_locomotive_tail_input(Path::new("map.gmp")));
|
||||
}
|
||||
}
|
||||
|
|
@ -3,11 +3,13 @@ pub(crate) mod post_special;
|
|||
|
||||
mod aligned_band;
|
||||
mod candidate_table;
|
||||
mod locomotive_tail;
|
||||
mod recipe_book;
|
||||
mod special_conditions;
|
||||
|
||||
pub(super) use aligned_band::scan_aligned_runtime_rule_band;
|
||||
pub(super) use candidate_table::{scan_candidate_table_headers, scan_candidate_table_named_runs};
|
||||
pub(super) use locomotive_tail::scan_locomotive_catalog_tail;
|
||||
pub(super) use post_special::{
|
||||
scan_post_special_conditions_scalars, scan_post_special_conditions_tail,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1571,8 +1571,10 @@ fn blocks_scalar_locomotive_availability_rows_without_catalog_context() {
|
|||
grouped_effect_rows: vec![SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index: 0,
|
||||
row_index: 0,
|
||||
descriptor_id: 250,
|
||||
descriptor_label: Some("Big Boy 4-8-8-4 Availability".to_string()),
|
||||
descriptor_id: 302,
|
||||
descriptor_label: Some(
|
||||
"Lower-Band Locomotive Availability Slot 62".to_string(),
|
||||
),
|
||||
target_mask_bits: Some(0x08),
|
||||
parameter_family: Some("locomotive_availability_scalar".to_string()),
|
||||
grouped_target_subject: None,
|
||||
|
|
@ -1587,11 +1589,13 @@ fn blocks_scalar_locomotive_availability_rows_without_catalog_context() {
|
|||
value_word_0x16: 0,
|
||||
row_shape: "scalar_assignment".to_string(),
|
||||
semantic_family: Some("scalar_assignment".to_string()),
|
||||
semantic_preview: Some("Set Big Boy 4-8-8-4 Availability to 42".to_string()),
|
||||
semantic_preview: Some(
|
||||
"Set Lower-Band Locomotive Availability Slot 62 to 42".to_string(),
|
||||
),
|
||||
recovered_cargo_slot: None,
|
||||
recovered_cargo_class: None,
|
||||
recovered_cargo_label: None,
|
||||
recovered_locomotive_id: Some(10),
|
||||
recovered_locomotive_id: Some(62),
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}],
|
||||
|
|
@ -1599,8 +1603,9 @@ fn blocks_scalar_locomotive_availability_rows_without_catalog_context() {
|
|||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec![
|
||||
"decoded from grounded real 0x4e9a row framing".to_string(),
|
||||
"scalar locomotive availability rows still need catalog context".to_string(),
|
||||
"decoded from lower-tail real 0x4e9a row framing".to_string(),
|
||||
"scalar lower-tail locomotive availability row still needs catalog context"
|
||||
.to_string(),
|
||||
],
|
||||
}],
|
||||
}),
|
||||
|
|
@ -1609,7 +1614,7 @@ fn blocks_scalar_locomotive_availability_rows_without_catalog_context() {
|
|||
|
||||
let input = build_runtime_state_input_from_save_slice(
|
||||
&save_slice,
|
||||
"packed-events-recovered-locomotive-availability-frontier",
|
||||
"packed-events-recovered-locomotive-availability-lower-tail-frontier",
|
||||
None,
|
||||
)
|
||||
.expect("save slice should project");
|
||||
|
|
@ -1737,7 +1742,7 @@ fn imports_scalar_locomotive_availability_rows_with_save_derived_catalog_context
|
|||
bridge_family: None,
|
||||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: Some(save_named_locomotive_table(61)),
|
||||
named_locomotive_availability_table: Some(save_named_locomotive_table(62)),
|
||||
locomotive_catalog: None,
|
||||
cargo_catalog: None,
|
||||
world_issue_37_state: None,
|
||||
|
|
@ -1799,7 +1804,7 @@ fn imports_scalar_locomotive_availability_rows_with_save_derived_catalog_context
|
|||
grouped_effect_row_counts: vec![2, 0, 0, 0],
|
||||
grouped_effect_rows: vec![
|
||||
real_locomotive_availability_row(250, 42),
|
||||
real_locomotive_availability_row(301, 7),
|
||||
real_locomotive_availability_row(302, 7),
|
||||
],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
|
|
@ -1820,7 +1825,7 @@ fn imports_scalar_locomotive_availability_rows_with_save_derived_catalog_context
|
|||
)
|
||||
.expect("save slice should project");
|
||||
|
||||
assert_eq!(input.state.locomotive_catalog.len(), 61);
|
||||
assert_eq!(input.state.locomotive_catalog.len(), 62);
|
||||
assert_eq!(input.state.event_runtime_records.len(), 1);
|
||||
assert_eq!(
|
||||
input
|
||||
|
|
@ -1845,11 +1850,228 @@ fn imports_scalar_locomotive_availability_rows_with_save_derived_catalog_context
|
|||
Some(&42)
|
||||
);
|
||||
assert_eq!(
|
||||
input.state.named_locomotive_availability.get("Zephyr"),
|
||||
input
|
||||
.state
|
||||
.named_locomotive_availability
|
||||
.get("Locomotive 62"),
|
||||
Some(&7)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imports_scalar_locomotive_availability_rows_with_dynamic_tail_catalog_context() {
|
||||
let mut names = (0..58)
|
||||
.map(default_save_named_locomotive_name)
|
||||
.collect::<Vec<_>>();
|
||||
names.extend([
|
||||
"242 A1".to_string(),
|
||||
"Class 460".to_string(),
|
||||
"Class A1".to_string(),
|
||||
"Class P8".to_string(),
|
||||
"U1".to_string(),
|
||||
]);
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
trailer_family: None,
|
||||
bridge_family: None,
|
||||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: Some(save_named_locomotive_table_with_names(&names)),
|
||||
locomotive_catalog: None,
|
||||
cargo_catalog: None,
|
||||
world_issue_37_state: None,
|
||||
world_economic_tuning_state: None,
|
||||
world_finance_neighborhood_state: None,
|
||||
world_locomotive_policy_state: None,
|
||||
company_roster: None,
|
||||
chairman_profile_table: None,
|
||||
region_collection: None,
|
||||
region_fixed_row_run_summary: None,
|
||||
placed_structure_collection: None,
|
||||
placed_structure_dynamic_side_buffer_summary: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(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_with_trigger_kind: 0,
|
||||
records_missing_trigger_kind: 0,
|
||||
nondirect_compact_record_count: 0,
|
||||
nondirect_compact_records_missing_trigger_kind: 0,
|
||||
trigger_kinds_present: vec![],
|
||||
control_lane_notes: vec![],
|
||||
add_building_dispatch_strip_record_indexes: vec![],
|
||||
add_building_dispatch_strip_descriptor_labels: vec![],
|
||||
add_building_dispatch_strip_records_with_trigger_kind: 0,
|
||||
add_building_dispatch_strip_records_missing_trigger_kind: 0,
|
||||
add_building_dispatch_strip_row_shape_families: vec![],
|
||||
add_building_dispatch_strip_signature_families: vec![],
|
||||
add_building_dispatch_strip_condition_tuple_families: vec![],
|
||||
add_building_dispatch_strip_signature_condition_clusters: vec![],
|
||||
records: vec![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_availability_row(299, 7)],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec![
|
||||
"save-derived locomotive availability row uses scenario-dependent tail names"
|
||||
.to_string(),
|
||||
],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let mut input = build_runtime_state_input_from_save_slice(
|
||||
&save_slice,
|
||||
"save-derived-dynamic-tail-locomotive-availability",
|
||||
None,
|
||||
)
|
||||
.expect("save slice should project");
|
||||
|
||||
execute_step_command(
|
||||
&mut input.state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||
)
|
||||
.expect("save-derived dynamic-tail locomotive availability record should run");
|
||||
|
||||
assert_eq!(
|
||||
input.state.named_locomotive_availability.get("242 A1"),
|
||||
Some(&7)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_upper_band_locomotive_availability_rows_on_descriptor_parity() {
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
trailer_family: None,
|
||||
bridge_family: None,
|
||||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: Some(save_named_locomotive_table(140)),
|
||||
locomotive_catalog: None,
|
||||
cargo_catalog: None,
|
||||
world_issue_37_state: None,
|
||||
world_economic_tuning_state: None,
|
||||
world_finance_neighborhood_state: None,
|
||||
world_locomotive_policy_state: None,
|
||||
company_roster: None,
|
||||
chairman_profile_table: None,
|
||||
region_collection: None,
|
||||
region_fixed_row_run_summary: None,
|
||||
placed_structure_collection: None,
|
||||
placed_structure_dynamic_side_buffer_summary: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(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_with_trigger_kind: 0,
|
||||
records_missing_trigger_kind: 0,
|
||||
nondirect_compact_record_count: 0,
|
||||
nondirect_compact_records_missing_trigger_kind: 0,
|
||||
trigger_kinds_present: vec![],
|
||||
control_lane_notes: vec![],
|
||||
add_building_dispatch_strip_record_indexes: vec![],
|
||||
add_building_dispatch_strip_descriptor_labels: vec![],
|
||||
add_building_dispatch_strip_records_with_trigger_kind: 0,
|
||||
add_building_dispatch_strip_records_missing_trigger_kind: 0,
|
||||
add_building_dispatch_strip_row_shape_families: vec![],
|
||||
add_building_dispatch_strip_signature_families: vec![],
|
||||
add_building_dispatch_strip_condition_tuple_families: vec![],
|
||||
add_building_dispatch_strip_signature_condition_clusters: vec![],
|
||||
records: vec![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_availability_row(457, 1)],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec![
|
||||
"upper-band locomotive availability row remains descriptor parity".to_string(),
|
||||
],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let input = build_runtime_state_input_from_save_slice(
|
||||
&save_slice,
|
||||
"packed-events-upper-band-locomotive-availability",
|
||||
None,
|
||||
)
|
||||
.expect("save slice should project");
|
||||
|
||||
assert!(input.state.event_runtime_records.is_empty());
|
||||
assert_eq!(
|
||||
input
|
||||
.state
|
||||
.packed_event_collection
|
||||
.as_ref()
|
||||
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
|
||||
Some("blocked_evidence_blocked_descriptor")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlays_scalar_locomotive_availability_rows_into_named_availability_effects() {
|
||||
let base_state = RuntimeState {
|
||||
|
|
@ -2099,11 +2321,13 @@ fn blocks_recovered_locomotive_cost_rows_without_catalog_context_lower_band() {
|
|||
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)],
|
||||
grouped_effect_rows: vec![real_locomotive_cost_row(413, 250000)],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec!["scalar locomotive cost row still needs catalog context".to_string()],
|
||||
notes: vec![
|
||||
"scalar lower-tail locomotive cost row still needs catalog context".to_string(),
|
||||
],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
|
|
@ -2237,7 +2461,7 @@ fn imports_scalar_locomotive_cost_rows_with_save_derived_catalog_context() {
|
|||
bridge_family: None,
|
||||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: Some(save_named_locomotive_table(61)),
|
||||
named_locomotive_availability_table: Some(save_named_locomotive_table(62)),
|
||||
locomotive_catalog: None,
|
||||
cargo_catalog: None,
|
||||
world_issue_37_state: None,
|
||||
|
|
@ -2299,7 +2523,7 @@ fn imports_scalar_locomotive_cost_rows_with_save_derived_catalog_context() {
|
|||
grouped_effect_row_counts: vec![2, 0, 0, 0],
|
||||
grouped_effect_rows: vec![
|
||||
real_locomotive_cost_row(352, 250000),
|
||||
real_locomotive_cost_row(412, 325000),
|
||||
real_locomotive_cost_row(413, 325000),
|
||||
],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
|
|
@ -2319,7 +2543,7 @@ fn imports_scalar_locomotive_cost_rows_with_save_derived_catalog_context() {
|
|||
)
|
||||
.expect("save slice should project");
|
||||
|
||||
assert_eq!(input.state.locomotive_catalog.len(), 61);
|
||||
assert_eq!(input.state.locomotive_catalog.len(), 62);
|
||||
assert_eq!(input.state.event_runtime_records.len(), 1);
|
||||
assert_eq!(
|
||||
input
|
||||
|
|
@ -2341,11 +2565,110 @@ fn imports_scalar_locomotive_cost_rows_with_save_derived_catalog_context() {
|
|||
Some(&250000)
|
||||
);
|
||||
assert_eq!(
|
||||
input.state.named_locomotive_cost.get("Zephyr"),
|
||||
input.state.named_locomotive_cost.get("Locomotive 62"),
|
||||
Some(&325000)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_upper_band_locomotive_cost_rows_on_descriptor_parity() {
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
trailer_family: None,
|
||||
bridge_family: None,
|
||||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: Some(save_named_locomotive_table(140)),
|
||||
locomotive_catalog: None,
|
||||
cargo_catalog: None,
|
||||
world_issue_37_state: None,
|
||||
world_economic_tuning_state: None,
|
||||
world_finance_neighborhood_state: None,
|
||||
world_locomotive_policy_state: None,
|
||||
company_roster: None,
|
||||
chairman_profile_table: None,
|
||||
region_collection: None,
|
||||
region_fixed_row_run_summary: None,
|
||||
placed_structure_collection: None,
|
||||
placed_structure_dynamic_side_buffer_summary: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(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: 42,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![42],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records_with_trigger_kind: 0,
|
||||
records_missing_trigger_kind: 0,
|
||||
nondirect_compact_record_count: 0,
|
||||
nondirect_compact_records_missing_trigger_kind: 0,
|
||||
trigger_kinds_present: vec![],
|
||||
control_lane_notes: vec![],
|
||||
add_building_dispatch_strip_record_indexes: vec![],
|
||||
add_building_dispatch_strip_descriptor_labels: vec![],
|
||||
add_building_dispatch_strip_records_with_trigger_kind: 0,
|
||||
add_building_dispatch_strip_records_missing_trigger_kind: 0,
|
||||
add_building_dispatch_strip_row_shape_families: vec![],
|
||||
add_building_dispatch_strip_signature_families: vec![],
|
||||
add_building_dispatch_strip_condition_tuple_families: vec![],
|
||||
add_building_dispatch_strip_signature_condition_clusters: vec![],
|
||||
records: vec![SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 42,
|
||||
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(475, 250000)],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec!["upper-band locomotive cost row remains descriptor parity".to_string()],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let input = build_runtime_state_input_from_save_slice(
|
||||
&save_slice,
|
||||
"packed-events-upper-band-locomotive-cost",
|
||||
None,
|
||||
)
|
||||
.expect("save slice should project");
|
||||
|
||||
assert!(input.state.event_runtime_records.is_empty());
|
||||
assert_eq!(
|
||||
input
|
||||
.state
|
||||
.packed_event_collection
|
||||
.as_ref()
|
||||
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
|
||||
Some("blocked_evidence_blocked_descriptor")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlays_scalar_locomotive_cost_rows_into_named_cost_effects() {
|
||||
let base_state = RuntimeState {
|
||||
|
|
@ -2519,6 +2842,119 @@ fn overlays_scalar_locomotive_cost_rows_into_named_cost_effects() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imports_scalar_locomotive_cost_rows_with_dynamic_tail_catalog_context() {
|
||||
let mut names = (0..58)
|
||||
.map(default_save_named_locomotive_name)
|
||||
.collect::<Vec<_>>();
|
||||
names.extend([
|
||||
"242 A1".to_string(),
|
||||
"Class 460".to_string(),
|
||||
"Class A1".to_string(),
|
||||
"Class P8".to_string(),
|
||||
"U1".to_string(),
|
||||
]);
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
trailer_family: None,
|
||||
bridge_family: None,
|
||||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
named_locomotive_availability_table: Some(save_named_locomotive_table_with_names(&names)),
|
||||
locomotive_catalog: None,
|
||||
cargo_catalog: None,
|
||||
world_issue_37_state: None,
|
||||
world_economic_tuning_state: None,
|
||||
world_finance_neighborhood_state: None,
|
||||
world_locomotive_policy_state: None,
|
||||
company_roster: None,
|
||||
chairman_profile_table: None,
|
||||
region_collection: None,
|
||||
region_fixed_row_run_summary: None,
|
||||
placed_structure_collection: None,
|
||||
placed_structure_dynamic_side_buffer_summary: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(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: 37,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![37],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records_with_trigger_kind: 0,
|
||||
records_missing_trigger_kind: 0,
|
||||
nondirect_compact_record_count: 0,
|
||||
nondirect_compact_records_missing_trigger_kind: 0,
|
||||
trigger_kinds_present: vec![],
|
||||
control_lane_notes: vec![],
|
||||
add_building_dispatch_strip_record_indexes: vec![],
|
||||
add_building_dispatch_strip_descriptor_labels: vec![],
|
||||
add_building_dispatch_strip_records_with_trigger_kind: 0,
|
||||
add_building_dispatch_strip_records_missing_trigger_kind: 0,
|
||||
add_building_dispatch_strip_row_shape_families: vec![],
|
||||
add_building_dispatch_strip_signature_families: vec![],
|
||||
add_building_dispatch_strip_condition_tuple_families: vec![],
|
||||
add_building_dispatch_strip_signature_condition_clusters: vec![],
|
||||
records: vec![SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 37,
|
||||
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(410, 325000)],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec![
|
||||
"save-derived locomotive cost row uses scenario-dependent tail names"
|
||||
.to_string(),
|
||||
],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let mut input = build_runtime_state_input_from_save_slice(
|
||||
&save_slice,
|
||||
"save-derived-dynamic-tail-locomotive-cost",
|
||||
None,
|
||||
)
|
||||
.expect("save slice should project");
|
||||
|
||||
execute_step_command(
|
||||
&mut input.state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||
)
|
||||
.expect("save-derived dynamic-tail locomotive cost record should run");
|
||||
|
||||
assert_eq!(
|
||||
input.state.named_locomotive_cost.get("242 A1"),
|
||||
Some(&325000)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_negative_locomotive_cost_rows_parity_only() {
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
|
|
|
|||
|
|
@ -590,9 +590,6 @@ pub(super) fn real_locomotive_availability_row(
|
|||
56 => Some("Trans-Euro"),
|
||||
57 => Some("V200"),
|
||||
58 => Some("VL80T"),
|
||||
59 => Some("GP 35"),
|
||||
60 => Some("U1"),
|
||||
61 => Some("Zephyr"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -709,9 +706,6 @@ pub(super) fn real_locomotive_cost_row(
|
|||
56 => Some("Trans-Euro"),
|
||||
57 => Some("V200"),
|
||||
58 => Some("VL80T"),
|
||||
59 => Some("GP 35"),
|
||||
60 => Some("U1"),
|
||||
61 => Some("Zephyr"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -759,77 +753,78 @@ pub(super) fn real_locomotive_cost_row(
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn save_named_locomotive_table(
|
||||
count: usize,
|
||||
) -> SmpLoadedNamedLocomotiveAvailabilityTable {
|
||||
fn grounded_locomotive_name(index: usize) -> String {
|
||||
match index {
|
||||
0 => "2-D-2",
|
||||
1 => "E-88",
|
||||
2 => "Adler 2-2-2",
|
||||
3 => "USA 103",
|
||||
4 => "American 4-4-0",
|
||||
5 => "Atlantic 4-4-2",
|
||||
6 => "Baldwin 0-6-0",
|
||||
7 => "Be 5/7",
|
||||
8 => "Beuth 2-2-2",
|
||||
9 => "Big Boy 4-8-8-4",
|
||||
10 => "C55 Deltic",
|
||||
11 => "Camelback 0-6-0",
|
||||
12 => "Challenger 4-6-6-4",
|
||||
13 => "Class 01 4-6-2",
|
||||
14 => "Class 103",
|
||||
15 => "Class 132",
|
||||
16 => "Class 500 4-6-0",
|
||||
17 => "Class 9100",
|
||||
18 => "Class EF 66",
|
||||
19 => "Class 6E",
|
||||
20 => "Consolidation 2-8-0",
|
||||
21 => "Crampton 4-2-0",
|
||||
22 => "DD 080-X",
|
||||
23 => "DD40AX",
|
||||
24 => "Duke Class 4-4-0",
|
||||
25 => "E18",
|
||||
26 => "E428",
|
||||
27 => "Brenner E412",
|
||||
28 => "E60CP",
|
||||
29 => "Eight Wheeler 4-4-0",
|
||||
30 => "EP-2 Bipolar",
|
||||
31 => "ET22",
|
||||
32 => "F3",
|
||||
33 => "Fairlie 0-6-6-0",
|
||||
34 => "Firefly 2-2-2",
|
||||
35 => "FP45",
|
||||
36 => "Ge 6/6 Crocodile",
|
||||
37 => "GG1",
|
||||
38 => "GP7",
|
||||
39 => "H10 2-8-2",
|
||||
40 => "HST 125",
|
||||
41 => "Kriegslok 2-10-0",
|
||||
42 => "Mallard 4-6-2",
|
||||
43 => "Norris 4-2-0",
|
||||
44 => "Northern 4-8-4",
|
||||
45 => "Orca NX462",
|
||||
46 => "Pacific 4-6-2",
|
||||
47 => "Planet 2-2-0",
|
||||
48 => "Re 6/6",
|
||||
49 => "Red Devil 4-8-4",
|
||||
50 => "S3 4-4-0",
|
||||
51 => "NA-90D",
|
||||
52 => "Shay (2-Truck)",
|
||||
53 => "Shinkansen Series 0",
|
||||
54 => "Stirling 4-2-2",
|
||||
55 => "Trans-Euro",
|
||||
56 => "V200",
|
||||
57 => "VL80T",
|
||||
58 => "GP 35",
|
||||
59 => "U1",
|
||||
60 => "Zephyr",
|
||||
_ => return format!("Locomotive {}", index + 1),
|
||||
}
|
||||
.to_string()
|
||||
pub(super) fn default_save_named_locomotive_name(index: usize) -> String {
|
||||
match index {
|
||||
0 => "2-D-2",
|
||||
1 => "E-88",
|
||||
2 => "Adler 2-2-2",
|
||||
3 => "USA 103",
|
||||
4 => "American 4-4-0",
|
||||
5 => "Atlantic 4-4-2",
|
||||
6 => "Baldwin 0-6-0",
|
||||
7 => "Be 5/7",
|
||||
8 => "Beuth 2-2-2",
|
||||
9 => "Big Boy 4-8-8-4",
|
||||
10 => "C55 Deltic",
|
||||
11 => "Camelback 0-6-0",
|
||||
12 => "Challenger 4-6-6-4",
|
||||
13 => "Class 01 4-6-2",
|
||||
14 => "Class 103",
|
||||
15 => "Class 132",
|
||||
16 => "Class 500 4-6-0",
|
||||
17 => "Class 9100",
|
||||
18 => "Class EF 66",
|
||||
19 => "Class 6E",
|
||||
20 => "Consolidation 2-8-0",
|
||||
21 => "Crampton 4-2-0",
|
||||
22 => "DD 080-X",
|
||||
23 => "DD40AX",
|
||||
24 => "Duke Class 4-4-0",
|
||||
25 => "E18",
|
||||
26 => "E428",
|
||||
27 => "Brenner E412",
|
||||
28 => "E60CP",
|
||||
29 => "Eight Wheeler 4-4-0",
|
||||
30 => "EP-2 Bipolar",
|
||||
31 => "ET22",
|
||||
32 => "F3",
|
||||
33 => "Fairlie 0-6-6-0",
|
||||
34 => "Firefly 2-2-2",
|
||||
35 => "FP45",
|
||||
36 => "Ge 6/6 Crocodile",
|
||||
37 => "GG1",
|
||||
38 => "GP7",
|
||||
39 => "H10 2-8-2",
|
||||
40 => "HST 125",
|
||||
41 => "Kriegslok 2-10-0",
|
||||
42 => "Mallard 4-6-2",
|
||||
43 => "Norris 4-2-0",
|
||||
44 => "Northern 4-8-4",
|
||||
45 => "Orca NX462",
|
||||
46 => "Pacific 4-6-2",
|
||||
47 => "Planet 2-2-0",
|
||||
48 => "Re 6/6",
|
||||
49 => "Red Devil 4-8-4",
|
||||
50 => "S3 4-4-0",
|
||||
51 => "NA-90D",
|
||||
52 => "Shay (2-Truck)",
|
||||
53 => "Shinkansen Series 0",
|
||||
54 => "Stirling 4-2-2",
|
||||
55 => "Trans-Euro",
|
||||
56 => "V200",
|
||||
57 => "VL80T",
|
||||
58 => "GP 35",
|
||||
59 => "U1",
|
||||
60 => "Zephyr",
|
||||
_ => return format!("Locomotive {}", index + 1),
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub(super) fn save_named_locomotive_table_with_names(
|
||||
names: &[String],
|
||||
) -> SmpLoadedNamedLocomotiveAvailabilityTable {
|
||||
let count = names.len();
|
||||
SmpLoadedNamedLocomotiveAvailabilityTable {
|
||||
source_kind: "runtime-save-direct-serializer".to_string(),
|
||||
semantic_family: "scenario-named-locomotive-availability-table".to_string(),
|
||||
|
|
@ -839,11 +834,13 @@ pub(super) fn save_named_locomotive_table(
|
|||
observed_entry_count: count,
|
||||
zero_availability_count: 0,
|
||||
zero_availability_names: vec![],
|
||||
entries: (0..count)
|
||||
.map(|index| SmpRt3105SaveNameTableEntry {
|
||||
entries: names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, name)| SmpRt3105SaveNameTableEntry {
|
||||
index,
|
||||
offset: 0x7c78 + index * 0x41,
|
||||
text: grounded_locomotive_name(index),
|
||||
text: name.clone(),
|
||||
availability_dword: 1,
|
||||
availability_dword_hex: "0x00000001".to_string(),
|
||||
trailer_word: 1,
|
||||
|
|
@ -853,6 +850,16 @@ pub(super) fn save_named_locomotive_table(
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn save_named_locomotive_table(
|
||||
count: usize,
|
||||
) -> SmpLoadedNamedLocomotiveAvailabilityTable {
|
||||
let names = (0..count)
|
||||
.map(default_save_named_locomotive_name)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
save_named_locomotive_table_with_names(&names)
|
||||
}
|
||||
|
||||
pub(super) fn save_cargo_catalog(
|
||||
entries: &[(u32, crate::event::targets::RuntimeCargoClass)],
|
||||
) -> SmpLoadedCargoCatalog {
|
||||
|
|
|
|||
|
|
@ -2,24 +2,21 @@ use super::super::super::*;
|
|||
use std::collections::BTreeMap;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
const STATIC_GROUNDED_LOCOMOTIVE_NAME_MAX_ID: u32 = 58;
|
||||
|
||||
pub(in crate::inspect::smp) fn recovered_locomotive_availability_descriptor_metadata(
|
||||
descriptor_id: u32,
|
||||
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
||||
if let Some(loco_id) = recovered_locomotive_availability_loco_id(descriptor_id) {
|
||||
let label = recovered_locomotive_availability_label(loco_id);
|
||||
let executable_in_runtime = (loco_id as usize) <= GROUNDED_LOCOMOTIVE_PREFIX.len();
|
||||
return Some(RealGroupedEffectDescriptorMetadata {
|
||||
descriptor_id,
|
||||
label,
|
||||
target_mask_bits: 0x08,
|
||||
parameter_family: "locomotive_availability_scalar",
|
||||
runtime_key: None,
|
||||
runtime_status: if executable_in_runtime {
|
||||
RealGroupedEffectRuntimeStatus::Executable
|
||||
} else {
|
||||
RealGroupedEffectRuntimeStatus::EvidenceBlocked
|
||||
},
|
||||
executable_in_runtime,
|
||||
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
||||
executable_in_runtime: true,
|
||||
});
|
||||
}
|
||||
(457..=474)
|
||||
|
|
@ -45,6 +42,9 @@ pub(in crate::inspect::smp) fn recovered_locomotive_availability_loco_id(
|
|||
}
|
||||
|
||||
pub(in crate::inspect::smp) fn grounded_locomotive_name(loco_id: u32) -> Option<&'static str> {
|
||||
if loco_id > STATIC_GROUNDED_LOCOMOTIVE_NAME_MAX_ID {
|
||||
return None;
|
||||
}
|
||||
let index = loco_id.checked_sub(1)? as usize;
|
||||
GROUNDED_LOCOMOTIVE_PREFIX.get(index).copied()
|
||||
}
|
||||
|
|
@ -153,8 +153,7 @@ pub(in crate::inspect::smp) fn recovered_locomotive_cost_descriptor_metadata(
|
|||
descriptor_id: u32,
|
||||
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
||||
recovered_locomotive_cost_label(descriptor_id).map(|label| {
|
||||
let executable_in_runtime = recovered_locomotive_cost_loco_id(descriptor_id)
|
||||
.is_some_and(|loco_id| (loco_id as usize) <= GROUNDED_LOCOMOTIVE_PREFIX.len());
|
||||
let executable_in_runtime = recovered_locomotive_cost_loco_id(descriptor_id).is_some();
|
||||
RealGroupedEffectDescriptorMetadata {
|
||||
descriptor_id,
|
||||
label,
|
||||
|
|
|
|||
|
|
@ -223,11 +223,33 @@ fn looks_up_upper_band_recovered_locomotive_availability_descriptor_metadata() {
|
|||
#[test]
|
||||
fn looks_up_extended_lower_band_locomotive_availability_descriptor_metadata() {
|
||||
let metadata =
|
||||
real_grouped_effect_descriptor_metadata(301).expect("descriptor metadata should exist");
|
||||
real_grouped_effect_descriptor_metadata(298).expect("descriptor metadata should exist");
|
||||
|
||||
assert_eq!(metadata.label, "Zephyr Availability");
|
||||
assert_eq!(metadata.label, "VL80T Availability");
|
||||
assert_eq!(metadata.parameter_family, "locomotive_availability_scalar");
|
||||
assert_eq!(recovered_locomotive_availability_loco_id(301), Some(61));
|
||||
assert_eq!(recovered_locomotive_availability_loco_id(298), Some(58));
|
||||
assert!(metadata.executable_in_runtime);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_up_first_unstable_lower_band_locomotive_availability_descriptor_metadata() {
|
||||
let metadata =
|
||||
real_grouped_effect_descriptor_metadata(299).expect("descriptor metadata should exist");
|
||||
|
||||
assert_eq!(metadata.label, "Lower-Band Locomotive Availability Slot 59");
|
||||
assert_eq!(metadata.parameter_family, "locomotive_availability_scalar");
|
||||
assert_eq!(recovered_locomotive_availability_loco_id(299), Some(59));
|
||||
assert!(metadata.executable_in_runtime);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_up_lower_tail_locomotive_availability_descriptor_metadata() {
|
||||
let metadata =
|
||||
real_grouped_effect_descriptor_metadata(302).expect("descriptor metadata should exist");
|
||||
|
||||
assert_eq!(metadata.label, "Lower-Band Locomotive Availability Slot 62");
|
||||
assert_eq!(metadata.parameter_family, "locomotive_availability_scalar");
|
||||
assert_eq!(recovered_locomotive_availability_loco_id(302), Some(62));
|
||||
assert!(metadata.executable_in_runtime);
|
||||
}
|
||||
|
||||
|
|
@ -359,11 +381,33 @@ fn looks_up_recovered_upper_band_locomotive_cost_descriptor_metadata() {
|
|||
#[test]
|
||||
fn looks_up_extended_lower_band_locomotive_cost_descriptor_metadata() {
|
||||
let metadata =
|
||||
real_grouped_effect_descriptor_metadata(412).expect("descriptor metadata should exist");
|
||||
real_grouped_effect_descriptor_metadata(409).expect("descriptor metadata should exist");
|
||||
|
||||
assert_eq!(metadata.label, "Zephyr Cost");
|
||||
assert_eq!(metadata.label, "VL80T Cost");
|
||||
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
|
||||
assert_eq!(recovered_locomotive_cost_loco_id(412), Some(61));
|
||||
assert_eq!(recovered_locomotive_cost_loco_id(409), Some(58));
|
||||
assert!(metadata.executable_in_runtime);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_up_first_unstable_lower_band_locomotive_cost_descriptor_metadata() {
|
||||
let metadata =
|
||||
real_grouped_effect_descriptor_metadata(410).expect("descriptor metadata should exist");
|
||||
|
||||
assert_eq!(metadata.label, "Lower-Band Locomotive Cost Slot 59");
|
||||
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
|
||||
assert_eq!(recovered_locomotive_cost_loco_id(410), Some(59));
|
||||
assert!(metadata.executable_in_runtime);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_up_lower_tail_locomotive_cost_descriptor_metadata() {
|
||||
let metadata =
|
||||
real_grouped_effect_descriptor_metadata(413).expect("descriptor metadata should exist");
|
||||
|
||||
assert_eq!(metadata.label, "Lower-Band Locomotive Cost Slot 62");
|
||||
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
|
||||
assert_eq!(recovered_locomotive_cost_loco_id(413), Some(62));
|
||||
assert!(metadata.executable_in_runtime);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue