OPENU5 Portada EN

Estas páginas se generan a partir de la documentación interna del proyecto. Las referencias a ficheros de código y a offsets del binario se conservan como procedencia —para que se vea de dónde sale cada afirmación—, pero todavía no se pueden seguir: el motor aún no está publicado. Cuando lo esté, pasarán a ser enlaces.

3 · Mejoras de experiencia

Controles táctiles, tocar el mapa para caminar, ranuras de guardado, el español y el historial con scroll.

Los cinco puntos están confirmados contra el código, no interpretados. Al final del documento hay una sección sobre lo que se comprobó y cómo. También hay una mejora retirada y un defecto vivo encontrado hoy que no estaban en el encargo.


Índice

  1. Tocar el mapa para caminar
  2. Controles táctiles: el deck
  3. El censo que faltaba: cinco comandos inalcanzables
  4. El bug del «on press»
  5. Múltiples ranuras de guardado
  6. El español
  7. El historial con scroll
  8. Lo que se QUITÓ: el diario y el minimapa 🗑️
  9. Un defecto vivo encontrado escribiendo esto
  10. Qué se verificó y cómo

1 · Tocar el mapa para caminar

AÑADIDO · Código: game/src/ui/autowalk.ts, game/src/core/world/pathfind.ts, skin/fiel/skin.ts (emisión del intent)

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:

Contrapartidas


2 · Controles táctiles: el deck

AÑADIDO · Código: game/src/ui/touch.ts (fuente única de la botonera), skin/portrait/deck-ancho.ts (CSS), deck-nativo.ts (comportamiento)

Deck táctil, layout partido

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, medidos en vivo el 04-08-2026:

HojaBotones
Mundo (WORLD_BUTTONS)25
Utilidad (UTIL_BUTTONS)3
Mazmorra (DUNGEON_BUTTONS)5
Combate (COMBAT_BUTTONS)2

El menú ☰

Menú del deck

Seis entradas: ES (idioma) · Piel · Sistema · Cambiar el pad de lado · Layout: partido / original · Cerrar.

«Cambiar el pad de lado» mueve la cruceta entre izquierda y derecha; el defecto es derecha, «para los diestros» (deck-nativo.ts:ladoGuardado). Y «Layout: partido / original» es el ▤ del que habla 2 · Layouts §4.

La pista de que hay más abajo

Los ~14 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).

Detalle que evita un bug clásico: los dos overlays llevan pointer-events: none, así que jamás roban un tap. Y la lógica (scrollHintState) es pura, con un épsilon de 2 px porque scrollTop llega con fracciones cuando el DPR no es 1.

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 menú ☰, o ⚙ → 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 · El censo que faltaba: cinco comandos inalcanzables

AÑADIDO · Acta: movil-cmds-acta (28-07-2026)

La pregunta del usuario

«El comando ignite torch está en los comandos botones de móvil? haz revisión de que tengamos todos los comandos en la lista de botones.»

Cómo se contestó, que es lo interesante

No con una lista de memoria, sino leyendo el despachador real: kernel_cmd_dispatch @0x3178, caso por caso, más la tabla de saltos de 'F'..'L' en 0x3490. Eso da el conjunto completo de comandos, no el que alguien recordaba.

De paso salieron dos no-comandos: 'D' y 'W' imprimen su propio «D-What?»/«W-What?», y el default en 0x34d8 cierra la tabla con «What?».

El resultado

No, «Ignite torch» no estaba: existía sólo en la hoja de MAZMORRA. De noche a la intemperie no había forma de encender una antorcha sin teclado físico. Y con él faltaban otros cuatro:

ComandoTeclaDónde estaba
AttackAsólo en COMBATE — en pueblo/overworld, inalcanzable
Ignite torchIsólo en MAZMORRA — a la intemperie, inalcanzable
New OrderNen ningún sitio
Quit & SaveQen ningún sitio (el «Save» del deck es F5 = panel multi-partida, no el flujo fiel por consola)
View a gemVen ningún sitio

Los cinco se añadieron al final de WORLD_BUTTONS, sin reordenar lo que el usuario ya tenía memorizado bajo el dedo. Y se añadieron a una sola tabla: los decks de vertical y apaisado la consumen a través de TouchControls, no tienen copia propia.

Comandos nuevos en el deck

Por qué importa más allá del arreglo

