Extend the EditableInterface with two new BlueprintNativeEvents so property combos can show human-friendly labels while storing technical values: - GetCustomOptionsWithValuesForProperty returns TArray<FPS_Editor_PropertyOption> where each pair carries a DisplayName (shown in the combo) and a Value (written to the property via ImportText). Wins over the legacy flat-string variant when non-empty. Ideal for TSubclassOf / TSoftClassPtr properties driven by a DataTable lookup where the stored value is a class path. - GetCurrentDisplayForProperty lets the actor authoritatively resolve the DisplayName from the current stored value, for cases where the automatic reverse lookup (normalize class path, exact-match) isn't reliable — e.g. several Values collapse to the same label, or the format is user-specific. UI refresh now asks the actor first, then falls back to a normalized match (strips the `TypeName'...'` wrapper from ExportText output so it compares apples to apples with a plain /Game/Path.X_C string). Same logic duplicated across both widget implementations (MainWidget and MainWidget_Legacy) since the project can switch UI mode at runtime. The new CLAUDE.md rule makes that requirement explicit. Also documented in CLAUDE.md: - Editor-vs-gameplay execution boundary: which interface methods fire where, including the gotcha that OnEditorPropertyChanged *does* fire in gameplay on the server when the timeline animates a property. - UI modes: keep all MainWidget* implementations in lockstep to avoid the "I edited the file but nothing changed" class of bug. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6.1 KiB
PS_ProserveEditor - Project Guidelines
Project Overview
Unreal Engine 5.5 project with runtime editor plugin (PS_Editor). The plugin enables runtime scene editing in packaged builds: object placement, property editing, scenario definition, splines, lighting, and JSON scene save/load.
Architecture
- Plugin: PS_Editor (runtime plugin, must work in packaged builds)
- UI: UMG driven by C++ (Slate)
- Gizmos: Custom actor with BasicShapes + unlit material (InteractiveToolsFramework is editor-only)
- Serialization: JSON via Json/JsonUtilities modules (FJsonObjectConverter)
- Input: Enhanced Input system (AZERTY: arrows + A/Q movement, E/R/T modes)
Editor vs. Gameplay execution boundary — CRITICAL
The plugin runs in two distinct modes and it's essential never to leak editor-only code
into the gameplay runtime loaded via UPS_Editor_SceneLoader::LoadScene (standalone).
Editor runtime mode
Activated when the APS_Editor_PlayerController + APS_Editor_HUD + UPS_Editor_MainWidget*
stack is created (typical during authoring — the user is in the runtime editor).
UPS_Editor_SceneLoader::bIsEditorModeActive == true- The HUD widget drives property editing, catalogue spawn, save/load, simulate, timeline
- Interface methods called only by the widget (never in gameplay):
GetCustomOptionsForProperty/GetCustomOptionsWithValuesForProperty/GetCurrentDisplayForPropertyOnEditorModeChanged,OnEditorTickOnEditorNeedsReinit,OnEditorNeedsRespawn
Gameplay mode
The project (PROSERVE) loads a scenario via UPS_Editor_SceneLoader::LoadScene from a GameMode
BeginPlay, without ever instantiating the editor PlayerController / HUD / widget.
UPS_Editor_SceneLoader::bIsEditorModeActive == false- Actors spawn, their properties are applied from JSON,
OnPropertiesLoadedfires once - The timeline subsystem auto-plays if the scenario has tracks, driven server-side only
Interface methods that fire in BOTH modes
OnPropertiesLoaded— after every scene load, editor or gameplay (applied once post-spawn)OnEditorPropertyChanged— fires both in editor (on UI edit viaApplyPropertyFromUI) AND in gameplay (on server when the timeline applies a new keyframe value viaTimelineSubsystem::ApplyTrackAtTime). Despite the name, the "Editor" refers to the PS_Editor plugin — the event is the canonical hook for "a property changed at runtime". In gameplay only the server invokes it; client-side reaction must go throughReplicatedUsing=OnRep_XRepNotify on the replicated property.
Rule when adding new features
- Anything tied to property panel UI (new interface method, new widget logic, dropdown helpers, etc.) lives in the widget only — no extra work to scope it out of gameplay
- Anything added to the
SceneLoader::LoadScenepath runs in gameplay and must handleNM_Client, replication, and server-authority correctly (the timeline subsystem is the reference pattern) - Expensive BP implementations of editor-UI interface methods (DataTable lookups, class resolution, etc.) can stay expensive — they never run outside the authoring session
How to guard code that could run in either mode
if (UPS_Editor_SceneLoader::IsInEditorMode())
{
// editor-only behaviour
}
or gate on bIsCurrentlyLoading to skip BP init during JSON restore.
UI Modes — IMPORTANT for widget changes
The runtime editor ships with multiple widget implementations that must all stay in sync:
PS_Editor_MainWidget.cpp/.h— modern UMG-based widget (primary target long-term)PS_Editor_MainWidget_Legacy.cpp/.h— Slate-only legacy widget (currently active as of April 2026)PS_Editor_MainWidget_UMG.cpp/.h— UMG variant
The user can switch between them via a UIMode setting. At any given time only one is running, but the project keeps all three compiled.
Rule: when changing any behavior inside a widget (new interface calls, new property row logic, new UI controls, bug fixes in RebuildDynamicProperties / ApplyPropertyFromUI / RefreshDynamicPropertyValues, etc.), apply the change to ALL widget implementations at the same time. Otherwise the user's current UIMode gets the fix and the others don't — the "it's like you didn't change anything" bug.
Symptom that the wrong widget was edited: logs you added don't appear in the Output Log despite the build being clean and the DLL being correctly loaded. Always grep for the function name across all PS_Editor_MainWidget*.cpp files before assuming a single edit is sufficient.
Naming Conventions
- All plugin files and classes use the
PS_Editor_prefix (e.g.,PS_Editor_GameMode,PS_Editor_Pawn) - Code and comments in English
- Standard UE5 prefixes:
Afor Actors,Ufor UObjects,Ffor structs,Efor enums,Ifor interfaces
Code Quality
- Prefer correct, complete implementations over minimal ones.
- Use appropriate data structures and algorithms — don't brute-force what has a known better solution.
- When fixing a bug, fix the root cause, not the symptom.
- If something requires error handling or validation to work reliably, include it without asking.
Build
- Engine: Unreal Engine 5.5
- Plugin location:
Unreal/Plugins/PS_Editor/ - Game module:
PS_ProserveEditor(inUnreal/Source/PS_ProserveEditor/) - CLI build command (Editor, Development):
UE install path can be found via registry:
"<UE_INSTALL>/Engine/Build/BatchFiles/Build.bat" PS_ProserveEditorEditor Win64 Development -Project="<REPO>/Unreal/PS_ProserveEditor.uproject" -WaitMutex -FromMsBuildHKLM\SOFTWARE\EpicGames\Unreal Engine\5.5>InstalledDirectory
Project Memory
Shared project memory is stored in .claude/memory/ (versioned in the repository). When starting a new session on any machine, read these files to restore context:
MEMORY.md— Index of all memory files with one-line summariesproject_ps_editor.md— Project scope, technical decisions, current phasefeedback_naming.md— Naming conventions and user preferencesreference_build.md— Full build command and UE install path details