秘宝六件迁移+掘进蓄能受理闸数据化+令牌兑付 match 收敛进效果类型(T4+T5——内容数据驱动扩展批)

T4 秘宝侧(六件按 id 硬编码全部拆除,行为与迁移前逐位一致):
- 淬毒刃纹:begin_battle attune_venom 注入+伤害控制器特判分支拆除——行 triggers clamp_check
  modify(mode:clamp_bonus) 双订阅(pierce_vs_ward / magic_vs_heavy eq 行=旧「pierce|magic × 矩阵>0」
  的精确集;mode 路由=主角攻击面专享,棋盘克制窗只消费无模式 delta——棋盘不吃毒面保持)。
- 偏运硬币:reward_economy.luck_bias 注入拆除——行 triggers reward_roll bias_rewards 订阅,
  发射点命中合计经 _biased_cell_rows 入参(合成序「图级先件级后」不变);冒烟偏斜段断言面随迁。
- 工兵晨令:编排器两处直调入队拆除——行 triggers round_start grant_ap(mode:queue) 订阅,
  when round_index ≤ effective_rounds(when value_from 行字段定影——新查询面,悬空校验同面补齐)。
- 惊巢反制:_grant_routed 按 id 减半特判拆除——mine_hit damage_mult(50) 订阅命中经 halve_pct
  入参(ceil(power×50%) 与旧 ceil(power/2) 逐位一致)+ open_ticket(channel:reflect) 订阅施加。
- 标中殉爆:flag_hit 按 id 开单特判拆除——行 triggers open_ticket(channel:blast, value_from
  magnitude) 订阅施加(施加位=迁移前开单位:殉爆→净化→图谱)。
- 掘进蓄能:use_treasure 按 id 受理闸特判拆除——treasure_used counter_spend 订阅施加(消耗成功
  才受理、积点不足拒绝零消耗,进度文案逐字保留);积点账本迁效果引擎计数器 dig_charge_bank
  (模判定留编排器侧:「每 N 格积 1 点」在发射点施加架构下无法行内表达先增后判——when 于
  dispatch 预评估,逐位等价优先;词汇表边界注记留档,遗留观察项随出口报告呈报)。
- pick_refresh:adventure_runner 按 id 特判拆除——effect_kinds bonus_refresh kind 分发查询
  (装配集行 magnitude 合计;刷新件类加件零 id 分支)。
- 主动件 kind 词族换代(reveal_tiles/preview/grant_ap/remove_mines/refresh_pick)挂账保留:
  本批只要求 pick_refresh 合流;全词族 rename 波及库存/呈现/单测零行为收益,不随批(出口报告
  遗留人闸项呈报)。
- curio_interact_controller 秘宝卡片文案表值化:kind 硬编码映射拆除,读行 desc——13 件 desc
  补齐(措辞按现行卡片文本逐字迁移;掘进蓄能/失义件补实际摘要=文案微调呈报级)。
- schema 适配:triggers 与 effect_kinds 互斥校验收窄至 passive 行(active_charged 双形合法=
  effect_kinds 主动分发+triggers 受理闸);trigger 行豁免「非 N 值 kind magnitude 应 0」与
  「非 ap_rounds_front 行 effective_rounds 应 0」两面(行字段=value_from 取数源)。

T5 令牌/树收尾:
- _apply_curio_pick match 收敛进效果类型:属性值提升四类(attack/max_hp/armor/ap)改
  adventure_table.CURIO_APPLY_ROUTES 声明路由(加算落点键,与效果引擎加法族同构)——加新加算类
  令牌=表行+配额/件值键零代码;gp/heal/refresh/relic 副作用通道面显式分支保留。
- meta_tree 规则键同面:先手压制/稳健拓荒两键已在 T3/T4 随 round_start/damage_taken 订阅行
  走效果引擎同面(rebuild 合成订阅+发射点施加),旧直调零残留。

验证:冒烟 800 项 0 失败(三闸过)· 单元 17 套全绿。

设计权威:design/research/研究-内容数据驱动扩展架构-2026-10-06.md(案乙)。
任务书:design/research/任务书-内容数据驱动扩展批-2026-10-06.md §六 T4/T5、§五映射表秘宝侧。
This commit is contained in:
DKChan committed 2026-10-06 23:54:58 +08:00
1 parent e28a6cb3c4
commit a33ddaf542
10 files changed
+215 -111

No files matched your search

+16 -13
View File
@@ -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:
+32 -38
View File
@@ -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
+5 -8
View File
@@ -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
+17 -2
View File
@@ -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
+12 -9
View File
@@ -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)
+17
View File
@@ -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]:
+10 -8
View File
@@ -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]})
+94 -11
View File
@@ -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,
+7 -15
View File
@@ -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 付费档:
+5 -7
View File
@@ -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)