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 + β 缓议
85 lines
2.9 KiB
GDScript
85 lines
2.9 KiB
GDScript
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)
|