Engineering Knowledge Base

TargetFrame Architecture FAQ

Answers for technical directors, rendering engineers, and game teams integrating TargetFrame into Unreal Engine projects. Covers multi-engine compatibility (5.5–5.8), dual quality accounting, the asymmetric cost ladder, 27 automation tests, and hardware calibration.

Search Questions Read Architecture Manual
TargetFrame Architecture & Telemetry
FRAME BUDGET: PROTECTED
60 / 90 / 120 FPS CALIBRATED
UE 5.5 – 5.8
Supported Floor
27 / 27 Pass
Automation Tests
Dual Quality Accounting: Preserves player custom settings while CPU governor manages foliage and view distance.
All Questions 18 Engine Matrix 3 Two-Plugin Architecture 2 Cost Ladder 2 Dual Quality 2 Hardware Tiers 2 CPU Governor 2 CVar Ownership 2 27 Automation Tests 2 Troubleshooting 1

No matching questions found

Try searching for terms like "5.5", "CPU", "ladder", "lock", or "tests".

Engine Matrix • 5.5 – 5.8

Engine & Multi-Version Support

How a single source tree serves Unreal Engine 5.5, 5.6, 5.7, and 5.8 without preprocessor spaghetti.

Engine Matrix How does TargetFrame support UE 5.5, 5.6, 5.7, and 5.8 from one codebase? ↓
TL;DR: Unreal Engine 5.5 is our absolute floor. We shim versioned APIs via TargetFrameEngineCompat.h and use BuildSettingsVersion.Latest so target files compile across all four engines without edits.

TargetFrame establishes Unreal Engine 5.5 as the absolute floor. It avoids version-specific C++ APIs by encapsulating necessary version shims into TargetFrameEngineCompat.h (such as GetTotalDeviceWorkingMemory which was introduced in 5.6).

In addition, target files use BuildSettingsVersion.Latest (which resolves cleanly to V5 on 5.5/5.6, V6 on 5.7, and V7 on 5.8) because no single build settings literal compiles across all four versions.

Descriptors Why do neither TargetFrame nor TargetFrameSample declare EngineVersion in .uplugin? ↓
TL;DR: Unreal treats EngineVersion as an exact-match gate. Declaring "5.7.0" would make the plugin refuse to load in 5.5, 5.6, or 5.8. Packaging tools stamp the exact version dynamically.

In Unreal Engine, FPluginManager::IsPluginCompatible treats EngineVersion as an exact-match gate, not a minimum floor. If a descriptor declares "EngineVersion": "5.7.0", it is instantly rejected when opened in 5.5, 5.6, or 5.8.

By omitting EngineVersion in source, the plugin opens cleanly in any compatible engine. When built for Fab distribution via RunUAT BuildPlugin, Unreal's automation tool automatically stamps the exact per-engine version into the output descriptor.

Content Rules What is the Package Version 1013 rule and why does it matter? ↓
TL;DR: Newer engines can read older asset packages, but never the reverse. Shipped assets are strictly serialized in UE 5.5 (ver 1013) and guarded by CI Python scripts.

Unreal Engine reads packages older than the running engine, but never packages newer than itself. Shipped plugin assets (such as WBP_TargetFrameExperience.uasset) are strictly authored and serialized on UE 5.5 (package version 1013).

If an asset were saved in UE 5.7 or 5.8, its version would increase to 1018, causing a hard crash/failure when loaded in UE 5.5 or 5.6. This requirement is enforced by Scripts/CheckContentEngineFloor.py.

Clean Namespace

Two-Plugin Clean Architecture

Why showcase code was extracted and how it preserves clean commercial shipping.

Architecture Why is TargetFrame split into TargetFrame and TargetFrameSample? ↓
TL;DR: Zero demo pollution. Shipped games get only the pure runtime policy module. Showcase actors, camera tours, and debug HUDs live in the optional sample plugin.

