# GregOrigin - PSO Autopilot: Shader Compilation Warm-up System to Prevent Lag & Crashes 

**URL:** <https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140>\
**Category:** Product Discussion\
**Tags:** fab\
**Created:** [March 6, 2026, 10:11am UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140 "2026-03-06T10:11:04Z")\
**Posts on this page:** 19\
**Page:** 2

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [May 31, 2026, 4:26pm UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/21 "2026-05-31T16:26:21Z")

</div>

New editable sample widgets + BP for those interested:

[CreateEditableLoadingWidget.py](https://forums.unrealengine.com/uploads/short-url/xwrkoA4aGTl01nNWmbu6tTVJ8me.py) (5.6 KB)  
[LoadingWidgetTemplates\_README.md](https://forums.unrealengine.com/uploads/short-url/1yamVjmwOS5eR06B4AyyCKuXdV3.md) (2.4 KB)

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [June 3, 2026, 7:33pm UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/22 "2026-06-03T19:33:28Z")

</div>

1.3.1a, 2026-06-04

Bug Fixes

• Fixed Infinite Loading Screen: Resolved an emergent deadlock issue where the loading screen would hang indefinitely at “Finding startup assets…” when launching in Standalone Game mode. The plugin now correctly binds and listens to the Asset Registry OnFilesLoaded delegate without prematurely yielding the tick.  
• UE 5.7 Compilation Fix: Fixed an “incomplete type” / “unrelated pointers” compilation error in PSOAutopilotSubsystemHelpers.cpp on Unreal Engine 5.7 by adding the explicit #include “UObject/Package.h” dependency for GetTransientPackage() during NewObject construction.

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [June 9, 2026, 11:41am UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/23 "2026-06-09T11:41:00Z")

</div>

Video update on v1.3:

[![](https://d3kjluh73b9h9o.cloudfront.net/original/4X/d/d/a/ddaf7c737742c1ce722ee5c9e12981c9466cfb43.jpeg "PSO Autopilot 1.3: Protect your game from shader compilation stutter and crash (UE5)") ](https://www.youtube.com/watch?v=PPTxKIusY9I)

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [June 11, 2026, 7:03pm UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/24 "2026-06-11T19:03:32Z")

</div>

## 1.3.2 update, 2026-06-13 💫

### ✨ Features

- 🎛 **Per-platform performance overrides.** A new **Per-Platform Performance Overrides** list (`FPSOAutopilotPlatformOverride`) lets you replace the effective items-per-step, frame-time budget, memory-cleanup, and pipeline-cache wait on a named platform (Windows, Mac, Linux, Android, IOS). An enabled override matching the running platform is overlaid onto the runtime snapshot; disabled or non-matching entries are ignored.
- 🧮 **CI coverage commandlet.** New headless `PSOAutopilotCoverage` commandlet resolves the configured warmup content via the asset registry, prints a per-type coverage report, and returns a non-zero exit code when discovered assets fall below `-MinAssets=<N>`, so a content/cook regression fails the build. Run: `UnrealEditor-Cmd.exe <project> -run=PSOAutopilotCoverage -MinAssets=50`. Its dedup/counting/threshold accounting is extracted into `FPSOAutopilotCoverageReport` and unit-tested.
- 🤖 **One-command CI gate.** New `Scripts/Run-CI.ps1` chains build → `PSOAutopilot.*` automation tests → coverage gate and fails fast on the first failed stage, for local pre-flight or a CI runner. The coverage floor defaults to `-MinAssets 900`, so losing the demo content set fails the gate.

### 🔧 Internal Hardening (settings → runtime snapshot)

- 🎚 **Single source of truth for Simple-preset tuning.** The Simple preset → performance-tuning mapping is now one shared helper (`ApplySimplePresetTuning`) applied identically to the persistent settings object and the runtime snapshot, so the two can never drift.
- 🧩 **Snapshot round-trip safety.** `CaptureFrom` and `ApplyTo` now share one field list (`PSO_SNAPSHOT_COPYABLE_FIELDS` X-macro), so adding or removing a snapshot-copied setting cannot desync capture and restore.
- 🧪 **Effective-capture overlay.** `CaptureEffectiveFrom` layers the Simple-mode seed and the per-platform override over the raw capture; Advanced mode passes raw content through unchanged.
- 🧽 **Runtime snapshot sanitize.** `FPSOAutopilotSettingsSnapshot::Sanitize()` trims, de-duplicates, and clamps the snapshot before warmup consumes it (wired into the live warmup start path alongside `CaptureEffectiveFrom`).
- ⏱ **Pipeline-flush tracker extracted.** The engine pipeline-cache flush accounting (cumulative compiled vs. newly-discovered PSOs on a non-monotonic queue, drain fraction, and a sliding-window completion rate for the ETA) is now an isolated, unit-tested `FPSOPipelineFlushTracker`.

### 🐛 Bug Fixes

- 💾 **“Apply Simple Setup” no longer wipes manually-configured Advanced content.** A previous code change introduced a nasty conflict: Applying Simple Setup reset the Advanced **Content Folders** , **Specific Content to Prepare** , and **Content to Load Before Play** lists, discarding any maps, assets, or packages entered directly in Advanced mode. Simple Setup choices are now merged into those lists, so existing Advanced entries are preserved.
- 🧹 **No duplicate entries when merging.** Folders are de-duplicated by path (and the gameplay seed level by asset path), so re-applying Simple Setup does not create repeated entries.

### 📚 Notes

- The merge is additive: only the persistent Advanced fields are affected. Simple-mode warmup still runs from a clean Simple snapshot, so this change does not alter what Simple mode actually warms.
- Because the merge is additive, changing **Level To Open After Warmup** and re-applying Simple Setup can leave the previous seed level listed in the Advanced lists. This only adds one extra, harmless warmup entry; remove it manually if undesired.
- Thank you to _Arash\_R_ for pointing this out soon after the last release.

### ✅ Validation

- 🧪 10 automation tests pass (`Automation RunTests PSOAutopilot`, NullRHI), exit code 0: `SettingsPresetTuningParity`, `SettingsSnapshotRoundTrip`, `SettingsEffectiveCapture`, `SettingsSanitize`, `SettingsPlatformOverride`, `PipelineFlushTracker`, `SettingsSimpleSetupMerge`, `LiveSmokeTest`, `CoverageReport`, `CoverageFolders`.
- 🧮 Coverage commandlet verified on both paths against the project (1001 discovered assets): `-MinAssets=0` → “Coverage gate passed (1001 \>= required 0)”, exit 0; `-MinAssets=5000` → “Coverage gate FAILED … below … 5000”, exit 1.
- 🤖 `Scripts/Run-CI.ps1` runs build + tests + coverage green end-to-end (exit 0).

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [June 13, 2026, 7:21pm UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/25 "2026-06-13T19:21:56Z")

</div>

PS1 files are not allowed in Fab packages. I provide them here for those interested in up-to-date validation and integration scripts:

[Run-CI.ps1](https://forums.unrealengine.com/uploads/short-url/5Kalbe5GYxAx7E9brFp8fRorICA.ps1) (9.8 KB)  
[ShippingValidation\_README.md](https://forums.unrealengine.com/uploads/short-url/sV1mIBSZbBgT7EEJsOeTAVfDTHI.md) (1.8 KB)  
[Build-ShippingValidation.ps1](https://forums.unrealengine.com/uploads/short-url/mBgNhzYOgcTO2eT749EUsiBb4wr.ps1) (11.8 KB)

---

<div class="post-metadata">

**Author:** ![PIXEL\_THUNDER](https://d3kjluh73b9h9o.cloudfront.net/original/4X/2/8/5/28502927f1d97ba1051b3b47ccfe17233bf41ef0.png) [@PIXEL\_THUNDER](https://forums.unrealengine.com/u/PIXEL_THUNDER)\
**Post date:** [July 6, 2026, 2:25pm UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/26 "2026-07-06T14:25:56Z")

</div>

This plugin seems amazing! We’d love to buy it for our project, but we need it to be compatible with UE5.8. version.

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [July 7, 2026, 1:08pm UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/27 "2026-07-07T13:08:07Z")

</div>

## 1.3.5 update, 2026-07-08 👨‍✈️

This technical update adds per-platform tuning, headless coverage/CI tooling, and a phase-aware  
pipeline-flush ETA. Finally it hardens the internals with a full decomposition of the warmup engine.

#### 🏗 Architecture

The monolithic warmup subsystem (2,700+ lines) was decomposed into a thin coordinator plus  
eight focused, individually unit-tested collaborators. **Runtime behavior, the Blueprint API,  
and the C++ public API are unchanged**.

```auto
                    UPSOAutopilotSubsystem (coordinator: state machine + Tick)
                                │
    ┌───────────┬───────────┬───┴────────┬─────────────┬─────────────┐
    ▼ ▼ ▼ ▼ ▼ ▼
 Progress Fingerprint Warmup Package Batch JIT
 Reporter Store Discovery Preload Mgr Loader Warmup Mgr
    │ │ │ │ │ │
    └───────────┴───────────┴─────┬──────┴─────────────┴─────────────┘
                                  ▼
                     AssetWarmupEngine + TextureStreamingStep
                     (shared per-asset PSO precache & texture work)

```

- 🏗 **Eight collaborators under `Source/PSOAutopilot/{Public,Private}/Warmup/`:** `FPSOWarmupProgressReporter` (progress bands, summary text, ETA inputs), `FPSOWarmupFingerprintStore` (smart-cache skip persistence), `FPSOWarmupDiscovery` (asset scanning + runtime-asset queueing), `FPSOPackagePreloadManager` (gameplay-map preloads), `FPSOAssetWarmupEngine` (per-asset PSO precache work), `FPSOBatchLoader` (time-sliced batch loop), `FPSOJitWarmupManager` (just-in-time actor-class warmup), `FPSOTextureStreamingStep` (texture residency phase). The pipeline-flush accounting extracted earlier (`FPSOPipelineFlushTracker`) completes the set.
- 🔧 **Encapsulation pass.** Raw internal-session access was replaced with named lifecycle methods (`ResetWorkAndCompileEvents`, `AbandonInFlightWork`, `ReleaseEngineRequests`, `ClearStagedNames`), each a 1:1 wrap of the previous inline call sites. Dead code removed (`MergeRuntimeAndFilter`, `QueueByPath`).
- 🔧 **`EPSOWarmupState` moved to its own thin header** `Public/PSOAutopilotWarmupState.h`. `PSOAutopilotSubsystem.h` still includes it, so existing C++ user code compiles unchanged; the Blueprint enum path is identical.
- 🧪 **Automation test suite grew 10 → 18 tests.** Every collaborator has a dedicated unit test; summary-text arms (preload / flush / demo-hold / cached-run / ready / fallbacks) and the JIT queue lifecycle (dedup, requeue-vs-abandon semantics) are now pinned.
- 🧪 **The fingerprint unit test runs against an isolated `.ini`** (new test-only config-path seam), so running the suite no longer clears the developer’s real smart-cache history.
- 🛡 **New packaged runtime smoke gate** (`Scripts/Run-PackagedWarmupSmoke.ps1`): cooks a Win64 build, launches it, and asserts the warmup state machine reaches `Finished (Success=true)` without hanging; catching the class of regression that headless NullRHI tests cannot see. The script clears the smart-cache fingerprint itself so a cached skip can never produce a vacuous pass.
- 🛡 **`Scripts/Run-CI.ps1` reliability fix:** stage waits use `WaitForExit()` instead of `Start-Process -Wait`, which could hang indefinitely on a lingering editor child process.

#### ✅ Validation

- 🧪 Full CI green on the final commit: build + **18/18** automation tests (NullRHI) + coverage gate **1001 ≥ 900**.
- 📦 Packaged Win64 smoke run: fresh cook, smart cache cleared → full warmup executed (1002 assets discovered, 1,749 PSOs drained in the pipeline flush), `Warmup Finished. Success=true`, no livelock.
- 🏭 Clean full-unity (adaptive-unity **off** , Fab-parity) `BuildPlugin` builds succeeded on **UE 5.5, 5.6, and 5.7** with the decomposed module layout.

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [July 7, 2026, 1:10pm UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/28 "2026-07-07T13:10:50Z")

</div>

UE5.8 update in this case is a challenge, but I want to do it sooner rather than later. 🙂

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [August 2, 2026, 8:45am UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/29 "2026-08-02T08:45:39Z")

</div>

## v1.3.6 update, 2026-08-03 🧑‍✈️

- The plugin no longer ships `/Content` (fixes potential sample asset issues on UE 5.5). Nothing depended on it: every path in the runtime module, the editor module, and the documentation resolves `/Game/...`, never the `/PSOAutopilot/...` mount point. The loading screen is created in your project by the setup wizard, and the demo is generated by `Scripts/CreatePSOAutopilotDemo.py`; both of which save through _your_ editor, so the assets always match your engine version. This removes the  
whole class of potential issues rather than re-saving assets per engine.

- **Upgrading from 1.3.5 or earlier:** installing over an existing copy does not delete files that were removed from the plugin, so the old `Plugins/PSOAutopilot/Content/` folder — and its warnings, if present — can survive the upgrade. Delete that folder once; nothing references it.

#### 🔧 Internal hardening

- Eight extracted `Public/Warmup/*.h` headers used `FSoftObjectPath`, `TSubclassOf`,  
`TObjectPtr`, and `TWeakObjectPtr` while including only `CoreMinimal.h`, which no longer  
provides them. They now include what they use, so they are self-contained for any consumer  
including them from their own module.

#### 🐛 Warmup runtime fixes

- **Cancelling a warmup stranded forced texture residency.** Every texture the run had touched was  
forced resident with a 300-second window, and a completed run winds that down to 60 seconds on  
hand-off. `StopWarmup` — which is `BlueprintCallable` _and_ the Cancel path on the loading-flow  
handle — only dropped its bookkeeping, so a cancel left mips pinned for up to five more minutes.  
Cancel now winds down exactly as completion does.
- **Material discovery was quadratic.** Each material gathered from a primitive component was  
de-duplicated by linearly scanning the whole pending work list, so a level whose components share  
materials cost O(n²) weak-pointer dereferences during discovery. Membership now goes through a  
set, making appends O(1). Worlds are warmable assets, so large levels hit this hardest.
- **Virtual textures were re-initialised once per referencing material.** `UpdateResource()` tears  
down and recreates the render resource; it ran on every encounter, so a virtual texture shared by  
N materials paid N teardowns. It now runs once per texture per batch.
- **Forced-texture bookkeeping grew without bound.** The forced-residency list was an array appended  
to once per (material, texture) pair, so the end-of-warmup restore pass issued one redundant  
`SetForceMipLevelsToBeResident` per duplicate; in a single frame, at the exact moment the loading  
screen hands off to gameplay. It is now a set. The per-encounter refresh is deliberately kept so a  
long warmup cannot let an early texture expire mid-run.

- Covered by a new `PSOAutopilot.WarmupMaterialDedup` test that pins the de-duplication  
contract and the array/set sync across `Reset`.

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [August 31, 2026, 5:22am UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/30 "2026-08-31T05:22:59Z")

</div>

# v1.4.0 update, 2026-08-31 📈

#### ✨ New features at a glance 👀

- **UE 5.8 is now supported.** PSO Autopilot ships for Unreal Engine **5.5, 5.6, 5.7 and 5.8**.
- The setup wizard generates the demo for your specific setup, no more manual stuff.
- The setup wizard’s validation logic no longer lives inside the widget.
- A single Blueprint node now runs the whole loading flow. (Existing Blueprints are retained.)
- The editor module has automation tests for the first time.
- The loading-flow handle is now usable from C++.

#### ✨ Features

- The setup wizard generates the demo for your specific setup, no more manual stuff.

- **Generate Demo Content.** _Window \> PSO Autopilot Startup Setup_ now has a button that creates the  
demo in your project: the boot flow, the stress level, 1000 material instances, the loader and the  
loading screen. It is offered whether or not the demo already exists — labelled **Regenerate Demo  
Content** when it does — because the generator is idempotent and anyone clearing a stale  
`Content/` folder after upgrading from 1.3.5 needs exactly this.

- **The wizard tells you when Python is missing.** _Generate Demo Content_ needs the **Python Editor  
Script Plugin**, which Unreal disables by default. If it is off, the wizard now names the menu path  
instead of idling.

#### 🏗 Architecture

- **The setup wizard’s validation logic no longer lives inside the widget.**  
`SPSOAutopilotSetupWizard.cpp` drops from **1,973 to 878 lines**. All 13 validation rules, their  
supporting helpers and the discovery-preview scan move into `FPSOAutopilotSetupValidator`, which  
has no Slate dependency and reaches the editor only through a small probe interface.

#### 🔧 Internal hardening

- Helpers shared between the validation rules and the wizard’s fix actions were promoted to a header  
as `inline`/`constexpr` before the split. Two `.cpp` files in one module defining the same  
anonymous-namespace symbol collide under Unreal’s unity builds.

#### 🧪 Tests & test tooling

- **The editor module has automation tests for the first time** — six, covering the wizard’s  
validation rules: batch size, scan folders, excluded classes, the boot-loader checks, the demo row,  
and the mapping from rules to the rows you see.
- A fake probe stands in for the editor, so states that are slow or awkward to reach for real — a  
configured-but-missing scan folder, a boot loader pointing at the wrong level, auto-start disabled  
— are now covered.

#### 📚 Documentation & notes

- The README, manual and FAQ now say the demo is generated by a wizard button, **state the Python  
Editor Script Plugin prerequisite**, and give a copy-pasteable command line for anyone who prefers  
it. The updated _Manual.html_ is shipped with the product for convenience, though the online manual is still preferred.

* * *

### One node for the loading flow, one call for status

#### ✨ Features

- **A single Blueprint node now runs the whole loading flow.** _Start PSO Autopilot Loading Flow  
(Async)_ reports progress, completion, cancellation and failure on execution pins, replacing the  
older pattern of calling a function, storing a handle, and binding a delegate by hand. The  
previous node still works and is marked deprecated, pointing at its replacement — existing  
Blueprints compile with a warning, never an error.
- **One call for all status values.** _Get PSO Autopilot Status_ returns a struct carrying  
everything the individual Status nodes expose, so a progress widget reads state once per frame  
instead of calling up to 24 separate nodes. **The individual nodes are unchanged and not  
deprecated** — binding one text block to one value is still the right thing to do.

#### 🔧 Internal hardening

- **The loading-flow handle is now usable from C++.** It was exported with `PSOAUTOPILOT_API` and  
returned by a public function, but its header sat in `Private/`, so a C++ caller received a  
pointer to a type they could not include and could call nothing on. Blueprint users were  
unaffected, since reflection needs no header. The header moved to `Public/`; no API changed.

#### 🧪 Tests & test tooling

- `PSOAutopilot.LoadingFlowActionLifecycle` pins the async node’s contract: the factory returns a  
valid action even when the flow cannot start, no handle exists before `Activate()`, and cancelling  
before activation is safe and idempotent.
- `PSOAutopilot.StatusSnapshotParity` asserts every status field equals the getter it mirrors, so  
the struct and the getters cannot drift apart.

* * *

### Unreal Engine 5.8 support

#### ✨ Features

- **UE 5.8 is now supported.** PSO Autopilot ships for Unreal Engine **5.5, 5.6, 5.7 and 5.8**.  
No settings changed and no behaviour changed on 5.5–5.7.

#### 🐛 Bug fixes

- **Removed a deprecated engine call that would have broken future builds.**  
`ForEachObjectWithOuter` was being passed a boolean `bIncludeNestedObjects`; UE 5.8 deprecates  
that form in favour of an `EGetObjectsFlags` enum and warns that it will stop compiling in a  
later release:

#### 🛡 Reliability / CI gates

- A dedicated **game-target build gate** now runs `BuildPlugin` against every supported engine as a  
routine step, rather than only during packaging. Editor-target builds structurally cannot catch  
defects that appear only in a packaged game — which is how the 1.3.6 packaged-build defect  
reached the final packaging step before anything caught it.
- **The packaged-warmup smoke gate now covers Shipping** , not just Development, and no longer leaks  
a running game process per run.
- The release packer now derives the version from the plugin descriptor and cross-checks that  
`VersionName` and `Version` encode each other (`1.4.0` → `140`).

#### 🧪 Tests & test tooling

- New automation test `PSOAutopilot.EditorPreviewWidgetFlags` pins the nested-object traversal in  
the editor-preview widget path, so the argument removed above cannot be silently reintroduced as  
a narrower walk.

#### 📚 Documentation & notes

- The manual’s package-version diagram gains a fourth engine. Measured values, read from generated  
package headers: **5.5 → 1013, 5.6 → 1017, 5.7 → 1018, 5.8 → 1018**. UE 5.8 did not bump the  
package version, so 5.7 and 5.8 content is mutually loadable.

#### ✅ Validation

- Clean full-unity `BuildPlugin` — editor **and** packaged-game targets — against UE 5.5, 5.6, 5.7  
and 5.8. All green, with zero deprecation warnings originating in plugin source on any of them.
- CI gate: build, **28/28** automation tests, coverage gate 1001 assets. **22 of those are runtime  
tests and 6 are the editor module’s first.**
- **Demo generation verified on UE 5.8.** `Scripts/CreatePSOAutopilotDemo.py` produced the full set  
(1001 material assets, both demo levels, boot loader, loading screen) with zero errors, saved at  
5.8’s own package version. This is the same check that backs the generate-don’t-bundle design on  
every other supported engine.
- **Cooked warmup smoke on UE 5.8: pass.** A packaged Development build reached  
`PSO Autopilot Warmup Finished. Success=true` on content that engine generated itself —  
1002 assets discovered, the per-batch load/GC cadence observed across 23 collections, and the  
pipeline-cache flush drained a real backlog of 1961 PSOs in 23.3s. Confirmed non-vacuous by  
`FingerprintMatched=false`, so the smart-cache skip did not short-circuit the asset loop.
- **Shipping configuration validated, not just Development.** The plugin compiles and links in the  
`UnrealGame Shipping` target on all four engines, and a packaged **Shipping** build completes  
warmup on both UE 5.7 and UE 5.8. Shipping is what customers actually ship — `WITH_EDITOR` is  
stripped, `UE_BUILD_SHIPPING` is set, and optimisation differs — so a Development-only smoke does  
not cover it.
- **The setup wizard was exercised by hand** on UE 5.7 after the extraction above: the editor loaded  
with no plugin warnings, the wizard opened, and _Regenerate Demo Content_ rewrote all 1000 material  
instances and both levels. No automated test drives Slate, so this check is the only one that  
covers the widget itself.

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [September 5, 2026, 11:45am UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/31 "2026-09-05T11:45:57Z")

</div>

# v1.4.1 update, 2026-09-06 👨‍✈️

A fix release over 1.4.0., aimed at projects that build file-by-file; upgrading from 1.4.0 needs no project changes.

#### 🐛 Fixes

- **Issues in projects that do not use unity builds.** Five files used symbols  
whose declaring headers they never included. Two of the five files are shipped runtime and editor source; the other three are automation tests, which are not shipped.

- **`Start PSO Autopilot Loading Flow (Async)` fired On Completed when it was cancelled.**  
Cancelling after the node had started reported completion, telling your graph the level had been  
prepared, and the **On Cancelled** pin could not fire.

- **The async node could be garbage collected in the middle of a flow.** A loading screen that removes its own widget while warmup is still running could have the  
flow collected out from under it.

- **Generate Demo Content issues.** The editor’s Python console command reports  
that it _routed_ the request, not that the script ran – and it returns the same answer when the  
interpreter is not ready and the command is merely queued.

#### 🧪 Tests

- The async-node test now asserts **which output pin** fired. All three terminal paths leave the  
node inactive, so the previous assertions could not tell success from cancellation: which is why  
the pin defect went unnoticed. It also no longer cancels before its own failure case, which had  
been quietly preventing that case from running.

#### ✅ Validation

- Full-unity and non-unity `BuildPlugin` gates, plus the collision scan, on Unreal Engine  
**5.5 / 5.6 / 5.7 / 5.8**. Automation suite: **28 tests** , 0 failures. Coverage gate: 1001 assets.  
The pin fix was verified by reverting it and confirming the test fails.

* * *

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [September 6, 2026, 6:04pm UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/32 "2026-09-06T18:04:27Z")

</div>

An attempt at a visual explainer of the Setup Wizard:

> **[PSO Autopilot Setup Wizard - Visual Guide](https://gregorigin.com/Manuals/PSO_Autopilot_Wizard_Visual/)**
>
> An interactive diagram of the PSO Autopilot Setup Wizard main screen. Select any control to see what it does, tick the two escape hatches to watch which preflight checks they switch off, and see which stage of the boot flow each setting governs.

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [September 7, 2026, 8:32am UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/33 "2026-09-07T08:32:51Z")

</div>

# 1.4.2 update, 2026-09-09 💻

A fix release over 1.4.1.:

Upgrading needs no project changes. A **Content to Prepare** row that shows a path like  
`D:/Projects/MyGame/Content` is now resolved correctly at warmup; editing that row in the settings  
rewrites it to `/Game` for good.

* * *

#### 🐛 Fixes

- The **Content to Prepare** row (and the Advanced **Content Folders** row) uses a directory picker, so choosing a folder stores an absolute path such as `D:/Projects/MyGame/Content`. Unreal’s asset registry only understands package paths like `/Game`, so warmup asked it for a path that could never match and prepared none of the content in that folder.

- Both sides now share one conversion, which also understands plugin content (`.../Plugins/FancyFX/Content/Emitters` resolves to `/FancyFX/Emitters`, not `/Game/...`). A folder that cannot be expressed as a package path is now reported as an error in the log and left in place in the settings, rather than scanned as if it were valid.

- **Settings now repair the stored value.** Editing a content folder rewrites it to its package path, so the value cooked into a packaged build is already `/Game/...` instead of a path that only resolves on the machine that set it.

#### 🔧 Hardening

- The editor no longer has its own copy of the path conversion. It had one, it drifted from the runtime’s, and the gap between them is the defect above. The wizard now calls the runtime.

#### 🧪 Tests

- **28 → 36 automation tests.** Seven pin the conversion itself – the absolute content root, subfolders, backslashes, plugin content, package paths left alone, unusable paths rejected rather than silently prefixed, and the settings repair. The eighth asserts that the editor and the runtime return **identical** results for the same input, which is the specific failure that shipped: not a wrong answer, but two different answers.

#### 📚 Docs

- **New 🆕 : `Docs/Setup_Wizard.html`** (name subject to change) – a control-by-control reference for the Setup Wizard main screen. It covers the four action buttons (including which two fixes `Fix Safe Items` deliberately will not run), every Simple Setup row with the preset tuning numbers, how to read Preflight Results, and what the two escape hatches – **Skip Packaging Validation** and **Allow Custom Startup Level** – actually switch off, together with what you give up by ticking them.

- **New 🆕 : `Docs/Setup_Wizard_Visual.html`** (name subject to change) – the same screen as an interactive diagram. May be actually more immediately helpful than the above. Selecting a control explains it; ticking **Skip Packaging Validation** rewrites the two preflight rows it silences in front of you, and **Allow Custom Startup Level** softens the startup-level error from ERROR to WARN. A flow diagram underneath lights up the stage of the boot each setting governs. Its labels were read from the running wizard rather than transcribed.

#### ✅ Validation

- CI on UE 5.7: build, **36/36** automation tests, coverage floor met.
- Build gate on UE **5.5 / 5.6 / 5.7 / 5.8** in **both** unity modes – 8 runs, all passing,  
static ODR scan clean and plugin-source `C4996` count 0 on every run.

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [September 10, 2026, 6:31pm UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/34 "2026-09-10T18:31:32Z")

</div>

## v1.4.3 update, 2026-09-12 📰

The big 1.4 update keeps producing new hiccups, and this fix remedies another batch of them. Upgrading from 1.4.x needs no action. Upgrading from 1.3.x may need one leftover file deleted.

* * *

#### 🛡 Reliability

- **A cooked smoke could pass judgement on a package built without the plugin in it, and say  
nothing.** The smoke now tees the cook to `Saved/Logs/smoke-cook.log` and fails immediately if the plugin was  
skipped, printing the `EngineVersion` the descriptor actually declares. It also says explicitly  
**not** to repair the content: the map and the Blueprints are never damaged, only unreadable while  
the plugin is absent. Pinning the descriptor and rebuilding restores them with no asset edits.

#### ✅ Validation

- Verified in both directions. Mis-pinned on a 5.5 project the smoke now fails in **29 seconds** ,  
before the game is launched at all, instead of running to a 300-second timeout.

#### 🐛 Fixes

- **A project could stop compiling after upgrading from 1.3.x** , with an error naming two copies of  
the same plugin header:

- **The build now diagnoses it.** `PSOAutopilot.Build.cs` checks for the stale header and fails with  
the full path to delete and the reason it is there. It fires as a rules error _before_ Unreal  
Header Tool runs, so the actionable message replaces the cryptic one rather than following it.  
A guard can only help someone upgrading **to** a version that  
contains it. Anyone moving from 1.3.x to 1.4.2 or earlier still sees the raw Unreal Header Tool  
error, which is why the FAQ now carries the answer as well.

#### ✨ New

- **Advanced \> Audio \> Mute Audio During Warmup** , default **off**. Silences the game while the  
warmup runs, and hands the player back exactly what they had. It is opt-in because changing audio  
behaviour by default would surprise every existing project on upgrade.

#### 🧪 Tests

- `PSOAutopilot.AudioMuter` pins the mix contents – including the flag that applies it to child  
sound classes, which defaults to false and would otherwise mute the master class only – and every  
asymmetry: double mute, double restore, restore without mute, and an unbound muter.

#### ✅ Validation

- CI **43/43** ; build gate 8/8 across UE 5.5/5.6/5.7/5.8 in both unity modes.
- Deliberately **not** claimed: the subsystem wiring has no test. That the mute runs on start and the  
unmute on the Idle transition was verified by reading all four exits, not by a test.

#### ✨ New

- **A version banner.** The version and descriptor number are read from the plugin descriptor at  
runtime rather than hardcoded, so a support log always names the build that produced it.

- **A startup diagnostic** , logged once about five seconds in. It warns when Simple Setup names a  
Loading Level that the project’s startup map is not, or is but holds no Loader actor – the exact  
state in which the plugin is installed, configured, and never runs. It stays silent in the editor  
and in PIE, and once anything has requested a warmup.

#### 🧪 Tests

- `PSOAutopilot.VersionBanner` asserts the descriptor lookup resolves, so a mistyped plugin name  
cannot degrade silently to “unknown” in every log ever sent. The diagnostic’s decision table is  
pinned separately.

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [September 24, 2026, 7:21am UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/35 "2026-09-24T07:21:57Z")

</div>

## v1.5.0 update, 2026-09-26 🎉

Niagara effects are now fixed: a muzzle flash, an impact or an explosion no longer compiles its  
pipelines the first time it fires. The demo is rebuilt as The Stutter Lab, which explains PSO hitches  
and then measures the fix live on the machine it runs on.

Upgrading needs no action. Content to Prepare folders that hold VFX simply start contributing them.

#### 🐛 Niagara fixes

- **A folder of VFX warmed its materials and none of its effects.** `UNiagaraSystem` was not in the  
discovery class filter, so an asset pack’s effects contributed only their materials – and those  
were precached against the static-mesh vertex factory, never the sprite, ribbon and mesh particle  
factories an effect actually draws with. The first time a weapon fired, its muzzle flash compiled on  
the spot.

- **The fix works through the effect asset itself,** which can collect every enabled renderer’s  
material and vertex factory without an instance. Discovery now includes `UFXSystemAsset` (the  
Engine-module base of both `UNiagaraSystem` and Cascade’s `UParticleSystem`), and the warmup finds  
effects wherever it already looks: folders, explicit assets, and through the Blueprints, levels and  
components that reference them – including a property that is only spawned later with  
`SpawnSystemAttached`. Each one’s precache is launched and the loading screen waits for it.  
**No Niagara dependency** was added: the plugin still requires nothing beyond the Engine module.

- **What differs by engine:**

- **Not covered:** effects whose materials are only chosen at runtime (a material set on the spawned  
component). Nothing about those can be predicted from the asset.

#### ✅ Validation

- **Measured in a packaged Development build on UE 5.8** (RTX 5060 Ti, D3D12), with a probe that runs  
the warmup over a folder of Niagara templates and then spawns each one the way gameplay spawns a  
muzzle flash, reading `r.PSOPrecache.DumpStats` before the first spawn:

#### 🧪 Tests

- `PSOAutopilot.DiscoveryFXSystems` – a Content to Prepare folder of Niagara systems discovers every  
one (it found 0 of 7 before the fix).
- `PSOAutopilot.WarmupEngineFXSystemAsset` – an effect is queued, processed and counted, both as an  
asset and through a component’s template, and the FX cursor resets with the rest of the work state.  
Cascade’s `UParticleSystem` stands in for Niagara, so the tests need no Niagara dependency either.

#### 📚 Docs

- The FAQ answers “Does PSO Autopilot prepare Niagara effects?”, with the per-engine behaviour above.
- **Two test names briefly hid two existing tests.** Naming a new test `PSOAutopilot.Discovery.FXSystems`  
turned `PSOAutopilot.Discovery` into a group node in the automation tree, and the existing leaf test  
of that name silently stopped running (the same happened to `PSOAutopilot.WarmupEngine`). Caught by  
diffing the test list against the previous run; the new tests use sibling names instead.

* * *

#### ✨ New: The Stutter Lab, a demo that measures itself

- **The Stutter Lab** (`/Game/Demo/Demo_StutterLab`, generated by `Scripts/CreatePSOAutopilotStutterLab.py`  
and by the wizard’s Generate Demo Content). A self-running loop in six chapters: what a PSO is, a  
_cold start_ with four waves of never-seen materials and Niagara effects, PSO Autopilot preparing an  
equivalent set behind a loading panel, a _prepared start_ with that set, and a scoreboard. A live  
frame-time graph, hitch counter and pop-in counter run throughout. Space skips a chapter, R restarts,  
H hides the overlay.

- **The comparison is built to be fair.** A pipeline can be cold only once per process, so the Cold  
and Prepared sets are structurally identical but share none: every set has its own master materials  
(each bakes a unique constant into its shader code) and its own copies of the effects, whose sprite  
and ribbon renderers are pointed at a set-unique particle material. The generator re-materials every  
renderer it can, disables any it cannot, and checks that no material is shared between sets.  
Cold/Prepared pairs are consumed for good, so a restart can never pass warm content off as cold; once  
they run out, the Lab replays its first measurement and says so.

- **It accounts for the GPU driver’s memory.** A driver keeps compiled pipelines across launches –  
and shares them between the editor and a packaged game. Three things follow, each learned the hard  
way while building the Lab:

- **Its materials are deliberately heavy.** With simple materials only a machine’s very first launch  
showed a cost: afterwards the driver built each new pipeline well inside a frame. Every Lab material  
now carries an unrolled, salted noise loop – the kind of per-pixel work real materials do – so each  
set’s own pipelines take real time to build on every launch.

- **It says where every number comes from.** The engine turns PSO precaching off in the editor, so in  
PIE the Lab runs as _Editor preview_ and tells you to package; a packaged Development build measures  
live and saves the result to `Saved/PSOAutopilot/StutterLabResults.json`. A recorded measurement can  
be played back (Presentation mode, `-StutterLabPresentation`) for Movie Render Queue, whose fixed  
timestep makes live frame times meaningless – and the overlay badges it as recorded, with the engine,  
build configuration, RHI and GPU it came from.

- The overlay is painted in Slate, so the plugin still ships no content, and it is laid out on a  
1920x1080 reference that scales to any viewport.

- The Lab is deliberately _not_ the boot flow’s destination: the loader warms its destination level  
with soft-reference expansion, which would reach the Lab’s Cold content and pre-warm it.

#### ✅ Validation

- **Measured in packaged Development builds on UE 5.7.4** (RTX 5060 Ti, D3D12), 20 items per show:

#### 🧪 Tests

- `PSOAutopilot.StutterLabMeterFrames`, `StutterLabMeterPopIn`, `StutterLabTimeline`,  
`StutterLabResultsJson` pin the scoreboard’s arithmetic: the hitch threshold is strict, items still  
invisible when a show ends are charged rather than dropped, the Autopilot chapter cannot end (or be  
skipped) before the warmup finishes, and results survive a save/load.

* * *

### Smaller fixes

#### 🐛 Fixes

- **The cinematic boot loader added its widget at z-order 0,** under any HUD the game added at the  
default, while every other loader used 999. All loaders now share one constant.

#### ✨ New

- **`Is Audio Muted For Preparation`** (`IsWarmupAudioMuted`), a Blueprint query for a loading screen  
that wants to show a muted icon.

#### 🧪 Tests

- `PSOAutopilot.AudioMuteWiringCancel` and `AudioMuteWiringFinish` drive a real subsystem through a  
run with Mute Audio During Warmup on: muted once it starts, unmuted after a cancel and after a run  
that finishes on its own. Until now only the muter’s own rules were tested, not that the subsystem  
called it.

#### ✅ Validation

- CI (`Run-CI.ps1`, PowerShell 7): build OK, **52/52** tests, coverage 1001 \>= 900, stale-header guard OK.
- Build gates: **8/8** – 5.5, 5.6, 5.7, 5.8 x unity and non-unity: build OK, ODR scan clean, zero C4996.

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [September 24, 2026, 7:23am UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/36 "2026-09-24T07:23:36Z")

</div>

The first video for 1.5.0 is out (still displays 1.4.3 on the widget).

[![](https://d3kjluh73b9h9o.cloudfront.net/original/4X/0/d/6/0d65a609fa0b45583e93dedaf5657113a77cc9d8.jpeg "PSO Autopilot 1.5: Protect your game from shader compilation stutter and crash (UE5.5 - 5.8, 2026)") ](https://www.youtube.com/watch?v=UhXUXsHKOeU)

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [September 30, 2026, 8:14am UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/37 "2026-09-30T08:14:24Z")

</div>

A fairly comprehensive video tutorial is out now:

[![](https://d3kjluh73b9h9o.cloudfront.net/original/4X/b/c/d/bcd6ba45584d31282d93c523cae8649f0cb99dba.jpeg "PSO Autopilot 1.6: Protect your game from shader compilation stutter and crash (UE5.5 - 5.8, 2026)") ](https://www.youtube.com/watch?v=zCUGV9AOb-c)

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [September 30, 2026, 8:48am UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/38 "2026-09-30T08:48:05Z")

</div>

## v1.6.0 update, 2026-10-03 🥂 🧑‍✈️

The warmup can no longer trip the engine’s precaching crash, the setup checks itself, the setup  
wizard’s choices finally reach your config files, and two new editor tools show where startup  
preparation spends its time and which shader permutations it could drop: a rebuilt **Bloat  
Profiler **and the new** Shader Diet**.

### 🗺 1.6.0 at a glance

| | Change | You notice it if… |
| --- | --- | --- |
| 🐛 ✨ | [**Precache Crash Guard**](#2026-09-29----the-precache-crash-guard): the warmup no longer rebuilds the shaders of materials already on screen | your warmup runs behind a menu, a lobby or a loading level |
| 🐛 | [**Texture preheat respects the streaming pool**](#2026-09-29----texture-preheat-respects-the-streaming-pool) | your project is large, or its streaming pool small |
| ✨ | [**Setup checks itself**](#2026-09-29----setup-checks-itself): Message Log page and one notification, at editor start, on a setting change, before Play | always, in the editor |
| 🐛 | [**The setup wizard’s choices reach `Config/Default*.ini`**](#2026-09-29----the-setup-wizards-choices-reach-configdefaultini) | you set the plugin up with the wizard before 1.6 |
| ✨ | [**Bloat Profiler rebuilt**](#2026-09-29----bloat-profiler-rebuilt-and-the-shader-diet): a static scan of the content you prepare, with its shader maps | you want to know what your startup warmup costs |
| ✨ | [**Shader Diet**](#2026-09-29----bloat-profiler-rebuilt-and-the-shader-diet): permutations your project compiles but its content never uses | you want fewer pipelines to prepare, on every platform |

**Upgrading needs no action.** The Precache Crash Guard and automatic setup validation are on by  
default; both can be turned off under **Project Settings \> PSO Autopilot \> Safety**.

> 🔁 **If you set the plugin up with the Setup Wizard before 1.6:** its changes may never have been  
> written to `Config/DefaultGame.ini` / `DefaultEngine.ini` ([why](#2026-09-29----the-setup-wizards-choices-reach-configdefaultini)).  
> Start the editor once: the automatic check lists anything that is missing, and **Fix Safe Items**  
> now writes it to disk.

* * *

### The Precache Crash Guard

#### 🐛 Fixes

- **The warmup rebuilt the shaders of materials already on screen.** It called  
`UMaterialInterface::CacheShaders()` on every material it prepared. On UE 5.5-5.8 that call ends in  
`RecacheUniformExpressions(true)`: the material’s uniform buffer is recreated, but the render state  
of primitives already drawing the material is not, so their cached mesh draw commands keep the old  
buffer and the RHI thread later binds freed memory. A warmup that runs while a menu, a lobby or the  
loading level is visible can crash that way. The call is now made only for a material that has no  
shaders for rendering yet – in a cooked build, practically none. An object still waiting for  
PostLoad has its load finished instead.

- **UE 5.7 made the feature-level `GetMaterialResource` overload final and always null.** The guard  
reads the rendering resource through the shader-platform overload on 5.7 and later; read the old  
way, every material would have looked “never set up” and been rebuilt anyway.

- **A cancelled run dropped its loaded content while its pipelines were still compiling.** Stop and  
cancel released the batch’s objects at once; garbage collection could then destroy a material with  
PSO precache tasks in flight. Those objects are now held until their tasks finish (at most 30 s),  
and released at shutdown.

#### ✨ New

- ⚙ **Project Settings \> PSO Autopilot \> Safety \> Precache Crash Guard** (on by default), plus  
`r.PSOAutopilot.CrashGuard` (1 by default) to turn it off without a rebuild. Either one off  
restores the pre-1.6 behaviour.
- 📊 **Get Precache Crash Guard Stats** (`GetCrashGuardStats`, Blueprint): what the guard did in the  
last run. The log carries a one-line summary at the end of every warmup:  
`Precache Crash Guard: 16 materials already set up for rendering were left alone, 0 finished loading, 0 were set up.`

#### 🏗 Architecture

- `Warmup/PSOPrecacheCrashGuard.h/.cpp`: the decision table as a pure function  
(`DecideCacheShaders`), `PrepareMaterialForPrecache`, which replaces the direct `CacheShaders()`  
call in `FPSOAssetWarmupEngine::WarmMaterial`, and `FPSOPrecacheQuarantine`. The subsystem owns the  
quarantine, reports it to garbage collection (`AddReferencedObjects`) and ticks it while it holds  
anything; `FPSOBatchLoader::GetInFlightWork` hands a cancelled batch over before `Reset()` drops it.

#### 🧪 Tests

- `PSOAutopilot.CrashGuard.Decision` pins the decision table, `PSOAutopilot.CrashGuard.Quarantine`  
the hold-until-complete and 30 s timeout, `PSOAutopilot.CrashGuard.Summary` the log line.

#### ✅ Validation

- Two packaged UE 5.8.3 first launches (D3D12 SM5 and SM6) logged the line above: 16 materials left  
alone, none rebuilt. The 16 also prove the 5.7+ resource lookup: the old overload would have found  
no resource and rebuilt all of them.

* * *

### Texture preheat respects the streaming pool

#### 🐛 Fixes

- **Texture preheat could pin more textures than the streaming pool holds.** Every texture of every  
prepared material was forced resident for 300 s, with no limit, starving video memory on a large  
warmup exactly while the pipelines compile. Forced residency is now reserved against half the  
streaming pool; the rest streams normally, and the log says how much was held and how many  
textures were left to streaming.

#### 🏗 Architecture

- `FPSOTextureStreamingStep::TryForceResident` reserves each texture’s size (all mips) against the  
pool (`FitsResidencyBudget`, pure); the warmup engine reaches the step through the new  
`FPSOWarmupRuntimeContext::TextureStreaming`. An unknown pool, or streaming off, never caps.

#### 🧪 Tests

- `PSOAutopilot.TextureResidencyBudget`: the budget stops at half the pool, remembers what it  
reserved, counts the textures it turned away, and never caps an unknown pool.

#### 📚 Docs

- The log’s “N further textures were left to normal streaming” counts each time the budget said no,  
so a texture shared by several materials can be counted more than once. Residency itself is exact.

* * *

### Setup checks itself

#### ✨ New

- **The Startup Setup checks run automatically,** and report in one place:

- **The wizard and the automatic check now read the engine side too:**

#### 🐛 Fixes

- **The wizard’s discovery preview never counted effects.** Its class filter was not given  
`UFXSystemAsset` when the runtime discovery was (1.5.0), so a folder of Niagara systems previewed as  
“0 VFX” – and a folder of effects and their materials could be called material-heavy – while the  
warmup prepared every effect in it. The preview and the warmup now count the same assets.
- **The automatic check complained in every gameplay map.** “The loading level is not open” and “the  
current editor level is the Level To Open” describe the level open in the editor, not the project,  
so they fired at every editor start and Play. The wizard still shows them; the Message Log, the  
notification and its Fix Safe Items (which would have switched your level) skip them.
- **The notification’s third button was clipped** to “..”: three buttons do not fit a notification’s  
default width. The notification is wider now.

#### 🏗 Architecture

- `Setup/PSOAutopilotSetupMonitor` (an editor subsystem) runs the checks and owns the Message Log page  
and the notification; `Setup/PSOAutopilotEngineChecks` appends the engine rows to the project rows  
for both the monitor and the wizard; validation rows carry `bDependsOnOpenLevel`. The editor module  
now depends on DeveloperSettings, EditorSubsystem, MainFrame and MessageLog.

#### 🧪 Tests

- `PSOAutopilot.Setup.EngineChecks`, `PSOAutopilot.Setup.MonitorPolicy` (the notification fingerprint  
and dismissal), `PSOAutopilot.Setup.PreviewCountsVFX` (0 of 7 before the fix, 7 of 7 after),  
`PSOAutopilot.Setup.MonitorSkipsOpenLevelRows`.

#### ✅ Validation

- The monitor ran at editor start in the CI editor and wrote its rows to the Message Log.
- Live, in a UE 5.8.3 editor with `r.PSOPrecaching=0` set on purpose: “1 of 19 checks need  
attention”, the notification with all three buttons, the Message Log page, and no open-level row  
in a gameplay map.

* * *

### The setup wizard’s choices reach Config/Default\*.ini

#### 🐛 Fixes

- **The setup wizard’s choices were lost on restart and never reached a packaged build.** Every  
wizard action, the notification’s Fix Safe Items and the wizard’s own settings panel saved with  
`SaveConfig()` alone, which for a `defaultconfig` class updates only the running editor’s config:  
nothing reached `Config/DefaultGame.ini` or `DefaultEngine.ini`, so the startup level, loading  
level, content folders and packaging entries were gone after a restart and a cook never saw them.  
Setting them in Project Settings was not affected. The wizard now writes each change to the default  
config file, as Project Settings does. Found while recording the 1.6 tutorial.

#### 🔧 Hardening

- A config file that cannot be written (checked in, not checked out) no longer fails silently: the  
log says which file, and the change applies to the running editor only.
- Every wizard action goes through one persister (`PSOAutopilotConfig::PersistToDefaultConfig`,  
`TryUpdateDefaultConfigFile`). It is injectable, so the tests use a recorder and never rewrite the  
host project’s config.

#### 📚 Docs

- `UPSOAutopilotSettings`’s own `ApplySimpleSetupToAdvancedSettings` and `PostEditChangeProperty`  
still save to the running editor only, as before. Code that calls them outside the wizard should  
persist the settings itself (`TryUpdateDefaultConfigFile`).

#### 🧪 Tests

- `PSOAutopilot.Setup.PersistWritesDefaultConfig` (a temporary ini under `Saved/PSOAutopilot/Tests`),  
`PSOAutopilot.Setup.ActionsPersistSettings`.

#### ✅ Validation

- Live in a UE 5.8.3 editor: Fix Safe Items and the settings panel, then an editor restart; the  
settings and the startup map read back from `DefaultGame.ini` / `DefaultEngine.ini`.

* * *

### Bloat Profiler rebuilt, and the Shader Diet

#### ✨ New

- **The Bloat Profiler scans instead of waiting for a run.** It used to list the top ten assets of the  
last Play In Editor warmup, where PSO requests are zero by design. **Scan Content to Prepare** now  
reads the same content the warmup uses, follows each asset’s package dependencies to the materials  
it reaches, and counts the shader maps and shaders behind them (a master’s, or a static  
permutation’s own). The scan is time-sliced, so the editor stays responsive on large projects.

- **The Shader Diet** (Tools \> PSO Autopilot \> Open Shader Diet, or Window \> Tools) finds shader  
permutations the project compiles, cooks and prepares but its content does not use:

#### 🐛 Fixes

Found while testing the new tools against real content, before release:

- **Shader counts were lower than a cook’s, without saying so.** The editor compiles material shaders  
on demand, as it draws them, so its shader maps are usually partial. Counts from a partly compiled  
map are now shown as lower bounds (`72+`), the summary says how many maps are partial, both CSVs  
carry the flag, and the views sort by Shader Maps, which does not depend on what the editor has  
drawn.
- **A first click on a number column sorted it lightest first.** It now puts the heaviest at the top; a  
second click reverses it.
- **The Shader Diet showed “~0 shaders saved” for every finding, and blamed masters that were “still  
compiling”.** A finding describes a permutation nothing draws, and the editor compiles shaders only  
for what it draws, so it never has those shaders to count. Savings it cannot count now show a dash  
(–), the summary and the column tooltip say why, and a master without a shader map is described as  
such.

#### 🏗 Architecture

- `Diet/PSOAutopilotBloatScan` (the static scan), `Diet/PSOAutopilotShaderDiet` (the analysis and its  
fixes) and `Diet/SPSOAutopilotShaderDiet` (the tab); `SPSOAutopilotProfiler` is rewritten on top of  
the scan. The Shader Diet tab registers with the other two; the editor module now depends on  
RenderCore and RHI.

#### 📚 Docs

- **Known limit:** neither tool gives cook-accurate shader counts. That needs the engine’s shader-map  
layout, which is private to the engine; counts are labelled as lower bounds instead of guessed.

#### 🧪 Tests

- `PSOAutopilot.BloatScan.Helpers`, `.Empty`, `.Graph`, `.SortClick`;  
`PSOAutopilot.ShaderDiet.ProjectSettings`, `.UsageFlags`, `.Permutations`, `.SavingText`;  
`PSOAutopilot.TabVisibility` now covers the Shader Diet tab.

#### ✅ Validation

- Live, in a UE 5.8.3 editor: the Bloat Profiler scan, its sorts, views, double-click and CSV export;  
the Shader Diet analysis and Apply.

* * *

### Housekeeping

#### 🔧 Hardening

- `Plugins/PSOAutopilot/Content/Scripts` held stale copies of the helper scripts that nothing loaded  
(the packer already excluded them). Removed; `Plugins/PSOAutopilot/Scripts` is the only copy.

#### 📚 Docs

- README, Manual and FAQ describe the Precache Crash Guard, the automatic setup check, the rebuilt  
Bloat Profiler and the Shader Diet; TESTING lists the 70 tests and the new manual checks.
- A narrated tutorial film (about ten minutes) walks through 1.6, and opens on the first launch  
measured below.

* * *

### Release verification

#### 🛡 Reliability

- `Run-CI.ps1` on UE 5.7: build OK, **70/70** automation tests (52 before 1.6), coverage 1001 \>= 900,  
stale-header guard fired as designed. Every new test was watched failing against a stub (or the  
unfixed code) before its implementation.
- `Run-PluginBuildGate.ps1` **8/8** on the final 1.6.0 tree: UE 5.5, 5.6, 5.7 and 5.8, each with full  
unity (Fab parity) and strict non-unity (`-StrictIncludes`): build OK, ODR scan clean (76 .cpp),  
0 C4996, 0 compiler warnings.
- Each change above also builds on its own: the first four one by one (strict non-unity, UE 5.7);  
the last one is the final tree.

#### ✅ Validation – a first launch, measured

The Stutter Lab in a packaged UE 5.8.3 Development build (D3D12 SM6, RTX 5060 Ti), on a shader  
platform this machine had never run, so every pipeline in it was new to the GPU driver. Recorded in  
real time, one run:

| | 🔴 Cold waves | 🔵 Prepared by PSO Autopilot |
| --- | --- | --- |
| Items that popped in (invisible for at least one frame) | 10 of 22 | 6 of 22 |
| Longest wait for a pipeline | **310 ms** (19 frames) | **63 ms** (2 frames) |
| Effects | all 6 held back, 103-262 ms | all 6 within 2 frames (32-63 ms) |
| Average frames invisible | 5.55 | 0.36 |
| Frames over 33 ms / worst frame | 19 / 35.4 ms | 18 / 36.5 ms |

- **Why nothing “stutters”:** with UE 5.8’s default proxy creation strategy, a late pipeline makes an  
object invisible instead of stalling the frame. The frames over 33 ms are the same in both shows:  
they are this scene’s frame time, not pipeline compiles.
- **Caveats:** a first launch can be measured once per shader platform (the driver keeps what it  
builds), so this is one run. The prepared column’s six are effects whose first one or two frames of  
activation the Lab counts as a pop-in.

---

<div class="post-metadata">

**Author:** ![andgregori](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/andgregori/32/1148407_2.png) [@andgregori](https://forums.unrealengine.com/u/andgregori)\
**Post date:** [October 24, 2026, 4:40pm UTC](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140/39 "2026-10-24T16:40:37Z")

</div>

# 1.7.0 update, 2026-10-26 👨‍✈️

The smart-skip cache now knows the render configuration, the GPU driver and the project version, a  
warmup that finds nothing is no longer remembered as done, a cancelled batch stays alive until its  
pipelines finish, and content mounted at runtime can be prepared as it arrives. Effects get their  
custom-depth pipelines, and warmups that run during gameplay leave on-screen textures alone.  
The **Shader Diet** now knows which materials ship, counts the shader maps static permutations  
really compile, finds landscape layer combinations and measures what a fix saves;  
the **Bloat Profiler** reads exact counts from a cook; and both run headless for CI.

### 🗺 1.7.0 at a glance

| | Change | You notice it if… |
| --- | --- | --- |
| 🐛 | [**Smart skip knows the render configuration and the driver**](#2026-10-09----smart-skip-knows-the-render-configuration): a new RHI, anti-aliasing method, quality level or GPU driver prepares again | your players change graphics settings, or update their driver |
| 🐛 | [**Smart skip knows the build**](#2026-10-13----patches-effects-and-gameplay-time-warmups): the project version is part of the key, and 16 configurations are remembered (was 8) | you ship patches that edit content, or your players switch graphics presets |
| 🐛 | [**A warmup that finds nothing is not remembered**](#2026-10-09----smart-skip-knows-the-render-configuration): it warns, reports `Success=false` and looks again next start | your Content to Prepare resolves to nothing in a build |
| 🐛 | [**Effects get their custom-depth pipelines**](#2026-10-13----patches-effects-and-gameplay-time-warmups) | your effects turn on custom depth while the game runs (outlines, highlights, masks) |
| 🐛 | [**Gameplay-time warmups leave textures alone**](#2026-10-13----patches-effects-and-gameplay-time-warmups) | you use Warmup Content (Time-Sliced) or Prepare Content Mounted at Runtime |
| 🐛 | [**Simple Setup says when it ignores the Advanced folders**](#2026-10-13----patches-effects-and-gameplay-time-warmups) | you configured Content Folders in Advanced mode, or in an ini file |
| 🐛 | [**The Crash Guard holds a cancelled batch until it finishes**](#2026-10-09----the-crash-guard-holds-a-cancelled-batch-until-it-finishes): no 30 s limit | you cancel warmups on slow hardware |
| ✨ | [**Content mounted at runtime**](#2026-10-09----content-mounted-at-runtime) (opt-in) and **Warmup Content (Time-Sliced)** | you mount mods, DLC or other pak files while the game runs |
| ✨ 🐛 | [**A Shader Diet that knows what ships**](#2026-10-09----a-shader-diet-that-knows-what-ships): a Ships column, soft references, distinct permutations | you act on the Diet’s findings |
| ✨ | [**Landscape layer combinations**](#2026-10-09----a-shader-diet-that-knows-what-ships) in the Shader Diet | your project has a painted landscape |
| ✨ | [**Measure Savings** and **Use Cooked Counts**](#2026-10-09----measured-savings-and-cooked-counts) | you want numbers instead of dashes and lower bounds |
| ✨ | [**Headless runs**](#2026-10-09----headless-runs): `-run=PSOAutopilotShaderDiet`, with `-FailOn=high` for CI | you want the reports in a build pipeline |
| 🐛 | [**The scoreboard and the Bloat Profiler fit their text**](#2026-10-24----the-scoreboard-and-the-bloat-profiler-fit-their-text) | you run the Stutter Lab on a machine with a long GPU name, or read long paths in the Bloat Profiler |
| 🐛 ✨ | [**Pak chunks, measured savings and a CI baseline**](#2026-10-24----pak-chunks-measured-savings-and-a-ci-baseline): downloaded pak chunks are prepared, 64 assets at a time; Measure Savings compiles the masters in full; Ships knows the game mode and game instance; `-Baseline=` for `-FailOn=high` | you download pak chunks during play, measure the Diet’s savings, or gate CI on the Diet |
| 🐛 | [**A downloaded chunk and a dedicated server, run for real**](#2026-10-24----a-downloaded-chunk-and-a-dedicated-server-run-for-real): a group stuck on slow compiles no longer stalls the rest of a pack; the last group is let go; the 10 s mount wait is 10 s | you prepare mounted content, or run dedicated servers |

**Upgrading needs no action.** The first start after upgrading prepares once: fingerprints stored by  
1.6 never match the new key. **Prepare Content Mounted at Runtime** is off by default.

* * *

Smart skip knows the render configuration

#### 🐛 Fixes

- **The smart-skip key ignored the render configuration.** It hashed the engine version, the  
dependency settings and the prepared assets, so a start with another RHI, anti-aliasing method or  
quality level reused the last run’s fingerprint and skipped a warmup whose pipelines were now the  
wrong ones. The key now also hashes a schema version, the RHI, the shader platform, the feature  
level, the 19 console variables that change which pipelines a material needs  
(`r.AntiAliasingMethod`, `r.MSAACount`, `r.MaterialQualityLevel`, the velocity, scene colour, HDR,  
global illumination, reflection, virtual shadow map and Nanite switches, and the `sg.*` quality  
levels; a missing one is written as `-`), and the GPU vendor, adapter and driver version: a driver  
update discards the driver’s pipeline cache.

- **A warmup that found nothing was remembered as a success.** When Content to Prepare resolved to  
no assets, the run finished with `Success=true` and stored its fingerprint, so the next start  
reported a cached run and a broken setup looked like a fast one. It now logs a warning naming  
Content to Prepare, shows “Nothing to prepare.”, logs `Success=false (nothing to prepare)` and is  
not remembered. `OnWarmupComplete` still fires, so a startup flow never waits on it.

#### ✨ New

- 🪝 **A game can add its own parts to the key:** bind `PSOAutopilot::CacheKey::OnAppendCacheKey()`  
(a native multicast delegate) and append strings such as an upscaler mode or a graphics preset.  
Parts are sorted, so binding order does not matter.
- 🧾 The log names the render configuration once per run:  
`Smart skip key, render configuration: v=3;rhi=D3D12;sp=PCD3D_SM6;fl=SM6;project=1.0.0.0;...`

#### 🏗 Architecture

- `Warmup/PSOWarmupCacheKey.h/.cpp`: `GatherRenderConfig` (game thread) and the pure  
`BuildRenderConfigSignature`; the hash itself stays on the worker thread.  
`FPSOWarmupFingerprintStore::ShouldRememberRun` decides what is remembered.

#### 🧪 Tests

- `PSOAutopilot.CacheKey.Signature` (every part changes the signature; hook parts are order-free),  
`PSOAutopilot.CacheKey.Gather` (the live gather fills every part) and  
`PSOAutopilot.SmartSkip.RemembersOnlyRealRuns` (no assets, failures or the editor preview are never  
remembered).

#### ✅ Validation

- The three tests above, watched failing against stubs first, then green on UE 5.7 with the full suite.

* * *

### The Crash Guard holds a cancelled batch until it finishes

#### 🐛 Fixes

- **A cancelled batch was released after 30 s even if its pipelines were still compiling.** On slow  
hardware a precache task can outlive that, and garbage collection could then destroy a material  
with tasks in flight: the hazard the guard exists to prevent. The hold now has no time limit by  
default; an entry leaves only when every task has finished. A hold longer than 60 s logs one  
warning, so a stuck task is visible:  
`Precache Crash Guard: 12 objects from a cancelled run are still held after 60 s; their graphics tasks have not finished.`  
A positive `TimeoutSeconds` still releases early, for anyone who sets one.

#### 🧪 Tests

- `PSOAutopilot.CrashGuard.UntimedHold`: an entry outlives any wait while a task runs, warns once,  
and leaves when the task completes.

#### ✅ Validation

- Watched failing against the 30 s default, then green on UE 5.7 with the existing  
`CrashGuard.Quarantine` test.

* * *

### Content mounted at runtime

#### ✨ New

- 📦 **Project Settings \> PSO Autopilot \> Advanced \> Runtime Content \> Prepare Content Mounted at  
Runtime** (off by default). When it is on and a pak file or plugin mounts content during play,  
PSO Autopilot finds the warmable assets under the new root (the same class filter and exclusions  
as Content to Prepare, without maps) and prepares them in the background through the time-sliced  
JIT warmup. Nothing happens in the editor outside Play In Editor. The assets are found through  
the asset registry, so content whose registry is loaded on mount (a plugin or game feature) is  
found; a bare pak whose registry is never loaded logs that nothing appeared within 10 s.
- ⏱ **Warmup Content (Time-Sliced)** (`QueueJITWarmupForAssets`, Blueprint): prepares any list of  
assets during play. It loads them asynchronously in groups of 64 and holds them until they have  
been prepared, so garbage collection cannot take them mid-warmup, then lets them go.

#### 🏗 Architecture

- `FPSOWarmupDiscovery::MakeWarmableClassFilter` and `FindWarmableAssetsUnder`: one class filter  
for Content to Prepare and mounted content. The JIT manager reports the assets it holds to garbage  
collection.

#### 🧪 Tests

- `PSOAutopilot.RuntimeContent.Find` (a mounted root’s warmable assets, maps excluded),  
`.Gate` (game worlds only, and only when the setting is on), `.Hold` (the JIT manager holds each  
queued asset once, for garbage collection to see) and `.Release` (it lets go once the queue has  
drained; the subsystem keeps ticking it until then).

#### ✅ Validation

- Watched failing first, then green on UE 5.7 with the full suite.

* * *

### A Shader Diet that knows what ships

#### ✨ New

- 🚢 **Ships.** The Diet now works out what a cook of the project includes, the way the cooker does:  
Always Cook maps; Maps to Cook (or, if that list is empty, the `AllMaps` section, or, if both are  
empty, every map under `/Game`, so it never claims a material does not ship when it cannot know);  
Directories to Always Cook; the default maps; every primary asset that is not Never Cook; and  
everything those reach through game dependencies. With **Cook Everything** , everything ships.  
Every finding says whether its material ships; one that does not “costs editor compile time  
only” and sorts after the ones that do. The tab and the CSV gain a **Ships** column.
- 🏔 **Landscape layer combinations.** Each painted landscape component compiles the shaders of its  
own combination of layers, so an open world can compile hundreds of copies of one landscape  
material. The Diet counts, per landscape material, the components, the distinct combinations, the  
distinct layer sets, the most and average layers per component and Max Painted Layers Per  
Component, and reports 16 combinations or more, naming how many repeat a layer set and differ only  
in weightmap packing. There is no automatic fix: it suggests Max Painted Layers Per Component,  
fewer layers, and a consistent layer order. The tab reads the open level (and says when only part  
of a World Partition landscape is loaded); the commandlet reads every map that ships.

#### 🐛 Fixes

- **Static permutations counted instances, not shader maps.** Instances with the same static  
switches, landscape layers and base property overrides compile one shader map between them. The  
rule now reports on distinct parameter sets (threshold 8, unchanged), names instances, distinct  
sets and how many ship, and estimates each distinct set once.

- **“High” confidence missed soft references and materials assigned from code.** Referencers now  
include soft references, and a master that nothing but materials references is reported at **Low**  
confidence: “Nothing references this material or its instances, so it may be assigned from code  
or loaded by path.”

#### 🏗 Architecture

- `Diet/PSOAutopilotCookReach.h/.cpp` (the roots and a pure breadth-first walk),  
`Diet/PSOAutopilotLandscapeDiet.h/.cpp`, and `Diet/PSOAutopilotDietRunner.h/.cpp`: one runner that  
the tab steps from Tick and the commandlet steps in a loop, so the rules cannot drift between them.  
Loading a map’s landscape actors refuses to run outside a commandlet, so the tab never loads  
actors into an open level.

#### 🧪 Tests

- `PSOAutopilot.CookReach.Walk` and `.MapRoots` (on a synthetic graph), `PSOAutopilot.ShaderDiet.Ships`,  
`.NoReferencer`, `.Permutations` (rewritten for distinct sets), `.Landscape`, `.Sort` and  
`.LandscapeMaps`.

#### ✅ Validation

- Watched failing first, then green on UE 5.7 with the full suite. On the demo project the  
commandlet’s reach covers 1,259 shipping packages from 164 master materials.

* * *

### Measured savings and cooked counts

#### ✨ New

- 📏 **Measure Savings** (Shader Diet tab): loads the masters behind usage-flag and permutation  
findings, waits for their shaders to compile and fills in what each fix saves; a permutation  
finding estimates distinct sets × the master’s shaders (`~`). It needs a renderer: the editor has  
one; the commandlet needs `-AllowCommandletRendering` and says so otherwise.
- 🧮 **Use Cooked Counts** (Bloat Profiler): the editor compiles shaders on demand, so its counts are  
lower bounds (`72+`). This reads your last cook’s shader libraries under  
`Saved/Cooked/<Platform>/<Project>/Content` (this editor’s shader format when the cook has it)  
and replaces every count with the exact one, then re-totals the assets and folders. With cooks  
for several platforms it reads the one that has this editor’s shader format; a material the cook  
does not contain (added since, say) keeps the editor’s count and its `+`.

#### 🐛 Fixes

- **A project setting’s saving read `0` when no shader map had been read.** Without a renderer (any  
commandlet) no master has an editor shader map, and the static-lighting rows reported that turning  
them off saves nothing. They now show a dash: unknown.

#### 🏗 Architecture

- `Diet/PSOAutopilotCookedShaders.h/.cpp` reads `ShaderArchive-*-<format>-*.ushaderbytecode` with the  
engine’s own `FSerializedShaderArchive` (archive version 2 on UE 5.5–5.7, 3 on 5.8) and the  
matching `ShaderAssetInfo-*.assetinfo.json`.

#### 🧪 Tests

- `PSOAutopilot.ShaderDiet.Measure` (with a renderer it measures the engine’s default material),  
`.UnknownCount`, `PSOAutopilot.CookedShaders.Counts` and `.Pick`, and  
`PSOAutopilot.BloatScan.CookedCounts` and `.NotInCook`.

#### ✅ Validation

- On the demo project’s Windows cook, the commandlet read the PCD3D\_SM6 libraries (16,894 unique  
shaders) and all 1,001 Bloat Profiler asset rows came out exact (no `+`).
- `PSOAutopilot.ShaderDiet.Measure` run once with a D3D12 renderer (not `-NullRHI`) measured both  
kinds of estimate on the engine’s default material.

* * *

### Headless runs

#### ✨ New

- 🤖 **`-run=PSOAutopilotShaderDiet`** runs the Shader Diet and the Bloat Profiler without the editor  
UI and writes the same CSV files as the tabs:

#### 🧪 Tests

- `PSOAutopilot.ShaderDiet.Commandlet` (options, defaults and the exit code).

#### ✅ Validation

- On the demo project: `-Report=diet,bloat,landscape -CookedShaders=auto` exits 0 and writes  
`ShaderDiet.csv` and the three `BloatProfiler_*.csv`; adding `-FailOn=high` exits 1 on its three  
high-confidence project settings.
- With a renderer (`-Measure -AllowCommandletRendering`, D3D12) the same run exits 0 and reads the  
Windows cook; the demo project has no usage-flag or permutation finding to measure, which the  
renderer run of `PSOAutopilot.ShaderDiet.Measure` covers.

* * *

### Patches, effects and gameplay-time warmups

#### 🐛 Fixes

- **A patch that edited content without adding or removing an asset was skipped.** A cooked build  
has no file dates, so the asset half of the key is the list of asset paths, and a source-built  
engine often reports the same engine version for every build. The key now also hashes the  
project’s version (Project Settings \> Description \> Project Version), and the schema moves to  
`v=3`. Bump the version for each patch you ship, or add your own build number through  
`OnAppendCacheKey()`.

- **The fingerprint store kept 8 entries.** Every combination of RHI, anti-aliasing, quality levels  
and game parts is its own fingerprint, so a player who tried a few presets pushed out the one they  
went back to, and it prepared again. The store keeps 16.

- **Effects that turn on custom depth while the game runs compiled on first draw.** Niagara prepares  
an effect with default parameters, custom depth off, so an outline, highlight or mask switched on  
for an effect at runtime found no custom-depth pipelines ready. For every material used with  
Niagara sprites or ribbons, the warmup now also asks for the custom-depth pass (movable, both  
culling directions) through the vertex factory the effect draws with, found by name: still no  
Niagara dependency. The log counts them once per run:  
`Effects: 24 materials had their custom-depth pipelines prepared.`  
To cover custom depth for every primitive, not only effects, the engine has  
`r.PSOPrecache.CustomDepth=2` (read-only: set it under `[ConsoleVariables]` in  
`DefaultEngine.ini`), at the cost of more pipelines.

- **Warmups that run during gameplay touched textures on screen.** **Warmup Content (Time-Sliced)**  
and **Prepare Content Mounted at Runtime** forced textures resident against the startup warmup’s  
budget, which nothing gave back until the next warmup, and rebuilt the render resources of virtual  
textures that could be drawing. They now prepare pipelines only and leave textures to the engine’s  
streaming.

- **Simple Setup ignored the Advanced Content Folders without saying so.** Simple Setup prepares only  
its own choices, so a project configured in Advanced mode, or in its ini files, and left in Simple  
Setup prepared nothing from those folders. The setup check and the warmup’s log now say so:  
`Simple Setup has no Content to Prepare, so the 3 Content Folder(s) set for Advanced mode are not prepared. Add them to Content to Prepare, or set Setup Mode to Advanced.`

- The precache throttle’s comment blamed a D3D12 uniform-buffer overload; it limits how many  
pipeline compiles are in flight, and now says so.

#### 🧪 Tests

- `PSOAutopilot.CacheKey.Signature` and `.Gather` (the project version changes the key and is  
gathered), `PSOAutopilot.SmartSkip.RemembersSixteenConfigurations`,  
`PSOAutopilot.EffectCustomDepth.Params` and `.VertexFactories`,  
`PSOAutopilot.RuntimeContent.LeavesTexturesAlone` and `PSOAutopilot.SettingsIgnoredAdvancedFolders`.

#### ✅ Validation

- Each new test watched failing against a stub first (7 of 7), then green on UE 5.7: `Run-CI.ps1`  
97/97, and the build gates 8/8 on UE 5.5–5.8.
- Not yet run in a packaged build: the four 1.7.0 rows added to `TESTING.md` for these changes.

* * *

### The scoreboard and the Bloat Profiler fit their text

#### 🐛 Fixes

- **The Stutter Lab’s scoreboard ran text past its card.** The provenance line (which names the GPU),  
the headline and the footnote were drawn on one line at a fixed size; at 1080p and 1440p the  
provenance and the footnote ran past the card’s right edge, and a longer GPU name ran further. Each  
now shrinks to fit the card, down to a floor, measured again at the whole-pixel size it is drawn at.
- **The Bloat Profiler cut long asset paths and type names off at the column edge,** so a path ran  
straight into the type beside it (`…m_SimplMaterialInstanceConsta`). They end in an ellipsis, with  
a gap before the next column, and the tooltip shows them in full.
- **Use Cooked Counts printed the cook’s full path** (`F:/…/Saved/Cooked/Windows/<Project>/Content`).  
A cook inside the project is shown relative to it: `Saved/Cooked/Windows/<Project>/Content`.

#### 🧪 Tests

- `PSOAutopilot.StutterLabFitText` (text that fits keeps its size, the fit, the step down when a font  
rounds up to the next pixel, the floor) and `PSOAutopilot.CookedShaders.DisplayPath` (inside and  
outside the project, backslashes, a sibling folder that shares the project folder’s name). The  
ellipsis is declarative Slate with nothing to assert on; it was checked on screen.

#### ✅ Validation

- Both new tests watched failing against stubs, then green. `Run-CI.ps1` on UE 5.7: build OK,  
**99/99** , coverage 1001 \>= 900, stale-header guard as designed. `Run-PluginBuildGate.ps1` **8/8**  
on UE 5.5–5.8, unity and non-unity: 0 plugin warnings (the C4996 lines on 5.7 are all in  
engine headers).
- On screen in the UE 5.7.4 editor: a Movie Render Queue frame of the scoreboard at 2560x1440 (every  
line inside the card) and the Bloat Profiler with cooked counts (relative path, ellipses).

* * *

### Pak chunks, measured savings and a CI baseline

A code review of the 1.7.0 tree before packaging found fifteen problems, all in features that had not  
shipped yet. Each was fixed test-first.

#### 🐛 Fixes: content mounted at runtime

- **A pak mounted under `/Game` was never prepared,** although the setting said pak files. It listened  
only for plugin content roots, and a downloaded chunk of the game’s own cook (ChunkDownloader’s  
`pakchunk12-Windows.pak`) mounts none. It now watches pak mounts too: a chunk other than 0 is prepared  
from the assets the cooked asset registry tags with that chunk, which are there from the start, so one  
look settles it. Paks can mount on a worker thread; the work runs on the game thread. A pak cooked  
separately and mounted straight under `/Game` is still not seen. The 2026-10-09 entry’s “a bare pak  
logs that nothing appeared within 10 s” never happened for such a pak; a chunk with nothing warmable  
now logs `Found no warmable assets in pak chunk N.`
- **A mounted pack was in memory all at once.** Warmup Content (Time-Sliced) requested every group of 64  
at the same moment, and the JIT manager holds what it loads until its queue drains. The groups now  
load one at a time: the next waits until the last has been prepared and let go.
- **The editor could hand over part of a plugin.** It registers a mounted plugin’s assets over several  
frames, and the first poll that found any was taken as the whole plugin. A plugin’s mount is ready  
once its count holds for two polls while the registry is not gathering (or after 10 s). An asset that  
two mounts report, a pak that carries a plugin, is prepared once.
- **Dedicated servers loaded mounted packs** for pipelines they never create. Mounted content is now  
watched only in a process that can render.
- In a cooked game, mounted content now falls back to everything under the root when the class filter  
finds nothing, as the startup warmup’s folder fallback does. Queueing no longer deduplicates with a  
linear search per path (a 20,000-asset pack spent about half a second in one frame). A Content to  
Prepare folder that is not a content path logged its error on every half-second poll, about twenty  
times per mount; each mount now reads the settings once.

#### 🐛 Fixes: the end of a run

- **A run that only loads packages before play said `Nothing to prepare.` and `Success=false`,** and its  
package preload failures were missing from the summary. An Advanced setup that preloads packages and  
prepares nothing now ends `Ready.` with `Loaded N packages before play` and  
`Success=true (packages loaded, no shaders to prepare)`, or `Ready, with warnings.` with its preload  
failures counted. The no-assets warning is a plain log line for that setup. A run with nothing at all  
is unchanged.

#### 🐛 Fixes: Shader Diet

- **Ships missed what the cooker always cooks:** the default game mode, the server game mode and the game  
instance class under Maps & Modes. Content reached only through a Blueprint game instance read as not  
shipping, so `-FailOn=high` could pass on a finding that ships. The roots now follow the cooker’s own  
list (`UCookOnTheFlyServer::GetGameDefaultObjects`).
- **Measure Savings in the editor measured nothing new.** The editor compiles material shaders as  
something draws them, so the tab re-counted the same partial maps and showed the result as measured,  
and it waited on every compile in the editor. For a permutation finding whose master had no map it  
turned a count into unknown. It now submits every shader the masters’ maps list for this editor’s  
platform, waits for those masters alone, and leaves an estimate alone when it cannot count. The  
commandlet’s `-Measure` uses the same code.
- **Distinct static permutations were miscounted both ways.** The key ignored static component masks and  
material layers, and compared the base property overrides as stored, so a box ticked with the parent’s  
own value counted as a new set. It now compares what the instance compiles with: the properties  
`UMaterialInstance::HasOverridenBaseProperties` compares.
- **Measure Savings reset applied rows to Apply,** so a fix could be applied twice. Rows are measured in  
place; applied rows keep their state and their number, and are not measured.
- **`-Report=landscape` analysed every master material first,** for findings it then threw away. It  
reads only landscapes now.
- A master that failed to load counted as one with a shader map, so the project settings’ savings could  
read 0 instead of unknown.

#### ✨ New

- **`-Baseline=<ShaderDiet.csv>`:** `-FailOn=high` ignores the findings an earlier run’s CSV lists, so a  
project can accept the findings no setting fixes (a landscape’s layer combinations, static  
permutations) and fail only on new ones. An unreadable baseline exits 2.

#### 🏗 Architecture

- `FPSOMountedContentWatcher` (mounts waiting for their assets to register), `FPSOJitLoadQueue` (one group  
at a time), `FPSOWarmupDiscovery::FindWarmableAssetsInChunk` and `SelectMountedAssets`,  
`FPSOWarmupProgressReporter::DescribeCompletion`, `FPSOAutopilotCookReach::AddGameDefaultObjects`,  
`FPSODietRunOptions::Masters` and `FPSODietCommandletOptions::MakeRunOptions`.

#### 🧪 Tests

- 13 new: `PSOAutopilot.RuntimeContent.Settle`, `.SettingsOnce`, `.Chunks`, `.CookedFallback`, `.Groups`  
and `.QueueScales`; `PSOAutopilot.ProgressCompletion`; `PSOAutopilot.CookReach.GameDefaults`;  
`PSOAutopilot.ShaderDiet.MeasureCompiles`, `.PermutationIdentity`, `.MeasureKeepsApplied`,  
`.RunnerMasters` and `.Baseline`. `RuntimeContent.Gate` now covers servers and pak chunk numbers.
- `ShaderDiet.MeasureCompiles` needs a renderer and returns early under `-NullRHI`, which is how  
`Run-CI.ps1` runs. Run `PSOAutopilot.ShaderDiet` without `-NullRHI` too (TESTING.md says so now).

#### ✅ Validation

- Every new test watched failing against a stub first (13 of 13, and the one-look rule for chunks after  
them). `ShaderDiet.MeasureCompiles` was watched failing and passing in the UE 5.7.4 editor on D3D12  
(RTX 5060 Ti), where all 17 `PSOAutopilot.ShaderDiet` tests pass.
- `Run-CI.ps1` on UE 5.7: build OK, **112/112** (69 runtime, 43 editor), coverage 1001 \>= 900,  
stale-header guard as designed.
- `Run-PluginBuildGate.ps1` **8/8** on UE 5.5–5.8, unity and non-unity: 0 plugin warnings, 0  
plugin C4996 (the 100 and 312 C4996 lines on 5.7 are all in engine headers).
- A packaged build that mounts a downloaded pak chunk, and a dedicated server, were run the same day and found three  
more defects: see the next entry.

* * *

### A downloaded chunk and a dedicated server, run for real

The two runtime checks the previous entry left open were run: a packaged UE 5.7.4 build mounting a downloaded pak  
chunk during play, and a dedicated server. They found three defects in the new mounted-content code, fixed below.

#### 🐛 Fixes

- **One group whose pipelines compile slowly stalled the rest of a mounted pack.** The time-sliced warmup holds a group  
until the engine has compiled its pipelines, and the next group waited for that. Those requests compile at the  
engine’s lowest priority, and in the first two packaged runs they did not finish for as long as the runs lasted  
(15 s), so the run stopped at 64 of 96 assets. A group still compiling 10 s after its last asset was prepared now goes  
to the Precache Crash Guard, which holds each object until its tasks finish, and the next group loads:  
`Time-sliced warmup: N assets still have M pipeline tasks compiling after 10 s; ...`. Later runs, including a first  
launch from a new folder, compiled within a second and never needed it; the hand-off is covered by a test, not yet  
seen in a packaged run.
- **The last group of every Warmup Content (Time-Sliced) call stayed in memory for the session.** The loader kept the  
streamable handle of the group it had requested, and a handle keeps its assets loaded. After a full garbage  
collection, 32 of the 96 assets were still in memory; they are let go once the JIT warmup has them, and none remain.  
What the warmup lets go of is reclaimed by the next garbage collection: during gameplay it forces none.
- **The 10 s wait for a mounted plugin’s assets lasted about five minutes.** The poll runs on a ticker every half  
second, but a ticker hands its delegate the frame’s delta, and the wait added those up. It is measured on the clock  
now. Pak chunks (looked at once) and plugins whose assets arrive and settle were not affected.

#### ✨ New

- On a dedicated server with the setting on, the log says why nothing happens:  
`Prepare Content Mounted at Runtime is on, but this process never renders (a dedicated server): content mounted during play is left alone.`

#### 📚 Docs

- The Crash Guard’s long-hold warning no longer says the objects are “from a cancelled run”:  
`Precache Crash Guard: N objects are still held after 60 s; their graphics tasks have not finished.`

#### 🧪 Tests

- `PSOAutopilot.RuntimeContent.HandOff` (a drained group waits 10 s on its tasks, then is handed over; finished tasks  
restart the wait). `RuntimeContent.Settle` now measures the wait on the clock.

#### ✅ Validation

- **Packaged pak chunk** , in a scratch project outside the repo (`PSO Autopilot Sandbox Work\PSOChunk57`): 96 assets  
(8 materials, 80 instances, 8 meshes) labelled into chunk 1, packaged Development with chunks and IoStore on UE 5.7.4,  
D3D12 SM6, RTX 5060 Ti. `pakchunk1` was held back at startup and mounted 3 s into play on a worker thread through  
`FCoreDelegates::MountPak`, as ChunkDownloader does. Final run: `Preparing 96 assets from pak chunk 1.` 0.5 s after the  
mount, all 96 prepared within about a second (24 PSO requests: the instances share their parents’ pipelines), and 0  
of 96 still loaded after a full garbage collection. With the setting overridden off on the command line, the same  
mount prepared and loaded nothing.
- **Dedicated server** : the Launcher builds of UE 5.5–5.8 cannot build a Server target, so the server was the UE 5.7.4  
editor binary in `-server` mode (`IsRunningDedicatedServer`, `FApp::CanEverRender` false), uncooked. It logged the  
line above, and a content path registered 3 s into play (the event a plugin mount raises) got no reaction. The same  
binary in `-game` logged the subscription and, for that empty mount, `Found no warmable assets in content mounted at /ChunkProbe.` 10.5 s after the mount.
- Each defect was shown in the packaged or `-game` run first (stalled at 64 of 96; 32 still loaded after a garbage  
collection; no give-up within 16 s), then fixed test-first where a unit test can see it: `RuntimeContent.HandOff` and  
the clock check in `RuntimeContent.Settle` failed against stubs. The handle fix has no unit test (it needs a game  
instance and an async load); the packaged run is its check, 32 before and 0 after.
- `Run-CI.ps1` on UE 5.7: build OK, **113/113** (70 runtime, 43 editor), coverage 1001 \>= 900.
- `Run-PluginBuildGate.ps1` **8/8** on UE 5.5–5.8, unity and non-unity: 0 plugin warnings, 0 plugin  
C4996.

* * *

[Previous page](https://forums.unrealengine.com/t/gregorigin-pso-autopilot-shader-compilation-warm-up-system-to-prevent-lag-crashes/2704140.md?page=1)
