Ë
    Š‰Œj1_  ã                  ó  — d Z ddlmZ ddlZddlmZ dZd"d„Zd#d„Z ed¬	«       G d
„ d«      «       Z	 ed¬	«       G d„ d«      «       Z
ddddœZd$d„ZdZdZddddœZd%d„Zd&d„Zd'd„Zd(d„ZdZdZ G d„ d«      Zd)d „Zd*d!„Zy)+u4  The recreational CRUISE tour â€” the producer-side visit scheduler.

A cruise broadcast has no race to score: the picture pans the venue, visits every device one by
one with a fly-in, shows each boat's own camera as a large panel, and presents nothing but the
boat's NAME (plus a small verification strip â€” the mode exists so an operator can watch the tour
and confirm, device by device, that the position is live, the video arrives, and nothing is mixed
up). This module is the schedule and nothing else: pure functions of (second, device snapshot),
no clock, no store, no server â€” the `_Cruise` driver in `serve_v3_live.py` feeds it once a second
and publishes whatever it returns over the ordinary delta channel.

It is deliberately a PRODUCER module. The renderer already knows how to honour a focus beat
(`focus` + `focusUntilSec` + `shotType`), an onboard directive (`rieOnboardDirectives`) and a
spoken line (`text`); the tour only composes those. The broadcast presents, the producer decides
â€” and what the tour decides is visible in the beats it emits, which is what the tests pin.

Truthfulness rules the scheduler enforces itself:
  * a device with no FRESH clip gets a shorter visit and NO picture directive â€” the panel is
    simply absent, never invented;
  * a device with no finite fix is skipped THIS LAP only (a camera cannot be aimed at a position
    nobody has), and rejoins the tour the moment it reports again;
  * ages ride the beat as data (`fixAgeS`/`clipAgeS`), `None` when the reading never arrived â€”
    the renderer draws an em dash, never a zero.
é    )ÚannotationsN)Ú	dataclass)ÚnorthÚ	northeastÚeastÚ	southeastÚsouthÚ	southwestÚwestÚ	northwestc                ó8   — t         t        | dz  dz  «      dz     S )uA   The spoken 8-point compass name of a bearing (0/360 â†’ "north").ih  é-   é   )Ú	_COMPASS8Úround)Údegs    ú>/srv/vr/releases/25f455cc4e75/v3/broadcast/live/cruise_tour.pyÚcompass_wordsr   #   s    € ä”U˜C #™I¨Ñ+Ó,¨qÑ0Ñ1Ð1ó    c                ój   — t        | xs d«      j                  «       }|j                  «       rd|› �S |S )u°  A sailing boat referred to by NUMBER is always "Boat {n}" (Ulf 2026-08-10) â€” a bare "10" on
    a plate or in a spoken line is a number, not a name (the same judgement
    `commentary_i18n.boat_reference` makes when a team label is a bare number). A real name
    ("Alpha") passes through untouched. Applied where the tour resolves its names, so the plate,
    the panel caption, the roll-call and the spoken line cannot disagree.Ú zBoat )ÚstrÚstripÚisdigit)ÚrawÚss     r   Úboat_display_namer   (   s3   € ô 	ˆCŠI�2‹×ÑÓ€AØŸ)™)œ+ˆU�1�#ˆ;Ð,¨1Ð,r   T)Úfrozenc            
      óº   — e Zd ZU dZdZded<   dZded<   dZded<   dZded	<   d
Z	ded<   dZ
ded<   dZded<   dZded<   dZded<   dddd	ddddddœ	Zedd„«       Zy)ÚCruiseConfigu°   The tour's knobs. Defaults here; an event overrides via `presentation.cruise` (camelCase),
    carried to the driver on `meta.cruise` â€” event data, never a renderer constant.é   ÚintÚdwell_sr   Údwell_no_video_sÚinterlude_sé   Úopening_pan_sé   Úfly_in_séZ   Úfresh_clip_max_age_sé   Úvenue_tour_every_lapsé   Úlandmark_dwell_sr   Úexcursion_every_laps)	ÚdwellSÚdwellNoVideoSÚ
interludeSÚopeningPanSÚflyInSÚfreshClipMaxAgeSÚvenueTourEveryLapsÚlandmarkDwellSÚexcursionEveryLapsc                ó(  — i }| j                   j                  «       D ]l  \  }}|xs i j                  |«      }t        |t        t
        f«      sŒ2t        |t        «      rŒCt        j                  |«      sŒY|dk\  sŒ_t	        |«      ||<   Œn  | di |¤ŽS )uº   The event's `presentation.cruise` block â†’ a config; unknown keys ignored, non-numeric
        values ignored (a malformed knob falls back to the default rather than crashing the show).r   © )	Ú_KEYSÚitemsÚgetÚ
isinstancer"   ÚfloatÚboolÚmathÚisfinite)ÚclsÚblockÚkwargsÚwireÚ
field_nameÚvs         r   Ú	from_metazCruiseConfig.from_metaI   s„   € ð ˆØ #§	¡	§¡Ó 1ò 	,ÑˆD�*Ø’˜"×!Ñ! $Ó'ˆAÜ˜!œc¤5˜\Õ*´:¸aÄÕ3FÌ4Ï=É=ÐYZÕK[Ð`aÐefÓ`fÜ%(¨£V��zÒ"ð	,ñ ‰}�V‰}Ðr   N)rE   údict | NoneÚreturnr    )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r#   Ú__annotations__r$   r%   r'   r)   r+   r-   r/   r0   r<   ÚclassmethodrJ   r;   r   r   r    r    2   sž   … ñYà€GˆSÓØÐ�cÓØ€K�ÓØ€M�3ÓØ€HˆcÓØ "Ð˜#Ó"Ø!"Ð˜3Ó"ØÐ�cÓð !"Ð˜#Ó!à Ð3EØ(¸Ø!Ð7MØ#:Ø1Ø#9ñ;€Eð òó ñr   r    c                  óÈ   — e Zd ZU dZded<   ded<   dZded<   dZded	<   dZd
ed<   dZd
ed<   dZ	d
ed<   dZ
d
ed<   dZd
ed<   dZd
ed<   dZd
ed<   dZd
ed<   dZd
ed<   dd„Zy)ÚTourStopu”   One device (or robotic mark) as the tour sees it THIS tick â€” a snapshot, rebuilt by the
    driver every second from the store, never cached here.r   ÚrefÚnameÚboatÚkindNú
str | NoneÚ
slot_labelúfloat | NoneÚlatÚlonÚ	fix_age_sÚ
clip_age_sÚ	speed_ktsÚwind_from_degÚwind_ktsÚ
course_degÚvenue_wind_from_degc                ó
  — t        | j                  t        t        f«      xrb t        | j                  t        t        f«      xr@ t        j                  | j                  «      xr t        j                  | j                  «      S ©N)r?   r\   r"   r@   r]   rB   rC   )Úselfs    r   Ú
