Ë
    Š‰ŒjØ­ ã                  óF  — d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
mZ ddlmZ  ee«      j                  «       j                   Zej$                  j'                  d ee«      «       ej$                  j'                  d eej                   j                   dz  «      «       ej$                  j'                  d eej                   j                   j                   «      «       ddlZddlmZmZ ddlmZ dd	lmZmZ dd
lmZm Z m!Z!m"Z"m#Z#m$Z$m%Z%m&Z& ddl'm(Z(m)Z) ddl*m+Z+ ddl,m-Z- dld„Z.dmd„Z/dnd„Z0ddddœ	 	 	 	 	 	 	 	 	 dod„Z1dpd„Z2dqd„Z3drdsd„Z4drdsd„Z5dtd„Z6 G d„ d«      Z7 G d„ d«      Z8 G d„ d«      Z9 G d„ d«      Z:dud „Z;dvd!„Z<dwd"„Z=dwd#„Z>dxd$„Z?dyd%„Z@dzd&„ZAd{d'„ZBddd(œ	 	 	 	 	 d|d)„ZCd}d*„ZDdd+œ	 	 	 	 	 d~d,„ZEdd-„ZF G d.„ d/«      ZGd€d0„ZH G d1„ d2«      ZIdd3œd�d4„ZJd‚d5„ZKdƒd6„ZLd„d7„ZMd8ZNd…d9„ZO G d:„ d;«      ZPd<ZQd=ZRd†d>„ZS G d?„ d@«      ZTdAZUdBZVdCdDdEœZWd‡dF„ZXdˆdG„ZY G dH„ dI«      ZZddddddJdej¶                  e\dKœ		 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d‰dL„Z]dŠdM„Z^d‹dN„Z_dŒdO„Z`dPddddddddddJddQddRdddddddddejÂ                  ej¶                  e\dSœ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d�dT„ZbdddddddddJddRddddej¶                  dUœ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dŽdV„ZcdddWddXddRddddddRdRde\dYœ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d�dZ„Zdd[dPddddd\œ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d�d]„ZedddXdJddRej¶                  dd^œ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d‘d_„ZfdddWdXdJdRdd`œ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d’da„Zgdb\  ZhZidcejÔ                  z  ddz  Zkde\  ZlZmZnd“df„Zodg„ Zpd”dh„Zqd”di„Zrd”dj„Zsetdkk(  r eq«        yy)•u  
Live **V3** broadcast driver â€” broadcast the live V3 `Store` to the 3D scene over SSE.

The production counterpart to `serve_live.py` (which replays a recorded feed file): this reads live
nav out of the V3 `Store` via `build_v3_live_adapter` (Â§6 seam #1 â€” `v3_adapter.py`), scores it
incrementally through `StreamingScorer`, and publishes `init`/`delta` over `LiveFeedServer` â€” the same
renderer, the same BroadcastFeed contract, unchanged. It closes the gap that `build_v3_live_adapter`
had no driver wiring it to the live feed.

Two modes:

  * ``--seed`` (default): an in-memory store seeded with a **moving two-boat race** around Stockholm,
    so the entire 3D pipeline (scene + standings + commentary, plus the optional AIS overlay) runs on a
    laptop with **no rig** â€” the 3D analogue of `v3/ingest/demo_serve.py` (which drives the 2D map).
  * production: pass a Postgres-backed store + the management-plane ``meta``/``course``/``team_config``
    (call ``run(...)`` directly), and live nav from the boats drives the broadcast.

Run:
    .venv-demo/Scripts/python v3/broadcast/live/serve_v3_live.py            # seeded offline demo
    # open http://localhost:8765/core/web/index.html?feed=live
    AISSTREAM_API_KEY=â€¦ .venv-demo/Scripts/python v3/broadcast/live/serve_v3_live.py --ais
é    )ÚannotationsN)ÚCallable)ÚPathÚingest)ÚREPLAY_WORTHYÚcue_kind)Úoverlay_event_config)ÚLiveFeedServerÚprogram_state)ÚPRESENTATION_LAG_SÚ
OnAirQueueÚStartedRacesÚcoverage_windowÚfinish_handover_secÚprogramme_metaÚprogramme_return_commentaryÚselect_start_camera_status)ÚCommentaryRotationLedgerÚStreamingScorer)ÚStartProgressTracker)Úbuild_v3_live_adapterc                óœ   — t         j                  j                  dt        t        j
                  d   dz  «      «       ddlm}  || ||«      S )uÆ   Build the streaming factual (team-racing-free) commentator (`--simple`) for non-team-race formats
    â€” e.g. the GSYS Aug-2 sprint. Lazily imported so the v3/scoring path is only needed when used.r   é   Úscoring)ÚSimpleCommentator)ÚsysÚpathÚinsertÚstrÚ_HEREÚparentsÚsimple_commentaryr   )ÚmetaÚteamsÚcourser   s       ú./srv/vr/app/v3/broadcast/live/serve_v3_live.pyÚ_simple_commentatorr'   ?   s;   € ô ‡H�H‡O�O�A”sœ5Ÿ=™=¨Ñ+¨iÑ7Ó8Ô9Ý3Ù˜T 5¨&Ó1Ð1ó    c                ó  — | j                  «       D �cg c].  }|j                  d«      �|j                  d«      �
|d   |d   f‘Œ0 }}|syt        d„ |D «       «      t        |«      z  t        d„ |D «       «      t        |«      z  fS c c}w )uW   Mean `(lat, lon)` of a tick's boat positions â€” the point the AIS overlay box follows.ÚlatNÚlonc              3  ó&   K  — | ]	  \  }}|–— Œ y ­w©N© )Ú.0ÚlaÚ_s      r&   ú	<genexpr>z_centroid.<locals>.<genexpr>M   s   è ø€ Ò$‘u�r˜1”Ñ$ùó   ‚c              3  ó&   K  — | ]	  \  }}|–— Œ y ­wr-   r.   )r/   r1   Úlos      r&   r2   z_centroid.<locals>.<genexpr>M   s   è ø€ Ò4I¹E¸A¸r´RÑ4Iùr3   )ÚvaluesÚgetÚsumÚlen)Ú	positionsÚpÚptss      r&   Ú	_centroidr=   G   s’   € à)2×)9Ñ)9Ó);ö E AØ�e‰e�E‹lÐ&¨1¯5©5°«<Ð+Cð ˆe‰H�a˜‘hÒð E€Cð EáØÜÑ$ Ô$Ó$¤s¨3£xÑ/´Ñ4IÀSÔ4IÓ1IÌCÐPSËHÑ1TÐUÐUùò	Es   “3Bc                ó>  — ddl m} ddlm}  || j	                  ||«      ||¬«      }t        |j                  |i «      j                  «       D ��ch c]  }|D ]  }|’Œ Œ c}}«      }	|	D �cg c]  }| j                  |«      x}
€Œ|
‘Œ }} |||«      S c c}}w c c}w )uÃ   MMSIs to hide as the tracked boats' own vessels â€” the instance's boats' `boat_meta` resolved
    against `candidates` (live AIS targets or replay observations) via `ais_feed.resolve_own_mmsis`.r   )Úresolve_own_mmsis©Úresolve_roster©Úinstance_id)	Úais_feedr?   ÚattributionrA   Úentries_covering_instanceÚsortedr7   r6   Úboat_meta_for)ÚstorerC   Út_msÚ
candidatesr?   rA   ÚrosterÚteamÚbÚboatsÚmÚmetass               r&   Ú
_own_mmsisrR   P   sŸ   € õ +Ý*ÙØ×'Ñ'¨°TÓ:Ø˜+ô'€Fô  &§*¡*¨[¸"Ó"=×"DÑ"DÓ"F×U˜$ÐPTÒUÈ1’AÐU�AÓUÓV€EØÖK�1 u×':Ñ':¸1Ó'=Ð"= !Ñ!JŠQÐK€EÐKÙ˜U JÓ/Ð/ùó VùÚKs   ÁB
Á,BÂB©Úsince_msÚfrom_msÚuntil_msc               ó   — ddl m } |D �ci c]  }|| j                  |«      “Œ }	} ||D ��
�ci c]C  }||	|   D �
�cg c]/  }
| j                  |
||¬«      D ]  }|j                   ||«      f‘Œ Œ1 c}}
“ŒE c}}
}|||¬«      }t	        | dd«      }|�€t        |«      �rddlm}m} |D �]  }g } ||	|   «      j                  «       D ]R  }	  ||j                  j                  dd	«      j                  d	d
«      d   «      \  }}|dk(  sŒB|j                  |«       ŒT t        |d„ d¬«      }|� ||«      nd}|sŒ’|j                  |g «      j                  d|dd|j                   xs |j                  t#        t        d|j                   xs |j                  |z
  dz  «      d«      dœ«       �Œ |S c c}w c c}}
w c c}}
}w # |$ r Y �Œw xY w)uð  The PRODUCTION live boat-cam media map â€” derived from the store's `video_segments` (Â§19.10),
    not from any driver-side bookkeeping. `clip_url(segment) -> str | None` maps a segment's
    `gcs_path` to a URL the renderer can fetch â€” a v4-signed GCS URL in production (`gcs_clip_url`),
    a static-root relative path in the local demo; `None` skips the clip. (Video only â€” stills live
    in the photos table and are not part of the live tile timeline.)

    The SHAPE is `media_map`'s, shared with the REST producer so the two cannot drift; this function
    is only the store's way of finding the clips.

    `since_ms` bounds the scan to THIS RACE's window, the same one the REST producer already asks
    `clips.json` for. Unbounded, every tick re-read every slot's entire history â€” and the tick runs
    once a second while the history grows all day, so the producer's own cadence decayed as the day
    went on. That cadence is the frontier the renderers present against, and a producer whose tick
    slips past a second is what a presentation clock has to absorb (`presentation_clock.js`). Fixing
    the renderer is the fix; this stops manufacturing the condition. Default `None` = the whole
    history, which is what replay and backfill want.r   )Ú	media_map)rT   rV   )rU   rV   Úlatest_video_for_manyN)Ú
MediaErrorÚparse_media_filenameú\ú/r   éÿÿÿÿÚfrontc                ó   — | j                   S r-   )Ústart_ms)Úsegments    r&   ú<lambda>z"media_from_store.<locals>.<lambda>Œ   s   € °G×4DÑ4D€ r(   )ÚkeyÚdefaultTç        éè  é   )ÚtSecÚfileÚvideoÚ	lastFrameÚcapturedAtMsÚsourceEndSec)rX   Úslots_of_boatÚvideo_segments_forra   ÚgetattrÚcallableÚmediarZ   r[   r6   Úgcs_pathÚreplaceÚrsplitÚappendÚmaxÚ
setdefaultÚend_msÚround)rI   rO   Útrack_start_msÚclip_urlrT   rU   rV   Ú_mapÚboatÚslotsÚslotÚsegÚmappedÚlatest_manyrZ   r[   r_   rb   Ú_tÚcameraÚlatestÚurls                         r&   Úmedia_from_storer‰   ]   s%  € õ& ,ð :?Ö?°ˆT�5×&Ñ& tÓ,Ñ,Ð?€EÐ?Ùð  %÷	&ð &ð ð	 Ø&+¨D¡k÷RØ"Ø%*×%=Ñ%=¸dÈXØGOð &>ó &QòRà!ð Ÿ<™<©°#«Ò7ð RÐ7ó Rñ Rô &ð (6¸wÐQYô	[€Fô ˜%Ð!8¸$Ó?€KØÑœH [Õ1ß:Øó 	ˆDØˆEÙ& u¨T¡{Ó3×:Ñ:Ó<ò *�ðÙ!5Ø×(Ñ(×0Ñ0°°sÓ;×BÑBÀ3ÈÓJÈ2ÑNó"P‘J�B˜ð ˜WÓ$Ø—L‘L Õ)ð*ô ˜Ñ$DÈdÔSˆFØ&,Ð&8‘(˜6Ô"¸dˆCÚØ×!Ñ! $¨Ó+×2Ñ2Ø s°TÈØ$*§M¡MÒ$D°V·_±_Ü$)¬#¨c°V·]±]Ò5UÀfÇoÁoØ7Eñ5FØIMñ4Nó +OØPQó%Sñ4ö ð	ð& €Mùò= @ùóRùô &øð  "ò Úðús-   ‹F2®F=¼4F7Á0F=Ã8GÆ7F=ÇGÇGc                ó`   — dj                  d„ t        | j                  «       «      D «       «      S )uÜ  A fingerprint of the media map, for deciding whether the feed needs republishing.

    This used to be a COUNT, compared with `>`, which quietly assumed a clip set only ever grows.
    It does not. The read plane answers `clips.json` over a WINDOW, and a local uploader sweeps clips
    past its retention â€” so an hour into a race the map is a rolling set whose size is flat while its
    contents move. Under `n > published`, the count reached its plateau, the comparison never fired
    again, and the renderer kept the last map it was handed while the presentation cursor walked off
    the end of it. The tiles played for a minute or two and then went dark for the rest of the race,
    which reads as the cameras stopping rather than as the producer never sending anything again.

    Identity, not size: the newest and oldest clip of each boat plus how many, which changes whenever
    the window slides, a clip lands, or an old one is swept â€” and is cheap on a per-second tick.ú|c              3  ó”   K  — | ]@  \  }}|r9|› d t        |«      › d |d   d   › d |d   d   › d t        d„ |D «       d¬«      › �	–— ŒB y­w)ú:r   ri   r^   c              3  ó|   K  — | ]4  }|j                  d «      sŒt        |j                  d«      xs d«      –— Œ6 y­w)rl   rm   r   N)r7   Úint©r/   Úrows     r&   r2   z'_media_key.<locals>.<genexpr>.<genexpr>§   s.   è ø€ ÒZ°cÀSÇWÁWÈ[ÕEY”�C—G‘G˜NÓ+Ò0¨q×1ÑZùs   ‚<™#<©re   N)r9   rx   )r/   r   Úrowss      r&   r2   z_media_key.<locals>.<genexpr>¥   sg   è ø€ ò 9ñ ˆD�$±4ð ˆ&�”#�d“)�˜A˜d 1™g f™oÐ.¨a°°R±¸Ñ0@Ð/AÀÜÑZ¸TÔZÐdeÔfÐ
gô	iñ9ùs   ‚AA)ÚjoinrG   Úitems©rs   s    r&   Ú
_media_keyr—   ˜   s/   € ð �8‰8ñ 9ô ! §¡£Ó/ô9ó 9ð 9r(   c                ót   — t        dt        |«      t        |«      z
  «      }t        dt        | «      |z
  «      S )a(  Rest only for the unused part of a polling interval.

    The cadence is measured between poll *starts*, not as a fixed delay after all work completes.
    Scoring a delayed multi-second phone batch can take most of a second. Sleeping another complete
    second after it creates the next delayed batch and a self-reinforcing stop/burst picture. A slow
    tick therefore rests for zero and catches up; an ordinary short tick retains the configured
    cadence. A backwards injected clock is treated as no elapsed work rather than a negative rest.
    rf   )rx   Úfloat)Ú
interval_sÚ	started_sÚ
finished_sÚelapseds       r&   Ú_poll_rest_srž   «   s6   € ô �#”u˜ZÓ(¬5°Ó+;Ñ;Ó<€GÜˆs”E˜*Ó%¨Ñ/Ó0Ð0r(   c                óœ   — | €t         j                  n| }t        |j                  dd«      «      j	                  «       j                  «       dv S )a  Whether the live picture must omit every camera clip and persistent last frame.

    This is an operational circuit breaker, not a retention setting: recordings remain in storage,
    while the producer publishes an empty media channel and performs no camera catalogue reads.
    ÚVR_DISABLE_CAMERA_MEDIAÚ >   Ú1ÚonÚyesÚtrue©ÚosÚenvironr   r7   ÚstripÚlower©ÚenvÚsources     r&   Ú_camera_media_disabledr®   ¸   sI   € ð ˜;ŒR�ZŠZ¨C€FÜˆv�z‰zÐ3°RÓ8Ó9×?Ñ?ÓA×GÑGÓIð Nð ð r(   c                óœ   — | €t         j                  n| }t        |j                  dd«      «      j	                  «       j                  «       dv S )aË  Whether live telemetry must use the current-position cache without rolling history.

    This is an availability circuit breaker for a degraded ``/tracks`` read plane. It leaves the
    fast fleet-wide ``/positions`` path enabled and prevents a successful-but-empty history response
    from becoming authoritative over fresh positions. The trade-off is losing intermediate fixes
    from delayed cellular batches until the history path is healthy again.
    ÚVR_DISABLE_TRACK_TAILr¡   >   r¢   r£   r¤   r¥   r¦   r«   s     r&   Ú_track_tail_disabledr±   Ä   sI   € ð ˜;ŒR�ZŠZ¨C€FÜˆv�z‰zÐ1°2Ó6Ó7×=Ñ=Ó?×EÑEÓGð Lð ð r(   c               ó&   — |rd„ S t        | d¬«      S )zQBuild the optional live camera channel without touching its loader when disabled.c                 ó   — i S r-   r.   )Ú_argsÚ_kwargss     r&   rc   z%_live_media_channel.<locals>.<lambda>Õ   s   € ¨€ r(   ç      $@©Ú	refresh_s)Ú_NonBlockingMedia)ÚloadÚdisableds     r&   Ú_live_media_channelr¼   Ò   s   € áÙ+Ð+Ü˜T¨TÔ2Ð2r(   c                  ór   — e Zd ZdZdej
                  edœ	 	 	 	 	 d
d„Zed„ «       Z	e
dd„«       Zdd„Zdd„Zy	)r¹   ao  Refresh optional camera media off the producer's single picture thread.

    A media endpoint is allowed to be slow or absent; boat positions are not. Calls return the last
    completed value immediately and start at most one daemon refresh for each race/fleet window.
    The next picture tick adopts a completed value and republishes the header if it changed.
    r¶   ©r¸   Ú	monotonicÚon_logc               óº   — || _         t        |«      | _        || _        || _        t        j                  «       | _        i | _        i | _	        t        «       | _        y r-   ©Ú_loadr™   Ú
_refresh_sÚ
_monotonicÚ_on_logÚ	threadingÚLockÚ_lockÚ_valuesÚ_refresh_afterÚsetÚ_running©Úselfrº   r¸   r¿   rÀ   s        r&   Ú__init__z_NonBlockingMedia.__init__á   sK   € ð ˆŒ
Ü 	Ó*ˆŒØ#ˆŒØˆŒÜ—^‘^Ó%ˆŒ
Ø*,ˆŒØ24ˆÔÜ$'£Eˆ�r(   c                ó  — t        | t        «      r)t        t        d„ | j	                  «       D «       «      «      S t        | t
        t        f«      rt        d„ | D «       «      S t        | t        «      rt        t        d„ | D «       «      «      S | S )Nc              3  óP   K  — | ]  \  }}|t         j                  |«      f–— Œ  y ­wr-   ©r¹   Ú_freeze)r/   ÚkÚvs      r&   r2   z,_NonBlockingMedia._freeze.<locals>.<genexpr>ð   s%   è ø€ Ò\ÁdÀaÈ Ô$5×$=Ñ$=¸aÓ$@Ô AÑ\ùs   ‚$&c              3  óF   K  — | ]  }t         j                  |«      –— Œ y ­wr-   rÓ   ©r/   rÖ   s     r&   r2   z,_NonBlockingMedia._freeze.<locals>.<genexpr>ò   s   è ø€ ÒE¸!Ô*×2Ñ2°1×5ÑEùó   ‚!c              3  óF   K  — | ]  }t         j                  |«      –— Œ y ­wr-   rÓ   rØ   s     r&   r2   z,_NonBlockingMedia._freeze.<locals>.<genexpr>ô   s   è ø€ ÒLÀÔ 1× 9Ñ 9¸!× <ÑLùrÙ   )Ú
isinstanceÚdictÚtuplerG   r•   ÚlistrÌ   )Úvalues    r&   rÔ   z_NonBlockingMedia._freezeí   sl   € ä�eœTÔ"ÜœÑ\ÈeÏkÉkËmÔ\Ó\Ó]Ð]Ü�eœd¤E˜]Ô+ÜÑE¸uÔEÓEÐEÜ�eœSÔ!ÜœÑLÀeÔLÓLÓMÐMØˆr(   c                óH   — | j                  |«      | j                  |«      z   S r-   )rÔ   )ÚclsÚargsÚkwargss      r&   Ú_keyz_NonBlockingMedia._key÷   s   € à�{‰{˜4Ó  3§;¡;¨vÓ#6Ñ6Ð6r(   c                óú  — | j                  ||«      }| j                  «       }| j                  5  | j                  j	                  |i «      }|| j
                  j	                  |d«      k\  }|ry|| j                  vrk| j                  j                  |«       || j                  z   | j
                  |<   t        j                  | j                  |||fdd¬«      j                  «        d d d «       |S # 1 sw Y   S xY w)Nrf   zlive-media-refreshT©Útargetrâ   ÚnameÚdaemon)rä   rÅ   rÉ   rÊ   r7   rË   rÍ   ÚaddrÄ   rÇ   ÚThreadÚ_refreshÚstart)rÏ   râ   rã   rd   Únowrß   Údues          r&   Ú__call__z_NonBlockingMedia.__call__û   sÜ   € Ø�i‰i˜˜fÓ%ˆØ�o‰oÓˆØ�Z‰Zñ 	QØ—L‘L×$Ñ$ S¨"Ó-ˆEØ˜×,Ñ,×0Ñ0°°cÓ:Ñ:ˆCÙ�s $§-¡-Ñ/Ø—‘×!Ñ! #Ô&Ø+.°·±Ñ+@�×#Ñ# CÑ(Ü× Ñ ¨¯©¸SÀ$ÈÐ<OØ&:À4ôIßINÉÌ÷	Qð ˆ÷	Qð ˆús   ¯B7C0Ã0C:c                ó¢  — d }	  | j                   |i |¤Žxs i }| j
                  5  |�|| j                  |<   | j                  «       | j                  z   | j                  |<   | j                  j                  |«       d d d «       y # t        $ r7}| j                  dt        |«      j                  › d|› d�«       d }Y d }~Œ§d }~ww xY w# 1 sw Y   y xY w# | j
                  5  |�|| j                  |<   | j                  «       | j                  z   | j                  |<   | j                  j                  |«       d d d «       w # 1 sw Y   w xY wxY w)Nz$[live] camera media refresh failed (ú: ú)©rÃ   Ú	ExceptionrÆ   ÚtypeÚ__name__rÉ   rÊ   rÅ   rÄ   rË   rÍ   Údiscard)rÏ   rd   râ   rã   rß   Úexcs         r&   rì   z_NonBlockingMedia._refresh  s2  € Øˆð
	+Ø�D—J‘J Ð/¨Ñ/Ò5°2ˆEð
 —‘ñ +ØÐ$Ø(-�D—L‘L Ñ%Ø+/¯?©?Ó+<¸t¿¹Ñ+N�×#Ñ# CÑ(Ø—‘×%Ñ% cÔ*÷	+ð +øô	 ò 	Ø�L‰LÐ?ÄÀSÃ	×@RÑ@RÐ?SÐSUÐVYÐUZÐZ[Ð\Ô]Ø�Eûð	ú÷+ð +û�—‘ñ +ØÐ$Ø(-�D—L‘L Ñ%Ø+/¯?©?Ó+<¸t¿¹Ñ+N�×#Ñ# CÑ(Ø—‘×%Ñ% cÔ*÷	+÷ +ñ +ýsN   „B ¦AC	Â	CÂ-CÂ<C ÃCÃC Ã	CÃEÃ"AEÄ9	EÅEÅEN©
rº   r   r¸   r™   r¿   úCallable[[], float]rÀ   úCallable[[str], None]ÚreturnÚNone)râ   rÝ   rã   rÜ   rý   rÝ   ©rý   rÜ   )rd   rÝ   râ   rÝ   rã   rÜ   rý   rþ   )r÷   Ú
__module__Ú__qualname__Ú__doc__Útimer¿   ÚprintrÐ   ÚstaticmethodrÔ   Úclassmethodrä   rð   rì   r.   r(   r&   r¹   r¹   Ù   sf   „ ñð >BØ26·.±.Ø16ñ
*Ø/ð
*à.ð
*à;?ó
*ð ñó ðð ò7ó ð7óô+r(   r¹   c                  óX   — e Zd ZdZdej
                  edœ	 	 	 	 	 dd„Z	 	 	 	 d	d„Zd
d„Z	y)Ú_NonBlockingFrameTailaJ  Recover cellular catch-up samples without putting a history read on the picture thread.

    ``/positions`` intentionally exposes only each boat's newest fix. A phone can upload several
    one-second fixes between producer polls, so that endpoint alone discards the intermediate motion
    anchors and the delayed picture holds, then jumps. A short rolling ``/tracks`` read recovers those
    anchors while the presentation delay can still absorb them. The completed tail is returned from
    memory; one bounded daemon read per race/fleet runs at a low cadence in the background.
    ç      @r¾   c               óº   — || _         t        |«      | _        || _        || _        t        j                  «       | _        i | _        i | _	        t        «       | _        y r-   rÂ   rÎ   s        r&   rÐ   z_NonBlockingFrameTail.__init__!  sO   € ð ˆŒ
Ü 	Ó*ˆŒØ#ˆŒØˆŒÜ—^‘^Ó%ˆŒ
ð FHˆŒØMOˆÔÜ?B»uˆ�r(   c                ó„  — t        |«      t        t        d„ |D «       «      «      t        |«      f}| j	                  «       }| j
                  5  | j                  j                  |«      }|| j                  j                  |d«      k\  r£|| j                  vr•| j                  j                  |«       || j                  z   | j                  |<   |t        |«      t        |«      t        |«      t        |«      f}	t        j                  | j                  ||	fdd¬«      j!                  «        d d d «       |S # 1 sw Y   S xY w)Nc              3  ó2   K  — | ]  }t        |«      –— Œ y ­wr-   )r   ©r/   r   s     r&   r2   z1_NonBlockingFrameTail.__call__.<locals>.<genexpr>4  s   è ø€ Ò-J¸D¬c°$¯iÑ-Jùó   ‚rf   zlive-track-tail-refreshTræ   )r   rÝ   rG   r�   rÅ   rÉ   rÊ   r7   rË   rÍ   rê   rÄ   rÞ   rÇ   rë   rì   rí   )
rÏ   rC   rO   Út_fromÚt_tor|   rd   rî   rß   râ   s
             r&   rð   z_NonBlockingFrameTail.__call__2  s
  € ä�;Ó¤¤vÑ-JÀEÔ-JÓ'JÓ!KÌSÐQ_ÓM`ÐaˆØ�o‰oÓˆØ�Z‰Zñ 	VØ—L‘L×$Ñ$ SÓ)ˆEØ�d×)Ñ)×-Ñ-¨c°3Ó7Ò7¸CÀtÇ}Á}Ñ<TØ—‘×!Ñ! #Ô&Ø+.°·±Ñ+@�×#Ñ# CÑ(Ø#¤T¨%£[´#°f³+¼sÀ4»yÌ#ÈnÓJ]Ð^�Ü× Ñ ¨¯©¸SÀ$¸KØ&?ÈôNßNSÉeÌg÷	Vð ˆ÷	Vð ˆús   ÁCD5Ä5D?c                ó  — d }	  | j                   |Ž xs i }| j
                  5  |�|| j                  |<   n| j                  j                  |d «       | j                  «       | j                  z   | j                  |<   | j                  j                  |«       d d d «       y # t        $ r5}| j                  dt        |«      j                  › d|› d�«       Y d }~ŒÂd }~ww xY w# 1 sw Y   y xY w# | j
                  5  |�|| j                  |<   n| j                  j                  |d «       | j                  «       | j                  z   | j                  |<   | j                  j                  |«       d d d «       w # 1 sw Y   w xY wxY w)Nz"[live] track-tail refresh failed (rò   ró   )rÃ   rõ   rÆ   rö   r÷   rÉ   rÊ   ÚpoprÅ   rÄ   rË   rÍ   rø   )rÏ   rd   râ   rß   rù   s        r&   rì   z_NonBlockingFrameTail._refresh@  sY  € Øˆð	+Ø�D—J‘J Ð%Ò+¨ˆEð —‘ñ +ØÐ$Ø(-�D—L‘L Ò%ð —L‘L×$Ñ$ S¨$Ô/Ø+/¯?©?Ó+<¸t¿¹Ñ+N�×#Ñ# CÑ(Ø—‘×%Ñ% cÔ*÷+ð +øô ò 	\Ø�L‰LÐ=¼dÀ3»i×>PÑ>PÐ=QÐQSÐTWÐSXÐXYÐZ×[Ñ[ûð	\ú÷+ð +û�—‘ñ +ØÐ$Ø(-�D—L‘L Ò%ð —L‘L×$Ñ$ S¨$Ô/Ø+/¯?©?Ó+<¸t¿¹Ñ+N�×#Ñ# CÑ(Ø—‘×%Ñ% cÔ*÷+÷ +ñ +ýsN   „B  £A4C!Â 	CÂ)+CÃC- ÃCÃC- Ã!C*Ã-FÃ:A4E7Å.	FÅ7F Å<FNrú   )rC   r   rO   ú	list[str]r  r�   r  r�   r|   r�   rý   údict | None)rd   z tuple[str, tuple[str, ...], int]râ   rÝ   rý   rþ   ©
r÷   r   r  r  r  r¿   r  rÐ   rð   rì   r.   r(   r&   r  r    sR   „ ñð >AØ26·.±.Ø16ñEØ/ðEà.ðEà;?óEð"Ø!$ðØ)4óô+r(   r  c                  óP   — e Zd ZdZdej
                  edœ	 	 	 	 	 dd„Zd	d„Zd
d„Z	y)Ú	_LiveWinda  Prime once, then refresh wind away from the producer's single picture thread.

    The first read is bounded and synchronous so an on-air ``init`` contains real conditions instead
    of making the renderer fall back to a northerly. Later reads hold the last completed value and
    refresh on a daemon thread: a slow weather/read endpoint can cost an update, never boat motion.
    g      @r¾   c               óº   — || _         t        |«      | _        || _        || _        t        j                  «       | _        i | _        i | _	        t        «       | _        y r-   rÂ   rÎ   s        r&   rÐ   z_LiveWind.__init__Z  óK   € ð ˆŒ
Ü 	Ó*ˆŒØ#ˆŒØˆŒÜ—^‘^Ó%ˆŒ
Ø46ˆŒØ<>ˆÔÜ.1«eˆ�r(   c                óú  — |si S ||f}| j                  «       }d}| j                  5  || j                  vr,|| j                  vr| j                  j	                  |«       d}n–|| j
                  j                  |d«      k\  rw|| j                  vri| j                  j	                  |«       || j                  z   | j
                  |<   t        j                  | j                  |fdd¬«      j                  «        | j                  j                  |i «      }d d d «       |rD| j                  |«       | j                  5  | j                  j                  |i «      }d d d «       |S S # 1 sw Y   ŒQxY w# 1 sw Y   S xY w)NFTrf   zlive-wind-refreshræ   )rÅ   rÉ   rÊ   rÍ   rê   rË   r7   rÄ   rÇ   rë   rì   rí   )rÏ   rC   Ú	course_idrd   rî   Úprimerß   s          r&   rð   z_LiveWind.__call__f  sG  € ÙØˆIØ˜IÐ&ˆØ�o‰oÓˆØˆØ�Z‰Zñ 		.Ø˜$Ÿ,™,Ñ&¨3°d·m±mÑ+CØ—‘×!Ñ! #Ô&Ø‘Ø˜×+Ñ+×/Ñ/°°SÓ9Ò9¸cÈÏÉÑ>VØ—‘×!Ñ! #Ô&Ø+.°·±Ñ+@�×#Ñ# CÑ(Ü× Ñ ¨¯©¸S¸FØ&9À$ôHßHMÉÌØ—L‘L×$Ñ$ S¨"Ó-ˆE÷		.ñ à�M‰M˜#ÔØ—‘ñ 2ØŸ™×(Ñ(¨¨bÓ1�÷2àˆˆuˆ÷		.ð 		.ú÷2àˆús   §C-E$Ä;E0Å$E-Å0E:c                ó˜  — d }	  | j                   |Ž xs i }| j
                  5  |�|| j                  |<   | j                  «       | j                  z   | j                  |<   | j                  j                  |«       d d d «       y # t        $ r5}| j                  dt        |«      j                  › d|› d�«       Y d }~Œ¥d }~ww xY w# 1 sw Y   y xY w# | j
                  5  |�|| j                  |<   | j                  «       | j                  z   | j                  |<   | j                  j                  |«       d d d «       w # 1 sw Y   w xY wxY w)Nz[live] wind refresh failed (rò   ró   rô   ©rÏ   rd   rß   rù   s       r&   rì   z_LiveWind._refresh}  s*  € Øˆð		+Ø�D—J‘J Ð$Ò*¨ˆEð —‘ñ +ØÐ$Ø(-�D—L‘L Ñ%Ø+/¯?©?Ó+<¸t¿¹Ñ+N�×#Ñ# CÑ(Ø—‘×%Ñ% cÔ*÷	+ð +øô ò 	VØ�L‰LÐ7¼¸S»	×8JÑ8JÐ7KÈ2ÈcÈUÐRSÐT×UÑUûð	Vú÷+ð +û�—‘ñ +ØÐ$Ø(-�D—L‘L Ñ%Ø+/¯?©?Ó+<¸t¿¹Ñ+N�×#Ñ# CÑ(Ø—‘×%Ñ% cÔ*÷	+÷ +ñ +ýóN   „B £ACÂ	CÂ+B<Â7C Â<CÃC ÃCÃE	ÃAD=Ä4	E	Ä=EÅE	N©
rº   zCallable[[str, str], dict]r¸   r™   r¿   rû   rÀ   rü   rý   rþ   ©rC   r   r  ú
str | Nonerý   rÜ   ©rd   ztuple[str, str]rý   rþ   r  r.   r(   r&   r  r  R  s?   „ ñð PSØ26·.±.Ø16ñ
4Ø/ð
4à.ð
4à;?ó
4óô.+r(   r  c                  óP   — e Zd ZdZdej
                  edœ	 	 	 	 	 dd„Zd	d„Zd
d„Z	y)Ú_LiveCoursea¯  Refresh course geometry away from the producer's single picture thread.

    The course resolved at the gun is a sound initial frame, but it is not a lifetime position for
    a drifting mark or a committee vessel. Calls return the last completed geometry immediately and
    start at most one refresh per race/course. A slow or failed course read therefore costs a mark
    update, never the boats, clock, standings or voice.
    g       @r¾   c               óº   — || _         t        |«      | _        || _        || _        t        j                  «       | _        i | _        i | _	        t        «       | _        y r-   rÂ   rÎ   s        r&   rÐ   z_LiveCourse.__init__”  r  r(   c                óÞ  — |si S ||f}| j                  «       }| j                  5  | j                  j                  |i «      }|| j                  j                  |d«      k\  rw|| j
                  vri| j
                  j                  |«       || j                  z   | j                  |<   t        j                  | j                  |fdd¬«      j                  «        d d d «       |S # 1 sw Y   S xY w)Nrf   zlive-course-refreshTræ   )rÅ   rÉ   rÊ   r7   rË   rÍ   rê   rÄ   rÇ   rë   rì   rí   )rÏ   rC   r  rd   rî   rß   s         r&   rð   z_LiveCourse.__call__   sÔ   € ÙØˆIØ˜IÐ&ˆØ�o‰oÓˆØ�Z‰Zñ 	RØ—L‘L×$Ñ$ S¨"Ó-ˆEØ�d×)Ñ)×-Ñ-¨c°3Ó7Ò7¸CÀtÇ}Á}Ñ<TØ—‘×!Ñ! #Ô&Ø+.°·±Ñ+@�×#Ñ# CÑ(Ü× Ñ ¨¯©¸S¸FØ&;ÀDôJßJOÉ%Ì'÷	Rð ˆ÷	Rð ˆús   ¥B3C"Ã"C,c                ó˜  — d }	  | j                   |Ž xs d }| j
                  5  |�|| j                  |<   | j                  «       | j                  z   | j                  |<   | j                  j                  |«       d d d «       y # t        $ r5}| j                  dt        |«      j                  › d|› d�«       Y d }~Œ¥d }~ww xY w# 1 sw Y   y xY w# | j
                  5  |�|| j                  |<   | j                  «       | j                  z   | j                  |<   | j                  j                  |«       d d d «       w # 1 sw Y   w xY wxY w)Nz[live] course refresh failed (rò   ró   rô   r  s       r&   rì   z_LiveCourse._refresh®  s/  € Øˆð	+Ø�D—J‘J Ð$Ò,¨ˆEð —‘ñ +ð Ð$Ø(-�D—L‘L Ñ%Ø+/¯?©?Ó+<¸t¿¹Ñ+N�×#Ñ# CÑ(Ø—‘×%Ñ% cÔ*÷+ð +øô ò 	XØ�L‰LÐ9¼$¸s»)×:LÑ:LÐ9MÈRÐPSÈuÐTUÐV×WÑWûð	Xú÷+ð +û�—‘ñ +ð Ð$Ø(-�D—L‘L Ñ%Ø+/¯?©?Ó+<¸t¿¹Ñ+N�×#Ñ# CÑ(Ø—‘×%Ñ% cÔ*÷+÷ +ñ +ýr  Nr   r!  r#  r  r.   r(   r&   r%  r%  ‹  s?   „ ñð PSØ26·.±.Ø16ñ
4Ø/ð
4à.ð
4à;?ó
4óô+r(   r%  c                ó>  ‡— i Šdˆfd„}| xs i }dD ]P  \  }}|j                  |«      xs i } ||› d�|j                  d«      «        ||› d�|j                  d«      «       ŒR t        |j                  d«      xs g d¬	«      D ]-  \  }} |t        |j                  d
«      xs d|› �«      |«       Œ/ t        |j                  d«      xs g d¬	«      D ]  \  }} |d|› �|«       Œ t        |j                  d«      xs g d¬	«      D ]  \  }} |d|› �|«       Œ ‰S )ax  Flatten resolved course geometry to the renderer's live ``marks`` channel.

    Role names are the stable identity shared by course resolution and ``scene.updateMarks``. This
    remains deliberately independent of device ids: the same mechanism follows a GNSS phone on a
    committee vessel, a MarkSetBot, a conventional buoy tracker, or a static fallback at any event.
    c                óª  •— t        |t        «      sy |j                  d«      |j                  d«      }}t        |t        t        z  «      rˆt        |t        t        z  «      rpt        j                  |«      rZt        j                  |«      rDd|cxk  rdk  r8n y d|cxk  rdk  r)n y |dk(  r|dk(  st	        |«      t	        |«      dœ‰| <   y y y y y y y y )	Nr*   r+   i¦ÿÿÿéZ   iLÿÿÿé´   r   ©r*   r+   )rÛ   rÜ   r7   r�   r™   ÚmathÚisfinite)ÚroleÚpointr*   r+   Úouts       €r&   rê   z#_course_role_positions.<locals>.addÇ  s·   ø€ Ü˜%¤Ô&ØØ—9‘9˜UÓ# U§Y¡Y¨uÓ%5ˆSˆÜ�sœC¤%™KÔ(¬Z¸¼SÄ5¹[Ô-IÜ—M‘M #Ô&¬4¯=©=¸Ô+=Ø˜3Ô$ "Õ$¨°Ô);¸Õ);Ø š c¨Q¢hÜ % c£
´5¸³:Ñ>ˆC�ŠIð '/ð *<Ð$ð ,>Ð&ð .JÐ(r(   ))rí   Ú	startLine)ÚfinishÚ
finishLineÚ_portÚportÚ_stbdÚ	starboardÚ	waypointsr   )rí   rè   ÚMÚcontestAreaÚcontestÚboundaryBuoysÚbuoy)r0  r   rý   rþ   )r7   Ú	enumerater   )	r%   rê   ÚgeoÚprefixrd   ÚlineÚindexr1  r2  s	           @r&   Ú_course_role_positionsrE  ¾  s6  ø€ ð (*€Cõ?ð Š,�B€CØIò 5‰ˆ�Ø�w‰w�s‹|Ò!˜rˆÙˆvˆh�eÐ˜dŸh™h vÓ.Ô/Ùˆvˆh�eÐ˜dŸh™h {Ó3Õ4ð5ô " #§'¡'¨+Ó"6Ò"<¸"ÀAÔFò :‰ˆˆuÙŒC�—	‘	˜&Ó!Ò0 q¨¨ [Ó1°5Õ9ð:ä! #§'¡'¨-Ó"8Ò">¸BÀaÔHò &‰ˆˆuÙˆg�e�WÐ˜uÕ%ð&ä! #§'¡'¨/Ó":Ò"@¸bÈÔJò #‰ˆˆuÙˆd�5�'ˆN˜EÕ"ð#à€Jr(   c                óR   — ddl m} 	  || j                  «      S # t        $ r Y yw xY w)z·Production `clip_url`: a short-lived signed HTTPS URL for a `gs://` clip (the renderer's plain
    <video> can't send auth headers). None for non-GCS paths (a LocalUploader dev path).r   )Ú_signed_clip_urlN)Úread_serverrG  rt   rõ   )rb   rG  s     r&   Úgcs_clip_urlrI  ß  s/   € õ -ðÙ × 0Ñ 0Ó1Ð1øÜò Ùðús   ˆ š	&¥&c                óN   — | j                  |«      }|rt        |dd«      xs dS dS )u  How this race is REFERRED TO â€” the name the race plane holds for it ("Race 1", an imported race
    number, whatever the schedule import wrote).

    Published so the picture can put the race in front of the teams. The renderer must not derive it:
    the only thing it has is the instance id, which is a PROVIDER's key (`ksail-15317`), and teaching
    the renderer to slice that apart would bake one integration's id format into the presentation
    layer. The race plane already knows what the race is called; it just never said so.rè   N©Úget_race_instancerq   ©rI   rC   Úinsts      r&   Úrace_name_forrO  é  s/   € ð ×"Ñ" ;Ó/€DÙ48ŒG�D˜& $Ó'Ò/¨4ÐB¸dÐBr(   c                óF   — | j                  |«      }|rt        |dd«      S dS )zYThe competition this instance belongs to (race_instances.event_id), or None (standalone).Úevent_idNrK  rM  s      r&   Úevent_id_for_instancerR  õ  s(   € à×"Ñ" ;Ó/€DÙ.2Œ7�4˜ TÓ*Ð<¸Ð<r(   c                ó:  — 	 t        | |«      }|r(| j                  |«      xs i j                  d«      xs i ni }|j                  d«      |j                  d«      }}t	        d„ ||fD «       «      }|r|s|rt        |«      t        |«      fS dS # t        $ r Y yw xY w)u»  Where the instance's EVENT says its venue is (`venue.centreLat/centreLon`), or None.

    Read here rather than left to the event overlay because the centre is needed while the COURSE is
    being resolved, and the overlay runs after â€” it only ever sees `meta`. Tolerates every absence
    (no event, no config, no venue, a malformed number) by answering None, so a standalone instance
    keeps the boat-position fallback it has always had.ÚvenueÚ	centreLatÚ	centreLonNc              3  óœ   K  — | ]D  }t        |t        t        f«      xr( t        |t        «       xr t	        j
                  |«      –— ŒF y ­wr-   ©rÛ   r�   r™   Úboolr.  r/  rØ   s     r&   r2   z)_declared_venue_centre.<locals>.<genexpr>  sD   è ø€ ò "Øô ˜œC¤˜<Ó(ÒY´¸A¼tÓ1DÐ-DÒYÌÏÉÐWXÓIYÓYñ "ùó   ‚A
A)rR  Úget_event_configr7   rõ   Úallr™   )rI   rC   rQ  rT  r*   r+   Úoks          r&   Ú_declared_venue_centrer^  û  s©   € ðÜ(¨°Ó<ˆÙQY�%×(Ñ(¨Ó2Ò8°b×=Ñ=¸gÓFÒLÉ"Ð_aˆØ—9‘9˜[Ó)¨5¯9©9°[Ó+AˆSˆô 
ñ "Ø˜C�jô"ó 
"€Bñ (*©s±cŒE�#‹Jœ˜c›
Ð#ÐDÀÐDøô ò Ùðús   ‚AB Â	BÂBc                ó   — 	 t        | |«      }|r(| j                  |«      xs i j                  d«      xs i ni }|j                  d«      }t	        |t
        «      rt        |«      dk  ryg }|D ]~  }t	        |t        «      s y|j                  d«      |j                  d«      }}t        d„ ||fD «       «      }	|	r
|dk(  r|dk(  r y|j                  t        |«      t        |«      d	œ«       Œ€ |S # t        $ r Y yw xY w)
aÄ  The event-declared competition polygon, validated for renderer use.

    `read_server` already supplies this fallback to REST clients. The store-backed producer and
    historical exporter must apply the same rule or a course created without persisted contest
    roles loses its perimeter rink despite carrying the event's partner-board configuration.
    Measured course roles still win; this is used only when `resolve_course` returned no area.
    rT  r<  Nrh   r*   r+   c              3  óœ   K  — | ]D  }t        |t        t        f«      xr( t        |t        «       xr t	        j
                  |«      –— ŒF y ­wr-   rX  )r/   rß   s     r&   r2   z)_declared_contest_area.<locals>.<genexpr>#  sL   è ø€ ò FØ16ô ˜u¤s¬E lÓ3ò -¼JÀuÌdÓ<SÐ8Sò -ÜŸ™ eÓ,ó-ñ FùrZ  r   r-  )rR  r[  r7   rõ   rÛ   rÞ   r9   rÜ   r\  rw   r™   )
rI   rC   rQ  rT  Úpointsr2  r1  r*   r+   Úvalids
             r&   Ú_declared_contest_arearc    s  € ðÜ(¨°Ó<ˆÙQY�%×(Ñ(¨Ó2Ò8°b×=Ñ=¸gÓFÒLÉ"Ð_aˆØ—‘˜=Ó)ˆô �fœdÔ#¤s¨6£{°Q¢ØØ
€CØò ;ˆÜ˜%¤Ô&ÙØ—9‘9˜UÓ# U§Y¡Y¨uÓ%5ˆSˆÜñ FØ;>À¸*ôFó Fˆá˜ š c¨Q¢hÙØ�
‰
œ5 ›:¬e°C«jÑ9Õ:ð;ð €Jøô ò Ùðús   ‚A	C1 Ã1	C=Ã<C=c                ó„   — 	 ddl m} t        | |«      }|r || j                  |«      xs i «      S dS # t        $ r Y yw xY w)zGNominal gate length from the event, for paired-end plausibility checks.r   )Úcourse_line_length_mN)Úevent_configre  rR  r[  rõ   )rI   rC   re  rQ  s       r&   Ú_declared_course_line_mrg  +  sL   € ðÝ5Ü(¨°Ó<ˆÙOWÑ# E×$:Ñ$:¸8Ó$DÒ$JÈÓKÐaÐ]aÐaøÜò Ùðús   ‚.3 ±3 ³	?¾?c                ój  — 	 ddl m} t        | |«      } || j                  |«      xs i d«      }t	        |t
        «      r|j                  d«      nd}t	        |t
        «      r|j                  d«      nd}t	        |t        «      r|D �cg c]  }t        |«      ‘Œ c}S dS c c}w # t        $ r Y yw xY w)z<Event-declared on-course wind instruments, kept server-side.r   )Úintegration_forÚ
marksetbotÚconfigNÚwindSerials)
rf  ri  rR  r[  rÛ   rÜ   r7   rÞ   r   rõ   )rI   rC   ri  rQ  Úintegrationrk  ÚserialsÚserials           r&   Ú_trusted_wind_serialsrp  5  sž   € ðÝ0Ü(¨°Ó<ˆÙ% e×&<Ñ&<¸XÓ&FÒ&LÈ"ÈlÓ[ˆÜ.8¸ÄdÔ.K�—‘ Ô*ÐQUˆÜ/9¸&Ä$Ô/G�&—*‘*˜]Ô+ÈTˆÜ6@ÀÌ$Ô6O¨'Ö2 ”�F•Ò2ÐYÐUYÐYùÒ2øÜò Ùðús*   ‚BB& Â
B!ÂB& ÂB& Â!B& Â&	B2Â1B2)rO   Úownerc               óf   — |€|S t        || j                  |«      xs i |xs t        |«      ¬«      S )u¨  Overlay the EVENT-level configuration (event_config.py) onto derived meta â€” venue, boat
    class, presentation flags â€” reading the config out of the STORE. `event_id` None or no config
    stored â†’ meta unchanged (full backwards compatibility).

    The overlay itself lives in `event_meta.overlay_event_config`, shared with the REST producer:
    the same race must not look like two different productions depending on whether the producer
    happens to have a database connection.

    `boats` is read only for its OWNER, which is what turns the event's declared vessel buoy ids into
    slot refs the picture can ask for clips by. Taken off a boat ref rather than an owner column
    because a slot prefix is the account NAME while every owner column is a `users.id`. During an
    unallocated waiting state there is no boat ref, so the caller may supply the canonical account
    name learned from the read plane as `owner`.©rq  )r	   r[  Ú	_owner_of)rI   rQ  r#   rO   rq  s        r&   Úapply_event_configru  B  s?   € ð ÐØˆÜ  e×&<Ñ&<¸XÓ&FÒ&LÈ"Ø&+Ò&?¬y¸Ó/?ôAð Ar(   c                ón   — | xs g D ],  }t        |«      j                  dd«      d   }|sŒ$||k7  sŒ*|c S  y)už   The account name a race's devices are registered under, read off a boat ref
    (`trwc2026:boat:7` â†’ `trwc2026`). Kept identical to `rest_source._owner_of`.r�   r   r   N)r   Úsplit)rO   rN   Úheads      r&   rt  rt  W  sC   € ð Š[�bò ˆÜ�1‹v�|‰|˜C Ó# AÑ&ˆÚ�D˜A“IØŠKðð r(   )Úteam_configc               ó¦  — t        | |«      }|ri |¥d|i¥}| j                  |«      xs i }|j                  d«      ri |¥dt        |d   «      i¥}|j                  d«      �i |¥dt	        |d   «      i¥}|j                  d«      �i |¥dt        |d   «      i¥}|j                  d«      �i |¥dt        |d   «      i¥}|j                  d«      ri |¥dt        |d   «      i¥}|j                  d«      �i |¥dt        |d   «      i¥}|j                  d«      �i |¥dt        |d   «      i¥}|j                  d	«      d
v ri |¥d	|d	   i¥}|j                  d«      �i |¥dt        |d   «      i¥}|j                  d«      �i |¥dt        |d   «      i¥}|j                  d«      �i |¥dt        |d   «      i¥}||j                  d«      xs |fS )uS  Overlay the management-plane BROADCAST CONFIG (production instance-meta model) onto the driver
    inputs: the operator sets `{title?, gunUtcMs?, teamConfig?}` via
    `PUT /v3/manage/instances/{id}/broadcast`, and the live driver reads it here â€” no more hand-passed
    launch parameters. Absent keys leave the derived values untouched.ÚraceNameÚtitleÚgunUtcMsÚnoDownwindSailsÚ	rcVesselsÚ
hullLiveryÚonboardTilesÚcockpitCrewÚobservedGeometryGuard)ÚrieÚoffÚ	simulatedÚneuralTtsOnlyÚneuralTtsFallbackÚ
teamConfig)rO  Úget_broadcast_configr7   r   r�   rY  )rI   rC   r#   ry  rè   Úcfgs         r&   Úapply_broadcast_configrŒ  a  s)  € ô ˜ Ó,€DÙØ)�$Ð)˜
 DÑ)ˆà
×
$Ñ
$ [Ó
1Ò
7°R€CØ
‡w�wˆwÔØ3�$Ð3˜¤ S¨¡\Ó!2Ñ3ˆØ
‡w�wˆzÓÐ&Ø9�$Ð9˜
¤C¨¨J©Ó$8Ñ9ˆØ
‡w�wÐ Ó!Ð-ØH�$ÐHÐ)¬4°Ð4EÑ0FÓ+GÑHˆØ
‡w�wˆ{ÓÐ'Ø<�$Ð<˜¤T¨#¨kÑ*:Ó%;Ñ<ˆØ
‡w�wˆ|ÔØ=�$Ð=˜¤c¨#¨lÑ*;Ó&<Ñ=ˆØ
‡w�wˆ~ÓÐ*ØB�$ÐB˜¬¨S°Ñ-@Ó(AÑBˆØ
‡w�wˆ}ÓÐ)Ø@�$Ð@˜¤t¨C°Ñ,>Ó'?Ñ@ˆØ
‡w�wÐ&Ó'¨>Ñ9ð O�$ÐNÐ/°Ð5LÑ1MÑNˆØ
‡w�wˆ{ÓÐ'ð =�$Ð<˜¤T¨#¨kÑ*:Ó%;Ñ<ˆØ
‡w�wˆÓÐ+ð E�$ÐD˜¬¨c°/Ñ.BÓ)CÑDˆØ
‡w�wÐ"Ó#Ð/ð M�$ÐLÐ+¬T°#Ð6IÑ2JÓ-KÑLˆØ�#—'‘'˜,Ó'Ò6¨;Ð7Ð7r(   c                óJ   — | j                  d«      xs | j                  d«      S )u
  Which account a race-plane row belongs to, whichever list it came off.

    One helper rather than two spellings at each call site, because the second spelling is the one
    that gets forgotten: the management rows say `ownerUserId`, the PUBLIC rows (what an
    unauthenticated producer follows) say `ownerUsername` and carry no user id at all. `None` means
    the row names no owner â€” "scope unknown", which callers must treat as *not excluded*, or a
    backend that publishes neither would put nothing on air.
    ÚownerUserIdÚownerUsername©r7   ©r‘   s    r&   Ú
_row_ownerr’  ™  s!   € ð �7‰7�=Ó!Ò= S§W¡W¨_Ó%=Ð=r(   c                  ó<   — e Zd ZdZdej
                  dœdd„Zdd„Zy)	Ú_ReadOwnerNamesab  Resolve immutable instance owners from the bounded read plane without polling per frame.

    The database follow path scopes races with a user id, but camera slot refs use the account name.
    A roster normally bridges those namespaces (`name:boat:7`); an unallocated waiting race has no
    boats, so its vessel cameras disappeared from the feed. The public instance list already carries
    the canonical `ownerUsername`. Cache positive matches because instance ownership is immutable,
    and rate-limit a missing match so read-plane propagation cannot turn the waiting loop into a
    request storm.
    g      >@)Úretry_sÚclockc               ó\   — || _         t        |«      | _        || _        i | _        i | _        y r-   )Úbackendr™   r•  r–  Ú_ownersÚ	_retry_at)rÏ   r˜  r•  r–  s       r&   rÐ   z_ReadOwnerNames.__init__°  s)   € ØˆŒÜ˜W“~ˆŒØˆŒ
Ø')ˆŒØ+-ˆ�r(   c                ó€  — || j                   v r| j                   |   S | j                  «       }|| j                  j                  |d«      k  ry 	 | j                  j                  «       }|xs g D ]^  }|j                  d«      xs |j                  d«      }|j                  d«      }|sŒ;|sŒ>t        |«      | j                   t        |«      <   Œ` || j                   vr|| j                  z   | j                  |<   | j                   j                  |«      S # t        $ r || j                  z   | j                  |<   Y y w xY w)Nrf   Ú
instanceIdÚidr�  )	r™  r–  rš  r7   r˜  Ú	instancesrõ   r•  r   )rÏ   rC   rî   r“   r‘   rN  rq  s          r&   Úfor_instancez_ReadOwnerNames.for_instance·  s  € Ø˜$Ÿ,™,Ñ&Ø—<‘< Ñ,Ð,Ø�j‰j‹lˆØ�—‘×#Ñ# K°Ó5Ò5Øð	Ø—<‘<×)Ñ)Ó+ˆDð ’:˜2ò 	5ˆCØ—7‘7˜<Ó(Ò9¨C¯G©G°D«MˆDØ—G‘G˜OÓ,ˆEÚšÜ*-¨e«*�—‘œS ›YÒ'ð		5ð
 ˜dŸl™lÑ*Ø*-°·±Ñ*<ˆD�N‰N˜;Ñ'Ø�|‰|×Ñ Ó,Ð,øô ò 	Ø*-°·±Ñ*<ˆD�N‰N˜;Ñ'Ùð	ús   ÁD Ä%D=Ä<D=N)r•  r™   ©rC   r   rý   r"  )r÷   r   r  r  r  r¿   rÐ   rŸ  r.   r(   r&   r”  r”  ¥  s   „ ñð 59ÀÇÁõ .ô-r(   r”  c                óÜ  — t        | t        «      rt        | «      dk  rg S g }t        | j	                  «       «      D ]ò  \  }}t        |t        «      r|ni }t        |j                  d«      xs d«      j                  «       }|sg c S g }|j                  d«      xs i j	                  «       D ]X  }t        |t        «      sŒ	 |j                  t        t        |j                  d«      xs d«      j                  «       «      «       ŒZ |rdt        |«      fnd|f}|j                  |||f«       Œô t        |«      D �	�
�cg c]  \  }	}
}|‘Œ
 c}}
}	S # t        t        f$ r Y Œ¶w xY wc c}}
}	w )a  Return explicitly configured team names in fleet order, or no unconfirmed matchup.

    The race office's ``teamConfig`` is sufficient to name teams before the gun. JSON object order
    is not a side/order contract, so configured sail numbers establish the stable fleet order.
    é   rè   r¡   rO   ÚsailIdr   r   )rÛ   rÜ   r9   r@  r6   r   r7   r©   rw   r�   Ú	TypeErrorÚ
ValueErrorÚminrG   )ry  ÚorderedrD  Úrawr‹  rè   Úsailsr   Úfleet_orderÚ_fleet_orderÚ_indexs              r&   Ú_confirmed_matchup_namesr­  Ì  sP  € ô �k¤4Ô(¬C°Ó,<¸qÒ,@Øˆ	Ø€GÜ × 2Ñ 2Ó 4Ó5ò 3‰
ˆˆsÜ ¤TÔ*‰c°ˆÜ�3—7‘7˜6“?Ò( bÓ)×/Ñ/Ó1ˆÙØŠIØˆØ—W‘W˜WÓ%Ò+¨×3Ñ3Ó5ò 	ˆDÜ˜d¤DÔ)ØðØ—‘œS¤ T§X¡X¨hÓ%7Ò%=¸2Ó!>×!DÑ!DÓ!FÓGÕHð		ñ */�qœ#˜e›*‘o°Q¸°JˆØ�‰˜ U¨DÐ1Õ2ð!3ô" 4:¸'³?×CÐCÑ/�\ 6¨4ŠDÔCÐCøô œzÐ*ò Ùðüô Ds   ÃAEÅ E'ÅE$Å#E$c                  óB   — e Zd ZdZdej
                  dœ	 	 	 dd„Zdd„Zy)	Ú_WaitingTeamConfigszWCache programme display facts so a one-second picture tick does not re-read every race.ç      .@)Úttl_sr¿   c               ód   — |t        dt        |«      «      |c| _        | _        | _        i | _        y )Nrf   )rx   r™   rÃ   Ú_ttl_sÚ_clockÚ_cache)rÏ   rº   r±  r¿   s       r&   rÐ   z_WaitingTeamConfigs.__init__ì  s*   € à/3´S¸¼eÀE»lÓ5KÈYÐ,ˆŒ
�D”K ¤Ø57ˆ�r(   c                ó   — | j                  «       }| j                  j                  |«      }|�||d   z
  | j                  k  r|d   S | j	                  |«      }t        |t        «      r|ni }||f| j                  |<   |S )Nr   r   )r´  rµ  r7   r³  rÃ   rÛ   rÜ   )rÏ   rC   rî   ÚcachedÚloadedrß   s         r&   rð   z_WaitingTeamConfigs.__call__ñ  sw   € Ø�k‰k‹mˆØ—‘—‘ Ó-ˆØÐ #¨¨q©	¡/°D·K±KÒ"?Ø˜!‘9ÐØ—‘˜KÓ(ˆÜ$ V¬TÔ2‘¸ˆØ$'¨ <ˆ�‰�KÑ Øˆr(   N)rº   zCallable[[str], dict]r±  r™   r¿   rû   rý   rþ   )rC   r   rý   rÜ   )r÷   r   r  r  r  r¿   rÐ   rð   r.   r(   r&   r¯  r¯  é  s)   „ ÙaàFJØ26·.±.ñ8Ø/ð8ØEIó8ô
r(   r¯  ©Úgust_ktsc               óÆ  — t        | t        t        f«      rt        |t        t        f«      si S t        j                  | «      rt        j                  |«      r|dk  ri S t        | «      t        |«      dœ}|r*t        |«      |d<   t        |«      |d<   t        |«      |d<   t        |t        t        f«      r(t        j                  |«      r|dk\  rt        |«      |d<   |S )u©  The feed-meta form of one wind reading, including direction/speed provenance.

    Empty when the reading is not usable. A wind that is absent is a block reading "â€”", which is
    honest; a wind that is `None`-cast-to-0 is a north-easterly the arrow points at, which is not.
    `windSource` names WHO supplied it; the correction layer may then split direction and speed
    sources without losing the provider credit.
    r   )ÚwindFromDegÚwindKtsÚ
windSourceÚwindDirectionSourceÚwindSpeedSourceÚwindGustKts)rÛ   r�   r™   r.  r/  r   )Údir_degÚ	speed_ktsr­   rº  r2  s        r&   Ú
_wind_metarÄ  ü  s¼   € ô �w¤¤e Ô-´*¸YÌÌeÈÔ2UØˆ	Ü�M‰M˜'Ô"¤t§}¡}°YÔ'?ÀIÐPQÂMØˆ	Ü ›.´U¸9Ó5EÑ
F€CÙÜ ›KˆˆLÑÜ%(¨£[ˆÐ!Ñ"Ü!$ V£ˆÐÑÜ�(œS¤%˜LÔ)¬d¯m©m¸HÔ.EÈ(ÐVWÊ-Ü" 8›_ˆˆMÑØ€Jr(   c                óB   — ddl m}  |t        | xs i «      |xs i «      S )zXPresentation-only direction fallback; stored instrument/model readings remain untouched.r   )Úapply_course_wind_direction)Úbroadcast_feedrÆ  rÜ   )Úwindr%   rÆ  s      r&   Ú_course_corrected_windrÉ    s   € å:Ù&¤t¨DªJ°BÓ'7¸ºÀ2ÓFÐFr(   c                óÐ   — d| vsd| vry| d   | d   dœ}t        | j                  d«      t        t        f«      r| d   |d<   dD ]  \  }}| j                  |«      sŒ| |   ||<   Œ! |S )uM   Feed metadata â†’ live wire wind, including split direction/speed provenance.r¼  r½  N)ÚfromDegÚktsrÁ  ÚgustKts))r¾  r­   )r¿  ÚdirectionSource)rÀ  ÚspeedSource)rÛ   r7   r�   r™   )r#   r2  r­   rç   s       r&   Ú_wind_wire_framerÐ    sƒ   € à˜DÑ  I°TÑ$9ØØ˜=Ñ)°$°y±/Ñ
B€CÜ�$—(‘(˜=Ó)¬C´¨<Ô8Ø˜mÑ,ˆˆI‰ð?ò '‰ˆ�ð �8‰8�FÕØ˜v™,ˆC�ŠKð	'ð
 €Jr(   c                ó@   — t        t        d„ | xs g D «       «      «      S )u°  Identity of a LINE-UP: which teams, holding which boats.

    Used both by the waiting picture (which re-reads its line-up every poll, because marshalling is
    live) and by a race on air (which, until 2026-08-02, never re-read it at all). Comparing this
    rather than the whole `teams` blob is what lets a re-resolution republish `init` only when the
    roster genuinely changed â€” an init per poll would restart the renderer.c              3  óˆ   K  — | ]:  }|j                  d «      t        d„ |j                  d«      xs g D «       «      f–— Œ< y­w)ÚteamIdc              3  ó>   K  — | ]  }|j                  d «      –— Œ y­w)ÚdeviceIdNr�  )r/   rN   s     r&   r2   z(_roster_key.<locals>.<genexpr>.<genexpr>.  s   è ø€ Ò/bÀa°·±°j×0AÑ/bùs   ‚rO   N)r7   rÝ   )r/   Úts     r&   r2   z_roster_key.<locals>.<genexpr>.  s?   è ø€ ò 0Øð Ÿ™˜x›¬%Ñ/bÈAÏEÉEÐRYËNÒL`Ð^`Ô/bÓ*bÔcñ 0ùs   ‚A A)rÝ   rG   )r$   s    r&   Ú_roster_keyr×  '  s)   € ô ”ñ 0Ø"'¢+¨2ô0ó 0ó 1ð 1r(   é`ê  c                ón  — i }i }t        | t        ¬«      D ]–  }| j                  |«      xs i j                  «       D ��ci c]  \  }}||v sŒ||“Œ }}}|j	                  |«       d}	|D ]'  }|t        |«      f}
|
|vsŒ|j                  |
«       d}	Œ) |	sŒ€t        |«      |t        |«      <   Œ˜ |S c c}}w )aá  Return newly arrived track seconds in source-time order, carrying a complete fleet state.

    Cellular uploads arrive in batches and can add an older second after newer ones are already in
    the feed. The rolling `/tracks` read contains those intermediate observations; this merge keeps
    the original second, republishes a second when another boat later fills it, and ignores boats
    outside the race roster. `seen` is updated in place across overlapping tail windows.
    ©rd   FT)rG   r�   r7   r•   Úupdater   rê   rÜ   )ÚframesÚseenrO   r‡   r2  ÚsecÚdevr1  r­   Úfreshrd   s              r&   Ú_new_tail_framesrá  5  sÅ   € ð !€FØ€CÜ�f¤#Ô&ò 
)ˆØ06·
±
¸3³Ò0EÀ2×/LÑ/LÓ/N×_¡  eÐRUÐY^ÒR^�#�u‘*Ð_ˆÑ_Ø�‰�fÔØˆØò 	ˆCØœ˜C›�/ˆCØ˜$ŠØ—‘˜”Ø‘ð		ò
 Ü  ›LˆC”�C“ŠMð
)ð €Jùó `s   ¿B1ÁB1c                  ó²  — e Zd ZdZddddddddddddddddedddddej                  ddœ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d"d„Zd#d„Zd$d„Z	dd	œd%d
„Z
d&d„Zd'd„Zd(d„Zd)d„Zd*d„Zd+d„Zd,d„Zd-d„Zd.d„Z	 	 d/	 	 	 d0d„Zd#d„Zd1d„Zd2d„Zd3d„Zd3d„Zd3d„Zd4d„Zd5d„Zd6d„Zd-d„Zed7d„«       Z d3d „Z!d#d!„Z"y)8Ú
_OnAirRacea‘  ONE race on air: its adapter, resolved static feed, scorer, commentator and per-tick publish.

    A race is a built object rather than a stretch of loop body so the producer can **move to the next
    race without restarting** (`broadcast_following`): build the next one, publish its `init`, keep the
    same server and the same SSE connection. The renderer swaps in place (`live/web/live_feed.js`).NFT)ry  r  Ú	read_baseÚ
read_tokenÚproducerÚsimpler}   ÚaisÚcenterÚ	media_forÚ
frame_tailÚwind_forÚ
course_forÚjoin_msr$   ÚbackfillrÀ   Ú
first_raceÚleaving_race_nameÚresuming_from_waitingÚcommentary_rotationÚttsr¿   Únow_msc               óÆ	  ‡‡‡— || _         t        «       | _        |xs d„ | _        ||||f\  | _        | _        | _        | _        |||c| _        | _	        | _
        || _        || _        || _        || _        d | _        d | _        d | _        || _        || _        d| _        g | _        d| _        ‰�9ddlm}  ddlm}! |
st7        d«      ‚ | || |!|
||¬«      ˆˆˆfd	„¬
«      | _        n%t;        ||||‰‰||	t=        ||«      |
d¬«      | _        | j8                  j?                  «       \  | _         | _!        | _"        d| j@                  vri | j@                  ¥ddi¥| _         |�i | j@                  ¥ddi¥| _         | jG                  «       }"|"r+i | j@                  ¥|"¥| _         | jI                  |"«      | _        |�tK        d|| j@                  d   z
  dz  «      nd}#tM        |#«      \  }$}%i | j@                  ¥dtO        |#| j@                  jQ                  d«      |||¬«      i¥| _         |�,|jS                  | j@                  d   d   | jD                  «       | j@                  jQ                  d«      rt        |«      n	t        «       | _*        tW        | j@                  | jB                  | jD                  |||�|jR                  nd ¬«      | _,        d| _-        d | _.        d| _/        |r+ta        | j@                  | jD                  | jB                  «      nd | _1        |�te        |dd «      nd | _3        t        «       | _4        i | _5        i | _6        d| _7        d| _8        | js                  «       }&|&rtu        |&«      | _8        | jw                  |&«       | j
                  jy                  | j@                  | jB                  | jD                  i |&¬«       | j                  «       | _=        d| _>        t        | jD                  «      | _?        |�Ý| j@                  d   }'|#}(|(dkD  rÆ|� ||||'|'|(dz  z   «      })n|�ddl@mA}*  |*|||'|'|(dz  z   ¬«      })ni })|)rƒ| jX                  j…                  |)|$¬«      }+|)j‡                  «       D �,�-�.ch c]  \  },}-|-D ]  }.|.t‰        |,«      f’Œ Œ c}.}-},| _4        | j‹                  |)|+|%|(«        |d|› d |(› d!|(|%z
  › d"�«       y  |d|› d |(› d#�«       y y y c c}.}-},w )$Nc                 óB   — t        t        j                  «       dz  «      S ©Nrg   ©r�   r  r.   r(   r&   rc   z%_OnAirRace.__init__.<locals>.<lambda>h  ó   € ¬#¬d¯i©i«k¸DÑ.@Ó*A€ r(   Fr^   r   )ÚLiveReadThroughAdapter©Úhttp_fetch_positionszFa pre-resolved presentation needs read_base (positions come over REST)©Útokenc                 ó   •— ‰‰ ‰fS r-   r.   )r%   r#   r$   s   €€€r&   rc   z%_OnAirRace.__init__.<locals>.<lambda>�  s   ø€  d¨F°EÐ%:€ r(   )Úfetch_positionsÚfetch_staticé0u  )r#   r%   ry  r  Úwind_serialsrä  Ú
