Notas de desarrollo

Una novela visual es un conjunto de imágenes. ¿Por qué el constructor se volvió tan complejo?

Cómo un pequeño prototipo llamado Fibber se convirtió en una plataforma distribuida con colaboración, publicación versionada y un runtime compartido para las historias.

Vista desde fuera, una novela visual parece casi trivial: un fondo, un personaje con transparencia, un cuadro de texto, algunas opciones que llevan a la siguiente escena y algo de música. Así era exactamente como la veía cuando empecé a crear la primera versión.

Sigo pensando que el núcleo es sencillo. Hoy, especialmente con la ayuda de la IA, casi cualquier desarrollador puede crear un reproductor de novelas visuales. La dificultad aparece cuando el objetivo deja de ser hacer una historia y pasa a ser construir una herramienta en la que otra persona, quizá incluso un niño, pueda crear, probar, publicar y seguir desarrollando su propia historia.

La primera versión tenía una sola tarea

Antes de que Romergo se llamara Romergo, era Fibber. Construía una misión a partir de escenas conectadas, me permitía llenarlas de texto e imágenes y admitía decisiones ramificadas. El stack era TypeScript, React, Strapi y Ant Design.

Esa versión cumplía su función. El grafo hacía visible la historia, las escenas podían editarse y el resultado podía probarse en una vista previa. Para un prototipo era suficiente. También bastó para revelar el problema real.

El primer cuello de botella real no era el código

Cuando empezamos a llenar una misión real con texto e imágenes, montar una escena de diez minutos podía llevar casi un día entero. Crear una escena. Subir un fondo. Subir un personaje. Colocarlos. Añadir diálogos. Conectar la siguiente escena. Repetir.

Una parte era un problema de interfaz. Mejoré la UX, aceleré las cargas y aumenté la fiabilidad de la publicación. Pero el mayor coste seguía intacto: una persona aún tenía que realizar a mano cada pequeña acción.

¿Y si dos personas pudieran crear al mismo tiempo?

La primera respuesta era sencilla: si una persona es el cuello de botella, dejemos que varias trabajen en la misma historia. Empecé con Liveblocks, aprendí de ese modelo y después avancé hacia mi propia capa de colaboración con Yjs como núcleo.

El experimento de abajo parece mostrar dos ventanas del editor moviéndose juntas. Debajo hay un cambio mucho mayor en la forma de pensar del producto: las ediciones se convierten en actualizaciones de un documento compartido en lugar de envíos aislados de formularios. De ahí surgen la presencia, las reconexiones, la gestión de conflictos, los permisos y la expectativa de que un proyecto siempre esté vivo.

Cada atajo útil acabó convirtiéndose en un límite

Strapi fue una forma excelente de obtener un backend rápidamente, pero cada nueva función específica de las historias pedía al CMS comportarse un poco menos como un CMS. Ant Design aportó velocidad y coherencia a la primera interfaz, pero poco a poco hizo que el producto quedara limitado por el sistema visual de otra persona.

Una iteración serverless posterior basada en Remix fue un paso importante. Aun así, la forma en que había unido la aplicación, los recursos y el despliegue no me daba la libertad horizontal que el producto empezaba a necesitar. Ninguna de estas decisiones fue un error: cada una me dio el tiempo suficiente para descubrir la siguiente limitación.

Las imágenes no son archivos adjuntos. Son infraestructura.

Los recursos multimedia se convirtieron en una lección propia. Una imagen de una historia debe subirse una vez, transformarse cuando sea necesario, almacenarse de forma segura y entregarse rápidamente a cualquier jugador del mundo. Mantenerla junto a un servidor de aplicación que ya crecía solo hacía el backend más pesado, así que la primera solución independiente fue Cloudinary.

