Lights up the player-defined sciences feature: a table view with sort
and filter, a designer with four percent inputs and a strict
sum-equals-100 gate, and a Research-sub-row integration so the
planet production picker lists the user's sciences alongside the
four tech buttons. Phase 21 decisions are baked back into ui/PLAN.md
(no UpdateScience on the wire — write-once via createScience +
removeScience; percentages instead of fractions; sciences live under
the existing Research segment).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two follow-up fixes after the initial Phase 19 landing:
1. The IncomingGroup dashed trajectory was drawn between raw
(x1, y1) and (x2, y2) world coordinates. On torus wrap mode
this took the long way around when origin and destination
sat near opposite seams. The line now picks endpoints via
`torusShortestDelta` so the segment crosses the seam when
that's the shorter visual path. The interpolated clickable
point follows the same unwrapped vector. The same helper
fixes the in-hyperspace position for local / foreign groups.
2. On-planet local and foreign groups previously rendered as
small offset points next to every populated planet, which
turned the canvas into noise as soon as a player held more
than a handful of planets. The map no longer renders any
in-orbit group; the planet inspector grows a compact
"stationed ship groups" subsection
(`lib/inspectors/planet/ship-groups.svelte`) that lists
each in-orbit group as a row of `<race> · <class> · <count>
ships · <mass>`. Race attribution: LocalGroup → the player's
race, OtherGroup on a foreign-owned planet → the planet's
owner, OtherGroup elsewhere → "foreign" placeholder. Rows
are non-interactive in Phase 19; Phase 21+ will deep-link
into the ship-groups table view with a (planet, race) filter.
Tests:
- `state-binding-groups.test.ts` swaps the on-planet rendering
expectation for the new "no map primitive" rule, and adds a
regression that asserts the incoming line crosses the torus
seam via `torusShortestDelta`.
- new `inspector-planet-ship-groups.test.ts` covers row
composition, the destination-mismatch filter, the
in-hyperspace exclusion, the foreign-planet owner fallback,
and the empty-state collapse.
- `inspector-planet.test.ts` and `inspector-ship-group.spec.ts`
pick up the new prop chain (`localShipGroups`,
`otherShipGroups`, `localRace`).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
Adds the second end-to-end command (`setProductionType`) with a
collapse-by-`planetNumber` rule on the order draft, the segmented
production-controls component on the planet inspector, the FBS
encoder/decoder pair for `CommandPlanetProduce`, and the
`localShipClass` projection on `GameReport`. Forecast number is
deferred and tracked in the new `ui/docs/calc-bridge.md`.
Wires the first end-to-end command through the full pipeline:
inspector rename action → local order draft → user.games.order
submit → optimistic overlay on map / inspector → server hydration
on cache miss via the new user.games.order.get message type.
Backend: GET /api/v1/user/games/{id}/orders forwards to engine
GET /api/v1/order. Gateway parses the engine PUT response into the
extended UserGamesOrderResponse FBS envelope and adds
executeUserGamesOrderGet for the read-back path. Frontend ports
ValidateTypeName to TS, lands the inline rename editor + Submit
button, and exposes a renderedReport context so consumers see the
overlay-applied snapshot.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Plumbs the map → inspector pathway: a click on a planet selects it
through the new SelectionStore, the sidebar Inspector tab swaps
its empty-state copy for a per-kind read-only field set, and a
mobile-only bottom-sheet mirrors the same content over the map.
Field projection in api/game-state.ts now surfaces every documented
planet field.