In earlier builds, demo classes like AScalabilityGameMode, AScalabilityDebugHUD, and ATargetFrameShowcaseActor lived inside the runtime plugin. This polluted the consumer's UObject namespace and forced games to compile demo assets and hardcoded shape lookups.

  • Plugins/TargetFrame: The pure runtime policy module, settings CDO, math library, and drop-in onboarding widget. Zero sample maps or demo classes.
  • Plugins/TargetFrameSample: The optional showcase arena, camera tour, and developer debug HUD, equipped with CoreRedirects so existing assets seamlessly resolve.
State Machine What are the 5 Gates of the TargetFrame State Machine? ↓
TL;DR: 1. Creation (No server alloc) → 2. Session (Map/PIE filters) → 3. Activation (Explicit enable) → 4. Governor (Warmup/Cinematic checks) → 5. Lock (Zero visual thrashing).
  1. Creation Gate: Under UE_SERVER, IsRunningDedicatedServer(), or !FApp::CanEverRender(), the subsystem refuses creation entirely. Zero memory allocated.
  2. Session Gate: IsDisabledInCurrentSession checks world type, map filters, and per-instance PIE overrides.
  3. Activation Gate: bPolicyControlActive must be explicitly activated by the game. Until then, TargetFrame only observes and benchmarks; it writes zero settings.
  4. Governor Gate: Enforces warmup (default 10s), evaluation cadence (default 4s), and suppression when window is unfocused, paused, or during cinematics.
  5. Lock Gate: The fire-and-forget stabilization lock, which silences all adjustments once frame rate is stable and only wakes on severe deficits.
Anti-Oscillation

Governor & Asymmetric Cost Ladder

How the governor distinguishes hitches from sustained drift and navigates quality steps.

Cost Ladder What is the exact order of the Asymmetric Cost Ladder? ↓
TL;DR: Deficit steps CPU → Upscaler → Culling → Scalability → Nanite → Resolution. Recovery is exactly reversed: CPU levers are restored LAST so foliage never oscillates.

When defending a deficit (StepQualityDown):

  1. CPU Groups: (View Distance & Foliage) — only if the frame is classified as CPU-bound.
  2. Upscaler Quality: Steps vendor upscaler mode (e.g. Quality → Balanced).
  3. Triangle Culling Scale: Tightens cull distances on heavy static meshes.
  4. Overall Scalability Level: Steps down base presets (Epic → High → Medium).
  5. Nanite Pixels-per-Edge: Softens rasterization density.
  6. Resolution Scale: Reduces 3D scene render percentage.

When recovering during a surplus (StepQualityUp), the order is reversed, with CPU groups restored last. This asymmetry prevents foliage and view distances from oscillating ahead of resolution and Nanite.

Filtering How does the wall-clock exponential filter reject hitches? ↓
TL;DR: One-off spikes ($> 100\text{ms}$) like GC pauses are completely ignored. If slow frames persist for over 1.0s, TargetFrame treats it as genuine load and steps down.

Isolated long frames (e.g., streaming hitches or garbage collection pauses exceeding HitchThresholdSeconds = 0.1s) are excluded from the smoothed frame rate.

However, if a slow frame run persists longer than SustainedSlowFrameWindowSeconds = 1.0s, TargetFrame recognizes that the machine is genuinely struggling and integrates the true frame rate, triggering controlled step-downs.

Settings Architecture

Dual Quality Accounting

The architectural solution that allows CPU-bound adjustments to coexist with overall quality.

Dual Accounting What was the defect with overall quality in earlier builds? ↓
TL;DR: Unreal's GetOverallScalabilityLevel() returns -1 if a single setting diverges. In old builds, stepping foliage down caused the policy to read back -1 and permanently freeze overall scalability.

Unreal's UGameUserSettings::GetOverallScalabilityLevel() returns -1 whenever any single per-group value (like Foliage or Shadows) diverges from a standard preset. In earlier builds, as soon as the CPU governor stepped down foliage, the policy engine read back -1, assumed overall quality was permanently invalid, and froze the overall scalability lever for the rest of the session.

