Files
game-code-4/docs/engineering/2026-10-20-f-engine-first-run-notes.md
程基岩(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

13 KiB
Raw Permalink Blame History

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)
  • 修法:显式标注 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])——前段标定值整体超前

需设计侧裁定(工程不外推):

  1. #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…(文策渊定)。
  2. #12:devour 近似口径(45×√产出/s)可能高估吞噬收益;待 W3 事件系统真口径复测。
  3. #2/#3/#14 同源——前段进度偏快,牵动 pre-H10 全段标定;建议与 #13 一并看(跳过机制也影响早期)。
  4. 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 直接玩灰盒。