feat(ui): Phase 30 ship-class calculator with goal-seek and reach circles
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:
+32
-7
@@ -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`/
|
||||
|
||||
Reference in New Issue
Block a user