بنية المهمة

لا توجد سكريبتات. هناك منطق: كيف تم ترتيب العقد والمتغيرات في Romergo

عندما يتحدثون عن المنطق في محرر الألعاب، عادةً ما ينتقلون بسرعة إلى السكريبتات. في مكان ما يكتب المؤلف JavaScript، وفي مكان آخر Python، وفي مكان آخر يدرس لغة المحرك الخاصة به. هذا يمنح حرية شبه غير محدودة، ولكنها تأتي أيضًا مع أخطاء نحوية، دورات متوقفة، ملحقات غير متوافقة، وكود يصعب فتحه حتى على مؤلفه بعد ستة أشهر.

في Romergo اتبعت طريقًا مختلفًا. لا توجد نصوص عشوائية داخل المهمة. لا يمكن إدراج وظيفة، أو إجراء طلب شبكي، أو الوصول المباشر إلى المشغل. بدلاً من ذلك، تُجمع القصة من مجموعة محدودة، لكنها معبرة بما فيه الكفاية: العقد المنطقية، المتغيرات، الشروط، والتأثيرات.

هذا لا يعني أنه في Romergo يمكن القيام فقط بتفرعات بسيطة. يمكن تذكر قرارات اللاعب، احتساب النقاط، فتح وإخفاء الخيارات، اختيار مسارات مختلفة في الفصول التالية، بناء أحداث عشوائية وإنشاء واجهات تفاعلية. ببساطة، بدلاً من الأمر "نفذ هذا الكود" يصف المؤلف "في هذا الوضع يجب أن يحدث هذا".

المخطط بالفعل هو برنامج

المنطق الأكثر أساسية Romergo موجود مباشرة على خريطة الفصل. المشاهد تتصل بالانتقالات، والعقد الخاصة تحدد إلى أين سيذهب اللعب بعد ذلك.

إذا بسطنا الأمر كثيرًا، فإن المهمة الصغيرة تبدو هكذا:

بداية
  ↓
حديث مع الحارس
  ↓
هل هناك تصريح؟
  ├─ نعم  → الأرشيف المغلق
  └─ لا → الطريق الالتفافي

هذا بالفعل منطق قابل للتنفيذ. Player يدخل إلى العقدة البداية، يعرض المشهد، يقرأ حالة السجل ويختار الانتقال المناسب. يرى المؤلف نفس المسار كخريطة مفهومة، وليس كمجموعة من if و goto والمعرفات الخدمية.

![Flow Romergo: الاتهام النهائي يمر عبر Switch وثلاثة If / Else إلى نهايات مختلفة، في الأسفل تم فتح لوحة المنطق](../assets/logic-without-scripts-flow-nodes.webp)

*مقتطف من Flow الحقيقي في Builder: مشهد Final Accusation ينقل القرار إلى Switch، ثم يقوم عدة If / Else بفحص الحالة ويُفرقون القصة إلى نهايات مختلفة. تعرض اللوحة المفتوحة Logic في الأسفل جميع المجموعات المتاحة: Dead End، Checkpoint، If / Else، Switch، Random و Teleport.*

في لوحة المنطق هناك الآن ستة عقد رئيسية:

البداية والنهاية تؤطر الفصل، المشاهد العادية تعرض المحتوى، والعقد المنطقية تحولها إلى مسار. حتى على هذا المستوى يمكن جمع قصة خطية، أو تفرع، أو عدة نهايات، أو حدث عشوائي، ونقطة عودة آمنة — دون أي سطر من السكربت.

المتغير هو ذاكرة التاريخ

لا يكفي وجود رسم بياني واحد إذا كان يجب أن تتذكر القصة ما حدث سابقًا. ولهذا السبب يوجد في المهمة متغيرات من ثلاثة أنواع بسيطة:

لكل متغير مفتاح وعنوان واضح للمؤلف وقيمة ابتدائية. يمكن وصف الحد الأدنى والأقصى للمتغير الرقمي، بينما يمكن إخفاء المتغير الخدمي من لوحة حالة اللاعب أو عرضه فقط عند تحقق شرط معين.

الأهم هنا هو أن المتغيرات تخص اجتياز المغامرة بالكامل، وليس مشهداً واحداً. إذا حصل اللاعب على تصريح في الفصل الأول، يمكن التحقق من نفس العلم في الفصل الرابع. عند الانتقال عبر النهاية إلى الفصل التالي، لا يتم إعادة تعيين الحالة. يتم حفظها مع الخيارات المختارة، التأثيرات المطبقة، نقطة التحقق، والمرحلة الحالية من العشوائية.

ينتج عن ذلك دورة بسيطة للمؤلف:

اختيار اللاعب يسجل القيمة
              ↓
المتغير يخزن الحالة
              ↓
الشرط يقرأه لاحقًا
              ↓
