rrt/docs/control-loop-atlas/map-and-scenario-content-load.md

52 KiB

Map and Scenario Content Load

  • Roots: shell_map_file_entry_coordinator at 0x00445ac0, the larger active-mode profile owner shell_active_mode_run_profile_startup_and_load_dispatch at 0x00438890, the shell-mode switcher shell_transition_mode at 0x00482ec0, the first grounded world-entry branch world_entry_transition_and_runtime_bringup at 0x00443a50, shell_map_file_world_bundle_coordinator at 0x00445de0, early reference-package save via map_bundle_open_reference_package_and_serialize_early_world_datasets at 0x00444dd0, and narrower tagged collection owners such as geographic_label_database_refresh_records_from_tagged_bundle and city_database_entry_collection_refresh_records_from_tagged_bundle.
  • Trigger/Cadence: shell tutorial launch, editor or detail-panel file actions through fileopt.win, map-scenario open paths, and scenario-text export batch commands.
  • Key Dispatchers: shell_map_file_entry_coordinator, shell_active_mode_run_profile_startup_and_load_dispatch, shell_transition_mode, world_entry_transition_and_runtime_bringup, world_runtime_release_global_services, shell_map_file_world_bundle_coordinator, map_bundle_open_reference_package_and_serialize_early_world_datasets, geographic_label_database_refresh_records_from_tagged_bundle, city_database_entry_collection_refresh_records_from_tagged_bundle, scenario_text_export_build_language_file, scenario_text_export_report_language_file, scenario_text_export_batch_process_maps.
  • State Anchors: shell-side file staging buffers at 0x0062bee0 and 0x0062bec4, shell and mode globals at 0x006cec74 and 0x006cec78, world object root 0x0062c120, map bundle state allocated through 0x00530c80, and geography tables rooted at 0x0062b2fc and 0x0062b268.
  • Subsystem Handoffs: the shared fileopt.win dialog rooted at 0x004dc670 now looks like the shell-side selector above the two broad file coordinators. Its message handler sets 0x006d07f8 for the load or restore side or 0x006d07ec for the save or package side before the detail-panel transition manager routes into shell_map_file_entry_coordinator or shell_map_file_world_bundle_coordinator. The former unresolved third flag at 0x006d07f0 is now accounted for too: it escapes into the standalone SettingsWindow.win path through shell_open_settings_window rather than another map or save verb. The broad coordinators now hand their interactive work through the shared filerqst.win helper at 0x004dd010, and that helper gives the extension split a firmer shape. The paired editor-map path is now grounded as .gmp through load mode 4 and save mode 3. The remaining non-editor families are no longer anonymous either: .gmc is the campaign-scenario branch, backed both by the campaign-screen resignation prompt on 0x006cec7c+0xc5 and by the numbered %s%02d.gmc save helper at 0x00517c70; .gmx is the sandbox branch, backed by the shell-side The briefing is not available in sandbox games. restriction on 0x006cec7c+0x82; and the default .gms branch is therefore the standalone scenario family. When a live runtime world is already active the same helper bypasses those non-runtime extensions and forces the .smp runtime-state branch instead. The auxiliary save-side mode 11 is tighter now too: it still maps to .gmt, but instead of looking like another gameplay save family it conditionally diverts into the same .gmt preview-surface pipeline owned by the Multiplayer preview dataset object at 0x006cd8d8, and only falls back to the normal reference-bundle path when that dataset object is absent. That fallback owner is tighter now too: 0x00444dd0 is not a generic database opener, but the early package-save prelude that seeds the shared stage/progress globals, opens one 0x30d40 bundle through 0x00530c80, handles the localized failure modal 0x0fda, and then serializes the first direct package band in a fixed order: chunks 0x32c8/0x32c9, direct saves of [world+0x66be], [world+0x66b2], and [world+0x66b6], chunk 0x32dc, the staged 0x108 profile block under 0x3714/0x3715, then world_serialize_runtime_grid_and_secondary_raster_tables_into_bundle 0x00449520 for the early world-grid and sidecar-table band, then aux_candidate_collection_serialize_records_into_bundle_payload 0x00416a70 for the direct 0x0062b2fc source-or-auxiliary record family, and only after that the neighboring reference and manager families before shell_map_file_world_bundle_coordinator continues with the later tagged collections. That 0x0062b2fc seam is tighter now too: 0x00416a70 first builds one temporary scored queue from each record's linked chain at [entry+0x04], writes one leading 0xbabe header, and then serializes the fixed fields, counted dword runs, optional byte payload, paired one-byte bands, and trailing dword for each queued record; the load-side companion is aux_candidate_collection_construct_stream_load_records_and_refresh_runtime_followons 0x004196c0, which now clearly tails into aux_candidate_collection_rebank_or_clone_records_by_availability_pass_and_refresh_owner_links 0x00419230 before the later world-load side continues, with destructor aux_candidate_collection_release_templates_queues_and_indexed_storage 0x00419680. The fixed stock source-family layer above that clone pass is bounded now too: the same 0x004196c0 import owner walks the %1*.bty file set, routes each match through parser 0x00414490, calls the stock load callback 0x00416ce0, and only then falls into the same 0x00419230 rebank-or-clone sweep. The recovered source-name table is StationSml, StationMed, StationLrg, ServiceTower, Maintenance, ClpBrd, Kyoto, Persian, SoWest, Tudor, and Victorian, so the remaining Tier-2 load-side question is the style-family row-selection path into that later bank pass rather than another hidden naming stage. The shipped asset directory makes the bare-name remap boundary explicit too: Data/BuildingTypes contains bare Port.bty/.bca and Warehouse.bty/.bca, but no stock Port00 or Warehouse00 assets on disk, so the loader-side port -> Port00 and warehouse -> Warehouse00 rewrite is the real stock bridge. The stock asset corpus agrees with that table directly too: Data/BuildingTypes contains VictorianStationSml/Med/Lrg.bty, TudorStationSml/Med/Lrg.bty, SoWestStationSml/Med/Lrg.bty, PersianStationSml/Med/Lrg.bty, KyotoStationSml/Med/Lrg.bty, ClpBrdStationSml/Med/Lrg.bty, plus standalone Maintenance.bty and ServiceTower.bty, alongside the style-specific house families. So the recovered 0x005f3c6c/0x005f3c80 naming strip is now backed by the shipped on-disk BuildingTypes filename families, not only by the loader-side disassembly. The new .bty header probes now tighten that stock source split too: Port.bty and Warehouse.bty both probe as ordinary type_id = 0x000003ec rows with direct bare-name headers and shared dword_0xbb = 0x000001f4, while the style-station families such as VictorianStationSml/Med/Lrg.bty stay on the same 0x000003ec family but keep name_0x7c = VictorianStations and zero dword_0xbb. Maintenance.bty and ServiceTower.bty likewise stay in the same stock family, but expose display names Maintenance Facility and Service Tower with zero dword_0xbb. So the later numbered Port%02d / Warehouse%02d clone seam is now bounded above the bare Port / Warehouse family itself rather than under a hidden station-style alias family in BuildingTypes. The wider nonzero stock family is explicit now too: the recovered report shows only one nonzero .bty header lane, dword_0xbb = 0x000001f4, and it spans exactly 22 files including Port.bty, Warehouse.bty, and a smaller industrial/commercial subset such as Brewery, ConcretePlant, ConstructionFirm, Hospital, Museum, PaperMill, and Steel Mill. So the later numbered clone seam is now bounded above that narrower 0x000001f4 stock family rather than above the full style/source strip. The checked-in header-name summaries sharpen that source split again: inside the nonzero family, name_0x40 / name_0x7c mostly stay on direct display/file roots, but name_0x5e clusters the shared alias roots (TextileMill x10, LumberMill x4, MeatPackingPlant x4, Distillery x2, Toolndie x2). So the next load-side source-selection pass should bias toward that 0x5e alias-root lane when testing why the later chooser seeds only part of the stock family into the numbered Tier-2 bank. The same name_0x5e dword-family summary now also says the current residue is still stock-side: MunitionsFactory belongs to a zero-valued WeaponsFactory alias cluster with Electric Plant, Fertilizer Factory, Nuclear Power Plant, Oil Well, and Weapons Factory. So the next load-side source-selection pass should treat the open question as one of cluster choice inside the wider stock corpus, not as a jump from stock rows to some unrelated non-stock family. The checked-in cluster-to-selector join sharpens that again: every grounded name_0x5e alias cluster is zero-selector by default, including both the nonzero 0x000001f4 industrial subset and the zero-family WeaponsFactory subset. The only surfaced nonzero joined outlier is MachineShop inside the TextileMill cluster (byte_0xba = 0x3f, byte_0xbb = 0x00). So the next load-side source-selection pass should focus on that row-level outlier and any matching replay/seed logic, not on a whole-cluster nonzero bank hypothesis. The direct-name plus alias plus selector join sharpens that one more step: inside the same nonzero 0x000001f4 family, the direct Warehouse/TextileMill/Warehouse shape splits into six all-zero selector peers (ConcretePlant, ConstructionFirm, ElectronicsPlant, Hospital, PharmaceuticalPlant, and Warehouse) plus one unique selector outlier, MachineShop = 0x00/0x80/0x3f/0x00. The sibling bare Port/TextileMill/Port row stays all-zero. So the remaining load-side question is no longer whether the bare Port / Warehouse row carries the stock exception; it is why one warehouse-shaped industrial peer in that same alias family carries the lone selector while the bare rows do not. The exact stock resolver-family strip is boxed in now too. The checked-in recovered_source_family_summaries report shows every 0x00419590 source-family row (VictorianStation*, TudorStation*, SoWestStation*, PersianStation*, KyotoStation*, ClpBrdStation*, Maintenance, and ServiceTower) staying on type_id = 0x000003ec with dword_0xbb = 0; almost all of them have no .bca pair at all, and the only paired standalone row ServiceTower still has byte_0xba = 0x00, byte_0xbb = 0x00. So the remaining load-side question is no longer whether the exact 0x00419590 strip itself carries the seeded nonzero selector; current evidence says it does not. The global stock selector report tightens that further: the full MachineShop.bca signature (0x00/0x80/0x3f/0x00 across 0xb8..0xbb) is unique across the checked-in stock .bca corpus. So the remaining load-side Tier-2 frontier is one surfaced stock-file outlier plus the later clone/replay logic that amplifies it, not a hidden wider stock-family selector strip. The fixed tail is explicit now too: 0x00444dd0 writes one direct dword from [world+0x19], one zeroed 0x1f4-byte slab under 0x32cf, closes the package, derives the preview path through 0x00442740, and then conditionally emits the companion-image and companion-payload sidecars through 0x00441f70 and 0x00442900 when [world+0x66c8] and [world+0x66c9] are set. The shell-side mode owner above those file coordinators is clearer now too. shell_transition_mode no longer reads like a generic mode switch: its ABI is now grounded as a thiscall with two stack arguments because the body reads the requested mode from [esp+0x0c] and returns with ret 8. The grounded world-entry load-screen call shape is (4, 0), not a one-arg mode switch. The second stack argument is now tighter too: current local evidence reads it as an old-active-mode teardown flag, because the 0x482fc6..0x482fff branch only runs when it is nonzero and then releases the prior active-mode world through 0x434300, falls through the neighboring no-op placeholder 0x433730, and then reaches the common free path before clearing 0x006cec78. The teardown owner itself is tighter now too: 0x434300 first destroys the two indexed world collections at [world+0x66b2] and [world+0x66b6], then releases the typed global roots 0x0062b244, 0x006cfcbc, 0x0062be10, 0x006ceb9c, 0x0062c120, 0x0062ba8c, 0x006ada84, 0x0062ba88, 0x0062b2fc, 0x0062b268, 0x006cea4c, and 0x006acd34 in that order before it drains the shell-helper handle band [world+0x46a80..+0x46aa0], the variable owner band [world+0x46aa4], and the linked chains at [world+0x66a6] and [world+0x66aa]. The corresponding world-load allocation branch is tighter now too: inside 0x00438c70, 0x0062b2fc is allocated as a 0xb8-byte object and then constructed through aux_candidate_collection_construct_seed_globals_and_helper_bands_then_import_records 0x0041aa50, which seeds the collection base, resets the neighboring constructor globals, builds the helper bands at [this+0x88/+0x8c/+0x90] through 0x0041a990, and only then tails into the tagged import owner 0x004196c0. The next two roots in the same load strip are now explicit too: 0x0062ba8c is allocated and constructed through structure_candidate_collection_construct_and_stream_load_records_then_refresh_counts 0x0041f4e0, which enters the tagged import owner 0x0041ede0 and then refreshes the aggregate filter/year-visible counts through 0x0041e970; and 0x006ada84 is allocated and constructed through locomotive_collection_construct_and_stream_load_records 0x00462520, which enters the linked-era locomotive import owner 0x00461f10 and then refreshes the live availability override band through 0x00461e00. The same fan-out now also has explicit constructor ownership for the adjacent typed roots: the live company collection 0x0062be10 is constructed through company_collection_construct 0x00429950, the live profile/chairman collection 0x006ceb9c is constructed through profile_collection_construct 0x00477740, the live train collection 0x006cfcbc is constructed through train_collection_construct 0x004b2340, the sibling indexed collection 0x006acd34 is constructed through runtime_object_collection_construct_vtable_5cae10 0x00455320, the support family 0x0062b244 is constructed through support_collection_construct_seed_counters_and_clear_large_sideband 0x0040aeb0, and the live world root 0x0062c120 is published through world_runtime_construct_root_and_seed_global_0x62c120 0x0044cf70 before the heavier bundle load body continues through 0x00449200 and 0x0044cfb0. The profile-side tagged header siblings are explicit too: runtime load re-enters profile_collection_refresh_tagged_header_counts_from_bundle 0x00477780, while package save later mirrors the same 0x5209/0x520a/0x520b trio back out through profile_collection_serialize_tagged_header_counts_into_bundle 0x004777e0. The same tagged symmetry is now bounded for the live company collection too: world-load refresh re-enters company_collection_load_tagged_header_counts_and_refresh_live_records_from_bundle 0x00429af0, while the package-save path mirrors the same 0x61a9/0x61aa/0x61ab bracket through company_collection_serialize_tagged_header_counts_and_save_live_records_into_bundle 0x00429b90, whose per-company callback is currently the no-op stub 0x00424000 while the load-side per-company follow-on is company_refresh_post_load_year_clamp_and_runtime_support_fields 0x004268e0. The same tagged symmetry is now bounded for the live train collection too: world-load refresh re-enters train_collection_load_tagged_header_counts_and_refresh_live_records_from_bundle 0x004b2700, while the package-save path mirrors the same 0x5209/0x520a/0x520b header bracket and per-train record walk through train_collection_serialize_tagged_header_counts_and_save_live_records_into_bundle 0x004b27a0; the per-train payload seam itself is now explicit too, with route-list load through train_refresh_tagged_route_list_payload_from_bundle 0x004a84b0 and route-list save through train_serialize_tagged_route_list_payload_into_bundle 0x004a7030. The later world-entry reactivation branch correspondingly uses (1, esi) rather than (1, 0). The current live hook probes now push the remaining auto-load gap much later too: on the hook-driven path shell_transition_mode(4, 0) returns cleanly, and the full old-mode teardown stack under 0x5389c0 now returns too, including 0x5400c0, the 0x53fe00 -> 0x53f860 remove-node sweep over [object+0x74], and the nearby mode-2 teardown helper 0x00502720. The same live run now also reaches and returns from shell_load_screen_window_construct 0x004ea620 and the immediate shell publish through 0x00538e50. Its constructor jump table is tighter now too: mode 1 is not the direct Game.win constructor, but the startup-dispatch arm rooted at 0x483012; mode 2 enters Setup.win, mode 3 enters Video.win, mode 4 enters the plain LoadScreen.win branch at 0x4832e5, mode 5 enters Multiplayer.win, mode 6 enters Credits.win, and mode 7 enters Campaign.win. The important static correction is that the startup-runtime slice 0x004ea710 -> 0x0053b070(0x46c40) -> 0x004336d0 -> 0x00438890 belongs to mode 1, not mode 4. Mode 4 only constructs and publishes the LoadScreen.win object through 0x004ea620 and 0x00538e50. The older hook-driven (4, 0) path therefore was not mysteriously skipping the startup-runtime object; it was entering the wrong jump-table arm for that work. The caller split above that owner is tighter now too: world_entry_transition_and_runtime_bringup reaches the same owner at 0x443b57 with (0, 0) after dismissing the current shell detail panel and servicing 0x4834e0(0, 0), while the saved-runtime path at 0x446d7f does the same before immediately building the .smp bundle payloads through 0x530c80/0x531150/0x531360. That makes the LoadScreen.win startup lane the only currently grounded caller that enters 0x438890 with (1, 0) instead of (0, 0). The remaining runtime uncertainty is narrower now too: the plain-run logs still show the plain LoadScreen.win state under (4, 0), and the corrected allocator-window run now reinforces the same read because the post-construct allocator stream stays empty instead of showing the expected 0x46c40 startup-runtime allocation. The first lower allocator probe on 0x005a125d was not trustworthy because that shared cdecl body sits behind the thunk and the initial hook used the wrong entry shape, and the first direct thunk hook was also not trustworthy because a copied relative-jmp thunk cannot be replayed through an ordinary trampoline. But the later corrected thunk run plus the jump-table decode now agree: the next meaningful hook-driven test is mode 1, not mode 4. mode_id = 2, and it never advanced into ready gate passed, staging, or transition. So that run did not actually exercise the 0x0053b070 -> 0x004336d0 -> 0x00438890 subchain at all. The next runtime pass now lowers the ready-poll defaults to 1 and 0 and adds an explicit ready-count log so the mode-4 startup lane either stages immediately or shows exactly how far the gate gets. That adjustment worked on the next run: the hook now stages and completes the shell_transition_mode path again, with LoadScreen.win construction and publish returning cleanly. The post-publish startup subchain is no longer unresolved on the static side, though. Direct disassembly of the mode-4 branch at 0x0048302a..0x004830ca now closes it completely: after constructing LoadScreen.win, the branch sets shell state [transition+0x08] = 4, chooses one page scalar 110.0f, 236.0f, or 5.0f, writes that page id through shell_load_screen_window_set_active_page 0x004ea710, allocates one 0x46c40-byte runtime object through 0x0053b070, resets it through world_runtime_reset_startup_dispatch_state_bands 0x004336d0, stores the result into 0x006cec78, and then directly enters shell_active_mode_run_profile_startup_and_load_dispatch 0x00438890 as (1, 0) before publishing the active-mode object back through 0x005389c0. So the static startup chain after LoadScreen.win publish is now explicit rather than inferred from the earlier hook traces. The remaining runtime-side question is narrower: why the earlier hook snapshots still showed [LoadScreen.win+0x78] == 0 and 0x006cec78 == 0 immediately after transition return even though the static mode-4 branch does not leave those fields that way once it reaches the startup-dispatch path. The internal selector split in 0x438890 is tighter now too: [0x006cec7c+0x01] is a separate seven-way startup selector, not the shell mode id. Values 1 and 7 load Tutorial_2.gmp and Tutorial_1.gmp, values 3/5/6 collapse into the same profile-seeded file-load lane through 0x445ac0([0x006cec7c]+0x11, 4, &out_success), value 2 is a world-root initialization lane that allocates 0x0062c120 and then forces selector 3, and value 4 is the setup-side world reset or regeneration lane that rebuilds 0x0062c120 from 0x006d14cc/0x006d14d0 before later world setup continues. The write side is tighter now too: Campaign.win writes selector 6, Multiplayer.win writes selector 3 on one pending-status path, and the larger Setup.win dispatcher writes selectors 2, 3, 4, and 5 on its validated launch branches. That makes the file-load subfamily read less like one generic save-open branch and more like a shared profile-file lane reused by setup, multiplayer, and campaign owners. The world-entry owner boundary is tighter now too: world_entry_transition_and_runtime_bringup at 0x00443a50 no longer stops at the initial shell transition and world allocation head. The same grounded function continues through the larger post-load generation tail up to 0x00444dc2, which means the later Setting up Players and Companies... and neighboring post-load passes are not floating raw callsites after all. That same owner now clearly covers the event-runtime refresh through 0x433130, chairman-profile materialization through 0x437220, neighboring route and tracker refresh families, and the one-shot kind-8 runtime-effect service through 0x432f40 before clearing shell-profile latch [0x006cec7c+0x97]. The Setup.win dispatcher is less opaque now too: the early 0x0bc1..0x0c24 family is mostly fixed submode selection above 0x00502c00, except for the separate 0x0bc2/0x0bc5/0x0bc6/0x0bc7 shell-open quartet above 0x00501f20; 0x0c1f is the settings-window escape; 0x0c1e/0x0c20/0x0c22 are direct shell requests into 0x00482150; the fixed submode buttons now have concrete lower targets such as 0x0bc1/0x0bc8 -> 15, 0x0bc3 -> 16, 0x0bc4 -> 1, 0x0c80 -> 13, 0x0c1c -> 17, 0x0c1d -> 2, 0x0c24 -> 14, 0x0c81 -> 14, 0x0c82 -> 6, 0x0c83 -> 10, and 0x0c84 -> 12; the 0x0ce6/0x0ce7/0x0d49/0x0d4a/0x0e82/0x0e83 branches are bounded list or slider adjustments on staged setup fields; and the later 0x0dca/0x0dcb/0x0de9/0x0df3/0x0e81/0x0f6f/0x0f70 controls are the explicit selector-writing launch buttons rather than one anonymous validated-launch blob. The constructor-side callbacks are tighter too: control 0x0ce8 is a table-driven payload-label draw callback above 0x00502030, not another launch root; that callback now clearly sits on top of the indexed string-table getter 0x0053de00 before handing the selected text span into the world-anchor marker queue path. Controls 0x0e86 and 0x0e87 do not select more setup roots; they update the persisted shell-state selector pairs at [0x006cec74+0x233/+0x237] and [0x006cec74+0x23b/+0x23f] through 0x00502160 and 0x005021c0, then immediately save config through 0x00484910(1). The constructor body is tighter too: it seeds the initial Setup.win state by running 0x502910, 0x502550, and 0x502c00(1) before the user interacts with the window, installs 0x0c80..0x0c86 and 0x0f6f..0x0f71 as homogeneous button bands, and treats 0x0e88 as one separate special control with retuned float fields rather than another ordinary launch root. The remaining optional constructor child 0x0bd0 is tighter now too: it is built from a separate template block and optional owned heap object before registration, not another hidden setup-root button. The generic shell helper layer beneath that constructor is tighter now too: 0x53fa50 is the shared resource-bind and child-list initialization helper, 0x53f830 is the child-control lookup-by-id helper over the intrusive list at [this+0x70], 0x558130 is the child-control finalizer that stamps the owner pointer and resolves localized captions before the control goes live, and 0x53f9c0 is the ordered child-control registration helper used by the optional 0x0bd0 branch and other shell dialogs. The submode selector itself is tighter now too because its button-state lane is mostly decoded: 0xbc5 tracks submode 15, 0xbc6 tracks 16, 0xbba tracks 2, 0xbbb tracks 3, 0xbc3 tracks 13, 0xbbc groups 3/4/5, 0xbbd tracks 4, 0xbbe tracks 5, 0xbbf groups 6/12/14, 0xbc0 tracks 7, 0xbc1 tracks 8, 0xbc2 tracks 9, 0xe75 tracks 10, and 0xf6e tracks 17. RT3.lng and the setup art families now also make several of those top-level roots read less like anonymous ids and more like real menu panels: submode 1 is the strongest current landing-panel fit, 2 is the Single Player root, 7/8/9 are the Editor / New Map / Load Map family, 15 is Extras, and 17 is Tutorial. By elimination against the separate shell Credits.win mode, the remaining top-level Setup.win roots now most safely read as 13 = Multi Player and 16 = High Scores. The file-backed side is tighter too: 0x502c00 now maps submodes exactly as 4 -> dataset 5, 9 -> 4, 6 -> 8, 10 -> 6, 12 -> 10, and 14 -> 9, so the saved-game-backed family is no longer one blurred list pane but a small set of stable dataset variants above 0x4333f0/0x4336a0. The file-list builder under that pair is tighter too: 0x4333f0 no longer just emits unsorted 0x25a-byte rows with raw names at +0x0 and normalized labels at +0x12d. After the scan it now clearly re-enters 0x51dc60, which is a generic adjacent-swap insertion sort over fixed records; in this setup-side caller it receives record_size = 0x25a and key_offset = 0x12d, so the resulting Setup.win rows are sorted by display label rather than by the raw filename. The file-backed header split is tighter too: 0x5027b0 now maps the top header-control ids as 4 -> 0xd4b, 9 -> 0xde8, 6/12/14 -> 0xdf2, and 10 -> 0xe85. RT3.lng closes one earlier mistake here: those values are local setup control or resource ids, not localized text ids. The setup art bundle now tightens that family split one step further too: under rt3_2WIN.PK4 the distinct file-backed setup art families are the shared Setup_Load lane and the separate Setup_Sandbox lane, which matches the selector-side evidence that mode 10 is the sandbox-backed new list while mode 12 is its load sibling. Combined with the builder at 0x4333f0, that shows only submodes 4 and 10 using the alternate localized-stem list-label path; 6, 9, 12, and 14 keep the direct filename-normalization lane. The rt3_2WIN.PK4 extraction path is grounded a bit more cleanly now too: current pack4 samples use a shared 0x03eb header, a fixed 0x4a-byte directory entry stride, and a payload base at 8 + entry_count * 0x4a. In the checked UI bundle the entry table is contiguous and Campaign.win extracts cleanly at directory index 3 with payload length 0x306a. The extracted .win payload family now has one narrow shared shape too: Campaign.win, CompanyDetail.win, and setup.win all share the same first 0x50 bytes at offsets 0x00, 0x0c, 0x10, 0x14, 0x34, 0x38, 0x40, and 0x48, while CompanyDetail.win and setup.win also carry an inline root .imb name immediately at 0x51. Current resource scans show Campaign.win embedding the litCamp*/Ribbon* family, CompanyDetail.win embedding mainly CompanyDetail.imb, GameWindow.imb, and Portrait.imb, and setup.win embedding the broader Setup_Background/Buttons/New_Game/Load/Sandbox art families. The control records between those strings are still undecoded, but the resource-record shell itself is tighter now: all checked .win samples use the same three-word prelude prefix 0x0bb8, 0x0, 0x0bb9, and the fourth prelude word matches resource_name_len + 1. The next word after the terminating NUL then behaves like a per-record selector lane. In setup.win the dominant Setup_Buttons.imb family alternates between 0x00040000 and the incrementing 0x00010c1c..0x00010c86 series with dominant inter-record strides 0xb7/0xdb; in Campaign.win the litCamp*/Ribbon* family carries the incrementing 0x0004c372..0x0004c38e selector block with dominant 0x158/0x159 and 0xb2/0xb3 strides. That campaign lane is now directly aligned to the executable-side control ids too: the low 16 bits of litCamp1..16 map exactly to 0xc372..0xc381, and the low 16 bits of Ribbon1..16 map exactly to 0xc382..0xc391, matching the Campaign.win progress and selector control bases already grounded from RT3.exe. The fuller selector export now tightens the auxiliary families too: litArrows.imb covers 0xc36c..0xc371 exactly and litExits.imb covers 0xc397..0xc39a exactly, matching the constructor-side six-control arrow strip and four-control exit strip. The second post-name dword is tighter now too: its middle 16-bit lane groups those same Campaign.win records under 0xc368, 0xc369, 0xc36a, and 0xc36b, which matches the four page-strip controls and cleanly buckets the first five campaign entries, the next five, the next three, and the final three under the same page families. There are still no .imb selector records for 0xc393..0xc396 or 0xc39b, which makes those message-side page-write controls look more like structural buttons than art-backed repeated resource records. The grouped 3/4/5 family is narrower now too: 0x0ce5 is no longer part of it, because that control writes selector 3 and then selects submode 9 as the Load Map sibling. The nearby 0x0dcb branch instead conditionally selects submode 5 or 3, which keeps 3 as the strongest current New Game / Options companion and 5 as the strongest current Sandbox companion. The file-backed single-player side is tighter in the same way now: modes 4 and 10 are the only siblings using the alternate localized-stem row-label path, so they now read most safely as the two setup-local template or profile list variants rather than ordinary save lists. Mode 10 is the stronger one of the pair because neighboring validated launch control 0x0e81 both routes into selector 5 and sets sandbox byte [0x006cec7c+0x82] = 1, which makes it the strongest current fit for the setup-local New Sandbox list. The distinct Setup_Sandbox art family in rt3_2WIN.PK4 now reinforces that same split one step further, which makes mode 12 the strongest closed fit for the paired Load Sandbox lane; mode 4 is therefore the strongest remaining non-sandbox peer in that same pair and now most safely reads as the setup-local New Scenario-style chooser. Modes 6, 12, and 14 now tighten one step further as the three selector-3 direct-filename setup-local load siblings because they stay on the direct filename-normalization lane, share the same 0xdf2 header family, and clear the same presence-style latch [0x006cec7c+0x97]; mode 14 is the strongest current landing panel for that cluster because 0x0c24 jumps to it directly while 0x0c82 and 0x0c84 only reach the sibling modes 6 and 12 from inside the same load family. Current control-pairing and setup-art evidence now make 12 = Load Sandbox the strongest closed per-submode assignment. The remaining non-sandbox pair is closed now too: the deeper bundle filter at 0x433260 distinguishes dataset 9 from dataset 8 by one extra nonzero payload-flag family, and the editor-side metadata path now grounds [0x006cec78+0x66de] as the direct Campaign Scenario checkbox bit because editorDetail.win ties control 0x5b6e to localized ids 3160/3161. That makes dataset 9 the campaign-designated load family and dataset 8 the ordinary scenario load family, so 14 = Load Campaign and 6 = Load Scenario now read as grounded rather than residual. 0x502910 is tighter in a corrective way too: it is not a mode-3-only helper after all, but the shared non-file-backed payload panel that formats 0xcf3/0xcf4/0xcf5/0xd4f, mirrors option byte [0x006cec7c+0x7d] into 0x0ce9..0x0ced, rebuilds row host 0x0ce8 from payload bytes [+0x31b/+0x31c], and mirrors live row markers into [0x006cec7c+0x87]. That makes mode 3 just one user of the shared payload-driven panel, not the sole owner of it. The payload-helper cluster under that panel is tighter now too: 0x502220 does not just republish labels and the preview surface. It first re-enters shell_setup_load_selected_profile_bundle_into_payload_record 0x442400, which clears one full 0x100f2-byte setup payload record, builds a rooted path from the staged profile stem, opens the selected bundle through 0x530c80, and then reads either the ordinary saved-profile chunk family or the map-style chunk family through 0x531150/0x531360 depending on the selected extension shape. Only after that does 0x502220 copy payload fields +0x14/+0x3b2/+0x3ba/+0x20 into the staged runtime profile through 0x47be50, which in turn normalizes the payload category bytes at [payload+0x31a + row*9] and the local marker-slot bytes at [payload+0x2c9..] through 0x47bc80. The copied-field consumer split is tighter now too: payload +0x14 becomes staged profile [0x006cec7c+0x77], which is the visible setup scalar later formatted into controls 0x0e84 and 0x0d4f and adjusted by the 0x0d49/0x0d4a pair; payload +0x3b2 becomes [0x006cec7c+0x79], the live-row threshold that the payload-row draw callback uses to choose style slot 0x18 versus 0x19; and payload +0x3ba becomes [0x006cec7c+0x7b], the current scroll or row-index lane adjusted by the 0x0ce6/0x0ce7 pair. So the known setup-side payload consumers still stop well before the later candidate table, but the early copied lanes are no longer anonymous. The adjacent region-side worker family is tighter in a negative way too: the setup payload path now gives one useful upper bound on the newer candidate-availability source block too. The map-style setup loader is definitely pulling chunk families 0x0004/0x2ee0/0x2ee1 into one large 0x100f2-byte payload record, and the fixed 0x6a70..0x73c0 candidate table clearly lives inside the same broad file family; but the grounded setup-side consumers we can actually name after that load still only touch earlier payload offsets such as +0x14, +0x20, +0x2c9, +0x31a, +0x3ae, +0x3b2, and +0x3ba. So the current evidence is strong enough to carry the candidate table conservatively as a lower setup-side candidate-table slab inside the broader setup payload family, even though the current named setup-side consumers still land only on earlier payload offsets. The newer fixed-offset compare pass tightens that lower setup slice too: across the checked map/save pairs Alternate USA.gmp -> Autosave.gms, Southern Pacific.gmp -> p.gms, and Spanish Mainline.gmp -> g.gms, the known setup payload lanes +0x14 and +0x3b2 are preserved map-to-save on the same scenario-family split as the later candidate-table headers (0x0771/0x0001, 0x0746/0x0006, 0x0754/0x0001), while +0x3ae stays fixed at 0x0186 and +0x3ba stays fixed at 1 across all six files. By contrast, +0x20 does not survive as one shared source value (0xd3 -> 0xb4, 0x6f -> 0x65, 0xe3 -> 0x78 across those same pairs). So the current best read is that the setup payload mixes preserved scenario metadata and later save-variant state well before the 0x6a70 candidate table, rather than acting as one uniformly copied prelude. The adjacent region-side worker family is tighter in a negative way too: the setup payload loader is now clearly separate from the broader region-building and placement cluster around 0x422320..0x423d30, whose current grounded helpers now include 0x422320, 0x4228b0, 0x422900, 0x422a70, 0x422be0, 0x422ee0, 0x4234e0, 0x4235c0, and world_region_refresh_cached_category_totals_and_weight_slots 0x423d30 rather than one hidden setup-only panel. The leading region helper is no longer unnamed either: 0x422320 is now bounded as the recurring cached-structure-scalar normalization pass that writes [region+0x2e2/+0x2e6/+0x2ea/+0x2ee], while 0x422a70 is the shared placement-validation and commit gate beneath both the per-region worker and one second world-side placement loop. The neighboring 0x4234e0 accessor is tighter too: it is the shared projected structure-count scalar query by category that later feeds both the per-region placement family and the map-editor city count stats report. So the remaining gap is no longer “what are these setup payload helpers doing,” but only how aggressive we want to be when naming the last top-level setup roots from mostly RT3.lng and asset-side evidence. The adjacent summary helper is tighter too: 0x502550 is not another hidden submode owner. It republishes the staged path tail in 0xe7f, the scalar summary in 0xe84, the two persisted selector lists in 0xe86/0xe87, and the config toggles in 0xe88/0xe89/0xe8a. The remaining top-level gap is cleaner now too: submode 16 still has no distinct downstream helper or launch branch in the local selector/refresh family, but it is no longer a blank bucket. Current evidence now bounds it as the 0x0bc3 -> 16 top-level button whose visual-state lane is surfaced through control 0xbc6, and the strongest residual fit is High Scores because Credits already lives in separate shell mode 6. The validated launch lane is tighter now too: it no longer just writes selector 3 or 5 as one undifferentiated blob, and it no longer includes 0x0ce5 or 0x0dcb. 0x0ce5 is the Load Map selector because it writes selector 3 and then selects submode 9, while 0x0dcb is the later conditional 5/3 companion-selector sibling rather than a file launch. The actual validated staged-profile lane is now bounded more narrowly: 0x0cf6 and 0x0e81 are the selector-5 siblings, while the neighboring selector-3 validated lane is at least shared by 0x0de9 and 0x0df3; the shared bridge at 0x4425d0 then forces [0x006cec7c+0xc5] = 1, mirrors payload bytes into [profile+0xc4], [profile+0x7d], and [profile+0xc6..+0xd5], and only then issues shell request 0x0cc. The file-side staging bytes in that bridge are now tighter too: across the checked Alternate USA, Southern Pacific, and Spanish Mainline map/save pairs, payload +0x33 stays 0, but payload +0x22 and token block +0x23..+0x32 do not preserve the earlier map-to-save pairing seen at +0x14/+0x3b2. Instead they split by the finer file family, with examples Alternate USA.gmp = 0x53 / 0131115401... versus Autosave.gms = 0xae / 01439aae01..., Southern Pacific.gmp = 0xeb / 00edeeeb... versus p.gms = 0x21 / 0100892101..., and Spanish Mainline.gmp = 0x5b / 0044f05b... versus g.gms = 0x7a / 0022907a.... So the validated launch bridge now looks more like a file-family staging lane than a simple copy-forward of the earlier setup summary fields. The destination-side consumers are tighter now too: [profile+0xc4] is no longer just an unnamed staged byte, but the campaign-progress slot later consumed by the numbered %s%02d.gmc save helper 0x00517c70; [profile+0x7d] is the same compact option byte mirrored back into the Setup.win option controls 0x0ce9..0x0ced; and the campaign-side selector block is no longer only a one-byte anchor. Direct RT3.exe disassembly of the campaign-side dispatcher at 0x004b89c0 now shows the exact mirror shape: when the local campaign-page selector [window+0x78] <= 4, the helper writes one highlighted progress control 0x0c372 + [profile+0xc4] and then mirrors the full sixteen-byte band [profile+0xc6..+0xd5] into controls 0x0c382..0x0c391 through repeated 0x540120 calls. The same body also treats [profile+0xc4] as an index into the fixed sixteen-entry scenario-name table at 0x00621cf0, whose observed entries include Go West!, Germantown, Central Pacific, Texas Tea, War Effort, State of Germany, Britain, Crossing the Alps, Third Republic, Orient Express, Argentina Opens Up, Rhodes Unfinished, Japan Trembles, Greenland Growing, Dutchlantis, and California Island. On the launch-side branch the same helper writes startup selector 6, copies the resolved campaign filename into [profile+0x11], forces [profile+0xc5] = 1, and then issues shell request 0x0cc. The lower refresh tail at 0x004b8d49..0x004b8d69 also shows the observed page-band split over the same progress byte: values < 5 pair with campaign page 1, values 5..9 with page 2, values 10..12 with page 3, and values >= 13 with page 4. So [profile+0xc6..+0xd5] is no longer a generic opaque span at all, but the staged sixteen-byte per-scenario campaign selector or unlock band consumed directly by Campaign.win. The neighboring profile helpers are tighter now too: 0x0047bbf0 is the broad default reset for the staged 0x108-byte runtime-profile record, clearing the whole record and then reseeding the visible setup and campaign anchors [profile+0x77], [profile+0x79], [profile+0x7d], the random-like dword [profile+0x83], the first setup row-marker byte [profile+0x87], and the file-backed launch or rehydrate latch [profile+0x97]; 0x00502c00 is now tighter on the same slab because its file-backed lane copies the selected row's primary and secondary 0x32-byte string bands into [profile+0x11] and [profile+0x44] while arming presence byte [profile+0x10]; and 0x0047bc50 is the compact scan helper over [profile+0xc6..+0xd5] that returns the first selector byte below 2, which keeps that band tied to staged campaign progress or unlock state rather than to the earlier setup-panel payload fields. The message-dispatch side is tighter now too. The local classifier at 0x004b91d8 routes the control range 0x0c352..0x0c39b through five concrete case classes: 0x0c352..0x0c361 enter the shared selector or launch branch, 0x0c362..0x0c367 force local page 1, 0x0c368..0x0c392 force local page 2, 0x0c393..0x0c396 force local page 0, and 0x0c39b alone forces local page 3. That matches the constructor and refresh shape: the sixteen scenario selector controls live at 0x0c352..0x0c361, the four page-strip controls live at 0x0c368..0x0c36b, the six-arrow auxiliary strip lives at 0x0c36c..0x0c371, the progress band lives at 0x0c372..0x0c381, the ribbon or selector band lives at 0x0c382..0x0c391, the string or voice target lane lives at 0x0c392, the four exit controls live at 0x0c397..0x0c39a, and 0x0c39b remains the one-off special action control that spawns or dismisses the campaign-side companion object. The Campaign.win blob now exposes that structural split directly too: alongside the art-backed 0x0004c3xx records it carries anonymous zero-name records with the same 0x0bb8 / 0 / 0x0bb9 prelude but selector words in the 0x0010c3xx family. Current local extraction shows exactly 0x0010c352..0x0010c361, 0x0010c362..0x0010c367, 0x0010c393..0x0010c396, and one 0x0010c39b record, which is the same non-.imb band the message dispatcher treats as the structural selector and page-write family. Their second selector word then buckets those records under the same page-strip ids as the art-backed records: 0xc368 for 0xc352..0xc356, 0xc362, 0xc393, and 0xc39b; 0xc369 for 0xc357..0xc35b, 0xc363, and 0xc394; 0xc36a for 0xc35c..0xc35e, 0xc365..0xc366, and 0xc395; and 0xc36b for 0xc367, 0xc35f..0xc361, and 0xc396. One final anonymous record at 0x0002c392 has no page bucket at all, which fits the already-grounded read of 0xc392 as the one-off string or voice target lane rather than a normal page-cell control. The anonymous body layout is only partially named, but one file-side distinction is already stable: the ordinary structural selector/page records all carry footer words 0xd3000000 and 0xd2000007 at relative +0x98/+0x9c, while the lone 0xc392 record carries 0x00000000/0x00000000 there. So 0xc392 is no longer just “outside the page buckets”; it is also the only current anonymous-record outlier in that trailing structural footer pair. The structural selector side is tighter now too: 0xc352..0xc361 partition exactly as 5 + 5 + 3 + 3 across page buckets 0xc368..0xc36b, matching the observed campaign page bands for scenario progress values < 5, 5..9, 10..12, and >= 13. That makes the anonymous selector records the file-side mirror of the same campaign page split, not a separate unrelated control family. The file-order adjacency is tighter in the same way: 0xc352..0xc361 each sit immediately after one RibbonN.imb record and before the next litCamp record, which makes them the strongest current file-side fit for the structural selector or click-target siblings of the visible campaign ribbon controls. The auxiliary anonymous bands line up the same way: 0xc362..0xc367 sit inside the local litArrows.imb clusters, 0xc393..0xc396 sit inside the litExits.imb clusters, and 0xc39b sits once between the first arrow and first exit group. So the current best read is that those anonymous records are the structural hitbox or action siblings of the visible arrow, exit, and selector art families, not an unrelated hidden control layer. The generic record cadence is tighter now too: in the current Campaign.win dump the ordinary anonymous structural records all span 0x0a7 bytes, while the named art-backed records cluster at 0x0b1, 0x0b2, and 0x0b3. The only two visible outliers are the leading 0x0080c351 record at 0x0041 with span 0x0a3, and the trailing 0x0002c392 record at 0x2fb9 with span 0x0b1. That reinforces the current read that 0xc351 and 0xc392 are one-off structural controls flanking the main selector or page family rather than ordinary repeated art-backed records. The setup-launch corpus now tightens one corrective caveat there too: when the checked Alternate USA, Southern Pacific, and Spanish Mainline .gmp/.gms files are decoded with that same campaign-side interpretation, payload byte +0x22 is always outside the known campaign progress range 0..15 (0x53, 0xeb, 0x5b, 0xae, 0x21, 0x7a), so the ordinary validated setup lane is not simply staging a normal campaign-screen state. The paired token block +0x23..+0x32 is still structurally compatible with the campaign selector band, but in the checked files it only populates a small recurring subset of the sixteen scenario lanes, chiefly Go West!, Germantown, Central Pacific, Texas Tea, and War Effort, with scenario-family-specific byte values rather than a simple 0/1 unlock bitmap. That makes the setup-side staging bridge look more like one shared destination layout being reused for a broader launch token family, not a proof that ordinary setup-launched .gmp/.gms content is already in campaign-screen form. Only 0x0e81 also sets [0x006cec7c+0x82] = 1, which is currently the strongest sandbox-side anchor beneath the later .gmx load family. That launch band is slightly tighter now too: 0x0dca is the grayscale-map picker branch above the TGA Files (*.tga) / .\Data\GrayscaleMaps helper at 0x004eb0b0, not another ordinary save-file validator. That launch band is tighter in a second way too: 0x0ddf is not a lobby branch after all, but the separate windowed-mode-gated grayscale-heightmap generation path. It reopens the TGA Files (*.tga) / .\Data\GrayscaleMaps picker through 0x0042a970, validates the chosen image through 0x005411c0, and only then writes startup selector 2. The fixed- button side is tighter too: submode 15 now aligns with the Extras family because it sits above the Readme/Weblinks shell-open quartet and the return-to-extras sibling, while submode 17 now aligns with the tutorial chooser because it is the only branch that later exposes startup selectors 1 and 7, which RT3 uses for Tutorial_2.gmp and Tutorial_1.gmp. The map-root family is tighter too: submodes 7/8/9 are the only setup branch that flips the file root into maps\\*.gm*, with mode 8 specifically the grayscale-heightmap picker and mode 9 the file-backed map-list sibling. Submode 13 is slightly tighter now too: current local evidence shows that it stays outside both the dedicated file-backed pane and the mode-3 option-heavy pane, so it is best read for now as one top-level non-file-backed setup branch rather than another saved-game list variant. The setup-side file roots are tighter now too: the shared file-list builder at 0x4333f0 formats either saved games\\*.smp or maps\\*.gm* from the shell-state fields at [0x006cec74+0x68/+0x6c], with the separate data\\tutorial root only appearing on the tutorial-side [shell+0x6c] == 2 branch. That means the Setup.win submode families are no longer one generic file pane: the ordinary setup-backed .smp family is broader than the narrower file-list pane, because modes 3/4/5/6/10/12/14 stay on the ordinary saved-game side while only 4/6/9/10/12/14 re-enter the dedicated file-list panel at 0x5027b0; the maps branch is the narrower setup or tutorial launch family above the same shared record builder.
  • Evidence: function-map map/scenario rows, analysis-context exports for 0x00445ac0, 0x00445de0, 0x00443a50, 0x00434300, and 0x00444dd0, plus objdump string and mode-table evidence for .gmp, .gmx, .gmc, .gms, .gmt, .smp, Quicksave, the 0x004dd010 mode table at 0x005f3d58, the auxiliary-owner presence check at 0x00434050, and the .gmt handoff through 0x00469d30, together with localized string evidence from ids 3018 and 3898.
  • Direct shell stubs above that coordinator are tighter now too: 0x004408b0 is the ordinary Save game wrapper and forwards the pure zero-flag triplet into shell_map_file_world_bundle_coordinator; 0x004408d0 is the sibling Quick save wrapper, forwarding flag triplet (0, 1, 0). RT3.lng closes the neighboring shell_map_file_entry_coordinator pair in the same way: 0x00441ac0 is Load game and 0x00441af0 is Quick load, with the same (0, 0, 0) versus (0, 1, 0) split above 0x00445ac0.
  • Current Boundary: bit 0x1 on both broad coordinators now grounds the Quicksave name seed and the former third fileopt.win flag has been ruled out as a file-flow question because it just opens SettingsWindow.win. The extension family is now carried conservatively as .gmp for the editor-map pair, .gms for the standalone scenario family, .gmc for the campaign-scenario family, .gmx for the sandbox family, and .gmt for the auxiliary preview-surface branch rather than another gameplay save family. The higher-value handoff boundary is also closed at the current evidence level: after this bring-up, long-lived simulation cadence still rendezvous with the same shell-owned frame path rather than surfacing a detached gameplay-only outer loop.