# 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(), triggerTimeline: Array.Empty(), artifacts: Array.Empty(), 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 GetTcp(); void SetTcp(double x, double y, double z); bool GetIo(int port, string ioType); void SetIo(int port, bool value, string ioType); IReadOnlyList GetJointPositions(); IReadOnlyList GetPose(); ControllerStateSnapshot GetSnapshot(); void ExecuteTrajectory(TrajectoryResult result, IReadOnlyList 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(() => 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> 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(() => 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> 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.