Lobby routes and view ownership

The modern lobby state machine owns parallel sub-view and top-view route trees. Calling goTo() submits a navigation event through the GUI bus. An idle machine can process it immediately; an active transition queues navigation. Calling it does not establish destination readiness. Guard and priority rules select one transition, and a visible route is only the highest visible leaf—not the complete set of entered parallel states.

  • Recorded back navigation stores explicit state and in-memory parameter records. Current event parameters are not automatically durable history.
  • Cross-layer transitions cannot be recorded as normal back-stack entries.
  • View-bearing states track their Wulf or Scaleform windows. External destruction navigates the state back when still entered; normal exit queues destruction for a related or fallback parent-readiness boundary.
  • Wulf router parameters cross JSON. Keep them compact and serializable, then resolve live services and objects on the Python side.

Parallel layers and transition selection

The current lobby machine keeps normal sub-views and higher overlay layers entered in parallel. Events are considered from entered leaves through their ancestors; one recorded navigation transition wins, otherwise priority and cross-subtree rules select the route. A broad guard can therefore redirect unrelated navigation and must be narrowly scoped.

Recorded history and view lifetime

Back navigation stores explicit state and parameter records, not arbitrary visible windows. States must serialize every value needed for reconstruction and compare semantic parameters when cycle cleanup requires it. When a tracked window closes externally, its state navigates back when still entered without killing the already-closing view; ordinary exits queue destruction until a related or fallback window is ready. With no such view, the pending keys remain queued.

History parameters can retain shared objects

Current public source; version-sensitive. The recorded-state owner combines ancestor and child serializeParams() values into a new outer dictionary, with child keys taking precedence. It does not recursively copy the values. Explicitly supplied parameter mappings, nested data and live context objects can remain shared; copying the returned stack list does not copy its parameter dictionaries.

Treat client navigation parameters as borrowed context. Mutating them can affect later back navigation or cycle comparisons. For mod-owned history, copy the specific plain fields that must retain their earlier values; do not blindly deep-copy live client objects. The router's separate JSON boundary does not turn the lobby's in-memory history into an immutable or persistent snapshot.

A destination ID in a pre-navigation hook describes a request. A guard transition can redirect that request before its intended route is entered; transition priority and parallel-layer selection still apply. Release resources before entry where required, but reconcile the eventual entered route rather than assuming the requested destination became active.

The framework's state entry and exit methods change the entered flag and call their hooks without awaiting a returned future. An asynchronous hook can therefore remain pending after the framework transition proceeds. Own its completion, failure and cancellation separately, reject stale owner generations, and avoid equating isEntered() with completed asynchronous work. These source paths do not establish actual dialog timing or a live teardown defect.

Route entry, reused views and readiness

Current public source; version-sensitive. The machine drains queued navigation before publishing its visible route. That signal describes the resulting route, not one completion notification per request, and does not establish frontend or feature-data readiness. A destination can reuse a view that already exists: the base GuiImplViewLobbyState focuses that view rather than loading it again or applying the new navigation parameters to it.

Give a reusable panel an explicit way to adopt its new context. Check the current route and view identity before applying delayed data, then wait separately for the owning model or cache. A constructor-only refresh can leave an old selection visible after re-entry. For frontend and retained-window boundaries, see Gameface Lifecycle & Cleanup.

Deferred cleanup must recheck ownership

The client records outgoing view keys, waits for a related or fallback parent window's isReady/onReady boundary, then checks which keys still belong to entered states before destroying windows. An outgoing window can therefore remain alive after its route exits. Window existence is not permission to keep updating that route.

Stop route-owned updates when their context ends; let the state machine manage its own windows. For mod-owned deferred work, retain exact subscription and callback identities, invalidate the old request generation on replacement or teardown, and recheck ownership when the wait completes. Detach one-shot handlers even if their work raises, and release multiple handlers from a stable snapshot rather than mutating the list being traversed. External window destruction follows a separate back-navigation path, so route cleanup and view disposal should tolerate either order.

Inspect the current route producer, view reuse and external-close handling, outgoing-view ownership checks and parent-window readiness helper. These Python callers establish ordering and admission checks, not native rendering timing or successful recovery during every runtime transition.

Common mistakes

  • Reading inventory on account entry before initial synchronization.
  • Assuming the selected vehicle exists while another lobby screen is loading.
  • Using hangar view lifetime as the lifetime of account settings.
  • Keeping prebattle identifiers after leaving the room or queue.
  • Writing settings on every high-frequency lobby update instead of batching changes.

Verification trail

Trace the account lifecycle event, cache synchronization completion, hangar controller, and the specific prebattle implementation. Verify which callbacks can fire during reconnect or rapid screen changes.