Selected vehicle and inventory identity
The hangar's selected vehicle is an inventory object, not a battle entity. Persist its compact descriptor or other stable semantic key when the feature must survive UI reconstruction.
Prebattle systems
Platoons, event queues, and other prebattle contexts can use different controllers and identifiers. Treat joining, updating, leaving, and disconnecting as separate transitions.
Account-session state
Data intended to survive entering a battle should remain owned by the account session. Clear it on actual disconnect, account replacement, explicit completion, or user cancellation.
Do not treat an early cache miss as deletion
During reconnects and fast transitions, the account GUI can appear before the relevant cache has finished filling. A lookup that returns nothing at that moment means not ready yet, not necessarily the item no longer exists.
- Keep previously confirmed pending state while the account and cache are still being restored.
- Retry for a short, bounded period and stop if the account, lobby, or feature context changes.
- Validate inventory only when an action actually requires it, such as activating or replacing an item.
- Do not let a notification refresh or an early lobby render delete unresolved state.
- Clear preserved state only after a confirmed account change, explicit cancellation, or an authoritative lookup proves it is no longer valid.
Empty before synchronization is an unknown result. Empty after the correct synchronization event can be treated as real.
Account diffs and cache notification
The Account synchronizes its owned components and caches before emitting broad player update events. Listeners normally see new cache state; the raw diff is not an old-state snapshot. Keep an old value beforehand when a comparison is required.
Initial/full synchronization is special. GUI personality updates selected lobby owners but skips
normal items/events-cache refresh and ClientUpdateManager dispatch. Initialize from
the authoritative cache after its own readiness event, then use update-manager paths for later
changes.
- Normal account diffs await items and events caches, update lobby context, dispatch path callbacks, and only then update the offers provider.
- Broad player events are safe fan-out, not an awaited transaction across async listeners.
- Path callbacks receive a matched raw fragment, not the reconstructed cache value; replacement and deletion markers have distinct meaning.
- Handlers are strongly retained. Default owner removal is deferred and safer during active dispatch than force removal from a snapshot.
Account commands and streamed responses
Typed Account command helpers allocate a request ID, retain a callback strongly, and dispatch
through a matching base-proxy signature. The Python 2 proxy accepts byte str values,
not arbitrary Unicode, and unsupported argument shapes fail before sending.
- The native layer has no public per-command timeout or cancellation. Validate account readiness and give every local busy flag an independent teardown path.
- Normal and extended callbacks are one-shot; streamed commands add a second completion phase under the same request ID. Transport completion does not prove decompression or payload validity.
- On account exit, pending command callbacks receive a terminal non-player result and dynamic stream callbacks receive failure. They must clear local state and must not start replacements.
- A local async timeout does not remove the native callback or stop the server action.
Local teardown and remote outcome are separate. In the current
Account implementation,
__cancelCommands() notifies pending callbacks and clears their maps;
it sends no cancellation or undo request. A terminal RES_NON_PLAYER
therefore does not establish server rejection or rollback. Reconcile a mutation
with authoritative state when a valid account context is available before considering a retry.
Callback lookup also comes after response preprocessing:
onCmdResponseExt() unpickles its payload and calls
shop.synchronize() for RES_SHOP_DESYNC before popping the callback.
An unmatched request ID therefore does not skip those earlier operations.
Shop synchronization has its own guards,
including an ignore flag set on account exit, so reaching that method does not
prove another request was sent. These are version-sensitive Python control-flow
findings; delivery to an old Account after transition was not runtime-tested.
Source excerpt (version-sensitive): the relevant body of
PlayerAccount.onCmdResponseExt() shows the boundary directly:
ext = pickle.loads(ext)
if resultID == AccountCommands.RES_SHOP_DESYNC:
self.shop.synchronize(ext.get('shopRev', None))
callback = self.__onCmdResponse.pop(requestID, None)
if callback is not None:
callback(requestID, resultID, errorStr, ext)
The presence check protects callback delivery, while decoding and the synchronization entry point precede it. Inspect the linked owner and its guards before reusing this flow; this excerpt is not a replacement command handler.