feat(ui): F8-12 — map polish (zoom invariance, labels, selection, soft radius) (#55)
Tests · UI / test (push) Waiting to run
Tests · UI / test (pull_request) Failing after 5m16s

* 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>
This commit is contained in:
Ilia Denisov
2026-05-27 23:51:16 +02:00
parent ba93a9092e
commit 680ebac919
30 changed files with 1240 additions and 322 deletions
@@ -17,7 +17,7 @@ import type {
ReportPlanet,
ReportUnidentifiedShipGroup,
} from "../src/api/game-state";
import { BATTLE_MARKER_ID_PREFIX, BOMBING_MARKER_ID_PREFIX } from "../src/map/battle-markers";
import { BATTLE_MARKER_ID_PREFIX } from "../src/map/battle-markers";
import { SHIP_GROUP_ID_OFFSETS } from "../src/map/ship-groups";
import { reportToWorld } from "../src/map/state-binding";
import { EMPTY_SHIP_GROUPS } from "./helpers/empty-ship-groups";
@@ -200,7 +200,7 @@ describe("reportToWorld — categories", () => {
expect(categories.get(unidentifiedId)).toBe("unidentifiedGroup");
});
test("battle markers and bombing markers each carry their own category", () => {
test("battle markers carry the battleMarker category", () => {
const { categories } = reportToWorld(
makeReport({
planets: [
@@ -208,6 +208,9 @@ describe("reportToWorld — categories", () => {
makePlanet({ number: 2, kind: "other", x: 200, y: 200 }),
],
battles: [makeBattle({ id: "b1", planet: 2 })],
// F8-12 / #30: bombings no longer emit their own
// primitives — the planet outline is drawn by
// `setPlanetOutlines` from the map view.
bombings: [makeBombing({ planetNumber: 2 })],
}),
);
@@ -216,8 +219,6 @@ describe("reportToWorld — categories", () => {
const battleB = BATTLE_MARKER_ID_PREFIX | (0 << 4) | 1;
expect(categories.get(battleA)).toBe("battleMarker");
expect(categories.get(battleB)).toBe("battleMarker");
const bombingId = BOMBING_MARKER_ID_PREFIX | 0;
expect(categories.get(bombingId)).toBe("bombingMarker");
});
});
@@ -235,7 +236,7 @@ describe("reportToWorld — planetDependents", () => {
expect(planetDependents.get(7)?.has(7)).toBe(true);
});
test("battle / bombing markers cascade onto their anchor planet", () => {
test("battle markers cascade onto their anchor planet", () => {
const { planetDependents } = reportToWorld(
makeReport({
planets: [
@@ -243,17 +244,18 @@ describe("reportToWorld — planetDependents", () => {
makePlanet({ number: 2, kind: "other", x: 200, y: 200 }),
],
battles: [makeBattle({ planet: 2 })],
// Bombings are still in the report but no primitive
// rides the cascade now — they paint a planet outline
// straight from `map.svelte`.
bombings: [makeBombing({ planetNumber: 2 })],
}),
);
const battleA = BATTLE_MARKER_ID_PREFIX | (0 << 4) | 0;
const battleB = BATTLE_MARKER_ID_PREFIX | (0 << 4) | 1;
const bombingId = BOMBING_MARKER_ID_PREFIX | 0;
const deps = planetDependents.get(2) ?? new Set();
expect(deps.has(2)).toBe(true);
expect(deps.has(battleA)).toBe(true);
expect(deps.has(battleB)).toBe(true);
expect(deps.has(bombingId)).toBe(true);
});
test("in-space groups cascade onto their destination planet", () => {