From 54ab330beb5488e9a28c5da3282c32f8927db23e Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sun, 11 Oct 2026 23:58:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(feats):=20=E8=BF=AD=E4=BB=A320=20T3=20?= =?UTF-8?q?=E4=B8=93=E9=95=BF=2027+2=20=E4=BB=B6=E6=8D=A2=E4=BB=A3?= =?UTF-8?q?=EF=BC=88=E4=B8=93=E9=95=BF=E5=AE=9E=E8=A3=85=E6=89=B9=20B?= =?UTF-8?q?=E6=AE=B5=E4=B8=80=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feats.tres 整表换代:旧 27 件删除(素材库表二退役面),重设计 29 行重建(基础 27+迁入 淬毒刃纹/封印爆裂——卡文 §10.4 推荐案件数口径);订阅行 on/when/do=效果引擎封闭词表; rarity 四级直映射令牌档(普通 12/优秀 9/稀有 8/传说 0);cap 已拿上限字段(案②解耦); desc 禁「雷/插旗」字面(契约 6——封印/陷阱风味系);dispatch 预评估口径「每第 N 次」按 N-1 落值(mod_eq ref/eq 阈值-1——effect_words_t1_test 钉住)。 feat_table schema 换代:RARITIES 四级;ROW_KEYS 摘 archetype 增 cap;流派扫雷铁律校验 随流派框架退役摘除(奇物.md v1「不设流派标签、不设档位机制」)。 battle_orchestrator:FeatsEngine 档位求值与 rider_tier 注入摘除(V14 旧实现删除覆盖—— 散件单体效果=唯一形态);FACTS 补 pct(处刑台血量阈值沿)。 smoke 特性段整段换代:牌库构成 29 件四级断言(V2)/契约静态钉扎(V18 牌库 kind 全集/ V19 零棋盘改写订阅/V21 禁字初筛/V22 结构锚形态机检半)/临时表新 schema 注入/门票与 relic 兑付断点主语换代/V3 附表 A 逐件三列断言 29 件(命中面 dispatch 直测+counter 落账 施加面同构 helper);同调测试两处主语 venom_edge/flag_burst→nest_counter/dawn_order (V14 换桩——T9 迁出 treasure_items 前置)。冒烟 863/0+run_all 20 套全绿。 --- game/core/battle_orchestrator.gd | 19 +- game/core/damage_controller.gd | 2 +- game/core/effect_engine.gd | 2 +- game/data/schemas/feat_table.gd | 63 +- game/data/tables/feats.tres | 1413 ++++++++++++++++++------------ game/smoke/smoke_all.gd | 482 +++++----- 6 files changed, 1151 insertions(+), 830 deletions(-) diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index b393601c..47f2394f 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -190,8 +190,7 @@ func _fx_fact(fact: String): return hits_n "hp_ratio": var hu: Dictionary = _enemy_system.hero_unit() - var hmax := maxi(1, int(hu.get("max_hp", 1))) - return float(int(hu.get("hp", 0))) / float(hmax) + return int(round(100.0 * float(int(hu.get("hp", 0))) / float(maxi(1, int(hu.get("max_hp", 1)))))) # 百分比 int(0-100——when lte 算子 int 截断比较口径) return null @@ -326,14 +325,10 @@ func begin_battle(enc: Dictionary) -> bool: _damage.fx_reveal_request = Callable(self, "_fx_enqueue_reveal") _damage.fx_ac_request = Callable(self, "_apply_ac_bonus") var feat_ids: Array = enc.get("feats", []) as Array - if not feat_ids.is_empty() and db.feats != null: - var tiers: Dictionary = FeatsEngine.tiers_for(feat_ids, db.feats) - var mt := int(tiers.get("magic", 0)) - _damage.rider_tier_bonus = 1 if mt >= 1 else 0 - _damage.rider_tier_crit = mt >= 2 - else: - _damage.rider_tier_bonus = 0 - _damage.rider_tier_crit = false + # 迭代 20 T3 换代(V14 旧实现摘除)——流派档位质变随重设计退役(奇物.md v1「不设流派标签、 + # 不设档位机制」):FeatsEngine 求值与 rider_tier 注入拆除,散件单体效果=唯一形态 + _damage.rider_tier_bonus = 0 + _damage.rider_tier_crit = false _feat_first_dig_used = false # 树 rule 键消费(迭代 12 T10 → 内容数据驱动扩展批 T3 换代:Fork 激活侧效果=效果引擎合成 # 订阅(rebuild 注入——round_start 首轮 AP+1 / damage_taken 减伤+1),旧直调拆除。其余 rule 键 @@ -624,7 +619,7 @@ func _settle() -> void: var mine_power := int((snap as Dictionary).get("power", 0)) var mh_hits := _fx_emit("mine_hit", {"cell": evt.cell, "round": ri, "damage": mine_power, "dug_under_flag": bool(evt.get("dug_under_flag", false)), - "hp_ratio": float(int(_enemy_system.hero_unit().get("hp", 0))) / float(maxi(1, int(_enemy_system.hero_unit().get("max_hp", 1))))}) + "hp_ratio": int(round(100.0 * float(int(_enemy_system.hero_unit().get("hp", 0))) / float(maxi(1, int(_enemy_system.hero_unit().get("max_hp", 1))))))}) var halve_pct := 0 var mine_reduce := 0 for h in mh_hits: @@ -679,7 +674,7 @@ func _settle() -> void: var dt_amount := int(vrow.get("effect_value", 0)) var dt_hits := _fx_emit("damage_taken", {"kind": "terrain", "amount": dt_amount, "source": String(vid), "cell": evt.cell, "round": ri, "terrain_kind": String(vid), - "hp_ratio": float(int(_enemy_system.hero_unit().get("hp", 0))) / float(maxi(1, int(_enemy_system.hero_unit().get("max_hp", 1))))}) + "hp_ratio": int(round(100.0 * float(int(_enemy_system.hero_unit().get("hp", 0))) / float(maxi(1, int(_enemy_system.hero_unit().get("max_hp", 1))))))}) for h in dt_hits: match String(h.get("kind", "")): "damage_reduce": diff --git a/game/core/damage_controller.gd b/game/core/damage_controller.gd index 760df266..3e6102f4 100644 --- a/game/core/damage_controller.gd +++ b/game/core/damage_controller.gd @@ -113,7 +113,7 @@ func _fx_fact(fact: String): return dig_streak "hp_ratio": var hu: Dictionary = units.hero_unit() - return float(int(hu.get("hp", 0))) / float(maxi(1, int(hu.get("max_hp", 1)))) + return int(round(100.0 * float(int(hu.get("hp", 0))) / float(maxi(1, int(hu.get("max_hp", 1)))))) # 百分比 int(when lte int 截断口径) return null diff --git a/game/core/effect_engine.gd b/game/core/effect_engine.gd index f93b2b61..bdd71b5e 100644 --- a/game/core/effect_engine.gd +++ b/game/core/effect_engine.gd @@ -53,7 +53,7 @@ const EFFECT_KINDS := ["add_ticket_rider", "modify_ticket", "damage_mult", "open const FACTS := ["matchup", "is_first_dig_of_battle", "terrain_kind", "target_armor_class", "flags_count", "charge_layers", "nest_count", "round_index", "counter", "new_resource_type", "is_edge_cell", "is_blank_zero", "dug_under_flag", - "ap_exhausted_last", "hp_ratio", "amount", "ap_left"] + "ap_exhausted_last", "hp_ratio", "amount", "ap_left", "pct"] ## when 算子词汇(封闭——fact 查询面落法随批登记:mod_zero=计数器取模判满〔掘进蓄能〕、 ## advantageous=克制矩阵正面判定〔淬毒刃纹〕、gte_ref/lt_ref=与另一计数器比较〔深挖钻头配额〕、 diff --git a/game/data/schemas/feat_table.gd b/game/data/schemas/feat_table.gd index f0e3baee..1e91f85a 100644 --- a/game/data/schemas/feat_table.gd +++ b/game/data/schemas/feat_table.gd @@ -1,8 +1,9 @@ # VERTICAL SLICE - NOT FOR PRODUCTION -# Validation Question: 七核心流派各 ≥3 件入局内随机渠道后,标签+档位激活引擎能否机械判「成型」、 -# 专长带能否挂进令牌池稀有/罕见档随机投放(P0——迭代 12 双层 BD 实施批 T1) -# Date: 2026-10-06(内容数据驱动扩展批 T3——行换代:hook/effects 挂点形态退役,triggers 事件订阅形态 -# 唯一合法行形——旧 23 挂点枚举消亡:事件名+效果类型取代,需求卡片 R4 裁决) +# Validation Question: 专长 27 件订阅行能否经效果引擎统一解释执行、逐件行为与声明一致(P0—— +# 迭代 20 专长实装批 T3;前史:迭代 12 双层 BD 实施批 T1 建表/迭代 17 内容数据驱动扩展批 T3 行换代) +# Date: 2026-10-11(迭代 20 专长实装批 T3——重设计换代:流派标签/档位激活机制退役〔奇物.md v1 +# 设计原则「不设流派标签、不设档位机制」〕;rarity 四级〔跨层框架〕;cap 已拿上限字段〔池结构案D +# 概率与上限解耦——每件标注两个独立属性:概率位=档+rarity、上限=cap(拿了才计、刷出不计)〕) # 概念指路:本表的专长,与 adventure.tres 的属性值提升、资源,同属一个对象—— # 「奇物」(一次冒险内各渠道给予玩家的增益总称)。定义见 design/gdd/systems/冒险循环/奇物.md。 # 它们是它分类轴上的枚举(专长 / 属性值提升 / 资源),不是三套系统;本表承载「专长」(专长 Feat)的逐项定义。 @@ -11,29 +12,25 @@ # 目标渠道:专长走关底 Boss 击败后专属三选一(不新增节点类型),见奇物文档 2.1 节。) # 数据配置 FeatTable schema(奇物·专长定义表;行 = Dictionary 键 feat_id,Rule 4 豁免 # 定义表——TreasureItemTable 先例)。 -# 行字段:feat_id 主键 / display_name / rarity ∈ {common, rare, rarest}(对齐令牌池三档命名—— -# 投放档映射:common/rare→稀有令牌档、rarest→罕见令牌档;relic 令牌永不入普通档)/ -# archetype ∈ ARCHETYPES 七流派标签 / desc 效果说明(文案表值化——呈现层读行,零硬编码)/ -# triggers 订阅行数组(订阅哪个事件 on / 什么条件 when / 什么效果 do——效果引擎 EffectEngine -# 统一解释执行;四词表封闭校验=EffectEngine.validate_trigger_row 单一事实源)。 -# 档位激活(整合定稿 设计工具 1——以撒套装/小骨铭文):同标签计数 ≥2→档一(流派核心规则)/≥4→档二 -# (完全体);散件单体保底=每件效果独立生效,档位只做质变(反例 B-2)——引擎纯函数 FeatsEngine。 -# 🔴 层级纪律(需求卡片红线 2):专长只走局内随机渠道(三选一/商店/格面)——本表行永不入同调位 -# (treasure_items 表)/局外树(T8 节点表);扫雷触发点铁律:每流派 ≥1 行订阅扫雷侧事件 -# (EffectEngine.MINE_EVENTS——加载校验,违反=红线①触碰面)。 +# 行字段:feat_id 主键 / display_name / rarity ∈ {common, excellent, rare, legendary}(四级稀有度, +# 与令牌池档位词表同名词族——令牌档 = rarity 直映射)/ cap 已拿上限(正整数,缺省 1——「同一件 +# 最多拿 N 次」;概率位与上限解耦见奇物.md 池结构节)/ desc 效果说明(文案表值化——呈现层读行, +# 零硬编码)/ triggers 订阅行数组(订阅哪个事件 on / 什么条件 when / 什么效果 do——效果引擎 +# EffectEngine 统一解释执行;四词表封闭校验=EffectEngine.validate_trigger_row 单一事实源)。 +# 🔴 层级纪律(需求卡片红线 2):专长只走局内随机渠道(Boss 三选一/凭证开启)——本表行永不入同调位 +# (treasure_items 表)/局外树(T8 节点表)。 +# 退役注记(V14 旧实现摘除):ARCHETYPES 流派标签/档位激活(FeatsEngine 档位质变)随重设计退役—— +# 事件族(开格/插旗/踩雷/节奏/战斗系)仅系设计侧分组工具不上数据行不上卡面(命名铁律)。 class_name FeatTable extends Resource @export var rows: Array = [] # Array[Dictionary] 键 feat_id -## 流派标签词表(七核心——喂雷/淘金不在本批,R5 甲/缓议) -const ARCHETYPES := ["magic", "pierce", "normal", "dig", "flag", "charge", "devour"] +## 稀有度词表(四级——与令牌池 TIERS 同名词族;令牌档 = rarity 直映射) +const RARITIES := ["common", "excellent", "rare", "legendary"] -## 稀有度词表(对齐令牌池三档;投放档映射 relic→rare / rarest→rarest) -const RARITIES := ["common", "rare", "rarest"] - -## 行键白名单(🔴 零裸 flat 同族护栏——键出域=加载致命;triggers/desc=T3 换代新形态) -const ROW_KEYS := ["feat_id", "display_name", "rarity", "archetype", "triggers", "desc", "note"] +## 行键白名单(🔴 零裸 flat 同族护栏——键出域=加载致命) +const ROW_KEYS := ["feat_id", "display_name", "rarity", "cap", "triggers", "desc", "note"] ## trigger 行键(订阅行三字段:on/when?/do) const TRIGGER_KEYS := ["on", "when", "do"] @@ -42,10 +39,8 @@ const TRIGGER_KEYS := ["on", "when", "do"] func validate() -> Array: var errs: Array = [] if rows.is_empty(): - return errs # 空表合法态(T1 基建先行——逐流派填行) + return errs # 空表合法态(T1 基建先行——逐件填行) var seen := {} - # 流派扫雷覆盖聚合(🔴 红线①铁律——每流派 ≥1 行订阅扫雷侧事件) - var arch_mine := {} # archetype → bool for row in rows: var rid: String = row.get("feat_id", "") if rid == "": @@ -59,30 +54,18 @@ func validate() -> Array: errs.append({"category": "domain", "message": "%s display_name 空" % rid}) if not RARITIES.has(String(row.get("rarity", ""))): errs.append({"category": "enum", "message": "%s rarity 出域 %s" % [rid, str(RARITIES)]}) - var arch := String(row.get("archetype", "")) - if not ARCHETYPES.has(arch): - errs.append({"category": "enum", "message": "%s archetype 出域 %s" % [rid, str(ARCHETYPES)]}) + if row.has("cap") and int(row.get("cap", 1)) < 1: + errs.append({"category": "domain", "message": "%s cap 出域 [1,∞): %d" % [rid, int(row.get("cap", 1))]}) if not row.has("triggers"): errs.append({"category": "domain", "message": "%s 缺 triggers(订阅形态唯一合法行形)" % rid}) - elif EffectEngine.validate_trigger_row(rid, row, row.get("triggers", []) as Array, errs): - arch_mine[arch] = true + else: + EffectEngine.validate_trigger_row(rid, row, row.get("triggers", []) as Array, errs) for k in row.keys(): if not ROW_KEYS.has(String(k)): errs.append({"category": "zero_flat", "message": "%s 行键出白名单: %s" % [rid, String(k)]}) - # 🔴 红线① 扫雷触发点铁律:**有行的流派**各 ≥1 行订阅扫雷侧事件(MINE_EVENTS 同义校验) - for arch in archs_with_rows(): - if not bool(arch_mine.get(arch, false)): - errs.append({"category": "load_bearing", "message": "流派 %s 无扫雷结算触发点行(🔴 红线①铁律——每流派 ≥1 件订阅扫雷侧事件)" % String(arch)}) return errs -func archs_with_rows() -> Array: - var out := {} - for row in rows: - out[String(row.get("archetype", ""))] = true - return out.keys() - - func row_by_id(feat_id: String) -> Dictionary: for row in rows: if String(row.get("feat_id", "")) == feat_id: diff --git a/game/data/tables/feats.tres b/game/data/tables/feats.tres index 310b2bc8..ed74f7ef 100644 --- a/game/data/tables/feats.tres +++ b/game/data/tables/feats.tres @@ -4,569 +4,850 @@ [resource] script = ExtResource("1") -rows = [{ -"feat_id": "arcane_ember", -"display_name": "奥术余烬", -"rarity": "common", -"archetype": "magic", -"desc": "普攻附加必中伤害", -"triggers": [{ -"on": "attack_commit", -"do": { -"kind": "add_ticket_rider", -"value": 1 -} -}] -}, { -"feat_id": "mana_weaver", -"display_name": "魔网织者", -"rarity": "rare", -"archetype": "magic", -"desc": "克制面强化", -"triggers": [{ -"on": "clamp_check", -"when": [{ -"fact": "matchup", -"op": "eq", -"value": "magic_vs_magic" -}], -"do": { -"kind": "modify_ticket", -"delta": 1 -} -}] -}, { -"feat_id": "azure_oath", -"display_name": "湛蓝誓约", -"rarity": "rare", -"archetype": "magic", -"desc": "克制面强化", -"triggers": [{ -"on": "clamp_check", -"when": [{ -"fact": "matchup", -"op": "eq", -"value": "magic_vs_heavy" -}], -"do": { -"kind": "modify_ticket", -"delta": 1 -} -}] -}, { -"feat_id": "mana_resonance", -"display_name": "魔能共鸣", -"rarity": "rarest", -"archetype": "magic", -"desc": "首格追加奥术矢", -"triggers": [{ -"on": "dig_reveal", -"when": [{ -"fact": "is_first_dig_of_battle", -"op": "eq", -"value": true -}], -"do": { -"kind": "spawn_spell", -"value": 2 -} -}] -}, { -"feat_id": "mine_atlas", -"display_name": "雷区图谱", -"rarity": "common", -"archetype": "flag", -"desc": "标中雷额外得资源", -"triggers": [{ -"on": "flag_hit", -"do": { -"kind": "grant_material", -"value": 1, -"label": "(雷区图谱)" -} -}] -}, { -"feat_id": "nest_hunter", -"display_name": "猎巢人", -"rarity": "rare", -"archetype": "flag", -"desc": "控巢攻击光环", -"triggers": [{ -"on": "attack_commit", -"do": { -"kind": "add_ticket_rider", -"value": 1, -"per": "nest_count" -} -}] -}, { -"feat_id": "perfect_defense", -"display_name": "完美布防", -"rarity": "rare", -"archetype": "flag", -"desc": "全中标旗伤加倍", -"triggers": [{ -"on": "flag_settle", -"when": [{ -"fact": "flags_count", -"op": "gte", -"value": 1 -}], -"do": { -"kind": "modify_ticket", -"value": 2, -"mode": "flag_mult" -} -}] -}, { -"feat_id": "war_banner", -"display_name": "战旗军规", -"rarity": "rarest", -"archetype": "flag", -"desc": "标中旗必中打敌", -"triggers": [{ -"on": "flag_settle", -"when": [{ -"fact": "flags_count", -"op": "gte", -"value": 1 -}], -"do": { -"kind": "open_ticket", -"value": 2, -"channel": "attack" -} -}] -}, { -"feat_id": "pierce_shot", -"display_name": "贯穿射击", -"rarity": "rarest", -"archetype": "pierce", -"desc": "击杀半额弹射", -"triggers": [{ -"on": "kill", -"do": { -"kind": "overflow", -"value": 50 -} -}] -}, { -"feat_id": "chain_arrows", -"display_name": "连珠矢", -"rarity": "rare", -"archetype": "pierce", -"desc": "开格积穿刺矢", -"triggers": [{ -"on": "dig_reveal", -"do": { -"kind": "counter_add", -"counter_id": "chain_digs", -"value": 1, -"reset_at": "round_start" -} -}, { -"on": "attack_commit", -"do": { -"kind": "attack_segments", -"value": 2, -"cap": 2, -"per": "dig_streak", -"divisor": 3 -} -}] -}, { -"feat_id": "weak_instinct", -"display_name": "猎弱本能", -"rarity": "rare", -"archetype": "pierce", -"desc": "打轻甲更易命中", -"triggers": [{ -"on": "clamp_check", -"when": [{ -"fact": "matchup", -"op": "ends_with", -"value": "vs_light" -}], -"do": { -"kind": "modify_ticket", -"value": 1, -"mode": "discount_shift" -} -}] -}, { -"feat_id": "hunter_rapier", -"display_name": "猎手刺剑", -"rarity": "common", -"archetype": "pierce", -"desc": "克制面强化", -"triggers": [{ -"on": "clamp_check", -"when": [{ -"fact": "matchup", -"op": "eq", -"value": "pierce_vs_light" -}], -"do": { -"kind": "modify_ticket", -"delta": 1 -} -}] -}, { -"feat_id": "miner_stubborn", -"display_name": "矿工的执拗", -"rarity": "common", -"archetype": "normal", -"desc": "开格越多打得越疼", -"triggers": [{ -"on": "dig_reveal", -"do": { -"kind": "counter_add", -"counter_id": "stubborn_digs", -"value": 1, -"reset_at": "round_start" -} -}, { -"on": "mine_hit", -"do": { -"kind": "counter_add", -"counter_id": "stubborn_digs", -"set": 0 -} -}, { -"on": "attack_commit", -"do": { -"kind": "add_ticket_rider", -"value": 1, -"cap": 5, -"per": "dig_streak" -} -}] -}, { -"feat_id": "miner_hammer", -"display_name": "矿工重锤", -"rarity": "common", -"archetype": "normal", -"desc": "加伤换命中", -"triggers": [{ -"on": "attack_commit", -"do": { -"kind": "modify_ticket", -"trade_atk": 25, -"trade_hit": 2 -} -}] -}, { -"feat_id": "focus_mind", -"display_name": "专注", -"rarity": "rare", -"archetype": "normal", -"desc": "攻击恒75%折扣", -"triggers": [{ -"on": "armor_roll", -"do": { -"kind": "modify_ticket", -"value": 75, -"mode": "armor_rewrite" -} -}] -}, { -"feat_id": "armor_crack", -"display_name": "碎甲震击", -"rarity": "rare", -"archetype": "normal", -"desc": "打重甲附加破防", -"triggers": [{ -"on": "attack_commit", -"when": [{ -"fact": "matchup", -"op": "ends_with", -"value": "vs_heavy" -}], -"do": { -"kind": "apply_status", -"status_id": "off_guard" -} -}] -}, { -"feat_id": "greedy_lantern", -"display_name": "贪婪矿灯", -"rarity": "common", -"archetype": "dig", -"desc": "开格产出放大", -"triggers": [{ -"on": "dig_reveal", -"do": { -"kind": "yield_mult", -"mult": 25, -"cost_mult": 25 -} -}] -}, { -"feat_id": "vein_intuition", -"display_name": "矿脉直觉", -"rarity": "rare", -"archetype": "dig", -"desc": "开格产出放大", -"triggers": [{ -"on": "dig_reveal", -"do": { -"kind": "counter_add", -"counter_id": "dig_combo", -"value": 1 -} -}, { -"on": "mine_hit", -"do": { -"kind": "counter_add", -"counter_id": "dig_combo", -"set": 0 -} -}, { -"on": "dig_reveal", -"do": { -"kind": "yield_mult", -"mult": 10, -"cap": 5 -} -}] -}, { -"feat_id": "gambler_pact", -"display_name": "赌徒的血签", -"rarity": "rarest", -"archetype": "dig", -"desc": "开格产出放大", -"triggers": [{ -"on": "dig_reveal", -"do": { -"kind": "yield_mult", -"mult": 50, -"cost_mult": 100 -} -}] -}, { -"feat_id": "deep_drill", -"display_name": "深挖钻头", -"rarity": "rare", -"archetype": "dig", -"desc": "开格2AP·产出双份", -"triggers": [{ -"on": "dig_paid", -"do": { -"kind": "counter_spend", -"counter_id": "deep_paid_round", -"value": 1, -"reset_at": "round_start", -"fallback": 1 -} -}, { -"on": "dig_reveal", -"when": [{ -"fact": "counter", -"id": "deep_paid_round", -"op": "gte", -"value": 1 -}, { -"fact": "counter", -"id": "deep_given_round", -"op": "lt_ref", -"ref": "deep_paid_round" -}], -"do": { -"kind": "grant_material", -"label": "(深挖)" -} -}, { -"on": "dig_reveal", -"when": [{ -"fact": "counter", -"id": "deep_paid_round", -"op": "gte", -"value": 1 -}, { -"fact": "counter", -"id": "deep_given_round", -"op": "lt_ref", -"ref": "deep_paid_round" -}], -"do": { -"kind": "counter_add", -"counter_id": "deep_given_round", -"value": 1 -} -}] -}, { -"feat_id": "armory_prep", -"display_name": "整备军备", -"rarity": "rarest", -"archetype": "charge", -"desc": "剩AP转下轮攻击", -"triggers": [{ -"on": "round_end", -"do": { -"kind": "grant_ap", -"mode": "transfer" -} -}, { -"on": "attack_commit", -"do": { -"kind": "attack_bonus", -"value": 1, -"per": "charge_layers" -} -}] -}, { -"feat_id": "iron_wall", -"display_name": "铁壁整备", -"rarity": "rare", -"archetype": "charge", -"desc": "蓄力转减伤", -"triggers": [{ -"on": "damage_taken", -"do": { -"kind": "shield", -"value": 1, -"per": "charge_layers" -} -}] -}, { -"feat_id": "patient_burst", -"display_name": "厚积薄发", -"rarity": "rare", -"archetype": "charge", -"desc": "蓄满下轮爆发", -"triggers": [{ -"on": "attack_commit", -"when": [{ -"fact": "charge_layers", -"op": "gte", -"value": 3 -}], -"do": { -"kind": "modify_ticket", -"value": 2, -"mode": "spike" -} -}] -}, { -"feat_id": "ley_harvest", -"display_name": "地脉采撷", -"rarity": "common", -"archetype": "devour", -"desc": "吃地形伤返资源", -"triggers": [{ -"on": "damage_taken", -"when": [{ -"fact": "terrain_kind", -"op": "has" -}], -"do": { -"kind": "grant_material", -"label": "(地脉补偿)" -} -}] -}, { -"feat_id": "thorn_weaver", -"display_name": "荆棘编织者", -"rarity": "rare", -"archetype": "devour", -"desc": "挖伤害格双产出", -"triggers": [{ -"on": "damage_taken", -"when": [{ -"fact": "terrain_kind", -"op": "has" -}], -"do": { -"kind": "grant_material", -"label": "(荆棘编织)" -} -}] -}, { -"feat_id": "purge_sapper", -"display_name": "净化工兵", -"rarity": "rare", -"archetype": "devour", -"desc": "标旗中和伤害格", -"triggers": [{ -"on": "flag_hit", -"when": [{ -"fact": "terrain_kind", -"op": "has" -}], -"do": { -"kind": "rewrite_board" -} -}] -}, { -"feat_id": "magma_body", -"display_name": "熔火之躯", -"rarity": "rarest", -"archetype": "devour", -"desc": "吃地形伤反打全体", -"triggers": [{ -"on": "damage_taken", -"when": [{ -"fact": "terrain_kind", -"op": "has" -}], -"do": { -"kind": "counter_add", -"counter_id": "magma_layers", -"value": 1 -} -}, { -"on": "round_end", -"when": [{ -"fact": "counter", -"id": "magma_layers", -"op": "gte", -"value": 1 -}], -"do": { -"kind": "open_ticket", -"value": 1, -"per": "magma_layers", -"channel": "blast" -} -}, { -"on": "round_end", -"when": [{ -"fact": "counter", -"id": "magma_layers", -"op": "gte", -"value": 1 -}], -"do": { -"kind": "counter_add", -"counter_id": "magma_layers", -"set": 0 -} -}] -}] - -; 内容数据驱动扩展批(2026-10-06 T3):专长表换代——行 = triggers 事件订阅形态 -; (feat_id/display_name/rarity/archetype/desc/triggers——订阅哪个事件 on / 什么条件 when / 什么效果 do)。 -; 旧 hook/effects 挂点形态随本批退役(挂点枚举消亡:事件名+效果类型取代——需求卡片 R4 裁决)。 -; 23 挂点逐行移植映射=需求卡片 §五(等价性对拍底单);desc=文案表值化(呈现层读行,零硬编码)。 -; 效果参数值直接写 do 字典(数值单一事实源=本表;效果引擎 DO_KEYS 白名单封闭)。 -; 🔴 护栏全部保留:事件/效果/fact/算子四词表封闭(EffectEngine 常量)、行键白名单、悬空引用 -; 致命、零裸 flat 禁令、流派扫雷触发铁律(每流派 ≥1 行订阅扫雷侧事件——MINE_EVENTS 同义校验)。 -; 映射表对拍注记(T3 落地语义修正,行为等价优先——commit 留痕): -; - terrain_dig_double(荆棘编织者):映射表标 yield_mult(×2),现行为=第二份同格产出兑付 -; (grant_material),×2 乘区对 percent 行不等价——落 grant_material。 -; - flag_all_hit(完美布防):映射表标 yield_mult(旗伤乘区),现行为=旗伤单值倍增(战旗单据 -; 数值改写)——落 modify_ticket(mode:flag_mult)。 -; - 连珠矢/矿工的执拗 streak 取数:映射表 on:dig_reveal counter_add 形态与现行为「轮内踩雷全轮 -; 清零」不等价(逐格清零后重积≠全轮判定)——计数器行保留(扫雷铁律承载),等价执行读 -; attack_commit 载荷 dig_streak(战斗段全轮推导,与迁移前逐位一致)。 -; - 厚积薄发:映射表 on:charge_full(满层沿)→spike,现行为=满层持续状态(层数无消耗点,沿触发 -; 一次≠持续增强)——落 on:attack_commit + when(charge_layers ≥3) → modify_ticket(mode:spike), -; charge_full 事件仍埋点(R6 框架先行)。 -; - 完美布防:「全中」条件装不进 fact 封闭 9 项(需 hits>0 且 misses==0 双读数)——when 落 -; flags_count ≥1(可命中保守判定),全中闸=flag_settle 发射点施加面承载(载荷 flags_hit_all -; 仅供观测)——禁私开 fact(停机点纪律),查询面落法=代拍板白名单。 -; - 猎巢人/矿工的执拗:映射表标 modify_ticket/attack_bonus,现行为=必中附加通道(伤害控制器 -; hero_rider 后置于乘区与下限)——落 add_ticket_rider(per 取数载荷),乘区外恒定兑付逐位一致。 -; - 整备军备:现行为双效果(轮末 AP 转蓄力层 + 每层下轮首段攻 +1)——两行订阅: -; round_end grant_ap(mode:transfer) + attack_commit attack_bonus(per:charge_layers)。 -; 数值全 provisional 归 M-4 数值批;desc 文案=旧 summaries 表迁移(措辞零变化)。 +; 迭代 20 专长实装批 T3——专长重设计 27+2 件换代(奇物.md 基础件表 27 件+迁入登记 2 件=29 行; +; 件数口径=卡文 §10.4 推荐案:29 行=基础 27+迁入 2,V2/V3 断言按落表实数)。旧 27 件随本批整体 +; 退役(素材库表二);订阅行 on/when/do 词表=效果引擎封闭集;rarity 四级直映射令牌档;cap=已拿 +; 上限(案②解耦——拿了才计刷出不计);数值全 provisional 归调参批;desc 禁「雷/插旗」字面 +; (契约 6 命名纪律:插旗=封印、危险格=陷阱/巢穴风味层)。dispatch 预评估口径注记:「每第 N 次/积 +; 满触发」系 when 于命中前读计数器现值——行 when 按 N-1 落值(双管镐 mod_eq ref 1/地质学家 eq 3/ +; 跳格 eq 2/封印匠 mod_eq ref 2/保险丝 eq 1——effect_words_t1_test mod_eq 断言钉住该口径)。 +rows = [ + { + "feat_id": "twin_pick", + "display_name": "双管镐", + "rarity": "common", + "desc": "每第 2 次付费开格返 1 AP", + "triggers": [ + { + "on": "dig_paid", + "do": { + "kind": "counter_add", + "counter_id": "twin_digs", + "value": 1, + "domain": "adventure" + } + }, + { + "on": "dig_paid", + "when": [ + { + "fact": "counter", + "id": "twin_digs", + "op": "mod_eq", + "value": 2, + "ref": 1 + } + ], + "do": { + "kind": "grant_ap", + "value": 1 + } + } + ] + }, + { + "feat_id": "geologist", + "display_name": "地质学家", + "rarity": "common", + "desc": "每首开一种新类型资源格集 1 点,满 4 种→揭示 1 格", + "triggers": [ + { + "on": "dig_reveal", + "when": [ + { + "fact": "new_resource_type", + "op": "eq", + "value": true + } + ], + "do": { + "kind": "counter_add", + "counter_id": "geo_types", + "value": 1 + } + }, + { + "on": "dig_reveal", + "when": [ + { + "fact": "counter", + "id": "geo_types", + "op": "eq", + "value": 3 + } + ], + "do": { + "kind": "reveal_tiles", + "value": 1 + } + }, + { + "on": "dig_reveal", + "when": [ + { + "fact": "counter", + "id": "geo_types", + "op": "eq", + "value": 3 + } + ], + "do": { + "kind": "counter_add", + "counter_id": "geo_types", + "set": 0 + } + } + ] + }, + { + "feat_id": "hard_hat", + "display_name": "安全帽", + "rarity": "excellent", + "desc": "开安全格攒 1 层;触陷阱耗层,每层减 1 伤害(一次清零)", + "triggers": [ + { + "on": "dig_reveal", + "do": { + "kind": "counter_add", + "counter_id": "helm_layers", + "value": 1 + } + }, + { + "on": "mine_hit", + "do": { + "kind": "damage_reduce", + "value_counter": "helm_layers" + } + }, + { + "on": "mine_hit", + "when": [ + { + "fact": "counter", + "id": "helm_layers", + "op": "gte", + "value": 1 + } + ], + "do": { + "kind": "counter_add", + "counter_id": "helm_layers", + "set": 0 + } + } + ] + }, + { + "feat_id": "skip_mining", + "display_name": "跳格采掘", + "rarity": "excellent", + "desc": "挖开空白格攒 1 点,3 点→揭示 1 格", + "triggers": [ + { + "on": "dig_reveal", + "when": [ + { + "fact": "is_blank_zero", + "op": "eq", + "value": true + } + ], + "do": { + "kind": "counter_add", + "counter_id": "skip_pts", + "value": 1 + } + }, + { + "on": "dig_reveal", + "when": [ + { + "fact": "counter", + "id": "skip_pts", + "op": "eq", + "value": 2 + } + ], + "do": { + "kind": "reveal_tiles", + "value": 1 + } + }, + { + "on": "dig_reveal", + "when": [ + { + "fact": "counter", + "id": "skip_pts", + "op": "eq", + "value": 2 + } + ], + "do": { + "kind": "counter_add", + "counter_id": "skip_pts", + "set": 0 + } + } + ] + }, + { + "feat_id": "blast_accounting", + "display_name": "爆破会计", + "rarity": "common", + "desc": "轮末:本轮开格花费 AP≥5 → +1 GP", + "triggers": [ + { + "on": "dig_paid", + "do": { + "kind": "counter_add", + "counter_id": "round_blast_spent", + "value": 1, + "value_event": "ap_cost", + "reset_at": "round_start" + } + }, + { + "on": "round_end", + "when": [ + { + "fact": "counter", + "id": "round_blast_spent", + "op": "gte", + "value": 5 + } + ], + "do": { + "kind": "grant_gp", + "value": 1 + } + } + ] + }, + { + "feat_id": "frontier_miner", + "display_name": "边陲矿工", + "rarity": "common", + "desc": "挖开棋盘边缘格:产出 +1", + "triggers": [ + { + "on": "dig_reveal", + "when": [ + { + "fact": "is_edge_cell", + "op": "eq", + "value": true + } + ], + "do": { + "kind": "grant_material", + "value": 1 + } + } + ] + }, + { + "feat_id": "seal_echo", + "display_name": "封印回响", + "rarity": "excellent", + "desc": "封印命中计数;轮末计数≥2 → 返 1 AP(下轮)", + "triggers": [ + { + "on": "flag_hit", + "do": { + "kind": "counter_add", + "counter_id": "echo_flags", + "value": 1 + } + }, + { + "on": "round_end", + "when": [ + { + "fact": "counter", + "id": "echo_flags", + "op": "gte", + "value": 2 + } + ], + "do": { + "kind": "grant_ap", + "value": 1, + "mode": "queue" + } + }, + { + "on": "round_end", + "when": [ + { + "fact": "counter", + "id": "echo_flags", + "op": "gte", + "value": 2 + } + ], + "do": { + "kind": "counter_add", + "counter_id": "echo_flags", + "set": 0 + } + } + ] + }, + { + "feat_id": "warning_beacon", + "display_name": "示警烟火", + "rarity": "common", + "desc": "封印命中:揭示周围 3×3 的资源类型(不揭安危)", + "triggers": [ + { + "on": "flag_hit", + "do": { + "kind": "preview_area", + "value": 1 + } + } + ] + }, + { + "feat_id": "seal_smith", + "display_name": "封印匠", + "rarity": "rare", + "desc": "封印命中每第 3 次 → 该危险格中立移除", + "triggers": [ + { + "on": "flag_hit", + "do": { + "kind": "counter_add", + "counter_id": "smith_flags", + "value": 1 + } + }, + { + "on": "flag_hit", + "when": [ + { + "fact": "counter", + "id": "smith_flags", + "op": "mod_eq", + "value": 3, + "ref": 2 + } + ], + "do": { + "kind": "remove_mines", + "value": 1 + } + } + ] + }, + { + "feat_id": "seal_stake", + "display_name": "封印桩", + "rarity": "common", + "desc": "封印命中:本轮 AC +1", + "triggers": [ + { + "on": "flag_hit", + "do": { + "kind": "ac_bonus", + "value": 1, + "mode": "round" + } + } + ] + }, + { + "feat_id": "pain_memory", + "display_name": "疼痛记忆", + "rarity": "rare", + "desc": "触陷阱攒 1 层;下次攻击附加层值伤害,用后清零", + "triggers": [ + { + "on": "mine_hit", + "do": { + "kind": "counter_add", + "counter_id": "pain_layers", + "value": 1, + "domain": "adventure" + } + }, + { + "on": "attack_commit", + "when": [ + { + "fact": "counter", + "id": "pain_layers", + "op": "gte", + "value": 1 + } + ], + "do": { + "kind": "attack_bonus", + "value_counter": "pain_layers" + } + }, + { + "on": "attack_commit", + "when": [ + { + "fact": "counter", + "id": "pain_layers", + "op": "gte", + "value": 1 + } + ], + "do": { + "kind": "counter_add", + "counter_id": "pain_layers", + "set": 0 + } + } + ] + }, + { + "feat_id": "fuse_wire", + "display_name": "保险丝", + "rarity": "rare", + "desc": "单次受伤 ≥5 攒 1 点,2 点→护盾 1 层", + "triggers": [ + { + "on": "damage_taken", + "when": [ + { + "fact": "amount", + "op": "gte", + "value": 5 + } + ], + "do": { + "kind": "counter_add", + "counter_id": "fuse_pts", + "value": 1 + } + }, + { + "on": "damage_taken", + "when": [ + { + "fact": "counter", + "id": "fuse_pts", + "op": "eq", + "value": 1 + } + ], + "do": { + "kind": "shield", + "value": 1 + } + }, + { + "on": "damage_taken", + "when": [ + { + "fact": "counter", + "id": "fuse_pts", + "op": "eq", + "value": 1 + } + ], + "do": { + "kind": "counter_add", + "counter_id": "fuse_pts", + "set": 0 + } + } + ] + }, + { + "feat_id": "blood_insight", + "display_name": "见血开窍", + "rarity": "common", + "desc": "触陷阱:揭示 1 格", + "triggers": [ + { + "on": "mine_hit", + "do": { + "kind": "reveal_tiles", + "value": 1 + } + } + ] + }, + { + "feat_id": "trap_instinct", + "display_name": "陷阱本能", + "rarity": "excellent", + "desc": "触陷阱:本轮攻击 +2(走攻击面板乘区)", + "triggers": [ + { + "on": "mine_hit", + "do": { + "kind": "counter_add", + "counter_id": "instinct_atk", + "value": 2, + "reset_at": "round_start" + } + }, + { + "on": "attack_commit", + "do": { + "kind": "attack_bonus", + "value_counter": "instinct_atk" + } + } + ] + }, + { + "feat_id": "chain_traps", + "display_name": "连环陷阱", + "rarity": "excellent", + "desc": "同轮第 2 次及以后的触陷阱:每次额外 +1 层(疼痛记忆同账)", + "triggers": [ + { + "on": "mine_hit", + "do": { + "kind": "counter_add", + "counter_id": "round_mines", + "value": 1, + "reset_at": "round_start" + } + }, + { + "on": "mine_hit", + "when": [ + { + "fact": "counter", + "id": "round_mines", + "op": "gte", + "value": 1 + } + ], + "do": { + "kind": "counter_add", + "counter_id": "pain_layers", + "value": 1, + "domain": "adventure" + } + } + ] + }, + { + "feat_id": "trap_burst", + "display_name": "陷阱爆发", + "rarity": "rare", + "desc": "触陷阱:全体存活敌受陷阱伤×1.5 直伤", + "triggers": [ + { + "on": "mine_hit", + "do": { + "kind": "blast_scaled", + "mult": 1.5 + } + } + ] + }, + { + "feat_id": "blood_honey", + "display_name": "血蜜", + "rarity": "common", + "desc": "触陷阱:回复 2 生命(陷阱伤仍净亏血)", + "triggers": [ + { + "on": "mine_hit", + "do": { + "kind": "heal", + "value": 2 + } + } + ] + }, + { + "feat_id": "stone_skin", + "display_name": "石肤", + "rarity": "excellent", + "desc": "受伤:AC +1(本场累计,上限 3)", + "triggers": [ + { + "on": "damage_taken", + "do": { + "kind": "ac_bonus", + "value": 1, + "mode": "battle", + "cap": 3 + } + } + ] + }, + { + "feat_id": "last_stand", + "display_name": "背水", + "rarity": "rare", + "desc": "生命≤50%:受伤额外 −2", + "triggers": [ + { + "on": "damage_taken", + "when": [ + { + "fact": "hp_ratio", + "op": "lte", + "value": 50 + } + ], + "do": { + "kind": "damage_reduce", + "value": 2 + } + } + ] + }, + { + "feat_id": "breaker_strike", + "display_name": "破封一击", + "rarity": "excellent", + "desc": "挖开带封印的危险格:+1 层(疼痛记忆同账)", + "triggers": [ + { + "on": "mine_hit", + "when": [ + { + "fact": "dug_under_flag", + "op": "eq", + "value": true + } + ], + "do": { + "kind": "counter_add", + "counter_id": "pain_layers", + "value": 1, + "domain": "adventure" + } + } + ] + }, + { + "feat_id": "map_exchange", + "display_name": "弃图兑换", + "rarity": "excellent", + "desc": "停手:剩余 AP≥3 → 折为攻击伤害", + "triggers": [ + { + "on": "dig_stop", + "when": [ + { + "fact": "ap_left", + "op": "gte", + "value": 3 + } + ], + "do": { + "kind": "counter_add", + "counter_id": "stop_swap", + "value": 0, + "value_event": "ap_left" + } + }, + { + "on": "attack_commit", + "when": [ + { + "fact": "counter", + "id": "stop_swap", + "op": "gte", + "value": 1 + } + ], + "do": { + "kind": "attack_bonus", + "value_counter": "stop_swap" + } + }, + { + "on": "attack_commit", + "when": [ + { + "fact": "counter", + "id": "stop_swap", + "op": "gte", + "value": 1 + } + ], + "do": { + "kind": "counter_add", + "counter_id": "stop_swap", + "set": 0 + } + } + ] + }, + { + "feat_id": "last_drop", + "display_name": "一滴不剩", + "rarity": "common", + "desc": "上轮 AP 耗尽 → +1 AP", + "triggers": [ + { + "on": "round_start", + "when": [ + { + "fact": "ap_exhausted_last", + "op": "eq", + "value": true + } + ], + "do": { + "kind": "grant_ap", + "value": 1, + "mode": "queue" + } + } + ] + }, + { + "feat_id": "market_close", + "display_name": "收盘", + "rarity": "common", + "desc": "轮末:本轮开格 ≥8 → +1 GP", + "triggers": [ + { + "on": "dig_reveal", + "do": { + "kind": "counter_add", + "counter_id": "round_digs", + "value": 1, + "reset_at": "round_start" + } + }, + { + "on": "round_end", + "when": [ + { + "fact": "counter", + "id": "round_digs", + "op": "gte", + "value": 8 + } + ], + "do": { + "kind": "grant_gp", + "value": 1 + } + } + ] + }, + { + "feat_id": "execution_scholar", + "display_name": "斩首学者", + "rarity": "common", + "desc": "击杀:揭示 1 格", + "triggers": [ + { + "on": "kill", + "do": { + "kind": "reveal_tiles", + "value": 1 + } + } + ] + }, + { + "feat_id": "executioner", + "display_name": "处刑台", + "rarity": "excellent", + "desc": "敌血量≤30%:主角对其伤害 +2", + "triggers": [ + { + "on": "enemy_hp_threshold", + "when": [ + { + "fact": "pct", + "op": "lte", + "value": 30 + } + ], + "do": { + "kind": "target_attack_bonus", + "value": 2 + } + } + ] + }, + { + "feat_id": "field_dressing", + "display_name": "战地包扎", + "rarity": "common", + "desc": "战斗胜利:回复 3 生命", + "triggers": [ + { + "on": "battle_won", + "do": { + "kind": "heal", + "value": 3 + } + } + ] + }, + { + "feat_id": "blood_tax", + "display_name": "血税", + "rarity": "rare", + "desc": "本局触陷阱 −2 最大生命;封印命中 +1 攻击", + "triggers": [ + { + "on": "mine_hit", + "do": { + "kind": "bonus_delta", + "axis": "max_hp", + "delta": -2 + } + }, + { + "on": "flag_hit", + "do": { + "kind": "bonus_delta", + "axis": "atk", + "delta": 1 + } + } + ] + }, + { + "feat_id": "venom_edge", + "display_name": "淬毒刃纹", + "rarity": "rare", + "desc": "克制命中追加 +2 伤害", + "triggers": [ + { + "on": "clamp_check", + "when": [ + { + "fact": "matchup", + "op": "advantageous" + } + ], + "do": { + "kind": "modify_ticket", + "mode": "clamp_bonus", + "delta": 2 + } + } + ] + }, + { + "feat_id": "flag_burst", + "display_name": "封印爆裂", + "rarity": "rare", + "desc": "封印命中:全体存活敌 3 点群伤", + "triggers": [ + { + "on": "flag_hit", + "do": { + "kind": "open_ticket", + "channel": "blast", + "value": 3 + } + } + ] + } +] diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index ba347c4d..bb2173ff 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -633,58 +633,76 @@ func _smoke_adventure() -> void: "放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)") -# ---- 双层 BD 规则改写型特性基建冒烟(迭代 12 T1——feat_table/档位激活引擎/规则改写型特性带池接线)---- +# ---- 专长重设计实装冒烟(迭代 20 T3——feats 四级换代 · 案D 池接线 · V3 订阅面逐件三列)---- func _smoke_feats() -> void: - print("--- 规则改写型特性基建(feat_table · 档位激活引擎 · 规则改写型特性带接线)---") - # ① 现行表牌库构成:七流派 27 件全量入池——98 数值令牌+27 relic 令牌(批次一铺量完成) - check(_db.feats != null and (_db.feats.rows as Array).size() == 27, "feat_table 已加载(七流派 27 件全量——批次一 T2-T6 铺量完成)") + print("--- 专长重设计实装(feat_table 四级 · 专长带池接线 · 订阅面逐件)---") + # ① 现行表牌库构成:重设计 29 件全量入池——98 数值令牌+29 relic 令牌(四级 rarity 直映射) + check(_db.feats != null and (_db.feats.rows as Array).size() == 29, "feat_table 已加载(重设计 29 件=基础 27+迁入 2——卡文 §10.4 推荐案口径)") + check(_db.feats.validate().is_empty(), "feat_table.validate 零违例(四级 rarity/cap 域/trigger 词表/行键白名单全过)") var a_r0 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(71)) - check(a_r0.start_adventure(71, {"first_script": true}), "规则改写型特性带场景:冒险启动(seed 71)") + check(a_r0.start_adventure(71, {"first_script": true}), "专长带场景:冒险启动(seed 71)") var feat_tokens := 0 - var rare_tier_tokens := 0 + var tier_counts := {} for t in a_r0._curio_deck: if String((t as Dictionary).get("kind", "")) == "relic": feat_tokens += 1 - if String((t as Dictionary).get("tier", "")) == "rare": - rare_tier_tokens += 1 - check(feat_tokens == 27 and rare_tier_tokens == 20 and a_r0._curio_deck.size() == 125, - "规则改写型特性带入池:27 件(transitional 直映射——旧 rare 名直通 14+旧 rarest 回退 6=稀有档 20、旧 common 7=普通档)——牌库 98+27=125〔专长批 V14 换代〕") - check(_db.feats.validate().is_empty(), "feat_table.validate 零违例(有行流派扫雷端铁律——magic 魔能共鸣 dig_reveal 订阅在案)") - # ② 档位激活引擎(纯函数直测——构造表 4 件 magic + 1 件 flag;内容数据驱动扩展批 T3 断言面随迁: - # 行=triggers 事件订阅形态,挂点投影断言换代=效果引擎订阅装载序〔feats 表序〕) + var tk := String((t as Dictionary).get("tier", "")) + tier_counts[tk] = int(tier_counts.get(tk, 0)) + 1 + check(feat_tokens == 29 and int(tier_counts.get("common", 0)) == 12 and int(tier_counts.get("excellent", 0)) == 9 and int(tier_counts.get("rare", 0)) == 8, + "专长带入池:29 件四级直映射(普通 12/优秀 9/稀有 8/传说 0)——牌库 98+29=127〔专长批 V2〕") + # ② 旧 27 件退役钉面(V2——arcane_ember 系旧件表首行;全仓 grep 零残留归批末静态核查) + check(_db.feats.row_by_id("arcane_ember").is_empty() and _db.feats.row_by_id("magma_body").is_empty(), + "旧 27 件已退役:arcane_ember/magma_body 表内零残留〔专长批 V2〕") + # ③ 契约面静态钉扎(V18/V19/V21/V22——人工核对 V20/V21 裁决面以外的可机检半) + var kinds_ok := true + for t in a_r0._curio_deck: + var kk := String((t as Dictionary).get("kind", "")) + if kk != "relic" and not _db.adventure.CURIO_KINDS.has(kk): + kinds_ok = false + check(kinds_ok, "层级纪律:牌库 kind 全集=CURIO_KINDS+relic(秘宝 item_id 零入池)〔专长批 V18〕") + var rewrite_rows := 0 + for row_v in (_db.feats.rows as Array): + for tv in ((row_v as Dictionary).get("triggers", []) as Array): + if String(((tv as Dictionary).get("do", {}) as Dictionary).get("kind", "")) == "rewrite_board": + rewrite_rows += 1 + check(rewrite_rows == 0, "棋盘事实不可变:新 29 行零棋盘改写类订阅(rewrite_board 已除名消费面)〔专长批 V19〕") + var bad_words := 0 + for row_v in (_db.feats.rows as Array): + var r_d: Dictionary = row_v as Dictionary + for txt_v in [String(r_d.get("display_name", "")), String(r_d.get("desc", ""))]: + if String(txt_v).contains("雷") or String(txt_v).contains("插旗"): + bad_words += 1 + check(bad_words == 0, "对外命名纪律:29 行 display_name/desc 零「雷/插旗」字面(封印/陷阱风味系)〔专长批 V21 初筛〕") + check(float((_db.feats.row_by_id("trap_burst").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("mult", 0)) > 1.0 + and int((_db.feats.row_by_id("blood_honey").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("value", 99)) < 5 + and int((_db.feats.row_by_id("stone_skin").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("cap", 0)) == 3, + "数值纪律:陷阱爆发倍率>1 · 血蜜回复<典型陷阱伤 · 石肤上限 3 在册(结构锚形态内 provisional)〔专长批 V22 机检半〕") + # ④ 临时表注入(新 schema 形态——零 archetype/cap 直用):5 行 relic 入牌库/兑付/踢池/断点往返 var rt := FeatTable.new() rt.rows = [ - {"feat_id": "m1", "display_name": "魔一", "rarity": "common", "archetype": "magic", "desc": "测试 rider", - "triggers": [{"on": "attack_commit", "do": {"kind": "add_ticket_rider", "value": 1}}]}, - {"feat_id": "m2", "display_name": "魔二", "rarity": "common", "archetype": "magic", "desc": "测试奥术矢", - "triggers": [{"on": "dig_reveal", "do": {"kind": "spawn_spell", "value": 1}}]}, - {"feat_id": "m3", "display_name": "魔三", "rarity": "rare", "archetype": "magic", "desc": "测试克制", - "triggers": [{"on": "clamp_check", "when": [{"fact": "matchup", "op": "eq", "value": "magic_vs_magic"}], "do": {"kind": "modify_ticket", "delta": 1}}]}, - {"feat_id": "m4", "display_name": "魔四", "rarity": "rare", "archetype": "magic", "desc": "测试加值", - "triggers": [{"on": "attack_commit", "do": {"kind": "attack_bonus", "value": 2}}]}, - {"feat_id": "f1", "display_name": "旗一", "rarity": "common", "archetype": "flag", "desc": "测试图谱", - "triggers": [{"on": "flag_hit", "do": {"kind": "grant_material", "value": 1}}]}, + {"feat_id": "x1", "display_name": "试一", "rarity": "common", "desc": "测试揭示", "cap": 1, + "triggers": [{"on": "mine_hit", "do": {"kind": "reveal_tiles", "value": 1}}]}, + {"feat_id": "x2", "display_name": "试二", "rarity": "excellent", "desc": "测试回复", "cap": 1, + "triggers": [{"on": "mine_hit", "do": {"kind": "heal", "value": 2}}]}, + {"feat_id": "x3", "display_name": "试三", "rarity": "rare", "desc": "测试群伤", "cap": 1, + "triggers": [{"on": "flag_hit", "do": {"kind": "open_ticket", "channel": "blast", "value": 3}}]}, + {"feat_id": "x4", "display_name": "试四", "rarity": "rare", "desc": "测试减伤", "cap": 2, + "triggers": [{"on": "damage_taken", "when": [{"fact": "hp_ratio", "op": "lte", "value": 50}], "do": {"kind": "damage_reduce", "value": 2}}]}, + {"feat_id": "x5", "display_name": "试五", "rarity": "legendary", "desc": "测试AC", "cap": 1, + "triggers": [{"on": "flag_hit", "do": {"kind": "ac_bonus", "value": 1, "mode": "round"}}]}, ] - check(rt.validate().is_empty(), "构造 relic 表 validate 零违例(magic 4 件含 dig_reveal 扫雷端订阅——红线①铁律过)") - var tiers := FeatsEngine.tiers_for(["m1", "m2", "m3", "m4", "f1"], rt) - check(int(tiers.get("magic", -1)) == 2 and int(tiers.get("flag", -1)) == 0, - "档位激活:magic 4 件→档二 · flag 1 件→散件档 0(集 2 档一/4 档二——以撒套装/小骨铭文先例)") - check(FeatsEngine.is_formed(tiers, "magic") and not FeatsEngine.is_formed(tiers, "flag"), - "成型判读:magic 成型(tier≥1)· flag 未成型(散件单体保底——档位只质变不启动)") - var t2 := FeatsEngine.tiers_for(["m1", "m2"], rt) - check(int(t2.get("magic", -1)) == 1, "档位激活:2 件→档一(同集合同档位确定性——MC 逐位可复放承载)") - # ③ 规则改写型特性带接线(临时表注入——relic 令牌入牌库/兑付入持有集/踢池/断点往返;测试尾还原现行表) + check(rt.validate().is_empty(), "新 schema 构造表 validate 零违例(四级 rarity/cap 字段/词表全过——流派标签已退役零 archetype)") var live_rows: FeatTable = _db.feats _db.feats = rt var a_r = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(72)) - check(a_r.start_adventure(72, {"first_script": true}), "规则改写型特性带场景:冒险启动(seed 72——注入 5 行临时表)") + check(a_r.start_adventure(72, {"first_script": true}), "专长带场景:冒险启动(seed 72——注入 5 行临时表)") var deck_relic := 0 for t in a_r._curio_deck: if String((t as Dictionary).get("kind", "")) == "relic": deck_relic += 1 check(deck_relic == 5 and a_r._curio_deck.size() == 103, - "规则改写型特性带入池:5 行 relic 每件 1 张(transitional——旧 common/rare→优秀档)——牌库 98+5=103") + "专长带入池:5 行 relic 每件 1 张(四级 rarity 直映射)——牌库 98+5=103") # T4 渠道白名单断言(迭代 15 卡②——渠道轴定位:通关渠道只出专长/节点渠道零专长) var feat_seen: Dictionary = {} var node_relic_seen := false @@ -696,15 +714,15 @@ func _smoke_feats() -> void: if String((t as Dictionary).get("kind", "")) == "relic": node_relic_seen = true check(feat_seen.size() >= 2, "通关渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)") - check(not node_relic_seen, "节点渠道零专长(60 次重抽零 relic——专长在非 Boss 渠道只以门票形态出现,T4 渠道白名单)") - # T5 门票行为断言(A5 拿到即开 / A6 保底计数——契约④四口径;独立 runner 防污染 a_r 牌库——后续 relic 兑付断言按 5 枚锚定) + check(not node_relic_seen, "节点渠道零专长(60 次重抽零 relic——专长在非 Boss 渠道只以门票形态出现)") + # T5 门票行为断言(A5 拿到即开 / A6 保底计数——契约④四口径;独立 runner 防污染 a_r 牌库) var a_t = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(91)) a_t.start_adventure(91, {"first_script": true}) var stock_before := {} for kk in ["attack", "gp", "heal"]: stock_before[kk] = a_t.pool_stock(kk) - a_t._node_curio_pending = {"candidates": [{"kind": "feat_ticket", "tier": "rare"}, {"kind": "attack", "tier": "common"}], "aux_detail": []} - check(a_t.apply_node_curio("feat_ticket", "rare"), "A5 门票兑付受理(节点渠道——拿到即开)") + a_t._node_curio_pending = {"candidates": [{"kind": "feat_ticket", "tier": "excellent"}, {"kind": "attack", "tier": "common"}], "aux_detail": []} + check(a_t.apply_node_curio("feat_ticket", "excellent"), "A5 门票兑付受理(节点渠道——拿到即开)") var tkt_cands: Array = (a_t.node_curio_pending_state().get("candidates", []) as Array) var tkt_all_relic := tkt_cands.size() > 0 for t in tkt_cands: @@ -734,12 +752,8 @@ func _smoke_feats() -> void: offered = true break check(offered, "A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)") - a_r._curio_pending = {"candidates": [{"kind": "relic", "tier": "rare", "feat_id": "m3"}, {"kind": "gp", "tier": "common"}], "payload": {}} - var deck_m3 := 0 - for t in a_r._curio_deck: - if String((t as Dictionary).get("feat_id", "")) == "m3": - deck_m3 += 1 - check(a_r.pick_curio("relic", "rare", "m3") and (a_r.feats_held() as Array).has("m3") and a_r.pool_stock("relic") == deck_relic - 1, + a_r._curio_pending = {"candidates": [{"kind": "relic", "tier": "rare", "feat_id": "x3"}, {"kind": "gp", "tier": "common"}], "payload": {}} + check(a_r.pick_curio("relic", "rare", "x3") and (a_r.feats_held() as Array).has("x3") and a_r.pool_stock("relic") == deck_relic - 1, "relic 兑付:入持有集 + 牌库摘牌(选中踢池——池库存 %d→%d)" % [deck_relic, deck_relic - 1]) # 断点往返(持有集+余库 feat_id 随行) var sm_r = load("res://core/save_manager.gd").new("user://_smoke_feats_bp.json") @@ -747,176 +761,224 @@ func _smoke_feats() -> void: a_r.set_save_manager(sm_r) a_r._on_level_finished({"result": "victory", "hp": int(a_r.ledger.get("hero_hp", 26)), "node_index": 1, "node_id": 1, "node_type": "shop"}) var bp_r: Dictionary = sm_r.breakpoint_snapshot() - check((bp_r.get("relics", []) as Array).has("m3"), "断点附随件:relics 持有集入快照(迭代 12 T1 扩列)") - var bp_deck_m3 := 0 + check((bp_r.get("relics", []) as Array).has("x3"), "断点附随件:relics 持有集入快照(迭代 12 T1 扩列)") + check(int((bp_r.get("adventure_counters", {}) as Dictionary).size()) >= 0, "断点附随件:adventure 计数器账本键在案(T6/T7 扩列)") + var bp_deck_x3 := 0 for t in (bp_r.get("bonus_deck", []) as Array): - if String((t as Dictionary).get("feat_id", "")) == "m3": - bp_deck_m3 += 1 - check(bp_deck_m3 == 0, "断点余库:已兑付 relic 令牌逐枚还原(m3 零残留——续玩不重复获得)") + if String((t as Dictionary).get("feat_id", "")) == "x3": + bp_deck_x3 += 1 + check(bp_deck_x3 == 0, "断点余库:已兑付 relic 令牌逐枚还原(x3 零残留——续玩不重复获得)") sm_r.clear_breakpoint() _db.feats = live_rows # 还原现行表(共享 _db 防污染——后续冒烟段零感知) - # ④ 魔法流订阅面(内容数据驱动扩展批 T3 断言面随迁——_feat_flags_for 挂点求值退役: - # 求值断言=效果引擎 rebuild+dispatch 命中面;落地行为=发射点施加面(本段下方各事件直测), - # 全链分岔读数归 T6 MC 对拍) + # ⑤ V3 订阅面逐件(附表 A 三列——命中面=EffectEngine dispatch 直测;账本面=counter 直读; + # 落地行为=发射点施加面〔T1 施加段承载——本段按声明逐件钉命中与边界/反例〕) var orch = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(73)) - var magic4: Array = ["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance"] - orch._fx.rebuild(magic4, [], []) - var ac4: Array = orch._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "magic_vs_magic", "dig_streak": 0, "nest_count": 0}) - check(orch._hits_of(ac4, "add_ticket_rider").size() == 1 - and int((orch._hits_of(ac4, "add_ticket_rider")[0] as Dictionary).get("value", -1)) == 1, - "魔法流订阅:奥术余烬 attack_commit→add_ticket_rider value 1(必中附加通道)") - check(int((orch._hits_of(orch._fx_emit("clamp_check", {"matchup": "magic_vs_magic"}), "modify_ticket")[0] as Dictionary).get("delta", -1)) == 1 - and int((orch._hits_of(orch._fx_emit("clamp_check", {"matchup": "magic_vs_heavy"}), "modify_ticket")[0] as Dictionary).get("delta", -1)) == 1, - "魔法流订阅:魔网织者 magic-vs-magic +1 · 湛蓝誓约 magic-vs-heavy +1(clamp_check modify 窗口)") - var spell4: Array = orch._fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": true, "terrain_kind": []}) - check(orch._hits_of(spell4, "spawn_spell").size() == 1 - and int((orch._hits_of(spell4, "spawn_spell")[0] as Dictionary).get("value", -1)) == 2, - "魔法流订阅:魔能共鸣每战首次开格→spawn_spell value 2(奥术矢)") - check(int(FeatsEngine.tiers_for(magic4, _db.feats).get("magic", -1)) == 2, - "魔法流档位:magic 4 件→档二(rider 可暴击——begin_battle 注伤害控制器两运行态)") - # ⑤ 标旗流订阅面(magic 4+flag 4——混合持有:flag 档一成型) - orch._fx.rebuild(magic4 + ["mine_atlas", "nest_hunter", "perfect_defense", "war_banner"], [], []) - check(orch._hits_of(orch._fx_emit("flag_hit", {"cell": Vector2i(1, 1), "round": 1, "terrain_kind": []}), "grant_material").size() == 1, - "标旗流订阅:雷区图谱 flag_hit→grant_material(标中雷额外资源)") - var nh5: Array = orch._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 3}) - var nh_hits: Array = orch._hits_of(nh5, "add_ticket_rider") - check(nh_hits.size() == 2 and String((nh_hits[1] as Dictionary).get("per", "")) == "nest_count", - "标旗流订阅:猎巢人 attack_commit→add_ticket_rider per nest_count(控巢光环 rider 通道)") - var fs5: Array = orch._fx_emit("flag_settle", {"flags_hit_all": true, "flags_count": 2, "round": 1}) - check(orch._hits_of(fs5, "open_ticket").size() == 1 - and int((orch._hits_of(fs5, "open_ticket")[0] as Dictionary).get("value", -1)) == 2 - and orch._hits_of(fs5, "modify_ticket").size() == 1, - "标旗流订阅:战旗军规 flag_settle→open_ticket value 2(attack 通道自开单)· 完美布防 flag_mult ×2") - check(orch._fx_emit("flag_settle", {"flags_hit_all": false, "flags_count": 0, "round": 1}).is_empty(), - "标旗流订阅:零标中旗→战旗/完美布防零命中(when flags_count 封闭判定)") - # ⑥ 穿刺/普攻流订阅面(16 件持有——四流派档位成型) - var ids16: Array = magic4 + ["mine_atlas", "nest_hunter", "perfect_defense", "war_banner", - "pierce_shot", "chain_arrows", "weak_instinct", "hunter_rapier", - "miner_stubborn", "miner_hammer", "focus_mind", "armor_crack"] - orch._fx.rebuild(ids16, [], []) - check(orch._hits_of(orch._fx_emit("kill", {"target": "敌", "overflow": 10}), "overflow").size() == 1, - "穿刺流订阅:贯穿射击 kill→overflow(击杀弹射旗)") - var dr6: Array = orch._fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": false, "terrain_kind": []}) - check(orch._hits_of(dr6, "counter_add").size() == 2, - "穿刺/普攻流订阅:连珠矢+执拗 dig_reveal→counter_add(扫雷侧事件铁律承载——chain_digs/stubborn_digs)") - var ac6: Array = orch._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "pierce_vs_light", "dig_streak": 9, "nest_count": 0}) - var seg6: Array = orch._hits_of(ac6, "attack_segments") - check(seg6.size() == 1 and int((seg6[0] as Dictionary).get("value", -1)) == 2 - and int((seg6[0] as Dictionary).get("cap", -1)) == 2 and int((seg6[0] as Dictionary).get("divisor", -1)) == 3, - "穿刺流订阅:连珠矢 attack_commit→attack_segments(段值 2 上限 2 每 3 格 +1 段)") - var clamp6: Array = orch._fx_emit("clamp_check", {"matchup": "pierce_vs_light"}) - check(orch._hits_of(clamp6, "modify_ticket").size() == 2, - "穿刺流订阅:猎弱本能 ends_with vs_light→discount_shift · 猎手刺剑 pierce_vs_light→delta +1(同一事件双订阅按声明序)") - check(orch._hits_of(orch._fx_emit("armor_roll", {"target_ac": 5, "roll": 3}), "modify_ticket").size() == 1, - "普攻流订阅:专注 armor_roll→modify armor_rewrite 75(免掷恒折扣)") - var ac6n: Array = orch._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "normal_vs_heavy", "dig_streak": 0, "nest_count": 0}) - check(orch._hits_of(ac6n, "apply_status").size() == 1 - and String((orch._hits_of(ac6n, "apply_status")[0] as Dictionary).get("status_id", "")) == "off_guard", - "普攻流订阅:碎甲震击 ends_with vs_heavy→apply_status off_guard(复用零新状态行)") - var ham6: Array = orch._hits_of(ac6, "modify_ticket") - var has_trade := false - for h in ham6: - if int((h as Dictionary).get("trade_atk", 0)) == 25 and int((h as Dictionary).get("trade_hit", 0)) == 2: - has_trade = true - check(has_trade, "普攻流订阅:矿工重锤 attack_commit→modify trade_atk 25/trade_hit 2(命中交换)") - check(int(FeatsEngine.tiers_for(ids16, _db.feats).get("pierce", -1)) == 2 - and int(FeatsEngine.tiers_for(ids16, _db.feats).get("normal", -1)) == 2, - "档位质变:magic/pierce/normal/flag 四流派档二(16 件混合——同标签 ≥4 判档二确定性)") - # ⑦ 开格/蓄力流订阅面(追加七件) - var ids23: Array = ids16.duplicate() - ids23.append_array(["greedy_lantern", "vein_intuition", "gambler_pact", "deep_drill", - "armory_prep", "iron_wall", "patient_burst"]) - orch._fx.rebuild(ids23, [], []) - var pos_m := 0 - var neg_m := 0 - var combo_cap := -1 - for h in orch._fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": false, "terrain_kind": []}): - if String(h.get("kind", "")) != "yield_mult": - continue - pos_m += int(h.get("mult", 0)) - neg_m += int(h.get("cost_mult", 0)) - if int(h.get("cap", 0)) > 0: - combo_cap = int(h.cap) - check(pos_m == 85 and neg_m == 125 and combo_cap == 5, - "开格流订阅:正向乘区 25+10+50=85% · 负向加重 25+100=125% · 直觉连击上限 5(dig_reveal yield_mult 乘法族三件同持收集)") - var dd7: Array = orch._fx_emit("dig_paid", {"ap_cost": 1, "ap_surplus": 1}) - check(orch._hits_of(dd7, "counter_spend").size() == 1 - and bool((orch._hits_of(dd7, "counter_spend")[0] as Dictionary).has("fallback")), - "开格流订阅:深挖钻头 dig_paid→counter_spend 对价(fallback 降级声明——AP 不足只付基础 1)") - var re7: Array = orch._fx_emit("round_end", {"round": 1}) - check(orch._hits_of(re7, "grant_ap").size() == 1 - and String((orch._hits_of(re7, "grant_ap")[0] as Dictionary).get("mode", "")) == "transfer", - "蓄力流订阅:整备军备 round_end→grant_ap transfer(剩 AP 转蓄力层——finish_round_stop 定夺)") - var ab7: Array = orch._hits_of(orch._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}), "attack_bonus") - check(ab7.size() == 1 and String((ab7[0] as Dictionary).get("per", "")) == "charge_layers", - "蓄力流订阅:整备军备 attack_commit→attack_bonus per charge_layers(每层下轮首段 +1)") - orch.action_economy._charge_layers = 2 - var sp7: Array = orch._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "normal_vs_light", "dig_streak": 0, "nest_count": 0}) - var spike7_off: Array = orch._hits_of(sp7, "modify_ticket") - var spike_off_n := 0 - for h in spike7_off: - if String((h as Dictionary).get("mode", "")) == "spike": - spike_off_n += 1 - check(spike_off_n == 0, - "蓄力流订阅:厚积薄发未满层(charge_layers 2<3)→零命中(when 封闭判定——满层持续状态口径)") - orch.action_economy._charge_layers = 3 - var sp7b: Array = orch._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "normal_vs_light", "dig_streak": 0, "nest_count": 0}) - var spike7: Array = [] - for h in orch._hits_of(sp7b, "modify_ticket"): - if String((h as Dictionary).get("mode", "")) == "spike": - spike7.append(h) - check(spike7.size() == 1 and int((spike7[0] as Dictionary).get("value", -1)) == 2, - "蓄力流订阅:厚积薄发满层→modify spike ×2(discount 恒全额+基值倍乘)") - orch.action_economy._charge_layers = 0 - check(orch._hits_of(orch._fx_emit("damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1}), "shield").size() == 1, - "蓄力流订阅:铁壁整备 damage_taken→shield per charge_layers(蓄力转减伤)") - # ⑦c 特性持有呈现段(2026-10-04 用户反馈补齐——C 区「特性」节:聚合行+成型档位;空态隐藏) - var panel = load("res://presentation/views/hero_info_panel_view.gd").new() - panel.set_feat_holdings([]) - check(not panel._feat_section.visible, "特性段空态隐藏(视图空态纪律同特性段)") - panel.set_feat_holdings(["魔法 ×2 · 档一", "开格 ×1 · 散件"]) - check(panel._feat_section.visible and panel._feat_list.get_child_count() == 2, - "特性段持有呈现(聚合行+成型档位常驻可读——「局内奖励选了什么看不到」缺口承载)") - check(String(load("res://presentation/battle_narrator.gd").feat_card_text({"feat_id": "arcane_ember", "display_name": "奥术余烬", "archetype": "magic", "desc": "普攻附加必中伤害"})).contains("普攻附加必中伤害"), - "特性卡文案表值化:desc 行字段直读(T3 文案入表——零硬编码映射)") - # ⑦b 深挖钻头付费开掘回归(2026-10-04 试玩缺陷修复口径,T3 对价面换代=dig_paid 订阅施加): - # AP=1 降级照常开格(fallback 只付基础 1 不积计数);AP=2 付全价积付费计数(深层产出配额取数面)。 - var ae_dd = load("res://core/action_economy.gd").new(_db) - var fx_dd = EffectEngine.new(_db) - fx_dd.rebuild(["deep_drill"], [], []) - ae_dd.set_fx(fx_dd) - ae_dd._phase = 1 if ae_dd.Phase.ACTIVE == 1 else ae_dd.Phase.ACTIVE # white-box 相位(冒烟专用) - ae_dd._ap = 1 - check(ae_dd.request_dig(0, false) and fx_dd.counter("deep_paid_round") == 0, - "钻头降级:AP=1 照常开格(fallback 只付基础 1 不积付费——持有钻头永不锁死挖格,散件保底 B-2)") - ae_dd._ap = 2 - ae_dd._phase = ae_dd.Phase.ACTIVE # 首挖 AP 归 0 已翻轮(ap_exhausted 仲裁)——白盒复位续测 - check(ae_dd.request_dig(0, false) and fx_dd.counter("deep_paid_round") == 1, - "钻头付费开掘:AP=2 付全价(基础 1+对价 1——deep_paid_round 计数器=授予段追加产出配额取数面)") - # ⑧ 吞噬流订阅面(27 件全量——七流派档位成型) - var ids27: Array = ids23.duplicate() - ids27.append_array(["ley_harvest", "thorn_weaver", "purge_sapper", "magma_body"]) - orch._fx.rebuild(ids27, [], []) - var dt8: Array = orch._fx_emit("damage_taken", {"kind": "terrain", "amount": 3, "source": "thorn_field", "round": 1, "terrain_kind": "thorn_field"}) - check(orch._hits_of(dt8, "grant_material").size() == 2 and orch._hits_of(dt8, "counter_add").size() == 1, - "吞噬流订阅:地脉采撷+荆棘编织 damage_taken(地形伤)→grant_material ×2 · 熔火之躯→counter_add 积层") - check(orch._hits_of(orch._fx_emit("flag_hit", {"cell": Vector2i(1, 1), "round": 1, "terrain_kind": ["thorn_field"]}), "rewrite_board").size() == 1 - and orch._hits_of(orch._fx_emit("flag_hit", {"cell": Vector2i(1, 1), "round": 1, "terrain_kind": []}), "rewrite_board").is_empty(), - "吞噬流订阅:净化工兵 flag_hit 邻伤害型地形→rewrite_board · 无伤害型邻格零命中(🔴 仅伤害型——terrain_kind has 在场判定)") - orch._fx.counter_add("magma_layers", 2, false) - var re8: Array = orch._fx_emit("round_end", {"round": 1}) - check(orch._hits_of(re8, "open_ticket").size() == 1 - and int((orch._hits_of(re8, "open_ticket")[0] as Dictionary).get("value", -1)) == 1 - and String((orch._hits_of(re8, "open_ticket")[0] as Dictionary).get("per", "")) == "magma_layers", - "吞噬流订阅:熔火之躯 round_end→open_ticket per magma_layers(受地形伤积层轮末反打)") - check(orch._hits_of(re8, "counter_add").size() == 1 - and (orch._hits_of(re8, "counter_add")[0] as Dictionary).has("set"), - "吞噬流订阅:熔火之躯反打后积层清零(counter_add set——即时执行族按声明序)") + var fx = orch._fx + # counter 落账 helper(dispatch 只返回命中不落账——施加面同构:counter_add 逐条落账本 helper 承载) + var apply_fx := func(hits: Array) -> void: + for h in hits: + if String(h.get("kind", "")) == "counter_add": + var sv_c = h.get("set", null) + fx.counter_add(String(h.get("counter_id", "")), int(h.get("value", 0)) if sv_c == null else int(sv_c), sv_c != null) + # 开格系 + fx.rebuild(["twin_pick"], [], []) + for i in 4: + var hits_dp: Array = fx.dispatch("dig_paid", {"ap_cost": 1}) + var refund := false + for h in hits_dp: + if String(h.get("kind", "")) == "grant_ap": + refund = true + fx.counter_add("twin_digs", 1) + if i == 1 or i == 3: + check(refund, "双管镐:第 %d 次付费挖格返 1 AP(每第 2 次——mod_eq 预评估口径)" % (i + 1)) + else: + check(not refund, "双管镐反例:第 %d 次不返(未满 2 次)" % (i + 1)) + fx.rebuild(["geologist"], [], []) + var geo_hits: Array = fx.dispatch("dig_reveal", {"new_resource_type": true}) + apply_fx.call(geo_hits) + check(orch._hits_of(geo_hits, "counter_add").size() == 1, "地质学家:首开新类型集 1 点〔专长批 V3〕") + fx.counter_add("geo_types", 2) # 手动基数补齐(row1 已落账 1+2=3——满 4 种的预评估现值) + var geo_hits2: Array = fx.dispatch("dig_reveal", {"new_resource_type": true}) + apply_fx.call(geo_hits2) + check(orch._hits_of(geo_hits2, "reveal_tiles").size() == 1 and fx.counter("geo_types") == 0, + "地质学家:满 4 种(预评估现值 3+本格)→揭示 1 格·计数清零〔专长批 V3〕") + var geo_hits3: Array = fx.dispatch("dig_reveal", {"new_resource_type": false}) + check(orch._hits_of(geo_hits3, "counter_add").is_empty(), "地质学家反例:同类型重复首开不叠点〔专长批 V3〕") + fx.rebuild(["hard_hat"], [], []) + apply_fx.call(fx.dispatch("dig_reveal", {})) + apply_fx.call(fx.dispatch("dig_reveal", {})) + check(fx.counter("helm_layers") == 2, "安全帽:开安全格攒层 ×2〔专长批 V3〕") + var hh_hits: Array = fx.dispatch("mine_hit", {"damage": 6}) + var hh_reduce := 0 + for h in hh_hits: + if String(h.get("kind", "")) == "damage_reduce": + hh_reduce = fx.counter(String(h.get("value_counter", ""))) + check(hh_reduce == 2, "安全帽:踩雷耗层减伤=层值 2(一次清零随施加面 set 行)〔专长批 V3〕") + fx.rebuild(["skip_mining"], [], []) + var sk_zero: Array = fx.dispatch("dig_reveal", {"is_blank_zero": true}) + apply_fx.call(sk_zero) + check(orch._hits_of(sk_zero, "counter_add").size() == 1, "跳格采掘:空白 0 格攒 1 点〔专长批 V3〕") + fx.counter_add("skip_pts", 1) # 手动基数补齐(已落账 1+1=2——满 3 点的预评估现值) + var sk_full: Array = fx.dispatch("dig_reveal", {"is_blank_zero": true}) + apply_fx.call(sk_full) + check(orch._hits_of(sk_full, "reveal_tiles").size() == 1, + "跳格采掘:满 3 点→揭示 1 格〔专长批 V3〕") + check(orch._hits_of(fx.dispatch("dig_reveal", {"is_blank_zero": false}), "counter_add").is_empty(), + "跳格采掘反例:普通开格不计点〔专长批 V3〕") + fx.rebuild(["blast_accounting"], [], []) + for i in 5: + var ba_hits: Array = fx.dispatch("dig_paid", {"ap_cost": 1}) + for h in ba_hits: + if String(h.get("kind", "")) == "counter_add": + fx.counter_add(String(h.get("counter_id", "")), 1) + check(orch._hits_of(fx.dispatch("round_end", {}), "grant_gp").size() == 1, "爆破会计:本轮挖格花费≥5→+1 GP〔专长批 V3〕") + fx.rebuild(["frontier_miner"], [], []) + check(orch._hits_of(fx.dispatch("dig_reveal", {"is_edge_cell": true}), "grant_material").size() == 1 + and orch._hits_of(fx.dispatch("dig_reveal", {"is_edge_cell": false}), "grant_material").is_empty(), + "边陲矿工:边缘格产出 +1·中心格反例零产出〔专长批 V3〕") + # 插旗系 + fx.rebuild(["seal_echo"], [], []) + apply_fx.call(fx.dispatch("flag_hit", {})) + apply_fx.call(fx.dispatch("flag_hit", {})) + var se_hits: Array = fx.dispatch("round_end", {}) + check(orch._hits_of(se_hits, "grant_ap").size() == 1 and String((orch._hits_of(se_hits, "grant_ap")[0] as Dictionary).get("mode", "")) == "queue", + "封印回响:标中计数≥2→轮末返 1 AP(下轮 queue)〔专长批 V3〕") + fx.rebuild(["seal_smith"], [], []) + var sm_remove := 0 + for i in 3: + var sm_dispatch: Array = fx.dispatch("flag_hit", {}) + apply_fx.call(sm_dispatch) + for h in sm_dispatch: + if String(h.get("kind", "")) == "remove_mines": + sm_remove += 1 + check(sm_remove == 1, "封印匠:标中每第 3 次→中立移除恰一次(频率闸死)〔专长批 V3〕") + fx.rebuild(["seal_stake"], [], []) + check(orch._hits_of(fx.dispatch("flag_hit", {}), "ac_bonus").size() == 1 + and String((orch._hits_of(fx.dispatch("flag_hit", {}), "ac_bonus")[0] as Dictionary).get("mode", "")) == "round", + "封印桩:标中本轮 AC +N(round 域——round_start 清零)〔专长批 V3〕") + fx.rebuild(["warning_beacon"], [], []) + check(orch._hits_of(fx.dispatch("flag_hit", {}), "preview_area").size() == 1, "示警烟火:标中揭示 3×3 资源类型(不揭安危位置)〔专长批 V3〕") + # 踩雷/受伤系 + fx.rebuild(["pain_memory"], [], []) + apply_fx.call(fx.dispatch("mine_hit", {})) + apply_fx.call(fx.dispatch("mine_hit", {})) + var pm_hits: Array = fx.dispatch("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}) + var pm_bonus := 0 + for h in orch._hits_of(pm_hits, "attack_bonus"): + pm_bonus = fx.counter(String(h.get("value_counter", ""))) + check(pm_bonus == 2, "疼痛记忆:攻击附加层值伤害=层值 2·用后清零(set 行随施加面)〔专长批 V3〕") + fx.rebuild(["fuse_wire"], [], []) + var fw_hits: Array = fx.dispatch("damage_taken", {"kind": "terrain", "amount": 6}) + var fw_shield := 0 + for h in fw_hits: + if String(h.get("kind", "")) == "shield": + fw_shield += int(h.get("value", 0)) + check(orch._hits_of(fx.dispatch("damage_taken", {"kind": "terrain", "amount": 3}), "shield").is_empty(), + "保险丝反例:单次 <5 不攒点不触发〔专长批 V3〕") + for h in fx.dispatch("damage_taken", {"kind": "terrain", "amount": 6}): + if String(h.get("kind", "")) == "counter_add": + fx.counter_add(String(h.get("counter_id", "")), int(h.get("value", 0))) + check(fw_shield == 0, "保险丝:首点不出盾(2 点才出——预评估口径)〔专长批 V3〕") + fx.rebuild(["blood_insight"], [], []) + check(orch._hits_of(fx.dispatch("mine_hit", {}), "reveal_tiles").size() == 1, "见血开窍:踩雷揭示 1 格〔专长批 V3〕") + fx.rebuild(["trap_instinct"], [], []) + apply_fx.call(fx.dispatch("mine_hit", {})) + var ti_hits: Array = fx.dispatch("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}) + var ti_val := 0 + for h in orch._hits_of(ti_hits, "attack_bonus"): + ti_val = fx.counter(String(h.get("value_counter", ""))) + check(ti_val == 2, "陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道——面板加算)〔专长批 V3〕") + fx.rebuild(["chain_traps", "pain_memory"], [], []) + apply_fx.call(fx.dispatch("mine_hit", {})) + var ct_before: int = fx.counter("pain_layers") + apply_fx.call(fx.dispatch("mine_hit", {})) + check(fx.counter("pain_layers") == ct_before + 2, "连环陷阱:同轮第 2 颗起每颗额外 +1 层(疼痛记忆同账)〔专长批 V3〕") + fx.rebuild(["trap_burst"], [], []) + var tb_hits: Array = fx.dispatch("mine_hit", {"damage": 8}) + check(orch._hits_of(tb_hits, "blast_scaled").size() == 1 + and float((orch._hits_of(tb_hits, "blast_scaled")[0] as Dictionary).get("mult", 0)) > 1.0, + "陷阱爆发:全体敌雷伤×倍率直伤(blast_scaled——施加面值=damage×mult)〔专长批 V3〕") + fx.rebuild(["blood_honey"], [], []) + check(orch._hits_of(fx.dispatch("mine_hit", {}), "heal").size() == 1, "血蜜:踩雷回复 N 生命〔专长批 V3〕") + fx.rebuild(["stone_skin"], [], []) + var ss_hits: Array = fx.dispatch("damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1}) + check(orch._hits_of(ss_hits, "ac_bonus").size() == 1 + and int((orch._hits_of(ss_hits, "ac_bonus")[0] as Dictionary).get("cap", 0)) == 3, + "石肤:受伤 AC+1(本场域 cap 3——达上限不再涨由累积器封顶)〔专长批 V3〕") + fx.rebuild(["last_stand"], [], []) + check(orch._hits_of(fx.dispatch("damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1, "hp_ratio": 40}), "damage_reduce").size() == 1 + and orch._hits_of(fx.dispatch("damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 2, "hp_ratio": 80}), "damage_reduce").is_empty(), + "背水:生命≤50% 减伤·过半血反例零减伤〔专长批 V3〕") + # 跨族桥 + fx.rebuild(["breaker_strike"], [], []) + check(orch._hits_of(fx.dispatch("mine_hit", {"dug_under_flag": true}), "counter_add").size() == 1 + and orch._hits_of(fx.dispatch("mine_hit", {"dug_under_flag": false}), "counter_add").is_empty(), + "破封一击:挖开带封印危险格 +1 层·无旗反例零触发〔专长批 V3〕") + # 节奏系 + fx.rebuild(["map_exchange"], [], []) + var me_hits: Array = fx.dispatch("dig_stop", {"ap_left": 4}) + apply_fx.call(me_hits) + fx.counter_add("stop_swap", 4) # value_event="ap_left" 解析归施加面(action_economy 载荷取值)——冒烟手动落账 + check(orch._hits_of(me_hits, "counter_add").size() == 1, "弃图兑换:停手剩余 AP≥3 入账〔专长批 V3〕") + var me_atk := 0 + for h in orch._hits_of(fx.dispatch("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}), "attack_bonus"): + me_atk = fx.counter(String(h.get("value_counter", ""))) + check(me_atk == 4, "弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕") + check(orch._hits_of(fx.dispatch("dig_stop", {"ap_left": 2}), "counter_add").is_empty(), + "弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕") + fx.rebuild(["last_drop"], [], []) + var ld_hits: Array = fx.dispatch("round_start", {"round": 2, "ap_exhausted_last": true}) + check(orch._hits_of(ld_hits, "grant_ap").size() == 1, "一滴不剩:上轮 AP 耗尽→+1 AP〔专长批 V3〕") + check(orch._hits_of(fx.dispatch("round_start", {"round": 3, "ap_exhausted_last": false}), "grant_ap").is_empty(), + "一滴不剩反例:上轮有余不发〔专长批 V3〕") + fx.rebuild(["market_close"], [], []) + for i in 8: + for h in fx.dispatch("dig_reveal", {}): + if String(h.get("kind", "")) == "counter_add": + fx.counter_add(String(h.get("counter_id", "")), 1) + check(orch._hits_of(fx.dispatch("round_end", {}), "grant_gp").size() == 1, "收盘:本轮开格≥8→+1 GP〔专长批 V3〕") + # 战斗系 + fx.rebuild(["execution_scholar"], [], []) + check(orch._hits_of(fx.dispatch("kill", {"target": "敌", "overflow": 0}), "reveal_tiles").size() == 1, + "斩首学者:击杀揭示 1 格〔专长批 V3〕") + fx.rebuild(["executioner"], [], []) + var ex_hits: Array = fx.dispatch("enemy_hp_threshold", {"unit": "e1", "pct": 30, "is_boss": false}) + check(orch._hits_of(ex_hits, "target_attack_bonus").size() == 1 + and orch._hits_of(fx.dispatch("enemy_hp_threshold", {"unit": "e1", "pct": 60, "is_boss": false}), "target_attack_bonus").is_empty(), + "处刑台:敌血量≤30% 挂单位标记加伤·阈值外反例零命中〔专长批 V3〕") + fx.rebuild(["field_dressing"], [], []) + check(orch._hits_of(fx.dispatch("battle_won", {"round": 2}), "heal").size() == 1, + "战地包扎:战斗胜利回复 N 生命(battle_won 新挂点)〔专长批 V3〕") + # 契约尖点 + fx.rebuild(["blood_tax"], [], []) + check(orch._hits_of(fx.dispatch("mine_hit", {}), "bonus_delta").size() == 1 + and String((orch._hits_of(fx.dispatch("flag_hit", {}), "bonus_delta")[0] as Dictionary).get("axis", "")) == "atk", + "血税:双向同局账本(踩陷阱−2max_hp/封印命中+1atk——bonus_delta 战末汇入)〔专长批 V3〕") + # 迁入 2 件 + fx.rebuild(["venom_edge"], [], []) + check(orch._hits_of(fx.dispatch("clamp_check", {"matchup": "magic_vs_heavy"}), "modify_ticket").size() == 1 + and orch._hits_of(fx.dispatch("clamp_check", {"matchup": "normal_vs_light"}), "modify_ticket").is_empty(), + "淬毒刃纹(迁入):克制正面(magic_vs_heavy 矩阵值>0)追加·中性面反例零触发〔专长批 V3〕") + fx.rebuild(["flag_burst"], [], []) + check(orch._hits_of(fx.dispatch("flag_hit", {}), "open_ticket").size() == 1, + "封印爆裂(迁入):标中全体敌群伤〔专长批 V3〕") + # 零持有 var orch0 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(74)) orch0._fx.rebuild([], [], []) check(orch0._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}).is_empty() - and orch0._fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": true, "terrain_kind": []}).is_empty(), + and orch0._fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": true, "terrain_kind": []}).is_empty() + and orch0._fx_emit("mine_hit", {"damage": 5}).is_empty() + and orch0._fx_emit("battle_won", {"round": 1}).is_empty(), "零持有订阅=零命中(relic 关着零扰动——伤害管线既有行为逐位不变)") + # ⑦c 特性持有呈现段(2026-10-04 用户反馈补齐——C 区「特性」节;呈现换代面=06 在册不随批) + var panel = load("res://presentation/views/hero_info_panel_view.gd").new() + panel.set_feat_holdings([]) + check(not panel._feat_section.visible, "特性段空态隐藏(视图空态纪律同特性段)") + panel.set_feat_holdings(["疼痛记忆 ×1", "双管镐 ×1"]) + check(panel._feat_section.visible and panel._feat_list.get_child_count() == 2, + "特性段持有呈现(逐件行常驻可读——「局内奖励选了什么看不到」缺口承载)") + check(String(load("res://presentation/battle_narrator.gd").feat_card_text({"feat_id": "pain_memory", "display_name": "疼痛记忆", "desc": "触陷阱攒 1 层"})).contains("触陷阱攒 1 层"), + "特性卡文案表值化:desc 行字段直读(零硬编码映射)") # 手工场景驱动:L1 全程 + L2 推进到商店房(_drive_level 复用 + 兑付确认/选路/大奖励确认手工编排) @@ -4645,7 +4707,7 @@ func _meta_settle_and_workshop() -> void: var mp_a = load("res://core/meta_progression.gd").new(_db, sv_a) check(Attunement.slots(_db, sv_a) == 0 and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 0, "白板档同调位求值 = 0(0 起步裁决)且 Y=0(未购无刷新能力——P1 甲初始 0 同构)") - check(mp_a.toggle_attunement("venom_edge") == false, + check(mp_a.toggle_attunement("nest_counter") == false, "满位禁选(Slots 0 时装配拒绝)") sv_a.add_keystone(200) check(mp_a.buy_attune_refresh() and sv_a.attune_refresh_level() == 1 @@ -4655,9 +4717,9 @@ func _meta_settle_and_workshop() -> void: "stopped_at": 2, "keystone_total": 0, "master_seed": 21}) check(Attunement.slots(_db, sv_a) == 1 and sv_a.boss_kill_count() == 1, "通关 1 关 → m1 达成 Slots=1 且 Boss 击杀总数=1(计数源收束入账)") - check(mp_a.toggle_attunement("venom_edge") and mp_a.toggle_attunement("flag_burst") == false, + check(mp_a.toggle_attunement("nest_counter") and mp_a.toggle_attunement("dawn_order") == false, "装配持久写入 + 满位禁选(Slots 1 装第二件拒绝)") - check(mp_a.toggle_attunement("venom_edge") and mp_a.toggle_attunement("flag_burst"), + check(mp_a.toggle_attunement("nest_counter") and mp_a.toggle_attunement("dawn_order"), "卸下恒可再装配(换装语义——装配序数组承载)") var sv_a2 = _meta_new_save(_META_SMOKE_PATHS[7]) sv_a2.set_attunement_equipped(["luck_coin"]) @@ -4763,7 +4825,7 @@ func _smoke_tree() -> void: # 迁移幂等(forge_levels→锻造脉原样映射;重复迁移零变化) var mp2 = load("res://core/meta_progression.gd").new(_db, sv) sv.set_forge_level("atk", 2) - sv.unlock_item("venom_edge") + sv.unlock_item("nest_counter") var m1: Dictionary = mp2.migrate_tree() check(int(m1.get("forge", 0)) == 1 and int(m1.get("unlock", 0)) == 1, "三体系迁移:forge_atk←2 级点亮 + ks_workshop←解锁集点亮(差额补偿=原样映射零亏损)")