Skip to content

Drones

Status: 🚧 Partial — per-drone lifecycle, batch + single-drone deploy, recall, repair, and upgrade routes shipped; Admin UI PATCH/DELETE on DroneOperationsTab.tsx tip-PRESENT; player recall UI tip-PRESENT via DroneFleetPanel. (re-verified 2026-09-02 vs Sectorwars2102 feat 57243131e. Tip delta from prior 70f1d3cc4 stamp: densify merge train through #1367 — no drone-kernel change.)

Drones are individually-tracked deployable units. Each drone is its own row with a type, a level, combat stats, and a deployment state — distinct from the bulk attack/defence drone counters that feed the combat damage roll (those are covered in combat.md#drones). This doc covers the per-drone lifecycle: how a drone is created, deployed to a sector, recalled, repaired, and upgraded, the five drone types and their base stats, and the per-player capacity that anti-exploit enforcement holds the fleet to.

A drone is owned by a player and may optionally be assigned to a team. It lives somewhere in a small state machine and occupies a capacity slot derived from the player's current ship.

Drone types

Five types exist, each with a distinct stat profile and ability pair. Base stats are stamped onto a drone at creation from its type and never change except by levelling (see Upgrade).

Type Health Attack Defence Speed Abilities
attack 80 20 5 1.5 precision_strike, rapid_fire
defense 150 8 20 0.8 shield_boost, area_defense
scout 60 5 8 2.0 enhanced_sensors, stealth
mining 100 3 10 1.0 resource_extraction, cargo_boost
repair 90 2 12 1.2 repair_beam, shield_recharge

Attack drones trade durability for the highest damage and speed; Defence drones are the durable area-control anchor; Scouts are fast and fragile; Mining and Repair are the utility profiles. Abilities are stored as a comma-separated list on the drone and surface as the type's tactical role. An unrecognized type falls back to the defense profile.

✅ Shipped — DroneType enum (attack, defense, scout, mining, repair) in services/gameserver/src/models/drone.py; base stats in DroneService._get_base_stats (services/gameserver/src/services/drone_service.py). GET /drones/types returns the type catalogue with descriptions and base stats.

Lifecycle states

A drone is always in exactly one status. DroneStatus values: idle, deployed, combat, returning, destroyed, damaged.

        create
          |
          v
        IDLE  <----- recall -----+
          |                      |
        deploy                   |
          |                      |
          v                      |
       DEPLOYED ----------------+
          |  \
          |   \--(health < 30% max)--> DAMAGED --(repair > 30%)--> DEPLOYED
          |
       (health -> 0)
          v
       DESTROYED   (terminal)
  • IDLE — created but not deployed to any sector. The terminal resting state after a recall.
  • DEPLOYED — stationed in a sector, occupying a deployment slot.
  • DAMAGED — a deployed drone whose health has dropped below 30% of its maximum; repairing it back above the 30% threshold returns it to DEPLOYED.
  • DESTROYED — health reached zero. Terminal; a destroyed drone no longer occupies a capacity slot and cannot be deployed, repaired, or recalled.

✅ Shipped — status transitions in models/drone.py (Drone.take_damage, Drone.repair) and services/drone_service.py. A recall sets the drone to IDLE and clears its sector synchronously — recall is instantaneous in this API, so a recalled drone is immediately available rather than stranded in transit.

Per-player capacity (anti-exploit enforcement)

A player cannot create or field more drones than their current ship can carry. The cap is ShipSpecification.max_drones for the player's active ship — the same source the armory loadout caps read. A player with no active ship has a cap of zero: you need a ship to carry drones.

Per-class capacities follow the Drones column of the ship roster (for example Defender 6, Carrier 12, Scout 1, Light Freighter 2, hulls with no drone bay 0). The Drone Bay ship module raises this capacity by +2 per level — see ship-systems.md#27-drone-bay.

Two separate caps are enforced, both atomically:

  • Creation cap — the count of a player's non-destroyed drones (every drone that occupies a slot, regardless of status) may not exceed max_drones. Creating one past the cap is rejected.
  • Deployment cap — the count of a player's fielded drones (status deployed or combat) may not exceed max_drones. Deploying one past the cap is rejected. The drone being deployed is excluded from its own tally, so re-deploying an already-fielded drone is a no-op against the cap.

Both checks lock the owning player row before counting, so the check-then-write is atomic per player: two concurrent creates (or two concurrent deploys) serialize on the lock and cannot both read an under-cap count and then both write past the cap. The deploy path recalls any prior active deployment of the same drone within the same transaction, holding the lock continuously from the cap check through the single commit so no concurrent deploy can slip past the cap in the gap.

✅ Shipped — DroneService.create_drone and DroneService.deploy_drone each acquire SELECT ... FOR UPDATE on the player row before the cap check; cap resolved by _get_max_drones (reads ShipSpecification.max_drones for the current ship), live count by _count_live_drones (creation) and _count_deployed_drones (deployment) in services/gameserver/src/services/drone_service.py.

The management loop

Create

Creating a drone validates the type, enforces the creation cap, stamps the type's base stats onto a new drone at level 1, and assigns it to the player (and optionally a team). A new drone starts IDLE.

✅ Shipped — DroneService.create_drone; POST /drones.

Deploy

Deploying sends a drone to a sector with a deployment type (defense, patrol, mining, etc.) and an optional target. It enforces the deployment cap, recalls any prior active deployment for that drone, flips the drone to DEPLOYED with the sector and a deploy timestamp, and writes a DroneDeployment record marked active. A destroyed drone cannot be deployed.

✅ Shipped (HTTP) — DroneService.deploy_drone ✅ Shipped (service layer). Tip HTTP (origin/feat 2d64087f4): POST /drones/deploy (batch) ✅ Shipped — deploy N undeployed drones to one sector. POST /drones/{drone_id}/deploy (single-drone) ✅ tip-PRESENT — route drones.py:256 (PR #685 / LEG-364 tip-ancestor on 2d64087f4).

Recall

Recalling marks the drone's active deployment inactive (stamping a recall time), then returns the drone to IDLE and clears its sector. A destroyed drone's status is left unchanged. Recall is instantaneous.

✅ Shipped — DroneService.recall_drone; POST /drones/{drone_id}/recall and DELETE /drones/{deploymentId}/recall (by deployment id).

Repair

Repair restores a given amount of health, capped at the drone's maximum. If the drone was DAMAGED and repair lifts it back above 30% of max health, it returns to DEPLOYED. A destroyed drone cannot be repaired.

✅ Shipped — DroneService.repair_drone (uses Drone.repair); POST /drones/{drone_id}/repair.

Upgrade

Upgrading raises the drone one level. Per level: max health, attack power, and defence power each increase by 10% (×1.1), speed increases by 5% (×1.05), and the drone is fully healed to its new maximum.

✅ Shipped — DroneService.upgrade_drone; POST /drones/{drone_id}/upgrade.

Sector and team deployment

Deployed drones provide area control in their sector. Drones can be queried by the sector they occupy, by their owning player, and by their assigned team.

  • By sector — all non-destroyed drones present in a sector. This is tactical intelligence (it reveals players' military positions), so the endpoint requires authentication rather than being anonymously enumerable.
  • By player — a player's own drones, optionally including destroyed ones.
  • By team — drones assigned to a team; access requires membership of that team.

A DroneDeployment record tracks each deployment for history and sector control: which drone, player, and sector; deploy and recall times; whether it is active; the deployment type and optional target; and per-deployment tallies (enemies_destroyed, resources_collected, damage_prevented).

✅ Shipped — DroneService.get_sector_drones, get_player_drones, get_team_drones, get_drone_deployments; routes GET /drones/sector/{sector_id}, GET /drones/, GET /drones/team/{team_id}, GET /drones/deployments, GET /drones/deployed. DroneDeployment model in services/gameserver/src/models/drone.py.

Combat record

Each drone carries cumulative combat stats — kills, damage_dealt, damage_taken, battles_fought — and a DroneCombat row records each encounter (participants, sector, rounds, winner, per-side damage, and a combat log). Drone-vs-drone exchanges and the sector-defence bonus for deployed drones are resolved by the combat system; see combat.md#sector-drones.

✅ Shipped — combat stat columns and DroneCombat model in services/gameserver/src/models/drone.py.

Administration

Administrators have full control over every drone on the gameserver admin API: list with filters (player, team, sector, type, status), inspect a drone with its recent deployments and combats, edit any attribute, delete a drone, force-recall a deployed drone, restore a destroyed drone to active, and pull a per-sector drone summary grouped by player and team. A game-wide statistics endpoint reports totals by status and type, average level, and aggregate kills and battles.

✅ GS Shipped — services/gameserver/src/api/routes/admin_drones.py (routes under /admin/drones, gated to admin users).

✅ Admin UI PATCH/DELETE tip-PRESENT (#1759 cleared): services/admin-ui/src/components/combat/DroneOperationsTab.tsx on tip 2d64087f4 — edit save api.patch(\/api/v1/admin/drones/${editingId}`)(:337); deleteapi.delete(`/api/v1/admin/drones/${drone.id}`)(:362); VitestDroneOperationsTab.test.tsx` pins patch/delete + 403/429 honesty.

Player-facing affordances

  • ✅ DroneFleetPanel + StatusBar mount — DroneFleetPanel.tsx overlay from the StatusBar drones chip (StatusBar.tsx:496; origin/feat 96c968ad). Distinct from planetary DefenseConfiguration fighters labeled "Drones" (panel header comment warns).
  • ✅ Commission typed drone — droneFleetAPI.create in the panel's Commission drone section (DroneFleetPanel.tsx; client exported from services/player-client/src/services/api.ts).
  • ✅ Batch deploy undeployed pool — count-based batch via combatAPI.deployDrones → POST /drones/deploy (not a per-type picker; panel copy states this). DroneFleetPanel.tsx.
  • ✅ Per-drone repair + upgrade — droneFleetAPI.repair / droneFleetAPI.upgrade (DroneFleetPanel.tsx).
  • ✅ Recall deployed drones — DroneFleetPanel.tsx:192-197 onRecallDeployment → combatAPI.recallDrones(deploymentId); per-drone droneFleetAPI.recall; mounted from StatusBar drones chip (StatusBar.tsx:496); Vitest recall failure honesty in DroneFleetPanel.test.tsx (tip 2d64087f4). (re-verified 2026-08-31.)
  • ✅ Single-drone deploy HTTP + UI tip-PRESENT — GS POST /drones/{drone_id}/deploy (drones.py:256); player per-drone Deploy this drone button in DroneFleetPanel.tsx:425-436 → droneFleetAPI.deployOne (api.ts:248); Vitest deploy-one path on tip 2d64087f4.

Source map

Concern Path
Drone service (create/deploy/recall/repair/upgrade, caps, queries) services/gameserver/src/services/drone_service.py
Base stats per type DroneService._get_base_stats
Cap resolution + atomic enforcement DroneService._get_max_drones, _count_live_drones, _count_deployed_drones
Models (Drone, DroneDeployment, DroneCombat) + enums (DroneType, DroneStatus) services/gameserver/src/models/drone.py
Player drone API (~14 endpoints) services/gameserver/src/api/routes/drones.py
Admin drone API services/gameserver/src/api/routes/admin_drones.py
Per-ship capacity source services/gameserver/src/models/ship.py (ShipSpecification.max_drones)
DroneFleet panel (typed-drone lifecycle UI) services/player-client/src/components/drones/DroneFleetPanel.tsx
StatusBar drones-chip mount services/player-client/src/components/layouts/StatusBar.tsx
Typed-drone + batch-deploy API client services/player-client/src/services/api.ts (droneFleetAPI, combatAPI.deployDrones, combatAPI.recallDrones)
Panel + API contract tests services/player-client/src/components/drones/__tests__/DroneFleetPanel.test.tsx, services/player-client/src/services/__tests__/droneFleetAPI.test.ts