Files
PS_ProserveEditor/CLAUDE.md
j.foucher bc491b7b94 Display-vs-value custom dropdown options + UI mode parity docs
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>
2026-04-22 12:43:40 +02:00

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 / GetCurrentDisplayForProperty
    • OnEditorModeChanged, OnEditorTick
    • OnEditorNeedsReinit, 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, OnPropertiesLoaded fires 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 via ApplyPropertyFromUI) AND in gameplay (on server when the timeline applies a new keyframe value via TimelineSubsystem::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 through ReplicatedUsing=OnRep_X RepNotify 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::LoadScene path runs in gameplay and must handle NM_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: A for Actors, U for UObjects, F for structs, E for enums, I for 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 (in Unreal/Source/PS_ProserveEditor/)
  • CLI build command (Editor, Development):
    "<UE_INSTALL>/Engine/Build/BatchFiles/Build.bat" PS_ProserveEditorEditor Win64 Development -Project="<REPO>/Unreal/PS_ProserveEditor.uproject" -WaitMutex -FromMsBuild
    
    UE install path can be found via registry: HKLM\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 summaries
  • project_ps_editor.md — Project scope, technical decisions, current phase
  • feedback_naming.md — Naming conventions and user preferences
  • reference_build.md — Full build command and UE install path details