positionedzTourStop.positionedl   s^   € Ü˜4Ÿ8™8¤c¬5 \Ó2ò H´zÀ$Ç(Á(ÌSÔRWÈLÓ7Yò HÜ—M‘M $§(¡(Ó+òHÜ04·±¸d¿h¹hÓ0Gð	Ir   )rL   rA   )rM   rN   rO   rP   rQ   rX   rZ   r\   r]   r^   r_   r`   ra   rb   rc   rd   rh   r;   r   r   rT   rT   U   sˆ   … ñ>à	ƒHØ
ƒIØ€Dˆ#ÓØ!€J�
Ó!Ø€CˆÓØ€CˆÓØ"€Iˆ|Ó"Ø#€J�Ó#Ø"€Iˆ|Ó"Ø"&€M�<Ó&Ø!€HˆlÓ!ð  $€J�Ó#Ø(,Ð˜Ó,ôIr   rT   é   é   )rW   ÚvesselÚmarkc                ó  — | j                   j                  dd«      d   }|j                  «       r-t        j	                  | j
                  d«      dt        |«      |fS t        j	                  | j
                  d«      dd|fS )uÀ   Sort key: kind (boats â†’ buoys â†’ marks), then the ref's own NUMBER (numerically, so Boat 2
    precedes Boat 10), letters after numbers within a kind (buoy A, B â€¦ after any numbered one).ú:ri   éÿÿÿÿr,   r   )rU   Úrsplitr   Ú
_KIND_RANKr>   rX   r"   )r   Útails     r   Ú
stop_orderrs   w   sf   € ð �5‰5�<‰<˜˜QÓ Ñ#€DØ‡|�|„~Ü—‘˜qŸv™v qÓ)¨1¬c°$«i¸Ð>Ð>Ü�N‰N˜1Ÿ6™6 1Ó% q¨!¨TÐ2Ð2r   g      ø?g      d@ÚbeatingÚreachingÚrunning)ÚupwindÚreachÚdownwindc                ó`  — t        | t        t        f«      xr¤ t        | t        «       xr‘ t        |t        t        f«      xry t        |t        «       xrf t	        j
                  | «      xrO t	        j
                  |«      xr8 t        |t        t        f«      xr  t	        j
                  |«      xr	 |t        k\  }|sy	 ddlm}m	}m
} t        | «      t        |«      }}t        j                   |||«      «      }	|	sy |||«      t        kD  r|	S |	› d |||«      › d�S # t        $ r Y yw xY w)uÞ  "beating on starboard tack" â€” what a boat is DOING, from her course against the true wind
    (Ulf 2026-08-11). A plain OBSERVATION: both facts are measured, and the derivation is the
    geometry every sailor reads off the same two numbers â€” never an inference about intent.

    `None` (and the line simply says less) whenever the geometry cannot support the claim: a missing
    or non-finite course or wind, or a hull too slow for her course to mean anything (`SAILING_MIN_KTS`).
    The shared `v3/scoring/race_rules` helpers do the maths, so the tour and the scoring engine can
    never disagree about which tack a boat is on; imported lazily, and a scoring path that is not
    there costs the clause rather than the show.Nr   )Úang_diffÚpoint_of_sailÚtack_ofz on z tack)r?   r"   r@   rA   rB   rC   ÚSAILING_MIN_KTSÚ
