Files
game-code-4/tests/unit/cases/test_economy_state.gd
T
程基岩(engineering-lead) c534e95458 fix: 首跑错误批次(Godot 4.7.2 引擎实证修复 R1-R14)
本机首次具备引擎自验证能力后的首跑修复。四条命令 + 主场景 180 帧全绿:
- --import exit=0(漂移门 git diff data/build 空)
- --validate-only exit=0 RESULT: PASS
- test_runner exit=0 PASS(584 检查,8/8 用例)
- sim_main exit=0(报告落 tests/sim_reports/report-2026-10-20.json)
- 主场景 --quit-after 180 exit=0 零 ERROR/WARNING(F5 可玩)

预定位错误(team-lead 清单):
- R1 economy_state.gd:596 enthone_ready -> enthrone_ready(单点故障连锁)
- R2 hud_root.gd:142,160 HorizontalAlignment.LEFT -> HORIZONTAL_ALIGNMENT_LEFT
- R3 boss_arena.gd 补 var _pending_hall_index := -1
- R4 hall_band_world.gd:95 删 "_ = ext"(非法语句;--check-only 定位)
- R5 sheet_importer._type_ok 补 object->Dictionary / array->Array(9 条假违规)
- R6 game_state.gd econ==null 防护(防 autoload 崩每帧刷错打满管道挂死)

引擎新暴露错误:
- R7 prestige_model floor() Variant 推断(4.7.2 按错误)连锁编译失败
- R8 gates.gd var pass 保留字;R9 %g/%.*f 格式符 ×6 处
- R10 greedy.gd Dictionary.get() 推断;R11 probe_p1 条件表达式推断
- R12 测试用例 data 参数统一标 BalanceData(无类型参数成员调用返 Variant)
- R13 boss_battle 用例 Dictionary 下标运算显式 float 化
- R14 band_world_root.compute_ranges 返回 Array[Vector2](类型化数组不隐式转换)

测试侧数值修正(根因已核实,实现符合设计):
- gen3 可购断言 merit 1000->2000(800 文书 + 1200 首购)
- 开场伤害断言改"循环至 ACTIVE 即停"(INTRO 结束 tick 不计军列伤)
- 容量断言预算 30->34s(战斗真实给 intro 3s + active 30s)

工程加固:
- EconomyState 红线守卫首次越线才报(sim 长跑日志 6.5MB->1KB)
- stage_world.gd stretch=false(SubViewport 手动尺寸驱动生效)

知识缺口登记:docs/engineering/2026-10-20-f-engine-first-run-notes.md
sim 首轮数据:主口径 11/14 过(#2/#3/#12/#13/#14 FAIL 待文策渊裁定)
2026-10-20 18:47:07 +08:00

169 lines
6.8 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: 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_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")
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: 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, "阴德清零")
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: 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×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: 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%)")