05-ke-dich-wave.md · Cập nhật lần cuối: 2026-09-05 08:39 +07 · sha256 nguồn 2ae8db11ed1f05 — Kẻ địch & wave (một Enemy chung, hành vi riêng ở component anh em)
Nguồn framework:
docs/framework/05-characters-pets-enemies.md§2.3, §3.4–3.6, §5;docs/framework/02-combat-projectiles.md§3.1/§3.6;docs/framework/06-juice-game-feel.md§3.2/§3.4;[liveops catalog: SCREEN_CATALOG.md]§6. Cấp bằng chứng: L1 ANALYSIS — dựng lại clean-room từ disasm + metadata + quan sát runtime; không phải source gốc. Mỗi dòng bên dưới mang nhãn bằng chứng riêng.
Mục tiêu
Muốn màn hình có hàng nghìn mục tiêu (cây, quặng, quái, boss) mà vẫn 60 fps, và muốn thêm một loại
mục tiêu mới không phải sửa code đạn — thì tách "phần chung" (máu, tiền, thanh máu, nảy) vào một
component Enemy, và để mỗi loại có component anh em quyết định chết thì làm gì. Trang này liệt kê
6 loại kẻ địch của CatGunner, số lượng thật trong scene, và cách wave/stage vận hành.
Game gốc làm gì
| Điều game làm | Con số / hằng số | Mức bằng chứng | Nguồn |
|---|---|---|---|
Enum Enemy.Type có 6 giá trị |
Tree=0, Tree_SpeedRun=1, BossMonster=2, Monster=3, Ore=4, RaidBoss=5 |
disasm-proven | doc 05 §2.3, Enemy.cs |
Enemy là component chung, hành vi riêng ở component anh em, dispatch qua Die_Reload() theo _type |
— | disasm-proven | doc 05 §2.3, Enemy.Die_Reload RVA 0x2AFF354 |
| Số instance trong scene | Enemy 1 933 · EnemyColl 1 933 · Enemy_Slider 1 931 · SpriteFill 1 931 · Ore 1 000 · Tree 501 · Monster 240 · Tree_SpeedRun 190 |
scene-census | doc 05 §2.3, scene_census.json |
| Toàn bộ kẻ địch đã tồn tại sẵn trong scene (không Instantiate lúc chạy) | Enemy..ctor 1 933×, Ore..ctor 1 000×, Tree..ctor 501× ngay lúc load |
scene-census + runtime-observed | doc 05 §2.3 |
Enemy giữ phần chung |
Health_Now, Health_Max, Money, is_Die, Slider (thanh máu), _bounce |
disasm-proven | doc 05 §3.4 |
EnemyColl.enemyScript là liên kết ngược để collider con tìm Enemy cha không cần GetComponentInParent |
3 data-holder chỉ có .ctor: EnemyColl, Enemy_Slider, Gun |
disasm-proven | doc 05 §3.4 |
Thanh máu quái = kênh đỏ của SpriteRenderer.color |
_sprite.color = new Color(value, 1, 1, 1); Value_Reload 202× |
disasm-proven + runtime-verified | doc 06 §2.3, RVA 0x2A52BF4 |
Boss không dùng Enemy_Slider: Slider == null && _type == BossMonster ⇒ Boss_manager.instance.gameSlider.Value_Reload(Health_Now, Health_Max) |
Boss_Slider có 2 thanh (máu + thời gian) |
disasm-proven + metadata-proven | doc 06 §2.3, RVA 0x2AFF108, dump.cs:593885 |
| Ore = mục tiêu đếm nhát đánh | máu 3 (skin 0) hoặc 5 (skin ≠ 0); mỗi nhát trừ đúng (InfVal)1 |
disasm-proven | doc 05 §2.3, Ore.Spawn 0x2AF70B8; Damage_Tag 0x2A503F0 |
| Ore rơi dia bằng literal cứng | "+0.1" (khi _diaValue == 1) / "+1" @0x5E81500 / 0x5E81508 |
disasm-proven | doc 06 §2.4, Ore.Die_Reload 0x2AF76E4 |
Chỉ Type.Monster nhận knockback |
damageRatio = (float)(Damage / Health_Max) → Monster.Apply_Knockback(damageRatio) |
disasm-proven | doc 05 §2.3, Enemy.Damaged 0x2AFEF9C |
Monster hằng số |
NormalSpeed = 1.2, BigSpeed = 0.9, BarrierRadius = 20, ReferenceSpawnDist = 65, MapCenter static |
metadata-proven | doc 05 §2.3, dump.cs:594503 |
Monster animation trên GPU bằng MaterialPropertyBlock, không Animator |
3 property id: ID_TimeOffset, ID_AnimSpeed, ID_ScaleAmount |
metadata-proven | doc 05 §2.3/§3.5, dump.cs:594503 |
Monster.Spawn(InfVal Health, Vector3 Pos, Sprite SkinSprite, bool is_Big) + Size_Big / Size_Small |
— | metadata-proven | doc 05 §2.3 |
Boss đi lang thang bằng coroutine và né đường đi của mèo |
WanderRoutine, MoveToPoint, PickWanderPoint, RandomPointInMap, PathCrossesCat(origin, target, catPos, radius); MapRadius = 60; catAvoidRadius + maxPointRetries |
metadata-proven | doc 05 §2.3, dump.cs:593531 |
Boss và Raid_Boss có Animator; Monster/Pet thì không |
— | metadata-proven (negative) | doc 05 §5 |
Raid_Boss tối giản |
chỉ Spawn(Health, SkinNum), Reload(Health), Die_Reload(), Game_End() |
metadata-proven | doc 05 §2.3, dump.cs:595047 |
| Raid là nội dung chưa phát hành | root Raid_manager m_IsActive: 0; Raid_UI 690 object; không có Raid_Btn, không có trong Contents_Lock_Reload; Raid_manager: BossCount = 5, GameTime_Max = 30, BossReward_Value = 10 |
VERIFIED (static, negative evidence) | SCREEN_CATALOG §6, G153 |
Trạng thái wave nằm ở Tree_manager |
TreeCount_Now @0x90, TreeCount_Max @0x94, GameTime_Now @0x98, GameTime_Max @0x9C |
metadata-proven | doc 05 §2.3, dump.cs:602135 |
Cây đổ giảm TreeCount_Now, cập nhật slider theo (Max - Now) / Max, Now <= 0 ⇒ Game_Win() — logic này nằm trong Tree_manager.Tree_Destroyed; Tree.Die_Reload thật chỉ 2 dòng (spawn hạt + gọi Tree_Destroyed). Hai hàm từng bị công cụ decompile gộp làm một |
Tree.Die_Reload 85× / MissionValue_Increase 86× |
disasm-proven (sau khi tách biên) + runtime-observed | doc 05 §2.3/§3.6 (G346) |
Game_Win: StageAdvance; relic Skip = Relic.Skip_Value % cơ hội tiến thêm 1 level, chỉ khi Level + 10·Stage < Stage_Value_Max (không vượt mốc cao nhất từng đạt); lập kỷ lục ⇒ Stage_Value_Max, SubmitScore [R1], Contents_Lock_Reload; _stageTimeAll cộng thời gian thực mỗi màn |
— | disasm-proven | doc 05 §3.6 (G347) |
Đặt cây theo đường cong Morton (Z-order), không xáo trộn ngẫu nhiên: Tree_Grid.Spawn sắp 100 ô theo Morton(i % 10, i / 10) rồi lấy TreeCount ô cách đều trên thứ tự đó ⇒ cây phủ đều lưới, tất định; ngẫu nhiên duy nhất là jitter ±randomRange từng cây. Lưới 10 cột, x bước 5, mỗi hàng x −50, z +5 |
10 × 10 ô | disasm-proven | doc 05 §2.3/§3.6 (G343) |
Ore.Spawn(int skinNum): tham số dùng như boolean — khác 0 ⇒ 5 HP / 10 dia, bằng 0 ⇒ 3 HP / 1 dia; Ore không trả Money qua Enemy (đối số 0), thưởng là _diaValue cấp khi chết |
3/1 · 5/10 | disasm-proven | doc 05 §2.3, doc 02 §3.6 (G343) |
Tỉ lệ bốc bậc cây Tree_Grid.degreeRates float[5,4] — hàng = level 0..4, cột = bậc 0..3: {80,20,0,0} {50,45,5,0} {20,55,25,0} {5,35,60,0} {0,15,85,0}; cột bậc 3 luôn 0 ⇒ cây bậc cao nhất chỉ là Boss_tree |
20 số, mỗi hàng 100 % | metadata-proven (blob SHA-256) + disasm-proven (consumer) | doc 11 §5.2 (G353) |
Monster_manager: MonsterCountPerLevel = [90, 120, 160, 200, 240], SpawnIntervalPerLevel = [0.35, 0.2, 0.17, 0.12, 0.08] s |
5 + 5 | metadata-proven (blob SHA-256) | doc 05 §2.3 (G353); chỉ số hàng chưa đọc |
| Bảng máu/tiền cây theo stage — hàm sinh: seed 250 (máu) / 20 (tiền), mỗi stage ×3 rồi cắt về 2 chữ số có nghĩa; mảng scene khớp (250, 750, 2200, 6600, 19000, 57000, 170000…; 20, 60, 180, 540, 1600, 4800, 14000…) | ×3, 2 chữ số | disasm-proven + scene-YAML | doc 05 §2.3 (G395) |
Chế độ Play (SpeedRun) dùng SpeeRun_Stage_Power_return = GetStageHealth(stage) × 5 (lỗi chính tả là của game gốc) |
×5 | disasm-proven | G395 |
Tree_manager ctor: GameTime_Max = 180 s, CatSpawnPos = (−100, 0, 0), hai cache Dictionary<int, InfVal> — khởi tạo field bị công cụ decompile bỏ rơi; đo trên thiết bị: thanh thời gian chạy đúng 180.0 s (sai 0.06 %), đếm xuống |
180 s | disasm-proven + build-measured | doc 05 §2.3 (G306, G351) |
Camera orthographic size 40, isometric yaw −45° / pitch 35.26°, Camera_manager.Follow → Cat; nửa bề rộng nhìn thấy trên 1080×2400 = 40 × 0.45 = 18 đơn vị |
18 u | disasm-proven + derived | doc 05 §2.4 (G354) |
Vì sao cây "không hiện": khối lưới tâm (−100, 0, 50.5) trong khi mèo spawn (−100, 0, 0) ⇒ lúc spawn chỉ 5/30 cây trong khung; mèo tự đi tới cây (~1 s) và camera bám theo ⇒ 5 → 12 cây vào khung (đo trên thiết bị) |
5 → 12 | build-measured | doc 05 §2.4 (G354, G358) |
| Vòng chơi đo được: round bắt đầu 8 s sau launch, kéo dài 180 s, hết giờ ⇒ tự bắt đầu lại và re-roll bố cục (chọn ô Morton + jitter + bốc bậc mới) | 8 s / 180 s | build-measured | doc 05 §2.4 (G358, G362) |
degreeRates: hàng = level wave 0..4, cột = bậc cây 0..3, bậc 3 không bao giờ bốc — dành cho cây boss; Degree_Random_return từng là return 0 (mọi cây bậc 0) |
chốt | disasm-proven | G354 |
| Máu/tiền theo stage tính trước 5 000 stage và cache | const int PrecomputedStageCount = 5000; Dictionary<int,InfVal> _healthCache / _moneyCache; static InfVal PowOfThree(int) |
metadata-proven | doc 05 §2.3, dump.cs:602135 (GetStageHealth 0x2B4111C, GetStageMoney 0x2B431B8) |
| Bảng stage thật nằm trong scene | Stage_Health_Balance @Main.unity:20614934, Stage_Money_Balance @:20624935; Tree_manager.Balance_Reload (0x2B447EC) 0 code-xref |
scene-YAML + VERIFIED (dead code) | doc 09 §3.2, LIVEOPS_SURFACE §4 |
| Hiệu ứng khi chết ở tầng hạt | cây thường 5 hạt, cây boss 15, quặng 5 hạt bụi (instance_dust) |
disasm-proven | doc 03 §2/§3.5, Tree.Die_Reload 0x2B43904, Ore.Die_Reload 0x2AF76E4 |
Công thức / pseudocode
// ---- L1 ANALYSIS (có RVA) ----
// Enemy.Die_Reload — RVA 0x2AFF354 : loại chung uỷ quyền vòng đời cho loại riêng
switch (_type) {
case Tree: GetComponent<Tree>().Die_Reload(); gameObject.SetActive(false); break;
case Tree_SpeedRun: GetComponent<Tree_SpeedRun>().Die_Reload(); gameObject.SetActive(false); break;
case BossMonster: GetComponent<Boss>().Die_Reload(); break; // KHÔNG tắt ở đây
case Monster: GetComponent<Monster>().Die_Reload(); break; // KHÔNG tắt
case Ore: GetComponent<Ore>().Die_Reload(); break; // KHÔNG tắt (Ore tự SetActive(false) ở cuối)
case RaidBoss: GetComponent<Raid_Boss>().Die_Reload(); break; // KHÔNG tắt
}
// Enemy.Damaged — RVA 0x2AFEF9C
if (is_Die) return; if (!Gamemanager.is_gaming) return; // G132b
Health_Now -= Damage;
if (_type == Type.Monster) GetComponent<Monster>()?.Apply_Knockback((float)(Damage / Health_Max)); // chia Health_MAX
Slider_Reload(); // ratio = Health_Now / Health_Max -> SpriteFill kênh đỏ
if (Health_Now <= 0) Die(); else _bounce.Bounce_Start();
// Enemy.Die -> Gamemanager.Get_Money(Money, transform.position); Die_Reload();
// Damage_Tag — RVA 0x2A503F0 : Ore là mục tiêu đếm nhát
if (tag == "Enemy") { e.Damaged(Damage); return true; }
if (tag == "Ore") { e.Damaged((InfVal)1); return true; }
// Tree.Die_Reload — RVA 0x2B43904 : chỉ HAI dòng (G346)
Particle_Polling.instance.Spawn(MoneySpawn_trans.position, is_Boss ? 15 : 5); // không null-guard: binary dereference thẳng
Tree_manager.instance.Tree_Destroyed();
// Tree_manager.Tree_Destroyed : vòng đời một wave (trước đây bị gộp nhầm vào Die_Reload)
if (!Gamemanager.is_gaming) return;
TreeCount_Now--;
gameSlider.Value_Reload((TreeCount_Max - TreeCount_Now) / TreeCount_Max);
// [R1] SendLog_WaveRatio CHỈ khi Stage_Now == 0 && Level_Now >= Stage_Value_Max, mốc 10 %, dedup "{stage}_{level}_{percent}"
if (TreeCount_Now <= 0) Game_Win(); // private, cùng class
Mission_manager.instance.MissionValue_Increase(Mission_Type.TreeCount);
// Tree_manager.Game_Win (G347)
if (!is_gaming) return; is_gaming = false;
_stageTimeAll += realtimeSinceStartup - _roundStartTime; gameSlider.Value_Reload(1f);
StageAdvance(ref Stage_Now, ref Level_Now);
if (Random.Range(0, 100) < Relic_manager.Skip_Value && Level_Now + 10*Stage_Now < Stage_Value_Max)
StageAdvance(ref Stage_Now, ref Level_Now); // relic Skip: chỉ trong vùng đã chinh phục
if (Level_Now + 10*Stage_Now >= Stage_Value_Max) {
Stage_Value_Max = Level_Now + 10*Stage_Now;
if (!is_ProgramDestroyed) Rank_manager.SubmitScore(false, null); // [R1]
Contents_Lock_Reload(); /* [R1] SendLog_WaveClear, SendLog_StageClear khi level cũ == 4 */
}
Sound_manager.PlaySfx(Win); MissionValue_Increase(StageClear); GameWin_UI on; Loading_Then_Start();
// Tree_Grid.Spawn (G343): đặt cây theo đường cong Morton, lấy mẫu cách đều
order = indices 0..99 sắp theo Morton(i % 10, i / 10) // Morton = Spread(x) | (Spread(z) << 1), Spread = đan bit
acc = 0; for k in 0..want-1 { acc += total; chọn order[acc / want]; }
localPosition = (5*j - 50*(j/10) + jitter, 0, 5*(j/10) + jitter), jitter ∈ ±randomRange
// Monster: animation trên shader, KHÔNG Animator (dump.cs:594503)
_mpb.SetFloat(ID_TimeOffset, <lệch pha riêng từng con>); // 240 con không đập cánh đồng loạt
_mpb.SetFloat(ID_AnimSpeed, …); _mpb.SetFloat(ID_ScaleAmount, …);
renderer.SetPropertyBlock(_mpb);
// ---- Gợi ý framework ----
Enemy (chung) : Health_Now/Max, Money, is_Die, IProgressBar, IBounce
IEnemyKind : OnDie() — quyết định vòng đời riêng (tắt ngay, chạy FX chết trước, đẩy wave, nhả dia…)
IDamageRouter : tag/component -> lượng damage (Ore = hằng 1, không đi qua chuỗi nhân)
ICrowdAnim : MaterialPropertyBlock(timeOffset, animSpeed, scaleAmount) cho đám đông; Animator chỉ cho boss
StageCurve : bảng tính trước N stage + cache; nguồn sự thật là BẢNG đã bake, không phải hàm sinh
Bẫy đã gặp
SetActive(false)bất đối xứng trongDie_Reloadlà có thật trong disasm: chỉTree/Tree_SpeedRunbị tắt tạiEnemy; 4 loại kia tự quyết (Ore tự tắt ở cuốiDie_Reload, Monster/Boss có thể cần FX chết). Ép mọi loại chết giống nhau sẽ làm mất hiệu ứng chết của boss (doc 05 §3.4).- Knockback chia
Health_Max, không phảiHealth_Now— dùngHealth_Nowthì quái sắp chết bị bắn văng kỳ quặc (doc 06 §3.4). - Thiếu guard
is_gamingtrongEnemy.Damaged/Die— quái ăn damage và rơi tiền trong menu (G132b).Ore.Die_ReloadvàTree_manager.Tree_Destroyedđều có guard rõ ràng trong disasm. Raidcó trong binary và scene nhưng không có lối vào — đừng coi nó là nội dung "đã ship" khi làm inventory; nó là chế độ ẩn (SCREEN_CATALOG §6, G153).- Bảng stage không sinh lúc chạy.
Tree_manager.Balance_Reload(0x2B447EC) là dead code như cácBalance_Reloadkhác (CONVERGENCE, LIVEOPS_SURFACE §4); mảng thật ởMain.unity:20614934/:20624935. ĐọcPowOfThreerồi suy ra "máu tăng theo bậc 3" là đoán — doc 05 §5 ghi rõ công thức UNKNOWN. - Đếm wave bằng nhiều state — SOT chỉ có một
TreeCount_Now, tỉ lệ tính lại từ nó; thêm bộ đếm thứ hai là mời bug lệch (doc 05 §3.6). GetComponentInParenttrên mỗi lần trúng đạn — SOT tránh bằngEnemyColl.enemyScript(doc 05 §3.4/§4.8).- Đọc comparer Morton thành "xáo trộn" rồi thay bằng
Random⇒ cây tụ đám, đúng thứ SOT cố tránh (G343). - Coi
Ore.Spawn(int skinNum)là chỉ số skin ⇒ âm thầm phá thưởng dia (binary dùng nó như boolean; G343). GameTime_Maxmặc định 0 vì công cụ decompile bỏ rơi khởi tạo field ⇒ màn thua ở frame 1 mà không ném gì — tệ hơn null vì nó im lặng (G306). Đọc.ctorcủa mọi class trước khi dựng bất kỳ method nào.- Công cụ decompile gộp hai hàm làm một (
Tree.Die_Reload"ôm" trọnTree_manager.Tree_Destroyedqua tail call) ⇒ ship sai class, sai visibility, một stub rỗng và một null-guard bịa. Kiểm biên hàm trước khi tin kích thước (G346; doc 08 §3.6).
Checklist khi tự làm
- [ ] Một component
Enemychung (máu/tiền/thanh máu/nảy) + component anh em cho từng loại;Die_Reload()dispatch theo enum. - [ ] Cho loại riêng tự quyết vòng đời (tắt ngay hay chạy FX chết) — không ép chung.
- [ ] Mọi kẻ địch nằm sẵn trong scene/pool; đếm
.ctorlúc load bằng số instance (không Instantiate lúc chạy). - [ ] Collider con có liên kết ngược tới
Enemy(kiểuEnemyColl.enemyScript). - [ ] Thanh máu đám đông = vertex color / shader (
SpriteFill); thanh boss = 9-slice + text (doc 06 §3.2). - [ ] Knockback tỉ lệ
damage / maxHealth, chỉ cho loại "quái", lưu handle coroutine để huỷ cái cũ. - [ ] Đám đông animate bằng
MaterialPropertyBlock+ lệch pha;Animatorchỉ cho boss/raid boss. - [ ] Mục tiêu tài nguyên phụ (quặng) nhận damage hằng số — tách khỏi chuỗi nhân damage.
- [ ] Wave = một số nguyên; tiến độ tính lại;
Game_Winkhi về 0. - [ ] Đường cong stage: bake bảng, cache dictionary, tính trước N stage (5 000 ở SOT); bảng là nguồn sự thật.
- [ ] Cây boss chỉ khác cây thường bằng một số
counthạt (15vs5), không cần prefab riêng. - [ ] Đặt mục tiêu trên lưới tất định (Z-order + lấy mẫu cách đều) + jitter nhỏ; giữ
Randomcho jitter, không cho phân bố. - [ ] Bảng bốc bậc theo level là trọng số 100 %/hàng; bậc "boss" không nằm trong roulette.
- [ ] Relic "skip màn" chỉ nhảy trong vùng đã chinh phục — tăng tốc cày lại, không đẩy tiến độ mới.
Điều chưa biết (UNKNOWN)
- ~~Công thức
Tree_manager.GetStageHealth/GetStageMoney~~ — ĐÃ ĐÓNG (G395): hàm sinh dùng seed 250 (máu) / 20 (tiền), ×3 mỗi stage, cắt về 2 chữ số có nghĩa — mảng scene 5 000 mục khớp 8 mục đầu (2250 → 2200, 19800 → 19000, 171000 → 170000). Hàm sinh vẫn là dead code lúc chạy — mảng scene là nguồn sự thật; công thức giải thích vì sao mảng trông như thế. Chưa đối chiếu đủ 5 000 mục. Monster.Freeze()vàBarrier_Touched()— CHƯA dựng lại; biết chữ ký vàBarrierRadius = 20(RVA0x2AF82E8/0x2AF7904).Monster.Knockback_Coroutine— CHƯA dựng lại; đường cong/khoảng cách/thời gian UNKNOWN (RVA0x2AF7DE8).Boss_Slider.Value_Reload/Time_Reload— CHƯA dựng lại, phụ thuộcInfVal_change()(G007a).- Có đường mở khoá
Raidnào ở runtime không — bằng chứng tĩnh: KHÔNG; cần probe xác nhận (UNLOCK_GATES §5, G153). Valuethực tế củaWantSizecho 1 939Bounce_Custom(nảy khi trúng) — UNKNOWN, dữ liệu inspector trong scene (doc 06 §5).Boss_manager(29m),SpeedRun_manager(26m),Mine_manager(31m) — CHƯA khảo sát ngoài chữ ký, hằng số và field restore;Monster_manager/Raid_managerđã đọc được các bảng static nhưng chưa đọc spawner (doc 07 §5).MonsterCountPerLevel/SpawnIntervalPerLevel— "level" làLevel_Now(0..4) hay bậc gì: UNKNOWN (consumer chưa đọc; G353).- ~~Cây/quặng chưa hiện trên bản dựng lại~~ — ĐÃ GIẢI (G354): cây nằm ngoài khung camera orthographic cho tới khi mèo tự đi tới (batch C);
quặng (Mine) vẫn cần batch riêng cho
Mine_manager(21 stub). - ~~Cây không mất máu khi bị bắn (G424)~~ — ĐÃ ĐÓNG (G405): build 20 cây đổ 104/104 hit (xem 04).
- Chế độ Play (SpeedRun) — đã đọc trọn (G406): số cây
clamp(15·stage + 20, 20..190), bậc ngẫu nhiên 1 / 1.8 / 3 trên đường cong stage, 60 s, bố cục vòng tròn, thắng = cây cuối đổ ⇒ stage++ và 30 dia (×4 auto); quét tự động khi sức mạnh ≥máu stage × n × 193/100/24. Xem 10. - Map "kẹo ngọt" ở stage 2 — không phải bug: chủ đề map =
stage % 10(10 chủ đề lặp) (G406). - Ánh xạ sprite/skin của quái (
SkinSpritetrongMonster.Spawn) tới asset cụ thể — chưa trích từ scene.
Đọc thêm
../framework/05-characters-pets-enemies.md§2.3, §3.4–3.6 — bảng bằng chứng đầy đủ../framework/02-combat-projectiles.md— đạn trúng quái như thế nào../framework/06-juice-game-feel.md— thanh máu 2 tầng, knockback, nảy../framework/03-particles-vfx.md§3.5 — số hạt theo ngữ cảnh../framework/09-prestige-rebirth.md§3.2 — vì sao bảng scene là nguồn sự thật[liveops catalog: SCREEN_CATALOG.md]§6 — Raid chưa phát hành[liveops catalog: UNLOCK_GATES.md]— cổng Boss / Hunt / Minegap list (nội bộ)— G127, G131, G132b, G153