race_rulesr{   r|   r}   ÚImportErrorÚ_POINT_OF_SAIL_VERBr>   ÚSAILING_BY_THE_LEE_TWA)
rc   ra   r`   Úokr{   r|   r}   ÚcogÚwindÚverbs
             r   Úsailing_wordsr‡   �   s0  € ô �Z¤#¤u Ó
.ò +´zÀ*ÌdÓ7SÐ3Sò +Ü˜¬¬e¨Ó5ò+Ü>HÈÔX\Ó>]Ð:]ò+ä�m‰m˜JÓ'ò+ä,0¯M©M¸-Ó,Hò+ô ˜¤S¬% LÓ1ò+ô 7;·m±mÀIÓ6Nò+ð œ?Ñ*ð	 ñ
 Øðß?Ñ?ô �jÓ!¤5¨Ó#7ˆ€CÜ×"Ñ"¡=°°dÓ#;Ó<€DÙØÙ��TÓÔ3Ò3ØˆØˆV�4™  TÓ*Ð+¨5Ð1Ð1øô ò Ùðús   Ã
D! Ä!	D-Ä,D-c                óR  — | j                   dk(  rW| j                  �;| j                  �/dt        | j                  «      › dt	        | j                  «      › d�S d| j
                  › d�S | j                  }t        |t        t        f«      r�t        j                  |«      r{t        |«      dk\  rm| j                   dk(  r^t        | j                  | j                  |«      }|rd| j
                  › d	|› d
t	        |«      › d�S d| j
                  › d
t	        |«      › d�S d| j
                  › d�S )uµ   The spoken introduction. A word always separates the name from any number that follows
    (root CLAUDE.md: never let two numbers sit adjacent â€” a name may itself end in a digit).rl   z'The robotic mark reports wind from the ú at ú.zThis is the robotic mark ri   rW   zThis is z, z	, making )rX   ra   rb   r   Ú
_kts_wordsrV   r`   r?   r"   r@   rB   rC   r   r‡   rc   rd   )ÚstopÚktsÚdoings      r   Ú
visit_liner�   ®   s  € ð ‡y�y�FÒØ×ÑÐ)¨d¯m©mÐ.GØ=¼mÈD×L^ÑL^Ó>_Ð=`ð aÜ$ T§]¡]Ó3Ð4°Að7ð 8à*¨4¯9©9¨+°QÐ7Ð7Ø
�.‰.€CÜ�3œœe˜Ô%¬$¯-©-¸Ô*<ÄÀsÃÈqÂØ—	‘	˜VÒ#ô
 ˜dŸo™o¨t×/GÑ/GÈÓMˆÙØ˜dŸi™i˜[¨¨5¨'°¼:Àc»?Ð:KÈ1ÐMÐMØ˜$Ÿ)™)˜ I¬j¸«oÐ->¸aÐ@Ð@Ø�d—i‘i�[ Ð"Ð"r   c                ó6   — t        | «      }|dk(  r|› d�S |› d�S )Nri   z knotz knots)r   )r�   Úns     r   r‹   r‹   Ä   s(   € Üˆc‹
€AØ˜qš&ˆaˆS�ˆ;Ð2¨¨¨6 lÐ2r   c                óN   — |rd|› �nd}dt        | «      › dt        |«      › |› d�S )Nz over r   zWind from the r‰   rŠ   )r   r‹   )ra   rb   Ú
venue_nameÚwheres       r   Úweather_liner•   É   s?   € Ù%/ˆf�Z�LÑ!°R€EØœ]¨=Ó9Ð:ð ;Ü˜XÓ&Ð'¨ w¨að1ð 2r   éð   éF   c                  ó’   — e Zd ZdZddddœ	 	 	 dd„Z	 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d„Zdd„Zy)ÚTourScheduleruÏ  The sequencer: one segment at a time, forever.

    `tick(now_sec, stops, wind)` is called once a second (idempotent â€” extra calls inside a
    running segment return nothing) and answers `(events, directives)` to publish at this second.
    The choreography:

        OPENING pan (+ spoken weather) â†’ guided landmark tour â†’
        per lap: visit â†’ interlude â†’ â€¦ â†’ skyline pan â†’ next lap
        (the landmark tour re-runs every `venue_tour_every_laps` laps)

    Visit order is the operator's own checklist (Ulf 2026-08-10): the boats in NUMBER order,
    then the buoys, then the robotic marks â€” `stop_order`. Reproducible, and the operator ticking
    devices off always knows who comes next.
    N)Ú	landmarksr“   Ú	excursionc               óT  — || _         |xs g D �cg c]N  }t        |j                  d«      t        t        f«      r't        |j                  d«      t        t        f«      r|‘ŒP c}| _        || _        || _        d| _        d | _	        d| _
        t        «       | _        g | _        y c c}w )Nr\   r]   Ústartr   )Úcfgr?   r>   r"   r@   rš   r“   r›   Ú_stateÚ_untilÚ_lapÚsetÚ_visitedÚ	_lm_queue)rg   rž   rš   r“   r›   Úlms         r   Ú__init__zTourScheduler.__init__ñ   s‘   € àˆŒà(1ª°Rö G Ü'¨¯©¨u«¼¼U°|ÔDÜ(¨¯©°«¼¼e¸ÔEò ò GˆŒð %ˆŒð #ˆŒØˆŒØ"&ˆŒØˆŒ	Ü"%£%ˆŒØ%'ˆ�ùòGs   �AB%c                ó¼   — t        |«      }| j                  �|| j                  k  rg g fS | j                  dk(  r| j                  ||«      S | j	                  ||«      S )u´   Advance the tour to `now_sec`. Returns `(events, directives)` â€” empty while the current
        segment is still running. `wind` is the feed-meta shape (`windFromDeg`/`windKts`).r�   )r"   r    rŸ   Ú_openingÚ_advance)rg   Únow_secÚstopsr…   s       r   ÚtickzTourScheduler.tick  sY   € ô �g“,ˆØ�;‰;Ð" w°·±Ò'<Ø�r�6ˆMØ�;‰;˜'Ò!Ø—=‘= ¨$Ó/Ð/Ø�}‰}˜W eÓ,Ð,r   c                ó  — d| _         || j                  j                  z   | _        g }|r`|j	                  d«      �O|j	                  d«      �>|j                  | j                  |dt        |d   |d   | j                  «      ¬«      «       |g fS )NÚopenedÚwindFromDegÚwindKtsÚtour_weather)Útext)	rŸ   rž   r'   r    r>   ÚappendÚ_beatr•   r“   )rg   Útr…   Úeventss       r   r¨   zTourScheduler._opening  s�   € ØˆŒØ˜$Ÿ(™(×0Ñ0Ñ0ˆŒØˆÙ�D—H‘H˜]Ó+Ð7¸D¿H¹HÀYÓ<OÐ<[Ø�M‰M˜$Ÿ*™* Q¨Ü*6°t¸MÑ7JÈDÐQZÉOØ7;·±ó+Hð %ó Iô Jð �rˆzÐr   c                ó
  — | j                   }|dk(  r/| j                  r| j                  |«      S | j                  ||«      S |dk(  r| j	                  ||«      S |dv r| j                  ||«      S |dk(  r)d| _         || j                  j                  z   | _        g g fS |dk(  r| j                  ||«      S |dk(  r| j                  |«      S |dk(  r| j                  ||«      S |d	k(  r| j                  ||«      S t        d
|›�«      ‚)Nr®   Úlandmark)ÚlapÚ	interludeÚvisitrº   ÚskylineÚskyline_then_landmarksÚskyline_then_excursionr›   zunknown tour state )rŸ   rš   Ú_begin_landmarksÚ
_begin_lapÚ_next_landmarkÚ_next_visitrž   r%   r    Ú_begin_excursionÚ_tick_excursionÚAssertionError)rg   rµ   r«   Ústates       r   r©   zTourScheduler._advance  s  € Ø—‘ˆØ�HÒØ/3¯~ª~�4×(Ñ(¨Ó+Ð\À4Ç?Á?ÐSTÐV[ÓC\Ð\Ø�JÒØ×&Ñ& q¨%Ó0Ð0ØÐ(Ñ(Ø×#Ñ# A uÓ-Ð-Ø�GÒà%ˆDŒKØ˜dŸh™h×2Ñ2Ñ2ˆDŒKØ�r�6ˆMØ�IÒØ—?‘? 1 eÓ,Ð,ØÐ,Ò,Ø×(Ñ(¨Ó+Ð+ØÐ,Ò,Ø×(Ñ(¨¨EÓ2Ð2Ø�KÒØ×'Ñ'¨¨5Ó1Ð1ÜÐ2°5°)Ð<Ó=Ð=r   c                óv   — d| _         t        | j                  «      | _        || _        | j                  |g «      S )Nr¸   )rŸ   Úlistrš   r¤   r    rÁ   )rg   rµ   s     r   r¿   zTourScheduler._begin_landmarks2  s3   € Ø ˆŒÜ˜dŸn™nÓ-ˆŒØˆŒØ×"Ñ" 1 bÓ)Ð)r   c                óº   — | j                   j                  |«       | j                   j                  r| j                  ||«      S d| _        | j                  ||«      S )uÐ   Hand the picture to the continuous city flight (`excursion.Excursion.begin`); with
        nothing eligible to fly the lap simply resumes â€” an excursion that cannot happen must
        never stall the tour.r›   )r›   ÚbeginÚdonerÀ   rŸ   rÄ   ©rg   rµ   r«   s      r   rÃ   zTourScheduler._begin_excursion8  sN   € ð 	�‰×Ñ˜QÔØ�>‰>×ÒØ—?‘? 1 eÓ,Ð,Ø!ˆŒØ×#Ñ# A uÓ-Ð-r   c                ó¤   — | j                   j                  |«      }| j                   j                  r| j                  ||«      S |dz   | _        |g fS )Nri   )r›   r¬   rË   rÀ   r    )rg   rµ   r«   Úbeatss       r   rÄ   zTourScheduler._tick_excursionB  sI   € Ø—‘×#Ñ# AÓ&ˆØ�>‰>×ÒØ—?‘? 1 eÓ,Ð,Ø˜!‘eˆŒØ�bˆyÐr   c                óü  — | j                   s| j                  ||«      S | j                   j                  d«      }t        |j	                  d«      xs d«      }t        | j                  j                  | j                  j                  t        |«      z   «      }||z   | _
        | j                  |dt        |j	                  d«      xs d«      |t        |d   «      t        |d   «      dœt        ||z   d	¬
«      }|gg fS )Nr   Úliner   Útour_poirV   r\   r]   ©r\   r]   úboat-feature)rV   r²   ÚpoiÚpoiDistMÚfocusUntilSecÚshotType)r¤   rÀ   Úpopr   r>   Úmaxrž   r/   r)   Ú_speech_secondsr    r´   r@   ÚPOI_DIST_LANDMARK_M)rg   rµ   r«   r¥   rÐ   ÚdwellÚevs          r   rÁ   zTourScheduler._next_landmarkI  sÞ   € Ø�~Š~Ø—?‘? 1 eÓ,Ð,Ø�^‰^×Ñ Ó"ˆÜ�2—6‘6˜&“>Ò' RÓ(ˆÜ�D—H‘H×-Ñ-Ø—H‘H×%Ñ%¬¸Ó(=Ñ=ó?ˆà˜%‘iˆŒØ�Z‰Z˜˜:¬C°·±°v³Ò0DÀ"Ó,EÈDÜ$)¨"¨U©)Ó$4¼UÀ2ÀeÁ9Ó=MÑNÜ!4Ø&'¨%¡i¸.ð ó Jˆð ˆt�Rˆxˆr   c                ó  — | xj                   dz  c_         t        «       | _        |s3d| _        |t	        d| j
                  j                  «      z   | _        g g fS d| _        || _        | j                  |d| j                   ¬«      gg fS )Nri   r¹   é
   Útour_lap)r¹   )	r¡   r¢   r£   rŸ   rÙ   rž   r%   r    r´   rÌ   s      r   rÀ   zTourScheduler._begin_lapW  sy   € Ø�	Š	�Q‰�	Ü›ˆŒÙàˆDŒKØœc " d§h¡h×&:Ñ&:Ó;Ñ;ˆDŒKØ�r�6ˆMð ˆŒØˆŒØ—