Una tarde abrí su panel y vi que un solo usuario había consumido unos 500 MB al subir la misma imagen cientos de veces. Mi solución de emergencia comparaba hashes y rechazaba duplicados. Funcionó, pero los límites de frecuencia y las cuotas habrían abordado el comportamiento de forma más directa. La mayor sorpresa fue el ancho de banda: una sola tarde de pruebas podía consumir más del diez por ciento del límite gratuito.

Ese episodio cambió el modelo. Los recursos multimedia ya no podían ser un campo de una escena. Necesitaban su propio flujo de almacenamiento, deduplicación, entrega, caché y acceso.

Cómo se ve hoy aquella idea sencilla

El Romergo actual separa Builder del reproductor, aunque ambos utilizan el mismo runtime de historias. La vista previa del editor y una partida publicada siguen las mismas reglas, lo que dificulta mucho introducir divergencias en el contrato.

Yjs sincroniza el proyecto editable entre colaboradores. La publicación crea una instantánea verificada de los datos y recursos de la historia, de modo que los autores pueden seguir cambiando el borrador sin alterar silenciosamente la versión que los jugadores ya están ejecutando. La API está construida con Hono, la interfaz utiliza primitivas de shadcn y Radix, y la capa cloud proporciona coordinación distribuida, almacenamiento de objetos y entrega mediante CDN.

Ese runtime compartido lleva ahora la historia al navegador y a la PWA, a Telegram y a una experiencia sincronizada en Discord. La compatibilidad entre versiones, el funcionamiento sin conexión y el estado multijugador ya no son efectos secundarios alrededor de una página. Son sistemas de producto por derecho propio.

Arquitectura actual

Una historia editable. Un contrato publicado.

Una vista general de la arquitectura actual de Romergo.

1. Proyecto editable

  • Builder UI: Escenas · flujo · recursos
  • AI / MCP: Operaciones de edición autenticadas
  • Realtime / Yjs: Durable Objects · documento de trabajo compartido
  • Editor API: Escrituras CAS · materialización del borrador
  • Instantánea runtime del borrador: PublishedQuestLocalizedContentV2 · D1 / R2
  • Recursos del borrador: Objetos R2 · metadatos D1

2. Publicación versionada

  • Comprobaciones de publicación: Esquema runtime · declaración del autor · existencia de recursos
  • Publicación inmutable vN: Instantánea runtime · recursos copiados y reasignados
  • Contrato de visibilidad: Público · no listado · privado

3. Un contrato de ejecución

  • Instantánea del borrador: Último estado editable
  • Publicación vN: Estado estable del reproductor
  • Player-runtime compartido: Escenas · transiciones · condiciones · variables · partidas guardadas
  • Vista previa de Builder: Ejecuta el borrador con el mismo motor

4. Canales de reproducción

  • Web / PWA: Reproductor del navegador
  • Telegram Mini App: Reproductor integrado
  • Discord Activity: Juego grupal sincronizado

5. Infraestructura de plataforma

  • Clerk + OAuth: Identidad y acceso MCP
  • Hono Workers: Servicios API y MCP
  • Cloudflare D1: Proyectos · versiones · metadatos
  • Cloudflare R2: Recursos · instantáneas runtime grandes
  • Durable Objects: Salas Yjs · sesiones de Discord
  • Progreso del jugador: Sincronización D1 · cola local sin conexión

La novela sigue siendo solo imágenes

Lo curioso es que la idea original sobrevivió. Una novela visual sigue siendo fondos, personajes, texto, decisiones y sonido. Romergo se volvió complejo porque todo lo que rodeaba ese núcleo sencillo debía ser fiable: colaboración, recursos, publicación, versiones, juego sin conexión y varias formas de vivir la misma historia.

Si esta evolución me enseñó algo, es a medir todo el recorrido humano, no solo el código que dibuja la pantalla final. El reproductor puede necesitar cinco elementos básicos. Un producto que ayuda a alguien a convertir una idea en una historia terminada y jugable necesita un sistema.

Volver al blog técnico