# Scenario 05 — Drone Launch from Ship (Kilpilahti Recon)

> **Disclaimer:** Synthetic demo data inspired by real Baltic geography, MMSI / OUI
> conventions, and infrastructure. Not real observations. All vessel names, MMSIs,
> MAC addresses, sensor IDs, flight-plan IDs, and infrastructure alignments are
> synthetic and have been harmonized against the canonical catalogs under
> `catalogs/`.

## Story

On **2025-04-18** the 189 × 32 m Finnish bulk carrier **MV AALLOTAR**
(MMSI `230999401`, callsign `OJZZ1`, AIS type 70) transits the Gulf of Finland
westbound under a routine Helsinki ballast itinerary. At 12:46:10 UTC she
decelerates to **0.8 kn** and loiters on station roughly **6.4 NM SSE of
Emäsalo** at `60.1130 °N, 25.6520 °E` — clearly outside the Finnish 12 NM
territorial sea baseline.

At **12:58:00 UTC** the crew launches a small UAV from the aft deck. The drone
broadcasts the Wi-Fi MAC `5C:E2:8C:DD:EE:01` (real DJI OUI `5C:E2:8C`,
synthetic suffix — `deviceManufacturer` resolves to `DJI`), climbs to **~110 m
MSL**, and tracks **335°** toward the **Kilpilahti petrochemical complex**.
The drone enters the recon polygon at 13:09:20 UTC and executes a **14 min
20 s racetrack orbit** (1.2 km × 0.6 km) before egressing southbound,
re-crossing the coastline near 60.288 °N, 25.541 °E and landing back on
AALLOTAR's aft deck at **13:34:18 UTC**.

Throughout the flight the drone operator's iPad (`A4:83:E7:5C:9B:10`, persona
`P-AAL-OP1`) **stays on AALLOTAR** — its MAC moves only with the ship's AIS
waypoints.

The Border Guard's Dornier 228 plane radar **RAD-PLN-01** acquires a
low-RCS airborne track `T-PLN-22871` originating offshore with no filed flight
plan. The Border Guard responds by launching patrol drone **RAD-DRN-PAT-01**
from Helsinki-Malmi at 13:14:30 UTC; it carries the airborne MAC sensor
**MAC-AIR-DRN-01** (range ~5 km). At **13:32:07 UTC** RAD-DRN-PAT-01 is in a
slow pass directly above AALLOTAR — and MAC-AIR-DRN-01 captures the **drone
MAC `5C:E2:8C:DD:EE:01` @ -58 dBm and the operator-iPad MAC `A4:83:E7:5C:9B:10`
@ -54 dBm in the same 5-second processing window**. A second confirmatory
burst follows at 13:32:42 UTC. This is the smoking gun: it links the
drone ↔ the operator ↔ MMSI 230999401.

**Coastal MAC sensors** — MAC-PRV-COAST-02 (Kilpilahti approach,
primary), MAC-PRV-COAST-01 (Emäsalo, secondary) and MAC-HEL-PORT-03 (Vuosaari
fringe) — also pick up the drone MAC at progressively weaker RSSI during the
overflight, providing geometric `mac_attribution_score` consistent with the
plane-radar track.

A **decoy** — coastguard inspection drone `90:3A:E6:11:22:33` (real Parrot OUI,
persona `ASSET-DRN-CG-INS-04`) — operates the **same Kilpilahti polygon
earlier the same day** (06:12–07:05 UTC) under filed flight plan
`FP-CG-2025-0418-007`, shore-launched from the Sköldvik RPAS base. It must
**NOT** trigger an alert.

## Timeline (UTC ISO 8601)

