Overlays
renderOverlay
A full-bleed, pointer-events: none layer drawn above the board. Use for connector lines (attacker → defender), badges that need to sit outside their piece's bounds, or arrows that span multiple spaces.
import type { RenderOverlayFn } from "boardweaver";
export const renderOverlay: RenderOverlayFn = (state, viewingPlayerId, clientState, overlay) => {
const from = overlay.rectOf({ piece: "attacker" });
const to = overlay.rectOf({ piece: "defender" });
if (!from || !to || from.visibility === "clipped" || to.visibility === "clipped") return [];
const start = { x: from.rect.x + from.rect.width / 2, y: from.rect.y + from.rect.height / 2 };
const end = { x: to.rect.x + to.rect.width / 2, y: to.rect.y + to.rect.height / 2 };
return [
{
type: "Svg",
style: { position: "absolute", top: 0, left: 0, width: "100%", height: "100%" },
viewBox: { width: 1000, height: 1000 },
paths: [
{ d: `M ${start.x} ${start.y} L ${end.x} ${end.y}`, stroke: "#ff3b3b", strokeWidth: 3 },
],
},
];
};
Signature:
type RenderOverlayFn<MetaData = Record<string, unknown>> = (
gameState: GameState<MetaData>,
viewingPlayerId: number,
clientState: GlobalClientStateReadView,
overlay: Overlay,
) => OverlayBwssNode[];
Returns a flat array. Drawn in array / paint order (no z-index). Top-level nodes position themselves with style.position: "absolute" + top / left.
OverlayBwssNode is the piece-side BWSS union: Box | Image | Svg. Connectors are Svg nodes : BWSS has no Line node, but Svg supports viewBox + path d. See styling.
Overlay resolver
type OverlayHandle =
| { piece: string } // piece id (registered by the Piece renderer)
| { space: string } // space id (registered by the Space renderer)
| { player: number }; // player area (registered by the PlayerInfo renderer)
type OverlayRect = { x: number; y: number; width: number; height: number };
type OverlayClip = OverlayRect | null;
type ResolvedTarget = {
rect: OverlayRect;
clip: OverlayClip;
visibility: "visible" | "clipped";
};
type Overlay = {
rectOf(handle: OverlayHandle): ResolvedTarget | null;
clampToClip(point: { x: number; y: number }, clip: OverlayClip): { x: number; y: number };
};
Coordinates are in overlay-layer local px; {x:0,y:0} is the top-left of the board area.
rectOf is a synchronous host function : calling it subscribes this overlay to re-run when:
- That handle's rect changes (resize / scroll / mount).
- The overlay element registry version bumps (mounts / unmounts).
- The viewer's
clientState.globalmutates.
A null return (handle present but element unmounted) still subscribes; the overlay re-fires when the element mounts.
clampToClip per-axis clamps a point into a clip rect. clip: null returns the point unchanged.