# VERTICAL SLICE - NOT FOR PRODUCTION # 战斗驱动循环单点——smoke 族探针共用的「挖掘至停手」整轮驱动。 # 策略 = AutoPlayer.choose_dig(O4 单一实现):挖最近可挖格(异格先移位)/ 无可挖或相位离 Active 停手。 # 语义锚(monte_carlo 原型逐位保持):guard 400 步防挂死;移位后相位/轮次离场即 break; # 轮末仍 Active 且未换轮 → settle_batch stop。smoke_all._run_battle/_auto_play_round 为冒烟本体 # 不经本面(含回归断言,禁并入);fx_shot_probe 为带墙钟抓帧的异步变体亦不在列。 extends RefCounted const AutoPlayer := preload("res://sim/auto_player.gd") const GUARD_ACTIONS := 400 # 单轮动作步防挂死上限(被收口探针原值逐一同值) # 驱动一整轮(驱动循环单点——通用签名)。still_running = 调用方战斗活性谓词(缺省恒真): # monte_carlo / skill_rhythm 传 `func(): return out.result == ""`(battle_finished 已发的轮内不再动手), # damage_probe 关卡驱动传 runner 绑定 + battle_result 谓词,battle 级探针传空即可。 # act_provider 契约(策略回调——func(settlement) -> Dictionary): # {"kind":"stop"}/空字典 = 停手出环;单步 {"kind":"move"|"dig"|"seal"|"refresh","cell":V}(refresh 无 cell); # 合步 {"kind":"move","cell":X,"next":{"kind":"dig","cell":Y}}——move 提交后查相位/轮次、变更即断弃 next。 # on_action 契约(日志回调——func(submit_kind:String, cell:Vector2i, ok:bool, round_index:int)): # submit_kind ∈ {move, dig, seal_place, refresh, stop}——轮末统一 stop 也经此回调(accept 取 outcomes[0].accepted); # 空 Callable = 不记日志。 # max_rejects:策略回调被拒连续上限(≤0 = 不设防——缺省形态零防线语义保持);ok 清零否则累加。 # 缺省两参形态(act_provider 空)= AutoPlayer.choose_dig 原语义逐位:guard 400、异格先移、合步 dig、 # 移位后相位/轮次离场断弃 next、轮末 stop——四调用方(dump_battle/monte_carlo/damage_probe/skill_rhythm)两参零改动。 static func drive_round(settlement, still_running: Callable = Callable(), act_provider: Callable = Callable(), on_action: Callable = Callable(), max_rejects: int = 0) -> void: var start_round: int = settlement.round_index var minesweeper = settlement.minesweeper var guard := GUARD_ACTIONS var rejects := 0 var using_provider: bool = not act_provider.is_null() while _alive(still_running) and settlement.action_economy.phase == "Active" \ and settlement.round_index == start_round and guard > 0 \ and (max_rejects <= 0 or rejects < max_rejects): guard -= 1 var act: Dictionary = act_provider.call(settlement) if using_provider else _default_act(minesweeper) var kind := String(act.get("kind", "stop")) if kind == "stop" or act.is_empty(): break if _submit_act(settlement, act, on_action, start_round): rejects = 0 else: rejects += 1 if _alive(still_running) and settlement.action_economy.phase == "Active" and settlement.round_index == start_round: var out_s: Array = settlement.minesweeper.settle_batch([{"action": "stop"}]) _on(on_action, "stop", Vector2i(-1, -1), _accepted(out_s), start_round) # 缺省策略(AutoPlayer.choose_dig 原生 plan 专用):同位直接 dig、异位先移合步 next=dig static func _default_act(minesweeper) -> Dictionary: var plan := AutoPlayer.choose_dig(minesweeper) if plan.is_empty(): return {"kind": "stop"} if plan.hero_cell == minesweeper.hero_pos(): return {"kind": "dig", "cell": plan.dig_cell} return {"kind": "move", "cell": plan.hero_cell, "next": {"kind": "dig", "cell": plan.dig_cell}} # 单步提交分派(move/dig/seal→settle_batch——seal 提交动作名 seal_place;refresh→player_request_refresh; # 合步 move→next 递归提交——相位/轮次离场断弃,与原内联逐位同) static func _submit_act(settlement, act: Dictionary, on_action: Callable, ri: int) -> bool: var kind := String(act.get("kind", "stop")) var cell: Vector2i = act.get("cell", Vector2i(-1, -1)) var ok := false if kind == "refresh": ok = bool(settlement.player_request_refresh()) _on(on_action, "refresh", Vector2i(-1, -1), ok, ri) return ok var action_name := "seal_place" if kind == "seal" else kind var out_s: Array = settlement.minesweeper.settle_batch([{"action": action_name, "cell": cell}]) ok = _accepted(out_s) _on(on_action, action_name, cell, ok, ri) if kind == "move": var next: Dictionary = act.get("next", {}) if next.is_empty(): return ok if settlement.action_economy.phase != "Active" or settlement.round_index != ri: return ok # 移位后相位/轮次离场——断弃 next _submit_act(settlement, next, on_action, ri) return ok static func _on(on_action: Callable, kind: String, cell: Vector2i, ok: bool, ri: int) -> void: if not on_action.is_null(): on_action.call(kind, cell, ok, ri) static func _accepted(outcomes: Array) -> bool: return not outcomes.is_empty() and bool(outcomes[0].accepted) static func _alive(still_running: Callable) -> bool: return still_running.is_null() or bool(still_running.call())