01-vong-lap-cot-loi.md · Cập nhật lần cuối: 2026-09-05 08:45 +07 · sha256 nguồn e47ee2e655d501 — Vòng lặp cốt lõi (chặt cây → tiền → nâng cấp → stage → mở khoá → Rebirth)
Nguồn framework:
docs/framework/07-progression-managers.md§3,docs/framework/05-characters-pets-enemies.md§3.6,docs/framework/09-prestige-rebirth.md§3.1/§3.3,docs/framework/02-combat-projectiles.md§3.1/§3.5,[liveops catalog: UNLOCK_GATES.md]§1–§2. 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 làm một game idle "bắn cây kiếm tiền" thì trước hết phải nhìn thấy toàn bộ vòng lặp trong một trang: người chơi làm gì, số nào tăng, số nào reset, và cửa nào mở ra ở đâu. Trang này vẽ vòng lặp của CatGunner từ viên đạn đầu tiên tới lần Rebirth đầu tiên, kèm đúng hàm (và RVA) thực hiện từng bước — để artist biết mình đang vẽ cho khoảnh khắc nào, và dev biết mình đang copy hành vi nào.
Game gốc làm gì
Vòng lặp nhìn từ trên xuống
┌──────────────────────────────────────────────────────────────────────────┐
│ VÒNG TRONG (mỗi màn) │
│ │
│ Cat bắn ──> Bullet trúng ──> Damage_Collider ──> Enemy.Damaged │
│ │ │ │
│ │ Health_Now <= 0 │
│ v v │
│ (Ore: -1 mỗi nhát) Enemy.Die │
│ │ │
│ Gamemanager.Get_Money(Money, pos) │
│ │ │
│ Money += MoneyValue_return(v) (chuỗi nhân Upgrade×Rebirth×…) │
│ │ │
│ mua Upgrade Power / Speed / Money (Upgrade_manager) │
│ │ │
│ Tree.Die_Reload → Tree_Destroyed: TreeCount_Now-- → 0 ⇒ Game_Win │
│ │ │
│ StageAdvance: Level_Now 0..4, hết 4 ⇒ Stage_Now++ │
│ Stage_Value_Max = max(Level_Now + 10·Stage_Now) │
│ │ │
│ Contents_Lock_Reload: mở Rebirth/Mine/Fish/Boss/Pet/Hunt/Trip │
└──────────────────────────────────────────────────────────────────────────┘
│
v
┌──────────────────────────────────────────────────────────────────────────┐
│ VÒNG NGOÀI (Rebirth) │
│ thưởng Ruby = n^2.5 (n = Level_Now + 5·Stage_Now; 0 nếu Stage_Now<=1) │
│ RESET: Money, Money_Accum, mọi Upgrade Lv, Stage_Now, Level_Now │
│ PERSIST: Ruby, Rebirth_Count, cây prestige, Dia, PetCoin, súng/pet/… │
│ Ruby → mua node Damage / Speed / Money (Rebirth_manager) → nhân thu nhập│
└──────────────────────────────────────────────────────────────────────────┘
Từng bước, kèm hàm thật
| Bước | Điều game làm | Con số / hằng số | Mức bằng chứng | Nguồn |
|---|---|---|---|---|
| Bắn | Cat (không phải Gun) sinh đạn: TrySpawnBulletEx / TryFireShotgun / TryFireMissile |
27 Bullet trong pool |
metadata-proven + scene-census | doc 02 §2 (RVA 0x2AEDFB0 / 0x2AEE134 / 0x2AEE32C) |
| Trúng | Bullet.OnTriggerEnter → Gamemanager.Damage_Collider → Damage_Tag → Enemy.Damaged |
trace: 207 / 207 / 202 lần trong 40 s | disasm-proven + runtime-verified | doc 02 §2, RVA 0x2B089C8 / 0x2A50048 / 0x2A503F0 |
| Mất máu | Health_Now -= Damage; Slider_Reload(); chết ⇒ Die(), chưa chết ⇒ _bounce.Bounce_Start() |
— | disasm-proven | doc 02 §3.1, Enemy.Damaged RVA 0x2AFEF9C |
| Rơi tiền | Enemy.Die → Gamemanager.Get_Money(Money, transform.position) |
Get_Money fired 85× trong 40 s |
disasm-proven + runtime-verified | doc 01 §2, RVA 0x2A4ECBC / 0x2A4EDD8 |
| Nhân thu nhập | MoneyValue_return: Upgrade × Rebirth × Skin × Pet × Relic (mỗi cái /100), rồi ×3 nếu Money_Buff |
parity live: 20 → 152.2, 60 → 456.6 (×7.61) | disasm-proven + runtime-verified | doc 01 §3.2, RVA 0x2A4E7B0 |
| Nâng cấp | Upgrade_manager: Power / Speed / Money, MaxLv = 10000 |
bảng thật đọc từ scene: Money_balance[46] = 3120, Speed_balance[46] = 468 |
metadata-proven + scene-YAML + observed | doc 09 §3.2, Main.unity:20393431 / :20413434; ảnh user_screenshot_stage5-4.png |
| Wave | Tree.Die_Reload (2 dòng) → Tree_manager.Tree_Destroyed: TreeCount_Now--, gameSlider.Value_Reload((Max-Now)/Max), Now <= 0 ⇒ Game_Win() (hai hàm từng bị công cụ gộp — G346) |
Tree.Die_Reload 85× ≈ MissionValue_Increase 86× |
disasm-proven + runtime-observed | doc 05 §3.6 |
| Tiến stage | StageAdvance: Level_Now >= 4 ⇒ Level_Now = 0, Stage_Now++, ngược lại Level_Now++ |
5 level / stage, 0-based | VERIFIED | UNLOCK_GATES §1.3, RVA 0x2B45820 |
| Hiển thị | GameSlider.Stage_Reload: "Stage {Stage_Now+1}-{Level_Now+1}" |
literal @0x5E94A48 |
VERIFIED | UNLOCK_GATES §1.4, RVA 0x2B156E4 |
| Vòng chơi trên thiết bị (bản dựng lại, fresh save) | round bắt đầu 8 s sau launch; 180 s/round (thanh thời gian đếm xuống 4.000 px/s); hết giờ ⇒ tự bắt đầu lại và re-roll bố cục (ô Morton + jitter + bậc cây mới); mèo tự đi tới cây (~1 s), camera bám | 8 s / 180 s | build-measured | doc 05 §2.4 (G354, G358, G362) |
| GAME CHƠI ĐƯỢC (build 20): đạn trúng (104/104), cây đổ (29 → 14 trong ~65 s), tờ tiền + hạt lá rơi, thanh máu, hàng nút HUD + bàn tay tutorial; vòng lặp "bắn → tiền → nâng cấp" chạy trọn trên thiết bị | 104/104 | build-measured | G405 | |
| ⚠ Caveat kinh tế của bản học: tốc độ kiếm tiền trên bản learning đang sai ×300 (3.2 M/s vs 8.6 K/s game gốc cùng file) — nghi hệ số base-100 áp thô, đang sửa; mọi con số kinh tế trong cookbook vẫn là của game gốc, không phải của bản học | ×300 | build-measured (lỗi) | G414 | |
| Biến cổng | Game_Win: v = Level_Now + 10·Stage_Now; if (v >= Stage_Value_Max) Stage_Value_Max = v — chỉ tăng, không bao giờ bị Rebirth reset |
ghi tại 0x2B45534 |
VERIFIED | UNLOCK_GATES §1.2, RVA 0x2B452E4 |
| Relic Skip | Trong Game_Win, sau StageAdvance: Relic.Skip_Value % cơ hội tiến thêm 1 level, chỉ khi Level + 10·Stage < Stage_Value_Max — skip không bao giờ vượt mốc cao nhất từng đạt (rút ngắn cày lại sau Rebirth, không đẩy tiến độ mới) |
% = Skip_Value |
disasm-proven | doc 05 §3.6 (G347) |
| Mở khoá | Contents_Lock_Reload: Locked_obj.SetActive(Stage_Value_Max < N) |
N = 20 / 30 / 50 / 90 / 110 / 110 / 340 (Rebirth / Mine / Fish / Boss / Pet / Hunt / Trip) | VERIFIED | UNLOCK_GATES §2, RVA 0x2B4441C |
| Rebirth | Rebirth(): Get_Ruby(reward), Rebirth_Count++, Money_Reset(), Reset_Upgrade(), Tree_manager.Rebirth_Reset(), Cat_manager.Balance_Reload() |
reward n^2.5·relic%/100, n = Level_Now + 5·Stage_Now |
disasm-proven | doc 09 §3.1, RVA 0x2B22AE0 / 0x2B22F18 |
| Thưởng bị chặn | if (Stage_Now <= 1) reward = 0 |
0 Ruby trước Stage 2 | disasm-proven | doc 09 §2, RVA 0x2B22F18 |
| Nhiệm vụ | Mission_manager.instance.MissionValue_Increase(Mission_Type) — chỉ 4 loại: TreeCount, StageClear, Rebirth, PlayMine |
enum 4 giá trị | metadata-proven + disasm-proven | doc 07 §2.5, dump.cs:587114 |
Đối chiếu với save thật
Ảnh chụp [device evidence: user_screenshot_stage5-4.png] (sha256 04ef8221…): "Stage 5-4" ⇒ Stage_Now = 4,
Level_Now = 3 ⇒ Stage_Value = 43. Theo bảng cổng: Rebirth (20) mở, Mine (30) mở; Fish (50), Boss (90),
Pet (110), Hunt (110), Trip (340) khoá — khớp 100 % với ảnh (UNLOCK_GATES §1.5, mức: VERIFIED + observed).
Công thức / pseudocode
// ---- L1 ANALYSIS (có RVA) ----
// Tree_manager.StageAdvance — RVA 0x2B45820
if (Level_Now >= 4) { Level_Now = 0; Stage_Now += 1; } else { Level_Now += 1; }
// Tree_manager.Game_Win — RVA 0x2B452E4 (đoạn tại 0x2B45520..0x2B4557C)
v = Level_Now + 10 * Stage_Now;
if (v >= Stage_Value_Max) {
Stage_Value_Max = v; // monotonic
Rank_manager.SubmitScore(...); // R1-adjacent (leaderboard)
Contents_Lock_Reload();
}
// Tree_manager.Contents_Lock_Reload — RVA 0x2B4441C
Rebirth_Btn_Locked_obj.SetActive(Stage_Value_Max < 20);
Mine_Btn_Locked_obj.SetActive(Stage_Value_Max < 30);
Fish_Btn_Locked_obj.SetActive(Stage_Value_Max < 50);
Boss_Btn_Locked_obj.SetActive(Stage_Value_Max < 90);
Pet_Btn_Locked_obj.SetActive(Stage_Value_Max < 110);
Monster_Btn_Locked_obj.SetActive(Stage_Value_Max < 110);
Adventure_Btn_Locked_obj.SetActive(Stage_Value_Max < 340);
// Gamemanager.Get_Money — RVA 0x2A4ECBC / 0x2A4EDD8
v = MoneyValue_return(v); Money += v; Money_Accum += v; OnMoneyChanged?.Invoke();
// (bản có Vector3) + Money_Txt_Spawn(v.InfVal_change(), pos)
// Rebirth_manager.Rebirth — RVA 0x2B22AE0 (tóm tắt theo doc 09 §3.1)
n = Level_Now + 5 * Stage_Now
reward = n^2.5 * Relic_manager.Rebirth_Value / 100 // ToPrecision(5), CẮT
if (Stage_Now <= 1) reward = 0
if (is_UpgradeRebirth) reward *= 3 // [R1] ad / IAP
Get_Ruby(reward); Rebirth_Count++
Money_Reset(); Upgrade_manager.Reset_Upgrade(); Tree_manager.Rebirth_Reset(); Cat_manager.Balance_Reload()
// ---- Gợi ý framework (không phải code game) ----
interface IGameLoop {
void OnKill(BigNum drop, Vector3 pos); // -> Wallet.Earn(Chain(drop)) + text
void OnWaveCleared(); // -> StageAdvance + Stage_Value_Max + gates
void Prestige(); // -> registry ResetOnPrestige / Persistent
}
// Cổng mở khoá là MỘT số nguyên monotonic + một bảng ngưỡng; đừng tạo cờ bool riêng cho từng feature.
Bẫy đã gặp
- Thiếu guard
is_gaming⇒ kinh tế sai âm thầm. Game chỉ có một scene, quái và đạn luôn tồn tại kể cả khi mở shop.Enemy.Damaged/Enemy.Dietừng bị dựng lại thiếuif (!Gamemanager.is_gaming) return;⇒ quái vẫn ăn damage và rơi tiền trong menu. Đã sửa, ghi G132b (doc 02 §3.5, doc 08 §3.1). Stage_Value_Maxkhông reset khi Rebirth — đó là tính năng, không phải bug: nút đã mở thì không khoá lại (UNLOCK_GATES §1.2, RVA0x2B45534). Nếu bạn "tiện tay" reset nó trongPrestige(), người chơi sẽ mất quyền vào Mine/Fish sau mỗi vòng.- Rebirth sớm trả 0 Ruby (
Stage_Now <= 1) — cổng chống spam rebirth, không phải lỗi tính toán (doc 09 §3.4, RVA0x2B22F18). - Bảng cân bằng KHÔNG sinh lúc chạy.
Balance_Reloadcủa Upgrade/Rebirth/Gun là DEAD CODE trong 1.1.59 (0 code-xref); bảng thật là mảng serialize trongMain.unity, sinh bằng lặp có cắtv[i] = trunc3(v[i-1]·k), vàToPrecisionCẮT — không phảipow(), không phải làm tròn (doc 09 §3.2,[phân tích nội bộ], GAPS G138a). Doc 01 §2 vẫn ghi phiên bản cũ ("làm tròn", "sinh lúc chạy") — coi doc 09 là bản đúng. Game_WingọiRank_manager.SubmitScorengay sau khi ghiStage_Value_Max(RVA0x2B45574). Bất kỳ probe/cheat nào chỉnh stage rồi đểGame_Winchạy sẽ đẩy điểm giả lên leaderboard thật (GAPS G148).- Cache LRU của
MoneyValue_returnphải invalidate khi mua upgrade (InvalidateMoneyCacheRVA0x2A4E324), nếu không người chơi trả tiền mà thu nhập không đổi tới khi key bị đẩy khỏi cache (doc 01 §3.3).
Checklist khi tự làm
- [ ] Có một cờ trạng thái toàn cục (
is_gaming) được kiểm ở mọi entry-point gây hậu quả kinh tế (Damaged, Die, Get_Money). - [ ] Bộ đếm wave là một số nguyên (
TreeCount_Now), tỉ lệ thanh tiến độ tính lại từ nó, không có state trùng. - [ ] Stage/level 0-based trong code,
+1chỉ khi hiển thị. - [ ] Biến cổng mở khoá là một số monotonic (
max(Level + 10·Stage)), ngưỡng là hằng số trong code; không reset khi prestige. - [ ] Thưởng prestige có cổng chống farm sớm (
Stage_Now <= 1 ⇒ 0) và siêu tuyến tính theo tiến độ (n^2.5). - [ ]
Prestige()gọi đủ 3 nhóm reset (tiền, upgrade, stage) và không chạm súng/pet/relic/skin — tốt hơn: đăng ký khai báo ResetOnPrestige / Persistent (doc 09 §4.2). - [ ] Nhiệm vụ chỉ theo dõi hành động cốt lõi (4 loại là đủ cho một game thương mại).
- [ ] Mọi call-site ad/IAP trong vòng lặp (buff ×3, rebirth ×3) đi qua interface fail-closed (R1).
Điều chưa biết (UNKNOWN)
- ~~Công thức máu/tiền theo stage~~ — ĐÃ ĐÓNG (G395): seed 250 (máu) / 20 (tiền), ×3 mỗi stage, cắt về 2 chữ số có nghĩa; mảng scene khớp (250, 750, 2200, 6600, 19000…). Xem 05.
- 2/5 code-xref còn lại tới
Contents_Lock_Reload— UNKNOWN (UNLOCK_GATES §2.4). - Phân rã hệ số ×7.61 theo từng mắt (Upgrade/Rebirth/Skin/Pet/Relic) — UNKNOWN, chỉ đo được tích (doc 01 §6, G142).
- Panel nâng cấp hiển thị giá trị gì qua
Value_str_Return(0x2B49134) — CHƯA dựng lại (doc 07 §5); ảnh chụp khớp 4/4 với mảng scene (doc 09 §3.2) nên câu hỏi còn lại là đường sinh chuỗi, không phải con số. - Kinh tế offline là một mô hình riêng (
OfflineProgressSimulator) — đã dựng trọn và khớp game gốc 17 chữ số (G382; 12 luật + ví dụ số thật ở 14); khoản trả trên thiết bị còn chờ 9 stub nhỏ.
Đọc thêm
../framework/07-progression-managers.md— bộ khung 35 manager, 4 quy ước method../framework/09-prestige-rebirth.md— vòng ngoài đầy đủ, bảng scene, sim [K]../framework/01-economy-money.md— chuỗi nhân, LRU, notation../framework/02-combat-projectiles.md§3.5 — bugis_gaming- 14 — Lưu game & offline — điều gì xảy ra giữa hai phiên chơi
[liveops catalog: UNLOCK_GATES.md]— bảng ngưỡng 7 feature, chuỗi bằng chứng[phân tích nội bộ]— hai phân tích độc lập hội tụ về Balance_Reload dead codegap list (nội bộ)— G132b, G138a, G142, G143, G148