Purpose: AI-readable reference for Structs combat. Consolidates formulas, requirements, outcomes, and edge cases.
| Action | Message | Description |
|---|---|---|
| Attack | struct-attack |
Direct struct-to-struct combat |
| Defend | struct-defense-set |
Set struct to defense mode (blocking) |
| Raid | planet-raid-complete |
Planet assault; steals unrefined ore |
Combat in Structs revolves around four ambits. Each struct operates in one ambit, and each weapon can only target specific ambits. This creates a strategic mesh where fleet composition and positioning determine what you can hit and what can hit you.
| Ambit | Reach Bit Value |
|---|---|
| none | 1 |
| Water | 2 |
| Land | 4 |
| Air | 8 |
| Space | 16 |
| local | 32 |
The four combat ambits are Water/Land/Air/Space; none (1) is a placeholder and local (32) is the Command Ship’s “current ambit” flag (its weapon reaches whatever ambit it currently occupies).
Two ambit numberings — do not conflate. The bit values above are the reach bitmask (bit = 1 << enum) used for StructType.possibleAmbit and weapon-reach fields. Transaction messages (struct-build-initiate, struct-move) and a struct’s stored operatingAmbit use a different enum: none=0, water=1, land=2, air=3, space=4, local=5. When building or moving, pass the enum (the CLI takes the name space|air|land|water), not the bitmask number. See building.md — Ambit Encoding and api/integration-notes.md — Ambit.
Which ambits each struct’s primary weapon can hit:
| Struct | Lives In | Targets (Primary) | Targets (Secondary) |
|---|---|---|---|
| Command Ship | Any (movable) | Local only (flag 32 = current ambit) | — |
| Battleship | Space | Land, Water (armour-piercing) | Space |
| Starfighter | Space | Space | Space |
| Frigate | Space | Space, Air | — |
| Pursuit Fighter | Air | Air | — |
| Stealth Bomber | Air | Land, Water | — |
| High Altitude Interceptor | Air | Space, Air | — |
| Mobile Artillery | Land | Land, Water | — |
| Tank | Land | Land | — |
| SAM Launcher | Land | Space, Air | — |
| Cruiser | Water | Land, Water | Air |
| Destroyer | Water | Air, Water | — |
| Submersible | Water | Space, Water | — |
Which structs can attack into each ambit:
| Target Ambit | Threatened By |
|---|---|
| Space | Battleship (secondary), Starfighter, Frigate, High Altitude Interceptor, SAM Launcher, Submersible |
| Air | Frigate, Pursuit Fighter, High Altitude Interceptor, SAM Launcher, Cruiser (secondary), Destroyer |
| Land | Battleship, Stealth Bomber, Mobile Artillery, Tank, Cruiser |
| Water | Battleship, Stealth Bomber, Mobile Artillery, Cruiser, Destroyer, Submersible |
The Command Ship can attack into any ambit but must first move there (see below). The Battleship’s armour-piercing primary covers Land and Water; its guided secondary reaches Space.
struct-attack accepts a comma-separated list of target IDs, but how many of those targets a weapon engages is governed by that weapon’s primaryWeaponTargets / secondaryWeaponTargets value.
primaryWeaponTargets = 1 on all fleet hulls, and secondaryWeaponTargets = 1 on the three hulls with a secondary (Battleship, Starfighter, Cruiser). Listing extra IDs does not let a targets = 1 weapon spread its volley across them.primaryWeaponShots), which is how many projectiles a weapon fires at a single target (e.g. Starfighter Attack Run = 3 shots, 1 target).The Command Ship is the only struct that can change ambits (movable=true). All other structs are fixed in their operating ambit, and the chain rejects struct-move on a non-movable struct — only the Command Ship can be moved.
structsd tx structs struct-move [cmd-ship-id] [new-ambit] [new-slot] [new-location] --from [key] --gas auto --gas-adjustment 1.5 -yEach struct has a Max HP that determines how much damage it can absorb before destruction.
| Struct | Max HP |
|---|---|
| Command Ship | 6 |
| Other fleet structs (IDs 2-13) | 3 |
| Baseline planetary structs (Ore Extractor, Ore Refinery, Orbital Shield Generator, Jamming Satellite, Ore Bunker, PDC) | 6 |
| Field Generator | 8 |
| Continental Power Plant | 10 |
| World Engine | 10 |
Planetary structs are hardened so a raider cannot casually demolish a planet’s infrastructure; the power generators are the toughest (and carry armour, damage reduction 1), making a power kill a deliberate objective. Armour-piercing weapons (Battleship primary) bypass that reduction.
damage = sum(successful_shots) - damageReduction
if damage >= health then health = 0
else health = health - damage
| Variable | Description |
|---|---|
| weaponShots | Number of shots per attack (primaryWeaponShots / secondaryWeaponShots) |
| weaponShotSuccessRate | Per-shot success (Numerator/Denominator) |
| weaponGuaranteedShots | Minimum number of shots that hit before the success rate roll applies (primaryWeaponGuaranteedShots / secondaryWeaponGuaranteedShots) |
| weaponDamage | Damage per successful shot |
| damageReduction | Defense reduction (target’s attackReduction, e.g. Tank/generator armour = 1); negated when the weapon is armour-piercing |
| armourPiercing | If the weapon is armour-piercing (primaryWeaponArmourPiercing / secondaryWeaponArmourPiercing), the target’s damageReduction is treated as 0 |
| health | Target current health |
Algorithm: For shot index i in 0..weaponShots, the shot hits if i < weaponGuaranteedShots OR IsSuccessful(weaponShotSuccessRate). The first weaponGuaranteedShots shots are auto-hits; only the remaining shots roll against the success rate. Sum the damage from successful shots, then apply damageReduction (unless the weapon is armour-piercing, in which case reduction is skipped). Minimum damage after reduction is 1. Cap at target health.
Armour-piercing: A weapon flagged armour-piercing negates the target’s damage reduction during volley resolution. The Battleship’s primary is armour-piercing — it deals full damage to Tanks and to power generators (which otherwise reduce incoming damage by 1). Each shot’s piercing is reported on EventAttackShotDetail.armourPiercing.
Why guaranteed shots exist: A weapon with shots=3 and successRate=1/3 has the same expected value as a single guaranteed hit, but its variance is much higher — most attacks would deal zero damage. Setting guaranteedShots=1 floors the damage at one hit per volley while preserving the upside of the other rolls. Guaranteed shots apply only to the Starfighter Attack Run (secondary weapon, secondaryWeaponGuaranteedShots = 1); other weapons leave the field at 0, which means “no guarantee, all shots roll”.
Querying note:
primaryWeaponGuaranteedShots/secondaryWeaponGuaranteedShotsareomitemptyin the chain’s JSON output. Because the value is 0 for every struct type except the Starfighter secondary, the field is omitted entirely from moststruct_typequery responses. A payload with no guaranteed-shots key means the value is 0, not that the field was removed from the chain. (Verified present in currentstructsdsource.)
Attack results: Attack events include health results (remaining health after attack) in addition to damage amounts.
if weaponControl == guided then successRate = guidedDefensiveSuccessRate
else successRate = unguidedDefensiveSuccessRate
canEvade = IsSuccessful(successRate) if successRate.Numerator != 0
The interaction between a weapon’s control type (guided/unguided) and the target’s defense type is the core of combat tactics. This matrix determines whether shots can be evaded:
| Target Defense | vs Guided | vs Unguided |
|---|---|---|
| Signal Jamming (Battleship, Pursuit Fighter, Cruiser) | 66% miss | Full hit |
| Defensive Maneuver (High Alt Interceptor) | Full hit | 66% miss |
| Armour (Tank, Field Generator, Continental Power Plant, World Engine) | Full hit, -1 damage | Full hit, -1 damage |
| Stealth Mode (Stealth Bomber, Submersible) | Same-ambit only | Same-ambit only |
| Indirect Combat Module (Mobile Artillery) | Full hit | Full hit |
| None | Full hit | Full hit |
Tactical takeaways: Use unguided weapons against Signal Jamming targets (Battleship, Pursuit Fighter, Cruiser). Use guided weapons against Defensive Maneuver targets (High Alt Interceptor). Armour reduces damage by 1 regardless of weapon control — except against armour-piercing weapons (Battleship primary), which ignore it entirely. The Battleship is the dedicated answer to Tanks and to armoured power generators.
Stealthed structs (Stealth Bomber, Submersible) are not invisible – they can still be targeted by structs in the same ambit. Stealth blocks cross-ambit targeting only. A stealthed Submersible (water) can be attacked by other water structs, but air/land/space structs cannot target it.
struct-stealth-activate [struct-id] (2 charge). Deactivation: struct-stealth-deactivate [struct-id].Attacker takes damage after firing: health = health - weaponRecoilDamage. Recoil only applies if the attacker survives the entire shot sequence (including counter-attacks).
If health == 0 and postDestructionDamage > 0, damage applies to surrounding structs.
struct-defense-set)Any struct can be assigned to defend another, regardless of ambit. Assignment checks only that the defender is co-located with the protected struct (same planet or fleet) and is built and online — an offline or still-building struct is rejected. Ambit does not gate assignment; it gates what the defender can do once an attack lands:
So assigning a water Submersible to defend a land struct is valid and useful: it will counter air/space-reachable attackers even though it can never block for the land struct. Spread defenders across ambits to widen counter coverage, and keep same-ambit defenders where you need actual damage interception. There is no per-ambit cap on how many structs can defend (see Edge Cases).
if !evaded and defender exists and defender.operatingAmbit == target.operatingAmbit then
if weapon.blockable and defender.ReadinessCheck() then
canBlock = IsSuccessful(defender.blockingSuccessRate)
Requirements (all must be true):
GetWeaponBlockable returns true)A struct cannot block for a friendly in a different ambit. Blocking is strictly same-ambit defense. Unlike counter-attacks, block is attempted on every shot (not limited to once per attack).
Each struct can counter-attack at most once per struct-attack invocation. Counter-spent state is tracked per struct per attack command (not per target, not per shot). For a 3-shot Attack Run: the defender counters on the first shot only, but can attempt to block all 3 shots. The target counters once after all shots resolve.
Counter-attacks are ambit-independent from the defended target. A space-based defender can counter-attack a space-based attacker even while defending a land-based struct. Defenders do not take counter-attack damage — only the original attacker and target can be damaged by counters.
Range rule: A struct on a fleet that is away from the home planet cannot defend planetary structs at that planet. Only on-station fleet structs and planet-based structs can defend their home planet.
| Scenario | Damage |
|---|---|
| Same ambit as attacker | the defender type’s counterAttackSameAmbit value |
| Different ambit from attacker | the defender type’s counterAttack value |
Counter damage comes from two per-type fields, not a flat number: counterAttackSameAmbit (when the counter-attacker shares the attacker’s ambit) and counterAttack (when it does not). Most hulls are 1 / 1; the Command Ship is 2 / 2; the Destroyer is 1 / 2 (cross / same). See struct-types.md — Defensive Properties for the per-type values.
Counters are a backstop, not a damage plan. The values are small (typically 1), and an attacker striking from an ambit your structs can’t reach takes no counter at all. Real damage comes from active struct-attack volleys — build offense around attacking from a safe ambit, not around baiting counters.
Requirements (all must be true):
GetWeaponCounterable returns true)CanCounterTargetAmbit)Two types of counter-attack:
struct-attack invocation. Fires even on evaded shots.struct-attack invocation. Destroyed targets cannot counter.Both the defender and the target can counter-attack – an attacker may take damage from two sources per target.
damage = planetaryShieldBase + sum(defenseCannon.damage for each cannon on planet)
Two planetary defenses are wired to structs and affect combat: the Planetary Defense Cannon and the low-orbit ballistic interceptor network (provided by the Jamming Satellite struct).
noUnitDefenses; its planetary effect gives the planet a chance to evade an incoming attack when the attacker is in air or space and the target struct is in land or water. On a successful evade the shot is flagged evadedByPlanetaryDefenses: true with cause lowOrbitBallisticInterceptorNetwork, dealing 0 damage (for example, a Battleship’s space secondary against a land or water struct). It has no effect against water- or land-based attackers. Each additional interceptor on the planet compounds the evade chance.signalJamming defense carried by the target struct (Battleship, Pursuit Fighter, Cruiser — 66% guided miss). This is a per-struct defense, not a planetary field. See Weapon Control vs Defense Type.When struct-attack is executed, the following steps occur in order per target:
ResolveDefenders) – For each projectile:
struct-attack invocation) – fires regardless of evasion. Only on the first shot where the defender hasn’t already countered.struct-attack invocation) – fires after all shots resolve. Destroyed targets cannot counter.After all targets are resolved:
Each projectile gets its own EventAttackShotDetail row. For a 3-shot Attack Run, the attack event contains 3 separate shot detail entries with per-projectile hit/miss breakdowns. targetPlayerId is on EventAttackShotDetail (not EventAttackDetail).
In the live struct_attack event detail, the attacker context is flat at the top and the per-shot rows are a nested array eventAttackShotDetail[]. See api/integration-notes.md — struct_attack event detail schema for the exact field layout integrators receive.
Key implications:
struct-attack invocation, regardless of how many shots or targets| Requirement | Attack | Raid |
|---|---|---|
| Operating struct online, and its owner online | ✓ | — |
| Raider player online (sufficient power) | — | ✓ |
| Sufficient charge | ✓ | — |
| Target struct built (a struct cannot be attacked until it finishes building) | ✓ | — |
| Raider fleet away (at the target) | — | ✓ |
| Defender’s shields vulnerable (defender’s fleet off-station, or their Command Ship offline / destroyed / non-existent) | — | ✓ |
Raid clock started (blockStartRaid != 0) |
— | ✓ |
| Proof-of-work | — | ✓ |
An attack action depends only on the attacking struct (and its owner) being online — the attacker’s Command Ship does not need to be online, and the same holds for defensive changes (struct-defense-set) and stealth changes. The target must be a built struct; struct-attack against a struct that is still building is rejected (unbuilt), and a destroyed struct is rejected (destroyed). The target’s online status is irrelevant.
Note: planet-raid-complete does not consume charge (it is a proof-of-work message, not a charge message). Direct struct-attack does consume the player’s charge.
A raid is only winnable while the defending planet’s shields are vulnerable. Shields are vulnerable whenever the defender’s fleet is off-station (the Command Ship only defends the home planet while the fleet is on station), or the defender’s Command Ship is offline, destroyed, or non-existent. While the defender’s fleet is on station with a built, online Command Ship, the planet’s shields are up and planet-raid-complete is rejected (shields_active) no matter how much work the raider does. The single most effective raid defense is therefore keeping your fleet on station with the Command Ship online — and note that sending your own fleet away to raid someone else leaves your planet’s shields vulnerable until it returns.
The blockStartRaid attribute is the vulnerability clock, and raid PoW age is measured from it:
raid_clock_unset).So a raider must catch the defender vulnerable and let the clock age before the puzzle becomes solvable. If the defender restores their shields mid-raid (Command Ship back online with the fleet on station), the clock resets and the raider must wait for the planet to become vulnerable again.
| Status | Meaning |
|---|---|
| initiated | Raider fleet has arrived at the planet |
| shieldsVulnerable | Defender’s shields are down (fleet off-station, or Command Ship offline/destroyed) — the raid is now winnable and the clock is running |
| ongoing | Defender restored shields mid-raid (Command Ship back online with the fleet on station) — completion blocked |
| raidSuccessful | Raider won and seized all of the defender’s stored ore |
| attackerDefeated | Raider’s own Command Ship was destroyed while away (trigger_raid_defeat_by_destruction) — the raiding fleet is defeated and sent home |
| attackerRetreated | Raider withdrew before completion |
| demilitarized | Planet has no defenders to resolve against (fleet.PeaceDeal()) |
Status values are the RaidStatus_* enum emitted on EventRaid. The most a defender loses to a raid is all of their stored ore (raidSuccessful).
Raid status also includes seizedOre – the amount of ore stolen during the raid, tracked on the planet_raid record. See schemas/entities/planet.md for the planet_raid table schema.
A successful planet-raid-complete seizes all of the defender’s storedOre, sends the raider’s fleet home, and emits raidSuccessful. Ore is the only thing a raid takes — a raid does not destroy the defending player or their structs.
Destroying the defender’s Command Ship (or catching their fleet off-station) makes the planet’s shields vulnerable (shieldsVulnerable), which is the condition that lets a raid complete. If the defender restores their shields before completion — Command Ship back online with the fleet on station — the shields return and planet-raid-complete is rejected with shields_active. So the planet is either vulnerable at completion — and the raider takes all the ore — or shielded, and the raid is rejected.
trigger_raid_defeat_by_destruction is a property of the Command Ship. When a Command Ship is destroyed while away from home (its planet’s owner differs from its own owner), its fleet is defeated: the raid ends with attackerDefeated and the fleet is sent home. This defeats an attacking fleet whose Command Ship dies during a raid.
Raid attack doctrine:
shieldsVulnerable window.storedOre) can be stolen – not unmined ore on the planet (remainingOre). Alpha Matter is secure. A successful raid seizes all of the target player’s storedOre, not a partial amount. One raid = total loss.IsSuccessful uses hash(blockHash, playerNonce) % Denominator < Numeratorstruct-attack invocation. For multi-shot weapons (Attack Run with 3 projectiles), the defender counters on the first shot only but can attempt to block all 3 shots. The target counters once after all shots.defense-set only checks co-location, and attack resolution iterates every registered defender. The planet’s slot structure provides 4 slots per ambit, which caps how many structs can exist per ambit, not how many can defend.When a struct reaches 0 HP, it is destroyed and removed from the game. The destroyed instance is gone forever and cannot be repaired. However, you can build a new struct of the same type as a replacement — full build PoW required.
| Consequence | Detail |
|---|---|
| Destroyed struct | Instance gone forever; build a replacement (full PoW) |
| Lost defenders | Each destroyed defender must be individually rebuilt |
| Rebuild cost | Full PoW + power draw, same as original build |
YES. A destroyed Command Ship cannot be repaired, but you can build a brand new Command Ship (type 1) to replace it. The new Command Ship gets a new struct ID and requires full build PoW (~17 min at D=3). You choose the starting ambit at build time.
Until the replacement is online, the fleet cannot move, raid, or build in space. This downtime is the real cost of losing a Command Ship. Always assign defenders to protect it via struct-defense-set before engaging in offensive operations.
status bitmask (e.g. 35 = destroyed)schemas/formulas.md — Verified formula definitionsreference/action-quick-reference.md — Combat action endpointsprotocols/action-protocol.md — Transaction flow