Files
game-code-4/tests/unit/test_runner.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

85 lines
2.9 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 SceneTree
# ============================================================================
# test_runner.gd —— core 层无头单测入口(GUT 未装前的自包含方案;W3 换 GUT)
#
# 运行:godot --headless --path . -s res://tests/unit/test_runner.gd
# 退出码:0 = 全过;1 = 有失败;2 = 数据/环境错误。
# 每个用例文件 = tests/unit/cases/*.gd,契约:func run(a) -> void(a = TestAssert)。
# ============================================================================
class TestAssert extends RefCounted:
var failures: Array[String] = []
var checks := 0
func check(cond: bool, msg: String) -> void:
checks += 1
if not cond:
failures.append(msg)
func eq(got: Variant, want: Variant, msg: String) -> void:
checks += 1
if got != want:
failures.append("%s:got %s want %s" % [msg, str(got), str(want)])
func near(got: float, want: float, tol: float, msg: String) -> void:
checks += 1
if absf(float(got) - float(want)) > tol:
failures.append("%s:got %.6g want %.6g(tol %g)" % [msg, got, want, tol])
func true_(cond: bool, msg: String) -> void:
check(cond, msg)
func false_(cond: bool, msg: String) -> void:
check(not cond, msg)
const CASE_FILES: Array[String] = [
"res://tests/unit/cases/test_number_format.gd",
"res://tests/unit/cases/test_economy_formulas.gd",
"res://tests/unit/cases/test_balance_data.gd",
"res://tests/unit/cases/test_economy_state.gd",
# W3 新增:相机 rig 数据行与纯数学 / 事件系统运行时 / 殿王战状态机
"res://tests/unit/cases/test_camera_data.gd",
"res://tests/unit/cases/test_event_director.gd",
"res://tests/unit/cases/test_boss_battle.gd",
# W4 新增:离线契约 v2 + 门 #9 三条子检查(P-8 裁定落地)
"res://tests/unit/cases/test_offline_contract.gd",
]
func _initialize() -> void:
var data := BalanceData.load_from_dir("res://data/build/")
if not data.load_errors.is_empty():
for e in data.load_errors:
printerr("test_runner:数据错误:%s" % e)
quit(2)
return
var total_checks := 0
var total_fail: Array[String] = []
for path in CASE_FILES:
var script: Variant = load(path)
if script == null:
printerr("test_runner:用例缺失 %s" % path)
quit(2)
return
var case_obj: Variant = script.new()
if not (case_obj is RefCounted) or not case_obj.has_method("run"):
printerr("test_runner:用例契约不符 %s" % path)
quit(2)
return
var a := TestAssert.new()
case_obj.run(a, data)
total_checks += a.checks
for f in a.failures:
total_fail.append("%s → %s" % [path.get_file(), f])
print("test_runner:%s:%d 检查,%d 失败" % [path.get_file(), a.checks, a.failures.size()])
if total_fail.is_empty():
print("test_runner:PASS(%d 检查全过)" % total_checks)
quit(0)
return
print("test_runner:FAIL(%d/%d)" % [total_fail.size(), total_checks])
for f in total_fail:
printerr(" ✗ %s" % f)
quit(1)