Skip to content

Combat

Status: ๐Ÿšง Partial โ€” core PvE/PvP combat resolver, shield regen, fleet battles, and sector retreat shipped; large-scale combat and several weapon profiles remain design-only. (re-verified 2026-08-21 vs Sectorwars2102 46bce720.)

How fights happen โ€” from a one-on-one dogfight to a team-scale fleet battle. Numbers below come from the implementation.

Scope and target classifications

Combat targets, in code: - Ship (PvP or vs hostile NPC) โ€” combat_service.attack_player. - Sector drones โ€” combat_service.attack_sector_drones. - Planet โ€” combat_service.attack_planet. - Port / station โ€” combat_service.attack_port. - Fleet vs fleet โ€” fleet_service.py.

Initiating combat costs turns; defending is free. Defender-side cost is read from the defender ship type's ShipSpecification.attack_turn_cost โ€” meaning it is intentionally expensive to attack tiny targets like an escape pod (which is set to a very high attack cost to discourage pod-killing).

Default turn costs (combat_service.py):

Action Turns
Attack player ship attack_turn_cost of defender's ship type, min 2
Attack sector drones 2
Attack planet 3
Attack port 3
Defend 0

Combat resolution model

The resolver is in services/gameserver/src/services/combat_service.py (~1940 lines; entry points _resolve_ship_combat, _resolve_drone_combat, _resolve_planet_combat, _resolve_port_combat).

Ship-vs-ship

Inputs: - Ship hull and shield values (per ship row). - Drone counts (Player.attack_drones, Player.defense_drones). - Ship type matchup multiplier (SHIP_COMBAT_MODIFIERS): - DEFENDER vs CARGO_HAULER = 1.5ร— - DEFENDER vs LIGHT_FREIGHTER = 1.3ร— - FAST_COURIER vs CARRIER = 0.7ร— - SCOUT vs CARRIER = 0.5ร— - CARRIER vs SCOUT = 1.8ร— - CARRIER vs FAST_COURIER = 1.5ร— - COLONY vs DEFENDER = 0.5ร— - Default weapon by ship (SHIP_DEFAULT_WEAPONS): Scout uses EMP, Defender uses Plasma, Carrier uses Missile, Warp Jumper uses Plasma, all others use Laser. Per-ship defaults are also surfaced in ./ship-roster.md#combat-sensor-stats (Combat & sensor stats table). - Weapon profile (WEAPON_TYPES):

Weapon Base damage vs Shields vs Hull Description
EMP 0.5 2.0 0.3 Electromagnetic pulse, devastating to shields
Laser 1.0 0.8 1.0 Standard energy weapon
Missile 1.5 0.6 1.5 Physical projectile, bypasses some shields
Plasma 1.2 1.2 0.9 High-energy plasma bolts

The four weapon types pair with each ship's combat archetype: - EMP excels at shield-stripping (2.0ร— vs shields) but tickles hull (0.3ร— vs hull); Scout's EMP suits the "disable, don't kill" recon role. - Laser is the balanced all-purpose weapon; civilian and utility hulls default to it. - Missile is the hull-killer (1.5ร— damage, 1.5ร— vs hull) but weaker against shields; Carrier's Missile pairs heavy ordnance with its drone bay. - Plasma is the well-rounded mid-tier (1.2ร— across the board, slightly weaker vs hull); Defender and Warp Jumper default to it.

