diff --git a/ui/docs/renderer.md b/ui/docs/renderer.md
index 78c011c..2417b14 100644
--- a/ui/docs/renderer.md
+++ b/ui/docs/renderer.md
@@ -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`.
diff --git a/ui/frontend/src/lib/active-view/map-toggles.svelte b/ui/frontend/src/lib/active-view/map-toggles.svelte
index 9b74f0c..c5fccdf 100644
--- a/ui/frontend/src/lib/active-view/map-toggles.svelte
+++ b/ui/frontend/src/lib/active-view/map-toggles.svelte
@@ -177,6 +177,15 @@ bottom-tabs bar.
/>
{i18n.t("game.map.toggles.unreachable_planets")}
+