{
  "service": "scrap-quilt",
  "tagline": "the yard IS the sheet — Scrapcraft's whole game state as live quilt cells",
  "sheet": "scrap",
  "layout": [
    {
      "id": "player.x",
      "group": "player",
      "label": "Player X",
      "emoji": "🚶",
      "kind": "value",
      "fmt": "num",
      "live": true,
      "description": "Where the kid stands in the yard, left→right.",
      "feeds": []
    },
    {
      "id": "player.z",
      "group": "player",
      "label": "Player Z",
      "emoji": "🚶",
      "kind": "value",
      "fmt": "num",
      "live": true,
      "description": "Where the kid stands, north→south.",
      "feeds": []
    },
    {
      "id": "player.biome",
      "group": "player",
      "label": "Biome",
      "emoji": "🗺",
      "kind": "value",
      "fmt": "str",
      "description": "Which scrapyard biome the kid is in (heaps, pits, workshop…).",
      "feeds": []
    },
    {
      "id": "player.scrap",
      "group": "player",
      "label": "Scrap",
      "emoji": "💰",
      "kind": "value",
      "fmt": "int",
      "description": "Scrap currency in the backpack — the yard's money.",
      "feeds": []
    },
    {
      "id": "player.inventoryCount",
      "group": "player",
      "label": "Backpack",
      "emoji": "🎒",
      "kind": "value",
      "fmt": "int",
      "description": "How many items the kid is hauling around.",
      "feeds": []
    },
    {
      "id": "robot.x",
      "group": "robot",
      "label": "Robot X",
      "emoji": "📍",
      "kind": "value",
      "fmt": "num",
      "live": true,
      "description": "Robot position, left→right on the yard grid.",
      "feeds": [
        "race.lapFrac"
      ]
    },
    {
      "id": "robot.z",
      "group": "robot",
      "label": "Robot Z",
      "emoji": "📍",
      "kind": "value",
      "fmt": "num",
      "live": true,
      "description": "Robot position, north→south.",
      "feeds": [
        "race.lapFrac"
      ]
    },
    {
      "id": "robot.heading",
      "group": "robot",
      "label": "Heading",
      "emoji": "🧭",
      "kind": "value",
      "fmt": "deg",
      "live": true,
      "description": "Which way the robot faces, 0–360°. 0 = north.",
      "feeds": []
    },
    {
      "id": "robot.batteryV",
      "group": "robot",
      "label": "Battery",
      "emoji": "🔋",
      "kind": "value",
      "fmt": "num",
      "live": true,
      "description": "Battery pack voltage. 8.4 V = full 2S LiPo; it sags as motors draw.",
      "feeds": [
        "robot.motorL.volts",
        "robot.motorR.volts",
        "robot.battery.pct"
      ]
    },
    {
      "id": "robot.dutyL",
      "group": "robot",
      "label": "Duty Left",
      "emoji": "⚙️",
      "kind": "value",
      "fmt": "pct",
      "live": true,
      "description": "Left motor PWM duty, −1…1. The program sets this pin.",
      "feeds": [
        "robot.motorL.volts",
        "robot.power.draw"
      ]
    },
    {
      "id": "robot.dutyR",
      "group": "robot",
      "label": "Duty Right",
      "emoji": "⚙️",
      "kind": "value",
      "fmt": "pct",
      "live": true,
      "description": "Right motor PWM duty, −1…1. Set by the program each tick.",
      "feeds": [
        "robot.motorR.volts",
        "robot.power.draw"
      ]
    },
    {
      "id": "robot.drivePower",
      "group": "robot",
      "label": "Drive Power",
      "emoji": "🛞",
      "kind": "value",
      "fmt": "pct",
      "description": "Net drive actuator, −1…1 — VirtualRobot.setDrive(). Pins stay set until changed.",
      "feeds": [
        "robot.speed"
      ]
    },
    {
      "id": "robot.turnPower",
      "group": "robot",
      "label": "Turn Power",
      "emoji": "🔄",
      "kind": "value",
      "fmt": "pct",
      "description": "Net turn actuator, −1…1 — VirtualRobot.setTurn().",
      "feeds": [
        "robot.turnRate"
      ]
    },
    {
      "id": "robot.sensor.ultrasonic",
      "group": "robot",
      "label": "Ultrasonic",
      "emoji": "📡",
      "kind": "value",
      "fmt": "num",
      "live": true,
      "description": "Distance to whatever is ahead, in blocks (0–6). The HC-SR04 of the yard.",
      "feeds": [
        "robot.think",
        "robot.sensor.sonarNorm"
      ]
    },
    {
      "id": "robot.sensor.ir",
      "group": "robot",
      "label": "IR Line",
      "emoji": "📉",
      "kind": "value",
      "fmt": "num",
      "live": true,
      "description": "Line-follower reflectivity under the bot, 0–1. High = tape detected.",
      "feeds": [
        "robot.think"
      ]
    },
    {
      "id": "robot.sensor.encoder",
      "group": "robot",
      "label": "Encoder",
      "emoji": "🧮",
      "kind": "value",
      "fmt": "int",
      "live": true,
      "description": "Cumulative wheel encoder ticks. 20 ticks = 1 block traveled.",
      "feeds": [
        "robot.distTraveled"
      ]
    },
    {
      "id": "robot.gripper",
      "group": "robot",
      "label": "Gripper",
      "emoji": "🦾",
      "kind": "value",
      "fmt": "str",
      "description": "Claw state: open, closed, or holding.",
      "feeds": []
    },
    {
      "id": "robot.think",
      "group": "robot",
      "label": "Think",
      "emoji": "🧠",
      "kind": "formula",
      "deps": [
        "robot.sensor.ultrasonic",
        "robot.sensor.ir"
      ],
      "fmt": "str",
      "expr": "sonar < 1.5 ? \"avoid\" : ir > 0.6 ? \"follow\" : \"cruise\"",
      "description": "The robot's little brain: obstacle close → avoid; line under → follow; else cruise. Sensors flow INTO logic.",
      "feeds": []
    },
    {
      "id": "robot.motorL.volts",
      "group": "robot",
      "label": "Motor L Volts",
      "emoji": "🔌",
      "kind": "formula",
      "deps": [
        "robot.batteryV",
        "robot.dutyL"
      ],
      "fmt": "num",
      "expr": "robot_batteryV * robot_dutyL",
      "description": "Real motor math: left terminal voltage = battery volts × PWM duty. Logic flows INTO motors.",
      "feeds": []
    },
    {
      "id": "robot.motorR.volts",
      "group": "robot",
      "label": "Motor R Volts",
      "emoji": "🔌",
      "kind": "formula",
      "deps": [
        "robot.batteryV",
        "robot.dutyR"
      ],
      "fmt": "num",
      "expr": "robot_batteryV * robot_dutyR",
      "description": "Right terminal voltage = battery volts × PWM duty.",
      "feeds": []
    },
    {
      "id": "robot.power.draw",
      "group": "robot",
      "label": "Power Draw",
      "emoji": "⚡",
      "kind": "formula",
      "deps": [
        "robot.dutyL",
        "robot.dutyR"
      ],
      "fmt": "num",
      "expr": "abs(robot_dutyL) + abs(robot_dutyR)",
      "description": "Total normalized motor draw, 0–2. Motors flow INTO telemetry.",
      "feeds": []
    },
    {
      "id": "robot.battery.pct",
      "group": "robot",
      "label": "Battery %",
      "emoji": "🔋",
      "kind": "formula",
      "deps": [
        "robot.batteryV"
      ],
      "fmt": "pct",
      "expr": "robot_batteryV / 8.4 * 100",
      "description": "Battery gauge: volts ÷ full-charge volts. This is the bar the kid watches.",
      "feeds": []
    },
    {
      "id": "robot.speed",
      "group": "robot",
      "label": "Speed",
      "emoji": "💨",
      "kind": "formula",
      "deps": [
        "robot.drivePower"
      ],
      "fmt": "num",
      "live": true,
      "expr": "robot_drivePower * 3.0",
      "description": "Blocks per second = drive power × DRIVE_SPEED (3.0). The same constant the in-game sim uses.",
      "feeds": [
        "race.onLine"
      ]
    },
    {
      "id": "robot.turnRate",
      "group": "robot",
      "label": "Turn Rate",
      "emoji": "🔄",
      "kind": "formula",
      "deps": [
        "robot.turnPower"
      ],
      "fmt": "num",
      "expr": "robot_turnPower * 180",
      "description": "Degrees per second = turn power × TURN_RATE (180).",
      "feeds": []
    },
    {
      "id": "robot.distTraveled",
      "group": "robot",
      "label": "Distance",
      "emoji": "📏",
      "kind": "formula",
      "deps": [
        "robot.sensor.encoder"
      ],
      "fmt": "num",
      "live": true,
      "expr": "robot_sensor_encoder / 20",
      "description": "Blocks traveled = encoder ticks ÷ 20. Real odometry, straight off the wheel counter.",
      "feeds": []
    },
    {
      "id": "robot.sensor.sonarNorm",
      "group": "robot",
      "label": "Sonar %",
      "emoji": "📡",
      "kind": "formula",
      "deps": [
        "robot.sensor.ultrasonic"
      ],
      "fmt": "num",
      "expr": "robot_sensor_ultrasonic / 6.0",
      "description": "Ultrasonic normalized to the 6-block range: 1 = wide open, 0 = kissing the wall.",
      "feeds": []
    },
    {
      "id": "program.currentTile",
      "group": "program",
      "label": "Current Tile",
      "emoji": "🎯",
      "kind": "value",
      "fmt": "str",
      "description": "Opcode executing this tick: drive, turn, repeat, forever, if_line, wait_until…",
      "feeds": []
    },
    {
      "id": "program.ip",
      "group": "program",
      "label": "Instr Pointer",
      "emoji": "➡️",
      "kind": "value",
      "fmt": "int",
      "live": true,
      "description": "Instruction pointer — which tile the VM is on.",
      "feeds": [
        "program.progressPct"
      ]
    },
    {
      "id": "program.length",
      "group": "program",
      "label": "Length",
      "emoji": "🧵",
      "kind": "value",
      "fmt": "int",
      "description": "Tiles in the compiled program.",
      "feeds": [
        "program.progressPct"
      ]
    },
    {
      "id": "program.loopDepth",
      "group": "program",
      "label": "Loop Depth",
      "emoji": "🔁",
      "kind": "value",
      "fmt": "int",
      "live": true,
      "description": "How deep the VM is inside repeat/forever frames.",
      "feeds": []
    },
    {
      "id": "program.tilesRun",
      "group": "program",
      "label": "Tiles Run",
      "emoji": "🏃",
      "kind": "value",
      "fmt": "int",
      "description": "Tiles executed since Run was pressed.",
      "feeds": []
    },
    {
      "id": "program.state",
      "group": "program",
      "label": "State",
      "emoji": "▶️",
      "kind": "value",
      "fmt": "str",
      "description": "idle | running | blocked | done.",
      "feeds": []
    },
    {
      "id": "program.progressPct",
      "group": "program",
      "label": "Progress",
      "emoji": "📊",
      "kind": "formula",
      "deps": [
        "program.ip",
        "program.length"
      ],
      "fmt": "pct",
      "expr": "program_ip / max(program_length, 1) * 100",
      "description": "How far through the program: IP ÷ length × 100.",
      "feeds": []
    },
    {
      "id": "race.lap",
      "group": "race",
      "label": "Lap",
      "emoji": "🏁",
      "kind": "value",
      "fmt": "int",
      "live": true,
      "description": "Laps completed. The YARD detects this from position crossing — no trust-me bro.",
      "feeds": []
    },
    {
      "id": "race.splitMs",
      "group": "race",
      "label": "Split",
      "emoji": "⏱",
      "kind": "value",
      "fmt": "int",
      "live": true,
      "description": "Last lap time in milliseconds.",
      "feeds": []
    },
    {
      "id": "race.bestLapMs",
      "group": "race",
      "label": "Best Lap",
      "emoji": "🥇",
      "kind": "value",
      "fmt": "int",
      "live": true,
      "description": "Personal best lap, ms. Feeds the Circuit City leaderboard.",
      "feeds": []
    },
    {
      "id": "race.position",
      "group": "race",
      "label": "Grid Pos",
      "emoji": "📐",
      "kind": "value",
      "fmt": "int",
      "description": "Position in the field, 1 = leading.",
      "feeds": []
    },
    {
      "id": "race.lapFrac",
      "group": "race",
      "label": "Lap Fraction",
      "emoji": "🥧",
      "kind": "formula",
      "deps": [
        "robot.x",
        "robot.z"
      ],
      "fmt": "pct",
      "live": true,
      "expr": "angleAroundTrack(robot_x, robot_z)",
      "description": "Angle around the oval, 0–1, computed from position. Crossing 1→0 IS the start line — lap detection formula.",
      "feeds": [
        "race.onLine"
      ]
    },
    {
      "id": "race.onLine",
      "group": "race",
      "label": "Near Line",
      "emoji": "🎚",
      "kind": "formula",
      "deps": [
        "race.lapFrac",
        "robot.speed"
      ],
      "fmt": "bool",
      "expr": "race_lapFrac < 0.08 && robot_speed > 0",
      "description": "TRUE in the first 8% of the lap while moving — the lap-counter gate.",
      "feeds": []
    },
    {
      "id": "build.partsCount",
      "group": "build",
      "label": "Parts",
      "emoji": "🔩",
      "kind": "value",
      "fmt": "int",
      "description": "Components bolted onto the chassis right now.",
      "feeds": []
    },
    {
      "id": "build.chassisIntegrity",
      "group": "build",
      "label": "Chassis HP",
      "emoji": "🛡",
      "kind": "value",
      "fmt": "num",
      "live": true,
      "description": "Chassis hit points. Crashes and scrapes chew this down.",
      "feeds": [
        "build.integrityPct"
      ]
    },
    {
      "id": "build.maxIntegrity",
      "group": "build",
      "label": "Max HP",
      "emoji": "🛡",
      "kind": "value",
      "fmt": "num",
      "description": "Chassis HP when freshly welded.",
      "feeds": [
        "build.integrityPct"
      ]
    },
    {
      "id": "build.motorTier",
      "group": "build",
      "label": "Motor Tier",
      "emoji": "🏎",
      "kind": "value",
      "fmt": "int",
      "description": "Upgrade tier of the drive motors, 1–3.",
      "feeds": []
    },
    {
      "id": "build.integrityPct",
      "group": "build",
      "label": "Integrity %",
      "emoji": "📊",
      "kind": "formula",
      "deps": [
        "build.chassisIntegrity",
        "build.maxIntegrity"
      ],
      "fmt": "pct",
      "expr": "build_chassisIntegrity / max(build_maxIntegrity, 1) * 100",
      "description": "Chassis health bar: current ÷ max × 100.",
      "feeds": []
    },
    {
      "id": "spark.lastQuestion",
      "group": "spark",
      "label": "Last Question",
      "emoji": "💬",
      "kind": "value",
      "fmt": "str",
      "description": "The last thing the kid asked Spark.",
      "feeds": []
    },
    {
      "id": "spark.cacheHit",
      "group": "spark",
      "label": "Cache Hit",
      "emoji": "🗃",
      "kind": "value",
      "fmt": "bool",
      "live": true,
      "description": "TRUE when Spark answered from the pincher cache instead of a fresh model call.",
      "feeds": []
    },
    {
      "id": "spark.lastTookMs",
      "group": "spark",
      "label": "Took (ms)",
      "emoji": "⏳",
      "kind": "value",
      "fmt": "int",
      "description": "How long the last Spark answer took.",
      "feeds": []
    },
    {
      "id": "spark.hits",
      "group": "spark",
      "label": "Cache Hits",
      "emoji": "✅",
      "kind": "value",
      "fmt": "int",
      "description": "Pincher cache hits this session.",
      "feeds": [
        "spark.cacheHitRate"
      ]
    },
    {
      "id": "spark.misses",
      "group": "spark",
      "label": "Cache Misses",
      "emoji": "❌",
      "kind": "value",
      "fmt": "int",
      "description": "Fresh model calls this session.",
      "feeds": [
        "spark.cacheHitRate"
      ]
    },
    {
      "id": "spark.cacheHitRate",
      "group": "spark",
      "label": "Hit Rate %",
      "emoji": "📈",
      "kind": "formula",
      "deps": [
        "spark.hits",
        "spark.misses"
      ],
      "fmt": "pct",
      "expr": "spark_hits / max(spark_hits + spark_misses, 1) * 100",
      "description": "Pincher doctrine made visible: model-call heavy at first, then more and more canned.",
      "feeds": []
    },
    {
      "id": "flash.hexHash",
      "group": "flash",
      "label": "Hex Hash",
      "emoji": "🔐",
      "kind": "value",
      "fmt": "str",
      "description": "SHA-256 prefix of the last .hex flashed to a real board.",
      "feeds": []
    },
    {
      "id": "flash.board",
      "group": "flash",
      "label": "Board",
      "emoji": "🧷",
      "kind": "value",
      "fmt": "str",
      "description": "Target board of the last flash: uno, nano, esp32…",
      "feeds": []
    },
    {
      "id": "flash.size",
      "group": "flash",
      "label": "Hex Size",
      "emoji": "📦",
      "kind": "value",
      "fmt": "int",
      "description": "Bytes of firmware in the last flash.",
      "feeds": []
    },
    {
      "id": "flash.at",
      "group": "flash",
      "label": "Flashed At",
      "emoji": "🕰",
      "kind": "value",
      "fmt": "str",
      "description": "ISO timestamp of the last real-board flash.",
      "feeds": []
    },
    {
      "id": "flash.count",
      "group": "flash",
      "label": "Flash Count",
      "emoji": "🔢",
      "kind": "value",
      "fmt": "int",
      "description": "Total flashes logged. Every board flash becomes part of the tapestry.",
      "feeds": []
    }
  ],
  "groups": {
    "player": {
      "label": "Player",
      "emoji": "👷",
      "color": "#f59e0b"
    },
    "robot": {
      "label": "Robot",
      "emoji": "🤖",
      "color": "#38bdf8"
    },
    "program": {
      "label": "Program",
      "emoji": "🧩",
      "color": "#a78bfa"
    },
    "race": {
      "label": "Race",
      "emoji": "🏁",
      "color": "#34d399"
    },
    "build": {
      "label": "Build",
      "emoji": "🔧",
      "color": "#fb923c"
    },
    "spark": {
      "label": "Spark",
      "emoji": "✨",
      "color": "#fbbf24"
    },
    "flash": {
      "label": "Flash Log",
      "emoji": "⚡",
      "color": "#f87171"
    }
  },
  "edges": [
    [
      "robot.sensor.ultrasonic",
      "robot.think"
    ],
    [
      "robot.sensor.ir",
      "robot.think"
    ],
    [
      "robot.batteryV",
      "robot.motorL.volts"
    ],
    [
      "robot.dutyL",
      "robot.motorL.volts"
    ],
    [
      "robot.batteryV",
      "robot.motorR.volts"
    ],
    [
      "robot.dutyR",
      "robot.motorR.volts"
    ],
    [
      "robot.dutyL",
      "robot.power.draw"
    ],
    [
      "robot.dutyR",
      "robot.power.draw"
    ],
    [
      "robot.batteryV",
      "robot.battery.pct"
    ],
    [
      "robot.drivePower",
      "robot.speed"
    ],
    [
      "robot.turnPower",
      "robot.turnRate"
    ],
    [
      "robot.sensor.encoder",
      "robot.distTraveled"
    ],
    [
      "robot.sensor.ultrasonic",
      "robot.sensor.sonarNorm"
    ],
    [
      "program.ip",
      "program.progressPct"
    ],
    [
      "program.length",
      "program.progressPct"
    ],
    [
      "robot.x",
      "race.lapFrac"
    ],
    [
      "robot.z",
      "race.lapFrac"
    ],
    [
      "race.lapFrac",
      "race.onLine"
    ],
    [
      "robot.speed",
      "race.onLine"
    ],
    [
      "build.chassisIntegrity",
      "build.integrityPct"
    ],
    [
      "build.maxIntegrity",
      "build.integrityPct"
    ],
    [
      "spark.hits",
      "spark.cacheHitRate"
    ],
    [
      "spark.misses",
      "spark.cacheHitRate"
    ]
  ],
  "chat_model": "@cf/deepseek-ai/deepseek-v4-flash-0731",
  "endpoints": {
    "POST /tick": "game → sheet: {cells:{id:value}} (≤2/sec) → cascade + WS broadcast + tape",
    "GET /state": "full live snapshot",
    "GET /ws": "WebSocket: snapshot on join, then tick deltas",
    "GET /history?cells=&limit=": "aligned tape (DAW + sparklines)",
    "GET /dashboard?cells=&limit=": "sparkline series",
    "POST /predict": "{ticks:5..60} → ghost states (real-kinematics forward sim)",
    "POST /chat": "{question} → Spark, pincher-cached (cached flag)",
    "POST /ask": "same as /chat with the quilt-ai router lane visible",
    "GET /ask/routes": "the two model lanes (cheap/strong)",
    "POST /reflex": "{sensorSnapshot} → {reflex, action, ms} — vector-matched reflex cells (quilt-pincher)",
    "GET /reflexes": "reflex DB + .nail ESP32 bundle (quilt-esp32 flash path)",
    "POST /lore": "{question} → grounded answer with [file:lines] citations (quilt-rag)",
    "GET /lore/index": "corpus stats",
    "POST /ghost/evolve": "{generations:N} → evolution report; winners replace the ghost pool (quilt-evolve)",
    "GET /ghost/pool": "the evolved ghost pool",
    "GET /chat/stats": "pincher ledger",
    "POST /flash-log": "{hexHash,board,size,source} — real-board flash becomes quilt cells",
    "GET /flash-log": "the tapestry record — last 50 flashes"
  }
}