عقد إعادة التشغيل
عقد إعادة التشغيل هو السطح الإلزامي الوحيد الذي تتّفق عليه لعبة قابلة لإعادة التشغيل مع المنصّة. يُنشَر كـ @caputchin/replay-contract: حزمة ضئيلة عمدًا بلا اعتماديات (تُحمَّل في عازل إعادة تشغيل مختوم، فلا تجرّ شيئًا). كل ما عداه عن لعبتك، المحرّك، والعارض، وهيئة الأثر، لك.
لعبة تشحن run مطابقًا قابلة لإعادة التشغيل فتستطيع حراسة مفتاح موقع؛ ولعبة بلا واحد ما زالت قابلة للتضمين، لكن كتجربة استخدام فقط (تُظهر غير قابلة للإعادة). انظر اشحن لعبة إلى المتجر لذلك التمييز.
دالة run
لعبة قابلة لإعادة التشغيل تشحن وحدة JS أو WASM تصدّر دالة واحدة اسمها run:
run(seed, config, trace) -> { passed, score, durationMs }type RunFn<C = unknown> = (
seed: Seed,
config: C | null,
trace: Uint8Array | string,
) => Verdict | Promise<Verdict>| الوسيط | المصدر | المعنى |
|---|---|---|
seed | الخادم | بذرة كل جولة. إعداد الجولة المملوك للخادم. |
config | الخادم | إعداد اللعب، قابل لأن يكون null ومعتم: لا تفحصه المنصّة أبدًا، ويُعاد حلّه على الخادم عند إعادة التشغيل، لا يدّعيه العميل أبدًا. null يعني "استخدم افتراضيات run الخاصة". في الـ MVP يمرّر الخادم null؛ والإعداد لكل موقع مرحلة مؤجَّلة. |
trace | العميل | الكتلة المعتمة التي بثّها عميل اللاعب، التي تفسّرها هذه الدالة وحدها. لا تحلّلها المنصّة ولا تكتب نوعها أبدًا؛ إنها بايتات خام أو نصّ، محدودة فقط بسقف حجم وحدّ وحدة معالجة العازل. |
ترتيب الوسائط يرمّز نموذج الثقة: seed وconfig إعداد الجولة على الخادم؛ وtrace مُدخَل اللاعب. القيم المؤثّرة في البوّابة (عتبة نجاح، أرواح) آمنة لقراءتها من config لكنها ستكون تجاوزًا لو قُرئت من trace العميل.
يجب أن تصدّر الوحدة الدالة باسم run (الثابت RUN_EXPORT_NAME). يستدعيها مضيف إعادة التشغيل وينتظر النتيجة (إنشاء WASM غير متزامن في أول نداء).
البذرة
type Seed = readonly [number, number, number, number] // 128 bits, 4 u32 words, MSW firstالبذرة هي أدنى 128 بتًّا من SHA-256(sessionId : gameId : roundIndex)، محمولة كأربع كلمات 32-بتية غير موقّعة، الكلمة الأكثر دلالة أولًا. deriveSeed(sessionId, gameId, roundIndex) في الحزمة يحسبها؛ يشتقّها الخادم عند إصدار الجولة وعند إعادة تشغيلها مجدّدًا، فلا تركب السلك أبدًا كمُدخَل عميل موثوق.
والأهمّ، تربط البذرة أثرًا بجلسة ولعبة وجولة واحدة: إعادة تشغيل أثر غريب أو أقدم تحت بذرة مختلفة تنتج passed: false. ذلك الربط هو كيف يُدافَع عن حقن الأثر وهجمات إعادة التشغيل، فيجب أن تبذر كل العشوائية منها كي يتّفق اللعب الحيّ وإعادة تشغيل الخادم.
الحُكم
interface Verdict {
readonly passed: boolean // drives the verification decision
readonly score: number // game-defined, any finite number
readonly durationMs: number // finite, non-negative
}passed هو الحقل الوحيد الذي يقود قرار الـ captcha؛ وscore وdurationMs محمولان في الرمز المُصدَر (ولوحة نتائج مستقبلية). parseVerdict(value) في الحزمة يتحقّق من قيمة عائدة غير موثوقة ويعامل نتيجة مشوّهة كجولة مرفوضة، لا ناجحة أبدًا، فـ run يرمي أو يعيد قمامةً يفشل مغلقًا.
الحتمية هي المتطلّب كله
قاعدة العقد الصارمة الوحيدة: يجب أن يكون run نقيًّا وحتميًّا عبر زمني تشغيل، متصفّح اللاعب وعازل الخادم. (seed, config, trace) متطابق يجب أن ينتج حُكمًا متطابقًا في كليهما. الطرق الشائعة لكسر هذا، كلها يمسكها الفحص الذاتي للنشر كـ run-not-conforming:
- قراءة
Date.now()، أوperformance.now()، أوMath.random()، أو متغيّرات عامة أخرى غير حتمية؛ - قراءة حالة خارجية (DOM، شبكة، تخزين) لا يقدّمها عازل إعادة التشغيل؛
- الاعتماد على حساب فاصلة عائمة يختلف بين زمني التشغيل.
كيف تحقّق الحتمية (فاصلة ثابتة، أو فواصل WASM-spec، أو IEEE-754 إضافةً إلى غلاف تحييد) خيارك؛ المنصّة تستضيف إعادة التشغيل فقط. عدّة المحرّك تقدّم أوّليات حتمية جاهزة إن آثرت ألّا تصنع خاصتك.
كيف يستخدمه الخادم
الأداة التي يحمّلها العازل هي حزمة run خاصتك، إمّا entry الحيّة أو أداة run المخصّصة من البيان.
انظر أيضًا
- عدّة engine-kit: أنتج
runمطابقًا من مُختزِل عادي، بالحتمية متولّاةً عنك. - بيان caputchin.json: إعلان أداة
runووحداتها. - ابنِ لعبة المتجر: تسجيل الأثر الذي يعيد هذا العقد تشغيله.
- مرجع أخطاء النشر: نتيجة الفحص الذاتي
run-not-conforming.