‘
˜1˜j¨d¯i©i�
Ó8Ð9¸2Ð=Ð=r   c                ó   — | j                  |«      }|€| j                  |«      S | j                  j                  |j                  «       d| _        |j                  d uxr# |j                  | j                  j                  k  }|r| j                  j                  n| j                  j                  }|| j                  j                  z   |z   }|| _        | j                  |d|j                  |j                  |j                  t!        |j"                  «      t!        |j                  «      t%        |«      |d¬«
      }|j&                  dk(  r|j                  g|d<   n%|j(                  |j*                  dœ|d<   t,        |d	<   g }|rE|j/                  || j                  j                  z   ||j                  d
ddd|j                  dœ«       |g|fS )Nr»   Ú
tour_visitrÓ   )ÚdeviceIdrV   Ú	slotLabelÚfixAgeSÚclipAgeSr²   rÖ   r×   rW   ÚfocusrÒ   rÔ   rÕ   z
tour-visitFri   Útour)ÚtSecÚendSecÚboatIdr×   Ú	fullFrameÚpriorityÚreasonrV   )Ú_pickÚ_skyliner£   ÚaddrU   rŸ   r_   rž   r+   r#   r$   r)   r    r´   rV   rZ   Ú_ager^   r�   rX   r\   r]   ÚPOI_DIST_VESSEL_Mr³   )	rg   rµ   r«   ÚnxtÚfreshrÜ   ÚendrÝ   Ú
directivess	            r   rÂ   zTourScheduler._next_visitf  s�  € Ø�j‰j˜ÓˆØˆ;Ø—=‘= Ó#Ð#Ø�‰×Ñ˜#Ÿ'™'Ô"ØˆŒØ—‘ tÐ+ò EØ—^‘^ t§x¡x×'DÑ'DÑDð 	á$)�—‘× Ò ¨t¯x©x×/HÑ/HˆØ�$—(‘(×#Ñ#Ñ# eÑ+ˆØˆŒØ�Z‰Z˜˜<°#·'±'ÀÇÁØ"%§.¡.¼$¸s¿}¹}Ó:MÜ!% c§n¡nÓ!5¼JÀs»OØ&)°Nð ó Dˆð �8‰8�vÒØŸ7™7˜)ˆBˆwŠKð !$§¡°·±Ñ8ˆBˆu‰IÜ.ˆBˆz‰NØˆ
ÙØ×Ñ q¨4¯8©8×+<Ñ+<Ñ'<ÈÐWZ×W^ÑW^Ø+7ÀeÐYZØ)/¸¿¹ñCô Dð ˆt�ZÐÐr   c                ó¨   — |D �cg c].  }|j                   | j                  vsŒ|j                  «       sŒ-|‘Œ0 }}|rt        |t        ¬«      S dS c c}w )u²   The next unvisited POSITIONED stop in checklist order (`stop_order`); a stop with no
        finite fix skips this lap only â€” a camera cannot be aimed at a position nobody has.)ÚkeyN)rU   r£   rh   Úminrs   )rg   r«   r   Útodos       r   rï   zTourScheduler._pick‚  sH   € ð !ÖR�a A§E¡E°·±Ò$>À1Ç<Á<Å>’ÐRˆÐRÙ,0Œs�4œZÔ(Ð:°dÐ:ùò Ss   …A£A´Ac                óÔ  — d| _         t        | j                  j                  d«      }||z   | _        | j                  j
                  }| j                  j                  }| j                  �9|dkD  r4| j                  |z  dk(  r"| j                  j                  «       rd| _         n*| j                  r|dkD  r| j                  |z  dk(  rd| _         | j                  |dd||z   ¬«      }|gg fS )	u  The between-laps breather: the venue establishing camera pans the city skyline
        (`cam: "venue"`, aimed along `venue.cityBearingDeg` by the renderer). `camUntilSec` bounds
        it explicitly â€” without it the renderer holds a cine until the next cam event, i.e. forever.r¼   rß   r   r¾   r½   Ú
tour_venueÚvenue)ÚcamÚcamUntilSec)rŸ   rÙ   rž   r%   r    r-   r0   r›   r¡   Ú	availablerš   r´   )rg   rµ   ÚholdÚeveryÚevery_xrÝ   s         r   rð   zTourScheduler._skyline‹  sÍ   € ð  ˆŒÜ�4—8‘8×'Ñ'¨Ó,ˆØ˜$‘hˆŒð —‘×.Ñ.ˆØ—(‘(×/Ñ/ˆØ�N‰NÐ&¨7°Qª;¸4¿9¹9ÀwÑ;NÐRSÒ;SØ—N‘N×,Ñ,Ô.Ø2ˆD�KØ�^Š^ ¨¢	¨d¯i©i¸%Ñ.?À1Ò.DØ2ˆDŒKØ�Z‰Z˜˜<¨WÀ!ÀdÁ(ˆZÓKˆØˆt�Rˆxˆr   c                óž   — t        |«      |dddœ}|j                  |j                  «       D ��ci c]  \  }}|€Œ	||“Œ c}}«       |S c c}}w )uX   Every tour beat is a `structure`-class claim spoken by the Caller â€” facts, no tactics.Ú	structureÚlead)ré   ÚtypeÚ
claimClassÚcommentaryRole)r"   Úupdater=   )rg   rµ   rX   ÚfieldsrÝ   ÚkrI   s          r   r´   zTourScheduler._beat¡  sI   € ä˜!“f d¸+ÐY_Ñ`ˆØ
�	‰	 F§L¡L£N×D™D˜A˜q°a±m�1�a‘4ÓDÔEØˆ	ùó Es
   ¯
