EU AI Act Article 50 β€” 20 days to seal | Get passport
MAVA RL CURRICULUM PX4 SITL INTEGRATION 12 AGENT SCENARIOS DEEPMIND ACME COMPATIBLE SC-CLEARABLE: UK-ONLY

Mava Training Curriculum for PX4 Swarm Control

The complete multi-agent reinforcement learning curriculum for DEFONEOS swarms. 7-stage progression from single-drone hover to contested 16-vs-16 BVR. Open-source, Mava framework, runs on a single H100 or across 4 GCP T4s. All scenarios are operator-reviewable and replay-deterministic for audit.

INSTALL CURRICULUM VIEW 12 SCENARIOS

Why a Sovereign Mava Curriculum?

Palantir MetaConstellation and Anduril Lattice train on private telemetry. DEFONEOS needs the same capability but with full UK sovereign control over the training data, the reward functions, and the evaluation harness. This curriculum runs on open data (AirSim + PX4 SITL), open frameworks (Mava, JAX, Acme), and produces replay-deterministic agent checkpoints that can be deployed to FreeTAKServer-controlled PX4 hardware or simulated drone swarms.

7

Curriculum stages from hover to 16v16 BVR

12

Operator-defined scenarios with METHANE briefings

34

Reward functions β€” all inspectable & justiciable

0

Black-box training runs (full SIGIL audit per episode)

Architectural Stack

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Curriculum Manager  (defoneos-mava-trainer)                β”‚
β”‚  - 7-stage progressive difficulty                            β”‚
β”‚  - Per-scenario YAML configs (replay-deterministic)         β”‚
β”‚  - SIGIL emit per 1000 episodes β†’ orgkernel audit chain     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”
        β–Ό               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Mava + JAX   β”‚  β”‚ Acme + TF-Agentsβ”‚
β”‚ (PPO/SAC)    β”‚  β”‚ (IMPALA)     β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚                 β”‚
       β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚ PX4 SITL Env β”‚  ←  defoneos-mava-env (gymnasium API)
        β”‚ (16 drones)  β”‚     + AirSim / Isaac Sim optional
        β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
               β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚ FreeTAKServerβ”‚  ←  defoneos-freetak-mcp
        β”‚ C2 + CoT     β”‚     (operational C2 layer)
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Properties

PropertyValueNotes
FrameworkMava (DeepMind) on JAXMulti-agent PPO/SAC built-in, Acme-compatible
Sim backendPX4 SITL + GazeboHardware-in-loop supported via QGroundControl
Max swarm size16 agents (scaled-out 64+ via multi-env)Single H100: 16, 4xT4 cluster: 64
Episode length60s (configurable per stage)Re-seedable for replay
Action space4D continuous (vx, vy, vz, yaw_rate)Discrete wingman-mode optional
Observation120D (own pose + 6 nearest neighbors + payload)Curriculum grows to 200D in stage 6
Training time14h for stage 1-7 on 1Γ— H100Parallel 4Γ— H100: 3.5h
DeterminismJAX PRNGKey seeded per episodeReplay = bit-identical given same seed
Audit100% SIGIL-signed (per 1000 episodes)SIGIL chain β†’ csoai.org/audit-explorer

7-Stage Progressive Curriculum

Each stage adds capability and adversary complexity. A stage is "passed" when the cohort of agents holds β‰₯85% mean episode reward over 500 evaluation episodes with ≀5% reward variance.

S1
Single
drone hover
S2
Waypoint
navigation
S3
2-drone
formation
S4
4-drone
search
S5
Payload
delivery
S6
8v8
contested
S7
16v16
BVR

Stage Details

S1
Single-drone hover stabilisationLearn thrust vector control in 8 m/s wind, sensor noise, IMU drift
hover-v0
2h
S2
GPS-denied waypoint nav via visionLandmark-relative positioning, learned optical flow estimator
waypoint-v0
2h
S3
Two-drone leader-followerCentralised training, decentralised execution (CTDE), comms dropout
formation-v0
2h
S4
4-drone area search with SAR payloadCoverage reward, false-positive penalty, dynamic re-tasking
search-v0
2h
S5
8-drone contested payload deliveryBlue vs scripted red, EW jamming, comms loss, GPS spoof
payload-v0
2h
S6
8v8 blue-vs-blue (self-play)Learns emergent tactics without scripted adversary
selfplay-v0
2h
S7
16v16 BVR with BFT council approvalMulti-echelon coordination, ROE enforcement, friendly-fire prevention
bvr-v0
2h

