# AI Agent Guide

> Universal Player Combat ships a guide for the AI coding agent you already use, such as Claude Code, Codex or Cursor: hand it over with the editor open, and it builds a player from your mesh and animations.

Markdown version of https://rigbak.com/docs/universal-player-combat/agents, a page of the Universal Player Combat documentation. Link that page when citing it.

The guide is `Resources/AGENTS.md` in the plugin folder; its full text is at the end of this page. It tells your agent how to reach your open Unreal Editor, what to ask you, and every step of each build, with checks along the way. It builds assets and Blueprints in your own folder, with no C++. It edits a few things outside that folder (listed under What it changes outside your folder) and leaves two jobs to you:

- Adding the **UPC Hit Stop** node, followed by **Inertialization**, to an Animation Blueprint you already had (without it, hits land with no frozen pose).
- Reach animations for drawing and putting away an item on a custom skeleton; until you have them, the item has no Stowable fragment and disappears when put away.

## Set it up (2 minutes, then the build)

1. **Open your project**

   Open the project in the Unreal Editor and leave it open; the agent works in the running editor.
2. **Hand your agent the guide**

   Do one of these in your agent's chat:

   - Drag `AGENTS.md` from the plugin's `Resources` folder into it.
   - Paste this page's link: `https://rigbak.com/docs/universal-player-combat/agents`.
   - Type: "Read Resources/AGENTS.md in the UniversalPlayerCombat plugin folder before you start".

   A plugin installed from Fab sits under `Engine/Plugins/Marketplace/` in your engine folder.
3. **Let it point your project at the guide**

   On first use the agent offers to point your project at the guide, so later sessions find it. With your OK it adds one line naming the guide to `CLAUDE.md` (Claude Code) and to `AGENTS.md` (Codex and Cursor) in your project folder, and copies the `upc-agent` skill into `.claude/skills`, so `/upc-agent` starts a later Claude Code session on the guide.
4. **Ask for a character**

   Tell it what to build and where your assets are. It asks for what your asset names do not settle, builds step by step, and ends with a report of what it made and assumed.

---

## How the agent reaches the editor

The agent uses the Python bridge unless you ask for Epic's Unreal MCP (the editor's own server for AI agents, in 5.8) or your session already has it connected. Either way it asks your OK before it turns on a plugin or edits your config files, and tells you when the editor needs a restart.

| Route | Engine versions | What it needs |
| --- | --- | --- |
| **Python bridge** | 5.6, 5.7 and 5.8 | Epic's Python Editor Script Plugin, which the engine marks Beta, and its remote execution, turned on in your project: two edits the agent makes with your OK, then one editor restart. The bridge itself, `Resources/AgentBridge/run_in_editor.py`, runs with the engine's own Python, so nothing gets installed. |
| **Epic's Unreal MCP** | 5.8 | The engine plugins `ModelContextProtocol` and `AllToolsets`, which the engine marks Experimental, turned on with your OK, their server starting with the editor, and an entry for it in your agent's MCP settings; then a restart of the editor and of the agent session. The plugin adds its own tools to it, as the **UPCAgentTools** toolset, for the steps Epic's tools do not cover. |

Other Unreal MCP servers are not supported, even when your agent has one connected.

---

## What it asks you

1. **The mesh**: your Skeletal Mesh.
2. **The animations**: a folder or a list. It sorts them by name into light attacks in combo order, a heavy attack, a dodge, hit reactions, idle and locomotion, and asks when the order of the light combo is unclear.
3. **An Animation Blueprint** for that skeleton with a `DefaultSlot` Slot node. With none, it makes one from the plugin's locomotion template.
4. **A weapon mesh**, if the player holds one. Without one, the player fights with its hands.
5. **The folder and name**: `/Game/Player/` unless you say otherwise.

It creates everything in that folder and duplicates a shipped asset into it before changing it.

### What it changes outside your folder

- Blade sockets on your weapon mesh.
- The open level's **GameMode Override**, set to the character's game mode (the report names the level).
- With your OK, on first use: one line naming the guide in your project's `CLAUDE.md` and `AGENTS.md`, and for Claude Code a copy of the `upc-agent` skill in `.claude/skills`.
- With your OK, to reach the editor: the `.uproject` plugin entries and config lines for the Python bridge, or for Epic's Unreal MCP those entries plus the MCP client entry (`.mcp.json` for Claude Code), each followed by an editor restart.

---

## What to ask it

| Ask | What you get |
| --- | --- |
| "Make a playable character called Knight from SKM_Knight." | A character Blueprint on your mesh and a game mode that spawns it in the open level, moving with the camera following. |
| "Give it a light combo from Attack_01 to Attack_03 and a heavy attack." | An action graph of the moves, with hit volumes on the hands and keys to press, on an unarmed item the character holds. |
| "Add a dodge that dodges through attacks." | A dodge move on its own key, with frames in which enemy swings pass through. |
| "Give it the sword SM_Sword with its own moves." | A weapon item with its own graph, keys and hit volumes along the blade. |
| "Set up lock-on with Soulslike strafing." | Lock-on on the middle mouse button, and the combat style that strafes around a locked target. |
| "How does the belt work?" | An answer from these docs, with the page's link. |

---

## What ships with the plugin

| File | What it is |
| --- | --- |
| `Resources/AGENTS.md` | The guide. |
| `Resources/AgentGuide/` | The guide's reference files, one per area, which the guide reads as it builds. |
| `Resources/AgentBridge/run_in_editor.py` | The Python bridge: sends the agent's Python to the open editor and prints what the editor printed. |
| `Resources/AgentSkills/upc-agent/SKILL.md` | The `/upc-agent` skill for Claude Code. |
| `Content/Python/upc_agent_tools` | The plugin's agent tools: Python helpers for montages, notify windows, blade sockets, a locomotion Animation Blueprint and action graphs, offered on Epic's MCP as the **UPCAgentTools** toolset. |

---

The rest of this page is the guide as the plugin ships it.

## Universal Player Combat: guide for AI agents

> **For the customer** (an agent reading this file skips this block): an AI agent may not find this file on its own. Fab installs the plugin under the engine's `Engine/Plugins/Marketplace/` folder. Tell the agent "Read Resources/AGENTS.md in the UniversalPlayerCombat plugin folder before you start". The same text is at [https://rigbak.com/docs/universal-player-combat/agents](https://rigbak.com/docs/universal-player-combat/agents). On first use, the agent offers to point the project at this file, so later sessions find it on their own.

Universal Player Combat (UPC) is an Unreal Engine 5.6 to 5.8 plugin for the player's side of combat: combos built as action graphs, dodges, items that bring their own moves, weapon hit detection, lock-on and a combat camera. You build a player from a character Blueprint, a few data assets and the customer's animations; no C++ is needed.

This file is written for an AI coding agent working in a customer's project while the Unreal Editor is open. Read all of it before you start. With the reference files in `AgentGuide/` beside it, it is enough to build what it describes; do not search the plugin's source files.

### One-time setup in a project

The first time this guide is used in a project, offer the customer this setup, which lets later sessions find the guide without being handed it. Make it only with their OK. It does not hold up the build: if they have not answered, build first and offer it again in your final report. Skip it when the project's `CLAUDE.md` or `AGENTS.md` already names this guide.

1. **Find the plugin folder**, the one holding `UniversalPlayerCombat.uplugin`: `Plugins/UniversalPlayerCombat` in the project folder, or, when Fab installed the plugin into the engine, a folder under `Engine/Plugins/Marketplace/` in the engine folder ("The Python bridge" below, step 1, finds the engine folder).
2. **Point the project at this guide.** Add this line to `CLAUDE.md` and to `AGENTS.md` in the project folder, making a file that is missing, with the plugin folder written out (relative to the project folder when the plugin is inside it): `For Universal Player Combat, read <plugin folder>/Resources/AGENTS.md before building anything with it.` Claude Code reads `CLAUDE.md`; Codex and Cursor read `AGENTS.md`.
3. **Claude Code only: the `/upc-agent` command.** Copy the folder `<plugin folder>/Resources/AgentSkills/upc-agent` into the project's `.claude/skills/` folder. Typing `/upc-agent` in a later session then starts it on this guide.

### Connecting to the editor

Everything below runs in the customer's open Unreal Editor. There are two ways in. Use the Python bridge unless the customer asks for Epic's Unreal MCP or this session already has it connected. Other Unreal MCP servers are not supported, even when one is connected.

#### The Python bridge (UE 5.6, 5.7 and 5.8)

The plugin ships `AgentBridge/run_in_editor.py` in its `Resources` folder, beside this file. If this file reached you on its own, without the `AgentBridge` folder beside it, find the plugin folder ("One-time setup in a project", step 1) and use its `Resources/AgentBridge`. Run with the engine's own Python, it sends Python to the open editor and prints what the editor printed. It needs a shell and nothing installed.

1. Read the project's `.uproject`. `EngineAssociation` is the engine version. For a version such as `5.7`, the engine folder is the `InstallLocation` of the `UE_5.7` entry in the launcher's `LauncherInstalled.dat` (JSON text despite its extension, so search it as text when a file reader refuses it): `C:/ProgramData/Epic/UnrealEngineLauncher/LauncherInstalled.dat` on Windows, usually naming `C:/Program Files/Epic Games/UE_5.7`, and `~/Library/Application Support/Epic/UnrealEngineLauncher/LauncherInstalled.dat` on a Mac. A source-built engine, and every engine on Linux, which has no launcher, is an identifier in `EngineAssociation` that `UnrealEngine/Install.ini` lists under `[Installations]` with its folder, in `~/.config/Epic/` on Linux and `~/Library/Application Support/Epic/` on a Mac; on Windows the registry key `HKEY_CURRENT_USER\Software\Epic Games\Unreal Engine\Builds` lists it. When none of these names the folder, ask the customer.
2. Check the connection:

   ```
   "<engine folder>/Engine/Binaries/ThirdParty/Python3/Win64/python.exe" "<plugin>/Resources/AgentBridge/run_in_editor.py" --project "<path to the .uproject>" -c "import unreal; print(unreal.SystemLibrary.get_engine_version())"
   ```

   It prints the engine version when it is connected. On a Mac the engine's Python is `Engine/Binaries/ThirdParty/Python3/Mac/bin/python3`, on Linux `Engine/Binaries/ThirdParty/Python3/Linux/bin/python3`. In PowerShell, put `&` and a space before the quoted `python.exe` path.
3. Exit code 2 means no editor answered. If the editor is closed, ask the customer to open the project. If it is open, the project needs a one-time setup. The bridge runs on Epic's Python Editor Script Plugin, which the engine marks Beta and UPC itself does not need, so make these two edits only with the customer's OK, then ask them to restart the editor:

   - In the `.uproject`'s `Plugins` list: `{ "Name": "PythonScriptPlugin", "Enabled": true }`.
   - In `Config/DefaultEngine.ini`:

     ```ini
     [/Script/PythonScriptPlugin.PythonScriptPluginSettings]
     bRemoteExecution=True
     ```

Write each step's Python to a file and run it: `... run_in_editor.py --project "<path to the .uproject>" step.py`. Exit code 1 means the Python raised; the traceback follows.

- Names a run defines stay defined in the editor until it restarts. Run the setup block in "Driving the editor with Python" once per editor session, and again after a restart or a `NameError`.
- The editor does nothing else while a run is going. Never wait or sleep inside the Python; start Play in one run and inspect it in the next.

#### Epic's Unreal MCP (UE 5.8)

Epic's **Unreal MCP** is two engine plugins, `ModelContextProtocol` and `AllToolsets`, which the engine marks Experimental and UPC itself does not need, so turn them on only with the customer's OK. Its tools are `list_toolsets`, `describe_toolset` and `call_tool`. To set it up, with that OK, make these edits, then ask them to restart the editor and this agent session:

- In the `.uproject`'s `Plugins` list: `{ "Name": "ModelContextProtocol", "Enabled": true }` and `{ "Name": "AllToolsets", "Enabled": true }`.
- In `Config/DefaultEditorPerProjectUserSettings.ini`, so the server starts with the editor:

  ```ini
  [/Script/ModelContextProtocolEngine.ModelContextProtocolSettings]
  bAutoStartServer=True
  ```
- The client entry, for Claude Code `.mcp.json` in the project root: `{ "mcpServers": { "unreal-mcp": { "type": "http", "url": "http://127.0.0.1:8000/mcp" } } }`. Cursor reads the same shape from `.cursor/mcp.json`; Codex reads `[mcp_servers.unreal-mcp]` with `url = "http://127.0.0.1:8000/mcp"` from `.codex/config.toml`.

It runs no editor Python (its `execute_tool_script` only strings its own tools together in a sandbox), so use the tool table in "Driving the editor without Python". The plugin adds its own toolset, **UPCAgentTools** (`list_toolsets` names it `upc_agent_tools._toolset.UPCAgentTools`; pass that name as `toolset_name`), for the steps Epic's tools do not cover.

#### The plugin's agent tools

On both routes the plugin's agent tools make montages, notify windows and the locomotion Animation Blueprint, add blade sockets to a weapon mesh, and wire the moves of an action graph. In Python they are the module `upc_agent_tools`, which ships with the plugin; on the Epic route they are the tools of the same names in **UPCAgentTools**, which take their arguments as "Driving the editor without Python" says.

### Before you start: ask the customer

Ask only what you cannot work out from asset names. State your assumptions in your final report so the customer can correct them.

**A player they already have.** A request to change the customer's player ("give my player a dodge") works on that player. Find it before building anything; build a new one only when there is none, and ask which one when there are several:

```python
reg = unreal.AssetRegistryHelpers.get_asset_registry()
players = [str(a.package_name) for a in reg.get_assets_by_path("/Game", recursive=True)
           if (a.get_tag_value("NativeParentClass") or "").endswith(".UPCCharacterBase'")]
print(players)
```

On Epic's MCP, list the customer's assets with `AssetTools.find_assets` and read each Blueprint's parent with `BlueprintTools.get_parent`. Then read what the player has: `DefaultItem` on its `EquipmentManagerComponent`, `StartingItems` on its `InventoryComponent`, and those items' action graphs (`upc.describe_action_graph`). Add to it: a new move goes into the graph of the item it belongs to, a new weapon into the inventory's `StartingItems`, and nothing the player already does is rebuilt.

1. **The mesh.** The Skeletal Mesh asset path. Read its Skeleton from the mesh.
2. **The animations.** A folder or a list. Sort them by name into light attacks (in combo order), heavy attack, dodge, hit reactions, idle and locomotion. Ask when the order of the light combo is unclear.
3. **An Animation Blueprint** for that skeleton, with a **Slot** node named `DefaultSlot` in its AnimGraph. If the customer has none, make one from the plugin's template ("No Animation Blueprint" below).
4. **A weapon mesh**, if the player holds one. Without one, the player fights with its hands.
5. **The folder and name.** Default: `/Game/Player/`, with `<Name>` a short PascalCase name such as `Knight`.

### Rules

- Create everything in the customer's folder. Do not edit or save anything under `/UniversalPlayerCombat/`; duplicate a plugin asset into the customer's folder when you need to change it.
- Level steps work in the level open in the editor. If that level is the plugin's own (its path starts with `/UniversalPlayerCombat/`), ask the customer which of their levels to use, or make one in their folder.
- Name assets `<Prefix>_<Name>_<Purpose>`: `BP_Knight`, `GM_Knight`, `ABP_Knight`, `AM_Knight_Light1`, `AG_Knight_Unarmed`, `Item_Knight_Unarmed`, `Item_Knight_Sword`, `IMC_Knight_Unarmed`, `Damage_Knight_Light`.
- Save each asset as soon as you finish it.
- Check each step as described before you move on.
- Use the exact property names in this file. `set_editor_property` accepts the C++ name (`HeldMesh`, `bIsMainWeapon`) as well as the Python name. Several editor labels differ from the property: the Visuals fragment's **Mesh** is `HeldMesh`, a transition's **Fires On** is its trigger, **When No Window Is Authored** is `ChainWithoutWindow`, **Main Weapon** is `bIsMainWeapon`.
- In Python, a C++ type drops its `U`, `A`, `F` or `E` prefix: `UUPCItemDefinition` is `unreal.UPCItemDefinition`, `FUPCTraceSocket` is `unreal.UPCTraceSocket`. Enum values are upper snake case: `EUPCTraceShape::CapsuleTwoPoint` is `unreal.UPCTraceShape.CAPSULE_TWO_POINT`. The engine's animation functions are `unreal.AnimationLibrary`.
- An input tag on an item mapping and on a graph transition must match exactly. A parent tag does not count.
- Make montages only from animations on the character's own skeleton, the one its mesh names. An animation of another skeleton plays with that skeleton's proportions, or not at all, and nothing warns. When one does not match, ask the customer for the mesh's own animation or a retargeted copy.
- Give `unreal.new_object` the asset that will hold the object as its `outer`, never a class default object (`unreal.get_default_object(...)`, `defaults(...)`), not even to look at a class's properties: a later object of that class with the same name takes it as its template, and the asset holding that object fails to save.

### Driving the editor with Python

Run this once at the start of each Python session. Every later snippet uses it.

```python
import unreal
import upc_agent_tools as upc
EAL = unreal.EditorAssetLibrary
TOOLS = unreal.AssetToolsHelpers.get_asset_tools()
DEMO = "/UniversalPlayerCombat/Demo"

def load(path):
    obj = EAL.load_asset(path)
    if obj is None:
        raise RuntimeError(f"No asset at {path}")
    return obj

def new_data_asset(cls, folder, name):
    factory = unreal.DataAssetFactory()
    factory.set_editor_property("DataAssetClass", cls)
    return TOOLS.create_asset(name, folder, cls, factory)

def duplicate(source_path, folder, name):
    return EAL.duplicate_asset(source_path, f"{folder}/{name}")

def defaults(bp):
    return unreal.get_default_object(bp.generated_class())

def tag(name):
    t = unreal.GameplayTag()
    t.import_text(name)
    return t

def tags(*names):
    return unreal.GameplayTagLibrary.make_gameplay_tag_container_from_array([tag(n) for n in names])

def length(anim):
    return unreal.AnimationLibrary.get_sequence_length(anim)

def save(*assets):
    for a in assets:
        if not EAL.save_loaded_asset(a, False):
            raise RuntimeError(f"{a.get_path_name()} did not save; log_warnings('EditorErrors') says why")

def log_warnings(prefix="LogUPC"):
    name = unreal.Paths.get_base_filename(unreal.Paths.get_project_file_path())
    path = unreal.Paths.project_log_dir() + name + ".log"
    with open(path, encoding="utf-8", errors="ignore") as f:
        return [l.strip() for l in f if prefix in l and ("Warning" in l or "Error" in l)]
```

The agent tools you call through `upc`:

| Call | Does |
| --- | --- |
| `upc.create_montage_from_animation(animation, folder, name, enable_root_motion)` | Makes a montage on the `DefaultSlot` slot, turns the animation's root motion on unless `enable_root_motion` is `False`, saves both. |
| `upc.add_notify_state_to_montage(montage, notify_state_class, start_seconds, duration_seconds, track)` | Adds a notify window on the `track` named (`Combat` when left out) and returns it, so you can set its properties. Save the montage afterward. |
| `upc.create_locomotion_anim_blueprint(skeleton, folder, name, idle, walk, run)` | Makes an Animation Blueprint for a skeleton that has none, and saves it. |
| `upc.add_blade_sockets(static_mesh)` | Adds the `TraceStart` and `TraceEnd` sockets a blade trace sweeps between, saves the mesh, and says where they sit. |
| `upc.add_montage_node(graph, node_name, montage, play_rate)` | Adds a move to an action graph. |
| `upc.add_transition(graph, from_node, to_node, input_tag, fires_on, timing, priority)` | Wires an input from one move to another. `fires_on`, `timing` and `priority` can be left out. |
| `upc.compile_action_graph(graph)` | Compiles the graph; returns an empty string when it is clean, else one line per problem. |
| `upc.describe_action_graph(graph)` | Lists the compiled moves and transitions. |
| `upc.add_node`, `upc.get_node_settings`, `upc.set_node_settings`, `upc.get_transition_settings`, `upc.set_transition_settings`, `upc.add_sub_graph`, `upc.remove_node`, `upc.remove_transition` | Other kinds of moves, a move's or a transition's settings, sub graphs, removing: `AgentGuide/ActionGraphs.md`. |
| `upc.set_montage_sections(montage, sections)`, `upc.add_gameplay_tag(tag, comment)`, `upc.validate_asset(asset)` | Montage sections, a new gameplay tag, an asset's own checks as text: `AgentGuide/Python.md`. |

Each call raises with a sentence saying what is wrong when it cannot do its job; read it and fix that before going on. `log_warnings()` is how you read UPC's setup warnings after a Play session. Each warning names the asset and the fix.

### Driving the editor without Python (Epic Unreal MCP, UE 5.8)

Epic's MCP lists three tools: `list_toolsets`, `describe_toolset` and `call_tool`. Use `describe_toolset` to read the exact argument names. In `call_tool`, `toolset_name` is the name `list_toolsets` prints, such as `editor_toolset.toolsets.asset.AssetTools`, and `tool_name` is the tool's own name, such as `find_assets`. Map each Python step to these tools:

| Python step | Epic Unreal MCP tool |
| --- | --- |
| `load`, find assets | `AssetTools.find_assets`, `AssetTools.load_asset` |
| `duplicate` | `AssetTools.duplicate` |
| `new_data_asset` | `DataAssetTools.create` |
| Blueprint with a parent class | `BlueprintTools.create` (`asset_type` is the parent class) |
| `defaults(bp)` and setting class defaults | `BlueprintTools.get_default_object`, then `ObjectTools.set_properties` |
| Instanced objects (item fragments, payloads) | `ObjectTools.set_properties` with the class path, as below |
| `save` | `AssetTools.save_assets` |
| Play and look | `EditorAppToolset.StartPIE`, `StopPIE`, `CaptureViewport` |
| `log_warnings` | `LogsToolset.GetLogEntries`, as below |
| `upc.*` | `UPCAgentTools` tools of the same names, given every argument (`[]` for no tags). A class is its path: `add_notify_state_to_montage` takes `/Script/UPCCore.UPCMeleeTraceNotifyState` and a track name (`Combat`), `add_node` takes `/Script/UPCActions.UPCHoldActionPayload`. `set_montage_sections` takes three lists in place of tuples: names, start seconds and next sections. |

The property names and values in every step are the same on both routes. How Epic's tools take values:

- **An asset** is its object path: the package path, a dot, and the asset name again, as in `/Game/Player/AM_Knight_Light1.AM_Knight_Light1`. The package path alone is refused.
- **A class** is `/Script/<Module>.<Name>`, the C++ name without its first letter. The classes this guide uses are in these modules: `UPCCore` (damage configs, trace sockets, the melee trace, state tag and anim effect windows, the gameplay event notify), `UPCMotion` (the approach window), `UPCActions` (action graphs, the action window, transitions, input mappings, ability sets), `UPCEquipment` (item definitions and their fragments, hold places, the inventory, the equipment manager, the equip commit notify), `UPCTargeting` (targetable, combat styles, targeting configs), `UPCFoundation` (telegraph marker windows, the movement component) and `UPCGame` (the character base). For example `/Script/UPCActions.UPCActionWindowNotifyState` and `/Script/UPCEquipment.UPCItemDefinition`.
- **An instanced object** (an item fragment, a payload) is made by giving its class path as a plain string: `{"Fragments": ["/Script/UPCEquipment.UPCItemFragment_Visuals", "/Script/UPCEquipment.UPCItemFragment_Equippable"]}`. Properties written beside the class path are dropped. Read the owner back with `ObjectTools.get_properties`, which gives each new object's path (`.../Item_Knight_Sword.Item_Knight_Sword:UPCItemFragment_Visuals_0`), and set its own properties on that path.
- **An array** changes one way per `ObjectTools.set_properties` call: elements added or removed while every other element stays exactly as `get_properties` returned it, or elements changed at the same length. Keep an existing instanced element as `{"refPath": "<its path>"}`. A call refused as ambiguous does both at once; split it in two.
- **Log lines** come from `LogsToolset.GetLogEntries`, which requires `pattern`, a regular expression, and reads only its own category unless `category` is empty: `{"category": "", "pattern": "LogUPC[A-Za-z]*: (Warning|Error)"}`.

### Recipe 1: a playable character from the customer's mesh

Set these first:

```python
F = "/Game/Player"                 # the customer's folder
N = "Knight"                       # the player's name
mesh = load("/Game/.../SKM_Knight")
abp = load("/Game/.../ABP_Knight") # the customer's Animation Blueprint, or the one "No Animation Blueprint" makes
probe = load("/Game/.../Attack_01")  # any animation on this skeleton, for bone checks
```

#### Step 1. Check the skeleton

Items are held at sockets or bones named `hand_r` (main hand) and `hand_l` (off hand).

```python
for bone in ("hand_r", "hand_l"):
    print(bone, unreal.AnimationLibrary.does_bone_name_exist(probe, bone))
```

**Check:** both print `True`. If not, find the hand bones' names with the customer; Step 2 points the body places at them.

#### Step 2. Make the character Blueprint

Duplicate the demo player. It carries everything a player needs beyond the base class: camera, movement and look input, sprint, Motion Warping, physical reactions, telegraph markers and a starting ability set with lock-on, and its `TeamComponent` from the base class is set to the player's team (Team ID 0). Give it a copy of that ability set without the demo's parry recoil and stun:

```python
bp = duplicate(f"{DEMO}/Characters/Player/BP_UPC_Character", F, f"BP_{N}")
abilities = duplicate(f"{DEMO}/Abilities/AbilitySet_UPC_Default", F, f"AbilitySet_{N}")
rows = [r for r in abilities.get_editor_property("GrantedGameplayAbilities")
        if r.get_editor_property("Ability").get_name() not in ("GA_UPC_ParryRecoil_C", "GA_UPC_PlayerStun_C")]
abilities.set_editor_property("GrantedGameplayAbilities", rows)
save(abilities)

d = defaults(bp)
d.set_editor_property("StartingAbilitySet", abilities)
body = d.get_editor_property("Mesh")
body.set_editor_property("SkeletalMeshAsset", mesh)
body.set_editor_property("AnimClass", abp.generated_class())
print(body.get_editor_property("ComponentTags"))       # must hold UPC.Montage and UPC.Attachment.Body
places = d.get_editor_property("EquipmentManagerComponent").get_editor_property("BodyPlaces")
for p in places:
    print(p.get_editor_property("Place"), p.get_editor_property("Socket"))
save(bp)
```

- `GA_UPC_ParryRecoil` and `GA_UPC_PlayerStun` play the demo mannequin's recoil and stun montages, which would make the customer's character perform the demo's motion. The copy keeps lock-on, sprint, interact, drop and the flinch on a poise break (`GA_UPC_HitReact`, which plays no animation). Without them the copy is never stunned and does not stagger back from a parried blow. To bring a recoil back on the customer's own animation, make a child Blueprint of `GA_UPC_ParryRecoil`, set its `StunMontage`, and add it to the copy. A stun is a child of `GA_UPC_Stun` added the same way; the duplicate's `UPC.Vital.Stance` row already sends `UPC.Event.GuardBreak` as its `DepletedGameplayEvent`, which starts it when the stance runs out. The demo's own player stun also lists `UPC.Style.Soulslike` in its `ActivationRequiredTags`, so only the Soulslike style stuns the demo player; leave that list empty for a stun under every style.
- `UPC.Montage` on the mesh is where montages play; `UPC.Attachment.Body` is where items attach. Without the attachment tag, items do not show. The duplicate keeps both.
- `BodyPlaces` maps each place to a socket: `UPC.Place.Main` to `hand_r`, `UPC.Place.Off` to `hand_l`, and three back places to the mannequin's `stow_back_r`, `stow_back_l` and `back_center_heavy` sockets. It also carries the demo's worn gear places: `UPC.Place.Head` on `head`, `UPC.Place.Ring.Left` on `ring_01_l`, `UPC.Place.Ring.Right` on `ring_01_r`, `UPC.Place.Shoulders` on `back_center_heavy`, and `UPC.Place.Chest`, `.Hands`, `.Feet` and `.Neck` with no socket. Change a row's `Socket` if the customer's skeleton names those bones otherwise, and delete the worn rows a game does not use. The back places matter only for items that hang on the body, the worn places only for worn gear (`AgentGuide/ItemsEquipment.md`, Worn gear).
- The duplicate keeps the demo mannequin's mesh placement, which fits a character built like the UE5 mannequin (origin at the feet, facing +Y). If the character floats, sinks or faces sideways, adjust the Mesh component's `RelativeLocation` and `RelativeRotation`.

