feat: integra renaser (kernel SASOS bare-metal) al monorepo

renaser —kernel asíncrono de espacio de direcciones único, no-POSIX,
`no_std` x86_64— entra al monorepo como su PROPIO workspace de Cargo,
no fusionado: usa toolchain nightly, target `x86_64-unknown-none` y
`panic = "abort"`, incompatibles con los perfiles globales de brahman.

- `renaser/` — copia del proyecto (sin su `.git`; el repo original
  conserva su historia standalone). Workspace propio con su
  `rust-toolchain.toml` y `.cargo/`.
- `exclude = ["renaser"]` en el workspace de brahman: Cargo lo trata
  como ajeno.
- El kernel de renaser path-depende `mirada-layout` cruzando la
  frontera de workspace — primer núcleo compartido. Semilla de la
  Fase 8 (compositor): geometría de teselado compartida, framebuffer
  nativo de renaser; smithay se queda en el lado Linux.

Verificado: `cargo build -p boot` compila kernel + imagen UEFI con
mirada-layout enlazado para bare-metal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-22 14:37:14 +00:00
parent 1c6aafbc24
commit e2272c0ed3
55 changed files with 6668 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "cronista"
version = "0.1.0"
+31
View File
@@ -0,0 +1,31 @@
# =============================================================================
# renaser :: apps/cronista — Fase 6.1c :: el primer escriba del userspace
# -----------------------------------------------------------------------------
# Un modulo WebAssembly que, a diferencia de sus vecinas, deja HUELLA. En cada
# arranque graba un objeto en el grafo persistente del kernel —enlazado al del
# arranque anterior, formando una cadena— y lo corona como raiz. Asi lleva la
# cronica de cuantas veces ha despertado el sistema: una cuenta que sobrevive
# a los reinicios porque vive en el disco, no en la RAM.
# Tiene su propio `[workspace]`: queda fuera del espacio de trabajo del kernel.
# =============================================================================
[package]
name = "cronista"
version = "0.1.0"
edition = "2021"
description = "renaser :: app WASM cronista — la cronica persistente de los arranques"
[workspace]
# `cdylib` produce un modulo `.wasm` que exporta funciones — el formato que
# wasmi instancia.
[lib]
crate-type = ["cdylib"]
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
+234
View File
@@ -0,0 +1,234 @@
// =============================================================================
// renaser :: apps/cronista — Fase 6.1c :: el primer escriba del userspace
// -----------------------------------------------------------------------------
// Las apps de fases anteriores eran efimeras: su mundo se borraba al apagar la
// maquina. `cronista` es la primera que deja HUELLA. En cada arranque:
//
// 1. pregunta al kernel por la RAIZ del grafo de objetos —la cabeza de la
// cadena de arranques anteriores—;
// 2. lee de ella el numero del ultimo arranque;
// 3. graba un objeto nuevo —sus datos: el numero de arranque; su hijo: la
// raiz anterior— y lo corona como raiz;
// 4. recorre la cadena entera para verificar que el DAG persiste integro;
// 5. pinta una celda por cada arranque registrado.
//
// La cuenta NO vive en la RAM: vive en el disco, en el grafo direccionado por
// contenido. Sobrevive a los reinicios. Cada vez que renaser despierta, la
// cronista añade un eslabon a la cadena y una celda a su rejilla.
//
// Sus unicas vias hacia el mundo son las capacidades `sys_object_*` que el
// kernel le inyecta. No conoce el disco, ni el bus PCI, ni el formato en
// sectores: solo objetos, hashes y aristas.
// =============================================================================
#![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);
/// Graba un objeto en el grafo: `datos` es su carga util; `hijos` apunta a
/// un arreglo de `hijos_cnt` hashes de 32 bytes —las aristas—. El hash
/// resultante se escribe en `salida`. Devuelve 0 si todo fue bien.
fn sys_object_put(datos: u32, datos_len: u32, hijos: u32, hijos_cnt: u32, salida: u32) -> i32;
/// Copia la carga util del objeto `hash` en `salida`. Devuelve el numero de
/// bytes copiados, o un valor negativo si fallo.
fn sys_object_datos(hash: u32, salida: u32, capacidad: u32) -> i32;
/// Devuelve el numero de hijos del objeto `hash` y, si `indice` es valido,
/// escribe el hash de ese hijo en `salida`. Negativo si el objeto no existe.
fn sys_object_hijo(hash: u32, indice: u32, salida: u32) -> i32;
/// Escribe en `salida` el hash de la raiz del grafo. Devuelve 1 si hay
/// raiz, 0 si el grafo aun esta vacio.
fn sys_object_raiz(salida: u32) -> i32;
/// Corona el objeto `hash` como raiz del grafo. Devuelve 0 si lo logro.
fn sys_object_fijar_raiz(hash: u32) -> i32;
}
/// 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 = 80;
/// Lado de paso de la rejilla de celdas, en pixeles.
const PASO: usize = 20;
/// Lado de una celda, en pixeles.
const LADO: usize = 16;
/// Celdas que caben en una fila.
const POR_FILA: usize = ANCHO / PASO;
/// Celdas que caben en la rejilla entera — el techo de lo que se pinta.
const MAX_CELDAS: usize = POR_FILA * (ALTO / PASO);
/// Indigo casi negro: el fondo del lienzo de la cronista.
const FONDO: u32 = 0x0E_14_22;
/// Ambar calido: una celda por arranque registrado.
const CELDA: u32 = 0xF2_B2_33;
/// Verde: el DAG se recorrio integro de la raiz al primer eslabon.
const VERDE: u32 = 0x35_C4_6A;
/// Rojo: la cadena se rompio — un objeto no resolvio.
const ROJO: u32 = 0xD4_1E_2C;
/// El lienzo de la aplicacion, en SU propia memoria lineal.
static mut LIENZO: [u32; ANCHO * ALTO] = [0; ANCHO * ALTO];
// --- Buferes de intercambio con las capacidades `sys_object_*`. El kernel lee
// y escribe hashes y datos AQUI, siempre dentro de esta memoria lineal. ---
/// El hash de la raiz anterior — la cabeza de la cadena al arrancar.
static mut HASH_RAIZ: [u8; 32] = [0; 32];
/// El hash del objeto que esta cronista graba en este arranque.
static mut HASH_NUEVO: [u8; 32] = [0; 32];
/// Hash de trabajo para recorrer la cadena del DAG.
static mut HASH_AUX: [u8; 32] = [0; 32];
/// Los ocho bytes del numero de arranque, de ida y de vuelta del grafo.
static mut DATOS_IO: [u8; 8] = [0; 8];
/// Preparacion: el kernel la invoca UNA sola vez. Aqui ocurre toda la cronica —
/// leer el grafo, grabar el eslabon nuevo, verificar el DAG y pintar.
#[no_mangle]
pub extern "C" fn init() {
// 1. ¿Hay ya una raiz? Es la cabeza de la cadena de arranques anteriores.
let tiene_raiz = unsafe { sys_object_raiz(core::ptr::addr_of_mut!(HASH_RAIZ) as u32) } == 1;
// 2. El numero del ultimo arranque vive en los `datos` de esa raiz.
let mut previo: u64 = 0;
if tiene_raiz {
let leidos = unsafe {
sys_object_datos(
core::ptr::addr_of!(HASH_RAIZ) as u32,
core::ptr::addr_of_mut!(DATOS_IO) as u32,
8,
)
};
if leidos == 8 {
// SEGURIDAD: lectura de un escalar `Copy` de un estatico propio.
previo = u64::from_le_bytes(unsafe { *core::ptr::addr_of!(DATOS_IO) });
}
}
let cuenta = previo + 1;
// 3. Grabar el objeto de ESTE arranque. Sus `datos` son el numero de
// arranque; su unico hijo, la raiz anterior — el eslabon nuevo del DAG.
// SEGURIDAD: escritura de un escalar `Copy` a un estatico propio.
unsafe {
*core::ptr::addr_of_mut!(DATOS_IO) = cuenta.to_le_bytes();
}
let (hijos_ptr, hijos_cnt) = if tiene_raiz {
(core::ptr::addr_of!(HASH_RAIZ) as u32, 1u32)
} else {
(0u32, 0u32)
};
let grabado = unsafe {
sys_object_put(
core::ptr::addr_of!(DATOS_IO) as u32,
8,
hijos_ptr,
hijos_cnt,
core::ptr::addr_of_mut!(HASH_NUEVO) as u32,
)
};
// 4. Coronar el objeto nuevo como raiz y verificar la integridad del DAG.
let mut integro = false;
if grabado == 0
&& unsafe { sys_object_fijar_raiz(core::ptr::addr_of!(HASH_NUEVO) as u32) } == 0
{
integro = verificar_cadena(cuenta);
}
// 5. Pintar la cronica: una celda por arranque, un testigo de integridad.
pintar(cuenta, integro);
}
/// Un fotograma de trabajo. El numero de arranque no cambia durante una sesion:
/// la cronica que `init` pinto persiste en el lienzo del kernel. `tick` solo
/// cede el control, fiel al ABI cooperativo — no toda app necesita redibujar.
#[no_mangle]
pub extern "C" fn tick() {}
/// Recorre la cadena del DAG desde el objeto recien grabado, descendiendo por
/// el hijo 0, y comprueba que su profundidad coincide con el numero de
/// arranque. Si coincide, el grafo entero se leyo de vuelta del disco integro.
fn verificar_cadena(cuenta: u64) -> bool {
// Partir del objeto recien grabado.
// SEGURIDAD: copia de un arreglo `Copy` entre dos estaticos propios.
unsafe {
*core::ptr::addr_of_mut!(HASH_AUX) = *core::ptr::addr_of!(HASH_NUEVO);
}
let mut profundidad: u64 = 0;
loop {
profundidad += 1;
// `sys_object_hijo` lee el hash de HASH_AUX y, si hay hijo, escribe el
// del hijo 0 en el MISMO bufer: la cadena desciende un eslabon.
let hijos = unsafe {
sys_object_hijo(
core::ptr::addr_of!(HASH_AUX) as u32,
0,
core::ptr::addr_of_mut!(HASH_AUX) as u32,
)
};
// Sin hijos: fin de la cadena — el primer arranque de todos. Un valor
// negativo seria un objeto que no resolvio: la cadena estaria rota.
if hijos <= 0 || profundidad >= 4096 {
break;
}
}
profundidad == cuenta
}
/// Pinta la cronica: el fondo, una celda ambar por arranque y, en la esquina,
/// el testigo de integridad del grafo.
fn pintar(cuenta: u64, integro: bool) {
// SEGURIDAD: durante `init` esta es la unica via de acceso a LIENZO, y el
// kernel jamas reentra el modulo mientras `init` corre.
let lienzo: &mut [u32] = unsafe { &mut *core::ptr::addr_of_mut!(LIENZO) };
for pixel in lienzo.iter_mut() {
*pixel = FONDO;
}
// Una celda ambar por cada arranque registrado, dispuestas en rejilla.
let celdas = (cuenta as usize).min(MAX_CELDAS);
for i in 0..celdas {
let x = (i % POR_FILA) * PASO + 2;
let y = (i / POR_FILA) * PASO + 2;
rellenar(lienzo, x, y, LADO, LADO, CELDA);
}
// El testigo de integridad: verde si la cadena se recorrio entera de la
// raiz al primer eslabon, rojo si algo se rompio.
let testigo = if integro { VERDE } else { ROJO };
rellenar(lienzo, ANCHO - 14, 4, 10, 10, testigo);
// 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);
}
}
/// Rellena un rectangulo, recortado con firmeza a los limites del lienzo.
fn rellenar(lienzo: &mut [u32], x: usize, y: usize, ancho: usize, alto: usize, color: u32) {
let x1 = (x + ancho).min(ANCHO);
let y1 = (y + alto).min(ALTO);
let mut fila = y;
while fila < y1 {
let base = fila * ANCHO;
let mut col = x;
while col < x1 {
lienzo[base + col] = color;
col += 1;
}
fila += 1;
}
}
+7
View File
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "discola"
version = "0.1.0"
+30
View File
@@ -0,0 +1,30 @@
# =============================================================================
# renaser :: apps/discola — el inquilino discolo del userspace
# -----------------------------------------------------------------------------
# Un modulo WebAssembly construido a proposito para portarse mal: su `tick`
# cae en un bucle cerrado y jamas retorna. Existe para una sola cosa —
# demostrar que el escudo de combustible (fuel) del kernel `renaser` lo
# desaloja sin colgar el sistema ni perturbar a sus apps vecinas.
# Tiene su propio `[workspace]`: queda fuera del espacio de trabajo del kernel.
# =============================================================================
[package]
name = "discola"
version = "0.1.0"
edition = "2021"
description = "renaser :: app WASM discola — un bucle infinito que el fuel fulmina"
[workspace]
# `cdylib` produce un modulo `.wasm` que exporta funciones — el formato que
# wasmi instancia.
[lib]
crate-type = ["cdylib"]
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
+46
View File
@@ -0,0 +1,46 @@
// =============================================================================
// renaser :: apps/discola — Fase 5 :: el inquilino discolo del userspace
// -----------------------------------------------------------------------------
// Esta aplicacion esta MAL a proposito. Su `tick` no hace un fotograma de
// trabajo y retorna —como manda el ABI cooperativo—: cae en un bucle cerrado
// y jamas devuelve el control. En un sistema cooperativo ingenuo, eso colgaria
// la maquina entera.
//
// Pero renaser ejecuta cada `tick` con un presupuesto estricto de COMBUSTIBLE.
// Cuando este bucle lo agota, el runtime `wasmi` lanza una trampa, el kernel
// recupera el mando y desaloja a este modulo — tatuando su region de purpura.
// El resto del userspace ni se entera. Eso es lo que esta app demuestra.
// =============================================================================
#![no_std]
/// Sin sistema operativo bajo nosotros, un panico solo puede detenerse en seco.
#[panic_handler]
fn al_fallar(_: &core::panic::PanicInfo) -> ! {
loop {}
}
/// Sumidero de las escrituras del bucle: obliga al compilador a CONSERVAR cada
/// iteracion —y, con ella, su consumo de combustible— en lugar de elidirla.
static mut SUMIDERO: u64 = 0;
/// Preparacion. No hay nada honrado que preparar: el kernel la invoca, retorna
/// sin incidentes, y la app pasa por buena... hasta su primer `tick`.
#[no_mangle]
pub extern "C" fn init() {}
/// El fotograma que nunca termina. Un bucle cerrado, deliberado: jamas retorna.
/// El kernel `renaser` lo cortara por agotamiento de combustible y desalojara
/// esta aplicacion sin que el sistema sufra un solo sobresalto.
#[no_mangle]
pub extern "C" fn tick() {
let mut contador: u64 = 0;
loop {
contador = contador.wrapping_add(1);
// SEGURIDAD: escritura volatil a un escalar estatico; su unico fin es
// que el optimizador no pueda vaciar el bucle. No se crea referencia.
unsafe {
core::ptr::write_volatile(core::ptr::addr_of_mut!(SUMIDERO), contador);
}
}
}
+7
View File
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "glotona"
version = "0.1.0"
+30
View File
@@ -0,0 +1,30 @@
# =============================================================================
# renaser :: apps/glotona — el inquilino voraz del userspace
# -----------------------------------------------------------------------------
# Un modulo WebAssembly construido a proposito para devorar memoria: su `tick`
# reclama paginas de memoria lineal sin freno. Existe para demostrar que el
# techo ESPACIAL del kernel `renaser` lo desaloja —baliza amarilla— sin agotar
# el heap del sistema ni perturbar a sus apps vecinas.
# Tiene su propio `[workspace]`: queda fuera del espacio de trabajo del kernel.
# =============================================================================
[package]
name = "glotona"
version = "0.1.0"
edition = "2021"
description = "renaser :: app WASM glotona — devora memoria; el techo espacial la frena"
[workspace]
# `cdylib` produce un modulo `.wasm` que exporta funciones — el formato que
# wasmi instancia.
[lib]
crate-type = ["cdylib"]
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
+39
View File
@@ -0,0 +1,39 @@
// =============================================================================
// renaser :: apps/glotona — Fase 6.0 :: el inquilino voraz del userspace
// -----------------------------------------------------------------------------
// Esta aplicacion esta MAL a proposito, como su hermana `discola` — pero en la
// otra dimension. Donde `discola` devora TIEMPO (un bucle sin fin), `glotona`
// devora ESPACIO: su `tick` invoca `memory.grow` reclamando memoria lineal sin
// freno alguno.
//
// renaser le impone a cada modulo un techo de memoria. Cuando esta peticion lo
// rebasa, el runtime `wasmi` —configurado para ello— lanza una trampa en vez
// de devolver un discreto -1; el kernel la captura, desaloja a este modulo y
// tiñe su region de amarillo palido. El heap del sistema jamas corre peligro.
// =============================================================================
#![no_std]
/// Sin sistema operativo bajo nosotros, un panico solo puede detenerse en seco.
#[panic_handler]
fn al_fallar(_: &core::panic::PanicInfo) -> ! {
loop {}
}
/// Preparacion. Nada honrado que preparar: la app pasa por buena hasta su
/// primer `tick`, igual que su hermana `discola`.
#[no_mangle]
pub extern "C" fn init() {}
/// El fotograma voraz. Reclama 4096 paginas de memoria lineal de un golpe —
/// 256 MiB, muy por encima de cualquier techo razonable. El kernel `renaser`
/// denegara la expansion con una trampa y desalojara esta aplicacion.
#[no_mangle]
pub extern "C" fn tick() {
// `memory_grow` sobre la memoria 0. Con el techo espacial activo y la
// denegacion configurada como trampa, esta instruccion NO retorna: aborta.
let _ = core::arch::wasm32::memory_grow(0, 4096);
// Red de seguridad: si la denegacion no fuese una trampa, este bucle
// cerrado garantiza el desalojo —por combustible— de todos modos.
loop {}
}
+7
View File
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "hello_wasm"
version = "0.1.0"
+28
View File
@@ -0,0 +1,28 @@
# =============================================================================
# renaser :: apps/hello_wasm — primera aplicacion del userspace aislado
# -----------------------------------------------------------------------------
# No es un ELF: es un modulo WebAssembly puro. Se compila para wasm32 y el
# kernel lo ejecuta dentro de wasmi, encerrado en su propia memoria lineal.
# Tiene su propio `[workspace]`: queda fuera del espacio de trabajo del kernel.
# =============================================================================
[package]
name = "hello_wasm"
version = "0.1.0"
edition = "2021"
description = "renaser :: app WASM de prueba — un cuadrado movil dirigido por teclado"
[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
+140
View File
@@ -0,0 +1,140 @@
// =============================================================================
// renaser :: apps/hello_wasm — Fase 4/5 :: el primer ciudadano del userspace
// -----------------------------------------------------------------------------
// Esta aplicacion vive DENTRO de su propia memoria lineal de WebAssembly. No
// conoce la MMU, no conoce los anillos de privilegio de la CPU: su unica via
// hacia el mundo son las dos capacidades que el kernel le inyecta. Lo que no
// este importado, sencillamente, no tiene camino fisico que recorrer.
//
// FASE 5 :: el ABI deja de ser un `run()` que se queda dentro para siempre.
// Ahora la app exporta `init()` —preparacion, una sola vez— y `tick()` —un
// fotograma de trabajo, y RETORNA—. Ese retorno es el punto de cesion
// cooperativa: el kernel recupera el control y atiende a las demas apps.
// =============================================================================
#![no_std]
// --- Las dos UNICAS capacidades que el kernel `renaser` expone al modulo. ---
#[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);
/// Devuelve el ultimo scancode crudo del teclado, o 0 si no hay ninguno.
fn sys_get_scancode() -> u32;
}
/// 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: el host rechaza cualquier fotograma de
// un tamaño que no sea, exactamente, el de su ventana. ---
const ANCHO: usize = 480;
const ALTO: usize = 560;
const LADO: usize = 96;
const PASO: i32 = 24;
/// Azul nocturno: el fondo del lienzo de la aplicacion.
const FONDO: u32 = 0x0A_18_30;
/// Ambar: el cuadrado que el usuario gobierna.
const CUADRO: u32 = 0xFF_B0_00;
/// 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];
/// Posicion del cuadrado. Vive entre fotogramas en la memoria lineal del modulo
/// — el estado persiste porque la instancia, en la Fase 5, ya no es efimera.
static mut POS_X: i32 = 0;
static mut POS_Y: i32 = 0;
/// Preparacion: el kernel la invoca UNA sola vez, al cargar el modulo. Pinta el
/// fondo, centra el cuadrado y vuelca el primer fotograma.
#[no_mangle]
pub extern "C" fn init() {
// 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) };
for pixel in lienzo.iter_mut() {
*pixel = FONDO;
}
let x = (ANCHO / 2 - LADO / 2) as i32;
let y = (ALTO / 2 - LADO / 2) as i32;
rellenar(lienzo, x, y, CUADRO);
// SEGURIDAD: escritura de escalares `Copy`; no se crea referencia alguna.
unsafe {
POS_X = x;
POS_Y = y;
}
volcar(lienzo);
}
/// Un fotograma de trabajo: escucha el teclado, mueve el cuadrado, vuelca la
/// imagen y RETORNA. El retorno cede la CPU al kernel y a las apps vecinas.
#[no_mangle]
pub extern "C" fn tick() {
let lienzo: &mut [u32] = unsafe { &mut *core::ptr::addr_of_mut!(LIENZO) };
let (mut x, mut y) = unsafe { (POS_X, POS_Y) };
// 1. Escuchar al teclado a traves de la capacidad del host.
let (dx, dy) = match unsafe { sys_get_scancode() } {
0x11 => (0, -PASO), // tecla W -> arriba
0x1F => (0, PASO), // tecla S -> abajo
0x1E => (-PASO, 0), // tecla A -> izquierda
0x20 => (PASO, 0), // tecla D -> derecha
_ => (0, 0),
};
// 2. Borrar el cuadrado anterior repintando su hueco con el fondo.
rellenar(lienzo, x, y, FONDO);
// 3. Moverlo, manteniendolo siempre dentro del lienzo.
x = (x + dx).clamp(0, (ANCHO - LADO) as i32);
y = (y + dy).clamp(0, (ALTO - LADO) as i32);
// 4. Dibujar el cuadrado en su nueva posicion y guardar el estado.
rellenar(lienzo, x, y, CUADRO);
unsafe {
POS_X = x;
POS_Y = y;
}
// 5. Volcar el fotograma: el host lo compondra dentro de nuestra region.
volcar(lienzo);
}
/// 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);
}
}
/// Rellena un cuadrado de lado `LADO`, con su esquina en (x, y), recortado con
/// firmeza a los limites del lienzo.
fn rellenar(lienzo: &mut [u32], x: i32, y: i32, color: u32) {
let x0 = x.max(0) as usize;
let y0 = y.max(0) as usize;
let x1 = (x0 + LADO).min(ANCHO);
let y1 = (y0 + LADO).min(ALTO);
let mut fila = y0;
while fila < y1 {
let base = fila * ANCHO;
let mut col = x0;
while col < x1 {
lienzo[base + col] = color;
col += 1;
}
fila += 1;
}
}