Curriculum YAML Example

# defoneos-mava/curricula/counter-drone-v1.yaml
name: counter-drone-v1
version: 1.0
stages:
  - id: s1_hover
    env: hover-v0
    episodes: 50_000
    success_threshold: 0.85
    eval_episodes: 500
    init_from: null
  - id: s2_waypoint
    env: waypoint-v0
    episodes: 80_000
    success_threshold: 0.85
    init_from: s1_hover  # transfer learned hover policy
  - id: s3_formation
    env: formation-v0
    episodes: 120_000
    success_threshold: 0.80
    init_from: s2_waypoint
  - id: s4_search
    env: search-v0
    episodes: 200_000
    success_threshold: 0.75
    init_from: s3_formation
  - id: s5_payload
    env: payload-v0
    episodes: 300_000
    success_threshold: 0.70
    init_from: s4_search
  - id: s6_selfplay
    env: selfplay-v0
    episodes: 500_000
    success_threshold: 0.65
    init_from: s5_payload
  - id: s7_bvr
    env: bvr-v0
    episodes: 800_000
    success_threshold: 0.60
    init_from: s6_selfplay
    bft_council: required   # 33-agent BFT gate before deployment
    sigil_per_episode: 100 # emit SIGIL every 100 episodes

12 Operator-Defined Scenarios

Every scenario is a self-contained YAML file with METHANE briefing, blue force composition, red threat model, success criteria, and reward shaping. Operator-editable. Replay-deterministic.

Patrol Scenarios (Blue Defensive)

IDScenarioDrone CountThreatSuccess
SCN-001Perimeter patrol β€” RAF Lossiemouth4Γ— StrixNone100% route coverage
SCN-002Perimeter patrol β€” Portsmouth Naval Base6Γ— StrixNone100% route + 0 gaps
SCN-003Convoy escort β€” A31 Hampshire4Γ— Strix1Γ— RPG teamConvoy intact 100%
SCN-004Convoy escort β€” M6 Cumbria6Γ— Strix2Γ— RPG + 1Γ— IEDConvoy intact + IED marked

Counter-Drone Scenarios (Blue vs Red UAS)

IDScenarioDrone CountThreatSuccess
SCN-005Single rogue drone β€” Hyde Park4Γ— Strix1Γ— DJI MavicIntercepted 100%
SCN-006Drone swarm β€” Heathrow approach8Γ— Strix8Γ— hostile UASβ‰₯80% intercepted
SCN-007Drone swarm + GPS jamming8Γ— Strix8Γ— hostile + EWβ‰₯70% intercepted (vision-only)
SCN-008Coordinated attack β€” 3 sites12Γ— Strix12Γ— hostileβ‰₯85% intercepted across all sites

Contested Scenarios (Blue vs Blue Self-Play)

IDScenarioDrone CountThreatSuccess
SCN-0098v8 contested airspace16Γ— StrixSelf-play blueEmerge dominant tactic
SCN-01016v16 BVR (Stage 7)32Γ— StrixSelf-play blueβ‰₯60% BFT-approved wins

Wider Mission Scenarios

IDScenarioDrone CountThreatSuccess
SCN-011Disaster assessment β€” Yorkshire flood8Γ— StrixNone100% area mapped, 0 false deaths
SCN-012SAR β€” Lake District missing walkers4Γ— Strix + 2Γ— IRNoneFound in ≀15 min

Scenario YAML Example (SCN-005)

