Documentation

Rendering and maintenance

v1 · Deprecated

Framework v1 is deprecated for new games. Existing games remain playable and editable.

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.global mutates.

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.

Need a hand with this?

Join our Discord