Skip to content

Drones

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 — DroneService.deploy_drone; POST /drones/{drone_id}/deploy (single drone) and POST /drones/deploy (batch: deploy N of the player's undeployed drones to one sector, clamping to the cap and returning the real deployment-row ids so each can be recalled).

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: 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.

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

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)