{
  "schemaVersion": 1,
  "id": "billiard-break",
  "version": "1.0.0",
  "status": "published",
  "kind": "object",
  "title": "Billiard Break",
  "summary": "The cue strikes, a rack of numbered balls bursts across the green baize, then rolls back to the rack. Loops perfectly.",
  "description": "A pool table at a slant under a lamp: the cue draws back and strikes, the white ball runs into the racked triangle and fifteen numbered balls burst across the cloth, bounce off the cushions and slow to a stop; then they roll back one by one into the rack and the cue ball returns to its spot, ready for the next break. Choose the cloth (green, blue, red or black), how hard the break is, the seed, the backdrop and whether the camera drifts.",
  "role": "Object (3D, three.js): a satisfying, punchy scene for games, sport, bar and energy content.",
  "collections": [
    "satisfying",
    "bouncing-falling",
    "hypnotic-loops"
  ],
  "tags": [
    "billiards",
    "pool",
    "break",
    "balls",
    "cue",
    "table",
    "snooker",
    "satisfying",
    "game",
    "loop",
    "3d"
  ],
  "synonyms": [
    "pool break",
    "eight ball",
    "nine ball",
    "rack and break",
    "billiard balls",
    "cue ball"
  ],
  "useCases": [
    "A game-night, bar or sports title scene",
    "A burst-and-return motion for a logo or reveal",
    "A satisfying looping background"
  ],
  "featured": null,
  "publishedAt": "2026-10-05",
  "implementation": {
    "target": "three",
    "runtime": "browser",
    "language": "javascript",
    "module": "esm",
    "entry": "src/billiard-break.js",
    "exports": [
      {
        "name": "createBilliardTable",
        "signature": "createBilliardTable({ cloth?, power?, seed? }) => { group, setProgress(progress), dispose() }",
        "description": "The table, cue and sixteen balls as a group (10 × 5, cloth on y = 0); the loop is 14 seconds. Add it with an environment map and a light."
      },
      {
        "name": "CLOTHS",
        "signature": "CLOTHS: { green, blue, red, black }",
        "description": "Cloth colours."
      }
    ],
    "reusable": [
      "src/billiard-break.js"
    ],
    "demo": "demo.js",
    "harness": "index.html"
  },
  "dependencies": [
    {
      "id": "three",
      "version": "0.185.1",
      "reason": "Renderer, geometry and physical materials."
    },
    {
      "id": "chrome-material",
      "version": "1.1.0",
      "scope": "demo",
      "reason": "Studio environment light, backdrop and contact shadow."
    },
    {
      "id": "apw-runtime",
      "version": "1.0.0",
      "scope": "demo",
      "reason": "Demo harness."
    }
  ],
  "params": [
    {
      "key": "cloth",
      "label": "Cloth",
      "type": "select",
      "default": "green",
      "options": [
        {
          "value": "green",
          "label": "Green"
        },
        {
          "value": "blue",
          "label": "Blue"
        },
        {
          "value": "red",
          "label": "Red"
        },
        {
          "value": "black",
          "label": "Black"
        }
      ]
    },
    {
      "key": "power",
      "label": "Power",
      "help": "How hard the break is.",
      "type": "number",
      "min": 0.5,
      "max": 1.6,
      "step": 0.05,
      "default": 1
    },
    {
      "key": "seed",
      "label": "Seed",
      "type": "integer",
      "min": 1,
      "max": 999,
      "step": 1,
      "default": 3
    },
    {
      "key": "background",
      "label": "Backdrop",
      "type": "color",
      "default": "#14181f"
    },
    {
      "key": "orbit",
      "label": "Camera drift",
      "type": "boolean",
      "default": true
    }
  ],
  "presets": [
    {
      "id": "default",
      "label": "Green baize, a clean break",
      "params": {}
    },
    {
      "id": "blue",
      "label": "Blue cloth, hard break",
      "params": {
        "cloth": "blue",
        "power": 1.4,
        "seed": 11,
        "background": "#10151f"
      }
    },
    {
      "id": "red",
      "label": "Red cloth",
      "params": {
        "cloth": "red",
        "seed": 6,
        "background": "#1c1214"
      }
    },
    {
      "id": "soft",
      "label": "A soft break",
      "params": {
        "power": 0.6,
        "cloth": "black",
        "background": "#1a1a1a"
      }
    }
  ],
  "output": {
    "width": 1080,
    "height": 1080,
    "fps": 30,
    "durationSeconds": 14,
    "posterSeconds": 6,
    "alpha": false,
    "motion": "procedural",
    "loop": {
      "seamless": true,
      "cycleSeconds": 14,
      "method": "After the strike every ball's place is a closed form of the time (friction-slowed travel folded back by the cushions), then all balls ease back to the rack and the cue ball to its spot before the loop ends, so the last frame meets the first."
    },
    "seed": {
      "param": "seed",
      "behavior": "Seed varies each ball's break direction, speed and friction: same seed, same break."
    }
  },
  "composition": {
    "inputs": "Time within the loop; the cloth, power, seed, backdrop and camera drift.",
    "outputs": "A full frame rendered in 3D with soft shadows.",
    "coordinates": "The cloth lies on y = 0, 10 along x and 5 along z; the rack stands at x = 2.4 and the cue ball at x = -3.3.",
    "lighting": "A key light with a shadow map and the studio environment on the glossy balls.",
    "timing": "setProgress(progress) sets every ball; frames render in any order. The loop's 14 seconds are the cue (0 to 1.5 s), the break (1.9 s), the balls at rest (about 8.8 s), the return (10.6 to 12.8 s) and a pause.",
    "compatibleWith": [
      "chrome-material",
      "orbit-camera",
      "kinetic-title",
      "number-reveal"
    ],
    "notes": [
      "x(t) = x0 + (v/k)(1 - e^(-kt)), folded into the cushions by a triangle wave; balls roll by the distance travelled."
    ]
  },
  "reuse": [
    "Game-night title: Green baize, a clean break.",
    "Energetic reveal: Blue cloth, hard break.",
    "Quiet loop: A soft break."
  ],
  "integration": {
    "newProject": "Create a WebGLRenderer with shadows and a studio environment (see chrome-material), add createBilliardTable().group and call table.setProgress(progress) each frame; use a 14-second loop.",
    "existingProject": "Copy src/billiard-break.js; add table.group to your scene with an environment map and a shadow-casting light."
  },
  "performance": {
    "notes": "Sixteen sphere meshes with canvas number textures, a few boxes and one shadow map. A few milliseconds a frame on an integrated Intel GPU.",
    "limits": "Software WebGL renders in about a second a frame."
  },
  "limitations": [
    "The balls do not collide with one another or fall into pockets: they scatter on divergent paths and may pass through each other.",
    "The rack's order is fixed; there is no cue-ball English."
  ],
  "license": {
    "spdx": "MIT",
    "notice": "Copyright (c) 2026 AI Pixel Wizard. Released under the MIT License. See LICENSE in this folder.",
    "thirdParty": []
  },
  "provenance": "Adapted from the owner's threejs-sketches/physics-simulation/billiard-break (MIT) and rebuilt for AI Pixel Wizard in October 2026 as an original scene: a closed-form, cushion-folded scatter, numbered balls drawn on canvas, a cue strike and a return to the rack for an exact loop.",
  "changelog": [
    {
      "version": "1.0.0",
      "date": "2026-10-05",
      "notes": "Initial release."
    }
  ]
}
