iter53-卡2:改写族清退批: 四段零现役注册死分支摘除+词表收窄spike与trade双键+负域探针两枚+注释面退役名35处清零+玩家可见文案三处与sim死匹配

This commit is contained in:
DKChan committed 2026-11-06 00:08:18 +08:00
1 parent 55f4de3ca4
commit 76a7067440
13 files changed
+89 -111

No files matched your search

+1 -1
View File
@@ -79,7 +79,7 @@ func try_spend(action_kind: String) -> bool:
# 排雷玩法 挖掘专用:chain_size = 连锁附加格数(不含首格);clears_map = 本次动作后全图安全格开完。
# 深挖钻头付费开掘(dig_paid counter_spend 订阅施加面):按序消耗族——AP 富余(≥基础+对价)时本动作付全价并积本轮付费计数(效果引擎
# 付费开掘(dig_paid counter_spend 订阅施加面):按序消耗族——AP 富余(≥基础+对价)时本动作付全价并积本轮付费计数(效果引擎
# 计数器=授予段追加产出配额取数面);AP 不足全价时 fallback 降级只付基础 1 照常开格、不积计数
# ——持有钻头永不锁死普通挖格(散件保底 B-2)。
func request_dig(chain_size: int, clears_map: bool) -> bool:
+2 -2
View File
@@ -255,7 +255,7 @@ func begin_battle(enc: Dictionary) -> bool:
_begin_battle_fx_state(enc)
_begin_battle_attune_params(enc)
_begin_battle_preview_economy(enc)
# 工兵晨令=round_start grant_ap(mode:queue) 订阅行承载(when round_index ≤ effective_rounds
# 晨间 AP=round_start grant_ap(mode:queue) 订阅行承载(when round_index ≤ effective_rounds
# value_from 定影);round_start 发射先于行动经济轮切换=树规则 queue 型当轮入队当轮兑现
# (先手压制=首轮 AP+1 语义锚);计数器 round_start 重置点同点生效
round_index = 1
@@ -535,7 +535,7 @@ func _exit_distribution() -> void:
if not _install_and_reset():
_fatal("换图编排失败")
return
# 工兵晨令续队直调拆除(内容数据驱动扩展批 T4——round_start 订阅行承载,见 begin_battle 注)
# 晨间 AP 续队直调拆除(内容数据驱动扩展批 T4——round_start 订阅行承载,见 begin_battle 注)
action_economy.notify_settlement_done(mode)
# round_start 发射先于行动经济轮切换(queue 型 grant_ap 当轮入队当轮兑现——与首轮同口径)
round_index += 1
+19 -66
View File
@@ -106,7 +106,7 @@ func _per_value(name: String, payload: Dictionary) -> int:
return 0
# 巢穴控制数(fact nest_count 取数面——敌方名下 weaken 实例总数,猎巢人订阅面;
# 巢穴控制数(fact nest_count 取数面——敌方名下 weaken 实例总数,nest_count 订阅消费面;
# 个体化后削弱为累加行、每体各持一条——总数 = 被削弱个体数)
func _nest_count() -> int:
var n := 0
@@ -135,7 +135,7 @@ func submit(t: Dictionary) -> int:
# attack_commit 发射点(我方伤害单据开立入口;
# 命中挂单据 context 随行——施加面在 _resolve_hero_attack 管线算术前逐族施加,开单时点=主角攻击
# 单恒为段内首单(结账现场与开单现场一致)。载荷 atk/target/matchup + 挖掘 streak 事实
# (执拗订阅面)+巢穴控制数〔猎巢人〕)
# (开格 streak 订阅面)+巢穴控制数〔nest_count 订阅面〕)
# spec 束必载键(缺载 assert——fail-fast):channel/base_value/target_rule/ri;
# 可选键缺省=原形缺省:origin_cell=(-1,-1)/attack_type=""/rider=0/highlight=""/context={}。
# 返回单据字典九栏目+requires/source_unit 逐字不变(契约 8)。
@@ -247,9 +247,9 @@ func _resolve(t: Dictionary) -> void:
# 主角先手攻击:掷点 → 折扣/暴击 → 克制点数 → hero_offense×enemy_defense 池乘区 → 下限 → 交付
# (attack_commit/clamp_check/armor_roll 订阅施加面:命中=开单入口随单
# context 携带,按固定算序逐族施加——命中序=数据表声明序,确定性)。
# context 携带,按固定算序逐族施加——命中序=数据表声明序,确定性;掷点折扣读数→克制点数两拍)。
# 四步分段:命中面(_hero_attack_ctx,目标/护甲/
# 掷点/按序改写族四拍/克制点数) → 基值装配(_hero_attack_base_value,加法族/计数器施加/倍乘) →
# 掷点/掷点折扣读数/克制点数) → 基值装配(_hero_attack_base_value,加法族/计数器施加) →
# 掷点兑现(_hero_attack_redeem,rolled/池乘/下限/rider/落地/击杀发射) → 交付(_hero_attack_deliver,
# 状态施加/标签/明细行)。步骤间经 ctx/redemption 字典穿线——算序/取值固定(🔴 逐位一致契约)。
func _resolve_hero_attack(t: Dictionary) -> void:
@@ -261,7 +261,7 @@ func _resolve_hero_attack(t: Dictionary) -> void:
_hero_attack_deliver(t, ctx, redemption)
# 命中面(目标/护甲克制 matchup/armor_roll 发射→按序改写族四拍→克制点数读值+订阅改写;
# 命中面(目标/护甲克制 matchup/armor_roll 发射→掷点折扣读数→克制点数读值+订阅改写;
# 后两步各由 _ctx_rewrites/_ctx_clamp_delta 承载)。返回穿线 ctx(空=目标不可达)。
# clamp_check 只在 _ctx_rewrites 内发射一次——_ctx_clamp_delta 消费同批命中(禁重复发射)。
func _hero_attack_ctx(t: Dictionary) -> Dictionary:
@@ -278,54 +278,25 @@ func _hero_attack_ctx(t: Dictionary) -> Dictionary:
return {"target": target, "armor": armor, "atk_type": atk_type,
"ac_hits": (t.context.get("fx_attack_hits", []) as Array), "ac_payload": ac_payload,
"roll_read": rewrites["roll_read"], "threshold": rewrites["threshold"],
"crit": rewrites["crit"], "discount": rewrites["discount"],
"spike_ready": rewrites["spike_ready"], "cdelta": cdelta}
"crit": rewrites["crit"], "discount": rewrites["discount"], "cdelta": cdelta}
# 按序改写族四拍+掷点快照(armor_roll 发射点在此):第一拍矿工重锤命中交换(正负双修原生形态:
# 命中判定减成 trade_hit 点〔更易命中〕换攻击增伤 trade_atk%——高 AC 负期望/低 AC 正期望;
# 改写位先于掷点折扣读数)→ 第二拍专注免掷骰恒定折扣(方差买断成期望——稳定器人格;roll 记 0 归因)
# → 第三拍满层必中(spike ready 时 discount 恒全额、暴击面走倍乘不加掷)→ 第四拍猎弱本能对轻甲敌折扣档改善 value 档
# (对冲 pierce 列 L1 −1 开局罚;改善 = 折扣表向全额方向移档,nat 最大面/已全额不重复)。
# clamp_check 同在此发射(唯一发射点——命中随返回值穿给 _ctx_clamp_delta)。
# 掷点折扣读数+克制点数命中批(armor_roll 发射点在此):一拍护甲掷骰折扣读数(掷点对照
# 防御阈值定折扣档——阈值=防御 AC+攻击加值,nat 面沿折扣表)→ 二拍 clamp_check 发射
# (唯一发射点——命中随返回值穿给 _ctx_clamp_delta)。
func _ctx_rewrites(t: Dictionary, target: Dictionary, armor: String, matchup: String) -> Dictionary:
var ac_hits: Array[Variant] = (t.context.get("fx_attack_hits", []) as Array)
var roll_read: Dictionary[Variant, Variant] = _armor_roll()
var threshold: int = defender_ac(target) + int(db.combat.settlement.damage_roll.attack_bonus)
# armor_roll 发射点(敌护甲掷骰判定;免掷恒折扣订阅面)
var ar_hits := _fx_hits("armor_roll", {"target_ac": threshold, "roll": int(roll_read.roll)})
var trade_hit := 0
for h in ac_hits:
if String(h.get("kind", "")) == "modify_ticket" and int(h.get("trade_hit", 0)) > 0:
trade_hit += int(h.trade_hit)
if trade_hit > 0:
threshold = maxi(1, threshold - trade_hit)
# armor_roll 发射点(敌护甲掷骰判定——事件在册、现役零订阅行,发射面保留)
_fx_hits("armor_roll", {"target_ac": threshold, "roll": int(roll_read.roll)})
var crit: bool = bool(roll_read.crit)
var discount: int = _roll_discount(int(roll_read.roll), threshold)
for h in ar_hits:
if String(h.get("kind", "")) == "modify_ticket" and String(h.get("mode", "")) == "armor_rewrite":
discount = int(h.get("value", 0))
crit = false
roll_read["roll"] = 0
var spike_ready := false
for h in ac_hits:
if String(h.get("kind", "")) == "modify_ticket" and String(h.get("mode", "")) == "spike":
spike_ready = true
discount = 100
var clamp_hits := _fx_hits("clamp_check", {"matchup": matchup})
var shift := 0
for h in clamp_hits:
if String(h.get("kind", "")) != "modify_ticket":
continue
if String(h.get("mode", "")) == "discount_shift":
shift += int(h.get("value", 0))
if shift > 0 and armor == "light" and discount < 100:
discount = _discount_shift_up(discount, shift)
return {"roll_read": roll_read, "threshold": threshold, "crit": crit,
"discount": discount, "spike_ready": spike_ready, "clamp_hits": clamp_hits}
"discount": discount, "clamp_hits": clamp_hits}
# 克制点数读值+订阅改写(魔网织者/湛蓝誓约/猎手刺剑 clamp_check modify delta——克制矩阵查表值
# 克制点数读值+订阅改写(clamp_check 带模式行 modify delta——克制矩阵查表值
# 后的直调;hero_attack 与 board_typed 两消费面同享)。命中批由 _ctx_rewrites 单次发射后传入。
# 淬毒刃纹(行 triggers clamp_check
# modify(mode:clamp_bonus) 订阅命中并入本处 delta 合计;mode 路由=主角攻击面专享,
@@ -338,8 +309,8 @@ func _ctx_clamp_delta(atk_type: String, armor: String, clamp_hits: Array) -> int
return cdelta
# 基值装配(基值+单位标记加伤+矿工重锤增伤面+加法族(attack_bonus/
# value_counter 层值)+计数器施加(attack_commit 用后清零行——命中序=声明序)+spike 倍乘。
# 基值装配(基值+单位标记加伤+加法族(attack_bonus/
# value_counter 层值)+计数器施加(attack_commit 用后清零行——命中序=声明序)。
func _hero_attack_base_value(t: Dictionary, ctx: Dictionary) -> float:
var ac_hits: Array[Variant] = ctx["ac_hits"]
var ac_payload: Dictionary[Variant, Variant] = ctx["ac_payload"]
@@ -347,10 +318,6 @@ func _hero_attack_base_value(t: Dictionary, ctx: Dictionary) -> float:
# 单位标记加伤(处刑台 target_attack_bonus 挂单位字典 fx_atk_bonus 键;
# 阈值沿发射点已写入,本处通用消费——对该敌伤害加算)
base_value += float(int((ctx["target"] as Dictionary).get("fx_atk_bonus", 0)))
# 按序改写族第五拍:矿工重锤增伤面(交换的正向侧)
for h in ac_hits:
if String(h.get("kind", "")) == "modify_ticket" and int(h.get("trade_atk", 0)) > 0:
base_value *= 1.0 + float(int(h.trade_atk)) / 100.0
# 加法族:attack_bonus per 计数器层值(层数×value 加算)。
# value_counter 扩展〔疼痛记忆/弃图兑换/陷阱本能——rider 值=计数器账面,
# 值读于施加时点=命中序先行 rider 后清零行〕
@@ -365,11 +332,6 @@ func _hero_attack_base_value(t: Dictionary, ctx: Dictionary) -> float:
if String(h.get("kind", "")) == "counter_add":
var set_v = h.get("set", null)
_port.counter_add(String(h.get("counter_id", "")), int(h.get("value", 0)) if set_v == null else int(set_v), set_v != null)
# 任意真族:modify_ticket mode=spike 满层倍乘(强制必中已在折扣拍承载)
if ctx["spike_ready"]:
for h in ac_hits:
if String(h.get("kind", "")) == "modify_ticket" and String(h.get("mode", "")) == "spike":
base_value *= float(int(h.get("value", 1)))
return base_value
@@ -391,7 +353,7 @@ func _hero_attack_redeem(t: Dictionary, ctx: Dictionary, base_value: float) -> D
var pooled: int = DamageMath.pool_damage(rolled, clamped_pool("hero_offense", StatusContainer.OWNER_HERO), clamped_pool("enemy_defense", StatusContainer.owner_key_for(target)))
var landed: int = maxi(int(db.combat.settlement.damage_roll.damage_floor), pooled)
# 加法族:英雄侧 rider(enemy_skill rider 对称位补齐口径)——后置必中附加,独立于掷点与乘区
# (奥术余烬「普攻命中后恒定结算」语义);猎巢人巢控光环(per nest_count)/矿工的执拗开格加成
# (普攻命中后恒定结算语义);巢控光环(per nest_count)/开格 streak 加成
# (per dig_streak、cap 封顶)同通道合计;档一质变 rider+1 / 档二(crit 时)rider 乘暴击倍率。
# rider 不改变下限语义(landed 含 rider 后不再过 floor)。
var hero_rider := int(_fact_or("rider_tier_bonus") or 0) # or 0=未绑缺省与 int(null) 缺省同值(int() 构造拒 null——端口缺省 null 面的空值合并)
@@ -421,7 +383,7 @@ func _kill_effects(t: Dictionary, target: Dictionary) -> void:
_port.reveal_effect() # 斩首学者——击杀揭示 1 格(端口路由宿主入队,授予段末兑付)
# 交付:即时执行族(碎甲震击打重甲附加「护甲裂解」——复用 off_guard,零新状态行;
# 交付:即时执行族(重甲附加「护甲裂解」——复用 off_guard,零新状态行;
# apply_effect 域闭/重复授予由状态容器自行守卫)/标签装配/明细行发射/兑现登记。
func _hero_attack_deliver(t: Dictionary, ctx: Dictionary, redemption: Dictionary) -> void:
var payload: Dictionary[Variant, Variant] = redemption["payload"]
@@ -524,11 +486,11 @@ func _resolve_board_typed(t: Dictionary) -> void:
var armor_b: String = String(target.get("armor_type", "light"))
var atk_b: String = String(t.attack_type)
# clamp_check 发射+施加(克制判定修正点棋盘面;与 hero_attack 同表同点:
# modify delta 订阅改写合计进克制点数;discount_shift 档位面属主角攻击专享,本面不施加)
# modify delta 订阅改写合计进克制点数;带模式行只施放于主角攻击面,本面不施加)
var cdelta: int = _counter_delta(atk_b, armor_b)
if atk_b != "":
for h in _fx_hits("clamp_check", {"matchup": atk_b + "_vs_" + armor_b}):
# 无模式 delta 才入棋盘窗(mode 路由:clamp_bonus=主角攻击面专享、discount_shift=折扣档面)
# 无模式 delta 才入棋盘窗(mode 路由:带模式行=主角攻击面专享,棋盘窗只吃无模式 delta)
if String(h.get("kind", "")) == "modify_ticket" and h.has("delta") and String(h.get("mode", "")) == "":
cdelta += int(h.delta)
var dealt: int = DamageMath.counter_component(int(t.base_value), cdelta, int(db.combat.settlement.damage_roll.damage_floor))
@@ -619,15 +581,6 @@ func _counter_delta(attack_type: String, armor: String) -> int:
return int(row.get(armor, 0))
# 折扣档改善(猎弱本能——折扣表向全额方向移 value 档;查不到当前档 = 不动防御口径)
func _discount_shift_up(discount: int, shift: int) -> int:
var tbl: Array[Variant] = db.combat.settlement.damage_roll.roll_tier_discounts
var idx := tbl.find(discount)
if idx < 0:
return discount
return int(tbl[mini(tbl.size() - 1, idx + shift)])
# 池内加算 → clamp [-100,+100](池间乘算归 DamageMath.pool_damage 调用侧);
# 池按个体读(增益状态 v1.5):主角侧池传 OWNER_HERO,敌方侧池传被打者/施放者个体键——
# 乘区只合计该个体名下实例(B 敌的削弱不进 A 敌的账)
+6 -6
View File
@@ -27,7 +27,7 @@ const MINE_EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "seal_se
## bias_rewards/preview。需求卡片标题计 23 系笔误,落码按行内容全收——commit 留痕)
const EFFECT_KINDS := ["add_ticket_rider", "modify_ticket", "damage_mult", "open_ticket",
"attack_bonus", "yield_mult", "spawn_spell", "grant_material",
"grant_ap", "grant_gp", "bias_wallet", "shield", "spike", "apply_status", "counter_add",
"grant_ap", "grant_gp", "bias_wallet", "shield", "apply_status", "counter_add",
"counter_spend", "reveal_tiles", "remove_mines", "refresh_pick",
"bias_rewards", "preview",
# 六型效果(奇物.md 基础件表 27 件声明清单):
@@ -61,8 +61,8 @@ const FACTS := ["matchup", "is_first_dig_of_battle", "terrain_kind", "target_arm
"ap_exhausted_last", "hp_ratio", "amount", "ap_left", "pct",
"battle_index"] # 关卡域战斗序(1 基,enc 携带;开局姿态组「每关首轮窗」条件面)
## when 算子词汇(封闭——fact 查询面落法:mod_zero=计数器取模判满〔掘进蓄能〕、
## advantageous=克制矩阵正面判定〔淬毒刃纹〕、gte_ref/lt_ref=与另一计数器比较〔深挖钻头配额〕、
## when 算子词汇(封闭——fact 查询面落法:mod_zero=计数器取模判满、
## advantageous=克制矩阵正面判定〔淬毒刃纹〕、gte_ref/lt_ref=与另一计数器比较(计数器配额面)、
## ends_with=后缀匹配〔对某甲族的泛条件〕、mod_eq=取模余数判定〔双管镐每第 2 次——value=模数
## ref=余数;dispatch 预评估口径〔when 于命中前读计数器现值〕下「每第 N 次」系用 ref=N-1 表达〕。
## 均系映射表现状条件装进 fact 的查询面落法,白名单覆盖)
@@ -79,7 +79,7 @@ const WHEN_KEYS := ["fact", "op", "value", "id", "ref", "value_from"]
## axis=bonus_delta 加算区轴向)
const DO_KEYS := ["kind", "value", "value_from", "delta", "delta_from", "mult", "mult_from",
"cap", "cap_from", "cost_mult", "per", "divisor", "dtype", "mode", "channel", "counter_id",
"set", "status_id", "label", "fallback", "target_kinds", "trade_atk", "trade_hit",
"set", "status_id", "label", "fallback", "target_kinds",
"value_event", "value_counter", "axis"]
## 主动件分发通道(不经事件订阅——刷新/显影/移除是玩家主动动作;use_treasure 按 kind 分发)
@@ -154,7 +154,7 @@ func _push_tree_rule(rk: String, params: Dictionary[Variant, Variant]) -> void:
"do": {"kind": "shield", "value": int(params["shield"])}}, {})
"sense_sharp":
# 锋=踩中危险格伤害减半·每关限次——mine_hit 挂点现成(damage_mult 收集→halve_pct,
# 与惊巢反制同路径;伤害在结算战斗段末兑付,探索阶段零改动)
# 与挖掘惩罚减半同路径;伤害在结算战斗段末兑付,探索阶段零改动)
_push_trigger("tree", rk, {"on": "mine_hit",
"when": [{"fact": "counter", "id": "lvl_tree_sense_sharp_used", "op": "lt", "value": int(params["cap_per_level"])}],
"do": {"kind": "damage_mult", "value": int(params["halve_pct"])}}, {})
@@ -449,7 +449,7 @@ static func _vtr_from_refs(rid: String, tr: Dictionary, row: Dictionary, errs: A
## 加法族(数值相加,可交换):add_ticket_rider、attack_bonus、grant_material、grant_ap、
## counter_add、shield——发射点逐条累加收集器。
## 乘法族(收集后一次施加,正负分桶):yield_mult、damage_mult。
## 任意真族(任一命中即生效):spawn_spell、spike。
## 任意真族(任一命中即生效):spawn_spell。
## 按序改写族(收集→单点按声明序施加):modify_ticket 及一切改写窗口效果。
## 按序消耗族(按订阅序消耗共享资源,耗尽即止;可选 fallback):counter_spend。
## 即时执行族(每条命中各自执行一次,执行序=声明序):open_ticket、
+6 -6
View File
@@ -1,5 +1,5 @@
## 奖励授予件(自 battle_orchestrator 收编 grant 族):极性字典路由兑付(grant_routed——含封印溢价
## 与惊巢反制减半)、三种行应用(flat 四分支/percent/gp)、伤害单据提交面(submit——案C 暂存闸承载)、
## 与挖掘惩罚减半)、三种行应用(flat 四分支/percent/gp)、伤害单据提交面(submit——案C 暂存闸承载)、
## 清图奖励与身份授予(完美封印第二份)、战斗内 GP 缓冲 escrow、秘宝使用四步编排(use_treasure——
## 相位闸→校验→受理闸→逐 kind 应用→消耗)、揭示奖励兑付(reveal_redeem——fx_host flush 的兑付回调面)。
## 生命周期=随编排器(编排器 _init 同建同灭,每战新建)。
@@ -127,7 +127,7 @@ func use_treasure(handle: int, target: Vector2i) -> Dictionary:
return {"ok": true, "item_id": v.item_id, "resolved": v.resolved, "record": rec}
# 受理闸(treasure_used 发射+施加:counter_spend 订阅=掘进蓄能积点账本,积点不足拒绝零消耗。
# 受理闸(treasure_used 发射+施加:counter_spend 订阅=付费积点账本,积点不足拒绝零消耗。
# 零 item_id 特判;拒绝文案读装配集积点进度——宿主 attune 读投影)
func _charge_gate(v: Dictionary, target: Vector2i) -> Dictionary:
var tu_hits := fx_host.fx_emit("treasure_used", {"item_id": String(v.item_id), "target": target})
@@ -180,13 +180,13 @@ func _safe_dig_open(v: Dictionary) -> Dictionary:
return {}
# 爆破工兵包兑付(中立移除危险格:未揭示/无封印/是雷过滤归排雷玩法;
# 中立移除兑付(未揭示/无封印/是雷过滤归排雷玩法;
# 邻接数字冻结零重算、清图分母零涉(危险格不入分母);空集拒绝零消耗——
# consume 之前 return(拒绝面注记扩「③内零消耗分支」),拒绝 reason 可呈现)
func _cell_removal_open(v: Dictionary) -> Dictionary:
var blasted: Array[Variant] = fx_host.minesweeper.remove_mines_in(v.resolved)
if blasted.is_empty():
return {"ok": false, "reason": "爆破工兵包:3×3 区域内无可移除危险格(须未揭示且未封印)"}
return {"ok": false, "reason": "中立移除:3×3 区域内无可移除危险格(须未揭示且未封印)"}
return {}
@@ -218,12 +218,12 @@ func grant_routed(snap: Dictionary, polarity: String, att: Dictionary) -> bool:
row = row.duplicate()
row["power"] = int(round(int(row.power) * prem_pct / 100.0))
row["premium_pct"] = prem_pct
# 惊巢反制(减半系数=mine_hit damage_mult 订阅命中〔行 value 50=减 50%,取整 ceil〕;
# 挖掘惩罚减半(减半系数=mine_hit damage_mult 订阅命中〔行 value 50=减 50%,取整 ceil〕;
# duplicate 隔离——减半不回写极性字典)
if halve_pct > 0 and polarity == "negative" and source_class == "mine_hit":
row = row.duplicate()
var half: int = int(ceil(float(int(row.power)) * float(100 - halve_pct) / 100.0))
emit_cb.call({"type": "effect", "source_class": source_class, "origin_cell": cell}, ri, "惊巢反制——危险格挖掘惩罚减半(%d→%d)" % [int(row.power), half], 0, {"effect_id": "nest_counter"})
emit_cb.call({"type": "effect", "source_class": source_class, "origin_cell": cell}, ri, "挖掘惩罚减半(%d→%d)" % [int(row.power), half], 0, {"effect_id": "nest_halve"})
row.power = half
return _dispatch_row(row, att)
+4 -4
View File
@@ -1,4 +1,4 @@
## 掷格件:装配期格面资源摇号——池行装配、区间行 roll、权重合成(偏运硬币/board_skew 两路投影)。
## 掷格件:装配期格面资源摇号——池行装配、区间行 roll、权重合成(订阅加权/board_skew 两路投影)。
## 自 RewardEconomy 迁出掷格面(architecture v1.29 轴戊②);受理账面(grant/身份查询/快照/retire/
## polarity_row)留守 RewardEconomy——衔接面=格→奖励身份映射批束(本件产出、受理件接收)。
## 取号流声明:roll 每批恰开一 reward_roll 流(scope=每次 install 批不变),体序=normalize(行主序
@@ -38,12 +38,12 @@ func _init(bundle: Dictionary) -> void:
func roll(eligible: Array) -> Dictionary[Variant, Variant]:
var list: Array[Dictionary] = _normalize_eligible(eligible)
var stream: RandomNumberGenerator = rng.open_stream("reward_roll") # 恰一 scope 批(格批 + 清图批尾 + 区间行追加取号)
# reward_roll 发射归 BattleFxHost(偏运硬币 bias_rewards 订阅命中合计经注入查询口取回);
# reward_roll 发射归 BattleFxHost(bias_rewards 订阅命中合计经注入查询口取回);
# 零命中/未接线=0=原行集零扰动
var luck_bias := 0
if luck_bias_query.is_valid():
luck_bias = luck_bias_query.call(list.size())
var cell_rows: Array[Variant] = _biased_cell_rows(luck_bias) # cell 池正向行 weight>0(偏运硬币装配时稀有正向 kinds 权重 +N——池结构零触碰)
var cell_rows: Array[Variant] = _biased_cell_rows(luck_bias) # cell 池正向行 weight>0(订阅件装配时稀有正向 kinds 权重 +N——池结构零触碰)
var clear_rows: Array[Variant] = _pool_rows("clear_default")
# ① 身份批:每非封锁格恰一类型身份(雷安同池——同一行集同一权重分布)
var draft := {}
@@ -83,7 +83,7 @@ func _normalize_eligible(eligible: Array) -> Array[Dictionary]:
return list
# 偏运硬币权重投影(底稿 §3:只动池内权重、雷安仍同池同分布;luck_bias=0 逐位返回原行集——off 态零扰动)
# 订阅加权权重投影(底稿 §3:只动池内权重、雷安仍同池同分布;luck_bias=0 逐位返回原行集——off 态零扰动)
# 合成序:先经图级偏斜投影再叠件级加法——合成序锚=图级先、件级后
#(环境事实先定型、玩家构筑叠其上;两投影均为池内权重面,雷安同池与取号次数零变化)
func _biased_cell_rows(bias: int) -> Array:
+11 -11
View File
@@ -124,8 +124,8 @@ func _reveal_safe(evt: Dictionary, snap: Dictionary, ri: int) -> void:
# dig_reveal 施加面(序=计数器〔连击计数——乘区收集后执行=本格兑付用旧值〕→ 揭示入队〔地质学家/
# 跳格采掘 reveal_tiles 订阅面,授予段末统一掷格兑付〕→ 追加产出〔深挖钻头 v2 双份制:grant_material
# 命中=配额内追加第二份〕→ 奥术矢〔魔能共鸣——每战首次开安全格追加魔法矢;任意真族单次生效〕)
# 跳格采掘 reveal_tiles 订阅面,授予段末统一掷格兑付〕→ 追加产出〔双份制:grant_material
# 命中=配额内追加第二份〕→ 奥术矢(每战首次开安全格追加魔法矢;任意真族单次生效))
func _reveal_safe_apply(dr_hits: Array, evt: Dictionary, ri: int) -> void:
for h in dr_hits:
if String(h.get("kind", "")) == "counter_add":
@@ -163,7 +163,7 @@ func _reveal_mine(evt: Dictionary, snap: Dictionary, ri: int) -> void:
reward_grant.grant_routed(reward_grant.feat_yield_adjust(snap, false, 0, 0), "negative", {"source_class": "mine_hit", "cell": evt.cell, "ri": ri, "halve_pct": int(applied["halve_pct"])})
for h in mh_hits:
if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "reflect":
var nct := damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_nest_counter", "惊巢反制·反弹",
var nct := damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_nest_halve", "反弹伤害",
{"channel": "board", "base_value": int(h.get("value", 0)), "target_rule": DamageController.TARGET_ALL_ALIVE,
"ri": ri, "origin_cell": evt.cell})
reward_grant.submit(nct, false)
@@ -229,7 +229,7 @@ func _terrain_batch(ri: int) -> void:
# 封印判定批+战封印军规开单段(封印流:seal_judged 批后统一开单:轮末每面封印命中对最前存活敌
# 结算 board_typed 必中伤害〔复用零新配方;「封印所在巢穴敌」简化为 default_target 最前存活敌——
# 台账披露待追认〕;完美布防=本轮封印批全中(hit>0 且 miss==0)时封印伤 ×mult——全对推理奖励)。
# 台账披露待追认〕;封印全中倍率=本轮 hit>0 且 miss==0 时封印伤 ×mult——全对推理奖励)。
# 伤害单据案C 暂存战斗段末兑付。
func _seal_batch(ri: int) -> void:
for evt in facts.seals:
@@ -237,7 +237,7 @@ func _seal_batch(ri: int) -> void:
_seal_strike_batch(ri)
# seal_settle 发射+施加(战封印 open_ticket + 完美布防 modify_ticket 改写窗口:封印伤基值×完美倍率后
# seal_settle 发射+施加(战封印 open_ticket + 封印全中倍率 modify_ticket 改写窗口:封印伤基值×完美倍率后
# 逐封印开单)
func _seal_strike_batch(ri: int) -> void:
var fs_hits := 0
@@ -262,7 +262,7 @@ func _seal_strike_batch(ri: int) -> void:
# 战封印军规基值决议(attack 通道 open_ticket 首命中取基值;seal_mult modify_ticket 连乘;
# 完美布防=miss==0 且 hit>0 时基值×倍率。空旗集=空字典〔不开单合法态〕)
# 封印全中倍率=miss==0 且 hit>0 时基值×倍率。空旗集=空字典〔不开单合法态〕)
func _seal_strike_value(fs_fx: Array, fs_hits: int, fs_misses: int) -> Dictionary:
var banner_hits: Array[Variant] = []
for h in fs_fx:
@@ -276,11 +276,11 @@ func _seal_strike_value(fs_fx: Array, fs_hits: int, fs_misses: int) -> Dictionar
if String(h.get("kind", "")) == "modify_ticket" and String(h.get("mode", "")) == "seal_mult":
seal_mult *= maxi(1, int(h.get("value", 1)))
if fs_misses == 0 and fs_hits > 0 and seal_mult > 1:
strike_value *= seal_mult # 完美布防:同轮全中封印伤倍增(全对推理奖励)
strike_value *= seal_mult # 封印全中:同轮全中封印伤倍增(全对推理奖励)
return {"value": strike_value}
# round_end 发射+施加(每轮结算收口:熔火之躯反打单开立〔open_ticket 即时执行〕+积层清零〔counter_add
# round_end 发射+施加(每轮结算收口:轮末反打单开立〔open_ticket 即时执行〕+积层清零〔counter_add
# set〕;停手侧 AP 已在 request_stop 记账,finish_round_stop 按转存率转存 RAP)+施加面:grant_gp〔爆破会计/收盘——escrow 战斗域
# 缓冲,随 gp_dropped 上抛〕/grant_ap mode:queue〔封印回响——返 1 AP 下轮,queue_ap_delta 通道与轮始
# 加成同面〕+段界收口(转存率入参、清图奖励、揭示兑付、段界结账)
@@ -348,7 +348,7 @@ func _judge_seal(evt: Dictionary, ri: int) -> void:
# 封印命中(资格以危险格实例为键去重——排雷玩法 诚实重复上报的对侧;seal_hit 发射序:
# 殉爆票据 → 施加面 → 雷区图谱额外兑付 → 基线正向兑付)
# 标中雷反打票据 → 施加面 → 标中雷额外兑付 → 基线正向兑付)
func _judge_seal_hit(evt: Dictionary, ri: int) -> void:
var cell: Vector2i = evt.cell
if facts.seal_consumed.has(cell):
@@ -359,7 +359,7 @@ func _judge_seal_hit(evt: Dictionary, ri: int) -> void:
"terrain_kind": minesweeper.harmful_adjacent(cell)})
_seal_burst_tickets(fh_hits, cell, ri)
_seal_hit_effects(fh_hits, cell, ri)
# 雷区图谱(封印流——标中雷额外获得一份格面资源:封印开始产出对标开格;
# 标中雷追加(封印流——标中雷额外获得一份格面资源:封印开始产出对标开格;
# 复用 grant 单点,额外兑付走同一 grant_routed 正向路由)
for h in fh_hits:
if String(h.get("kind", "")) == "grant_material":
@@ -370,7 +370,7 @@ func _judge_seal_hit(evt: Dictionary, ri: int) -> void:
reward_grant.grant_routed(snap, "positive", {"source_class": "seal_hit", "cell": cell, "ri": ri, "highlight": "seal_hit"})
# 标中殉爆票据(行 triggers open_ticket channel blast value_from magnitude——每标中一面封印全体存活敌
# 标中雷反打票据(行 triggers open_ticket channel blast value_from magnitude——每标中一面封印全体存活敌
# 吃 N 群伤,无类型走 blast 通道;封印爆裂〔专长〕同面消费)
func _seal_burst_tickets(fh_hits: Array, cell: Vector2i, ri: int) -> void:
for h in DamageController.hits_of(fh_hits, "open_ticket"):
+2 -2
View File
@@ -17,7 +17,7 @@ const UNLOCK_COUNTER_KEYS := ["adventure_cleared", "boss_kill_count", "vision_us
const DIMENSIONS := ["info", "risk", "action", "plan", "counter"]
# enum 九 kind 全部条件触发/机制修正类;mine_reveal/ap_grant 语义不变。
# cell_removal = 爆破工兵包中立移除格;mine_hit_halve 系固定
# cell_removal = 中立移除危险格通道;mine_hit_halve 系固定
# 「减半」语义不消费 magnitude(反弹 N 由 reflect_damage 行 magnitude 承载)。
# 主动件 kind 词族(reveal_tiles/preview/grant_ap/remove_mines/
# refresh_pick——效果引擎 ACTIVE_KINDS 同名闭集);被动件不走 effect_kinds 词族
@@ -119,7 +119,7 @@ func _validate_row_head(row: Dictionary, iid: String) -> Array[Variant]:
## 订阅形态域:triggers 事件订阅行判据与行级触发词表校验(被动件形态;与 effect_kinds 互斥校验
## 面=passive 行(单形态——效果行为只走订阅);active_charged 行两形合法并存
## (effect_kinds=主动分发通道 + triggers=受理闸订阅,掘进蓄能先例))
## (effect_kinds=主动分发通道 + triggers=受理闸订阅,积点解锁先例))
func _validate_row_triggers(row: Dictionary, iid: String) -> Array[Variant]:
var errs: Array[Variant] = []
if row.has("triggers"):
+1 -1
View File
@@ -72,7 +72,7 @@ func _initialize() -> void:
if attune_mode == "on":
# 贪心画像:Slots=3(槽位判定源=树同调位节点点亮数,模拟画像=三节点
# 全点亮常量);装配序 =
# 表序 passive 优先至 Slots-1 + 首个 active(slots=3 → [淬毒刃纹, 标中殉爆, 掘进蓄能],
# 表序 passive 优先至 Slots-1 + 首个 active(slots=3 → 表序头三件占满示例,
# provisional 画像归调参);Y = 营地升级 Lv1 表值 1。autoplay 默认不刷——Y 仅注入容量,
# 刷新消费归玩家策略面(钓鱼归玩家)。
var slots: int = 3
+2 -4
View File
@@ -411,14 +411,12 @@ static func _on_step_row(row: Dictionary, _runner, st: Dictionary, collect_rows:
# 封印经济聚合(自结算流水聚合,零新随机源):
# ⚠ 行侧判据考证实录:结算段对存续增益实例每轮复发行 effect 行且复用实例 source_class
#(battle_orchestrator 状态列举行)——source_class=seal_hit 的行 ≠ 标中事件。事件行唯一判据
# = 授予行专带 highlight="seal_hit"(实测 87 行 == 受理封印 87 动作,1:1 精确);
# 雷区图谱追加分grant 无高光,以 display_name 后缀补捕(计入事件与合计——同封印第二份兑付)。
# = 授予行专带 highlight="seal_hit"(实测 87 行 == 受理封印 87 动作,1:1 精确)。
# 满层拒收行(reward_ineffective)零兑付不入。标空事件=归因行(type=seal_miss 每格恰一次);
# 标空惩罚合计只入一次性兑付行(flat 直伤 damage/heal + gp_loss escrow 行)——sapped 授予行
# 与其状态复发行行侧不可区分不入合计(登记注记,判读侧按事件数补算)。
if row.source_class == "seal_hit" and row.type != "reward_ineffective":
if String(row.get("highlight", "")) == "seal_hit" \
or String(row.get("display_name", "")).contains("雷区图谱"):
if String(row.get("highlight", "")) == "seal_hit":
st.seal_hit += 1
st.seal_hit_gain += int(row.value)
if row.type == "seal_miss":
@@ -236,7 +236,7 @@ func _smoke_global_variant() -> void:
Kit.check("生存族下调:heal 4→3 / defense 11→7(v=30 构造例)", wmap.get("heal_minor", -1) == 3 and wmap.get("ac_up", -1) == 7)
Kit.check("进攻族上浮:attack 14→15 / blast 16→18 / pierce 8→9 / magic 8→9(v=30 构造例,等比上浮守恒)", wmap.get("attack_up", -1) == 15 and wmap.get("spark_aoe", -1) == 18 and wmap.get("pierce_strike", -1) == 9 and wmap.get("magic_bolt", -1) == 9)
eco.roller.board_skew = 30
var combo_rows: Array = eco.roller._biased_cell_rows(2) # 件级加算 2=偏运硬币行 value(reward_roll bias_rewards 命中面同值)
var combo_rows: Array = eco.roller._biased_cell_rows(2) # 件级加算 2=订阅行 value(reward_roll bias_rewards 命中面同值)
var cw := _rows_weight_map(combo_rows)
Kit.check("合成序:图级偏斜先(乘法面)件级 luck_bias 后(加法 +2×四稀有类)——合计 77+8", eco.roller._pool_weight(combo_rows) == 77 + 4 * 2)
Kit.check("合成序构造例:skew(heal 3)+2=5 / gp 2+2=4 / pierce 9+2=11 / magic 9+2=11(图级定型后玩家构筑叠加)", cw.get("heal_minor", -1) == 5 and cw.get("gp_coin", -1) == 4 and cw.get("pierce_strike", -1) == 11 and cw.get("magic_bolt", -1) == 11)
@@ -674,7 +674,7 @@ func _smoke_tree_rules() -> void:
ctx_fz2.level_bucket = lvl_shared # 挂同一 level 桶引用(新形态跨战持久:无 export/seed,同字典即持久)
Kit.check("锋:限次跨战持久(level 桶共享引用——替代旧副本搬运链)〔无状态化 V4〕",
(fz2.dispatch(ctx_fz2, "mine_hit", {}, Callable()) as Array).is_empty())
# 锋·惊巢反制效果行换代断言(迭代36 u13/wa12——_grant_routed 减半分支效果行=战斗日志
# 锋·挖掘惩罚减半效果行换代断言(迭代36 u13/wa12——_grant_routed 减半分支效果行=战斗日志
# step_row 真实生产面;display_name 玩家可见,禁字「挖雷」由此行清零钉住)
var orch_nc = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(777003))
orch_nc.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40,
@@ -687,17 +687,17 @@ func _smoke_tree_rules() -> void:
# heal 负向行表值 power=6(极性字典行值——负向 power 走表值不走快照)→ halve 50% ceil=3
orch_nc.reward_grant().grant_routed({"cell_type": "heal", "power": 10}, "negative",
{"source_class": "mine_hit", "cell": Vector2i.ZERO, "ri": 0, "halve_pct": 50})
# 受理判据=惊巢反制效果行落 step_row(halve 分支先于 dispatch 发行——flat 行 _apply_flat_row 分派
# 受理判据=减半效果行落 step_row(halve 分支先于 dispatch 发行——flat 行 _apply_flat_row 分派
# 恒返 false,返回值不承载受理语义;行发行即分支受理)
var nc_hit := ""
for r in nc_rows:
if String((r as Dictionary).get("display_name", "")).contains("惊巢反制"):
if String((r as Dictionary).get("display_name", "")).contains("挖掘惩罚减半"):
nc_hit = String((r as Dictionary).get("display_name", ""))
break
Kit.check("锋:惊巢反制 mine_hit 减半分支受理(wa12 前提——halve_pct=50 效果行落 step_row)〔wa12〕",
Kit.check("锋:挖掘惩罚减半 mine_hit 减半分支受理(wa12 前提——halve_pct=50 效果行落 step_row)〔wa12〕",
nc_hit != "")
Kit.check("惊巢反制效果行==「惊巢反制——危险格挖掘惩罚减半(6→3)」零「挖雷」(u13 换代)〔wa12〕",
nc_hit == "惊巢反制——危险格挖掘惩罚减半(6→3)" and not nc_hit.contains("挖雷"))
Kit.check("减半效果行==「挖掘惩罚减半(6→3)」零「挖雷」(u13 换代)〔wa12〕",
nc_hit == "挖掘惩罚减半(6→3)" and not nc_hit.contains("挖雷"))
# 守侧五场景(契约 12):编排 rig——begin_battle 携 sense_guard,授予段 _grant_seg._judge_seal 直注
var orch = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(777001))
Kit.check("守侧 rig:战斗编排启动(tree_rules=sense_guard 携带)",
@@ -11,12 +11,33 @@ const Kit := preload("res://tests/unit/test_kit.gd")
const SRC_DIR := "res://data/tables"
const BAD_OVERFLOW_DIR := "user://effect_kind_fixture_overflow"
const BAD_SEGMENTS_DIR := "user://effect_kind_fixture_segments"
const BAD_SPIKE_DIR := "user://effect_kind_fixture_spike"
const BAD_TRADEKEY_DIR := "user://effect_kind_fixture_tradekey"
# 注入探针:死通道 do.kind(甲案摘名两族——各一探针行)
const PROBE_OVERFLOW := ' {\n "feat_id": "kind_overflow_probe",\n "display_name": "通道探针·溢出",\n "rarity": "common",\n "triggers": [ { "on": "dig_reveal", "when": [], "do": { "kind": "overflow", "value": 50 } } ],\n "desc": "夹具探针——甲案退役词 overflow"\n},\n'
const PROBE_SEGMENTS := ' {\n "feat_id": "kind_segments_probe",\n "display_name": "通道探针·连珠",\n "rarity": "common",\n "triggers": [ { "on": "dig_reveal", "when": [], "do": { "kind": "attack_segments", "cap": 2, "divisor": 3, "value": 0 } } ],\n "desc": "夹具探针——甲案退役词 attack_segments"\n},\n'
# 注入探针:改写族退役词(改写族清退批 T2)——spike kind 出域与 trade 键出域(各一探针行)
const PROBE_SPIKE_MODE := ' {
"feat_id": "kind_spike_probe",
"display_name": "词表探针·甲",
"rarity": "common",
"triggers": [ { "on": "dig_reveal", "when": [], "do": { "kind": "spike", "value": 2 } } ],
"desc": "夹具探针——改写族退役 kind spike"
},
'
const PROBE_TRADE_KEY := ' {
"feat_id": "kind_tradekey_probe",
"display_name": "键闭探针·乙",
"rarity": "common",
"triggers": [ { "on": "dig_reveal", "when": [], "do": { "kind": "grant_ap", "trade_hit": 5, "value": 50 } } ],
"desc": "夹具探针——改写族退役 do 键 trade_hit"
},
'
func _inject(dir: String, probe: String) -> bool:
var path := dir + "/feats.tres"
var f := FileAccess.open(path, FileAccess.READ)
@@ -65,5 +86,11 @@ func _init() -> void:
if not _assert_rejected(BAD_SEGMENTS_DIR, PROBE_SEGMENTS, "effect_kind_retired_test/②attack_segments 出域"):
quit(2)
return
print("[effect_kind_retired_test] 双探针均正确拒绝(甲案词表退役——死通道误写加载致命)〔架构整改 V3〕")
if not _assert_rejected(BAD_SPIKE_DIR, PROBE_SPIKE_MODE, "effect_kind_retired_test/③词表闭面:kind=spike 出域拒绝"):
quit(2)
return
if not _assert_rejected(BAD_TRADEKEY_DIR, PROBE_TRADE_KEY, "effect_kind_retired_test/④do 键闭面:trade_hit 出域拒绝"):
quit(2)
return
print("[effect_kind_retired_test] 四探针均正确拒绝(甲案词表退役+改写族清退——退役词/键误写加载致命)〔架构整改 V3〕〔改写族清退 T2〕")
quit(1)