# Monolith — Technical Specification **Version:** 0.21.3 (Beta) **Wiki:** https://github.com/tumourlove/monolith/wiki **Engine:** Unreal Engine 5.7+ **Platform:** Windows, macOS, Linux **License:** MIT **Author:** tumourlove **Repository:** https://github.com/tumourlove/monolith --- ## 1. Overview Monolith is a unified Unreal Engine editor plugin that consolidates 9 separate MCP (Model Context Protocol) servers and 4 C++ plugins into a single plugin with an embedded HTTP MCP server. It reduces ~220 individual tools down to a small set of MCP dispatcher tools (~1,400+ actions across 25+ in-tree namespaces — 45 experimental town gen actions disabled by default; query `monolith_discover()` for the live figure), cutting AI assistant context consumption by ~95%. The CommonUI action pack (51 actions, conditional on `WITH_COMMONUI`) shipped M0.5, v0.14.0 (2026-04-19), tested M0.5.1 (2026-04-25). Editor +2 (`run_automation_tests`, `list_automation_tests`) landed v0.14.8 — PR #48 by @MaxenceEpitech. v0.14.9 added Editor +2 (`run_python`, `load_level` — Issue #50, ported from @JCSopko's fork) and Animation +1 (`copy_bone_pose_between_sequences` — PR #51 by @MaxenceEpitech). v0.14.10 added Audio +12 MetaSound document introspection actions plus a new `FMetaSoundIndexer` deep indexer in MonolithIndex (PR #18 by @alakangas, refactored into existing `audio_query` namespace), AND Animation +1 (`list_bone_tracks`) + Editor +3 (`start_pie`, `stop_pie`, `run_console_command` with maintainer-pinned in-viewport mode + GEngine null-guard) — PR #54 by @MaxenceEpitech. ### What It Replaces | Original Server/Plugin | Actions | Replaced By | |------------------------|---------|-------------| | unreal-blueprint-mcp + BlueprintReader | 46 | MonolithBlueprint | | unreal-material-mcp + MaterialMCPReader | 46 | MonolithMaterial | | unreal-animation-mcp + AnimationMCPReader | 62 | MonolithAnimation (62 actions) | | unreal-niagara-mcp + NiagaraMCPBridge | 70 | MonolithNiagara | | unreal-editor-mcp | 11 | MonolithEditor | | unreal-config-mcp | 6 | MonolithConfig | | unreal-project-mcp | 17 | MonolithIndex | | unreal-source-mcp (concept from Codeturion) | 9 | MonolithSource | | unreal-api-mcp | — | MonolithSource | --- ## 2. Architecture ``` Monolith.uplugin MonolithCore — HTTP server (bind retry with port probe, Restart()), tool registry, discovery, settings, auto-updater MonolithBlueprint — Blueprint inspection, variable/component/graph CRUD, node operations, compile, spawn, variable-contract reconciliation (~120+ actions) MonolithMaterial — Material inspection + graph editing + CRUD + function suite + tiling quality + texture preview (~60+ actions) MonolithAnimation — Animation sequences, montages, ABPs, curves, notifies, skeletons, PoseSearch, IK rig / retargeting (retarget pose + op-stack tuning + per-bone translation retargeting), locomotion authoring (root-motion speed, distance-curve baking), ABP/ControlRig write, anim-graph-authoring pack, layout (~170+ actions) MonolithNiagara — Niagara particle systems, HLSL module/function creation, DI config, event handlers, sim stages, NPC, effect types, scalability, layout, temporal control, stateless-emitter factory, search & discovery (~120+ actions) MonolithEditor — Build triggers, live compile, log capture, compile output, crash context, scene capture, texture import, flipbook stitching, asset deletion, viewport info, blank-map factory + module status (Phase J F8), automation test list/run, Python escape-hatch (`run_python`), map swap (`load_level`), PIE smoke/capture harness, deterministic PIE input/control + object introspection, stat-group readout (~50+ actions — query `monolith_discover("editor")` for the live figure) MonolithConfig — Config/INI resolution and search (6 actions, fixed surface) MonolithIndex — SQLite FTS5 deep project indexer, 18 internal indexers (incl. `FMetaSoundIndexer` from PR #18 by @alakangas — conditional on `WITH_METASOUND`) (~10+ MCP actions) MonolithSource — Engine source + API lookup, auto-reindex on hot-reload (Phase J F17), LLM C++ authoring ergonomics (include/signature/deprecation/verify/example/lint/stub) (~20+ actions) MonolithUI — Widget blueprint CRUD + slot/template/styling, animation v1 (deprecated) + v2 (hoisted), settings scaffolding, accessibility, UISpec build/dump/schema, EffectSurface sub-bag actions (reflective optional-provider probe, decoupled 2026-04-27), CommonUI categories A–I, Type Registry diagnostic, Style Service diagnostic. (66 always-on + 51 CommonUI = 117 module-owned + 4 GAS UI binding aliases registered cross-namespace). CommonUI actions conditional on #if WITH_COMMONUI. EffectSurface actions return -32010 ErrOptionalDepUnavailable when the optional EffectSurface provider is absent (see specs/SPEC_MonolithUI.md § "Error Contract"). Architecture expansion Phase A–L landed 2026-04-26 (Spec Builder, Type Registry, EffectSurface, Style Service, hoisted Design Import verbs) MonolithMesh — Mesh inspection, scene manipulation, spatial queries, level blockout, GeometryScript ops, horror/accessibility, lighting, audio/acoustics, performance, decals, level design, tech art, context props, procedural geometry (sweep walls, auto-collision, proc mesh caching, blueprint prefabs), genre presets, encounter design, accessibility reports (~190+ core actions) + EXPERIMENTAL procedural town generator (45 actions, disabled by default via bEnableProceduralTownGen) MonolithGAS — Gameplay Ability System integration: abilities, attributes, effects, ASC, tags, cues, targets, input, inspection, scaffolding, UI attribute binding (~130+ actions, incl. 4 also aliased into `ui` namespace). Conditional on #if WITH_GBA MonolithComboGraph — ComboGraph plugin integration: combo graph CRUD, node/edge management, effects, cues, ability scaffolding (~13 actions). Conditional on #if WITH_COMBOGRAPH MonolithAI — AI asset manipulation: Behavior Trees, Blackboards, State Trees, EQS, Smart Objects, AI Controllers, Perception, Navigation, Runtime/PIE, Scaffolds, Discovery, Advanced (~220+ actions). Conditional on #if WITH_STATETREE, #if WITH_SMARTOBJECTS (required); #if WITH_MASSENTITY, #if WITH_ZONEGRAPH (optional) MonolithLogicDriver — Logic Driver Pro integration: SM CRUD, graph read/write, node config, runtime/PIE, JSON spec, scaffolding, discovery, components, text graph (~66 actions). Conditional on #if WITH_LOGICDRIVER MonolithAudio — Audio asset creation, inspection, batch management, Sound Cue graph building, MetaSound graph building via Builder API, MetaSound document introspection (read-side, +12 actions by @alakangas, PR #18), AI Perception sound binding, sine-tone test wave (Phase J F18) (~90+ actions). MetaSound features conditional on #if WITH_METASOUND MonolithAudioRuntime — Runtime sub-module (Type: Runtime) holding `UMonolithSoundPerceptionUserData` + `UMonolithAudioPerceptionSubsystem` consumed by `audio::bind_sound_to_perception`. **Editor-only ship caveat** — Monolith does not currently ship to cooked game builds, so the runtime sub-module is not present at runtime in shipped Steam builds. See `COOKED_BUILD_TODO.md`. (0 MCP actions — provides runtime classes only) MonolithBABridge — Optional IModularFeatures bridge for Blueprint Assist integration. Exposes IMonolithGraphFormatter; enables BA-powered auto_layout across blueprint, material, animation, and niagara modules when Blueprint Assist is present (0 MCP actions — integration only) MonolithReflectionIntel — Deterministic reflection intelligence layer (~28 actions): `decision_query` (5 — markdown decision-record mining), `risk_query` (5 — git churn / co-change / hotspot scoring / conditional-gate inventory), `cppreflect_query` (6 — UE 5.7 reflection-edge queries from UHT artefacts cross-joined with `IAssetRegistry`, incl. the `list_class_specifiers` token-discovery action), `network_query` (4 — replicated-class / RPC / OnRep enumeration + unbalanced-OnRep audit), `pipeline_query` (2 — `pr_review` + `release_readiness` composers), `reflect_query` (1 — `rebuild_reflection_index` project-only force-rebuild of the RI reflection tables; WRITE/maintenance, idempotent), plus `source_query("audit_module_dep_reality")` (1 — UPROPERTY/Build.cs missing-dep audit) and 4 audit actions on existing namespaces (`material_query("audit_orphan_materials")`, `niagara_query("audit_cross_asset_refs")`, `blueprint_query("audit_cdo_drift")`, `project_query("audit_orphan_assets")`). All six indexers write into `EngineSource.db` with lazy bootstrap + hot-reload refresh. Phase 3b (tree-sitter native-tag tracking) and Phase 4b (tag-graph + thread-safety audits) are WISHLIST — both blocked on tree-sitter substrate. ``` **Custom sibling plugins (not inside core Monolith; source + per-module specs are private to their respective repos):** Additional sibling plugins may register their own namespaces outside this repository. They are intentionally excluded from public Monolith action counts and release packages; their source, action rosters, and module specs belong in their own repos. Sibling-plugin namespaces are documented in those repos and are NEVER part of the public release zip or the advertised public action count. See §12 Total row for live counts. **Optional widget runtime providers** (not bundled with the public Monolith release zip): MonolithUI can expose EffectSurface action handlers through a reflective UClass probe when an external provider supplies the expected widget classes. MonolithUI has zero compile-time dependency on that provider. When the provider is absent, EffectSurface actions return `-32010 ErrOptionalDepUnavailable` (see [`specs/SPEC_MonolithUI.md` § "Error Contract"](specs/SPEC_MonolithUI.md#error-contract--optional-effectsurface-provider-absence--32010)); the rest of `ui::` is fully functional. The `make_release.ps1` `$LeakSentinels` list defends against accidental optional-provider symbol leakage into public release DLLs. For the architectural pattern that lets you write your own sibling plugin and register actions into Monolith's MCP registry from outside the core repo, see [`SIBLING_PLUGIN_GUIDE.md`](SIBLING_PLUGIN_GUIDE.md). > **Live editor `monolith_status` will report a higher count than the in-tree total.** When sibling plugins are loaded the editor reports the union of in-tree and sibling actions. This is expected. The numbers in §12 below are the **in-tree** ground truth; sibling totals are specced in their own repos. ### Discovery/Dispatch Pattern All domain modules register actions with `FMonolithToolRegistry` (central singleton). Each domain exposes a single `{namespace}_query(action, params)` MCP tool. The 4 core tools (`monolith_discover`, `monolith_status`, `monolith_reindex`, `monolith_update`) are standalone. Conditional modules gate registration on compile-time defines: MonolithGAS (`#if WITH_GBA`), MonolithComboGraph (`#if WITH_COMBOGRAPH`), MonolithLogicDriver (`#if WITH_LOGICDRIVER`), MonolithUI CommonUI actions (`#if WITH_COMMONUI`), MonolithAI (`#if WITH_STATETREE` + `#if WITH_SMARTOBJECTS` required; `#if WITH_MASSENTITY` + `#if WITH_ZONEGRAPH` optional), MonolithAudio (MetaSound actions conditional on `#if WITH_METASOUND`). `monolith_discover(namespace)` is **terse by default** — it returns each action's name + a one-line description, not the full per-action param schema. For an action's full param schema call `describe_query action_schema`, or pass `detail=true` (alias `verbose=true`) to inline every action's schema. Optional `filter`/`offset`/`limit` narrow or paginate the listing (`limit=0` = the complete list). See [`specs/SPEC_MonolithCore.md` § "Terse per-namespace discover"](specs/SPEC_MonolithCore.md). The full `discover()` (no namespace) response is unchanged. ### MCP Protocol - **Protocol version:** Echoes client's requested version; supports both `2024-11-05` and `2025-03-26` (defaults to `2025-03-26`) - **Transport:** HTTP with JSON-RPC 2.0 (POST for requests, GET for SSE stub, OPTIONS for CORS). Transport type in `.mcp.json` varies by client: `"http"` for Claude Code, `"streamableHttp"` for Cursor/Cline - **Endpoint:** `http://localhost:{port}/mcp` (default port 9316) - **Bind retry:** `FMonolithHttpServer::Start()` attempts up to 5 binds with exponential backoff and TCP port probe before failing. `Restart()` method available for runtime recovery. Console command: `Monolith.Restart` - **Batch support:** Yes (JSON-RPC arrays) - **Session management:** None — server is fully stateless (session tracking removed; no per-session state was ever stored) - **CORS:** `Access-Control-Allow-Origin: *` #### JSON-RPC error catalogue Standard codes mirror the JSON-RPC 2.0 spec. Monolith server-defined codes live in the `-32000..-32099` range. Constants: `Plugins/Monolith/Source/MonolithCore/Public/MonolithJsonUtils.h`. | Constant | Code | Meaning | |----------|------|---------| | `ErrParseError` | `-32700` | JSON parse failure on the request body. | | `ErrInvalidRequest` | `-32600` | Request shape doesn't match JSON-RPC 2.0. | | `ErrMethodNotFound` | `-32601` | Tool / action name not registered. | | `ErrInvalidParams` | `-32602` | Action found, params invalid (missing required field, bad enum, etc.). | | `ErrInternalError` | `-32603` | The server choked. Default for unspecified failures. | | `ErrOptionalDepUnavailable` | `-32010` | An optional sibling/marketplace plugin the action depends on is not present. The action exists in the registry; the call cannot be served. First consumer: the 10 EffectSurface action handlers when the optional EffectSurface provider is absent (see [`specs/SPEC_MonolithUI.md` § "Error Contract — Optional EffectSurface Provider Absence (-32010)"](specs/SPEC_MonolithUI.md#error-contract--optional-effectsurface-provider-absence--32010)). Reserved range `-32011..-32019` left open for future "optional dep" codes. | **`warnings[]` channel (Phase 1.0, 2026-05-27).** Successful responses may carry an optional top-level `warnings: string[]` array. The array is appended to (not replaced) by three independent sources before emit, and is omitted when empty: 1. **K3 unknown-param soft-warn** — typo / not-in-schema param keys. Hard error only when `STRICT_PARAMS=1`. K3's per-action allowlist additionally admits the three universal response-shaping params `_fields` / `_omit` / `_compact_json` (Phase 1.0) plus the pre-existing `asset_path` legacy alias. See `MonolithToolRegistry.cpp` around line 115. 2. **Survivor D AssetPath rewrite** — when a `Kind == AssetPath` param receives a backslash-bearing value, the dispatcher rewrites `\` → `/` and appends a notification. Never silent. See §14.2. 3. **Survivor B `_fields` / `_omit` collision** — when both response-shaping whitelist and blacklist are non-empty, `_fields` wins and a warning is appended. See §14.1. All three sources emit free-text strings into the same array. No schema or envelope change — `FMonolithActionResult` shape is unchanged. **`error.data.suggestions` channel (Phase 2, 2026-05-27).** On `ErrMethodNotFound` / `ErrInvalidParams` dispatch failures where the action name or namespace is unknown, the error envelope carries an optional `data: { kind: "action"|"namespace", suggestions: [{namespace, action, score}, ...] }` payload. Top-3 fuzzy matches over the registry keyspace via UE's `Algo::LevenshteinDistance`; normalised score = `1.0 - dist/max(len)`. The dispatcher snapshots the keyspace under `FScopeLock`, drops the lock, then scores and sorts — no read-path latency spike on the hot dispatch loop. Asset-path and property-name fuzzy matching are explicitly OUT-OF-SCOPE (O(N·L²) over 10K+ registry entries kills it; property-name overlap with the K3 unknown-key warning produces noise). See §14.4. (WISHLIST) K3 unknown-key counter as telemetry feedback for evaluating future retry-thrash interventions. ### Module Loading | Module | Loading Phase | Type | |--------|--------------|------| | MonolithCore | PostEngineInit | Editor | | All others (17 Editor + 1 Runtime) | Default | Editor (MonolithBABridge is optional — empty shell when Blueprint Assist absent). MonolithReflectionIntel ships v0.17.0 Phases 1 + 2 + 3a of Reflection Intelligence — `decision_query` + `risk_query` + `cppreflect_query` namespaces plus a `source_query("audit_module_dep_reality")` audit action. MonolithAudioRuntime is Type: Runtime — provides `UMonolithSoundPerceptionUserData` + `UMonolithAudioPerceptionSubsystem` for `audio::bind_sound_to_perception` | ### Plugin Dependencies - Niagara - SQLiteCore - EnhancedInput - EditorScriptingUtilities - PoseSearch - IKRig - ControlRig - RigVM - Sockets - Networking - GeometryScripting (optional — enables Tier 5 mesh operations) - GameplayAbilities (optional — enables MonolithGAS module; `#if WITH_GBA` compile guard) ### Optional-Dependency Detection Matrix Modules that probe for optional plugins follow a unified Build.cs convention: 3-location detection (project Plugins/, engine Plugins/Marketplace/, engine Plugins/Runtime/) with `MONOLITH_RELEASE_BUILD=1` env-var escape hatch. When the env var is `"1"`, detection short-circuits and the corresponding `WITH_*` define is forced off — the released DLL drops the hard import and Blueprint-only users without the dep don't hit `GetLastError=126` at module load. | Module | Optional dep | Compile guard | Build.cs file | Hotfix landed | |--------|--------------|---------------|---------------|---------------| | MonolithBABridge | BlueprintAssist | `WITH_BLUEPRINT_ASSIST` | `MonolithBABridge.Build.cs` | (canonical) | | MonolithMesh | GeometryScripting | `WITH_GEOMETRYSCRIPT` | `MonolithMesh.Build.cs` | **v0.14.1** (#26 / #30) | | MonolithGAS | GameplayAbilities | `WITH_GBA` | `MonolithGAS.Build.cs` | (existing) | | MonolithComboGraph | ComboGraph | `WITH_COMBOGRAPH` | `MonolithComboGraph.Build.cs` | (existing) | | MonolithLogicDriver | Logic Driver Pro | `WITH_LOGICDRIVER` | `MonolithLogicDriver.Build.cs` | (existing) | | MonolithAudio | MetaSound | `WITH_METASOUND` | `MonolithAudio.Build.cs` | (existing) | | MonolithUI | CommonUI | `WITH_COMMONUI` | `MonolithUI.Build.cs` | **v0.14.0** (M0.5) | | MonolithAI | StateTree, SmartObjects | `WITH_STATETREE`, `WITH_SMARTOBJECTS` (required); `WITH_MASSENTITY`, `WITH_ZONEGRAPH` (optional) | `MonolithAI.Build.cs` | (existing) | --- ## 3. Module Reference Each module has its own spec file under `specs/`. The table below is the index. | # | Module | Spec | Summary | |---|--------|------|---------| | 3.1 | MonolithCore | [specs/SPEC_MonolithCore.md](specs/SPEC_MonolithCore.md) | HTTP server (bind retry, Restart(), `Monolith.Restart` console cmd), tool registry, discovery, settings, auto-updater, `monolith_guide` editorial cross-namespace guide (section-keyed), improved central error messages (15 sites carry inline recovery guidance) | | 3.2 | MonolithBlueprint | [specs/SPEC_MonolithBlueprint.md](specs/SPEC_MonolithBlueprint.md) | Blueprint inspection, variable/component/graph CRUD, node ops, compile, spawn (~120+ actions) | | 3.3 | MonolithMaterial | [specs/SPEC_MonolithMaterial.md](specs/SPEC_MonolithMaterial.md) | Material inspection + graph editing + CRUD + function suite (~60+ actions) | | 3.4 | MonolithAnimation | [specs/SPEC_MonolithAnimation.md](specs/SPEC_MonolithAnimation.md) | Animation sequences, montages, ABPs, curves, notifies, skeletons, PoseSearch, ABP write, ControlRig (~150+ actions) | | 3.5 | MonolithNiagara | [specs/SPEC_MonolithNiagara.md](specs/SPEC_MonolithNiagara.md) | Niagara particle systems, HLSL module/function, DI config, event handlers, sim stages, layout, temporal control, stateless-emitter factory, search & discovery (~120+ actions) | | 3.6 | MonolithEditor | [specs/SPEC_MonolithEditor.md](specs/SPEC_MonolithEditor.md) | Build triggers, live compile, log capture, crash context, scene capture, PIE smoke/capture + input/control harness (~50+ actions) | | 3.7 | MonolithConfig | [specs/SPEC_MonolithConfig.md](specs/SPEC_MonolithConfig.md) | Config/INI resolution and search (6 actions, fixed surface) | | 3.8 | MonolithIndex | [specs/SPEC_MonolithIndex.md](specs/SPEC_MonolithIndex.md) | SQLite FTS5 deep project indexer (~10+ MCP actions, 18 internal indexers incl. `FMetaSoundIndexer` from PR #18 by @alakangas) | | 3.9 | MonolithSource | [specs/SPEC_MonolithSource.md](specs/SPEC_MonolithSource.md) | Engine source + API lookup, LLM C++ authoring ergonomics (~20+ actions) | | 3.10 | MonolithUI | [specs/SPEC_MonolithUI.md](specs/SPEC_MonolithUI.md) | Widget blueprint CRUD, slot/template/styling, animation v1+v2, bindings, settings/accessibility scaffolds, **Spec Builder + Type Registry + EffectSurface + Style Service** (Phase A–L expansion 2026-04-26), CommonUI categories A–I. **~130+ module-owned actions** (always-on + CommonUI under `WITH_COMMONUI`) + 4 GAS UI binding aliases | | 3.11 | MonolithMesh | [specs/SPEC_MonolithMesh.md](specs/SPEC_MonolithMesh.md) | Mesh/scene/spatial/blockout/GeometryScript/procedural (~190+ core + 45 experimental town gen) | | 3.12 | MonolithBABridge | [specs/SPEC_MonolithBABridge.md](specs/SPEC_MonolithBABridge.md) | IModularFeatures bridge for Blueprint Assist (0 MCP actions, integration only) | | 3.13 | MonolithGAS | [specs/SPEC_MonolithGAS.md](specs/SPEC_MonolithGAS.md) | Gameplay Ability System integration (~130+ actions, incl. 4 UI binding aliased into `ui::`, WITH_GBA) | | 3.14 | MonolithComboGraph | [specs/SPEC_MonolithComboGraph.md](specs/SPEC_MonolithComboGraph.md) | ComboGraph integration (~13 actions, WITH_COMBOGRAPH) | | 3.15 | MonolithLogicDriver | [specs/SPEC_MonolithLogicDriver.md](specs/SPEC_MonolithLogicDriver.md) | Logic Driver Pro integration (~66 actions, WITH_LOGICDRIVER) | | 3.16 | MonolithAI | [specs/SPEC_MonolithAI.md](specs/SPEC_MonolithAI.md) | Behavior Trees, Blackboards, State Trees, EQS, Smart Objects, Perception, Nav (~220+ actions) | | 3.17 | MonolithAudio | [specs/SPEC_MonolithAudio.md](specs/SPEC_MonolithAudio.md) | Sound Cues, MetaSounds (Builder API + document introspection), batch audio ops, AI Perception bind (~90+ actions, MetaSound features WITH_METASOUND) | | 3.18 | MonolithReflectionIntel | [specs/SPEC_MonolithReflectionIntel.md](specs/SPEC_MonolithReflectionIntel.md) | Deterministic, $0-LLM reflection intelligence — Decision + Risk + CppReflect + Network + Pipeline + Reflect (maintenance) + Module-Dep Reality Audit + 4 cross-namespace audit actions. v0.17.0 shipped 26 actions across `decision_query` (5), `risk_query` (5), `cppreflect_query` (5), `network_query` (4), `pipeline_query` (2), `source_query("audit_module_dep_reality")` (1), plus 4 audit actions on existing namespaces — `material_query("audit_orphan_materials")` / `niagara_query("audit_cross_asset_refs")` / `blueprint_query("audit_cdo_drift")` / `project_query("audit_orphan_assets")`; the `cppreflect_query("list_class_specifiers")` follow-up brings `cppreflect_query` to 6, and the `reflect_query("rebuild_reflection_index")` network-completeness follow-up adds the new `reflect` namespace (1 WRITE/maintenance action — module total ~28 actions). Phase 3b native-tag tracking (tree-sitter) and Phase 4b tag-graph / thread-safety audits are WISHLIST — both blocked on tree-sitter substrate. | --- ## 4. Source Indexer ### 4.1 C++ Indexer (current) The engine source indexer is a native C++ implementation within `MonolithSource`. `UMonolithSourceSubsystem` builds and maintains `EngineSource.db` in-process. Indexing is triggered via: - **`trigger_reindex`** — full engine source re-index - **`trigger_project_reindex`** — incremental project-only C++ re-index (faster; only updates project symbols) ### 4.2 Python Source Indexer (legacy) > **LEGACY:** The Python tree-sitter indexer in `Scripts/source_indexer/` has been superseded by the native C++ indexer. It is no longer invoked by MonolithSource and is retained only for reference. **Location:** `Scripts/source_indexer/` **Entry point:** `python -m source_indexer --source PATH --db PATH [--shaders PATH]` **Dependencies:** tree-sitter>=0.21.0, tree-sitter-cpp>=0.21.0, Python 3.10+ #### Pipeline (IndexingPipeline) 1. **Module Discovery** — Walks Runtime, Editor, Developer, Programs under Engine/Source + Engine/Plugins. Optionally Engine/Shaders 2. **File Processing** — C++ files -> CppParser (tree-sitter AST) -> symbols, includes. Shader files -> ShaderParser (regex) -> symbols, includes 3. **Source Line FTS** — Chunks source in batches of 10 lines into source_fts table 4. **Finalization** — Resolves inheritance, runs ReferenceBuilder for call/type cross-references #### Parsers | Parser | Technology | Handles | |--------|-----------|---------| | CppParser | tree-sitter-cpp | Classes, structs, enums, functions, variables, macros, typedefs. UE macro awareness (UCLASS, USTRUCT, UENUM, UFUNCTION, UPROPERTY). 3 fallback strategies | | ShaderParser | Regex | #include, #define, struct, function declarations in .usf/.ush | | ReferenceBuilder | tree-sitter-cpp (2nd pass) | Call references, type references, local variable type resolution | ### Source DB Schema | Table | Purpose | |-------|---------| | `modules` | id, name, path, module_type, build_cs_path | | `files` | id, path, module_id, file_type, line_count, last_modified | | `symbols` | id, name, qualified_name, kind, file_id, line_start, line_end, parent_symbol_id, access, signature, docstring, is_ue_macro | | `inheritance` | id, child_id, parent_id | | `references` | id, from_symbol_id, to_symbol_id, ref_kind, file_id, line | | `includes` | id, file_id, included_path, line | | `symbols_fts` | FTS5 on name, qualified_name, docstring | | `source_fts` | FTS5 on text (file_id, line_number UNINDEXED) | | `meta` | key, value | --- ## 5. Offline CLI Two options for offline access (no full editor session required): ### 5.1 monolith_query.exe (preferred) **Binary:** `Plugins/Monolith/Binaries/monolith_query.exe` **Source:** `Tools/MonolithQuery/` — build via `build.bat` **Run via:** ``` 'Plugins/Monolith/Binaries/monolith_query.exe' [args...] ``` Standalone C++ executable. No UE runtime, no Python, instant startup. Queries `EngineSource.db` and `ProjectIndex.db` directly. Replaces the previous `MonolithQueryCommandlet` (removed) and supersedes `monolith_offline.py` as the primary offline access path. ### 5.2 monolith_offline.py (legacy) > **LEGACY:** `monolith_offline.py` is superseded by `monolith_query.exe`. It remains functional as a zero-dependency fallback requiring only Python stdlib and no UE installation. **Location:** `Scripts/monolith_offline.py` **Dependencies:** Python stdlib only (sqlite3, argparse, json, re, pathlib) — no pip installs required **Python version:** 3.8+ A companion CLI that queries `EngineSource.db` and `ProjectIndex.db` directly without the Unreal Editor running. Intended as a fallback when MCP is unavailable (editor down, CI environments, quick terminal lookups). **Scope:** Read/query operations only. Write operations require the editor and MCP. ### Usage ``` python Scripts/monolith_offline.py [args...] ``` ### Namespaces and Actions **Source (9 actions)** — mirrors `source_query` MCP tool: | Action | Positional | Key Options | Description | |--------|-----------|-------------|-------------| | `search_source` | `query` | `--limit`, `--module`, `--kind` | FTS across symbols + source lines, BM25 ranked | | `read_source` | `symbol` | `--max-lines`, `--members-only`, `--no-header` | Source for a class/function/struct; FTS fallback on no exact match | | `find_references` | `symbol` | `--ref-kind`, `--limit` | All usage sites | | `find_callers` | `symbol` | `--limit` | Functions that call the given function | | `find_callees` | `symbol` | `--limit` | Functions called by the given function | | `get_class_hierarchy` | `symbol` | `--direction up\|down\|both`, `--depth` | Inheritance tree traversal | | `get_module_info` | `module_name` | — | File count, symbol counts by kind, key classes | | `get_symbol_context` | `symbol` | `--context-lines` | Definition with surrounding context | | `read_file` | `file_path` | `--start`, `--end` | Read source lines; resolves via absolute path → DB exact → DB suffix match | **Project (5 actions)** — mirrors `project_query` MCP tool: | Action | Positional | Key Options | Description | |--------|-----------|-------------|-------------| | `search` | `query` | `--limit` | FTS across assets FTS + nodes FTS, BM25 ranked | | `find_by_type` | `asset_class` | `--limit`, `--offset` | Filter assets by class with pagination | | `find_references` | `asset_path` | — | Bidirectional: depends_on + referenced_by | | `get_stats` | — | — | Row counts for all tables + top 20 asset class breakdown | | `get_asset_details` | `asset_path` | — | Nodes, variables, parameters for one asset | **Reflection Intelligence (20 actions) — full parity, byte-identical to live.** All four RI namespaces are fully servable offline: `cppreflect` (6 — `get_uclass`, `list_uproperties`, `list_ufunctions`, `find_interface_impls`, `find_class_specifier`, `list_class_specifiers`), `network` (4 — `list_replicated_classes`, `list_rpc_functions`, `list_onrep_handlers`, `audit_unbalanced_onreps`), `decision` (5 — `list_decisions`, `get_decision`, `list_stale`, `find_supersession_chain`, `find_referent_decisions`), and `risk` (5 — `get_hotspot_score`, `get_cochange_pairs`, `get_file_churn`, `get_release_window_hotspots`, `list_conditional_gates`). The offline JSON is **byte-identical** to the live MCP server: same field names, types, key ordering, row data, float formatting (`%.17g`), and cursor tokens (base64 of UE pretty-printed `{\r\n\t"qh":N,...}` using UE's `Strihash_DEPRECATED` filter-hash). Prior builds covered only 4 of the 20 with divergent shapes and a phantom `risk.list_hotspots` action (now removed). **Two intentional, documented differences vs the live MCP payload (NOT bugs):** 1. **`success` envelope.** The offline CLI adds a top-level `success: true|false` as its in-band status channel — a CLI has no MCP protocol envelope to carry success/error out-of-band. Live payloads have no `success` key. The DATA payload nested under it is byte-identical. 2. **Wall-clock fields.** Time-derived values (`cutoff_unix`, `since_unix`) and any cursor whose filter-hash includes them (`risk.get_release_window_hotspots`) legitimately differ by the run-time gap across separate process invocations — on both live and offline. This is faithful parity with a live time-quirk, not drift. **exe is canonical; py is a dev-only fallback in lockstep.** `monolith_query.exe` is the preferred path. `monolith_offline.py` is a stdlib-only fallback kept byte-for-byte equal to the exe — the parity guard enforces `exe == py`. **Parity + freshness guards (ship-blocking):** | Script | Role | |--------|------| | `Scripts/verify_offline_parity.py` | HARD-GATE parity guard — byte-diffs exe vs py across all 20 RI actions; exits non-zero on any diff. `make_release.ps1` runs it as a ship gate. | | `Scripts/check_offline_exe_fresh.py` | Staleness guard — compares the exe's `--version` `source_hash` against a fresh hash of `monolith_query.cpp`; flags a stale exe. | | `Tools/MonolithQuery/build.bat` | Injects `SOURCE_HASH` (certutil SHA256 of the source) into the exe via `/DSOURCE_HASH`. | `make_release.ps1` now builds the exe fresh (vcvars + `build.bat`) before the Binaries copy, then runs the parity guard — a stale or drifted exe can never ship. The 20-action RI parity is the scope verified this sprint; the source/project namespaces share the same engine but were not re-audited for byte-parity in this pass. ### Implementation Notes - Opens DBs with `PRAGMA query_only=ON` + `PRAGMA journal_mode=DELETE`. The DELETE journal mode override is mandatory — WAL mode silently returns 0 rows on Windows when opened in any read-only mode (same bug that affected the C++ module; see CLAUDE.md Key Lessons). - FTS escaping mirrors `EscapeFTS()` in C++: `::` replaced with space, non-word chars stripped, each token wrapped as `"token"*` for prefix match. - `read_source` defaults to `--header` (includes `.h` declarations). Pass `--no-header` to skip header files. - `read_file` with `--end 0` (default) reads 200 lines from `--start`. - Source output is plain text. Project output is JSON. --- ## 6. Skills (11 bundled) | Skill | Trigger Words | Entry Point | Actions | |-------|--------------|-------------|---------| | unreal-animation | animation, montage, ABP, blend space, notify, curves, compression, PoseSearch | `animation_query()` | 116 | | unreal-audio | audio, sound, SoundCue, MetaSound, attenuation, submix, mixing | `audio_query()` | 81 | | unreal-blueprints | Blueprint, BP, event graph, node, variable | `blueprint_query()` | 86 | | unreal-build | build, compile, Live Coding, hot reload, rebuild | `editor_query()` | 19 | | unreal-cpp | C++, header, include, UCLASS, Build.cs, linker error | `source_query()` + `config_query()` | 11+6 | | unreal-debugging | build error, crash, log, debug, stack trace | `editor_query()` | 19 | | unreal-materials | material, shader, PBR, texture, material graph | `material_query()` | 57 | | unreal-niagara | Niagara, particle, VFX, emitter | `niagara_query()` | 129 | | unreal-performance | performance, optimization, FPS, frame time | Cross-domain | config + material + niagara | | unreal-project-search | find asset, search project, dependencies | `project_query()` | 7 | | unreal-ui | UI, HUD, widget, menu, settings, save game, accessibility, CommonUI, activatable, button style, input glyph, focus, **spec builder, EffectSurface, type registry, style service** | `ui_query()` | 117 | All skills follow a common structure: YAML frontmatter, Discovery section, Asset Path Conventions table, action tables, workflow examples, and rules. --- ## 7. Configuration **Settings location:** Editor Preferences > Plugins > Monolith **Config file:** `Config/MonolithSettings.ini` section `[/Script/MonolithCore.MonolithSettings]` Setting names below match the actual `UMonolithSettings` UPROPERTY identifiers in `Source/MonolithCore/Public/MonolithSettings.h` (verified 2026-04-26 audit). The convention is `bEnable` for module toggles, `bEnableProceduralTownGen` for experimental sub-features. | Setting | Default | Description | |---------|---------|-------------| | ServerPort | 9316 | MCP HTTP server port | | bAutoUpdateEnabled | True | GitHub Releases auto-check on startup | | DatabasePathOverride | (empty) | Override default DB path (Plugins/Monolith/Saved/) | | EngineSourceDBPathOverride | (empty) | Override engine source DB path | | EngineSourcePath | (empty) | Override engine source directory | | bEnableBlueprint | True | Enable Blueprint module | | bEnableMaterial | True | Enable Material module | | bEnableAnimation | True | Enable Animation module | | bEnableNiagara | True | Enable Niagara module | | bEnableEditor | True | Enable Editor module | | bEnableConfig | True | Enable Config module | | bEnableIndex | True | If false, skips the indexing *run* at subsystem init (query actions stay registered, so an existing DB still answers). Escape hatch for the large-project deep-index GC-worker-exhaustion crash class | | bEnableSource | True | Enable Source module | | bEnableUI | True | Enable UI module | | bEnableMesh | True | Enable Mesh module (core actions) | | bEnableGAS | True | Enable GAS module (requires GameplayAbilities plugin; no-op if `WITH_GBA=0`) | | bEnableComboGraph | True | Enable ComboGraph module (no-op if `WITH_COMBOGRAPH=0`) | | bEnableLogicDriver | True | Enable Logic Driver Pro module (no-op if `WITH_LOGICDRIVER=0`) | | bEnableAI | True | Enable AI module (Behavior Trees, Blackboards, State Trees, EQS, Smart Objects, Perception, Navigation) | | bEnableAudio | True | Enable Audio module (Sound Cues, MetaSounds, batch ops, AI Perception bind) | | bEnableExternalInventoryModule | True | Allow an external sibling plugin to register `inventory_query` actions | | bEnableProceduralTownGen | **False** | Enable Procedural Town Generator actions (45 actions). Requires `bEnableMesh`. **Work-in-progress** — known geometry issues, disabled by default. Unless you're willing to dig in and help improve it, best left alone | | bEnableBlueprintAssist | True | Allow MonolithBABridge to register IMonolithGraphFormatter when Blueprint Assist is present. Set false to force built-in layout for all auto_layout calls | | bDeferFirstTimeIndex | False | If true, first-time indexing won't run automatically. Use `Monolith.StartIndex` console command to trigger | | bLogMemoryStats | False | Log memory usage during indexing for debugging. Default off — enable when investigating memory pressure | | LogVerbosity | 3 (Log) | 0=Silent, 1=Error, 2=Warning, 3=Log, 4=Verbose | **Note:** Module enable toggles are functional — each module checks its toggle at registration time and skips action registration if disabled. **Exception — `bEnableIndex`:** the Index module always registers its query actions (so `project_query` answers from an existing DB); the toggle instead gates the indexing *run* at `UMonolithIndexSubsystem::Initialize`. `bDeferFirstTimeIndex` similarly skips only the automatic first-time index, leaving it triggerable via the `Monolith.StartIndex` console command. --- ## 8. Templates | File | Purpose | |------|---------| | `Templates/.mcp.json.example` | Minimal MCP config. Transport type varies by client: `"http"` for Claude Code, `"streamableHttp"` for Cursor/Cline. URL: `http://localhost:9316/mcp` | | `Templates/.mcp.json.proxy.example` | MCP config variant for clients that need the stdio↔HTTP proxy bridge (Cursor / Cline / Continue). Spawns `Scripts/monolith_proxy.py`. | **Note on AI project-instructions files:** As of v0.14.7 we no longer ship a `CLAUDE.md.example` template. Conventions across AI assistants (`CLAUDE.md`, `AGENTS.md`, `.cursorrules`, `.github/copilot-instructions.md`, etc.) drift faster than a static template can track — installers are pointed at their own assistant for the right shape. See README §Step 5. --- ## 9. File Structure ``` YourProject/Plugins/Monolith/ Monolith.uplugin README.md LICENSE (MIT) ATTRIBUTION.md (Credits: Codeturion concept, tumourlove originals) .gitignore Config/ MonolithSettings.ini Docs/ plans/ 2026-03-06-monolith-design.md 2026-03-06-monolith-implementation-plan.md phase-3-animation-niagara.md Plans/ Phase6_Skills_Templates_Polish.md Skills/ unreal-animation/unreal-animation.md unreal-blueprints/unreal-blueprints.md unreal-build/unreal-build.md unreal-cpp/unreal-cpp.md unreal-debugging/unreal-debugging.md unreal-materials/unreal-materials.md unreal-niagara/unreal-niagara.md unreal-performance/unreal-performance.md unreal-project-search/unreal-project-search.md unreal-ui/unreal-ui.md Templates/ .mcp.json.example .mcp.json.proxy.example Scripts/ source_indexer/ (LEGACY: Python tree-sitter indexer — superseded by C++ indexer in MonolithSource) db/schema.py ... MCP/ pyproject.toml (Package scaffold — CLI is unimplemented stub) src/monolith_source/ Source/ MonolithCore/ (8 source files) MonolithBlueprint/ (4 source files) MonolithMaterial/ (4 source files) MonolithAnimation/ (6 source files — includes PoseSearch) MonolithNiagara/ (4 source files) MonolithEditor/ (4 source files) MonolithConfig/ (4 source files) MonolithIndex/ (12+ source files) MonolithSource/ (8 source files) MonolithUI/ (17+ source files — UMG baseline + CommonUI categories A-I, conditional on WITH_COMMONUI) MonolithGAS/ (conditional on WITH_GBA — abilities, attributes, effects, ASC, tags, cues, targets, input, inspect, scaffold) MonolithComboGraph/ (conditional on WITH_COMBOGRAPH — combo graph CRUD, nodes, edges, effects, cues, ability scaffolding) MonolithAI/ (conditional on WITH_STATETREE + WITH_SMARTOBJECTS — BT, BB, ST, EQS, SO, Controllers, Perception, Navigation, Runtime, Scaffolds) MonolithLogicDriver/ (conditional on WITH_LOGICDRIVER — SM CRUD, graph read/write, node config, runtime/PIE, JSON spec, scaffolding, discovery, components, text graph) Tools/ MonolithProxy/ (MCP stdio-to-HTTP proxy source + build.bat) MonolithQuery/ (Offline query tool source + build.bat) Binaries/ monolith_proxy.exe (Compiled MCP proxy — replaces Python proxy) monolith_query.exe (Compiled offline query tool — replaces MonolithQueryCommandlet) Saved/ .gitkeep monolith_offline.py (Legacy offline CLI — superseded by monolith_query.exe) EngineSource.db (Engine source index, ~1.8GB — not in git) ProjectIndex.db (Project asset index — not in git) ``` --- ## 10. Deployment ### Development & Release Workflow Everything lives in one place: `YourProject/Plugins/Monolith/` This folder is both the working copy and the git repo (`git@github.com:tumourlove/monolith.git`). Edit, build, commit, push, and release all happen here — no file copying. #### Publishing a release 1. Bump version in `Source/MonolithCore/Public/MonolithCoreModule.h` (`MONOLITH_VERSION`) and `Monolith.uplugin` (`VersionName`) 2. Update `CHANGELOG.md` 3. UBT build (bakes version into DLLs) 4. `git add -A && git commit && git push origin master` 5. Create zip: `powershell -ExecutionPolicy Bypass -File Scripts/make_release.ps1 -Version "X.Y.Z"` (excludes Intermediate/Saved/.git, sets `"Installed": true` for BP-only users) 6. `gh release create vX.Y.Z "../Monolith-vX.Y.Z.zip" --title "..." --notes "..."` **Important:** Release zips MUST include pre-compiled DLLs (`Binaries/Win64/*.dll`) so Blueprint-only users can use the plugin without rebuilding. The `make_release.ps1` script sets `"Installed": true` in the zip's `.uplugin` to suppress rebuild prompts. The local dev copy keeps `"Installed": false`. #### Auto-updater flow 1. On editor startup (5s delay), checks `api.github.com/repos/tumourlove/monolith/releases/latest` 2. Compares `tag_name` semver against compiled `MONOLITH_VERSION` 3. Selects the release asset for the running engine (compile-time `ENGINE_MINOR_VERSION`; fail-closed if a per-engine release has no matching asset) and parses the matching `Monolith-SHA256-v2-UE5.:` integrity marker from the release notes (fail-closed if a per-engine asset has no matching v2 marker; legacy assets use `Monolith-SHA256-v2:`, absent = proceed unverified with a warning) 4. If newer: shows a dialog window with full release notes + "Install Update" / "Remind Me Later" 5. Download stages to `Saved/Monolith/Staging/` (NOT Plugins/ — would cause UBT conflicts); the downloaded zip's SHA-256 is computed with the plugin's portable `MonolithSha256::Compute` (FIPS 180-4, no engine platform hook — `FPlatformMisc::GetSHA256Signature` has no Windows impl and its generic fallback fatally asserts, Issues #90/#94) and must equal the parsed marker or the install refuses. The "v2" marker generation exists because v0.14.7–v0.21.0 updaters hard-assert on the old marker names — old names must never be emitted again 6. On editor exit, a detached swap script runs: - Polls `tasklist` for `UnrealEditor.exe` until it's gone (120s timeout) - Asks for user confirmation (Y/N) - `move` command with retry loop (10 attempts × 3s) to handle Defender/Indexer file locks - `xcopy /h` copies new version, preserves `.git/`, `.gitignore`, `.github/` - Rollback on failure: removes partial copy, restores backup - Shows conditional message: C++ users rebuild, BP-only users launch immediately ### Installation (for other projects) 1. Clone to `YourProject/Plugins/Monolith` 2. Copy `Templates/.mcp.json.example` to project root as `.mcp.json` 3. Launch editor — Monolith auto-starts and indexes 4. Optionally copy `Skills/*` to `~/.claude/skills/` --- ### Editor Component Persistence (UPROPERTY-Backed References) Any MCP action that builds components on an editor-spawned `AActor` via `NewObject<...>(Actor, ...)` MUST register each component on `AActor::InstanceComponents` (a `UPROPERTY(Instanced) TArray>` at `Engine/Source/Runtime/Engine/Classes/GameFramework/Actor.h:4331`). Without `AActor::AddInstanceComponent(Comp)` the owning actor holds no UPROPERTY-backed reference to the component, `RegisterComponent()` wires it up for the current session only, and the component disappears on level save/reload — silent data loss. This was the root cause of Issue #63 across three action sites. The canonical sequence — `Modify()` -> `NewObject(... RF_Transactional)` -> `SetupAttachment` / `SetRootComponent` -> `AddInstanceComponent` -> `RegisterComponent` -> component setup -> `MarkPackageDirty()` — matches `Engine/Source/Editor/UnrealEd/Private/Factories/ActorFactory.cpp:1321` and `Engine/Source/Editor/UnrealEd/Private/Kismet2/ComponentEditorUtils.cpp:621`: ```cpp Actor->Modify(); USomeComponent* Comp = NewObject( Actor, USomeComponent::StaticClass(), TEXT("ComponentName"), RF_Transactional); Comp->SetupAttachment(RootComp); // or Actor->SetRootComponent(Comp) for the root Actor->AddInstanceComponent(Comp); // THE persistence anchor — order vs RegisterComponent is flexible; what matters is the array references the component at save-time Comp->RegisterComponent(); Comp->SetStaticMesh(...); // component-specific configuration AFTER register Actor->MarkPackageDirty(); // once all components are added ``` --- ### Preview & Inspection Surface (`editor::`) The `editor::` namespace exposes a tight family of capture and inspect actions that let AI agents introspect Unreal assets at higher fidelity than the default 256² thumbnail. Four new actions land alongside three extensions to the existing `editor::capture_scene_preview`. **Extended `asset_type` enum values:** `static_mesh`, `skeletal_mesh` (with optional `animation_path` + `seek_time` for posed-frame capture), and `widget` (UMG via `FWidgetRenderer` with `scale` DPI multiplier) join the prior `material` / `niagara`. **New actions:** `editor::capture_material_grid` (N material instances side-by-side under shared lighting, auto-grid via `ceil(sqrt(N))` with optional `columns` override); `editor::capture_with_overlay` (single-asset capture under one of five engine debug-view show flags — `wireframe`, `normals`, `uv_density`, `lightmap_density`, `shader_complexity`); `editor::inspect_material_pbr` (reflective walk of a material's texture parameter list, classifying each by PBR slot and detecting ORM / ARM / MRA channel-packing — pure JSON, no rendering); `editor::inspect_texture_channels` (per-channel R/G/B/A min/max/mean statistics + optional per-channel split PNGs via `emit_splits`). All seven are editor-only and live in `MonolithEditor`. The canonical pattern for the capture-style actions is `FAdvancedPreviewScene` + `USceneCaptureComponent2D` + `UTextureRenderTarget2D` + `FImageUtils::SaveImageAutoFormat` (mirrors the existing `HandleCaptureScenePreview` recipe — game-thread invoke, render-thread enqueue via `CaptureScene()`, readback via `GameThread_GetRenderTargetResource()->ReadPixels()`). The widget path additionally uses `FWidgetRenderer::DrawWidget` against the same RT (guard with `FApp::CanEverRender()` — headless commandlets and `-nullrhi` will return a clear error). The inspect-style actions skip the render path entirely: `inspect_material_pbr` walks `UMaterialEditingLibrary::GetTextureParameterNames` + `GetTextureParameterValue` then routes each through a small PBR classifier; `inspect_texture_channels` locks the source mip via `FTextureSource::LockMipReadOnly`, computes statistics in a single pass, and writes split PNGs only when `emit_splits=true`. Source-of-truth files: `Source/MonolithEditor/Private/MonolithEditorActions.cpp` (the `asset_type` enum extension lives inside `HandleCaptureScenePreview`), `Source/MonolithEditor/Private/MonolithEditorPreviewActions.cpp` (`capture_material_grid` + `capture_with_overlay`), `Source/MonolithEditor/Private/MonolithEditorInspectActions.cpp` (`inspect_material_pbr` + `inspect_texture_channels`). Header surface lives in `Source/MonolithEditor/Public/MonolithEditorActions.h` (four new static handler declarations alongside the existing capture handler). **`editor::delete_assets` non-interactive deletion (v0.18.0).** `delete_assets` now takes an optional `force` bool (default `false`) and never raises a blocking modal. Before deleting each target it clears the package dirty flag and closes any open asset editor, then performs the delete inside an unattended-script guard so the engine cannot pop a Slate "asset in use" / "save changes" dialog (which would freeze an unattended MCP session). `force=false` soft-deletes after closing editors; `force=true` calls `ForceDeleteObjects`, nulling referencers. Per-asset failures are surfaced in a `failed_to_delete[]` response array instead of aborting the whole call. Native C++ — no Python. **Known limitation:** a `UNiagaraScript` created and compiled within the same session can't be deleted until its compile state clears (a transient compilation/traversal graph retains a reference — the engine's own Content Browser hits the same wall); it becomes deletable after the state clears, e.g. on editor restart. Per-action detail: [`SPEC_MonolithEditor.md`](specs/SPEC_MonolithEditor.md). AI discoverability: `monolith_guide(section="recipes")` returns Recipe 5 ("Visual introspection — going beyond thumbnails") and Recipe 6 ("Reading asset structure without rendering"); `monolith_guide(section="decisions")` returns the `capture_scene_preview` vs `capture_material_grid` vs `inspect_material_pbr` decision matrix. Live param schemas come from `describe_query("action_schema", target_namespace="editor", target_action="")` (`monolith_discover("editor")` lists action names + one-line descriptions only). --- ## 11. Known Issues & Workarounds See `TODO.md` for the full list. Key architectural constraints: - **6 reimplemented NiagaraEditor helpers** — NiagaraEditor APIs not exported by Epic; Monolith reimplements them locally - **SSE is stub-only** — GET endpoint returns single event and close, not full streaming - **MaterialExpressionNoise fails on Lumen card passes** — Compiles for base pass but errors on Lumen card capture shaders ("function signature unavailable"). Engine limitation, not a Monolith bug. Workaround: use custom HLSL noise or pre-baked noise textures instead. - **MaterialExpressionRadialGradientExponential does not exist** — Despite appearing in some community references, this expression class is not in UE 5.7. Use a Custom HLSL node with `pow(1.0 - saturate(length(UV - 0.5) * 2.0), Exponent)` instead. ### Recent Fixes (Phase J — shipped in 0.14.7) - **F1 (2026-04-26) — BT crash hardening (`MonolithAIBehaviorTreeActions.cpp`).** Five `ai::add_bt_*` actions and `build_behavior_tree_from_spec` now reject Task-under-Root parenting at the API entry point via `ValidateParentForChildTask` helper and a schema-checked `ConnectParentChild`. Root cause: `UBehaviorTreeGraphNode_Root::NodeInstance` is `nullptr` by engine design; wiring a Task there produced a malformed graph that crashed `UBehaviorTreeGraph::UpdateAsset()` at `BehaviorTreeGraph.cpp:517` (`RootNode->Children.Reset()` on null `BTAsset->RootNode`). Hardened sites: `add_bt_node` (Task class), `add_bt_run_eqs_task`, `add_bt_smart_object_task`, `add_bt_use_ability_task`, `move_bt_node` (Task target), `build_behavior_tree_from_spec` (Task root). `reorder_bt_children` audited, structurally safe (permutation of existing children only). Investigation: `Docs/research/2026-04-26-bt-ability-task-crash-investigation.md`. Plan: `Docs/plans/2026-04-26-monolith-j-phase-fix-plan.md` §F1. Tests: `Plugins/Monolith/Docs/testing/2026-04-26-j2-bt-gas-ability-task-test.md` Failure Modes §Phase F1 (J2-AB-Crash-01..09). - **F2 (2026-04-26) — `gas::bind_widget_to_attribute` rejects unknown `owner_resolver` (`MonolithGASUIBindingActions.cpp`).** `ParseOwner` was a `EMonolithAttrBindOwner`-returning function whose fall-through silently coerced any unrecognized string (e.g. `owner_resolver="banana"`) to `OwningPlayerPawn`. Refactored to `bool ParseOwner(S, OutOwner, OutSocketTag, OutError)`. Empty input still defaults to `OwningPlayerPawn` (back-compat); any non-empty string that matches none of `[owning_player_pawn, owning_player_state, owning_player_controller, self_actor, named_socket:]` now returns an `FMonolithActionResult::Error` with the full valid-list enumeration. Socket-tag extraction collapsed into the validator (single source of truth — call site no longer re-splits `OwnerStr` on `:`). Investigation: `Docs/research/2026-04-26-j1-gas-binding-findings.md` §A.1. Plan: `Docs/plans/2026-04-26-monolith-j-phase-fix-plan.md` §F2. - **F3 (2026-04-26) — `gas::bind_widget_to_attribute` rejects `format=format_string` templates missing required slots (`MonolithGASUIBindingActions.cpp`).** New helper `ValidateFormatStringPayload(Payload, bHasMaxAttribute, OutError)` enforces that `{0}` is present whenever `format=format_string` is selected, and additionally `{1}` whenever `max_attribute` is bound. Both bare slot (`{0}`) and typed-slot (`{0:int}`, `{1:int}`) forms are accepted. Two guard sites in `HandleBindWidgetToAttribute`: (1) immediately after `ParseFormat` (catches the user-supplied `format=format_string:NoSlots` case); (2) after `ValidateWidgetProperty` (catches `format=auto` auto-promoted to `FormatString` for Text widgets with `max_attribute` bound but no template — distinct error message instructing the caller to pass an explicit `format_string: