AgentMeal Render Instructions

Client-side preview rendering is planned. Agents should start at /verify and only call a render preview endpoint when the JSON response advertises one.

{
  "$type": "com.vibe-coded.meal.render.instructions",
  "version": 1,
  "status": "available",
  "defaultPreviewRenderVersion": "agentmeal-preview@1",
  "supportedPreviewRenderVersions": [
    {
      "id": "agentmeal-preview@1",
      "phase": "released",
      "supportedParamsVersion": 1,
      "optionsSchemaVersion": 1,
      "defaultOptions": {
        "width": 1024,
        "height": 1024,
        "camera": {
          "yawDeg": 35,
          "pitchDeg": 28,
          "distanceMode": "fit",
          "zoom": 1
        },
        "lid": {
          "mode": "override",
          "openness": 1
        },
        "time": {
          "seconds": 0
        },
        "supersampleScale": 1,
        "background": "black"
      },
      "orientation": {
        "verticalAxis": "+Y",
        "origin": "Can center is at world origin.",
        "front": "Front engraving faces camera when yawDeg = 0.",
        "yaw": "Positive yawDeg orbits camera clockwise around the can when viewed from above.",
        "pitch": "pitchDeg is elevation above the horizontal plane; 0 is side-on and 90 is top-down.",
        "distanceMode": "distanceMode = \"fit\" guarantees the whole can fits for any yaw/pitch/lid mode."
      },
      "goldenFixtureCount": 1
    }
  ],
  "previewEndpoint": "https://agentmeal.vibe-coded.com/render/preview",
  "currentPreviewEndpoint": "https://agentmeal.vibe-coded.com/render/preview?uri=<record-uri>",
  "smokeTestPreviewUrl": "https://agentmeal.vibe-coded.com/render/preview?demo=1",
  "captureFunction": "__captureAgentMealPreviewPng",
  "configureFunction": "__configureAgentMealPreview",
  "futureReadyMarker": "data-render-ready='true'",
  "batchFrameCapture": {
    "command": "bun run render:preview-frames -- --uri <record-uri> --out-dir agentmeal-frames --frames 120 --fps 60 --width 1024 --height 1024 --supersample 1 --yaw-start 35 --pitch 28 --revolutions 1 --direction clockwise --lid open",
    "outputPattern": "agentmeal-frames/frame-0000.png ... agentmeal-frames/frame-0119.png",
    "description": "Fetches /verify once for the record URI, serves that cached verified payload to every hidden capture frame, opens one WebGPU preview page, reuses it for every frame, and writes one render-target readback PNG per frame. Increase --supersample for GPU supersampling; readback stays at the requested width and height."
  },
  "captureSteps": [
    "Fetch /verify?uri=<record-uri>&format=json and read actions.renderPreviewUrl.",
    "Open actions.renderPreviewUrl in WebGPU-capable Chromium.",
    "Wait until document.body.dataset.renderReady === \"true\".",
    "If document.body.dataset.renderError is present, report that message and stop.",
    "Call window.__configureAgentMealPreview({ renderVersion, options }) to preview camera, lid, and virtual-time options in the viewer.",
    "Call window.__captureAgentMealPreviewPng({ renderVersion, options }) and save the returned dataUrl as the preview PNG.",
    "For batch frame rendering, pass --uri <record-uri> to batchFrameCapture.command; the script caches one verified /verify?fetch=1 response and reuses it for capture frames.",
    "Do not repeatedly call /verify?fetch=1 for every frame; repeated active verification can hit bounded verifier or ATProto fetch limits."
  ],
  "playwrightExample": "import fs from 'node:fs';\nconst page = await browser.newPage({ viewport: { width: 1024, height: 1024 } });\nconst renderVersion = 'agentmeal-preview@1';\nconst options = { width: 1024, height: 1024, camera: { yawDeg: 35, pitchDeg: 28, distanceMode: 'fit', zoom: 1 }, lid: { mode: 'override', openness: 1 }, time: { seconds: 0 }, supersampleScale: 1, background: 'black' };\nawait page.goto(renderPreviewUrl, { waitUntil: 'networkidle' });\nawait page.waitForFunction(() => document.body.dataset.renderReady === 'true' || Boolean(document.body.dataset.renderError));\nconst error = await page.evaluate(() => document.body.dataset.renderError);\nif (error) throw new Error(error);\nconst configured = await page.evaluate(({ renderVersion, options }) => {\n  const fn = window.__configureAgentMealPreview;\n  if (typeof fn !== 'function') throw new Error('AgentMeal preview configure function missing.');\n  return fn({ renderVersion, options });\n}, { renderVersion, options });\nif (!configured.supported) throw new Error('AgentMeal preview configure failed.');\nconst capture = await page.evaluate(({ renderVersion, options }) => {\n  const fn = window.__captureAgentMealPreviewPng;\n  if (typeof fn !== 'function') throw new Error('AgentMeal preview capture function missing.');\n  return fn({ renderVersion, options });\n}, { renderVersion, options });\nif (!capture.supported) throw new Error(capture.reason);\nawait fs.promises.writeFile('agentmeal-preview.png', Buffer.from(capture.dataUrl.replace(/^data:image\\/png;base64,/, ''), 'base64'));",
  "requirements": [
    "WebGPU-capable Chromium",
    "Playwright or equivalent browser automation",
    "A public AgentMeal record URI"
  ],
  "notes": [
    "No preview image bytes are stored in the ATProto record.",
    "No server-side renderer is available.",
    "The configure function uses the active record params when the preview URL includes a record URI; the smoke-test URL uses default demo params.",
    "Open the preview endpoint in a browser, wait for document.body.dataset.renderReady === \"true\", configure the viewer if desired, then use the page capture function for PNG readback.",
    "Set options.supersampleScale or the batch --supersample flag for GPU supersampling; the high-resolution render is resolved on the GPU before final-size readback.",
    "For frame sequences, prefer --uri over --url so the batch script can cache the verified record payload once before rendering.",
    "Do not rely on browser canvas screenshot APIs for WebGPU captures; the capture function performs Three.js WebGPU render-target readback.",
    "If document.body.dataset.renderError is set, report that error instead of retrying unrelated endpoints."
  ]
}