{"version":3,"file":"index.cjs","names":[],"sources":["../src/app/dominoTheme.ts","../src/rules/rulesConfig.ts","../src/rules/dominoSet.ts","../src/variants/index.ts","../src/app/pipColors.ts","../src/app/pipLayouts/classic.ts","../src/app/pipGrid.ts","../src/app/pipLayouts/highValue.ts","../src/app/pipLayouts.ts","../src/app/trainLayout.ts","../src/app/lineLayout.ts","../src/app/trainBends.ts","../src/app/hubLayout.ts","../src/app/viewportMath.ts","../src/harness/layoutValidation.ts","../src/harness/trainFixtures.ts","../src/game/generateSampleTrains.ts","../src/rules/placement.ts","../src/ai/types.ts","../src/ai/candidate-generator.ts","../src/ai/heuristics.ts","../src/ai/skill-profiles.ts","../src/ai/policy.ts","../src/ai/create-ai-player.ts","../src/ai/search.ts","../src/harness/dominoFixtures.ts"],"sourcesContent":["import type { CSSProperties, ReactNode } from 'react';\n\nimport type { PipGridSize } from './pipGrid';\n\nexport interface PipRenderContext {\n  value: number;\n  row: number;\n  col: number;\n  gridSize: PipGridSize;\n  color: string;\n  hollow?: boolean;\n  top?: string;\n  left?: string;\n  positionStyle: CSSProperties;\n}\n\nexport interface TileRenderContext {\n  value1: number;\n  value2: number;\n  width: number;\n  height: number;\n  backgroundColor: string;\n  borderColor: string;\n  rotation: number;\n}\n\n/** Optional presentation hooks for domino tiles and pips. */\nexport interface DominoTheme {\n  /** Root class on each tile — use for app-specific CSS modules. */\n  tileClassName?: string;\n  /** Extra data attributes for CSS selectors, e.g. holographic toggles. */\n  tileDataAttributes?: Record<string, string | boolean | number | undefined>;\n  tileStyle?: (ctx: TileRenderContext) => CSSProperties;\n  halfDividerStyle?: (ctx: TileRenderContext) => CSSProperties;\n  /** Merged onto each pip after layout positioning. */\n  pipStyle?: (ctx: PipRenderContext) => CSSProperties;\n  /** Replace the default pip element entirely. */\n  renderPip?: (ctx: PipRenderContext) => ReactNode;\n}\n\nexport const DEFAULT_DOMINO_THEME: DominoTheme = {};\n\nexport function mergeDominoTheme(\n  base: DominoTheme,\n  patch?: DominoTheme\n): DominoTheme {\n  if (!patch) {\n    return base;\n  }\n\n  return {\n    ...base,\n    ...patch,\n    tileDataAttributes: {\n      ...base.tileDataAttributes,\n      ...patch.tileDataAttributes,\n    },\n  };\n}\n\n/** Convert theme tileDataAttributes to React data-* props. */\nexport function themeDataAttributes(\n  attrs?: DominoTheme['tileDataAttributes']\n): Record<string, string> {\n  if (!attrs) {\n    return {};\n  }\n\n  const result: Record<string, string> = {};\n  for (const [key, value] of Object.entries(attrs)) {\n    if (value === undefined || value === false) {\n      continue;\n    }\n    result[`data-${key}`] = value === true ? 'true' : String(value);\n  }\n  return result;\n}\n","/**\n * How a played double must be answered before play continues.\n * - `none`: doubles are ordinary tiles.\n * - `cover`: the double must be answered by one tile on its open end.\n * - `chicken-foot`: the double must grow a full foot (`chickenFoot.toeCount`\n *   answers: the straight center continuation plus the angled side toes).\n */\nexport type DoubleObligation = 'none' | 'cover' | 'chicken-foot';\n\nexport interface ChickenFootConfig {\n  /**\n   * Total toes a double must grow to be satisfied, counting the straight center\n   * continuation as one. Stays 3 by default (center + two ±angle side toes).\n   */\n  toeCount: number;\n  /** Angles (degrees, relative to the train) of the side toes (toeCount - 1). */\n  sideToeAngles: number[];\n}\n\n/**\n * Every knob that governs legal play. Pass a partial override to {@link resolveRules}\n * to get a fully-populated config; unspecified fields fall back to DEFAULT_RULES.\n */\nexport interface RulesConfig {\n  /** Highest pip value in the set (12 → double-12, 18 → double-18, 91 / 190 tiles). */\n  maxPips: number;\n  /** Value of the starting engine double (defaults to maxPips). */\n  engineValue: number;\n  /** Forbid a double immediately following a double in a chain. */\n  allowConsecutiveDoubles: boolean;\n  /** Each physical tile may be placed at most once across all play. */\n  requireUniqueTiles: boolean;\n  /** A tile may only attach where one of its ends matches the open value. */\n  requireSequential: boolean;\n  /** Obligation imposed by playing a double. */\n  doubleObligation: DoubleObligation;\n  chickenFoot: ChickenFootConfig;\n}\n\nexport const DEFAULT_RULES: RulesConfig = {\n  maxPips: 18,\n  engineValue: 18,\n  allowConsecutiveDoubles: false,\n  requireUniqueTiles: true,\n  requireSequential: true,\n  doubleObligation: 'cover',\n  chickenFoot: {\n    toeCount: 3,\n    sideToeAngles: [-45, 45],\n  },\n};\n\n/** Number of answers a double needs to be satisfied under the given rules. */\nexport function requiredDoubleAnswers(config: RulesConfig): number {\n  switch (config.doubleObligation) {\n    case 'chicken-foot':\n      return Math.max(1, config.chickenFoot.toeCount);\n    case 'cover':\n      return 1;\n    case 'none':\n    default:\n      return 0;\n  }\n}\n\n/** Number of angled side-toe slots a double exposes (center is the main line). */\nexport function sideToeSlots(config: RulesConfig): number {\n  if (config.doubleObligation === 'chicken-foot') {\n    return Math.max(0, config.chickenFoot.toeCount - 1);\n  }\n  // Outside chicken-foot, doubles do not branch; covers go on the main line.\n  return 0;\n}\n\n/** Fills in any missing fields from DEFAULT_RULES (engineValue tracks maxPips). */\nexport function resolveRules(overrides: Partial<RulesConfig> = {}): RulesConfig {\n  const maxPips = overrides.maxPips ?? DEFAULT_RULES.maxPips;\n  return {\n    ...DEFAULT_RULES,\n    ...overrides,\n    maxPips,\n    engineValue: overrides.engineValue ?? maxPips,\n    chickenFoot: {\n      ...DEFAULT_RULES.chickenFoot,\n      ...(overrides.chickenFoot ?? {}),\n    },\n  };\n}\n","import { DominoValue } from '@/game/DominoValue';\n\n/**\n * Canonical, order-independent key for a tile. `6-3` and `3-6` are the same\n * physical domino, so they share a key. Used to enforce tile uniqueness.\n */\nexport function tileKey(value1: number, value2: number): string {\n  return value1 <= value2 ? `${value1}:${value2}` : `${value2}:${value1}`;\n}\n\nexport function dominoKey(tile: DominoValue): string {\n  return tileKey(tile.value1, tile.value2);\n}\n\nexport function isDouble(tile: DominoValue): boolean {\n  return tile.value1 === tile.value2;\n}\n\nexport function tileHasValue(tile: DominoValue, value: number): boolean {\n  return tile.value1 === value || tile.value2 === value;\n}\n\n/** The pip value on the opposite end from `value`, or null if it doesn't touch. */\nexport function otherEnd(tile: DominoValue, value: number): number | null {\n  if (tile.value1 === value) return tile.value2;\n  if (tile.value2 === value) return tile.value1;\n  return null;\n}\n\n/**\n * Orients a tile so its `value1` is the end that connects to `connectingValue`.\n * Returns null if the tile has no such end.\n */\nexport function orientForConnection(\n  tile: DominoValue,\n  connectingValue: number\n): DominoValue | null {\n  if (tile.value1 === connectingValue) {\n    return { value1: tile.value1, value2: tile.value2 };\n  }\n  if (tile.value2 === connectingValue) {\n    return { value1: tile.value2, value2: tile.value1 };\n  }\n  return null;\n}\n\n/** Every unique tile in a double-`maxPips` set (e.g. maxPips=12 → 91 tiles). */\nexport function generateDominoSet(maxPips: number): DominoValue[] {\n  const tiles: DominoValue[] = [];\n  for (let a = 0; a <= maxPips; a++) {\n    for (let b = a; b <= maxPips; b++) {\n      tiles.push({ value1: a, value2: b });\n    }\n  }\n  return tiles;\n}\n\n/** Count of tiles in a double-`maxPips` set: (n+1)(n+2)/2 where n = maxPips. */\nexport function dominoSetSize(maxPips: number): number {\n  const n = maxPips + 1;\n  return (n * (n + 1)) / 2;\n}\n","import { resolveRules, type RulesConfig } from '../rules/rulesConfig';\nimport { dominoSetSize } from '../rules/dominoSet';\n\n/** Supported double-N set sizes. */\nexport const DOMINO_SETS = {\n  9: { maxPips: 9, tileCount: dominoSetSize(9), engineValue: 9 },\n  12: { maxPips: 12, tileCount: dominoSetSize(12), engineValue: 12 },\n  15: { maxPips: 15, tileCount: dominoSetSize(15), engineValue: 15 },\n  18: { maxPips: 18, tileCount: dominoSetSize(18), engineValue: 18 },\n} as const;\n\nexport type DominoSetSize = keyof typeof DOMINO_SETS;\n\nconst VALID_SET_SIZES = new Set<number>([9, 12, 15, 18]);\n\n/** Coerce an arbitrary number to the nearest supported set size (defaults to 18). */\nexport function normalizeSetSize(value: number | undefined): DominoSetSize {\n  if (value === 9 || value === 12 || value === 15 || value === 18) {\n    return value;\n  }\n  return 18;\n}\n\n/** Clamp a pip value to [0, maxPips]. */\nexport function clampPipValue(value: number, maxPips: number): number {\n  if (!Number.isFinite(value)) return 0;\n  return Math.max(0, Math.min(maxPips, Math.round(value)));\n}\n\n/** Resolve rules for a supported double-N set. */\nexport function resolveRulesForSet(\n  setSize: DominoSetSize,\n  overrides: Partial<RulesConfig> = {}\n): RulesConfig {\n  const preset = DOMINO_SETS[setSize];\n  return resolveRules({\n    maxPips: preset.maxPips,\n    engineValue: preset.engineValue,\n    ...overrides,\n  });\n}\n\n/** Whether a number is a supported double-N set size. */\nexport function isDominoSetSize(value: number): value is DominoSetSize {\n  return VALID_SET_SIZES.has(value);\n}\n","export interface PipColorStyle {\n  color: string;\n  hollow?: boolean;\n}\n\n/** Partial map of domino values (0–18) to pip styles. */\nexport type PipColorMap = Partial<Record<number, PipColorStyle>>;\n\n/** Standard double-18 domino pip colors by value. */\nexport const DEFAULT_PIP_COLORS: PipColorMap = {\n  0: { color: 'transparent' },\n  1: { color: '#1a1a1a' },\n  2: { color: '#8B1A1A' },\n  3: { color: '#E6B800' },\n  4: { color: '#e8e8e8', hollow: true },\n  5: { color: '#2E8B57' },\n  6: { color: '#2563EB' },\n  7: { color: '#E8A87C' },\n  8: { color: '#DC2626' },\n  9: { color: '#1E3A8A' },\n  10: { color: '#EA580C' },\n  11: { color: '#166534' },\n  12: { color: '#DC2626' },\n  13: { color: '#F472B6' },\n  14: { color: '#64748B' },\n  15: { color: '#7C3AED' },\n  16: { color: '#F59E0B' },\n  17: { color: '#78350F' },\n  18: { color: '#14532D' },\n};\n\n/** @deprecated Use DEFAULT_PIP_COLORS instead. */\nexport const PIP_COLORS = DEFAULT_PIP_COLORS;\n\n/** Merge custom overrides onto the default double-18 color set. */\nexport function mergePipColors(overrides?: PipColorMap): PipColorMap {\n  return { ...DEFAULT_PIP_COLORS, ...overrides };\n}\n\n/** Resolve the pip style for a value when colored pips are enabled. */\nexport function resolvePipStyle(\n  value: number,\n  pipColors?: PipColorMap\n): PipColorStyle | undefined {\n  if (pipColors === undefined) {\n    return undefined;\n  }\n\n  return (\n    pipColors[value] ??\n    DEFAULT_PIP_COLORS[value] ?? { color: '#1a1a1a' }\n  );\n}\n\n/** @deprecated Use resolvePipStyle instead. */\nexport function getPipStyle(value: number): PipColorStyle {\n  return resolvePipStyle(value, DEFAULT_PIP_COLORS)!;\n}\n","import { PipLayoutCell } from '../pipGrid';\n\n/** Canonical pip layouts for values 0–12 (shared across all double-N sets). */\nexport const PIP_LAYOUTS: Record<number, readonly PipLayoutCell[]> = {\n  0: [],\n  1: [{ row: 1, col: 1, gridSize: '3x3' }],\n  2: [\n    { row: 0, col: 2, gridSize: '3x3' },\n    { row: 2, col: 0, gridSize: '3x3' },\n  ],\n  3: [\n    { row: 0, col: 2, gridSize: '3x3' },\n    { row: 1, col: 1, gridSize: '3x3' },\n    { row: 2, col: 0, gridSize: '3x3' },\n  ],\n  4: [\n    { row: 0, col: 0, gridSize: '3x3' },\n    { row: 0, col: 2, gridSize: '3x3' },\n    { row: 2, col: 0, gridSize: '3x3' },\n    { row: 2, col: 2, gridSize: '3x3' },\n  ],\n  5: [\n    { row: 0, col: 0, gridSize: '3x3' },\n    { row: 0, col: 2, gridSize: '3x3' },\n    { row: 1, col: 1, gridSize: '3x3' },\n    { row: 2, col: 0, gridSize: '3x3' },\n    { row: 2, col: 2, gridSize: '3x3' },\n  ],\n  6: [\n    { row: 0, col: 0, gridSize: '3x3' },\n    { row: 0, col: 2, gridSize: '3x3' },\n    { row: 1, col: 0, gridSize: '3x3' },\n    { row: 1, col: 2, gridSize: '3x3' },\n    { row: 2, col: 0, gridSize: '3x3' },\n    { row: 2, col: 2, gridSize: '3x3' },\n  ],\n  7: [\n    { row: 0, col: 0, gridSize: '3x3' },\n    { row: 0, col: 2, gridSize: '3x3' },\n    { row: 1, col: 0, gridSize: '3x3' },\n    { row: 1, col: 1, gridSize: '3x3' },\n    { row: 1, col: 2, gridSize: '3x3' },\n    { row: 2, col: 0, gridSize: '3x3' },\n    { row: 2, col: 2, gridSize: '3x3' },\n  ],\n  8: [\n    { row: 0, col: 0, gridSize: '3x3' },\n    { row: 0, col: 1, gridSize: '3x3' },\n    { row: 0, col: 2, gridSize: '3x3' },\n    { row: 1, col: 0, gridSize: '3x3' },\n    { row: 1, col: 2, gridSize: '3x3' },\n    { row: 2, col: 0, gridSize: '3x3' },\n    { row: 2, col: 1, gridSize: '3x3' },\n    { row: 2, col: 2, gridSize: '3x3' },\n  ],\n  9: [\n    { row: 0, col: 0, gridSize: '3x3' },\n    { row: 0, col: 1, gridSize: '3x3' },\n    { row: 0, col: 2, gridSize: '3x3' },\n    { row: 1, col: 0, gridSize: '3x3' },\n    { row: 1, col: 1, gridSize: '3x3' },\n    { row: 1, col: 2, gridSize: '3x3' },\n    { row: 2, col: 0, gridSize: '3x3' },\n    { row: 2, col: 1, gridSize: '3x3' },\n    { row: 2, col: 2, gridSize: '3x3' },\n  ],\n  10: [\n    { row: 0, col: 0, gridSize: '4x3' },\n    { row: 0, col: 1, gridSize: '4x3' },\n    { row: 0, col: 2, gridSize: '4x3' },\n    { row: 1, col: 0, gridSize: '4x3' },\n    { row: 1, col: 2, gridSize: '4x3' },\n    { row: 2, col: 0, gridSize: '4x3' },\n    { row: 2, col: 2, gridSize: '4x3' },\n    { row: 3, col: 0, gridSize: '4x3' },\n    { row: 3, col: 1, gridSize: '4x3' },\n    { row: 3, col: 2, gridSize: '4x3' },\n  ],\n  11: [\n    { row: 0, col: 0, gridSize: '4x3' },\n    { row: 1, col: 0, gridSize: '4x3' },\n    { row: 2, col: 0, gridSize: '4x3' },\n    { row: 3, col: 0, gridSize: '4x3' },\n    { row: 0, col: 1, gridSize: '4x3' },\n    { row: 2, col: 1, gridSize: '4x3', top: '50%' },\n    { row: 3, col: 1, gridSize: '4x3' },\n    { row: 0, col: 2, gridSize: '4x3' },\n    { row: 1, col: 2, gridSize: '4x3' },\n    { row: 2, col: 2, gridSize: '4x3' },\n    { row: 3, col: 2, gridSize: '4x3' },\n  ],\n  12: [\n    { row: 0, col: 0, gridSize: '4x3' },\n    { row: 1, col: 0, gridSize: '4x3' },\n    { row: 2, col: 0, gridSize: '4x3' },\n    { row: 3, col: 0, gridSize: '4x3' },\n    { row: 0, col: 1, gridSize: '4x3' },\n    { row: 1, col: 1, gridSize: '4x3' },\n    { row: 2, col: 1, gridSize: '4x3' },\n    { row: 3, col: 1, gridSize: '4x3' },\n    { row: 0, col: 2, gridSize: '4x3' },\n    { row: 1, col: 2, gridSize: '4x3' },\n    { row: 2, col: 2, gridSize: '4x3' },\n    { row: 3, col: 2, gridSize: '4x3' },\n  ],\n};\n","export type PipGridSize = '3x3' | '3x4' | '4x3' | '4x4' | '4x4s' | '5x4';\n\nexport interface PipLayoutCell {\n  row: number;\n  col: number;\n  gridSize: PipGridSize;\n  top?: string;\n  left?: string;\n}\n\n/** Four-column positions aligned with the 3×3 / 4×3 edge inset (20–80%). */\nconst COLS_4 = [20, 40, 60, 80] as const;\n\n/** Three-column positions (evenly spaced, same inset as 3×3). */\nconst COLS_3 = [20, 50, 80] as const;\n\nfunction evenlySpaced(count: number, start: number, end: number): number[] {\n  if (count <= 1) return [start];\n  const step = (end - start) / (count - 1);\n  return Array.from({ length: count }, (_, i) =>\n    Math.round((start + i * step) * 10) / 10\n  );\n}\n\n/** Four evenly spaced row bands for solid / dense layouts (14–86%). */\nconst ROWS_4_EVEN = evenlySpaced(4, 14, 86);\n\nconst GRID_POSITIONS: Record<\n  PipGridSize,\n  { rows: number[]; cols: number[]; size: string }\n> = {\n  '3x3': { rows: [20, 50, 80], cols: [...COLS_3], size: '18%' },\n  /** 3 rows × 4 cols — rotated double-12 body (values 13–15). */\n  '3x4': { rows: [38, 62, 85], cols: [...COLS_4], size: '12%' },\n  '4x3': { rows: [15, 38, 62, 85], cols: [...COLS_3], size: '14%' },\n  /** 4 rows: centered 3-col top + 3×4 body (values 13–15). */\n  '4x4': { rows: [14, 38, 62, 85], cols: [...COLS_4], size: '12%' },\n  /** Solid 4×4 grid (value 16) — even row and column spacing. */\n  '4x4s': { rows: ROWS_4_EVEN, cols: [...COLS_4], size: '11%' },\n  /**\n   * 5 cols × 4 rows (values 17–18): 2×4 double columns at each edge,\n   * center column for 1–2 vertically aligned pips.\n   */\n  '5x4': {\n    rows: ROWS_4_EVEN,\n    cols: evenlySpaced(5, 14, 86),\n    size: '9%',\n  },\n};\n\nfunction midpoint(a: number, b: number): string {\n  return `${(a + b) / 2}%`;\n}\n\n/** Midpoints between adjacent 4-col positions — top row sits equidistant over the body. */\nexport function centeredTop3Left(\n  gridSize: '4x4'\n): readonly [string, string, string] {\n  const cols = GRID_POSITIONS[gridSize].cols;\n  return [\n    `${(cols[0] + cols[1]) / 2}%`,\n    `${(cols[1] + cols[2]) / 2}%`,\n    `${(cols[2] + cols[3]) / 2}%`,\n  ] as const;\n}\n\n/** @deprecated Use centeredTop3Left('4x4'). */\nexport const CENTERED_TOP_3_LEFT = centeredTop3Left('4x4');\n\n/**\n * Vertical center of a gap between two consecutive 4-row bands.\n * @param gapIndex 0 = between rows 0–1, 1 = between rows 1–2, 2 = between rows 2–3\n */\nexport function fourRowGapTop(gapIndex: number): string {\n  const rows = GRID_POSITIONS['5x4'].rows;\n  return midpoint(rows[gapIndex], rows[gapIndex + 1]);\n}\n\n/** @deprecated Use fourRowGapTop. */\nexport const denseColumnGapTop = fourRowGapTop;\n\nexport function resolvePipPosition(cell: PipLayoutCell): {\n  top: string;\n  left: string;\n  width: string;\n  height: string;\n} {\n  const grid = GRID_POSITIONS[cell.gridSize];\n\n  return {\n    top: cell.top ?? `${grid.rows[cell.row]}%`,\n    left: cell.left ?? `${grid.cols[cell.col]}%`,\n    width: grid.size,\n    height: grid.size,\n  };\n}\n","import type { PipLayoutCell } from '../pipGrid';\nimport { centeredTop3Left, fourRowGapTop } from '../pipGrid';\nimport { PIP_LAYOUTS as CLASSIC_LAYOUTS } from './classic';\n\n/**\n * Rotated double-12 body for 13–15: portrait 4×3 turned sideways → 3 rows × 4 cols.\n * (Distinct from classic value 12, which stays portrait 4×3.)\n */\nexport function buildTwelveBodyRotated(): PipLayoutCell[] {\n  const cells: PipLayoutCell[] = [];\n  for (let row = 0; row < 3; row++) {\n    for (let col = 0; col < 4; col++) {\n      cells.push({ row, col, gridSize: '3x4' });\n    }\n  }\n  return cells;\n}\n\n/** @deprecated Use buildTwelveBodyRotated — high-value body is 3×4, not portrait 4×3. */\nexport const buildTwelveBody = buildTwelveBodyRotated;\n\n/** Top-row pip columns for remainder 1 (center), 2 (outer), or 3 (full). */\nfunction remainderRowCols(remainder: number): number[] {\n  switch (remainder) {\n    case 1:\n      return [1];\n    case 2:\n      return [0, 2];\n    case 3:\n      return [0, 1, 2];\n    default:\n      return [];\n  }\n}\n\n/** Centered 3-column remainder row above the 4-wide rotated body. */\nfunction buildCenteredTopRow(remainder: number): PipLayoutCell[] {\n  const lefts = centeredTop3Left('4x4');\n  return remainderRowCols(remainder).map((col) => ({\n    row: 0,\n    col,\n    gridSize: '4x4',\n    left: lefts[col],\n  }));\n}\n\n/** 13–15: centered top row + 3×4 rotated double-12 body. */\nfunction buildOnTwelveBody(extra: number): PipLayoutCell[] {\n  const body = buildTwelveBodyRotated().map((cell) => ({\n    ...cell,\n    row: cell.row + 1,\n    gridSize: '4x4' as const,\n  }));\n  const top = buildCenteredTopRow(extra);\n  return [...top, ...body];\n}\n\n/** Value 16: solid 4×4 grid with even row/column spacing. */\nfunction buildLayout16(): PipLayoutCell[] {\n  const cells: PipLayoutCell[] = [];\n  for (let row = 0; row < 4; row++) {\n    for (let col = 0; col < 4; col++) {\n      cells.push({ row, col, gridSize: '4x4s' });\n    }\n  }\n  return cells;\n}\n\n/** Left and right 2×4 double columns (cols 0–1 and 3–4). */\nfunction buildEdgeDoubleColumns(): PipLayoutCell[] {\n  const gridSize = '5x4' as const;\n  const cells: PipLayoutCell[] = [];\n  for (const col of [0, 1, 3, 4] as const) {\n    for (let row = 0; row < 4; row++) {\n      cells.push({ row, col, gridSize });\n    }\n  }\n  return cells;\n}\n\n/** Value 17: 2×4 at each edge + one pip centered between the outer blocks. */\nfunction buildLayout17(): PipLayoutCell[] {\n  return [\n    ...buildEdgeDoubleColumns(),\n    {\n      row: 10,\n      col: 2,\n      gridSize: '5x4',\n      top: '50%',\n      left: '50%',\n    },\n  ];\n}\n\n/** Value 18: 2×4 at each edge + two pips vertically aligned in the center column. */\nfunction buildLayout18(): PipLayoutCell[] {\n  return [\n    ...buildEdgeDoubleColumns(),\n    {\n      row: 10,\n      col: 2,\n      gridSize: '5x4',\n      top: fourRowGapTop(0),\n      left: '50%',\n    },\n    {\n      row: 11,\n      col: 2,\n      gridSize: '5x4',\n      top: fourRowGapTop(2),\n      left: '50%',\n    },\n  ];\n}\n\n/**\n * Build layouts 13–18.\n * - 13–15: rotated 3×4 body + centered top row.\n * - 16: solid 4×4.\n * - 17–18: 2×4 double columns at each edge + 1–2 center pips.\n */\nexport function buildHighValueLayout(value: number): readonly PipLayoutCell[] {\n  if (value <= 12) {\n    return CLASSIC_LAYOUTS[value] ?? [];\n  }\n\n  if (value <= 15) {\n    return buildOnTwelveBody(value - 12);\n  }\n\n  switch (value) {\n    case 16:\n      return buildLayout16();\n    case 17:\n      return buildLayout17();\n    case 18:\n      return buildLayout18();\n    default:\n      return [];\n  }\n}\n\n/** Precomputed layouts for values 13–18. */\nexport const HIGH_VALUE_LAYOUTS: Record<number, readonly PipLayoutCell[]> = {\n  13: buildHighValueLayout(13),\n  14: buildHighValueLayout(14),\n  15: buildHighValueLayout(15),\n  16: buildHighValueLayout(16),\n  17: buildHighValueLayout(17),\n  18: buildHighValueLayout(18),\n};\n","import { PIP_LAYOUTS as CLASSIC_LAYOUTS } from './pipLayouts/classic';\nimport { HIGH_VALUE_LAYOUTS } from './pipLayouts/highValue';\n\n/** Canonical pip layouts for values 0–18. */\nexport const PIP_LAYOUTS: Record<number, readonly import('./pipGrid').PipLayoutCell[]> =\n  {\n    ...CLASSIC_LAYOUTS,\n    ...HIGH_VALUE_LAYOUTS,\n  };\n\nexport { CLASSIC_LAYOUTS };\nexport { HIGH_VALUE_LAYOUTS, buildHighValueLayout } from './pipLayouts/highValue';\n\nexport function getPipLayout(value: number): readonly import('./pipGrid').PipLayoutCell[] {\n  return PIP_LAYOUTS[value] ?? [];\n}\n","import { DominoValue } from '@/game/DominoValue';\nimport { TrainBend, TrainBranch } from '@/game/TrainData';\n\nexport const DOMINO_WIDTH = 60;\nexport const DOMINO_HEIGHT = 120;\n\n/**\n * Side-toe angles (degrees) relative to the branch direction for a chicken-foot\n * double. The 0° center toe is the straight main-line continuation and is not\n * listed here; these are the two angled toes that fan off the double's open end.\n */\nexport const CHICKEN_FOOT_TOE_ANGLES = [-45, 45] as const;\n\nexport type TrainLayoutStyle = 'offset' | 'linear';\n\nexport interface TrainLayoutEntry {\n  x: number;\n  y: number;\n  rotation: number;\n  isDouble: boolean;\n  value1: number;\n  value2: number;\n}\n\nexport interface ComputeTrainLayoutInput {\n  startX: number;\n  startY: number;\n  angle: number;\n  dominoes: readonly DominoValue[];\n  layoutStyle: TrainLayoutStyle;\n  dominoWidth?: number;\n  dominoHeight?: number;\n  /**\n   * Distance from (startX, startY) to the center of the first tile, along the\n   * train direction. Defaults to a small hub gap; chicken-foot toes pass half a\n   * domino-height so the first toe tile butts against the host double's far end.\n   */\n  leadGap?: number;\n  /**\n   * Which side the offset zigzag seeds on (+1 / -1). Defaults to the natural\n   * outward side for `angle`. Chicken-foot toes override this so each toe's\n   * zigzag starts toward the outside of the foot, clear of the center row.\n   */\n  outwardSign?: number;\n  /**\n   * Offset mode only: index of a chicken-foot double that should act as a\n   * centered hub. The double and the tile feeding into it are snapped onto the\n   * train axis (perp 0) so the inbound tile reads as centered on the double and\n   * the offset center toe fans out symmetrically — which lets the two angled\n   * toes sit at equal, close distances on either side.\n   */\n  hubIndex?: number;\n  /**\n   * Pivots that fold this run's path into Ls, Us, or snakes. When present, the\n   * run is split into straight sub-runs at each bend index and chained corner to\n   * corner. Hub-centering is skipped (corners relax centering by design).\n   */\n  bends?: readonly TrainBend[];\n}\n\nexport function halfExtentAlongTrain(\n  isDouble: boolean,\n  dominoWidth = DOMINO_WIDTH,\n  dominoHeight = DOMINO_HEIGHT\n): number {\n  return isDouble ? dominoWidth / 2 : dominoHeight / 2;\n}\n\nexport function stepAlongTrain(\n  fromIsDouble: boolean,\n  toIsDouble: boolean,\n  dominoWidth = DOMINO_WIDTH,\n  dominoHeight = DOMINO_HEIGHT\n): number {\n  return (\n    halfExtentAlongTrain(fromIsDouble, dominoWidth, dominoHeight) +\n    halfExtentAlongTrain(toIsDouble, dominoWidth, dominoHeight)\n  );\n}\n\nexport function trainDirection(angle: number): { dirX: number; dirY: number } {\n  const angleRad = (angle * Math.PI) / 180;\n  return {\n    dirX: Math.cos(angleRad),\n    dirY: Math.sin(angleRad),\n  };\n}\n\nexport function trainPerpendicular(angle: number): { perpX: number; perpY: number } {\n  const { dirX, dirY } = trainDirection(angle);\n  return { perpX: -dirY, perpY: dirX };\n}\n\n/**\n * Orients a value chain for rendering so each tile's connecting value (`value1`,\n * the near end) faces the previous tile. A tile is flipped only when it is\n * stored reversed (its `value2`, not `value1`, is the one that matches the\n * previous tile's open end). A correctly-stored chain is left untouched, and\n * doubles are never flipped. This is identical for linear and offset layouts —\n * the connection rule doesn't depend on spacing.\n */\nexport function orientDominoValues(dominoes: DominoValue[]): DominoValue[] {\n  const oriented = dominoes.map((domino) => ({ ...domino }));\n\n  for (let i = 1; i < oriented.length; i++) {\n    const domino = oriented[i];\n    const prevValue = oriented[i - 1].value2;\n    const isDouble = domino.value1 === domino.value2;\n\n    if (!isDouble && domino.value1 !== prevValue && domino.value2 === prevValue) {\n      oriented[i] = { value1: domino.value2, value2: domino.value1 };\n    }\n  }\n\n  return oriented;\n}\n\nexport function outwardPerpSign(angle: number): number {\n  const { dirX, dirY } = trainDirection(angle);\n\n  if (Math.abs(dirX) >= Math.abs(dirY)) {\n    return dirX >= 0 ? 1 : -1;\n  }\n\n  return dirY >= 0 ? 1 : -1;\n}\n\nexport function nextPerpOffset(current: number, outwardSign: number): number {\n  if (current === 0) {\n    return outwardSign;\n  }\n\n  return current === outwardSign ? -outwardSign : outwardSign;\n}\n\ninterface PlaceOrientedRunInput {\n  orientedDominoes: readonly DominoValue[];\n  startX: number;\n  startY: number;\n  angle: number;\n  layoutStyle: TrainLayoutStyle;\n  dominoWidth: number;\n  dominoHeight: number;\n  leadGap: number;\n  outwardSign: number;\n  hubIndex?: number;\n}\n\n/**\n * Places an already value-oriented run of dominoes along a single straight\n * heading. This is the geometric core shared by straight runs and by each\n * sub-run of a bent (folded) path; it never re-orients tiles, so callers that\n * split a run at bends can orient the whole value-chain once and still keep\n * like-values touching across every corner.\n */\nfunction placeOrientedRun({\n  orientedDominoes,\n  startX,\n  startY,\n  angle,\n  layoutStyle,\n  dominoWidth,\n  dominoHeight,\n  leadGap,\n  outwardSign,\n  hubIndex,\n}: PlaceOrientedRunInput): TrainLayoutEntry[] {\n  const layout: TrainLayoutEntry[] = [];\n  const { dirX, dirY } = trainDirection(angle);\n  const { perpX, perpY } = trainPerpendicular(angle);\n  const isHub = layoutStyle === 'offset' && hubIndex != null;\n  // Lane (perpStep units) of each placed tile, used to recenter the inbound run\n  // onto the hub double afterward.\n  const laneByIndex: number[] = [];\n\n  let currentX = startX + dirX * leadGap;\n  let currentY = startY + dirY * leadGap;\n  let perpOffset = 0;\n  // Lane (in perpStep units) of the current tile. Regular tiles brick by\n  // flipping lanes; a double stays in the lane of the tile it connects to, and\n  // the tile coming out of the double stays in that lane too — so the run out\n  // of a double mirrors the run into it and the train holds two fixed rows.\n  let laneSign = 0;\n\n  // Regular tiles alternate half a domino-width to each side of the centerline\n  // so the two rows touch along the spine (no gap) and interlock cleanly.\n  const perpStep = dominoWidth / 2;\n\n  // perpOffset is the current net perpendicular position in units of perpStep.\n  // Moving to a new lane steps by the delta.\n  const setPerpOffset = (target: number) => {\n    const delta = (target - perpOffset) * perpStep;\n    currentX += perpX * delta;\n    currentY += perpY * delta;\n    perpOffset = target;\n  };\n\n  for (let i = 0; i < orientedDominoes.length; i++) {\n    const domino = orientedDominoes[i];\n    const isDouble = domino.value1 === domino.value2;\n    const prevIsDouble =\n      i > 0 &&\n      orientedDominoes[i - 1].value1 === orientedDominoes[i - 1].value2;\n\n    if (layoutStyle === 'linear') {\n      if (i > 0) {\n        if (isDouble) {\n          currentX += dirX * stepAlongTrain(prevIsDouble, true, dominoWidth, dominoHeight);\n          currentY += dirY * stepAlongTrain(prevIsDouble, true, dominoWidth, dominoHeight);\n        } else if (prevIsDouble) {\n          currentX += dirX * stepAlongTrain(true, false, dominoWidth, dominoHeight);\n          currentY += dirY * stepAlongTrain(true, false, dominoWidth, dominoHeight);\n        } else {\n          currentX += dirX * dominoHeight;\n          currentY += dirY * dominoHeight;\n        }\n      }\n    } else if (isDouble) {\n      // A double aligns with the tile it connects to: it stays in the current\n      // lane (no perpendicular move) and only advances along the train.\n      if (i > 0) {\n        currentX += dirX * stepAlongTrain(prevIsDouble, true, dominoWidth, dominoHeight);\n        currentY += dirY * stepAlongTrain(prevIsDouble, true, dominoWidth, dominoHeight);\n      }\n    } else {\n      // Regular tile.\n      if (i === 0) {\n        laneSign = outwardSign;\n      } else if (prevIsDouble) {\n        // First tile out of a double: stay in the double's lane (centered on\n        // it), so the outro mirrors the intro. Advance along only.\n        currentX += dirX * stepAlongTrain(true, false, dominoWidth, dominoHeight);\n        currentY += dirY * stepAlongTrain(true, false, dominoWidth, dominoHeight);\n      } else {\n        // Brick against the previous regular: flip lanes, overlap 50% along.\n        currentX += dirX * (dominoHeight / 2);\n        currentY += dirY * (dominoHeight / 2);\n        laneSign = nextPerpOffset(laneSign, outwardSign);\n      }\n\n      setPerpOffset(laneSign);\n    }\n\n    laneByIndex.push(perpOffset);\n\n    layout.push({\n      x: currentX,\n      y: currentY,\n      rotation: isDouble ? angle + 180 : angle - 90,\n      isDouble,\n      value1: domino.value1,\n      value2: domino.value2,\n    });\n  }\n\n  // Center the hub double on the train axis by rigidly sliding the whole run\n  // perpendicular. Because the inbound tile, the double, and the outgoing tile\n  // all share the double's lane, this lands all three on the axis (each reads as\n  // centered on the double) while the offset zigzag — and the no-overlap\n  // guarantee of the original lattice — is preserved. The center toe still fans\n  // off-axis, so the two angled toes end up symmetric and close on either side.\n  if (isHub && hubIndex != null) {\n    const shift = -laneByIndex[hubIndex] * perpStep;\n    if (shift !== 0) {\n      for (let i = 0; i < layout.length; i++) {\n        layout[i] = {\n          ...layout[i],\n          x: layout[i].x + perpX * shift,\n          y: layout[i].y + perpY * shift,\n        };\n      }\n    }\n  }\n\n  return layout;\n}\n\n/**\n * Normalizes a run's bends: integer indices strictly inside the run, one per\n * index (last wins), sorted. Index 0 is dropped — a run can't bend before its\n * first tile. Returns the cleaned, sorted list.\n */\nexport function normalizeBends(\n  bends: readonly TrainBend[] | undefined,\n  tileCount: number\n): TrainBend[] {\n  if (!bends || bends.length === 0) return [];\n  const byIndex = new Map<number, number>();\n  for (const bend of bends) {\n    if (!Number.isInteger(bend.index)) continue;\n    if (bend.index <= 0 || bend.index >= tileCount) continue;\n    byIndex.set(bend.index, bend.turn);\n  }\n  return [...byIndex.entries()]\n    .map(([index, turn]) => ({ index, turn }))\n    .sort((a, b) => a.index - b.index);\n}\n\n/**\n * Local heading (degrees) of the tile at `index` in a (possibly bent) run: the\n * base `angle` plus every bend turn at or before that index. With no bends this\n * is just `angle`. Used to anchor chicken-foot toes off a double's *actual*\n * heading when the double sits in a turned section of the path.\n */\nexport function headingAtIndex(\n  angle: number,\n  bends: readonly TrainBend[] | undefined,\n  index: number,\n  tileCount = Infinity\n): number {\n  const cleaned = normalizeBends(bends, Number.isFinite(tileCount) ? tileCount : index + 1);\n  let heading = angle;\n  for (const bend of cleaned) {\n    if (bend.index <= index) heading += bend.turn;\n    else break;\n  }\n  return heading;\n}\n\n/**\n * Lays out a run that folds at one or more bends. The whole value-chain is\n * oriented once (so like-values keep touching), then split into straight\n * sub-runs at each bend index. Each post-bend sub-run is anchored at the open\n * end of the previous sub-run's last tile and turned by the bend's angle.\n *\n * Corner handling: a perpendicular tile butted straight onto the prior tile's\n * end would overlap it by a quarter-tile, so each post-bend sub-run is nudged\n * half a tile-width along the *previous* heading. That converts the would-be\n * overlap into a clean edge/point touch at the corner while the connecting pips\n * still meet. Centering relaxes at corners (tiles bunch) — by design.\n */\nfunction placeBentRun(\n  orientedDominoes: readonly DominoValue[],\n  input: Required<\n    Pick<\n      ComputeTrainLayoutInput,\n      'startX' | 'startY' | 'angle' | 'layoutStyle' | 'dominoWidth' | 'dominoHeight' | 'leadGap' | 'outwardSign'\n    >\n  >,\n  bends: readonly TrainBend[],\n  hubIndex?: number\n): TrainLayoutEntry[] {\n  const { startX, startY, angle, layoutStyle, dominoWidth, dominoHeight, leadGap, outwardSign } = input;\n  const boundaries = [0, ...bends.map((b) => b.index), orientedDominoes.length];\n\n  const result: TrainLayoutEntry[] = [];\n  let heading = angle;\n  let subStartX = startX;\n  let subStartY = startY;\n  let subLeadGap = leadGap;\n\n  for (let s = 0; s < boundaries.length - 1; s++) {\n    const slice = orientedDominoes.slice(boundaries[s], boundaries[s + 1]);\n    if (slice.length === 0) continue;\n\n    // Keep the hub double centered as long as it lives in the first (pre-bend)\n    // sub-run: this preserves the straight-run vertical position so a bend\n    // elsewhere doesn't shift the whole train and spuriously collide. Later\n    // sub-runs chain off this centered run, so they follow along.\n    const subHubIndex =\n      s === 0 && hubIndex != null && hubIndex < boundaries[1] ? hubIndex : undefined;\n\n    const sub = placeOrientedRun({\n      orientedDominoes: slice,\n      startX: subStartX,\n      startY: subStartY,\n      angle: heading,\n      layoutStyle,\n      dominoWidth,\n      dominoHeight,\n      leadGap: subLeadGap,\n      outwardSign,\n      hubIndex: subHubIndex,\n    });\n    result.push(...sub);\n\n    const isLastSub = s >= boundaries.length - 2;\n    if (isLastSub) break;\n\n    // Chain the next sub-run off this one's open end, turned by the bend angle.\n    const last = sub[sub.length - 1];\n    const prevDir = trainDirection(heading);\n    const halfPrev = halfExtentAlongTrain(last.isDouble, dominoWidth, dominoHeight);\n\n    heading += bends[s].turn;\n    const nextFirst = orientedDominoes[boundaries[s + 1]];\n    const nextIsDouble = nextFirst.value1 === nextFirst.value2;\n    const halfNext = halfExtentAlongTrain(nextIsDouble, dominoWidth, dominoHeight);\n    const nextDir = trainDirection(heading);\n    const nextPerp = trainPerpendicular(heading);\n    const perpStep = dominoWidth / 2;\n\n    // Land the next sub-run's first tile so its connecting half sits edge-flush\n    // against the previous tile's open half — a clean L corner where the matching\n    // pips fully touch. Pull a half-width back along the previous heading (into\n    // the corner) and advance an extra half-width along the new heading so the\n    // turning tile clears the previous tile's body instead of overlapping it.\n    const targetX =\n      last.x + prevDir.dirX * (halfPrev - perpStep) + nextDir.dirX * (halfNext + perpStep);\n    const targetY =\n      last.y + prevDir.dirY * (halfPrev - perpStep) + nextDir.dirY * (halfNext + perpStep);\n\n    // In offset mode placeOrientedRun seeds a regular first tile a half-width\n    // into its outward lane; pre-cancel that so the tile lands exactly on the\n    // target. Linear runs and doubles get no perpendicular seed.\n    const seeds = layoutStyle === 'offset' && !nextIsDouble;\n    const seedX = seeds ? nextPerp.perpX * perpStep * outwardSign : 0;\n    const seedY = seeds ? nextPerp.perpY * perpStep * outwardSign : 0;\n\n    subStartX = targetX - seedX;\n    subStartY = targetY - seedY;\n    subLeadGap = 0;\n  }\n\n  return result;\n}\n\nexport function computeTrainLayout({\n  startX,\n  startY,\n  angle,\n  dominoes,\n  layoutStyle,\n  dominoWidth = DOMINO_WIDTH,\n  dominoHeight = DOMINO_HEIGHT,\n  leadGap = dominoHeight * 0.3,\n  outwardSign: outwardSignInput,\n  hubIndex,\n  bends,\n}: ComputeTrainLayoutInput): TrainLayoutEntry[] {\n  const orientedDominoes = orientDominoValues([...dominoes]);\n  const outwardSign = outwardSignInput ?? outwardPerpSign(angle);\n\n  const cleanedBends = normalizeBends(bends, orientedDominoes.length);\n  if (cleanedBends.length > 0) {\n    // Hub-centering still applies to the pre-bend sub-run (so a bend doesn't\n    // shift the whole train); corners past it relax centering by design.\n    return placeBentRun(\n      orientedDominoes,\n      {\n        startX,\n        startY,\n        angle,\n        layoutStyle,\n        dominoWidth,\n        dominoHeight,\n        leadGap,\n        outwardSign,\n      },\n      cleanedBends,\n      hubIndex\n    );\n  }\n\n  return placeOrientedRun({\n    orientedDominoes,\n    startX,\n    startY,\n    angle,\n    layoutStyle,\n    dominoWidth,\n    dominoHeight,\n    leadGap,\n    outwardSign,\n    hubIndex,\n  });\n}\n\n/** The four world-space corners of a tile (its rotated rectangle). */\nexport function tileCorners(\n  entry: TrainLayoutEntry,\n  dominoWidth = DOMINO_WIDTH,\n  dominoHeight = DOMINO_HEIGHT\n): Array<{ x: number; y: number }> {\n  const r = (entry.rotation * Math.PI) / 180;\n  const cos = Math.cos(r);\n  const sin = Math.sin(r);\n  const hw = dominoWidth / 2;\n  const hh = dominoHeight / 2;\n  return [\n    [-hw, -hh],\n    [hw, -hh],\n    [hw, hh],\n    [-hw, hh],\n  ].map(([x, y]) => ({\n    x: entry.x + x * cos - y * sin,\n    y: entry.y + x * sin + y * cos,\n  }));\n}\n\nfunction projectionGap(\n  a: Array<{ x: number; y: number }>,\n  b: Array<{ x: number; y: number }>,\n  axis: { x: number; y: number }\n): number {\n  let aMin = Infinity;\n  let aMax = -Infinity;\n  let bMin = Infinity;\n  let bMax = -Infinity;\n  for (const p of a) {\n    const d = p.x * axis.x + p.y * axis.y;\n    aMin = Math.min(aMin, d);\n    aMax = Math.max(aMax, d);\n  }\n  for (const p of b) {\n    const d = p.x * axis.x + p.y * axis.y;\n    bMin = Math.min(bMin, d);\n    bMax = Math.max(bMax, d);\n  }\n  return Math.min(aMax, bMax) - Math.max(aMin, bMin);\n}\n\n/**\n * True when two tiles physically overlap (separating-axis test on their rotated\n * rectangles). Tiles that merely touch (within `epsilon`) are not overlapping,\n * so legitimately adjacent dominoes — bricked, end-to-end, or butted against a\n * double — pass cleanly while real collisions are caught.\n */\nexport function tilesOverlap(\n  a: TrainLayoutEntry,\n  b: TrainLayoutEntry,\n  epsilon = 1,\n  dominoWidth = DOMINO_WIDTH,\n  dominoHeight = DOMINO_HEIGHT\n): boolean {\n  const ca = tileCorners(a, dominoWidth, dominoHeight);\n  const cb = tileCorners(b, dominoWidth, dominoHeight);\n  for (const corners of [ca, cb]) {\n    for (let i = 0; i < 4; i++) {\n      const p = corners[i];\n      const q = corners[(i + 1) % 4];\n      const ex = q.x - p.x;\n      const ey = q.y - p.y;\n      const len = Math.hypot(ex, ey) || 1;\n      const axis = { x: -ey / len, y: ex / len };\n      if (projectionGap(ca, cb, axis) <= epsilon) {\n        return false;\n      }\n    }\n  }\n  return true;\n}\n\nfunction overlapsAny(\n  tile: TrainLayoutEntry,\n  others: readonly TrainLayoutEntry[],\n  dominoWidth: number,\n  dominoHeight: number\n): boolean {\n  return others.some((other) =>\n    tilesOverlap(tile, other, 1, dominoWidth, dominoHeight)\n  );\n}\n\n/**\n * True when any tile of `layout` overlaps any tile in `obstacles` — i.e. this\n * path would physically intersect another path. Used to forbid a bend that\n * would cross another train.\n */\nexport function layoutsCollide(\n  layout: readonly TrainLayoutEntry[],\n  obstacles: readonly TrainLayoutEntry[],\n  epsilon = 1,\n  dominoWidth = DOMINO_WIDTH,\n  dominoHeight = DOMINO_HEIGHT\n): boolean {\n  return layout.some((tile) =>\n    obstacles.some((other) =>\n      tilesOverlap(tile, other, epsilon, dominoWidth, dominoHeight)\n    )\n  );\n}\n\n/**\n * True when a path crosses itself — any two of its own tiles overlap. Adjacent\n * tiles that merely touch are fine (tilesOverlap ignores contact), so this only\n * fires when a fold (e.g. a too-tight U-turn) makes the path collide with itself.\n */\nexport function layoutSelfIntersects(\n  layout: readonly TrainLayoutEntry[],\n  epsilon = 1,\n  dominoWidth = DOMINO_WIDTH,\n  dominoHeight = DOMINO_HEIGHT\n): boolean {\n  for (let i = 0; i < layout.length; i++) {\n    for (let j = i + 1; j < layout.length; j++) {\n      if (tilesOverlap(layout[i], layout[j], epsilon, dominoWidth, dominoHeight)) {\n        return true;\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * A single straight run of dominoes within a chicken-foot tree: the main line\n * or one toe. `depth` is 0 for the main line, 1 for its toes, and so on.\n */\nexport interface TrainSegment {\n  angle: number;\n  depth: number;\n  layoutStyle: TrainLayoutStyle;\n  /** Outward side this segment's zigzag seeds on (needed to re-derive layout). */\n  outwardSign: number;\n  dominoes: readonly DominoValue[];\n  layout: TrainLayoutEntry[];\n  /** Anchor point this segment hangs off (host double's open end), if any. */\n  anchor?: { x: number; y: number };\n}\n\nexport interface ComputeTrainTreeInput {\n  startX: number;\n  startY: number;\n  angle: number;\n  branch: TrainBranch;\n  layoutStyle: TrainLayoutStyle;\n  dominoWidth?: number;\n  dominoHeight?: number;\n  leadGap?: number;\n  depth?: number;\n  anchor?: { x: number; y: number };\n  outwardSign?: number;\n  /**\n   * Accumulator of every tile already placed in the tree. Toes are nudged\n   * outward until they clear everything in here, so no two dominoes overlap.\n   * Callers normally omit this; the recursion threads it through.\n   */\n  placed?: TrainLayoutEntry[];\n  /**\n   * Unit direction a toe may be nudged along (outward, parallel to the host\n   * double's open edge) to resolve overlaps. The trunk passes none.\n   */\n  pushAxis?: { x: number; y: number };\n  /**\n   * Minimum number of nudge steps to apply before checking for clearance. Both\n   * toes of a foot share this so they stay symmetric about the double even when\n   * only one side is crowded by the offset center toe.\n   */\n  minPushSteps?: number;\n}\n\n/** Outward nudge increment and cap used to space chicken-foot toes apart. */\nconst TOE_PUSH_STEP = DOMINO_WIDTH / 4;\nconst TOE_PUSH_MAX_STEPS = 24;\n\n/**\n * Lays out a branch and, recursively, the chicken-foot side toes hanging off any\n * of its doubles. Returns a flat list of segments (main line first, then toes in\n * depth-first order) so callers can render every tile and validate each run.\n */\nexport function computeTrainTree({\n  startX,\n  startY,\n  angle,\n  branch,\n  layoutStyle,\n  dominoWidth = DOMINO_WIDTH,\n  dominoHeight = DOMINO_HEIGHT,\n  leadGap,\n  depth = 0,\n  anchor,\n  outwardSign,\n  placed = [],\n  pushAxis,\n  minPushSteps = 0,\n}: ComputeTrainTreeInput): TrainSegment[] {\n  const segmentOutward = outwardSign ?? outwardPerpSign(angle);\n\n  // The first double that sprouts a foot becomes a centered hub so its inbound\n  // tile reads centered and its two angled toes stay symmetric.\n  const hubIndex = branch.feet\n    ? Object.keys(branch.feet)\n        .map(Number)\n        .filter((index) => {\n          const tile = branch.dominoes[index];\n          return tile && tile.value1 === tile.value2;\n        })\n        .sort((a, b) => a - b)[0]\n    : undefined;\n\n  const buildLayout = (originX: number, originY: number) =>\n    computeTrainLayout({\n      startX: originX,\n      startY: originY,\n      angle,\n      dominoes: branch.dominoes,\n      layoutStyle,\n      dominoWidth,\n      dominoHeight,\n      leadGap,\n      outwardSign: segmentOutward,\n      hubIndex,\n      bends: branch.bends,\n    });\n\n  // Nudge this run outward (only toes get a pushAxis) until none of its tiles\n  // overlap anything already placed, so dominoes never sit on top of each other.\n  // Start from minPushSteps so a foot's two toes share a nudge and stay symmetric.\n  let layout = buildLayout(\n    startX + (pushAxis?.x ?? 0) * TOE_PUSH_STEP * minPushSteps,\n    startY + (pushAxis?.y ?? 0) * TOE_PUSH_STEP * minPushSteps\n  );\n  let appliedAnchor =\n    anchor && pushAxis\n      ? {\n          x: anchor.x + pushAxis.x * TOE_PUSH_STEP * minPushSteps,\n          y: anchor.y + pushAxis.y * TOE_PUSH_STEP * minPushSteps,\n        }\n      : anchor;\n  if (pushAxis && placed.length > 0) {\n    for (let k = minPushSteps; k <= TOE_PUSH_MAX_STEPS; k++) {\n      const originX = startX + pushAxis.x * TOE_PUSH_STEP * k;\n      const originY = startY + pushAxis.y * TOE_PUSH_STEP * k;\n      const trial = buildLayout(originX, originY);\n      const clear = !trial.some((tile) =>\n        overlapsAny(tile, placed, dominoWidth, dominoHeight)\n      );\n      if (clear || k === TOE_PUSH_MAX_STEPS) {\n        layout = trial;\n        appliedAnchor = anchor\n          ? {\n              x: anchor.x + pushAxis.x * TOE_PUSH_STEP * k,\n              y: anchor.y + pushAxis.y * TOE_PUSH_STEP * k,\n            }\n          : anchor;\n        break;\n      }\n    }\n  }\n\n  placed.push(...layout);\n\n  const segments: TrainSegment[] = [\n    {\n      angle,\n      depth,\n      layoutStyle,\n      outwardSign: segmentOutward,\n      dominoes: branch.dominoes,\n      layout,\n      anchor: appliedAnchor,\n    },\n  ];\n\n  if (branch.feet) {\n    const perpStep = dominoWidth / 2;\n    const leadGap = dominoHeight / 2;\n\n    for (const key of Object.keys(branch.feet)) {\n      const hostIndex = Number(key);\n      const host = layout[hostIndex];\n      const toes = branch.feet[hostIndex];\n      if (!host || !host.isDouble || !toes) {\n        continue;\n      }\n\n      // Anchor toes off the double's LOCAL heading so a double inside a bent\n      // section fans its toes relative to the turned path, not the base angle.\n      const hostAngle = headingAtIndex(\n        angle,\n        branch.bends,\n        hostIndex,\n        branch.dominoes.length\n      );\n      const { dirX, dirY } = trainDirection(hostAngle);\n      const { perpX, perpY } = trainPerpendicular(hostAngle);\n\n      for (let toeIndex = 0; toeIndex < toes.length; toeIndex++) {\n        const toe = toes[toeIndex];\n        const toeOffset = CHICKEN_FOOT_TOE_ANGLES[toeIndex] ?? 0;\n        const sideSign = Math.sign(toeOffset);\n        const toeAngle = hostAngle + toeOffset;\n        const toePerp = trainPerpendicular(toeAngle);\n        // Seed the zigzag on the toe's INNER lane so each toe splays outward\n        // (away from the center toe) as it extends rather than curling in.\n        const outward = -sideSign;\n\n        // The double's open corner on this toe's side: half a domino-width out\n        // along the train, half a domino-height across to the corner.\n        const cornerX =\n          host.x + dirX * (dominoWidth / 2) + perpX * (dominoHeight / 2) * sideSign;\n        const cornerY =\n          host.y + dirY * (dominoWidth / 2) + perpY * (dominoHeight / 2) * sideSign;\n\n        // Snug placement: the first toe tile butts its inner edge midpoint\n        // against that corner (its center lands at corner + toeDir * leadGap).\n        // Pick the origin so the offset seed cancels and that lands exactly.\n        const originX = cornerX - toePerp.perpX * outward * perpStep;\n        const originY = cornerY - toePerp.perpY * outward * perpStep;\n\n        segments.push(\n          ...computeTrainTree({\n            startX: originX,\n            startY: originY,\n            angle: toeAngle,\n            branch: toe,\n            // Toes inherit the main style so they zigzag in offset mode.\n            layoutStyle,\n            dominoWidth,\n            dominoHeight,\n            leadGap,\n            outwardSign: outward,\n            depth: depth + 1,\n            anchor: { x: originX, y: originY },\n            placed,\n            // If the snug spot is still blocked (the offset center toe leans into\n            // one side), slide this toe along the double's open edge, away from\n            // center, until it clears. This keeps it butted against the double\n            // while stepping past the obstacle — independent per toe, so a foot\n            // ends up snug and only as asymmetric as the obstruction requires.\n            pushAxis: { x: perpX * sideSign, y: perpY * sideSign },\n          })\n        );\n      }\n    }\n  }\n\n  return segments;\n}\n\n/** Flattens a list of segments into a single list of tiles for rendering. */\nexport function flattenSegments(\n  segments: readonly TrainSegment[]\n): TrainLayoutEntry[] {\n  return segments.flatMap((segment) => segment.layout);\n}\n\nexport interface TrainLayoutBounds {\n  width: number;\n  height: number;\n  offsetX: number;\n  offsetY: number;\n}\n\n/** Bounding box for rendering a train layout on a felt canvas. */\nexport function getTrainLayoutBounds(\n  layout: readonly TrainLayoutEntry[],\n  padding = 24,\n  dominoWidth = DOMINO_WIDTH,\n  dominoHeight = DOMINO_HEIGHT\n): TrainLayoutBounds {\n  const halfExtent = Math.hypot(dominoWidth, dominoHeight) / 2;\n\n  if (layout.length === 0) {\n    return {\n      width: padding * 2 + dominoWidth,\n      height: padding * 2 + dominoHeight,\n      offsetX: padding,\n      offsetY: padding,\n    };\n  }\n\n  let minX = Infinity;\n  let minY = Infinity;\n  let maxX = -Infinity;\n  let maxY = -Infinity;\n\n  for (const entry of layout) {\n    minX = Math.min(minX, entry.x - halfExtent);\n    minY = Math.min(minY, entry.y - halfExtent);\n    maxX = Math.max(maxX, entry.x + halfExtent);\n    maxY = Math.max(maxY, entry.y + halfExtent);\n  }\n\n  return {\n    width: Math.ceil(maxX - minX + padding * 2),\n    height: Math.ceil(maxY - minY + padding * 2),\n    offsetX: padding - minX,\n    offsetY: padding - minY,\n  };\n}\n","import type { DominoValue } from '../game/DominoValue';\nimport {\n  DOMINO_HEIGHT,\n  DOMINO_WIDTH,\n  halfExtentAlongTrain,\n} from './trainLayout';\n\nexport type LineEndId = 'left' | 'right' | 'top' | 'bottom';\n\n/** Degrees: 0 = +x (right), 90 = +y (down). */\nexport const LINE_END_ANGLES: Readonly<Record<LineEndId, number>> = {\n  right: 0,\n  bottom: 90,\n  left: 180,\n  top: 270,\n};\n\nexport interface LineArmData {\n  readonly end: LineEndId;\n  readonly dominoes: readonly DominoValue[];\n}\n\nexport interface ComputeLineLayoutInput {\n  readonly centerX: number;\n  readonly centerY: number;\n  /** Spinner or seed tile at the center (null = empty board). */\n  readonly center: DominoValue | null;\n  readonly arms: readonly LineArmData[];\n  /**\n   * Extra gap between the center tile's outer edge and the first arm tile.\n   * Default 0 so arm tiles abut the spinner/seed.\n   */\n  readonly startGap?: number;\n}\n\nexport interface LineArmPlacement {\n  readonly end: LineEndId;\n  /** Train origin — same as the center tile's center (DominoTrain leadGap reaches out). */\n  readonly startX: number;\n  readonly startY: number;\n  readonly angle: number;\n  /**\n   * Distance from (startX, startY) to the center of the first arm tile so that\n   * tile's near edge touches the center tile (plus optional startGap).\n   */\n  readonly leadGap: number;\n  readonly dominoes: readonly DominoValue[];\n  /** Hit-target / play-button anchor for this end. */\n  readonly highlightX: number;\n  readonly highlightY: number;\n}\n\nexport interface LineLayoutResult {\n  readonly centerX: number;\n  readonly centerY: number;\n  readonly center: DominoValue | null;\n  readonly arms: readonly LineArmPlacement[];\n  readonly width: number;\n  readonly height: number;\n}\n\nfunction isDoubleValue(tile: DominoValue): boolean {\n  return tile.value1 === tile.value2;\n}\n\n/**\n * Half-extent of the center tile along a cardinal arm.\n * Spinner (double) is rendered upright; seed (non-double) is rendered on its side.\n */\nexport function centerHalfExtentAlongEnd(\n  end: LineEndId,\n  center: DominoValue | null\n): number {\n  if (!center) {\n    return DOMINO_WIDTH / 2;\n  }\n  if (isDoubleValue(center)) {\n    return end === 'left' || end === 'right'\n      ? DOMINO_WIDTH / 2\n      : DOMINO_HEIGHT / 2;\n  }\n  // Horizontal seed: long axis left–right.\n  return end === 'left' || end === 'right'\n    ? DOMINO_HEIGHT / 2\n    : DOMINO_WIDTH / 2;\n}\n\nfunction firstArmTileHalfExtent(dominoes: readonly DominoValue[]): number {\n  if (dominoes.length === 0) {\n    // Empty play slot: assume a regular tile will be placed.\n    return halfExtentAlongTrain(false);\n  }\n  const first = dominoes[0]!;\n  return halfExtentAlongTrain(isDoubleValue(first));\n}\n\n/**\n * Place a classic spinner (or seed) at center with four cardinal arm starts.\n * Arm geometry is consumed by DominoTrain (same as Mexican Train spokes).\n *\n * Train origins sit at the center; `leadGap` is sized so the first arm tile\n * abuts the center tile (zero default gap).\n */\nexport function computeLineLayout(\n  input: ComputeLineLayoutInput\n): LineLayoutResult {\n  const gap = input.startGap ?? 0;\n  const arms: LineArmPlacement[] = input.arms.map((arm) => {\n    const angle = LINE_END_ANGLES[arm.end];\n    const radians = (angle * Math.PI) / 180;\n    const leadGap =\n      centerHalfExtentAlongEnd(arm.end, input.center) +\n      gap +\n      firstArmTileHalfExtent(arm.dominoes);\n    const dirX = Math.cos(radians);\n    const dirY = Math.sin(radians);\n    // Highlight at the first-tile center for empty arms; otherwise past the\n    // open end (approximate: center + leadGap + remaining run length).\n    const runExtra =\n      arm.dominoes.length <= 1\n        ? 0\n        : (arm.dominoes.length - 1) * (DOMINO_HEIGHT * 0.75);\n    const highlightDist = leadGap + runExtra;\n    return {\n      end: arm.end,\n      startX: input.centerX,\n      startY: input.centerY,\n      angle,\n      leadGap,\n      dominoes: arm.dominoes,\n      highlightX: input.centerX + highlightDist * dirX,\n      highlightY: input.centerY + highlightDist * dirY,\n    };\n  });\n\n  const maxLead = Math.max(0, ...arms.map((a) => a.leadGap));\n  const maxArm = Math.max(0, ...arms.map((a) => a.dominoes.length));\n  const extent =\n    maxLead + maxArm * (DOMINO_HEIGHT + 4) + DOMINO_HEIGHT;\n  const size = Math.max(extent * 2, 320);\n\n  return {\n    centerX: input.centerX,\n    centerY: input.centerY,\n    center: input.center,\n    arms,\n    width: size,\n    height: size,\n  };\n}\n\nexport { DOMINO_WIDTH, DOMINO_HEIGHT };\n","import { TrainBend, TrainBranch } from '@/game/TrainData';\nimport {\n  TrainLayoutEntry,\n  TrainLayoutStyle,\n  computeTrainTree,\n  flattenSegments,\n  layoutSelfIntersects,\n  layoutsCollide,\n  outwardPerpSign,\n  trainPerpendicular,\n} from './trainLayout';\n\nexport type TurnSide = 'left' | 'right';\n\n/** Default pivot magnitude. The interactive UI only produces square corners. */\nexport const TURN_DEGREES = 90;\n\n/**\n * Signed turn (degrees) for a side. Headings use the screen convention\n * (0° = +x, +90° = +y / downward), so a `+90` turn rotates +x toward +y, which\n * reads as a clockwise/\"right\" turn on screen.\n */\nexport function sideToTurn(side: TurnSide, degrees = TURN_DEGREES): number {\n  return side === 'right' ? degrees : -degrees;\n}\n\nexport function oppositeSide(side: TurnSide): TurnSide {\n  return side === 'right' ? 'left' : 'right';\n}\n\n/**\n * Default turn side in offset mode: fold toward the empty side — the one\n * opposite the lane the zigzag biases into (`outwardSign`). A `+90` turn heads\n * toward the heading's `+perp`; outwardSign is measured on that same perp axis,\n * so the empty side is `-outwardSign`, i.e. side = outwardSign >= 0 ? 'left' : 'right'.\n */\nexport function offsetDefaultSide(angle: number, outwardSign?: number): TurnSide {\n  const bias = outwardSign ?? outwardPerpSign(angle);\n  return bias >= 0 ? 'left' : 'right';\n}\n\nexport interface TableBounds {\n  width: number;\n  height: number;\n}\n\n/**\n * Default turn side in linear mode: fold toward whichever perpendicular side has\n * more open table from the bend point. Distance is measured from `point` along\n * each perpendicular until it exits the table rectangle; the roomier side wins.\n * Ties (e.g. dead-center) fall back to 'right'.\n */\nexport function linearDefaultSide(\n  point: { x: number; y: number },\n  angle: number,\n  bounds: TableBounds\n): TurnSide {\n  const { perpX, perpY } = trainPerpendicular(angle);\n  const distanceToExit = (sx: number, sy: number): number => {\n    // Largest t >= 0 with point + t*(sx,sy) still inside [0,w] x [0,h].\n    let t = Infinity;\n    if (sx > 0) t = Math.min(t, (bounds.width - point.x) / sx);\n    else if (sx < 0) t = Math.min(t, (0 - point.x) / sx);\n    if (sy > 0) t = Math.min(t, (bounds.height - point.y) / sy);\n    else if (sy < 0) t = Math.min(t, (0 - point.y) / sy);\n    return Number.isFinite(t) ? Math.max(0, t) : Infinity;\n  };\n\n  // +90 turn heads toward +perp ('right'); -90 toward -perp ('left').\n  const rightRoom = distanceToExit(perpX, perpY);\n  const leftRoom = distanceToExit(-perpX, -perpY);\n  return rightRoom >= leftRoom ? 'right' : 'left';\n}\n\nexport interface BuildTrainTilesInput {\n  startX: number;\n  startY: number;\n  angle: number;\n  layoutStyle: TrainLayoutStyle;\n}\n\n/** Flattens a branch (with feet and bends) to its world-space tiles. */\nexport function buildBranchTiles(\n  branch: TrainBranch,\n  input: BuildTrainTilesInput\n): TrainLayoutEntry[] {\n  return flattenSegments(\n    computeTrainTree({\n      startX: input.startX,\n      startY: input.startY,\n      angle: input.angle,\n      branch,\n      layoutStyle: input.layoutStyle,\n    })\n  );\n}\n\n/** Replaces (or removes) the bend at `index`, returning a new bends array. */\nexport function withBendAt(\n  bends: readonly TrainBend[] | undefined,\n  index: number,\n  turn: number | null\n): TrainBend[] {\n  const rest = (bends ?? []).filter((bend) => bend.index !== index);\n  if (turn === null) return rest;\n  return [...rest, { index, turn }].sort((a, b) => a.index - b.index);\n}\n\nexport interface ResolveBendResult {\n  /** The legal turn to apply, or null when no side is collision-free. */\n  turn: number | null;\n  /** Why null: 'blocked' = both sides collide; never set on success. */\n  reason?: 'blocked';\n}\n\nexport interface ResolveBendInput {\n  branch: TrainBranch;\n  index: number;\n  build: BuildTrainTilesInput;\n  /** Tiles belonging to every OTHER path; a bend may not intersect these. */\n  obstacles: readonly TrainLayoutEntry[];\n  /** Preferred side to try first (from the mode's heuristic). */\n  preferredSide: TurnSide;\n  /** Turn magnitude in degrees (default 90). */\n  degrees?: number;\n}\n\n/**\n * Picks a collision-free turn for a new bend at `index`. Tries the preferred\n * side first, then the opposite; a candidate is rejected if the resulting path\n * crosses itself or any obstacle path. Returns `{ turn: null, reason: 'blocked' }`\n * when neither side is legal, so the caller can refuse the bend.\n */\nexport function resolveBend({\n  branch,\n  index,\n  build,\n  obstacles,\n  preferredSide,\n  degrees = TURN_DEGREES,\n}: ResolveBendInput): ResolveBendResult {\n  const candidates: TurnSide[] = [preferredSide, oppositeSide(preferredSide)];\n\n  for (const side of candidates) {\n    const turn = sideToTurn(side, degrees);\n    const candidateBranch: TrainBranch = {\n      ...branch,\n      bends: withBendAt(branch.bends, index, turn),\n    };\n    const tiles = buildBranchTiles(candidateBranch, build);\n    if (layoutSelfIntersects(tiles)) continue;\n    if (layoutsCollide(tiles, obstacles)) continue;\n    return { turn };\n  }\n\n  return { turn: null, reason: 'blocked' };\n}\n\n/**\n * Cycles a tile's bend on repeated clicks: none → preferred legal side →\n * opposite legal side → none. Skips sides that collide. Returns the next bends\n * array, or the unchanged input when no legal bend exists.\n */\nexport function cycleBendAt(\n  branch: TrainBranch,\n  index: number,\n  build: BuildTrainTilesInput,\n  obstacles: readonly TrainLayoutEntry[],\n  preferredSide: TurnSide,\n  degrees = TURN_DEGREES\n): { bends: TrainBend[]; changed: boolean; blocked: boolean } {\n  const current = (branch.bends ?? []).find((bend) => bend.index === index);\n  const preferredTurn = sideToTurn(preferredSide, degrees);\n  const oppositeTurn = sideToTurn(oppositeSide(preferredSide), degrees);\n\n  const isLegal = (turn: number): boolean => {\n    const candidate: TrainBranch = {\n      ...branch,\n      bends: withBendAt(branch.bends, index, turn),\n    };\n    const tiles = buildBranchTiles(candidate, build);\n    return !layoutSelfIntersects(tiles) && !layoutsCollide(tiles, obstacles);\n  };\n\n  // Cycle order by current state. `null` means \"straighten\" (remove the bend),\n  // which is always legal — but it's only a cycle target when a bend already\n  // exists. Starting from straight with both sides blocked reports `blocked`\n  // rather than performing a no-op removal.\n  let order: (number | null)[];\n  if (!current) {\n    order = [preferredTurn, oppositeTurn];\n  } else if (current.turn === preferredTurn) {\n    order = [oppositeTurn, null];\n  } else if (current.turn === oppositeTurn) {\n    order = [null];\n  } else {\n    order = [preferredTurn, oppositeTurn, null];\n  }\n\n  for (const turn of order) {\n    if (turn === null) {\n      return { bends: withBendAt(branch.bends, index, null), changed: true, blocked: false };\n    }\n    if (isLegal(turn)) {\n      return { bends: withBendAt(branch.bends, index, turn), changed: true, blocked: false };\n    }\n  }\n\n  return { bends: branch.bends ?? [], changed: false, blocked: true };\n}\n","/**\n * Distance from the hub center at which a train should start so its first tile\n * clears its neighbours. Trains fan out 360/slots° apart, so the neighbour gap\n * at distance d is ~2πd/slots; it must exceed a tile's footprint. Offset trains\n * zigzag wider (a perpendicular half-tile seed) and need a bigger ring; linear\n * trains are skinny and stay near the hub. Never smaller than `radius + 20`.\n *\n * Pure geometry — no React — so it lives in the headless core and is shared by\n * the `DominoHub` component (in `double-eighteen-react`) and layout adapters.\n */\nexport function hubTrainStartDistance(\n  slots: number,\n  radius: number,\n  dominoWidth: number,\n  layoutStyle: 'offset' | 'linear'\n): number {\n  const minNeighborGap = dominoWidth * (layoutStyle === 'offset' ? 2.5 : 1.3);\n  return Math.max(\n    radius + 20,\n    Math.ceil((minNeighborGap * slots) / (2 * Math.PI))\n  );\n}\n","/** Pan/zoom transform: content is scaled by `scale` then translated by (x, y). */\nexport interface ViewportTransform {\n  scale: number;\n  x: number;\n  y: number;\n}\n\nexport interface Size {\n  width: number;\n  height: number;\n}\n\nexport interface Point {\n  x: number;\n  y: number;\n}\n\nexport function clampScale(scale: number, min: number, max: number): number {\n  return Math.min(max, Math.max(min, scale));\n}\n\n/**\n * Zooms by `factor` about a fixed screen `pivot` so the content point under the\n * pivot stays put. Scale is clamped to [min, max]; the translation is adjusted\n * by the *effective* factor after clamping so panning can't drift at the limits.\n */\nexport function zoomAt(\n  view: ViewportTransform,\n  factor: number,\n  pivot: Point,\n  min: number,\n  max: number\n): ViewportTransform {\n  const scale = clampScale(view.scale * factor, min, max);\n  const effective = scale / view.scale;\n  return {\n    scale,\n    x: pivot.x - (pivot.x - view.x) * effective,\n    y: pivot.y - (pivot.y - view.y) * effective,\n  };\n}\n\n/**\n * Centers `content` within `viewport` at the largest scale that fits inside the\n * given padding (clamped to [min, max]). Use this for a \"fit / reset\" control.\n */\nexport function fitToBounds(\n  content: Size,\n  viewport: Size,\n  padding: number,\n  min: number,\n  max: number\n): ViewportTransform {\n  const safeW = Math.max(1, content.width);\n  const safeH = Math.max(1, content.height);\n  const raw = Math.min(\n    (viewport.width - padding * 2) / safeW,\n    (viewport.height - padding * 2) / safeH\n  );\n  const scale = clampScale(raw, min, max);\n  return {\n    scale,\n    x: (viewport.width - safeW * scale) / 2,\n    y: (viewport.height - safeH * scale) / 2,\n  };\n}\n\n/** Converts a screen point inside the viewport to content coordinates. */\nexport function screenToContent(view: ViewportTransform, screen: Point): Point {\n  return {\n    x: (screen.x - view.x) / view.scale,\n    y: (screen.y - view.y) / view.scale,\n  };\n}\n","import { DominoValue } from '@/game/DominoValue';\nimport { TrainBranch } from '@/game/TrainData';\nimport {\n  DOMINO_HEIGHT,\n  DOMINO_WIDTH,\n  TrainLayoutEntry,\n  TrainLayoutStyle,\n  TrainSegment,\n  nextPerpOffset,\n  outwardPerpSign,\n  stepAlongTrain,\n  tilesOverlap,\n  trainDirection,\n  trainPerpendicular,\n} from '@/app/trainLayout';\n\nexport interface LayoutValidationIssue {\n  code: string;\n  message: string;\n  index?: number;\n}\n\nexport interface LayoutValidationResult {\n  valid: boolean;\n  issues: LayoutValidationIssue[];\n}\n\nconst DEFAULT_TOLERANCE = 1;\n\nexport function projectOnTrainAxis(\n  dx: number,\n  dy: number,\n  angle: number\n): number {\n  const { dirX, dirY } = trainDirection(angle);\n  return dx * dirX + dy * dirY;\n}\n\nexport function projectOnPerpendicularAxis(\n  dx: number,\n  dy: number,\n  angle: number\n): number {\n  const { perpX, perpY } = trainPerpendicular(angle);\n  return dx * perpX + dy * perpY;\n}\n\nexport function validateDominoChain(dominoes: readonly DominoValue[]): LayoutValidationResult {\n  const issues: LayoutValidationIssue[] = [];\n\n  for (let i = 1; i < dominoes.length; i++) {\n    if (dominoes[i].value1 !== dominoes[i - 1].value2) {\n      issues.push({\n        code: 'chain-break',\n        message: `Domino ${i} does not connect to domino ${i - 1}`,\n        index: i,\n      });\n    }\n  }\n\n  for (let i = 1; i < dominoes.length; i++) {\n    const prevIsDouble = dominoes[i - 1].value1 === dominoes[i - 1].value2;\n    const currentIsDouble = dominoes[i].value1 === dominoes[i].value2;\n    if (prevIsDouble && currentIsDouble) {\n      issues.push({\n        code: 'consecutive-doubles',\n        message: `Consecutive doubles at index ${i - 1} and ${i}`,\n        index: i,\n      });\n    }\n  }\n\n  return { valid: issues.length === 0, issues };\n}\n\nexport interface AxisPosition {\n  along: number;\n  perp: number;\n}\n\n/**\n * Reconstructs the expected position of every tile in train-axis space\n * (along the train and perpendicular to it), mirroring computeTrainLayout.\n * Positions are relative to the first tile, so only deltas are meaningful.\n */\nexport function expectedAxisLayout(\n  layout: readonly TrainLayoutEntry[],\n  layoutStyle: TrainLayoutStyle,\n  outwardSign: number,\n  dominoWidth = DOMINO_WIDTH,\n  dominoHeight = DOMINO_HEIGHT\n): AxisPosition[] {\n  const perpStep = dominoWidth / 2;\n  const isDoubleArr = layout.map((entry) => entry.isDouble);\n\n  const positions: AxisPosition[] = [];\n  let along = 0;\n  let perp = 0;\n  let laneSign = 0;\n\n  for (let i = 0; i < layout.length; i++) {\n    const isDouble = isDoubleArr[i];\n    const prevIsDouble = i > 0 && isDoubleArr[i - 1];\n\n    if (layoutStyle === 'linear') {\n      if (i > 0) {\n        along += stepAlongTrain(prevIsDouble, isDouble, dominoWidth, dominoHeight);\n      }\n      perp = 0;\n    } else if (isDouble) {\n      // Double stays in the current lane (aligned with the tile it connects to).\n      if (i > 0) {\n        along += stepAlongTrain(prevIsDouble, true, dominoWidth, dominoHeight);\n      }\n      // perp unchanged\n    } else if (i === 0) {\n      laneSign = outwardSign;\n      perp = laneSign * perpStep;\n    } else if (prevIsDouble) {\n      // First tile out of a double stays in the double's lane (mirror).\n      along += stepAlongTrain(true, false, dominoWidth, dominoHeight);\n      // perp unchanged\n    } else {\n      along += dominoHeight / 2;\n      laneSign = nextPerpOffset(laneSign, outwardSign);\n      perp = laneSign * perpStep;\n    }\n\n    positions.push({ along, perp });\n  }\n\n  return positions;\n}\n\nexport function validateConsecutiveSpacing(\n  layout: readonly TrainLayoutEntry[],\n  angle: number,\n  layoutStyle: TrainLayoutStyle,\n  tolerance = DEFAULT_TOLERANCE,\n  outwardSignOverride?: number\n): LayoutValidationResult {\n  const issues: LayoutValidationIssue[] = [];\n  const outwardSign = outwardSignOverride ?? outwardPerpSign(angle);\n  const expected = expectedAxisLayout(layout, layoutStyle, outwardSign);\n\n  for (let i = 1; i < layout.length; i++) {\n    const prev = layout[i - 1];\n    const current = layout[i];\n    const dx = current.x - prev.x;\n    const dy = current.y - prev.y;\n    const along = projectOnTrainAxis(dx, dy, angle);\n    const perp = projectOnPerpendicularAxis(dx, dy, angle);\n    const expectedAlong = expected[i].along - expected[i - 1].along;\n    const expectedPerp = expected[i].perp - expected[i - 1].perp;\n\n    if (Math.abs(along - expectedAlong) > tolerance) {\n      issues.push({\n        code: 'spacing-along-train',\n        message: `Along-train spacing between domino ${i - 1} and ${i} is ${along.toFixed(2)}px (expected ${expectedAlong}px)`,\n        index: i,\n      });\n    }\n\n    if (Math.abs(perp - expectedPerp) > tolerance) {\n      issues.push({\n        code: 'spacing-perpendicular',\n        message: `Perpendicular spacing between domino ${i - 1} and ${i} is ${perp.toFixed(2)}px (expected ${expectedPerp}px)`,\n        index: i,\n      });\n    }\n  }\n\n  return { valid: issues.length === 0, issues };\n}\n\nexport function validateNoPairOverlap(\n  layout: readonly TrainLayoutEntry[],\n  angle: number,\n  layoutStyle: TrainLayoutStyle,\n  tolerance = DEFAULT_TOLERANCE,\n  outwardSignOverride?: number\n): LayoutValidationResult {\n  const issues: LayoutValidationIssue[] = [];\n  const outwardSign = outwardSignOverride ?? outwardPerpSign(angle);\n  const expected = expectedAxisLayout(layout, layoutStyle, outwardSign);\n\n  for (let i = 1; i < layout.length; i++) {\n    const prev = layout[i - 1];\n    const current = layout[i];\n    const dx = current.x - prev.x;\n    const dy = current.y - prev.y;\n    const distance = Math.hypot(dx, dy);\n    const expectedAlong = expected[i].along - expected[i - 1].along;\n    const expectedPerp = expected[i].perp - expected[i - 1].perp;\n    const minDistance = Math.hypot(expectedAlong, expectedPerp) * 0.9;\n\n    if (distance + tolerance < minDistance) {\n      issues.push({\n        code: 'overlap',\n        message: `Domino ${i - 1} and ${i} centers are ${distance.toFixed(2)}px apart (minimum ${minDistance.toFixed(2)}px)`,\n        index: i,\n      });\n    }\n  }\n\n  return { valid: issues.length === 0, issues };\n}\n\nexport function validateTrainLayout(\n  layout: readonly TrainLayoutEntry[],\n  dominoes: readonly DominoValue[],\n  angle: number,\n  layoutStyle: TrainLayoutStyle,\n  tolerance = DEFAULT_TOLERANCE,\n  outwardSignOverride?: number\n): LayoutValidationResult {\n  const issues: LayoutValidationIssue[] = [\n    ...validateDominoChain(dominoes).issues,\n    ...validateConsecutiveSpacing(\n      layout,\n      angle,\n      layoutStyle,\n      tolerance,\n      outwardSignOverride\n    ).issues,\n    ...validateNoPairOverlap(\n      layout,\n      angle,\n      layoutStyle,\n      tolerance,\n      outwardSignOverride\n    ).issues,\n  ];\n\n  if (layout.length !== dominoes.length) {\n    issues.push({\n      code: 'layout-length',\n      message: `Layout length ${layout.length} does not match domino count ${dominoes.length}`,\n    });\n  }\n\n  return { valid: issues.length === 0, issues };\n}\n\n/**\n * Validates the chicken-foot branch tree as data: every chain links up, every\n * foot hangs off a real double, and every toe's first tile matches the double's\n * value. Recurses into nested feet.\n */\nexport function validateChickenFootChain(\n  branch: TrainBranch\n): LayoutValidationResult {\n  const issues: LayoutValidationIssue[] = [];\n\n  const walk = (current: TrainBranch, path: string) => {\n    issues.push(\n      ...validateDominoChain(current.dominoes).issues.map((issue) => ({\n        ...issue,\n        message: `[${path}] ${issue.message}`,\n      }))\n    );\n\n    if (!current.feet) {\n      return;\n    }\n\n    for (const key of Object.keys(current.feet)) {\n      const hostIndex = Number(key);\n      const host = current.dominoes[hostIndex];\n      const toes = current.feet[hostIndex] ?? [];\n\n      if (!host) {\n        issues.push({\n          code: 'foot-host-missing',\n          message: `[${path}] Foot references missing tile ${hostIndex}`,\n        });\n        continue;\n      }\n\n      if (host.value1 !== host.value2) {\n        issues.push({\n          code: 'foot-host-not-double',\n          message: `[${path}] Foot host tile ${hostIndex} is not a double`,\n        });\n      }\n\n      if (toes.length > 2) {\n        issues.push({\n          code: 'foot-too-many-toes',\n          message: `[${path}] Double ${hostIndex} has ${toes.length} side toes (max 2; the center toe is the main line)`,\n        });\n      }\n\n      toes.forEach((toe, toeIndex) => {\n        const first = toe.dominoes[0];\n        if (first && first.value1 !== host.value1) {\n          issues.push({\n            code: 'foot-connection',\n            message: `[${path}] Toe ${toeIndex} on double ${hostIndex} starts with ${first.value1} but the double is ${host.value1}`,\n          });\n        }\n        walk(toe, `${path}.${hostIndex}.${toeIndex}`);\n      });\n    }\n  };\n\n  walk(branch, 'main');\n  return { valid: issues.length === 0, issues };\n}\n\n/**\n * Validates a laid-out chicken-foot tree. Each run's tiles must link up by value\n * and match its tile count; each toe must start the right distance out from its\n * host double (measured along the toe's axis); and — the hard physical rule — no\n * two tiles anywhere in the tree may overlap.\n *\n * The center toe is a centered linear run while the inbound spine is offset, so\n * a single run can mix layout styles; the per-tile overlap test below checks the\n * real constraint directly rather than reconstructing each style's spacing.\n */\nexport function validateTrainTree(\n  segments: readonly TrainSegment[],\n  tolerance = DEFAULT_TOLERANCE\n): LayoutValidationResult {\n  const issues: LayoutValidationIssue[] = [];\n\n  segments.forEach((segment, segmentIndex) => {\n    issues.push(\n      ...validateDominoChain(segment.dominoes).issues.map((issue) => ({\n        ...issue,\n        message: `[segment ${segmentIndex} @${segment.angle}°] ${issue.message}`,\n      }))\n    );\n\n    if (segment.layout.length !== segment.dominoes.length) {\n      issues.push({\n        code: 'layout-length',\n        message: `[segment ${segmentIndex}] Layout length ${segment.layout.length} does not match domino count ${segment.dominoes.length}`,\n      });\n    }\n\n    if (segment.anchor && segment.layout.length > 0) {\n      const first = segment.layout[0];\n      const along = projectOnTrainAxis(\n        first.x - segment.anchor.x,\n        first.y - segment.anchor.y,\n        segment.angle\n      );\n      const expected = DOMINO_HEIGHT / 2;\n      if (Math.abs(along - expected) > tolerance) {\n        issues.push({\n          code: 'foot-anchor',\n          message: `[segment ${segmentIndex}] First toe tile sits ${along.toFixed(2)}px from the double along the toe (expected ${expected}px)`,\n          index: 0,\n        });\n      }\n    }\n  });\n\n  // Physical rule: dominoes are solid, so no two tiles may overlap anywhere.\n  const tiles = segments.flatMap((segment) => segment.layout);\n  for (let i = 0; i < tiles.length; i++) {\n    for (let j = i + 1; j < tiles.length; j++) {\n      if (tilesOverlap(tiles[i], tiles[j])) {\n        issues.push({\n          code: 'tile-overlap',\n          message: `Tiles ${i} and ${j} overlap`,\n          index: j,\n        });\n      }\n    }\n  }\n\n  return { valid: issues.length === 0, issues };\n}\n\nexport function dominoCorners(\n  entry: TrainLayoutEntry,\n  dominoWidth = DOMINO_WIDTH,\n  dominoHeight = DOMINO_HEIGHT\n): { x: number; y: number }[] {\n  const rotation = (entry.rotation * Math.PI) / 180;\n  const halfW = dominoWidth / 2;\n  const halfH = dominoHeight / 2;\n  const localCorners = [\n    { x: -halfW, y: -halfH },\n    { x: halfW, y: -halfH },\n    { x: halfW, y: halfH },\n    { x: -halfW, y: halfH },\n  ];\n\n  return localCorners.map(({ x, y }) => {\n    const rotatedX = x * Math.cos(rotation) - y * Math.sin(rotation);\n    const rotatedY = x * Math.sin(rotation) + y * Math.cos(rotation);\n    return { x: entry.x + rotatedX, y: entry.y + rotatedY };\n  });\n}\n","import { DominoValue } from '@/game/DominoValue';\nimport { TrainBranch } from '@/game/TrainData';\nimport { TrainLayoutStyle } from '@/app/trainLayout';\n\nexport interface TrainFixture {\n  id: string;\n  name: string;\n  description: string;\n  angle: number;\n  dominoes: DominoValue[];\n  layoutStyles: TrainLayoutStyle[];\n}\n\nexport interface ChickenFootFixture {\n  id: string;\n  name: string;\n  description: string;\n  angle: number;\n  branch: TrainBranch;\n  layoutStyles: TrainLayoutStyle[];\n}\n\nexport const TRAIN_FIXTURES: TrainFixture[] = [\n  {\n    id: 'regular-after-double',\n    name: 'Regular after double',\n    description: 'Double followed by a two-tile offset run',\n    angle: 0,\n    dominoes: [\n      { value1: 12, value2: 6 },\n      { value1: 6, value2: 6 },\n      { value1: 6, value2: 3 },\n      { value1: 3, value2: 1 },\n    ],\n    layoutStyles: ['linear', 'offset'],\n  },\n  {\n    id: 'double-after-regular',\n    name: 'Double after regular',\n    description: 'Offset run, a double, then another offset run',\n    angle: 0,\n    dominoes: [\n      { value1: 12, value2: 9 },\n      { value1: 9, value2: 4 },\n      { value1: 4, value2: 4 },\n      { value1: 4, value2: 2 },\n      { value1: 2, value2: 7 },\n    ],\n    layoutStyles: ['linear', 'offset'],\n  },\n  {\n    id: 'double-after-double',\n    name: 'Double after double',\n    description: 'Offset runs at the head, middle, and tail around two doubles',\n    angle: 90,\n    dominoes: [\n      { value1: 12, value2: 7 },\n      { value1: 7, value2: 8 },\n      { value1: 8, value2: 8 },\n      { value1: 8, value2: 3 },\n      { value1: 3, value2: 5 },\n      { value1: 5, value2: 5 },\n      { value1: 5, value2: 2 },\n      { value1: 2, value2: 1 },\n    ],\n    layoutStyles: ['linear', 'offset'],\n  },\n  {\n    id: 'offset-zigzag',\n    name: 'Offset zigzag',\n    description: 'Alternating perpendicular tiles without doubles',\n    angle: 0,\n    dominoes: [\n      { value1: 12, value2: 5 },\n      { value1: 5, value2: 9 },\n      { value1: 9, value2: 2 },\n      { value1: 2, value2: 7 },\n      { value1: 7, value2: 1 },\n    ],\n    layoutStyles: ['offset'],\n  },\n  {\n    id: 'horizontal-open',\n    name: 'Horizontal train',\n    description: 'Rightward train: offset head, double, offset tail',\n    angle: 0,\n    dominoes: [\n      { value1: 5, value2: 12 },\n      { value1: 12, value2: 11 },\n      { value1: 11, value2: 11 },\n      { value1: 11, value2: 6 },\n      { value1: 6, value2: 2 },\n    ],\n    layoutStyles: ['linear', 'offset'],\n  },\n  {\n    id: 'vertical-open',\n    name: 'Vertical train',\n    description: 'Downward train: offset head, double, offset tail',\n    angle: 90,\n    dominoes: [\n      { value1: 3, value2: 12 },\n      { value1: 12, value2: 10 },\n      { value1: 10, value2: 10 },\n      { value1: 10, value2: 4 },\n      { value1: 4, value2: 1 },\n    ],\n    layoutStyles: ['linear', 'offset'],\n  },\n];\n\nexport function getTrainFixture(id: string): TrainFixture | undefined {\n  return TRAIN_FIXTURES.find((fixture) => fixture.id === id);\n}\n\nexport const CHICKEN_FOOT_FIXTURES: ChickenFootFixture[] = [\n  {\n    id: 'single-foot',\n    name: 'Single foot',\n    description:\n      'A double fans two angled toes (±45°) while the main line continues straight as the center toe',\n    angle: 0,\n    branch: {\n      dominoes: [\n        { value1: 12, value2: 6 },\n        { value1: 6, value2: 6 },\n        { value1: 6, value2: 3 },\n        { value1: 3, value2: 1 },\n      ],\n      feet: {\n        1: [\n          {\n            dominoes: [\n              { value1: 6, value2: 2 },\n              { value1: 2, value2: 5 },\n            ],\n          },\n          {\n            dominoes: [\n              { value1: 6, value2: 4 },\n              { value1: 4, value2: 0 },\n            ],\n          },\n        ],\n      },\n    },\n    layoutStyles: ['linear', 'offset'],\n  },\n  {\n    id: 'foot-no-center',\n    name: 'Foot at the tail',\n    description:\n      'Double ends the main line, so both side toes are present with no straight continuation',\n    angle: 0,\n    branch: {\n      dominoes: [\n        { value1: 9, value2: 7 },\n        { value1: 7, value2: 7 },\n      ],\n      feet: {\n        1: [\n          {\n            dominoes: [\n              { value1: 7, value2: 3 },\n              { value1: 3, value2: 8 },\n            ],\n          },\n          {\n            dominoes: [\n              { value1: 7, value2: 5 },\n              { value1: 5, value2: 0 },\n            ],\n          },\n        ],\n      },\n    },\n    layoutStyles: ['linear', 'offset'],\n  },\n  {\n    id: 'nested-foot',\n    name: 'Nested foot',\n    description:\n      'A side toe contains its own double, which sprouts a second-level foot',\n    angle: 90,\n    branch: {\n      dominoes: [\n        { value1: 12, value2: 8 },\n        { value1: 8, value2: 8 },\n        { value1: 8, value2: 3 },\n      ],\n      feet: {\n        1: [\n          {\n            dominoes: [\n              { value1: 8, value2: 5 },\n              { value1: 5, value2: 5 },\n              { value1: 5, value2: 2 },\n            ],\n            feet: {\n              1: [\n                {\n                  dominoes: [\n                    { value1: 5, value2: 9 },\n                    { value1: 9, value2: 1 },\n                  ],\n                },\n                {\n                  dominoes: [\n                    { value1: 5, value2: 4 },\n                    { value1: 4, value2: 6 },\n                  ],\n                },\n              ],\n            },\n          },\n          {\n            dominoes: [\n              { value1: 8, value2: 1 },\n              { value1: 1, value2: 7 },\n            ],\n          },\n        ],\n      },\n    },\n    layoutStyles: ['linear', 'offset'],\n  },\n];\n\nexport function getChickenFootFixture(\n  id: string\n): ChickenFootFixture | undefined {\n  return CHICKEN_FOOT_FIXTURES.find((fixture) => fixture.id === id);\n}\n","import { DominoValue } from './DominoValue';\nimport { TrainBranch, TrainData } from './TrainData';\nimport { tileKey } from '@/rules/dominoSet';\n\nexport { tileKey };\n\nexport interface GenerateSampleTrainsOptions {\n  /** Attach chicken-foot side toes (±45°) to every double. */\n  chickenFeet?: boolean;\n}\n\n/** Demo train generator that respects tile-uniqueness constraints for the set. */\nexport function generateSampleTrains(\n  playerCount: number,\n  engineValue = 18,\n  options: GenerateSampleTrainsOptions = {}\n): TrainData[] {\n  const usedTiles = new Set<string>([tileKey(engineValue, engineValue)]);\n  const trains: TrainData[] = [];\n\n  for (let playerId = 0; playerId < playerCount; playerId++) {\n    const dominoCount = 4 + Math.floor(Math.random() * 7);\n    const dominoes: DominoValue[] = [];\n    let openValue = engineValue;\n    let prevWasDouble = false;\n\n    for (let j = 0; j < dominoCount; j++) {\n      const value2 = pickNextValue(\n        openValue,\n        prevWasDouble,\n        j === 0,\n        engineValue,\n        usedTiles\n      );\n\n      // No tile left that keeps the chain legal and unique: stop this train.\n      if (value2 === null) {\n        break;\n      }\n\n      const isDouble = value2 === openValue;\n      usedTiles.add(tileKey(openValue, value2));\n\n      dominoes.push({ value1: openValue, value2 });\n      prevWasDouble = isDouble;\n      openValue = value2;\n    }\n\n    const feet = options.chickenFeet\n      ? buildFeet(dominoes, usedTiles, engineValue)\n      : undefined;\n\n    trains.push({\n      playerId,\n      dominoes,\n      isPublic: Math.random() > 0.7,\n      ...(feet ? { feet } : {}),\n    });\n  }\n\n  return trains;\n}\n\n/**\n * Builds chicken-foot side toes for every double in a chain. Each toe is a short\n * straight run (no doubles, so no nested feet) starting from the double's value.\n */\nfunction buildFeet(\n  dominoes: readonly DominoValue[],\n  usedTiles: Set<string>,\n  maxPips: number\n): Record<number, TrainBranch[]> | undefined {\n  const feet: Record<number, TrainBranch[]> = {};\n\n  for (let i = 0; i < dominoes.length; i++) {\n    if (dominoes[i].value1 !== dominoes[i].value2) {\n      continue;\n    }\n\n    const doubleValue = dominoes[i].value1;\n    const toes: TrainBranch[] = [];\n    for (let t = 0; t < 2; t++) {\n      const toe = buildToe(doubleValue, usedTiles, maxPips);\n      if (toe) {\n        toes.push(toe);\n      }\n    }\n\n    if (toes.length) {\n      feet[i] = toes;\n    }\n  }\n\n  return Object.keys(feet).length ? feet : undefined;\n}\n\nfunction buildToe(\n  startValue: number,\n  usedTiles: Set<string>,\n  maxPips: number\n): TrainBranch | null {\n  const length = 1 + Math.floor(Math.random() * 2);\n  const dominoes: DominoValue[] = [];\n  let openValue = startValue;\n\n  for (let j = 0; j < length; j++) {\n    const next = pickNonDouble(openValue, usedTiles, maxPips);\n    if (next === null) {\n      break;\n    }\n    usedTiles.add(tileKey(openValue, next));\n    dominoes.push({ value1: openValue, value2: next });\n    openValue = next;\n  }\n\n  return dominoes.length ? { dominoes } : null;\n}\n\nfunction pickNonDouble(\n  openValue: number,\n  usedTiles: Set<string>,\n  maxPips: number\n): number | null {\n  const candidates: number[] = [];\n  for (let value = 0; value <= maxPips; value++) {\n    if (value === openValue) {\n      continue;\n    }\n    if (usedTiles.has(tileKey(openValue, value))) {\n      continue;\n    }\n    candidates.push(value);\n  }\n\n  if (candidates.length === 0) {\n    return null;\n  }\n\n  return candidates[Math.floor(Math.random() * candidates.length)];\n}\n\nfunction pickNextValue(\n  openValue: number,\n  prevWasDouble: boolean,\n  isFirstDomino: boolean,\n  engineValue: number,\n  usedTiles: Set<string>\n): number | null {\n  const candidates = Array.from({ length: engineValue + 1 }, (_, value) => value).filter(\n    (value) =>\n      isValidNextValue(\n        openValue,\n        value,\n        prevWasDouble,\n        isFirstDomino,\n        engineValue,\n        usedTiles\n      )\n  );\n\n  if (candidates.length === 0) {\n    return null;\n  }\n\n  return candidates[Math.floor(Math.random() * candidates.length)];\n}\n\nfunction isValidNextValue(\n  openValue: number,\n  value: number,\n  prevWasDouble: boolean,\n  isFirstDomino: boolean,\n  engineValue: number,\n  usedTiles: Set<string>\n): boolean {\n  const isDouble = value === openValue;\n\n  if (isFirstDomino && isDouble && openValue === engineValue) {\n    return false;\n  }\n\n  if (isDouble && prevWasDouble) {\n    return false;\n  }\n\n  if (usedTiles.has(tileKey(openValue, value))) {\n    return false;\n  }\n\n  return true;\n}\n","import { DominoValue } from '@/game/DominoValue';\nimport { TrainBranch } from '@/game/TrainData';\nimport {\n  dominoKey,\n  isDouble,\n  orientForConnection,\n} from '@/rules/dominoSet';\nimport {\n  RulesConfig,\n  requiredDoubleAnswers,\n  sideToeSlots,\n} from '@/rules/rulesConfig';\n\n/**\n * Locates a branch inside a chicken-foot tree. Empty path = the main line; each\n * step descends into the `toeIndex`-th side toe hanging off the double at\n * `doubleIndex` of the current branch.\n */\nexport type BranchPath = ReadonlyArray<{\n  doubleIndex: number;\n  toeIndex: number;\n}>;\n\nexport interface OpenEnd {\n  path: BranchPath;\n  attach: 'run-tail' | 'side-toe';\n  /** Pip value a tile must match to attach here. */\n  value: number;\n  /** For side-toe ends: which double in the branch, and which toe slot. */\n  doubleIndex?: number;\n  toeSlot?: number;\n  /** The tile being attached to is a double (for the no-consecutive rule). */\n  attachToDouble: boolean;\n  /** This end exists only because an unanswered double must be satisfied. */\n  obligation: boolean;\n}\n\nexport interface Move {\n  end: OpenEnd;\n  tile: DominoValue;\n}\n\nexport type PlacementViolation =\n  | 'value-mismatch'\n  | 'duplicate-tile'\n  | 'consecutive-doubles';\n\nexport interface PlacementResult {\n  legal: boolean;\n  violations: PlacementViolation[];\n}\n\nexport function getBranchAt(\n  root: TrainBranch,\n  path: BranchPath\n): TrainBranch | undefined {\n  let current: TrainBranch | undefined = root;\n  for (const step of path) {\n    current = current?.feet?.[step.doubleIndex]?.[step.toeIndex];\n    if (!current) return undefined;\n  }\n  return current;\n}\n\ninterface DoubleStatus {\n  path: BranchPath;\n  doubleIndex: number;\n  value: number;\n  hasCenter: boolean;\n  sideToes: number;\n  answers: number;\n}\n\nfunction walkBranches(\n  branch: TrainBranch,\n  path: BranchPath,\n  visit: (branch: TrainBranch, path: BranchPath) => void\n): void {\n  visit(branch, path);\n  if (!branch.feet) return;\n  for (const key of Object.keys(branch.feet)) {\n    const doubleIndex = Number(key);\n    branch.feet[doubleIndex].forEach((toe, toeIndex) => {\n      walkBranches(toe, [...path, { doubleIndex, toeIndex }], visit);\n    });\n  }\n}\n\nfunction collectDoubles(root: TrainBranch): DoubleStatus[] {\n  const out: DoubleStatus[] = [];\n  walkBranches(root, [], (branch, path) => {\n    branch.dominoes.forEach((domino, doubleIndex) => {\n      if (domino.value1 !== domino.value2) return;\n      const hasCenter = doubleIndex < branch.dominoes.length - 1;\n      const sideToes = branch.feet?.[doubleIndex]?.length ?? 0;\n      out.push({\n        path,\n        doubleIndex,\n        value: domino.value1,\n        hasCenter,\n        sideToes,\n        answers: (hasCenter ? 1 : 0) + sideToes,\n      });\n    });\n  });\n  return out;\n}\n\n/** Doubles that still owe answers under the current rules. */\nexport function getUnsatisfiedDoubles(\n  root: TrainBranch,\n  config: RulesConfig\n): DoubleStatus[] {\n  const required = requiredDoubleAnswers(config);\n  if (required <= 0) return [];\n  return collectDoubles(root).filter((d) => d.answers < required);\n}\n\n/** Every key of every tile already placed in the tree (for uniqueness checks). */\nexport function collectPlayedKeys(root: TrainBranch): Set<string> {\n  const keys = new Set<string>();\n  walkBranches(root, [], (branch) => {\n    for (const domino of branch.dominoes) {\n      keys.add(dominoKey(domino));\n    }\n  });\n  return keys;\n}\n\n/**\n * All places a tile may legally attach to this train, honoring double\n * obligations. When a double is unanswered (and the rules require answers),\n * only that double's open slots are offered until it is satisfied.\n */\nexport function getOpenEnds(\n  root: TrainBranch,\n  startValue: number,\n  config: RulesConfig\n): OpenEnd[] {\n  if (root.dominoes.length === 0) {\n    return [\n      {\n        path: [],\n        attach: 'run-tail',\n        value: startValue,\n        attachToDouble: true, // a train starts off the engine double\n        obligation: false,\n      },\n    ];\n  }\n\n  const unsatisfied = getUnsatisfiedDoubles(root, config);\n\n  if (config.doubleObligation !== 'none' && unsatisfied.length > 0) {\n    const slots = sideToeSlots(config);\n    const ends: OpenEnd[] = [];\n\n    for (const status of unsatisfied) {\n      const branch = getBranchAt(root, status.path);\n      if (!branch) continue;\n\n      // Center continuation: only available if the double is the run's tail.\n      if (!status.hasCenter && status.doubleIndex === branch.dominoes.length - 1) {\n        ends.push({\n          path: status.path,\n          attach: 'run-tail',\n          value: status.value,\n          attachToDouble: true,\n          obligation: true,\n        });\n      }\n\n      if (status.sideToes < slots) {\n        ends.push({\n          path: status.path,\n          attach: 'side-toe',\n          value: status.value,\n          doubleIndex: status.doubleIndex,\n          toeSlot: status.sideToes,\n          attachToDouble: true,\n          obligation: true,\n        });\n      }\n    }\n\n    return ends;\n  }\n\n  // No active obligation: the growing tip of every branch is open.\n  const ends: OpenEnd[] = [];\n  walkBranches(root, [], (branch, path) => {\n    const last = branch.dominoes[branch.dominoes.length - 1];\n    if (!last) return;\n    ends.push({\n      path,\n      attach: 'run-tail',\n      value: last.value2,\n      attachToDouble: isDouble(last),\n      obligation: false,\n    });\n  });\n  return ends;\n}\n\nexport function evaluatePlacement(\n  tile: DominoValue,\n  end: OpenEnd,\n  playedKeys: ReadonlySet<string>,\n  config: RulesConfig\n): PlacementResult {\n  const violations: PlacementViolation[] = [];\n\n  const oriented = orientForConnection(tile, end.value);\n  if (config.requireSequential && !oriented) {\n    violations.push('value-mismatch');\n  }\n\n  if (config.requireUniqueTiles && playedKeys.has(dominoKey(tile))) {\n    violations.push('duplicate-tile');\n  }\n\n  if (!config.allowConsecutiveDoubles && end.attachToDouble && isDouble(tile)) {\n    violations.push('consecutive-doubles');\n  }\n\n  return { legal: violations.length === 0, violations };\n}\n\n/** Every legal (open end × hand tile) move for this train. */\nexport function getLegalMoves(\n  root: TrainBranch,\n  startValue: number,\n  hand: readonly DominoValue[],\n  playedKeys: ReadonlySet<string>,\n  config: RulesConfig\n): Move[] {\n  const ends = getOpenEnds(root, startValue, config);\n  const moves: Move[] = [];\n  for (const end of ends) {\n    for (const tile of hand) {\n      if (evaluatePlacement(tile, end, playedKeys, config).legal) {\n        moves.push({ end, tile });\n      }\n    }\n  }\n  return moves;\n}\n\nfunction updateBranchAt(\n  branch: TrainBranch,\n  path: BranchPath,\n  updater: (branch: TrainBranch) => TrainBranch\n): TrainBranch {\n  if (path.length === 0) {\n    return updater(branch);\n  }\n  const [step, ...rest] = path;\n  const toes = branch.feet?.[step.doubleIndex] ?? [];\n  const updatedToes = toes.map((toe, index) =>\n    index === step.toeIndex ? updateBranchAt(toe, rest, updater) : toe\n  );\n  return {\n    ...branch,\n    feet: { ...branch.feet, [step.doubleIndex]: updatedToes },\n  };\n}\n\n/**\n * Returns a new tree with `move` applied. The tile is oriented so its matching\n * end connects. Does not validate; call {@link evaluatePlacement} first (or use\n * {@link playMove}).\n */\nexport function applyMove(\n  root: TrainBranch,\n  move: Move,\n  _config?: RulesConfig\n): TrainBranch {\n  const oriented =\n    orientForConnection(move.tile, move.end.value) ?? { ...move.tile };\n\n  return updateBranchAt(root, move.end.path, (branch) => {\n    if (move.end.attach === 'run-tail') {\n      return { ...branch, dominoes: [...branch.dominoes, oriented] };\n    }\n\n    const doubleIndex = move.end.doubleIndex ?? 0;\n    const slot = move.end.toeSlot ?? branch.feet?.[doubleIndex]?.length ?? 0;\n    const existing = branch.feet?.[doubleIndex]\n      ? [...branch.feet[doubleIndex]]\n      : [];\n    existing[slot] = { dominoes: [oriented] };\n    return {\n      ...branch,\n      feet: { ...branch.feet, [doubleIndex]: existing },\n    };\n  });\n}\n\nexport interface PlayMoveResult {\n  ok: boolean;\n  board: TrainBranch;\n  violations: PlacementViolation[];\n}\n\n/** Validates a move against the rules and applies it only if legal. */\nexport function playMove(\n  root: TrainBranch,\n  move: Move,\n  config: RulesConfig\n): PlayMoveResult {\n  const result = evaluatePlacement(\n    move.tile,\n    move.end,\n    collectPlayedKeys(root),\n    config\n  );\n  if (!result.legal) {\n    return { ok: false, board: root, violations: result.violations };\n  }\n  return { ok: true, board: applyMove(root, move, config), violations: [] };\n}\n","import { DominoValue } from '@/game/DominoValue';\nimport { TrainData } from '@/game/TrainData';\nimport { RulesConfig } from '@/rules/rulesConfig';\nimport { Move } from '@/rules/placement';\nimport { GenericHeuristic, Rng, SkillProfile } from './policy';\n\n// The skill/RNG primitives live in the model-agnostic policy core.\nexport type { Rng, SkillProfile } from './policy';\n\n/**\n * Base shape every action shares. Games extend the action space by declaring\n * their own `kind` literals (e.g. Warp12's `'deploy-beacon'`) and unioning them\n * with {@link AiAction}; the scoring pipeline treats unknown kinds opaquely.\n */\nexport interface AiActionBase {\n  readonly kind: string;\n}\n\n/** Attach `tile` at `move.end` of the train at `trainIndex` in the observation. */\nexport interface AiPlayAction extends AiActionBase {\n  readonly kind: 'play';\n  readonly trainIndex: number;\n  readonly move: Move;\n}\n\nexport interface AiDrawAction extends AiActionBase {\n  readonly kind: 'draw';\n}\n\nexport interface AiPassAction extends AiActionBase {\n  readonly kind: 'pass';\n}\n\n/** The base action space shared by all double-N variants. */\nexport type AiAction = AiPlayAction | AiDrawAction | AiPassAction;\n\n/** Narrows any action to a play action (kind discriminant on the base is widened). */\nexport function isPlayAction(action: AiActionBase): action is AiPlayAction {\n  return action.kind === 'play';\n}\n\n/**\n * Everything the bot is allowed to see this turn. Game-specific extras (beacon\n * flags, fracture state, scores, turn order…) ride along in {@link meta} so\n * custom heuristics can read them without changing this interface.\n */\nexport interface AiObservation {\n  readonly selfPlayerId: number;\n  readonly hand: readonly DominoValue[];\n  readonly rules: RulesConfig;\n  readonly trains: readonly TrainData[];\n  readonly engineValue: number;\n  /** Tiles left to draw; omit for \"unlimited/unknown\". 0 forbids drawing. */\n  readonly drawPileSize?: number;\n  /**\n   * Set by the host once this player has already taken their single draw this\n   * turn. Standard Mexican Train allows exactly one draw when you can't play;\n   * if the drawn tile still can't be played you must pass (which marks your\n   * train public). When true the generator stops offering `draw`, so the bot\n   * falls through to `pass` instead of draining the pile.\n   */\n  readonly turnDrawUsed?: boolean;\n  readonly meta?: Readonly<Record<string, unknown>>;\n}\n\n/** Shared, pre-computed turn data handed to every heuristic (built once per decision). */\nexport interface EvalContext {\n  readonly obs: AiObservation;\n  /** Canonical keys of every tile already on the table (global uniqueness). */\n  readonly playedKeys: ReadonlySet<string>;\n  readonly candidates: readonly AiActionBase[];\n  readonly playCandidates: readonly AiPlayAction[];\n  /** Tiles neither played nor in hand — the basis for tile-counting heuristics. */\n  readonly unseen: readonly DominoValue[];\n  readonly rng: Rng;\n}\n\n/**\n * A single, pure rule-of-thumb over the domino action space. Higher score =\n * more attractive; return 0 when it doesn't apply so it stays weight-neutral.\n * This is the domino specialization of the generic {@link GenericHeuristic}.\n */\nexport type Heuristic = GenericHeuristic<AiActionBase, EvalContext>;\n\n/** Produces the legal/considered actions for a turn. Override to change rules access. */\nexport type CandidateGenerator<TAction extends AiActionBase = AiAction> = (\n  obs: AiObservation\n) => TAction[];\n\nexport interface AiPlayer<TAction extends AiActionBase = AiAction> {\n  decide(obs: AiObservation): TAction;\n}\n\nexport interface CreateAiPlayerOptions<TAction extends AiActionBase = AiAction> {\n  skill: SkillProfile;\n  /** Defaults to {@link DEFAULT_HEURISTICS}. Append your own to extend behavior. */\n  heuristics?: Heuristic[];\n  /** Defaults to {@link defaultCandidateGenerator}. */\n  generateCandidates?: CandidateGenerator<TAction>;\n  /** Defaults to `Math.random`. Inject a seeded RNG for reproducible games/tests. */\n  rng?: Rng;\n}\n","import { TrainData } from '@/game/TrainData';\nimport { collectPlayedKeys, getLegalMoves } from '@/rules/placement';\nimport {\n  AiAction,\n  AiObservation,\n  AiPlayAction,\n  CandidateGenerator,\n} from './types';\n\n/**\n * Indices (into `obs.trains`) the player may legally build on under standard\n * Mexican Train access: your own train, plus any train flagged public. Games\n * with richer access rules (Warp12's Distress Beacon, locked fractures, …)\n * supply their own {@link CandidateGenerator}.\n */\nexport function getAccessibleTrainIndices(obs: AiObservation): number[] {\n  const indices: number[] = [];\n  obs.trains.forEach((train, index) => {\n    if (train.playerId === obs.selfPlayerId || train.isPublic) {\n      indices.push(index);\n    }\n  });\n  return indices;\n}\n\n/** Union of every played tile's key across all trains (uniqueness is global). */\nexport function collectAllPlayedKeys(\n  trains: readonly TrainData[]\n): Set<string> {\n  const keys = new Set<string>();\n  for (const train of trains) {\n    for (const key of collectPlayedKeys(train)) {\n      keys.add(key);\n    }\n  }\n  return keys;\n}\n\n/** Every legal placement of a hand tile onto an accessible train. */\nexport function generatePlayActions(obs: AiObservation): AiPlayAction[] {\n  const playedKeys = collectAllPlayedKeys(obs.trains);\n  const actions: AiPlayAction[] = [];\n\n  for (const trainIndex of getAccessibleTrainIndices(obs)) {\n    const train = obs.trains[trainIndex];\n    const moves = getLegalMoves(\n      train,\n      obs.engineValue,\n      obs.hand,\n      playedKeys,\n      obs.rules\n    );\n    for (const move of moves) {\n      actions.push({ kind: 'play', trainIndex, move });\n    }\n  }\n\n  return actions;\n}\n\nexport interface CandidateGeneratorOptions {\n  /**\n   * Offer `draw` even when legal plays exist. Off by default (canonical \"must\n   * play if you can\"). Turn on for variants where drawing is always optional —\n   * combined with a high blunder rate this is what makes a beginner draw when\n   * they didn't have to.\n   */\n  allowOptionalDraw?: boolean;\n}\n\n/**\n * Builds the standard candidate set: all legal plays, plus `draw` when the pile\n * isn't empty (and either there are no plays, or optional drawing is enabled),\n * falling back to `pass` only when nothing else is possible.\n */\nexport function createCandidateGenerator(\n  options: CandidateGeneratorOptions = {}\n): CandidateGenerator<AiAction> {\n  return (obs) => {\n    const plays = generatePlayActions(obs);\n    const actions: AiAction[] = [...plays];\n\n    const pileHasTiles = (obs.drawPileSize ?? Number.POSITIVE_INFINITY) > 0;\n    // One draw per turn: once the host marks the draw used, the bot must pass\n    // (which is what flips its own train public) rather than draw again.\n    const canDraw = pileHasTiles && !obs.turnDrawUsed;\n    if (canDraw && (plays.length === 0 || options.allowOptionalDraw)) {\n      actions.push({ kind: 'draw' });\n    }\n    if (actions.length === 0) {\n      actions.push({ kind: 'pass' });\n    }\n    return actions;\n  };\n}\n\nexport const defaultCandidateGenerator = createCandidateGenerator();\n","import {\n  dominoKey,\n  orientForConnection,\n  tileHasValue,\n} from '@/rules/dominoSet';\nimport { AiPlayAction, Heuristic, isPlayAction } from './types';\n\n/** Stable ids so skill profiles and overrides can reference heuristics by name. */\nexport const HEURISTIC_IDS = {\n  preferPlay: 'prefer-play',\n  dumpPips: 'dump-pips',\n  doublesEarly: 'play-doubles-early',\n  ownTrain: 'own-train',\n  obligationRelief: 'obligation-relief',\n  handFlexibility: 'hand-flexibility',\n  defensivePublic: 'defensive-public',\n} as const;\n\n/** Pip value the open end will expose after this tile is oriented and placed. */\nfunction newOpenEndValue(action: AiPlayAction): number {\n  const oriented = orientForConnection(action.move.tile, action.move.end.value);\n  return oriented ? oriented.value2 : action.move.tile.value2;\n}\n\n/**\n * Strongly favors playing over drawing, and drawing over passing. This is the\n * baseline that keeps every competent profile playing whenever it legally can;\n * mistakes come from the blunder rate, not from declining a free play.\n */\nconst preferPlay: Heuristic = {\n  id: HEURISTIC_IDS.preferPlay,\n  score(action) {\n    if (action.kind === 'play') return 100;\n    if (action.kind === 'draw') return 0;\n    return -50;\n  },\n};\n\n/** Offload weight: shed the heaviest tiles first to minimize end-of-round points. */\nconst dumpPips: Heuristic = {\n  id: HEURISTIC_IDS.dumpPips,\n  score(action) {\n    if (!isPlayAction(action)) return 0;\n    const tile = action.move.tile;\n    return tile.value1 + tile.value2;\n  },\n};\n\n/** Doubles are hard to get rid of late; nudge the bot to play them while its hand is full. */\nconst playDoublesEarly: Heuristic = {\n  id: HEURISTIC_IDS.doublesEarly,\n  score(action, ctx) {\n    if (!isPlayAction(action)) return 0;\n    const tile = action.move.tile;\n    if (tile.value1 !== tile.value2) return 0;\n    return Math.min(ctx.obs.hand.length, 12);\n  },\n};\n\n/** Playing on your own train keeps it active (shields up) and under your control. */\nconst ownTrain: Heuristic = {\n  id: HEURISTIC_IDS.ownTrain,\n  score(action, ctx) {\n    if (!isPlayAction(action)) return 0;\n    const train = ctx.obs.trains[action.trainIndex];\n    return train && train.playerId === ctx.obs.selfPlayerId ? 8 : 0;\n  },\n};\n\n/** Clearing an outstanding obligation (e.g. covering a double) unblocks the board. */\nconst obligationRelief: Heuristic = {\n  id: HEURISTIC_IDS.obligationRelief,\n  score(action) {\n    if (!isPlayAction(action)) return 0;\n    return action.move.end.obligation ? 10 : 0;\n  },\n};\n\n/**\n * Rewards leaving yourself a follow-up: how many of your remaining tiles can\n * attach to the new open end this move creates. Encourages building runs you\n * can actually continue rather than stranding yourself.\n */\nconst handFlexibility: Heuristic = {\n  id: HEURISTIC_IDS.handFlexibility,\n  score(action, ctx) {\n    if (!isPlayAction(action)) return 0;\n    const endValue = newOpenEndValue(action);\n    const playedKey = dominoKey(action.move.tile);\n\n    let skipped = false;\n    let matches = 0;\n    for (const tile of ctx.obs.hand) {\n      if (!skipped && dominoKey(tile) === playedKey) {\n        skipped = true; // don't count the tile we're about to play\n        continue;\n      }\n      if (tileHasValue(tile, endValue)) matches++;\n    }\n    return matches * 3;\n  },\n};\n\n/**\n * Defensive play on shared/opponent trains: prefer leaving an open end that is\n * hard for others to extend (few unseen tiles match it). Neutral on your own\n * train, where flow is desirable instead.\n */\nconst defensivePublic: Heuristic = {\n  id: HEURISTIC_IDS.defensivePublic,\n  score(action, ctx) {\n    if (!isPlayAction(action)) return 0;\n    const train = ctx.obs.trains[action.trainIndex];\n    if (!train || train.playerId === ctx.obs.selfPlayerId) return 0;\n\n    const endValue = newOpenEndValue(action);\n    let openCount = 0;\n    for (const tile of ctx.unseen) {\n      if (tileHasValue(tile, endValue)) openCount++;\n    }\n    return -openCount;\n  },\n};\n\n/** The stock, game-agnostic heuristic set. Append/replace by `id` to customize. */\nexport const DEFAULT_HEURISTICS: Heuristic[] = [\n  preferPlay,\n  dumpPips,\n  playDoublesEarly,\n  ownTrain,\n  obligationRelief,\n  handFlexibility,\n  defensivePublic,\n];\n","import { HEURISTIC_IDS } from './heuristics';\nimport { SkillProfile } from './types';\n\nconst H = HEURISTIC_IDS;\n\n/**\n * Stock skill tiers. Each is just a configuration of the same engine:\n *\n * - **beginner** — only cares about playing and lightly about dumping pips, with\n *   high temperature and a real blunder rate: erratic, often suboptimal plays.\n * - **intermediate** — adds doubles-early and own-train sense, low noise.\n * - **advanced** — full heuristic suite (obligations, flexibility, defense),\n *   near-deterministic, no blunders.\n *\n * Clone and tweak (`{ ...SKILL_PRESETS.advanced, temperature: 0.3 }`) for any\n * point on the spectrum.\n */\nexport const SKILL_PRESETS: Record<'beginner' | 'intermediate' | 'advanced', SkillProfile> = {\n  beginner: {\n    id: 'beginner',\n    temperature: 2.5,\n    blunderRate: 0.25,\n    lookaheadDepth: 0,\n    enabled: new Set([H.preferPlay, H.dumpPips]),\n    weights: {\n      [H.preferPlay]: 1,\n      [H.dumpPips]: 0.2,\n    },\n  },\n  intermediate: {\n    id: 'intermediate',\n    temperature: 0.6,\n    blunderRate: 0.05,\n    lookaheadDepth: 0,\n    enabled: new Set([H.preferPlay, H.dumpPips, H.doublesEarly, H.ownTrain]),\n    weights: {\n      [H.preferPlay]: 1,\n      [H.dumpPips]: 1,\n      [H.doublesEarly]: 1,\n      [H.ownTrain]: 1,\n    },\n  },\n  advanced: {\n    id: 'advanced',\n    temperature: 0.15,\n    blunderRate: 0,\n    lookaheadDepth: 0,\n    enabled: new Set([\n      H.preferPlay,\n      H.dumpPips,\n      H.doublesEarly,\n      H.ownTrain,\n      H.obligationRelief,\n      H.handFlexibility,\n      H.defensivePublic,\n    ]),\n    weights: {\n      [H.preferPlay]: 1,\n      [H.dumpPips]: 1.2,\n      [H.doublesEarly]: 1.5,\n      [H.ownTrain]: 1,\n      [H.obligationRelief]: 1.5,\n      [H.handFlexibility]: 1,\n      [H.defensivePublic]: 1.5,\n    },\n  },\n};\n\nexport type SkillLevel = keyof typeof SKILL_PRESETS;\n\nexport function getSkillProfile(level: SkillLevel): SkillProfile {\n  return SKILL_PRESETS[level];\n}\n","/**\n * Model-agnostic decision core shared by every game built on this library.\n *\n * Nothing here knows about dominoes, trains, or any specific rule set: it only\n * knows how to turn a set of candidate actions into one chosen action, given a\n * skill profile and a way to score actions. The domino-specific player\n * (`createAiPlayer`) and downstream variants (e.g. Warp12) are thin adapters\n * over {@link createPolicyPlayer}.\n */\n\n/** Pseudo-random source in [0, 1). Inject a seeded one for deterministic play. */\nexport type Rng = () => number;\n\n/**\n * The dials that define \"skill\". The same engine spans beginner→advanced purely\n * by changing which heuristics are active, their weights, and how sharply (or\n * randomly) the policy commits to the highest-scoring action.\n */\nexport interface SkillProfile {\n  readonly id: string;\n  /** Softmax temperature over candidate scores. 0 = argmax; higher = noisier. */\n  readonly temperature: number;\n  /** Probability of ignoring the policy and picking a uniformly random action. */\n  readonly blunderRate: number;\n  /** Plies of simulation (0 = greedy). Reserved; greedy-only in this release. */\n  readonly lookaheadDepth: number;\n  readonly weights: Readonly<Record<string, number>>;\n  readonly enabled: ReadonlySet<string>;\n}\n\n/**\n * A single, pure rule-of-thumb over actions of type `TAction`, given a turn\n * context of type `TCtx`. Higher score = more attractive; return 0 when the\n * heuristic doesn't apply so it stays weight-neutral.\n */\nexport interface GenericHeuristic<TAction, TCtx> {\n  readonly id: string;\n  score(action: TAction, ctx: TCtx): number;\n}\n\n/** Weighted sum of the enabled heuristics for one action. */\nexport function scoreWithHeuristics<TAction, TCtx>(\n  action: TAction,\n  ctx: TCtx,\n  byId: ReadonlyMap<string, GenericHeuristic<TAction, TCtx>>,\n  skill: SkillProfile\n): number {\n  let total = 0;\n  for (const id of skill.enabled) {\n    const heuristic = byId.get(id);\n    if (!heuristic) continue;\n    const weight = skill.weights[id] ?? 1;\n    total += weight * heuristic.score(action, ctx);\n  }\n  return total;\n}\n\n/** Index of the max score, breaking ties uniformly at random. */\nexport function argmaxIndex(scores: readonly number[], rng: Rng): number {\n  let best = Number.NEGATIVE_INFINITY;\n  let tied: number[] = [];\n  scores.forEach((score, index) => {\n    if (score > best) {\n      best = score;\n      tied = [index];\n    } else if (score === best) {\n      tied.push(index);\n    }\n  });\n  return tied[Math.floor(rng() * tied.length)];\n}\n\n/** Sample an index proportional to exp(score / temperature). */\nexport function softmaxIndex(\n  scores: readonly number[],\n  temperature: number,\n  rng: Rng\n): number {\n  const max = Math.max(...scores);\n  const weights = scores.map((score) => Math.exp((score - max) / temperature));\n  const sum = weights.reduce((acc, value) => acc + value, 0);\n\n  let threshold = rng() * sum;\n  for (let i = 0; i < weights.length; i++) {\n    threshold -= weights[i];\n    if (threshold <= 0) return i;\n  }\n  return weights.length - 1;\n}\n\n/** Temperature-controlled choice: argmax at 0, softmax sampling above it. */\nexport function chooseActionIndex(\n  scores: readonly number[],\n  skill: SkillProfile,\n  rng: Rng\n): number {\n  if (scores.length <= 1) return 0;\n  if (skill.temperature <= 0) return argmaxIndex(scores, rng);\n  return softmaxIndex(scores, skill.temperature, rng);\n}\n\nexport interface PolicyPlayerConfig<TObs, TAction, TCtx> {\n  skill: SkillProfile;\n  heuristics: ReadonlyArray<GenericHeuristic<TAction, TCtx>>;\n  generateCandidates: (obs: TObs) => TAction[];\n  buildContext: (obs: TObs, candidates: readonly TAction[]) => TCtx;\n  /** Returned when the generator yields no candidates at all. */\n  fallback: (obs: TObs) => TAction;\n  rng?: Rng;\n}\n\nexport interface PolicyPlayer<TObs, TAction> {\n  decide(obs: TObs): TAction;\n}\n\n/**\n * The reusable decision engine. Per turn:\n *\n *   observation → candidates → (blunder?) → weighted heuristics → policy → action\n *\n * Context is built once per decision and shared across heuristics. A single\n * candidate short-circuits scoring; an empty set returns `fallback`.\n */\nexport function createPolicyPlayer<TObs, TAction, TCtx>(\n  config: PolicyPlayerConfig<TObs, TAction, TCtx>\n): PolicyPlayer<TObs, TAction> {\n  const { skill, generateCandidates, buildContext, fallback } = config;\n  const rng = config.rng ?? Math.random;\n  const byId = new Map(config.heuristics.map((heuristic) => [heuristic.id, heuristic] as const));\n\n  return {\n    decide(obs) {\n      const candidates = generateCandidates(obs);\n      if (candidates.length === 0) return fallback(obs);\n      if (candidates.length === 1) return candidates[0];\n      if (skill.blunderRate > 0 && rng() < skill.blunderRate) {\n        return candidates[Math.floor(rng() * candidates.length)];\n      }\n\n      const ctx = buildContext(obs, candidates);\n      const scores = candidates.map((candidate) =>\n        scoreWithHeuristics(candidate, ctx, byId, skill)\n      );\n      return candidates[chooseActionIndex(scores, skill, rng)];\n    },\n  };\n}\n","import { dominoKey, generateDominoSet } from '@/rules/dominoSet';\nimport { collectAllPlayedKeys, defaultCandidateGenerator } from './candidate-generator';\nimport { DEFAULT_HEURISTICS } from './heuristics';\nimport { GenericHeuristic, Rng, createPolicyPlayer } from './policy';\nimport {\n  AiAction,\n  AiActionBase,\n  AiObservation,\n  AiPlayer,\n  CandidateGenerator,\n  CreateAiPlayerOptions,\n  EvalContext,\n  isPlayAction,\n} from './types';\n\nfunction buildEvalContext(\n  obs: AiObservation,\n  candidates: readonly AiActionBase[],\n  rng: Rng\n): EvalContext {\n  const playedKeys = collectAllPlayedKeys(obs.trains);\n\n  const seen = new Set<string>(playedKeys);\n  for (const tile of obs.hand) {\n    seen.add(dominoKey(tile));\n  }\n  const unseen = generateDominoSet(obs.rules.maxPips).filter(\n    (tile) => !seen.has(dominoKey(tile))\n  );\n\n  return {\n    obs,\n    playedKeys,\n    candidates,\n    playCandidates: candidates.filter(isPlayAction),\n    unseen,\n    rng,\n  };\n}\n\n/**\n * Builds an offline, heuristic-driven domino player over the standard double-N\n * model. The decision flow per turn:\n *\n *   observation → candidate generator → weighted heuristics → policy → action\n *\n * Every stage is injectable: swap the generator to change rules access, append\n * heuristics (including ones that read custom `kind`s or `obs.meta`) to teach it\n * variant-specific tactics, and pick/clone a {@link SkillProfile} to set strength.\n * Pass a seeded {@link Rng} for fully reproducible games. Under the hood this is\n * a thin adapter over the model-agnostic {@link createPolicyPlayer}.\n */\nexport function createAiPlayer<TAction extends AiActionBase = AiAction>(\n  options: CreateAiPlayerOptions<TAction>\n): AiPlayer<TAction> {\n  const heuristics = options.heuristics ?? DEFAULT_HEURISTICS;\n  const generate: CandidateGenerator<TAction> =\n    options.generateCandidates ??\n    (defaultCandidateGenerator as unknown as CandidateGenerator<TAction>);\n  const rng = options.rng ?? Math.random;\n\n  return createPolicyPlayer<AiObservation, TAction, EvalContext>({\n    skill: options.skill,\n    heuristics: heuristics as ReadonlyArray<GenericHeuristic<TAction, EvalContext>>,\n    generateCandidates: generate,\n    buildContext: (obs, candidates) => buildEvalContext(obs, candidates, rng),\n    fallback: () => ({ kind: 'pass' } as unknown as TAction),\n    rng,\n  });\n}\n","/**\n * Model-agnostic lookahead (\"gaming it out\").\n *\n * The greedy policy ({@link createPolicyPlayer}) scores the *current* options\n * with handcrafted heuristics. Search instead *simulates*: it applies an action\n * to a forward model, lets the game continue for a few plies, and evaluates the\n * resulting position. With imperfect information (hidden hands, an unknown draw\n * order) we can't do plain minimax, so we use **determinized depth-limited\n * search** (a.k.a. Perfect-Information Monte Carlo):\n *\n *   1. sample a plausible full world consistent with what we can see,\n *   2. run depth-limited paranoid minimax in that now-perfect-information world,\n *   3. average each root action's value over several sampled worlds.\n *\n * Nothing here knows about any specific game — a caller supplies a\n * {@link SearchModel}. Warp12 plugs its engine in to get real lookahead; the\n * same core could drive any turn-based game with a forward model.\n */\n\nimport { Rng } from './policy';\n\nexport type PlayerRef = number | string;\n\n/**\n * The forward model the search drives. Implement these over your engine:\n * `applyAction` is the transition function, `evaluate` is the leaf heuristic\n * (higher = better for `perspective`), and `determinize` samples the hidden\n * state so the search isn't allowed to peek at information a player shouldn't\n * have. `orderActions` is an optional breadth control (good move ordering lets\n * `maxBranch` prune to the promising moves).\n */\nexport interface SearchModel<TState, TAction> {\n  legalActions(state: TState): TAction[];\n  applyAction(state: TState, action: TAction): TState;\n  isTerminal(state: TState): boolean;\n  currentPlayer(state: TState): PlayerRef;\n  /** Position value from `perspective`'s point of view (higher is better). */\n  evaluate(state: TState, perspective: PlayerRef): number;\n  /** Sample a concrete world consistent with `perspective`'s knowledge. */\n  determinize?(state: TState, perspective: PlayerRef, rng: Rng): TState;\n  /** Reorder actions best-first; the search expands only the first `maxBranch`. */\n  orderActions?(state: TState, actions: TAction[]): TAction[];\n}\n\nexport interface SearchOptions {\n  /** Plies to look ahead, including the root action itself (>= 1). */\n  depth: number;\n  perspective: PlayerRef;\n  rng?: Rng;\n  /** Worlds to sample for imperfect-information averaging (default 1). */\n  determinizations?: number;\n  /** Cap candidates expanded per node (default unlimited). */\n  maxBranch?: number;\n}\n\nexport interface ScoredAction<TAction> {\n  readonly action: TAction;\n  readonly value: number;\n}\n\nfunction limitedActions<TState, TAction>(\n  state: TState,\n  model: SearchModel<TState, TAction>,\n  maxBranch: number\n): TAction[] {\n  let actions = model.legalActions(state);\n  if (model.orderActions) {\n    actions = model.orderActions(state, actions);\n  }\n  if (Number.isFinite(maxBranch) && actions.length > maxBranch) {\n    actions = actions.slice(0, maxBranch);\n  }\n  return actions;\n}\n\n/**\n * Paranoid minimax in a (now perfect-information) world: the perspective player\n * maximizes their own evaluation; everyone else is assumed to minimize it. This\n * is pessimistic but cheap and stable for multi-player games.\n */\nfunction minimaxValue<TState, TAction>(\n  state: TState,\n  model: SearchModel<TState, TAction>,\n  depth: number,\n  perspective: PlayerRef,\n  maxBranch: number\n): number {\n  if (depth <= 0 || model.isTerminal(state)) {\n    return model.evaluate(state, perspective);\n  }\n\n  const actions = limitedActions(state, model, maxBranch);\n  if (actions.length === 0) {\n    return model.evaluate(state, perspective);\n  }\n\n  const maximizing = model.currentPlayer(state) === perspective;\n  let best = maximizing ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;\n  for (const action of actions) {\n    const value = minimaxValue(\n      model.applyAction(state, action),\n      model,\n      depth - 1,\n      perspective,\n      maxBranch\n    );\n    best = maximizing ? Math.max(best, value) : Math.min(best, value);\n  }\n  return best;\n}\n\n/**\n * Value every root action by simulating it forward. For each action we average\n * its minimax value across `determinizations` sampled worlds. Returns one entry\n * per (ordered, breadth-capped) root action; the caller turns these values into\n * a choice (e.g. skill-scaled softmax via {@link chooseActionIndex}).\n */\nexport function searchActionValues<TState, TAction>(\n  rootState: TState,\n  model: SearchModel<TState, TAction>,\n  options: SearchOptions\n): ScoredAction<TAction>[] {\n  const rng = options.rng ?? Math.random;\n  const samples = Math.max(1, options.determinizations ?? 1);\n  const maxBranch = options.maxBranch ?? Number.POSITIVE_INFINITY;\n  const depth = Math.max(1, options.depth);\n\n  const rootActions = limitedActions(rootState, model, maxBranch);\n\n  return rootActions.map((action) => {\n    let total = 0;\n    for (let sample = 0; sample < samples; sample++) {\n      const world = model.determinize\n        ? model.determinize(rootState, options.perspective, rng)\n        : rootState;\n      total += minimaxValue(\n        model.applyAction(world, action),\n        model,\n        depth - 1,\n        options.perspective,\n        maxBranch\n      );\n    }\n    return { action, value: total / samples };\n  });\n}\n","import type { DominoSetSize } from '../variants';\nimport { normalizeSetSize } from '../variants';\n\nexport interface DominoFixture {\n  id: string;\n  label: string;\n  value1: number;\n  value2: number;\n  rotation?: number;\n}\n\nexport function doubleFixtures(maxPips: number): DominoFixture[] {\n  return Array.from({ length: maxPips + 1 }, (_, value) => ({\n    id: `double-${value}`,\n    label: `${value}|${value}`,\n    value1: value,\n    value2: value,\n  }));\n}\n\nexport const DOUBLE_FIXTURES = doubleFixtures(12);\n\nexport function mixedFixtures(maxPips: number): DominoFixture[] {\n  const pairs: [number, number][] = [\n    [maxPips, 0],\n    [Math.max(0, maxPips - 1), 3],\n    [Math.max(0, maxPips - 2), 5],\n    [9, 7],\n    [8, 2],\n    [6, 4],\n    [5, 1],\n  ];\n  return pairs\n    .filter(([a, b]) => a <= maxPips && b <= maxPips)\n    .map(([value1, value2]) => ({\n      id: `${value1}-${value2}`,\n      label: `${value1}|${value2}`,\n      value1,\n      value2,\n    }));\n}\n\nexport const MIXED_FIXTURES = mixedFixtures(12);\n\nexport const ROTATION_FIXTURES: DominoFixture[] = [0, 90, 180, 270].map(\n  (rotation) => ({\n    id: `rotation-${rotation}`,\n    label: `6|9 @ ${rotation}°`,\n    value1: 6,\n    value2: 9,\n    rotation,\n  })\n);\n\n/**\n * Asymmetric faces (13–15) make the required 180° second-half rotation visible:\n * remainder pips sit on one side of the body only. Symmetric faces (0–12, 16–18)\n * look identical with or without the half rotation.\n */\nexport function halfOrientationFixtures(maxPips: number): DominoFixture[] {\n  const asymmetric = [13, 14, 15].filter((value) => value <= maxPips);\n  return asymmetric.flatMap((value) => [\n    {\n      id: `half-orient-double-${value}`,\n      label: `${value}|${value} (half 180°)`,\n      value1: value,\n      value2: value,\n    },\n    {\n      id: `half-orient-mixed-${value}`,\n      label: `${value}|0 (half 180°)`,\n      value1: value,\n      value2: 0,\n    },\n  ]);\n}\n\nexport function parseSetParam(raw: string | null): DominoSetSize {\n  const parsed = raw ? Number(raw) : NaN;\n  return normalizeSetSize(Number.isFinite(parsed) ? parsed : undefined);\n}\n"],"mappings":"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G,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,CACpC,EACA,EAAG,CACD,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,CACpC,EACA,EAAG,CACD,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,CACpC,EACA,EAAG,CACD,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,CACpC,EACA,EAAG,CACD,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,EAClC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAM,CACpC,EACA,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"}