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:
| Scopo | Colonne |
|---|---|
| Identità e tempistiche | session_id, user_id, game_name, game_slug, recording_started_at, recording_ended_at, duration_seconds |
| Stato | status, uploadability_status, block_reason, dismissed |
| Fatti di cattura | chunk_count, detected_resolution (jsonb), required_artifact_mode (mp4_csv_json / mp4_csv), transcript_expected, transcript_skipped |
| Provenienza | lobby_id (per valore — nessuna FK), hw_snapshot_id (FK → device_hw_snapshots.id), core_version, ui_version, installer_version |
| Ciclo di vita lato server | client_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).
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.lobby_idè metadato opzionale e non una foreign key. Una sessione è identificata dal propriosession_id; la lobby si limita ad accoppiare le sessioni simultanee. Una registrazione in solitaria halobby_idnullo e non è meno valida. Il vincolo lobby↔gioco è imposto da un trigger sus3_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:
| Artefatto | file_type / artifact_kind | Blocca il caricamento? |
|---|---|---|
| Video | mp4 / video | Sì — richiesto dal validatore. |
| Log di input | csv / input (_input.csv) | Sì — richiesto. |
| Metadati | json / meta (_meta.json) | Sì — richiesto. |
| Audio del microfono | _mic.aac | No — sidecar opzionale (EC-259). |
| Trascrizione | json / transcript (_transcript.json) | No — opzionale, generata durante/dopo il caricamento. |
| Audio (legacy) | ogg | No — rimosso in EC-173; presente solo su righe storiche. |
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 modernoNonostante 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(defaultplayroll-captures),filename,file_type,artifact_kind— cosa è l'oggetto e dove si trova.artifact_kindnon ha alcun CHECK (EC-289): è derivato dal suffisso del nome file (video/input/meta/transcript), eNULLsignifica non classificato (es. righe legacy ogg).upload_year/upload_month/upload_day— chiavi di partizione denormalizzate che rispecchiano il layout dei prefissi S3user/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 — selobby_id IS NULL(una registrazione in solitaria) ritorna immediatamente e non controlla mai. Quando esiste una rigarecording_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 = truesolo 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.cppesegue Whisper sulla macchina dell'utente, scegliendo un tier di modello in base a RAM/VRAM. Su una macchina non-NVIDIA restituisceNO_NVIDIA_GPUe nulla viene generato. - Il sidecar del microfono muore una volta che la trascrizione esiste
(EC-356): un
_transcript.jsonvalido 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 — unGRANT 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.jsonin loco prima del caricamento. - Si carica come riga a sé (EC-289):
file_type = 'json',artifact_kind = 'transcript', distinta dal JSONmeta. - "Trascrizione completata" è quindi
NOT transcript_expected OR transcript_skipped OR transcript_count > 0— esattamente ciò che la vistaplayer_recording_sessionsespone.
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 functionregister-device-hw-snapshotscrive uno snapshot a ogni login riuscito. Questo è il percorso dominante (dati live: ~18 righeui_loginper ogni 1core_meta_json).core_meta_json— la Lambdaon_uploadesegue il back-fill dal blocco_meta.json::hardwaredi 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, lostagecorrente (uno smallint),status, le chiavi degli artefatti risolte (mp4_key,csv_key,ogg_key),size_bytes_totaleverified_at. La sua chiave primaria è composita —(session_id, environment), conenvironment ∈ (dev, prod)(defaultprod): la stessa sessione può essere elaborata in modo indipendente in dev e prod, producendo due righe.statuspercorrepending → processing → verified | failed.pipeline_runs— una riga per ogni esecuzione su una sessione: ilpipeline_name, iltrigger_type(defaultmanual), lostatus(running/pass/fail/skipped), l'arraysteps_executed, un jsonbstep_durations_s,params_used, ilprefect_flow_run_ideoutput_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 chiaveprocess_name, contotal_events,total_users,sample_codecs,sample_resolutionse 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.