Эволюция продукта
Новелла состоит из картинок. Почему конструктор оказался таким сложным?
Как небольшой прототип Fibber вырос в распределённую платформу с совместной работой, версионной публикацией и общим runtime историй.
Со стороны визуальная новелла кажется почти элементарной: фон, персонаж с прозрачностью, окно с текстом, несколько кнопок выбора со ссылками на следующие сцены и музыка. Именно так я и рассуждал, когда начинал первую версию.
Я до сих пор считаю, что ядро новеллы простое. Сегодня, особенно вместе с ИИ, почти любой разработчик может собрать плеер визуальной истории. Сложность начинается, когда нужно сделать не одну новеллу, а инструмент, в котором другой человек, возможно даже ребёнок, сможет создать, проверить, опубликовать и продолжать развивать собственную историю.
У первой версии была одна задача
До того как Romergo стал называться Romergo, он был Fibber. Он собирал один квест из связанных сцен, позволял заполнять их текстом и изображениями и поддерживал сюжетные развилки. В основе были TypeScript, React, Strapi и Ant Design.
Эта версия справлялась со своей задачей. Граф делал историю видимой, сцены можно было редактировать, а результат запускать в предпросмотре. Для прототипа этого было достаточно. И этого же хватило, чтобы обнаружить настоящую проблему.
Первым узким местом оказался не код
Когда мы начали наполнять настоящий квест текстом и изображениями, сборка десятиминутной сцены могла занимать почти целый день. Создай сцену. Загрузи фон. Загрузи персонажа. Расставь их. Добавь реплики. Соедини со следующей сценой. И повтори всё снова.
Часть этой цены была следствием интерфейса. Я улучшал UX, ускорял загрузку файлов, стабилизировал публикацию. Но самая дорогая часть никуда не исчезала: каждое небольшое действие всё ещё должен был вручную выполнить человек.
А что, если над историей смогут работать двое?
Первый ответ казался очевидным: если один человек стал узким местом, нужно разрешить нескольким людям работать над одной историей одновременно. Сначала я пришёл к Liveblocks, изучил этот подход, а позднее начал строить собственный слой совместной работы на основе Yjs.
На демонстрации ниже просто два окна редактора меняются синхронно. Но под этим скрывается более важный поворот: правки становятся обновлениями общего документа, а не отдельными отправками форм. Отсюда появляются присутствие участников, переподключение, разрешение конфликтов, права доступа и ожидание, что проект всегда остаётся живым.
Каждое удачное сокращение пути со временем становилось границей
Strapi был отличным способом быстро получить backend, но каждая новая функция, специфичная для интерактивных историй, заставляла CMS вести себя всё меньше как CMS. Ant Design дал первому интерфейсу скорость и единообразие, а затем постепенно начал удерживать продукт внутри чужой визуальной системы.
Следующая serverless-итерация на Remix стала важным шагом вперёд. Но выбранная связка приложения, медиа и развёртывания всё ещё не давала той свободы горизонтального масштабирования, которая уже требовалась продукту. Ни одно из этих решений не было ошибкой: каждое покупало мне достаточно времени, чтобы обнаружить следующее ограничение.
Изображения не просто вложения. Это инфраструктура.
Медиа быстро превратились в отдельный урок. Изображение для истории нужно один раз загрузить, при необходимости преобразовать, надёжно сохранить и быстро отдать любому игроку в любой точке мира. Хранить всё рядом с уже растущим сервером приложения означало ещё сильнее нагружать backend, поэтому первым отдельным решением стал Cloudinary.
Однажды вечером я открыл его кабинет и увидел, что один пользователь израсходовал около 500 МБ, сотни раз загрузив одно и то же изображение. В качестве срочного решения я начал сравнивать хэши и блокировать дубли. Это сработало, хотя лимиты запросов и квоты решали бы поведение прямее. Главным сюрпризом оказался трафик: за один вечер тестирования квеста я сам мог потратить больше десяти процентов бесплатного лимита.
После этого медиа перестали быть просто полем сцены. Им понадобился собственный путь: хранение, дедупликация, доставка, кэширование и контроль доступа.
Как простая идея выглядит сегодня
В нынешнем Romergo конструктор и плеер живут раздельно, но используют общий runtime истории. Предпросмотр внутри редактора и опубликованное прохождение подчиняются одним правилам. Так намного сложнее случайно разорвать контракт между созданием и игрой.
Yjs синхронизирует редактируемый проект между участниками. При публикации создаётся проверенный снимок данных истории и её медиа: авторы могут продолжать менять черновик, не подменяя незаметно версию, в которую уже играют. API построен на Hono, интерфейс использует компоненты shadcn и Radix, а облачный слой отвечает за распределённую координацию, объектное хранилище и доставку через CDN.
Общий runtime переносит одну и ту же историю в браузер и PWA, Telegram и синхронное прохождение в Discord. Совместимость версий, офлайн-режим и состояние групповой игры теперь не побочные эффекты вокруг страницы, а самостоятельные продуктовые системы.
Текущая архитектура
Одна редактируемая история. Один опубликованный контракт.
Обобщённая схема текущей архитектуры Romergo.
1. Редактируемый проект
- Builder UI: Сцены · граф · медиа
- AI / MCP: Авторизованные операции редактирования
- Realtime / Yjs: Durable Objects · общий рабочий документ
- Editor API: CAS-запись · сборка draft snapshot
- Draft runtime snapshot: LocalizedContentV2 · D1 / R2
- Черновые медиа: Объекты в R2 · метаданные в D1
2. Версионная публикация
- Проверки публикации: Схема runtime · подтверждение автора · наличие медиа
- Неизменяемая публикация vN: Runtime snapshot · скопированные и переназначенные медиа
- Контракт видимости: Публичная · по ссылке · приватная
3. Один контракт исполнения
- Draft snapshot: Последнее редактируемое состояние
- Публикация vN: Стабильное состояние для игроков
- Общий player-runtime: Сцены · переходы · условия · переменные · сохранения
- Preview в Builder: Запускает draft через тот же движок
4. Каналы воспроизведения
- Web / PWA: Браузерный плеер
- Telegram Mini App: Встроенный плеер
- Discord Activity: Синхронное групповое прохождение
5. Платформенная инфраструктура
- Clerk + OAuth: Идентификация и доступ MCP
- Hono Workers: API и MCP-сервисы
- Cloudflare D1: Проекты · версии · метаданные
- Cloudflare R2: Медиа · крупные runtime snapshots
- Durable Objects: Yjs-комнаты · Discord-сессии
- Прогресс игрока: Синхронизация с D1 · offline-очередь
Новелла всё ещё остаётся набором картинок
Самое забавное, что исходное предположение так и не стало ложным. Визуальная новелла по-прежнему состоит из фонов, персонажей, текста, выборов и звука. Romergo стал сложным потому, что надёжным должно было стать всё вокруг этого простого ядра: совместная работа, медиа, публикация, версии, офлайн-игра и несколько способов пройти одну историю.
Эта эволюция научила меня измерять весь путь человека, а не только код, который рисует финальный экран. Плееру действительно может хватить пяти примитивов. Продукту, который помогает превратить идею в законченную игровую историю, нужна система.