> Public copy of the guide that ships in the Advanced Framework Pro plugin under `Docs/AI/`. The `Reference/` it mentions is this documentation, every page also as Markdown (see https://docs.human-codeable.com/llms.txt); the `Codex/` pseudo-code ships with the plugin only.

# Advanced Framework Pro — for AI assistants

You are working in an Unreal Engine 5.8 project that uses Advanced Framework Pro
(AFPro), a cross-platform interaction and UI framework. One set of content runs in VR,
on desktop and on mobile, with no platform-specific blueprints.

Read this page before changing anything in a project that uses AFPro. It is short on
purpose: its job is to give you the framework's mental model, the rules that keep your
changes working, and a way to look up anything else without reading the whole plugin.

This file ships inside the plugin, in `Docs/AI/`. Everything else you need sits beside it:

- **`Reference/`** — the framework documentation as Markdown. `plugins/<system>.md` is one
  page per system: its assets, what each one is for, and its public API. `assets.md` and
  `assets.json` list every asset in one place. `concepts/`, `guides/`, `workflows/`, `start/`
  and `reference/` (FAQ, glossary, troubleshooting) cover the rest.
- **`Codex/`** — pseudo-code of every framework blueprint, enum, struct and data asset:
  declarations and every graph, one file per asset, mirroring `/AdvancedFrameworkPro/`.
- **`VERSION`** — the plugin version these files describe.
- **`UpgradeGuide.md`** — moving a project from the legacy Advanced Framework (`/Game/AFCore`)
  to AFPro: preparation, mapping tables, recipes, pitfalls. Read it only for such an upgrade.

Read the page for the system you are touching, not all of them.

**Connected to the editor but unable to read this folder?** A Fab install puts the plugin
under the engine, outside the project. The `AFProGuideTools` toolset reads it for you:
`get_guide` (this file, or one section), `list_ai_docs`, `read_ai_doc`, `search_ai_docs`.

**New project or new feature?** Do not start building. Follow "Planning a project: work in
phases" below: brief, design, review gate, then build. "Working with the user" says where
your files go and how you report; "Performance" says how the budget is set and measured. The
user does not have to repeat any of it in a prompt.

The plugin also ships worked examples, the showrooms in `AF_Examples`. When a
recipe is unclear, open the example that does the thing rather than assembling it from
first principles: every one of them is a working arrangement of real components.

---

## The mental model: three sides to every interaction

Almost every mistake an assistant makes in this framework comes from missing this. An
interaction is never one component. It is an **ability** on the thing that acts, an
**interaction** on the thing being acted upon, and — where several actors or hands can
act on one object — a **coordinator** that reconciles them.

**Abilities** live on the pawn or on one of its motion controllers. `Controller` in
`BPC_ControllerAbility_*` means the motion controller, not the player controller — those
components sit on the `BP_MotionController_*` blueprints the pawn owns, one per hand.
`BPC_PawnAbility_*` sits on the pawn itself, and `BPC_ActorAbility_*` on any other actor
that needs to reach out. Nothing lives on the player controller: `BP_GameMode` ships the
engine's stock `PlayerController` and AFPro adds nothing to it. They are the side that
reaches out: `BPC_ControllerAbility_Hand_Grab` traces from the palm for
something to hold, `BPC_PawnAbility_Teleport` moves the player, `BPC_ControllerAbility_Laser`
points.

**Interactions** live on the object and answer. They are named `BPC_Interaction_*`:
`BPC_Interaction_Grip` makes its owner grabbable, `BPC_Interaction_Select` makes it
clickable, `BPC_Interaction_Teleport` makes it a teleport destination,
`BPC_Interaction_Overlap` and `BPC_Interaction_Gaze` do the obvious. An object with no
interaction cannot be touched by any ability, however the ability is configured.

**Coordinators** sit on the actor and arbitrate. `BPC_PickupCoordinator` is the one you
will meet: an actor with two grip points can be held by two hands, and the coordinator is
what decides the resulting transform, fires once on first pickup rather than per hand, and
applies release physics when the last hand lets go.

To make something grabbable you therefore need a mesh, a `BPC_Interaction_Grip_Pickup`,
and a `BPC_PickupCoordinator` — and the hand must already have a grab ability. Adding the
interaction alone does nothing. The `Reference/` pages give the component combinations
that actually ship, per system.

## Source Info: who did this

`ST_SourceInfo` threads through every interaction in the framework. It carries the
interacting pawn, the ability acting, and an identifier. Read it to find out who acted;
pass it on when you call into the framework; never invent one.

It is what lets a haptic response go to the hand that pressed rather than both, what lets
an interaction tell two players apart, and what a replicated state component reports when
a value changes. If you are writing a handler and wondering which player triggered it,
the answer is in the Source Info you were handed.

## State components: values that replicate and announce themselves

`BPC_State_Boolean`, `_Float`, `_Color`, `_Integer`, `_Name`, `_Trigger` and `_Drag` hold
a value, replicate it, and fire a dispatcher when it changes. Add one rather than writing
a replicated variable and a repnotify by hand.

They follow a convention worth knowing: `OnValueUpdated` fires once per change, and the
float and colour components also offer `OnValueUpdated_Interpolated`, which fires every
frame while the value travels to its target. Bind the first to react to a change, the
second to follow it. Binding both applies the change twice.

**Settings values and where they are live.** A `BP_Value_Settings_*` class may name a
Reference Class; the value exists only while a component of that class is registered with
the game state's settings component, which for locomotion, turn and teleport values means
a VR hand. A row for such a value on a desktop pawn gets an owner-less object and the
control throws at start-up. Before putting a settings value on a panel, read its Reference
Class and make sure that component exists on every pawn the panel will be seen from, or
pick a value with none (theme, volumes, graphics).

**The value contract.** Every UI control that edits a value carries one `Assign*Object`
call — `AssignBooleanObject` on a toggle, `AssignFloatObject` on a slider,
`AssignColorObject` on the colour picker — that binds the control to a value object in both
directions: it displays the value, writes it with the pressing pawn's Source Info, and
follows later changes through the value's event listener. A state component is such a
value object. One call per control; no event wiring, no polling, no refresh. The widget is
bound to a value, never to an actor, which is why the same control can be pointed at any
state of that type.

**Where the values live.** Only values that something outside the widget reacts to (a lamp, a mesh, another panel) are state components; a panel's own view state (a filter, a page, a selection used only for display) stays inside the widget, and the element gets `Should Replicate` so every player sees the same view. Put the state components on the actor that reacts to them —
the lamp, if the lamp is what changes — give each a component tag, and react with the
component-bound `OnValueUpdated` (or `_Interpolated`) events on that actor: one event, one
reaction, nothing at BeginPlay, since a state component applies its default with notify
when play begins. A widget reaches the states with `Get Reference Component (key, component
tag)`: the actor comes from the widget's reference map, the component from its tag. The
widget never talks to the reacting actor, and the actor never knows about the widget.
Binding another actor's states by hand (find, cast, assign delegate, apply current) is the
exception for a consumer that cannot own them, not the pattern.

Titles and captions on a state component are shown automatically by the
`WBP_SettingItem_*` rows; a hand-built panel labels its rows itself until `WBP_InfoRow`
gains its assign-state function.

`WidgetElement_PostInitiate` is the hook for widget setup that needs the tree, the theme
and the widget component in place: it fires when the widget has finished initialising.
`WBP_Base` has no logic in it, so override it without calling the parent.

## Latches: a hand driving a constrained object

A pickup detaches the object and carries it; a latch leaves it where it is and lets the
hand drive it along a constraint. A latched control is two components on the owning actor:
`BPC_Interaction_Grip_Latch_Drag` is *how you grab it* (the hand takes hold at the mesh's
`AttachPoint` socket, or a `BPC_Socket_AttachPoint`), and `BPC_State_Drag` is *what it
means* — the value, with its drag type (slide along an axis, turn about an axis, or a
spline), value range, display range, default, snap mode and sections. They meet through a
tag: the latch's Drag Tag names the drag states it drives, the drag state's Tag To
Controlled Component names the mesh it moves.

Pick the drag type by how the hand holds the thing: a lever or anything with an arm takes an
Angle type (the value comes from where the hand is around the pivot); a knob or dial whose
attach point sits on its own axis takes a Rotation type (the value comes from the hand's
twist) — an Angle type on a knob does nothing. A drawer or slider takes a Linear type along
the owner's axis; build the control along the actor's own axes and turn the actor, not a
mount. One latch drives every drag state carrying its tag *at once*; that is for several axes of
one handle. Independent controls each get their own latch and their own tag, with Relevant
Components set to their mesh so the right one is grabbed. The drag state is a value object
like any state component (title, caption, event listener), and it announces itself with
`OnDragStateChanged_Constant` (every move, with the percent of the range),
`OnDragStateChanged_Section` (entering or leaving a section) and `OnSnappingCompleted`
(when it lands on a section — where a switch's real action belongs). Default Value is the
resting pose shown in the editor and is not applied at start-up: pose the mesh to match it.
Leave a section's Pull at 0 unless you have tested it on that control: a pull towards the
snap value while the hand still holds the handle fights the hand, and a drawer with two
pulling sections felt broken until the pull was removed. Snapping on release is what you
usually want.

## Overlap: touching instead of reaching

Select reaches across the room; overlap happens only where a hand is. The hand side is
`BPC_ActorAbility_Overlap` on each motion controller, carrying nothing but an identifier
(`Controller_Left`, `Controller_Right`) and Source Info. The object side is
`BPC_Interaction_Overlap` (reports `OnOverlapStart` / `OnOverlapEnd` with the ability that
touched, plays its sounds, nothing else) or `BPC_Interaction_Overlap_Drag`, which starts a
drag on contact and hands it to a `BPC_State_Drag` on the owner — the same drag state a
lever uses; the value does not care what moved it. Both pick their collider by Component Tag
Of Collision and accept only the identifiers in their allowed list: an identifier not on the
list is ignored silently, and the finger goes straight through. A pressable button is the
cap mesh (overlap events on, tagged) plus an overlap drag plus a drag state with a short
linear travel, Snap Reset and one section at the bottom; act on entering that section, not on
every move. The cap's collision must *overlap* the hand, which is a physics body: the shipped
cap uses the OverlapAll profile, query only. A fresh mesh's default blocking profile makes the
hand collide instead and nothing happens, with no error; if a Select shares the mesh, keep
Visibility blocking so the laser still hits. Interactions stack: the same cap can carry a Select for the laser and an Overlap
Drag for the finger with no conflict.

## Gaze: acting by looking

Gaze is a pawn ability, not a per-object one: `BPC_PawnAbility_Gaze` on the pawn reads the
pawn's existing trace each tick (nothing new is aimed) and applies to everything in the
room at once. The object side is `BPC_Interaction_Gaze`: leave Relevant Components empty so the whole
object counts (a dwell on a small cap resets each time the trace drifts onto the housing) and
narrow the highlight with the highlight tag instead; keep Build-up Time short (it is the delay
before the ring even appears); set Trigger Time (one to two seconds; shorter fires on a glance,
longer reads as broken) and the indicator widget (circular by default, linear shipped,
subclass for your own and set its desired dimensions). It fires `OnGazeTriggered` with the
Source Info of the looker. To refuse a look from the object's own state, implement
`Interface_Gaze` and answer `CanGazeView`; a refused gaze shows no ring at all. Select,
overlap and gaze stack on one object without knowing about each other — route all three
into one function.

## Graphs you write

Events call functions; functions hold the logic. An event graph should read as one call per
event, so the person recording can see the shape at a glance. After writing a new graph,
compile, then arrange it with the project's layout tool
(`codeable_ai_editor_tools.graph_layout.GraphLayoutTools.arrange_graph`, fed the `get_node_infos`
JSON of all nodes of the graph, comments on) before saving — the writer drops nodes where it
can, not where a person would put them. The tool lays out one block per event with a titled
comment box; re-runs replace its own boxes and leave hand-made ones alone.

**Arrange only what you wrote.** A graph a person laid out keeps that layout. Put new nodes in
free space next to the nodes they connect to, and never run the layout tool (or any other
auto-format) on a graph you did not write from scratch.

Three things about the DSL writer that cost time to learn. `write_graph_dsl` on a *function*
graph appends to what is there; delete every node but the entry first, or the old chain stays
as an orphan. `(return x)` is positional, never `(return :Pin x)`. Nodes whose first pin is
`self` (`SetWorldRotation`, `SetRelativeRotation`, the Transformation family) need keyword
arguments, because a positional value lands on `self`. A new child Blueprint already carries
`BeginPlay → Parent:BeginPlay` and `Tick → Parent:Tick`; the DSL can read a parent call but not
write one. Write the override without it, then add it with `add_parent_call` (below).
Variables given a category move their node ids (`Variables|<Category>|GetX`). A component template inside a child Blueprint does not pick up
new class defaults you set on the CDO afterwards; set the instance too.

## Rules

**Bind dispatchers; do not tick.** The framework is careful about tick: components enable
it when they have work and disable it when they do not. Every system announces what it
does through event dispatchers — the `Reference/` page for a system lists them with what
fires each one. Polling a state component every frame is both slower and later than
binding to it.

**Respect the server.** Anything replicated goes through a `Server_*` event or a
repnotify, and the pattern is consistent: act locally for responsiveness, tell the
server, let the server's replication reach everyone else. If you add a path that changes
shared state, follow the same shape rather than changing state on a client and hoping.

**Build on the interaction, not around it.** If you want an object to do something when
grabbed, bind `OnGripped_Start` on its grip interaction. Do not reach into the hand's
ability from the object, and do not trace for the hand yourself.

**Forwarded input is how a held object gets controls.** A held object does not read the
controller. Its grip interaction broadcasts `OnButtonForward_Key`,
`_TriggerAxis`, `_GripAxis` and `_Thumbstick` while the holding ability forwards what the
player presses. The gun example fires from `OnButtonForward_Key`. This is also why an
object can be held in either hand and still respond to the right one: the Source Info
says which.

**No collision setup is needed.** AFPro declares no custom collision channels. Every
trace in the framework runs on an Unreal default: Visibility for pointing - grabbing, the
laser, the finger, widget interaction, teleport aim, touch input - and Camera for a couple
of world traces. So a project using AFPro needs no channel configuration, and an object
that a pointer never hits is missing an interaction component or facing a hand with no
ability, never a missing channel.

**Check which level is loaded before placing anything.** `add_to_scene` puts the actor in
the level that is currently open, which is not necessarily the one you have been editing. Put
one into a plugin's example map by accident and you have modified framework content — the one
thing the boundary rule forbids. Ask what the current level is first. For the same reason,
never save every dirty asset at once: name the assets you mean to save, or a plugin asset that
happens to be dirty gets written with them.

**Compile and verify after every change.** Compile the blueprint you touched. Then check
the thing you claimed to change — read the value back, run the interaction, count the
result. A report that says a change was made is not evidence that it landed where you
meant; confirm it against the asset.

**Import descriptions from a named file, never from a folder with "replace".** A Codeable AI Editor Tools
import of a whole corpus folder with "Replace descriptions the asset already has" rewrites every
member whose corpus text differs from the asset - and the corpus can carry text that is not the
member's own. Build a file with exactly the rows you mean, pick that file in the dialog, read the
report's counts before saving anything, and restart without saving if they are not what you
expected.

**Give the user clear instructions.** Anything only the user can do — a restart, a build, a
Perforce checkout, a VR test, a click in a modal window, an approval — goes in its own block,
marked so it cannot be missed (here: `> **Your turn:**`), never inside a paragraph. Say exactly
what: the menu path, the button, the file, the command to paste, the value to enter. Number the
steps when there is more than one, in the order they must happen. Say what to send back
("paste the error", "tell me when the editor is open") and what you will do next, so the user
knows the hand-off. If something must not be done (save all, two editors open), say so in the
same block. A decision you need goes as one clear question with the options, not as a hint at
the end of a report.

**Say what you checked.** If the answer is not in the reference and not in the project,
say so rather than inferring it from a name. A name in this framework is a strong hint
and not a fact: `ST_Drag_Position.Positions` holds a single float, and
`Socket Out Of Range` is a function that returns a socket.

## Working with the user

These rules hold in every project on AFPro, whether or not the user's prompt repeats them.
The project's own `AGENTS.md` adds to them and wins where it says something else. All paths
below are relative: `/Game/...` is a content path, everything else is relative to the
project root. Never write a machine's absolute path into a file or an asset.

**Do the work yourself; ask a person only for what a person has to do.** With the editor
open (its MCP server running) and, for modelled props, Blender open with the Codeable AI Editor Tools
Bridge, the tools take a project from an empty folder to a finished, tested level: levels,
interfaces, renames, project configuration, meshes, sounds, UI. Ask a person only when the
step needs one and leaving it out would change the result, or doing it by tool would be
unreasonably harder: decisions and approvals, feel and comfort in the headset, restarting the
editor, opening a program, anything on hardware (deploying to a headset, measuring on it).
A step you cannot do by tool is a gap in the tools: do what you can around it, and name it in
the hand-over instead of quietly handing it to the user. "What an assistant cannot do" lists
the steps that remain.

**Your files have fixed places.**
- Assets go in one folder named after the project, `/Game/<Project>/` (for example
  `/Game/EscapeRoom/`), in subfolders by kind (`Blueprints`, `Maps`, `Materials`, `Meshes`,
  `UI`, `Audio`, `Data`). Nothing goes into a plugin: not AFPro, not Codeable AI Editor Tools, not any
  other (see "The boundary").
- Source art (`.blend`, PNG, SVG, FBX, WAV, and the scripts that generate them) lives outside
  `Content`, in `SourceArt/` in the project root, mirroring the content path:
  `SourceArt/<Project>/Meshes/SM_Key.blend` for `/Game/<Project>/Meshes/SM_Key`.
- The brief and the design (`BRIEF.md`, `DESIGN.md`) sit in the project root.
- A work log, `AGENT_LOG.md` in the project root: one entry per step, written in the same
  step, naming every asset created, changed, moved or re-imported. It answers "who made this
  and why" weeks later.

**Never delete.** Move what nobody needs any more to `/Game/_to_delete/` (files into a
`_to_delete` folder beside them); the user empties it. A move can be checked and undone; a
deletion cannot.

**Save by name, and never during Play.** Save only the assets you changed, by path. Do not
save, compile or edit assets while Play or Simulate runs; stop it first.

**Describe everything you make.** Every Blueprint (class description), variable (tooltip),
function (description), event and event dispatcher gets one plain sentence on what it is
for. The person reading the graph, the Codex export and the next assistant all depend on it.

**Check the placed instances, not only the Blueprint.** After changing a Blueprint, read the
values of its actors in the level back; they can keep old values (see "What an assistant
cannot do").

**Build in segments.** One room, station or puzzle at a time. Each segment runs on its own and
can be started on its own, and is tested before the next one builds on it.

**Report plainly.** Short replies in plain language: what you did, what you checked, what you
need. No internal paths, tool names or log dumps unless asked. Every step that changes what a
player sees or touches ends with click-by-click test steps (which map, which play mode, what
to press, what should happen) in the `Your turn` block described under "Rules".

**Source control is the user's.** Files you change may be checked out for you; submitting is
the user's call. Do not submit, and do not remind the user to.

## Levels: four things, not one

Usually you are not making a level. You are being handed one — the map the project has
been built in — and asked to make AFPro work in it. Either way the work is the same four
pieces, and missing any one of them gives a level that opens with the wrong pawn or none.

1. **The map**, with a `BP_PlayerPosition` actor where the player should start. Start from
   an **Empty Level** (`LevelTools.create_level` from Codeable AI Editor Tools, or **File > New
   Level > Empty Level** without it) rather than Unreal's Basic template: the template's own Player Start
   collides with the marker, since `BP_PlayerPosition` is itself a `PlayerStart` subclass
   and with two of them the engine chooses between them. If you are converting a map that
   already has one, delete it. The pawn appears at the marker's exact location and yaw. `Default Position` marks the fallback used when
   no marker matches the joining player's index, and `Player Index` is how you give each
   player their own.
2. **A `PDA_Level` data asset**, one per level, with `Persistant Map` pointing back at the
   map. It names the game mode (left empty, the persistent map's World Settings GameMode
   Override applies, or the project's default game mode), and a pawn class for each platform:
   `Default_Pawn_Desktop`, `Default_Pawn_VR`, `Default_Pawn_Mobile`. `PawnSelect` is
   `Dynamic` by default, which resolves VR first, then mobile, then desktop; the Force
   options override that for testing. An empty pawn class for the platform you are on
   means the level loads with no pawn at all. What you get instead is the loading helper
   pawn, still possessed: the world renders, the view works, and nothing else does. Measured
   twice. This is the failure to recognise first when a level "works but the player cannot
   move" — it is a configuration gap on the data asset, not a movement problem, and it
   produces no error of any kind. `Streaming Maps` holds the maps loaded in under this one; a non-empty list is what
   asks for a loading transition. The example levels stream `Map_Showroom_Env` into theirs,
   which is plugin teaching content — a project that copies that pattern ships the showroom
   inside its own game.
3. **A `BP_MapInfo` actor in the map**, with its `Level Key` set to that data asset. This
   is what ties the two together: the loading library looks it up the first time anything
   asks which level is running, and caches it. A map without one has no level
   configuration, whatever data assets exist.
4. **A game mode that will actually be in play.** The data asset's `GameMode` rides the
   travel URL, so it applies when the loading system travels to the level and not when the
   map is opened directly. Pressing Play on a map uses `World Settings ▸ GameMode Override`
   if it is set, and the project's default game mode otherwise — so one of those two must
   name a game mode whose default pawn is the loading helper. Measured: with the override
   cleared and the project default left at the engine's stock `GameMode`, the map plays
   under that and spawns an engine `DefaultPawn`; with the override set, the log says
   `Game class is 'BP_GameMode_Tutorial_C'` and the pawn from the data asset appears at the
   marker. Set the override per level, or the project default once — but check that one of
   them is true before concluding a level is broken.

The game mode to name is `BP_GameMode` from `AFS_Core_Loading`, or a subclass of it. Its
default pawn is deliberately the loading helper rather than your pawn: the helper runs the
transition, then spawns the real pawn from the data asset and goes away. Subclass it rather
than the engine's Game Mode when a project needs its own game state or player controller.

**Converting a level that already exists** is the common case, and it is subtractive as
much as additive. Nothing in the map has to move. Add the marker where the player should
stand and delete the Player Start it replaces; add the Map Info; make the data asset and
point it back at the map; set the World Settings override. Anything the map already
contains — its geometry, lighting, its own actors — carries on working, because AFPro
takes over the pawn and the game mode and nothing else. If the project has its own game
mode, reparent it to `BP_GameMode` rather than replacing it — `BP_GameMode` derives from
the engine's `GameMode`, so a game mode built on `GameModeBase` gains the match-state
machinery when it moves.

Three instincts to resist, because all three are the ordinary Unreal answer and all three
are wrong here. Do not place a pawn in the level. Do not leave the map's Player Start in
place. Do not expect the pawn to come from World Settings — only the game mode does.

For a desktop-only test level, `BP_Pawn_Desktop` is the plain keyboard-and-mouse pawn and
`BP_Pawn_Desktop_Char` adds a visible body. `PawnSelect = ForceDesktop` keeps it desktop
on a machine with a headset attached.

**The base pawns cannot grab or click.** `BP_Pawn_Desktop`, `BP_Pawn_Mobile` and `BP_Pawn_VR`
(and their `_Char` children) carry only teleport, and the VR pawn's `BP_MotionController_Controller_*`
carry no hand abilities. A level built on them opens, but nothing in it reacts. Give the player a
pawn with the abilities the level needs: a child of the base pawn with `BPC_PawnAbility_Select`
(AFS_UX_Select) and `BPC_PawnAbility_Grip` (AFS_UX_Grip) for desktop and mobile, and motion
controllers carrying `BPC_ControllerAbility_Hand_Grab` and `BPC_ControllerAbility_Hand_Laser` for
VR. The showroom pawns in `AF_Examples/Pawn` (`BP_Pawn_Desktop_Char_Showroom`,
`BP_Pawn_Mobile_Char_Showroom`, `BP_Pawn_VR_Char_Showroom` with
`BP_MotionController_Hands_*_Showroom`) carry these already and are fine for a quick test, but
they bring every showroom ability; add only what the level uses.

The example and showroom levels are poor templates to copy. They use `PDA_Level_Showroom`,
a subclass carrying the name, description and images the showroom's navigation menu needs,
and they stream the showroom environment map. A level in your own game wants the plain
`PDA_Level`.

**Several rooms, one persistent map — the examples' pattern.** A loading transition only plays
when the level data asset lists Streaming Maps, and the loader finds each with Get Streaming
Level, so each must be a Blueprint-loaded sub-level of the persistent map. The examples put every
room under one nearly empty persistent map (`Map_Examples`: World Settings with the game-mode
override, nothing else); each room is a sub-level carrying its own `BP_MapInfo`, `BP_PlayerPosition`,
floor and content; the shared environment is one more sub-level; every room's `PDA_Level` names
the shared persistent map and streams environment + room. Travel between rooms then always shows
the transition. Travel itself is the `Loading_SwitchLevel` macro (`ML_Loading`, actor graphs
only; `ML_Comp_Loading` in components) with the target `PDA_Level`. The player controller needs
`BPC_PlayerController_CameraFade`, or the switch's fade step logs Accessed None (the framework's
controllers carry it; a plain engine PlayerController subclass does not).
Tools (Codeable AI Editor Tools): `codeable_ai_editor_tools.levels.LevelTools.create_level`, `add_streaming_level`,
`split_into_streaming_level` (pass `keep_class_names='BP_MapInfo_C,BP_PlayerPosition_C'` so the
map info and player position stay in the persistent map).
The persistent map itself is not playable — it has no map info. Press Play on a room (the
examples open `Map_Examples_Lobby` in the editor); a packaged game enters through a start-up map
that switches into the first room (`Map_Examples_Intro`, the examples' GameDefaultMap).

## Hiding, fading and handing out

**Show and hide through `BPC_Visibility_Switch`, never Set Hidden.** Add the component and call
its `SetVisibility` / `ToggleVisiblity`; it fades the meshes through a hologram copy instead of
popping, and by default also turns off collision and tick while hidden. `tagSelection` limits it
to meshes carrying `meshSelectionTag` (`IncludeOnlyTag`) or spares them (`ExcludeWithTag`). For an
object that starts hidden, set Actor Hidden in Game. Bind `OnVisibilityChanged` for anything that
should follow it (a label to deactivate, a button colour); a label that should only appear with
the object needs Auto Activate off.

**Anything that gets deleted carries a visibility switch.** The framework's delete
(`Delete|DeleteActor`, and the spawner's despawn) uses the switch when the actor has one, so the
object fades out instead of vanishing. A spawned prop without it disappears abruptly. Delete
through the framework, not Destroy Actor.

**The spawner.** `BPC_Spawner` (a scene component) shows a pickup dummy (`meshRepresentation`);
grabbing it spawns `spawnClass` in the hand and a new dummy appears — at once, or only after the
last one is gone with `singleInstance`. `despawnTimer` is the delay after release (negative keeps
it). Place the component where the dummy should stand (a temporary mesh helps). The player
controller needs `BPC_PlayerController_ReplicatedSpawner` (the framework's controllers carry it).
For an inventory inside a menu tab, the overlay spawner widget does the same and hides its dummy
when the tab or menu closes.

## The boundary: your work is not in the plugin

Everything AFPro ships lives in the plugin. Everything you make lives in the project's own
`Content`. You subclass framework classes, you never edit them — which is what makes an
update a replacement rather than a merge. If you are about to change an asset under
`/AdvancedFrameworkPro/`, you are about to make the customer's next update painful; make a
subclass in `/Game/` instead.

## The game classes, and the one thing that is actually required

**AFPro requires no particular game mode, game state, player controller or player state.**
All of its capability arrives as components you add to whatever classes a project already
has, which is what lets it drop into a project that has its own. If you are joining an
existing project, do not replace its game classes.

There is exactly one hard requirement, and it is easy to miss: **the game mode in play must
have `BP_Helper_Pawn_Loading` as its default pawn.** That helper is what runs the transition
and then spawns the real pawn named on the level data asset. A game mode without it never
starts the chain, and the symptom is silent — measured here, a level under the engine's stock
`GameMode` simply spawns an engine `DefaultPawn` and nothing of the framework runs.

Subclassing `BP_GameMode` from `AFS_Core_Loading` is the easy way to satisfy that, and it
brings seamless travel already enabled, so it is the right default for a new project. It is a
convenience, not a dependency.

A project typically makes four classes of its own early and leaves them empty — game mode,
player controller, game state, player state — then sets the last three on the game mode. The
framework ships no base for the controller, game state or player state, so those subclass the
engine's. Capability arrives later as components: the camera fade and the replicated spawner
on the controller, the theme manager, the settings and the project info on the game state.

**Leave them empty until something needs them.** The temptation is to copy the component set
off `BP_GameState_Showroom_Examples` and `BP_PlayerController_Showroom_Examples` in one go, because those are
the shipped examples and their descriptions invite it. Resist it: add each component when you
introduce the system that needs it, so a project carries nothing it does not use. The player
state in particular stays empty for a long time — it exists from the start because swapping
the class in later means re-testing everything that spawns.

## Reading the folder names

One plugin, forty-three modules, sorted into six categories, and the prefix tells you which
one a folder sits in. The categories form a dependency hierarchy: a category may depend on
the ones below it and never on the ones above. Bottom to top:

- `AFA_` **assets** — colours, sounds. Raw material, depends on nothing.
- `AFL_` **libraries** — function libraries, used from above rather than from inside the stack.
- `AFM_` **modules** — self-contained capabilities: abilities, settings, state, spawner,
  visibility, camera fade, replication. Fourteen of them.
- `AFS_` **systems** — the framework proper, built on the modules: `AFS_Core_*` for loading,
  pawn, motion controllers, snapping and highlight, `AFS_UX_*` for interaction, `AFS_UI*` for
  interface. Twenty-one.
- `AFU_` **utilities** — debug and intro.
- `AF_Examples` — the showrooms: teaching content, not framework.

**And above all of them sits the project's own content.** That is the top tier, which is why
the boundary rule is not an extra instruction to remember: your work is above the framework
in the hierarchy, so it may depend on anything in the plugin and nothing in the plugin may
depend on it. Editing a plugin asset inverts that, which is what makes the next update a
merge instead of a replacement.

This is worth more than it looks. It tells you what an asset may depend on without opening
it, it tells you where a new thing belongs, and it tells you which whole areas you can ignore
for the question in front of you. If you are looking for where a capability lives, the prefix
narrows it before any search does.

A practical note for reading the project at all: **plugin content is hidden in the content
browser by default**. If the plugin looks empty, that setting is why, not a broken install.

## Moving the player

Three abilities, and they form a dependency ladder — each needs everything the one above it
needs, plus one thing more:

- **`BPC_ControllerAbility_Movement`** — thumbstick locomotion. Needs nothing else. Movement
  Direction reads the stick against the camera or against the controller; Movement Speed
  scales it.
- **`BPC_ControllerAbility_Turn`** — snap turning. Needs the pawn's teleport ability, because
  **a snap turn is a teleport in this framework**, which is also why the fade matches one.
  Without `BPC_PawnAbility_Teleport_Character` on the pawn, nothing turns at all. The shipped
  pawns already carry it; a pawn built from scratch will not.
- **`BPC_ControllerAbility_Teleport`** — the arc. Needs the pawn's teleport ability and a rule
  about where landing is allowed.

The shipped convention puts **movement on the right hand, turning and teleport on the left**.
Desktop and mobile need none of this: the desktop pawn's mapping context already moves it, and
the mobile pawn teleports on a double tap on ground within thirty degrees of level.

**An ability component's own transform matters, and this is easy to miss.** Adding the
component is enough to make an ability work — but the teleport arc originates from the
component and fires along its local +X. Added fresh it sits at the actor origin with no
rotation, which puts the arc in the wrong place pointing the wrong way. It has to be moved
onto the hand and turned so its red axis points forward. The shipped controllers attach it to
the `palm_l` socket. This is a viewport judgement, not a value to compute: if you are working
without a headset, set it approximately and say that it needs checking in VR.

**Choosing where a player may land.** `Teleport Mode` defaults to `TeleportOnAnySurface`, and
that default is wrong for most real applications: anything facing upwards becomes a
destination, so players teleport onto tables, shelves and the tops of walls. The alternatives
are a nav mesh, or `None` plus explicit destinations — which is the framework's own
recommendation. `BP_TeleportLocation` is a fixed spot, `BP_TeleportPlane` and `BP_TeleportSpline` a
region, and `Use Rotation` on a location dictates the facing the player arrives with.

**But those two actors are only the basics.** The actor itself does almost nothing: what makes
something a destination is `BPC_Interaction_Teleport` on it. Put that component on any object —
a chair, the seat of a car — and it becomes somewhere the player can teleport to, with its own
settings for attaching to the target, arriving at ground or eye level, the fade and duration,
and whether it forces a rotation. The same capability-by-presence rule as everywhere else:
the component is the capability.

Two comfort settings worth setting deliberately rather than inheriting. `Teleport On Neutral
Thumbstick` is off by default; turning it on lets a player aim with the stick and teleport by
releasing it, instead of reaching for the trigger. `Use Teleport Motion` should stay `None`
in VR — moving the camera towards the destination is what makes people sick.

**Check the keys before putting two abilities on one hand.** Every ability brings its own input
mapping context, most at priority 0 with consuming actions, so two abilities that map the same
key on the same hand are decided by registration order and one of them silently never fires.
Known pair: the hand laser's scroll and the teleporter's start both use `Thumbstick_Y`, which
is why the showroom keeps the laser on the right hand and the teleport on the left. Read the
`IMC_VR_*` assets for both sides before combining, and when a clash is unavoidable, assign a
project copy of the context with the clashing mapping removed on the ability's
instance-editable context field rather than editing the plugin's.

## Picking things up

Two components make an object grabbable, and they are a pair: **`BPC_Interaction_Grip_Pickup`**
is the side a hand finds, and **`BPC_PickupCoordinator`** holds the settings and the list of
holders. One coordinator per actor however many hands are on it. Without the coordinator an
object cannot be carried properly.

The hand side is **`BPC_ControllerAbility_Hand_Grab`** on each motion controller, and
**`BPC_PawnAbility_Grip`** on the desktop and mobile pawns. The base pawns carry neither (see
"The base pawns cannot grab or click"); the showroom pawns carry both. Nothing else is needed for a basic
pickup — drop the two components on an actor and it can be picked up on all three platforms.

**A mesh that simulates physics must be the root component.** This is Unreal practice as much
as framework rule: a simulating primitive under a non-simulating parent leaves the parent's
transform the moment physics starts. The framework relies on it too: the coordinator switches
simulation on the owner's *root* and does nothing when the root is not a primitive, and a desktop
grab moves the root. A Blueprint created by tool starts with a `DefaultSceneRoot`, so a simulating
mesh under it keeps simulating and stays behind while the root is carried, which reads as "cannot
pick it up". Promote the mesh to root (`set_parent_component` with the scene root as the
component and the mesh as its parent) before adding the pickup pair, as every shipped pickup does.

**Grip sockets decide where a hand lands, and their names are load-bearing.** The convention is
`Grip_<L|R>_<PoseKey>`: the prefix the search looks for, the hand, and a pose key. The key is
the third underscore-separated part and it is what the hand pose is looked up by, so a socket
called `Grip_L_Medium` finds the pose `Medium`. The framework ships predefined keys —
`Medium`, `Pinch`, `Power`, `Flat`, `Full` — and a key with no matching pose finds nothing, so
invent a key only when you are also authoring the pose for it. Sockets normally live on the
**static mesh**, which means every actor using that mesh inherits them; `BPC_Socket_Grip`
components on the blueprint are the alternative, and interactions gather both.

**`Assigned Sockets` is what stops an object being grabbed anywhere.** Left empty, the
interaction accepts any socket and the hand takes hold wherever it happens to be. Fill it with
the socket names that interaction is responsible for.

**Two hands means two pickup interactions, one per hand that may hold simultaneously.** This is
the part that is not guessable. A single interaction can be held by one ability at a time, so a
two-handed object carries one `BPC_Interaction_Grip_Pickup` per grip position, each with its own
`Assigned Sockets` — and the coordinator is separate precisely so that several interactions can
share one set of settings and one averaged transform. It is also how multiplayer works: several
people each take their own interaction.

**Forwarded buttons arrive on the interaction that is being held**, not on the actor. So a rifle
with a trigger grip and a foregrip fires only when the hand on the trigger grip presses, because
only that interaction broadcasts `On Button Forward Key`. Bind the event on the interaction you
mean, not on the object in general.

Two settings worth deciding rather than inheriting. `Release Type` on the coordinator should be
`Physics` for anything that ought to fall when dropped — the default leaves it hanging in the
air — and the mesh needs Simulate Physics for that to mean anything. `Allow Grip If Occupied`
decides whether a second hand can take an object out of the first; off makes a hold exclusive,
on makes passing an object between hands easy.

**Custom poses** go in `Custom Hand Poses Left` and `Custom Hand Poses Right` on the grip
interaction, keyed by that same third part of the socket name. They take precedence over the
grabbing ability's own poses. Authoring the pose itself is Hand Pose Studio, a modal editor
window — see what an assistant cannot do.

**The pose key runs through four places, and only one of them needs a human.** For a key `K`
and hand `L` or `R`: the socket is named `Grip_L_K`; the pose animation is looked up by `K`;
the socket's editor preview mesh is `SM_Preview_Grip_L_K`; and a custom pose is registered
under `K` in the interaction's pose map. The five shipped keys — `Medium`, `Pinch`, `Power`,
`Flat`, `Full` — have both poses and preview meshes in
`AFS_UX_Grip/Animations/Hand/Preview/`, so using one gives a working, previewable grip with
nothing to author. A custom key needs the pose made in Hand Pose Studio and a preview mesh
saved beside it, following the same name; the showroom's rifle is the worked example.

**One socket name, one place: give each face both hands.** A name can exist once on a mesh,
and an interaction with `Assigned Sockets` refuses a hand that finds none of its own prefix
within `Max Attach Distance` (25 cm). So `Grip_L_Medium` on one end and `Grip_R_Medium` on the
other means the wrong hand on an end simply does nothing. The pose key is the *third*
underscore part and anything after it is ignored, so `Grip_R_Medium_2` is a second right-hand
socket with the same pose: put one socket per hand on every face a hand may come from, same
frame for both, and list them all in `Assigned Sockets`.

**Never scale a component that anything attaches to — a grip, a connector, a snap surface.** A hand attached to a socket inherits the
component's scale, so a 100 cm cube scaled to 40 × 25 × 25 puts a squashed hand on a crate
that the mesh editor shows at full size. Bake the size into the mesh and leave the component at
(1, 1, 1); the project's `mesh_geometry` toolset has `bake_scale` for exactly this. The same holds for a wall a connector sticks things to: a button attached at
begin play to a cube wall scaled (0.1, 4, 3) inherited the scale and ended up squashed inside
the wall. Bake the wall too.

**Where a socket goes, measured rather than guessed.** The hand sits on a socket with its
wrist at the socket origin, fingers along the socket's *right* axis, the back of the hand along
*up* (palm faces −up) and its width along *forward*; the left thumb is on −forward, the right
thumb on +forward. Each pose holds the object at a fixed point in that frame (left hand; the
right mirrors the first value): Power wraps a cylinder's axis at (0, 7.9, −4.2); Full wraps a
bar's centre at (0, 8.3, −4.5); Medium closes on a slab's centre at (0, 9.6, −7.2) or hooks a
thin handle at the fingertips, (0, 12.3, −5.4); Pinch meets at (∓3, 11.3, −8.5); Flat lays the
palm plane at −2.4 below the origin. So placing a socket is: find the target on the object
(`describe_profile` slices a mesh along an axis and shows where the handle is), choose the
frame from the shape (width along the handle's axis, fingers toward it), and put the origin at
target − (px·forward + py·right + pz·up). Where one hand comes from each side, the right-hand
frame is the left one turned 180° about *right*; where both come from the same side (a drawer
handle), the frames are identical and the mirrored hand mesh supplies the handedness. Read the
result back with `describe_socket_frame`, then set the preview mesh and let a person look.

Set the socket's preview mesh whenever you place a grip socket. It changes nothing at
runtime, and it is what lets a person open the mesh and see whether the hand actually sits on
the object — which is the judgement you cannot make from outside the viewport.

## Props you model: the framework look

Props made for a project or an example follow the look of the framework's own tools - the laser
pointer, camera, measuring tool and paintbrush in `AFPro_ProductivityTools` are the reference.
Simple bevelled primitives (tubes, slabs, blocks with soft edges), no fine detail, roughly 150 to
1,000 triangles for a hand-held item. A dark body - `M_Generic_Plastic_Rough_Black` or
`M_Generic_Plastic_Shiny_Black` - with metal parts (`M_Generic_Metal_Rough` / `M_Generic_Metal`) and one
accent: a glow (`M_Generic_Glow_White`, `M_Generic_Glow_Red`) or one coloured plastic
(`M_Generic_Plastic_Rough_Red` / `_Green` / `_Yellow`, `M_Generic_Plastic_Shiny_Orange` ...). At most
three material slots, every one a shared material from `/AdvancedFrameworkPro/AFA_Materials/World`;
no textures, no per-prop material instances. Name the slots after the material in the modelling
file so the assignment is unambiguous. A colour the set lacks is added to `AFA_Materials/World` as
a new material in the same style (a child of the matching generic material, or a copy of it),
never as a one-off instance next to the prop.

**Sockets come with the mesh, always.** Every mesh that is meant to be grabbed gets its grip
sockets the moment it is made or replaced - not later, when the pickup is built: one
`Grip_L_<Pose>` and one `Grip_R_<Pose>` per face a hand may come from, placed with the formula in
*Picking things up*, read back with `describe_socket_frame`, and each with its
`SM_Preview_Grip_<L|R>_<Pose>` preview mesh. Every mesh that is meant to be latched onto (a lever,
a handle, a knob, a drawer pull) gets its `AttachPoint` socket the same way. Pose by shape: Power
for a round handle, Full for a flat bar, Medium for a slab held like a phone, Pinch for anything
small enough to take between two fingers.

## Clicking things

A click is the simplest interaction the framework has, and it is the clearest example of the
three-sided model: one ability does the trace and decides which selectable wins, one
interaction on the object fires one event, and the object never learns which input reached it.

**The ability side.** `BPC_PawnAbility_Select` on a desktop or mobile pawn, and
`BPC_ControllerAbility_Hand_Laser` on a VR hand (not on the base pawns; add them, see "The base
pawns cannot grab or click"). The pawn ability brings its own input
mapping context (`Input_Select`) and adds it on activate, so there is nothing to bind. The
hand laser is a scene component: parent it to the hand's `HandOffset`, and start from the
showroom's offset — location (0, 18.7, −3.1), rotation (−20, 90, 90) — rather than guessing,
because a laser that leaves the wrong part of the hand reads as broken even when it works. The
same goes for every ability that fires along its own forward, the teleport arc included: left
at the controller's identity on a hand-mesh controller it points up, not forward.

**The object side.** Add `BPC_Interaction_Select` and bind `On Select`. That is the whole
contract; the event carries Source Info and the hit result. Two fields on the interaction
restrict it, and they are easy to mistake for one: `Relevant Components` is the component tag
that decides *which parts respond*, `Highlight Tag` decides *which parts highlight*. They
usually want the same tag, and a button whose housing is tagged and whose cap is not will
highlight as a whole and still press as a whole.

**Vetoing and redirecting.** `Interface_Select` on the owning actor has two functions. `Can Be
Selected` returns an Override flag and a verdict — return Override true, Is Selectable false
to refuse, and leave Override false to defer to the default. `Select Hit Location Override`
replaces the point the pointer appears to land on; the idiom is a scene component placed at
the centre of the control and its world location returned, so a laser snaps to the middle of
a curved cap. Add the interface with `BlueprintInterfaceTools.implement_interface` (see what an
assistant cannot do).

**Two tags on the ability side, one on the object.** The laser also drives world widgets, so
it is the same component later episodes use for UI; nothing changes on the button for that.

## Snapping

Two halves, matched by an ID. **`BPC_Anchor`** is the fixed half: put it on the rack, the
shelf, the pawn's hip, list the connector IDs it accepts and set its radius. It registers with
the anchor cache on begin play, so there is no list to maintain, and it stops accepting once
occupied. **`BPC_Connector_Anchor`** is the moving half, on the object, with the matching
`Connector ID`; it asks the cache for anchors under that ID and the nearest inside its radius
wins. **`BPC_Connector_Surface`** replaces the anchor with any surface it is traced against,
within sixty degrees and optionally only on components tagged `Requires Tag` (a component tag,
whatever the tooltip says).

**Both are scene components, and their transforms are the mating points.** The connector's own
transform is what lands on the anchor, so the connector goes where the object touches the
target (the crate's bottom centre) and the anchor goes where the target is touched (the rack's
top). The surface connector traces along its own −Up for `Search Distances` and aligns Up to
the surface normal: put it on the face that will touch the surface, Up pointing away from it.

**Snapping only happens to something being carried.** The search runs from
`BPC_PickupCoordinator` while the object is held and the connection is made on release, so a
button that should stick to a wall needs the pickup pair as well as the connector. Sounds and the
hologram preview come with the connector; nothing to add.

**A level can start assembled.** `Initial Connection` on a connector names an actor and a
component tag, and attaches on the server at begin play. Nothing snaps at edit time.

## Highlighting

**The highlight is named, not built.** Every interaction has a `Highlight Class`; nothing exists
until the first hover, when the interaction adds a component of that class to the owner with
its colour, sounds and `Highlight Tag`. Do not add a `BPC_Highlight_*` component by hand.

**Three classes, one question.** `BPC_Highlight_Material` writes into the object's own
materials and is the cheapest, but the material must carry a scalar parameter and a vector
parameter; `BPC_Highlight_MeshCopy` spawns a ghost duplicate and asks nothing of the material,
at the cost of a second draw; `BPC_Highlight_PostProcess` draws a true outline through a shared
`BP_PostProcess_Highlight` actor (spawned on demand, or place one yourself), needs custom depth
(the plugin's config turns it on) and takes one stencil slot per distinct colour. Is the
material yours? Material. Not yours? Mesh copy. Needs reading at a distance? Post process.

**The material contract, using the shipped names.** The component's defaults are scalar
`Highlight` and vector `Highlight_Color`; name your parameters that way and there is nothing to
configure. Multiply by the scalar last so an unhovered object costs nothing, feed emissive rather
than base colour so it reads the same on headset, desktop and phone, and a Fresnel in front
makes it a rim rather than a bulb. When you replace a material on a mesh that something already
writes a parameter into, keep that parameter's name: the E06 button writes `Color`, as the
engine's basic shapes do.

**Three silent failures.** A misspelt parameter name, a material without the parameters, and
a tag that matches no mesh all do nothing, with no warning. Check in that order when a highlight
does not appear. The mesh-copy and post-process styles have none of these.

**For a comparison, use the showroom.** `Map_Examples_Highlight` has the same statue four times,
once per style and once with no highlight component at all; it is the reference build, with the
identical geometry and lighting a comparison needs.

## Showing an interface in the world

**Three components before any widget.** `BPC_GameState_ThemeManager` on the game state, or every
widget silently falls back to the default light theme; `BPC_PawnAbility_Widget` on the desktop and
mobile pawns, or world widgets cannot be clicked there (the VR hand laser already drives them); and
`BPC_Widget`, the component every world interface is built on, which `BP_Widget` carries ready to
place: set its Widget Class and its Reference Actors, and leave draw-at-desired-size on.

**Build a panel the way the showroom does.** Inherit `WBP_Base`, never User Widget, and put a
`WBP_Scaffold_Default` at the root with your content in its `Slot_Scaffold`: an `AFVerticalBox`
of `WBP_InfoRow`s, each with a `Title`, a `Caption` and the control in its `Slot_InfoRow`
(`WBP_Button_Normal`, `WBP_Button_Toggle`, `WBP_Slider_Unit`, `WBP_ColorPicker`), with the
`TitleSize` fixed so the columns line up; `AFText` on its own for a heading (choose a Typography
level, not a font). Content in two clearly separate parts (navigation and page, list and detail)
goes on `WBP_Scaffold_Expand` instead: `Slot_Content` is the left part, sized by the scaffold's
`Width_Override`/`Height_Override`, `Slot_Expand` the right part, sized by `WidthOverride_Expand`;
the scaffold draws the divider and the padding, and `Set Collapsed`/`Toggle Collapse` slide the
right part away and back. Give every panel a fixed width and height on its scaffold so it never
resizes with its content, and put anything that can grow (lists, filters, chat, alerts) in an
`AFScrollbox_Vertical` set to Fill, with one `AFVerticalBox` inside holding the content (a
scroll box needs free space on its right for its bar, which the scaffolds' slots provide, so place it
directly in a scaffold slot's box rather than inside a card or a narrow column); message-like widgets (a chat or speech bubble) get a width
only, so the text wraps and the height follows. A window that opens on top of a panel (an edit
form, a confirmation, a detail view) is an overlay, not a notification: build it on
`WBP_Scaffold_Overlay`, open it from the panel with `Spawn Overlay` (Blocking on dims and locks
what is underneath), hand it what it needs through a function on the returned widget, and close
it with `Close Widget`; `FL_UI_Overlays` has ready-made confirm, notification and loading ones. A changing value goes in the row's caption through `Update Caption`. In a form, every input that can be filled in wrongly sits inside a `WBP_Error` (`Slot_Error`); a
validate function calls `Show Error` with the message, or with no text to clear it, on commit and on
submit, and submit checks every field. A card or framed area is a `WBP_Box_Default` (content in `Slot_Box`; `WBP_Box_Card*` for card
layouts), which brings the theme's corners, colour and padding; `AFBorder_Rounded` is for shapes such
as an avatar circle - size it through its own padding (padding r on every side and custom corners r give a circle of 2r), not a SizeBox around it, and colour it with a normal palette colour (Category Custom + a `DA_Color_*` asset from AFA_Colors), not Primary. A section that opens and closes is a `WBP_Expandable_Default` (header in `HeaderSlot`, body in `ContentSlot`), not a hand-made collapse with its own button; drive it with its `SetExpanded` event, not the inner Expandable Area. Tags or labels laid over a picture go bare, or in an `AFBorder_Rounded` with Color category Transparent (semi-transparent backdrop, the border's own padding as inset) - never in a `WBP_Box_Default`. Size an `AFImage` with `Limit To Dimensions` = Fit To Dimensions and `Max Dimensions` (-1 leaves an axis free), which keeps the texture's aspect; a SizeBox around it stretches the picture. A render target has no pixel size to fit, so for one use Custom with the brush size, and set the texture through `Update Image` at runtime. A 3D preview in the UI is a scene capture into a render target; give the staged meshes Visible In Scene Capture Only and their own lighting channel so they neither show nor light anything in the world. For a see-through preview, do not change project settings: capture only the meshes (no backdrop, no shadows) with Capture Source "SceneColor (HDR) in RGB, Inv Opacity in A" into an RGBA16f render target, show it in an `AFImage_Rounded` child whose Create Material override uses a copy of `M_UI_Image_Rounded` with One Minus on the texture alpha (the AF image material uses the texture alpha as opacity; SceneDepth in A stays opaque), and put that image in an `AFBorder_Rounded` that gives the background. A bulleted list is a `WBP_BulletPoints` (`Populate Bulletpoints` with a text array) and a numbered one a `WBP_Enumerator`, never a stack of texts with typed dashes. Lists of things get pictures: cards with images (e.g. `WBP_Button_Image`) look far better than text-only cards; make simple pictures if the project has none. Filters and sorting that would eat the panel go into an overlay behind one small button, with the current settings shown as a small caption. Small widgets (a speech bubble, a label, a tooltip-sized card) use `WBP_Scaffold_Overlay`, which has the tighter padding, never a bare SizeBox + border; and their widget component gets Use Panel off - the panel frame is the component's default and is meant for full panels, not small widgets. Set a scaffold's height only when its content changes size (lists that grow, pages that switch, sections that collapse); when the content keeps its size, leave the height override off so the scaffold fits it. A fixed scaffold size must fit its content: whatever runs past the bottom is clipped and cannot be clicked (an overlay whose Close button is cut off cannot be closed), so check the overlay in play. An `AFSwitcher` collapses the pages it is not showing, so the panel takes the size of the current page: give a paged panel a fixed scaffold width and height that fit its largest page. The switcher also sets the alignment of its direct children from its own Horizontal/Vertical Alignment, so a page whose content should sit centred goes into a wrapper (an Overlay) and is centred in that wrapper's slot. Auto-wrapping text only wraps when something limits its width; in a panel without a fixed width a long description stays one line and stretches the panel. A panel with several pages is split into widgets: the main widget holds the switchers and the navigation shared by the pages, and every page is its own widget (e.g. inventory: main widget with one switcher between the category page and an area of [second switcher between detail page and all-items page + bottom navigation]). Pages report up through event dispatchers (`OnCategorySelected`), bound in the main widget with `BindToEventProperty`; the main widget keeps the navigation state and the shared buttons. A page turn builds a fresh page: the switcher's `Create Widget` (Remove Widgets After Fade Out on, Focus and Transition on) adds the new page and cross-fades from the old one, which is then removed, so only one page exists at a time and its spawners start clean; cast the returned widget and hand it its data (`ShowItem`, `ShowPage`), and have the page apply that data both there and in its own Post Initiate, since the main widget's Post Initiate runs before its page widgets'. A switcher that builds its pages this way does not replicate (Should Replicate off); the buttons that turn the pages replicate instead, so every client builds the same page. Choices in such an overlay are lists in a `WBP_Group_Button_Vertical`. Pick the button by selection type: Single Select uses `WBP_Button_Radio`; Single Select Toggle and Multiple Select use `WBP_Button_Checkbox` (e.g. categories to filter by are checkboxes, the sort order is radio buttons). A group that should show the click needs Automatic Selection on. A button whose design carries more than a label and an icon (e.g. a dialogue answer with a number circle) gets its own button class, not a framework button with a content widget: duplicate the closest `WBP_Button_*` into the project (it brings the frame, the hover and outline borders and the animations, which cannot be made by tool), rebuild its content, override `getButtonText` for the label and handle extra texts in `Event Update Button Content`, and remove the overrides that point at removed elements (`getButtonSlot`, `getButtonImage`, named slots). Keep the standard sizes of framework buttons; only these may be resized (directly or through a group's size settings): width and height - `WBP_Button_Image`, `WBP_Button_Card_Top`, `WBP_Button_Card_Left`, `WBP_Button_Normal_Keyboard`; width only - `WBP_Button_Normal`, `WBP_Button_Large`, `WBP_Button_ChipSelector` (the chips), `WBP_Button_Tab_Horizontal`, `WBP_Button_Tab_Vertical`; never - `WBP_Button_Small`, `WBP_Button_Color`, `WBP_Button_Toggle`, `WBP_Button_Checkbox`, `WBP_Button_Radio`, `WBP_Button_Arrow`, `WBP_Button_Scrollarrow`. When a row is too tight, change the layout around the button, not the button. To fill a row exactly, compute the width instead of measuring screenshots: (scaffold width - 2 x 80 scaffold padding - (n - 1) x the gap between the buttons) / n for n buttons in the row (e.g. 2 large buttons in a 1450 scaffold with a 20 gap: (1450 - 160 - 20) / 2 = 635). `WBP_Scaffold_Overlay` pads 40 per side instead of 80, so there it is width - 80. To keep a button at its standard size, compute the scaffold width the other way round: n x button + (n - 1) x gap + 2 x padding (3 large tiles of 250 in an overlay scaffold: 750 + 40 + 80 = 870). A `WBP_Group_Button_WrapBox` only wraps after Num Of Elements In Column buttons when its width override is on - with the override off every button gets its own row. An inventory you can grab from is a grid of fixed slots, each wrapped in an `AFOverlay_Spawner`: on a filter or page change set the overlay's Spawn Class and Mesh Representation and call `Update Spawner` on its spawner (or `Set Active` false for an empty slot). On a panel at 0.05 scale the spawner stands at 5x world scale, so shrink the dummy through the Mesh Representation transform and leave Reset Scale on for the spawned actor. Bring the offered object close to its slot: the spawner's default Relative Distance From Widget of 150 floats it about 7.5 cm in front of a 0.05-scale panel; 75 keeps small items just in front of the surface. The spawned class needs the full pickup pair (grip pickup + `BPC_PickupCoordinator`, Release Type Physics, simulating mesh as root). A stepper bound to an integer state reads the state's Texts when it is assigned: set the texts first, then assign. Icons are 128x128, pure white with the shape in alpha so the UI can tint them; take them from Material Design Icons (pictogrammers.com/library/mdi). General-purpose icons (arrows, close, check, plus/minus, search, filter, sort, edit, lock, settings, user, media, status and more, 92 in all) are already in `/AdvancedFrameworkPro/AFA_Icons/Textures` as `T_Icon_<Name>` - look there first. An icon only one widget needs goes next to that widget, not into AFA_Icons; a new general icon also gets a row in `ThirdParty/MaterialDesignIcons/README.md`. Leave slot padding alone: `AFVerticalBox` / `AFHorizontalBox` and the framework widgets space their
children themselves, so a manual padding only adds a second gap. Nothing is styled by hand; the theme asset styles everything,
and a copy of `DA_Theme_Default_Light` with other palettes, listed on the theme manager, restyles
the project. Button labels go in the `Content` struct (entry 0 resting, entry 1 selected); text is
changed with `Update Text` on the element, not Set Text. A dropdown or anchor menu gets its
`Button Class` (`WBP_DropDownButton`), a `Placement` that opens towards the panel's inside and `Fit In
Window` on, so the menu never leaves the widget's bounds. A toggle (`WBP_Button_Toggle`) keeps its own width: never set its
`Width Override`. Its `Content` texts are the labels beside it, entry 0 off and entry 1 on: give it
none or both, never a single title; a bound state component's `Text False`/`Text True` replace them.
The name of what it switches goes beside it (an info row's title, a header text), not in the toggle;
leave room for the labels, which draw outside the toggle's own box. Every chip selector and tab bar starts with a
selection (`Selected Ids`) that matches the panel's starting state. A list that refreshes keeps its widgets: set the group's
`Widget Class`, call `Resize Widget Group` with the data's length, then hand each widget its entry
(cast `Get Widgets`[i], call its setter); do not clear and recreate the group. Never call `Post Add
Design` yourself; the group runs it. An element that only holds the
widget's own state (a filter, a tab bar, a page switch) gets `Should Replicate` on, so every player
sees the same panel; an element bound to a state component replicates through the state instead. `ST_Button_Content` is for button
content only; data that describes something else (a player, a message, a product, a task) gets its
own struct with named, typed members (`ST_<Project>_Player`: Name text, Ping integer, Ready and Host
booleans) and its own data table. Never encode numbers or flags as texts in another struct.
Sub-widgets get their parent on `Create Widget` (Self on the Parent pin), not through
`Initialize Sub Widget` afterwards.

**Reach the world through references, never through Get All Actors.** The widget component's
`Reference Actors` map holds key and instance, set in the level; inside the widget,
`getReferenceActor` with the key has a Then and a Not Found branch, and the Not Found branch is
where a renamed key shows up, so write something to the panel there.

**By tool.** `AddWidget` refuses the Blueprinted primitives (`AFText`, `AFHorizontalBox`); add the
plain engine widget and `ReplaceWidgetWithTemplate` it with the `AF*` class. Not for `AFScrollbox_Vertical`: the
replace carries the engine scroll box's bar style, thickness and padding over the framework's
design. Make it with `WrapWidgets` (wrapper class `AFScrollbox_Vertical_C`) around the content,
and never set its own properties; only its slot (Fill) is yours. A new struct comes from
`StructTools.create_struct` (members as JSON: name, type such as `text`, `int`, `bool`,
`object:/Script/Engine.Texture2D`), edited with `add_member`, `set_member_type`, `set_member_default`, `set_member_tooltip`, `rename_member`,
`remove_member`; a data table on it from `DataTableTools.create` (rows by `add_rows`, then `set_rows`
with the members in lowerCamelCase). Do not call a member `Name`; the table tools drop it. Enum-keyed maps are
written by clearing to `{}` first, then writing the whole map. A widget delegate needs
`BindToEventProperty` before the DSL's `(event OnPressed(Widget) ...)` resolves, and a function
needs `add_function_graph` before `(fn ...)`. Change the widget tree before the graphs, delete any
of your nodes that referred to a removed widget, and compile clean before writing a graph: a graph
written against a blueprint that does not compile leaves duplicates (`RefreshStatus_0`) behind.

## Hand tracking and gestures

Tracked hands need no setup: with hand tracking on in the headset, putting the controllers down
switches the hand motion controllers to tracked hands. What they lack is buttons, and gestures
are the buttons: a `UAFSHandGestureRecognizer` on the motion controller (or the pawn) with its
**Hand Side** and a list of gesture definitions turns each recognised gesture into a key press,
`AFS_LeftHand_<Name>` / `AFS_RightHand_<Name>`, which binds in Enhanced Input like any key. Only
the gestures in that hand's list are evaluated. The framework's own contexts already map Pinch to
grab (both hands) and to the right laser's select, and Drag to drag-move, so Pinch alone makes a
room usable; add `BPC_ControllerAbility_DragMove` and the Drag gesture for locomotion.

Read a hand direction rule literally: **Down of Palm facing WorldUp** is the palm facing *up*
(Drag is thumb–middle with the palm up). A gesture definition is a data asset of class
`AFSHandGestureDefinition`: finger curl rules, bone distance rules, hand direction rules, hold and
release times, priority, and the left/right key. A gesture of your own needs its key names first:
**Project Settings → Plugins → AFPro Hand Gestures → Additional Gesture Names** takes the bare name
(`Fireball`), registers both sides, and needs an editor restart. Keep the set small, prefer the
pinch, and avoid self-occluding poses: thumb–middle hides the index, which then reads as curled.

## Aiming from a tracked hand

Anything attached under a motion controller inherits the controller's *late update*: the pose
is re-applied after Tick, so a rotation you set every frame gets the controller's own tremor
and its pinch-time shift added back on top. If you compute a direction yourself (a laser under
hand tracking), set the component's rotation to absolute while hands are tracked and back to
relative when controllers return. That one flag was the whole fix for a laser that "jumped
15 cm at a metre" on every pinch; filters, dead zones and pinch detection were not.

For the direction itself, the shoulder ray works: a shoulder proxy (camera location plus an
offset in head-yaw space, right hand about (−5, 17, −25) cm) through the raw tracked wrist
from `GetAFSHandTrackingSubsystem → GetBoneTransform (Side, Wrist)` — raw data, not a mesh
bone, which moves with animation blends and the rig. Smooth with two speeds blended by the
remaining error in degrees: steady (6) under the range (5°), fast (16) beyond it, so tremor
is damped and a real move is not.

## What an assistant cannot do, and what to ask for instead

Very little, once the editor and the programs the work needs are open (see "Working with the
user"). What still needs a person: decisions and approvals, judging feel, comfort and
readability in the headset, restarting the editor, opening a program, and anything on
hardware. Name that step precisely when you ask for it. Below are the traps found by hitting
them, and the operations that look impossible but have a tool.

**Things that look impossible and have a tool** (Codeable AI Editor Tools unless noted):
- **Implement a Blueprint interface:** `blueprint_components.BlueprintInterfaceTools`
  (`implement_interface`, `remove_interface`, `list_interfaces`). Then write the function
  bodies with the graph DSL; interface functions without outputs are events. This matters,
  because `Interface_Select`, `Interface_Grip`, `Interface_TeleportLocation` and
  `BPI_AbilityRestriction` are how objects talk back to the framework.
- **Create a level:** `levels.LevelTools.create_level` (Codeable AI Editor Tools) makes, saves and opens an empty map
  (it refuses while another map has unsaved changes). Then everything in "Levels: four
  things, not one" is done by tool.
- **Rename or move assets:** `content_tools.MoveTools.move_assets` renames in one batch without
  the editor's dialog; then `ContentTools.fix_up_redirectors`. Do not use a rename that raises
  the editor's confirmation dialog: a modal dialog freezes the tool server until a person
  closes it.
- **Configure the project:** `afpro_agent_toolsets.setup.SetupTools` (AFPro) runs the Setup
  window's checks and fixes (see "Project configuration for VR and Quest").
- **Parent socket of a component, parent calls, hand poses:** see the entries below.

**Author a hand pose.** Hand Pose Studio is a modal editor window; do not open it. Everything
around the pose — the socket, the key in the pose map — can be prepared by tool, and
`afpro_agent_toolsets.hand_pose.HandPoseTools` reaches the pose itself: `copy_pose` from a base
pose, `finger_curls` / `set_finger_curls`, `curl_to_contact` (closes the fingers onto the mesh
with the hand on the socket) and `bake_preview` (the socket's `SM_Preview_` mesh). Nothing is
saved; save the pose, the preview mesh and the object mesh by path. Judging the result is a
person's job in Hand Pose Studio or in VR.

**Load a new tool without a restart.** The agent toolsets register once at editor start.
`AdminTools.reload_toolsets(package_name)` (Codeable AI Editor Tools) re-imports and re-registers a
package after a change to an existing module; a new module, or a change to `admin.py` itself,
still needs a restart. Ask for it, and batch tool additions so one restart covers them. The
toolsets ship in the plugins - the general editor tools in Codeable AI Editor Tools
(`Content/Python/codeable_ai_editor_tools/`), the guide and the hand pose tools in AFPro (`Content/Python/afpro_agent_toolsets/`);
do not edit them there, they are replaced on update.

**Trust a placed instance after changing its Blueprint.** Components and defaults added to a
Blueprint do not reach instances already in the level during the session, and writes to those
instance components may not stick. Remove and re-place the instance (then re-set its
instance-editable values), or restart the editor, before reading it back.

**Never read a graph with `read_graph_dsl` directly.** Its decompiler calls `get_node_type_pins`,
which creates and removes a temporary node per type; for Assign / Bind Event nodes the editor also
creates a matching custom event that is *not* removed, so each read leaves an empty `OnX_Event_N`
in the Blueprint — in framework assets too. Read with `codeable_ai_editor_tools.graph_nodes.
GraphNodeTools.read_graph_dsl_safe`, which removes whatever the read added, and do not call
`get_node_type_pins` on an Assign node type at all.

**Use an asset you just created in a graph.** BlueprintTools caches each graph's node list and
rebuilds it only when that Blueprint's own structure changes, so an input action, function library
or Blueprint class created this session is missing from `find_node_types`, `create_node` and the
DSL even though the engine knows it. Call `codeable_ai_editor_tools.graph_nodes.GraphNodeTools.
refresh_node_types` (optionally for one Blueprint) and search again. Never conclude "the editor
cannot do this" or "needs a restart" before refreshing. A node's own hidden settings (an input
event's Input Action) are still out of reach; make the right node instead of re-pointing one.

**Parent socket of a component.** Not settable through Python or the property tools, but it is
through `codeable_ai_editor_tools.blueprint_components.BlueprintComponentTools.set_component_parent_socket`
(Codeable AI Editor Tools C++ underneath). It checks the bone or socket against the parent and refuses an
unknown name; read it back with `get_component_parent_socket`.

**Reading framework graphs dirties them; the next PIE pays for it.** Even `read_graph_dsl_safe`
adds and removes nodes, which marks the Blueprint modified; PIE then recompiles it in-session,
and the first start afterwards floods the log with settings-row errors (`AddButtonToGroup`
create returns None, ensure `ParentUserWidget->WidgetTree`, ChipSelector / DropDown / Toggle
Accessed None). A fresh editor start is clean (checked 9/10 Oct: 120+ errors → 3). So: read
framework graphs before a test round, not between test runs; if you did, ask for an editor
restart (without saving framework assets nobody edited) before judging the log.

**Who sees a message.** Decide per message, not per system. A prompt that asks one player
something (a pop-up, "next level?") goes to that player only: filter on `IsLocallyControlled`
before showing it, since overlaps and many events fire on every machine. A message about the
shared world (a lamp at full power, a door opened) should reach everyone. Notifications and
pop-ups do not replicate on their own, so a world message has to be raised on each machine
(e.g. from a replicated state change). Label show distance: pick it per object, about 2 m for
hand-sized things.

**Placed actors keep stale components until the level reloads.** A component added to a
Blueprint by tool, or a component template edited with `set_properties`, does not reach the
actors already placed in the open level: they keep the class defaults (a label at (0,0,0)
titled "Label"), and editing the placed instance is undone when it reconstructs. The saved
asset is right. Reopen the level (without saving it) or restart the editor before testing,
and say so in the hand-over.

**Call to parent function.** The DSL writer, `create_node` and `add_event` cannot make a parent
call, but `codeable_ai_editor_tools.graph_nodes.GraphNodeTools.add_parent_call` can (Codeable AI Editor Tools
C++ underneath, the same as the editor's "Add call to parent function"). Pass the override's event
or function-entry node, or `blueprint` plus `function_name` (`ReceiveBeginPlay`, `ReceiveTick`,
`Value_setFloat`, a function override's graph name). With `wire` on it goes first in the
override (event → Parent → what followed) and passes the parameters on by name. It returns an
existing parent call rather than adding a second. Any override of a framework event that does
work in the parent (a setting's `Value_set…`, an ability's `BeginPlay`) needs one; check before
you compile. Then compile → arrange → save as usual.

**Build a Timeline.** The graph DSL has no Timeline node. A Tick with `FInterpTo` and a
target value does the same job for a swing, a fade or a slide, and is what the tutorial
room's door uses.

## Looking things up

In order, stopping as soon as you have the answer:

1. **`Reference/plugins/<system>.md`** — every asset in that system, what it is for, its
   public API with descriptions, its components, and the interfaces it implements. This is
   generated from the assets, so it does not drift from them.
2. **`Reference/assets.md` / `assets.json`** — every asset in one list, for when you want to
   search across systems rather than read one. `Reference/workflows/` has step-by-step
   recipes ("add a pressable button", "add a teleport area").
3. **`Codex/<Folder>/…/<Asset>.bp.txt`** — what a framework graph actually does. Search the
   whole `Codex/` folder to find every place a function or dispatcher is used.
4. **The live project.** Descriptions are on the assets themselves: a variable's tooltip,
   a function's description, an event's comment bubble. If you can inspect the open
   editor, that is the most current answer there is, and the only one that accounts for
   changes made since the plugin was installed.

### What this reference does not cover

Be explicit about these rather than guessing around them.

- **C++ is not in it.** A substantial part of AFPro is native — the hand pose system, the
  gesture recogniser, the function libraries. If behaviour has no blueprint explanation,
  that is where it is, and the reference will not tell you.
- **Logic is not in it.** The reference says what exists and what it is for, not what
  each graph does. For that, read `Codex/` (AFPro as shipped) or the blueprint in the editor.
- **Private members are not in it.** Only the editor-visible API is listed.
- **Data tables are listed, not expanded.** A row struct can look unused when its only
  reference is a table. `ST_TargetValues` is the example: nothing in any graph names it,
  and it is the row type of `DT_TargetStats`.
- **It describes AFPro as shipped.** Your own assets, and any change you have made to
  AFPro's, are not in it.
- **Names repeat across assets.** `OnValueUpdated` is declared by six different
  components. Always qualify a member by the asset that owns it.

### The Codex: framework logic as text

`Codex/` is a Codeable AI Editor Tools export of AFPro as shipped: one text file per asset, with
declarations, every graph, comments and descriptions. Read it before describing what a
framework graph does; do not describe graph logic you have not read. Two conventions are
worth knowing. A `;;` line is a description that lives on the asset, and a `;` line is a
comment written in the graph. A dispatcher call prints as its display name spaced out,
running into its arguments — `Call On Highlight Tick SourceInfo TraceResult` — so search
for the spaced form, not the variable name.

It covers AFPro only, as shipped. The project you are in, and any change made to AFPro's
own assets, are not in it. Codeable AI Editor Tools (a separate plugin, recommended, not required)
exports a whole project the same way; if you find a folder of `.bp.txt` files in the
project, `graphs/` by convention, that is such an export and you should read it. If there
is none, say so and read the project's blueprints in the editor.

## Performance: a budget per target, measured early

The design sets a frame budget for every target; the foundation measures a baseline before
content goes in, and every build step is checked against the budget. A budget discovered at
the end costs rooms.

**Frame time.** One set of content runs on every target, so design for the weakest one the
project lists. Keep about a fifth of the frame free for spikes (a level streaming in, a burst
of effects).

| Target | Refresh | Frame | Start budget: GPU / game and render thread |
|---|---|---|---|
| Quest 3 and other standalone headsets | 72 Hz (90 if the project chooses it) | 13.9 ms | 11 ms / 9 ms |
| PC VR | 90 Hz | 11.1 ms | 9 ms / 8 ms |
| Desktop | 60 Hz | 16.7 ms | 13 ms / 12 ms |
| Mobile | 60 Hz (30 for heavy scenes) | 16.7 ms | 13 ms / 12 ms |

**Content, for a standalone headset in the worst view** (a conservative start; your own
baseline replaces it): at most 150 draw calls (mesh sections), 300 k triangles, two
overlapping translucent layers, no dynamic shadow-casting lights, no post-process beyond
tonemapping. Hand-held props 150 to 1,000 triangles, furniture up to 5,000 (see "Props you
model"). PC VR and desktop have several times the room, but the shared content does not get
to use it.

**Rendering approach for a VR or standalone target**, set by the Setup window's checks:
forward shading, MSAA, mobile multiview, no Lumen, no ray tracing, no virtual shadow maps,
Nanite off on every mesh. Rooms use baked static lighting; lightning, glows and beams are
emissive materials and material parameters, not lights. The framework's own costs and knobs
(highlight style, replication rates, ticking abilities, widget fade) are in
`Reference/guides/performance.md`.

**Measuring.**
1. **Baseline** on the bare shell (walls, sky, lighting, no props), before the first segment.
2. **In the editor**, at the headset's pixel count, not the desktop viewport's: set the
   screen percentage so PIE renders what the headset renders (Quest 3: two eyes of
   2064 x 2208), then `stat unit`, `stat rhi` (draw calls, triangles) and `stat gpu`. A 1080p
   viewport renders about a quarter of a Quest 3's pixels and flatters everything. Editor
   numbers are relative: they show what a step added.
3. **On the device**, the numbers that decide: a development build from the Setup window's
   Builds page, read with `stat unit` or the headset's metrics overlay (OVR Metrics Tool on
   Quest). Deploying and wearing the headset is a person's step; ask for it at the baseline
   and at the end.
4. **Record** every measurement in the design's budget table, again after each segment and
   in the worst view at the end.
5. **Over budget?** Cut in this order: merge mesh sections and materials, make the
   surroundings cheaper (sky, sea, distant geometry), shorten translucency, simplify the
   heaviest mesh. The interactions are never what gets cut.

## Planning a project: work in phases

A project goes through phases, and each ends in something the user can look at and approve.
Skipping straight to building is the most expensive mistake an assistant makes here: rooms get
rebuilt, systems get replaced, and the performance budget is discovered last. Say which phase
you are in, and do not start the next one before the user has approved the current one.

1. **Brief.** One page: what it is, for whom, the platform and quality bar, what it must prove
   about AFPro, and the user's rules. Ask the questions whose answers change what you build
   (target hardware, scope, what may be copied); decide the rest and write the assumption down.
2. **Design, complete enough to build from without guessing.** Two halves that point at each
   other:
   - **A written design** (`DESIGN.md` beside the brief): goal and audience; the experience
     flow, beat by beat, with timing and what ends each beat; the space, zone by zone, with
     coordinates; every object with the states it owns, who writes them and how it reacts; every
     grabbable with its interaction, pose key and sockets; UI per space (world panel, hand UI,
     HUD, notifications, labels); look and audio; the rendering approach with a measured
     baseline and a budget; build order; acceptance criteria; open questions, numbered.
   - **A visual design** on a canvas the user can open, edit and comment on (in Claude: an
     artifact made from the Design type): a storyboard of the flow with a picture per beat; a
     top-view layout to scale; mock-ups of every UI at real proportions, with what each control
     is bound to; a card per grabbable with its pose; the rendering plan. Use captures from the
     actual level where something exists (fixed camera positions, no editor chrome) and mark
     everything not built yet as such.
   - Anything decided by looking goes on the canvas; tables, rules and budgets go in the text.
3. **Review gate.** The user reads, comments and answers the open questions. Fold the answers
   into both halves. Only then build. A change of mind later goes back into the design first.
4. **Foundation.** Project setup, configuration and the rendering approach the design chose,
   measured at the target resolution before content goes in (see "Performance": match the
   headset's pixel count, not the desktop viewport).
5. **Build, in the design's order**, one segment at a time, each finished with compile, arrange,
   save, a log entry, test steps for the user and a run in PIE that drives it the way a player would (the connector's search, not a
   direct attach; the drag state, not a set value).
6. **Verify.** Against the acceptance list: script-driven PIE runs, a clean log, the
   performance budget at target resolution, and a look at every grip (preview hands placed at
   the socket transforms and captured).
7. **Hands-on test by the user**, in the headset and on desktop. An assistant cannot judge feel,
   comfort or readability in VR; ask for this explicitly and fold the findings back in.
8. **Polish and hand-over.** A run log (what was built, what was tested, interventions, what is
   open), the design updated to what was actually built, and the user's next steps.

## Starting a project on AFPro

From an empty editor, the order that works:

1. **A blank project on the Scalable preset**, not one of the templates. Scalable because the
   project has to stay VR-compatible; blank because template content fights the framework.
2. **Enable the plugin and restart the editor.** The restart is required, not optional, and it
   is a person's step.
3. **Prove the install before building anything.** Open one of the example maps and play it,
   on desktop and in VR if a headset is there. A problem found later is then never "was the
   install wrong?". Note that plugin content is hidden in the content browser until you turn
   it on, so an apparently empty plugin is a view setting.
4. **The project's own game classes**, in its own Content: a game mode, and a player
   controller, game state and player state from the engine classes. Set the latter three on
   the game mode, and set the game mode's default pawn to `BP_Helper_Pawn_Loading`. Leave
   them otherwise empty; components arrive with the systems that need them.
5. **A level**: a `PDA_Level` data asset, a `BP_MapInfo` actor naming it, a `BP_PlayerPosition`
   to spawn at, and a game mode that will actually be in play. See the Levels section.
6. **A pawn per platform**, subclassed into the project, named on the level data asset, with
   motion controller subclasses of your own for the VR pawn to spawn — abilities live on
   those, and you cannot put an ability on a plugin asset.
7. **Then the room**: each object needs its own interaction component, and a coordinator if
   it can be held. The settings and theme components go on the game state once there is UI,
   since widgets read the theme manager and register themselves with settings.

Then compile and run it. An interaction that does nothing is almost always a missing
interaction component on the object or a missing ability on the hand — check both sides
before looking anywhere else.

### Project configuration for VR and Quest

The plugin's own `Config/Engine.ini` only brings the net driver and the 2.0 CoreRedirects, and
`Input.ini` the gesture key names; do not repeat the net driver in the project. Everything else
(custom depth, UI scale and font DPI, focus rule, sound classes, default mapping contexts off, and
per platform Mobile HDR or instanced stereo off) is a Setup check the project sets. Each check's
`for` field names the feature it is for; required checks are `essential` or `recommended` (`tier`). What the project itself needs
(checked against the example project, 11 Oct). Apply them through the Setup window's checks,
not by editing ini files: `SetupTools.set_targets`, then `fix_project` with `dry_run` on. The
change list goes into the design for approval, since it changes the whole project; after the
approval run it for real and ask for one editor restart:

- **`.uproject` plugins:** OpenXR, OpenXRHandTracking, ModularGameplay, AudioModulation;
  target platforms Android and Windows.
- **DefaultEngine.ini:** hardware target Mobile / Scalable; forward shading, mobile multiview,
  MSAA (`r.AntiAliasingMethod=3`, `r.Mobile.AntiAliasing=3`), auto exposure, ambient occlusion
  and motion blur off, static lighting allowed, `r.Mobile.UseHWsRGBEncoding`,
  `r.Mobile.PropagateAlpha`, `r.Mobile.ShadingPath=0`; `NearClipPlane=1`; Android
  `bPackageForMetaQuest`, min/target SDK 32 and the Quest `supportedDevices` meta-data;
  `xr.OpenXRInvertAlpha`, `bExplicitCanvasChildZOrder`, `bAllowClientSideNavigation`;
  `[OnlineSubsystem] DefaultPlatformService=Null`, `bHasVoiceEnabled=true`.
  Optional (Setup window, C · Project): `[/Script/NavigationSystem.NavigationSystemV1]
  bShouldDiscardSubLevelNavData=False` and `[/Script/NavigationSystem.RecastNavMesh]
  RuntimeGeneration=Dynamic`. Levels stream into a persistent map; Unreal discards a nav mesh
  inside a streamed map and spawns no replacement, so the teleporter's NavMesh mode finds no
  ground when a map is loaded through the menu (starting the map directly still works).
- **Remove the template's `FontDPIPreset=Standard` / `FontDPI=72`.** They override the plugin's
  Unreal / 96 and every framework text comes out at the wrong size.
- **DefaultGame.ini:** `bStartInVR=True`, project and company name.
- **DefaultInput.ini:** `DefaultTouchInterface=None` (no virtual joysticks on Quest),
  `bEnableInputDeviceSubsystem`, `bEnableDynamicComponentInputBinding`.
- Start map and game mode only once the level exists (step 5 above).

### Setting up the AI tools in a new project

AFPro brings its own part; the engine's MCP plugins are the user's to enable.

- **`.uproject`:** ModelContextProtocol plus the toolset plugins (AllToolsets, EditorToolset,
  GASToolsets, NiagaraToolsets, PhysicsToolsets), and PythonScriptPlugin. Without them AFPro
  works the same; only the assistant tools are missing.
- **DefaultEngine.ini:** `[/Script/PythonScriptPlugin.PythonScriptPluginSettings]
  bDeveloperMode=True`.
- **The Setup window** (Tools ▸ Advanced Framework Pro ▸ Setup…, opens by itself on the first
  start and after an update) does the rest. Its Project setup page checks every setting AFPro
  needs for the chosen targets (PC VR, Quest, desktop, mobile), with current → wanted values and a
  Fix button; its **AI assistants** section enables the engine plugins above, writes the
  `AGENTS.md` / `CLAUDE.md` pointer files in the project root, and holds the per-user "start the
  MCP server with the editor" switch. Its Updates page lists upgrade steps after a plugin update.
  Use it through `afpro_agent_toolsets.setup.SetupTools` (targets, checks, fixes with a dry run,
  pointer files) rather than editing `Config/` by hand; it backs files up and logs every change
  in `Saved/AFPro/`.
- **At editor start the plugins' Python** registers the toolsets when the toolset plugin is
  enabled: AFPro's `init_unreal.py` registers `afpro_agent_toolsets` (the guide) and reports to
  the window (`Saved/AFPro/toolsets_status.json`); Codeable AI Editor Tools' registers `codeable_ai_editor_tools`
  (CodexTools, graph layout and safe graph reads, parent calls, components, structs, levels,
  sockets, mesh geometry and import, Blender, diagnostics, admin). Without the switch above, the
  MCP server waits for the console command `ModelContextProtocol.StartServer` (port 8000,
  `-ModelContextProtocolPort=N`).
- **Codeable AI Editor Tools - recommended, not required.** Without it the guide works and you use the
  engine's own toolsets only: no safe graph reads, layout, parent calls, sockets, levels or mesh
  import. Say so to the user once instead of working around it, and check `list_toolsets` for
  `codeable_ai_editor_tools` before planning work that needs it.
- **No project copy of the toolsets.** A `Content/Python/afpro_agent_toolsets/` in the project
  shadows the shipped one (the log says so; the Updates page moves it). Project-specific tools
  go in a package of their own name.
- **Blender** (`codeable_ai_editor_tools.blender.BlenderTools`) needs the free Codeable Bridge
  extension in the user's Blender; `blender_status` tells, and `install_bridge` installs it only
  with `confirm=True` after the user said yes. The mesh importer creates its pipeline asset
  (`/Game/Pipelines/IP_StaticMeshProp`) on first use.
- **Only one editor open.** The desktop connector talks to whichever editor holds the MCP
  server; with two open you may be working in the wrong project. Check the open level's path
  before the first change.
