Saltar al contenido principal

JARU 0.9.6

Estas son las notas de JARU 0.9.6 tal como salió; lo que cambió después está en JARU 0.9.7.

  • Fecha: julio de 2026
  • Componentes: IDE (0.9.6.1, build 54) · VM Windows · VM ESP32 / ESP32-S3 · VM ESP32-P4 · Compilador
  • Versión anterior: 0.9.5

Esta versión es, con diferencia, el salto más grande del ecosistema hasta la fecha. Los cuatro bloques principales son:

  1. Un motor de físicas y colisiones nuevo, escrito desde cero, con rotación real, contactos con impulsos, sleeping, torque y CCD.
  2. Un sistema de sonido y música completo: motor en todas las VMs, tracker y editor de instrumentos en el IDE, importador de MIDI y formato binario propio.
  3. Una VM nueva para ESP32-P4 con drivers MIPI-DSI.
  4. Dos incorporaciones al lenguaje: el tipo struct y las funciones lambda fn, junto a la unificación de los callbacks (una función, un método o un string valen en todas partes).

Además se refuerza toda la inteligencia del IDE y llega una tanda larga de correcciones en la VM, el compilador y los editores de recursos.


Lo más destacado​


Cambios incompatibles​

Son pocos, pero afectan a proyectos hechos con 0.9.5.

CambioQué hacer
sprite.spin respeta ya los grados por segundo. Llevaba un factor 60 de más, así que giraba 60 veces más rápido de lo documentado.Multiplicar por 60 los valores antiguos si se quería la velocidad que se veía antes.
sprite.acceleration queda obsoleto como alias de sprite.force; se retirará en 1.0. Su semántica real siempre fue "fuerza de un solo paso", no una aceleración persistente.Renombrar a force cuando sea cómodo.
scene.maxDelta es ahora una propiedad de cada escena. Antes escribía un global de fichero, así que tocarlo en una escena lo cambiaba en todas (y ni siquiera leía su argumento).Fijarlo en cada escena que lo necesite.
friction y elasticity se acotan a [0,1] en los tres puntos de entrada (sprite, grid y setters).Valores fuera de rango producían NaN o ganaban energía; ya no se aceptan.
La fricción entre sprites es de Coulomb, acotada por el impulso normal.Los roces suaves ya no frenan y aparece fricción estática real en rampas. Puede notarse en juegos que dependían del frenado viscoso anterior.
Los mensajes de error de la VM están todos en inglés. Quedaban 130 literales en castellano, con tres redacciones distintas para la misma familia de error.Solo afecta a código que compare el texto de e.message; comparar el código de error en su lugar.
La partición de aplicación del ESP32-S3 crece de 0x140000 a 0x160000, y el littlefs se desplaza a 0x170000.Hace falta un flasheo completo (firmware + filesystem), no solo el firmware.

Lenguaje y compilador​

Nuevo tipo struct​

  • Tipo struct de primera clase: definición, instanciación y acceso a campos.
  • El compilador emite el opcode oSTRUCT; soportado por las tres VMs.
  • Campos invocables: se puede guardar una función en un campo y llamarla con a.campo().
  • Soporte completo en el IDE (ver más abajo) y página propia en la guía, en los dos idiomas.

Funciones lambda fn​

  • Sintaxis fn(parametros): cuerpo end. Una lambda es un valor: se guarda en una variable, se mete en una lista o mapa, se devuelve desde una función o se pasa directamente como callback.
  • Captura por referencia: la lambda ve los cambios posteriores de la variable capturada, y la variable sigue viva mientras la lambda exista, aunque la función donde nació ya haya terminado.
  • Dentro de un método, una lambda también captura this.
  • Recursión asignando primero la lambda a una variable y llamando a través de ella.
  • Documentado en guia/funciones (ES + EN), con el highlighter, el parser en vivo y el completado del IDE al día.

Callbacks unificados​

Donde antes cada módulo aceptaba una cosa distinta, ahora los cuatro puntos de entrada aceptan lo mismo: una referencia directa (miFuncion), un método de objeto (objeto.metodo), un nombre en string ("miFuncion") o una lambda fn en línea.

