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.combatshields first then overflows into hull, destruction fires on the unroundedhull <= 0, and attrition persists on both ships (flag_modified) even when nobody dies. The attack-drones+5% per 10modifier 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, readingshield_recharge_rateseeded fromShipSpecification(ship_service.py:126/_ensure_combat_state:3039), capped per window bySHIELD_REGEN_MAX_CREDIT_HOURS=24.0(:742), anchorSHIELD_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: thesector modifierterm is read via_sector_combat_modifier(WO-CR1,combat_service.py:3286, mappingSector.typethroughSECTOR_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_ratingare seeded per-ship-type fromship_specifications_seeder.py(non-zero for most types โ e.g. Cargo Hauler 0.03/0.05), copied ontoShipat creation (ship_service.py:139-140,npc_spawn_service.py:424-425,first_login_service.py:1853-1854), and read (not hardcoded 0) viagetattrwith a0.0fallback 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_bountyif 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.pyread the real player-name attribute (fleet.commander.username); thefleet.commander.nameAttributeErrorthat 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
FleetBattlemodel 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'sdefensesJSONB; 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 atarmory.py:50-55, purchasable viaPOST /armory/purchaseand deployable viaPOST /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.tsxon origin/feat46bce720). 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.retreathas zero UI callers; leftover.action-btn.retreatCSS 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_historyonplayer_combat.py, PR #756 tip-ancestor) returns the caller's queryableCombatLogrows (limit/offset). Adminget_combat_logsis not this affordance. ๐ง PC Soft-HOLD Fibril#1180/ PR #689 tip-pending โ no player-client CombatHistory browse UI on tip (git grep CombatHistoryunderplayer-clientempty).
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 |