diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index e312ea57..6f214914 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -425,19 +425,15 @@ func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String = _feats.append(feat_id) _curio_taken["relic"] = int(_curio_taken.get("relic", 0)) + 1 return true + # 加算形态路由(内容数据驱动扩展批 T5——match 收敛进效果类型:属性值提升四类走 + # adventure_table.CURIO_APPLY_ROUTES 声明路由(bonus 落点键),加新加算类令牌零代码) + var sink := db.adventure.curio_apply_route(kind) + if sink != "": + bonus[sink] = int(bonus.get(sink, 0)) + value + _curio_taken[kind] = int(_curio_taken.get(kind, 0)) + 1 + return true + # 通道面(副作用形态——显式分支) match kind: - "attack": - bonus["atk"] = int(bonus.get("atk", 0)) + value - _curio_taken["attack"] = int(_curio_taken.get("attack", 0)) + 1 - "max_hp": - bonus["max_hp"] = int(bonus.get("max_hp", 0)) + value - _curio_taken["max_hp"] = int(_curio_taken.get("max_hp", 0)) + 1 - "armor": - bonus["armor_class"] = int(bonus.get("armor_class", 0)) + value - _curio_taken["armor"] = int(_curio_taken.get("armor", 0)) + 1 - "ap": - bonus["ap"] = int(bonus.get("ap", 0)) + value - _curio_taken["ap"] = int(_curio_taken.get("ap", 0)) + 1 "gp": if _runner != null: _runner.grant_curio_gp(value) @@ -649,7 +645,14 @@ func _seed_attune_charges() -> void: # 达上限剔牌/掷定前置直显;停点可中断)---- func _attune_refresh_x_total() -> int: - return int(db.treasure_item_row("pick_refresh").get("magnitude", 0)) if _attunement.has("pick_refresh") else 0 + # 内容数据驱动扩展批 T4——按 id 特判拆除:kind 分发查询(装配集内 effect_kinds 含 bonus_refresh + # 的行 magnitude 合计;刷新件类加件零 id 分支) + var total := 0 + for iid_v in _attunement: + var arow: Dictionary = db.treasure_item_row(String(iid_v)) + if (arow.get("effect_kinds", []) as Array).has("bonus_refresh"): + total += int(arow.get("magnitude", 0)) + return total func _reset_refresh_x() -> void: diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index 6c6a4176..e8415804 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -75,8 +75,7 @@ var _flag_consumed := {} # 兑付资格以雷格实例为键 # 同调装配运行态(迭代 11 第①批 T5——begin_battle 经 enc.attunement 注入;off=空集全部效果零扰动) var attunement: Array = [] # 装备中同调件 item_id 集 var _attune_charges_ref: Dictionary = {} # 充能账本共享引用(迭代 11 第①批 T6——use_treasure 消费直改) -var _attune_bank := 0 # 掘进蓄能积点(battle 域——每 charge_n 安全格 +1 点) -var _attune_safe_seen := 0 # 掘进蓄能 battle 域安全开格计数 +var _attune_safe_seen := 0 # 掘进蓄能 battle 域安全开格计数(进度呈现取数面;积点账本=效果引擎计数器 dig_charge_bank——行 triggers counter_spend 订阅同账本) # 特性运行态(内容数据驱动扩展批 T3——relic 持有集经 enc.feats 注入:效果行为=数据行 triggers 经 # 效果引擎分发;魔矢每战首次计数 battle 域。空集=零扰动) var _feat_first_dig_used := false @@ -126,15 +125,12 @@ func _attune_has(item_id: String) -> bool: return attunement.has(item_id) -# 装备件表值读取(magnitude/effective_rounds 等——TreasureItemTable 行;未装配不读表) +# 装备件表值读取(charge_n 等——TreasureItemTable 行;未装配不读表。magnitude 读数面随 T4 +# 秘宝订阅迁移退役——行值经 value_from 在 rebuild 定影,零运行期按件取值) func _attune_row(item_id: String) -> Dictionary: return db.treasure_item_row(item_id) if _attune_has(item_id) else {} -func _attune_mag(item_id: String) -> int: - return int(_attune_row(item_id).get("magnitude", 0)) - - # 特性求值(内容数据驱动扩展批 T3——_feat_flags_for 挂点求值退役:效果行为=数据行 triggers 订阅, # 效果引擎 dispatch 分发;旧 23 hook 枚举与旗注入消亡。流派档位质变=FeatsEngine 纯函数求值 # (档位机制本身系引擎基建非内容效果),两运行态注伤害控制器:档一 rider+1 / 档二 rider 可暴击) @@ -208,10 +204,9 @@ func begin_battle(enc: Dictionary) -> bool: # 伤害控制器、偏运 N 注入地图战斗资源——均须先于首板 install) attunement = (enc.get("attunement", []) as Array).duplicate() _attune_charges_ref = enc.get("attune_charges", {}) # 共享 dict 引用(level/adventure 同账本——use_treasure 扣减直改) - _attune_bank = 0 _attune_safe_seen = 0 - _damage.attune_venom = _attune_mag("venom_edge") if _attune_has("venom_edge") else 0 - _reward_economy.luck_bias = _attune_mag("luck_coin") if _attune_has("luck_coin") else 0 + # 淬毒/偏运注入拆除(内容数据驱动扩展批 T4——两件被动改 triggers 订阅行:淬毒=clamp_check + # modify 窗口、偏运=reward_roll bias_rewards;value 行字段 value_from 定影,零 begin_battle 注入) # 特性运行态(内容数据驱动扩展批 T3——enc.feats 携带持有集:效果行为=数据行 triggers 经效果 # 引擎分发;流派档位=FeatsEngine 纯函数求值两运行态注伤害控制器。魔矢每战首次开格计数同步归零) var feat_ids: Array = enc.get("feats", []) as Array @@ -237,9 +232,8 @@ func begin_battle(enc: Dictionary) -> bool: var ap_bonus: int = int(enc.get("hero_ap_bonus", 0)) if ap_bonus > 0: action_economy.queue_ap_delta(ap_bonus) - # 工兵晨令(迭代 11 同调批 T5——前 effective_rounds 轮每轮 +1 AP;首轮在此入队,后续轮在轮始段续队) - if _attune_has("dawn_order"): - action_economy.queue_ap_delta(_attune_mag("dawn_order")) + # 工兵晨令直调拆除(内容数据驱动扩展批 T4——round_start grant_ap(mode:queue) 订阅行承载, + # when round_index ≤ effective_rounds 行字段 value_from 定影;发射位先于轮切换=当轮兑现) # round_start 发射+施加(内容数据驱动扩展批 T3——发射位先于行动经济轮切换:树规则 grant_ap # mode:queue 当轮入队、notify_round_start 即兑现(先手压制=首轮 AP+1 语义锚,与迁移前 # begin_battle 直调 queue_ap_delta 逐位一致);计数器 round_start 重置点同点生效) @@ -474,12 +468,15 @@ func _settle() -> void: "奥术矢", "board", int((spell_hits[0] as Dictionary).get("value", 0)), DamageController.TARGET_FRONT_ALIVE, ri, evt.cell, "magic") _submit(spell_t, false) - # 掘进蓄能计数(迭代 11 同调批 T6——battle 域安全开格计数,每 charge_n 格积 1 点) + # 掘进蓄能计数(迭代 11 同调批 T6 → 内容数据驱动扩展批 T4:积点账本迁效果引擎计数器 + # dig_charge_bank——行 triggers counter_spend(treasure_used) 受理闸同账本。模判定留编排器侧: + # 「每 N 格积 1 点」在发射点施加架构下无法行内表达先增后判(when 于 dispatch 预评估), + # 逐位等价优先——词汇表边界注记随 commit 留痕;charge_n 读行零字面量) if _attune_has("dig_charge"): _attune_safe_seen += 1 var cn_b: int = int(db.treasure_item_row("dig_charge").get("charge_n", 999)) if cn_b > 0 and _attune_safe_seen % cn_b == 0: - _attune_bank += 1 + _fx.counter_add("dig_charge_bank", 1) elif evt.kind == "mine": # 插旗不可逆四情况②:插雷格再挖开 = 只受挖雷一层惩罚(mine_hit 照常兑付——惊动巢穴), # 标中不发(旗已随挖掘擦除、不入轮末判定基线)。 @@ -689,14 +686,14 @@ func use_treasure(handle: int, target: Vector2i) -> Dictionary: func(c: Vector2i): return not board.is_revealed(c)) if not bool(v.ok): return v # ② 拒绝零消耗 - # treasure_used 发射点(内容数据驱动扩展批 T1——使用主动秘宝受理面;掘进蓄能 counter_spend 订阅面) - _fx_emit("treasure_used", {"item_id": String(v.item_id), "target": target}) - # 掘进蓄能受理闸(迭代 11 同调批 T6——bank>0 才可发起,一行一点;积点不足拒绝零消耗) - if String(v.item_id) == "dig_charge" and _attune_has("dig_charge"): - if _attune_bank <= 0: - var cn_info: int = int(db.treasure_item_row("dig_charge").get("charge_n", 0)) - return {"ok": false, "reason": "掘进蓄能:安全格积点未达(%d/%d)" % [_attune_safe_seen % cn_info if cn_info > 0 else 0, cn_info]} - _attune_bank -= 1 + # treasure_used 发射+施加(内容数据驱动扩展批 T4——使用主动秘宝受理面:counter_spend 订阅=受理闸 + # (掘进蓄能积点账本),消耗成功才受理;积点不足拒绝零消耗。按 item_id 特判随本批拆除) + var tu_hits := _fx_emit("treasure_used", {"item_id": String(v.item_id), "target": target}) + for h in tu_hits: + if String(h.get("kind", "")) == "counter_spend": + if not _fx.counter_spend(String(h.get("counter_id", "")), int(h.get("value", 1))): + var cn_info: int = int(db.treasure_item_row("dig_charge").get("charge_n", 0)) + return {"ok": false, "reason": "掘进蓄能:安全格积点未达(%d/%d)" % [_attune_safe_seen % cn_info if cn_info > 0 else 0, cn_info]} var kinds: Array = v.kinds if kinds.has("mine_reveal"): treasure_inventory.apply_overlay("mine_reveal", v.resolved) @@ -750,18 +747,14 @@ func _grant_routed(snap: Dictionary, polarity: String, source_class: String, cel row = row.duplicate() row["power"] = int(round(int(row.power) * prem_pct / 100.0)) row["premium_pct"] = prem_pct - # 惊巢反制(迭代 11 同调批 T5——底稿 §3:挖开雷格该次惩罚减半 + 反弹 N;duplicate 隔离——减半 - # 不回写极性字典;仅 source_class=mine_hit 受理,标错旗/负向字典行走原通道) - if polarity == "negative" and source_class == "mine_hit" and _attune_has("nest_counter"): + # 惊巢反制(迭代 11 同调批 T5 → 内容数据驱动扩展批 T4:按 id 特判拆除——减半系数=mine_hit + # damage_mult 订阅命中(行 value 50=减 50%,取整 ceil 与迁移前逐位一致);反弹单=mine_hit + # open_ticket channel reflect 订阅施加(编排器 mine_hit 施加段);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)) / 2.0)) + var half: int = int(ceil(float(int(row.power)) * float(100 - halve_pct) / 100.0)) _emit_row("effect", source_class, cell, ri, "惊巢反制——挖雷惩罚减半(%d→%d)" % [int(row.power), half], 0, {"effect_id": "nest_counter"}, highlight) row.power = half - var reflect: int = _attune_mag("nest_counter") - if reflect > 0: - var nct := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_nest_counter", "惊巢反制·反弹", "board", - reflect, DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, highlight, {}) - _submit(nct, false) return _dispatch_row(row, source_class, cell, ri, highlight, label_suffix) @@ -889,10 +882,13 @@ func _judge_flag(evt: Dictionary, ri: int) -> void: # 净化改写 → 图谱额外资源 → 基线兑付) var fh_hits := _fx_emit("flag_hit", {"cell": cell, "round": ri, "terrain_kind": _harmful_adjacent(cell)}) - # 标中殉爆(迭代 11 同调批 T5——底稿 §3:每标中一面旗,全体存活敌吃 N 群伤;无类型走 blast 通道) - if _attune_has("flag_burst"): + # 标中殉爆(迭代 11 同调批 T5 → 内容数据驱动扩展批 T4:订阅施加——行 triggers open_ticket + # channel blast value_from magnitude;每标中一面旗全体存活敌吃 N 群伤,无类型走 blast 通道) + for h in _hits_of(fh_hits, "open_ticket"): + if String(h.get("channel", "")) != "blast": + continue var fbt := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_flag_burst", "标中殉爆", "board", - _attune_mag("flag_burst"), DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, "highlight", {}) + int(h.get("value", 0)), DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, "highlight", {}) _submit(fbt, false) # 净化工兵(迭代 12 T6 吞噬流——标中旗中和相邻伤害型地形格):🔴 R4 甲永不对冲边界= # **仅限伤害型**(thorn/magma/rockfall——variation 表 effect=flat_damage 判据),信息型 @@ -1182,9 +1178,7 @@ func _exit_distribution() -> void: if not _install_new_board(): _fatal("换图编排失败") return - # 工兵晨令续队(迭代 11 同调批 T5——upcoming 轮号 = round_index+1 ≤ effective_rounds 才续,P4 甲表值化) - if _attune_has("dawn_order") and round_index + 1 <= int(db.treasure_item_row("dawn_order").get("effective_rounds", 0)): - action_economy.queue_ap_delta(_attune_mag("dawn_order")) + # 工兵晨令续队直调拆除(内容数据驱动扩展批 T4——round_start 订阅行承载,见 begin_battle 注) action_economy.notify_settlement_done(mode) # round_start 发射先于行动经济轮切换(queue 型 grant_ap 当轮入队当轮兑现——与首轮同口径) round_index += 1 diff --git a/game/core/damage_controller.gd b/game/core/damage_controller.gd index 9b2608d4..abd5002a 100644 --- a/game/core/damage_controller.gd +++ b/game/core/damage_controller.gd @@ -313,10 +313,9 @@ func _resolve_hero_attack(t: Dictionary) -> void: for h in clamp_hits: if String(h.get("kind", "")) == "modify_ticket" and h.has("delta"): cdelta += int(h.delta) - # 淬毒刃纹(迭代 11 同调批 T5——底稿 §3:对被克制敌人的穿刺/魔法直伤 +N;条件 = 克制成立 × - # 攻击类型限定,主角攻击面专属——棋盘克制直伤走 _resolve_board_typed 不吃本加成) - if attune_venom > 0 and (atk_type == "pierce" or atk_type == "magic") and cdelta > 0: - cdelta += attune_venom + # 淬毒刃纹(迭代 11 同调批 T5 → 内容数据驱动扩展批 T4:特判拆除——行 triggers clamp_check + # modify(mode:clamp_bonus) 订阅命中已并入上方 delta 合计;mode 路由=主角攻击面专享, + # 棋盘克制直伤窗只消费无模式 delta——棋盘不吃毒面与迁移前一致) var base_value := float(t.base_value) # 按序改写族第五拍:矿工重锤增伤面(交换的正向侧) for h in ac_hits: @@ -437,7 +436,8 @@ func _resolve_board_typed(t: Dictionary) -> void: 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}): - if String(h.get("kind", "")) == "modify_ticket" and h.has("delta"): + # 无模式 delta 才入棋盘窗(mode 路由:clamp_bonus=主角攻击面专享、discount_shift=折扣档面) + 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.settlement.damage_floor)) units.apply_damage(target, dealt) @@ -528,9 +528,6 @@ func _discount_shift_up(discount: int, shift: int) -> int: return int(tbl[mini(tbl.size() - 1, idx + shift)]) -# 淬毒刃纹 N(迭代 11 同调批 T5——战斗结算 begin_battle 注入;0 = 未装配零扰动) -var attune_venom: int = 0 - # 池内加算 → clamp [-100,+100](池间乘算归 DamageMath.pool_damage 调用侧) func clamped_pool(pool: String) -> int: var total := 0 diff --git a/game/core/effect_engine.gd b/game/core/effect_engine.gd index 7481b998..54bc60ad 100644 --- a/game/core/effect_engine.gd +++ b/game/core/effect_engine.gd @@ -110,8 +110,16 @@ func _push_trigger(source: String, id: String, tr: Dictionary, row: Dictionary) if do_d.has(pair[0]): do_d[pair[1]] = int(row.get(String(do_d[pair[0]]), 0)) do_d.erase(pair[0]) + # when value_from 同面解析(行字段引用条件——dawn_order effective_rounds 先例:字面量出行=数值 + # 单一事实源破面,行引用随 rebuild 定影) + var when_d: Array = (tr.get("when", []) as Array).duplicate(true) + for c_v in when_d: + var c: Dictionary = c_v as Dictionary + if c.has("value_from"): + c["value"] = int(row.get(String(c["value_from"]), 0)) + c.erase("value_from") var t := {"source": source, "id": id, "on": on, - "when": (tr.get("when", []) as Array).duplicate(true), + "when": when_d, "do": do_d} _triggers.append(t) # 计数器重置点收集(counter_add/counter_spend 行声明 reset_at) @@ -290,12 +298,19 @@ static func validate_trigger_row(rid: String, row: Dictionary, triggers: Array, var kind := String(do_d.get("kind", "")) if not EFFECT_KINDS.has(kind): errs.append({"category": "enum", "message": "%s 效果类型出域(解释器闭集): %s" % [rid, kind]}) - # value_from/delta_from/mult_from/cap_from 引用面:行字段存在性(悬空=加载致命) + # value_from/delta_from/mult_from/cap_from 引用面:行字段存在性(悬空=加载致命)—— + # do 与 when 两侧同面(when value_from=dawn_order effective_rounds 先例) for vf_k in ["value_from", "delta_from", "mult_from", "cap_from"]: if do_d.has(vf_k): var fld := String(do_d.get(vf_k, "")) if fld != "" and not row.has(fld): errs.append({"category": "dangling", "message": "%s do.%s 悬空(行无字段 %s)" % [rid, vf_k, fld]}) + for c_v in (tr.get("when", []) as Array): + var c_w: Dictionary = c_v as Dictionary + if c_w.has("value_from"): + var wfld := String(c_w.get("value_from", "")) + if wfld != "" and not row.has(wfld): + errs.append({"category": "dangling", "message": "%s when.value_from 悬空(行无字段 %s)" % [rid, wfld]}) return mine_covered diff --git a/game/core/reward_economy.gd b/game/core/reward_economy.gd index 96704b68..4322dc85 100644 --- a/game/core/reward_economy.gd +++ b/game/core/reward_economy.gd @@ -65,11 +65,14 @@ func roll_board(eligible: Array) -> bool: _clear_granted = false _clear_snapshot = {} var stream: RandomNumberGenerator = rng.open_stream("reward_roll") # 恰一 scope 批(格批 + 清图批尾 + 区间行追加取号) - # reward_roll 发射点(内容数据驱动扩展批 T1——格面资源掷定、摇号前权重合成层; - # 偏运硬币 bias_rewards 订阅面。双轨期权重投影仍走 luck_bias 旧面,发射仅观测) + # reward_roll 发射+施加(内容数据驱动扩展批 T4——格面资源掷定、摇号前权重合成层: + # bias_rewards 订阅命中合计=偏运权重加算(行 value_from magnitude 定影);零命中=原行集零扰动) + var luck_bias := 0 if _fx != null: - _fx.dispatch("reward_roll", {"polarity": "positive", "cell_count": list.size()}) - var cell_rows: Array = _biased_cell_rows() # cell 池正向行 weight>0(偏运硬币装配时稀有正向 kinds 权重 +N——池结构零触碰) + for h in _fx.dispatch("reward_roll", {"polarity": "positive", "cell_count": list.size()}): + if String(h.get("kind", "")) == "bias_rewards": + luck_bias += int(h.get("value", 0)) + var cell_rows: Array = _biased_cell_rows(luck_bias) # cell 池正向行 weight>0(偏运硬币装配时稀有正向 kinds 权重 +N——池结构零触碰) var clear_rows: Array = _pool_rows("clear_default") # ① 身份批:每非封锁格恰一类型身份(雷安同池——同一行集同一权重分布) var draft := {} @@ -184,8 +187,8 @@ func polarity_row(cell_type: String, polarity: String) -> Dictionary: # ---- F1 选行算子纯函数 ---- -# 偏运硬币 N(迭代 11 同调批 T5——战斗结算 begin_battle 注入;0 = 未装配零扰动) -var luck_bias: int = 0 +# 偏运硬币 N(迭代 11 同调批 T5 → 内容数据驱动扩展批 T4:begin_battle 注入拆除—— +# reward_roll bias_rewards 订阅命中合计经 _biased_cell_rows 入参;0 = 未装配零扰动) # 稀有正向资源 kinds(底稿 §3——权重投影面,池结构零触碰) const LUCK_BIAS_KINDS := ["heal", "gp", "pierce", "magic"] @@ -202,15 +205,15 @@ const SKEW_UP_KINDS := ["attack", "blast", "pierce", "magic"] # 偏运硬币权重投影(底稿 §3:只动池内权重、雷安仍同池同分布;luck_bias=0 逐位返回原行集——off 态零扰动) # 基座换源(首发批 2026-10-04):先经图级偏斜投影再叠件级加法——合成序锚=图级先、件级后 #(环境事实先定型、玩家构筑叠其上;两投影均为池内权重面,雷安同池与取号次数零变化) -func _biased_cell_rows() -> Array: +func _biased_cell_rows(bias: int) -> Array: var base: Array = _skewed_cell_rows() - if luck_bias <= 0: + if bias <= 0: return base var out: Array = [] for row in base: if LUCK_BIAS_KINDS.has(String(row.get("cell_type", ""))): var biased: Dictionary = row.duplicate() - biased["weight"] = int(row.weight) + luck_bias + biased["weight"] = int(row.weight) + bias out.append(biased) else: out.append(row) diff --git a/game/data/schemas/adventure_table.gd b/game/data/schemas/adventure_table.gd index 3cc181b8..bd5dd0b4 100644 --- a/game/data/schemas/adventure_table.gd +++ b/game/data/schemas/adventure_table.gd @@ -80,6 +80,23 @@ const TIER_QUOTAS := {"common": 70, "rare": 25, "rarest": 3} @export var feat_ticket_pity_step: int = 5 # [1,99] knob(口径定死=5)——保底步数(连续未出展示次数) +## 令牌兑付路由表(内容数据驱动扩展批 T5——_apply_curio_pick match 收敛进效果类型: +## 属性值提升四类 = 「加算落点键」声明路由,加新加算类令牌 = 表行+配额/件值键,零代码; +## 通道面三类(gp 钱包/heal 台账/refresh 界面余量)+relic 持有集 = 副作用通道显式分支不进本表 +## ——路由表只承载「纯加算落点」形态,与效果引擎组合规则加法族同构) +const CURIO_APPLY_ROUTES := { + "attack": "atk", + "max_hp": "max_hp", + "armor": "armor_class", + "ap": "ap", +} + + +## 令牌加算路由读数(空 = 非加算形态——调用方走通道面分支) +func curio_apply_route(kind: String) -> String: + return String(CURIO_APPLY_ROUTES.get(kind, "")) + + ## 令牌配额读数(未列 kind×tier 组合 = 0——heal 无普通档/ap 无普通稀有档按 v2.2 构成) func curio_quota(kind: String, tier: String) -> int: match [kind, tier]: diff --git a/game/data/schemas/treasure_item_table.gd b/game/data/schemas/treasure_item_table.gd index 76cf6e5b..e4dff0dc 100644 --- a/game/data/schemas/treasure_item_table.gd +++ b/game/data/schemas/treasure_item_table.gd @@ -94,9 +94,11 @@ func validate() -> Array: for k in row.keys(): if not KEY_WHITELIST.has(String(k)): errs.append({"category": "zero_flat", "message": "%s 行键出白名单: %s(KEY_WHITELIST 外键禁入)" % [iid, String(k)]}) - # triggers 事件订阅行(内容数据驱动扩展批 T2——被动件换代形态;与 effect_kinds 互斥共存校验) + # triggers 事件订阅行(内容数据驱动扩展批 T2/T4——被动件换代形态;与 effect_kinds 互斥校验 + # 面=passive 行(换代单形态——效果行为只走订阅);active_charged 行两形合法并存 + # (effect_kinds=主动分发通道 + triggers=受理闸订阅,掘进蓄能先例)) if row.has("triggers"): - if (row.get("effect_kinds", []) as Array).size() > 0 and not bool(row.get("retired", false)): + if (row.get("effect_kinds", []) as Array).size() > 0 and String(row.get("form", "")) != "active_charged" and not bool(row.get("retired", false)): errs.append({"category": "domain", "message": "%s triggers 与 effect_kinds 并存(换代单形态——被动件迁移期禁双挂)" % iid}) EffectEngine.validate_trigger_row(iid, row, row.get("triggers", []) as Array, errs) var form: String = String(row.get("form", "")) @@ -106,8 +108,8 @@ func validate() -> Array: # 退役/失义行:免 effect_kinds 非空与承重护栏校验(登记面保留,不入投放与 F2) continue # ---- 活行校验 ---- - if kinds.is_empty(): - errs.append({"category": "domain", "message": "%s effect_kinds 空(活行;失义件应标 deferred)" % iid}) + if kinds.is_empty() and not row.has("triggers"): + errs.append({"category": "domain", "message": "%s effect_kinds 空(活行;失义件应标 deferred,被动件应挂 triggers 订阅)" % iid}) var kind_seen := {} for k in kinds: var kind: String = String(k) @@ -132,8 +134,8 @@ func validate() -> Array: mag_consuming = true if mag_consuming and mag < 1: errs.append({"category": "cross_field", "message": "%s N 值 kind 行 magnitude 应 ≥1,实得 %d" % [iid, mag]}) - if not mag_consuming and mag != 0: - errs.append({"category": "cross_field", "message": "%s 非 N 值 kind 活行 magnitude 应 0,实得 %d" % [iid, mag]}) + if not mag_consuming and mag != 0 and not row.has("triggers"): + errs.append({"category": "cross_field", "message": "%s 非 N 值 kind 活行 magnitude 应 0(trigger 行 magnitude=value_from 取数源豁免),实得 %d" % [iid, mag]}) # ---- 同调行约束(form 非空——装配面;过渡行 form="" 不校验)---- if form != "": if int(row.get("slot_cost", 1)) != 1: @@ -148,8 +150,8 @@ func validate() -> Array: errs.append({"category": "domain", "message": "%s effective_rounds 越域 [0,999]: %d" % [iid, eff_rounds]}) if kind_seen.has("ap_rounds_front") and eff_rounds < 1: errs.append({"category": "cross_field", "message": "%s ap_rounds_front 行 effective_rounds 应 ≥1(P4 甲表值化),实得 %d" % [iid, eff_rounds]}) - if not kind_seen.has("ap_rounds_front") and eff_rounds != 0: - errs.append({"category": "cross_field", "message": "%s 非 ap_rounds_front 行 effective_rounds 应 0,实得 %d" % [iid, eff_rounds]}) + if not kind_seen.has("ap_rounds_front") and eff_rounds != 0 and not row.has("triggers"): + errs.append({"category": "cross_field", "message": "%s 非 ap_rounds_front 行 effective_rounds 应 0(trigger 行=when value_from 取数源豁免),实得 %d" % [iid, eff_rounds]}) var charge_n: int = int(row.get("charge_n", 0)) if charge_n < 0 or charge_n > 999: errs.append({"category": "domain", "message": "%s charge_n 越域 [0,999]: %d" % [iid, charge_n]}) diff --git a/game/data/tables/treasure_items.tres b/game/data/tables/treasure_items.tres index 3da8a3f6..4aaf2ec3 100644 --- a/game/data/tables/treasure_items.tres +++ b/game/data/tables/treasure_items.tres @@ -10,6 +10,7 @@ rows = [{ "item_id": "probe_dust", "display_name": "显影尘", "dimension_tags": ["info", "counter"], +"desc": "效果失义待重定", "effect_kinds": [], "target": "none", "magnitude": 0, @@ -19,6 +20,7 @@ rows = [{ "item_id": "sapper_order", "display_name": "工兵令", "dimension_tags": ["action"], +"desc": "AP +2", "effect_kinds": ["ap_grant"], "target": "none", "magnitude": 2, @@ -28,6 +30,7 @@ rows = [{ "item_id": "loot_compass", "display_name": "探宝罗盘", "dimension_tags": ["plan"], +"desc": "预读 3×3", "effect_kinds": ["reward_preview"], "target": "area_3x3", "magnitude": 0, @@ -37,6 +40,7 @@ rows = [{ "item_id": "nest_pendulum", "display_name": "探巢摆钟", "dimension_tags": ["risk"], +"desc": "真视 3×3", "effect_kinds": ["mine_reveal"], "target": "area_3x3", "magnitude": 0, @@ -48,6 +52,7 @@ rows = [{ "item_id": "catacomb_scroll", "display_name": "地穴全图卷", "dimension_tags": ["info", "plan"], +"desc": "效果失义待重定", "effect_kinds": [], "target": "board_wide", "magnitude": 0, @@ -57,6 +62,7 @@ rows = [{ "item_id": "blast_kit", "display_name": "爆破工兵包", "dimension_tags": ["action", "risk"], +"desc": "AP +4 真视 3×3 移除雷格", "effect_kinds": ["ap_grant", "mine_reveal", "cell_removal"], "target": "area_3x3", "magnitude": 4, @@ -68,47 +74,108 @@ rows = [{ "item_id": "venom_edge", "display_name": "淬毒刃纹", "dimension_tags": ["counter"], -"effect_kinds": ["venom_rider"], +"desc": "克制正面直伤+2", "target": "none", "magnitude": 2, "use_phase": "active_dig", "form": "passive", -"slot_cost": 1 +"slot_cost": 1, +"triggers": [{ +"on": "clamp_check", +"when": [{ +"fact": "matchup", +"op": "eq", +"value": "pierce_vs_ward" +}], +"do": { +"kind": "modify_ticket", +"delta_from": "magnitude", +"mode": "clamp_bonus" +} +}, { +"on": "clamp_check", +"when": [{ +"fact": "matchup", +"op": "eq", +"value": "magic_vs_heavy" +}], +"do": { +"kind": "modify_ticket", +"delta_from": "magnitude", +"mode": "clamp_bonus" +} +}] }, { "item_id": "flag_burst", "display_name": "标中殉爆", "dimension_tags": ["action"], -"effect_kinds": ["flag_burst"], +"desc": "每标中一旗全体敌受2", "target": "none", "magnitude": 2, "use_phase": "active_dig", "form": "passive", -"slot_cost": 1 +"slot_cost": 1, +"triggers": [{ +"on": "flag_hit", +"do": { +"kind": "open_ticket", +"value_from": "magnitude", +"channel": "blast" +} +}] }, { "item_id": "nest_counter", "display_name": "惊巢反制", "dimension_tags": ["risk"], -"effect_kinds": ["mine_hit_halve", "reflect_damage"], +"desc": "踩雷惩罚减半+反弹2", "target": "none", "magnitude": 2, "use_phase": "active_dig", "form": "passive", -"slot_cost": 1 +"slot_cost": 1, +"triggers": [{ +"on": "mine_hit", +"do": { +"kind": "damage_mult", +"value": 50 +} +}, { +"on": "mine_hit", +"do": { +"kind": "open_ticket", +"value_from": "magnitude", +"channel": "reflect" +} +}] }, { "item_id": "dawn_order", "display_name": "工兵晨令", "dimension_tags": ["action"], -"effect_kinds": ["ap_rounds_front"], +"desc": "前3轮每轮AP+1", "target": "none", "magnitude": 1, "use_phase": "active_dig", "form": "passive", "slot_cost": 1, -"effective_rounds": 3 +"effective_rounds": 3, +"triggers": [{ +"on": "round_start", +"when": [{ +"fact": "round_index", +"op": "lte", +"value_from": "effective_rounds" +}], +"do": { +"kind": "grant_ap", +"mode": "queue", +"value_from": "magnitude" +} +}] }, { "item_id": "dig_charge", "display_name": "掘进蓄能", "dimension_tags": ["action"], +"desc": "积点换AP+1", "effect_kinds": ["ap_charge_safe"], "target": "none", "magnitude": 1, @@ -116,21 +183,37 @@ rows = [{ "form": "active_charged", "slot_cost": 1, "charges_per_adventure": 2, -"charge_n": 3 +"charge_n": 3, +"triggers": [{ +"on": "treasure_used", +"do": { +"kind": "counter_spend", +"counter_id": "dig_charge_bank", +"value": 1 +} +}] }, { "item_id": "luck_coin", "display_name": "偏运硬币", "dimension_tags": ["risk"], -"effect_kinds": ["reward_weight_bias"], +"desc": "稀有资源权重+2", "target": "none", "magnitude": 2, "use_phase": "active_dig", "form": "passive", -"slot_cost": 1 +"slot_cost": 1, +"triggers": [{ +"on": "reward_roll", +"do": { +"kind": "bias_rewards", +"value_from": "magnitude" +} +}] }, { "item_id": "pick_refresh", "display_name": "三选一刷新件", "dimension_tags": ["info"], +"desc": "三选一刷新+1", "effect_kinds": ["bonus_refresh"], "target": "none", "magnitude": 1, diff --git a/game/presentation/curio_interact_controller.gd b/game/presentation/curio_interact_controller.gd index 1657f04c..4cf3b92a 100644 --- a/game/presentation/curio_interact_controller.gd +++ b/game/presentation/curio_interact_controller.gd @@ -191,27 +191,19 @@ func _curio_card_texts(candidates: Array, treasure_detail: Array = []) -> Array: return out -# 道具件卡片行(掷定前置——具体道具名 + 效果摘要;空 iid = 无预掷回退态显示通用文案) +# 道具件卡片行(掷定前置——具体道具名 + 效果摘要;空 iid = 无预掷回退态显示通用文案)。 +# 内容数据驱动扩展批 T4 文案表值化:效果摘要=数据行 desc 字段直读(零 kind 硬编码映射—— +# 新件写行即有名有姓) func _treasure_card_line(iid: String) -> String: if iid == "": return "" var row: Dictionary = db.treasure_item_row(iid) if row.is_empty(): return "" - var parts: Array[String] = [] - for k in (row.get("effect_kinds", []) as Array): - if String(k) == "mine_reveal": - parts.append("真视") - elif String(k) == "reward_preview": - parts.append("预读") - elif String(k) == "ap_grant": - parts.append("AP +%d" % int(row.get("magnitude", 0))) - var tgt := String(row.get("target", "none")) - if tgt == "area_3x3": - parts.append("3×3") - elif tgt == "board_wide": - parts.append("全图") - return "%s · %s" % [String(row.get("display_name", iid)), " ".join(parts)] + var desc := String(row.get("desc", "")) + if desc != "": + return "%s · %s" % [String(row.get("display_name", iid)), desc] + return String(row.get("display_name", iid)) # 三选一刷新(迭代 11 第①批 T5b X/Y 双源 + 2026-10-06 用户令对齐商店 MC-1——免费位用尽转 GP 付费档: diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index a1001955..37579de6 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -1542,10 +1542,10 @@ func _smoke_global_variant() -> void: var base_sum: int = eco._pool_weight(base_rows) check(base_sum == 77, "偏斜基线:正向池现行合计 77(挖掘风险批口径)") eco.board_skew = 0 - var off_rows: Array = eco._biased_cell_rows() + var off_rows: Array = eco._biased_cell_rows(0) # 内容数据驱动扩展批 T4:偏运权重改 reward_roll 订阅命中入参(luck_bias 注入位退役) check(off_rows.size() == base_rows.size() and _rows_weights_equal(off_rows, base_rows), "off 态零扰动:board_skew=0 行集与基线逐位相同(不进投影域)") eco.board_skew = 30 - var on_rows: Array = eco._biased_cell_rows() + var on_rows: Array = eco._biased_cell_rows(0) check(eco._pool_weight(on_rows) == 77, "合计守恒:偏斜后正向池合计 = 77(整数最大余数重归一)") var wmap := _rows_weight_map(on_rows) check(wmap.get("weaken", -1) == 8 and wmap.get("ap_up", -1) == 6 and wmap.get("gp_coin", -1) == 2, @@ -1554,16 +1554,14 @@ func _smoke_global_variant() -> void: check(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, "进攻族上浮:attack 14→15 / blast 16→18 / pierce 8→9 / magic 8→9(v=30 构造例,等比上浮守恒)") eco.board_skew = 30 - eco.luck_bias = 2 - var combo_rows: Array = eco._biased_cell_rows() + var combo_rows: Array = eco._biased_cell_rows(2) # 件级加算 2=偏运硬币行 value(reward_roll bias_rewards 命中面同值) var cw := _rows_weight_map(combo_rows) check(eco._pool_weight(combo_rows) == 77 + 4 * 2, "合成序:图级偏斜先(乘法面)件级 luck_bias 后(加法 +2×四稀有类)——合计 77+8") check(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, "合成序构造例:skew(heal 3)+2=5 / gp 2+2=4 / pierce 9+2=11 / magic 9+2=11(图级定型后玩家构筑叠加)") - eco.luck_bias = 0 eco.board_skew = 50 - var v50: Dictionary = _rows_weight_map(eco._biased_cell_rows()) - check(eco._pool_weight(eco._biased_cell_rows()) == 77 and int(v50.get("heal_minor", -1)) > 0, "域上界 v=50:守恒成立且逐行权重恒正(域 [0,50] 安全)") + var v50: Dictionary = _rows_weight_map(eco._biased_cell_rows(0)) + check(eco._pool_weight(eco._biased_cell_rows(0)) == 77 and int(v50.get("heal_minor", -1)) > 0, "域上界 v=50:守恒成立且逐行权重恒正(域 [0,50] 安全)") # 普通图摇号全链路确定性(off 路径)——同种子普通图资源映射逐位相同(golden 对拍另有批前快照) var nrng = load("res://core/rng_service.gd").new() nrng.set_master_seed(42)