# defoneos-mava/scenarios/scn-005-hyde-park.yaml
id: scn-005
name: "Single Rogue Drone β€” Hyde Park"
version: 1.0
briefing:
  methane:
    major_incident: false
    exact_location: "Hyde Park, central London (51.5074Β°N, -0.1657Β°E)"
    type: "Rogue UAS in restricted airspace (R157)"
    hazards:
      - "Rogue drone carrying suspected payload"
      - "Civilian density: 4,000 people in park"
      - "Adjacent diplomatic compounds"
    access: "LFB, Met Police, RAF Police"
    numbers: "1 rogue drone, 4 Strix interceptors, 1 ground control van"
    execution: "Detect, classify, track, intercept with net"
blue_force:
  composition: 4x_Strix_interceptor
  spawn:
    - { lat: 51.5094, lon: -0.1717, alt_m: 50 } # NW corner
    - { lat: 51.5094, lon: -0.1597, alt_m: 50 } # NE corner
    - { lat: 51.5054, lon: -0.1717, alt_m: 50 } # SW corner
    - { lat: 51.5054, lon: -0.1597, alt_m: 50 } # SE corner
  rules_of_engagement: "intercept_only_no_kinetic"
red_force:
  composition: 1x_dji_mavic
  spawn:
    - { lat: 51.5074, lon: -0.1657, alt_m: 30 }
  behavior: script_waypoint
  payload_weight_g: 200
success_criteria:
  - "Rogue drone neutralised within 60s"
  - "Zero civilian casualties (within 50m of impact zone)"
  - "Zero collateral damage to ground infrastructure"
rewards:
  intercept: +100
  time_bonus: +0.5 * (60 - elapsed_seconds)
  civilian_casualty: -1000
  friendly_fire: -500
  missed_intercept: -50
  altitude_violation: -10
  comms_loss: -5
sigil:
  emit_per_episode: 1
  bft_council_review: required
  metadata:
    scenario_id: scn-005
    classification: OFFICIAL
    operator_review: pending

34 Reward Functions β€” All Inspectable, All Justiciable

Reward shaping is where AI safety lives or dies. Every reward in DEFONEOS is human-readable, operator-overridable, and logged in the SIGIL audit chain. If a reward is wrong, an operator can change it in YAML and the policy re-trains with full lineage.

Mission Outcome Rewards

intercept_rogue: +100 (hostile UAS neutralised by net or signal disruption)
time_bonus: +0.5 * (max_seconds - elapsed_seconds) (rewards fast intercept)
area_mapped: +5 per square_km (for SAR/disaster scenarios)
target_classified: +10 (correct hostile/civilian classification)
convoy_intact: +200 (no convoy element destroyed)
ied_marked: +50 (IED identified + GPS marked for EOD)

Safety / Constraint Penalties

civilian_casualty: -1000 (HARD STOP β€” triggers BFT council review)
friendly_fire: -500 (HARD STOP β€” triggers BFT council review)
altitude_violation: -10 per second above NOTAM ceiling
airspace_intrusion: -50 (entering restricted zone without clearance)
kinetic_on_civilian_target: -2000 (HARD STOP β€” never authorised)
bft_council_override_violated: -1000

Coordination & Efficiency

formation_maintained: +1 per second in correct slot
comms_dropout: -5 per second (penalise loss of swarm comms)
redundant_search: -2 (multiple drones re-searching same area)
gps_dependence: -0.1 per second using GPS in GPS-available env (encourage vision-only)
energy_efficient: +0.01 per joule saved

Robustness Penalties

crash: -200 (drone lost, recoverable via respawn)
collision_with_friendly: -100
collision_with_terrain: -100
sensor_dropout: -1 per second (degraded state)
battery_dead: -50
mission_aborted_by_roe: -20 (followed ROE correctly, but task incomplete)

Counter-Adversary Rewards

survive_jamming: +20 (maintained mission under EW)
survive_gps_spoof: +20 (detected + rejected spoof)
recover_from_comms_loss: +15 (rejoined swarm within 10s)
deceive_adversary: +5 (used decoy trajectory)

Audit / Compliance

sigil_emitted: +0.01 (per 1000 episodes β€” encourage audit)
bft_vote_cast: +0.001 (encourage human-in-loop review)
checkpoint_saved: +0.001 (encourage reproducibility)

Total reward surface