Dual Accounting How does Dual Quality Accounting fix this? ↓
TL;DR: We maintain two properties: Status.OverallQualityLevel (engine mirror) and Status.ManagedOverallQualityLevel (our internal target). Applying presets preserves the independent CPU and resolution levers.

TargetFrame separates quality into two distinct properties:

  • Status.OverallQualityLevel: Faithfully mirrors the engine's report (reporting -1 for custom mixes).
  • Status.ManagedOverallQualityLevel: Tracks the actual tier TargetFrame is actively driving, seeded at activation.

When ApplyOverallQualityLevel writes a preset, it explicitly preserves ResolutionQuality (owned by the resolution lever) and, when bCPUQualityManaged is set, the foliage and view-distance groups. The two levers compose harmoniously without clobbering each other.

Calibration

Engine-Calibrated Hardware Tiers

Fixing benchmark index thresholds by reading GScalabilityIni dynamically.

Tiers What was the hardware tier calibration defect and how was it resolved? ↓
TL;DR: Old builds demanded an index of 220, demoting an RTX 5060 Ti to Entry tier (45 FPS). We now parse GScalabilityIni dynamically where 115 is Epic tier, restoring 90 FPS performance.

Unreal's synthetic benchmark returns a normalized performance index on a scale where 115 represents the boundary for Epic-quality settings (as defined by [ScalabilitySettings] PerfIndexThresholds_* across UE 5.5–5.8: GPU 18 42 115).

An earlier build of TargetFrame mistakenly demanded an index of 220 for its top tier. As a result, powerful cards (such as an RTX 5060 Ti) were erroneously demoted to Entry tier and capped at 45 FPS.

The Fix: TargetFrame now dynamically reads PerfIndexThresholds directly from GScalabilityIni at runtime:

  • Entry: Index < 42 (Target: 45 FPS)
  • Mainstream: Index 42 – 114 (Target: 60 FPS)
  • Performance: Index ≥ 115 (Target: 90 FPS)
Mobile What happens if a mobile or integrated GPU returns a negative benchmark score? ↓
TL;DR: On devices like the Mali-G57 where synth-benchmarks can return -25.1, our pure math core detects the negative index, falls back to the CPU score, and safely assigns Entry tier.

On devices like the Mali-G57 (tested on Android 14), Unreal's synthetic GPU benchmark can fail or return an invalid negative score (e.g. -25.1). TargetFrame's pure math core detects the non-monotonic or negative score, rejects it, falls back to the CPU benchmark score, and applies the low-memory constraint to select Entry tier safely.

Thread Decoupling

CPU Governor & Thread Decoupling

Attacking GameThread bottlenecks without needlessly degrading image resolution.

CPU vs GPU Why does dropping resolution fail to fix CPU bottlenecks? ↓
TL;DR: Render resolution only affects GPU pixel shading. If the GameThread is lagging, dropping resolution yields 0 ms of CPU savings while making the game blurry. TargetFrame targets View Distance and Foliage instead.

Render resolution scale affects GPU pixel shading and rasterization cost. If the game is bottlenecked by the GameThread (e.g., hundreds of ticking actors, AI, or complex animation graphs), dropping the screen percentage from 100% to 50% yields 0 ms of CPU savings while making the game blurry. TargetFrame instead targets CPU-bound scalability levers (View Distance and Foliage) to reduce culling and transform overhead.

Validation How was the CPU Governor validated in automated tests? ↓
TL;DR: Tested via TargetFrame.Soak.CPUBoundRealWorkload, executing 30ms/frame of genuine math on the GameThread. UE measured 31.5ms GameThread vs 6.1ms GPU, properly triggering CPU step-downs.

TargetFrame includes two complementary soak tests:

  • TargetFrame.Soak.CPUBoundLadder: Injects deterministic thread timings (40ms GameThread vs 6ms GPU) to test the decision logic.
  • TargetFrame.Soak.CPUBoundRealWorkload: Executes 30ms/frame of genuine mathematical work on the Unreal GameThread. Across all four engines (5.5–5.8), the engine measured 31.3–31.6ms of GGameThreadTime vs 6.0–6.3ms GPU time, proving that real workloads properly trigger the CPU ladder.
