Restoring saved positions safely

Preference readiness is independent from Gameface, DOM, and Wulf-window readiness. A panel can be fully rendered while Settings.g_instance.userPrefs is still unavailable. Do not mark position loading complete until the preference section can actually be read.

_position_loaded = False


def try_restore_position(attempt=0):
    global _position_loaded
    settings = getattr(Settings, 'g_instance', None)
    prefs = getattr(settings, 'userPrefs', None) if settings else None

    if prefs is None:
        if attempt < MAX_POSITION_ATTEMPTS:
            schedule_retry(lambda: try_restore_position(attempt + 1))
        return False

    section = prefs['myMod']
    screen_w, screen_h = GUI.screenResolution()
    x = section.readInt('x', int(screen_w // 2))
    y = section.readInt('y', int(screen_h // 2))

    _position_loaded = True
    apply_restored_position(x, y)
    return True

This illustration owns one module-level preference-read flag; the global declaration publishes the successful read instead of creating an unused local. A view-owned implementation should keep the equivalent flag on that view and reset it with the owner. The flag is set before apply_restored_position, so it marks completed preference reads, not successful application or native position readback.

Exact helper checks with inert settings, preference sections, viewport and scheduler substitutes, starting with a false flag, confirmed that unavailable preferences and a raised read leave it false, while successful reads publish true before the apply call. Existing retry limits and returns are preserved. No actual preferences, GUI, callbacks, timers, browser or client runtime were exercised; historical layout evidence elsewhere on this page retains its scope.

  • Use a bounded retry so startup cannot schedule forever when preferences never become available.
  • Set the position-loaded guard only after a successful read, not when the first attempt begins.
  • Immediately reapply the restored coordinates when a delayed read succeeds; otherwise the already-visible panel remains centered.
  • GUI.screenResolution() may return floating-point dimensions. Cast defaults passed to ResMgr.DataSection.readInt to int, because its binding rejects float defaults before reading an otherwise valid stored integer.
  • After restoring coordinates saved at another resolution, remap both axes proportionally and clamp against the current scaled surface.

Tooltip and active-row ownership

Pointer movement, keyboard focus, and tooltip ownership are separate states. Raw :focus or an independent fading :hover background can leave the old row highlighted after the tooltip has moved.

  • Give the current tooltip anchor one feature-owned class, transfer it synchronously when ownership moves, and remove it whenever the tooltip closes or rows rerender.
  • Use that owned class as the sole background-highlight authority. Keep :focus only for keyboard outline and accessibility behavior.
  • Use pointer-events: none for an informational tooltip appended outside the native row so it cannot steal leave events from its anchor.
  • Remove owned tooltip nodes on row leave, blur, native rerender, and page unload. A document-level pointer-position check can close a stale tooltip when a native overlay blocks the normal leave transition.
  • Use only cursor identifiers supported by the target Gameface runtime; unsupported values are replaced and generate client log errors.

Input and hybrid UI limits

  • The Gameface native surface is the hit-test surface. Transparent pixels inside it are not automatically transparent to underlying Scaleform controls.
  • Keep the native surface tightly fitted to the visible panel.
  • A standalone Gameface panel can coexist with a Scaleform HUD, but it does not modify the internal behavior of those Scaleform controls.
  • Do not mix incompatible decorators or window implementations without verifying the current client contract.
  • When text entry, chat, or menus are active, avoid consuming keys intended for the native interface.

Bitmap painting and element geometry

Use explicit image dimensions, a sized non-repeating background with contain, or Canvas drawImage after load. Inspect actual pixels as well as element bounds; a correctly sized box can still contain clipped artwork.

Do not rely on image.complete or image.naturalWidth being available. Use retained load listeners and supplied dimensions, invalidate late callbacks, and inspect actual pixels as well as bounds. See image and layout guidance.

Native resize and input evidence

Verify native resize delivery rather than relying on a frontend breakpoint. A frontend size change can leave Python reporting the previous Wulf size. Retain requested dimensions separately from measurements, observe onSizeChanged and window.size, and remember that a delayed report may already describe a restored size.

A separate actual resolution change from 1920×1080 borderless to supported 1280×768 windowed and back retained the tested panel position. Only effective 1× interface scale was available, so that run did not exercise a different scale. It does not replace the separately scoped physical-pixel/Wulf-coordinate checks above.

Replay keyboard routing precedes normal GUI handling in game.handleKeyEvent. The replay controller can consume hotkeys and mapped commands even when a text field has focus; mouse buttons follow an earlier GUI-first path. Selective text entry during replay therefore does not establish missing glyph support or live-battle behavior. show(False) avoids focus on show but does not prohibit click focus.

Copy action data before deferring a DOM handler

Observed scope: WG 2.4.1.0 #977 / Cohtml 2.2.8.1. In tested synthetic DOM events, target and currentTarget were usable inside the handler but null after dispatch, including later microtask, frame and timer reads. Retaining the event did not preserve those routing references.

Copy the primitive action ID, value and revision during the callback, then pass that data to generation-guarded work. A retained detail object is not a snapshot: later mutation remained visible through the event. This is a raw DOM-event result, not proof of React wrapper or native mouse/key lifetimes.

Delegate actions through one owned ancestor when appropriate, then verify the matched node is inside that root. Retain the exact function and capture mode for removal. Identical DOM registrations were deduplicated in the tested case, unlike engine registrations; capture and bubble registrations remained distinct. Synthetic cancellation does not establish suppression of a game command or native input routing.