CatGunner CookbookBardCat 1.1.59 · L1 ANALYSIS · tài liệu học
Trang chủTài liệu framework › 09 — Prestige / Rebirth (vòng lặp lõi của idle)
Nguồn: 09-prestige-rebirth.md · Cập nhật lần cuối: 2026-09-05 08:22 +07 · sha256 nguồn f76fa60ff067

09 — Prestige / Rebirth (vòng lặp lõi của idle)

Nguồn: chưng cất từ restore SOT com.Chodun.CatGunner 1.1.59 (BardCat). Mọi C# / công thức ở đây là L1 ANALYSIS — đọc từ ARM64 / Hex-Rays của libil2cpp.so tại RVA được ghi kèm. Không phải source gốc. Liên quan: 01-economy-money.md (Rebirth là một mắt trong chuỗi nhân thu nhập, và §7 đặc tả module kinh tế chạy độc lập), 07-progression-managers.md.

Mô hình tham chiếu chạy được: [F001: economy_reference_sim.py] hiện thực hoá toàn bộ mô hình dưới đây bằng Python thuần và tự kiểm với 70 vector — trong đó có việc tái tạo 8 mảng balance thật của scene, 0/360 lệch. Chạy để đối chiếu.


1. Vì sao prestige là vòng lặp lõi

Idle không có "hết game". Nó có reset có thưởng: người chơi tự nguyện vứt bỏ tiến độ đã tích để đổi lấy một multiplier vĩnh viễn, rồi chạy lại quãng đường cũ nhanh hơn nhiều lần. Toàn bộ nhịp "chơi vài giờ → reset → chơi lại nhanh gấp N" là nội dung của thể loại. CatGunner gọi nó là Rebirth (환생).

Tiền tệ prestige là RUBY (Gamemanager.Ruby, static InfVal @0x68). Rebirth không tốn gì cả — bạn trả bằng tiến độ, không bằng tiền — và trả về Ruby, thứ chỉ dùng để mua các node trong cây nâng cấp vĩnh viễn.


2. Stable truths

Mức bằng chứng: disasm-proven = đọc từ ARM64/Hex-Rays tại RVA đã ghi. metadata-proven = từ IL2CPP metadata đã giải mã (dump.cs) — tên/kiểu/offset/const chính xác. runtime-observed = đã thấy chạy trên thiết bị. derived = tính ra từ công thức disasm.

