本机首次具备引擎自验证能力后的首跑修复。四条命令 + 主场景 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 待文策渊裁定)
13 KiB
F《怨返》引擎首跑记录(Godot 4.7.2)——错误批次修复 + 4.7 API 行为实证
撰写:程基岩(engineering-lead)| 日期:2026-10-20 | 里程碑:本机首次具备引擎自验证能力 环境:
D:\Godot\Godot.exe= Godot 4.7.2.stable.official.ed1daf0bf(便携版,sha256 与官方 digest 一致) 上游:docs/architecture/2026-10-19-F-architecture.md§7.1(引擎钉定)|本文件 = 架构要求的「4.7.x 知识缺口」登记簿 纪律:一切结论来自本机实证(报错原文 + 退出码),不引用训练记忆;训练数据里没有的一律记录在此。
0. 结论速览
命令(--headless --path D:\wrath-of-the-dead) |
结果 |
|---|---|
--import |
✅ exit=0;漂移门 git diff --exit-code -- data/build/ drift_exit=0(canonical JSON 与提交一致) |
-s res://src/tools/import_main.gd -- --validate-only |
✅ exit=0,RESULT: PASS(build 目录全部合规) |
-s res://tests/unit/test_runner.gd |
✅ exit=0,PASS(584 检查全过),8/8 用例 |
-s res://src/core/sim/sim_main.gd |
✅ exit=0,报告落 tests/sim_reports/report-2026-10-20.json |
--quit-after 180(主场景 stage_root.tscn) |
✅ exit=0,零 ERROR 零 WARNING(F5 可玩) |
W2-W4 全部 GDScript 首次经引擎解析/执行验证。 首跑共修复 6 个团队已定位错误(R1-R6)+ 8 个引擎新暴露错误(R7-R14,下文编号延续),全部有实证记录。
1. --check-only 在 4.7.2 的确切行为(R4 定位手段)
D:\Godot\Godot.exe --headless --path . --check-only -s res://<脚本>.gd
| 场景 | 输出 | 退出码 |
|---|---|---|
| 脚本干净 | 仅引擎 banner,无任何 SCRIPT ERROR | 0 |
| 有解析错误 | SCRIPT ERROR: Parse Error: <原因> + at: GDScript::reload (res://...:行号) + ERROR: Failed to load script ... with error "Parse error". + at: load (modules/gdscript/gdscript_resource_format.cpp:46) |
1 |
关键价值:无行号的 "Could not parse global class X"(从引用方报出)一律可用 --check-only -s 直接加载该脚本拿到精确行号——R4(hall_band_world.gd)即此法定位(L95)。建议 CI 增加逐文件 parse 门:对 src/**/*.gd 跑 --check-only -s,exit≠0 即红(比 --validate-only 更早暴露语法债)。
另:全量加载扫描可用一次性 SceneTree 脚本遍历 DirAccess + load() + GDScript.can_instantiate()(首跑修复期的临时工具,已删);注意 autoload 编译失败时 load() 返回非 null 但 can_instantiate()=false、.new() 报 Nonexistent function 'new' in base 'GDScript'——判活用 can_instantiate(),不要用 == null。
2. 4.7.2 API 行为实证(训练数据没有/与记忆不符的)
2.1 var x := <Variant 表达式> 直接是解析错误(最大的一坑,涉及 12 处)
- 报错:
Parse Error: Cannot infer the type of "x" variable because the value doesn't have a set type./The variable type is being inferred from a Variant value, so it will be typed as Variant. (Warning treated as error.) - project.godot 未开 warnings-as-errors——4.7.2 把
INFERENCE_ON_VARIANT默认按错误处理(与 4.x 早期记忆不符,实证为准)。 - 触发源(Variant 返回值家族,全部实证):
- 数学 builtin:
floor / ceil / round / pow / min / max / clamp / lerp / abs / sign …(4.x 起支持 Vector 分量运算故返回 Variant) Dictionary.get()、数组下标arr[i]、条件表达式含上述任一项- 无类型函数参数的
data成员调用:func f(a, data)里data.tune_num(...)返回 Variant——这是测试文件大规模中雷的根因(见 §3 R12)
- 数学 builtin:
- 修法:显式标注
var x: float = …/var x: Type = …;或给参数标类型(data: BalanceData)。
2.2 String % 格式化不支持 C 风格符
%g、%.3g、%.*f→ERROR: String formatting error: unsupported format character.(validated_evaluate)- 可用:
%d %i %o %x %X %f(含%.2f精度)、%s、%% - 大数展示走项目自己的
NumberFormat.short();科学计数走BigNumber.sci()(已改为String.num(mant, digits))
2.3 保留字与语句
pass是语句保留字:var pass := …→Parse Error: Expected variable name after "var".(gates.gd 曾用var pass)_ = expr不是合法语句:Parse Error: Expected statement, found "_" instead.("丢弃变量"的 Rust/C++ 习惯在 GDScript 不存在——直接删掉该行或让变量参与计算)
2.4 枚举名
HorizontalAlignment.LEFT→Cannot find member "LEFT" in base "HorizontalAlignment";正确写法是全局枚举HORIZONTAL_ALIGNMENT_LEFT(Button.alignment 等)。同族:VERTICAL_ALIGNMENT_*。
2.5 类型化数组不隐式转换(运行时错误)
var ranges: Array[Vector2]← 赋无类型Array→ 运行时SCRIPT ERROR: Trying to assign an array of type "Array" to a variable of type "Array[Vector2]".- 静态函数返回值类型必须与接收方字段标注一致(
compute_ranges() -> Array[Vector2])
2.6 SubViewport / SubViewportContainer
- 容器
stretch = true时禁止手动改SubViewport.size:WARNING: Can't change the size of a SubViewport with a SubViewportContainer parent that has stretch enabled.(且尺寸不生效)。要手动驱动尺寸就把stretch = false。
2.7 -s + SceneTree 入口模式(4.7.2 仍稳定)
extends SceneTree+_initialize()+quit(code)全部按预期工作;--quit-after N(帧数)可用于无头跑主场景验证 F5 路径。- 挂死警示:autoload 每帧
_process报错时,stderr 洪水可打满重定向管道 → 进程挂死(首跑 test_runner 240s 超时即此)。GameState 已加econ == null防护;长跑 headless 一律重定向到文件。
2.8 控制台编码( cosmetic )
- Godot 控制台输出按 Windows OEM 代码页(936)写文件时中文变乱码(
姝e湪鎵弿…)。不影响功能;日志判读用退出码 + 英文行,中文细节以 JSON 报告为准。
2.9 schema 校验器(工程 bug,非引擎)
SheetImporter._type_ok()缺"object"/"array"分支 → 9 张 build JSON 全报假违规类型期望 object,得到 Dictionary。JSON object 在 GDScript 是 Dictionary(不是 Object)。已补两分支;push_error(v)本身无需str()(violations 确实是Array[String],那条 ERROR 只是把违规消息当日志打)。
3. 修复清单(R1-R14)
| # | 文件:行 | 现象 | 修法 |
|---|---|---|---|
| R1 | core/economy/economy_state.gd:596 |
enthone_ready() 拼错 → economy_state 整体编译失败,连锁炸 game_state(每帧 tick(null))与 sim(SimGates 不可解析) |
→ enthrone_ready() |
| R2 | presentation/hud/hud_root.gd:142,160 |
HorizontalAlignment.LEFT |
→ HORIZONTAL_ALIGNMENT_LEFT ×2 |
| R3 | presentation/stage/boss_arena.gd var 区 |
_pending_hall_index 未声明 |
+ var _pending_hall_index := -1 |
| R4 | presentation/stage/hall_band_world.gd:95 |
_ = ext 非法语句(无行号,--check-only 定位) |
删 _ = ext;ext 改参与中景剪影跨度(复用被测的 silhouette_x,整体平移 -ext),顺带消除视差露缝 |
| R5 | tools/sheet_importer.gd:_type_ok |
9 条假违规(§2.9) | + "object"→Dictionary、"array"→Array 分支;import_main.gd:49 不动(元素本就是 String) |
| R6 | game/game_state.gd _ready/_process |
autoload 崩 → 每帧刷错 → 管道打满挂死 | + econ == null 早退防护(_ready 里创建失败也 push_error 后 return) |
| R7 | core/economy/prestige_model.gd:26-27 |
floor() Variant 推断错误 → economy_state/game_state「Failed to compile depended scripts」+ 运行时 Nonexistent function 'wrath_penalty'… 一串 |
var raw: float = … / var adjusted: float = … |
| R8 | core/sim/gates.gd:255 |
var pass := 保留字 |
→ var ok_all := |
| R9 | core/numbers/big_number.gd:23,34,36;core/sim/gates.gd:42;tests/unit/test_runner.gd:28;cases/test_economy_formulas.gd:75 |
%g/%.*f 不支持 → 格式化错误刷屏 |
→ NumberFormat.short() / str() / String.num() |
| R10 | core/sim/strategies/greedy.gd:21 |
Dictionary.get() 推断 |
→ var bulk10: bool = … int(…) > 0 |
| R11 | tests/probe_p1_screen_texture/probe_p1.gd:125 |
条件表达式+下标推断 | → var mode_name: String = … |
| R12 | 8 个测试用例全部 run(a, data) 及辅助函数 |
无类型 data 参数 → 成员调用全返 Variant → 批量推断错误(test_balance_data / boss_battle / camera_data / event_director / offline_contract) |
统一 data: BalanceData(含 _new_econ/_settle/_full_click_config 等辅助函数) |
| R13 | cases/test_boss_battle.gd:340,350,353,356,365,373,382 |
_run_battle() 返回 Dictionary 的下标运算推断 |
→ : float + float() 显式化 |
| R14 | presentation/stage/band_world_root.gd:31,71 |
无类型 Array → Array[Vector2] 运行时错误,世界组装失败(F5 缺十殿视差) |
compute_ranges() -> Array[Vector2] |
测试侧数值修正(均为测试 setup/口径错误,实现符合设计,根因已核实):
cases/test_economy_state.gd:gen3 可购断言前merit = 1000→2000(800 文书 + gen3 首购 1200)cases/test_boss_battle.gd开场伤害 ×3:for _i in 31→ 循环至phase == ACTIVE即 break(INTRO 结束 tick 不造成军列伤害,多跑一拍会掺入 0.1s 军列伤)cases/test_boss_battle.gd容量断言 ×2:harness 预算30.0→34.0(战斗真实给 intro 3s + active 30s = 33s;30s 预算含 INTRO 导致 H10 挂机容量只有 27s < S_10=28s)
工程加固:
core/economy/economy_state.gd红线守卫加"首次越线才报"闩锁(_red_line_reported,prestige 重置)——越线后每 tick push_error 会在 sim 长跑刷出 6.5MB 日志(首跑实证),现每轮恰 1 条。presentation/stage/stage_world.gd:stretch = true→false(§2.6,手动尺寸驱动生效)。
4. sim 首轮数据(2026-10-20,交文策渊的平衡输入)
主口径(#1/#2 采 novice,其余采 greedy;report.primary):
| 门 | 结果 | 数据 |
|---|---|---|
| #1 首购点击 | PASS | 16(目标 [10,20]) |
| #2 前 5min 购买 | FAIL | 23 次(目标 [3,6])——novice 策略买太快 |
| #3 解锁时间表 | FAIL | H1 8/25min…H10 182/180min——前段普遍偏快(H1 仅 32%) |
| #4 购买间隔 | PASS | 最大 9min(上限 60min) |
| #5 主线时长(硬门) | PASS | H10 = 182min(目标 120-300min) |
| #6 无卡墙 | PASS | 最差窗口 4.03× |
| #7 跨策略极差 | PASS | 1.21×(novice 215 / greedy 182 / random 220;目标 <1.5×) |
| #8 回收期 | PASS | t2-t6 = 120/150/188/234/293s |
| #9 离线契约 v2 | PASS | a/b/c 三子检查全过(P-8 已裁定:改契约不改表) |
| #10 终局红线(硬门) | PASS | primary 峰值 11.8B(≤1e14) |
| #11 殿王战胜率 | deferred | BossBattle W4 已落地,接入评估排期 |
| #12 怨气取舍 | FAIL | devour/pure = 0.21×(38min vs 182min;契约 (0.3,3.0))——吞噬口径过强(sim 用 GDD §3.3a 近似,W3 事件流接入后复测) |
| #13 重开曲线 | FAIL | R1=1.00 / R2=0.06 / R3=0.03 / R4=0.02(目标 1:0.6:0.4:0.3) |
| #14 殿门标定 | FAIL | H1 34%…H10 101%(目标 [80%,120])——前段标定值整体超前 |
需设计侧裁定(工程不外推):
- #13 崩塌根因已排除接线 bug:
tune_suihui_div=1e6 / mult=5与 [B§4.1] 逐值一致;11.8B 轮 → 宿慧 543 → 跳过 108 次 ×25 级 ×3 档 ≈ 开局 2700 级 → 次轮 16× 提速。是 [B§4.2] 跳过参数在高宿慧区的设计后果,非实现错误。可选方向:skip 封顶、按轮次衰减、或重开曲线目标改 1:0.15:0.08…(文策渊定)。 - #12:devour 近似口径(45×√产出/s)可能高估吞噬收益;待 W3 事件系统真口径复测。
- #2/#3/#14 同源——前段进度偏快,牵动 pre-H10 全段标定;建议与 #13 一并看(跳过机制也影响早期)。
- restart 轮(rebirths=4)峰值 9.5e15 超红线 100×——与 #13 同根;门 #10 只评 primary 三策略,建议 sim 增加"全轮次峰值"口径(工程侧可加,一行)。
5. 遗留与下一步
- CI 增强建议:加
--check-only -s逐文件 parse 门(§1);sim 报告硬门 #5/#10 的 CI 红留在 W6(架构 §7.2 不变)。 - 门 #11(殿王战胜率):BossBattle 已可无头构造,接入
Simulator的 devour/boss 轮即可评估(排 W4 尾)。 - 4.7 知识缺口簿 = 本文件 §2;后续每发现一条新行为追加于此(不静默跨 minor,架构 §7.1)。
docs/engineering/2026-10-19-f-w2-csv-provenance.md的 P-x 裁定清单不变;sim 数据回执已并入 §4。
记录完。首跑 = 6 个预定位错误 + 8 个引擎新暴露错误全部修复;四条命令 + 主场景 180 帧全绿;584 项单测全过。用户现可 F5 直接玩灰盒。