Fixes a black-canvas regression on /map after creating a science in
DEV: when Vite hot-reloads the decoder bump that adds the
`localScience` field, the live in-memory `gameState.report` keeps
its older shape with no such field, so the overlay's
`[...report.localScience]` throws inside the reactive getter and
silently aborts the map view's `$effect`. The fix wraps the spread
and the final return in `?? []` defaults — and matches the
ship-class branches for symmetry — so the overlay stays well-defined
for any partial report shape upstream consumers may carry across an
HMR boundary.
Also adds order-overlay regression tests covering the createScience /
removeScience branches plus the explicit HMR-stale shape, and a
Playwright e2e (sciences-map-regress.spec.ts) replaying the
user-reported flow: /map → /designer/science → save → /map, asserting
no map-mount-error overlay and no console errors.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wires Phase 19's data and rendering layers without yet adding the
inspector UI:
- game-state.ts grows ReportLocalShipGroup / ReportOtherShipGroup
/ ReportIncomingShipGroup / ReportUnidentifiedShipGroup /
ReportLocalFleet types and walks the matching FlatBuffers
vectors (LocalGroup, OtherGroup, IncomingGroup,
UnidentifiedGroup, LocalFleet) inside decodeReport. The Tech
map is folded into the fixed-shape ShipGroupTech struct;
cargo strings normalise to the closed CargoLoadType | "NONE"
union; UUIDs come back as canonical 36-char strings.
- synthetic-report.ts mirrors the new fields so the DEV-only
lobby loader can feed JSON produced by legacy-report-to-json
straight into the live UI surface.
- selection.svelte.ts widens its discriminated union with a
`kind: "shipGroup"` branch carrying a ShipGroupRef
(local UUID / other / incoming / unidentified by index).
- world.ts adds Style.strokeDashPx and render.ts.drawLine
honours it via manual segmentation (PixiJS v8 has no native
dash API). Ignored on points and circles.
- state-binding.ts now returns { world, hitLookup }: the
hit-lookup map keys every primitive id back to a concrete
HitTarget so the click handler can dispatch to selectPlanet
or selectShipGroup. Ship-group primitives live in a separate
ship-groups.ts that emits one point per local / other /
unidentified group, plus a dashed origin→destination line +
clickable point per incoming group. Position is interpolated
along the trajectory for in-hyperspace groups.
- map.svelte threads the hitLookup into handleMapClick.
Vitest:
- tests/helpers/empty-ship-groups.ts exposes EMPTY_SHIP_GROUPS
so existing fixtures can spread the new five empty arrays
without enumerating every field.
- state-binding-groups.test.ts covers each group variant's
primitive geometry and lookup correctness.
- All previously-existing fixture builders pick up the spread
so GameReport stays a complete object.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wires pkg/calc/ship.go into the WASM Core boundary as seven thin
wrappers (DriveEffective, EmptyMass, WeaponsBlockMass, FullMass,
Speed, CargoCapacity, CarryingMass). The ship-class designer reads
Core through a new CORE_CONTEXT_KEY populated by the in-game layout
and renders a five-row preview pane (mass, full-load mass, max
speed, range at full load, cargo capacity) that updates reactively
on every form edit and on the player's localPlayer{Drive,Weapons,
Shields,Cargo} tech levels — three of which are now decoded from
the report's Player block alongside the existing localPlayerDrive.
CarryingMass is the seventh wrapper added to the original six-function
list so that "full-load mass" composes through pkg/calc/ functions
without putting math in TypeScript.
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`.
Replaces the manual Submit button with an auto-sync pipeline driven
by `OrderDraftStore`: every successful add / remove / move
coalesces a `submitOrder` call so the engine always mirrors the
local draft. Removing the last command sends an empty cmd[] PUT —
the engine, repo, and rest model now accept that as a valid
"player cleared their draft" state.
`hydrateFromServer` is now invoked unconditionally on game boot so
a fresh device picks up the player's stored order, and the local
cache is overwritten by the server's view (server is the source of
truth).
Header replaces the static "race ?" + turn counter with a single
headline string `<race> @ <game>, turn <n>`, sourced from the
engine's Report.race + the lobby's GameSummary.gameName + the live
turn number, with a `?` fallback while any piece is loading.
Tests:
- engine: empty PUT round-trips, repo round-trips empty Commands
- order-draft: auto-sync sends full draft on every mutation,
rejected response surfaces error sync status, rapid mutations
coalesce, server hydration overwrites cache
- order-tab: per-row status flips through the auto-sync lifecycle,
remove → empty cmd[] PUT, rejected → retry button
- inspector overlay: applied + valid + submitting all participate
in the optimistic projection
- header: live race / game / turn rendering with fall-back
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>