# Autopilot Fly a Crazyflie across a procedurally generated world, find the landing pad inside a noisy search area, and touch down clean: `cf_autopilot`, the navigation family of Swarm Subnet 124. ---
Table of Contents
  1. Mission
  2. Interface
  3. State vector layout
  4. Maps
  5. Episode rules
  6. Scoring
  7. Runtime limits
  8. Local testing
--- ## Mission One drone, one goal pad, 60 seconds. Your policy pilots a single CF2X Crazyflie from a start platform to a landing platform placed somewhere in the world: behind buildings, over mountain ridges, between trees. You never get the pad's exact position: the state vector carries a **noisy search-area centre**, so the drone has to fly to the area, find the pad with its depth camera, and land on it. On some maps the pad is **moving**. Scoring rewards landing, landing fast, and flying with clearance from obstacles. Autopilot is active with a **0.15 emission allocation** and runs its own [King of the Hill](../king_of_the_hill.md) lineage.

(back to top)

--- ## Interface ### Model contract | | | |---|---| | Interface version | `submission_zip.v1` | | Entry point | `drone_agent.DroneFlightController` | | Contract file | `swarm_policy_contract.json` (policy_contract.v1) | | Environment types | city, open, mountain, village, warehouse, forest | ### Observation: `dict` with keys `depth`, `state` | Key | Dtype | Shape | Range / layout | |---|---|---|---| | `depth` | float32 | (128, 128, 1) | [0, 1] | | `state` | float32 | (141) at evaluation settings | `position_xyz` → `orientation_rpy` → `linear_velocity_xyz` → `angular_velocity_xyz` → `action_history` → `altitude_norm` → `goal_offset_xyz` | ### Action: float32 tensor, shape (5) | # | Component | Min | Max | |---|---|---|---| | 0 | `dir_x` | -1 | 1 | | 1 | `dir_y` | -1 | 1 | | 2 | `dir_z` | -1 | 1 | | 3 | `speed` | 0 | 1 | | 4 | `yaw` | -1 | 1 | ### Depth camera The `depth` image comes from a forward-facing camera mounted **0.13 m ahead and 0.05 m above** the drone's centre of gravity. FOV is **90° ± 2°**, seeded per map, so train with a little FOV tolerance. The renderer's depth buffer is converted to metric range, clipped to **[0.5 m, 20 m]**, and normalized: `(d − 0.5) / (20 − 0.5)`. Anything closer than 0.5 m reads 0.0; anything at 20 m or beyond reads 1.0. ### Action semantics Components 0–2 are a direction vector: it is normalized to a unit vector (an all-zero vector commands zero velocity). The commanded velocity is `3.0 m/s × speed × direction`, with **`SPEED_LIMIT` = 3.0 m/s**. `yaw` maps to a target heading of `yaw × π` radians, slewed at most **3.141 rad/s**. A DSLPID controller converts the command to rotor RPMs each step. The runner canonicalizes every action before it reaches the sim: components are clipped to the contract bounds and quantized to a fixed step so every validator computes the identical value. A NaN/inf or wrong-shaped action is an artifact fault that fails the seed (`MG_OUTPUT_NONFINITE` / `MG_OUTPUT_CONTRACT`).

(back to top)

--- ## State vector layout `state` is a flat float32 vector of length **141** at evaluation settings (50 Hz control): | Index | Channel | Units / frame | Notes | |---|---|---|---| | 0–2 | Position x, y, z | m, world frame | | | 3–5 | Orientation roll, pitch, yaw | rad | | | 6–8 | Linear velocity vx, vy, vz | m/s | | | 9–11 | Angular velocity wx, wy, wz | rad/s | | | 12–136 | Action history | raw actions | Last **25** raw 5-dim actions, oldest first; zeroed at every reset (no cross-episode leak) | | 137 | `altitude_norm` | [0, 1] | Downward single-ray distance ÷ 20 m; reads 1.0 when nothing is hit within 20 m | | 138–140 | `goal_offset` x, y, z | m | Noisy search-area centre minus drone position (see below) | ### The noisy goal offset `goal_offset` does **not** point at the pad. It points at a search-area centre drawn once per seed: - centre = goal + `uniform(−R, +R)` independently on x and y, + `uniform(−5, +5)` on z (clamped to z ≥ 0) - **R = `search_radius`**, drawn per seed as `min(uniform(5.0, 20.0), feasibility cap)`: the cap guarantees the seed's time budget stays completable within the horizon So the pad can be anywhere within R metres along each axis of where the offset points (up to about 1.4·R metres diagonally). Getting to the offset is navigation; finding the pad from there is perception. The time budget in [Scoring](#scoring) includes an allowance for sweeping that disk.

