製品の進化

ビジュアルノベルは絵の集まりです。では、なぜ Builder はこれほど複雑になったのでしょう?

Fibber という小さな試作が、共同編集、バージョン管理された公開、共通のストーリーランタイムを備える分散プラットフォームへ成長した過程を振り返ります。

ビジュアルノベルは、背景、透明なキャラクター、テキスト ボックス、次のシーンを示すいくつかの選択肢、そしていくつかの音楽など、外から見るとほとんど些細なものに見えます。最初のバージョンを構築し始めたとき、まさにそれが私が見た方法でした。

やはり核はシンプルだと思います。今日、特に AI がそばにあれば、ほぼすべての開発者がビジュアルノベル プレイヤーを作成できます。困難は、目標がもはや 1 つのストーリーを作成することではなく、別の人 (おそらく子供であっても) が独自のストーリーを作成、テスト、公開し、進化させ続けることができるツールを構築することになったときに始まります。

最初のバージョンにはジョブが 1 つありました

Romergo が Romergo と呼ばれる前は、Fibber でした。接続されたシーンから 1 つのクエストを組み立て、それらのシーンをテキストと画像で埋められるようにし、分岐選択肢をサポートしました。スタックは TypeScript、React、Strapi、Ant Design でした。

そのバージョンはその役割を果たしました。フロー グラフによりストーリーが可視化され、シーンが編集され、結果がプレビューされるようになりました。プロトタイプとしてはそれで十分でした。それはまた、本当の問題を明らかにするのに十分でした。

最初の本当のボトルネックはコードではありませんでした

実際のクエストにテキストと画像を埋め始めると、10 分のシーンを組み立てるのにほぼ丸一日かかることがありました。シーンを作成します。背景をアップロードします。キャラクターをアップロードします。それらを配置します。ダイアログを追加します。次のシーンにつなげます。繰り返す。

その一部はインターフェースの問題でした。 UX を改善し、アップロードを高速化し、公開の信頼性を高めました。しかし、最大のコストは手つかずのままでした。つまり、人間は依然としてあらゆる小さな動作を手作業で実行しなければなりませんでした。

2 人で同時に構築できたらどうなるでしょうか?

最初の答えは単純でした。1 人がボトルネックになっている場合は、複数人で同じストーリーに取り組むようにします。私は Liveblocks から始めて、そのモデルから学び、その後 Yjs を中核とした独自のコラボレーション レイヤーに移行しました。

以下の実験は、2 つのエディタ ウィンドウが一緒に移動しているように見えます。その根底には、製品の考え方におけるはるかに大きな変更があります。編集は、個別のフォーム送信ではなく、共有ドキュメントへの更新になります。そこから、プレゼンス、再接続、競合処理、権限、そしてプロジェクトが常に有効であるという期待が生まれます。

すべての便利なショートカットは最終的に境界線になりました

Strapi はバックエンドを迅速に取得する優れた方法でしたが、ストーリー固有の新しい機能が登場するたびに、CMS は CMS らしくない動作をするように求められました。 Ant Design は、最初にインターフェースの速度と一貫性を与え、その後徐々に製品が他の誰かの視覚システムによって制約されているように感じられるようにしました。

その後の Remix ベースのサーバーレス反復は重要な前進でした。それでも、アプリケーション、メディア、展開を組み合わせた方法では、製品に必要とされ始めていた水平方向の自由が得られませんでした。これらの選択はどれも間違いではありませんでした。それぞれの選択で次の制約を発見するのに十分な時間を稼いだのです。

画像は添付ファイルではありません。それらはインフラです。

メディア自体が教訓になった。ストーリー画像は一度アップロードし、必要に応じて変換し、安全に保存して、世界中のプレイヤーに迅速に配信する必要があります。すでに成長しているアプリケーション サーバーの横に置くとバックエンドが重くなるだけだったので、最初の個別のソリューションが Cloudinary でした。