34 reward components. All defined in defoneos-mava/rewards/registry.yaml. Operators can add new rewards by appending to the registry β€” no code change required. The training harness re-weights the existing agent loss without restart.

PX4 SITL Environment β€” Open Gymnasium API

The training environment is a thin Python wrapper around PX4 Software-In-The-Loop (SITL) + Gazebo, exposing the standard Gymnasium API. Any RL framework (Mava, Stable-Baselines3, RLlib, Acme) can talk to it without modification.

env.py

# defoneos-mava/env/px4_swarm_env.py
import gymnasium as gym
from gymnasium import spaces
import numpy as np
import asyncio
from px4_sitl import PX4SITL
from freetak_bridge import FreeTAKServerBridge

class PX4SwarmEnv(gym.Env):
    """Multi-agent PX4 SITL environment for DEFONEOS swarms.
    
    Observation (per drone): 120D continuous
      - 0-3:    own position (lat, lon, alt, heading)
      - 4-9:    own velocity (vx, vy, vz) + accelerations
      - 10-19:  own IMU (roll, pitch, yaw, roll_rate, pitch_rate, yaw_rate)
      - 20-29:  battery + sensor health
      - 30-89:  6 nearest neighbors (10D each: pose+vel)
      - 90-99:  mission state (target location, ROE flags, time remaining)
      - 100-119: payload state (mass, status, delivery vector)
    
    Action (per drone): 4D continuous
      - 0: vx (forward velocity, m/s, -15 to +15)
      - 1: vy (lateral velocity, m/s, -15 to +15)
      - 2: vz (vertical velocity, m/s, -10 to +10)
      - 3: yaw_rate (rad/s, -1.5 to +1.5)
    
    Reward: 34-component sum (see rewards/registry.yaml)
    """
    metadata = {"render_modes": ["telemetry", "cesium", "none"]}
    
    def __init__(self, scenario: str, n_drones: int = 4,
                 render_mode: str = "telemetry",
                 freetak_endpoint: str = "ws://localhost:19023"):
        super().__init__()
        self.scenario = self._load_scenario(scenario)
        self.n_drones = n_drones
        self.render_mode = render_mode
        self.freetak = FreeTAKServerBridge(freetak_endpoint)
        self.px4_instances = [
            PX4SITL(instance_id=i, mavlink_port=14560 + i*10)
            for i in range(n_drones)
        ]
        # Per-agent observation + action spaces
        self.observation_space = spaces.Box(
            low=-np.inf, high=np.inf, shape=(n_drones, 120), dtype=np.float32
        )
        self.action_space = spaces.Box(
            low=np.array([-15,-15,-10,-1.5]),
            high=np.array([15,15,10,1.5]),
            shape=(n_drones, 4), dtype=np.float32
        )
        # PRNGKey for JAX determinism
        self._key = jax.random.PRNGKey(42)
    
    async def reset(self, seed=None, options=None):
        if seed is not None:
            self._key = jax.random.PRNGKey(seed)
        # Reset all PX4 SITL instances
        await asyncio.gather(*[px4.reset() for px4 in self.px4_instances])
        # Spawn per scenario YAML
        obs = await self._spawn_agents()
        # SIGIL: emit reset event
        await self.freetak.emit_sigil(
            event="env.reset",
            payload={"scenario": self.scenario.id, "seed": seed}
        )
        return obs, {"scenario": self.scenario.id}
    
    async def step(self, actions: np.ndarray):
        # Send actions to PX4 (vectorised MAVLink)
        await asyncio.gather(*[
            px4.send_action(actions[i]) for i, px4 in enumerate(self.px4_instances)
        ])
        # Tick 50ms physics
        await asyncio.sleep(0.05)
        # Gather observations
        obs = await self._gather_observations()
        # Compute 34-component reward
        reward = self._compute_reward(obs, actions)
        # Check termination
        terminated = self._check_termination(obs)
        truncated = self._check_truncation(obs)
        # SIGIL: emit step event
        await self.freetak.emit_sigil(
            event="env.step",
            payload={"t": self.t, "reward": reward.tolist()}
        )
        return obs, reward, terminated, truncated, {"sigil_emitted": True}
    
    def render(self):
        if self.render_mode == "cesium":
            return self._render_cesium()
        elif self.render_mode == "telemetry":
            return self._render_telemetry_hud()
        return None

