Case File 04 Architecture Audit · NDA Profiling / Refactor · 9 MIN READ

UE5 Architecture Audit:
40% Tick,
30 Hours a Week Back

Thirty-plus engineer-hours a week were going to bug chasing, performance degraded with every feature, and Blueprint called into C++ in an order nobody could predict. This file documents the audit — what an Unreal Insights pass actually finds, how findings were ranked so the team could act on them, and which fixes were worth doing first.

RS
Reignance Studios // UE5 SYSTEMS & ARCHITECTURE · NDA-PROTECTED ENGAGEMENT

Outcome

Roughly 40% tick performance improvement and about 30 engineer-hours a week returned to feature work, from a profiling and architecture pass that ended in a prioritised refactor plan the team executed themselves.

~40%TICK IMPROVEMENTMeasured, steady state
~30HRS/WEEK RECOVEREDPreviously lost to bug chasing
3,000+LINES REMOVEDDead and unreachable code
1PRIORITISED PLANRisk and effort scored per finding
// NDA Notice

Client name, project title and identifying detail are withheld under mutual NDA. Figures are as reported at handoff. Everything documented below is technique, which is the part that transfers.

The Symptom

The team reported the codebase as unmaintainable and losing 30-plus engineer-hours a week to bug chasing. Frame time degraded measurably with each feature added. Blueprint graphs called into C++ in an order that varied between play sessions, and the resulting bugs were intermittent enough to be argued about rather than fixed.

Two failures were being reported as one. Performance degradation and maintenance cost look like the same problem to a team living inside it, and they usually share a cause — but they need separate evidence, because the fix for one is not the fix for the other.

// Engagement Context

Architecture audit engagement: a full Unreal Insights profiling pass, a written architectural review, a prioritised refactor plan with risk and effort scoring per finding, plus hotfix implementation of the highest-value items. Client withheld under mutual NDA.

The Profiling Pass

An audit that begins with reading code produces opinions. An audit that begins with a trace produces findings. The order is not negotiable.

  • Unreal Insights capture of representative gameplay — not a synthetic benchmark, and not the main menu — at the population and content density the team actually ships.
  • Separate captures for game thread, render thread and, where relevant, the server, so a cost is attributed to the thread that actually pays it rather than the one it was noticed on.
  • stat command sampling for a cheap continuous baseline, so a later change can be shown to have moved the number rather than asserted to have.
  • A recorded before-state per finding, so every claimed improvement is a difference between two measurements.

If you cannot state the before number, you cannot claim the after number. Most performance work that fails does so because it was never measured, not because it was wrong.

What It Found

The findings clustered into four groups, which is typical for a codebase in this state.

Heavy tick functions

Actors ticking every frame to poll a condition that changes seconds apart, or never — distance checks, state polling, UI value refreshes recomputed at frame rate for values that update on event. Individually trivial, collectively the largest single cost in the trace.

Redundant per-frame queries

The same value queried repeatedly within a single frame by different systems, each unaware the others existed: ability system tag queries, component lookups by class, and trace results recomputed rather than cached for the frame.

Dead and unreachable code

Over 3,000 lines with no live call path — abandoned prototypes, superseded implementations left in place, and disabled subsystems still being ticked. This costs little at runtime and a great deal in maintenance, because every engineer reading the codebase must first determine whether it is live.

Blueprint reaching into C++ out of order

The most expensive category, and the one producing the intermittent bugs.

Execution Order Bugs

C++ systems exposed initialisation and mutation entry points to Blueprint without documenting or enforcing a required call order. Blueprint graphs then called them in whatever order was convenient when each graph was written.

That produces bugs with a specific and expensive signature: they reproduce sometimes, they change behaviour when unrelated code is added, and they are frequently fixed by an added delay that hides the race rather than removing it. This is where the 30 hours a week were going.

The fix was structural rather than a bug list:

  • Entry points that require an order stopped being individually exposed. A single Blueprint-callable operation performs the sequence internally and cannot be called out of order.
  • Genuine initialisation dependencies moved into engine-guaranteed lifecycle points rather than being called opportunistically from graphs.
  • Where an ordering assumption had to remain, an assertion enforces it in development builds, so a violation fails loudly at the call site instead of silently three systems downstream.
  • Every delay node found masking a race was removed alongside the underlying fix, and recorded, so it could not be quietly reintroduced.
// Do Not Expose Order-Dependent Entry Points

If two Blueprint-callable functions must be called in a specific order, that order is part of the interface and Blueprint has no way to express it. Expose the sequence as one operation. Every alternative depends on a convention holding across every future graph anyone writes.

Ranking the Fixes

A finding list is not a deliverable. A list of forty problems handed to a team already losing thirty hours a week is one more thing they cannot act on.

Every finding was scored on two axes and ordered by the ratio:

Axis What it captures
Impact Measured cost from the trace, plus maintenance-hours attributable to the finding
Effort and risk Implementation time, blast radius, and how much of it can be verified automatically

Highest-ratio items were implemented directly as part of the engagement — tick removal on the worst offenders, per-frame caching of the hottest redundant queries, dead code removal, and the ordering fixes on the two systems generating most of the intermittent bugs. The remainder shipped as a sequenced plan with the before-numbers attached, so the team could continue and verify their own progress.

The roughly 40% tick improvement came predominantly from the first two categories. The 30 hours a week came from the fourth. They are separate wins from one audit, which is the argument for auditing rather than optimising.

What Transfers

Independent of this project:

  • Trace before reading code. Findings beat opinions, and a before-number is what makes an after-number a claim rather than a hope.
  • Most tick cost is polling for something that could be an event. Auditing tick usage is usually the highest-yield first pass in a UE5 codebase.
  • Cache per-frame query results that multiple systems recompute independently.
  • Delete dead code. Its runtime cost is near zero and its maintenance cost is paid by every engineer who reads it.
  • Never expose order-dependent entry points to Blueprint. Expose the sequence, or enforce the order with an assertion.
  • Rank findings by impact over effort, or the team cannot act on the audit you delivered.