60553bec44
memoriosa (Fase 7c) demostró que un app podía persistir su huella. Esta fase la lleva al gesto natural: un editor de texto. Tecleas, reinicias renaser, el texto sigue ahí. La huella vive en el grafo de objetos como todo lo demás. - Nuevo crate `apps/bitacora/`: lienzo 480×280, tipografía 8×8 embebida (`font8x8 = "0.3"`) escalada x2 a 16×16, render pixel a pixel desde la memoria del propio app. Buffer 512 bytes con wrap automático a 28 columnas; `Enter` salta línea, Backspace borra; al desbordar el buffer se descartan los 64 primeros para amortizar la mudanza. Cada cambio invoca `sys_estado_guardar`; al arrancar, `init` llama a `sys_estado_cargar` y reconstruye. - Mapeo de scancodes US a ASCII (letras, dígitos, puntuación básica, espacio). Sin shift ni mayúsculas — minimalismo. - `GENESIS` crece de 7 a 8 apps; `bitacora` es la PRIMERA — gana la celda maestra al arrancar y te invita a teclear. - `CELDA_TASKBAR_ANCHO` baja de 150 a 130 px para que las ocho pestañas + lanzador + reloj quepan holgadas en 1280 px. Verificado en QEMU: tras escribir "hola renaser" y reiniciar el kernel con el mismo disk.img, bitácora muestra el texto donde lo dejó. El `almacen` reporta 24 objetos en el grafo (frente a 9 antes de escribir) y `raiz presente` — cada `guardar` anexó una versión al log direccionado por contenido. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1122 lines
60 KiB
Markdown
1122 lines
60 KiB
Markdown
# Registro de cambios — renaser
|
||
|
||
Registro técnico detallado, fase a fase, en orden cronológico. Formato
|
||
inspirado en *Keep a Changelog*. Para la crónica en lenguaje llano, ver
|
||
`DIARIO.md`; para el estado y los planes, `ROADMAP.md`.
|
||
|
||
---
|
||
|
||
## Fase 1 — El primer microsegundo — 2026-05-21
|
||
|
||
### Infraestructura
|
||
- Espacio de trabajo Cargo, `kernel/Cargo.toml`, `.cargo/config.toml`,
|
||
`rust-toolchain.toml`.
|
||
- Target inicial: especificación JSON propia `x86_64-renaser.json` — PIC,
|
||
soft-float, sin SSE, enlazador `lld`.
|
||
|
||
### Añadido
|
||
- `kernel/src/main.rs`: punto de entrada con el macro `entry_point!` de
|
||
`bootloader_api` 0.11.
|
||
- Adopción y verificación con seguridad de tipos del framebuffer GOP (ancho,
|
||
alto, formato de píxel, *stride*).
|
||
- Lienzo intermedio estático de 8 MiB en `.bss`, alineado a página: la técnica
|
||
de doble búfer para evitar parpadeos.
|
||
- `#[panic_handler]` que dibuja una franja roja directamente sobre el
|
||
framebuffer mediante escrituras volátiles.
|
||
- Dependencias: `bootloader_api` 0.11, `x86_64` 0.15, `embedded-graphics` 0.8.
|
||
|
||
### Notas
|
||
- Corrección clave: `bootloader` (constructor de imagen, lado anfitrión) no es
|
||
`bootloader_api` (la API `no_std` que consume el kernel).
|
||
|
||
---
|
||
|
||
## Fase 1.5 — Empaquetado y arranque — 2026-05-21
|
||
|
||
### Cambiado
|
||
- Migración del target del kernel: de la JSON propia `x86_64-renaser.json` al
|
||
target nativo precompilado `x86_64-unknown-none`. Elimina `build-std` y la
|
||
burocracia de compilador asociada.
|
||
|
||
### Añadido
|
||
- Miembro `boot/`: orquestador de anfitrión. Dependencia de artefacto
|
||
(RFC 3028) sobre el kernel; construcción de la imagen de disco UEFI con
|
||
`bootloader::UefiBoot`; lanzador de QEMU.
|
||
|
||
### Toolchain
|
||
- Instalación de la toolchain nightly (la máquina solo tenía la estable).
|
||
|
||
### Corregido
|
||
- Los nightly recientes exigen `-Zjson-target-spec` para usar specs JSON
|
||
(resuelto al migrar al target nativo).
|
||
- Sintaxis del acelerador de QEMU: `-machine q35,accel=kvm:tcg`.
|
||
|
||
### Verificado
|
||
- Arranque en QEMU: superficie índigo limpia a 1280×800.
|
||
- Baliza de pánico (franja roja) confirmada inyectando un pánico de prueba.
|
||
|
||
---
|
||
|
||
## Fase 2.0 — Cimientos del manejo de fallos — 2026-05-21
|
||
|
||
### Añadido
|
||
- `kernel/src/gdt.rs`: GDT propia, TSS y un stack de emergencia (IST)
|
||
reservado para el manejador de doble fallo.
|
||
- `kernel/src/interrupts.rs`: IDT con manejadores de excepción de CPU. El
|
||
breakpoint (#BP) es recuperable; #UD, #DE, #GP, #PF y #DF son fatales y
|
||
encienden la baliza.
|
||
- `#![feature(abi_x86_interrupt)]`.
|
||
|
||
### Corregido
|
||
- La GDT debe recargar los registros SS/DS/ES con un segmento de datos del
|
||
kernel: el cargador deja `SS = 0x10`, valor que en la GDT nueva pasa a ser el
|
||
descriptor del TSS; el primer `iretq` de una rutina de excepción provocaba un
|
||
#GP. Diagnosticado con la traza `-d int` de QEMU.
|
||
|
||
### Verificado
|
||
- Excepción `int3` atrapada y superada; línea-latido teal dibujada como prueba.
|
||
|
||
---
|
||
|
||
## Fase 2.1 — Interrupciones de hardware — 2026-05-21
|
||
|
||
### Añadido
|
||
- `kernel/src/pic.rs`: remapeo del par 8259 (PIC) fuera del rango de las
|
||
excepciones (vectores 0x20+); programación del temporizador PIT a 100 Hz;
|
||
IRQ1 de teclado.
|
||
- Bucle de render en `kernel_main`, despertado por el temporizador, en
|
||
sustitución del `hlt` estático.
|
||
|
||
### Verificado
|
||
- La línea-latido se anima (temporizador en marcha); las pulsaciones de teclado
|
||
inyectadas cambian el cuadro-eco (teclado en marcha).
|
||
|
||
---
|
||
|
||
## Fase 3 — Memoria dinámica y reactor asíncrono — 2026-05-21
|
||
|
||
### Añadido
|
||
- `kernel/src/memory/`: `linked_list_allocator` como `#[global_allocator]`
|
||
sobre una región estática en `.bss`.
|
||
- `#[alloc_error_handler]`: franja naranja de agotamiento de memoria.
|
||
`#![feature(alloc_error_handler)]`.
|
||
- `kernel/src/async_system/`: reactor cooperativo — `Executor`, `Task`/`TaskId`,
|
||
`Waker` (vía `alloc::task::Wake`) y `ScancodeStream`.
|
||
- `kernel/src/texto.rs`: rasterización de tipografía vectorial con `fontdue`;
|
||
TTF Adwaita Mono empotrada con `include_bytes!`.
|
||
- Dependencias: `linked_list_allocator` 0.10, `spin` 0.9, `crossbeam-queue`
|
||
0.3, `futures-util` 0.3, `fontdue` 0.9.
|
||
|
||
### Cambiado
|
||
- Heap ampliado de 16 MiB a 64 MiB: `fontdue`, al analizar una tipografía real,
|
||
agotaba los 16 MiB iniciales. El `#[alloc_error_handler]` lo delató pintando
|
||
la franja naranja — el guardarraíl funcionó en silicio real.
|
||
|
||
### Corregido
|
||
- El modo `no_std` de `fontdue` está condicionado a su feature `hashbrown`; sin
|
||
ella recae en `std`. Se fija `default-features = false, features = ["hashbrown"]`.
|
||
|
||
### Verificado
|
||
- Rótulo de bienvenida rasterizado al arranque; el texto tecleado aparece en vivo.
|
||
|
||
---
|
||
|
||
## Fase 4 — El escudo de aislamiento WASM — 2026-05-21
|
||
|
||
### Añadido
|
||
- `kernel/src/wasm/`: intérprete `wasmi` 1.0.9 en modo `no_std`. `env.rs`
|
||
define la matriz de capacidades — exactamente dos funciones de host:
|
||
`sys_render_frame(ptr, len)`, con validación infranqueable de los límites de
|
||
la memoria lineal, y `sys_get_scancode()`.
|
||
- `apps/hello_wasm/`: primera aplicación del userspace — módulo
|
||
`wasm32-unknown-unknown` (cdylib), un cuadrado móvil dirigido por teclado. Se
|
||
empotra en el kernel con `include_bytes!`.
|
||
- Dependencia: `wasmi` 1.0.
|
||
|
||
### Cambiado
|
||
- El kernel se EXCLUYE del espacio de trabajo (`exclude = ["kernel", "apps"]`):
|
||
al añadir `wasmi`, `cargo build` intentaba compilar el kernel para el
|
||
anfitrión y fallaba por un símbolo `_start` duplicado. El kernel pasa a
|
||
construirse únicamente como dependencia de artefacto de `boot`.
|
||
|
||
### Corregido
|
||
- `wasmi` `no_std` necesita `default-features = false, features = ["hash-collections"]`.
|
||
- `wasmi` 1.0.9 usa `Linker::instantiate_and_start` (no existe `instantiate`).
|
||
|
||
### Verificado
|
||
- La aplicación WASM pinta su propia superficie y responde a las pulsaciones de
|
||
teclado inyectadas, sin más vía hacia el kernel que las dos capacidades.
|
||
|
||
---
|
||
|
||
## Mantenimiento — Estructura, documentación e integración — 2026-05-21
|
||
|
||
### Cambiado
|
||
- Refactorización: `main.rs` dividido de 692 a ~155 líneas. Se extraen los
|
||
módulos `sync.rs`, `grafico.rs`, `consola.rs` y `baliza.rs`. Sin cambios de
|
||
comportamiento (verificado en QEMU).
|
||
|
||
### Añadido
|
||
- Documentación: `CLAUDE.md`, `README.md`, `ARCHITECTURE.md`, `ROADMAP.md`,
|
||
`CHANGELOG.md` y `DIARIO.md`.
|
||
- Integración con git: repositorio inicializado, remoto `origin` en Gitea,
|
||
`.gitignore`.
|
||
|
||
### Seguridad
|
||
- `renaser.txt` —un borrador de trabajo que contenía una credencial— se subió
|
||
por error en el commit inicial. Se purgó de todo el historial con
|
||
`git filter-branch` y se reescribió el remoto con `push --force`. La
|
||
credencial expuesta debe rotarse.
|
||
|
||
---
|
||
|
||
## Fase 5 — Multitarea cooperativa, guardarrail de fuel y reloj — 2026-05-22
|
||
|
||
Unificación de la Fase 3 (reactor) y la Fase 4 (WASM): el userspace deja de ser
|
||
una sola app que monopoliza la CPU y pasa a ser un conjunto de aplicaciones
|
||
cooperativas, aisladas también en el TIEMPO.
|
||
|
||
### Añadido
|
||
- `kernel/src/async_system/reloj.rs`: convierte la IRQ0 (PIT, 100 Hz) en una
|
||
primitiva asíncrona. `CONTADOR_PULSOS` (`AtomicU64`) y `EsperaFrame`, un
|
||
`Future` que se resuelve en el siguiente pulso — la unidad de cesión
|
||
cooperativa del userspace. Censo de wakers tras `Mutex`, drenado por la IRQ0.
|
||
- `kernel/src/wasm`: ABI de fotograma. `AplicacionWasm` —instancia PERSISTENTE
|
||
entre fotogramas (`Store` + `TypedFunc<(), ()>` + región)— sustituye al
|
||
`wasm::ejecutar` de fuego-y-olvido. El módulo del userspace exporta ahora
|
||
`init()` (una vez) y `tick()` (un fotograma, y retorna).
|
||
- Escudo de combustible: `Engine` con `Config::consume_fuel(true)` y
|
||
`CompilationMode::Eager` (el `fuel` mide solo ejecución). Presupuesto recargado
|
||
antes de cada `tick` — `FUEL_ARRANQUE` (20 M) y `FUEL_FOTOGRAMA` (2 M).
|
||
- `kernel/src/wasm/env.rs`: capacidad `sys_render_frame` con **regiones de
|
||
dibujo** — cada fotograma se compone desplazado por el `(offset_x, offset_y)`
|
||
de la app; un tamaño ajeno a la región se rechaza. Canal de teclado **por
|
||
aplicación**: la IRQ1 difunde cada scancode a TODOS los canales, de modo que
|
||
varias apps reciben la entrada en paralelo sin robársela.
|
||
- `kernel/src/grafico.rs`: `RegionPantalla` y `Color::DESALOJO` (púrpura).
|
||
- `apps/discola/`: aplicación WASM construida para portarse mal — su `tick` es
|
||
un bucle cerrado. Demuestra el guardarrail de fuel en vivo.
|
||
|
||
### Cambiado
|
||
- `apps/hello_wasm`: migrada al ABI `init`/`tick`; su lienzo se dimensiona a la
|
||
región (480×560). El estado del cuadrado persiste en su memoria lineal.
|
||
- `interrupts.rs`: la IRQ0 avanza el `reloj`; la IRQ1 difunde a los canales.
|
||
- `consola.rs`: `volcar_marco` compone en una sub-región; `pintar_desalojo`
|
||
tatúa la baliza púrpura.
|
||
- Contención de fallos: el subsistema WASM ya no usa `.expect()`. Toda falla
|
||
—carga, instanciación, desbordamiento, agotamiento de fuel— se devuelve como
|
||
`Result`; la tarea desaloja la app (baliza púrpura) y el kernel sigue vivo.
|
||
|
||
### Verificado
|
||
- QEMU: tres apps concurrentes. Dos instancias de `hello_wasm` (mismo bytecode,
|
||
regiones izquierda y derecha) renderizan y responden a W/A/S/D **en paralelo**,
|
||
con movimiento idéntico. La app díscola es desalojada en su primer fotograma
|
||
por agotamiento de combustible: su región queda púrpura y el sistema —kernel y
|
||
apps vecinas— no sufre un solo sobresalto.
|
||
|
||
---
|
||
|
||
## Fase 6.0 — Cuotas de memoria y ciclo de vida del userspace — 2026-05-22
|
||
|
||
El aislamiento de las aplicaciones, que la Fase 5 hizo temporal (combustible),
|
||
se completa ahora en la dimensión espacial (memoria) y se cierra la fuga de
|
||
recursos del ciclo de vida.
|
||
|
||
### Añadido
|
||
- Techo de memoria lineal por aplicación: `4 MiB`. `wasm/mod.rs` construye un
|
||
`StoreLimits` (`StoreLimitsBuilder::memory_size` + `trap_on_grow_failure`) y lo
|
||
liga al `Store` con `Store::limiter`. Un `memory.grow` que rebase la cuota se
|
||
convierte en trampa; el kernel la captura y desaloja la app.
|
||
- `FallaApp::SinMemoria` y `Color::DESALOJO_MEMORIA` (amarillo pálido). El
|
||
desalojo distingue la causa por color: púrpura (tiempo/aborto), amarillo
|
||
(memoria). La clasificación es robusta — `Error::as_trap_code()` da un código
|
||
público y unívoco: `TrapCode::GrowthOperationLimited`.
|
||
- `Drop` para `AplicacionWasm`: al morir una app desalojada, su canal de teclado
|
||
se da de baja del censo de difusión de la IRQ1 — cierra la fuga señalada en la
|
||
Fase 5. `async_system::teclado` se reorganiza en `crear_canal` /
|
||
`registrar_canal` / `cerrar_canal`; el canal se inscribe al FINAL de la carga,
|
||
de modo que una carga fallida no deja canales huérfanos.
|
||
- `apps/glotona/`: aplicación WASM construida para devorar memoria — su `tick`
|
||
invoca `memory.grow` sin freno. Demuestra el guardarrail espacial en vivo.
|
||
|
||
### Verificado
|
||
- QEMU: cuatro apps concurrentes. Las dos `hello_wasm` renderizan y responden al
|
||
teclado en paralelo; la app díscola es desalojada por combustible (franja
|
||
púrpura) y la app glotona por cuota de memoria (franja amarilla), ambas en su
|
||
primer fotograma, sin que el kernel ni las apps honradas se inmuten.
|
||
|
||
---
|
||
|
||
## Fase 6.1a — Sonda PCI y disco de pruebas — 2026-05-22
|
||
|
||
Primer paso de la estrategia incremental hacia el almacenamiento: derribar el
|
||
muro del descubrimiento de hardware antes de diseñar nada encima.
|
||
|
||
### Añadido
|
||
- `kernel/src/drivers/`: nuevo subsistema de drivers. `pci.rs` enumera el bus
|
||
PCI por fuerza bruta mediante el mecanismo de configuración #1 —puertos de E/S
|
||
`0xCF8` (dirección) y `0xCFC` (datos)—; recorre buses y dispositivos leyendo
|
||
el Vendor/Device ID, y localiza el disco virtio-blk (vendor `0x1AF4`, device
|
||
`0x1001`/`0x1042`), devolviendo su ubicación y sus seis BARs.
|
||
- `kernel_main`: tras fundar la consola, sondea el bus PCI y deja constancia
|
||
visual del hallazgo.
|
||
|
||
### Cambiado
|
||
- `boot/src/main.rs`: forja un disco de pruebas `target/disk.img` (fichero
|
||
disperso de 32 MiB; se respeta si ya existe) y lo adjunta a QEMU como
|
||
`virtio-blk-pci`. **Corrección de plataforma:** la máquina `q35` es x86_64 y
|
||
exige la transmisión PCI; `virtio-blk-device` (su gemelo MMIO) es de ARM.
|
||
|
||
### Verificado
|
||
- QEMU: el kernel enumera el bus y reporta en pantalla
|
||
`virtio-blk en bus 0 dev 3 :: BAR0 E/S 0x6000` — un dispositivo virtio-blk
|
||
transicional (device `0x1001`), con su BAR0 en espacio de E/S. El muro del
|
||
descubrimiento PCI queda derribado; las cuatro apps del userspace siguen
|
||
operando sin alteración.
|
||
|
||
---
|
||
|
||
## Fase 6.1b — HAL, DMA y lectura del sector 0 — 2026-05-22
|
||
|
||
Segundo paso del sustrato de almacenamiento: el diálogo real con el disco. El
|
||
kernel monta el dispositivo virtio-blk y lee su primer sector por DMA.
|
||
|
||
### Añadido
|
||
- Dependencia `virtio-drivers` 1.13 (`no_std`, `default-features = false`,
|
||
feature `alloc`).
|
||
- `kernel/src/drivers/disco.rs`:
|
||
- **Asignador de marcos** «bump» — reparte páginas físicas de 4 KiB para el
|
||
DMA, tomadas de la mayor región de RAM libre que el cargador reporta. No
|
||
libera: suficiente para una sonda (la gestión fina llegará con el grafo).
|
||
- **`KernelHal`** — implementa el `trait Hal` de `virtio-drivers`. `dma_alloc`
|
||
entrega marcos físicos a cero; `mmio_phys_to_virt` traduce los BARs (el
|
||
cargador mapea ≥ 4 GiB de memoria física, que cubre todo MMIO de PCI);
|
||
`share`/`unshare` usan un buffer rebote para que cualquier región del kernel
|
||
pueda viajar al dispositivo.
|
||
- **`montar_y_leer_sector0`** — enumera el bus, habilita E/S + memoria +
|
||
bus-master, monta el `PciTransport` y el `VirtIOBlk`, y lee el sector 0 por
|
||
**sondeo** del *used ring* (sin depender aún de interrupciones).
|
||
- `kernel/src/drivers/pci.rs`: reescrito como `CamPuertos`, la implementación de
|
||
`ConfigurationAccess` de `virtio-drivers` sobre los puertos `0xCF8`/`0xCFC`.
|
||
- `kernel_main` captura `physical_memory_offset` y la región de RAM de
|
||
`BootInfo`, funda el subsistema de disco y reporta el resultado de la sonda.
|
||
|
||
### Cambiado
|
||
- `boot/src/main.rs`: al forjar el disco de pruebas graba una firma
|
||
(`renaser-6.1b`) en su sector 0 — el testigo del viaje de ida y vuelta.
|
||
|
||
### Verificado
|
||
- QEMU: el kernel reporta `virtio-blk :: bus 0 dev 3 :: 65536 sectores ::
|
||
s0=renaser-6.1b`. La firma grabada por el anfitrión se lee de vuelta intacta:
|
||
descubrimiento PCI, transporte, DMA y transferencia funcionan de punta a
|
||
punta. Las cuatro apps del userspace siguen operando sin alteración.
|
||
|
||
---
|
||
|
||
## Fase 6.1c — El grafo de objetos direccionado por contenido — 2026-05-22
|
||
|
||
Tercer y último paso del sustrato de almacenamiento. El disco deja de ser un
|
||
dispositivo que se sondea y pasa a ser una MEMORIA QUE PERDURA: un grafo
|
||
dirigido acíclico de objetos direccionados por contenido — no un sistema de
|
||
archivos plano POSIX.
|
||
|
||
### Añadido
|
||
- `kernel/src/almacen.rs` — el grafo de objetos:
|
||
- **Objeto** — una carga útil de bytes (`datos`) y una lista de aristas
|
||
(`hijos`: hashes de otros objetos). Las aristas hacen del almacén un DAG.
|
||
- **Direccionamiento por contenido** — la identidad de un objeto es el hash
|
||
BLAKE3 de su forma serializada. De ello se siguen dos propiedades que un
|
||
sistema de archivos jamás regala: INTEGRIDAD (el contenido leído se
|
||
rehashea y se verifica contra el hash pedido) y DEDUPLICACIÓN (contenido
|
||
idéntico produce el mismo hash; se almacena una sola vez).
|
||
- **Disco como log** — el sector 0 es el superbloque (magia `RENASGRF`,
|
||
versión, cursor del log y hash de la raíz); tras él se anexan los registros
|
||
de objetos, `[longitud u32 LE][payload postcard][relleno a cero]`. Un
|
||
índice en memoria (hash -> sector) se reconstruye al arrancar recorriendo
|
||
el log de cabo a rabo.
|
||
- API: `init` —monta el disco, lee o forja el superbloque, reconstruye el
|
||
índice—, `almacenar`, `recuperar`, `raiz` y `fijar_raiz`.
|
||
- Cinco capacidades nuevas del host en `wasm/env.rs` — `sys_object_put`,
|
||
`sys_object_datos`, `sys_object_hijo`, `sys_object_raiz` y
|
||
`sys_object_fijar_raiz` —, con la misma validación infranqueable de límites
|
||
de la memoria lineal que `sys_render_frame`. Distinguen dos clases de fallo:
|
||
un puntero inválido ABORTA la app —es su culpa, se traduce en trampa—; un
|
||
fallo del almacenamiento le devuelve un código de error negativo —no lo es—.
|
||
- `apps/cronista/` — la primera aplicación del userspace que escribe en el
|
||
almacenamiento PERSISTENTE. En cada arranque consulta la raíz del grafo,
|
||
graba un objeto nuevo —`datos`: el número de arranque; `hijos`: la raíz
|
||
anterior, el eslabón del DAG— y lo corona como raíz. Pinta una celda por
|
||
arranque registrado y un testigo de integridad que recorre la cadena entera.
|
||
- Dependencias: `serde` 1 y `postcard` 1 (serialización binaria compacta, el
|
||
formato que viaja al disco) y `blake3` 1 (la función hash). Las tres `no_std`.
|
||
|
||
### Cambiado
|
||
- `kernel/src/drivers/disco.rs` reescrito para un sustrato PERMANENTE:
|
||
- El asignador de marcos «bump» de la Fase 6.1b cede el paso a uno de MAPA DE
|
||
BITS con liberación real. `dma_dealloc` y `unshare` devuelven los marcos a
|
||
la arena: un almacén vivo, con su trasiego incesante de DMA, ya no la agota.
|
||
- El `VirtIOBlk` deja de montarse y destruirse en cada llamada: se monta UNA
|
||
vez y queda tras un `Mutex` global. `leer_sectores` / `escribir_sectores`
|
||
exponen la E/S de bloques — el disco deja de ser de solo lectura. Se retira
|
||
`montar_y_leer_sector0`, la sonda de un solo uso de la Fase 6.1b.
|
||
- `boot/src/main.rs`: el disco de pruebas pasa a ser el disco de objetos. Ya
|
||
no se le graba una firma — se forja virgen, a cero, y el kernel lo formatea
|
||
la primera vez que no halle el superbloque.
|
||
- `main.rs`: `informar_disco` (la sonda de la Fase 6.1b) se sustituye por
|
||
`informar_almacen`, que funda el grafo. El userspace pasa de cuatro a cinco
|
||
apps; las regiones de discola y glotona se reajustan para alojar a cronista.
|
||
|
||
### Notas
|
||
- **blake3 forzado a escalar.** El target del kernel corre sin SSE; un camino
|
||
SIMD de blake3 activado por detección en tiempo de ejecución ejecutaría
|
||
instrucciones que la CPU, sin `CR4.OSFXSR`, rechazaría con un #UD. Se fija
|
||
blake3 con `pure` + los cuatro `no_*` (`sse2`, `sse41`, `avx2`, `avx512`):
|
||
implementación puramente escalar, sin SIMD ni ensamblador.
|
||
|
||
### Verificado
|
||
- QEMU, TRES arranques consecutivos sobre el mismo disco persistente: la app
|
||
cronista pinta 1, luego 2, luego 3 celdas — la cuenta de arranques sobrevive
|
||
a los reinicios porque vive en el grafo, en el disco, no en la RAM. El
|
||
testigo de integridad queda VERDE en los tres: el DAG entero se recorre, de
|
||
la raíz al primer eslabón, y su profundidad cuadra con la cuenta. El
|
||
superbloque en disco confirma la magia `RENASGRF`, versión 1, cursor 4 y la
|
||
raíz del tercer arranque; el registro del primer arranque guarda `datos = 1`
|
||
y cero hijos. Las otras cuatro apps siguen su curso sin alteración — las dos
|
||
`hello_wasm` renderizando, discola desalojada en púrpura, glotona en amarillo.
|
||
|
||
---
|
||
|
||
## Fase 6.2 — E/S de disco asíncrona por interrupción — 2026-05-22
|
||
|
||
La Fase 6.1 hizo hablar al disco, pero por SONDEO: el procesador se quedaba en
|
||
espera activa vigilando el *used ring* de virtio, incapaz de atender nada más.
|
||
La 6.2 libera el planificador cooperativo — la E/S de bloques pasa a ser
|
||
REACTIVA, guiada por la interrupción física del dispositivo.
|
||
|
||
### Añadido
|
||
- `EsperaDisco` (`drivers/disco.rs`): una transferencia de bloques expresada
|
||
como `Future` nativo. Posee sus buferes DMA —`BlkReq`, `BlkResp` y los datos,
|
||
en el heap para una dirección estable—; su `poll` envía la petición por la
|
||
API NO BLOQUEANTE de `virtio-drivers` (`read_blocks_nb`/`write_blocks_nb`),
|
||
consulta el *used ring* (`peek_used`) y, si la transferencia sigue en vuelo,
|
||
inscribe el waker y cede. `leer_bloques`/`escribir_bloques` lo construyen.
|
||
- La IRQ del disco: `montar` descubre la línea de IRQ legada del dispositivo
|
||
(registro «Interrupt Line», offset 0x3C del espacio de configuración PCI),
|
||
registra un manejador en la IDT y abre la línea en el PIC.
|
||
`interrupts::irq_disco` → `disco::atender_irq` reconoce la interrupción en el
|
||
dispositivo —leer su registro ISR baja la línea INTx— y despierta, vía un
|
||
waker de ranura única, a la tarea que aguardaba el bloque.
|
||
- `bloquear_en` — el puente para los contextos SÍNCRONOS (el arranque, las
|
||
capacidades WASM, que no pueden `.await`): lleva un `Future` de disco hasta
|
||
su final durmiendo la CPU con `hlt` —la despiertan la IRQ del disco o el
|
||
temporizador, como red de seguridad—; jamás en espera activa con el sistema
|
||
ya en marcha.
|
||
- `pic::desenmascarar` / `pic::vector_irq`: abrir una línea concreta del par
|
||
8259 (con su cascada, si vive en el esclavo) y mapear línea → vector de IDT.
|
||
- `pci::linea_irq`: leer el registro «Interrupt Line» de un dispositivo.
|
||
- `tarea_sonda_disco` (`main.rs`): una tarea del reactor que lee el sector 0 de
|
||
forma asíncrona — la prueba viva de que la IRQ conduce la E/S sin detener a
|
||
las aplicaciones.
|
||
|
||
### Cambiado
|
||
- `leer_sectores`/`escribir_sectores` se reescriben sobre la maquinaria
|
||
asíncrona (`bloquear_en` + `EsperaDisco`). El `almacen` y las capacidades
|
||
`sys_object_*` NO cambian una línea: heredan la E/S por interrupción de
|
||
forma transparente — la espera de disco deja de quemar ciclos en sondeo.
|
||
- El `VirtIOBlk` pide al dispositivo que emita interrupciones al completar cada
|
||
petición (`enable_interrupts`).
|
||
|
||
### Decisiones de ingeniería
|
||
- **PIC, no IOAPIC.** El kernel corre íntegramente sobre el par 8259 (`pic.rs`)
|
||
y la crate `x86_64` no ofrece abstracción de APIC. Las IRQ legadas de PCI de
|
||
la máquina `q35` se enrutan por el 8259: basta leer la línea que el firmware
|
||
UEFI ya asignó y abrirla. Migrar al IOAPIC habría exigido levantar LAPIC +
|
||
IOAPIC + parseo de tablas ACPI — un subsistema entero, desproporcionado para
|
||
el objetivo. El resultado funcional es idéntico: E/S conducida por la
|
||
interrupción física.
|
||
- **Las capacidades WASM no `.await`-ean.** El intérprete `wasmi` ejecuta las
|
||
funciones de host de forma SÍNCRONA: no hay manera de suspender un módulo a
|
||
mitad de una llamada de host. Por eso `sys_object_*` no se vuelven
|
||
asíncronas; usan `bloquear_en`, que duerme la CPU con `hlt` en lugar de
|
||
sondear. El verdadero solapamiento E/S ↔ render lo aprovechan las TAREAS del
|
||
reactor (`tarea_sonda_disco` hoy; la carga dinámica de módulos, mañana).
|
||
- Una IRQ legada de PCI es de NIVEL: el manejador reconoce primero al
|
||
dispositivo —lo que baja su línea— y sólo después cierra el EOI del PIC; el
|
||
orden inverso reavivaría la interrupción en bucle.
|
||
- Todo acceso al disco toma su `Mutex` con las interrupciones desactivadas
|
||
(`without_interrupts`): la IRQ del disco jamás encuentra el cerrojo ocupado,
|
||
lo que hace imposible el interbloqueo por interrupción.
|
||
|
||
### Verificado
|
||
- QEMU: el disco virtio-blk se enruta a la IRQ 11. La `tarea_sonda_disco`
|
||
reporta «sonda asíncrona OK -- 2 IRQ de disco atendidas»: la interrupción del
|
||
disco dispara de verdad. La app cronista, sobre el disco persistente heredado
|
||
de la Fase 6.1c, continúa la cuenta de arranques (3 → 4 → 5 celdas) con el
|
||
testigo de integridad del DAG en verde — la nueva E/S asíncrona lee y escribe
|
||
el grafo sin un solo fallo. Las cuatro apps WASM siguen su curso, sin un
|
||
sobresalto ni un micro-congelamiento.
|
||
|
||
## Fase 7 — Apertura: Apps que nacen del Grafo — 2026-05-22
|
||
|
||
Apertura de la Fase 7. Este commit asienta el plan y el andamiaje; **no
|
||
cambia el comportamiento del kernel**. La Fase 7 destierra el
|
||
`include_bytes!` del userspace: las aplicaciones pasan a ser objetos del
|
||
grafo, y un Manifiesto de Génesis —también en el grafo— dicta qué arranca,
|
||
con qué cuota y en qué región.
|
||
|
||
### Añadido
|
||
- `FASE7.md` — el plan de ataque: el objetivo, el problema huevo-y-gallina
|
||
de la génesis, las sub-fases 7a (manifiesto + carga desde el grafo, con
|
||
semilla por el kernel), 7b (siembra de la imagen por `boot`, muerte del
|
||
`include_bytes!`) y 7c (persistencia inter-sesión), y los guardarraíles.
|
||
- `kernel/src/manifiesto.rs` — andamiaje del Manifiesto de Génesis. Los
|
||
tipos `Manifiesto` / `EntradaApp` y la (de)serialización `postcard` están
|
||
completos; `cargar` y `sembrar_genesis` son esbozos hasta la Fase 7a.
|
||
La sub-región se guarda en campos `u32` de ancho fijo (formato en disco),
|
||
no como `RegionPantalla` (`usize`, ancho de plataforma).
|
||
|
||
### Notas
|
||
- El módulo se declara en `main.rs` pero aún no se cablea a `kernel_main`
|
||
(`#![allow(dead_code)]` temporal, hasta que la 7a le dé un llamador). El
|
||
kernel compila y se comporta idéntico a la Fase 6.2 — nada observable que
|
||
verificar en QEMU en esta apertura.
|
||
|
||
## Fase 7a — Apps que nacen del Grafo: el Manifiesto — 2026-05-22
|
||
|
||
El kernel deja de empotrar el userspace. Las cinco aplicaciones ya no llegan
|
||
por `include_bytes!` en `main.rs`: nacen del grafo de objetos, gobernadas por
|
||
un Manifiesto de Génesis que también vive en el grafo.
|
||
|
||
### Añadido
|
||
- `kernel/src/manifiesto.rs` — el Manifiesto de Génesis implementado: tipos
|
||
`Manifiesto` / `EntradaApp`, (de)serialización `postcard`, `cargar` (lee el
|
||
manifiesto del grafo vía el ancla del superbloque) y `sembrar_genesis`
|
||
(puebla un disco virgen con las cinco apps de génesis).
|
||
- `almacen`: el `SuperBloque` gana el ancla `manifiesto: Option<Hash>` —
|
||
gemela de `raiz`, pero del lado del kernel. Accesores `manifiesto()` y
|
||
`fijar_manifiesto()`.
|
||
|
||
### Cambiado
|
||
- `almacen::VERSION` 1 → 2: el superbloque cambia de forma. Un disco v1 se
|
||
reformatea al arrancar, como cualquier disco ajeno — la cuenta de la
|
||
cronista reinicia una vez.
|
||
- `kernel_main`: las cinco llamadas `encender_app` escritas a mano se
|
||
sustituyen por `cargar_userspace`, que carga el manifiesto —o lo siembra si
|
||
el disco está virgen— e itera sus `EntradaApp`.
|
||
- `encender_app` recibe una `EntradaApp` y recupera el bytecode del grafo.
|
||
`recuperar` ya recomputa y verifica el hash: un bytecode corrupto se niega,
|
||
se pinta la baliza de desalojo en su región y el arranque continúa.
|
||
- `wasm::AplicacionWasm::cargar` recibe el techo de memoria por-app (del
|
||
manifiesto) en vez de la constante global `TECHO_MEMORIA`.
|
||
|
||
### Notas
|
||
- La siembra es TRANSITORIA: el bytecode aún viaja empotrado en
|
||
`manifiesto.rs` (`include_bytes!`) como semilla. La Fase 7b lo moverá al
|
||
constructor de imagen `boot` y el `include_bytes!` morirá del todo.
|
||
|
||
### Verificado
|
||
- QEMU (captura headless por el monitor, `screendump`): los dos caminos del
|
||
Manifiesto confirmados.
|
||
- **Disco virgen** — el almacén reporta «disco formateado :: 65536 sectores
|
||
:: 0 objetos», la consola imprime «manifiesto :: genesis sembrada en disco
|
||
virgen» y «manifiesto :: 5 apps nacidas del grafo». La pantalla es
|
||
idéntica a la Fase 6.2: dos `hello_wasm` renderizando, discola desalojada
|
||
en púrpura, glotona en amarillo, cronista pintando su primera celda.
|
||
- **Disco ya sembrado** — sin línea de siembra: el almacén reporta «grafo
|
||
montado :: 6 objetos :: raíz presente» (las 5 semillas más el objeto que
|
||
la cronista persistió), la consola imprime sólo «manifiesto :: 5 apps
|
||
nacidas del grafo», y la cronista pinta su segunda celda — la cuenta de
|
||
arranques sobrevivió al apagón. El userspace nace del grafo en ambos
|
||
casos.
|
||
|
||
## Fase 7b — La imagen sembrada por `boot`; muere el `include_bytes!` — 2026-05-22
|
||
|
||
El kernel deja de empotrar el userspace POR COMPLETO. Hasta la 7a, el bytecode
|
||
de las cinco apps de génesis aún viajaba dentro del binario del kernel
|
||
(`include_bytes!`), como semilla transitoria. La 7b lo mata: es el constructor
|
||
de imagen `boot` —en el anfitrión— quien siembra el disco con el grafo ya
|
||
poblado. El binario del kernel ya no carga ni un solo `.wasm`.
|
||
|
||
### Añadido
|
||
- **`formato`** — nueva crate: un núcleo `#![no_std]` COMPARTIDO que define el
|
||
formato del grafo de objetos en disco. Tipos (`Objeto`, `SuperBloque`,
|
||
`Manifiesto`, `EntradaApp`), su (de)serialización `postcard`, la función hash
|
||
BLAKE3 (escalar pura) y el trazado de un registro del log
|
||
(`componer_registro` / `longitud_registro`). Lo enlazan el kernel bare-metal
|
||
Y el anfitrión `boot`: una sola verdad del formato, imposible de divergir
|
||
entre los dos lados. 5 pruebas de ida y vuelta. Excluida del workspace, como
|
||
el kernel.
|
||
- `boot`: `sembrar_grafo` — siembra un disco virgen con el grafo ya poblado.
|
||
Graba el bytecode de cada app de génesis como un objeto (deduplicado:
|
||
`app.wasm`, usado dos veces, se guarda una sola), compone el Manifiesto de
|
||
Génesis con sus regiones y cuotas, lo graba con las aristas hacia los objetos
|
||
de bytecode, y forja el superbloque que lo ancla. El `.wasm` se lee de
|
||
`kernel/assets/` en tiempo de ejecución.
|
||
|
||
### Cambiado
|
||
- `kernel/manifiesto.rs`: pierde los cuatro `include_bytes!`, el descriptor
|
||
`AppGenesis`, `genesis()` y `sembrar_genesis()`. Se reduce a `cargar()` —leer
|
||
el manifiesto del grafo— y `region()` —traducir la región en disco a la del
|
||
kernel—. Los tipos `Manifiesto` / `EntradaApp` migran a `formato`.
|
||
- `kernel/almacen.rs`: los tipos `Objeto` / `SuperBloque`, las constantes del
|
||
formato, el hash y el trazado de registros migran a `formato`. El módulo
|
||
queda como el almacén VIVO: cursor, índice y E/S contra virtio-blk.
|
||
- `kernel_main` / `cargar_userspace`: sin la rama de siembra. Un disco sin
|
||
manifiesto anclado ya no se siembra desde el kernel —`boot` lo hace siempre
|
||
al forjar la imagen—; el kernel se levanta sin userspace y lo reporta.
|
||
- `kernel/Cargo.toml`: deja de declarar `serde` / `postcard` / `blake3` por su
|
||
cuenta; los hereda —mismas features, BLAKE3 escalar puro— a través de
|
||
`formato`.
|
||
- `wasm/mod.rs`: se retira la constante `TECHO_MEMORIA`, ya sin uso (el techo
|
||
por-app lo dicta el manifiesto; el valor de génesis vive ahora en `boot`).
|
||
- `.cargo/config.toml`: el alias `cargo kernel` pasa de `-p kernel` a
|
||
`--manifest-path kernel/Cargo.toml`. Con `formato` compartido entre el kernel
|
||
y `boot`, pedir `-p kernel` dentro del workspace —con el kernel a la vez como
|
||
dependencia de artefacto— hace caer al resolvedor de features de cargo;
|
||
apuntar al manifiesto del kernel lo compila como raíz y esquiva el grafo.
|
||
|
||
### Verificado
|
||
- QEMU (captura headless por el monitor, `screendump`), los dos caminos:
|
||
- **Disco virgen** — `boot` imprime «disco de objetos sembrado :: 5 objetos,
|
||
manifiesto anclado»; el kernel monta el grafo de `boot` —«grafo montado ::
|
||
5 objetos :: raíz ausente», SIN «disco formateado» ni «génesis sembrada»—
|
||
e instancia las cinco apps. Pantalla idéntica a la Fase 6.2.
|
||
- **Segundo arranque** — `boot` respeta el disco existente («el grafo
|
||
perdura»); el kernel carga «6 objetos :: raíz presente» y la cronista pinta
|
||
su segunda celda: la persistencia sobrevive a través de un disco sembrado
|
||
por `boot`.
|
||
- `cargo build -p boot` y `cargo kernel` compilan limpios; `cargo test` de
|
||
`formato` — 5/5 en verde.
|
||
|
||
## Fase 7c — Persistencia inter-sesión: cada app recuerda lo suyo — 2026-05-22
|
||
|
||
La cronista dejaba huella, pero en la RAÍZ del grafo — un ancla única que sólo
|
||
una app puede usar. La 7c estrena la persistencia POR-APP: cada `EntradaApp`
|
||
del Manifiesto de Génesis tiene su propia ranura `estado`, y una app guarda y
|
||
recobra lo suyo sin pisar a nadie. El estado de cada aplicación sobrevive, por
|
||
separado, a un reinicio.
|
||
|
||
### Añadido
|
||
- **`apps/memoriosa`** — nueva app WASM interactiva. Cuenta las teclas pulsadas
|
||
en toda su historia y persiste el recuento; al reiniciar, despierta con su
|
||
cuenta intacta. Una celda violeta por pulsación; el testigo de la esquina es
|
||
verde si nació limpia, ámbar si despertó con memoria de una vida anterior.
|
||
- `kernel` — dos capacidades del host nuevas:
|
||
- `sys_estado_cargar(salida, capacidad)` — copia el estado persistido de la
|
||
app que llama en su memoria lineal; 0 si aún no tiene estado previo.
|
||
- `sys_estado_guardar(datos, datos_len)` — graba `datos` como objeto del
|
||
grafo y ancla su hash en la `EntradaApp` de la app, re-grabando y
|
||
re-anclando el manifiesto.
|
||
- `manifiesto.rs` — el manifiesto VIVO: `VIVO`, un `Mutex<Manifiesto>` que el
|
||
kernel custodia. `instalar` (en el arranque), `estado_de` (lee la ranura de
|
||
una app) y `fijar_estado` (la muta, re-graba el manifiesto y lo re-ancla).
|
||
- `ContextoCapacidades` gana `indice_app`: la identidad de cada app en el
|
||
manifiesto, con la que las capacidades de estado hallan SU ranura — jamás la
|
||
de otra.
|
||
|
||
### Cambiado
|
||
- `AplicacionWasm::cargar` y `encender_app` reciben el `indice_app` de la app.
|
||
- `cargar_userspace` instala el manifiesto VIVO ANTES de instanciar las apps:
|
||
el `init` de una app ya consulta su estado persistido al despertar.
|
||
- `almacen::fijar_manifiesto` deja de ser `dead_code` — lo invoca `fijar_estado`.
|
||
- La génesis (`boot`) sustituye la segunda instancia de `hola` por `memoriosa`
|
||
(región 700,120 de 360×80).
|
||
|
||
### Verificado
|
||
- QEMU (captura headless + `sendkey` por el monitor):
|
||
- **Disco virgen** — memoriosa arranca con 0 celdas y el testigo verde.
|
||
- **Cinco pulsaciones** (`sendkey`) — memoriosa pinta 5 celdas violetas y
|
||
persiste el recuento; `sys_estado_guardar` re-ancla el manifiesto.
|
||
- **Reinicio** — `boot` respeta el disco; memoriosa despierta con las 5
|
||
celdas intactas y el testigo en ámbar: su `init` releyó el estado del
|
||
grafo. El estado por-app sobrevivió al apagón.
|
||
|
||
## Fase 8a — El compositor teselante — 2026-05-22
|
||
|
||
Hasta la Fase 7, cada app llevaba su región escrita a mano en el manifiesto —
|
||
coordenadas fijas, una composición rígida. La Fase 8 entrega esa decisión a un
|
||
COMPOSITOR: el kernel ya no coloca las ventanas a mano, las TESELA con
|
||
`mirada-layout`.
|
||
|
||
### Añadido
|
||
- **`kernel/compositor.rs`** — el compositor teselante. Enlaza `mirada-layout`
|
||
—el mismo núcleo `no_std` que ordena las ventanas del compositor Wayland de
|
||
brahman, ya enlazado por `path` cruzando la frontera de workspace— y, con su
|
||
algoritmo `MasterStack`, calcula un marco para cada app. `disponer(n, …)`
|
||
devuelve un marco por ventana; `area_apps`, la zona teselable (la pantalla
|
||
menos la franja superior de la consola).
|
||
- `consola`: `pintar_escenario` —inunda el área de apps y tiñe cada marco con
|
||
el color de panel, de modo que el teselado se vea como una rejilla— y el
|
||
color `Color::PANEL`.
|
||
|
||
### Cambiado
|
||
- `consola::volcar_marco` ya no deposita el fotograma en `(region.x, region.y)`:
|
||
CENTRA el fotograma natural de la app dentro de su marco teselado y lo
|
||
recorta a sus bordes.
|
||
- `ContextoCapacidades` lleva ahora `marco` (el rectángulo teselado) y
|
||
`natural_ancho`/`natural_alto` (el tamaño del lienzo de la app);
|
||
`sys_render_frame` valida el fotograma contra el tamaño natural.
|
||
- `AplicacionWasm` / `encender_app` / `cargar_userspace` cablean el marco
|
||
teselado. `cargar_userspace` recibe las dimensiones de pantalla, tesela con
|
||
el compositor y pinta el escenario antes de encender las apps.
|
||
- `RegionPantalla::pixeles` se retira — sin uso desde que `sys_render_frame`
|
||
mide contra el tamaño natural.
|
||
- **Las apps NO cambian.** Renderizan su lienzo natural, fijo; el kernel lo
|
||
compone centrado en el marco. El compositor reordena la pantalla sin que
|
||
ninguna app toque una sola instrucción.
|
||
|
||
### Verificado
|
||
- QEMU (captura headless): las cinco apps de génesis aparecen teseladas en
|
||
`MasterStack` — `hola` como ventana maestra (izquierda, 60% del ancho) y
|
||
memoriosa, discola, glotona y cronista apiladas a la derecha. Cada lienzo,
|
||
centrado en su panel; las apps desalojadas (discola, glotona) tiñen su marco
|
||
entero con la baliza. El registro de arranque permanece legible en la franja
|
||
superior de la consola.
|
||
|
||
## Fases 8b y 8c — El escritorio interactivo — 2026-05-22
|
||
|
||
El compositor de la Fase 8a teselaba, pero era inmóvil. Las Fases 8b y 8c lo
|
||
hacen VIVO: el teclado reordena el escritorio en caliente y mueve el foco. Y
|
||
para que las apps estáticas no se queden atrás en una reordenación, el kernel
|
||
asume la persistencia visual con una caché de fotogramas.
|
||
|
||
### Añadido
|
||
- **Caché de fotogramas (backbuffer).** Cada ventana del compositor guarda, en
|
||
RAM del kernel, el último fotograma que su app envió. La caché se reserva UNA
|
||
vez, acotada al lienzo natural de la app —jamás crece—. Al re-teselar o mover
|
||
el foco, el kernel recompone cada ventana desde su caché: una app que sólo
|
||
pintó en su `init` —la cronista— conserva su imagen intacta a través de
|
||
cualquier reordenación, sin enterarse del cambio geométrico.
|
||
- **`compositor`: el escritorio interactivo.** Un registro `ESCRITORIO` —las
|
||
ventanas, sus marcos, sus cachés, el modo de teselado—; `presentar_fotograma`
|
||
(cachea y compone), `desalojar`, `atender_mandos`, `ciclar_layout`,
|
||
`mover_foco`. El foco vive en un `AtomicUsize`; los mandos del teclado, en
|
||
una cola lock-free.
|
||
- **`tarea_compositor`** — atiende los mandos del teclado en cada fotograma del
|
||
reactor: el contexto cooperativo donde es seguro re-teselar y recomponer.
|
||
- `Color::FOCO` (índigo brillante) y `Color::SIN_FOCO` (gris mate) — el borde
|
||
de cada ventana, que delata de un vistazo quién tiene el foco.
|
||
|
||
### Cambiado
|
||
- **Teclado (Fase 8c).** El manejador de IRQ1 deja de difundir a ciegas:
|
||
- La tecla **Alt** es el modificador del sistema. `Alt+Espacio` cicla el
|
||
modo de teselado; `Alt+J` / `Alt+K` mueven el foco. Estos mandos se
|
||
consumen en la IRQ — jamás llegan a una app.
|
||
- Una tecla ordinaria se entrega SÓLO a la app ENFOCADA. El censo de canales
|
||
se reindexó por `indice_app` (`Vec<Option<CanalTeclado>>`): el foco —un
|
||
índice— elige el canal exacto.
|
||
- `consola::volcar_marco` y `pintar_desalojo` trazan el borde de foco de la
|
||
ventana. `sys_render_frame` ya no compone directo: delega en
|
||
`compositor::presentar_fotograma`, que cachea antes de componer.
|
||
- `AplicacionWasm` / `ContextoCapacidades` pierden el `marco` —ahora vive en el
|
||
registro del compositor, mutable—; conservan sólo el `indice_app`.
|
||
|
||
### Exclusión de interrupciones (guardarraíl)
|
||
- El registro `ESCRITORIO` lo tocan SÓLO tareas cooperativas; el manejador de
|
||
IRQ1 jamás lo bloquea. La IRQ se comunica con el mundo cooperativo por un
|
||
canal estrecho y a prueba de interbloqueos: dos atómicos —el foco y el estado
|
||
de Alt— y una cola lock-free de mandos. Ningún cerrojo en disputa entre la
|
||
interrupción y una tarea.
|
||
- Las cachés se reservan en `compositor::fundar`, una sola vez, al tamaño
|
||
natural de cada app: ninguna asignación dinámica dentro del bucle del reactor.
|
||
|
||
### Verificado
|
||
- QEMU (captura headless + `sendkey` por el monitor):
|
||
- **Arranque** — cinco apps teseladas en `MasterStack`; `hola` enfocada luce
|
||
el borde índigo, las demás el gris.
|
||
- **`Alt+Espacio`** — el escritorio cicla a `CenteredMaster`; discola,
|
||
glotona y cronista conservan su contenido a través del re-teselado: la
|
||
caché de fotogramas en acción.
|
||
- **`Alt+J`** — el foco salta de `hola` a `memoriosa`; el borde índigo se
|
||
mueve con él.
|
||
- **Enrutamiento** — con `memoriosa` enfocada, cuatro pulsaciones llegan sólo
|
||
a ella —cuatro celdas violetas—; las demás apps, intactas.
|
||
|
||
## Fase 8d — Manipulación de ventanas — 2026-05-22
|
||
|
||
El escritorio de la 8c se podía recorrer con la vista —el foco— pero no
|
||
reordenar. La 8d lo hace MANIPULABLE: el orden de teselado se separa de la
|
||
identidad de las ventanas, y el teclado promueve y reordena.
|
||
|
||
### Añadido
|
||
- `Escritorio` gana `orden: Vec<usize>` — una permutación que dice qué ventana
|
||
ocupa cada celda del teselado. Separar el orden de la identidad
|
||
(`indice_app`) permite mover una ventana sin tocar su canal de teclado ni su
|
||
ranura de estado: cambia de pared, no de identidad.
|
||
- `aplicar_teselado` — recalcula los marcos y los reparte según el orden.
|
||
- Mandos `Promover`, `MoverAdelante`, `MoverAtras`:
|
||
- `Alt+Enter` — promueve la ventana enfocada a la celda maestra; las demás
|
||
se desplazan una posición.
|
||
- `Alt+L` / `Alt+H` — mueven la ventana enfocada adelante / atrás en el
|
||
orden, intercambiándola con su vecina.
|
||
|
||
### Cambiado
|
||
- `mover_foco` recorre ahora el ORDEN de teselado —no los índices crudos—: el
|
||
foco salta entre ventanas visualmente contiguas.
|
||
- `ciclar_layout` y `fundar` delegan en `aplicar_teselado`.
|
||
|
||
### Verificado
|
||
- QEMU (`sendkey`): con `memoriosa` enfocada, `Alt+Enter` la promueve a la
|
||
ventana maestra y `hola` baja a la pila; `Alt+L` la devuelve a la pila y
|
||
`hola` recupera la maestra. El foco —el borde índigo— viaja siempre con la
|
||
ventana, no con la celda.
|
||
|
||
## Fase 9 — Orden-Z y ventanas flotantes — 2026-05-22
|
||
|
||
Hasta la 8d el escritorio era TESELADO puro: las ventanas repartían la pantalla
|
||
sin solaparse jamás. La Fase 9 introduce un segundo modelo de composición —el
|
||
SOLAPAMIENTO—: una ventana puede abandonar el teselado y FLOTAR, con un marco
|
||
propio y libre, por encima de las demás.
|
||
|
||
### Añadido
|
||
- `Escritorio` gana `flotantes: Vec<usize>` — la pila de ventanas flotantes en
|
||
orden-Z, de atrás hacia adelante; `flotantes.last()` es la frontal. Junto con
|
||
`orden` forma una partición de las ventanas: cada una está teselada o flota,
|
||
nunca en ambas ni en ninguna.
|
||
- Mando `Flotar` (`Alt+F`): alterna la ventana enfocada entre teselada y
|
||
flotante. Al flotar, abandona el teselado —que se recalcula para las que
|
||
quedan—, recibe un marco propio (su lienzo natural más un reborde de cromo)
|
||
colocado en CASCADA, y sube al frente del orden-Z. Al volver, se reincorpora
|
||
al final del orden de teselado.
|
||
- `compositor::recomponer` — recompone el escritorio entero de una pasada: arma
|
||
la lista de capas (las teseladas al fondo; las flotantes encima, de atrás
|
||
hacia adelante) y se la entrega a la consola.
|
||
- `consola::recomponer` y los tipos `Capa` / `Contenido`: la consola funde las
|
||
capas EN ORDEN sobre el lienzo; el solapamiento se resuelve por el orden del
|
||
pintado, sin recortes ni máscaras. Una sola presentación cierra la pasada.
|
||
- `consola::componer_fotograma` — el volcado de un fotograma natural centrado
|
||
en su marco, extraído de `volcar_marco` para compartirlo entre el camino
|
||
rápido y la recomposición.
|
||
|
||
### Cambiado
|
||
- `presentar_fotograma` y `desalojar` bifurcan: sin ventanas flotantes
|
||
conservan el camino RÁPIDO de la Fase 8 —pintar sólo la ventana que cambia—;
|
||
con flotantes vivas recomponen el escritorio entero respetando el orden-Z.
|
||
- `mover_foco` recorre ahora TODAS las ventanas —las teseladas y las
|
||
flotantes—; al enfocar una flotante, la alza al frente del orden-Z: la
|
||
flotante con el foco está siempre delante.
|
||
- `componer_escenario`, `ciclar_layout`, `promover` y `mover_ventana` delegan
|
||
el repintado en `recomponer`; desaparecen `redibujar_todo`,
|
||
`redibujar_ventana` y `pintar_escenario`.
|
||
|
||
### Verificado
|
||
- QEMU (`sendkey`): `Alt+F` saca la ventana maestra del teselado y la deja
|
||
flotando sobre las demás, que se re-teselan. Un segundo `Alt+F` sobre otra
|
||
ventana la flota en cascada, solapando a la primera. `Alt+K` devuelve el foco
|
||
a la ventana grande y ésta sube al frente, tapando por completo a la pequeña.
|
||
Un `Alt+F` final la reintegra al teselado.
|
||
|
||
## Fase 10 — Alta y baja de aplicaciones en vivo — 2026-05-22
|
||
|
||
Hasta la Fase 9 el censo de aplicaciones se fijaba en el arranque: las apps
|
||
nacían del manifiesto y sólo morían al fallar. La Fase 10 lo vuelve DINÁMICO —
|
||
una app puede nacer o cerrarse con el reactor ya en marcha.
|
||
|
||
### Añadido
|
||
- **El reactor admite nacimientos en vivo.** `executor`: una cola de
|
||
NACIMIENTOS (`Vec<FuturoTarea>` tras un `Mutex`) y la función `engendrar`.
|
||
El ejecutor la drena al inicio de cada vuelta (`recoger_nacimientos`) y
|
||
adopta cada futuro como tarea. `Task::adoptar` acoge un futuro ya
|
||
empaquetado. `dormir_si_inactivo` cuenta los nacimientos como trabajo.
|
||
- **Baja en vivo — `Alt+Q`.** Mando `Cerrar`: el compositor marca la ventana
|
||
enfocada como `cerrada`, libera su caché de respaldo, la saca del teselado y
|
||
del orden-Z, y traslada el foco a una ventana viva contigua. La app, en su
|
||
tarea, consulta `compositor::ventana_cerrada` cada fotograma; al verla
|
||
cerrada concluye su tarea — y `AplicacionWasm::drop` libera su memoria
|
||
lineal, su combustible y su canal de teclado. Una baja LIMPIA, sin baliza.
|
||
- **Alta en vivo — `Alt+N`.** Mando `Lanzar`: el compositor cuenta la petición
|
||
(`PARTOS`); la tarea del compositor la atiende con `partos_pendientes` y
|
||
`lanzar_app`. `compositor::nacer_ventana` añade la ventana y devuelve su
|
||
índice; el orquestador instancia el WASM con ese índice y `engendra` su
|
||
tarea. Las apps de génesis dejan su bytecode cacheado en RAM como
|
||
`Plantilla`; cada `Alt+N` instancia la siguiente en rotación, sin volver al
|
||
disco —una E/S por sondeo en mitad del reactor sería un mal vecino—.
|
||
- `compositor`: campo `Ventana.cerrada`, mandos `Cerrar` / `Lanzar`, y las
|
||
funciones `cerrar`, `nacer_ventana`, `ventana_cerrada`, `partos_pendientes`.
|
||
- `teclado`: `Alt+Q` → `Cerrar`, `Alt+N` → `Lanzar`.
|
||
|
||
### Cambiado
|
||
- `encender_app` devuelve la `Plantilla` de la app —su bytecode y geometría—
|
||
para los lanzamientos en vivo.
|
||
- `tarea_aplicacion` consulta `ventana_cerrada` antes de cada `tick`.
|
||
- `presentar_fotograma` y `desalojar` ignoran una ventana ya cerrada: una baja
|
||
limpia gana a un fotograma o a un desalojo que lleguen tarde.
|
||
|
||
### Verificado
|
||
- QEMU (`sendkey`): tres `Alt+N` dan a luz tres apps nuevas y el escritorio se
|
||
re-tesela de 5 a 8 ventanas. Tres `Alt+Q` cierran la app enfocada una a una
|
||
y el teselado reclama su espacio, de 8 de vuelta a 5. El kernel sigue estable
|
||
a través de todas las altas y bajas.
|
||
|
||
## Fase 11 — El reloj del sistema como capacidad de host — 2026-05-22
|
||
|
||
Hasta la Fase 10 una aplicación sólo sabía CUÁNTAS veces la habían llamado —un
|
||
`tick` tras otro—, no CUÁNTO tiempo había pasado. La Fase 11 le da al userspace
|
||
un sentido del tiempo: el reloj monótono del sistema, como capacidad.
|
||
|
||
### Añadido
|
||
- **Capacidad `sys_tiempo_mono() -> u64`** — la décima función del host. Los
|
||
milisegundos transcurridos desde el arranque. El temporizador (PIT) ya late a
|
||
100 Hz; `reloj` expone esa cuenta como `milisegundos()` y `env` la inyecta.
|
||
Es una lectura PURA —no toca la memoria lineal del módulo, no hay puntero que
|
||
validar— y MONÓTONA: jamás retrocede.
|
||
- **App `pulso`** (`apps/pulso/`, `wasm32`). Un compás visual: una cabeza
|
||
brillante que recorre una pista y vuelve, con un período de 6 s. Su escena es
|
||
una FUNCIÓN PURA de `sys_tiempo_mono` —no guarda estado entre fotogramas—.
|
||
De ahí su prueba: dos instancias de `pulso`, nazca una al arrancar y otra
|
||
mucho después con un `Alt+N`, laten exactamente al unísono.
|
||
- `pulso` se suma al userspace de génesis: `GENESIS` pasa de 5 a 6 apps, con
|
||
`pulso` como la primera —la ventana maestra del escritorio—.
|
||
|
||
### Verificado
|
||
- QEMU (`sendkey`): la barra de `pulso` avanza con el tiempo de pared entre dos
|
||
capturas. Un `Alt+N` da a luz un segundo `pulso` ~15 s después del arranque;
|
||
flotado junto al primero, su barra está en la MISMA fase — la prueba de que
|
||
el compás se rige por el reloj absoluto, no por una cuenta de fotogramas.
|
||
|
||
## Fase 12 — La bocina del PC como capacidad de host — 2026-05-22
|
||
|
||
La Fase 11 le dio al userspace un reloj; la Fase 12, una voz. La bocina del PC
|
||
—el canal 2 del PIT como generador de onda cuadrada— se suma a la matriz de
|
||
capacidades. Hasta hoy renaser sólo sabía DIBUJAR para llamar la atención.
|
||
|
||
### Añadido
|
||
- **Driver `drivers/altavoz`.** Programa el canal 2 del PIT a una frecuencia y
|
||
abre la compuerta del puerto 0x61. El canal 0 del PIT es el latido del
|
||
kernel; el canal 2 es de la bocina y de nadie más — no se perturban—.
|
||
`tono(hz)` es su única vía; un `0` la silencia.
|
||
- **Capacidad `sys_tono(frecuencia_hz)`** — la undécima función del host. La
|
||
bocina es un recurso ÚNICO y global: para que dos apps no se la disputen,
|
||
pertenece —como el teclado desde la Fase 8c— a la ventana ENFOCADA. Una app
|
||
sin foco puede pedir un tono; sencillamente, no se oye. Al cambiar el foco,
|
||
el compositor calla la bocina (`mover_foco`, `cerrar`): la nueva dueña la
|
||
reclama en su próximo fotograma.
|
||
- **App `tonada`** (`apps/tonada/`, `wasm32`). Toca una escala de Do mayor en
|
||
bucle y la dibuja como una escalera de ocho barras, con la nota que suena
|
||
encendida. Junta las dos capacidades nuevas del kernel: el reloj
|
||
(`sys_tiempo_mono`) le marca el compás, la bocina (`sys_tono`) le da voz.
|
||
- `tonada` encabeza el userspace de génesis: `GENESIS` pasa de 6 a 7 apps, con
|
||
`tonada` como la ventana maestra —enfocada al arrancar, así suena de
|
||
inmediato—.
|
||
|
||
### Verificado
|
||
- QEMU. Visual: la escalera de `tonada` enciende la nota en curso y ésta
|
||
recorre la escala con el tiempo (capturas con la 6.ª y la 1.ª nota vivas).
|
||
Sonido: con la bocina enrutada a un WAV (`-audiodev wav` + `-machine
|
||
pcspk-audiodev`), el PCM capturado —50 s— es una onda cuadrada oscilante de
|
||
±24 000 de amplitud, con una frecuencia media de ~375 Hz: la de la escala de
|
||
Do mayor.
|
||
|
||
## Fase 13 — Ratón, puntero y arrastre de flotantes — 2026-05-22
|
||
|
||
renaser tenía teclado y bocina como entrada/salida del usuario, pero no
|
||
puntero. Las ventanas flotantes de la Fase 9 nacían en cascada y se quedaban
|
||
ahí, clavadas. La Fase 13 trae el ratón: un puntero en pantalla, clic-para-
|
||
enfocar y arrastre del marco de las ventanas flotantes.
|
||
|
||
### Añadido
|
||
- **Driver `drivers/raton`** — el ratón PS/2 cuelga del dispositivo auxiliar
|
||
del 8042 (la misma controladora que el teclado) y anuncia cada movimiento
|
||
por la IRQ12. El driver despierta el aux, enciende su IRQ, le ordena
|
||
reportar, y ensambla los paquetes de 3 bytes con la guarda del bit-3 que
|
||
detecta desincronización. Como el teclado, la IRQ12 sólo toca atómicos
|
||
—posición del puntero— y una cola lock-free de eventos.
|
||
- **El puntero, capa de presentación.** `Pantalla` gana `formato` y la
|
||
función `estampar_puntero`: un sprite de flecha de 12×18 que se pinta
|
||
DIRECTAMENTE sobre el framebuffer, justo después de copiar el lienzo. El
|
||
lienzo permanece libre de puntero —hace de save-under natural—; cada
|
||
`presentar` sella el puntero al final.
|
||
- **Capacidad del compositor**: `atender_raton` drena eventos del ratón cada
|
||
fotograma. El botón izquierdo bajando es un CLIC: enfoca la ventana viva
|
||
bajo el puntero (consistente con `mover_foco` — silencia la bocina, alza al
|
||
frente si flota). Si la enfocada es flotante, arranca un ARRASTRE con el
|
||
desfase de agarre; con el botón sostenido, la ventana sigue al puntero;
|
||
soltarlo lo termina.
|
||
- **`refrescar_puntero`**: en una vuelta tranquila en que ninguna app pinte,
|
||
reestampa el framebuffer si el puntero se ha movido — el centinela
|
||
empacado evita repintar dos veces el mismo instante.
|
||
- `Escritorio` gana `arrastre: Option<Arrastre>` y `raton_izq: bool`.
|
||
`Arrastre` guarda el índice de la ventana asida y el desfase de agarre.
|
||
`cerrar` libera el arrastre si la ventana cerrada era la arrastrada.
|
||
|
||
### Verificado
|
||
- QEMU (`mouse_move` + `mouse_button` del monitor). El puntero aparece en el
|
||
centro al arrancar; `mouse_move` lo lleva por la pantalla. Clic sobre la
|
||
ventana `pulso` (en la pila) la enfoca: el borde índigo deja la maestra y
|
||
envuelve a `pulso`. `Alt+F` la flota; el ratón la agarra y la arrastra de
|
||
la esquina superior izquierda al centro-abajo de la pantalla con el botón
|
||
sostenido. El kernel sigue estable a través de los gestos.
|
||
|
||
## Mapeador MMIO en el kernel — 2026-05-23
|
||
|
||
OVMF aloja los BAR prefetchables 64-bit de virtio en la ventana PCI de 64
|
||
bits, que el `bootloader_api` no mapea. `KernelHal::mmio_phys_to_virt`
|
||
devolvía `phys + offset` a ciegas y el primer registro MMIO leído reventaba
|
||
con #PF, dejando una franja roja sin pista visible. La autopsia: el commit
|
||
anterior añadió el dump del panic-handler por COM1, que sacó a la luz el IP
|
||
en `unchecked_mul::precondition_check` y la dirección de fallo
|
||
`offset + 0x800000014` — phys 32 GiB, donde OVMF había puesto el BAR.
|
||
|
||
### Añadido
|
||
- `memory::mmio`: envuelve la tabla L4 activa (vía `Cr3` + el mapeo de
|
||
memoria física del cargador) en un `OffsetPageTable`. `mapear(fisica,
|
||
tam)` abre páginas hacia la región pedida con `PRESENT | WRITABLE |
|
||
NO_CACHE | WRITE_THROUGH`, tratando `PageAlreadyMapped` y
|
||
`ParentEntryHugePage` como éxito silencioso.
|
||
- Los marcos para tablas intermedias salen del banco DMA del disco
|
||
(`asignar_marco_para_tabla`, sin pánico).
|
||
- `KernelHal::mmio_phys_to_virt` llama a `memory::mmio::mapear` antes de
|
||
devolver el puntero, asegurándose de que el BAR sea accesible.
|
||
|
||
### Cambiado
|
||
- `baliza::Serie` es ahora `pub(crate)`: cualquier módulo puede dejar
|
||
trazas por COM1. El panic-handler y el OOM-handler dejan en COM1 su
|
||
mensaje y ubicación además de pintar la franja. `kernel_main` traza
|
||
cada hito del arranque para diagnóstico remoto.
|
||
|
||
## Fase 14 — La identidad del escritorio: nombres y barra de tareas — 2026-05-23
|
||
|
||
Las ventanas eran anónimas: el escritorio reía con sus colores pero no sabía
|
||
cómo se llama lo que muestra. La Fase 14 le da nombre a cada cuarto y una
|
||
barra al pie con la lista de quien vive en la casa.
|
||
|
||
### Añadido
|
||
- Cada `Ventana` lleva un `nombre: String` —el que dicta su `EntradaApp` del
|
||
manifiesto, o el que el orquestador pasa al engendrarla en vivo (Fase 10)—.
|
||
`Plantilla` se acuerda del nombre para los lanzamientos posteriores.
|
||
- **`FRANJA_TASKBAR`** (40 px al pie) y `area_taskbar`: la barra de tareas
|
||
vive en su propia franja. `area_apps` la descuenta — las ventanas teselan
|
||
y flotan SIN tapar la barra ni la consola de la cima.
|
||
- `consola`: tipos `Taskbar` y `CeldaTaskbar`; métodos `pintar_taskbar`
|
||
(fondo + línea divisoria + una pestaña por ventana viva) y
|
||
`pintar_etiqueta` (rasteriza una cadena en un punto fijo, sin tocar la
|
||
pluma de la consola). La pestaña enfocada lleva el índigo del foco; las
|
||
desalojadas, su color de baliza; las demás, el slate del panel.
|
||
- `compositor::recomponer` arma la lista de celdas y la pasa a la consola
|
||
junto con las capas: un único repintado, una única presentación.
|
||
- `atender_raton`: un clic dentro de la franja de la barra busca su pestaña
|
||
con `celda_taskbar_en` y enfoca la ventana correspondiente (sin iniciar
|
||
arrastre). El comportamiento sobre el área de apps queda intacto.
|
||
|
||
### Cambiado
|
||
- `compositor::fundar` y `nacer_ventana` exigen un nombre para cada ventana.
|
||
`cargar_userspace` lo toma de `EntradaApp.nombre`; `lanzar_app`, de
|
||
`Plantilla.nombre`.
|
||
|
||
### Verificado
|
||
- QEMU. Tras arrancar se ve la barra al pie con las 7 pestañas (`tonada`
|
||
enfocada, índigo), las desalojadas en sus colores de baliza. Un clic
|
||
sobre `pulso` cambia el foco al instante: el borde índigo del compositor
|
||
deja la maestra y envuelve a `pulso`, y la pestaña `pulso` se ilumina.
|
||
|
||
## Fase 15 — La voz del sistema — 2026-05-23
|
||
|
||
La bocina pertenecía a la ventana enfocada (Fase 12), pero el kernel necesita
|
||
hablar también: un acorde al abrir la casa, un repique al recibir un inquilino
|
||
nuevo, un bajo al echar a uno que se rompió. La Fase 15 le da al sistema su
|
||
propia voz, prioritaria sobre las de los apps.
|
||
|
||
### Añadido
|
||
- **`altavoz::SECUENCIA`** — una `VecDeque<(u32, u32)>` (frecuencia, duración
|
||
ms) que el kernel encola con `altavoz::agendar(&[(...)])`. `FIN_NOTA` (un
|
||
`AtomicU64`) recuerda el milisegundo del reloj monótono en que la nota
|
||
actual debe terminar.
|
||
- **`altavoz::atender()`** — invocada por la tarea del compositor cada
|
||
fotograma; si la nota actual ya terminó, saca la siguiente de la cola y la
|
||
toca; si la cola está vacía, silencia.
|
||
- **`altavoz::kernel_sonando()`** — `true` mientras `FIN_NOTA` esté en el
|
||
futuro. `sys_tono` lo consulta y, en ese caso, ignora la llamada del app:
|
||
el kernel no se interrumpe a sí mismo.
|
||
- **Catálogo de voces**: `VOZ_BIENVENIDA` (Do5-Mi5-Sol5 ascendente, 500 ms),
|
||
`VOZ_LANZAR` (repique 700→1050 Hz), `VOZ_CERRAR` (descendente 900→520 Hz),
|
||
`VOZ_DESALOJO` (bajo 180 Hz, 260 ms).
|
||
- **Hitos sonoros**: `kernel_main` agenda `VOZ_BIENVENIDA` justo antes de
|
||
`ejecutor.run()`. `nacer_ventana` agenda `VOZ_LANZAR`. `cerrar` agenda
|
||
`VOZ_CERRAR`. `desalojar` agenda `VOZ_DESALOJO`.
|
||
|
||
### Cambiado
|
||
- Las pestañas de la barra de tareas calculan su tinta por brillo del fondo
|
||
(ITU-R BT.601): la pestaña amarilla pálida del desalojo por memoria, que
|
||
llevaba texto blanco invisible, ahora luce su nombre en tinta oscura.
|
||
|
||
### Verificado
|
||
- QEMU con `-audiodev wav -machine pcspk-audiodev=spk`. El PCM crudo revela,
|
||
en orden, las tres notas del acorde de bienvenida (≈520, 630, 760 Hz),
|
||
inmediatamente un brevísimo bajo de 180 Hz (la baliza de discola/glotona
|
||
desalojadas), y después la escala de `tonada` tomando la bocina.
|
||
- Captura: la pestaña de `glotona` (crema) muestra ahora su nombre legible
|
||
en tinta oscura; la de `discola` (púrpura) sigue clara, como antes.
|
||
|
||
## Fase 16 — La barra viva: lanzador y reloj — 2026-05-23
|
||
|
||
La barra de tareas era informativa pero pasiva: nombraba a los inquilinos sin
|
||
ofrecerse a ningún gesto propio. La Fase 16 le añade los dos accesorios de
|
||
toda barra de tareas digna: un **botón lanzador** a la izquierda y un **reloj**
|
||
a la derecha, y le pone el reloj a latir cada segundo.
|
||
|
||
### Añadido
|
||
- **Botón lanzador («+»)** a la izquierda de la barra (36 px de ancho, fondo
|
||
índigo del foco, cruz blanca). Un clic incrementa `PARTOS` — el mismo
|
||
contador que `Alt+N` —, así que la tarea del compositor lo recoge y lanza
|
||
la siguiente app de la rotación. El teclado y el ratón comparten ahora la
|
||
misma vía para crear ventanas.
|
||
- **Reloj** a la derecha de la barra (80 px), formato `mm:ss` desde el
|
||
arranque, leído de `reloj::milisegundos()`. Tinta blanca sobre slate.
|
||
- **`compositor::tick_reloj()`** — la invoca la tarea del compositor cada
|
||
fotograma. Si el segundo del reloj cambió respecto al último mostrado
|
||
(`ULTIMO_SEGUNDO: AtomicU64`), recompone; si no, vuelve. Cero coste cuando
|
||
no hace falta refrescar.
|
||
- `Taskbar` gana los campos `launcher: RegionPantalla`, `reloj: &str` y
|
||
`reloj_region: RegionPantalla`. `pintar_taskbar` los pinta como dos cuñas
|
||
fijas de la barra: el lanzador con su cruz dibujada en píxeles directos
|
||
(independiente de la tipografía), el reloj con la etiqueta de costumbre.
|
||
|
||
### Cambiado
|
||
- El layout de la barra se recalcula: `cells_x0` empieza tras el lanzador,
|
||
`cells_x_max` termina antes del reloj. `celda_taskbar_en` respeta esos
|
||
límites; un clic en el lanzador (`clic_en_launcher`) tiene su propia rama
|
||
en `atender_raton`, antes que la búsqueda de pestaña.
|
||
- `CELDA_TASKBAR_ANCHO` baja de 156 a 150 para que el lanzador, las siete
|
||
pestañas y el reloj quepan holgados en una pantalla de 1280 píxeles.
|
||
|
||
### Verificado
|
||
- QEMU. La barra al arrancar muestra el botón «+» indigo a la izquierda, las
|
||
siete pestañas (con `glotona` ya legible en tinta oscura sobre crema), y el
|
||
reloj `0:17` a la derecha (el tiempo que el kernel lleva vivo al capturar).
|
||
Diez segundos después, el reloj marca `0:29` — la barra se ha refrescado
|
||
doce veces sin intervención del ratón ni del teclado.
|
||
|
||
## Fase 17 — `bitacora` :: el editor que recuerda — 2026-05-23
|
||
|
||
`memoriosa` (Fase 7c) demostró que un app podía persistir un contador a
|
||
través de los reinicios. `bitacora` extiende esa demostración a un EDITOR de
|
||
texto: tecleas, los caracteres aparecen; reinicias el kernel, el texto
|
||
sigue ahí. La huella vive en el grafo de objetos, como todo lo demás.
|
||
|
||
### Añadido — app `bitacora` (`apps/bitacora/`)
|
||
- **Lienzo 480×280** con título "bitacora :: el texto persiste" en índigo
|
||
arriba y, debajo, las últimas líneas del buffer. Wrap automático a 28
|
||
columnas; `Enter` salta de línea; Backspace borra el último carácter.
|
||
- **Tipografía 8×8** embebida (crate `font8x8 = "0.3"`), escalada x2 a 16×16.
|
||
El app rasteriza cada glifo pixel a pixel desde su propia memoria lineal.
|
||
- **Persistencia automática**: cada cambio invoca `sys_estado_guardar`. Al
|
||
arrancar, `init` llama a `sys_estado_cargar` y restaura el buffer.
|
||
- Buffer de 512 bytes; al desbordarse descarta los 64 primeros para hacer
|
||
hueco (amortiza el coste — no es una mudanza por cada pulsación).
|
||
- Mapeo de scancodes US a ASCII para letras, dígitos y puntuación común;
|
||
Enter genera `\n`, Backspace borra. Sin mayúsculas ni modificadores.
|
||
|
||
### Cambiado
|
||
- **`GENESIS` crece de 7 a 8 apps** con `bitacora` como la PRIMERA — gana
|
||
la celda maestra al arrancar, así que la primera ventana grande del
|
||
escritorio te invita a teclear.
|
||
- **`CELDA_TASKBAR_ANCHO`** baja de 150 a 130 píxeles para que las ocho
|
||
pestañas + el lanzador + el reloj quepan holgadas en 1280 px.
|
||
|
||
### Verificado
|
||
- QEMU (`sendkey` del monitor): tras escribir `hola renaser` y `quit` →
|
||
relanzar QEMU con el mismo `disk.img`, la `bitacora` muestra de nuevo el
|
||
texto justo donde quedó. El `almacen` reporta 24 objetos en el grafo
|
||
(frente a 9 antes de escribir) y `raiz presente`: cada `guardar` anexó
|
||
una versión al log direccionado por contenido.
|