a89048f6c5
MVP web client (Phases 1-30) is complete; reorganize planning + living docs around that. - PLAN.md kept as the staged MVP record (1-30) with a status block + pointers; removed the 31-36 stages, regression scenarios, and deferred-TODO section (moved out); fixed a stale cross-machine plan path. - ui/PLAN-finalize.md (new): active web-finalization plan in 8 stages (visual system, a11y, i18n, error UX, PWA, build hygiene, docs, owner manual-QA loop); absorbs former Phases 33 and 35. - ui/ROADMAP.md (new): post-MVP (Wails, Capacitor, realistic projection, acceptance + regression scenarios) and triaged deferred follow-ups. - ui/docs/README.md (new): grouped topic-doc index. - De-archaeologized all 20 ui/docs topic docs + ui/README.md + ui/core/README.md: stripped Phase-N build history, rewritten as current-state; deferred work now points at ROADMAP.md / PLAN-finalize.md. Docs-only; no code change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
136 lines
6.3 KiB
Markdown
136 lines
6.3 KiB
Markdown
# Ship Class Calculator — UX
|
||
|
||
The ship-class designer and calculator are fused into one sidebar
|
||
tool (`lib/sidebar/calculator-tab.svelte`). The standalone designer
|
||
view/route was replaced by this combined tool. 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 (in cargo
|
||
units) that the loaded-column results use. At **full** the toggle
|
||
shows the ship's cargo capacity; a **custom** load over that capacity
|
||
is flagged as an error. With a zero cargo block there is no hold, so
|
||
the load is pinned to empty and the toggle is disabled.
|
||
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 planned (see ../ROADMAP.md).
|
||
|
||
## 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. Locking a speed is disabled when the drive block is
|
||
zero (a deliberately immobile ship has no speed to back-solve).
|
||
|
||
## Validation and display
|
||
|
||
Every numeric input is validated independently and an offending one gets
|
||
a red border and a hover/tap tooltip with the reason: no value may be
|
||
negative, the five blocks follow the engine value rules
|
||
(`pkg/calc/validator.go`, surfaced per-field by
|
||
`shipClassFieldErrors`), and a custom load may not exceed cargo capacity.
|
||
|
||
Every displayed number — the derived results and the goal-seek
|
||
back-solved input — is rounded **up** to three decimals through the
|
||
shared `pkg/calc/number.go.Ceil3` (bridged as `core.ceil3`), so a value
|
||
is never shown lower than it is (a speed of 5.0003 reads 5.001). The
|
||
engine keeps its own round-to-nearest `util.Fixed*`; `Ceil3` is a
|
||
display-only helper that lives in `pkg/calc` so the UI and Go share one
|
||
implementation.
|
||
|
||
## 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 1–3 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.
|