8 engineering rules, 30 battle-tested gotchas, 4 completed audit rounds, and zero tolerance for technical debt.
Every line of code, every commit, every system must comply. No exceptions.
11 non-negotiable rules for every line of C++ code. UPROPERTY for all UObject members, IsValid() checks, proper initialization, TObjectPtr usage, and TEXT() macro for all string literals.
Every Bind must have a symmetric Unbind. Pattern by subsystem type ensures zero dangling delegate pointers. This is the #1 crasher in UE projects — PGX enforces symmetric lifecycle in every subsystem.
Internal documentation and public docs must always reflect actual code. Both updated on every system change. If the docs don't match the code, the docs are wrong.
Correct and minimal dependencies. Star topology enforced in Build.cs. Every #include implies a Build.cs dependency. L2 plugins never depend on other L2 plugins.
Metadata lives in ONE canonical location. Registry is the source, everything derives from it. If data appears in 2+ places, consolidate to one and derive the rest.
Every UI element must be functional. No placeholder buttons, no TODO in user-facing strings. Inspector panels show real data, not hardcoded test values.
A system is not done when it works. It's done when it's clean, documented, and wired. 0 errors, 0 warnings, all deliverables completed, all docs updated, editor wiring checklist passed.
Never LogTemp in production code. Every system has its own LogPGX{System} category. Appropriate verbosity levels: Log for normal ops, Warning for recoverable issues, Error for failures.
Each one cost hours of debugging. Documented so you don't have to.
UPROPERTY members CANNOT be inside #if WITH_EDITOR — UHT rejects it. Use #if WITH_EDITORONLY_DATA for UPROPERTY members instead.
UButton::OnClicked is a dynamic delegate and does NOT support AddLambda. Must use AddDynamic(this, &ThisClass::MyUFunction) with a proper UFUNCTION method.
SHorizontalBox::Slot().FixedWidth() was removed in UE 5.6. Use .AutoWidth() combined with SNew(SBox).WidthOverride(N) instead.
The FSlateFontInfo constructor is deprecated in UE 5.6. Use FCoreStyle::GetDefaultFontStyle("Regular", Size) for all font creation.
The override signature is virtual uint32 GetCategories() = 0 — NO const qualifier. Adding const will silently create a new function instead of overriding.
Use UE_DECLARE_GAMEPLAY_TAG_EXTERN in .h and UE_DEFINE_GAMEPLAY_TAG in .cpp. Never use UE_DEFINE_GAMEPLAY_TAG_STATIC in headers — it triggers static_assert under adaptive non-unity builds in UE 5.6.
UHT does not allow two headers with the same filename across the entire project, even in different plugins. If two plugins both have Types.h, one must be renamed.
UDeveloperSettings lives in the DeveloperSettings module, NOT in Engine. Add "DeveloperSettings" to your Build.cs dependencies or compilation will fail with a missing symbol.
C functions are NOT exported by default across DLL boundaries. Create an exported C++ wrapper function with the MODULE_API macro to make them accessible.
Since UE 5.5, EAutomationTestFlags changed from a namespace to an enum class. Use the underscore form: EAutomationTestFlags_ApplicationContextMask.
Every system documented at three levels: architecture, usage guides, and testing guides.