max_age_msÚcommentaryDensityÚdenserˆ  TÚtrackStartUtcMsrg   Ú	programmer{  )Ú	elapsed_sÚ	race_namerð  rñ  rò  ÚreturnCommentaryr�  )ræ  ró  Úon_scored_eventsr¡   Úrace_signals_forr–   )Úboat_frames)r|   Útrack_end_ms)Úair_fromú[live] z joined in progress at T+u;   s â€” back-filled so the standings are whole, and the last z:s put on the wire so the picture is not frozen at the joinu=   s with NO history available â€” standings start from the join)FrÅ   rÌ   Ú_offered_cuesÚ_now_msrI   Úsrvrè  ré  rC   rO   r}   Ú
_media_forÚ_frame_tailÚ	_wind_forÚ_course_forÚ_windÚ_wind_errorÚ_course_errorrÆ   Ú_ttsÚ_manual_course_guide_requestedÚ_manual_course_guide_pendingÚ!_manual_course_guide_active_untilÚlive_adapterrû  Ú
v3_adapterrý  r¥  Úadapterr   rp  Úresolve_meta_course_teamsr#   r%   r$   Ú_fresh_windÚ_wind_framerx   r   r   r7   ÚsubmitÚ_camera_boatsr   ÚscorerÚ_server_closedÚ_server_resultÚ_result_keyr'   Úcommentatorrq   Ú_signals_forÚ_seenÚ_latest_wire_positionsÚ_latest_source_msÚ_last_published_secÚpublished_clipsÚ_resolve_mediar—   Ú_note_camerasÚpublish_initÚ_last_publish_sÚ_staler×  Úreadsr  Úseed_framesr•   r   Ú_publish_history)/rÏ   rI   r  rC   Úgroup_idrO   r#   r%   ry  r  rä  rå  ræ  rç  r}   rè  ré  rê  rë  rì  rí  rî  r$   rï  rÀ   rð  rñ  rò  ró  rô  r¿   rõ  rû  rý  Úinitial_windr
  Úcoverage_secÚfrom_secrs   Út0r�   rÜ  r  ÚhistrÞ  r:   rß  s/         ``              `                        r&   rÐ   z_OnAirRace.__init__T  s  ú€ ð$ $ˆŒÜ'*£uˆÔØÒBÑ"AˆŒØ6;¸SÀ#ÀvÐ6MÑ3ˆŒ
�D”H˜dœh¨¬Ø6AÀ5È(Ð3ˆÔ˜$œ* d¤mð $ˆŒð &ˆÔð "ˆŒð &ˆÔØ"&ˆŒ
Ø'+ˆÔØ)-ˆÔØˆŒØˆŒ	Ø.3ˆÔ+Ø8:ˆÔ)Ø13ˆÔ.ØÐõ <Ý7ÙÜ Ð!iÓjÐjÙ1Ø˜XÙ 4°YÀÐS]Ô ^Ý:ô<ˆD�Lô
 1Ø�{ H¨eØ &°kÈYÜ2°5¸+ÓFØ#ð "ôˆDŒLð .2¯\©\×-SÑ-SÓ-UÑ*ˆŒ	�4”; ¤
ð  d§i¡iÑ/ØC˜4Ÿ9™9ÐCÐ&9¸7ÑCˆDŒIØˆ?ð A˜4Ÿ9™9Ð@Ð&9¸4Ñ@ˆDŒIð ×'Ñ'Ó)ˆÙØ5˜4Ÿ9™9Ð5¨Ð5ˆDŒIØ×)Ñ)¨,Ó7ˆDŒJð  Ð+ô ˜˜W t§y¡yÐ1BÑ'CÑCÈÑLÔMØ12ð 	ä!0°Ó!;Ñˆ�hð:�t—y‘yð : +¬~Ø¨4¯9©9¯=©=¸Ó+DÐQ[Ø/Ø"7ô09ñ :ˆŒ	ð ˆ?Ø�J‰J�t—y‘y Ñ-Ð.@ÑAÀ4Ç:Á:ÔNð 6:·Y±Y·]±]À>Ô5R¤s¨5¤zÔX[ÓX]ˆÔÜ%Ø�I‰I�t—{‘{ D§J¡J¸Ø 3ð -0¨O˜cŸjšjÀô
ˆŒð $ˆÔð ,0ˆÔð ˆÔÙV\Ô.¨t¯y©y¸$¿*¹*ÀdÇkÁkÔRÐbfˆÔð INÐHYœG EÐ+=¸tÔDÐ_cˆÔÜ+.«5ˆŒ
ð 8:ˆÔ#Ø9;ˆÔØ#%ˆÔ à!ˆÔØ×#Ñ#Ó%ˆÙÜ#-¨eÓ#4ˆDÔ Ø×Ñ˜5Ô!Ø�‰×Ñ˜dŸi™i¨¯©°d·j±jÀ"ÈEÐÔRð  $Ÿ™Ó0ˆÔØˆŒô ' t§z¡zÓ2ˆÔð ÐØ—‘Ð,Ñ-ˆBØˆGØ˜Š{ØÐ'Ù% k°5¸"¸bÀ7ÈTÁ>Ñ>QÓR‘FØÐ&Ý1Ù(¨°ÀbØ68¸7ÀT¹>Ñ6IôK‘Fð  �FÙØŸ;™;×2Ñ2°6ÀLÐ2ÓQ�DØHNÏÉË÷ "8ð "8±n°c¸9Ø-6ò"8Ø&)ð $'¬¨C«¢/ð "8 /ô "8�D”Jà×)Ñ)¨&°$¸À'ÔJÙ˜W [ MÐ1JÈ7È)ð TGØGNÐQYÑGYÐFZð [OðPõ Qñ ˜W [ MÐ1JÈ7È)ð TIð Jõ Kð% ð ùô"8s   Ñ8Sc                ót  — | j                   j                  d«      xs i j                  «       D �cg c]  }|| j                  vr|‘Œ }}| j	                  t        | j                  «      «      xs i }|r&|j                  | j	                  |d¬«      xs i «       | j                  €| j                  �|S dS c c}w )up  The boat-cam clip map for this race, from whichever source this producer has.

        `None` means "this producer carries no camera channel at all" and is distinct from `{}`,
        "the channel works and nobody has filmed yet" â€” the tile's message is the same either way,
        which is exactly how the REST path went a whole regatta looking like empty cameras.ÚvesselCamerasT©Úown_race_onlyN)	r#   r7   r6   rO   Ú_media_for_boatsrÞ   rÛ  r  r}   )rÏ   ÚsÚvesselsrs   s       r&   r4  z_OnAirRace._resolve_media  s¬   € ð&  $Ÿy™yŸ}™}¨_Ó=ÒCÀ×KÑKÓMö +˜Ø˜tŸz™zÑ)ò ð +ˆð +à×%Ñ%¤d¨4¯:©:Ó&6Ó7Ò=¸2ˆÙØ�L‰L˜×.Ñ.¨wÀdÐ.ÓKÒQÈrÔRØŸ™Ð4¸¿¹Ð8QˆuÐ\ÐX\Ð\ùò+s   °B5c                óº   — t        | j                  d   «      }| j                  j                  d«      }|t        |t         t        z  «      r|rt        |«      fS dfS )u
  `(from, until)` for footage that belongs to THIS race â€” its own track window.

        `until` is None while the race is still running: there is no later race yet, so there is
        nothing to exclude, and a bound guessed from the clock would cut the finish off.r  ÚtrackEndUtcMsN)r�   r#   r7   rÛ   r™   )rÏ   Útrack_startÚends      r&   Ú_race_window_msz_OnAirRace._race_window_ms4  sQ   € ô
 ˜$Ÿ)™)Ð$5Ñ6Ó7ˆØ�i‰i�m‰m˜OÓ,ˆØ¬°C¼¼u¹Ô)EÉ#œS ›XÐXÐXÐSWÐXÐXr(   rD  c          	     ór  — |si S ddl m} t        | j                  d   «      }|r| j	                  «       nd\  }}|r|nt        d||z
  «      }| j                  �!| j                  | j                  |||||¬«      S | j                  �2| j                  �&t        | j                  ||| j                  |||¬«      S y)u»   The clip map for `boats`, from whichever source this producer has. `own_race_only` applies
        the race's own window instead of the pre-race buffering margin â€” see `_resolve_media`.r   )ÚPRE_RACE_CLIP_MARGIN_MSr  ©NNNrS   )r}   rT   rU   rV   )rX   rO  r�   r#   rM  rx   r  rC   r}   rI   r‰   )rÏ   rO   rE  rO  rK  rU   rV   Úsinces           r&   rF  z_OnAirRace._media_for_boats=  sÁ   € ñ ØˆIÝ5Ü˜$Ÿ)™)Ð$5Ñ6Ó7ˆÙ6C˜D×0Ñ0Ô2ÈÑˆ�Ù,‘´#°a¸ÐG^Ñ9^Ó2_ˆØ�?‰?Ð&Ø—?‘? 4×#3Ñ#3°U¸KØ,1¸7ÈXð #ó Wð Wà�=‰=Ð$¨¯©Ð)?Ü# D§J¡J°°{ÈTÏ]É]Ø-2¸GÈhôXð Xàr(   c                óP  — t        | j                  «      |xs i j                  «       D ��ch c]
  \  }}|sŒ	|’Œ c}}z  }|| j                  k(  rd| j                  v ry|| _        i | j                  ¥dt	        |«      i¥| _        | j                  | j
                  _        yc c}}w )uP  Adopt the boats whose camera can be shown, and tell the scorer.

        `meta` is rebuilt rather than mutated everywhere else in this class, so the scorer's copy is
        updated alongside it â€” the producer reads `availableOnboardCameraBoatIds` off the meta it was
        given, and a set that grew only here would never reach it.ÚavailableOnboardCameraBoatIdsN)rÌ   r(  r•   r#   rG   r)  )rÏ   rs   rß  ÚclipsrO   s        r&   r5  z_OnAirRace._note_camerasN  s�   € ô �D×&Ñ&Ó'À%Â+È2×ATÑATÓAV×*`±:°3¸ÒZ_ª3Ó*`Ñ`ˆØ�D×&Ñ&Ò&Ð+JÈdÏiÉiÑ+WØØ"ˆÔØQ�t—y‘yÐQÐ"AÄ6È%Ã=ÑQˆŒ	ØŸ9™9ˆ�‰Õùó +as
   ¬
B"·B"c                ó*  — t        | j                  d   | j                  d   t        |«      | j                  «       t        | j                  j	                  d«      xs i j	                  d«      xs d«      | j                  j	                  d«      ¬«      S )uX   This second's presentation phase â€” the gun, the countdown and the last signal sounded.r}  r  ÚstartSequenceÚ	preStartSr,  ÚraceSignals)Ú
gun_utc_msÚtrack_start_utc_msrÞ  ÚfinishedÚpre_start_sÚsignals)r   r#   r�   r[  r7   )rÏ   rÞ  s     r&   Ú_programz_OnAirRace._program[  sy   € ä¨¯	©	°*Ñ(=Ø04·	±	Ð:KÑ0LÜ!$ S£°D·M±M³OÜ),¨d¯i©i¯m©m¸OÓ.LÒ.RÐPRß.1©c°+Ó.>ò.FØBEó*Gà%)§Y¡Y§]¡]°=Ó%AôCð 	Cr(   c                ón  — t        t        |«      t        |«      dz   «      D ]�  }|j                  t        |«      «      }|sŒ | j	                  ||«      }|€Œ5|\  }}	| j
                  j                  ||j                  t        |«      «      g |	g | j                  |«      ¬«       Œƒ | j                  «        y)u‚  Put the joined race's COVERAGE WINDOW on the wire before its first live tick.

        THE PICTURE NEEDS THE HISTORY, NOT JUST THE SCORER. The renderer presents `frontier âˆ’ lag`
        (`core/web/director.js` `LIVE_LAG_MS`), so with nothing published behind the live edge its
        cursor sits below `feed.startSec`, `stateAt` clamps, and the boats, the clock and the drone
        all hold one frame for the whole delay â€” the ~30 s freeze at every hand-over. Publishing the
        last `PRESENTATION_LAG_S` seconds gives the cursor real, moving, scored data to land on.

        POSITIONS AND STANDINGS ONLY, no commentary: the lines for these same seconds arrive through
        the normal finalisation path on the first live tick, already settled. Emitting them here would
        dump a settled backlog into the broadcast all at once â€” the thing the air floor exists to stop.
        r   N)Úprogram)	Úranger�   r7   r   Ú_monotonic_framer  Úpublish_deltar^  Ú_note_published)
rÏ   rÜ  Ú	standingsr?  Úto_secrÞ  r:   r¿   Úwire_secÚwire_positionss
             r&   r;  z_OnAirRace._publish_historyd  s¯   € ô œ˜X›¬¨F«°a©Ó8ò 		DˆCØŸ
™
¤3 s£8Ó,ˆIÙØØ×-Ñ-¨c°9Ó=ˆIØÐ ØØ'0Ñ$ˆH�nØ�H‰H×"Ñ" 8¨Y¯]©]¼3¸s»8Ó-DÀbÈ.ÐZ\Ø+/¯=©=¸Ó+Bð #õ Dð		Dð 	×ÑÕr(   c           
     ó¬  — d}|j                  «       D ]™  \  }}|j                  d«      }| j                  j                  |«      }t        |t        t
        f«      rt        |t        t
        f«      r||k  rŒd|| j                  |<   t        |t        t
        f«      r|| j                  |<   d}Œ› |sy| j                  j                  «       D �cg c])  }t	        |t	        | j                  d   «      z
  dz  «      ‘Œ+ }	}t        t	        |«      | j                  t        |	| j                  ¬«      «      }
|
| _
        |
t        | j                  «      fS c c}w )a  Merge a source frame onto the live frontier without allowing time or hull regression.

        The read plane is deliberately eventually consistent: phones upload delayed batches and
        replicas may expose those batches in different orders.  Recovered NEW fixes must land at
        once, but replaying their older neighbours on air makes boats stop and jump backwards.
        FÚtimestampUtcMsTNr  rg   r’   )r•   r7   r1  rÛ   r�   r™   r0  r6   r#   rx   r2  rÜ   )rÏ   rÞ  r:   Úadvancedrß  r1  Ú	source_msÚprevious_msrJ   Úsource_frontierrg  s              r&   rb  z_OnAirRace._monotonic_frame}  sD  € ð ˆØ#Ÿ/™/Ó+ò 
	‰JˆC�ØŸ	™	Ð"2Ó3ˆIØ×0Ñ0×4Ñ4°SÓ9ˆKÜ˜9¤s¬E lÔ3Ü" ;´´e°Ô=Ø! [Ò0ØØ/4ˆD×'Ñ'¨Ñ,Ü˜)¤c¬5 \Ô2Ø.7�×&Ñ& sÑ+Ø‰Hð
	ñ Øð ×.Ñ.×5Ñ5Ó7ö
àô �œ˜DŸI™IÐ&7Ñ8Ó9Ñ9¸dÑBÕCð
ˆð 
ô ”s˜3“x ×!9Ñ!9Ü˜?°D×4LÑ4LÔMóOˆà#+ˆÔ Øœ˜d×9Ñ9Ó:Ð:Ð:ùò
s   Ã.Ec                ó<   — | j                  «       | j                  z
  S )z6Seconds since this race last put anything on the wire.)rÅ   r7  ©rÏ   s    r&   Úsince_publish_sz_OnAirRace.since_publish_sž  s   € à�‰Ó  4×#7Ñ#7Ñ7Ð7r(   c                ój   — d| _         | j                  j                  d|| j                  «       ¬«       y)zêON AIR and publishing nothing. Tell the clients so the picture can say so.

        Re-sent while the stall lasts (not only on the transition) so the signal stays inside the
        backlog tail that a reconnecting OBS source replays.T)ÚreasonÚsince_sN)r8  r  Úpublish_stalerq  )rÏ   rs  s     r&   Únote_stalledz_OnAirRace.note_stalled¢  s-   € ð
 ˆŒØ�‰×Ñ˜t¨F¸D×<PÑ<PÓ<RÐÕSr(   c                ó’  — t        | j                  j                  d«      xs d«      }|sy| j                  j                  d«      }|rt        |«      |z
  dz  nd}|D ]ç  }t        ||¬«      }|t        vrŒt        |j                  dd«      «      }| j                  › dt        |d	z  «      › d|› �}|| j                  v rŒc| j                  j                  |«       | j                  j                  |||j                  d
«      xs |dd t        ||t        z   dz  z   «      | j                  j                  d«      xs ddœ«       Œé y)uÐ  Publish the moments in this delta that are worth a replay. OFFERS, never actions.

        Two things make a cue usable rather than merely interesting.

        WHICH MOMENTS: `broadcast_metadata.cue_kind` â€” the same classifier that builds the per-race
        chapter list in the distribution package, so the broadcast cannot decide a rounding deserves
        a marker afterwards but not an offer while it is happening. It carries the claim gate with it,
        so an interpretive claim with no RIE behind it is withheld here exactly as it is everywhere.

        WHEN, IN THE RECORDING: **presentation time, not water time.** The recording holds the picture
        as it went out, and the producer runs `PRESENTATION_LAG_S` AHEAD of that picture â€” so a cue
        carrying the instant the boats actually rounded would cut a replay 22 seconds early. The whole
        clip would be in the wrong place and plausible enough on screen for nobody to question it.
        r  r   Nr}  ç     @�@©Úgun_secri   r]   é
   Útextéx   rg   r{  r¡   )r�  ÚkindÚlabelÚutcMsÚrace)r�   r#   r7   r   r   r™   rC   r  rê   r  Úpublish_replay_cuer   )	rÏ   Úeventsr@  Úgunrz  Úeventr~  Út_secÚcue_ids	            r&   Ú_offer_replay_cuesz_OnAirRace._offer_replay_cuesª  s6  € ô �—‘—‘Ð0Ó1Ò6°QÓ7ˆÙØØ�i‰i�m‰m˜JÓ'ˆÙ.1”3�s“8˜b‘= FÒ*°tˆØò 	ˆEÜ˜E¨7Ô3ˆDØœ=Ñ(ØÜ˜%Ÿ)™) F¨AÓ.Ó/ˆEØ×(Ñ(Ð)¨¬3¨u°r©z«?Ð*;¸1¸T¸FÐCˆFØ˜×+Ñ+Ñ+ØØ×Ñ×"Ñ" 6Ô*Ø�H‰H×'Ñ'Ø dØŸ)™) FÓ+Ò3¨t°T°cÐ:Ü˜R 5Ô+=Ñ#=ÀÑ"EÑEÓFØŸ	™	Ÿ™ jÓ1Ò7°Rñ	)õ ñ	r(   c                ó�   — | j                  «       | _        | j                  r%d| _        | j                  j	                  dd¬«       yy)z?Something reached the wire. Clears a stall, if one was showing.FÚ	recovered)rs  N)rÅ   r7  r8  r  ru  rp  s    r&   rd  z_OnAirRace._note_publishedÎ  s;   € à#Ÿ™Ó0ˆÔØ�;Š;ØˆDŒKØ�H‰H×"Ñ" 5°Ð"Õ=ð r(   c                ó†  — |€| j                   j                  «       \  }}}t        |«      }|| j                  k(  ry||||f\  | _        | _        | _        | _        | j                  j                  |«       | j                  j                  | j                  | j                  | j
                  i «       | j                  «        y)uÔ  Re-resolved race plane: adopt it and re-publish `init` IF the roster actually changed.

        The broadcast READS the race plane, it never decides it (CLAUDE.md) â€” this is reading it
        again, and authors nothing. Republishing unconditionally would restart the renderer every
        poll, so the roster key is the gate, the same shape the waiting picture already uses for the
        line-up.

        `teams=None` means the CALLER has no teams source â€” the store path's `presentation_for`
        returns four elements â€” and it must never be adopted as a roster: `enrich` iterates `teams`,
        so a None handed to the scorer kills every tick from then on with the picture frozen and a
        one-line TypeError repeating in the log. Measured twice (2026-08-09 proxy death,
        2026-08-10 on air). This race re-resolves its OWN teams instead, through the same adapter
        read it was built from â€” the store re-read is exactly what the watchdog wanted.FT)r#  r$  r×  r#   r%   r$   r)  Úreplace_teamsr  r6  rd  )rÏ   r#   r%   r$   Ú_mÚ_crd   s          r&   Úrefresh_presentationz_OnAirRace.refresh_presentationÕ  s¢   € ð ˆ=Ø ŸL™L×BÑBÓD‰MˆB��EÜ˜%Ó ˆØ�$×"Ñ"Ò"ØØ?CÀVÈUÐTWÐ?WÑ<ˆŒ	�4”; ¤
¨DÔ,<Ø�‰×!Ñ! %Ô(Ø�‰×Ñ˜dŸi™i¨¯©°d·j±jÀ"ÔEØ×ÑÔØr(   c                ó  ‡— d| _         |syt        |«      Št        ˆfd„| j                  D «       i «      }t	        d|�'t        |«      t        | j                  d   «      z
  dz  nt        | j                  «      «      }‰|j                  d«      xs ‰||t        | j                  «      g |dz   ‰g| j                  D �cg c]9  }t        |j                  d	«      «      ‰k7  rt        |j                  d	«      «      ‘Œ; c}¢dd
dœ
| _        yc c}w )uS  The race plane has ended this race â€” the operator's stop, or an abandonment. WHETHER is
        the question.  When its finish detector also supplies a preliminary winner, preserve that
        authoritative read-side fact even if incomplete tracking prevents the presentation scorer
        from independently reconstructing the finish.TNc              3  ó^   •K  — | ]$  }t        |j                  d «      «      ‰k(  sŒ!|–— Œ& y­w)rÓ  N©r   r7   )r/   r‘   Úteam_ids     €r&   r2   z0_OnAirRace.note_server_closed.<locals>.<genexpr>ø  s&   øè ø€ ÒT˜S´#°c·g±g¸hÓ6GÓ2HÈGÓ2S”SÑTùs   ƒ"-¦-rf   r  rx  rè   é<   rÓ  Úfinish_detector)
ÚwinnerÚ
winnerNameÚ	winnerSecÚpresentationEndSecÚexpectedFinishersÚ	finishersÚswitchAtSecÚorderÚpreliminaryr­   )r*  r   Únextr$   rx   r�   r#   r™   r2  r7   r9   rO   r+  )rÏ   Úpreliminary_winner_team_idÚstop_utc_msrM   Ústop_secr‘   r“  s         @r&   Únote_server_closedz_OnAirRace.note_server_closedî  s  ø€ ð #ˆÔÙ)ØÜÐ0Ó1ˆÜÓT D§J¡JÔTÐVXÓYˆÜ�sØ +Ð 7ô " +Ó.´°T·Y±YÐ?PÑ5QÓ1RÑRÐV\Ò\Ü=BÀ4×C[ÑC[Ó=\ó_ˆð ØŸ(™( 6Ó*Ò5¨gØ!Ø"*Ü!$ T§Z¡Z£ØØ# b™=Øð IÀ4Ç:Á:ö !H¸CÜ%(¨¯©°Ó):Ó%;¸wÒ%Fô "% S§W¡W¨XÓ%6Õ!7ò !Hð IàØ'ñ
ˆÕùò!Hs   Â>>D
c                óÊ   — | j                   j                  «       }| j                  €|S |€t        | j                  «      S i |¥| j                  d   | j                  d   dddœ¥S )zWMerge scorer detail with the race plane's authoritative preliminary team, when present.r–  r—  Tr•  )r–  r—  rž  r­   )r)  Úresultr+  rÜ   )rÏ   Úscoreds     r&   Ú_effective_resultz_OnAirRace._effective_result	  ss   € à—‘×#Ñ#Ó%ˆØ×ÑÐ&ØˆMØˆ>Ü˜×+Ñ+Ó,Ð,ð-�&ð -Ø×-Ñ-¨hÑ7Ø"×1Ñ1°,Ñ?Ø#Ø+ò	-ð 	-r(   c           	     óh  — | j                   ry| j                  «       }|syt        |j                  d«      xs g D �cg c]#  }t	        |t
        «      r|j                  d«      ‘Œ% c}|j                  d«      xs t        | j                  «      «      }|duxr | j                  t        z
  |k\  S c c}w )uà  Is the race over?

        THE SERVER OWNS THIS. A race ends when the Race Officer's record says it ended â€” the
        broadcast reads that, it does not decide it. This used to answer from the presentation's own
        scorer, which meant the commentary engine calling a winner was what made a race "finished":
        a race the server had stopped could still read as running here, and a scorer that stayed
        silent could leave a finished fleet marked ongoing.

        The scorer remains the FALLBACK, deliberately. A broadcast that cannot reach the read plane
        must still be able to end a race rather than sit on a finished fleet forever â€” losing the
        server's answer costs authority, not the show.
        TFr›  Ú	finishSecrš  N)
r*  r§  r   r7   rÛ   rÜ   r9   rO   r2  r   )rÏ   r¥  r‘   rç   s       r&   r[  z_OnAirRace.finished  s¨   € ð ×ÒØØ×'Ñ'Ó)ˆÙØÜ$Ø.4¯j©j¸Ó.EÒ.KÈö ' cÜ˜3¤Ô%ð �W‰W�[Õ!ò 'à�J‰JÐ*Ó+Ò>¬s°4·:±:«ó@ˆð ˜TÐ!Ò] d×&>Ñ&>ÔASÑ&SÐW]Ñ&]Ð]ùò's   ½(B/c                ó:   — | j                  «       | j                  fS )zKStructured scorer state and source race-time frontier for the on-air queue.)r§  r2  rp  s    r&   Úfinish_progressz_OnAirRace.finish_progress.  s   € à×%Ñ%Ó'¨×)AÑ)AÐAÐAr(   c                óº   — | j                   j                  «       }|j                  d«      }|�-i |¥dt        | j                  d   t        |«      dz  z   «      i¥}|S )zAObserved roster starts translated onto the programme's UTC clock.ÚallBoatsStartedSecÚallStartedUtcMsr  rg   )r)  Ústart_progressr7   r�   r#   )rÏ   ÚprogressÚall_secs      r&   Ústart_camera_progressz _OnAirRace.start_camera_progress2  sh   € à—;‘;×-Ñ-Ó/ˆØ—,‘,Ð3Ó4ˆØÐðd˜(ð dØ)¬3¨t¯y©yÐ9JÑ/KÌcÐRYËlÐ]aÑNaÑ/aÓ+bñdˆHàˆr(   c                óŠ   — | j                   rdnd| j                  | j                  dk\  r| j                  nd| j                  dœS )zBState of the operator-requested guide over this race's live fleet.ÚactiveÚreadyr   N)ÚstatusÚ	requestedÚactiveUntilSecrœ  )r  r  r   rC   rp  s    r&   Úcourse_guide_statez_OnAirRace.course_guide_state;  sI   € ð #'×"CÒ"C‘hÈØ×<Ñ<à"&×"HÑ"HÈAÒ"Mð  $×EÒEØSWØ×*Ñ*ñ
ð 	
r(   c                óT   — | j                   sd| _        i | j                  «       ¥ddi¥S )zOQueue one course explanation without replacing the fleet or altering race data.Tr¶  Úqueued)r  r  r¹  rp  s    r&   Úrequest_course_guidez_OnAirRace.request_course_guideE  s.   € à×0Ò0Ø26ˆDÔ/Ø@�$×)Ñ)Ó+Ð@¨X°xÑ@Ð@r(   c                ó$  — | j                   rL| j                   D �cg c]  }|d   |k  sŒ|‘Œ }}| j                   D �cg c]  }|d   |kD  sŒ|‘Œ c}| _         |S | j                  sg S d| _        t        | j                  |«      }|sg S |dd | _         |t	        |d   d   «      z   t
        z   | _        | j                  �&| j                  j                  || j                  «       | j                  d| j                  › d�«       |dd S c c}w c c}w )	zORelease a requested guide at the live frontier, one authored chapter at a time.ri   Fr   Nr   ÚcourseGuideEndOffsetSr  z.: operator course guide queued over live fleet)r  r  Ú_waiting_course_guider%   r�   ÚWAITING_COURSE_OVERVIEW_Sr   r  r'  r$   rÆ   rC   ©rÏ   rÞ  r…  rµ  Úguides        r&   Ú_manual_course_guide_beatsz%_OnAirRace._manual_course_guide_beatsK  s  € à×,Ò,Ø(,×(IÑ(IÖb˜uÈUÐSYÉ]Ð^aÓMa’UÐbˆEÐbà#'×#DÑ#Dö1]ØÈÈfÉÐX[ÓH[’ò1]ˆDÔ-àˆLØ×2Ò2ØˆIØ.3ˆÔ+Ü% d§k¡k°3Ó7ˆÙØˆIØ,1°!°"¨IˆÔ)à”#�e˜A‘hÐ6Ñ7Ó8Ñ8Ô;TÑTð 	Ô.à�9‰9Ð Ø�I‰I×Ñ˜U D§J¡JÔ/Ø�‰�w˜t×/Ñ/Ð0Ð0^Ð_Ô`Ø�R�aˆyÐùò! cùò1]s   ›D©D½DÁDc                óî  — t        |«      }|j                  d«      xs g D �cg c]  }t        |t         «      sŒ|‘Œ }}t        |j                  d«      xs t	        | j
                  «      «      }t        |D �cg c]  }|j                  d«      ‘Œ c}|«      }|�||d<   n*t        |j                  d«      t        t        f«      sd|d<   t	        |«      |k  r|j                  dd«       |S c c}w c c}w )zDAdd the bounded on-water coverage end to a live presentation result.r›  rš  r©  Nr™  ÚmaxFinishSec)	rÜ   r7   rÛ   r�   r9   rO   r   r™   r  )rÏ   r¥  r2  r‘   r“   ÚexpectedÚpresentation_ends          r&   Ú_wire_resultz_OnAirRace._wire_result`  sØ   € ä�6‹lˆØ #§¡¨Ó 4Ò :¸ÖU˜¼zÈ#ÌtÕ?T’ÐUˆÐUÜ�s—w‘wÐ2Ó3ÒF´s¸4¿:¹:³ÓGˆÜ.Ø-1Ö2 cˆS�W‰W�[Õ!Ò2°Hó>ÐàÐ'Ø(8ˆCÐ$Ò%Ü˜CŸG™GÐ$8Ó9¼CÄ¸<ÔHØ(,ˆCÐ$Ñ%Üˆt‹9�xÒà�G‰G�N DÔ)Øˆ
ùò Vùò 3s   £C-¹C-Á9C2c                ó\  — |j                  d«      }t        |t        t        f«      rt	        j
                  t        |«      «      s|j                  d«      }t        |t        t        f«      xr? t	        j
                  t        |«      «      xr | j                  t        z
  t        |«      k\  S )uö  Whether the delayed programme picture has reached this result.

        Scoring and the race plane operate at the live telemetry frontier, while the renderer shows
        ``frontier - PRESENTATION_LAG_S``. Publishing the result document as soon as the server
        closed the race let consumers that only inspected ``feed.result`` put a winner over an
        earlier picture â€” in production, over the first visible finisher even though all four
        finish signals were correctly recorded. Keep the result off the wire until the same delayed
        cursor has reached the selected finish instant. Internal hand-over logic still receives the
        frontier result through :meth:`finish_progress`, so this changes presentation timing only.
        r™  r˜  )r7   rÛ   r�   r™   r.  r/  r2  r   )rÏ   r¥  Úats      r&   Ú_wire_result_duez_OnAirRace._wire_result_duep  s…   € ð �Z‰ZÐ,Ó-ˆÜ˜"œs¤E˜lÔ+´4·=±=ÄÀrÃÔ3KØ—‘˜KÓ(ˆBÜ˜2¤¤U˜|Ó,ò O´·±¼uÀR»yÓ1Iò OØ×,Ñ,Ô/AÑAÄUÈ2ÃYÑNð	Pr(   c                ó  — d}| j                  «       }|ri | j                  ¥|¥| _        | j                  |«      }|�|| j                  k7  r
||c| _        }| j                  �µ| j                  «       }t        | j                  d   «      }	 | j	                  | j                  | j                  t        ||t        z
  «      ||«      }|€%| j                  j                  | j                  «      n)t        || j                  t        | j                  «      «      }n%| j                  j                  | j                  «      }t        | j                  «      }	|j+                  «       D �
���ci c]/  \  }
}|
|j+                  «       D ��ci c]  \  }}||	v sŒ||“Œ c}}“Œ1 }}}}
}|j+                  «       D �
�ci c]  \  }
}|sŒ	|
|“Œ }}
}t-        |t        ¬«      }| j/                  «       }|D �]j  }
| j1                  |
||
   «      }|€Œ|\  }}| j2                  ��| j                  d   |d	z  z   }| j3                  | j                  «      D �cg c]D  }|j4                  xs d
|k  r/|j6                  |j4                  |j                  |j8                  dœ‘ŒF c}| j:                  _        | j:                  j?                  ||«      }tA        |«      }|�| jB                  �|| jB                  d<   g }| jD                  �ž|�œ| jF                  �btI        | jF                  | j                  t        tK        jJ                  «       d	z  «      | jD                  jF                  jM                  «       «      n	t        «       }| jD                  jO                  |d
   |d   |¬«      }| jQ                  |«      }| jR                  � | jR                  jU                  ||d   |«      n|d   }|
|d   k(  r| jW                  |«      |z   }| jX                  j[                  ||d   |||||
|d   k(  r|nd||j]                  d«      ¬«	       | j_                  |«       d}| ja                  «        �Œm | jc                  «       }|�{| je                  |«      }| jg                  |«      rYti        jj                  |dd¬«      }|| jl                  k7  r2|| _6        | jX                  jo                  |«       | ja                  «        | jq                  «       }|�rts        |«      }|| jt                  k7  rW|| _:        | jw                  |«       | jX                  jy                  | j                  | jz                  | j|                  i |¬«       yyy# t         $ rt}| j#                  d| j                  › dt%        |«      j&                  › dt)        |«      dd › d�«       | j                  j                  | j                  «      }Y d}~�Œ¤d}~ww xY wc c}}w c c}}}}
w c c}}
w c c}w )z…One poll: push every new second into the scorer and publish its delta, then refresh the
        boat-cam header if the clip set grew.Nr  r  z: track tail failed (rò   r}  u   ) â€” using live positionsrÚ  rg   r   ©ÚsignalÚtMsr#   r­   Úposr   )Úexclude_mmsire  rƒ  r^   ÚrieOnboardDirectives©r`  ÚmarksrÈ  ÚonboardT©ú,r�   ©Ú	sort_keysÚ