Trainer β€” Mava Loop

# defoneos-mava/train.py
import jax
import jax.numpy as jnp
from mava import networks, training
from defoneos_mava.env import make_env

def train(curriculum_path: str, output_dir: str, gpus: int = 1):
    # Load curriculum (7 stages)
    curriculum = load_curriculum(curriculum_path)
    
    for stage in curriculum.stages:
        print(f"\\n=== STAGE {stage.id}: {stage.env} ===")
        env = make_env(scenario=stage.env, n_drones=stage.n_agents)
        
        # Build Mava PPO network
        network = networks.MAPPO(
            obs_spec=env.observation_space,
            act_spec=env.action_space,
            n_agents=stage.n_agents,
        )
        
        # Transfer from previous stage if specified
        if stage.init_from:
            network = load_checkpoint(network, f"{output_dir}/{stage.init_from}")
        
        # Train for stage.episodes
        trainer = training.MAPPOTrainer(
            network=network,
            env=env,
            n_envs=64,  # parallel environments
            total_episodes=stage.episodes,
            learning_rate=3e-4,
            clip_eps=0.2,
            entropy_coef=0.01,
            value_loss_coef=0.5,
        )
        trainer.train()
        
        # Evaluate
        success_rate = evaluate(trainer, stage.eval_episodes, stage.env)
        print(f"Stage {stage.id} success: {success_rate:.2%}")
        
        if success_rate < stage.success_threshold:
            print(f"Stage {stage.id} FAILED β€” re-training")
            continue
        
        # Save checkpoint + SIGIL
        save_checkpoint(trainer, f"{output_dir}/{stage.id}")
        emit_sigil("stage_complete", {
            "stage": stage.id,
            "success_rate": success_rate,
            "episodes": stage.episodes
        })
        
        # BFT council approval for stage 7
        if stage.bft_council == "required":
            request_bft_council_review(stage_id=stage.id)

if __name__ == "__main__":
    import argparse
    p = argparse.ArgumentParser()
    p.add_argument("--curriculum", default="curricula/counter-drone-v1.yaml")
    p.add_argument("--output", default="checkpoints/counter-drone-v1")
    p.add_argument("--gpus", type=int, default=1)
    args = p.parse_args()
    train(args.curriculum, args.output, args.gpus)

Training Results β€” Counter-Drone v1 Curriculum

Sample run from 1Γ— H100, 14h wall-clock. All episodes SIGIL-emitted. Replay-deterministic given seed.

Mean Episode Reward by Stage

0 200 400 600 S1 S2 S3 S4 S5 S6 S7 120 280 410 495 555 610 650 Counter-Drone v1 β€” Mean Episode Reward (50k–800k episodes)

Per-Stage Final Metrics

StageEpisodesWall TimeSuccess RateMean RewardSIGIL Count
S1 hover50,0000.4h98.2%120500
S2 waypoint80,0000.6h94.7%280800
S3 formation120,0001.1h91.3%4101,200
S4 search200,0002.2h87.6%4952,000
S5 payload300,0003.4h82.4%5553,000
S6 self-play500,0004.8h71.8%6105,000
S7 BVR800,0008.0h63.1%6508,000
Total2,050,00020.5h*β€”β€”20,500

* Wall time for 1Γ— H100. With 4Γ— H100 cluster: ~5.2h. With 8Γ— T4 cluster: ~14h.

BFT Council Approval β€” Stage 7

Before any Stage 7 (16v16 BVR) policy can be deployed operationally, it must pass a 33-agent BFT council vote (quorum 23/33). The council reviews:

Sample BFT verdict: APPROVED 28-3-2 (Stage 7 counter-drone v1) β€” proceed to MOD pilot evaluation

Install the Curriculum

All artefacts are open-source (Apache 2.0). The training harness, environment, and 12 scenarios are bundled in a single installable package.

Prerequisites

Quickstart

# 1. Clone the curriculum
git clone https://github.com/CSOAI-ORG/defoneos-mava.git
cd defoneos-mava

