Files
MatchLiveTv/docs/TABELLONI_E_OVERLAY.md
Emiliano Frascaro ce8939fffb Aggiunge pulizia ciclica segmenti locali e doc tabelloni/overlay.
Evita il riempimento disco da segmenti MediaMTX post-sessione con cron orario;
documenta come board, overlay e regolamenti sportivi si relazionano nel sistema.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 22:08:05 +02:00

14 KiB
Raw Blame History

Tabelloni, overlay e regolamenti sportivi

Questo documento descrive come Match Live TV modella gli sport, applica i regolamenti di punteggio e li traduce in tabelloni (logica + controlli) e overlay (grafica sul video in diretta).


Panoramica

Il sistema separa tre livelli che spesso vengono confusi:

Livello Cosa rappresenta Dove vive Esempio basket
Sport (sport_key) La disciplina scelta per squadra/partita config/sports.yml, DB basket
Board (board_type) Il tipo di tabellone — logica di punteggio e controlli Derivato dal catalogo, score_states.board_type basket
Overlay (overlay_kind) Il layout grafico sul video Per partita, validato dal catalogo basket o none

In sintesi:

  • Il regolamento definisce come si segna (set da vincere, punti per set, durata quarti…).
  • Il board è il motore che applica quel regolamento.
  • Loverlay è solo la resa visiva sullo stream; può essere disattivato (none) anche quando il board resta attivo in regia.
flowchart LR
  subgraph catalogo [Catalogo sport]
    SK[sport_key]
    BR[board_type]
    OV[overlay default]
    DR[default_rules]
  end
  subgraph partita [Partita]
    M[Match]
    OK[overlay_kind opzionale]
    SR[scoring_rules opzionale]
  end
  subgraph runtime [Sessione live]
    SS[ScoreState]
    ENG[Scoring Engine]
    OL[Overlay video]
  end
  SK --> BR
  SK --> OV
  SK --> DR
  M --> SK
  M --> OK
  M --> SR
  BR --> ENG
  DR --> ENG
  SR --> ENG
  ENG --> SS
  SS --> OL
  OK --> OL

Catalogo sport e regolamenti

Il catalogo è definito in backend/config/sports.yml e caricato da Sports::Catalog. Non esiste una tabella sports nel database: lo sport è una configurazione versionata esposta via API (GET /api/v1/sports).

Per ogni disciplina il catalogo specifica:

Campo Significato
label Nome visualizzato (es. «Pallavolo»)
board Tipo tabellone implicito
overlay Overlay video predefinito
allowed_overlays Overlay ammessi sulla partita
default_rules Regolamento standard di quello sport

Tipi di regolamento (default_rules)

Gli sport si raggruppano in tre famiglie di regole, validate da Sports::RulesSchema:

Sport a set e punti (board: volley o racket)

Chiave Significato Esempio pallavolo
sets_to_win Set necessari per vincere la partita 3 (best of 5)
points_per_set Punti per vincere un set «normale» 25
points_deciding_set Punti dellultimo set (tie-break) 15
min_point_lead Vantaggio minimo per chiudere il set 2

Sport che usano queste regole: pallavolo, beach volley, sitting volley, tennis, tennis tavolo, badminton, padel, pickleball.

La logica è in Scoring::Rules (set_winner_from_points, match_winner): un set si vince al raggiungimento del target (points_per_set o points_deciding_set sullultimo set possibile) con almeno min_point_lead punti di distacco.

Sport a tempi e periodi (board: basket o timed)

Chiave Significato Esempio basket Esempio calcio a 5
periods Numero di periodi regolamentari 4 quarti 2 tempi
period_duration_secs Durata iniziale del cronometro per periodo 600 (10 min) 1200 (20 min)
overtime_duration_secs Durata supplementare / OT 300 (5 min) 300

Sport timed: calcio a 5, pallamano, hockey indoor, floorball.

Il cronometro è gestito dal motore (clock_toggle, clock_reset, clock_tick, advance_period) e memorizzato in score_states.data (JSONB).

Sport senza punteggio a referto (board: timer o generic)

Board Uso Regole Overlay default
timer Ginnastica, danza, cheerleading, pattinaggio Nessuna ({}) none
generic «Altro» Nessuna none

Per questi sport la regia può usare un cronometro (TimerEngine) ma non viene sovrapposto un tabellone sul video.


Board type: il motore di punteggio

Il board_type non si sceglie manualmente in creazione partita: deriva sempre dallo sport_key tramite Match#effective_board_typeSports::Catalog.board_for.

Scoring::Engine.for_match(match) seleziona lengine:

