Files
galaxy-game/ui/docs/cargo-routes-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

170 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Cargo routes UX
This document covers the cargo-route surface: the four-slot
inspector subsection, the map-driven destination pick, and the
optimistic overlay that keeps the inspector and the map in lock-step
with the local order draft. The engine semantics are quoted from
[`game/rules.txt`](../../game/rules.txt) section "Грузовые маршруты"
(lines 808843); this file is the source of truth for how the UI
surfaces those rules.
## Engine semantics in one paragraph
A cargo route on a planet you own pairs a load-type slot
(`COL`/`CAP`/`MAT`/`EMP`) with a destination planet. Once set, the
engine loads transport ships at the source on every turn cutoff and
sends them to the destination, draining the load-type stockpile
("colonists" → population pool, "capital" → industry crates, "mat" →
raw materials, "empty" → ships returning unloaded). When several
slots are configured the engine processes them in
`COL > CAP > MAT > EMP` priority order
(`game/internal/controller/route.go.SendRoutedGroups`, line 101).
Routes are constrained by reach: the destination must be no further
than `40 × driveTech` world units along the torus-shortest path
(`util.ShortDistance``Race.FlightDistance()`), and a route whose
destination becomes unreachable at the next turn is auto-removed
(`RemoveUnreachableRoutes`).
## Four-slot inspector subsection
The cargo-routes subsection renders below the production controls
on every owned planet inspector. Slots appear in
`CARGO_LOAD_TYPE_VALUES` order (COL, CAP, MAT, EMP) so visual order
matches the engine's load priority — players who scan top-down see
the highest-priority cargo first.
Slot states:
- **Empty** — `(no route)` text plus a single `Add` button.
- **Filled** — `→ {destination name}` plus `Edit` and `Remove`.
`Add` and `Edit` open a renderer-driven destination pick (see next
section). `Remove` emits a `removeCargoRoute` command. The collapse
rule on the order draft store ensures only one entry per
`(source, loadType)` slot survives in the draft at any time, so a
sequence of `Add → Edit → Remove` collapses to the latest verb only
(matching the production-controls pattern).
Disabled state: every button is disabled when the
`OrderDraftStore` or `MapPickService` context is missing (the
component is mounted outside the in-game shell, in tests, etc.).
## Map-driven destination pick
The picker is renderer-side: the inspector calls
`MapPickService.pick({sourcePlanetNumber, reachableIds})` and awaits a
planet number (or `null` on cancel). Reach is computed inline against
`localPlayerDrive` — see the calc-bridge waiver in
[`calc-bridge.md`](./calc-bridge.md).
While a session is active:
- All planets whose ids are not in `reachableIds` and are not the
source render at `alpha = 0.3`. The visual fade signals "not a
valid destination" without removing the planet from the map.
- The source planet keeps full alpha and gains an outline ring so
the player sees where the route originates.
- A line is drawn from the source to the cursor. On touch devices
the line follows the finger during drag.
- Hover over a reachable planet adds an outline highlight at
`pointRadiusPx + 4` world units so the player can confirm which
planet they are about to pick.
Resolution paths:
- Click on a reachable planet → `setCargoRoute` enters the draft;
the inspector slot fills; the arrow appears immediately on the
map (overlay route applied on the next render).
- Click on empty space or a non-reachable planet → no-op (a
forgiving rule for accidental taps mid-pan).
- Press Escape, click the inspector's `Cancel pick` button, or
unmount the active map view (e.g. switch tools, navigate away)
→ session cancels; the slot stays as it was.
The inspector renders an inline status line `pick a destination on
the map (Esc to cancel)` while the session is active so the player
knows the click target and the cancel hotkey. The line vanishes as
soon as the picker resolves.
## Optimistic map overlay
`applyOrderOverlay` projects every locally-valid, in-flight, or
applied `setCargoRoute` / `removeCargoRoute` onto `report.routes`,
re-runs `reportToWorld` (which appends `LinePrim` arrows from
`buildCargoRouteLines`), and remounts the renderer when the routes-
content fingerprint changes. The map active view captures camera
centre + zoom before each remount and restores them when the game
id is unchanged, so adding a route mid-pan does not jolt the view.
Arrows are drawn as a shaft plus two short arrowhead wings. Per-type
styling (visual refinements are deferred to the finalization plan
(../PLAN-finalize.md)):
| Load type | Stroke colour | Notes |
| --------- | ------------- | ------------------------ |
| COL | `#4FC3F7` | brightest blue, highest priority |
| CAP | `#FFB74D` | warm orange |
| MAT | `#81C784` | green |
| EMP | `#90A4AE` | dim grey, thinner stroke |
Arrow priority (COL=8, CAP=7, MAT=6, EMP=5) sits above all planet
priorities (1..4), so two arrows that overlap exactly resolve to the
higher-priority load type during hit-test. Planet primitives still
win over arrows because the line ids carry a high-bit prefix
(`0x80000000`) and the renderer keeps points at the kind tie-break
position 0.
## Reach computation
Implementation: inline TypeScript using `torusShortestDelta` for
each axis and `Math.hypot` for the distance, compared against `40 ×
localPlayerDrive`. The local player's drive comes from the report's
`Player` block, looked up by `name === report.race`
(`api/game-state.ts.findLocalPlayerDrive`).
The Go-side counterpart is `pkg/calc/race.go.FligthDistance`. The
engine accepts a route from a player-owned planet to **any** planet
inside that distance — own, foreign-race, uninhabited, or
unidentified all qualify
(`game/internal/controller/route.go.PlanetRouteSet` only enforces
ownership of the *origin*). The picker mirrors that contract: the
`reachableSet()` in `cargo-routes.svelte` filters out only the
source planet itself.
Why inline rather than via a Go calc bridge? See the deferral note
in [`calc-bridge.md`](./calc-bridge.md). The formula is trivial
(`tech × 40`) and the WASM glue would be premature infrastructure;
when the calc bridge lands the shared `pkg/calc.FligthDistance` will
replace this implementation.
## Tests
| Layer | File | What it covers |
| ----------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| Order draft collapse | `tests/order-draft.test.ts` | `(source, loadType)` collapse rules across `set` and `remove`. |
| Encode / decode round-trip | `tests/submit.test.ts`, `tests/order-load.test.ts` | `setCargoRoute` and `removeCargoRoute` ↔ FBS payloads; UNKNOWN load-type drops with a warn. |
| Overlay | `tests/order-overlay.test.ts` | `applyOrderOverlay` upserts / drops route entries for valid / submitting / applied statuses. |
| Inspector subsection | `tests/inspector-planet-cargo-routes.test.ts` | Slot rendering, pick invocation, emit, cancel, edit, remove, per-type independence. |
| Map arrows | `tests/map-cargo-routes.test.ts` | `buildCargoRouteLines` shape on torus + no-wrap fixtures, per-type style, priority ordering. |
| End-to-end | `tests/e2e/cargo-routes.spec.ts` | Mocked gateway: open inspector, dim outside reach, pick destination, arrow appears, reload. |
## File index
- `ui/frontend/src/lib/inspectors/planet/cargo-routes.svelte`
inspector subsection.
- `ui/frontend/src/sync/order-types.ts``CargoLoadType`,
`SetCargoRouteCommand`, `RemoveCargoRouteCommand`.
- `ui/frontend/src/sync/order-draft.svelte.ts` — collapse rule.
- `ui/frontend/src/sync/submit.ts` — encoder.
- `ui/frontend/src/sync/order-load.ts` — decoder.
- `ui/frontend/src/api/game-state.ts``routes` and
`localPlayerDrive` decoding plus overlay extension.
- `ui/frontend/src/map/cargo-routes.ts` — arrow geometry.
- `ui/frontend/src/map/state-binding.ts` — appends route lines.
- `ui/frontend/src/lib/active-view/map.svelte` — fingerprint guard
+ camera preservation.
- `ui/frontend/src/map/pick-mode.ts` and
`ui/frontend/src/map/render.ts` — pick-mode foundation.
- `ui/frontend/src/lib/map-pick.svelte.ts` — Svelte adapter.
- `ui/docs/renderer.md` — pick-mode and debug-surface contract.