Priority Semantics

Console Variable Ownership Semantics

How TargetFrame interacts with Unreal's internal CVar priority hierarchy.

Priority What CVar priority does TargetFrame use? ↓
TL;DR: Writes at ECVF_SetByGameSetting. This ranks above engine scalability presets (SetByScalability), preventing engine sweeps from wiping our VSM or texture pool caps.

TargetFrame writes console variables at ECVF_SetByGameSetting. In Unreal Engine's priority hierarchy:

CVar Priority Stack
ECVF_SetByCode > ECVF_SetByGameSetting > ECVF_SetByScalability > ECVF_SetByProjectSetting

This means TargetFrame overrides standard engine scalability sweeps (sg.*), preventing Unreal from clobbering its VSM page limits or texture pool budgets.

Instrumentation What is Status.RejectedConsoleVariables? ↓
TL;DR: If a C++ override or Device Profile holds SetByCode, lower-priority writes are ignored. TargetFrame detects this and logs the rejected variable so developers always know what took precedence.

If a variable is locked by C++ code using ECVF_SetByCode or by an active Device Profile, Unreal silently ignores writes with lower priority. TargetFrame instruments this, detects when a write is rejected, records the variable in Status.RejectedConsoleVariables, and notes it in the diagnostic summary.

CI • Quality Control

27 Automation Tests

Comprehensive test coverage across pure math, runtime state, and soak endurance.

Test Matrix What are the three automation test suites? ↓
TL;DR: 12 PolicyMath tests (instant math), 11 Runtime tests (live world client), and 4 Soak tests (endurance and real 30ms CPU workloads).
SuiteTestsExecution ContextCoverage
TargetFrame.PolicyMath12Any context (Instant)Pure math, budgets, hitch rejection, smoothing consistency at 33Hz and 333Hz, tier decisions.
TargetFrame.Runtime11Client context (Live World)Snapshots, custom mix protection, fractional t.MaxFPS, UI separation, delegates.
TargetFrame.Soak4Client context (Live World)Governor ladder sweep, level travel recovery, injected CPU ladder, and 30ms real CPU workload.
Integrity Why were tests updated to remove EditorContext? ↓
TL;DR: In a bare editor, tests were hitting "no subsystem" guards and returning true — producing 12 false "green" passes. Removing EditorContext and requiring a live world guarantees 100% real execution.

In a bare editor session with no active world or GameInstance, client-dependent tests hit their "no live subsystem" guard. Previously, this guard logged a warning but returned true — resulting in 12 tests reporting a false "green" pass without ever executing.

Removing EditorContext ensures they only run when a live world exists (via -game or PIE), and the guard now explicitly calls AddError so silent skips are impossible.

Support • Diagnostics

Troubleshooting & Integration Pitfalls

Fast diagnosis for common integration and packaging hurdles.

Packaging BuildPlugin fails on TargetFrameSample with "missing plugin dependency". Why? ↓
TL;DR: RunUAT BuildPlugin runs in an isolated sandbox containing only that single plugin. Because the sample depends on TargetFrame, package the parent project or package TargetFrame first.

Unreal's RunUAT BuildPlugin compiles a plugin inside an isolated throwaway host project containing only that plugin. Because TargetFrameSample explicitly depends on TargetFrame, BuildPlugin cannot resolve the dependency in an isolated container. To package the sample, package the parent host project or include both plugins in your game repository.

TargetFrame Emblem
Production Ready

Engineered for Real Shipped Games

TargetFrame 0.4.0 is verified across Unreal Engine 5.5, 5.6, 5.7, and 5.8 on Win64, Android, and Linux. Join our Discord community for integration guidance, custom engine patches, or technical Q&A.

View on Fab Join Discord Support