Las referencias a ficheros de código y a offsets del binario que verás aquí son la procedencia de cada afirmación: dicen de dónde sale. El motor está publicado bajo GPL-3.0-or-later en github.com/kokoima/openu5, así que se pueden ir a mirar una a una.
3 · Mejoras de experiencia
Controles táctiles, tocar el mapa para caminar, ranuras de guardado, el español, el historial con scroll, la consola de desarrollo y el atlas que construye tu navegador.
Cada punto de este documento está confirmado contra el código, no interpretado.
Índice
- Tocar el mapa para caminar ✨
- Controles táctiles: el deck ✨
- Múltiples ranuras de guardado ✨
- El español ✨
- El historial con scroll ✨
- La consola de desarrollo ✨
- El atlas que construye tu navegador ✨
1 · Tocar el mapa para caminar
✨ AÑADIDO · Tocando o pulsando una casilla del visor · Código: game/src/ui/autowalk.ts, game/src/core/world/pathfind.ts, skin/fiel/skin.ts (emisión del intent)
Se toca una casilla del visor y el grupo camina hasta ella, por el mismo camino que una flecha del teclado.
Antes
Flechas del teclado, un paso por pulsación. Sin teclado —o sea, en un móvil— quedaba la cruceta de la botonera, un toque por casilla.
Ahora
Se toca una casilla del visor y el grupo camina hasta ella. Un paso cada 140 ms, siguiendo el camino que calcula un A* (findPath) sobre el mapa activo y respetando el medio de transporte (a pie, a caballo, alfombra, esquife, barco: el mismo transport que usa el motor).
La decisión de diseño que lo hace seguro
Cada paso pasa por game.move(dir) y applyEvents — exactamente el mismo camino que una pulsación de flecha. No hay una ruta paralela que teletransporte, ni un atajo que se salte los eventos del turno. Es la diferencia entre «una comodidad» y «una segunda implementación del movimiento que se desincroniza».
Y se cancela ante:
- cualquier comando manual,
- combate (caminar solo en combate era exactamente lo que no se quería),
- cambio de mapa,
- y
"Blocked!"— si el camino se corta a mitad, la marcha para.
Contrapartidas
- El paso es de 140 ms fijo. No acelera en trayectos largos. Cruzar Britannia tocando el borde del visor es lento a propósito: cada paso es un turno del juego, con sus encuentros y su reloj.
- Sin camino, no pasa nada.
findPathdevuelvenully la función es un no-op silencioso: tocar una montaña no da mensaje de error. Es discreto, pero también es indistinguible de un toque no registrado. - Es QoL, no fidelidad. El original no tenía ratón. Está en el mismo saco que el resto de este documento.
2 · Controles táctiles: el deck
✨ AÑADIDO · Solo, donde el puntero es grueso (pointer: coarse) · Código: game/src/ui/touch.ts (fuente única de la botonera), skin/portrait/deck-ancho.ts (CSS), deck-nativo.ts (comportamiento)
Botonera táctil con todos los comandos del juego, censada contra el binario, que dispara al levantar el dedo y aparece cuando el puntero es grueso.

La botonera aparece cuando el puntero es grueso (pointer: coarse) — se mide el puntero, no el user-agent. Y no es un criterio elegido para esto: es el mismo predicado con el que aparece el layout partido y con el que la piel decide su encaje móvil. Atarlos garantiza que el layout partido y la botonera que lo hace usable aparezcan siempre juntos.
Botones, derivados de las tablas del código el 16-08-2026 (las cifras las carea game/tests/dungeon-botonera.test.ts, así que no pueden volver a quedarse rancias):
| Hoja | Botones |
|---|---|
Mundo (WORLD_BUTTONS) | 25 |
Utilidad (UTIL_BUTTONS) | 3 |
Mazmorra (DUNGEON_BUTTONS) | 18 |
Combate (COMBAT_BUTTONS) | 13 |
Completa contra el original, no contra el recuerdo
La botonera no salió de una lista de memoria: se censó contra el despachador real del binario (kernel_cmd_dispatch @0x3178, caso por caso, más la tabla de saltos de 'F'..'L' en 0x3490), y las hojas contextuales contra el bucle de turno de la arena y el gate por localización de mazmorra. Todo comando que el original acepta tiene botón — encender una antorcha de noche a la intemperie, atacar en un pueblo o mirar una gema ya no exigen teclado físico.
El precio, dicho: los comandos que el censo añadió van al final de la rejilla, para no mover los botones que quien ya usaba el deck tenía memorizados bajo el dedo. Atacar y Encender antorcha, de los más frecuentes, quedan bajo el pliegue.
El disparo, al levantar el dedo
Los botones deciden en pointerup, no al apoyar: es un tap sólo si el dedo no se desplazó más de 10 px (≈ el touch-slop de Android/iOS), y si el navegador se queda el gesto para su pan nativo, no cuenta. Deslizar para ver más comandos ya no pulsa el botón de debajo. La única excepción es la cruceta, que se queda en pointerdown: andar quiere respuesta inmediata y repetición al mantener, y no vive en una zona scrolleable.
El botón ☰: el menú SISTEMA a un toque

