BeamNG.Earth
BeamNG.drive용 1:1 비율 실시간 지구, 실제 고도·위성 이미지·부동 원점·보조 서버에서 LOD 스트리밍
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기لعبة مواجهة الوحوش MTO — لعبة كروت استراتيجية + طورا بقاء 2D/3D (Next.js 16 + Three.js + Capacitor)
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
لعبة كروت استراتيجية 1 ضد 1 ضد خصم آلي: سطح مشترك من 272 كارت، مطابقة عناصر وأرقام على طريقة الأونو، نظام طاقة متصاعد، فخاخ تنطلق تلقائياً، هجمات مشتركة، ووحش أعظم يحسم المباراة فوراً. مبنية على Next.js 16 (App Router) + Tailwind v4، جاهزة للنشر على Vercel، مع Supabase اختياري لحفظ سجل المباريات. خصائص الوحوش: اندفاع (يهاجم فور استدعائه) · شحن (+1 طاقة كل دور) · حراسة (-1 ضرر) · اختراق (الضرر الزائد يصيب اللاعب) · امتصاص (يشفي نصف الضرر) · رابط (يُدمج مع أي عنصر) · استطلاع (اسحب عند الاستدعاء) · سُم (1 ضرر للمهاجم).README 전문 보기 ↗
لعبة كروت استراتيجية 1 ضد 1 ضد خصم آلي: سطح مشترك من 272 كارت، مطابقة عناصر وأرقام على طريقة الأونو، نظام طاقة متصاعد، فخاخ تنطلق تلقائياً، هجمات مشتركة، ووحش أعظم يحسم المباراة فوراً.
مبنية على Next.js 16 (App Router) + Tailwind v4، جاهزة للنشر على Vercel، مع Supabase اختياري لحفظ سجل المباريات.
| النوع | العدد | التفاصيل |
|---|---|---|
| الوحوش | 120 | 6 عناصر × 5 فصائل × مرحلتين (أساسي/متطوّر) × نسختين |
| كروت الحركة | 36 | تخطي الدور (12)، اسحب كرتين (12)، انعكاس (4)، تغيير العنصر (4)، وحش بري + اسحب 4 (4) |
| الفخاخ | 54 | 27 تصميماً × نسختين |
| السحر | 54 | 27 تصميماً × نسختين |
| قطع الوحش الأعظم | 8 | قلب، ناب، درع، تاج × نسختين |
العناصر الستة: 🔥 نار · 💧 ماء · 🌿 عشب · ⚡ كهرباء · 🔮 نفسي · 🌑 ظلام.
خصائص الوحوش: اندفاع (يهاجم فور استدعائه) · شحن (+1 طاقة كل دور) · حراسة (-1 ضرر) · اختراق (الضرر الزائد يصيب اللاعب) · امتصاص (يشفي نصف الضرر) · رابط (يُدمج مع أي عنصر) · استطلاع (اسحب عند الاستدعاء) · سُم (1 ضرر للمهاجم).
اجمع القطع الأربع (قلب · ناب · درع · تاج) وادفع 6 طاقة → فوز فوري. الفخ المضاد «تحطيم الأثر» يدمّر إحدى قطع الخصم ويعيدها إلى دورة السطح.
npm install
npm run dev # http://localhost:3000
| الأمر | الوظيفة |
|---|---|
npm run dev | خادم التطوير |
npm run build | بناء الإنتاج |
npm run typecheck | فحص الأنواع |
npm run lint | فحص ESLint |
npm run simulate -- 300 | محاكاة 300 مباراة آلي-ضد-آلي للتحقق من المحرّك |
npm run check:tutorial | التأكد أن كل خطوة في التعليم قابلة للإنجاز فعلاً |
npm run balance -- 300 | قياس نسبة فوز اللاعب في كل مستوى صعوبة |
npm run check:order -- 600 | قياس ميزة البدء أولاً (المقبول ±3 نقاط) |
npm run check:coin | التأكد أن قرعة البداية متكافئة |
npm run check:redact | التأكد أن الخصم لا يستقبل أي معلومة سرّية في اللعب الجماعي |
npm run check:hand | التأكد أن ترتيب اليد يضع القابل للعب أولاً ولا يتبدّل بتغيّر الدور |
npm run check:messages | التأكد أن كل رسالة تُعرض في اللغتين بلا انهيار ولا معامل ناقص |
npm run check:effects | التأكد أن كل تأثير فخّ وسحر يُحدث أثراً فعلياً ويسجّل سطره |
npm run check:clock | التأكد أن عدّاد الجولة لا يُصفَّر إلا بانتقال الدور إلى لاعب آخر |
npm run check:auth | التأكد أن تطبيع اسم المستخدم والبريد المشتقّ لم يتغيّرا |
npm run check:level | التأكد أن صيغة المستوى في TS تطابق نظيرتها في SQL |
npm run check:stats | التأكد أن حصيلة المباراة تُحصي اللعب وحده ولا تفقد شيئاً عند قصّ السجل |
npm run check:social | التأكد أن معرّف المباراة حتميّ وأن الإحصاءات تُنقّى مقابل الكتالوج |
simulate يتحقق من ثلاثة ثوابت: مجموع الكروت يبقى ثابتاً بلا تكرار، لا مباراة تتعثّر بلا نهاية،
وتوزيع الانتصارات معقول. اجعله جزءاً من أي تعديل على القواعد.
check:tutorial يمشي في خطوات الدرس وينفّذ الحركة المطلوبة في كل خطوة، ويفشل إذا طلب الدرس
شيئاً ترفضه القواعد (مثل لعب كارت لا تكفيه الطاقة) أو إذا كان شرط الإنجاز لا يتحقّق أبداً.
شغّله بعد أي تعديل على تكاليف الكروت أو على نصوص التعليم.
lib/audio/sfx.ts يولّد كل المؤثّرات بـWeb Audio — بلا ملفّات صوت: صفر بايت في الحزمة،
ولا تراخيص، ولا انتظار تحميل قبل أول صوت (مهمّ في تطبيق يعمل دون إنترنت).
الأصوات تُشتقّ من سجل المباراة (lib/audio/logSfx.ts)، فيصدر الصوت المناسب لكل حدث —
بما في ذلك ما يقع في دور الخصم — دون نثر نداءات صوت داخل المحرّك. وحين تقع أحداث كثيرة
في لحظة واحدة يُكتفى بأهمّ مؤثّرين حتى لا تتحوّل إلى ضجيج. زر 🔊 يكتم ويُحفَظ الإعداد.
/guide — سبعة أقسام تشرح ما لا تشرحه القواعد: ما هو طابور التدفق ولماذا يغيّره كل كارت
تلعبه، لماذا العناصر «مفاتيح» لا أفضلية ضرر، خطوات دمج البطاقات بالتفصيل، والفخاخ وكروت
السحب والوحش الأعظم. لكل قسم سرّ لا يُذكر في الشرح العادي. المحتوى في
lib/i18n/guide.ts باللغتين.
عربي وإنجليزي مع مبدّل في كل شاشة (components/LanguageSwitch.tsx). الاختيار يُحفظ في
localStorage ويُقرأ عبر useSyncExternalStore (lib/i18n/store.ts) — فلا يحدث تعارض ترطيب
بين الخادم والعميل، ولا setState داخل مؤثّر جانبي. تغيير اللغة يقلب lang وdir فوراً،
ويُترجم حتى المباراة الجارية.
كيف تحقّق ذلك: النصوص ثنائية اللغة تُخزَّن مع البيانات نفسها (Localized = { ar, en }) في
cards.ts وdifficulty.ts وtutorial.ts، لا في قاموس منفصل يتخلّف عنها. وسجل المباراة
لا يخزّن نصّاً جاهزاً بل مفتاحاً ومعاملات (lib/i18n/messages.ts)، فيُترجَم وقت العرض.
أسماء اللاعبين المدمجة تُخزَّن كمفاتيح (@you / @ai / @coach) بينما تبقى الأسماء التي
يكتبها اللاعب كما هي.
ملاحظة صياغة: صيغ الملكية الإنجليزية ({player}'s turn) تنكسر حين يكون الاسم «You»،
فاستُبدلت بصيغ محايدة (Turn: {player}).
components/game/CardArt.tsx يولّد رسم كل كارت كـSVG من تعريف الكارت نفسه: الشكل يُشتقّ من
اسم الفصيلة (ستّة أنماط: وحش رباعي، أفعى، طائر، كرة، عملاق صخري، طيف)، والألوان من العنصر،
والمرحلة الثانية أضخم ومزيّنة بقرون وأجنحة وهالة. غير الوحوش لها رموز خاصّة لكل تأثير.
توليدها بدل ملفّات صور يعني: 134 تصميماً حادّاً في كل المقاسات، بلا بايت إضافي في الحزمة، وتتبع ألوان العنصر تلقائياً — وإضافة كارت جديد تعطيه رسماً فوراً.
npm run cards:export -- --png
يصدّر كل تصميم صورةً كاملة (إطار + رسم + الاسم + النص + الإحصاءات) إلى public/cards/
بصيغتَي SVG وPNG بمقاس 420×600. الرسم يأتي من نفس المكوّن المستعمل في اللعبة فلا تتفرّع نسختان.
manifest.webmanifest + public/sw.js. عامل الخدمة يخزّن الأصول والصفحات، فتعمل المباريات
ضد الخصم الآلي والتعليم واللعب على جهاز واحد دون إنترنت بعد أول فتح. نداءات /api لا تُخزَّن.
من المتصفّح: «إضافة إلى الشاشة الرئيسية».
غلاف Capacitor أصلي (capacitor.config.ts) يحمّل النسخة المنشورة، فيبقى التطبيق محدَّثاً دون
إعادة تثبيت، وتعمل فيه كل المزايا بما فيها اللعب الجماعي.
npm run icons # يولّد الأيقونات من الشعار
npx @capacitor/assets generate --android
npm run android:apk # يرفع الإصدار تلقائياً، يبني APK، وينسخها إلى public/download/
المصدر الوحيد: version في package.json. يظهر في أسفل كل صفحة، بجانب رابط APK، وفي إعدادات تطبيق أندرويد.
| الأمر | متى تستخدمه |
|---|---|
npm run version:bump | رفع patch (0.1.0 → 0.1.1) |
npm run version:release | رفع + مزامنة — قبل دفع تحديث الموقع |
npm run android:apk | يرفع الإصدار تلقائياً (SKIP_VERSION_BUMP=1 لتخطّي الرفع) |
الحزمة الحالية موقّعة بمفتاح debug — تُثبَّت وتُشارَك بعد تفعيل «مصادر غير معروفة».
لإصدار موقّع للنشر: أنشئ keystore، أضف signingConfigs في android/app/build.gradle،
ثم npm run android:apk -- release.
ملاحظة بناء على ويندوز: scripts/build-apk.mjs يجبر java.io.tmpdir على مسار قصير.
منتقي NIO ينشئ أنبوباً عبر مقبس AF_UNIX داخل مجلّد المؤقّت، وحدّ مسار المقبس ~108 بايت،
فإن كان مسار TEMP طويلاً فشل البناء برسالة مُضلّلة: «Unable to establish loopback connection».
ولهذا أيضاً عُطِّل org.gradle.jvmargs في android/gradle.properties ويُمرَّر من السكربت.
عند النقر على رابط https://mto-game.vercel.app/... من كروم أو الرسائل على أندرويد:
/vs/ABC).الإعداد:
| الملف | الغرض |
|---|---|
android/app/src/main/AndroidManifest.xml | intent-filter مع autoVerify لـ mto-game.vercel.app + مخطط mto-game:// احتياطي |
public/.well-known/assetlinks.json | بصمة SHA256 لمفتاح debug الحالي (com.mto.monsterclash) |
lib/deep-link.ts | بناء intent:// وفتح التطبيق من الويب |
components/DeepLinkHandler.tsx | توجيه WebView عند فتح رابط والتطبيق يعمل |
التحقق بعد النشر: يجب أن يكون assetlinks.json متاحاً على
https://mto-game.vercel.app/.well-known/assetlinks.json حتى يتحقق أندرويد من App Links تلقائياً.
# محاكاة نقرة على رابط (بعد تثبيت APK)
adb shell am start -a android.intent.action.VIEW -d "https://mto-game.vercel.app/vs/test"
# حالة التحقق من الروابط
adb shell pm get-app-links com.mto.monsterclash
بصمة debug الحالية (SHA256): 08:BA:62:C4:...:6B:47. عند إنشاء keystore إصدار، أضف بصمته إلى
assetlinks.json بجانب بصمة debug.
/localلاعبان يتناوبان على نفس الشاشة. بين كل دور وآخر تظهر ستارة تسليم تخفي اللوحة حتى يضغط اللاعب التالي، فلا يرى أحدهما يد الآخر. لا يحتاج إنترنت ولا أي إعداد.
/vsالمضيف ينشئ غرفة فيحصل على رمز من 5 خانات، والضيف يدخله. القناة هي
Supabase Realtime Broadcast باسم mto-room-<CODE> — بلا جداول ولا تخزين.
المضيف هو الحَكَم: متصفّحه وحده يشغّل المحرّك. يستقبل حركات الضيف، يتحقّق أنها في دوره
(current !== GUEST_SEAT تُرفض)، يطبّقها، ثم يبثّ الحالة.
ما يُبَثّ منقوص: redactFor(state, seat) تستبدل يد الخصم وترتيب السطح وهوية الفخاخ المقلوبة
بكارت وهمي (HIDDEN_CARD_ID) مع الحفاظ على الأعداد. يتحقّق npm run check:redact من ذلك على
40 مباراة كاملة وفي كل حالة وسيطة من منظور الطرفين، ويفحص أيضاً ألا تتسرّب المعرّفات (uid).
حدود معروفة:
SUPABASE_SERVICE_ROLE_KEY.lib/game/difficulty.ts — المستوى يضبط الخصم وحده، ولا يُمَسّ اللاعب:
| المستوى | حياة الخصم | سقف طاقته | نسبة خطئه | كروت التعطيل | الدمج | فوز اللاعب* |
|---|---|---|---|---|---|---|
| 🌱 سهل (الافتراضي) | 20 | 6 | 50% | 15% | لا | ~92% |
| ⚔️ متوسط | 26 | 8 | 22% | 55% | نعم | ~74% |
| 🔥 صعب | 30 | 10 | 0% | 100% | نعم | ~56% |
* مقاسة بـnpm run balance حيث يلعب «اللاعب» بأفضل سياسة ممكنة؛ اللاعب البشري سيفوز أقل من ذلك.
«كروت التعطيل» هي تخطي الدور واسحب كرتين — أكثر ما يُحبط اللاعب الجديد، فيندر استعمالها في السهل.
من يبدأ يُحدَّد بالقرعة في كل مباراة (firstPlayer: 'random' افتراضياً؛ التوزيع المُعدّ في
التعليم يبدأ دائماً باللاعب). تعويضاً لميزة البدء: من يلعب ثانياً يأخذ كارتاً وطاقة إضافية لمرة
واحدة، والبادئ لا يسحب في دوره الأول. النتيجة المقاسة: ميزة البدء +1.0 نقطة مئوية فقط.
المهلة ملك اللاعب لا ملك رقم الدور، وتُمثَّل في الحالة بحقلَي clockSeat وclockEpoch
(lib/game/types.ts). الحقبة تزيد في beginTurn فقط حين يبدأ لاعب مختلف دوراً يتصرّف فيه
فعلاً، فتُصفَّر المهلة عندئذ وحده:
| ما لعبته | رقم الدور | العدّاد |
|---|---|---|
| «تخطي» أو «انعكاس» في 1×1 | +2 (يعود إليك) | يستمر — لا مهلة مجانية |
| «تغيير العنصر» | لا يتغيّر | يستمر |
| «اسحب كرتين» أو «اسحب 4» | +1 إلى الخصم | يبدأ الخصم بمهلة كاملة |
| إنهاء الدور | +1 | مهلة كاملة للتالي |
جعل العدّاد جزءاً من حالة المحرّك بدل استنتاجه في الواجهة يعني أنه حتمي ويتزامن بين المضيف
والضيف مجاناً بدل حسابه مرّتين. الدور المُتخطَّى لا يبتلع حقبة لأن armClock لا يُنادى في فرع
skipNext — فاللاعب هناك لا يتصرّف أصلاً. يحرس ذلك npm run check:clock.
/tutorial درس تفاعلي من 12 خطوة على توزيع ثابت (lib/game/tutorial.ts): يشرح قراءة الكارت،
قاعدة المطابقة، الطاقة، الاستدعاء وخاصية «اندفاع»، الهجوم، الفخاخ، قطع الوحش الأعظم، والهجوم
المشترك. الخطوات الفعلية تتقدّم تلقائياً عند تنفيذ الحركة، ولكل خطوة زر «تخطَّ» حتى لا يعلق اللاعب.
وداخل أي مباراة، زر «؟ كيف ألعب» يفتح مرجعاً سريعاً.
/arcade)لعبة حركة 2D منفصلة عن نظام الكروت لكن بنفس ثيم MTO: عناصره الستّة، وأشكال وحوشه الستّة (وحش/أفعى/طائر/كرة/عملاق/طيف)، والوحش الأعظم، وقطعه الأربع. تُرسم كلّها على Canvas للأداء الحيّ. الشاشة نفسها هي الساحة، والأعداء يتوافدون من الحواف.
lib/audio/sfx.ts) — بلا ملفّات.localStorage. يعمل دون إنترنت ضمن الـPWA وغلاف Capacitor.المحرّك (lib/arcade/engine.ts) قائم بذاته على Canvas؛ الـHUD يُرسم داخل اللوحة، والقوائم
(بداية/ترقية/نهاية) طبقة React فوقها (components/arcade/ArcadeCanvas.tsx).
/arcade3d)النظير ثلاثي الأبعاد لوضع البقاء، بـThree.js (WebGL). نفس اللعب والثيم والترقيات والوحش الأعظم، لكن في ساحة مجسّمة: كاميرا تتبع البطل من أعلى-خلف، والأعداء يتوافدون من حلقة حوله.
lib/arcade، ويحافظ على نفس المقياس العددي للمحرّك 2D فتعمل كل القيم دون تعديل.lib/arcade3d/meshes.ts): الأشكال الستّة تُبنى من أشكال أوّلية (كرة/صندوق/مخروط/
حلقة) بألوان العناصر وخامات وهّاجة؛ الهندسات والخامات مشتركة، والقالب يُستنسخ رخيصاً لكل عدو.
الجسيمات على GPU عبر THREE.Points.MeshPhysicalMaterial، انعكاسات بيئية، وأرضية بخريطتَي خشونة ونتوءات)،
وخرائط نتوءات (normal maps) إجرائية على الوحوش، وضوء نقطي يتبع البطل بلون عنصره.EffectComposer + UnrealBloomPass + OutputPass
(من three/addons) يُبرز العناصر المضيئة؛ الرسم يمرّ بالمُركّب بدل renderer.render المباشر.onHud
في كل إطار — فتبقى حلقة اللعب سلسة.localStorage (mto-arcade3d-best). يتحقّق Three.js من دعم WebGL،
ويتراجع بأمان إن غاب.يضيف هذا الوضع تبعية three إلى package.json. المحرّك في lib/arcade3d/engine3d.ts
والواجهة في components/arcade3d/Game3D.tsx.
lib/game/
types.ts أنواع الحالة والحركات
rng.ts عشوائية حتمية (mulberry32) — نفس البذرة تعيد نفس المباراة
cards.ts كتالوج الـ272 كارت
engine.ts المحرّك: دوال خالصة، applyGameAction(state, action) → state جديد
ai.ts الخصم الآلي (سياسة جشعة: قطع → وحوش/سحر → هجوم → كروت تُنهي الدور)
components/game/
GameBoard.tsx شاشة المباراة
CardView.tsx عرض الكارت
MonsterView.tsx وحش على الساحة
CardsExplorer.tsx متصفّح الكتالوج
lib/arcade/ وضع البقاء (أكشن 2D)
engine.ts محرّك اللعب على Canvas (حلقة، أعداء، قذائف، خبرة، زعيم، جسيمات)
theme.ts ألوان العناصر وأشكال الوحوش وأنواع الأعداء والقطع
draw.ts رسم الوحوش الستّة والبطل والكرات على Canvas
upgrades.ts الترقيات العنصرية الاثنتا عشرة
strings.ts نصوص الوضع ثنائية اللغة (يشاركها الوضعان 2D و3D)
components/arcade/
ArcadeCanvas.tsx تركيب اللوحة + قوائم البداية/الترقية/النهاية + الجويستيك
lib/arcade3d/ وضع البقاء 3D (Three.js) — يعيد استخدام ثيم/ترقيات lib/arcade
engine3d.ts محرّك المشهد ثلاثي الأبعاد (كاميرا تتبّع، أعداء، قذائف، زعيم، جسيمات GPU)
meshes.ts بناء مُجسّمات الوحوش الستّة والبطل والقطع من أشكال أوّلية
components/arcade3d/
Game3D.tsx تركيب المُصيّر + HUD طبقة DOM + قوائم + الجويستيك
lib/auth/ الحسابات
username.ts تطبيع اسم المستخدم واشتقاق البريد الصوريّ (نهائي لا يتغيّر)
session.ts مخزن الجلسة خارج React + useSession
lib/social/ الطبقة الاجتماعية
types.ts Account وFriendEdge والرسائل والدعوات
api.ts أغلفة fetch لمسارات الأصدقاء والرسائل والدعوات
store.ts مخزن الأصدقاء والطلبات وغير المقروء
realtime.ts قناة واحدة لكل مستخدم (طلبات، قبول، دعوات، رسائل)
record.ts تنقية الإحصاءات واشتقاق معرّف المباراة المشترك
lib/game/stats.ts حصيلة الكروت والعناصر — تُشتقّ من السجل خارج المحرّك
app/
page.tsx /cards /play /leaderboard /arcade /arcade3d
/friends /account /vs /vs/[code]
/api/matches /api/matches/online /api/auth/register
/api/friends /api/messages /api/invites
middleware.ts يجدّد كوكي الجلسة على المسارات المحميّة وحدها
المحرّك خالٍ من React ومن أي واجهة، وكل حركة تُنتج حالة جديدة عبر structuredClone،
لذلك يمكن تشغيله على الخادم لاحقاً لدعم اللعب الجماعي دون تعديل.
الحساب مطلوب للّعب الجماعي وحده. اللعب ضد الخصم الآلي، والتعليم، واللعب على جهاز واحد،
ووضع البقاء تعمل كلّها بلا حساب ودون إنترنت كما كانت. البوّابة (components/auth/AuthGate.tsx)
مركَّبة في OnlineGame وRoomLobby وصفحات /vs — لا في GameBoard، لأن اللوح نفسه يخدم
/play و/local و/tutorial.
Supabase Auth يتطلّب بريداً، فيُشتقّ بريد صوريّ معمّى من اسم المستخدم
(lib/auth/username.ts): u<sha256("mto:v1:"+norm)…>@users.mto-game.vercel.app.
أ إ آ→ا وى→ي وة→ه ويزيل التشكيل، فمن كتب «أحمد» يجد «احمد».npm run check:auth ببريد مثبَّت.المستوى عمود مولَّد مخزَّن في profiles: دالة في عدد الانتصارات، والانتصارات لا تُكتب إلا عبر
record_match — فالانحراف مستحيل بنيوياً. الانتقال من المستوى N إلى N+1 يكلّف 3N انتصاراً.
نسخة TS في lib/player/level.ts لشريط التقدّم وحده، يحرس تطابقها npm run check:level.
«أكثر الكروت لعباً» و«أكثر العناصر» تُشتقّان من سجل المباراة خارج المحرّك (lib/game/stats.ts)
فيبقى مُختزِلاً خالصاً. التجميع تزايديّ على logSeq لا على طول السجل، لأن السجل حلقة تُقصّ عند
200 سطر فالقراءة الدفعية تفقد أوائل المباريات الطويلة. الفخاخ عدّاد منفصل لا تدخل «أكثر
الكروت»: هويّة الفخّ المقلوب لا تُسجَّل عمداً حتى لا يقرأها الخصم من السجل المبثوث.
الغرف بلا تخزين على الخادم كما كانت؛ صفّ game_invites هو نقطة الالتقاء العابرة (3 دقائق).
LobbyFriendsPanel يشتقّ وضعه من خصائصه:
| السياق | الزرّ | السلوك |
|---|---|---|
بلا رمز غرفة (/vs و/friends) | «ادعُ إلى لوبي جديد» | يولّد رمزاً، يُدرج الدعوة، ثم يدخل مضيفاً |
| مع رمز ومقعد شاغر | «ادعُ إلى هذا اللوبي» | يُدرج الدعوة بالرمز الحالي بلا تنقّل |
| بلا مقعد شاغر أو بعد بدء المباراة | معطّل مع سبب | — |
وهذا ما يجعل 1×1×1 قابلاً للتكوين من الأصدقاء: افتح غرفة ثلاثية، ادعُ الأول، وحين ينضمّ
ادعُ الثاني إلى الغرفة نفسها. التسابق على آخر مقعد لا يحتاج حجزاً: claimSeat يردّ الثاني
بـroomFull وتعرضها OnlineGame عبر فرع الخطأ القائم.
clientId يبقى هو مالك المقعد (sessionStorage، لكل تبويب). userId يُحمَل إلى جانبه
للعرض والنسبة فقط: معرّف ثابت لكل حساب كان سيجعل تبويباً ثانياً يسرق مقعد الأول.
اللعبة تعمل بالكامل بدونه؛ عند غيابه تظهر رسالة واضحة في صفحة السجل ولا تُحفظ النتائج،
ويشرح /vs أن اللعب الجماعي غير مهيّأ.
supabase/migrations/0001_init.sqlsupabase/migrations/0002_accounts.sql — بـsupabase db push أو من محرّر SQL في اللوحة،
لأن مُشغّل auth.users يحتاج صلاحية مرتفعة لا يملكها مفتاح REST..env.example)./api/auth/register ينادي
admin.createUser فيتخطّى ذلك المفتاح ويبقى تسجيلنا يعمل، بينما لا يستطيع أحد إنشاء حساب
بلا ملفّ شخصي عبر POST /auth/v1/signup بالمفتاح المجهول.الجدول matches يبقى كما كان: سجلّ عامّ مجهول الهوية، قراءة عامة وإضافة فقط.
جداول الحسابات (profiles, friendships, direct_messages, game_invites, match_records,
profile_card_stats) ممنوعة على المفتاح المجهول تماماً، والعدّادات لا تُكتب إلا عبر
record_match — سياسة update وحدها كانت ستتيح set wins = 9999، والمانع الفعلي هو
grant update (display_name) على مستوى العمود.
إصلاح مضمَّن: 0002 يضيف عمود difficulty الناقص إلى matches. المسار
app/api/matches/route.ts كان يُدرجه ويقرأه بينما 0001_init.sql لا ينشئه، فكان كل حفظ
نتيجة يفشل بـ500 على قاعدة نظيفة.
scripts/deploy.ps1 يجمع المراحل الثلاث في مكان واحد: GitHub ← Supabase ← Vercel.
الترتيب مقصود — الهجرة قبل النشر، وإلا وصل كودٌ يقرأ جداول لم تُنشأ بعد.
# اعرض ما سيحدث دون لمس شيء — ابدأ من هنا دائماً
.\scripts\deploy.ps1 -DryRun
# مرحلة واحدة
.\scripts\deploy.ps1 -Stage github -Message "إضافة الحسابات"
.\scripts\deploy.ps1 -Stage supabase
.\scripts\deploy.ps1 -Stage vercel
# الثلاث مراحل، ونشر إنتاجي
.\scripts\deploy.ps1 -Prod
| الخيار | المعنى |
|---|---|
-Stage | all (الافتراضي) · github · supabase · vercel |
-Prod | نشر إنتاجي؛ بدونه معاينة |
-Remote | ريموت git، الافتراضي mphpmaster |
-Message | رسالة الالتزام (تُطلب تفاعلياً إن غابت) |
-DryRun | اطبع كل خطوة دون تنفيذ |
-SkipChecks | تخطّي فحوص المشروع — لا تستعمله إلا وأنت تعرف لماذا |
-Yes | لا تسأل (لـCI فقط، ويلزمه -Message) |
ملاحظة: مخرجات السكربت بالإنجليزية عمداً — رسائل تشغيل تُقرأ بجانب مخرجات
gitوvercelوsupabase— بينما تعليقات الكود بالعربية كبقيّة المستودع.
مبادئ مقصودة:
-AsSecureString)، ولا تُطبع ولا تُكتب في ملف. و.env* مُستثنى من git عدا .env.example.typecheck وcheck:auth وcheck:level وcheck:social وcheck:clock
وcheck:effects وcheck:messages وcheck:redact وsimulate — وأي فشل يوقف النشر.npx فلا يُفرض على أحد تثبيت CLI عام ليدفع كوداً. اربط المشروع مرّة واحدة:
npx supabase link --project-ref <ref>.db pushهجرات هذا المستودع مرقّمة 0001/0002 لا بطوابع زمنية، بينما يتتبّع CLI النسخَ في جدول
supabase_migrations.schema_migrations على الخادم. فإن حمل الخادمُ نسخاً لا توجد محلياً — لأن
المشروع أُعِدّ من اللوحة أو من مستودع آخر — رفض db push العملَ كلَّها برسالة
«Remote migration versions not found in local migrations directory».
السكربت لا يعيد كتابة تاريخ الهجرات على قاعدة حيّة تلقائياً؛ يعرض الخيارات ويقف:
| الخيار | متى |
|---|---|
| أ) طبّق SQL يدوياً من محرّر SQL في اللوحة | الأبسط والموصى به |
ب) npx supabase db pull ثم db push | حين تريد مزامنة التاريخ فعلاً |
ج) supabase migration repair --status applied <versions> | حين تتيقّن أنها مُطبَّقة |
احذر اقتراح CLI نفسه: يقترح --status reverted، أي إعلان أن تلك الهجرات قد تراجعت — فإن
كانت مُطبَّقة فعلاً تركتَ التاريخ يكذب على قاعدتك.
0002_accounts.sql مكتوب ليُعاد تشغيله بأمان (if not exists وcreate or replace و
drop policy if exists)، ودالة level_from_wins محروسة بـdo $$ … if not exists لأن
create or replace عليها يفشل بعد إنشاء عمود profiles.level المولَّد الذي يعتمد عليها.
الإعدادات في vercel.ts. وإن فضّلت العمل يدوياً فالأوامر المكافئة:
git push mphpmaster <branch>
npx supabase db push
vercel deploy --prod
npm run lint يخرج بـ29 مشكلة، كلّها اعتراضات مُصرِّف React على أنماط
متعمَّدة عاملة. الرقم مقصود: هبوطه يعني إصلاحاً، وارتفاعه يعني انحداراً.
| العدد | القاعدة | الموضع | لماذا تُترك |
|---|---|---|---|
| 21 | Cannot access refs during render | lib/chat/useMatchChat.ts (13) · GameBoard.tsx:222 | ستّة منها في useMatchChat.ts:96-101 هي نمط «مرجع القيمة الأحدث» الذي يحتاجه خطّاف WebRTC ليقرأ آخر اسم/مقعد/كتم داخل مُعالجات لا تُعاد إنشاؤها؛ واثنان في :71-72 تهيئةُ مرجع كسولة |
| 7 | setState synchronously within an effect | GameBoard.tsx 244 · 289 · 339 · MatchChatDock:34 · QrScannerModal:37 | تزامنُ حالةِ عرضٍ مع حالة المحرّك بعد كل تغيّر دور |
| 1 | Compilation Skipped: memoization not preserved | GameBoard.tsx:267 | تنازلُ المُصرِّف عن التذكير، لا خطأ في الكود |
لماذا لا تُصلَح: كلّها تقع في GameBoard.tsx — الملفّ الذي يملك عدّاد
الدور — وفي خطّاف الصوت. إصلاحها إعادةُ هيكلةٍ بمخاطر ارتداد حقيقية على
أسخن مسارين في اللعبة، لا تنظيفٌ سطحي. من يُصلحها فليُثبّت
npm run check:clock وcheck:chat وcheck:online قبل وبعد.