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 1574Shell 204Rust 111Markdown 993763 Definiciones

mirada-usb: imagen USB de prueba de mirada en GPU ajena (NVIDIA)

- recipes/mesa-swrast.toml: mesa softpipe hammer-nativo (sin LLVM) para render software.
- scripts/mirada-usb.sh: ensambla rootfs (dev-fs musl + hydrate mirada/seatd/libinput/
  mesa + overlay Alpine llvmpipe/lavapipe/nouveau + XKB + PAM + init seatd+compositor) y
  empaqueta la USB con linux-generic vía systemd-boot.
- docs/mirada-usb-nvidia.md: uso + estado.

Validado en QEMU virtio-gpu: el COMPOSITOR arranca entero (libseat→card0→GBM+EGL+
GlesRenderer→[8/8]→escritorio, acepta clientes, dibuja la ventana del greeter). El
GREETER (Vello) exige shaders float16 que llvmpipe/lavapipe NO dan ⇒ su login sólo
pinta con GPU real (nouveau+firmware, v2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

c82588ced8d61d9b48bb3544dac4f6e7b56da319 · por sergio · (hace 3 meses) · padre 4aaefa53e · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

3 archivos · +351 −0

diff --git a/docs/mirada-usb-nvidia.md b/docs/mirada-usb-nvidia.md
new file mode 100644
index 00000000..e044eaa5
--- /dev/null
+++ b/docs/mirada-usb-nvidia.md
@@ -0,0 +1,60 @@
+# Imagen USB de prueba de mirada (GPU ajena / NVIDIA)
+
+Genera una USB live que arranca el compositor **mirada** para probarlo en hardware ajeno
+(NVIDIA/AMD/lo-que-sea). Cierra la parte de *imagen* del `HANDOFF-nvidia-mirada.md`.
+
+## Armar y quemar
+
+```sh
+scripts/mirada-usb.sh                 # → work/hammer-mirada-usb.img (~147 MB)
+sudo dd if=work/hammer-mirada-usb.img of=/dev/sdX bs=4M oflag=sync status=progress
+```
+
+Bootear con **Secure Boot OFF**. Aparece el menú de systemd-boot; ENTER arranca. La tecla **`e`**
+edita la cmdline en vivo (para alternar software/hardware, ver abajo).
+
+## Qué contiene
+
+- **Kernel** `linux-generic` 7.1.2 (nouveau/i915/radeon built-in + simpledrm fallback).
+- **mirada** ya sellado en el store: `mirada-compositor` (smithay) + `mirada-greeter` (wgpu/Vello) + `mirada-ctl`.
+- **Stack gráfico por software + nouveau** (mesa 26.1.1 de Alpine, musl): **llvmpipe** (GL) + **lavapipe**
+  (Vulkan) + `nouveau_dri.so` (GL por hardware si nouveau bindea). Más `seatd`, `libinput`,
+  `libxkbcommon`, datos XKB, PAM permisivo, busybox, loader musl.
+- `/sbin/init` = lanzador: monta pseudo-fs, arranca `seatd`, corre `mirada-compositor --drm --greeter`.
+
+La receta nueva es `recipes/mesa-swrast.toml` (softpipe hammer-nativo, sin LLVM); el stack robusto de la
+imagen viene de Alpine (deuda de soberanía: falta `recipes/{llvm,mesa-llvmpipe,nvk}` desde fuente).
+
+## Estado validado (QEMU virtio-gpu, software)
+
+✅ **El compositor arranca entero**: libseat → `/dev/dri/card0` → GBM+EGL+**GlesRenderer** →
+`[8/8]` → «escritorio en marcha» → acepta clientes Wayland → dibuja la ventana del greeter.
+
+⚠️ **El greeter (login) NO termina de pintar por software.** Su renderer **Vello** exige la capacidad
+de shaders **float16** (`SHADER_FLOAT16_IN_FLOAT32`, `unpack2x16float`) que **llvmpipe y lavapipe NO
+proveen** — sólo una **GPU real**. Por eso el login sólo se pinta con hardware.
+
+## Los dos modos (cmdline)
+
+- **Por defecto (`mirada.sw nouveau.modeset=0`)** → SOFTWARE. Garantiza que el **compositor** se ve en
+  la pantalla de la NVIDIA en el primer boot, sin driver ni firmware. El greeter no completa (float16).
+- **Hardware nouveau** → en el menú, `e`, **quitá `mirada.sw nouveau.modeset=0`**. nouveau intentará
+  manejar la GPU y mesa usará `nouveau_dri.so` (GL) → el greeter con float16 real debería pintar.
+  **Requiere firmware GSP** de la GPU en `/lib/firmware/nvidia/<chip>/gsp/*.bin` (Turing/RTX en adelante).
+
+## Falta para el greeter completo en la NVIDIA (v2)
+
+1. **Modelo EXACTO de la GPU** → elegir el firmware GSP correcto (imprescindible para el modeset de
+   nouveau en Turing+) y confirmar soporte.
+2. **Firmware nouveau** en `/lib/firmware/` (pasar `FW_SRC=<dir>` al script para hornearlo).
+3. **NVK** (Vulkan nouveau) para que wgpu use Vulkan por hardware (o GL nouveau vía naga).
+
+## Validar en VM
+
+```sh
+# compositor por software (virtio-gpu); el greeter no completa (float16) — es lo esperado:
+qemu-system-x86_64 -enable-kvm -m 4096 -smp 4 \
+  -kernel store/*-linux-generic/boot/bzImage -initrd work/metal-sdboot/initramfs.cpio.gz \
+  -append "console=ttyS0,115200 mirada.sw nouveau.modeset=0 rdinit=/init" \
+  -device virtio-gpu-pci -display none -serial stdio -no-reboot
+```
diff --git a/recipes/mesa-swrast.toml b/recipes/mesa-swrast.toml
new file mode 100644
index 00000000..f3b4061c
--- /dev/null
+++ b/recipes/mesa-swrast.toml
@@ -0,0 +1,40 @@
+# Mesa 24.0.9 — SWRAST/softpipe (render por SOFTWARE, sin LLVM) para la imagen GENÉRICA de test de
+# mirada en GPU ajena (NVIDIA/AMD/lo-que-sea). Hermana de mesa.toml (iris-only, Intel): NO la
+# reemplaza — es otra imagen. Ver HANDOFF-nvidia-mirada.md y docs/14-mesa-stack.md.
+#
+# POR QUÉ SWRAST: el compositor (smithay→gbm-sys) y el greeter (wgpu, dlopen EGL/GLES/GBM) enlazan
+# contra libgbm/libEGL/libGLESv2, que son ABI-agnósticas del driver. Un mesa con `swrast` provee
+# `kms_swrast_dri.so` ⇒ render por software sobre CUALQUIER KMS device: simpledrm (framebuffer EFI,
+# anda en todo UEFI), virtio-gpu (VM de validación), o nouveau sin aceleración. Así mirada corre en
+# la NVIDIA SIN driver de GPU ni firmware — la ruta "uso normal / genérico". La aceleración real por
+# nouveau (NVK/gallium-nouveau + GSP firmware, que sí piden LLVM) queda para una v2.
+#
+# softpipe (no llvmpipe): `swrast` con `-Dllvm=disabled` compila SOFTPIPE ⇒ CERO cola LLVM (igual que
+# el plan iris-sin-LLVM). Es lento pero funcionalmente completo (GL 3.3 / GLES 3.1) — suficiente para
+# el greeter/compositor. Si wgpu resultara exigir features que softpipe no da, la v1.1 sería llvmpipe
+# reusando el LLVM 20 del frente Rust (store-rust).
+#
+# Deps C estáticas en .so ⇒ PIC (libffi/zlib/zstd/expat con -fPIC), mako = codegen Python, PYTHONPATH
+# al site-packages, y mesa-version-script-comma.patch (zig cc reordena el -Wl,--version-script) —
+# TODO idéntico a mesa.toml; ver su encabezado para el detalle de cada uno.
+name    = "mesa-swrast"
+version = "24.0.9"
+
+[source]
+tarball = "https://archive.mesa3d.org/mesa-24.0.9.tar.xz"
+sha256  = "51aa686ca4060e38711a9e8f60c8f1efaa516baf411946ed7f2c265cd582ca4c"
+patches = ["mesa-version-script-comma.patch"]
+
+[build]
+compiler    = "zig-cc"
+target      = "x86_64-linux-musl"
+link        = "dynamic"
+zig_version = "0.13.0"
+
+[build.phases]
+configure = "PYTHONPATH=/usr/lib/python3.12/site-packages meson setup output --prefix=/usr --buildtype=release -Db_ndebug=true -Dgallium-drivers=swrast -Dvulkan-drivers= -Dllvm=disabled -Dintel-clc=disabled -Dplatforms=wayland -Dglx=disabled -Dgbm=enabled -Degl=enabled -Dopengl=true -Dgles1=disabled -Dgles2=enabled -Dexpat=enabled -Dzstd=enabled -Dshader-cache=enabled -Dxmlconfig=enabled -Dglvnd=false -Dgallium-rusticl=false -Dgallium-va=disabled -Dgallium-extra-hud=false -Dvideo-codecs= -Dtools= -Dbuild-tests=false -Dvalgrind=disabled -Dlibunwind=disabled"
+compile   = "PYTHONPATH=/usr/lib/python3.12/site-packages ninja -C output"
+install   = "DESTDIR=/out ninja -C output install"
+
+[deps]
+build = ["meson", "samurai", "python3", "pkgconf", "flex", "bison", "mako", "markupsafe", "packaging", "libdrm", "wayland", "wayland-protocols", "expat", "zlib", "zstd", "libffi"]
diff --git a/scripts/mirada-usb.sh b/scripts/mirada-usb.sh
new file mode 100644
index 00000000..83b2f8cb
--- /dev/null
+++ b/scripts/mirada-usb.sh
@@ -0,0 +1,251 @@
+#!/usr/bin/env bash
+# mirada-usb.sh — arma el ROOTFS live de mirada por SOFTWARE (mesa-swrast/softpipe, sin GPU driver ni
+# firmware) para probar el compositor en GPU ajena (NVIDIA/AMD/lo-que-sea). Genérico: arranca sobre
+# simpledrm (framebuffer EFI, anda en cualquier UEFI), virtio-gpu (VM) o nouveau-sin-accel.
+#
+# Es la ruta "uso normal" del HANDOFF-nvidia-mirada.md: valida el compositor SIN depender del driver.
+# La aceleración real por nouveau (NVK/gallium-nouveau + GSP firmware) queda para una v2.
+#
+# Qué hace:
+#   1) Rootfs SLIM: loader musl + busybox + .so runtime tomados de .dev-fs/alpine (la musl userland
+#      contra la que se construyeron los binarios), NO el 1.2G entero.
+#   2) Hydrata los artefactos hammer del stack gráfico + mirada desde el store (por hash).
+#   3) Cierra el grafo DT_NEEDED: escanea los ELF, y toda .so que falte la trae de .dev-fs/alpine.
+#   4) Escribe /sbin/init = lanzador (seatd + `mirada-compositor --drm --greeter`).
+#   5) Empaqueta la imagen USB con el kernel linux-generic vía scripts/metal-usb-sdboot.sh.
+#
+# Uso:   scripts/mirada-usb.sh            # arma rootfs + imagen work/hammer-mirada-usb.img
+#        PACKAGE=0 scripts/mirada-usb.sh  # sólo el rootfs (work/mirada-rootfs), sin imagen
+# Quemar: sudo dd if=work/hammer-mirada-usb.img of=/dev/sdX bs=4M oflag=sync status=progress
+set -euo pipefail
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
+
+STORE="${STORE:-./store}"
+DEVFS="${DEVFS:-.dev-fs/alpine}"
+RFS="${RFS:-work/mirada-rootfs}"
+HAMMER="${HAMMER:-target/release/hammer}"
+PACKAGE="${PACKAGE:-1}"
+
+[ -d "$DEVFS" ]   || { echo "no existe $DEVFS (corré scripts/bootstrap-devfs.sh)" >&2; exit 1; }
+[ -x "$HAMMER" ]  || { echo "no existe $HAMMER (cargo build --release)" >&2; exit 1; }
+
+# --- artefactos del store a hydratar (hash explícito para los recién construidos; resto por nombre) ---
+declare -A PIN=(
+  [mesa-swrast]=410e4fb9806598921048f492508725f55ee8475a1e9ef8ea2dc64f357154a0d9
+  [libxkbcommon]=cef7db4a82882ee942fef5e758344da67c9f3fce6b221610a7db9cb05bfb3b53
+)
+# Orden: libs primero, binarios mirada al final. Todos aportan .so o /usr/bin.
+PKGS="mesa-swrast wayland libdrm seatd libinput libudev-zero libxkbcommon pixman linux-pam mtdev libevdev mirada-compositor mirada-greeter mirada-ctl"
+
+resolve() { # nombre -> hash del store (pin si existe, si no el más reciente por mtime)
+  local n="$1"
+  if [ -n "${PIN[$n]:-}" ]; then echo "${PIN[$n]}"; return; fi
+  local d; d=$(ls -dt "$STORE"/*-"$n" 2>/dev/null | grep -E "\-$n\$" | head -1) || true
+  [ -n "$d" ] || { echo "" ; return; }
+  basename "$d" | sed "s/-$n\$//"
+}
+
+echo "==> rootfs slim en $RFS"
+rm -rf "$RFS"; mkdir -p "$RFS"/{bin,sbin,usr/bin,usr/sbin,usr/lib,lib,etc,dev,proc,sys,run,tmp,root}
+mkdir -p "$RFS"/usr/lib/dri "$RFS"/var/lib/hammer
+
+# 1) loader musl + libc.so + .so runtime base desde el dev-fs
+echo "==> base musl + runtime .so desde $DEVFS"
+cp -aL "$DEVFS"/lib/ld-musl-x86_64.so.1 "$RFS"/lib/ 2>/dev/null || true
+for so in libc.so libz.so.1 libzstd.so.1 libexpat.so.1 libffi.so.8 libstdc++.so.6 libgcc_s.so.1 libbz2.so.1 liblzma.so.5; do
+  f=$(find "$DEVFS" -name "$so" 2>/dev/null | head -1)
+  # -aL: DESREFERENCIA symlinks (libz.so.1→libz.so.1.3.2) ⇒ archivo real con el SONAME que pide NEEDED.
+  [ -n "$f" ] && cp -aL "$f" "$RFS"/usr/lib/ || true
+done
+
+# 2) busybox + applets
+echo "==> busybox"
+cp -a "$DEVFS"/bin/busybox "$RFS"/bin/busybox
+for a in sh mount umount mkdir ln cp mv rm cat echo sleep ls ps kill chmod chown ip udhcpc dmesg mknod switch_root getty login setsid; do
+  ln -sf busybox "$RFS/bin/$a"
+done
+ln -sf /bin/busybox "$RFS/sbin/init.busybox"
+
+# 3) hydratar el stack gráfico + mirada desde el store
+echo "==> hydratando artefactos hammer"
+for p in $PKGS; do
+  h=$(resolve "$p")
+  [ -n "$h" ] || { echo "  ✗ $p — no está en el store" >&2; exit 1; }
+  "$HAMMER" --store "$STORE" hydrate "$h" --into "$RFS" --link static >/dev/null 2>&1
+  printf '  ✓ %-18s %s\n' "$p" "${h:0:12}"
+done
+
+# 4) cierre DT_NEEDED: toda .so requerida y ausente, traerla del dev-fs (desreferenciada).
+#    Varias rondas fijas (una .so nueva puede arrastrar NEEDED propios). Todo guardado del set -e.
+echo "==> cerrando el grafo DT_NEEDED (faltantes ← $DEVFS)"
+libdirs=("$RFS/usr/lib" "$RFS/lib" "$RFS/lib64" "$RFS/usr/lib/dri")
+have_so() { local s="$1" d; for d in "${libdirs[@]}"; do [ -e "$d/$s" ] && return 0; done; return 1; }
+list_elf() { find "$RFS" -type f 2>/dev/null | while read -r f; do head -c4 "$f" 2>/dev/null | grep -q ELF && echo "$f"; done; }
+needed_sonames() { local e; list_elf | while read -r e; do readelf -d "$e" 2>/dev/null | awk -F'[][]' '/NEEDED/{print $2}'; done | sort -u; }
+for round in 1 2 3; do
+  falto=0
+  for so in $(needed_sonames); do
+    [ -n "$so" ] || continue
+    have_so "$so" && continue
+    f=$(find "$DEVFS" -name "$so" 2>/dev/null | head -1 || true)
+    if [ -n "$f" ]; then cp -aL "$f" "$RFS/usr/lib/" && echo "  + $so"; else echo "  ! FALTA (no en dev-fs): $so"; falto=1; fi
+  done
+  [ "$falto" -eq 0 ] && break
+done
+: <<'DEADCODE'
+DEADCODE
+
+# 5) config mínima: passwd/group/pam + ld.so path
+echo "==> config (passwd/group/pam)"
+cat > "$RFS/etc/passwd" <<'EOF'
+root:x:0:0:root:/root:/bin/sh
+EOF
+cat > "$RFS/etc/group" <<'EOF'
+root:x:0:
+video:x:27:root
+input:x:28:root
+seat:x:29:root
+EOF
+mkdir -p "$RFS/etc/pam.d"
+# PAM permisivo: el greeter RENDERIZA antes de autenticar; con esto un login vacío/root pasa.
+cat > "$RFS/etc/pam.d/other" <<'EOF'
+auth     sufficient pam_permit.so
+account  sufficient pam_permit.so
+password sufficient pam_permit.so
+session  sufficient pam_permit.so
+EOF
+for svc in login mirada mirada-greeter system-auth; do cp "$RFS/etc/pam.d/other" "$RFS/etc/pam.d/$svc"; done
+# ld musl busca en /usr/lib y /lib por defecto; dejamos un ld-musl path explícito por las dudas.
+mkdir -p "$RFS/etc"
+# musl REEMPLAZA su path por defecto con este fichero ⇒ hay que listar TODOS los dirs, incl. /lib64
+# (linux-pam instala ahí) y /usr/lib/dri (drivers mesa).
+printf '/usr/lib\n/lib\n/lib64\n/usr/lib/dri\n' > "$RFS/etc/ld-musl-x86_64.path"
+
+# 5b) DATOS XKB (xkeyboard-config): libxkbcommon compila el keymap por defecto (evdev/pc105/us) desde
+#     /usr/share/X11/xkb; sin estos datos xkb_keymap_new falla y el compositor SEGFAULTEA en
+#     libxkbcommon. Son datos de texto/XML AGNÓSTICOS de arquitectura ⇒ los tomamos del host.
+#     (TODO soberanía: recipes/xkeyboard-config.toml — hoy no existe; para la imagen de test basta.)
+XKB_SRC="${XKB_SRC:-/usr/share/X11/xkb}"
+if [ -d "$XKB_SRC" ]; then
+  echo "==> datos XKB (xkeyboard-config) ← $XKB_SRC"
+  mkdir -p "$RFS/usr/share/X11"
+  cp -aL "$XKB_SRC" "$RFS/usr/share/X11/xkb"
+else
+  echo "  ⚠ no encuentro datos XKB en $XKB_SRC — el compositor crasheará en libxkbcommon" >&2
+fi
+
+# 5c) STACK GL/VULKAN por SOFTWARE + nouveau — de Alpine edge (musl, mismo libLLVM que el dev-fs).
+#   Por qué NO la mesa-swrast de hammer: su softpipe basta para el COMPOSITOR, pero el greeter (wgpu)
+#   necesita un GL/Vulkan más completo. La mesa 26.1.1 de Alpine trae:
+#     · llvmpipe (swrast_dri.so + libgallium + libLLVM.so.22) → software GL robusto (compositor),
+#     · lavapipe (libvulkan_lvp.so + ICD) → software Vulkan (backend primario de wgpu),
+#     · nouveau_dri.so → GL por HARDWARE en la NVIDIA (si nouveau bindea con firmware GSP).
+#   Es la vía pragmática de la imagen de TEST (deuda de soberanía: build LLVM+mesa+NVK desde fuente).
+#   El greeter/Vello igual EXIGE shaders float16 ⇒ su login sólo pinta con GPU real (ver init).
+MESAROOT="${MESAROOT:-work/alpine-mesa-root}"
+if [ ! -e "$MESAROOT/usr/lib/libgallium-26.1.1.so" ]; then
+  echo "==> bajando mesa+lavapipe de Alpine edge (apk) → $MESAROOT"
+  APK="$DEVFS/sbin/apk"; ALD="$DEVFS/lib/ld-musl-x86_64.so.1"
+  rm -rf "$MESAROOT"; mkdir -p "$MESAROOT"
+  "$ALD" --library-path "$DEVFS/usr/lib:$DEVFS/lib" "$APK" add --root "$PWD/$MESAROOT" --initdb --usermode --no-cache \
+    --repository https://dl-cdn.alpinelinux.org/alpine/edge/main \
+    --repository https://dl-cdn.alpinelinux.org/alpine/edge/community \
+    --arch x86_64 --allow-untrusted \
+    mesa-dri-gallium mesa-egl mesa-gles mesa-gbm mesa-vulkan-swrast vulkan-loader >/dev/null 2>&1 \
+    || { echo "  ✗ apk falló (¿red?); no puedo bajar mesa" >&2; exit 1; }
+fi
+echo "==> overlay mesa+lavapipe en el rootfs"
+cp -aL "$MESAROOT"/usr/lib/*.so* "$RFS/usr/lib/" 2>/dev/null || true
+for sub in dri gbm; do
+  [ -d "$MESAROOT/usr/lib/$sub" ] && { mkdir -p "$RFS/usr/lib/$sub"; cp -aL "$MESAROOT/usr/lib/$sub/"*.so "$RFS/usr/lib/$sub/" 2>/dev/null || true; }
+done
+mkdir -p "$RFS/usr/share/vulkan/icd.d"
+cp -aL "$MESAROOT"/usr/share/vulkan/icd.d/*.json "$RFS/usr/share/vulkan/icd.d/" 2>/dev/null || true
+[ -d "$MESAROOT/usr/share/drirc.d" ] && cp -aL "$MESAROOT/usr/share/drirc.d" "$RFS/usr/share/" 2>/dev/null || true
+rm -f "$RFS/usr/lib/libgcc_s.so"   # symlink glibc espurio (el musl .so.1 sí está)
+# cerrar deps de los drivers/ICD nuevos ← alpine-root + dev-fs
+for round in 1 2 3; do
+  for so in $(needed_sonames); do
+    [ -n "$so" ] || continue
+    have_so "$so" && continue
+    f=$(find "$MESAROOT/usr/lib" "$DEVFS" -name "$so" 2>/dev/null | grep -v ld-linux | head -1 || true)
+    [ -n "$f" ] && cp -aL "$f" "$RFS/usr/lib/" && echo "  + $so" || true
+  done
+done
+
+# 6) /sbin/init = lanzador de mirada (software)
+echo "==> lanzador /sbin/init"
+cat > "$RFS/sbin/init" <<'INIT'
+#!/bin/sh
+# init de la imagen live de mirada (software/swrast). Arranca seatd y el compositor en modo greeter.
+export PATH=/usr/bin:/bin:/usr/sbin:/sbin
+/bin/busybox mount -t proc     proc /proc     2>/dev/null || true
+/bin/busybox mount -t sysfs    sys  /sys      2>/dev/null || true
+/bin/busybox mount -t devtmpfs dev  /dev      2>/dev/null || true
+/bin/busybox mount -t tmpfs    tmpfs /run     2>/dev/null || true
+/bin/busybox mkdir -p /run/user/0 /dev/pts /dev/shm /run/seatd
+/bin/busybox mount -t devpts devpts /dev/pts  2>/dev/null || true
+/bin/busybox chmod 0700 /run/user/0
+
+export XDG_RUNTIME_DIR=/run/user/0
+export LIBGL_DRIVERS_PATH=/usr/lib/dri
+export LIBSEAT_BACKEND=seatd
+export XDG_SEAT=seat0
+export XDG_SESSION_TYPE=wayland
+export MIRADA_GREETER_BIN=/usr/bin/mirada-greeter
+export XKB_DEFAULT_LAYOUT=us
+export XKB_CONFIG_ROOT=/usr/share/X11/xkb
+
+# GPU: por defecto se INTENTA hardware (nouveau, si el firmware GSP lo deja hacer modeset) y, si no,
+# mesa cae a swrast/llvmpipe sobre simpledrm solo. Con `mirada.sw` en la cmdline se FUERZA software
+# (necesario en la VM virtio-sin-virgl de validación, donde mesa intentaría el driver virtio-3D y
+# fallaría). El compositor (smithay/GlesRenderer) corre en ambos; el greeter (wgpu/Vello) NECESITA la
+# capacidad de shaders float16 (SHADER_FLOAT16_IN_FLOAT32) que los rasterizadores software
+# (llvmpipe/lavapipe) NO dan ⇒ su login sólo pinta con GPU real (nouveau). Ver HANDOFF/README.
+if grep -qw 'mirada.sw' /proc/cmdline 2>/dev/null; then
+  echo "  ## modo SOFTWARE forzado (mirada.sw): compositor sí; greeter no (sin float16)"
+  export LIBGL_ALWAYS_SOFTWARE=1
+  export GALLIUM_DRIVER=llvmpipe
+  export VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json
+  export VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json
+fi
+# En modo hardware el loader Vulkan escanea /usr/share/vulkan/icd.d/ y elige el mejor ICD (nouveau NVK
+# si está, si no lavapipe); no fijamos VK_ICD_FILENAMES para no tapar el hardware.
+
+echo ""
+echo "  ##  hammer · mirada live"
+echo "  ##  DRM: $(ls /dev/dri 2>/dev/null | tr '\n' ' ')"
+echo ""
+
+# seatd en background (gestiona el DRM master y la sesión sobre la VT).
+/usr/bin/seatd -g video >/run/seatd.log 2>&1 &
+sleep 1
+
+echo "== lanzando mirada-compositor --drm --greeter =="
+/usr/bin/mirada-compositor --drm --greeter 2>&1 | /bin/busybox tee /run/mirada.log
+
+echo ""
+echo "== mirada terminó — shell de rescate (logs en /run/mirada.log, /run/seatd.log) =="
+exec /bin/sh
+INIT
+chmod +x "$RFS/sbin/init"
+# NO creamos /init: metal-usb-sdboot.sh escribe su propio /init (monta pseudofs y hace exec /sbin/init).
+
+echo "==> rootfs listo: $RFS  ($(du -sh "$RFS" | cut -f1))"
+
+# 7) empaquetar imagen USB con el kernel linux-generic
+if [ "$PACKAGE" = "1" ]; then
+  KGEN=$(ls -dt "$STORE"/*-linux-generic/boot/bzImage 2>/dev/null | head -1)
+  [ -n "$KGEN" ] || { echo "no encuentro el kernel linux-generic (build recipes/linux-generic.toml)" >&2; exit 1; }
+  echo "==> empaquetando USB (kernel linux-generic: $KGEN)"
+  # Cmdline por defecto = SOFTWARE (mirada.sw) + simpledrm (nouveau.modeset=0): garantiza que el
+  # COMPOSITOR arranca y se VE mirada en la pantalla de la NVIDIA en el primer boot, sin depender del
+  # driver ni del firmware. Para el greeter completo (login) hace falta GPU real: en el menú de
+  # systemd-boot, tecla 'e', quitá `mirada.sw nouveau.modeset=0` para que nouveau intente el hardware
+  # (requiere firmware GSP en /lib/firmware — ver FW_SRC).
+  ROOTFS="$RFS" KERNEL="$KGEN" IMG="work/hammer-mirada-usb.img" \
+    OPTIONS="${CMDLINE:-mirada.sw nouveau.modeset=0 console=tty0 loglevel=4}" \
+    scripts/metal-usb-sdboot.sh
+  echo ""
+  echo "✓ imagen: work/hammer-mirada-usb.img"
+fi