El ☰ del deck abre el menú SISTEMA directamente — la misma superficie que F10 y que el botón ⚙. Aquí hubo un popover intermedio de cinco entradas y se retiró tras medirlo: para llegar a las partidas, el vídeo o las teclas había que dar dos toques siempre, y el primero no decidía nada. Sus entradas viven hoy en el propio menú: Cambiar el pad de lado (mueve la cruceta; el defecto es derecha, «para los diestros», deck-nativo.ts:ladoGuardado) y Layout partido (vertical) — el mismo conmutador que el ▤ del que habla 2 · Layouts §4.
La pista de que hay más abajo
Los comandos bajo el pliegue eran invisibles: «no parece que haya más». La solución es un chevron con degradado en el borde correspondiente cuando hay contenido fuera de la vista (ui/scroll-hint.ts). Los dos overlays llevan pointer-events: none, así que jamás roban un tap.
Contrapartida: medir el puntero también acierta de más
Atarse a pointer: coarse es lo correcto —el user-agent miente y un criterio propio dejaría el layout partido sin sus botones—, pero tiene un coste que conviene saber: un portátil con pantalla táctil da coarse. Ese usuario tiene teclado y ratón, y aun así recibe el deck y el layout partido por defecto.
No es un fallo del predicado: es que la pregunta «¿hay dedos?» y la pregunta «¿es esto un móvil?» no son la misma, y sólo la primera se puede medir. La salida existe y es de un clic —el ▤ del deck, o la casilla de ⚙ → Vídeo— y la preferencia manda sobre el defecto en los dos sentidos, así que el que lo apaga se queda apagado. Pero el primer arranque en ese equipo se ve como un móvil.
3 · Múltiples ranuras de guardado
✨ AÑADIDO · Código: game/src/core/persistence.ts, ui/savepanel.ts · F5
Ranuras con nombre e ilimitadas, con exportar e importar, en vez del único SAVED.GAM del original.
Antes
El original tenía un único SAVED.GAM. Está dicho en la primera línea del docstring de persistence.ts, y es la razón de ser del panel.
Ahora