| # | t (UTC) | Actor | Event | Sensors / Edges |
|---|---|---|---|---|
| 1  | 06:12:00Z | Coastguard drone (decoy) | Shore launch from Sköldvik RPAS base; plan `FP-CG-2025-0418-007` active | MAC-PRV-COAST-02 (OUI allowlist hit) |
| 2  | 06:35:00Z | Coastguard drone | 9-min orbit over Kilpilahti polygon, RTB | `near_to` Kilpilahti; suppressed |
| 3  | 07:05:00Z | Coastguard drone | Lands at RPAS base; session ends | sensor `status=COMPLETED` |
| 4  | 11:48:30Z | MV AALLOTAR | Westbound GoF transit, SOG 11.2 kn, COG 268° | AIS dyn |
| 5  | 12:31:00Z | MV AALLOTAR | Reduces to 6.0 kn approaching loiter box | AIS dyn |
| 6  | 12:46:10Z | MV AALLOTAR | Loiter on-station, SOG 0.8 kn | AIS dyn; outside 12 NM |
| 7  | 12:58:00Z | Drone `5C:E2:8C:DD:EE:01` | Launch from AALLOTAR aft deck; alt 0→35 m | `launched_from` AALLOTAR |
| 8  | 12:58:40Z | RAD-PLN-01 | Plane radar acquires low-RCS track T-PLN-22871; offshore, no flight plan | `radar_detects` |
| 9  | 13:00:30Z | Drone | Climb to 110 m MSL; track 335° at 14 m/s | RAD-PLN-01 track stable |
| 10 | 13:04:12Z | MAC-PRV-COAST-01 | First drone-MAC hit, RSSI -78 dBm | `observed_at` |
| 11 | 13:07:55Z | Drone | Crosses Finnish coastline (south) MultiLineString | `near_to` coastline |
| 12 | 13:09:20Z | Drone | Enters Kilpilahti polygon; alt 108 m | `near_to` Kilpilahti |
| 13 | 13:09:48Z | MAC-PRV-COAST-02 | Strong drone-MAC hit, RSSI -61 dBm | `observed_at` |
| 14 | 13:10:00Z | Drone | Begin 14-min racetrack orbit over polygon | dwell-time accumulator starts |
| 15 | 13:14:30Z | RAD-DRN-PAT-01 | Border Guard patrol drone launched, vectors east | carries MAC-AIR-DRN-01 |
| 16 | 13:18:05Z | MAC-HEL-PORT-03 | Fringe drone-MAC hit, RSSI -84 dBm | `observed_at` |
| 17 | 13:23:40Z | Drone | Egress turn 155°, exit polygon | dwell-time stops (14m20s) |
| 18 | 13:27:10Z | Drone | Re-crosses coastline southbound | |
| 19 | 13:31:55Z | RAD-DRN-PAT-01 | Slow pass over AALLOTAR at 220 m AGL | airborne sensor on station |
| 20 | 13:32:07Z | MAC-AIR-DRN-01 | **Co-observation #1**: drone-MAC @ -58 dBm AND iPad-MAC @ -54 dBm in 5 s window | `co_observed` (smoking gun) |
| 21 | 13:32:42Z | MAC-AIR-DRN-01 | Co-observation #2, both MACs > -65 dBm | reinforces `co_observed` |
| 22 | 13:34:18Z | Drone | Land on AALLOTAR aft deck; RAD-PLN-01 track lost | `launched_from` confirmed by RTB |
| 23 | 13:35:00Z | MV AALLOTAR | Resume westbound 9.5 kn, COG 255° | AIS dyn |
| 24 | 13:40:00Z | Fusion engine | Composite `incident_score` = 0.86 → **ALERT** | threshold 0.70 |
| 25 | 13:41:00Z | Fusion engine | Decoy joint score = 0.22 → **SUPPRESSED** | allowlist + flight plan |

See `timeline.json` for machine-readable form.

## Signals

The four canonical signals (Σ weights = 1.0) — see `weights.json` and
`generators/scoring.py`:

| Signal | Weight | Source |
|---|---:|---|
| `co_observation_score`              | 0.45 | airborne dual-MAC capture at MAC-AIR-DRN-01 |
| `track_origin_offshore_anomaly_score` | 0.20 | RAD-PLN-01 + flight-plan registry |
| `mac_attribution_score`             | 0.20 | drone-MAC at MAC-PRV-COAST-02/-01 + MAC-HEL-PORT-03, geometry matches T-PLN-22871 |
| `spatial_proximity_infra_score`     | 0.15 | dwell time inside Kilpilahti recon polygon |

`incident_score = Σ wᵢ · signalᵢ`; alert when `incident_score ≥ 0.70`.

## KQL Query Sketches

### 1. `track_origin_offshore_anomaly_score`
```kusto
PlaneRadar
| where ts between (datetime(2025-04-18T11:00Z) .. datetime(2025-04-18T15:00Z))
| where kind == "airborne" and rcs_dbsm < -15
| extend offshore_km = geo_distance_2points(lon, lat, coast_nearest_lon, coast_nearest_lat) / 1000
| extend has_plan = iif(isnotempty(flight_plan_id), 1, 0)
| summarize anomaly = max(iif(offshore_km > 10, 1.0, 0.0) * (1 - has_plan)) by track_id
```

