# Bullet Reign · Roblox

`04 · roblox-game · Published`

لعبة bullet-heaven على Roblox مع custom MegaMesh renderer (300 عدو عند ≥55 FPS على mid-range mobile، hard cap 500) وart pipeline كامل عبر Blender + MCP agents — solo بلا artist.

**النطاق:** Solo · 6 أسابيع  
**الدور:** Performance engineering على Roblox

**الفيديو:** [YouTube](https://www.youtube.com/watch?v=ioNp3dIX8aI) · [RuTube](https://rutube.ru/video/private/7b61cb55503f104cf9b05bdcc14c53d9/?p=NjzBlpAMbq7jh2xCvn6_jQ)

## شرح الفيديو

Bullet-heaven على Roblox بثلاثمئة عدو على الشاشة وخمسة وخمسين FPS على جوال متوسط عبر MegaMesh renderer مخصص بخمسة عشر draw call مهما كان عدد الأعداء. ست نقاط اهتمام، ستة وعشرون سلاحا مع عشرين تطويرا وأربعة وأربعين passive، ستايلان فنيان، سبعة عشر نوع عدو، وتسعون أيقونة — كل ذلك بناء solo عبر render وnetwork وAI وخط إنتاج المحتوى.

Bullet-heaven على Roblox: ثلاث مية عدو على الشاشة، وخمسة وخمسين FPS على جوال متوسط.

كل عدو هو Lua table على السيرفر، وbone داخل MegaMesh مشترك على الكلاينت. خمسة عشر draw call مهما كان عدد الأعداء على الجودة المنخفضة.

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

اضغط أي عنصر في الكتالوج: الإحصائيات كاملة، وكل مسار evolution أو fusion ممكن يدخل فيه.

ستايلين فنيين: Classic وBrainrot. سبعة عشر نوع عدو، وتسعين أيقونة.

Bullet Reign — متوفرة الآن على Roblox.

مهندس واحد · رندر مخصص، شبكة، AI، وخط إنتاج المحتوى.

---

## السياق

> النوع يحتاج مئات الأعداء على الشاشة. Defaults في Roblox تعطيك عشرات.

Bullet-heaven كنوع يحتاج 300+ عدو نشط على الشاشة مع framerate ثابت. على Roblox الجمهور ضخم لكنه mobile-first، وdefaults في المحرك تنهار على mobile عند عشرات أو مئة أعداء. مسار rendering الافتراضي هو البوابة، لا simulation.

Solo يعني لا artist لـ 15 enemy slots × 2 styles × 3 LODs، ولا animator لعشرة skeleton types، ولا engine specialist منفصل. حتى يعمل النوع على هذه المنصة يجب تخصيص render وnetwork وAI وcontent pipeline وتشغيلها من شخص واحد.

## حقائق

| | |
|---|---|
| **النطاق** | 6 أسابيع solo |
| **النوع** | Bullet-heaven · run 30 دقيقة · octagonal arena R=400 studs |
| **Render** | draw call واحد لكل enemy type · hard cap 500 · ≥55 FPS @ 300 على mid-range mobile |
| **Network** | 7 bytes/enemy · 20 Hz tick · ~70 KB/s @ 500 |
| **Content** | 17 enemies · 6 bosses · 26 weapons · 44 passive items · 13 locales |
| **الحالة** | منشورة على Roblox |

## المعمارية

### Render pipeline

```text
 1  Server tick (20 Hz · authoritative)
        │  500 enemies = 500 Lua tables (no Instances)
        │  EnemyManager.update():  pos · hp · ai-state in place
        │  ~120 B / table · zero engine alloc per mob
        ▼
 2  NetworkProtocol.packEnemyBatch()
        │  reusable _syncBatch upvalue (zero alloc)
        │  per enemy:  u16 id · i16 x×10 · i16 z×10 · u8 hp%   = 7 B
        ▼
 3  UnreliableRemoteEvent  ──►  client      ~3.5 KB @ 500
        │                                   ~70 KB/s @ 20 Hz
 4  NetworkProtocol.unpackEnemyBatch(buf, fn)
        │  callback-style reader  (zero alloc, hot path)
        ▼
 5  BoneRenderer v5
        │  one MeshPart per enemy type   →   ≤ 15 draw calls
        │  multi-size sub-pools via  Ex<size>_<slot>_BoneName
        │  lazy · auto-expand · schedule-preload T-10 s
        ▼
 6  bone.Transform = restCFInv * worldCF      (~200–435 active bones / frame)
```

**ميزانية 20K مثل تخطيط capacity.** `MAX_TRIS_BUDGET = 20000` في `export_megamesh.py` يحدد سقف المثلثات لكل pool؛ `count = budget // tris` يحدد عدد النسخ داخل MeshPart واحد. عدو 400 مثلث يعطي 50 نسخة؛ boss-preview بحجم 1000 مثلث يعطي 20. parameter وقت البناء يحدد مباشرة سقف runtime للأعداء المتزامنين.

**بروتوكول أسماء العظام.** `export_megamesh.py` يكتب أسماء العظام لكل نسخة بصيغة `E{NNN}_<bone>`، و`BoneRenderer.luau` يستخرج الاسم canonical عبر `^E%d+_(.+)$`. بروتوكول واحد يخدم كل art direction؛ runtime بلا شروط حسب style.

**Sub-pools متعددة الأحجام.** MeshPart واحد يحمل sub-pools x1 / x1.5 / x2 جنبا إلى جنب؛ merge لـ XP gem يعيد الربط إلى slot أكبر في نفس pool — بلا Instance churn وبلا draw call إضافي.

**Schedule-preload.** Surges في `SpawnDefinitions` تحمل إنذارا معروفا قبل 10 ثوان. pool المطابق يتكون قبل spawn بعشر ثوان — أول موجة لا تتوقف على lazy-init وFPS يبقى مستقرا عبر الانتقالات.

### Wire format

```text
Wire format · UnreliableRemoteEvent · 20 Hz · server-authoritative

  HEADER     u16  enemyCount                              2 B
  ENTRY × N  u16  id              0 – 65 535              2
             i16  x × 10          ±3 276.8 studs          2
             i16  z × 10          ±3 276.8 studs          2
             u8   hp %            0 – 100                 1
                                                         = 7 B / enemy

  packet @ 500 enemies   = 2 + 500 × 7    = 3 502 B
  bandwidth @ 20 Hz      = 3 502 × 20    ≈ 70 KB / s

  hit batching:  26 weapons → 1 HitRequest / 100 ms
                 server: damage clamp [0, 2000] · 15 req/s/player
```

**لماذا fixed-point ×10.** المواضع تغطي ±3,276.8 studs بدقة 0.1 stud. نصف قطر الساحة 400 — المجال يغطيها 8×، و0.1 stud أدق من snap المرئي على mobile.

**لماذا u8 hp%.** السيرفر يحتفظ بالـ HP الحقيقي لحساب الضرر؛ client يرسم bar فقط. دقة 1% دون عتبة الرؤية في الشريط وتوفر 3 bytes لكل عدو مقارنة بقيمة u32 مطلقة.

**×4–5 مقارنة بمزامنة Lua tables.** نفس payload كـ `{id, x, y, z, hp}` table يقارب 16 KB لكل tick — اتصال mobile cellular يسقط أثناء run. Binary بحجم 3.5 KB يبقى مريحا حتى على link متدهور.

### Server-tick topology

```text
SERVER (20 Hz · authoritative · enemies as Lua tables)

  GameManager (Lobby → Countdown → Run → End)
       │
       ├── SpawnManager    base 70% · surge 20% · event 10%
       │                   8 surge types · 7 separate spawners
       │                   soft-cap 350→500 · adaptive ±20%
       │
       ├── EnemyManager    5 modules · 17 types · 11 AI behaviors
       ├── BossController  6 bosses · Harvester m28 arena shrink
       ├── POIManager      shrine · chest · crystal · obelisk · …
       ├── DataManager     DataStore + schema migration · save 60 s
       └── HitValidator    damage clamp · 15 req/s / player
                                       │
                                       ▼   7 B / enemy @ 20 Hz
CLIENT (every frame)

  EnemyRenderer  →  BoneRenderer v5     →  bone.Transform   (15 draw calls)
  WeaponManager  →  SpatialGrid cell=20 →  26 weapons       (O(K) per query)
  ~25 UI modules · CardRenderer (LevelUp / Chest / POI rewards)
```

**سجل الخدمات: Services.luau لا _G.** كل reference عابر للmodules يمر عبر ModuleScript registry واحد. موجة R9 نقلت 35 key و402 reference عبر 65 ملفا — صفر `_G` متبق. `_G` wiring هو antipattern Roblox المعتاد؛ refactor بلا features هو بالضبط العمل الذي تتخطاه معظم المشاريع.

**Single-slot مقابل multi-slot pool.** Bosses يحصلون على pool مخصص single-slot فوق preview mesh بحجم ×2.5–3 حتى يرتبط `Highlight` الخاص بالshield/invuln VFX بشكل نظيف؛ multi-slot كان سيربط highlight بكل عدو يشارك MeshPart.

**Spawner بثلاث قنوات.** Base rate يزيد خطيا مع الدقيقة، bosses الستة على offsets ثابتة، و8 surge types مع 7 spawners منفصلة تجعل الجدول predictable بما يكفي للpreload ومتغيرا بما يكفي لاختلاف runs.

### مخرجات AI art pipeline

![The icon library — weapon and perk silhouettes processed through `cut_icons.py` (batch-sheet slicing) and `whiten_icons.py` (RGB(0,0,0) → RGB(255,255,255) for Roblox `ImageColor3` tinting).](https://ilyadev.xyz/private/roblox-icons.png)

*Icon library*

![Roblox Studio scene with enemy meshes, collectible props, and three LOD variants (low / medium / high) for select assets — all baked through `export_megamesh.py` with the `E{NNN}_<bone>` bone-naming protocol.](https://ilyadev.xyz/private/roblox-bestiary.png)

*Bestiary + collectibles · 3 LODs*

**الحجم داخل الإطار.** 60 ملف .blend، و220 export إلى .fbx، و16 Python script في bake pipeline. bestiary يغطي 15 enemy types مع LODs مختارة، وicon library يغطي 26 weapon + perks — slicing من batch-sheets ثم recolor إلى أبيض حتى يستطيع Roblox `ImageColor3` تلوينها runtime.

**نمطا رسم، pipeline واحد.** Classic وalt-style يدخلان نفس bake pipeline — نفس `export_megamesh.py`، ونفس `cut_icons.py`، ونفس naming `E{NNN}_<bone>`، ونفس FBX preset. كل enemy slot يحمل skeleton خاصا به؛ split لـ AnimData هو الثمن.

## قرارات هندسية رئيسية

### 01 · Bone-Transform MegaMesh بدلا من Roblox Models

**القرار.** MeshPart واحد لكل enemy type مع N skinned bones؛ كل frame نضبط bone.Transform لكل عدو نشط. 15 enemy types = 15 draw calls بغض النظر عن العدد.

**لماذا.** النوع يحتاج مئات الأعداء على mobile. Model+Humanoid+AnimationTrack لكل عدو ينهار قبل ذلك بكثير؛ renderer هو البوابة.

**الكلفة.** لا AnimationTrack ولا Humanoid ولا auto-replication — كل ذلك أعيد بناؤه: BoneRenderer وkeyframe sampler وAnimData.

### 02 · الأعداء على السيرفر كLua tables لا Instances

**القرار.** الأعداء server-side هم dict entries فقط: pos/hp/ai-state/effects. لا Instance.new في hot path.

**لماذا.** 500 Instance.new لكل موجة ثم عند الموت قاتل للـ GC والreplication. tables تبقى memory عادية وتبقي tick ضيقا.

**الكلفة.** أي Roblox feature يحتاج Instance يختفي للأعداء العاديين؛ hit detection وVFX أعيد بناؤهما.

### 03 · Buffer-packed binary على UnreliableRemoteEvent

**القرار.** 7-byte fixed-form لكل عدو عبر UnreliableRemoteEvent مع zero allocations.

**لماذا.** 20 Hz × 500 enemies كtables ≈16 KB/tick؛ binary ≈3.5 KB. فقدان tick واحد طبيعي في snapshots.

**الكلفة.** لا schema ولا inspector؛ كل تغيير wire format يتطلب تنسيق sender/reader يدويا.

### 04 · AI-driven art pipeline عبر Blender + MCP agents

**القرار.** Concept → img-to-3D → Blender via MCP agent → bake MegaMesh → LODs → FBX → upload. 16 Python scripts تغطي pipeline.

**لماذا.** ≈90 mesh variations + icon library خلال 6 أسابيع solo لا تناسب hand-authoring.

**الكلفة.** 16 scripts للصيانة وAI topology يحتاج post-pass calibration.

### 05 · Art style في source لا code

**القرار.** مجموعتا .blend (classic + alt-style) تغذيان نفس bake pipeline؛ runtime يقرأ pools عبر toggle واحد.

**لماذا.** branching حسب style في runtime يعني مسارين لكل code path؛ branching في bake-time يعني صفر paths جديدة.

**الكلفة.** بعض alt-style meshes تختلف bone names؛ 11 من 15 تحتاج AnimData خاصة.

## التقنيات

| | |
|---|---|
| **Language** | Luau (typed) · Rojo 7.6.1 · Rokit toolchain |
| **Render** | Custom Bone-Transform MegaMesh · BoneRenderer v5 · BoneAnimator (own keyframe format) |
| **Network** | UnreliableRemoteEvent · 7-byte packed protocol · 20 Hz tick · custom SpatialGrid (cell 20 studs) |
| **Persist** | Roblox DataStore + schema migration · auto-save 60 s · onLeave · onShutdown |
| **Art pipe** | Blender + MCP agents · 16 Python scripts · 60 .blend · 220 .fbx · weapon + perk icon library |
| **Scale** | ~100 K Luau LOC across ~370 modules — ~100 locale (13 × 8 namespaces), ~90 definition (26 weapons + 17 enemies + 44 passives), 5-module EnemyManager core (≈3 K LOC of hot-path) · 2 Roblox places · ~58 RemoteEvents · 10 R-wave refactors |

## الدروس والحالة

### ما سأحمله للأمام

- Bone-Transform MegaMesh دفع قيمته 100× — pattern day-one لأي Roblox project كبير.
- Server-as-data جعل memory وreplication predictable خلال كل optimization phases.
- Buffer-packed binary مع reusable _syncBatch — zero-allocation discipline بقي من prototype إلى publish.
- AI-driven art pipeline — بحلول الأسبوع 4 أصبح prompt → 3D → bake → upload عادة عمل؛ بدونه ≈90 mesh variations غير واقعية solo.
- Bootstrap handshake ClientReady أغلق سباقات listener-registration التي تجعل lazy ModuleScripts خطرة.

### ما سأغيره

- Modular decomposition R1–R10 كان يجب أن يبدأ أبكر؛ EnemyManager كان يقترب من monolith قبل موجة refactor.
- AI-generated meshes تحتاج pre-bake gate أشد. اليوم سأشغل fix_normals_and_colors.py داخل export_megamesh.py دائما وأفشل export عند vertex colors سيئة.
- AnimData split اكتشف reactively. spec مبكر لـ bone-name parity كان سيضع العقد مرة واحدة.

---

المصدر: https://ilyadev.xyz/cases/roblox-game (HTML) · /cases/roblox-game.ar.md (هذا الملف)
السابق: 03 — AI Video Editor · محرر فيديو → https://ilyadev.xyz/cases/ai-video-editor.ar.md
التالي: 05 — macOS VPN · توجيه لكل تطبيق → https://ilyadev.xyz/cases/macos-vpn.ar.md
الفهرس: https://ilyadev.xyz/llms-ar.txt — قائمة دراسات الحالة الكاملة
المؤلف: إليا كازانتسيف — https://ilyadev.xyz/index.ar.md