**Check:** capture the Blueprint viewport; the mesh stands inside the capsule, facing along the blue arrow.

#### Step 3. Make the game mode and use it

This step sets the **GameMode Override** of the level open in the editor to the new game mode, so Play spawns the customer's character. Make the change, then name the level in your final report: it is one setting of that level, which the customer can set back in its World Settings.

Make the game mode a child Blueprint of the demo's `BP_UPC_GameMode` rather than a duplicate: the duplicated player's death and the respawn key bring the player back only through a `BP_UPC_GameMode`. A project with its own game mode makes it a child of `BP_UPC_GameMode` too. On Epic's MCP, `BlueprintTools.create` makes the child, with `asset_type` `/UniversalPlayerCombat/Demo/Game/BP_UPC_GameMode.BP_UPC_GameMode_C`.

```python
factory = unreal.BlueprintFactory()
factory.set_editor_property("ParentClass", load(f"{DEMO}/Game/BP_UPC_GameMode").generated_class())
gm = TOOLS.create_asset(f"GM_{N}", F, unreal.Blueprint, factory)
defaults(gm).set_editor_property("DefaultPawnClass", bp.generated_class())
save(gm)

world = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world()
world.get_world_settings().set_editor_property("DefaultGameMode", gm.generated_class())
unreal.get_editor_subsystem(unreal.LevelEditorSubsystem).save_current_level()
```

The game mode keeps the demo's player controller, `BP_UPC_PlayerController`, which adds the movement keys (`IMC_UPC_Default`: move, look, sprint, lock-on, dash) and the belt of item slots. To use it in every level, the customer sets it in Project Settings, Maps and Modes.

**Check:** press Play. The character spawns, moves with the movement keys, and the camera follows. It has no attacks yet.

### Recipe 2: a combo

A move is a node in an **action graph**. A node plays a montage; a **transition** names the input that leads from one node to the next. The graph reaches the character through an **item**: the item that drives combat hands its graph over while it is drawn. A character with nothing in its hands uses its **Default Item**, so a bare-handed combo lives on an "unarmed" item.

```python
seqs = {"Light1": load("/Game/.../Attack_01"),
        "Light2": load("/Game/.../Attack_02"),
        "Light3": load("/Game/.../Attack_03"),
        "Heavy": load("/Game/.../Attack_Heavy")}
```

#### Step 1. Make a montage per attack

```python
skeleton = mesh.get_editor_property("Skeleton")
other = [key for key, seq in seqs.items() if seq.get_editor_property("Skeleton") != skeleton]
if other:
    raise RuntimeError(f"{other} are animations of another skeleton than {mesh.get_name()}'s")
am = {key: upc.create_montage_from_animation(seq, F, f"AM_{N}_{key}") for key, seq in seqs.items()}
```

The tool turns root motion on, so an attack that steps forward moves the player, and saves the montage. It plays on the `DefaultSlot` slot.

For an attack animation that stops on its strike pose, with no return to idle, set the montage's **Blend Out** to the fade length (the demo combos use 0.5 s) and **Blend Out Trigger Time** to 0. The montage then holds its last frame and fades to idle, and looks the same on every player's machine in multiplayer.

**Check:** no error. An animation of another skeleton stops the step: ask the customer for the mesh's own animation or a retargeted copy, as the Rules say.

#### Step 2. Make the damage configs

A **Damage Config** (`UUPCDamageConfig`) says what one hit is worth: its health damage, and a poise row for how hard it staggers.

```python
def poise(amount):
    row = unreal.UPCVitalDamage()
    row.set_editor_property("VitalTag", tag("UPC.Vital.Poise"))
    row.set_editor_property("Amount", amount)
    return row

dmg_light = new_data_asset(unreal.UPCDamageConfig, F, f"Damage_{N}_Light")
dmg_light.set_editor_property("Damage", 10.0)
dmg_light.set_editor_property("AdditionalVitalDamage", [poise(20.0)])
dmg_heavy = new_data_asset(unreal.UPCDamageConfig, F, f"Damage_{N}_Heavy")
dmg_heavy.set_editor_property("Damage", 25.0)
dmg_heavy.set_editor_property("AdditionalVitalDamage", [poise(60.0)])
save(dmg_light, dmg_heavy)
```

The demo characters flinch only on the hit that empties their poise, so a config with no poise row never flinches them. 20 and 60 match the demo's punch and the sword's heavy swing; `AgentGuide/DamageDefense.md`, Hit reactions, covers poise.

#### Step 3. Mark the montages

Two notify windows on each attack:

- **UPC Melee Trace Window** (`UUPCMeleeTraceNotifyState`) over the frames that connect. Nothing takes damage without it. Without frame data from the customer, start it at 30% of the montage (40% for the heavy attack) and make it 25% long.
- **UPC Action Window** (`UUPCActionWindowNotifyState`) where the next press may start the next attack. It has no settings. Put it on every attack that leads into another, from about 55% to 90%. A press made earlier waits for the window.

```python
for key in ("Light1", "Light2", "Light3", "Heavy"):
    m, L = am[key], length(am[key])
    start = 0.40 if key == "Heavy" else 0.30
    w = upc.add_notify_state_to_montage(m, unreal.UPCMeleeTraceNotifyState, L * start, L * 0.25)
    w.set_editor_property("SocketIDs", [])          # [] sweeps every socket the item registered
    w.set_editor_property("DamageConfig", dmg_heavy if key == "Heavy" else dmg_light)
    if key in ("Light1", "Light2"):
        upc.add_notify_state_to_montage(m, unreal.UPCActionWindowNotifyState, L * 0.55, L * 0.35)
    save(m)
```

Tell the customer the times you picked; only a person can judge whether the window sits on the right frames.

**Check:** `unreal.AnimationLibrary.get_animation_notify_events(am["Light1"])` lists both windows.

#### Step 4. Build the action graph

Every graph starts with a node named `ROOT`, the character standing idle. Add one node per move and wire the transitions. A move that ends with nothing leading on returns to `ROOT` by itself.

```python
graph = new_data_asset(unreal.UPCActionGraphAsset, F, f"AG_{N}_Unarmed")
for key in ("Light1", "Light2", "Light3", "Heavy"):
    upc.add_montage_node(graph, key, am[key], 1.0)
upc.add_transition(graph, "ROOT", "Light1", "UPC.Input.LightAttack")
upc.add_transition(graph, "Light1", "Light2", "UPC.Input.LightAttack")
upc.add_transition(graph, "Light2", "Light3", "UPC.Input.LightAttack")
upc.add_transition(graph, "ROOT", "Heavy", "UPC.Input.HeavyAttack")
print(upc.compile_action_graph(graph))    # empty means no problems
print(upc.describe_action_graph(graph))
save(graph)
```

Compile after the last edit and before saving: an edited graph keeps running its previous compile until then. Node names must be unique; `ROOT` is taken.

Transition triggers (`fires_on`, `unreal.UPCTransitionTrigger`):

| Value | Editor label | Fires when |
| --- | --- | --- |
| `ON_STARTED` | Input Pressed | The input is pressed. The default; combos use it. |
| `ON_COMPLETED` | Input Released | The input is released. |
| `ON_CANCELLED` | Input Released Early | The input is released before a hold finishes. |
| `AUTO` | Action Completed | The move finished with nothing chaining. Pass `""` as the input tag. |
| `ON_FAILED` | Action Failed | The move could not start. Pass `""` as the input tag. |

Timing (`timing`, `unreal.UPCTransitionTiming`): `WAIT_FOR_WINDOW`, the default, waits for the running move's action window; `INTERRUPT` cuts the running move at once, for a dodge or a parry.

**Check:** the describe output lists `ROOT`, the four moves with their montages, and each transition with its input tag.

#### Step 5. Make the unarmed item's keys

The item adds its own key mapping while drawn. Duplicate the demo unarmed mapping context:

```python
imc = duplicate(f"{DEMO}/Weapons/Unarmed/IMC_UPC_Unarmed", F, f"IMC_{N}_Unarmed")
```

`IMC_UPC_Unarmed` already maps the left mouse button to `IA_UPC_LightAttack`, the right mouse button to `IA_UPC_HeavyAttack`, Q to `IA_UPC_Guard` and Space to `IA_UPC_Dash`, with gamepad keys for each, so the copy needs no new key. For a key the demo lacks, `key = unreal.Key()`, `key.import_text("F")`, `imc.map_key(action, key)`, then `save(imc)`.

#### Step 6. Make the unarmed item

An **Item Definition** (`UUPCItemDefinition`) is a list of **fragments**. The **Equippable** fragment (`UUPCItemFragment_Equippable`) makes it drawable and carries its graph, its keys and its hit volumes.

```python
item = new_data_asset(unreal.UPCItemDefinition, F, f"Item_{N}_Unarmed")
eq = unreal.new_object(unreal.UPCItemFragment_Equippable, outer=item)
eq.set_editor_property("ActionGraph", graph)
eq.set_editor_property("InputMappingContext", imc)

def mapping(action_name, tag_name):
    m = unreal.UPCInputMapping()
    m.set_editor_property("InputAction", load(f"{DEMO}/Inputs/{action_name}"))
    m.set_editor_property("InputTag", tag(tag_name))
    return m

eq.set_editor_property("InputMappings", [
    mapping("IA_UPC_LightAttack", "UPC.Input.LightAttack"),
    mapping("IA_UPC_HeavyAttack", "UPC.Input.HeavyAttack"),
    mapping("IA_UPC_Dash", "UPC.Input.Dash"),
])

def hand_socket(socket_id, bone):
    s = unreal.UPCTraceSocket()
    s.set_editor_property("ID", socket_id)
    s.set_editor_property("Shape", unreal.UPCTraceShape.SPHERE)
    s.set_editor_property("SocketOrBoneName", bone)
    s.set_editor_property("Radius", 12.0)
    return s                     # SourceComponentTag left empty: the socket follows the body mesh

eq.set_editor_property("TraceSockets", [hand_socket("HandR", "hand_r"), hand_socket("HandL", "hand_l")])
eq.set_editor_property("AttackStopDistance", 25.0)
item.set_editor_property("Fragments", [eq])
save(item)
```

- `AttackStopDistance` is how close in front of a target this item's attacks stop, in centimeters between the two bodies. The duplicated player's combat style stops attacks 50 cm short, which suits a blade; at that distance a punch stops out of reach, so fists take about 25. One move with a shorter reach than the rest, such as a shield bash beside a sword, sets its own `StopDistance` on its Play Montage node (`AgentGuide/TargetingStylesSteering.md`).
- A new Equippable fragment already holds the main hand in `HoldPlaces` (`UPC.Place.Main`).

**Check:** reload the item and print each fragment's class, `ActionGraph`, the three mappings, the two sockets and `AttackStopDistance`.

#### Step 7. Give it to the character

```python
d = defaults(bp)
d.get_editor_property("EquipmentManagerComponent").set_editor_property("DefaultItem", item)
d.get_editor_property("MeleeTraceComponent").set_editor_property("DefaultDamageConfig", dmg_light)
save(bp)
```

`DefaultItem` is what the character holds when nothing else is drawn.

#### Step 8. Play and check

```python
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
les.editor_request_begin_play()
```

Play starts once this run returns. Check it in the next run, a few seconds later:

```python
game = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_game_world()
unreal.SystemLibrary.execute_console_command(game, "UPC.Debug.Actions 1")
pawn = unreal.GameplayStatics.get_player_pawn(game, 0)
graph_comp = pawn.get_component_by_class(unreal.UPCActionGraphComponent)
print(graph_comp.process_action_input(tag("UPC.Input.LightAttack"), unreal.UPCTransitionTrigger.ON_STARTED))
print(graph_comp.get_action_state())
unreal.AutomationLibrary.take_high_res_screenshot(1280, 720, f"{N}_combo.png")
les.editor_request_end_play()
print(log_warnings())
```

**Success looks like:** `process_action_input` reports the press as entered, the overlay above the character names `Light1`, and the first attack plays. Ask the customer to press light attack three times: the attacks chain in order and the character returns to idle after the third. Against a demo enemy, its punch stops at the second light hit, as its poise breaks. `log_warnings()` returns nothing about this character.

### Recipe 3: a dodge

A dodge is a graph node like any attack, reached on its own input, whose montage holds **i-frames**: the tag `UPC.State.Intangible`, during which enemy swings pass through.

```python
am_dodge = upc.create_montage_from_animation(load("/Game/.../Roll_Back"), F, f"AM_{N}_Dodge")
L = length(am_dodge)
w = upc.add_notify_state_to_montage(am_dodge, unreal.UPCStateTagNotifyState, L * 0.05, L * 0.50)
w.set_editor_property("Tags", tags("UPC.State.Intangible"))
save(am_dodge)

interrupt = unreal.UPCTransitionTiming.INTERRUPT
press = unreal.UPCTransitionTrigger.ON_STARTED
upc.add_montage_node(graph, "Dodge", am_dodge, 1.0)
upc.add_transition(graph, "ROOT", "Dodge", "UPC.Input.Dash")
for key in ("Light1", "Light2", "Light3", "Heavy"):
    upc.add_transition(graph, key, "Dodge", "UPC.Input.Dash", press, interrupt, 10)
print(upc.compile_action_graph(graph))
save(graph)
```