separatorsr–   )?r%  r#   r&  r  r  r  r�   rC   rO   rx   ÚTRACK_TAIL_WINDOW_MSr#  Ú	poll_oncer/  rá  rÌ   rõ   rÆ   rö   r÷   r   r•   rG   Ú_fresh_course_marksrb  r.  rJ   rÎ  r­   r)  Úrace_signalsÚ
push_framer=   ré  rè  rI   rR   r  Úall_targetsÚtargets_nearr^  r-  ÚtickrÃ  r  rc  r7   rˆ  rd  r§  rÈ  rË  ÚjsonÚdumpsr,  Úpublish_resultr4  r—   r3  r5  r6  r%   r$   )rÏ   rÈ  Ú
fresh_windÚ
wind_framerî   rK  r¨  Úbatchrù   ÚfleetrÞ  r:   rß  r1  ÚsecsrÔ  r¿   rg  rõ  rG  ÚdeltaÚcentroidÚais_listÚexclÚprogÚevtsÚscored_resultÚwire_resultÚ
result_keyrs   rd   s                                  r&   râ  z_OnAirRace.tick�  s“  € ð ˆØ×%Ñ%Ó'ˆ
Ùð 4˜4Ÿ9™9Ð3¨
Ð3ˆDŒIØ×%Ñ% jÓ1ˆ
ØÐ! j°D·J±JÒ&>Ø)¨:ÐˆDŒJ˜Ø×ÑÐ'Ø—,‘,“.ˆCÜ˜dŸi™iÐ(9Ñ:Ó;ˆKð	;Ø×&Ñ&Ø×$Ñ$ d§j¡jÜ˜ SÔ+?Ñ%?Ó@À#À{óT�ð @C¸{˜Ÿ™×/Ñ/°·
±
Ô;Ü)¨#¨t¯z©z¼3¸t¿z¹z»?ÓKñ ð —L‘L×*Ñ*¨4¯:©:Ó6ˆEô
 �D—J‘J“ˆà',§{¡{£}÷6ñ 6Ù#�S˜)ð °I·O±OÓ4E×V¡j c¨5ÈÐPUÊ�s˜E‘zÓVÑVð 6ˆó 6à6;·k±k³m×Q¡N C¨Ây��i‘ÐQˆÑQÜ�e¤Ô%ˆð ×(Ñ(Ó*ˆØó #	#ˆCØ×-Ñ-¨c°5¸±:Ó>ˆIØÐ ØØ"+ÑˆH�iØ× Ñ Ð,ØŸ™Ð#4Ñ5¸À4¹ÑG�ð "×.Ñ.¨t×/?Ñ/?Ó@ö,]àÀQÇVÁVÂ[ÈqÐU[ÒD[ð  !Ÿx™x°·±ÀÇÁÐRS×RZÑRZÓ[ò,]�—‘Ô(ð —K‘K×*Ñ*¨8°YÓ?ˆEÜ  Ó+ˆHØÐ#¨¯©Ð(?Ø%-�—‘˜EÑ"ØˆHØ�x‰xÐ#¨Ð(<ð EIÇJÁJÐDZô # 4§:¡:¨t×/?Ñ/?ÄÄTÇYÁYÃ[ÐSWÑEWÓAXØ#'§8¡8§>¡>×#=Ñ#=Ó#?ôAÜ`cÓ`eð àŸ8™8×0Ñ0°¸!±¸hÀq¹kÐX\Ð0Ó]�ð —=‘= Ó*ˆDà×'Ñ'Ð3ð ×$Ñ$×)Ñ)¨(°E¸+Ñ4FÈ	ÔRØ9>¸x¹ð à�d˜2‘hŠð ×6Ñ6°xÓ@À4ÑG�Ø�H‰H×"Ñ" 8¨U°;Ñ-?ÀÀyÐRZØ+/ÀÈÈRÉÂ¹ÐVZÐbfØ+0¯9©9Ð5KÓ+Lð #ô Nð ×#Ñ# DÔ)ØˆDØ× Ñ Ö"ðG#	#ðV ×.Ñ.Ó0ˆØÐ$Ø×+Ñ+¨MÓ:ˆKØ×$Ñ$ [Ô1Ü!ŸZ™Z¨¸tÐPZÔ[�
Ø ×!1Ñ!1Ò1Ø'1�DÔ$Ø—H‘H×+Ñ+¨KÔ8Ø×(Ñ(Ô*Ø×#Ñ#Ó%ˆØÐÜ˜UÓ#ˆCØ�d×*Ñ*Ò*Ø'*�Ô$Ø×"Ñ" 5Ô)Ø—‘×%Ñ% d§i¡i°·±¸d¿j¹jÈ"ÐTYÐ%ÕZð +ð øô_ ò ;Ø—‘˜w t×'7Ñ'7Ð&8ð 9!Ü!% c£×!3Ñ!3Ð 4°B´s¸3³xÀÀ°~Ð6FÐF`ðbô càŸ™×.Ñ.¨t¯z©zÓ:–ûð;üó$ Wùõ 6ùãQùò,]sK   ÂBU( Å&W.
ÆW(ÆW(ÆW.
Æ2
W6Æ=W6ÉA	W<Õ(	W%Õ1A)W × W%×(W.
c                ó   — t        | «      S )zRConvert feed metadata to the per-second live-wind shape, without inventing zeroes.)rÐ  ©r#   s    r&   r&  z_OnAirRace._wind_frameì  s   € ô   Ó%Ð%r(   c                ón  — | j                   €i S 	 t        | j                  «       xs i | j                  «      }d| _        |S # t        $ rj}t        |«      j                  › dt        |«      dd › �}|| j                  k7  r)| j                  d| j                  › d|› d�«       || _        i cY d}~S d}~ww xY w)zTRead usable current conditions; a wind-source fault is never a picture-source fault.Nrò   é    r  z: wind read failed (u   ) â€” holding wind)
r  rÉ  r%   r  rõ   rö   r÷   r   rÆ   rC   )rÏ   rÈ  rù   Úerrors       r&   r%  z_OnAirRace._fresh_windñ  s´   € à�>‰>Ð!ØˆIð		Ü)¨$¯.©.Ó*:Ò*@¸bÀ$Ç+Á+ÓNˆDØ#ˆDÔØˆKøÜò 	Ü˜C“y×)Ñ)Ð*¨"¬S°«X°d°s¨^Ð,<Ð=ˆEØ˜×(Ñ(Ò(Ø—‘˜w t×'7Ñ'7Ð&8Ð8LÈUÈGÐSeÐfÔgØ#(�Ô Ø�Iûð	ús   �0A Á	B4Á
AB/Â)B4Â/B4c                ó†  — | j                   €y	 | j                  «       xs i }t        |«      }|syi | j                  ¥|¥| _        d| _        |S # t        $ rh}t        |«      j                  › dt        |«      dd › �}|| j                  k7  r)| j                  d| j                  › d|› d�«       || _        Y d}~yd}~ww xY w)zQResolve current role positions, holding the initialized course on any read fault.Nrò   r÷  r  z: course read failed (u   ) â€” holding marks)
r  rE  r%   r  rõ   rö   r÷   r   rÆ   rC   )rÏ   r%   rÔ  rù   rø  s        r&   rÝ  z_OnAirRace._fresh_course_marks   sÐ   € à×ÑÐ#Øð	Ø×%Ñ%Ó'Ò-¨2ˆFÜ*¨6Ó2ˆEÙØð 4˜TŸ[™[Ð3¨FÐ3ˆDŒKØ!%ˆDÔØˆLøÜò 	Ü˜C“y×)Ñ)Ð*¨"¬S°«X°d°s¨^Ð,<Ð=ˆEØ˜×*Ñ*Ò*Ø—‘˜w t×'7Ñ'7Ð&8Ð8NÈuÈgÐUhÐiÔjØ%*�Ô"Üûð	ús   �!A ±A Á	C ÁAB;Â;C ):r  r
   rC   r   r<  r   rO   r  r#   rÜ   r%   rÜ   ry  r  r  r"  rä  r"  rå  r"  ræ  úCallable | Nonerç  rY  r}   rú  ré  r  rê  rú  rë  rú  rì  úCallable[[], dict] | Nonerí  rû  rî  ú
int | Noner$   úlist | Nonerï  rú  rÀ   rü   rð  rY  rñ  r"  rò  rY  ró  zCommentaryRotationLedger | Noner¿   rû   rõ  úCallable[[], int] | Nonerý   rþ   )rý   r  )rý   ztuple[int, int | None])rO   r  rE  rY  rý   r  )rs   r  rý   rþ   )rÞ  r�   rý   rÜ   )
rÜ  rÜ   re  rÜ   r?  r�   rf  r�   rý   rþ   )rÞ  z	int | strr:   rÜ   rý   ztuple[int, dict] | None)rý   r™   )rs  r   rý   rþ   )rƒ  rÞ   rý   rþ   ©rý   rþ   )r#   rÜ   r%   rÜ   r$   rý  rý   rY  rP  )r   r"  r¡  rü  rý   rþ   ©rý   rY  )rý   ztuple[dict | None, int]rÿ   )rÞ  r�   rý   ú
list[dict])r¥  rÜ   rý   rÜ   )r¥  rÜ   rý   rY  ©r#   rÜ   rý   r  )#r÷   r   r  r  r  r  r¿   rÐ   r4  rM  rF  r5  r^  r;  rb  rq  rv  rˆ  rd  r�  r£  r§  r[  r«  r²  r¹  r¼  rÃ  rÈ  rË  râ  r  r&  r%  rÝ  r.   r(   r&   rã  rã  M  sN  „ ñgð Y]Ø)-ÀtØ*.Ø-1À%Ø-1°tÐSWØ.2Ø/3Ø7;Ø9=Ø'+À$Ø-1ÐSXØ$(È$Ø/4ØHLØØ26·.±.Ø48ñ#DKØ!ðDKØ)-ðDKØ7;ðDKØJUðDKà&ðDKà:DðDKð (ðDKð +ð	DKð <@ð	DKð
 +ðDKð
 FQðDKð ,ðDKð -ðDKð 5ðDKð 7ðDKð %ðDKð 5@ðDKð +ðDKð <QðDKð "ðDKð >HðDKð )-ðDKð 'FðDKð  0ð!DKð" 2ð#DKð" >Bó#DKóL]ó4Yð KPõ ó"%óCóó2;óB8óTó"óH>óð2 KOØ59ð
Ø(2ð
Ø>Bó
ó6-ó^ó0Bóó
óAóó*ó Pó"i[ðV ò&ó ð&óôr(   rã  é   é   c                ót  — | j                  d«      r| j                  d«      sg S t        | j                  d«      xs g «      D ��cg c]9  \  }}t        |t        «      r$t	        |j                  d«      xs d|dz   › �«      ‘Œ; }}}dg|¢d‘}t        |«      dz
  }g }g }t        t        |d	d
 |dd	 d¬«      «      D ]Û  \  }\  }	}
|dk(  r|
dk(  rdddœ}n9|dk(  rd|
› d�d|
› d�dœ}n&|
dk(  rd|	› d�d|	› d�dœ}nd|	› d|
› d�d|	› d|
› d�dœ}t        |«      |t        z  z   }t        ||z  d«      t        |dz   |z  d«      g}|d|d   |dddg d |t        z   d|d!œ}|dk(  rd"|d#<   |j                  |«       |j                  |t        z  t        |d$œ«       ŒÝ ||d   d%<   |t        z  |d   d&<   |S c c}}w )'aW  A steady, chaptered route orientation for a genuine programme hold.

    Each travelled segment gets its own sentence and camera fraction. This deliberately uses the
    same course-guide contract as the pre-start walk; a single sentence over ``[0, 1]`` makes the
    camera sprint around the whole course and is not a usable orientation.
    r3  r5  r:  rè   r;  r   z	the startz
the finishNr^   T)Ústrictr   zMHere is the course again at a steady pace: from the start through the finish.u>   HÃ¤r Ã¤r banan igen i lugnt tempo: frÃ¥n starten genom mÃ¥let.)ÚenÚsvz=Here is the course again at a steady pace: from the start to ú.u6   HÃ¤r Ã¤r banan igen i lugnt tempo: frÃ¥n starten till zFrom z), the route continues through the finish.u   FrÃ¥n u     fortsÃ¤tter banan genom mÃ¥let.z, the route continues to u    fortsÃ¤tter banan till é   Úcourse_tourr  ÚfÚ	structureÚanalystz
fleet-wide)ri   rö   r|  Úi18nÚvoiceÚclaimÚcommentaryRoleÚfocusÚshotTypeÚfocusUntilSecÚcourseGuideÚ
courseFracr%   Úcam)ÚoffsetSÚ	durationSr  ÚcourseGuidePlanr¾  )r7   r@  rÛ   rÜ   r   r9   Úzipr�   ÚWAITING_COURSE_CHAPTER_Sr{   rw   )r%   Ú	start_secÚiÚwrÔ  ÚstopsÚnsegrƒ  Úplanr­   rç   ÚlinesÚ	event_secÚfractionr…  s                  r&   r¿  r¿    sX  € ð �:‰:�kÔ"¨&¯*©*°\Ô*BØˆ	ä" 6§:¡:¨kÓ#:Ò#@¸bÓA÷ZÙ��AÄZÐPQÔSWÔEXô �—‘�v“Ò- A a¨!¡e W +Õ.ð Z€Eñ ZàÐ/˜5Ð/ ,Ð/€EÜˆu‹:˜‰>€DØ€FØ€DÜ(¬¨U°3°B¨Z¸¸q¸r¸È4Ô)PÓQò !UÑˆÑˆF�FØ�Š6�f Ò,àeØVñ‰Eð �!ŠVàUÐV\ÐU]Ð]^Ð_ØNÈvÈhÐVWÐXñ‰Eð �|Ò#à˜f˜XÐ%NÐOØ˜v˜hÐ&FÐGñ‰Eð ˜f˜XÐ%>¸v¸hÀaÐHØ˜v˜hÐ&>¸v¸hÀaÐHñˆEô ˜	“N QÔ)AÑ%AÑAˆ	Ü˜!˜d™( AÓ&¬¨q°1©u¸©n¸aÓ(@ÐAˆà }¸eÀD¹kÐSXØ ;À)ÐVXØ$°yÔC[Ñ7[Ø¨xñ	
ˆð �Š6Ø#ˆE�%‰LØ�‰�eÔØ�‰ Ô$<Ñ <Ü":È(ñTõ 	UðA!UðD $(€Fˆ1�IÐÑ Ø)-Ô0HÑ)H€Fˆ1�IÐ%Ñ&Ø€MùóUZs   Á>F4c                  óx   — e Zd ZdZdddddedœ	 	 	 	 	 	 	 	 	 	 	 	 	 dd„Zdd„Zdd„Zdd„Zdd„Z	dd	„Z
d
dœdd„Zy)Ú_Waitingu  The WAITING presentation â€” what is ON AIR when no race is.

    A broadcast that has nothing to show must show that it has nothing to show: the venue, the boats
    that are out there, and `meta.idle` so the renderer runs its area-panning look-down camera instead
    of a race pose. Two failure modes this exists to prevent, both seen live:

      * a BLANK page while the operator gets ready (nothing published at all), and
      * a presentation that LIES â€” a placeholder gun makes the scene claim "RACE 11:30" with standings
        and commentary while the fleet is in fact still sitting on the line waiting for the start.

    No scorer runs here: there is no race to score. Positions still stream, so the fleet moves on
    screen and the camera has the venue to pan over.N)r|  rì  Únext_gun_msÚupcomingrô  rÀ   c       
   
     óp  — |||c| _         | _        | _        || _        || _        d | _        |j                  d«      xs  t        t        j                  «       dz  «      }i |¥dd||dz   |dz   ddœ¥| _	        i | j                  j                  d«      xs i ¥dt        «       i¥| j                  d<   t        | j                  j                  d	«      t        «      �sl| j                  j                  d
«      xs i }| j                  j                  d«      xs i j                  d«      xs g }g }|D ]d  }t        |t        «      r|j                  d«      sŒ%|j                  dD �ci c]%  }|j                  |«      �||j                  |«      “Œ' c}«       Œf | j                  j                  d«      xs | j                  j                  d«      xs d|j                  d«      xs ddg t        | j                  j                  d«      xs i «      t        | j                  j                  d«      xs i «      |dœ| j                  d	<   |	�Ëg }|	d d D ]Ÿ  }t        |t        «      sŒdD �ci c]%  }|j                  |«      �||j                  |«      “Œ' }}|j                  d«      xs g D �cg c]&  }t        |«      j                  «       rt        |«      ‘Œ( c}|d<   |sŒ�|j                  |«       Œ¡ i | j                  d	   ¥d|i¥| j                  d	<   |r|| j                  d<   || _        ddlm} ddlm}m} | j                  j                  d
«      xs i }|j-                  | j                  j                  d«      «      }|j-                  | j                  j                  d«      «      }|j                  d«      |j                  d«      f}|j.                  dkD  rT|j                  d «      rC|d   �>|d!   �9 |||j                  d «      ||j1                  «       |j                  d«      ¬"«      nd | _        || _        |
| _        |
�©|
j9                  | j                  d   d   g «       |j                  d «      xs g D �cg c]>  }t        |t        «      r,t        |j                  d#«      xs d«      j                  «       ‘Œ@ }}|D �cg c]  }|sŒd$|id%d&œ‘Œ }}|r|
j9                  |g «       t;        d'|j<                  «      | _        d | _         t;        dt        | j                  j                  d(«      xs d«      «      | _!        | jB                  xs d | _"        g | _#        d)| _$        d*| _%        dd+l&m'}   | | j                  j                  d,«      «      | _(        d | _)        |
�6| jB                  r*tU        | j                  d«      }!|!r|
j9                  |!g «       |jW                  | j                  ||i «        |d-| j2                  �	d.z   «       y dz   «       y c c}w c c}w c c}w c c}w c c}w )/Nr  rg   Tì    ,b/ é \&)ÚidleÚ	openEndedr  r}  rJ  ÚframeStepMsr	  r  ÚwaitingPresentationrT  ÚpartnerMarksrÔ  rè   )	r�  rè   Ú
assetLightÚ	assetDarkÚassetHoldingÚholdingRenderÚholdingKeyColourÚholdingKeyToleranceÚpanelÚ	eventNamer|  r¡   zWaiting for the next raceÚeventWaterMarkÚwaitingMusic©Ú
eventTitleÚlocationÚheadliner*  Ú	eventMarkÚmusicÚsponsorsé   )rœ  r|  r$   ÚscheduledStartUtcMsr$   r*  r   )ÚCruiseConfig©Ú	ExcursionÚExcursionConfigÚcruiserU  rV  Ú	tourSitesr   )ÚsitesÚcentreÚradius_mÚ	home_namerC  r  Úlead©r  r  r{  ÚwaitingCoursePresentationEverySr^   F)Úannounce_lead_msÚpreStartSecondszI[live] WAITING presentation on air - panning the area until a race startsz (guided excursions armed)),r  r%   r$   Ú_fetchr  r  r7   r�   r  r#   r   rÛ   rÜ   rw   r   r©   Ú_t0Úcruise_tourrF  Ú	excursionrH  rI  Ú	from_metaÚexcursion_every_lapsÚrecall_radius_mÚ_excÚ	_next_gunr  r'  rx   Úinterlude_sÚ_x_gap_sÚ_x_next_secÚ_course_guide_interval_sÚ_course_guide_due_secÚ_course_guide_pendingÚ_course_guide_active_untilÚ_course_guide_requestedÚ
race_queuerS  Ú_x_recall_msÚ_x_requestedr¿  r6  )"rÏ   r  r#   r%   r$   r  r|  rì  r)  r*  rô  rÀ   r@  rT  rÔ  rC  Úmarkrd   Úsafe_upcomingr‘   ÚitemrM   rF  rH  rI  ÚccfgÚxcfgrM  ÚxÚspokenÚlnrƒ  rS  rÂ  s"                                     r&   rÐ   z_Waiting.__init___  s×  € ð -0°¸Ð)ˆŒ�$”+˜tœzØ%ˆŒð "ˆŒØ"&ˆŒ
ð �X‰XÐ'Ó(ÒC¬C´·	±	³¸dÑ0BÓ,CˆðL�tð L T¸Ø(*¸Ð=MÑ8MØ&(¨:¡oÀdòLˆŒ	ð"U T§Y¡Y§]¡]°;Ó%?Ò%EÀ2ð "UØ"4Ô6QÓ6Sñ"Uˆ�	‰	�+Ñô ˜$Ÿ)™)Ÿ-™-Ð(=Ó>ÄÕEØ—I‘I—M‘M 'Ó*Ò0¨bˆEØ—Y‘Y—]‘] >Ó2Ò8°b×=Ñ=¸gÓFÒLÈ"ˆEØˆHØò g�Ü! $¬Ô-°T·X±X¸fÔ5EØØ—‘ð @Hö !f¸àKOÏ8É8ÐTWË=ÐKdð "% d§h¡h¨s£mÑ!3ò !fõ gðgð #Ÿi™iŸm™m¨KÓ8ÒX¸D¿I¹I¿M¹MÈ'Ó<RÒXÐVXØ!ŸI™I fÓ-Ò3°Ø7ØÜ! $§)¡)§-¡-Ð0@Ó"AÒ"GÀRÓHÜ˜dŸi™iŸm™m¨NÓ;ÒA¸rÓBØ$ñ0ˆD�I‰IÐ+Ñ,ð Ðð ˆMØ  �|ò 	/�Ü! #¤tÔ,Øð6Kö 5¨càŸ7™7 3›<Ð3ð ˜SŸW™W S›\Ñ)ð 5�ð 5ð 9=¿¹ÀÓ8IÒ8OÈRö !7¨tÜ$'¨£I§O¡OÔ$5ô "% T¥ò !7��W‘âØ!×(Ñ(¨Õ.ð	/ð0OØ—)‘)Ð1Ñ2ð0OØ4>Àñ0OˆD�I‰IÐ+Ñ,áØ!&ˆD�I‰I�gÑØˆŒõ 	-ß8Ø—	‘	—‘˜gÓ&Ò,¨"ˆØ×%Ñ% d§i¡i§m¡m°HÓ&=Ó>ˆØ×(Ñ(¨¯©¯©°xÓ)@ÓAˆØ—)‘)˜KÓ(¨%¯)©)°KÓ*@ÐAˆð ×2Ñ2°QÒ6¸5¿9¹9À[Ô;QØ# A™YÐ2°v¸a±yÐ7Lñ ˜t¨5¯9©9°[Ó+AÈ&Ø(,×(<Ñ(<Ó(>È%Ï)É)ÐTZÓJ[õ]ð TXð 	Œ	ð %ˆŒð ˆŒ	Øˆ?Ø�J‰J�t—y‘y Ñ-Ð.@ÑAÀ2ÔFØAFÇÁÈ;ÓAWÒA]Ð[]ö .¸1Ü# A¤tÔ,ô ˜!Ÿ%™% ›-Ò-¨2Ó.×4Ñ4Õ6ð .ˆFð .àOUÖ\ÈÒY[  b˜z¸VÓDÐ\ˆFÐ\ÙØ—
‘
˜6 2Ô&Ü˜B × 0Ñ 0Ó1ˆŒØ'+ˆÔÜ(+ØŒs�4—9‘9—=‘=Ð!BÓCÒHÀqÓIó)KˆÔ%à%)×%BÑ%BÒ%JÀdˆÔ"Ø13ˆÔ"Ø*,ˆÔ'Ø',ˆÔ$õ 	0Ù,¨T¯Y©Y¯]©]Ð;LÓ-MÓNˆÔð
 *.ˆÔØˆ?˜t×<Ò<Ü)¨$¯+©+°qÓ9ˆEÙØ—
‘
˜5 "Ô%Ø×Ñ˜Ÿ™ F¨E°2Ô6ÙÐZØ26·)±)Ð2GÐ.ñQõ 	RØMOñQõ 	Rùòq!fùò&5ùò!7ùòF.ùâ\s%   Å$*X
É(*X$Ê++X)ÒAX.ÓX3Ó	X3c                óÒ  — t        t        j                  «       dz  «      }t        d|| j                  z
  dz  «      }| j	                  «       xs i }d }| j
                  �Nt        | j                  «       xs i | j                  «      }t        |«      }|r|| j                  k7  r
||c| _	        }| j                  t        |«      «      }| j                  t        |«      «      }||z   }	| j                  �|r| j                  j                  |g «       | j                  j                  t        |«      d |	|g t!        | j"                  d   | j                  t        |«      d¬«      |¬«       y )Nrg   r   r}  F©rY  rZ  rÞ  r[  )r`  rÈ  )r�   r  rx   rV  rU  r  rÉ  r%   rÐ  r  Ú_course_guide_beatsÚ_excursion_beatsr  r'  r  rc  r   r#   )
rÏ   rõ  rÞ  r:   rÈ  rà  r   Úguide_beatsÚexcursion_beatsrƒ  s
             r&   râ  z_Waiting.tickÞ  s6  € Ü”T—Y‘Y“[ 4Ñ'Ó(ˆÜ�!�f˜tŸx™xÑ'¨DÑ0Ó1ˆØ—K‘K“MÒ' Rˆ	ð ˆØ�>‰>Ð%Ü*¨4¯>©>Ó+;Ò+A¸rÀ4Ç;Á;ÓOˆEÜ  Ó'ˆAÙ�Q˜$Ÿ*™*’_Ø#$ aÐ �”
˜DØ×.Ñ.¬s°3«xÓ8ˆØ×/Ñ/´°C³Ó9ˆØ  ;Ñ.ˆØ�9‰9Ð ¡[Ø�I‰I×Ñ˜[¨"Ô-Ø�‰×Ñœs 3›x¨¨v°yÀ"Ü'4ÀÇ	Á	È*Ñ@UØHLÏÉÔVYÐZ]ÓV^Ø>Cô(Eð %)ð	 	õ 	*r(   c                óÈ   — | j                   €y	 | j                  «       }|d uxr0 |t        t        j                  «       dz  «      z
  | j                  k  S # t        $ r Y yw xY w)NFrg   )r]  rõ   r�   r  rg  )rÏ   Úarmeds     r&   Ú_start_is_closingz_Waiting._start_is_closingõ  sf   € Ø�>‰>Ð!Øð	Ø—N‘NÓ$ˆEð ˜DÐ Ò[ e¬c´$·)±)³+ÀÑ2DÓ.EÑ&EÈ$×J[ÑJ[Ñ%[Ð[øô ò 	Ùð	ús   �A Á	A!Á A!c                óÎ  — | j                   sg S | j                  rK| j                  D �cg c]  }|d   |k  sŒ|‘Œ }}| j                  D �cg c]  }|d   |kD  r|‘Œ c}| _        |S || j                  k  s|| j                   k  rg S | j                  «       rg S d| _        | j
                  �3| j
                  j                  r| j
                  j                  |«       g S t        | j                  |«      }|s|| j                  z   | _         d| _        g S d| _        |dd | _        |t        |d   d   «      z   t        z   | _        || j                  z   | _         |dd S c c}w c c}w )zTRelease one paced course chapter at a time, about every configured waiting interval.ri   TNFr   r   r¾  )rb  rc  rd  ry  re  r\  ÚflyingÚrecallr¿  r%   ra  r�   rÀ  rÁ  s        r&   rs  z_Waiting._course_guide_beatsþ  sp  € à×)Ò)ØˆIØ×%Ò%Ø(,×(BÑ(BÖ[˜uÀeÈFÁmÐWZÓFZ’UÐ[ˆEÐ[Ø=A×=WÑ=Wö *B°EØ-2°6©]¸SÒ-@ò +0ò *BˆDÔ&àˆLØ�×0Ñ0Ò0°C¸$×:TÑ:TÒ4TØˆIØ×!Ñ!Ô#ØˆIØ'+ˆÔ$Ø�9‰9Ð  T§Y¡Y×%5Ò%5Ø�I‰I×Ñ˜SÔ!ØˆIÜ% d§k¡k°3Ó7ˆÙØ),¨t×/LÑ/LÑ)LˆDÔ&Ø+0ˆDÔ(ØˆIØ',ˆÔ$Ø%*¨1¨2 YˆÔ"à”#�e˜A‘hÐ6Ñ7Ó8Ñ8Ô;TÑTð 	Ô'à%(¨4×+HÑ+HÑ%HˆÔ"Ø�R�aˆyÐùò- \ùò*Bs   ©E·EÁE"c                ól  — | j                   €g S | j                  «       }| j                  dc}| _        |du r| j                   j                  |«       |r| j                   j                  |«       | j                   j                  rG| j                   j                  |«      }| j                   j                  r|| j                  z   | _        |S |sæ| j                  s|| j                  k  rg S |du rW| j                   j                  «       r=d| _        | j                   j                  |«       | j                   j                  |«      S | j                  €|| j                  z   | _        g S || j                  k\  r=d| _        | j                   j                  |«       | j                   j                  |«      S g S )a{  This second's tour beats, or nothing. An ARMED race (any future gun on the plane) is
        the recall: the running flight turns for home and no new one begins. In the following live
        producer that same ARM immediately replaces this state with the venue/course opening; this
        graceful return remains useful to any caller that can observe the future gun in advance.NFT)r\  ry  rh  r|  r{  râ  Údoner_  r`  re  rd  Ú	availableÚbegin)rÏ   rÞ  ÚclosingÚrequestÚbeatss        r&   rt  z_Waiting._excursion_beats  sn  € ð
 �9‰9ÐØˆIð ×(Ñ(Ó*ˆØ%)×%6Ñ%6¸Ð"ˆ�Ô"Ø�eÑØ�I‰I×Ñ˜SÔ!ÙØ�I‰I×Ñ˜SÔ!Ø�9‰9×ÒØ—I‘I—N‘N 3Ó'ˆEØ�y‰y�~Š~Ø#&¨¯©Ñ#6�Ô ØˆLÙð ×+Ò+¨s°T×5TÑ5TÒ/TØ�	ð ˜$‰ 4§9¡9×#6Ñ#6Ô#8Ø#'�Ô Ø—	‘	—‘ Ô$Ø—y‘y—~‘~ cÓ*Ð*Ø×ÑÐ'Ø#&¨¯©Ñ#6�Ô ð
 ˆ	ð	 ˜×(Ñ(Ò(Ø#'�Ô Ø—	‘	—‘ Ô$Ø—y‘y—~‘~ cÓ*Ð*Øˆ	r(   c                óð   — | j                   dut        | j                   xr | j                   j                  «      t        | j                   xr | j                   j                  «       «      | j                  dœS )zøWhat the operator's button needs to know: whether a flight is possible, and what is
        happening now. `available` false with `armed` true is the honest "not while a start is
        closing" rather than a button that does nothing when pressed.N)rx  r{  r  r·  )r\  rY  r{  r  rh  rp  s    r&   Úexcursion_statez_Waiting.excursion_stateC  s\   € ð
 —Y‘Y dÐ*Ü˜4Ÿ9™9Ò9¨¯©×)9Ñ)9Ó:Ü˜dŸi™iÒA¨D¯I©I×,?Ñ,?Ó,AÓBØ×*Ñ*ñ	
ð 	
r(   T)Úflyc               óŠ   — | j                   €ddi| j                  «       ¥S t        |«      | _        ddi| j                  «       ¥S )u£  Ask for a flight (or ask one home). Honoured on the next tick â€” see `_x_requested`.

        This is a PRESENTATION control and nothing else: it writes no gun, no stop and no result, so
        the broadcast is still only presenting. It exists because the alternative was republishing an
        event and restarting a producer, which is minutes, and which then arms every qualifying gap
        rather than this one.r¶  Úunarmedr»  )r\  r…  rY  rh  )rÏ   r†  s     r&   Úrequest_excursionz_Waiting.request_excursionN  sL   € ð �9‰9ÐØ˜iÐB¨4×+?Ñ+?Ó+AÐBÐBÜ  ›IˆÔØ˜(Ð= d×&:Ñ&:Ó&<Ð=Ð=r(   )r  r
   r#   rÜ   r%   rÜ   r$   rÞ   r  úCallable[[], dict]r|  r"  rì  rû  r)  zCallable[[], int | None] | Noner*  úlist[dict] | NonerÀ   rü   rý   rþ   rÿ  r   )rÞ  r�   rý   rÞ   rÿ   )r†  rY  rý   rÜ   )r÷   r   r  r  r  rÐ   râ  ry  rs  rt  r…  r‰  r.   r(   r&   r(  r(  Q  s™   „ ñ8ð KOØ7;Ø@DØ/3ØØ16ñ}RØ"4ð}RØ=Gð}Rà4ð}Rð >ð}Rð -ð	}Rð /ð}Rð <@ó}Ró~*ó.\óó:%óP	
ð 04ö 
>r(   r(  iÀÔ i@w zStart VesselzFinish Vessel)rí   r4  c                ót   — ddl m}  || «      }|r|j                  d«      d   S | j                  dd«      d   S )un  The hull/sail NUMBER from a boat slot ref â€” the segment after `boat:`, so a MOUNTED slot
    (`owner:boat:7:Mast`) reads `7`, not its mount. The last colon-segment of a 4-part slot is the
    mount, not the number, so a naive `rsplit(':')[-1]` named the boat "Mast"; `boat_of_slot` is the
    canonical boat-portion helper (`owner:boat:7:Mast` â†’ `owner:boat:7`).r   )Úboat_of_slotr�   r^   r   )r€   r�  rw  rv   )Úrefr�  Úbos      r&   Ú_boat_numberr�  n  s;   € õ
 #Ù	�cÓ	€BÙ "ˆ2�8‰8�C‹=˜ÑÐ>¨¯
©
°3¸Ó(:¸2Ñ(>Ð>r(   c                óX  — | �&t        | d«      r| j                  t        |«      «      ni }ddlm} g }t        |«      D ]i  }t        |«      }|j                  |«      } ||�"t        |dd«      rt        |j                  «      n|«      }|j                  d|› �||||dddœgd	œ«       Œk |S )
u  The cruise picture's roster: one team-less entry per hull, NAMED BY THE BOAT ITSELF.

    The name is `BoatMeta.name` â€” what the registration QR's `nm` field said, the boat's own name,
    which exists with no race whatsoever â€” falling back to the hull number (the waiting picture's
    labelling). Never a team: a cruise has no teams, and asserting one would be a line-up for a
    race that does not exist.

    NOT `spectator`, unlike the waiting picture's hulls: the renderer deliberately excludes
    spectators from the camera's position map (`scene.js` `racingByDev` â€” a spectator is a boat in
    somebody ELSE's race), so a spectator can never be framed. On a cruise the boats are the whole
    subject â€” the visit beat's focus must be able to land on them.NÚboat_meta_for_manyr   )Úboat_display_namerè   úhull-ú#8899aa)rÕ  r£  ÚvisualIdÚcolorÚsternM©rÓ  rè   rO   )Úhasattrr’  rÞ   rW  r“  rG   r�  r7   rq   r   rè   rw   )	rI   rO   rQ   r“  r$   rN   ÚhullrP   rè   s	            r&   Ú_cruise_teamsrœ  x  sÈ   € ð Ð!¤g¨eÐ5IÔ&Jð ×%Ñ%¤d¨5£kÔ2ØPRð 
å-Ø€EÜ�E‹]ò 	EˆÜ˜A‹ˆØ�I‰I�a‹Lˆñ !Ø˜=¬W°Q¸ÀÔ-EŒC�—‘ŒKÈ4óQˆà�‰ %¨ v ¸Ø-.¸$ÈAØ*3¸qñ!Bð  CñDõ 	Eð	Eð €Lr(   c                  ó~   — e Zd ZdZdddddedœ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dd„Zdd„Zdd„Zdd„Zdd„Z	dd	„Z
dd
„Zy)Ú_Cruiseus  The CRUISE presentation â€” the recreational device tour. No race, no scorer, no standings.

    What is on air: the venue under the idle pan, every device visited one by one with a fly-in
    and its own camera as a large panel, only the device's NAME presented (plus the verification
    strip â€” the mode exists so an operator can confirm every device works and nothing is mixed
    up). The schedule itself is `cruise_tour.TourScheduler` (pure, tested); this class is the
    wiring: store reads in, `publish_init`/`publish_delta` out, the same renderer contract as
    every other presentation.

    Truthfulness: `onboardTiles` is stripped (a cruise INVENTS no footage), the course is
    published marks-only (no start, no finish, no competition area â€” there is none), and a device
    with no fresh clip is visited in 3D with its name and ages, never a substitute picture.N)r|  r}   rì  rõ  rô  rÀ   c               óž
  — ddl m}m} ddlm}m} ||c| _        | _        ||c| _        | _	        t        |«      | _        |	| _        |
| _        d | _        |xs d„ | _        |j!                  d«      xs | j                  «       }i |¥ddd||dz   |d	z   d
dœ¥| _        | j"                  j%                  dd «       |r|| j"                  d<   || _        |xs i j)                  «       D ��ci c]  \  }}|dvr||“Œ c}}| _        t-        | j"                  j!                  d«      xs i «      | _        t1        | j                  «      xs+ t3        d„ | j.                  j5                  «       D «       d «      }|�|rt7        |d«      r|j9                  |«      ng }t        t;        |«      t;        | j.                  j5                  «       «      z
  «      | _        |�t7        |d«      r|j?                  «       ng }t        t;        | j                  «      |D �ch c]  }|sŒ|jA                  |› d�«      sŒ|’Œ c}z  «      | _        | j                  t        | j.                  j5                  «       «      z   | j<                  z   | _!        tE        || j                  «      | _#        | j"                  j!                  d«      xs i }|j!                  d«      |j!                  d«      f}|d   �W|d   �R ||jI                  | j"                  j!                  d«      «      |j!                  d«      ||j!                  d«      ¬«      nd | _         ||jI                  | j"                  j!                  d«      «      |j!                  d«      |j!                  d«      | j                  ¬«      | _%        || _&        ddl'm(}  ||«      D ���cg c]  \  }}}|||dœ‘Œ c}}}| _)        | jU                  |j!                  d«      |j!                  d«      «       |�tW        ||«      nd | _,        d| _-        |j!                  d«      | _.        ddl/m0} |� ||| jB                  d ¬!«      nd"„ | _1        i | _2        d#| _3        ti        «       | _5        | jm                  | j                  «       d$¬%«       |jo                  | j"                  | j*                  | jF                  i | jd                  ¬&«        |d'tq        | jB                  «      › d(tq        | j                  «      › d)tq        | j.                  «      › d*tq        | j<                  «      › d+|xs d,› �
«       y c c}}w c c}w c c}}}w )-Nr   )rF  ÚTourSchedulerrG  c                 óB   — t        t        j                  «       dz  «      S rø  rù  r.   r(   r&   rc   z"_Cruise.__init__.<locals>.<lambda>´  rú  r(   r  TrJ  r,  r-  rg   )r.  r/  ÚpresentationModer  r}  rJ  r0  r�  r|  )r3  r5  r<  r>  rC  c              3  óF   K  — | ]  }|j                  d d«      d   –— Œ y­w)r�   r   r   N)rw  ©r/   rG  s     r&   r2   z#_Cruise.__init__.<locals>.<genexpr>Ì  s   è ø€ Ò? AˆQ�W‰W�S˜!‹_˜QÕÑ?ùrÙ   Ú
buoy_slotsÚknown_boatsz:boat:rT  rU  rV  r   rK  rè   )rL  rM  rO  Ú	landmarks)r§  Ú
venue_namerX  ©Úvenue_speech_names©rè   ÚnameLangÚipa©Úv3_fetch_positionsr  ©r  c                 ó   — i S r-   r.   r.   r(   r&   rc   z"_Cruise.__init__.<locals>.<lambda>   ó   € ¸"€ r(   r¡   F©Úpublishr–   z%[live] CRUISE tour on air - visiting z
 devices (z boats, z line phones, z marks) on z	no course)9rW  rF  r   rX  rH  rI  rI   r  rC   r  rG   rO   r}   r  r  r  r7   r#   r  rV  r•   r%   rÜ   rH  rt  rŸ  r6   rš  r¥  rÌ   rÔ  r¦  Ú
startswithÚ	tour_refsrœ  r$   rY  Úschedr  rf  rª  Ú_speech_teamsÚ_submit_tour_linesrR  Ú	_event_idÚ_sites_next_msÚ_sites_lastr"  r¯  rU  Ú_mediar3  Ú	frozensetÚ_cruise_keyÚ_refresh_mediar6  r9   ) rÏ   rI   r  rC   r  r#   r%   rO   r|  r}   rì  rõ  rô  rÀ   rF  r   rH  rI  r@  rÕ   rÖ   rq  ÚbuoysÚknownrN   rT  rM  rª  ÚnÚlgr­  r¯  s                                    r&   rÐ   z_Cruise.__init__£  sË  € ÷ 	<ß8Ø$ cÐˆŒ
�D”HØ+6¸	Ð(ˆÔ˜$œ.Ü˜E“]ˆŒ
Ø ˆŒØ!ˆŒØ"&ˆŒ
ð ÒBÑ"AˆŒØ�X‰XÐ'Ó(Ò:¨D¯L©L«NˆðL�tð L T¸ÐRZØ(*¸Ð=MÑ8MØ&(¨:¡oÀdòLˆŒ	ð
 	�	‰	�‰�n dÔ+ÙØ!&ˆD�I‰I�gÑØˆŒð *0ª°2×(<Ñ(<Ó(>÷ `¡  1ØÐ#^Ñ^ð ˜!‘tó `ˆŒô (,¨D¯I©I¯M©M¸/Ó,JÒ,PÈbÓ'QˆŒô ˜$Ÿ*™*Ó%ò G¬Ù?¨¯©×)<Ñ)<Ó)>Ô?Àó*Gˆà,1Ð,=Á%Ü˜U LÔ1ð ×!Ñ! %Ô(Ø79ð 	äœC ›J¬¨T¯\©\×-@Ñ-@Ó-BÓ)CÑCÓDˆŒ
ð ).Ð(9Ü˜U MÔ2ð ×"Ñ"Ô$Ø8:ð 	äœC §
¡
›OØ*/Ö\ Q²5¸Q¿\¹\ÈUÈGÐSYÐJZÕ=[šqÒ\ñ]ó ^ˆŒ
à$(§J¡J´¸¿¹×8KÑ8KÓ8MÓ1NÑ$NÐQU×Q[ÑQ[Ñ$[ˆŒÜ" 5¨$¯*©*Ó5ˆŒ
Ø—	‘	—‘˜gÓ&Ò,¨"ˆð —)‘)˜KÓ(¨%¯)©)°KÓ*@ÐAˆð $ A™YÐ2°v¸a±yÐ7Lñ $ O×$=Ñ$=¸d¿i¹i¿m¹mÈHÓ>UÓ$VØ*/¯)©)°KÓ*@ÈØ.3¯i©i¸Ó.?õAð SWð 	Œñ # <×#9Ñ#9¸$¿)¹)¿-¹-ÈÓ:QÓ#RØ-2¯Y©Y°{Ó-CØ.3¯i©i¸Ó.?Ø-1¯^©^ô=ˆŒ
ð ˆŒ	õ
 	4á0BÀ5Ó0I÷Kð KÙ", ! R¨ð ()°bÀÓEô KˆÔà×Ñ §	¡	¨+Ó 6¸¿	¹	À+Ó8NÔOð #Ð.ô 0°°{ÔCØ48ð 	ŒàˆÔØ Ÿ9™9 [Ó1ˆÔÝ1àÐ+ñ *¨%°·±ÈFÕSÙ2<ð 	ŒàˆŒØ!ˆÔô '0£kˆÔØ×Ñ˜DŸL™L›N°EÐÔ:Ø×Ñ˜Ÿ™ D§K¡K°·±¸RÀtÇ{Á{ÐÔSÙÐ6´s¸4¿>¹>Ó7JÐ6Kð LÜ�t—z‘z“?Ð# 8¬C°·±Ó,=Ð+>¸nÜ�d—j‘j“/Ð" +¨iÒ.F¸;Ð-GðIõ 	JùóI`ùò( ]ùô:Ks   Ã'T=ÈU
ÈU
È*U
Î7Uc           
     ó`  — | j                   €y|D ��cg c]I  }|xs g D ]>  }t        |t        «      r,t        |j	                  d«      xs d«      j                  «       ‘Œ@ ŒK }}}|D �cg c]  }|sŒd|iddœ‘Œ }}|r'| j                   j                  || j                  «       yyc c}}w c c}w )u»   Queue every static spoken line (landmark ring + excursion catalogue) on the neural TTS
        cache â€” fire-and-forget; without a cache this is a no-op and the browser voices fall back.NrC  r¡   r  rP  rQ  )r  rÛ   rÜ   r   r7   r©   r'  r¸  )rÏ   Ú
