Passa al contenuto principale

Dorsale della registrazione

Riepilogo in parole semplici. Questo è il cuore di Playroll. Un giocatore registra un frammento di gameplay; quella registrazione è una sessione; la sessione produce caricamenti (un insieme fisso di file); registriamo su quale macchina è stata eseguita; e i file scorrono poi attraverso una pipeline GPU che trasforma le catture grezze in dati pronti per l'addestramento. Ogni ricompensa, ogni indagine di supporto e ogni KPI per gli investitori sono calcolati a partire dalle tabelle di questa pagina.

Per il contratto comportamentale (stati, gate, validazione) vedi Ciclo di vita della sessione di registrazione e la Pipeline di validazione dei caricamenti. Questa pagina è la vista sui dati della stessa storia.

Mappa

recording_sessions — l'unità di ogni cosa

Una riga per ogni tentativo di registrazione dall'avvio all'arresto, con chiave una stringa session_id nella forma rec-<epoch-ms> (lo stesso id usato dal DB SQLite locale, da ogni evento di telemetria capture.v2.*, dalla cartella su disco e dal segmento della chiave S3 — così una sessione si mappa 1:1 dal disco del giocatore al cloud).

Colonne chiave per scopo:

ScopoColonne
Identità e tempistichesession_id, user_id, game_name, game_slug, recording_started_at, recording_ended_at, duration_seconds
Statostatus, uploadability_status, block_reason, dismissed
Fatti di catturachunk_count, detected_resolution (jsonb), required_artifact_mode (mp4_csv_json / mp4_csv), transcript_expected, transcript_skipped
Provenienzalobby_id (per valore — nessuna FK), hw_snapshot_id (FK → device_hw_snapshots.id), core_version, ui_version, installer_version
Ciclo di vita lato serverclient_ended_at, server_verified_at, created_at, updated_at

status vale per default uploading all'avvio della sessione; uploadability_status è uno stretto uploadable / blocked / NULL. L'unica foreign key dichiarata su questa tabella è hw_snapshot_id; lobby_id è un mero collegamento per valore (vedi sotto).

Due invarianti da imparare a memoria
  1. duration_seconds è l'orologio canonico. Le ore di registrazione — per il pagamento, per i KPI — derivano da questa tempistica di sessione validata, e non dal conteggio dei file caricati. La vista per gli investitori (investor_daily_recording_kpis_vc_view) è costruita su di essa.
  2. lobby_id è metadato opzionale e non una foreign key. Una sessione è identificata dal proprio session_id; la lobby si limita ad accoppiare le sessioni simultanee. Una registrazione in solitaria ha lobby_id nullo e non è meno valida. Il vincolo lobby↔gioco è imposto da un trigger su s3_uploads (sotto), non da una FK su questa colonna.

s3_uploads — l'evidenza di caricamento per singolo file

Una riga per ogni file caricato, non per sessione — così una sessione completata ha diverse righe che condividono uno stesso session_id. Ciò che viene caricato non è una tripletta fissa di 3 file:

Artefattofile_type / artifact_kindBlocca il caricamento?
Videomp4 / video — richiesto dal validatore.
Log di inputcsv / input (_input.csv) — richiesto.
Metadatijson / meta (_meta.json) — richiesto.
Audio del microfono_mic.aacNo — sidecar opzionale (EC-259).
Trascrizionejson / transcript (_transcript.json)No — opzionale, generata durante/dopo il caricamento.
Audio (legacy)oggNo — rimosso in EC-173; presente solo su righe storiche.
"Tutto o niente" è una proprietà del gate, non del caricamento

