# ONDA 64 · Formato OND5, transporte y motor Referencia de la implementación actual. Para aprender a programar, consulta la [guía ONDA BASIC 1](LANGUAGE.html). Este documento describe los bytes del cartucho, su transmisión y el intérprete; ONDA no emula Amstrad CPC ni su formato de cinta. ## Versiones y aplicaciones Hay tres versiones independientes: - **ONDA BASIC 1**: lenguaje de autoría. La sintaxis usa exclusivamente palabras clave en inglés. - **OND5**: contenedor binario que exporta Studio, con programa comprimido y paleta propia. - **VM 3**: único intérprete admitido. Todos los programas declaran `vm:3`, usen efectos o no. Durante el desarrollo previo a 1.0 no se mantienen lectores, alias ni migraciones para revisiones retiradas. La base vigente es ONDA BASIC en inglés, OND5 y VM 3. Un cambio de formato puede requerir volver a guardar y exportar los proyectos. | Superficie | Función | | --- | --- | | Aplicación Android | Consola instalada con WebView, recepción nativa de micrófono y puentes de vibración y orientación. | | `/console.html` | Consola web: colección, recepción, reproducción y compartición. El micrófono web requiere HTTPS o localhost. | | `/` e `/index.html` | Consola en desarrollo; página de descarga cuando el servidor usa `ONDA_PUBLIC_SITE=1`. | | `/studio.html` | Editor de escritorio: menús, proyectos JSON, configuración de cinta, carátula, paleta y prueba con la misma VM. | | `/radio.html` | Emisión del cartucho configurado en la web. Actualmente utiliza `cartridges/cosmic-cow.onda`. | La consola web tiene manifest y caché de recursos. Studio y la guía no forman parte del precargado offline de la consola. La colección se guarda localmente; no se sincroniza con un servidor. ## Cartucho OND5 Máximo **16.384 bytes (16 KiB)**, incluidos cabecera, carátula y programa comprimido. Los enteros multibyte del cartucho y de las tramas acústicas son **big-endian**. El WAV utiliza el orden little-endian de su contenedor estándar. | Desplazamiento | Longitud | Contenido | | --- | --- | --- | | 0 | 4 bytes | Firma ASCII `OND5`. | | 4 | 2 bytes | Longitud uint16 del JSON de metadatos. | | 6 | Hasta 202 bytes | Metadatos UTF-8; el exportador deja a cero el espacio restante hasta el byte 207. | | 208 | 48 bytes | Paleta: 16 tripletas RGB de ocho bits, en orden de índice. | | 256 | 1.536 bytes | Carátula de 64 × 48, dos píxeles por byte. | | 1.792 | `codeBytes` | Programa JSON UTF-8 comprimido con DEFLATE raw. | Metadatos: | Campo | Significado | | --- | --- | | `vm` | Versión del intérprete: siempre 3. | | `name` | Nombre no vacío, hasta 40 caracteres. | | `author` | Autor, hasta 40 caracteres; puede estar vacío. | | `codeBytes` | Número de bytes comprimidos del programa. | | `rawBytes` | Número exacto de bytes del JSON descomprimido, entre 1 y 65.536. | | `codeCrc` | CRC32 del JSON descomprimido. | El límite de 202 **bytes UTF-8** para los metadatos se aplica además de los límites de caracteres de nombre y autor. Los textos largos con caracteres multibyte pueden necesitar acortarse. La carátula se recorre por filas. El primer píxel ocupa el nibble alto; el segundo, el bajo. Cada valor es un índice 0–15. No se comprime la carátula: así puede mostrarse progresivamente tras verificar sus bloques. El programa tiene las listas `init` y `frame`. Se comprime con DEFLATE **sin contenedor zlib ni gzip**, nivel 9 en el exportador actual. La longitud total debe ser exactamente `1792 + codeBytes`. El receptor descomprime en un búfer fijo de `rawBytes`, comprueba longitud y `codeCrc`, analiza el JSON y valida sus instrucciones antes de ejecutarlas. Código, reglas, niveles dibujados por programa, paleta y partituras viajan dentro del cartucho. No contiene URLs que descarguen reglas ni muestras PCM para el juego. ## Proyecto de Studio y ONDA BASIC El archivo `.json` de autoría guarda `name`, `author`, `cover`, `palette` y `source`. Si el código compila, Studio añade también `program`. Se pueden guardar borradores con errores; para probar o exportar deben compilar. - `cover`: exactamente 3.072 índices 0–15 para proyectos actuales. - `palette`: 16 colores hexadecimales `#RRGGBB`. - `source`: código ONDA BASIC y comentarios. No se transmite como texto fuente en el cartucho. - `program`: representación compilada opcional para el archivo de autoría. Studio requiere `source` y recompila ese texto; no migra proyectos ni recupera código ausente. El descompilador es una utilidad interna para inspeccionar programas. El editor permite abrir y guardar desde **Archivo**, configurar metadatos y gráficos desde **Cinta**, y probar desde **Ejecutar**. `Ctrl/Cmd+O`, `Ctrl/Cmd+S` y `Ctrl/Cmd+Enter` abren, guardan y prueban. Guardar descarga un JSON; exportar descarga un WAV. Ejemplo de código fuente completo: ```basic init x = 150 end frame clear 0 if key("right") then x = min(308, x + 100 * dt) end rect x, 100, 12, 12, 6 end ``` El programa compilado utiliza listas, no evalúa JavaScript. Ejemplo independiente de esa representación: ```json { "init": [["set", "x", 150]], "frame": [ ["clear", 0], ["set", "x", ["min", 308, ["+", "$x", ["*", ["key", "right"], ["*", 100, "$dt"]]]]], ["rect", "$x", 100, 12, 12, 6] ] } ``` ## Valores, expresiones e instrucciones de la VM Valores: números finitos, cadenas literales y variables representadas por cadenas con prefijo `$`. Los textos literales del lenguaje no pueden comenzar por `$`. Las variables no inicializadas y la memoria numérica empiezan en cero. `$dt` contiene el tiempo de simulación del fotograma, limitado a 0–0,04 segundos. El compilador impide asignar a `dt`. Las operaciones numéricas de la VM limitan sus operandos mediante `number()`; valores no numéricos se convierten en cero y los números se acotan a ±10.000.000. | Expresiones | Operaciones | | --- | --- | | Binarias | `+ - * / % < > <= >= == && || min max` | | Unarias | `abs ! key mem` | La división y el módulo por cero devuelven cero. `==` compara valor y tipo. Las comparaciones producen 1 o 0. La VM evalúa ambos operandos de las expresiones lógicas. El lenguaje traduce `and`, `or`, `not` a `&&`, `||`, `!`; `!=` se compila como negación de igualdad. `key` admite `left`, `right`, `up`, `down`, `a`, `b`, `start` y `select`. `mem` lee una de 1.024 celdas. Las direcciones de memoria se truncan hacia abajo y se limitan a 0–1023. | Instrucción VM | Argumentos | | --- | --- | | `set` | Nombre de variable sin `$`, valor. En BASIC: `x = valor`. | | `store` | Índice de memoria, valor numérico. | | `if` | Condición, lista cierta, lista falsa opcional. | | `for` | Nombre de índice, cantidad de iteraciones, lista. En BASIC: `repeat`. | | `clear` | Índice de color. | | `rect` | x, y, ancho, alto, color. Coordenadas y dimensiones redondeadas a píxeles enteros. | | `text` | Contenido, x, y, color, tamaño. `y` es la línea base. | | `sprite` | x, y, ancho, alto, índice inicial de píxel en carátula; paso de origen de 64 píxeles por fila. | | `ink` | Índice 0–15, color hexadecimal `#RRGGBB`. | | `cycle` | Primer índice, último índice, desplazamiento entero. | | `tone` | Frecuencia 60–4.000 Hz, duración 0,01–0,3 s. | | `music` | Canal, partitura, tempo, volumen. | | `note` | Canal, MIDI, segundos, volumen. | | `silence` | Canal. | | `vibrate` | Milisegundos, intensidad. | | `finish` | Mensaje final; detiene el programa. | `repeat` limita la cantidad a 0–1.024 iteraciones y usa índices desde cero. Las instrucciones de dibujo aceptan índices numéricos que se normalizan a los 16 colores. `ink` y `cycle` validan sus índices y rangos explícitamente. ### Presupuestos | Recurso | Límite actual | | --- | --- | | Fuente BASIC | 100.000 caracteres, 2.000 líneas. | | Programa JSON descomprimido | 65.536 bytes. | | Cartucho OND5 completo | 16.384 bytes. | | Operaciones del intérprete | 20.000 por inicialización o fotograma; incluye evaluación de expresiones. | | Memoria numérica | 1.024 celdas `Float64`. | | Profundidad del validador | 24. | | Nodos visitados por el validador | 12.000. | | Instrucciones por lista | 500. | | Cadenas del programa | Hasta 120 caracteres. | | `tone` | Cuatro llamadas por fotograma. | | `music` / `note` / `silence` / `vibrate` | Ocho llamadas compartidas por fotograma. | No hay funciones de usuario, acceso a DOM, red o archivos desde el cartucho. Los límites de complejidad se comprueban además de los límites de bytes. ## Pantalla, paleta y controles del sistema La pantalla actual tiene 320 × 240 píxeles, relación 4:3. El escalado visual conserva esa proporción. La consola Android permite el modo horizontal de juego cuando hay un juego cargado; la presentación vuelve al modo normal al girar a vertical. Cada VM conserva una paleta mutable independiente. `ink` cambia un color y `cycle` rota un rango inclusivo; los pasos negativos invierten la dirección. El renderizador mantiene índices por píxel y aplica la paleta al presentar la imagen: dos índices con el mismo RGB siguen siendo independientes. Al reiniciar se recupera la paleta del cartucho. Cambiar colores durante la partida no modifica la cinta guardada. Los presets CPC son ayudas del editor: se eligen 16 valores de la aproximación de 27 tonos RGB con componentes 0, 128 y 255. El texto usa fuentes bitmap incorporadas: m5x7 para tamaños 6–11; Press Start 2P a escalas 1, 2 y 3 para 12–15, 16–23 y 24, respectivamente. El rasterizado se alinea a píxeles enteros. Los glifos no disponibles se sustituyen por `?`. START inicia y después alterna la pausa del sistema. SELECT abre o cierra la ayuda. Pausa y ayuda congelan el programa; START no se entrega al juego como otra pulsación al reanudar. A/B conservan las acciones del cartucho. Los paneles del sistema y el final utilizan colores seleccionados de la paleta del juego, próximos al grafito, crema y amarillo de la consola. Un cartucho puede proporcionar `help1` y `help2` como cadenas para personalizar las dos primeras líneas de ayuda. Las cadenas opcionales `help3`, `help4` y `help5` añaden una leyenda de objetos; el panel adapta su altura y conserva al final la indicación de SELECT. ## Música y vibración: VM 3 No se transportan muestras: notas y partituras pertenecen al programa. | Canal | Voz | | --- | --- | | 0 | Pulso al 50 %. | | 1 | Pulso al 25 %. | | 2 | Triángulo. | | 3 | Ruido LFSR. | `tone` usa una voz transitoria adicional. `note channel, midi, seconds, volume` reemplaza la voz anterior del canal; duración de 0,01 a 12 s, MIDI 24–96 y volumen 0–1. MIDI 0 apaga la voz. `music channel, score, bpm, volume` repite una partitura. Cada token tiene formato `MIDI:tiempos`: notas 24–96 o 0 para silencio, duración 0,125–8 tiempos, hasta 120 caracteres, tempo 40–240 BPM. Repetir exactamente la misma instrucción en un canal no reinicia su secuencia. `silence channel` elimina la partitura y corta la voz. Una nota puntual puede ser reemplazada por el siguiente evento de una partitura que siga activa en ese canal. ```basic init last_a = 0 end frame if key("a") and not last_a then note 0, 76, .12, .3 vibrate 25, .3 end last_a = key("a") end ``` El ejemplo dispara efectos solo en una pulsación nueva. Para una melodía puntual, programa sus notas con `dt` o detén la partitura al acabar. `music` por sí sola continúa en bucle. Las partituras avanzan con el reloj de simulación. Al pausar, abrir Cintas o ayuda, pasar a segundo plano o cerrar la ventana de prueba de Studio, se cortan las voces y se suspende la simulación correspondiente. Al reanudar, la secuencia continúa; una nota cortada no vuelve a dispararse hasta el siguiente evento. Reiniciar o terminar elimina las voces de la partida anterior. `vibrate ms, intensity` solicita hasta 200 ms e intensidad 0–1. El adaptador háptico limita las peticiones a una cada 100 ms. Con el puente nativo `OndaHaptics`, la vibración está activada inicialmente; en web parte desactivada. Una preferencia guardada prevalece sobre ese valor inicial. Android utiliza el vibrador nativo; la web depende del soporte de `navigator.vibrate`. Studio reproduce sonido, pero no emite vibración. El juego no puede cambiar la preferencia del usuario. ## Transporte acústico El cartucho se divide en bloques de 128 bytes. El máximo OND5 supone **128 bloques**; se calcula sobre la longitud del contenedor ya comprimido, no sobre el código fuente ni sobre el JSON descomprimido. ### Trama antes de la corrección de errores Cada trama tiene 144 bytes: | Desplazamiento | Longitud | Campo | | --- | --- | --- | | 0 | 2 | Firma `0x4F32` (`O2`). | | 2 | 4 | Identificador: CRC32 del cartucho completo. | | 6 | 2 | Total de bloques: `ceil(longitud / 128)`. | | 8 | 2 | Índice del bloque en el cartucho, desde cero. | | 10 | 2 | Longitud total del cartucho. | | 12 | 128 | Datos; último bloque rellenado con ceros fuera de la longitud útil. | | 140 | 4 | CRC32 de los primeros 140 bytes de trama. | El CRC usa el polinomio reflejado `0xEDB88320`, estado inicial `0xFFFFFFFF` y XOR final `0xFFFFFFFF`. Detecta corrupción; no autentica al autor. Cada nibble, primero el alto, se codifica con Hamming extendido (8,4), SECDED. Corrige un bit y detecta dos por palabra. Las palabras se entrelazan en grupos de 16, recorriendo sus ocho planos de bits. Cada pareja de bits produce un símbolo 0–3: **1.152 símbolos por trama**, que seleccionan los cuatro tonos en el orden de la tabla siguiente. ### Perfiles de audio La síntesis usa PCM mono a 24.000 Hz. Hay 200 ms iniciales de silencio. Cada trama contiene piloto, pausa, símbolos y silencio final. | Perfil | Símbolos/s | Tonos para 0, 1, 2, 3 (Hz) | Piloto (Hz) | Piloto / pausa / final | Segundos por trama | | --- | --- | --- | --- | --- | --- | | Original | 600 | 1200, 2400, 3600, 4800 | 600 | 60 / 20 / 30 ms | 2,03 | | Turbo experimental | 1200 | 1200, 2400, 3600, 4800 | 3000 | 60 / 20 / 30 ms | 1,07 | | Lento | 300 | 900, 1200, 1500, 1800 | 2100 | 120 / 40 / 60 ms | 4,06 | | Muy lento | 150 | 900, 1200, 1500, 1800 | 2250 | 120 / 40 / 60 ms | 7,90 | Duración de la cinta: `0,2 + ceil(bytes / 128) × segundos_por_trama`. No incluye locución ni sintonía de Radio Pirata. Original es el valor predeterminado del sintetizador y de la exportación de Studio. Turbo conserva estructura, SECDED y CRC; reduce el tiempo con menor margen ante eco y ruido. El receptor actual detecta los perfiles automáticamente. El WAV es RIFF/WAVE PCM de 16 bits con cabecera de 44 bytes, mono, a 24.000 Hz. ### Orden de emisión y recuperación Se emiten primero los bloques 0 y 1 de cabecera. Después se intercalan proporcionalmente carátula y programa. La imagen ocupa los índices 2–13; su último bloque se emite al final. El índice del bloque sigue indicando su dirección original, aunque la emisión no sea secuencial. Cada trama debe superar SECDED y CRC. El ensamblador reconstruye el cartucho por índice; al completar la longitud útil verifica el CRC total. Después, el cargador valida cabecera, descompresión y programa. Solo los píxeles de bloques verificados se muestran. Con paleta propia se necesitan ambos bloques de cabecera para interpretar los colores. Las recepciones externas parciales se conservan por identificador en IndexedDB. Al iniciar la app se retienen hasta ocho recepciones parciales. Otra emisión puede completar huecos; un bloque erróneo no invalida uno ya verificado. El cambio de perfil de audio no cambia el identificador del cartucho. La matriz de recepción tiene 128 posiciones: negro para bloques no usados, gris para pendientes, verde para verificados y rojo para errores localizados. El bloque actual pulsa cuando se conoce su posición. Se muestra al grabar desde el exterior, no al cargar una cinta guardada. Para situar errores sin cabecera válida se necesita una referencia temporal de un bloque identificado y el orden de emisión; no toda señal fallida permite deducir un índice con certeza. ### Reproducción, FF y Radio Pirata - **PLAY local**: sintetiza la cinta guardada y alimenta el capturador/decodificador. Silenciar la escucha no interrumpe esa decodificación. - **REC**: recibe señal externa; en web usa micrófono y AudioWorklet, y en Android puede usar el capturador nativo. - **Compartir**: emite la cinta guardada por el altavoz sin cargar el juego en el emisor. Conserva los bytes del cartucho: OND5 ya está comprimido; no recomprime el programa al compartir. - **FF**: acelera la carga local a unos cinco segundos de audio desde el comienzo, además de la introducción visual del comando. Si se activa durante PLAY, conserva la posición y acelera lo restante. Una segunda pulsación completa la carga. La señal se escucha acelerada, pero no se decodifica acústicamente a esa velocidad: los bloques guardados pasan por la validación de trama/CRC. - **Radio Pirata**: genera la señal del cartucho para el perfil elegido y construye un WAV completo en memoria, incluyendo los segmentos de emisión. Lo reproduce mediante un elemento de audio; no depende de sintetizar cada fragmento desde temporizadores de la interfaz durante la reproducción. El remuestreador de recepción conserva fase entre bloques PCM. Los cambios de reloj, ruido y eco se prueban mediante hipótesis de sincronización y verificación; no se aceptan candidatos que fallen los controles de integridad. Los modos de mejora de recepción son opciones del receptor, no otro formato de cartucho. ## Validación y mantenimiento Fuentes de referencia del repositorio: - [`cartridge.js`](cartridge.js): cabecera, paleta, compresión, versiones y validador. - [`codec.js`](codec.js): tramas, CRC, SECDED, orden de emisión, perfiles y WAV. - [`language.js`](language.js): compilación/descompilación y sintaxis inglesa. - [`vm.js`](vm.js), [`bitmap-text.js`](bitmap-text.js): ejecución, representación y paneles del sistema. - [`chiptune.js`](chiptune.js), [`haptics.js`](haptics.js): voces, partituras y adaptadores hápticos. - [`block-progress.js`](block-progress.js), [`app.js`](app.js), [`radio.js`](radio.js): progreso y reproducción. `npm test` cubre cartuchos completos, corrupción y recuperación acústica sintetizada, remuestreo, compresión, límites, validación del lenguaje y rechazo de formatos ajenos, paletas y reglas de los juegos. Estas pruebas no equivalen a validar todos los teléfonos, altavoces o canales de audio reales. `npm run build` regenera los cartuchos de ejemplo y el WAV de Cosmic Cow. Tras modificar un juego, incluye los binarios regenerados en el cambio: CI comprueba que coincidan con las fuentes. El identificador depende de los bytes del cartucho completo, incluida su cabecera. `fflate.js` incorpora fflate 0.8.3 con licencia MIT, sin descarga en ejecución. La modificación local rechaza desbordamientos del búfer fijo en bloques almacenados, literales y referencias. Véase [`fflate-LICENSE.txt`](fflate-LICENSE.txt).