diff --git a/renaser/CHANGELOG.md b/renaser/CHANGELOG.md index 44c1512..d477953 100644 --- a/renaser/CHANGELOG.md +++ b/renaser/CHANGELOG.md @@ -847,3 +847,29 @@ una app puede nacer o cerrarse con el reactor ya en marcha. 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. diff --git a/renaser/CLAUDE.md b/renaser/CLAUDE.md index 2e67c1f..4a7ddae 100644 --- a/renaser/CLAUDE.md +++ b/renaser/CLAUDE.md @@ -28,7 +28,7 @@ Reconstruir una app WASM del userspace tras tocarla. Los `.wasm` viven en modulo `hello_wasm` se copia como `app.wasm`, el resto conserva su nombre: ```sh -cd apps/ # hello_wasm, discola, glotona, cronista, memoriosa +cd apps/ # hello_wasm, discola, glotona, cronista, memoriosa, pulso cargo build --target wasm32-unknown-unknown --release cp target/wasm32-unknown-unknown/release/.wasm ../../kernel/assets/.wasm # (hello_wasm es la excepcion: su destino es kernel/assets/app.wasm) @@ -78,8 +78,9 @@ objetos—, la Fase 8 COMPLETA —el compositor teselante e interactivo: teselad con `mirada-layout` (8a), ciclado de layout (8b), foco y enrutamiento selectivo del teclado (8c), promoción y reordenación de ventanas (8d)—, la Fase 9 COMPLETA —orden-Z y ventanas flotantes: composición con solapamiento (`Alt+F`)— -y la Fase 10 COMPLETA —alta y baja de aplicaciones en vivo (`Alt+N` / `Alt+Q`)—. -Todo verificado en QEMU. Ver `ROADMAP.md`. +la Fase 10 COMPLETA —alta y baja de aplicaciones en vivo (`Alt+N` / `Alt+Q`)— y +la Fase 11 COMPLETA —el reloj del sistema como capacidad de host +(`sys_tiempo_mono`) + la app `pulso`—. Todo verificado en QEMU. Ver `ROADMAP.md`. ## Flujo de trabajo diff --git a/renaser/DIARIO.md b/renaser/DIARIO.md index c0558d3..3853276 100644 --- a/renaser/DIARIO.md +++ b/renaser/DIARIO.md @@ -434,6 +434,26 @@ y no volvía a tocar la lista. Ahora tiene una bandeja donde van dejándose los recién llegados, y en cada ronda la mira y les da su sitio. La casa ya no se escribe entera de una vez: se va escribiendo, día a día, mientras se vive. +## El sentido del tiempo — un reloj para los inquilinos + +Los inquilinos de la casa sabían hacer su trabajo, turnarse, recordar entre +sesiones. Pero había algo que no sabían: qué hora era. Vivían en un presente +sin medida —cada uno contaba las veces que lo habían despertado, y nada más—. +Dos inquilinos que empezaran la misma tarea en momentos distintos no tenían +forma de ir a la par. + +Hoy la casa les regaló un reloj. No uno para cada cuarto: uno solo, en el +recibidor, que todos pueden mirar. Marca, sin más, cuánto hace que la casa +despertó. Quien quiera saber la hora, la mira; quien no, sigue a lo suyo. + +Y para estrenarlo llegó un inquilino nuevo, «pulso», que no hace otra cosa que +mirar ese reloj y dibujar su paso: una luz que recorre un riel, una y otra vez, +con un compás de seis segundos. Lo hermoso es lo que ocurre al invitar a un +segundo «pulso» mucho después: no empieza su recorrido desde el principio —se +incorpora justo donde va el primero, al instante, como dos bailarines que oyen +la misma música—. Porque ninguno lleva su propia cuenta: ambos miran el mismo +reloj del recibidor. + --- *El diario continúa. La próxima página la escribirá la próxima jornada.* diff --git a/renaser/ROADMAP.md b/renaser/ROADMAP.md index ff74c7e..9db872f 100644 --- a/renaser/ROADMAP.md +++ b/renaser/ROADMAP.md @@ -181,8 +181,22 @@ Verificada en QEMU (`sendkey`). - El censo de ventanas sólo crece —los índices son la identidad, jamás se reciclan—; una ventana cerrada queda como ranura inerte, fuera del teselado. -Líneas abiertas posteriores: más capacidades del host (temporización, audio); -reciclado de las ranuras de ventana cerradas. +## Fase 11 — el reloj del sistema como capacidad de host (completada) + +Hasta la Fase 10 una aplicación sólo sabía cuántas veces la habían llamado, no +cuánto tiempo había pasado. La Fase 11 le da al userspace un sentido del +tiempo. Verificada en QEMU (`sendkey`). + +- Capacidad `sys_tiempo_mono`: los milisegundos monótonos desde el arranque —la + décima función del host—. El temporizador (PIT) ya late a 100 Hz; `reloj` + expone esa cuenta y `env` la inyecta. Lectura pura, jamás retrocede. +- App nueva `pulso`: un compás visual cuya escena es una función PURA del reloj + del host. Dos instancias, nazcan cuando nazcan, laten al unísono — la prueba + de que el tiempo es absoluto, no una cuenta de fotogramas. +- El userspace de génesis crece de 5 a 6 apps. + +Líneas abiertas posteriores: audio como capacidad de host; reciclado de las +ranuras de ventana cerradas. ## Principios que persisten entre fases diff --git a/renaser/apps/pulso/Cargo.lock b/renaser/apps/pulso/Cargo.lock new file mode 100644 index 0000000..4240cc9 --- /dev/null +++ b/renaser/apps/pulso/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "pulso" +version = "0.1.0" diff --git a/renaser/apps/pulso/Cargo.toml b/renaser/apps/pulso/Cargo.toml new file mode 100644 index 0000000..25dc3aa --- /dev/null +++ b/renaser/apps/pulso/Cargo.toml @@ -0,0 +1,29 @@ +# ============================================================================= +# renaser :: apps/pulso — Fase 11 :: el compas visual del reloj del host +# ----------------------------------------------------------------------------- +# Un modulo WebAssembly puro que no guarda estado alguno: su escena es una +# funcion pura del reloj monotono del sistema (`sys_tiempo_mono`). Por eso dos +# instancias de `pulso`, nazcan cuando nazcan, laten al unisono. Tiene su +# propio `[workspace]`: queda fuera del espacio de trabajo del kernel. +# ============================================================================= + +[package] +name = "pulso" +version = "0.1.0" +edition = "2021" +description = "renaser :: app WASM — un compas visual gobernado por el reloj del host" + +[workspace] + +# `cdylib` produce un modulo `.wasm` que exporta funciones — el formato que +# wasmi instancia. La aplicacion solo habla con el kernel por funciones del host. +[lib] +crate-type = ["cdylib"] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +opt-level = "s" +lto = true diff --git a/renaser/apps/pulso/src/lib.rs b/renaser/apps/pulso/src/lib.rs new file mode 100644 index 0000000..70ff7d3 --- /dev/null +++ b/renaser/apps/pulso/src/lib.rs @@ -0,0 +1,137 @@ +// ============================================================================= +// renaser :: apps/pulso — Fase 11 :: el compas visual del reloj del host +// ----------------------------------------------------------------------------- +// Hasta la Fase 10 una aplicacion solo sabia CUANTAS veces la habian llamado +// —un `tick` tras otro—, no CUANTO tiempo habia pasado. La Fase 11 le da al +// userspace un reloj: la capacidad `sys_tiempo_mono`, los milisegundos +// monotonos desde el arranque. +// +// `pulso` es su testigo. Dibuja un compas —una cabeza brillante que recorre +// una pista y vuelve— cuya posicion es una FUNCION PURA del reloj del host. +// No guarda estado entre fotogramas: no le hace falta. Y de ahi su prueba: +// dos instancias de `pulso`, nazca una al arrancar y otra mucho despues con +// un `Alt+N`, laten EXACTAMENTE al unisono — porque ambas leen el mismo +// reloj, no un contador propio de fotogramas. +// ============================================================================= + +#![no_std] + +// --- Las capacidades que el kernel `renaser` inyecta a esta aplicacion. --- +#[link(wasm_import_module = "renaser")] +extern "C" { + /// Compone un bufer de pixeles (de ESTA memoria lineal) en la region que el + /// kernel asigno a esta aplicacion. + fn sys_render_frame(ptr: u32, len: u32); + /// El reloj monotono del sistema: milisegundos desde el arranque. + fn sys_tiempo_mono() -> u64; +} + +/// Sin sistema operativo bajo nosotros, un panico solo puede detenerse en seco. +#[panic_handler] +fn al_fallar(_: &core::panic::PanicInfo) -> ! { + loop {} +} + +// --- Geometria de la escena. El ancho y el alto DEBEN coincidir con la region +// que el kernel asigna a esta app. --- +const ANCHO: usize = 360; +const ALTO: usize = 120; + +/// El periodo del compas, en milisegundos: la cabeza recorre la pista entera y +/// vuelve a empezar cada `PERIODO`. +const PERIODO: u64 = 6000; + +/// Azul nocturno: el fondo del lienzo. +const FONDO: u32 = 0x0A_18_30; +/// La pista por donde corre el compas — un surco tenue. +const PISTA: u32 = 0x1E_2A_44; +/// La estela ya recorrida en este ciclo. +const ESTELA: u32 = 0x2E_50_C8; +/// La cabeza del compas — el indigo brillante del foco del compositor. +const CABEZA: u32 = 0x8B_5C_F6; + +/// Margen lateral de la pista, en pixeles. +const MARGEN: usize = 24; +/// Filas que ocupa la pista — un surco horizontal centrado en el lienzo. +const PISTA_Y0: usize = 46; +const PISTA_Y1: usize = 74; +/// Anchura de la cabeza del compas, en pixeles. +const CABEZA_ANCHO: usize = 7; + +/// El lienzo de la aplicacion, en SU propia memoria lineal. El kernel jamas lo +/// ve directamente: solo recibe el (ptr, len) que cada fotograma le entrega. +static mut LIENZO: [u32; ANCHO * ALTO] = [0; ANCHO * ALTO]; + +/// Preparacion: el kernel la invoca UNA sola vez, al cargar el modulo. Pinta el +/// primer fotograma — el compas en el instante de nacer. +#[no_mangle] +pub extern "C" fn init() { + pintar(); +} + +/// Un fotograma de trabajo: vuelve a pintar el compas en el instante actual y +/// RETORNA, cediendo la CPU al kernel y a las apps vecinas. +#[no_mangle] +pub extern "C" fn tick() { + pintar(); +} + +/// Pinta el compas en el instante ACTUAL. No guarda estado alguno: la escena es +/// una funcion pura del reloj del host. Por eso dos instancias de `pulso`, +/// nazcan cuando nazcan, laten EXACTAMENTE al unisono. +fn pintar() { + // SEGURIDAD: durante `init` y `tick` esta es la unica via de acceso a + // LIENZO, y el kernel jamas reentra el modulo mientras una de ellas corre. + let lienzo: &mut [u32] = unsafe { &mut *core::ptr::addr_of_mut!(LIENZO) }; + + // La fase del compas: 0 al inicio del periodo, casi 1 al final. Se deriva + // SOLO del reloj del host — ni un contador propio, ni un origen guardado. + // SEGURIDAD: `sys_tiempo_mono` es una capacidad del host; no toca memoria. + let tiempo = unsafe { sys_tiempo_mono() }; + let fase = tiempo % PERIODO; + + // El ancho util de la pista y hasta donde ha llegado la cabeza. + let util = ANCHO - 2 * MARGEN; + let avance = (fase as usize * util) / PERIODO as usize; + + // Fondo limpio. + for pixel in lienzo.iter_mut() { + *pixel = FONDO; + } + // La pista entera, tenue. + banda(lienzo, MARGEN, MARGEN + util, PISTA); + // La estela ya recorrida en este ciclo. + banda(lienzo, MARGEN, MARGEN + avance, ESTELA); + // La cabeza del compas: una barra brillante al frente de la estela. + let cabeza0 = MARGEN + avance; + banda(lienzo, cabeza0, cabeza0 + CABEZA_ANCHO, CABEZA); + + volcar(lienzo); +} + +/// Rellena la pista —las filas [`PISTA_Y0`, `PISTA_Y1`)— entre las columnas +/// [x0, x1) con un color, recortado con firmeza al ancho del lienzo. +fn banda(lienzo: &mut [u32], x0: usize, x1: usize, color: u32) { + let x0 = x0.min(ANCHO); + let x1 = x1.min(ANCHO); + let mut fila = PISTA_Y0; + while fila < PISTA_Y1 { + let base = fila * ANCHO; + let mut col = x0; + while col < x1 { + lienzo[base + col] = color; + col += 1; + } + fila += 1; + } +} + +/// Entrega el lienzo completo al kernel. El (ptr, len) apunta SIEMPRE dentro de +/// nuestra memoria lineal, y su tamaño es, exactamente, el de la region. +fn volcar(lienzo: &[u32]) { + // SEGURIDAD: `sys_render_frame` es una capacidad del host; el (ptr, len) + // describe nuestra propia memoria lineal y el host lo verifica sin piedad. + unsafe { + sys_render_frame(lienzo.as_ptr() as u32, (ANCHO * ALTO * 4) as u32); + } +} diff --git a/renaser/boot/src/main.rs b/renaser/boot/src/main.rs index 594c5a3..6a87aef 100644 --- a/renaser/boot/src/main.rs +++ b/renaser/boot/src/main.rs @@ -107,11 +107,13 @@ struct AppGenesis { region: (u32, u32, u32, u32), } -/// El userspace de genesis — las cinco aplicaciones que pueblan un disco recien -/// forjado. Un saludo (`hola`), la `memoriosa` interactiva que recuerda entre -/// sesiones (Fase 7c), y tres demos de los guardarrailes del kernel: `discola` -/// (combustible), `glotona` (memoria) y `cronista` (la cronica de los arranques). -const GENESIS: [AppGenesis; 5] = [ +/// El userspace de genesis — las seis aplicaciones que pueblan un disco recien +/// forjado. El compas visual `pulso` (Fase 11), un saludo (`hola`), la +/// `memoriosa` interactiva que recuerda entre sesiones (Fase 7c), y tres demos +/// de los guardarrailes del kernel: `discola` (combustible), `glotona` +/// (memoria) y `cronista` (la cronica de los arranques). +const GENESIS: [AppGenesis; 6] = [ + AppGenesis { nombre: "pulso", archivo: "pulso.wasm", region: (100, 120, 360, 120) }, AppGenesis { nombre: "hola", archivo: "app.wasm", region: (100, 120, 480, 560) }, AppGenesis { nombre: "memoriosa", archivo: "memoriosa.wasm", region: (700, 120, 360, 80) }, AppGenesis { nombre: "discola", archivo: "discola.wasm", region: (60, 700, 360, 80) }, diff --git a/renaser/kernel/assets/pulso.wasm b/renaser/kernel/assets/pulso.wasm new file mode 100755 index 0000000..ce66105 Binary files /dev/null and b/renaser/kernel/assets/pulso.wasm differ diff --git a/renaser/kernel/src/async_system/reloj.rs b/renaser/kernel/src/async_system/reloj.rs index 00320ab..ae905de 100644 --- a/renaser/kernel/src/async_system/reloj.rs +++ b/renaser/kernel/src/async_system/reloj.rs @@ -54,6 +54,13 @@ fn pulsos() -> u64 { CONTADOR_PULSOS.load(Ordering::Acquire) } +/// Milisegundos transcurridos desde el arranque (Fase 11). El temporizador late +/// a 100 Hz —un pulso cada 10 ms—; esta es la lectura del reloj MONOTONO que el +/// host ofrece al userspace como `sys_tiempo_mono`. Jamas retrocede. +pub fn milisegundos() -> u64 { + pulsos().saturating_mul(10) +} + /// Inscribe un waker en el censo de espera del proximo fotograma. fn inscribir(waker: Waker) { if let Some(censo) = EN_ESPERA.get() { diff --git a/renaser/kernel/src/wasm/env.rs b/renaser/kernel/src/wasm/env.rs index d7a7792..44c9557 100644 --- a/renaser/kernel/src/wasm/env.rs +++ b/renaser/kernel/src/wasm/env.rs @@ -13,7 +13,8 @@ // * sys_object_raiz — leer la raiz del grafo; // * sys_object_fijar_raiz — coronar un objeto como raiz; // * sys_estado_cargar — leer el estado persistido de la app (Fase 7c); -// * sys_estado_guardar — anclar el estado persistido de la app (Fase 7c). +// * sys_estado_guardar — anclar el estado persistido de la app (Fase 7c); +// * sys_tiempo_mono — leer el reloj monotono del sistema (Fase 11). // // GUARDARRAIL: el kernel valida MATEMATICAMENTE todo puntero que el modulo le // entrega contra los limites reales de su memoria lineal. No se confia en que @@ -442,5 +443,19 @@ pub(crate) fn enlazar_capacidades( }, )?; + // --- CAPACIDAD 10 :: sys_tiempo_mono() -> u64 --- + // El reloj MONOTONO del sistema: milisegundos transcurridos desde el + // arranque. Le da al userspace un sentido del tiempo independiente del + // ritmo de los fotogramas — una app sabe CUANTO ha pasado, no solo CUANTAS + // veces la han llamado—. Jamas retrocede. No toca la memoria del modulo: + // es una lectura pura, sin puntero que validar. + enlazador.func_wrap( + "renaser", + "sys_tiempo_mono", + |_caller: Caller<'_, ContextoCapacidades>| -> u64 { + crate::async_system::reloj::milisegundos() + }, + )?; + Ok(()) }