# Minimal Runtime Orchestration 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:** Build the first real execution slice after the HTTP-only refactor by routing `ExecuteTrajectory` and `ExecuteFlyShotTraj` through planning, triggering, and a new minimal FANUC runtime project instead of in-memory last-point assignment.

**Architecture:** Keep `Flyshot.Server.Host` as a pure HTTP adapter and keep uploaded program state in `Flyshot.ControllerClientCompat`, but move controller runtime state into a new `Flyshot.Runtime.Fanuc` project and add a focused planning/orchestration helper in `Flyshot.ControllerClientCompat`. Ordinary trajectory execution will use `ICspPlanner`; uploaded flyshot execution will use `SelfAdaptIcspPlanner` plus `ShotTimelineBuilder`, then hand the resulting `TrajectoryResult` to the runtime.

**Tech Stack:** C#, .NET 8, xUnit, existing `Flyshot.Core.Domain`, `Flyshot.Core.Planning`, `Flyshot.Core.Triggering`, ASP.NET Core DI.

---

### Task 1: Add Runtime Contracts And Minimal FANUC Runtime

**Files:**
- Create: `src/Flyshot.Runtime.Common/IControllerRuntime.cs`
- Create: `src/Flyshot.Runtime.Fanuc/Flyshot.Runtime.Fanuc.csproj`
- Create: `src/Flyshot.Runtime.Fanuc/FanucControllerRuntime.cs`
- Modify: `FlyshotReplacement.sln`
- Modify: `src/Flyshot.Server.Host/Flyshot.Server.Host.csproj`
- Modify: `src/Flyshot.Server.Host/Program.cs`
- Test: `tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs`

- [x] **Step 1: Write the failing runtime test**

```csharp
[Fact]
public void FanucControllerRuntime_ExecuteTrajectory_UpdatesSnapshotAndFinalJointPositions()
{
    var runtime = new FanucControllerRuntime();
    var robot = TestRobotFactory.CreateRobotProfile();
    runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD");
    runtime.SetActiveController(sim: false);
    runtime.Connect("192.168.10.101");
    runtime.EnableRobot(bufferSize: 2);

    var result = new TrajectoryResult(
        programName: "demo",
        method: PlanningMethod.Icsp,
        isValid: true,
        duration: TimeSpan.FromSeconds(1.2),
        shotEvents: Array.Empty<ShotEvent>(),
        triggerTimeline: Array.Empty<TrajectoryDoEvent>(),
        artifacts: Array.Empty<TrajectoryArtifact>(),
        failureReason: null,
        usedCache: false,
        originalWaypointCount: 4,
        plannedWaypointCount: 4);

    runtime.ExecuteTrajectory(result, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);

    var snapshot = runtime.GetSnapshot();
    Assert.Equal("Connected", snapshot.ConnectionState);
    Assert.False(snapshot.IsInMotion);
    Assert.Equal([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], snapshot.JointPositions);
}
```

- [x] **Step 2: Run test to verify it fails**

Run: `dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter FanucControllerRuntime_ExecuteTrajectory_UpdatesSnapshotAndFinalJointPositions -v minimal -m:1 -nodeReuse:false`
Expected: FAIL because `FanucControllerRuntime` and `IControllerRuntime` do not exist.

- [x] **Step 3: Write the minimal runtime contracts and implementation**

```csharp
public interface IControllerRuntime
{
    void ResetRobot(RobotProfile robot, string robotName);
    void SetActiveController(bool sim);
    void Connect(string robotIp);
    void Disconnect();
    void EnableRobot(int bufferSize);
    void DisableRobot();
    void StopMove();
    double GetSpeedRatio();
    void SetSpeedRatio(double ratio);
    IReadOnlyList<double> GetTcp();
    void SetTcp(double x, double y, double z);
    bool GetIo(int port, string ioType);
    void SetIo(int port, bool value, string ioType);
    IReadOnlyList<double> GetJointPositions();
    IReadOnlyList<double> GetPose();
    ControllerStateSnapshot GetSnapshot();
    void ExecuteTrajectory(TrajectoryResult result, IReadOnlyList<double> finalJointPositions);
}
```