Ranuras con nombre, ilimitadas, cada una con su lugar, su turno y su fecha, y botones de Cargar y Borrar. Debajo: nombrar y Guardar, más Exportar, Exportar .GAM e Importar.
Las cuatro decisiones que hay detrás
localStorage, no OPFS. OPFS sería más limpio para blobs grandes, pero su API es asíncrona y aún desigual entre navegadores.localStoragees síncrono, universal y de sobra para unGameStateserializado.- Índice ligero aparte. El índice vive en
u5clone:savesy cada partida completa enu5clone:save:<id>, para que listar las ranuras no obligue a deserializar todos los estados. - Nunca se lanza por cuota llena. El hallazgo del soak fue que un
QuotaExceededErrorsin capturar en el autoguardado del arranque dejaba el juego medio inicializado — un boot zombie. AhorasaveGamedevuelve{ok:false, reason:"quota"}y el llamante decide: mensaje al usuario en el guardado manual, silencio y log en el autoguardado. - Autoguardado rotatorio de 3 ranuras (
autosave-1/2/3, round-robin) para que un autoguardado no pise al anterior.
El puente con el original, que es lo elegante
«Journey Onward» sigue funcionando. El menú de portada del original recarga SAVED.GAM verbatim; aquí SAVED.GAM = la ranura con mayor timestamp, sea un autoguardado o un guardado manual. Sin ninguna, arranca partida nueva — igual que el SAVED.GAM por defecto que traía el disco. La multi-ranura se añade sin romper el flujo de 1988.
Exportar en formato de 1988
«Exportar .GAM» produce un SAVED.GAM + SAVED.OOL + sidecar en el formato de disco original, a partir de una plantilla de 4192 B. Se puede sacar una partida del port y —en principio— llevarla al juego real.
Contrapartidas
localStoragese puede vaciar sin avisar, y una app instalada en la pantalla de inicio arranca con partición de almacenamiento propia: las partidas del navegador no viajan a ella. Es el mismo mecanismo que hace que instalar el port en la pantalla de inicio parezca devolver una versión anterior — ver 2 · Layouts §4.- El
.GAMnativo depende de dos plantillas de assets. Si faltainit.ool, se exporta con bloques a cero: degradación declarada, no un fallo silencioso.
4 · El español
✨ AÑADIDO · Código: game/src/i18n/ · Cambio en vivo con el botón 🌐 / ES
Todo el texto del juego en español, como tabla de sustitución sobre el inglés del calco y conmutable en vivo.
| Español | Inglés |
|---|---|
![]() | ![]() |
El modelo, que es lo que lo hace seguro
El inglés es el suelo del calco, no «un idioma más»: todo el texto sale byte-exacto del binario y contra él están ancladas todas las guardas (pixeldiff, Grand Tour, approved-strings). Un idioma ≠ inglés es una tabla de sustitución paralela, indexada por el string inglés original, resuelta en un único punto de estrangulamiento (t()):
lang === 'en'⇒t()es la identidad estricta. Ni una llamada altera la salida; el calco y las guardas quedan intactos.lang !== 'en'⇒t(str)devuelvetabla[str] ?? str. Un idioma incompleto degrada a inglés string a string, lo que permite aterrizarlo por lotes.
Esa última propiedad es la que hace que traducir un juego calcado byte a byte no sea un riesgo: lo que falta no rompe, se ve en inglés.
Dónde vive la tabla, y por qué ahí
En src/i18n/ (código fuente versionado), no en game/assets/. assets/ es material extraído del binario, propiedad de EA y fuera de git; la tabla de idioma es contenido autorado nuevo, y debe versionarse.
Estado, medido el 04-08-2026
| Métrica | Valor |
|---|---|
Entradas en es.json | 4 000 |
Marcadas como revisadas (reviewed: true) | 3 994 |
| Comillas del idioma | « … » |
Hay una capa aparte para el shell (menús, paneles: texto autorado, no del binario) con su propia función ts().
Contrapartidas
- Los nombres de piel no se traducen — son nombres propios. Sólo el prefijo «Piel».
- El cuerpo de los letreros va en runas y el historial en latín. El DOS pinta los carteles en runas con los dígrafos colapsados y el port hace lo mismo (game/src/skin/fiel/sign-box.ts:148, rúnico salvo que te opongas); el texto se sigue leyendo en latín en el historial de la consola, en el idioma que tengas puesto.
5 · El historial con scroll
✨ AÑADIDO · Rueda o arrastre sobre la consola · Código: game/src/skin/fiel/skin.ts (ConsoleScrollback, consoleScrollLines, consoleScrollToLive)
La consola guarda historial: rueda o arrastre para releer lo que se salió por arriba, con la banda >HISTORIAL< del marco de 1988.
Antes
La consola del original muestra las últimas líneas y nada más. Lo que se sale por arriba se ha ido: no hay forma de releer qué dijo un NPC hace tres turnos.
Ahora
| Consola viva | Modo historial |
|---|---|
![]() | ![]() |
Rueda o arrastre sobre el área de consola y se entra en modo historial. Fíjate en la banda que aparece en el chrome: >HISTORIAL<, con los mismos remates de banda que el resto del marco de 1988. No es un scrollbar moderno pegado encima; es un indicador dibujado en el lenguaje de la piel.
Se sale volviendo al fondo o con cualquier tecla: nunca te quedas atrapado en el pasado mientras el juego espera un comando.
Los detalles que lo hacen no estorbar
- Por zonas, sin colisiones. La rueda sobre la consola desplaza el historial; sobre el panel con una lista de Ztats abierta, desplaza la lista; sobre el visor, nada (ahí vive el tocar-para-caminar). Fuera de una lista, la rueda sobre el panel ni se captura — la piel fiel queda intacta.
- El arrastre «tira del papel»: hacia abajo revela lo viejo, en las dos zonas.
- El resto sub-línea se acumula. Un arrastre lento no se pierde:
restguarda la fracción entrepointermoves y sólo aplica líneas enteras. - Byte-idéntico en reposo. Con
consoleScroll = 0el render es el de siempre. La mejora no cuesta un píxel cuando no se usa. - La piel shader lo reenvía. No lo reimplementa: captura los eventos de su canvas y llama a
consoleScrollLinesde la fiel que aloja.
Contrapartida
El historial es de la sesión, no de la partida: es estado de la piel, no del GameState. Recargar la página lo pierde.
6 · La consola de desarrollo
✨ AÑADIDO · Tecla ` ` o F4, o ⚙ → «Debug (QA)» · Código: game/src/debug/ (panel, registro de secciones, fachada window.__u5debug`), gate en game/src/main.ts
El panel de depuración del proyecto, abierto para todos: edita casi cualquier campo de la partida — y editar la deja marcada como tocada, para siempre.
Antes
El original no trae nada parecido. Quien quería trucar una partida de 1988 editaba SAVED.GAM a byte con una herramienta externa, sin red: un byte mal puesto y el save no carga.
Ahora
El mismo panel con el que se depura el port viaja en el juego publicado, activo por defecto (la salida de emergencia es ?debug=0 en la URL). Es un drawer lateral con buscador de campos y un acordeón de secciones: teletransporte con selector de mapa point-and-click, party y stats, recursos e inventario, reloj de Britannia, banderas de trama, NPCs, moonstones, transportes. Escribe directo en el estado vivo, sin consumir el RNG del motor — editar no desincroniza el determinismo de la partida.
En móvil no hay backquote ni F4: la entrada es ⚙ → «Debug (QA)» → «Abrir menú debug», siempre disponible.

El cajón sobre el borde derecho del juego, en el escritorio.
![]() | ![]() |
|---|
En el teléfono: la entrada por ⚙ → «Debug (QA)» y el mismo cajón, a lo ancho.
Contrapartida
Es deliberada y visible: editar marca la partida. La primera mutación que pasa por el panel estampa una marca persistente en el save (viaja con él, sobrevive a guardar y cargar) y enciende un distintivo discreto en pantalla — ⚑ EDITED. Abrir el panel y mirar no marca; editar sí, y la marca no se puede quitar desde el propio panel. Una partida tocada queda distinguible de una jugada limpia — a propósito.
![]() | ![]() |
|---|
La misma pantalla antes y después de cerrar el cajón: el distintivo ⚑ EDITED se queda.
7 · El atlas que construye tu navegador
✨ AÑADIDO · ⚙ → «Help · Atlas & guide», o directamente en /companion · Código: docs/manual/companion/byo-runtime.mjs (la construcción en el cliente), sonda del menú en game/src/ui/shell/companion.ts
Una guía completa —recorrido, bestiario, tablas de conjuros y reactivos, mapas navegables— que no guarda ni un dato del juego: los mapas, los interiores y los retratos los dibuja tu navegador con tu copia.
Antes
El original traía su documentación en la caja: el mapa de tela, el libro de conjuros y el Book of Lore. Quien juega hoy con una copia digital rara vez tiene nada de eso a mano, y lo que circula por la red son escaneos de material que no es de nadie que los reparta.
Ahora
El port trae su propio atlas, servido en /companion y enlazado desde el juego en SISTEMA → Help · Atlas & guide. El enlace sólo aparece si la sonda responde, así que nunca lleva a una página que no está.
Lo que hay dentro está partido en dos mitades, y la partición es el asunto entero:
- La mitad que escribe el proyecto —recorrido, bestiario, tablas de conjuros y reactivos, lore, pergaminos, créditos— viaja con el sitio: son 832 KB propios, y no llevan ni un byte del original.
- La mitad que no es nuestra —mapas dibujados, interiores, sprites, retratos, el diálogo— no está ahí dentro: la fabrica tu navegador a partir de los ficheros que aportas en
/byo, arrastrando la carpeta o un.zip. No se sube nada a ningún sitio: los datos acaban en el almacén de tu propio navegador y los sirve un Service Worker.
Sin aportar la copia el atlas no se rompe: sigue dando entera la mitad escrita, y marca las secciones que necesitan datos con una puerta a /byo en vez de una página en blanco.

El atlas con una copia aportada: el mapa lo dibuja el navegador, tile a tile.
![]() | ![]() |
|---|
La mitad que escribe el proyecto —aquí, los ocho reactivos— no necesita tu copia; a la derecha, el atlas en el teléfono.
Contrapartida
Dos, y las dos a propósito:
- Sin tu copia hay medio libro, no un libro peor. Las secciones de datos se marcan como lo que son; no se rellenan con una aproximación ni con material bajado de ningún sitio.
- El atlas no lee tu partida. Se abre en otra pestaña, no sabe dónde estás y no te señala el siguiente objetivo: es material de consulta, como el manual de la caja. Averiguar adónde ir a partir de una conversación es el juego, y una flecha lo sustituiría.