الرسم البياني أو المشهد أو الواجهة يتفاعل

على سبيل المثال، في المشهد الأول، اختيار «عرض الصورة التي تم العثور عليها» قد يحدد portrait_revealed = true. لاحقًا، ستتحقق العقدة **إذا / وإلا** من هذا العلم وتفتح مشهد محادثة جديد. في وقت لاحق، ستعرض واجهة المحطة تسجيلًا إضافيًا فقط للاعبين الذين كشفوا الصورة.

متغير واحد يربط ثلاثة أجزاء مختلفة من المهمة. لهذا لا تحتاج إلى تمرير القيمة يدويًا بين المشاهد أو الفصول: فهي تقرأ حالة تقدم واحدة مشتركة.

![تعرض صفحة المتغير route المفتاح والاسم والنوع ورؤية الحالة](../assets/logic-without-scripts-variables.webp)

*للمتغير route، تم تحديد مفتاح ثابت واسم مفهوم ونوع القيمة ورؤية الحالة في بطاقة واحدة. ترتبط الاختيارات والفروع بالأسفل في نفس الصفحة.*

الاختيارات تكتب، والفروع تقرأ

في المشهد العادي، أبسط طريقة لتغيير الحالة هي إضافة تأثير إلى خيار الاختيار. في المحرر المرئي، يبدو هذا مثل التعيين:

«الثقة بماري» → ally = "mary"
«الذهاب بمفرده» → ally = "none"

بعد ذلك، يمكن لـ Switch توجيه اللاعب إلى المشهد المطلوب بناءً على قيمة ally. تعرض صفحة المتغيرات بشكل منفصل الخيارات التي تسجل المتغير والفروع التي تقرأه. هذه تفاصيل صغيرة، لكنها في مهمة كبيرة تحل محل البحث المؤلم عبر عشرات المشاهد.

![إعدادات Switch مع الفروع Laboratory و Habitat، التي تقارن متغير المسار المختار](../assets/logic-without-scripts-switch-rules.webp)

*يتم التحقق من فروع Switch من أعلى إلى أسفل. في هذا المثال، يتم تفعيل مسار Laboratory عند route = lab، و Habitat عند route = habitat.*

تدعم الشروط المقارنات العادية:

في الحالة البسيطة، يختار المؤلف المتغير والمشغل والقيمة مباشرة في إعدادات If / Else أو Switch. بالنسبة للمنطق الأكثر تعقيدًا، يفهم runtime المجموعات **جميع الشروط**، **أي شرط** والنفي.

يمكن وصف هذه القاعدة بشكل هيكلي:

{
  "op": "all",
  "conditions": [
    { "op": "gte", "key": "evidence", "value": 3 },
    { "op": "eq", "key": "alarm_active", "value": false }
  ]
}

يشبه هذا نصًا برمجيًا صغيرًا، لكنه ليس في الجوهر كذلك. هنا لا يمكن استدعاء دالة أو تغيير شيء عشوائي. Romergo يعرف مسبقًا جميع العمليات المسموح بها، ويتحقق من الهيكل وينفذه بنفس الطريقة في معاينة Builder وفي Player المنشور.

النتائج: أوامر صغيرة بدون لغة برمجة

الشرط يقرأ الحالة، بينما التأثير يغيرها. Runtime و Romergo تدعمان عدة عمليات ذرية:

في المفتش العادي للاختيار، السيناريو الأساسي بسيط عن عمد: اختيار متغير وتسجيل قيمة جديدة عبر set. في منطق واجهة المستخدم المتقدم، يمكن تحديد الشروط والتأثيرات كهيكليات قابلة للتحقق. على سبيل المثال، يمكن للانتقال بين حالات الطرفية أن يتذكر القرار في الوقت نفسه، ويضيف دليلًا، ويفعل الإنذار:

![محرر المشهد Romergo: ثلاثة خيارات للاختيار تسجل lab و habitat و server في المتغير route](../assets/logic-without-scripts-choice-effects.webp)

*يتم إرفاق التأثير مباشرة بخيار الإجابة: يتم تسجيل ثلاثة خيارات في route القيم lab و habitat و server. يظل حقل المتغير والقيمة الجديدة مفصولين حتى في المفتش الضيق.*

[
  { "op": "set", "key": "terminal_decision", "value": "cut-power" },
  { "op": "inc", "key": "evidence", "amount": 1 },
  { "op": "toggle", "key": "alarm_active" }
]

لهذه الطريقة قيود مهمة: فهي ليست آلة حاسبة للمعادلات العشوائية. لا يمكن كتابة evidence * trust / 2، أو تشغيل حلقة، أو إنشاء دالتك الخاصة. يتم تجميع السلوك المعقد من عدة خطوات واضحة، ويتم توسيع المنطق من خلال عمليات runtime الجديدة القابلة للتحقق، وليس من خلال تنفيذ رمز غير معروف.

