ملاحظات التطوير
الرواية المرئية مجموعة من الصور. لماذا أصبح Builder معقدًا إلى هذا الحد؟
كيف نما نموذج أولي صغير اسمه Fibber ليصبح منصة موزعة تدعم التعاون والنشر بإصدارات وruntime مشتركًا للقصص.
تبدو الرواية المرئية من الخارج بسيطة جدًا: خلفية، وشخصية ذات شفافية، ومربع نص، وبعض الخيارات التي تقود إلى المشهد التالي، وقليل من الموسيقى. هكذا رأيتها تمامًا عندما بدأت بناء الإصدار الأول.
ما زلت أرى أن جوهرها بسيط. اليوم، وخصوصًا مع مساعدة AI، يستطيع أي مطور تقريبًا إنشاء player لرواية مرئية. تبدأ الصعوبة عندما لا يعود الهدف صنع قصة واحدة، بل بناء أداة يستطيع فيها شخص آخر، وربما طفل، إنشاء قصته واختبارها ونشرها ومواصلة تطويرها.
كان للإصدار الأول عمل واحد
قبل أن يحمل Romergo اسمه كان Fibber. كان يجمع quest واحدة من مشاهد مترابطة، ويتيح لي ملأها بالنصوص والصور، ويدعم الخيارات المتفرعة. تكونت الحزمة من TypeScript وReact وStrapi وAnt Design.
أنجز ذلك الإصدار مهمته. جعل flow graph القصة مرئية، وأتاح تعديل المشاهد ومعاينة النتيجة. كان ذلك كافيًا لنموذج أولي، وكافيًا أيضًا لكشف المشكلة الحقيقية.
لم تكن الشفرة أول عنق زجاجة حقيقي
عندما بدأنا ملء quest حقيقية بالنصوص والصور، كان تجميع مشهد مدته عشر دقائق يستغرق يومًا كاملًا تقريبًا. أنشئ مشهدًا. ارفع خلفية. ارفع شخصية. ضع العناصر. أضف الحوار. اربط المشهد التالي. ثم كرر.
كان جزء من ذلك مشكلة في الواجهة. حسنت UX، وسرعت الرفع، وجعلت النشر أكثر موثوقية. لكن التكلفة الأكبر بقيت كما هي: كان على شخص تنفيذ كل خطوة صغيرة يدويًا.
ماذا لو استطاع شخصان البناء في الوقت نفسه؟
كان الجواب الأول بسيطًا: إذا كان شخص واحد هو عنق الزجاجة، فلنجعل عدة أشخاص يعملون على القصة نفسها. بدأت مع Liveblocks، وتعلمت من ذلك النموذج، ثم اتجهت إلى طبقة تعاون خاصة بي يكون Yjs في قلبها.
تبدو التجربة أدناه كنافذتي editor تتغيران معًا. تحت ذلك تحول أكبر بكثير في طريقة تفكير المنتج: تصبح التعديلات تحديثات لمستند مشترك بدل إرسال نماذج منفصلة. ومن هنا تأتي حالة الحضور، وإعادة الاتصال، ومعالجة التعارضات، والصلاحيات، وتوقع أن يبقى المشروع حيًا دائمًا.
تحول كل اختصار مفيد في النهاية إلى حد
كان Strapi طريقة ممتازة للحصول على backend بسرعة، لكن كل ميزة جديدة خاصة بالقصص كانت تطلب من CMS أن يتصرف بدرجة أقل كـ CMS. منح Ant Design الواجهة الأولى سرعة واتساقًا، ثم بدأ تدريجيًا يحصر المنتج داخل النظام المرئي لشخص آخر.
كانت نسخة serverless لاحقة مبنية على Remix خطوة مهمة. ومع ذلك، لم يمنحني الجمع بين التطبيق وmedia والنشر الحرية الأفقية التي بدأ المنتج يحتاجها. لم يكن أي من هذه الخيارات خطأ. كل خيار اشترى وقتًا كافيًا لاكتشاف القيد التالي.
الصور ليست مرفقات. إنها بنية تحتية.
أصبحت media درسًا قائمًا بذاته. يجب رفع صورة القصة مرة واحدة، وتحويلها عند الحاجة، وحفظها بأمان، وتسليمها بسرعة إلى أي player في العالم. كان إبقاؤها بجوار خادم تطبيق يكبر بالفعل يزيد ثقل backend، لذلك كان Cloudinary أول حل منفصل.
فتحت لوحة التحكم ذات مساء ورأيت أن مستخدمًا واحدًا استهلك نحو 500 MB برفع الصورة نفسها مئات المرات. قارن الإصلاح الطارئ image hashes ورفض النسخ المكررة. نجح، لكن rate limits وquotas كانا سيعالجان السلوك بصورة مباشرة أكثر. كانت المفاجأة الأكبر في bandwidth: قد تستهلك أمسية اختبار واحدة أكثر من عشرة في المئة من الحصة المجانية.
غيرت تلك الحادثة النموذج. لم يعد ممكنًا أن تكون media حقلًا في المشهد. احتاجت إلى pipeline خاص للتخزين وإزالة التكرار والتسليم والتخزين المؤقت والتحكم في الوصول.
كيف تبدو الفكرة البسيطة اليوم
يفصل Romergo الحالي بين Builder وplayer، لكنهما يستخدمان story runtime نفسه. تتبع المعاينة داخل editor والتجربة المنشورة القواعد نفسها، ما يجعل ظهور contract drift أصعب بكثير.
يزامن Yjs المشروع القابل للتعديل بين المتعاونين. ينشئ النشر snapshot مدققة لبيانات القصة وmedia، فيستطيع المبدعون متابعة تغيير المسودة من دون تغيير الإصدار الذي يشغله اللاعبون بصمت. بنيت API باستخدام Hono، وتستخدم الواجهة مكونات shadcn وRadix، وتوفر طبقة cloud التنسيق الموزع وobject storage والتسليم عبر CDN.
ينقل shared runtime القصة الآن إلى المتصفح وPWA وTelegram وتجربة Discord متزامنة. لم يعد توافق الإصدارات والعمل offline وحالة multiplayer آثارًا جانبية حول صفحة، بل أصبحت أنظمة مستقلة في المنتج.
البنية الحالية
قصة واحدة قابلة للتعديل. عقد منشور واحد.
مخطط عام للبنية الحالية في Romergo.
1. مشروع قابل للتعديل
- Builder UI: مشاهد · flow · media
- AI / MCP: عمليات تعديل موثقة
- Realtime / Yjs: Durable Objects · مستند عمل مشترك
- Editor API: كتابات CAS · بناء المسودة
- Draft runtime snapshot: PublishedQuestLocalizedContentV2 · D1 / R2
- Media المسودة: عناصر R2 · بيانات D1 الوصفية
2. نشر قائم على الإصدارات
- فحوص النشر: Runtime schema · إقرار المؤلف · وجود media
- نشر ثابت vN: Runtime snapshot · media منسوخة ومعاد ربطها
- عقد الظهور: عام · غير مدرج · خاص
3. عقد تنفيذ واحد
- Snapshot المسودة: أحدث حالة قابلة للتعديل
- النشر vN: حالة player مستقرة
- Shared player-runtime: مشاهد · انتقالات · شروط · متغيرات · حفظ
- معاينة Builder: تشغل المسودة بالمحرك نفسه
4. قنوات التشغيل
- Web / PWA: Player في المتصفح
- Telegram Mini App: Player مدمج
- Discord Activity: لعب جماعي متزامن
5. البنية التحتية للمنصة
- Clerk + OAuth: الهوية والوصول إلى MCP
- Hono Workers: خدمات API وMCP
- Cloudflare D1: مشاريع · إصدارات · بيانات وصفية
- Cloudflare R2: Media · runtime snapshots كبيرة
- Durable Objects: غرف Yjs · جلسات Discord
- تقدم اللاعب: مزامنة D1 · قائمة انتظار محلية offline
ما زالت الرواية مجرد صور
الطريف أن الفرضية الأولى بقيت صحيحة. ما زالت الرواية المرئية خلفيات وشخصيات ونصوصًا وخيارات وصوتًا. أصبح Romergo معقدًا لأن كل ما يحيط بهذا الجوهر البسيط كان يجب أن يصبح موثوقًا: التعاون وmedia والنشر والإصدارات واللعب offline وطرق متعددة لتجربة القصة نفسها.
إذا علمتني هذه الرحلة شيئًا، فهو قياس المسار البشري كاملًا، لا الشفرة التي تعرض الشاشة الأخيرة فقط. قد يحتاج player إلى خمسة عناصر أساسية. أما المنتج الذي يساعد شخصًا على تحويل فكرة إلى قصة مكتملة قابلة للعب فيحتاج إلى نظام.