Board Classe engine Azioni principali
volley VolleyEngine home_point, away_point, home_undo, away_undo, close_set
racket RacketEngine Come volley (fase 1 — stessa logica a set)
basket BasketEngine +1/+2/+3, undo, advance_period, cronometro
timed TimedEngine +1, undo, periodi, cronometro
timer TimerEngine Solo cronometro (conteggio crescente)
generic GenericEngine +1 / undo senza chiusura set

Come il regolamento entra nel motore

Allavvio di ogni azione, lengine costruisce le regole effettive con:

match.effective_scoring_rules
# => default_rules del catalogo deep_merge scoring_rules della partita

Quindi:

  1. Default dello sport — es. pallavolo: 25 punti, set decisivo a 15.
  2. Override partita — campo matches.scoring_rules (JSON) per deroghe locali (categoria giovanile, torneo ridotto…).
  3. Legacy — per il volley, matches.sets_to_win viene fuso in effective_scoring_rules se presente.

Il volley applica automaticamente la vittoria di set quando il punteggio soddisfa Rules#set_winner_from_points; close_set forza la chiusura manuale (utile se si preferisce confermare a bordo campo).


Overlay kind: la grafica sul video

Loverlay è indipendente dal motore di scoring: descrive cosa disegnare sul frame, non come calcolare i punti.

Valori ammessi

Definiti in Sports::OverlayKind: none, volley, basket, timed, racket.

Il valore legacy timer come overlay è mappato a none in Match#effective_overlay_kind.

Scelta per partita

  • Campo opzionale matches.overlay_kind.
  • Se assente → default del catalogo (Sports::Catalog.overlay_for).
  • Deve essere incluso in allowed_overlays dello sport, altrimenti si ripiega sul default.

Esempio: il basket ammette [basket, none] — si può trasmettere con tabellone o solo watermark.

Layout Android (pipeline video)

Sullapp nativa loverlay è composito in GPU senza ricodifica aggiuntiva:

  1. BroadcastScreen costruisce un OverlayState a partire da effectiveOverlayKind e ScoreState.
  2. OverlayRendererOverlayCanvasRenderer disegna gli elementi su bitmap trasparente.
  3. Il bitmap viene applicato come filtro OpenGL sul flusso RTMP.

Elementi grafici (OverlayCanvasRenderer):

Elemento Quando è attivo
WatermarkElement Sempre tranne overlay_kind = none
ScoreboardElement volley, racket — tabellone a colonne per set
CompactScoreboardElement basket, timed — squadre ai lati, punteggio centrale, etichetta periodo
SponsorElement Se configurato

Nota: il cronometro non è più renderizzato nelloverlay video (basket/timed mostrano solo periodo, es. Q2 o 2° tempo). Il cronometro resta disponibile nella regia e nei controlli operatore, non sullo stream.

Mappatura overlay ← punteggio

Overlay Struttura dati overlay Fonte dati
volley / racket ScoreboardState (colonne set) set_partials, current_set, punti correnti
basket / timed CompactScoreboardState home_score / away_score in data, periodLabel
none Solo watermark / stato LIVE opzionale

Stato tabellone (ScoreState)

Ogni StreamSession ha al massimo uno ScoreState, creato allapertura sessione (Sessions::CreateScoring::Engine.ensure_score_for).

Campi condivisi

Campo Uso
board_type Snapshot del board al momento della creazione
home_sets / away_sets Set vinti (volley/racket)
home_points / away_points Punti set corrente oppure mirror del punteggio basket/timed
current_set Set in corso
set_partials Storico parziali [{set, home, away}, …]
data (JSONB) Stato esteso basket/timed/timer

Payload WebSocket

ScoreState#as_cable_payload arricchisce il JSON in base al board:

  • volley / racket: set, punti, parziali.
  • basket / timed: home_points/away_points come punteggio totale, period, clock_secs, clock_running.
  • timer: solo cronometro.

Lapp mobile e la regia web si sottoscrivono al canale SessionChannel e ricevono score_update dopo ogni azione.


Regia web (controlli operatore)

La pagina regia (public/regia) usa lo stesso motore di scoring dellAPI, con partial diversi per board:

Partial Board Controlli tipici
_volley.html.erb volley +1, undo, chiudi set, hint punti target
_racket.html.erb racket Come volley
_basket.html.erb basket +1/+2/+3, cronometro, avanza quarto
_timed.html.erb timed +1, cronometro, avanza tempo
_timer.html.erb timer Solo cronometro
_generic.html.erb generic Contatore punti semplice

Il partial viene scelto in base a data-board sul container #regia-app. I pulsanti inviano azioni al controller regia, che chiama Scoring::ApplyAction.


