本机首次具备引擎自验证能力后的首跑修复。四条命令 + 主场景 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 待文策渊裁定)
112 lines
5.1 KiB
GDScript
112 lines
5.1 KiB
GDScript
extends RefCounted
|
||
|
||
# 用例:W3 相机 rig 数据行 + CameraDef 纯数学(camera.csv / balance_tuning.csv)
|
||
# 说明:这些都是纯函数/静态函数,故可在无头 SceneTree 下直接跑,不需要 Godot 渲染。
|
||
|
||
func run(a, data: BalanceData) -> void:
|
||
_test_rows(a, data)
|
||
_test_geometry(a, data)
|
||
_test_kinematics(a, data)
|
||
|
||
|
||
# ---- 数据行:每殿恰一行 + 参数合法 ----
|
||
func _test_rows(a, data: BalanceData) -> void:
|
||
a.eq(data.cameras.size(), 10, "camera.csv 十行(每殿一行)")
|
||
for h in data.halls:
|
||
var c := data.camera_for_hall(h.id)
|
||
a.true_(c != null, "殿 %s 有 camera 行" % h.id)
|
||
if c == null:
|
||
continue
|
||
a.true_(c.band_screens >= 1.0 and c.band_screens <= 6.0,
|
||
"%s.band_screens 在 [1,6]" % h.id)
|
||
a.true_(c.arena_zoom > c.idle_zoom, "%s:殿王战推近缩放 > 巡游缩放" % h.id)
|
||
a.true_(c.layer_far_scroll < c.layer_mid_scroll,
|
||
"%s:远景比中景慢" % h.id)
|
||
a.true_(c.layer_near_scroll <= c.layer_fore_scroll,
|
||
"%s:前景不慢于近景" % h.id)
|
||
# 全局标量已进 balance_tuning(缺失会被 tune_num 记 load_errors,此处兜底断言)
|
||
a.true_(data.has_tune("tune_cam_peek_max_px"), "tune_cam_peek_max_px 存在")
|
||
a.true_(data.has_tune("tune_cam_peek_spring"), "tune_cam_peek_spring 存在")
|
||
a.true_(data.has_tune("tune_cam_peek_damp"), "tune_cam_peek_damp 存在")
|
||
a.true_(data.has_tune("tune_cam_follow_tau"), "tune_cam_follow_tau 存在")
|
||
a.true_(data.has_tune("tune_cam_zoom_tau"), "tune_cam_zoom_tau 存在")
|
||
a.true_(data.has_tune("tune_boss_preview_min"), "tune_boss_preview_min 存在")
|
||
|
||
|
||
# ---- 几何:band 排布 + 进度焦点锚定 ----
|
||
func _test_geometry(a, data: BalanceData) -> void:
|
||
var cams: Array = []
|
||
for h in data.halls:
|
||
cams.append(data.camera_for_hall(h.id))
|
||
var rng := BandWorldRoot.compute_ranges(cams)
|
||
a.eq(rng.size(), 10, "ranges 十段")
|
||
a.near(float(rng[0].x), 0.0, 1e-9, "首段左端 = 0")
|
||
var total := 0.0
|
||
for c in cams:
|
||
total += (c as CameraDef).band_width()
|
||
a.near(float(rng[9].y), total, 1e-6, "末段右端 = Σ band 宽度")
|
||
|
||
var w0 := float(rng[0].y) - float(rng[0].x)
|
||
a.near(BandWorldRoot.front_x_of(rng, 0, 0.0), 0.0, 1e-9, "H1 进度 0 → 段首")
|
||
a.near(BandWorldRoot.front_x_of(rng, 0, 0.5), w0 * 0.5, 1e-6, "H1 进度 0.5 → 段中")
|
||
a.near(BandWorldRoot.front_x_of(rng, 0, 1.0), w0, 1e-6, "H1 进度 1.0 → 段尾")
|
||
# 越界夹逼(保底:不会算出段外坐标)
|
||
a.near(BandWorldRoot.front_x_of(rng, 99, 0.5), float(rng[9].x) + (
|
||
float(rng[9].y) - float(rng[9].x)) * 0.5, 1e-6, "殿序越界夹到末段")
|
||
a.near(BandWorldRoot.front_x_of(rng, -5, 0.5), w0 * 0.5, 1e-6, "负序夹到首段")
|
||
|
||
# arena 锚点在段内偏殿门一侧
|
||
var anchor := BandWorldRoot.arena_anchor_x(rng, 0)
|
||
a.true_(anchor > 0.0 and anchor < w0, "arena 锚点落在段内")
|
||
a.near(anchor, w0 * 0.65, 1e-6, "arena 锚点 = 段内 65%(构图常量)")
|
||
|
||
# 焦点锚定:focus=0.66 时相机比 front 靠左 0.16 屏宽
|
||
var cam_x := CameraDef.camera_x_for_front(1000.0, 1.0, 0.66, 1280.0)
|
||
a.near(cam_x, 1000.0 - 1280.0 * 0.16, 1e-6, "焦点 0.66 → 相机左移 0.16 屏")
|
||
var cam_x2 := CameraDef.camera_x_for_front(1000.0, 2.0, 0.66, 1280.0)
|
||
a.near(cam_x2, 1000.0 - 1280.0 * 0.16 / 2.0, 1e-6, "缩放 2× 时位移减半")
|
||
|
||
# 视口宽度与夹逼
|
||
a.near(CameraDef.view_width(2.0, 1280.0), 640.0, 1e-9, "缩放 2× → 可视宽 640")
|
||
a.near(CameraDef.clamp_camera_x(100.0, 1.0, 400.0, 1280.0), 200.0, 1e-9,
|
||
"世界比视口窄 → 居中")
|
||
a.near(CameraDef.clamp_camera_x(-9999.0, 1.0, 5000.0, 1280.0), 640.0, 1e-9,
|
||
"左侧越界夹到半屏")
|
||
a.near(CameraDef.clamp_camera_x(9999.0, 1.0, 5000.0, 1280.0), 4360.0, 1e-9,
|
||
"右侧越界夹到 world_w - 半屏")
|
||
|
||
# 确定性剪影布局(同一数据行永远同一张图)
|
||
a.near(HallBandWorld.silhouette_x(0, 1000.0), 1000.0 / 6.0, 1e-6, "剪影 0 位置")
|
||
a.near(HallBandWorld.silhouette_x(4, 1000.0), 1000.0 / 6.0 * 5.0, 1e-6, "剪影 4 位置")
|
||
|
||
|
||
# ---- 运动学:弹簧回弹 / 指数平滑 / 偷看夹逼 ----
|
||
func _test_kinematics(a, data: BalanceData) -> void:
|
||
var k := data.tune_num("tune_cam_peek_spring", 42.0)
|
||
var c := data.tune_num("tune_cam_peek_damp", 11.0)
|
||
var max_px := data.tune_num("tune_cam_peek_max_px", 320.0)
|
||
|
||
# 偷看夹逼
|
||
a.near(CameraDef.clamp_peek(9999.0, max_px), max_px, 1e-9, "偷看上限夹逼")
|
||
a.near(CameraDef.clamp_peek(-9999.0, max_px), -max_px, 1e-9, "偷看下限夹逼")
|
||
|
||
# 弹簧:给 200px 初位移,2 秒后应回到 ~0(欠阻尼也收敛)
|
||
var x := 200.0
|
||
var v := 0.0
|
||
for _i in 120:
|
||
var s := CameraDef.spring_step(x, v, 1.0 / 60.0, k, c)
|
||
x = s.x
|
||
v = s.y
|
||
a.true_(absf(x) < 1.0, "松手 2s 后偷看位移回到 ≈0(got %.4f)" % x)
|
||
a.true_(absf(v) < 5.0, "速度同时收敛(got %.4f)" % v)
|
||
|
||
# 指数平滑:tau=0.35 → 一步后应走到目标的一部分且单调
|
||
var y := 0.0
|
||
var prev := 0.0
|
||
for _i in 5:
|
||
y = CameraDef.exp_smooth(y, 100.0, 0.1, 0.35)
|
||
a.true_(y >= prev, "平滑单调不回头")
|
||
prev = y
|
||
a.true_(y > 20.0 and y < 100.0, "0.5s 后走到目标的 20%%-100%% 之间(got %.2f)" % y)
|
||
a.near(CameraDef.exp_smooth(0.0, 100.0, 0.1, 0.0), 100.0, 1e-9, "tau=0 → 瞬时")
|