Split control-loop atlas into section files

This commit is contained in:
Jan Petykiewicz 2026-04-11 17:55:16 -07:00
commit 1040a131da
21 changed files with 5356 additions and 3868 deletions

View file

@ -0,0 +1,18 @@
# Control-Loop Atlas
This directory holds the canonical atlas narrative split by the original top-level sections from
[docs/control-loop-atlas.md](/home/jan/projects/rrt/docs/control-loop-atlas.md).
Use the compatibility index when an existing link only knows the old single-file path. Use these
section files when adding or reviewing grounded narrative content.
## Sections
- [crt-and-process-startup.md](/home/jan/projects/rrt/docs/control-loop-atlas/crt-and-process-startup.md)
- [bootstrap-and-shell-service-bring-up.md](/home/jan/projects/rrt/docs/control-loop-atlas/bootstrap-and-shell-service-bring-up.md)
- [shell-ui-command-and-deferred-work-flow.md](/home/jan/projects/rrt/docs/control-loop-atlas/shell-ui-command-and-deferred-work-flow.md)
- [presentation-overlay-and-frame-timing.md](/home/jan/projects/rrt/docs/control-loop-atlas/presentation-overlay-and-frame-timing.md)
- [map-and-scenario-content-load.md](/home/jan/projects/rrt/docs/control-loop-atlas/map-and-scenario-content-load.md)
- [multiplayer-session-and-transport-flow.md](/home/jan/projects/rrt/docs/control-loop-atlas/multiplayer-session-and-transport-flow.md)
- [input-save-load-and-simulation.md](/home/jan/projects/rrt/docs/control-loop-atlas/input-save-load-and-simulation.md)
- [next-mapping-passes.md](/home/jan/projects/rrt/docs/control-loop-atlas/next-mapping-passes.md)

View file

@ -0,0 +1,24 @@
## 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.

View file

@ -0,0 +1,17 @@
## 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`.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,450 @@
## 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`, 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`,
`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_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. 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`, `0x433730`, and the common free path before clearing `0x006cec78`.
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. But the post-publish startup subchain is still unresolved: there is still no trusted
`0x46c40` allocator hit, no direct `0x004336d0` entry, and no direct `0x00438890` entry. So
the next clean runtime boundary is the tiny `LoadScreen.win` scalar setter at `0x004ea710`,
which sits immediately before the `0x0053b070` allocation in the static mode-`4` branch. The
immediate next runtime check is even more concrete than the helper hook, though: inspect the
state that `0x004ea710` should leave behind. The hook now logs the post-transition
`LoadScreen.win` singleton, its field `[+0x78]`, `0x006cec78`, the shell state's `[+0x0c]`
active-mode object field, and the startup selector. If `0x004ea710` really ran on the mode-`4`
branch, `[LoadScreen.win+0x78]` should no longer be zero after `shell_transition_mode` returns.
The latest run answered that directly: after transition return, `field_active_mode_object` is
still the `LoadScreen.win` singleton, `0x006cec78` is still null, `[LoadScreen.win+0x78]` is
still zero, and the startup selector is still `3`. So the current best read is that RT3 is
still parked in the plain `LoadScreen.win` state at transition return rather than having entered
the separate runtime-object path yet. That shifts the best next runtime boundary from “deeper
inside `shell_transition_mode`” to “what later active-mode service tick, if any, promotes the
load-screen object into the startup-dispatch path.” The next run now logs the first few
shell-state service ticks after auto-load is attempted with that same state tuple
(`0x006cec78`, `[shell_state+0x0c]`, `0x006d10b0`, `[LoadScreen.win+0x78]`, selector), so the
next question is very narrow: does one later service tick finally promote the plain
`LoadScreen.win` state into the startup-runtime object path, or does it stay frozen as-is? 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 say the candidate table is probably housed inside the setup payload family, but
not yet strong enough to name one setup-side consumer that reads the `0x6a70` header or the
fixed-width entries directly. 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]`,
`[profile+0x83]`, `[profile+0x87]`, and `[profile+0x97]`; 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 pure
zero-flag wrapper into `shell_map_file_world_bundle_coordinator`, while `0x004408d0` is the
sibling wrapper with flag triplet `(0, 1, 0)`. The exact user-facing command names for those two
wrappers are still open, but the dispatcher shape is no longer.
- 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.

View file

@ -0,0 +1,607 @@
## 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. The preview owner now has a tighter
internal request split too. The fixed selector-descriptor list at `[0x006cd8d8+0x8f28]` is built
through `multiplayer_preview_dataset_append_named_callback_descriptor` `0x00469930`, whose
current grounded owner is the fixed registration block
`multiplayer_preview_dataset_register_fixed_named_callback_descriptors_1_to_10` `0x00473a60`;
the variable-size named UI-request list at `[0x006cd8d8+0x8f2c]` is built through
`multiplayer_preview_dataset_append_named_ui_request_record` `0x00469a50`; and the staged
profile/path strings live at `[0x006cd8d8+0x8e10]` and `[0x006cd8d8+0x8f48]`, with the broader
action-`2` staging path now bounded by
`multiplayer_preview_dataset_stage_profile_text_selected_path_and_sync_session_state`
`0x0046a030`. The companion action-`3` commit path is tighter too:
`multiplayer_preview_dataset_match_named_field_slot_copy_profile_text_and_probe_selection`
`0x0046a110` now sits directly above the lazily initialized `0x80 * 0x11c` field-slot table at
`[0x006cd8d8+0x10]`, whose owner is
`multiplayer_preview_dataset_ensure_field_slot_table_initialized` `0x0046a1a0`. The shared
submitter above those lists is now explicit too:
`multiplayer_preview_dataset_submit_transport_request` `0x00469d30` accepts the callers
request-id, selector, payload pointer and length, flag word, and optional auxiliary pointer,
optionally allocates one sidecar object, and then either sends the request directly through the
session-event transport or takes the slower packed branch through `0x00553000/0x00552ff0` into
`0x00521dc0`. The constructor and teardown side are tighter now too.
`multiplayer_preview_dataset_construct_reset_globals_and_seed_callback_owners` `0x0046be80`
is the real reset owner above the action-`2/3` branches in
`multiplayer_dispatch_requested_action`: it re-enters the paired release body
`multiplayer_preview_dataset_release_owned_lists_transients_and_session_side_state`
`0x0046bc40`, clears the surrounding launcher globals, allocates the field-slot table and the
keyed request/descriptor owners, seeds the render target and staging buffers, and then calls
`multiplayer_preview_dataset_reset_global_callback_state_and_register_selector_handlers`
`0x00473280` plus the smaller fixed-name block
`multiplayer_preview_dataset_register_fixed_named_callback_descriptors_1_to_10` `0x00473a60`.
That broader registration pass `0x00473280` now bounds the selector-handler family too: it
zeroes the global scratch bands `0x006ce808..0x006ce994` and `0x006ce290`, seeds default
callback root `[0x006cd8d8+0x08]` with `0x0046f4a0`, and populates the keyed selector table at
`[0x006cd8d8+0x04]` through
`multiplayer_preview_dataset_register_selector_callback_if_absent` `0x00468b10` for selectors
`1..0x83`. The concrete callback bodies under that table are tighter now too. The small split is
real rather than guessed: `0x0046c390` is the direct control-`0x109` publish leaf through
`0x00469d30`; `0x0046c3c0` is the fixed-session-token plus launch-latch arm path; and the large
sibling `0x0046c420` is the real apply-owner for one incoming session payload slab copied into
`0x006cec74` before the shell-refresh follow-ons. A second callback cluster now owns live
session-entry flags instead of just relaying transport payloads: `0x0046c7c0` rewrites the
elapsed-pair dwords `[entry+0x54/+0x58]`, `0x0046c840` sets bit `0x08` in `[entry+0x5c]`,
`0x0046c870` toggles bit `0x01` from payload byte `[msg+0x08]`, `0x0046cf10` sets bit `0x04`,
and `0x0046cf70` is the list-wide clear of that same bit-`0x01` lane across every live session
entry. The broader callback `0x0046c8c0` sits above those single-field leaves and applies either
one or many embedded session-field update records before republishing the list. The pending-state
side is separated now too: `0x0046cce0`, `0x0046cd10`, `0x0046ce90`, and `0x0046d230` are the
current small latch/state owners under `0x006cd91c`, `0x006d1280`, `0x006d1284`, and
`0x006ce9c8`, while `0x0046cd30` and `0x0046ce10` are the paired current-session string/scalar
submit-and-apply handlers over `[entry+0x258c/+0x268c/+0x2690/+0x2694]`. The same callback table
also owns one small fixed transfer-progress family rooted at `0x006ce2e8`: `0x0046cfe0`
allocates the first free `0x5c` slot and optionally formats one label string, `0x0046d090`
appends progress payload into the matched slot while publishing one percent string through
`0x005387a0`, and `0x0046d1d0` clears the finished slot and frees that optional label. On the
release side, `0x0046bc40` now clearly owns the request-list,
descriptor-list, semicolon-name, pooled-span, render-target, and auxiliary-owner teardown, while
`multiplayer_shutdown_preview_dataset_session_object_and_global_helper` `0x0046c230` is the
final wrapper that additionally drops the live session object at `0x006d40d0` and the shared
helper at `0x00d973b4`. The small tuple staging below that family is bounded too:
`multiplayer_preview_dataset_touch_current_session_year_bucket_and_return_staged_tuple`
`0x0046b6d0` now owns the keyed session-bucket touch under `[session+0x2580]` for the staged
tuple at `[0x006cd8d8+0x9048]`, and the companion
`multiplayer_preview_dataset_stage_optional_selected_token_from_source_ptr` `0x0046d610` writes
the optional derived token into `[0x006cd8d8+0x8f38]`. The next service layer under the same
owner is tighter too: `multiplayer_preview_dataset_prune_session_buckets_below_current_year_key`
`0x0046b910` now bounds the older keyed-bucket drain under `[session+0x2580]`, while
`multiplayer_preview_dataset_service_current_session_buckets_and_publish_selector0x67`
`0x0046b9f0` owns the current-bucket walk, the local counters `[+0x987c/+0x9890]`, and the
later selector-`0x67` publish branch back through `0x00469d30`. Its local batch-publish child
`multiplayer_preview_dataset_publish_accumulated_selector0x71_record_batch` `0x00473bf0` is now
bounded too: it packages the fixed-width records at `0x006cd990`, prefixes `(0, count)`, sends
them as selector `0x71`, and then clears the live record count `0x006ce9a0`. The immediate local
helpers under that same band are now explicit too: `0x0046d260` is the fixed-record append
primitive that grows `0x006cd990` up to `0x80` entries, while `0x0046d240` is the paired release
of the optional selector-`0x71` staging blob at `0x006ce994`. The first fixed named-descriptor
block under `multiplayer_preview_dataset_register_fixed_named_callback_descriptors_1_to_10`
`0x00473a60` is tighter in the same way now. Its concrete roots `0x0046db10`, `0x0046db50`,
`0x0046db90`, `0x0046dbd0`, `0x0046dd10`, `0x0046de40`, `0x0046de80`, `0x0046e030`, and
`0x0046e250` all return small static records rooted at `0x006ce9dc..0x006cea3c`; each record
carries a leading tag byte plus the same derived world-year key from `[0x006cec78+0x0d]`, while
the heavier siblings add collection-backed totals from `0x0062be10`, `0x006ceb9c`, `0x0062b26c`,
`0x006cfca8`, or `0x006cfcbc` together with local overlay, geometry, or object-metric sample
paths. The immediate helper strip beneath that same first named block is tighter now too.
`0x0046d980` is the direct `0x006ceb9c` name-to-index lookup over `[entry+0x08]`, while
`0x0046d9e0` is the heavier companion that first resolves the current live session from
`0x006d40d0`, matches that session-side string against the same `0x006ceb9c` table, and returns
the matching entry index or `0xff`. Above those, `0x0046f8f0` resolves and validates one
`0x0062be10` entry, then keeps it only when its profile-index field `[entry+0x3b]` equals the
live-session-backed `0x006ceb9c` index from `0x0046d9e0`; and `0x0046f870` is the paired
rounded-delta leaf that takes one float plus one collection byte index and writes the rounded
positive/negative pair into metric ids `0x0f` and `0x0d` through `0x0042a040`. So this first
descriptor band is no longer just a bag of static record roots: it also owns a real local helper
family for `0x006ceb9c` name matching, live-session profile matching, and one narrow metric-pair
apply path beneath the higher callback bodies. The next selector layer above that helper strip is
tighter now too. Selector `0x12` is the small validate-or-error wrapper above selector `0x13`,
and selector `0x13` body `0x004706b0` resolves the same live-session company match, attempts the
placed-structure apply path through `0x004197e0`, `0x004134d0`, `0x0040eba0`, `0x0052eb90`, and
`0x0040ef10`, and otherwise falls back to a hashed selector-`0x6e` publish over the first
`0x1c` payload bytes. The same pattern appears again one pair later: selector `0x16` is the
thin validate-or-error wrapper above selector `0x17`, and selector `0x17` consumes a count plus
`0x33`-stride adjunct record band, resolves one live train-side entry under `0x006cfcbc`,
re-enters `0x004a77b0`, `0x0052e720`, `0x0040eba0`, `0x0052eb90`, and `0x0040ef10`, and again
falls back to hashed selector `0x6e` publish when the live apply path does not land. The later
status pair is bounded too: selector `0x19` is another thin wrapper, and selector `0x1a` either
derives a status code from `0x0046ed30` and current live-session name matching or treats the
four-byte payload directly as that status code before publishing localized status text
`0x0b7f/0x0b80`. One selector-pair above the metric leaf is now explicit too:
the earlier front edge of the same callback table is tighter now too. Selector `0x02` compares
staged profile text against the shell profile band at `[0x006cec7c+0x11]`, can advance the
requested-action fields `0x006d1280/0x006d1284`, can queue selector `0x53`, and on the success
path syncs the larger shell profile block rooted at `[0x006cec7c+0x44]`. The next small strip is
also grounded: selector `0x0a` clears `[world+0x19]`, seeds `[world+0x66ae]`, mirrors peer byte
`[peer+0x2690]` into named-profile byte `[entry+0x15c]`, refreshes `0x006ce98c`, and optionally
republishes one local status path. Selector `0x0b` is then the small token-staging wrapper above
selector `0x0c`, and selector `0x0c` itself forwards one signed byte pair into `0x00434680`
before adjusting dataset counter `0x006cd8e8`. The other small early leaves are now bounded too:
selector `0x0f` pops one node from the current session queue at `[session+0x64]`, publishes that
node through `0x00469d30`, and releases it; selector `0x10` looks one payload key up in the
session-side store `[session+0x60]` and forwards the result plus dataset string root
`[0x006cd8d8+0x8f48]` into `0x00521790`. One selector-pair above the metric leaf is now explicit too:
selector-`0x15` body `0x00470950` consumes the same compact `(float delta, company-index byte)`
payload shape, resolves the matching live-session company entry through `0x0046f8f0`, submits
selector `0x6b` through `0x00469d30`, and then immediately re-enters `0x0046f870` for the local
apply. The neighboring name-match lane is now explicit too:
selector-`0x61` body `0x00472700` scans `0x0062be10` for a company-name match against the caller
string at `[payload+0x08]` and then either submits selector `0x62` with the original payload or
falls back to the paired error-style `0x21` branch. The next registered band around selectors
`0x1c..0x5d` is tighter now too. Selector-adjacent helpers `0x00470ed0` and `0x00470fa0`
are the paired global preset passes beneath that strip: both walk the guarded named-profile
table `0x006ceb9c`, add opposite signed integer presets into qword field `[profile+0x154]`
through `0x00476050`, then walk `0x0062be10` and write opposite preset
scalars into metric id `0x0d` through `0x0042a040`. Above them, selectors `0x1d`, `0x1f`,
`0x21`, `0x23`, `0x25`, `0x27`, `0x29`, `0x2b`, `0x2d`, `0x2f`, `0x31`, `0x33`, `0x35`,
`0x37`, and `0x39` are now explicit token-staging forwarders into selectors `0x1e`, `0x20`,
`0x22`, `0x24`, `0x26`, `0x28`, `0x2a`, `0x2c`, `0x2e`, `0x30`, `0x32`, `0x34`, `0x36`,
`0x38`, and `0x3a`. The next live-train strip is also grounded: selectors `0x3b`, `0x3d`,
`0x3f`, `0x41`, and `0x43` resolve live train ids from `0x006cfcbc`, stage the current token,
and republish into selectors `0x3c`, `0x3e`, `0x40`, `0x42`, and `0x44`; selectors `0x3c`,
`0x3e`, and `0x40` then dispatch directly into `0x004abd70`, `0x004b2f00`, and `0x004b3000`,
while selector `0x42` is the heavier train-adjunct branch through `0x004b2b70`,
`0x004b3160`, `0x004b2c10`, `0x004a9460`, and `0x004ab980`. The prompt or bitmap cluster is
tighter too: selector `0x48` consumes one 12-byte record, either marks the current-session bit
directly or opens localized prompt `0x0b81` through `0x00469a50` and callback `0x004719c0`,
and then republishes selector `0x49`; selector `0x49` turns that 12-byte result into one keyed
bitset object and republishes selector `0x47`; selector `0x47` consumes the resulting ten-slot
masks and drops straight into `0x004eb230` plus
`shell_resolve_merger_vote_and_commit_outcome` `0x004ebd10`. The same pattern repeats one
size smaller for selectors `0x4c`, `0x4d`, and `0x4b`: selector `0x4c` consumes one 10-byte
record, either marks the current-session bit directly or opens localized prompt `0x0b82`
through `0x00469a50` and callback `0x00471d50`, selector `0x4d` folds that 10-byte result into
the keyed bitset object, and selector `0x4b` turns the resulting masks into one ten-dword cache
setter at `0x0050c4e0` rooted at `0x006d1a08` plus the paired outcome resolver
`0x0050c940`. The direct setter strip after that is explicit too: selector `0x4f` republishes
selector `0x6f` when the live session object exists and dataset
gate `0x006cd91c` is clear, selector `0x50` copies `[dataset+0x9058]` into `[dataset+0x9054]`,
selector `0x51` derives one small session-status code and either republishes selector `0x52` or
shell control `0x109`, selectors `0x55`, `0x56`, and `0x57` directly store dataset field
`0x9860`, a `0x006ceb9c` inline name, and guard field `[entry+0x1e1]`, selector `0x58` flushes
the deferred 16-slot named-profile clear queue `[dataset+0x9864..+0x9873]`, selector `0x59`
derives one roster or capacity status and republishes selector `0x5a`, selector `0x5b` is
another token-staging forwarder into selector `0x5c`, selector `0x5c` gates a
`0x00493960` dispatch plus optional local `0x0046f870` apply, and selector `0x5d` validates
one payload string before republishing selector `0x5e`. The next registered band around selectors
`0x5e..0x7d` is tighter too. Selector `0x5e` updates the named-profile side table
`0x006ceb9c`, mirrors the same string into the resolved live session object, and when the
session-side guard is active hashes that string back into `[session+0x48]` and dataset field
`[0x006cd8d8+0x8f48]`; selector `0x5f` then stages the current year-derived token and republishes
into selector `0x60`, whose body writes one guarded byte field into the same `0x006ceb9c` entry
family. The `0x62..0x64` strip forms the same kind of pair over `0x0062be10`: selector `0x62`
copies one fixed `0x32`-byte band into the matched company entry, selector `0x63` rejects
duplicate field-`0x37` values before forwarding, and selector `0x64` applies that same dword
field directly into the matched entry or one live fallback owner. The receive-side correction is
explicit now too: selector `0x6b` is the tiny local metric-apply wrapper
`0x00472db0 -> 0x0046f870`, selector `0x6c` is the separate train-record wrapper
`0x00472dc0 -> 0x0046d780`, selector `0x6d` formats localized status `0x0f4e` into the grounded
world outcome-text buffer `[world+0x4b47]`, and selector `0x6e` walks the current keyed bucket
under `[session+0x2580]` and marks the first matching companion record by payload hash. The
later wrappers are cleaner too: selectors `0x71`, `0x73`, `0x75`, `0x77`, `0x79`, `0x7b`, and
`0x7d` are all token-staging forwarders into selectors `0x72`, `0x74`, `0x76`, `0x78`, `0x7a`,
`0x7c`, and `0x7e`. Beneath them, selector `0x72` is the heavier counted live-world apply path
over `0x0062b2fc`, `0x0062b26c`, and `0x0062bae0`; selector `0x74` dispatches a resolved
company-entry id into `0x0062b26c` under the small latch `0x006ce9a8`; selectors `0x76`,
`0x7a`, and `0x7c` resolve one company-style entry and then tail into narrower local handlers;
and selector `0x78` is the broader projection-or-notify body over `0x0044b160`, `0x00538e00`,
and the local-session refresh strip. The next adjacent owner `0x0046e5c0` is broader but still
belongs to the same structural neighborhood: in mode `1` it serializes a dual-collection metric
blob from `0x0062be10` and `0x006ceb9c`, writing the two row counts into local header bytes and
then packing one `0x24`-stride band plus one `0x2c`-stride band behind a `0x10`-byte header; the
opposite branch starts validating and applying those packed metrics back into live entries. So
that first named block is no longer just a string-name registry; it already includes a real typed
static-record family beneath the preview dataset, and it now sits directly beside one broader
fixed-record callback-successor strip. Right after the selector-`0x71` batch publisher, the local
`0x005ce418` fixed-record family becomes explicit: `0x00473ce0` constructs one `0x187`-byte
record from two copied strings, shell-profile bytes `[0x006cec78+0x0d/+0x0f/+0x11]`, owner field
`[this+0x04]`, and monotonic sequence dword `[this+0x14]` seeded from `0x006cea48`;
`0x00473dc0`, `0x00473e70`, `0x00473ee0`, and `0x00473f30` are the max-sequence, min-sequence,
previous-sequence, and next-sequence queries over the same live collection; `0x00473e20` is the
boolean scan for any inactive record with a nonzero owner dword; and `0x00473f80` is the real
allocator or constructor owner, trimming the collection down to `0x19` entries, allocating one
live record through `0x00518900`, and then dispatching the heavier constructor. So the callback
registry now leads directly into a concrete preview-side fixed-record collection, not into
another anonymous transport helper band. The broader snapshot/apply owner still sits over the
same multiplayer collection state. 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 probe request id plus displayed version/build integer, 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 `ms` sample through `multiplayer_transport_enqueue_callback_slot24_record`. So the
publication boundary is explicit and the request-id ownership is explicit: `[entry+0x80]` now
reads as the averaged millisecond probe sample and `[entry+0x54]` as the displayed version/build
companion integer. The adjacent route-callback side is tighter too, but it is now kept separate:
the staged route-callback path at `0x5958e0` and the later compatibility gate at
`multiplayer_transport_route_binding_matches_route_callback_descriptor_tuple` `0x595d00` operate
on a compact GameSpy-style server or route descriptor family with a primary endpoint tuple at
`[descriptor+0x00]/[+0x04]`, an optional secondary endpoint tuple at `[descriptor+0x08]/[+0x0c]`,
string-key lookups such as `hostname` and `gamever`, and numeric-key lookups such as
`numplayers`, `numservers`, `numwaiting`, and `gsi_am_rating`. The route-binding side uses that
descriptor family's primary dword and host-order port plus the optional secondary tuple against
route-binding offsets `+0x54/+0x58` and route word `+0x30`. Current evidence still does not prove
that descriptor tuple is the same field family as the selector-view marker companion integer at
`[entry+0x54]`. The higher compact decode owners are tighter now too: `0x5907d0` is the
allocate-and-append lane for one self-consistent compact payload, while `0x590d00` is the keyed
upsert-by-primary-endpoint lane that reuses an existing descriptor when possible and then notifies
the owner callback. The route-callback-table runtime above that decode side is tighter now too:
`multiplayer_transport_route_callback_table_construct` `0x5905e0` seeds one transport-owned
table block, `multiplayer_transport_route_callback_table_release_decoded_schema_dictionary`
`0x5906f0` tears down the decoded schema dictionary rooted at `[this+0x08]`,
`multiplayer_route_callback_runtime_acquire_shared_string_copy` `0x590540` and
`multiplayer_route_callback_runtime_release_shared_string_copy` `0x5905a0` now bound the shared
string pool used by that decoded schema, and the higher bring-up owner `0x596090` now clearly
splits between `[transport+0xba4]` with owner callback `0x595a40`, the local field-cache family
`[transport+0x1724]` seeded through `0x5a08f0/0x595b60`, and `[transport+0x1164]` with owner
callback `0x595bc0`, while
`multiplayer_transport_route_callback_table_service_receive_decode_state_machine` `0x5908c0`
is the current live receive/decode state machine serviced by `0x591290` in table states `2/3`.
The callback-owner mode split above that runtime is now explicit too: append-notify `0x590370`
publishes mode `0`, compact upsert `0x590d00` publishes mode `1`, remove-notify `0x590430`
publishes mode `2`, and the live receive/decode path `0x5908c0` publishes modes `6`, `5`, and
`3`.
The route-handle lifecycle above that decode path is tighter now too: `0x590740` cleanly resets
one table's live route plus decode-side runtime without destroying the outer object; `0x5907a0`
is the broader destroy path that also releases the active descriptor collection; `0x590ed0`
opens the live route handle into `[this+0x4a0]`, stages the initial outbound request, and seeds
state `3` plus the staged receive buffer; `0x5911e0` is the state-`2/3` socket-service wrapper
that reads new bytes, grows the staged buffer, and re-enters `0x5908c0`; `0x5912c0` is the
one-shot send-with-reopen-retry helper; and `0x590ea0` is the shared disconnect publication and
reset tail. The recurring service helper `0x591290` is tighter too: it now first clears the
staged intrusive descriptor list through `0x590490` before entering the state-driven seed-or-
receive branch. The upstream owners are tighter too: `0x5962e0` is now the field-subscription
route-table opener above `[transport+0xba4]`, while `0x596530` is the `gsi_am_rating` reopen
path above `[transport+0x18bc]`. On that latter branch, `0x590dc0` is now bounded as the
state-`0` raw-endpoint seed pass over the live route handle, repeatedly pulling endpoint tuples
through `0x58bc7e` record type `0x1f3` before stamping descriptor flag byte `0x15` with `0x11`.
That makes the remaining source-flag meaning narrower too: current evidence now supports reading
byte-`0x15` bit `0x1` as a primary-endpoint-seed or endpoint-only marker. In the `gsi_am_rating`
dispatcher, clear-bit descriptors can take the richer direct transition lane, while set-bit
descriptors are staged through the queued enrichment path and still suppress that direct
transition even after the ready bit arrives.
The adjacent capacity-descriptor side is tighter too: `0x595bc0` now clearly publishes a
descriptor block from live descriptor properties `hostname`, `numwaiting`, `maxwaiting`,
`numservers`, and `numplayers` plus three carried sidecar scalars. That sidecar at
`[transport+0x1778]` is tighter now too: current evidence says it behaves as one cached pointer
into the transient work-record family at `[transport+0x1780]`, because every meaningful branch
in `0x595bc0` reads the same `+0x0c/+0x10/+0x18` metadata triplet and replay modes later consume
the pointer through `0x5933a0`. The negative result is stronger too: local text-side xrefs still
show no direct store to `[transport+0x1778]`, and a wider local sweep also failed to show any
obvious `lea`-based replay-band writer. The transient-request lifecycle tightens that further:
`0x593330/0x593370/0x593380/0x5934e0/0x5933a0` now fully bound `[transport+0x1780]`, `0x1784`,
and `0x1788` without ever touching `[transport+0x1778]`, and the neighboring active-opcode reset
helper `0x5929a0` is likewise scoped only to `[transport+0x17fc]`. A broader constructor and
teardown pass tightened that further too: `0x596090`, `0x5961b0`, and `0x5962e0` all touch the
neighboring replay-band fields without ever seeding `[transport+0x1778]`. A full-binary
literal-offset sweep tightens it further still: the only direct `0x1778` hit in `RT3.exe` is
the read in `0x595bc0`. One nearby ambiguity is now closed too: the
mode-`5` mirror path in `0x595a40` and `0x595e10` does not target `[transport+0x1778]`; it
writes `[transport+0x54]` and mirrors the same staged route companion dword only into queue-side
slot `[transport+0x1724+0x24]` through `0x005a0940`. So the sidecar writer remains upstream of this
leaf publisher. Mode `0` is now also tied more cleanly to the generic descriptor append-notify lane
at `0x590370`, while mode `2` stays outside this helper as the separate
remove-notify-and-stage path at `0x590430`. The opcode-`2` payload boundary is tighter too:
`0x592ae0` now grounds that payload
as a seven-dword block with an owned string slot at `+0x08`, so live mode supplies a populated
payload while modes `3` and `5` deliberately enqueue an all-zero payload and reuse only
the wrapper-side sidecar metadata. Those two modes are tighter now too: they are the live
receive-state owner callbacks emitted by `0x5911e0 -> 0x5908c0`, not loose generic replay
guesses. So those paths are better read as delayed metadata replays over one cached work record,
not over a separate anonymous cache blob. The neighboring
capacity-owner split is tighter now too: `0x595bc0` only stages descriptor records for modes
`0`, `3`, and `5`; the upstream route-callback-table owner still delivers modes `1`, `2`, and
`6`, but those are explicit no-ops in this capacity leaf. So the owner wiring itself is no
longer the open edge; only the upstream sidecar producer remains unresolved. The neighboring
work queue is tighter too: `0x593330/0x593370/0x593380` now bound `[transport+0x1780]` as the
construct/clear/destroy owner family, while `0x5933a0`, `0x5934e0`, and `0x593570` ground the
remove, allocate, and completion side over that same collection. The small sibling `0x593400` is
tighter too: it is a pure work-record uniqueness predicate over field `+0x0c`. Its caller is
tighter now too: `0x58d720` is an immediate-drain quiescence gate over one transport context id,
using `0x593400` for the queued work family at `[transport+0x1780]` and `0x592970` for the
active opcode-record collection at `[transport+0x17fc]`. The strongest current read is that
`0x5934c0` seeds that shared drain context id first, then the transport copies it into queued
work field `+0x0c` and active opcode-record field `+0x14` before the immediate-drain roots wait
on one shared disappearance test rather than on a vague settle loop. The currently grounded
roots are `0x58df20`, the neighboring formatted selector-text publish path at `0x58dfb0`, and
callback-table registration at `0x58e200`. The active-opcode side is tighter too: `0x5927b0`
now bounds the per-record service-and-retire path, `0x592800` the wider context-or-idle sweep,
`0x5929a0` the remove-by-opcode-type sweep, and `0x5929f0` the narrower opcode-`3`
field-snapshot removal keyed by the subscribed callback-pair payload. That also corrects
`0x595b80`, whose final cleanup is an active field-snapshot purge rather than a queued-work
drain. The adjacent route-callback descriptor-table lifecycle is tighter too: `0x590410` now
grounds `[table+0x5bc]` as the staged intrusive descriptor-list head, `0x590430` is the generic
remove-notify-and-stage lane, `0x590490` releases the staged list, and `0x5904d0` releases the
active descriptor collection before tearing that staged list down. That also makes the earlier
`0x5962e0` “release active descriptors” step explicit. The callback-table attach side now constrains the
same work-record metadata family a little further too: `0x593650` deliberately duplicates its
first caller metadata dword into both fields `+0x0c` and `+0x10`, while carrying the second
caller metadata dword in `+0x18`. The lower opcode wrappers are tighter now too: `0x592a40`
turned out to be the explicit opcode-`1` trigger wrapper whose constructor is a no-op and whose
active-side service is `0x5913c0`, while `0x592c40` is the real `0x08`-byte explicit opcode-`5`
binding leaf. The earlier opcode-`4` read was just the table-indexing mistake in `0x5928a0`:
selector `4` lands on the row at `0x5e2044`, not the row at `0x5e2034`. The producer side is tighter too:
bound-route requests, selector-text route requests, and the type-`9` text fastpath also stage
that same triplet through `0x5934e0`, and the fastpath shim `0x593d00` now gives the cleanest
proof of the callback split by only re-emitting the follow-on lane when `+0x10` is nonnull and
then forwarding `(+0x10, +0x18, +0x0c)` into `0x593170` as callback function, callback
companion, and trailing drain context. So the replay-side triplet is clearly a broader
transport callback-wrapper family, not one fixed route-only tuple. The nearby
field-subscription side is tighter too: `0x592b50` now clearly uses `[transport+0x1774]` as a
cached progress percentage under `[transport+0xba4]`, and `0x5962e0` seeds that percentage to
`1` just before the first immediate mode-`3` snapshot. The nearby route-callback-table
lifecycle is tighter now too: `0x596090` seeds `[transport+0xba0]` as the callback-plumbing
enable latch, clears staged payload slot `[transport+0xb50]`, and constructs the three owner
branches rooted at `[transport+0xba4]`, `[transport+0x1164]`, and `[transport+0x18bc]`;
`0x596210` is the recurring service sweep over those same three tables plus the local field
cache and queued-descriptor family; `0x596060` is the explicit `gsi_am_rating` reset; and
`0x596530` is the reopen-from-stored-label sibling above that same am-rating table. The
matching local cleanup is tighter too: `0x5962c0` is the explicit staged route-callback payload
clear on `[transport+0xb50]`, while `0x595ce0` now clearly resets only the capacity-descriptor
route callback table at `[transport+0x1164]`, not the field-subscription table at
`[transport+0xba4]`. The remaining gap on the capacity side is therefore narrower: the carried
sidecar fields themselves now read more cleanly as the cached callback-wrapper triplet reused
elsewhere (`drain context id +0x0c`, `callback fn +0x10`, `callback companion +0x18`), and the
negative result is stronger too: nearby replay-band fields `[transport+0x176c]`,
`[transport+0x1770]`, `[transport+0x1774]`, `[transport+0x177c]`, `[transport+0x1780]`, and
`[transport+0x1784]` all have direct local owners while `[transport+0x1778]` still appears only
as the single read in `0x595bc0`; even the broader callback-owner lifecycle now skips it while
seeding, servicing, resetting, reopening, or tearing down those neighboring tables and caches.
The constructor now closes that local search further: `0x58dc50` bulk-zeroes the full transport
body and still never writes a nonzero value into `[transport+0x1778]` before later explicit
neighbor initialization. The callback-binding owner stack now tightens that boundary too:
`0x5934e0` stages the shared work-record metadata triplet, `0x593650` binds it into the
callback-table worker path, and `0x593570` later consumes and republishes it, while
`[transport+0x1778]` still appears only as the borrowed sidecar read in `0x595bc0`. So this
edge is now locally closed, and the remaining producer looks like an upstream callback or worker
handoff rather than one missing ordinary field store in the local cluster. The adjacent staged-route
callback side is tighter too: `0x595860` is now bounded as the
submit-result handler beneath `0x5958e0`, and the old `[transport+0xac0]` ambiguity there is now
gone. That branch is using the already-grounded third selector-generation counter at `[0xac0]`
together with target `[0xb48]` to decide whether staged route-callback traffic can push the
multiplayer route-mode ladder from `2` into `3` and later `4`. The selector-view counter beneath
that gate is tighter now too: `0x594e30` counts slot-`2` entries whose flag dword carries bit
`0x20`, optionally filtered by the current transport name buffer. The selector-view mutation
family under that same lane is tighter too: `0x594a30` is now the direct keyed-store remover,
`0x594fb0` clears one selector-slot ownership pointer plus its slot-local flag dword and drops
the whole entry when no slots remain, `0x595010` rekeys one selector-view entry under a new name
while preserving the `0x40..` runtime band, and callback root `0x59f9c0` now reads as the
sibling lane that clears one named selector-view slot, publishes callback slot `18`, and may
still re-enter the route-mode setter from the same slot-`2` status and generation gates. The
neighboring callback roots are tighter now too: `0x5950a0` clears one selector slot from every
selector-view entry in the keyed store, `0x59fab0` is the rename or relabel sibling above
`0x595010`, `0x59faf0` updates one selector slot's fixed sample-text buffer and refreshes the
active selector object when present, and `0x59fb60` replaces one selector slot's name set,
requests the default profile-key bundle for that slot, and publishes callback slot `20`. Slot
`16` is tighter now too: current grounded caller `0x59f440` forwards the staged route-callback
payload handle from `[transport+0xb50]` through `0x592ea0` just before route mode `5`. The
last adjacent callback root in that block is tighter now too: `0x59fbd0` is the built-in
per-slot profile-key query sibling. It resolves the caller selector name into one slot index,
forwards the caller trio into `0x596b90`, and then publishes callback slot `28`; that lower
helper indexes one slot-specific built-in string pair from `[transport+0x189c]` and
`[transport+0x18ac]`, reuses the generic per-key handler `0x596970`, and only republishes slot
`28` when that lower query path succeeds.
The compact-header side is tighter now too: `0x58fe20` and `0x58ff20` now show that compact
payloads always carry the primary IPv4 dword and that header bit `0x10` only gates whether the
primary port word is inline or inherited from the owner default port. `0x58fe90` now validates
the `0x40` inline keyed-property vector against the owner schema, and `0x58fe50` validates the
signed-`0x80` trailing string-pair tail before decode. `0x58ff60` then grounds bit `0x02` as
the inline secondary IPv4 dword branch, bit `0x20` as the paired secondary-port word branch with
owner-port fallback, bit `0x08` as one still-unresolved auxiliary dword stored at
`[descriptor+0x10]`, bit `0x40` as one inline keyed-property vector decoded through the
property-store writers, and signed bit `0x80` as one trailing string-pair tail. The
descriptor-state side is tighter now too: the shared queue helper at
`0x005a09a0` stamps pending state `0x4` for the local field-cache family `[transport+0x1724]`
and pending state `0x8` for the `gsi_am_rating` queued-descriptor family `[transport+0x1e7c]`,
while later service through `0x005a0c80` promotes those pending tags into ready bits `0x1` and
`0x2` in descriptor byte `[entry+0x14]`. That makes the current transport-side tests cleaner:
`0x58d1c0` is the field-cache ready gate, `0x58d1d0` is the `gsi_am_rating` queued-descriptor
ready gate, and `0x58d230` is the remaining flag-byte split between direct primary-endpoint
handling at `[transport+0x18bc]` and the queued path at `[transport+0x1e7c]`. That byte-`0x14`
story is no longer queue-only either: `0x58ff60` can also OR in bit `0x1` after the inline
keyed-property vector and bit `0x2` after the signed string-pair tail. The flag-byte split is no
longer purely behavioral either: current evidence now says byte `[descriptor+0x15]` bit `0x1` is
a source-side descriptor header bit, explicitly seeded during the primary-endpoint table refresh
around `0x590dc0` and preserved by the compact descriptor decode path at `0x58ff60`, rather than
a queue-generated runtime state. The `gsi_am_rating` dispatcher side is tighter too: that same
bit no longer just looks like a direct-versus-queued routing split, because `0x595e10` also uses
it to suppress the direct `0x595dc0` transition even after queued ready bit `0x2` is present.
The descriptor body is tighter too: `[descriptor+0x20]` is now the intrusive next-link used by
the transport-owned primary-endpoint list headed at `[table+0x5bc]`, and `[descriptor+0x1c]` is
now the special numeric scalar behind the current `queryid`/`ping` fallback pair. The compact-only
auxiliary dword at `[descriptor+0x10]` is tighter in a negative way too: local xref scans now
only show it being preserved by later generic helpers like
`generic_record_0x1c_deep_copy_with_owned_string_at_0x08` `0x591410` and the adjacent
callback-marshaling wrappers `0x591480` and `0x591510`, not read through any dedicated semantic
accessor yet. The route-event dispatcher side is tighter too: the mode-`5` tails in both
callback families do not copy a descriptor-local field but instead mirror the transport-staged
companion dword at `[this+0x490]` into `[this+0x54]` and queue-side slot `[this+0x1724+0x24]`. The
`gsi_am_rating` maintenance lane is tighter now too: after pruning failed descriptors it sorts
the surviving primary-endpoint table through `0x590310` in mode `1` with key `gsi_am_rating`,
then selects the new head through `0x590480` before re-entering the route-transition path. 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 core `X%sX` marker text from `[entry+0x50]` through
`multiplayer_transport_format_selector_view_probe_marker_core`, formats one `PNG %s %d` line
around that marker and the entry-local averaged millisecond 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: `multiplayer_transport_set_route_mode`
latches the requested small mode at `[this+0x18b8]`, then uses mode `0` for the direct-versus
queued `gsi_am_rating` split, mode `1` for the ready-bit plus queued fallback, mode `2` for
pending-descriptor cleanup, mode `3` for the empty-table fallback, mode `4` for deferred
route-status recovery, and mode `5` for copying the staged route companion dword into `[this+0x54]`
and queue-side slot `[this+0x1724+0x24]`. The current grounded mode transitions still 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]`. The surrounding status-route callback vector is tighter now too:
`0x005970e0` publishes either the active selector text or the averaged probe sample at
`[entry+0x80]` and otherwise falls back to owner callback `[transport+0x17e0]`;
`0x00597180` is a straight owner-forwarding lane through `[transport+0x17e4]`;
`0x005971b0` seeds the local status-control id list and can then notify owner callback
`[transport+0x17e8]`; and `0x00597270` returns the third selector-slot generation counter
`[transport+0xac0]` on its bounded local branch before falling back to owner callback
`[transport+0x17ec]`. 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.

View file

@ -0,0 +1,9 @@
## 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.

View file

@ -0,0 +1,32 @@
## 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.

View file

@ -0,0 +1,20 @@
## 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.