# v0.8.0 Sequencer Scaffolding Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Ship 3 new MCP handlers (`inspect_sequence`, `create_sequence`, `bind_actor_to_sequence`) so the LLM can read existing Level Sequences and scaffold new ones with possessable bindings, while keyframe authoring remains accessible via the Python escape hatch.

**Architecture:** Three single-file handlers under `Source/UnrealClaudeMCP/Private/MCP/Handlers/`, each registered via `extern Make_Handler_*()` + `Reg.Register(...)` in `UnrealClaudeMCPModule.cpp`. Adds four UE module dependencies to `UnrealClaudeMCP.Build.cs` (`LevelSequence`, `MovieScene`, `MovieSceneTracks` public + `LevelSequenceEditor` private — first Build.cs change since v0.4.0). Reuses path normalization helpers from v0.7.0's `AssetPathUtil.h`.

**Tech Stack:** UE 5.7 C++, `LevelSequence`/`MovieScene`/`MovieSceneTracks`/`LevelSequenceEditor` modules, `EditorAssetLibrary` (already in deps via v0.7.0), Python 3.11+ for bridge, pytest for unit tests.

**Reference implementations:** `Handler_InspectAsset.cpp` (v0.7.0 — the closest analog: reads structured data from an asset and builds JSON), `Handler_FindAssets.cpp` (asset registry pattern), `Handler_DeleteActor.cpp` (hybrid actor identification used by `bind_actor_to_sequence`).

**Spec:** `docs/superpowers/specs/2026-05-08-v0.8.0-sequencer-design.md` — read this first.