Incluye raíces de GC para los callbacks vivos y correcciones de atasco y doble-pop en el despachador.

Correcciones del compilador​

  • this.propiedad++ / this.propiedad-- ya no marcaban error de forma indebida.
  • Corrección del código de salida del compilador (se reescribía a 0 en main).
  • Tabla de líneas fragmentada: los nodos sintéticos de índices y dimensiones de los literales de array llevaban línea 0, lo que rompía la agrupación RLE y duplicaba entradas. Ahora heredan la línea real; el .jxr y la RAM que ocupa la tabla se reducen.
  • Validación estricta de functionsCount y de los datos finales del .jxr al cargar.
  • Los nombres de funciones padre se internan y se asocian por identidad en el cargador.

platform() documentado de verdad​

La tabla publicada llevaba tiempo equivocada (documentaba 3 = Windows cuando Windows es el 4). Ahora es correcta y añade la columna de constantes, que no estaban documentadas en ninguna parte:

ValorPlataformaConstante
0Desconocida—
1ESP32HW_ESP32
2ESP32-S3HW_ESP32S3
3Reservado—
4Windows (SDL2)HW_WIN32
5Web (Emscripten)HW_WEB
6ESP32-P4HW_ESP32P4

Motor de físicas y colisiones​

El motor anterior se sustituye por uno nuevo (broadphase, narrowphase, solver de contactos y colisión contra grid), presente en las tres VMs y validado con baterías propias: PhysicsAudit 101/101, CollisionLab 35/35, Collision2 13/13, PhysProps 25/25 y JaruTest 14/14 (608 asserts).

API nueva del sprite​

Propiedad / métodoQué hace
forceFuerza de un solo paso (sustituye a acceleration, que queda como alias obsoleto).
rotatesPermite o bloquea la rotación física del cuerpo.
angularVelVelocidad angular en grados por segundo.
angularDampingAmortiguación angular por cuerpo (modelo Box2D w *= 1/(1+dt·d)).
gravityScaleMultiplicador de gravedad por cuerpo (0 = flota, 2 = pesado, negativo = cae hacia arriba).
dampingAmortiguación lineal por cuerpo, sin necesidad de contacto (agua, aire).
category / collideWithFiltro de colisión por bits estilo Box2D.
bulletActiva CCD continuo en el cuerpo: un móvil rápido no atraviesa a otro cuerpo entre dos pasos.
onCollision(cb [, minImpulso])Callback de impacto, con umbral opcional de impulso.
applyForce(fx, fy)Fuerza en el centro de masas.
applyImpulse(ix, iy)Impulso instantáneo.
applyTorque(t)Par puro; no desplaza el centro.
applyForceAt(fx, fy, px, py)Fuerza en un punto del mundo: genera par r × F.

API nueva de la escena​

  • scene.sleeping: auto-sleep de los cuerpos en reposo, por islas de contacto completas y sin heap extra. Desactivarlo despierta a todos.
  • scene.sleepAll(): solicita dormir toda la escena; la petición la consume el siguiente paso físico completo.
  • scene.maxDelta pasa a ser propiedad real de cada escena.
  • Un mundo por escena: desaparece el pool estático de 4 World con reciclado LRU. Cada escena posee el suyo, se crea de forma perezosa y se libera al destruir la escena, así que la RAM del motor vuelve al sistema en el momento.

Rotación y geometría​

  • El origen dinámico (centro de masas y de rotación) pasa a ser el centro del collider; el pivot queda como ancla visual. Con el pivot excéntrico como centro de masas, cada impulso normal creaba un par fantasma y el collider orbitaba su esquina.
  • solveContacts reordenado al criterio Box2D: impulsos de velocidad primero, pasada posicional después. Corregir la posición antes desalineaba los brazos de palanca y drenaba el giro (~8 %/s con fricción 0).
  • El centro del collider, su offset y el fondo usado por las plataformas one-way rotan con el sprite, tanto contra otros sprites como contra el grid.
  • El spin invalida la caché de rotación: un sprite girando por spin colisionaba con los vértices de su último angle manual.
  • grounded se evalúa en el centro del parche de contacto, y las caras planas del grid aplican un único impulso ahí (sin par fantasma al aterrizar plano).
  • Sprites con render subpíxel en Windows (SDL_RenderCopyExF): el truncado a entero bamboleaba el giro físico.

