PSO Autopilot
Validation & Test Report
Comprehensive findings from code review, bug resolution, and end-to-end runtime validation of the Pipeline State Object warmup system for Unreal Engine 5.7.
All Systems Operational — Warmup Pipeline Verified
18 issues identified and resolved. 1,000 material assets discovered, batch-loaded, shader-compiled, and texture-streamed across 20 batches with garbage collection — all within a smooth, time-sliced frame budget. Full pipeline validated end-to-end in standalone game mode.
Test Environment & Key Metrics
- Unreal Engine 5.7
- D3D12 / SM5
- Visual Studio 2026 (14.44)
- Windows 11 26200
- Module: Runtime / PreDefault
- Platforms: Win64, Mac, Linux
- CanContainContent: false
- Dependencies: 8 modules
- Map: Demo_PSOTorture
- Mode: -game (standalone)
- Resolution: 800 × 600 windowed
- Flag: -unattended
- 1000 MaterialInstanceConstant
- Path: /Game/DemoMaterials
- MI_PSO_Demo_0000 … 0999
- GC between batches: ON
State Machine & Data Flow
Batch Loop
States LoadingBatch through WaitingForGC loop 20 times (1000 assets ÷ 50 batch size). The ↻ arrow shows the GC state cycling back to load the next batch. Each cycle: async load → time-sliced shader compile → texture stream → unload → garbage collect.
Asset Processing Heatmap — 1,000 Materials in 20 Batches
Test Run Timeline
Four sequential test iterations were performed, each uncovering progressively deeper issues in the asset discovery pipeline. The final run achieved full end-to-end validation.
Run 1 — Map Path Resolution Crash
CrashLaunched with /Game/Demo/Demo_PSOTorture as the map argument. The engine interpreted this as a filesystem path (G:/1gregorigin/Git/Game/Demo/...), failed to find the map, showed an error dialog, and the -unattended flag auto-dismissed it — leading to an EnhancedInput crash on shutdown.
LogLoad: Error: Failed to load map: /Game/Demo/Demo_PSOTorture LogEnhancedInput: Fatal: Null player controller during shutdown
Fix: Use absolute .umap file path instead of content path for -game mode.
Run 2 — Zero Assets Discovered
0 AssetsMap loaded successfully. Plugin initialized, demo manager configured settings, warmup started. But ScanForAssets() returned 0 assets despite 1,000 material instances existing in /Game/DemoMaterials. The warmup ran to completion with nothing to process.
[08.21.25:178] LogPSOAutopilot: Starting PSO Autopilot Warmup... [08.21.25:180] LogPSOAutopilot: Discovered 0 assets for PSO Warmup. [08.21.25:456] LogAssetRegistry: Asset registry cache read as 67.3 MiB ← loaded 280ms AFTER scan
Key observation: The asset registry cache loaded 280ms after the scan ran. IsLoadingAssets() returned false because the background gatherer hadn't started yet — not because it was finished.
Run 3 — Registry Loaded, Still Zero
0 AssetsAdded SearchAllAssets(true) to force synchronous registry completion before querying. The asset registry cache now loaded before the scan. Added diagnostic logging revealing the true root cause.
[08.26.12:094] LogPSOAutopilot: Scan path: '/Game/DemoMaterials' [08.26.12:094] LogPSOAutopilot: Scan path: '' ← EMPTY PATH! [08.26.12:095] LogPSOAutopilot: Registry total assets: 8688 [08.26.12:095] LogPSOAutopilot: Path-only: 0 | Class filter: /Script/Engine.MaterialInterface [08.26.12:095] LogPSOAutopilot: Sample[7]: path=/Game/DemoMaterials class=MaterialInstanceConstant [08.26.12:095] LogPSOAutopilot: Sample[8]: path=/Game/DemoMaterials class=MaterialInstanceConstant [08.26.12:370] LogPSOAutopilot: Discovered 0 assets for PSO Warmup.
Root cause identified: DefaultGame.ini contained +DirectoriesToScan=(Path="") alongside the valid path. The empty FName in FARFilter::PackagePaths caused GetAssets() to return zero results — even with valid paths present.
Run 4 — Full Pipeline Success
1000 AssetsRemoved the empty path from DefaultGame.ini and added an empty-path guard in ScanForAssets(). All 1,000 materials discovered, batch-loaded, shader-compiled, texture-streamed, and warmup completed successfully.
[11.03.23:144] LogPSOAutopilot: PSO Autopilot Module Started. [11.03.24:338] LogPSOAutopilot: PSO Autopilot Subsystem Initialized. [11.03.24:922] LogPSOAutopilot: Demo manager applied validation settings (min duration 45.0s) [11.03.24:923] LogPSOAutopilot: Demo widget NativeConstruct complete. [11.03.24:923] LogPSOAutopilot: Demo widget initialized and bound to subsystem delegates. [11.03.24:923] LogPSOAutopilot: Demo HUD add-to-screen (success): BP_PSOAutopilot_HUD_C [11.03.24:923] LogPSOAutopilot: Starting PSO Autopilot Warmup... [11.03.25:099] LogPSOAutopilot: Demo widget received first progress update: Scanning for Assets... (0.00) [11.03.25:360] LogPSOAutopilot: Discovered 1000 assets for PSO Warmup. [11.03.36:843] LogPSOAutopilot: Holding warmup completion for demo validation: 33.1s remaining. [11.04.11:065] LogPSOAutopilot: PSO Autopilot Warmup Finished successfully.
Root Cause Analysis
Bug 1: Empty Path Poisoning FARFilter
An empty string (Path="") in DefaultGame.ini was loaded into the FARFilter::PackagePaths array as NAME_None. UE5's asset registry interprets this as an invalid constraint that causes the entire path filter to return zero results, even when valid paths are present alongside it.
[/Script/PSOAutopilot.PSOAutopilotSettings] +DirectoriesToScan=(Path="/Game/DemoMaterials") +DirectoriesToScan=(Path="") BatchSize=50
[/Script/PSOAutopilot.PSOAutopilotSettings] +DirectoriesToScan=(Path="/Game/DemoMaterials") « empty entry removed » BatchSize=50
Bug 2: Asset Registry Race Condition in -game Mode
IsLoadingAssets() returns false when no background work is queued, not when it has completed. In -game mode, BeginPlay() fires before the asset data gatherer is even started, so the guard passed immediately on an empty registry.
IAssetRegistry& Registry = ...Get(); if (Registry.IsLoadingAssets()) { return; // Wait for next tick } // Immediately query the (empty) registry FARFilter Filter; ...
IAssetRegistry& Registry = ...Get(); if (Registry.IsLoadingAssets()) { return; // Wait for next tick } // Force synchronous scan completion Registry.SearchAllAssets(true); FARFilter Filter; ...
Bug 3: No Defensive Guard Against Empty Paths
for (const FDirectoryPath& Dir
: Settings->DirectoriesToScan)
{
Filter.PackagePaths.Add(
FName(*Dir.Path));
}
for (const FDirectoryPath& Dir
: Settings->DirectoriesToScan)
{
if (!Dir.Path.IsEmpty())
{
Filter.PackagePaths.Add(
FName(*Dir.Path));
}
}
UE5 FARFilter Gotcha
Adding NAME_None (empty FName) to FARFilter::PackagePaths silently corrupts the entire filter query — it returns zero results without logging any warning. This is an undocumented UE5 behavior that can waste hours of debugging. Always guard against empty paths when building asset registry filters from user-configurable data.
Final Run — Pipeline Performance
Timing breakdown of the successful Run 4. All 1,000 assets were processed across 20 batches with GC cycles, followed by a 33-second validation hold before final completion.
Time-Slicing Validation
Processing 1,000 materials in ~11.5 seconds with a 7.77ms per-frame budget means roughly 1,480 frames were available for processing. At 50 assets per batch, each batch completes quickly enough that the UI remained responsive throughout. The progress delegate fires per-asset, providing smooth loading bar updates.
Complete Issue Registry — 18 Resolved
15 issues identified during static code review + 3 additional issues found during runtime testing. All resolved and verified.
| # | Severity | Issue | File | Status |
|---|---|---|---|---|
| #01 | Critical | ForceAssetWarmup only handled UMaterialInterface; non-material assets silently skipped |
PSOAutopilotSubsystem.cpp | Fixed |
| #02 | Critical | APSOAutopilotBootLoader missing EndPlay — delegates not unbound on destruction |
PSOAutopilotBootLoader.cpp | Fixed |
| #03 | High | Dead BuildWarmupFingerprint() declaration & implementation (~50 lines unreachable code) |
PSOAutopilotSubsystem.h/.cpp | Fixed |
| #04 | High | No StopWarmup() method — no way to cancel in-progress warmup |
PSOAutopilotSubsystem.h/.cpp | Fixed |
| #05 | High | ForceAssetWarmup used raw property iteration — fragile, missed indirect refs |
PSOAutopilotSubsystem.cpp | Fixed |
| #06 | High | Forced streaming textures not tracked — no way to refresh residency post-warmup | PSOAutopilotSubsystem.h/.cpp | Fixed |
| #07 | Medium | Fingerprint future had no timeout — infinite hang if thread pool stalls | PSOAutopilotSubsystem.cpp | Fixed |
| #08 | Low | Streaming telemetry message missing resource count | PSOAutopilotSubsystem.cpp | Fixed |
| #09 | Medium | CDO settings mutations stacked across runs — missing ReloadConfig() |
DemoManager + BootLoader | Fixed |
| #10 | Medium | Custom HUDClass ignored when bForceValidationRun was true |
PSOAutopilotDemoManager.cpp | Fixed |
| #11 | Medium | StartWarmup guard allowed re-entry from Finished state |
PSOAutopilotSubsystem.cpp | Fixed |
| #12 | Low | CanContainContent: true in .uplugin but Content dir was empty |
PSOAutopilot.uplugin | Fixed |
| #13 | Low | Virtual Texture UpdateResource() not called during preheat |
PSOAutopilotSubsystem.cpp | Fixed |
| #14 | Low | Spinner widget used WhiteSquareTexture — looked flat and dated |
DemoWidget + LoadingWidget | Fixed |
| #15 | Low | Redundant BuildFallbackWidgetTree() calls in NativeConstruct |
DemoWidget + LoadingWidget | Fixed |
| #16 | Critical | Empty FName in FARFilter::PackagePaths silently zeroed all query results |
DefaultGame.ini + Subsystem | Fixed |
| #17 | Critical | Asset registry race: IsLoadingAssets() false before gatherer starts in -game mode |
PSOAutopilotSubsystem.cpp | Fixed |
| #18 | Medium | Deprecated GetUsedTextures 5-param API call (UE 5.7 breaking change) |
PSOAutopilotSubsystem.cpp | Fixed |
Issue Severity Breakdown
4 Critical (would crash or produce completely wrong results) • 4 High (significant functionality gaps) • 5 Medium (correctness or robustness) • 5 Low (polish, cleanup, UX). Issues #16 and #17 were the most impactful — without them, the warmup pipeline appeared to work but processed zero assets, making all other optimizations meaningless.
Build Verification Matrix
All three standard UE5 build configurations compiled successfully via RunUAT BuildPlugin. Zero warnings, zero errors.
$ RunUAT.bat BuildPlugin -Plugin="PSOAutopilot.uplugin" -TargetPlatforms=Win64 -Rocket [Editor Dev] UHT processed in 1.76s — 8 actions compiled Result: Succeeded Total: 31.09s [Game Dev] UHT processed in 1.52s — 8 actions compiled Result: Succeeded Total: 28.44s [Game Ship] UHT processed in 1.76s — 8 actions compiled Result: Succeeded Total: 28.01s BUILD SUCCESSFUL AutomationTool exiting with ExitCode=0 (Success)