# SIM S1C live-worker runbook

## Scope

This runbook starts and commands the first browser-visible stock Ackermann rover
through the Mission Core control plane. It accepts only a virtual PX4/Gazebo
run. It does not admit a physical vehicle, direct actuator setpoints, navigation
or safety.

Runtime placement:

```text
React Control Station
  -> same-origin Mission Core Polygon API
  -> mode-0600 Unix socket
  -> unprivileged Simulation Worker agent
  -> loopback-only network namespace
  -> Micro XRCE-DDS Agent + PX4 SITL + Gazebo Harmonic
```

The Unix socket is ephemeral runtime coordination. Source, run journals,
provider logs and qualification artifacts remain under
`/mnt/d/NDC_MISSIONCORE/simulation`.

## Preconditions

- use the dedicated `MissionCore-Sim` WSL distribution;
- stage an immutable Git archive below the reviewed D root;
- verify its SHA-256 against the source artifact;
- use an exact 40-character Mission Core commit;
- confirm no PX4, Gazebo or Micro XRCE-DDS provider residue exists;
- keep `actuator_authority=false`;
- do not add a routed NIC to the worker namespace.

## Start worker agent

From the exact staged source generation:

```bash
sudo simulation/s1/worker-agent.sh <exact-40-character-commit>
```

The wrapper creates `/run/missioncore-sim` as a private runtime directory,
enters a fresh network namespace, raises only loopback and drops to the
`missioncore` user before importing product code.

Expected status through the backend gateway:

```json
{
  "schema_version": "missioncore.simulation-worker-status/v2",
  "worker_id": "mission-gpu-s1",
  "transport": "unix",
  "mode": "simulation",
  "available": true,
  "control_available": true,
  "active_run_id": null,
  "run_state": null,
  "active_provider_ids": [],
  "provider_profile": {
    "schema_version": "missioncore.simulation-provider-profile/v1",
    "profile_id": "stock-rover-gazebo-px4-s1d",
    "providers": [
      {
        "provider_id": "gazebo",
        "roles": ["world", "physics", "state", "sensor"],
        "capabilities": [
          "clock.simulation",
          "state.vehicle-pose",
          "truth.ground-truth",
          "sensor.virtual"
        ]
      },
      {
        "provider_id": "px4-ros2-offboard",
        "roles": ["controller"],
        "capabilities": ["command.rover-speed-steering/v1"]
      },
      {
        "provider_id": "micro-xrce-dds-agent",
        "roles": ["transport"],
        "capabilities": ["transport.ros2"]
      }
    ],
    "clock": {
      "provider_id": "gazebo",
      "domain": "gazebo:/clock",
      "unit": "nanoseconds",
      "mode": "simulation"
    },
    "control_profiles": ["rover-speed-steering/v1"],
    "canonical_frames": {
      "world": "map_enu",
      "body": "base_link_flu"
    }
  },
  "isolation": {
    "network": "loopback-only-netns",
    "process_identity": "missioncore",
    "artifact_policy": "d-only"
  },
  "authority": {
    "scope": "virtual-only",
    "actuator_authority": false,
    "direct_actuator_setpoints_allowed": false
  }
}
```

## Start Mission Core

```bash
export MISSIONCORE_POLYGON_RUNS_ROOT=/mnt/d/NDC_MISSIONCORE/simulation/artifacts/s1/runs
export MISSIONCORE_POLYGON_WORKER_SOCKET=/run/missioncore-sim/worker.sock
export MISSIONCORE_POLYGON_WORKER_CONTROL=internal-virtual-only
export MISSIONCORE_COMMIT=<exact-40-character-commit>
uv run uvicorn k1link.web.app:app --host 127.0.0.1 --port 8765
```

Open the Control Station:

```text
http://127.0.0.1:8765/
```

After Mission Core confirms the worker, `Полигон` must appear in the header and
open the implemented Polygon workspace directly. The legacy
`?workspace=polygon-run[&run=<qualification-run-id>]` URL redirects to
`polygon-datasets`; it is not a separate operator surface. The live panel must
show `Worker готов`. Start/stop calls require an
`Idempotency-Key`; the React client generates one per operator intent. Removing
the `internal-virtual-only` environment gate must leave status visible while
disabling lifecycle actions.