Colisión contra GridMap​

  • Recolocación absoluta en la cara del tile: la rama de tiles sólidos sumaba el overlap del sweep a la posición final, hundiendo al sprite en caídas rápidas, atravesando muros finos y elevándolo el doble al aterrizar a caballo de dos tiles.
  • Filtro de caras internas (ghost collisions) también en la versión rectangular, con máscara de bloqueo separada de la de detección: una plataforma one-way no tapa a un sólido. Es el defecto que obligaba al truco del doble collider en los juegos.
  • Red anti-embebido rediseñada: un único contacto sintético calculado como traslación mínima que saca al rect de la unión de tiles bloqueantes, evaluada en la posición final y en el marco del sprite (correcto también en mapas con wrap).
  • Los contactos de esquina de un círculo se resuelven por penetración radial, no por AABB, que sobre-corregía ~3 px por 0,1 px de penetración real.
  • hitsGrid(grid) vuelve a reportar todos los tiles solapados, como dice la documentación; el filtro de caras queda solo en la resolución física.
  • Barrido de círculos y rectángulos contra el grid unificado, con coordenadas de tile, wrap y deduplicación correctas.

Rendimiento​

  • CCD con descarte previo: runCcd copiaba el Body entero (140 bytes) y barría contra todos los cuerpos del mundo. Con 6 balas y 75 cuerpos eran hasta 1.350 barridos por paso. Ahora una AABB conservadora de movimiento descarta antes de copiar.
  • El CCD solo se activa si la escena tiene algún cuerpo bullet: una escena sin balas no paga ni el recorrido.
  • La masa y la inercia se recalculan solo si pudieron cambiar (masa, forma o tipo), en vez de 3-4 divisiones en coma flotante por cuerpo y paso.
  • La caché de trigonometría se reutiliza en el escaneo del grid: el camino one-way llegaba a ~18 llamadas a libm por tile examinado.
  • La caché de rotación baja de 44 a 12 bytes por sprite.

Correcciones​

  • Los sprites inactivos ya no calculan física: un sprite con isActive = false seguía integrándose y colisionando en el solver, contra lo que dice la documentación.
  • Umbral de restitución calibrado: estaba haciendo tres trabajos a la vez y rompía onGround con la elasticidad por defecto. Un sprite en reposo sobre un STATIC pasa del 35-56 % al 99,6 % de frames con onGround cierto.
  • Tres fallos de stepRate / maxDelta: un stepRate por debajo de 1/maxDelta simulaba menos tiempo del que consumía y el juego iba a cámara lenta sin avisar (stepRate 10 corría al 50 %; ahora al 97 %); y la deuda del acumulador crecía sin límite al salir por el tope de sub-pasos, disparando la simulación al recuperar los fps (stepRate 240 al 331 %; ahora al 106 %).
  • Crash por null deref con sprites VISUAL rozados por un DYNAMIC.
  • Overlap sobredimensionado en círculo-vs-rect y círculo-vs-OBB cuando el borde inferior era el más cercano (expulsiones de decenas de píxeles).
  • scene.iters saturado a 255: el cast a uint8_t envolvía 256 → 0 y desactivaba en silencio el solver sprite-sprite.
  • Validación de rango 0-255 en los tile sets del grid, portada a ESP32 y ESP32-P4 (existía solo en Windows).
  • Filtro de caras internas del círculo invertido, bug preexistente: el emparejamiento estaba cruzado, así que nunca suprimía nada.
  • Relleno y cierre de polígonos implementados en ESP32 y ESP32-P4 (draw.polygon(vertices, fill)), respetando el backbuffer y los offsets de pantalla del P4.

Límite conocido​

El motor no sostiene apilamientos de dos o más cuerpos dinámicos en reposo: hay bombeo de energía porque falta warm-starting de contactos. Está analizado y medido en PLAN_WARMSTARTING.md; ninguna palanca del .aru lo evita.


