The garage can already be visible while some account data is still loading. A battle can exist before every entity or UI controller is ready. Lifecycle handling means waiting for the specific object you need and cleaning up when that object disappears.

Client context lifecycle

What you can observe in the client

  • The mod file loads during startup, before the player reaches the garage.
  • The garage may appear before inventory or dossier caches are populated.
  • Entering battle destroys garage views and creates battle-side objects.
  • Leaving battle creates a new garage view rather than restoring the previous instance.
  • Reconnects and replay seeks can repeat parts of this sequence in a different order.

Import is discovery, not readiness

Top-level module code can run before the account, avatar, arena, dependency container, or target view exists. Keep import-time behavior minimal, make init() idempotent, and use fini() to cancel callbacks and release only owned resources.

Import is setup time

  • Register constants, create lightweight controllers, and prepare idempotent installation functions.
  • Do not assume the player object, GUI loader, cache, dependency manager, or mode package is available.
  • Keep optional imports local so one missing feature does not stop the complete file from loading.
  • Prefer explicit init() work over irreversible import-time side effects.

Anchor work to the correct event

Need Useful readiness boundary What can still be missing
Lobby feature Account GUI and lobby-view lifecycle The final main window or target view may still be loading.
Battle feature Avatar and arena lifecycle The arena may not yet be in the active battle period.
Inventory or dossier data Cache synchronization event Import-time cache objects may exist but contain stale data.
View augmentation Target view loaded event The model or DOM may not be ready during construction.
Mode-specific integration Feature controller reports active The package can exist while the current season or realm keeps it inactive.

Common player lifecycle anchors

The shared player event hub exposes useful boundaries. Names and argument shapes remain version-sensitive, so inspect the target client before depending on them.

Event family Typical use
onAccountShowGUI Start lobby UI work after the account GUI begins loading.
onAccountBecomePlayer / onAccountBecomeNonPlayer Track transitions into and out of the lobby-side player entity.
onAvatarBecomePlayer / onAvatarBecomeNonPlayer Create and remove battle-avatar behavior.
onArenaPeriodChange Gate behavior on countdown, active battle, or post-battle periods.
onPrebattleJoined Refresh prebattle-specific state after joining a room or queue context.
onClientUpdated React to synchronized account data diffs without polling every frame.
onDisconnected Clear account-session ownership and cancel work that must not survive a disconnect.

Player events feed a second gameplay state machine

Account and Avatar callbacks are not the complete GUI lifecycle. The current client adapts global player events into a hierarchical gameplay state machine that separately tracks account entry, account GUI creation, avatar entry, arena information, battle GUI creation, arena loading, replay transitions, and exit.

ACCOUNT_ENTERING -> ACCOUNT_SHOW_GUI -> ACCOUNT_EXITING

AVATAR_ENTERING -> AVATAR_ARENA_INFO -> AVATAR_SHOW_GUI -> AVATAR_ARENA_LOADED
                                  \---------------------> AVATAR_ARENA_LOADED
                                                          -> AVATAR_EXITING

The arena-information state can proceed directly to arena-loaded or pass through GUI creation, so observers must not assume one fixed intermediate route. The app loader maps these states to lobby and battle applications, loading views, GUI-space changes, and battle-page display.

  • onAvatarBecomePlayer establishes battle ownership; it does not prove that the battle application or page is populated.
  • A feature that needs a populated battle page should bind to that page or a later verified gameplay-state boundary.
  • Replay entry, rewind, finish, and exit add branches that ordinary live-battle teardown does not cover.
  • Private state IDs and observer mappings are build-dependent; trace their current producers before using them as hook points.

Bounded readiness retries

A short retry is appropriate when an event selects the intended context but a service or view settles slightly later. It needs an attempt limit and a context token.