```csharp
public sealed class FanucControllerRuntime : IControllerRuntime
{
    // Stage-1 runtime: owns controller state in one place so later sockets can replace internals without rewriting compat service.
}
```

- [x] **Step 4: Run test to verify it passes**

Run: `dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter FanucControllerRuntime_ExecuteTrajectory_UpdatesSnapshotAndFinalJointPositions -v minimal -m:1 -nodeReuse:false`
Expected: PASS.

### Task 2: Add Planning And Triggering Orchestration For Execution

**Files:**
- Create: `src/Flyshot.ControllerClientCompat/PlannedExecutionBundle.cs`
- Create: `src/Flyshot.ControllerClientCompat/ControllerClientTrajectoryOrchestrator.cs`
- Modify: `src/Flyshot.ControllerClientCompat/Flyshot.ControllerClientCompat.csproj`
- Modify: `tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj`
- Test: `tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs`

- [x] **Step 1: Write the failing orchestration tests**

```csharp
[Fact]
public void ControllerClientTrajectoryOrchestrator_PlanOrdinaryTrajectory_RejectsThreeTeachPoints()
{
    var orchestrator = new ControllerClientTrajectoryOrchestrator();
    var robot = TestRobotFactory.CreateRobotProfile();

    Assert.Throws<ArgumentException>(() =>
        orchestrator.PlanOrdinaryTrajectory(robot,
        [
            [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
            [0.5, 0.0, 0.0, 0.0, 0.0, 0.0],
            [1.0, 0.0, 0.0, 0.0, 0.0, 0.0]
        ]));
}

[Fact]
public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_BuildsShotTimeline()
{
    var orchestrator = new ControllerClientTrajectoryOrchestrator();
    var robot = TestRobotFactory.CreateRobotProfile();
    var uploaded = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot();

    var bundle = orchestrator.PlanUploadedFlyshot(robot, uploaded);

    Assert.True(bundle.Result.IsValid);
    Assert.Single(bundle.Result.ShotEvents);
    Assert.Single(bundle.Result.TriggerTimeline);
}
```

- [x] **Step 2: Run tests to verify they fail**

Run: `dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter ControllerClientTrajectoryOrchestrator -v minimal -m:1 -nodeReuse:false`
Expected: FAIL because the orchestrator types do not exist.

- [x] **Step 3: Write the minimal orchestration layer**

```csharp
public sealed class PlannedExecutionBundle
{
    public PlannedExecutionBundle(PlannedTrajectory plannedTrajectory, ShotTimeline shotTimeline, TrajectoryResult result)
    {
        PlannedTrajectory = plannedTrajectory;
        ShotTimeline = shotTimeline;
        Result = result;
    }

    public PlannedTrajectory PlannedTrajectory { get; }
    public ShotTimeline ShotTimeline { get; }
    public TrajectoryResult Result { get; }
}
```

```csharp
public sealed class ControllerClientTrajectoryOrchestrator
{
    public PlannedExecutionBundle PlanOrdinaryTrajectory(RobotProfile robot, IReadOnlyList<IReadOnlyList<double>> waypoints) { ... }
    public PlannedExecutionBundle PlanUploadedFlyshot(RobotProfile robot, ControllerClientCompatUploadedTrajectory uploaded) { ... }
}
```

- [x] **Step 4: Run tests to verify they pass**

Run: `dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter ControllerClientTrajectoryOrchestrator -v minimal -m:1 -nodeReuse:false`
Expected: PASS.

### Task 3: Rewire ControllerClientCompatService To Runtime + Orchestrator