Claim Mức bằng chứng Nguồn / provenance
Tiền tệ prestige = Ruby; thưởng trao qua Gamemanager.Get_Ruby(reward) disasm-proven PRESTIGE_RULES.md §4; Rebirth_Reward_return RVA 0x2B22F18
Rebirth không tốn tiền tệ nào. Rebirth() không trừ gì cả disasm-proven RVA 0x2B22AE0; PRESTIGE_RULES.md §2
Rebirth_manager.Money_return() = Upgrade_list[2].Value_return() + 100, dùng như × /100 trong chuỗi thu nhập disasm-proven RVA 0x2B22434
Damage_return() = Upgrade_list[0].Value_return() + 100; Speed_return() = Upgrade_list[1].Value_return() + 100 disasm-proven RVA 0x2B22234 / 0x2B22368
Value_return() = Value_balance[clamp((int)Lv, 0, len-1)] disasm-proven RVA 0x2B222FC
Balance_Reload là DEAD CODE trong 1.1.59CodeRefsTo = [] cho cả Rebirth_manager (0x2B21CB4), Upgrade_manager (0x2B4A02C), Gun_manager (0x2B0AB4C); không string literal, không UnityEvent trong scene. Nó không bao giờ chạy lúc runtime. VERIFIED (IDA xrefs + stringliteral + scene) [phân tích nội bộ] §U4, §B-3
Bảng cân bằng THẬT lúc chạy = mảng đã serialize trong Main.unity (nhiều khả năng do một Editor script ngoài build bake vào) scene-YAML Main.unity — Rebirth Value_balance ×3 @20451299/20491308/20531317, Price_balance @20571320; Upgrade Price/Power/Money/Speed @20353425/20373428/20393431/20413434
Các mảng đó được sinh bằng LẶP CÓ CẮT, không phải dạng đóng: v[i] = trunc3(v[i-1] × k), với hằng số là double nhị phân VERIFIED (tái tạo 0/360 lệch trên 8 mảng) [F001: economy_reference_sim.py] mục [M]
Bảng Damage[0] và Money[2] (mảng scene): Value(lv) = trunc3(int(trunc3(1.05^lv · 100 − 100))) ⇒ hệ số xấp xỉ 1.05^Lv, nhưng luôn thấp hơn do cắt (lv50: ×11.40 vs dạng đóng 11.4674) scene-YAML + VERIFIED Main.unity:20451299 / :20531317; sim mục [M]
Bảng Speed[1] (mảng scene): Value(lv) = 5 · lv ⇒ hệ số 1 + 0.05·Lv (tuyến tính, khớp chính xác) scene-YAML + VERIFIED Main.unity:20491308; sim mục [M]
MaxLv = 20000 cho cả ba node; mảng cỡ 20001 disasm-proven + metadata-proven RVA 0x2B21CB4; const UpgradeMaxLv = 20000 dump.cs:598987
Giá node prestige (Ruby), một bảng dùng chung cho cả 3 node (mảng scene): Price(lv) = trunc3(int(trunc4(3·(lv+1)^1.6))) — tái tạo 0/60 lệch. Hệ số C(lv) = 1.002^(lv−14998) cho lv ≥ 14999 chỉ tồn tại trong Balance_Reload (dead code); điểm gãy trong mảng scene là UNKNOWN scene-YAML + VERIFIED Main.unity:20571320; sim mục [M]
Điều kiện mua node: (int)Lv < MaxLv Price_return() <= Gamemanager.Ruby; chạm trần ⇒ UI_manager.Alert(ScriptLocalization.Alarm.MaxLv) disasm-proven UpgradeAble_return RVA 0x2B23768
Cổng thưởng: if (Tree_manager.instance.Stage_Now <= 1) reward = 0 — rebirth trước Stage 2 được 0 Ruby disasm-proven RVA 0x2B22F18
Công thức thưởng: n = Level_Now + 5·Stage_Now; reward = n² · √n · Relic_manager.Rebirth_Value / 100 (= n^2.5 · relic% / 100), ToPrecision(5) CẮT. Baseline không relic: Rebirth_Value = 100reward = n^2.5 disasm-proven RVA 0x2B22F18; Relic_manager.Value_Reload 0x2ADF8F0
Thưởng ×3 nếu is_UpgradeRebirth (boost quảng cáo/IAP), áp dụng trước khi trao disasm-proven RVA 0x2B22F18; const UpgradeRebirth_Multiplier = 3
Relic_manager.Rebirth_Value (static int %, @0x68) chỉ nhân KHOẢN TRẢ prestige — khác hẳn Relic_manager.Money_Value vốn nhân THU NHẬP trong MoneyValue_return disasm-proven PRESTIGE_RULES.md §4
Đường ghi Rebirth_Value (đợt 4): Relic_manager.Value_Reload (0x2ADF8F0) đặt baseline 100 rồi cộng relic Rebirth tuyến tính 5·Lv + 5 (%); Skip_Value = 2·Lv + 2, Offline_Value = 10·Lv + 10 phút, MoveSpeed_Value = 2·Lv + 2; ba đường cong Dmg/Money/CriDmg dùng chung seed 105 ×1.05/level (3 chữ số) −100, bậc ×1 / ×1.5 / ×2 disasm-proven G361
RESET khi Rebirth(): Money Money_Accum → 0; toàn bộ level upgrade thường → 0; Stage_Now = 0, Level_Now = 0 (+ Game_Exit() + Game_Start()); Cat_manager.Balance_Reload() disasm-proven RVA 0x2B22AE0; Money_Reset 0x2A4F1F8; Reset_Upgrade 0x2B4A61C; Rebirth_Reset 0x2B45EF8
PERSIST: Ruby (còn tăng thêm thưởng), Rebirth_Count (++), level cây prestige, Dia, PetCoin, súng/pet/relic/skin/mission (không hề được Rebirth() tham chiếu) disasm-proven (kể cả bằng chứng phủ định) RVA 0x2B22AE0
Rebirth() cũng gọi: ẩn rebirth_panel_ui, Review_manager.Review_Panel_Open(), Mission_manager.MissionValue_Increase(Mission_Type.Rebirth), SaveSystem.Save() disasm-proven RVA 0x2B22AE0
Level cây prestige là ObscuredInt (ACTk) — prestige state được coi là dữ liệu nhạy cảm metadata-proven + package-source dump.cs:598957; ACTk = Asset Store 202695
Điều kiện mở khoá Rebirth: Tree_manager.Contents_Lock_Reload đặt Rebirth_Btn_Locked_obj.SetActive(Stage_Value_Max < 20); Stage_Value_Max = max(Level_Now + 10·Stage_Now) ghi trong Game_Win, và KHÔNG BAO GIỜ bị Rebirth() reset ⇒ nút không khoá lại VERIFIED (IDA decompile + xrefs) Contents_Lock_Reload RVA 0x2B4441C; Game_Win 0x2B452E4; FABLE §U1
Cùng ngưỡng ở nút bấm: Content_Btn_Rebirth (0x2B44B58) — Stage_Value_Max >= 20 thì mở panel, ngược lại UI_manager.Alert(Alarm.StageUnlock, 3). Ngưỡng 20 = vào "Stage 3" hiển thị VERIFIED FABLE §U1
Thang mở khoá nội dung khác trên cùng Stage_Value_Max: 20 / 30 / 50 / 90 / 110 / 110 / 340 = Rebirth / Mine / Fish / Boss / Pet / Hunt / Trip, alert "mở ở stage" 3 / 4 / 6 / 10 / 12 / 12 / 35 VERIFIED số và tên (G395: 7 handler Content_Btn_*) Contents_Lock_Reload 0x2B4441C; Content_Btn_* G395
Cờ boost-kind = Shop_manager.Is_Remove_AD_All (static @0x9). Kind: 1 = xem quảng cáo, 2 = đã mua Remove-AD-All, 3 = IAP VERIFIED (DataRefsTo(0x5E30D10)) dump.cs:600308; FABLE §U2
MathInfVal.Pow(in InfVal, int power, bool conservePrecision = true): bình phương liên tiếp, không cap; conservePrecisionToPrecision(value.precision). int → InfVal có precision 5 VERIFIED RVA 0x44E7964, 0x44DCEB0; FABLE §U3
ToPrecision CẮT, không làm tròn — xác nhận 2 kênh: code (0x44E0E5C → 0x44DF888) và dữ liệu (1202 phần tử mảng scene khớp 100 % mô hình cắt, 0 % mô hình tròn) VERIFIED FABLE §U3, §B-1
Trong 1.1.59, liveops KHÔNG ghi đè kinh tế: Firebase RemoteConfig chỉ chạm UpdateManager; PlayFab chỉ login/leaderboard (GetTitleData 0 caller); ServerConfig chỉ ads/freecash/offerwall. Gun_manager.Request_Sheet (Google Sheet TSV) chỉ tới được từ Balance_Reload đã chết VERIFIED FABLE §U4
Upgrade_manager.Money_return() = Money_balance[Lv_return(Upgrade_type.Money)], Upgrade_type.Money == 2 disasm-proven RVA 0x2B4A864
Money_balance / Power_balance (mảng scene): [0]=100; [i] = trunc3([i-1] · 1.08)đệ quy CÓ CẮT. Trôi khỏi dạng đóng 100·1.08^lv tới −10.2 % ở lv 50 scene-YAML + VERIFIED Main.unity:20393431; sim mục [M]
Speed_balance (int[] blob trong scene): [lv] = 100 + 8·lv (khớp chính xác) scene-YAML + VERIFIED Main.unity:20413434; sim mục [M]
Price_balance (upgrade thường, mảng scene): acc[0]=50; acc = trunc4(acc · 1.38_double), lưu trunc3(int(acc)). [1] = 68, KHÔNG phải 69 — vì 1.38 là double nhị phân 1.3799999999999998… scene-YAML + VERIFIED Main.unity:20353425; sim mục [M]
Runtime: Rebirth_manager.Money_return 93×, Damage_return 10×, Speed_return 10×, SaveData_return trong ~40 s idle-play; không có lần rebirth nào runtime-observed [runtime trace: runtime_ranking.json]
Có biến thể "rebirth tăng cường": UpgradeRebirth_DailyLimit = 1, UpgradeRebirth_IAP_DailyLimit = 1, UpgradeRebirth_Multiplier = 3, UpgradeRebirth_IAP_UnlockStageValue = 989 metadata-proven dump.cs:598987
Số lần rebirth là một loại nhiệm vụ (Mission_Type.Rebirth = 2); có AD_Type.Upgrade_Rebirth = 8 metadata-proven dump.cs:587114, dump.cs:584959

