وثائق أصول Core

توثيق محرك حالة العالم الحتمي ومراجع API.

وثائق بوابة القرار

مخزون اللعبة: تجهيز حتمي، تجارة، خزينة، وغنائم

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

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

لماذا هذا مهم

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

نموذج النظام

  • اللاعبون: مُصمَّمون كمالكين ومعرّفات الممثلين (بدون تتبع حركة اللاعبين).
  • الجرد: حاويات الشبكة للتوزيع الحتمي + قواعد التصادم.
  • المعدات: حاويات الفتحات لدلالات التجهيز الصريحة.
  • Stash: حاوية شبكة لعرض آليات التخزين على المدى الطويل.
  • المحافظ: حاويات رصيد للعملة والتداولات الذرية.
  • شبكة نهب المنطقة: نقاط نهب حتمية للمواجهات، الصناديق، أو السقوط.
  • تمثل نقاط السلب الموثوقة أماكن موثوقة، وليست فيزيائية أو حركية.
  • الفئات والأشكال: مسجلة حتى تظل قواعد التوزيع متسقة.
  • الوحدات: خلية (fixed_point_scale=1).

الإعداد العالمي

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

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

الحاويات التي تم إنشاؤها

  • zone_loot (id container-32001): شبكة نهب المنطقة لسقوط المواجهات ومكافآت الصناديق.
  • p1_inventory (id container-32002): شبكة جرد اللاعب 1 (الحقيبة).
  • p1_equipment (id container-32004): فتحات معدات اللاعب 1 (سلاح، يد غير مسلحة، درع).
  • p1_stash (id container-32008): شبكة تخزين اللاعب 1 للتخزين على المدى الطويل.
  • p1_wallet (id container-32006): حافظة اللاعب 1 رصيد الحافظة (العملة).
  • p2_inventory (id container-32003): شبكة مخزون اللاعب 2.
  • p2_equipment (id container-32005): فتحات معدات اللاعب 2.
  • p2_stash (id container-32009): شبكة تخزين اللاعب 2.
  • p2_wallet (id container-32007): حافظة اللاعب 2 رصيد الحافظة.

الفصول المسجلة

  • سيف (id class-42001): مثيل عنصر السيف.
  • درع (id class-42002): مثيل عنصر الدرع.
  • القوس (id class-42003): مثيل عنصر القوس.
  • درع (id class-42004): مثيل عنصر الدرع.
  • جرعة (id class-42005): عنصر جرعة قابل للتكديس.
  • ذهب (id class-42006): عملة ذهبية قابلة للتداول.

إعداد الالتزام

تسجيل الفصول/الأشكال وإنشاء الحاويات.

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

الشروط المسبقة:

  • لا شيء. هذا هو الالتزام الابتدائي.

ما الذي ينشئه هذا الالتزام:

  • zone_loot (id container-32001): شبكة نهب المنطقة لسقوط المواجهات ومكافآت الصناديق.
  • p1_inventory (id container-32002): شبكة جرد اللاعب 1 (الحقيبة).
  • p1_equipment (id container-32004): فتحات معدات اللاعب 1 (سلاح، يد غير مسلحة، درع).
  • p1_stash (id container-32008): شبكة تخزين اللاعب 1 للتخزين على المدى الطويل.
  • p1_wallet (id container-32006): حافظة اللاعب 1 رصيد الحافظة (العملة).
  • p2_inventory (id container-32003): شبكة مخزون اللاعب 2.
  • p2_equipment (id container-32005): فتحات معدات اللاعب 2.
  • p2_stash (id container-32009): شبكة تخزين اللاعب 2.
  • p2_wallet (id container-32007): حافظة اللاعب 2 رصيد الحافظة.

ما يسجله هذا الالتزام:

  • سيف (id class-42001): مثيل عنصر السيف.
  • درع (id class-42002): مثيل عنصر الدرع.
  • القوس (id class-42003): مثيل عنصر القوس.
  • درع (id class-42004): مثيل عنصر الدرع.
  • جرعة (id class-42005): عنصر جرعة قابل للتكديس.
  • ذهب (id class-42006): عملة ذهبية قابلة للتداول.

ما يجب ملاحظته في الكود:

  • كل استدعاء لـ CreateContainer يتوافق 1:1 مع حاوية مدرجة أعلاه.
  • كل RegisterClass يحدد قواعد الترتيب والتكديس المستخدمة لاحقًا.

