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
+115
View File
@@ -0,0 +1,115 @@
# Ship Class Calculator — UX
Phase 30 fuses the ship-class designer and a calculator into one sidebar
tool (`lib/sidebar/calculator-tab.svelte`). It replaced the standalone
designer view/route from Phases 17/18. All numeric math lives in
`pkg/calc` and is reached through the `Core` WASM bridge; the calculator
holds input state and orchestrates, it never computes.
## Modes
- **Calculator** (`ship`): the full tool — design area, derived results,
planet build, goal-seek.
- **Modernization**: reuses the design area and shows per-block and
total `BlockUpgradeCost` from the current tech to an editable target
tech. The design-area component is extracted
(`lib/calculator/ship-design-area.svelte`) so the future ship-group
upgrade flow can reuse it.
The `path` mode from the original plan was dropped (MVP path-finding is
brute force); reach circles on the map replace it. `bombing` is folded
in as a per-ship result rather than a separate mode.
## Areas
1. **Ship Class design area** — five blocks (drive, armament, weapons,
shields, cargo) and four tech levels (drive, weapons, shields,
cargo). Tech defaults to the player's current tech and shows a lock
icon once overridden; clicking it resets to the default.
2. **Calculator area** — derived results: empty/loaded mass, empty/
loaded speed, attack, defence, bombing (per ship), cargo capacity.
A load toggle (empty / full / custom) sets the cargo load that the
loaded-column results use.
3. **Planet area** — when an own planet is selected on the map, shows
its MAT (overridable) and the single-turn build rate (ships per turn,
turns per ship). The realistic multi-turn forecast with CAP/COL
supply is Phase 34.
## Locks and goal-seek
Two distinct lock semantics share one icon (a closed padlock; it only
appears once a value is pinned, click to release):
- **Override locks** on inputs that have a default — the four techs and
the planet MAT. Editing one overrides the default; the lock resets it.
Any number may be overridden at once.
- **Goal-seek locks** on derived results. Pinning a result back-solves
the single input it claims, which then renders read-only (computed):
| result | claims |
| ------------- | ------------- |
| attack | weapons block |
| defence | shields block |
| empty speed | drive block |
| loaded speed | drive block |
| empty mass | cargo block |
| loaded mass | cargo load |
Only **one** result may be locked at a time (the others' lock
affordances disable with a tooltip). An unreachable target — e.g. a
speed at or above the stripped-hull ceiling `20 × driveTech`, or a
solved block that fails the value rules — leaves the locked cell in a
red error state and does not apply. Inverse solving lives in
`pkg/calc/solve.go`; the bisection for defence → shields is the only
non-analytic case.
## Create / load / delete
The name field is a combobox over the player's existing classes. Picking
an existing class loads it as a template (so you can tweak and Create a
new one); Create is disabled while the name is invalid or duplicate
(reusing `lib/util/ship-class-validation.ts`). When a saved class is
loaded, a Delete affordance appears. Create / Delete reuse the existing
`createShipClass` / `removeShipClass` order-draft flow, so the optimistic
overlay reflects the change immediately. Ship classes are immutable after
creation (per `game/rules.txt`), so there is no edit — only Create-new
and Delete.
## Reach circles
When an own planet is selected in calculator mode, the calculator
publishes the planet origin and the design's loaded speed to a shared
store (`lib/calculator/reach.svelte`). The map view
(`lib/active-view/map.svelte`) reads it and draws 13 thin concentric
reach circles (`map/reach-circles.ts`) for 1/2/3 turns. The ring count
shrinks as speed grows: a ring is dropped once the previous one reaches
the torus wrap-midpoint (half the shorter side) or the no-wrap map edge
(farthest corner). The circles clear when the selection clears or the
design is invalid.
## State preservation and history
Calculator inputs are component-local state. The sidebar keeps the tab
mounted while the player navigates between active views, so inputs
persist across view switches per the global state-preservation rule
(`ui/docs/navigation.md`). Tech levels track the rendered report, so in
history mode the calculator computes against the viewed snapshot's tech.
The ship-classes table and the view/bottom menus open the calculator via
a shared request store (`lib/calculator/load-request.svelte`): the
in-game layout flips the sidebar to the calculator tab and the
calculator loads the requested class (or starts a fresh design).
## Layout and mobile
Everything stacks vertically to fit the 18 rem sidebar; the design and
result rows use compact two-column (ship/tech, empty/loaded) grids. On
mobile the sidebar is the existing bottom-sheet/overlay; the calc bottom
tab opens it.
## Runtime note
The new bridge functions are only present after `make wasm` rebuilds
`ui/frontend/static/core.wasm` (needs TinyGo). Vitest injects a fake
`Core` (`tests/fake-core.ts`) mirroring `pkg/calc`, so unit/component
tests do not need the rebuild; the Playwright suite and the live app do.