**Files:**
- Modify: `src/Flyshot.ControllerClientCompat/ControllerClientCompatService.cs`
- Modify: `src/Flyshot.ControllerClientCompat/ControllerClientCompatServiceCollectionExtensions.cs`
- Modify: `src/Flyshot.ControllerClientCompat/IControllerClientCompatService.cs`
- Modify: `tests/Flyshot.Server.IntegrationTests/LegacyHttpApiCompatibilityTests.cs`
- Modify: `tests/Flyshot.Server.IntegrationTests/ControllerClientCompatRegistrationTests.cs`
- Test: `tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs`
- Test: `tests/Flyshot.Server.IntegrationTests/LegacyHttpApiCompatibilityTests.cs`

- [x] **Step 1: Write the failing compat-service test**

```csharp
[Fact]
public void ControllerClientCompatService_ExecuteTrajectory_RejectsThreeTeachPointsAfterPlanningIsIntroduced()
{
    var service = TestRobotFactory.CreateCompatService();
    service.SetUpRobot("FANUC_LR_Mate_200iD");
    service.SetActiveController(sim: false);
    service.Connect("192.168.10.101");
    service.EnableRobot(2);

    Assert.Throws<ArgumentException>(() =>
        service.ExecuteTrajectory(
        [
            [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
            [0.5, 0.0, 0.0, 0.0, 0.0, 0.0],
            [1.0, 0.0, 0.0, 0.0, 0.0, 0.0]
        ]));
}
```

- [x] **Step 2: Run test to verify it fails**

Run: `dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter ControllerClientCompatService_ExecuteTrajectory_RejectsThreeTeachPointsAfterPlanningIsIntroduced -v minimal -m:1 -nodeReuse:false`
Expected: FAIL because current service still treats ordinary execution as "move to last waypoint".

- [x] **Step 3: Rewire service to the runtime and orchestrator**

```csharp
public sealed class ControllerClientCompatService : IControllerClientCompatService
{
    private readonly IControllerRuntime _runtime;
    private readonly ControllerClientTrajectoryOrchestrator _trajectoryOrchestrator;

    public void ExecuteTrajectory(IReadOnlyList<IReadOnlyList<double>> waypoints)
    {
        var robot = RequireActiveRobot();
        var bundle = _trajectoryOrchestrator.PlanOrdinaryTrajectory(robot, waypoints);
        _runtime.ExecuteTrajectory(bundle.Result, bundle.PlannedTrajectory.PlannedWaypoints[^1].Positions);
    }

    public void ExecuteTrajectoryByName(string name)
    {
        var robot = RequireActiveRobot();
        var uploaded = RequireUploadedTrajectory(name);
        var bundle = _trajectoryOrchestrator.PlanUploadedFlyshot(robot, uploaded);
        _runtime.ExecuteTrajectory(bundle.Result, bundle.PlannedTrajectory.PlannedWaypoints[^1].Positions);
    }
}
```

- [x] **Step 4: Run focused tests to verify green**

Run: `dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter "ControllerClientCompatService|ControllerClientTrajectoryOrchestrator|FanucControllerRuntime" -v minimal -m:1 -nodeReuse:false`
Expected: PASS.

- [x] **Step 5: Run integration verification**

Run: `dotnet test tests/Flyshot.Server.IntegrationTests/Flyshot.Server.IntegrationTests.csproj -v minimal -m:1 -nodeReuse:false`
Expected: PASS, with existing HTTP compatibility tests still green.

### Task 4: Verify Solution Build And Update Progress Docs

**Files:**
- Modify: `README.md`
- Modify: `AGENTS.md`
- Modify: `docs/controller-client-api-compatibility-requirements.md`

- [x] **Step 1: Update docs to reflect the new stage**

```markdown
- [x] 落地最小 FANUC 运行时骨架
- [x] 将 ExecuteTrajectory / ExecuteFlyShotTraj 接入 Planning + Triggering + Runtime
- [x] 落地 Web 状态页
- [x] 固化 10010 / 10012 / 60015 FANUC 基础协议帧编解码
- [ ] 落地真实 10010 / 10012 / 60015 Socket 通讯与现场联调
```

- [x] **Step 2: Run final build**

Run: `dotnet build FlyshotReplacement.sln --no-restore -v minimal -m:1 -nodeReuse:false`
Expected: PASS with 0 errors.
