Commit Graph

448 Commits

Author SHA1 Message Date
Sergio 906e5f639c gioser-graph: edges behind nodes, variable thickness by raw weight
- Edges and nodes now in separate SVG <g> groups (edges first = behind)
- Stroke width: 0.6 + w*4.0 instead of normalized range (more visible variation)
- Brightness: uses raw weight directly, not normalized against max
- Edges group has no breathing animation (only nodes breathe)
2026-05-23 15:53:00 +00:00
Sergio ef3d698c4b gioser-graph: edge colors blend from connected node caminos
- Each edge color is a 50/50 blend of its two nodes' camino colors
- Extra brightness proportional to weight (higher weight = closer to white)
- Edge opacity/width also varies by weight
- Edges drawn before nodes in SVG (already behind them)
2026-05-23 15:51:21 +00:00
Sergio 2588673caf gioser-graph: fix panic slicing multi-byte chars (á, ñ, etc)
- Use .chars().take(18) instead of byte slicing &node.name[..18]
  which panics on accented characters
2026-05-23 15:42:32 +00:00
Sergio fa2bedf851 gioser-graph: fix edge UUID mapping, index 4 docs, unify page bg
- Fix: map positions by node.id (UUID) not doc_id — edges now draw
- Index the 4 docs/ files into Qdrant (15 fragments via index-gioser-docs.py)
- Page background: single smooth radial-gradient per element (no color
  divisions), animated 'page-breathe' — opacity pulses 0.35↔0.80
- Graph CSS: 'graph-breathe' 5s opacity animation (feels alive)
2026-05-23 15:40:42 +00:00
Sergio b5032de1e3 gioser-graph: grid layout 3 cols, line brightness by weight, breathing animation
- Replaced force-directed layout with explicit grid: 3 columns wide,
  rows based on node count (minimum 3 visual rows)