3. Mô hình hành vi

3.1 Kiến trúc hai tầng

Vòng ngoài — Rebirth()  [RVA 0x2B22AE0]
    reward = n^2.5 * relic% / 100      (n = Level_Now + 5*Stage_Now; 0 nếu Stage_Now <= 1)
    Get_Ruby(reward)                   -> PERSIST
    Rebirth_Count++                    -> PERSIST
    Money_Reset()                      -> RESET  (Money và Money_Accum)
    Upgrade_manager.Reset_Upgrade()    -> RESET  (mọi Upgrade_list[i].Lv = 0)
    Tree_manager.Rebirth_Reset()       -> RESET  (Stage_Now=0, Level_Now=0, Game_Exit+Game_Start)
    Cat_manager.Balance_Reload()       -> tính lại chỉ số mèo từ state mới

Vòng trong — Rebirth_Upgrade(num)  [RVA 0x2B22500]
    tiêu RUBY vào Upgrade_list[num]:  0=Damage, 1=Speed, 2=Money
    điều kiện: Lv < 20000  và  Price(Lv) <= Ruby

Điểm thiết kế: phần thưởng prestige không tự động thành sức mạnh. Nó là Ruby — một tiền tệ meta mà người chơi phải phân bổ vào một trong ba trục. Điều này thêm một quyết định vào mỗi vòng lặp và cho designer một cần gạt (đường cong giá) độc lập với đường cong kiếm thưởng.

3.2 Công thức của designer vs BẢNG ĐÃ SHIP — hai thứ khác nhau

Đây là bài học framework có giá trị nhất trong toàn bộ tài liệu, và nó suýt bị ghi sai.

Sự thật: Balance_Reload — hàm sinh bảng cân bằng — là DEAD CODE trong build 1.1.59. CodeRefsTo rỗng cho cả ba (Upgrade_manager 0x2B4A02C, Rebirth_manager 0x2B21CB4, Gun_manager 0x2B0AB4C); không có string literal (loại trừ Invoke/StartCoroutine theo tên); không có m_MethodName: Balance_Reload trong scene (loại trừ UnityEvent). Nó không bao giờ chạy.

Bảng thật lúc chạy là các mảng đã serialize trong Main.unity — gần như chắc chắn được một Editor script (không có trong build) chạy một lần rồi bake vào scene.

Upgrade_manager   Price_balance  @Main.unity:20353425     Power_balance @:20373428
                  Money_balance  @:20393431               Speed_balance @:20413434 (blob int32 LE)
Rebirth_manager   Value_balance[0..2] @:20451299 / :20491308 / :20531317
                  Price_balance  @:20571320
Tree_manager      Stage_Health_Balance @:20614934         Stage_Money_Balance @:20624935

Và các mảng đó KHÔNG khớp dạng đóng. Chúng được sinh bằng lặp có CẮT:

# Upgrade Money/Power — tái tạo mảng scene, 0/60 lệch
v = 100
for i in range(1, n): v = trunc3(v * 1.08)        # KHÔNG phải 100 * 1.08**i

# Upgrade Price — 0/60 lệch. Lưu ý 1.38 là DOUBLE NHỊ PHÂN 1.3799999999999998…
acc = 50
for i in range(1, n): acc = trunc4(acc * 1.38); price[i] = trunc3(int(acc))

# Rebirth Value[0] (Damage) và [2] (Money) — 0/60 lệch
value[lv] = trunc3(int(trunc3(1.05**lv * 100 - 100)))
# Rebirth Value[1] (Speed): 5 * lv        Upgrade Speed: 100 + 8 * lv     (khớp chính xác)

# Rebirth Price (Ruby) — 0/60 lệch
price[lv] = trunc3(int(trunc4(3 * (lv+1)**1.6)))

ToPrecision CẮT về phía 0, không làm tròn — xác nhận hai kênh độc lập: code (0x44E0E5C → ToExponent 0x44DF888 → set_exponent, chia BigInteger) và dữ liệu (1 202 phần tử mảng scene khớp 100 % mô hình cắt, 0 % mô hình làm tròn).

Sai lệch tích luỹ — vì sao dùng luỹ thừa là sai:

lv Money_balance mảng thật 100·1.08^lv (dạng đóng) lệch
0 100 100.00 0 %
1 108 108.00 0 %
2 116 116.64 −0.55 %
5 145 146.93 −1.32 %
10 210 215.89 −2.73 %
25 653 684.85 −4.65 %
46 3 120 3 447.41 −9.50 %
50 4 210 4 690.16 −10.24 %
59 8 370 9 375.65 −10.73 %

Ở lv 1 000, dạng đóng lệch ~6.5 lần (4.06e34 mảng vs 2.65e35 công thức). Sai số luôn theo một hướng (thấp hơn), vì cắt không bao giờ làm tăng.

Bảng prestige cũng vậy, chỉ nhẹ hơn (cắt một lần mỗi phần tử, không tích luỹ qua đệ quy):

lv Value_balance[2] hệ số thu nhập 1.05^lv
0 0 ×1.0000 1.0000
1 5 ×1.0500 1.0500
10 62 ×1.6200 1.6289
20 165 ×2.6500 2.6533
50 1 040 ×11.4000 11.4674
59 1 670 ×17.7000 17.7897

Giá node prestige (Ruby)giá upgrade thường, đọc thẳng từ mảng scene:

lv Rebirth Price_balance Upgrade Price_balance
0 3 50
1 9 68 (không phải 69)
9 119 906
25 550 156 000 (156a)
46 1 420 (1.42a) 1.34×10⁸ (134b)
59 2 090 (2.09a) 8.83×10⁹ (8.83c)

Mọi con số trong ba bảng trên đọc trực tiếp từ Main.unity, đã trích ra [F001: scene_balance_tables.json] và được mô hình tham chiếu tái tạo 0/360 lệch (mục test [M]). Phạm vi đã đối chiếu: lv 0…59.

Xác nhận cuối cùng: ảnh chụp save thật khớp cả BỐN giá trị