def wait_for_view(token, attempts_left=20):
    global _callback_id
    _callback_id = None

    if token != _context_token or not _started:
        return

    view = resolve_target_view()
    if view is not None and view_is_loaded(view):
        attach_feature(view)
        return

    if attempts_left <= 0:
        logger.warning('Target view did not become ready')
        return

    _callback_id = BigWorld.callback(
        0.1,
        lambda: wait_for_view(token, attempts_left - 1)
    )

Context transitions are not always logout

  • Account-to-avatar and avatar-to-account transitions are normal battle changes.
  • Session state intended to survive a battle should clear on disconnect, account change, completion, or explicit cancellation—not simply when the account temporarily stops being the active player entity.
  • Per-arena state should reset by arena identity and battle period.
  • Per-view state should be recreated when the owning view instance changes.

Replay seeks are separate transitions

Replay time warp can rebuild battle entities, controllers, views, roots, and callback owners without leaving replay playback. Treat it as a dedicated teardown-and-recovery boundary rather than an ordinary repaint.

  • Stop owned loops and clear transient battle state at time-warp start.
  • Recreate at the actual UI or controller owner after time-warp finish.
  • Use a generation token so several rapid seeks cannot revive stale work.
  • Preserve disabled and hidden state instead of force-enabling the feature.

See Results & Replays for the complete recovery pattern.

Plugin membership does not establish lifecycle completion

When integrating with an existing client plugin collection, inspect each operation separately. The collection retains named objects rather than per-plugin initialized or started flags.

OperationBoundary
Bulk init, start, stop or finiLogs individual plugin-method exceptions and continues when iteration permits. It does not snapshot the dictionary or isolate iterator failures.
Dynamic add with autoStart=TrueStores the object before direct init/start calls. A failure propagates while the admitted object remains registered.
Dynamic removePops the object before direct stop/fini calls. A failure can skip finalization or later requested removals after the object is already absent.

When iteration completes, collection fini() clears the registry without calling stop(). Preserve the owning component's explicit stop/finalization order and inspect plugin overrides before treating registry absence as successful cleanup. These latest-source contracts were checked with fake plugins and logging; real client plugins, subscriptions, rendering, Python 2 iteration order and installed bytecode were not exercised.

Teardown order

  1. Mark the controller inactive and advance the generation token.
  2. Cancel callbacks and stop recurring loops.
  3. Unbind input and unsubscribe events with the exact original handler objects.
  4. Destroy windows, GUI roots, observers, and temporary entities.
  5. Stop workers or local servers with bounded joins.
  6. Restore only hooks still owned by this mod.
  7. Clear context-specific state and references.

Player entity readiness sequence

BigWorld.player() changes among login, account, and avatar entity types. Reacquire it at use time and invalidate retained work when the player entity starts changing. A false entity-changing flag only means the transition guard has cleared; it does not prove caches, routes, arena space, input, or vehicle visuals are ready.

BoundaryWhat it establishesWhat it does not establish
onAccountBecomePlayerAccount helpers and proxies entered player mode.Database ID, GUI context, cache sync, or hangar readiness.
onAccountShowGUIGUI context is unpacked and ordered lobby sync begins.Completion of that synchronization chain.
onAccountComponentsSyncedThe selected chain completed or aborted. Success unless its flag is true, or hangar work when the context skipped it.
onAvatarBecomePlayerBattle-scoped ownership can begin.Space, input, HUD, vehicle, appearance, or CGF readiness.
onAvatarReadyRequired avatar initialization and arena loading have completed.Every derived component state; vehicle property republishing can follow.

Exit and disconnect order

Account non-player callbacks run after helper teardown and command cancellation have begun. Avatar non-player callbacks run after most battle GUI, arena, and visual services are already gone. Use the earlier player-entity-changing signal to cancel asynchronous work; use non-player events for final local invalidation rather than new dependent requests.

GUI personality cleanup clears caches and services before the global disconnected event fires. Treat it as final invalidation. Capture account, arena, player, vehicle, view, or generation identity for delayed work and revalidate it before applying a result.