atuq: no era el perfil — son DOS SALIDAS, y el «3 de 13» medía la cámara

Dos cosas antes de la medición: el MOZ_LOG de las que pintan y las que no termina IDÉNTICO (las dos
commitean WaylandBufferSHM de 1280x696), y el cruce de las 15 corridas está confundido — las 13 de
«perfil del usuario» salieron todas con VGA=1.

`screendump` de QMP fotografía UN dispositivo. Con VGA=1 hay dos, y hasta ahora se miraba uno. Con
`id=` en cada uno y capturando los dos en cada toma, en la misma corrida y el mismo instante:

    +312s  ██ PRIMERA PINTURA — 704456 px EN vga0
           gpu0 = 962675 px de fondo + panel y dock (el escritorio, SIN navegador)
           vga0 = 703766 px magenta                 (la ventana, con la página)

⇒ cosmic-comp maneja las DOS salidas y la ventana cae en una u otra. «Pinta 3 de 13» era cuántas
veces cayó en la pantalla que yo fotografiaba: una tasa de mi cámara, no del producto. El §6.10.ter
tiene la misma explicación (aquel arranque también traía `drm: card0 card1`).

Queda en pie, ya sin confundido: el navegador arranca, mapea y commitea SIEMPRE; cuando la ventana
está en la pantalla que se mira, se ve entre +184 y +312 s; el perfil no era la causa y cosmic-comp
nesteado tampoco.

⚠ Tercera vez en el día con la misma forma: un ✗ de una captura de UNA pantalla no dice «la ventana
no está», dice «no está en ESA» — igual que `mapped 1` no probaba que se viera. El estado anota ahora
en qué pantalla apareció y cuántas se fotografiaron, y el vigía NO cuenta las ciegas: las nombra.
This commit is contained in:
Sergio
2026-09-14 18:57:00 +00:00
parent 26dd2233dc
commit 5b98d1b2bf
4 changed files with 162 additions and 34 deletions
+53 -1
View File
@@ -1773,7 +1773,7 @@ buffer no llega a la pantalla. Lo próximo es mirarlo del lado del compositor
recibe su `configure`, en qué workspace y en qué salida quedó la ventana— y correr N veces para tener
una tasa, no una anécdota.
#### 6.10.septies La TASA: pinta **3 de 13** con el perfil del usuario — y cuando pinta, siempre a ~200 s (2026-09-14)
#### 6.10.septies La TASA: «3 de 13» — ⚠ **el número medía LA CÁMARA, no el producto (ver §6.10.octies)** (2026-09-14)
Diez corridas seguidas, idénticas, con `scripts/cosmic/tasa-primera-pintura.sh`: `--as-user`, `VGA=1`,
ventana de 480 s, captura cada 30 s. Más las cinco de las secciones anteriores, todas sobre la misma
@@ -1784,6 +1784,12 @@ imagen y anotadas en `docs/state/primera-pintura.json`:
| perfil del usuario (lo que hace un usuario) | **3 de 13** | 184, 199, 204 s |
| perfil nuevo en tmpfs | 2 de 2 | 197, 197 s |
⚠ **ESTA TABLA NO SIGNIFICA LO QUE PARECE.** Las 13 del perfil del usuario salieron todas con `VGA=1`
y la de perfil nuevo casi ninguna ⇒ «perfil» y «vídeo» están confundidos; y las capturas miraban UNA
de las DOS salidas. El §6.10.octies lo mide: la ventana cae en cualquiera de las dos pantallas, así
que los ✗ son «no estaba en la que fotografié». Lo que sí sobrevive es el tiempo: cuando se la ve,
se la ve entre +184 y +312 s.
**Dos cosas que el número dice y una anécdota no podía:**
1. **cuando pinta, pinta siempre en la misma ventana** —184…204 s— y nunca a los 300, 400 ni 900. O
@@ -1805,6 +1811,52 @@ ventana de observación›»—. **No las invalida** (se verificó una por una q
dejó su `MOZ_LOG` de 12005000 líneas), pero una corrida abortada de verdad se vería casi igual: por
eso el guion de la tasa cuenta las **abortadas aparte** en vez de sumarlas a los fallos.
#### 6.10.octies La causa: **son DOS SALIDAS y la ventana cae en cualquiera** — y el «3 de 13» medía la cámara (2026-09-14)
El §6.10.septies cerró con «con el perfil del usuario pinta 3 de 13, con perfil nuevo 2 de 2» y la
pregunta «qué hace el primer arranque del perfil». La respuesta es: **nada**. Dos cosas antes de la
medición que la contestó:
1. **el `MOZ_LOG` de las que pintan y las que no termina IDÉNTICO** — las dos commitean
`WaylandBufferSHM` de 1280×696. O sea que el navegador dibuja igual en los dos casos;
2. **el cruce de las 15 corridas está CONFUNDIDO**: las 13 «perfil del usuario» salieron TODAS con
`VGA=1`, y de «perfil nuevo» hay una sola con dos tarjetas. Con esa tabla no se puede separar
«perfil» de «vídeo»: comparar una columna que no varía es comparar con nada.
**La medición que lo contesta:** `screendump` de QMP fotografía **un** dispositivo. Con `VGA=1` hay
dos (`vga0` estándar + `gpu0` virtio-gpu) y hasta ahora se fotografiaba uno. Se le puso `id=` a cada
uno y se capturan LOS DOS en cada toma. Resultado, en la misma corrida y el mismo instante:
+312s ██ PRIMERA PINTURA — 704456 px de la página EN vga0
gpu0 = 962 675 px de fondo azul + panel y dock (el escritorio, SIN navegador)
vga0 = 703 766 px magenta (la ventana, con la página)
⇒ **`cosmic-comp` maneja las DOS salidas y la ventana cae en una u otra.** Lo que se publicó como
«pinta 3 de 13» era **cuántas veces la ventana cayó en la pantalla que yo fotografiaba**. No es una
tasa del producto: es una tasa de mi cámara. Y el §6.10.ter —«arranca y no pinta la ventana»— tiene
la misma explicación: aquel arranque también tenía dos tarjetas (`drm: card0 card1`), y la captura
miraba una.
**Lo que queda en pie de todo lo anterior**, ahora sí sin confundido:
· el navegador arranca, mapea su superficie y commitea cuadros **siempre** (eso nunca falló);
· cuando la ventana está en la pantalla que se mira, se ve entre **+184 y +312 s**;
· el perfil del usuario **no** era la causa, y `cosmic-comp` nesteado tampoco (§6.10.quater).
⚠ **La lección de método, que es la tercera vez en el mismo día:** con dos salidas, un ✗ de una
captura de una sola pantalla **no dice «la ventana no está»**, dice «no está en ESA». Igual que
`mapped 1` no probaba que se viera, una foto no prueba que no exista: prueba dónde no está. Por eso
`docs/state/primera-pintura.json` anota ahora **en qué pantalla** apareció y **cuántas fotografió**
cada corrida, y `scripts/vigia-imagen.py` **no cuenta las ciegas** — las nombra aparte:
⚠ pintaron 6 de 6 corridas CONCLUYENTES · primera pintura +184…+312s
✓ +312s vga0 perfil del usuario vga+virtio-gpu (dos DRM)
⚠ 10 corridas NO se cuentan: con dos salidas fotografiaron UNA sola, y su ✗ sólo dice que
la ventana no estaba en esa pantalla
**Lo que sigue**, y ahora es otra pregunta: no «por qué no pinta» sino **por qué el escritorio abre la
ventana en la salida que no tiene el foco** —y si con una sola salida (`-vga none`, que es como se va
a usar la imagen de verdad) eso desaparece—. Es barato: la misma serie con una tarjeta.
### 6.11 Y ahora la pregunta que faltaba: ¿REPRODUCE? (2026-09-07)
El §6.10 preguntó «¿qué NO puede hacer?» y contestó leyendo cadenas. Media pregunta. La otra mitad
+52 -15
View File
@@ -14,7 +14,9 @@
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py",
"capturas": "work/atuq-imagen-1tarjeta",
"nota": "reconstruida de las capturas guardadas"
"nota": "reconstruida de las capturas guardadas",
"pantallas_fotografiadas": 1,
"pantalla": "desconocida (una sola captura)"
},
{
"medido": "2026-09-14T14:36:00Z",
@@ -30,7 +32,9 @@
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py",
"capturas": "work/atuq-imagen-2tarjetas",
"nota": "reconstruida de las capturas guardadas"
"nota": "reconstruida de las capturas guardadas",
"pantallas_fotografiadas": 1,
"pantalla": "desconocida (una sola captura)"
},
{
"medido": "2026-09-14T14:47:00Z",
@@ -46,7 +50,8 @@
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py",
"capturas": "work/atuq-imagen-comousuario",
"nota": "reconstruida; el MOZ_LOG commiteaba buffers a +330 s, DESPUÉS de la última captura"
"nota": "reconstruida; el MOZ_LOG commiteaba buffers a +330 s, DESPUÉS de la última captura",
"pantallas_fotografiadas": 1
},
{
"medido": "2026-09-14T15:10:00Z",
@@ -62,7 +67,9 @@
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py",
"capturas": "work/atuq-imagen-usuario-largo",
"nota": "reconstruida de las capturas guardadas"
"nota": "reconstruida de las capturas guardadas",
"pantallas_fotografiadas": 1,
"pantalla": "desconocida (una sola captura)"
},
{
"medido": "2026-09-14T15:58:44Z",
@@ -78,7 +85,8 @@
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py",
"capturas": "work/atuq-imagen-medicion",
"nota": "NO pintó en 900 s y el MOZ_LOG commiteaba WaylandBufferSHM desde ≤ +377 s: «mapped 1» NO prueba que se vea"
"nota": "NO pintó en 900 s y el MOZ_LOG commiteaba WaylandBufferSHM desde ≤ +377 s: «mapped 1» NO prueba que se vea",
"pantallas_fotografiadas": 1
},
{
"medido": "2026-09-14T16:17:13Z",
@@ -92,7 +100,8 @@
"mem_mb": 2560,
"vcpus": 2,
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py"
"guion": "scripts/cosmic/atuq-en-imagen.py",
"pantallas_fotografiadas": 1
},
{
"medido": "2026-09-14T16:26:43Z",
@@ -106,7 +115,9 @@
"mem_mb": 2560,
"vcpus": 2,
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py"
"guion": "scripts/cosmic/atuq-en-imagen.py",
"pantallas_fotografiadas": 1,
"pantalla": "desconocida (una sola captura)"
},
{
"medido": "2026-09-14T16:39:48Z",
@@ -120,7 +131,8 @@
"mem_mb": 2560,
"vcpus": 2,
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py"
"guion": "scripts/cosmic/atuq-en-imagen.py",
"pantallas_fotografiadas": 1
},
{
"medido": "2026-09-14T16:48:45Z",
@@ -134,7 +146,9 @@
"mem_mb": 2560,
"vcpus": 2,
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py"
"guion": "scripts/cosmic/atuq-en-imagen.py",
"pantallas_fotografiadas": 1,
"pantalla": "desconocida (una sola captura)"
},
{
"medido": "2026-09-14T17:03:19Z",
@@ -148,7 +162,8 @@
"mem_mb": 2560,
"vcpus": 2,
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py"
"guion": "scripts/cosmic/atuq-en-imagen.py",
"pantallas_fotografiadas": 1
},
{
"medido": "2026-09-14T17:18:52Z",
@@ -162,7 +177,8 @@
"mem_mb": 2560,
"vcpus": 2,
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py"
"guion": "scripts/cosmic/atuq-en-imagen.py",
"pantallas_fotografiadas": 1
},
{
"medido": "2026-09-14T17:34:16Z",
@@ -176,7 +192,8 @@
"mem_mb": 2560,
"vcpus": 2,
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py"
"guion": "scripts/cosmic/atuq-en-imagen.py",
"pantallas_fotografiadas": 1
},
{
"medido": "2026-09-14T17:51:50Z",
@@ -190,7 +207,8 @@
"mem_mb": 2560,
"vcpus": 2,
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py"
"guion": "scripts/cosmic/atuq-en-imagen.py",
"pantallas_fotografiadas": 1
},
{
"medido": "2026-09-14T18:10:43Z",
@@ -204,7 +222,8 @@
"mem_mb": 2560,
"vcpus": 2,
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py"
"guion": "scripts/cosmic/atuq-en-imagen.py",
"pantallas_fotografiadas": 1
},
{
"medido": "2026-09-14T18:25:53Z",
@@ -218,7 +237,25 @@
"mem_mb": 2560,
"vcpus": 2,
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py"
"guion": "scripts/cosmic/atuq-en-imagen.py",
"pantallas_fotografiadas": 1
},
{
"medido": "2026-09-14T18:54:22Z",
"imagen": "/mnt/cosecha/takana-cosmic-qemu.img",
"segundos_primera_pintura": 312,
"presupuesto_s": 420,
"ventana_s": 480,
"cadencia_s": 40,
"lanzamiento": "perfil del usuario",
"video": "vga+virtio-gpu (dos DRM)",
"mem_mb": 2560,
"vcpus": 2,
"acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py",
"pantallas_fotografiadas": 2,
"pantalla": "vga0",
"nota": "la ventana apareció en vga0 mientras gpu0 seguía mostrando el escritorio sin navegador"
}
]
}
+36 -13
View File
@@ -46,7 +46,13 @@ SMP = os.environ.get("SMP", "2")
# Un compositor con dos tarjetas puede componer en la que el `screendump` NO muestra. Por eso el
# default acá es UNA sola (-vga none) y la reproducción del caso original es una opción explícita:
# son dos mediciones distintas y hay que poder pedir cada una por su nombre.
VGA_ARGS = [] if os.environ.get("VGA") == "1" else ["-vga", "none"]
# Las dos pantallas van con ID EXPLÍCITO (`vga0`, `gpu0`) y no por el `-vga std` implícito, porque
# `screendump` de QMP fotografía UN dispositivo y hay que poder pedir cada uno por su nombre: con dos
# salidas, el compositor puede poner la ventana en la que la captura por defecto NO muestra, y eso se
# ve exactamente igual que «la ventana no aparece».
DOS_PANTALLAS = os.environ.get("VGA") == "1"
VGA_ARGS = (["-vga", "none", "-device", "VGA,id=vga0"] if DOS_PANTALLAS else ["-vga", "none"])
PANTALLAS = ["gpu0", "vga0"] if DOS_PANTALLAS else ["gpu0"]
class Serial:
@@ -115,6 +121,18 @@ class Serial:
return self.esperar(marca, segundos, nombre=f"de «{orden[:40]}»")
def capturar(ruta_qmp, out, nombre):
"""Fotografía CADA pantalla del guest y devuelve {pantalla: fichero}.
Con dos salidas hay que mirar las dos: la ventana puede estar viva y dibujando en la que no se
fotografía, y eso es indistinguible de «no pinta» si sólo se mira una.
"""
ficheros = {p: os.path.join(out, f"{nombre}-{p}.ppm") for p in PANTALLAS}
qmp(ruta_qmp, [{"execute": "screendump", "arguments": {"filename": f, "device": p}}
for p, f in ficheros.items()])
return ficheros
def pintado(ppm):
"""¿Cuántos píxeles de la pantalla son la página de prueba?
@@ -132,7 +150,7 @@ def pintado(ppm):
return sum(n for n, (r, g, b) in im.getcolors(1 << 24) if r > 90 and b > 90 and g < 40)
def anotar_medicion(segundos, a):
def anotar_medicion(segundos, a, pantalla=None):
"""Deja el tiempo de primera pintura en `docs/state/primera-pintura.json`.
Va al estado del repo y no a un log porque quien pregunta «¿esta imagen se puede usar?» es
@@ -145,11 +163,14 @@ def anotar_medicion(segundos, a):
"medido": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"imagen": IMG,
"segundos_primera_pintura": segundos, # None = no pintó dentro de la ventana
"pantalla": pantalla, # EN CUÁL de las salidas apareció (medido 2026-09-14:
# con dos, la ventana cae en cualquiera de las dos)
"pantallas_fotografiadas": len(PANTALLAS), # con 1 y dos salidas, un ✗ NO es concluyente
"presupuesto_s": a.budget,
"ventana_s": a.wait,
"cadencia_s": a.cadence, # la RESOLUCIÓN del número: ±cadencia
"lanzamiento": "perfil del usuario" if a.as_user else "perfil nuevo en tmpfs",
"video": "vga+virtio-gpu (dos DRM)" if VGA_ARGS == [] else "virtio-gpu solo (-vga none)",
"video": "vga+virtio-gpu (dos DRM)" if DOS_PANTALLAS else "virtio-gpu solo (-vga none)",
"mem_mb": int(MEM), "vcpus": int(SMP), "acel": "tcg",
"guion": "scripts/cosmic/atuq-en-imagen.py",
}
@@ -214,7 +235,7 @@ def main():
qemu = [
"nice", "-n", "10", "qemu-system-x86_64",
"-machine", "q35", "-accel", "tcg", "-cpu", "max", "-m", MEM, "-smp", SMP,
*VGA_ARGS, "-device", "virtio-gpu-pci", "-display", "none", "-nic", "none",
*VGA_ARGS, "-device", "virtio-gpu-pci,id=gpu0", "-display", "none", "-nic", "none",
"-drive", f"if=pflash,format=raw,unit=0,file={OVMF_CODE},readonly=on",
"-drive", f"if=pflash,format=raw,unit=1,file={vars_fd}",
"-drive", f"file={IMG},format=raw,if=virtio",
@@ -244,8 +265,8 @@ def main():
print(ser.cmd("echo ENTORNO: WD=$WAYLAND_DISPLAY XDG=$XDG_RUNTIME_DIR DBUS=$DBUS_SESSION_BUS_ADDRESS").strip())
print(ser.cmd("ls -l $XDG_RUNTIME_DIR; ls /usr/lib/dri").strip())
qmp(qmp_sock, [{"execute": "screendump", "arguments": {"filename": os.path.join(out, "antes.ppm")}}])
print("== captura ANTES", flush=True)
capturar(qmp_sock, out, "antes")
print("== captura ANTES (%s)" % ", ".join(PANTALLAS), flush=True)
if a.only_cosmic:
print("== CONTROL: no se lanza el navegador")
@@ -278,21 +299,23 @@ def main():
primera = None # segundos hasta la PRIMERA captura con la página en pantalla
while time.time() - t0 < a.wait:
time.sleep(a.cadence); n += 1
shot = os.path.join(out, f"durante-{n}.ppm")
qmp(qmp_sock, [{"execute": "screendump", "arguments": {"filename": shot}}])
tomas = capturar(qmp_sock, out, f"durante-{n}")
t = int(time.time() - t0)
px = pintado(shot)
medidas = {p: pintado(f) for p, f in tomas.items()}
px = max((v for v in medidas.values() if v is not None), default=None)
donde = next((p for p, v in medidas.items() if v and v > 100_000), None)
if primera is None and px and px > 100_000:
primera = t
print(f" +{t}s ██ PRIMERA PINTURA — {px} px de la página", flush=True)
print(f" +{t}s ██ PRIMERA PINTURA — {px} px de la página EN {donde}", flush=True)
if a.until_paint:
break
else:
vivos = ser.cmd("for c in /proc/[0-9]*/comm; do cat $c 2>/dev/null; done | grep -c atuq")
v = vivos.splitlines()[1].strip() if len(vivos.splitlines()) > 1 else "?"
print(f" +{t}s px de la página: {px if px is not None else '?'} procesos atuq: {v}", flush=True)
detalle = " ".join(f"{p}={medidas[p]}" for p in PANTALLAS)
print(f" +{t}s px de la página: {detalle} procesos atuq: {v}", flush=True)
qmp(qmp_sock, [{"execute": "screendump", "arguments": {"filename": os.path.join(out, "despues.ppm")}}])
capturar(qmp_sock, out, "despues")
print("== captura DESPUES", flush=True)
# EL NÚMERO. No «pinta / no pinta»: CUÁNTO TARDA. Medido el 2026-09-14 sobre esta misma
@@ -314,7 +337,7 @@ def main():
# El número se PUBLICA donde lo pueda leer el vigía de la imagen, que es estático y no
# arranca nada: sin esto el dato vive en el scrollback del que corrió la VM y se pierde,
# que es exactamente lo que pasó con la corrida a mano del §6.10.ter.
anotar_medicion(primera, a)
anotar_medicion(primera, a, donde if primera is not None else None)
# La evidencia que no se puede leer de una captura: qué hizo el widget de Wayland.
print("---- atuq.log (cola) ----")
+21 -5
View File
@@ -43,6 +43,10 @@
# Ninguno contesta «¿y cuándo se ve?» — y esa pregunta ya cobró: el §6.10.ter del SDD 26 dio por
# hecho que `atuq` «no pinta nunca» en la imagen arrancada CON ESTE VIGÍA EN ✓, y la medición
# repetida mostró que pintaba a los +137…+204 s. Lo medido había sido la paciencia.
# Y el 2026-09-14, peor: con DOS tarjetas DRM la ventana cae en cualquiera de las dos salidas, y las
# capturas miraban UNA ⇒ el «3 de 13» que se publicó medía dónde apuntaba la cámara. Por eso el dato
# que se lee acá trae en qué PANTALLA apareció, y las corridas ciegas (dos salidas, una captura) no
# se cuentan.
# Arrancar la imagen son ~15 min de QEMU sin KVM, así que no se hace acá: `scripts/cosmic/atuq-en-
# imagen.py` lo mide y deja el número en `docs/state/primera-pintura.json`, y este vigía lo LEE y lo
# dice con sus condiciones (lanzamiento, vídeo, RAM, vcpus) — el tiempo depende de la carga de la
@@ -273,26 +277,38 @@ def informe_primera_pintura():
print(" ⊘ el fichero no tiene corridas")
return False
# Se informan TODAS las corridas y no la última, porque el fenómeno es INTERMITENTE: el
# 2026-09-14 dos corridas idénticas dieron «+204 s» y «no pintó en 900 s». Un solo número acá
# sería el de la corrida que tocó, y el lector lo leería como el del producto.
# Se informan TODAS las corridas y no la última, porque el fenómeno es INTERMITENTE. Y se separan
# por CUÁNTAS SALIDAS fotografió cada una: medido el 2026-09-14, con dos tarjetas DRM la ventana
# cae en cualquiera de las dos salidas, así que un ✗ de una corrida que sólo miró una pantalla NO
# dice que la ventana no esté: dice que no estaba EN ESA. Contarlos juntos fue lo que produjo un
# «3 de 13» que medía la cámara y no el producto.
ciegas = [c for c in corridas if c.get("pantallas_fotografiadas", 1) < 2
and "dos DRM" in (c.get("video") or "")
and c.get("segundos_primera_pintura") is None]
corridas = [c for c in corridas if c not in ciegas]
pintaron = [c["segundos_primera_pintura"] for c in corridas
if c.get("segundos_primera_pintura") is not None]
n, m = len(pintaron), len(corridas)
marca = "" if n == m else ("" if n == 0 else "")
if ciegas:
marca = "" # con corridas ciegas de por medio, un 6/6 no es un ✓: es un «lo que se pudo medir»
if pintaron:
print(" %s pintaron %d de %d corridas · primera pintura %s"
print(" %s pintaron %d de %d corridas CONCLUYENTES · primera pintura %s"
% (marca, n, m,
("+%ds" % pintaron[0]) if n == 1 else "+%d…+%ds" % (min(pintaron), max(pintaron))))
else:
print(" %s NINGUNA de %d corridas pintó" % (marca, m))
for c in corridas[-6:]:
seg = c.get("segundos_primera_pintura")
print(" %s %-9s %-22s %-26s %s"
print(" %s %-9s %-10s %-22s %-26s %s"
% ("" if seg is not None else "",
("+%ds" % seg) if seg is not None else "",
(c.get("pantalla") or "")[:10],
c.get("lanzamiento", "?"), c.get("video", "?"),
(c.get("medido") or "")[:16]))
if ciegas:
print("%d corridas NO se cuentan: con dos salidas fotografiaron UNA sola, y su ✗ sólo"
" dice que la ventana no estaba en esa pantalla" % len(ciegas))
fallidas = [c for c in corridas if c.get("segundos_primera_pintura") is None]
if fallidas:
print(" ⚠ en las que NO pintó, el MOZ_LOG igual decía `mapped 1` + «has buffer» +"