[device evidence: user_screenshot_stage5-4.png] (sha256 04ef8221…), Power/Speed/Money Lv.46:

Ô trên màn hình Mảng scene Màn hình
Speed Lv.46 Speed_balance[46] = 468 468
Power Lv.46 Power_balance[46] = 3 120 3.12a
Money Lv.46 Money_balance[46] = 3 120 3.12a
Giá Lv.46 Price_balance[46] = 1.34×10⁸ 134b

4/4 khớp. Mô hình dựa trên công thức + làm tròn mà tài liệu này từng dùng cho 3.5a136bsai 2/3 ô. Chính hai ô lệch đó là thứ chỉ ra rằng mô hình sai, chứ không phải panel hiển thị thứ khác. (Xem DECISIONS D008/D010.)

Xác nhận bằng mắt: panel Rebirth trên máy thật

[liveops catalog: 31_panel_Rebirth.png] (mở bằng chính Tree_manager.Content_Btn_Rebirth sau khi đặt Stage_Value_Max = 1000 trên save .sot riêng) xác nhận trực tiếp mô hình ở §3.1 và §3.4:

Quan sát trên panel Khớp với
"Phần thưởng khả dụng" = 0 cổng Stage_Now <= 1 ⇒ reward = 0 (§3.4)
"Super Rebirth! ×3" sau một nút quảng cáo 1/1 UpgradeRebirth_Multiplier = 3 + UpgradeRebirth_DailyLimit = 1 (§3.5)
Ruby là tiền tệ hiển thị (= 0) tiền tệ prestige = Ruby (§1)
Đúng 3 nâng cấp: Power / Speed / Money Increase Upgrade_list[0..2] = Damage / Speed / Money (§3.1)
Cả ba ở Lv.0, +0% Value_balance[0] = 0 ⇒ hệ số ×1.00 (§3.2)
Giá mỗi cái = 3 Ruby Price_balance[0] = 3 (§3.2, mảng scene)

Sáu điểm dữ liệu độc lập, tất cả khớp. Đây là xác nhận đầu-cuối cho tầng prestige: từ mảng serialize trong scene → công thức disasm → con số hiện trên màn hình.

Bài học framework

Công thức mà designer viết ≠ bảng mà game ship. Ở đây chúng lệch tới 10 % ở lv 50 và 6.5 lần ở lv 1 000, chỉ vì một phép cắt 3 chữ số lặp lại — và vì 1.38 là double nhị phân.

Nếu bạn bake bảng cân bằng vào asset, bảng là nguồn sự thật, còn hàm sinh chỉ là công cụ Editor. Ai đọc hàm sinh rồi nghĩ mình đã hiểu nền kinh tế sẽ sai — theo một hướng nhất quán, ở mọi level cao. Với một bản restore, hệ quả trực tiếp: giữ nguyên mảng serialize; đừng gọi Balance_Reload lúc chạy. Gọi nó với một InfVal khác phiên bản (làm tròn thay vì cắt, hoặc 1.38 thập phân) sẽ sinh ra một nền kinh tế khác SOT.

3.3 Nối vào chuỗi thu nhập

// Gamemanager.MoneyValue_return — RVA 0x2A4E7B0 (runtime-verified)
r = value * Upgrade_manager.Money_return() / 100;   // = Money_balance[Lv] (mảng scene) -> RESET
r = r * Rebirth_manager.Money_return()     / 100;   // = Value_balance[2][Lv] + 100 -> BỀN qua rebirth
r = r * Skin_manager.Money_Accum           / 100;
r = r * Pet_manager.Pet_Money_Accum        / 100;
r = r * Relic_manager.Money_Value          / 100;
if (Money_Buff active) r = r * 3;

Đây chính là cơ chế "chạy lại nhanh hơn": mắt Upgrade_manager rơi về 100 % (trung tính) sau mỗi rebirth, còn mắt Rebirth_manager giữ nguyên và nhân lên trên mọi thu nhập kể từ giây đầu của vòng mới. Vì tất cả là phần trăm nhân nhau, prestige kéo giãn toàn bộ đường cong chứ không cộng thêm.

Ví dụ một chu kỳ đầy đủ (mô phỏng, mục [K] trong sim):

Trước rebirth:  Stage 10, Level 0, upgrade Money lv 25  -> mắt Upgrade = 653 %  (Money_balance[25])
Rebirth():      n = 0 + 5*10 = 50;  reward = 50^2.5 = 17 677 Ruby  (ToPrecision(5) CẮT)
                Money/Money_Accum -> 0 ; upgrade lv 25 -> 0 ; Stage/Level -> 0
Mua node:       17 677 Ruby mua được 40 level node Money (còn dư 270 Ruby)
Sau rebirth:    mắt Upgrade = 100 % (đã reset)  |  mắt Rebirth = 703 % (×7.03, vĩnh viễn)

Người chơi mất mọi thứ trong vòng, nhưng bước vào vòng mới với thu nhập ×7.03 — và mắt Upgrade sẽ lại leo lên 653 % nhanh hơn nhiều vì mọi đồng tiền kiếm được đều đã nhân 7.03. (Số liệu từ mô hình tham chiếu mục [K], chạy trên mảng scene thật.)

3.4 Đường cong thưởng và giá

Thưởng (RVA 0x2B22F18), tính từ tiến độ đạt được:

n = Tree_manager.Level_Now + 5 * Tree_manager.Stage_Now
reward = n^2.5 * Relic_manager.Rebirth_Value / 100      # ToPrecision(5) — CẮT, không làm tròn
if (Stage_Now <= 1) reward = 0                          # cổng chống farm rebirth sớm
if (is_UpgradeRebirth) reward *= 3                       # [R1] boost quảng cáo/IAP

Giá trị thật (relic 100 %, tính lại từ công thức — sim mục [J]):

Level_Now Stage_Now n thưởng (Ruby) hiển thị
9 1 0 (cổng) 0
0 2 10 316.22 316
5 2 15 871.42 871
0 10 50 17 677 17.67a
20 20 120 157 740 157.74a

2.5 rất đáng chú ý: nó siêu tuyến tính theo tiến độ. Đi sâu gấp đôi ⇒ thưởng gấp 2^2.5 ≈ 5.66 lần. Đây là thứ khiến người chơi có động cơ đẩy xa thêm một chút trước khi reset, thay vì reset liên tục — và cùng lúc, Stage_Now <= 1 ⇒ 0 chặn hoàn toàn chiến thuật "rebirth spam".

