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:
@@ -1,6 +1,8 @@
|
||||
// Phase 27 battle and bombing markers on the map.
|
||||
//
|
||||
// Two visual markers per planet:
|
||||
// Phase 27 battle markers on the map. Bombing markers used to live
|
||||
// here as a separate ring primitive but F8-12 / #30 turned them into
|
||||
// a planet-outline overlay drawn by `render.ts.setPlanetOutlines`,
|
||||
// driven from `map.svelte`. The remaining surface here is the battle
|
||||
// X-cross:
|
||||
//
|
||||
// * Battle marker — an X cross drawn through the corners of the
|
||||
// square that circumscribes the planet circle. Two yellow
|
||||
@@ -8,18 +10,10 @@
|
||||
// shots: 1 shot → 1px, 100+ shots → 5px (capped). Clicking
|
||||
// either line opens the Battle Viewer for the corresponding
|
||||
// UUID.
|
||||
// * Bombing marker — a thin stroke-only circle slightly larger
|
||||
// than the planet circle. Yellow on damaged planets, red on
|
||||
// wiped planets. Clicking it deep-links to the bombings row in
|
||||
// the Reports view for the planet number.
|
||||
//
|
||||
// Both markers are wired into `state-binding.ts` so they live in the
|
||||
// same `world` / `hitLookup` plumbing as planets and ship groups.
|
||||
|
||||
import type { GameReport, ReportPlanet } from "../api/game-state";
|
||||
import {
|
||||
DARK_THEME,
|
||||
type CirclePrim,
|
||||
type LinePrim,
|
||||
type Primitive,
|
||||
type PrimitiveID,
|
||||
@@ -27,20 +21,17 @@ import {
|
||||
type Theme,
|
||||
} from "./world";
|
||||
|
||||
/** Battle and bombing marker primitive ids use a high-bit prefix to
|
||||
* avoid colliding with planet numbers or cargo-route line ids. */
|
||||
/** Battle marker primitive ids use a high-bit prefix to avoid
|
||||
* colliding with planet numbers or cargo-route line ids. */
|
||||
export const BATTLE_MARKER_ID_PREFIX = 0xa0000000;
|
||||
export const BOMBING_MARKER_ID_PREFIX = 0xc0000000;
|
||||
|
||||
const PLANET_RADIUS_WORLD = 6;
|
||||
const BOMBING_RING_RADIUS = PLANET_RADIUS_WORLD + 3;
|
||||
const BATTLE_CROSS_HALF = PLANET_RADIUS_WORLD + 2;
|
||||
|
||||
/** Battle marker priority sits between planets (1..4) and cargo
|
||||
* routes; the cross is over the planet but loses clicks against the
|
||||
* planet glyph itself. */
|
||||
const BATTLE_MARKER_PRIORITY = 9;
|
||||
const BOMBING_MARKER_PRIORITY = 10;
|
||||
|
||||
const BATTLE_LINE_INDEX_A = 0;
|
||||
const BATTLE_LINE_INDEX_B = 1;
|
||||
@@ -51,21 +42,16 @@ export interface BattleMarkerTarget {
|
||||
planet: number;
|
||||
}
|
||||
|
||||
export interface BombingMarkerTarget {
|
||||
kind: "bombing";
|
||||
planet: number;
|
||||
}
|
||||
|
||||
export type MarkerTarget = BattleMarkerTarget | BombingMarkerTarget;
|
||||
export type MarkerTarget = BattleMarkerTarget;
|
||||
|
||||
/**
|
||||
* MarkerCategory tags every emitted primitive with the toggleable
|
||||
* surface it belongs to so the Phase 29 hide-set machinery can flip
|
||||
* each independently. Battles and bombings have their own toggles —
|
||||
* a player can hide the bombing rings while keeping the battle
|
||||
* crosses visible.
|
||||
* each independently. Battle markers are the only category left here;
|
||||
* the `bombingMarker` toggle now hides the planet-outline overlay
|
||||
* built in `map.svelte.applyPlanetOutlines` (F8-12 / #30).
|
||||
*/
|
||||
export type MarkerCategory = "battleMarker" | "bombingMarker";
|
||||
export type MarkerCategory = "battleMarker";
|
||||
|
||||
export interface BuildMarkersResult {
|
||||
primitives: Primitive[];
|
||||
@@ -92,11 +78,13 @@ export function battleMarkerStrokeWidth(shots: number): number {
|
||||
}
|
||||
|
||||
/**
|
||||
* buildBattleAndBombingMarkers emits battle and bombing marker
|
||||
* primitives plus a hit-lookup mapping for the current-turn report.
|
||||
* Battles whose planet is not visible (e.g. observer-only without a
|
||||
* report.planets entry) are skipped — they have no on-map location
|
||||
* to anchor against.
|
||||
* buildBattleAndBombingMarkers emits battle X-cross primitives plus a
|
||||
* hit-lookup mapping for the current-turn report. Battles whose
|
||||
* planet is not visible (e.g. observer-only without a report.planets
|
||||
* entry) are skipped — they have no on-map location to anchor
|
||||
* against. Bombing visuals are no longer produced here (F8-12 / #30);
|
||||
* the renderer paints them as a planet-outline overlay driven from
|
||||
* `map.svelte.applyPlanetOutlines`.
|
||||
*/
|
||||
export function buildBattleAndBombingMarkers(
|
||||
report: GameReport,
|
||||
@@ -167,32 +155,8 @@ export function buildBattleAndBombingMarkers(
|
||||
addDependent(battle.planet, lineB.id);
|
||||
}
|
||||
|
||||
for (let i = 0; i < report.bombings.length; i++) {
|
||||
const bombing = report.bombings[i];
|
||||
const planet = planetByNumber.get(bombing.planetNumber);
|
||||
if (planet === undefined) continue;
|
||||
const color = bombing.wiped ? theme.bombingWiped : theme.bombingDamaged;
|
||||
const style: Style = {
|
||||
strokeColor: color,
|
||||
strokeAlpha: 0.9,
|
||||
strokeWidthPx: 1.5,
|
||||
};
|
||||
const id = BOMBING_MARKER_ID_PREFIX | i;
|
||||
const ring: CirclePrim = {
|
||||
kind: "circle",
|
||||
id,
|
||||
priority: BOMBING_MARKER_PRIORITY,
|
||||
style,
|
||||
hitSlopPx: 0,
|
||||
x: planet.x,
|
||||
y: planet.y,
|
||||
radius: BOMBING_RING_RADIUS,
|
||||
};
|
||||
primitives.push(ring);
|
||||
lookup.set(id, { kind: "bombing", planet: bombing.planetNumber });
|
||||
categories.set(id, "bombingMarker");
|
||||
addDependent(bombing.planetNumber, id);
|
||||
}
|
||||
|
||||
// Bombing visuals are produced by `setPlanetOutlines` in the
|
||||
// renderer (F8-12 / #30); the data still lives on
|
||||
// `report.bombings`, but no primitive is emitted here.
|
||||
return { primitives, lookup, categories, planetDependents };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user