- The dodge uses `UPC.Input.Dash`, which the unarmed item already maps to `IA_UPC_Dash`.
- `INTERRUPT` lets the dodge cut a swing. A running move still keeps its first 0.1 seconds (**Cancel Window Start** on the node) before an interrupt fires.
- A UPC melee trace skips a target holding `UPC.State.Intangible` (the trace's `PassThroughTags` default), so no other setting is needed.
- Multiplayer detail: for a dodge that travels far and fast, also put `UPC.State.Dashing` in the same State Tag Window and set `GraceTag` = `UPC.State.Dashing` on the character's `CharacterMovement` (a `UUPCCharacterMovementComponent`), so the server accepts the dash's travel instead of pulling the player back.

To give the dodge a stamina cost or a cooldown, see `AgentGuide/GatesCostsHooks.md`.

**Check:** in Play, pressing the dash key mid-swing cuts to the roll, and `UPC.Debug.Actions 1` names the `Dodge` node.

### Recipe 4: a weapon with its own moves

A weapon is an item with a look, its own graph, its own keys and hit volumes on its mesh.

#### Step 1. Sockets on the weapon mesh

A blade is traced by a two-point capsule between two sockets on its mesh, by default `TraceStart` (above the guard) and `TraceEnd` (the tip). Check, and add them when missing:

```python
sword_mesh = load("/Game/.../SM_Sword")
for name in ("TraceStart", "TraceEnd"):
    print(name, sword_mesh.find_socket(name) is not None)

print(upc.add_blade_sockets(sword_mesh))
```

This places `TraceStart` near the guard and `TraceEnd` near the tip, and saves the mesh. It changes the customer's mesh: tell them, and ask them to check the sockets sit on the blade when the grip is not at the mesh's origin.

#### Step 2. Montages, windows and graph

Repeat Recipe 2, Steps 1 to 4, with the weapon's animations: montages `AM_<Name>_Sword_*`, damage configs, windows, and a graph `AG_<Name>_Sword` kept in the variable `sword_graph`. Add the dodge node from Recipe 3 to this graph too: a drawn weapon's graph replaces the unarmed one, so moves the player keeps with a weapon in hand must be in the weapon's graph.

#### Step 3. The weapon's keys

```python
imc_sword = duplicate(f"{DEMO}/Weapons/Sword/IMC_UPC_Sword", F, f"IMC_{N}_Sword")
save(imc_sword)
```

`IMC_UPC_Sword` maps keys to `IA_UPC_LightAttack`, `IA_UPC_HeavyAttack` and `IA_UPC_Guard`. The dash key needs no mapping here: the controller's `IMC_UPC_Default` maps Space to `IA_UPC_Dash` whatever the player holds, and the item's `IA_UPC_Dash` row in Step 4 routes it to the sword's graph.

#### Step 4. The weapon item

```python
sword = new_data_asset(unreal.UPCItemDefinition, F, f"Item_{N}_Sword")
vis = unreal.new_object(unreal.UPCItemFragment_Visuals, outer=sword)
vis.set_editor_property("HeldMesh", sword_mesh)          # editor label: Mesh
eq = unreal.new_object(unreal.UPCItemFragment_Equippable, outer=sword)
eq.set_editor_property("ActionGraph", sword_graph)
eq.set_editor_property("InputMappingContext", imc_sword)
eq.set_editor_property("InputMappings", [
    mapping("IA_UPC_LightAttack", "UPC.Input.LightAttack"),
    mapping("IA_UPC_HeavyAttack", "UPC.Input.HeavyAttack"),
    mapping("IA_UPC_Dash", "UPC.Input.Dash"),
])
blade = unreal.UPCTraceSocket()
blade.set_editor_property("ID", "Blade")
blade.set_editor_property("SourceComponentTag", "UPC.ItemMesh")
blade.set_editor_property("Shape", unreal.UPCTraceShape.CAPSULE_TWO_POINT)
blade.set_editor_property("SocketOrBoneName", "TraceStart")
blade.set_editor_property("EndSocketOrBoneName", "TraceEnd")
blade.set_editor_property("Radius", 8.0)
eq.set_editor_property("TraceSockets", [blade])
sword.set_editor_property("Fragments", [vis, eq])
save(sword)
```

- An item with a Visuals `HeldMesh` and no `ItemActorClass` shows that mesh in the hand on a component tagged `UPC.ItemMesh`, which is why the blade socket's `SourceComponentTag` is `UPC.ItemMesh`. An empty tag would trace from the body mesh instead.
- `bIsMainWeapon` (editor label **Main Weapon**) is on by default: the sword's graph takes over while it is drawn.
- The item has no **Stowable** fragment, so it disappears when put away rather than hanging on the body. Stowing needs reach animations and back sockets on the customer's skeleton; see the Equipment page.

#### Step 5. Give it to the character

```python
inv = defaults(bp).get_editor_property("InventoryComponent")
items = list(inv.get_editor_property("StartingItems"))
if sword not in items:
    items.append(sword)
inv.set_editor_property("StartingItems", items)
save(bp)
```

`DefaultItem`, what the character holds when nothing else is drawn, is the demo's unarmed item on the duplicated character until Recipe 2, Step 7 sets the customer's own. When Recipe 2 was not run, clear it, so the demo's moves do not stay on the character:

```python
defaults(bp).get_editor_property("EquipmentManagerComponent").set_editor_property("DefaultItem", None)
save(bp)
```

The belt on `BP_UPC_PlayerController` takes the item into its first free slot and draws it, since nothing else is held. The slot keys in `IMC_UPC_Default` (1 to 4) draw and put away each slot.

**Check:** in Play, the sword sits in the right hand and the light attack plays the sword combo.

If the sword is held at the wrong angle, judge a correction from a capture and set it as the `Offset` of the Equippable fragment's `UPC.Place.Main` row in `HoldPlaces`. Python cannot set a Hold Places row's fields one by one, so write the whole row as text:

```python
offset = unreal.Transform(unreal.Vector(0, 0, 0), unreal.Rotator(roll=0, pitch=0, yaw=90), unreal.Vector(1, 1, 1))
row = unreal.UPCHoldPlaceSpec()
row.import_text(f'(Place=(TagName="UPC.Place.Main"),Offset={offset.export_text()})')
eq.set_editor_property("HoldPlaces", [row])     # eq: the sword's Equippable fragment from Step 4
save(sword)
```

### Recipe 5: lock-on

The duplicated character already locks on: its starting ability set (`AbilitySet_<Name>`, the copy of `AbilitySet_UPC_Default` from Recipe 1) grants the lock-on ability on `UPC.Input.LockOn`, and the demo controller maps the middle mouse button (gamepad: top face button) to `IA_UPC_LockOn`. Tell the customer the key.

1. **What can be locked.** Any actor with a **UPC Targetable** component (`UUPCTargetableComponent`) that is not on the player's team. Every character built on the UPC character base has one. For another kind of enemy, add the component to its Blueprint; `AimAtSocket` names where the lock aims.
2. **How locked movement feels.** The combat style decides. The duplicate uses the Over Shoulder style. For the Soulslike style's strafing around a locked target:

   ```python
   d = defaults(bp)
   d.get_editor_property("CombatComponent").set_editor_property("Style", load(f"{DEMO}/Combat/Styles/CombatStyle_UPC_Soulslike"))
   save(bp)
   ```
3. **Range.** The style's targeting config sets the scan radius (`ScanRadius`, 2000 cm) and whether a lock breaks with distance (`LockBreakDistance`, 0 turns it off). To change them, duplicate `TargetingConfig_UPC_Soulslike` and point the style's Targeting fragment at the copy, which means duplicating the style as well.

**Check:** place an enemy (the demo's `BP_UPC_Enemy_Weak` works) and play. Pressing the lock key holds the camera and facing on the enemy; the ability system carries `UPC.State.LockedOn`. Pressing again releases.

### No Animation Blueprint

The player needs an Animation Blueprint on its skeleton that blends idle, walk and run by speed and has a `DefaultSlot` Slot node, which is where every UPC montage plays. If the customer has none, ask whether one came with the animation pack under another name. Otherwise make one from the plugin's locomotion template:

```python
anims = "/Game/.../"
abp = upc.create_locomotion_anim_blueprint(mesh.get_editor_property("Skeleton"), F, f"ABP_{N}",
                                           load(anims + "Idle"), load(anims + "Walk_Fwd"), load(anims + "Run_Fwd"))
```

The new Animation Blueprint blends idle into walk by 200 cm/s and walk into run by 500 cm/s, and already carries the **UPC Hit Stop** and **Inertialization** nodes after its slot, so a landed hit freezes the pose. To match the character's own speeds, set `WalkSpeed` and `RunSpeed` on its class defaults (`defaults(abp)`) and save it.

An Animation Blueprint the customer already has works as it is. Hit stop (the frozen pose on a landed hit) also needs a **UPC Hit Stop** node after the slot with an **Inertialization** node after it; without it the log says so once and hits land without the freeze. Adding the node to the customer's own Animation Blueprint is a step for the customer.

### More tasks: the reference files

The recipes above build the common cases. For anything else, open the one file below that covers the task. Each lists its tasks first, then recipes with a check, the exact class and property names, and the common mistakes. They sit in `AgentGuide/` beside this file, in the plugin's `Resources` folder; when this file reached you on its own, open them there.

| Task | File |
| --- | --- |
| A property refuses a value, rows a plain write refuses, components on a Blueprint, montage sections, new gameplay tags, checking an asset | `AgentGuide/Python.md` |
| Other kinds of moves (a held guard, a wait, an ability), a move's settings, cancel windows, state tags, transitions back to idle, tag gates and conditions on a transition, sub graphs, editing or removing moves | `AgentGuide/ActionGraphs.md` |
| Stamina, attack costs, cooldowns and charges, resource and tag gates, sprint drain, slowing during a move | `AgentGuide/GatesCostsHooks.md` |
| New keys and input tags, chords, abilities on keys, sprint, pausing input | `AgentGuide/InputAbilities.md` |
| Item fragments, the off hand, shields, two-handed weapons, presets, stowing on the body, swing trails, worn gear (helmets, capes, rings) | `AgentGuide/ItemsEquipment.md` |
| Items that are not gear (keys, materials, potions), containers, inventory configs and their rules made from scratch, a keychain that collects keys by itself, weight limits, combine recipes, saving the inventory, inventory screens, using an item, storage boxes, bags on a bag bar, the belt, pickups, drops, weapon racks, loot chests, interactable objects | `AgentGuide/InventoryInteraction.md` |
| Hit effects, blocks and parries, health and stamina rows, hit reactions | `AgentGuide/DamageDefense.md` |
| Attacks closing on the target, combat styles, targeting configs, targetable actors | `AgentGuide/TargetingStylesSteering.md` |
| Camera settings, camera shots, attack warning markers | `AgentGuide/CameraTelegraphs.md` |
| Finishers, counters and other two-character moves (Paired Actions) | `AgentGuide/PairedActions.md` |
| Montage windows, several meshes, animation layers, footsteps, HUD, button prompts, project settings, debugging | `AgentGuide/AnimationHUDDebug.md` |

### Reference

#### Assets you create

| Asset | Class | Made with | Holds |
| --- | --- | --- | --- |
| Player character | Blueprint of `AUPCCharacterBase` (duplicate of `BP_UPC_Character`) | `duplicate` | Mesh, `StartingAbilitySet`, components below |
| Ability set | `UUPCAbilitySet` (duplicate of `AbilitySet_UPC_Default`) | `duplicate` | `GrantedGameplayAbilities` rows: `Ability`, `InputTag`, `InputAction` |
| Game mode | Child Blueprint of `BP_UPC_GameMode` | `unreal.BlueprintFactory` with `ParentClass` | `DefaultPawnClass`, controller `BP_UPC_PlayerController` |
| Animation Blueprint | Child of `ABP_UPC_LocomotionTemplate` | `upc.create_locomotion_anim_blueprint` | `IdleAnimation`, `WalkAnimation`, `RunAnimation`, `WalkSpeed`, `RunSpeed` |
| Action Graph | `UUPCActionGraphAsset` | `new_data_asset`, then `upc.add_*` and `upc.compile_action_graph` | Nodes and transitions; `DefaultInputBufferMode` |
| Item Definition | `UUPCItemDefinition` | `new_data_asset` | `Fragments`, `DisplayName`, `Icon`, `SlotTags`, `MaxStackSize` |
| Equippable fragment | `UUPCItemFragment_Equippable` | `unreal.new_object(..., outer=item)` | `ActionGraph`, `InputMappingContext`, `InputMappings`, `TraceSockets`, `AttackStopDistance`, `HoldPlaces`, `bIsMainWeapon`, `EquipmentTags`, `AbilitySetsToGrant`, `AnimLayerClass`, `ItemActorClass` |
| Visuals fragment | `UUPCItemFragment_Visuals` | `unreal.new_object(..., outer=item)` | `HeldMesh` (label Mesh) |
| Container Definition | `UUPCContainerDefinition` | `new_data_asset` | `Layout`, `SlotCount`, `GridSize`, `Rules` (`unreal.new_object(..., outer=definition)`), `bCarried`, `bTakesNewItems`, `AccessTag`, `bOpensBags` |
| Inventory Config | `UUPCInventoryConfig` | `new_data_asset` | `Containers` rows (`import_text`), `CarryRules`, `CombineSets`, `SortOrder`, `bEquipAtOnce`, `bPutAwayAtOnce`, `bCombineAtOnce`; set on the character's `InventoryComponent.Config` |
| Combine Set | `UUPCCombineSet` | `new_data_asset` | `Recipes` rows (two items, their counts, `Results`, `Seconds`), `Combines`; listed on the config's `CombineSets` |
| Damage Config | `UUPCDamageConfig` | `new_data_asset` | `Damage`, `AdditionalVitalDamage` (a `UPC.Vital.Poise` row), `DamageTags`, `HitEffects` |
| Input Mapping Context | `UInputMappingContext` (duplicate a demo one) | `duplicate`, `map_key` | Keys to Input Actions |
| Montage | `UAnimMontage` | `upc.create_montage_from_animation` | Notify windows below |

Character components you touch (all on the duplicated character): `Mesh` (`SkeletalMeshAsset`, `AnimClass`, `ComponentTags`), `EquipmentManagerComponent` (`DefaultItem`, `BodyPlaces`), `InventoryComponent` (`StartingItems`, `Config`), `MeleeTraceComponent` (`DefaultDamageConfig`), `VitalsComponent` (`Vitals` rows: `VitalTag`, `MaxValue`, `RegenRatePerSecond`), `CombatComponent` (`Style`), `TargetingComponent` (`TargetingConfig`), `CharacterMovement` (`GraceTag`).

Rows: `FUPCInputMapping` (`InputAction`, `InputTag`), `FUPCTraceSocket` (`ID`, `SourceComponentTag`, `Shape`, `SocketOrBoneName`, `EndSocketOrBoneName`, `Radius`, `LocalOffset`), `FUPCHoldPlaceSpec` (`Place`, `Offset`, `SocketOverride`), `FUPCBodyPlace` (`Place`, `Socket`).

#### Shipped assets you reuse

| Asset | Path under `/UniversalPlayerCombat/` |
| --- | --- |
| Locomotion template | `Core/Animation/ABP_UPC_LocomotionTemplate` |
| Demo player, controller, game mode | `Demo/Characters/Player/BP_UPC_Character`, `Demo/Characters/Player/BP_UPC_PlayerController`, `Demo/Game/BP_UPC_GameMode` |
| Input Actions | `Demo/Inputs/IA_UPC_LightAttack`, `IA_UPC_HeavyAttack`, `IA_UPC_Dash`, `IA_UPC_Guard`, `IA_UPC_LockOn`, `IA_UPC_Move`, `IA_UPC_Look`, `IA_UPC_Sprint` |
| Mapping contexts | `Demo/Inputs/IMC_UPC_Default`, `Demo/Weapons/Unarmed/IMC_UPC_Unarmed`, `Demo/Weapons/Sword/IMC_UPC_Sword` |
| Combat styles and targeting | `Demo/Combat/Styles/CombatStyle_UPC_OverShoulder`, `_Soulslike`, `_Freeflow`, each with a `TargetingConfig_UPC_*` |
| Ability set with lock-on (Recipe 1 copies it without the parry recoil and stun) | `Demo/Abilities/AbilitySet_UPC_Default` |
| Damage configs | `Demo/Weapons/Unarmed/Damage_UPC_Unarmed`, `Demo/Weapons/Sword/Damage_UPC_Sword` |
| A test enemy | `Demo/Characters/Enemy/BP_UPC_Enemy_Weak` |
| Demo level and complete examples | `Demo/Maps/LVL_UPC_Demo`; `Demo/Weapons/Sword/Item_UPC_Sword` and `AG_UPC_Sword` |
| Inventory screens, one per ready inventory style, opened with I or a tap of the pad's View button | `Demo/UI/Widgets/Inventory/WBP_UPC_InventoryScreen_OpenWorldList`, `_SurvivalGrid`, `_PartyRPG` (`AgentGuide/InventoryInteraction.md`, Inventory screens) |
| Every demo item to try on: walk onto a pad to equip its item, again to put it away; an inventory style pad switches the inventory to that style and its starter kit | `Demo/Maps/LVL_UPC_Wardrobe` (its hall `LVL_UPC_Wardrobe_Hall` also streams into `LVL_UPC_Demo`, reached by the Wardrobe plate in the start room), its pads `Demo/Showcase/Equipment/BP_UPC_EquipPad` and `Demo/Showcase/Inventory/BP_UPC_InventoryStylePad` |

The demo content must stay in the project while anything references it. Duplicate a demo asset into the customer's folder when the customer plans to remove the demo. The demo's animation layers, reach montages and the montages of `GA_UPC_Stun` and `GA_UPC_ParryRecoil` belong to the demo mannequin's skeleton, so do not give them to another skeleton's character or items.

#### Montage notifies

| Notify (editor name) | Class | Needed for | Key properties |
| --- | --- | --- | --- |
| UPC Melee Trace Window | `UUPCMeleeTraceNotifyState` | Any hit to deal damage | `SocketIDs`, `DamageConfig`, `ExtraDamageTags` |
| UPC Action Window | `UUPCActionWindowNotifyState` | Where the next press may chain. Without one, the node's **When No Window Is Authored** (`ChainWithoutWindow`) decides: by default the next move starts when this one ends | none |
| UPC State Tag Window | `UUPCStateTagNotifyState` | Tags for a stretch: `UPC.State.Intangible` (i-frames), `UPC.State.HyperArmor` (less poise damage) | `Tags` |
| UPC Approach Window | `UUPCApproachWindowNotifyState` | Gliding to the target during a wind-up |  |
| UPC Telegraph Window | `UUPCTelegraphMarkerNotifyState` | A warning marker over an enemy's swing |  |
| UPC Anim Effect Window | `UUPCAnimEffectWindowNotifyState` | Swing trails (`UPC.AnimEffect.Weapon.Trail`) and glows | `Effect` |
| UPC Anim Effect | `UUPCAnimEffectNotify` | Footsteps, landings and other one-frame sounds the character's Anim Effect Bank plays | `Effect` |
| UPC Equipment Commit | `UUPCEquipmentCommitNotify` | The swap frame of a draw or stow reach |  |
| UPC Gameplay Event | `UUPCGameplayEventNotify` | Sending a gameplay event at a frame | `EventTag` |

Attack steering (an attack turning to the target and closing the gap) uses the engine's **Motion Warping** notify with the **UPC Skew Warp** modifier and Warp Target Name `UPCTarget`. The duplicated character has the Motion Warping component it needs.

#### Gameplay tags you set

| Tag | Use |
| --- | --- |
| `UPC.Input.LightAttack`, `.HeavyAttack`, `.Dash`, `.Dodge`, `.Guard`, `.LockOn`, `.Sprint`, `.Special`, `.Aim`, `.Fire`, `.Execute`, `.Drop` | Input tags on item mappings and graph transitions |
| `UPC.State.Intangible` | i-frames: melee traces skip the holder |
| `UPC.State.HyperArmor` | Less poise damage, so light hits do not flinch |
| `UPC.State.Dashing` | With `GraceTag`, server trust for a fast dash |
| `UPC.State.Blocking` | A held guard (node `AppliedTags`) |
| `UPC.State.ActionsBlocked` | On the character, drops every press |
| `UPC.State.ActionRunning`, `UPC.State.LockedOn` | Read only: a move is running; a lock is held |
| `UPC.Place.Main`, `UPC.Place.Off`, `UPC.Place.Back`, `.Back.Left`, `.Back.Right` | Body places and hold places |
| `UPC.Place.Head`, `.Chest`, `.Hands`, `.Feet`, `.Shoulders`, `.Neck`, `.Ring.Left`, `.Ring.Right`, `.Top`, `.Bottom` | The demo's worn gear places; a game names its own |
| `UPC.Vital.Health`, `.Stamina`, `.Stance` | Vitals rows |
| `UPC.ItemMesh` | Source Component Tag for a socket on a mesh-only item |

There are no input tags for moving and looking; those keys are wired in the character Blueprint.

#### Common mistakes

| Symptom | Cause and fix |
| --- | --- |
| Press does nothing | The item mapping's `InputTag` differs from the transition's, the item's mapping context lacks the Input Action, or the graph was not compiled after the last change (`upc.describe_action_graph` says so on its first line). |
| Press does nothing and the log names the ability system | The character has no Ability System Component; the duplicate has one. |
| Combo never chains | The press landed before the action window and the buffer is set to Ignore, the transition's trigger is not `ON_STARTED`, or the press came after the move ended, later than its late input grace (the graph's `LateInputGrace`, or the move's own override); a move that leaves through an `AUTO` transition to another move grants none. |
| Item not visible in the hand | No mesh tagged `UPC.Attachment`, or the body place's socket is missing on the skeleton. The log names both. |
| Hits pass through | No UPC Melee Trace Window on the montage, the window names a socket ID the item does not register, the blade socket's `SourceComponentTag` is wrong, the target is on the player's team, or an unarmed item has no short `AttackStopDistance`, so punches stop out of reach. The log names an unregistered socket ID. |
| Weapon held crooked | Set `Offset` on the item's `HoldPlaces` row, as in Recipe 4, Step 5. |
| T-pose, or montages do not play | The Animation Blueprint is for another skeleton, or has no `DefaultSlot` Slot node. |
| An attack plays stretched, twisted or not at all | Its montage was made from an animation of another skeleton (Rules). |
| Lock-on finds nothing | The target has no UPC Targetable component, or is on the player's team; or the targeting config is unset. |
| Dodge does not avoid hits | The dodge montage has no State Tag Window with `UPC.State.Intangible`, or it does not cover the frames of the incoming hit. |
| Draw or stow plays nothing on a custom skeleton | Reach montages from the demo belong to the mannequin. Leave the item without a Stowable fragment, or give it reach montages on the customer's skeleton. |

Debug in Play: `UPC.Debug.Actions 1` writes each character's graph state above its head; `UPC.Logs Verbose` raises every UPC log category and `UPC.Logs` puts them back.

### Learn more

When the customer wants to understand something rather than build it, find the page in [https://rigbak.com/docs/universal-player-combat/llms.txt](https://rigbak.com/docs/universal-player-combat/llms.txt), which lists every docs page with a one-line summary, fetch the page's Markdown version (the page URL plus `.md`), answer from it, and give them the page link. The table names the main pages.

| Topic | Page |
| --- | --- |
| Overview | [https://rigbak.com/docs/UniversalPlayerCombatDocs](https://rigbak.com/docs/UniversalPlayerCombatDocs) |
| Action graphs, combos, transitions | [https://rigbak.com/docs/universal-player-combat/action-graph](https://rigbak.com/docs/universal-player-combat/action-graph) |
| The graph editor | [https://rigbak.com/docs/universal-player-combat/action-graph-editor](https://rigbak.com/docs/universal-player-combat/action-graph-editor) |
| Gates, costs, cooldowns | [https://rigbak.com/docs/universal-player-combat/gates-costs-cooldowns](https://rigbak.com/docs/universal-player-combat/gates-costs-cooldowns) |
| Input and key mappings | [https://rigbak.com/docs/universal-player-combat/input](https://rigbak.com/docs/universal-player-combat/input) |
| Items and fragments | [https://rigbak.com/docs/universal-player-combat/items](https://rigbak.com/docs/universal-player-combat/items) |
| Drawing, stowing, body places | [https://rigbak.com/docs/universal-player-combat/equipment](https://rigbak.com/docs/universal-player-combat/equipment) |
| Inventory | [https://rigbak.com/docs/universal-player-combat/inventory](https://rigbak.com/docs/universal-player-combat/inventory) |
| Taking only some of the inventory: a belt alone, no grid, no bags | [https://rigbak.com/docs/universal-player-combat/inventory-pick-what-you-need](https://rigbak.com/docs/universal-player-combat/inventory-pick-what-you-need) |
| The ready inventory styles: survival grid, party RPG, open world list | [https://rigbak.com/docs/universal-player-combat/inventory-survival-grid](https://rigbak.com/docs/universal-player-combat/inventory-survival-grid), [https://rigbak.com/docs/universal-player-combat/inventory-party-rpg](https://rigbak.com/docs/universal-player-combat/inventory-party-rpg), [https://rigbak.com/docs/universal-player-combat/inventory-open-world-list](https://rigbak.com/docs/universal-player-combat/inventory-open-world-list) |
| Inventory containers, grids, weight and caps | [https://rigbak.com/docs/universal-player-combat/inventory-containers](https://rigbak.com/docs/universal-player-combat/inventory-containers) |
| Bags and banks | [https://rigbak.com/docs/universal-player-combat/inventory-bags](https://rigbak.com/docs/universal-player-combat/inventory-bags) |
| Combines (rounds into a magazine) | [https://rigbak.com/docs/universal-player-combat/inventory-combines](https://rigbak.com/docs/universal-player-combat/inventory-combines) |
| Loot chests | [https://rigbak.com/docs/universal-player-combat/loot-chests](https://rigbak.com/docs/universal-player-combat/loot-chests) |
| Inventory screens | [https://rigbak.com/docs/universal-player-combat/inventory-screens](https://rigbak.com/docs/universal-player-combat/inventory-screens) |
| Saving the inventory | [https://rigbak.com/docs/universal-player-combat/inventory-saving](https://rigbak.com/docs/universal-player-combat/inventory-saving) |
| Inventory rules, combines and item data in Blueprint | [https://rigbak.com/docs/universal-player-combat/inventory-blueprint](https://rigbak.com/docs/universal-player-combat/inventory-blueprint) |
| The belt of slots | [https://rigbak.com/docs/universal-player-combat/quick-bar](https://rigbak.com/docs/universal-player-combat/quick-bar) |
| Pickups and drops | [https://rigbak.com/docs/universal-player-combat/pickups-drops-holders](https://rigbak.com/docs/universal-player-combat/pickups-drops-holders) |
| Combat styles | [https://rigbak.com/docs/universal-player-combat/combat-styles](https://rigbak.com/docs/universal-player-combat/combat-styles) |
| Targeting and lock-on | [https://rigbak.com/docs/universal-player-combat/targeting](https://rigbak.com/docs/universal-player-combat/targeting) |
| Attack steering | [https://rigbak.com/docs/universal-player-combat/attack-steering](https://rigbak.com/docs/universal-player-combat/attack-steering) |
| Camera | [https://rigbak.com/docs/universal-player-combat/camera](https://rigbak.com/docs/universal-player-combat/camera) |
| Sprint and speed | [https://rigbak.com/docs/universal-player-combat/sprint-and-speed](https://rigbak.com/docs/universal-player-combat/sprint-and-speed) |
| Hit detection | [https://rigbak.com/docs/universal-player-combat/melee-trace](https://rigbak.com/docs/universal-player-combat/melee-trace) |
| Damage and hit effects | [https://rigbak.com/docs/universal-player-combat/damage-and-hit-effects](https://rigbak.com/docs/universal-player-combat/damage-and-hit-effects) |
| Blocks and parries | [https://rigbak.com/docs/universal-player-combat/defense](https://rigbak.com/docs/universal-player-combat/defense) |
| Hit reactions | [https://rigbak.com/docs/universal-player-combat/hit-reactions](https://rigbak.com/docs/universal-player-combat/hit-reactions) |
| Physical reactions and ragdoll | [https://rigbak.com/docs/universal-player-combat/physical-reactions](https://rigbak.com/docs/universal-player-combat/physical-reactions) |
| Health and stamina | [https://rigbak.com/docs/universal-player-combat/vitals](https://rigbak.com/docs/universal-player-combat/vitals) |
| Telegraphs | [https://rigbak.com/docs/universal-player-combat/telegraphs](https://rigbak.com/docs/universal-player-combat/telegraphs) |
| Interaction | [https://rigbak.com/docs/universal-player-combat/interaction](https://rigbak.com/docs/universal-player-combat/interaction) |
| Interaction payloads | [https://rigbak.com/docs/universal-player-combat/interaction-payloads](https://rigbak.com/docs/universal-player-combat/interaction-payloads) |
| Paired Actions (finishers) | [https://rigbak.com/docs/universal-player-combat/paired-actions](https://rigbak.com/docs/universal-player-combat/paired-actions) |
| Pairing editor | [https://rigbak.com/docs/universal-player-combat/pairing-editor](https://rigbak.com/docs/universal-player-combat/pairing-editor) |
| Montages, mesh tags, hit stop | [https://rigbak.com/docs/universal-player-combat/animation-integration](https://rigbak.com/docs/universal-player-combat/animation-integration) |
| HUD and input prompts | [https://rigbak.com/docs/universal-player-combat/hud-and-input-prompts](https://rigbak.com/docs/universal-player-combat/hud-and-input-prompts) |

### Reference files

The guide's reference files ship beside it, in the plugin's `Resources/AgentGuide/` folder. Each follows under a heading naming its file.

#### Action graphs: every card, edited from Python (`AgentGuide/ActionGraphs.md`)

`AGENTS.md`, Recipe 2, builds a combo from montage moves and input transitions. This file covers the rest of the graph: other kinds of moves, a move's settings, transitions back to idle, tag gates and conditions on a transition, sub graphs, and editing or removing what exists.

Tasks: a move held while a button is down (a guard) and released back to idle; a move that fires an ability or waits; a heavy attack that cannot be canceled early; tags on the character while a move runs; a transition only while a tag is held or only in one stick direction; the next attack starting at once; moves that exist only while an item is drawn; renaming, editing or removing a move or a transition.

##### How a move is edited

`upc.get_node_settings(graph, name)` returns the move's settings as an `unreal.UPCActionNode`; edit the copy, nested fields included, and hand it to `upc.set_node_settings(graph, name, settings)`:

```python
s = upc.get_node_settings(graph, "Heavy")
s.cancellation.cancel_window_start = 0.3        # nothing may cut the heavy for 0.3 s
upc.set_node_settings(graph, "Heavy", s)
print(upc.compile_action_graph(graph))
save(graph)
```

Every edit waits for `upc.compile_action_graph` before it reaches the game; compile after the last edit and before saving. `upc.describe_action_graph(graph)` then lists each move with its payload, the values changed from their defaults, its state tags, costs, gates and hooks, and each transition with its tags and conditions.

**On Epic's MCP**, `set_node_settings` cannot make a new cost, gate or hook, since the MCP has no owner to make it in. Add them on the move's card instead, the object that owns the move's payload: `get_node_settings` returns the payload's path, and the card's path is that path without its last part (`.../AG_Knight.AG_Knight:<graph>.<card>`). Call `ObjectTools.set_properties` on the card with `{"ActionNodeData": {"Costs": ["/Script/UPCActions.UPCActionCost_Vital"]}}` (or `Gates`, `Hooks`), read the card back with `ObjectTools.get_properties` (`properties`: `["ActionNodeData"]`) for each new object's path, set the object's own properties there, then `compile_action_graph`. Everything else goes through `set_node_settings`.

##### A move's settings (`unreal.UPCActionNode`)

| Field | Holds |
| --- | --- |
| `NodeName` | The move's name. Write another name to rename it; transitions follow. |
| `Execution` (`unreal.UPCNodeExecution`) | `Payload`: what the move does (below). `InputBufferOverride` (`unreal.UPCInputBufferOverride`: `USE_GRAPH_DEFAULT`, `IGNORE`, `ACCEPT_FIRST`, `ACCEPT_LAST`): what an early press does. `bOverrideLateInputGrace` with `LateInputGrace` (seconds, 0 off): this move's own late input grace in place of the graph's. `bRetriggerFromRootOnFailedStart`: a move that cannot start goes back to idle and tries the same press once more. |
| `Cancellation` (`unreal.UPCNodeCancellation`) | `CancelWindowStart` (seconds, default 0.1): the earliest an Interrupt transition may cut the move. `bCanBeCancelled`, `CancellableByInputs`, `IgnoredCancelInputs`: whether, and by which input tags, a press may cancel the move outright. |
| `State` (`unreal.UPCNodeState`) | `AppliedTags`: tags the character holds while the move runs, such as `UPC.State.Blocking` for a guard or `UPC.State.HyperArmor`. |
| `Costs`, `Gates`, `Hooks` | Stamina costs, cooldowns and resource checks, and effects while the move runs: `AgentGuide/GatesCostsHooks.md`. |

##### Payloads: what a move does

`upc.add_montage_node` makes a Play Montage move. `upc.add_node(graph, name, payload_class)` makes any other kind; read its payload from `get_node_settings(...).get_editor_property("Execution").get_editor_property("Payload")`, set its fields, and write the settings back. On Epic's MCP, `add_node` takes the payload's class path, such as `/Script/UPCActions.UPCHoldActionPayload`.

| Class (editor label) | Does | Fields |
| --- | --- | --- |
| `UUPCMontageActionPayload` (Play Montage) | Plays a montage | `Montage`, `PlayRate`, `SectionName` (start from a section; see `AgentGuide/Python.md` for adding sections), `ChainWithoutWindow` (label **When No Window Is Authored**; `AT_COMPLETION` starts the next move when this one ends, `FROM_ENTRY` lets the next press start it at once), `BackMontage`, `LeftMontage`, `RightMontage` (played in place of `Montage` when the stick pushes that way from the body's facing; empty plays `Montage`), `bTurnToInputAtStart` (turns the body to the stick as the move starts while rotation follows movement, then plays `Montage`), `StopDistance` (cm between the bodies this attack stops at; -1 uses a playing window's, else the weapon's, else the style's; `AgentGuide/TargetingStylesSteering.md`), `MakeRoomSeconds` (-1 follows the style, 0 skips the step back for this move, above 0 turns it on) |
| `UUPCHoldActionPayload` (Hold) | Runs until a transition leaves it, so its state tags stay on as long as the button is held. Plays no animation | `bOpenActionWindow` (on: any input leaves at once) |
| `UUPCDelayActionPayload` (Delay) | Waits, then ends so its Action Completed transition fires | `DelaySeconds`, `bOpenActionWindow` |
| `UUPCAbilityEventActionPayload` (Trigger Ability / Event) | Sends a gameplay event to the character, or activates its abilities by tag | `Mode` (`SEND_EVENT`, `ACTIVATE_BY_TAG`), `EventTag`, `EventMagnitude`, `AbilityTags`, `bEndImmediately` |
| `UUPCPlayPairedActionPayload` (Play Paired Action) | Starts a synced two-body move | `AgentGuide/PairedActions.md` |

##### Recipe: a held guard with a parry window

The guard plays the customer's guard animation and holds its pose while the guard button is down; releasing it returns to idle (a transition to `ROOT` ends the running move). The move holds `UPC.State.Blocking`, and the first frames of the raise hold `UPC.State.Parrying`, which is what a guard rule set reads (`AgentGuide/DamageDefense.md`).

```python
am_guard = upc.create_montage_from_animation(load("/Game/.../Guard"), F, f"AM_{N}_Guard", enable_root_motion=False)
L = length(am_guard)
upc.set_montage_sections(am_guard, [("Raise", 0.0, "Hold"), ("Hold", L * 0.5, "Hold")])   # the second half loops while held
w = upc.add_notify_state_to_montage(am_guard, unreal.UPCStateTagNotifyState, 0.0, min(0.25, L * 0.5))
w.set_editor_property("Tags", tags("UPC.State.Parrying"))
save(am_guard)

upc.add_montage_node(graph, "Guard", am_guard, 1.0)
s = upc.get_node_settings(graph, "Guard")
s.state.applied_tags = tags("UPC.State.Blocking")
s.cancellation.cancel_window_start = 0.0        # a quick tap lets go at once
upc.set_node_settings(graph, "Guard", s)

upc.add_transition(graph, "ROOT", "Guard", "UPC.Input.Guard")
upc.add_transition(graph, "Guard", "ROOT", "UPC.Input.Guard", unreal.UPCTransitionTrigger.ON_COMPLETED, unreal.UPCTransitionTiming.INTERRUPT)
print(upc.compile_action_graph(graph))
save(graph)
```

- The item's mappings need a row for `IA_UPC_Guard` on `UPC.Input.Guard` (`AGENTS.md`, Recipe 2, Step 6). Both demo mapping contexts map it already.
- Pick the loop point from the animation: the frame where the guard is fully up. Tell the customer the times you picked.
- The looping guard opens no action window, so every way out of it is an `INTERRUPT` transition: the release above, and an attack out of the guard. A release that waits for the window never lands.
- A guard whose pose comes from the item's animation layer reading `UPC.State.Blocking`, as the demo's do, needs no montage: make the move with `upc.add_node(graph, "Guard", unreal.UPCHoldActionPayload)` instead, with the same tags and transitions.

**Check:** `upc.describe_action_graph(graph)` lists `Guard plays AM_..._Guard ...; state tags UPC.State.Blocking`, `UPC.Input.Guard, Input Pressed ... -> Guard` from ROOT and `UPC.Input.Guard, Input Released, Interrupt ... -> ROOT` from Guard. `unreal.AnimationLibrary.get_animation_notify_events(am_guard)` lists the State Tag Window, and `am_guard.get_num_sections()` is 2.

##### Transitions

`upc.add_transition(graph, from_node, to_node, input_tag, fires_on, timing, priority)`; `to_node` may be `ROOT`.

| `fires_on` (`unreal.UPCTransitionTrigger`) | Editor label | Fires when |
| --- | --- | --- |
| `ON_STARTED` | Input Pressed | The input is pressed (combos) |
| `ON_COMPLETED` | Input Released | The input is released (letting go of a hold) |
| `ON_CANCELLED` | Input Released Early | The input is released before its Input Action's hold trigger finishes |
| `AUTO` | Action Completed | The move ended on its own; input tag `""` |
| `ON_FAILED` | Action Failed | The move could not start (a cost, a gate); input tag `""` |

`timing` is `WAIT_FOR_WINDOW` (the press waits for the running move's action window) or `INTERRUPT` (it cuts the running move once `CancelWindowStart` has passed: dodges and parries). Higher `priority` wins between transitions on the same input; a transition with conditions wins over an equal one without.

**Dodge from every move:** one `add_transition(graph, move, "Dodge", "UPC.Input.Dash", ON_STARTED, INTERRUPT, 10)` per move, as `AGENTS.md`, Recipe 3, does, or one `add_alias(graph, "Dodge", "UPC.Input.Dash", ON_STARTED, INTERRUPT, 10)`, which also reaches moves added later and the moves a sub graph imports. `required_tags` and `blocked_tags` on an alias route one press to different moves by the character's tags.

**A dodge per direction:** one Play Montage move whose `Montage` is the forward dodge and whose `BackMontage`, `LeftMontage` and `RightMontage` hold the others; the stick's push against the body's facing picks one as the move starts, and in multiplayer the player's own machine makes the pick and the server plays the same. With `bTurnToInputAtStart` on, a character whose rotation follows its movement turns to the push and plays `Montage`, so one roll goes every way; locked on or facing the camera, it plays the direction montages instead. The demo routes Dash by style this way: `AG_UPC_Sword` sends it to `Hop` under `UPC.Style.OverShoulder`, to `Dodge Locked` under `UPC.State.LockedOn`, and to `Dodge` (the turning roll) otherwise.

###### Transition settings

`upc.get_transition_settings(graph, from_node, to_node, input_tag, fires_on=None)` returns an `unreal.UPCActionTransition` with `InputTag`, `Trigger`, `Timing`, `Priority`, `RequiredTags`, `BlockedTags` (tags on the character; blocked beats required) and `Conditions`. Edit it and write it back with `upc.set_transition_settings(graph, from_node, to_node, input_tag, settings, fires_on=None)`, naming the transition as it is before the edit.

```python
t = upc.get_transition_settings(graph, "ROOT", "BackStep", "UPC.Input.LightAttack")
cond = unreal.new_object(unreal.UPCTransitionCondition_Direction)
cond.set_editor_property("RequiredDirection", unreal.UPCInputDirection.BACK)
t.set_editor_property("Conditions", [cond])
upc.set_transition_settings(graph, "ROOT", "BackStep", "UPC.Input.LightAttack", t)
```

With that condition, light attack pulled back plays `BackStep` and a plain transition on the same input plays the usual first attack.

| Condition class (editor label) | Passes when | Fields |
| --- | --- | --- |
| `UUPCTransitionCondition_Direction` (Direction) | The stick points one way at the press | `RequiredDirection` (`unreal.UPCInputDirection`: `NEUTRAL`, `FORWARD`, `FORWARD_RIGHT`, `RIGHT`, `BACK_RIGHT`, `BACK`, `BACK_LEFT`, `LEFT`, `FORWARD_LEFT`), or `bUseAngleRange` with `MinAngleDegrees` and `MaxAngleDegrees` |
| `UUPCTransitionCondition_VitalThreshold` (Vital Threshold) | A vital meets a level | `VitalTag`, `Comparison`, `Threshold`, `Tolerance`, `bUseFraction` |
| `UUPCTransitionCondition_TargetState` (Target State) | The aimed-at enemy holds tags | `RequiredTags`, `BlockedTags`, `bRepickAtPress` |
| `UUPCTransitionCondition_TargetDistance` (Target Distance) | The aimed-at enemy is near or far | `Comparison`, `Distance` (cm), `Tolerance`, `bRepickAtPress` |
| `UUPCTransitionCondition_CooldownReady` (Cooldown Ready) | A cooldown has a charge | `CooldownTag` |
| `UUPCPairedActionCondition` (Paired Action Available) | A synced move would play now | `Actions` (paired action sets) |

`Comparison` is `unreal.UPCValueComparison`: `LESS`, `LESS_OR_EQUAL`, `NEARLY_EQUAL`, `GREATER_OR_EQUAL`, `GREATER`.

##### Sub graphs

A sub graph folds another action graph's moves into this one, gated by tags, so a shield's moves exist only while the shield is drawn:

```python
upc.add_sub_graph(sword_graph, "ROOT", shield_graph, entry_required_tags=["UPC.Equipment.Shielded"])
print(upc.compile_action_graph(sword_graph))
```

`shield_graph`'s transitions leaving its `ROOT` leave `ROOT` here too, each requiring the entry tags; its other moves come along. Recompile the parent after editing the source graph. `entry_blocked_tags` and `priority_bias` (added to each entry transition's priority) are optional.

To start the folded-in moves from more moves than one, such as a shield guard pressed mid-combo, attach those moves to the same import; the moves are imported once and each attached move gets the entry transitions with the same tags and priority:

```python
for move in ("Light1", "Light2", "Light3"):
    upc.attach_sub_graph(sword_graph, move, shield_graph)
print(upc.compile_action_graph(sword_graph))
```

An entry that should cut a running attack needs `INTERRUPT` timing on the source graph's `ROOT` transition, and a priority above the parent's own transition on the same input (`priority_bias`).

##### Removing and renaming

- `upc.remove_transition(graph, from_node, to_node, input_tag, fires_on=None)`.
- `upc.remove_node(graph, name)` takes the move, every transition into and out of it and its row in every alias. `ROOT` stays.
- Rename by writing another `NodeName` through `set_node_settings`.

##### Graph and character settings

- On the graph asset: `DefaultInputBufferMode` (`unreal.UPCInputBufferMode`: `IGNORE`, `ACCEPT_FIRST`, `ACCEPT_LAST`, the default) decides what a press made before the action window does; `LateInputGrace` (seconds, default 0.25, 0 off): how long after a move runs to its end a press or release still follows that move's own transitions, so a combo carries on when the next press comes a moment late; a canceled move, or one that leaves through an `AUTO` transition to another move, grants none; `HeldInputRestartWindow` (seconds).
- On the character's `UPC Action Graph` component (the C++ base creates it): `GlobalBlockTags` (default `UPC.State.ActionsBlocked`) drops every press while the character holds one; `ActionSpeed` scales how fast every move plays (1 is authored speed).

##### Mistakes

| Symptom | Cause and fix |
| --- | --- |
| An edit has no effect in Play | The graph was not compiled after the edit; `describe_action_graph` says so on its first line. |
| The guard never ends | No `ON_COMPLETED` transition from the guard to `ROOT` on the guard input, or that transition is `WAIT_FOR_WINDOW`, which waits for a window the looping guard never opens; make it `INTERRUPT`. |
| A set_node_settings call raises about an empty entry | A `Costs`, `Gates` or `Hooks` list holds `None`; make each object with `unreal.new_object`. |
| A directional move never plays | Its transition has the Direction condition, but the Input Action's movement is read from the character's movement input; with no stick pushed the direction is `NEUTRAL`. |
| A shield's moves play without the shield | The sub graph has no `entry_required_tags`; gate it on a tag the shield's item holds while drawn (its Equippable `EquipmentTags`). |
| A shield's guard starts only from idle | Only `ROOT` is attached to the shield's sub graph; `attach_sub_graph` each combo move it should start from. |

#### Animation, HUD, settings and debugging (`AgentGuide/AnimationHUDDebug.md`)

Tasks: a character made of several meshes; walking differently while a weapon is drawn; hit stop in the customer's own Animation Blueprint; footstep sounds, per surface too; a weapon's own swing effects; finding out why an effect plays nothing; i-frames or hyper armor on frames; showing stamina on screen; button prompts; project-wide settings; finding out why a combo does not chain.

##### Montage windows

Every window is added with `upc.add_notify_state_to_montage(montage, cls, start_seconds, duration_seconds)`; a one-frame notify with `unreal.AnimationLibrary.add_animation_notify_event(montage, "Combat", seconds, cls)`. Save the montage after setting properties.

| Window (editor name) | Class | Fields |
| --- | --- | --- |
| UPC Melee Trace Window | `UUPCMeleeTraceNotifyState` | `SocketIDs`, `DamageConfig`, `ExtraDamageTags` |
| UPC Action Window | `UUPCActionWindowNotifyState` | none: where the next press may chain |
| UPC State Tag Window | `UUPCStateTagNotifyState` | `Tags`: `UPC.State.Intangible` (i-frames), `UPC.State.HyperArmor` (less poise damage), `UPC.State.Parrying`, `UPC.State.Dashing` |
| UPC Approach Window, UPC Approach Or Move Window | `UUPCApproachWindowNotifyState`, `UUPCApproachOrMoveWindowNotifyState` | `AgentGuide/TargetingStylesSteering.md` |
| UPC Telegraph Window | `UUPCTelegraphMarkerNotifyState` | `AgentGuide/CameraTelegraphs.md` |
| UPC Anim Effect Window | `UUPCAnimEffectWindowNotifyState` | `Effect` (`EffectTags`, `SocketName`): trails, glows, sounds |
| UPC Anim Effect (one frame) | `UUPCAnimEffectNotify` | `Effect`: footsteps, landings |
| UPC Gameplay Event (one frame) | `UUPCGameplayEventNotify` | `EventTag` |
| UPC Equipment Commit (one frame) | `UUPCEquipmentCommitNotify` | `BlendSeconds` |

##### Several meshes and animation layers

- Component tags on the character's skeletal meshes say what each is for: `UPC.Montage` (follows the action's montage), `UPC.AnimLayer` (takes the item's animation layer), `UPC.Attachment.<Slot>` (items attach to it; the duplicated player's body has `UPC.Attachment.Body`). Matching is by prefix. Add the tags to an extra mesh's `ComponentTags`.
- **Walking differently with a weapon:** the Equippable fragment's `AnimLayerClass`, a child of the demo's `ABP_UPC_ItemAnimLayersBase` for the customer's skeleton with its own walk and jog sets. Its `Upper Body Default` sequence also holds the arms through a dodge while `UPC.State.Dashing` is on, so a weapon playing the bare hands' dodge montages keeps its grip. Building the layer's AnimGraph is a step for the customer.
- **Tag-driven bools:** an Animation Blueprint whose parent is `UUPCAnimInstanceBase` has `GameplayTagPropertyMap` on its class defaults, setting a bool while a tag is held.
- **Hit stop in the customer's own Animation Blueprint:** a **UPC Hit Stop** node after the `DefaultSlot` slot and an **Inertialization** node after it. That is a step for the customer; the plugin's locomotion template already has both (`AGENTS.md`, "No Animation Blueprint"), and without them the log warns once and hits land without the freeze.

##### Footsteps and effects

An animation asks the character for an effect with a **UPC Anim Effect** notify (one frame, `UUPCAnimEffectNotify`) or a **UPC Anim Effect Window** (`UUPCAnimEffectWindowNotifyState`), both carrying `Effect` (`EffectTags`, `SocketName`). Everything on the character that answers is asked, each once: the character's own Blueprint when it implements **UPC Anim Effect Handler** (`IUPCAnimEffectHandler`), then each component that does. The **UPC Anim Effects** component (`UUPCAnimEffectBankComponent`, `AnimEffectComponent` on the character base) plays the matching row of its **Anim Effect Bank** (`UUPCAnimEffectBank`: `Rows` with `EffectTag`, `Sounds`, `Particles`, `bAttachToSocket`; `SurfaceSet`), the deepest row whose tag the request sits under. The equipment manager runs each drawn item's Swing Trail and passes the request on to the drawn weapon's Blueprint.

- **Footsteps:** duplicate the demo's `AnimEffectBank_UPC_Default` and set it as the component's `Bank`. The console command `UPC.AnimEffects.PlantFootsteps <content path> save` writes `UPC.AnimEffect.Footstep.Left` and `.Right` notifies where each foot lands, for every animation in a folder.
- **Footsteps per surface:** give the bank a `SurfaceSet` (`UUPCImpactSurfaceSet`: `MaterialSuffixes` maps a physical material asset to a name such as `Stone`, `SurfaceSuffixes` a surface type) and a row for `UPC.AnimEffect.Footstep.Stone`, which then answers every footstep on that material unless a deeper row such as `Footstep.Left` exists. A row's tag must exist: `upc.add_gameplay_tag(tag, comment)`. The component looks at the ground under the socket for an effect a bank it uses has a surface row for. `upc.validate_asset(bank)` warns about a surface no row ends in and notes a row that plays nothing, which silences its tag (how a layered bank mutes one).
- **A weapon Blueprint's own swing effects** (a blade glow, a whoosh of its own, sparks): the Equippable fragment's `ItemActorClass`, a child of **UPC Equippable Item**, already implements **UPC Anim Effect Handler**, answering false. Overriding its functions Handle Anim Effect, Handle Anim Effect Begin and Handle Anim Effect End (under Interfaces in the Blueprint editor), checking the request's Effect Tags, returning true when it acted and false for the rest, is a Blueprint graph step for the customer; the demo `BP_UPC_Sword` lights its blade on `UPC.AnimEffect.Weapon.Glow` windows that way. The request's Mesh and Socket Name are the character's, so a blade effect goes on the weapon's own mesh. Windows can overlap: a heavy attack chained into another can open the next window before the last one closes, when the next montage blends in, so the graph counts open windows and switches the effect off when the count returns to zero, as the demo sword does. The character's own handlers do not pass requests on to the weapon; the equipment manager already does.
- **A ribbon behind the blade** needs no graph: the Swing Trail fragment (`AgentGuide/ItemsEquipment.md`) and a window carrying `UPC.AnimEffect.Weapon.Trail` over each swing. `upc.validate_asset(item)` warns when no montage the item's Action Graph plays carries such a window.
- **Item sounds are item events, not anim effects:** an item's draw and put-away sounds are its Stowable fragment's `DrawSound` and `PutAwaySound`, and its drop and landing sounds come from its World Item fragment.
- **In the animation editor**, playing or scrubbing a clip plays the effects its notifies ask for from a preview bank: `UUPCEditorSettings` (Project Settings, Plugins, Universal Player Combat Editor, Animation Preview) `PreviewBank`, or `PreviewBanksBySkeleton` for one skeleton. While the clip, an animation or a montage, carries a UPC Anim Effect, the viewport's bottom left corner names the bank; a bank the project lacks plays nothing and shows no name.
- **Nothing plays:** `UPC.Debug.AnimEffects 1` in Play writes each character's last requests above it with what answered (the bank, its row and surface, the Swing Trail, a Blueprint by class name), in red when nothing did. Then check, in order: the notify sits on the mesh that plays the montage; the character has a handler (the component has a `Bank`); a row's tag sits above the requested tag; a surface row's tag exists; the effect plays on a client or listen server, never a dedicated server; the notify's `TriggerWeightThreshold` and `NotifyTriggerChance`.

##### HUD and prompts

- The demo HUD `WBP_UPC_HUD` shows vitals (`WBP_UPC_CoreVitals`), the crosshair, cooldowns and the belt. A style's **Tags** fragment holds `UPC.UI.Show.Stamina`, `UPC.UI.Show.Belt` and the like, which the HUD reads to show each part. Changing a widget's layout is a step for the customer.
- **Button prompts:** `UUPCInputDisplaySettings` (Project Settings, Plugins, Universal Player Combat Input Display) lists `GlyphSets` (`UUPCInputGlyphSet`: `DeviceFamily`, `HardwareDeviceIdentifiers` naming the pads a sheet serves, `Glyphs` rows `Key`, `Icon`, `ShortLabel`, `LongLabel`), top to bottom, the first sheet serving the device and covering the key winning: the PlayStation sheet (DualSense, DualSenseEdge, DualShock4), the generic gamepad sheet, then keyboard.

##### Project settings

`unreal.get_default_object(unreal.UPCCombatSettings)`: `DefaultHitDetectionAuthority`, `MaxHitStopSeconds`. The demo hit effects are switched off with Load Demo Hit Effects under Project Settings, Plugins, Universal Player Combat Demo. Editor Python cannot save a project setting to the config file before UE 5.8, so a value set this way lasts until the editor closes: ask the customer to set it in Project Settings, Plugins.

##### Debugging

| Console command (in Play) | Shows |
| --- | --- |
| `UPC.Debug.Actions 1` | Each character's action graph above its head: node, window, buffered input |
| `UPC.Debug.Vitals 1`, `UPC.Debug.Equipment 1`, `UPC.Debug.Targeting 1`, `UPC.Debug.Approach 1` | Vitals, drawn items, targets, approach state |
| `UPC.Debug.Melee.DrawTracing 1` | Every melee sweep |
| `UPC.Debug.Steering.Draw 1` | Where attacks aim and stop |
| `UPC.Debug.Paired.Draw 1` | Paired action spots |
| `UPC.Debug.AnimEffects 1` | Each character's last anim effect requests and what answered them |
| `UPC.Logs Verbose` | Raises every UPC log category; `UPC.Logs` puts them back |

Without Play: `upc.describe_action_graph(graph)` and `upc.compile_action_graph(graph)` for a graph, `upc.validate_asset(asset)` for anything else. After Play, `log_warnings()` returns UPC's warnings, each naming the asset and the fix.

#### Camera and attack warnings (`AgentGuide/CameraTelegraphs.md`)

Tasks: a closer over-the-shoulder camera; the camera pulling back while sprinting; a different camera with a weapon drawn; a cinematic camera shot; warning markers over enemy swings; a marker over a stunned enemy.

##### The camera rig

The duplicated player carries a **UPC Camera Rig** component (`UPCCameraRig`, added by the Blueprint; `AgentGuide/Python.md`, "Components on a Blueprint", shows how to read its template). `DefaultSettings` (`unreal.UPCCameraRigSettings`) is used when neither the drawn item nor the combat style names a camera:

| Field | Does |
| --- | --- |
| `ArmLength` | Distance behind the character, cm |
| `SocketOffset` | Offset from the arm's end, cm; Y moves the camera over a shoulder |
| `FieldOfView` | Degrees |
| `PitchOffset` (label Camera Tilt) | Tilts the view without moving the aim |
| `PositionLagSpeed`, `RotationLagSpeed`, `SettingsBlendSpeed` | How quickly it follows and blends |
| `bFollowSoftTarget`, `SoftFollowSpeed`, `SoftFollowDeadzone`, `SoftFollowResumeDelay` | Turning toward the soft target while unlocked |

Other rig fields: `OutputMode`, `bCreateRigIfMissing`, `ShoulderSwapSeconds`.

**Per item:** a `UUPCItemFragment_CameraRig` (Camera Rig) on the item, `Settings`, while it drives combat. **Per style:** a `UUPCStyleFragment_CameraRig` on the combat style, `Settings` and `States` (`unreal.UPCCameraStateRow`: `Label`, `RequiredTags`, `BlockedTags`, `Layer`), a layer that blends in while the character holds the tags.

**Pull back while sprinting:** a style state row with `RequiredTags` `UPC.State.Sprinting` and a `Layer` overriding `ArmLength`. Read a demo style's row first (`CombatStyle_UPC_OverShoulder` has a Camera Rig fragment) and copy its shape with `export_text`.

**Check:** in Play, capture the viewport before and after; a camera change only a person can judge, so describe it to the customer.

##### Camera shots

A **Camera Shot** (`UUPCCameraShot`, made with the UPC Camera Shot factory or `new_data_asset`) holds `Keys` (`unreal.UPCCameraShotKey`: `TimeSeconds`, `Location`, `Rotation`, `FieldOfView`, `Interpolation`), the first at 0 and the rest in increasing time. A paired action plays one through an Authored Camera Shot hook (`AgentGuide/PairedActions.md`). `upc.validate_asset(shot)` checks the key order.

##### Warning markers over swings

1. The attacker's montage carries a **UPC Telegraph Window** (`UUPCTelegraphMarkerNotifyState`) over the wind-up: `TelegraphType`, `bFindAttackAutomatically` (finds the trace window it describes), `DamageConfig`, `Tags`, `OriginSpace`, `OriginItemTag`, `Broadcast` (its `Shape`). Add it with `upc.add_notify_state_to_montage`.
2. The player sees markers through a **UPC Telegraph Viewer** (the duplicated player has one) with a `StyleSet`: a **Telegraph Style Set** (`UUPCTelegraphStyleSet`: `Styles` rows `unreal.UPCTelegraphStyle` `RequiredTags`, `BlockedTags`, `Priority`, `PresenterClass`, `FallbackSocket`, `Duration`; `ViewRules`). Duplicate the demo's `TelegraphStyle_UPC_Default` and change rows; a new marker look is a presenter Blueprint (duplicate a `BP_UPC_TelegraphPresenter_*`).
3. A marker over a character rather than a swing: a **UPC Telegraph Subject** component (`UUPCTelegraphSubjectComponent`) and its Show Marker call, a Blueprint step.

##### Mistakes

| Symptom | Cause and fix |
| --- | --- |
| A camera setting does nothing | The drawn item's Camera Rig fragment or the style's wins over `DefaultSettings`. |
| No marker shows | The viewer has no `StyleSet`, no row's tags match the announcement, or the window is on the player's own attack. |
| A shot jumps | Its keys are out of time order; `validate_asset` says which. |

#### Damage, hit effects, blocks, parries and vitals (`AgentGuide/DamageDefense.md`)

`AGENTS.md`, Recipe 2, Step 2, makes damage configs. This file covers what a hit does beyond its damage, and how the player answers a hit: blocks, parries, i-frames, and the health and stamina pools.

Tasks: hits that drain stamina or stance; a knock-back; a hit stop; swings that slow when they strike armor; blocking frontal hits while guarding; a timed guard that parries; a shield that blocks; more health; a flinch when hit, or only when a few hits break poise.

##### Damage configs

`UUPCDamageConfig`: `Damage`, `AdditionalVitalDamage` (`unreal.UPCVitalDamage` rows `VitalTag`, `Amount`), `DamageTypeClass`, `DamageTags` (what kind of hit it is: `UPC.DamageType.Blunt`, `.Slash`), `MultiHitInterval`, `MaxTargetsPerSwingOverride`, `bUseAuthoredHitDirection`, `HitEffects`.

| Hit effect (picker label) | Does | Fields |
| --- | --- | --- |
| `UUPCPredictedGameplayCueHitEffect` (UPC Play Gameplay Cues) | Cues on the attacker and the target | `AttackerCues`, `TargetCues`, `SurfaceSet`, `bAllowPredicted` |
| `UUPCPhysicsImpulseHitEffect` (UPC Apply Physics Impulse) | Shoves the struck body | `HitBoneForce`, `HitOverallForce`, `HitRotationalForce`, `bVelocityChange` |
| `UUPCApplyGameplayEffectHitEffect` (UPC Apply Gameplay Effect) | Applies a gameplay effect | `GameplayEffect`, `Level` |
| `UUPCHitStopEffect` (UPC Hit Stop) | Freezes the pose for a moment | `HoldSeconds` (cut to `MaxHitStopSeconds` in Project Settings, 0.3 by default), `FatalHoldMultiplier`, `SurfaceScaling` |
| `UUPCSendGameplayEventHitEffect` (UPC Send Gameplay Event) | Sends an event, which a hit reaction ability answers | `EventTag` (`UPC.Event.Reaction`) |
| `UUPCRefillVitalHitEffect` (UPC Refill Vital) | Fills a pool to its maximum, such as broken poise | `VitalTag` (`UPC.Vital.Poise`) |
| `UUPCImpactSlowHitEffect` (UPC Impact Slow) | Slows the swing when it strikes the level or armor | `RelativeRate`, `Duration`, `RequireAnyTargetTags` |
| `UUPCPhysicalReactionHitEffect` (UPC Play Physical Reaction) | Moves the struck body's bones under physics | `ReactionName`, `Force` |

Every hit effect also has `AffectedSides`, `RequireAnyResultTags`, `SuppressOnResultTags` (run only, or never, on a Blocked or Parried hit), `RequireAnyTargetTags`, `SuppressOnTargetTags`, `bRequireDamageApplied`. Make each with `unreal.new_object(cls, outer=damage_config)`.

The character's `MeleeTraceComponent` (`UUPCCombatTraceComponent`): `DefaultDamageConfig`, `TeamFilter` (`unreal.UPCTeamFilter`, Enemies Only by default), `PassThroughTags` (`UPC.State.Intangible`), `MaxTargetsPerSwing`, `HitDetectionAuthority`.

##### Recipe: a shield that blocks and a timed guard that parries

The guard rules are a **Defense Rule Set** (`UUPCDefenseRuleSet`) that an item lends while drawn through its **Guard** fragment (`UUPCItemFragment_Guard`, `DefenseSet`). The responses run in order and the first that answers decides; the parry goes first.

```python
rules = new_data_asset(unreal.UPCDefenseRuleSet, F, f"DefenseRuleSet_{N}_Shield")
parry = unreal.new_object(unreal.UPCDefenseRule_Guard, outer=rules)
parry.set_editor_property("RequiredTags", tags("UPC.State.Parrying"))
parry.set_editor_property("DamageMultiplier", 0.0)
parry.set_editor_property("ResultTag", tag("UPC.DamageResult.Parried"))
parry.set_editor_property("CostPerDamage", 0.0)
block = unreal.new_object(unreal.UPCDefenseRule_Guard, outer=rules)
block.set_editor_property("RequiredTags", tags("UPC.State.Blocking"))
block.set_editor_property("ArcHalfAngleDegrees", 70.0)
block.set_editor_property("DamageMultiplier", 0.2)
block.set_editor_property("ResultTag", tag("UPC.DamageResult.Blocked"))
rules.set_editor_property("Responses", [parry, block])
save(rules)

guard = unreal.new_object(unreal.UPCItemFragment_Guard, outer=shield)       # shield: the item from AgentGuide/ItemsEquipment.md
guard.set_editor_property("DefenseSet", rules)
shield.set_editor_property("Fragments", list(shield.get_editor_property("Fragments")) + [guard])
save(shield)
```

- The tags come from the guard move: `UPC.State.Blocking` while the guard node runs and `UPC.State.Parrying` over the first frames of its montage (`AgentGuide/ActionGraphs.md`, "A held guard with a parry window"). Without them the rules never answer and hits land whole.
- Put the guard move in the graph of the weapon held with the shield: the shield itself drives no moves.
- A guard costs stamina when `CostVitalTag` names a vital (`CostPerDamage` per point of damage, `FlatCost` per hit); empty costs nothing.
- `ArcHalfAngleDegrees` is half the arc in front that blocks; a hit from behind lands whole.

**Check:** reload the rule set and print each response's class, `RequiredTags`, `ResultTag` and `DamageMultiplier`. In Play, with the guard held, a frontal hit takes a fifth of its damage and the attacker's hit carries `UPC.DamageResult.Blocked`; inside the parry window nothing lands and it carries `UPC.DamageResult.Parried`.

##### Defense responses

| Response (picker label) | Does | Fields |
| --- | --- | --- |
| `UUPCDefenseRule_Guard` (UPC Guard) | Soaks a hit from inside an arc | `ArcHalfAngleDegrees` (60), `DamageMultiplier` (0.2), cost (`CostPerDamage` 1 when `CostVitalTag` is set) |
| `UUPCDefenseRule_Scale` (UPC Scale Damage) | Scales the hit and lets the next response run | `DamageMultiplier` |
| `UUPCDefenseRule_Invulnerable` (UPC Invulnerable) | Takes nothing |  |

All have `RequiredTags`, `BlockedTags` (shown as Required and Blocked Defender Tags), `ResultTag`, `RequiredAttackTags`, `BlockedAttackTags` (an unblockable attack's damage tag), `CostVitalTag`, `FlatCost`, `CostPerDamage`, `bSpendRemainderWhenUnaffordable`, `bContinueAfterClaim`. A rule set's `OutcomeEffects` (hit effects) run once the hit is answered: the demo's sword set sends the attacker `UPC.Event.ParryRecoil` on a parry. The character's own **UPC Defense** component (`DefenseComponent`) holds `DefenseSet`, `Responses` and `OutcomeEffects` that do not come from an item.

##### Vitals

`VitalsComponent` on the character: `Vitals` rows (`unreal.UPCVitalDefinition`: `VitalTag`, `MaxValue`, `RegenRatePerSecond`, `RegenDelaySeconds`, `DepletedLooseTag`, `DepletedResultTag`, `DepletedGameplayEvent`). The duplicated player has health; stamina is in `AgentGuide/GatesCostsHooks.md`. An emptied `UPC.Vital.Stance` row with `DepletedGameplayEvent` `UPC.Event.GuardBreak` is how the demo breaks a guard.

##### Hit reactions

A flinch is an ability that answers `UPC.Event.Reaction`, which a UPC Send Gameplay Event hit effect on the attacker's damage config (or a defense set's outcome effects) sends. The demo's `GA_UPC_HitReact` is one: it cuts the running move and holds `UPC.State.ActionsBlocked` for its `HoldSeconds`, plays no animation, and serves any character, so add it to the character's ability set.

To flinch only on the hit that breaks poise, as the demo does: a `UPC.Vital.Poise` row on the character's vitals with `DepletedResultTag` `UPC.DamageResult.PoiseBroken`, a poise row in each damage config's `AdditionalVitalDamage`, `UPC.DamageResult.PoiseBroken` in the event's `RequireAnyResultTags` with `bRequireDamageApplied` off, and a `UUPCRefillVitalHitEffect` (`VitalTag` `UPC.Vital.Poise`, the same result gate) beside it in the same outcome effects. The demo characters share these in `DefenseRuleSet_UPC_Character`. `UPC.State.HyperArmor` on the character (a State Tag Window over a swing) halves its poise damage through a `UUPCDefenseRule_Scale` in that set (`RequiredTags` `UPC.State.HyperArmor`, `DamageMultiplier` 1, `VitalDamageMultipliers` row `UPC.Vital.Poise` at 0.5, `bContinueAfterClaim` on), so light hits do not stop the swing and a heavy one does.

##### Mistakes

| Symptom | Cause and fix |
| --- | --- |
| The guard blocks nothing | Nothing holds `UPC.State.Blocking`, the shield is not drawn, or the hit comes from outside `ArcHalfAngleDegrees`. |
| Every guarded hit is a parry | The parry window covers the whole guard montage; keep it to the first quarter second. |
| The parry never happens | The parry response is after the block in `Responses`, or the State Tag Window holds another tag. |
| The attacker never learns it was blocked | `ResultTag` is empty on the response. |

#### Stamina, costs, cooldowns, gates and hooks (`AgentGuide/GatesCostsHooks.md`)

Read `AgentGuide/ActionGraphs.md` first: everything here is set on a move through `upc.get_node_settings` and `upc.set_node_settings`, or on Epic's MCP on the move's card, as that file's "How a move is edited" says.

Tasks: give the player stamina; make attacks cost stamina; give a move a cooldown or charges; allow a move only at a resource level or while a tag is held; make sprinting drain stamina; slow the player or let them walk out of a move; read a cooldown for the HUD.

##### Recipe: stamina, attack costs, a dodge cooldown and sprint drain

```python
d = defaults(bp)
vitals = d.get_editor_property("VitalsComponent")
rows = list(vitals.get_editor_property("Vitals"))
names = [str(unreal.GameplayTagLibrary.get_tag_name(r.get_editor_property("VitalTag"))) for r in rows]
if "UPC.Vital.Stamina" in names:                  # the duplicated demo player has one
    row = rows[names.index("UPC.Vital.Stamina")]
else:
    row = unreal.UPCVitalDefinition()
    row.set_editor_property("VitalTag", tag("UPC.Vital.Stamina"))
    rows.append(row)
row.set_editor_property("MaxValue", 100.0)
row.set_editor_property("RegenRatePerSecond", 25.0)
row.set_editor_property("RegenDelaySeconds", 1.0)
vitals.set_editor_property("Vitals", rows)
save(bp)

def add_cost(graph, node, amount):
    s = upc.get_node_settings(graph, node)
    cost = unreal.new_object(unreal.UPCActionCost_Vital)
    cost.set_editor_property("VitalTag", tag("UPC.Vital.Stamina"))
    cost.set_editor_property("Amount", float(amount))
    s.set_editor_property("Costs", list(s.get_editor_property("Costs")) + [cost])
    upc.set_node_settings(graph, node, s)

for node in ("Light1", "Light2", "Light3"):
    add_cost(graph, node, 10)
add_cost(graph, "Dodge", 25)

s = upc.get_node_settings(graph, "Dodge")
gate = unreal.new_object(unreal.UPCActionGate_Cooldown)
gate.set_editor_property("CooldownTag", tag("UPC.Cooldown.Ability.Dodge"))
gate.set_editor_property("Seconds", 1.0)
gate.set_editor_property("Charges", 1)
s.set_editor_property("Gates", list(s.get_editor_property("Gates")) + [gate])
upc.set_node_settings(graph, "Dodge", s)
print(upc.compile_action_graph(graph))
save(graph)
```

Sprint drain is set on the character's **UPC Sprint** component, which the Blueprint adds (`AgentGuide/Python.md`, "Components on a Blueprint", shows how to read its template): `StaminaVital` = `UPC.Vital.Stamina`, `StaminaDrainPerSecond` = 15, `MinStaminaToStart` = 10. Compile and save the Blueprint.

- A cost is checked when the press arrives and spent when the move starts. A press the player cannot pay for does not start the move; it takes the move's Action Failed transition when one is wired.
- Do the same in every graph that has these moves: a drawn weapon's graph replaces the unarmed one.
- The cooldown tag names the timer. Gates sharing a tag share the timer, so they must agree on `Seconds` and `Charges` (the compile warns when they differ). For a second cooldown, add its own tag first: `upc.add_gameplay_tag("UPC.Cooldown.Ability.Heavy", "Heavy attack cooldown")`.

**Check:** `upc.describe_action_graph(graph)` lists `costs Vital (VitalTag=..., Amount=10)` on each light attack and `gates Cooldown (... Seconds=1 ...)` on the dodge. In Play, `UPC.Debug.Vitals 1` writes the stamina above the character; it drops by the cost after each press and climbs back after a second.

##### Gates, costs and hooks

| Class (editor label) | Goes in | Properties |
| --- | --- | --- |
| `UUPCActionCost_Vital` (Vital) | `Costs` | `VitalTag`, `Amount` |
| `UUPCActionCost_Attribute` (Attribute) | `Costs` | `Attribute` (a Gameplay Ability System attribute), `Amount` |
| `UUPCActionGate_Cooldown` (Cooldown) | `Gates` | `CooldownTag`, `Seconds`, `Charges` (uses it can bank), `EffectClass` (advanced) |
| `UUPCActionGate_Vital` (Vital) | `Gates` | `VitalTag`, `Comparison` (`unreal.UPCValueComparison`: `LESS`, `LESS_OR_EQUAL`, `NEARLY_EQUAL`, `GREATER_OR_EQUAL`, `GREATER`), `Threshold`, `Tolerance`, `bUseFraction` (compare 0 to 1 of the max) |
| `UUPCActionGate_Tags` (Tags) | `Gates` | `RequiredTags`, `BlockedTags`, `WhenClosed` (`unreal.UPCGateTagMode.SKIP` tries the next matching transition, `REFUSE` drops the press) |
| `UUPCActionHook_LocomotionModifier` (Locomotion Modifier) | `Hooks` | `WalkSpeedFactor`, `AccelerationFactor`, `RotationRateFactor` (0 freezes, 1 keeps) |
| `UUPCActionHook_CancelOnMovement` (Cancel On Movement) | `Hooks` | `ReadMode` (`INPUT_INTENT`, `SPEED`), `MovementThreshold` (0 to 1) |
| `UUPCActionHook_DrainVital` (Drain Vital) | `Hooks` | `VitalTag`, `DrainRatePerSecond`, `EndBehavior` (`NONE`, `FINISH`, `CANCEL` the move when the vital empties) |

A gate decides whether the move may start; a cost is paid when it starts; a hook runs while it runs. A gate never spends anything.

**Falling through to another move.** When the press should do something else while a move is shut, put a condition on the transition rather than relying on the gate: `UUPCTransitionCondition_CooldownReady` (`CooldownTag`) or `UUPCTransitionCondition_VitalThreshold` (`VitalTag`, `Comparison`, `Threshold`, `bUseFraction`) on the transition into the gated move, and a second transition on the same input without it. See `AgentGuide/ActionGraphs.md`, "Transition settings".

**HUD.** `unreal.UPCActionGate_Cooldown.get_cooldown_state(owner, cooldown_tag)` reports charges ready, charges it can bank and when the next comes back. The demo HUD's `WBP_UPC_AbilityCooldown` reads it.

##### Mistakes

| Symptom | Cause and fix |
| --- | --- |
| Attacks cost nothing | The character has no row for the cost's `VitalTag` in `VitalsComponent`'s `Vitals`, or the cost sits on a move in a graph the player does not use while that item is drawn. |
| A press never starts the move | A cost the player cannot pay, a gate that is shut, or a cooldown still running. `UPC.Debug.Actions 1` shows the graph state; `log_warnings()` names a cost whose vital is missing. |
| The compile warns about a cooldown | Two gates share a `CooldownTag` with different `Seconds` or `Charges`. Make them agree or give one its own tag. |
| Sprint never drains | `StaminaVital` is empty on the UPC Sprint component, or the character has no row for it. |

#### Input, abilities and sprint (`AgentGuide/InputAbilities.md`)

Tasks: put a move on a new key; a combined press of two buttons; an input that works with any weapon; an ability on a key that can cut a swing (a potion, a heal); sprint speed and stamina; pausing combat input.

##### How a press reaches a move

A key maps to an **Input Action** in a mapping context; a mapping row turns the Input Action into an input tag; the action graph's transitions listen for the tag. All three must agree.

- **Item mappings** (while an item is drawn): the Equippable fragment's `InputMappingContext`, `InputMappingPriority` and `InputMappings` rows (`unreal.UPCInputMapping`: `InputAction`, `InputTag`, `RequiredStateTags`, `DisplayName`), as `AGENTS.md`, Recipe 2, Steps 5 and 6, builds.
- **Character mappings** (always): the character's `InputMappingComponent` (`UUPCInputMappingComponent`): `InputMappings`, `InputChords`, `ChordWindowSeconds`, `bAutoBind`.
- **Keys:** `IMC_UPC_Default` on the controller maps movement, look, slots 1 to 4, dash, lock-on, interact, drop and sprint. Item contexts map attack and guard keys.

##### Recipe: a kick on F

```python
upc.add_gameplay_tag("UPC.Input.Kick", "Kick")
ia = unreal.AssetToolsHelpers.get_asset_tools().create_asset(f"IA_{N}_Kick", F, unreal.InputAction, unreal.InputAction_Factory())
ia.set_editor_property("ValueType", unreal.InputActionValueType.BOOLEAN)
save(ia)
key = unreal.Key()
key.import_text("F")
imc.map_key(ia, key)                         # imc: the item's mapping context copy
save(imc)
m = unreal.UPCInputMapping()
m.set_editor_property("InputAction", ia)
m.set_editor_property("InputTag", tag("UPC.Input.Kick"))
eq.set_editor_property("InputMappings", list(eq.get_editor_property("InputMappings")) + [m])   # eq: the item's Equippable fragment
save(item)
upc.add_transition(graph, "ROOT", "Kick", "UPC.Input.Kick")
```

**Check:** reload the item and print its mappings; `upc.describe_action_graph(graph)` lists `UPC.Input.Kick` leaving ROOT. In Play, `graph_comp.process_action_input(tag("UPC.Input.Kick"), unreal.UPCTransitionTrigger.ON_STARTED)` enters the move (`AGENTS.md`, Recipe 2, Step 8).

##### Chords

`unreal.UPCInputChord` rows (`MemberTags`, two or more input tags; `OutputTag`, the tag sent when all are pressed inside `ChordWindowSeconds`; `Priority`) in an item's `InputChords` or the character's. A transition on the output tag plays the move. `ChordWindowSeconds` 0 turns chords off.

##### Abilities on keys

The character's starting ability set (`StartingAbilitySet`, a copy of `AbilitySet_UPC_Default` in `AGENTS.md`, Recipe 1) grants abilities with their keys: `GrantedGameplayAbilities` rows (`unreal.UPCAbilitySet_GameplayAbility`: `Ability`, `AbilityLevel`, `InputTag`, `InputAction`, `bInterruptsActionGraph` (cuts the running move, for a dodge or a potion), `bForwardInputWhileActive` (a second press goes into the running ability, for a hold)). The row's fields refuse `set_editor_property`, so write the row as text:

```python
row = unreal.UPCAbilitySet_GameplayAbility()
row.import_text(f'(Ability="{heal_bp.generated_class().get_path_name()}",AbilityLevel=1,InputTag=(TagName="UPC.Input.Special"),InputAction="{ia_heal.get_path_name()}",bInterruptsActionGraph=True)')
abilities.set_editor_property("GrantedGameplayAbilities", list(abilities.get_editor_property("GrantedGameplayAbilities")) + [row])
save(abilities)
```

An item grants its own sets while drawn through `AbilitySetsToGrant`. A press goes to the action graph first, then to a granted ability whose row carries the same input tag.

##### Sprint

The sprint is the **UPC Sprint Ability** (`UUPCSprintAbility`, `LatchTapSeconds`) on `UPC.Input.Sprint` (in the copied ability set) and the **UPC Sprint** component (`Sprint` on the duplicated player, added by the Blueprint): `WalkSpeed`, `SprintSpeed`, `SpeedBlendSeconds`, `MinInputStrength`, `MaxSprintAngle`, `BlockedTags`, `StopGraceSeconds`, `StaminaVital`, `StaminaDrainPerSecond`, `MinStaminaToStart`. Stamina for it: `AgentGuide/GatesCostsHooks.md`.

**Slower while a tag is held:** a **UPC Speed Modifier** component (`UPCSpeedModifier` on the duplicated player): `SpeedScaleByTag`, a map of tag to multiplier.

##### Pausing combat input

`GlobalBlockTags` on the character's `ActionGraphComponent` (default `UPC.State.ActionsBlocked`) drops every press while the character holds one; holding the tag (a dialogue, a cutscene) is a Blueprint step or a gameplay effect.

##### Mistakes

| Symptom | Cause and fix |
| --- | --- |
| A press does nothing | The mapping row's tag differs from the transition's, the context lacks the Input Action, or the tag was never added. |
| The ability never fires mid-swing | `bInterruptsActionGraph` off, or a transition on the same tag in the graph takes the press first. |
| A new tag reads empty | It was used before `upc.add_gameplay_tag` added it. |

#### Inventory, belt, pickups, drops and interaction (`AgentGuide/InventoryInteraction.md`)

Tasks: start with an item; an item, a container and an inventory config made from scratch; a keychain that takes every key by itself, cannot be dropped and weighs nothing; a weight limit or a cap per item kind; a belt that takes any item, or a belt laid out by item kind; saving the inventory in the game's save; a survival grid, a party RPG inventory, an open world list, or a character carrying only what its hands hold; an inventory screen, the demo's as it is, a copy changed for the game, or one of the project's own; a potion drunk from a screen or the belt; a stash; a rule, a combine or an item fragment of the project's own in Blueprint; two items combined into a third by a recipe; rounds loaded into a magazine by dropping them on it; a bag bar whose pouches hold items; an item on the floor to pick up; dropping the held weapon; a weapon rack; a loot chest every nearby player sees; only interacting with what the player looks at; a lever or a door that plays an animation.

##### Inventory and belt

- **Starting items:** `StartingItems` on the character's `InventoryComponent` (`AGENTS.md`, Recipe 4, Step 5).
- **The belt** is the **UPC Quick Bar** component on the player controller (`BP_UPC_PlayerController`; duplicate it to change it, and set the copy as the game mode's `PlayerControllerClass`). Fields: `Slots` (`unreal.UPCQuickBarSlot` rows: `Filter`, an item kind the item's `SlotTags` must hold, its children included), `bAutoPopulateFromInventory` (off by default; on, a new item also takes the first free slot that admits it, as on the demo controller; what a combine makes is no new item), `bAutoDrawOnInit` (with Auto Populate on, an item landing on a slot also comes to hand when its places are free; it does nothing while Auto Populate is off), `bRestoreWhenPlaceFrees`, `bWeaponKeepsItsPlace`, `DefaultMaxCopiesPerDefinition` (1 on the demo controller, so its belt holds one of each item; the demo sword's Belt Limit fragment of 2 lets it hold two swords), `DriverReplacementOnDrop`, `BlockedTags` (tags that switch the belt off while the character carries one; `UPC.State.BeltBlocked` by default). The controller's `IMC_UPC_Default` maps keys 1 to 4 and the D-pad's up, right, down and left to `IA_UPC_SelectSlot_1` to `_4`.
- **Ask the customer which belt layout the game wants.** Any item on any slot, as a survival game's hotbar: leave every slot's `Filter` empty, and a worn item drawn from the belt is put on. Slots by item kind, as the demo's souls-like diamond: slot 1 (top) `UPC.Slot.Weapon`, slot 2 (right) `UPC.Slot.Consumable.Potion`, slot 3 (bottom) `UPC.Slot.OffHand`, slot 4 (left) `UPC.Slot.Consumable`, with a Belt Limit fragment (`MaxBeltCount` 1) on the potion so its stacks take the right slot alone and the left slot holds other consumables; worn gear and a second weapon then stay in the inventory. Ask too whether any combat style has no belt: put `UPC.State.BeltBlocked` in that style's Gameplay Tags fragment (`unreal.UPCStyleFragment_Tags`, field `Tags`), as the demo Freeflow style does, and while it is active a belt press does nothing, nothing new goes on the belt and its slots keep their items. The demo HUD shows its belt only while the character carries `UPC.UI.Show.Belt`, which a style with a belt adds the same way.
- **Item kinds** are one `UPC.Slot` hierarchy in each item's `SlotTags`, read by the belt's filters, the inventory's rules and its sort order: `UPC.Slot.Weapon` (`.Sword`, `.TwoHanded`), `UPC.Slot.OffHand` (`.Shield`), `UPC.Slot.Consumable` (`.Potion`), `UPC.Slot.Apparel`, `UPC.Slot.Ingredient`, `UPC.Slot.Ammo` (`.Arrow`), `UPC.Slot.KeyItem`, `UPC.Slot.Upgrade`. Add the project's own kinds under these, such as a spear kind under `UPC.Slot.Weapon`, so a filter on the parent takes them. An item with no `SlotTags` fits unfiltered slots only. A Belt Limit fragment caps copies on the belt.

##### Containers

An inventory with no config is one list with no limits. For slots, a grid, a bank, caps per item kind or a weight limit, set the inventory component's `Config` to a **UPC Inventory Config** (`unreal.UPCInventoryConfig`): `defaults(bp).get_editor_property("InventoryComponent").set_editor_property("Config", config)` on the character Blueprint, then `save(bp)`. Three ready styles sit in `/UniversalPlayerCombat/Demo/Items/Inventory`, each a recipe below; duplicate one into the project's folder and change the copy. The demo player starts on `InventoryConfig_UPC_SurvivalGrid`; the style pads in the wardrobe hall switch it.

- `Containers` rows (`unreal.UPCContainerSpec`): a `Container` tag and a **UPC Container Definition** (`unreal.UPCContainerDefinition`: `DisplayName`, `Icon`, `Layout` `unreal.UPCContainerLayout.LIST`, `SLOTS` or `GRID`, `SlotCount`, `GridSize` (`unreal.IntPoint`), `bAllowRotation`, `CapacityTag`, `Rules`, `bCarried`, `bTakesNewItems`, `AccessTag`, `bOpensBags`). Adds try the rows in order, each followed by the bags open in it, and take the first container whose rules let the item in and that has room; a `bTakesNewItems` container off is filled by moves only. A List has no size and takes anything its rules allow, so give a container meant for one kind of item an **Item Filter** rule.
- `CarryRules`, judged over every carried container together. The rules: **Weight Limit** (`Limit`, `CapacityTag`, `bRefuseOverLimit`, `Thresholds` of `Fraction` and `Tag`), **Category Limit** (`Limits` rows of `Category`, `MaxUnits`, `CapacityTag`, matched against the item's `SlotTags`) **Item Filter** (`AllowedTags`, `BlockedTags`) and **Belt Only** (no settings: lets in only what the belt holds, for a style with no inventory; the belt needs `bAutoPopulateFromInventory` on. It turns down any bag, an item with a Container fragment, since what a bag holds would be carried off the belt. In the Carry Rules it limits everything carried; on a container it limits that container alone, as the demo Simple config's Belt container beside a Worn container whose Item Filter takes apparel).
- `BeltSpace` and `EquippedSpace` (`unreal.UPCHeldItemSpace`: `KEEPS_ITS_PLACE`, `FREES_ITS_PLACE`): whether an item on the belt, or drawn and off the belt, keeps its place in its container. Under `FREES_ITS_PLACE`, `RequestMove` of the whole held stack to a place in a carried container has the belt and the hand let go of it and puts it there, and `CanPlace` answers for that move. A screen that drops an item out of the hand onto a place sends `RequestMove` first and the belt's clear or `RequestUnequipItem` after it: a timed put-away running when the move arrives refuses the move. `SortOrder`: the Slot Tags `RequestSort` lays items out by. `bEquipAtOnce`, `bPutAwayAtOnce` and `bCombineAtOnce` (Timing): on, an equip, a put-away or a combine asked for from the inventory lands at once with no animation and no wait, which a screen that pauses the game needs; off, the request plays the item's reach, or waits `DrawTiming` or `StowTiming` (`unreal.UPCItemActionTiming`: `Seconds`, `StartSound`, `CommitSound`, `bCancelOnMove`) for an item with none, or the recipe's time for a combine. The server reads these three, so no request takes a flag for them. `bSwapWhenFull` is explained with pickups, in the paragraph after the code below. `bShowCarriedItemsOnBody` (off by default): on, every stowable item carried outside a bag hangs on the body while out of hand; off, only the belt's items do and an item only in the inventory shows nowhere on the character. `BeltSlots` (one tag per slot, an empty tag taking any item): the belt this config gives, standing in for the belt's own `Slots` while the config is used, as the party RPG's four slots taking any item; left empty, the belt keeps its own.
- An item with a **Capacity Bonus** fragment raises a capacity tag while owned; a container's or a limit rule's `CapacityTag` names it.
- **Another config at runtime**, such as a game that lets the player pick an inventory style: call the inventory's `SetConfig` on the server, never set `Config` itself outside the defaults. A container both configs list, such as a stash, keeps its items; every other item outside a bag is placed again in the new layout as an add would, each staying on the belt and in hand; one that fits nowhere stays in a carried container, past its end when that is full, with a warning. To start the new config empty, call `RemoveAllItems` first, which empties the stash too; to keep the stash, remove only the items `IsItemCarriedAndInReach` answers true for, as the demo wardrobe's `BP_UPC_InventoryStylePad` does before it adds the style's starter kit.

Row fields are edited on the asset only, so Python fills a row with `import_text`:

```python
spec = unreal.UPCContainerSpec()
spec.import_text('(Container=(TagName="UPC.Inventory.Container.Backpack"),Definition="/Game/Inventory/Container_Backpack.Container_Backpack")')
config = load("/Game/Inventory/InventoryConfig_Knight")      # the project's copy of a demo config
config.set_editor_property("Containers", list(config.get_editor_property("Containers")) + [spec])
save(config)
```

Adds and pickups take only what fits and a pickup leaves the rest on the ground. The world item's `GetUnitsThatFit` (with the picker) answers what a pickup would take, the inventory's what an add would. A pickup that takes nothing stays on the prompt with its verb saying why (No Room, Too Heavy) and a press does nothing; with the config's `bSwapWhenFull` on it reads Swap instead, and a press puts down the item in the way and takes the new one (`GetSwapItem` names what goes): an item in hand or on a belt slot that takes the new item, one of the same kind first, so a second sword replaces the sword put away rather than the shield. The demo's inventory screens draw all of this (Inventory screens, below); what follows is what any screen reads and sends. A UI reads `GetItemsInContainer`, `GetItemsInSortOrder` (a list kept in the config's `SortOrder` without moving anything), `GetItemPlacement`, `GetContainerSize` and `CanPlace` (its units out says how many a merge takes), a grid UI `GetItemCells` and `GetPositionOfCell`, a weight bar `GetWeight` with an empty scope, and redraws on `OnLayoutChanged`. The UI changes nothing itself: it asks the server through the inventory's `RequestMove` (move, split, merge, swap, combine, or into the bag dropped on), `RequestSort` and `RequestUseItem`, the belt's `RequestAssignSlot`, and the equipment manager's `RequestEquipItem`, `RequestUnequipItem` and `RequestCancelSequence`, and draws the change when it arrives. Clearing or refilling a belt slot whose item is in hand takes that item out of the hand at once, with no animation, once `CanAssignSlot` allows it. `RequestEquipItem` and `CanEquipItem` take the item and a Place, a paper doll's place (leave it empty to follow the item's Hold Places order), and `RequestUnequipItem` takes the item only. The config's `bEquipAtOnce` and `bPutAwayAtOnce` decide whether the requests play the draw and put-away animation or land at once. Each request has a check the UI asks first, the one the server makes: `CanPlace` for a move, the belt's `CanAssignSlot` for a drag onto or off the belt, the equipment manager's `CanEquipItem` and `CanUnequipItem` for Equip and Unequip buttons, and the inventory's `IsItemCarriedAndInReach` for a Use button; gray a button out while its check answers false. `IsContainerInReach` tells a bank or stash screen whether the player can reach it now. A timed draw announces `OnEquipStepStarted` and `OnEquipSequenceEnded` on the equipment manager, and `GetPlayingStep` answers how far it has got.

**Check:** in Play with the survival grid config on the player, `GetContainerSize` for `UPC.Inventory.Container.Case` reads 6 by 4, a sword added lands at the case's first free place (`GetItemPlacement`), and a hip pouch added lands in Key Items and opens six cells (`GetContainerOpenedBy`, then `GetContainerSize` of that container reads 6 by 1).

###### Recipe: a survival grid

A small case where items take room by size and turn to fit, key items in a list beside it, a hip pouch that adds six cells under the case, and a stash for the rest. Duplicate `InventoryConfig_UPC_SurvivalGrid`:

- `Containers`, in this order: Case (`UPC.Inventory.Container.Case`: Grid, `GridSize` 6 by 4, `bAllowRotation` on, an **Item Filter** rule with `BlockedTags` `UPC.Slot.KeyItem` and `UPC.Slot.Upgrade`, `CapacityTag` `UPC.Inventory.Capacity.CaseRows`), Key Items (`UPC.Inventory.Container.KeyItems`: List, an **Item Filter** rule with `AllowedTags` `UPC.Slot.KeyItem` and `UPC.Slot.Upgrade`, `bOpensBags` on, so keys and pouches take no room in the case and a pouch opens there) and Stash (`UPC.Inventory.Container.Stash`: Slots, `SlotCount` 30, `bCarried` off, `bTakesNewItems` off, `AccessTag` `UPC.State.AtStash`; the same container asset the three demo style configs list, so a stash survives a switch of config).
- Each item larger than one cell gets a **Grid Footprint** fragment (`unreal.UPCItemFragment_GridFootprint`, `Size` columns by rows: the demo sword 1 by 3, the shield 2 by 2). The hip pouch (`/UniversalPlayerCombat/Demo/Items/Bags/Item_UPC_HipPouch`) is a bag whose **Container** fragment opens `Container_UPC_HipPouch`, a Grid 6 by 1 with the case's Item Filter; the case is listed first, so adds fill it before the pouch's cells, and what lies in those cells drops, travels and comes back with the pouch. The demo survival grid screen draws each open pouch's cells under the case (`WBP_UPC_ExtraCells`). For space that stays with the character, such as a case upgrade, give an item a **Capacity Bonus** row for `UPC.Inventory.Capacity.CaseRows` instead: it adds a row, and the player cannot drop it while that row holds an item.
- `CarryRules`: the demo's Blueprint rule `BP_UPC_InventoryRule_UniqueKeyItems` (one of each key item). `CombineSets`: the demo's `CombineSet_UPC_Demo` (Combines, below). `bPutAwayAtOnce` on, so a screen's put-away leaves the hand at once while Equip keeps its draw animation. `bShowCarriedItemsOnBody` on, so every weapon in the case hangs on the body.
- The stash is opened at a demo **storage box** (Storage boxes, below). A whole stack on the belt never goes into storage the character does not carry until the belt lets go of it; the demo screens' Store clears the belt slot first.

**Check:** in Play, a sword's `CanPlace` onto the case's cell (0, 3) answers `REFUSED` with `UPC.Inventory.Refused.OutOfBounds` upright and `PLACE` with `bRotated` on.

###### Recipe: a party RPG inventory

A backpack that never refuses for space, weight tiers that slow, pouches that open, worn gear for a paper doll and a stash. Duplicate `InventoryConfig_UPC_PartyRPG`:

- `Containers`: Backpack (`UPC.Inventory.Container.Backpack`: List, `bOpensBags` on, so a pouch opens once picked up) and Stash (`UPC.Inventory.Container.Stash`: Slots, `SlotCount` 30, `bCarried` off, `bTakesNewItems` off, `AccessTag` `UPC.State.AtStash`).
- `CarryRules`: a **Weight Limit** with `Limit` 60, `bRefuseOverLimit` off and `Thresholds` `UPC.State.Encumbered` at 0.7 and `UPC.State.HeavilyEncumbered` at 1. Every item gets a **Weight** fragment. The slow is the character's **UPC Speed Modifier** (`SpeedScaleByTag`: the demo character's 0.85, and 0 for `UPC.State.HeavilyEncumbered` and `UPC.State.Overburdened`, which hold it still); the demo character's UPC Sprint names `UPC.State.Encumbered` and `UPC.State.Overburdened` in its `BlockedTags`, so an encumbered character cannot sprint.
- `CombineSets`: `CombineSet_UPC_Demo`. `bEquipAtOnce`, `bPutAwayAtOnce` and `bCombineAtOnce` on, so the screen equips, puts away and combines at once. `BeltSpace` and `EquippedSpace` `FREES_ITS_PLACE`, so an item on the belt or in hand, worn gear included, leaves the backpack while held and its weight still counts. `BeltSlots`: four empty tags, a row taking any item.
- The Stash is the same container as the survival grid's, opened at the same **storage box** (Storage boxes, below). The party RPG screen's paper doll is `WBP_UPC_PaperDoll` (Inventory screens, below); a doll of the project's own reads the equipment manager's `GetInstanceInPlace` for each place it shows, skips the unarmed fallback (`IsDefaultItem`), and redraws on `OnDrawnStateChanged`. Loot chests are `BP_UPC_LootChest` (Loot chests, below).

**Check:** in Play, seven of the demo shields (weight 6 each) put `UPC.State.Encumbered` on the character, eleven add `UPC.State.HeavilyEncumbered`, and the character's speed modifier's `GetCurrentSpeedScale` reads 0.85, then 0.

###### Recipe: an open world list

One list with no size, weight that slows past the limit without refusing, categories by item kind and no belt, as in Skyrim. Duplicate `InventoryConfig_UPC_OpenWorldList`:

- `Containers`: Satchel (`UPC.Inventory.Container.Satchel`: List) and the demo's Stash (`Container_UPC_Stash`, as the survival grid lists it), which the list screen shows in a panel of its own on the right of its frame while the character stands at a storage box.
- `CarryRules`: a **Weight Limit** with `Limit` 60, `bRefuseOverLimit` off and a threshold `UPC.State.Overburdened` at 1, and the demo's Blueprint rule `BP_UPC_InventoryRule_Valuables` (`UPC.State.CarryingValuables` while an item worth 100 gold or more is carried).
- `SortOrder`: `UPC.Slot.Weapon`, `UPC.Slot.OffHand`, `UPC.Slot.Apparel`, `UPC.Slot.Consumable`, `UPC.Slot.Ingredient`; items of no listed kind sort last. The demo's list screen shows the satchel in that order (`GetItemsInSortOrder`), with a category column filtering by the same kinds.
- `bHasBelt` off: the player has no belt while this config is used. The belt has no slots, so the 1 to 4 keys do nothing and nothing goes on it, and the HUD and the list screen show no belt, no On Belt category and no Add to belt prompt. Leave it on for a list with the belt as favorites: the screen then sends `RequestAssignSlot`, and its On Belt category lists the belt's items.
- `CombineSets`: `CombineSet_UPC_Demo`. `bEquipAtOnce`, `bPutAwayAtOnce` and `bCombineAtOnce` on, since the list screen pauses a game with no other players and a paused world runs no timers.

**Check:** in Play, `RequestSort` of `UPC.Inventory.Container.Satchel` lays a key, a herb, a potion, a helmet and a sword out as sword, helmet, potion, herb, key, and the player's belt `GetNumSlots` reads 0.

###### Recipe: hands only

The character carries only what its two hands hold: a pickup comes straight to hand, a second one-handed item to the other hand, and a third swaps for the item in hand. `InventoryConfig_UPC_SingleItem` does it for one hand; for two, make the config from scratch (Containers, above):

- `Containers`: one container, Slots with `SlotCount` 2, `bCarried` on, no rules. `BeltSpace` and `EquippedSpace` stay `KEEPS_ITS_PLACE` (the defaults), so two held items fill it.
- `bSwapWhenFull` on, so a third pickup reads Swap and puts down the item in hand that makes room, the weapon driving combat first.
- `BeltSlots`: two empty tags, a belt of two slots taking any item. On the controller's belt, `bAutoPopulateFromInventory` and `bAutoDrawOnInit` on: a picked-up item lands on the first free slot and comes to hand while its Hold Places are free. For two of one item, the belt's `DefaultMaxCopiesPerDefinition` must allow two (0 is no limit; the demo controller's is 1), or the item needs a Belt Limit fragment of 2.
- No HUD shows the belt: leave `UPC.UI.Show.Belt` out of the style's Gameplay Tags fragment. Leave the belt's keys out of the game's mapping context so no key puts an item away.

An item the hands cannot hold beside what they hold, a two-handed weapon beside a sword, stays in its slot, unheld, until something draws it.

**Check:** in Play, picking up two swords puts one in each hand (`GetItemsInHand` holds two), and a third pickup's prompt reads Swap; taking it leaves the first sword on the ground and the new one in its hand.

###### Recipe: a keychain

Every key the player picks up goes onto a keychain by itself and never into the pack, a key cannot be dropped, and a key weighs nothing, with a pack that holds 30 weight beside it. This is also the pattern for any items that live apart from the rest: ammunition, quest items, crafting materials. Three facts carry it:

- A container listed **first** is tried first by each add, and an **Item Filter** rule on it lets in only the `UPC.Slot.KeyItem` kind, so keys land there and nothing else does. The pack, listed second, takes the rest.
- An item with **no World Item fragment** cannot be dropped or lie on the ground: the drop ability refuses it and the demo screens show no Drop for it.
- An item with **no Weight fragment** weighs nothing, so the weight limit does not count a key, even in a pack already full.

```python
F = "/Game/Inventory"                                    # the customer's folder
upc.add_gameplay_tag("UPC.Inventory.Container.Keychain", "The player's keychain")

def make_item(name, kind, stack=1, weight=None):
    item = new_data_asset(unreal.UPCItemDefinition, F, f"Item_{name}")
    item.set_editor_property("DisplayName", name)
    item.set_editor_property("SlotTags", tags(kind))
    item.set_editor_property("MaxStackSize", stack)
    if weight is not None:
        w = unreal.new_object(unreal.UPCItemFragment_Weight, outer=item)
        w.set_editor_property("Weight", weight)
        item.set_editor_property("Fragments", [w])
    save(item)
    return item

rusty = make_item("RustyKey", "UPC.Slot.KeyItem")        # no Weight, no World Item fragment
gold = make_item("GoldKey", "UPC.Slot.KeyItem")
flint = make_item("Flint", "UPC.Slot.Ingredient", stack=10, weight=1.0)

keychain = new_data_asset(unreal.UPCContainerDefinition, F, "Container_Keychain")     # a List by default
only_keys = unreal.new_object(unreal.UPCInventoryRule_ItemFilter, outer=keychain)
only_keys.set_editor_property("AllowedTags", tags("UPC.Slot.KeyItem"))
keychain.set_editor_property("Rules", [only_keys])
save(keychain)
pack = new_data_asset(unreal.UPCContainerDefinition, F, "Container_Pack")
save(pack)

config = new_data_asset(unreal.UPCInventoryConfig, F, "InventoryConfig_Hero")
rows = []
for container_tag, definition in (("UPC.Inventory.Container.Keychain", keychain), ("UPC.Inventory.Container.Backpack", pack)):
    row = unreal.UPCContainerSpec()
    row.import_text(f'(Container=(TagName="{container_tag}"),Definition="{definition.get_path_name()}")')
    rows.append(row)
config.set_editor_property("Containers", rows)             # the keychain first
limit = unreal.new_object(unreal.UPCInventoryRule_WeightLimit, outer=config)
limit.set_editor_property("Limit", 30.0)
config.set_editor_property("CarryRules", [limit])
save(config)

bp = load("/Game/Player/BP_Hero")                          # the customer's player
defaults(bp).get_editor_property("InventoryComponent").set_editor_property("Config", config)
save(bp)
```

`UPC.Slot.KeyItem` exists already; the keychain's container tag is new, and `UPC.Inventory.Container.Backpack` is a ready one. Every further key is one more `make_item(..., "UPC.Slot.KeyItem")` with no weight. A key with no World Item fragment has no look on the ground either, so it reaches the player from a chest (Loot chests, below), a reward or a starting item: `inv.add_item_definition(key, 1)` on the server. A key that has to lie in the level carries the fragment and can be dropped again, unless the project's drop ability refuses it in its Can Drop (a Blueprint step).

**Check:** in Play (`AGENTS.md`, Recipe 2, Step 8, starts it and reaches the pawn), `inv` is the pawn's `unreal.UPCInventoryComponent`. With 10 Flint in the pack, `inv.add_item_definition(rusty, 1)` returns a stack, `unreal.GameplayTagLibrary.get_tag_name(inv.get_item_placement(stack).container.tag)` reads `UPC.Inventory.Container.Keychain`, `inv.get_weight(unreal.UPCContainerId())` is the same before and after, and `rusty.find_fragment_by_class(unreal.UPCItemFragment_WorldItem)` is `None`. The flint's placement reads `UPC.Inventory.Container.Backpack`.

**Variant: the keychain is an item the character carries.** A bag opens a container of its own (Bags, below), so the keychain can be an item the player is given, and the pack, which opens bags, turns keys away so adds reach the open keychain. The keychain is a bag with no World Item fragment, so it cannot be dropped either:

```python
holds = keychain                                           # Container_Keychain above: the Item Filter on keys
chain = make_item("Keychain", "UPC.Slot.Upgrade")
bag = unreal.new_object(unreal.UPCItemFragment_Container, outer=chain)
bag.set_editor_property("Definition", holds)               # ContainerTag keeps its default, UPC.Inventory.Container.Bag
chain.set_editor_property("Fragments", [bag])
save(chain)

pack = new_data_asset(unreal.UPCContainerDefinition, F, "Container_PackWithBags")
pack.set_editor_property("bOpensBags", True)
no_keys = unreal.new_object(unreal.UPCInventoryRule_ItemFilter, outer=pack)
no_keys.set_editor_property("BlockedTags", tags("UPC.Slot.KeyItem"))
pack.set_editor_property("Rules", [no_keys])
save(pack)
# The config lists the pack alone; the character starts with the keychain.
defaults(bp).get_editor_property("InventoryComponent").set_editor_property("StartingItems", [chain])
```

###### More pieces from Python

The keychain recipe shows the calls; the rest of the pieces take the same ones.

- **A container shaped as slots or a grid.** `layout = unreal.UPCContainerLayout.SLOTS` with `SlotCount`, or `GRID` with `GridSize` as `unreal.IntPoint(6, 4)` and `bAllowRotation`; either grows with a `CapacityTag`. An item bigger than one grid cell carries a **Grid Footprint** fragment (`Size`, `unreal.IntPoint(1, 3)`). A container off the character, such as a bank, sets `bCarried` off (its items leave the weight limit, the belt and Get Item Count), and `bTakesNewItems` off keeps adds out of it; `AccessTag` locks it to a character holding that tag.
- **A cap per item kind.** A **Category Limit** rule, on a container's `Rules` or the config's `CarryRules`, with `Limits` rows written as text: `row = unreal.UPCCategoryLimit(); row.import_text('(Category=(TagName="UPC.Slot.Ammo.Arrow"),MaxUnits=20)')`, then `rule.set_editor_property("Limits", [row])`.
- **A weight limit that slows instead of refusing.** `bRefuseOverLimit` off and `Thresholds` rows, each `'(Fraction=0.7,Tag=(TagName="UPC.State.Encumbered"))'` through `unreal.UPCWeightThreshold().import_text(...)`; the tag then sits on the character, and the character's **UPC Speed Modifier** or a transition's `BlockedTags` gives it an effect.
- **An item that grows a capacity.** A **Capacity Bonus** fragment whose `Bonuses` row names the capacity tag and an `Amount`, written as text (`unreal.UPCCapacityBonus().import_text('(CapacityTag=(TagName="UPC.Inventory.Capacity.CaseRows"),Amount=1)')`); a container's, a Weight Limit's or a Category Limit's `CapacityTag` names the same tag. The player cannot drop, store or put back such an item while items sit in the slots or rows it gives (`CanItemLeave` answers `UPC.Inventory.Refused.CapacityInUse`); for extra space whose items go with it, make the item a bag.
- **The config's other settings.** `SortOrder` (a list of tags: `[tag("UPC.Slot.Weapon"), tag("UPC.Slot.Consumable")]`), `BeltSpace` and `EquippedSpace` (`unreal.UPCHeldItemSpace.FREES_ITS_PLACE`), `bSwapWhenFull`, and the three at-once switches, all plain `set_editor_property` writes.
- **Which screen it opens.** A config of the project's own needs a row in the screens component's `InventoryScreenClasses`, or it opens the open world list (Inventory screens, below).

###### Building on it in Blueprint

Each extension is a Blueprint class whose parent is a UPC class, created with `unreal.BlueprintFactory` (`parent_class`) and listed on the config; its graph is a Blueprint step. The demo's are in `/UniversalPlayerCombat/Demo/Items/Inventory/Blueprints`, ready to duplicate:

- **A rule** (parent `unreal.UPCInventoryRule`): override `GetUnitsAccepted` to turn items away (`BP_UPC_InventoryRule_UniqueKeyItems` reads the inventory's `GetItemCount`) or `GetStatusTags` to tag the character (`BP_UPC_InventoryRule_Valuables`). The context's `Departing` lists what leaves the rule's scope in the same change, a row per stack (a swap's other item, what a combine takes from both of its stacks), and `ArrivingWith` what comes in beside the item; count the first as gone and the second as already in, as the unique key rule counts `Departing`. List it on a container's `Rules` or the config's `CarryRules`. A rule keeps no state of its own.
- **A combine** (parent `unreal.UPCInventoryCombine`): for what a Combine Set's recipe row cannot say; the demo's Load Holder and Coat Weapon (Combines, below).
- **An item fragment** (parent `unreal.UPCItemFragment`): data an item carries, such as `BP_UPC_ItemFragment_GoldValue` (an integer `Value`). Add an instance to an item's `Fragments` like any fragment; a graph reads it with the item instance's `FindFragmentByClass` and a cast. The C++ sort reads Slot Tags only, so a Blueprint fragment orders nothing by itself.

##### Inventory screens

The demo ships one inventory screen per ready style, Widget Blueprints in `/UniversalPlayerCombat/Demo/UI/Widgets/Inventory`:

| Screen | Opens for | Shows |
| --- | --- | --- |
| `WBP_UPC_InventoryScreen_OpenWorldList` | `InventoryConfig_UPC_OpenWorldList`, any config with no row of its own, and an inventory with no config | A category column, the satchel as one list in `SortOrder`, an item card with its prompts, a weight bar; a chest's items, or the Stash at a storage box, in a mirrored panel of their own on the right |
| `WBP_UPC_InventoryScreen_SurvivalGrid` | `InventoryConfig_UPC_SurvivalGrid` | The case as a grid where items turn to fit, Key Items beside it |
| `WBP_UPC_InventoryScreen_PartyRPG` | `InventoryConfig_UPC_PartyRPG` | The backpack, a panel per open pouch, the paper doll, a weight bar marking both slowdowns |

`BPC_UPC_InventoryScreens`, a component on `BP_UPC_PlayerController`, opens the screen for the character's inventory config on I, or a tap of View on a pad (holding View respawns), and I, View, B or Escape closes it. While it is open the character stands: move, look and combat input stop, and a hit still lands. Every screen also shows the belt while the character holds `UPC.UI.Show.Belt` and the storage panel at a storage box; the grid and party screens show the chest panel at a loot chest. On the grid and party screens a mouse click selects an item and shows its details and actions (Consume and Combine sharing the first row, Equip, Unequip, Split, Drop, Add to belt, each grayed when it does not apply; Take alone for an item in a chest), a drag moves it, and right click runs its main action (Equip, Unequip or Consume; Take or Store at a chest or a storage box); a pad picks up and puts down with A, opens the actions with X, turns a carried item with Y and moves between panels with LB and RB. The list screen opens on All with the first item highlighted and its card showing: the mouse over an item, Up and Down, or the D-pad highlight it and its card follows; a click, a right click, E or pad A runs its main action (Equip, Unequip or Consume; Take or Store at a chest or a storage box), C or the right trigger combines, R or pad X drops it (takes everything at a chest), F or pad Y adds it to the belt, 1 to 4 put it on that belt slot, LB and RB step through the categories, and Left or B goes back to the categories. On every screen Combine holds the item until the next item picked, in any category or panel, is combined with it; Cancel, Back and closing the screen let it go.

**Ask the customer** whether the game uses a demo screen as it is, a copy changed for the game, or a screen of its own.

- **As it is.** Keep `BP_UPC_PlayerController`, or the project's duplicate of it, as the game mode's `PlayerControllerClass`, or add `BPC_UPC_InventoryScreens` to the project's own player controller (Components panel, Add; a Blueprint step), which brings the screens, the I and Back keys and the chest's Open option with it; a player ability set duplicated from `AbilitySet_UPC_Default` already grants the use and drop abilities (Using and dropping from a screen, below). The component's `InventoryScreenClasses` maps each inventory config to its screen, and `FallbackInventoryScreen` serves the rest, so a config duplicated from a demo one needs a row of its own, or it opens the open world list.
- **A copy.** Duplicate the screen into the project's folder, point the screens component's row at the copy, and change the copy in the Widget Blueprint editor (a Blueprint step). Data the customer changes in the editor's Class Defaults or Details panel: the list panel's `CategoryRows` (`WBP_UPC_ListPanel`, which both sides of the list screen share; each a `Label`, the item kinds it shows in `Kinds`, `OtherKinds` for a row taking what no other row names, or `BeltOnly` for a row of the belt's items), the paper doll's `Places` (`WBP_UPC_PaperDoll`: a place tag, a label, a row and a column each, following `BP_UPC_Character`'s places, held once in the doll's class defaults, so a body with other `BodyPlaces` needs them edited there) and the message line's `RefusalWords` (`WBP_UPC_InventoryMessage`: the words shown for each `UPC.Inventory.Refused` and `UPC.Equipment.Cancelled` reason; a reason with no row shows the last part of its tag). A copy keeps using the blocks in `Common` beside the screens, so keep the demo in the project while it does.
- **A screen of the project's own.** Start from a copy all the same: the blocks in `Common` hold every action once. `WBP_UPC_ItemTile` draws one item or one empty place; `WBP_UPC_ContainerList`, `WBP_UPC_ContainerGrid` and `WBP_UPC_ContainerSlots` draw one container each; `BP_UPC_ItemMove` is the carried item, for a mouse drag and a click or pad pick-up alike; `BFL_UPC_InventoryScreens` holds each item action as a check and a send, and the carry flow every screen calls (Pick Up, Put Down, Put Back, Send To Belt); `BPI_UPC_InventoryScreen` is what the screens component and the tiles call on a screen, Show Inventory among them; the panels are `WBP_UPC_BeltPanel`, `WBP_UPC_StoragePanel`, `WBP_UPC_ChestPanel`, `WBP_UPC_BagPanel` and `WBP_UPC_PaperDoll`. Keep the screens' contract: every action asks the check its request runs on the server (Containers, above), sends the request only when the check said yes, and redraws from the components' events once the change arrives, about a round trip later, with nothing predicted. A button that moves an item with no place picked, such as Take or Store, asks the inventory where it lands: `FindTransferDestination` between the player's inventory and a chest, `FindMoveDestination` into one container, `FindAddDestination` where an add would put it; each answers the place one request moves the most units into, with the server's own answer there and, on a refusal, its reason (Full, or Held when the belt or the hand holds the item and more of it would land once they let go), and the button sends `RequestTransfer` or `RequestMove` with that place. Pick Up plays the item's grab sound and Put Down and Put Back its place sound, from the equipment manager's `GetInventoryMoveSound` (the item's own `InventoryGrabSound` and `InventoryPlaceSound`, else its Stowable fragment's `DrawSound` and `PutAwaySound`, and none for a move into or out of the hand that the item's look sounds itself). `UPCInventoryStatics.GetItemFootprint` answers an item's grid cells for a drag picture, the belt's `GetSlotOfItem` the slot an item sits in, and the inventory's `IsHeldBy` whether the belt or a hand holds it.

**The look.** The screens, the HUD and the interaction prompts all draw from one theme asset, `/UniversalPlayerCombat/Demo/UI/Theme/UITheme_UPC_Default`: its colors, fonts, surfaces and paddings restyle every one of them, and a copied screen keeps reading it. In a copy, blocks move freely, and the weight bar, belt panel, hint bar and item details can be deleted. A new widget takes a theme role from a row in its Widget Blueprint's `ThemeRoles` map (the widget's name and an `E_UPC_UIRole`); a renamed widget needs its key renamed too, or that part keeps its own look and the log shows a line (LogBlueprintUserMessages) each time the designer refreshes that widget. A color change is an edit to a copy of the theme in the project's folder, since a plugin update replaces the shipped one:

```python
theme = duplicate(f"{DEMO}/UI/Theme/UITheme_UPC_Default", F, f"UITheme_{N}")
theme.set_editor_property("Accent", unreal.LinearColor(0.80, 0.25, 0.05, 1.0))
save(theme)
```

Every shipped widget reads its theme through the Get UI Theme function of `BFL_UPC_UITheme`, beside the theme asset, which returns `UITheme_UPC_Default`; pointing it at the copy is a Blueprint step for the customer.

A row for a duplicated config or a copied screen goes on the screens component's class defaults: duplicate `BPC_UPC_InventoryScreens` into the project and put the copy on the project's controller in place of the demo's (a Blueprint step), then:

```python
comp = load("/Game/Player/BPC_InventoryScreens")
d = defaults(comp)
screens = d.get_editor_property("InventoryScreenClasses")
screens[load("/Game/Inventory/InventoryConfig_Survival")] = load(f"{DEMO}/UI/Widgets/Inventory/WBP_UPC_InventoryScreen_SurvivalGrid").generated_class()
d.set_editor_property("InventoryScreenClasses", screens)
save(comp)
```

**Opening from the world.** An interaction option whose `OptionId` is `OpenInventory` opens the screen as the interaction starts, with the interactable's UPC World Inventory beside it when the actor has one. Give it a payload that ends as it starts, as the demo's Delay of 0 s, since only a payload reports the start; a longer one keeps the interaction running behind the screen, which hides the prompt and refuses the next interact press after a close (Interaction, below, writes the option as text):

```python
opt = unreal.UPCInteractionOption()
opt.import_text('(OptionId="OpenInventory",ActivationInputTag=(TagName="UPC.Input.Interact"),Prompt=(Verb=NSLOCTEXT("", "", "Open"),Name=NSLOCTEXT("", "", "Chest")),PayloadSpec=/Script/UPCInteraction.UPCDelayInteractionPayloadSpec(DelaySeconds=0.0))')
```

**Check:** in Play on `LVL_UPC_Demo`, F at the hallway's loot chest takes its potions, ring and necklace; I then opens the open world list on its categories, All lists the ring and the necklace above the potions, Consumables lists the potions only, and I closes it again.

###### Storage boxes

A stash holds each player's own items: the Stash is a container of the player's inventory reached with an Access Tag (the survival grid and party RPG recipes, above). The demo's `BP_UPC_StorageBox` (`/UniversalPlayerCombat/Demo/Interactables/StorageBox`) gives a character standing in its zone the Access Tag of that character's own storage, the first container its inventory does not carry, through `GE_UPC_StorageAccess`, applied on the server, and its Open option opens the screen with the storage panel beside the bag. One box serves every style. From the storage panel the player can equip an item or put it on the belt directly: `RequestEquipItem` and `RequestAssignSlot` move it into the carried containers first, and their checks (`CanEquipItem`, `CanAssignSlot`) answer for it; the inventory's `CanTakeToHold` names why one is refused, such as Too Heavy or Full. A chest's items work the same way. To open one container only, place one and set its tag:

```python
actors = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
box = actors.spawn_actor_from_class(EAL.load_blueprint_class(f"{DEMO}/Interactables/StorageBox/BP_UPC_StorageBox"), unreal.Vector(400, 0, 0))
box.set_editor_property("AccessTag", tag("UPC.State.AtStash"))
unreal.get_editor_subsystem(unreal.LevelEditorSubsystem).save_current_level()
```

`LVL_UPC_Wardrobe_Hall` places one stash box between the Backpack And Weight and One List pads.

###### Using and dropping from a screen

Use drinks a potion or eats a herb. The screen's Use sends the inventory's `RequestUseItem`, and a belt slot holding an item that is not equippable does the same when pressed; on the server both send the gameplay event `UPC.Event.UseItem` with the item as the event's Optional Object to the character's ability system. The demo's `GA_UPC_UseItem` (Server Only, Ability Trigger `UPC.Event.UseItem`, granted by `AbilitySet_UPC_Default` with no input) spends one unit and applies the item's `BP_UPC_ItemFragment_UseEffect` (a vital tag and an amount); the screen offers Use only for an item carrying that fragment, and the demo potion, herb and mixed herb do. An ability of the project's own on the same trigger must be Server Only (`NetExecutionPolicy` `SERVER_ONLY`): the event is sent on the server, which starts no Local Predicted ability for a remote player.

Drop sends `UPC.Event.DropItem` with the item to the player's own character, where `GA_UPC_DropFromScreen` (a **UPC Drop Ability** with `ItemSelection` From Event and `DropCount` 0, granted by `AbilitySet_UPC_Default` with no input) drops the whole stack. The screen offers Drop only for an item with a World Item fragment, while the inventory's `CanItemLeave` says yes.

**Check:** in Play, with the character's health below its maximum, Use on a potion spends one (`GetItemStackCount`) and raises `UPC.Vital.Health` (the character's `VitalsComponent`, `GetVitalValue`); a potion on the belt's right slot does the same when its key is pressed.

##### Combines

What combining one item with another makes, such as two herbs mixed into a mixed herb, rounds loaded into a magazine one at a time or a key opening a locked box, lives in a **UPC Combine Set** (`unreal.UPCCombineSet`, a data asset: Add, UPC, Items, Combine Set) on the config's `CombineSets`, so several configs share one set. A drop of one item onto another, or a screen's Combine, asks each set's recipes and then its combines, in order; the first that matches the two items decides, ahead of a merge or a swap. The demo's `CombineSet_UPC_Demo` (`/UniversalPlayerCombat/Demo/Items/Inventory`) holds Mix Herbs and Dip Arrows as recipes and Load Holder and Coat Weapon as Blueprint combines.

**Recipes** (`unreal.UPCCombineRecipe`, plain rows, no Blueprint): `item_a` and `count_a`, `item_b` and `count_b`, `results` (each a `unreal.UPCCombineResult` with `item` and `count`; a second result gives a byproduct back, such as an empty bottle), `order_matters` (off: either item dropped onto the other combines), `required_tags` (the character's ability system must hold them all, else the drop is refused `UPC.Inventory.Refused.Locked`; a workbench zone grants one as the demo storage box grants its Access Tag) and `seconds` (each combine's time, moving stopping it; 0 is at once). A drop makes as many whole combines as the dragged units and both stacks allow; it is refused `UPC.Inventory.Refused.NotEnough` while a stack holds too few for one, and with the add's reason when the results do not fit. The results of each combine are judged with the units it takes already gone, so at a weight limit a result weighing no more than what it uses passes. A row that would use up a bag still holding anything it does not also use is refused `UPC.Inventory.Refused.BagNotEmpty`; the bag is emptied first. The config's `CombineResultPlace` (`unreal.UPCCombineResultPlace`: `ONTO_THE_TARGET`, the default, `ONTO_THE_SOURCE`, `AS_ADDED`) says where the first result goes: the place of the item combined onto, or of the item picked first, once the combine uses that item's stack up, the place is not inside the other item (a bag the combine uses up) and the result fits there, else where an add would; the other results go where an add would. Nothing a combine makes goes on the belt by `bAutoPopulateFromInventory`, but a belt slot an ingredient emptied takes the first result when `CanAssignSlot` lets it in.

```python
herb = load("/Game/Items/Item_Herb")
mixed = load("/Game/Items/Item_MixedHerb")
combine_set = new_data_asset(unreal.UPCCombineSet, "/Game/Inventory", "CombineSet_Knight")
made = unreal.UPCCombineResult()
made.set_editor_property("item", mixed)
recipe = unreal.UPCCombineRecipe()
recipe.set_editor_property("item_a", herb)
recipe.set_editor_property("item_b", herb)
recipe.set_editor_property("results", [made])
recipe.set_editor_property("seconds", 1.0)
combine_set.set_editor_property("recipes", list(combine_set.get_editor_property("recipes")) + [recipe])
save(combine_set)
config = load("/Game/Inventory/InventoryConfig_Knight")     # the project's copy of a demo config
config.set_editor_property("combine_sets", list(config.get_editor_property("combine_sets")) + [combine_set])
save(config)
```

**Combines** (`unreal.UPCInventoryCombine`, instanced on the set's `combines`) for logic a row cannot say. The combine is the project's own class, a Blueprint child overriding its three functions (a Blueprint step) or a C++ subclass in the project's module; the demo's `BP_UPC_Combine_LoadHolder` and `BP_UPC_Combine_CoatWeapon` in `/UniversalPlayerCombat/Demo/Items/Inventory/Blueprints` are ready to copy. Load Holder fills any item carrying the demo Holder fragment (`BP_UPC_ItemFragment_Holder`: `Holds`, the item it takes; `Capacity`; `StatTag`, the stat counting what one unit holds), so a quiver is an item with that fragment holding arrows, with nothing else to set, and the demo screens show its load over its capacity, 12/20.

- `Matches(Item, Target)`: whether it handles Item dropped onto Target.
- `GetUnits(Query, OutReason)`: how many units to make, from 0 to `Query.Count` (`Query`, a `UPCInventoryCombineQuery`: `Inventory`, `Item`, `Target`, `Count`). Asked when the drop is judged, also by a screen previewing it, and again before each unit with the units left; it changes nothing. 0 turns the drop down; set `OutReason` to a tag under `UPC.Inventory.Refused`, such as `UPC.Inventory.Refused.Full`.
- `ApplyUnit(Context)` (`Context`, a `UPCInventoryCombineContext` with the same four fields): makes one unit on the server, such as one more stack of a loaded stat tag on `Target` (`AddStatTagStack`) and one unit of `Item` consumed (the inventory's `ConsumeItemInstance`).
- `UnitTiming` (`unreal.UPCItemActionTiming`): half a second per unit by default, stopped by moving; 0 makes every unit at once.

For a magazine: rounds stack, the magazine has `MaxStackSize` 1 and counts its rounds in a stat tag; `GetUnits` is the least of `Query.Count`, the inventory's `GetItemStackCount(Item)` and the room left, else 0 with `Refused.Full`. For a locked box: `GetUnits` is 1 when the key and the box share a lock tag in their `SlotTags` and the box lacks an unlocked stat tag, else 0 with `UPC.Inventory.Refused.NotAdmitted`; `ApplyUnit` adds the unlocked stat tag and consumes the key; `UnitTiming` 1.5 s.

```python
combine_set = load("/Game/Inventory/CombineSet_Knight")
combine = unreal.new_object(EAL.load_blueprint_class("/Game/Inventory/BP_MagazineCombine"), outer=combine_set)
combine_set.set_editor_property("combines", list(combine_set.get_editor_property("combines")) + [combine])
save(combine_set)
```

The UI changes nothing for a combine beyond what it does for a move: `CanPlace` answers `COMBINE` (`unreal.UPCPlaceResult`) with the units, or `REFUSED` with the recipe's or combine's reason, and `RequestMove` starts it. A Combine button names the target by item instead: `RequestCombine(Item, Target)` combines Item's whole stack with Target, an item in hand with no place included, and `unreal.UPCInventoryStatics.can_combine(inventory, item, target)` is its check: `None` when the two do not combine now, else `(results, reason)` with the recipe's results (`UPCCombineResult` rows) and an empty reason (Python.md says why the yes or no is not returned; a Blueprint node has pins for the results and the reason, `UPC.Inventory.Refused.NoCombine` when nothing combines the two). The inventory's `OnCombined` reports each landing on the server and the owning player's machine, with the two items, the recipe (its `Set` and `RecipeIndex` in that set's `recipes`) or the `Combine`, and the units that landed together in its `Units`. The config's `bCombineAtOnce` lands every unit at once with no wait, as a screen that pauses the game needs, and `OnCombined` then fires once for all of them. While it runs, `OnEquipStepStarted` fires once per unit (the step's `SourceItem` is the target, `UsedItem` the dropped item, `Timing.Seconds` the unit's length) and `OnEquipSequenceEnded` reports the end, while `GetPlayingStep` answers how far the current unit has got and how many units have landed, for a widget opened partway; moving, `RequestCancelSequence` with either item, or either item leaving stops it, and the units made stay. One equipment change runs at a time, so a belt press during a load is turned down.

**Check:** in Play, with a stack of 10 rounds and an empty magazine of 6, the inventory's `CanPlace` for 10 rounds onto the magazine's place answers `COMBINE` with 6; `RequestMove` with the same arguments loads one round per half second, and after 3 seconds the magazine counts 6 and the stack 4.

##### Bags

A bag bar whose pouches hold items, as in an MMO: a bag in a bag bar slot opens a container of its own, and in a backpack that opens no bags it is closed, keeping what it holds out of reach. The demo pouch is `/UniversalPlayerCombat/Demo/Items/Bags/Item_UPC_Pouch`; the demo party RPG config opens it in its backpack instead (`Container_UPC_Backpack` has `bOpensBags` on).

- The bag bar is a config row whose definition has `bOpensBags` on and `bTakesNewItems` off, listed after the backpack, so adds fill the backpack and then the bags in bag bar order (row tag `UPC.Inventory.Container.BagBar`).
- A bag is an item with `MaxStackSize` 1 and a **Container** fragment (`unreal.UPCItemFragment_Container`): `Definition`, the **UPC Container Definition** of what it holds (its own size, rules, `bCarried`, `bTakesNewItems`, `AccessTag`, `bOpensBags` off), and `ContainerTag` (`UPC.Inventory.Container.Bag` by default; never a tag the config's own rows use).
- A bag's container is named by a **UPC Container Id** (`unreal.UPCContainerId`: `Tag` and `ProvidingItem`, the bag); a tag alone names one of the config's rows. `GetContainers` lists each open bag's id right after the bag bar, and `GetContainerOpenedBy` answers it for one bag, so a bag bar slot opens its bag's window with it.

```python
pouch = new_data_asset(unreal.UPCItemDefinition, F, f"Item_{N}_Pouch")
bag = unreal.new_object(unreal.UPCItemFragment_Container, outer=pouch)
bag.set_editor_property("Definition", load("/Game/Inventory/Container_Pouch"))
pouch.set_editor_property("MaxStackSize", 1)
pouch.set_editor_property("Fragments", [bag])
save(pouch)
```

A UI moves a bag onto the bag bar with `RequestMove`; its container opens and `OnCapacityChanged` fires with its id. Dropping a stack on an open bag puts it inside: `CanPlace` answers `INTO_BAG` (`unreal.UPCPlaceResult`), or `REFUSED` with the bag's reason, such as `UPC.Inventory.Refused.Full`. A bag on the belt or in hand stays open, also where the belt or the hand frees its place. A bag keeps what it holds wherever it goes: moved off the bag bar, into storage or a chest, or dropped, it closes with its items inside, and they come back in their places when it opens again or is picked up (a dropped bag's `GetUnitsThatFit` judges the bag and its items together). A bag holding items never goes inside another bag (`REFUSED` with `UPC.Inventory.Refused.BagNotEmpty`), and an item the belt or the hand holds stays with the character when its bag leaves. Game code removing or consuming a bag holding items spills them into the first free places, kept past the end of the backpack when none is free; `TakeItemOut` takes a bag out with its items instead, as a drop does.

**Check:** in Play with a backpack row and a bag bar row on the player's config and a pouch added, `RequestMove` of the pouch to `UPC.Inventory.Container.BagBar` place 0 makes `GetContainerSize` of its id read 8 by 1; a potion stack's `CanPlace` onto that bag bar place answers `INTO_BAG`, and after `RequestMove` the potions sit in the pouch's container (`GetItemsInContainer`).

##### Saving the inventory

UPC is no save system: the game's own Save Game keeps the inventory. Give the Save Game Blueprint a variable of type **UPC Inventory Snapshot** (`unreal.UPCInventorySnapshot`), then, on the server (a Blueprint step):

- Save: the inventory's `ExportSnapshot` into that variable, then Save Game to Slot, while the player's controller still possesses the character.
- Load: Load Game from Slot, then the inventory's `ImportSnapshot` with the variable, from the player controller's On Possess after its parent call, or later. The character's Event Possessed is too early: the belt is not bound yet, so the belt slots stay empty and a warning says so. The import replaces everything the inventory held, starting items included, also on the first possession at map start, which comes before Begin Play, and puts back each stack's place, stat data, belt slot and what was in hand or worn, in the same places; anything the belt or the hand held from outside the inventory is put away.

Keep the redirector of an item asset moved or renamed after a save, or add a CoreRedirect for it, or the import leaves that item out with a warning. An item whose saved place a changed config no longer has takes the first free place, a bag's items come back inside it, and a stack saved larger than its item's Max Stack Size comes back as several stacks.

##### Pickups

A **UPC World Item** (`AUPCWorldItem`, or the demo's `BP_UPC_WorldItem`) placed in the level is picked up with the interact key: `ItemDefinition`, `StackCount`, `PhysicsMode`. It shows the item's Visuals mesh (its `DropMesh` when set) only for an item that also has a World Item fragment, so the item needs both. To put a fresh item in the world from Blueprint, as the demo's pickup spots do when they restore a taken pickup, Spawn Actor `BP_UPC_WorldItem` on the server and set `Item Definition`, `Stack Count` and `Physics Mode` on the node; one world item class serves every item.

```python
actors = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
pickup = actors.spawn_actor_from_class(unreal.UPCWorldItem, unreal.Vector(300, 0, 50))
pickup.set_editor_property("ItemDefinition", load(f"{F}/Item_{N}_Sword"))
unreal.get_editor_subsystem(unreal.LevelEditorSubsystem).save_current_level()
```

The duplicated player's ability set grants the interact ability on `UPC.Input.Interact` (E). A picked-up item goes into the inventory, and onto the first belt slot that takes it while the belt's `bAutoPopulateFromInventory` is on, as the demo controller's is.

##### Drops

Give the item a **World Item** fragment (`UUPCItemFragment_WorldItem`: `DropMontage`, `WorldItemActorClass`, `DropAnchor`, `DropOffset`, `bProjectToGround`, `DropRotation`, `bSimulatePhysics`, and `ImpactSound` with `ImpactSilentSpeed` and `ImpactFullSpeed` for the sound it makes each time it lands or bounces, louder the harder it hits) and the item's Visuals `HeldMesh` (or `DropMesh`) shows on the ground. The duplicated player's ability set grants the **UPC Drop Ability** (`UUPCDropAbility`: `ItemSelection`, `DropCount`, `bSkipUndroppable`) on `UPC.Input.Drop`, which `IMC_UPC_Default` maps to G. By default it drops what a hand holds, the weapon first, read from the equipment manager whether the item is on the belt or not; worn gear is never in hand, so G never drops a ring or a helmet. A screen's Drop button reaches a second drop ability, `GA_UPC_DropFromScreen`, through the event `UPC.Event.DropItem` (Using and dropping from a screen, above).

**Check:** in Play, G drops the held item as a world item and the inventory loses it; E near it picks it back up.

##### Item holders

A **UPC Item Holder** component (`UUPCItemHolderComponent`: `Places`, `unreal.UPCItemHolderPlace` rows `PlaceId`, `PlaceComponentTag`, `Filter`; `ClaimRadius`) on a replicated actor offers Take and Put Back for each place. Duplicate the demo's `BP_UPC_WeaponRack`.

##### Loot chests

A **UPC World Inventory** component (`UUPCWorldInventoryComponent`) on a replicated actor holds items every nearby player sees: `Config` and `StartingItems` as on a character's inventory, `AccessRadius` (cm from the character to the actor's collision, at least the interactor's Detection Radius plus the interact ability's Server Range Tolerance; 0 reaches from any distance) and `AccessTag` (a tag the character's own ability system must hold, a key). Duplicate the demo's `BP_UPC_LootChest` (`/UniversalPlayerCombat/Demo/Interactables/LootChest`). Its UPC Interactable has two options: Open (`OpenInventory`, on `UPC.Input.Interact`, E) opens the player's inventory screen with the chest's items beside the player's (Opening from the world, above), and Take All (on `UPC.Input.Interact.Secondary`, F) calls the interacting character's inventory `RequestTakeAll` with the chest's World Inventory, which takes everything that fits and leaves the rest.

To give a chest contents of the project's own, duplicate the demo chest, write its World Inventory component and place the copy in the level. The component is a template inside the Blueprint (Python.md, Components on a Blueprint); `InventoryConfig_UPC_LootChest` is the demo's thirty-slot config, or make a config as the keychain recipe does and list its container on it:

```python
chest = duplicate(f"{DEMO}/Interactables/LootChest/BP_UPC_LootChest", F, "BP_ProjectChest")
sds = unreal.get_engine_subsystem(unreal.SubobjectDataSubsystem)
for handle in sds.k2_gather_subobject_data_for_blueprint(chest):
    obj = unreal.SubobjectDataBlueprintFunctionLibrary.get_object_for_blueprint(unreal.SubobjectDataBlueprintFunctionLibrary.get_data(handle), chest)
    if isinstance(obj, unreal.UPCWorldInventoryComponent):
        obj.set_editor_property("Config", load(f"{DEMO}/Items/Inventory/InventoryConfig_UPC_LootChest"))
        obj.set_editor_property("StartingItems", [potion, potion, rusty])      # one unit per entry; repeats join into one stack
        obj.set_editor_property("AccessRadius", 250.0)                         # cm; an AccessTag locks it to a key holder
unreal.BlueprintEditorLibrary.compile_blueprint(chest)
save(chest)
```

Place the copy in the level the way the storage box example does (Storage boxes, above).

The demo's chest panel, `WBP_UPC_ChestPanel`, reads the World Inventory like any inventory (`GetItemsInContainer`, `OnLayoutChanged`) and moves items through the player's own inventory: `CanTransfer` and `RequestTransfer` (an item in either inventory, a destination in the other) and `RequestTakeAll` for its Take All button. Put the loot on an actor of its own, never on a character, and leave the chest config's container Access Tags empty; the World Inventory's own `AccessTag` locks the chest.

**Check:** in Play with two players, both read the chest's items with `GetItemsInContainer`, and F at the chest (Take All) moves them into the interacting player's inventory.

##### Interaction

The character's **UPC Interactor** (`InteractorComponent`): `Detection` (`unreal.UPCInteractionDetection`: `NEAREST`, `FOCUSED`, `AIM`, `OVERLAP`), `Tracking` (`CONTINUOUS`, `ON_DEMAND`), `DetectionRadius`, `FocusConeHalfAngle`, `RequireSight`, `RequiredTags`, `BlockedTags`.

An actor becomes interactable with a **UPC Interactable** component (`UUPCInteractableComponent`): `Options` (`unreal.UPCInteractionOption` rows: `OptionId`, `ActivationInputTag` (`UPC.Input.Interact`), `Prompt` (`Verb`, `Name`, `Icon`), `PayloadSpec`, `Approach` (`SpotComponentTag`, `AcceptanceRadius`), `Restrictions` (`bLockMovement`, `bLockFacing`, `bLockCamera`, `bEndRunningAction`), `bCancelByAbility`, `Priority`). With no payload, choosing the option fires the component's On Option Interacted event, which the actor's own Blueprint answers (opening the door: a Blueprint step; the demo's `BP_UPC_Door` and `BP_UPC_Chest` show it).

`PayloadSpec` makes the character do something first: Play Montage (`FUPCMontageInteractionPayloadSpec`: `Montage`, `PlayRate`, `SectionName`), Delay (`FUPCDelayInteractionPayloadSpec`), Trigger Ability / Event (`FUPCEventInteractionPayloadSpec`), Run Ability (`FUPCRunAbilityInteractionPayloadSpec`) or Payload Asset (`FUPCAssetInteractionPayloadSpec`). Run Ability triggers the character's own ability (Server Initiated or Server Only, granted once) with the interactable as the event's Target; the ability reads which option started it with Get Interaction Option Id on the event's Target Data (`UUPCInteractionStatics::GetInteractionOptionId` in C++). Python cannot set its fields one by one; write the option as text, the payload as its struct path followed by its fields:

```python
opt = unreal.UPCInteractionOption()
opt.import_text('(OptionId="Pull",ActivationInputTag=(TagName="UPC.Input.Interact"),Prompt=(Verb=NSLOCTEXT("", "", "Pull"),Name=NSLOCTEXT("", "", "Lever")),PayloadSpec=/Script/UPCInteraction.UPCMontageInteractionPayloadSpec(Montage="/Game/Player/AM_Knight_PullLever.AM_Knight_PullLever",PlayRate=1.0))')
```

Read `opt.export_text()` back to confirm the payload took. An **Action Payload** asset for the Payload Asset kind is made with `unreal.UPCActionPayloadFactory`: set its `PayloadClass` (such as `unreal.UPCMontageActionPayload`), then `TOOLS.create_asset(name, folder, unreal.UPCActionPayload, factory)`.

##### Mistakes

| Symptom | Cause and fix |
| --- | --- |
| A drop on an item merges or swaps the two instead of combining them | No recipe or combine in the Combine Sets on the inventory's config takes the pair, the set is not on the config's `CombineSets`, or the character's inventory uses another config. |
| A key lands in the pack, not on the keychain | The keychain is not listed before the pack in the config's `Containers`, or its Item Filter's `AllowedTags` does not name the kind in the key's `SlotTags` (`UPC.Slot.KeyItem`). Rules are on the container definition's `Rules`, not on the config row. |
| Everything lands on the keychain | A List takes anything its rules allow; the keychain has no Item Filter rule, or the rule was never written to the container definition's `Rules` list. |
| A key can be dropped, or counts toward the weight limit | The key carries a World Item fragment (droppable) or a Weight fragment (weighs); leave both off the item definition. |
| A recipe's drop does nothing at the weight limit | The result weighs more than the units the combine takes, so the load would pass the limit (`UPC.Inventory.Refused.TooHeavy`). |
| A recipe using a bag does nothing | The bag holds an item the recipe does not use (`UPC.Inventory.Refused.BagNotEmpty`); take it out first. |
| A rule, fragment or row set from Python has no effect | The list was edited and not written back with `set_editor_property`, or a row struct (a container row, a threshold, a limit, a bonus) was filled field by field instead of with `import_text` (Python.md). |
| A combine does nothing | The character has no UPC Equipment Manager (a warning names it), or another equipment change is running. |
| An item lands in no slot | The belt's `bAutoPopulateFromInventory` is off, every slot's `Filter` refuses its `SlotTags`, the belt is full, or the character carries one of the belt's `BlockedTags`. |
| A belt press does nothing under one combat style | That style's Gameplay Tags fragment puts on a tag the belt lists in `BlockedTags`, such as `UPC.State.BeltBlocked` on the demo Freeflow style. |
| A ring or a helmet lands on the belt and draws with a number key | The belt's slots have no `Filter`; give each slot an item kind, as the demo's diamond does. |
| I opens no inventory screen | The game mode's player controller carries no `BPC_UPC_InventoryScreens`, or its default mapping context lacks `IA_UPC_Inventory`. |
| I opens the open world list for another style | The screens component's `InventoryScreenClasses` has no row for the config the character's inventory uses, as for a duplicated config; add one. |
| A screen offers no Use, or Use does nothing | The item carries no `BP_UPC_ItemFragment_UseEffect`, the character's ability set lacks `GA_UPC_UseItem`, or the project's own use ability is not Server Only. |
| A potion on the belt does nothing when pressed | No Server Only ability answers `UPC.Event.UseItem` on the character. |
| A screen's Drop does nothing | The character's ability set lacks `GA_UPC_DropFromScreen`, or the item has no World Item fragment. |
| The storage panel never shows at a box | The box's `AccessTag` differs from the container's Access Tag in the character's config, or the character's config has no container the character does not carry. |
| A stack on the belt cannot go into the stash | A whole stack on the belt never goes into storage the character does not carry; take it off the belt with `RequestAssignSlot` first, as the demo screens' Store does. |
| The pickup shows no mesh | The item has no World Item fragment, or no Visuals mesh. |
| G does nothing | The held item has no World Item fragment, or the character's ability set lacks the drop ability. |
| The prompt never shows | The interactor's `Detection` or `DetectionRadius` misses the actor, or `RequireSight` is blocked. |
| A placed actor ignores Blueprint changes to its options | The placed actor keeps its own copy of `Options`; reset it to the Blueprint's on the placed actor. |
| A chest shows its items on the server only | The chest's actor does not replicate; turn on Replicates in its Class Defaults (a warning names the class). |
| Take All or a transfer moves nothing | The character stands farther than the chest's `AccessRadius` from its collision, lacks its `AccessTag`, nothing fits in its inventory, or the request went to a World Inventory rather than the player's own inventory. |

#### Items and equipment: fragments, hands, shields and stowing (`AgentGuide/ItemsEquipment.md`)

`AGENTS.md`, Recipes 2 and 4, make an unarmed item and a sword. This file covers the rest of an item: every fragment, which hand it takes, an off-hand shield, items shared through a preset, hanging items on the body when put away, and worn gear.

Tasks: an item that is not gear (a key, a material, a potion) and what it weighs, whether it can be dropped and where it is kept; a shield in the left hand beside the sword; a two-handed weapon; a supporting item that adds moves only while drawn; all swords sharing one set of keys; a sword on the back when sheathed; a draw animation; a Swing Trail behind each swing; only one heavy weapon on the belt; a helmet, a cape, armor or rings the character wears.

##### The item and its fragments

An **Item Definition** (`UUPCItemDefinition`): `Fragments`, `Preset` (`UUPCItemPreset`, whose `Fragments` the item inherits unless it has its own of that kind), `SlotTags` (which belt slots take it), `MaxStackSize`, `DisplayName`, `Description`, `Icon`. Make each fragment with `unreal.new_object(cls, outer=item)` and write the whole `Fragments` list back.

| Fragment (picker label) | Does | Fields |
| --- | --- | --- |
| `UUPCItemFragment_Equippable` (Equippable) | Makes it drawable | `ActionGraph`, `bIsMainWeapon` (label Main Weapon), `HoldPlaces`, `PlacesRequired`, `EquipmentTags`, `InputMappingContext`, `InputMappingPriority`, `InputMappings`, `InputChords`, `TraceSockets`, `AttackStopDistance`, `AbilitySetsToGrant`, `AnimLayerClass`, `ItemActorClass`, `InstanceType`, `DrawTiming` and `StowTiming` behind `bOverrideDrawTiming` and `bOverrideStowTiming` |
| `UUPCItemFragment_Visuals` (Visuals) | What it looks like | `HeldMesh` (label Mesh), `bOverridePerState`, `StowMesh`, `DropMesh` |
| `UUPCItemFragment_WornMesh` (Worn Mesh) | Skeletal meshes worn on the body while equipped, following its pose | `Meshes` |
| `UUPCItemFragment_Guard` (Guard) | Lends a defense rule set while drawn | `DefenseSet` (`AgentGuide/DamageDefense.md`) |
| `UUPCItemFragment_Stowable` (Stowable) | Hangs on the body when put away | `StowPlaces`, `StowActorClass` |
| `UUPCItemFragment_WorldItem` (World Item) | Lies on the ground: dropped and picked up | `AgentGuide/InventoryInteraction.md` |
| `UUPCItemFragment_SwingTrail` (Swing Trail) | A ribbon behind swings | `TrailSystem`, `StartSocket`, `EndSocket`, `TrailSeconds`, `Tint`, `TrailTags` |
| `UUPCItemFragment_Weight` (Weight), `UUPCItemFragment_GridFootprint` (Grid Footprint), `UUPCItemFragment_CapacityBonus` (Capacity Bonus) | How heavy one unit is, the cells it covers in a grid container, and the capacity owning it adds (`AgentGuide/InventoryInteraction.md`, Containers) | `Weight`; `Size`; `Bonuses` |
| `UUPCItemFragment_Container` (Container) | Makes the item a bag that opens a container of its own on a bag bar (`AgentGuide/InventoryInteraction.md`, Bags) | `Definition`, `ContainerTag` |
| `UUPCItemFragment_BeltLimit` (Belt Limit) | Caps its copies on the belt | `MaxBeltCount` |

**An item that is not gear** (a key, a material, a potion) needs no Equippable fragment. Its kind is `SlotTags` (`UPC.Slot.KeyItem`, `UPC.Slot.Ingredient`, `UPC.Slot.Consumable`; the inventory's rules and the belt read it), `MaxStackSize` sets how many share a stack, and each fragment adds one behavior: Weight makes it count toward a weight limit, World Item lets it lie on the ground and be dropped, Grid Footprint gives it cells in a grid, Container makes it a bag, Capacity Bonus grows a capacity. An item has only the behaviors whose fragment it carries: with no Weight fragment it weighs nothing, with no World Item fragment it cannot be dropped. `AgentGuide/InventoryInteraction.md`, Recipe: a keychain, builds items of this kind with the container and config that keep them.

**Main Weapon.** `bIsMainWeapon` on (the default): its graph drives combat while drawn. Off with an `ActionGraph`: it drives only while no main weapon is drawn (a torch). Off with no graph: a supporting item (a shield), whose moves live in the graph of the weapon it is held with, or in a sub graph gated on its `EquipmentTags` (`AgentGuide/ActionGraphs.md`, Sub graphs).

##### Hands and places

`HoldPlaces` rows (`unreal.UPCHoldPlaceSpec`: `Place`, `Offset`, `SocketOverride`, `PlaceTags`) list where an item can be held, in order; `PlacesRequired` is how many it takes. A new Equippable holds `UPC.Place.Main`. The character's `EquipmentManagerComponent.BodyPlaces` maps each place to a socket (`UPC.Place.Main` to `hand_r`, `UPC.Place.Off` to `hand_l`). Python cannot set a row's fields one by one, so write the row as text:

```python
row = unreal.UPCHoldPlaceSpec()
row.import_text('(Place=(TagName="UPC.Place.Off"))')
eq.set_editor_property("HoldPlaces", [row])
```

A two-handed weapon lists both hands and takes both: two rows (`UPC.Place.Main`, `UPC.Place.Off`) and `PlacesRequired` 2.

A sword held in either hand lists both, main first, with `PlacesRequired` 1: a second copy takes the off hand. Turn on the off-hand row's `bMoveToEarlierPlaceWhenFree` (Move To Earlier Place When Free, written with `import_text` like the row's other fields) and, once the main-hand sword is put away or dropped, the other moves into the main hand and drives; leave it off for an item that stays in the hand it was drawn into, such as a dagger kept in the off hand. To put the second sword on a diamond belt's off-hand slot, give it the off-hand kind too (`SlotTags` `UPC.Slot.Weapon.Sword` and `UPC.Slot.OffHand`).

##### Recipe: a shield in the left hand

```python
shield_mesh = load("/Game/.../SM_Shield")
shield = new_data_asset(unreal.UPCItemDefinition, F, f"Item_{N}_Shield")
vis = unreal.new_object(unreal.UPCItemFragment_Visuals, outer=shield)
vis.set_editor_property("HeldMesh", shield_mesh)
eq = unreal.new_object(unreal.UPCItemFragment_Equippable, outer=shield)
eq.set_editor_property("bIsMainWeapon", False)
eq.set_editor_property("EquipmentTags", tags("UPC.Equipment.Shielded"))
row = unreal.UPCHoldPlaceSpec()
row.import_text('(Place=(TagName="UPC.Place.Off"))')
eq.set_editor_property("HoldPlaces", [row])
shield.set_editor_property("Fragments", [vis, eq])
save(shield)

inv = defaults(bp).get_editor_property("InventoryComponent")
items = list(inv.get_editor_property("StartingItems"))
items.append(shield)
inv.set_editor_property("StartingItems", items)
save(bp)
```

- The belt takes each starting item into its own slot and draws every one that fits beside what is held, so the sword (main hand) and the shield (off hand) are both drawn at the start. The slot keys 1 to 4 draw and put away each one.
- To draw an item the inventory holds from code at once, putting nothing away, as a loadout handed out in a level does: the equipment manager's `EquipItemIfFree` (server only), which draws it only while it is equippable, not drawn, carried and in reach and every place it needs is free.
- To give a character gear from Blueprint, on the server: the inventory's Add Item Definition, then Equip Item If Free on the equipment manager with the item it returns. A null answer means a place is taken, such as by another cape; Request Equip Item puts that one away first. Remove Item Instance takes the gear off as it leaves the inventory. For a game with no inventory, belt or weapons that only dresses a character, skip the inventory: Equip Item on the equipment manager (server) takes the item definition with no inventory item, puts away whatever holds the places it needs (places are gameplay tags, as many as the body's Body Places names) and returns the drawn instance, null when the body has no place for it; Unequip Item with that instance, or the one Get Instance In Place answers, takes it off. A player's equip request, `RequestEquipItem`, also takes an item in the Stash or a chest, moving it into the carried containers first.
- The shield's block and parry are its Guard fragment (`AgentGuide/DamageDefense.md`); the guard move goes in the sword's graph (`AgentGuide/ActionGraphs.md`).
- `UPC.Equipment.Shielded` sits on the character while the shield is drawn, so a transition or sub graph can require it.
- If the shield sits crooked, judge a correction from a capture and write it as the row's `Offset`, as `AGENTS.md`, Recipe 4, Step 5, does for the sword.

**Check:** reload the item and print its fragments' classes, `bIsMainWeapon`, the hold place and `EquipmentTags`. In Play, the shield is in the left hand and the sword in the right.

##### Stowing on the body

An item with no Stowable fragment disappears when put away. To hang it on the body: a `UUPCItemFragment_Stowable` with `StowPlaces` rows (`unreal.UPCStowPlaceSpec`: `Place` such as `UPC.Place.Back.Left`, `Offset`, `DrawMontage`, `StowMontage`), written with `import_text` as above; a **UPC Stow Visual** component on the character Blueprint (the duplicated demo player has one); and the place's socket on the customer's skeleton, mapped in `BodyPlaces`. By default only an item on the belt hangs while out of hand, and an item only in the inventory shows nowhere on the character; the inventory config's `bShowCarriedItemsOnBody` hangs every carried item outside a bag as well, as the demo survival grid does, so no weapon can be hidden. A draw or stow montage carries a **UPC Equipment Commit** notify (`UUPCEquipmentCommitNotify`, `BlendSeconds`) on the frame the hand closes on the grip; add it with `unreal.AnimationLibrary.add_animation_notify_event(montage, "Combat", seconds, unreal.UPCEquipmentCommitNotify)`. The demo's reach montages belong to the demo mannequin; the customer's character needs its own. The item's own sounds go on the same fragment, never on the reach montage, so items sharing one reach still sound like themselves: `DrawSound` plays as the item arrives in the hand and `PutAwaySound` as it arrives in its place, whether a reach, a pickup or a screen moved it, on every machine that shows the character; an item that appears with nothing showing it before, such as a starting item, plays nothing.

##### Worn gear: helmets, capes, armor and rings

Worn gear is an Equippable item with `bIsMainWeapon` off and no `ActionGraph`, so it is drawn beside the weapon and leaves the moves alone. Its places are gameplay tags the project names, such as `UPC.Place.Head` and `UPC.Place.Ring.Left`, or only a top and a bottom. The character's `EquipmentManagerComponent.BodyPlaces` says where each place is on the body; a row needs a `Socket` only where a look sits. The duplicated demo player already has rows for the demo's worn places (`AGENTS.md`, Step 2), so the script below sets the socket on a row it finds and adds a row only for a place that is missing. Give worn gear `SlotTags` the belt slots' `Filter` does not pass, or a belt that takes new items puts it on a slot and puts it on at pickup. On a belt, its key puts it on and takes it off; it never counts as in hand, since only the equipment manager's `HandPlaces` (`UPC.Place.Main` and `UPC.Place.Off` by default) are hands, so the belt does not highlight its slot and the drop key never drops it. A body whose weapons are held in places of its own lists those places in `HandPlaces`.

| Look | How |
| --- | --- |
| A static mesh on one bone (a helmet, a ring) | Visuals `HeldMesh`, the body place's socket or bone, and the hold place's `Offset` to turn it into place |
| A skinned mesh on the body (a cape, gloves, armor) | Worn Mesh `Meshes`, rigged to the character's skeleton; it follows the body's pose and comes off with the item |
| Nothing (a necklace that only grants a stat) | No look fragment, and a body place with no socket |

A ring's bonus is a tag in its `EquipmentTags`, held on the character while the ring is worn, that something else reads: the duplicated demo player's UPC Speed Modifier turns `UPC.State.Swift` into five percent more walk speed, as the demo ring and necklace use it, and another character gets a row for the tag in its UPC Speed Modifier's `SpeedScaleByTag`. An attribute bonus is a gameplay effect in an ability set's `GrantedGameplayEffects`, on the Equippable's `AbilitySetsToGrant`, and that effect is authored in the editor, since Python cannot set a gameplay effect's components: a Gameplay Effect Blueprint with Duration Policy Infinite, plus a modifier for an attribute or, for a tag, the Grant Tags to Target Actor component with the tag in its Add Tags. Items fill their places in the order `HoldPlaces` lists them, so two rings take the left hand, then the right, and a third replaces the left one; `CanDraw(item, evicted)` names the one it would replace. A UI keeps its own list of the places it shows, asks `GetInstanceInPlace` for each and skips an instance whose `IsDefaultItem` is true, redraws on the equipment manager's `OnDrawnStateChanged`, which fires once a change has settled, the item's place included, and asks `RequestEquipItem` with the place an item was dropped on (`CanEquipItem` with that place first) and `RequestUnequipItem`. Hiding the body under armor, morph targets and cloth are set up in the meshes.

```python
eqm = defaults(bp).get_editor_property("EquipmentManagerComponent")
rows = list(eqm.get_editor_property("BodyPlaces"))
for place, socket in (("UPC.Place.Head", "head"), ("UPC.Place.Shoulders", "None"), ("UPC.Place.Ring.Left", "ring_01_l"), ("UPC.Place.Ring.Right", "ring_01_r")):
    row = unreal.UPCBodyPlace()
    row.import_text(f'(Place=(TagName="{place}"),Socket="{socket}")')
    found = [i for i, r in enumerate(rows) if str(unreal.GameplayTagLibrary.get_tag_name(r.get_editor_property("Place"))) == place]
    if found:
        rows[found[0]] = row
    else:
        rows.append(row)
eqm.set_editor_property("BodyPlaces", rows)
save(bp)

def worn(item, places):
    eq = unreal.new_object(unreal.UPCItemFragment_Equippable, outer=item)
    eq.set_editor_property("bIsMainWeapon", False)
    held = []
    for place in places:
        row = unreal.UPCHoldPlaceSpec()
        row.import_text(f'(Place=(TagName="{place}"))')
        held.append(row)
    eq.set_editor_property("HoldPlaces", held)
    return eq

helmet = new_data_asset(unreal.UPCItemDefinition, F, f"Item_{N}_Helmet")
vis = unreal.new_object(unreal.UPCItemFragment_Visuals, outer=helmet)
vis.set_editor_property("HeldMesh", load("/Game/.../SM_Helmet"))
helmet.set_editor_property("Fragments", [worn(helmet, ["UPC.Place.Head"]), vis])
save(helmet)

cape = new_data_asset(unreal.UPCItemDefinition, F, f"Item_{N}_Cape")
look = unreal.new_object(unreal.UPCItemFragment_WornMesh, outer=cape)
look.set_editor_property("Meshes", [load("/Game/.../SK_Cape")])
cape.set_editor_property("Fragments", [worn(cape, ["UPC.Place.Shoulders"]), look])
save(cape)

ring = new_data_asset(unreal.UPCItemDefinition, F, f"Item_{N}_Ring")
ring_eq = worn(ring, ["UPC.Place.Ring.Left", "UPC.Place.Ring.Right"])
ring_eq.set_editor_property("EquipmentTags", tags("UPC.State.Swift"))
ring_vis = unreal.new_object(unreal.UPCItemFragment_Visuals, outer=ring)
ring_vis.set_editor_property("HeldMesh", load("/Game/.../SM_Ring"))
ring.set_editor_property("Fragments", [ring_eq, ring_vis])
save(ring)
```

- Add each place tag to the project's tags first; the plugin declares the demo's places.
- A ring that sits crooked gets an `Offset` on each of its two rows, written into the row text as for the shield.

**Check:** in Play, equip each item from the inventory: the helmet sits on the head, the cape moves with the body, the first ring goes on the left hand and the second on the right, and the ring's stat comes and goes with it.

##### Mistakes

| Symptom | Cause and fix |
| --- | --- |
| The shield replaces the sword | The shield has `bIsMainWeapon` on, or its hold place is `UPC.Place.Main`. |
| Putting on a helmet takes the sword's moves away | The helmet has `bIsMainWeapon` on; worn gear has it off and no `ActionGraph`. |
| A worn item is refused | The character's `BodyPlaces` has none of the item's hold places. |
| A cape or gloves stand in the reference pose or float | The worn mesh is rigged to another skeleton than the character's mesh tagged `UPC.Attachment`. |
| The item is invisible in the hand | No Visuals `HeldMesh` and no `ItemActorClass`, or the body place's socket is missing on the skeleton. |
| A Hold Places row is empty after a reload | It was set field by field; write it with `import_text`. |
| The item vanishes when put away | No Stowable fragment or UPC Stow Visual, or off the belt with `bShowCarriedItemsOnBody` off. |

#### Paired Actions: finishers, counters and grabs (`AgentGuide/PairedActions.md`)

A paired action is two characters playing synced animations together: an execution, a counter, a vault over an enemy. Tasks: a finisher from two animations; executing an enemy only while it is stunned; dealing the damage on the right frame; a cinematic camera during it; starting it from a combo.

##### The asset

A **Paired Action** (`UUPCPairedActionAsset`, `new_data_asset`) holds `Participants` (`unreal.UPCPairedActionParticipant`), one per role, and its placement and timing:

| Participant field | Does |
| --- | --- |
| `RoleId` | The role's name, such as `Attacker` or `Victim`. Read-only in the editor; set it with a constructor keyword: `unreal.UPCPairedActionParticipant(role_id="Attacker")` |
| `Montage`, `StartSection` | What this role plays |
| `Offset` | Where it stands relative to the shared anchor |
| `TravelMode` | How it gets to its spot |
| `bRequired` | The action fails without a body in this role |
| `RequiredActorTags`, `BlockedActorTags` | Who may fill it |
| `TagsDuringAction` | Tags held while it plays (`UPC.State.ActionsBlocked` from the start) |
| `Events` | What happens on frames: `UUPCPairedActionEvent_Damage` (Deal Damage: `DamageConfig`, `DamageOverride`, `LeaveHealthAtLeast`, `bKill`, `ExtraDamageTags`, `TimeSeconds`, `TargetRoleId`), `UUPCPairedActionEvent_Ragdoll` (Ragdoll: `Impact` with `Bone`, `Velocity` in cm/s along `ImpactFrame` (X ahead, Z up) and `Spread`) and others |
| `Hooks` | Behavior riding the role: `UUPCPairedActionHook_AutoCinematicView` (`FocusRole`, `ViewAngle`, `HeightOffset`), `UUPCPairedActionHook_CameraShot` (`Shot`) |
| `bSyncWithCurrent`, `SyncMarker` | Start from an animation already playing, meeting at a **UPC Paired Sync Marker** notify (`MarkerName`) |

Asset fields: `AnchorMode`, `AnchorRoleId`, `MaxSlotDistance`, `MaxSlotYawDegrees`, `bRequireClearSlots`, `AbortRule`, `ReleaseRule`, `PlayRate`, `WarpTargetName`.

```python
pa = new_data_asset(unreal.UPCPairedActionAsset, F, f"Paired_{N}_Execute")
atk = unreal.UPCPairedActionParticipant(role_id="Attacker")
atk.set_editor_property("Montage", am_exec_attacker)
atk.set_editor_property("bRequired", True)
vic = unreal.UPCPairedActionParticipant(role_id="Victim")
vic.set_editor_property("Montage", am_exec_victim)
vic.set_editor_property("bRequired", True)
cut = unreal.new_object(unreal.UPCPairedActionEvent_Damage, outer=pa)
cut.set_editor_property("DamageConfig", dmg_heavy)
cut.set_editor_property("LeaveHealthAtLeast", 1.0)     # an earlier blow leaves the victim alive for the killing blow
cut.set_editor_property("TimeSeconds", 0.6)
cut.set_editor_property("TargetRoleId", "Victim")
hit = unreal.new_object(unreal.UPCPairedActionEvent_Damage, outer=pa)
hit.set_editor_property("DamageConfig", dmg_heavy)
hit.set_editor_property("bKill", True)                 # takes all the health left, whatever defends the victim
hit.set_editor_property("TimeSeconds", 1.2)
hit.set_editor_property("TargetRoleId", "Victim")
atk.set_editor_property("Events", [cut, hit])
pa.set_editor_property("Participants", [atk, vic])
pa.set_editor_property("AnchorRoleId", "Victim")        # the victim stands still and the attacker lines up on it
pa.call_method("DeriveOffsetsFromMontages")
save(pa)
print(upc.validate_asset(pa))
```

`DeriveOffsetsFromMontages` places each role from where its montage puts it, for animations authored together; set the anchor role first. `upc.validate_asset(pa)` lists what the pairing editor would warn about (a role with no montage, an anchor role that does not exist, a travel setting that loses the montage's own motion), each line naming the setting to change.

##### Starting it

- **From a combo:** a move whose payload is `UUPCPlayPairedActionPayload` (Play Paired Action: `Source` `PAIRED_ACTION` with `PairedAction`, `OwnerRoleId`, `TargetRoleId`, `PrimaryPicker`; or `PAIRED_ACTION_SETS` with `PairedActionSets`), reached by a transition with a **Paired Action Available** condition (`UUPCPairedActionCondition`, `Actions`) or a **Target State** condition requiring a stun tag (`AgentGuide/ActionGraphs.md`). For a move the stick aims, such as a vault, make the `PrimaryPicker` a Proximity Scan (`unreal.UPCPairedTargetPicker_Scan`: `Radius`, `MaxAngleDegrees`, `bArcFollowsMovementInput` on), and give the node an `ON_FAILED` transition to the move that plays when nobody stands in that direction, such as a roll.
- **A set of options:** a **Paired Action Set** (`UUPCPairedActionSet`: `GatherRadius`, `GatherArcDegrees`, `Options` rows `unreal.UPCPairedActionOption` `Action`, `OwnerRoleId`, `TargetRoleId`, `PrimaryPicker`, `RequiredTargetTags`, `TargetTeams`, `Priority`), which one button searches. The demo sword's `PairedActionSet_UPC_SwordExecute` executes only the demo's Soulslike boss in its finisher stun: its one row requires `UPC.Enemy.SoulslikeBoss`, which `BP_UPC_Boss_Soulslike` carries in its `CombatComponent` `CharacterTags`, and `UPC.State.Finishable`, which that stun holds. For one action with its own montages per enemy type, put a type tag of the project's own (`Enemy.Type.Heavy`) in the enemy Blueprint's `CombatComponent` `CharacterTags`, add one row per type with a higher `Priority`, that tag in `RequiredTargetTags` and the type's own paired action as `Action`, and keep one catch-all row last with no `RequiredTargetTags`, which every other enemy gets.
- **From the level:** a **UPC Paired Action Spot** or **UPC Paired Action Interactable** component on an actor.

**Check:** `upc.validate_asset(pa)` is empty. In Play, `UPC.Debug.Paired.Draw 1` draws each role's spot.

##### Mistakes

| Symptom | Cause and fix |
| --- | --- |
| The roles drift apart | Offsets not derived or set; run `DeriveOffsetsFromMontages` or set each `Offset`. |
| It plays on the wrong body | The option's `RequiredTargetTags` or `TargetTeams` are too loose. |
| The damage never lands | No Deal Damage event, or its `TimeSeconds` is past the action's end. |
| The victim survives the kill | A large `DamageOverride` meets a guard, a resistance or a health floor that holds it back. Tick `bKill` on the Deal Damage event: it takes whatever health is left, and its `HealthVitalTag` names the row to empty when the victim's health row is not `UPC.Vital.Health`. |
| The victim dies too early | An earlier Deal Damage event takes its last health, or has `bKill` ticked. Set `LeaveHealthAtLeast` (1 is enough) on every Deal Damage event before the killing blow, and tick `bKill` on the killing blow only. |
| A role is empty after a reload | `RoleId` was set with `set_editor_property`; build the participant with the `role_id` keyword. |
| A role skips its opening step | Its montage starts inside its Data Driven glide, which throws that root motion away, and `upc.validate_asset(pa)` names the loss and the value to set. Set `ApproachDurationSeconds` to `StartDelaySeconds` minus `RoleStartSeconds`, or `StartDelaySeconds` to the glide's end (and `PlayFromSeconds` to the frame shown at the join, for a montage already playing then). |

#### Editor Python: what can be set, and how (`AgentGuide/Python.md`)

Rules every other file relies on. Read this when a `set_editor_property` call is refused, when a value you wrote is gone after a reload, or before you add components, rows or instanced objects.

Tasks: set a property; fill a struct row a plain field write refuses; add an instanced object (a fragment, a payload, a cost); edit a component on a Blueprint; give a montage sections; add a gameplay tag; check an asset; read the log.

##### Setting properties

- `obj.set_editor_property("Name", value)` takes the C++ name (`bIsMainWeapon`) or the Python name (`is_main_weapon`). It refuses a property the editor shows read-only, and it refuses a **class-defaults-only** property on anything that is not a template (an asset, a class default object, or an object inside an asset).
- **Arrays come back as copies.** Edit the list, then write the whole list back:

  ```python
  rows = list(item.get_editor_property("Fragments"))
  rows.append(frag)
  item.set_editor_property("Fragments", rows)
  ```
- **A struct you make in Python is not a template**, so its class-defaults-only fields refuse `set_editor_property`. Several of this plugin's row structs are like that, among them `unreal.UPCHoldPlaceSpec` (Hold Places rows), `unreal.UPCStowPlaceSpec` (Stow Places rows), `unreal.UPCContainerSpec` (an inventory config's `Containers` rows), `unreal.UPCCapacityBonus` (a Capacity Bonus fragment's `Bonuses`), `unreal.UPCWeightThreshold` (a Weight Limit's `Thresholds`), `unreal.UPCCategoryLimit` (a Category Limit's `Limits`) and the ability set rows `unreal.UPCAbilitySet_GameplayAbility` and `unreal.UPCAbilitySet_GameplayEffect`; an interaction option's `PayloadSpec` refuses a field write too. A Combine Set's rows (`unreal.UPCCombineRecipe`, `unreal.UPCCombineResult`) take field writes. Write any row that refuses as text:

  ```python
  row = unreal.UPCAbilitySet_GameplayAbility()
  row.import_text('(Ability="/Game/Player/GA_Knight_Heal.GA_Knight_Heal_C",InputTag=(TagName="UPC.Input.Special"))')
  ```

  Read a shipped row with `row.export_text()` to see the text form of each field.
- **A struct read from an object is a copy:** edit it, then write it back to the object with `set_editor_property`. Inside that copy, nested fields edit in place: `s.state.applied_tags = tags("UPC.State.Blocking")`.
- **A call that answers yes or no and also fills a value** (`unreal.UPCInventoryStatics.can_combine`, the inventory's `get_item_placement` and `can_item_leave`) hands back the filled values and not the yes or no: one value comes back alone, several as a tuple, and `None` stands for no. `answer = unreal.UPCInventoryStatics.can_combine(inv, item, target)` is `None` when the two do not combine, else `(results, reason)`. A call that returns a number and fills a value gives both: `units, reason = inv.get_units_that_fit(item, 1, False)`.

##### Instanced objects

Item fragments, action graph payloads, costs, gates, hooks, transition conditions, hit effects, defense rules and camera layers are objects that live inside the asset holding them.

- Make one with `unreal.new_object(cls, outer=<the asset that holds it>)`, set its properties, put it in the owner's list, then save the asset. The lists in this plugin: an item's `Fragments`, an inventory container's `Rules`, an inventory config's `CarryRules` and a Combine Set's `Combines`. Write the whole list back with `set_editor_property`: a list read from the asset is a copy, so adding to it without writing it back changes nothing and raises nothing.
- An object made without `outer=` belongs to no asset: its properties refuse a write ("cannot be edited on instances").
- **Exception:** for `upc.set_node_settings` and `upc.set_transition_settings`, make it with `unreal.new_object(cls)` and no outer; the call copies it into the graph.
- Never give a class default object as the outer (`unreal.get_default_object(...)`), not even to look at a class. The next object of that class with the same name takes it as its template, and the asset holding it fails to save.

##### Components on a Blueprint

- **A component the C++ base creates** (every component `AUPCCharacterBase` makes: `EquipmentManagerComponent`, `InventoryComponent`, `ActionGraphComponent`, `MeleeTraceComponent`, `InputMappingComponent`, `AnimManagerComponent`, `VitalsComponent`, `DefenseComponent`, `TargetingComponent`, `TargetableComponent`, `TeamComponent`, `ApproachComponent`, `CombatComponent`, `InteractorComponent`, `AnimEffectComponent`, `AbilitySystemComponent`, and the character's own `CapsuleComponent`, `CharacterMovement` and `Mesh`) is read from the class defaults: `defaults(bp).get_editor_property("VitalsComponent")`. Edit it there and save the Blueprint.
- **A component the Blueprint adds** (sprint, camera rig, speed modifier, stow visuals on the demo player) lives in its construction script. Read its template:

  ```python
  sds = unreal.get_engine_subsystem(unreal.SubobjectDataSubsystem)
  handles = sds.k2_gather_subobject_data_for_blueprint(bp)
  for h in handles:
      data = unreal.SubobjectDataBlueprintFunctionLibrary.get_data(h)
      obj = unreal.SubobjectDataBlueprintFunctionLibrary.get_object_for_blueprint(data, bp)
      if isinstance(obj, unreal.UPCSprintComponent):
          sprint = obj
  ```

  Edit `sprint`, then `unreal.BlueprintEditorLibrary.compile_blueprint(bp)` and `save(bp)`.
- **Adding a component** to a Blueprint:

  ```python
  params = unreal.AddNewSubobjectParams(parent_handle=handles[0], new_class=unreal.UPCSpeedModifierComponent, blueprint_context=bp)
  new_handle, fail = sds.add_new_subobject(params)
  ```

  Then read it as above, set it, compile and save.

##### Montage sections

The engine's Python cannot give a montage sections. Use the plugin's tool:

```python
upc.set_montage_sections(montage, [("Light1", 0.0), ("Light2", 0.9), ("Light3", 1.8)])
```

It replaces every section and saves the montage. On Epic's MCP it takes three lists in place of the tuples: section names, start seconds, and next sections (empty, or one per section). Each section ends the montage, so a move whose payload names a section (`SectionName` on `unreal.UPCMontageActionPayload`) plays only that part. A third value names the section it plays on into instead, or its own name to loop it, which is how a held guard keeps its pose: `[("Raise", 0.0, "Hold"), ("Hold", 0.4, "Hold")]`.

##### Gameplay tags

A tag must exist before a property can hold it (`tag("UPC.Input.Kick")` gives an empty tag otherwise). The plugin's input, state, vital, place and cooldown tags exist already. To add one:

```python
upc.add_gameplay_tag("UPC.Input.Kick", "Kick attack")
```

It writes `Config/DefaultGameplayTags.ini` and the tag can be used at once. A tag that exists is left as it is.

##### Checks

- `upc.validate_asset(asset)` runs the checks the editor's Validate Assets runs (item definitions, damage configs, combat styles, targeting configs, defense rule sets, paired actions, camera shots) and returns one line per problem.
- For an action graph, `upc.compile_action_graph(graph)` is the check.
- After Play, `log_warnings()` returns UPC's warnings; each names the asset and the fix.

#### Targeting, combat styles and attack steering (`AgentGuide/TargetingStylesSteering.md`)

`AGENTS.md`, Recipe 5, sets up lock-on. This file covers how attacks turn to and close on the target, combat styles, targeting configs and what can be targeted.

Tasks: attacks that snap to the target and close the gap; an attack that steps forward with nothing targeted; a lunge that lands on the target; souls-like strafing when locked on; lock range and target switching; making an actor targetable; snappier movement in combat.

##### Recipe: attacks that close on the target

The demo's unarmed combo puts a **UPC Approach Or Move Window** (`UUPCApproachOrMoveWindowNotifyState`) over each attack's wind-up: with a target it turns the player to it and glides into reach, and with none it steps forward. Put it before the attack's trace window:

```python
for key in ("Light1", "Light2", "Light3", "Heavy"):
    m, L = am[key], length(am[key])
    a = upc.add_notify_state_to_montage(m, unreal.UPCApproachOrMoveWindowNotifyState, L * 0.05, L * 0.25)
    a.set_editor_property("StopDistanceOverride", 25.0)   # cm left between the bodies: about 25 for fists, 50 for a blade
    a.set_editor_property("MoveDistance", 60.0)           # cm stepped forward when nothing is targeted
    save(m)
```

- The target is the character's soft target or lock (the targeting config below); the duplicated player already picks one.
- `StopDistanceOverride` -1 uses the item's `AttackStopDistance`, then the style's stop distance. A move's own `StopDistance` on its Play Montage node takes over from it (below). The gap must be shorter than the attack reaches past the player's body, or the swing stops short.
- A plain **UPC Approach Window** (`UUPCApproachWindowNotifyState`) does the same without the step when nothing is targeted. Its `Mode` (`unreal.UPCApproachMode`: `MOVE_AND_FACE`, `FACE_ONLY`, `MOVE_ONLY`) picks turning, moving or both; `bMoveByOffset` with `OffsetDirection` and `OffsetDistance` moves a set distance instead (a step back: direction (-1, 0, 0)).

**Check:** `unreal.AnimationLibrary.get_animation_notify_events(am["Light1"])` lists the window before the trace window. In Play, `UPC.Debug.Steering.Draw 1` draws where each attack aims and stops; an attack started a few meters from an enemy glides to it and lands.

**Root-motion attacks** that travel by their own animation land on the target with the engine's Motion Warping window and the **UPC Skew Warp** modifier, as the demo sword does:

```python
w = upc.add_notify_state_to_montage(m, unreal.AnimNotifyState_MotionWarping, L * 0.1, L * 0.3)
mod = unreal.new_object(unreal.UPCRootMotionModifier_SkewWarp, outer=w)
mod.set_editor_property("WarpTargetName", "UPCTarget")
mod.set_editor_property("StopDistanceOverride", 50.0)
w.set_editor_property("RootMotionModifier", mod)
save(m)
```

A dash that carries the player into reach uses `UUPCRootMotionModifier_GapCloser` (UPC Gap Closer Warp) the same way. The duplicated player has the Motion Warping component.

##### Who steers, and which way attacks face

The character's **UPC Attack Approach** component (`UUPCAttackApproachComponent`, `ApproachComponent` on the duplicated player) aims and steers attacks. Another character takes this one beside a **UPC Targeting** component (`AgentGuide/Python.md`, Components on a Blueprint), never its base `UUPCApproachComponent`, whose attacks neither aim nor keep their distance. The attack approach's settings: `SteeringMode` (`unreal.UPCAttackSteering`: `OFF`, `TOWARD_TARGET`, `DISTANCE_ONLY`), `AttackFacing` (`unreal.UPCAttackFacing`: `NONE`, `AIM`, `MOVEMENT`, `LOCK_TARGET`), `KeepDistance` (`ON` stops attacks the stop distance short of the target and of enemies in their path), `MakeRoomSeconds` (how long an attack that starts closer than the stop distance takes to step back to it; 0 turns it off; a move's own `MakeRoomSeconds` takes over), `DefaultStopDistance`, `DefaultMaxApproachDistance`, `WarpTargetName` (`UPCTarget`). The combat style sets most of these as it applies, so change them on the style.

**Which stop distance an attack keeps**, top wins, -1 passing to the next:

| Level | Set on |
| --- | --- |
| The move | `StopDistance` on its Play Montage node (`UUPCMontageActionPayload`) |
| A window, advanced | `StopDistanceOverride` on the UPC Approach Window or UPC Skew Warp playing at that moment |
| The weapon | `AttackStopDistance` on the drawn weapon's Equippable fragment |
| The style | `StopDistance` on its Approach fragment (`DefaultStopDistance` on the component) |

The move's `MakeRoomSeconds` works the same way against the style's: -1 follows the style, 0 turns the step back off for that move, above 0 turns it on. Set the move's own values for a short move beside a long weapon, such as a shield bash with a sword drawn. With `UPC.Debug.Approach 1` each attack logs its stop distance and where it came from.

##### Combat styles

A **Combat Style** (`UUPCCombatStyleAsset`, `Fragments`) on the character's `CombatComponent` (`Style`) sets rotation, targeting, steering, approach and camera together. The demo ships `CombatStyle_UPC_OverShoulder` (the duplicate's), `CombatStyle_UPC_Soulslike` and `CombatStyle_UPC_Freeflow` under `/UniversalPlayerCombat/Demo/Combat/Styles/`; duplicate one to change it.

| Fragment (picker label) | Fields |
| --- | --- |
| `UUPCStyleFragment_Rotation` (Rotation) | `RotationMode`, `LockedRotationMode` (`unreal.UPCRotationMode`: `CAMERA_FACING`, `MOVEMENT_FACING`, `CONTROLLER_DESIRED`), `RotationRateYaw`, `LockedRotationRateYaw`, `AttackFacing` |
| `UUPCStyleFragment_Targeting` (Targeting) | `Targeting` (a targeting config), `Steering` |
| `UUPCStyleFragment_Approach` (Approach) | `StopDistance`, `MaxApproachDistance`, `FacingInterpSpeed`, `SpeedLimit`, `MovingTarget`, `bBlockMovementInput`, `MakeRoomSeconds` |
| `UUPCStyleFragment_Movement` (Movement) | Acceleration, braking and friction overrides |
| `UUPCStyleFragment_Camera`, `UUPCStyleFragment_CameraRig` | The lock-on camera and the rig (`AgentGuide/CameraTelegraphs.md`) |
| `UUPCStyleFragment_Tags`, `UUPCStyleFragment_InputMapping` | Tags held while the style is active; a mapping context added while it is |

##### Targeting configs

`UUPCTargetingConfig`: `ScanRadius` (cm), `TargetTeams` (Enemies Only by default), `bAllowLock`, `LockBreakDistance` (0 never breaks), `LockBreakGraceSeconds`, `ScoreSwitchMargin`, `ScorePolicies` and `LockScorePolicies` (`UUPCTargetScorePolicy_Distance`, `_ViewAngle`, `_InputDirection`, `_AimDirection`, `_LineOfSight`), `SwitchInputAction` (the stick that moves a lock), `EngagedRadius`, `EngagedTag`. Duplicate `TargetingConfig_UPC_Soulslike` (beside the styles) and point a duplicated style's Targeting fragment at it.

**Targetable actors:** a **UPC Targetable** component (`UUPCTargetableComponent`: `AimAtSocket`, `AimAtComponentTag`, `AimOffset`). Every character built on the UPC character base has one; add it to any other actor (`AgentGuide/Python.md`, Components on a Blueprint).

##### Mistakes

| Symptom | Cause and fix |
| --- | --- |
| Attacks stop short of the target | The stop distance is longer than the attack's reach: lower the move's `StopDistance`, `StopDistanceOverride` or the item's `AttackStopDistance`. |
| Attacks play in place | No approach window on the montage, `SteeringMode` `OFF`, or no target (an Approach Or Move Window still steps). |
| Attacks slide through enemies | `KeepDistance` off on the UPC Attack Approach component. |
| A swing from contact reaches into the enemy | `MakeRoomSeconds` is 0 on the style's Approach fragment; around 0.15 steps the attack back to its stop distance first. |
| A short move beside a long weapon backs off and misses (a shield bash beside a sword) | The move takes the weapon's `AttackStopDistance`: set the move's own `StopDistance` on its Play Montage node (the demo bash keeps 25). |
| Lock-on picks teammates | `TargetTeams` changed from Enemies Only, or both sides share a team. |
| The lunge overshoots | The Motion Warping window has no modifier, or its `WarpTargetName` is not `UPCTarget`. |

---

Previous page: [Character Setup](https://rigbak.com/docs/universal-player-combat/character-setup). Next page: [Action Graph](https://rigbak.com/docs/universal-player-combat/action-graph)
