rrt/docs/control-loop-atlas.md

45 KiB
Raw Blame History

Control-Loop Atlas

This atlas is the high-level map for RT3 1.06. It is intentionally broader than the function map: each section explains who owns a loop, where it starts, what dispatches it, which state blocks anchor it, and where control is handed to neighboring subsystems.

CRT and Process Startup

  • Roots: entry at 0x005a313b, CRT helpers in the 0x005a2d..0x005ad4.. range, and app_bootstrap_main at 0x00484440.
  • Trigger/Cadence: single process startup path before shell or engine services exist.
  • Key Dispatchers: startup_init_tls_state, startup_init_file_handle_table, startup_run_init_callback_table, __setenvp, startup_build_argv, ___crtGetEnvironmentStringsA, then app_bootstrap_main.
  • State Anchors: CRT heap and file-handle tables; process environment and argv storage.
  • Subsystem Handoffs: exits the generic CRT path at app_bootstrap_main, which becomes the first RT3-owned coordinator.
  • Evidence: artifacts/exports/rt3-1.06/startup-call-chain.md, artifacts/exports/rt3-1.06/function-map.csv.
  • Open Questions: exact ownership boundary between compiler CRT shims and the first game-owned bootstrap helper; whether any nontrivial startup callbacks seed game globals before app_bootstrap_main.

Bootstrap and Shell Service Bring-Up

  • Roots: app_bootstrap_main at 0x00484440, bootstrap_init_shell_window_services at 0x004840e0, and shell_install_global_controller at 0x0051ff90.
  • Trigger/Cadence: one-time bootstrap handoff immediately after CRT completion.
  • Key Dispatchers: app_bootstrap_main, bootstrap_init_shell_window_services, shell_install_global_controller, shell_service_pump_iteration, graphics config load or default-init helpers, runtime capability probes, and early shell service initializers under the 0x004610..0x0053f0.. branch.
  • State Anchors: global shell controller pointer 0x006d4024, sibling display/runtime globals under 0x006d402c and 0x006d4030, and host capability flags gathered by bootstrap_probe_system_profile.
  • Subsystem Handoffs: after the global shell controller has been installed the bootstrap path enters the repeating shell_service_pump_iteration loop, which services shell-state work and hands each iteration down into the controller frame path; when the shell lifetime ends this same bootstrap path tears the shell bundle down and returns to app_bootstrap_main.
  • Evidence: startup-call-chain.md, function-map rows for app_bootstrap_main, bootstrap_init_shell_window_services, shell_install_global_controller, shell_service_pump_iteration, shell_load_graphics_config_or_init_defaults, shell_reset_display_runtime_defaults, and related graphics setup helpers.
  • Open Questions: whether gameplay or in-engine world stepping later escapes this bootstrap-owned shell loop entirely, or whether gameplay entry still rendezvous with the same outer coordinator before the shell bundle is destroyed.

Shell UI Command and Deferred Work Flow

  • Roots: shell callback paths that converge on shell_dispatch_ui_command at 0x00464410.
  • Trigger/Cadence: event-driven UI command dispatch plus deferred-message queue flushing during shell activity.
  • Key Dispatchers: shell_dispatch_ui_command, shell_enqueue_deferred_work_message, shell_post_deferred_message_type5, shell_post_deferred_message_type6, shell_enqueue_deferred_message_type4, shell_enqueue_deferred_message_type1.
  • State Anchors: shell object at 0x0062be68, queue roots around [this+0x11369d], [this+0x1136a1], and [this+0x1136a5], global routing gates at 0x006d4034, and the separate detail-panel controller rooted at 0x006d0818.
  • Subsystem Handoffs: routes into graphics config, scenario-text export, overlay generation, multiplayer UI, shell detail windows such as EditorPanel.win and TrainDetail.win, and presentation-facing deferred work later drained by shell_service_frame_cycle.
  • Evidence: function-map shell rows around 0x00464410, 0x004d4500, 0x004ddbd0, and 0x0051f1d0..0x0051f460.
  • Open Questions: whether the shell command dispatcher is also used by hotkeys or only by UI callback tables; the current 0x006d0818 detail-panel path looks shell-only and does not yet explain gameplay world entry.

Presentation, Overlay, and Frame Timing

  • Roots: the bootstrap-owned shell_service_pump_iteration at 0x00483f70, the shell-state service pass shell_state_service_active_mode_frame at 0x00482160, the installed global shell controller at 0x006d4024, the pending frame-cycle owner shell_service_frame_cycle at 0x00520620, and the frame-time history path under shell_update_frame_time_history at 0x0051fd70.
  • Trigger/Cadence: recurring bootstrap-owned shell service work once the active mode, controller window, and display runtime are live; special modal or content-building paths can also force immediate frame servicing inside that broader cadence.
  • Key Dispatchers: shell_service_pump_iteration, shell_state_service_active_mode_frame, shell_service_frame_cycle, shell_refresh_presentation_frame, shell_update_frame_time_history, shell_get_smoothed_frame_scalar, shell_set_gamma_ramp_scalar, and overlay builders such as shell_queue_single_world_anchor_overlay, shell_queue_world_anchor_overlay_list, and shell_queue_indexed_world_anchor_marker.
  • State Anchors: shell state at 0x006cec74, active mode pointer 0x006cec78, shell frame history ring at 0x006d403c, display/runtime state inside the controller block near [this+0x114282] and [this+0x11428a], presentation service pointer at [this+0x0c], and the native controller window handle at [this+0x00].
  • Subsystem Handoffs: the bootstrap-owned pump runs shell-bundle polling and shell-state maintenance before the shell-state pass synchronizes active-mode helpers and modal-status work and dispatches the controller frame cycle, which then consumes world/object state for overlays and presentation refresh, uses the controller window handle for one-time ShowWindow and related UI interaction, and drains the deferred work queues at the end of the cycle.
  • Evidence: function-map rows for shell_service_pump_iteration, shell_state_service_active_mode_frame, shell_service_frame_cycle, shell_flush_deferred_work_queues, frame history, gamma ramp, world-anchor overlay builders, and graphics runtime helpers.
  • Open Questions: how simulation cadence rendezvous with the shell presentation cadence once gameplay takes over and whether gameplay stepping exits this shell-owned controller path entirely.

Map and Scenario Content Load

  • Roots: shell_map_file_entry_coordinator at 0x00445ac0, the first grounded world-entry branch world_entry_transition_and_runtime_bringup at 0x00443a50, shell_map_file_world_bundle_coordinator at 0x00445de0, reference-database setup via map_bundle_open_reference_databases at 0x00444dd0, and narrower loaders such as map_load_geographic_label_database and map_load_city_database.
  • 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, world_entry_transition_and_runtime_bringup, world_runtime_release_global_services, shell_map_file_world_bundle_coordinator, map_bundle_open_reference_databases, map_load_geographic_label_database, map_load_city_database, 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.
  • 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.
  • Open Questions: 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 old broad extension question is mostly resolved: .gmp is the editor-map pair, .gms is the standalone scenario family, .gmc is the campaign-scenario family, .gmx is the sandbox family, and .gmt is at least bounded as an auxiliary preview-surface branch rather than another gameplay save family. The higher-value global question is no longer whether 0x00443a50 is world entry. It is where the long-lived simulation cadence takes over after this bring-up and whether that cadence still rendezvous with the shell-owned frame path or escapes into a separate gameplay loop.

Multiplayer Session and Transport Flow

  • Roots: multiplayer_window_init_globals at 0x004efe80, multiplayer_window_service_loop at 0x004f03f0, the Multiplayer.win session-event callback table built by multiplayer_register_session_event_callbacks at 0x0046a900, the active session-event transport object at 0x006cd970, the Multiplayer preview dataset object at 0x006cd8d8, and the lower pending-template and text-stream helpers around 0x00597880..0x0059caf0.
  • Trigger/Cadence: shell-owned Multiplayer.win frame service plus event-driven session callbacks and repeated transport pump steps.
  • Key Dispatchers: session-event wrappers for actions 1, 2, 4, 7, 8; multiplayer_register_session_event_callbacks; multiplayer_dispatch_requested_action; multiplayer_reset_preview_dataset_and_request_action; multiplayer_preview_dataset_service_frame; multiplayer_load_selected_map_preview_surface; multiplayer_flush_session_event_transport; multiplayer_transport_service_frame; multiplayer_transport_service_worker_once; multiplayer_transport_service_route_callback_tables; multiplayer_transport_service_status_and_live_routes; multiplayer_transport_text_stream_service_io; multiplayer_transport_dispatch_pending_template_node; multiplayer_transport_service_pending_template_dispatch_store.
  • State Anchors: live session globals at 0x006d40d0, active session-event transport object at 0x006cd970, status latch at 0x006cd974, session-event mode latch at 0x006cd978, retry counter at 0x006cd984, preview dataset object at 0x006cd8d8, preview-valid flag at 0x006ce9bc, staged preview strings at 0x006ce670 and [0x006cd8d8+0x8f48], Multiplayer.win backing block at 0x006d1270, selector-view store root at [transport+0xab4], selector-view generation counters at [transport+0xab8]..[transport+0xac0], selector-view backing arrays around [transport+0xad0]..[transport+0xae4], selector-view entry probe-request id at [entry+0x50], live-state gate at [entry+0x58], third-slot flag word at [entry+0x64], probe-schedule tick at [entry+0x68], last-success tick at [entry+0x6c], pending-probe latch at [entry+0x74], success generation at [entry+0x78], consecutive-failure counter at [entry+0x7c], rolling average at [entry+0x80], recent sample window at [entry+0x84..], bounded sample-count at [entry+0x94], total-success count at [entry+0x98], pending-template list at [transport+0x550], dispatch store at [worker+0x54c], and text-stream buffers rooted under [worker+0x1c].
  • Subsystem Handoffs: the Multiplayer.win initializer seeds the backing block at 0x006d1270, later reset paths construct the separate preview dataset at 0x006cd8d8, and the shell-owned active-mode frame services that dataset every frame through multiplayer_preview_dataset_service_frame. That preview side publishes roster and status controls through the Multiplayer window control paths, loads .gmt preview surfaces through multiplayer_load_selected_map_preview_surface, and is even reused by the map/save coordinators mode-11 .gmt path when the dataset already exists. In parallel, multiplayer_register_session_event_callbacks allocates and registers the separate session-event transport object at 0x006cd970. The shell-side bridge into that deeper transport cadence is now tighter: multiplayer_window_service_loop and neighboring reset or initializer branches call multiplayer_flush_session_event_transport, which forces one status flush and then drops into multiplayer_transport_flush_and_maybe_shutdown. That wrapper in turn runs multiplayer_transport_service_frame, the recurring pump that services one worker step through multiplayer_transport_service_worker_once, sweeps the transport-owned callback tables and field caches through multiplayer_transport_service_route_callback_tables, services both the auxiliary status route and the current live route through multiplayer_transport_service_status_and_live_routes, and then descends one layer farther into the shared GameSpy route helper multiplayer_gamespy_route_service_frame at 0x58d040. The transport also owns a separate selector-view sidecar beneath that route cadence. multiplayer_transport_ensure_selector_view_store allocates the keyed selector-view store at [transport+0xab4], multiplayer_transport_find_selector_view_entry_by_name resolves entries from that store, multiplayer_transport_upsert_selector_name_entry marks per-slot activity and flags inside each entry, and multiplayer_transport_mark_selector_slot_views_dirty plus multiplayer_transport_reset_selector_view_entry_runtime_state manage the dirty or refresh fields at +0xa0, +0xa4, and +0xa8. That selector-view maintenance path is now split more cleanly too. The recurring owner is multiplayer_transport_service_selector_view_refresh_cycle, which first runs the fast deferred-probe lane: multiplayer_transport_collect_refreshable_selector_view_entries walks the store through multiplayer_transport_filter_insert_refreshable_selector_view_entry, which now shows that [entry+0x64] is not a generic flag bucket but the third selector-slot flag word, parallel to [entry+0x5c] and [entry+0x60]. In that collector, the g and a mode-letter bits produced by multiplayer_transport_parse_selector_mode_letters become mask 0x0c in the slot-local flag words, and any third-slot entry carrying those bits at [entry+0x64] is excluded from the refreshable set. Eligible entries then pass slot-aware retry timing on [entry+0x68], [entry+0x6c], [entry+0x78], and [entry+0x7c], after which the service loop schedules refresh probes through multiplayer_transport_schedule_selector_view_entry_refresh_probe. That fast lane is narrower now too: the entry-side match key [entry+0x50] is no longer just an opaque request field. The profile-key callback lanes feed multiplayer_transport_parse_selector_view_probe_marker, which decodes one local X%sX|%d marker into a request id plus companion value, and multiplayer_transport_arm_selector_view_probe_tracking stores those into [entry+0x50] and [entry+0x54] before arming the live probe gate at [entry+0x58]. The current-selector callback root at 0x59f8b0 is now bounded as well: it resolves and upserts the active selector name, optionally reuses a cached username marker to arm probe tracking immediately, then submits the same profile-key bundle with selector context and forwards that selector through callback slot 17, with the status-route side able to force route-mode transitions 2 -> 3 -> 4 afterward. One layer lower, multiplayer_transport_handle_profile_key_query_result at 0x596970 now bounds the per-key result path itself. It treats username as the probe-marker field, b_flags as the selector mode-letter field, and (END) as a real sentinel that publishes a zeroed slot-22 payload instead of a marker pair. The same helper also hashes the selector-name, key-name, and resolved value text back into the caller table, so the profile-key bundle now looks like a real bounded handoff rather than an anonymous callback cloud. The deferred callback shim multiplayer_transport_dispatch_selector_view_refresh_probe_result then walks the keyed store through multiplayer_transport_finish_selector_view_refresh_probe_if_matching, which only completes entries whose pending latch [entry+0x74] is still armed and whose parsed marker request id [entry+0x50] matches the finished request. A failed result -1 clears the pending latch and increments the consecutive-failure counter at [entry+0x7c]. A nonfailure result clears the pending latch, increments the success generation at [entry+0x78] and total-success count [entry+0x98], clears [entry+0x7c], stamps the last-success tick at [entry+0x6c], appends the returned sample into the short rolling history at [entry+0x84..], grows the bounded sample-count [entry+0x94] up to four, computes the current average into [entry+0x80], and then publishes that averaged sample through multiplayer_transport_enqueue_callback_slot24_record. So the publication boundary is explicit and the request-id ownership is explicit, even though the exact user-facing meaning of the sample and the companion value at [entry+0x54] is still open. The same service loop also owns a slower sidecar lane keyed off [entry+0xa4]: multiplayer_transport_select_stale_selector_view_progress_entry walks the store through multiplayer_transport_pick_stale_selector_view_progress_entry, picks one stale entry whose progress latch [entry+0x9c] is clear and whose last progress tick [entry+0x70] is old enough, and then hands it to multiplayer_transport_stage_selector_view_progress_snapshot. That helper now looks more bounded too: it rebuilds the marker text from [entry+0x50] through multiplayer_transport_format_selector_view_probe_marker, formats one PNG %s %d line around that marker and the entry-local selector-view sample at [entry+0x80], appends bounded selector-slot PNG fragments for live overlapping slots, marks progress-snapshot state in flight at [entry+0x9c], and stamps both [entry+0x70] and the transport-wide throttle tick [transport+0xaec]. So the selector-view sidecar no longer looks like one undifferentiated refresh bucket: it has a faster deferred-probe lane plus a slower progress-snapshot lane, both still under the shell-owned multiplayer transport cadence. The two descriptor lanes installed by multiplayer_transport_attach_callback_table_descriptor are now tighter too. The first lane rooted at 0x59f5c0 can arm deferred-close state on the owner transport and then forward through callback slot 23. The second lane is no longer just a loose selector-view bucket: multiplayer_transport_callback_dispatch_selector_name_payload_lane at 0x59f650 classifies selector payloads through multiplayer_transport_is_selector_control_line, routes @@@NFO control lines into multiplayer_transport_sync_selector_view_nfo_r_flag, and otherwise publishes either callback slot 13 or the split token-plus-tail callback slot 14 through multiplayer_transport_split_selector_payload_token_and_tail. That local @@@NFO helper is now bounded more tightly too: it only accepts lines ending in the literal X\ tail, searches for the field marker \$flags$\, and then sets or clears bit 0x2 in the third selector-slot flag word at [entry+0x64] depending on whether that field contains the letter r before the next backslash. The sibling multiplayer_transport_callback_dispatch_current_selector_payload_lane at 0x59f720 first resolves the active selector through 0x5951a0, then handles the current-selector variants of the same control vocabulary: @@@NFO continues into the same local r-flag sync helper, @@@GML plus mode-3/4 payloads feed the shared control-token helper multiplayer_transport_handle_gml_or_png_selector_control, and the remaining non-control payloads publish callback slot 9. That shared helper now narrows the GML and PNG split materially: when the token is GML and the tail is Disconnected, it requires the active selector-view entry to pass multiplayer_transport_selector_view_entry_has_gml_disconnect_gate, which currently means the entry participates in the third selector slot and has bit 0x20 set in [entry+0x64], before it forces one status-pump pass, emits callback slot 16, and re-enters route mode 5. Its sibling PNG branch resolves a named selector-view entry from the tail text and, when that entry overlaps the active selector-slot ownership, refreshes selector-view runtime state through 0x5948f0 and republishes callback slot 25. Alongside those dispatchers, one grounded branch at 0x59f560 still updates selector-view runtime state through 0x5948f0 and forwards the same selector-name pair through callback slot 25, while 0x59f850 resets selector text state through 0x5954b0, forwards through callback slot 19, and when selector 2 is active in a nonterminal route mode re-enters multiplayer_transport_set_route_mode with mode 1. The low-level route helper still looks like a two-part cycle: multiplayer_gamespy_route_service_retry_and_keepalive_timers handles challenge or retry pressure and periodic outbound control traffic around the master.gamespy.com, PING, natneg, localport, localip%d, and statechanged strings, while multiplayer_gamespy_route_drain_inbound_packets drains inbound datagrams and dispatches semicolon lines, backslash-delimited key bundles, and 0xfe 0xfd GameSpy control packets. The transport-owned callback story is now narrower too. The shared route constructor multiplayer_gamespy_route_construct_and_seed_callback_vector seeds [route+0x88] through [route+0x9c] from the caller-supplied transport callback table, records the owner transport at [route+0x104], and explicitly zeroes [route+0xa0], [route+0xa4], and [route+0xd4] before any later patch-up. For the transport-owned status route, multiplayer_transport_try_connect_status_route then patches [route+0xa0] through multiplayer_gamespy_route_set_extended_payload_callback to point at multiplayer_transport_forward_validated_extended_route_payload 0x00597330, which simply forwards the validated payload wrapper into the owner callback at [transport+0x17f4] with context [transport+0x17f8]. The grounded live-route connect path at multiplayer_transport_try_connect_live_route does not currently perform any matching post-construction patch for [route+0xa0], [route+0xa4], or [route+0xd4], and the higher route-mode state machine now looks consistent with that: current grounded mode transitions switch by releasing route objects through multiplayer_gamespy_route_release_and_free and rebuilding them through multiplayer_transport_try_connect_live_route, not by mutating callback slots in place. The parser behavior is now tighter as well: semicolon lines only dispatch when [route+0xd4] is non-null, and the subtype-6 raw fallback only dispatches when [route+0xa4] is non-null. For the currently grounded transport-owned status and live routes, those two branches therefore remain optional and can cleanly no-op instead of implying a hidden mandatory callback path. Inside the packet parser, subtype 4 is no longer an unknown callback hop: after cookie validation it dispatches through [route+0x9c], which the transport-owned status and live routes seed to multiplayer_transport_handle_validated_route_cookie_event 0x005972c0. That helper either marks route progress and re-enters multiplayer_transport_set_route_mode, or forwards the event id plus payload into the owner callback at [transport+0x17f0] with context [transport+0x17f8]. Subtype 6 still validates the same cookie, dedupes one 32-bit cookie or packet id, and then dispatches the trailing payload through the natneg-or-raw callback layer rooted at [route+0xa0] and [route+0xa4]. This separates the shell-frame preview refresh at 0x006cd8d8 from the actual transport cadence at 0x006cd970, and also separates that transport cadence from the lower GameSpy route-service and packet-parser layer beneath it.
  • Evidence: function-map.csv, pending-template-store-management.md, pending-template-store-functions.csv, plus objdump caller traces showing multiplayer_window_service_loop reaching multiplayer_flush_session_event_transport and the transport pump chain multiplayer_flush_session_event_transport -> multiplayer_transport_flush_and_maybe_shutdown -> multiplayer_transport_service_frame -> multiplayer_transport_service_worker_once -> multiplayer_transport_drain_request_text_queue.
  • Open Questions: unresolved request-id semantics for 1, 2, 4, and 7; whether any non-mode-path helper outside the currently grounded release-and-rebuild flow ever patches [route+0xa4] and [route+0xd4] for the transport-owned status/live routes, or whether those packet families are simply unused in this stack; and how far the Multiplayer preview-dataset machinery is reused outside Multiplayer.win beyond the currently grounded .gmt save-mode hook.

Input, Save/Load, and Simulation

  • Roots: the shell controller window-message ingress shell_controller_window_message_dispatch at 0x0054e3a0, the shell input-state object initialized at 0x006d4018 through shell_input_state_init 0x0054e710, the saved-world restore path world_load_saved_runtime_state_bundle at 0x00446d40, the live-world save path world_runtime_serialize_smp_bundle at 0x00446240, world_entry_transition_and_runtime_bringup at 0x00443a50, the frame-owned cadence simulation_frame_accumulate_and_step_world at 0x00439140, the recurring GameMessage.win service branch through game_message_window_service_if_present 0x004e0720, the world-facing GameUppermost.win overlay branch ensured by shell_ensure_game_uppermost_window 0x004e0e40 and serviced through game_uppermost_window_service_world_hotspot_band 0x004e0780, and the lower step family rooted at simulation_advance_to_target_calendar_point 0x0040ab50 with periodic branches through simulation_service_periodic_boundary_work 0x0040a590.
  • Trigger/Cadence: shell-side input is event-driven by controller-window WM_* traffic while save or load work is triggered either directly from shell commands or through the fileopt.win branch flags into the .smp runtime-state family; post-bring-up world service becomes recurring once a world root exists at 0x0062c120, but the current grounded top-level cadence still remains the shell-owned shell_service_pump_iteration path, which calls simulation_frame_accumulate_and_step_world directly and lets it accumulate elapsed wall-clock time into one or more simulation-step quanta.
  • Key Dispatchers: shell_controller_window_message_dispatch, shell_input_apply_window_key_transition, shell_input_snapshot_dispatch_state, shell_input_cursor_inside_active_view, world_load_saved_runtime_state_bundle, world_runtime_serialize_smp_bundle, simulation_frame_accumulate_and_step_world, game_message_window_service_if_present, game_message_window_service_frame, game_uppermost_window_handle_message, game_uppermost_window_service_world_hotspot_band, game_uppermost_window_refresh_controls, world_view_service_keyboard_turn_pan_and_zoom_bindings, world_view_step_heading_quadrant, world_view_step_zoom_bucket, station_place_world_surface_sync_and_dispatch, station_place_window_handle_message, track_lay_window_refresh_controls, track_lay_window_service_frame, track_lay_window_handle_message, simulation_advance_to_target_calendar_point, the smaller single-step helper at 0x00409e80, 0x0040a9c0, 0x0040a910, and simulation_service_periodic_boundary_work.
  • State Anchors: shell input object 0x006d4018, per-key state table starting at [input+0x100], packed shell input flags at [input+0xa8c] now grounded as Right Shift 0x1, Left Shift 0x2, Control 0x4, and Alt 0x20, nested dispatch counter [input+0xa90], global shell controller 0x006d4024, active world root 0x0062c120, GameMessage.win object 0x006d081c, GameUppermost.win overlay object 0x006d0820, StationPlace.win tool object 0x006d1720, TrackLay.win tool object 0x006d1a8c, accumulated leftover simulation time at [this+0x4c80], shell and mode globals at 0x006cec74, 0x006cec78, and 0x006cec7c, world manager collections at 0x0062be10, 0x006ceb9c, 0x006cfcbc, 0x006cec20, 0x0062bae0, and 0x006acd34, plus the packed calendar-like tuple fields around [this+0x0d], [this+0x0f], [this+0x11], and [this+0x14].
  • Subsystem Handoffs: the controller window dispatcher now looks like the first grounded input ingress. It translates keyboard and mouse WM_* traffic into shell controller state and the separate input object at 0x006d4018; read-side cursor and camera helpers later snapshot that object through shell_input_snapshot_dispatch_state and gate world-relative interaction through shell_input_cursor_inside_active_view. Current grounded consumers around 0x00478cb0, 0x004e0780, 0x0053f450, and 0x0053fe90 still sit on the shell controller path and consult 0x006d4024 or the world owner at 0x0062be68, so there is still no evidence for a distinct gameplay-only input object after world entry. Instead, the first deeper world-mode interaction branch now looks like a shared world-view coordinator layered on top of the same shell-fed input state. Shell_transition_mode ensures the GameUppermost.win object at 0x006d0820; its message dispatcher game_uppermost_window_handle_message owns the narrow action band 0x7918 through 0x7921; and the recurring service helper game_uppermost_window_service_world_hotspot_band rate-limits those hotspot actions, rechecks shell_input_cursor_inside_active_view, and then pans the live world view through world_view_pan_relative_offset_in_camera_plane 0x0043d130. The same lower setter family is also reached from the cursor-drag path through world_view_apply_screen_delta_to_focus_position 0x0043d0c0. Above both of those sits the larger recurring service world_view_service_shell_input_pan_and_hover 0x0043db00, which now has one grounded keyboard branch beneath it: world_view_service_keyboard_turn_pan_and_zoom_bindings 0x0043d740. That helper resolves four binding-pair families from the shell input table via 0x0054e7d0, and the localized labels are now grounded from Data/Language/RT3.lng: Camera Forward and Camera Backward feed the first signed pan channel, Camera Left and Camera Right feed the second signed pan channel, Camera Zoom In and Camera Zoom Out feed the signed zoom-step channel that 0x0043db00 smooths into world_view_step_zoom_bucket 0x0043cc30, and Camera Rotate Left plus Camera Rotate Right feed the continuous heading-turn branch through 0x0043c810. The setup side is now better bounded too: world_view_seed_keyboard_binding_slot_pairs at 0x00439e40 seeds the eight slot pairs at [this+0x0a6] through [this+0x0e2] from the global action-binding registry through 0x0045f370 using the distinct registry keys 0x0043d2a0 through 0x0043d310, and the registration block at 0x00460769 through 0x004608e7 shows those roots are defaulted to the expected Up Down Left and Right virtual keys before runtime polling begins. Beneath that camera stack, the enclosing frame path now has one grounded non-view sidecar: after 0x0043db00 it reads the active controller-view object pointer at [0x006d4024+0x18]+0x366e, compares it against the latched target at [frame_owner+0x66a2], fires exit and enter-style vtable callbacks on pointer changes through slots +0x64 and +0x60, and only latches the new object when the object passes its own slot +0x1c availability test and shell detail control id 0x07d6 on 0x006d0818 has flag bit 0x4. That 0x07d6 gate is now more bounded than before: the dedicated TrackLay.win tool family rooted at 0x006d1a8c special-cases the same control id in both track_lay_window_service_frame and track_lay_window_handle_message, uses world hit tests through 0x00448ac0 to arm and release a drag latch on that surface, and routes the resulting command work through the shared track-lay mode state at 0x00622b0c. The surrounding track_lay_window_refresh_controls pass now shows that this is not just one isolated drag handler: the same tool family owns three mutually exclusive primary mode buttons at 0x985e 0x985f and 0x9860, and current primary evidence now bounds those values as Lay single track. 0x1, Lay double track. 0x4, and Bulldoze 0x40 from the localized strings 2054 2055 and 1721 plus the matching control-routing branches in track_lay_window_handle_message. The same family also owns a bridge-type preference selector rooted at 0x006cec74+0x138, two wrapped Never through Common frequency settings at 0x006cec74+0x140 and 0x006cec74+0x13c, two boolean track-lay preference toggles, and the electrify-all-track action path. Those last two toggles are now tighter than before: current evidence strongly aligns control 0x986e and state 0x006cec74+0x144 with Auto-Hide Trees During Track Lay from strings 1838 and 1839, while control 0x986d and state 0x006cec78+0x4c74 align with Auto-Show Grade During Track Lay from strings 3904 and 3905. That mapping is still evidence-backed rather than absolutely direct from a recovered resource table, but the state ownership and control order now make it the strongest current fit. That makes 0x07d6 look like the shared main-world interaction surface inside a broader TrackLay world-command subsystem, not an unrelated detail button. The neighboring StationPlace.win family is now grounded on that same surface too: the shell detail-panel constructor family allocates it through station_place_window_construct at 0x00509d80, publishes it at 0x006d1720, services it each frame through station_place_window_service_frame at 0x0050a530, and routes player-facing commands through station_place_window_handle_message at 0x005091b0. That dispatcher special-cases the same 0x07d6 control, and the shared helper station_place_world_surface_sync_and_dispatch at 0x00508bb0 either accepts that direct surface traffic or falls back to the same detail-panel control looked up through 0x006d0818, rechecks flag bit 0x4, hit-tests the world through 0x00448ac0, stages world coordinates into 0x006d1738 and 0x006d173c, refreshes the selected-site summary through station_place_format_selected_site_summary, and updates the live station-placement selection state at 0x00622af0, 0x00622aec, and 0x006d1740. Together with station_place_select_category_and_refresh 0x00508880, station_place_refresh_category_controls 0x00507b90, and station_place_format_preview_panel 0x00508730, that makes StationPlace a second grounded consumer of the shared main-world interaction surface rather than only a sibling constructor plus frame hook. The StationPlace control semantics are now tighter too: the top category strip at 0x697c through 0x6981 now grounds as small station, medium station, large station, service tower, maintenance facility, and non-station building from RT3.lng strings 2197 through 2202. For the three station categories only, the secondary strip at 0x6988 and 0x6989 plus display field 0x698c no longer looks like another placement mode family; it is a building-style scroller. The click handlers on 0x6988 and 0x6989 cycle the style override in 0x00622aec, and the display path builds the active style token from StationSml, StationMed, or StationLrg plus the localized architecture styles Victorian, Tudor, Southwest, Persian, Kyoto, and Clapboard from RT3.lng ids 2672 through 2667. One layer lower, the remaining opaque controls are now much tighter: 0x6985 and 0x6986 are no longer a generic assist toggle but the two station-rotation policy choices from strings 2207 and 2206, switching between auto-orienting the building to track or obstacles and strictly obeying the rotation specified by the circle above. The dedicated control at 0x6987 is the station-rotation circle itself, wired through callback 0x00507a90 and aligned with Click to rotate the building. You can also use bracket keys [ and ] to rotate buildings. string 2208. That matches the lower behavior too: when the strict-rotation choice is off, both preview and commit paths route staged coordinates through 0x00508040, which performs the extra orientation search before validation; when strict rotation is on, that pass is skipped and the current angle in 0x006d172c is used directly. The direct shell UI also exposes the same discrete view-step family through world_view_step_heading_quadrant 0x0043cb00 and world_view_step_zoom_bucket 0x0043cc30. The neighboring gating predicates world_view_should_drive_primary_pan_channel and world_view_should_drive_secondary_pan_channel test packed shell input bits 0x3, and shell_input_apply_window_key_transition now grounds those bits as the left and right Shift modifiers from scan codes 0x2a and 0x36. That means cursor drag, overlay hotspots, held Shift state, direct keyboard turn/pan/zoom bindings, the TrackLay and StationPlace world-command surfaces, and at least one frame-owned hover or focus-target branch all converge under the same shell-fed world-mode input path rather than a separate gameplay-input stack.
  • Evidence: function-map rows for shell_controller_window_message_dispatch, shell_drain_pending_window_messages, shell_input_state_init, shell_input_apply_window_key_transition, shell_input_snapshot_dispatch_state, shell_input_cursor_inside_active_view, world_load_saved_runtime_state_bundle, world_runtime_serialize_smp_bundle, world_entry_transition_and_runtime_bringup, simulation_frame_accumulate_and_step_world, game_message_window_service_if_present, game_message_window_service_frame, shell_ensure_game_uppermost_window, game_uppermost_window_construct, game_uppermost_window_handle_message, game_uppermost_window_service_world_hotspot_band, world_view_set_focus_position_xyz, world_view_apply_screen_delta_to_focus_position, world_view_pan_relative_offset_in_camera_plane, world_view_seed_keyboard_binding_slot_pairs, world_view_service_keyboard_turn_pan_and_zoom_bindings, world_view_step_heading_quadrant, world_view_step_zoom_bucket, world_view_should_drive_primary_pan_channel, world_view_should_drive_secondary_pan_channel, world_view_service_shell_input_pan_and_hover, station_place_refresh_category_controls, station_place_format_selected_site_summary, station_place_format_preview_panel, station_place_select_category_and_refresh, station_place_world_surface_sync_and_dispatch, station_place_window_handle_message, station_place_window_construct, station_place_window_service_frame, track_lay_window_refresh_controls, track_lay_window_construct, track_lay_window_service_frame, track_lay_window_handle_message, and simulation_service_periodic_boundary_work, plus the shell-input disassembly around 0x0054f290, the world-view setup and service branches around 0x00439e40, 0x0043d740, 0x0043db00, 0x0043cb00, and 0x0043cc30, the StationPlace.win constructor plus category, dispatcher, rotation-circle, shared-world-surface, preview-build, and recurring service branches around 0x00507a90, 0x00507b90, 0x00508550, 0x00508730, 0x00508880, 0x00508bb0, 0x005091b0, 0x00509d80, and 0x0050a530, the StationPlace string cluster Place a small station 2197 Place a medium station 2198 Place a large station 2199 Place a service tower 2200 Place a maintenance facility 2201 Place a non-station building 2202 Scroll through building styles. 2203 When placing the building, it will strictly adhere to the rotation specified by the circle above. 2206 When placing the building, it will rotate itself as needed to orient to track or avoid obstacles. 2207 Click to rotate the building. You can also use bracket keys [ and ] to rotate buildings. 2208 and the architecture-style labels Clapboard 2667 Kyoto 2668 Persian 2669 Southwest 2670 Tudor 2671 and Victorian 2672, the TrackLay.win constructor and dispatcher family around 0x0050d2d0, 0x0050e400, 0x0050e1e0, and 0x0050e5c0, the localized Never through Common strings 615 through 618, the track-lay strings Bulldoze 1721 Lay single track. 2054 Lay double track. 2055 and 3114, the TrackLay preference strings Auto-Hide Trees During Track Lay 1838 If 'Auto-Hide Trees During Track Lay' is checked, trees will automatically be reduced to small stumps whenever you are in track laying mode. 1839 Auto-Show Grade During Track Lay 3904 and If 'Auto-Show Grade During Track Lay' is checked, you'll see the grade number over the track while laying track - useful for trying to keep your slopes to a minimum. 3905, the electrify-all confirmation and failure strings 3083 3084 3837 and 3900, the binding-registry lookup path at 0x0045f370, the registration block at 0x00460769 through 0x004608e7, and the localized labels in Data/Language/RT3.lng ids 3466 through 3473.
  • Open Questions: no separate outer gameplay loop is grounded above simulation_frame_accumulate_and_step_world yet and no deeper gameplay-only input object is grounded either, but the first deeper world-mode interaction branch is now better bounded: GameUppermost.win hotspots, cursor drag, held Shift state, discrete shell view-step commands, direct keyboard turn/pan/zoom bindings, the TrackLay.win and StationPlace.win world-command surfaces, and a frame-owned hover or focus-target transition branch all feed the same shell-controller-backed path. The remaining uncertainty has moved farther from basic ownership: the hover-target branch clearly exists, and 0x07d6 now looks like the shared main-world interaction surface rather than a generic detail button for one tool family only. The next unresolved layer is narrower and more semantic: the TrackLay.win family now clearly owns Lay single track. Lay double track. and Bulldoze as its three primary modes, its bridge selector, its wrapped frequency preferences, and a strongly aligned pair of Auto-Hide Trees During Track Lay and Auto-Show Grade During Track Lay toggles; the StationPlace.win family now clearly owns its six top-level category buttons, the station-style scroller, and the station-rotation controls; and the packed simulation tuple fields remain semantically open. The older Building placement center string 671 no longer looks like a live StationPlace control label in the current recovered flow, because the active constructor, preview, refresh, and dispatcher paths all use neighboring ids such as 669 and 2208 without a direct recovered lookup of 671. On save or load the broad serialize-versus-restore split is now grounded, the non-Quicksave .gmp/.gmx/.gmc/.gms families are separated, and the auxiliary .gmt path is at least bounded to the preview-surface side owner. The higher-value shell-facing gap has therefore shifted upward to subsystem ownership, especially the outer multiplayer transport and service cadence.

Next Mapping Passes

  • Resolve the owner-side callback roles behind the validated GameSpy packet branches, especially [route+0x9c], [route+0xa0], [route+0xa4], and [route+0xd4].
  • Determine whether any later world-mode branch beneath the frame owner bypasses the shell controller input path for non-cursor gameplay input, since the current grounded overlay and cursor helpers still reuse 0x006d4018.
  • Keep detailed pending-template or transport work scoped to the specific atlas edges that remain unresolved.