Files

250 lines
11 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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%)")