Giá node — một bảng dùng chung cho cả 3 node, đọc thẳng từ mảng scene (Main.unity:20571320):

lv giá (Ruby) hiển thị
0 3 3
1 9 9
9 119 119
25 550 550
46 1 420 1.42a
59 2 090 2.09a

Chỉ liệt kê trong phạm vi đã đối chiếu với mảng scene (lv 0…59). Giá trị ở level cao hơn và điểm gãy ×1.002 (lv ≥ 14999) là UNKNOWN — chúng chỉ tồn tại trong Balance_Reload đã chết.

Giá là đa thức (lv^1.6) trong khi phần thưởng là siêu tuyến tính theo tiến độ và sức mạnh là lãi kép (1.05^Lv). Ba tốc độ khác nhau này là cái tạo ra nhịp: giá đa thức bị lãi kép vượt mặt về lâu dài, nên hệ thống tự mở dần. (Balance_Reload đã chết còn có một hệ số ×1.002/level từ lv 14999 dựng một bức tường mềm ở cuối; mảng scene chưa được đối chiếu ở tầm level đó — UNKNOWN.)

3.5 Biến thể "rebirth tăng cường" — và ranh giới R1

UpgradeRebirth_Able_return()          -> còn lượt hôm nay? (DailyLimit = 1)
UpgradeRebirth_IAP_Unlocked_return()  -> đã qua Stage_Value 989?
UpgradeRebirth_IAP_Able_return()      -> còn lượt IAP hôm nay? (IAP_DailyLimit = 1)
UpgradeRebirth_Start_By_Ad()   [R1]   RVA 0x2B233D0 -> thưởng ×3   (kind 1, hoặc 2 nếu đã mua Remove-AD-All)
UpgradeRebirth_Start_By_IAP()  [R1]   RVA 0x2B23454 -> thưởng ×3   (kind 3)
UpgradeRebirth_DayCheck()             -> so UpgradeRebirth_LastDate với Today_str_return()

Mẫu monetization điển hình của idle: nó không bán sức mạnh, nó bán tốc độ của vòng lặp — nhân 3 phần thưởng của một hành động người chơi vốn đã muốn làm. Giới hạn 1 lần/ngày cho mỗi kênh biến nó thành thói quen quay lại, không thành cửa hàng. Cổng IAP_UnlockStageValue = 989 chỉ mở kênh trả tiền sau khi người chơi đã tiến đủ sâu. Reset theo chuỗi ngày, không theo timestamp.

Các call-site R1 trong đường rebirth (ghi nhận, không dựng lại nội dung SDK):

Call-site Loại Điều kiện
AppsFlyerInit.SendLog_Rebirth(kind) analytics → IAnalyticsService mỗi lần rebirth
Admob.instance.ShowPlacementInterstitial(...) quảng cáo → IAdService chỉ khi rebirth không được boost popup review không mở
UpgradeRebirth_Start_By_Ad quảng cáo → IAdService lối vào boost ×3
UpgradeRebirth_Start_By_IAP IAP → IPurchaseService lối vào boost ×3

Ràng buộc R1 tuyệt đối: binding mặc định là NullService fail-closed — trả ServiceResult.NotAvailable, không bao giờ cấp ×3. Một stub được phép log và no-op; nó không bao giờ được bịa ra một lần rebirth tăng cường.


4. Framework takeaway

4.1 IPrestigeLayer — bốn khái niệm

public interface IPrestigeLayer
{
    bool     CanPrestige { get; }        // ở CatGunner: luôn true, nhưng thưởng có cổng riêng
    BigNum   PreviewReward();            // ~ Rebirth_Reward_return()
    void     Prestige();                 // reset-set + trao meta-currency + count++
    int      PrestigeCount { get; }      // ~ Rebirth_Count
}

Tách "được phép reset" khỏi "reset có đáng không". CatGunner cho phép rebirth bất cứ lúc nào nhưng trả 0 nếu Stage_Now <= 1. Đây tốt hơn là khoá nút: người chơi luôn có thể thoát, hệ thống chỉ không thưởng cho hành vi thoái hoá.

4.2 Reset-set / Persist-set phải là khai báo

Sai lầm phổ biến: Prestige() gọi tay N hàm reset, và hệ thống thứ N+1 thêm sau đó bị quên → người chơi giữ lại thứ lẽ ra phải mất. Framework nên đảo ngược quan hệ:

[PrestigeScope(PrestigeScope.ResetOnPrestige)]   // hoặc .Persistent
public sealed class StageProgress : IPrestigeAware { public void OnPrestige() { … } }

Tầng prestige duyệt registry và gọi OnPrestige(); một hệ thống mới bắt buộc phải khai báo phe của nó. CatGunner làm việc này bằng kỷ luật con người (3 lời gọi tường minh trong Rebirth()) — chạy đúng, nhưng không có gì bắt buộc, và bằng chứng phủ định (súng/pet/relic/skin không hề được tham chiếu) là thứ phải đọc disasm mới biết.

4.3 Phần thưởng prestige là tiền tệ meta, không phải multiplier trực tiếp

Prestige()        -> cấp MetaCurrency (Ruby)
MetaUpgradeTree   -> node { Lv, MaxLv, ValueCurve(lv), PriceCurve(lv) }
IMultiplierSource -> tree.PercentFor(kind)      // cắm vào chuỗi nhân của doc 01

Nhờ vậy tầng prestige nối vào chuỗi nhân y hệt mọi nguồn khác (trả phần trăm, mặc định 100) — không cần trường hợp đặc biệt nào trong công thức kinh tế.

4.4 Ba tốc độ tăng trưởng, tham số hoá tách bạch

Bằng chứng từ SOT cho một bộ tham số rất sạch mà framework nên phơi ra nguyên vẹn:

Trục CatGunner (upgrade trong vòng) CatGunner (prestige)
Trần level 10 000 20 000
Sức mạnh/level ×1.08 lãi kép, đệ quy có CẮT (bảng bake) ×1.05 lãi kép có CẮT (bảng bake)
Tốc độ đánh/level +8 tuyến tính +5 % tuyến tính
Giá — dạng 50 · 1.38^lv (hàm mũ) 3 · (lv+1)^1.6 (đa thức)
Tường cuối ×2.0/level từ lv ≥ 5 000 ×1.002/level từ lv ≥ 14 999
Lượng tử ToPrecision(3)CẮT ToPrecision(3)CẮT
Nguồn lúc chạy mảng bake trong scene (hàm sinh là dead code) mảng bake trong scene

