Merge branch 'master' into m4iter14-narrow-20261005

This commit is contained in:
DKChan committed 2026-10-05 08:36:32 +08:00
commit 74678d80bc
54 files changed
+1288 -2023

No files matched your search

+23 -23
View File
@@ -80,8 +80,8 @@ var _save_manager = null # 断点承载注入(迭代 8 卡①
var _resume_bp := {} # 续玩快照暂存(resume_adventure → _start_current_level 单跳传递)
# ---- 三选一停点态(迭代 10 奖励重构批)----
var _bonus_taken := {} # 件获取次数计数(kind 维度派生账本——断点附随件与摘要读数承载;池库存正主=_bonus_deck)
var _bonus_deck: Array = [] # 令牌池(TP0 v2.2——持久多重集,元素 {"kind","tier"};洗牌抽候选/选中踢池未选回池/池耗即枯竭;商店货架同库共账;迭代 12 T1 起机制件带=relic 令牌随表追加)
var _relics: Array[String] = [] # 流派件持有集(迭代 12 T1——局内随机渠道获得、冒险级跨关携带、收束清零;🔴 层级纪律承载面:不入同调位不入局外树)
var _bonus_deck: Array = [] # 令牌池(TP0 v2.2——持久多重集,元素 {"kind","tier"};洗牌抽候选/选中踢池未选回池/池耗即枯竭;商店货架同库共账;迭代 12 T1 起规则改写型特性带=relic 令牌随表追加)
var _relics: Array[String] = [] # 特性持有集(迭代 12 T1——局内随机渠道获得、冒险级跨关携带、收束清零;🔴 层级纪律承载面:不入同调位不入局外树)
var _tree_rules: Array = [] # 树 rule 键集(迭代 12 T10——Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面)
var _bonus_pending := {} # 停点态:{"candidates": Array, "payload": Dictionary, "chosen"/"incoming": 替换在途}——空 = 无停点
var _bonus_result := {} # 已兑付待推进态(bonus_resolved 载荷副本——中断续玩不重兑付)
@@ -128,8 +128,8 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive(迭代 11 第①批 T5)
_attune_refresh_y = maxi(0, int(params.get("attune_refresh_y", 0))) # additive(迭代 11 第①批 T5b——Y 冒险周期池)
_seed_attune_charges() # 迭代 11 第①批 T6——冒险开始充能账本按装备主动件重置(每冒险充能语义)
# 流派件开局注入(迭代 12 T7——MC 成型画像载体:params.relics_seed 非空=开局持有集〔七流派
# 各 2 件档一成型——测量面专用;真实局流派件只走局内随机渠道,红线②层级纪律不破〕)
# 特性开局注入(迭代 12 T7——MC 成型画像载体:params.relics_seed 非空=开局持有集〔七流派
# 各 2 件档一成型——测量面专用;真实局规则改写型特性只走局内随机渠道,红线②层级纪律不破〕)
var relic_seed: Array = params.get("relics_seed", []) if params.get("relics_seed", []) is Array else []
_tree_rules = params.get("tree_rules", []) if params.get("tree_rules", []) is Array else [] # 迭代 12 T10——A/B 面 rule 键集
if not relic_seed.is_empty():
@@ -190,7 +190,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
rtok["relic_id"] = String(tok.get("relic_id", ""))
restored.append(rtok)
_bonus_deck = restored
# 流派件持有集还原(迭代 12 T1——缺键 = 旧版断点防御空集)
# 特性持有集还原(迭代 12 T1——缺键 = 旧版断点防御空集)
_relics = []
for r in (bp.get("relics", []) as Array):
_relics.append(String(r))
@@ -267,14 +267,14 @@ func _reset_bonus_state() -> void:
_node_bonus_pending = {}
# 令牌池构建(TP0 v2.2——按表 kind×tier 配额展开为持久多重集;构建序确定 = BONUS_IDS×TIERS 逐层,
# 随机性只发生在洗牌消费 reward_pick 封闭流的时刻。迭代 12 T1:机制件带=relic 活行每件 1 张追加
# 令牌池构建(TP0 v2.2——按表 kind×tier 配额展开为持久多重集;构建序确定 = FEATURE_KINDS×TIERS 逐层,
# 随机性只发生在洗牌消费 reward_pick 封闭流的时刻。迭代 12 T1:规则改写型特性带=relic 活行每件 1 张追加
# (rarity common/rare→稀有令牌档、rarest→罕见令牌档——relic 永不入普通档;空表=零追加零扰动))
func _build_bonus_deck() -> Array:
var deck: Array = []
for kind in db.adventure.BONUS_IDS:
for kind in db.adventure.FEATURE_KINDS:
for tier in db.adventure.TIERS:
for i in int(db.adventure.bonus_token_count(String(kind), String(tier))):
for i in int(db.adventure.feature_quota(String(kind), String(tier))):
deck.append({"kind": String(kind), "tier": String(tier)})
for row in db.relic_rows():
var rtier := "rarest" if String(row.get("rarity", "")) == "rarest" else "rare"
@@ -282,7 +282,7 @@ func _build_bonus_deck() -> Array:
return deck
# 令牌唯一身份(抽签去重/兑付匹配共用——数值件=kind,机制件=relic_id;同档多张不同 relic 可同屏)
# 令牌唯一身份(抽签去重/兑付匹配共用——属性加成型特性=kind,规则改写型特性=relic_id;同档多张不同 relic 可同屏)
func _token_identity(tok: Dictionary) -> String:
if String(tok.get("kind", "")) == "relic":
return "relic:" + String(tok.get("relic_id", ""))
@@ -337,7 +337,7 @@ func continue_to_next_level() -> bool:
# ---- 提升三选一(迭代 10 奖励重构批——选择停点零自动推进、重复兑付被拒;兑付恰一件、未选项零生效)----
# 候选令牌匹配(身份键恒等——数值件 kind+tier、机制件 relic_id;迭代 12 T1 机制件带)
# 候选令牌匹配(身份键恒等——属性加成型特性 kind+tier、规则改写型特性 relic_id;迭代 12 T1 规则改写型特性带)
func _find_token(candidates: Array, kind: String, tier: String, relic_id: String = "") -> Dictionary:
for t in candidates:
var tok: Dictionary = t as Dictionary
@@ -351,7 +351,7 @@ func _find_token(candidates: Array, kind: String, tier: String, relic_id: String
# 玩家点选兑付(呈现层/冒烟/自动模拟确认入口):令牌 ∈ 停点候选(身份键精确匹配——relic 件
# 另须 relic_id)。属性件写加算区、AP 件写第四轴、GP 件走钱包「奖励入账」入口、恢复件当即回血、
# relic 件入流派件持有集(🔴 层级纪律:只走局内随机渠道)、道具件受同调位约束(随池种退役不可达)。
# relic 件入特性持有集(🔴 层级纪律:只走局内随机渠道)、道具件受同调位约束(随池种退役不可达)。
# 消费=选中令牌踢池(未选回池由「抽签不摘牌、兑付才摘牌」语义承载——TP0 v2.2)。
func pick_bonus(kind: String, tier: String = "", relic_id: String = "") -> bool:
if state != "running" or _bonus_pending.is_empty():
@@ -361,7 +361,7 @@ func pick_bonus(kind: String, tier: String = "", relic_id: String = "") -> bool:
if _find_token(candidates, kind, tier, relic_id).is_empty():
push_error("[冒险结构] pick_bonus 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id])
return false
var value: int = int(db.adventure.bonus_value(kind, tier))
var value: int = int(db.adventure.feature_value(kind, tier))
var hp_before := int(ledger.get("hero_hp", 0))
if kind == "aux_item":
return _pick_aux_item(value) # 道具件分派通关渠道(持有容器 = 跨关携带——与节点渠道分轨;池种退役后不可达)
@@ -435,7 +435,7 @@ func _consume_deck_token(kind: String, tier: String, relic_id: String = "") -> b
return false
# 流派件持有集读面(效果接线/求值/摘要唯一取数口——副本防外部改写)
# 特性持有集读面(效果接线/求值/摘要唯一取数口——副本防外部改写)
func relics_held() -> Array:
return _relics.duplicate()
@@ -457,10 +457,10 @@ func pool_stock(kind: String) -> int:
# 写加算区+账本+摘牌(与奖励三选一真单点——买一件=奖励面少一件的库存语义由同库成立)。
# 未知 kind 拒绝(货架词表封闭的前置防御)。
func _consume_pool_purchase(kind: String, tier: String) -> bool:
if not db.adventure.BONUS_IDS.has(kind):
if not db.adventure.FEATURE_KINDS.has(kind):
push_error("[冒险结构] 商店购买消费拒绝——件不在池词表: %s" % kind)
return false
return _apply_bonus_pick(kind, tier, int(db.adventure.bonus_value(kind, tier)))
return _apply_bonus_pick(kind, tier, int(db.adventure.feature_value(kind, tier)))
# 终局池快照(_settle 清账前调用——_reset_bonus_state 会重置 _bonus_taken,摘要 additive 键
@@ -552,7 +552,7 @@ func apply_node_bonus(kind: String, tier: String = "", relic_id: String = "") ->
if _find_token(candidates, kind, tier, relic_id).is_empty():
push_error("[冒险结构] apply_node_bonus 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id])
return false
var value: int = int(db.adventure.bonus_value(kind, tier))
var value: int = int(db.adventure.feature_value(kind, tier))
if kind == "aux_item":
return _apply_node_aux(value) # 道具件分派节点渠道(持有容器 = 关卡域库存——与通关渠道分轨;池种退役后不可达)
if not _apply_bonus_pick(kind, tier, value, relic_id):
@@ -573,7 +573,7 @@ func _apply_node_aux(count: int) -> bool:
var records: Array = []
if _runner != null and _runner.aux_inventory != null:
records = _runner.aux_inventory.holding_records()
# 迭代 11 第①批 T7——守卫随过渡键退役拆除(BONUS_IDS 已除名 aux_item,本分支不可达;
# 迭代 11 第①批 T7——守卫随过渡键退役拆除(FEATURE_KINDS 已除名 aux_item,本分支不可达;
# 替换子交互路由随容量闸一并退役——能力件容量闸正主 = 营地装配面满位禁选)
if _runner != null:
for iid in picked:
@@ -739,7 +739,7 @@ func _draw_bonus_candidates() -> Array:
if ident.begins_with("relic:"):
cand["relic_id"] = String((tok as Dictionary).get("relic_id", ""))
out.append(cand)
if out.size() >= int(db.adventure.bonus_candidate_count):
if out.size() >= int(db.adventure.feature_candidate_count):
break
return out
@@ -828,7 +828,7 @@ func _start_current_level() -> bool:
if ok:
_runner.set_attunement(_attunement) # 迭代 11 第①批 T5——同调装配下传(enc 携带进战斗结算)
_runner.set_attune_charges(attune_charges) # 迭代 11 第①批 T6——充能账本下传(能力件骑库存面注入)
_runner.set_relics(_relics) # 迭代 12 T1——流派件持有集下传(begin_battle enc.relics 求值面)
_runner.set_relics(_relics) # 迭代 12 T1——特性持有集下传(begin_battle enc.relics 求值面)
_runner.set_tree_rules(_tree_rules) # 迭代 12 T10——树 rule 键集下传(A/B 面消费)
if _shop_mode == "pool":
# 商店共享池注入(迭代 11 第②批 T3——库存账本引用+消费 handler 双下传;legacy 模式零注入零扰动)
@@ -908,7 +908,7 @@ func _settle(result: String) -> void:
"aux_cleared_last": _last_aux_cleared, # additive 扩签(迭代 7 T4——末关清空反馈;非末关走 bonus 载荷)
"keystone_drops": _keystone_drops.duplicate(true), # additive 扩签(迭代 10——逐杀掉落明细;settle 入账明细/复盘归因数据源)
"pool_stock": _pool_stock_snapshot(), # additive 扩签(迭代 11 第②批 T5——终局池读数:清账前快照,商店买走 vs 奖励领走 vs 枯竭的终局面)
"relics": _relics.duplicate(), # additive 扩签(迭代 12 T1——流派件终局持有集:清账前快照,成型判读取数面)
"relics": _relics.duplicate(), # additive 扩签(迭代 12 T1——特性终局持有集:清账前快照,成型判读取数面)
}
# 败退明细(数值验证工具取数面 2026-09-22——tools/balance_verifier.py 止步节点分布数据源;
# 仅失败局附加,键缺省 = 未败退,既有五件事消费方零感知)
@@ -941,7 +941,7 @@ func _forward_choices_presented(candidates: Array) -> void:
# ---- 断点快照(迭代 8 卡①——七字段拍板面+恢复必需附随件)----
# 七字段(拍板面):关卡序号/节点位/生命/本关加成区/GP 钱包/商店已购档/已抽模板;
# 七字段(拍板面):关卡序号/节点位/生命/属性加成区/GP 钱包/商店已购档/已抽模板;
# 附随件(续玩正确性必需,随批注记):主种子+取数计数器(rng 快照契约回填——续接原时间线)、
# 钥石累计(大奖励中途值——终局摘要五件事不失真)、已到节点序(呈现层图条重建)、道具库存
# (奖励节点已选件——防静默丢失)。注入缺省 null = 整面静默(模拟/冒烟零扰动)。
@@ -976,7 +976,7 @@ func _snapshot_breakpoint() -> void:
"aux_items": aux_items,
"bonus_taken": _bonus_taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
"bonus_deck": _bonus_deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
"relics": _relics.duplicate(), # 流派件持有集(迭代 12 T1——局内随机渠道获得态续玩防丢失)
"relics": _relics.duplicate(), # 特性持有集(迭代 12 T1——局内随机渠道获得态续玩防丢失)
"bonus_pending": _bonus_pending.duplicate(true), # 三选一停点态(空 = 无停点——选择停点可中断契约)
"bonus_result": _bonus_result.duplicate(true), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
"bonus_aux_carry": _bonus_aux_carry.duplicate(true), # 道具件跨关携带(注入前中断防静默丢失)
+4 -4
View File
@@ -72,7 +72,7 @@ var attunement: Array = [] # 装备中同调件 item_id 集
var _attune_charges_ref: Dictionary = {} # 充能账本共享引用(迭代 11 第①批 T6——use_aux 消费直改)
var _attune_bank := 0 # 掘进蓄能积点(battle 域——每 charge_n 安全格 +1 点)
var _attune_safe_seen := 0 # 掘进蓄能 battle 域安全开格计数
# 流派件运行态(迭代 12 T2——relic 持有集经 enc.relics 注入;求值旗注 damage_controller.relic_flags;
# 特性运行态(迭代 12 T2——relic 持有集经 enc.relics 注入;求值旗注 damage_controller.relic_flags;
# 魔矢每战首次计数 battle 域。空集=零扰动)
var _relic_first_dig_used := false
var _relic_combo := 0 # 矿脉直觉连击计数(battle 域——safe +1/mine 清零)
@@ -94,7 +94,7 @@ func _attune_mag(item_id: String) -> int:
return int(_attune_row(item_id).get("magnitude", 0))
# 流派件求值(迭代 12 T2——持有集 × relic_table → damage_controller.relic_flags 注入面):
# 特性求值(迭代 12 T2——持有集 × relic_table → damage_controller.relic_flags 注入面):
# rider_flat = Σ rider 件 value(奥术余烬 +1 / 魔能共鸣 +2);magic 档一(≥2 件)rider +1、
# 档二(≥4 件)rider 可暴击;clamp 两件直调键;first_dig_spell 旗(魔能共鸣第二效果——魔矢值
# = 件 effects.value)。空集/空表 = 零旗零扰动。
@@ -247,7 +247,7 @@ func begin_battle(enc: Dictionary) -> bool:
_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
# 流派件运行态注入(迭代 12 T2 魔法流——enc.relics 携带持有集;RelicsEngine 求值后注伤害控制器
# 特性运行态注入(迭代 12 T2 魔法流——enc.relics 携带持有集;RelicsEngine 求值后注伤害控制器
# relic_flags;空集/空表=零扰动。魔矢每战首次开格计数同步归零)
_relic_flags_for((enc.get("relics", []) as Array))
_relic_first_dig_used = false
@@ -680,7 +680,7 @@ func set_aux_unlocked(ids: Dictionary) -> void:
func _grant_routed(snap: Dictionary, polarity: String, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "") -> bool:
# 迭代 11 第①批 T7——aux 授予分支随掉落/停点渠道退役拆除(reward 词表第十型 aux_item 保留但零投放:
# 无 aux 行入池、BONUS_IDS 词表已除名);剩余类型走极性字典路由。
# 无 aux 行入池、FEATURE_KINDS 词表已除名);剩余类型走极性字典路由。
var row: Dictionary = _reward_economy.polarity_row(String(snap.get("cell_type", "")), polarity)
if row.is_empty():
_fatal("极性字典缺行: %s %s" % [String(snap.get("cell_type", "")), polarity])
+2 -2
View File
@@ -27,7 +27,7 @@ const TABLE_MANIFEST := {
"aux_items": "aux_items.tres", # 迭代 7 T1 魔法道具内容批(AuxItemTable 六行——定义表 Rule 4 豁免;罗盘 retired/失义件 deferred 标记见 schema 头注)
"adventure": "adventure.tres", # 冒险结构实现批 2026-09-21(AdventureTable 建表——大奖励与钥石行值单点)
"attunement_growth": "attunement_growth.tres", # 迭代 11 第①批 同调实施批 T1(AttunementGrowthTable 单行全局——同调位里程碑谓词表;单行先例 Rule 4 豁免)
"relics": "relics.tres", # 迭代 12 T1 双层 BD 机制件基建(RelicTable 定义表 Rule 4 豁免——AuxItemTable 先例;空表合法态=主时间线零扰动)
"relics": "relics.tres", # 迭代 12 T1 双层 BD 规则改写型特性基建(RelicTable 定义表 Rule 4 豁免——AuxItemTable 先例;空表合法态=主时间线零扰动)
}
var action_economy: ActionEconomyTable
@@ -154,7 +154,7 @@ func relic_row(relic_id: String) -> Dictionary:
return relics.row_by_id(relic_id) if relics != null else {}
# 机制件带令牌构建源(池接线——adventure_runner._build_bonus_deck 消费;行 rarity→投放档映射)
# 规则改写型特性带令牌构建源(池接线——adventure_runner._build_bonus_deck 消费;行 rarity→投放档映射)
func relic_rows() -> Array:
return relics.rows if relics != null else []
+5 -5
View File
@@ -99,18 +99,18 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void:
for e in validate_skill_pool_links(skt, en, loaded.get("status_effects")):
push_diag.call("error", e.category, e.message)
# ⑥ 商店货架 × 冒险共享池跨表校验(M-4 迭代 11 第②批 T1——次数上限域):
# pool_shelf kind 须命中 adventure.BONUS_IDS 且为有限库存种(cap ∈ [1,99]——gp 无上限种/牌库外种
# 入货架 = 库存账本无锚,加载致命;gp 袋负交易切边/机制件三轴切边由 ShopTable 词表封闭另拦)
# pool_shelf kind 须命中 adventure.FEATURE_KINDS 且为有限库存种(cap ∈ [1,99]——gp 无上限种/牌库外种
# 入货架 = 库存账本无锚,加载致命;gp 袋负交易切边/规则改写型特性三轴切边由 ShopTable 词表封闭另拦)
var adv = loaded.get("adventure")
var shp = loaded.get("shop")
if adv != null and shp != null:
for row in shp.pool_shelf:
var kind: String = String(row.get("kind_id", ""))
if not adv.BONUS_IDS.has(kind):
push_diag.call("error", "shelf_dangling_kind", "ShopTable.pool_shelf kind %s 不在 AdventureTable.BONUS_IDS(共享池牌库外种)" % kind)
if not adv.FEATURE_KINDS.has(kind):
push_diag.call("error", "shelf_dangling_kind", "ShopTable.pool_shelf kind %s 不在 AdventureTable.FEATURE_KINDS(共享池牌库外种)" % kind)
continue
# TP0 令牌池——货架件须为有限令牌种(配额 >0;种类次数上限退役后库存=剩余令牌数)
var quota := int(adv.bonus_token_count(kind, adv.TIER_COMMON)) + int(adv.bonus_token_count(kind, adv.TIER_RARE)) + int(adv.bonus_token_count(kind, adv.TIER_RAREST))
var quota := int(adv.feature_quota(kind, adv.TIER_COMMON)) + int(adv.feature_quota(kind, adv.TIER_RARE)) + int(adv.feature_quota(kind, adv.TIER_RAREST))
if quota < 1:
push_diag.call("error", "shelf_zero_quota", "ShopTable.pool_shelf kind %s 令牌配额为 0(牌库空种入货架 = 库存无锚)" % kind)
+2 -2
View File
@@ -60,7 +60,7 @@ var _outcomes := {} # ticket_id → "redeemed"/"zero"/"voided"(
var _seq := 0 # 流水号
var _halted := false # 主角倒下终止旗(死亡事件置位——flush 余单作废)
var _hero_ac_override := -1 # 主角护甲等级合流入参(begin_battle 显式设定——-1 = 表值兜底,口径同战斗编排器)
# 流派件求值旗(迭代 12 T2——begin_battle 编排器经 RelicsEngine 求值后注入;空字典=零扰动。
# 特性求值旗(迭代 12 T2——begin_battle 编排器经 RelicsEngine 求值后注入;空字典=零扰动。
# rider_flat=英雄侧必中附加值〔奥术余烬族〕/rider_crit=档二 rider 可暴击/first_dig_spell=每战首次
# 开安全格追加魔法矢〔消费在编排器授予段〕/clamp_magic_light·clamp_magic_heavy=克制点直调)
var relic_flags: Dictionary = {}
@@ -418,7 +418,7 @@ func _counter_delta(attack_type: String, armor: String) -> int:
return int(row.get(armor, 0))
# 克制点直调(迭代 12 T2 魔法流/T4 穿刺流 clamp_modifier 件——克制矩阵查表值后的流派件调整;
# 克制点直调(迭代 12 T2 魔法流/T4 穿刺流 clamp_modifier 件——克制矩阵查表值后的特性调整;
# 空旗=0 零扰动):魔网织者=magic vs magic(矩阵罚 −1 → 0/正);湛蓝誓约=magic vs heavy(+1 → +2);
# 猎手刺剑=pierce vs light(+1——对轻甲敌穿刺提升,对重甲无增益=正负双修)。hero_attack 与
# board_typed 两消费面同享。
+5 -5
View File
@@ -590,7 +590,7 @@ func set_attunement(ids: Array) -> void:
_attunement = ids.duplicate()
# 流派件持有集注入(迭代 12 T1——AdventureRunner._start_current_level 唯一调用方;begin_battle
# 特性持有集注入(迭代 12 T1——AdventureRunner._start_current_level 唯一调用方;begin_battle
# enc.relics 携带进战斗求值;**共享引用不复制**——关内奖励节点兑付的 relic 须当关后续战斗生效
# (attune_charges 共享账本同款);空集=零扰动)
var _relics: Array = []
@@ -600,7 +600,7 @@ func set_relics(ids: Array) -> void:
_relics = ids
# 流派件持有集读面(迭代 12 呈现补齐——HUD C 区流派件段数据源)
# 特性持有集读面(迭代 12 呈现补齐——HUD C 区特性段数据源)
func relics_held() -> Array:
return _relics.duplicate()
@@ -779,7 +779,7 @@ func _shop_draw_shelf() -> void:
_shop_shelf.append(tok)
if _shop_shelf.size() >= 3:
break
# 商店保底(迭代 12 T6——「本关变异交互件必刷」简化口径:货架零流派件时**挤掉最后一张普通卡**
# 商店保底(迭代 12 T6——「本关变异交互件必刷」简化口径:货架零特性时**挤掉最后一张普通卡**
# 换吞噬流件〔地形交互件广义映射——按关变异精细绑定归数值批,台账披露待追认〕;占位制货架恒
# ≤3;池内无 devour 令牌=不保底合法态)
var has_relic := false
@@ -834,10 +834,10 @@ func shop_shelf() -> Array:
var tier := String((tok as Dictionary).get("tier", ""))
var row: Dictionary = db.shop_pool_row(kind)
var display := String(row.get("display_name", kind))
var value := int(db.adventure.bonus_value(kind, tier))
var value := int(db.adventure.feature_value(kind, tier))
if row.is_empty() and kind == "relic":
var rrow: Dictionary = db.relic_row(String(tok.get("relic_id", "")))
display = String(rrow.get("display_name", "流派件"))
display = String(rrow.get("display_name", "特性"))
value = 0
out.append({
"kind_id": kind,
+61 -52
View File
@@ -13,9 +13,18 @@
class_name AdventureTable
extends Resource
## 概念指路:本表的属性值提升与资源,与 relics.tres 的专长,同属一个对象——
## 「奇物」(一次冒险内各渠道给予玩家的增益总称)。定义见 design/gdd/systems/冒险循环/奇物.md。
## 它们是它的分类轴上的枚举(专长 / 属性值提升 / 资源),不是三套系统;
## 本表承载属性值提升与资源的种类配额与件值。
## (2026-10-05 命名统一 + v2.0 改名:旧称「数值件」→ 属性提升;顶层「特性」→「奖励」→「奇物」。
## 常量与字段随之前缀 feature_*,**尚未**随顶层改名换到 reward_*——见 06-待办清单。
## 分类轴三个枚举:属性值提升〔生命上限 / AC / 攻击〕;资源〔行动点上限 /
## 刷新次数 / 金币 / 回复,命名待定〕——本表两类混编;刷新次数与专长获取两项尚未入表。
## 类内异质性:金币与回复是即时兑付,不进持有集、不持续生效。)
## 三选一提升池 kind 词表(封闭——断点附随件与牌库剔除态依赖它;扩件走本表+registry 修订)。
## aux_item 池种退役(迭代 11 第①批 T7——同调批出池);heal 恢复件新入池(v2.2 令牌池追认 b)。
const BONUS_IDS: Array[String] = ["attack", "max_hp", "armor", "gp", "ap", "heal"]
const FEATURE_KINDS: Array[String] = ["attack", "max_hp", "armor", "gp", "ap", "heal"]
## 令牌档位词表(v2.2 三档位——稀有度命名对齐 MC 策略判读序 316-f:1 级最稀有=rarest 优先)
const TIERS: Array[String] = ["common", "rare", "rarest"]
const TIER_COMMON := "common"
@@ -30,65 +39,65 @@ const TIER_QUOTAS := {"common": 70, "rare": 25, "rarest": 3}
@export var clear_heal_pct: int = 20 # [0,100] knob——回复生效上限的百分比,封顶上限不回满
# —— 三选一候选数(2026-10-02 修复批 T2 魔法数结构化)——
@export var bonus_candidate_count: int = 3 # [1,6] knob——令牌洗牌不放回抽取张数上界
@export var feature_candidate_count: int = 3 # [1,6] knob——令牌洗牌不放回抽取张数上界
@export var shop_refresh_free: int = 0 # [0,9] knob(MC-1 provisional——进店免费刷新次数;基础 0=Q2 裁,天赋增益位预留)
@export var shop_refresh_cost_base: int = 20 # [1,999] knob(MC-1 provisional——GP 刷新基价)
@export var shop_refresh_cost_curve: float = 1.8 # [1.0,4.0] knob(MC-1 provisional——按已付费刷新数幂曲线,A 链同族)
# —— 令牌配额(kind × tier 计数;未列组合 = 0——heal 只入稀有/ap 只入罕见,v2.2 档位构成)——
@export var token_attack_common: int = 18 # [0,99]
@export var token_attack_rare: int = 5 # [0,99]
@export var token_max_hp_common: int = 17 # [0,99]
@export var token_max_hp_rare: int = 5 # [0,99]
@export var token_armor_common: int = 17 # [0,99]
@export var token_armor_rare: int = 5 # [0,99]
@export var token_gp_common: int = 18 # [0,99]
@export var token_gp_rare: int = 5 # [0,99]
@export var token_heal_rare: int = 5 # [0,99]——恢复件(新入池种类,当即回血)
@export var token_ap_rarest: int = 3 # [0,99]——AP 件(罕见档;道具件 2 席位随池种退役收窄)
@export var feature_quota_attack_common: int = 18 # [0,99]
@export var feature_quota_attack_rare: int = 5 # [0,99]
@export var feature_quota_max_hp_common: int = 17 # [0,99]
@export var feature_quota_max_hp_rare: int = 5 # [0,99]
@export var feature_quota_armor_common: int = 17 # [0,99]
@export var feature_quota_armor_rare: int = 5 # [0,99]
@export var feature_quota_gp_common: int = 18 # [0,99]
@export var feature_quota_gp_rare: int = 5 # [0,99]
@export var feature_quota_heal_rare: int = 5 # [0,99]——恢复件(新入池种类,当即回血)
@export var feature_quota_ap_rarest: int = 3 # [0,99]——AP 件(罕见档;道具件 2 席位随池种退役收窄)
# —— 令牌件值(小/大数值对——v2.2 追认 b 定稿:20/50、+1/+2、+1/+2、+5/+10、恢复+6、AP+1)——
@export var bonus_attack_flat: int = 1 # [0,999] knob——小增伤
@export var bonus_attack_flat_large: int = 2 # [0,999] knob——大增伤
@export var bonus_max_hp_flat: int = 5 # [0,999] knob——小生命
@export var bonus_max_hp_flat_large: int = 10 # [0,999] knob——大生命
@export var bonus_armor_flat: int = 1 # [0,999] knob——小护甲
@export var bonus_armor_flat_large: int = 2 # [0,999] knob——大护甲
@export var bonus_gp_amount: int = 20 # [0,999] knob——小GP
@export var bonus_gp_amount_large: int = 50 # [0,999] knob——大GP
@export var bonus_heal_flat: int = 6 # [0,999] knob——恢复(当即回血,封顶生效上限)
@export var bonus_ap_flat: int = 1 # [0,10] knob——AP 件每场战斗首轮行动点基线增量
@export var feature_value_attack_common: int = 1 # [0,999] knob——小增伤
@export var feature_value_attack_rare: int = 2 # [0,999] knob——大增伤
@export var feature_value_max_hp_common: int = 5 # [0,999] knob——小生命
@export var feature_value_max_hp_rare: int = 10 # [0,999] knob——大生命
@export var feature_value_armor_common: int = 1 # [0,999] knob——小护甲
@export var feature_value_armor_rare: int = 2 # [0,999] knob——大护甲
@export var feature_value_gp_common: int = 20 # [0,999] knob——小GP
@export var feature_value_gp_rare: int = 50 # [0,999] knob——大GP
@export var feature_value_heal_rare: int = 6 # [0,999] knob——恢复(当即回血,封顶生效上限)
@export var feature_value_ap_rarest: int = 1 # [0,10] knob——AP 件每场战斗首轮行动点基线增量
## 令牌配额读数(未列 kind×tier 组合 = 0——heal 无普通档/ap 无普通稀有档按 v2.2 构成)
func bonus_token_count(kind: String, tier: String) -> int:
func feature_quota(kind: String, tier: String) -> int:
match [kind, tier]:
["attack", TIER_COMMON]: return token_attack_common
["attack", TIER_RARE]: return token_attack_rare
["max_hp", TIER_COMMON]: return token_max_hp_common
["max_hp", TIER_RARE]: return token_max_hp_rare
["armor", TIER_COMMON]: return token_armor_common
["armor", TIER_RARE]: return token_armor_rare
["gp", TIER_COMMON]: return token_gp_common
["gp", TIER_RARE]: return token_gp_rare
["heal", TIER_RARE]: return token_heal_rare
["ap", TIER_RAREST]: return token_ap_rarest
["attack", TIER_COMMON]: return feature_quota_attack_common
["attack", TIER_RARE]: return feature_quota_attack_rare
["max_hp", TIER_COMMON]: return feature_quota_max_hp_common
["max_hp", TIER_RARE]: return feature_quota_max_hp_rare
["armor", TIER_COMMON]: return feature_quota_armor_common
["armor", TIER_RARE]: return feature_quota_armor_rare
["gp", TIER_COMMON]: return feature_quota_gp_common
["gp", TIER_RARE]: return feature_quota_gp_rare
["heal", TIER_RARE]: return feature_quota_heal_rare
["ap", TIER_RAREST]: return feature_quota_ap_rarest
return 0
## 令牌件值(kind × tier;v2.2 数值对——小/大两档)
func bonus_value(kind: String, tier: String = TIER_COMMON) -> int:
func feature_value(kind: String, tier: String = TIER_COMMON) -> int:
match [kind, tier]:
["attack", TIER_COMMON]: return bonus_attack_flat
["attack", TIER_RARE]: return bonus_attack_flat_large
["max_hp", TIER_COMMON]: return bonus_max_hp_flat
["max_hp", TIER_RARE]: return bonus_max_hp_flat_large
["armor", TIER_COMMON]: return bonus_armor_flat
["armor", TIER_RARE]: return bonus_armor_flat_large
["gp", TIER_COMMON]: return bonus_gp_amount
["gp", TIER_RARE]: return bonus_gp_amount_large
["heal", TIER_RARE]: return bonus_heal_flat
["ap", TIER_RAREST]: return bonus_ap_flat
["attack", TIER_COMMON]: return feature_value_attack_common
["attack", TIER_RARE]: return feature_value_attack_rare
["max_hp", TIER_COMMON]: return feature_value_max_hp_common
["max_hp", TIER_RARE]: return feature_value_max_hp_rare
["armor", TIER_COMMON]: return feature_value_armor_common
["armor", TIER_RARE]: return feature_value_armor_rare
["gp", TIER_COMMON]: return feature_value_gp_common
["gp", TIER_RARE]: return feature_value_gp_rare
["heal", TIER_RARE]: return feature_value_heal_rare
["ap", TIER_RAREST]: return feature_value_ap_rarest
return 0
@@ -104,8 +113,8 @@ func tier_rank(tier: String) -> int:
## 档位令牌总数(货架/校验读面)
func tier_total(tier: String) -> int:
var total := 0
for kind in BONUS_IDS:
total += bonus_token_count(kind, tier)
for kind in FEATURE_KINDS:
total += feature_quota(kind, tier)
return total
@@ -118,18 +127,18 @@ func validate() -> Array:
var errs: Array[Dictionary] = []
if clear_heal_pct < 0 or clear_heal_pct > 100:
errs.append({"category": "domain", "message": "clear_heal_pct 越域 [0,100]: %d" % clear_heal_pct})
for kind in BONUS_IDS:
for kind in FEATURE_KINDS:
for tier in TIERS:
var c := bonus_token_count(kind, tier)
var c := feature_quota(kind, tier)
if c < 0 or c > 99:
errs.append({"category": "domain", "message": "token_count(%s,%s) 越域 [0,99]: %d" % [kind, tier, c]})
var v := bonus_value(kind, tier)
var v := feature_value(kind, tier)
if v < 0 or v > 999:
errs.append({"category": "domain", "message": "bonus_value(%s,%s) 越域 [0,999]: %d" % [kind, tier, v]})
errs.append({"category": "domain", "message": "feature_value(%s,%s) 越域 [0,999]: %d" % [kind, tier, v]})
for tier in TIERS:
var total := tier_total(tier)
if total != int(TIER_QUOTAS.get(tier, -1)):
errs.append({"category": "domain", "message": "档位配额结构失锚(v2.2 追认 b——%s 应=%d 实=%d;改总量须重裁)" % [tier, int(TIER_QUOTAS.get(tier, -1)), total]})
if bonus_candidate_count < 1 or bonus_candidate_count > 6:
errs.append({"category": "domain", "message": "bonus_candidate_count 越域 [1,6]: %d" % bonus_candidate_count})
if feature_candidate_count < 1 or feature_candidate_count > 6:
errs.append({"category": "domain", "message": "feature_candidate_count 越域 [1,6]: %d" % feature_candidate_count})
return errs
+4 -4
View File
@@ -13,8 +13,8 @@
# requires Array[node_id](显式前置——父开子脉内链)/ effects Dictionary(stat 四线 flat:
# atk/max_hp/hit/armor_class;rule 键封闭 RULE_KEYS)/ fork_group/fork_side(a/b——同组互斥、
# 营地免费切换)。
# 🔴 层级纪律(任务书红线 2):局外树只放全局规则件——effects 禁流派标签(relic 流派件永不入树,
# archetype 键出域=加载致命);流派件只走局内随机渠道。
# 🔴 层级纪律(任务书红线 2):局外树只放全局规则件——effects 禁流派标签(relic 特性永不入树,
# archetype 键出域=加载致命);规则改写型特性只走局内随机渠道。
# A/B 只放规则面(定稿 1.3——全树互斥面 ≤10-15% 首期 4 组);两侧「不同强非强弱」MC 验差 <~3pp(T10)。
# 彩蛋 ≤3(定稿 1.4——点满某脉某排免费觉醒;发现率 <5% 改灰色预告——T10 追认单呈报)。
class_name MetaTreeTable
@@ -76,9 +76,9 @@ func validate() -> Array:
for ek in fx.keys():
if not (STAT_KEYS.has(String(ek)) or RULE_KEYS.has(String(ek))):
errs.append({"category": "zero_flat", "message": "%s effects 键出域(四线/rule 词表封闭): %s" % [nid, String(ek)]})
# 🔴 层级纪律:局外树禁流派件(archetype 键出域=加载致命)
# 🔴 层级纪律:局外树禁规则改写型特性(archetype 键出域=加载致命)
if fx.has("archetype"):
errs.append({"category": "load_bearing", "message": "%s effects 含 archetype(🔴 层级纪律——流派件禁入局外树)" % nid})
errs.append({"category": "load_bearing", "message": "%s effects 含 archetype(🔴 层级纪律——特性禁入局外树)" % nid})
# A/B 互斥对结构:fork 行必带 fork_group+side;同组恰 a/b 各一
if kind == "fork":
var fg := String(row.get("fork_group", ""))
+9 -3
View File
@@ -1,8 +1,14 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# Validation Question: 七核心流派各 ≥3 件入局内随机渠道后,标签+档位激活引擎能否机械判「成型」、
# 机制件带能否挂进令牌池稀有/罕见档随机投放(P0——迭代 12 双层 BD 实施批 T1)
# 专长带能否挂进令牌池稀有/罕见档随机投放(P0——迭代 12 双层 BD 实施批 T1)
# Date: 2026-10-04(双层 BD 实施批 T1——任务书五裁定稿;设计权威 双层BD体系-整合定稿-2026-10-03 v3)
# 数据配置 RelicTable schema(双层BD/机制件 回填 owner 批末落文;行 = Dictionary 键 relic_id,Rule 4 豁免
# 概念指路:本表的专长,与 adventure.tres 的属性值提升、资源,同属一个对象——
# 「奇物」(一次冒险内各渠道给予玩家的增益总称)。定义见 design/gdd/systems/冒险循环/奇物.md。
# 它们是它分类轴上的枚举(专长 / 属性值提升 / 资源),不是三套系统;本表承载「专长」(专长 Feat)的逐项定义。
# (2026-10-05 命名统一 + 分类重构:旧称「流派件」→「规则改写型特性」→ 现定名「专长」;
# 表名与 relic_id 沿用历史命名,与统一词族对齐归后续批次——见 06-待办清单「命名统一」条目。
# 目标渠道:专长走关底 Boss 击败后专属三选一(不新增节点类型),见奇物文档 2.1 节。)
# 数据配置 RelicTable schema(奇物·专长定义表;行 = Dictionary 键 relic_id,Rule 4 豁免
# 定义表——AuxItemTable 先例)。
# 行字段:relic_id 主键 / display_name / rarity ∈ {common, rare, rarest}(对齐令牌池三档命名——
# 投放档映射:common/rare→稀有令牌档、rarest→罕见令牌档;relic 令牌永不入普通档=「挂稀有/罕见档」
@@ -12,7 +18,7 @@
# 仅校验键域不禁纯增益(专项论证件以 positive_only 批注登记,人工清单呈报)。
# 档位激活(整合定稿 设计工具 1——以撒套装/小骨铭文):同标签计数 ≥2→档一(流派核心规则)/≥4→档二
# (完全体);散件单体保底=每件效果独立生效,档位只做质变(反例 B-2)——引擎纯函数 RelicsEngine。
# 🔴 层级纪律(任务书红线 2):流派件只走局内随机渠道(三选一/商店/格面)——本表行永不入同调位
# 🔴 层级纪律(任务书红线 2):专长只走局内随机渠道(三选一/商店/格面)——本表行永不入同调位
# (aux_items 表)/局外树(T8 节点表);扫雷触发点铁律:每流派 ≥1 行 hook ∈ MINE_HOOKS(加载校验,
# 违反=红线①触碰面)。
class_name RelicTable
+1 -1
View File
@@ -10,7 +10,7 @@
# 现价公式 = base_price + price_level_bonus(关卡) + price_increment × 该商品已购档数(已购档数在 冒险结构)。
# price_level_bonus = 关级定价加值行(level_index 连续从 1 递增)。
# pool_shelf = 共享池货架行(M-4 迭代 11 第②批 T1——任务书 v1.3 P1 共享池/P4 ceil):键 kind_id 词表
# 封闭 = 四切边后种(SHELF_KINDS:attack/max_hp/armor/ap——GP 袋负交易切边、机制件三轴 doctrine 切边);
# 封闭 = 四切边后种(SHELF_KINDS:attack/max_hp/armor/ap——GP 袋负交易切边、规则改写型特性三轴 doctrine 切边);
# 库存=上限即库存(账本在 冒险结构 _bonus_taken 单点共享,行族不记库存);price_a/price_b = 乘法价格
# 因子(公式 ceil(base × b^(关序−1) × A^(本次进店已购次数)),provisional 投值——P5 甲研究拍板后置追认)。
# products 行族 + 加法定价随 legacy 模式(shop=off 对照承载)保留,退役归 off 锚换代批。
+21 -21
View File
@@ -5,27 +5,27 @@
[resource]
script = ExtResource("1")
clear_heal_pct = 20
bonus_candidate_count = 3
token_attack_common = 18
token_attack_rare = 5
token_max_hp_common = 17
token_max_hp_rare = 5
token_armor_common = 17
token_armor_rare = 5
token_gp_common = 18
token_gp_rare = 5
token_heal_rare = 5
token_ap_rarest = 3
bonus_attack_flat = 1
bonus_attack_flat_large = 2
bonus_max_hp_flat = 5
bonus_max_hp_flat_large = 10
bonus_armor_flat = 1
bonus_armor_flat_large = 2
bonus_gp_amount = 20
bonus_gp_amount_large = 50
bonus_heal_flat = 6
bonus_ap_flat = 1
feature_candidate_count = 3
feature_quota_attack_common = 18
feature_quota_attack_rare = 5
feature_quota_max_hp_common = 17
feature_quota_max_hp_rare = 5
feature_quota_armor_common = 17
feature_quota_armor_rare = 5
feature_quota_gp_common = 18
feature_quota_gp_rare = 5
feature_quota_heal_rare = 5
feature_quota_ap_rarest = 3
feature_value_attack_common = 1
feature_value_attack_rare = 2
feature_value_max_hp_common = 5
feature_value_max_hp_rare = 10
feature_value_armor_common = 1
feature_value_armor_rare = 2
feature_value_gp_common = 20
feature_value_gp_rare = 50
feature_value_heal_rare = 6
feature_value_ap_rarest = 1
; TP0 令牌池换代(2026-10-03,node-reward v2.2 追认 b 落地):三选一池 = 固定配额令牌池——
; 普通 70/稀有 25/罕见 3 = 98 张(罕见档原列「道具件 2」随 aux_item 池种退役收窄——台账自决披露项);
+1 -1
View File
@@ -411,5 +411,5 @@ rows = [{
; ≥ 4×(row−1));锻造脉 3 节点收编 forge_levels(钥石付价=现行迁移零痛);钥石脉=工坊解锁收编
; (free 迁移点亮)+刷新升级移植;同调脉 socket 3(钥石=规则面首站)+Fork 4 对(A/B 集中同调脉
; 营地免费切换——effects 首期两 rule 键真实接线余组 stat 面 provisional);彩蛋 1(锻造满排觉醒)。
; 🔴 层级纪律:局外树只放全局规则件——零 archetype 键(schema 校验强制);流派件只走局内随机渠道。
; 🔴 层级纪律:局外树只放全局规则件——零 archetype 键(schema 校验强制);规则改写型特性只走局内随机渠道。
; 数值全 provisional 归 M-4 数值批;A/B 两侧差 <~3pp 验收=T10 MC。
+4 -4
View File
@@ -260,15 +260,15 @@ rows = [{
}
}]
; 双层 BD 实施批(2026-10-04):机制件表——行 = 流派件定义
; 双层 BD 实施批(2026-10-04):规则改写型特性表——行 = 特性定义
; (relic_id/display_name/rarity/archetype/hook/effects)。
; T2 魔法流四件(整合定稿 §2.3 S1):奥术余烬〔普通·rider+1 必中附加〕/魔网织者〔稀有·magic vs
; magic 克制罚修正〕/湛蓝誓约〔稀有·magic vs heavy 克制加成〕/魔能共鸣〔罕见锚点·rider+2+每战首次
; 开格奥术矢——「每关首次」字面落地为「每战首次」台账披露待追认〕;magic 档一(≥2 件)rider+1 /
; 档二(≥4 件)rider 可暴击——引擎 tiers_for 承载。T3-T6 逐流派续填(flag 4/pierce 4/normal 4/
; dig 4/charge 3/devour 4——整合定稿 §2.3 七流派件面)。
; dig 4/charge 3/devour 4——整合定稿 §2.3 七流派的件面)。
; 稀有度→投放档映射:common/rare → 稀有令牌档、rarest → 罕见令牌档(relic 永不入普通令牌档
; ——「机制件带挂稀有/罕见档」追认 b 接线口径,普通档 70 张纯数值维持)。
; 🔴 层级纪律:本表行永不入同调位(aux_items)/局外树(T8 节点表)——流派件只走局内随机渠道。
; ——「规则改写型特性带挂稀有/罕见档」追认 b 接线口径,普通档 70 张纯数值维持)。
; 🔴 层级纪律:本表行永不入同调位(aux_items)/局外树(T8 节点表)——规则改写型特性只走局内随机渠道。
; 数值全 provisional 归 M-4 数值批;effects 键白名单封闭(零裸 flat 同族护栏)。
+1 -1
View File
@@ -94,7 +94,7 @@ price_b = 1.1
;(用户拍板骨架先行、数值整体后置,行值归 难度曲线与Boss)。
; GP 商店房重构批(M-4 迭代 11 第②批 T1,2026-10-03——任务书 v1.3 全裁 P1 共享池/P4 ceil/P5 甲):
; pool_shelf 行族 = 共享池货架(kind_id 词表封闭四切边后种 attack/max_hp/armor/ap——GP 袋负交易切边、
; 机制件三轴 doctrine 切边;库存=上限即库存,账本在 冒险结构 _bonus_taken 单点共享,行族不记库存);
; 规则改写型特性三轴 doctrine 切边;库存=上限即库存,账本在 冒险结构 _bonus_taken 单点共享,行族不记库存);
; 价格公式换代(pool 模式)= ceil(base_price × b^(关序−1) × A^(本次进店已购次数)),price_a=1.8/price_b=1.1
; provisional 投值(零人工令改 provisional+后置追认,原 P5 甲研究拍板后置);products 行族 + 加法定价
; 随 legacy 模式(shop=off 对照承载)保留不退役,退役归 off 锚换代批。货架行值全部 provisional 归
+3 -3
View File
@@ -131,11 +131,11 @@ static func bonus_result_text(result: Dictionary) -> String:
return "已选择:%s\n其余选项已丢弃(不可反悔)" % name
# 流派件卡片文案(迭代 12 T1;2026-10-04 呈现补强——第三行=hook 效果摘要,用户反馈「不知道件是
# 特性卡片文案(迭代 12 T1;2026-10-04 呈现补强——第三行=hook 效果摘要,用户反馈「不知道件是
# 干嘛的」缺口承载;摘要=静态映射表 provisional 文案归代拍板白名单「件文案微调」)
static func relic_card_text(row: Dictionary) -> String:
if row.is_empty() or String(row.get("relic_id", "")) == "":
return "流派件\n???"
return "特性\n???"
var arch_names := {"magic": "魔法", "pierce": "穿刺", "normal": "普攻", "dig": "开格",
"flag": "标旗", "charge": "蓄力", "devour": "吞噬"}
var arch := String(arch_names.get(String(row.get("archetype", "")), "流派"))
@@ -167,7 +167,7 @@ static func relic_card_text(row: Dictionary) -> String:
var summary := String(summaries.get(String(row.get("hook", "")), ""))
if summary != "":
return "%s · %s\n%s" % [String(row.get("display_name", row.get("relic_id", ""))), arch, summary]
return "流派件 · %s\n%s" % [arch, String(row.get("display_name", row.get("relic_id", "")))]
return "特性 · %s\n%s" % [arch, String(row.get("display_name", row.get("relic_id", "")))]
# Boss 胜利战斗总结结算行(settlement-summary v1.1——钥石增量行;组合根拼 GP 行后追加)
@@ -127,9 +127,9 @@ func _bonus_card_texts(candidates: Array, aux_detail: Array = []) -> Array:
elif kind == "aux_item":
var iid := String(aux_detail[aux_i]) if aux_i < aux_detail.size() else ""
aux_i += 1
text = Narrator.bonus_card_text(kind, tier, int(db.adventure.bonus_value(kind, tier)), _aux_card_line(iid))
text = Narrator.bonus_card_text(kind, tier, int(db.adventure.feature_value(kind, tier)), _aux_card_line(iid))
else:
text = Narrator.bonus_card_text(kind, tier, int(db.adventure.bonus_value(kind, tier)))
text = Narrator.bonus_card_text(kind, tier, int(db.adventure.feature_value(kind, tier)))
out.append({"id": cid, "text": text})
return out
+4 -4
View File
@@ -19,7 +19,7 @@ extends RefCounted
# HP/状态效果单投喂 B 区站桩(原 B/C 双投喂为重复展示——信息归属拍板单一展示面原则)
static func refresh(stage: BattleStageView, info: HeroInfoPanelView, ap_bar: ApBarView,
settlement, runner, level_active: bool, db, refresh_remaining: int) -> void:
# 本关强化列表(v3.5 迭代 9 卡⑫ 2026-09-30——runner 统一加算区投影:商店购买与关卡大奖励
# 特性列表(v3.5 迭代 9 卡⑫ 2026-09-30——runner 统一加算区投影:商店购买与关卡大奖励
# 共账本〔level_runner bonus{atk,max_hp,armor_class}〕;空加算=整节隐藏〔视图内空态纪律〕。
# 呈现只读投影零核心触碰;行文格式=代拍板白名单项)
if runner != null and level_active:
@@ -34,7 +34,7 @@ static func refresh(stage: BattleStageView, info: HeroInfoPanelView, ap_bar: ApB
info.set_level_bonuses(lines)
else:
info.set_level_bonuses([])
# 流派件持有投影(迭代 12 呈现补齐 2026-10-04——用户反馈「局内奖励选了什么看不到」:
# 特性持有投影(迭代 12 呈现补齐 2026-10-04——用户反馈「局内奖励选了什么看不到」:
# 逐流派聚合行+成型档位;常驻可读+空态隐藏。档位求值=RelicsEngine 纯函数直调)
if runner != null and level_active:
info.set_relic_holdings(relic_holdings_lines(db, runner.relics_held()))
@@ -46,7 +46,7 @@ static func refresh(stage: BattleStageView, info: HeroInfoPanelView, ap_bar: ApB
return
var s: Dictionary = settlement.presentation_snapshot()
# 地形变异常驻角标投影(全局作用域变异首发批 2026-10-04——板级变异快照 → 文案行;
# 空态隐藏=流派件段先例;战斗全程常驻防中局遗忘)
# 空态隐藏=特性段先例;战斗全程常驻防中局遗忘)
info.set_board_variants(board_variant_lines(db, s.get("board_variants", [])))
var hero: Dictionary = s.hero
if hero.is_empty() or not bool(hero.get("alive", false)):
@@ -93,7 +93,7 @@ static func _refresh_economy(ap_bar: ApBarView, s: Dictionary, db, refresh_remai
ap_bar.set_stop_enabled(bool(s.hero.get("alive", false)) and digging)
# 流派件持有聚合文案(迭代 12 呈现补齐——战斗 C 区与关卡选择页两处共用;空持有=空数组)
# 特性持有聚合文案(迭代 12 呈现补齐——战斗 C 区与关卡选择页两处共用;空持有=空数组)
static func relic_holdings_lines(db, held: Array) -> Array[String]:
var rlines: Array[String] = []
if db == null or db.relics == null or (db.relics.rows as Array).is_empty() or held.is_empty():
+10 -10
View File
@@ -1,6 +1,6 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# 实现 GDD:design/ux/battle.md(C 区角色信息 240×548——2026-09-26 信息归属拍板精简:
# 攻击(+修正来源角标)/护甲 AC 数据框 + 魔法道具槽(预留)+ 本关强化列表(预留)。
# 攻击(+修正来源角标)/护甲 AC 数据框 + 魔法道具槽(预留)+ 特性列表(预留)。
# HP 条/立绘占位/状态效果槽/RAP 池/GP/钥石退役——HP 与状态唯一展示面 = B 区站桩(同一数据
# hud_presenter 双投喂 B/C 两区的重复展示);RAP 迁 D 区 AP 条(贴弃图刷新消费口);
# GP(中循环)/钥石(大循环)迁出战斗屏)。
@@ -19,9 +19,9 @@ var item_slots: HBoxContainer # 魔法道具槽(迭代 7 T4 接
var _slot_panels: Array = [] # 槽 PanelContainer 引用(选中高亮/置灰重刷)
var _selected_handle := 0 # 选中态(0=无;组合根回写维持单源)
var _slot_handles: Array = [] # 槽位→handle 映射(set_aux_inventory 刷新)
var _bonus_section: VBoxContainer # 本关强化(预留——空态隐藏,接线归大奖励/锻造呈现批)
var _bonus_section: VBoxContainer # 特性(预留——空态隐藏,接线归大奖励/锻造呈现批)
var _bonus_list: VBoxContainer
var _relic_section: VBoxContainer # 流派件持有段(迭代 12 呈现补齐——空态隐藏)
var _relic_section: VBoxContainer # 特性持有段(迭代 12 呈现补齐——空态隐藏)
var _relic_list: VBoxContainer
var _board_variant_section: VBoxContainer # 地形变异常驻角标(全局作用域变异首发批——空态隐藏)
var _board_variant_list: VBoxContainer
@@ -70,19 +70,19 @@ func _init() -> void:
item_slots.add_child(slot)
_slot_panels.append(slot)
# 流派件持有段(迭代 12 呈现补齐 2026-10-04——三选一流派件兑付后常驻可读:逐流派聚合行+成型档位;
# 特性持有段(迭代 12 呈现补齐 2026-10-04——三选一特性兑付后常驻可读:逐流派聚合行+成型档位;
# 空态整节隐藏。用户试玩反馈「局内奖励选了什么看不到」缺口承载;2026-10-04 二次反馈上移——
# 构筑核心信息优先级高于临时强化,且防 C 区下部裁切不可见)
_relic_section = VBoxContainer.new()
_relic_section.add_theme_constant_override("separation", 4)
_relic_section.visible = false
col.add_child(_relic_section)
_relic_section.add_child(UiKit.label("流派件", 16, true))
_relic_section.add_child(UiKit.label("特性", 16, true))
_relic_list = VBoxContainer.new()
_relic_list.add_theme_constant_override("separation", 2)
_relic_section.add_child(_relic_list)
# 地形变异常驻角标(全局作用域变异首发批 2026-10-04——Boss 图整图型战斗全程可见防中局遗忘;
# 空态整节隐藏=流派件段先例;摆位/文案 provisional 归 HUD 框架批与本地化批)
# 空态整节隐藏=特性段先例;摆位/文案 provisional 归 HUD 框架批与本地化批)
_board_variant_section = VBoxContainer.new()
_board_variant_section.add_theme_constant_override("separation", 4)
_board_variant_section.visible = false
@@ -91,13 +91,13 @@ func _init() -> void:
_board_variant_list = VBoxContainer.new()
_board_variant_list.add_theme_constant_override("separation", 2)
_board_variant_section.add_child(_board_variant_list)
# 本关强化(预留):锻造/大奖励/关卡奖励节点的属性提升——中循环数据因改变战斗数学准入;
# 特性(预留):锻造/大奖励/关卡奖励节点的属性提升——中循环数据因改变战斗数学准入;
# 与战斗内状态效果(B 区站桩,随战斗消散)分区,防「战斗结束被清」误读;空态整节隐藏
_bonus_section = VBoxContainer.new()
_bonus_section.add_theme_constant_override("separation", 4)
_bonus_section.visible = false
col.add_child(_bonus_section)
_bonus_section.add_child(UiKit.label("本关强化", 16, true))
_bonus_section.add_child(UiKit.label("特性", 16, true))
_bonus_list = VBoxContainer.new()
_bonus_list.add_theme_constant_override("separation", 2)
_bonus_section.add_child(_bonus_list)
@@ -113,7 +113,7 @@ func set_armor(ac: int) -> void:
_armor_label.text = "护甲 AC %d" % ac
# 流派件持有段(迭代 12 呈现补齐):lines = 聚合文案行(「魔法 ×2 · 档一」式)——空数组 = 整节隐藏
# 特性持有段(迭代 12 呈现补齐):lines = 聚合文案行(「魔法 ×2 · 档一」式)——空数组 = 整节隐藏
func set_relic_holdings(lines: Array) -> void:
for c2 in _relic_list.get_children():
_relic_list.remove_child(c2)
@@ -135,7 +135,7 @@ func set_board_variants(lines: Array) -> void:
_board_variant_section.visible = not lines.is_empty()
# 本关强化列表(预留接线面):lines = 文案行数组(如「锻造 +2 攻击」)——空数组 = 整节隐藏
# 特性列表(预留接线面):lines = 文案行数组(如「锻造 +2 攻击」)——空数组 = 整节隐藏
func set_level_bonuses(lines: Array) -> void:
for c in _bonus_list.get_children():
_bonus_list.remove_child(c)
+12 -8
View File
@@ -6,7 +6,7 @@
# 悬停预承诺(描边加粗+指针)后点击提交;press_candidate 探针入口改走图上命中同路径
# ③ 放弃冒险二次确认(布局决策 3)——首击转确认态(文案+危险红脉冲约 3 秒超时回落),二击提交
# ④ 顶栏语境:关卡名(level_row.display_name)+ GP 常驻徽章(2026-09-26 二轮拍板去向落地面)
# ⑤ 底部状态栏(布局决策 4):生命/属性/本关加成三组常读(零加成隐藏),放弃按钮右端异位
# ⑤ 底部状态栏(布局决策 4):生命/属性/特性三组常读(零加成隐藏),放弃按钮右端异位
# 关卡画面(独立屏,Level Map Screen——2026-09-28 形态升格批 M-2 迭代 3 T2:原 RouteOverlay 模态
# 覆盖层升格独立屏,spec level-map.md 形态章节同批改写):全屏不透明独占呈现——激活期战斗屏宿主
# 由组合根整体隐藏(非暗幕半透、零透出)。零自动推进的玩家停点语义不变:唯一前进通道 =
@@ -57,7 +57,7 @@ var _hp_bar: Control
var _attr_value: Label
var _bonus_row: HBoxContainer
var _bonus_value: Label
var _relic_value: Label # 流派件持有行(迭代 12 呈现补齐——组合根投喂聚合文案)
var _relic_value: Label # 特性持有行(迭代 12 呈现补齐——组合根投喂聚合文案)
var _cand_ids: Array[int] = [] # 本停点候选 id 集(图上命中合法集)
var _content_size := Vector2(620, 240) # 图内容包络(open_choices 按图拟合——画布 min 尺寸随它)
var _hover_id := -1 # 悬停预承诺候选(-1 = 无)
@@ -159,10 +159,14 @@ func _init() -> void:
strip.add_child(_bonus_row)
var bonus_box := VBoxContainer.new()
_bonus_row.add_child(bonus_box)
bonus_box.add_child(UiKit.label("本关加成", 11, false, Color8(110, 69, 52)))
_bonus_value = UiKit.label("", 15, true, UiKit.INK_TITLE)
bonus_box.add_child(_bonus_value)
_relic_value = UiKit.label("", 12, false, Color8(110, 69, 52))
var bonus_inner := VBoxContainer.new()
bonus_box.add_child(bonus_inner)
var bonus_cap := UiKit.label("特性", 12, false, UiKit.WARM_SOFT)
bonus_inner.add_child(bonus_cap)
_bonus_value = UiKit.label("", 15, true, UiKit.WARM)
bonus_inner.add_child(_bonus_value)
# 特性持有行(迭代 12 呈现补齐 2026-10-04——用户反馈关卡选择页看不到局内奖励;组合根投喂)
_relic_value = UiKit.label("", 13, false, UiKit.WARM_SOFT)
_relic_value.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
bonus_box.add_child(_relic_value)
var spacer := Control.new()
@@ -200,7 +204,7 @@ func _draw_hp_bar(bar: Control) -> void:
# 放弃入口可见性(组合根在冒险启动时开、默认关——语义对象 = 整局冒险)
# 流派件持有投影(迭代 12 呈现补齐——组合根投喂聚合文案;空文案=行隐藏)
# 特性持有投影(迭代 12 呈现补齐——组合根投喂聚合文案;空文案=行隐藏)
func set_relic_holdings(text: String) -> void:
_relic_value.text = text
@@ -335,7 +339,7 @@ func _refresh_status() -> void:
if ac_bonus > 0:
parts.append("AC +%d" % ac_bonus)
_bonus_value.text = " · ".join(parts)
# 流派件持有行可见性(与加成行并框——任一非空整框显示;文案归组合根 set_relic_holdings)
# 特性持有行可见性(与加成行并框——任一非空整框显示;文案归组合根 set_relic_holdings)
_bonus_row.visible = not parts.is_empty() or _relic_value.text != ""
+2 -2
View File
@@ -42,8 +42,8 @@ func _initialize() -> void:
var shop_mode := "off"
if args.size() >= 6 and args[5] in ["off", "on"]:
shop_mode = args[5]
# 流派件/树分层开关(迭代 12 T7/T10——off=基线;formed=成型画像开局注入七流派各 2 件档一成型集
# 〔测量面专用 params.relics_seed——真实局流派件只走局内随机渠道〕;ab_a/ab_b=Fork posture 两侧
# 特性/树分层开关(迭代 12 T7/T10——off=基线;formed=成型画像开局注入七流派各 2 件档一成型集
# 〔测量面专用 params.relics_seed——真实局规则改写型特性只走局内随机渠道〕;ab_a/ab_b=Fork posture 两侧
# rule 键集——A/B 面两侧差 <~3pp 验收载体)
var relic_mode := "off"
if args.size() >= 7 and args[6] in ["off", "formed", "ab_a", "ab_b"]:
+4 -4
View File
@@ -137,7 +137,7 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_
"log_path": "", "policy": policy, "refreshes": 0, "mine_hits": 0, "mine_dmg": 0,
"visited_ids": [], "choices": 0, "last_battle_enemies": [],
"summary": {}, "wallet_events": [], "encounter_max_hp_seq": [], "reward_events": [],
"relic_mode": String(relic.get("mode", "off")), # 流派件分层开关(迭代 12 T7——off=316-f 通用竞争/on=成型画像优先捕捞 relic)
"relic_mode": String(relic.get("mode", "off")), # 特性分层开关(迭代 12 T7——off=316-f 通用竞争/on=成型画像优先捕捞 relic)
"shop_gp_seq": [], # 商店房到达余额序(逐关 1 次——GP 跨关延续验收取数面)
# 旗经济五字段(迭代14②判式换代批 T1——与 run_level 同口径,见彼处注释)
"flag_placed": 0, "flag_hit": 0, "flag_miss": 0, "flag_hit_gain": 0, "flag_miss_penalty": 0,
@@ -161,7 +161,7 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_
if not attune.is_empty():
adv_params["attunement"] = attune.get("equipped", [])
adv_params["attune_refresh_y"] = int(attune.get("refresh_y", 0))
# 流派件注入(迭代 12 T7——formed 画像=开局注入七流派各 2 件档一成型集;测量面专用)
# 特性注入(迭代 12 T7——formed 画像=开局注入七流派各 2 件档一成型集;测量面专用)
if relic.has("seed_ids"):
adv_params["relics_seed"] = relic.get("seed_ids", [])
# 树规则键注入(迭代 12 T10——A/B 面读数载体:fork 激活侧 effects rule 键集透传)
@@ -190,7 +190,7 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_
# 终局池读数(迭代 11 第②批 T5——摘要 additive 键 pool_stock:_settle 清账前快照,
# 商店买走 vs 奖励领走 vs 枯竭的终局面;事后读 adr.pool_stock 会被收束清账重置=满库假象)
st["pool_stock"] = (st.get("summary", {}).get("pool_stock", {}) as Dictionary).duplicate(true)
st["relics"] = (st.get("summary", {}).get("relics", []) as Array).duplicate() # 流派件终局持有集(迭代 12 T1——摘要 additive 键清账前快照,同 pool_stock 模式)
st["relics"] = (st.get("summary", {}).get("relics", []) as Array).duplicate() # 特性终局持有集(迭代 12 T1——摘要 additive 键清账前快照,同 pool_stock 模式)
st.log_path = logger.log_path()
if own_session:
logger.close_session()
@@ -273,7 +273,7 @@ static func _on_adventure_bonus(payload: Dictionary, st: Dictionary, adr) -> voi
# 三选一确定性选件(TP0 316-f——1 级优先度=最稀有〔档位最高 rarest>rare>common〕、2 级=最左;
# 候选并列同档取最左=稳定序保证。空候选/未知档位 = 回退最左防御口径。
# 成型画像(迭代 12 T7——relic 模式 on:候选含 relic 令牌时优先在 relic 集内按 316-f 择一
# 〔贪心捕捞→流派件入局→成型画像全流派覆盖——R3 乙单锚承载〕;off=relic 与数值件同规则竞争)
# 〔贪心捕捞→特性入局→成型画像全流派覆盖——R3 乙单锚承载〕;off=relic 与属性加成型特性同规则竞争)
static func _pick_token(candidates: Array, relic_mode: String = "off") -> Dictionary:
var pool: Array = candidates
if relic_mode == "on":
+1 -1
View File
@@ -1,7 +1,7 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# Date: 2026-09-11(2026-09-15 格面资源批重写:类型频次测量——供给控制器批口径随可选类删除退役)
# 奖励期望测量:逐深度实测 |非封锁格|(safe ∪ mines ∖ R₀)分布、格面资源类型频次
# (应涌现自正向行权重 attack 20 / defense 16 / blast 16 / heal 15 / weaken 12,合计 79)、
# (应涌现自正向行九型权重 attack 14 / defense 11 / blast 16 / heal 4 / weaken 8 / pierce 8 / magic 8 / action 6 / gp 2,合计 77——reward.tres 现行)、
# 封锁格零泄漏(R₀ 格不在摇号清单)——走真实 地图战斗资源.roll_board 管线。
# 运行:Godot_console.exe --headless --path . --script res://smoke/reward_expectation.gd
extends SceneTree
+25 -25
View File
@@ -220,12 +220,12 @@ func _smoke_adventure_table() -> void:
check(int(_db.adventure.tier_total(tier)) == int(_db.adventure.TIER_QUOTAS[tier]),
"档位配额结构锚(v2.2 追认 b):%s = %d 张" % [tier, int(_db.adventure.TIER_QUOTAS[tier])])
check(int(_db.adventure.deck_total()) == 98, "令牌池总量 = 98 张(普通 70/稀有 25/罕见 3——罕见档随道具件池种退役收窄)")
check(int(_db.adventure.bonus_value("gp", "common")) == 20 and int(_db.adventure.bonus_value("gp", "rare")) == 50
and int(_db.adventure.bonus_value("attack", "common")) == 1 and int(_db.adventure.bonus_value("attack", "rare")) == 2
and int(_db.adventure.bonus_value("max_hp", "common")) == 5 and int(_db.adventure.bonus_value("max_hp", "rare")) == 10
and int(_db.adventure.bonus_value("armor", "common")) == 1 and int(_db.adventure.bonus_value("armor", "rare")) == 2,
check(int(_db.adventure.feature_value("gp", "common")) == 20 and int(_db.adventure.feature_value("gp", "rare")) == 50
and int(_db.adventure.feature_value("attack", "common")) == 1 and int(_db.adventure.feature_value("attack", "rare")) == 2
and int(_db.adventure.feature_value("max_hp", "common")) == 5 and int(_db.adventure.feature_value("max_hp", "rare")) == 10
and int(_db.adventure.feature_value("armor", "common")) == 1 and int(_db.adventure.feature_value("armor", "rare")) == 2,
"数值对(v2.2):小GP 20/大GP 50 · 小增伤 +1/大 +2 · 小生命 +5/大 +10 · 小护甲 +1/大 +2")
check(int(_db.adventure.bonus_value("heal", "rare")) == 6 and int(_db.adventure.bonus_value("ap", "rarest")) == 1,
check(int(_db.adventure.feature_value("heal", "rare")) == 6 and int(_db.adventure.feature_value("ap", "rarest")) == 1,
"恢复件 +6 当即回血(新入池种类,rare 档)· AP +1(rarest 档)")
check(_db.attunement_growth != null and int(_db.attunement_slot_cap()) == 3
and Attunement.slots(_db, null) == 0,
@@ -349,7 +349,7 @@ func _smoke_adventure() -> void:
var cand_ok := picks.size() == 2
var ks_gain_ok := picks.size() == 2
var resolve_ok := level_resolves.size() == 2
var max_flat := int(_db.adventure.bonus_max_hp_flat)
var max_flat := int(_db.adventure.feature_value_max_hp_common)
var heal_pct := int(_db.adventure.clear_heal_pct)
var hp_acc := 0 # max_hp 件累加(回血封顶断言的生效上限推导——TP0 起按兑付令牌实值,大小档分别计)
var ks_prefix := 0
@@ -386,7 +386,7 @@ func _smoke_adventure() -> void:
var r: Dictionary = level_resolves[i]
var rk := String(r.get("picked", ""))
var rt := String(r.get("tier", ""))
if _smoke_find_token(p.get("candidates", []) as Array, rk, rt).is_empty() or int(r.get("value", -1)) != int(_db.adventure.bonus_value(rk, rt)):
if _smoke_find_token(p.get("candidates", []) as Array, rk, rt).is_empty() or int(r.get("value", -1)) != int(_db.adventure.feature_value(rk, rt)):
resolve_ok = false
if rk == "max_hp":
hp_acc += int(r.get("value", max_flat))
@@ -399,7 +399,7 @@ func _smoke_adventure() -> void:
var exp_base := maxi(1, int(round(float(int(_db.hero.max_hp)) * k)))
# 模拟档 v3.4 商店贪心购血(数值修复批)合法抬升——上限窗口 = 基础 + 加算区 3 件 + 购血余量;
# TP0 数值对后 max_hp 件有大生命 +10(rare 档)——上界按大件最坏构成 3×10+购血 15=+45(TP0 放宽)
var hp_ceiling := maxi(1, int(round(float(int(_db.hero.max_hp) + 3 * int(_db.adventure.bonus_value("max_hp", "rare")) + 15) * k)))
var hp_ceiling := maxi(1, int(round(float(int(_db.hero.max_hp) + 3 * int(_db.adventure.feature_value("max_hp", "rare")) + 15) * k)))
var seq_ok := seq.size() > 0 and int(seq[0]) == exp_base
for v in (seq as Array):
if int(v) < exp_base or int(v) > hp_ceiling:
@@ -594,22 +594,22 @@ func _smoke_adventure() -> void:
a13.present_node_bonus(ncands13, ndraw.get("aux_detail", []) as Array)
a13.present_node_bonus([{"kind": "gp", "tier": "common"}])
var gp13 := int(a13.ledger.get("gp", 0))
check(a13.apply_node_bonus("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.bonus_value("gp", "common")),
"奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" % int(_db.adventure.bonus_value("gp", "common")))
check(a13.apply_node_bonus("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.feature_value("gp", "common")),
"奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" % int(_db.adventure.feature_value("gp", "common")))
check(a13._node_bonus_pending.is_empty(), "节点停点态兑付后清零(恰一次语义同构)")
var s10: Dictionary = a10.summary
check(String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)),
"放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)")
# ---- 双层 BD 机制件基建冒烟(迭代 12 T1——relic_table/档位激活引擎/机制件带池接线)----
# ---- 双层 BD 规则改写型特性基建冒烟(迭代 12 T1——relic_table/档位激活引擎/规则改写型特性带池接线)----
func _smoke_relics() -> void:
print("--- 机制件基建(relic_table · 档位激活引擎 · 机制件带接线)---")
print("--- 规则改写型特性基建(relic_table · 档位激活引擎 · 规则改写型特性带接线)---")
# ① 现行表牌库构成:七流派 27 件全量入池——98 数值令牌+27 relic 令牌(批次一铺量完成)
check(_db.relics != null and (_db.relics.rows as Array).size() == 27, "relic_table 已加载(七流派 27 件全量——批次一 T2-T6 铺量完成)")
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 relic_tokens := 0
var rarest_tokens := 0
for t in a_r0._bonus_deck:
@@ -618,7 +618,7 @@ func _smoke_relics() -> void:
if String((t as Dictionary).get("tier", "")) == "rarest":
rarest_tokens += 1
check(relic_tokens == 27 and rarest_tokens == 6 and a_r0._bonus_deck.size() == 125,
"机制件带入池:27 件(rare 档 21 + rarest 锚点 6)——牌库 98+27=125")
"规则改写型特性带入池:27 件(rare 档 21 + rarest 锚点 6)——牌库 98+27=125")
check(_db.relics.validate().is_empty(), "relic_table.validate 零违例(有行流派扫雷端铁律——magic first_dig_spell 在案)")
# ② 档位激活引擎(纯函数直测——构造表 4 件 magic + 1 件 flag + 1 件 rarest 锚点)
var rt := RelicTable.new()
@@ -640,17 +640,17 @@ func _smoke_relics() -> void:
var hooks := RelicsEngine.hooks_for(["m1", "f1"], rt)
check(hooks.size() == 2 and String(hooks[0].get("hook", "")) == "hero_attack_rider",
"挂点投影:表序稳定(同集合→同投影)")
# ③ 机制件带接线(临时表注入——relic 令牌入牌库/兑付入持有集/踢池/断点往返;测试尾还原现行表)
# ③ 规则改写型特性带接线(临时表注入——relic 令牌入牌库/兑付入持有集/踢池/断点往返;测试尾还原现行表)
var live_rows: RelicTable = _db.relics
_db.relics = 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._bonus_deck:
if String((t as Dictionary).get("kind", "")) == "relic":
deck_relic += 1
check(deck_relic == 5 and a_r._bonus_deck.size() == 103,
"机制件带入池:5 行 relic 每件 1 张(common/rare→稀有档 rarest→罕见档)——牌库 98+5=103")
"规则改写型特性带入池:5 行 relic 每件 1 张(common/rare→稀有档 rarest→罕见档)——牌库 98+5=103")
var relic_seen: Dictionary = {}
for i in 60:
for t in a_r._draw_bonus_candidates():
@@ -728,13 +728,13 @@ func _smoke_relics() -> void:
"深挖钻头=开掘 +1 AP/每 4 格深层产出(R4 甲非信息税对价)· 蓄力三件=层值 1/减伤 1/层/满层 ×2")
check(orch.action_economy.relic_ap_carry and orch.action_economy.relic_dig_extra == 1,
"蓄力/钻头 action_economy 注入(ap_carry 改写停手转存 + 付费开掘对价注入——关着 false/0 零扰动)")
# ⑦c 流派件持有呈现段(2026-10-04 用户反馈补齐——C 区「流派件」节:聚合行+成型档位;空态隐藏)
# ⑦c 特性持有呈现段(2026-10-04 用户反馈补齐——C 区「特性」节:聚合行+成型档位;空态隐藏)
var panel = load("res://presentation/views/hero_info_panel_view.gd").new()
panel.set_relic_holdings([])
check(not panel._relic_section.visible, "流派件段空态隐藏(视图空态纪律同本关强化段)")
check(not panel._relic_section.visible, "特性段空态隐藏(视图空态纪律同特性段)")
panel.set_relic_holdings(["魔法 ×2 · 档一", "开格 ×1 · 散件"])
check(panel._relic_section.visible and panel._relic_list.get_child_count() == 2,
"流派件段持有呈现(聚合行+成型档位常驻可读——「局内奖励选了什么看不到」缺口承载)")
"特性段持有呈现(聚合行+成型档位常驻可读——「局内奖励选了什么看不到」缺口承载)")
# ⑦b 深挖钻头付费开掘回归(2026-10-04 试玩缺陷修复——对价挂付费侧):AP=1 降级照常开格
# (用户实测「AP=1 无法开格只能标旗」的回归闸);AP=2 付全价积付费计数(深层产出配额取数面)。
var ae_dd = load("res://core/action_economy.gd").new(_db)
@@ -3857,8 +3857,8 @@ func _smoke_shop_pool() -> void:
var val0: int = int(a8.bonus.get(String(val_map[kind]), -999))
check(r8.buy_pool_item(kind), "购买受理(pool 口——%s|%s)" % [kind, tier])
check(int(r8.gp) == gp0 - int(buy_c.get("price", 0)), "钱包扣现价(%d → %d)" % [gp0, int(r8.gp)])
check(int(a8.bonus.get(String(val_map[kind]), -999)) == val0 + int(_db.adventure.bonus_value(kind, tier)),
"加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.adventure.bonus_value(kind, tier))])
check(int(a8.bonus.get(String(val_map[kind]), -999)) == val0 + int(_db.adventure.feature_value(kind, tier)),
"加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.adventure.feature_value(kind, tier))])
check(int(a8._bonus_taken.get(kind, 0)) == taken0 + 1, "共享池账本 -1(跨渠道单点——taken %d → %d)" % [taken0, int(a8._bonus_taken.get(kind, 0))])
# A 链:同visit 第二购现价 = ceil(base×b^(l2−1)×A^1)(货架已全重抽——新卡新价同公式)
var second: Dictionary = {}
@@ -3872,7 +3872,7 @@ func _smoke_shop_pool() -> void:
check(int(second.get("price", -1)) == expect_p2,
"A 链爬价 = ceil(base×b^(关序−1)×A^1)(进店第 2 购——%d)" % expect_p2)
check(r8.buy_pool_item(String(second.get("kind_id", ""))), "同 visit 第二购受理(A 链软闸在价不在限)")
# ④ 切边零入店(白-box 重抽 40 次——GP 袋/机制件永不在架;重抽消费 shop_draw 签,场景尾不回写主时间线)
# ④ 切边零入店(白-box 重抽 40 次——GP 袋/规则改写型特性永不在架;重抽消费 shop_draw 签,场景尾不回写主时间线)
var cut_seen := false
for i in 40:
r8._shop_draw_shelf()
@@ -3923,7 +3923,7 @@ func _smoke_shop_pool() -> void:
for k in ps.keys():
var quota := 0
for tier in _db.adventure.TIERS:
quota += int(_db.adventure.bonus_token_count(String(k), tier))
quota += int(_db.adventure.feature_quota(String(k), tier))
if int(ps[k]) < 0 or int(ps[k]) > quota:
ps_ok = false
check(ps_ok, "终局池余量在域 [0,配额](%s)" % str(ps))
@@ -4514,7 +4514,7 @@ func _smoke_save() -> void:
and int(bp.get("hero_hp", -1)) == 13, "断点七字段往返:关卡序号/节点位/生命")
var bp_bonus: Dictionary = bp.get("bonus", {})
check(int(bp_bonus.get("atk", -1)) == 2 and int(bp_bonus.get("max_hp", -1)) == 5
and int(bp_bonus.get("armor_class", -1)) == 1, "断点七字段往返:本关加成区")
and int(bp_bonus.get("armor_class", -1)) == 1, "断点七字段往返:属性加成区")
check(int(bp.get("gp", -1)) == 17, "断点七字段往返:GP 钱包")
var bp_shop: Dictionary = bp.get("shop_purchased", {})
check(int(bp_shop.get("hp_small", -1)) == 2 and int(bp_shop.get("atk_small", -1)) == 1,