La garanzia atomica è al gate di validazione locale, non tra i file in S3. RecordingArtifactValidator richiede che MP4 e _input.csv e _meta.json superino tutti la validazione; se la validazione restituisce blocked, il finalizzatore non registra nessuno di essi per il caricamento (l'unica eccezione è il caso mp4_no_frames, che elimina del tutto il filmato). Questo è il punto del tutto-o-niente.

Dopo che il gate è superato, il finalizzatore registra ciascun artefatto solo se esiste su disco (registerFileIfPresent per mp4, csv, json — in modo indipendente), e i sidecar opzionali microfono/trascrizione non fanno mai parte del gate. Quindi i conteggi delle righe di s3_uploads per file_type non sono uguali tra le sessioni — le sessioni più vecchie precedono _meta.json, esistono righe dell'era ogg e i sidecar vanno e vengono. Non dare per scontato "3 righe ⇒ una sessione completa" o "ogni sessione ha un JSON". Vedi la Pipeline di validazione dei caricamenti per il contratto esatto del validatore.

complete_triplets è la vista legacy, non l'insieme moderno

Nonostante il nome, la vista complete_triplets richiede mp4 ≥ 1 AND ogg ≥ 1 AND csv ≥ 1 — il suo termine "audio" è OGG, senza alcun termine _meta.json. Poiché OGG è stato rimosso in EC-173, corrisponde solo alle sessioni storiche dell'era OGG e restituisce nulla per le registrazioni moderne MP4 + CSV + JSON. Non usarla come "l'insieme validato". Per trovare le sessioni con gli artefatti validati attuali, interroga direttamente s3_uploads sulle righe mp4/csv/json+meta.

Colonne di rilievo:

  • s3_key, s3_bucket (default playroll-captures), filename, file_type, artifact_kindcosa è l'oggetto e dove si trova. artifact_kind non ha alcun CHECK (EC-289): è derivato dal suffisso del nome file (video / input / meta / transcript), e NULL significa non classificato (es. righe legacy ogg).
  • upload_year / upload_month / upload_day — chiavi di partizione denormalizzate che rispecchiano il layout dei prefissi S3 user/game/date/<sessionId>/<filename> (vincolate: mese 1–12, giorno 1–31).
  • game_slug, lobby_id — copiate sul caricamento affinché il trigger di confine del gioco (s3_uploads_enforce_lobby_game_match, BEFORE INSERT OR UPDATE) possa imporli. Agisce solo sui caricamenti etichettati con lobby — se lobby_id IS NULL (una registrazione in solitaria) ritorna immediatamente e non controlla mai. Quando esiste una riga recording_sessions, è quella riga a fornire la verità di base con il suo (lobby_id, game_slug); lobbies.game_slug è solo un ripiego quando non è presente alcuna riga di sessione. È il confine rigido che il modello delle quest estende.
  • size_bytes, etag, uploaded_at, status (uploaded [default] / processing / completed / failed) — integrità e stato dell'oggetto.

Il collegamento a recording_sessions è per valore su session_id (entrambi text), non una foreign key dichiarata. L'unica FK dichiarata su s3_uploads è lobby_id → lobbies.id (ed è marcata NOT VALID).

La trascrizione — un artefatto di prima classe

Il _transcript.json non è un ripensamento; ha un proprio ciclo di vita, le proprie colonne di stato su recording_sessions e un flusso di lavoro scrivibile dal giocatore. Vale la pena comprenderlo a fondo perché "questa sessione è completa?" dipende da esso.

Come funziona, dall'inizio alla fine:

  • Deve una trascrizione se e solo se è stata catturata la voce. Alla fine della sessione (EC-407) il core imposta transcript_expected = true solo quando esiste un sidecar _mic.aac — lo stesso segnale che l'observer di trascrizione usa per accodare un job. Nessun microfono ⇒ nessuna trascrizione dovuta.
  • La generazione è locale e solo su GPU NVIDIA. transcription_planner.cpp esegue Whisper sulla macchina dell'utente, scegliendo un tier di modello in base a RAM/VRAM. Su una macchina non-NVIDIA restituisce NO_NVIDIA_GPU e nulla viene generato.
  • Il sidecar del microfono muore una volta che la trascrizione esiste (EC-356): un _transcript.json valido lo sostituisce, così l'audio vocale grezzo non sopravvive alla propria trascrizione.
  • Il giocatore può saltare o modificare. transcript_skipped (EC-407/EC-409) viene scritto direttamente dal client autenticato — un GRANT UPDATE(transcript_skipped) a livello di colonna più una policy RLS (auth.uid()::text = user_id) consentono all'app di impostarlo senza alcun round-trip con il service-role. L'editor in-app (EC-384) modifica {stem}_transcript.json in loco prima del caricamento.
  • Si carica come riga a sé (EC-289): file_type = 'json', artifact_kind = 'transcript', distinta dal JSON meta.
  • "Trascrizione completata" è quindi NOT transcript_expected OR transcript_skipped OR transcript_count > 0 — esattamente ciò che la vista player_recording_sessions espone.

dismissed (EC-411) segue lo stesso schema scrivibile dal giocatore: un GRANT limitato consente al client di impostarlo a true per nascondere una sessione dalla vista Activity (l'inversione è riservata al supporto).

device_hw_snapshots — su cosa è stata eseguita

Inventario hardware per (utente, dispositivo) con una chiave di deduplicazione (user_id, hw_fingerprint), così un cambio di driver / RAM / GPU produce naturalmente una nuova riga — dandoci coorti prima/dopo gratuite per le regressioni di prestazioni (vedi EC-20). La colonna source registra quale dei due writer ha prodotto la riga:

  • ui_login — la edge function register-device-hw-snapshot scrive uno snapshot a ogni login riuscito. Questo è il percorso dominante (dati live: ~18 righe ui_login per ogni 1 core_meta_json).
  • core_meta_json — la Lambda on_upload esegue il back-fill dal blocco _meta.json::hardware di una sessione caricata.

Entrambi condividono la stessa chiave di deduplicazione, quindi i due percorsi convergono su un'unica riga per ogni fingerprint hardware distinto.

Colonne appiattite e interrogabili — cpu_model, cpu_architecture, logical_processors, physical_memory_mib, os_release, primary_gpu_vendor, primary_gpu_name, primary_gpu_driver, primary_gpu_driver_nvidia — affiancano il jsonb snapshot completo. Una sessione punta allo snapshot esatto su cui è stata eseguita tramite recording_sessions.hw_snapshot_id. Vedi Inventario HW dei dispositivi.

La pipeline GPU

Dopo il caricamento, il box GPU di data-filtering elabora le sessioni. Due tabelle ne tengono traccia:

  • pipeline_sessions — l'indice della pipeline di "cosa esiste in S3 per questa sessione": s3_prefix, lo stage corrente (uno smallint), status, le chiavi degli artefatti risolte (mp4_key, csv_key, ogg_key), size_bytes_total e verified_at. La sua chiave primaria è composita — (session_id, environment), con environment ∈ (dev, prod) (default prod): la stessa sessione può essere elaborata in modo indipendente in dev e prod, producendo due righe. status percorre pending → processing → verified | failed.
  • pipeline_runs — una riga per ogni esecuzione su una sessione: il pipeline_name, il trigger_type (default manual), lo status (running / pass / fail / skipped), l'array steps_executed, un jsonb step_durations_s, params_used, il prefect_flow_run_id e output_prefix. Una sessione può avere molte esecuzioni (rielaborazione, pipeline diverse).

Entrambe si collegano a una sessione per valore su session_id. Vedi Panoramica del Data Filtering.

Diagnostica

Due tabelle spiegano perché una registrazione ha avuto difficoltà, entrambe alimentate dalla sonda NVML del core:

  • nvenc_known_contenders — un registro aggregato di processi di terze parti osservati mentre detenevano una sessione NVENC (encoder hardware della GPU) sulle macchine degli utenti mentre Playroll registrava. Con chiave process_name, con total_events, total_users, sample_codecs, sample_resolutions e i timestamp di prima/ultima osservazione. Questa è la nostra vista a livello di flotta su ciò che compete con Playroll per l'encoder (Discord, OBS, accelerazione HW del browser, altri registratori).
  • nvenc_contender_user_links — la tabella di collegamento per (contender, utente) a supporto del conteggio di utenti distinti di cui sopra. Limitata via RLS così che un utente veda solo i propri collegamenti.

bug_reports è il canale di segnalazione in-app (inviato tramite la edge function report-bug, insert con service-role, con rate-limit di 2/min/utente). Ogni segnalazione porta con sé il title (1–200 caratteri) / description (1–10000) / category (ui / recording / performance / onboarding / other), lo status (logged [default] / in_progress / fixed / verified / wont_fix), le images allegate e il contesto completo (hw_config, connection, jsonb electron_runtime, colonne di versione).

Il collegamento linear_ticket non viene compilato manualmente. Un trigger bug_reports_to_linear (AFTER INSERT, SECURITY DEFINER) si attiva a ogni insert ed esegue una POST verso la edge function bug-report-to-linear, che crea/ collega il ticket Linear e riscrive linear_ticket sulla riga. Un trigger separato bug_reports_set_updated_at mantiene updated_at. Vedi Segnalazione dei bug.


Vedi anche: riferimento completo delle colonne per queste tabelle.