Flusso end-to-end

1. Creazione partita
   team.sport_key → match.sport_key
   opzionale: match.overlay_kind, match.scoring_rules

2. Avvio sessione (POST /sessions)
   → ScoreState inizializzato con default_data dellengine
   → Per basket/timed: clock_secs = period_duration_secs del regolamento

3. Azione operatore (app, regia, API)
   POST score_action / WebSocket
   → Scoring::ApplyAction → Engine.apply
   → ScoreState aggiornato + broadcast Action Cable

4. Rendering overlay (solo app camera / relay grafico)
   ScoreState + Match.effectiveOverlayKind
   → OverlayState → bitmap → filtro GL sul video

5. Spettatori
   Vedono il video con overlay già composito (non ricevono il tabellone come layer separato)

Tabella sport → board → overlay → regolamento

sport_key Board Overlay default Regolamento chiave
pallavolo volley volley 3 set, 25 pt, 5° set a 15, +2
beach_volley volley volley 2 set, 21 pt, 3° set a 15
sitting_volley volley volley Come pallavolo
basket basket basket 4×10 min, OT 5 min
calcio_a_5 timed timed 2×20 min, suppl. 5 min
pallamano timed timed 2×30 min
hockey_indoor timed timed 2×20 min
floorball timed timed 3×20 min
tennis racket racket 2 set, 6 game, tie-break 7
tennis_tavolo racket racket 3 set, 11 pt
badminton racket racket 2 set, 21 pt
padel racket racket Come tennis
pickleball racket racket 2 set, 11 pt
ginnastica_* / danza / cheer / pattinaggio timer none Nessun punteggio
altro generic none Contatore libero

Personalizzazione per partita

Campo DB Effetto
sport_key Determina board, overlay default e regole base
overlay_kind Cambia solo la grafica sul video (entro allowed_overlays)
scoring_rules Sovrascrive i numeri del regolamento (es. points_per_set: 21 su pallavolo giovanile)
sets_to_win Legacy; fuso in effective_scoring_rules per il volley

Esempio API partita:

{
  "sport_key": "pallavolo",
  "overlay_kind": "none",
  "scoring_rules": {
    "points_per_set": 21,
    "sets_to_win": 2
  }
}

→ Regia e motore usano regole da 21 punti; lo stream non mostra tabellone (none).


Relazione regolamento ↔ UI ↔ overlay

Aspetto Governato da Visibile in overlay?
Vittoria set (25+2) Scoring::Rules + VolleyEngine Sì — colonne set / parziali
Vittoria partita (3 set) match_winner Sì — conteggio set
Punti basket (+2/+3) BasketEngine Sì — punteggio centrale
Durata quarto period_duration_secs No — solo etichetta periodo (Q1…)
Cronometro score_states.data No sulloverlay (sì in regia)
Sport ginnico TimerEngine No overlay punteggio

Questa separazione è intenzionale: il regolamento alimenta sempre il modello dati (ScoreState); loverlay sceglie quali campi mostrare sul video.


File di riferimento

Area Percorso
Catalogo sport backend/config/sports.yml
API catalogo backend/app/domain/sports/catalog.rb
Validazione regole backend/app/domain/sports/rules_schema.rb
Logica regolamento backend/app/services/scoring/rules.rb
Engine backend/app/services/scoring/engine.rb, engines/*
Modello partita backend/app/models/match.rb
Stato punteggio backend/app/models/score_state.rb
Overlay Android native/android/.../streaming/overlay/*
Schermata broadcast native/android/.../ui/broadcast/BroadcastScreen.kt
Regia web backend/app/views/public/regia/_*.html.erb

Documento correlato: MULTI_SPORT.md (panoramica più sintetica della migrazione multi-sport).


Limitazioni attuali

  1. racket = volley a livello motore — tennis/padel usano logica a set+punti numerici; non cè ancora scoring «15-30-40» o tie-break automatico stile tennis.
  2. Cronometro non in overlay — per basket/timed il tempo è gestito in regia ma non bruciato sul video.
  3. Overlay solo lato publisher — lo spettatore vede il grafico composito nel flusso; non cè tabellone HTML separato sulla pagina live pubblica.
  4. Regolamenti = parametri numerici — il sistema non incorpora testi di regolamento FIPAV/FIBA; implementa le regole standard dichiarate in sports.yml, personalizzabili per partita.

Per aggiungere uno sport nuovo: estendere sports.yml, verificare che board e allowed_overlays siano coerenti, e — se serve — aggiungere un partial regia e un ramo in BroadcastScreen / OverlayCanvasRenderer.