Sonido y música​

Todo este bloque es nuevo: 0.9.5 no tenía sistema de sonido.

Motor​

  • Canales: cuatro de tono (0-3), uno de ruido y dos de PCM.
  • Envolventes ADSR por instrumento, modelo gate estilo SID.
  • Formas de onda: cuadrada, sierra, triangular, pulso y senoidal, con filtro polyBLEP para reducir armónicos indeseados.
  • Sound.pwm(canal, depth, rate [, delayMs]): LFO del duty, audible en PULSE (a fondo barre ±40 puntos de duty).
  • Sound.tremolo(canal, depth, rate [, delayMs]): LFO de amplitud, en tonos y ruido, con dip unipolar hasta el silencio.
  • Vibrato por instrumento y por canal.
  • Afinación exacta en mili-hercios: una única tabla de frecuencias por nota MIDI, independiente del sample rate, con los graves afinados (A0 = 27,5 Hz exactos). Sound.tone / pulse / slideFreq aceptan frecuencias con decimales (Sound.tone(0, 65.406, 200)).
  • Sample rate seleccionable por perfil de placa (22050 / 44100). El reloj del ruido queda fijo a 22050 con un contador Q16, de modo que una canción suena igual en placas a 22 y a 44 kHz.
  • Latencia baja en ESP32: block_frames por defecto de 256, unos 58 ms a 22050 Hz, manteniendo 35 ms de colchón anti-underrun.
  • Anti-chasquido: retrigger legato sin corte a cero, fase libre del oscilador y rampas de volumen por frame. Cada onda arranca en su cruce por cero, incluida la triangular, cuya fase 0 es el valle y saltaba desde el silencio.
  • Prioridades de SFX respetadas por la música: los note-off del secuenciador no matan un SFX con priority > 0. El transporte (play / seek / stopMusic) sigue silenciando todo.
  • Balance de los canales PCM ajustado e instrumento Dark para el canal de ruido.

Formato de música compilada (.jmu)​

  • Binario compacto, generado por el IDE al compilar el proyecto.
  • Tabla de instrumentos en cabecera y opcode SELECT: el patch completo (envolvente, vibrato, PWM y trémolo) viaja una sola vez y las filas solo lo seleccionan.
  • Canal en 3 bits para dar cabida a los cuatro canales de tono, con el byte de control aparte.
  • Los slides de tono viajan en mili-hercios, la misma unidad y precisión que las notas, así que un pitch bend no deja la nota desafinada al soltarlo.
  • El tempo de cabecera se restaura al re-reproducir o hacer seek.

Importador de MIDI​

  • Volumen y expresión (CC7 y CC11): muchos MIDIs orquestales llevan el fraseo en CC11 sobre un CC7 estático de balance, y esas canciones perdían toda su dinámica. Se combinan en consulta como CC7 × CC11 / 127, para tolerar ficheros con el CC7 en un track de setup y el CC11 en el de las notas.
  • Respeta el volumen nominal del timbre: cada nota importada llevaba un volumen absoluto derivado solo de velocity y CC7, lo que descartaba el balance diseñado en TIMBRES (en particular la compensación de las triangulares).
  • Nota GM 58 (Vibraslap) mapeada: era la única de las 47 del mapa de percusión sin asignar, y sonaba como una caja.
  • Afinación exacta con pitch bend: nota entera resuelta de tabla, bend fraccional en mili-hercios.

Máquinas virtuales​

VM ESP32-P4​

