Files
galaxy-game/ui/docs/science-designer-ux.md
T
Ilia Denisov a89048f6c5 docs(ui): finalize MVP plan structure and de-archaeologize topic docs
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>
2026-05-21 23:17:51 +02:00

107 lines
5.0 KiB
Markdown

# Science designer UX
A *science* is a named mix of four tech proportions —
`drive`, `weapons`, `shields`, `cargo` — that sum to `1.0`. When a
planet's production is set to a science, the planet's industry
output for that turn is split between the four tech research tracks
in those proportions
(`game/internal/controller/planet/production.go.runScienceResearch`).
The CRUD list, the designer, and the production-picker integration
are provided by the UI; the wire and engine validation are handled
by the backend.
## Engine semantics in one paragraph
`pkg/schema/fbs/order.fbs.CommandScienceCreate` carries
`name + drive + weapons + shields + cargo` as four `float64`
proportions. The engine validator
(`pkg/calc/validator.go.ValidateScienceValues`) refuses any value
outside `[0, 1]` and any sum that drifts further than its float
tolerance from `1.0`. Names follow the universal entity-name rules
(`pkg/util/string.go.ValidateTypeName`): trimmed, non-empty, ≤ 30
runes, only letters / digits / combining marks / the allowed special
set `!@#$%^*-_=+~()[]{}`, no special at start or end, ≤ 2 specials
in a row, no whitespace. There is no `CommandScienceUpdate` on the
wire — sciences are write-once, and an "edit" is a Remove + Create
sequence.
## Percent input model
The designer presents the four proportions as percentages
(`step="0.1"`, range `[0, 100]`) so the player can type and reason
about whole-number splits — closer to how `game/rules.txt` describes
sciences (`game/rules.txt:345-362`: "10 parts Drive, 5 parts
Weapons, 30 parts Shields, 0 parts Cargo, …"). The wire shape is
still fractions; conversion happens inside `validateScience` only on
Save (`value / 100` for each of the four).
The four inputs are *not* auto-rebalanced. The validator refuses a
draft whose sum drifts further than `SUM_EPSILON_PERCENT` (`1e-3`)
from `100`, and the form's Save button stays disabled until the sum
matches. A live readout under the inputs displays the running total
so the player can chase it down without trial-and-error guessing.
The strict-sum gate was chosen over alternatives — auto-rebalance
and raw-parts-with-engine-normalisation — because keeping the input
model close to "what gets sent on the wire" minimises surprises when
the engine returns the science exactly as typed. See
`lib/util/science-validation.ts` for the validator and the
conversion helper.
## Name validation
`validateScience` runs `validateEntityName` first and returns its
invalid-reason verbatim, so the designer's `aria-describedby`
mapping reuses the existing translation keys for `empty`,
`too_long`, `starts_with_special`, `ends_with_special`,
`consecutive_specials`, `whitespace`, `disallowed_character`. A
new key `duplicate_name` covers the UX-only check against the
optimistic-overlay `localScience` projection — the engine would
refuse the duplicate at submit time, but catching it locally keeps
the Save button disabled with a clear hint instead of letting a
red-badge `rejected` row land in the order tab.
## Read-only view mode
A `scienceId`-bearing URL renders the designer in view mode: a
read-only table of the four percentages plus name, with Back and
Delete affordances. Sciences are write-once on the wire, so there
is no Save-edits affordance — to change a science, the player
deletes it and creates a new one. Delete dispatches a
`removeScience` order command; the engine refuses removals when the
science is referenced by an active production target on any planet,
which surfaces as `rejected` in the order tab.
## Production-picker integration
The planet inspector's Research sub-row
(`lib/inspectors/planet/production.svelte`) renders the four tech
buttons and one extra button per defined science from the player's
`localScience` overlay. A click on a science button dispatches
`setProductionType("SCIENCE", "<scienceName>")`, mirroring the
wire-level `CommandPlanetProduce` shape
(`pkg/schema/fbs/order.fbs.CommandPlanetProduce`).
The active highlight is derived from `planet.production` — the
display string the engine emits in the report. A science name
shadows the matching tech display string when they collide (a
science deliberately named `Drive` wins over the Drive tech
button), because the wire string is ambiguous and the user clearly
intended the named science. This is a pragmatic accept; a
structured production tag on the wire would let us disambiguate
without the shadow rule, but that is a separate backend concern.
## Tests
- `tests/science-validation.test.ts` — validator branches, percent
→ fraction conversion, sum tolerance, duplicate-name detection.
- `tests/table-sciences.test.ts` — table rendering, filter, sort,
Delete dispatches `removeScience`, navigation to the designer.
- `tests/designer-science.test.ts` — empty form Save disabled,
live sum readout, valid Save dispatches `createScience` with
fractions, view-mode Delete dispatches `removeScience`,
duplicate-name guard against the overlay.
- `tests/e2e/sciences.spec.ts` — full Playwright walkthrough:
create → list → set planet production via the Research sub-row
→ delete.