Vista generada de dec-0112: La suite WC-JF12-xx contra Jellyfin 12 es gate obligatorio de todo diferencial
docs/decisions/dec-0112-wc-jf12-gate-obligatorio.mdVista generada desde
docs/decisions/dec-0112-wc-jf12-gate-obligatorio.md. No se edita a mano:bun run docs:genla regenera ybun run docs:checkfalla si difiere. El estado aquí es el del model: si discrepa con otra página, manda el model.
| Campo | Valor |
|---|---|
| Estado | LOCKED |
| Fecha | 2026-09-28 |
| Fichero | docs/decisions/dec-0112-wc-jf12-gate-obligatorio.md |
Enmienda a: r48
Por qué importa (del frontmatter del ADR):
Gobierna cuándo Styx puede afirmar un diferencial frente a Jellyfin y cómo se mide. Sin este ADR, un executor leería r48 L4 («WC-HIBR-01 NO es gate, es input de diseño») y cerraría nodos con claims de rendimiento sin benchmark, o repetiría los overclaims de
jellyfin12-design(3x en direct play, RSS por sesión que esconde el coste fijo de r17, ratios contra un suelo, p99 sobre 5 muestras). Fija la metodología, los ratios por benchmark y la derivación del gate vía evidence-manifest.
Nodos del roadmap que lo citan en refs: track/media-engine, track/docs
Páginas de la documentación que lo citan: Escribir una página de contrato (especificado), Motor de medios y plan de reproducción (implementado), Motores Zig y libav (especificado), Visión (especificado)
Leído de docs/decisions/dec-0112-wc-jf12-gate-obligatorio.md, el fichero canónico.
AskUserQuestion (2026-09-28): la suite WC-JF12-xx (seek, densidad,
plan, crash, librería, subtítulos) contra Jellyfin 12 en el mismo host es GATE OBLIGATORIO: un
diferencial frente a Jellyfin sólo cuenta como hecho si el benchmark reproducible lo demuestra
con el ratio fijado.r48 L4 (WC-HIBR-01 era input de diseño, «Styx debe ganar» prohibido como gate).r28 §3 (anti-especulativo: cada capa termina en un workload real), r31 (verde falso;
verificador ≠ implementador), r17 (split-duro: coste fijo de la topología), r23 (capabilities
versionadas), r44 (SLOs de Candidate H), dec-0103.dec-0110; la numeración final de las tandas
2/3 asigna dec-0110 al motor de medios dual, dec-0111 a client-core ABI v0 (rama
w2/client-core-c0) y dec-0113 a identity token model (rama w2/identity). Este ADR queda en
dec-0112. Tabla completa en la cabecera de dec-0110.WC-HIBR-01 (4K REMUX ~80 Mbps, 2 h, fuente remota), Styx vs
Jellyfin, explícitamente como input de diseño y no gate. El motivo era sano entonces: no
había runtime que medir y un gate «Styx gana» habría empujado a diseñar para el benchmark.jellyfin12-design, sesión 2026-09-28) propone una familia
WC-JF12-xx con objetivos «3x». Su crítica adversarial encontró que varios ratios parten de
premisas falsas o no demuestran nada:
WC-JF12-* con verdict: pass lo respalda en el evidence-manifest del nodo que lo afirma.done
sin ese item en pass. Si el benchmark no lo demuestra, el nodo tiene dos salidas, ambas
explícitas y registradas: (a) seguir abierto, o (b) retirar o rebajar el claim (p. ej. de
«ratio 1/3» a «paridad») vía AskUserQuestion. Rebajar en silencio está prohibido.product-horizon o UI un «Nx mejor que Jellyfin» sin item
en pass. Tampoco se afirma nada frente a Plex u otros no medidos en el harness.Corpus fijado por hash.
corpus.manifest con sha256 por fichero, tamaño, origen/licencia y, para lo sintético, el
generador + seed. Toda corrida registra el hash del manifest; si no coincide, la corrida no vale.Mismo host, contenedores lado a lado.
deploy/), desplegados en el mismo host. El host de referencia es el nodo local de
waxin; su huella (CPU, RAM, kernel, versión de cgroup, versiones de Docker, aceleración HW
disponible) queda en host.fingerprint y los objetivos se pre-registran contra ese host.
Evidencia generada en otro host (p. ej. el contenedor de desarrollo) no cierra items.docker compose stop) para que no compita por CPU/caché. Orden intercalado A B A B … para
cancelar deriva térmica y de caché de página; caché de página del host vaciada entre corridas
cuando la métrica sea de arranque en frío (y declarado cuando sea en caliente).DeviceProfile
de Jellyfin y como capabilities r23 de Styx, generados desde una única fuente en el harness.Separación por modo (corrección de la crítica, punto 1). Cada benchmark de reproducción se reporta por separado en direct play / remux / transcode. Los ratios agresivos sólo se pre-registran donde Jellyfin usa FFmpeg de verdad (remux y transcode). En direct play el objetivo por defecto es paridad.
Clientes. Cliente sintético sin interfaz (pull de byte ranges / segmentos, registra stalls y tiempos por evento) para seek y densidad; perfil «direct-play-capable» (Apple/TV emulado por capabilities) para el escenario WC-HIBR-01; Chromium con MSE sólo para remux y UX.
Red. Rejilla netem realista: pérdida {0,5 %, 1 %, 2 %} × RTT {20, 50, 80 ms}, más el caso sin netem. No se usa 10 % de pérdida.
Corridas y estadística.
Recursos: host completo, simétrico (corrección de la crítica, punto 4).
cpu.stat usage_usec, memory.current,
memory.peak), muestreados a 1 Hz, agregados por sistema: Jellyfin = su contenedor (con sus
hijos ffmpeg); Styx = todos sus contenedores (servicios, daemon, Postgres, Valkey, NATS,
otel-collector).Pre-registro. El baseline de Jellyfin se publica antes que los números de Styx. Los
objetivos (targets.yml: métrica, clase ratio|umbral|paridad, objetivo, suelo, modo) se
commitean antes de la corrida de Styx; cambiarlos después de ver datos de Styx exige
AskUserQuestion.
Clase: R = ratio Styx/Jellyfin (con IC95 y suelo), U = umbral absoluto, P = paridad (Styx ≤ Jellyfin + IC95), I = informe obligatorio sin objetivo.
| Item | Qué mide | Objetivos |
|---|---|---|
WC-JF12-SEEK-01 | TTFF y seek aleatorio (50 seeks/corrida, incl. saltos > 30 min) sobre el REMUX 4K; absorbe el escenario WC-HIBR-01 | remux: seek p95 R ≤ 1/3, TTFF p95 R ≤ 1/3, stalls/h U = 0 · direct play: TTFF y seek p95 P, stalls/h U = 0 · bytes-before-first-frame y overfetch I |
WC-JF12-DENSITY-01 | sesiones sostenidas sin stall en rampa 1→N hasta saturación | remux: sesiones por núcleo R ≥ 3x, CPU/Gbps R ≤ 1/3, pendiente RSS marginal R ≤ 1/3 · direct play: sesiones por núcleo P · RSS en reposo de host y N* I |
WC-JF12-PLAN-01 | tasa de transcode completo en matriz 30 ficheros × 6 perfiles (mismos perfiles en ambos lados) | tasa de transcode completo R ≤ 1/3 (si Jellyfin < 9/180: U Styx ≤ Jellyfin) · planes con explicación máquina-legible por pista U = 100 % |
WC-JF12-CRASH-01 | 20 kills aleatorios en 2 h, dos escenarios simétricos: (a) proceso de datos (daemon Styx vs hijo ffmpeg de Jellyfin), (b) proceso servidor | (a) pausa visible p95 U < 1,5 s y R ≤ 1/3 si el baseline supera el suelo; segmentos perdidos U = 0 · (b) reanudación sin acción del usuario U (Styx) e I (Jellyfin) |
WC-JF12-LIB-01 | Continue Watching, Next Up, búsqueda y listado sobre 50k items / 200k episodios | p99 de cada endpoint R ≤ 1/3 (baseline de la 12.0 primero: es el área con BD rediseñada, la más difícil) |
WC-JF12-SUBS-01 | 40 ficheros con SRT/ASS/PGS/VobSub y embebidos grandes | bloqueos de vídeo causados por subtítulos U = 0 · tiempo al primer cue (texto) p95 R ≤ 1/3 · subtítulos de imagen: ruta declarada (render en cliente o burn-in contado como transcode, reportado aparte) I |
WC-JF12-SCAN-01 (escaneo e identificación) entra en la suite con las mismas reglas cuando
exista su consumidor; sus objetivos los fija dec-0114.AskUserQuestion; no hace falta
enmendar este ADR.w3prep/jf12-harness; ruta propuesta tools/jf12-bench/, gana
la ruta real) produce por corrida un results.json con: muestras crudas por evento, resúmenes
por corrida, series cgroup, host.fingerprint, digests de imagen, hash de corpus.manifest,
commit de Styx, configuración de motor (dec-0110: engine=zig|libav) y netem aplicado.verdict mecánico (sin juicio humano) lee results.json + targets.yml y emite
por item: pass | fail | inconclusive (inconclusive = IC95 cruza el objetivo, n insuficiente o
huella de host/corpus que no coincide), con el cálculo resumido.docs/<nodo>/evidence/gate.manifest.yml con command,
output (resumen del verdict: ratio, IC95, n, modo), evidenceRef (resultados crudos
commiteados, o ruta + sha256 si pesan demasiado), author ≠ verifier (r31).class: benchmark-jf12 y el bloque gate: del nodo es
generated: true: el veredicto del modelo se deriva del manifest, nunca se escribe a mano.track/byte-runtime; PLAN-01,
CRASH-01 y SUBS-01 → outcome/playback-core; LIB-01 → track/domain-media /
outcome/first-vertical; SCAN-01 → la wave de scanner de track/plugin-seams (dec-0114).
La autoridad de metodología sigue en spike/reference-harvest (split de r48 L4 intacto):
metodología, baseline y targets.yml allí; decisiones de runtime en cada nodo.track/byte-runtime M3 (WC-HIBR-01) deja de ser un informe y pasa a ser el escenario direct
play / remux de SEEK-01; su DoD («no convertir esto en gate») queda superado por este ADR.testing-evidence (mutante del verdict que invierte un resultado debe fallar).styx.model.yml ni toca ROADMAP.md: eso es la
pasada de gobernanza posterior.targets.yml más allá de la tabla §3; los suelos concretos por
métrica se pre-registran con el baseline de Jellyfin.r48 L4 lleva banner de enmienda apuntando aquí.dec-0110 (motor de medios dual: la configuración
engine es una dimensión de las corridas), dec-0114 (scanner por contenido: SCAN-01).