line_listsÚlstrn  r$  rp  rƒ  s          r&   r¹  z_Cruise._submit_tour_lines  s±   € ð �9‰9ÐØØ=G÷ >°cØš9 "ò>Ø¬°A´tÔ)<ô �Q—U‘U˜6“]Ò( bÓ)×/Ñ/Õ1ð >Ð1ð >ˆñ >àKPÖWÀRÒTV˜D "˜:¸Ó@ÐWˆÐWÙØ�I‰I×Ñ˜V T×%7Ñ%7Õ8ð ùó>ùâWs   “AB%Á(B+Á0	B+c           	     ó"  — | j                   �'| j                  �| j                  �|| j                  k  ry|dz   | _        	 t	        | j                  | j                  i g ¬«      }|j                  d«      xs i }|j                  d«      }|| j                  k(  ry|| _        | j                   j                  |«      r?ddl	m
}  ||«      D ���cg c]  \  }}}|||dœ‘Œ c}}}| _        | j                  |«       yy# t        $ r Y yw xY wc c}}}w )	u×  Re-read the excursion catalogue from the event config (~every 30 s) and swap it BETWEEN
        excursions â€” the running route always finishes on the plan it took off with. A read that
        fails keeps the previous catalogue (hold-last); new lines go straight to the TTS cache so
        their clips exist before any route reaches them. This is what makes the catalogue live
        event data: publish a new site mid-day and the next excursion flies it, no restart.Nr  ©r#   rO   rT  rK  r   r©  r«  )rX  rI   rº  r»  ru  r7   rõ   r¼  Úupdate_sitesrf  rª  r¸  r¹  )	rÏ   rõ  rà  Úfresh_venuerL  rª  rÃ  rÄ  r­  s	            r&   Ú_refresh_tour_sitesz_Cruise._refresh_tour_sites  s  € ð �N‰NÐ" d§j¡jÐ&8¸D¿N¹NÐ<RØ˜D×/Ñ/Ò/ØØ$ v™oˆÔð	Ü& t§z¡z°4·>±>ÈÐRTÔUˆEØŸ)™) GÓ,Ò2°ˆKØ—O‘O KÓ0ˆEð �D×$Ñ$Ò$ØØ ˆÔØ�>‰>×&Ñ& uÔ-å7á4FÀ{Ó4S÷"Uð "UÙ&0 a¨¨Sð ,-¸"ÀSÓ"Iô "UˆDÔà×#Ñ# EÕ*ð .øô ò 	Ùð	üô"Us   Á A	C; ÃD
Ã;	DÄDc                óT  — | j                  «       }t        d|| j                  z
  dz  «      }| j                  |«       | j	                  «       xs i }| j
                  �| j                  «       xs i ni }d }t        |«      }|r|| j                  k7  r
||c| _        }| j                  |t        |«      |«      }|D �ci c]L  }|j                  dv r<|j                  �0|j                  �$|j                  |j                  |j                  dœ“ŒN }	}| j                  j                  t        |«      ||xs d «      \  }
}| j                   �b|
r`|
D �cg c]-  }|j#                  d«      rd|d   i|j#                  dd«      d	œ‘Œ/ }}|r&| j                   j%                  || j&                  «       | j(                  j+                  t        |«      d |
|g t-        | j.                  d
   | j                  t        |«      d¬«      |	xs d ||xs d ¬«	       | j1                  |d¬«       y c c}w c c}w )Nr   rg   )ri  Úvesselr-  r|  r  r  rP  rQ  r}  Frr  rÓ  Tr³  )r  rx   rV  rÌ  rU  r  rÐ  r  Ú_stopsr�   r~  r*   r+   rŽ  r·  râ  r  r7   r'  r¸  r  rc  r   r#   rÀ  )rÏ   rõ  rÞ  r:   rà  rÈ  r   r!  rG  Úmarks_deltarƒ  rÕ  Úero  s                 r&   râ  z_Cruise.tick3  s  € Ø—‘“ˆÜ�!�f˜tŸx™xÑ'¨DÑ0Ó1ˆØ× Ñ  Ô(Ø—K‘K“MÒ' Rˆ	à,0¯N©NÐ,F�—‘Ó!Ò'¡RÈBˆØˆÜ˜UÓ#ˆÙ��d—j‘j’Ø  !ÐˆDŒJ˜Ø—‘˜F¤C¨£H¨eÓ4ˆð EJö d¸qØŸ&™&Ð$6Ñ6¸1¿5¹5Ð;LÐQR×QVÑQVÐQbð —u‘u a§e¡e°A·E±EÑ:Ñ:ð dˆð dàŸ*™*Ÿ/™/¬#¨c«(°E¸5º=ÀDÓI‰ˆ�ð �9‰9Ð ¡Vð  &ö8à¨¯©¨v¬ð !% a¨¡iÐ0Ø)*¯©Ð/?ÀÓ)HóJð 8ˆFð 8ñ Ø—	‘	× Ñ  ¨×);Ñ);Ô<Ø�‰×Ñœs 3›x¨¨v°yÀ"Ü'4ÀÇ	Á	È*Ñ@UØHLÏÉÔVYÐZ]ÓV^Ø>Cô(Eð &1Ò%8°D¸tÈWÊ_ÐX\ð	 	ô 	^ð
 	×Ñ˜F¨DÐÕ1ùò'dùò8s   Â<AH Å2H%c                óâ  ‡— ddl m}m} ddlm} ddlm} | j                  �$ || j                  | j                  |t        ¬«      ni }| j                  D �	ci c]  }	|	d   d   d   |	d	   “Œ }
}	t        |j                  d
«      xs d«      }|r!|j                  «       j                  d«      s|ni }g }| j                  D �]'  Š|j                  ‰«      }‰| j                  v rdn‰| j                   j#                  «       v rdnd}|dk(  rDt%        ‰«      }|
j                  ‰«      xs  ||«      }|� ||j&                  «      ndxs d|› �}nit)        ˆfd„| j                   j+                  «       D «       d«      }|rt,        j                  |«      n‰j/                  dd«      d   xs ‰} |‰«      xs ‰}| j0                  j                  ‰«      xs g }t3        d„ |D «       d¬«      }|�t3        d||z
  «      nd}d}|�8t5        |j6                  t8        t:        f«      rt;        |j6                  «      dz  }|j=                   |‰t        |«      |||�|j>                  nd|�|j@                  nd|�|jB                  dz  nd|||dk(  r|j                  d«      nd|dk(  r|j                  d«      nd|�|dk(  r|jD                  nd|dk(  r|j                  d«      nd¬«      «       �Œ* |S c c}	w )uü   Every tour device as `cruise_tour.TourStop` â€” last-known fix (up to a day old, WITH its
        age), newest-clip age from the published media map, name and slot label. Built fresh each
        tick, so a device that wakes up joins the very next lap.r   )ÚTourStopr“  )Úslot_display_name©Úboat_positions_atNr°  rO   rÕ  rè   r¾  r¡   ú
open-meteor   rÎ  ri  zBoat c              3  ó4   •K  — | ]  \  }}|‰k(  sŒ|–— Œ y ­wr-   r.   ©r/   ÚrrG  rŽ  s      €r&   r2   z!_Cruise._stops.<locals>.<genexpr>s  s   øè ø€ ÒL¡4 1 aÀ1ÈÃ8œQÑLùó   ƒ‘r�   r   r^   c              3  ó&   K  — | ]	  }|d    –— Œ y­w)ri   Nr.   )r/   rÚ  s     r&   r2   z!_Cruise._stops.<locals>.<genexpr>y  s   è ø€ Ò2¨˜!˜F�)Ñ2ùr3   r’   rf   g™�Êø÷ÿ?rg   r¼  r½  )rŽ  rè   r~  Ú
slot_labelr*   r+   Ú	fix_age_sÚ
clip_age_srÃ  Úwind_from_degÚwind_ktsÚ
course_degÚvenue_wind_from_deg)#rW  rÓ  r“  Údevice_configrÔ  r9  rÖ  rI   r¶  ÚCRUISE_STOP_FIX_MAX_AGE_MSr$   r   r7   rª   rµ  rO   rH  r6   r�  Úsource_slotrŸ  r•   ÚCRUISE_VESSEL_NAMESrv   r½  rx   rÛ   Úspeedr�   r™   rw   ÚlatitudeÚ	longitudeÚ
fix_age_msr%   )rÏ   rõ  rÞ  Ú	wind_metarÓ  r“  rÔ  rÖ  ÚfixesrÖ  ÚnamesÚsrcÚ	mark_windr!  Úfixr~  Únumrè   r  r0  r“   ÚnewestÚclip_agerÃ  rŽ  s                           @r&   rÏ  z_Cruise._stopsY  sÌ  ø€ ÷ 	<Ý3Ý+ð —J‘JÐ*ñ # 4§:¡:¨t¯~©~¸vÜ.HõJà02ð 	ð @D¿z¹zÖJ¸!��7‘˜A‘˜zÑ*¨A¨f©IÑ5ÐJˆÐJô �)—-‘- Ó-Ò3°Ó4ˆÙ"%¨c¯i©i«k×.DÑ.DÀ\Ô.R‘YÐXZˆ	ØˆØ—>‘>ó #	aˆCØ—)‘)˜C“.ˆCØ! T§Z¡ZÑ/‘FØ%(¨D¯L©L×,?Ñ,?Ó,AÑ%A™Àvð à�vŠ~Ü" 3Ó'�Ø—y‘y “~Ò?Ñ):¸3Ó)?�Ø?B¸Ñ*¨3¯?©?Ô;ÐTXò %Ø˜s˜e�}ñ ô ÓL¨4¯<©<×+=Ñ+=Ó+?ÔLÈdÓS�ñ :>Ô+×/Ñ/°Ô5À3Ç:Á:ÈcÐSTÓCUÐVXÑCYÒaÐ^a�Ù)¨#Ó.Ò5°#�Ø—;‘;—?‘? 3Ó'Ò-¨2ˆDÜÑ2¨TÔ2¸DÔAˆFØ17Ð1C”s˜3  f¡Ô-ÈˆHØˆIØˆ¤:¨c¯i©i¼#¼u¸Ô#FÜ! #§)¡)Ó,¨wÑ6�	Ø�L‰L™Øœc $›i¨d¸uØ%( _�S—\’\¸$Ø&) o�S—]’]¸4Ø47°O˜3Ÿ>™>¨DÒ0ÈØ#¨yØ?CÀvº~˜yŸ}™}¨]Ô;ÐSWØ6:¸f²n˜)Ÿ-™-¨	Ô2È$ð +.¨/¸dÀfºn˜CŸJšJÐRVØEIÈVÂ^ Y§]¡]°=Ô%AÐY]ô`ö að+#	aðH ˆùòU Ks   ÁK,c           	     óÈ  ‡— ddl m} | j                  €g S t        | j                  j                  «       «      t        | j                  «      z   } || j                  ||t        ¬«      }g }|D ]r  Š|j                  ‰«      }|€Œt        ˆfd„| j                  j                  «       D «       d«      }|j                  ‰|rdnd||j                  |j                  dœ«       Œt |S )	u  The tour's non-hull devices as renderable SCENE objects: each robotic mark a bare buoy,
        each line phone its start/finish vessel, at its last-known fix. The fix is the STARTING
        point only â€” the per-tick `marks` delta glides each object to its live position. A fix-less
        device is omitted (there is nowhere to place it) and reappears when it reports, the same
        self-healing the media map already has. `role` is `start`/`finish` for a line phone (which
        boat model to draw) and `None` for a mark.r   rÕ  Nr°  c              3  ó4   •K  — | ]  \  }}|‰k(  sŒ|–— Œ y ­wr-   r.   rÙ  s      €r&   r2   z*_Cruise._cruise_objects.<locals>.<genexpr>   s   øè ø€ ÒH™t˜q !¸qÀC»xœÑHùrÛ  rÎ  ri  )rŽ  r~  r0  r*   r+   )r9  rÖ  rI   rÞ   rH  r6   rÔ  rå  r7   rŸ  r•   rw   ré  rê  )	rÏ   rõ  rÖ  Úrefsrí  Úobjectsrñ  r0  rŽ  s	           @r&   Ú_cruise_objectsz_Cruise._cruise_objects�  sÌ   ø€ õ 	,Ø�:‰:ÐØˆIÜ�D—L‘L×'Ñ'Ó)Ó*¬T°$·*±*Ó-=Ñ=ˆÙ! $§*¡*¨d°FÔGaÔbˆØ ˆØò 	VˆCØ—)‘)˜C“.ˆCØˆ{ØÜÓH t§|¡|×'9Ñ'9Ó';ÔHÈ$ÓOˆDØ�N‰N 3¹D±ÀfØ$(°·±ÀcÇmÁmñUõ Vð	Vð ˆr(   c               óH  — | j                   €y| j                  }| j                  �It        | j                   | j                  | j
                  | j                  t        d|t        z
  «      ¬«      }t        |«      }| j                  |«      }t        d„ |D «       «      }|| j                  k(  r|| j                  k(  ry||c| _        | _        ||c| j                  d<   | _        |r?| j                  j                  | j                  | j                   | j"                  i |¬«       yy)u  Refresh the two things that make a cruise init go stale between the fixed header and the
        next lap: the rolling clip map (EVERY tour device â€” `slots_of_boat` resolves a buoy slot to
        itself) and the set of standalone device OBJECTS in the scene (a mark or line phone that just
        woke). Republished as an `init` only when one of them actually changes â€” the `_OnAirRace`
        rule. The objects are refreshed even with no clip signer, so a lineup with no clips still
        draws its buoys and boats.Nr   )r}   rT   c              3  ó&   K  — | ]	  }|d    –— Œ y­w)rŽ  Nr.   )r/   Úos     r&   r2   z)_Cruise._refresh_media.<locals>.<genexpr>´  s   è ø€ Ò6¨˜A˜e�HÑ6ùr3   ÚcruiseObjectsr–   )rI   r½  r}   r‰   r¶  rV  rx   ÚCRUISE_MEDIA_WINDOW_MSr—   rù  r¾  r3  r¿  r#   r  r6  r%   r$   )rÏ   rõ  r´  rs   Ú	media_keyrø  Úobj_keys          r&   rÀ  z_Cruise._refresh_media¥  sý   € ð �:‰:ÐØØ—‘ˆØ�=‰=Ð$Ü$ T§Z¡Z°·±ÀÇÁÐTX×TaÑTaÜ.1°!°VÔ>TÑ5TÓ.UôWˆEä˜uÓ%ˆ	Ø×&Ñ& vÓ.ˆÜÑ6¨gÔ6Ó6ˆØ˜×,Ñ,Ò,°¸D×<LÑ<LÒ1LØØ,1°9Ð)ˆŒ�TÔ)Ø7>ÀÐ4ˆ�	‰	�/Ñ" DÔ$4ÙØ�H‰H×!Ñ! $§)¡)¨T¯[©[¸$¿*¹*ÀbÐPUÐ!ÕVð r(   )r  r
   rC   r   r  r"  r#   rÜ   r%   rÜ   rO   r  r|  r"  r}   rú  rì  rû  rõ  rþ  rÀ   rü   rý   rþ   rÿ  )rõ  r�   rý   rþ   )rõ  r�   rÞ  r�   rì  rÜ   rý   rÞ   )rõ  r�   rý   r  )rõ  r�   r´  rY  rý   rþ   )r÷   r   r  r  r  rÐ   r¹  rÌ  râ  rÏ  rù  rÀ  r.   r(   r&   rž  rž  •  s±   „ ñ_ð &*ÀtØ7;Ø48ØØ16ñgJØ&ðgJØ.2ðgJØ<@ðgJØIRðgJà"ðgJà5DðgJð 5ðgJð 2ð	gJð /ðgJð <@ógJóR	9ó+ó8"2óL4ólô,Wr(   rž  ç      ð?)	r|  r}   rì  rõ  rô  rš   Ú	max_pollsÚsleeprÀ   c               ó¢   — t        | |||||||||	|
||¬«      }d}|�||k  r.|dz  }|j                  «        |�||k  r ||«       |€Œ'||k  rŒ-yy)u±   Publish the cruise tour forever (or for `max_polls` bounded ticks â€” the testable form,
    `broadcast_live`'s shape). One `_Cruise` for the session: the tour loops on its own.)rC   r  r#   r%   rO   r|  r}   rì  rõ  rô  rÀ   r   Nr   )rž  râ  )rI   r  rC   r  r#   r%   rO   r|  r}   rì  rõ  rô  rš   r  r  rÀ   rJ  Úpollss                     r&   Úbroadcast_cruiser  ½  sv   € ô* �U˜C¨[ÀIÐTXØ"¨%°uÀxØ&¨v¸3ÀvôO€Fð €EØ
Ð
˜u yÒ0Ø�‰
ˆØ�‰ŒØÐ ¨	Ò 1Ù�*Ôð	 Ñ
˜u yÕ0r(   c                ób   — t        | dd«      }|€t        t        | dd«      «      S t        |«      S )u  Whether to prepare neural clips: what `--neural-tts/--no-neural-tts` said, and ON by default
    for a live broadcast (see the argument's own note). An unset flag is `None`, which is why the
    default cannot live in `add_argument` â€” it depends on `--live`.Ú
neural_ttsNÚliveF)rq   rY  )râ   Úwants     r&   Ú_neural_tts_wantedr  Ý  s3   € ô �4˜ tÓ,€DØ15°Œ4”˜˜f eÓ,Ó-ÐMÄ4ÈÃ:ÐMr(   c                ó€   — | sy	 ddl m}  ||¬«      } |d«       |S # t        $ r} |d|› d�«       Y d}~yd}~ww xY w)uz  The ahead-of-air neural voice cache, or None. Imported LAZILY and behind a broad except: this
    is the one component whose absence must cost the VOICE and never the picture, so a missing
    library, missing credentials or a disabled API all degrade to the renderer's own fallback with a
    line in the log â€” the same contract the cache itself keeps for a failed synthesis.Nr   )ÚLiveNeuralTtsCache©rÀ   z?[live] neural TTS ON - lines are prepared as clips ahead of airz[live] neural TTS unavailable (z$) - the renderer keeps its own voice)Útts.live_cacher  rõ   )ÚenabledrÀ   r  Úcacherù   s        r&   Ú_neural_ttsr  å  sP   € ñ
 ØðÝ5Ù"¨&Ô1ˆÙÐPÔQØˆøÜò ÙÐ0°°Ð5YÐZÔ[Üûðús   … ž	=§8¸=c                ó   ‡ ‡— dˆˆ fd„}|S )u°   The cruise's conditions read â€” the course's resolved wind in feed-meta shape, `{}` when
    there is nothing usable (the block reads an em dash, never an invented northerly).c                 óþ   •— ‰�‰si S ddl m}   | ‰j                  ‰«      t        t	        j                  «       dz  «      «      }|r7t        |j                  |j                  |j                  |j                  ¬«      S i S )Nr   ©Úresolve_windrg   r¹  )
rE   r  Úwind_readings_forr�   r  rÄ  rÂ  rÃ  r­   rº  )r  r   r  rI   s     €€r&   rÈ  z_store_wind_for.<locals>.windù  sb   ø€ Øˆ=¡	ØˆIÝ,Ù˜×0Ñ0°Ó;¼SÄÇÁÃÈtÑASÓ=TÓUˆÙTUŒz˜!Ÿ)™) Q§[¡[°!·(±(ÀQÇZÁZÔPÐ]Ð[]Ð]r(   rÿ   r.   )rI   r  rÈ  s   `` r&   Ú_store_wind_forr  ö  s   ù€ ö^ð €Kr(   Úgr°  F)r<  r  rè  ré  r}   rê  rë  Úpositions_forrì  rí  rš   rô  Ústall_sr  rç  rä  rå  ræ  Úqueuerï  Úwaiting_forÚon_air_courseÚ
asrun_pathrõ  r¿   r  rÀ   c               óŽ  ‡‡‡‡‡
‡‡‡‡‡‡‡‡‡F‡G‡H‡I‡J‡K‡L‡M‡N— |xs
 t        «       }|xs d„ ŠIdŠJdŠNd} dld„ŠHdmˆHˆIˆˆˆˆˆˆˆf	d„}! |!«       \  ŠN} t        «       }"d}#t        «       }$d}%t        d«      }&d}'t        «       }(i ŠKdŠLt        d«      ŠMdnˆJˆˆˆKfd„ŠGdoˆGˆ
ˆJˆˆfd	„ŠFdpˆFˆGˆIˆˆJˆˆˆLˆMˆNf
d
„})d}*|�|*|k  �r¤ ‰«       }+|*dz  }*	 ‰j	                  «       j                  «       D ]  \  },}-|j                  |,|-«       Œ ‰j                  «       D ]A  \  },}.|j                  |,|.«      sŒ ‰d|,› d|j                  › d|j                  «       › �«       ŒC 	 t         t!        |dd„ «      «       «      D ]W  }0 t!        ‰dd„ «      |0«       }1|1rdn t!        ‰dd„ «      |0«      }2|1s|2sŒ5|j#                  |0«        ‰d|0› d|1rdnd› �«       ŒY t!        ‰dd«      }3|3�”|3j%                  d«      |3j%                  d«      |3j%                  d «      f}4|4|%k7  s ‰«       |&z
  d!k\  rP|4 ‰«       }&}% ‰d"|4d   › d#|4d   › d$t!        ‰d%d«      ›d&t!        ‰d'd«      ›d(|4d)   › d*‰J�‰Jj&                  nd+› �«       ‰J�ó t!        ‰dd,„ «      ‰Jj&                  «      sÕ|'dz  }'|'d)k\  rÍ ‰d‰Jj&                  › d-�«       t)        j*                  |t)        j,                   ‰I«       ‰Jj&                  ¬.«      ‰¬/«       |j#                  ‰Jj&                  «       |j/                  «        |"j1                  ‰Jj&                  «       |(j1                  ‰Jj&                  «       dŠJd}'‰�‰sd‰d0<    |!«       \  ŠN} nd}'‰J��B‰j3                  ‰Jj&                  «      r3 t!        ‰d1d2„ «      ‰Jj&                  «      \  }5}6‰Jj5                  |5|6«       ‰Jj7                  «       \  }7}8|j9                  |7|8¬3«       ‰Jj;                  «       s¾‰Jj=                  «       |k\  r« ‰‰Jj&                  ‰Jj>                  d4   «      }9tA        |9«      d5kD  r|9d5   nd}:‰JjC                  |9d   |9d   |:«      r ‰d‰Jj&                  › d6�«       nF|:s|9d)   sd7nd8};‰JjE                  |;«        ‰d9‰Jj&                  › d:‰Jj=                  «       d;›d<|;› �«       |jG                  ‰Jduxr ‰j3                  ‰Jj&                  «      ¬=«      }<|<��N|<\  },}. ‰|,|.«      }=|=dd5 \  }>}?}@}AtA        |=«      d5kD  r|=d5   nd}B@s˜Bs–|j/                  «         t!        ‰dd>„ «      |,«      r6|j                  |,|.«       |,|"v�rr|"jI                  |,«        ‰d|,› d?�«       �nStK        |d@«      r|j#                  |,«       |"j1                  |,«        ‰d|,› dA�«       �n|"j1                  |,«       ‰�‰xs |>j%                  dB«      ‰d0<   ‰J�‰jM                  ‰Jj&                  «      nd}C‰J�‰Jj&                  nd}Dt)        j*                  |t)        jN                   ‰I«       |,‰jM                  |,«      |.|D¬C«      ‰¬/«       tQ        | ‰fi dD|,“dE|“dF@“dG|>“dH|?“dIA“d%‰“dJ|“dK|“dL|“dM|“dN|“dO|	“dP‰
“dQ‰�|,‰xs |>j%                  dB«      fˆfdR„	nd“dS‰�|,‰xs |>j%                  dB«      fˆfdT„	nd“dU|“dV|“dW ‰I«       “dXB“dY|“dZ‰“d[|#dk(  “d\C“d]‰Ndu“d^|$“d_‰“d`‰“ŽŠJ|#dz  }#dŠNd‰_)        ‰J‰_*         ‰da|,› db‰jM                  |,«      xs dc› ddtA        @«      › de|.› �«       n�‰J�Ž‰�Œ|jW                  ‰j3                  ‰Jj&                  «      ¬=«      rat)        j*                  |t)        j,                   ‰I«       ‰Jj&                  ¬.«      ‰¬/«       dŠJ‰�‰sd‰d0<   |jY                  «         |!«       \  ŠN} ‰J�	 ‰Jj[                  «        n]‰N�O‰� ‰«       nd}9 ‰H|9«      }E|9r)E| k7  r$ta        ‰f‰‰t!        ‰djd«      dkœ|9¤ŽŠN‰N‰_)        E} ‰Nj[                  «        n‰�
 |!«       \  ŠN}  |)«        |�|*|k  r |tc        ||+ ‰«       «      «       |€�Œœ|*|k  r�Œ£yy# t        $ r9}/ ‰dt        |/«      j                  › dt        |/«      dd › d�«       Y d}/~/�ŒHd}/~/ww xY w# t        $ r™}/ ‰d‰Jj&                  › dft        |/«      j                  › dt        |/«      dd › dg�«       ‰Jj&                  |(vrE|(jI                  ‰Jj&                  «        ‰dh‰Jj&                  › dit]        j^                  «       › �«       Y d}/~/�Œd}/~/ww xY w)quÂ  Broadcast the operator's races, one after another, **without ever restarting**.

    The first published state is the waiting presentation. `races` reports each race as its
    start-countdown trigger fires; `OnAirQueue` then decides what is on air: the
    broadcast stays with the race it is showing until that race has its WINNER (plus the hold on the
    finish) or the operator closes it, and only then puts up the next one â€” joined in progress, with
    its elapsed track back-filled so its standings are whole. One process, one port, one SSE stream,
    for a whole regatta day: what used to need a relaunch per heat.

    `presentation_for(instance, gun_ms)` supplies that race's `(meta, course, boats, team_config)` â€”
    the store/config resolution, injected so this loop stays independent of where a race is defined.

    This loop only ever READS the race plane. It never writes a gun, a stop or a result: the broadcast
    presents existing data, and the Race Officer's record must not be a function of what the
    presentation happened to score. Whoever owns the boats (the sim) or the committee (the operator)
    decides a race is over; the broadcast finds out by reading, like every other client.c                 óB   — t        t        j                  «       dz  «      S rø  rù  r.   r(   r&   rc   z%broadcast_following.<locals>.<lambda>@	  s   € œs¤4§9¡9£;°Ñ#5Ó6€ r(   Nc                óú  — | sy| j                  d«      xs i }| j                  d«      xs i }|j                  d«      xs i }|j                  d«      |j                  d«      |j                  d«      |j                  d«      |j                  d	«      |j                  d
«      f}t        t        d„ | j                  d«      xs g D «       «      «      }t        d„ | j                  d«      xs g D «       «      }|||fS )a|  Identity of the schedule and fleet currently shown by the waiting picture.

        The old key covered only hull allocation. A moved gun or renamed race therefore stayed stale
        on an open renderer until a boat also changed. Keep this key small and deterministic so the
        frequent race-plane poll republishes ``init`` only when visible facts actually change.
        Nr#   r%   ré  r:  r|  rœ  rè   r*   r+   c              3  ó¢   K  — | ]G  }t        |j                  d «      xs d«      t        d„ |j                  d«      xs g D «       «      f–— ŒI y­w)rÓ  r¡   c              3  óX   K  — | ]"  }t        |j                  d «      xs d«      –— Œ$ y­w)rÕ  r¡   Nr’  r  s     r&   r2   zFbroadcast_following.<locals>._waiting_key.<locals>.<genexpr>.<genexpr>[	  s-   è ø€ ò 2ZØ7;ô 36°d·h±h¸zÓ6JÒ6PÈb×2Qñ 2Zùs   ‚(*rO   N)r   r7   rÝ   )r/   rM   s     r&   r2   z<broadcast_following.<locals>._waiting_key.<locals>.<genexpr>Z	  sY   è ø€ ò 5ð ô �—‘˜(Ó#Ò) rÓ*¬Eñ 2ZØ@DÇÁÈÓ@QÒ@WÐUWô2Zó -Zô [ñ5ùs   ‚AAr$   c           	   3  óÚ   K  — | ]c  }t        |t        «      rQ|j                  d «      |j                  d«      |j                  d«      t        |j                  d«      xs g «      f–— Œe y­w)rœ  r|  rE  r$   N)rÛ   rÜ   r7   rÝ   r�   s     r&   r2   z<broadcast_following.<locals>._waiting_key.<locals>.<genexpr>^	  s`   è ø€ ò Pð ¼ÀCÌÔ9Nð �W‰W�\Ó" C§G¡G¨GÓ$4°c·g±gÐ>SÓ6TÜ�3—7‘7˜7Ó#Ò) rÓ*ô,ñPùs   ‚A)A+r*  )r7   rÝ   rG   )rã   r#   r%   rM  ÚpresentationrL   r	  s          r&   Ú_waiting_keyz)broadcast_following.<locals>._waiting_keyF	  sý   € ñ ØØ�z‰z˜&Ó!Ò' RˆØ—‘˜HÓ%Ò+¨ˆØ—‘˜HÓ%Ò+¨ˆà�H‰H�[Ó!Ø�H‰H�WÓØ�H‰H�\Ó"Ø�J‰J�vÓØ�J‰J�uÓØ�J‰J�uÓð
