extends RefCounted # 用例:EconomyState 状态机(点击/购买/殿门/冤气/prestige/敕封) func run(a, data: BalanceData) -> void: _test_first_purchase(a, data) _test_hall_gate(a, data) _test_upgrades_and_meta(a, data) _test_wrath(a, data) _test_prestige(a, data) _test_suihui_asymptotic(a, data) _test_skip_cap(a, data) _test_enthrone(a, data) _test_event_and_offline(a, data) # ---- 首购循环:15 点击 → 买 gen1 → 产出 0.1/s(GDD demo 验证点)---- func _test_first_purchase(a, data: BalanceData) -> void: var e := EconomyState.new(data) a.near(e.merit, 0.0, 1e-9, "初始阴德 0") a.near(e.output_per_sec(), 0.0, 1e-9, "初始产出 0") for _i in 15: e.click() a.near(e.merit, 15.0, 1e-9, "15 次点击 = 15 阴德") a.near(e.click_value(), 1.0, 1e-9, "点击价值 = 1") var r: Dictionary = e.buy_generator(0, 1) a.true_(bool(r.get("ok", false)), "15 阴德可买游魂×1") a.eq(e.gen_levels[0], 1, "游魂 1 级") a.near(e.merit, 0.0, 1e-9, "买后阴德 0") a.near(e.output_per_sec(), 0.1, 1e-9, "产出 0.1/s") var r2: Dictionary = e.buy_generator(0, 1) a.true_(not bool(r2.get("ok", false)), "0 阴德再买失败") e.merit = 100.0 var r3: Dictionary = e.buy_upgrade("up_click_1") a.true_(bool(r3.get("ok", false)), "买指诀·倍力 I(50)") a.near(e.click_value(), 2.0, 1e-9, "点击价值 ×2") # ---- 殿门单计数器闸门 ---- func _test_hall_gate(a, data: BalanceData) -> void: var e := EconomyState.new(data) a.false_(e.generator_unlocked(2), "gen3 初始锁定") e.merit_run_total = 1999.0 e.tick(0.1) a.eq(e.halls_passed, 0, "1999 不过 H1") e.merit_run_total = 2000.0 e.tick(0.1) a.eq(e.halls_passed, 1, "2000 过 H1(单计数器闸门)") a.true_(e.upgrade_visible(data.upgrade("up_eff_gen_03_guizu_1")), "H1 后 gen3 效率可见") a.true_(e.upgrade_visible(data.upgrade("up_gate_guizu")), "H1 后入职文书可见") a.false_(e.generator_unlocked(2), "过殿但未买文书 → gen3 仍锁") e.merit = 2000.0 # 覆盖入职文书 800 + gen3 首购 1200(generators.csv base_cost) var r: Dictionary = e.buy_upgrade("up_gate_guizu") a.true_(bool(r.get("ok", false)), "买鬼卒班入职文书(800)") a.true_(e.generator_unlocked(2), "双闸齐 → gen3 解锁") var r2: Dictionary = e.buy_generator(2, 1) a.true_(bool(r2.get("ok", false)), "gen3 可购") # ---- 升级/宿慧商店 ---- func _test_upgrades_and_meta(a, data: BalanceData) -> void: var e := EconomyState.new(data) e.suihui = 10 var r: Dictionary = e.buy_meta("meta_01") a.true_(bool(r.get("ok", false)), "买枕旧梦(3 宿慧)") a.eq(e.suihui, 7, "扣 3 宿慧") a.eq(int(e.meta_ranks.get("meta_01", 0)), 1, "meta_01 阶 1") var r2: Dictionary = e.buy_meta("meta_01") a.true_(not bool(r2.get("ok", false)), "meta_01 一阶限购") e.suihui = 100 var r3: Dictionary = e.buy_meta("meta_03") a.true_(bool(r3.get("ok", false)), "买宿慧通透(8)") # 效率升级进班底区 e.merit = 100000.0 e.gen_levels[0] = 10 var r4: Dictionary = e.buy_upgrade("up_eff_gen_01_youhun_1") a.true_(bool(r4.get("ok", false)), "买游魂操练 I(83)") a.near(e.gen_mult(0), 2.0, 1e-9, "班底区 ×2(+100%)") # ---- 冤气线 ---- func _test_wrath(a, data: BalanceData) -> void: var e := EconomyState.new(data) e.devour(100.0) a.near(e.wrath, 100.0, 1e-9, "吞噬 +100 冤气") a.eq(e.devour_count, 1, "吞噬计数") a.eq(e.wrath_layer_cost(), 8, "第 1 层成本 8(wrath_layers.csv 逐行)") var r: Dictionary = e.buy_wrath_layer() a.true_(bool(r.get("ok", false)), "买第 1 层怨煞") a.eq(e.wrath_layers, 1, "1 层") # W5 裁定 P-25:每层全局 +25% → +15%(合计 15 层后封顶 +225%) a.near(e.global_mult(), 1.15, 1e-9, "全局 ×1.15(每层 +15%)") # W5 裁定 P-26:宿慧削减 +3%/层 → +4%/层(封顶 40%) a.near(e.wrath_penalty(), 0.04, 1e-9, "宿慧削减 4%") e.wrath = 0.0 var r2: Dictionary = e.buy_wrath_layer() a.true_(not bool(r2.get("ok", false)), "冤气不足买不了") e.wrath = 1e6 for _i in 21: e.buy_wrath_layer() a.eq(e.wrath_layers, 22, "22 层上限") var r3: Dictionary = e.buy_wrath_layer() a.true_(not bool(r3.get("ok", false)), "满层后再买失败") # W5:L1-15 各 +15%(合计 +225% = ×3.25);L16-22 effect = 0(过盛不顶用) a.near(e.global_mult(), 1.0 + 15.0 * 0.15, 1e-9, "满层全局 ×3.25(15 层后归零)") a.near(e.wrath_penalty(), 0.4, 1e-9, "22 层削减封顶 40%") # 成本列逐行 = round(8×n^1.8)(表即公式落盘;抽检首/中/末) a.eq(e.wrath_layer_cost(), 2087, "满层后下一层成本回退末行 2087") # ---- 轮回转世 ---- func _test_prestige(a, data: BalanceData) -> void: var e := EconomyState.new(data) a.false_(e.prestige_available(), "初始不可轮回") e.merit_run_total = 1.5e9 a.true_(e.prestige_available(), "H10 门达成 → 可轮回") a.eq(e.suihui_gain(), 193, "1.5B → 193 宿慧") e.gen_levels[0] = 50 e.merit = 123.0 var r: Dictionary = e.do_prestige() a.true_(bool(r.get("ok", false)), "执行轮回") a.eq(e.suihui, 193, "宿慧入账") a.eq(e.rebirths, 1, "转世 1 次") a.near(e.merit, 0.0, 1e-9, "阴德清零") # W5 裁定 F-2:跳过每档封顶 50 级(193/5=38 次 ×25 = 950 → 钳到 50; # 注意 do_prestige 先清零班底([B§4.1] 全清)再加跳过,故原有 50 级不计入) a.eq(e.gen_levels[0], 50, "跳过封顶:193 宿慧 → 50 级/档(旧口径 950)") a.eq(e.halls_passed, 0, "殿门进度重置") a.eq(e.bought.size(), 0, "升级清空") # 第二轮更快(宿慧全局渐近加成,F-1) var smax: float = data.tune_num("tune_suihui_global_max", 0.5) var sscale: float = data.tune_num("tune_suihui_global_scale", 420.0) a.near(e.global_mult(), 1.0 + smax * (1.0 - exp(-193.0 / sscale)), 1e-9, "宿慧全局渐近加成(193 → +18.4%)") # ---- 宿慧全局渐近封顶(W5 裁定 F-1:线性无界 → 0.50×(1−e^(−宿慧/420)))---- func _test_suihui_asymptotic(a, data: BalanceData) -> void: var e := EconomyState.new(data) var smax: float = data.tune_num("tune_suihui_global_max", 0.5) var sscale: float = data.tune_num("tune_suihui_global_scale", 420.0) e.suihui = 0 e.recompute() a.near(e.global_mult(), 1.0, 1e-9, "0 宿慧 → 无加成") # 裁定 §2.4 查点表:193→+18.4%;386→+30.1%;579→+37.4%;772→+42.0% for s in [193, 386, 579, 772, 1550, 5000]: e.suihui = s e.recompute() var want: float = 1.0 + smax * (1.0 - exp(-float(s) / sscale)) a.near(e.global_mult(), want, 1e-9, "宿慧 %d → 加成 %.4f" % [s, want - 1.0]) # 渐近上限:宿慧 → ∞ 也只到 +50%(封死"银行式刷宿慧"死亡螺旋) e.suihui = 100000000 e.recompute() a.near(e.global_mult(), 1.0 + smax, 1e-6, "巨量宿慧仍封顶 +50%") # 单调不减(渐近函数的基本性质) var prev := 0.0 for s in [0, 10, 50, 100, 193, 386, 579, 772, 1550, 5000]: e.suihui = s e.recompute() var gm := e.global_mult() a.true_(gm >= prev - 1e-12, "宿慧加成单调不减 @%d" % s) prev = gm # ---- 跳过机制钳制(W5 裁定 F-2:每档 min(floor(宿慧/5)×25, 50))---- func _test_skip_cap(a, data: BalanceData) -> void: var cap: int = int(data.tune_num("tune_skip_level_cap_per_gen", 50.0)) a.eq(cap, 50, "调参:每档封顶 50 级") var e := EconomyState.new(data) # 公式直测:_apply_rebirth_start_levels 是纯应用函数。走 do_prestige 测不了 # 低宿慧区——H10 门 1.5e9 决定每轮 gain ≥ 193(38 次触发必触顶)。 _apply_skip(e, 5) a.eq(e.gen_levels[0], 25, "5 宿慧 → 25 级/档(未触顶)") _apply_skip(e, 10) a.eq(e.gen_levels[0], 50, "10 宿慧 → 50 级/档(恰触顶)") _apply_skip(e, 400) a.eq(e.gen_levels[0], 50, "400 宿慧 → 仍 50 级/档(钳制生效)") # 三档覆盖:前 3 档各 +50(skip_gens = 3) a.eq(e.gen_levels[1], 50, "第 2 档同获赠") a.eq(e.gen_levels[2], 50, "第 3 档同获赠") a.eq(e.gen_levels[3], 0, "第 4 档不受跳过覆盖") # 集成:do_prestige 后(gain 193 入账)R2 开盘 = 50 级/档 e.merit_run_total = 1.5e9 var r: Dictionary = e.do_prestige() a.true_(bool(r.get("ok", false)), "轮回(集成)") a.eq(e.gen_levels[0], 50, "do_prestige 后 50 级/档(钳制生效;旧口径 950)") # m04(间隔 5→4):400/4=100 次 ×25 = 2500 → 同样钳到 50 var rm: Dictionary = e.buy_meta("meta_04") a.true_(bool(rm.get("ok", false)), "买 m04 天生熟练(间隔 4)") _apply_skip(e, 400) a.eq(e.gen_levels[0], 50, "m04 下仍钳到 50 级/档") ## 测试辅助:清零班底级数 → 设宿慧 → 应用跳过/开局加成(_apply 为加法,须先清零) func _apply_skip(e: EconomyState, suihui_v: int) -> void: for i in e.gen_levels.size(): e.gen_levels[i] = 0 e.suihui = suihui_v e._apply_rebirth_start_levels() # ---- 阴司敕封 + gen12 愿景档 ---- func _test_enthrone(a, data: BalanceData) -> void: var e := EconomyState.new(data) e.rebirths = 2 e.suihui = 100 a.true_(e.enthrone_ready(), "转世 2 + 宿慧 100 → 敕封就绪") e.merit_run_total = 1.5e9 e.merit = 1e12 var r: Dictionary = e.buy_upgrade("up_gate_tianzi") a.true_(bool(r.get("ok", false)), "买敕封诏书(800B)") a.true_(e.enthrone_available(), "受敕可用") a.false_(e.generator_unlocked(11), "未受敕 → gen12 仍锁(P-12)") var r2: Dictionary = e.do_enthrone() a.true_(bool(r2.get("ok", false)), "执行受敕") a.true_(e.enthroned, "enthroned 旗标") a.true_(e.generator_unlocked(11), "受敕 → gen12 解锁") # 全局区 = 1 + 宿慧100 渐近加成 + 敕封 25%(W5 F-1:宿慧项改渐近公式) var smax: float = data.tune_num("tune_suihui_global_max", 0.5) var sscale: float = data.tune_num("tune_suihui_global_scale", 420.0) var want_global: float = 1.0 + smax * (1.0 - exp(-100.0 / sscale)) + 0.25 a.near(e.global_mult(), want_global, 1e-9, "全局 = 1 + 宿慧渐近 + 敕封 25%(≈×1.356)") # gen12 产出接入 e.gen_levels[11] = 1 a.near(e.output_per_sec(), 1073741824.0 * want_global, 1.0, "gen12 1 级产出") # ---- 事件瞬时区 + 离线加法(契约 v2:长明灯 ×2.31 → 加法 +30%) ---- func _test_event_and_offline(a, data: BalanceData) -> void: var e := EconomyState.new(data) e.gen_levels[0] = 10 var base_out := e.output_per_sec() e.set_event_mult(5.0) a.near(e.output_per_sec(), base_out * 5.0, 1e-9, "事件 ×5 瞬时区") e.set_event_mult(1.0) a.near(e.offline_add(), 0.0, 1e-9, "无长明灯 → 加法 +0") e.halls_passed = 4 # 离线升级出现条件 e.merit = 1e9 var r: Dictionary = e.buy_upgrade("up_offline_1") a.true_(bool(r.get("ok", false)), "买长明灯 I") a.near(e.offline_add(), 0.05, 1e-9, "长明灯 I → 加法 +5%(旧 ×1.15 乘性已废止)") for i in range(2, 7): var ri: Dictionary = e.buy_upgrade("up_offline_%d" % i) a.true_(bool(ri.get("ok", false)), "买长明灯 %d" % i) a.near(e.offline_add(), 0.30, 1e-9, "长明灯 6 级满 = 加法 +30%(各档速率硬顶 100%)") # m05 阴司人脉(每阶 +25% 加法,max_rank 1) e.suihui = 100 var rm: Dictionary = e.buy_meta("meta_05") a.true_(bool(rm.get("ok", false)), "买阴司人脉") a.near(e.offline_add(), 0.55, 1e-9, "+ m05 = 加法 +55%(仍由 OfflineModel 钳各档 ≤100%)")