ある晩、ダッシュボードを開いたところ、1 人のユーザーが同じ画像を数百回アップロードすることで約 500 MB を消費していることがわかりました。私の緊急修正では、画像のハッシュを比較し、重複を拒否しました。これは機能しましたが、レート制限とクォータによって、より直接的に動作に対処できたはずです。さらに大きな驚きは帯域幅でした。一晩のテストで無料割り当て量の 10 パーセント以上が消費される可能性がありました。

そのエピソードがモデルを変えました。メディアはもはや現場のフィールドではありえません。ストレージ、重複排除、配信、キャッシュ、アクセスのために独自のパイプラインが必要でした。

今日のシンプルなアイデアはどのようなものですか

現在の Romergo は、Builder をプレイヤーから分離しますが、どちらも同じストーリー ランタイムを使用します。エディター内のプレビューと公開されたプレイスルーは同じルールに従うため、コントラクト ドリフトの導入がはるかに困難になります。

Yjs は、共同編集者間で編集可能なプロジェクトを同期します。公開すると、ストーリー データとそのメディアのチェック済みスナップショットが作成されるため、クリエイターはプレイヤーがすでに実行しているバージョンを変更することなく、ドラフトの変更を続けることができます。 API は Hono で構築され、インターフェイスは shadcn および Radix プリミティブを使用し、クラウド層は分散調整、オブジェクト ストレージ、CDN 配信を提供します。

その共有ランタイムにより、ブラウザと PWA、Telegram、および同期された Discord エクスペリエンスにストーリーが組み込まれるようになりました。リリース間の互換性、オフライン動作、マルチプレイヤー状態は、ページに関する副作用ではなくなりました。それらは独自の製品システムです。

現在のアーキテクチャ

一つの編集可能な物語。一つの公開契約。

Romergo の現在のアーキテクチャの概要。

1. 編集可能なプロジェクト

  • Builder UI: シーン・フロー・メディア
  • AI / MCP: 認証された編集操作
  • リアルタイム/YJS: Durable Objects · 共同編集ドキュメント
  • 編集者 API: CAS 書き込み · ドラフトの実体化
  • ドラフトの実行時スナップショット: LocalizedContentV2 · D1 / R2
  • ドラフトメディア: R2 オブジェクト · D1 メタデータ

2. バージョン付き公開

  • 公開前チェック: Runtime スキーマ · 作者の確認 · メディアの存在
  • 不変のパブリケーション vN: Runtime スナップショット · コピーおよび再マップされたメディア
  • 可視性契約: 公開 · 限定公開 · 非公開

3. 一つの実行契約

  • ドラフトスナップショット: 最新の編集可能状態
  • パブリケーション vN: 安定したプレイヤーの状態
  • 共有プレイヤー ランタイム: シーン、トランジション、条件、変数、保存
  • Builder プレビュー: 同じエンジンを通じてドラフトを実行します

4. 再生チャンネル

  • ウェブ / PWA: ブラウザ版 Player
  • Telegram ミニアプリ: 埋め込みプレイヤー
  • Discord アクティビティ: 同期されたグループプレイ

5. プラットフォームインフラストラクチャ

  • Clerk + OAuth: 認証と MCP アクセス
  • Hono Workers: API と MCP サービス
  • Cloudflare D1: プロジェクト、バージョン、メタデータ
  • Cloudflare R2: メディア・大規模なランタイムスナップショット
  • Durable Objects: Yjs ルーム · Discord セッション
  • Playerの進捗状況: D1 同期 · ローカルオフラインキュー

ビジュアルノベルは、今も絵の集まりです

面白いのは、当初の仮定がそのまま残っていることです。ビジュアルノベルには背景、キャラクター、テキスト、選択肢、サウンドが含まれます。 Romergo が複雑になったのは、コラボレーション、メディア、出版、バージョン管理、オフライン プレイ、同じストーリーを体験するためのいくつかの方法など、そのシンプルなコアの周りのすべてが信頼できるものになる必要があったためです。

この進化が私に教えてくれたことが 1 つあるとすれば、それは、最終画面をレンダリングするコードだけでなく、人間の経路全体を測定することです。プレイヤーには 5 つのプリミティブが必要な場合があります。アイデアを完成したプレイ可能なストーリーに変えるのに役立つ製品には、システムが必要です。

開発ブログに戻る