Bài học thiết kế: tầng trong dốc và ngắn (giá hàm mũ vượt mặt sức mạnh hàm mũ ⇒ tự tắc nghẽn, buộc phải prestige); tầng prestige thoải và dài (giá đa thức thua sức mạnh hàm mũ ⇒ tự mở dần). Đó chính xác là hai vai trò cần có.

Framework nên tham số hoá mỗi tầng bằng đúng bộ này:

maxLevel, valueCurve(kind, lv), priceCurve(lv), softCapLevel, lateGrowth, quantizeDigits

4.5 Bảng đã bake là nguồn sự thật; hàm sinh chỉ là công cụ Editor

SOT bake bảng vào scene ở 3 chữ số có nghĩa, sinh bằng đệ quy có CẮT, với hằng số là double nhị phân. Hàm sinh (Balance_Reload) không bao giờ chạy trong build.

Nếu framework của bạn dùng double + pow(), bạn không ra "một game hơi khác" — bạn ra một game lệch 10 % ở lv 50 và 6.5 lần ở lv 1 000, và luôn lệch về một phía.

Ba điều framework nên bắt buộc: 1. quantizeDigits và luật cắt/tròn là tham số tường minh của đường cong, không phải mặc định ngầm. 2. "Đệ quy" hay "dạng đóng" là lựa chọn được ghi lại, vì hai cái cho kết quả khác nhau. 3. Nếu bảng được bake, bảng đi kèm một checksum và hàm sinh phải tái tạo được nó trong CI. Nếu không, hàm sinh sẽ âm thầm trôi khỏi dữ liệu đã ship — đúng như đã xảy ra ở đây.

4.5b Mốc mở khoá dùng một tiến độ ĐƠN ĐIỆU riêng, không dùng tiến độ hiện tại

Stage_Value_Max = max(Level_Now + 10·Stage_Now) là một giá trị chỉ tăng, ghi ở Game_Win, và không bị prestige reset. Toàn bộ thang mở khoá nội dung (20 / 30 / 50 / 90 / 110 / 110 / 340) đọc từ nó, không đọc từ Stage_Now hiện tại.

Đây là mẫu đúng và rất đáng chép: nếu cổng mở khoá đọc tiến độ hiện tại, mọi lần prestige sẽ khoá lại những gì người chơi đã mở — trải nghiệm tệ và là một lớp bug dễ mắc. Framework nên cung cấp sẵn một HighWaterMark riêng cho việc mở khoá, tách khỏi tiến độ vòng hiện tại.

4.6 State prestige là dữ liệu nhạy cảm

SOT dùng ObscuredInt (ACTk) cho level cây rebirth, không dùng int trần. Trong idle, prestige state giá trị tài khoản; framework nên mặc định bọc nó và cung cấp điểm cắm cho lớp bảo vệ.

4.7 Booster prestige phải fail-closed

Mọi đường "nhân thưởng prestige bằng quảng cáo/IAP" đi qua interface, mặc định từ chối. Giới hạn theo ngày (chuỗi ngày + nguồn thời gian đáng tin), không theo timestamp cục bộ.


4b. Mô hình kinh tế THỨ HAI: thu nhập offline

Tài liệu này (và doc 01) mô tả nền kinh tế online. Có một mô hình thứ hai, tách hẳn, mà người chơi thật nhận phần lớn tiền qua đó.

Điểm cốt lõi: OfflineProgressSimulator.ApplyToManagers (RVA 0x2AB7398) ghi thẳng vào Gamemanager.Money / Money_Accum và chỉ gọi Get_Money((InfVal)0) để bắn OnMoneyChanged. ⇒ Offline KHÔNG đi qua MoneyValue_return.

Thành phần Điều đã biết Mức bằng chứng
Cửa sổ thời gian ComputeElapsed (0x2A75258): capped = min(total, cap); sàn 30 giây hoặc không cấp gì; đồng hồ vặn lùi ⇒ 0; gate bởi Time_manager.IsUsable + guard PendingCountPrefix("offline:") > 2. Trả hai giá trị riêng: elapsed (đã cap, được trả tiền) và absent (khoảng thật, chỉ hiển thị) disasm-proven (G332)
Gọi ComputeElapsed hai lần EvaluateRoutine (0x2A75714): lần 1 = cổng rẻ trước UI/đồng bộ giờ (kết quả bỏ); lần 2 sau Time_manager.WaitForCheck(5f) = giá trị được trả. Xoá "lời gọi trùng" ⇒ trả tiền theo giờ chưa đồng bộ disasm-proven (G334)
Kênh log = kênh chống lạm dụng Khoản ghi qua Time_manager.RecordGain("offline:{0:F1}h") — cùng tiền tố "offline:" mà guard đếm disasm-proven (G334)
Cap EffectiveMaxOfflineSeconds (0x2A74B2C) = MaxOfflineSeconds 28800 (8 h — hai nguồn: Main.unity:20446002 mặc định .ctor 0x2A756F0, G332) + (Relic_manager.instance != null ? Offline_Value × 60f : 0). Offline_Value tính bằng PHÚT (hàm nhân 60f) ⇒ relic "+30" = +30 phút = cap 30 600 s. Baseline 0 ⇒ đúng 8 giờ disasm-proven + scene-YAML
Tự mua nâng cấp BuyAllAffordable (0x2AB9840) / TryBuy (0x2AB9974) tiêu tiền mô phỏng vào upgrade trong cửa sổ offline ⇒ offline cũng làm tăng level Power/Speed/Money disasm-proven
Multiplier nào được áp ĐÍNH CHÍNH (G382/G1001): Upgrade money, Rebirth money, Skin Money_Accum, Pet Pet_Money_Accum, Relic Money_Value (đọc trong MoveNext 0x2ABA4DC..0x2ABA754, nhân vào cả tiền/giây lẫn tiền/wave), đường cong stage/degree. KHÔNG chỉ mỗi buff quảng cáo ×3. Bản trước ghi Skin/Pet/Relic "không" — sai do census bỏ sót lambda disasm-proven (dựng lại trọn simulator + oracle 17 chữ số)
Mô hình sinh tiền offline Mô phỏng clear wave: clearTime = combat × 1.3 + 5 s; tự mua nâng cấp level thấp nhất trước (hoà: Power > Speed > Money); không tiến quá Stage_Value_Max (cày lại wave cao nhất); level 4 thêm cây boss (HP ×35, tiền ×100); combat > 180 s ⇒ cày tới khi mua được nâng cấp (trần 1 năm); chuỗi sát thương cắt mỗi bước %, tiền/giây không cắt; bão hoà 1e15. 12 luật đầy đủ + ví dụ số thật: 01 §5.8 VERIFIED (G382, G1003)
Hằng số riêng MaxIterations 30000, OfflineClearTimeScale 1.3, StageClearDelay 5, MinTraversalPerTree 0.05, StageTimeLimit 180, Pierce/Laser 10, Blast 5 metadata-proven
Bảng của simulator (blob, SHA MATCH) DegreeHealthMult {1, 1.8, 3, 35}, DegreeMoneyMult {1, 1.8, 3, 100} (= hai mảng Tree_manager..cctor, xác nhận độc lập); DegreeRates float[5,4] = {80,20,0,0} {50,45,5,0} {20,55,25,0} {5,35,60,0} {0,15,85,0} (cùng blob với Tree_Grid.degreeRates; hàng = level 0..4, cột = bậc cây 0..3, mỗi hàng 100 %) metadata-proven (G353, G701)
Đối chiếu sim ↔ disasm economy_reference_sim.py (MIN 30 / MAX 28 800 / relic phút×60 / lùi ⇒ 0) khớp chính xác ComputeElapsed — đã so, không chỉ giả định VERIFIED (G332)

