Per the documented turn order (game/rules.txt "Последовательность
действий"), no ship should dodge the pre-departure battle by slipping
into hyperspace. MakeTurn now runs merge -> battle -> load+launch routed
groups -> fly -> merge -> battle, so:
- ships ordered to depart (Launched) and ships being upgraded now take
part in the pre-departure battle at their planet (CollectPlanetGroups /
FilterBattleGroups); only survivors then enter hyperspace;
- routed transports are loaded and launched AFTER that battle, so they
fight empty and cannot escape it.
A just-launched group has no stored hyperspace position, so moveShipGroup
starts its first leg from the origin planet; the previous code read the
nil launch coordinate and would panic.
Because upgrading groups can now lose ships in the battle, the pending
upgrade cost is recomputed from the group's current ship count instead of
the value stored when the order was validated.
Rules: reordered "Последовательность действий" and rewrote the combat
note that ordered/routed ships skip the battle.
Tests: launched-group move from origin, launched/upgrade groups taking
part in battle, upgrade cost tracking ship losses.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Validation of a player's order now applies every command against a
transient game-state snapshot and records the per-command outcome
(cmdApplied, cmdErrorCode) in each command's meta. The order is
persisted even when some commands are rejected, and the response is
202 + UserGamesOrder so clients can surface the partial failure
without the chain collapsing into "downstream service is unavailable".
Pkg/error consts are reshelved onto three explicit ranges with a
package doc and helpers (IsInternalCode/IsInputCode/IsGameStateCode):
1xxx internal/server (500/501), 2xxx structural input (400), 3xxx
game-state per-command rejection (400 when escaping HTTP, otherwise
recorded as cmdErrorCode). Two pre-existing typos fixed mechanically
(ErrBeakGroupNumberNotEnough -> ErrBreakGroupNumberNotEnough,
ErrRaceExinct -> ErrRaceExtinct) along with all callsites.
Engine errorResponse maps *GenericError by shelf rather than mapping
everything to 500. The Quit-not-last structural check in
Controller.ValidateOrder is preserved and its type assertion fixed
(was a value assertion against a pointer-typed command, so the check
silently never fired).
Backend, gateway and UI are unchanged — they were already correct on
the 202 path; only the engine collapsing per-command rejection into
500 was needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eight ship-group operations land on the inspector behind a single
inline-form panel: split, send, load, unload, modernize, dismantle,
transfer, join fleet. Each action either appends a typed command to
the local order draft or surfaces a tooltip explaining the
disabled state. Partial-ship operations emit an implicit
breakShipGroup command before the targeted action so the engine
sees a clean (Break, Action) pair on the wire.
`pkg/calc.BlockUpgradeCost` migrates from
`game/internal/controller/ship_group_upgrade.go` so the calc
bridge can wrap a pure pkg/calc formula; the controller now
imports it. The bridge surfaces the function as
`core.blockUpgradeCost`, which the inspector calls once per ship
block to render the modernize cost preview.
`GameReport.otherRaces` is decoded from the report's player block
(non-extinct, ≠ self) and feeds the transfer-to-race picker. The
planet inspector's stationed-ship rows become clickable for own
groups so the actions panel is reachable from the standard click
flow (the renderer continues to hide on-planet groups).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>