PURPOSE

Renders a visual overlay of named anchor points on top of the weapon-workbench preview canvas. Each anchor in the supplied AnchorMap is drawn as a colored crosshair with a label showing its name and kind, so the author can see where attachment points live on a sprite. Anchor positions are synthesized at author time by laying them out in a grid across the preview, because real world positions are not available outside the running engine.

OWNS

  • The AnchorOverlay exported React component.
  • The KIND_COLOR lookup mapping each AnchorKind (pivot, edge, radial) to a hex color string used for the crosshair and label.
  • The KIND_TOOLTIP lookup mapping each AnchorKind to a short human-readable description shown as the marker’s title tooltip.
  • The anchorScreenPos helper that converts an (index, total, size) triple to an { x, y } screen position by row-major grid placement with a fixed margin and a column count of ceil(sqrt(total)).
  • The inline-styled DOM: an absolutely positioned container sized to the preview, plus one absolutely positioned marker per entry containing a 12x12 crosshair (two colored bars) and a monospace label with a double black textShadow.
  • The empty-state handling when total === 0, returning the canvas center.
  • The non-rendering early return when visible is false.

READS FROM

  • @engine/vfx-workbench/anchor-types for the AnchorMap and AnchorKind types.
  • The Props it receives:
    • anchors: AnchorMap — the dictionary of anchor specs keyed by name; iterated via Object.entries.
    • size: number — the side length of the square preview area; controls both the container box and the grid layout math.
    • visible: boolean — gate; when false the component renders nothing.
  • For each entry, the spec’s kind field is used to look up color and tooltip; the entry key is used as the displayed anchor name.

PUSHES TO

  • The DOM only. Renders into whatever parent positions it; the root container uses position: absolute at top: 0, left: 0 with the configured size, so it expects to overlay a relatively-positioned preview parent.
  • No Zustand store writes, no events, no engine calls, no network, no side effects.

DOES NOT

  • Does not compute real anchor world positions. Grid placement is decorative and does not reflect the sprite’s actual pivot, edge, or radial geometry.
  • Does not read sprite, ship, weapon, or VFX data; only consumes the AnchorMap passed in by the parent.
  • Does not handle input or selection. Markers have pointerEvents: 'all' so the browser shows the native title tooltip on hover, but there are no click, drag, or keyboard handlers.
  • Does not animate. There are no transitions, timers, or effects.
  • Does not validate that spec.kind is a known key — an unknown kind would produce undefined color and tooltip.
  • Does not subscribe to any store. Re-renders only when its props change.
  • Does not memoize or use refs.

Signals

  • data-testid="anchor-overlay" on the root container.
  • data-testid={anchor-marker-${name}} on each per-anchor marker, where name is the AnchorMap key.
  • title attribute on each marker carries the kind tooltip text for hover.
  • Each anchor’s color and kind label are derived purely from spec.kind.

Entry points

  • Default React component invocation: <AnchorOverlay anchors={...} size={...} visible={...} />.
  • Imported by the weapon-workbench preview composition that owns the sprite canvas and toggles overlay visibility.

Pattern notes

  • Pure presentational component: no hooks, no state, no effects. Re-rendering is the only update path.
  • Color and tooltip use Record<AnchorKind, string> lookups so adding a new AnchorKind is a type error until both maps are updated.
  • Grid layout uses Math.ceil(Math.sqrt(total)) columns and row-major insertion order; ordering is stable as long as Object.entries order on the AnchorMap is stable.
  • The margin = 16 and the 12x12 crosshair / 2px bar dimensions are inline constants; there is no theming hook.
  • Container is pointerEvents: 'none', individual markers re-enable pointerEvents: 'all' so hover tooltips work without blocking interaction with the rest of the preview surface.
  • Labels use a double 0 0 3px #000 textShadow for legibility against arbitrary preview backgrounds.
  • Empty-map and hidden states are handled by short-circuit returns rather than conditional content inside the JSX tree.