Liên hệ với prestige: mắt Rebirth_manager.Money_return được áp offline — nên cây prestige tiếp tục sinh lợi khi người chơi không mở game. Đính chính (G382): Relic_manager.Money_Value cũng được áp offline (bản trước ghi "không" — sai); Relic_manager.Offline_Value thì kéo dài cap. Hai relic, hai vai trò: một cái tăng tốc độ kiếm tiền (cả online lẫn offline), cái kia tăng thời lượng offline (tính bằng phút). Và mắt Rebirth Speed cũng vào tốc độ đánh của mô phỏng (atkSpd, luật 8) — prestige rút ngắn thời gian clear wave offline chứ không chỉ nhân tiền.

Bài học framework: hai mô hình kinh tế là có chủ đích. Dùng lại chuỗi online cho offline ⇒ cấp thừa; dùng lại mô hình offline cho online ⇒ cấp thiếu. Mô hình tham chiếu phơi chúng ra thành hai hàm có chữ ký khác nhau (offline_income_factor cố tình không nhận skin/pet/relic/buff) để không thể dùng sai. Chi tiết đầy đủ + bẫy khôi phục: 01 §5.8.

⚠ Rút lại: bản trước ghi "offline có thể cao hơn ~10 % nếu nhánh fallback chạy". Nhánh đó không thể chạy trong 1.1.59 (mảng có đúng 10 001 phần tử, chỉ số kẹp [0,10000]; instance luôn được Awake đặt trước vì chỉ có một scene). Giữ lại chỉ như một bẫy khôi phục — xem 01 §5.8.

Vị trí trong luồng boot (disasm-proven):

// LoadData.Load(data)
if (data == null) InitNewGame();
else OfflineProgressManager.instance.EvaluateAndApply(data, ContinueLoad);

CodeRefsTo(EvaluateAndApply) = đúng một người gọi: LoadData.Load. Tiến trình offline được đánh giá TRƯỚC mọi việc nạp state, và ContinueLoad được truyền vào làm continuation — nên popup "Chào mừng trở lại!" nằm trong luồng điều khiển, không phải hiệu ứng phụ. ContinueLoad rồi toả ra ~48 điểm gọi X.LoadFromSaveData(data). Thứ tự này là bắt buộc: offline ghi Gamemanager.Money và đẩy level upgrade, nên nếu manager nạp trước thì kết quả offline sẽ bị ghi đè. Chi tiết + định dạng save: 07 §3.2c.

UNKNOWN còn lại: số học chính xác của CalcFarmMoneyPerSec (0x2AB8A74) và CalcMoneyPerStage (0x2AB9128) — mới liệt kê được đầu vào của chúng, chưa dựng lại công thức (G194). Cửa sổ thời gian, máy trạng thái và các bảng Degree* thì đã đóng trong đợt 3 (G332, G334, G353).

5. Negative evidence & UNKNOWN

Đã ĐÓNG trong đợt này (trước đây là UNKNOWN)

Mục Kết quả Bằng chứng
Điều kiện mở khoá Rebirth ĐÓNG. Contents_Lock_Reload (0x2B4441C): Rebirth_Btn_Locked_obj.SetActive(Stage_Value_Max < 20); Stage_Value_Max = max(Level_Now + 10·Stage_Now) ghi trong Game_Win, không bị Rebirth() reset. Ngưỡng 20 = vừa vào "Stage 3" hiển thị FABLE §U1 (IDA decompile + CodeRefsTo)
Cờ boost-kind tại off_5E30D10 static+9 ĐÓNG. = Shop_manager.Is_Remove_AD_All. Kind 1 = xem quảng cáo, 2 = đã mua Remove-AD-All, 3 = IAP FABLE §U2 (DataRefsTo, dump.cs:600308)
MathInfVal.Pow tham số thứ 3 ĐÓNG. = conservePrecision; true ⇒ ToPrecision(value.precision). int → InfVal precision 5 FABLE §U3 (RVA 0x44E7964)
ToPrecision làm tròn hay cắt ĐÓNG: CẮT. Hai kênh độc lập — code (0x44E0E5C → 0x44DF888) và dữ liệu (1 202 phần tử mảng scene, 100 % khớp cắt / 0 % khớp tròn) FABLE §U3, §B-1
Bảng cân bằng từ xa có ghi đè không ĐÓNG: KHÔNG (trong 1.1.59). RemoteConfig chỉ UpdateManager; PlayFab GetTitleData 0 caller; ServerConfig chỉ ads/freecash/offerwall; Request_Sheet chỉ tới được từ Balance_Reload đã chết FABLE §U4
Nội dung Price_balance[] / Value_balance[] ĐÓNG. Trích ra [F001: scene_balance_tables.json] (lv 0…59, 8 mảng) và tái tạo 0/360 lệch Main.unity; sim mục [M]
Panel nâng cấp hiển thị gì ĐÓNG. Hiển thị đúng Money_balance[Lv]. Kết luận trước ("panel hiển thị giá trị dẫn xuất") là SAI, do mô hình làm tròn của chính tài liệu này sim mục [N]; ảnh chụp Lv.46
Lệch giá 136b vs 134b ĐÓNG. Mảng scene cho đúng 1.34×10⁸134b. Lệch là lỗi của mô hình làm tròn, không phải của SOT sim mục [N]