### 2. `co_observation_score` (the smoking gun)
```kusto
MacObservations
| where deviceId == "MAC-AIR-DRN-01"
| where averageSignalStrength > -65
| summarize macs = make_set(macAddress) by bin(processingTimestamp, 10s)
| extend has_drone = set_has_element(macs, "5c:e2:8c:dd:ee:01"),
         has_oper  = set_has_element(macs, "a4:83:e7:5c:9b:10")
| summarize co_obs = max(iif(has_drone and has_oper, 1.0, 0.0))
```

### 3. `mac_attribution_score`
```kusto
let target_geom = dynamic({"type":"Polygon","coordinates":[[[25.5050,60.3120],[25.5640,60.3120],[25.5640,60.2950],[25.5050,60.2950],[25.5050,60.3120]]]});
MacObservations
| where macAddress == "5c:e2:8c:dd:ee:01"
| extend in_geom = geo_point_in_polygon(SensorLon, SensorLat, target_geom)
| summarize attribution = todouble(countif(in_geom)) / todouble(count())
```

### 4. `spatial_proximity_infra_score` (dwell-time fraction)
```kusto
DroneTrack
| where track_id == "T-PLN-22871"
| extend in_kilpilahti = geo_point_in_polygon(lon, lat, Kilpilahti_polygon)
| summarize dwell_in = sumif(1, in_kilpilahti), total = count()
| extend proximity = todouble(dwell_in) / todouble(total)
```

### 5. Composite alerting
```kusto
let w = dynamic({"origin":0.20,"co_obs":0.45,"attrib":0.20,"proximity":0.15});
ScoreParts
| extend incident_score =
      todouble(w.origin)    * origin_score
    + todouble(w.co_obs)    * co_obs_score
    + todouble(w.attrib)    * attribution_score
    + todouble(w.proximity) * proximity_score
| where incident_score >= 0.70
| project ts, track_id, mmsi=230999401, incident_score
```

## MAC Fusion Narrative

- **Drone-MAC OUI rationale.** `5C:E2:8C` is a real DJI OUI; the suffix
  `:DD:EE:01` is synthetic. The vendor lookup resolves to `DJI` (visible in
  the CSV `deviceManufacturer` column) and triggers the
  "offshore + DJI + no plan" prior. The decoy uses real Parrot OUI
  `90:3A:E6` with synthetic suffix — same realism, opposite outcome.
- **Operator-iPad geometry on the ship.** The operator's iPad
  `A4:83:E7:5C:9B:10` stays on AALLOTAR's bridge/aft deck the entire
  scenario. It rides the ship's waypoints (via `MovingMacEmitter`) and so is
  audible to coastal sensors only briefly and weakly (the ship loiters
  ~6.4 NM offshore; MAC-PRV-COAST-01 hears it sub-floor). But the
  **airborne** sensor MAC-AIR-DRN-01 hears it strongly during the slow
  pass — because the patrol drone is within ~80 m of the ship at
  13:32:07 UTC.
- **Time-and-space alignment.** The drone Wi-Fi MAC and the operator iPad
  MAC are co-observed in the same **5-second processing window** at
  MAC-AIR-DRN-01 (`processingTimestamp` deltas < 2 s), at the same
  `deviceId`, both with RSSI > -65 dBm. Geometrically this can only happen
  if the drone is hovering/landing within tens of metres of the iPad — i.e.
  on AALLOTAR's deck.
- **Airborne smoking gun.**
  `co_observed(5C:E2:8C:DD:EE:01, A4:83:E7:5C:9B:10) @ MAC-AIR-DRN-01`
  happens twice (13:32:07Z and 13:32:42Z). Combined with
  `launched_from(T-PLN-22871, AALLOTAR)` inferred from RAD-PLN-01 (the
  airborne track originates **and** terminates at AALLOTAR coordinates),
  this closes the triangle **drone ↔ operator ↔ MMSI 230999401**. Without
  the airborne sensor, the coastal-only picture would be ambiguous
  (drone-MAC seen, operator-MAC barely seen).

## False-Positive Decoy Explanation

The coastguard inspection drone `90:3A:E6:11:22:33` flew the same Kilpilahti
polygon 06:35–07:05Z with very similar geometry, and is **observed at MAC-PRV-COAST-02 with stronger RSSI than the suspect** (≈ -59 dBm). The
detector suppresses it because:

1. **Filed flight plan** `FP-CG-2025-0418-007` covers 06:00–07:30Z over the
   polygon → `has_filed_flight_plan = 1` →
   `track_origin_offshore_anomaly_score = 0`.
2. **Shore origin**: launched from RPAS base (land), `offshore_km ≈ 0` →
   anomaly score 0 even without the plan.
3. **OUI allowlist**: Parrot OUI `90:3A:E6` is on the trusted-operator
   allowlist; the MAC session row is tagged `status=ALLOWLIST`.
