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:
+79
-6
@@ -116,7 +116,11 @@ target.
|
||||
|
||||
Per-primitive distance:
|
||||
|
||||
- **Point**: `distSq ≤ slopWorld²`.
|
||||
- **Point**: `distSq ≤ (pointRadiusPx + slopWorld)²`. The visible
|
||||
disc is part of the click target — a click on any pixel of the
|
||||
rendered planet registers as a hit, with `slopWorld` adding a
|
||||
small ergonomic margin on top. `pointRadiusPx` defaults to
|
||||
`DEFAULT_POINT_RADIUS_PX = 3` when unset.
|
||||
- **Filled circle**: `distSq ≤ (radius + slopWorld)²` where
|
||||
`radius` is in world units. The circle counts as filled when
|
||||
`style.fillColor` is set and `style.fillAlpha > 0`.
|
||||
@@ -220,6 +224,72 @@ If a future regression requires a programmatic perf gate, the
|
||||
right place is a Tier 2 (release-line) Playwright trace measuring
|
||||
average frame time over a scripted drag.
|
||||
|
||||
## Pick mode
|
||||
|
||||
Phase 16 introduced a generic *map-driven destination pick* the
|
||||
inspector uses for cargo routes and that ship-group dispatch
|
||||
(Phase 19/20) will reuse. The renderer owns the visual lifecycle;
|
||||
the Svelte side wraps it in a promise-shaped service.
|
||||
|
||||
Lifecycle (`RendererHandle.setPickMode(opts)`):
|
||||
|
||||
1. **Open** (`opts !== null`): renderer marks `pickModeActive`,
|
||||
sets `alpha = 0.3` on every primitive whose id is neither the
|
||||
source nor in `reachableIds`, mounts an overlay `Graphics` in
|
||||
the origin tile, and subscribes to pointer-move + hover-change
|
||||
+ viewport `clicked` + document `keydown`.
|
||||
2. **Tick** (every pointer-move and hover transition): the
|
||||
renderer asks `computePickOverlay(opts, cursorWorld,
|
||||
hoveredId, points, allIds)` (`src/map/pick-mode.ts`) for a
|
||||
draw spec — anchor ring + cursor line + optional hover
|
||||
outline + dim set — and re-paints the overlay.
|
||||
3. **Resolve**: a click on a primitive whose id is in
|
||||
`reachableIds` calls `opts.onPick(id)` and tears down. A click
|
||||
on empty space or a non-reachable primitive is a no-op
|
||||
(forgiving for accidental taps mid-pan). Escape (or the
|
||||
imperative `cancel()` on the returned handle) calls
|
||||
`opts.onPick(null)`.
|
||||
4. **Tear down**: alpha overrides are restored, the overlay
|
||||
`Graphics` is destroyed, every listener is detached, and
|
||||
`pickModeActive` returns to `false`. Existing `onClick`
|
||||
subscriptions are gated on `pickModeActive`, so the standard
|
||||
planet-selection path does not fire on the destination click.
|
||||
|
||||
The pure overlay-spec helper lives in `src/map/pick-mode.ts` and
|
||||
is covered by `tests/map-pick-mode.test.ts` without booting Pixi.
|
||||
The Pixi side (alpha mutation, `Graphics` overlay, listener
|
||||
hookup) is exercised in the in-browser e2e specs.
|
||||
|
||||
The Svelte adapter `MapPickService` (`src/lib/map-pick.svelte.ts`)
|
||||
turns the callback contract into `pick(request) → Promise<id |
|
||||
null>`. The map active view (`lib/active-view/map.svelte`)
|
||||
constructs the service, sets `MAP_PICK_CONTEXT_KEY`, and binds a
|
||||
resolver that translates `sourcePlanetNumber` to the underlying
|
||||
`PickModeOptions` (looking up the source coordinates from the
|
||||
current report). Inspector subsections call `service.pick(...)`
|
||||
and react to the resolved id.
|
||||
|
||||
## Debug surface
|
||||
|
||||
The DEV-only `__galaxyDebug` object (defined in
|
||||
`routes/__debug/store/+page.svelte`) exposes
|
||||
`getMapPrimitives()` and `getMapPickState()` so e2e specs can
|
||||
assert the renderer's current state without scraping pixels:
|
||||
|
||||
- `getMapPrimitives()` returns a snapshot of every primitive in
|
||||
the active world: id, kind, priority, current alpha
|
||||
(post-overlay), and the explicit fill / stroke colour from its
|
||||
`Style` (no theme fallback). Tests use this to count cargo
|
||||
arrows or to verify dim state during pick mode.
|
||||
- `getMapPickState()` returns `{ active, sourcePlanetNumber,
|
||||
reachableIds, hoveredId }` — the renderer's view of the
|
||||
current pick session.
|
||||
|
||||
The active map view registers providers on mount via
|
||||
`registerMapPrimitivesProvider` / `registerMapPickStateProvider`
|
||||
in `src/lib/debug-surface.svelte.ts`, deregisters on dispose, and
|
||||
the surface invokes them lazily on every read.
|
||||
|
||||
## Tests
|
||||
|
||||
- `tests/map-math.test.ts` — `clamp`, `torusShortestDelta`,
|
||||
@@ -227,11 +297,14 @@ average frame time over a scripted drag.
|
||||
- `tests/map-no-wrap.test.ts` — `clampCameraNoWrap`,
|
||||
`minScaleNoWrap`, `pivotZoom` (point-under-cursor invariant
|
||||
verified within float64 precision).
|
||||
- `tests/map-hit-test.test.ts` — 22 hand-built cases covering
|
||||
every rule from the algorithm above: hit/miss with default and
|
||||
custom slop, torus wrap copies, filled vs stroked circles,
|
||||
line endpoint clamping, priority/kind/id ordering, scale
|
||||
effect on slop.
|
||||
- `tests/map-hit-test.test.ts` — hand-built cases covering every
|
||||
rule from the algorithm above: hit/miss with default and
|
||||
custom slop (now including `pointRadiusPx`), torus wrap
|
||||
copies, filled vs stroked circles, line endpoint clamping,
|
||||
priority/kind/id ordering, scale effect on slop.
|
||||
- `tests/map-pick-mode.test.ts` — pure-state coverage for
|
||||
`computePickOverlay`: anchor / line / hover-outline / dim-set
|
||||
shape against representative pick configurations.
|
||||
- `tests/e2e/playground-map.spec.ts` — Pixi mount in real
|
||||
browsers, mode toggle, wheel zoom, no-wrap clamp after drag,
|
||||
hit-test plumbing.
|
||||
|
||||
Reference in New Issue
Block a user