Probe map title hints for kind-8 carriers
This commit is contained in:
parent
7463d87e0e
commit
b9054b1780
6 changed files with 655 additions and 18 deletions
129
artifacts/exports/rt3-1.05/add-building-map-title-hints.json
Normal file
129
artifacts/exports/rt3-1.05/add-building-map-title-hints.json
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
{
|
||||||
|
"root_path": "/tmp/rrt-add-building-carrier-maps",
|
||||||
|
"report": {
|
||||||
|
"maps_scanned": 6,
|
||||||
|
"maps_with_probe": 5,
|
||||||
|
"maps_with_grounded_title_hits": 5,
|
||||||
|
"maps_with_adjacent_title_pairs": 1,
|
||||||
|
"maps_with_same_stem_adjacent_pairs": 1,
|
||||||
|
"maps": [
|
||||||
|
{
|
||||||
|
"path": "/tmp/rrt-add-building-carrier-maps/Alternate USA.gmp",
|
||||||
|
"probe": {
|
||||||
|
"source_kind": "grounded-title-string-scan",
|
||||||
|
"profile_family": "rt3-105-map-container-v1",
|
||||||
|
"grounded_title_hits": [
|
||||||
|
{
|
||||||
|
"title": "Germany",
|
||||||
|
"earliest_offset": 22150015
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "France",
|
||||||
|
"earliest_offset": 21969932
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Britain",
|
||||||
|
"earliest_offset": 22150089
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"embedded_map_references": [],
|
||||||
|
"adjacent_reference_title_pairs": [],
|
||||||
|
"strongest_same_stem_pair": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/tmp/rrt-add-building-carrier-maps/Chicago to New York.gmp",
|
||||||
|
"probe": {
|
||||||
|
"source_kind": "grounded-title-string-scan",
|
||||||
|
"profile_family": "unknown",
|
||||||
|
"grounded_title_hits": [
|
||||||
|
{
|
||||||
|
"title": "Germany",
|
||||||
|
"earliest_offset": 11444199
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "France",
|
||||||
|
"earliest_offset": 11444215
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"embedded_map_references": [],
|
||||||
|
"adjacent_reference_title_pairs": [],
|
||||||
|
"strongest_same_stem_pair": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/tmp/rrt-add-building-carrier-maps/Louisiana.gmp",
|
||||||
|
"probe": {
|
||||||
|
"source_kind": "grounded-title-string-scan",
|
||||||
|
"profile_family": "unknown",
|
||||||
|
"grounded_title_hits": [
|
||||||
|
{
|
||||||
|
"title": "Germany",
|
||||||
|
"earliest_offset": 9165940
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Dutchlantis",
|
||||||
|
"earliest_offset": 29648
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"embedded_map_references": [
|
||||||
|
{
|
||||||
|
"offset": 29648,
|
||||||
|
"text": "Dutchlantis.gmp"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"adjacent_reference_title_pairs": [
|
||||||
|
{
|
||||||
|
"map_reference_offset": 29648,
|
||||||
|
"map_reference_text": "Dutchlantis.gmp",
|
||||||
|
"title_offset": 29648,
|
||||||
|
"title": "Dutchlantis",
|
||||||
|
"byte_distance": 0,
|
||||||
|
"normalized_stem_match": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"strongest_same_stem_pair": {
|
||||||
|
"map_reference_offset": 29648,
|
||||||
|
"map_reference_text": "Dutchlantis.gmp",
|
||||||
|
"title_offset": 29648,
|
||||||
|
"title": "Dutchlantis",
|
||||||
|
"byte_distance": 0,
|
||||||
|
"normalized_stem_match": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/tmp/rrt-add-building-carrier-maps/Pacific Coastal.gmp",
|
||||||
|
"probe": {
|
||||||
|
"source_kind": "grounded-title-string-scan",
|
||||||
|
"profile_family": "unknown",
|
||||||
|
"grounded_title_hits": [
|
||||||
|
{
|
||||||
|
"title": "Central Pacific",
|
||||||
|
"earliest_offset": 7854281
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"embedded_map_references": [],
|
||||||
|
"adjacent_reference_title_pairs": [],
|
||||||
|
"strongest_same_stem_pair": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/tmp/rrt-add-building-carrier-maps/Texas Tea.gmp",
|
||||||
|
"probe": {
|
||||||
|
"source_kind": "grounded-title-string-scan",
|
||||||
|
"profile_family": "unknown",
|
||||||
|
"grounded_title_hits": [
|
||||||
|
{
|
||||||
|
"title": "Germany",
|
||||||
|
"earliest_offset": 9985405
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"embedded_map_references": [],
|
||||||
|
"adjacent_reference_title_pairs": [],
|
||||||
|
"strongest_same_stem_pair": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
# Runtime Effect Kind-8 Title Overlap Note
|
||||||
|
|
||||||
|
This note tightens the current `0x00442c30` title-fixup hypothesis for the shipped add-building
|
||||||
|
carrier maps.
|
||||||
|
|
||||||
|
## Grounded title-hint scan
|
||||||
|
|
||||||
|
The checked report
|
||||||
|
`artifacts/exports/rt3-1.05/add-building-map-title-hints.json`
|
||||||
|
scans the six bundled add-building carrier maps:
|
||||||
|
|
||||||
|
- `Alternate USA.gmp`
|
||||||
|
- `Chicago to New York.gmp`
|
||||||
|
- `Louisiana.gmp`
|
||||||
|
- `Pacific Coastal.gmp`
|
||||||
|
- `Rhodes Unfinished.gmp`
|
||||||
|
- `Texas Tea.gmp`
|
||||||
|
|
||||||
|
against the currently grounded `0x00442c30` title set:
|
||||||
|
|
||||||
|
- `Go West!`
|
||||||
|
- `Germany`
|
||||||
|
- `France`
|
||||||
|
- `State of Germany`
|
||||||
|
- `New Beginnings`
|
||||||
|
- `Dutchlantis`
|
||||||
|
- `Britain`
|
||||||
|
- `New Zealand`
|
||||||
|
- `South East Australia`
|
||||||
|
- `Tex-Mex`
|
||||||
|
- `Germantown`
|
||||||
|
- `The American`
|
||||||
|
- `Central Pacific`
|
||||||
|
- `Orient Express`
|
||||||
|
|
||||||
|
Observed result:
|
||||||
|
|
||||||
|
- `5 / 6` carrier maps show at least one grounded title hit.
|
||||||
|
- `1 / 6` carrier maps show an adjacent embedded `.gmp` reference plus grounded title.
|
||||||
|
- `1 / 6` carrier maps show a same-stem pair.
|
||||||
|
|
||||||
|
The only strong same-stem overlap is:
|
||||||
|
|
||||||
|
- `Louisiana.gmp`
|
||||||
|
- embedded map reference: `Dutchlantis.gmp` at `0x73d0`
|
||||||
|
- grounded title hit: `Dutchlantis` at `0x73d0`
|
||||||
|
- byte distance: `0`
|
||||||
|
|
||||||
|
All other current carrier-map overlaps are weaker late-string hits:
|
||||||
|
|
||||||
|
- `Alternate USA.gmp`: `Germany`, `France`, `Britain`
|
||||||
|
- `Chicago to New York.gmp`: `Germany`, `France`
|
||||||
|
- `Pacific Coastal.gmp`: `Central Pacific`
|
||||||
|
- `Texas Tea.gmp`: `Germany`
|
||||||
|
- `Rhodes Unfinished.gmp`: no current grounded hit
|
||||||
|
|
||||||
|
## Louisiana versus Dutchlantis runtime-event comparison
|
||||||
|
|
||||||
|
Direct `inspect-smp` comparison shows the strong same-stem title overlap does **not** carry over
|
||||||
|
to the actual add-building dispatch strip.
|
||||||
|
|
||||||
|
`Louisiana.gmp`:
|
||||||
|
|
||||||
|
- `nondirect_compact_record_count = 14`
|
||||||
|
- add-building dispatch record index: `5`
|
||||||
|
- add-building label: `Add Building Warehouse05`
|
||||||
|
- add-building signature family:
|
||||||
|
`nondirect-ge1e-h0001-0007-0000-5200-0200-p0000-0000-0000-ffff`
|
||||||
|
- add-building condition tuple family: `[7:0]`
|
||||||
|
- add-building cluster:
|
||||||
|
`nondirect-ge1e-h0001-0007-0000-5200-0200-p0000-0000-0000-ffff :: [7:0]`
|
||||||
|
|
||||||
|
`Dutchlantis.gmp`:
|
||||||
|
|
||||||
|
- `nondirect_compact_record_count = 15`
|
||||||
|
- no add-building dispatch rows
|
||||||
|
- current dispatch-strip grouped descriptor labels:
|
||||||
|
`Company Variable 1`, `Company Variable 2`, `Company Variable 3`
|
||||||
|
|
||||||
|
## Current implication
|
||||||
|
|
||||||
|
The title-fixup branch remains possible for narrow scenario-specific retagging, but the strongest
|
||||||
|
current filename/title overlap (`Louisiana -> Dutchlantis`) does not reproduce the actual
|
||||||
|
add-building dispatch cluster. That weakens `0x00442c30` as the primary explanation for the
|
||||||
|
shipped add-building carrier strip and keeps the stronger bias on the non-title-specific late
|
||||||
|
bringup owners between ordinary reload `0x00433130` and final kind-`8` service `0x00432f40`.
|
||||||
|
|
@ -24,12 +24,13 @@ use rrt_runtime::{
|
||||||
RuntimeOverlayImportDocument, RuntimeOverlayImportDocumentSource, RuntimeSaveSliceDocument,
|
RuntimeOverlayImportDocument, RuntimeOverlayImportDocumentSource, RuntimeSaveSliceDocument,
|
||||||
RuntimeSaveSliceDocumentSource, RuntimeSnapshotDocument, RuntimeSnapshotSource, RuntimeSummary,
|
RuntimeSaveSliceDocumentSource, RuntimeSnapshotDocument, RuntimeSnapshotSource, RuntimeSummary,
|
||||||
SAVE_SLICE_DOCUMENT_FORMAT_VERSION, SNAPSHOT_FORMAT_VERSION, SmpClassicPackedProfileBlock,
|
SAVE_SLICE_DOCUMENT_FORMAT_VERSION, SNAPSHOT_FORMAT_VERSION, SmpClassicPackedProfileBlock,
|
||||||
SmpInspectionReport, SmpLoadedSaveSlice, SmpRt3105PackedProfileBlock, SmpSaveLoadSummary,
|
SmpInspectionReport, SmpLoadedSaveSlice, SmpMapTitleHintProbe, SmpRt3105PackedProfileBlock,
|
||||||
WinInspectionReport, compare_save_region_fixed_row_run_candidates, execute_step_command,
|
SmpSaveLoadSummary, WinInspectionReport, compare_save_region_fixed_row_run_candidates,
|
||||||
extract_pk4_entry_file, inspect_building_types_dir_with_bindings, inspect_campaign_exe_file,
|
execute_step_command, extract_pk4_entry_file, inspect_building_types_dir_with_bindings,
|
||||||
inspect_cargo_economy_sources_with_bindings, inspect_cargo_skin_pk4, inspect_cargo_types_dir,
|
inspect_campaign_exe_file, inspect_cargo_economy_sources_with_bindings, inspect_cargo_skin_pk4,
|
||||||
inspect_pk4_file, inspect_save_company_and_chairman_analysis_file,
|
inspect_cargo_types_dir, inspect_map_title_hint_file, inspect_pk4_file,
|
||||||
inspect_save_infrastructure_asset_trace_file, inspect_save_periodic_company_service_trace_file,
|
inspect_save_company_and_chairman_analysis_file, inspect_save_infrastructure_asset_trace_file,
|
||||||
|
inspect_save_periodic_company_service_trace_file,
|
||||||
inspect_save_placed_structure_dynamic_side_buffer_file,
|
inspect_save_placed_structure_dynamic_side_buffer_file,
|
||||||
inspect_save_region_queued_notice_records_file, inspect_save_region_service_trace_file,
|
inspect_save_region_queued_notice_records_file, inspect_save_region_service_trace_file,
|
||||||
inspect_smp_file, inspect_unclassified_save_collection_headers_file, inspect_win_file,
|
inspect_smp_file, inspect_unclassified_save_collection_headers_file, inspect_win_file,
|
||||||
|
|
@ -135,6 +136,9 @@ enum Command {
|
||||||
RuntimeInspectCompactEventDispatchClusterCounts {
|
RuntimeInspectCompactEventDispatchClusterCounts {
|
||||||
root_path: PathBuf,
|
root_path: PathBuf,
|
||||||
},
|
},
|
||||||
|
RuntimeInspectMapTitleHints {
|
||||||
|
root_path: PathBuf,
|
||||||
|
},
|
||||||
RuntimeSummarizeSaveLoad {
|
RuntimeSummarizeSaveLoad {
|
||||||
smp_path: PathBuf,
|
smp_path: PathBuf,
|
||||||
},
|
},
|
||||||
|
|
@ -319,6 +323,28 @@ struct RuntimeCompactEventDispatchClusterCountsOutput {
|
||||||
report: RuntimeCompactEventDispatchClusterCountsReport,
|
report: RuntimeCompactEventDispatchClusterCountsReport,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct RuntimeMapTitleHintDirectoryOutput {
|
||||||
|
root_path: String,
|
||||||
|
report: RuntimeMapTitleHintDirectoryReport,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct RuntimeMapTitleHintDirectoryReport {
|
||||||
|
maps_scanned: usize,
|
||||||
|
maps_with_probe: usize,
|
||||||
|
maps_with_grounded_title_hits: usize,
|
||||||
|
maps_with_adjacent_title_pairs: usize,
|
||||||
|
maps_with_same_stem_adjacent_pairs: usize,
|
||||||
|
maps: Vec<RuntimeMapTitleHintMapEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct RuntimeMapTitleHintMapEntry {
|
||||||
|
path: String,
|
||||||
|
probe: SmpMapTitleHintProbe,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct RuntimeCompactEventDispatchClusterReport {
|
struct RuntimeCompactEventDispatchClusterReport {
|
||||||
maps_scanned: usize,
|
maps_scanned: usize,
|
||||||
|
|
@ -1061,6 +1087,9 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
Command::RuntimeInspectCompactEventDispatchClusterCounts { root_path } => {
|
Command::RuntimeInspectCompactEventDispatchClusterCounts { root_path } => {
|
||||||
run_runtime_inspect_compact_event_dispatch_cluster_counts(&root_path)?;
|
run_runtime_inspect_compact_event_dispatch_cluster_counts(&root_path)?;
|
||||||
}
|
}
|
||||||
|
Command::RuntimeInspectMapTitleHints { root_path } => {
|
||||||
|
run_runtime_inspect_map_title_hints(&root_path)?;
|
||||||
|
}
|
||||||
Command::RuntimeSummarizeSaveLoad { smp_path } => {
|
Command::RuntimeSummarizeSaveLoad { smp_path } => {
|
||||||
run_runtime_summarize_save_load(&smp_path)?;
|
run_runtime_summarize_save_load(&smp_path)?;
|
||||||
}
|
}
|
||||||
|
|
@ -1311,6 +1340,13 @@ fn parse_command() -> Result<Command, Box<dyn std::error::Error>> {
|
||||||
root_path: PathBuf::from(root_path),
|
root_path: PathBuf::from(root_path),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
[command, subcommand, root_path]
|
||||||
|
if command == "runtime" && subcommand == "inspect-map-title-hints" =>
|
||||||
|
{
|
||||||
|
Ok(Command::RuntimeInspectMapTitleHints {
|
||||||
|
root_path: PathBuf::from(root_path),
|
||||||
|
})
|
||||||
|
}
|
||||||
[command, subcommand, path]
|
[command, subcommand, path]
|
||||||
if command == "runtime" && subcommand == "summarize-save-load" =>
|
if command == "runtime" && subcommand == "summarize-save-load" =>
|
||||||
{
|
{
|
||||||
|
|
@ -1611,7 +1647,7 @@ fn parse_command() -> Result<Command, Box<dyn std::error::Error>> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => Err(
|
_ => 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 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-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(),
|
.into(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
@ -1816,6 +1852,59 @@ fn run_runtime_inspect_smp(smp_path: &Path) -> Result<(), Box<dyn std::error::Er
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_runtime_inspect_map_title_hints(root_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut maps = Vec::new();
|
||||||
|
let mut maps_scanned = 0usize;
|
||||||
|
let mut maps_with_probe = 0usize;
|
||||||
|
let mut maps_with_grounded_title_hits = 0usize;
|
||||||
|
let mut maps_with_adjacent_title_pairs = 0usize;
|
||||||
|
let mut maps_with_same_stem_adjacent_pairs = 0usize;
|
||||||
|
|
||||||
|
let mut paths = fs::read_dir(root_path)?
|
||||||
|
.filter_map(|entry| entry.ok().map(|entry| entry.path()))
|
||||||
|
.filter(|path| {
|
||||||
|
path.extension()
|
||||||
|
.and_then(|extension| extension.to_str())
|
||||||
|
.is_some_and(|extension| extension.eq_ignore_ascii_case("gmp"))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
paths.sort();
|
||||||
|
|
||||||
|
for path in paths {
|
||||||
|
maps_scanned += 1;
|
||||||
|
if let Some(probe) = inspect_map_title_hint_file(&path)? {
|
||||||
|
maps_with_probe += 1;
|
||||||
|
if !probe.grounded_title_hits.is_empty() {
|
||||||
|
maps_with_grounded_title_hits += 1;
|
||||||
|
}
|
||||||
|
if !probe.adjacent_reference_title_pairs.is_empty() {
|
||||||
|
maps_with_adjacent_title_pairs += 1;
|
||||||
|
}
|
||||||
|
if probe.strongest_same_stem_pair.is_some() {
|
||||||
|
maps_with_same_stem_adjacent_pairs += 1;
|
||||||
|
}
|
||||||
|
maps.push(RuntimeMapTitleHintMapEntry {
|
||||||
|
path: path.display().to_string(),
|
||||||
|
probe,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = RuntimeMapTitleHintDirectoryOutput {
|
||||||
|
root_path: root_path.display().to_string(),
|
||||||
|
report: RuntimeMapTitleHintDirectoryReport {
|
||||||
|
maps_scanned,
|
||||||
|
maps_with_probe,
|
||||||
|
maps_with_grounded_title_hits,
|
||||||
|
maps_with_adjacent_title_pairs,
|
||||||
|
maps_with_same_stem_adjacent_pairs,
|
||||||
|
maps,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
println!("{}", serde_json::to_string_pretty(&output)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn run_runtime_inspect_compact_event_dispatch_cluster(
|
fn run_runtime_inspect_compact_event_dispatch_cluster(
|
||||||
root_path: &Path,
|
root_path: &Path,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,8 @@ pub use smp::{
|
||||||
SmpLoadedWorldEconomicTuningState, SmpLoadedWorldFinanceNeighborhoodState,
|
SmpLoadedWorldEconomicTuningState, SmpLoadedWorldFinanceNeighborhoodState,
|
||||||
SmpLoadedWorldIssue37State, SmpLocomotivePolicyFieldObservation,
|
SmpLoadedWorldIssue37State, SmpLocomotivePolicyFieldObservation,
|
||||||
SmpLocomotivePolicyFloatAlignmentCandidate, SmpLocomotivePolicyNeighborhoodProbe,
|
SmpLocomotivePolicyFloatAlignmentCandidate, SmpLocomotivePolicyNeighborhoodProbe,
|
||||||
SmpPackedProfileWordLane, SmpPeriodicCompanyServiceTraceReport,
|
SmpMapTitleHintAdjacentPair, SmpMapTitleHintMapReference, SmpMapTitleHintProbe,
|
||||||
|
SmpMapTitleHintTitleHit, SmpPackedProfileWordLane, SmpPeriodicCompanyServiceTraceReport,
|
||||||
SmpPostSpecialConditionsScalarLane, SmpPostSpecialConditionsScalarProbe,
|
SmpPostSpecialConditionsScalarLane, SmpPostSpecialConditionsScalarProbe,
|
||||||
SmpPostTextFieldNeighborhoodProbe, SmpPostTextFloatAlignmentCandidate,
|
SmpPostTextFieldNeighborhoodProbe, SmpPostTextFloatAlignmentCandidate,
|
||||||
SmpPostTextGroundedFieldObservation, SmpPreRecipeScalarPlateauLane,
|
SmpPostTextGroundedFieldObservation, SmpPreRecipeScalarPlateauLane,
|
||||||
|
|
@ -139,7 +140,8 @@ pub use smp::{
|
||||||
SmpSaveWorldSelectionRoleAnalysis, SmpSaveWorldSelectionRoleAnalysisEntry,
|
SmpSaveWorldSelectionRoleAnalysis, SmpSaveWorldSelectionRoleAnalysisEntry,
|
||||||
SmpSecondaryVariantProbe, SmpServiceTraceBranchStatus, SmpSharedHeader,
|
SmpSecondaryVariantProbe, SmpServiceTraceBranchStatus, SmpSharedHeader,
|
||||||
SmpSpecialConditionEntry, SmpSpecialConditionsProbe,
|
SmpSpecialConditionEntry, SmpSpecialConditionsProbe,
|
||||||
compare_save_region_fixed_row_run_candidates, inspect_save_company_and_chairman_analysis_bytes,
|
compare_save_region_fixed_row_run_candidates, inspect_map_title_hint_bytes,
|
||||||
|
inspect_map_title_hint_file, inspect_save_company_and_chairman_analysis_bytes,
|
||||||
inspect_save_company_and_chairman_analysis_file, inspect_save_infrastructure_asset_trace_file,
|
inspect_save_company_and_chairman_analysis_file, inspect_save_infrastructure_asset_trace_file,
|
||||||
inspect_save_periodic_company_service_trace_file,
|
inspect_save_periodic_company_service_trace_file,
|
||||||
inspect_save_placed_structure_dynamic_side_buffer_file,
|
inspect_save_placed_structure_dynamic_side_buffer_file,
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,24 @@ const PACKED_EVENT_REAL_COMPACT_CONTROL_LEN: usize = 37;
|
||||||
const PACKED_EVENT_NONDIRECT_CONDITION_ROW_SERIALIZED_LEN: usize = 22;
|
const PACKED_EVENT_NONDIRECT_CONDITION_ROW_SERIALIZED_LEN: usize = 22;
|
||||||
const PACKED_EVENT_NONDIRECT_GROUPED_EFFECT_ROW_SERIALIZED_LEN: usize = 45;
|
const PACKED_EVENT_NONDIRECT_GROUPED_EFFECT_ROW_SERIALIZED_LEN: usize = 45;
|
||||||
const PACKED_EVENT_NONDIRECT_OPTIONAL_NAME_BLOCK_LEN: usize = 0x64;
|
const PACKED_EVENT_NONDIRECT_OPTIONAL_NAME_BLOCK_LEN: usize = 0x64;
|
||||||
|
const MAP_TITLE_HINT_ASCII_FRAGMENT_MAX_LEN: usize = 160;
|
||||||
|
const MAP_TITLE_HINT_REFERENCE_PAIR_DISTANCE_LIMIT: usize = 0x100;
|
||||||
|
const POST_LOAD_SCENARIO_FIXUP_TITLE_SET: [&str; 14] = [
|
||||||
|
"Go West!",
|
||||||
|
"Germany",
|
||||||
|
"France",
|
||||||
|
"State of Germany",
|
||||||
|
"New Beginnings",
|
||||||
|
"Dutchlantis",
|
||||||
|
"Britain",
|
||||||
|
"New Zealand",
|
||||||
|
"South East Australia",
|
||||||
|
"Tex-Mex",
|
||||||
|
"Germantown",
|
||||||
|
"The American",
|
||||||
|
"Central Pacific",
|
||||||
|
"Orient Express",
|
||||||
|
];
|
||||||
const PACKED_EVENT_TEXT_BAND_LABELS: [&str; 6] = [
|
const PACKED_EVENT_TEXT_BAND_LABELS: [&str; 6] = [
|
||||||
"primary_text_band",
|
"primary_text_band",
|
||||||
"secondary_text_band_0",
|
"secondary_text_band_0",
|
||||||
|
|
@ -1306,6 +1324,38 @@ pub struct SmpAsciiPreview {
|
||||||
pub truncated: bool,
|
pub truncated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct SmpMapTitleHintTitleHit {
|
||||||
|
pub title: String,
|
||||||
|
pub earliest_offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct SmpMapTitleHintMapReference {
|
||||||
|
pub offset: usize,
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct SmpMapTitleHintAdjacentPair {
|
||||||
|
pub map_reference_offset: usize,
|
||||||
|
pub map_reference_text: String,
|
||||||
|
pub title_offset: usize,
|
||||||
|
pub title: String,
|
||||||
|
pub byte_distance: usize,
|
||||||
|
pub normalized_stem_match: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct SmpMapTitleHintProbe {
|
||||||
|
pub source_kind: String,
|
||||||
|
pub profile_family: Option<String>,
|
||||||
|
pub grounded_title_hits: Vec<SmpMapTitleHintTitleHit>,
|
||||||
|
pub embedded_map_references: Vec<SmpMapTitleHintMapReference>,
|
||||||
|
pub adjacent_reference_title_pairs: Vec<SmpMapTitleHintAdjacentPair>,
|
||||||
|
pub strongest_same_stem_pair: Option<SmpMapTitleHintAdjacentPair>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct SmpSharedHeader {
|
pub struct SmpSharedHeader {
|
||||||
pub byte_len: usize,
|
pub byte_len: usize,
|
||||||
|
|
@ -4249,6 +4299,8 @@ pub struct SmpInspectionReport {
|
||||||
pub save_company_roster_probe: Option<SmpLoadedCompanyRoster>,
|
pub save_company_roster_probe: Option<SmpLoadedCompanyRoster>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub save_chairman_profile_table_probe: Option<SmpLoadedChairmanProfileTable>,
|
pub save_chairman_profile_table_probe: Option<SmpLoadedChairmanProfileTable>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub map_title_hint_probe: Option<SmpMapTitleHintProbe>,
|
||||||
pub rt3_105_save_name_table_probe: Option<SmpRt3105SaveNameTableProbe>,
|
pub rt3_105_save_name_table_probe: Option<SmpRt3105SaveNameTableProbe>,
|
||||||
pub rt3_105_save_named_locomotive_availability_probe:
|
pub rt3_105_save_named_locomotive_availability_probe:
|
||||||
Option<SmpRt3105SaveNamedLocomotiveAvailabilityProbe>,
|
Option<SmpRt3105SaveNamedLocomotiveAvailabilityProbe>,
|
||||||
|
|
@ -4279,6 +4331,41 @@ pub fn inspect_smp_file(path: &Path) -> Result<SmpInspectionReport, Box<dyn std:
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn inspect_map_title_hint_file(
|
||||||
|
path: &Path,
|
||||||
|
) -> Result<Option<SmpMapTitleHintProbe>, Box<dyn std::error::Error>> {
|
||||||
|
let bytes = fs::read(path)?;
|
||||||
|
let file_extension_hint = path
|
||||||
|
.extension()
|
||||||
|
.and_then(|extension| extension.to_str())
|
||||||
|
.map(|extension| extension.to_ascii_lowercase());
|
||||||
|
Ok(inspect_map_title_hint_bytes(
|
||||||
|
&bytes,
|
||||||
|
file_extension_hint.as_deref(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inspect_map_title_hint_bytes(
|
||||||
|
bytes: &[u8],
|
||||||
|
file_extension_hint: Option<&str>,
|
||||||
|
) -> Option<SmpMapTitleHintProbe> {
|
||||||
|
let shared_header = parse_shared_header(bytes);
|
||||||
|
let header_variant_probe = shared_header.as_ref().map(classify_header_variant_probe);
|
||||||
|
let first_ascii_run = find_first_ascii_run(bytes);
|
||||||
|
let early_content_probe = first_ascii_run
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|ascii_run| probe_early_content_layout(bytes, ascii_run));
|
||||||
|
let secondary_variant_probe = early_content_probe
|
||||||
|
.as_ref()
|
||||||
|
.and_then(classify_secondary_variant_probe);
|
||||||
|
let container_profile = classify_container_profile(
|
||||||
|
file_extension_hint,
|
||||||
|
header_variant_probe.as_ref(),
|
||||||
|
secondary_variant_probe.as_ref(),
|
||||||
|
);
|
||||||
|
parse_map_title_hint_probe(bytes, file_extension_hint, container_profile.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inspect_unclassified_save_collection_headers_file(
|
pub fn inspect_unclassified_save_collection_headers_file(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
) -> Result<Vec<SmpSaveUnclassifiedTaggedCollectionHeaderProbe>, Box<dyn std::error::Error>> {
|
) -> Result<Vec<SmpSaveUnclassifiedTaggedCollectionHeaderProbe>, Box<dyn std::error::Error>> {
|
||||||
|
|
@ -13447,6 +13534,11 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
|
||||||
save_world_selection_context_probe.as_ref(),
|
save_world_selection_context_probe.as_ref(),
|
||||||
save_company_collection_header_probe.as_ref(),
|
save_company_collection_header_probe.as_ref(),
|
||||||
);
|
);
|
||||||
|
let map_title_hint_probe = parse_map_title_hint_probe(
|
||||||
|
bytes,
|
||||||
|
file_extension_hint.as_deref(),
|
||||||
|
container_profile.as_ref(),
|
||||||
|
);
|
||||||
let rt3_105_save_name_table_probe = parse_rt3_105_save_name_table_probe(
|
let rt3_105_save_name_table_probe = parse_rt3_105_save_name_table_probe(
|
||||||
bytes,
|
bytes,
|
||||||
file_extension_hint.as_deref(),
|
file_extension_hint.as_deref(),
|
||||||
|
|
@ -13611,6 +13703,7 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
|
||||||
save_unclassified_tagged_collection_header_probes,
|
save_unclassified_tagged_collection_header_probes,
|
||||||
save_company_roster_probe,
|
save_company_roster_probe,
|
||||||
save_chairman_profile_table_probe,
|
save_chairman_profile_table_probe,
|
||||||
|
map_title_hint_probe,
|
||||||
rt3_105_save_name_table_probe,
|
rt3_105_save_name_table_probe,
|
||||||
rt3_105_save_named_locomotive_availability_probe,
|
rt3_105_save_named_locomotive_availability_probe,
|
||||||
special_conditions_probe,
|
special_conditions_probe,
|
||||||
|
|
@ -21358,6 +21451,173 @@ fn find_first_ascii_run(bytes: &[u8]) -> Option<SmpAsciiPreview> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_map_title_hint_probe(
|
||||||
|
bytes: &[u8],
|
||||||
|
file_extension_hint: Option<&str>,
|
||||||
|
container_profile: Option<&SmpContainerProfile>,
|
||||||
|
) -> Option<SmpMapTitleHintProbe> {
|
||||||
|
if file_extension_hint != Some("gmp") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let grounded_title_hits = POST_LOAD_SCENARIO_FIXUP_TITLE_SET
|
||||||
|
.iter()
|
||||||
|
.filter_map(|title| {
|
||||||
|
let offset = find_first_subsequence_offset(bytes, title.as_bytes())?;
|
||||||
|
Some(SmpMapTitleHintTitleHit {
|
||||||
|
title: (*title).to_string(),
|
||||||
|
earliest_offset: offset,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let embedded_map_references = find_ascii_fragment_occurrences_with_suffix(bytes, ".gmp")
|
||||||
|
.into_iter()
|
||||||
|
.map(|(offset, text)| SmpMapTitleHintMapReference { offset, text })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let adjacent_reference_title_pairs =
|
||||||
|
build_map_title_hint_adjacent_pairs(&embedded_map_references, &grounded_title_hits);
|
||||||
|
let strongest_same_stem_pair = adjacent_reference_title_pairs
|
||||||
|
.iter()
|
||||||
|
.find(|pair| pair.normalized_stem_match)
|
||||||
|
.cloned();
|
||||||
|
|
||||||
|
if grounded_title_hits.is_empty()
|
||||||
|
&& embedded_map_references.is_empty()
|
||||||
|
&& strongest_same_stem_pair.is_none()
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(SmpMapTitleHintProbe {
|
||||||
|
source_kind: "grounded-title-string-scan".to_string(),
|
||||||
|
profile_family: container_profile.map(|profile| profile.profile_family.clone()),
|
||||||
|
grounded_title_hits,
|
||||||
|
embedded_map_references,
|
||||||
|
adjacent_reference_title_pairs,
|
||||||
|
strongest_same_stem_pair,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_map_title_hint_adjacent_pairs(
|
||||||
|
map_references: &[SmpMapTitleHintMapReference],
|
||||||
|
title_hits: &[SmpMapTitleHintTitleHit],
|
||||||
|
) -> Vec<SmpMapTitleHintAdjacentPair> {
|
||||||
|
let mut pairs = Vec::new();
|
||||||
|
|
||||||
|
for map_reference in map_references {
|
||||||
|
let mut best_pair: Option<SmpMapTitleHintAdjacentPair> = None;
|
||||||
|
for title_hit in title_hits {
|
||||||
|
let byte_distance = map_reference.offset.abs_diff(title_hit.earliest_offset);
|
||||||
|
if byte_distance > MAP_TITLE_HINT_REFERENCE_PAIR_DISTANCE_LIMIT {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let candidate = SmpMapTitleHintAdjacentPair {
|
||||||
|
map_reference_offset: map_reference.offset,
|
||||||
|
map_reference_text: map_reference.text.clone(),
|
||||||
|
title_offset: title_hit.earliest_offset,
|
||||||
|
title: title_hit.title.clone(),
|
||||||
|
byte_distance,
|
||||||
|
normalized_stem_match: normalize_map_title_hint_stem(&map_reference.text)
|
||||||
|
== normalize_map_title_hint_stem(&title_hit.title),
|
||||||
|
};
|
||||||
|
let replace = match &best_pair {
|
||||||
|
Some(current) => {
|
||||||
|
(candidate.normalized_stem_match && !current.normalized_stem_match)
|
||||||
|
|| (candidate.normalized_stem_match == current.normalized_stem_match
|
||||||
|
&& candidate.byte_distance < current.byte_distance)
|
||||||
|
}
|
||||||
|
None => true,
|
||||||
|
};
|
||||||
|
if replace {
|
||||||
|
best_pair = Some(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(pair) = best_pair {
|
||||||
|
pairs.push(pair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs.sort_by_key(|pair| {
|
||||||
|
(
|
||||||
|
!pair.normalized_stem_match,
|
||||||
|
pair.byte_distance,
|
||||||
|
pair.map_reference_offset,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
pairs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_map_title_hint_stem(text: &str) -> String {
|
||||||
|
text.trim()
|
||||||
|
.trim_end_matches(".gmp")
|
||||||
|
.trim_end_matches(".GMP")
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_first_subsequence_offset(bytes: &[u8], needle: &[u8]) -> Option<usize> {
|
||||||
|
if needle.is_empty() || bytes.len() < needle.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
bytes
|
||||||
|
.windows(needle.len())
|
||||||
|
.position(|window| window == needle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_ascii_fragment_occurrences_with_suffix(bytes: &[u8], suffix: &str) -> Vec<(usize, String)> {
|
||||||
|
let suffix_bytes = suffix.as_bytes();
|
||||||
|
if suffix_bytes.is_empty() || bytes.len() < suffix_bytes.len() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut occurrences = Vec::new();
|
||||||
|
let mut seen_offsets = BTreeSet::new();
|
||||||
|
for offset in 0..=bytes.len() - suffix_bytes.len() {
|
||||||
|
if &bytes[offset..offset + suffix_bytes.len()] != suffix_bytes {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some((start, text)) = extract_ascii_fragment_containing(bytes, offset) {
|
||||||
|
if seen_offsets.insert(start) {
|
||||||
|
occurrences.push((start, text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
occurrences
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_ascii_fragment_containing(bytes: &[u8], offset: usize) -> Option<(usize, String)> {
|
||||||
|
if offset >= bytes.len() || !is_map_title_hint_ascii_fragment_byte(bytes[offset]) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut start = offset;
|
||||||
|
while start > 0 && is_map_title_hint_ascii_fragment_byte(bytes[start - 1]) {
|
||||||
|
start -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut end = offset;
|
||||||
|
while end < bytes.len() && is_map_title_hint_ascii_fragment_byte(bytes[end]) {
|
||||||
|
end += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if end <= start {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let len = (end - start).min(MAP_TITLE_HINT_ASCII_FRAGMENT_MAX_LEN);
|
||||||
|
let text = String::from_utf8_lossy(&bytes[start..start + len])
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
if text.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some((start, text))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_map_title_hint_ascii_fragment_byte(byte: u8) -> bool {
|
||||||
|
matches!(byte, b' ' | b'!' | b'-' | b'.' | b'/' | b'\\' | b':' | b'_' | b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z')
|
||||||
|
}
|
||||||
|
|
||||||
fn build_ascii_preview(bytes: &[u8], start: usize, end: usize) -> SmpAsciiPreview {
|
fn build_ascii_preview(bytes: &[u8], start: usize, end: usize) -> SmpAsciiPreview {
|
||||||
let byte_len = end - start;
|
let byte_len = end - start;
|
||||||
let preview_bytes = &bytes[start..end];
|
let preview_bytes = &bytes[start..end];
|
||||||
|
|
@ -29834,6 +30094,56 @@ mod tests {
|
||||||
assert_eq!(probe.footer_progress_word_1, 0x3714);
|
assert_eq!(probe.footer_progress_word_1, 0x3714);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_map_title_hint_probe_from_grounded_titles_and_embedded_map_reference() {
|
||||||
|
let mut bytes = vec![0u8; 0x9000];
|
||||||
|
let embedded_reference = b"Dutchlantis.gmp";
|
||||||
|
let title = b"Dutchlantis";
|
||||||
|
let later_title = b"Germany";
|
||||||
|
|
||||||
|
bytes[0x73d0..0x73d0 + embedded_reference.len()].copy_from_slice(embedded_reference);
|
||||||
|
bytes[0x73e0..0x73e0 + title.len()].copy_from_slice(title);
|
||||||
|
bytes[0x8400..0x8400 + later_title.len()].copy_from_slice(later_title);
|
||||||
|
|
||||||
|
let probe = parse_map_title_hint_probe(
|
||||||
|
&bytes,
|
||||||
|
Some("gmp"),
|
||||||
|
Some(&SmpContainerProfile {
|
||||||
|
profile_family: "rt3-105-map-container-v1".to_string(),
|
||||||
|
profile_evidence: vec![],
|
||||||
|
is_known_profile: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.expect("map title hint probe should parse");
|
||||||
|
|
||||||
|
assert_eq!(probe.source_kind, "grounded-title-string-scan");
|
||||||
|
assert_eq!(
|
||||||
|
probe.profile_family,
|
||||||
|
Some("rt3-105-map-container-v1".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(probe.grounded_title_hits.len(), 2);
|
||||||
|
assert_eq!(probe.grounded_title_hits[0].title, "Germany");
|
||||||
|
assert_eq!(probe.grounded_title_hits[0].earliest_offset, 0x8400);
|
||||||
|
assert_eq!(probe.grounded_title_hits[1].title, "Dutchlantis");
|
||||||
|
assert_eq!(probe.grounded_title_hits[1].earliest_offset, 0x73d0);
|
||||||
|
assert_eq!(probe.embedded_map_references.len(), 1);
|
||||||
|
assert_eq!(probe.embedded_map_references[0].offset, 0x73d0);
|
||||||
|
assert_eq!(probe.embedded_map_references[0].text, "Dutchlantis.gmp");
|
||||||
|
assert_eq!(probe.adjacent_reference_title_pairs.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
probe
|
||||||
|
.strongest_same_stem_pair
|
||||||
|
.as_ref()
|
||||||
|
.map(|pair| pair.title.as_str()),
|
||||||
|
Some("Dutchlantis")
|
||||||
|
);
|
||||||
|
let pair = probe.strongest_same_stem_pair.expect("same-stem pair");
|
||||||
|
assert_eq!(pair.map_reference_offset, 0x73d0);
|
||||||
|
assert_eq!(pair.title_offset, 0x73d0);
|
||||||
|
assert!(pair.normalized_stem_match);
|
||||||
|
assert_eq!(pair.byte_distance, 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_rt3_105_save_named_locomotive_availability_probe() {
|
fn parses_rt3_105_save_named_locomotive_availability_probe() {
|
||||||
let mut bytes = vec![0u8; 0x9000];
|
let mut bytes = vec![0u8; 0x9000];
|
||||||
|
|
|
||||||
|
|
@ -435,16 +435,37 @@ Working rule:
|
||||||
late-bringup facts for `0x00446d40`, `0x00443a50`, `0x00442c30`, and the explicit
|
late-bringup facts for `0x00446d40`, `0x00443a50`, `0x00442c30`, and the explicit
|
||||||
`SP - GOLD` / `Labor` trigger-kind rewrites, so the next pass can work from one bounded note
|
`SP - GOLD` / `Labor` trigger-kind rewrites, so the next pass can work from one bounded note
|
||||||
instead of stitching the same ordering back together from the queue plus function-map prose.
|
instead of stitching the same ordering back together from the queue plus function-map prose.
|
||||||
- the shipped add-building carrier corpus weakens the current retagger hypothesis too:
|
- the shipped add-building carrier corpus no longer supports the older filename-mismatch bias:
|
||||||
the six bundled-map titles in
|
the checked report
|
||||||
|
`artifacts/exports/rt3-1.05/add-building-map-title-hints.json`
|
||||||
|
now scans the six bundled carrier maps in
|
||||||
`artifacts/exports/rt3-1.05/add-building-compact-dispatch-corpus.json`
|
`artifacts/exports/rt3-1.05/add-building-compact-dispatch-corpus.json`
|
||||||
(`Alternate USA`, `Chicago to New York`, `Louisiana`, `Pacific Coastal`,
|
against the grounded `0x00442c30` title set (`Go West!`, `Germany`, `France`,
|
||||||
`Rhodes Unfinished`, `Texas Tea`) do not currently overlap the grounded
|
`State of Germany`, `New Beginnings`, `Dutchlantis`, `Britain`, `New Zealand`,
|
||||||
`0x00442c30` scenario-title set (`Go West!`, `Germany`, `France`, `State of Germany`,
|
`South East Australia`, `Tex-Mex`, `Germantown`, `The American`, `Central Pacific`,
|
||||||
`New Beginnings`, `Dutchlantis`, `Britain`, `New Zealand`, `South East Australia`,
|
`Orient Express`).
|
||||||
`Tex-Mex`, `Germantown`, `The American`, `Central Pacific`, `Orient Express`). That biases the
|
- the new title-hint probe narrows that evidence precisely:
|
||||||
remaining add-building source search away from the already-grounded title-fixup branch and
|
five of the six shipped carrier maps now show at least one grounded retagger-title hit,
|
||||||
toward some other late bringup owner between `0x00433130` reload and final kind-`8` service.
|
but only one map currently shows an adjacent embedded `.gmp` reference plus grounded title and
|
||||||
|
only one shows a same-stem pair. `Louisiana.gmp` carries
|
||||||
|
`Dutchlantis.gmp` / `Dutchlantis` at offset `0x73d0` with zero byte distance, while the other
|
||||||
|
current carrier-map hits stay weaker (`Alternate USA.gmp` late `Germany` / `France` /
|
||||||
|
`Britain`, `Chicago to New York.gmp` late `Germany` / `France`,
|
||||||
|
`Pacific Coastal.gmp` later `Central Pacific`, `Texas Tea.gmp` later `Germany`,
|
||||||
|
`Rhodes Unfinished.gmp` no current hit).
|
||||||
|
- that keeps the title-fixup branch alive but no longer as a broad filename-level explanation:
|
||||||
|
the evidence now supports a narrow “one strong `Louisiana -> Dutchlantis` overlap plus several
|
||||||
|
weaker prose-only or late-string overlaps” reading rather than a clean one-to-one mapping from
|
||||||
|
the shipped add-building carrier filenames to the grounded `0x00442c30` scenario-title set.
|
||||||
|
- the direct runtime-event comparison narrows it further too:
|
||||||
|
the checked note
|
||||||
|
`artifacts/exports/rt3-1.06/runtime-effect-kind8-title-overlap-note.md`
|
||||||
|
shows that `Louisiana.gmp` is the only carrier with a same-stem
|
||||||
|
`Dutchlantis.gmp` / `Dutchlantis` pair, but `Dutchlantis.gmp` itself still has no current
|
||||||
|
add-building dispatch rows while `Louisiana.gmp` keeps the one-row
|
||||||
|
`Add Building Warehouse05` cluster on
|
||||||
|
`nondirect-ge1e-h0001-0007-0000-5200-0200-p0000-0000-0000-ffff :: [7:0]`. So the strongest
|
||||||
|
current title overlap still does not reproduce the actual shipped add-building row family.
|
||||||
- the post-reload candidate set is checked in now too:
|
- the post-reload candidate set is checked in now too:
|
||||||
`artifacts/exports/rt3-1.06/runtime-effect-kind8-post-reload-candidates.md` extracts the
|
`artifacts/exports/rt3-1.06/runtime-effect-kind8-post-reload-candidates.md` extracts the
|
||||||
currently plausible late `0x00443a50` branches between ordinary reload and final kind-`8`
|
currently plausible late `0x00443a50` branches between ordinary reload and final kind-`8`
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue