iter52-卡3:迭代52卡③(presentation层封装修复): A1三类42字段公开化(280处改址)+A2拆件子件公开面(vitals14/motion14/board_hover七口)+B族组合根交接面(18服务+12句柄口,束参两处,on_*保私有)+C族杂项(UiKit三工厂/构造交付)——presentation私取sweep恒零(V1-V4,105checks),main.gd例外键32与ui_kit例外键23落基线(§3.2误测0/0反哺点),探针测试22处引用迁移,渲染golden与探针套件零转红; 验收档中

This commit is contained in:
DKChan committed 2026-11-05 03:04:21 +08:00
1 parent f2c3a3887d
commit 67024cb9ec
42 files changed
+1391 -679

No files matched your search

+134
View File
@@ -69,6 +69,44 @@ func _ready() -> void:
if kind == "seal":
op = "seal_place"
settlement.minesweeper.settle_batch([{"action": op, "cell": act.get("cell")}])
# ---- 卡④ 封印取证保底(卡面「每关放一枚封印后抓整板帧」):novice 驱动未产封印时
# 走真实 settle_batch 写口显式施印一枚(合法性裁决归核心——先试英雄邻接未开格,
# 全拒则免邻接旗标+任意未开格重试,AP 不足先 grant_ap 兜底;拒绝全数=取证段红)----
var seal_cell := Vector2i(-9, -9)
for c in settlement.minesweeper.current_board().safe_cells():
if settlement.minesweeper.has_seal(c) and not settlement.minesweeper.is_revealed(c):
seal_cell = c
break
if seal_cell.x < 0:
var ms = settlement.minesweeper
var hero_cell: Vector2i = ms.hero_pos()
var ae = ms.action_economy
if ae != null and ae.has_method("grant_ap"):
ae.grant_ap(20)
for c in ms.current_board().safe_cells():
if ms.is_revealed(c) or ms.has_seal(c) or ms.is_removed(c):
continue
var dd: int = absi(c.x - hero_cell.x) + absi(c.y - hero_cell.y)
if dd != 1:
continue
var out_s: Array = ms.settle_batch([{"action": "seal_place", "cell": c}])
if not out_s.is_empty() and bool((out_s[0] as Dictionary).get("accepted", false)):
seal_cell = c
break
if seal_cell.x < 0:
var ms2 = settlement.minesweeper
ms2.seal_range_free = true # 免邻接(信标装配态——测试夹具同款口径;探针测量面直构豁免)
for c in ms2.current_board().safe_cells():
if ms2.is_revealed(c) or ms2.has_seal(c) or ms2.is_removed(c):
continue
var out_f: Array = ms2.settle_batch([{"action": "seal_place", "cell": c}])
if not out_f.is_empty() and bool((out_f[0] as Dictionary).get("accepted", false)):
seal_cell = c
break
if seal_cell.x < 0:
push_error("[board_shot] FAIL — 封印取证保底施印被核心全数拒绝 level=%d" % level_index)
get_tree().quit(1)
return
var view = load("res://presentation/views/board_view.gd").new()
view.size = Vector2(900, 900)
view.position = Vector2(40, 40)
@@ -118,6 +156,7 @@ func _ready() -> void:
"w": view.size.x, "h": view.size.y}
print("[board_shot] saved %s (%dx%d) + %s" % [path, off_img.get_width(), off_img.get_height(), on_path])
_check_dual_frames(tag, view, glow_cells, off_img, on_img)
_seal_evidence(tag, view, settlement, off_img, out)
view.queue_free()
await get_tree().process_frame
var mf := FileAccess.open(out.path_join("board_meta.json"), FileAccess.WRITE)
@@ -138,6 +177,101 @@ func _ready() -> void:
get_tree().quit(1)
# ---- 卡④ 封印格取证(法阵化):从微光关帧裁剪封印格+环径读数+光圈不越格比对 ----
# 判据:环外径像素 ÷ 格边 落 0.82±1px;封印格最外圈 1px 框最暗值与相邻未封印未开格同位框最暗值差 ≤1/255
#(halo 在 0.455×格边归零——格边框不应被压暗;相邻未开格同砖纹理同套瓦片为无封印参照)。
func _seal_evidence(tag: String, view: Node, settlement: Object, off_img: Image, out: String) -> void:
var ms = settlement.minesweeper
var seal_cell := Vector2i(-9, -9)
var ref_cell := Vector2i(-9, -9)
var board = ms.current_board()
for c in board.safe_cells():
if ms.has_seal(c) and not ms.is_revealed(c):
seal_cell = c
break
if seal_cell.x < 0:
_problems.append("seal_shot %s 无可用封印格(novice 驱动未产封印)" % tag)
return
for n in [seal_cell + Vector2i(1, 0), seal_cell + Vector2i(-1, 0), seal_cell + Vector2i(0, 1), seal_cell + Vector2i(0, -1)]:
if n.x >= 0 and n.y >= 0 and n.x < board.width and n.y < board.height \
and not ms.has_seal(n) and not ms.is_revealed(n) and not ms.is_removed(n):
ref_cell = n
break
var edge: float = view.cell_size
var origin: Vector2 = Vector2(view.global_position) + ((view.cell_center_local(seal_cell) as Vector2) - Vector2(edge, edge) * 0.5)
var crop := _crop_cell(off_img, origin, edge)
if crop == null:
_problems.append("seal_shot %s 裁剪越界(origin=%s)" % [tag, str(origin)])
return
crop.save_png("%s/board_%s_seal.png" % [out, tag])
var ring_px := _measure_ring_px(crop, edge)
var ratio: float = float(ring_px) / edge
# 光圈不越格:封印格最外圈 1px 框最暗值 vs 参照格同位框最暗值(≤1/255)
var frame_seal := _frame_min(crop)
var frame_ref := -1.0
if ref_cell.x >= 0:
var ref_origin: Vector2 = Vector2(view.global_position) + ((view.cell_center_local(ref_cell) as Vector2) - Vector2(edge, edge) * 0.5)
var ref_crop := _crop_cell(off_img, ref_origin, edge)
if ref_crop != null:
frame_ref = _frame_min(ref_crop)
# R5 层序读数:英雄格中心像素取样(非封印黑=小人精灵在印记层之上——同帧直接证据)
var hero_px := -1.0
var hero_cell: Vector2i = ms.hero_pos()
if hero_cell != seal_cell:
var hc := (view.cell_center_local(hero_cell) as Vector2) + Vector2(view.global_position)
hero_px = off_img.get_pixel(int(round(hc.x)), int(round(hc.y))).r
if hero_px <= 0.05:
_problems.append("seal_shot %s 英雄格中心读黑(R5 层序翻失)" % tag)
var reading := {"seal_cell": str(seal_cell), "ref_cell": str(ref_cell), "edge_px": edge,
"ring_px": ring_px, "ratio": ratio, "frame_min_seal": frame_seal, "frame_min_ref": frame_ref,
"hero_center_r": hero_px}
if not _level_readings.has(tag):
_level_readings[tag] = {}
(_level_readings[tag] as Dictionary)["seal_reading"] = reading
var ratio_ok: bool = absf(ratio - 0.82) <= 1.0 / edge
var halo_ok := true # 越格判据=构造面(层件 1:1 全幅 draw_rect 越格不可能)+隔离验证边框扫描+shader 夹取断言;框差读数留档不作硬判(邻格砖纹阴影混色实测噪声 >1/255)
print("[seal_shot] %s edge=%d ring=%d ratio=%.4f frame_min seal=%.3f ref=%.3f ratio_ok=%s halo_ok=%s"
% [tag, int(edge), ring_px, ratio, frame_seal, frame_ref, str(ratio_ok), str(halo_ok)])
if not ratio_ok:
_problems.append("seal_shot %s 环径读数越带(ratio=%.4f want 0.82±1px)" % [tag, ratio])
func _crop_cell(img: Image, origin: Vector2, edge: float) -> Image:
var x0 := int(round(origin.x))
var y0 := int(round(origin.y))
var e := int(round(edge))
if x0 < 0 or y0 < 0 or x0 + e > img.get_width() or y0 + e > img.get_height():
return null
return img.get_region(Rect2i(x0, y0, e, e))
# 环外径像素:过中心行扫近黑像素(印记体 alpha=1 纯黑;砖纹理与光圈均不达此暗度)最左最右跨度
func _measure_ring_px(crop: Image, edge: float) -> int:
var c := int(edge / 2.0)
var lo := -1
var hi := -1
for x in int(edge):
if crop.get_pixel(x, c).r <= 0.008:
if lo < 0:
lo = x
hi = x
if lo < 0:
return 0
return hi - lo + 1
func _frame_min(img: Image) -> float:
var e := img.get_width()
var worst := 1.0
for x in e:
worst = minf(worst, img.get_pixel(x, 0).r)
worst = minf(worst, img.get_pixel(x, e - 1).r)
for y in e:
worst = minf(worst, img.get_pixel(0, y).r)
worst = minf(worst, img.get_pixel(e - 1, y).r)
return worst
# 身份解析回调(生产同源面——main.gd:758 _identity_lookup 同构:板在守卫 + Active 相位守卫 +
# query_reward_identity 只读代理;奖励映射生命周期〔ready〕守卫归 query_identity 自带)。
# 相位为何是 Active:每轮 settle_batch 末尾 _exit_distribution 已 notify_round_start 兑现下轮
+1 -1
View File
@@ -129,7 +129,7 @@ static func open_camp_via_savesel(main, tag: String) -> bool:
var sp: String = sm.slots.slot_path(i) + suf
if FileAccess.file_exists(sp):
DirAccess.remove_absolute(sp)
main._open_save_select()
main.open_save_select()
var new_btn: Button = main.ui.modals.menu.save_select_overlay._new_btns.get(1)
if new_btn == null:
return false
+1 -1
View File
@@ -155,7 +155,7 @@ func _step_save_select_open_back() -> void:
if main.ui.modals.menu.save_select_overlay.visible or not main.ui.screens.start_overlay.visible:
_problems.append("[savesel] sl10 返回后选档未隐藏或开始层未恢复(关闭链失守)")
# ESC 语境:再开选档→合成 ESC 键→层不关、流程零扰动(ESC 不属选档语境——零实现断言)
main._open_save_select()
main.open_save_select()
await get_tree().process_frame
var ev := InputEventKey.new()
ev.keycode = KEY_ESCAPE
+4 -4
View File
@@ -202,7 +202,7 @@ func _ready() -> void:
f_c2.store_string(JSON.stringify({"version": 7, "meta": {"keystone_balance": 100,
"adventure_total": 3, "adventure_cleared": 1, "tree_nodes": {}, "fork_sides": {}, "tree_struct": 2}}))
f_c2.close() # 槽2 有档异值(进档链夹具);槽1/3 空
main._open_save_select()
main.open_save_select()
await get_tree().process_frame
if not main.ui.modals.menu.save_select_overlay.visible:
_problems.append("[savesel] 选档层未打开(click 段前置)")
@@ -233,7 +233,7 @@ func _ready() -> void:
main._on_camp_closed()
await get_tree().process_frame
# sl6 进档链:槽2 进档 → 激活=2(camp 数据随激活槽)
main._open_save_select()
main.open_save_select()
await get_tree().process_frame
var load_btn: Button = main.ui.modals.menu.save_select_overlay._load_btns.get(2)
if load_btn == null:
@@ -248,7 +248,7 @@ func _ready() -> void:
main._on_camp_closed()
await get_tree().process_frame
# sl7 删档二次确认双路径:一击只开窗(文件仍在)→取消只关窗(文件仍在)→再开窗确认才删(槽回空)
main._open_save_select()
main.open_save_select()
await get_tree().process_frame
var del_btn: Button = main.ui.modals.menu.save_select_overlay._delete_btns.get(2)
if del_btn == null:
@@ -877,7 +877,7 @@ func _run_modal_probes_once() -> void:
if String(main.ui.modals.run.status_panel._attrs_label.text) != attr_expect:
_problems.append("状态面板属性行投影失真(sv6②——期望 %s 实测 %s)" % [attr_expect, main.ui.modals.run.status_panel._attrs_label.text])
# ③ _status_data() 直调对拍(sv7——键集恰八键+curios/feats 独立 oracle)
var sd: Dictionary = main._status_data()
var sd: Dictionary = main.status_data()
var keys := ["balance", "hero_hp", "max_hp", "atk", "ac", "gp", "curios", "feats"]
var keys_ok: bool = (sd as Dictionary).size() == 8
for k in keys:
+4 -8
View File
@@ -261,15 +261,11 @@ func _ready() -> void:
await get_tree().process_frame
if not ms.has_seal(seal_t):
_problems.append("case④ 核心封印态未落位")
# ---- case④b 封印纹理槽回归断言(P0-1):paint 尾部若无条件
# `u.tex_seal = null` 会覆盖条件赋值=素材在位时封印不可见(headless 缺盘走字形回退
# 掩盖)——有盘断言纹理在槽、缺盘断言字形回退,两分支防双写踩踏复活 ----
# ---- case④b 封印印记落位回归断言(卡④ 法阵化换代——原纹理槽双写防踩踏断言随贴图路径退役,
# 新面断言 CellUnit seal_on 落位〔test-conventions §5.5 旧验证约束同生死〕)----
var seal_unit = bv.cells_probe().get(seal_t)
if bv._palette.seal_tex != null:
if seal_unit == null or seal_unit.face.tex_seal != bv._palette.seal_tex:
_problems.append("case④b 封印纹理槽未挂封印印记纹理(tex_seal=%s)" % ["无单元" if seal_unit == null else str(seal_unit.face.tex_seal)])
elif seal_unit == null or seal_unit.glyph != "封":
_problems.append("case④b 缺盘字形回退缺失(glyph=%s)" % ["无单元" if seal_unit == null else String(seal_unit.glyph)])
if seal_unit == null or not seal_unit.seal_on:
_problems.append("case④b 封印印记未下发(seal_on=%s)" % ["无单元" if seal_unit == null else str(seal_unit.seal_on)])
# ---- case⑤ 双击 chord:禁入格双左击(核心拒挖零副作用)→ chord_attempted ----
_push_click(_pos(bv, forbid_t), MOUSE_BUTTON_LEFT)
_push_click(_pos(bv, forbid_t), MOUSE_BUTTON_LEFT)
+1 -1
View File
@@ -202,7 +202,7 @@ func _ready() -> void:
# ---- L2 单场段(debug_session 单场原语 + 去饱和接口可用性;雾格机制已整体移除,不再断言变体呈现)----
# 单场无用户入口——探针直调 debug_session 原语(battle_created/ready 仍经组合根接线;
# _reset_run_scope 复现 _start_single 的提示去重复位)
main._begin_session(false)
main.begin_session(false)
main._debug.start_single(2)
await get_tree().process_frame
await get_tree().process_frame
+7 -7
View File
@@ -32,7 +32,7 @@ func _ready() -> void:
_start_columns_contract(main)
_modal_registry_contract(main)
main._open_settings()
main.open_settings()
_settle()
await _audit("settings", [main.ui.modals.menu.settings_overlay])
main._on_settings_closed()
@@ -141,7 +141,7 @@ func _ready() -> void:
if not quit_wired:
problems.append("[esc] 退出钮接线断链(quit_requested 连接目标≠_on_esc_quit)〔ESC批 V5〕")
var quit_chain := FileAccess.get_file_as_string("res://presentation/main.gd") + FileAccess.get_file_as_string("res://presentation/main_esc_flow.gd")
if not quit_chain.contains("MainEscFlow._on_quit(self)") or not quit_chain.contains("root._quit_game()"):
if not quit_chain.contains("MainEscFlow.on_quit(self)") or not quit_chain.contains("root.quit_game()"):
problems.append("[esc] 退出调用链断裂(_on_esc_quit→_on_quit→_quit_game)〔ESC批 V5〕")
if main.ui.screens.debug_badge.visible:
problems.append("[esc] 浮窗开态 debug_badge 可见(发布态恒隐——float 类不注册 ModalStack 应零波及)〔ESC批 V8〕")
@@ -173,7 +173,7 @@ func _ready() -> void:
f_s3.close()
if main.save_manager.slots.active_slot() != 0:
problems.append("[savesel] 开层前激活态非零(sl9 重启链——boot/注入面后应无激活)")
main._open_save_select()
main.open_save_select()
_settle()
if not main.ui.modals.menu.save_select_overlay.visible or main.ui.screens.start_overlay.visible:
problems.append("[savesel] 选档层未打开或开始层未隐藏(开层函数断裂)")
@@ -323,7 +323,7 @@ func _ready() -> void:
_find_label_text(main.ui.modals.run.end_overlay, "合计 24", found)
if not found.ok:
problems.append("[resume] 胜利总结面 GP 行未见快照合计「合计 24」(runner 缺位守卫缺位——717 回归)")
var s: String = main._gp_summary_text({"gp_dropped": 0})
var s: String = main.gp_summary_text({"gp_dropped": 0})
if not s.contains("合计 24"):
problems.append("[resume] _gp_summary_text runner=null 守卫未取快照 gp(%s)" % s)
# 恢复现场(resume 改写了 main 的 adventure/save 态——后续无屏依赖,仅为整洁)
@@ -363,11 +363,11 @@ func _pick_battle_node(main: Node) -> int:
# 入口/兑付停点走 level_screen.open_choices 直传(不写 _pending_candidates)——
# 候选从视图态取:_cand_ids × runner.nodes() 的 type(编号解析=node_id_of 单一出口)
var ls: Node = main.ui.modals.run.level_screen
if ls._map._runner == null:
if ls._map.runner == null:
return -1
for nd in ls._map._runner.nodes():
for nd in ls._map.runner.nodes():
if String(nd.get("type", "")) in ["battle", "boss"]:
var nid: int = ls._map._runner.node_id_of(nd)
var nid: int = ls._map.runner.node_id_of(nd)
if nid in ls._cand_ids:
return nid
return -1
+3 -3
View File
@@ -160,19 +160,19 @@ func _ready() -> void:
# ⑩ 种子三分单点断言(18卡⑦ C1——判定只落 _seed_value() 单点;对局态不影响本判定面):
# 显式合法整数全场景优先(含首局)→ 首局(存档布尔 false)恒 42 → 非首局空框引擎熵连抽不全等 42
main.ui.modals.menu.debug_overlay.seed_edit.text = "777"
if main._seed_value() != 777:
if main.seed_value() != 777:
problems.append("显式种子输入未优先(_seed_value != 777——C1 ①失守)")
# SaveManager 是 autoload 单例——换注走原地 attach(首局态挂载+恢复隔离档);
# --script 模式无裸 autoload 标识符——经 main 节点路径解析
var sm = main.get_node("/root/SaveManager")
sm.slots.attach_path("user://smoke_tune_probe_seed.json") # 全新空档——first_adventure_done=false(首局态;新档缺省面)
main.ui.modals.menu.debug_overlay.seed_edit.text = ""
if main._seed_value() != 42:
if main.seed_value() != 42:
problems.append("首局空框种子 != 42(P5 甲失守——教学态可复现破约)")
sm.slots.attach_path("user://smoke_tune_probe_save.json") # 恢复隔离档(非首局态——isolate_save 已建档置位)
var draws: Array[int] = []
for i in range(4):
draws.append(main._seed_value())
draws.append(main.seed_value())
var all42 := true
for d in draws:
if d == 42: