Render Pipeline

How YAML frontmatter becomes an SVG board diagram. One function call, no translation layers.

Overview

The pipeline has four stages:

Frontmatter YAML
    ↓ parseFrontmatter()
Parsed meta object
    ↓ cascadeResolve({ surface, family, variant })
Resolved cascade (topology + surface + render + setup + pieces)
    ↓ renderFromEngine(resolved, { pieceImages })
SVG string

The entry point is renderFromEngine() in packages/render/src/render-engine.js. It takes a resolved cascade object and returns a complete SVG string.

Cascade resolver

packages/schema/src/cascade-resolver.js deep-merges three layers:

  1. Surface — named palette resolved to colours object
  2. Family — from the game's rulebook.md engine block
  3. Variant — from the specific variant's engine block

The result is a flat object with topology, surface, render, setup, pieces, players, components, and meta fields. Variant values override family values; family values override surface defaults.

produceLayout

packages/schema/src/produce-layout.js takes the resolved cascade and produces a layout configuration object for the appropriate topology renderer. It handles:

Output: { type, config } where config is everything the topology renderer needs.

Topology renderers

Each topology exports a render function that produces an element array:

renderGridLayout(rows, cols, config) → { width, height, elements, cells, labels }
renderHexLayout(config)             → { width, height, elements }
renderGraphLayout(config)           → { width, height, elements }
renderPitLayout(config)             → { width, height, elements }
renderTrackLayout(config)           → { width, height, elements }

Elements are plain objects: { tag: 'rect', attrs: { x, y, width, height, fill } }. They're serialised to SVG strings by serialize-layout.js.

renderFromEngine

The orchestrator. It:

  1. Clones the render object (prevents mutation leaking between calls)
  2. Handles multi-board games (renders each layer recursively)
  3. Suppresses labels for xiangqi/shogi (matching legacy behaviour)
  4. Prepares hex/pit/track internal state (parsed setups, piece images)
  5. Calls produceLayout() → topology renderer
  6. Assembles the SVG: viewport, elements, overlays, pieces, labels

Piece rendering

For grid topologies, pieces are rendered externally (after the board). The engine:

  1. Parses the setup string into a position map (FEN, SFEN, FEN4, vocabulary)
  2. Resolves piece images from the gallery (FEN char → piece ID → SVG path)
  3. Places <image> elements at cell coordinates
  4. Falls back to <circle> for stone/man/king types without images

For hex/pit/track/graph, pieces are rendered internally by the topology renderer (via _pieceImages and _parsedSetup on the render config).

Multi-board

Games like Alice Chess have multiple boards. When topology.layers is set and setup is an array of FENs, the engine renders each board as a separate layer and composites them side-by-side with labels.

topology:
  type: grid
  rows: 8
  cols: 8
  layers: 2
  layer_labels: [Board A, Board B]
setup:
  - rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR
  - 8/8/8/8/8/8/8/8