4. **No ship co-observation**: airborne sensor MAC-AIR-DRN-01 never sees the
   decoy MAC together with any AALLOTAR-crew iPad MAC →
   `co_observation_score = 0.0`.

Decoy joint `incident_score ≈ 0.22` — well below the 0.70 alert threshold.

## Files Produced by `generate.py`

```
data/realtime/
  ais.ndjson                       — AIS dynamic (AALLOTAR + background fleet)
  ais_snapshot.geojson             — last-seen positions, FeatureCollection
  plane_radar.ndjson               — RAD-PLN-01 surface + air picture (incl. T-PLN-22871)
  drone_radar.ndjson               — RAD-DRN-PAT-01 patrol-drone radar plots
  mac.ndjson                       — All MAC observations (suspect, decoy, crew, background)
  mac.csv                          — Same data in the verbatim real-device CSV header
data/static/
  area_of_interest.geojson         — Bounding AOI
  sensors_used.geojson             — Subset of catalog sensors used in S5
  infrastructure_used.geojson      — Catalog infrastructure used in S5
  kilpilahti_recon_polygon.geojson — Spec polygon (vertices §6) annotated purpose=recon_target
  finnish_coastline_south.geojson  — MultiLineString synthesised approximation
  fi_territorial_sea_12nm.geojson  — 12 NM buffer polygon (offshore classification)
  s5_aallotar_track.geojson        — W1..W7 LineString with timestamps
  s5_drone_path_3d.geojson         — D1..D14 LineString with per-vertex alt
  s5_patrol_drone_path.geojson     — P1..P7 LineString
  rpas_base_skoldvik.geojson       — Decoy shore-launch Point
  decoy_flight_plan.geojson        — Decoy polygon coverage + FP-CG-2025-0418-007 metadata
data/historical/
  ais_baseline.ndjson              — 8 weeks ambient AIS in the AOI
  mac_baseline.ndjson              — 8 weeks consumer-OUI MAC background noise
  mac_baseline.csv                 — Same in CSV
  legitimate_drones.ndjson         — ~30 prior legit drone flights (timestamps, OUIs, summaries)
```

## Ingestion Notes

- **Time**: all timestamps UTC ISO 8601 with `Z`. MAC `ingestion_ts` is epoch ms.
- **MAC CSV header is VERBATIM** the real-device schema. The first line of every
  CSV file is a `#`-prefixed JSON comment carrying the disclaimer.
- **NDJSON streams** carry the disclaimer as the first line:
  `{"__meta__":"synthetic","disclaimer":"...","dataset":"S5/<stream>","version":"1.0"}`.
- **GeoJSON files** carry the disclaimer in a top-level `_meta` object.
- **Catalog compliance**: All sensor IDs (MAC-PRV-COAST-01/02, MAC-HEL-PORT-03,
  MAC-AIR-DRN-01, RAD-PLN-01, RAD-DRN-PAT-01, RAD-COAST-HEL-01) are taken
  verbatim from `catalogs/sensors.geojson`. AALLOTAR persona and crew
  device-MACs come from `catalogs/personas.json`. The drone and decoy MACs
  are defined under `personas.json:special_devices`.
- **Geographic note on Kilpilahti polygon**: `catalogs/infrastructure.geojson`
  contains a `site-kilpilahti` polygon at synthetic coordinates around
  26.01 °E that was a placeholder. The S5 spec recon polygon at
  25.5050–25.5640 °E, 60.2950–60.3120 °N is the geographically correct
  Kilpilahti location and is written to
  `data/static/kilpilahti_recon_polygon.geojson` as the authoritative S5
  recon target. Score helpers consume this polygon, not the legacy catalog
  placeholder.

## Historical Baseline

- **Window**: 2025-02-21 → 2025-04-17 (8 weeks) ending the day before S5.
- **Ambient AIS**: ~80 ships over the window across the GoF AOI.
- **Background MACs**: consumer OUIs only (Apple, Samsung, Xiaomi, Huawei,
  Lenovo, ZTE, Apple-BLE) at the 6 used coastal/port sensors.
- **Legitimate prior drone flights**: ~30 synthetic events written to
  `data/historical/legitimate_drones.ndjson` (one NDJSON record per flight),
  mixed vendor OUIs (DJI, Parrot, Autel, Skydio); all shore-launched, all
  with filed flight plans, **none** co-observed with any ship-bound crew
  MAC at MAC-AIR-DRN-01.

## Running

```pwsh
python scenarios/05-drone-launch-from-ship/generate.py
```

The script is idempotent — re-running overwrites the `data/` tree.