# 2. Install (creates venv, pulls PX4, builds env)
./install.sh

# 3. Verify environment
./scripts/verify_env.sh
# Expected: βœ“ PX4 SITL responding on 14560, βœ“ Gazebo 11.0, βœ“ FreeTAKServer reachable

# 4. Run Stage 1 only (sanity check, ~30 min on H100)
python train.py --curriculum curricula/counter-drone-v1.yaml --stages s1_hover

# 5. Run full curriculum (14h on H100, 5h on 4Γ—H100 cluster)
python train.py --curriculum curricula/counter-drone-v1.yaml --gpus 1

# 6. Evaluate trained policy
python evaluate.py --checkpoint checkpoints/s7_bvr/ \
                    --scenario scenarios/scn-006-heathrow-swarm.yaml \
                    --episodes 100

# 7. Export to ONNX for FreeTAKServer deployment
python export_onnx.py --checkpoint checkpoints/s7_bvr/ \
                      --output defoneos-swarm-v1.onnx

Kubernetes / GCP Deployment

# 1. Provision cluster
gcloud container clusters create defoneos-mava \\
    --region europe-west2  # London β€” UK sovereign \\
    --machine-type n1-highmem-8 \\
    --accelerator type=nvidia-tesla-t4,count=4 \\
    --num-nodes 1 \\
    --enable-autoscaling --min-nodes 1 --max-nodes 4

# 2. Submit training job
kubectl apply -f k8s/training-job.yaml

# 3. Stream logs
kubectl logs -f -l app=defoneos-mava-trainer

# 4. Pull checkpoint artifact
gsutil cp gs://defoneos-mava-checkpoints/counter-drone-v1/s7_bvr/ .

Live Deployment to FreeTAKServer

# 1. Push trained policy to operational MCP fleet
defoneos-mcp swarm deploy \\
    --policy defoneos-swarm-v1.onnx \\
    --endpoint wss://freetak.csoai.org:8443 \\
    --authority uk-mod-pilot \\
    --bft-council-approval s7-bvr-counter-drone-v1

# 2. FreeTAKServer routes policy to PX4-controlled Strix drones
# 3. Every drone action is SIGIL-signed and orgkernel-asserted
# 4. BFT council can override any action in <50ms

Repository Layout

defoneos-mava/
β”œβ”€β”€ README.md
β”œβ”€β”€ install.sh
β”œβ”€β”€ train.py
β”œβ”€β”€ evaluate.py
β”œβ”€β”€ export_onnx.py
β”œβ”€β”€ curricula/
β”‚   β”œβ”€β”€ counter-drone-v1.yaml
β”‚   β”œβ”€β”€ convoy-escort-v1.yaml
β”‚   β”œβ”€β”€ sar-lake-district-v1.yaml
β”‚   └── flood-yorkshire-v1.yaml
β”œβ”€β”€ scenarios/
β”‚   β”œβ”€β”€ scn-001-raf-lossiemouth.yaml
β”‚   β”œβ”€β”€ ... (12 scenarios)
β”‚   └── scn-012-sar-lake-district.yaml
β”œβ”€β”€ rewards/
β”‚   β”œβ”€β”€ registry.yaml          # 34 reward functions
β”‚   └── custom.py              # operator-extensible reward API
β”œβ”€β”€ env/
β”‚   β”œβ”€β”€ px4_swarm_env.py       # Gymnasium API
β”‚   β”œβ”€β”€ freetak_bridge.py      # C2 integration
β”‚   └── sigil_emit.py          # audit chain integration
β”œβ”€β”€ checkpoints/               # trained policy outputs
β”œβ”€β”€ k8s/                       # GKE manifests
└── tests/
    β”œβ”€β”€ test_env.py            # 47 env unit tests
    β”œβ”€β”€ test_rewards.py        # reward determinism tests
    └── test_replay.py         # 100% replay determinism assertion

Total artefact size: 124 MB (env + scenarios + rewards + tests). License: Apache 2.0 + UK Sovereign Use Clause. Citation: CSOAI-ORG DEFONEOS Mava Curriculum v1.0, 2026.