Census locomotive tail blockers across local saves

This commit is contained in:
Jan Petykiewicz 2026-04-21 22:15:17 -07:00
commit cbfe0a8df9
16 changed files with 2022 additions and 319 deletions

View file

@ -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,

View file

@ -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"
}
]
}
]
}

View file

@ -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(),

View file

@ -1,8 +1,8 @@
use crate::app::command::ScanCommand;
use crate::app::runtime_scan::{
scan_aligned_runtime_rule_band, scan_candidate_table_headers, scan_candidate_table_named_runs,
scan_post_special_conditions_scalars, scan_post_special_conditions_tail,
scan_recipe_book_lines, scan_special_conditions,
scan_locomotive_catalog_tail, scan_post_special_conditions_scalars,
scan_post_special_conditions_tail, scan_recipe_book_lines, scan_special_conditions,
};
pub(super) fn dispatch_scan(command: ScanCommand) -> Result<(), Box<dyn std::error::Error>> {
@ -13,6 +13,9 @@ pub(super) fn dispatch_scan(command: ScanCommand) -> Result<(), Box<dyn std::err
ScanCommand::ScanCandidateTableNamedRuns { root_path } => {
scan_candidate_table_named_runs(&root_path)
}
ScanCommand::ScanLocomotiveCatalogTail { root_path } => {
scan_locomotive_catalog_tail(&root_path)
}
ScanCommand::ScanSpecialConditions { root_path } => scan_special_conditions(&root_path),
ScanCommand::ScanAlignedRuntimeRuleBand { root_path } => {
scan_aligned_runtime_rule_band(&root_path)

View file

@ -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())

View file

@ -0,0 +1,569 @@
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::path::{Path, PathBuf};
use serde::Serialize;
use rrt_runtime::inspect::smp::save_load::{
SmpLoadedLocomotiveCatalogEntry, SmpLoadedSaveSlice, load_save_slice_file,
};
const EXTRA_SHIPPED_ENGINE_TYPE_NAMES: [&str; 5] =
["242 A1", "Class 460", "Class A1", "Class P8", "Class QJ"];
#[derive(Debug, Clone)]
struct RuntimeLocomotiveCatalogTailScanSample {
path: String,
container_profile_family: Option<String>,
mechanism_family: String,
map_path: Option<String>,
display_name: Option<String>,
named_locomotive_table_entry_count: Option<usize>,
locomotive_catalog_entries: Vec<SmpLoadedLocomotiveCatalogEntry>,
descriptor_rows: Vec<RuntimeLocomotiveDescriptorRowHit>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct RuntimeLocomotiveDescriptorRowHit {
descriptor_id: u32,
descriptor_label: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
pub(crate) struct RuntimeObservedLocomotiveCatalogEntry {
pub(crate) locomotive_id: u32,
pub(crate) name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct RuntimeLocomotiveCatalogTailScanSampleReport {
pub(crate) path: String,
pub(crate) container_profile_family: Option<String>,
pub(crate) mechanism_family: String,
pub(crate) map_path: Option<String>,
pub(crate) display_name: Option<String>,
pub(crate) named_locomotive_table_entry_count: Option<usize>,
pub(crate) locomotive_catalog_entry_count: usize,
pub(crate) tail_entries: Vec<RuntimeObservedLocomotiveCatalogEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct RuntimeLocomotiveCatalogTailCluster {
pub(crate) entry_count: usize,
pub(crate) tail_entries: Vec<RuntimeObservedLocomotiveCatalogEntry>,
pub(crate) file_count: usize,
pub(crate) sample_paths: Vec<String>,
pub(crate) map_paths: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct RuntimeLocomotiveExtraNameCoverageSummary {
pub(crate) name: String,
pub(crate) observed_in_catalog_file_count: usize,
pub(crate) observed_ordinals: Vec<u32>,
pub(crate) sample_paths: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct RuntimeLocomotiveDescriptorBandHitSummary {
pub(crate) row_count: usize,
pub(crate) file_count: usize,
pub(crate) descriptor_ids_present: Vec<u32>,
pub(crate) descriptor_labels_present: Vec<String>,
pub(crate) carrier_paths: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct RuntimeLocomotiveCatalogTailCensusReport {
pub(crate) root_path: String,
pub(crate) file_count: usize,
pub(crate) files_with_named_locomotive_table_count: usize,
pub(crate) files_with_locomotive_catalog_count: usize,
pub(crate) files_with_packed_event_collection_count: usize,
pub(crate) stable_prefix_length: usize,
pub(crate) stable_prefix_last_name: Option<String>,
pub(crate) tail_cluster_count: usize,
pub(crate) tail_clusters: Vec<RuntimeLocomotiveCatalogTailCluster>,
pub(crate) extra_shipped_name_coverage: Vec<RuntimeLocomotiveExtraNameCoverageSummary>,
pub(crate) descriptor_452_hits: RuntimeLocomotiveDescriptorBandHitSummary,
pub(crate) upper_availability_band_hits: RuntimeLocomotiveDescriptorBandHitSummary,
pub(crate) upper_cost_band_hits: RuntimeLocomotiveDescriptorBandHitSummary,
pub(crate) skipped_file_count: usize,
pub(crate) samples: Vec<RuntimeLocomotiveCatalogTailScanSampleReport>,
}
pub(crate) fn scan_locomotive_catalog_tail(
root_path: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let mut candidate_paths = Vec::new();
collect_locomotive_tail_input_paths(root_path, &mut candidate_paths)?;
let file_count = candidate_paths.len();
let mut samples = Vec::new();
let mut skipped_file_count = 0usize;
for path in candidate_paths {
match load_locomotive_catalog_tail_scan_sample(&path) {
Ok(sample) => samples.push(sample),
Err(_) => skipped_file_count += 1,
}
}
let files_with_named_locomotive_table_count = samples
.iter()
.filter(|sample| sample.named_locomotive_table_entry_count.is_some())
.count();
let files_with_locomotive_catalog_count = samples
.iter()
.filter(|sample| !sample.locomotive_catalog_entries.is_empty())
.count();
let files_with_packed_event_collection_count = samples
.iter()
.filter(|sample| !sample.descriptor_rows.is_empty())
.count();
let stable_prefix_length = stable_catalog_prefix_length(&samples);
let stable_prefix_last_name = stable_prefix_last_name(&samples, stable_prefix_length);
let tail_clusters = build_tail_clusters(&samples, stable_prefix_length);
let extra_shipped_name_coverage = build_extra_shipped_name_coverage(&samples);
let descriptor_452_hits = build_descriptor_band_hit_summary(&samples, 452..=452);
let upper_availability_band_hits = build_descriptor_band_hit_summary(&samples, 457..=474);
let upper_cost_band_hits = build_descriptor_band_hit_summary(&samples, 475..=502);
let sample_reports = build_sample_reports(&samples, stable_prefix_length);
let report = RuntimeLocomotiveCatalogTailCensusReport {
root_path: root_path.display().to_string(),
file_count,
files_with_named_locomotive_table_count,
files_with_locomotive_catalog_count,
files_with_packed_event_collection_count,
stable_prefix_length,
stable_prefix_last_name,
tail_cluster_count: tail_clusters.len(),
tail_clusters,
extra_shipped_name_coverage,
descriptor_452_hits,
upper_availability_band_hits,
upper_cost_band_hits,
skipped_file_count,
samples: sample_reports,
};
println!("{}", serde_json::to_string_pretty(&report)?);
Ok(())
}
fn build_sample_reports(
samples: &[RuntimeLocomotiveCatalogTailScanSample],
stable_prefix_length: usize,
) -> Vec<RuntimeLocomotiveCatalogTailScanSampleReport> {
let mut reports = samples
.iter()
.map(|sample| RuntimeLocomotiveCatalogTailScanSampleReport {
path: sample.path.clone(),
container_profile_family: sample.container_profile_family.clone(),
mechanism_family: sample.mechanism_family.clone(),
map_path: sample.map_path.clone(),
display_name: sample.display_name.clone(),
named_locomotive_table_entry_count: sample.named_locomotive_table_entry_count,
locomotive_catalog_entry_count: sample.locomotive_catalog_entries.len(),
tail_entries: tail_entries(sample, stable_prefix_length),
})
.collect::<Vec<_>>();
reports.sort_by(|left, right| left.path.cmp(&right.path));
reports
}
fn stable_prefix_last_name(
samples: &[RuntimeLocomotiveCatalogTailScanSample],
stable_prefix_length: usize,
) -> Option<String> {
(stable_prefix_length != 0).then(|| {
samples
.iter()
.find_map(|sample| {
sample
.locomotive_catalog_entries
.get(stable_prefix_length - 1)
})
.map(|entry| entry.name.clone())
.expect("stable prefix entry should exist")
})
}
fn stable_catalog_prefix_length(samples: &[RuntimeLocomotiveCatalogTailScanSample]) -> usize {
let catalog_samples = samples
.iter()
.filter(|sample| !sample.locomotive_catalog_entries.is_empty())
.collect::<Vec<_>>();
if catalog_samples.is_empty() {
return 0;
}
let min_count = catalog_samples
.iter()
.map(|sample| sample.locomotive_catalog_entries.len())
.min()
.unwrap_or(0);
let mut stable_prefix_length = 0usize;
for index in 0..min_count {
let first_name = &catalog_samples[0].locomotive_catalog_entries[index].name;
if catalog_samples
.iter()
.all(|sample| sample.locomotive_catalog_entries[index].name == *first_name)
{
stable_prefix_length += 1;
} else {
break;
}
}
stable_prefix_length
}
fn build_tail_clusters(
samples: &[RuntimeLocomotiveCatalogTailScanSample],
stable_prefix_length: usize,
) -> Vec<RuntimeLocomotiveCatalogTailCluster> {
let mut grouped = BTreeMap::<
Vec<RuntimeObservedLocomotiveCatalogEntry>,
Vec<&RuntimeLocomotiveCatalogTailScanSample>,
>::new();
for sample in samples
.iter()
.filter(|sample| !sample.locomotive_catalog_entries.is_empty())
{
grouped
.entry(tail_entries(sample, stable_prefix_length))
.or_default()
.push(sample);
}
let mut clusters = grouped
.into_iter()
.map(|(tail_entries, samples)| {
let mut sample_paths = samples
.iter()
.map(|sample| sample.path.clone())
.collect::<Vec<_>>();
let mut map_paths = samples
.iter()
.filter_map(|sample| sample.map_path.clone())
.collect::<BTreeSet<_>>()
.into_iter()
.collect::<Vec<_>>();
sample_paths.sort();
map_paths.sort();
RuntimeLocomotiveCatalogTailCluster {
entry_count: stable_prefix_length + tail_entries.len(),
tail_entries,
file_count: samples.len(),
sample_paths,
map_paths,
}
})
.collect::<Vec<_>>();
clusters.sort_by(|left, right| {
left.tail_entries
.cmp(&right.tail_entries)
.then(left.entry_count.cmp(&right.entry_count))
});
clusters
}
fn tail_entries(
sample: &RuntimeLocomotiveCatalogTailScanSample,
stable_prefix_length: usize,
) -> Vec<RuntimeObservedLocomotiveCatalogEntry> {
sample
.locomotive_catalog_entries
.iter()
.skip(stable_prefix_length)
.map(|entry| RuntimeObservedLocomotiveCatalogEntry {
locomotive_id: entry.locomotive_id,
name: entry.name.clone(),
})
.collect()
}
fn build_extra_shipped_name_coverage(
samples: &[RuntimeLocomotiveCatalogTailScanSample],
) -> Vec<RuntimeLocomotiveExtraNameCoverageSummary> {
EXTRA_SHIPPED_ENGINE_TYPE_NAMES
.into_iter()
.map(|name| {
let mut observed_ordinals = BTreeSet::new();
let mut sample_paths = Vec::new();
for sample in samples {
let matching_ordinals = sample
.locomotive_catalog_entries
.iter()
.filter(|entry| entry.name == name)
.map(|entry| entry.locomotive_id)
.collect::<Vec<_>>();
if matching_ordinals.is_empty() {
continue;
}
observed_ordinals.extend(matching_ordinals);
sample_paths.push(sample.path.clone());
}
sample_paths.sort();
RuntimeLocomotiveExtraNameCoverageSummary {
name: name.to_string(),
observed_in_catalog_file_count: sample_paths.len(),
observed_ordinals: observed_ordinals.into_iter().collect(),
sample_paths,
}
})
.collect()
}
fn build_descriptor_band_hit_summary(
samples: &[RuntimeLocomotiveCatalogTailScanSample],
descriptor_ids: std::ops::RangeInclusive<u32>,
) -> RuntimeLocomotiveDescriptorBandHitSummary {
let mut row_count = 0usize;
let mut carrier_paths = Vec::new();
let mut descriptor_ids_present = BTreeSet::new();
let mut descriptor_labels_present = BTreeSet::new();
for sample in samples {
let matching_rows = sample
.descriptor_rows
.iter()
.filter(|row| descriptor_ids.contains(&row.descriptor_id))
.collect::<Vec<_>>();
if matching_rows.is_empty() {
continue;
}
row_count += matching_rows.len();
carrier_paths.push(sample.path.clone());
for row in matching_rows {
descriptor_ids_present.insert(row.descriptor_id);
if let Some(label) = &row.descriptor_label {
descriptor_labels_present.insert(label.clone());
}
}
}
carrier_paths.sort();
RuntimeLocomotiveDescriptorBandHitSummary {
row_count,
file_count: carrier_paths.len(),
descriptor_ids_present: descriptor_ids_present.into_iter().collect(),
descriptor_labels_present: descriptor_labels_present.into_iter().collect(),
carrier_paths,
}
}
fn load_locomotive_catalog_tail_scan_sample(
smp_path: &Path,
) -> Result<RuntimeLocomotiveCatalogTailScanSample, Box<dyn std::error::Error>> {
let save_slice = load_save_slice_file(smp_path)?;
Ok(build_locomotive_catalog_tail_scan_sample(
smp_path,
&save_slice,
))
}
fn build_locomotive_catalog_tail_scan_sample(
smp_path: &Path,
save_slice: &SmpLoadedSaveSlice,
) -> RuntimeLocomotiveCatalogTailScanSample {
RuntimeLocomotiveCatalogTailScanSample {
path: smp_path.display().to_string(),
container_profile_family: save_slice.container_profile_family.clone(),
mechanism_family: save_slice.mechanism_family.clone(),
map_path: save_slice
.profile
.as_ref()
.and_then(|profile| profile.map_path.clone()),
display_name: save_slice
.profile
.as_ref()
.and_then(|profile| profile.display_name.clone()),
named_locomotive_table_entry_count: save_slice
.named_locomotive_availability_table
.as_ref()
.map(|table| table.observed_entry_count),
locomotive_catalog_entries: save_slice
.locomotive_catalog
.as_ref()
.map(|catalog| catalog.entries.clone())
.unwrap_or_default(),
descriptor_rows: save_slice
.event_runtime_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.flat_map(|record| {
record.grouped_effect_rows.iter().map(|row| {
RuntimeLocomotiveDescriptorRowHit {
descriptor_id: row.descriptor_id,
descriptor_label: row.descriptor_label.clone(),
}
})
})
.collect()
})
.unwrap_or_default(),
}
}
fn collect_locomotive_tail_input_paths(
root_path: &Path,
out: &mut Vec<PathBuf>,
) -> Result<(), Box<dyn std::error::Error>> {
let metadata = match fs::symlink_metadata(root_path) {
Ok(metadata) => metadata,
Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => return Ok(()),
Err(err) => return Err(err.into()),
};
if metadata.file_type().is_symlink() {
return Ok(());
}
if root_path.is_file() {
if path_is_locomotive_tail_input(root_path) {
out.push(root_path.to_path_buf());
}
return Ok(());
}
let entries = match fs::read_dir(root_path) {
Ok(entries) => entries,
Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => return Ok(()),
Err(err) => return Err(err.into()),
};
for entry in entries {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
collect_locomotive_tail_input_paths(&path, out)?;
continue;
}
if path_is_locomotive_tail_input(&path) {
out.push(path);
}
}
Ok(())
}
fn path_is_locomotive_tail_input(path: &Path) -> bool {
path.extension()
.and_then(|ext| ext.to_str())
.is_some_and(|ext| matches!(ext.to_ascii_lowercase().as_str(), "gms" | "gmx" | "smp"))
}
#[cfg(test)]
mod tests {
use super::*;
fn sample(
path: &str,
names: &[&str],
descriptor_ids: &[u32],
) -> RuntimeLocomotiveCatalogTailScanSample {
RuntimeLocomotiveCatalogTailScanSample {
path: path.to_string(),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
mechanism_family: "classic-save-rehydrate-v1".to_string(),
map_path: Some(format!("{path}.gmp")),
display_name: Some(path.to_string()),
named_locomotive_table_entry_count: Some(names.len()),
locomotive_catalog_entries: names
.iter()
.enumerate()
.map(|(index, name)| SmpLoadedLocomotiveCatalogEntry {
locomotive_id: (index + 1) as u32,
name: (*name).to_string(),
})
.collect(),
descriptor_rows: descriptor_ids
.iter()
.map(|descriptor_id| RuntimeLocomotiveDescriptorRowHit {
descriptor_id: *descriptor_id,
descriptor_label: Some(format!("Descriptor {descriptor_id}")),
})
.collect(),
}
}
#[test]
fn computes_stable_prefix_length_across_catalog_samples() {
let samples = vec![
sample("a", &["Big Boy", "VL80T", "242 A1"], &[]),
sample("b", &["Big Boy", "VL80T", "GP 35"], &[]),
];
assert_eq!(stable_catalog_prefix_length(&samples), 2);
assert_eq!(
stable_prefix_last_name(&samples, 2),
Some("VL80T".to_string())
);
}
#[test]
fn groups_tail_clusters_by_exact_tail_sequence() {
let samples = vec![
sample("a", &["Big Boy", "VL80T", "242 A1", "Class 460"], &[]),
sample("b", &["Big Boy", "VL80T", "242 A1", "Class 460"], &[]),
sample("c", &["Big Boy", "VL80T", "GP 35", "U1"], &[]),
];
let clusters = build_tail_clusters(&samples, 2);
assert_eq!(clusters.len(), 2);
let counts = clusters
.iter()
.map(|cluster| cluster.file_count)
.collect::<Vec<_>>();
assert_eq!(counts, vec![2, 1]);
}
#[test]
fn summarizes_extra_shipped_name_coverage() {
let samples = vec![
sample("a", &["Big Boy", "242 A1", "Class 460"], &[]),
sample("b", &["Big Boy", "Class QJ"], &[]),
];
let coverage = build_extra_shipped_name_coverage(&samples);
assert_eq!(coverage[0].name, "242 A1");
assert_eq!(coverage[0].observed_in_catalog_file_count, 1);
assert_eq!(coverage[0].observed_ordinals, vec![2]);
assert_eq!(coverage[4].name, "Class QJ");
assert_eq!(coverage[4].observed_in_catalog_file_count, 1);
assert_eq!(coverage[4].observed_ordinals, vec![2]);
}
#[test]
fn summarizes_descriptor_band_hits() {
let samples = vec![
sample("a", &["Big Boy"], &[452, 457, 475]),
sample("b", &["Big Boy"], &[457, 458]),
];
let descriptor_452_hits = build_descriptor_band_hit_summary(&samples, 452..=452);
let upper_availability_hits = build_descriptor_band_hit_summary(&samples, 457..=474);
assert_eq!(descriptor_452_hits.row_count, 1);
assert_eq!(descriptor_452_hits.file_count, 1);
assert_eq!(upper_availability_hits.row_count, 3);
assert_eq!(upper_availability_hits.file_count, 2);
assert_eq!(
upper_availability_hits.descriptor_ids_present,
vec![457, 458]
);
}
#[test]
fn accepts_gmx_inputs_in_locomotive_tail_scan() {
assert!(path_is_locomotive_tail_input(Path::new("save.gms")));
assert!(path_is_locomotive_tail_input(Path::new("sandbox.gmx")));
assert!(path_is_locomotive_tail_input(Path::new("fixture.smp")));
assert!(!path_is_locomotive_tail_input(Path::new("map.gmp")));
}
}

View file

@ -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,
};

View file

@ -1571,8 +1571,10 @@ fn blocks_scalar_locomotive_availability_rows_without_catalog_context() {
grouped_effect_rows: vec![SmpLoadedPackedEventGroupedEffectRowSummary {
group_index: 0,
row_index: 0,
descriptor_id: 250,
descriptor_label: Some("Big Boy 4-8-8-4 Availability".to_string()),
descriptor_id: 302,
descriptor_label: Some(
"Lower-Band Locomotive Availability Slot 62".to_string(),
),
target_mask_bits: Some(0x08),
parameter_family: Some("locomotive_availability_scalar".to_string()),
grouped_target_subject: None,
@ -1587,11 +1589,13 @@ fn blocks_scalar_locomotive_availability_rows_without_catalog_context() {
value_word_0x16: 0,
row_shape: "scalar_assignment".to_string(),
semantic_family: Some("scalar_assignment".to_string()),
semantic_preview: Some("Set Big Boy 4-8-8-4 Availability to 42".to_string()),
semantic_preview: Some(
"Set Lower-Band Locomotive Availability Slot 62 to 42".to_string(),
),
recovered_cargo_slot: None,
recovered_cargo_class: None,
recovered_cargo_label: None,
recovered_locomotive_id: Some(10),
recovered_locomotive_id: Some(62),
locomotive_name: None,
notes: vec![],
}],
@ -1599,8 +1603,9 @@ fn blocks_scalar_locomotive_availability_rows_without_catalog_context() {
decoded_actions: vec![],
executable_import_ready: false,
notes: vec![
"decoded from grounded real 0x4e9a row framing".to_string(),
"scalar locomotive availability rows still need catalog context".to_string(),
"decoded from lower-tail real 0x4e9a row framing".to_string(),
"scalar lower-tail locomotive availability row still needs catalog context"
.to_string(),
],
}],
}),
@ -1609,7 +1614,7 @@ fn blocks_scalar_locomotive_availability_rows_without_catalog_context() {
let input = build_runtime_state_input_from_save_slice(
&save_slice,
"packed-events-recovered-locomotive-availability-frontier",
"packed-events-recovered-locomotive-availability-lower-tail-frontier",
None,
)
.expect("save slice should project");
@ -1737,7 +1742,7 @@ fn imports_scalar_locomotive_availability_rows_with_save_derived_catalog_context
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: Some(save_named_locomotive_table(61)),
named_locomotive_availability_table: Some(save_named_locomotive_table(62)),
locomotive_catalog: None,
cargo_catalog: None,
world_issue_37_state: None,
@ -1799,7 +1804,7 @@ fn imports_scalar_locomotive_availability_rows_with_save_derived_catalog_context
grouped_effect_row_counts: vec![2, 0, 0, 0],
grouped_effect_rows: vec![
real_locomotive_availability_row(250, 42),
real_locomotive_availability_row(301, 7),
real_locomotive_availability_row(302, 7),
],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
@ -1820,7 +1825,7 @@ fn imports_scalar_locomotive_availability_rows_with_save_derived_catalog_context
)
.expect("save slice should project");
assert_eq!(input.state.locomotive_catalog.len(), 61);
assert_eq!(input.state.locomotive_catalog.len(), 62);
assert_eq!(input.state.event_runtime_records.len(), 1);
assert_eq!(
input
@ -1845,11 +1850,228 @@ fn imports_scalar_locomotive_availability_rows_with_save_derived_catalog_context
Some(&42)
);
assert_eq!(
input.state.named_locomotive_availability.get("Zephyr"),
input
.state
.named_locomotive_availability
.get("Locomotive 62"),
Some(&7)
);
}
#[test]
fn imports_scalar_locomotive_availability_rows_with_dynamic_tail_catalog_context() {
let mut names = (0..58)
.map(default_save_named_locomotive_name)
.collect::<Vec<_>>();
names.extend([
"242 A1".to_string(),
"Class 460".to_string(),
"Class A1".to_string(),
"Class P8".to_string(),
"U1".to_string(),
]);
let save_slice = SmpLoadedSaveSlice {
file_extension_hint: Some("gms".to_string()),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
mechanism_family: "classic-save-rehydrate-v1".to_string(),
mechanism_confidence: "grounded".to_string(),
trailer_family: None,
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: Some(save_named_locomotive_table_with_names(&names)),
locomotive_catalog: None,
cargo_catalog: None,
world_issue_37_state: None,
world_economic_tuning_state: None,
world_finance_neighborhood_state: None,
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
region_collection: None,
region_fixed_row_run_summary: None,
placed_structure_collection: None,
placed_structure_dynamic_side_buffer_summary: None,
special_conditions_table: None,
event_runtime_collection: Some(SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
mechanism_family: "classic-save-rehydrate-v1".to_string(),
mechanism_confidence: "grounded".to_string(),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
metadata_tag_offset: 0x7100,
records_tag_offset: 0x7200,
close_tag_offset: 0x7600,
packed_state_version: 0x3e9,
packed_state_version_hex: "0x000003e9".to_string(),
live_id_bound: 34,
live_record_count: 1,
live_entry_ids: vec![34],
decoded_record_count: 1,
imported_runtime_record_count: 0,
records_with_trigger_kind: 0,
records_missing_trigger_kind: 0,
nondirect_compact_record_count: 0,
nondirect_compact_records_missing_trigger_kind: 0,
trigger_kinds_present: vec![],
control_lane_notes: vec![],
add_building_dispatch_strip_record_indexes: vec![],
add_building_dispatch_strip_descriptor_labels: vec![],
add_building_dispatch_strip_records_with_trigger_kind: 0,
add_building_dispatch_strip_records_missing_trigger_kind: 0,
add_building_dispatch_strip_row_shape_families: vec![],
add_building_dispatch_strip_signature_families: vec![],
add_building_dispatch_strip_condition_tuple_families: vec![],
add_building_dispatch_strip_signature_condition_clusters: vec![],
records: vec![SmpLoadedPackedEventRecordSummary {
record_index: 0,
live_entry_id: 34,
payload_offset: Some(0x7202),
payload_len: Some(96),
decode_status: "parity_only".to_string(),
payload_family: "real_packed_v1".to_string(),
trigger_kind: Some(7),
active: None,
marks_collection_dirty: None,
one_shot: Some(false),
compact_control: Some(real_compact_control()),
text_bands: vec![],
standalone_condition_row_count: 0,
standalone_condition_rows: vec![],
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![1, 0, 0, 0],
grouped_effect_rows: vec![real_locomotive_availability_row(299, 7)],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
executable_import_ready: false,
notes: vec![
"save-derived locomotive availability row uses scenario-dependent tail names"
.to_string(),
],
}],
}),
notes: vec![],
};
let mut input = build_runtime_state_input_from_save_slice(
&save_slice,
"save-derived-dynamic-tail-locomotive-availability",
None,
)
.expect("save slice should project");
execute_step_command(
&mut input.state,
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
)
.expect("save-derived dynamic-tail locomotive availability record should run");
assert_eq!(
input.state.named_locomotive_availability.get("242 A1"),
Some(&7)
);
}
#[test]
fn keeps_upper_band_locomotive_availability_rows_on_descriptor_parity() {
let save_slice = SmpLoadedSaveSlice {
file_extension_hint: Some("gms".to_string()),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
mechanism_family: "classic-save-rehydrate-v1".to_string(),
mechanism_confidence: "grounded".to_string(),
trailer_family: None,
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: Some(save_named_locomotive_table(140)),
locomotive_catalog: None,
cargo_catalog: None,
world_issue_37_state: None,
world_economic_tuning_state: None,
world_finance_neighborhood_state: None,
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
region_collection: None,
region_fixed_row_run_summary: None,
placed_structure_collection: None,
placed_structure_dynamic_side_buffer_summary: None,
special_conditions_table: None,
event_runtime_collection: Some(SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
mechanism_family: "classic-save-rehydrate-v1".to_string(),
mechanism_confidence: "grounded".to_string(),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
metadata_tag_offset: 0x7100,
records_tag_offset: 0x7200,
close_tag_offset: 0x7600,
packed_state_version: 0x3e9,
packed_state_version_hex: "0x000003e9".to_string(),
live_id_bound: 34,
live_record_count: 1,
live_entry_ids: vec![34],
decoded_record_count: 1,
imported_runtime_record_count: 0,
records_with_trigger_kind: 0,
records_missing_trigger_kind: 0,
nondirect_compact_record_count: 0,
nondirect_compact_records_missing_trigger_kind: 0,
trigger_kinds_present: vec![],
control_lane_notes: vec![],
add_building_dispatch_strip_record_indexes: vec![],
add_building_dispatch_strip_descriptor_labels: vec![],
add_building_dispatch_strip_records_with_trigger_kind: 0,
add_building_dispatch_strip_records_missing_trigger_kind: 0,
add_building_dispatch_strip_row_shape_families: vec![],
add_building_dispatch_strip_signature_families: vec![],
add_building_dispatch_strip_condition_tuple_families: vec![],
add_building_dispatch_strip_signature_condition_clusters: vec![],
records: vec![SmpLoadedPackedEventRecordSummary {
record_index: 0,
live_entry_id: 34,
payload_offset: Some(0x7202),
payload_len: Some(96),
decode_status: "parity_only".to_string(),
payload_family: "real_packed_v1".to_string(),
trigger_kind: Some(7),
active: None,
marks_collection_dirty: None,
one_shot: Some(false),
compact_control: Some(real_compact_control()),
text_bands: vec![],
standalone_condition_row_count: 0,
standalone_condition_rows: vec![],
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![1, 0, 0, 0],
grouped_effect_rows: vec![real_locomotive_availability_row(457, 1)],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
executable_import_ready: false,
notes: vec![
"upper-band locomotive availability row remains descriptor parity".to_string(),
],
}],
}),
notes: vec![],
};
let input = build_runtime_state_input_from_save_slice(
&save_slice,
"packed-events-upper-band-locomotive-availability",
None,
)
.expect("save slice should project");
assert!(input.state.event_runtime_records.is_empty());
assert_eq!(
input
.state
.packed_event_collection
.as_ref()
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
Some("blocked_evidence_blocked_descriptor")
);
}
#[test]
fn overlays_scalar_locomotive_availability_rows_into_named_availability_effects() {
let base_state = RuntimeState {
@ -2099,11 +2321,13 @@ fn blocks_recovered_locomotive_cost_rows_without_catalog_context_lower_band() {
standalone_condition_rows: vec![],
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![1, 0, 0, 0],
grouped_effect_rows: vec![real_locomotive_cost_row(352, 250000)],
grouped_effect_rows: vec![real_locomotive_cost_row(413, 250000)],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
executable_import_ready: false,
notes: vec!["scalar locomotive cost row still needs catalog context".to_string()],
notes: vec![
"scalar lower-tail locomotive cost row still needs catalog context".to_string(),
],
}],
}),
notes: vec![],
@ -2237,7 +2461,7 @@ fn imports_scalar_locomotive_cost_rows_with_save_derived_catalog_context() {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: Some(save_named_locomotive_table(61)),
named_locomotive_availability_table: Some(save_named_locomotive_table(62)),
locomotive_catalog: None,
cargo_catalog: None,
world_issue_37_state: None,
@ -2299,7 +2523,7 @@ fn imports_scalar_locomotive_cost_rows_with_save_derived_catalog_context() {
grouped_effect_row_counts: vec![2, 0, 0, 0],
grouped_effect_rows: vec![
real_locomotive_cost_row(352, 250000),
real_locomotive_cost_row(412, 325000),
real_locomotive_cost_row(413, 325000),
],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
@ -2319,7 +2543,7 @@ fn imports_scalar_locomotive_cost_rows_with_save_derived_catalog_context() {
)
.expect("save slice should project");
assert_eq!(input.state.locomotive_catalog.len(), 61);
assert_eq!(input.state.locomotive_catalog.len(), 62);
assert_eq!(input.state.event_runtime_records.len(), 1);
assert_eq!(
input
@ -2341,11 +2565,110 @@ fn imports_scalar_locomotive_cost_rows_with_save_derived_catalog_context() {
Some(&250000)
);
assert_eq!(
input.state.named_locomotive_cost.get("Zephyr"),
input.state.named_locomotive_cost.get("Locomotive 62"),
Some(&325000)
);
}
#[test]
fn keeps_upper_band_locomotive_cost_rows_on_descriptor_parity() {
let save_slice = SmpLoadedSaveSlice {
file_extension_hint: Some("gms".to_string()),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
mechanism_family: "classic-save-rehydrate-v1".to_string(),
mechanism_confidence: "grounded".to_string(),
trailer_family: None,
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: Some(save_named_locomotive_table(140)),
locomotive_catalog: None,
cargo_catalog: None,
world_issue_37_state: None,
world_economic_tuning_state: None,
world_finance_neighborhood_state: None,
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
region_collection: None,
region_fixed_row_run_summary: None,
placed_structure_collection: None,
placed_structure_dynamic_side_buffer_summary: None,
special_conditions_table: None,
event_runtime_collection: Some(SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
mechanism_family: "classic-save-rehydrate-v1".to_string(),
mechanism_confidence: "grounded".to_string(),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
metadata_tag_offset: 0x7100,
records_tag_offset: 0x7200,
close_tag_offset: 0x7600,
packed_state_version: 0x3e9,
packed_state_version_hex: "0x000003e9".to_string(),
live_id_bound: 42,
live_record_count: 1,
live_entry_ids: vec![42],
decoded_record_count: 1,
imported_runtime_record_count: 0,
records_with_trigger_kind: 0,
records_missing_trigger_kind: 0,
nondirect_compact_record_count: 0,
nondirect_compact_records_missing_trigger_kind: 0,
trigger_kinds_present: vec![],
control_lane_notes: vec![],
add_building_dispatch_strip_record_indexes: vec![],
add_building_dispatch_strip_descriptor_labels: vec![],
add_building_dispatch_strip_records_with_trigger_kind: 0,
add_building_dispatch_strip_records_missing_trigger_kind: 0,
add_building_dispatch_strip_row_shape_families: vec![],
add_building_dispatch_strip_signature_families: vec![],
add_building_dispatch_strip_condition_tuple_families: vec![],
add_building_dispatch_strip_signature_condition_clusters: vec![],
records: vec![SmpLoadedPackedEventRecordSummary {
record_index: 0,
live_entry_id: 42,
payload_offset: Some(0x7202),
payload_len: Some(96),
decode_status: "parity_only".to_string(),
payload_family: "real_packed_v1".to_string(),
trigger_kind: Some(7),
active: None,
marks_collection_dirty: None,
one_shot: Some(false),
compact_control: Some(real_compact_control()),
text_bands: vec![],
standalone_condition_row_count: 0,
standalone_condition_rows: vec![],
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![1, 0, 0, 0],
grouped_effect_rows: vec![real_locomotive_cost_row(475, 250000)],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
executable_import_ready: false,
notes: vec!["upper-band locomotive cost row remains descriptor parity".to_string()],
}],
}),
notes: vec![],
};
let input = build_runtime_state_input_from_save_slice(
&save_slice,
"packed-events-upper-band-locomotive-cost",
None,
)
.expect("save slice should project");
assert!(input.state.event_runtime_records.is_empty());
assert_eq!(
input
.state
.packed_event_collection
.as_ref()
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
Some("blocked_evidence_blocked_descriptor")
);
}
#[test]
fn overlays_scalar_locomotive_cost_rows_into_named_cost_effects() {
let base_state = RuntimeState {
@ -2519,6 +2842,119 @@ fn overlays_scalar_locomotive_cost_rows_into_named_cost_effects() {
);
}
#[test]
fn imports_scalar_locomotive_cost_rows_with_dynamic_tail_catalog_context() {
let mut names = (0..58)
.map(default_save_named_locomotive_name)
.collect::<Vec<_>>();
names.extend([
"242 A1".to_string(),
"Class 460".to_string(),
"Class A1".to_string(),
"Class P8".to_string(),
"U1".to_string(),
]);
let save_slice = SmpLoadedSaveSlice {
file_extension_hint: Some("gms".to_string()),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
mechanism_family: "classic-save-rehydrate-v1".to_string(),
mechanism_confidence: "grounded".to_string(),
trailer_family: None,
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: Some(save_named_locomotive_table_with_names(&names)),
locomotive_catalog: None,
cargo_catalog: None,
world_issue_37_state: None,
world_economic_tuning_state: None,
world_finance_neighborhood_state: None,
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
region_collection: None,
region_fixed_row_run_summary: None,
placed_structure_collection: None,
placed_structure_dynamic_side_buffer_summary: None,
special_conditions_table: None,
event_runtime_collection: Some(SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
mechanism_family: "classic-save-rehydrate-v1".to_string(),
mechanism_confidence: "grounded".to_string(),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
metadata_tag_offset: 0x7100,
records_tag_offset: 0x7200,
close_tag_offset: 0x7600,
packed_state_version: 0x3e9,
packed_state_version_hex: "0x000003e9".to_string(),
live_id_bound: 37,
live_record_count: 1,
live_entry_ids: vec![37],
decoded_record_count: 1,
imported_runtime_record_count: 0,
records_with_trigger_kind: 0,
records_missing_trigger_kind: 0,
nondirect_compact_record_count: 0,
nondirect_compact_records_missing_trigger_kind: 0,
trigger_kinds_present: vec![],
control_lane_notes: vec![],
add_building_dispatch_strip_record_indexes: vec![],
add_building_dispatch_strip_descriptor_labels: vec![],
add_building_dispatch_strip_records_with_trigger_kind: 0,
add_building_dispatch_strip_records_missing_trigger_kind: 0,
add_building_dispatch_strip_row_shape_families: vec![],
add_building_dispatch_strip_signature_families: vec![],
add_building_dispatch_strip_condition_tuple_families: vec![],
add_building_dispatch_strip_signature_condition_clusters: vec![],
records: vec![SmpLoadedPackedEventRecordSummary {
record_index: 0,
live_entry_id: 37,
payload_offset: Some(0x7202),
payload_len: Some(96),
decode_status: "parity_only".to_string(),
payload_family: "real_packed_v1".to_string(),
trigger_kind: Some(7),
active: None,
marks_collection_dirty: None,
one_shot: Some(false),
compact_control: Some(real_compact_control()),
text_bands: vec![],
standalone_condition_row_count: 0,
standalone_condition_rows: vec![],
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![1, 0, 0, 0],
grouped_effect_rows: vec![real_locomotive_cost_row(410, 325000)],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
executable_import_ready: false,
notes: vec![
"save-derived locomotive cost row uses scenario-dependent tail names"
.to_string(),
],
}],
}),
notes: vec![],
};
let mut input = build_runtime_state_input_from_save_slice(
&save_slice,
"save-derived-dynamic-tail-locomotive-cost",
None,
)
.expect("save slice should project");
execute_step_command(
&mut input.state,
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
)
.expect("save-derived dynamic-tail locomotive cost record should run");
assert_eq!(
input.state.named_locomotive_cost.get("242 A1"),
Some(&325000)
);
}
#[test]
fn keeps_negative_locomotive_cost_rows_parity_only() {
let save_slice = SmpLoadedSaveSlice {

View file

@ -590,9 +590,6 @@ pub(super) fn real_locomotive_availability_row(
56 => Some("Trans-Euro"),
57 => Some("V200"),
58 => Some("VL80T"),
59 => Some("GP 35"),
60 => Some("U1"),
61 => Some("Zephyr"),
_ => None,
}
}
@ -709,9 +706,6 @@ pub(super) fn real_locomotive_cost_row(
56 => Some("Trans-Euro"),
57 => Some("V200"),
58 => Some("VL80T"),
59 => Some("GP 35"),
60 => Some("U1"),
61 => Some("Zephyr"),
_ => None,
}
}
@ -759,77 +753,78 @@ pub(super) fn real_locomotive_cost_row(
}
}
pub(super) fn save_named_locomotive_table(
count: usize,
) -> SmpLoadedNamedLocomotiveAvailabilityTable {
fn grounded_locomotive_name(index: usize) -> String {
match index {
0 => "2-D-2",
1 => "E-88",
2 => "Adler 2-2-2",
3 => "USA 103",
4 => "American 4-4-0",
5 => "Atlantic 4-4-2",
6 => "Baldwin 0-6-0",
7 => "Be 5/7",
8 => "Beuth 2-2-2",
9 => "Big Boy 4-8-8-4",
10 => "C55 Deltic",
11 => "Camelback 0-6-0",
12 => "Challenger 4-6-6-4",
13 => "Class 01 4-6-2",
14 => "Class 103",
15 => "Class 132",
16 => "Class 500 4-6-0",
17 => "Class 9100",
18 => "Class EF 66",
19 => "Class 6E",
20 => "Consolidation 2-8-0",
21 => "Crampton 4-2-0",
22 => "DD 080-X",
23 => "DD40AX",
24 => "Duke Class 4-4-0",
25 => "E18",
26 => "E428",
27 => "Brenner E412",
28 => "E60CP",
29 => "Eight Wheeler 4-4-0",
30 => "EP-2 Bipolar",
31 => "ET22",
32 => "F3",
33 => "Fairlie 0-6-6-0",
34 => "Firefly 2-2-2",
35 => "FP45",
36 => "Ge 6/6 Crocodile",
37 => "GG1",
38 => "GP7",
39 => "H10 2-8-2",
40 => "HST 125",
41 => "Kriegslok 2-10-0",
42 => "Mallard 4-6-2",
43 => "Norris 4-2-0",
44 => "Northern 4-8-4",
45 => "Orca NX462",
46 => "Pacific 4-6-2",
47 => "Planet 2-2-0",
48 => "Re 6/6",
49 => "Red Devil 4-8-4",
50 => "S3 4-4-0",
51 => "NA-90D",
52 => "Shay (2-Truck)",
53 => "Shinkansen Series 0",
54 => "Stirling 4-2-2",
55 => "Trans-Euro",
56 => "V200",
57 => "VL80T",
58 => "GP 35",
59 => "U1",
60 => "Zephyr",
_ => return format!("Locomotive {}", index + 1),
}
.to_string()
pub(super) fn default_save_named_locomotive_name(index: usize) -> String {
match index {
0 => "2-D-2",
1 => "E-88",
2 => "Adler 2-2-2",
3 => "USA 103",
4 => "American 4-4-0",
5 => "Atlantic 4-4-2",
6 => "Baldwin 0-6-0",
7 => "Be 5/7",
8 => "Beuth 2-2-2",
9 => "Big Boy 4-8-8-4",
10 => "C55 Deltic",
11 => "Camelback 0-6-0",
12 => "Challenger 4-6-6-4",
13 => "Class 01 4-6-2",
14 => "Class 103",
15 => "Class 132",
16 => "Class 500 4-6-0",
17 => "Class 9100",
18 => "Class EF 66",
19 => "Class 6E",
20 => "Consolidation 2-8-0",
21 => "Crampton 4-2-0",
22 => "DD 080-X",
23 => "DD40AX",
24 => "Duke Class 4-4-0",
25 => "E18",
26 => "E428",
27 => "Brenner E412",
28 => "E60CP",
29 => "Eight Wheeler 4-4-0",
30 => "EP-2 Bipolar",
31 => "ET22",
32 => "F3",
33 => "Fairlie 0-6-6-0",
34 => "Firefly 2-2-2",
35 => "FP45",
36 => "Ge 6/6 Crocodile",
37 => "GG1",
38 => "GP7",
39 => "H10 2-8-2",
40 => "HST 125",
41 => "Kriegslok 2-10-0",
42 => "Mallard 4-6-2",
43 => "Norris 4-2-0",
44 => "Northern 4-8-4",
45 => "Orca NX462",
46 => "Pacific 4-6-2",
47 => "Planet 2-2-0",
48 => "Re 6/6",
49 => "Red Devil 4-8-4",
50 => "S3 4-4-0",
51 => "NA-90D",
52 => "Shay (2-Truck)",
53 => "Shinkansen Series 0",
54 => "Stirling 4-2-2",
55 => "Trans-Euro",
56 => "V200",
57 => "VL80T",
58 => "GP 35",
59 => "U1",
60 => "Zephyr",
_ => return format!("Locomotive {}", index + 1),
}
.to_string()
}
pub(super) fn save_named_locomotive_table_with_names(
names: &[String],
) -> SmpLoadedNamedLocomotiveAvailabilityTable {
let count = names.len();
SmpLoadedNamedLocomotiveAvailabilityTable {
source_kind: "runtime-save-direct-serializer".to_string(),
semantic_family: "scenario-named-locomotive-availability-table".to_string(),
@ -839,11 +834,13 @@ pub(super) fn save_named_locomotive_table(
observed_entry_count: count,
zero_availability_count: 0,
zero_availability_names: vec![],
entries: (0..count)
.map(|index| SmpRt3105SaveNameTableEntry {
entries: names
.iter()
.enumerate()
.map(|(index, name)| SmpRt3105SaveNameTableEntry {
index,
offset: 0x7c78 + index * 0x41,
text: grounded_locomotive_name(index),
text: name.clone(),
availability_dword: 1,
availability_dword_hex: "0x00000001".to_string(),
trailer_word: 1,
@ -853,6 +850,16 @@ pub(super) fn save_named_locomotive_table(
}
}
pub(super) fn save_named_locomotive_table(
count: usize,
) -> SmpLoadedNamedLocomotiveAvailabilityTable {
let names = (0..count)
.map(default_save_named_locomotive_name)
.collect::<Vec<_>>();
save_named_locomotive_table_with_names(&names)
}
pub(super) fn save_cargo_catalog(
entries: &[(u32, crate::event::targets::RuntimeCargoClass)],
) -> SmpLoadedCargoCatalog {

View file

@ -2,24 +2,21 @@ use super::super::super::*;
use std::collections::BTreeMap;
use std::sync::OnceLock;
const STATIC_GROUNDED_LOCOMOTIVE_NAME_MAX_ID: u32 = 58;
pub(in crate::inspect::smp) fn recovered_locomotive_availability_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
if let Some(loco_id) = recovered_locomotive_availability_loco_id(descriptor_id) {
let label = recovered_locomotive_availability_label(loco_id);
let executable_in_runtime = (loco_id as usize) <= GROUNDED_LOCOMOTIVE_PREFIX.len();
return Some(RealGroupedEffectDescriptorMetadata {
descriptor_id,
label,
target_mask_bits: 0x08,
parameter_family: "locomotive_availability_scalar",
runtime_key: None,
runtime_status: if executable_in_runtime {
RealGroupedEffectRuntimeStatus::Executable
} else {
RealGroupedEffectRuntimeStatus::EvidenceBlocked
},
executable_in_runtime,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
});
}
(457..=474)
@ -45,6 +42,9 @@ pub(in crate::inspect::smp) fn recovered_locomotive_availability_loco_id(
}
pub(in crate::inspect::smp) fn grounded_locomotive_name(loco_id: u32) -> Option<&'static str> {
if loco_id > STATIC_GROUNDED_LOCOMOTIVE_NAME_MAX_ID {
return None;
}
let index = loco_id.checked_sub(1)? as usize;
GROUNDED_LOCOMOTIVE_PREFIX.get(index).copied()
}
@ -153,8 +153,7 @@ pub(in crate::inspect::smp) fn recovered_locomotive_cost_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
recovered_locomotive_cost_label(descriptor_id).map(|label| {
let executable_in_runtime = recovered_locomotive_cost_loco_id(descriptor_id)
.is_some_and(|loco_id| (loco_id as usize) <= GROUNDED_LOCOMOTIVE_PREFIX.len());
let executable_in_runtime = recovered_locomotive_cost_loco_id(descriptor_id).is_some();
RealGroupedEffectDescriptorMetadata {
descriptor_id,
label,

View file

@ -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);
}

View file

@ -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

View file

@ -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)

View file

@ -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.

View file

@ -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

View file

@ -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`