# Cloud Run image for the LIVE BROADCAST-FEED PRODUCER (Phase 1 of
# docs/plans/live-broadcast-feed-in-gcloud.md).
#
# Runs `serve_v3_live --live` co-located with the prod DB in europe-north1: it serves the 3D scene
# (core/web) + the live SSE feed on $PORT, reading positions/static from the DB IN-REGION (sub-ms).
# The on-site render PC's OBS Browser source points at this service's `/core/web/index.html?feed=live`
# — so the PC holds no DB connection, proxy, or ADC. Producer next to the data, pixels on the PC.
#
# Build context = REPO ROOT:
#     docker build -f v3/broadcast/deploy/producer.Dockerfile -t trap-v3-producer .
FROM python:3.13-slim
WORKDIR /app

# Layer-cached deps. The producer rides on the ingest requirements (psycopg + stdlib); the broadcast
# scorer/feed code carries no extra pip deps. AIS (`--ais`) needs `websockets`, which is NOT installed
# here — leave AIS off in this service.
COPY v3/ingest/requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r /app/requirements.txt

# serve_v3_live spans: v3/ingest (store/reads/broadcast_feed/postgres_store), v3/broadcast/live
# (live_feed_server + v3_adapter + streaming_score glue), v3/broadcast/core (the web scene it serves)
# and v3/scoring (the shared scoring + commentary engine, a PEER of the broadcast — it used to sit at
# v3/broadcast/core/scoring, so this COPY is load-bearing: without it the image boots and dies on
# `import score_race`). v3/registration is imported by v3/ingest/slots.py (the QR codec).
COPY v3/ingest         /app/v3/ingest
COPY v3/registration   /app/v3/registration
COPY v3/scoring        /app/v3/scoring
# v3/rie: `streaming_score.StreamingScorer` imports `v3.rie.enrich_geometry_guard` (the tracking
# quarantine that keeps geometry claims off unreliable tracks), and importing THAT runs the package
# __init__, which pulls the whole engine — all stdlib, no pip deps. Without this COPY the container
# BOOTS FINE and dies with `ModuleNotFoundError: v3.rie` at the exact moment a race goes on air —
# the one moment nobody is watching a boot log. Measured 2026-08-10: the deployed producer crash-
# looped on every hand-over attempt while every probe of the idle service looked healthy.
COPY v3/rie            /app/v3/rie
COPY v3/broadcast/live /app/v3/broadcast/live
COPY v3/broadcast/core /app/v3/broadcast/core

ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1 PORT=8080
EXPOSE 8080

# VR_INSTANCE / VR_COURSE select the broadcast; VR_DATABASE_URL (Cloud SQL socket) is the in-region DB;
# VR_EXTRA_ARGS is optional (e.g. "--wind --simple"). NOTE for deploy: Cloud Run must run with CPU
# always allocated (`--no-cpu-throttling`) so the background broadcast_live producer loop keeps ticking
# between SSE requests, and `--timeout=3600` for the long-lived SSE stream. See PRODUCER_CLOUDRUN.md.
# `VR_PRODUCER_READ_BASE` selects the bounded REST read plane for current positions. The producer
# token is deliberately NOT placed on the command line: `RestBackend` reads
# `VR_PRODUCER_READ_TOKEN` from the environment and adds it as a request header. Leaving the base
# empty retains the direct-database mode as an explicit rollback path.
#
# Two live modes, both first-class:
#   PINNED   VR_INSTANCE=<id>            — broadcast that one race.
#   FOLLOW   VR_INSTANCE= (empty) plus   — broadcast the operator's races on VR_COURSE in sequence:
#            VR_EXTRA_ARGS="--follow"      stay with the race on air until it has a winner, then move
#                                          to the next. One process for a whole regatta day; the
#                                          renderer swaps the race in place and never reloads.
# `--instance` is omitted entirely when VR_INSTANCE is empty — passing `--instance ""` would leave an
# empty id travelling through the producer instead of selecting the follow path cleanly. `--course`
# gets the SAME guard for the same reason: the documented race-day deploy sets `VR_COURSE=` (empty),
# and passing `--course ""` used to reach `StartedRaces` as a course FILTER that no race's real
# course id could ever match — the producer then sat on "Waiting for a race" for ever while armed
# races sailed past it. An empty VR_COURSE now means "no course scope", exactly like an empty
# VR_INSTANCE means "no pinned race".
CMD ["sh", "-c", "python -u /app/v3/broadcast/live/serve_v3_live.py --live \
  ${VR_INSTANCE:+--instance \"$VR_INSTANCE\"} ${VR_COURSE:+--course \"$VR_COURSE\"} \
  ${VR_PRODUCER_READ_BASE:+--read-base \"$VR_PRODUCER_READ_BASE\"} \
  --port \"$PORT\" $VR_EXTRA_ARGS"]