Cinco comandos de veinticinco estaban fuera de alcance para cualquiera sin teclado, y la lista llevaba tiempo pareciendo completa. Lo que la destapó no fue una revisión: fue comparar contra la fuente de verdad en vez de contra el recuerdo de quien la escribió.

Contrapartida: la lista ya no tiene un orden defendible

Añadirlos al final protege la memoria muscular de quien ya usaba el deck —el motivo por el que se hizo así— pero el precio es que la rejilla deja de tener una lógica que un recién llegado pueda seguir. Atacar y Encender antorcha, que son de los comandos más frecuentes, quedan al fondo, por debajo del pliegue, detrás de Prestar o Abordar.

Es la elección correcta para los usuarios que ya estaban y la peor para los que llegan. Reordenar la rejilla es un cambio de producto pendiente, no un olvido: la alternativa era mover los botones bajo el dedo de alguien que ya los tenía situados.

Y un bug de presentación del mismo carril

antesdespués
space, antesspace, después

Al pulsar espacio, el original crea una entrada de log con su >; el port ponía líneas sin > y pegadas unas a otras.


4 · El bug del «on press»

AÑADIDO (arreglo de un añadido) · Código: game/src/ui/tap-or-drag.ts, deck-nativo.ts

El problema

Los botones del deck disparaban su comando en pointerdown: apoyabas el dedo y ya estaba hecho, sin poder abortar deslizando fuera. En cuanto la zona de comandos se hizo scrolleable por toque, cualquier intento de arrastre nacía sobre un botón y disparaba el comando de debajo.

El arreglo

TapGate: se registra el pointerdown, se sigue el pointermove, y en pointerup se decide. Es un tap sólo si el puntero no se desplazó más de 10 px (distancia de Chebyshev). Diez píxeles ≈ el touch-slop de Android/iOS: el temblor natural del dedo no cancela el tap, un arrastre deliberado sí.

Y cubre el caso que se olvida: cuando el navegador se queda el gesto para su pan nativo, emite pointercancel — el gate lo traduce a «no era tap».

La excepción deliberada

La cruceta se queda en pointerdown. Andar quiere respuesta inmediata y repetición al mantener; exigir el pointerup haría el movimiento esponjoso. Además la cruceta no vive en una zona scrolleable, que es de donde venía el problema. Es la única excepción y está escrita como tal.

Y cómo se hizo sin tocar ui/

Un interceptor en fase de captura sobre el deck corta el pointerdown antes de que llegue al botón, y el módulo pone su propio disparo en pointerup. Cero líneas modificadas en ui/touch.ts.


5 · Múltiples ranuras de guardado

AÑADIDO · Código: game/src/core/persistence.ts, ui/savepanel.ts · F5

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

Panel de partidas con dos ranuras

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

  1. localStorage, no OPFS. OPFS sería más limpio para blobs grandes, pero su API es asíncrona y aún desigual entre navegadores. localStorage es síncrono, universal y de sobra para un GameState serializado.
  2. Índice ligero aparte. El índice vive en u5clone:saves y cada partida completa en u5clone:save:<id>, para que listar las ranuras no obligue a deserializar todos los estados.
  3. Nunca se lanza por cuota llena. El hallazgo del soak fue que un QuotaExceededError sin capturar en el autoguardado del arranque dejaba el juego medio inicializado — un boot zombie. Ahora saveGame devuelve {ok:false, reason:"quota"} y el llamante decide: mensaje al usuario en el guardado manual, silencio y log en el autoguardado.
  4. 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


6 · El español

AÑADIDO · Código: game/src/i18n/ · Cambio en vivo con el botón 🌐 / ES

EspañolInglés
consola en españolconsola en 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()):

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étricaValor
Entradas en es.json4 000
Marcadas como revisadas (reviewed: true)3 994
Comillas del idioma«»

⚠️ Ojo con el docstring. La cabecera de i18n/index.ts sigue diciendo «una SEMILLA de 20 strings (es.json) para probarlo de punta a punta». Eso describía el alcance de la fase F1 y hoy está caducado por dos órdenes de magnitud. Las 4 000 de arriba están contadas en vivo sobre el fichero; el docstring no.

Hay una capa aparte para el shell (menús, paneles: texto autorado, no del binario) con su propia función ts().

Contrapartidas


7 · El historial con scroll

AÑADIDO · Código: game/src/skin/fiel/skin.ts (ConsoleScrollback, consoleScrollLines, consoleScrollToLive)

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 (líneas 36-40)Modo historial (líneas 06-11)
consola vivamodo 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

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.