Damage stack (canonical โ€” full order-of-operations in ../../SYSTEMS/combat-resolver.md#damage-stack-order-of-operations):

1. base_damage = weapon.base_damage
                 ร— attack_drones_modifier        (+5% per 10 drones)
                 ร— ship-vs-ship matchup modifier
                 ร— sector modifier               (nebula, radiation, etc.)
                 ร— (1 + rank.combat_bonus / 100) (per-rank percent scalar)

2. shield_hit  = min(base_damage, defender.shields)
                 ร— (1 - defender.shield_resistance)
                 ร— weapon.shield_effectiveness

3. residual    = base_damage - min(base_damage, defender.shields)   (pre-resistance)
   hull_hit    = residual
                 ร— (1 - defender.armor_rating)
                 ร— weapon.hull_effectiveness
                 ร— (1 - defender.defense_drones_modifier)            (-5% per 10 drones)

4. critical    = (RNG < 0.05) ? hull_hit ร— 0.5 : 0

5. defender.shields -= shield_hit
   defender.hull    -= hull_hit + critical

Shields absorb first (capped at the shield pool), and the pre-resistance residual is what feeds hull damage โ€” so a shield-resistance fraction never leaks across to hull when shields would have absorbed everything.

defender.shield_resistance and defender.armor_rating are Floats in [0.0, 1.0] โ€” real columns directly on Ship (models/ship.py, not inside the combat JSONB), seeded from ShipSpecification at ship creation. rank.combat_bonus is a per-rank percent scalar applied to base damage (Spacer +1% up to Fleet Admiral +40%, see ./ranking.md#military-rank).

Round-by-round drone attrition is rolled stochastically; outcome is one of ATTACKER_VICTORY, DEFENDER_VICTORY, DRAW. Logged to CombatLog.

Status: โœ… Shipped โ€” round damage depletes Ship.combat shields first then overflows into hull, destruction fires on the unrounded hull <= 0, and attrition persists on both ships (flag_modified) even when nobody dies. The attack-drones +5% per 10 modifier applies in both directions, the hull-ratio escape valve is live, escape pods are rejected as targets (S-V1), and a self-attack guard holds. โœ… Between-battle shield regeneration (WO-SR1) is also shipped as advance-on-read (not a background scheduler): _ensure_combat_state (services/combat_service.py:3014) calls _apply_shield_regen (:3084) at engagement start, reading shield_recharge_rate seeded from ShipSpecification (ship_service.py:126 / _ensure_combat_state :3039), capped per window by SHIELD_REGEN_MAX_CREDIT_HOURS=24.0 (:742), anchor SHIELD_REGEN_ANCHOR_KEY (:741); hull stays repair-only. Regression pin: tests/unit/test_shield_regen_sr1.py. Corrected 2026-08-04 โ€” both previously-flagged stubs are actually โœ… shipped, not deferred: the sector modifier term is read via _sector_combat_modifier (WO-CR1, combat_service.py:3286, mapping Sector.type through SECTOR_COMBAT_MODIFIERS โ€” NEBULA cuts damage dealt, other types no-op 1.0) and multiplied into both attacker/defender damage rolls (:3515, :3623). shield_resistance/armor_rating are seeded per-ship-type from ship_specifications_seeder.py (non-zero for most types โ€” e.g. Cargo Hauler 0.03/0.05), copied onto Ship at creation (ship_service.py:139-140, npc_spawn_service.py:424-425, first_login_service.py:1853-1854), and read (not hardcoded 0) via getattr with a 0.0 fallback used only as a defensive default, clamped by _resistance_fraction.

Sector drones

Defender drones get a +5% bonus over ship-deployed drones (SECTOR_DEFENSE). Drone-vs-drone exchanges resolve probabilistically until one side hits zero; survivor keeps remaining drones.

Planet assault

Assault is rejected if attacker is the owner, planet has no owner, or attacker is docked/landed. Resolution incorporates planet.defense_level, deployed drones, citadel-level defenses (turrets, shields, orbital platforms, rail guns โ€” see planets/defense.md). On capture, ownership transfers atomically.

Port assault

Port defenses scale with port class โ€” Class 1: 50 drones; Class 2: 100; Class 3: 200; Class 4: 300 + auto-turrets; Class 5: 500 + advanced grid. Classes outside that table (Class 0 and Class 6โ€“11) borrow the Class-5 profile verbatim โ€” ratified in DECISIONS.md station-defense-class-0-6-11-drone-tiers. Ports are formidable fixed installations: a regenerating shield over deep hull armor plus strong defensive fire and point-defense that shreds drone swarms. Capture / disable is deliberately unreachable in the shipped station-defense kernel (per-round hull damage ceiling vs hull_armor floor; attack_port route unwired / 501). ๐Ÿšง Partial โ€” a future Max-blessed takeover design may revisit capture; today the design is deterrence, not port-taking.

Escape mechanics

Players can attempt to flee. Success is a function of: - Ship type โ€” Fast Courier and Scout get a flat boost (FAST_ESCAPE_SHIP_TYPES). - Remaining hull (lower hull = harder). - Distance to sector edge (closer = better). - Pursuer ship class (heavier ships pursue worse).

If a ship is destroyed: 1. Player is auto-ejected to escape pod. 2. Cargo lost. Credits retained. 3. If insured, payout based on InsuranceType (see ships.md). 4. Combat log records cause and salvage.

Sector retreat (out-of-combat)

โœ… Shipped. Distinct from in-combat Escape mechanics above โ€” POST /api/v1/combat/retreat lets a player attempt to flee their current sector to a random warp-connected sector at any time (not gated on an active combat round), for a flat 3-turn cost regardless of outcome. Blocked while docked or landed. Escape chance: clamp(10, 90, 50 + speed_bonus + type_bonus) โ€” a 50% base, min(25, int(ship.current_speed ร— 10)) speed bonus, and a flat +15% for FAST_COURIER / SCOUT_SHIP hulls. On success the player lands in a random connected sector (bidirectional warps only when traversing in reverse); on failure they stay put, turns still spent. A sector with no connected warps at all reports failure and still charges the turn cost. (services/gameserver/src/api/routes/player_combat.py retreat_from_sector.)

Post-combat hooks

combat_service.attack_player fires the following on resolution (best-effort; failures are logged but don't roll back the fight):

  • Ranking โ€” winner gets rank points via RankingService.calculate_combat_points (see ranking.md).
  • ARIA consciousness โ€” winner.aria_total_interactions += 1. Thresholds: 50 โ†’ L2 (1.1ร— turn regen), 150 โ†’ L3 (1.2ร—), 400 โ†’ L4 (1.35ร—), 1000 โ†’ L5 (1.5ร—).
  • Medals โ€” MedalService.check_combat_medals(winner_id, victory_count) (Bronze Cluster at 100 wins, Silver at 1k, Quantum Cross at 10k).
  • Personal reputation (PersonalReputationService):
  • Killed a defender with active bounties โ†’ +100, "defeat_bounty_target".
  • Killed an innocent (no bounty) โ†’ โˆ’100, "attack_innocent".
  • Killed a defender in an escape pod โ†’ additional โˆ’500, "kill_escape_pod".
  • Successfully defended โ†’ +50, "defend_against_attacker".
  • Bounty โ€” BountyService.collect_bounty if the defender had bounties placed (see bounties.md).

Drones

Drone type Cost (cr) Notes
Attack 1,000 Offensive primary
Defense 1,200 Damage reduction

Every 10 attack drones = +5% combat effectiveness; every 10 defense drones = โˆ’5% incoming damage. Drones prefer engaging enemy drones first.

Code: services/gameserver/src/services/drone_service.py, model services/gameserver/src/models/drone.py.

Weapons

combat_service.py:WEAPON_TYPES covers laser/plasma/missile/emp plus ๐Ÿšง Partial autocannon/particle/torpedo (profile magnitudes NO-CANON launch values; selectable via autocannon_mount / particle_projector / torpedo_bay equipment that set weapon_type only โ€” ship-systems.md ยง2.6 still forbids raw-firepower modules). Still ๐Ÿšง Planned: tractor (combat-face partial elsewhere), mining weapon profile.

Tractor weapon mode

๐Ÿšง Partial, corrected 2026-08-04. The tractor weapon entry is the combat-side face of the dual-use Tractor Beam equipment slot (the tow-side use is in ./ships.md#tractor-beam-tow-operations). A single-shot MVP is โœ… shipped: if the attacker's ship carries weapon_mode="tractor" (and isn't actively towing โ€” mutually exclusive per the Mutual exclusion row below), the defender's flee/escape roll for that resolution is forced to 0 (combat_service.py:_resolve_ship_combat, "WO-BC tractor escape-suppression"). Deals no damage, matching the Damage row below. Still ๐Ÿ“ Design-only: the multi-round 3-round-window lock, the 50% speed debuff, and additive multi-attacker stacking below โ€” these are deliberately deferred as part of the larger "Combat v2 โ€” spatial/positional combat" initiative (DECISIONS.md item 4: "the multi-round combat-engagement model, the full tractor lock/stacking"), not a simple follow-on to build piecemeal ahead of that model landing.

Property Value
Damage 0 (no hull or shield damage)
Effect Locks target; reduces target's effective current_speed by 50% for 3 combat rounds
Flee suppression Locked target cannot succeed at flee actions while the lock holds
Counterplay Target breaks the lock by destroying the tractor-equipped ship, by being towed out of weapons range by an ally, or by waiting out the 3-round window
Stack Multiple tractor locks from different attackers stack the speed debuff additively up to a 90% floor
Mutual exclusion A ship in active tow operation cannot also fire tractor in weapon mode (the equipment can only do one thing at a time)

Tractor is intended as a tactical control weapon โ€” denying escape, holding a flagship in place for focused fire, or pulling a Warp Jumper out of jump-cooldown range. It does no damage on its own; effectiveness depends on coordinated team fire.

Fleet battles

services/fleet_service.py (~908 lines) handles team fleets: - Roles (FleetRole): flagship, attacker, defender, support, scout. At most one flagship per fleet; flagship destruction triggers a one-shot โˆ’30 morale penalty. - Formations (single string field on the fleet, applied to all members): standard ร—1.00/ร—1.00, aggressive (Wedge) ร—1.15/ร—0.85, defensive ร—0.85/ร—1.15, flanking (Offensive) ร—1.10/ร—0.90, turtle (Scatter) ร—0.60/ร—1.40 โ€” values are (attack_modifier, defense_modifier). - Stats aggregated from member ships' combat JSONB via _recalculate_fleet_stats. - Status machine (FleetStatus): forming โ†’ ready โ†’ in_battle โ†’ retreating โ†’ disbanded (with reversible transitions back to ready on battle end). Ships can only be added while forming or ready; movement is blocked while in_battle.

See fleet-tactics.md for the full breakdown of role bonuses, morale and supply mechanics, coordination scaling, and battle-resolution math.

Battle records are persisted as FleetBattle + FleetBattleCasualty rows.

Status: โœ… Shipped โ€” fleet response builders in services/gameserver/src/api/routes/fleets.py read the real player-name attribute (fleet.commander.username); the fleet.commander.name AttributeError that broke six response sites is resolved.

๐Ÿšง Planned: large-scale (100v100+) tier. Current fleet system handles team-vs-team coordinated combat at moderate scale; the items below are ๐Ÿ“ Design-only โ€” captured here as launch-direction intent without committed timing.

๐Ÿ“ Design-only โ€” large-scale combat ambitions:

  • Multi-team battles โ€” up to 3โ€“8 teams in a single engagement (current FleetBattle model is two-fleet only; needs a multi-side battle model).
  • Battle scale taxonomy โ€” named tiers for grouping engagement size (e.g. SKIRMISH 2โ€“5 players / 10โ€“50 units; ENGAGEMENT 6โ€“15 / 51โ€“200; CAMPAIGN 16โ€“30 / 201โ€“1,000; MASSIVE_WAR 31โ€“75 / 1,001โ€“5,000; LEGENDARY 76+ / 5,000+). Drives matchmaking, resource budget, performance targets.
  • Hot-join / late-arrival โ€” players warp in at rally points after a battle starts, with an escalating join cost the longer the battle has been running.
  • Reinforcement waves โ€” SOS broadcast triggers staggered re-entry (e.g. every 5 minutes) for team-mates not initially engaged; escalation spiral as more reinforcements arrive.
  • Adaptive UI by chaos level โ€” interface complexity scales with sector occupancy: cleaner views at low player counts (1โ€“10), aggregated readouts at medium (11โ€“25), command-only views at high (26โ€“50), AI-assisted summaries at extreme (51+).
  • Team home-base infrastructure โ€” team-owned forward bases that produce drones on a timer (e.g. 50 drones/hour), construct ships, host garrisons, and run automated supply convoys to active battle sectors.
  • Spectator viewing modes โ€” beyond the basic combat:{combat_id} topic stream, richer modes including god view, commander cam, individual unit tracking, and cinematic mode.
  • Battle chronicles โ€” long-term lore-persistence layer above CombatLog: hall of fame for participants, decisive-moment highlight reels, sector monuments commemorating major battles, ship naming for heroes, AI-generated battle narratives.
  • Tournament / event system โ€” scheduled player tournaments, role-play wars, charity battles, and developer challenges with bracket structure, rewards, and broadcast hooks.
  • Behavioral cheat detection โ€” automated analytics for impossible actions (e.g. simultaneous moves), statistical anomalies in win rates, multi-account coordination patterns, and timing-based bot detection. Distinct from the existing rate-limiting and server-side validation layer.
  • Combat performance SLOs โ€” explicit turn-resolution targets per battle size (e.g. 100v100 in <3s, 500v500 in <8s), concurrent-battle capacity per gameserver, and degradation behavior under load. Drives infrastructure scaling decisions.

These items are captured as design intent for future ADRs; none commit to timing or implementation approach.

Sector control & deployable assets

  • Deployed drones can be left in a sector to defend it (one player per sector). Deployed drones get +5% effectiveness vs ship-based.
  • Mines damage hostile entrants. โœ… Armored mines shipped 2026-06-14 (5,000 cr): bought at any mine_dealer/spacedock armory, laid in open space (POST /armory/deploy) into the sector's defenses JSONB; a hostile ship entering takes 200 hull from one mine (consumed per entry), floored at 1 hull (cripple, not destroy) โ€” see ADR-0083. Same-team fields are friendly; a sector holds one commander's field at a time. โœ… Limpet mines shipped (2,000 cr, tracking/surveillance): catalogued at armory.py:50-55, purchasable via POST /armory/purchase and deployable via POST /armory/deploy (armory.py:311-396); movement attaches trackers and pushes WS "limpet_signal" to the owner (movement_service.py:2489-2579).
  • Rented NPC defenders โ€” design only, not implemented in code.

See galaxy/sectors.md.

Player-facing affordances

  • โœ… Combat engage: target pick + ENGAGE COMBAT / CHANGE TARGET (CombatInterface.tsx on origin/feat 46bce720). Stats shown are Attack/Defense ratings + drone count. Outcome is COMBAT COMPLETE + this-fight round replay. ๐Ÿ“ Hull/shield/cargo bars, weapon list, and Attack / Defend / Evade / Flee round-action buttons are not on this HUD (combatAPI.retreat has zero UI callers; leftover .action-btn.retreat CSS is not a control).
  • ๐Ÿ“ Turn-cost preview before initiating โ€” Design-only on the pre-engage panel. GS turn-cost table above stays โœ….
  • ๐Ÿšง Per-fight log: GET /{combatId}/status + CombatInterface round replay for the engagement just resolved. โœ… GS tip-PRESENT player history list โ€” GET /api/v1/combat/history (get_combat_history on player_combat.py, PR #756 tip-ancestor) returns the caller's queryable CombatLog rows (limit/offset). Admin get_combat_logs is not this affordance. ๐Ÿšง PC Soft-HOLD Fibril #1180 / PR #689 tip-pending โ€” no player-client CombatHistory browse UI on tip (git grep CombatHistory under player-client empty).

Source map

Topic Path
Player-vs-player resolver services/gameserver/src/services/combat_service.py
Drone combat helpers services/gameserver/src/services/combat_service.py:_resolve_drone_combat
Player combat orchestration services/gameserver/src/services/combat_service.py (tip 46bce720 โ€” there is no player_combat_service.py; attack/retreat orchestration lives here + player_combat.py routes)
Fleet battles services/gameserver/src/services/fleet_service.py
Combat models services/gameserver/src/models/combat.py, combat_log.py
Drones services/gameserver/src/models/drone.py, services/gameserver/src/services/drone_service.py
Combat REST routes services/gameserver/src/api/routes/player_combat.py (tip โ€” no api/routes/combat.py)
Admin combat tools services/gameserver/src/api/routes/admin_combat.py
Ranking hook services/gameserver/src/services/ranking_service.py
Reputation hook services/gameserver/src/services/personal_reputation_service.py
Bounty hook services/gameserver/src/services/bounty_service.py
Medal hook services/gameserver/src/services/medal_service.py