1. Visión general
El Reverse Player es un intérprete tree-walk implementado en JavaScript vanilla, sin dependencias externas. Corre en el browser mediante Canvas 2D API, Web Audio API y fetch(). Está empaquetado como un conjunto de archivos modulares que se sirven desde un servidor HTTP o se consolidan en un único HTML autocontenido.
La separación entre el núcleo del intérprete (Lexer, Parser, Intérprete) y los módulos de runtime es la decisión arquitectónica central. El intérprete recorre el AST y llama a un objeto runtime cuyo contrato es independiente de la plataforma. Ver Manual del Intérprete para el detalle de cada contrato.
2. Decisiones de diseño fundamentales
Estas decisiones fueron establecidas en el diseño original y sobrevivieron sin cambios a toda la implementación:
3. Pipeline completo
4. Orden de tick por frame
5. Sistema two-canvas
El Player usa dos elementos <canvas> separados. El canvas virtual (#virtual-screen, oculto) tiene la resolución QBJr. El canvas de display (#screen, visible) recibe una copia escalada cada frame.
6. Estructura de archivos
7. Lexer — qbjr-lexer.js
Transforma el código fuente en una secuencia plana de tokens tipados. Es el único módulo que toca el texto fuente.
| ASPECTO | DETALLE |
|---|---|
| Tokens | KEYWORD · IDENTIFIER · NUMBER · STRING · OPERATOR · LPAREN/RPAREN · LBRACKET/RBRACKET · NEWLINE · EOF · ERROR |
| AND OR XOR NOT | KEYWORD solo en MAYÚSCULAS. En minúsculas son IDENTIFIER. |
| La letra A | IDENTIFIER en el lexer. El parser la reconoce contextualmente en PARA i DE 1 A 10. |
| Case sensitivity | Keywords normalizadas a UPPERCASE. Variables preservan el case original. |
8. Parser — qbjr-parser.js
Recursive descent manual. Precedencia de operadores en 8 niveles (Pratt-style). Pre-pasada de subprogramas para habilitar HACER antes de DEFINIR.
| ASPECTO | DETALLE |
|---|---|
| Técnica | Recursive descent con 8 niveles de precedencia. |
| Pre-pasada | Los bloques DEFINIR se extraen antes del parse principal. |
| Dos palabras | 30+ pares de keywords emitidos como un único nodo (CREAR_SPRITE, PONER_TILE, etc.). |
| COLISIONA infijo | Se trata en parseComparison() como operador de comparación. |
| Errores | Se acumulan sin abortar. Múltiples errores reportados en una pasada. |
9. Intérprete — qbjr-interpreter.js
Recorre el AST con JavaScript Generators. Cinco helpers críticos abstraen las ambigüedades del lenguaje:
| HELPER | PROPÓSITO |
|---|---|
| varKey(name, estado) | Lookup case-insensitive de variables. Retorna la clave canónica. |
| rawS(i) | Retorna el nombre literal de un Identifier SIN evaluar como variable. Para constantes nombradas (PALETA, COLOR, etc.). |
| argNom(i) | Retorna el nombre raw de un arg de FuncCall. Para sprites y archivos en funciones (EN_SUELO, FIN_ARCHIVO, etc.). |
| argStr(i) | Semántica híbrida para LEER_DATO. Variable string no vacía → su valor. TODO_MAYÚSCULAS → clave literal. Mixto → nombre literal. |
| yieldPromise(p) | Suspende el generator hasta que la Promise se resuelva. Para ABRIR y CARGAR_DATOS. |
10. Canvas RT — qbjr-canvas.js
Implementa todos los comandos de dibujo sobre el canvas virtual. Firma crítica: rectangulo(x, y, ancho, alto) — ancho antes que alto. Requiere ctx.textBaseline = "top" antes de cada fillText.
11. Input RT — qbjr-input.js
Abstrae teclado, mouse y touch en estados booleanos. Los botones táctiles despachan KeyboardEvent sintéticos. frameReset() limpia estados de frame al inicio de cada tick.
12. Sprites RT — qbjr-sprites.js
Motor de sprites e instancias. Cada instancia tiene hitbox independiente del visual. Animaciones por fotogramas. Colisiones AABB. tick(ctx, dt) avanza animaciones y dibuja.
13. Audio RT — qbjr-audio.js
Dos subsistemas: archivos externos (AudioBufferSourceNode) y síntesis por pistas (OscillatorNode). AudioContext inicializado de forma lazy por política de autoplay. Notas musicales en español (Do4, Re#3, Lab5) con conversión a Hz por temperamento igual.
14. Layers RT — qbjr-layers.js
6 capas como OffscreenCanvas independientes. Capas 0-2: parallax. Capa 3: principal. Capa 4: frontal. Capa 5: HUD estático. composite() dibuja todas en orden sobre el virtual canvas.
15. Tiles RT — qbjr-tiles.js
Tilesets como imágenes divididas. Tilemaps como grillas por capa. Pre-renderizado en OffscreenCanvas con flag dirty — solo redibujan los tiles modificados. Tiles RIGIDO generan hitboxes automáticas para la física.
16. Physics RT — qbjr-physics.js
Integración Euler semi-implícita. dt clampado a 50ms (anti-tunneling básico). Resolución AABB separación mínima por eje. Fricción en suelo (vx × 0.85/frame). Velocidad máxima 1200px/s.
17. Files RT — qbjr-files.js
LECTURA via fetch(), ESCRITURA via localStorage con prefijo "qbjr:". Formato .qdat parseado a secciones y claves. reset() debe llamarse en engine.stop() para limpiar datasets de la sesión anterior.
18. Text RT — qbjr-text.js
Pipeline de markdown en 4 etapas: parseBlocks → parseInline → wrapTokens → renderInlineLine. Soporta énfasis, color (nombre o hex), encabezados, subíndice, superíndice. El 5° argumento de TEXTO_MD limita la altura de render.
19. Engine — qbjr-engine.js
| MÉTODO | DESCRIPCIÓN |
|---|---|
| cargar(fuente) | Lexea + parsea. No ejecuta. Retorna errores de parseo. |
| play() | Inicia el RAF loop. Reanuda si estaba pausado. |
| pause() | Cancela RAF sin destruir el generator. |
| stop() | Termina, destruye el generator, llama filesRT.reset(). |
| clearPause() | Limpia el flag de PAUSAR programático. Llamar al presionar START. |
20. Estado actual del proyecto (v0.9.31)
Todos los módulos planificados están implementados. El stack está completo. La versión 1.0.0 del motor no existe todavía: el criterio es especificación cerrada + intérprete sin bugs estructurales conocidos + suite de demos representativa completa.
| MÓDULO | VERSIÓN INCORPORADA | ESTADO |
|---|---|---|
| qbjr-lexer.js | v0.1.0 | ✓ AND/OR/XOR/NOT case-sensitive. Soporte español. |
| qbjr-parser.js | v0.1.0 | ✓ 30+ comandos dos palabras. COLISIONA infijo. argStr. |
| qbjr-interpreter.js | v0.1.0 | ✓ Generators. varKey, rawS, argNom, argStr, yieldPromise. |
| qbjr-canvas.js | v0.1.0 | ✓ Flood fill. Press Start 2P. textBaseline top. |
| qbjr-input.js | v0.2.0 | ✓ Teclado+touch+mouse. frameReset. Combos. |
| qbjr-sprites.js | v0.4.0 | ✓ Sprites/instancias. Animaciones. AABB. |
| qbjr-audio.js | v0.5.0 | ✓ Web Audio + síntesis. Pistas/figuras/dinámicas. |
| qbjr-layers.js | v0.6.0 | ✓ 6 capas. Parallax. Scroll/RUTA. |
| qbjr-tiles.js | v0.7.0 | ✓ Tilesets/tilemaps. Tiles rígidos. COLISIONA_TILE. |
| qbjr-physics.js | v0.8.0 | ✓ Gravedad. Velocidad. Resolución AABB vs tiles. |
| qbjr-files.js | v0.9.0 | ✓ ABRIR/LEER/ESCRIBIR. Formato .qdat. |
| qbjr-text.js | v0.9.16+ | ✓ Markdown extendido. TEXTO_MD con alturaMax. |
| qbjr-engine.js | v0.2.0 | ✓ RAF. Two-canvas. Orden de tick correcto. |
21. Portabilidad
Los módulos de runtime son la única capa que necesita reimplementarse por plataforma. El núcleo (Lexer, Parser, Intérprete) puede correr sin cambios en Node.js, Deno o QuickJS.
| PLATAFORMA | EQUIVALENTES |
|---|---|
| Generators JS | C++20 coroutines · C# async/await · Python generators |
| Canvas 2D API | SDL_Renderer · Godot CanvasItem · SFML |
| Web Audio API | SDL_mixer · miniaudio · Godot AudioStreamPlayer |
| requestAnimationFrame | SDL game loop · Godot _process() · Unity Update() |
| fetch + localStorage | fopen/fclose · File API del SO |
| OffscreenCanvas (layers/tiles) | Render textures · Godot CanvasLayer |