بالنسبة للمؤلف، هذا أقل لا حدود له بعض الشيء، ولكن القصة تظل متوقعة. يمكن التحقق من نفس التأثيرات قبل النشر، حفظها، مزامنتها بين اللاعبين، وإعادة تشغيلها بعد تحميل التقدم.

لماذا لا يوجد داخل العقدة المنطقية حتى الآن Flow آخر

على الرغم من كل التوقعات، هناك عيب صريح في النهج الحالي: فقد يبدو لشخص بدون خبرة في التطوير أن شكل "المتغير — المشغل — القيمة" يعتبر برمجة بالفعل. وعندما تظهر بجانبه مجموعات من الشروط وعدة تأثيرات، تظل البنية الإعلانية آمنة لـ Player، لكنها لا تصبح بالضرورة بسيطة بالنسبة للمؤلف.

يبدو طبيعيًا أن تأتي يومًا ما إلى العقد البصرية وداخل المنطق نفسه. بدلاً من قائمة الشروط، يمكن ربط الكتل **و**، **أو**، **ليس**، مقارنة المتغير وتغيير القيمة. على الورق، يبدو هذا أكثر ودية من النص أو JSON.

ولكن بعد ذلك أتخيل سيناريو حقيقي: يفتح المؤلف عقدة منطقية على خريطة الفصل، وداخلها يجد Flow آخر لها عقدها وروابطها الخاصة. يصبح الأمر بمثابة رسم بياني داخل رسم بياني. يجب أن تفهم أين أنت الآن، كيف تعود إلى مستوى القصة، وفي أي من Flow تبحث عن الخطأ. بالنسبة للمبتدئ، يمكن أن تكون هذه التعقيدات أكثر رعبًا من الشكل الحالي.

لذلك يتم استخدام حل وسط في الوقت الحالي. يظل مسار القصة بصريًا، بينما يتم عرض الشروط والتأثيرات داخل العقد في حقول صريحة مضغوطة. هذا يعمل، لكنني لا أعتبر الواجهة محسومة بالكامل. ربما في وقت لاحق سيظهر مزيج أكثر وضوحًا من القوالب الجاهزة، والإعداد خطوة بخطوة، والكتل البصرية — شيء يقلل بالفعل التعقيد، وليس مجرد نقله إلى محرر آخر.

المنطق لا يعيش فقط على الخريطة

المشاهد الواجهية تستخدم نفس الحالة. يمكن أن يظهر الواجهة المصغرة أو الرسالة أو خيار الرد أو المهمة أو الإشعار أو تدفق الوسائط فقط عند تطابق showWhen. يمكن ربط قيمة المقياس بمتغير، ويمكن تنفيذ الانتقال بين حالات الواجهة المصغرة بعد تأخير أو وفق شرط أو بعد إجراء اللاعب.

على سبيل المثال، يمكن لشاشة الكمبيوتر على متن السفينة أن تتصرف كالتالي:

1. في البداية، يكون المحطة المقفلة هي المرئية فقط.

2. بعد اختيار اللاعب، يقوم التأثير بتعيين terminal_unlocked = true.

3. يفتح الشرط مخطط السفينة وأزرار جديدة.

4. النقر على الزر يغير حالة الواجهة ويزيد evidence.

5. عندما يكون evidence >= 3، يظهر إشعار مخفي وتصبح فرع جديد من المهمة متاحًا.

خريطة الفصل، الخيارات العادية والواجهة التفاعلية لا تخلق ثلاث أنظمة منفصلة. إنها تقرأ وتغير كائن حالة واحد. لذلك الدليل الموجود في الحوار يمكن أن يفتح عنصر واجهة، والإجراء داخل الواجهة يمكن أن يغير المشهد التالي.

هنا بالضبط تبدأ المنطقية التصريحية في الشعور وكأنها برمجة حقيقية. فقط بدلاً من الملفات والدوال وناقل الأحداث، يعمل المؤلف مع كيانات مفهومة من السرد: الحقائق، والخيارات، والشروط، والانتقالات.

لماذا أنا عن قصد لا أضيف JavaScript عشوائية

تبدو القدرة على إدراج سكريبت أقصر طريق إلى أي ميكانيك جديدة. لكن بمجرد أن يحصل المهمة المنشورة على الحق في تنفيذ كود عشوائي، تتغير كل نموذج المنتج.

من الضروري تحديد أي واجهات برمجة التطبيقات للمتصفح متاحة لهذا الرمز، وكيفية تقييد الشبكة والتخزين، وماذا نفعل مع الحلقات اللانهائية، وكيفية نقل المهمة إلى وضع عدم الاتصال، وكيفية مزامنتها في اللعب التشاركي، وكيفية ضمان استمرار عمل المشروع القديم بعد تحديث Player.

