From cbfe0a8df964b2c941b81a12649ae1a14f699dae Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Tue, 21 Apr 2026 22:15:17 -0700 Subject: [PATCH] Census locomotive tail blockers across local saves --- .../event-effects-semantic-catalog.json | 368 +++++------ .../locomotive-catalog-tail-census.json | 477 +++++++++++++++ .../rrt-cli/src/app/command/runtime/scan.rs | 5 + .../rrt-cli/src/app/dispatch/runtime/scan.rs | 7 +- .../src/app/runtime_scan/candidate_table.rs | 3 +- .../src/app/runtime_scan/locomotive_tail.rs | 569 ++++++++++++++++++ crates/rrt-cli/src/app/runtime_scan/mod.rs | 2 + .../src/documents/tests/packed_events.rs | 470 ++++++++++++++- .../src/documents/tests/support.rs | 163 ++--- .../smp/events/descriptors/locomotives.rs | 17 +- .../inspect/smp/tests/events/descriptors.rs | 56 +- docs/README.md | 14 +- docs/rehost-queue.md | 29 +- docs/rehost-queue/README.md | 5 + .../locomotive-descriptor-tails-2026-04-21.md | 139 +++++ docs/runtime-rehost-plan.md | 17 +- 16 files changed, 2022 insertions(+), 319 deletions(-) create mode 100644 artifacts/exports/rt3-1.06/locomotive-catalog-tail-census.json create mode 100644 crates/rrt-cli/src/app/runtime_scan/locomotive_tail.rs create mode 100644 docs/rehost-queue/locomotive-descriptor-tails-2026-04-21.md diff --git a/artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json b/artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json index d350e7a..a4318b5 100644 --- a/artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json +++ b/artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json @@ -2696,7 +2696,7 @@ }, { "descriptor_id": 299, - "label": "GP 35 Availability", + "label": "Lower-Band Locomotive Availability Slot 59", "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, @@ -2705,7 +2705,7 @@ }, { "descriptor_id": 300, - "label": "U1 Availability", + "label": "Lower-Band Locomotive Availability Slot 60", "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, @@ -2714,7 +2714,7 @@ }, { "descriptor_id": 301, - "label": "Zephyr Availability", + "label": "Lower-Band Locomotive Availability Slot 61", "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, @@ -2727,8 +2727,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 303, @@ -2736,8 +2736,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 304, @@ -2745,8 +2745,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 305, @@ -2754,8 +2754,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 306, @@ -2763,8 +2763,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 307, @@ -2772,8 +2772,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 308, @@ -2781,8 +2781,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 309, @@ -2790,8 +2790,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 310, @@ -2799,8 +2799,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 311, @@ -2808,8 +2808,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 312, @@ -2817,8 +2817,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 313, @@ -2826,8 +2826,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 314, @@ -2835,8 +2835,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 315, @@ -2844,8 +2844,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 316, @@ -2853,8 +2853,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 317, @@ -2862,8 +2862,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 318, @@ -2871,8 +2871,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 319, @@ -2880,8 +2880,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 320, @@ -2889,8 +2889,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 321, @@ -2898,8 +2898,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 322, @@ -2907,8 +2907,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 323, @@ -2916,8 +2916,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 324, @@ -2925,8 +2925,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 325, @@ -2934,8 +2934,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 326, @@ -2943,8 +2943,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 327, @@ -2952,8 +2952,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 328, @@ -2961,8 +2961,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 329, @@ -2970,8 +2970,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 330, @@ -2979,8 +2979,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 331, @@ -2988,8 +2988,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 332, @@ -2997,8 +2997,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 333, @@ -3006,8 +3006,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 334, @@ -3015,8 +3015,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 335, @@ -3024,8 +3024,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 336, @@ -3033,8 +3033,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 337, @@ -3042,8 +3042,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 338, @@ -3051,8 +3051,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 339, @@ -3060,8 +3060,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 340, @@ -3069,8 +3069,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 341, @@ -3078,8 +3078,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 342, @@ -3087,8 +3087,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 343, @@ -3096,8 +3096,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 344, @@ -3105,8 +3105,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 345, @@ -3114,8 +3114,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 346, @@ -3123,8 +3123,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 347, @@ -3132,8 +3132,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 348, @@ -3141,8 +3141,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 349, @@ -3150,8 +3150,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 350, @@ -3159,8 +3159,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 351, @@ -3168,8 +3168,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_availability_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 352, @@ -3695,7 +3695,7 @@ }, { "descriptor_id": 410, - "label": "GP 35 Cost", + "label": "Lower-Band Locomotive Cost Slot 59", "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, @@ -3704,7 +3704,7 @@ }, { "descriptor_id": 411, - "label": "U1 Cost", + "label": "Lower-Band Locomotive Cost Slot 60", "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, @@ -3713,7 +3713,7 @@ }, { "descriptor_id": 412, - "label": "Zephyr Cost", + "label": "Lower-Band Locomotive Cost Slot 61", "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, @@ -3726,8 +3726,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 414, @@ -3735,8 +3735,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 415, @@ -3744,8 +3744,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 416, @@ -3753,8 +3753,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 417, @@ -3762,8 +3762,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 418, @@ -3771,8 +3771,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 419, @@ -3780,8 +3780,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 420, @@ -3789,8 +3789,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 421, @@ -3798,8 +3798,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 422, @@ -3807,8 +3807,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 423, @@ -3816,8 +3816,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 424, @@ -3825,8 +3825,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 425, @@ -3834,8 +3834,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 426, @@ -3843,8 +3843,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 427, @@ -3852,8 +3852,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 428, @@ -3861,8 +3861,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 429, @@ -3870,8 +3870,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 430, @@ -3879,8 +3879,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 431, @@ -3888,8 +3888,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 432, @@ -3897,8 +3897,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 433, @@ -3906,8 +3906,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 434, @@ -3915,8 +3915,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 435, @@ -3924,8 +3924,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 436, @@ -3933,8 +3933,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 437, @@ -3942,8 +3942,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 438, @@ -3951,8 +3951,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 439, @@ -3960,8 +3960,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 440, @@ -3969,8 +3969,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 441, @@ -3978,8 +3978,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 442, @@ -3987,8 +3987,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 443, @@ -3996,8 +3996,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 444, @@ -4005,8 +4005,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 445, @@ -4014,8 +4014,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 446, @@ -4023,8 +4023,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 447, @@ -4032,8 +4032,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 448, @@ -4041,8 +4041,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 449, @@ -4050,8 +4050,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 450, @@ -4059,8 +4059,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 451, @@ -4068,8 +4068,8 @@ "target_mask_bits": 11, "parameter_family": "locomotive_cost_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 452, diff --git a/artifacts/exports/rt3-1.06/locomotive-catalog-tail-census.json b/artifacts/exports/rt3-1.06/locomotive-catalog-tail-census.json new file mode 100644 index 0000000..449ab47 --- /dev/null +++ b/artifacts/exports/rt3-1.06/locomotive-catalog-tail-census.json @@ -0,0 +1,477 @@ +{ + "root_path": "rt3_wineprefix/drive_c", + "file_count": 29, + "files_with_named_locomotive_table_count": 5, + "files_with_locomotive_catalog_count": 5, + "files_with_packed_event_collection_count": 26, + "stable_prefix_length": 58, + "stable_prefix_last_name": "VL80T", + "tail_cluster_count": 2, + "tail_clusters": [ + { + "entry_count": 63, + "tail_entries": [ + { + "locomotive_id": 59, + "name": "242 A1" + }, + { + "locomotive_id": 60, + "name": "Class 460" + }, + { + "locomotive_id": 61, + "name": "Class A1" + }, + { + "locomotive_id": 62, + "name": "Class P8" + }, + { + "locomotive_id": 63, + "name": "U1" + } + ], + "file_count": 1, + "sample_paths": [ + "rt3_wineprefix/drive_c/rt3_105/Saved Games/g.gms" + ], + "map_paths": [ + "Spanish Mainline.gmp" + ] + }, + { + "entry_count": 61, + "tail_entries": [ + { + "locomotive_id": 59, + "name": "GP 35" + }, + { + "locomotive_id": 60, + "name": "U1" + }, + { + "locomotive_id": 61, + "name": "Zephyr" + } + ], + "file_count": 4, + "sample_paths": [ + "rt3_wineprefix/drive_c/rt3_105/Saved Games/Autosave.gms", + "rt3_wineprefix/drive_c/rt3_105/Saved Games/nom.gms", + "rt3_wineprefix/drive_c/rt3_105/Saved Games/p.gms", + "rt3_wineprefix/drive_c/rt3_105/Saved Games/q.gms" + ], + "map_paths": [ + "Alternate USA.gmp", + "Southern Pacific.gmp" + ] + } + ], + "extra_shipped_name_coverage": [ + { + "name": "242 A1", + "observed_in_catalog_file_count": 1, + "observed_ordinals": [ + 59 + ], + "sample_paths": [ + "rt3_wineprefix/drive_c/rt3_105/Saved Games/g.gms" + ] + }, + { + "name": "Class 460", + "observed_in_catalog_file_count": 1, + "observed_ordinals": [ + 60 + ], + "sample_paths": [ + "rt3_wineprefix/drive_c/rt3_105/Saved Games/g.gms" + ] + }, + { + "name": "Class A1", + "observed_in_catalog_file_count": 1, + "observed_ordinals": [ + 61 + ], + "sample_paths": [ + "rt3_wineprefix/drive_c/rt3_105/Saved Games/g.gms" + ] + }, + { + "name": "Class P8", + "observed_in_catalog_file_count": 1, + "observed_ordinals": [ + 62 + ], + "sample_paths": [ + "rt3_wineprefix/drive_c/rt3_105/Saved Games/g.gms" + ] + }, + { + "name": "Class QJ", + "observed_in_catalog_file_count": 0, + "observed_ordinals": [], + "sample_paths": [] + } + ], + "descriptor_452_hits": { + "row_count": 0, + "file_count": 0, + "descriptor_ids_present": [], + "descriptor_labels_present": [], + "carrier_paths": [] + }, + "upper_availability_band_hits": { + "row_count": 0, + "file_count": 0, + "descriptor_ids_present": [], + "descriptor_labels_present": [], + "carrier_paths": [] + }, + "upper_cost_band_hits": { + "row_count": 0, + "file_count": 0, + "descriptor_ids_present": [], + "descriptor_labels_present": [], + "carrier_paths": [] + }, + "skipped_file_count": 3, + "samples": [ + { + "path": "rt3_wineprefix/drive_c/rt3/Saved Games/Autosave.gms", + "container_profile_family": "rt3-classic-save-container-v1", + "mechanism_family": "classic-save-rehydrate-v1", + "map_path": "British Isles.gmp", + "display_name": "British Isles", + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3/Saved Games/hh.gms", + "container_profile_family": "rt3-classic-save-container-v1", + "mechanism_family": "classic-save-rehydrate-v1", + "map_path": "British Isles.gmp", + "display_name": "British Isles", + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3/Saved Games/kk.gms", + "container_profile_family": "rt3-classic-save-container-v1", + "mechanism_family": "classic-save-rehydrate-v1", + "map_path": "British Isles.gmp", + "display_name": "British Isles", + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/111.gmx", + "container_profile_family": "rt3-105-sandbox-container-v1", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/Autosave.gms", + "container_profile_family": "rt3-105-save-container-v1", + "mechanism_family": "rt3-105-save-post-span-bridge-v1", + "map_path": "Alternate USA.gmp", + "display_name": "Alternate USA", + "named_locomotive_table_entry_count": 61, + "locomotive_catalog_entry_count": 61, + "tail_entries": [ + { + "locomotive_id": 59, + "name": "GP 35" + }, + { + "locomotive_id": 60, + "name": "U1" + }, + { + "locomotive_id": 61, + "name": "Zephyr" + } + ] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/Autosave.gmx", + "container_profile_family": "rt3-105-sandbox-container-v1", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/g.gms", + "container_profile_family": "rt3-105-alt-save-container-v1", + "mechanism_family": "rt3-105-alt-save-post-span-bridge-v1", + "map_path": "Spanish Mainline.gmp", + "display_name": "Spanish Mainline", + "named_locomotive_table_entry_count": 63, + "locomotive_catalog_entry_count": 63, + "tail_entries": [ + { + "locomotive_id": 59, + "name": "242 A1" + }, + { + "locomotive_id": 60, + "name": "Class 460" + }, + { + "locomotive_id": 61, + "name": "Class A1" + }, + { + "locomotive_id": 62, + "name": "Class P8" + }, + { + "locomotive_id": 63, + "name": "U1" + } + ] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/ggg.gmx", + "container_profile_family": "unknown", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/kai0.gmx", + "container_profile_family": "rt3-105-sandbox-container-v1", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/kai1.gmx", + "container_profile_family": "rt3-105-sandbox-container-v1", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/kai2.gmx", + "container_profile_family": "rt3-105-sandbox-container-v1", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/kai3.gmx", + "container_profile_family": "rt3-105-sandbox-container-v1", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/kai4.gmx", + "container_profile_family": "rt3-105-sandbox-container-v1", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/kai5.gmx", + "container_profile_family": "rt3-105-sandbox-container-v1", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/kai6.gmx", + "container_profile_family": "rt3-105-sandbox-container-v1", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/kai7.gmx", + "container_profile_family": "rt3-105-sandbox-container-v1", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/kai8.gmx", + "container_profile_family": "rt3-105-sandbox-container-v1", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/kai9.gmx", + "container_profile_family": "rt3-105-sandbox-container-v1", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/kaimama.gmx", + "container_profile_family": "rt3-105-sandbox-container-v1", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/lll.gmx", + "container_profile_family": "unknown", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/n.gmx", + "container_profile_family": "rt3-105-sandbox-container-v1", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/nom.gms", + "container_profile_family": "rt3-105-save-container-v1", + "mechanism_family": "rt3-105-save-post-span-bridge-v1", + "map_path": "Alternate USA.gmp", + "display_name": "Alternate USA", + "named_locomotive_table_entry_count": 61, + "locomotive_catalog_entry_count": 61, + "tail_entries": [ + { + "locomotive_id": 59, + "name": "GP 35" + }, + { + "locomotive_id": 60, + "name": "U1" + }, + { + "locomotive_id": 61, + "name": "Zephyr" + } + ] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/ooo.gmx", + "container_profile_family": "unknown", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/p.gms", + "container_profile_family": "rt3-105-scenario-save-container-v1", + "mechanism_family": "rt3-105-scenario-post-span-bridge-v1", + "map_path": "Southern Pacific.gmp", + "display_name": "Southern Pacific", + "named_locomotive_table_entry_count": 61, + "locomotive_catalog_entry_count": 61, + "tail_entries": [ + { + "locomotive_id": 59, + "name": "GP 35" + }, + { + "locomotive_id": 60, + "name": "U1" + }, + { + "locomotive_id": 61, + "name": "Zephyr" + } + ] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/ppp.gmx", + "container_profile_family": "rt3-105-sandbox-container-v1", + "mechanism_family": "rt3-105-candidate-catalog-source-v1", + "map_path": null, + "display_name": null, + "named_locomotive_table_entry_count": null, + "locomotive_catalog_entry_count": 0, + "tail_entries": [] + }, + { + "path": "rt3_wineprefix/drive_c/rt3_105/Saved Games/q.gms", + "container_profile_family": "rt3-105-scenario-save-container-v1", + "mechanism_family": "rt3-105-scenario-post-span-bridge-v1", + "map_path": "Southern Pacific.gmp", + "display_name": "Southern Pacific", + "named_locomotive_table_entry_count": 61, + "locomotive_catalog_entry_count": 61, + "tail_entries": [ + { + "locomotive_id": 59, + "name": "GP 35" + }, + { + "locomotive_id": 60, + "name": "U1" + }, + { + "locomotive_id": 61, + "name": "Zephyr" + } + ] + } + ] +} diff --git a/crates/rrt-cli/src/app/command/runtime/scan.rs b/crates/rrt-cli/src/app/command/runtime/scan.rs index ae19b1d..8921104 100644 --- a/crates/rrt-cli/src/app/command/runtime/scan.rs +++ b/crates/rrt-cli/src/app/command/runtime/scan.rs @@ -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(), diff --git a/crates/rrt-cli/src/app/dispatch/runtime/scan.rs b/crates/rrt-cli/src/app/dispatch/runtime/scan.rs index dfa4a23..a0e2ec6 100644 --- a/crates/rrt-cli/src/app/dispatch/runtime/scan.rs +++ b/crates/rrt-cli/src/app/dispatch/runtime/scan.rs @@ -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> { @@ -13,6 +13,9 @@ pub(super) fn dispatch_scan(command: ScanCommand) -> Result<(), Box { 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) diff --git a/crates/rrt-cli/src/app/runtime_scan/candidate_table.rs b/crates/rrt-cli/src/app/runtime_scan/candidate_table.rs index 135a80b..dec473b 100644 --- a/crates/rrt-cli/src/app/runtime_scan/candidate_table.rs +++ b/crates/rrt-cli/src/app/runtime_scan/candidate_table.rs @@ -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()) diff --git a/crates/rrt-cli/src/app/runtime_scan/locomotive_tail.rs b/crates/rrt-cli/src/app/runtime_scan/locomotive_tail.rs new file mode 100644 index 0000000..7641e60 --- /dev/null +++ b/crates/rrt-cli/src/app/runtime_scan/locomotive_tail.rs @@ -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, + mechanism_family: String, + map_path: Option, + display_name: Option, + named_locomotive_table_entry_count: Option, + locomotive_catalog_entries: Vec, + descriptor_rows: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct RuntimeLocomotiveDescriptorRowHit { + descriptor_id: u32, + descriptor_label: Option, +} + +#[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, + pub(crate) mechanism_family: String, + pub(crate) map_path: Option, + pub(crate) display_name: Option, + pub(crate) named_locomotive_table_entry_count: Option, + pub(crate) locomotive_catalog_entry_count: usize, + pub(crate) tail_entries: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub(crate) struct RuntimeLocomotiveCatalogTailCluster { + pub(crate) entry_count: usize, + pub(crate) tail_entries: Vec, + pub(crate) file_count: usize, + pub(crate) sample_paths: Vec, + pub(crate) map_paths: Vec, +} + +#[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, + pub(crate) sample_paths: Vec, +} + +#[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, + pub(crate) descriptor_labels_present: Vec, + pub(crate) carrier_paths: Vec, +} + +#[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, + pub(crate) tail_cluster_count: usize, + pub(crate) tail_clusters: Vec, + pub(crate) extra_shipped_name_coverage: Vec, + 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, +} + +pub(crate) fn scan_locomotive_catalog_tail( + root_path: &Path, +) -> Result<(), Box> { + 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 { + 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::>(); + reports.sort_by(|left, right| left.path.cmp(&right.path)); + reports +} + +fn stable_prefix_last_name( + samples: &[RuntimeLocomotiveCatalogTailScanSample], + stable_prefix_length: usize, +) -> Option { + (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::>(); + 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 { + let mut grouped = BTreeMap::< + Vec, + 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::>(); + let mut map_paths = samples + .iter() + .filter_map(|sample| sample.map_path.clone()) + .collect::>() + .into_iter() + .collect::>(); + 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::>(); + 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 { + 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 { + 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::>(); + 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, +) -> 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::>(); + 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> { + 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, +) -> Result<(), Box> { + 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::>(); + 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"))); + } +} diff --git a/crates/rrt-cli/src/app/runtime_scan/mod.rs b/crates/rrt-cli/src/app/runtime_scan/mod.rs index 0c47e8c..fc285f7 100644 --- a/crates/rrt-cli/src/app/runtime_scan/mod.rs +++ b/crates/rrt-cli/src/app/runtime_scan/mod.rs @@ -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, }; diff --git a/crates/rrt-runtime/src/documents/tests/packed_events.rs b/crates/rrt-runtime/src/documents/tests/packed_events.rs index 8b35096..a21b92c 100644 --- a/crates/rrt-runtime/src/documents/tests/packed_events.rs +++ b/crates/rrt-runtime/src/documents/tests/packed_events.rs @@ -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::>(); + 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::>(); + 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 { diff --git a/crates/rrt-runtime/src/documents/tests/support.rs b/crates/rrt-runtime/src/documents/tests/support.rs index 80a3653..9afc8f2 100644 --- a/crates/rrt-runtime/src/documents/tests/support.rs +++ b/crates/rrt-runtime/src/documents/tests/support.rs @@ -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::>(); + + save_named_locomotive_table_with_names(&names) +} + pub(super) fn save_cargo_catalog( entries: &[(u32, crate::event::targets::RuntimeCargoClass)], ) -> SmpLoadedCargoCatalog { diff --git a/crates/rrt-runtime/src/inspect/smp/events/descriptors/locomotives.rs b/crates/rrt-runtime/src/inspect/smp/events/descriptors/locomotives.rs index 683c740..fe17036 100644 --- a/crates/rrt-runtime/src/inspect/smp/events/descriptors/locomotives.rs +++ b/crates/rrt-runtime/src/inspect/smp/events/descriptors/locomotives.rs @@ -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 { 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 { 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, diff --git a/crates/rrt-runtime/src/inspect/smp/tests/events/descriptors.rs b/crates/rrt-runtime/src/inspect/smp/tests/events/descriptors.rs index 619498d..06f73d3 100644 --- a/crates/rrt-runtime/src/inspect/smp/tests/events/descriptors.rs +++ b/crates/rrt-runtime/src/inspect/smp/tests/events/descriptors.rs @@ -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); } diff --git a/docs/README.md b/docs/README.md index 4e569f6..9b1431b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -313,8 +313,12 @@ The highest-value next passes are now: - recovered scalar locomotive availability and locomotive-cost descriptors now import through that save-native or embedded `RuntimeState.locomotive_catalog` context into the ordinary `named_locomotive_availability` and `named_locomotive_cost` runtime maps -- the grounded executable lower locomotive prefix now extends through save-backed locomotive id - `61` (`Zephyr`); the unresolved lower tail and upper locomotive bands stay on explicit parity +- the full lower locomotive bands now execute through that save-native ordinal catalog too: + availability `241..351` and cost `352..451`; the checked `29`-save `.gms + .gmx` + `locomotive-catalog-tail-census.json` export now fixes the last save-stable static descriptor + boundary at ordinal `58` (`VL80T`) and leaves descriptor `452` plus the upper bands + `457..474` / `475..502` as external-corpus or dynamic blockers rather than active repo-local + static work - cargo-production `230..240` and territory-access-cost `453` now execute too through minimal world-side scalar landing surfaces: slot-indexed `cargo_production_overrides` and `world_restore.territory_access_cost` @@ -331,8 +335,10 @@ The highest-value next passes are now: - the company/chairman frontier has moved too: checked-in save-slice documents can now carry that context natively, so the next work on that axis is broader recovery and eventual raw save reconstruction rather than overlay-only ownership -- keep in mind that the current local `.gms` corpus still exports with no packed event collection, - so real descriptor mapping needs to stay plumbing-first until better captures exist +- keep in mind that the current local `.gms + .gmx` corpus does carry packed-event collections, + but the checked `29`-save locomotive census still finds no descriptor carriers in `452` or + `457..502`, so further locomotives-page closure now needs either broader save evidence or + dynamic captures - use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution environment - keep `docs/runtime-rehost-plan.md` current as the runtime baseline and next implementation slice diff --git a/docs/rehost-queue.md b/docs/rehost-queue.md index ced621b..27c94a9 100644 --- a/docs/rehost-queue.md +++ b/docs/rehost-queue.md @@ -10,18 +10,23 @@ This file is the short active queue for the current runtime and reverse-engineer ## Current Active Items -- Keep the periodic-company trace as the main shellless simulation frontier, with the next concrete control-lane pass focused on the ordinary loaded runtime-effect strip `0x00444d92 -> 0x00432f40(kind 8) -> 0x004323a0 -> 0x00431b20`. - The checked `rt3_105/maps` compact-dispatch corpus is now exported directly and partially mirrored into the periodic-company trace: `41` maps scanned, `38` with dispatch-strip rows, `318` nondirect rows total, the add-building subset is only `10` grouped occurrences across `7` descriptor keys, and the strongest broader nondirect families are now bounded too at `36` grouped occurrences across `18` maps for `nondirect-ge1e-h0001-0360-0004-0100-0200-p0000-0000-0000-ffff :: [864:4]` plus `27` across `14` maps for the mixed `[-1:4]` cluster. All of those checked rows still lack recovered trigger kind. The packed-state bridge is narrower than that queue head used to allow too: `0x0042db20/0x00430d70` rebuild and serialize only the fixed text bands plus the standalone and grouped row lists, while the metadata band `+0x7ee..+0x80e` is only mirrored by deep-copy helper `0x0042e050`. The active open question is therefore which ordinary loaded rows acquire or bypass the missing trigger-kind control lane before they can reach placed-structure mutation opcodes. - The dispatcher-side caller census is wider in a way that makes the remaining blocker sharper: `0x00432f40` is already driven shelllessly for kinds `1/0/3/2` and then `5/4` from the recurring simulation-maintenance strip `0x0040a220..0x0040a9ac`, for kind `7` from the grounded company-startup family, and for kind `6` from the placed-structure post-create, startup-refresh, and route-entry post-change tails, while `LoadScreen.win` still owns kind `9`. So the missing piece is no longer “find another shellless dispatcher entrypoint.” It is why ordinary loaded rows still fail to present a matching nonzero `[event+0x7ef]` when the later world-entry one-shot at `0x00444d92` requests kind `8`. - The largest direct writer table is ruled out now too: `0x004d8ea0` is the shell-side `EventConditions.win` commit helper, where controls `0x4e98..0x4ea2` write `[event+0x7ef] = 0..10` on the currently selected live event, so that seed family does not explain shellless post-load bringup. - The broad scenario-name fixup owner is narrower in the same direction: `0x00442c30` really does mutate live event rows after reload, but its grounded trigger-kind writes still only retag `1 -> 5` and `0 -> 2`, while the surrounding event-side branches only patch modifier bytes or nested payload dwords under already-existing kinds. No grounded branch there seeds kind `8`. - The metadata-copy helper is ruled out in the same way: `0x0042e050` really does clone `[event+0x7ef]`, but the current whole-binary caller search still finds only the shell-side selected-event clone path `0x004db8b0`, not any shellless post-load or periodic caller. - The direct write census is tighter in the same direction: the only grounded explicit write of value `8` into `[event+0x7ef]` is `0x004d91b3` inside that same shell helper, while the runtime-side grounded writers still only cover zero-init, copy, `2/3` follow-on seeds, and the later `5` / `2` retags. - Static progress on this head now appears genuinely blocked: the whole-binary `[...+0x7ef]` reference census still collapses to that same grounded writer set plus the already-known compare and copy helpers, so the next honest step likely requires hook-side or runtime tracing between reload `0x00433130` and the world-entry kind-`8` sweep at `0x00444d92`. +- No active repo-local non-dynamic items remain. + The last local static head was the locomotives-page tail, and the checked [locomotive catalog tail census](../artifacts/exports/rt3-1.06/locomotive-catalog-tail-census.json) now exhausts the full local `.gms + .gmx` corpus under `rt3_wineprefix/drive_c`: `29` candidate saves found, `26` parsed samples, `5` catalog-bearing saves, one save-stable ordinal prefix through `58` (`VL80T`), two observed `59+` tail clusters (`g.gms` with `242 A1 / Class 460 / Class A1 / Class P8 / U1`, and the four classic 1.05 saves with `GP 35 / U1 / Zephyr`), zero observed `Class QJ`, and zero packed-event carriers for descriptor `452` or the upper bands `457..474` / `475..502`. + The added `18` `.gmx` sandbox saves widen the local corpus and packed-event coverage, but they still contribute no named locomotive table and no derived `locomotive_catalog`, so they do not move the save-native tail evidence beyond the same five catalog-bearing `.gms` saves. + That means the remaining locomotive questions are no longer repo-local static work. They now require either a broader save corpus or dynamic tracing. + Preserved checked locomotive blocker detail now lives in [Locomotive descriptor tails](rehost-queue/locomotive-descriptor-tails-2026-04-21.md). + Preserved checked format inventory detail now lives in [RT3 format inventory](rehost-queue/format-inventory-2026-04-21.md). + +## Preserved External And Dynamic Blockers + +- The locomotives-page tail remains preserved as an external-corpus blocker. + Static repo-local work on this head is now exhausted: the local `29`-save `.gms + .gmx` census proves the stable prefix through `58`, proves the same two scenario-dependent `59+` tail families, and still finds no `Class QJ` placement plus no descriptor carriers in `452` or `457..502`. The next honest non-hook step requires a broader save corpus; the next non-static step requires dynamic tracing. + Preserved checked blocker detail now lives in [Locomotive descriptor tails](rehost-queue/locomotive-descriptor-tails-2026-04-21.md). +- The periodic-company control-lane head remains preserved as a dynamic blocker around the ordinary loaded runtime-effect strip `0x00444d92 -> 0x00432f40(kind 8) -> 0x004323a0 -> 0x00431b20`. + Static progress on this head now appears genuinely blocked: the whole-binary `[...+0x7ef]` reference census still collapses to the grounded writer set plus the already-known compare and copy helpers, so the next honest step likely requires hook-side or runtime tracing between reload `0x00433130` and the world-entry kind-`8` sweep at `0x00444d92`. Preserved checked control-lane detail now lives in [Periodic company control lane](rehost-queue/periodic-company-control-lane-2026-04-21.md). -- Keep the next static Tier-2 building pass focused on the earlier seed/projection seam into `0x00412d70`, not another broad `BuildingTypes` sweep. - The grounded owner strip is `0x004196c0 -> 0x00414490 -> 0x00416ce0 -> 0x00419230`, and the checked candidate-table exports now keep the concrete scenario-side families explicit too: among the `37` probe-bearing maps, `Port00/Warehouse00` stay at `35/43` on `30` maps and shift earlier to `10/18` on `7`, while `Port01..11` / `Warehouse01..11` stay fixed at `45..55` / `56..66` and the numbered trailer family splits independently at `0x00000001 -> 28 maps` versus `0x00000000 -> 9 maps`. The new crossover matrix stays mixed rather than collapsing to one side too: `35/43 :: 0x00000001 -> 25 maps`, `35/43 :: 0x00000000 -> 5 maps`, `10/18 :: 0x00000000 -> 4 maps`, and `10/18 :: 0x00000001 -> 3 maps`. The checked header-cluster export keeps the same root scan bounded to only `3` families: `0x00000000 / 0x00000000 -> 27 maps`, `0xcdcdcdcd / 0xcdcdcdcd -> 9 maps`, and `0x10000000 / 0x00009000 -> 1 map` (`Alternate USA.gmp`). The load-side handoff is narrower now too: `0x004120b0` explicitly reads `[candidate+0xba]` and `[candidate+0xbb]` as one-byte stream fields, and the very next projection owner `0x00412d70` immediately consumes those bytes in two passes, first `+0xba` and then `+0xbb`, to pick one seed row whose full `0x1f2`-dword body will be cloned or reused for each numbered runtime record. The stock decode side is narrower in the same direction: `0x00414490` does not just copy the `0xb8..0xbb` tail, it already derives the optional plane size from `[record+0xb8] * [record+0xb9] << 5` and uses the high nibble of `[record+0xba]` while materializing the four optional plane buffers at `[record+0xcf/+0xd3/+0xd7/+0xdb]`, before `0x00416ce0` remaps only the bare `port` / `warehouse` names and the later `0x00419230` rebank-or-clone pass consumes any bank-qualified owners. The same static pass rules out one lingering false lead too: the earlier suspected `0x00414500..0x00414b14` replay strip is not a separate serializer or import family at all, just the interior plane-decode band of `0x00414490`. The stock `BuildingTypes` corpus is narrower too: across `77` checked `.bca` files only `MachineShop.bca` carries nonzero selector bytes at `0xb8..0xbb`, while the broader nonzero stock signal lives in the `22`-file `.bty` alias-root family with `dword_0xbb = 0x000001f4`, especially the `TextileMill` branch that already covers `Port.bty` and `Warehouse.bty`. The active open question is therefore which later seed or projection seam turns that already-decoded stock-side shape or selector state together with the fixed numbered cluster into nonzero live `[candidate+0xba/+0xbb]` before `0x00412d70` and `0x00419230` consume it. - Static progress on this head is close to the same boundary now: the stock decode chain, the bare-name remap callback, the rebank-or-clone owner, and the earlier suspected mid-range replay strip are all grounded, so the next honest step likely requires runtime tracing of which source rows actually enter the live bank-qualified seed set. +- The Tier-2 building head remains preserved as a dynamic blocker around the seed/projection seam into `0x00412d70`. + The stock decode chain, the bare-name remap callback, the rebank-or-clone owner, and the earlier suspected mid-range replay strip are all now grounded, so the next honest step likely requires runtime tracing of which source rows actually enter the live bank-qualified seed set. Preserved checked row-family detail now lives in [Tier2 candidate row families](rehost-queue/tier2-candidate-row-families-2026-04-21.md). Preserved checked stock selector-byte detail now lives in [Tier2 selector-byte sources](rehost-queue/tier2-selector-byte-sources-2026-04-21.md). Preserved checked rebuild sequencing detail now lives in [Tier2 rebuild sequencing](rehost-queue/tier2-rebuild-sequencing-2026-04-21.md). @@ -29,6 +34,8 @@ This file is the short active queue for the current runtime and reverse-engineer ## Preserved Detail - [Archive snapshot](rehost-queue/archive-2026-04-19.md) +- [RT3 format inventory](rehost-queue/format-inventory-2026-04-21.md) +- [Locomotive descriptor tails](rehost-queue/locomotive-descriptor-tails-2026-04-21.md) - [Periodic company control lane](rehost-queue/periodic-company-control-lane-2026-04-21.md) - [Tier2 candidate row families](rehost-queue/tier2-candidate-row-families-2026-04-21.md) - [Tier2 rebuild sequencing](rehost-queue/tier2-rebuild-sequencing-2026-04-21.md) diff --git a/docs/rehost-queue/README.md b/docs/rehost-queue/README.md index f5408ce..1a3c1bc 100644 --- a/docs/rehost-queue/README.md +++ b/docs/rehost-queue/README.md @@ -4,6 +4,11 @@ This directory preserves older queue snapshots and long-form implementation note useful as evidence, but should not stay in the short active queue file. - `archive-2026-04-19.md`: preserved detailed queue snapshot from the pre-index cleanup. +- `format-inventory-2026-04-21.md`: current file-format inventory under `rt3/` and `rt3_105/`, + including the RT3-native families we still do not parse. +- `locomotive-descriptor-tails-2026-04-21.md`: checked `.gms + .gmx` local locomotive catalog + census and the remaining external-corpus blocker around `Class QJ`, descriptor `452`, and the + upper descriptor bands. - `periodic-company-control-lane-2026-04-21.md`: checked compact-dispatch/control-lane evidence for the active periodic-company queue head, including the top ordinary nondirect cluster carrier maps. diff --git a/docs/rehost-queue/locomotive-descriptor-tails-2026-04-21.md b/docs/rehost-queue/locomotive-descriptor-tails-2026-04-21.md new file mode 100644 index 0000000..c7a5e24 --- /dev/null +++ b/docs/rehost-queue/locomotive-descriptor-tails-2026-04-21.md @@ -0,0 +1,139 @@ +# Locomotive Descriptor Tails (2026-04-21) + +This note preserves the checked evidence behind the locomotives-page tail after the full local +`.gms + .gmx` save-corpus pass, so the short queue can treat the remaining work honestly as an +external-corpus or dynamic blocker rather than another repo-local static task. + +## Local Save-Corpus Census + +The checked [locomotive catalog tail census](../../artifacts/exports/rt3-1.06/locomotive-catalog-tail-census.json) +now exhausts the full local save corpus under `rt3_wineprefix/drive_c`: + +- `29` candidate saves found +- `26` parsed samples +- `5` saves with a save-side named locomotive table and derived `locomotive_catalog` +- `26` saves with packed-event collections present +- one save-stable catalog prefix through ordinal `58` (`VL80T`) +- two observed tail clusters beyond that prefix: + - one `63`-entry tail on `g.gms` / `Spanish Mainline.gmp`: `242 A1 / Class 460 / Class A1 / + Class P8 / U1` + - one `61`-entry tail on the four 1.05 save families: `GP 35 / U1 / Zephyr` + +The added `18` `.gmx` sandbox saves still carry no reconstructed named locomotive table and no +derived `locomotive_catalog`, so they widen the local event corpus without widening the save-native +ordinal catalog evidence. The three classic `rt3/` `.gms` saves (`Autosave.gms`, `hh.gms`, +`kk.gms`) remain catalog-free too, so the save-native tail evidence is still carried only by the +same five 1.05 `.gms` saves. + +The scan still skips the three classic `rt3/` sandbox saves (`Autosave.gmx`, `aaa.gmx`, +`bbb.gmx`) at save-slice load time, so the currently parsed sample set is `26` rather than the +full `29` candidate paths. That does not change the locomotive blocker: none of the successfully +parsed `.gmx` samples widens the save-native catalog, places `Class QJ`, or surfaces descriptor +carriers in `452` or `457..502`. + +## Grounded Lower Bands + +The lower descriptor bands are now grounded enough to execute through the existing save-native +owner seam: + +- availability descriptors `241..351` already map directly onto recovered locomotive ids `1..111` +- cost descriptors `352..451` already map directly onto recovered locomotive ids `1..100` +- the save-side named locomotive table already projects into `RuntimeState.locomotive_catalog` as + one ordinal id-to-name map derived from row order +- that means the remaining lower-tail rows `302..351` and `413..451` are no longer missing an + owner seam; they are just later ordinals on the same catalog + +The old “grounded executable prefix through save-backed locomotive id `61` (`Zephyr`)" boundary is +therefore retired. The whole lower bands are executable whenever the save or overlay supplies the +catalog context. + +## Grounded Shipped Name Cohort + +The remaining lower-tail labels are no longer blocked on missing shipped engine names. + +The checked export [engine-type locomotive display census](../../artifacts/exports/rt3-1.06/engine-type-locomotive-display-census.json) +now parses the fixed `.car` header fields for every shipped `Data/EngineTypes` locomotive pair: + +- `66` shipped `.car` / `.lco` locomotive families +- `61` primary display names that match the broader checked shipped-name prefix +- `5` extra shipped named families not present in the current grounded prefix: + - `242 A1` + - `Class 460` + - `Class A1` + - `Class P8` + - `Class QJ` + +That means the remaining lower-tail uncertainty is no longer “what are the extra shipped +locomotive names?” It is where those five named families land in the live ordinal catalog that the +save-side named availability table mirrors. + +The full local save corpus tightens that further. The only observed nonclassic tail in the checked +census is `rt3_wineprefix/drive_c/rt3_105/Saved Games/g.gms`, which exposes one `63`-entry named +locomotive table whose observed tail is: + +- `59` `242 A1` +- `60` `Class 460` +- `61` `Class A1` +- `62` `Class P8` +- `63` `U1` + +That observation matters because the four catalog-bearing 1.05 save families still carry the +classic `59..61` tail `GP 35 / U1 / Zephyr`. So the currently honest static descriptor-name +boundary is not the broader shipped-name prefix through `61`; it is the last save-stable prefix +through ordinal `58` (`VL80T`). After that point, save-backed ordinal names are real and +executable, but they are no longer universal enough to hard-code in the static semantic catalog. + +The same checked local corpus still does **not** surface `Class QJ`, even though the shipped +engine-type corpus proves that family exists on disk. So the current non-hook evidence only closes +the dynamic tail through the observed `63`-entry family above; it does not yet place the fifth +extra shipped name in any live ordinal slot. + +The same checked local corpus also exposes no packed-event rows in descriptor range `452..502`, so +the remaining `Unknown Loco Cost` and upper-band frontier is absent from the local save-backed +event corpus as well as unmapped in the static descriptor metadata. + +## Remaining Blocker + +What is left is narrower and cleaner, but it is no longer a repo-local static task: + +- ordinal placement of the scenario-dependent locomotive tail beyond the save-stable `1..58` prefix +- descriptor `452` `Unknown Loco Cost` +- upper availability band `457..474` +- upper cost band `475..502` + +The upper bands still carry only slot labels in the checked semantic catalog: + +- `Upper-Band Locomotive Availability Slot 1..18` +- `Upper-Band Locomotive Cost Slot 1..28` + +The lower bands now split cleanly into two layers: + +- ordinals `1..58` keep fixed checked descriptor labels through `VL80T` +- ordinals `59+` stay executable through recovered ids plus save catalog context, but the checked + semantic catalog keeps generic slot labels because the local save corpus already shows + scenario-dependent tails + +Unlike the lower bands, the current checked row summary metadata does not yet recover live +locomotive ids for the upper-band descriptors at all. + +## Blocker Result + +The next question is no longer whether locomotive page descriptors can import through a save-native +owner seam at all, or whether the shipped engine-type files even have stable names. The checked +repo-local answer is already as tight as it can get: + +- where the extra shipped named families (`242 A1`, `Class 460`, `Class A1`, `Class P8`, + `Class QJ`) land across the scenario-dependent tail beyond the save-stable `1..58` prefix, and +- whether the upper bands continue the same ordinal locomotive catalog after the lower `1..111` / + `1..100` families, or +- do they belong to a separate page-owned selector family that only sits adjacent in the recovered + `EventEffects` table + +Descriptor `452` should stay separate from that question until stronger evidence ties it to either +the lower cost band or the upper tails. + +The current repo-local blocker is therefore explicit: + +- the next honest non-hook step needs a broader save corpus than the eight local saves already + exhausted here +- the next honest non-static step needs dynamic tracing or hooks diff --git a/docs/runtime-rehost-plan.md b/docs/runtime-rehost-plan.md index 8227572..ad938aa 100644 --- a/docs/runtime-rehost-plan.md +++ b/docs/runtime-rehost-plan.md @@ -166,13 +166,16 @@ Implemented today: - the recovered locomotives-page availability bands can now import as full scalar overrides through `RuntimeState.locomotive_catalog` into `RuntimeState.named_locomotive_availability`; raw `.smp` inspection/export now reconstructs the save-side locomotive row family and derives the - catalog directly into save-slice documents, so standalone save-slice imports can execute those - rows whenever the save carries enough catalog entries, and the grounded executable lower prefix - now extends through save-backed locomotive id `61` (`Zephyr`) -- the grounded lower locomotive-cost band `352..409` now imports too through the same save-native - or embedded catalog into the event-owned `RuntimeState.named_locomotive_cost` map when its - scalar payloads are nonnegative; the unresolved lower tail and upper cost tail now stay on - explicit parity instead of synthetic execution + catalog directly into save-slice documents, so standalone save-slice imports can execute the + full lower availability band `241..351` whenever the save carries enough catalog entries; the + checked `29`-save `.gms + .gmx` `locomotive-catalog-tail-census.json` export now fixes the last + save-stable static boundary at ordinal `58` (`VL80T`), leaving the upper bands `457..474`, + `475..502`, plus descriptor `452` as external-corpus or dynamic blockers instead of active + repo-local static work +- the full lower locomotive-cost band `352..451` now imports too through the same save-native or + embedded catalog into the event-owned `RuntimeState.named_locomotive_cost` map when its scalar + payloads are nonnegative; the remaining unresolved tail is the separate descriptor `452` plus + the upper cost band `475..502` - the remaining recovered scalar world families now execute as well: cargo-production `230..240` rows lower into slot-indexed `cargo_production_overrides`, and territory-access-cost descriptor `453` lowers into `world_restore.territory_access_cost`