iter56-卡2:封印动作改版(迭代56卡2·受理即揭示): 规则核心(target_sealed挖封印格拒/清图口径=挖开∪封印/能清图的封最优先/notify_cleared清图收轮/sealed_identity撤出→BoardData.cell_identity——上限20用户裁)+结算面dug_under_seal删净+破封一击退役+呈现层(地板+身份字形+印记不依赖揭示态+文案四件)+探针V8-V10+sim域(标空封印剔除约束增强+错触直挖补偿注入0.10)+golden三图换代; MC同批重锚50/档=100局(2026-11-08用户批准)三方对照无结构性恶化; 高档审计通过(17行全量·审计后勘误2项闭环)

This commit is contained in:
DKChan committed 2026-11-08 03:47:33 +08:00
1 parent 908e518306
commit 5ec4a311bb
34 files changed
+589 -164

No files matched your search

+9
View File
@@ -142,6 +142,15 @@ func _spend_cost_and_arbitrate(cost: int, clears_map: bool) -> bool:
return true
# 封印路径清图事实上报(排雷玩法 _do_seal_place 受理后清图判定成立时调用):
# 轮末仲裁与 request_dig 的 cleared 语义同源——剩余 AP 当场清零作废、不进 RAP(EC7/EC10),
# 同帧 cleared > ap_exhausted 胜出(覆盖写 _last_round_end)。封印不产挖掘计费,
# 清图事实经独立上报口进入仲裁(硬塞 request_dig 会引入假挖掘记账)。
func notify_cleared() -> void:
_ap = 0
_finish_round("cleared", "new_board")
func _spend_and_arbitrate(action_kind: String, clears_map: bool) -> bool:
var cost := _cost(action_kind)
if _ap < cost:
+8
View File
@@ -43,6 +43,14 @@ func is_safe(c: Vector2i) -> bool:
func digit_of(c: Vector2i) -> int:
return digits.get(c, -1)
## 格身份事实(封闭枚举):kind = "mine"(digit 恒 -1)| "safe"(digit = 邻雷数)。
## 封印受理即揭示的消费形态 = 呈现/测量侧按封印态(MinesweeperRules.has_seal)取本读数拼装——
## 身份事实单点在板数据(is_mine/digit_of 同源),禁消费方各自拼 kind 枚举。
func cell_identity(c: Vector2i) -> Dictionary:
if is_mine(c):
return {"kind": "mine", "digit": -1}
return {"kind": "safe", "digit": digit_of(c)}
func variant_ids(c: Vector2i) -> Array:
return variants.get(c, [])
+3 -3
View File
@@ -52,12 +52,12 @@ const EFFECT_KINDS := ["add_ticket_rider", "modify_ticket", "damage_mult", "open
## fact 条件词汇(封闭枚举——payload 同名键直读面〔fact 取值序 payload
## 优先——effect_engine._fact_value 既有机制〕。new_resource_type=首开资源类型〔地质学家〕;
## is_edge_cell=边缘格〔边陲矿工〕;is_blank_zero=空白连锁〔跳格采掘〕;dug_under_seal=被挖格
## 当时有封印〔破封一击〕;ap_exhausted_last=上轮 AP 耗尽〔一滴不剩〕;hp_ratio=生命比例〔背水
## is_edge_cell=边缘格〔边陲矿工〕;is_blank_zero=空白连锁〔跳格采掘〕;ap_exhausted_last=上轮
## AP 耗尽〔一滴不剩〕;hp_ratio=生命比例〔背水
## 06 前置条兑付〕;amount=单次伤害量〔保险丝〕;ap_left=停手剩余 AP〔弃图兑换〕)
const FACTS := ["matchup", "is_first_dig_of_battle", "terrain_kind", "target_armor_class",
"seals_count", "nest_count", "round_index", "counter",
"new_resource_type", "is_edge_cell", "is_blank_zero", "dug_under_seal",
"new_resource_type", "is_edge_cell", "is_blank_zero",
"ap_exhausted_last", "hp_ratio", "amount", "ap_left", "pct",
"battle_index"] # 关卡域战斗序(1 基,enc 携带;开局姿态组「每关首轮窗」条件面)
+58 -46
View File
@@ -8,8 +8,7 @@ extends RefCounted
## 揭示事实流:每格首次进入已开集恰发一次(已开格零事件)。
## 载荷键集(四处发射位并集):cell: Vector2i/kind: "safe"|"mine"/digit: int(安全格邻雷数;危险格 -1)/
## variant_ids: Array(地形变异身份,危险格空集)/channel: "initial"|"dig"|"chain"|"fx_reveal"/
## resource: bool(格面资源兑付资格:initial=false,其余 true)/dug_under_seal: bool(首格带封印直挖标记——
## initial 通道不发此键)。
## resource: bool(格面资源兑付资格:initial=false,其余 true)。
## 发送时点:initial=install_board 初始展开逐格;dig/chain=_do_dig 揭示闭包内(本格 AP 扣费在其后);
## fx_reveal=fx_reveal 闭包内(零 AP 消耗零相位要求)。
## 消费方依赖:战斗结算 _on_cell_revealed 以 resource 分流缓冲(fx_reveal 由兑付点直入账,禁二次缓冲)、
@@ -94,14 +93,16 @@ func has_seal(c: Vector2i) -> bool:
return _seals.has(c)
func revealed_cells() -> Array:
return _revealed.keys()
func _safe_remaining() -> int:
# 清图口径 = 已挖 ∪ 已封印(封印受理即揭示改版——封掉的安全格计入完成)
var n := 0
for c in _board.safe_cells():
if not _revealed.has(c):
if not _revealed.has(c) and not _seals.has(c):
n += 1
return n
@@ -266,25 +267,41 @@ func settle_batch(intents: Array) -> Array:
func _classify(intent: Dictionary) -> int:
# 0 = C_dig(执行后清图——真模拟,禁计数器捷径);1 = C_stop;2 = C_other
# 清图口径 = 已挖 ∪ 已封印(封印受理即揭示改版——「能清图的挖或封」同归最先类)
if intent.get("action", "") == "dig":
var c: Vector2i = intent.get("cell", Vector2i(-9, -9))
if _board_face_dig(c) == "" and not _board.is_mine(c):
# 模拟 Chain(O, c) 后清图判定——挖封印格同口径:本格封印视为已隐式退役(其余封印照挡)
var sim_seals: Dictionary[Variant, Variant] = _seals.duplicate()
sim_seals.erase(c)
var chain := _chain_closure(c, sim_seals) # 与真实执行同口径——空封印集捷径会在封印挡连锁时误判 C_dig(排雷玩法:真模拟)
# 模拟 Chain(O, c) 后清图判定——真模拟禁计数器捷径(连锁不越封印,真封印集口径)
var chain := _chain_closure(c, _seals)
var remaining := 0
for sc in _board.safe_cells():
if not _revealed.has(sc) and not chain.has(sc):
if not _revealed.has(sc) and not chain.has(sc) and not _seals.has(sc):
remaining += 1
if remaining == 0:
return 0
return 2
if intent.get("action", "") == "seal_place":
return _classify_seal(intent.get("cell", Vector2i(-9, -9)))
if intent.get("action", "") == "stop":
return 1
return 2
# 封印意图的清图预判分类(批次语义序「能清图的封」取数面——受理合法性模拟与清图口径模拟)
func _classify_seal(c: Vector2i) -> int:
if not _board.in_bounds(c) or is_revealed(c) or has_seal(c) or _removed.has(c):
return 2
if _board.is_mine(c):
return 2 # 封危险格不清图(封印受理即揭示——身份当场可见但安全格分母不减)
# 模拟封印受理后清图判定(换装口径:安全格完成 = 已揭示 ∪ 已封印)
var sim_seals: Dictionary[Variant, Variant] = _seals.duplicate()
sim_seals[c] = true
for sc in _board.safe_cells():
if not _revealed.has(sc) and not sim_seals.has(sc):
return 2
return 0
func _execute(intent: Dictionary) -> Dictionary:
match intent.get("action", ""):
"dig":
@@ -305,16 +322,8 @@ func _do_dig(c: Vector2i) -> Dictionary:
var sub := _board_face_dig(c)
if sub != "":
return _outcome(false, "rejected", "board_face", sub)
# 挖封印格(封印不可逆):本格封印事务内隐式退役——链闭包按「本格无封印、其余封印照挡」
# 计算(封印格为 0 时连锁照常外扩);request_dig 被拒则封印原地保留(事务性),受理后该封印轮末不判
# (发射面基线 = 在场封印——标中/标空兑付让位于挖掘本体结局,四情况②④)。
var was_sealed := has_seal(c)
var eff_seals: Dictionary[Variant, Variant] = _seals
if was_sealed:
eff_seals = _seals.duplicate()
eff_seals.erase(c)
# 真模拟连锁与清图(F2/F1——单一实现两名字)
var chain := _chain_closure(c, eff_seals)
# 真模拟连锁与清图(F2/F1——单一实现两名字;连锁不越封印,挖封印格已被合法性表拒绝)
var chain := _chain_closure(c, _seals)
var clears: bool = _cleared_after(chain)
# F2 口径:chain_size = |O′ ∖ (O ∪ {c})|——闭包含已开集 O,只数新开格(不含首格)
var chain_size := 0
@@ -325,10 +334,8 @@ func _do_dig(c: Vector2i) -> Dictionary:
if not action_economy.request_dig(chain_size, clears):
return _outcome(false, "rejected", "resource_face", "")
# 事务内执行:首格 + 连锁新格揭示(BFS 规范序,零特判);事件 = O′ ∖ O
if was_sealed:
_seals.erase(c)
var reveal_order: Array[Variant] = _chain_reveal_order(c, eff_seals)
_reveal_chain(c, reveal_order, was_sealed)
var reveal_order: Array[Variant] = _chain_reveal_order(c, _seals)
_reveal_chain(c, reveal_order)
if action_economy.phase != action_economy.PHASE_ACTIVE:
_emit_round_end_seals_and_payload()
return _outcome(true, "executed", "", "")
@@ -337,8 +344,7 @@ func _do_dig(c: Vector2i) -> Dictionary:
## 连锁揭示域:首格+连锁新格揭示(BFS 规范序——cell_revealed 发射序即链序,整体随段搬迁)。
## 格面资源资格:chain/dig 恒 true——安全格与危险格一视同仁(危险格亦携带,
## 负向兑付归 战斗结算 极性路由);initial(封锁格)恒 false。资源本体与极性不在本系统。
## dug_under_seal = 首格带封印直挖标记(战斗结算 情况④兑付改判消费;连锁格恒 false——连锁不越封印)
func _reveal_chain(c: Vector2i, reveal_order: Array, was_sealed: bool) -> void:
func _reveal_chain(c: Vector2i, reveal_order: Array) -> void:
for rc in reveal_order:
if _revealed.has(rc):
continue # 闭包可穿过已开区——已开格零事件(cell_revealed × |O′ ∖ O|)
@@ -348,14 +354,12 @@ func _reveal_chain(c: Vector2i, reveal_order: Array, was_sealed: bool) -> void:
cell_revealed.emit({
"cell": rc, "kind": "mine", "digit": -1,
"variant_ids": [], "channel": channel, "resource": true,
"dug_under_seal": was_sealed and rc == c,
})
else:
_revealed[rc] = {"kind": "safe", "digit": _board.digit_of(rc)}
cell_revealed.emit({
"cell": rc, "kind": "safe", "digit": _board.digit_of(rc),
"variant_ids": _board.variant_ids(rc), "channel": channel, "resource": true,
"dug_under_seal": was_sealed and rc == c,
})
@@ -366,9 +370,10 @@ func _board_face_dig(c: Vector2i) -> String:
return "not_adjacent"
if is_revealed(c):
return "target_revealed"
if has_seal(c):
return "target_sealed" # 封印受理即揭示、不可再挖开(封印承诺 = 对该格的最终处置——改判路径已退役)
if _removed.has(c):
return "target_removed" # 中立移除格不可交互(拒绝先于消耗,pattern 1 board_face 腿)
# 封印不拦挖掘(封印不可逆):有封印格直接挖 = 对承诺的兑现,兑付改判归 战斗结算。
return ""
@@ -387,17 +392,14 @@ func fx_reveal(c: Vector2i) -> bool:
cell_revealed.emit({
"cell": rc, "kind": "safe", "digit": _board.digit_of(rc),
"variant_ids": _board.variant_ids(rc), "channel": "fx_reveal", "resource": true,
"dug_under_seal": false,
})
return true
## 挖一格→真实翻开集只读预览(sim 信息面出口——与 _classify 同口径:本格封印视为已隐式退役、
## 其余封印照挡;复用 _chain_closure 闭包;零状态写入、零事件发射)
## 挖一格→真实翻开集只读预览(sim 信息面出口——与 _classify 同口径:连锁不越封印、
## 真封印集口径;复用 _chain_closure 闭包;零状态写入、零事件发射)
func preview_reveal(c: Vector2i) -> Array[Vector2i]:
var sim_seals := _seals.duplicate()
sim_seals.erase(c)
var closure := _chain_closure(c, sim_seals)
var closure := _chain_closure(c, _seals)
var out: Array[Vector2i] = []
for cc in closure.keys():
if cc != c and not _revealed.has(cc):
@@ -436,34 +438,44 @@ func _chain_reveal_order(start: Vector2i, seal_set: Dictionary) -> Array:
func _cleared_after(chain: Dictionary) -> bool:
# 清图口径 = 已挖 ∪ 已封印(与 _safe_remaining 同源——私有共享口径单点,禁第三处重复实现)
for c in _board.safe_cells():
if not _revealed.has(c) and not chain.has(c):
if not _revealed.has(c) and not chain.has(c) and not _seals.has(c):
return false
return true
# ---- 封印 ----
func _do_seal_place(c: Vector2i) -> Dictionary:
var sub := ""
# 封印棋盘面合法性(board_face 拒绝情形序:目录外/不相邻/已开/已有封印/已移除——与挖掘合法性同构分域)
func _seal_face_reject(c: Vector2i) -> String:
if not _board.in_bounds(c):
sub = "out_of_catalog"
elif not (seal_range_free or _adjacent_to_hero(c)):
sub = "not_adjacent" # 信标网络装配时豁免(封印不限相邻——封印动作仍按标准价计费,规则放宽仅可达性)
elif is_revealed(c):
sub = "target_revealed"
elif has_seal(c):
sub = "seal_present"
elif _removed.has(c):
sub = "target_removed" # 中立移除格不可交互
return "out_of_catalog"
if not (seal_range_free or _adjacent_to_hero(c)):
return "not_adjacent" # 信标网络装配时豁免(封印不限相邻——封印动作仍按标准价计费,规则放宽仅可达性)
if is_revealed(c):
return "target_revealed"
if has_seal(c):
return "seal_present"
if _removed.has(c):
return "target_removed" # 中立移除格不可交互
return ""
func _do_seal_place(c: Vector2i) -> Dictionary:
var sub := _seal_face_reject(c)
if sub != "":
return _outcome(false, "rejected", "board_face", sub)
if not action_economy.try_spend("seal_place"):
return _outcome(false, "rejected", "resource_face", "")
_seals[c] = true # 封印动作零格子事件外发(发射面封闭;反馈经调用返回值命令式驱动)
if _cleared_after({}):
# 封印受理即揭示改版:封掉最后安全格 = 清图完成——轮末仲裁走 cleared(行动经济
# notify_cleared 独立上报口,与 request_dig 的 cleared 语义同源;同帧覆盖 ap_exhausted)
action_economy.notify_cleared()
if action_economy.phase != action_economy.PHASE_ACTIVE:
# 封印动作花完末点 AP 同样终结轮次(行动经济 三出口——AP 用完与动作种类无关;缺漏使 round_end
# 永不发出、战斗结算 的 Waiting 结算点永不到达 = 整局软锁)
# 永不发出、战斗结算 的 Waiting 结算点永不到达 = 整局软锁);清图收轮同此出口发判定批
_emit_round_end_seals_and_payload()
return _outcome(true, "executed", "", "")
+1 -1
View File
@@ -172,7 +172,7 @@ func _safe_dig_open(v: Dictionary) -> Dictionary:
for c_v in v.resolved:
var c := Vector2i(c_v)
if fx_host.minesweeper.has_seal(c):
continue
continue # 封印格不代开(封印受理即揭示——天眼揭示绕开承诺格,零动作)
if fx_host.minesweeper.fx_reveal(c):
opened_any = true
if not opened_any:
+6 -9
View File
@@ -93,15 +93,12 @@ func _reveal_batch(ri: int) -> void:
_reveal_mine(evt, snap, ri)
# safe 事件兑付(封印不可逆四情况④:插安全格再挖开 = 零惩罚零奖励——正向兑付作废〔归因行在案〕;
# 标空判定已随封印擦除自然缺席——被挖的封印不在轮末在场基线。dig_reveal 发射+施加序:
# safe 事件兑付(封印受理即揭示改版:挖封印格已被规则合法性表拒绝——改判路径整体退役,
# 本函数只受理真实揭示的 safe 事件。dig_reveal 发射+施加序:
# 乘区收集 → 正向兑付 → 施加面〔计数器/揭示入队/追加产出/奥术矢〕。
# payload 携 is_edge_cell〔边陲矿工〕/is_blank_zero〔跳格采掘〕/
# new_resource_type〔地质学家——快照 cell_type 首见判定,见后集登记〕)
func _reveal_safe(evt: Dictionary, snap: Dictionary, ri: int) -> void:
if bool(evt.get("dug_under_seal", false)):
emit_row_cb.call({"type": "reward_ineffective", "source_class": "safe_cell", "origin_cell": evt.cell}, ri, "奖励作废(封印格直接挖开——零惩罚零奖励)", 0, {})
return
var board_b: BoardData = minesweeper.current_board()
var ctype := String(snap.get("cell_type", ""))
var is_new_type := fx_host.mark_new_resource_type(ctype)
@@ -145,12 +142,12 @@ func _reveal_safe_apply(dr_hits: Array, evt: Dictionary, ri: int) -> void:
# mine 事件兑付(封印不可逆四情况②:插危险格再挖开 = 只受挖雷一层惩罚——mine_hit 照常兑付惊动巢穴,
# 标中不发〔封印已随挖掘擦除、不入轮末判定基线〕。mine_hit 发射+施加序:施加面收集 → 减伤落快照 →
# mine_hit 发射+施加序:施加面收集 → 减伤落快照 →
# 计数器 → 揭示入队 → 负向兑付〔带减半系数〕→ 反打开单)
func _reveal_mine(evt: Dictionary, snap: Dictionary, ri: int) -> void:
var mine_power := int(snap.get("power", 0))
var mh_hits := fx_host.fx_emit("mine_hit", {"cell": evt.cell, "round": ri,
"damage": mine_power, "dug_under_seal": bool(evt.get("dug_under_seal", false)),
"damage": mine_power,
"hp_ratio": int(round(100.0 * float(int(units.hero_unit().get("hp", 0))) / float(maxi(1, int(units.hero_unit().get("max_hp", 1))))))})
var applied := _reveal_mine_apply(mh_hits, snap, evt.cell, ri)
if int(applied["mine_reduce"]) > 0:
@@ -201,8 +198,8 @@ func _reveal_mine_apply(mh_hits: Array, snap: Dictionary, cell: Vector2i, ri: in
# 地形变异 兑付批(授予段尾——触发枚举 on_open:挖开即伤、chain 通道同口径;伤害单据案C 暂存到
# 战斗段末兑付〔案C〕;信息型 hide_number 不进结算——呈现层读取,见 board_view 数字遮蔽;
# 危险格恒无变异——布置段排除,双层叠罚不存在;封印格直挖照吃变异伤害——地形后果同 mine_hit 口径,
# 非奖励不作废;吞噬流资源兑付〔荆棘编织/地脉补偿〕中伤害形态行同样经案C闸暂存,非伤害形态照常当场)
# 危险格恒无变异——布置段排除,双层叠罚不存在;封印格不可挖(封印受理即揭示——变异伤害只随
# 真实揭示兑付);吞噬流资源兑付〔荆棘编织/地脉补偿〕中伤害形态行同样经案C闸暂存,非伤害形态照常当场)
func _terrain_batch(ri: int) -> void:
for evt in facts.reveals:
var vids: Array[Variant] = evt.get("variant_ids", [])