* Bug fix: theme flip no longer leaves planets oversized. The
camera-preserving remount now calls a new
`RendererHandle.refreshCameraDerivedDraws` explicitly after the
manual moveCenter/setZoom pair so the post-mount geometry tracks
`viewport.scaled` even if pixi-viewport's `'zoomed'` listener
races the next Ticker tick.
* Doc #3: clicks on a planet label route through the same hit-test
path as a click on the disc. The label `Container` now has a
pointer hit area sized to the text + frame padding; pointertap
simulates a click at the planet centre, so selection and
pick-mode resolution behave identically.
* Doc #4: battle X-crosses + cargo arrowhead wings grow
sub-linearly with zoom (PLANET_SIZE_ZOOM_ALPHA). New
`Style.softLengthAnchor` ('center' / 'start') makes the renderer
treat the recorded endpoints as the geometry "at the reference
scale" and rescale around the midpoint (X-cross) or the start
endpoint (arrow wings). Arrowhead base length is halved from 6
to 3 world units to match the owner's "in half" request.
* Doc #5: picker overlay loses the anchor ring at the source, the
cursor line drops to a cargo-route-thin 0.6 px stroke, and the
hover ring around the destination is replaced by a planet-style
outline (visible disc + 1 px padding) in the `pickHighlight`
accent — so candidate destinations read like selection in warm
yellow.
* Doc #6: regression test pins the in-disc hit zone.
* Perf #1: camera-driven redraws are throttled onto the next
Ticker tick. A rapid wheel / pinch burst now coalesces into at
most one `clear() + redraw` pass per painted frame, which keeps
the 500-planet map responsive on zoom and toggle flips.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Planet size formula moves to pixel-space:
`pointRadiusBasePx = 2 + 2 * cbrt(size / SIZE_NORMALIZER)`. The
on-screen disc now reads ~4-7 px at the reference zoom regardless
of how large the world rectangle is — the previous `world-units`
formulation blew up on small maps and made Source-class planets
swallow their neighbours.
* Labels + outlines live in the origin copy only. The 9× replication
across torus copies was the dominant cost on a 100+ planet map
(Pixi.Text creation + Graphics rebuilds on every zoom step); the
origin-copy layout is what the camera-wrap listener guarantees
the user actually sees.
* `setPlanetLabels` and `setPlanetOutlines` skip Pixi-object
rebuilds when the input fingerprint is unchanged — toggle flips
and selection changes now keep the existing Text / Graphics
instances alive and only repaint the affected pieces.
* `renderer.md` updated to the new contract.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Honest pixel-space sizing for `pointRadiusPx` / `strokeWidthPx`: the
renderer divides by the current camera scale on every
`viewport.zoomed` so thin lines / small markers stay the same on-screen
size at any zoom.
* Known-size planets switch to `pointRadiusWorld`, softened against the
reference scale by `PLANET_SIZE_ZOOM_ALPHA = 0.33`; unidentified
planets pin to a 3-px disc.
* New planet label layer renders a two-line `name / #N` legend under
each planet (`#N` only for unidentified or when the new `planetNames`
toggle is off). Selection now paints an inverse-fill frame around the
selected planet's label plus an outline on the disc; the old
selection-ring primitive is retired.
* Bombing markers swap the separate CirclePrim for a planet-outline
overlay (damaged / wiped colour); the report deep-link moves to a
"view bombing report" link in the planet inspector.
* Docs + tests follow: `renderer.md` reflects the new sizing contract +
label / outline layers, vitest covers the sizing math, label
formatting, and the new toggle, and the map-toggles e2e adds a
persistence case for `planetNames`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the gear-icon popover on the map view with per-game persistence
of every category toggle plus the wrap-mode radio. Hide-by-id and
visibility-fog facilities land on the renderer so every flip applies
within one frame without a Pixi remount; the wrap-mode toggle keeps
its existing remount + camera-preserve path. A new server-side turn
force-resets every flag to defaults so a hidden category never makes
the player miss the next turn's news.
Also fixes the FligthDistance → FlightDistance typo in pkg/calc/race.go
(plus the single Go caller); the TS side keeps duplicating the formula
until a race-level WASM bridge lands.
Co-Authored-By: Claude Opus 4.7 (1M context) <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>
Stand up the vector map renderer in ui/frontend/src/map/ on top of
PixiJS v8 + pixi-viewport@^6. Torus mode renders nine container
copies for seamless wrap; no-wrap mode pins the camera at world
bounds and centres on an axis when the viewport exceeds the world
along that axis. Hit-test is a brute-force pass with deterministic
[-priority, distSq, kindOrder, id] ordering and torus-shortest
distance, validated by hand-built unit cases.
The development playground at /__debug/map exposes a window
debug surface for the Playwright spec, which forces WebGPU on
chromium-desktop, WebGL on webkit-desktop, and accepts the
auto-picked backend on mobile projects.
Algorithm spec lives in ui/docs/renderer.md, which also pins the
new deprecation status of galaxy/client (the entire Fyne client
module, including client/world). client/world/README.md and the
Phase 9 stub in ui/PLAN.md gain matching deprecation banners.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>