Otros cambios de plataforma​

  • Actualización de LovyanGFX a 1.2.21.
  • Driver QSPI y ejemplo de prueba.
  • Unificación de buses SPI (SPI1/SPI2/SPI3) sin etiquetas adicionales.
  • draw.font validado a 0-8: draw.font(9) desreferenciaba un nullptr de LovyanGFX y draw.font(200) leía fuera de la tabla al dibujar texto. La documentación pasa a listar las seis fuentes reales con su alto y su juego de caracteres (documentaba 4 de las 9 entradas).
  • Bitmap.load distingue la causa del fallo: lanza ERR_OUT_OF_MEMORY con mensaje propio en vez del genérico ERR_FILE_NOT_FOUND. Lo mismo para los fotogramas de Sprite.load.
  • Validación más estricta al leer boot.cfg: una clave ausente conserva su valor por defecto en vez de convertirse en 0, y los pines no declarados se quedan en −1.
  • platformio.ini soporta las dos placas a la vez: [env:esp32dev] y [env:esp32s3dev] sobre una sección [base] común, sin sustituir el fichero para cambiar de placa.
  • Corrección de los scrolls de mapa a saltos en la VM de Windows.
  • readLine dejaba de responder al llegar a EOF, escribiendo basura del heap sobre el buffer.

Correcciones de la VM y del intérprete​

  • foreach en try / switch leía el ObjIterator en crudo.
  • Escritura indexada y ++/-- sobre la variable del iterador.
  • Claves de mapa booleanas ya no colisionan con enteros; el hash de claves objeto va por puntero.
  • Iterar un no-iterable lanza correctamente en oBUILD_ITER; foreach sobre una expresión dejaba de crashear.
  • Tombstones fantasma en size() al sobrescribir una clave viva, y crash de AdjustCapacity.
  • Fuga de pila en for con condición booleana literal.
  • Terminador nulo de la respuesta HTTP en Windows.

Optimización de la VM (medida en placa)​

Campaña de rendimiento portada a las cuatro plataformas:

  • Despacho e intérprete: ip cacheado en local de Run(), safepoints de interrupciones y fast-paths enteros.
  • Opcodes fusionados JUMP_IF_FALSE_POP / JUMP_IF_TRUE_POP.
  • oCONSTANT8: carga de constantes con índice de 8 bits (bytecode ~−2,3 %).
  • Objetos y tablas hash: caché de constructores, bitmask de operadores, propiedades inline en ObjInstance, Entry de 24 → 16 bytes (tablas −33 %), helpers calientes en IRAM_ATTR.
  • Recolector de basura: se elimina CollectGarbage de AdjustCapacity, raíces perennes con GC de frontera y censo de strings dinámicas y módulos perennes.

IDE​

Editor de música / tracker​

  • Editor tipo tracker integrado, con formato propio del IDE y conversión al formato de VM al compilar.
  • Resalte del canal y de la celda activos: gradiente más vivo en la cabecera del canal, barra dorada de 3 px donde arrancan sus datos, campo enfocado (Note/Inst/Vol/Dur) en blanco y recuadro de 2 px en la celda exacta bajo el cursor.
  • Editor gráfico de instrumentos con parámetros, envolventes ADSR, onda senoidal, PWM y trémolo.
  • Selector de presets General MIDI: combos de familia y programa, con percusión, alimentados desde los datos GM del importador.
  • Importador de MIDI a música JARU, cada vez más consistente en envolventes e instrumentos.
  • Preview fiel al motor: el reproductor del IDE espeja el anti-chasquido, el bus máster, el onset de la triangular y el del trémolo, y suena al sample rate del perfil de placa activo (reabre el device SDL al cambiar de placa).
  • Buffer del preview a 2048 frames a 22050 Hz (el doble a 44100, para conservar los mismos ~92 ms), que elimina los chasquidos por underrun.
  • VU corregidos: el reproductor publica el pico crudo de cada bloque, sin decay, y la caída visual vive en el tick de 33 ms del editor, así que ya no depende del tamaño del buffer de audio.
  • La barra de estado indica playing/paused @ 22kHz|44kHz.
  • Mejoras de edición: copy / cut / paste, selección múltiple de filas, sistema de teclado, iconos en los botones y el botón «Copy ms (seekMusic)» integrado en el estilo del tracker.

Editores de recursos​

Herramientas ESP32 y hardware​

  • Rediseño del formulario ESP32 Tools con perfiles de HW y dispositivos de usuario.
  • Gestión de buses de display independiente del chip controlador: todos los controladores y todos los buses (SPI / QSPI / I80 / MIPI-DSI) aparecen en cualquier SoC, y el usuario elige la combinación correcta.
  • El campo Rate del editor de perfiles pasa de TEdit libre a combo 22050 / 44100, y block_frames por defecto a 256.
  • Reorganización de la gestión de Devices.

