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:
+46
-7
@@ -66,13 +66,48 @@ interface LinePrim extends PrimitiveBase { kind: 'line';
|
||||
|
||||
`radius` is in world units. `style.strokeWidthPx` and
|
||||
`style.pointRadiusPx` are in screen pixels and stay constant under
|
||||
zoom (Pixi's stroke width is in pixel space when the parent
|
||||
container is scaled).
|
||||
zoom — F8-12 / #28 wired the renderer to repaint every affected
|
||||
`Graphics` on every `viewport.zoomed` event with
|
||||
`size_in_world = size_in_pixels / cameraScale`. `displayStrokeWidthWorld`
|
||||
and `displayPointRadiusWorld` (in `src/map/world.ts`) compute those
|
||||
world-space values; the hit-test reads the same helpers so the click
|
||||
zone always matches the visible footprint.
|
||||
|
||||
`style.pointRadiusWorld` is the alternative sizing rule for planet
|
||||
discs with a known `size`: the renderer treats the base radius as
|
||||
world units and softens its growth with the camera scale through
|
||||
`PLANET_SIZE_ZOOM_ALPHA` (0.33). At `scale = scaleRef` (the
|
||||
"whole world fits the viewport" zoom) the visible radius equals the
|
||||
base radius; zooming in grows it sub-linearly so on-screen pixel
|
||||
size scales as `scale^α`. Setting both `pointRadiusWorld` and
|
||||
`pointRadiusPx` ignores the pixel-space field.
|
||||
|
||||
Default hit slop in screen pixels: point=8, circle=6, line=6.
|
||||
These are touch-ergonomic defaults; per-primitive `hitSlopPx > 0`
|
||||
overrides them.
|
||||
|
||||
### Planet label layer
|
||||
|
||||
Independent of the primitive stream, the renderer mounts a per-copy
|
||||
`labelLayer` (F8-12 / #29). `RendererHandle.setPlanetLabels(labels,
|
||||
selectedPlanetId)` replaces the dataset; the renderer keeps each
|
||||
label container at `(planet.x, planet.y + visibleRadius + gapPx)`
|
||||
and at `scale = 1 / cameraScale` so the text reads at the same
|
||||
pixel size regardless of zoom. The selected planet gets an
|
||||
inverse-fill frame around its label, replacing the retired
|
||||
`selection-ring` primitive (F8-12 / #30).
|
||||
|
||||
### Planet outline overlay
|
||||
|
||||
`RendererHandle.setPlanetOutlines(outlines)` paints a thin stroke
|
||||
around the visible disc of any planet number listed in the spec.
|
||||
The map view feeds it the union of bombings (damaged / wiped accent
|
||||
colour, gated by the `bombingMarkers` toggle) and the current
|
||||
selection (`selectionAccent` colour); selection wins on the same
|
||||
planet. The radius follows `displayPointRadiusWorld`, so the
|
||||
outline hugs the disc through every zoom step — softened or
|
||||
pixel-space alike.
|
||||
|
||||
## Theme
|
||||
|
||||
A `Theme` is the renderer's full colour palette: the canvas background
|
||||
@@ -127,11 +162,15 @@ target.
|
||||
|
||||
Per-primitive distance:
|
||||
|
||||
- **Point**: `distSq ≤ (pointRadiusPx + slopWorld)²`. The visible
|
||||
disc is part of the click target — a click on any pixel of the
|
||||
rendered planet registers as a hit, with `slopWorld` adding a
|
||||
small ergonomic margin on top. `pointRadiusPx` defaults to
|
||||
`DEFAULT_POINT_RADIUS_PX = 3` when unset.
|
||||
- **Point**: `distSq ≤ (visibleRadiusWorld + slopWorld)²`. The
|
||||
visible disc is part of the click target — a click on any pixel of
|
||||
the rendered planet registers as a hit, with `slopWorld` adding a
|
||||
small ergonomic margin on top. `visibleRadiusWorld` comes from
|
||||
`displayPointRadiusWorld` (F8-12 / #28 + #31): pixel-space
|
||||
`pointRadiusPx / scale` for unidentified planets and most ship
|
||||
groups, softened-by-zoom `pointRadiusWorld * (scale / scaleRef)^(α-1)`
|
||||
for planets with a known `size`. `pointRadiusPx` defaults to
|
||||
`DEFAULT_POINT_RADIUS_PX = 3` when neither field is set.
|
||||
- **Filled circle**: `distSq ≤ (radius + slopWorld)²` where
|
||||
`radius` is in world units. The circle counts as filled when
|
||||
`style.fillColor` is set and `style.fillAlpha > 0`.
|
||||
|
||||
Reference in New Issue
Block a user