feat(ui): F8-12 — map polish (zoom invariance, labels, selection, soft radius) (#55)
* 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:
@@ -0,0 +1,56 @@
|
||||
// Two-line planet labels drawn on the map under each planet (F8-12 /
|
||||
// issue #55, п.29). The first line shows the planet name when known
|
||||
// and the `planetNames` toggle is on; the second line shows `#N`. For
|
||||
// unidentified or unnamed planets only the `#N` line is rendered.
|
||||
//
|
||||
// Selection is wired through `selectedPlanetId`: the renderer wraps
|
||||
// the selected planet's label in an inverse-fill frame (F8-12 / п.30)
|
||||
// instead of drawing a separate ring around the planet disc — see the
|
||||
// "label-driven selection" branch in `render.ts`.
|
||||
|
||||
import type { GameReport } from "../api/game-state";
|
||||
|
||||
export interface PlanetLabelData {
|
||||
planetNumber: number;
|
||||
x: number;
|
||||
y: number;
|
||||
/**
|
||||
* The primary line: planet name. `null` when the `planetNames`
|
||||
* toggle is off or the planet has no name (unidentified, or a
|
||||
* legacy report row with an empty string). When null the renderer
|
||||
* only paints the secondary `#N` line.
|
||||
*/
|
||||
name: string | null;
|
||||
/** Secondary line — always present. Pre-formatted as `#N`. */
|
||||
numberLabel: string;
|
||||
}
|
||||
|
||||
export interface BuildPlanetLabelsOptions {
|
||||
/** Mirrors `MapToggles.planetNames`. */
|
||||
showNames: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* buildPlanetLabels translates the report's planet list into the
|
||||
* on-map label dataset. The toggle drives whether the name line is
|
||||
* present; for unidentified planets the name is suppressed even when
|
||||
* the toggle is on, because the player has no name to display.
|
||||
*/
|
||||
export function buildPlanetLabels(
|
||||
report: GameReport,
|
||||
opts: BuildPlanetLabelsOptions,
|
||||
): PlanetLabelData[] {
|
||||
const out: PlanetLabelData[] = [];
|
||||
for (const p of report.planets) {
|
||||
const named =
|
||||
opts.showNames && p.kind !== "unidentified" && p.name.length > 0;
|
||||
out.push({
|
||||
planetNumber: p.number,
|
||||
x: p.x,
|
||||
y: p.y,
|
||||
name: named ? p.name : null,
|
||||
numberLabel: `#${p.number}`,
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
Reference in New Issue
Block a user