feat(ui): Phase 29 map visibility toggles
Tests · Go / test (push) Successful in 2m31s
Tests · UI / test (push) Failing after 8m7s

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>
This commit is contained in:
Ilia Denisov
2026-05-19 21:33:53 +02:00
parent 65c0fbb87d
commit 2bd1b54936
32 changed files with 3046 additions and 63 deletions
+84 -21
View File
@@ -3167,28 +3167,44 @@ Targeted tests:
- Playwright e2e: send a message between two seeded players, confirm
arrival.
## Phase 29. Map Toggles
## ~~Phase 29. Map Toggles~~
Status: pending.
Status: done.
Goal: deliver the gear-icon control for hiding categories of map
content and switching between torus and no-wrap view modes. All
toggleable categories are already rendered by earlier phases; this
phase only exposes the controls.
content and switching between torus and no-wrap view modes. LOCAL
planets stay always-on; every other category gets a toggle that
applies within one frame via the renderer's hide-by-id facility.
Artifacts:
- `ui/frontend/src/lib/active-view/map-toggles.svelte` gear icon in
the map view's corner; popover (desktop) / bottom sheet (mobile)
- two sections inside the popover:
- object visibility: hyperspace groups, incoming groups, cargo
routes, reach / visibility zones, battle and bombing markers
- view options: wrap scrolling (torus / no-wrap)
- planets are always rendered and not toggleable
- `ui/frontend/src/lib/map/reach-zones.ts` implementation of reach /
visibility zone overlays, off by default (the only category not yet
rendered by earlier phases)
- toggle state persists per game in `Cache`
- `ui/frontend/src/lib/active-view/map-toggles.svelte` gear icon
in the map view's corner; popover (desktop) / bottom sheet
(mobile). Three fieldsets:
- **Objects** — hyperspace groups, incoming groups,
unidentified groups, cargo routes, battle markers, bombing
markers (six independent checkboxes; battle and bombing have
their own toggles, not a shared one).
- **Planets** — foreign / uninhabited / unidentified kind
toggles plus a `unreachablePlanets` switch that, when off,
hides planets beyond `FlightDistance(localPlayerDrive)` of
every LOCAL planet (torus-aware).
- **View** — visibility-fog checkbox + torus / no-wrap radios.
- `RendererHandle.setHiddenPrimitiveIds(ids)` —
declarative hide set; flips `Graphics.visible` per copy and
threads the set into `hitTest` so click-through to deeper
primitives is correct.
- `RendererHandle.setVisibilityFog(circles)` — fog overlay
drawn via Pixi v8 `Graphics.cut()` per torus copy, below
primitives in z-order.
- `src/map/visibility.ts` — pure helpers (`computeHiddenPlanetNumbers`,
`computeHiddenIds`, `computeFogCircles`, `isCategoryVisible`,
`fingerprintHiddenPlanets`) consumed by the map view.
- `GameStateStore.mapToggles` rune + `setMapToggle` method;
single-blob persistence in cache namespace `game-map-toggles`
(key `{gameId}`, value `{toggles, lastResetTurn}`).
- New-turn reset path inside `setGame` / `advanceToPending`
drops user overrides when `lastResetTurn < currentTurn`.
Dependencies: Phases 9 (no-wrap engine), 11 (planets), 16 (cargo
routes), 19 (groups, incoming), 27 (battle markers).
@@ -3205,11 +3221,58 @@ Acceptance criteria:
Targeted tests:
- Vitest component tests for toggle state persistence;
- Vitest unit tests for reach-zone rendering on torus and no-wrap
fixtures;
- Playwright e2e in desktop and mobile viewports: toggle each
category and the wrap scrolling, assert visual change.
- `tests/visibility-helpers.test.ts` — unit coverage for the
hide-set / fog computation;
- `tests/state-binding-cascade.test.ts` — `reportToWorld` emits
the `categories` + `planetDependents` maps;
- `tests/map-toggles-component.test.ts` — popover lifecycle +
store wiring;
- `tests/map-toggles-state.test.ts` — single-blob persistence +
new-turn reset path against a real fake-IndexedDB cache;
- `tests/map-hit-test.test.ts` — `hitTest` honours the
`hiddenIds` parameter;
- `tests/e2e/map-toggles.spec.ts` — cascade, fog, wrap-mode
camera preservation, reload persistence across the four
Playwright projects.
Decisions:
1. **"Reach zones" reinterpreted as `unreachablePlanets` filter.**
The original plan listed a "reach / visibility zones" category
rendered as concentric circles. The realised stage drops the
circle overlay and instead exposes an inverse
`unreachablePlanets` toggle that hides planets beyond the
player's `FlightDistance`. Reach is already implicit in the
reach-aware destination picker (Phase 16+), so the cleaner UX
is filtering, not adding extra rings.
2. **Visibility fog overlay**. A separate `visibilityFog` toggle
draws a slightly lighter fog over the world outside the union
of `VisibilityDistance` circles around LOCAL planets. The fog
is a renderer-level concept (Pixi `Graphics.cut()`), not a
primitive — it never participates in hit-test.
3. **Per-kind planet toggles + unidentified-group toggle**. The
spec's original "object visibility" list was extended:
foreign / uninhabited / unidentified planet kinds and
unidentified ship groups each get their own toggle.
4. **Battle and bombing markers are independent toggles.** The
spec text grouped them as a single line item; on review the
player wanted finer control, so each kind gets its own
checkbox.
5. **Single-blob persistence + new-turn reset.** Toggles persist
per game as one JSON blob `{toggles, lastResetTurn}` under
`game-map-toggles/{gameId}`. A new server-side turn force-
resets every flag to defaults so a hidden category cannot
silently swallow the next turn's news. History-mode
navigation (`viewTurn`) keeps the shared state.
6. **Hide-by-id renderer extension.** The wrap-mode toggle keeps
the existing remount + camera-preserve path (it has to —
torus copies need different `.visible` flags). Every
visibility flip uses the new `setHiddenPrimitiveIds` / hide-
aware `hitTest` so it applies within one frame.
7. **`pkg/calc/race.go` typo fixed**. The Go-side helper was
`FligthDistance`; the Phase 29 work renamed it to
`FlightDistance` (and the only TS call site duplicates the
formula directly, awaiting a future race-level WASM bridge).
## Phase 30. Calculator Tab