Inteligencia del código​

  • Miembros heredados ofrecidos en el completado y en el call-tip.
  • Ctrl+clic y hover resuelven el segmento bajo el cursor, incluyendo cadenas de 3+ niveles.
  • El texto de tipo inferido del hover filtra por fichero.
  • Soporte completo de struct en el highlighter, el parser en vivo, el beautifier, el checker y la tabla de inferencia; el Watch despliega structs y hay completado y tooltips de campos tras p..
  • Soporte de lambdas fn en el lexer, el parser en vivo, el highlighter y el completado.
  • Error tracker: diagnóstico de ciclos de herencia y guarda anti-cuelgue (caso A:B:A).
  • Reducción de falsos positivos en inferencia y error tracker; validación y métodos nativos de struct.
  • Soporte de ficheros .txt y .md (excluidos del coloreado y de la detección de errores) y marcado de líneas de error en el minimapa.
  • Guarda para no permitir compilar sin un proyecto activo.
  • Hints al día con toda la API nueva: bullet, gravityScale, damping, category, collideWith, angularDamping, onCollision, applyTorque, applyForceAt, scene.sleeping, scene.sleepAll, Sound.pwm, Sound.tremolo, rango de draw.font y los valores de platform().

Otras mejoras del IDE​

  • Agente de generación de drivers I2C por IA, lanzado desde el Python embebido del IDE. Genera los drivers en inglés, con DRV_ES8311.aru como canon.
  • Degradados de hover y pulsado derivados del color de cada botón: los 171 botones del IDE compartían el mismo degradado azul, así que los botones con color propio (el naranja de Save Device, el verde de Scan Ports, el rojo de Cancel…) se volvían azules al tocarlos. Ahora se regeneran en su propio tono conservando la estructura del degradado original.
  • Formulario de errores: multiselección de filas, menú contextual para copiar al portapapeles y mejor contraste del texto.
  • Unificación estética: color de fondo #211E28 en más formularios.
  • Corrección del carácter «ñ» que no se veía en el editor de código.
  • Mejoras en el ranking de sugerencias y en el minimapa.

Documentación y web​

  • Página de Structs en la guía (ES + EN) y sección de callbacks en campos.
  • Sección de lambdas fn en guia/funciones, con las cuatro formas de pasar un callback (función, método, string y lambda) documentadas en Timer, gpio, MQTT y sprite.onCollision.
  • Módulo sprite reescrito en su sección de física: applyForce, applyImpulse, applyTorque, applyForceAt, rotates, angularVel, angularDamping y bullet, más la corrección de la semántica real de acceleration, que se documentaba como aceleración persistente cuando siempre fue una fuerza de un solo paso.
  • Módulo scene: sleeping y sleepAll.
  • Módulo sound documentado por completo: tone, pulse, noise, fade, slides, setRelease, envelope, vibrato, pwm, tremolo, setMusicVolume, isMusicPlaying, y las frecuencias con decimales.
  • Tabla de platform() corregida en los dos idiomas, con la columna de constantes, y tabla de fuentes de draw.font completa.
  • SEO: mejoras generales, integración de Google Analytics (GA4) con evento en las descargas.
  • URLs unificadas en inglés en ambos idiomas (fin de los slugs cruzados) y redirects de las páginas de structs.
  • Nuevas entradas de blog (music tracker; el IDE para prototipado y formación con ESP32).

Notas de instalación / actualización​

  • IDE Windows: instalador JARU_IDE_0.9.6_Setup (ProductVersion 0.9.6.1, build 54).
  • VM ESP32 / ESP32-S3: build JARU_VM_ESP32_0.9.6 (PlatformIO, LovyanGFX 1.2.21).
  • VM ESP32-P4: target esp32-p4 (ESP-IDF).
  • Recompila el proyecto tras actualizar para regenerar los recursos con el compilador nuevo.
  • Revisa la tabla de cambios incompatibles de arriba: sprite.spin, sprite.acceleration y scene.maxDelta son los tres puntos que pueden requerir tocar código.