Scan numbered candidate table run families
This commit is contained in:
parent
87a5667c9e
commit
dd973ad44b
3 changed files with 264 additions and 1 deletions
|
|
@ -246,6 +246,9 @@ enum Command {
|
|||
RuntimeScanCandidateTableHeaders {
|
||||
root_path: PathBuf,
|
||||
},
|
||||
RuntimeScanCandidateTableNamedRuns {
|
||||
root_path: PathBuf,
|
||||
},
|
||||
RuntimeScanSpecialConditions {
|
||||
root_path: PathBuf,
|
||||
},
|
||||
|
|
@ -857,6 +860,41 @@ struct RuntimeCandidateTableHeaderScanSample {
|
|||
zero_trailer_entry_names: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct RuntimeCandidateTableNamedRun {
|
||||
prefix: String,
|
||||
start_index: usize,
|
||||
end_index: usize,
|
||||
count: usize,
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
start_offset: usize,
|
||||
end_offset: usize,
|
||||
distinct_trailer_hex_words: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct RuntimeCandidateTableNamedRunScanSample {
|
||||
path: String,
|
||||
profile_family: String,
|
||||
source_kind: String,
|
||||
observed_entry_count: usize,
|
||||
port_runs: Vec<RuntimeCandidateTableNamedRun>,
|
||||
warehouse_runs: Vec<RuntimeCandidateTableNamedRun>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct RuntimeCandidateTableNamedRunScanReport {
|
||||
root_path: String,
|
||||
file_count: usize,
|
||||
files_with_probe_count: usize,
|
||||
files_with_any_numbered_port_runs_count: usize,
|
||||
files_with_any_numbered_warehouse_runs_count: usize,
|
||||
files_with_both_numbered_run_families_count: usize,
|
||||
skipped_file_count: usize,
|
||||
samples: Vec<RuntimeCandidateTableNamedRunScanSample>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct RuntimeSpecialConditionsScanSample {
|
||||
path: String,
|
||||
|
|
@ -1220,6 +1258,9 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
Command::RuntimeScanCandidateTableHeaders { root_path } => {
|
||||
run_runtime_scan_candidate_table_headers(&root_path)?;
|
||||
}
|
||||
Command::RuntimeScanCandidateTableNamedRuns { root_path } => {
|
||||
run_runtime_scan_candidate_table_named_runs(&root_path)?;
|
||||
}
|
||||
Command::RuntimeScanSpecialConditions { root_path } => {
|
||||
run_runtime_scan_special_conditions(&root_path)?;
|
||||
}
|
||||
|
|
@ -1603,6 +1644,13 @@ fn parse_command() -> Result<Command, Box<dyn std::error::Error>> {
|
|||
root_path: PathBuf::from(root_path),
|
||||
})
|
||||
}
|
||||
[command, subcommand, root_path]
|
||||
if command == "runtime" && subcommand == "scan-candidate-table-named-runs" =>
|
||||
{
|
||||
Ok(Command::RuntimeScanCandidateTableNamedRuns {
|
||||
root_path: PathBuf::from(root_path),
|
||||
})
|
||||
}
|
||||
[command, subcommand, root_path]
|
||||
if command == "runtime" && subcommand == "scan-special-conditions" =>
|
||||
{
|
||||
|
|
@ -1647,7 +1695,7 @@ fn parse_command() -> Result<Command, Box<dyn std::error::Error>> {
|
|||
})
|
||||
}
|
||||
_ => Err(
|
||||
"usage: rrt-cli [validate [repo-root] | finance eval <snapshot.json> | finance diff <left.json> <right.json> | runtime validate-fixture <fixture.json> | runtime summarize-fixture <fixture.json> | runtime export-fixture-state <fixture.json> <snapshot.json> | runtime diff-state <left.json> <right.json> | runtime summarize-state <snapshot.json> | runtime import-state <input.json> <snapshot.json> | runtime inspect-smp <file.smp> | runtime inspect-candidate-table <file.smp> | runtime inspect-compact-event-dispatch-cluster <maps-dir> | runtime inspect-compact-event-dispatch-cluster-counts <maps-dir> | runtime inspect-map-title-hints <maps-dir> | runtime summarize-save-load <file.smp> | runtime load-save-slice <file.smp> | runtime inspect-save-company-chairman <file.smp> | runtime inspect-save-placed-structure-triplets <file.smp> | runtime compare-region-fixed-row-runs <left.gms> <right.gms> | runtime inspect-periodic-company-service-trace <file.smp> | runtime inspect-region-service-trace <file.smp> | runtime inspect-infrastructure-asset-trace <file.smp> | runtime inspect-save-region-queued-notice-records <file.smp> | runtime inspect-placed-structure-dynamic-side-buffer <file.smp> | runtime inspect-unclassified-save-collections <file.smp> | runtime import-save-state <file.smp> <snapshot.json> | runtime export-save-slice <file.smp> <save-slice.json> | runtime export-overlay-import <snapshot.json> <save-slice.json> <overlay-import.json> | runtime inspect-pk4 <file.pk4> | runtime inspect-cargo-types <CargoTypes-dir> | runtime inspect-building-type-sources <BuildingTypes-dir> [building-bindings.json] | runtime inspect-cargo-skins <Cargo106.PK4> | runtime inspect-cargo-economy-sources <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-cargo-production-selector <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-cargo-price-selector <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-win <file.win> | runtime extract-pk4-entry <file.pk4> <entry-name> <output-path> | runtime inspect-campaign-exe <RT3.exe> | runtime compare-classic-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-105-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-candidate-table <file1> <file2> [fileN...] | runtime compare-recipe-book-lines <file1> <file2> [fileN...] | runtime compare-setup-payload-core <file1> <file2> [fileN...] | runtime compare-setup-launch-payload <file1> <file2> [fileN...] | runtime compare-post-special-conditions-scalars <file1> <file2> [fileN...] | runtime scan-candidate-table-headers <root-dir> | runtime scan-special-conditions <root-dir> | runtime scan-aligned-runtime-rule-band <root-dir> | runtime scan-post-special-conditions-scalars <root-dir> | runtime scan-post-special-conditions-tail <root-dir> | runtime scan-recipe-book-lines <root-dir> | runtime export-profile-block <save.gms> <profile.json>]"
|
||||
"usage: rrt-cli [validate [repo-root] | finance eval <snapshot.json> | finance diff <left.json> <right.json> | runtime validate-fixture <fixture.json> | runtime summarize-fixture <fixture.json> | runtime export-fixture-state <fixture.json> <snapshot.json> | runtime diff-state <left.json> <right.json> | runtime summarize-state <snapshot.json> | runtime import-state <input.json> <snapshot.json> | runtime inspect-smp <file.smp> | runtime inspect-candidate-table <file.smp> | runtime inspect-compact-event-dispatch-cluster <maps-dir> | runtime inspect-compact-event-dispatch-cluster-counts <maps-dir> | runtime inspect-map-title-hints <maps-dir> | runtime summarize-save-load <file.smp> | runtime load-save-slice <file.smp> | runtime inspect-save-company-chairman <file.smp> | runtime inspect-save-placed-structure-triplets <file.smp> | runtime compare-region-fixed-row-runs <left.gms> <right.gms> | runtime inspect-periodic-company-service-trace <file.smp> | runtime inspect-region-service-trace <file.smp> | runtime inspect-infrastructure-asset-trace <file.smp> | runtime inspect-save-region-queued-notice-records <file.smp> | runtime inspect-placed-structure-dynamic-side-buffer <file.smp> | runtime inspect-unclassified-save-collections <file.smp> | runtime import-save-state <file.smp> <snapshot.json> | runtime export-save-slice <file.smp> <save-slice.json> | runtime export-overlay-import <snapshot.json> <save-slice.json> <overlay-import.json> | runtime inspect-pk4 <file.pk4> | runtime inspect-cargo-types <CargoTypes-dir> | runtime inspect-building-type-sources <BuildingTypes-dir> [building-bindings.json] | runtime inspect-cargo-skins <Cargo106.PK4> | runtime inspect-cargo-economy-sources <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-cargo-production-selector <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-cargo-price-selector <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-win <file.win> | runtime extract-pk4-entry <file.pk4> <entry-name> <output-path> | runtime inspect-campaign-exe <RT3.exe> | runtime compare-classic-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-105-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-candidate-table <file1> <file2> [fileN...] | runtime compare-recipe-book-lines <file1> <file2> [fileN...] | runtime compare-setup-payload-core <file1> <file2> [fileN...] | runtime compare-setup-launch-payload <file1> <file2> [fileN...] | runtime compare-post-special-conditions-scalars <file1> <file2> [fileN...] | runtime scan-candidate-table-headers <root-dir> | runtime scan-candidate-table-named-runs <root-dir> | runtime scan-special-conditions <root-dir> | runtime scan-aligned-runtime-rule-band <root-dir> | runtime scan-post-special-conditions-scalars <root-dir> | runtime scan-post-special-conditions-tail <root-dir> | runtime scan-recipe-book-lines <root-dir> | runtime export-profile-block <save.gms> <profile.json>]"
|
||||
.into(),
|
||||
),
|
||||
}
|
||||
|
|
@ -2938,6 +2986,50 @@ fn run_runtime_scan_candidate_table_headers(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn run_runtime_scan_candidate_table_named_runs(
|
||||
root_path: &Path,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut candidate_paths = Vec::new();
|
||||
collect_candidate_table_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_candidate_table_named_run_scan_sample(&path) {
|
||||
Ok(sample) => samples.push(sample),
|
||||
Err(_) => skipped_file_count += 1,
|
||||
}
|
||||
}
|
||||
|
||||
let files_with_probe_count = samples.len();
|
||||
let files_with_any_numbered_port_runs_count = samples
|
||||
.iter()
|
||||
.filter(|sample| !sample.port_runs.is_empty())
|
||||
.count();
|
||||
let files_with_any_numbered_warehouse_runs_count = samples
|
||||
.iter()
|
||||
.filter(|sample| !sample.warehouse_runs.is_empty())
|
||||
.count();
|
||||
let files_with_both_numbered_run_families_count = samples
|
||||
.iter()
|
||||
.filter(|sample| !sample.port_runs.is_empty() && !sample.warehouse_runs.is_empty())
|
||||
.count();
|
||||
|
||||
let report = RuntimeCandidateTableNamedRunScanReport {
|
||||
root_path: root_path.display().to_string(),
|
||||
file_count,
|
||||
files_with_probe_count,
|
||||
files_with_any_numbered_port_runs_count,
|
||||
files_with_any_numbered_warehouse_runs_count,
|
||||
files_with_both_numbered_run_families_count,
|
||||
skipped_file_count,
|
||||
samples,
|
||||
};
|
||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_runtime_scan_special_conditions(root_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut candidate_paths = Vec::new();
|
||||
collect_special_conditions_input_paths(root_path, &mut candidate_paths)?;
|
||||
|
|
@ -4152,6 +4244,23 @@ fn load_candidate_table_header_scan_sample(
|
|||
})
|
||||
}
|
||||
|
||||
fn load_candidate_table_named_run_scan_sample(
|
||||
smp_path: &Path,
|
||||
) -> Result<RuntimeCandidateTableNamedRunScanSample, Box<dyn std::error::Error>> {
|
||||
let report = load_candidate_table_inspection_report(smp_path)?;
|
||||
let port_runs = collect_numbered_candidate_name_runs(&report.entries, "Port");
|
||||
let warehouse_runs = collect_numbered_candidate_name_runs(&report.entries, "Warehouse");
|
||||
|
||||
Ok(RuntimeCandidateTableNamedRunScanSample {
|
||||
path: report.path,
|
||||
profile_family: report.profile_family,
|
||||
source_kind: report.source_kind,
|
||||
observed_entry_count: report.observed_entry_count,
|
||||
port_runs,
|
||||
warehouse_runs,
|
||||
})
|
||||
}
|
||||
|
||||
fn load_special_conditions_scan_sample(
|
||||
smp_path: &Path,
|
||||
) -> Result<RuntimeSpecialConditionsScanSample, Box<dyn std::error::Error>> {
|
||||
|
|
@ -4495,6 +4604,61 @@ fn collect_candidate_table_input_paths(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_numbered_candidate_name_runs(
|
||||
entries: &[RuntimeCandidateTableEntrySample],
|
||||
prefix: &str,
|
||||
) -> Vec<RuntimeCandidateTableNamedRun> {
|
||||
let mut numbered_entries = entries
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
parse_numbered_candidate_name(&entry.text, prefix).map(|number| (entry, number))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
numbered_entries.sort_by_key(|(entry, number)| (entry.index, *number));
|
||||
|
||||
let mut runs = Vec::new();
|
||||
let mut cursor = 0usize;
|
||||
while cursor < numbered_entries.len() {
|
||||
let (first_entry, first_number) = numbered_entries[cursor];
|
||||
let mut last_entry = first_entry;
|
||||
let mut last_number = first_number;
|
||||
let mut distinct_trailer_hex_words = BTreeSet::from([first_entry.trailer_word_hex.clone()]);
|
||||
let mut next = cursor + 1;
|
||||
while next < numbered_entries.len() {
|
||||
let (entry, number) = numbered_entries[next];
|
||||
if entry.index != last_entry.index + 1 || number != last_number + 1 {
|
||||
break;
|
||||
}
|
||||
distinct_trailer_hex_words.insert(entry.trailer_word_hex.clone());
|
||||
last_entry = entry;
|
||||
last_number = number;
|
||||
next += 1;
|
||||
}
|
||||
runs.push(RuntimeCandidateTableNamedRun {
|
||||
prefix: prefix.to_string(),
|
||||
start_index: first_entry.index,
|
||||
end_index: last_entry.index,
|
||||
count: next - cursor,
|
||||
first_name: first_entry.text.clone(),
|
||||
last_name: last_entry.text.clone(),
|
||||
start_offset: first_entry.offset,
|
||||
end_offset: last_entry.offset,
|
||||
distinct_trailer_hex_words: distinct_trailer_hex_words.into_iter().collect(),
|
||||
});
|
||||
cursor = next;
|
||||
}
|
||||
|
||||
runs
|
||||
}
|
||||
|
||||
fn parse_numbered_candidate_name(text: &str, prefix: &str) -> Option<usize> {
|
||||
let digits = text.strip_prefix(prefix)?;
|
||||
if digits.is_empty() || !digits.bytes().all(|byte| byte.is_ascii_digit()) {
|
||||
return None;
|
||||
}
|
||||
digits.parse().ok()
|
||||
}
|
||||
|
||||
fn collect_special_conditions_input_paths(
|
||||
root_path: &Path,
|
||||
out: &mut Vec<PathBuf>,
|
||||
|
|
@ -6551,6 +6715,83 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collects_numbered_candidate_name_runs_by_prefix() {
|
||||
let entries = vec![
|
||||
RuntimeCandidateTableEntrySample {
|
||||
index: 35,
|
||||
offset: 28535,
|
||||
text: "Port00".to_string(),
|
||||
availability_dword: 1,
|
||||
availability_dword_hex: "0x00000001".to_string(),
|
||||
trailer_word: 1,
|
||||
trailer_word_hex: "0x00000001".to_string(),
|
||||
},
|
||||
RuntimeCandidateTableEntrySample {
|
||||
index: 43,
|
||||
offset: 28807,
|
||||
text: "Warehouse00".to_string(),
|
||||
availability_dword: 1,
|
||||
availability_dword_hex: "0x00000001".to_string(),
|
||||
trailer_word: 1,
|
||||
trailer_word_hex: "0x00000001".to_string(),
|
||||
},
|
||||
RuntimeCandidateTableEntrySample {
|
||||
index: 45,
|
||||
offset: 28875,
|
||||
text: "Port01".to_string(),
|
||||
availability_dword: 1,
|
||||
availability_dword_hex: "0x00000001".to_string(),
|
||||
trailer_word: 1,
|
||||
trailer_word_hex: "0x00000001".to_string(),
|
||||
},
|
||||
RuntimeCandidateTableEntrySample {
|
||||
index: 46,
|
||||
offset: 28909,
|
||||
text: "Port02".to_string(),
|
||||
availability_dword: 1,
|
||||
availability_dword_hex: "0x00000001".to_string(),
|
||||
trailer_word: 1,
|
||||
trailer_word_hex: "0x00000001".to_string(),
|
||||
},
|
||||
RuntimeCandidateTableEntrySample {
|
||||
index: 56,
|
||||
offset: 29249,
|
||||
text: "Warehouse01".to_string(),
|
||||
availability_dword: 1,
|
||||
availability_dword_hex: "0x00000001".to_string(),
|
||||
trailer_word: 1,
|
||||
trailer_word_hex: "0x00000001".to_string(),
|
||||
},
|
||||
RuntimeCandidateTableEntrySample {
|
||||
index: 57,
|
||||
offset: 29283,
|
||||
text: "Warehouse02".to_string(),
|
||||
availability_dword: 1,
|
||||
availability_dword_hex: "0x00000001".to_string(),
|
||||
trailer_word: 1,
|
||||
trailer_word_hex: "0x00000001".to_string(),
|
||||
},
|
||||
];
|
||||
|
||||
let port_runs = collect_numbered_candidate_name_runs(&entries, "Port");
|
||||
let warehouse_runs = collect_numbered_candidate_name_runs(&entries, "Warehouse");
|
||||
|
||||
assert_eq!(port_runs.len(), 2);
|
||||
assert_eq!(port_runs[0].first_name, "Port00");
|
||||
assert_eq!(port_runs[0].count, 1);
|
||||
assert_eq!(port_runs[1].first_name, "Port01");
|
||||
assert_eq!(port_runs[1].last_name, "Port02");
|
||||
assert_eq!(port_runs[1].count, 2);
|
||||
|
||||
assert_eq!(warehouse_runs.len(), 2);
|
||||
assert_eq!(warehouse_runs[0].first_name, "Warehouse00");
|
||||
assert_eq!(warehouse_runs[0].count, 1);
|
||||
assert_eq!(warehouse_runs[1].first_name, "Warehouse01");
|
||||
assert_eq!(warehouse_runs[1].last_name, "Warehouse02");
|
||||
assert_eq!(warehouse_runs[1].count, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diffs_recipe_book_line_samples_across_multiple_files() {
|
||||
let sample_a = RuntimeRecipeBookLineSample {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue