# Multiplayer

Primary atlas source:

- [multiplayer-session-and-transport-flow.md](/home/jan/projects/rrt/docs/control-loop-atlas/multiplayer-session-and-transport-flow.md)

Current grounded owners:

- `multiplayer_window_init_globals`
- `multiplayer_window_service_loop`
- `multiplayer_register_session_event_callbacks`
- `multiplayer_dispatch_requested_action`
- `multiplayer_preview_dataset_service_frame`
- `multiplayer_transport_service_frame`
- `multiplayer_transport_service_worker_once`
- `multiplayer_transport_service_route_callback_tables`
- `multiplayer_transport_service_status_and_live_routes`

Current bounded state blocks:

- session globals at `0x006d40d0`
- active session-event transport at `0x006cd970`
- preview dataset at `0x006cd8d8`
- Multiplayer window backing block at `0x006d1270`
- selector-view store rooted at `[transport+0xab4]`

What this note is for:

- Multiplayer window and preview-dataset ownership
- Session-event transport routing
- GameSpy-facing callback and live-route semantics
- Selector-view refresh, retry, and probe state

Latest local closure:

- The remaining multiplayer branch is narrower now:
  the status-route callback vector is bounded through selector-text or averaged-sample publication,
  control-id-list seeding, scalar-query forwarding, and the validated cookie or extended-payload
  callbacks. The selector-view sidecar is tighter too: `[entry+0x80]` now reads as the averaged
  millisecond probe sample and `[entry+0x54]` as the displayed version/build companion integer from
  the local `X...X|%d` marker family. Separately, the route-callback side now has its own compact
  GameSpy-style server or route descriptor family with primary and optional secondary endpoint
  tuples plus keyed fields like `hostname`, `gamever`, `numplayers`, and `numservers`, feeding the
  route-binding compatibility gate. The compact decode owners are tighter too: `0x5907d0` is now
  the allocate-and-append lane for self-consistent compact payloads, while `0x590d00` is the keyed
  upsert-by-primary-endpoint lane that reuses an existing descriptor when one already matches and
  then notifies the transport owner callback. The route-callback-table runtime above that decode
  path is tighter too: `0x5905e0` now constructs one transport-owned callback-table block,
  `0x5906f0` tears down the decoded schema dictionary rooted at `[this+0x08]`, `0x590540/0x5905a0`
  acquire and release refcounted shared schema strings through the global pool, and `0x5908c0`
  now reads as the live receive/decode state machine serviced by `0x591290` in table states `2/3`.
  The owner-callback mode split above that runtime is tighter now too: mode `0` comes from the
  generic append-notify lane `0x590370`, mode `1` from compact upsert `0x590d00`, mode `2` from
  generic remove-notify `0x590430`, and modes `6/5/3` from the receive/decode state machine
  `0x5908c0`.
  The higher transport bring-up split is tighter too: `0x596090` now clearly constructs
  `[transport+0xba4]` through `0x5905e0` with owner callback `0x595a40`, then seeds the local
  field-cache family `[transport+0x1724]` through `0x5a08f0` with helper `0x595b60`, and then
  constructs `[transport+0x1164]` through the same `0x5905e0` path but with owner callback
  `0x595bc0`.
  The live-route lifecycle above it is tighter too: `0x590740` now 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 route
  handle into `[this+0x4a0]`, stages the initial outbound request, and seeds state `3` plus the
  receive buffer, `0x5911e0` is the state-`2/3` socket-service wrapper above `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 openers are tighter now too: `0x5962e0` is the
  field-subscription route-table owner 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 flag-bit question much narrower too: current
  evidence now supports reading byte-`0x15` bit `0x1` as a primary-endpoint-seed or endpoint-only
  source marker. In the `gsi_am_rating` dispatcher, clear-bit descriptors can take the richer
  direct transition lane, while set-bit descriptors are pushed through the queued enrichment path
  and later still suppress that direct transition even when the ready bit is present.
  The adjacent capacity-descriptor side is tighter too: `0x595bc0` no longer reads as a vague
  progress helper. Mode `0` is now clearly the live publish lane, and it lines up with the generic
  descriptor append-notify owner callback at `0x590370`: it samples `hostname`,
  `numwaiting`, `maxwaiting`, `numservers`, and `numplayers` from the current descriptor before
  publishing an opcode-`2` descriptor block, while still carrying three cached side scalars from
  the local capacity sidecar at `[transport+0x1778]`. That sidecar 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 the 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 sweep also failed to show any obvious `lea`-based
  replay-band writer. The owned request lifecycle now tightens that further too:
  `0x593330/0x593370/0x593380/0x5934e0/0x5933a0` fully own `[transport+0x1780]`, `0x1784`, and
  `0x1788`, while still leaving `[transport+0x1778]` outside that family; the neighboring active
  opcode reset path `0x5929a0` also only targets `[transport+0x17fc]`. Constructor and teardown
  passes around `0x596090/0x5961b0/0x5962e0` tighten that negative result further: those owners
  seed or clear the neighboring replay-band fields while still leaving `[transport+0x1778]`
  untouched. 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 seed `[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 capacity publisher. The
  payload split is tighter too:
  `0x592ae0` now grounds opcode `2` as a seven-dword descriptor payload 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 sidecar metadata in the wrapper. Those two modes
  are tighter now too: they are not generic replay guesses, they are the live receive-state owner
  callbacks emitted by `0x5911e0 -> 0x5908c0`. So they are best read as delayed metadata replays
  over one cached work record, not over a standalone custom cache object. The capacity owner split
  itself is tighter now too: `0x595bc0` only does real work for modes
  `0`, `3`, and `5`; the upstream table still delivers modes `1`, `2`, and `6`, but those are
  explicit no-ops in the capacity leaf. So the callback-owner edge is now effectively closed: the
  route-callback table feeds the capacity publisher only through the live append lane and the two
  replay lanes, while the remaining missing piece is still the upstream sidecar producer, not the
  owner-mode wiring. The producer side is tighter now too: callback-table attach,
  bound-route requests, selector-text route requests, and the type-`9` text fastpath all stage
  that same `+0x0c/+0x10/+0x18` triplet through `0x5934e0`, so the capacity replay sidecar is
  clearly reusing one broader transport work-record family. The generic owner-callback split above that family is tighter now
  too: `0x590370` is the shared append-notify lane in mode `0`, while `0x590430` is the distinct
  remove-notify-and-stage lane in mode `2`. So `0x595bc0` only owns the live append-style publish
  path plus delayed replay modes `3/5`, not the remove path. The neighboring transient work queue
  is tighter too:
  `0x593330/0x593370/0x593380` now bound `[transport+0x1780]` as a real construct/clear/destroy
  collection owner, while `0x5933a0`, `0x5934e0`, and `0x593570` now ground the remove, allocate,
  and completion side over that same family. The small sibling `0x593400` is tighter too: it is a
  pure uniqueness predicate over work-record 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 one vague “drain until something settles” loop. The
  currently grounded roots are `0x58df20`, the neighboring formatted selector-text publish path at
  `0x58dfb0`, and callback-table registration at `0x58e200`. The callback-table attach side now
  has a tighter active-opcode owner stack too: `0x5927b0` services and retires one active opcode
  record, `0x592800` performs the broader context-or-idle retirement sweep, `0x5929a0` removes
  active records by opcode type, and `0x5929f0` removes only opcode-`3` field-snapshot records
  keyed by the subscribed callback-pair payload. That also corrects `0x595b80`: its final cleanup
  is not a queued field-snapshot drain, but an active opcode-type-`3` purge beneath the field
  cache reset.
  The adjacent route-callback descriptor-table lifecycle is tighter too: `0x590410` now grounds
  `[table+0x5bc]` as a real staged intrusive descriptor-list head, `0x590430` is the generic
  remove-notify-and-stage path, `0x590490` releases that staged list, and `0x5904d0` releases the
  active descriptor collection before tearing the staged list down. That also tightens
  `0x5962e0`: its earlier “release active descriptors” step is now explicitly this same
  `0x5904d0` family, not a vague collection clear.
  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 work fields `+0x0c` and `+0x10`, while
  carrying the second caller metadata dword in `+0x18`. The lower opcode wrappers are tighter now
  too: `0x592a40` turned out not to be the real 0x08-byte binding leaf at all, but the explicit
  opcode-`1` trigger wrapper whose constructor is a no-op and whose active-side service is
  `0x5913c0`. The real lower binding leaf is `0x592c40`, which builds the local `0x08`-byte
  payload later deep-copied by the explicit opcode-`5` family `0x591540/0x591570/0x591580`. The
  earlier opcode-`4` reading was just the table-indexing mistake: `0x5928a0` multiplies the pushed
  selector by `0x10`, so selector `4` lands on the row at `0x5e2044`, not the row at `0x5e2034`.
  So
  the replay-side triplet is still a broader transport callback-wrapper family, not one fixed
  route-only tuple. The type-`9` text fastpath confirms the same split from the other side too:
  `0x593d00` only emits the follow-on callback lane when work field `+0x10` is nonnull, and then
  forwards `(+0x10, +0x18, +0x0c)` into `0x593170` as callback function, callback companion, and
  trailing drain context.
  The nearby field-subscription side is tighter too: `0x592b50` now clearly uses
  `[transport+0x1774]` as a cached progress percentage under the `[transport+0xba4]` callback
  table, 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 families; `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 only meaningful gap left on the
  capacity side is no longer a local writer search. The carried
  sidecar fields are no longer anonymous: current evidence now says they are just the same cached
  callback-wrapper triplet reused by other work-record families, namely drain context id `+0x0c`,
  callback function `+0x10`, and callback companion `+0x18`. The negative result is stronger now
  too: the neighboring replay-band fields `[transport+0x176c]`, `[transport+0x1770]`,
  `[transport+0x1774]`, `[transport+0x177c]`, `[transport+0x1780]`, and `[transport+0x1784]` all
  have direct local lifecycle owners, but `[transport+0x1778]` still only appears as the single
  read in `0x595bc0`; even the broader callback-owner lifecycle now skips it while touching those
  neighbors, including bring-up `0x596090`, recurring service `0x596210`, am-rating reset/reopen
  `0x596060/0x596530`, teardown `0x5961b0`, and field-subscription open `0x5962e0`. The
  constructor now closes that negative result further: `0x58dc50` bulk-zeroes the full transport
  body and still never seeds `[transport+0x1778]` before later explicit neighbor initialization.
  So this edge is now locally closed: no writer is grounded anywhere in `RT3.exe`, and the
  remaining staging path is best read as an upstream callback or worker handoff rather than one
  missing ordinary field store in the local text cluster. The callback-binding family at
  `0x5934e0 -> 0x593650 -> 0x58f2f0` now gives the cleanest local boundary for that claim:
  RT3 stages and later consumes the shared work-record metadata triplet, but the sidecar itself
  still appears only as a borrowed cached pointer at `0x595bc0`, never as a locally seeded
  replay-band field.
  One adjacent staged-route callback is
  tighter now too: `0x595860` is the submit-result handler below
  `0x5958e0`, using the already-grounded third selector-generation counter at `[transport+0xac0]`
  plus the target at `[transport+0xb48]` to choose whether staged route-callback traffic can
  advance the route-mode state machine from mode `2` into `3` and later `4`. The counter beneath
  that decision is tighter too: `0x594e30` now reads as a selector-view entry counter for slot `2`
  flag bit `0x20`, optionally filtered by the current transport name buffer. The selector-view
  mutation side beneath that callback family 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` is now
  bounded as the lane that clears one named selector-view slot, publishes callback slot `18`, and
  can still re-enter the route-mode setter from the same slot-`2` state and generation gates. The
  next adjacent 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 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. The variable tails are tighter too:
  `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 inline 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 too: queue insertion now cleanly splits
  pending tags `0x4/0x8` from serviced-ready bits `0x1/0x2`, and the `gsi_am_rating` lane now
  separates direct primary-endpoint handling from queued-descriptor handling. Current evidence now
  also says byte `[descriptor+0x14]` is not queue-only: `0x58ff60` can also OR in bit `0x1` after
  the inline keyed-property vector and bit `0x2` after the signed string-pair tail. Byte
  `[descriptor+0x15]` bit `0x1` is source-side too, not queue-generated: it is explicitly seeded
  by the primary-endpoint refresh path and preserved by the compact descriptor decode path before
  the transport later uses it in the `gsi_am_rating` lane both to choose direct-vs-queued handling
  and to suppress the direct `0x595dc0` transition even after 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, 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 different way: a local xref scan now shows it being preserved
  by later generic preservation helpers such as
  `generic_record_0x1c_deep_copy_with_owned_string_at_0x08` `0x591410` and the callback-marshaling
  wrappers `0x591480` and `0x591510`, but still no dedicated semantic reader has been recovered.
  So the main remaining uncertainty is the exact higher-level meaning of header bit
  `[descriptor+0x15] & 0x1`, plus whether `[descriptor+0x10]` ever matters outside that
  preservation path. The route-mode setter is clearer too: `0x595650` now reads as a compact
  state machine over the transport-owned route tables, where mode `0` splits the `gsi_am_rating`
  branch into direct primary-endpoint handling versus queued insertion, mode `1` adds the ready-bit
  gate and the same queued fallback, mode `2` removes pending descriptors from the queued family,
  mode `3` forces mode `2` when the primary-endpoint table is empty, mode `4` updates the deferred
  route-status state around `[this+0x1ed4]` and `[this+0x1ed8]`, and mode `5` copies the staged
  route companion dword at `[this+0x490]` into `[this+0x54]` while mirroring that value into
  queue-side slot `[this+0x1724+0x24]` through `0x005a0940`. The route-event dispatcher side is cleaner too:
  the mode-`5` tails in both callback families do not copy a descriptor-local field. They mirror the
  transport-staged companion dword at `[this+0x490]` into `[this+0x54]` and that same queue-side
  slot instead, not into `[this+0x1778]`. The `gsi_am_rating` maintenance lane is tighter now too: it sorts the
  primary-endpoint descriptor table through `0x590310` in mode `1` with key `gsi_am_rating`, then
  selects the new head through `0x590480` rather than treating the table as pure insertion order.
