tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 604Rust 56Shell 36Markdown 281227 Definiciones

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.

5b98d1b2bfb5de2d0092e7f5d1bf0a6ad737212f · por Sergio · (hace 1 mes) · padre 26dd2233d · ver el árbol en este commit

Delta semántico · 4

scripts/cosmic/atuq-en-imagen.py

scripts/vigia-imagen.py

4 archivos · +162 −34

diff --git a/docs/26-atuq-envoltorio-gecko.md b/docs/26-atuq-envoltorio-gecko.md
index 9044e229..9eab1405 100644
--- a/docs/26-atuq-envoltorio-gecko.md
+++ b/docs/26-atuq-envoltorio-gecko.md
@@ -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 1200–5000 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
diff --git a/docs/state/primera-pintura.json b/docs/state/primera-pintura.json
index f0babc4c..c761244c 100644
--- a/docs/state/primera-pintura.json
+++ b/docs/state/primera-pintura.json
@@ -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"
     }
   ]
 }
diff --git a/scripts/cosmic/atuq-en-imagen.py b/scripts/cosmic/atuq-en-imagen.py
index b47ead4f..d715f248 100755
--- a/scripts/cosmic/atuq-en-imagen.py
+++ b/scripts/cosmic/atuq-en-imagen.py
@@ -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) ----")
diff --git a/scripts/vigia-imagen.py b/scripts/vigia-imagen.py
index c8fc00e3..cef8c4f4 100644
--- a/scripts/vigia-imagen.py
+++ b/scripts/vigia-imagen.py
@@ -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» +"