8 · Lo que se QUITÓ: el diario y el minimapa

🗑️ RETIRADO · y (minimapa), 26-07-2026

Esta sección es la que hace honesto el resto del documento. El port tuvo dos comodidades muy cómodas y las quitó a propósito:

QuéTeclaPor qué se fue
Diario (registro de charlas)F6El original no tiene diario en ninguna tecla: getkey @0x1D5E mapea F1..F10 a 0xC9..0xD2, que caen en el «What?» del despachador. Era un popup ajeno al UI original.
Minimapa (con niebla de guerra)TabEl original sólo enseña el mapa por (V)iew con la gema. El panel de Tab regalaba gratis y sin gema lo que el original cobra.

Los dos son veredicto explícito del usuario, del mismo día.

Lo que se conservó, y por qué

La lección que dejan

Un port de esta clase tiene dos formas de mejorar el juego, y son opuestas: añadir lo que el original no podía dar, y quitar lo que le roba su economía. El minimapa era comodísimo. También convertía en decorativo un objeto por el que el juego te hace pasar media aventura.

⚠️ Aviso para quien lea docs/verdicts/shell-8x8/censo.md: ese censo lista todavía el «Panel de diario (ui/journal.ts, F6)» y el «Panel de minimapa (ui/minimap.ts, Tab)» como componentes vivos del shell. Los dos ficheros ya no existen, y no hay teclas F6 ni Tab. El censo es de antes de la retirada y no se actualizó.


9 · Un defecto vivo encontrado escribiendo esto

No estaba en el encargo, salió mirando la captura del español de §6.

Compara las dos primeras líneas de la consola en español:

>Look-Norte          ← «Look» EN INGLÉS
Veis adoquines
>Buscar-             ← «Search» traducido
Cancelado.

El eco del verbo de (L)ook sale sin traducir mientras el de (S)earch sí se traduce, y la dirección («Norte») también.

La causa, verificada en la tabla

"Look-"    → SIN ENTRADA   ⇒ t() cae al inglés
"Look"     → "Mirar"       ⇒ existe, pero NO es la clave que se emite
"Search-"  → "Buscar-"     ⇒ existe con el guion, y por eso funciona

La clave que se emite en tiempo de ejecución es la compuesta, con guion. Está la constante "Look" y falta la viva "Look-". Un fallo de una línea en la tabla, invisible a cualquier recuento de cobertura que mire las constantes del código en vez de lo que imprime la consola.

No lo he arreglado: este carril es de documentación, y meter una entrada en es.json toca el corpus vigilado. Queda reportado.


10 · Qué se verificó y cómo

Los cinco puntos del encargo se confirmaron leyendo el código, no infiriéndolos:

PuntoVerificado enVeredicto
Controles táctilesui/touch.ts (tablas contadas en vivo), deck-nativo.ts, tap-or-drag.ts✅ Correcto
Tocar el mapa para caminarui/autowalk.ts (A*, 140 ms, cancelaciones), emisión del intent en skin/fiel/skin.ts✅ Correcto
Ranuras de guardado múltiplescore/persistence.ts (índice + slots + autosave ×3), ui/savepanel.ts; y en vivo: dos ranuras creadas y listadas✅ Correcto — el docstring confirma que el original tenía un SAVED.GAM
El español (i18n)i18n/index.ts (modelo), es.json (4 000 entradas contadas hoy)✅ Correcto
El historial con scrollskin/fiel/skin.ts (ConsoleScrollback + rueda + arrastre por zonas); y en vivo: A/B con 40 líneas numeradas✅ Correcto

Lo tomado de un acta y no re-verificado: el censo de comandos del despachador ASM de §3 (fuente: movil-cmds-acta) — no he releído kernel_cmd_dispatch yo. Lo que he contado hoy son las 25 entradas de WORLD_BUTTONS, que es el resultado del arreglo.

Tres afirmaciones documentadas que resultaron caducadas y están señaladas donde tocan: el docstring de i18n («20 strings»), el censo shell-8x8 (diario y minimapa vivos) y la propia claim de ese censo de que el shell se ve vectorial.


Y la comprobación del génesis

Las imágenes de estos documentos llevan arte de EA. La exclusión del repositorio público está verificada corriendo el génesis de verdad, no leyendo el script; el resultado está en el resumen de entrega del carril y en README.