diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index 319e1d2a..7d412df8 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -1654,64 +1654,6 @@ func _hero_attack_row_with(opts: Dictionary, hero_atk_val: int, seed_val: int) - return {} -func _smoke_board_generator() -> void: - print("--- 棋盘生成 ---") - var board_generator = load("res://core/board_generator.gd").new() - for L in [1, 2]: - var row = _db.resolve_board_gen(L, 0) - check(not row.is_empty(), "BoardGen 行解析 (%d,0)" % L) - if L == 2: - # R1 ceil 口径:无精确行向上取整保证偏难——L2 深度 3 为恢复点无行,解析到深度 4 - var ceil_row: Dictionary = _db.resolve_board_gen(2, 3) - check(int(ceil_row.get("node_depth", -1)) == 4, "R1 ceil 解析 (2,3)→(2,4)(向上取整偏难)") - var b1 = board_generator.generate(L, 0, _rng.open_stream("board_gen"), _db) - check(b1 != null, "L%d 生成产物非空" % L) - if b1 == null: - continue - check(board_generator.is_clearable(b1), "L%d clearable(安全格 8 邻连通)" % L) - check(board_generator.legality(b1, row, _db), "L%d legality 全过" % L) - var inf := float(b1.metrics.get("inferability", 0.0)) - var mode := String(b1.metrics.get("accept_mode", "")) - print(" L%d 实测:inferability=%.3f / attempts_used=%d / accept_mode=%s / target=%.2f" % [ - L, inf, int(b1.metrics.get("attempts_used", -1)), mode, float(row.inferability_target)]) - # 出图验收(棋盘生成 登记口径):first_pass 必须 ≥ target;accept_best(重试耗尽接受最优)允许低于 target - # 但必须打印实测值(正常出口);accept_worst 由下方 illegal_product 断言承担。 - check(inf >= float(row.inferability_target) or mode == "accept_best", - "L%d inferability %.3f %s target %.2f(accept_mode=%s)" % [L, inf, "≥" if inf >= float(row.inferability_target) else "<", float(row.inferability_target), mode]) - check(b1.variants.is_empty(), "L%d 生成器产物零变体格(变异布置归战斗初始化第 4 步——生成器管线不布置,2026-09-19 用户拍板棋盘设置顺序)" % L) - check(b1.metrics.has("degraded_product") and b1.metrics.has("target_mines"), "L%d 指标集输出降级标记与目标雷数(三分出口表)" % L) - check(b1.degraded_product == (mode == "accept_best"), "L%d 降级产物标记 = (accept_mode==accept_best)(%s→%s)" % [L, mode, str(b1.degraded_product)]) - check(b1.illegal_product == (mode == "accept_worst"), "L%d 非法产物标记 = (accept_mode==accept_worst)(%s→%s)" % [L, mode, str(b1.illegal_product)]) - check(not b1.degraded_product or not b1.illegal_product, "L%d 降级/非法产物标记互斥(两出口不可同真)" % L) - check(int(b1.metrics.actual_mines) <= int(b1.metrics.target_mines), "L%d 产物断言:实际雷数 %d ≤ 目标雷数 N=%d(先布后删只减不增)" % [L, int(b1.metrics.actual_mines), int(b1.metrics.target_mines)]) - check(b1.metrics.get("attempts_used", 99) >= 1 and b1.metrics.get("attempts_used", 99) <= int(row.max_gen_attempts), "L%d attempts_used ∈ [1,%d]" % [L, int(row.max_gen_attempts)]) - check(not b1.illegal_product, "L%d 非法产物标记 = false" % L) - # 生成确定性:同种子逐格相同 - var b2 = board_generator.generate(L, 0, _mk_stream("board_gen", 777), _db) - var b3 = board_generator.generate(L, 0, _mk_stream("board_gen", 777), _db) - check(_board_dump(b2) == _board_dump(b3), "L%d 同种子两次生成逐格相同" % L) - var b4 = board_generator.generate(L, 0, _mk_stream("board_gen", 778), _db) - check(_board_dump(b2) != _board_dump(b4), "L%d 异种子产物不同(退化护栏)" % L) - # ---- 初始展开收窄断言 ---- - # ① 结构断言:R₀ ≤ cap(正式表行投值,合法性闸承载——出图必过);② r0_cap_rejected 尝试事实在位; - # ③ ring_removed 量级:≤ 域内候选危险格数(5×5−出生 9=16——无全域剃雷); - # ④ 闸压线行为:cap=9(出生保障面下限)时出图必 r0≤9 或 accept_worst 非法标记(三分出口诚实降级,玩家永不卡死) - var cap_val: int = int(row.get("r0_max_cap", 0)) - check(cap_val >= BoardData.SPAWN_AREA_CELLS, "L%d r0_max_cap 正式行投值在位(%d ≥ 出生保障面 9——板面 30%% 锚)" % [L, cap_val]) - check(int(b1.metrics.r0_count) <= cap_val, "L%d 结构断言 R₀=%d ≤ cap=%d(R₀≤cap 重掷闸承载)" % [L, int(b1.metrics.r0_count), cap_val]) - check(int(b1.metrics.get("r0_cap_rejected", -1)) >= 0, "L%d r0_cap_rejected 尝试事实指标在位(本图 %d 次触发)" % [L, int(b1.metrics.get("r0_cap_rejected", -1))]) - check(int(b1.metrics.get("ring_removed", 99)) <= (2 + int(row.get("shave_ring_radius", 1))) * (2 + int(row.get("shave_ring_radius", 1))) * 4 - 5, "L%d ring_removed=%d ≤ 剃雷域候选危险格数((3+2r)²−9——全域剃雷退役)" % [L, int(b1.metrics.get("ring_removed", 99))]) - var row9: Dictionary = row.duplicate() - row9["r0_max_cap"] = BoardData.SPAWN_AREA_CELLS - row9["max_gen_attempts"] = 4 - var stub9 := _BoardRowStub.new() - stub9.row = row9 - var b9 = board_generator.generate(1, 0, _mk_stream("board_gen", 909), stub9) - check(b9 != null and (int(b9.metrics.r0_count) <= BoardData.SPAWN_AREA_CELLS or b9.illegal_product), - "L1 cap 压线 9:出图 R₀=%d %s(三分出口诚实降级——超阈即 accept_worst 非法标记,玩家永不卡死)" % [int(b9.metrics.r0_count), "≤9" if int(b9.metrics.r0_count) <= BoardData.SPAWN_AREA_CELLS else ">9→illegal_product"]) - check(int(b9.metrics.get("r0_cap_rejected", 0)) >= 0 and String(b9.metrics.get("accept_mode", "")) != "", "L1 cap 压线出图指标集完整(accept_mode=%s / 闸触发 %d 次)" % [String(b9.metrics.get("accept_mode", "")), int(b9.metrics.get("r0_cap_rejected", 0))]) - - func _mk_stream(stream_name: String, scope: int) -> RandomNumberGenerator: # stream_name = 调用点自述标签(不参与派生),保留参数零改调用方 var rg := RandomNumberGenerator.new() @@ -1738,15 +1680,6 @@ class _CountVariantRng extends RngServiceCore: return super.open_stream(purpose) -# 板行覆盖桩(cap 压线行为断言;接口面 = resolve_board_gen 单点) -class _BoardRowStub: - var row: Dictionary - func resolve_board_gen(_L: int, _D: int) -> Dictionary: - return row - func status_effect_row(_id: String) -> Dictionary: - return {} - - func _smoke_global_variant() -> void: print("--- 全局作用域变异(门控三维 + 全局池空兜底回落 + 资源面偏斜)---") # ① 跨字段互锁负样本(resource_face ⟺ scope=global 三条 + effect_value 新域 [0,50]) @@ -1998,15 +1931,6 @@ func _variant_dump(b) -> String: return "|".join(parts) -func _board_dump(b) -> String: - var parts: Array = [str(b.width), str(b.height), str(b.spawn_center)] - for y in b.height: - for x in b.width: - var c := Vector2i(x, y) - parts.append("%d:%d:%d:%s" % [x, y, 1 if b.is_mine(c) else 0, str(b.variants.get(c, []))]) - return "\n".join(parts) - - # ---- 完整战斗回放(自动玩家,确定性)——回放断言:同种子两遍步骤流逐行相同 ---- func _smoke_full_battle(L: int, D: int, label: String) -> void: diff --git a/game/tests/unit/core/board_gen_reward_test.gd b/game/tests/unit/core/board_gen_reward_test.gd index f1a8a35f..b1fe72d1 100644 --- a/game/tests/unit/core/board_gen_reward_test.gd +++ b/game/tests/unit/core/board_gen_reward_test.gd @@ -32,6 +32,7 @@ func _init() -> void: # 套执行序沿源编排序 _smoke_layout_cell_formula() _smoke_reward_identity_unit() + _smoke_board_generator() quit(Kit.finish("board_gen_reward_test")) @@ -112,3 +113,92 @@ func _smoke_reward_identity_unit() -> void: # 退役后受理拒绝 reward_economy.retire_mapping() Kit.check("映射退役后 grant 拒绝(生命周期闭合)", reward_economy.grant(eligible[0].cell) == null) + + +# ---- 棋盘生成(生成-验证循环 / 三分出口 / r0 闸)---- + +# 板行覆盖桩(cap 压线行为断言;接口面 = resolve_board_gen 单点) +class _BoardRowStub: + var row: Dictionary + func resolve_board_gen(_L: int, _D: int) -> Dictionary: + return row + func status_effect_row(_id: String) -> Dictionary: + return {} + + +func _smoke_board_generator() -> void: + print("--- 棋盘生成 ---") + var board_generator = load("res://core/board_generator.gd").new() + for L in [1, 2]: + var row = _db.resolve_board_gen(L, 0) + Kit.check("BoardGen 行解析 (%d,0)" % L, not row.is_empty()) + if L == 2: + # R1 ceil 口径:无精确行向上取整保证偏难——L2 深度 3 为恢复点无行,解析到深度 4 + var ceil_row: Dictionary = _db.resolve_board_gen(2, 3) + Kit.check("R1 ceil 解析 (2,3)→(2,4)(向上取整偏难)", int(ceil_row.get("node_depth", -1)) == 4) + var b1 = board_generator.generate(L, 0, _rng.open_stream("board_gen"), _db) + Kit.check("L%d 生成产物非空" % L, b1 != null) + if b1 == null: + continue + Kit.check("L%d clearable(安全格 8 邻连通)" % L, board_generator.is_clearable(b1)) + Kit.check("L%d legality 全过" % L, board_generator.legality(b1, row, _db)) + var inf := float(b1.metrics.get("inferability", 0.0)) + var mode := String(b1.metrics.get("accept_mode", "")) + print(" L%d 实测:inferability=%.3f / attempts_used=%d / accept_mode=%s / target=%.2f" % [ + L, inf, int(b1.metrics.get("attempts_used", -1)), mode, float(row.inferability_target)]) + # 出图验收(棋盘生成 登记口径):first_pass 必须 ≥ target;accept_best(重试耗尽接受最优)允许低于 target + # 但必须打印实测值(正常出口);accept_worst 由下方 illegal_product 断言承担。 + Kit.check("L%d inferability %.3f %s target %.2f(accept_mode=%s)" % [L, inf, "≥" if inf >= float(row.inferability_target) else "<", float(row.inferability_target), mode], + inf >= float(row.inferability_target) or mode == "accept_best") + Kit.check("L%d 生成器产物零变体格(变异布置归战斗初始化第 4 步——生成器管线不布置,2026-09-19 用户拍板棋盘设置顺序)" % L, b1.variants.is_empty()) + Kit.check("L%d 指标集输出降级标记与目标雷数(三分出口表)" % L, b1.metrics.has("degraded_product") and b1.metrics.has("target_mines")) + Kit.check("L%d 降级产物标记 = (accept_mode==accept_best)(%s→%s)" % [L, mode, str(b1.degraded_product)], + b1.degraded_product == (mode == "accept_best")) + Kit.check("L%d 非法产物标记 = (accept_mode==accept_worst)(%s→%s)" % [L, mode, str(b1.illegal_product)], + b1.illegal_product == (mode == "accept_worst")) + Kit.check("L%d 降级/非法产物标记互斥(两出口不可同真)" % L, + not b1.degraded_product or not b1.illegal_product) + Kit.check("L%d 产物断言:实际雷数 %d ≤ 目标雷数 N=%d(先布后删只减不增)" % [L, int(b1.metrics.actual_mines), int(b1.metrics.target_mines)], + int(b1.metrics.actual_mines) <= int(b1.metrics.target_mines)) + Kit.check("L%d attempts_used ∈ [1,%d]" % [L, int(row.max_gen_attempts)], + b1.metrics.get("attempts_used", 99) >= 1 and b1.metrics.get("attempts_used", 99) <= int(row.max_gen_attempts)) + Kit.check("L%d 非法产物标记 = false" % L, not b1.illegal_product) + # 生成确定性:同种子逐格相同 + var b2 = board_generator.generate(L, 0, _mk_stream("board_gen", 777), _db) + var b3 = board_generator.generate(L, 0, _mk_stream("board_gen", 777), _db) + Kit.check("L%d 同种子两次生成逐格相同" % L, _board_dump(b2) == _board_dump(b3)) + var b4 = board_generator.generate(L, 0, _mk_stream("board_gen", 778), _db) + Kit.check("L%d 异种子产物不同(退化护栏)" % L, _board_dump(b2) != _board_dump(b4)) + # ---- 初始展开收窄断言 ---- + # ① 结构断言:R₀ ≤ cap(正式表行投值,合法性闸承载——出图必过);② r0_cap_rejected 尝试事实在位; + # ③ ring_removed 量级:≤ 域内候选危险格数(5×5−出生 9=16——无全域剃雷); + # ④ 闸压线行为:cap=9(出生保障面下限)时出图必 r0≤9 或 accept_worst 非法标记(三分出口诚实降级,玩家永不卡死) + var cap_val: int = int(row.get("r0_max_cap", 0)) + Kit.check("L%d r0_max_cap 正式行投值在位(%d ≥ 出生保障面 9——板面 30%% 锚)" % [L, cap_val], + cap_val >= BoardData.SPAWN_AREA_CELLS) + Kit.check("L%d 结构断言 R₀=%d ≤ cap=%d(R₀≤cap 重掷闸承载)" % [L, int(b1.metrics.r0_count), cap_val], + int(b1.metrics.r0_count) <= cap_val) + Kit.check("L%d r0_cap_rejected 尝试事实指标在位(本图 %d 次触发)" % [L, int(b1.metrics.get("r0_cap_rejected", -1))], + int(b1.metrics.get("r0_cap_rejected", -1)) >= 0) + Kit.check("L%d ring_removed=%d ≤ 剃雷域候选危险格数((3+2r)²−9——全域剃雷退役)" % [L, int(b1.metrics.get("ring_removed", 99))], + int(b1.metrics.get("ring_removed", 99)) <= (2 + int(row.get("shave_ring_radius", 1))) * (2 + int(row.get("shave_ring_radius", 1))) * 4 - 5) + var row9: Dictionary = row.duplicate() + row9["r0_max_cap"] = BoardData.SPAWN_AREA_CELLS + row9["max_gen_attempts"] = 4 + var stub9 := _BoardRowStub.new() + stub9.row = row9 + var b9 = board_generator.generate(1, 0, _mk_stream("board_gen", 909), stub9) + Kit.check("L1 cap 压线 9:出图 R₀=%d %s(三分出口诚实降级——超阈即 accept_worst 非法标记,玩家永不卡死)" % [int(b9.metrics.r0_count), "≤9" if int(b9.metrics.r0_count) <= BoardData.SPAWN_AREA_CELLS else ">9→illegal_product"], + b9 != null and (int(b9.metrics.r0_count) <= BoardData.SPAWN_AREA_CELLS or b9.illegal_product)) + Kit.check("L1 cap 压线出图指标集完整(accept_mode=%s / 闸触发 %d 次)" % [String(b9.metrics.get("accept_mode", "")), int(b9.metrics.get("r0_cap_rejected", 0))], + int(b9.metrics.get("r0_cap_rejected", 0)) >= 0 and String(b9.metrics.get("accept_mode", "")) != "") + + +# 排雷 core 板面摘要(确定性对照面——尺寸/出生点/逐格雷位与变体清单) +func _board_dump(b) -> String: + var parts: Array = [str(b.width), str(b.height), str(b.spawn_center)] + for y in b.height: + for x in b.width: + var c := Vector2i(x, y) + parts.append("%d:%d:%d:%s" % [x, y, 1 if b.is_mine(c) else 0, str(b.variants.get(c, []))]) + return "\n".join(parts) diff --git a/reports/批B-断言迁移账本-2026-10-22.md b/reports/批B-断言迁移账本-2026-10-22.md index 3a606349..d3a03fa1 100644 --- a/reports/批B-断言迁移账本-2026-10-22.md +++ b/reports/批B-断言迁移账本-2026-10-22.md @@ -269,6 +269,7 @@ - `_smoke_layout_cell_formula`(2 条):2/2 验红。变异点——board_view.gd:233 删 `- float((n - 1) * sep)` 格间距项(bug⑥ 原缺陷形态;红唯一两条迭代齐红 L1 438/L2 442 > 436,档内条保持绿);board_view.gd:234 clamp 上界 46→25(该条守面=钳制带本体——夹具板实测格边 41/33 为带内值,带内改界不红,改界出带方验红;红唯一两条迭代齐红 25px < 26,网格高条保持绿)。 - `_smoke_reward_identity_unit`(10 条):10/10 验红(全部红唯一 1/14)。变异点——reward_economy.gd:89 roll_board 尾 return true→false(红「roll_board 受理」);:39 相位守卫 `_phase == "ready"`→`"never"`(红「重复 roll_board 被拒」);:113 投影删 tier 键(红「query_identity 投影」);:111 清单外出口 return null→{}(红「清单外坐标查询」;null→{} 形态避免 `_mapping[cell]` 缺键 crash);:140 grant 幂等缓存出口改扰动副本 duplicate+stacks=99(红「同格重复 grant 幂等」——重算快照值相等,非扰动副本形态红不了);:137 grant 清单外出口 return null→{}(红「清单外 grant 拒绝」);:87 `_clear_identity = clear_rid`→`"gp_coin"`(红「清图批尾身份」,power 掷定仍循真 clear_rid 故 :1977 power 条保持绿——隔离成功);:85 `_clear_power` 区间掷点→`power_min - 1`(红「清图奖励 power ∈ 表区间」roll 23 < 24);:152 grant_clear 幂等缓存出口改扰动副本(红「grant_clear 同批次重调幂等」);:163–164 两点变异 `_phase = "retired"` 与 `_mapping.clear()` 双双 pass 化(红「映射退役后 grant 拒绝」——该条守面=相位门+映射 wipe 联合,单点均被另一机制代偿必须两点)。 +- `_smoke_board_generator`(22 条):22/22 验红(零死断言候选)。变异点——config_db_resolver.gd:156 行匹配 return row→`return {}`(红「BoardGen 行解析」双迭代+generate 行缺失断言链连带:「产物非空」「R1 ceil」同轮红——resolve 与 generate 共用单点不可分);**首试无效变异**:_ceil_key `return int(k)`→`int(k) - 1` 键值偏移致 level 键错位空 depth_keys 数组越界 crash(Parse 级污染不算红),换形 depth_ceil 路径插 `if missing == "depth_ceil": return {}`(红「R1 ceil 解析」红唯一 1/57);board_generator.gd:75 return accepted→null(红「生成产物非空」双迭代+suite1 rebuild 空板访问中断连带——预期形态);:278 is_clearable 尾 return true→false(红「clearable」双迭代+legality 内嵌消费链连带 6 条:legality 双红/accept_worst 链 inferability/非法产物/R₀ 结构);:282 legality 头插判别器 `if params.has("node_depth"): return false`(红「legality 全过」双迭代红唯一——generate 内部 params 无 node_depth 不受扰,防三分出口链连带);:340 指标 inferability→0.0(红「inferability ≥ target」L1 迭代红唯一——L2 原生 accept_best 走右支保持绿);:354 后插雷位格放变异 `for c in b.mines.keys(): b.variants[c] = []; break`(红「零变体格」双迭代红唯一——雷位格不入 spawn/R₀ 合法性扫描域);:346 指标删 degraded_product 键(红「指标集输出」双迭代红唯一——板字段独立在 :354 故三分出口条零连带);:354 板 degraded_product→true(红「降级标记」L1 红唯一——L2 原生 accept_best 与恒真恰一致);:353 板 illegal_product→true(红「非法标记==」双迭代+A14 双红+L2 互斥连带);:353–354 双标记恒 true(红「互斥」双迭代+A9/A10/A14 连带);:341 指标 actual_mines+10(红「实际≤目标」双迭代——真实雷数不动绕开 _finalize 内同不变式 _fatal 守卫);:344 指标 attempts_used→0(红「attempts 域」双迭代红唯一);:353 板 illegal_product 取反(红「非法标记=false」双迭代+A10 双红+L2 互斥+A21 b9 诚实降级标记连带);:100 取号换全局未播种 randi_range(红「同种子逐格相同」双迭代红唯一);:100 取号恒 i 恒等置换(红「异种子不同」+退化板 legality 链连带——板全同构时退化板本身非法属必然形态);board_gen.tres L1 depth0 行 r0_max_cap 45→5(红「cap 投值 ≥9」+cap 闸压全尝试 accept_worst 链连带 2 条);:349 指标 r0_count+100(红「R₀≤cap」双迭代红唯一——b9 illegal 支路承载故 A21 零连带);:351 指标 r0_cap_rejected→-1(红「尝试事实在位」双迭代+A22 双红连带);:348 指标 ring_removed→999(红「剃雷量界」双迭代红唯一);:316 r0_cap 闸条件 >→<(红「cap 压线 9」双迭代+L2 结构断言连带——闸关后高 R₀ 板入收);:345 指标 accept_mode→""(红「指标集完整」双迭代+L2 A6/A9 连带——右支与降级比较均读该指标)。 ## 十、支援线 9 项待裁闭合(2026-10-22 主会话裁)