ui/phase-16: cargo routes inspector + map pick foundation
Add per-planet cargo routes (COL/CAP/MAT/EMP) to the inspector with a renderer-driven destination picker (faded out-of-reach planets, cursor-line anchor, hover-highlight) and per-route arrows on the map. The pick-mode primitives are exposed via `MapPickService` so ship-group dispatch in Phase 19/20 can reuse the same surface. Pass A — generic map foundation: - hit-test now sizes the click zone to `pointRadiusPx + slopPx` so the visible disc is always part of the target. - `RendererHandle` gains `onPointerMove`, `onHoverChange`, `setPickMode`, `getPickState`, `getPrimitiveAlpha`, `setExtraPrimitives`, `getPrimitives`. The click dispatcher is centralised: pick-mode swallows clicks atomically so the standard selection consumers do not race against teardown. - `MapPickService` (`lib/map-pick.svelte.ts`) wraps the renderer contract in a promise-shaped `pick(...)`. The in-game shell layout owns the service so sidebar and bottom-sheet inspectors see the same instance. - Debug-surface registry exposes `getMapPrimitives`, `getMapPickState`, `getMapCamera` to e2e specs without spawning a separate debug page after navigation. Pass B — cargo-route feature: - `CargoLoadType`, `setCargoRoute`, `removeCargoRoute` typed variants with `(source, loadType)` collapse rule on the order draft; round-trip through the FBS encoder/decoder. - `GameReport` decodes `routes` and the local player's drive tech for the inline reach formula (40 × drive). `applyOrderOverlay` upserts/drops route entries for valid/submitting/applied commands. - `lib/inspectors/planet/cargo-routes.svelte` renders the four-slot section. `Add` / `Edit` call `MapPickService.pick`, `Remove` emits `removeCargoRoute`. - `map/cargo-routes.ts` builds shaft + arrowhead primitives per cargo type; the map view pushes them through `setExtraPrimitives` so the renderer never re-inits Pixi on route mutations (Pixi 8 doesn't support that on a reused canvas). Docs: - `docs/cargo-routes-ux.md` covers engine semantics + UI map. - `docs/renderer.md` documents pick mode and the debug surface. - `docs/calc-bridge.md` records the Phase 16 reach waiver. - `PLAN.md` rewrites Phase 16 to reflect the foundation + feature split and the decisions baked in (map-driven picker, inline reach, optimistic overlay via `setExtraPrimitives`). Tests: - `tests/map-pick-mode.test.ts` — pure overlay-spec helper. - `tests/map-cargo-routes.test.ts` — `buildCargoRouteLines`. - `tests/inspector-planet-cargo-routes.test.ts` — slot rendering, picker invocation, collapse, cancel, remove. - Extensions to `order-draft`, `submit`, `order-load`, `order-overlay`, `state-binding`, `inspector-planet`, `inspector-overlay`, `game-shell-sidebar`, `game-shell-header`. - `tests/e2e/cargo-routes.spec.ts` — Playwright happy path: add COL, add CAP, remove COL, asserting both the inspector and the arrow count via `__galaxyDebug.getMapPrimitives()`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+111
-22
@@ -1814,22 +1814,77 @@ Verified on local-ci run 16 (`success`, 4273102).
|
||||
Status: pending.
|
||||
|
||||
Goal: configure up to four cargo routes per planet (colonists,
|
||||
industry, materials, empty) through the inspector.
|
||||
industry, materials, empty) through the inspector, with the
|
||||
destination picked directly on the map. Phase 16 also lands the
|
||||
generic map-pick foundation (Pass A) the inspector consumes; Phase
|
||||
19/20 (ship-group dispatch) reuses the same renderer surface.
|
||||
|
||||
Artifacts:
|
||||
Artifacts (Pass A — renderer foundation):
|
||||
|
||||
- `ui/frontend/src/map/pick-mode.ts` carries the `PickModeOptions` /
|
||||
`PickModeHandle` types and the pure `computePickOverlay` helper.
|
||||
- `ui/frontend/src/map/render.ts` extends `RendererHandle` with
|
||||
`setPickMode` / `isPickModeActive` / `getPickState`,
|
||||
`onPointerMove` / `onHoverChange`, and the
|
||||
`getPrimitiveAlpha(id)` debug accessor. The standard `onClick`
|
||||
consumers are gated on the `pickModeActive` flag so the
|
||||
destination click does not also trigger planet selection.
|
||||
- `ui/frontend/src/map/hit-test.ts` widens point matching to
|
||||
`(pointRadiusPx + slopPx) / camera.scale` so hover and click
|
||||
zones match the visible disc; default radius shared via
|
||||
`DEFAULT_POINT_RADIUS_PX = 3`.
|
||||
- `ui/frontend/src/lib/map-pick.svelte.ts` defines the Svelte
|
||||
`MapPickService` (promise-shaped `pick(...)` plus reactive
|
||||
`active`); `lib/active-view/map.svelte` constructs the service
|
||||
and binds a renderer-side resolver that resolves
|
||||
`sourcePlanetNumber` against the current report.
|
||||
- `ui/frontend/src/lib/debug-surface.svelte.ts` registers
|
||||
`getMapPrimitives()` and `getMapPickState()` providers; the
|
||||
DEV-only `__galaxyDebug` surface in
|
||||
`routes/__debug/store/+page.svelte` exposes them so e2e specs
|
||||
can assert the renderer's state without scraping pixels.
|
||||
|
||||
Artifacts (Pass B — feature):
|
||||
|
||||
- `ui/frontend/src/lib/inspectors/planet/cargo-routes.svelte`
|
||||
four-slot UI listing existing routes and offering add / edit /
|
||||
remove
|
||||
- `ui/frontend/src/sync/order-types.ts` extends with
|
||||
`SetCargoRoute` and `RemoveCargoRoute` command variants
|
||||
- destination-planet picker filtered by reach (uses `pkg/calc/` reach
|
||||
function via `ui/core/calc/`)
|
||||
- `ui/frontend/src/map/cargo-routes.ts` renders route arrows on the
|
||||
map between source and destination planet, styled per cargo type
|
||||
- topic doc `ui/docs/cargo-routes-ux.md` capturing the priority
|
||||
semantics from [`rules.txt`](../game/rules.txt) (`colonists → industry → materials →
|
||||
empty`)
|
||||
`CargoLoadType`, `SetCargoRouteCommand`, and
|
||||
`RemoveCargoRouteCommand`. `CARGO_LOAD_TYPE_VALUES` is the
|
||||
priority order (`COL`, `CAP`, `MAT`, `EMP`).
|
||||
- `ui/frontend/src/sync/order-draft.svelte.ts` collapses both
|
||||
variants by `(sourcePlanetNumber, loadType)`; the newer entry
|
||||
supersedes any prior `set` or `remove` for the same slot.
|
||||
- `ui/frontend/src/sync/submit.ts` and
|
||||
`ui/frontend/src/sync/order-load.ts` round-trip the two new
|
||||
variants through `CommandPlanetRouteSet` and
|
||||
`CommandPlanetRouteRemove`. UNKNOWN load-type values drop with
|
||||
a `console.warn`.
|
||||
- `ui/frontend/src/api/game-state.ts` extends `GameReport` with
|
||||
`routes: ReportRoute[]` (decoded from `report.route()` in
|
||||
`CARGO_LOAD_TYPE_VALUES` order) and `localPlayerDrive: number`
|
||||
(looked up via `findLocalPlayerDrive`). `applyOrderOverlay`
|
||||
upserts / drops route entries for valid / submitting / applied
|
||||
cargo-route commands.
|
||||
- `ui/frontend/src/lib/inspectors/planet/cargo-routes.svelte` is
|
||||
the four-slot subsection. `Add` / `Edit` call
|
||||
`MapPickService.pick(...)`; `Remove` emits
|
||||
`removeCargoRoute`.
|
||||
- `ui/frontend/src/map/cargo-routes.ts` builds the `LinePrim`
|
||||
arrows (shaft + two arrowhead wings) per
|
||||
`(source, loadType, destination)` triple. Per-type style and
|
||||
priority (`COL=8` … `EMP=5`); ids prefixed with `0x80000000`
|
||||
to avoid colliding with planet numbers.
|
||||
- `ui/frontend/src/map/state-binding.ts` appends
|
||||
`buildCargoRouteLines(report)` to the world primitives.
|
||||
- `ui/frontend/src/lib/active-view/map.svelte` adds a
|
||||
routes-content fingerprint to the same-snapshot guard and
|
||||
preserves camera centre + zoom across route-driven remounts
|
||||
inside the same game id.
|
||||
- Topic doc `ui/docs/cargo-routes-ux.md` quotes
|
||||
[`rules.txt`](../game/rules.txt) (lines 808–843) and maps
|
||||
semantics to UI; `ui/docs/renderer.md` documents the pick-mode
|
||||
contract; `ui/docs/calc-bridge.md` records the Phase 16 reach
|
||||
waiver (inline TS rather than a calc bridge for one
|
||||
constant-time multiplication).
|
||||
|
||||
Dependencies: Phase 15.
|
||||
|
||||
@@ -1837,20 +1892,54 @@ Acceptance criteria:
|
||||
|
||||
- the user can add, edit, and remove cargo routes through the
|
||||
inspector;
|
||||
- destination picker disables planets outside reach with a tooltip
|
||||
explaining the constraint;
|
||||
- the destination picker happens on the map: out-of-reach planets
|
||||
fade to `α=0.3`, the source gains an anchor ring, the cursor
|
||||
draws a live line to the source, and hover over a reachable
|
||||
planet outlines it. Clicks on non-reachable space are no-ops; a
|
||||
click on a reachable planet emits `setCargoRoute`; Escape
|
||||
cancels;
|
||||
- the four route types are mutually exclusive — only one route per
|
||||
type per source planet;
|
||||
- configured routes are rendered as arrows on the map between source
|
||||
and destination planets, distinguishable per cargo type.
|
||||
- configured routes are rendered as arrows on the map between
|
||||
source and destination planets, distinguishable per cargo type
|
||||
(placeholder colour palette; final values land in Phase 35
|
||||
polish);
|
||||
- the optimistic overlay surfaces draft routes immediately on the
|
||||
map; the camera survives the routes-fingerprint remount so the
|
||||
view does not jolt mid-edit.
|
||||
|
||||
Targeted tests:
|
||||
|
||||
- Vitest unit tests for slot-conflict detection;
|
||||
- Vitest unit tests for cargo-route arrow rendering on torus and
|
||||
no-wrap fixtures;
|
||||
- Playwright e2e: add a route end-to-end, confirm server applies it
|
||||
on next turn and the arrow is visible on the map.
|
||||
- Vitest: `tests/map-hit-test.test.ts` (regenerated for the
|
||||
visible-radius formula), `tests/map-pick-mode.test.ts`
|
||||
(`computePickOverlay` lifecycle),
|
||||
`tests/map-cargo-routes.test.ts`,
|
||||
`tests/inspector-planet-cargo-routes.test.ts`,
|
||||
`tests/state-binding.test.ts` extension,
|
||||
`tests/order-draft.test.ts` extension,
|
||||
`tests/submit.test.ts` and `tests/order-load.test.ts`
|
||||
extensions, `tests/order-overlay.test.ts` extension.
|
||||
- Playwright e2e `tests/e2e/cargo-routes.spec.ts`: open
|
||||
inspector, trigger `Add`, assert dim state via
|
||||
`__galaxyDebug.getMapPickState()`, click a reachable planet,
|
||||
assert `setCargoRoute` shipped + arrow visible via
|
||||
`__galaxyDebug.getMapPrimitives()`. Add a CAP route to confirm
|
||||
slots coexist; Remove COL → arrow gone; reload → restored from
|
||||
`user.games.order.get`.
|
||||
|
||||
Decisions baked into Phase 16 (vs. the original stage description):
|
||||
|
||||
- The destination picker is map-driven, not list-based. The
|
||||
acceptance criterion "disables planets outside reach with a
|
||||
tooltip" is replaced by "fades planets outside reach to
|
||||
`α=0.3` and forbids picking them"; the rendered map is the
|
||||
player's spatial reference, so a list duplicates information
|
||||
the planet already conveys.
|
||||
- Reach is computed inline in TypeScript, not via a `pkg/calc/`
|
||||
Go bridge (`ui/docs/calc-bridge.md` Phase 16 waiver).
|
||||
- Wrap-mode is treated as a per-game property set at map load;
|
||||
the camera-preservation refactor only fires when the
|
||||
routes-fingerprint changes inside the same game id.
|
||||
|
||||
## Phase 17. Ship Classes — CRUD Without Calc
|
||||
|
||||
|
||||
Reference in New Issue
Block a user