Ground snap: - When an actor has no colliding components at all (e.g. a VR start marker, placement helper, or any BP whose collision is only enabled on entering editor mode), SnapActorToGround now anchors the PIVOT directly on the ground instead of falling back to the full-bounds bbox bottom. The full-bounds bottom could be far below the pivot (helper meshes, arrows, visual markers) and was lifting objects hundreds of units into the air. - Actors with collision keep the original behavior (collision-bbox bottom rests on the ground). Catalog spawn: - Always snap to ground (drop the bAutoSnapToGround gate) — a user clicking in the catalog expects the object to land on the ground every time; the flag is kept for gameplay-side spawning via SceneLoader. - SpawnActor now uses AlwaysSpawn instead of AdjustIfPossibleButAlwaysSpawn so UE never pushes the actor vertically to resolve overlaps — SnapActorToGround is the authoritative placement step. CLAUDE.md workflow rule: - Document that behavior / logic changes require explaining the plan first and waiting for go-ahead; only diagnostic-only additions (temporary UE_LOG) are fine without approval. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6.8 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.
Workflow — CRITICAL
- Do not make behavior / logic changes without first explaining the plan and waiting for the user's go-ahead. Explain what the change will be, why it fixes the issue, and what side effects it could have. Only after the user approves (or asks to proceed), apply the edit.
- Diagnostic-only additions (temporary
UE_LOG, read-only inspections) are fine without approval because they can't regress the project. - Refactors, algorithm changes, comportement changes, new defaults, removed features — all require approval first, even when they seem obvious.
- When revealing new data (e.g. logs) suggests a fix, STATE the proposed fix in plain words and wait. Don't pre-emptively write + apply the code change in the same turn.
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