ˆô ”vñ 5ð  Ÿ™ GÓ,Ò2°ô5ó 5ó 6ˆô ñ Pð Ÿ
™
 :Ó.Ò4°"ôPó Pˆ	ð ˜V YÐ.Ð.r(   c            
     ó  •	— ‰€y ‰«       } | s# ‰«       }d|dddg i i g dœdœi g d„ dœ}  ‰d	«       t        ‰	f‰‰
t        ‰d
d«      dœ| ¤Ž}|‰	_        d‰	_        ‰� ‰s|j                  j                  d«      ‰d<   | ‰| «      fS )u×  Publish the programme's startup state: the venue waiting for a countdown trigger.

        A scheduled race and its event/course are sufficient. Boat allocation is live marshalling,
        not permission to start the model; an empty allocation therefore produces the branded venue
        with no invented line-up and is republished when the real allocation arrives. `None` means
        the race-day definition itself is not readable yet. Even then, publish a neutral holding
        state: an SSE stream that emits camera-status deltas without ever emitting ``init`` leaves a
        newly restarted renderer on â€œLoading broadcast feedâ€¦â€� forever. The neutral state asserts no
        event, course, line-up or schedule; it is replaced as soon as those facts become readable.

        Returns `(waiting, key)`; the key identifies the schedule and line-up on screen so the caller
        can rebuild when the operator re-arms, renames, allocates, un-allocates or swaps a hull.NrP  zWaiting for race datar¡   r=  )r|  r  r1  c                 ó   — i S r-   r.   r.   r(   r&   rc   z=broadcast_following.<locals>._begin_waiting.<locals>.<lambda>†	  s   € ¨2€ r(   )r#   r%   r$   r  zo[live] race-day definition unavailable - neutral waiting presentation on air; retrying the race plane each pollr)  ©rÀ   rô  r)  ÚcourseIdr�  )r(  rq   rX  Úcourse_guider#   r7   )rã   rî   r   r'  r–  r  r  rÀ   Úracesr  rô  r  s      €€€€€€€€€r&   Ú_begin_waitingz+broadcast_following.<locals>._begin_waitingd	  s×   ø€ ð ÐØÙ“ˆÙÙ“'ˆCð 5Ø'*à&(Ø$&Ø$;Ø$&Ø%'Ø!#Ø$&ñ,ñð ØÙ#-ñ!ˆFñ$ ð 7ô 8ô �Sð P ¨SÜ!(¨°¸tÓ!DñPØHNñPˆð
 ˆŒØˆÔØÐ$©Yð
 #$§&¡&§*¡*¨ZÓ"8ˆM˜$ÑØ‘,˜vÓ&Ð&Ð&r(   r   z-infr¡   c                ó   •— | t        |«      f}‰�L‰j                  | k(  r=‰j                  ‰j                  t	        ‰j
                  «      ‰j                  f\  }}}}n®|‰v r‰|   j                  d«      r‰|   S 	  ‰| t        |«      «      }|d d \  }}}t        |«      dkD  r|d   nd }|s^|r\|D �	�
cg c]O  }	|	j                  d«      xs g D ]5  }
|
j                  d	«      r"|
j                  d
«      s|
j                  d	«      ‘Œ7 ŒQ }}	}
t        |j                  d«      xs |«      }||t	        |xs g «      |xs g t        |t	        |xs g «      t        t        |«      |z
  dz  «      ¬«      t        «       dœ}|‰|<   |S # t        $ r;} ‰d| › dt        |«      j                  › dt        |«      d d › d�«       Y d }~y d }~ww xY wc c}
}	w )NrO   z[live] start-camera header for ú	 failed (rò   r}  u   ) â€” retryingrh   r
  rÕ  Ú	spectatorr  rg   ry  )r#   r%   rO   r$   ÚtrackerÚseenSeconds)r�   rC   r#   r%   rÞ   rO   r$   r7   rõ   rö   r÷   r   r9   r   r{   rÌ   )Úinstancer„  rd   r#   r%   rO   r$   Úresolvedrù   rM   r   rK  ÚcontextÚon_airrÀ   Úpresentation_forÚstart_contextss                €€€€r&   Ú_start_contextz+broadcast_following.<locals>._start_context¶	  só  ø€ Øœ˜S›Ð"ˆØÐ &×"4Ñ"4¸Ò"@Ø)/¯©°f·m±mÄTÈ&Ï,É,ÓEWÐY_×YeÑYeÐ)eÑ&ˆD�&˜%¡Ø�NÑ" ~°cÑ':×'>Ñ'>¸wÔ'GØ! #Ñ&Ð&ðÙ+¨H´c¸#³hÓ?�ð
 #+¨2¨A ,ÑˆD�&˜%Ü#& x£=°1Ò#4�H˜Q’K¸$ˆEÙ™UØ:?÷ P°$ÈTÏXÉXÐV]ÓM^ÒMdÐbdò PÀDØ ŸH™H ZÔ0¸¿¹À+Ô9Nð Ÿ™ *Õ-ð PÐ-ð P�ñ Pä˜$Ÿ(™(Ð#4Ó5Ò<¸Ó=ˆØ¨6¼DÀÂÈ"Ó<MÐX]ÒXcÐacÜ2Øœt E¢K¨RÓ0¼%ÄÀSÃÈKÑAWÐ[_Ñ@_Ó:`ôbä"%£%ñ)ˆð &ˆ�sÑØˆøô ò ÙÐ8¸¸
ð CÜ ›9×-Ñ-Ð.¨b´°S³¸$¸3°Ð0@ÀðPô QäûðüóPs   Á;F Â4AG
Æ	GÆ1GÇGc                ó  •—  ‰| |«      }|€i d fS ‰�!‰j                   | k(  r‰j                  «       |fS |d   }|si |fS |d   }t        |j                  d«      xs |«      }t	        |t        |«      dz
  «      t        |t        |«      dz   «      }}d}		 ‰�t ‰| |«      xs i }
t        |
«      r |
«       }
t	        d||z
  dz  «      }|
r<t        |«      |
j                  «       D ��ci c]  \  }}|t        |«      v r||“Œ c}}ini }d	}	n‰� ‰| ||||«      xs i }ni }|	r|n<|j                  «       D ��ci c]!  \  }}t        |«      |d   vrt        |«      |“Œ# c}}}|	s|d   j                  |«       |d   j                  |«      }|j                  d«      }|�i |¥d|t        |«      dz  z   i¥}||fS c c}}w # t        $ r=} ‰d
| › dt        |«      j                  › dt        |«      d d › d�«       i }Y d }~Œàd }~ww xY wc c}}w )NrO   r#   r  iˆ  rØ  Fr   rg   Tz[live] start-camera tracks for r0  rò   r}  u!   ) â€” using the one-minute cutoffr3  r2  r­  r®  )rC   r²  r�   r7   rx   r¦  rr   r   r•   rÌ   rõ   rö   r÷   rÛ  Úobserve_frames)r4  r„  rî   r6  rO   r#   rK  ra   rz   Úlive_snapshotÚsnapshotrÞ  r   r1  rÜ  rù   r6   Úunseenr°  r±  r:  rë  r7  rÀ   r  s                       €€€€€r&   Ú_off_air_start_progressz4broadcast_following.<locals>._off_air_start_progressÐ	  sq  ø€ Ù  ¨3Ó/ˆØˆ?Ø�t�8ˆOØÐ &×"4Ñ"4¸Ò"@Ø×/Ñ/Ó1°7Ð:Ð:Ø˜Ñ ˆÙØ�w�;ÐØ�v‰ˆÜ˜$Ÿ(™(Ð#4Ó5Ò<¸Ó=ˆÜ˜{¬C°«H°uÑ,<Ó=¼sÀ3ÌÈCËÐSYÑHYÓ?Z�&ˆØˆð	ØÐ(ñ
 )¨°5Ó9Ò?¸R�Ü˜HÔ%Ù'›z�HÜ˜!˜c KÑ/°DÑ8Ó9�á@Hô ˜c›(ÀHÇNÁNÓDT÷ %<±[°T¸5Ø(,´°E³
Ñ(:ð &*¨5¡[ó %<ñ =ØNPð à $‘ØÐ'Ù# H¨e°X¸vÀ{ÓSÒYÐWY‘à�ñ *‘&Ø7=·|±|³~÷ >©¨¨VÜ˜#“h g¨mÑ&<Ñ<ô �s“8˜VÑ#ó >ð 	ñ Ø�MÑ"×)Ñ)¨&Ô1Ø˜9Ñ%×4Ñ4°VÓ<ˆØ—,‘,Ð3Ó4ˆØÐØY˜(ÐYÐ$5°{ÄSÈÃ\ÐTXÑEXÑ7XÑYˆHØ˜Ð Ð ùó-%<øô ò 	ÙÐ4°X°Jð ?Ü˜C“y×)Ñ)Ð*¨"¬S°«X°d°s¨^Ð,<Ð<]ð_ô `à�Fûð	üó>s1   ÂAF? Ã'F9ÄF? Ä7&HÆ9F? Æ?	HÇ3H È Hc                 óÌ  •
— t        ‰dd «      }  t        ‰dd„ «      «       }| €y  ‰«       }t        ||«      }i }d }|j                  d«      dk(  r6|j                  d«      r% ‰t        |d   «      t	        |d   «      |«      \  }}|j                  d«      r-|j                  d«      �t        |d   «      t	        |d   «      ini }t        |||«      }|j                  d«      �r|�)t        |d   «      t        |j                  d«      «      k7  r# ‰t        |d   «      t	        |d   «      «      }i }|xs i j                  d	«      xs i }|d   |d   t	        |d   «      |j                  d
«      xs |j                  d«      xs d|j                  d«      xs |j                  d«      xs |d   dœdD �	ci c]  }	|	|v r|	||	   “Œ c}	¥}
|j                  d«      �Ht	        |d   «      |
d<   n6‰�‰j
                  n‰�‰j
                  ni }d|j                  d
«      xs ddœ}
t        j                  |
dd¬«      }	 ‰«       }|	‰k7  s|‰z
  dk\  r | |
«       |	|cŠŠy y c c}	w )NÚpublish_start_cameraÚstart_camera_rowsc                 ó   — g S r-   r.   r.   r(   r&   rc   zDbroadcast_following.<locals>._publish_start_camera.<locals>.<lambda>
  s   € ÀR€ r(   ÚmodeÚstartedrœ  r„  r®  r#   r:  r|  r¡   r{  rè   )rE  rœ  r}  r:  r{  )ÚstartedBoatIdsÚstartedBoatCountÚexpectedBoatCountÚallBoatsStartedr®  ÚexpiresUtcMsÚawaiting)rE  r:  TrÖ  rØ  r¶   )rq   r   r7   r   r�   r#   rã  rä  )r´  Úrows_for_startrî   Úfirstr°  r6  Úall_startedÚchosenr#   rd   r¶  Úfallback_metaÚnow_sr@  r:  r–  r¿   r7  r-  r  Ústart_last_keyÚstart_last_publish_sÚwaitings                €€€€€€€€€€r&   Ú_publish_start_cameraz2broadcast_following.<locals>._publish_start_cameraÿ	  s•  ø€ ä˜#Ð5°tÓ<ˆØHœ Ð(;¹ZÓHÓJˆØˆ?ØÙ‹gˆÜ*¨>¸3Ó?ˆØˆØ#ˆØ�9‰9�VÓ 	Ò)¨e¯i©i¸Ô.EÙ 7Ü�E˜,Ñ'Ó(¬#¨e°E©lÓ*;¸Só!BÑˆH�gð  Ÿ)™) LÔ1°h·l±lÐCTÓ6UÐ6aô ˜E ,Ñ/Ó0´#°hÐ?PÑ6QÓ2RÑSàð 	ô ,¨N¸CÀÓMˆØ�:‰:�lÕ#Øˆ¤# f¨\Ñ&:Ó";¼sÀ5Ç9Á9È\ÓCZÓ?[Ò"[Ù(¬¨V°LÑ-AÓ)BÄCÈÈuÉÓDVÓW�Ø�Ø’M˜r×&Ñ& vÓ.Ò4°"ˆDà˜v™Ø$ \Ñ2Ü  u¡Ó.Ø!ŸX™X kÓ2ÒM°d·h±h¸wÓ6GÒMÈ2Ø ŸH™H ZÓ0Ò^°F·J±J¸vÓ4FÒ^È&ÐQ]ÑJ^ñ	ð2:ö N¨#à=@ÀH¹_ð ˜ ™Ñ%ò Nð	ˆFð �z‰z˜.Ó)Ð5Ü),¨V°NÑ-CÓ)D��~Ò&à,2Ð,>˜VŸ[š[Ø29Ð2E '§,¢,È2ð à(°}×7HÑ7HÈÓ7UÒ7[ÐY[Ñ\ˆFÜ�j‰j˜¨4¸JÔGˆÙ“ˆð �.Ò  EÐ,@Ñ$@ÀDÒ$HÙ�FŒOØ36¸Ð0ˆNÑ0ð %IùòNs   Æ8I!r   r  z started while ua    is ON AIR â€” QUEUED (the broadcast stays with the race on air until it has a winner); waiting: z[live] race-plane poll failed (rò   r÷  u   ) â€” retrying next tickrU  c                 ó   — g S r-   r.   r.   r(   r&   rc   z%broadcast_following.<locals>.<lambda>L
  r²  r(   Úlistsc                 ó   — y©NTr.   ©Ú_is    r&   rc   z%broadcast_following.<locals>.<lambda>M
  ó   � r(   FÚ	cancelledc                 ó   — y)NFr.   r[  s    r&   rc   z%broadcast_following.<locals>.<lambda>O
  r]  r(   u2    left the waiting queue â€” race plane says it is zno longer listedzabandoned/cancelledÚ
last_statsÚlistedÚin_scopeÚeligibleg      N@z[live] race plane: z races, z in scope (course=r  z, owner=rq  z), r¢  z  in their start window; on air: Únonec                 ó   — yrZ  r.   r[  s    r&   rc   z%broadcast_following.<locals>.<lambda>l
  r]  r(   uq    VANISHED from the race plane while ON AIR â€” dropped (deleted by an operator, not finished); the queue moves on)rõ  rC   r  r�  Úpreliminary_resultc                 ó   — y)NrP  r.   r[  s    r&   rc   z%broadcast_following.<locals>.<lambda>„
  r]  r(   )Úfrontier_secr}  r
  u=   : roster changed while on air â€” republished the feed headerzno boats in this racez no positions from the read planez[live] STALLED z: nothing published for ú.0fu   s â€” )Úoverc                 ó   — yrZ  r.   r[  s    r&   rc   z%broadcast_following.<locals>.<lambda>³
  r]  r(   un    has NO BOATS â€” not going on air; staying on the waiting picture and retrying each poll as the line-up fillsÚdropue    VANISHED from the race plane while waiting to air â€” dropped (deleted by an operator, not finished)r+  )rõ  rC   r  rY  Úleaving_instance_idrC   r<  rO   r#   r%   ry  rä  rå  ræ  rç  r}   rê  rë  rì  c                ó   •—  ‰| |«      S r-   r.   )rN  Úcidrì  s     €r&   rc   z%broadcast_following.<locals>.<lambda>Ø
  s   ø€ ÙIQÐRVÐX[ÓI\ð r(   rí  c                ó   •—  ‰| |«      S r-   r.   )rN  ro  rí  s     €r&   rc   z%broadcast_following.<locals>.<lambda>Û
  s   ø€ ÙKUÐVZÐ\_ÓK`ð r(   rè  ré  rî  r$   rï  rÀ   rð  rñ  rò  ró  rô  r¿   z[live] ON AIR: z (r�  u   ) â€” z boats, gun z: tick failed (u4   ) â€” holding the picture; the watchdog will re-readz([live] first tick-failure traceback for z:
r)  r*  )rã   r  rý   útuple | None)rý   rÝ   )r4  r   r„  r�   rý   r  )r4  r   r„  r�   rî   r�   rý   útuple[dict, dict | None]rÿ  )2r   rÌ   r   r™   Ú
prioritiesr•   Úset_priorityÚpollÚofferr7  rU  rõ   rö   r÷   r   rÞ   rq   rl  r7   rC   Ú	asrun_logrw   Úoff_air_rowÚreleaserø   Úclosedr£  r«  Únote_finish_progressr[  rq  r#   r9   r�  rv  Ú	take_nextrê   rš  Úname_ofÚ
on_air_rowrã  rX  r,  Úwaiting_view_dueÚrelease_to_waitingrâ  Ú	tracebackÚ
format_excr(  rž   )OrI   r  r-  r8  r<  r  rè  ré  r}   rê  rë  r  rì  rí  rš   rô  r  r  rç  rä  rå  ræ  r  rï  r  r  r  rõ  r¿   r  rÀ   ÚqÚwaiting_keyr.  Úempty_roster_seenÚraces_airedró  Ú
plane_lastÚplane_atÚon_air_unlistedÚtick_tb_seenrV  r  Úpoll_started_sr4  Úflaggedr„  rÑ  ÚpendingÚvanishedr^  ÚstatsÚsnaprž  Ústop_msÚfinish_resultÚfinish_frontierrà  Úf_teamsÚwhyÚnxtr5  r#   r%   rO   ry  r$   Úleaving_nameÚ
leaving_idrd   r@  r:  r'  r–  r7  r9  rS  rT  rU  sO    ``` `    ```` `        ``  ` `                                       @@@@@@@@@r&   Úbroadcast_followingr™  	  sý
  ÿÿý€ ðz 	Ò”“€AØÒ7Ñ6€EØ $€FØ#€Gà!%€Kó/÷<5'õ 5'ñn *Ó+Ñ€Gˆ[ô #&£%Ðð
 €Kô 3Ó4Ðà#€JÜ�V‹}€Hà€Oä ›U€Lð 35€NØ€NÜ  ›=Ð÷ð ÷4-!ñ -!÷^->ö ->ð^ €EØ
Ð
˜u yÓ0Ù"›ˆØ�‰
ˆð
	*Ø%*×%5Ñ%5Ó%7×%=Ñ%=Ó%?ò 2Ñ!�˜'Ø—‘˜x¨Õ1ð2à!&§¡£ò 6‘�˜#Ø—7‘7˜8 SÕ)Ù˜W X J¨o¸a¿h¹h¸Zð H'à'(§y¡y£{ mð5õ 6ñ6ô  Ð=œG A y±*Ó=Ó?Ó@ò 	WˆGØCœ7 5¨'±?ÓCÀGÓLÐLˆHÙ"*™ØFœ ¨Ñ5EÓFÀwÓOð áš9Ø—‘�w”Ù˜  	ð *0á08Ñ,Ð>SÐTðVõ Wð	Wô ˜˜|¨TÓ2ˆØÐØ—I‘I˜hÓ'¨¯©°:Ó)>ÀÇ	Á	È*Ó@UÐVˆDØ�zÒ!¡Y£[°8Ñ%;¸tÒ%CØ'+©Y«[˜H�
ÙÐ,¨T°!©W¨I°X¸dÀ1¹g¸Yð G"Ü")¨%°¸dÓ"CÐ!Fð G Ü '¨¨w¸Ó =Ð@ÀØ˜q™'˜Ð"BØ06Ð0B˜&×,Ò,ÈÐOð	Qô Rð ÐÐ&N¤g¨e°W¹oÓ&NÈv×OaÑOaÔ&bØ˜qÑ ˆOØ !Ò#Ù˜ ×!3Ñ!3Ð 4ð 5\ð ]ô ^ä× Ñ  ¬Y×-BÑ-BÙ ›7°×0BÑ0Bô.DØLRõTà—‘�v×)Ñ)Ô*Ø—	‘	”Ø!×)Ñ)¨&×*<Ñ*<Ô=Ø×$Ñ$ V×%7Ñ%7Ô8Ø�Ø"#�Ø Ð,±YØ*.�M $Ñ'Ù'5Ó'7Ñ$�™àˆOØÑð �|‰|˜F×.Ñ.Ô/ð(J¤wØÐ/Ñ1Hó(JØJP×J\ÑJ\ó(^Ñ$�˜Wà×)Ñ)¨+°wÔ?Ø-3×-CÑ-CÓ-EÑ*ˆM˜?Ø×"Ñ" =¸Ð"ÔOð —?‘?Ô$¨×)?Ñ)?Ó)AÀWÒ)LÙ(¨×);Ñ);¸V¿[¹[ÈÑ=TÓU�Ü&)¨%£j°1¢n˜% š(¸$�Ø×.Ñ.¨u°Q©x¸¸q¹À7ÔKÙ˜W V×%7Ñ%7Ð$8ð 99ð :õ ;ñ <CÀeÈAÂhÑ2Ø Bð à×'Ñ'¨Ô,Ù˜_¨V×-?Ñ-?Ð,@Ð@XØ$×4Ñ4Ó6°sÐ;¸6À#ÀðHô Ià�k‰k ¨dÐ 2Ò W°u·|±|ÀF×DVÑDVÓ7WˆkÓYˆØ‰?Ø‰MˆH�cñ (¨°#Ó6ˆHØ/7¸¸¨|Ñ,ˆD�&˜% Ü#& x£=°1Ò#4�H˜Q’K¸$ˆEÙ¡ð —	‘	”ð <”7˜5 '©?Ó;¸HÔEØ—G‘G˜H cÔ*ØÐ'8Ò8Ø)×-Ñ-¨hÔ7Ù ¨¨
ð 3]ð  ^ö _ô ˜q &Ô)ØŸ™˜xÔ(Ø%×-Ñ-¨hÔ7Ù˜W X Jð /Lð Mö Nð "×)Ñ)¨(Ô3Ø Ð,ð +4Ò*K°t·x±xÀ
Ó7K�M $Ñ'ð
 FLÐEW §¡¨f×.@Ñ.@Ô AÐ]a�Ø39Ð3E˜V×/Ò/È4�
ô
 × Ñ  ¬Y×-AÑ-AÙ ›7°ÀEÇMÁMÐRZÓD[Ø"¸
ô.DàLRõTô $ E¨3ò 9¹Hð 9Éxð 9Ñ_dð 9Ù)-ð9Ù6<ð9ÙJUð9á.7ð9áCLð9ñ 0:ð9ñ EMð9ñ ,2ð	9ñ =Eð	9ñ QZð	9ñ
 0:ð9ð 2:Ð1Eð <DÈ)ò KGØ26·(±(¸:Ó2Fõ0]àKOøð9ð 4>Ð3Ið >FÈIò MIØ48·H±H¸ZÓ4Hõ2aàOSøð9ñ ),ð9ñ 5;ð9ñ -2¬Gð9ñ <Að9ñ LTð9ñ ]cð9ð 1<¸qÒ0@ð9ñ Vbð9ð <CÈ$Ñ;Nð9ñ  9Lð!9ñ" ),ð#9ñ$ /8ð%9�ð& ˜qÑ �Ø�Ø $�”Ø#)�Ô Ù˜¨¨
°"°U·]±]À8Ó5LÒ5VÐPVÐ4WÐW]Ü˜e›*˜ \°#°ð8õ 9àÐ  [Ð%<Ø×$Ñ$¨%¯,©,°v×7IÑ7IÓ*JÐ$ÔKô
 ×Ñ˜Z¬×)>Ñ)>Ù“w¨F×,>Ñ,>ô*@ØHNõPàˆFØÐ(±ð '+�˜dÑ#Ø× Ñ Ô"Ù#1Ó#3Ñ ˆG�[ØÐðOØ—‘•ð Ð ð
 &1Ð%<‘K”MÀ$ˆEÙ˜uÓ%ˆCÙ˜ Ò+Ü" 3ð ]¨v¸3Ü/6°u¸mÈTÓ/Rñ]ØV[ñ]�à '�”Ø!�Ø�L‰L�NØÐ$Ù#1Ó#3Ñ ˆG�[ÙÔØÐ ¨	Ò 1ñ ”,˜z¨>¹9»;ÓGÔHðs Ò
˜u yÖ0øô$ ò 	*ÙÐ4´T¸!³W×5EÑ5EÐ4FÀbÌÈQËÐPTÐQTÈÈð W(ð )÷ *ò *ûð	*ûôP ò 
OÙ˜ ×!3Ñ!3Ð 4°OÄDÈÃG×DTÑDTÐCUÐUWÜ˜a›&  #˜,˜Ð'[ð]ô ^ð ×%Ñ%¨\Ñ9Ø ×$Ñ$ V×%7Ñ%7Ô8ÙÐEØ$×0Ñ0Ð1°´Y×5IÑ5IÓ5KÐ4LðNô Oÿùð
Oús8   ÃA a Ä&+a Þ<b" á	bá&.bâbâ"	eâ+Bd?ä?e)ry  r  rè  ré  r}   rê  rì  rí  rš   r  rç  rä  rå  ræ  rô  r  c               ó
  — t        | |fi d|“d|“d|“d|“d|“d|“d|“d|“d	|“d
|“d|“d|“d|“d|“d|“d|	“d|
“dt        «       “d|“Ž}d}|�||k  r.|dz  }|j                  «        |�||k  r ||«       |€Œ'||k  rŒ-yy)u•  Resolve the static feed, publish `init`, then stream `delta`s as live fixes land. Each tick the
    boat centroid is written into `center["pos"]` so the AIS box + the wind poller follow the boat.
    With `clip_url`, the boats' uploaded clips are read from the STORE (`media_from_store`) and the
    feed header is re-published whenever the clip set grows â€” the live boat-cam tile channel, driven
    by the same `video_segments` production writes. `max_polls` + `sleep` are injectable so a test can
    run a bounded, no-wait loop; production leaves them default (poll forever). Pure over the injected
    `store` â€” the same path a real Postgres run takes.rC   r<  rO   r#   r%   ry  r  rä  rå  ræ  rç  r}   rê  rì  rí  rè  ré  ró  rô  r   Nr   )rã  r   râ  )rI   r  rC   r<  rO   r#   r%   ry  r  rè  ré  r}   rê  rì  rí  rš   r  rç  rä  rå  ræ  rô  r  r�  r  s                            r&   Úbroadcast_liver›  +  s!  € ôB �e˜Sò O©kð OÁHð OÑTYð OÙðOÙ(.ðOÙ<GðOÙS\ðOá )ðOá6@ðOáKSðOñ $ðOñ /7ðOñ !*ð	Oñ 5=ð	Oñ JTð	Oñ
 ðOñ
 '-ðOô +CÔ*DðOñ KNðO€Dð €EØ
Ð
˜u yÒ0Ø�‰
ˆØ�	‰	ŒØÐ ¨	Ò 1Ù�*Ôð	 Ñ
˜u yÕ0r(   é="  ç     @@)ry  r  r7  Úais_keyÚais_radius_mÚwind_providerrç  rä  rå  r}   ræ  ÚfollowrJ  r  r  rÀ   c               ó   ‡ ‡‡‡&‡'‡(‡)— t        d|t         j                  ||¬«      }|j                  d¬«       t        d|j                  › �«       t        d|j                  › d�«       dd	iŠ'd	}|	r‰ €t        d
«       d	}	|r‰ €t        d«       d	}|	r?ddlm} ddlmŠ&  ||	ˆ'fd„|
ˆ&ˆ fd„¬«      }|j                  «        t        d|
d›d�«       d‰iŠ(d	}|rQ‰s|�Mddl	m
}  |ˆ'fd„‰ j                  ˆ(fd„|¬«      }|j                  «        t        ‰r	d|› d‰›d�nd|› d�«       n|rt        d«       	 |�zg |¢d	‘d	‘d	‘d	‘d	‘d	‘d	‘­d	d \	  }}}}}} }!}"}#t        ||«      }$t        ‰ |fi d |“d!|“d"|$“d#|“d$‰“d%|“d&‰'“d'|“d(|“d)|“d*|“d+|“d,|“d-|“d.|“d/| “d0|!“d1|"“d2|#“d3‰(“d4|“d5|“Ž n |r+t        ‰ |‰‰||||t        ‰ ‰«      t        ||«      |¬6«       nst        ||«      }%‰ �‰rt!        ˆ fd7„|¬8«      nd	Š)t#        ‰ |fi d9‰“d#|“d:|“d;|“d<|“d=|“d$‰“d%|“d&‰'“d'|“d(|“d)|“d+|“d*|“d0t        ‰ ‰«      “d1‰)�ˆˆˆ)fd>„nd	“d"|%“Ž t        d?«       	 t%        j&                  d@«       Œ# t(        $ r t        dA«       Y nw xY w	 |�|j+                  «        |�|j+                  «        |j+                  «        y	# |�|j+                  «        |�|j+                  «        |j+                  «        w xY w)Buf  Boot the `LiveFeedServer` (serving the static 3D page + the SSE stream) and broadcast the live
    store forever. Wire an `AISFeed` overlay when `ais_key` is given, and a `WindFeed` (real wind â†’
    live `WindReading`s) when `wind_provider` is given (needs `course_id`). Both follow the boat via
    one shared `center` holder.

    With `follow=(races, presentation_for)` the producer FOLLOWS the operator's races
    (`broadcast_following`) instead of the one pinned `instance_id`: it stays with the race on air
    until that race has a winner, then moves to the next â€” same process, same port, same stream.ú0.0.0.0)Úhostr7  Úlive_backlogr  rÀ   T©Ú
backgroundzV3 live broadcast on :zOpen: http://localhost:ú/core/web/index.html?feed=liverÐ  NzJAIS overlay: OFF - the REST source has no store to persist observations toz€Wind feed: OFF - the REST source has no store to write WindReadings to (the backend's own wind poller supplies the wind instead)r   )ÚAISFeed)ÚAisObservationc                 ó   •— ‰ d   S ©NrÐ  r.   ©ré  s   €r&   rc   zrun.<locals>.<lambda>£  s   ø€  v¨e¡}€ r(   c                óD   •— ‰j                  ‰j                  | «      «      S r-   )Úadd_ais_observationÚfrom_ais)Útgtrª  rI   s    €€r&   rc   zrun.<locals>.<lambda>¤  s   ø€ ¨%×*CÑ*CÀN×D[ÑD[Ð\_ÓD`Ó*a€ r(   )rN  ÚpersistzAIS overlay: ON (aisstream.io, ri  z" m radius); observations persistedr�  )ÚWindFeedc                 ó   •— ‰ d   S r¬  r.   r­  s   €r&   rc   zrun.<locals>.<lambda>¯  s   ø€   u¡€ r(   c                 ó   •— ‰ d   S )Nr�  r.   )r  s   €r&   rc   zrun.<locals>.<lambda>°  s   ø€ ¨-¸Ñ*=€ r(   )r  ÚproviderzWind feed: ON (u!    â†’ live WindReadings on course ró   u1    â†’ live WindReadings on each race's own course)z<Wind feed: OFF - needs a course_id to attach WindReadings toé	   r-  r8  rô  r<  r  rè  ré  rç  rä  rå  ræ  r}   rï  r  rê  rë  rì  rí  r  r  r  rÀ   )	rC   r  r#   r%   rO   r}   rì  rô  rÀ   c           	     ó�   •— t        d«      j                  ‰|t        t        j                  «       dz  «      t	        ‰| «      ¬«      S )Nr%   rg   ©Úexpected_line_m)Ú
__import__Úresolve_courser�   r  rg  )rN  ro  rI   s     €r&   rc   zrun.<locals>.<lambda>Í  s?   ø€ ¤*¨XÓ"6×"EÑ"EØ˜3¤¤D§I¡I£K°$Ñ$6Ó 7Ü$;¸EÀ4Ó$Hð #Fó #J€ r(   r  rC   rO   r#   r%   ry  c                 ó   •—  ‰‰‰ «      S r-   r.   )r  rC   Úpinned_courses   €€€r&   rc   zrun.<locals>.<lambda>Ö  s   ø€ ±¸kÈ9Ó0U€ r(   u8   stream ended â€” serving for reconnects. Ctrl-C to stop.r   ú	
stopping)r
   ÚLIVE_BACKLOGrí   r  r7  rD   r©  rI   rª  Ú	wind_feedr³  Úadd_wind_readingr  r™  r  r  r%  r›  r  r  ÚKeyboardInterruptÚstop)*rI   rC   r<  rO   r#   r%   ry  r  r7  rž  rŸ  r   rç  rä  rå  r}   ræ  r¡  rJ  r  r  rÀ   r  rè  r©  rÈ  r³  r-  r8  rï  r  rê  rë  rì  rí  r  Úfollowing_ttsÚ
pinned_ttsrª  ré  r  r¾  s*   ``     `                              @@@@r&   ÚrunrÇ  [  s”  þ€ ôl ˜i¨dÄ×A\ÑA\Ø$.°vô?€Cà‡I�I˜€IÔÜ	Ð" 3§8¡8 *Ð
-Ô.Ü	Ð# C§H¡H :Ð-KÐ
LÔMà˜4�=€FØ
€CÙ�5�=ÜÐZÔ[ØˆÙ˜˜Üð Jô 	KàˆÙÝ$Ý(Ù�gÓ4¸|Üaôcˆà�	‰	ŒÜÐ/°¸SÐ/AÐAcÐdÔeð
   Ð+€MØ€DÙ™) vÐ'9Ý&ÙÓ-¨u×/EÑ/EÛ"=ÈôWˆà�
‰
ŒÜÙ#,ð   ˜Ð.OØ�˜Qñ à ˜Ð._Ð`õ	bñ 
ÜÐLÔMð,ØÐðzCØðzCØðzCØ#ðzCØ%)ðzCØ+/ðzCØ15ðzCØ7;ðzCØ=AñzCØCEÀAðzGÑvˆEÐ# X¨{¸IÀzÐS[Ð]gÐivä'¨
°FÓ;ˆMÜ  sò c±%ð cÑJZð cÙ$1ðcá)1ðcá=FðcáLOðcáX^ðcñ (.ðcñ :Cðcñ PZðcñ *2ð	cñ =Eð	cñ
 *2ðcñ
 @Kðcñ
 W`ðcñ ,6ðcñ AIðcñ V`ðcñ /<ðcñ /<ðcñ ISðcñ \bòcñ Ü˜U C°[ÈIØ"&¨v¸UÈXÜ&5°e¸YÓ&GÌ[ÐYcÐekÓMlØ$*ö,ô
 % Z°Ó8ˆJð
 Ð$©ô	 )óJàRXõZð 9=ð	 ô
 ˜5 #ò +±;ð +Éð +ÑY^ð +Ù $ð+Ù-3ð+ÙALð+ÙXað+á"ð+á+1ð+á:@ð+áLUð+ñ '1ð+ñ <Dð+ñ OWð+ô %4°E¸9Ô$Eð	+ð +8Ð*Cö )VØIMøð+ñ  *ñ+ô 	ÐHÔIØÜ�J‰J�qŒMð øäò ÜˆlÖðúØàˆ?Ø�H‰HŒJØÐØ�I‰IŒKØ�‰�
øð	 ˆ?Ø�H‰HŒJØÐØ�I‰IŒKØ�‰�
ús%   Ä9D>I7 É7JÊK ÊJÊK Ë6K=ÚGSYS)Úuserr<  Úgun_msrõ  r|   r  c       	        óf  — ddl m}
 ddlm} |�|nt	        t        j
                  «       dz  «      }|�|n|}|	�|	n|dz   }|�|n|} || |||t        | |«      ¬«      }|j                  d«      st        | |«      }|r
