feat(ui): Phase 30 ship-class calculator with goal-seek and reach circles
Tests · UI / test (push) Successful in 2m14s
Tests · Go / test (push) Successful in 2m25s

Fuse the standalone ship-class designer (Phases 17/18) into a sidebar calculator: live mass/speed/attack/defence/bombing results, a planet build-rate readout, single-target goal-seek, a modernization-cost mode, and auto reach circles on the map for the selected planet.

pkg/calc becomes the single source for the new math (no mirroring): extract BombingPower from the engine model and the per-turn ship-production loop from controller.ProduceShip into pkg/calc (engine now delegates), and add inverse goal-seek solvers in pkg/calc/solve.go. Thin-bridge the combat, planet-build, and solver functions through ui/core/calc + ui/wasm and rebuild core.wasm.

Remove the standalone designer view/route; the ship-classes table and the view/bottom menus open the calculator via a shared request store.

Docs: rewrite ui/PLAN.md Phase 30, adjust Phase 34 (realistic forecast + CAP/COL ownership), add ui/docs/calculator-ux.md, extend calc-bridge.md, fix navigation.md; remove ui/CALCULATOR.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-21 19:52:08 +02:00
parent 00159ddf7c
commit 9ae7b88b89
53 changed files with 3748 additions and 1298 deletions
+32 -7
View File
@@ -11,11 +11,14 @@ matching TS adapter in `ui/frontend/src/platform/core/`.
Phase 18 lands the **ship-math slice** of the bridge — everything
the ship-class designer needs to render its preview pane. Phase 20
extends it with `BlockUpgradeCost` so the ship-group inspector can
preview modernize cost. Other slices (production forecast, science
research, ship build progress) remain deferred to dedicated future
phases. This document is the running audit trail of what is live,
what is missing, and how each function maps to its `pkg/calc/`
source.
preview modernize cost. Phase 30 extends it with the **combat,
planet-build, and goal-seek slice** for the ship-class calculator:
`EffectiveAttack`, `EffectiveDefence`, `BombingPower`, `ShipBuildCost`,
`ProduceShipsInTurn`, and the inverse solvers from `pkg/calc/solve.go`.
Other slices (production/science forecast, the realistic multi-turn
planet projection) remain deferred to dedicated future phases. This
document is the running audit trail of what is live, what is missing,
and how each function maps to its `pkg/calc/` source.
## Live bridge surface
@@ -35,7 +38,27 @@ on the JS-side `globalThis.galaxyCore` (registered in
| `speed` | `calc.Speed(driveEffective, fullMass)` | `number` | designer preview (speed + range) |
| `cargoCapacity` | `calc.CargoCapacity(cargo, cargoTech)` | `number` | designer preview (cargo row) |
| `carryingMass` | `calc.CarryingMass(load, cargoTech)` | `number` | designer preview (full-load mass) |
| `blockUpgradeCost` | `calc.BlockUpgradeCost(blockMass, currentTech, target)` | `number` | ship-group inspector modernize preview |
| `blockUpgradeCost` | `calc.BlockUpgradeCost(blockMass, currentTech, target)` | `number` | ship-group inspector + modernization mode |
| `effectiveAttack` | `calc.EffectiveAttack(weapons, weaponsTech)` | `number` | calculator (attack result) |
| `effectiveDefence` | `calc.EffectiveDefence(shields, shieldsTech, fullMass)` | `number` | calculator (defence result) |
| `bombingPower` | `calc.BombingPower(weapons, weaponsTech, armament, n)` | `number` | calculator (bombing result, n = 1) |
| `shipBuildCost` | `calc.ShipBuildCost(shipMass, material, resources)` | `number` | calculator (planet build) |
| `produceShipsInTurn`| `calc.ProduceShipsInTurn(L, material, resources, mass)` | `{ships,…}` | calculator (planet ships/turn) |
| `weaponsForAttack` | `calc.WeaponsForAttack(targetAttack, weaponsTech)` | `number\|null` | calculator goal-seek (attack → weapons) |
| `driveForSpeed` | `calc.DriveForSpeed(targetSpeed, driveTech, restMass)` | `number\|null` | calculator goal-seek (speed → drive) |
| `shieldsForDefence` | `calc.ShieldsForDefence(targetDefence, sTech, restMass)` | `number\|null` | calculator goal-seek (defence → shields) |
| `cargoForEmptyMass` | `calc.CargoForEmptyMass(targetEmptyMass, restMass)` | `number\|null` | calculator goal-seek (mass → cargo) |
| `loadForFullMass` | `calc.LoadForFullMass(targetFullMass, emptyMass, cTech)` | `number\|null` | calculator goal-seek (loaded mass → load)|
`BombingPower` and the per-turn build loop are no longer engine-only:
Phase 30 extracted `BombingPower` from
`game/internal/model/game/group.go` and the per-iteration build math
from `controller.ProduceShip` into `pkg/calc` (`ProduceShipsInTurn`),
and the engine now delegates to both — a true refactor, not a mirror.
The inverse solvers (`pkg/calc/solve.go`) invert the forward formulas
for single-target goal-seek and return `null` when infeasible;
`shieldsForDefence` uses bisection, the rest are analytic. Parity and
round-trip tests live in `ui/core/calc/{ship,planet,solve}_test.go`.
`number|null` returns mirror the Go `(float64, bool)` signature: the
upstream validator rejects weapons/armament pairings with one zero
@@ -85,12 +108,14 @@ whether the underlying Go function exists.
| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | :-------------: | :-------------: |
| Ship-class designer preview (Phase 18) | `EmptyMass`, `FullMass`, `Speed`, `DriveEffective`, `CargoCapacity`, `CarryingMass`, `WeaponsBlockMass` (`pkg/calc/ship.go`) | yes | yes |
| Ship-group modernize cost preview (Phase 20) | `BlockUpgradeCost` (`pkg/calc/ship.go`, migrated from `game/internal/controller/ship_group_upgrade.go`) | yes | yes |
| Ship calculator combat (Phase 30) | `EffectiveAttack`, `EffectiveDefence`, `BombingPower` (`pkg/calc/ship.go`; `BombingPower` extracted from `model/game/group.go`) | yes | yes |
| Ship calculator goal-seek (Phase 30) | inverse solvers in `pkg/calc/solve.go` | yes | yes |
| Free production potential (`freeIndustry`) | `Planet.ProductionCapacity``industry*0.75 + population*0.25` (`game/internal/model/game/planet.go`) | no | no |
| Industry production output per turn | `Planet.ProduceIndustry(freeProduction)` (`planet.go`); `freeProduction/5` modulo material constraint | no | no |
| Materials production output per turn | `Planet.ProduceMaterial(freeProduction)` (`planet.go`); `freeProduction * resources` | no | no |
| Per-tech research progress (DRIVE/WEAPONS/…) | `ResearchTech` (`game/internal/model/game/science.go`); `freeProduction / 5000` per tech level | no | no |
| Custom-science progress | weighted form of `ResearchTech` driven by `Race.Sciences[].(Drive\|Weapons\|Shields\|Cargo)` (`science.go`) | no | no |
| Ship build progress | `PlanetProduceShipMass(L, Mat, Res) / ShipProductionCost(class.EmptyMass)` (combination of two existing exports) | partial | no |
| Ship build progress / planet build rate (Phase 30)| `ProduceShipsInTurn(L, Mat, Res, mass)` (`pkg/calc/planet.go`, extracted from `controller.ProduceShip`); `ShipBuildCost` | yes | yes |
`partial` means the Go primitives exist in `pkg/calc/` but the
composition (and the conversion of TS-side `ReportPlanet`/