القاعدة التصريحية أكثر مللاً بكثير من الدالة العشوائية — ولهذا السبب هي أكثر موثوقية. يمكن التحقق منها قبل التشغيل. يمكن معرفة المتغيرات التي تقرأها وتكتبها. يمكن حفظ التقدم بأمان في منتصف السلسلة. يمكن تنفيذ المهمة بنفس الطريقة في المتصفح، أو في التجميع المستقل، أو في Player المدمج.

القاموس المحدود هنا لا يعيق المنطق، بل يحدد حدوده. إذا ظهرت عملية مفيدة حقًا، فمن الأفضل إضافتها إلى runtime العام ومنح جميع المؤلفين سلوكًا موحدًا مختبرًا، بدلاً من إجبار كل مشروع على ابتكار محركه الخاص.

كحلوى — لديك HTML و CSS الخاصان بك

وفي الوقت نفسه، يظل Romergo يترك مجالًا للكود الحقيقي حيث يكون أكثر أمانًا: في تصميم المشهد العادي.

في المشروع يمكن إنشاء موضوع مخصص للمظهر على HTML و CSS واستخدامه في فصول مختلفة. HTML يحدد موقع المناطق المعدة مسبقًا — نافذة الحوار، البورتريه، اسم الشخصية، النص، السؤال وأزرار الاختيار. CSS يسمح بتغيير هذه المناطق بشكل ملحوظ: الإطارات، الخلفية، شكل البطاقات، الهوامش، الطباعة ورد الفعل على حجم الشاشة.

الموضوعات الجاهزة **Standard**، **Minimalism**، **Brutalism**، **Romance** و**Neon** تظهر النطاق المؤكد بدون تنسيق يدوي. عند إنشاء موضوع مخصص، يقوم Romergo بنسخ القالب المختار: بعد ذلك يمكن تعديل HTML و CSS مباشرة والتحقق منها في المعاينة الحقيقية Player.

![المحرر الحقيقي لتنسيق Romergo: CSS لموضوع Brutalism والمعاينة المحمولة للحوار](../assets/logic-without-scripts-custom-html-css.webp)

*لا يوجد هنا فن مفهومي: على اليسار يظهر CSS الموضوع المدمج Brutalism الفعلي، وعلى اليمين ناتج نفس الكود في معاينة الهاتف. يتيح مفتاح التبديل بين سطح المكتب / الهاتف التحقق مباشرة من قواعد التكيف، وتبدأ الموضوعات المخصصة بالعمل من نسخة من القالب المحدد.*

لكن الحدود تبقى كما هي. في HTML المخصص لا يوجد <script>، ولا معالجات الأحداث، أو النماذج، أو iframe، أو الموارد الخارجية. لا يمكن لـ CSS تحميل url() أو @import الخارجية. يجب أن تظل المناطق الإلزامية للمشهد في مكانها؛ إذا فقدها القالب، يعود Player إلى التنسيق القياسي الآمن.

أي أن HTML و CSS مسؤولان عن **كيف يبدو القصة**، بينما العقد والمتغيرات والشروط والتأثيرات — عن **كيف تعمل**.

أنا أحب هذا النوع من الفصل تحديدًا. يمكن للمؤلف أن يجعل المهمة تبدو مختلفة بصريًا عن البقية، ولكن اجتيازها يبقى جزءًا من runtime القابلة للفحص العامة. يمكن إعادة طلاء الطبقة الخارجية وإعادة بنائها بشكل جذري، دون تحويل كل موضوع إلى برنامج منفصل.

الكود بدون كود ليس غيابًا للمنطق

ليس لدى Romergo هدف استبدال لغة البرمجة بالصور. الغرافيك غير مناسب جيدًا للرياضيات المعقدة، ومجموعة التأثيرات الذرية لن تصبح محرك أتمتة عالميًا.

لكن للقصة التفاعلية غالبًا ما تكون هناك احتياجات أخرى: تذكر حقيقة، تغيير عداد، فتح خيار، اختيار مسار، عرض حالة الواجهة، حفظ نقطة رجوع ونقل كل هذه القرارات إلى الفصل التالي.

لهذا الغرض، تصبح العقد والمتغيرات ليس نسخة مبسطة من السكريبتات، بل لغة أكثر مباشرة للقصة نفسها. يصف المؤلف ليس أوامر للكمبيوتر، بل الروابط السببية للعالم: اللاعب اتخذ اختيارًا، تغيرت الحالة، وتفاعلت القصة.

وإذا كنت ترغب في بعض الشيفرة الحقيقية، فإن HTML و CSS ما زالا في انتظار الحلوى.

العودة إلى مدونة التطوير