{
  "_": [
    "The 3x3 VIDEO WALL: nine 1920x1080 screens driven as one 5760x3240 output.",
    "",
    "Cells are named row-then-column, as the operator names them: 11 12 13 across the top, 21 22 23,",
    "31 32 33 along the bottom. Each is 1920x1080, so cell RC sits at x=(C-1)*1920, y=(R-1)*1080.",
    "",
    "  +----------------+----------------+----------------+",
    "  |  11            |  12            |  13 Boat cam A |",
    "  +   Race view, 2x2 projection     +----------------+",
    "  |  21            |  22            |  23 Boat cam B |",
    "  +----------------+----------------+----------------+",
    "  | 31 results|rost|  32 Boat cam D |  33 Boat cam C |",
    "  +----------------+----------------+----------------+",
    "",
    "THIS FILE OWNS BOTH LEVERS, because they are different things and each one alone is a trap.",
    "The CANVAS belongs to an OBS PROFILE, so the wall names its own (`Video wall`, 5760x3240) and",
    "leaves the 1080p profile's stream key and output settings untouched. The SCENES belong to a",
    "COLLECTION, and collections are shared across profiles — so a 1080p scene laid out at 1920x1080",
    "still exists, with those same absolute transforms, when the operator switches to this profile,",
    "where it fills the top-left NINTH and black fills the rest. Naming a collection too is what stops",
    "the wall's scenes and the streaming scenes from ever being shown against each other's canvas.",
    "",
    "The wall owns its four cameras (`wall:cam:A..D`) rather than borrowing the streaming collection's,",
    "because each sits in its own CELL rather than in a grid. Each asks for a SLOT, not a hull, so they",
    "follow the racing fleet with nothing running — see `vars._slots`.",
    "",
    "Cell 31 is split in two because Burgee serves recent results and the entry list as two pages;",
    "there is no combined one.",
    "",
    "  python -m v3.broadcast.live.obs_scenes apply v3/broadcast/live/obs/video_wall.json \\",
    "      --var producer=http://localhost:8766 --var burgee=http://127.0.0.1:8090",
    "  ...or the same file against gcloud by changing only --var producer=https://<cloud-run-url>"
  ],
  "profile": "Video wall",
  "canvas": [
    5760,
    3240
  ],
  "vars": {
    "producer": "http://localhost:8766",
    "burgee": "http://127.0.0.1:8090",
    "event": "trwc-2026",
    "_mediabase": "Clips are fetched from a SECOND ORIGIN, and that is load-bearing. Every page on this wall holds a permanent SSE connection to the feed; a browser allows about six connections per origin and OBS shares one network stack across all its browser sources, so a wall of cameras plus the programme page exhausts the origin and every clip fetch queues behind them for ever - readyState 0, no error, no fallback card: a black tile under a live overlay. 127.0.0.1 and localhost are DIFFERENT ORIGINS to Chromium, so this is the very same server with its own pool of connections.",
    "mediabase": "http://127.0.0.1:8766/demo/data/media/",
    "readbase": "http://127.0.0.1:8766",
    "_slots": "Each camera cell asks for a POSITION — `boatcam.html?slot=A..D` — and the race plane answers with `meta.cameraSlots` in the feed header. The cells used to be PINNED to four hulls by `--var camA=…` at apply time, which is right for the race it was applied for and black for every race after it: the pinned hulls become spectators, no clips exist for them, and nothing says so. Keeping them honest then needed a second process rewriting four URLs on every heat. A slot URL is permanent, so a rotation re-points nothing and there is nothing left to run."
  },
  "program": "Video wall",
  "scenes": [
    {
      "name": "Video wall",
      "items": [
        {
          "source": "wall:race",
          "kind": "browser_source",
          "enabled": true,
          "_cell": "11 12 21 22 — the race, at native 3840x2160 rather than a 1080p page stretched 2x",
          "settings": {
            "url": "${producer}/core/web/index.html?feed=live&res=1&adaptive=guard&resmin=1&fpsmin=24&trustedAutoplayAudio=1&mediaBase=${mediabase}",
            "width": 3840,
            "height": 2160,
            "shutdown": false,
            "restart_when_active": false,
            "reroute_audio": true
          },
          "transform": {
            "positionX": 0,
            "positionY": 0,
            "alignment": 5,
            "boundsType": "OBS_BOUNDS_SCALE_INNER",
            "boundsAlignment": 5,
            "boundsWidth": 3840,
            "boundsHeight": 2160
          }
        },
        {
          "source": "wall:cam:A",
          "kind": "browser_source",
          "enabled": true,
          "_cell": "13 — Boat cam A",
          "settings": {
            "url": "${producer}/core/web/boatcam.html?slot=A&feed=live&hls=1&readBase=${readbase}",
            "width": 1920,
            "height": 1080,
            "shutdown": false,
            "restart_when_active": false,
            "reroute_audio": false
          },
          "transform": {
            "positionX": 3840,
            "positionY": 0,
            "alignment": 5,
            "boundsType": "OBS_BOUNDS_SCALE_INNER",
            "boundsAlignment": 5,
            "boundsWidth": 1920,
            "boundsHeight": 1080
          }
        },
        {
          "source": "wall:cam:B",
          "kind": "browser_source",
          "enabled": true,
          "_cell": "23 — Boat cam B",
          "settings": {
            "url": "${producer}/core/web/boatcam.html?slot=B&feed=live&hls=1&readBase=${readbase}",
            "width": 1920,
            "height": 1080,
            "shutdown": false,
            "restart_when_active": false,
            "reroute_audio": false
          },
          "transform": {
            "positionX": 3840,
            "positionY": 1080,
            "alignment": 5,
            "boundsType": "OBS_BOUNDS_SCALE_INNER",
            "boundsAlignment": 5,
            "boundsWidth": 1920,
            "boundsHeight": 1080
          }
        },
        {
          "source": "wall:cam:C",
          "kind": "browser_source",
          "enabled": true,
          "_cell": "33 — Boat cam C",
          "settings": {
            "url": "${producer}/core/web/boatcam.html?slot=C&feed=live&hls=1&readBase=${readbase}",
            "width": 1920,
            "height": 1080,
            "shutdown": false,
            "restart_when_active": false,
            "reroute_audio": false
          },
          "transform": {
            "positionX": 3840,
            "positionY": 2160,
            "alignment": 5,
            "boundsType": "OBS_BOUNDS_SCALE_INNER",
            "boundsAlignment": 5,
            "boundsWidth": 1920,
            "boundsHeight": 1080
          }
        },
        {
          "source": "wall:cam:D",
          "kind": "browser_source",
          "enabled": true,
          "_cell": "32 — Boat cam D",
          "settings": {
            "url": "${producer}/core/web/boatcam.html?slot=D&feed=live&hls=1&readBase=${readbase}",
            "width": 1920,
            "height": 1080,
            "shutdown": false,
            "restart_when_active": false,
            "reroute_audio": false
          },
          "transform": {
            "positionX": 1920,
            "positionY": 2160,
            "alignment": 5,
            "boundsType": "OBS_BOUNDS_SCALE_INNER",
            "boundsAlignment": 5,
            "boundsWidth": 1920,
            "boundsHeight": 1080
          }
        },
        {
          "source": "wall:results",
          "kind": "browser_source",
          "enabled": true,
          "_cell": "31 left half — recent results, beside the roster rather than above it: both are tall lists, so splitting the cell down the middle gives each its full height instead of halving it",
          "_legibility": "Rendered at HALF the half-cell and scaled 2x by the bounds below. These are ordinary desktop pages; at native width the type lands at desk-reading size, which is unreadable across a room from a video wall.",
          "settings": {
            "url": "${burgee}/e/${event}/results",
            "width": 480,
            "height": 540,
            "shutdown": false,
            "restart_when_active": false,
            "reroute_audio": false
          },
          "transform": {
            "positionX": 0,
            "positionY": 2160,
            "alignment": 5,
            "boundsType": "OBS_BOUNDS_SCALE_INNER",
            "boundsAlignment": 5,
            "boundsWidth": 960,
            "boundsHeight": 1080
          }
        },
        {
          "source": "wall:roster",
          "kind": "browser_source",
          "enabled": true,
          "_cell": "31 right half — the entry list",
          "_legibility": "Half-size render, scaled 2x — see the results cell beside it.",
          "settings": {
            "url": "${burgee}/e/${event}/entries",
            "width": 480,
            "height": 540,
            "shutdown": false,
            "restart_when_active": false,
            "reroute_audio": false
          },
          "transform": {
            "positionX": 960,
            "positionY": 2160,
            "alignment": 5,
            "boundsType": "OBS_BOUNDS_SCALE_INNER",
            "boundsAlignment": 5,
            "boundsWidth": 960,
            "boundsHeight": 1080
          }
        }
      ]
    }
  ],
  "collection": "Video wall"
}
