From cd7d3ea40126dc4a3d66f3dc81decae0c1de89bf Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sun, 4 Oct 2026 06:44:43 +0800 Subject: [PATCH 1/8] =?UTF-8?q?T1(D1):=20ui=5Fclick=5Fprobe=20=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E5=B1=80=E9=80=82=E9=85=8D=E2=80=94=E2=80=94=E5=88=86?= =?UTF-8?q?=E6=B4=BE=E9=93=BE=E5=BA=8F=E4=BF=AE=E6=AD=A3+=E9=94=81?= =?UTF-8?q?=E6=9C=9F/=E9=87=8D=E6=94=BE=E4=B8=89=E6=80=81=E6=96=AD?= =?UTF-8?q?=E8=A8=80=EF=BC=88=E4=B8=89=E7=A7=8D=E5=AD=90=E7=BB=BF+?= =?UTF-8?q?=E5=86=92=E7=83=9F718/0+run=5Fall15+t17=5Fdefer=E7=BB=BF?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ①分派链序修正(主循环):shop_overlay 停点提前到 level_screen 之前——迭代11商店房重构后 停点呈现为选路页+商店层双 visible,旧序下层候选点击空转烧尽预算(实证 lv2 商店停点双 visible 循环 12000 帧 done=false budget=0 问题=0;收口驱动循环序本就正确,仅主循环错序)。 ②停点态断言:三选一停点输入锁定断言(BonusPickOverlay 分派链顶)。 ③锁期断言(每run一次脚本化 _locktest_tick):演播期注入旗击→直落被冻结(旗未置)+ _locked_click 缓存命中(最后一次语义)。 ④重放断言:演播完边沿 pump_ended→_replay_locked_click 重入唯一动作通道受理恰一次 (旗布尔=单次送达探测器:双发翻转回false/零发false均暴露)。 ⑤演播期豁免保留+注记(冻结/缓存/重放语义由 _locktest_tick 正面承载);_play_one_action 增演播期不出手守卫(逐帧连点只会被「最后一次」覆盖且毁锁测注入独占前提);锁测在飞期 (state1)冻结探针动作面——排空帧免费 move 抢在帧末 deferred 重放前改英雄位致 flag 拒收 not_adjacent 实证,「注入→重放」窗口局面零变化=v3.1 被测语义本体。 基线:ui_click_probe 开工日红(budget 耗尽 done=false)→ 三种子绿(budget 9-10k/20000)。 --- game/smoke/ui_click_probe.gd | 113 +++++++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 10 deletions(-) diff --git a/game/smoke/ui_click_probe.gd b/game/smoke/ui_click_probe.gd index 22ddf15f..8949011b 100644 --- a/game/smoke/ui_click_probe.gd +++ b/game/smoke/ui_click_probe.gd @@ -30,6 +30,9 @@ var _tested_tooltip := false var _inter_confirms := 0 # 大奖励屏确认计数(冒险推进停点——首见确认推进 L2,再见 = L2 通关收口) var _settlement_confirms := 0 # 卡⑥ 胜利结算框确认计数(观测面——每场普通战斗胜利恰一) var _route_clicks := 0 # 选路覆盖层点击计数(岔路使用率观测面) +var _locktest_state := 0 # T1 D1 锁期/重放断言状态机(0=待注入 1=等重放 2=完结)——每 run 一次 +var _locktest_cell := Vector2i(-9, -9) +var _locktest_frames := 0 func _ready() -> void: @@ -190,9 +193,12 @@ func _ready() -> void: _flags_placed.clear() continue if main.bonus_overlay.visible: - # 迭代 10 三选一停点(BonusPickOverlay——奖励节点/通关兑付共用面;T17 硬锁批适配—— - # 本探针此前未适配该层,走到即卡死)。三态分派:替换子态(满位——放弃新件)/ - # result 态(兑付反馈——点继续推进)/ 待选态(点首件+确认)。真实信号路径全线。 + # 迭代 10 三选一停点(BonusPickOverlay——奖励节点/通关兑付共用面;T17 硬锁批适配—— + # 本探针此前未适配该层,走到即卡死)。三态分派:替换子态(满位——放弃新件)/ + # result 态(兑付反馈——点继续推进)/ 待选态(点首件+确认)。真实信号路径全线。 + # T1 D1——三选一停点输入锁定断言(停点态断言面:替换子/待选/兑付三态共用此层)。 + if not main._input_locked(): + _problems.append("三选一停点打开时输入未锁定") if main.bonus_overlay._replace_box.visible: main.bonus_overlay.replace_chosen.emit(-1) elif main.bonus_overlay._continue_btn.visible: @@ -204,17 +210,20 @@ func _ready() -> void: main.bonus_overlay.press_continue() # 停点已消费的残面——继续关屏 _flags_placed.clear() continue + if main.shop_overlay.visible: + # 商店房覆盖层(GP+商店房批——L2 商店为 Boss 前必经位):零购离开(真实 leave 信号路径)放行选路。 + # T1 D1 分派链序修正:商店层=选路页上的模态覆盖(迭代 11 商店房重构后停点呈现为 + # level_screen+shop 双 visible)——必须先于 level_screen 分派,否则下层候选点击 + # 空转烧尽预算(实证:lv2 商店停点双 visible 循环 12000 帧 done=false)。 + main.shop_overlay.leave_pressed.emit() + _flags_placed.clear() + continue if main.level_screen.visible: # 节点图选路口(零自动推进停点)——图上命中路径交替选候选(岔路使用行为;布局决策 2 改造) main.level_screen.press_candidate(_route_clicks % 2) _route_clicks += 1 _flags_placed.clear() continue - if main.shop_overlay.visible: - # 商店房覆盖层(GP+商店房批——L2 商店为 Boss 前必经位):零购离开(真实 leave 信号路径)放行选路 - main.shop_overlay.leave_pressed.emit() - _flags_placed.clear() - continue if main._inter_overlay.visible: # 大奖励屏(冒险级停点——非末关通关恰一次):首见确认推进 L2(真实按钮信号路径);再见 = L2 已通关,收口 if not main._input_locked(): @@ -281,6 +290,8 @@ func _ready() -> void: # 排播演播期豁免(T17 硬锁 v3 随批——合法停顿态):演播期棋盘锁生效、probe 点击被吞、 # 逻辑态恒定属预期。判据=纯队列(与 main 锁同源)。Waiting 收尾等待期**不豁免**—— # 锁若把收尾链卡死,此处 stall 照报(探测面)。 + # T1 D1 处置=保留豁免+注记:冻结/缓存/重放语义不再靠豁免绕行,由 _locktest_tick + # 脚本化断言正面承载(每 run 一次;逻辑态恒定本豁免继续作 stall 判据)。 if main._row_queue.is_empty(): _stall_frames += 1 if _stall_frames == 30: @@ -290,9 +301,14 @@ func _ready() -> void: _stall_frames = 0 _ap_spent_this_round = 0 _last_state = st + _locktest_tick() if main.settlement.battle_result == "" and main.settlement.action_economy.phase == "Active": - _run_modal_probes_once() - _play_one_action() + # T1 D1——锁测在飞期(state 1)冻结探针动作面:排空帧 autoplay 的免费 move 会抢在 + # 帧末 deferred 重放之前改变英雄位(flag 拒收 not_adjacent 实证)——「注入→重放」 + # 窗口内局面零变化正是 v3.1 被测语义(演播期冻结),动作面恢复待断言收口。 + if _locktest_state != 1: + _run_modal_probes_once() + _play_one_action() # ---- 预算耗尽收口(T17 硬锁批适配):选件变强后 probe 可能活过预算不再战败——断点段前提 # (冒险终局面在案)须主动满足。收口驱动:任何停点层逐个推进(复用主循环分支语义)至 # level_screen 选路停点→真实放弃二击→终局面。战斗中则继续 autoplay 打完。 @@ -527,6 +543,8 @@ func _play_one_action() -> void: var settlement = main.settlement if settlement == null: return # 结算竞态防抖:battle_finished 与本轮动作调度同帧到达时 settlement 可能已被置空(基线既有噪声——加固) + if not main._row_queue.is_empty(): + return # T1 D1——演播期不出手:此时点击只会进锁期缓存被「最后一次」覆盖(原逐帧连点语义无行为覆盖价值,且毁锁期断言注入的独占前提) var minesweeper = settlement.minesweeper # 行为①:随机插旗(右键——未开相邻格),约 20% 轮次 if _ap_spent_this_round == 0 and _rand.randf() < 0.2: @@ -581,6 +599,81 @@ func _click_stop() -> void: main.ap_bar._btn_stop.pressed.emit() +# ---- T1 D1:锁期/重放断言(每 run 一次,脚本化场景——演播期豁免的断言化承载) ---- +# 锁期断言(排播队列活跃=锁面):注入旗击 → 直落被冻结不受理(旗未置)+ `_locked_click` +# 缓存命中且为本注入(「最后一次」语义——注入帧内无并发点击)。 +# 重放断言(演播完边沿 pump_ended→_replay_locked_click 重入唯一动作通道):缓存清空 + +# 受理恰一次——旗布尔=单次送达探测器(双发=两次翻转回 false、零发=false,均暴露)。 +# 前提消失守卫:等重放期间出现停点层/战斗收场(注入时点恰逢终结演播)→ 静默弃测不计 +# problem(旗动作不杀伤,正常局不触发;断言实现形态取舍在代拍白名单内)。 +func _locktest_tick() -> void: + if _locktest_state == 2: + return + var settlement = main.settlement + if settlement == null or settlement.minesweeper.current_board() == null: + return + if _locktest_state == 0: + if main._row_queue.is_empty() or main._input_locked(): + return + if String(settlement.battle_result) != "" or String(settlement.action_economy.phase) != "Active": + return + if int(settlement.action_economy.ap) < 1: + return + var target := _locktest_pick_cell(settlement.minesweeper) + if target.x < 0 or settlement.minesweeper.has_flag(target): + return # 无合法邻格/目标被并发旗行为占位——下帧重挑 + _locktest_cell = target + _locktest_frames = 0 + main.board_view.click_cell(target, MOUSE_BUTTON_RIGHT) + if settlement.minesweeper.has_flag(target): + _problems.append("锁期断言:演播期点击未被冻结(旗即时受理——v3 冻结失守)") + _locktest_state = 2 + return + if main._locked_click.is_empty() or main._locked_click.get("cell") != target: + _problems.append("锁期断言:演播期点击未入缓存(v3.1 缓存失守)") + _locktest_state = 2 + return + _locktest_state = 1 + elif _locktest_state == 1: + if main.bonus_overlay.visible or main._inter_overlay.visible or main._end_overlay.is_open() \ + or String(settlement.battle_result) != "": + _locktest_state = 2 # 前提消失守卫:终结演播竞态——静默弃测 + return + if not main._row_queue.is_empty(): + _locktest_frames = 0 # 演播未完——重放耐心只在排空后起算(链式大演播数百帧不占预算) + return + _locktest_frames += 1 + if _locktest_frames < 3: + return # 排空边沿的 deferred 重放(pump_ended 帧末执行)在途宽限 + if _locktest_frames > 90: + _problems.append("重放断言:演播完边沿后缓存未清(%s)" % str(main._locked_click)) + _locktest_state = 2 + return + if main._locked_click.is_empty(): + if not settlement.minesweeper.has_flag(_locktest_cell): + var b = settlement.minesweeper.current_board() + print("[lockdiag] 重放后旗未置:br='%s' phase=%s ap=%d cell=%s revealed=%s board=%dx%d hero=%s" % [ + str(settlement.battle_result), str(settlement.action_economy.phase), + int(settlement.action_economy.ap), str(_locktest_cell), + str(settlement.minesweeper.is_revealed(_locktest_cell)), + b.width, b.height, str(settlement.minesweeper.hero_pos())]) + _problems.append("重放断言:锁期缓存点击未受理恰一次(旗未置位——零发或双发)") + _locktest_state = 2 + main.board_view.click_cell(_locktest_cell, MOUSE_BUTTON_RIGHT) # 拔旗恢复现场(尽力而为不判定) + + +func _locktest_pick_cell(minesweeper) -> Vector2i: + var hero: Vector2i = minesweeper.hero_pos() + for dy in range(-1, 2): + for dx in range(-1, 2): + if dx == 0 and dy == 0: + continue + var c: Vector2i = hero + Vector2i(dx, dy) + if minesweeper.current_board().in_bounds(c) and not minesweeper.is_revealed(c) and not minesweeper.has_flag(c): + return c + return Vector2i(-1, -1) + + func _report_stall(st: Dictionary) -> void: var why := "" if st.phase == "Waiting": From 22417783db8235cb356bf7eb414f798fa8925f8c Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sun, 4 Oct 2026 06:56:20 +0800 Subject: [PATCH 2/8] =?UTF-8?q?T2(D2):=20=E6=8E=A2=E9=92=88=E5=8F=AA?= =?UTF-8?q?=E8=AF=BB=E5=87=BA=E5=8F=A3=E2=80=94=E2=80=94BoardView/level=5F?= =?UTF-8?q?map=5Fscreen=20=E5=9B=9B=E5=87=BA=E5=8F=A3=E8=90=BD=E5=9C=B0+?= =?UTF-8?q?=E7=A7=81=E6=9C=89=E5=9F=9F=E7=9B=B4=E5=85=A5=E6=B8=85=E9=9B=B6?= =?UTF-8?q?=EF=BC=88=E5=85=A8=E6=8E=A2=E9=92=88=E7=BB=BF+=E5=86=92?= =?UTF-8?q?=E7=83=9F718/0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 出口四件(形态仿 fx_probe 先例:只读 Dictionary 快照): - BoardView.cells_probe()/diggable_probe()(键面同构快照)+hover_probe()/fog_params_probe() (后两件=D2 清单外同类项随批收口:_hover 1 处+_fog_lantern/_fog_enabled 2 处,完成定义 「私有域直入清零」口径,26 处 > 任务书盘点 ~22 处——超出面系断言消息实参里的隐性直读) - level_map_screen.layout_probe(nodes)/canvas_probe()(origin+min_size 快照) 消费侧改写:ui_input_probe 24 处(含循环体三处快照局部变量防逐迭代 duplicate)、 ui_probe 2 处、ui_click_probe 1 处;grep 复核 _cells/_diggable/_hover/_fog_*/_layout/_canvas 探针侧零残留。组合根接缝面(_row_queue 等)按边界注记不入本卡。烟测断言数 718 持平。 --- game/presentation/views/board_view.gd | 23 ++++++++ game/presentation/views/level_map_screen.gd | 12 ++++ game/smoke/ui_click_probe.gd | 2 +- game/smoke/ui_input_probe.gd | 62 +++++++++++---------- game/smoke/ui_probe.gd | 4 +- 5 files changed, 72 insertions(+), 31 deletions(-) diff --git a/game/presentation/views/board_view.gd b/game/presentation/views/board_view.gd index 53f5b617..1a73135c 100644 --- a/game/presentation/views/board_view.gd +++ b/game/presentation/views/board_view.gd @@ -1117,3 +1117,26 @@ func _make_cursor_texture(glyph: String) -> ImageTexture: if f != null: f.draw_string(img, Vector2(2, 18), glyph, HORIZONTAL_ALIGNMENT_LEFT, 20, 18, COL_DIGIT) return ImageTexture.create_from_image(img) + + +# ---- 探针只读出口(D2 改造 2026-10-04——_cells/_diggable 私有容器直入退役;形态仿 fx_probe 先例:只读快照) ---- + +# 格件映射只读快照(Vector2i → CellUnit;键面同构——断言语义零变化;破私有面的只是容器字典, +# 格件字段本为公有呈现面,快照值即原格件引用) +func cells_probe() -> Dictionary: + return _cells.duplicate() + + +# 可挖域只读快照(Vector2i → true;8 向相邻未开格=呼吸微光作用集) +func diggable_probe() -> Dictionary: + return _diggable.duplicate() + + +# 悬停格只读出口(case① 悬停到达断言消费面——_hover 私有域直入退役,D2 清单外同类项随批收口) +func hover_probe() -> Vector2i: + return _hover + + +# 迷雾参数只读快照(case⑮ 参数漂移断言消费面——_fog_lantern/_fog_enabled 私有域直入退役,D2 清单外同类项随批收口) +func fog_params_probe() -> Dictionary: + return {"lantern": _fog_lantern, "enabled": _fog_enabled} diff --git a/game/presentation/views/level_map_screen.gd b/game/presentation/views/level_map_screen.gd index d919e31b..74155e7e 100644 --- a/game/presentation/views/level_map_screen.gd +++ b/game/presentation/views/level_map_screen.gd @@ -468,6 +468,18 @@ func _layout(nodes: Array, canvas: Control) -> Dictionary: return pos +# ---- 探针只读出口(D2 改造 2026-10-04——_layout/_canvas 私有域直入退役;形态仿 fx_probe 先例:只读快照) ---- + +# 选路布局只读快照(node → 局部位置;画布固定取现行 _canvas,调用侧零私有域触达) +func layout_probe(nodes: Array) -> Dictionary: + return _layout(nodes, _canvas) + + +# 路由画布只读快照(origin=全局原点;min_size=最小拟合尺寸——画布拟合断言消费面) +func canvas_probe() -> Dictionary: + return {"origin": _canvas.get_global_position(), "min_size": _canvas.custom_minimum_size} + + # 分层(layout/fit 共享件):layer = 入口 BFS 深度;同层 slot 垂直排布;未达节点兜底排末层 func _assign_layers(nodes: Array, layer: Dictionary, slot: Dictionary) -> void: var layer_counts := {} diff --git a/game/smoke/ui_click_probe.gd b/game/smoke/ui_click_probe.gd index 8949011b..7ccd89ba 100644 --- a/game/smoke/ui_click_probe.gd +++ b/game/smoke/ui_click_probe.gd @@ -177,7 +177,7 @@ func _ready() -> void: var first_cell_ms := t_entry if first_cell_ms > 500: _problems.append("进入战斗屏首格可点超时 %dms > 500ms" % first_cell_ms) - if main.board_view._cells.is_empty(): + if main.board_view.cells_probe().is_empty(): _problems.append("首格未可点(cells=0)") print("[click_probe] 首格可点计时 %dms(进入战斗屏口径,≤500ms AC)· 场景冷启动 %dms(含 UI 构建 %dms,观测项)" % [ first_cell_ms, t_total, t_ui_ready]) diff --git a/game/smoke/ui_input_probe.gd b/game/smoke/ui_input_probe.gd index eea9e17f..f51acf98 100644 --- a/game/smoke/ui_input_probe.gd +++ b/game/smoke/ui_input_probe.gd @@ -45,8 +45,8 @@ func _ready() -> void: for n in nds: if main.runner.node_id_of(n) == int(cands[0]): tgt = n - var npos: Vector2 = ro._layout(nds, ro._canvas)[tgt] - var gpos: Vector2 = ro._canvas.get_global_position() + npos + var npos: Vector2 = ro.layout_probe(nds)[tgt] + var gpos: Vector2 = ro.canvas_probe()["origin"] + npos var evm := InputEventMouseMotion.new() evm.position = gpos evm.global_position = gpos @@ -97,12 +97,13 @@ func _ready() -> void: # 常数锚(画布拟合探针):70/122/26/8 与 presentation/views/level_map_screen.gd 的 _content_size # 公式(70 + maxl*122 + NODE_HALF.x + 8)同源复刻——改常数须两侧同步(本探针只读断言层,真源在屏) var need_x2: float = 70.0 + maxl2 * 122.0 + 26.0 + 8.0 - if float(ro2._canvas.custom_minimum_size.x) < need_x2 - 0.5: - _pre_fail("画布拟合不足(min.x=%.0f < 最深内容 %.0f——深层节点不可点回归)" % [ro2._canvas.custom_minimum_size.x, need_x2]) + var cvmin: Dictionary = ro2.canvas_probe() + if float(cvmin["min_size"].x) < need_x2 - 0.5: + _pre_fail("画布拟合不足(min.x=%.0f < 最深内容 %.0f——深层节点不可点回归)" % [cvmin["min_size"].x, need_x2]) var bv = main.board_view var ms = main.settlement.minesweeper if main.settlement != null else null - if ms == null or bv._cells.is_empty(): - _pre_fail("战斗未就绪(cells=%d settlement=%s)" % [bv._cells.size(), str(main.settlement)]) + if ms == null or bv.cells_probe().is_empty(): + _pre_fail("战斗未就绪(cells=%d settlement=%s)" % [bv.cells_probe().size(), str(main.settlement)]) return bv.action_requested.connect(func(c: Vector2i, kind: String): _actions.append([c, kind])) bv.chord_attempted.connect(func(c: Vector2i): _chords.append(c)) @@ -130,7 +131,7 @@ func _ready() -> void: # 并流批(ADR-0007 裁丙 2026-09-27)后首战板面种子随全局序漂移——_diggable(主角 8 邻未开域) # 不足两枚时,沿已开区真点击移位到「≥2 未开邻」的前沿格再选;移位失败即收手交由选取失败分支报状态 for _expand in range(16): - if bv._diggable.size() >= 2: + if bv.diggable_probe().size() >= 2: break var mv_t := Vector2i(-9, -9) for y in bv._board.height: @@ -157,7 +158,7 @@ func _ready() -> void: break var flag_t := Vector2i(-9, -9) var dig_t := Vector2i(-9, -9) - for c in bv._diggable: + for c in bv.diggable_probe(): if not ms.has_flag(c): if flag_t.x == -9: flag_t = c @@ -172,9 +173,9 @@ func _ready() -> void: # ---- case① 悬停可挖域:motion → 悬停态到达(金框 + 十字光标)---- _push_motion(_pos(bv, flag_t)) await get_tree().process_frame - if bv._hover != flag_t: - _problems.append("case① 悬停未到达(_hover=%s 应=%s)" % [bv._hover, flag_t]) - var u1 = bv._cells[flag_t] + if bv.hover_probe() != flag_t: + _problems.append("case① 悬停未到达(_hover=%s 应=%s)" % [bv.hover_probe(), flag_t]) + var u1 = bv.cells_probe()[flag_t] if not u1.hl_visible or u1.hl_color != UiKit.GOLD: _problems.append("case① 悬停金框缺失(visible=%s color=%s)" % [str(u1.hl_visible), str(u1.hl_color)]) if bv.mouse_default_cursor_shape != Input.CURSOR_CROSS: @@ -184,14 +185,14 @@ func _ready() -> void: await get_tree().process_frame if _forbids.is_empty() or _forbids[_forbids.size() - 1] != forbid_t: _problems.append("case② hover_forbidden 未达(%s)" % [str(_forbids)]) - if bv._cells[forbid_t].hl_color != UiKit.RED_FORBID: - _problems.append("case② 禁入红框缺失(color=%s)" % str(bv._cells[forbid_t].hl_color)) + if bv.cells_probe()[forbid_t].hl_color != UiKit.RED_FORBID: + _problems.append("case② 禁入红框缺失(color=%s)" % str(bv.cells_probe()[forbid_t].hl_color)) # ---- case③ 悬停已开格:路径预览点迹 + 移动光标 ---- _push_motion(_pos(bv, reveal_t)) await get_tree().process_frame var dots := 0 - for c in bv._cells: - if bv._cells[c].dot_visible: + for c in bv.cells_probe(): + if bv.cells_probe()[c].dot_visible: dots += 1 if dots < 1: _problems.append("case③ 路径预览点迹未亮(dots=0)") @@ -208,7 +209,7 @@ func _ready() -> void: # ---- case④b 旗纹理槽回归断言(P0-1 修复批 2026-10-02):T13 批曾在 paint 尾部无条件 # `u.tex_flag = null` 覆盖 T17 批的条件赋值=素材在位时插旗不可见(headless 缺盘走字形回退 # 掩盖)——有盘断言纹理在槽、缺盘断言字形回退,两分支防双写踩踏复活 ---- - var flag_unit = bv._cells.get(flag_t) + var flag_unit = bv.cells_probe().get(flag_t) if bv._flag_tex != null: if flag_unit == null or flag_unit.tex_flag != bv._flag_tex: _problems.append("case④b 旗纹理槽未挂封印旗纹理(tex_flag=%s)" % ["无单元" if flag_unit == null else str(flag_unit.tex_flag)]) @@ -298,7 +299,7 @@ func _ready() -> void: # 必须回退「人」(数字格仍显数字——回退判据与抑制判据同源);验毕还原纹理重绘。 ---- if bv._world.hero != null: var hp11: Vector2i = ms.hero_pos() - var u11 = bv._cells[hp11] + var u11 = bv.cells_probe()[hp11] var tex_saved: Texture2D = bv._hero_tex_idle bv._hero_tex_idle = null bv.refresh() @@ -315,23 +316,25 @@ func _ready() -> void: if mark_t.x >= 0: bv.set_variant_mark_resolver(func(_c): return "flat_damage") bv.refresh() - if String(bv._cells[mark_t].mutation_mark) != "flat_damage": - _problems.append("case⑭ 变异角标未落位(mutation_mark=%s)" % str(bv._cells[mark_t].mutation_mark)) + if String(bv.cells_probe()[mark_t].mutation_mark) != "flat_damage": + _problems.append("case⑭ 变异角标未落位(mutation_mark=%s)" % str(bv.cells_probe()[mark_t].mutation_mark)) bv.set_variant_mark_resolver(Callable()) bv.refresh() - if String(bv._cells[mark_t].mutation_mark) != "": - _problems.append("case⑭ 角标未随 resolver 复位(mutation_mark=%s)" % str(bv._cells[mark_t].mutation_mark)) + if String(bv.cells_probe()[mark_t].mutation_mark) != "": + _problems.append("case⑭ 角标未随 resolver 复位(mutation_mark=%s)" % str(bv.cells_probe()[mark_t].mutation_mark)) else: _problems.append("case⑭ 无未开格可验(板面异常)") # ---- case⑮ 视野迷雾(迭代 11 第④批 T5——三态呈态/半径圈/移动跟随/开关两态零扰动/提灯插值位; # 纯呈现零逻辑——迷雾开关切换前后 minesweeper 状态摘要逐位一致) ---- - if int(bv.FOG_RADIUS) != 8 or int(bv.FOG_LANTERN_BONUS) != 2 or int(bv._fog_lantern) != 0 or not bv._fog_enabled: + var fogp: Dictionary = bv.fog_params_probe() + if int(bv.FOG_RADIUS) != 8 or int(bv.FOG_LANTERN_BONUS) != 2 or int(fogp["lantern"]) != 0 or not fogp["enabled"]: _problems.append("case⑮ 迷雾参数漂移(R=%s bonus=%s lantern=%s enabled=%s 应=8/2/0/true)" - % [str(bv.FOG_RADIUS), str(bv.FOG_LANTERN_BONUS), str(bv._fog_lantern), str(bv._fog_enabled)]) + % [str(bv.FOG_RADIUS), str(bv.FOG_LANTERN_BONUS), str(fogp["lantern"]), str(fogp["enabled"])]) else: var hero15: Vector2i = ms.hero_pos() var b15 = ms.current_board() var fog_ok := true + var cells_f1: Dictionary = bv.cells_probe() for y in b15.height: for x in b15.width: var c15 := Vector2i(x, y) @@ -339,7 +342,7 @@ func _ready() -> void: var expect15: int = 0 if d15 > bv.FOG_RADIUS: expect15 = 2 if ms.is_revealed(c15) else 1 - var got15: int = int(bv._cells[c15].fog_state) + var got15: int = int(cells_f1[c15].fog_state) if got15 != expect15: fog_ok = false _problems.append("case⑮ 迷雾呈态不符(%s dist=%d got=%d expect=%d)" % [str(c15), d15, got15, expect15]) @@ -348,11 +351,12 @@ func _ready() -> void: break # 半径边界格恰在圈内(曼哈顿距离恰 =R 的格 fog_state=0——存在性校验,12×12 板必有) var has_boundary := false + var cells_f2: Dictionary = bv.cells_probe() for y in b15.height: for x in b15.width: if absi(x - hero15.x) + absi(y - hero15.y) == int(bv.FOG_RADIUS): has_boundary = true - if int(bv._cells[Vector2i(x, y)].fog_state) != 0: + if int(cells_f2[Vector2i(x, y)].fog_state) != 0: _problems.append("case⑮ 边界圈内格被误遮(dist==R 应 fog_state=0)") break if has_boundary: @@ -369,9 +373,10 @@ func _ready() -> void: digest_before.append(ms.is_revealed(c16)) bv.set_fog_enabled(false) var all_clear := true + var cells_f3: Dictionary = bv.cells_probe() for y in b15.height: for x in b15.width: - if int(bv._cells[Vector2i(x, y)].fog_state) != 0: + if int(cells_f3[Vector2i(x, y)].fog_state) != 0: all_clear = false if not all_clear: _problems.append("case⑮ 迷雾关未全板归零(off 态应 fog_state=0)") @@ -383,7 +388,7 @@ func _ready() -> void: if digest_off != digest_before: _problems.append("case⑮ 迷雾开关改写逻辑态(两态结算逐位一致 🔴 契约破)") bv.set_fog_enabled(true) - if int(bv._cells[Vector2i(0, 0)].fog_state) == 0 \ + if int(bv.cells_probe()[Vector2i(0, 0)].fog_state) == 0 \ and absi(0 - hero15.x) + absi(0 - hero15.y) > int(bv.FOG_RADIUS): _problems.append("case⑮ 迷雾复开后远角未复原遮蔽(fog_state=0)") if _problems.is_empty(): @@ -405,10 +410,11 @@ func _has_action(cell: Vector2i, kind: String) -> bool: func _pick_forbidden(bv, ms) -> Vector2i: var b = ms.current_board() + var digp: Dictionary = bv.diggable_probe() for y in b.height: for x in b.width: var c := Vector2i(x, y) - if not ms.is_revealed(c) and not bv._diggable.has(c): + if not ms.is_revealed(c) and not digp.has(c): return c return Vector2i(-9, -9) diff --git a/game/smoke/ui_probe.gd b/game/smoke/ui_probe.gd index d4ab4c06..e8451371 100644 --- a/game/smoke/ui_probe.gd +++ b/game/smoke/ui_probe.gd @@ -82,7 +82,7 @@ func _ready() -> void: if main.adventure == null or String(main.adventure.state) != "running": problems.append("首战中放弃未被拒(战斗域单事务在飞——state 应保持 running)") # ---- 既有断言(回归基线;A 区进度条断言随 停点前移批 2026-09-23 退役——节点链进度由关卡画面独立屏承载)---- - if main.board_view._cells.is_empty(): + if main.board_view.cells_probe().is_empty(): problems.append("棋盘格未生成(cells=0)") if main.board_view.board() == null: problems.append("board 未装载") @@ -228,7 +228,7 @@ func _ready() -> void: main._close_modal(main._end_overlay) if problems.is_empty(): print("[ui_probe] PASS — cells=%d board=%dx%d hp='%s' enemies=%d ap_dots=%d" % [ - main.board_view._cells.size(), main.board_view.board().width, main.board_view.board().height, + main.board_view.cells_probe().size(), main.board_view.board().width, main.board_view.board().height, main.info_panel.attack_text(), main.stage.enemy_box.get_child_count(), main.ap_bar._dots.get_child_count()]) get_tree().quit(0) From fec30644bdc80758887de45597e52cd24f3c0891 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sun, 4 Oct 2026 07:10:00 +0800 Subject: [PATCH 3/8] =?UTF-8?q?T3(D3):=20ArmedTimer=20helper=E2=80=94?= =?UTF-8?q?=E2=80=94=E4=B8=89=E5=A4=84=E6=AD=A6=E8=A3=85=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E8=AE=A1=E6=97=B6=E6=9C=BA=E5=88=B6=E6=94=B6=E5=8F=A3=EF=BC=88?= =?UTF-8?q?P4=E4=B9=99=EF=BC=9A=E7=BC=9D=E9=9B=B6=E5=8A=A8+=E8=A7=86?= =?UTF-8?q?=E8=A7=89=E9=9B=B6=E5=8F=98=E5=8C=96=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新件 game/presentation/armed_timer.gd(RefCounted):武装/回落/超时/减动效档四能力 (motion_still 档判单点;elapsed 相位取数——1Hz 连续呼吸视觉零变化;窗口<=0=挂起语义)。 三处迁移: ①AP 条弃图刷新(ap_bar_view):tween 秒表(4.0s)→ArmedTimer;三探针缝 press_refresh_for_probe/confirm_visible/press_confirm_for_probe 语义等价零动 (click 探针 :513-515 弃图确认链断言全绿实证);字面量 4.0 升 REFRESH_CONFIRM_WINDOW_S。 ②选路放弃钮(level_map_screen):_process 帧驱动倒计时+1Hz 呼吸→timer.tick+elapsed 相位; 减动效静态半透变体语义零变化(档判据归 motion_still);真实二击路径零改。 ③战斗总结结算框(main):_settlement_stop_pending 裸布尔→ArmedTimer 挂起态属性化 (窗口 0;读=armed/写=arm/disarm——探针与冒烟直读写接缝语义零变化;main 无 _process 无需 tick 线)。 闸:click 三种子绿(7/9191、11/7343、23/6026 帧)+冒烟 718/0+run_all 15+ui_probe 绿。 --- game/presentation/armed_timer.gd | 47 +++++++++++++++++++++ game/presentation/armed_timer.gd.uid | 1 + game/presentation/main.gd | 10 ++++- game/presentation/views/ap_bar_view.gd | 23 +++++----- game/presentation/views/level_map_screen.gd | 29 ++++++------- 5 files changed, 84 insertions(+), 26 deletions(-) create mode 100644 game/presentation/armed_timer.gd create mode 100644 game/presentation/armed_timer.gd.uid diff --git a/game/presentation/armed_timer.gd b/game/presentation/armed_timer.gd new file mode 100644 index 00000000..3c9b7e24 --- /dev/null +++ b/game/presentation/armed_timer.gd @@ -0,0 +1,47 @@ +# 武装确认计时器(迭代 13 T3/D3——P4 乙裁决:只统一计时+脉冲机制,视觉三处各自保留)。 +# 收口债:三处二次确认计时机制异构(AP 条弃图刷新=tween 秒表 / 选路放弃钮=_process 帧驱动 +# 倒计时 / 战斗总结结算框=纯载荷暂存裸布尔)——本件消掉「计时机制三种实现」漂移源。 +# 保缝纪律:AP 条三探针缝(press_refresh_for_probe/confirm_visible/press_confirm_for_probe) +# 语义等价零动;放弃钮真实二击路径零改;结算框挂起态经组合根属性继续可读写(探针接缝)。 +# 减动效档:档判据单点 motion_still()——脉冲相位由视觉层取 elapsed 自算(1Hz 连续呼吸 +# 视觉零变化),减动效档视觉层走静态呈现(放弃钮静态半透先例)。 +class_name ArmedTimer +extends RefCounted + +signal expired # 窗口到点回落边沿(仅 window_s > 0 时发出;宿主接回落视觉复位) + +var window_s := 0.0 # 超时窗口秒(<=0 = 无窗口——挂起直到显式回落,结算框语义) +var elapsed := 0.0 # 武装起算累计秒(视觉层呼吸相位取数;tick 累计) + +var _armed := false + + +# 档判据单点(减动效档四能力之一——两处视觉层共用,替代各自内联 UiKit.motion_scale 判断) +static func motion_still() -> bool: + return UiKit.motion_scale < 1.0 + + +func arm(window_override := -1.0) -> void: + if window_override >= 0.0: + window_s = window_override + _armed = true + elapsed = 0.0 + + +func disarm() -> void: + _armed = false + elapsed = 0.0 + + +func is_armed() -> bool: + return _armed + + +# 帧驱动(宿主 _process 委托;unarmed 或无窗口时零开销直返——结算框挂起态无需接 tick) +func tick(delta: float) -> void: + if not _armed or window_s <= 0.0: + return + elapsed += delta + if elapsed >= window_s: + disarm() + expired.emit() diff --git a/game/presentation/armed_timer.gd.uid b/game/presentation/armed_timer.gd.uid new file mode 100644 index 00000000..9df7ad34 --- /dev/null +++ b/game/presentation/armed_timer.gd.uid @@ -0,0 +1 @@ +uid://4avok0unhaek diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 724ac29e..691f1587 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -53,7 +53,15 @@ var _level_active := false var _seed_edit: LineEdit var _resume_btn: Button # 继续上局按钮句柄(迭代 8 卡②——有断点才亮) var confirm_dialog: ConfirmDialog # 通用确认弹窗(二轮修改批 A——新冒险互斥确认弹窗化) -var _settlement_stop_pending := false # 卡⑥ 战斗胜利总结结算框挂起态(确认「下一节点」后进选路页) +var _settlement_stop_timer := ArmedTimer.new() # 卡⑥ 结算框挂起计时(T3/D3 P4 乙——裸布尔暂存机制收口;窗口 0=挂起到显式回落) +var _settlement_stop_pending: bool: # 挂起态属性化(探针/冒烟直读写接缝语义零变化——读=armed,写=arm/disarm) + get: + return _settlement_stop_timer.is_armed() + set(v): + if v: + _settlement_stop_timer.arm() + else: + _settlement_stop_timer.disarm() var _pending_candidates: Array = [] # 结算框挂起期的选路候选暂存(choices_presented 载荷) var _last_gain_amount := 0 # 最近一笔钱包入账(卡⑥ GP 增量取数面——wallet_changed gain 载荷) var _pending_bonus := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用) diff --git a/game/presentation/views/ap_bar_view.gd b/game/presentation/views/ap_bar_view.gd index 128057c1..cba6b97b 100644 --- a/game/presentation/views/ap_bar_view.gd +++ b/game/presentation/views/ap_bar_view.gd @@ -31,12 +31,15 @@ var _rap_label: Label var _rap_feedback: Label var _last_rap := 0 # 上次投影值(清零反馈判据——禁解析自身 label 文本当状态) var refresh_cost: int = 0 # 表值驱动(装配经 set_refresh_cost 注入 ActionEconomyTable.rap_refresh_cost) -var _confirm_armed := false -var _arm_tween: Tween +var _arm_timer := ArmedTimer.new() # 弃图刷新确认窗(T3/D3 P4 乙——tween 秒表机制收口;窗口=REFRESH_CONFIRM_WINDOW_S) + + +const REFRESH_CONFIRM_WINDOW_S := 4.0 # 弃图刷新确认窗(原 tween_interval 字面量升常量——语义零变化) func _init() -> void: custom_minimum_size = Vector2(0, 44) + _arm_timer.expired.connect(_disarm) var sb := UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, 0, 1, Vector4(8, 4, 8, 4)) add_theme_stylebox_override("panel", sb) @@ -186,7 +189,7 @@ func confirm_visible() -> bool: func press_confirm_for_probe() -> void: - if _confirm_armed: + if _arm_timer.is_armed(): _disarm_and_fire() @@ -200,12 +203,7 @@ func _on_refresh_pressed() -> void: if _btn_refresh.disabled: return _confirm_row.visible = true - _confirm_armed = true - if _arm_tween != null and _arm_tween.is_valid(): - _arm_tween.kill() - _arm_tween = create_tween() - _arm_tween.tween_interval(4.0) - _arm_tween.tween_callback(_disarm) + _arm_timer.arm(REFRESH_CONFIRM_WINDOW_S) func _disarm_and_fire() -> void: @@ -214,10 +212,15 @@ func _disarm_and_fire() -> void: func _disarm() -> void: - _confirm_armed = false + _arm_timer.disarm() _confirm_row.visible = false +# 帧驱动(T3/D3——超时回落走 ArmedTimer;unarmed 零开销直返) +func _process(delta: float) -> void: + _arm_timer.tick(delta) + + # 资源面拒绝反馈(pattern 1 resource_face 腿):AP 数字脉冲 func pulse_ap() -> void: _pulse(_ap_label) diff --git a/game/presentation/views/level_map_screen.gd b/game/presentation/views/level_map_screen.gd index 74155e7e..a03bb209 100644 --- a/game/presentation/views/level_map_screen.gd +++ b/game/presentation/views/level_map_screen.gd @@ -61,14 +61,14 @@ var _cand_ids: Array[int] = [] # 本停点候选 id 集(图上命 var _content_size := Vector2(620, 240) # 图内容包络(open_choices 按图拟合——画布 min 尺寸随它) var _hover_id := -1 # 悬停预承诺候选(-1 = 无) var abandon_btn: Button # 放弃入口(可见性归组合根 set_abandon_available——只在冒险模式开) -var _abandon_armed := false # 二次确认态(首击置位、二击提交、超时/重开回落) -var _confirm_left := 0.0 # 确认态剩余秒数(_process 倒计时+脉冲驱动) +var _abandon_timer := ArmedTimer.new() # 放弃确认窗(T3/D3 P4 乙——_process 帧驱动倒计时机制收口;窗口=CONFIRM_WINDOW_S) var _node_icons := {} # 节点图标贴图(load_tex 守卫——缺位 null → 字形回退,冒烟/无素材检出安全) var _noise_rect: TextureRect # 屏底石纹(换层色板渐变承载面——null = 纯色回退无挂点) var _tint_level := 0 # 当前已应用 tint 的层号(0 = 未应用) func _init() -> void: + _abandon_timer.expired.connect(_reset_abandon_confirm) # 节点图标贴图装载(样张 v1 冻结形制——load_tex 守卫,缺位回退现行字形) for t in TEX_NODE_ICONS: _node_icons[t] = UiKit.load_tex(TEX_NODE_ICONS[t]) @@ -201,34 +201,33 @@ func set_abandon_available(v: bool) -> void: # 放弃二次确认(布局决策 3):首击转确认态(文案+危险红脉冲约 3 秒超时回落),二击才提交。 # 单击不提交——ui_probe 战斗中 pressed.emit() 单击路径保持「未受理」语义(战斗中拒绝归组合根)。 func _on_abandon_pressed() -> void: - if not _abandon_armed: - _abandon_armed = true - _confirm_left = CONFIRM_WINDOW_S + if not _abandon_timer.is_armed(): + _abandon_timer.arm(CONFIRM_WINDOW_S) abandon_btn.text = "再点一次确认放弃——不可逆" + if ArmedTimer.motion_still(): + abandon_btn.modulate = Color(1, 1, 1, 0.75) # 减动效静态基色(脉冲层不再驱动) return _reset_abandon_confirm() abandon_requested.emit() func _reset_abandon_confirm() -> void: - _abandon_armed = false - _confirm_left = 0.0 + _abandon_timer.disarm() abandon_btn.modulate = Color(1, 1, 1, 1) abandon_btn.text = "放弃" # 打磨包 2026-09-29——文案收敛 level-map 4 字上限(确认态转义面见 _on_abandon_pressed) func _process(delta: float) -> void: - if not _abandon_armed: + if not _abandon_timer.is_armed(): return - _confirm_left -= delta - if _confirm_left <= 0.0: - _reset_abandon_confirm() - return - # 危险红边框脉冲(单段呼吸约 1Hz——闪烁基线 ≤3 次/秒内;减动效 = 静态半透不闪) - if UiKit.motion_scale < 1.0: + _abandon_timer.tick(delta) + if not _abandon_timer.is_armed(): + return # 窗口到点回落(expired 信号已驱动 _reset_abandon_confirm) + # 危险红边框脉冲(单段呼吸约 1Hz——闪烁基线 ≤3 次/秒内;减动效 = 静态半透不闪——档判据单点 motion_still) + if ArmedTimer.motion_still(): abandon_btn.modulate = Color(1, 1, 1, 0.75) else: - var k := 0.5 + 0.5 * sin(_confirm_left * TAU) + var k := 0.5 + 0.5 * sin(_abandon_timer.elapsed * TAU) abandon_btn.modulate = Color(1, 1, 1, 0.65 + 0.35 * k) From 4c7931064e2726a31aaf1f77062ec8e51328515f Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sun, 4 Oct 2026 07:38:48 +0800 Subject: [PATCH 4/8] =?UTF-8?q?T4(D4):=20main.gd=20=E4=BA=8C=E6=9C=9F?= =?UTF-8?q?=E7=98=A6=E8=BA=AB=E2=80=94=E2=80=94=E5=AE=B6=E6=97=8F=E2=91=A0?= =?UTF-8?q?=E4=B8=89=E9=80=89=E4=B8=80/=E6=9B=BF=E6=8D=A2=E5=AD=90+?= =?UTF-8?q?=E5=AE=B6=E6=97=8F=E2=91=A2=E8=90=A5=E5=9C=B0=E5=95=86=E5=BA=97?= =?UTF-8?q?=E6=8A=95=E5=BD=B1=E6=8B=86=E4=BB=B6=EF=BC=881378=E2=86=921119?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新件两枚(RefCounted,仿 aux_interact_controller 房内范式): - bonus_interact_controller.gd(241行):三选一停点编排 on_presented/present_node_bonus/ show_pick/on_refresh/on_pick_confirmed/on_aux_replace_presented/on_replace_chosen/ on_resolved/on_continue + TP0 复合键卡片装配 + 工坊名投影;_pending_bonus/_bonus_context 随迁;兑付写调用唯一经 adventure.pick_bonus/apply_node_bonus/resolve_bonus_replace。 - camp_shop_presenter.gd(172行):营地开合/快照/锻造·同调·天赋树·Fork 四写口 + 商店 pool/product 双面购买/离开;写调用唯一经 MetaProgression 原语与 LevelRunner.buy_*。 接线留组合根(ADR-0001):overlay/adventure 十三信号由组合根接至控制器 on_* 入口; 模态栈/演播后排窗/呈现刷新/fatal 经回调注入;meta/save_manager/adventure/runner 现值 回调注入——探针 isolate_save 换注 main.meta 实证 ctor 捕获会记错账(营地购买未出账), 现值回调为房内先例口径。 家族②debug 接线收口=契约3 组合根绑定面(settlement 写+_wire_battle+override/seed 取数 在组合根 UI 态)不迁移,节头注记记档——任务书族级估 ~50 行与 ADR-0001 相抵,处置在 代拍白名单「拆件归属」内。 探针缝保缝:main._on_camp_closed/_camp_data 保缝转发(click/ui_probe 直呼面); bonus_overlay 主类公有字段维持。 行数对照:1378→1119(净减 259);对照任务书锚 ~900±50(净减 ~430)差 -160,归因= 三家族可迁清单实测上限 ~336 行(任务书自估 315),锚位与族级清单不自洽——差异进出口 报告呈用户。闸:三种子 click 绿(7/9117、11/10202、23/9156)+冒烟 718/0+run_all 15+ui_probe。 --- .../presentation/bonus_interact_controller.gd | 241 ++++++++++++ .../bonus_interact_controller.gd.uid | 1 + game/presentation/camp_shop_presenter.gd | 172 +++++++++ game/presentation/camp_shop_presenter.gd.uid | 1 + game/presentation/main.gd | 353 +++--------------- 5 files changed, 462 insertions(+), 306 deletions(-) create mode 100644 game/presentation/bonus_interact_controller.gd create mode 100644 game/presentation/bonus_interact_controller.gd.uid create mode 100644 game/presentation/camp_shop_presenter.gd create mode 100644 game/presentation/camp_shop_presenter.gd.uid diff --git a/game/presentation/bonus_interact_controller.gd b/game/presentation/bonus_interact_controller.gd new file mode 100644 index 00000000..6abe4178 --- /dev/null +++ b/game/presentation/bonus_interact_controller.gd @@ -0,0 +1,241 @@ +# VERTICAL SLICE - NOT FOR PRODUCTION +# 提升三选一与替换子交互控制器(迭代 13 T4/D4——二期家族① 自 main.gd 迁出;规范 1.1 单一职责): +# 三选一停点编排(presented→pick→confirm/refresh→resolved→continue)+ 同调位替换子态 +#(presented→chosen)+ TP0 令牌池卡片文案装配("kind|tier|relic_id" 复合键)+ 工坊显示名投影。 +# 装配纪律(ADR-0001,仿 aux_interact_controller 先例):信号连接不在此发生——bonus_overlay +# 四信号与 adventure 两信号由组合根接至本类 on_* 入口;模态栈/演播后排窗/呈现刷新/终局面/ +# 推进失败 fatal 经组合根回调注入;adventure/runner 随局更替——现值回调注入;兑付写调用唯一 +# 经 adventure.pick_bonus/apply_node_bonus/resolve_bonus_replace(core 侧单口,本类零直写)。 +extends RefCounted + +const Narrator := preload("res://presentation/battle_narrator.gd") + +var db +var _meta_get: Callable # () → 元进度账本现值(组合根 lambda——探针 isolate_save 换注面,迭代13 T4 实证) +var bonus_overlay: BonusPickOverlay +var end_overlay: BattleEndOverlay +var board_view: BoardView +var toast: ToastStack +var _save_get: Callable # () → 存档承载现值(同上——Attunement.slots 读数面) +var _adventure_get: Callable # () → 冒险结构现值(组合根 lambda——随局更替) +var _runner_get: Callable # () → 关卡推进器现值(组合根 lambda——reward_node 继续推进) +var _adventure_mode_get: Callable # () → 冒险模式标记现值(组合根 lambda) +var _open_modal: Callable # 模态栈单口(组合根) +var _open_modal_after_playback: Callable # 演播后排窗单口(T17——组合根) +var _close_modal: Callable +var _refresh_presentations: Callable +var _fatal_screen: Callable +var _gp_summary: Callable # () → 战斗总结 GP 行文案(组合根 lambda——_last_gain_amount 取数面在根) + +var _pending_bonus := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用) +var _bonus_context := "level_clear" # 三选一面上下文(level_clear=通关停点→推进下一关 / reward_node=奖励节点→回图选路) +var _last_replace_items: Array = [] # 替换子态续玩重建缓存(aux_replace_presented 载荷) +var _last_replace_incoming := {} # 同上——incoming 记录 + + +func _init(db_ref, meta_get: Callable, bonus_overlay_ref: BonusPickOverlay, end_overlay_ref: BattleEndOverlay, + board_view_ref: BoardView, toast_ref: ToastStack, save_get: Callable, + adventure_get: Callable, runner_get: Callable, adventure_mode_get: Callable, + open_modal: Callable, open_modal_after_playback: Callable, close_modal: Callable, + refresh_presentations: Callable, fatal_screen: Callable, gp_summary: Callable) -> void: + db = db_ref + _meta_get = meta_get + bonus_overlay = bonus_overlay_ref + end_overlay = end_overlay_ref + board_view = board_view_ref + toast = toast_ref + _save_get = save_get + _adventure_get = adventure_get + _runner_get = runner_get + _adventure_mode_get = adventure_mode_get + _open_modal = open_modal + _open_modal_after_playback = open_modal_after_playback + _close_modal = close_modal + _refresh_presentations = refresh_presentations + _fatal_screen = fatal_screen + _gp_summary = gp_summary + + +# 停点在途判据(组合根 _on_battle_end_continue 分派面——战斗总结确认后开三选一) +func has_pending() -> bool: + return not _pending_bonus.is_empty() + + +# 续玩停点恢复入口(true=已接手呈现,调用方中止后续分派) +func resume_if_pending() -> bool: + if _pending_bonus.is_empty(): + return false + show_pick() + return true + + +# 三选一停点(非末关通关恰一次):Boss 胜利先经战斗总结结算面(settlement-summary v1.1——钥石 +# 增量行在其上),确认后进三选一面(level-clear.md v2);续玩停点恢复同入口(零自动推进契约) +func on_presented(payload: Dictionary) -> void: + if int(payload.get("aux_cleared", 0)) > 0: + toast.show_toast("上关剩余 %d 件魔法道具已随关卡结束清空" % int(payload.get("aux_cleared", 0)), "info") # 迭代 7 T4 清空反馈 + _pending_bonus = payload.duplicate(true) + _bonus_context = "level_clear" + if bool(payload.get("replace_pending", false)): + # 续玩:替换子交互在途中断——重建三选一面后直落替换子态(候选不重抽) + show_pick() + return + end_overlay.open_victory(Narrator.battle_end_text("victory", false), "继续", + board_view.get_global_rect(), + "%s\n%s" % [String(_gp_summary.call()), Narrator.bonus_keystone_line(payload)]) + _open_modal_after_playback.call(end_overlay) + _refresh_presentations.call() + + +# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 aux_replace_presented 续发直落子态) +# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;BonusPickOverlay 共用面, +# 道具件满位替换子交互同构)。原「魔法道具投放渠道」三选一(迭代 7 过渡形态)随批退役。 +func present_node_bonus() -> void: + var adventure = _adventure_get.call() + _bonus_context = "reward_node" + var node_draw: Dictionary = adventure.node_bonus_candidates() + var node_cands: Array = node_draw.get("candidates", []) as Array + _open_modal.call(bonus_overlay) + bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(node_cands, node_draw.get("aux_detail", []) as Array)) + adventure.present_node_bonus(node_cands, node_draw.get("aux_detail", []) as Array) + bonus_overlay.set_refresh_info(adventure.attune_refresh_left()) # 迭代 11 第①批 T5b——刷新余量投喂 + + +# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 aux_replace_presented 续发直落子态) +func show_pick() -> void: + var payload: Dictionary = _pending_bonus + _open_modal.call(bonus_overlay) + bonus_overlay.open_pick(Narrator.bonus_pick_header(payload), _bonus_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array)) + var adventure = _adventure_get.call() + bonus_overlay.set_refresh_info(adventure.attune_refresh_left() if adventure != null else 0) # 迭代 11 第①批 T5b——刷新余量投喂 + _refresh_presentations.call() + + +func _bonus_card_texts(candidates: Array, aux_detail: Array = []) -> Array: + # TP0 令牌池——候选 = {"kind","tier"} 令牌字典;卡片 id = "kind|tier|relic_id" 复合键(同 kind + # 大小两档/同档多 relic 可同屏,kind 单键不构成唯一身份),确认回程拆键(on_pick_confirmed) + var out: Array = [] + var aux_i := 0 + for tok in candidates: + var kind := String((tok as Dictionary).get("kind", "")) + var tier := String((tok as Dictionary).get("tier", "")) + var rid := String((tok as Dictionary).get("relic_id", "")) + var cid := "%s|%s|%s" % [kind, tier, rid] + var text := "" + if kind == "relic": + text = Narrator.relic_card_text(db.relic_row(rid)) + elif kind == "aux_item": + var iid := String(aux_detail[aux_i]) if aux_i < aux_detail.size() else "" + aux_i += 1 + text = Narrator.bonus_card_text(kind, tier, int(db.adventure.bonus_value(kind, tier)), _aux_card_line(iid)) + else: + text = Narrator.bonus_card_text(kind, tier, int(db.adventure.bonus_value(kind, tier))) + out.append({"id": cid, "text": text}) + return out + + +# 道具件卡片行(掷定前置——具体道具名 + 效果摘要;空 iid = 无预掷回退态显示通用文案) +func _aux_card_line(iid: String) -> String: + if iid == "": + return "" + var row: Dictionary = db.aux_item_row(iid) + if row.is_empty(): + return "" + var parts: Array[String] = [] + for k in (row.get("effect_kinds", []) as Array): + if String(k) == "mine_reveal": + parts.append("真视") + elif String(k) == "reward_preview": + parts.append("预读") + elif String(k) == "ap_grant": + parts.append("AP +%d" % int(row.get("magnitude", 0))) + var tgt := String(row.get("target", "none")) + if tgt == "area_3x3": + parts.append("3×3") + elif tgt == "board_wide": + parts.append("全图") + return "%s · %s" % [String(row.get("display_name", iid)), " ".join(parts)] + + +# 三选一刷新(迭代 11 第①批 T5b——P1 甲 X/Y 双源;通关停点经重发 bonus_choice_presented 自然重建, +# 节点停点在此读在途态重 present——零自动推进契约不破,停点仍在途) +func on_refresh() -> void: + var adventure = _adventure_get.call() + if adventure == null: + return + if not adventure.refresh_bonus(): + return + if _bonus_context == "reward_node": + var st: Dictionary = adventure.node_bonus_pending_state() + bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(st.get("candidates", []) as Array, st.get("aux_detail", []) as Array)) + bonus_overlay.set_refresh_info(adventure.attune_refresh_left()) + + +func on_pick_confirmed(item_id: String) -> void: + var adventure = _adventure_get.call() + if adventure == null: + return + # TP0 令牌池——卡片 id "kind|tier|relic_id" 拆键(缺 | = 旧载荷防御归普通档;relic 三段) + var parts := item_id.split("|") + var kind := parts[0] + var tier := parts[1] if parts.size() > 1 else "" + var rid := parts[2] if parts.size() > 2 else "" + var ok: bool = adventure.apply_node_bonus(kind, tier, rid) if _bonus_context == "reward_node" else adventure.pick_bonus(kind, tier, rid) + if not ok: + push_error("[bonus_interact] 三选一兑付被拒——见输出日志") + + +func on_aux_replace_presented(items: Array, incoming: Dictionary) -> void: + _last_replace_items = items.duplicate(true) + _last_replace_incoming = incoming.duplicate(true) + if not bonus_overlay.visible: + _open_modal.call(bonus_overlay) # 续玩替换在途直落——三选一面未开时先入栈(模态栈纪律) + _open_replace(items, incoming) + + +func _open_replace(items: Array, incoming: Dictionary) -> void: + var named: Array = [] + for it in items: + var rec: Dictionary = it.duplicate() + rec["display_name"] = String(item_name_lookup().get(String(rec.get("item_id", "")), String(rec.get("item_id", "")))) + named.append(rec) + bonus_overlay.open_replace(named, String(item_name_lookup().get(String(incoming.get("item_id", "")), String(incoming.get("item_id", "")))), + named.size(), Attunement.slots(db, _save_get.call())) # 迭代 11 第①批 T7——呈现读数源切谓词表求值(过渡键退役) + + +func on_replace_chosen(discard: int) -> void: + var adventure = _adventure_get.call() + if adventure == null or not adventure.resolve_bonus_replace(discard): + push_error("[bonus_interact] 同调位替换受理被拒——见输出日志") + + +# 兑付反馈(bonus_resolved 到达——结果行上屏,唯一出口 = 继续) +func on_resolved(result: Dictionary) -> void: + _pending_bonus = {} + bonus_overlay.show_result(Narrator.bonus_result_text(result)) + _refresh_presentations.call() + + +# 继续钮:收三选一面 → 上下文分派(通关停点=推进下一关 / 奖励节点=兑付确认回图选路——零自动推进) +func on_continue() -> void: + _close_modal.call(bonus_overlay) + bonus_overlay.close_pick() + var adventure = _adventure_get.call() + if _bonus_context == "reward_node": + var runner = _runner_get.call() + if runner != null: + runner.continue_after_node() + return + if bool(_adventure_mode_get.call()) and adventure != null: + if not adventure.continue_to_next_level(): + _fatal_screen.call("关卡推进失败——见输出日志") + + +# 工坊 item_id → 显示名投影(keystone_settle_text/替换子命名文案数据源——拼句归 Narrator) +func item_name_lookup() -> Dictionary: + var lookup := {} + var meta = _meta_get.call() + if meta != null: + for row in db.meta_workshop_items(): + lookup[String(row.get("item_id", ""))] = String(row.get("display_name", "")) + return lookup diff --git a/game/presentation/bonus_interact_controller.gd.uid b/game/presentation/bonus_interact_controller.gd.uid new file mode 100644 index 00000000..137a8dcc --- /dev/null +++ b/game/presentation/bonus_interact_controller.gd.uid @@ -0,0 +1 @@ +uid://cp308xdkheml3 diff --git a/game/presentation/camp_shop_presenter.gd b/game/presentation/camp_shop_presenter.gd new file mode 100644 index 00000000..5662abf6 --- /dev/null +++ b/game/presentation/camp_shop_presenter.gd @@ -0,0 +1,172 @@ +# VERTICAL SLICE - NOT FOR PRODUCTION +# 营地/商店投影控制器(迭代 13 T4/D4——二期家族③ 自 main.gd 迁出;规范 1.1 单一职责): +# 营地面开合/快照投影/锻造·同调·天赋树·Fork 购买与切换 + 商店房购买/离开(pool 与 product 双面)。 +# 装配纪律(ADR-0001,仿 aux_interact_controller 先例):信号连接不在此发生——campbtn/ +# camp_overlay 四信号/shop_overlay 两信号由组合根接至本类 on_* 入口;模态栈/呈现刷新/续玩按钮 +# 刷新经组合根回调注入;runner 随局更替——现值回调注入;写调用唯一经 MetaProgression 购买原语 +# 与 LevelRunner.buy_*(core 侧单口,本类零直写——拒绝可见 pattern 1 兜底 toast 保留原地)。 +extends RefCounted + +var db +var _meta_get: Callable # () → 元进度账本现值(组合根 lambda——探针 isolate_save 换注面,迭代13 T4 实证) +var _save_get: Callable # () → 存档承载现值(同上——营地 recent 摘要读数面) +var camp_overlay: CampOverlay +var shop_overlay: ShopOverlay +var toast: ToastStack +var start_overlay: Control # 开始层互斥面(开合仿 TunePanel 先例——非模态且树序在后,规范 2.3) +var logger # 试玩日志(ux_reject 取数面) +var _runner_get: Callable # () → 关卡推进器现值(组合根 lambda——随局更替) +var _open_modal: Callable +var _close_modal: Callable +var _refresh_presentations: Callable +var _refresh_resume_button: Callable + + +func _init(db_ref, meta_get: Callable, save_get: Callable, camp_overlay_ref: CampOverlay, + shop_overlay_ref: ShopOverlay, toast_ref: ToastStack, start_overlay_ref: Control, + logger_ref, runner_get: Callable, open_modal: Callable, close_modal: Callable, + refresh_presentations: Callable, refresh_resume_button: Callable) -> void: + db = db_ref + _meta_get = meta_get + _save_get = save_get + camp_overlay = camp_overlay_ref + shop_overlay = shop_overlay_ref + toast = toast_ref + start_overlay = start_overlay_ref + logger = logger_ref + _runner_get = runner_get + _open_modal = open_modal + _close_modal = close_modal + _refresh_presentations = refresh_presentations + _refresh_resume_button = refresh_resume_button + + +# 打开营地(主菜单入口——开合与开始层互斥仿 TunePanel 先例:开始覆盖层非模态且树序在后, +# 规范 2.3 遮挡纪律;打开时先刷工坊解锁——达标未解锁就地入账) +func open_camp() -> void: + var meta = _meta_get.call() + if meta == null: + return + meta.refresh_workshop_unlocks() + start_overlay.visible = false + _open_modal.call(camp_overlay) # 模态栈单口(T3 收口批——登记层开合一律走栈) + camp_overlay.open_camp(camp_data()) + _refresh_presentations.call() + + +func on_camp_closed() -> void: + _close_modal.call(camp_overlay) # 模态栈单口(T3 收口批) + start_overlay.visible = true + _refresh_resume_button.call() # 迭代 8 卡②——回菜单按断点在档态刷新 + _refresh_presentations.call() # 关闭后刷新顶栏(工坊解锁可能变了余额侧读数面) + + +# 营地快照投影(纯读——锻造三行/工坊六行/余额/最近冒险;拼句归 CampOverlay,账面归 MetaProgression) +func camp_data() -> Dictionary: + var meta = _meta_get.call() + var forge_rows: Array[Dictionary] = [] + if meta != null: + for attr in MetaTable.FORGE_ATTRS: + var row: Dictionary = db.meta_forge_row(attr) + forge_rows.append({ + "attr": attr, + "display_name": String(row.get("display_name", "")), + "level": meta.forge_level(attr), + "flat": int(row.get("flat_value", 0)), + "price": meta.forge_price(attr), + }) + return { + "balance": meta.keystone_balance() if meta != null else 0, + "forge": forge_rows, + "workshop": meta.workshop_status() if meta != null else [], + "attune": meta.attune_camp_status() if meta != null else {}, # 迭代 11 第①批 T4——同调装配面投影 + "tree": meta.tree_camp_rows() if meta != null else [], # 迭代 12 T9——天赋树面投影 + "fork_groups": meta.tree_fork_groups() if meta != null else [], + "recent": _save_get.call().recent_summaries() if _save_get.call() != null else [], + } + + +func on_camp_buy(attr: String) -> void: + var meta = _meta_get.call() + if meta == null: + return + if attr == "attune_refresh": + # Y 刷新升级行购买(迭代 11 第①批 T4——P1 甲;按钮禁用为常态防线,兜底拒绝可见 pattern 1) + if not meta.buy_attune_refresh(): + toast.show_toast("钥石不足——买不起这次升级", "warn") + return + camp_overlay.refresh_camp(camp_data()) + _refresh_presentations.call() + return + if not meta.buy_forge(attr): + # 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1) + toast.show_toast("钥石不足——买不起这件强化", "warn") + return + camp_overlay.refresh_camp(camp_data()) + _refresh_presentations.call() + + +# 同调装配切换(迭代 11 第①批 T4——装配关系唯一写口经 MetaProgression.toggle_attunement;满位兜底拒绝可见) +func on_camp_attune_toggle(item_id: String) -> void: + var meta = _meta_get.call() + if meta == null: + return + if not meta.toggle_attunement(item_id): + toast.show_toast("同调位已满——先卸下再装配", "warn") + return + camp_overlay.refresh_camp(camp_data()) + + +# 天赋树购买(迭代 12 T9——写口唯一归 MetaProgression.tree_buy/tree_buy_fork;Fork 节点购=激活侧) +func on_camp_tree_buy(node_id: String) -> void: + var meta = _meta_get.call() + if meta == null: + return + var ok: bool = meta.tree_buy_fork(node_id) if String(db.tree_node(node_id).get("kind", "")) == "fork" else meta.tree_buy(node_id) + if not ok: + toast.show_toast("购买被拒——前置/行门槛/余额不足", "warn") + return + camp_overlay.refresh_camp(camp_data()) + _refresh_presentations.call() + + +# A/B 免费切换(迭代 12 T8——同组换侧零币;未激活组走购买面) +func on_camp_fork_toggle(group: String, side: String) -> void: + var meta = _meta_get.call() + if meta == null: + return + if not meta.tree_toggle_fork(group, side): + toast.show_toast("切换被拒——未激活组请先购买", "warn") + return + camp_overlay.refresh_camp(camp_data()) + _refresh_presentations.call() + + +func on_shop_buy(product_id: String) -> void: + var runner = _runner_get.call() + if runner == null: + return + if runner.is_shop_pool(): + # pool 面:买 1 → 核心全重抽——货架整架重投喂(重抽反馈;余额不足核心拒绝零副作用) + if not runner.buy_pool_item(product_id): + logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"}) + toast.show_toast("金币不足——买不起这件商品", "warn") + return + shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp)) + _refresh_presentations.call() + return + if not runner.buy_product(product_id): + # 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1) + logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"}) + toast.show_toast("金币不足——买不起这件商品", "warn") + return + shop_overlay.refresh_state(runner.shop_products(), int(runner.gp)) + _refresh_presentations.call() + + +func on_shop_leave() -> void: + _close_modal.call(shop_overlay) + var runner = _runner_get.call() + if runner != null: + # 复用兑付确认纪律(接口漂移清偿):离开 ≠ 推进——确认后经 choices_presented 弹出选路口 + runner.continue_after_node() diff --git a/game/presentation/camp_shop_presenter.gd.uid b/game/presentation/camp_shop_presenter.gd.uid new file mode 100644 index 00000000..f474e102 --- /dev/null +++ b/game/presentation/camp_shop_presenter.gd.uid @@ -0,0 +1 @@ +uid://b3t0ocw10p5rs diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 691f1587..baca32f8 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -38,6 +38,8 @@ const DebugSessionScript := preload("res://presentation/debug_session.gd") const RowPacer := preload("res://presentation/row_pacer.gd") # 结算行排播泵(T3 拆件批) const AuxInteractController := preload("res://presentation/aux_interact_controller.gd") # 魔法道具两步交互(T3 拆件批) const GpShadowLedgerScript := preload("res://presentation/gp_shadow_ledger.gd") # GP 影子账本(T3 拆件批) +const BonusInteractController := preload("res://presentation/bonus_interact_controller.gd") # 三选一与替换子交互(T4/D4 拆件批) +const CampShopPresenter := preload("res://presentation/camp_shop_presenter.gd") # 营地/商店投影(T4/D4 拆件批) const SaveManagerScript := preload("res://core/save_manager.gd") # 存档承载(preload 避开全局类缓存时序——与 DebugSessionScript 同款) const MetaProgressionScript := preload("res://core/meta_progression.gd") # 元进度账本服务(元进度实现批——同 preload 纪律) @@ -64,9 +66,7 @@ var _settlement_stop_pending: bool: # 挂起态属性化(探针/冒烟直读 _settlement_stop_timer.disarm() var _pending_candidates: Array = [] # 结算框挂起期的选路候选暂存(choices_presented 载荷) var _last_gain_amount := 0 # 最近一笔钱包入账(卡⑥ GP 增量取数面——wallet_changed gain 载荷) -var _pending_bonus := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用) var bonus_overlay: BonusPickOverlay # 提升三选一覆盖层(迭代 10——level-clear.md v2 选择面承载;追加批起奖励节点共用) -var _bonus_context := "level_clear" # 三选一面上下文(level_clear=通关停点→推进下一关 / reward_node=奖励节点→回图选路) var _start_overlay: Control var _end_overlay: BattleEndOverlay var _inter_overlay: Control @@ -91,6 +91,8 @@ var settlement_fx: SettlementFxView # 结算演出覆盖层(跨区弹道 # T3 拆件批(2026-10-02)——三件迁出后的组合根持有面(职责见各类头注;装配见 _build_ui) var _row_pacer = null # 结算行排播泵(row_pacer.gd——本类只入队/排空/读队列) var _aux = null # 魔法道具两步交互(aux_interact_controller.gd——本类只接线与转发意图) +var _bonus = null # 三选一与替换子交互(bonus_interact_controller.gd——本类只接线与回调注入) +var _camp_pres = null # 营地/商店投影(camp_shop_presenter.gd——本类只接线、探针缝保缝转发) var _gp_ledger := GpShadowLedgerScript.new() # GP 冒险钱包影子账(gp_shadow_ledger.gd——wallet 事件投喂) # GP 影子账委托面(探针/终局面板取数保留——ui_probe 合成注入经 set 转发账本,读面即账本实况) @@ -259,13 +261,6 @@ func _build_ui() -> void: node_overlay.continue_pressed.connect(_on_node_continue) level_screen.route_chosen.connect(_on_route_chosen) level_screen.abandon_requested.connect(_on_abandon_requested) # 放弃入口(2026-09-21 用户拍板——台账 119) - shop_overlay.buy_requested.connect(_on_shop_buy) - shop_overlay.leave_pressed.connect(_on_shop_leave) - camp_overlay.buy_requested.connect(_on_camp_buy) - camp_overlay.attune_toggled.connect(_on_camp_attune_toggle) - camp_overlay.tree_buy_requested.connect(_on_camp_tree_buy) - camp_overlay.tree_fork_toggled.connect(_on_camp_fork_toggle) - camp_overlay.closed.connect(_on_camp_closed) _end_overlay.review_pressed.connect(_on_end_review) _end_overlay.continue_pressed.connect(_on_battle_end_continue) tune_panel.closed.connect(_close_tune_panel) @@ -277,10 +272,17 @@ func _build_ui() -> void: # 提升三选一覆盖层(迭代 10——z 序 = 节点层之上、终局层之下;确认后同拍推进) bonus_overlay = BonusPickOverlay.new() add_child(bonus_overlay) - bonus_overlay.pick_confirmed.connect(_on_bonus_pick_confirmed) - bonus_overlay.replace_chosen.connect(_on_bonus_replace_chosen) - bonus_overlay.continue_pressed.connect(_on_bonus_continue) - bonus_overlay.refresh_requested.connect(_on_bonus_refresh) + # 提升三选一与替换子交互(T4/D4 拆件批——迁 bonus_interact_controller.gd;本类只接线与回调注入, + # ADR-0001——模态栈/演播后排窗/呈现刷新/fatal 经回调注入,adventure/runner 现值回调) + _bonus = BonusInteractController.new(db, func() -> Variant: return meta, bonus_overlay, _end_overlay, board_view, + board_zone.toast, func() -> Variant: return save_manager, func() -> Variant: return adventure, + func() -> Variant: return runner, func() -> Variant: return _adventure_mode, + _open_modal, _open_modal_after_playback, _close_modal, _refresh_presentations, + _fatal_screen, func() -> String: return _gp_summary_text({"gp_dropped": _last_gain_amount})) + bonus_overlay.pick_confirmed.connect(_bonus.on_pick_confirmed) + bonus_overlay.replace_chosen.connect(_bonus.on_replace_chosen) + bonus_overlay.continue_pressed.connect(_bonus.on_continue) + bonus_overlay.refresh_requested.connect(_bonus.on_refresh) move_child(bonus_overlay, _end_overlay.get_index()) var start: Dictionary = UiAssembler.build_start_overlay(self, db) @@ -292,7 +294,19 @@ func _build_ui() -> void: start.tunebtn.pressed.connect(_show_tune_panel) start.dbgbtn.pressed.connect(_start_debug_levels) start.dbgbtn.visible = OS.get_environment("DS_DEBUG") == "1" # 迭代 8 卡④——调试入口默认隐藏门控(DS_DEBUG=1 点亮) - start.campbtn.pressed.connect(_open_camp) + # 营地/商店投影(T4/D4 拆件批——迁 camp_shop_presenter.gd;本类只接线、探针缝保缝转发; + # 购买写调用唯一经 MetaProgression/LevelRunner core 单口——组合根 lambda 现值注入) + _camp_pres = CampShopPresenter.new(db, func() -> Variant: return meta, func() -> Variant: return save_manager, camp_overlay, shop_overlay, + board_zone.toast, _start_overlay, logger, func() -> Variant: return runner, + _open_modal, _close_modal, _refresh_presentations, _refresh_resume_button) + shop_overlay.buy_requested.connect(_camp_pres.on_shop_buy) + shop_overlay.leave_pressed.connect(_camp_pres.on_shop_leave) + camp_overlay.buy_requested.connect(_camp_pres.on_camp_buy) + camp_overlay.attune_toggled.connect(_camp_pres.on_camp_attune_toggle) + camp_overlay.tree_buy_requested.connect(_camp_pres.on_camp_tree_buy) + camp_overlay.tree_fork_toggled.connect(_camp_pres.on_camp_fork_toggle) + camp_overlay.closed.connect(_camp_pres.on_camp_closed) + start.campbtn.pressed.connect(_camp_pres.open_camp) start.settingsbtn.pressed.connect(_open_settings) # 迭代 8 卡④——最小设置面入口 start.quitbtn.pressed.connect(_quit_game) # 二轮修改批 D1——退出游戏提级一级菜单 @@ -435,9 +449,9 @@ func _wire_adventure() -> void: adventure.choices_presented.connect(_on_choices_presented) adventure.wallet_changed.connect(_on_wallet_changed) # 迭代 10 奖励重构批——big_reward_paid 退役,三选一信号族接线 - adventure.bonus_choice_presented.connect(_on_bonus_presented) - adventure.bonus_resolved.connect(_on_bonus_resolved) - adventure.aux_replace_presented.connect(_on_aux_replace_presented) + adventure.bonus_choice_presented.connect(_bonus.on_presented) + adventure.bonus_resolved.connect(_bonus.on_resolved) + adventure.aux_replace_presented.connect(_bonus.on_aux_replace_presented) adventure.adventure_finished.connect(_on_adventure_finished) adventure.level_started.connect(_on_adventure_level_started) adventure.set_save_manager(save_manager) # 断点承载注入(迭代 8 卡①——停点快照写盘;调试/模拟不经过此路径) @@ -490,15 +504,9 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void: _show_board_fx_for_node(t) log_view.append_bb(Narrator.node_banner(runner, node, t)) elif t == "reward": - # 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;BonusPickOverlay 共用面, - # 道具件满位替换子交互同构)。原「魔法道具投放渠道」三选一(迭代 7 过渡形态)随批退役。 - _bonus_context = "reward_node" - var node_draw: Dictionary = adventure.node_bonus_candidates() - var node_cands: Array = node_draw.get("candidates", []) as Array - _open_modal(bonus_overlay) - bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(node_cands, node_draw.get("aux_detail", []) as Array)) - adventure.present_node_bonus(node_cands, node_draw.get("aux_detail", []) as Array) - bonus_overlay.set_refresh_info(adventure.attune_refresh_left()) # 迭代 11 第①批 T5b——刷新余量投喂 + # 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;BonusPickOverlay 共用面)。 + # T4/D4 拆件:呈现编排归 bonus_interact_controller.present_node_bonus(_bonus_context 随迁)。 + _bonus.present_node_bonus() log_view.append_bb(Narrator.reward_line()) elif t == "rest": var healed: int = int(info.get("healed", 0)) @@ -521,151 +529,6 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void: # ---- 冒险模式终局面(三选一选择面 / 冒险结算屏——迭代 10 奖励重构批)---- -# 三选一停点(非末关通关恰一次):Boss 胜利先经战斗总结结算面(settlement-summary v1.1——钥石 -# 增量行在其上),确认后进三选一面(level-clear.md v2);续玩停点恢复同入口(零自动推进契约) -func _on_bonus_presented(payload: Dictionary) -> void: - if int(payload.get("aux_cleared", 0)) > 0: - board_zone.toast.show_toast("上关剩余 %d 件魔法道具已随关卡结束清空" % int(payload.get("aux_cleared", 0)), "info") # 迭代 7 T4 清空反馈 - _pending_bonus = payload.duplicate(true) - _bonus_context = "level_clear" - if bool(payload.get("replace_pending", false)): - # 续玩:替换子交互在途中断——重建三选一面后直落替换子态(候选不重抽) - _show_bonus_pick() - return - _end_overlay.open_victory(Narrator.battle_end_text("victory", false), "继续", - board_view.get_global_rect(), - "%s\n%s" % [_gp_summary_text({"gp_dropped": _last_gain_amount}), Narrator.bonus_keystone_line(payload)]) - _open_modal_after_playback(_end_overlay) - _refresh_presentations() - - -# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 aux_replace_presented 续发直落子态) -func _show_bonus_pick() -> void: - var payload: Dictionary = _pending_bonus - _open_modal(bonus_overlay) - bonus_overlay.open_pick(Narrator.bonus_pick_header(payload), _bonus_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array)) - bonus_overlay.set_refresh_info(adventure.attune_refresh_left() if adventure != null else 0) # 迭代 11 第①批 T5b——刷新余量投喂 - _refresh_presentations() - -var _last_replace_items: Array = [] # 替换子态续玩重建缓存(aux_replace_presented 载荷) -var _last_replace_incoming := {} # 同上——incoming 记录 - - -func _bonus_card_texts(candidates: Array, aux_detail: Array = []) -> Array: - # TP0 令牌池——候选 = {"kind","tier"} 令牌字典;卡片 id = "kind|tier|relic_id" 复合键(同 kind - # 大小两档/同档多 relic 可同屏,kind 单键不构成唯一身份),确认回程拆键(_on_bonus_pick_confirmed) - var out: Array = [] - var aux_i := 0 - for tok in candidates: - var kind := String((tok as Dictionary).get("kind", "")) - var tier := String((tok as Dictionary).get("tier", "")) - var rid := String((tok as Dictionary).get("relic_id", "")) - var cid := "%s|%s|%s" % [kind, tier, rid] - var text := "" - if kind == "relic": - text = Narrator.relic_card_text(db.relic_row(rid)) - elif kind == "aux_item": - var iid := String(aux_detail[aux_i]) if aux_i < aux_detail.size() else "" - aux_i += 1 - text = Narrator.bonus_card_text(kind, tier, int(db.adventure.bonus_value(kind, tier)), _aux_card_line(iid)) - else: - text = Narrator.bonus_card_text(kind, tier, int(db.adventure.bonus_value(kind, tier))) - out.append({"id": cid, "text": text}) - return out - - -# 道具件卡片行(掷定前置——具体道具名 + 效果摘要;空 iid = 无预掷回退态显示通用文案) -func _aux_card_line(iid: String) -> String: - if iid == "": - return "" - var row: Dictionary = db.aux_item_row(iid) - if row.is_empty(): - return "" - var parts: Array[String] = [] - for k in (row.get("effect_kinds", []) as Array): - if String(k) == "mine_reveal": - parts.append("真视") - elif String(k) == "reward_preview": - parts.append("预读") - elif String(k) == "ap_grant": - parts.append("AP +%d" % int(row.get("magnitude", 0))) - var tgt := String(row.get("target", "none")) - if tgt == "area_3x3": - parts.append("3×3") - elif tgt == "board_wide": - parts.append("全图") - return "%s · %s" % [String(row.get("display_name", iid)), " ".join(parts)] - - -# 三选一刷新(迭代 11 第①批 T5b——P1 甲 X/Y 双源;通关停点经重发 bonus_choice_presented 自然重建, -# 节点停点在此读在途态重 present——零自动推进契约不破,停点仍在途) -func _on_bonus_refresh() -> void: - if adventure == null: - return - if not adventure.refresh_bonus(): - return - if _bonus_context == "reward_node": - var st: Dictionary = adventure.node_bonus_pending_state() - bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(st.get("candidates", []) as Array, st.get("aux_detail", []) as Array)) - bonus_overlay.set_refresh_info(adventure.attune_refresh_left()) - - -func _on_bonus_pick_confirmed(item_id: String) -> void: - if adventure == null: - return - # TP0 令牌池——卡片 id "kind|tier|relic_id" 拆键(缺 | = 旧载荷防御归普通档;relic 三段) - var parts := item_id.split("|") - var kind := parts[0] - var tier := parts[1] if parts.size() > 1 else "" - var rid := parts[2] if parts.size() > 2 else "" - var ok: bool = adventure.apply_node_bonus(kind, tier, rid) if _bonus_context == "reward_node" else adventure.pick_bonus(kind, tier, rid) - if not ok: - push_error("[组合根] 三选一兑付被拒——见输出日志") - - -func _on_aux_replace_presented(items: Array, incoming: Dictionary) -> void: - _last_replace_items = items.duplicate(true) - _last_replace_incoming = incoming.duplicate(true) - if not bonus_overlay.visible: - _open_modal(bonus_overlay) # 续玩替换在途直落——三选一面未开时先入栈(模态栈纪律) - _open_bonus_replace(items, incoming) - - -func _open_bonus_replace(items: Array, incoming: Dictionary) -> void: - var named: Array = [] - for it in items: - var rec: Dictionary = it.duplicate() - rec["display_name"] = String(_item_name_lookup().get(String(rec.get("item_id", "")), String(rec.get("item_id", "")))) - named.append(rec) - bonus_overlay.open_replace(named, String(_item_name_lookup().get(String(incoming.get("item_id", "")), String(incoming.get("item_id", "")))), - named.size(), Attunement.slots(db, save_manager)) # 迭代 11 第①批 T7——呈现读数源切谓词表求值(过渡键退役) - - -func _on_bonus_replace_chosen(discard: int) -> void: - if adventure == null or not adventure.resolve_bonus_replace(discard): - push_error("[组合根] 同调位替换受理被拒——见输出日志") - - -# 兑付反馈(bonus_resolved 到达——结果行上屏,唯一出口 = 继续) -func _on_bonus_resolved(result: Dictionary) -> void: - _pending_bonus = {} - bonus_overlay.show_result(Narrator.bonus_result_text(result)) - _refresh_presentations() - - -# 继续钮:收三选一面 → 上下文分派(通关停点=推进下一关 / 奖励节点=兑付确认回图选路——零自动推进) -func _on_bonus_continue() -> void: - _close_modal(bonus_overlay) - bonus_overlay.close_pick() - if _bonus_context == "reward_node": - if runner != null: - runner.continue_after_node() - return - if _adventure_mode and adventure != null: - if not adventure.continue_to_next_level(): - _fatal_screen("关卡推进失败——见输出日志") - - # 冒险结算(completed/failed/abandoned):复用 BattleEndOverlay——摘要五件事 + 钥石入账明细进文案 func _on_adventure_finished(summary: Dictionary) -> void: _level_active = false @@ -681,7 +544,7 @@ func _on_adventure_finished(summary: Dictionary) -> void: detail.assign(meta.settle_adventure(summary)) var text := Narrator.adventure_summary_text(summary) if not detail.is_empty(): - text += "\n" + Narrator.keystone_settle_text(detail, _item_name_lookup()) + text += "\n" + Narrator.keystone_settle_text(detail, _bonus.item_name_lookup()) if result == "completed": _end_overlay.open_victory(text, "返回冒险", board_view.get_global_rect(), "本局 GP 入账 · 合计 %d" % _adventure_gp_final) # 迭代 6 T7 卡⑩——GP 行只上冒险终局面板(C5) @@ -694,22 +557,12 @@ func _on_adventure_finished(summary: Dictionary) -> void: _refresh_presentations() -# 工坊 item_id → 显示名投影(keystone_settle_text 文案数据源——装配归本根,拼句归 Narrator) -func _item_name_lookup() -> Dictionary: - var lookup := {} - if meta != null: - for row in db.meta_workshop_items(): - lookup[String(row.get("item_id", ""))] = String(row.get("display_name", "")) - return lookup - - func _on_battle_end_continue() -> void: # 终局出口:返回开始界面(冒险摘要留档试玩日志;钥石已在收束时入存档——存档序列化批) _close_modal(_end_overlay) _end_overlay.close() # 迭代 10——三选一停点挂起分支:战斗总结结算(Boss 胜利钥石增量行)确认后进三选一面 - if not _pending_bonus.is_empty(): - _show_bonus_pick() + if _bonus.resume_if_pending(): # T4/D4 拆件——三选一停点在途恢复入口(bonus_interact_controller) return # 卡⑥ 挂起分派:战斗胜利总结结算框的「下一节点」≠ 终局——进选路页(暂存候选恢复呈现) if _settlement_stop_pending: @@ -740,7 +593,10 @@ func _on_end_review() -> void: # ==================== 调试模式(连续两层——编排归 debug_session.gd; -# 单场遭遇用户入口已删(2026-09-13 用户裁决)——debug_session.start_single 原语仅探针使用)==================== +# 单场遭遇用户入口已删(2026-09-13 用户裁决)——debug_session.start_single 原语仅探针使用) +# T4/D3 收口注记:本节处理器(battle_created/battle_ready/override/seed)系组合根绑定面—— +# settlement 写与 _wire_battle=ADR-0001 组合根 essence,override/seed 取数面在组合根 UI 态 +#(tune_panel/_seed_edit)——不迁移,信号连接留本根(契约 3)。==================== func _start_debug_levels() -> void: tune_panel.reset_sliders() @@ -1028,25 +884,6 @@ func _on_abandon_requested() -> void: # ---- 营地覆盖层与钥石账本(元进度实现批——写调用唯一归组合根)---- -# 打开营地(主菜单入口——开合与开始层互斥仿 TunePanel 先例:开始覆盖层非模态且树序在后, -# 规范 2.3 遮挡纪律;打开时先刷工坊解锁——达标未解锁就地入账) -func _open_camp() -> void: - if meta == null: - return - meta.refresh_workshop_unlocks() - _start_overlay.visible = false - _open_modal(camp_overlay) # 模态栈单口(T3 收口批——登记层开合一律走栈) - camp_overlay.open_camp(_camp_data()) - _refresh_presentations() - - -func _on_camp_closed() -> void: - _close_modal(camp_overlay) # 模态栈单口(T3 收口批) - _start_overlay.visible = true - _refresh_resume_button() # 迭代 8 卡②——回菜单按断点在档态刷新 - _refresh_presentations() # 关闭后刷新顶栏(工坊解锁可能变了余额侧读数面) - - # ---- 最小设置面(迭代 8 卡④——入口/开合仿营地先例:开始层互斥;生效与持久化归 SettingsStore)---- func _open_settings() -> void: @@ -1060,110 +897,14 @@ func _on_settings_closed() -> void: _refresh_resume_button() -# 营地快照投影(纯读——锻造三行/工坊六行/余额/最近冒险;拼句归 CampOverlay,账面归 MetaProgression) +# ---- 探针缝保缝(T4/D4 拆件——click/ui_probe 营地段直呼组合根面;实现归 camp_shop_presenter.gd)---- + +func _on_camp_closed() -> void: + _camp_pres.on_camp_closed() + + func _camp_data() -> Dictionary: - var forge_rows: Array[Dictionary] = [] - if meta != null: - for attr in MetaTable.FORGE_ATTRS: - var row: Dictionary = db.meta_forge_row(attr) - forge_rows.append({ - "attr": attr, - "display_name": String(row.get("display_name", "")), - "level": meta.forge_level(attr), - "flat": int(row.get("flat_value", 0)), - "price": meta.forge_price(attr), - }) - return { - "balance": meta.keystone_balance() if meta != null else 0, - "forge": forge_rows, - "workshop": meta.workshop_status() if meta != null else [], - "attune": meta.attune_camp_status() if meta != null else {}, # 迭代 11 第①批 T4——同调装配面投影 - "tree": meta.tree_camp_rows() if meta != null else [], # 迭代 12 T9——天赋树面投影 - "fork_groups": meta.tree_fork_groups() if meta != null else [], - "recent": save_manager.recent_summaries() if save_manager != null else [], - } - - -func _on_camp_buy(attr: String) -> void: - if meta == null: - return - if attr == "attune_refresh": - # Y 刷新升级行购买(迭代 11 第①批 T4——P1 甲;按钮禁用为常态防线,兜底拒绝可见 pattern 1) - if not meta.buy_attune_refresh(): - board_zone.toast.show_toast("钥石不足——买不起这次升级", "warn") - return - camp_overlay.refresh_camp(_camp_data()) - _refresh_presentations() - return - if not meta.buy_forge(attr): - # 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1) - board_zone.toast.show_toast("钥石不足——买不起这件强化", "warn") - return - camp_overlay.refresh_camp(_camp_data()) - _refresh_presentations() - - -# 同调装配切换(迭代 11 第①批 T4——装配关系唯一写口经 MetaProgression.toggle_attunement;满位兜底拒绝可见) -func _on_camp_attune_toggle(item_id: String) -> void: - if meta == null: - return - if not meta.toggle_attunement(item_id): - board_zone.toast.show_toast("同调位已满——先卸下再装配", "warn") - return - camp_overlay.refresh_camp(_camp_data()) - - -# 天赋树购买(迭代 12 T9——写口唯一归 MetaProgression.tree_buy/tree_buy_fork;Fork 节点购=激活侧) -func _on_camp_tree_buy(node_id: String) -> void: - if meta == null: - return - var ok: bool = meta.tree_buy_fork(node_id) if String(db.tree_node(node_id).get("kind", "")) == "fork" else meta.tree_buy(node_id) - if not ok: - board_zone.toast.show_toast("购买被拒——前置/行门槛/余额不足", "warn") - return - camp_overlay.refresh_camp(_camp_data()) - _refresh_presentations() - - -# A/B 免费切换(迭代 12 T8——同组换侧零币;未激活组走购买面) -func _on_camp_fork_toggle(group: String, side: String) -> void: - if meta == null: - return - if not meta.tree_toggle_fork(group, side): - board_zone.toast.show_toast("切换被拒——未激活组请先购买", "warn") - return - camp_overlay.refresh_camp(_camp_data()) - _refresh_presentations() - - -# ---- 商店覆盖层与 GP 钱包(GP+商店房实施批——写调用唯一归组合根)---- - -func _on_shop_buy(product_id: String) -> void: - if runner == null: - return - if runner.is_shop_pool(): - # pool 面:买 1 → 核心全重抽——货架整架重投喂(重抽反馈;余额不足核心拒绝零副作用) - if not runner.buy_pool_item(product_id): - logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"}) - board_zone.toast.show_toast("金币不足——买不起这件商品", "warn") - return - shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp)) - _refresh_presentations() - return - if not runner.buy_product(product_id): - # 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1) - logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"}) - board_zone.toast.show_toast("金币不足——买不起这件商品", "warn") - return - shop_overlay.refresh_state(runner.shop_products(), int(runner.gp)) - _refresh_presentations() - - -func _on_shop_leave() -> void: - _close_modal(shop_overlay) - if runner != null: - # 复用兑付确认纪律(接口漂移清偿):离开 ≠ 推进——确认后经 choices_presented 弹出选路口 - runner.continue_after_node() + return _camp_pres.camp_data() func _on_wallet_changed(event: String, gp: int, extra: Dictionary) -> void: From d78b8d4eb08183b688f00f3cc7628638bac0439b Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sun, 4 Oct 2026 07:44:24 +0800 Subject: [PATCH 5/8] =?UTF-8?q?T5+T6:=20aux=20=E4=BA=8C=E6=9C=9F=E8=AF=84?= =?UTF-8?q?=E4=BC=B0=E5=88=A4=E4=B8=8D=E5=90=88=EF=BC=88=E9=9B=B6=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=EF=BC=89+=20D5=20=E6=9D=82=E9=A1=B9=E5=B0=BE=E5=B7=AE?= =?UTF-8?q?=E5=9B=9B=E4=BB=B6=E9=94=80=E8=B4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T5(判不合=合法出口,§五白名单):丢弃/授予/收尾三步强合否决——两渠道三轴语义相异 (寻址 carry 序号 vs 库存句柄/持久域 跨关 vs 单关/收尾 断点快照 vs 不载),原实现已带 三处「保守不合注记在案」(adventure_runner :479/:495/:566/:676),强合需适配层且触碰 核心 RNG 邻接重构(契约 1 高危区)换 ~30-50 行收益;结论随 T9 落 06 注记收口,零 MC。 T6 四件:①grant_aux_item 节点渠道(_resolve_node_replace)返回值接留痕——镜像 :580 防御兜底留痕范式(零行为面变化)②README 旧名 _autoplay_battle_perfect 改现行 _decide_act 分派口径 ③critical-paths「切片 96/96」换代现行 718/0 口径 ④hero_info_panel_view 死色名 PANEL_HERO 注记校正。 闸:冒烟 718/0+click 种子7 绿(5375 帧)。 --- game/README.md | 2 +- game/core/adventure_runner.gd | 4 +++- game/presentation/views/hero_info_panel_view.gd | 2 +- game/tests/smoke/critical-paths.md | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/game/README.md b/game/README.md index e205732f..e35159ad 100644 --- a/game/README.md +++ b/game/README.md @@ -401,7 +401,7 @@ Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/s **落地变更**: - `auto_player.gd` 增完美策略求解器(`perfect_action`/`_deduce`):single + subset 推理不动点(A1 F1 `measure_inferability` 同族规则集,**去 safe_set oracle**——约束未知邻格含雷格,比生成期闸保守);**零盲挖**(只挖已证安全格 = 构造保证零踩雷,扫描末断言兜底违例 quit(1))· **已证雷格全标旗**(旗先于挖——轮末 weaken + 魔法 首次充能,雷上旗不阻连锁)· 卡死且资源可 → `b1.player_request_refresh()` 轮内刷新 · 否则停手(残 AP 1:1 入 RAP)。输入只经 A2 查询面(reveal_record/is_revealed/has_flag),**禁读棋盘雷区真值**——测推理上界,非全知作弊。core 层零改动; - `window_selection` 增 `heal_no_waste` 选项(缺口 < 治疗量不用;完美档启用)+ 完美档轮次门解除(round_gate=0); -- `level_autoplay.gd` 增 `policy` 参数(默认 "normal"——tune_sim_controller/defense_scan/level_debug 既有调用零改动);完美档驱动 `_autoplay_battle_perfect`(move/dig/flag 走 A2 settle_batch、refresh 走 B1 编排、连续拒绝 ≥3 停手防死循环);st 增 `mine_hits`/`refreshes` 计数(mine_hit 证据行埋点); +- `level_autoplay.gd` 增 `policy` 参数(默认 "normal"——tune_sim_controller/defense_scan/level_debug 既有调用零改动);完美档经 `policy="perfect"` 由 `_decide_act` 分派承载(原专职驱动 `_autoplay_battle_perfect` 已随样板收口并入分派——move/dig/flag 走 A2 settle_batch、refresh 走 B1 编排、连续拒绝 ≥3 停手防死循环);st 增 `mine_hits`/`refreshes` 计数(mine_hit 证据行埋点); - `level_monte_carlo.gd` 增第 3 CLI 参 `[policy]`(normal/perfect/both,缺省 normal 兼容既有文档命令)+ 策略/踩雷/刷新列;脚注 F5 带文案同步 [30,50] 带锚定稿。 **完美档基线(N=30 · 种子 4200–4229 · 耗时 597.6s)**:**0 加成通关率 100%**(弱策略基线档 60%)、通关剩余 HP 均 **47.4/50**、整关承伤均 3.5(p90 18.0)、普通场承伤 0.5%(带 ≤20%)、Boss 场 5.3%(带 ≤50%)、踩雷 0.00/局(断言 ✓)、刷新 0.23/局、跳战率 20%。+50%~+200% 全档位恒 100% 满血通关(+200% 承伤均 0.4)——**技能轴上界完全碾压现行曲线:F5 带与 F6 S/K 区间对完美档零分辨力**(区间模型无策略水平轴的结构性证实;calibration_gap 获得机制解释与定量上界)。 diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index f5f0019f..cb7e866d 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -594,7 +594,9 @@ func _resolve_node_replace(discard_handle: int) -> bool: push_error("[冒险结构] resolve_node_replace 拒绝——句柄不在持有集: %d" % discard_handle) return false for iid in incoming: - _runner.grant_aux_item(String(iid), "reward_node") + var rec: Dictionary = _runner.grant_aux_item(String(iid), "reward_node") + if rec.is_empty(): + push_warning("[冒险结构] 替换子道具授予被拒(防御兜底留痕): %s" % String(iid)) # 放弃新件(discard_handle < 0)= 原持有集不动、新件不获取 _finish_node_bonus("aux_item", incoming.size()) return true diff --git a/game/presentation/views/hero_info_panel_view.gd b/game/presentation/views/hero_info_panel_view.gd index 4254e04e..4e6f39fb 100644 --- a/game/presentation/views/hero_info_panel_view.gd +++ b/game/presentation/views/hero_info_panel_view.gd @@ -28,7 +28,7 @@ var _relic_list: VBoxContainer func _init() -> void: custom_minimum_size = Vector2(240, 0) clip_contents = true - # 主面板穿石板戏服(样张 v2 冻结批 T2——纹理本色≈PANEL_HERO;数据框仍素面 §7.1;缺位回退 flat) + # 主面板穿石板戏服(样张 v2 冻结批 T2——纹理本色;数据框仍素面 §7.1;缺位回退 flat) var sb := UiKit.panel_skinned(Vector4(8, 6, 8, 6)) add_theme_stylebox_override("panel", sb) var col := VBoxContainer.new() diff --git a/game/tests/smoke/critical-paths.md b/game/tests/smoke/critical-paths.md index 9fccca72..c1e5e361 100644 --- a/game/tests/smoke/critical-paths.md +++ b/game/tests/smoke/critical-paths.md @@ -3,7 +3,7 @@ **Purpose**: QA 交接前 15 分钟内人工跑完的关键路径清单。 **Run via**: `/smoke-check`(读本文件)。 **Update**: 新核心系统落地后增补条目。 -**分工注记**:自动化冒烟(切片 96/96)覆盖规则正确性;本清单面向**表现与手感**(自动化探针不可 G-W-T 的部分)。 +**分工注记**:自动化冒烟(全量断言,现行口径 718/0——2026-10-04 迭代 13 T6 随批换代,随批末闸走)覆盖规则正确性;本清单面向**表现与手感**(自动化探针不可 G-W-T 的部分)。 ## Core Stability(恒跑) From 89ed0d1c592b5a4e600f8a51ac3398b250b7b665 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sun, 4 Oct 2026 08:21:42 +0800 Subject: [PATCH 6/8] =?UTF-8?q?T7+T8(D6):=20=E7=88=86=E7=A0=B4=E5=B7=A5?= =?UTF-8?q?=E5=85=B5=E5=8C=85=E4=B8=AD=E7=AB=8B=E7=A7=BB=E9=99=A4=E9=9B=B7?= =?UTF-8?q?=E6=A0=BC=E2=80=94=E2=80=94=E7=BB=86=E5=8C=96+=E5=AE=9E?= =?UTF-8?q?=E6=96=BD=EF=BC=88blast=5Fkit=20=E6=8D=A2=E8=A3=85=E5=90=AF?= =?UTF-8?q?=E7=94=A8=EF=BC=8C=E5=88=A4=E5=AE=9A=E9=80=BB=E8=BE=91=E9=9B=B6?= =?UTF-8?q?=E6=94=B9=E5=8A=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T7 细化(loop 计划书附录C,批末吸收即死):cell_removal 语义=移除集仅「未揭示·无旗·是雷」 ——清图分母只数安全格(safe_remaining/_cleared_after 实证)→雷格移除对分母/连锁/C_dig 模拟天然零涉,P1 甲「rules 零改动」按裁决语境兑付为判定逻辑零改动;移除跳过有旗格=旗判 定基线零回溯;P2 甲=CellUnit 退色降档呈现(暗淡记忆态同族)。 T8 六面: ①换装 aux_items.tres blast_kit:+form active_charged/+slot_cost 1/+charges_per_adventure 2/ effect_kinds 挂 cell_removal/清 deferred(契约 4 裁定面;镜像 nest_pendulum 先例)+ 表头注记 :8 半过期面校正。 ②minesweeper_rules:_removed 集(install/retire 清空骑既有 reset 位)+remove_mines_in()/ is_removed() 只读查询+交互守卫两处(_board_face_dig/_do_flag_place sub=target_removed, 拒绝先于 try_spend 零消耗)——邻接数字/分母/连锁/快照零触(停点仅选路/奖励位,战斗中态 不入档)。 ③use_aux cell_removal 分支:remove_mines_in(v.resolved) 空集拒绝零消耗(consume 前返)。 ④呈现:board_view _paint_cell removed 分支(数字/旗/瓦片基座压写)+_diggable 排除(hover 金框不现)+CellUnit removed_dim 退色暗幕层(程序化零新资产,雾层之上不再参与迷雾语义)。 ⑤使用后棋盘刷新:aux_interact_controller.attempt_use 增 board_view.refresh()(幂等)。 ⑥充能编排零新代码:表驱动+营地同调位装配+use_aux consume 扣减全现成。 MC 口径注记:换装后 form 非空→transitional_rows 过滤(schema :181)→aux_live_ids/ _bonus_aux_pool 永久排除→模拟投放池/道具件候选零涉;同调装备模拟档零操作——MC/autoplay/ verifier 读数零漂移(静态证明,零跑测)。 闸:冒烟 718/0+run_all 15(aux_items_test 三件过换装行)+click 三种子绿(6241/7345/6018) +ui_probe/ui_input/t17_defer 绿。 --- game/core/battle_orchestrator.gd | 7 +++++ game/core/minesweeper_rules.gd | 27 ++++++++++++++++++++ game/data/tables/aux_items.tres | 8 +++--- game/presentation/aux_interact_controller.gd | 1 + game/presentation/views/board_cell_unit.gd | 8 ++++++ game/presentation/views/board_view.gd | 8 +++++- 6 files changed, 55 insertions(+), 4 deletions(-) diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index f5ba6acc..b0f88b79 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -632,6 +632,13 @@ func use_aux(handle: int, target: Vector2i) -> Dictionary: # 掘进蓄能兑付(迭代 11 同调批 T6——临时 AP 当轮生效,grant_ap 同 sapper_order 通道) if not action_economy.grant_ap(int(v.magnitude)): return {"ok": false, "reason": "行动点加量被拒(相位防御)"} + if kinds.has("cell_removal"): + # 爆破工兵包兑付(迭代 13 T8/D6——中立移除雷格:未揭示/无旗/是雷过滤归排雷玩法; + # P1 甲邻接数字冻结零重算、清图分母零涉(雷格不入分母);空集拒绝零消耗—— + # consume 之前 return(拒绝面注记扩「③内零消耗分支」),拒绝 reason 可呈现) + var blasted: Array = board.remove_mines_in(v.resolved) + if blasted.is_empty(): + return {"ok": false, "reason": "爆破工兵包:3×3 区域内无可移除雷格(须未揭示且未插旗)"} var rec: Dictionary = aux_inventory.consume(handle) # 同调主动件充能扣减(迭代 11 第①批 T6——共享账本直改:adventure/level/settlement 同一字典引用) if not rec.is_empty() and String(rec.get("source_class", "")) == "attunement": diff --git a/game/core/minesweeper_rules.gd b/game/core/minesweeper_rules.gd index 548bf0ba..b7b4e736 100644 --- a/game/core/minesweeper_rules.gd +++ b/game/core/minesweeper_rules.gd @@ -20,6 +20,7 @@ var action_economy: ActionEconomy var _board: BoardData = null var _revealed := {} # Vector2i → {"kind": "safe"/"mine", "digit": int} var _flags := {} # Vector2i → true(棋盘单态;跨轮保留随图存续) +var _removed := {} # Vector2i → true(迭代 13 T8/D6——中立移除雷格集;随 install/retire 清空) var _hero := Vector2i.ZERO var _spawn: Dictionary[Vector2i, bool] = {} @@ -73,6 +74,26 @@ func clear_map_cleared() -> bool: return safe_remaining() == 0 +# ---- 中立移除(迭代 13 T8/D6——P1 甲:邻接数字=原始雷数冻结零重算;P2 甲:移除格呈现退色降档; +# 移除集=未揭示、无旗、是雷——旗判定基线零回溯;雷格不入清图分母/连锁/C_dig 模拟,判定逻辑零改动) ---- + +func remove_mines_in(cells: Array) -> Array: + var out: Array = [] + for c in cells: + var cell: Vector2i = c + if not _board.in_bounds(cell) or is_revealed(cell) or has_flag(cell) or not _board.is_mine(cell): + continue + if _removed.has(cell): + continue + _removed[cell] = true + out.append(cell) + return out + + +func is_removed(c: Vector2i) -> bool: + return _removed.has(c) + + # 未开格存在查询(aux 道具 usable 预判消费——全板扫描只读面) func has_unrevealed() -> bool: for y in _board.height: @@ -100,6 +121,7 @@ func install_board(b: BoardData, during_round := false) -> Dictionary: _board = b _revealed.clear() _flags.clear() + _removed.clear() _spawn = {} for c in b.spawn_cells(): _spawn[c] = true @@ -127,6 +149,7 @@ func retire_board(during_round := false) -> Dictionary: _board = null _revealed.clear() _flags.clear() + _removed.clear() return _outcome(true, "executed", "", "") @@ -303,6 +326,8 @@ func _board_face_dig(c: Vector2i) -> String: return "not_adjacent" if is_revealed(c): return "target_revealed" + if _removed.has(c): + return "target_removed" # 迭代 13 T8/D6——中立移除格不可交互(拒绝先于消耗,pattern 1 board_face 腿) # 旗不拦挖掘(插旗不可逆批 2026-09-19):有旗格直接挖 = 对承诺的兑现,兑付改判归 战斗结算。 return "" @@ -356,6 +381,8 @@ func _do_flag_place(c: Vector2i) -> Dictionary: sub = "target_revealed" elif has_flag(c): sub = "flag_present" + elif _removed.has(c): + sub = "target_removed" # 迭代 13 T8/D6——中立移除格不可交互 if sub != "": return _outcome(false, "rejected", "board_face", sub) if not action_economy.try_spend("flag_place"): diff --git a/game/data/tables/aux_items.tres b/game/data/tables/aux_items.tres index 1b6b1688..db17404f 100644 --- a/game/data/tables/aux_items.tres +++ b/game/data/tables/aux_items.tres @@ -5,7 +5,7 @@ [resource] script = ExtResource("1") ; 迭代 11 第①批 T1(2026-10-03):追加七件同调行(form 非空——永不入过渡渠道);值全 provisional 归 难度曲线与Boss。 -; nest_pendulum/blast_kit 维持过渡行,T7 渠道退役时原位换装 form=active_charged;sapper_order/显影尘/地穴全图卷/罗盘 T7 逐件销账。 +; nest_pendulum 已换装 active_charged(迭代 11 第①批);blast_kit 已换装 cell_removal+active_charged(迭代 13 T8/D6——中立移除雷格启用);sapper_order/显影尘/地穴全图卷/罗盘 T7 逐件销账。 rows = [{ "item_id": "probe_dust", "display_name": "显影尘", @@ -57,11 +57,13 @@ rows = [{ "item_id": "blast_kit", "display_name": "爆破工兵包", "dimension_tags": ["action", "risk"], -"effect_kinds": ["ap_grant", "mine_reveal"], +"effect_kinds": ["ap_grant", "mine_reveal", "cell_removal"], "target": "area_3x3", "magnitude": 4, "use_phase": "active_dig", -"deferred": true +"form": "active_charged", +"slot_cost": 1, +"charges_per_adventure": 2 }, { "item_id": "venom_edge", "display_name": "淬毒刃纹", diff --git a/game/presentation/aux_interact_controller.gd b/game/presentation/aux_interact_controller.gd index ffcc7133..dcff5fd8 100644 --- a/game/presentation/aux_interact_controller.gd +++ b/game/presentation/aux_interact_controller.gd @@ -95,6 +95,7 @@ func attempt_use(handle: int, cell: Variant = null) -> void: _use_selection = 0 refresh_slots() refresh_overlays() + board_view.refresh() # 迭代 13 T8/D6——移除格即时落图(无移除态的件 diff 零变化幂等) var arow: Dictionary = db.aux_item_row(String(v.get("item_id", ""))) toast.show_toast("已使用 %s" % String(arow.get("display_name", "")), "info") diff --git a/game/presentation/views/board_cell_unit.gd b/game/presentation/views/board_cell_unit.gd index 024848ba..d98f3995 100644 --- a/game/presentation/views/board_cell_unit.gd +++ b/game/presentation/views/board_cell_unit.gd @@ -118,6 +118,10 @@ var fog_state := 0: if fog_state != v: fog_state = v queue_redraw() +var removed_dim := false: # 中立移除退色层(T8/D6——BoardView paint 按排雷玩法 is_removed 写入) + set(v): + removed_dim = v + queue_redraw() func _draw() -> void: @@ -256,6 +260,10 @@ func _draw() -> void: draw_rect(Rect2(Vector2.ZERO, Vector2(edge, edge)), FOG_MEMORY_PLATE, true) if glyph != "": _draw_glyph(glyph, FOG_MEMORY_INK, maxi(18, int(ceil(edge * 0.5)))) # 字号下限 18(§7.4 冻结公式同源) + # ⑦ 中立移除退色层(迭代 13 T8/D6——P2 甲退色降档:暗幕压顶全层覆盖,暗淡记忆态同族呈现语义; + # 顶层于迷雾之上——移除格不再参与迷雾语义。本实现移除集=未揭示雷格(无数字无旗印)) + if removed_dim: + draw_rect(Rect2(Vector2.ZERO, Vector2(edge, edge)), Color(0.12, 0.10, 0.09, 0.62), true) # 南立面(D 档件一——墙格南缘内侧,自底边向上:双段暗带在下、口沿高光线贴其上沿): diff --git a/game/presentation/views/board_view.gd b/game/presentation/views/board_view.gd index 1a73135c..c0fc3a84 100644 --- a/game/presentation/views/board_view.gd +++ b/game/presentation/views/board_view.gd @@ -316,7 +316,7 @@ func refresh() -> void: var hero: Vector2i = _minesweeper.hero_pos() _diggable.clear() for n in _board.neighbors8(hero): - if not _minesweeper.is_revealed(n): + if not _minesweeper.is_revealed(n) and not _minesweeper.is_removed(n): _diggable[n] = true for y in _board.height: for x in _board.width: @@ -684,6 +684,7 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void: return var revealed: bool = _minesweeper.is_revealed(c) var flag: bool = _minesweeper.has_flag(c) + var removed: bool = _minesweeper.is_removed(c) # 迭代 13 T8/D6——中立移除格(P2 甲退色降档) var is_hero: bool = c == hero var glyph := "" var bg: Color @@ -691,6 +692,10 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void: # 旗槽统一维护(T17 修复):旗格=封印旗纹理;其余格清 null(插旗格挖开后残留在 tex_flag # 会让 _draw 无条件把旗印画在已开格上——tex_flag 绘制无 revealed 条件)。缺盘走字形回退。 u.tex_flag = _flag_tex if (flag and not revealed and _flag_tex != null) else null + if removed: + revealed = false # 移除格固定未开呈现基座(退色暗幕层承载;雷格本无数字——P1 甲周边数字冻结) + flag = false + u.tex_flag = null if revealed: var rec: Dictionary = _minesweeper.reveal_record(c) # E2 收口:已开记录经查询面 if rec.get("kind", "") == "mine": @@ -809,6 +814,7 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void: u.fog_state = (2 if revealed else 1) if fogged else 0 else: u.fog_state = 0 + u.removed_dim = removed u.queue_redraw() From 74c62392d7389bb49762def5c6ba3a5218540a65 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sun, 4 Oct 2026 08:27:51 +0800 Subject: [PATCH 7/8] =?UTF-8?q?docs(06+active):=20=E8=BF=AD=E4=BB=A313?= =?UTF-8?q?=E9=9A=8F=E6=89=B9=E8=90=BD=E6=96=87=E2=80=94=E2=80=9406?= =?UTF-8?q?=E5=85=AD=E6=9D=A1=E7=9B=AE=E9=94=80=E8=B4=A6=EF=BC=88D1-D5+D6?= =?UTF-8?q?=E4=B8=A4=E5=8F=A3=E5=BE=84P1=E7=94=B2/P2=E7=94=B2=E8=90=BD?= =?UTF-8?q?=E6=96=87+=E4=B8=89=E5=A4=84=E5=BD=92=E5=B1=9E=E4=BF=AE?= =?UTF-8?q?=E8=AE=A2=E6=B3=A8=E8=AE=B0+main.gd=E8=A1=8C=E6=95=B0=E5=9F=BA?= =?UTF-8?q?=E7=BA=BF=E6=A0=A1=E6=AD=A31237=E2=86=921119=EF=BC=89+=E5=8F=B0?= =?UTF-8?q?=E8=B4=A6342=E9=9A=8F=E6=89=B9=E7=99=BB=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- active.md | 1 + design/gdd/06-待办清单.md | 32 +++++++++++++++++--------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/active.md b/active.md index 4a727dbb..d29bda29 100644 --- a/active.md +++ b/active.md @@ -753,3 +753,4 @@ 335. **全局作用域变异调研裁决+迭代14③号卡立项批落籍(2026-10-04,本会话;worktree global-variant-iter14-20261004 单提交 29db278+主区合并)**:**台账号 335=接续 334**(334 系并行挖掘风险不等式调研批先期落籍,撞号改登——先例同台账 96/90);纯文档零代码零 git 冒烟面。**裁决**——调研报告(design/research/调研-全局作用域变异设计空间-2026-10-04.md,本会话前期 v1.0 已交付)P1-P8 全按推荐案:P1 乙=立项(撤 2026-09-19「Alpha 再设计」挂起)/P2 甲=案A 资源面首发/P3 甲=替换制(Boss 图不布 region 片——L2 完美 Boss 承伤 55.8% 已越带不叠加)/P4 甲=L2/L3 Boss 恒定制+类型固定演进/P5 甲=开战横幅+HUD 常驻角标+帮助手册行三层预告/P6 甲=L1 Boss 免疫(带B [50,85] 教学保护)/P7 甲=带A 收敛杠杆注册 M-4/P8 甲=案B/E/C 按档位留档;grilling 盘问四前沿同全按推荐:Q1 甲=案A 换形**微贫向偏斜**(v1.0「沃土+雷密对冲」勘误作废——雷数系生成器事实、变异布置在装板第 4 步不可加,改生成端=踩结构面否决理由;正向格面池合计不变、进攻族 attack/blast/pierce/magic 上浮/生存族 heal/defense 明显下调、EV 微降 3-5% provisional、gp/action 白名单不动)/Q2 甲=L2/L3 双关同型(关间台阶由 L3 既有新内容满足;同型双关白送同型对照读数)/Q3 甲=迭代 12 四件地形应对件(地脉采撷/荆棘编织者/净化工兵/熔火之躯)Boss 图失效接受为知情代价(主战场普通图 6/7,Boss 覆盖 1/7;registry/调参日志随批登记)/Q4 甲=独立批先行、带A 判读后置 M-4 只测不判;代拍=偏运硬币合成序图级先件级后(随批逐位锚登记)。**落地**——报告 v1.1 裁决落盘(全文按裁决后口径回写;勘误注记两处=雷密对冲作废+固定演进下全局型无可重掷〔替换制 Boss 图 variant_place 零取号=合法态〕;新增 §8 实现面补强八条=门控兜底/偏斜白名单/清图独立池/承载方式/合成序/随批文档清单七正主/MC off 基线语义注记/呈现件闲置注记)+roadmap 迭代 14 行**③号卡**需求登记(①初始展开收窄批②判式换代批③全局变异首发批互不撞——收窄=普通图初始展开轴/判式=不等式判读口径/全局变异=Boss 图资源面轴;出口读数各自分开归因);批位备注=任务书待起草,前置换用户发令。**流程**——v1.0 系任务书「零 git 操作」约束下经 .zcode/tmp 中转落主区未跟踪件(守卫豁免路径,先例同 A5 调研);本批转正入籍时删除主区未跟踪 v1.0(内容已由提交历史承载)防 merge 撞名;合并遇并行批双撞(active.md 台账 334+roadmap 迭代 12 行落籍状态)双方保留解决、本批改号 335。**验证**——doc_link_audit --strict 死链 2 处均存量非本批引入(06 链 A5 未跟踪件与 archive gitignored 件,主区盘面存在提交树不存在,主区 0 死链对照);纯文档批冒烟不适用(两入口铁规)。 335 注. 台账落籍时点补记:主区清账合并时撞并行会话合并冲突残场一次(「unmerged files」瞬态——并行会话自行收口至 8f3fc72 后重试即过);worktree 清理后仅剩并行在飞 `global-variant-iter14`(迭代 14 全局变异批,本会话零触碰)。master 终态 f3fcfa7。 +342. **迭代 13 工程债与测试基建批落籍(2026-10-04,本会话;worktree m4iter13-debt-20261004 六提交 cd7d3ea/2241778/fec3064/4c79310/d78b8d4/89ed0d1)**:D1 click 探针完整局适配(分派链序修正〔shop 停点先于 level_screen——迭代 11 商店房双 visible 烧预算根因实证〕+停点/锁期/重放三态断言三种子绿)/D2 探针只读出口六件 26 直入清零/D3 ArmedTimer helper 三处计时收口(P4 乙,缝零动视觉零变化)/D4 main.gd 二期瘦身 1378→1119(bonus_interact_controller+camp_shop_presenter 拆件接线留组合根;家族②契约3绑定面留置;~900±50 锚差归因=族级清单上限 336 行不自洽,出口报告呈报)/T5 aux 二期判不合收口(三轴相异证据链,零代码零 MC)/D5 杂项四件/D6 爆破工兵包换装启用(cell_removal=未揭示·无旗·是雷——判定逻辑零改动+CellUnit 退色暗幕 P2 甲+充能表驱动零新码;MC 静态零漂移证明零跑测)。**闸**=冒烟 718/0(断言总数持平基线不减)+run_all 15+click 三种子绿+全探针绿+doc 三闸零新增(lint 2/link 2/term 1 存量同款非本批面)+熵增 PASS。**待用户**:出口人闸(出口包=loop 计划书附录A/B+C+06 六条目销账预览+P1/P2 落文预览);实机目检候选=营地装配爆破工兵包→使用→移除格退色呈现(本批零真机面)。 diff --git a/design/gdd/06-待办清单.md b/design/gdd/06-待办清单.md index 3b08014d..12870c2e 100644 --- a/design/gdd/06-待办清单.md +++ b/design/gdd/06-待办清单.md @@ -46,9 +46,11 @@ **现状**:同调批 T6 前置半日影响评估(P5 甲)破面判定=**大**:中立移除格态触排雷玩法(第三格态「已移除」——挖掘/插旗/和弦/邻接数字/清空判定全要问它)+战斗结算(完美排雷读数与旗判定基线需定义移除格语义)+清图判定(移除安全格改分母——BoardData 清空判定+MC 清图率读数口径)三面,且底稿欠规格两处口径(被移除雷格是否仍计入邻接数字/移除格数字呈现)实做需追加拍板。用户裁(2026-10-03):拆后批+blast_kit 行标 deferred 留位(effect_kinds cell_removal 已在 schema 枚举、行位保留,后批立项时换装 form=active_charged 即启)。后批开工前置=两处口径拍板+三面改动面任务书。 -**归属**:后批(迭代 12 或独立 mini-loop,与反制批/双层 BD 批次排序待用户发令) +**两口径裁决落文(2026-10-04 用户,任务书 v1.2 P1/P2)**:**P1 甲**=被移除雷格仍计入邻接数字——邻接数字=原始雷数历史真相冻结(digit_of 静态板值零重算);**P2 甲**=移除格保留原数字、视觉退色降档(CellUnit 呈现态,暗淡记忆态同族)。**实施归属 P3 甲=迭代 13 工程债与测试基建批随批做完**(超推荐案乙系知情裁决)。 -**状态**:待立项(拆后批) +**归属**:~~后批(迭代 12 或独立 mini-loop)~~→迭代 13 工程债与测试基建批(P3 甲裁 2026-10-04) + +**状态**:**已落籍销账(2026-10-04,迭代 13 T7+T8,worktree m4iter13-debt-20261004 提交 89ed0d1)**——blast_kit 换装(form=active_charged+slot_cost 1+charges 2+effect_kinds 挂 cell_removal+清 deferred);cell_removal 语义=移除集仅「未揭示·无旗·是雷」→清图分母/连锁/C_dig 模拟天然零涉(判定逻辑零改动,P1 甲兑付),交互守卫两点(dig/flag sub=target_removed 零消耗拒绝);呈现=CellUnit removed_dim 退色暗幕(P2 甲)+_diggable 排除;充能编排零新代码(表驱动+营地同调位装配+use_aux consume 扣减全现成);MC 口径注记=换装后 form 非空被 transitional_rows 过滤,模拟投放池零涉零漂移(静态证明零跑测)。闸=冒烟 718/0+run_all 15+click 三种子+探针绿。 ## 战斗循环 @@ -243,9 +245,9 @@ **现状**:ui_click_probe 现行断言基于短局夹具,未适配完整局——结算演播硬锁 v3.1(锁期点击缓存+演播完重放)落地后,探针的合成点击时序与硬锁交互存在语义差(perf 审计 2026-10-02 实测双态全红归 T17 在飞批,落籍后未重适配)。需把探针点击注入改为完整局语境(停点/锁期/重放三态断言)。 -**归属**:M-4 迭代 11 窗口评估(测试基建,与奖励重构读数批同窗排序) +**归属**:~~M-4 迭代 11 窗口评估~~(**归属修订 2026-10-04**:迭代 11 已落籍,归属失效改判迭代 13 工程债与测试基建批——任务书 §一) -**状态**:待立项(挂账勿重开——2026-10-02 出口段定性,本条为补登记) +**状态**:**已落籍销账(2026-10-04,迭代 13 T1,worktree m4iter13-debt-20261004 提交 cd7d3ea)**——探针分派链序修正(shop 停点先于 level_screen,迭代 11 商店房双 visible 实证烧预算根因)+停点/锁期/重放三态断言(_locktest_tick 脚本化:旗布尔=恰一次送达探测器)+PROBE_SEED 三种子绿(7/11/23)+演播期豁免保留注记;冒烟 718/0+run_all 15+t17_defer 绿。 #### 资产升档重产批(art-bible v3.0 中修挂账③,2026-09-29 登记) @@ -310,38 +312,38 @@ **现状**:三处武装确认语义异构(AP 条 chips+4s tween 自动回落 / 选路页放弃钮 3s _process 倒计时+危险红呼吸+减动效静态变体 / 战斗总结结算框纯载荷暂存无计时无视觉)——保语义收口需可插拔视觉+双计时机制+可选脉冲,共享件本体 40-60 行而每处仅省约 6 行,且动 press_confirm_for_probe/confirm_visible 探针语义,修复批 T4 降级不做。来源:代码质量审计(ap_bar_view/level_map_screen/main 三份手抄) -**归属**:表现层打磨批(M-4 窗口) +**归属**:~~表现层打磨批(M-4 窗口)~~→迭代 13 工程债批(P4 乙裁 2026-10-04) -**状态**:待办(2026-10-02 T4 降级登记,做了半套不如不做) +**状态**:**已落籍销账(2026-10-04,迭代 13 T3,worktree m4iter13-debt-20261004 提交 fec3064)**——P4 乙=ArmedTimer helper(武装/回落/超时/减动效档四能力,motion_still 档判单点)三处计时机制收口:AP 条 tween 秒表→timer(三探针缝语义等价零动)、放弃钮 _process 倒计时→timer.tick+elapsed 相位(1Hz 呼吸视觉零变化)、结算框裸布尔→挂起态属性化(窗口 0);视觉三处各自保留;ui_click_probe :513-515 零改全绿实证。 #### main.gd 组合根二期瘦身(1237 → ~900) -**现状**:T3 已迁排播泵/aux 两步交互/GP 影子账三件;到 ~900 还差三选一与替换子交互编排(约 115 行)、debug 模式接线、营地/商店投影 handler 等家族。来源:T3 执行报告 +**现状**:T3 已迁排播泵/aux 两步交互/GP 影子账三件;到 ~900 还差三选一与替换子交互编排(约 115 行)、debug 模式接线、营地/商店投影 handler 等家族。来源:T3 执行报告。**行数基线校正(2026-10-04)**:1237 系 T3 拆后读数——开工日实测 1378(迭代 11/12 增量),批末 **1119**(净减 259)。 -**归属**:下一表现层结构批 +**归属**:~~下一表现层结构批~~→迭代 13 工程债批 -**状态**:待办(2026-10-02 登记,ADR-0001 接线留组合根前提不变) +**状态**:**已落籍销账(2026-10-04,迭代 13 T4,worktree m4iter13-debt-20261004 提交 4c79310)**——家族①=bonus_interact_controller.gd(241 行,三选一/替换子编排+TP0 复合键卡片装配)、家族③=camp_shop_presenter.gd(172 行,营地/商店投影与四写口);接线留组合根(ADR-0001,十三信号组合根接至 on_* 入口,meta/save_manager/runner 现值回调注入——探针 isolate_save 换注实证);家族②debug 接线=契约 3 组合根绑定面不迁移(节头注记记档)。**~900±50 锚差归因**:三家族可迁清单实测上限 ~336 行(任务书自估 315),锚位与族级清单不自洽——1333−315+胶水≈1018 已高于锚,净减 259 为清单内全量;再深切属新立项面。闸=冒烟 718/0 断言链全绿+三种子 click+run_all 15。 #### aux 替换子交互容器操作二期合并 **现状**:通关渠道(carry 序号寻址+hp 账本收尾)与节点渠道(库存 handle 寻址+node 收尾)已合共用前置谓词,丢弃/授予/收尾三步仍分轨——强合需适配层,收益低。来源:T5 执行报告保守注记 -**归属**:随同调位实施批(attunement)顺路评估 +**归属**:~~随同调位实施批(attunement)顺路评估~~(**归属变更 2026-10-04**:同调批已落籍未做此评估,改判迭代 13 工程债批——任务书 §一) -**状态**:待办 +**状态**:**评估收口·判不合(2026-10-04,迭代 13 T5,合法出口非缩水)**——三轴语义相异(寻址 carry 序号 vs 库存句柄/持久域 跨关 vs 单关/收尾 断点快照 vs 不载),原实现已带三处「保守不合注记在案」(adventure_runner :479/:495/:566/:676);强合需适配层且触碰核心 RNG 邻接重构(契约 1 高危区)换 ~30-50 行收益。零代码,零 MC。 #### 探针白盒私有域访问改 *_probe() 只读出口 **现状**:ui_input_probe 等直入 BoardView._cells/_diggable/_layout 等私有域(重构即碎),fx_probe 已有只读出口先例。来源:代码质量审计(登记债务,修复批边界外) -**归属**:探针基建批 +**归属**:~~探针基建批~~→迭代 13 工程债批 -**状态**:待办 +**状态**:**已落籍销账(2026-10-04,迭代 13 T2,worktree m4iter13-debt-20261004 提交 2241778)**——BoardView cells_probe/diggable_probe/hover_probe/fog_params_probe+level_map_screen layout_probe/canvas_probe 六只读出口(fx_probe 先例形态);直入点改写 26 处(任务书盘点 ~22+断言消息实参隐性直读 4——_hover 1/_fog_lantern·_fog_enabled 2/_cells 消息实参 3 内含);grep 复核零残留;06 原条目「BoardView._layout」系笔误(_layout/_canvas 属 level_map_screen)随批勘正。 #### 杂项尾差(一批打包) adventure_table.tres 补 bonus_candidate_count 字段落档(T5 注:脚本默认 3=现行值,落档属结构改动);grant_aux_item 返回值接留痕同型件 level_runner :438 区(T5 注);README.md:402 提及已删 _autoplay_battle_perfect 旧名(T6 注);critical-paths.md:6「切片 96/96」过期字样(T7 注);hero_info_panel_view:29 散文提及已删 PANEL_HERO 色名(T7 注)。来源:T4-T7 执行报告遗留观察 -**归属**:下一清扫批顺路 +**归属**:~~下一清扫批顺路~~→迭代 13 工程债批 -**状态**:待办 +**状态**:**已落籍销账(2026-10-04,迭代 13 T6,worktree m4iter13-debt-20261004 提交 d78b8d4)**——首件 adventure_table.tres bonus_candidate_count 核证已落档销账(迭代 7-11 间落地,字段在档/schema 校验在位/消费点已读表值);余四件全清=grant_aux_item 节点渠道返回值接留痕(镜像 :580 防御兜底范式)/README 旧名 _autoplay_battle_perfect 改 _decide_act 分派口径/critical-paths「切片 96/96」换代 718/0 口径/hero_info_panel_view 死色名 PANEL_HERO 注记校正。 From 2402877589e00583b560d38b32e937e2ab8f87bd Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sun, 4 Oct 2026 08:33:04 +0800 Subject: [PATCH 8/8] =?UTF-8?q?docs(active):=20=E5=8F=B0=E8=B4=A6342=20bra?= =?UTF-8?q?nch=E4=BB=BD=E5=9B=9E=E9=80=80=E2=80=94=E2=80=94=E6=94=B9?= =?UTF-8?q?=E8=B5=B0=E4=B8=BB=E5=8C=BA=E8=B1=81=E5=85=8D=E9=9D=A2=E7=9B=B4?= =?UTF-8?q?=E6=94=B9=EF=BC=88A4=E6=89=B9336-341=E5=9C=A8=E5=86=8C=E5=90=8C?= =?UTF-8?q?=E6=AC=BE=E5=85=88=E4=BE=8B=EF=BC=9Bbranch=E4=BB=BD=E4=BC=9A?= =?UTF-8?q?=E9=98=BB=E5=A1=9E=E4=B8=BB=E5=8C=BA=E5=90=88=E5=B9=B6=E3=80=94?= =?UTF-8?q?=E4=B8=BB=E5=8C=BA=E5=B7=A5=E4=BD=9C=E6=A0=91=20active.md=20?= =?UTF-8?q?=E6=9C=AA=E6=8F=90=E4=BA=A4=E6=80=81=E3=80=95=EF=BC=8C=E4=B8=BB?= =?UTF-8?q?=E5=8C=BA=E5=90=88=E5=B9=B6=E5=89=8D=E7=9B=B4=E6=94=B9=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E5=8F=B0=E8=B4=A6342=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- active.md | 1 - 1 file changed, 1 deletion(-) diff --git a/active.md b/active.md index d29bda29..4a727dbb 100644 --- a/active.md +++ b/active.md @@ -753,4 +753,3 @@ 335. **全局作用域变异调研裁决+迭代14③号卡立项批落籍(2026-10-04,本会话;worktree global-variant-iter14-20261004 单提交 29db278+主区合并)**:**台账号 335=接续 334**(334 系并行挖掘风险不等式调研批先期落籍,撞号改登——先例同台账 96/90);纯文档零代码零 git 冒烟面。**裁决**——调研报告(design/research/调研-全局作用域变异设计空间-2026-10-04.md,本会话前期 v1.0 已交付)P1-P8 全按推荐案:P1 乙=立项(撤 2026-09-19「Alpha 再设计」挂起)/P2 甲=案A 资源面首发/P3 甲=替换制(Boss 图不布 region 片——L2 完美 Boss 承伤 55.8% 已越带不叠加)/P4 甲=L2/L3 Boss 恒定制+类型固定演进/P5 甲=开战横幅+HUD 常驻角标+帮助手册行三层预告/P6 甲=L1 Boss 免疫(带B [50,85] 教学保护)/P7 甲=带A 收敛杠杆注册 M-4/P8 甲=案B/E/C 按档位留档;grilling 盘问四前沿同全按推荐:Q1 甲=案A 换形**微贫向偏斜**(v1.0「沃土+雷密对冲」勘误作废——雷数系生成器事实、变异布置在装板第 4 步不可加,改生成端=踩结构面否决理由;正向格面池合计不变、进攻族 attack/blast/pierce/magic 上浮/生存族 heal/defense 明显下调、EV 微降 3-5% provisional、gp/action 白名单不动)/Q2 甲=L2/L3 双关同型(关间台阶由 L3 既有新内容满足;同型双关白送同型对照读数)/Q3 甲=迭代 12 四件地形应对件(地脉采撷/荆棘编织者/净化工兵/熔火之躯)Boss 图失效接受为知情代价(主战场普通图 6/7,Boss 覆盖 1/7;registry/调参日志随批登记)/Q4 甲=独立批先行、带A 判读后置 M-4 只测不判;代拍=偏运硬币合成序图级先件级后(随批逐位锚登记)。**落地**——报告 v1.1 裁决落盘(全文按裁决后口径回写;勘误注记两处=雷密对冲作废+固定演进下全局型无可重掷〔替换制 Boss 图 variant_place 零取号=合法态〕;新增 §8 实现面补强八条=门控兜底/偏斜白名单/清图独立池/承载方式/合成序/随批文档清单七正主/MC off 基线语义注记/呈现件闲置注记)+roadmap 迭代 14 行**③号卡**需求登记(①初始展开收窄批②判式换代批③全局变异首发批互不撞——收窄=普通图初始展开轴/判式=不等式判读口径/全局变异=Boss 图资源面轴;出口读数各自分开归因);批位备注=任务书待起草,前置换用户发令。**流程**——v1.0 系任务书「零 git 操作」约束下经 .zcode/tmp 中转落主区未跟踪件(守卫豁免路径,先例同 A5 调研);本批转正入籍时删除主区未跟踪 v1.0(内容已由提交历史承载)防 merge 撞名;合并遇并行批双撞(active.md 台账 334+roadmap 迭代 12 行落籍状态)双方保留解决、本批改号 335。**验证**——doc_link_audit --strict 死链 2 处均存量非本批引入(06 链 A5 未跟踪件与 archive gitignored 件,主区盘面存在提交树不存在,主区 0 死链对照);纯文档批冒烟不适用(两入口铁规)。 335 注. 台账落籍时点补记:主区清账合并时撞并行会话合并冲突残场一次(「unmerged files」瞬态——并行会话自行收口至 8f3fc72 后重试即过);worktree 清理后仅剩并行在飞 `global-variant-iter14`(迭代 14 全局变异批,本会话零触碰)。master 终态 f3fcfa7。 -342. **迭代 13 工程债与测试基建批落籍(2026-10-04,本会话;worktree m4iter13-debt-20261004 六提交 cd7d3ea/2241778/fec3064/4c79310/d78b8d4/89ed0d1)**:D1 click 探针完整局适配(分派链序修正〔shop 停点先于 level_screen——迭代 11 商店房双 visible 烧预算根因实证〕+停点/锁期/重放三态断言三种子绿)/D2 探针只读出口六件 26 直入清零/D3 ArmedTimer helper 三处计时收口(P4 乙,缝零动视觉零变化)/D4 main.gd 二期瘦身 1378→1119(bonus_interact_controller+camp_shop_presenter 拆件接线留组合根;家族②契约3绑定面留置;~900±50 锚差归因=族级清单上限 336 行不自洽,出口报告呈报)/T5 aux 二期判不合收口(三轴相异证据链,零代码零 MC)/D5 杂项四件/D6 爆破工兵包换装启用(cell_removal=未揭示·无旗·是雷——判定逻辑零改动+CellUnit 退色暗幕 P2 甲+充能表驱动零新码;MC 静态零漂移证明零跑测)。**闸**=冒烟 718/0(断言总数持平基线不减)+run_all 15+click 三种子绿+全探针绿+doc 三闸零新增(lint 2/link 2/term 1 存量同款非本批面)+熵增 PASS。**待用户**:出口人闸(出口包=loop 计划书附录A/B+C+06 六条目销账预览+P1/P2 落文预览);实机目检候选=营地装配爆破工兵包→使用→移除格退色呈现(本批零真机面)。