- Nodes are larger: 170x44px with 8px radius
- Lines: color brightness + stroke opacity based on normalized weight
  (weight 1.0 → white #ffff, 0.5 → dimmer rgb)
- CSS animation 'graph-breathe': opacity pulses slowly (4.2s ease-in-out)
- Hover: drop-shadow glow + fill-opacity increase
- Glow circle behind each node (subtle)
2026-05-23 15:34:54 +00:00
Sergio 7f7ba1fef9 gioser-web: revert Cytoscape, back to SVG gioser-graph-web
- Remove cytoscape-graph.js (wasn't working with hidden deck)
- Restore gioser-graph-web Rust SVG widget with gioser-web dep
- Rebuild WASM (514K)
2026-05-23 15:28:41 +00:00
Sergio 24218447e3 gioser-graph: poll container width before Cytoscape init
- Uses setInterval(50ms) waiting for offsetWidth > 0
- Falls back to minWidth/minHeight after 10s timeout
- Solves 'cannot access property w' when deck is hidden (scale 0)
2026-05-23 15:25:45 +00:00
Sergio 9db79617eb gioser-graph: wrap init in requestAnimationFrame for hidden deck
- Wraps the entire fetch+render in requestAnimationFrame
  so the container has real size before Cytoscape runs cose layout
- Uses 'preset' layout first, then cose with animate:'end'
  to play nice with 0-size containers during deck animation
2026-05-23 15:24:15 +00:00
Sergio a908d8420c gioser-web: fix graph not appearing — use MutationObserver
- cytoscape-graph.js now uses MutationObserver, not DOMContentLoaded
  (the <gioser-graph> element is created dynamically by WASM)
- Remove unused dispatchEvent from lib.rs
- Rebuild WASM
2026-05-23 15:22:13 +00:00
Sergio d4c31d70b7 gioser-web: replace Rust SVG graph with Cytoscape.js
- Add cytoscape-graph.js: fetches /graph, renders with Cytoscape.js
- Style: round-rect nodes, cose layout, edge width proportional to weight
- Click: center node + fade rest (wineandcheesemap effect)
- Double-click: trigger navigation callback
- Hover: tooltip with preview text
- Click background: restore all
- Remove gioser-graph-web crate dependency (no longer needed)
- Add CDN cytoscape@3.30.4 + defer script to index.html
- gioser-graph custom element auto-initialized on mount
2026-05-23 15:17:28 +00:00
Sergio 529287f01d gioser-web: fix graph widget — rect nodes, edge weight, CSS anim, layout
- Switch from circles to horizontal rounded rectangles with text inside
- Text size 12px body + 8px sublabel (camino), no overlaps
- Edge stroke-width proportional to semantic weight
- Fix 'Layout was forced' warning
- Reduce CSS page-ambience animations: only opacity (no transform)
  to fix 'breathing background' visual glitch
- Layout: more separation (k*1.6), 80 iterations
2026-05-23 15:12:48 +00:00
Sergio 8235391add gioser-web: rebuild WASM with graph widget integration
- Fix missing reader declaration in spawn_local
- Rebuild pkg/ with wasm-bindgen 0.2.121
- Graph widget now appears below page content (SVG force-directed)
2026-05-23 14:57:13 +00:00
Sergio 7a53fea13c gioser-graph-web: fix web-sys feature casing and compilation
- SvgSvgElement → SvgsvgElement
- Add Response feature
- Make color owned String for closure lifetime
2026-05-23 14:49:26 +00:00
Sergio cb5d83b1f7 gioser-web: integrate gioser-graph-web widget below page content
- Add gioser-graph-web dependency to gioser-web
- After markdown loads, mount SVG semantic graph below content
- Graph fetches from api.gioser.net/graph endpoint
- Uses Qdrant k-NN edges, colored by camino
- Callback navigation placeholder (will be wired in next commit)
2026-05-23 14:40:35 +00:00
Sergio 38e95e0620 gioser-web: add gioser-graph-web module for SVG semantic graph
- New crate: gioser-graph-web (WASM widget)
- Fetches /graph from the gioser API
- Force-directed layout (Fruchterman-Reingold) in Rust
- SVG inline rendering: nodes clickable, colored by camino
- Agnostic container: caller provides div + callback
2026-05-23 14:38:42 +00:00
Sergio b17149c528 gioser-web: add docs/ dir with frontmatter for Qdrant indexing
- 4 md files (aire, fuego, tierra, agua) with YAML frontmatter
- caminos mapped: logos, nomos, kay, uku
- original md/ unchanged
- add scripts/index-gioser-docs.py (adapted from gioserv)
2026-05-23 14:36:20 +00:00
sergio 12e3b1d4d0 docs(renaser): plan de la Fase 21 — Atlas, el explorador del grafo
Plan trazado para mañana. Tres capas:

  1. Cuatro capacidades de host read-only (sys_grafo_manifiesto,
     sys_grafo_raiz, sys_grafo_recuperar, sys_grafo_hijo) que abren
     el grafo de objetos al userspace. Mismo patron de validacion
     de memoria que sys_net_*.

  2. La app 'atlas': lienzo ~520x400, lee el grafo perezosamente con
     cache LRU de 16 entradas, navega con flechas / Enter / Backspace
     desde el manifiesto hacia los hijos.

  3. Representacion radial: foco central con su firma cromatica
     (3 primeros bytes del hash), hijos en circulo, padre en cima
     si hay historial, cartela inferior con hash completo + tamaño
     + previsualizacion (texto si pasa el test ASCII, hex si no).

Subfases 21a (caps), 21b (app navegable), 21c (paseo guiado).

Sinergias y mejoras del sistema documentadas: dedup visual del
grafo direccionado por contenido, integridad por uso (cada
navegacion rehashea), validacion del almacen, camino natural a la
escritura (Fase 22), encaje con el faro Akasha de la Fase 20
(quien recibe AnunciarRaiz puede explorar la raiz del par).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 05:24:35 +00:00
sergio 42fee6fcbc feat(renaser): Fase 20 — Akasha Over Ether (grafo distribuido)
Tres mensajes y un EtherType propio bastan para extender el grafo de
objetos —direccionado por contenido, ya BLAKE3— a otras maquinas
renaser que escuchen en la misma red de capa-2. Sin TCP, sin IP,
sin DNS.

Crate nueva 'akasha/' (no_std compartido, gemela de 'formato',
excluida del workspace):

  - MensajeAkasha enum con SolicitarObjeto(id), ProveedorObjeto(id,
    payload), AnunciarRaiz(id).
  - Codec: postcard (mismo que ya usa el grafo en disco).
  - EtherType: 0x88B5. MAX_PAYLOAD_AKASHA = 1486 (MTU sin fragmentar).
  - Helpers componer_frame(src, dst, msg) y analizar_frame(bytes) que
    distinguen EtherType ajeno, frame truncado y payload basura.
  - 6 pruebas unitarias en verde.

Modulo nuevo 'kernel/src/akasha.rs' con tres oficios:

  1. Demuxer (drenar_y_demultiplexar): drena la cola RX del dispositivo
     virtio-net y demultiplexa: frames AoE con payload valido los
     procesa el respondedor; el resto va a una cola del userspace que
     'sys_net_recibir' ahora lee. Frames 0x88B5 con payload
     no-postcard (saludo de pregon) se cuentan y tambien viajan al
     userspace.

  2. Atencion de mensajes (procesar):
     - SolicitarObjeto(id): consulta almacen::recuperar; si tenemos el
       objeto, respondemos ProveedorObjeto unicast con objeto.serializar()
       y re-hashing de defensa en profundidad.
     - ProveedorObjeto(id, payload): verifica blake3(payload)==id antes
       de absorber con almacen::almacenar.
     - AnunciarRaiz(id): si ignoramos el nodo, le solicitamos al emisor.

  3. Faro periodico (difundir_raiz cada 5 s): broadcast del hash del
     manifiesto actual. Cadencia medida contra reloj::milisegundos(),
     no contra los awaits — el interprete wasmi de los apps degrada
     la cadencia de EsperaFrame::await a varios cientos de ms, asi
     que se mide contra el reloj monotono y los oficios per-fotograma
     se enganchan al tic del compositor (cuyo latido es fiable).

Contadores ResumenAkasha (rx/tx por variante, descartados, cola del
usuario) listos para un futuro indicador AoE en la barra de tareas.

Cambios complementarios:

  - sys_net_recibir lee de akasha::pop_usuario, no de
    drivers::red::recibir_en (que queda #[allow(dead_code)] como
    primitiva del driver para diagnostico).
  - tarea_red queda corta: envia un ARP al gateway y termina. El
    demuxer y el faro viven en el tic del compositor.

Verificacion:

  - 'cargo test -p akasha' → 6 pruebas en verde.
  - QEMU headless 60 s con -object filter-dump → 14 frames: 11
    AnunciarRaiz (Δ promedio 5.86 s sobre 5.00 s de target), 2 ARP
    y el pregon hello. Cada AnunciarRaiz lleva el hash del manifiesto
    '2f3deadfcc7dae25..' en 33 bytes postcard sobre 47 bytes de frame.
  - COM1 vuelca 'akasha :: ANUNCIO emitido :: raiz=2f3deadfcc7dae25..'
    en cada disparo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 05:14:43 +00:00
sergio 07ab095d42 feat(renaser): Fase 19 — voz del userspace hacia la red (pregon)
Tres capacidades nuevas en wasm/env (12-14):

- sys_net_mac(salida) -> i32: escribe los seis bytes del MAC del
  dispositivo. 0 OK, -1 si no hay red.
- sys_net_enviar(ptr, len) -> i32: envia un frame Ethernet crudo.
  Valida rango contra la memoria lineal del modulo.
- sys_net_recibir(salida, capacidad) -> i32: drena UN paquete por
  llamada hacia el buffer del modulo. Devuelve los bytes copiados, 0
  si nada pendiente, codigos negativos diagnosticos.

Añadida red::recibir_en(buf) -> usize como su contraparte del driver:
gemelo cooperativo de drenar_rx que aterriza en un buffer del usuario.

App nueva pregon (apps/pregon/, 4.2 KiB WASM): lienzo 480x160, tipografia
8x8 (font8x8) escalada x2. Al init pide su MAC y anuncia su presencia
con un broadcast Ethernet — destino FF:FF:FF:FF:FF:FF, EtherType
experimental 0x88B5, payload ASCII 'renaser :: hola desde mi red'. En
cada tick drena un paquete con sys_net_recibir y muestra el titulo, el
MAC propio, las cuentas TX/RX, y los datos del ultimo frame entrante.

GENESIS 8 -> 9 apps (pregon en posicion 2 detras de bitacora);
CELDA_TASKBAR_ANCHO 130 -> 116 px para que las nueve pestañas + lanzador
+ reloj caben holgadas en 1280 px.

tarea_red del kernel ya no drena RX (la cola pertenece al userspace),
conserva solo el envio del ARP de prueba al arrancar.

Verificada en QEMU con -object filter-dump. El pcap captura tres frames
en orden: (1) broadcast 88B5 de pregon con su payload, (2) ARP request
del kernel, (3) ARP reply del gateway 52:55:0a:00:02:02. La consola
anuncia 'manifiesto :: 9 apps nacidas del grafo'.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 04:26:22 +00:00
sergio b1be94e7c4 fix(install-arje): fuentes (DejaVu/Cantarell/Noto) en runtime libs
GPUI panic-ea si no encuentra ninguna fuente del fallback
(.SystemUIFont → Helvetica → Cantarell → DejaVu Sans → ...).
Fedora minimal no trae fuentes — sin esto el greeter crash al
inicializar text_system.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 04:11:57 +00:00
sergio 54f51ad2f8 fix(install-arje): vulkan-loader + lavapipe + seatd en runtime libs
GPUI (mirada-greeter) requiere libvulkan.so.1 al iniciar; sin un ICD
no levanta. mesa-vulkan-drivers trae lavapipe (Vulkan por software vía
llvmpipe), apto para VPS sin GPU real.

También se agrega seatd al hint (preferido sobre noop para producción
porque maneja VT-switching correctamente).

El check de runtime ahora detecta libEGL, libvulkan, libgbm y libseat.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 04:10:38 +00:00
sergio bdd088b89e feat(renaser): Fase 18 — red: virtio-net y el primer hola al exterior
renaser hablaba consigo mismo. Esta fase abre una boca y una oreja al
exterior con una tarjeta de red, reutilizando el `KernelHal` del
disco y el mapeador MMIO (la pieza estructural que hizo esto posible).

- `drivers/red`: monta `VirtIONet<KernelHal, PciTransport, 16>`,
  expone `enviar(frame)` y `drenar_rx(callback)`. Sin pila TCP/IP —
  solo Ethernet crudo; la composición de paquetes la hace el llamante.
- `componer_arp_request(mac, ip, objetivo)` construye el saludo
  inicial: «¿quien tiene 10.0.2.2?» dirigido al gateway de QEMU.
- `interrupts::registrar_irq_red` + handler `irq_red`, gemelo del de
  disco. La IRQ del dispositivo activa `red::atender_irq`, que hace
  `ack_interrupt` y suelta la línea.
- `tarea_red` en el reactor: al arrancar envía el ARP, después cada
  fotograma drena la cola RX y vuelca cada paquete a COM1.
- QEMU args ganan `-netdev user,id=net0 -device virtio-net-pci`.

Verificado con `-object filter-dump,...,file=/tmp/red.pcap`:
  red :: virtio-net :: MAC 52:54:00:12:34:56 :: IRQ Some(11)
  red :: ARP REQUEST enviado :: ¿quien tiene 10.0.2.2?
  red :: RX 64 bytes :: src=52:55:0a:00:02:02 type=0x0806

El src del paquete entrante (`52:55:0a:00:02:02`) codifica `10.0.2.2`
dentro del MAC — es el gateway de QEMU respondiendo. Renaser ya habla
con el exterior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 04:06:23 +00:00
sergio 60553bec44 feat(renaser): Fase 17 — bitácora, el editor que recuerda
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>
2026-05-23 03:52:20 +00:00
sergio e100c5acff fix(install-arje): pixman-devel para mirada-compositor (smithay)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 03:39:51 +00:00
sergio 71ebcea899 feat(renaser): Fase 16 — la barra viva: botón lanzador + reloj
La barra de tareas era pasiva: nombraba pero no hacía. Ahora lleva
en sus extremos los dos adornos de toda barra digna.

- Botón «+» indigo a la izquierda (36 px). Un clic incrementa
  `PARTOS` — el mismo contador que `Alt+N` — y la tarea del
  compositor lo recoge para lanzar la siguiente app de la rotación.
  Teclado y ratón comparten ya la misma vía para crear ventanas.
- Reloj `mm:ss` a la derecha (80 px), leído de
  `reloj::milisegundos()`. Tinta blanca sobre slate.
- `compositor::tick_reloj()` lo invoca la tarea del compositor cada
  fotograma; recompone solo cuando el segundo del reloj monótono
  cambia respecto al último mostrado (`ULTIMO_SEGUNDO: AtomicU64`).
  Cero coste mientras no toca refrescar.
- `Taskbar` crece con `launcher`, `reloj` y `reloj_region`; el
  layout de las pestañas se ajusta entre ambas cuñas. La cruz del
  lanzador se dibuja en píxeles directos —dos rectángulos cruzados,
  independiente de la tipografía—.

Verificado en QEMU con dos capturas separadas: la barra muestra el
«+» indigo, las siete pestañas (con `glotona` ya legible) y el
reloj. En la primera marca `0:17`; diez segundos después, `0:29` —
la barra se refrescó doce veces sin intervención.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 03:36:46 +00:00
sergio 0422780dd9 fix(install-arje): libxkbcommon-x11-devel para mirada-greeter
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 03:34:57 +00:00
sergio 08ec152b7f fix(install-arje): pam-devel en la lista de dev-libs (brahman-auth → pam-sys)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 03:23:41 +00:00
sergio 9fe9c8319e fix(install-arje): clang-devel en la lista de dev-libs para libclang.so
Sin clang-devel, los crates *-sys que corren bindgen (libinput-sys,
wayland-sys) fallan en runtime con 'couldn't find libclang.so'. El
mensaje de WARN ahora lo lista explícitamente.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 03:12:40 +00:00
sergio d40382ad01 feat(arje): cadena DM activa — kmod virtio_gpu + carmen-dm en arje-prod
- seed arje-prod gana dos Cards antes de getty-tty1:
  * kmod-gpu (OneShot): modprobe virtio_gpu — pone /dev/dri/card0
    listo antes de que el compositor intente abrir DRM/KMS.
  * carmen-dm (Restart): /usr/bin/mirada-compositor --greeter --drm,
    con MIRADA_GREETER_BIN apuntando a /usr/bin/mirada-greeter.

- install-arje-as-init.sh gana paso 1b: build nativo (no-musl) de
  mirada-compositor y mirada-greeter. GPUI/Mesa/EGL son dinámicos
  contra glibc — la cadena DM no puede vivir en el binario musl
  estático de arje-zero. Si el build falla por dev-libs ausentes,
  se imprime la lista de paquetes Fedora que faltan y se sigue
  (la instalación de arje-zero no se rompe).

- En el paso de instalación, los binarios del compositor se copian
  a /usr/bin/ sólo si el build de paso 1b tuvo éxito; ldconfig
  comprueba libEGL en runtime y avisa si falta.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 02:51:07 +00:00
sergio 28c2e6af18 feat(renaser): Fase 15 — la voz del sistema (acorde + eventos)
La bocina pertenecía al app enfocado (Fase 12), pero el kernel
necesita hablar también. Ahora tiene voz propia, prioritaria.

- `altavoz`: cola `SECUENCIA: Mutex<VecDeque<(u32,u32)>>` (freq, ms)
  + reloj `FIN_NOTA: AtomicU64`. `agendar(&[...])` encola;
  `atender()` (tarea del compositor cada fotograma) avanza la
  secuencia y silencia al acabar; `kernel_sonando()` gatea a los
  apps — mientras el kernel suena, `sys_tono` no-op.
- Catálogo: VOZ_BIENVENIDA (Do5-Mi5-Sol5, 500 ms), VOZ_LANZAR
  (700→1050 Hz), VOZ_CERRAR (900→520 Hz), VOZ_DESALOJO (180 Hz).
- Hitos: `kernel_main` agenda el acorde antes de `ejecutor.run`;
  `nacer_ventana` (Alt+N), `cerrar` (Alt+Q), `desalojar` (falla)
  agendan al hacer su trabajo.
- De paso: las pestañas de la barra de tareas calculan su tinta por
  brillo del fondo (ITU-R BT.601); la pestaña crema del desalojo por
  memoria, que llevaba texto blanco invisible, ahora luce su nombre
  en tinta oscura.

Verificado en QEMU con `-audiodev wav -machine pcspk-audiodev=spk`:
el PCM crudo trae, en orden, el acorde de bienvenida (~520, 630, 760
Hz), un brevísimo 180 Hz (las balizas de discola/glotona desalojadas)
y después la escala de Do mayor de tonada.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 02:21:37 +00:00
sergio 6a29152feb feat(renaser): Fase 14 — identidad del escritorio (nombres + barra de tareas)
Las ventanas eran anónimas: el escritorio no sabía nombrar lo que
mostraba. Esta fase le pone un nombre a cada cuarto y una barra al
pie con la lista de quienes lo habitan.

- Cada `Ventana` lleva un `nombre: String` —del manifiesto, o del
  orquestador al engendrarla en vivo—. `Plantilla` lo guarda para las
  copias que `Alt+N` instancia.
- Franja `FRANJA_TASKBAR=40px` reservada al pie. `area_apps` la
  descuenta — las ventanas teselan y flotan sin tapar la barra.
- `consola`: tipos `Taskbar` / `CeldaTaskbar` + métodos `pintar_taskbar`
  y `pintar_etiqueta` (rasteriza una cadena en (x, base_y) sobre un
  fondo conocido, sin tocar la pluma). La pestaña enfocada se pinta con
  el índigo del foco, las desalojadas con su color de baliza, el resto
  con el slate del panel.
- `compositor::recomponer` arma las celdas y las pasa junto a las capas
  a la consola; un único repintado, una única presentación.
- `atender_raton`: si el clic cae en la franja de la barra,
  `celda_taskbar_en` localiza la pestaña pulsada y la enfoca (sin
  iniciar arrastre).

Verificado en QEMU: al arrancar, la barra al pie muestra las 7
pestañas con sus nombres; `tonada` enfocada en índigo, `discola` y
`glotona` en sus colores de baliza. Un clic sobre `pulso` traslada el
foco al instante — el borde del compositor envuelve `pulso` y su
pestaña se ilumina.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 02:05:51 +00:00
sergio be4de986be feat(seed): arje-prod levanta red + sshd, así se puede entrar por SSH
Sin systemd ni NetworkManager, arje-zero quedaba sin red y sin sshd:
útil como bare init, inútil para sacar logs de una VPS sin pegado en
la consola web. Dos Cards nuevas en el seed prod:

- `net-up`: corre `/usr/sbin/arje-net-up` (script nuevo en scripts/),
  que pone up todas las interfaces y arranca `dhclient -d` en
  foreground sobre la primera no-loopback. Fallback a dhcpcd o
  busybox-udhcpc si dhclient no está. Crea de paso /run/sshd y
  /var/empty/sshd para que sshd no tenga que pelearlos. Restart
  supervisión.

- `sshd`: corre `/usr/sbin/sshd -D -e` (foreground + log a stderr).
  Usa las host keys que Fedora ya tenía. Restart supervisión.

El install script copia arje-net-up.sh a /usr/sbin/arje-net-up.

Prerequisito en el host (no automatizable desde acá): si la VPS no
tiene un cliente DHCP (Fedora Cloud trae sólo NetworkManager por
defecto), el script duerme con el link up y no obtiene IPv4. En ese
caso instalar antes del próximo boot: `dnf install dhcp-client`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 01:56:31 +00:00
sergio 8fcc4dc067 fix(renaser): mapeador MMIO en el kernel — la causa real del colapso
El `-global pci-hole64-size=0` del commit anterior NO movía los BAR:
verifiqué con `info pci` que OVMF seguía alojando el BAR4 prefetchable
64-bit del virtio-blk en `0xc000000000` (mi Proxmox) o `0x800000000`
(la laptop del usuario). El cargador `bootloader_api` 0.11 mapea la
memoria física pero no extiende su mapeo hasta la ventana PCI de 64
bits, y `KernelHal::mmio_phys_to_virt` devolvía `phys + offset` a
ciegas — un puntero a memoria sin tabla de páginas, al primer registro
MMIO leído → #PF.

La solución: un mapeador MMIO propio del kernel.

- `memory::mmio`: envuelve la tabla L4 activa (vía CR3 + el mapeo de
  memoria física del cargador) en un `OffsetPageTable`. Su función
  `mapear(fisica, tam)` abre, para cada página de la región, una
  entrada en la L4 con `PRESENT | WRITABLE | NO_CACHE | WRITE_THROUGH`
  — las banderas habituales del MMIO.
- Los marcos para tablas intermedias salen del banco DMA del disco
  (`drivers::disco::asignar_marco_para_tabla`, sin pánico). Se ponen
  a cero antes de cederlos: las tablas empiezan vacías.
- Tratamos `PageAlreadyMapped` y `ParentEntryHugePage` como éxito: la
  región ya estaba cubierta por el cargador (con páginas 4 KiB o
  hugepages 2 MiB / 1 GiB) y el acceso ya funciona. Solo abortamos el
  mapeo si se nos agota la arena DMA.
- `KernelHal::mmio_phys_to_virt` llama a `memory::mmio::mapear` antes
  de devolver el puntero virtual. virtio-drivers lo invoca con la
  base y el tamaño exactos de cada BAR; el kernel asegura que cada
  uno sea accesible antes de devolverlo.
- `kernel_main` funda el mapeador justo después del heap (paso 4.5),
  antes del disco. Necesita `physical_memory_offset` para alcanzar
  la L4 activa.

Quito el `-global q35-pcihost.pci-hole64-size=0` que añadí antes: no
movía los BAR (verificado con `info pci`) y solo confundía la
descripción del fix. Esta solución es la robusta: el kernel sabe
mapear sus propios MMIOs y deja de depender del firmware.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 01:28:32 +00:00
sergio c715ee2dee fix(init): la salida de arje-zero ahora se ve en VGA Y serial
Síntoma: el screenshot del usuario en la VPS Hetzner mostraba systemd
booteando y se quedaba congelado en el último printk del kernel justo
antes del switch-root. arje-zero arrancaba bien pero su salida iba al
serial invisible.

Causa: el cmdline traía `console=tty1 console=ttyS0,115200` — y el
kernel hace que `/dev/console` apunte al ÚLTIMO `console=`, así toda la
salida de stdout/stderr de arje-zero (tracing + banner de la rescue
shell) caía en ttyS0 (serial), no en la VGA que muestra noVNC.

Dos arreglos:

- Orden de consolas invertido en el menuentry → `/dev/console` = tty1
  (lo que efectivamente se ve en la consola web del proveedor).
- arje-zero también escribe a `/dev/kmsg` (ring buffer del kernel), que
  el kernel hace eco a TODAS las consolas registradas — el mecanismo
  que usa systemd para que sus mensajes salgan tanto en VGA como en
  serial. Defense in depth: el banner de rescue y un eco temprano
  «despierta como PID 1» aparecen sí o sí en cualquier consola.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 01:16:26 +00:00
sergio 89fd927f76 fix(renaser): forzar BARs PCI a los primeros 4 GiB (pci-hole64-size=0)
Esta era la verdadera causa del «pantalla negra + franja roja» en máquinas
distintas a la del autor. El trace de boot del usuario lo cantó:

  boot :: disco :: init               <- llegamos aquí
  disco :: init offset=0x20000000000 region=[...] base=...
  boot :: almacen :: init              <- y aquí
  *** renaser :: panico ***
    EXCEPCION FATAL :: fallo de pagina (#PF) en 0x20800000014

  0x20800000014 - phys_offset(0x20000000000) = 0x800000014  (= 32 GiB + 0x14)

Un acceso vía el mapeo de memoria física a phys=32 GiB. Eso es el BAR
MMIO del virtio-blk: OVMF en QEMU q35 moderno con KVM aloja los BARs
prefetchables 64-bit en el «pci hole» de 64 bits (típicamente a partir
de 32 GiB). El `bootloader` 0.11 con `Mapping::Dynamic` mapea la RAM
del sistema, pero no extiende el mapeo hasta los BARs ahí arriba.

KernelHal::mmio_phys_to_virt devolvía `phys + offset` sin verificar
nada — el host esperaba que el BAR estuviese en los primeros 4 GiB,
como mi Proxmox con TCG y mi nightly. En la laptop con KVM y el OVMF
del usuario, OVMF lo subía y todo reventaba al leer el primer registro.

El parche: `-global q35-pcihost.pci-hole64-size=0` en los args por
defecto de QEMU. Apaga la ventana PCI de 64 bits, OVMF se ve forzada
a alojar todos los BARs en los primeros 4 GiB y el mapeo del cargador
los cubre. Verificado: arranca limpio en Proxmox y debería arrancar
también en la laptop del usuario.

(Las verificaciones unchecked_mul del commit anterior eran una pista
falsa — eran solo donde caía la IP del último build; el fallo de
escritura siempre fue el mismo BAR sin mapear.)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 01:11:12 +00:00
sergio 2ad11b53c3 fix(renaser): apagar debug-assertions/overflow-checks vía rustflags
Era la causa raíz del «pantalla negra + franja roja» en máquinas con
nightlies recientes (laptops, KVM, etc.) que mi Proxmox no exhibía:

- En modo debug, la stdlib inyecta `<usize>::unchecked_mul::precondition_check`
  en cada `unchecked_mul` (RawVec::current_memory, in_place_collect…).
- El camino de pánico de ese check, en bare-metal, escribe en regiones
  que el cargador no mapeó → #PF → al panic handler del kernel, que pinta
  la franja roja: un colapso DENTRO de la red de seguridad del colapso.
- 1202 callsites en el binario debug — uno fallaba en la laptop del usuario.

El fix: `rustflags = ["-Cdebug-assertions=off", "-Coverflow-checks=off"]`
en `[target.x86_64-unknown-none]` de `.cargo/config.toml`. Los `[profile]`
del manifiesto no propagaban a las deps precompiladas (wasmi, virtio-
drivers, etc., que se quedan como artifact-deps fuera del workspace);
`rustflags` por target sí. Tras un `cargo clean` + rebuild, cero llamadas
a precondition_check, y el boot trace por COM1 corre completo.

También: `[profile.dev]`/`[profile.release]` del kernel y workspace
declaran los flags explícitamente, por si alguna ruta de cargo cambia.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 00:27:06 +00:00
sergio 5edd8de917 fix(scripts): Fedora/RHEL usan /boot/grub2/grub.cfg, no el wrapper EFI
Desde Fedora 34 / RHEL 9, /boot/efi/EFI/<distro>/grub.cfg es un wrapper
que sourcea /boot/grub2/grub.cfg — y grub2-mkconfig se niega a
sobreescribirlo ("will overwrite the GRUB wrapper. Please run [...] on
/boot/grub2/grub.cfg instead"). Mi loop de detección lo encontraba
primero y fallaba. Apuntamos directo al canónico.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 00:25:18 +00:00
sergio 89117f47cc diag(renaser): trazado por serie del arranque + DMA salta la página 0
Para localizar dónde colapsa el kernel en máquinas que no son la del
autor, cada hito de `kernel_main` deja una traza por COM1 (con el
panic-handler-a-serie de antes, ya tenemos boot trace + autopsia).

- `baliza::Serie` se hace `pub(crate)` para que cualquier módulo deje
  trazas con `writeln!(baliza::Serie, ...)`.
- `kernel_main`: traza tras adoptar el framebuffer, encender la baliza,
  fundar GDT/IDT/PIC, fundar el heap, fundar teclado/reloj/texto,
  publicar la consola, iniciar disco y almacén, arrancar el ratón,
  crear el ejecutor, cargar el userspace y arrancar el reactor. Y un
  volcado de `physical_memory_offset` + `region_dma` al inicio.
- `drivers::disco::init`: registra offset, región, base de la arena y
  número de marcos disponibles.
- Endurecimiento: `disco::init` ahora salta SIEMPRE la primera página
  física al elegir la base de la arena DMA. Algunos cargadores la dejan
  sin mapear como protección NULL; un marco DMA ahí se traduce a una
  dirección que peta al desreferenciar.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 00:07:07 +00:00
sergio d85bb3819e feat(renaser): pánico y OOM dejan testimonio por COM1
En pantalla solo cabe la franja roja: un grito breve, sin matiz. Para
diagnosticar colapsos en máquinas distintas a la del autor —donde no
es posible reproducirlos a mano—, los manejadores de fallo escriben
ahora una pista por COM1, además de pintar la franja.

- `baliza`: sumidero `Serie` que formatea sin tocar el heap, escribe a
  0x3F8 con espera acotada (antes mudo que colgar el sistema).
- El panic-handler vuelca el mensaje y la ubicación del `panic!`.
- El alloc-error-handler vuelca el `Layout` que reventó el techo.

QEMU con `-serial stdio` enruta COM1 a la terminal de `cargo run` — la
pista llega a quien lanzó el kernel, aunque la pantalla esté en negro.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 23:51:40 +00:00
sergio 8787b0566a fix(scripts): install-arje no esconde el build — chequeo previo + salida visible
El `>/dev/null` del paso 1 ocultaba lo que pasaba: si faltaba un
prerrequisito o el build moría, sólo se veía «paso 1/5» y el script
salía silencioso. Cambios:

- Paso 0 nuevo: chequeo de cargo / musl-gcc / busybox / cpio / gzip /
  rust target con mensajes accionables por distro (Fedora + Debian).
- El build de cargo deja fluir su salida a la terminal — incluida la
  espera de 10-20 min la primera vez, así no parece un cuelgue.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 23:48:24 +00:00
sergio 8fc1d99ddf feat(renaser): Fase 13 — ratón, puntero y arrastre de flotantes
renaser dialogaba sólo con el teclado; las ventanas flotantes nacían
en cascada y allí se quedaban. La Fase 13 trae el ratón.

- Driver `drivers/raton`: el ratón PS/2 cuelga del dispositivo
  auxiliar del 8042 + IRQ12. El driver despierta el aux, programa
  su IRQ, le ordena reportar, ensambla paquetes de 3 bytes con
  guarda del bit-3. Posición como atómicos, eventos como cola
  lock-free — el mismo guardarraíl que el teclado.
- El puntero, capa de PRESENTACIÓN: `Pantalla::estampar_puntero`
  pinta un sprite de flecha 12×18 sobre el framebuffer después de
  copiar el lienzo. El lienzo nunca lo contiene — hace de
  save-under natural—.
- Compositor: `atender_raton` drena eventos. Botón bajando es un
  clic-para-enfocar consistente con `mover_foco` (silencia bocina,
  alza si flota). Si la enfocada flota, arranca un arrastre con el
  desfase de agarre; el botón sostenido la sigue al puntero; al
  soltar, termina.
- `refrescar_puntero` reestampa el framebuffer si el puntero se
  movió en una vuelta tranquila en que ninguna app pintó.

Verificado en QEMU (mouse_move / mouse_button del monitor): el
puntero aparece al arrancar, se mueve por la pantalla, un clic
sobre pulso le da el foco, y un arrastre con el botón sostenido
mueve la flotante de la cascada al centro-abajo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 23:21:06 +00:00
sergio d1b700eb2b fix(init): el reboot-loop de Fedora — remount rw + /run tmpfs + shell de rescate
Diagnóstico: en el VPS Fedora arje-zero caía como PID 1 y el cmdline
traía `panic=10`, así que el kernel rebooteaba cada 10 s. Tres causas
encadenadas, todas arregladas:

1) **Cmdline `ro` + sin `/run` tmpfs.** El menuentry montaba `/` como
   sólo lectura (systemd lo remonta rw temprano; arje no). Sin eso, el
   socket del bus interno se intenta crear sobre un FS de sólo lectura
   y falla con EROFS → spawn_bus devuelve Err → PID 1 sale → kernel
   panic. arje-kernel ahora remonta `/` rw en el bootstrap y monta
   `/run`, `/tmp`, `/dev/pts`, `/dev/shm` como tmpfs — superficies
   escribibles aunque la raíz quede ro.

2) **PID 1 saliendo en cualquier `?`.** Doctrina dura nueva: PID 1
   NUNCA puede salir. Cualquier error de arranque ahora cae a una
   `emergency_shell()` que imprime el diagnóstico en `/dev/console`,
   abre `/bin/sh` y, si la shell muere, la reabre — así el operador
   puede reparar en vez de mirar la máquina reiniciarse en bucle.

3) **El script no conocía grub2 (Fedora).** `install-arje-as-init.sh`
   sólo probaba `update-grub` (Debian) y `grub-mkconfig` (Arch). Ahora
   detecta `grub2-mkconfig` y resuelve el `grub.cfg` correcto
   (UEFI/BIOS, fedora/redhat/centos/almalinux/rocky). El menuentry
   también pasa de `ro` a `rw` — el remount es belt-and-suspenders.
   Mismo arreglo en `uninstall-arje.sh`.

Renaser intacto: estos cambios son Linux-side puro (arje-kernel y
arje-zero usan nix/libc/tracing); renaser sólo comparte mirada-layout y
formato, ninguno tocado.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 23:02:45 +00:00
sergio 922ad1f86b feat(renaser): Fase 12 — la bocina del PC como capacidad de host
La Fase 11 dio al userspace un reloj; la Fase 12, una voz. Hasta hoy
renaser solo sabía dibujar para llamar la atención.

- Driver `drivers/altavoz`: el canal 2 del PIT como generador de onda
  cuadrada + la compuerta del puerto 0x61. El canal 0 —latido del
  kernel— no se toca. `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: pertenece a la ventana ENFOCADA,
  como el teclado desde la Fase 8c. Al cambiar el foco, el compositor
  la calla; la nueva dueña la reclama en su próximo fotograma.
- App nueva `tonada` (`apps/tonada/`, wasm32): toca una escala de Do
  mayor y la dibuja como una escalera de barras. Junta el reloj
  (`sys_tiempo_mono`) y la bocina (`sys_tono`).
- `GENESIS` crece de 6 a 7 apps; `tonada` es la maestra del escritorio.

Verificado en QEMU. Visual: la escalera de `tonada` recorre la escala
con el tiempo. Sonido: con la bocina enrutada a un WAV, el PCM
capturado es una onda cuadrada oscilante de ~375 Hz — la frecuencia
media de la escala de Do mayor.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:54:48 +00:00
sergio a388ab14b7 fix(compat): auditoría de stubs — los métodos que mentían dejan de mentir
Repaso de los 11 shims restantes buscando métodos que devolvían éxito
sin hacer el trabajo (como los dos setters de localed). Resultado:

timedated — tres setters arreglados de verdad:
- SetTime: aplica el reloj con clock_settime(CLOCK_REALTIME) en vez de
  sólo loggear; si falla (sin CAP_SYS_TIME) devuelve error honesto.
- SetLocalRTC: escribe la tercera línea de /etc/adjtime (UTC|LOCAL),
  conservando las dos primeras.
- SetNTP: arje no gestiona un daemon NTP — en vez de fingir éxito,
  rechaza honestamente; `CanNTP` pasa a `false` para que GNOME deje el
  toggle deshabilitado y ni llegue a llamarlo.

systemd1 — StopUnit/RestartUnit/KillUnit dejaban creer que habían
detenido la unit; ahora devuelven NotSupported honesto (como StartUnit).

Lo demás del repaso ya era honesto: resolved/machined devuelven
NotSupported de frente; polkit/tmpfiles/notify/binfmt/journald no
mienten. timer-compat queda como hueco conocido y autodocumentado (sus
timers disparan pero el spawn es un no-op a la espera del bus).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:45:53 +00:00
sergio 4bcdc88c83 feat(renaser): Fase 11 — el reloj del sistema como capacidad de host
El userspace gana un sentido del tiempo: hasta ahora una app solo sabía
cuántas veces la habían llamado, no cuánto tiempo había pasado.

- Capacidad `sys_tiempo_mono() -> u64` — la décima función del host:
  los milisegundos monótonos desde el arranque. `reloj` expone la
  cuenta del PIT (100 Hz) como `milisegundos()`; `env` la inyecta.
  Lectura pura, no toca la memoria del módulo, jamás retrocede.
- App nueva `pulso` (`apps/pulso/`, wasm32): un compás visual cuya
  escena es una función PURA de `sys_tiempo_mono` — sin estado entre
  fotogramas—. Dos instancias laten al unísono nazcan cuando nazcan.
- `GENESIS` crece de 5 a 6 apps; `pulso` es la maestra del escritorio.

Verificado en QEMU (sendkey): la barra de `pulso` avanza con el tiempo
de pared; un segundo `pulso` lanzado con Alt+N ~15 s después aparece
sincronizado con el primero — el compás se rige por el reloj absoluto,
no por una cuenta de fotogramas.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:43:17 +00:00
sergio 19d04a2766 feat(compat): logind — Inhibit real (fd vivo) + ListInhibitors
`Inhibit` dejó de ser un stub que devolvía NotSupported. Ahora cumple
el contrato de systemd-logind:

- Crea un pipe; el cliente recibe el extremo de escritura, el shim
  conserva el de lectura. Mientras el cliente no cierre su fd, el
  inhibidor sigue activo; al cerrarlo —o morir— el shim ve EOF y una
  tarea guardiana lo retira de la tabla.
- Tabla de inhibidores activos + método `ListInhibitors`.
- Las propiedades `BlockInhibited` / `DelayInhibited` ahora reflejan
  los inhibidores reales (tokens únicos del modo, unidos por `:`),
  en vez de devolver siempre vacío.

Es lo que GNOME/KDE usan para frenar la suspensión automática durante
una presentación o una descarga. 1 test (`inhibited_what`).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:41:10 +00:00
sergio 089afccbbc feat(renaser): Fase 10 — alta y baja de aplicaciones en vivo
El censo de aplicaciones deja de fijarse en el arranque: una app puede
nacer o cerrarse con el reactor ya en marcha.

- El reactor admite NACIMIENTOS en vivo: cola `NACIMIENTOS` +
  `engendrar()`, drenada al inicio de cada vuelta de `run()`;
  `Task::adoptar` acoge un futuro ya empaquetado.
- `Alt+Q` (`Mando::Cerrar`): baja limpia. El compositor saca la
  ventana enfocada del teselado y del orden-Z; la app advierte la
  baja (`ventana_cerrada`) y concluye su tarea — su memoria, su
  combustible y su canal de teclado se liberan. Sin baliza.
- `Alt+N` (`Mando::Lanzar`): alta en vivo. `nacer_ventana` añade la
  ventana y entrega su índice; el orquestador instancia el WASM y
  engendra su tarea. Las apps de génesis dejan su bytecode cacheado
  como `Plantilla`; cada `Alt+N` instancia una en rotación.

Verificado en QEMU (sendkey): tres Alt+N hacen crecer el escritorio
de 5 a 8 ventanas; tres Alt+Q lo reducen de 8 a 5. Kernel estable.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:26:25 +00:00
sergio 8f946b449c feat(compat): arje-compat-common — núcleo puro y testeado para los shims
El hallazgo del repaso del monorepo: la capa compat (14 shims D-Bus de
systemd) era lo más incompleto relativo a su peso — load-bearing para
correr GNOME/KDE sobre arje, y con CERO tests. Cada shim copiaba su
propio `atomic_write`, su parseo `KEY=value` y sus validadores.

Primer golpe:

- `arje-compat-common`: crate nuevo con la lógica pura compartida
  (atomic_write, parse_kv, merge_kv, conf_entries, is_valid_hostname),
  cubierta con 8 tests. Antes esa lógica vivía duplicada y sin un test.
- `arje-hostnamed-compat` y `arje-localed-compat` migrados al núcleo —
  quedan más finos y su lógica pasa a estar cubierta.
- localed: los dos setters que eran stub (sólo loggeaban) ahora
  escriben de verdad — `SetVConsoleKeymap` → /etc/vconsole.conf,
  `SetX11Keyboard` → 00-keyboard.conf. + 2 tests propios.
- Bug corregido de paso: el parser xorg de localed devolvía el NOMBRE
  de la opción en vez del valor (tomaba la 1ª comilla); ahora toma la
  2ª cadena, la correcta.

Compat: de 0 a 10 tests. Quedan 12 shims con la misma migración
mecánica pendiente; el plato fuerte real es `Inhibit` en logind.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:25:58 +00:00
sergio c4d1dd7bc2 feat(cosmobiologia): corpus — capa de composición por evidencia vecina
La capa de composición, resuelta con honestidad. El producto numérico
de perfiles (Hadamard y parientes) se descarta: da falsos —una
dimensión en 0 nunca «se enciende»— y, sobre todo, un perfil compuesto
es una conjetura, no evidencia.

En su lugar, `Corpus::evidencia_relacionada`: para una combinación SIN
pasaje propio, junta la evidencia VECINA —pasajes que comparten un
componente (el planeta, el signo, la casa, el tipo de aspecto)—,
agrupada por lo que comparten. No sintetiza un texto; son citas reales
de contextos parecidos para que el astrólogo componga él.

En la rueda 2D, el panel de la tajada ahora muestra, bajo los pasajes
directos, una sección «Composición» con esa evidencia vecina por cada
combinación sin texto propio.

16 tests del corpus (2 nuevos) + 2 del engine verdes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:07:35 +00:00
sergio 2035e6dfa3 feat(cosmobiologia): corpus — las tajadas sobre la rueda
La interpretación por dominio, integrada al lienzo 2D. Tres botones
(Vital / Social / Psíquico, o tecla I para ciclar): al elegir una
tajada, la rueda resalta con un anillo los cuerpos de ese dominio y un
panel a la derecha lista los pasajes citados —combinación, texto,
fuente—, o avisa de los huecos sin texto.

- El canvas carga el corpus al arrancar: corpus.ron del directorio de
  datos del usuario, o la plantilla ejemplo.ron embebida como fallback.
- El JOIN corre con corpus_inputs (engine) + interpretar_por_dominio:
  cada longitud → signo, cada casa → su tajada, los aspectos puentean.
- El resalte es una capa transparente sobre la rueda, sin tocar el
  render del wheel.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 19:54:23 +00:00