تغيير الحالة المتوقع:

  • الحاويات والفئات مسجلة وجاهزة للتفاعل في اللعب.

العمليات

  • CreateContainer (x9) - ينشئ حاوية (منطقة ذاكرة منظمة) بالنوع المطلوب.
  • RegisterClass (x6) - يسجل تعريف فئة بحيث يمكن للعمليات المستقبلية الإشارة إليها.
  • RegisterClassShape (x5) - يسجل بصمة شكل الشبكة لفئة أو نوع فئة.
await write_client.commit_operations(
    [
        CreateContainer(
            kind={"capacity": 25, "grid_width": 5, "type": "grid"},
            policies=None,
            external_id="container-32001",
            owner_external_id="owner-73",
        ),
        CreateContainer(
            kind={"capacity": 25, "grid_width": 5, "type": "grid"},
            policies=None,
            external_id="container-32002",
            owner_external_id="owner-71",
        ),
        CreateContainer(
            kind={"capacity": 25, "grid_width": 5, "type": "grid"},
            policies=None,
            external_id="container-32003",
            owner_external_id="owner-72",
        ),
        CreateContainer(
            kind={"count": 4, "type": "slots"},
            policies=None,
            external_id="container-32004",
            owner_external_id="owner-71",
        ),
        CreateContainer(
            kind={"count": 4, "type": "slots"},
            policies=None,
            external_id="container-32005",
            owner_external_id="owner-72",
        ),
        CreateContainer(
            kind={"quantization_inv": 1, "type": "balance"},
            policies=None,
            external_id="container-32006",
            owner_external_id="owner-71",
        ),
        CreateContainer(
            kind={"quantization_inv": 1, "type": "balance"},
            policies=None,
            external_id="container-32007",
            owner_external_id="owner-72",
        ),
        CreateContainer(
            kind={"capacity": 25, "grid_width": 5, "type": "grid"},
            policies=None,
            external_id="container-32008",
            owner_external_id="owner-71",
        ),
        CreateContainer(
            kind={"capacity": 25, "grid_width": 5, "type": "grid"},
            policies=None,
            external_id="container-32009",
            owner_external_id="owner-72",
        ),
        RegisterClass(
            request={
                "behavior": {"balance_scale": 1},
                "class_id": "class-42001",
                "flags": 0,
                "name": "item-sword",
            },
        ),
        RegisterClass(
            request={
                "behavior": {"balance_scale": 1},
                "class_id": "class-42002",
                "flags": 0,
                "name": "item-shield",
            },
        ),
        RegisterClass(
            request={
                "behavior": {"balance_scale": 1},
                "class_id": "class-42003",
                "flags": 0,
                "name": "item-bow",
            },
        ),
        RegisterClass(
            request={
                "behavior": {"balance_scale": 1},
                "class_id": "class-42004",
                "flags": 0,
                "name": "item-armor",
            },
        ),
        RegisterClass(
            request={
                "behavior": {"balance_scale": 1},
                "class_id": "class-42005",
                "flags": 0,
                "name": "item-potion",
            },
        ),
        RegisterClass(
            request={
                "behavior": {"balance_scale": 1},
                "class_id": "class-42006",
                "flags": 0,
                "name": "currency-gold",
            },
        ),
        RegisterClassShape(
            request={"class_id": "class-42001", "shape": {"height": 1, "width": 1}},
        ),
        RegisterClassShape(
            request={"class_id": "class-42002", "shape": {"height": 2, "width": 2}},
        ),
        RegisterClassShape(
            request={"class_id": "class-42003", "shape": {"height": 1, "width": 1}},
        ),
        RegisterClassShape(
            request={"class_id": "class-42004", "shape": {"height": 2, "width": 2}},
        ),
        RegisterClassShape(
            request={"class_id": "class-42005", "shape": {"height": 1, "width": 1}},
        ),
    ],
    namespace_id=1,
    idempotency_key="game-inventory-setup",
    actor_id="game-setup",
    metadata={
        "job_id": "job-inventory-001",
        "step": "setup",
        "trace_id": "trace-inventory-001",
    },
)

التزام البذور

توزيع مخزونات اللاعبين، حظر التخزين، والأرصدة.

لماذا هذه الخطوة مهمة: تعتبر عملية التهيئة هي الوقت الوحيد الذي نقوم فيه بحقن العناصر والأرصدة. بعد ذلك، تتحرك الحالة فقط بين الحاويات أو تنقص العد.