**Stacking:** This branch (`feat/v0.8.0-sequencer`) is stacked on `feat/v0.7.0-asset-registry` (PR #12). The PR base will be set to `feat/v0.7.0-asset-registry`; GitHub will auto-retarget to main when PR #12 merges.

---

## File structure

```
Source/UnrealClaudeMCP/UnrealClaudeMCP.Build.cs                          (MODIFY — +3 public deps, +1 private dep)
Source/UnrealClaudeMCP/Private/MCP/Handlers/
  Handler_InspectSequence.cpp        (NEW — read tracks/sections/bindings/time range)
  Handler_CreateSequence.cpp         (NEW — UAssetTools::CreateAsset + Initialize + frame rate)
  Handler_BindActorToSequence.cpp    (NEW — AddPossessable + BindPossessableObject)

Source/UnrealClaudeMCP/Private/UnrealClaudeMCPModule.cpp                 (MODIFY — +3 externs, +3 Reg.Register)

UnrealClaudeMCP/UnrealClaudeMCP.uplugin                                  (MODIFY — VersionName 0.7.0 → 0.8.0)
UnrealClaudeMCP/Resources/mcp_manifest.json                              (MODIFY — version, +3 tools)

bridge/unreal_claude_mcp_bridge.py                                       (MODIFY — version, docstring count, +3 TOOLS)

tests/test_bridge.py                                                     (MODIFY — count assertions + 3 schema tests)
tests/test_manifest_sync.py                                              (MODIFY — count assertion)
examples/smoke_test.py                                                   (MODIFY — count assertion + 1 new step)

docs/TOOLS.md                                                            (MODIFY — +3 sections)
docs/superpowers/specs/2026-05-08-v0.8.0-sequencer-design.md             (already written, untracked on this branch)
docs/superpowers/plans/2026-05-08-v0.8.0-sequencer.md                    (this file)

README.md                                                                (MODIFY — count 25→28, version row, table rows)
```

Vertical decomposition again — one task per handler, plus framing tasks. Each commit ends with green pytest.

---

## Task 1: Build.cs additions + spec/plan commit

**Files:**
- Modify: `UnrealClaudeMCP/Source/UnrealClaudeMCP/UnrealClaudeMCP.Build.cs`
- Stage: `docs/superpowers/specs/2026-05-08-v0.8.0-sequencer-design.md` (already written, untracked on this branch)
- Stage: `docs/superpowers/plans/2026-05-08-v0.8.0-sequencer.md` (this file, after writing)

**Why first:** The handlers in Tasks 2-4 use APIs from `LevelSequence`, `MovieScene`, and `LevelSequenceEditor` modules. Without the Build.cs additions, those handlers fail to link.

- [x] **Step 1.1: Branch already created**

```
git checkout -b feat/v0.8.0-sequencer feat/v0.7.0-asset-registry
```

Already done. HEAD is at `0bc62c5` (the v0.7.0 codex P2 fix tip).

- [ ] **Step 1.2: Add four UE module dependencies to Build.cs**

Open `UnrealClaudeMCP/Source/UnrealClaudeMCP/UnrealClaudeMCP.Build.cs`. The current `PublicDependencyModuleNames` list ends with `"UMGEditor"`. Append three new entries before the closing `});`. The current `PrivateDependencyModuleNames` list ends with `"LevelEditor"`. Append one new entry before its closing `});`.

The diff (insert these lines exactly):

```csharp
// In PublicDependencyModuleNames, after "UMGEditor":
            // Sequencer (v0.8.0)
            "LevelSequence",
            "MovieScene",
            "MovieSceneTracks"
```

```csharp
// In PrivateDependencyModuleNames, after "LevelEditor":
            ,
            // Sequencer editor library (v0.8.0)
            "LevelSequenceEditor"
```

Each module name has been verified against `F:/UE_5.7/Engine/{Source,Plugins}/...` Build.cs files: `LevelSequence` (Runtime), `MovieScene` (Runtime), `MovieSceneTracks` (Runtime), `LevelSequenceEditor` (Plugin).

- [ ] **Step 1.3: Stage and commit Task 1 (spec + plan + Build.cs)**

```bash
git add UnrealClaudeMCP/Source/UnrealClaudeMCP/UnrealClaudeMCP.Build.cs \
        docs/superpowers/specs/2026-05-08-v0.8.0-sequencer-design.md \
        docs/superpowers/plans/2026-05-08-v0.8.0-sequencer.md
git commit -m "docs(v0.8.0): sequencer scaffolding — spec + plan + Build.cs deps"
```

Expected: clean commit. No build is required for spec/plan changes; Build.cs only takes effect on next compile.

---

## Task 2: `inspect_sequence` handler

**Files:**
- Create: `UnrealClaudeMCP/Source/UnrealClaudeMCP/Private/MCP/Handlers/Handler_InspectSequence.cpp`
- Modify: `UnrealClaudeMCP/Source/UnrealClaudeMCP/Private/UnrealClaudeMCPModule.cpp`
- Modify: `bridge/unreal_claude_mcp_bridge.py`
- Modify: `UnrealClaudeMCP/Resources/mcp_manifest.json`
- Modify: `tests/test_bridge.py`
- Modify: `tests/test_manifest_sync.py`
- Modify: `docs/TOOLS.md`

- [ ] **Step 2.1: Create Handler_InspectSequence.cpp**

Mirror `Handler_InspectAsset.cpp` from v0.7.0 — same shape (load asset → cast → walk fields → build JSON). Algorithmic flow:

1. Validate `path` (required, non-empty). Error: `inspect_sequence: missing_required_field: 'path' is required`.
2. Normalize via `UCMCPAssetPath::ToObjectPath(path)`.
3. Load: `ULevelSequence* Sequence = Cast<ULevelSequence>(UEditorAssetLibrary::LoadAsset(ObjectPath))`. If null AND `UEditorAssetLibrary::DoesAssetExist(ObjectPath)` is false: `asset_not_found`. If null but the asset exists (i.e. it's some other type): `not_a_sequence: '<path>' is not a ULevelSequence`.
4. Get `UMovieScene* Scene = Sequence->GetMovieScene()`. If null: `not_a_sequence: sequence has no MovieScene`. (Defensive — shouldn't happen for a valid sequence.)
5. Build the result JSON:
   - `name`, `package_path` from the asset
   - `tick_resolution` = `Scene->GetTickResolution().Numerator`
   - `display_rate_fps` = `Scene->GetDisplayRate().AsDecimal()`
   - `playback_range`: `Scene->GetPlaybackRange()` is a `TRange<FFrameNumber>`. Use `Scene->GetPlaybackRange().GetLowerBoundValue().Value` for start (already in tick units). Convert to display-rate frames by multiplying by `Scene->GetDisplayRate() / Scene->GetTickResolution()`. Or simpler: just report tick-units start/end in `start_frames`/`end_frames` — document.
6. `bindings` array: loop `Scene->GetSpawnableCount()` and `Scene->GetPossessableCount()`. For each:
   - Possessable: `Scene->GetPossessable(i)` returns `FMovieScenePossessable&`. Get `GetGuid()`, `GetName()`. Try `Sequence->LocateBoundObjects(Guid, ResolutionContext)` to get bound actor; if non-empty, take the first cast to AActor and read its label.
   - Spawnable: `Scene->GetSpawnable(i)` — same shape, type field = "spawnable". (We don't bind spawnables in v0.8.0 but we read existing ones.)
7. `tracks` array: loop `Scene->GetMasterTracks()` (the array property `MasterTracks` or `GetMasterTracks()` accessor — check header). For each `UMovieSceneTrack*`: read class name, section count via `Track->GetAllSections().Num()`, `binding_guid` is empty for master tracks. Then for each binding, loop `Scene->FindTracks(Class, Guid)` or `Scene->FindBinding(Guid).GetTracks()` and add those with the binding GUID populated.

Required header includes:

```cpp
#include "MCP/MCPHandler.h"
#include "Dom/JsonObject.h"
#include "Dom/JsonValue.h"
#include "EditorAssetLibrary.h"
#include "LevelSequence.h"
#include "MovieScene.h"
#include "MovieSceneTrack.h"
#include "MovieSceneSection.h"
#include "MovieSceneSpawnable.h"
#include "MovieScenePossessable.h"
#include "MovieSceneBinding.h"
#include "Misc/FrameRate.h"
#include "Misc/FrameNumber.h"
#include "GameFramework/Actor.h"
#include "Engine/World.h"
#include "Editor.h"
#include "MCP/Handlers/AssetPathUtil.h"
```

Stable error codes: `missing_required_field`, `asset_not_found`, `not_a_sequence`.

End with the factory:

```cpp
TSharedRef<IUCMCPHandler> Make_Handler_InspectSequence()
{
    return MakeShared<FHandler_InspectSequence>();
}
```

- [ ] **Step 2.2: Register handler in UnrealClaudeMCPModule.cpp**

Add the extern declaration after the existing `Make_Handler_DeleteAsset()` extern:

```cpp
extern TSharedRef<IUCMCPHandler> Make_Handler_InspectSequence();
```

Add the registration call after the existing `Reg.Register(Make_Handler_DeleteAsset());`:

```cpp
    Reg.Register(Make_Handler_InspectSequence());
```

- [ ] **Step 2.3: Add bridge TOOLS entry**

Append to the `TOOLS` array (after the v0.7.0 `delete_asset` entry):

```python
    {
        "name": "inspect_sequence",
        "description": "Read structure of a Level Sequence asset: tracks, sections, bindings, frame rate, playback range.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "Level Sequence asset path (object path or package path)."},
            },
            "required": ["path"],
        },
    },
```

- [ ] **Step 2.4: Add manifest entry**

Append to the `tools` array (after the v0.7.0 `delete_asset` entry):

```json
    {
      "name": "inspect_sequence",
      "description": "Read structure of a Level Sequence asset: tracks, sections, bindings, frame rate, playback range.",
      "params": {
        "path": "string (required) - Level Sequence asset path"
      },
      "returns": { "ok": "bool", "name": "string", "package_path": "string", "tick_resolution": "int", "display_rate_fps": "number", "playback_range": "object", "bindings": "array of object", "tracks": "array of object" }
    },
```

- [ ] **Step 2.5: Add bridge schema test**

Append to `tests/test_bridge.py`:

```python
def test_inspect_sequence_in_tools_catalog():
    """v0.8.0: inspect_sequence requires 'path'."""
    t = next((t for t in bridge.TOOLS if t["name"] == "inspect_sequence"), None)
    assert t is not None, "inspect_sequence must be in TOOLS catalog"
    assert "path" in t["inputSchema"]["properties"]
    assert t["inputSchema"]["required"] == ["path"]
```

- [ ] **Step 2.6: Add docs/TOOLS.md section**

Insert before the existing `## Adding more tools` section:

````markdown
## inspect_sequence

Read the structure of a Level Sequence asset.

**Params**
- `path` (string, required) — Level Sequence asset path. Both forms accepted.

**Result**
- `name`, `package_path` — asset identity
- `tick_resolution` — internal tick rate (typically 24000 for 24fps display rate × 1000 sub-frame divisor)
- `display_rate_fps` — sequence's display frame rate (e.g. 24.0, 30.0)
- `playback_range` — `{start_frames, end_frames}` in tick units (divide by `tick_resolution / display_rate_fps` for display frames)
- `bindings` — array of `{guid, name, type, bound_actor_label?}` for each possessable/spawnable binding
- `tracks` — array of `{name, class, section_count, binding_guid?}` for every master track and binding-attached track

**Errors:** `missing_required_field`, `asset_not_found`, `not_a_sequence`.

**Example**
```json
{"jsonrpc":"2.0","id":1,"method":"inspect_sequence","params":{
  "path": "/Game/Cinematics/MainCinematic"
}}
```
````

- [ ] **Step 2.7: Bump count anchors (25 → 26)**

Three places hardcode the count and must be bumped together to keep pytest green:

In `tests/test_bridge.py`:
- `def test_tools_list_has_twentyfive_entries():` → rename to `_twentysix_entries`, change `== 25` to `== 26`.
- In `test_tool_names_are_unique_and_match_handlers`, append `"inspect_sequence",` to the expected name set.
- The `test_handle_tools_list_returns_all_tools` test asserts `len(resp["result"]["tools"]) == 25` — change to `26`.

In `tests/test_manifest_sync.py`:
- `assert len(manifest["tools"]) == len(bridge.TOOLS) == 25` → `== 26`.

- [ ] **Step 2.8: Run pytest**

```bash
py -3 -m pytest tests/ -q
```

Expected: all prior tests + new `test_inspect_sequence_in_tools_catalog` pass. Total: 85 (was 84).

- [ ] **Step 2.9: Commit**

```bash
git add UnrealClaudeMCP/Source/UnrealClaudeMCP/Private/MCP/Handlers/Handler_InspectSequence.cpp \
        UnrealClaudeMCP/Source/UnrealClaudeMCP/Private/UnrealClaudeMCPModule.cpp \
        bridge/unreal_claude_mcp_bridge.py \
        UnrealClaudeMCP/Resources/mcp_manifest.json \
        tests/test_bridge.py \
        tests/test_manifest_sync.py \
        docs/TOOLS.md
git commit -m "feat(inspect_sequence): read tracks+sections+bindings+time-range (v0.8.0 part 1/3)"
```

---

## Task 3: `create_sequence` handler

**Files:** Mirror Task 2 layout (handler + module + bridge + manifest + tests + docs).

- [ ] **Step 3.1: Create Handler_CreateSequence.cpp**

Algorithmic flow:

1. Validate `path` (required, starts with `/Game/`, no trailing slash) and `name` (required, passes `UCMCPAssetPath::IsValidLeafName`).
2. Read optional `display_rate_fps` (default 30.0) and `playback_end_frames` (default 240).
3. Build target ObjectPath = `path + "/" + name + "." + name`.
4. If `UEditorAssetLibrary::DoesAssetExist(ObjectPath)`: `dest_exists`.
5. Get IAssetTools: `IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();`
6. Call `UObject* NewAsset = AssetTools.CreateAsset(name, path, ULevelSequence::StaticClass(), nullptr)`. UE auto-resolves the factory by class. If null: `create_failed`.
7. Cast: `ULevelSequence* Sequence = Cast<ULevelSequence>(NewAsset)`. If null: `create_failed`.
8. Initialize: `Sequence->Initialize();` (creates the underlying UMovieScene; verified at `LevelSequence.h:38`).
9. Get scene: `UMovieScene* Scene = Sequence->GetMovieScene();` Configure:
   - `Scene->SetDisplayRate(FFrameRate(static_cast<uint32>(display_rate_fps * 1000.0), 1000));` (use 1000 denominator for non-integer rates like 23.976)
   - Convert end frames to ticks: `FFrameTime EndTime = FFrameRate::TransformTime(FFrameTime(FFrameNumber(playback_end_frames)), Scene->GetDisplayRate(), Scene->GetTickResolution());`
   - `Scene->SetPlaybackRange(0, EndTime.FrameNumber.Value);`
10. `UEditorAssetLibrary::SaveAsset(ObjectPath, false);`
11. Return `{ok, asset_path, package_path, display_rate_fps, playback_range}`.

Required headers (additions over Task 2):

```cpp
#include "AssetToolsModule.h"
#include "IAssetTools.h"
```

Stable error codes: `missing_required_field`, `invalid_path`, `invalid_asset_name`, `dest_exists`, `create_failed`.

- [ ] **Step 3.2: Register in module**

Add `extern Make_Handler_CreateSequence();` after the inspect_sequence extern, and `Reg.Register(Make_Handler_CreateSequence());` after its registration call.

- [ ] **Step 3.3: Add bridge TOOLS entry**

```python
    {
        "name": "create_sequence",
        "description": "Create a new Level Sequence asset. Initializes an empty MovieScene with the given display frame rate and playback end-frame.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "Destination folder under /Game/."},
                "name": {"type": "string", "description": "Leaf asset name (no '/' or '.')."},
                "display_rate_fps": {"type": "number", "description": "Display frame rate (default 30.0)."},
                "playback_end_frames": {"type": "integer", "description": "End of playback range in display frames (default 240)."},
            },
            "required": ["path", "name"],
        },
    },
```

- [ ] **Step 3.4: Add manifest entry**

```json
    {
      "name": "create_sequence",
      "description": "Create a new Level Sequence asset with display frame rate and playback range.",
      "params": {
        "path": "string (required) - destination folder under /Game/",
        "name": "string (required) - leaf asset name (no '/' or '.')",
        "display_rate_fps": "number (optional, default 30.0)",
        "playback_end_frames": "int (optional, default 240)"
      },
      "returns": { "ok": "bool", "asset_path": "string", "package_path": "string", "display_rate_fps": "number", "playback_range": "object" }
    },
```

- [ ] **Step 3.5: Add bridge schema test**

```python
def test_create_sequence_in_tools_catalog():
    """v0.8.0: create_sequence requires path + name."""
    t = next((t for t in bridge.TOOLS if t["name"] == "create_sequence"), None)
    assert t is not None
    assert set(t["inputSchema"]["required"]) == {"path", "name"}
```

- [ ] **Step 3.6: Add docs/TOOLS.md section**

Insert before `## Adding more tools`:

```markdown
## create_sequence

Create a new empty Level Sequence asset.

**Params**
- `path` (string, required) — destination folder under `/Game/`.
- `name` (string, required) — leaf asset name. No `/` or `.` allowed.
- `display_rate_fps` (number, optional, default `30.0`) — sequence display frame rate.
- `playback_end_frames` (int, optional, default `240`) — end of playback range in display frames.

**Result**
- `ok`, `asset_path`, `package_path`
- `display_rate_fps` — final value applied to the MovieScene
- `playback_range` — `{start_frames, end_frames}` in tick units

**Errors:** `missing_required_field`, `invalid_path`, `invalid_asset_name`, `dest_exists`, `create_failed`.

**Example**
```json
{"jsonrpc":"2.0","id":1,"method":"create_sequence","params":{
  "path": "/Game/Cinematics",
  "name": "MyNewSequence",
  "display_rate_fps": 24.0,
  "playback_end_frames": 144
}}
```
```

- [ ] **Step 3.7: Bump count anchors (26 → 27)**

Same three places as Step 2.7. Rename test, change `== 26` to `== 27` in three asserts, append `"create_sequence",` to expected name set.

- [ ] **Step 3.8: Run pytest + commit**

```bash
py -3 -m pytest tests/ -q
```

Expected: 86 passed.

```bash
git add <same files as Task 2>
git commit -m "feat(create_sequence): create + initialize Level Sequence asset (v0.8.0 part 2/3)"
```

---

## Task 4: `bind_actor_to_sequence` handler

**Files:** Mirror Task 2 layout.

- [ ] **Step 4.1: Create Handler_BindActorToSequence.cpp**

Algorithmic flow:

1. Validate `sequence_path` and `actor_name` (both required, non-empty).
2. Load sequence (same as Task 2 step 2-4): `ULevelSequence* Sequence = Cast<ULevelSequence>(UEditorAssetLibrary::LoadAsset(ToObjectPath(sequence_path)))`. Check existence + cast → `asset_not_found` / `not_a_sequence`.
3. Resolve actor by label or FName in the editor world. The hybrid identification helper used by `set_actor_property`, `delete_actor`, `set_actor_transform`, etc. lives somewhere in the v0.3.0/v0.4.0 build-a-level handlers — find the helper's name (likely `FindActorByLabelOrFName(World, Name, OutAmbiguous)` or similar) and call it. World = `GEditor->GetEditorWorldContext().World()`.
   - If actor null and `bAmbiguous`: `ambiguous_actor: <details>`.
   - If actor null otherwise: `actor_not_found: '<name>'`.
4. Get scene: `UMovieScene* Scene = Sequence->GetMovieScene();`
5. Add the binding: `FGuid Guid = Scene->AddPossessable(Actor->GetActorLabel(), Actor->GetClass());`
6. Validate: `if (!Guid.IsValid()) { return bind_failed; }`
7. Wire the binding to the live actor: `Sequence->BindPossessableObject(Guid, *Actor, World);`
8. Save the sequence: `UEditorAssetLibrary::SaveAsset(ObjectPath, false);`
9. Return `{ok, sequence_path, binding_guid: Guid.ToString(EGuidFormats::DigitsWithHyphens), actor_label, binding_type: "possessable"}`.

Required headers (additions over Task 2):

```cpp
// Already covered by Task 2 includes. Plus:
#include "Engine/World.h"
#include "Editor.h"
```

Stable error codes: `missing_required_field`, `asset_not_found`, `not_a_sequence`, `actor_not_found`, `ambiguous_actor`, `bind_failed`.

**Important pre-implementation step:** Before writing this handler, grep the v0.3.0+ codebase for the actor-resolution helper:

```bash
grep -rn 'ambiguous_actor' UnrealClaudeMCP/Source/ | head -5
```

This will identify where the helper is implemented (probably in `Handler_DeleteActor.cpp` or a shared util). If it's currently inlined, factor it into a small `ActorResolver.h` shared header so this handler can reuse it without copy-paste. This refactor is a Task 4 sub-step, not a Task 7 deferral.

- [ ] **Step 4.2: Register in module**

Add `extern Make_Handler_BindActorToSequence();` and `Reg.Register(...)` lines.

- [ ] **Step 4.3: Add bridge TOOLS entry**

```python
    {
        "name": "bind_actor_to_sequence",
        "description": "Add a level actor as a possessable binding to a Level Sequence. Creates the binding GUID and wires it to the live actor.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "sequence_path": {"type": "string", "description": "Level Sequence asset path."},
                "actor_name": {"type": "string", "description": "Actor label or FName in the current editor world."},
            },
            "required": ["sequence_path", "actor_name"],
        },
    },
```

- [ ] **Step 4.4: Add manifest entry**

```json
    {
      "name": "bind_actor_to_sequence",
      "description": "Add a level actor as a possessable binding to a Level Sequence.",
      "params": {
        "sequence_path": "string (required) - Level Sequence asset path",
        "actor_name": "string (required) - actor label or FName"
      },
      "returns": { "ok": "bool", "sequence_path": "string", "binding_guid": "string", "actor_label": "string", "binding_type": "string" }
    }
```

(Note: this is the **last** entry — no trailing comma after the closing `}`.)

- [ ] **Step 4.5: Add bridge schema test**

```python
def test_bind_actor_to_sequence_in_tools_catalog():
    """v0.8.0: bind_actor_to_sequence requires sequence_path + actor_name."""
    t = next((t for t in bridge.TOOLS if t["name"] == "bind_actor_to_sequence"), None)
    assert t is not None
    assert set(t["inputSchema"]["required"]) == {"sequence_path", "actor_name"}
```

- [ ] **Step 4.6: Add docs/TOOLS.md section**

Insert before `## Adding more tools`:

```markdown
## bind_actor_to_sequence

Add a level actor as a possessable binding to a Level Sequence.

**Params**
- `sequence_path` (string, required) — Level Sequence asset path.
- `actor_name` (string, required) — actor label or FName in the current editor world. Hybrid identification: ambiguous labels return `ambiguous_actor`.

**Result**
- `ok` (bool)
- `sequence_path` (string)
- `binding_guid` (string) — GUID of the created possessable binding (canonical hyphenated form)
- `actor_label` (string)
- `binding_type` (string) — always `"possessable"` in v0.8.0

**Errors:** `missing_required_field`, `asset_not_found`, `not_a_sequence`, `actor_not_found`, `ambiguous_actor`, `bind_failed`.

**Behavior note:** v0.8.0 only supports possessables (existing actors). Spawnables (per-sequence-instance actors created from a template) are deferred to v0.8.x. Use `execute_unreal_python` for spawnable workflows in the meantime.

**Example**
```json
{"jsonrpc":"2.0","id":1,"method":"bind_actor_to_sequence","params":{
  "sequence_path": "/Game/Cinematics/MainCinematic",
  "actor_name": "MyHero_Actor"
}}
```
```

- [ ] **Step 4.7: Bump count anchors (27 → 28)**

Same pattern. Rename count test, bump three asserts to 28, append `"bind_actor_to_sequence",` to expected name set.

- [ ] **Step 4.8: Run pytest + commit**

```bash
py -3 -m pytest tests/ -q
```

Expected: 87 passed.

```bash
git add <same files as Task 2>
git commit -m "feat(bind_actor_to_sequence): possessable binding wiring (v0.8.0 part 3/3)"
```

---

## Task 5: Version bumps + smoke + PR

**Files:**
- Modify: `UnrealClaudeMCP/UnrealClaudeMCP.uplugin`
- Modify: `UnrealClaudeMCP/Resources/mcp_manifest.json` (version + description)
- Modify: `bridge/unreal_claude_mcp_bridge.py` (version + docstring)
- Modify: `README.md`
- Modify: `examples/smoke_test.py`

- [ ] **Step 5.1: Bump .uplugin VersionName + description**

Change `"VersionName": "0.7.0"` to `"VersionName": "0.8.0"`. Update `Description` count from "Twenty-five tools" to "Twenty-eight tools" and append "Level Sequence introspection and scaffolding" to the capability list.

- [ ] **Step 5.2: Bump mcp_manifest.json**

Change `"version": "0.7.0"` to `"version": "0.8.0"`. Update description count from "Twenty-five generic editor-automation tools" to "Twenty-eight generic editor-automation tools".

- [ ] **Step 5.3: Bump bridge SERVER_VERSION + docstring**

In `bridge/unreal_claude_mcp_bridge.py`:
- `SERVER_VERSION = "0.7.0"` → `SERVER_VERSION = "0.8.0"`
- Docstring: `tools/list returns a static list mirroring the 25 handlers` → `28 handlers`
- Inline comment: `# v0.7.0: 25 tools (added inspect_asset, move_asset, rename_asset, delete_asset; find_assets gained tags+include_tags).` → `# v0.8.0: 28 tools (added inspect_sequence, create_sequence, bind_actor_to_sequence).`

- [ ] **Step 5.4: Update README.md**

Five edits in this file:

1. Hero text: `Twenty-five editor-automation tools` → `Twenty-eight editor-automation tools`
2. Tools table: append three rows after `delete_asset`:
   ```
   | `inspect_sequence` | Read structure of a Level Sequence: tracks, sections, bindings, frame rate. |
   | `create_sequence` | Create a new empty Level Sequence asset. |
   | `bind_actor_to_sequence` | Add a level actor as a possessable binding. |
   ```
3. Adding-a-Nth: `Adding a 26th tool` → `Adding a 29th tool`
4. Quick start log preview: `... (25 lines)` → `... (28 lines)`
5. Smoke test description: `eleven sections of JSON output (the eleventh round-trips the asset-registry handlers...)` → `twelve sections (the twelfth inspects a Level Sequence; round-trip read-only)`
6. Status table: `v0.7.0` → `v0.8.0`, `25 live` → `28 live`, `84 pytest cases` → `87 pytest cases`

- [ ] **Step 5.5: Add a smoke test step for sequencer inspection**

The smoke test is read-only / opportunistic. Append after the v0.7.0 `t_asset_registry` step:

```python
    # -------- v0.8.0: sequencer scaffolding --------------------------------
    # One read-only check: find any LevelSequence in the project and inspect
    # its structure. If the project has no Level Sequences, log and skip
    # (no warning) — sequencer is optional content, not all projects use it.
    def t_sequencer():
        find_resp = call("find_assets", {
            "class_path": "/Script/LevelSequence.LevelSequence",
            "path_under": "/Game/",
            "limit": 1,
        })
        find_res = assert_ok(find_resp, "find_assets.level_sequences")
        if find_res.get("returned", 0) < 1:
            print("  [t_sequencer] no Level Sequences in /Game/ — skipping inspect_sequence")
            return

        first_seq = find_res["assets"][0]
        inspect_resp = call("inspect_sequence", {"path": first_seq["package_path"]})
        inspect_res = assert_ok(inspect_resp, "inspect_sequence.first_found")
        for required_key in ("name", "package_path", "tick_resolution",
                             "display_rate_fps", "playback_range",
                             "bindings", "tracks"):
            if required_key not in inspect_res:
                raise SmokeFailure(
                    f"[inspect_sequence] missing key '{required_key}' in result: {inspect_res}"
                )
        if not isinstance(inspect_res["bindings"], list):
            raise SmokeFailure(f"[inspect_sequence] bindings not a list: {inspect_res['bindings']}")
        if not isinstance(inspect_res["tracks"], list):
            raise SmokeFailure(f"[inspect_sequence] tracks not a list: {inspect_res['tracks']}")
    step("sequencer", t_sequencer)
```

Also bump the `list_tools` count assertions in `examples/smoke_test.py`:
- `header("1. list_tools (should list 25 tool names)")` → `28 tool names`
- `if len(tools) != 25:` → `!= 28:`
- `f"[list_tools] expected 25 tools, got ..."` → `expected 28 tools`

- [ ] **Step 5.6: Run pytest**

```bash
py -3 -m pytest tests/ -q
```

Expected: 87 passed (no new tests in this task; just version bumps).

- [ ] **Step 5.7: Build the editor (deferred to user)**

The plugin lives at `C:\Users\<USERNAME>\Desktop\UnrealClaudeMCP\UnrealClaudeMCP\` but the host UE project (`.uproject` containing this plugin) lives elsewhere on the user's machine. The user runs the build from their UE project — typically by killing UE, then opening the `.uproject`, which triggers UE to detect the plugin source change and offer to compile. Or via:

```powershell
taskkill /IM UnrealEditor.exe /F
& "F:\UE_5.7\Engine\Build\BatchFiles\Build.bat" UnrealClaudeMCPEditor Win64 Development -project="<full path to host .uproject>"
```

Common Build.cs failures and fixes:
- `error LNK2001: unresolved external symbol "<class>::<method>"` — likely a Build.cs module name typo. Re-verify each module exists at `F:/UE_5.7/Engine/{Source,Plugins}/.../<Name>.Build.cs`.
- `Cannot find class 'ULevelSequence'` — `LevelSequence` module not in PublicDependencyModuleNames.
- `'ULevelSequenceEditorBlueprintLibrary' not declared` — `LevelSequenceEditor` not in PrivateDependencyModuleNames.

- [ ] **Step 5.8: Run smoke test (deferred to user)**

After the build succeeds and UE is open with the plugin loaded:

```bash
py -3 examples/smoke_test.py
```

Expected: 12 sections of JSON output, all `ok: true`. The `sequencer` section may print "no Level Sequences in /Game/ — skipping" if the project has no cinematics — that's acceptable, not a failure.

- [ ] **Step 5.9: Final commit + push + open PR**

```bash
git add UnrealClaudeMCP/UnrealClaudeMCP.uplugin \
        UnrealClaudeMCP/Resources/mcp_manifest.json \
        bridge/unreal_claude_mcp_bridge.py \
        README.md \
        examples/smoke_test.py
git commit -m "feat(v0.8.0): version bumps + smoke step coverage for sequencer"
git push -u origin feat/v0.8.0-sequencer
```

Open the PR with base set to `feat/v0.7.0-asset-registry` (the v0.7.0 PR) — this stacks the v0.8.0 PR on PR #12. GitHub auto-retargets when PR #12 merges.

```bash
gh pr create --base feat/v0.7.0-asset-registry --title "feat: v0.8.0 sequencer scaffolding (3 new handlers)" --body-file <(cat <<'EOF'
## v0.8.0 — Sequencer scaffolding

Tool count: 25 → 28. Version: 0.7.0 → 0.8.0.

**Stacked on PR #12 (v0.7.0).** Will auto-retarget to main when PR #12 merges.

### Three new handlers
1. **inspect_sequence** — read tracks, sections, bindings, frame rate, playback range from a ULevelSequence.
2. **create_sequence** — create + initialize a new Level Sequence asset with display rate and playback end-frame.
3. **bind_actor_to_sequence** — add an existing level actor as a possessable binding (hybrid label/FName identification).

### Spec + plan
- Spec: `docs/superpowers/specs/2026-05-08-v0.8.0-sequencer-design.md`
- Plan: `docs/superpowers/plans/2026-05-08-v0.8.0-sequencer.md`

### Build.cs additions
First Build.cs change since v0.4.0: `LevelSequence`, `MovieScene`, `MovieSceneTracks` (public) + `LevelSequenceEditor` (private). All four module names verified against UE 5.7 source Build.cs files.

### Verification
- Bridge tests 84 → 87 (3 new schema tests). All pass locally.
- CI 4-job pytest matrix runs on push.
- UE build + live smoke pending (host .uproject lives outside this repo).

### Out of scope (deferred to v0.8.x)
- Keyframe authoring (channel APIs are templated; use execute_unreal_python).
- Movie Render Queue.
- Spawnable bindings (template-instanced).
- Sub-sequences, shot tracks, camera-cut tracks.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)
```

- [ ] **Step 5.10: Check codex review on the new PR**

Per the user's directive ("after a PR finishes, codex/chatgpt review will post — keep an eye on his comments"), check `gh pr view <num> --json reviews,comments` and inline comments via `gh api repos/<owner>/<repo>/pulls/<num>/comments` 2-5 minutes after opening. Address any P1/P2 findings before moving on.

---

## Self-review

**1. Spec coverage:**
| Spec section | Task |
|---|---|
| `inspect_sequence` handler | Task 2 |
| `create_sequence` handler | Task 3 |
| `bind_actor_to_sequence` handler | Task 4 |
| Build.cs additions | Task 1 |
| Version bumps + smoke + tests | Task 5 |
| Stable error codes | Distributed across handler tasks (2-4) |
| References (UE 5.7 source paths) | In spec; not a code task |

✓ Full coverage.

**2. Placeholder scan:** No "TBD"/"TODO"/"implement later". Code blocks are concrete. C++ handler skeletons are described as algorithmic flows because the spec already grounds the API surface and v0.7.0's `Handler_InspectAsset.cpp` provides the structural template — same calibration as the v0.7.0 plan, which shipped cleanly.

**3. Type consistency:**
- `UCMCPAssetPath::ToObjectPath` / `ToPackagePath` / `IsValidLeafName` referenced consistently across Tasks 2-4 (defined in v0.7.0).
- Module-registration extern names follow the `Make_Handler_<Name>()` pattern.
- All three handlers return JSON shapes aligned with their spec entries.
- The actor-resolver helper (Task 4 step 4.1) is the one ambiguous reference — Task 4 explicitly calls out the pre-implementation grep step to find it, with an inline factor-out-if-still-inlined directive. That's a real pre-implementation lookup, not a placeholder.
