When a changed file does not appear in the client, first identify
the layer that owns the stale value. Python imports, package lookup, ResMgr, Scaleform,
Gameface, and generated accessors have different invalidation boundaries.
Separate filesystem, engine, and application caches
- Filesystem/package lookup decides which bytes win for a virtual path. Replacing an archive while the process is running does not prove that the VFS will remount it.
- Engine resource lifetime owns loaded sections, textures, models, and native handles. A correct replacement on disk can coexist with a live reference to old data.
- Application/framework state owns decoded models, generated accessors, resource maps, browser bundles, routes, and view instances. Reopening one view may leave a process-level registration or browser cache untouched.
Diagnose these layers in order. A successful virtual-path lookup cannot prove that an already loaded texture changed, and a newly loaded frontend file cannot prove that the native view or handler registration matches it.
Invalidation matrix
| Layer | Owns | Refresh boundary | Typical stale symptom |
|---|---|---|---|
| Python import cache | Loaded modules, class objects, monkey patches, callback closures. | Reliable validation usually requires a client restart. | Edited code is packaged, but old hooks or captured objects still run. |
| VFS and package lookup | Which archive or res_mods path supplies a virtual file. |
Restart after changing package contents or precedence assumptions. | The client loads a same-named resource from another package or path. |
ResMgr / engine resources |
DataSection trees, model data, textures, and engine resource handles. |
Depends on owner and resource type; prefer restart for contract changes. | A section exists, but a model, texture, or higher-level owner still uses old data. |
| Scaleform cache | Loaded SWFs, textures, atlases, and application/view registrations. | View destruction may be enough for local state; registration and SWF changes often need restart. | A recreated view still displays an old Flash asset or alias contract. |
| Gameface registration and browser assets | Native WG declarations or legacy maps, generated IDs, coui:// paths,
HTML/CSS/JS/image assets. |
Restart after registration changes; view reload may be enough only for already registered loose assets. | Python resolves the feature, but the browser loads old CSS, JS, or no asset. |
| Generated contracts | Resource accessors, aliases, model fields, view IDs, localization keys. | Regenerate or verify against the target build; restart for runtime confirmation. | An accessor exists in source history but no longer matches the current client. |
An existing loader may not be initialized
Current public source; version-sensitive. The client GUI color loader stores a module-level instance before parsing its XML. If parsing raises, the wrapper logs the exception and returns that same instance. Later calls return it without retrying the parse. Object existence therefore does not establish successful initialization; this is a readable-source failure path, not a reproduced live-client failure.
On its normal parse path, the loader clears XML value caches
and purges the resource section after building its own color dictionary. Those operations
do not rebuild that dictionary. The color-scheme
manager retains the loader, and its base update() only sends a frontend update;
creating another manager obtains the same loader.
For a mod-owned loader, define when parsed state becomes usable, what happens to partial state after failure, and which owner may retry or replace it. Build and validate replacement data before publishing it when atomic replacement is required. Keep parse readiness separate from view refresh and native resource invalidation; do not reset a client-owned singleton to force a mod's refresh. A purge call alone proves neither reparsing nor native release timing.
Verification order
- Verify the package contains the changed file at the exact virtual path and case.
- Confirm no earlier package or loose resource wins the same virtual path.
- Confirm the current client source still references the same accessor, alias, or map key.
- Log both the stable key and resolved path/ID when a resource fails.
- Restart once before investigating lifecycle timing; stale cache can mimic readiness bugs.
- If restart fixes it, record the responsible layer instead of adding arbitrary retries.
A loose-resource development loop
For editable mod-owned XML, use an explicit purge, reopen, validate and publish action, then apply the candidate to the live GUI component. An already-loaded mod can provide this action so caption/configuration edits do not require a whole-client restart when the resource lookup exposes the edited file.
If that action still reads old or missing data, check the virtual path and selected loose-file location, then identify the resource and application cache owners. Use a restart comparison to locate the remaining boundary. Changes to archives, Python modules, generated registrations and same-path textures have separate requirements; saving a file is not an automatic reload trigger.
Mapped images have a separate cache owner
The ImagesFetchCoordinator caches retained texture IDs by URL. A cache hit returns its existing link before consulting a new request's oneUse argument. Changing that argument does not change an already cached entry's mapping policy.
Its single-URL clear method requests texture removal but leaves the URL key in that Python cache; it is not a complete remove-and-fetch-again transaction. Whole-cache cleanup clears the mapping. fini also marks the coordinator dying, so a later image callback returns None instead of requesting another mapping. This rejects late results; it does not prove that downloading was cancelled.
In the current client consumer, a view-owned data receiver calls coordinator fini and separately checks its own disposed state after awaiting image links. Keep mapping cleanup and stale-view rejection as separate responsibilities.
The underlying file cache can group same-URL byte requests while still invoking separate image callbacks. That is not deduplication of the coordinator's mapping work: if overlapping misses return distinct persistent IDs, the single URL entry retains only the last. An owned adapter should coalesce pending mapping work or account for every mapping it must release. This is a conditional Python ownership path, not a reproduced native leak or an assertion that the inspected view makes overlapping calls.
These source contracts do not establish native eviction timing, temporary-image lifetime or same-path image freshness. Distinguish the file, bytes, ID and link representations before selecting a reload operation.
Restart versus recreate
Recreating a view can clear view-owned state, subscriptions, and model values. It does not prove that package lookup, generated resource registration, or native/browser caches were refreshed. Treat registration, alias, generated-accessor, resource-declaration, and packaged-code changes as restart-required unless current runtime evidence proves a narrower reload boundary.
Asynchronous loads carry owner lifetime
Background loading separates request time from completion time. The completion handler must re-check the arena/space, view, model owner, and local generation before attaching the result. Retain the returned resource owner only as long as the consuming object needs it, and release or replace old references through the same owner that attached them.
Generated frontend assets add another invalidation boundary: Python can still construct a valid window while its HTML/JavaScript bundle, resource declaration, or handler contract belongs to a different client build. Treat bundle and native-registration changes as one version-sensitive contract.
Common misdiagnoses
- Adding readiness retries when the client is using an old packaged file.
- Rebuilding a Gameface view after changing its native declaration or legacy map instead of restarting.
- Editing generated output and losing the change when the source contract is regenerated.
- Assuming
ResMgrsection existence means the UI framework has accepted the corresponding view or asset. - Trusting a successful Python import after a hot reload when old callbacks still capture old objects.