Còn MỞ

Mục Trạng thái Bằng chứng khoá được nó
Điểm gãy ×2.0 (upgrade, lv ≥ 5000) và ×1.002 (rebirth, lv ≥ 14999) trong mảng scene UNKNOWN — mới đối chiếu tới lv 59 Đọc Main.unity ở idx 4999–5002 và 14998–15001
Precision của InfVal.op_Implicit(double) (0x44DF694) UNKNOWN — ảnh hưởng chữ số cuối của reward (√n tính bằng double) Decompile 0x44DF694 + op_Multiply
Thu nhập offline (mô hình kinh tế thứ hai) ĐÃ ĐÓNG (G382) — simulator dựng lại trọn, harness tái tạo oracle 17 chữ số; 12 luật ở doc 01 §5.8. Còn mở: khoản trả trên thiết bị (9 stub R13, G1002) Codegen điền stub → nghiệm thu lại
Relic_manager.Skip_Value dùng ở đâu ĐÓNG (đợt 3): Tree_manager.Game_Win (0x2B452E4) — phần trăm cơ hội bỏ qua thêm một level (StageAdvance lần 2), chỉ khi level + 10·stage < Stage_Value_Max ⇒ skip không bao giờ vượt mốc cao nhất từng đạt; kết quả đẩy vào _pendingGameSkipUI G347
Phân rã hệ số ×7.61 của oracle KHÔNG THỂ tách từ dữ liệu hiện có — bộ bắt không ghi state Lần chạy oracle kế tiếp: log 5 getter multiplier + IsBuffActive(0) + level, cùng frame
Skin_manager.Money_Accum / Pet_manager.Pet_Money_Accum tính từ bộ sưu tập thế nào ĐÓNG MỘT PHẦN: Skin_Value_Reload (0x2B3F068) đã dựng lại — cộng dồn base-100 (thân rỗng từng làm mèo 0 damage / 0 tốc độ, G359); Relic_manager.Value_Reload đã dựng lại đầy đủ (G361). Pet_manager.Pet_Money_Accum vẫn UNKNOWN Decompile Pet_manager
10 Balance_Reload còn lại (Mission/Relic/Cat/Boss/Monster/Fish/Pet/Skin/Tree/Cat_manager) HYPOTHESIS "cũng chết" — mới kiểm CodeRefsTo cho 3 Một lệnh py_eval cho 10 RVA còn lại
Tên nội dung ứng với thang khoá 20/30/50/90/110/110/340 VERIFIED số, HYPOTHESIS tên Map các off_… static sang class; lane [liveops catalog: liveops]
UpgradeRebirth_IAP_UnlockStageValue = 989 so với Stage_Value_Max? HYPOTHESIS Decompile UpgradeRebirth_IAP_Unlocked_return 0x2B232D0
Đường cong Tree_manager.GetStageHealth/GetStageMoney ĐÃ ĐÓNG (G395): seed 250 / 20, ×3 mỗi stage, cắt 2 chữ số có nghĩa (không phải 3 — đó là lý do trunc3(×3) từng không khớp: 2250 → 2200, 19800 → 19000) — mảng scene khớp 8 mục đầu Đối chiếu đủ 5 000 mục
Không quan sát thấy Trong cửa sổ trace 40 s không có lần rebirth nào; chỉ 3 getter multiplier + SaveData_return chạy runtime_ranking.json
Không quan sát thấy Rebirth() không tham chiếu Gun/Pet/Relic/Skin manager — cơ sở của bảng PERSIST RVA 0x2B22AE0

⚠ Bài học phương pháp (đừng lặp lại)

Tài liệu này từng ghi "Rebirth_Btn_Locked_obj không hề được method nào tham chiếu" và kết luận điều kiện mở khoá là UNKNOWN.

Đó là một lỗi phương pháp. dump.cs chỉ liệt kê khai báo, nó không bao giờ cho thấy nơi một field được đọc/ghi. "Không thấy trong dump.cs" ≠ "không được dùng".

Cách đúng — và là cách đã tìm ra người ghi field đó:

DataRefsTo(<Il2CppClass* của class chủ>)  ->  decompile từng hàm  ->  tìm offset (+0x30 = "+ 48")

Cùng kỹ thuật (CodeRefsTo) là thứ chứng minh Balance_Reload là dead code — một kết luận không thể rút ra từ dump.cs.

6. Raw evidence

  • [phân tích nội bộ] — U1–U4 + B-1…B-13; nguồn của bản đính chính lớn ở §3.2
  • [F001: scene_balance_tables.json] — 8 mảng balance trích từ Main.unity (nguồn sự thật lúc chạy)
  • [evidence codegen: PRESTIGE_RULES.md] — quy tắc gốc (một phần bị FABLE_ANALYSIS supersede: ToPrecision CẮT chứ không tròn; Balance_Reload là dead code)
  • [evidence codegen: Rebirth.c]Rebirth() RVA 0x2B22AE0
  • [evidence codegen: Rebirth_Balance_Reload.c] — RVA 0x2B21CB4
  • [evidence codegen: Rebirth_Reward_return.c] — RVA 0x2B22F18
  • [evidence codegen: Upgrade_Balance_Reload.c] — RVA 0x2B4A02C
  • [evidence codegen: MoneyValue_return.cs.evidence] — vị trí mắt Rebirth trong chuỗi
  • dump.cs:598957 / :598987 / :602928 — khai báo + hằng số
  • [runtime trace: runtime_ranking.json] — tần suất gọi thật
  • [F001: economy_reference_sim.py] — mô hình tham chiếu chạy được (70/70 PASS)
  • [device evidence: user_screenshot_stage5-4.png] — save thật (Stage 5-4, Lv.46), xác nhận Speed_balance[46] = 468
  • [liveops catalog: liveops] — danh mục màn hình + cổng mở khoá (lane riêng, liên kết khi bàn giao)
  • [restore project: Services] — ranh giới R1 fail-closed