A	
ºA	
)rž   r    rš   zlist[dict] | Noner“   rY   rL   ÚNonerf   )rª   r"   r«   úlist[TourStop]r…   rK   rL   útuple[list[dict], list[dict]])rµ   r"   r…   rK   rL   r  )rµ   r"   r«   r  rL   r  )rµ   r"   rL   r  )r«   r  rL   zTourStop | None)rµ   r"   rX   r   rL   Údict)rM   rN   rO   rP   r¦   r¬   r¨   r©   r¿   rÃ   rÄ   rÁ   rÀ   rÂ   rï   rð   r´   r;   r   r   r™   r™   á   sy   „ ñð MQØ*.¸$ñ(Ø'ð(ØCGó(ð( "&ð	-Øð	-Ø*Gó	-óó>ó.*ó.óóó>ó ó8;óô,r   r™   c                ó4   — | €dS t        dt        | «      «      S )zTAges ride the wire as whole seconds; `None` stays `None` (an em dash, never a zero).Nr   )rÙ   r"   )rI   s    r   rò   rò   ¨  s   € à�9ˆ4Ð0¤# a¬¨Q«£.Ð0r   c                óÄ   — t        t        | xs d«      j                  «       D �cg c]  }|sŒ|‘Œ	 c}«      }t        dt	        j
                  |dz  dz   «      «      S c c}w )u®   The same conservative words-per-second model the renderer's camera pacing uses
    (`estimatedSpeechSeconds`, app.js) â€” so a segment is never shorter than its own sentence.r   r,   gš™™™™™@gš™™™™™é?)Úlenr   ÚsplitrÙ   rB   Úceil)r²   ÚwÚwordss      r   rÚ   rÚ   ­  sP   € ô œC ¢
¨›O×1Ñ1Ó3Ö9�q²q’Ò9Ó:€EÜˆq”$—)‘)˜E C™K¨#Ñ-Ó.Ó/Ð/ùò :s
   ¥A­A)r   r@   rL   r   )r   r   rL   r   )r   rT   rL   Útuple)rL   rY   )rŒ   rT   rL   r   )r�   r@   rL   r   )ra   r@   rb   r@   r“   rY   rL   r   )rI   r[   rL   z
int | None)r²   r   rL   r"   )rP   Ú
__future__r   rB   Údataclassesr   r   r   r   r    rT   rq   rs   r~   r‚   r�   r‡   r�   r‹   r•   rÛ   ró   r™   rò   rÚ   r;   r   r   ú<module>r     sÐ   ðñõ. #ã Ý !ð8€	ó2ó
-ñ �$Ô÷ð ó ðñD �$Ô÷Ið Ió ðIð<  1¨aÑ0€
ó3ð €ð Ð ð "+°ZÈYÑWÐ ó2ó<#ó,3ó
2ð( Ð ØÐ ÷Dñ DóN1ô
0r   