Files
game-code-4/tests/unit/cases/test_economy_state.gd
T
程基岩(engineering-lead) 08dc5020c3 W4 批次:三项设计裁定落地(Boss 战 Plan B / 离线契约 v2 / 长明灯加法化)
Boss 战修复(Plan B,用户拍板):
- HP 公式 15xPxhall_factor -> HP = P x boss_army_seconds_n(S=23.0→28.0 递增,
  每场军列约 23-28 秒清空,战斗时长成为可直接书写的设计旋钮,结构性消除
  高殿 P 抵消导致不可胜的旧 bug);hall_factor 降级为纯叙事/视觉参数
- 新增 souls_par 列(0→90000):开场冲锋 intro_dmg = HP x 10% x clamp(souls/souls_par,0,1),
  上限 20%->10%;删 hp_factor/intro_dmg_souls_factor/intro_dmg_cap_ratio 三列
- 点击伤害 P x κ x combo_mult,κ = (0.010 + 0.052 x click_ratio) x (1 + 0.30 x m06_owned),
  click_ratio = 已购点击升级 / 12(κ 0.010→0.0806,满配点击占比约 20.5%±3pp)
- 防双重计入:L5-12 点击升级 +1% output/s 留全局区(抬 P 同时抬军列 DPS 与点击
  伤害,可接受),不得再计入 κ 分子(recompute 注释固化)

离线契约 v2(P-8,改契约不改表):
- OfflineModel.settle 第三参 offline_mult -> offline_add(加法);各档效率
  min(rate+add, 1.0) 硬顶 100%,离线永不超在线
- 长明灯 x1.15 乘性 -> +5%/级 加法(满级 +30%,各档封顶 100%)
- 0-2h 100% 保持;8h = 6.2 等效小时(77.5% 效率)
- Gate #9 拆 9a/9b/9c:9a FAIL->PASS(0-2h 全额 + 8h∈[5.5,6.5]),
  9b 长明灯满级 8h∈[7.5,8.0] 且 ≤100%,9c 任意配置效率 ≤100%

数据与工具:
- gen_balance_sheets.py 补登 W3 遗留 7 行 tune(防重跑静默丢失),self_check 更新
- boss_battles/upgrades schema 同步(offline_mult->offline_add enum,漏改会违反导入)

测试(runner 7->8 用例,Node 交叉验证 145 断言全过):
- 可胜性哨兵重写为「十殿全可胜」:每殿 S_n<30、军列+点击≥HP、余时∈[2,8]、
  满配余时差≥4、点击占比∈[15,25]、H10≈20.5%±3pp
- κ 四档断言(0.010/0.036/0.062/0.0806,分母 12)
- 新增 test_offline_contract.gd:契约 v2 数值面 + 9a/9b/9c 全 PASS + β 缓议
2026-10-20 17:01:25 +08:00

169 lines
6.7 KiB
GDScript
Raw 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) -> 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_enthrone(a, data)
_test_event_and_offline(a, data)
# ---- 首购循环:15 点击 → 买 gen1 → 产出 0.1/s(GDD demo 验证点)----
func _test_first_purchase(a, data) -> 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) -> 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 = 1000.0
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) -> 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) -> 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")
var r: Dictionary = e.buy_wrath_layer()
a.true_(bool(r.get("ok", false)), "买第 1 层怨煞")
a.eq(e.wrath_layers, 1, "1 层")
a.near(e.global_mult(), 1.25, 1e-9, "全局 ×1.25(每层 +25%)")
a.near(e.wrath_penalty(), 0.03, 1e-9, "宿慧削减 3%")
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)), "满层后再买失败")
# ---- 轮回转世 ----
func _test_prestige(a, data) -> 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, "阴德清零")
a.eq(e.gen_levels[0], 950, "跳过机制:193/5=38 ×25 = 950 级")
a.eq(e.halls_passed, 0, "殿门进度重置")
a.eq(e.bought.size(), 0, "升级清空")
# 第二轮更快(宿慧全局 +3%)
a.near(e.global_mult(), 1.0 + 193 * 0.03, 1e-9, "宿慧全局加成")
# ---- 阴司敕封 + gen12 愿景档 ----
func _test_enthrone(a, data) -> 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×3% + 敕封 25% = 4.25
a.near(e.global_mult(), 4.25, 1e-9, "全局 ×4.25(宿慧+敕封)")
# gen12 产出接入
e.gen_levels[11] = 1
a.near(e.output_per_sec(), 1073741824.0 * 4.25, 1.0, "gen12 1 级产出")
# ---- 事件瞬时区 + 离线加法(契约 v2:长明灯 ×2.31 → 加法 +30%) ----
func _test_event_and_offline(a, data) -> 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%)")