(back to top)

--- ## Maps Autopilot runs on **all six environment types** with no exclusions. Each 14-day epoch (anchored Monday 16:00 UTC; 14 days from epoch 19 onward) draws **1,100 seeds**: indices 0–299 are templated by a second, 50-slot template (tiled to 300), indices 300–1099 by the 100-slot benchmark template below. Every submission runs all 1,100. The benchmark template (the bulk of your score) is 100 interleaved slots: | Type | Environment | Slots | Start→goal distance | Moving pad | Goal height | |---|---|---|---|---|---| | 1 | City | 17 | 22–45 m | 25% | 0.2–1.0 m | | 2 | Open | 17 | 28–72 m | 80% | 4–14 m | | 3 | Mountain | 17 | 65–100 m | 25% | terrain surface | | 4 | Village | 17 | 28–56 m | 25% | ground surface | | 5 | Warehouse | 16 | 18–35 m | 0% | 0.2–10 m | | 6 | Forest | 16 | 22–45 m | 0% | 0.2–3 m | Each type's distance band is split into three equal sub-bands cycled across its slots, so you see the near, middle, and far end of every band. The moving-pad count is `round(slots × probability)`. The template tiles over the 800 benchmark seeds. ### Spawn and goal placement - **Start**: random XY inside the per-type world box: city ±75 m, open ±60 m, village ±40 m, warehouse ±38 × ±23 m, forest ±42 m, mountain ±(0.6 × 250 m × the map's global scale). On types 1, 2, 5 and 6 the drone spawns on a start platform (radius 0.6 m, height 0.2 m) whose surface sits at a random **0.2–10 m**; type 3 spawns on the terrain surface, type 4 on the ground. The drone starts **0.121 m** above the pad surface, level. - **Goal**: placed at a random angle from the start, at a radius uniform in the slot's distance band (clamped to the world box, min distance enforced, retried up to 100×). Goal height comes from the slot's band; on mountain and village the pad sits on the terrain/ground and distance is measured in 3D. The world builder may nudge start and goal to a clear spot and reseats the goal height on the landing-pad surface.

(back to top)

--- ## Episode rules | | | |---|---| | Drone | 1 × CF2X Crazyflie | | Control / physics rate | 50 Hz / 50 Hz (`SIM_DT` = 1/50 s) | | Horizon | 60 s on every type (3,000 control steps max) | | First observation | From `env.reset()`, before the first `act()` | ### Success: landing, not proximity Success means **landing on the goal platform** (acceptance radius 0.6 m), held stable for **0.5 s** continuously: - in contact with a goal-pad body (contact normal force > 0.01), and - |vz| ≤ 0.5 m/s, and - horizontal speed relative to the platform ≤ 0.6 m/s, and - |roll| and |pitch| ≤ 0.26 rad (~15°). **Moving-platform exception:** the first valid contact is instant success, no dwell required. A `goal_reach_radius` of 1.0 m and a min-goal-distance figure appear in the per-step info dict; they are diagnostics only. Hovering near the pad scores nothing. Land. ### Failure reasons | Reason | Trigger | |---|---| | `OBSTACLE_COLLISION` | Any contact (normal force > 0.01) with a body that is not the drone or a start/goal platform. Both pads are collision-exempt. | | `TILT` | \|roll\| or \|pitch\| exceeds 1.047 rad (~60°) | | `TIMEOUT` | 60 s elapse without a landing | ### Moving platforms Only Autopilot has them. When a slot is moving, the pad follows a **circular, linear, or figure-8** pattern seeded from the map seed: speed **0.6–1.2 m/s**, orbit radius **2–4 m**, start delay **0–2 s**, smooth transitions of **2.5–3.5 s**. The platform actively steers around obstacles (13 candidate angles) and stays within orbit radius + 0.3 m of its centre. Track it, match its velocity, touch down.

