Inspect mixed-framework collections defensively

Window managers and diagnostic inventories can contain Wulf and Scaleform objects together. They do not implement one shared Python property surface merely because they appear in the same collection. Current runtime inspection confirmed that Scaleform views can lack Wulf-only fields used by a generic window record.

  • Classify the object or framework before reading framework-specific properties.
  • Define a small common record for identity, type, status, and ownership.
  • Represent an inapplicable optional field as absent or null; do not fail the complete inventory.
  • Do not hide unexpected failures with one broad exception handler. Distinguish an expected cross-framework capability miss from a real inspection error.

Scaleform and DAAPI

Legacy Scaleform views bind an ActionScript 3 SWF entry class to a Python view class through DAAPI. Python calls methods exported by the SWF through self.flashObject; function properties declared by the AS3 class can call methods on the bound Python object.

Step Current client surface Ownership check
Register IGuiLoader.entitiesFactory + ViewSettings Use a unique alias and avoid duplicate registration.
Load Live Scaleform application + SFViewLoadParams Wait for the correct lobby or battle application.
Exchange data DAAPI methods and flashObject Keep Python and AS3 names, argument order, and types aligned.
Destroy View/application lifecycle Remove listeners and release only resources owned by the mod.

The current GuiLoader owns the GuiEntitiesFactories registry exposed through IGuiLoader.entitiesFactory. The old gui.Scaleform.framework.g_entitiesFactories import is absent from the current framework module; older external examples do not establish a current stock API.

addSettings validates the selected layer and factory contract, then rejects a duplicate alias; it does not silently replace the entry. Current package loading registers settings through this same owner and clears its aliases on unload. Keep registration ownership separate from existing view instances: removing settings is not a call to destroy those views.

from frameworks.wulf import WindowLayer
from gui.Scaleform.framework import ScopeTemplates, ViewSettings
from gui.Scaleform.framework.managers.loaders import SFViewLoadParams
from helpers import dependency
from skeletons.gui.impl import IGuiLoader

VIEW_ALIAS = 'MY_MOD_VIEW'

settings = ViewSettings(
    alias=VIEW_ALIAS,
    clazz=MyPythonView,
    url='my_mod.swf',
    layer=WindowLayer.TOP_WINDOW,
    event=None,
    scope=ScopeTemplates.TOP_WINDOW_SCOPE,
)
# Register once, after the GUI loader service has been installed.
gui_loader = dependency.instance(IGuiLoader)
gui_loader.entitiesFactory.addSettings(settings)

# Resolve the correct live Scaleform application at the owning lifecycle point.
app.loadView(SFViewLoadParams(VIEW_ALIAS))

Keep the settings object for the registration lifetime. A load's instance name belongs to SFViewLoadParams/ViewKey, separately from the registered alias. This example follows current source contracts; GUI service resolution, SWF loading and actual view creation were not exercised.

Cross-runtime DAAPI transactions

A Python-to-AS3 request and its AS3-to-Python callback form one transaction. A visible Flash change proves only that the outbound half ran; it does not prove that Python-owned logical state, settings plugins, or other callback consumers advanced. Instrument both directions when the runtimes can disagree.

  • Let the native transaction complete before considering reconciliation.
  • Compare the requested state with the resulting state owned by Python, not only rendered dimensions or coordinates.
  • Repair only the lifecycle state where the missing reverse callback was established, and route the repair through the original Python transaction so dependent plugins update together.
  • When native layout can run after the callback, use one deferred completion path for both the real-callback and bounded-repair branches.

A component reference crossing the Scaleform boundary may also be a weak proxy. Python identity with is is not a safe ownership test for that reference. Prefer current lifecycle and attachment checks, or equality only after its proxy semantics have been verified. These callback, proxy, layout, and hit-area contracts are private and version-sensitive.

Shared Scaleform libraries

Scaleform screens are not always self-contained. A small view SWF can expose symbols or timeline art while the ActionScript logic that consumes DAAPI fields lives in a shared lobby or battle library. If a Python view and the visible SWF do not explain the rendered behavior, inspect the shared SWFs loaded by the same application before changing data production.

Current battle crosshair contract

The current crosshair proxy publishes onGunMarkerStateChanged(markerType, gunMarkerState, supportMarkersInfo). Code that expects direct hitPoint, direction, and collision arguments uses an older contract. Read those values from the verified gun-marker state instead.

createShotResultResolver() and _CrosshairShotResults exist, but their collision traversal, ricochet, normalization, shell-selection, spaced-armor, and jet-loss helpers are private and version-sensitive. Prefer the native result where possible; do not copy the resolver into a mod as frozen game logic.

Vehicle Preview and Vehicle Hub

The legacy Scaleform preview family and the Wulf Vehicle Hub can display the same vehicle but do not share one route contract. Preview routes can carry commerce, offer, style, return-alias, and appearance context that a plain Hub vehicle overview does not preserve.

  • Choose a destination from the complete entry context, not merely vehicle ownership.
  • Do not redirect specialized trade-in, rental, hero, offer, configurable, style, or purchase previews unless the target route explicitly represents that contract.
  • Audit direct-import aliases before patching a shared navigation helper; module replacement does not rewrite function references imported earlier.
  • Keep routing wrappers composable and restore only a wrapper still owned by the mod. A caller importing during the patch can retain that wrapper even after the module attribute is restored.

For ordinary Wulf lobby routes, let the lobby state machine own navigation history and window destruction. Pass JSON-safe route data and resolve live vehicle/cache objects after entry.

View lifecycle

Never store a view instance as permanent module state. A screen can be recreated after resolution changes, application transitions, reconnects, or navigation. Subscribe on view creation and unsubscribe using the same handler identities on destruction.