Controller ownership is a second axis

GUI game controllers are both dependency services and members of a game-state tracker. The tracker forwards connection, account, avatar, lobby, and replay transitions; dependency lookup only returns the currently registered implementation. Replacing a service does not remove an earlier controller from the tracker or rewrite references already retained elsewhere.

Late configuration hooks do not supply missing ownership automatically. Pair service and tracker registration with initialization, exact subscription removal, context cleanup, and a process finalizer. Cleanup may run at disconnect and again at shutdown, so it must be idempotent.

Battle sessions own controller repositories

Battle controllers are separate from process-level dependency services and GUI game controllers. The current session provider creates battle context, arena listeners and view bridges, then a shared controller repository and a mode-selected dynamic repository. The dynamic repository can differ for Classic, Frontline, Battle Royale, Comp7, and other arena GUI types.

Ownership axisConsequence
RepositoryRegistration starts the controller and makes it available for the current battle session.
Arena listener collectionArena controllers receive arena events and may be stopped through listener cleanup.
View-components bridgeView controllers attach only when the matching battle UI view becomes ready.
Mode-selected dynamic repositoryAn optional controller can legitimately be absent in another arena GUI type.

Session stop clears bridges and listener collections before destroying repositories. A controller registered along more than one axis can encounter cleanup more than once, and sibling repository destruction order is not stable. Make stopControl() idempotent, release only owned resources, and never retain a repository controller across battle sessions.

Common mistakes

  • Treating an unavailable service as a permanent installation failure.
  • Resolving every dependency during import.
  • Caching a context-specific implementation across account or battle transitions.
  • Catching all errors around both service resolution and feature behavior, hiding real defects.
  • Using an implementation class directly when callers should depend on the interface.

Verification trail

Start at the interface, find its registration, identify the context that installs the implementation, then inspect teardown. Search call sites to learn whether users expect exceptions, None, or delayed readiness.

Collection has its own construction and refresh contract

system_factory stores callbacks that run against a context when a collector is invoked. Registration alone does not construct or install a service, and collectors have different result policies:

Registration exampleCollection behaviorOwnership limit
Game-controller triplesConstructs controllers and passes them to a configuratorRecollection constructs again; the configurator owns initialization and service/tracker registration.
Package or tooltip-builder listExtends a fresh result list from retained inputsLater input changes affect later collections; repeated registration can duplicate entries.
Client-message formatterAssigns the supplied object to a context slotThis collector selects the last invoked registration without constructing a new formatter; earlier callbacks remain.
Server-message formatter for one message typeAssigns the retained object to the context slot for either replace valueLast invoked assignment wins if callbacks complete; earlier callbacks remain. The flag does not establish conflict detection or ownership.

Consumer refresh timing is separate. The app factory captures lobby packages when creating the lobby and explicitly recollects on reload, while the service channel selects a client formatter when adding a client message and a server formatter by type when processing a server message. A new registration does not retroactively refresh every existing consumer. The manager shares a live context and listener list; an exception can leave earlier contributions or construction effects in place. Verify the exact register/collect pair rather than assuming one universal factory policy.

Static inspection of original Python bytecode from WG 2.4.1.1 #989 confirms that both server-registration replace branches reach the same assignment. Ordinary registrations use the default False, while personality registration passes True. This verifies the inspected callback policy, not live registration order, whole-module parity or a message-delivery defect.

Shutdown walks one registration snapshot

The dependency manager sorts one snapshot of registered item objects, finalizes that list, clears those same objects, then clears its registration dictionary. Uncreated lazy entries are included. If a later finalizer first resolves a lazy service after its saved finalization turn, the manager does not revisit it. New registrations or replacement items can also remain outside the saved passes.

Keep cleanup separate from service registration and lazy bootstrap. An escaping finalizer error can stop later finalizations and the clear/reset path, so retrying may repeat earlier finalizers. Ordinary exceptions inside valid named finalizers are logged and suppressed; callable finalizers and missing named methods have different failure boundaries. A resolvable service during shutdown is not proof that it remains operational.

These are current Python source and recording-service contracts. Actual client services were not constructed or finalized in the checks, and no installed resource leak or real shutdown failure is diagnosed.