(back to top)

--- ## Scoring Per seed: ```text final_score = clamp01(0.45 · success_term + 0.45 · time_term + 0.10 · safety_term) ``` | Term | Weight | Definition | |---|---|---| | `success_term` | 0.45 | 1.0 on a successful landing, else 0.0 | | `time_term` | 0.45 | 1.0 if `t ≤ target_time`, else `clamp01(1 − (t − target_time) / (horizon − target_time))` | | `safety_term` | 0.10 | From the episode's minimum obstacle clearance: 1.0 at ≥ 1.0 m (forest: ≥ 0.6 m), 0.0 at ≤ 0.2 m, linear in between; 0.0 on any collision | The time budget is search-aware: ```text target_time = 1.06 × ( dist(start, goal) / 3.0 + 0.75 · π · R² / (5.1 · 3.0) + 2.0 ) ``` This is straight-line flight at the 3.0 m/s speed limit, plus a sweep of the search disk (area × 0.75, detection width 5.1 m, at 3.0 m/s), plus 2.0 s to land, all with a 6% buffer. Larger search radii get proportionally more time. Hard rules: - **No landing → no score.** A failed seed, or a "success" with a recorded collision, zeroes all terms and pays the participation floor of **0.01** (only when the episode actually ran, t > 0). - An illegitimate model or an `EVAL_ERROR` seed scores **0.0** flat. - The clearance scan ignores the drone itself, the ground plane, both pads, and the thin landing-floor body under a low goal pad during final descent: it measures real obstacle proximity, not the landing itself. To take the family throne, a challenger must beat the champion's benchmark score by at least **0.015** (dynamic floor: 0.005 minimum). See [King of the Hill](../king_of_the_hill.md).

(back to top)

--- ## Runtime limits Your zip runs inside a Docker container as a Cap'n Proto RPC server; the validator calls `reset()` between seeds and `act()` every control step. Autopilot uses the default timing profile: | Budget | Value | |---|---| | First `act()` of a seed | 2.0 s (hardware-fair hard cap: 3.0 reference-seconds) | | Every later `act()` | 0.6 s baseline-equivalent pure compute (hard cap 2.0 reference-seconds + measured RPC overhead + 50 ms margin) | | `reset()` between seeds | 5.0 s | | Connect budget | 60 s to answer the first ping | | Strikes | 15 timeouts or 3 hard-cap hits fail the seed; each timed-out step substitutes a zero action | | Zip size | 50 MiB uncompressed max | | Container | 6 GB RAM, 2 CPUs | `requirements.txt` packages must be on the Docker whitelist: torch, onnxruntime, stable-baselines3, gymnasium, numpy, opencv and friends; the full list lives in `swarm/constants.py`. Submissions enter the queue as `PENDING_BENCHMARK` and run the full 1,100-seed benchmark directly. A separate champion-gated screening phase (300 seeds with early-fail checkpoints) exists behind a code constant but is switched off.

(back to top)

--- ## Local testing Package, verify, and benchmark the same artifact with the family pinned explicitly: ```bash swarm model package --source ./my_model --family-id cf_autopilot swarm model verify --model Submission/submission.zip swarm benchmark --model Submission/submission.zip --family-id cf_autopilot --workers 4 ``` `--seeds-per-group 1` gives a fast smoke run; see the [CLI reference](../CLI_readme.md) for seed files, timeout relaxation, and report parsing.

(back to top)