||d<   d	|d
<   |j                  d«      }|r,|d   d   |d   d   z   dz  |d   d   |d   d   z   dz  dœ|d<    |
| j                  ||«      ||¬«      }t        |j                  |i «      j                  «       D ��ch c]  }|D ]  }|’Œ Œ c}}«      }|j                  d«      st        | |«      }|�|d   |d   dœ|d<   |j                  d«      s9|r7ddlm} |D ],  } || ||«      }|€Œ|j                  |j                   dœ|d<    n |du }|||||d|||ddœ
}|||fS c c}}w )u]  Derive the engine `(meta, course)` + the boats list **from the store** â€” the production path a
    real Postgres-backed trip takes: `course` geometry from `course_roles` via `resolve_course`
    (drifting marks + fallback), boats from the `entry_occupancy` roster. `gun`/`title` are
    management-plane inputs; the rest is derived. Center = start-gate midpoint when known.

    For LIVE, leave the window unset (track spans now â†’ now+24 h). For **regeneration**, pass
    `track_start_ms`/`track_end_ms` = the `[t0, t1]` window to re-render, and `gun_ms`; course + roster
    are resolved at the gun.r   r@   ©r¼  Nrg   r-  )rè   rº  r<  r…  ÚcontestAreaSourcer3  r7  r*   r9  r¢  r+   r-  ré  rB   r   )Úboat_position_atÚteam_race_2)
r|  rÉ  rœ  ÚgroupIdr/  ÚraceTyper  r}  rJ  r0  )rE   rA   r%   r¼  r�   r  rg  r7   rc  rF   rG   r6   r^  r9  rÎ  ré  rê  )rI   rC   r  r|  rÉ  r<  rÊ  rõ  r|   r  rA   r¼  rî   rí   rL  r„  r%   Úcontest_areaÚslrL   rM   rN   rO   rM  rÎ  rñ  r	  r#   s                               r&   Úmeta_course_from_storerÔ  æ  s  € õ* +Ý%ØÐ&‰&¬C´·	±	³¸dÑ0BÓ,C€CØ,Ð8‰N¸c€EØ&Ð2‰,¸¸jÑ8H€CØÐ&‰&¨E€CÙ˜E 9¨c¸Ü,CÀEÈ;Ó,WôY€Fà�:‰:�mÔ$Ü-¨e°[ÓAˆÙØ$0ˆF�=Ñ!Ø*1ˆFÐ&Ñ'Ø	�‰�KÓ	 €BÙ	Ø$& v¡J¨uÑ$5¸¸;¹ÈÑ8NÑ$NÐRSÑ#SØ$& v¡J¨uÑ$5¸¸;¹ÈÑ8NÑ$NÐRSÑ#SñUˆˆxÑáØ×'Ñ'¨°SÓ9Ø˜ô&€Fô  &§*¡*¨[¸"Ó"=×"DÑ"DÓ"F×U˜$ÐPTÒUÈ1’AÐU�AÓUÓV€EØ�:‰:�hÔô (¨¨{Ó;ˆØÐØ'-¨a¡y¸À¹ÑCˆF�8ÑØ�:‰:�hÔ¡Eõ 	+Øò 	ˆAÙ" 5¨!¨SÓ1ˆCØ‰Ø+.¯<©<ÀÇÁÑ#N��xÑ Ùð		ð
 ˜TÐ!€DØ D¸ÐPXØ¨=Ø$°#ÈÐ\`ñb€Dð �˜ÐÐùó7 Vs   ÄF-
)ry  r  rŸ  rè  Ú	max_ticksrç  r  ræ  c               ó„  — ddl m}m} ddlm}  || ||||||¬«      }|d   |d   |d   |d   f\  }}}}|j                  |||i «       t        ||||t        «       ¬	«      }t        | d
d«      }|�H ||«      D �cg c]1  }|j                  |j                  |j                  |j                  dœ‘Œ3 c}|_        |rt        |||«      nd}|d   }t        d„ |D «       «      }|
�|d|
 }dt!        |	d«      z  }di dœ}|D ]Ç  }|t#        |«         }|j%                  ||«      } g }!t'        |«      }"|"�@| j)                  ||dz  z   |¬«      }#t+        | |||dz  z   |#«      }$ ||#|"d   |"d   ||$¬«      }!|�|j-                  || d   |«      n| d   }%|j/                  || d   |%||!| j1                  d«      ¬«       | d   |dœ} ||«       ŒÉ |j3                  «       }&|s3|&j1                  d«      r"|r |j/                  |d   |d   |&d   |d   «       |j5                  «       r |j7                  |j5                  «       «       yyc c}w )uÃ  **Regenerate the complete video from the DB** (docs/plans/youtube-live-demo-hr36.md): rebuild
    the whole feed for the `[trackStart, trackEnd]` window in `meta` from the immutable store
    (`build_feed` â†’ frames from `boat_frames`; teams + wind derived), then re-broadcast it tick-by-tick
    through the same StreamingScorer + LiveFeedServer the live path uses â€” so OBS can re-record a fresh
    MP4. **AIS is replayed per tick** from the persisted `ais_observations` at that tick's UTC, so the
    surrounding traffic comes back too. Deterministic: same window â†’ same broadcast.

    `speed`/`max_ticks`/`sleep` are injectable (tests run bounded + no-wait); `speed=1` re-records in
    real time.r   )ÚDEFAULT_TTL_MSÚ	wire_near)Ú
build_feed)r#   r%   ry  r  r#   r%   r$   rÜ  )ræ  ró  r  NrÍ  r  c              3  ó2   K  — | ]  }t        |«      –— Œ y ­wr-   )r�   r¤  s     r&   r2   zregenerate.<locals>.<genexpr>[  s   è ø€ Ò)˜Q”#�a—&Ñ)ùr  r  g{®Gáz„?)re  r:   rg   r°  r   )rN  rÑ  re  rƒ  rÒ  )rÕ  r^   r:   )rD   r×  rØ  rÇ  rÙ  r6  r   r   rq   rÎ  rJ   r#   r­   rÞ  r'   rG   rx   r   rß  r=   Úais_observations_atrR   râ  rc  r7   r4  r¥  rå  )'rI   r  rC   rO   r#   r%   ry  r  rŸ  rè  rÕ  rç  r  ræ  r×  rØ  rÙ  ÚfeedÚmeta_sÚcourse_sr$   rÜ  r)  r.  rG  r-  rK  rê  ÚdtÚlastrÞ  r:   rë  rí  rì  Úobsrî  rð  Útails'                                          r&   Ú
regeneraterã  -  sž  € ÷4 3Ý)Ù�e˜[¨%°dÀ6Ø"-¸ôD€Dà&*¨6¡l°D¸±NÀDÈÁMÐSWÐX`ÑSaÐ&aÑ#€FˆH�e˜VØ×Ñ�V˜X u¨bÔ1äØ�˜%¨(Ü4Ó6ô€Fô ˜5Ð"4°dÓ;€LØÐá(4°[Ó(AöCØ#$ð +,¯(©(¸1¿6¹6È1Ï6É6Ð]^×]eÑ]eÓfò CˆÔáBHÔ% f¨e°XÔ>Èd€KØÐ*Ñ+€KÜÑ) &Ô)Ó)€DØÐØ�J�YÐˆØ	Œs�5˜$ÓÑ	€BØ#°"Ñ5€DØò ˆØœ3˜s›8Ñ$ˆ	Ø×!Ñ! # yÓ1ˆØˆÜ˜YÓ'ˆØÐØ×+Ñ+¨K¸#À¹*Ñ,DÐQ_Ð+Ó`ˆCÜ˜e [°+ÀÀdÁ
Ñ2JÈCÓPˆDÙ   h¨q¡k°8¸A±;ÈÐdhÔiˆHàÐ*ð × Ñ   e¨KÑ&8¸)ÔDØ05°h±ð 	à×Ñ˜#˜u [Ñ1°4¸ÀHØ"'§)¡)Ð,BÓ"Cð 	ô 	Eà" ;Ñ/¸iÑHˆÙˆb�	ðð �=‰=‹?€DÙ�d—h‘h˜xÔ(©TØ×Ñ˜$˜r™( D¨Ñ$5°t¸H±~ÀtÈKÑGXÔYØ‡}�}„Ø×Ñ˜6Ÿ=™=›?Õ+ð ùò7Cs   Á:6H=)rÊ  ry  r7  rŸ  rè  rç  ræ  c               ó,  — t        | ||||||¬«      \  }}}t        | t        | |«      ||¬«      }t        | |||¬«      \  }}t	        d|¬«      }|j                  d¬«       t        d|›d	|› d
|› d|j                  › �«       t        d|j                  › d�«       	 t        | ||||||||	|
||¬«       t        d«       	 t        j                  d«       Œ# t        $ r t        d«       Y nw xY w	 |j                  «        y# |j                  «        w xY w)a\  Boot the LiveFeedServer and regenerate the `[t0, t1]` window of `instance_id` from `store`.
    Point an OBS Browser source at the printed scene URL to re-record the MP4.

    `producer` defaults to enrich inside StreamingScorer; pass a RieLiveProducer to re-render the
    RIE-only commentary/camera (the near-live-replay recovery + archive path).)rC   r  r|  rÊ  r|   r  rÉ  )r#   ry  r£  )r¤  r7  Tr¦  zRegenerating z [z..z] on :z&Open / point OBS at: http://localhost:r¨  )
rC   rO   r#   r%   ry  r  rŸ  rè  rç  ræ  uA   regeneration complete â€” serving for reconnects. Ctrl-C to stop.r   r¿  N)rÔ  ru  rR  rŒ  r
   rí   r  r7  rã  r  r  rÃ  rÄ  )rI   rC   r  r|  r@  Út1rÊ  ry  r7  rŸ  rè  rç  ræ  r#   r%   rO   r  s                    r&   Úrun_regenerateræ  v  s#  € ô* 1Ø˜;°)À5Ø b°rô;Ñ€Dˆ&�%ô ˜eÔ%:¸5À+Ó%NÐUYØ$)ô+€Dä.¨u°kÈÐZeÔfÑ€Dˆ+Ü
˜i¨dÔ
3€CØ‡I�I˜€IÔÜ	ˆM˜+˜¨¨2¨$¨b°°°F¸3¿8¹8¸*Ð
EÔFÜ	Ð2°3·8±8°*Ð<ZÐ
[Ô\ð
Ü�5˜#¨;¸eÈ$ÐW]Ø*°iÈlØ v¸õ	Bô 	ÐQÔRØÜ�J‰J�qŒMð øäò ÜˆlÖðúØà�‰�
øˆ�‰�
ús$   Â9C ÃC,Ã)D Ã+C,Ã,D ÄD)g+‡©M@gZd;ßO2@g    ®MXAç     €f@)Údemo3dzg-demozdemo3d-coursec           	     óœ   — t         | t        z  z   t        |t        t        j                  t        j
                  t         «      «      z  z  z   fS r-   )Ú_ORIGIN_LATÚ
_M_PER_DEGÚ_ORIGIN_LONr.  ÚcosÚradians)Únorth_mÚeast_ms     r&   Ú_llrñ  ¬  s?   € Ü˜'¤JÑ.Ñ.Ü˜&¤J´·±¼$¿,¹,Ä{Ó:SÓ1TÑ$TÑUÑUðWð Wr(   c                 óÄ  ‡‡‡— ddl m} m}m} ddlm} ddlmŠ ddlm	} ddl
m} t        t        j                  «       dz  «      } |«       } |||j                  «      ŠdD ]F  \  }}	d	|d
dddddd|dz
  dœ
}
‰j                  |j!                  |
|j                  «      |	|dz
  ¬«       ŒH |j#                   |dt$        d|dz
  d«      «       |j#                   |dt$        d|dz
  d«      «       |j'                   |t(        |dz
  ddd«      «       t+        dd«      t+        dd«      t+        dd«      }}}d|fd|fd|fd |fd!|ffD ]'  \  }\  }}|j-                   | t(        |||¬"«      «       Œ) t/        |t$        t(        t0        d#|¬$«      \  }}}d%dd&d'd(œid)œd*dd+d,d(œid)œd-œ}t        j                  «       Šd0ˆˆˆfd.„} ||«       |||||t(        t$        t0        |d/œ	S )1uÀ   Seed an in-memory store with two boats (RED/BLUE) + a wind reading, and return everything the
    driver needs plus a `move_once(t_ms)` that advances the boats â€” call it ~1 Hz from a thread.r   )Ú
CourseRoleÚEntryÚWindReading)ÚIngestService)ÚBoatNavRecord)Ú	qr_coding)ÚInMemoryStorerg   ))Ú2údev-red)Ú3údev-bluerÈ  ÚLimezJ/80ÚMasté   g      Ð¿g333333ó?)
ÚurN   ÚscÚbtÚmtr�  ÚmlÚhÚhdÚiss)rõ  zGSYS:boat:2ÚREDNzGSYS:boat:3ÚBLUEg     àp@ç       @ÚmanualiÄÿÿÿr”  iô  Ú
start_portÚ
start_stbdÚfinish_portÚfinish_stbdÚM1)Ú
static_latÚ
static_lonu   V3 live demo â€” Red v Blue)rC   r  r<  r|  rõ  ÚRedrú  z#C8102E)r£  r—  )rè   rO   ÚBluerü  z#0033A0)r	  r
  c                ó(  •— t        j                   «       ‰z
  }d\  }}dD ]p  \  }}}}|t        d||z
  «      z  d|z  z  }||k  }	||	r|nd|z  |z
  z   }
t        |
|«      \  }}‰j                  ‰j	                  || | d||||	rdnddœ|«      «       Œr y )N)r�  gš™™™™™ù?))rû  g      (Àg      D@rf   )rý  g      (@g      4@r  rf   r¢  Údemorç  )rÕ  Ú	timestampÚgnssTimestampÚ	sessionIdré  rê  rè  r%   )r  rx   rñ  Ú
ingest_navÚ	from_wire)rJ   r�   Úleg_mrÖ   rß  rð  Únorth0Úlag_sÚdÚupÚnorthr*   r+   r÷  Úsvcr@  s                €€€r&   Ú	move_oncez"seed_demo_store.<locals>.move_onceÒ  s¿   ø€ ô —)‘)“+ Ñ"ˆØ‰ˆˆqð+Jò 		8Ñ&ˆC�˜ à”S˜˜g¨™oÓ.Ñ.°1°u±9Ñ=ˆAØ�e‘ˆBØ¡2™a¨1¨u©9°q©=Ñ9ˆEÜ˜5 &Ó)‰HˆC�Ø�N‰N˜=×2Ñ2Ø ¨tÀdÐY_Ø ¨s¸QÙ"$™3¨%ñ1à25ó7õ 8ñ		8r(   )	rI   rO   r#   r%   ry  r  rC   r<  r$  )rJ   r�   rý   rþ   )rE   ró  rô  rõ  r   rö  Úmodelsr÷  r€   rø  rI   rù  r�   r  ÚDEMO_SECRETÚregisterÚ
encode_urlÚ	add_entryÚ	_INSTANCErÂ  Ú_COURSErñ  Úadd_course_rolerÔ  Ú_GROUP)ró  rô  rõ  rö  rø  rù  rî   rI   r   rß  ÚfieldsÚspÚssÚwpr0  r*   r+   r#   r%   rO   ry  r$  r÷  r#  r@  s                         @@@r&   Úseed_demo_storer2  ±  s	  ú€ ÷ ;Ñ:Ý$Ý$ÝÝ#ä
Œd�i‰i‹k˜DÑ Ó
!€CÙ‹O€EÙ
˜˜y×4Ñ4Ó
5€CØ:ò b‰	ˆˆcØ D°¸fÈFØ ¨S¸À#ÈÁ*ñNˆà�‰�Y×)Ñ)¨&°)×2GÑ2GÓHÈ#ÐVYÐ\`ÑV`ˆÕaðbð 
‡O�O‘E˜-¬°E¸3À¹:ÀtÓLÔMØ	‡O�O‘E˜-¬°F¸CÀ$¹JÈÓMÔNØ	×Ñ™;¤w°°d±
¸EÀ3ÈÓQÔRä�Q˜“œc ! R›j¬#¨c°1«+ˆBˆ€BØ*¨BÐ/°,ÀÐ1CØ+¨RÐ0°=À"Ð2EÈÈbÀzðSò YÑˆ‰jˆs�Cà×Ñ™j¬°$À3ÐSVÔWÕXðYô 1Øœ9´Ä&Ø+°Cô9Ñ€Dˆ&�%ð $)°MÈcÐ\eÑCfÐ3gÑhØ$*°mÐPSÐ^gÑEhÐ5iÑjñl€Kô 
�‰‹€B÷8ñ  ˆc„NØ U°DÀFØ&´WÌYÜ¨Yñ8ð 8r(   c                 ó  ‡‡— dd l } dd l}t        j                  t        j                  fD ]"  }t        |d«      sŒ|j                  dd¬«       Œ$ | j                  t        ¬«      }|j                  dt        d¬	«       |j                  d
ddd¬«       |j                  ddd¬«       |j                  dt        ddd¬«       |j                  dddd ddgd¬«       |j                  d| j                  d d¬«       |j                  ddd ¬«       |j                  d!dd"¬«       |j                  d#d$¬%«       |j                  d&d'¬%«       |j                  d(dd)¬«       |j                  d*d+¬%«       |j                  d,d-d.¬/«       |j                  d0d1d2¬/«       |j                  d3d4d5¬/«       |j                  d6d7t        d8¬9«       |j                  d:d;t        d<¬9«       |j                  d=d>d?¬@«       |j                  dAt        dBdC¬D«       |j                  dEddF¬«       |j                  dGddH¬«       |j                  dIddJ¬«       |j                  «       }|j                  rt        |«      S |j                   rt#        |«      S t%        «       Št'        j(                  «       ŠdYˆˆfdK„}t'        j*                  |dLd¬M«      j-                  «        |j.                  r|j0                  j3                  dN«      nd }|j.                  r|st5        dO«       	 t7        ‰dP   ‰dQ   ‰dR   ‰dS   ‰dT   ‰dU   ‰dV   ‰dW   |j8                  ||j:                  |j<                  |j>                  ¬X«       ‰jA                  «        y # ‰jA                  «        w xY w)ZNr   Úreconfigurezutf-8ru   )ÚencodingÚerrors)Údescriptionz--portrœ  )rö   re   z--seedÚ
store_trueTz3offline demo: seed a moving two-boat race (default))Úactionre   Úhelpz--aisz6overlay AIS context vessels (needs $AISSTREAM_API_KEY))r9  r:  z--ais-radiusr�  Ú
ais_radiusz`AIS overlay radius in metres around the boat (default 500; widen for demos in low-traffic spots))rö   re   Údestr:  z--windú?r×  ÚsmhizClive wind from a real provider: open-meteo (default, no key) | smhi)ÚnargsÚconstre   Úchoicesr:  z--neural-ttsa›  prepare every spoken line as a Google neural clip ahead of air, served from this producer and played as audio. DEFAULT ON with --live. Needed for a stream to have any voice in a renderer whose browser has no speech synthesis (OBS's embedded Chromium), where the Web Speech fallback is silent. Needs ADC + the Text-to-Speech API; failures degrade to that fallback, never to no show. --no-neural-tts turns it off.z--livezJbroadcast a real prod instance live from the DB (with --instance --course)z--regeneratezJre-render a past window from the DB (with --instance --course --from --to)z
--instancez#instance id to broadcast/regenerate)r:  z--coursez!course id to broadcast/regeneratez--followu&  with --live: FOLLOW the operator's races on --course instead of pinning one --instance. The broadcast stays with the race on air until it has a winner, then moves to the next â€” one process for the whole regatta day, no restart per heat (the renderer swaps the race in place, it never reloads)z--ownerz4with --live --follow: only follow this owner's racesz--asrun-logrw  u”  append the AS-RUN here (JSONL): race hand-overs plus selected and actually started commentary variants. The only record of what was actually shown or started in the programme renderer â€” those decisions otherwise live in memory and are forgotten. Feeds the metadata packager; commentary audit rows are ignored by the race partition. Unset = file off (commentary rows still go to structured runtime logs))r<  r:  z--read-baserä  uµ  poll live positions from this read-API base (e.g. https://apps.viewregatta.com) instead of per-tick DB queries â€” the co-located cached /positions endpoint (Phase 0 of docs/plans/live-broadcast-feed-in-gcloud.md). With --follow and no $VR_DATABASE_URL the WHOLE producer runs over REST: no database, so the same command works against a local backend or gcloud, from the render PC or the cloud, over live, replay or simulated races alikez--read-tokenrå  z:bearer token for --read-base when the races are not publicz--fromr@  zwindow start (UTC ms))r<  rö   r:  z--torå  zwindow end (UTC ms)z--titleúRegenerated racez broadcast title for regeneration)re   r:  z--speedr  u)   re-render rate Ã—realtime (1 = real time))rö   re   r:  z--riezQregenerate with the RIE-only producer (base+tactical narration) instead of enrichz--simpleuš   factual, team-racing-FREE commentary (non-team-race formats, e.g. the GSYS sprint): start/leader/gaps/roundings/winner only â€” no combinations or rule-18z--cruiseu6  with --live --instance: the recreational DEVICE TOUR instead of a scored race â€” pan the venue, visit every device with a fly-in and its own camera as a large panel, present only the boat's name (+ a verification strip). No race furniture, no scorer. Also selected by the event's presentation.mode == 'cruise'c                 ó¾   •— ‰j                  «       sL ‰ d   t        t        j                  «       dz  «      «       ‰j                  d«       ‰j                  «       sŒKy y )Nr$  rg   r  )Úis_setr�   r  Úwait)r   rÄ  s   €€r&   Úmoverzmain.<locals>.moverF  s@   ø€ Ø—+‘+”-ØˆAˆk‰Nœ3œtŸy™y›{¨TÑ1Ó2Ô3Ø�I‰I�cŒNð —+‘+—-r(   z
demo-mover)rç   rè   ré   ÚAISSTREAM_API_KEYú3AIS overlay: OFF - set $AISSTREAM_API_KEY to enablerI   rC   r<  rO   r#   r%   ry  r  )rC   r<  rO   r#   r%   ry  r  r7  rž  rŸ  r   rç  rÿ  )!Úargparser§   r   ÚstdoutÚstderrrš  r4  ÚArgumentParserr  Úadd_argumentr�   r™   ÚBooleanOptionalActionÚ
parse_argsrã  Ú_main_regenerater	  Ú
_main_liver2  rÇ   ÚEventrë   rí   rè  r¨   r7   r  rÇ  r7  r;  rÈ  rç  rÌ   )	rI  r§   Ú_streamÚaprâ   rF  rž  r   rÄ  s	          @@r&   ÚmainrU  è  s–  ù€ ÛÛô —J‘J¤§
¡
Ð+ò DˆÜ�7˜MÕ*Ø×Ñ¨¸ÐÕCðDð 
×	 Ñ	 ¬WÐ	 Ó	5€BØ‡O�O�H¤3°€OÔ5Ø‡O�O�H \¸4ØNð ô Pà‡O�O�G LØQð ô Sà‡O�O�N¬¸ÀLð7ð ô 8ð ‡O�O�H C¨|ÀTØ)¨6Ð2Ø^ð ô `ð ‡O�O�N¨8×+IÑ+IÐSWðUð ô Vð ‡O�O�H \Øeð ô gð ‡O�O�N¨<Øeð ô gà‡O�O�LÐ'L€OÔMØ‡O�O�JÐ%H€OÔIØ‡O�O�J |ð]ð ô ^ð
 ‡O�O�IÐ$Z€OÔ[Ø‡O�O�M¨ð@ð ô Að ‡O�O�M¨ðQð ô Rð ‡O�O�N¨ØUð ô Wà‡O�O�H 4¬cÐ8O€OÔPØ‡O�O�F ¬CÐ6K€OÔLØ‡O�O�IÐ'9Ð@b€OÔcØ‡O�O�I¤E°3Ð=h€OÔiØ‡O�O�G LØlð ô nà‡O�O�J |ðkð ô lð ‡O�O�J |ð9ð ô :ð �=‰=‹?€Dà‡‚Ü Ó%Ð%Ø‡y‚yÜ˜$ÓÐäÓ€Aä�?‰?Ó€Döô ×Ñ˜E¨¸TÔB×HÑHÔJà59·X²Xˆb�j‰j�n‰nÐ0Ô1À4€GØ‡x‚x™ÜÐCÔDðÜˆAˆg‰J A mÑ$4¸qÀ¹}ÐTUÐV]ÑT^Ø�6‘ 1 X¡;¸A¸mÑ<LØ˜‘n¨4¯9©9¸gØŸ™¸¿	¹	È$Ï+É+õ	Wð
 	�‰�
øˆ�‰�
ús   ÌAM: Í:Nc                ó¨  ‡ ‡'‡(‡)‡*‡+‡,‡-‡.‡/‡0‡1‡2‡3‡4‡5‡6‡7‡8— t        ‰ dd«      }t        ‰ dd«      Š4t        «       }|rt        d«       ‰ j                  s|st	        d«      ‚‰ j                  r‰ j
                  st	        d«      ‚t        j                  j                  d«      }|xr | xr t        ‰4«      }|s|st	        d	«      ‚dŠ6|rd
dl
m}m}  | ||«      «      Š6d_ˆ ˆ6fd„}	 	 d`ˆ fd„	Š-d
dlmŠ* d
dlmŠ3 d
dlmŠ' d
dlmŠ, daˆ3fd„Š+d}d}	d}
d}d}d}d}dŠ1‰4r�d
dlm}  |‰4t        ‰ dd«      ¬«      Š1ˆ1fd„}ˆ'ˆ ˆ4fd„}ˆ1fd„}t+        «       rt        d«       nt-        |d¬«      }t        ‰1d‰1j.                  «      }t1        ||¬«      }
t3        ˆ1fd„«      }t5        ˆ1fd „«      }‰1�t7        ‰1«      ndŠ5|r³d
d!lm} ‰1Š0t;        ‰0j<                  ‰ j
                  ‰ j>                  ˆ0ˆ3fd"„¬#«      } |‰0‰ j
                  ‰ j@                  d$k7  r‰ j@                  nd¬%«      }i i g df\  }}}}tC        ‰0jD                  «      Š8ˆ ˆ0ˆ8fd&„Š(ˆ ˆ0fd'„Š.ˆ'ˆ(ˆ-ˆ.ˆ ˆ0ˆ4ˆ5fd(„}	t        d)‰4› d*�«       �n	|r‚t;        ˆ ˆ6fd+„‰ j
                  ˆ+ˆ6fd,„¬-«      }i i g df\  }}}}tC        ˆ6fd.„«      Š8ˆ ˆ6ˆ8fd/„Š)ˆ ˆ6fd0„Š/|€ˆ/fd1„}|€i Š2ˆ*ˆ2ˆ6fd2„}t5        |«      }‰1�ˆ'ˆ ˆ4fd3„nˆ,ˆ6fd4„Š7‰7}ˆ)ˆ*ˆ-ˆ/ˆ ˆ5ˆ6ˆ7fd5„}	�n…‰6jG                  ‰ j                  «      }|�t        |d6d«      nd}tI        ‰6‰ j                  ‰ j
                  ‰ j@                  |¬7«      \  }}}tK        ‰6tM        ‰6‰ j                  «      ||¬8«      }tO        ‰6‰ j                  |¬9«      \  }}|r’d
d:l(m)} d
d;l*m+} |�tY        |«      ntY        t[        jZ                  «       d<z  «      } |‰6j]                  ‰ j                  |«      |‰ j                  ¬=«      j                  ‰ j                  i «      } |||«      \  }} | r| }|�S|j                  d>«      xs i j                  d?«      xs d@}!tY        |«      |dA<   tY        |«      tY        |!«      d<z  z
  |dB<   t        t        ‰ dCd«      xs |j                  dD«      dCk(  «      }"|"r|rt	        dE«      ‚‰ j^                  rt        j                  j                  dF«      nd}#‰ j^                  r|#st        dG«       d}$t        ‰ dHd«      rft`        jb                  je                  d
tg        ti        tj        «      jm                  «       jn                  dI   «      «       d
dJl8m9}% d
dKl:m;}&  |&|%¬L«      }$ty        ‰6fi d‰ j                  “dM|j                  dNdO«      “dP|“dQ|“dR|“dS|“dT‰ j
                  “dU‰ jz                  “dV|#“dW‰ j|                  “dX‰ j~                  “dY|rdnt€        “dZt        ‰ dZd«      “dt        ‰ dd«      “dt        ‰ dd«      “d[|$“d|r|||	|
||||f	nd“dC|"“d\tƒ        ‰ «      “d]t…        j†                  t        ‰ d^d«      «      “Ž y)buý  `--live` CLI: broadcast a **real prod instance** live from Postgres â€” the production counterpart
    to the seeded demo. Meta/course/boats are derived from the store (`meta_course_from_store`, no
    window â†’ `openEnded=True`, boats from the roster); `run` then publishes the live store forever
    with the same AIS/wind overlays as the demo path. The phones upload nav into this same store, so the
    scene follows the real track. Needs `$VR_DATABASE_URL` (e.g. the prod store via the Cloud SQL proxy).r¡  Frä  NuH   [live] camera media OFF â€” clips and persistent last frames are omittedz/--live needs either --instance <id> or --followzG--live --instance needs --course <id> (a pinned race states its course)ÚVR_DATABASE_URLzˆ--live needs $VR_DATABASE_URL (the Postgres store the phones upload to), or --follow --read-base <url> to run entirely over the REST APIr   ©ÚPostgresStoreÚconnectc                ó  •— ‰j                  | «      }‰j                  xs t        |dd«      }t        ‰| |‰j                  |¬«      \  }}}||d<   t        ‰t        ‰| «      ||¬«      }t        ‰| |¬«      \  }}|rGddlm	} dd	l
m}	  |‰j                  | |«      || ¬
«      j                  | i «      }
 |	|
|«      \  }}|r|}|j                  d«      xs i j                  d«      xs d}||d<   |t        |«      dz  z
  |d<   ||||fS )u  One race's `(meta, course, boats, team_config)`. The gun is passed to `meta_course_from_store`
        so the roster â€” and therefore the teams, the standings and the camera â€” is resolved at the
        OPERATOR'S start signal, not at whatever moment the producer happened to look.r  N©rC   r  r|  rÊ  r+  rÉ  rõ  r   r@   ©Úactive_presentation_rosterrB   rV  rW  r,  r}  rg   r  )rL  r%   rq   rÔ  r|  ru  rR  rŒ  rE   rA   Úroster_scoper^  rF   r7   r�   )r4  rÊ  Úinst_rowÚrace_courserP   ÚcrN   r‹  rA   r^  r5  Ú_active_rosterÚactive_boatsÚpre_srâ   rI   s                 €€r&   Ú_presentation_forz%_main_live.<locals>._presentation_forx  s1  ø€ ð ×*Ñ*¨8Ó4ˆØ—k‘kÒI¤W¨X°{ÀDÓ%IˆÜ(¨¸HÐP[Ø/3¯z©zÀ&ôJ‰ˆˆ1ˆað $ˆˆ*‰ô ˜uÔ&;¸EÀ8Ó&LÐSTØ%&ô(ˆä'¨¨x¸aÔ@‰ˆˆ3Ùõ 3Ý?Ù%Ø×/Ñ/°¸&ÓAØ Hô.ç.1©c°(¸BÓ.?ð ñ ,FÀhÐPSÓ+TÑ(ˆN˜LÙØ �Ø—%‘%˜Ó(Ò.¨B×3Ñ3°KÓ@ÒHÀSˆØˆˆ*‰Ø%¬¨E«
°TÑ(9Ñ9ˆÐ
ÑØ�!�Q˜ˆ|Ðr(   c                ó  •‡‡‡—  | «       }|€y|dd \  Š}t        |«      dkD  r|d   nd}t        |t        «      r|g}	n3t        |t        «      r!|D �
cg c]  }
t        |
t        «      sŒ|
‘Œ }	}
ng }	 |‰«      Š‰sy‰j                  dk7  r‰j                  nd‰j
                  xs d‰dddd	œ}|� ||‰t        |«      «      n|}‰�i |¥ ‰‰«      xs i ¥}t        |‰«      }|�i |¥d
 |‰«      xs di¥}|‰�ˆˆˆfd„nd‰t        |«      D �cg c]L  }d|j                  dd«      d   › �|j                  dd«      d   ||j                  dd«      d   |ddddœgdœ‘ŒN c} |‰t        |«      «      |	dd dœS c c}
w c c}w )u6  The WAITING presentation's `_Waiting` kwargs, or None without a race-day definition.

        A producer pointed at a race day spends its first minutes â€” and every gap between heats â€” with
        no race on air. Without this it publishes NOTHING, so the OBS browser source sits blank until
        the first gun: no venue, no fleet, none of the area-panning idle camera we built for exactly
        this moment. Only the SIMULATOR supplied one, so the picture was right in rehearsal and blank
        in production â€” the worst way round.

        What it shows is the NEXT race's venue and, when present, its allocated fleet. Boat meshes
        remain team-less spectators because no race has started; the programme card may nevertheless
        name a matchup when the race office has explicitly published a complete ``teamConfig``. An
        empty allocation still shows the venue: allocation is marshalling state, not a gate on the
        model's waiting state. `_Waiting` adds `idle` and the sentinel clock; no scorer runs, since
        there is no race to score.Nr¢  rB  zWaiting for a racer¡   r  rÏ  T)r|  rÉ  rœ  rÐ  rÑ  r.  r+  c                 ó(   •— t         ‰‰«      ‰ «      S r-   )rÉ  )rA  r4  rì  s   €€€r&   rc   z6_main_live.<locals>._waiting_picture.<locals>.<lambda>ã  s   ø€ Ô"8¹À(Ó9KÈSÓ"Q€ r(   r”  r�   r   r^   r•  r   )rÕ  r£  r–  r—  r˜  r1  r™  rD  )r#   rì  r%   r$   r  r*  )	r9   rÛ   rÜ   rÞ   r|  rq  rG   rÉ  rv   )Ú	next_raceÚ
