ui/phase-20: ship-group inspector actions

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>
This commit is contained in:
Ilia Denisov
2026-05-10 16:27:55 +02:00
parent f7109af55c
commit 3626998a33
36 changed files with 4033 additions and 89 deletions
+25
View File
@@ -171,6 +171,31 @@ func TestCarryingMassParity(t *testing.T) {
}
}
func TestBlockUpgradeCostParity(t *testing.T) {
t.Parallel()
cases := []struct {
name string
blockMass float64
currentTech float64
targetTech float64
}{
{"zero_block_mass", 0, 1, 2},
{"target_equal_to_current", 5, 2, 2},
{"target_below_current", 5, 2, 1},
{"doubling_tech_on_mass_5", 5, 1, 2},
{"partial_step_2_to_2_5", 5, 2, 2.5},
{"high_tech_to_higher_tech", 12, 4, 6},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
t.Parallel()
want := source.BlockUpgradeCost(c.blockMass, c.currentTech, c.targetTech)
got := bridge.BlockUpgradeCost(c.blockMass, c.currentTech, c.targetTech)
assert.Equal(t, want, got)
})
}
}
// TestDesignerPreviewComposition exercises the exact composition the
// ship-class designer performs: empty mass, full-load mass via
// CarryingMass(CargoCapacity), max speed at empty, and range at full