الشروط المسبقة:

  • تم إنشاء الحاويات وتسجيل الفئات بواسطة عملية الالتزام.

ما يجب ملاحظته في الكود:

  • يتم سكّ مثيلات العناصر مباشرة في مخزونات اللاعبين والمخازن.
  • يتم تمويل أرصدة المحفظة وتكدسات الجرع عبر AddFungible.
  • تم وضع حظر التخزين عن عمد لفرض فرع POSITION_OCCUPIED.

تغيير الحالة المتوقع:

  • العناصر الأساسية، الأرصدة، وتكدسات الجرع موجودة في حاوياتها المستهدفة.
  • يقوم حظر التخزين باحتلال مرساة خاصة به لفرض تصادم حتمي.

العمليات

  • AddInstance (x5) - يصنع مثيلاً جديداً ويضعه في موقع مستهدف.
  • AddFungible (x4) - يضيف كمية قابلة للتداول إلى رصيد أو خلية شبكة محددة.
await write_client.commit_operations(
    [
        AddInstance(
            class_id="class-42001",
            client_tag="p1-sword",
            key=None,
            location={
                "container_id": "container-32002",
                "kind": "grid_cell",
                "position": 3,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42003",
            client_tag="p1-bow",
            key=None,
            location={
                "container_id": "container-32002",
                "kind": "grid_cell",
                "position": 5,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42004",
            client_tag="p1-armor",
            key=None,
            location={
                "container_id": "container-32002",
                "kind": "grid_cell",
                "position": 7,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42002",
            client_tag="p2-shield",
            key=None,
            location={
                "container_id": "container-32003",
                "kind": "grid_cell",
                "position": 8,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42004",
            client_tag="stash-blocker",
            key=None,
            location={
                "container_id": "container-32008",
                "kind": "grid_cell",
                "position": 1,
                "rotation": None,
            },
        ),
        AddFungible(
            class_id="class-42006",
            key=None,
            location={"container_id": "container-32006", "kind": "balance"},
            quantity="1000",
        ),
        AddFungible(
            class_id="class-42006",
            key=None,
            location={"container_id": "container-32007", "kind": "balance"},
            quantity="800",
        ),
        AddFungible(
            class_id="class-42005",
            key=None,
            location={
                "container_id": "container-32002",
                "kind": "grid_cell",
                "position": 11,
                "rotation": None,
            },
            quantity="5",
        ),
        AddFungible(
            class_id="class-42005",
            key=None,
            location={
                "container_id": "container-32002",
                "kind": "grid_cell",
                "position": 14,
                "rotation": None,
            },
            quantity="3",
        ),
    ],
    namespace_id=1,
    idempotency_key="game-inventory-seed",
    actor_id="world-seed",
    metadata={
        "job_id": "job-inventory-001",
        "step": "seed",
        "trace_id": "trace-inventory-001",
    },
)

الحالات في لمحة

  1. تجهيز - تجهيز عنصر في فتحات اللاعب 1.
  2. التجارة - قم بتجارة عنصر وعملة بشكل ذري.
  3. إسقاط - إسقاط عنصر في منطقة الغنائم.
  4. استلام - استلام الغنائم إلى مخزون اللاعب 1.
  5. تخزين محجوز - التخزين المحجوز يعيد POSITION_OCCUPIED.
  6. Stash Grid Free - ابحث عن نقطة التخزين المتاحة التالية.
  7. Stash - خزّن الدروع في نقطة ربط مجانية.
  8. دمج المكدسات - دمج مكدسات الجرع في مخزون اللاعب 1.
  9. استهلاك - استهلاك الجرع من الكومة المدمجة.

تصميم النظام: ما اخترناه ولماذا

هذا السيناريو يركز على دقة المخزون من مستوى MMO: كل تفاعل هو التزام صغير وذري يمكن إعادة تشغيله وتدقيقه.

الخيار: اللاعبون كمالكين

الاختيار: اللاعبون هم المالكون؛ المخزونات والفجوات هي حاويات مملوكة، وليست عناصر متحركة.

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

الخيار: شبكة نهب كمرتكزات حتمية

الاختيار: تعيش الغنائم في حاوية شبكية مع نقاط تثبيت مستقرة (لكل مواجهة/صدر/منطقة).

  • لماذا: تجعل المراسي عمليات الإسقاط والالتقاط حتمية وقابلة للتدقيق دون الارتباط بالفيزياء.
  • التبادل: العرض المكاني يكون على جانب المحرك؛ يوفر Asset Core حالة الغنائم الموثوقة.
  • عندما يكون الأمر مهمًا: الغنائم في العالم المشترك، مكافآت الغارات، والتحقيقات القابلة لإعادة التشغيل.

الخيار: الالتزامات التجارية الذرية

الاختيار: يتم نقل العناصر + نقل العملة في عملية واحدة.

  • لماذا: تضمن الذرية عدم وجود تكرارات أو صفقات جزئية تحت إعادة المحاولة أو انقطاع الاتصال.
  • التبادل: يجب على العملاء تكوين عمليات التزام متعددة (التبادل هو معاملة واحدة).
  • عندما يكون الأمر مهمًا: تداول اللاعب مع اللاعب، تسوية المزادات، وتدفقات الضمان.

الخيار: مفاتيح الاستقرارية لسلامة إعادة المحاولة

الاختيار: مفاتيح التكرار الاختياري اختيارية ولكن، عند وجودها، تقوم بإزالة التكرار في المحاولات بناءً على تجزئة معيارية لحزمة الطلب الكاملة.

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

ما لا نتعامل معه (ولماذا)

  • حركة اللاعب: لا تتعقب AssetCore الحركة أو الفيزياء. محركك يمتلك الحركة؛ بينما تمتلك AssetCore سلطة الجرد.
  • Combat RNG: تحدث فرص السقوط وعمليات المكافأة في أنظمة اللعب؛ يسجل AssetCore الحالة الناتجة.

تساعد هذه الحدود AssetCore على التركيز على ضمانات المخزون والمعاملات الحتمية.

دلالات التكرار

مفاتيح التكرار اختيارية وتتحكم فقط في إزالة التكرار عند إعادة المحاولة. عندما يكون المفتاح موجودًا، يقوم Asset Core بتجزئة الحمولة الكاملة للطلب ويستخدم (namespace، key، hash) لتحديد:

  • نفس المفتاح + نفس الحمولة: إرجاع الاستجابة المخزنة (بدون إعادة التنفيذ).
  • نفس المفتاح + حمولة مختلفة: الرفض مع 409 تعارض.
  • فشل التحقق لا يحتفظ بالمفتاح؛ إعادة الاستخدام بعد التصحيح صالحة.
  • No key: request executes normally, retries re-run. لا مفتاح: يتم تنفيذ الطلب بشكل طبيعي، وتُعاد المحاولات. في التدفقات المعتمدة على الخادم، يقوم خدمة اللعبة بإنشاء أو اشتقاق المفاتيح ويجب ألا تقوم بتمرير مفاتيح العملاء غير الموثوق بها.

كيفية قراءة الحالات

  • تفترض الحالات أن إعدادات القاعدة الأساسية والتزامات البذور قد تم تطبيقها بالفعل.
  • كل حالة هي تفاعل مركز (التزام أو قراءة) يمكنك إعادة تشغيله بشكل مستقل.
  • تُدرج قراءات/تدفقات التحقق بشكل منفصل بعد الحالات.
  • تستخدم حالات العملية الفردية مساعدات الإجراءات لزيادة وضوح القراءة.
  • تبقى حالات العمليات المتعددة كنداءات التزام للحفاظ على الذرية.
  • يستخدم Rust HTTP دائمًا نقطة النهاية الخاصة بالالتزام مع عملية واحدة أو أكثر.
  • تظهر علامات C ABI تكاملًا مباشرًا في وقت التشغيل لفرق المحرك.

أدوات السيناريو

لقطة سيناريو

{
  "scenario_id": "game_inventory",
  "version": "0.1.0",
  "namespace_id": 1,
  "containers": {
    "p1_equipment": "container-32004",
    "p1_inventory": "container-32002",
    "p1_stash": "container-32008",
    "p1_wallet": "container-32006",
    "p2_equipment": "container-32005",
    "p2_inventory": "container-32003",
    "p2_stash": "container-32009",
    "p2_wallet": "container-32007",
    "zone_loot": "container-32001"
  },
  "classes": {
    "armor": "class-42004",
    "bow": "class-42003",
    "gold": "class-42006",
    "potion": "class-42005",
    "shield": "class-42002",
    "sword": "class-42001"
  },
  "units": {
    "fixed_point_scale": 1,
    "length_unit": "cell",
    "time_unit": "ms"
  }
}

فرع الفشل

POSITION_OCCUPIED -> الشبكة_فارغة -> إعادة المحاولة بنفس مفتاح التكرار

سجل التدقيق (اختياري)

تلتقط النسخة النصية أزواج الطلبات والاستجابات الدقيقة التي تم إصدارها خلال التشغيل. هنا مقتطف مضغوط يمكنك استخدامه للتحقق من الحتمية وانتقال البيانات الوصفية:

{
  "kind": "commit",
  "name": "setup",
  "request": {
    "path": "/v1/write/namespaces/1/commit",
    "idempotency_key": "game-inventory-setup",
    "actor_id": "game-setup",
    "metadata": {
      "job_id": "job-inventory-001",
      "step": "setup",
      "trace_id": "trace-inventory-001"
    },
    "operations": [
      "CreateContainer",
      "CreateContainer",
      "CreateContainer",
      "CreateContainer",
      "CreateContainer",
      "CreateContainer",
      "CreateContainer",
      "CreateContainer",
      "CreateContainer",
      "RegisterClass",
      "RegisterClass",
      "RegisterClass",
      "RegisterClass",
      "RegisterClass",
      "RegisterClass",
      "RegisterClassShape",
      "RegisterClassShape",
      "RegisterClassShape",
      "RegisterClassShape",
      "RegisterClassShape"
    ]
  },
  "response": {
    "commit_id": "00000000000000000000000000000007",
    "world_seq_start": 1,
    "world_seq_end": 20,
    "event_count": 20
  }
}

ملاحظة ما قبل الرحلة

  • تقوم استدعاءات الإجراء بتقديم الالتزامات بشكل افتراضي.
  • قم بتعيين ActionOptions(preflight=True) أو استدعاء assetcore_commit_preflight للتحقق من الصحة.

إعداد SDK

from assetcore_sdk import AssetCoreClient
from assetcore_sdk.actions import ActionOptions
from assetcore_sdk.operations import (
    AddFungible,
    AddInstance,
    CreateContainer,
    MergeStacks,
    MoveInstance,
    RegisterClass,
    RegisterClassShape,
    RemoveFungible,
    TransferFungible,
)

# Snippets assume an async context (e.g., inside async def main()).
write_client = AssetCoreClient(
    base_url="http://localhost:8080",
    api_key="WRITE_API_KEY",
)
read_client = AssetCoreClient(
    base_url="http://localhost:8081",
    api_key="READ_API_KEY",
)

الحالات

الحالة 1: التجهيز

تجهيز عنصر في فتحات اللاعب 1.

لماذا هذه الخطوة مهمة: التجهيز هو حركة خالصة من المخزون إلى الفتحة. لا توجد حالة مخفية - فقط تغيير موضع حتمي.

الشروط المسبقة:

  • العنصر موجود في مخزون اللاعب من التزام البذور.
  • الفتحة المستهدفة فارغة.

ما يجب ملاحظته في الكود:

  • عملية MoveInstance واحدة تستهدف حاوية الفتحة
  • مفتاح التماثل المرتبط بإجراء التجهيز

تغيير الحالة المتوقع:

  • العنصر يغادر شبكة المخزون ويشغل الفتحة المستهدفة.

العمليات

  • MoveInstance - ينقل مثيلاً موجودًا إلى موقع جديد.
await write_client.actions.move_instance(
    instance="inst-1",
    to={"container_id": "container-32004", "kind": "slot", "slot_index": 1},
    options=ActionOptions(
        namespace_id=1,
        idempotency_key="game-inventory-equip",
        actor_id="player-1",
        metadata={
            "job_id": "job-inventory-001",
            "player_id": "player-1",
            "step": "equip",
            "trace_id": "trace-inventory-001",
        },
    ),
)

الحالة 2: التجارة

تداول عنصر وعملة بشكل ذري.

لماذا هذه الخطوة مهمة: يجب أن تكون المعاملات ذرية لمنع التكرار والتحويلات غير المكتملة.

الشروط المسبقة:

  • يوجد عنصر في مخزون البائع من البذور أو الحالات السابقة.
  • كلا رصيد المحفظة موجود.

ما يجب ملاحظته في الكود:

  • MoveInstance + TransferFungible في عملية واحدة
  • معرف الالتزام الواحد للعناصر + تسوية العملة

تغيير الحالة المتوقع:

  • تنتقل العنصر إلى مخزون المشتري وتتحرك العملة إلى محفظة البائع بشكل ذري.

العمليات

  • MoveInstance - ينقل مثيلاً موجودًا إلى موقع جديد.
  • TransferFungible - ينقل الكمية القابلة للتبادل بين الحاويات المهيكلة.
await write_client.commit_operations(
    [
        MoveInstance(
            instance="inst-2",
            to={
                "container_id": "container-32003",
                "kind": "grid_cell",
                "position": 2,
                "rotation": None,
            },
        ),
        TransferFungible(
            class_id="class-42006",
            from_container="container-32007",
            key=None,
            quantity="50",
            to_container="container-32006",
        ),
    ],
    namespace_id=1,
    idempotency_key="game-inventory-trade",
    actor_id="trade-service",
    metadata={
        "from_player": "player-1",
        "job_id": "job-inventory-001",
        "step": "trade",
        "to_player": "player-2",
        "trace_id": "trace-inventory-001",
    },
)

الحالة 3: إسقاط

اسقط عنصرًا في منطقة الغنائم.

لماذا هذه الخطوة مهمة: الإسقاط هو حركة من مخزون اللاعب إلى شبكة غنائم المنطقة. الشبكة هي الجدول الرسمي للغنائم للاشتباك.

الشروط المسبقة:

  • العنصر موجود في مخزون اللاعب.
  • نقطة ربط الشبكة المستهدفة مجانية.

ما يجب ملاحظته في الكود:

  • MoveInstance يحدد فقط الهدف؛ المصدر هو الموقع الحالي للحالة.
  • يحدد ربط الشبكة مكان الغنائم بشكل حتمي
  • العنصر يغادر مخزون اللاعب ويصبح غنيمة مملوكة للمنطقة

تغيير الحالة المتوقع:

  • تمت إزالة العنصر من مخزون اللاعب وتم وضعه في غنائم المنطقة.

العمليات

  • MoveInstance - ينقل مثيلاً موجودًا إلى موقع جديد.
await write_client.actions.move_instance(
    instance="inst-4",
    to={
        "container_id": "container-32001",
        "kind": "grid_cell",
        "position": 6,
        "rotation": None,
    },
    options=ActionOptions(
        namespace_id=1,
        idempotency_key="game-inventory-drop",
        actor_id="player-2",
        metadata={
            "job_id": "job-inventory-001",
            "player_id": "player-2",
            "step": "drop",
            "trace_id": "trace-inventory-001",
        },
    ),
)

الحالة 4: الاستلام

التقاط الغنائم في مخزون اللاعب 1.

لماذا هذه الخطوة مهمة: تعتبر عمليات الالتقاط حركات حتمية من غنائم المنطقة إلى مخزون اللاعب - لا يوجد أي غموض حول من يملك العنصر.

الشروط المسبقة:

  • العنصر موجود في شبكة نهب المنطقة (من حالة السقوط).
  • نقطة الربط في المخزون المستهدف مجانية.

ما يجب ملاحظته في الكود:

  • نفس معرف الحالة ينتقل عبر الحاويات
  • ترتبط بيانات الالتزام بالإجراء باللاعب

تغيير الحالة المتوقع:

  • العنصر يغادر منطقة السلب ويعود إلى فتحة مخزون اللاعب.

العمليات

  • MoveInstance - ينقل مثيلاً موجودًا إلى موقع جديد.
await write_client.actions.move_instance(
    instance="inst-4",
    to={
        "container_id": "container-32002",
        "kind": "grid_cell",
        "position": 19,
        "rotation": None,
    },
    options=ActionOptions(
        namespace_id=1,
        idempotency_key="game-inventory-pickup",
        actor_id="player-1",
        metadata={
            "job_id": "job-inventory-001",
            "player_id": "player-1",
            "step": "pickup",
            "trace_id": "trace-inventory-001",
        },
    ),
)

الحالة 5: تم حظر التخزين (خطأ متوقع)

تم حظر الإيداع، والعودة إلى POSITION_OCCUPIED.

لماذا هذه الخطوة مهمة: يتم التحقق من تصادمات التخزين المؤقت قبل التعديل. لا توجد كتابات جزئية، ولا منطق تنظيف.

الشروط المسبقة:

  • يقوم حظر التخزين باحتلال نقطة الربط المستهدفة (التزام البذور).

ما يجب ملاحظته في الكود:

  • خطأ POSITION_OCCUPIED قبل أي تغيير في الحالة
  • إعادة المحاولة تستخدم grid_free للعثور على نقطة ربط جديدة

تغيير الحالة المتوقع:

  • لم يتم تطبيق أي تغييرات على الحالة؛ هذه هي فشل التحقق الحتمي.

العمليات

  • MoveInstance - ينقل مثيلاً موجودًا إلى موقع جديد.

خطأ متوقع

{
  "code": "POSITION_OCCUPIED",
  "detail": "Position 1 in container 8 is occupied by item at anchor 1",
  "hint": "Select an unoccupied position or move the existing item first.",
  "retryable": false,
  "status": 409,
  "title": "ConflictError",
  "type": "urn:assetcore:error:POSITION_OCCUPIED"
}
await write_client.actions.move_instance(
    instance="inst-3",
    to={
        "container_id": "container-32008",
        "kind": "grid_cell",
        "position": 1,
        "rotation": None,
    },
    options=ActionOptions(
        namespace_id=1,
        idempotency_key="game-inventory-stash",
        actor_id="player-1",
        metadata={
            "job_id": "job-inventory-001",
            "player_id": "player-1",
            "step": "stash-attempt",
            "trace_id": "trace-inventory-001",
        },
    ),
)

الحالة 6: شبكة التخزين المجانية

ابحث عن نقطة التخزين المتاحة التالية.

await read_client.get_container_grid_free(
    container_id="container-32008",
    namespace_id=1,
    height=2,
    width=2,
)

الحالة 7: التخزين

قم بتخزين الدروع في نقطة ربط مجانية.

العمليات

  • MoveInstance - ينقل مثيلاً موجودًا إلى موقع جديد.
await write_client.actions.move_instance(
    instance="inst-3",
    to={
        "container_id": "container-32008",
        "kind": "grid_cell",
        "position": 3,
        "rotation": None,
    },
    options=ActionOptions(
        namespace_id=1,
        idempotency_key="game-inventory-stash",
        actor_id="player-1",
        metadata={
            "job_id": "job-inventory-001",
            "player_id": "player-1",
            "step": "stash",
            "trace_id": "trace-inventory-001",
        },
    ),
)

الحالة 8: دمج المكدس

دمج كميات الجرع في مخزون اللاعب 1.

لماذا هذه الخطوة مهمة: تساعد عملية دمج المكدسات في الحفاظ على المخزونات مضغوطة مع الحفاظ على الكمية الإجمالية.

الشروط المسبقة:

  • توجد عدة أكوام من الجرع في شبكة المخزون.

ما يجب ملاحظته في الكود:

  • MergeStacks يستخدم معرفات المكدس من عرض القراءة

تغيير الحالة المتوقع:

  • يتم دمج المكدسات المصدرية في مكدس واحد مع الحفاظ على الكمية الإجمالية.

العمليات

  • MergeStacks - يدمج كومة قابلة للتبادل في أخرى داخل حاوية.
await write_client.actions.merge_stacks(
    dst_stack="stack-1",
    src_stack="stack-2",
    options=ActionOptions(
        namespace_id=1,
        idempotency_key="game-inventory-stack-merge",
        actor_id="player-1",
        metadata={
            "job_id": "job-inventory-001",
            "player_id": "player-1",
            "step": "stack-merge",
            "trace_id": "trace-inventory-001",
        },
    ),
)

الحالة 9: الاستهلاك

استهلك الجرع من الكومة المدمجة.

لماذا هذه الخطوة مهمة: الاستهلاك هو مجرد تخفيض حتمي؛ تظهر سجلات التدقيق بالضبط ما الذي تغير.

الشروط المسبقة:

  • توجد مجموعة من الجرع في شبكة المخزون.

ما يجب ملاحظته في الكود:

  • RemoveFungible يستهدف موقع كومة الشبكة

تغيير الحالة المتوقع:

  • تنخفض كمية الكومة بمقدار الكمية المستهلكة.

العمليات

  • RemoveFungible - يزيل الكمية القابلة للتداول من الرصيد أو خلية الشبكة المحددة.
await write_client.actions.remove_fungible(
    class_id="class-42005",
    from_={
        "container_id": "container-32002",
        "kind": "grid_cell",
        "position": 11,
        "rotation": None,
    },
    key=None,
    quantity="2",
    options=ActionOptions(
        namespace_id=1,
        idempotency_key="game-inventory-consume",
        actor_id="player-1",
        metadata={
            "job_id": "job-inventory-001",
            "player_id": "player-1",
            "step": "consume",
            "trace_id": "trace-inventory-001",
        },
    ),
)

التحقق

تؤكد هذه القراءات والتدفقات الحالة النهائية بعد الحالات دون تغيير أي شيء.

البث 1: تجارة البث

اقرأ التزام التجارة من SSE.

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "assetcore_read_stream",
  "params": {
    "from_world_seq": 36,
    "limit": 1,
    "namespace_id": 1
  }
}

قراءة 2: قراءة كومات الجرع

حل معرفات كومة الجرع للدمج.

await read_client.get_container_grid(
    container_id="container-32002",
    namespace_id=1,
)

قراءة 3: قراءة الفتحات

تحقق من فتحات معدات اللاعب 1.

await read_client.get_container_slots(
    container_id="container-32004",
    namespace_id=1,
)

قراءة 4: قراءة مخزون P1

تحقق من شبكة مخزون اللاعب 1.

await read_client.get_container_grid(
    container_id="container-32002",
    namespace_id=1,
)

اقرأ 5: اقرأ مخزون P2

تحقق من شبكة مخزون اللاعب 2.

await read_client.get_container_grid(
    container_id="container-32003",
    namespace_id=1,
)

قراءة 6: قراءة غنائم المنطقة

تحقق من أن شبكة نهب المنطقة فارغة.

await read_client.get_container_grid(
    container_id="container-32001",
    namespace_id=1,
)

اقرأ 7: اقرأ التخزين المؤقت

تحقق من مواضع خزائن اللاعب 1.

await read_client.get_container_grid(
    container_id="container-32008",
    namespace_id=1,
)

اقرأ 8: اقرأ المحافظ

تحقق من أرصدة محافظ اللاعبين بعد التجارة.

let request = client
    .get(format!("{}/v1/read/namespaces/1/containers/container-32006/balances", read_base_url))
    .bearer_auth(read_api_key)
    .send()
    .await?;
let body = request.text().await?;

قراءة 9: قراءة محافظ P2

تحقق من أرصدة محفظة اللاعب 2 بعد التجارة.

let request = client
    .get(format!("{}/v1/read/namespaces/1/containers/container-32007/balances", read_base_url))
    .bearer_auth(read_api_key)
    .send()
    .await?;
let body = request.text().await?;

اقرأ 10: اقرأ الجرع

تحقق من رصيد الجرع الموحد في شبكة المخزون.

let request = client
    .get(format!("{}/v1/read/namespaces/1/containers/container-32002/balances", read_base_url))
    .bearer_auth(read_api_key)
    .send()
    .await?;
let body = request.text().await?;

ما الذي يثبته هذا: جرد من مستوى MMO بدون تكرارات

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

نقطة الإثبات 1: التداولات الذرية وضمانات مكافحة التكرار

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

نقطة الإثبات 2: نهب المنطقة كدولة ذات سلطة

تسقط العناصر في شبكة نهب منطقة (أو شبكة لكل مواجهة). تعتبر عمليات الالتقاط حركات حتمية من تلك الشبكة إلى مخزون اللاعب. يقوم محرك اللعبة بالرسم، وتضمن AssetCore السلطة.

نقطة الإثبات 3: الإغلاق الكامل عبر المخزون، المعدات، والتخزين

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

نقطة الإثبات 4: آليات التخزين والتكديس مع أوضاع فشل واضحة

تفشل المواقع المحجوبة قبل التغيير، ويجد grid_free المرساة التالية، وتتجمع المكدسات دون فقدان الكمية. كل تغيير يمكن تدقيقه وإعادة تشغيله.

نقطة الإثبات 5: تكامل على مستوى المحرك

كل خطوة تتضمن علامة C ABI حتى تتمكن فرق المحرك من التكامل دون الاعتماد على HTTP أو SDK مع الحفاظ على الضمانات الحتمية.

الخطوات التالية

  • جرّبه محليًا: قم بتشغيل السيناريو لتنفيذ مجموعة التفاعل الكاملة
  • قم بتمديده: أضف الحرف، المزادات، أو المخزونات الموسمية بنفس العناصر الأساسية
  • دمجه: استخدم علامة C ABI للتوصيل في وقت تشغيل محركك