course_geor  rì  Úcourse_id_forÚapply_eventÚpickedrO   Úpublished_upcomingr*  r‘   r#   rN   rA  r4  râ   s      `         @@€r&   Ú_waiting_picturez$_main_live.<locals>._waiting_picture   sï  û€ ñ  “ˆØˆ>ØØ   !˜*‰ˆ�%Ü*-¨f«+¸ª/˜V AšY¸tÐÜÐ(¬$Ô/Ø*Ð+‰HÜÐ*¬DÔ1Ø'9ÖS ¼ZÈÌTÕ=RšÐSˆHÑSàˆHÙ˜Ó"ˆÙØð (,§z¡zÐ5GÒ'G˜ŸšÐMaØŸ
™
Ò( b¸ÈSØ)°4ñ9ˆð >IÐ=T‰{˜4 ¬6°%«=Ô9ÐZ^ˆð ÐØ9�dÐ9™x¨Ó1Ò7°RÐ9ˆDÜ% d¨CÓ0ˆð Ð$ØH�dÐH˜J©°hÓ(?Ò(GÀ4ÑHˆDàð %Ð0ö RØ6:Øô  & e›}ö.ð ð $)¨¯©°#°qÓ)9¸"Ñ)=Ð(>Ð!?ÈÏÉÐRUÐWXÓIYÐZ\ÑI]Ø./¸1¿8¹8ÀCÈÓ;KÈBÑ;OÐ]^Ø+4ÀÐPTñ"Vð !WóXò .ñ  -¨X´v¸e³}ÓEð !  !˜ñ!
ð 	
ùòQ Tùò`.s   ÁE=Á*E=ÄAF
rÌ  )Úpre_start_secondsrü  r®  rC   c                ó~   •— | j                  |«      }|�t        |dd«      nd}|r ‰| j                  |«      «      S dS )u£   The event sequence length for a race, over the store. The REST twin is one call on the
        backend; this side has to walk instance â†’ event â†’ config itself.NrQ  )rL  rq   r[  )rI   rC   rN  rQ  rp  s       €r&   Ú_store_pre_start_sz&_main_live.<locals>._store_pre_start_sø  sI   ø€ ð ×&Ñ& {Ó3ˆØ6:Ð6F”7˜4 ¨TÔ2ÈDˆÙFNÑ  ×!7Ñ!7¸Ó!AÓBÐXÐTXÐXr(   )ÚRestBackendrå  rþ  c                ó.   •— ‰j                  | |||¬«      S )N)r  r  r|   ©rÜ  )rN  Ú_boatsr@  rå  Úhot_backends       €r&   rc   z_main_live.<locals>.<lambda>  s!   ø€ °×0BÑ0BØ˜ "°Rð 1Có 19€ r(   c           	     ó0   •—  ‰‰| t        ‰dd «      ¬«      S ©Nrå  rþ  ©rq   ©rN  rv  Ú_http_positionsrâ   rä  s     €€€r&   rc   z_main_live.<locals>.<lambda>  s   ø€ ©_Ø�t¤7¨4°¸tÓ#Dô.F€ r(   c                ó0   •— ‰j                  | |||d¬«      S )Ng      @)r  r  r|   Ú	timeout_sru  )rN  rv  r  r  rK  rw  s        €r&   Ú_load_frame_tailz$_main_live.<locals>._load_frame_tail!  s(   ø€ Ø×%Ñ% d°6ÀØ5@ÈCð &ó Qð Qr(   u?   [live] track tail OFF â€” using current-position telemetry onlyr	  r·   Ú
live_media)r»   c           
     ó   •— t        ‰j                  | |t        t        j                  «       dz  «      d¬«      x}j	                  d«      |j	                  d«      |j	                  d«      |j	                  d«      ¬«      S )	Nrg   r  )r~  ÚdirDegÚspeedKtsr­   rÍ  r¹  )rÄ  rÈ  r�   r  r7   )rN  ro  r   rw  s      €r&   rc   z_main_live.<locals>.<lambda>6  sg   ø€ œjØ!×&Ñ& t¨S´#´d·i±i³kÀDÑ6HÓ2IÐUXÐ&ÓYÐY�×^Ñ^Ð_gÓhØ—‘�jÓ! 1§5¡5¨£?¸Q¿U¹UÀ9Ó=MôO€ r(   c                ól   •— ‰j                  | |t        t        j                  «       dz  «      dd¬«      S )Nrg   r   g      ø?)Úattemptsr~  )r%   r�   r  )rN  ro  rw  s     €r&   rc   z_main_live.<locals>.<lambda>:  s3   ø€ ˜k×0Ñ0Ø�cœ3œtŸy™y›{¨TÑ1Ó2¸QÈ#ð 1ó O€ r(   )Úrest_presentation_forc                óP   •—  ‰‰j                  ‰j                  | «      «      «      S r-   )rf  Úevent_of)rN  r˜  rp  s    €€r&   rc   z_main_live.<locals>.<lambda>C  s'   ø€ Ñ8IØ!(×!5Ñ!5°g×6FÑ6FÀtÓ6LÓ!Mó9O€ r(   )r  rq  Úpre_start_forrB  )r  r|  c                 ó  •— t        t        j                  «       dz  «      } t        ‰j                  «       d„ ¬«      }g }|D ]Ú  }|j	                  d«      }|�t        |«      | k  s|j	                  d«      �Œ6|j	                  d«      dv rŒJ‰j
                  r|j	                  d	«      ‰j
                  k7  rŒu‰j                  rt        |«      d‰j                  fvrŒ›|j	                  d
«      }‰j
                  s|j	                  d	«      sŒÊ|j                  |«       ŒÜ |syg }|dd D ]{  }|j	                  d
«      }|t         ‰|«      «      dœ}|j	                  d«      }|�t        |«      |d<   |j	                  d«      rt        |d   «      |d<   |j                  |«       Œ} |d   j	                  d
«      }‰j                  || «      xs i }	|	j                  «       D �
�ch c]  }
|
D ]  }|’Œ Œ }}
}|||fS c c}}
w )ú<The next race's fleet plus the confirmed upcoming programme.rg   c                óž   — | j                  d«      d u t        | j                  d«      xs d«      t        | j                  d«      xs d«      fS )NÚofficialStartUtcMsr   rœ  r¡   )r7   r�   r   r‘  s    r&   rc   z0_main_live.<locals>._next_rest.<locals>.<lambda>T  sJ   € Ø—‘Ð,Ó-°Ð5Ü�C—G‘GÐ0Ó1Ò6°QÓ7¼¸S¿W¹WÀ\Ó=RÒ=XÐVXÓ9Yð@[€ r(   rÚ  r�  NÚofficialStopUtcMsÚ	raceState©Ú	abandonedr^  r+  rœ  rD  ©rœ  r$   rE  rè   r|  r   )r�   r  rG   rž  r7   r%   rq  r’  rw   r­  r   rL   r6   )rî   r“   rK   r‘   r„  rN  r	  rk  r4  ÚgridrM   rN   ré  râ   r˜  Úwaiting_team_configs                €€€r&   Ú
_next_restz_main_live.<locals>._next_restQ  sú  ø€ ä”d—i‘i“k DÑ(Ó)ˆCÜ˜'×+Ñ+Ó-ñ 4[ô \ˆDð ˆJØò '�Ø—g‘gÐ2Ó3�Ø�O¬¨C«°Cª¸C¿G¹GÐDWÓ<XÐ<dØØ—7‘7˜;Ó'Ð+EÑEØØ—;’; 3§7¡7¨:Ó#6¸$¿+¹+Ò#EØð —:’:¤*¨S£/¸$ÀÇ
Á
Ð9KÑ"KØØ—w‘w˜|Ó,�ð Ÿš s§w¡w¨zÔ':ØØ×!Ñ! #Õ&ð+'ñ, ØàˆIØ! " 1�~ò 	'�Ø—w‘w˜|Ó,�Ø&*Ü!9Ñ:MÈdÓ:SÓ!TñV�à—g‘gÐ2Ó3�Ø�?Ü25°c³(�DÐ.Ñ/Ø—7‘7˜6”?Ü$'¨¨F©Ó$4�D˜‘MØ× Ñ  Õ&ð	'ð " !‘}×(Ñ(¨Ó6ˆHØ—>‘> (¨CÓ0Ò6°BˆDØ#'§;¡;£=×?˜4¸$Ò?°Q’QÐ?�QÐ?ˆEÑ?Ø˜U IÐ-Ð-ùó @s   Ç$G<c                ó>  •— ‰j                  | ‰j                  xs ‰j                  | «      t        t	        j                  «       dz  «      «      xs i }t        |j                  d«      |j                  d«      |j                  d«      |j                  d«      ¬«      S )Nrg   r‚  rƒ  r­   rÍ  r¹  )rÈ  r%   Ú	course_ofr�   r  rÄ  r7   )rN  r   râ   r˜  s     €€r&   Ú
_wind_restz_main_live.<locals>._wind_rest‚  s   ø€ Ø—‘˜T 4§;¡;Ò#I°'×2CÑ2CÀDÓ2IÜ ¤§¡£¨tÑ!3Ó4ó6ò <Ø9;ð ä˜aŸe™e H›o¨q¯u©u°ZÓ/@À!Ç%Á%ÈÃ/Ø'(§u¡u¨YÓ'7ô9ð 9r(   c            	     óB   •—  ‰‰ˆˆfd„ˆ ˆˆfd„‰ˆˆfd„ˆˆfd„¬«      S )Nc                ó    •— ‰j                  | ‰j                   xs ‰j                  | «      t        t        j                  «       dz  «      «      S rø  )r%   r—  r�   r  ©rN  râ   r˜  s    €€r&   rc   z._main_live.<locals>.<lambda>.<locals>.<lambda>Š  s<   ø€ ˜Ÿ™¨¨d¯k©kÒ.T¸W×=NÑ=NÈtÓ=TÜ(+¬D¯I©I«K¸$Ñ,>Ó(?óA€ r(   c                ó.   •—  ‰‰| ‰j                   ¬«      S )Nrþ  )rå  r{  s     €€€r&   rc   z._main_live.<locals>.<lambda>.<locals>.<lambda>Œ  s   ø€ ¡°¸DÈÏÉÔ!X€ r(   c                óB   •— ‰j                   xs ‰j                  | «      S r-   )r%   r—  r›  s    €€r&   rc   z._main_live.<locals>.<lambda>.<locals>.<lambda>Ž  s   ø€ ˜Ÿ™Ò?¨×(9Ñ(9¸$Ó(?€ r(   c                ó”   •— t        | ‰j                  ‰j                  |«      «      t        |«      xs ‰j	                  |«      ¬«      S )Nrs  )r	   rf  rˆ  rt  rŸ  )r#   rN  rO   r˜  Úread_owner_namess      €€r&   rc   z._main_live.<locals>.<lambda>.<locals>.<lambda>‘  sB   ø€ Ô2FØ�g×*Ñ*¨7×+;Ñ+;¸DÓ+AÓBÜ  Ó'ÒNÐ+;×+HÑ+HÈÓ+Nô3Q€ r(   ©rl  r.   )r|  r•  ro  r˜  râ   r˜  rä  rŸ  s   €€€€€€€€r&   rc   z_main_live.<locals>.<lambda>ˆ  s&   ø€ Ñ.ØôAåXØÜ?ôQôR€ r(   zfollowing races over REST at z (no database)c                 óD   •— ‰j                  ‰ j                  xs d ¬«      S )N©Úowner_user_id)Úlist_race_instancesrq  )râ   rI   s   €€r&   rc   z_main_live.<locals>.<lambda>›  s   ø€  U×%>Ñ%>ÈdÏjÉjÒN`Ð\`Ð%>Ó%b€ r(   c                ó   •—  ‰‰| «      S r-   r.   )rN  rr  rI   s    €€r&   rc   z_main_live.<locals>.<lambda>�  s   ø€ Ñ8JÈ5ÐRVÓ8W€ r(   )r  r‰  c                óT   •— ‰j                  | «      xs i j                  d«      xs i S )Nr‰  )rŠ  r7   )rN  rI   s    €r&   rc   z_main_live.<locals>.<lambda>   s(   ø€ ˜%×4Ñ4°TÓ:Ò@¸b×EÑEÀlÓSÒYÐWY€ r(   c                 óÔ  •— ddl m}  t        t        j                  «       dz  «      }t	        ‰j                  ‰j                  xs d¬«      d„ ¬«      }g }|D ]“  }|j                  �t        |j                  «      |k  rŒ(|j                  €t        |dd«      d	v rŒD‰j                  r|j                  ‰j                  k7  rŒj‰j                  s|j                  sŒƒ|j                  |«       Œ• |syg }|dd
 D ]„  }|j                  t         ‰|j                  «      «      dœ}|j                  �t        |j                  «      |d<   t        |dd«      rt        |j                   «      |d<   |j                  |«       Œ† |d   j                  } | ‰j#                  ||«      ||¬«      j%                  |i «      }|j'                  «       D �	�
ch c]  }	|	D ]  }
|
’Œ Œ }}	}
|||fS c c}
}	w )r‹  r   r@   rg   Nr¢  c                óx   — | j                   d u t        | j                   xs d«      t        | j                  «      fS )Nr   )Úofficial_start_utc_msr�   r   r�  )rN  s    r&   rc   z1_main_live.<locals>._next_store.<locals>.<lambda>¨  s5   € Ø×.Ñ.°$Ð6Ü˜×2Ñ2Ò7°aÓ8¼#¸d¿g¹g»,ð"H€ r(   rÚ  Ú
race_stater�  rD  r’  rE  rè   r|  rB   )rE   rA   r�   r  rG   r¤  rq  r©  Úofficial_stop_utc_msrq   r%   r  rw   r�  r­  r   rè   rF   r7   r6   )rA   rî   rž  rK   rN  r	  rk  r4  r“  rM   rN   ré  râ   rI   r”  s               €€€r&   Ú_next_storez_main_live.<locals>._next_store¢  sæ  ø€ å2Ü”d—i‘i“k DÑ(Ó)ˆCÜØ×)Ñ)¸¿¹Ò9KÀtÐ)ÓMñHôIˆIð
 ˆJØ!ò (�Ø×.Ñ.Ð:Ü × :Ñ :Ó;¸sÒBØØ×,Ñ,Ð8¼GØ˜l¨Dó=2Ø5Oñ=PàØ—;’; 4§>¡>°T·[±[Ò#@Øð Ÿš t§~¢~ØØ×!Ñ! $Õ'ð(ñ ØàˆIØ" 2 A˜ò '�Ø&*§g¡gÜ!9Ñ:MÈdÏgÉgÓ:VÓ!WñY�à×-Ñ-Ð9Ü25°d×6PÑ6PÓ2Q�DÐ.Ñ/Ü˜4 ¨Ô.Ü$'¨¯	©	£N�D˜‘MØ× Ñ  Õ&ð'ð " !‘}×'Ñ'ˆHÙ!Ø×/Ñ/°¸#Ó>Ø ô+ç+.©3¨x¸Ó+<ð ð $(§;¡;£=×?˜4¸$Ò?°Q’QÐ?�QÐ?ˆEÑ?Ø˜U IÐ-Ð-ùó @s   ÇG$c                óV  •— ddl m} ‰j                  xs  t        ‰j	                  | «      dd «      xs d}|r6 |‰j                  |«      t        t        j                  «       dz  «      «      nd }|r7t        |j                  |j                  |j                  |j                  ¬«      S i S )Nr   r  r  r¡   rg   r¹  )rE   r  r%   rq   rL  r  r�   r  rÄ  rÂ  rÃ  r­   rº  )rN  r  ro  r   râ   rI   s       €€r&   Ú_wind_storez_main_live.<locals>._wind_storeÏ  s�   ø€ Ý0Ø—+‘+Ò`¤¨×)@Ñ)@ÀÓ)FÈÐUYÓ!ZÒ`Ð^`ˆCÙWZ‘˜U×4Ñ4°SÓ9¼3¼t¿y¹y»{ÈTÑ?QÓ;RÔSÐ`dˆAá67ô ˜aŸi™i¨¯©°a·h±hØ'(§z¡zô3ð @Ø=?ð@r(   c                ó   •—  ‰| «      S r-   r.   )rN  Ú_cidr®  s     €r&   rc   z_main_live.<locals>.<lambda>×  s   ø€ ©;°tÓ+<€ r(   c                ó„   •— | ‰vrt        ‰| «      ‰| <    ‰‰|t        t        j                  «       dz  «      ‰|    ¬«      S )Nrg   r¹  )rg  r�   r  )rN  ro  Ú_resolve_courseÚline_lengthsrI   s     €€€r&   Ú_course_storez!_main_live.<locals>._course_storeÛ  sH   ø€ Ø˜|Ñ+Ü)@ÀÈÓ)M�L Ñ&Ù&Ø˜3¤¤D§I¡I£K°$Ñ$6Ó 7Ø$0°Ñ$6ô8ð 8r(   c           	     ó0   •—  ‰‰| t        ‰dd «      ¬«      S ry  rz  )rN  Ú_fleetr|  râ   rä  s     €€€r&   rc   z_main_live.<locals>.<lambda>å  s   ø€ ¡/Ø˜4¤w¨t°\À4Ó'Hô#J€ r(   c                ó   •—  ‰‰|«      S r-   r.   )Ú_instré  Ú_v3_positionsrI   s     €€r&   rc   z_main_live.<locals>.<lambda>è  s   ø€ ¡-°°uÓ"=€ r(   c            	     ó:   •—  ‰‰ ˆˆˆfd„‰‰ˆˆfd„ˆˆfd„¬«      S )Nc                ó¬   •—  ‰‰‰j                   xs  t        ‰j                  | «      dd «      xs dt        t	        j                  «       dz  «      «      S )Nr  r¡   rg   )r%   rq   rL  r�   r  )rN  r²  râ   rI   s    €€€r&   rc   z._main_live.<locals>.<lambda>.<locals>.<lambda>í  sH   ø€ ™Ø�t—{‘{Òe¤g¨e×.EÑ.EÀdÓ.KÈ[ÐZ^Ó&_ÒeÐceÜ”D—I‘I“K $Ñ&Ó'ó)€ r(   c                óX   •— ‰j                   xs t        ‰j                  | «      dd «      S )Nr  )r%   rq   rL  )rN  râ   rI   s    €€r&   rc   z._main_live.<locals>.<lambda>.<locals>.<lambda>ò  s%   ø€ ˜Ÿ™Òa¬°×0GÑ0GÈÓ0MÈ{Ð\`Ó(a€ r(   c           	     óh   •— t        ‰t        ‰|«      | |‰r‰j                  |«      ¬«      S d ¬«      S )N)r#   rO   rq  )ru  rR  rŸ  )r#   rN  rO   rŸ  rI   s      €€r&   rc   z._main_live.<locals>.<lambda>.<locals>.<lambda>ó  s;   ø€ Ô2DØÔ,¨U°DÓ9ÀÈEÙ>NÐ'×4Ñ4°TÓ:ô3[€ àTXô3[€ r(   r   r.   )r¬  r²  ro  r®  râ   rŸ  rI   Úwaiting_positionss   €€€€€€€€r&   rc   z_main_live.<locals>.<lambda>ë  s'   ø€ Ñ.Øõ)ð ØÜaô[ô
\€ r(   r©  r\  rÉ  rõ  r@   r]  rg   rB   rV  rW  r,  r}  r  rJ  r¢  zc--cruise is a pinned presentation: use --live --instance <id> --course <id> --cruise (not --follow)rG  rH  r„  rh   ©Úenrich©ÚRieLiveProducer©r)  r<  rÐ  r  rO   r#   r%   ry  r  r7  rž  rŸ  r   r}   rç  ræ  r  r  rw  )r4  r   rÊ  r�   rý   rÝ   )NNN)rC   r   rý   rü  )Drq   r®   r  r4  Ú
SystemExitr%   r§   r¨   r7   rY  Úpostgres_storerY  rZ  r¼  rf  rp  r"  rý  r¯  Úrest_sourcers  r±   r  rs   r¼   r  r%  r”  r†  r   rž  rq  r|  r¯  ry  rL  rÔ  ru  rR  rŒ  rE   rA   r_  r^  r�   r  rF   rè  r   r   r   r   r   Ú__file__Úresolver!   Ú
score_racerÀ  Úv3.rie.live_producerrÂ  rÇ  r7  r;  rÈ  rI  r  rw  Údefault_path)9râ   Úfollow_modeÚcamera_media_disabledÚdsnÚ	rest_moderY  rZ  rf  rï  r  Ú
rest_mediarë  Ú	live_windÚlive_courser  rs  r  Úmedia_loaderr†  r-  r#   r%   rO   ry  r´  r`  Úofficial_gunrA   r^  Ú	roster_atr5  rc  rd  re  rJ  rž  ræ  rÀ  rÂ  r|  r•  r¬  r²  rr  r¹  ro  r˜  r®  r˜  rw  r³  rp  rä  rŸ  rI   r¾  r”  s9   `                                      @@@@@@@@@@@@@@@@@@r&   rQ  rQ  X  s¤  ÿÿú€ ô ˜$ ¨%Ó0€KÜ˜˜k¨4Ó0€IÜ2Ó4ÐÙÜÐXÔYØ�MŠM™[ÜÐJÓKÐKØ‡}‚}˜TŸ[š[ÜÐbÓcÐcô �*‰*�.‰.Ð*Ó
+€Cð
 Ò; C Ò;¬D°«O€IÙ‘yÜð [ó \ð 	\à€EÙ
ß9Ù™g c›lÓ+ˆö&ðP IMØ9=õQ
õf 9Ý.ÝBÝ>õYð €HØ€KØ€JØ€JØ€IØ€KØ€Mð €KÙÝ+Ù! )´7¸4ÀÈtÓ3TÔUˆó9ˆõFˆô	Qô  Ô!ÜÐSÕTä.Ð/?È3ÔOˆJô ˜{¨L¸+×:KÑ:KÓLˆÜ(¨Ð@UÔVˆ
ô óOóPˆ	ô "óOóPˆð 8CÐ7N” {Ô3ÐTXÐÙõ 	6ØˆÜ˜W×.Ñ.¸$¿+¹+ÈTÏZÉZô,OôPˆñ 2°'ÀTÇ[Á[ØGKÇzÁzÐUgÒGg¸¿ºØ>BôEÐð ,.¨r°2°tÐ+;Ñ(ˆˆf�e˜[Ü1°'×2EÑ2EÓFÐö/	.õb	9÷Rò Rˆô 	Ð-¨i¨[¸ÐGÖHÙ	ô ÔbØ'+§{¡{Ü+WôYˆð ,.¨r°2°tÐ+;Ñ(ˆˆf�e˜[Ü1ÛYó[Ðö+	.õZ	@ð ÐÛ<ˆIØÐØ46ˆLö8ô & mÓ4ˆKð
 Ð&öJô >ð	 	ð *ˆ÷
\ò 
\Šð" ×*Ñ*¨4¯=©=Ó9ˆà#Ð/ô   Ð*AÀ4ÔHØ59ð 	ä4Ø˜tŸ}™}¸¿¹È4Ï:É:Øô!Ñˆˆf�eô " %Ô)>¸uÀdÇmÁmÓ)TØ'+°5ô:ˆä2°5¸$¿-¹-ÈdÔSÑˆˆkÙõ 3Ý?Ø-9Ð-Eœ˜LÔ)Ì3ÌtÏyÉyË{Ð]aÑOaÓKbˆIÙ%Ø×/Ñ/°·±¸yÓIØ t§}¡}ô6ç69±c¸$¿-¹-ÈÓ6Lð ñ ,FÀhÐP[Ó+\Ñ(ˆN˜LÙØ$�ØÐ#Ø—h‘h˜Ó/Ò5°2×:Ñ:¸;ÓGÒOÈCˆEÜ" <Ó0ˆD�ÑÜ&)¨,Ó&7¼#¸e»*ÀtÑ:KÑ&KˆDÐ"Ñ#ô ”'˜$ ¨%Ó0Ò\°D·H±HÐ=OÓ4PÐT\Ñ4\Ó]€FÙ‘+Üð Aó Bð 	Bà59·X²XŒb�j‰j�n‰nÐ0Ô1À4€GØ‡x‚x™ÜÐCÔDØ€HÜˆt�U˜EÔ"ä�‰�‰˜œ3œt¤H›~×5Ñ5Ó7×?Ñ?ÀÑBÓCÔDÝ%å8Ù"¨&Ô1ˆô ˆò 
M˜4Ÿ=š=ð 
M°4·8±8¸IÀsÔ3Kð 
MÑSXð 
MÙð
MÙ ð
MÙ.9ð
MØEIÇ[Â[ð
MØW[×W`ÒW`ð
Máð
Mà&*§o¢oð
MàEIÇYÂYð
Mñ /‘´Løð
Mô �t˜X uÔ-ð	
Mô :AÀÀ{ÐTXÔ9Yð	
Mô
 ˜4 ¨tÔ4ð
Mñ
 @Hð
Mñ ð Ð)¨8°[À*ÈjÐZcØ˜mñ-à$(øð
Mñ ð
Mô #5°TÔ":ð
Mô ×)Ñ)¬'°$¸ÀTÓ*JÔKò
Mr(   c                ó´  — ddl }| j                  r$| j                  r| j                  r| j                  st        d«      ‚|j                  j                  d«      }|st        d«      ‚ddlm	}m
}  | ||«      «      }d}t        | dd«      rft        j                  j                  dt        t!        t"        «      j%                  «       j&                  d	   «      «       dd
lm} ddlm}  ||¬«      }t1        || j                  | j                  | j2                  | j                  | j                  | j4                  | j6                  t        | dd«      |¬«
       y)zZ`--regenerate` CLI: re-render a past `[--from, --to]` window of an instance from Postgres.r   NzB--regenerate needs --instance --course --from <utcMs> --to <utcMs>rW  zP--regenerate needs $VR_DATABASE_URL (the Postgres store written during the trip)rX  r„  Frh   r¿  rÁ  rÃ  rç  )	rC   r  r|  r@  rå  r7  rè  rç  ræ  )r§   r4  r%   r@  rå  rÄ  r¨   r7   rÅ  rY  rZ  rq   r   r   r   r   r   rÇ  rÈ  r!   rÉ  rÀ  rÊ  rÂ  ræ  r|  r7  rè  )	râ   r§   rÎ  rY  rZ  rI   ræ  rÀ  rÂ  s	            r&   rP  rP  =  sô   € ãØ�MŠM˜dŸkšk¨d¯gªg¸$¿'º'ÜÐ]Ó^Ð^Ø
�*‰*�.‰.Ð*Ó
+€CÙÜÐkÓlÐlß5Ù™' #›,Ó'€EØ€HÜˆt�U˜EÔ"ä�‰�‰˜œ3œt¤H›~×5Ñ5Ó7×?Ñ?ÀÑBÓCÔDÝ%å8Ù"¨&Ô1ˆÜ�5 d§m¡m¸t¿{¹{ÐRV×R\ÑR\Ø—g‘g $§'¡'°·	±	ÀÇÁÜ! $¨°%Ó8À8öMr(   Ú__main__)r#   rÜ   r$   rÞ   r%   rÜ   )r:   rÜ   rý   ztuple[float, float] | None)rC   r   rJ   r�   rý   rÌ   )rO   r  r|   r�   r}   r   rT   rü  rU   rü  rV   rü  rý   rÜ   )rs   rÜ   rý   r   )rš   r™   r›   r™   rœ   r™   rý   r™   r-   )r¬   r  rý   rY  )rº   r   r»   rY  )r%   r  rý   zdict[str, dict[str, float]])rý   r"  r   )rC   r   )rC   r   rý   r‹  )rC   r   rý   zfloat | None)rC   r   rý   úlist[str] | None)
rQ  r"  r#   rÜ   rO   rØ  rq  r"  rý   rÜ   )rO   rØ  rý   r"  )rC   r   r#   rÜ   ry  r  rý   rr  )r‘   rÜ   )ry  r  rý   r  rÿ   )rÈ  rÜ   r%   rÜ   rý   rÜ   r  )r$   rý  rý   rÝ   )rÜ  rÜ   rÝ  zset[tuple[str, str]]rO   zset[str]rý   rÜ   )r%   rÜ   r  r�   rý   r  )rŽ  r   rý   r   )rO   r  rý   rÞ   )r  r
   rC   r   r  r"  r#   rÜ   r%   rÜ   rO   r  r|  r"  r}   rú  rì  rû  rõ  rþ  rš   r™   r  rü  r  úCallable[[float], None]rÀ   rü   rý   rþ   r   )r  rY  rÀ   rü   )r  r"  rý   rŠ  ):r  r
   r-  r   r8  zCallable[[str, int], tuple]r<  r   r  r"  ré  r  r}   rú  rê  rú  rë  rú  r  z<Callable[[str, list[str]], dict | Callable[[], dict]] | Nonerì  ú(Callable[[str, str | None], dict] | Nonerí  rÚ  rš   r™   r  r™   r  rü  rç  rY  rä  r"  rå  r"  ræ  rú  r  zOnAirQueue | Nonerï  rú  r  rû  r  r  r  úPath | Nonerõ  rþ  r¿   rû   r  rÙ  rÀ   rü   rý   rþ   )*r  r
   rC   r   r<  r   rO   r  r#   rÜ   r%   rÜ   ry  r  r  r"  ré  r  r}   rú  rê  rú  rì  rû  rí  rû  rš   r™   r  rü  rç  rY  rä  r"  rå  r"  ræ  rú  r  rÙ  rý   rþ   ),rC   r   r<  r   rO   r  r#   rÜ   r%   rÜ   ry  r  r  r"  r7  r�   rž  r"  rŸ  r™   r   r"  rç  rY  rä  r"  rå  r"  r}   rú  ræ  rú  r¡  rq  rJ  rY  r  rY  r  rÛ  rÀ   rü   rý   rþ   )rC   r   r  r   r|  r   rÉ  r   r<  r   rÊ  rü  rõ  rü  r|   rü  r  rü  rý   ztuple[dict, dict, list[str]])r  r
   rC   r   rO   r  r#   rÜ   r%   rÜ   ry  r  r  r"  rŸ  r™   rè  r™   rÕ  rü  rç  rY  r  rÙ  ræ  úCallable[[dict], dict] | Nonerý   rþ   )rC   r   r  r   r|  r   r@  r�   rå  r�   rÊ  rü  ry  r  r7  r�   rŸ  r™   rè  r™   rç  rY  ræ  rÜ  rý   rþ   )rï  r™   rð  r™   rý   ztuple[float, float]rÿ  )ur  Ú
__future__r   rã  r.  r§   r   rÇ   r  r�  Úcollections.abcr   Úpathlibr   rÇ  rÈ  Úparentr    r   r   r   rw  Úbroadcast_metadatar   r   Ú
event_metar	   Úlive_feed_serverr
   r   rf  r   r   r   r   r   r   r   r   Ústreaming_scorer   r   r¯  r   r"  r   r'   r=   rR   r‰   r—   rž   r®   r±   r¼   r¹   r  r  r%  rE  rI  rO  rR  r^  rc  rg  rp  ru  rt  rŒ  r’  r”  r­  r¯  rÄ  rÉ  rÐ  r×  rÛ  rá  rã  r  rÀ  r¿  r(  rå  rþ  rç  r�  rœ  rž  r  r  r  r  r  r  r¿   r™  r›  rÇ  rÔ  rã  ræ  rê  rì  Úpirë  r*  r-  r+  rñ  r2  rU  rQ  rP  r÷   r.   r(   r&   ú<module>ræ     s¹	  ðñõ, #ã Û Û 	Û 
Û Û Û Ý $Ý áˆX‹×ÑÓ ×'Ñ'€Ø ‡�‡��‘3�u“:Ô Ø ‡�‡��‘3�u—|‘|×*Ñ*¨XÑ5Ó6Ô 7ð ‡�‡��‘3�u—|‘|×*Ñ*×1Ñ1Ó2Ô 3ã ß 6Ý +ß :÷Q÷ Qó Q÷ FÝ /Ý ,ó2óVó
0ð AEØ+/Èñ8Ø'ð8Ø3=ð8à(ð8à;Eð8àQUó8óv9ó&
1ô	ôó3÷;+ñ ;+÷|8+ñ 8+÷v6+ñ 6+÷r0+ñ 0+ófóBó	Có=óEó&ó:ó
ð 26È4ñAØ.ðAØ>HðAØTXóAó*ð CGñ58Ø!%ð58Ø4?ð58ØKcó58óp	>÷$-ñ $-óND÷:ñ ð& 8<õ ó,Góó1ð Ð ó÷0Gñ GðT Ð ØÐ ó3÷lG>ñ G>ðh %Ð ð %Ð ð !/¸/ÑJÐ ó?ó÷:eWñ eWðb	 Ø $Ø*.Ø'+ØØØ Ø%)§Z¡ZØ$)ñ#à	ðð ð	ð
 ðð ðð ðð ðð ðð ðð (ðð %ðð ðð ðð  #ð!ð" "ð#ð$ 
ó%ó@Nóó"	ð$ Ø ØØØ $Ø!%Ø"&ØRVà9=Ø;?ØØð Ø ØØ ð "Ø $Ø#Ø $Ø-1ð "&ð #Ø'+Ø%)§^¡^Ø%)§Z¡ZØ$)ñWfIà	ðfIð ð	fIð
 2ðfIð ðfIð ðfIð ðfIð ðfIð ðfIð  ðfIð PðfIð 7ðfIð  9ð!fIð" ð#fIð, ð-fIð. ð/fIð0 ð1fIð2 ð3fIð: ð;fIð< ð=fIð> ð?fIð@ ðAfIðB +ðCfIðH ðIfIðN ðOfIðP %ðQfIðR #ðSfIðT #ðUfIðV "ðWfIðX 
óYfIðd  $Ø ØØØ $Ø!%Ø*.Ø,0ØØ ØØ Ø!Ø $ØØ%)§Z¡Zñ1-à	ð-ð ð	-ð
 ð-ð ð-ð ð-ð ð-ð ð-ð ð-ð ð-ð ð-ð ð-ð (ð-ð  *ð!-ð" ð#-ð$ ð%-ð& ð'-ð( ð)-ð* ð+-ð, ð--ð0 #ð1-ð2 
ó3-ðp  $Ø ØØØØ $ØØ Ø!ð !%Ø $Øð ð Ø"ð %*ñUHð ðHð ð	Hð
 ðHð ðHð ðHð ðHð ðHð ðHð ðHð ðHð ðHð ðHð ðHð  ð!Hð( ð)Hð* ð+Hð, ð-Hð2 ð3HðJ ðKHðL ðMHðT "ðUHðV 
óWHðb ØØØØ!%Ø#ñDð ðDð ð	Dð
 ðDð ðDð ðDð ðDð ðDð ðDð ðDð "óDð^  $Ø ØØØ ØØ%)§Z¡ZØ.2ñF,à	ðF,ð ð	F,ð
 ðF,ð ðF,ð ðF,ð ðF,ð ðF,ð ðF,ð ðF,ð ðF,ð ðF,ð #ðF,ð ,ðF,ð  
ó!F,ðb Ø#ØØØØØ.2ñ,ð ð,ð ð	,ð
 ð,ð 	ð,ð 	ð,ð ð,ð ð,ð ð,ð ð,ð ð,ð ð,ð ,ð,ð 
ó,ð` .Ñ €ˆ[à˜4Ÿ7™7Ñ" UÑ*€
Ø@Ñ €	ˆ6�7óWò
48ónmó`bMóJMð. ˆzÒÙ…Fð r(   