Cold PX4/Gazebo/ROS 2 startup is expected to take roughly 30–60 seconds on the
reviewed worker. The lifecycle request remains bounded at the gateway and the
controller fails closed if ROS publication or armed Offboard admission is not
confirmed.

## Live-state contract

The first S1C live sample comes from:

```text
/world/rover/dynamic_pose/info
```

It is published to the browser as `missioncore.vehicle-state/v1` with:

- `frame_id=map_enu`;
- `child_frame_id=base_link_flu`;
- integer Gazebo simulation time;
- bounded position and quaternion;
- `signal=ground-truth`;
- `quality=diagnostic`;
- `scope=virtual-only`;
- no navigation or safety acceptance.

This signal proves live presentation and frame naming only. It is not the
accepted PX4/ROS 2 telemetry path.

## S1D command contract

While the run is `running`, the React controls submit:

```text
POST /api/v1/polygon/worker/runs/<run-id>/commands
Idempotency-Key: <unique operator intent>
{"speed_mps": 1.0, "steering_normalized": -0.6}
```

Mission Core validates the S1 envelope, assigns a monotonic sequence and
250 ms simulation-time TTL, and durably journals the command before worker
delivery. The worker keeps the external speed/steering contract but maps the
stock model to PX4 rover-level Offboard control:

- `RoverThrottleSetpoint.throttle_body_x = speed_mps / 3.1`;
- `RoverSteeringSetpoint.normalized_steering_setpoint` carries steering;
- `OffboardControlMode.thrust_and_torque=true`;
- `OffboardControlMode.direct_actuator=false`;
- no `ActuatorMotors` or `ActuatorServos` message is published by Mission Core.

The browser repeats a held directional intent fast enough to renew TTL. Release,
the explicit `Стоп` control, stale command TTL or run stop all drive the
setpoint to zero. The `3.1 m/s` factor is the stock PX4 model's
`RO_MAX_THR_SPEED`; S1D motion acceptance must not be described as calibrated
closed-loop speed tracking.

## Acceptance checklist

- [ ] exact source commit and source/build SHA-256 recorded;
- [ ] focused target tests pass from the same staged generation;
- [ ] worker status reports loopback-only, unprivileged and D-only boundaries;
- [ ] browser start creates one new immutable qualification run;
- [ ] status traverses `starting -> running`;
- [ ] both provider IDs appear only after readiness;
- [ ] live samples advance in Gazebo time;
- [ ] React shows run ID, sim time, ENU pose and provider list;
- [ ] archive remains path-free and command content is not exposed;
- [ ] browser stop reaches terminal `completed`;
- [ ] stop records `operator-stop-clean`;
- [ ] PX4, Gazebo and Micro XRCE-DDS leave no process residue;
- [ ] straight, turn and reverse commands produce causal pose changes;
- [ ] one isolated non-zero command expires at 250 ms and produces a stable
      stopped pose;
- [ ] command acceptance reports armed Offboard and remains below TTL;
- [ ] command count and command journal sequences match submitted intents.

## Accepted S1D evidence

- code commit:
  `49b0f47fea0ae29c977bbd60808696b85a9380cd`;
- source archive SHA-256:
  `30aad33664a536f2a6cb87bc172a5dae111ae479d5899723832a1abc17c4517b`;
- Control Station archive SHA-256:
  `014724896c8c2169a89b0ed6971b285b6e80da918d892509e9e93568efb99f25`;
- 40 focused tests passed from the exact source-first D-only generation;
- cold start: one request, 32.08 seconds;
- run: `s1c-49b0f47-20260724t195259z-62edd2`;
- straight endpoint: `(3.74, -0.00)` m from origin;
- turn endpoint: `(5.11, 2.13)` m;
- reverse endpoint: `(5.58, 0.67)` m;
- sampled command admission: 44.6 ms;
- TTL evidence: counter 4 -> 5 and identical following X/Y samples;
- explicit stop: no PX4, Gazebo or Micro XRCE-DDS residue.

## Failure handling

- A missing socket returns unavailable status; it does not fall back to PX4.
- Start/stop is `403` without the explicit backend control gate.
- Commit drift is rejected before provider start.
- A malformed or oversized worker message fails the gateway contract.
- Provider readiness failure seals the run as failed and triggers supervised
  cleanup.
- Agent termination aborts an active run and stops owned provider groups.
- Agent restart reconciles an interrupted journal as failed; restart-safe
  cross-process PID ownership remains a separate open checker.
