An open API service indexing awesome lists of open source software.

https://github.com/undergroundrap/uefn-toolbelt

The ultimate, ever-expanding Swiss Army Knife for the UEFN Python API (287+ tools registered across 43+ categories). Automate world-building, manage assets, generate boilerplate Verse code, and control the editor with AI via a fully-offline PySide6 dashboard.
https://github.com/undergroundrap/uefn-toolbelt

3d-world-mapping api automation editor-extension fortnite-creative framework game-development gui mcp-server plugin procedural-generation pyside6 python python-editor-scripting toolkit tools uefn unreal-engine-5 unreal-engine-fortnite verse

Last synced: about 2 months ago
JSON representation

The ultimate, ever-expanding Swiss Army Knife for the UEFN Python API (287+ tools registered across 43+ categories). Automate world-building, manage assets, generate boilerplate Verse code, and control the editor with AI via a fully-offline PySide6 dashboard.

Awesome Lists containing this project

README

          

# UEFN TOOLBELT
**355 Professional Tools for UEFN Python Integration.**

> Built by **Ocean Bennett** — 2026

[![CI](https://github.com/undergroundrap/UEFN-TOOLBELT/actions/workflows/ci.yml/badge.svg)](https://github.com/undergroundrap/UEFN-TOOLBELT/actions/workflows/ci.yml)
[![License: AGPL-3.0](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](LICENSE)
[![Version](https://img.shields.io/badge/version-2.2.1-green.svg)](docs/CHANGELOG.md)
[![Discussions](https://img.shields.io/badge/community-discussions-blueviolet)](https://github.com/undergroundrap/UEFN-TOOLBELT/discussions)

![UEFN Toolbelt Dashboard](docs/dashboard_hero.png)

---

> **Historic Discovery — March 22, 2026**
>
> On this date, **Ocean Bennett** became the first person to programmatically catalogue
> the complete Fortnite Creative device palette using UEFN Python's Asset Registry —
> **4,698 Creative device Blueprints across 35 categories**, extracted from 24,926 total
> Blueprint assets in the sandboxed UEFN environment.
>
> Prior to this, no public tool, script, or documentation existed that mapped the full
> set of placeable Creative devices accessible from Python. Epic's own documentation
> covers individual devices. This is the first machine-readable index of the entire palette.
>
> The scan also uncovered a critical silent failure pattern in UEFN 40.00's Asset Registry
> API: deprecated `AssetData` properties (`object_path`, `asset_class`) silently throw
> inside a `try/except` block, causing every asset to be skipped with no error message —
> a bug that would defeat any developer who didn't know to split the exception handling.
>
> The full technical breakdown, the category table, and the discovery story are in
> **[docs/AI_AUTONOMY.md](docs/AI_AUTONOMY.md)**.

---

Automate the tedious, script the impossible, and bridge the gap between Python and Verse.
UEFN Toolbelt is a master utility designed to leverage the **2026 UEFN Python 3.11 Update**,
allowing creators to manipulate actors, manage assets, and generate boilerplate Verse code
through a high-level, developer-friendly interface — all from a single persistent menu entry
in the UEFN editor bar. **355 registered tools** across 55 categories, complete AI-agent
readiness (100% structured dict returns), and a unified theme system so every window in the
platform looks and feels identical.

---

## The Demo — New Project Setup in Under a Minute

> Proven live. Scaffold + Verse game manager + 493-actor arena + reusable room stamps. No manual steps except one Build click.

```python
# Command 1 — professional folder structure + Verse game manager deployed
tb.run("project_setup", project_name="MyGame")
# → 56 folders created in Content Browser
# → MyGameManager.verse generated and deployed to project

# Command 2 — symmetrical Red vs Blue arena spawns in the viewport
tb.run("arena_generate", size="medium")
# → 493 actors placed (6 Red spawns, 6 Blue spawns)

# Command 3 — save a loot room you built as a reusable stamp
tb.run("stamp_save", name="loot_room")
# → saved 12 actors to Saved/UEFN_Toolbelt/stamps/loot_room.json

# Navigate camera to a new location, then stamp it in
tb.run("stamp_place", name="loot_room", yaw_offset=90)
# → 12 FortStaticMeshActors placed at camera, rotated 90°

# One click — Verse menu → Build Verse Code

# Command 4 — error loop closes automatically
tb.run("verse_patch_errors")
# → build_status: SUCCESS
```

What used to take 30+ minutes of manual setup runs in seconds. Scaffold, code, layout,
stamp your best rooms, build — ready to publish.

---

## 🤖 Claude Can Build Your Game — Autonomously

> **This is the headline feature.** Not "AI helps you code" — AI *reads your live level, writes the Verse, and deploys it*. Zero copy-paste. Zero manual wiring.

Here is the complete autonomy loop running live on a real project with 521 actors:

**Step 1a — Claude reads the entire level:**
```python
tb.run("world_state_export")
# → Captured 521 actors. Saved to docs/world_state.json
```

**Step 1b — Claude reads every device available in Fortnite (not just what's placed):**
```python
tb.run("device_catalog_scan")
# → Scanned 24,926 Blueprint assets across all Fortnite packages
# → 4,698 Creative devices identified across 35 categories
# → (Timer ×8, Capture ×7, Score ×28, Spawner ×94, Camera ×446, NPC ×173, ...)
# → docs/device_catalog.json — Claude's complete device palette, git-tracked
```

**Step 2 — Claude identifies all Creative devices and generates a wired game skeleton:**

Claude reads `world_state.json`, finds every device in the level (`FortCreativeTimerDevice`,
`capture_area_device`, `guard_spawner_device`, `button_device`, 14 teleporters, etc.),
and writes a complete `creative_device` class — `@editable` declarations, `OnBegin` event
wiring, round flow, creature waves, capture logic, and clean shutdown — all referencing the
**actual device labels from your level**.

**Step 3 — Claude deploys it directly to the project:**
```python
tb.run("verse_write_file", filename="device_api_game_manager.verse", content=verse_code)
# → Written: Device_API_Mapping\Verse\device_api_game_manager.verse (6187 bytes)
```

**Step 4 — Build Verse. First try.**
```
VerseBuild: SUCCESS -- Build complete.
```

No type errors. No manual editing. A fully wired, compilable Verse game manager — generated
from live level state in under 60 seconds.

**What the generated skeleton contains:**
- `@editable` refs for every device discovered: Timer, Timed Objective, Round Settings, Capture Area, 2× Item Spawners, Creature Spawner, Creature Manager, Creature Placer, 2× Buttons, 2× Conditional Buttons, Lock Device, 5 keyed Teleporters, Audio Mixer, Weapon Mod Bench
- `OnBegin` that starts the timer, spawns async watchers, and subscribes to all button/timer events
- `WatchCaptureArea()` — async loop: team captures zone → spawn reward items → trigger creature wave
- `WatchCreatureWaves()` — async loop: enable creature manager → 30s cooldown → repeat
- `OnButtonPressed` / `OnButton2Pressed` — unlock doors, activate conditional gates
- `EndRound()` — stops timer, disables creature manager, despawns guards, stops audio

Place the device in your level, drag your real actors into the `@editable` slots, and play.

> See [docs/PIPELINE.md](docs/PIPELINE.md) for the complete 6-phase industrial pipeline —
> every tool, every phase, Claude's full execution script, and the recursive build+fix loop.
> See [docs/AI_AUTONOMY.md](docs/AI_AUTONOMY.md) for the live run technical breakdown.

---

## 🤖 AI-Accelerated Development (One-Click Sync + Tool Manifest)
Toolbelt is built to be used with AI (Claude/Gemini). To give your AI **perfect information** about your project's unique Verse devices and custom props:

1. **Open Dashboard**: Run `tb.launch_qt()` or use the `Toolbelt` menu.
2. **One-Click Sync**: Click the **"Sync Level Schema to AI"** button in **Quick Actions**.
3. **Instant Content**: The 1.6MB schema is automatically copied to your `docs/` folder. Your AI now knows every hidden property in your specific level.
4. **Export Tool Manifest**: Run `tb.run("plugin_export_manifest")` to write `Saved/UEFN_Toolbelt/tool_manifest.json` — a machine-readable index of all 358 tools with their parameter signatures, types, defaults, and categories. An AI agent can load this file and immediately know what every tool does and how to call it, without reading source code.

---

## Table of Contents

- [Claude Can Build Your Game — Autonomously](#-claude-can-build-your-game--autonomously)
- [Concept](#concept)
- [The Workflow Loop — Efficiency Mechanic](#the-workflow-loop--efficiency-mechanic)
- [Tech Stack](#tech-stack)
- [Architecture Overview](#architecture-overview)
- [Directory Structure](#directory-structure)
- [Key Systems — How They Work](#key-systems--how-they-work)
- [Math & Performance Scaling](#math--performance-scaling)
- [Tool Reference](#tool-reference)
- [Getting Started](#getting-started)
- [Adding a New Tool](#adding-a-new-tool)
- [Custom Plugins & Security](#custom-plugins--security)
- [API Capability Crawler](#api-capability-crawler)
- [Fortnite Device API Mapping](#fortnite-device-api-mapping)
- [MCP — Connect Any AI to UEFN](#mcp--connect-any-ai-to-uefn)
- [Spec-Accurate Verse Code Generation](#spec-accurate-verse-code-generation)
- [CLAUDE.md — Instant AI Context](#claudemd--instant-ai-context)
- [Why This Is the Best UEFN Python Tool](#why-this-is-the-best-uefn-python-tool)
- [Documentation](#documentation)
- [Quick-Reference Command Table](#quick-reference-command-table)
- [Patch Notes](#patch-notes)
- [Contributing](#contributing)
- [Tool Requests](#tool-requests)
- [License](#license)

---

## Security

> **Why this matters:** The UEFN Python community has already seen proof-of-concept malicious
> scripts that flood the editor with spam windows when run from untrusted sources.
> Python editor scripts run with full access to your project — treat them like executable code.

### This repo's safety guarantees

| Practice | How it's enforced here |
|---|---|
| **Audited code** | Every line in this repo is open-source and reviewable |
| **No network calls** | Zero outbound HTTP/socket connections anywhere in the codebase |
| **No file writes outside project** | All output goes to `Saved/UEFN_Toolbelt/` inside your project |
| **No eval/exec on external input** | No dynamic code execution from files, URLs, or user strings |
| **Undo-safe** | Every destructive operation is wrapped in `ScopedEditorTransaction` |

### How to verify before running any `.py` file (from this repo or anyone's)

```python
# Before exec()-ing any script, read it first:
with open("path/to/script.py") as f:
print(f.read())

# Red flags to look for:
# - import requests / import http / import socket
# - exec() / eval() on anything fetched from outside
# - os.system() / subprocess with network targets
# - anything writing outside your project folder
```

### Running untrusted community scripts safely

1. Always read the script before running it
2. Run unknown scripts in a **throwaway project** first
3. Keep UEFN's Python access set to **Editor Scripting only** (the default)

---

## ⚠️ Automated Integration Testing

The smoke test verifies that all tools *register* correctly. The integration test verifies that they *work* — in a live UEFN editor, against real actors.

### What the integration test actually does

`toolbelt_integration_test` is a fixture-driven test harness that runs inside UEFN:

1. **Spawns** temporary cube/sphere actors programmatically
2. **Selects** them via `EditorActorSubsystem`
3. **Runs** each tool against that live selection
4. **Verifies** the result — property changed, actor spawned, file written, count correct
5. **Cleans up** every actor it touched via a single undo transaction

**163 tests across 358 tools** — covering materials, bulk ops, patterns, scatter, splines, snapshots, asset management, Verse tools, screenshots, LODs, arena, measurement, localization, zones, stamps, actor org, proximity placement, advanced alignment, signs, post-process, audio, level health, config, lighting, and world state.

This is the closest thing to a full CI suite possible inside the UEFN Python sandbox. If this passes, you have high confidence that the core tool logic is sound — not just that it imported.

### How to run it

```python
# Use a clean template level — never run in a production project
import UEFN_Toolbelt as tb; tb.register_all_tools(); tb.run("toolbelt_integration_test")
```

Results are written to:
```
Saved/UEFN_Toolbelt/integration_test_results.txt
```

If the editor crashes mid-run (rare), the file will contain partial results up to the last completed test — useful for pinpointing which section caused the crash.

> [!WARNING]
> **DO NOT run the full integration test in a live production project.**
> It spawns, modifies, and deletes actors. Use a blank **Empty Level** or throwaway test project.
> Every test section cleans up after itself, but the undo history will be long.

### Smoke test vs integration test

| | Smoke Test | Integration Test |
|---|---|---|
| Runs outside UEFN? | No (needs editor) | No (needs editor) |
| Tests all 358 tools? | Registry only | Live execution |
| Requires level actors? | No | Yes (spawns its own) |
| Safe in production? | Yes | **No — use blank level** |
| Runtime | ~5 seconds | ~35 seconds |
| Command | `tb.run("toolbelt_smoke_test")` | `tb.run("toolbelt_integration_test")` |

Run the smoke test after every change. Run the integration test before submitting a PR.

---

> **Community context:** Built in response to the March 2026 UEFN Python wave. Inspired by
> early community tools like standalone material editors, spline prop placers,
> and Verse device editors — then taken further into a unified, permanently
> docked creator toolkit.

---

## Concept

UEFN Toolbelt isn't just a library — it's a **meta-framework**. While the standard UEFN
Python API provides the *what*, Toolbelt provides the *how*. It follows a
**Blueprint-to-Script philosophy**: taking complex manual editor tasks (mass material
assignment, procedural arena generation, bulk Verse device editing) and reducing them to
single-line Python commands callable from a top-menu dropdown.

The toolbelt is designed for three creator personas:

| Persona | Problem Solved |
|---|---|
| **The Automator** | Needs to spawn and configure hundreds of actors across a large map in seconds |
| **The Integrator** | Building bridges between Python data and Verse-driven gameplay systems |
| **The Janitor** | Mass-cleaning naming violations, missing materials, misaligned props |

---

## 🏗️ Solving Soul-Crushing Repetition (The 8 Pillars)

The UEFN Toolbelt exists because manual work doesn't scale, but scripts do. We solve 90% of the repetitive tasks reported by the UEFN community:

1. **Manual Placement & Spacing**: Procedural spawners, align/distribute scripts, and spline-based distribution.
2. **Asset Organization**: Bulk import pipelines, smart renaming, and auto-folder scaffolding.
3. **Material Batch Edits**: Parameter randomization, team-color splitters, and bulk texture swaps.
4. **Structural Elements**: High-performance cable, wire, fence, and rail generators.
5. **Layout Math**: Instant 3D distance and travel-time (Walk/Run/Sprint) estimation.
6. **Bulk Optimization**: Automated LOD generation, memory audits, and cooking pre-flight checks.
7. **Verse Boilerplate**: Selection-to-code generation and spec-accurate device stubs.
8. **Component Spam**: Scripted "Quick-Add" macros for entity sets and Niagara effects.
9. **AI-Agent Readiness**: Every tool returns a structured `dict` — AI agents operate on results, not logs. The MCP bridge, `tool_manifest.json`, and `describe_tool` command let any LLM discover and call every tool autonomously. This is the first UEFN toolkit designed to be equally usable by humans and AI agents.

---

## AI Return Contract — 100% Machine-Readable

Every single one of the 358 tools returns a clean, structured `dict`. No `None` returns. No bare primitives. No output that requires parsing log lines. This was completed as Phase 21 and applies to every tool added since.

### What Claude gets back

```python
# Asset scan
{"status": "ok", "count": 4, "textures": [
{"name": "T_Rock", "path": "/Device_API_Mapping/Textures/T_Rock",
"compression": "TC_DEFAULT", "srgb": "true", "size_x": "2048", "size_y": "2048"}
]}

# Audit
{"status": "ok", "total": 12, "clean": 10, "issues": 2, "issue_list": [
{"name": "SK_Hero", "path": "/Device_API_Mapping/Characters/SK_Hero",
"issues": ["No physics asset assigned"]}
]}

# Write op (dry_run)
{"status": "ok", "dry_run": true, "changed": 8, "skipped": 3, "changes": [
{"name": "T_Wall", "from": "TC_DEFAULT", "to": "TC_NORMALMAP"}
]}

# Error — always catchable
{"status": "error", "message": "Could not load asset at '/Game/Missing'."}
```

### The full MCP chain

```
Claude Code → MCP server → run_tool → registry.execute() → _serialize(result) → JSON response → Claude reads it
```

Claude never has to parse a log line. Every domain follows the same shape:

| Domain | Keys Claude reads |
|---|---|
| Asset scans | `count` + array with `name/path/type` per asset |
| Audits | `total/clean/issues` + `issue_list` with per-item details |
| Write ops | `dry_run` + `changed/skipped` + `changes` array |
| Level actors | `count` + array with `label/class/location` |
| Inspections | Full property dict for the specific asset |
| Exports | `output_path` + `count` |
| Errors | `{"status": "error", "message": "..."}` — always catchable |

### Tool discovery before first call

`tb.run("plugin_export_manifest")` writes `Saved/UEFN_Toolbelt/tool_manifest.json` — a machine-readable index of all 358 tools with full parameter signatures (name, type, required, default) and a concrete `example` call string for every tool. Claude reads this once and knows exactly what params to pass to any tool without guessing.

---

## The Workflow Loop — Efficiency Mechanic

This is the core philosophy. We solve the **"Iteration Tax"** — the time lost between an
idea and its implementation in the editor.

### The Three Efficiency Scalars

$$T_{\text{save}} = (T_{\text{manual}} \times N) - (T_{\text{script}} + T_{\text{execution}})$$

| Scalar | Value | Description |
|---|---|---|
| **Batch Multiplier** | $1.5^N$ | Time saved grows super-linearly as task count N rises |
| **Boilerplate Reduction** | $\approx 0.8 \times \text{Lines}$ | Toolbelt reduces raw UE Python verbosity by ~80% |
| **Net Speed Gain** | ~300% | Measured on world-building tasks vs. manual editor clicks |

---

## Tech Stack

| Layer | Technology | Purpose |
|---|---|---|
| **Core** | Python 3.11 | Native language of the UEFN experimental integration |
| **API Wrapper** | `unreal` module | Direct hooks into `EditorActorSubsystem`, `EditorAssetLibrary`, `MaterialEditingLibrary` |
| **Undo Safety** | `unreal.ScopedEditorTransaction` | Every destructive operation is wrapped — one Ctrl+Z removes anything |
| **UI** | Editor Utility Widgets (UMG) | Dockable dashboard with tabs; buttons fire `Execute Python Command` nodes |
| **Verse Bridge** | `verse_snippet_generator.py` | Generates `.verse` files from Python context (selected actors, level state) |
| **Data** | JSON | Custom preset storage and import logs in `Saved/UEFN_Toolbelt/` |
| **Menu** | `unreal.ToolMenus` | Permanent top-bar menu entry injected on editor startup |

---

## Architecture Overview

```
UEFN Editor

│ init_unreal.py ← auto-executed by UEFN on every startup
│ (generic submodule loader — NOT part of the Toolbelt package)
│ If you already have one, add the package-discovery pattern
│ from the provided template instead of overwriting yours.

UEFN TOOLBELT ─── UEFN_Toolbelt/__init__.py (register, launch, run, registry accessor)

├── core.py Shared utilities: undo_transaction, get_selected_actors,
│ with_progress, color_from_hex, spawn_static_mesh_actor, …

├── registry.py ToolRegistry singleton — @register_tool decorator,
│ execute-by-name, category listing, tag search,
│ to_manifest() for full parameter-introspected export

└── tools/
├── material_master.py 17 presets, gradient, harmony, team split, save/load
├── arena_generator.py Symmetrical Red/Blue arenas (S/M/L)
├── spline_prop_placer.py Props along splines — count or distance mode
├── bulk_operations.py Align, distribute, randomize, snap, mirror, stack
├── verse_device_editor.py List, filter, bulk-edit, export Verse devices
├── smart_importer.py FBX batch import + auto-material + Content Browser organizer
├── verse_snippet_generator.py Context-aware Verse boilerplate from level selection
├── text_painter.py Colored 3D text actors with saved style presets
├── asset_renamer.py Epic naming convention enforcer with dry-run + audit
├── project_scaffold.py Professional folder structure generator (4 templates)
├── verse_schema.py Verse Digest IQ & Universal Schema Search (143th Tool)
├── system_build.py Automated UEFN Build Scraper & Error Monitor
├── system_perf.py Background CPU Optimizer & Monitor
├── measurement_tools.py Distance Calculator & Travel Time Estimator (Phase 15)
└── localization_tools.py Multi-language Text Export/Import (Phase 15)
```

All heavy logic lives in Python. The optional UMG dashboard calls into it via
`Execute Python Command` Blueprint nodes — zero coupling, infinitely extensible.

---

## Directory Structure

```
[YourUEFNProject]/
├── Content/
│ ├── Python/
│ │ ├── init_unreal.py ← COPY HERE if you don't have one yet
│ │ │ (generic loader — not Toolbelt-specific)
│ │ │ ⚠️ If you already have this file, do NOT
│ │ │ overwrite it. Add the package-discovery
│ │ │ loop from the template into your existing file.
│ │ └── UEFN_Toolbelt/ ← COPY HERE — the full package
│ │ ├── __init__.py
│ │ ├── core.py
│ │ ├── registry.py
│ │ └── tools/
│ │ ├── __init__.py
│ │ ├── material_master.py
│ │ ├── arena_generator.py
│ │ ├── spline_prop_placer.py
│ │ ├── bulk_operations.py
│ │ ├── verse_device_editor.py
│ │ ├── asset_importer.py # NEW: URL & Clipboard image importing
│ │ ├── verse_snippet_generator.py
│ │ ├── text_painter.py
│ │ ├── asset_renamer.py
│ │ ├── procedural_geometry.py # NEW: Wire & Volumetric generators
│ │ ├── text_voxelizer.py # NEW: 3D Text geometry generation
│ │ ├── smart_organizer.py # Proprietary Heuristics Engine
│ │ ├── system_perf.py # Background CPU Optimizer
│ │ ├── verse_schema.py # NEW: Verse Digest IQ (Phase 14)
│ │ ├── system_build.py # NEW: Automated Build Monitor (Phase 14)
│ │ ├── measurement_tools.py # NEW: Distance & Travel Time (Phase 15)
│ │ └── localization_tools.py # NEW: Text Export & Translation (Phase 15)
│ └── UEFN_Toolbelt/
│ ├── Blueprints/
│ │ └── WBP_ToolbeltDashboard ← optional EUW — create in UEFN
│ └── Materials/
│ └── M_ToolbeltBase ← required for Material Master
├── deploy.bat ← double-click to deploy to any UEFN project
└── README.md
```

> **Critical path:** Files must live under `Content/Python/` exactly.
> Any other location (e.g. `Content/Scripts/`) is **not** scanned by UEFN's Python interpreter.

---

## Key Systems — How They Work

### 1. The Tool Registry

Every tool self-registers with a one-line decorator. The registry is a singleton shared
across the entire session.

```python
from UEFN_Toolbelt.registry import register_tool

@register_tool(
name="my_tool",
category="Procedural",
description="Does something amazing",
tags=["procedural", "spawn"],
)
def run(**kwargs) -> dict:
...
return {"status": "ok", "count": placed}
```

Calling `tb.run("my_tool")` from anywhere — REPL, Blueprint node, another tool — routes
through the registry with full error containment. A crashing tool never kills your session.

Every tool returns a structured `dict` with at minimum a `"status"` key. This is the
**MCP Return Contract**: AI agents calling tools via the bridge read the return dict directly
from the JSON response. Zero log parsing required.

---

### 2. Material Master

17 built-in presets stored as pure data dictionaries. All material work happens through
`MaterialEditingLibrary` — no Blueprint graph required.

```python
tb.run("material_apply_preset", preset="chrome") # apply to selection
tb.run("material_gradient_painter", # world-space gradient
color_a="#0044FF", color_b="#FF2200", axis="X")
tb.run("material_team_color_split") # auto Red / Blue by X position
tb.run("material_save_preset", preset_name="MyLook") # save to JSON
```

Custom presets persist across sessions in `Saved/UEFN_Toolbelt/custom_presets.json`.

---

### 3. Arena Generator

Instant symmetrical competitive arenas. What used to take 45 minutes of manual prop
placement now runs in under 5 seconds.

```python
tb.run("arena_generate", size="large", apply_team_colors=True)
```

Internally places floor tiles, perimeter walls, a center platform, and team spawn pads —
all in a single `ScopedEditorTransaction`. One Ctrl+Z removes the entire arena.

---

### 4. The Verse Bridge

The biggest pain point in UEFN is syncing editor state with Verse code. The snippet
generator reads your **actual level selection** and produces strongly-typed output:

```python
# Select 6 devices in the viewport, then:
tb.run("verse_gen_device_declarations")
```

Output (written to `Saved/UEFN_Toolbelt/snippets/` and copied to clipboard):

```verse
# Actor: 'TriggerDevice_01' @ (1200, 400, 0)
@editable triggerdevice_01 : trigger_device = trigger_device{}

# Actor: 'SpawnPad_Red_01' @ (3200, 0, 0)
@editable spawnpad_red_01 : player_spawner_device = player_spawner_device{}
```

---

### 5. Undo Safety

Every destructive operation is wrapped in `core.undo_transaction()`:

```python
# From core.py — used by every tool:
with undo_transaction("Material Master: Apply chrome"):
for actor in actors:
_apply_preset_to_actor(actor, preset_data, "chrome")
```

If something goes wrong mid-operation the transaction closes cleanly. No corrupted
undo stack, no phantom changes.

---

### 6. Project Scaffold

The first tool you run on any new UEFN project. Four opinionated templates — pick
the one that matches your workflow:

```python
# See what it would create before touching anything
tb.run("scaffold_preview", template="uefn_standard", project_name="MyIsland")

# One command builds 50+ folders in the right hierarchy
tb.run("scaffold_generate", template="uefn_standard", project_name="MyIsland")
```

Templates are stored as plain JSON in `Saved/UEFN_Toolbelt/scaffold_templates.json`
— email or Git the file to every teammate so everyone gets the same structure instantly.

Custom templates are one call:

```python
tb.run("scaffold_save_template",
template_name="StudioDefault",
folders=["Maps/Main", "Materials/Master", "Materials/Instances",
"Meshes/Props", "Verse/Modules", "Audio/SFX"])
```

---

### 7. Smart Importer

Drop a folder of FBXs and get back a fully organized Content Browser:

```python
tb.run("import_fbx_folder",
folder_path="C:/MyAssets/Props/",
apply_material=True,
place_in_level=False)
```

Assets land in `/Game/Imported/[date]/Meshes/` with auto-generated material instances.
An import log is appended to `Saved/UEFN_Toolbelt/import_log.json` after every run.

---

### 8. Advanced Project Intelligence (Phase 14)

The Toolbelt now features a native "Off-Engine IQ" that understands your Verse code without needing a live actor in the level.

- **Verse Schema IQ**: Parses your `.digest.verse` files to build a mapping of all Verse classes, properties, and events.
- **Build Monitor**: Triggers a background UEFN build and scrapes the log for Verse compilation errors with file/line precision.
- **Global Safety Gate**: A centralized protection layer (`core.safety_gate`) that all "Write" operations must pass through. It prevents accidental modification of Epic/Fortnite core assets.

---

### System & Safety (`category="System"`)

Advanced project diagnostics and protection layers.

| Tool Name | Description |
|---|---|
| `api_verse_get_schema` | Returns the Verse schema (props/events) for any class via digest parsing |
| `api_verse_refresh_schemas` | Forces a re-scan of all .digest.verse files |
| `system_build_verse` | Triggers a background UEFN build and scrapes for Verse errors |
| `system_get_last_build_log` | Reads the most recent UEFN log file for error diagnostics |

---

## Math & Performance Scaling

To prevent the editor from hanging during mass operations, Toolbelt uses
`unreal.ScopedSlowTask` — a native UE progress dialog with cancel support.

### Complexity Formula

For an operation across $A$ actors with $M$ material slots each:

$$\text{Complexity} = O(A + (A \times M))$$

### Chunked Execution Pattern

```python
# From core.py — wrap any iterable:
with with_progress(actors, "Applying materials") as bar_iter:
for actor in bar_iter:
_apply_preset_to_actor(actor, preset, "Chrome")
# ScopedSlowTask advances one step per actor
# User can cancel mid-way — no partial corrupt state
```

The progress bar displays a cancel button. Cancellation is handled gracefully; already-
processed actors retain their changes (inside the transaction, so all-or-nothing undo
still works at the outer level).

---

## Tool Reference

> **358 tools · 55 categories** — all return `{"status": "ok"/"error", ...}` structured dicts.
> Run any tool with `tb.run("tool_name", param=value)`.
> AI agents: use `tb.run("plugin_export_manifest")` to get the full machine-readable manifest.

**Quick navigation:**
[Actor Organization](#actor-organization-10) ·
[Alignment](#alignment-6) ·
[API Explorer](#api-explorer-10) ·
[Asset Management](#asset-management-5) ·
[Asset Tagger](#asset-tagger-6) ·
[Assets](#assets-11) ·
[Audio](#audio-4) ·
[Bulk Ops](#bulk-ops-9) ·
[Entities](#entities-2) ·
[Environmental](#environmental-2) ·
[Generative](#generative-2) ·
[Level Snapshot](#level-snapshot-8) ·
[Lighting](#lighting-6) ·
[Localization](#localization-2) ·
[Materials](#materials-10) ·
[MCP Bridge](#mcp-bridge-4) ·
[Measurement](#measurement-3) ·
[Optimization](#optimization-8) ·
[Pipeline](#pipeline-2) ·
[Post-Process](#post-process-4) ·
[Procedural](#procedural-12) ·
[Project](#project-7) ·
[Project Admin](#project-admin-3) ·
[Prop Patterns](#prop-patterns-9) ·
[Proximity Tools](#proximity-tools-6) ·
[Reference Auditor](#reference-auditor-7) ·
[Screenshot](#screenshot-4) ·
[Selection](#selection-3) ·
[Sequencer](#sequencer-2) ·
[Simulation](#simulation-2) ·
[Stamps](#stamps-5) ·
[System](#system-4) ·
[Tests](#tests-1) ·
[Text & Signs](#text--signs-14) ·
[Utilities](#utilities-12) ·
[Verse Helpers](#verse-helpers-26) ·
[Zone Tools](#zone-tools-7)

---

### Actor Organization (10)

| Tool | Description |
|---|---|
| `actor_attach_to_parent` | Last selected actor becomes parent of all others (Maya-style). Preserves world transforms. |
| `actor_detach` | Detach all selected actors from their parent. Preserves world transforms. |
| `actor_folder_list` | List all World Outliner folders with actor counts. |
| `actor_match_transform` | Copy the first selected actor's transform to all others. Toggle location/rotation/scale independently. |
| `actor_move_to_folder` | Move all selected actors into a named World Outliner folder (auto-created). |
| `actor_move_to_root` | Move all selected actors out of any folder to the root level. |
| `actor_rename_folder` | Rename a World Outliner folder — re-paths all actors inside it. Supports dry_run. |
| `actor_select_by_class` | Select all level actors whose class name contains a filter string. |
| `actor_select_by_folder` | Select all actors in a named World Outliner folder. |
| `actor_select_same_folder` | Expand selection to all actors sharing the first actor's folder. |

---

### Alignment (6)

| Tool | Description |
|---|---|
| `align_to_reference` | Align all selected actors' X, Y, or Z to the first or last actor's position. |
| `align_to_surface` | Snap selected actors' Z down to the nearest floor surface (uses line traces). |
| `align_to_grid_two_points` | Define a local XY grid from two anchor actors, then snap everything else to it. |
| `distribute_with_gap` | Set an exact cm gap between bounding boxes along an axis (not pivot-to-pivot). |
| `match_spacing` | Evenly space pivots between first and last actor — endpoints stay fixed. |
| `rotate_around_pivot` | Orbit selection around center-of-bounds or first actor by angle_deg on any axis. |

---

### API Explorer (10)

| Tool | Description |
|---|---|
| `api_search` | Fuzzy-search class/function names across the live `unreal` module. |
| `api_inspect` | Print full signature + docstring for any `unreal.*` class or function. |
| `api_list_subsystems` | Print every `*Subsystem` class available in this UEFN build. |
| `api_generate_stubs` | Write `.pyi` stub for one class (or all if name omitted). |
| `api_export_full` | Export a monolithic `unreal.pyi` stub for full IDE autocomplete. |
| `api_crawl_selection` | Deep-introspect selected actors, components, and properties → JSON. |
| `api_crawl_level_classes` | Headless map of all unique classes + exposed properties in the current level. |
| `api_sync_master` | One-click: level crawl + Verse schema merge → updates `docs/DEVICE_API_MAP.md`. |
| `device_catalog_scan` | Scan the Asset Registry for every Creative/Verse device available in Fortnite. |
| `world_state_export` | Export full live state of every actor (transforms + device properties) → `world_state.json`. |

---

### Asset Management (5)

| Tool | Description |
|---|---|
| `prefab_migrate_open` | Open the Prefab Asset Migrator — paste Ctrl+C T3D text to extract + copy all asset dependencies to another project. |
| `prefab_parse_refs` | Headless: parse a T3D prefab string and return all `/Game/` asset paths found. |
| `prefab_resolve_deps` | Headless: walk the AssetRegistry dependency graph for a list of package paths. |
| `prefab_export_to_disk` | Headless: copy resolved `.uasset` files from Content folder to a destination directory. |
| `ui_icon_import_open` | Open the UI Icon Importer — paste any image from clipboard (browser, Figma, Photoshop) to import as a UEFN Texture2D. |

---

### Asset Tagger (6)

All tags use the `TB:` namespace prefix to avoid collisions.

| Tool | Description |
|---|---|
| `tag_add` | Add a searchable metadata tag to all selected Content Browser assets. |
| `tag_remove` | Remove a tag from all selected Content Browser assets. |
| `tag_show` | Print all Toolbelt tags on selected assets. |
| `tag_search` | Find assets by tag key/value across a folder. |
| `tag_list_all` | List every unique tag key used under a folder with asset counts. |
| `tag_export` | Export full tag → asset index to `Saved/UEFN_Toolbelt/tag_export.json`. |

---

### Assets (11)

| Tool | Description |
|---|---|
| `rename_dry_run` | Preview naming convention violations without changing anything. |
| `rename_enforce_conventions` | Rename assets to follow Epic's prefix conventions (`SM_`, `T_`, `M_`, etc.). |
| `rename_strip_prefix` | Strip a specific prefix from all assets in a folder. |
| `rename_report` | Export a full naming audit JSON for a folder. |
| `organize_assets` | Sort assets into typed sub-folders by asset class. |
| `import_fbx` | Import a single FBX file into the Content Browser. |
| `import_fbx_folder` | Batch-import all FBX files in a folder. |
| `lod_audit_folder` | Report which static meshes in a folder have no LODs or collision. |
| `lod_auto_generate_selection` | Auto-generate LODs on selected actors' meshes. ⚠️ Disabled in UEFN (Quirk #18). |
| `lod_auto_generate_folder` | Auto-generate LODs on all meshes in a folder. ⚠️ Disabled in UEFN (Quirk #18). |
| `lod_set_collision_folder` | Batch-set collision complexity on all static meshes in a folder. |

---

### Audio (4)

| Tool | Description |
|---|---|
| `audio_place` | Spawn an AmbientSound actor at the camera position. Optionally assign a sound asset. |
| `audio_set_volume` | Set volume multiplier on all selected AmbientSound actors. |
| `audio_set_radius` | Override attenuation falloff radius on selected AmbientSound actors. |
| `audio_list` | List all AmbientSound actors in the level with label, location, and folder. |

---

### Bulk Ops (9)

| Tool | Description |
|---|---|
| `bulk_align` | Align all selected actors to the first actor's position on one axis. |
| `bulk_distribute` | Space selected actors evenly between the two extremes along an axis. |
| `bulk_randomize` | Apply random location / rotation / scale offsets to selected actors. |
| `bulk_snap_to_grid` | Snap all selected actors' locations to a world grid. |
| `bulk_reset` | Reset rotation to (0,0,0) and scale to (1,1,1) on all selected actors. |
| `bulk_mirror` | Mirror selected actors across an axis (reflects position about center). |
| `bulk_normalize_scale` | Set all selected actors to a uniform target scale. |
| `bulk_stack` | Stack selected actors vertically (Z) on top of each other. |
| `bulk_face_camera` | Rotate all selected actors to face the editor viewport camera (yaw only). |

---

### Entities (2)

| Tool | Description |
|---|---|
| `entity_list_kits` | List all available Standard Kits for the quick-spawn tool. |
| `entity_spawn_kit` | Spawn a pre-configured Standard Kit of Creative devices in one click. |

---

### Environmental (2)

| Tool | Description |
|---|---|
| `foliage_convert_selected_to_actor` | Convert selected StaticMeshActors into Foliage Actors. |
| `foliage_audit_brushes` | Audit all foliage brushes and return the mesh paths they use. |

---

### Generative (2)

| Tool | Description |
|---|---|
| `text_render_texture` | Render a text string into a transparent Texture2D asset natively in-editor. |
| `text_voxelize_3d` | Voxelize a text string into a 3D StaticMesh using procedural cubes. |

---

### Level Snapshot (8)

| Tool | Description |
|---|---|
| `snapshot_save` | Save a named JSON snapshot of actor transforms in the current level. |
| `snapshot_restore` | Restore actor transforms from a named snapshot (fully undoable). |
| `snapshot_list` | List all saved snapshots with actor count and timestamp. |
| `snapshot_diff` | Compare two snapshots — see what actors were added, removed, or moved. |
| `snapshot_compare_live` | Compare a saved snapshot against the current live level state. |
| `snapshot_export` | Copy a snapshot JSON to a custom path for sharing. |
| `snapshot_import` | Import a snapshot JSON from an external path. |
| `snapshot_delete` | Delete a saved snapshot by name. |

```python
tb.run("snapshot_save", name="before_scatter")
# ... make changes ...
tb.run("snapshot_compare_live", name="before_scatter")
tb.run("snapshot_restore", name="before_scatter")
```

---

### Lighting (6)

| Tool | Description |
|---|---|
| `light_place` | Spawn a light at camera position. `light_type`: point/spot/rect/directional/sky. |
| `light_set` | Batch-set intensity, color, and attenuation on selected lights. Only provided params change. |
| `light_list` | List all light actors with type, label, and location. |
| `sky_set_time` | Simulate time-of-day by pitching the DirectionalLight. `hour`: 0–24. |
| `light_cinematic_preset` | Apply mood preset to directional light + fog. Presets: Star Wars, Cyberpunk, Vibrant. |
| `light_randomize_sky` | Randomise sun pitch/yaw for quick look-dev iterations. |

---

### Localization (2)

| Tool | Description |
|---|---|
| `text_export_manifest` | Export all level text (TextRenderActors + devices) to CSV or JSON for translation. |
| `text_apply_translation` | Read a translated manifest and update level actors with new strings. |

---

### Materials (10)

**17 built-in presets:** `chrome` · `gold` · `neon` · `hologram` · `lava` · `ice` · `concrete` · `wood_oak` · `metal_rust` · `glass` · `team_red` · `team_blue` · `team_green` · `team_yellow` · `glow_pulse` · `scanlines` · `iridescent`

| Tool | Description |
|---|---|
| `material_list_presets` | Print all presets (17 built-in + custom). |
| `material_apply_preset` | Apply a named preset to all selected actors. |
| `material_randomize_colors` | Random HSV color on each selected actor. |
| `material_gradient_painter` | World-space color gradient across selection (`axis`, `color_a`, `color_b`). |
| `material_team_color_split` | Auto Red/Blue split by world X midpoint. |
| `material_pattern_painter` | Checkerboard or stripe alternation across selection. |
| `material_glow_pulse_preview` | Set emissive peak intensity for viewport preview. |
| `material_color_harmony` | Apply complementary / triadic / analogous palette to selection. |
| `material_save_preset` | Save current material params as a named reusable preset. |
| `material_bulk_swap` | Replace one material slot with another across selected actors or the full level. |

---

### MCP Bridge (4)

Start the listener in UEFN, then Claude Code (or any MCP client) can run all 358 tools by name, spawn actors, execute arbitrary Python, and more.

| Tool | Description |
|---|---|
| `mcp_start` | Start the HTTP listener so Claude Code can control UEFN directly. |
| `mcp_stop` | Stop the listener. |
| `mcp_restart` | Restart after hot-reload or port conflict. |
| `mcp_status` | Print port, running state, and command count. |

```python
tb.run("mcp_start")
# Output Log: [MCP] ✓ Listener running on http://127.0.0.1:8765
```

---

### Measurement (3)

| Tool | Description |
|---|---|
| `measure_distance` | 3D distance between two actors or total chain length of a selection. |
| `measure_travel_time` | Travel time between selected points at Walk/Run/Sprint/Vehicle speeds. |
| `spline_measure` | Precise world-space length of a selected spline actor. |

---

### Optimization (8)

| Tool | Description |
|---|---|
| `rogue_actor_scan` | Find actors with extreme scale, at-origin placement, or unnamed labels. |
| `memory_scan` | Full island memory scan — textures, meshes, sounds → JSON report. |
| `memory_scan_textures` | Audit textures and flag oversized ones (`warn_px`, `crit_px` thresholds). |
| `memory_scan_meshes` | Audit static meshes — polygon count, LODs, collision. |
| `memory_top_offenders` | Print the top N heaviest assets by estimated memory cost. |
| `memory_autofix_lods` | Auto-generate LODs on all meshes missing them in a folder. |
| `convert_to_hism` | Merge selected actors sharing the same mesh into a single HISM actor (one draw call). |
| `material_parent_audit` | Audit material instances — group by parent, flag orphaned MIs. |

---

### Pipeline (2)

| Tool | Description |
|---|---|
| `import_image_from_clipboard` | Capture the current Windows Clipboard image and import it as a Texture2D. |
| `import_image_from_url` | Download an image from a URL and import it into the Content Browser. |

---

### Post-Process (4)

| Tool | Description |
|---|---|
| `postprocess_spawn` | Find or create a global (infinite-extent) PostProcessVolume — no duplicates. |
| `postprocess_set` | Set bloom, exposure, contrast, vignette, saturation. Only provided params change. |
| `postprocess_preset` | Apply a named visual preset. Presets: `cinematic` · `night` · `vibrant` · `bleach` · `horror` · `fantasy` · `reset`. |
| `world_settings_set` | Change gravity (cm/s²) and time dilation world-wide. |

---

### Procedural (12)

| Tool | Description |
|---|---|
| `arena_generate` | Generate a symmetrical Red vs Blue arena. `size`: small/medium/large. |
| `scatter_props` | Scatter StaticMesh props in a radius with Poisson-disk distribution. |
| `scatter_hism` | Dense instanced scatter using HISM — one draw call for thousands of props. |
| `scatter_avoid` | Scatter props but skip positions within `avoid_radius` of matching actors. |
| `scatter_road_edge` | Place props along both shoulders of a path (road edges, forest borders). |
| `scatter_along_path` | Scatter prop clusters along a list of world-space path points. |
| `scatter_export_manifest` | Export positions/rotations of all scatter actors in a folder to JSON. |
| `scatter_clear` | Delete all scatter actors in a named World Outliner folder. |
| `spline_place_props` | Place props along a selected spline actor. |
| `spline_clear_props` | Delete all actors in the SplineProps folder. |
| `procedural_volume_scatter` | Scatter random meshes within a spherical or cubic boundary. |
| `procedural_wire_create` | Draw a procedural sagging wire/cable between exactly two selected actors. |

---

### Project (7)

| Tool | Description |
|---|---|
| `scaffold_list_templates` | List all available scaffold templates (built-in + saved custom). |
| `scaffold_preview` | Preview what a template would create — zero changes made. |
| `scaffold_generate` | Generate a professional Content Browser folder tree from a template. |
| `scaffold_save_template` | Save a custom folder list as a named reusable template. |
| `scaffold_delete_template` | Delete a saved custom template. |
| `scaffold_organize_loose` | Move loose `/Game` root assets into the project folder structure. |
| `organize_smart_categorize` | Auto-organize assets functionally (Meshes/Trees, Materials/Master, etc.) via smart keyword matching. |

**Built-in templates:** `uefn_standard` · `competitive_map` · `solo_dev` · `verse_heavy`

---

### Project Admin (3)

| Tool | Description |
|---|---|
| `project_setup` | One-command project setup: scaffold folders + deploy Verse game manager skeleton. |
| `system_backup_project` | Create a timestamped `.zip` backup of the Content folder. |
| `system_perf_audit` | Fast performance check of the current level. |

---

### Prop Patterns (9)

All pattern tools take `asset_path` + layout params and spawn at camera position.

| Tool | Description |
|---|---|
| `pattern_grid` | N×M rectangular grid (`rows`, `cols`, `spacing`). |
| `pattern_circle` | Evenly-spaced ring (`count`, `radius`). |
| `pattern_arc` | Partial arc between two angles (`count`, `radius`, `angle_start`, `angle_end`). |
| `pattern_spiral` | Archimedean spiral — radius grows with angle (`count`, `turns`). |
| `pattern_line` | Props between two world points (`count`, `start`, `end`). |
| `pattern_wave` | Sine-wave path (`count`, `amplitude`). |
| `pattern_helix` | 3D corkscrew (`count`, `height`, `radius`). |
| `pattern_radial_rows` | Concentric rings — dartboard/stadium layout (`rings`, `count_per_ring`). |
| `pattern_clear` | Delete actors spawned by pattern tools (`preview_only=True` to preview first). |

---

### Proximity Tools (6)

| Tool | Description |
|---|---|
| `actor_place_next_to` | Move last selected actor flush against the first on a specified face (`direction`: +X/-X/+Y/-Y/+Z/-Z). |
| `actor_chain_place` | Arrange selected actors end-to-end along an axis using bounds. |
| `actor_duplicate_offset` | Duplicate selected actor N times with cumulative offset — build arrays without copy-paste. |
| `actor_replace_class` | Replace every actor matching a class filter with a new asset. Always `dry_run=True` first. |
| `actor_cluster_to_folder` | Group nearby actors into World Outliner subfolders by XY-proximity radius. |
| `actor_copy_to_positions` | Stamp copies of the selected actor at every `[[x,y,z], ...]` in a list. |

---

### Reference Auditor (7)

Always `dry_run=True` before any destructive operation.

| Tool | Description |
|---|---|
| `ref_audit_orphans` | Find assets with no referencers — candidates for deletion. |
| `ref_audit_redirectors` | Find stale ObjectRedirectors left behind after moves/renames. |
| `ref_audit_duplicates` | Find assets sharing the same base name in different folders. |
| `ref_audit_unused_textures` | Find Texture2D assets not referenced by any material. |
| `ref_fix_redirectors` | Consolidate ObjectRedirectors. `dry_run=True` first. |
| `ref_delete_orphans` | Delete unreferenced assets. `dry_run=True` first. NOT undoable. |
| `ref_full_report` | Run all scans and export a JSON health report for the project. |

---

### Screenshot (4)

Output: `Saved/UEFN_Toolbelt/screenshots/{name}_{YYYYMMDD_HHMMSS}_{W}x{H}.png`

| Tool | Description |
|---|---|
| `screenshot_take` | Capture the viewport at any resolution (`width`, `height`, `name`). |
| `screenshot_focus_selection` | Auto-frame the selection, capture, then restore the camera. |
| `screenshot_timed_series` | Take N screenshots at a timed interval for before/after comparisons. |
| `screenshot_open_folder` | Print the screenshots output folder path to the Output Log. |

---

### Selection (3)

| Tool | Description |
|---|---|
| `select_by_property` | Select actors where a property matches a specific value. |
| `select_by_verse_tag` | Select actors that have a specific Verse tag. |
| `select_in_radius` | Select all actors of a class within a radius of the current selection. |

---

### Sequencer (2)

| Tool | Description |
|---|---|
| `seq_actor_to_spline` | Animate a selected actor along a selected spline path in the Sequencer. |
| `seq_batch_keyframe` | Add a transform keyframe for all selected actors at the current time. |

---

### Simulation (2)

| Tool | Description |
|---|---|
| `sim_generate_proxy` | Generate a Python simulation proxy for a selected Verse device. |
| `sim_trigger_method` | Trigger a discoverable method on a Verse device via the Python API. |

---

### Stamps (5)

Save a group of placed actors as a reusable named stamp and re-place it anywhere. Not the same as `prefab_migrate_open` — stamps are for level layout reuse, not asset migration.

| Tool | Description |
|---|---|
| `stamp_save` | Save selected StaticMesh actors as a named stamp → `Saved/UEFN_Toolbelt/stamps/{name}.json`. |
| `stamp_place` | Spawn stamp at camera (or `location=[x,y,z]`). `yaw_offset` rotates group; `scale_factor` rescales. |
| `stamp_list` | List all saved stamps with actor counts. |
| `stamp_info` | Print actor names, mesh paths, and relative offsets for a stamp. |
| `stamp_delete` | Delete a saved stamp by name. |

```python
tb.run("stamp_save", name="guard_post")
tb.run("stamp_place", name="guard_post", yaw_offset=90.0)

# Instant symmetric layout — 4 compass points
for angle, x, y in [(0,5000,0),(90,0,5000),(180,-5000,0),(287,0,-5000)]:
tb.run("stamp_place", name="guard_post", location=[x,y,0], yaw_offset=angle)
```

---

### System (4)

| Tool | Description |
|---|---|
| `system_build_verse` | Trigger Verse compilation and parse errors back as structured JSON. |
| `system_get_last_build_log` | Read the last 100 lines of the UEFN log for error analysis. |
| `system_optimize_background_cpu` | Disable UEFN sleep when alt-tabbed so Python/AI runs at max speed. |
| `verse_patch_errors` | AI error loop: read build log, extract errors + file content, return everything Claude needs to fix and redeploy. |
| `toolbelt_activity_log` | Rolling log of every `tb.run()` call — tool name, status, duration (ms), timestamp, error message. Newest first. |
| `toolbelt_activity_stats` | Aggregate session stats: total calls, error rate, slowest tool, most-called tool, last 5 errors. |
| `toolbelt_activity_clear` | Clear the in-memory ring buffer and the persisted `activity_log.json`. |

---

### Tests (1)

| Tool | Description |
|---|---|
| `toolbelt_integration_test` | ⚠️ Invasive — spawns/deletes actors. Run in a clean template level only. |

---

### Text & Signs (14)

| Tool | Description |
|---|---|
| `text_place` | Place a single styled 3D text actor at a world location. |
| `text_label_selection` | Auto-label every selected actor with its own name, floating above it. |
| `text_paint_grid` | Paint a coordinate grid of labels across a map area (A1, B2, …). |
| `text_color_cycle` | Place text actors cycling through a color palette. |
| `text_save_style` | Save a named text style preset for reuse across levels. |
| `text_list_styles` | Print all saved text style presets. |
| `text_clear_folder` | Delete all TextRenderActors in the Toolbelt text folder. |
| `sign_spawn_bulk` | Spawn N text signs in a row, column, or grid at a given location. |
| `sign_batch_edit` | Edit text/color/size on all selected signs — only provided fields change. |
| `sign_batch_set_text` | Assign individual text strings to each selected sign in order. |
| `sign_batch_rename` | Rename selected signs sequentially: `prefix_01`, `prefix_02`, … |
| `sign_list` | List all TextRenderActors in a folder with their current text. |
| `sign_clear` | Delete all signs in a named folder. |
| `label_attach` | Spawn a floating text label above each selected actor, parented so it follows movement. |

---

### Utilities (12)

| Tool | Description |
|---|---|
| `config_list` | Show all Toolbelt config keys — current values + whether overridden. |
| `config_get` | Read one config key. |
| `config_set` | Persist a config value (`key`, `value`). Survives restarts and updates. |
| `config_reset` | Reset a key (or `"all"`) back to built-in defaults. |
| `theme_list` | List all available Toolbelt UI themes. |
| `theme_get` | Get the currently active theme name. |
| `theme_set` | Switch the Toolbelt UI theme — applies live, persists across restarts. |
| `level_health_report` | Run all audit tools and return a unified 0–100 health score with A+/A/B/C/D/F grade. MCP-friendly. |
| `level_health_open` | Print a formatted Level Health Report to the Output Log (score + per-category breakdown). |
| `plugin_validate_all` | Validate all registered tools against Toolbelt schema requirements. |
| `plugin_list_custom` | List all loaded third-party custom plugins from `Saved/UEFN_Toolbelt/Custom_Plugins/`. |
| `plugin_export_manifest` | Export `tool_manifest.json` — full machine-readable index of all tools with parameter signatures. |

---

### Verse Helpers (26)

| Tool | Description |
|---|---|
| `verse_list_devices` | List all Verse/Creative device actors in the current level. |
| `verse_select_by_name` | Select all devices whose label contains a filter string. |
| `verse_select_by_class` | Select all devices of a given class name substring. |
| `verse_bulk_set_property` | Set a UPROPERTY on all selected Verse device actors at once. |
| `device_set_property` | AI write layer — set any base-class property on actors matching class/label/path filter. |
| `device_call_method` | V2 runtime control — call any exposed method on matched actors (`timer_start`, `Enable`, etc.). |
| `verse_export_report` | Export a JSON report of all Verse device properties to the Saved folder. |
| `verse_find_project_path` | Auto-detect the Verse source directory for the current UEFN project. |
| `verse_write_file` | AI deploy layer — write generated Verse directly into the project's Verse source directory. |
| `verse_gen_game_skeleton` | Generate a full `creative_device` game manager Verse stub. |
| `verse_gen_device_declarations` | Generate `@editable` device declarations from the current selection. |
| `verse_gen_elimination_handler` | Generate a Verse elimination event handler skeleton. |
| `verse_gen_scoring_tracker` | Generate a Verse zone scoring tracker device skeleton. |
| `verse_gen_prop_spawner` | Generate a trigger-controlled prop spawn/despawn Verse skeleton. |
| `verse_gen_custom` | Write arbitrary Verse to the snippets folder. |
| `verse_list_snippets` | List all generated snippet files in `Saved/UEFN_Toolbelt/snippets/`. |
| `verse_open_snippets_folder` | Open the Verse snippet library folder in Windows Explorer. |
| `verse_graph_open` | Interactive Verse Device Graph — force-directed layout, minimap, category filter, live sync, write-back. |
| `verse_graph_scan` | Headless scan → full device adjacency dict (MCP-friendly). |
| `verse_graph_export` | Export the device graph to JSON for external tooling. |
| `spline_to_verse_points` | Convert selected spline to a Verse `vector3` array literal. |
| `spline_to_verse_patrol` | Generate a full Verse patrol AI device skeleton from a selected spline. |
| `spline_to_verse_zone_boundary` | Convert selected spline to a Verse zone boundary + `IsPointInZone` helper. |
| `spline_export_json` | Export spline points to JSON (for `scatter_along_path` etc.). |
| `api_verse_get_schema` | Get the cached Verse schema for a device class. |
| `api_verse_refresh_schemas` | Refresh the Verse schema cache from current level devices. |

---

### Zone Tools (7)

Zone actors can be any box-shaped actor. **Convention:** select the zone actor first, then other actors.

| Tool | Description |
|---|---|
| `zone_spawn` | Spawn a visible cube zone marker at camera position in the "Zones" folder. |
| `zone_resize_to_selection` | Resize zone to exactly contain all other selected actors (+ `padding` cm). |
| `zone_snap_to_selection` | Move zone center to match combined selection bounds — no resize. |
| `zone_select_contents` | Select every level actor whose pivot is inside the zone's bounds. |
| `zone_move_contents` | Move zone + all actors inside it as a unit by a world-space offset. |
| `zone_fill_scatter` | Randomly scatter copies of an asset throughout the zone volume. |
| `zone_list` | List all actors in the "Zones" folder with center, width, depth, height. |

```python
tb.run("zone_spawn", width=4000, depth=4000, height=800, label="ArenaCenter")
tb.run("zone_resize_to_selection", padding=200)
tb.run("zone_select_contents")
tb.run("zone_move_contents", offset_x=5000)
tb.run("zone_fill_scatter", asset_path="/Engine/BasicShapes/Cube", count=50, min_spacing=300)
```

---

### Health Check

```python
# Run all 6 layers: Python env → UEFN API → Toolbelt core → MCP → Dashboard → Verse Book
tb.run("toolbelt_smoke_test")
# Results saved to Saved/UEFN_Toolbelt/smoke_test_results.txt
```

## Getting Started

> Tested on UEFN 40.00, Windows 11, Python 3.11.8 — March 2026.

### Step 1 — Enable Python in UEFN

Open your UEFN project. Go to **Edit → Project Settings**, search `python`, and tick:

- **Python Editor Script Plugin** ✓

> **Note:** UEFN only shows one Python checkbox — unlike full Unreal Engine which shows three. That's normal. Once you see "Enter a Python statement" appear in the Output Log, Python is active.

Restart UEFN when prompted.

---

### Step 2 — Install PySide6 (one-time, required for the dashboard UI)

Open a **regular Windows terminal** — not inside UEFN — and run:

```bat
"C:\Program Files\Epic Games\Fortnite\Engine\Binaries\ThirdParty\Python3\Win64\python.exe" -m pip install PySide6
```

> **Why outside UEFN?** UEFN's embedded Python can't run pip itself. You must use the same `python.exe` that UEFN uses, just run it directly from a terminal.

If the path above doesn't exist, find it at:
```
C:\Program Files\Epic Games\Fortnite\Engine\Binaries\ThirdParty\Python3\Win64\python.exe
```

You only need to do this once. PySide6 stays installed across projects.

---

### Step 3 — Install the Toolbelt into your UEFN project

**First time — run the installer:**

```bash
python install.py
```

It will find your Fortnite Projects folder automatically, let you pick a project, copy the Toolbelt in, and handle `init_unreal.py` safely — whether you already have one or not.

Or point it directly at a project:

```bash
python install.py --project "C:\Users\YOURNAME\Documents\Fortnite Projects\YOURPROJECT"
```

> **Updating after a `git pull`:** Re-run `python install.py` — it overwrites only the Toolbelt package, never your project's own files.

---

**If you're actively developing the Toolbelt itself — use `deploy.bat` instead:**

`deploy.bat` is the dev workflow tool. Double-click it or run it from a terminal. In addition to copying files it will:
- Check and install PySide6 automatically if it's missing
- Copy the `tests/` folder and `verse-book/` spec alongside the package
- Print the hot-reload command to paste into UEFN so you don't need a full restart

```bat
deploy.bat
```

> **Hot-reload after any code change** (no UEFN restart needed):
> ```python
> import sys; [sys.modules.pop(k) for k in list(sys.modules) if "UEFN_Toolbelt" in k]; import UEFN_Toolbelt as tb; tb.register_all_tools(); tb.launch_qt()
> ```

---

**Manual install** (if neither script works):

Your UEFN project lives at `C:\Users\YOURNAME\Documents\Fortnite Projects\YOURPROJECT\`.

```bat
xcopy /E /I /Y "PATH_TO_REPO\Content\Python\UEFN_Toolbelt" "C:\Users\YOURNAME\Documents\Fortnite Projects\YOURPROJECT\Content\Python\UEFN_Toolbelt"
```

Then copy `init_unreal.py` into `Content\Python\` — or if you already have one, paste only the package-discovery `for` loop from it into your existing file (under `# ── 2. Discover and load all packages`). It won't conflict with anything already there.

### Step 4 — Restart UEFN

Close and reopen your project. Watch the **Output Log**. You should see:

```
[TOOLBELT] ✓ All tools registered.
[TOOLBELT] ✓ Menu registered — look for 'Toolbelt' in the top menu bar.
```

A **Toolbelt** menu now appears in the top menu bar next to Help. No Blueprint setup, no manual script execution — it's automatic every time UEFN starts.

---

### Step 5 — Verify with the smoke test

In the Output Log, switch the input dropdown from **Cmd** to **Python** and run:

```python
import UEFN_Toolbelt as tb; tb.run("toolbelt_smoke_test")
```

> **UEFN Python console tip:** The console only accepts **one line at a time** — no multi-line pastes. Chain multiple statements on the same line using semicolons (`;`). A space before and after the semicolon works fine: `import UEFN_Toolbelt as tb; tb.run("toolbelt_smoke_test")`

Expected output in the log:
```
✓ All systems healthy — Toolbelt is ready.
```

| Layer | What It Verifies | Checks |
|---|---|---|
| **Layer 1** — Python Environment | stdlib, threading, sockets, HTTP server, file I/O | 13 |
| **Layer 2** — UEFN API Surface | `unreal` module, subsystems, AutomationLibrary, Materials | 13 |
| **Layer 3** — Toolbelt Core | All 76 modules loaded, 358 tools registered | 40 |
| **Layer 4** — MCP Bridge | 31 command handlers, HTTP listener state | 4 |
| **Layer 5** — Dashboard (PySide6) | PySide6 importable, QApplication, ToolbeltDashboard | 3 |
| **Layer 6** — Verse Book | clone present, git reachable, 22 chapters parsed | 12 |
| **Layer 7** — Integration | Fixture-based verification of context-aware tools — see Integration Testing section above | — |

> If you haven't installed PySide6 or cloned the verse-book yet, you may see a few non-critical failures.
> Complete Steps 2 and 7 in the Getting Started guide above to resolve them.

---

### Step 6 — Open the dashboard

After completing Step 2 (PySide6 installed), click **Toolbelt → Open Dashboard (PySide6)** in the top menu bar, or run:

```python
import UEFN_Toolbelt as tb; tb.launch_qt()
```

A dark-themed floating window opens with a left sidebar nav and 358 tools across 55 categories (including an About page).

![Global Dashboard Search](docs/dashboard_search.png)

**Two search modes:**

| Search | Where | What it does |
|---|---|---|
| **Find any tool…** | Sidebar (top) | Global — searches all 358 tools across every category by name, description, or tag. Results show a category badge so you know where each tool lives. |
| **Filter this page…** | Content header (top-right) | Within-category — hides/shows buttons on the current page as you type. Disappears during global search. |

> **If the dashboard doesn't open after installing PySide6:** The module may be cached from before PySide6 was installed. Paste this single line to clear and reload:
> ```python
> import sys; [sys.modules.pop(k) for k in list(sys.modules) if "UEFN_Toolbelt" in k]; import UEFN_Toolbelt as tb; tb.launch_qt()
> ```

---

### Step 7 — (Optional) Clone the Verse spec for AI codegen

In your repo folder, run:

```bat
git clone https://github.com/verselang/book.git verse-book
```

This gives Claude access to the full live Verse language spec for spec-accurate code generation. After cloning, the smoke test will show 63/64 (only PySide6 remaining if not installed).

---

### Step 8 — (Optional) Connect Claude Code via MCP

This lets Claude Code directly control UEFN — spawn actors, run any tool, generate Verse code — without you typing anything in the console.

**1. Install the MCP Python package** (regular terminal, not UEFN):
```bat
pip install mcp
```

**2. Create your `.mcp.json`:**
```bat
copy .mcp.json.template .mcp.json
```
Open `.mcp.json` and update the path to point to your repo:
```json
{
"mcpServers": {
"uefn-toolbelt": {
"command": "python",
"args": ["C:/Users/YOURNAME/Projects/UEFN-TOOLBELT/mcp_server.py"]
}
}
}
```

**3. Restart Claude Code** so it picks up the new server config.

**4. Start the listener inside UEFN** — paste this in the Python console:
```python
import UEFN_Toolbelt as tb; tb.run("mcp_start")
```
You should see in the Output Log:
```
[MCP] ✓ Listener running on http://127.0.0.1:8765
```

**5. Test it in Claude Code:**
```
run the toolbelt smoke test
```
Claude will call `run_toolbelt_tool("toolbelt_smoke_test")` through the bridge and show you the results.

> **Note:** The listener must be started in UEFN each session. You can also click **Dashboard → MCP → Start Listener** instead of pasting the command.

**What Claude can do once connected:**
- Run any of the 358 tools by name
- Spawn, move, delete actors directly
- Generate spec-accurate Verse code (pulls from the live verse-book spec)
- Execute arbitrary Python inside UEFN
- Batch multiple operations in one editor tick

---

### Step 9 — (Optional) Verse Snippet Library

The Verse tab in the dashboard has a built-in **Snippet Library** browser. Every time you generate a Verse snippet, it's saved to:

```
Saved/UEFN_Toolbelt/snippets/
├── game_systems/ ← game manager, elimination handler, scoring tracker
├── device_wiring/ ← @editable declarations, prop spawner
├── spline_tools/ ← patrol AI, zone boundary, vector3 arrays
└── custom/ ← your custom / Claude-generated snippets
```

In the dashboard:
1. Go to **Verse → Code Generation** and generate any snippet
2. Click **Refresh Library** in the Snippet Library section below
3. Your snippets appear organized by category — click any to **copy to clipboard**
4. Click **Open Folder** to browse them in Windows Explorer
5. Paste directly into your `.verse` file in UEFN

Snippets also auto-copy to the Windows clipboard when generated, so you can paste immediately without even opening the library.

---

### **The "Nuclear Reload" Command**
If you have modified the Toolbelt source code, run this in the UEFN console to refresh everything without a restart:
```python
import sys; [sys.modules.pop(k) for k in list(sys.modules) if "UEFN_Toolbelt" in k]; import UEFN_Toolbelt as tb; tb.register_all_tools(); tb.launch_qt()
```
> [!IMPORTANT]
> Always include `tb.register_all_tools()` after a pop, otherwise the tool registry will be empty!

### REPL Usage

> **UEFN console tip:** Chain multiple statements on one line with semicolons (`;`). The console does not accept multi-line pastes, but a single line can contain as many `;`-separated statements as you need.

**Dashboard & Health Check:**
```python
import UEFN_Toolbelt as tb; tb.launch_qt()
```
```python
import UEFN_Toolbelt as tb; tb.run("toolbelt_smoke_test")
```

**Force reload (after git pull or update):**
```python
import sys; [sys.modules.pop(k) for k in list(sys.modules) if "UEFN_Toolbelt" in k]; import UEFN_Toolbelt as tb; tb.launch_qt()
```

**Materials & Bulk Ops:**
```python
import UEFN_Toolbelt as tb; tb.run("material_apply_preset", preset="gold")
```
```python
import UEFN_Toolbelt as tb; tb.run("bulk_align", axis="Z")
```
```python
import UEFN_Toolbelt as tb; tb.run("arena_generate", size="large", apply_team_colors=True)
```

**Plugin Management:**
```python
import UEFN_Toolbelt as tb; tb.run("plugin_validate_all")
```
```python
import UEFN_Toolbelt as tb; tb.run("plugin_list_custom")
```

**API Explorer & Capability Crawling:**
```python
import UEFN_Toolbelt as tb; tb.run("api_crawl_level_classes")
```
```python
import UEFN_Toolbelt as tb; tb.run("api_crawl_selection")
```
```python
import UEFN_Toolbelt as tb; tb.run("api_export_full")
```

**Search & Discovery:**
```python
import UEFN_Toolbelt as tb; tb.registry.search("material")
```
```python
import UEFN_Toolbelt as tb; print(len(tb.registry.list_tools()))
```

**Handy Combos:**
```python
import UEFN_Toolbelt as tb; tb.run("toolbelt_smoke_test"); tb.run("plugin_validate_all")
```
```python
import UEFN_Toolbelt as tb; tb.run("snapshot_save", name="before"); tb.run("bulk_randomize", rot_range=360)
```

### Required Asset Setup (Material Master)

Create `Content/UEFN_Toolbelt/Materials/M_ToolbeltBase` in the material editor with
these five exposed parameters:

| Parameter | Type | Connects to |
|---|---|---|
| `BaseColor` | Vector | Base Color |
| `Metallic` | Scalar (0–1) | Metallic |
| `Roughness` | Scalar (0–1) | Roughness |
| `EmissiveColor` | Vector | × EmissiveIntensity → Emissive Color |
| `EmissiveIntensity` | Scalar (0–20) | (see above) |

Everything else works out of the box.

---

## Adding a New Tool

**3 steps. No boilerplate. No registration config.**

**Step 1** — Create `Content/Python/UEFN_Toolbelt/tools/my_tool.py`:

```python
"""UEFN TOOLBELT — My Tool"""
import unreal
from ..core import undo_transaction, require_selection, log_info
from ..registry import register_tool

@register_tool(
name="my_tool",
category="Utilities",
description="Does something amazing",
tags=["utility", "amazing"],
)
def run(**kwargs) -> dict:
actors = require_selection()
if actors is None:
return {"status": "error", "count": 0}
with undo_transaction("My Tool"):
for actor in actors:
log_info(f"Processing {actor.get_actor_label()}")
# your logic here
return {"status": "ok", "count": len(actors)}
```

> **Return contract:** Every tool must return a `dict` with a `"status"` key. This is what
> makes tools readable by AI agents via the MCP bridge — they read `result["status"]` and
> domain keys like `"count"` or `"path"` directly from the JSON response. Never return `None`.

**Step 2** — Add one line to `tools/__init__.py`:

```python
from . import my_tool
```

**Step 3** — Add a menu entry in `init_unreal.py`'s `_build_toolbelt_menu()` (optional):

```python
_add_entry(tb_sub, "Utilities", "MyTool",
"My Tool", "Does something amazing",
"import UEFN_Toolbelt as tb; tb.run('my_tool')")
```

Your tool is now registered, searchable, undo-safe, and accessible from the top menu bar.

---

## Plugin Hub & Community Ecosystem

UEFN Toolbelt ships a **live Plugin Hub** tab in the dashboard. Click **Refresh Hub** to pull the latest community registry directly from GitHub — no restart required.

### What you see in the Hub

| Section | What it shows |
|---|---|
| **Core Tools** (green, BUILT-IN badge) | 10 flagship modules built by Ocean Bennett — already included, View Source only |
| **Community Plugins** (blue, Install button) | Third-party tools submitted to `registry.json` — one-click download into `Custom_Plugins/` |

The registry is hosted at:
```
https://raw.githubusercontent.com/undergroundrap/UEFN-TOOLBELT/main/registry.json
```

### Getting your plugin listed

Add an entry to `registry.json` at the repo root and open a PR:

```json
{
"id": "my_cool_tool",
"name": "My Cool Tool",
"version": "1.0.0",
"author": "Your Name",
"author_url": "https://github.com/yourhandle",
"type": "community",
"description": "One sentence on what it does.",
"category": "Gameplay",
"tags": ["verse", "gameplay"],
"url": "https://github.com/yourhandle/yourrepo/blob/main/my_cool_tool.py",
"download_url": "https://raw.githubusercontent.com/yourhandle/yourrepo/main/my_cool_tool.py",
"min_toolbelt_version": "1.5.3",
"size_kb": 10
}
```

> `download_url` must be a raw GitHub URL. The dashboard downloads it directly into
> `Saved/UEFN_Toolbelt/Custom_Plugins/` — no zip, no installer, single `.py` file.

---

## Custom Plugins & Security

UEFN Toolbelt is a **security-first extensible platform**. Third-party developers can create custom tools that automatically load into the Dashboard and AI bridge — but the Toolbelt enforces strict sandboxing to protect your editor and project files.

### How Custom Plugins Work

Drop a `.py` file into `[Your Project]/Saved/UEFN_Toolbelt/Custom_Plugins/`. The Toolbelt will:
1. **Discover** the file on startup.
2. **Security-screen** it through four gates (see below).
3. **Register** it in the tool registry with a UI button and AI accessibility.
4. **Log** a tamper-evident audit record.

```python
# Example: one-file custom plugin
from UEFN_Toolbelt.registry import register_tool
from UEFN_Toolbelt import core

@register_tool(name="my_tool", category="Community", description="Does something cool")
def run(**kwargs) -> dict:
core.log_info("My custom tool ran!")
return {"status": "ok"} # always return a dict
```

### 🔒 Four-Gate Security Model

Every plugin passes through **four security gates** before it can execute inside your editor:

| Gate | What It Checks | What Happens If It Fails |
|---|---|---|
| **1. File Size Limit** | Rejects files > 50 KB | Blocks obfuscated or suspiciously large payloads |
| **2. AST Import Scanner** | Parses the source code *without executing it* using Python's `ast` module | Blocks dangerous imports: `subprocess`, `socket`, `ctypes`, `shutil`, `http`, `urllib`, `requests`, `webbrowser`, `smtplib`, `ftplib`, `xmlrpc`, `multiprocessing`, `signal`, `_thread` |
| **3. Namespace Protection** | Checks if the tool name collides with a core Toolbelt tool | Rejects registration — prevents hijacking of system commands like `toolbelt_smoke_test` |
| **4. SHA-256 Integrity Hash** | Computes a cryptographic fingerprint of every loaded plugin | Logged to `plugin_audit.json` — detect tampering by comparing hashes across sessions |

### 📋 Plugin Audit Log

Every time the Toolbelt boots, it writes a tamper-evident audit trail to:
```
Saved/UEFN_Toolbelt/plugin_audit.json
```

Each entry records the plugin name, its SHA-256 hash, file size, load status, and timestamp. If a plugin file changes between sessions, the hash changes — making unauthorized modifications instantly detectable.

**Read the full developer guide here:** [docs/plugin_dev_guide.md](docs/plugin_dev_guide.md)

---

## API Capability Crawler

Epic's documentation for the UEFN Python API is incomplete. Many Fortnite device properties are exposed to Python but never documented. The **Capability Crawler** solves this by brute-forcing introspection on live actors inside the editor — mapping out exactly what variables exist, which are readable, and what values they hold.

### Why This Matters

- **For Verse developers:** Discover hidden properties on Fortnite devices that aren't in the docs
- **For tool builders:** Know exactly what you can automate before writing a single line of code
- **For AI-assisted workflows:** Generate a machine-readable API map that Claude can analyze and act on

### Verified Results (Live)

Tested on a real UEFN project (March 2026):

```
Found 41 total actors, distilling to 12 unique classes...
✓ Level class schema saved: api_level_classes_schema.json
```

The crawler scanned 41 actors, identified 12 unique class types, and deep-introspected every property, method, and component hierarchy — all in under a second, with zero editor impact.

### Workflow

**Step 1 — Scan your level** (headless, no selection required):
```python
import UEFN_Toolbelt as tb; tb.run("api_crawl_level_classes")
```

**Step 2 — Scan a specific selection** (select actors in viewport first):
```python
import UEFN_Toolbelt as tb; tb.run("api_crawl_selection")
```

**Step 3 — Review the output:**
```
Saved/UEFN_Toolbelt/api_level_classes_schema.json ← full level scan
Saved/UEFN_Toolbelt/api_selection_crawl.json ← selection scan
```

### Output Format

The JSON contains a per-class schema with every exposed property, its type, readability, and an example value:

```json
{
"classes": {
"StaticMeshActor": {
"properties": {
"root_component": { "type": "SceneComponent", "readable": true },
"mobility": { "type": "str", "readable": true, "example_value": "EComponentMobility.STATIC" }
},
"methods": ["get_actor_label", "set_actor_location", "get_components_by_class"],
"components": {
"StaticMeshComponent0": {
"properties": { "static_mesh": { "type": "StaticMesh", "readable": true } }
}
}
}
}
}
```

Properties marked `"readable": false` with an `"error"` field indicate locked-down C++ internals that Epic has not yet exposed.

### AI-Powered Analysis (Claude Integration)

Because the crawler output is structured JSON, it becomes a direct input for AI analysis. After running a crawl, you can ask Claude Code:

> *"Read `Saved/UEFN_Toolbelt/api_level_classes_schema.json` and tell me which properties on each device class are writable from Python."*

> *"Which classes in my level have components with exposed transform properties I could animate?"*

> *"Generate a Verse class that references every device in my level using the correct variable types from the crawl data."*

The crawler is the **data collection engine**. Claude is the **analysis layer**. Together they let you reverse-engineer Epic's undocumented API surface without ever reading a single page of docs.

---

## Fortnite Device API Mapping

The Toolbelt includes a fully schema-derived API map built from real UEFN level introspection —
not guesswork. The reference schema (`uefn_reference_schema.json`) was generated by running
`api_crawl_level_classes` against a live level with 76 actors, producing a 1.6MB JSON
with every readable and restricted property across 14 C++ classes.

### Discovery Workflow

```python
import UEFN_Toolbelt as tb

# Crawl your level — produces docs/api_level_classes_schema.json
tb.run("api_crawl_level_classes")

# Or use the Dashboard "Sync Level Schema" button for one-click sync
```

### The Three Schema Layers

| Layer | File | What it contains |
|:---|:---|:---|
| **Tool Layer** | `Saved/UEFN_Toolbelt/tool_manifest.json` | 358 tools — names, params, types, defaults |
| **C++ Layer** | `docs/uefn_reference_schema.json` | 14 actor classes, 1,031 properties with types + defaults |
| **Verse Layer** | `docs/api_level_classes_schema.json` | Your project's `@editable` device properties (git-ignored) |

### Verified Actor Classes (from real schema)

| Class | Readable Props | Key Automation Properties |
|:---|:---|:---|
| `BuildingProp` | 130 | `is_invulnerable`, `no_collision`, `team_index`, `static_mesh`, `allow_interact` |
| `BuildingFloor` | 125 | `snap_grid_size` (512), `register_with_structural_grid`, `resource_type` |
| `FortCreativeDeviceProp` | 130 | Verse shell — use `getattr()` or `verse_bulk_set_property` |
| `FortPlayerStartCreative` | 41 | `is_enabled`, `applicable_team`, `priority_group`, `use_as_island_start` |
| `FortMinigameSettingsBuilding` | 53 | `max_players`, `join_in_progress_behavior`, `creative_matchmaking_privacy` |
| `FortStaticMeshActor` | 31 | `static_mesh_component`, non-destructible by default |
| `TextRenderActor` | 31 | `text_render` component — world text placed by `text_place` tool |
| `FortWaterBodyActor` | 37 | `water_body_primary_color`, `water_waves`, `overlapping_players` |
| `Actor` (base) | 30 | `hidden`, `tags`, `replicates`, `net_dormancy`, `pivot_offset` |

> **Full reference** — property tables, enums, collision groups, automation patterns, and
> access cheatsheet: **[docs/DEVICE_API_MAP.md](docs/DEVICE_API_MAP.md)**
>
> **Creative device reference** — Trigger, Score Manager, Teleporter, Guard Spawner, channel
> wiring, Verse code gen: **[docs/FORTNITE_DEVICES.md](docs/FORTNITE_DEVICES.md)**

---

## PySide6 Dashboard

The real dashboard. One `pip install PySide6` (or just run `deploy.bat`) and you get a full floating window with a scrollable left sidebar nav, two search modes, dark theme, inline parameter inputs, and live status feedback.
**No Blueprint. No UMG. No Restart.**

### What you get

```
⬡ UEFN Toolbelt
┌─────────────────────────────────────────────────────┐
│ Materials │ Procedural │ Bulk Ops │ Text │ Assets │ Verse │ Project │ API │
├─────────────────────────────────────────────────────┤
│ ┌─ PRESETS ──────────────────────────────────────┐ │
│ │ [Chrome] [Gold] [Neon] [Hologram] │ │
│ │ [Lava] [Plasma][Ice] [Mirror] │ │
│ │ [Wood] [Conc.] [Rubber][Carbon] │ │
│ │ [Jade] [Obsid.][Rusty] [Team Red][Team Blue] │ │
│ └────────────────────────────────────────────────┘ │
│ ┌─ OPERATIONS ───────────────────────────────────┐ │
│ │ [Randomize Colors ] │ │
│ │ [Team Color Split (Red / Blue by X pos) ] │ │
│ │ [Color Harmony] [triadic ▾] │ │
│ │ [Gradient Painter (Blue → Red along X) ] │ │
│ └────────────────────────────────────────────────┘ │
│ ────────────────────────────────────────────────── │
│ ✓ material_apply_preset — done 4s │
└─────────────────────────────────────────────────────┘
```

### Launch

```python
import UEFN_Toolbelt as tb; tb.launch_qt()
```

Or: **Toolbelt → Open Dashboard (PySide6)** from the top menu bar.

### Color reference (QSS applied automatically)

| Element | Color |
|---|---|
| Window background | `#181818` |
| Section / card background | `#212121` |
| Button default | `#262626` border `#363636` |
| Button hover | `#333333` |
| Button pressed / accent | `#3A3AFF` |
| Active tab | `#3A3AFF` white text |
| Body text | `#CCCCCC` |
| Section headers | `#555555` small-caps |
| Status: success | `#44FF88` |
| Status: error | `#FF4444` |

### Event loop integration

The dashboard registers a `slate_post_tick_callback` that calls
`QApplication.processEvents()` once per editor tick — the same pattern used in
[Kirch's uefn_listener.py](https://github.com/KirChuvakov/uefn-mcp-server).
This keeps the Qt window responsive while UEFN runs heavy operations without
needing a separate thread.

---

## Blueprint Dashboard (Legacy / Optional)

The Blueprint approach still works and is documented for completeness.
**Prefer the PySide6 dashboard** — it launches faster and requires no Blueprint setup.

The top-menu Toolbelt entries work without this. Build the widget if you want
a visual panel with buttons, sliders, and live text input instead of the REPL.

### Create the Widget Blueprint

1. In Content Browser → `Content/UEFN_Toolbelt/Blueprints/`
2. Right-click → **Editor Utilities → Editor Widget**
3. Name it exactly: `WBP_ToolbeltDashboard`
4. Double-click to open the UMG Designer.

### Dark Mode Styling

The dashboard targets a `#181818 / #212121` dark theme to match the UEFN editor chrome.
Apply these values in the **Details** panel for each widget:

| Element | Property | Value |
|---|---|---|
| Root Canvas / Overlay | **Background Color** | `#181818` (R 0.024, G 0.024, B 0.024, A 1.0) |
| Section panels / cards | **Background Color** | `#212121` (R 0.033, G 0.033, B 0.033, A 1.0) |
| Header Text Block | **Color and Opacity** | `#FFFFFF` / Alpha 1.0 |
| Body / label Text Blocks | **Color and Opacity** | `#CCCCCC` (R 0.8, G 0.8, B 0.8) |
| Tab bar active button | **Normal → Tint** | `#3A3AFF` (accent blue) |
| Tab bar inactive button | **Normal → Tint** | `#2A2A2A` |
| Tool buttons (default) | **Normal → Tint** | `#2D2D2D` |
| Tool buttons (hover) | **Hovered → Tint** | `#3D3D3D` |
| Tool buttons (pressed) | **Pressed → Tint** | `#1A1AFF` |
| Separator / divider | **Border** widget, **Brush Tint** | `#383838` |
| Warning / critical text | **Color and Opacity** | `#FF4444` |
| Success / ok text | **Color and Opacity** | `#44FF88` |

**Setting background color on a Border widget** (UMG has no plain colored panel):

1. Drag a **Border** widget into the hierarchy where you need a colored region.
2. In **Details → Brush → Image**: leave blank (or use a 1×1 white texture).
3. Set **Brush → Tint** to your hex color.
4. Set **Content → Horizontal/Vertical Alignment** to `Fill`.
5. Drop your content widgets *inside* the Border as children.

**Font recommendation:** Import `Rajdhani-SemiBold.ttf` (free, SIL OFL) as a Font Face asset
at `/Game/UEFN_Toolbelt/Fonts/Rajdhani`. Set it on all Text Blocks for a modern HUD look.
Fallback: the built-in `Roboto` works fine.

### Recommended UMG Layout

```
[Canvas Panel] — #181818 background (via full-screen Border)
└── [Vertical Box] (fill screen, padding 0)
├── [Border] header bar — #212121, height 48px
│ └── [Horizontal Box]
│ ├── [Text Block] "⬡ UEFN TOOLBELT" white, 18pt, Rajdhani
│ └── [Spacer]
├── [Border] tab bar — #181818, height 36px
│ └── [Horizontal Box]
│ Buttons: Materials | Procedural | BulkOps | Text | Optimize | Verse
│ (blue tint on active, dark on rest)
└── [Widget Switcher] (content area — fills remaining space)
└── [Scroll Box] (per tab)
└── [Vertical Box] (tool buttons, padding 8px, gap 4px)
└── [Border] per button row — #2D2D2D hover #3D3D3D
└── [Button] → Execute Python Command
```

**Tip:** Wire each tab button's **On Clicked** → set the **Active Widget Index** on the
Widget Switcher. Each index corresponds to one category's button panel.

**Tip:** Add a `[Text Block]` at the bottom of each panel wired to a Blueprint variable
`StatusText`. On tool button click, set `StatusText` to `"Running..."` before the
Execute Python Command node and `"Done ✓"` after. This gives live feedback without
needing to read the Output Log.

### Wiring a Button to a Tool

1. Add a **Button** widget with a **Text Block** child (e.g. "Apply Chrome").
2. In the **Graph** tab, find the button's **On Clicked** event.
3. Search for and add: **Execute Python Command**.
4. Wire **On Clicked** → **Execute Python Command**.
5. Type the tool call into the **Python Command** string pin:

```
import UEFN_Toolbelt as tb; tb.run("material_apply_preset", preset="chrome")
```

### Complete Button String Reference

```python
# Materials
import UEFN_Toolbelt as tb; tb.run("material_apply_preset", preset="chrome")
import UEFN_Toolbelt as tb; tb.run("material_apply_preset", preset="gold")
import UEFN_Toolbelt as tb; tb.run("material_apply_preset", preset="neon")
import UEFN_Toolbelt as tb; tb.run("material_apply_preset", preset="hologram")
import UEFN_Toolbelt as tb; tb.run("material_randomize_colors")
import UEFN_Toolbelt as tb; tb.run("material_gradient_painter", color_a="#0044FF", color_b="#FF2200")
import UEFN_Toolbelt as tb; tb.run("material_team_color_split")
import UEFN_Toolbelt as tb; tb.run("material_color_harmony", harmony="triadic")

# Procedural
import UEFN_Toolbelt as tb; tb.run("arena_generate", size="small")
import UEFN_Toolbelt as tb; tb.run("arena_generate", size="medium")
import UEFN_Toolbelt as tb; tb.run("arena_generate", size="large")
import UEFN_Toolbelt as tb; tb.run("spline_place_props", count=20, align_to_tangent=True)
import UEFN_Toolbelt as tb; tb.run("scatter_props", mesh_path="/Engine/BasicShapes/Sphere", count=100, radius=3000.0)

# Bulk Ops
import UEFN_Toolbelt as tb; tb.run("bulk_align", axis="Z")
import UEFN_Toolbelt as tb; tb.run("bulk_distribute", axis="X")
import UEFN_Toolbelt as tb; tb.run("bulk_randomize", rot_range=360.0, randomize_rot=True)
import UEFN_Toolbelt as tb; tb.run("bulk_snap_to_grid", grid=100.0)
import UEFN_Toolbelt as tb; tb.run("bulk_reset")
import UEFN_Toolbelt as tb; tb.run("bulk_mirror", axis="X")

# Text & Signs
import UEFN_Toolbelt as tb; tb.run("text_place", text="ZONE", color="#FFDD00", location=(0,0,200))
import UEFN_Toolbelt as tb; tb.run("text_label_selection", color="#00FFCC")
import UEFN_Toolbelt as tb; tb.run("text_paint_grid", cols=4, rows=4, cell_size=2000.0)
import UEFN_Toolbelt as tb; tb.run("text_clear_folder")

# Assets
import UEFN_Toolbelt as tb; tb.run("rename_dry_run", scan_path="/Game")
# lod_auto_generate_folder is disabled in UEFN (see UEFN_QUIRKS.md #18) — use lod_audit_folder to find meshes, then add LODs manually in the Static Mesh Editor
import UEFN_Toolbelt as tb; tb.run("lod_audit_folder", folder_path="/Game")
import UEFN_Toolbelt as tb; tb.run("organize_assets", source_path="/Game/Imports")

# Verse
import UEFN_Toolbelt as tb; tb.run("verse_gen_device_declarations")
import UEFN_Toolbelt as tb; tb.run("spline_to_verse_points")
import UEFN_Toolbelt as tb; tb.run("spline_to_verse_patrol")
import UEFN_Toolbelt as tb; tb.run("verse_list_devices")
import UEFN_Toolbelt as tb; tb.run("verse_bulk_set_property", property_name="bIsEnabled", value=True)

# Optimization
import UEFN_Toolbelt as tb; tb.run("memory_scan", scan_path="/Game")
import UEFN_Toolbelt as tb; tb.run("memory_scan_textures", scan_path="/Game")
import UEFN_Toolbelt as tb; tb.run("memory_scan_meshes", scan_path="/Game")
import UEFN_Toolbelt as tb; tb.run("memory_top_offenders", limit=20)
import UEFN_Toolbelt as tb; tb.run("memory_autofix_lods", scan_path="/Game/Meshes")

# API Explorer
import UEFN_Toolbelt as tb; tb.run("api_export_full")
import UEFN_Toolbelt as tb; tb.run("api_list_subsystems")
import UEFN_Toolbelt as tb; tb.run("api_search", query="material")
import UEFN_Toolbelt as tb; tb.run("api_inspect", name="EditorActorSubsystem")
```

### Adding a Text Input to a Button

When a tool needs a dynamic argument (file path, preset name, text string):

1. Add an **Editable Text Box** above the button.
2. In the button's **On Clicked** event, drag off the text box → **Get Text**.
3. **Convert Text to String** → feed into a **Format Text** node to build the command:
```
import UEFN_Toolbelt as tb; tb.run("text_place", text="{0}", location=(0,0,200))
```
4. Pass the formatted string into **Execute Python Command**.

### Opening the Dashboard Manually

```python
import UEFN_Toolbelt as tb; tb._try_open_widget()
```

Or pin it permanently: **Window → UEFN Toolbelt** (registers as a dockable tab).

---

## Community & Prior Art

This project builds on the first 48 hours of real-world community testing after the
UEFN Python v40.00 experimental drop (March 2026).

**API discovery vs. Execution: The "Superset" Architecture**

> **[KirChuvakov/uefn-mcp-server](https://github.com/KirChuvakov/uefn-mcp-server) · [@KirchCreator](https://x.com/KirchCreator)**
> Kirch pioneered the MCP bridge pattern for UEFN Python — the queue + Slate tick
> architecture that makes it possible to call `unreal.*` from an external process
> without deadlocking the editor. He built the foundational "API Ground Truth" tools
> (`dump_uefn_api.py`, `generate_uefn_stub.py`, `uefn_listener.py`). Full credit.
>
> **How UEFN Toolbelt differs:** Kirch's project focused on *Discovery* — mapping the API
> and teaching AI what exists. **UEFN Toolbelt is an Execution Engine** — 358 tools for
> actually building levels, built on top of that same bridge architecture. You don't need
> to run a standalone MCP server alongside Toolbelt — Toolbelt is the all-in-one superset.

**PySide6 UI approach inspired by:**
- Early PySide6 experiments — proving out polished Qt UIs in the UEFN Python space
- Community validations — confirming PySide6 install paths and event loop patterns

**Community requests that shaped this toolbelt:**
- Map Makers — Spline → Verse data converters (shipped as `spline_to_verse.py`)
- Device Programmers — Verse device property editing workarounds (shipped as `verse_device_editor.py`)
- Performance Testers — memory profilers, API explorers, level snapshots, prop patterns

---

## ⚠️ Critical: The "Main Thread Lock" Quirk

Because UEFN runs Python on the **main render thread**, there is a unique deadlock behavior you must avoid:

* **Async APIs**: Commands like `take_high_res_screenshot` or background asset saves are queued for the **next frame**.
* **The Deadlock**: If your Python script uses `time.sleep()` or a long loop to "wait" for a file to appear, you are **blocking the main thread**. Unreal cannot finish the current frame, so it will **never write the file** while your script is waiting.
* **The Solution**: Trigger the action and exit. Do not attempt to verify file existence in the same execution block if the engine needs to tick to produce that file.

## 🚀 Automated "Hands-Free" Verification

The UEFN Toolbelt includes a sophisticated integration test suite that validates tools **programmatically**.

* **How it works**: The `integration_test.py` script uses the Unreal Python API to remote-control the editor. It spawns actors, modifies transforms, applies tags, and captures screenshots—verified against the engine's internal state.
* **What it tests**: It validates the "Logic & Engine" layer (Layer 3). While it doesn't click the physical buttons on the Pyside6 UI, it proves that the toolbelt's brain is working perfectly.
* **Benefits**: Run 12 tests in under 5 seconds (excluding screenshots) to ensure no regressions were introduced.

---

## 🏎️ Optimized Development Workflow (Hot Reload)

You **never need to restart UEFN** when developing for the Toolbelt.

1. **Edit** your Python code in your IDE.
2. **Run** `deploy.bat` to sync files to your UEFN project.
3. **Paste** the right command from the table below into the UEFN Python console.

> [!WARNING]
> Always include `tb.register_all_tools()` after the module pop. If you skip it the registry will be empty and every tool call will fail with "Unknown Tool".

### Quick-Reference Command Table

Copy the command that matches what you're doing right now:

| What you're working on | Command to paste in UEFN console |
|---|---|
| **Standard dev — open dashboard** | `import sys; [sys.modules.pop(k) for k in list(sys.modules) if "UEFN_Toolbelt" in k]; import UEFN_Toolbelt as tb; tb.register_all_tools(); tb.launch_qt()` |
| **Verse Device Graph window** | `import sys; [sys.modules.pop(k) for k in list(sys.modules) if "UEFN_Toolbelt" in k]; import UEFN_Toolbelt as tb; tb.register_all_tools(); tb.run("verse_graph_open")` |
| **Run integration tests** | `import sys; [sys.modules.pop(k) for k in list(sys.modules) if "UEFN_Toolbelt" in k]; import UEFN_Toolbelt as tb; tb.register_all_tools(); tb.run("toolbelt_integration_test")` |
| **Quick smoke test only** | `import sys; [sys.modules.pop(k) for k in list(sys.modules) if "UEFN_Toolbelt" in k]; import UEFN_Toolbelt as tb; tb.register_all_tools(); tb.run("toolbelt_smoke_test")` |
| **Sync docs + Verse IQ** | `import sys; [sys.modules.pop(k) for k in list(sys.modules) if "UEFN_Toolbelt" in k]; import UEFN_Toolbelt as tb; tb.register_all_tools(); tb.run("api_sync_master")` |
| **Headless reload (no UI)** | `import sys; [sys.modules.pop(k) for k in list(sys.modules) if "UEFN_Toolbelt" in k]; import UEFN_Toolbelt as tb; tb.register_all_tools()` |

All of these also appear in `deploy.bat` output — after deploying, just copy from the terminal.

---

## 🛠 Troubleshooting

| Problem | Solution |
|---|---|
| `ModuleNotFoundError: UEFN_Toolbelt` | Files not copied correctly OR this is a new project. **UEFN only scans `Content/Python` on startup.** If you just deployed to a new project, restart UEFN. |
| `Qt dashboard is blank / doesn't open` | PySide6 not installed. Run `pip install PySide6` using the **UEFN Python exe** at `/Engine/Binaries/ThirdParty/Python3/Win64/python.exe`. |
| `ImportError: No module named PySide6` | Same as above. Pip installing into your system Python won't help — must use the UE Python binary. |
| Python settings not sticking | Enable Python in **Edit → Project Settings** (not Preferences) — UEFN only has **one** Python checkbox, not three like full UE. Tick it, restart. |
| Material instances not appearing in viewport | Missing `update_material_instance()` call — already fixed. Check `PARENT_MATERIAL_PATH` in `material_master.py` points to a real asset. |
| `spawn_actor_from_class returned None` | No level is open, or world context is unavailable. Open a level first. |
| Blueprint widget doesn't open | Create `WBP_ToolbeltDashboard` in `Content/UEFN_Toolbelt/Blueprints/` — or just use the PySide6 dashboard instead. |
| Property set fails on Verse device | Property may be read-only in-editor or the name differs. Check the Details panel spelling. |
| FBX import produces no assets | Check Output Log for FBX parse errors. Use forward slashes in file paths. |
| Scatter actors not visible | Check the mesh path is valid — tool falls back to `/Engine/BasicShapes/Cube` if it can't find the mesh. |
| Something isn't loading after an update | Module may be cached. Paste: `import sys; [sys.modules.pop(k) for k in list(sys.modules) if "UEFN_Toolbelt" in k]; import UEFN_Toolbelt as tb; tb.launch_qt()` |
| LOD generation fails | `StaticMeshEditorSubsystem.set_lods_with_notification` requires the mesh to be fully loaded. Run `load_asset()` first. |
| Menu bar entry missing | `init_unreal.py` failed silently — check Output Log for `[TOOLBELT]` error lines on startup. |

---

## Performance Tips

- **Slow on large selections?** `with_progress(actors, "label")` gives a cancellable progress bar — already used in all bulk tools.
- **Accumulating material instances?** They stack in `/Game/UEFN_Toolbelt/Materials/Instances/`. Periodically delete unused ones via the Content Browser.
- **Arena generation slow?** Increase `tile_size` in `ARENA_PRESETS` so fewer tiles are needed for the same area.
- **Dense scatter hanging the editor?** Use `scatter_hism` instead of `scatter_props` for counts above ~500 — it's one actor instead of N.
- **Spline sampling too slow?** Reduce `sample_count` in `spline_to_verse_points` — control points (default) are instant.

---

## MCP — Connect Any AI to UEFN