From d4cde739763f0588ae0ee860eafe05791fcbd5fe Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 5 Oct 2026 18:30:44 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E5=A5=87=E7=89=A9=E6=89=B9T1=EF=BC=9Afeatu?= =?UTF-8?q?re=5F*=E2=86=92curio=5F*=E8=AF=8D=E6=97=8F=E6=8D=A2=E5=90=8D?= =?UTF-8?q?=EF=BC=88CURIO=5FKINDS/curio=5Fquota/curio=5Fvalue/curio=5Fcand?= =?UTF-8?q?idate=5Fcount+adventure.tres=E9=94=AE=E5=90=8D+entities.yaml?= =?UTF-8?q?=E6=8D=A2=E4=BB=A3=E7=99=BB=E8=AE=B0=E7=BB=AD=E5=86=99=EF=BC=8C?= =?UTF-8?q?=E5=80=BC=E9=9B=B6=E5=8F=98=E6=9B=B4=E5=AE=9E=E6=B5=8B=EF=BC=9B?= =?UTF-8?q?T0=E5=B7=AE=E5=BC=82=E5=9B=9E=E5=86=99=EF=BC=9A93=E5=A4=84/7?= =?UTF-8?q?=E6=96=87=E4=BB=B6+bonus=5Faux=5Fcarry=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=E6=BE=84=E6=B8=85=E5=89=94=E9=99=A4=E6=94=B9=E5=90=8D=E9=9D=A2?= =?UTF-8?q?+=E5=9F=BA=E7=BA=BF772/0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/registry/entities.yaml | 10 +- ...务书-奇物词族对齐与渠道分离批-2026-10-05.md | 2 + game/core/adventure_runner.gd | 18 +-- game/core/battle_orchestrator.gd | 2 +- game/core/config_db_cross_validator.gd | 8 +- game/core/level_runner.gd | 2 +- game/data/schemas/adventure_table.gd | 106 +++++++++--------- game/data/tables/adventure.tres | 42 +++---- .../presentation/bonus_interact_controller.gd | 4 +- game/smoke/smoke_all.gd | 26 ++--- 10 files changed, 111 insertions(+), 109 deletions(-) diff --git a/design/registry/entities.yaml b/design/registry/entities.yaml index d31ff5e0..f386a869 100644 --- a/design/registry/entities.yaml +++ b/design/registry/entities.yaml @@ -613,12 +613,12 @@ entities: fields: | 退役两字段(迭代 10 奖励重构批,2026-09-30 用户拍板):level_clear_hp_bonus(「血回满+上限提升」件随大奖励三件套退役——回血改 clear_heal_pct 部分回血、上限提升改三选一件值列)、keystone_per_level(钥石退役出大奖励改 Boss/精英击杀掉落)。 clear_heal_pct: int [0,100](knob 默认 20——非末关通关部分回血回复生效上限的百分比,封顶上限不回满、满血回 0 合法;难度上压方向,带重扫义务归 难度曲线与Boss) - **2026-10-05 命名统一批**:本表的属性值提升奇物字段与常量整体换名(旧 → 新)——BONUS_IDS → FEATURE_KINDS、bonus_token_count() → feature_quota()、bonus_value() → feature_value()、bonus_candidate_count → feature_candidate_count、token_{kind}_{tier} → feature_quota_{kind}_{tier}、bonus_*_flat/_amount(_large) → feature_value_{kind}_{tier};值零变更(句柄改称不改数),断点附随键名沿用历史形态不动(存档兼容)。换名动因见 curio 条目(v2.0 顶层改名为「奇物」)。 - feature_value_attack_common / feature_value_max_hp_common / feature_value_armor_common: int [0,999](三选一属性值提升奇物兑付值——加算区攻击/生命上限/护甲轴累加,经覆盖值通道;值锚与上调约束归 难度曲线与Boss) - feature_value_gp_common: int [0,999](GP 袋件定额入钱包);feature_value_ap_rarest: int [0,10](AP 件每场战斗首轮行动点基线增量);bonus_aux_item_count: int [1,9](魔法道具件授予件数) + **2026-10-05 命名统一批**:本表的属性值提升奇物字段与常量整体换名(旧 → 新)——BONUS_IDS → CURIO_KINDS、bonus_token_count() → curio_quota()(→ curio_quota(),本批)、bonus_value() → curio_value()、bonus_candidate_count → curio_candidate_count、token_{kind}_{tier} → curio_quota_{kind}_{tier}、bonus_*_flat/_amount(_large) → curio_value_{kind}_{tier};值零变更(句柄改称不改数),断点附随键名沿用历史形态不动(存档兼容)。换名动因见 curio 条目(v2.0 顶层改名为「奇物」)。 + curio_value_attack_common / curio_value_max_hp_common / curio_value_armor_common: int [0,999](三选一属性值提升奇物兑付值——加算区攻击/生命上限/护甲轴累加,经覆盖值通道;值锚与上调约束归 难度曲线与Boss) + curio_value_gp_common: int [0,999](GP 袋件定额入钱包);curio_value_ap_rarest: int [0,10](AP 件每场战斗首轮行动点基线增量);bonus_aux_item_count: int [1,9](魔法道具件授予件数) ~~bonus_weight_*(E-S 加权洗牌权重族)~~ / ~~bonus_cap_*(种类次数上限族)~~:**v2.2 令牌池换代退役(迭代 12 TP0——2026-10-03 追认 b 实施批;洗牌改令牌 Fisher-Yates、池消耗即上限)** - feature_quota_{kind}_{tier}: int [0,99](**TP0 令牌配额族**——kind×tier 计数:attack 18/5 · max_hp 17/5 · armor 17/5 · gp 18/5 · heal 0/5 · ap 0/0/3〔common/rare/rarest〕;未列组合=0;档位结构锚 70/25/3=98 张入 schema validate 硬断言〔罕见档原列「道具件 2」随 aux_item 池种退役收窄——台账自决披露项〕) - feature_value_attack_rare / feature_value_max_hp_rare / feature_value_armor_rare / feature_value_gp_rare: int [0,999](稀有档大数值对——+2/+10/+2/50);feature_value_heal_rare: int [0,999](恢复件 +6 当即回血——新入池种类 rare 档);MC 策略 316-f(autoplay 1 级最稀有/2 级最左)随批落地 + curio_quota_{kind}_{tier}: int [0,99](**TP0 令牌配额族**——kind×tier 计数:attack 18/5 · max_hp 17/5 · armor 17/5 · gp 18/5 · heal 0/5 · ap 0/0/3〔common/rare/rarest〕;未列组合=0;档位结构锚 70/25/3=98 张入 schema validate 硬断言〔罕见档原列「道具件 2」随 aux_item 池种退役收窄——台账自决披露项〕) + curio_value_attack_rare / curio_value_max_hp_rare / curio_value_armor_rare / curio_value_gp_rare: int [0,999](稀有档大数值对——+2/+10/+2/50);curio_value_heal_rare: int [0,999](恢复件 +6 当即回血——新入池种类 rare 档);MC 策略 316-f(autoplay 1 级最稀有/2 级最左)随批落地 transitional_sync_slots: int [0,9](同调位过渡读数源,默认 1——道具持有上限;正主 = attunement_growth.tres 里程碑谓词表,同调实施批落地后切换读数源) mvp_content: 单行全局默认值即 MVP 行(provisional 归 曲线调参批——迭代 10 扩列批注) extension_reserve: 大奖励形态扩列位已消费(迭代 10 奖励重构批落地三选一/攻击型——本键使命完成退役;冒险结构 OQ2 收口) diff --git a/design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md b/design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md index 0656330f..7d163423 100644 --- a/design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md +++ b/design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md @@ -101,6 +101,8 @@ ### T1 词族对齐第一批:`feature_*` → `curio_*`(≈0.7d) +> ✅ **T0 复核差异回写(2026-10-05 实测,master 413c936)**:①`feature_*` 实测 **93 处/7 文件**(§2.1 的 103 系③秘宝批动 smoke/bonus_interact_controller 前口径);②运行层 `bonus_` 族实测远超 160 处(含信号 bonus_choice_presented/bonus_resolved/bonus_overlay 等全部形态);③**`bonus_aux_carry` 语义澄清=秘宝件跨关携带(③批已对齐 treasure 词族,内部变量现名 _bonus_treasure_carry),非奇物概念——本批改名面剔除,存档键引号字面量照③批先例禁改**;④基线=冒烟 772/0+run_all 14/15(reward_overlay 红=M7 bcard 换控件致 `c is Button` 断言失效,成因已考,T2 触碰该测试时适配);⑤res_ 仅 smoke 局部变量 res_box 4 处(语义无关,资源词族 res_* 不撞)。 + - 范围:`FEATURE_KINDS` → `CURIO_KINDS`、`feature_quota()` → `curio_quota()`、`feature_value()` → `curio_value()`、`feature_candidate_count` → `curio_candidate_count`、`feature_quota_{kind}_{tier}` → `curio_quota_*`、`feature_value_{kind}_{tier}` → `curio_value_*` - 涉及:`adventure_table.gd` + `adventure.tres`(键名)+ 6 个引用文件 - **值零变更**(只改键名与常量名) diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index 561a5795..3df74700 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -267,14 +267,14 @@ func _reset_bonus_state() -> void: _node_bonus_pending = {} -# 令牌池构建(TP0 v2.2——按表 kind×tier 配额展开为持久多重集;构建序确定 = FEATURE_KINDS×TIERS 逐层, +# 令牌池构建(TP0 v2.2——按表 kind×tier 配额展开为持久多重集;构建序确定 = CURIO_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.FEATURE_KINDS: + for kind in db.adventure.CURIO_KINDS: for tier in db.adventure.TIERS: - for i in int(db.adventure.feature_quota(String(kind), String(tier))): + for i in int(db.adventure.curio_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" @@ -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.feature_value(kind, tier)) + var value: int = int(db.adventure.curio_value(kind, tier)) var hp_before := int(ledger.get("hero_hp", 0)) if kind == "treasure_item": return _pick_treasure_item(value) # 道具件分派通关渠道(持有容器 = 跨关携带——与节点渠道分轨;池种退役后不可达) @@ -457,10 +457,10 @@ func pool_stock(kind: String) -> int: # 写加算区+账本+摘牌(与奖励三选一真单点——买一件=奖励面少一件的库存语义由同库成立)。 # 未知 kind 拒绝(货架词表封闭的前置防御)。 func _consume_pool_purchase(kind: String, tier: String) -> bool: - if not db.adventure.FEATURE_KINDS.has(kind): + if not db.adventure.CURIO_KINDS.has(kind): push_error("[冒险结构] 商店购买消费拒绝——件不在池词表: %s" % kind) return false - return _apply_bonus_pick(kind, tier, int(db.adventure.feature_value(kind, tier))) + return _apply_bonus_pick(kind, tier, int(db.adventure.curio_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.feature_value(kind, tier)) + var value: int = int(db.adventure.curio_value(kind, tier)) if kind == "treasure_item": return _apply_node_treasure(value) # 道具件分派节点渠道(持有容器 = 关卡域库存——与通关渠道分轨;池种退役后不可达) if not _apply_bonus_pick(kind, tier, value, relic_id): @@ -573,7 +573,7 @@ func _apply_node_treasure(count: int) -> bool: var records: Array = [] if _runner != null and _runner.treasure_inventory != null: records = _runner.treasure_inventory.holding_records() - # 迭代 11 第①批 T7——守卫随过渡键退役拆除(FEATURE_KINDS 已除名 treasure_item,本分支不可达; + # 迭代 11 第①批 T7——守卫随过渡键退役拆除(CURIO_KINDS 已除名 treasure_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.feature_candidate_count): + if out.size() >= int(db.adventure.curio_candidate_count): break return out diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index 85da5281..11667074 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -680,7 +680,7 @@ func set_treasure_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 词表第十型 treasure_item 保留但零投放: - # 无 aux 行入池、FEATURE_KINDS 词表已除名);剩余类型走极性字典路由。 + # 无 aux 行入池、CURIO_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]) diff --git a/game/core/config_db_cross_validator.gd b/game/core/config_db_cross_validator.gd index 0efe416c..583c3d2f 100644 --- a/game/core/config_db_cross_validator.gd +++ b/game/core/config_db_cross_validator.gd @@ -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.FEATURE_KINDS 且为有限库存种(cap ∈ [1,99]——gp 无上限种/牌库外种 + # pool_shelf kind 须命中 adventure.CURIO_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.FEATURE_KINDS.has(kind): - push_diag.call("error", "shelf_dangling_kind", "ShopTable.pool_shelf kind %s 不在 AdventureTable.FEATURE_KINDS(共享池牌库外种)" % kind) + if not adv.CURIO_KINDS.has(kind): + push_diag.call("error", "shelf_dangling_kind", "ShopTable.pool_shelf kind %s 不在 AdventureTable.CURIO_KINDS(共享池牌库外种)" % kind) continue # TP0 令牌池——货架件须为有限令牌种(配额 >0;种类次数上限退役后库存=剩余令牌数) - 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)) + var quota := int(adv.curio_quota(kind, adv.TIER_COMMON)) + int(adv.curio_quota(kind, adv.TIER_RARE)) + int(adv.curio_quota(kind, adv.TIER_RAREST)) if quota < 1: push_diag.call("error", "shelf_zero_quota", "ShopTable.pool_shelf kind %s 令牌配额为 0(牌库空种入货架 = 库存无锚)" % kind) diff --git a/game/core/level_runner.gd b/game/core/level_runner.gd index 0d5f745a..99a12096 100644 --- a/game/core/level_runner.gd +++ b/game/core/level_runner.gd @@ -834,7 +834,7 @@ 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.feature_value(kind, tier)) + var value := int(db.adventure.curio_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", "特性")) diff --git a/game/data/schemas/adventure_table.gd b/game/data/schemas/adventure_table.gd index 8b55a98f..e82a3a10 100644 --- a/game/data/schemas/adventure_table.gd +++ b/game/data/schemas/adventure_table.gd @@ -18,13 +18,13 @@ extends Resource ## 它们是它的分类轴上的枚举(专长 / 属性值提升 / 资源),不是三套系统; ## 本表承载属性值提升与资源的种类配额与件值。 ## (2026-10-05 命名统一 + v2.0 改名:旧称「数值件」→ 属性提升;顶层「特性」→「奖励」→「奇物」。 -## 常量与字段随之前缀 feature_*,**尚未**随顶层改名换到 reward_*——见 06-待办清单。 +## 常量与字段随之前缀 curio_*,**尚未**随顶层改名换到 reward_*——见 06-待办清单。 ## 分类轴三个枚举:属性值提升〔生命上限 / AC / 攻击〕;资源〔行动点上限 / ## 刷新次数 / 金币 / 回复,命名待定〕——本表两类混编;刷新次数与专长获取两项尚未入表。 ## 类内异质性:金币与回复是即时兑付,不进持有集、不持续生效。) ## 三选一提升池 kind 词表(封闭——断点附随件与牌库剔除态依赖它;扩件走本表+registry 修订)。 ## treasure_item 池种退役(迭代 11 第①批 T7——同调批出池);heal 恢复件新入池(v2.2 令牌池追认 b)。 -const FEATURE_KINDS: Array[String] = ["attack", "max_hp", "armor", "gp", "ap", "heal"] +const CURIO_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" @@ -39,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 feature_candidate_count: int = 3 # [1,6] knob——令牌洗牌不放回抽取张数上界 +@export var curio_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 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 席位随池种退役收窄) +@export var curio_quota_attack_common: int = 18 # [0,99] +@export var curio_quota_attack_rare: int = 5 # [0,99] +@export var curio_quota_max_hp_common: int = 17 # [0,99] +@export var curio_quota_max_hp_rare: int = 5 # [0,99] +@export var curio_quota_armor_common: int = 17 # [0,99] +@export var curio_quota_armor_rare: int = 5 # [0,99] +@export var curio_quota_gp_common: int = 18 # [0,99] +@export var curio_quota_gp_rare: int = 5 # [0,99] +@export var curio_quota_heal_rare: int = 5 # [0,99]——恢复件(新入池种类,当即回血) +@export var curio_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 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 件每场战斗首轮行动点基线增量 +@export var curio_value_attack_common: int = 1 # [0,999] knob——小增伤 +@export var curio_value_attack_rare: int = 2 # [0,999] knob——大增伤 +@export var curio_value_max_hp_common: int = 5 # [0,999] knob——小生命 +@export var curio_value_max_hp_rare: int = 10 # [0,999] knob——大生命 +@export var curio_value_armor_common: int = 1 # [0,999] knob——小护甲 +@export var curio_value_armor_rare: int = 2 # [0,999] knob——大护甲 +@export var curio_value_gp_common: int = 20 # [0,999] knob——小GP +@export var curio_value_gp_rare: int = 50 # [0,999] knob——大GP +@export var curio_value_heal_rare: int = 6 # [0,999] knob——恢复(当即回血,封顶生效上限) +@export var curio_value_ap_rarest: int = 1 # [0,10] knob——AP 件每场战斗首轮行动点基线增量 ## 令牌配额读数(未列 kind×tier 组合 = 0——heal 无普通档/ap 无普通稀有档按 v2.2 构成) -func feature_quota(kind: String, tier: String) -> int: +func curio_quota(kind: String, tier: String) -> int: match [kind, tier]: - ["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 + ["attack", TIER_COMMON]: return curio_quota_attack_common + ["attack", TIER_RARE]: return curio_quota_attack_rare + ["max_hp", TIER_COMMON]: return curio_quota_max_hp_common + ["max_hp", TIER_RARE]: return curio_quota_max_hp_rare + ["armor", TIER_COMMON]: return curio_quota_armor_common + ["armor", TIER_RARE]: return curio_quota_armor_rare + ["gp", TIER_COMMON]: return curio_quota_gp_common + ["gp", TIER_RARE]: return curio_quota_gp_rare + ["heal", TIER_RARE]: return curio_quota_heal_rare + ["ap", TIER_RAREST]: return curio_quota_ap_rarest return 0 ## 令牌件值(kind × tier;v2.2 数值对——小/大两档) -func feature_value(kind: String, tier: String = TIER_COMMON) -> int: +func curio_value(kind: String, tier: String = TIER_COMMON) -> int: match [kind, tier]: - ["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 + ["attack", TIER_COMMON]: return curio_value_attack_common + ["attack", TIER_RARE]: return curio_value_attack_rare + ["max_hp", TIER_COMMON]: return curio_value_max_hp_common + ["max_hp", TIER_RARE]: return curio_value_max_hp_rare + ["armor", TIER_COMMON]: return curio_value_armor_common + ["armor", TIER_RARE]: return curio_value_armor_rare + ["gp", TIER_COMMON]: return curio_value_gp_common + ["gp", TIER_RARE]: return curio_value_gp_rare + ["heal", TIER_RARE]: return curio_value_heal_rare + ["ap", TIER_RAREST]: return curio_value_ap_rarest return 0 @@ -113,8 +113,8 @@ func tier_rank(tier: String) -> int: ## 档位令牌总数(货架/校验读面) func tier_total(tier: String) -> int: var total := 0 - for kind in FEATURE_KINDS: - total += feature_quota(kind, tier) + for kind in CURIO_KINDS: + total += curio_quota(kind, tier) return total @@ -127,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 FEATURE_KINDS: + for kind in CURIO_KINDS: for tier in TIERS: - var c := feature_quota(kind, tier) + var c := curio_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 := feature_value(kind, tier) + var v := curio_value(kind, tier) if v < 0 or v > 999: - errs.append({"category": "domain", "message": "feature_value(%s,%s) 越域 [0,999]: %d" % [kind, tier, v]}) + errs.append({"category": "domain", "message": "curio_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 feature_candidate_count < 1 or feature_candidate_count > 6: - errs.append({"category": "domain", "message": "feature_candidate_count 越域 [1,6]: %d" % feature_candidate_count}) + if curio_candidate_count < 1 or curio_candidate_count > 6: + errs.append({"category": "domain", "message": "curio_candidate_count 越域 [1,6]: %d" % curio_candidate_count}) return errs diff --git a/game/data/tables/adventure.tres b/game/data/tables/adventure.tres index e64cd4d8..aa3390b1 100644 --- a/game/data/tables/adventure.tres +++ b/game/data/tables/adventure.tres @@ -5,27 +5,27 @@ [resource] script = ExtResource("1") clear_heal_pct = 20 -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 +curio_candidate_count = 3 +curio_quota_attack_common = 18 +curio_quota_attack_rare = 5 +curio_quota_max_hp_common = 17 +curio_quota_max_hp_rare = 5 +curio_quota_armor_common = 17 +curio_quota_armor_rare = 5 +curio_quota_gp_common = 18 +curio_quota_gp_rare = 5 +curio_quota_heal_rare = 5 +curio_quota_ap_rarest = 3 +curio_value_attack_common = 1 +curio_value_attack_rare = 2 +curio_value_max_hp_common = 5 +curio_value_max_hp_rare = 10 +curio_value_armor_common = 1 +curio_value_armor_rare = 2 +curio_value_gp_common = 20 +curio_value_gp_rare = 50 +curio_value_heal_rare = 6 +curio_value_ap_rarest = 1 ; TP0 令牌池换代(2026-10-03,node-reward v2.2 追认 b 落地):三选一池 = 固定配额令牌池—— ; 普通 70/稀有 25/罕见 3 = 98 张(罕见档原列「道具件 2」随 treasure_item 池种退役收窄——台账自决披露项); diff --git a/game/presentation/bonus_interact_controller.gd b/game/presentation/bonus_interact_controller.gd index ac1621b5..87abb5a2 100644 --- a/game/presentation/bonus_interact_controller.gd +++ b/game/presentation/bonus_interact_controller.gd @@ -127,9 +127,9 @@ func _bonus_card_texts(candidates: Array, treasure_detail: Array = []) -> Array: elif kind == "treasure_item": var iid := String(treasure_detail[treasure_i]) if treasure_i < treasure_detail.size() else "" treasure_i += 1 - text = Narrator.bonus_card_text(kind, tier, int(db.adventure.feature_value(kind, tier)), _treasure_card_line(iid)) + text = Narrator.bonus_card_text(kind, tier, int(db.adventure.curio_value(kind, tier)), _treasure_card_line(iid)) else: - text = Narrator.bonus_card_text(kind, tier, int(db.adventure.feature_value(kind, tier))) + text = Narrator.bonus_card_text(kind, tier, int(db.adventure.curio_value(kind, tier))) out.append({"id": cid, "text": text}) return out diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index 836d9492..c613f74b 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -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.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, + check(int(_db.adventure.curio_value("gp", "common")) == 20 and int(_db.adventure.curio_value("gp", "rare")) == 50 + and int(_db.adventure.curio_value("attack", "common")) == 1 and int(_db.adventure.curio_value("attack", "rare")) == 2 + and int(_db.adventure.curio_value("max_hp", "common")) == 5 and int(_db.adventure.curio_value("max_hp", "rare")) == 10 + and int(_db.adventure.curio_value("armor", "common")) == 1 and int(_db.adventure.curio_value("armor", "rare")) == 2, "数值对(v2.2):小GP 20/大GP 50 · 小增伤 +1/大 +2 · 小生命 +5/大 +10 · 小护甲 +1/大 +2") - check(int(_db.adventure.feature_value("heal", "rare")) == 6 and int(_db.adventure.feature_value("ap", "rarest")) == 1, + check(int(_db.adventure.curio_value("heal", "rare")) == 6 and int(_db.adventure.curio_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.feature_value_max_hp_common) + var max_flat := int(_db.adventure.curio_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.feature_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.curio_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.feature_value("max_hp", "rare")) + 15) * k))) + var hp_ceiling := maxi(1, int(round(float(int(_db.hero.max_hp) + 3 * int(_db.adventure.curio_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,8 +594,8 @@ 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.feature_value("gp", "common")), - "奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" % int(_db.adventure.feature_value("gp", "common"))) + check(a13.apply_node_bonus("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.curio_value("gp", "common")), + "奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" % int(_db.adventure.curio_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)), @@ -3875,8 +3875,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.feature_value(kind, tier)), - "加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.adventure.feature_value(kind, tier))]) + check(int(a8.bonus.get(String(val_map[kind]), -999)) == val0 + int(_db.adventure.curio_value(kind, tier)), + "加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.adventure.curio_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 = {} @@ -3941,7 +3941,7 @@ func _smoke_shop_pool() -> void: for k in ps.keys(): var quota := 0 for tier in _db.adventure.TIERS: - quota += int(_db.adventure.feature_quota(String(k), tier)) + quota += int(_db.adventure.curio_quota(String(k), tier)) if int(ps[k]) < 0 or int(ps[k]) > quota: ps_ok = false check(ps_ok, "终局池余量在域 [0,配额](%s)" % str(ps)) From 8e2694de4998323d47ffdd4f0e79a9a1ab25b781 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 5 Oct 2026 18:44:51 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E5=A5=87=E7=89=A9=E6=89=B9T2=EF=BC=9A?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E5=B1=82bonus=5F*=E2=86=92curio=5F*=E5=85=A8?= =?UTF-8?q?=E6=97=8F=E6=8D=A2=E8=AF=8D=EF=BC=88=5Fcurio=5Fpending/deck/tak?= =?UTF-8?q?en/result/candidates+pick=5Fcurio/apply=5Fnode=5Fcurio/curio=5F?= =?UTF-8?q?choice=5Fpresented/curio=5Fresolved=E7=AD=89+=E4=B8=A4=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=94=B9=E5=90=8Dcurio=5Finteract=5Fcontroller/curio?= =?UTF-8?q?=5Fpick=5Foverlay=E5=90=AB.uid+CurioPickOverlay/CurioInteractCo?= =?UTF-8?q?ntroller=E7=B1=BB=E5=90=8D=EF=BC=89=EF=BC=9BT2=E8=A3=81?= =?UTF-8?q?=E7=82=B9=E7=94=B2=E6=A1=88=E8=87=AA=E8=A3=81=3D=E4=BA=94?= =?UTF-8?q?=E5=AD=98=E6=A1=A3=E9=94=AE=E5=BC=95=E5=8F=B7=E5=AD=97=E9=9D=A2?= =?UTF-8?q?=E9=87=8F=E7=A6=81=E6=94=B9=EF=BC=88=E5=85=A8=E4=BB=93=E5=AF=B9?= =?UTF-8?q?=E6=8B=8D=E5=85=AD=E5=BD=A2=E6=80=81=E5=85=A8=E7=AD=89=EF=BC=89?= =?UTF-8?q?=EF=BC=9B=5Fbonus=5Ftreasure=5Fcarry=E6=8C=89T0=E6=BE=84?= =?UTF-8?q?=E6=B8=85=E5=89=94=E9=99=A4=EF=BC=88=E7=A7=98=E5=AE=9D=E8=AF=AD?= =?UTF-8?q?=E4=B9=89=EF=BC=89=EF=BC=9B=E9=9D=9E=E5=A5=87=E7=89=A9bonus?= =?UTF-8?q?=E8=AF=AD=E4=B9=89=E4=BF=9D=E5=85=A8=EF=BC=88=E5=8A=A0=E7=AE=97?= =?UTF-8?q?=E5=8C=BAbonus/price=5Flevel=5Fbonus/attack=5Fbonus/bp=5Fbonus?= =?UTF-8?q?=E7=AD=89=EF=BC=89=EF=BC=9Breward=5Foverlay=5Ftest=E7=BA=A2?= =?UTF-8?q?=E8=BD=AC=E7=BB=BF=E9=80=82=E9=85=8D=EF=BC=88M7=20bcard?= =?UTF-8?q?=E6=8D=A2PanelContainer=E8=87=B4c=20is=20Button=E6=81=920?= =?UTF-8?q?=E2=80=94=E2=80=94=E6=94=B9=E6=95=B0=5Fchoice=5Fbox=E5=AD=90?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E6=95=B0=E6=96=AD=E8=A8=80=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=E4=B8=8D=E5=8F=98=EF=BC=8Crun=5Fall=2016=E5=A5=97=E5=85=A8?= =?UTF-8?q?=E7=BB=BF=E5=8E=86=E5=8F=B2=E9=A6=96=E6=AC=A1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/adventure_runner.gd | 226 +++++++++--------- game/core/config_db.gd | 2 +- game/core/level_runner.gd | 8 +- game/data/schemas/shop_table.gd | 2 +- game/presentation/battle_narrator.gd | 2 +- ...roller.gd => curio_interact_controller.gd} | 80 +++---- ...d.uid => curio_interact_controller.gd.uid} | 0 game/presentation/main.gd | 38 +-- ..._pick_overlay.gd => curio_pick_overlay.gd} | 4 +- ...erlay.gd.uid => curio_pick_overlay.gd.uid} | 0 .../views/hero_info_panel_view.gd | 28 +-- game/smoke/level_autoplay.gd | 26 +- game/smoke/smoke_all.gd | 106 ++++---- game/smoke/ui_click_probe.gd | 40 ++-- game/smoke/ui_struct_probe.gd | 6 +- .../tests/unit/core/breakpoint_resume_test.gd | 18 +- .../unit/presentation/reward_overlay_test.gd | 11 +- 17 files changed, 298 insertions(+), 299 deletions(-) rename game/presentation/{bonus_interact_controller.gd => curio_interact_controller.gd} (82%) rename game/presentation/{bonus_interact_controller.gd.uid => curio_interact_controller.gd.uid} (100%) rename game/presentation/views/{bonus_pick_overlay.gd => curio_pick_overlay.gd} (99%) rename game/presentation/views/{bonus_pick_overlay.gd.uid => curio_pick_overlay.gd.uid} (100%) diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index 3df74700..b044da92 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -41,9 +41,9 @@ class_name AdventureRunner extends RefCounted signal level_started(level_index: int, display_name: String) # 每关开始(含冒险首关)——呈现层重取 runner()、刷新图条与层名行 -signal bonus_choice_presented(payload: Dictionary) # 提升三选一停点(非末关通关恰一次;迭代 10 大奖励重构——big_reward_paid 退役)——{level_index, candidates, heal_gained, hp_before, hp_after, keystone_gained, keystone_total, gp, aux_cleared} -signal bonus_resolved(result: Dictionary) # 三选一兑付完成(pick_bonus/替换受理后恰一次)——{picked, value, hp_before, hp_after, level_index} -signal treasure_replace_presented(items: Array, incoming: Dictionary) # 同调位满位替换子交互(pick_bonus 道具件满位时)——items=现有持有件记录、incoming=新件记录 +signal curio_choice_presented(payload: Dictionary) # 提升三选一停点(非末关通关恰一次;迭代 10 大奖励重构——big_reward_paid 退役)——{level_index, candidates, heal_gained, hp_before, hp_after, keystone_gained, keystone_total, gp, aux_cleared} +signal curio_resolved(result: Dictionary) # 三选一兑付完成(pick_curio/替换受理后恰一次)——{picked, value, hp_before, hp_after, level_index} +signal treasure_replace_presented(items: Array, incoming: Dictionary) # 同调位满位替换子交互(pick_curio 道具件满位时)——items=现有持有件记录、incoming=新件记录 signal adventure_finished(summary: Dictionary) # 冒险结算(completed/failed/abandoned)——summary 五件事:打穿没有/通关几关/止步第几关/钥石合计/主种子(+additive keystone_drops 迭代 10) # ---- 转发族(签名与 LevelRunner 同款——呈现层对接透明;ADR-0001 S6 经 _forward_* 方法)---- signal node_entered(node: Dictionary, info: Dictionary) @@ -79,16 +79,16 @@ var attune_charges := {} # 同调主动件冒险充能账本( var _save_manager = null # 断点承载注入(迭代 8 卡①——null = 不写断点;模拟/冒烟缺省) 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 _curio_taken := {} # 件获取次数计数(kind 维度派生账本——断点附随件与摘要读数承载;池库存正主=_curio_deck) +var _curio_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 载荷副本——中断续玩不重兑付) +var _curio_pending := {} # 停点态:{"candidates": Array, "payload": Dictionary, "chosen"/"incoming": 替换在途}——空 = 无停点 +var _curio_result := {} # 已兑付待推进态(curio_resolved 载荷副本——中断续玩不重兑付) var _bonus_treasure_carry: Array[String] = [] # 道具件跨关携带(兑付后注入下一关库存——清库后授予的体验窗) var _keystone_drops: Array = [] # Boss 逐杀掉落明细(additive 摘要键 keystone_drops——结算明细/复盘归因数据源) var _last_draw_treasure: Array[String] = [] # 最近一次抽签掷定的道具件(掷定前置——卡片直显具体道具;追加批二) -var _node_bonus_pending := {} # 奖励节点三选一停点态(迭代 10 追加批——共享池;断点不载:续玩重建到选路停点重走,恢复的计数器保证同候选重抽) +var _node_curio_pending := {} # 奖励节点三选一停点态(迭代 10 追加批——共享池;断点不载:续玩重建到选路停点重走,恢复的计数器保证同候选重抽) var _shop_mode := "legacy" # 商店模式(迭代 11 第②批 T3——"legacy"=固定三件加定价〔off 对照承载,缺省零扰动〕/"pool"=共享池抽 3 买 1 重抽;真实入口 main.gd 显式投 pool) @@ -114,7 +114,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool: purchased = {} keystone = 0 summary = {} - _reset_bonus_state() + _reset_curio_state() levels_total = db.level_count() # 表访问收口(2026-10-02——行集直扫退役) level_index = 1 _pending_next_level = false @@ -174,10 +174,10 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: levels_total = db.level_count() # 表访问收口(2026-10-02——行集直扫退役) level_index = int(bp.get("level_index", 1)) _pending_next_level = false - _reset_bonus_state() + _reset_curio_state() var bp_taken: Dictionary = bp.get("bonus_taken", {}) as Dictionary - for id in _bonus_taken.keys(): - _bonus_taken[id] = maxi(0, int(bp_taken.get(id, 0))) + for id in _curio_taken.keys(): + _curio_taken[id] = maxi(0, int(bp_taken.get(id, 0))) # 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径; # 迭代 12 T1——relic 令牌 relic_id 随还原) var bp_deck = bp.get("bonus_deck", null) @@ -189,7 +189,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: if String(rtok["kind"]) == "relic": rtok["relic_id"] = String(tok.get("relic_id", "")) restored.append(rtok) - _bonus_deck = restored + _curio_deck = restored # 特性持有集还原(迭代 12 T1——缺键 = 旧版断点防御空集) _relics = [] for r in (bp.get("relics", []) as Array): @@ -223,28 +223,28 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: migrated.append({"kind": String((c as Dictionary).get("kind", "")), "tier": String((c as Dictionary).get("tier", ""))}) else: migrated.append({"kind": String(c), "tier": db.adventure.TIER_COMMON}) - _bonus_pending = {"candidates": migrated} + _curio_pending = {"candidates": migrated} if bp_pending.has("aux_detail"): - _bonus_pending["aux_detail"] = (bp_pending.get("aux_detail", []) as Array).duplicate(true) # 掷定前置随停点快照续接 + _curio_pending["aux_detail"] = (bp_pending.get("aux_detail", []) as Array).duplicate(true) # 掷定前置随停点快照续接 var payload: Dictionary = (bp_pending.get("payload", {}) as Dictionary).duplicate(true) if bp_pending.has("incoming"): - _bonus_pending["chosen"] = "treasure_item" + _curio_pending["chosen"] = "treasure_item" var incoming: Array = (bp_pending.get("incoming", []) as Array).duplicate(true) - _bonus_pending["incoming"] = incoming + _curio_pending["incoming"] = incoming state = "running" - bonus_choice_presented.emit(payload) + curio_choice_presented.emit(payload) # 替换在途续发(呈现层直落替换子态——持有件记录按恢复后 carry 重建) treasure_replace_presented.emit(_carry_records(), {"index": -1, "item_id": String(incoming[0]) if not incoming.is_empty() else "", "count": incoming.size()}) return true state = "running" - bonus_choice_presented.emit(payload) + curio_choice_presented.emit(payload) return true if not bp_result.is_empty(): _pending_next_level = true - _bonus_result = bp_result.duplicate(true) + _curio_result = bp_result.duplicate(true) state = "running" - bonus_resolved.emit(_bonus_result.duplicate(true)) + curio_resolved.emit(_curio_result.duplicate(true)) return true _resume_bp = bp.duplicate(true) # 迭代 11 第①批 T6——充能账本随断点回局(缺键 = 旧档/无断点,保持 _seed_attune_charges 种子态) @@ -256,21 +256,21 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: # 三选一停点态归零(冒险开始/结算收束共用;令牌池按表配额重建——池消耗即上限,账本随池重生) -func _reset_bonus_state() -> void: - _bonus_taken = {"attack": 0, "max_hp": 0, "armor": 0, "gp": 0, "ap": 0, "heal": 0} - _bonus_deck = _build_bonus_deck() +func _reset_curio_state() -> void: + _curio_taken = {"attack": 0, "max_hp": 0, "armor": 0, "gp": 0, "ap": 0, "heal": 0} + _curio_deck = _build_curio_deck() _relics = [] - _bonus_pending = {} - _bonus_result = {} + _curio_pending = {} + _curio_result = {} _bonus_treasure_carry = [] _keystone_drops = [] - _node_bonus_pending = {} + _node_curio_pending = {} # 令牌池构建(TP0 v2.2——按表 kind×tier 配额展开为持久多重集;构建序确定 = CURIO_KINDS×TIERS 逐层, # 随机性只发生在洗牌消费 reward_pick 封闭流的时刻。迭代 12 T1:规则改写型特性带=relic 活行每件 1 张追加 # (rarity common/rare→稀有令牌档、rarest→罕见令牌档——relic 永不入普通档;空表=零追加零扰动)) -func _build_bonus_deck() -> Array: +func _build_curio_deck() -> Array: var deck: Array = [] for kind in db.adventure.CURIO_KINDS: for tier in db.adventure.TIERS: @@ -326,11 +326,11 @@ func continue_to_next_level() -> bool: if not _pending_next_level: push_error("[冒险结构] continue_to_next_level 拒绝——无待确认的关卡推进") return false - if not _bonus_pending.is_empty(): + if not _curio_pending.is_empty(): push_error("[冒险结构] continue_to_next_level 拒绝——三选一停点未兑付(零自动推进契约)") return false _pending_next_level = false - _bonus_result = {} + _curio_result = {} level_index += 1 return _start_current_level() @@ -353,22 +353,22 @@ func _find_token(candidates: Array, kind: String, tier: String, relic_id: String # 另须 relic_id)。属性件写加算区、AP 件写第四轴、GP 件走钱包「奖励入账」入口、恢复件当即回血、 # relic 件入特性持有集(🔴 层级纪律:只走局内随机渠道)、道具件受同调位约束(随池种退役不可达)。 # 消费=选中令牌踢池(未选回池由「抽签不摘牌、兑付才摘牌」语义承载——TP0 v2.2)。 -func pick_bonus(kind: String, tier: String = "", relic_id: String = "") -> bool: - if state != "running" or _bonus_pending.is_empty(): - push_error("[冒险结构] pick_bonus 拒绝——无待选停点(state=%s)" % state) +func pick_curio(kind: String, tier: String = "", relic_id: String = "") -> bool: + if state != "running" or _curio_pending.is_empty(): + push_error("[冒险结构] pick_curio 拒绝——无待选停点(state=%s)" % state) return false - var candidates: Array = _bonus_pending.get("candidates", []) as Array + var candidates: Array = _curio_pending.get("candidates", []) as Array if _find_token(candidates, kind, tier, relic_id).is_empty(): - push_error("[冒险结构] pick_bonus 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id]) + push_error("[冒险结构] pick_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id]) return false var value: int = int(db.adventure.curio_value(kind, tier)) var hp_before := int(ledger.get("hero_hp", 0)) if kind == "treasure_item": return _pick_treasure_item(value) # 道具件分派通关渠道(持有容器 = 跨关携带——与节点渠道分轨;池种退役后不可达) - if not _apply_bonus_pick(kind, tier, value, relic_id): - push_error("[冒险结构] pick_bonus 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id]) + if not _apply_curio_pick(kind, tier, value, relic_id): + push_error("[冒险结构] pick_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id]) return false - _finish_bonus_pick(kind, value, hp_before, tier, relic_id) + _finish_curio_pick(kind, value, hp_before, tier, relic_id) return true @@ -383,9 +383,9 @@ func _relic_payload_keys(relic_id: String) -> Dictionary: # 三选一件兑付共享体(2026-10-02 修复批双胞胎收口;TP0 改令牌消费——摘牌单点;迭代 12 T1 relic 分支): # 属性件写加算区 + 件获取计数、AP 件写第四轴、GP 件走钱包、恢复件当即回血(封顶生效上限—— # 与账本置满/通关回血同式 effective_max_hp)、relic 件入持有集(散件单体保底——效果接线面 -# 按挂点消费,档位质变由 RelicsEngine 求值)。兑付即从 _bonus_deck 摘除该令牌(选中踢池; +# 按挂点消费,档位质变由 RelicsEngine 求值)。兑付即从 _curio_deck 摘除该令牌(选中踢池; # 未选候选从未摘牌=自动回池)。道具件不入此体——未知件返回 false 由调用方按渠道名报错。 -func _apply_bonus_pick(kind: String, tier: String, value: int, relic_id: String = "") -> bool: +func _apply_curio_pick(kind: String, tier: String, value: int, relic_id: String = "") -> bool: if kind == "relic" and db.relic_row(relic_id).is_empty(): push_error("[冒险结构] relic 兑付失败——表无此件: %s" % relic_id) return false # 先校验后摘牌(令牌零丢失) @@ -395,28 +395,28 @@ func _apply_bonus_pick(kind: String, tier: String, value: int, relic_id: String if kind == "relic": if not _relics.has(relic_id): _relics.append(relic_id) - _bonus_taken["relic"] = int(_bonus_taken.get("relic", 0)) + 1 + _curio_taken["relic"] = int(_curio_taken.get("relic", 0)) + 1 return true match kind: "attack": bonus["atk"] = int(bonus.get("atk", 0)) + value - _bonus_taken["attack"] = int(_bonus_taken.get("attack", 0)) + 1 + _curio_taken["attack"] = int(_curio_taken.get("attack", 0)) + 1 "max_hp": bonus["max_hp"] = int(bonus.get("max_hp", 0)) + value - _bonus_taken["max_hp"] = int(_bonus_taken.get("max_hp", 0)) + 1 + _curio_taken["max_hp"] = int(_curio_taken.get("max_hp", 0)) + 1 "armor": bonus["armor_class"] = int(bonus.get("armor_class", 0)) + value - _bonus_taken["armor"] = int(_bonus_taken.get("armor", 0)) + 1 + _curio_taken["armor"] = int(_curio_taken.get("armor", 0)) + 1 "ap": bonus["ap"] = int(bonus.get("ap", 0)) + value - _bonus_taken["ap"] = int(_bonus_taken.get("ap", 0)) + 1 + _curio_taken["ap"] = int(_curio_taken.get("ap", 0)) + 1 "gp": if _runner != null: _runner.grant_bonus_gp(value) - _bonus_taken["gp"] = int(_bonus_taken.get("gp", 0)) + 1 + _curio_taken["gp"] = int(_curio_taken.get("gp", 0)) + 1 "heal": ledger["hero_hp"] = mini(int(ledger.get("hero_hp", 0)) + value, effective_max_hp()) - _bonus_taken["heal"] = int(_bonus_taken.get("heal", 0)) + 1 + _curio_taken["heal"] = int(_curio_taken.get("heal", 0)) + 1 _: return false return true @@ -424,13 +424,13 @@ func _apply_bonus_pick(kind: String, tier: String, value: int, relic_id: String # 从牌库摘除一枚令牌(TP0——选中踢池单点;迭代 12 T1 relic 按 relic_id 精确摘;找不到 = false) func _consume_deck_token(kind: String, tier: String, relic_id: String = "") -> bool: - for i in _bonus_deck.size(): - var tok: Dictionary = _bonus_deck[i] as Dictionary + for i in _curio_deck.size(): + var tok: Dictionary = _curio_deck[i] as Dictionary if String(tok.get("kind", "")) != kind or String(tok.get("tier", "")) != tier: continue if kind == "relic" and String(tok.get("relic_id", "")) != relic_id: continue - _bonus_deck.remove_at(i) + _curio_deck.remove_at(i) return true return false @@ -443,27 +443,27 @@ func relics_held() -> Array: # ---- 商店共享池消费面(迭代 11 第②批 T3——P1 共享池单点账本;TP0 令牌池换代——库存=牌库剩余令牌)---- # 库存余量(TP0:stock = 牌库内该 kind 剩余令牌数——「池消耗即上限」,种类次数上限退役; -# 奖励三选一兑付摘牌 与 商店购买消费 由同一 _bonus_deck 承载——两渠道跨关共库,全冒险一次性)。 +# 奖励三选一兑付摘牌 与 商店购买消费 由同一 _curio_deck 承载——两渠道跨关共库,全冒险一次性)。 func pool_stock(kind: String) -> int: var n := 0 - for t in _bonus_deck: + for t in _curio_deck: if String((t as Dictionary).get("kind", "")) == kind: n += 1 return n # 商店购买消费 handler(LevelRunner.buy_pool_item 经 Callable 调用——ADR-0001 S6 方法引用): -# 消费在架令牌本体(kind+tier 精确——货架卡即令牌,TP0 令牌级货架)→ _apply_bonus_pick 同口 +# 消费在架令牌本体(kind+tier 精确——货架卡即令牌,TP0 令牌级货架)→ _apply_curio_pick 同口 # 写加算区+账本+摘牌(与奖励三选一真单点——买一件=奖励面少一件的库存语义由同库成立)。 # 未知 kind 拒绝(货架词表封闭的前置防御)。 func _consume_pool_purchase(kind: String, tier: String) -> bool: if not db.adventure.CURIO_KINDS.has(kind): push_error("[冒险结构] 商店购买消费拒绝——件不在池词表: %s" % kind) return false - return _apply_bonus_pick(kind, tier, int(db.adventure.curio_value(kind, tier))) + return _apply_curio_pick(kind, tier, int(db.adventure.curio_value(kind, tier))) -# 终局池快照(_settle 清账前调用——_reset_bonus_state 会重置 _bonus_taken,摘要 additive 键 +# 终局池快照(_settle 清账前调用——_reset_curio_state 会重置 _curio_taken,摘要 additive 键 # pool_stock 须在此前取数;货架四种 stock=cap−taken,读数面 = 商店买走 vs 奖励领走 vs 枯竭) func _pool_stock_snapshot() -> Dictionary: var out := {} @@ -474,30 +474,30 @@ func _pool_stock_snapshot() -> Dictionary: # 道具件兑付(同调位约束):有位直接入跨关携带;满位转替换子交互(incoming 暂存、等 resolve_treasure_replace)。 -# 掷定前置(追加批二):具体道具随候选抽定(_last_draw_treasure / _bonus_pending.aux_detail)——本函数消费预掷值, +# 掷定前置(追加批二):具体道具随候选抽定(_last_draw_treasure / _curio_pending.aux_detail)——本函数消费预掷值, # 断点续玩/无预掷值时回退当场掷定(流序不同但消费数不变,确定性由调用序保证)。 # 持有容器 = 跨关携带 _bonus_treasure_carry(与节点渠道的关卡域库存分轨——容器差异保守不合,注记在案)。 func _pick_treasure_item(count: int) -> bool: - var picked: Array = _draw_pending_treasure(_bonus_pending, count, true) + var picked: Array = _draw_pending_treasure(_curio_pending, count, true) if picked.is_empty(): - push_error("[冒险结构] pick_bonus(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)") + push_error("[冒险结构] pick_curio(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)") return false var hp_before := int(ledger.get("hero_hp", 0)) # 迭代 11 第①批 T7——同调位守卫随过渡键退役拆除(aux 卡出池后本路径不可达;能力件容量闸 # 正主 = 营地装配面 Attunement.slots 满位禁选) for iid in picked: _bonus_treasure_carry.append(String(iid)) - _finish_bonus_pick("treasure_item", picked.size(), hp_before) + _finish_curio_pick("treasure_item", picked.size(), hp_before) return true # 替换子交互受理(discard_index = -1 放弃新件不获取;否则丢该件腾位收新)——终态恒 ≤ 同调位 # 持有容器 = 跨关携带(序号寻址);与节点渠道(库存句柄寻址)的丢弃/授予/收尾三步保守不合。 func resolve_treasure_replace(discard_index: int) -> bool: - if not _replace_in_transit(_bonus_pending): + if not _replace_in_transit(_curio_pending): push_error("[冒险结构] resolve_treasure_replace 拒绝——无替换在途") return false - var incoming: Array = _bonus_pending.get("incoming", []) as Array + var incoming: Array = _curio_pending.get("incoming", []) as Array var hp_before := int(ledger.get("hero_hp", 0)) if discard_index >= 0: if discard_index >= _bonus_treasure_carry.size(): @@ -507,15 +507,15 @@ func resolve_treasure_replace(discard_index: int) -> bool: for iid in incoming: _bonus_treasure_carry.append(String(iid)) # 放弃新件(discard_index < 0)= 原持有集不动、新件不获取 - _finish_bonus_pick("treasure_item", incoming.size(), hp_before) + _finish_curio_pick("treasure_item", incoming.size(), hp_before) return true # 兑付收口(恰一次):清停点态 → 记已兑付态 → emit → 停点快照(等继续确认——中断续玩不重兑付)。 # picked = kind;tier 随载荷(TP0——大小档位读数面);relic 伴随键随载荷(迭代 12 T1——反馈行取数) -func _finish_bonus_pick(picked: String, value: int, hp_before: int, tier: String = "", relic_id: String = "") -> void: - _bonus_pending = {} - _bonus_result = { +func _finish_curio_pick(picked: String, value: int, hp_before: int, tier: String = "", relic_id: String = "") -> void: + _curio_pending = {} + _curio_result = { "picked": picked, "tier": tier, "value": value, @@ -523,8 +523,8 @@ func _finish_bonus_pick(picked: String, value: int, hp_before: int, tier: String "hp_after": int(ledger.get("hero_hp", 0)), "level_index": level_index, } - _bonus_result.merge(_relic_payload_keys(relic_id)) - bonus_resolved.emit(_bonus_result.duplicate(true)) + _curio_result.merge(_relic_payload_keys(relic_id)) + curio_resolved.emit(_curio_result.duplicate(true)) _snapshot_breakpoint() @@ -533,30 +533,30 @@ func _finish_bonus_pick(picked: String, value: int, hp_before: int, tier: String # 候选呈现(节点到达时调用——恰一 reward_pick scope 洗牌 + 道具件掷定 scope;本停点断点不载, # 续玩重建到选路停点后重走重抽,恢复的全局计数器保证同候选逐位复现)。 # 掷定前置(追加批二):返回 dict 带 aux_detail(道具件具体道具)供卡片直显。 -func node_bonus_candidates() -> Dictionary: - var cands: Array = _draw_bonus_candidates() +func node_curio_candidates() -> Dictionary: + var cands: Array = _draw_curio_candidates() return {"candidates": cands, "aux_detail": _last_draw_treasure.duplicate()} -func present_node_bonus(candidates: Array, treasure_detail: Array = []) -> void: +func present_node_curio(candidates: Array, treasure_detail: Array = []) -> void: _reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转) - _node_bonus_pending = {"candidates": candidates.duplicate(true), "aux_detail": treasure_detail.duplicate(true)} + _node_curio_pending = {"candidates": candidates.duplicate(true), "aux_detail": treasure_detail.duplicate(true)} # 点选兑付(节点域——无部分回血无钥石;令牌消费与牌库摘牌与通关三选一同库;relic 同权入持有集) -func apply_node_bonus(kind: String, tier: String = "", relic_id: String = "") -> bool: - if _node_bonus_pending.is_empty(): - push_error("[冒险结构] apply_node_bonus 拒绝——无待选停点") +func apply_node_curio(kind: String, tier: String = "", relic_id: String = "") -> bool: + if _node_curio_pending.is_empty(): + push_error("[冒险结构] apply_node_curio 拒绝——无待选停点") return false - var candidates: Array = _node_bonus_pending.get("candidates", []) as Array + var candidates: Array = _node_curio_pending.get("candidates", []) as Array if _find_token(candidates, kind, tier, relic_id).is_empty(): - push_error("[冒险结构] apply_node_bonus 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id]) + push_error("[冒险结构] apply_node_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id]) return false var value: int = int(db.adventure.curio_value(kind, tier)) if kind == "treasure_item": return _apply_node_treasure(value) # 道具件分派节点渠道(持有容器 = 关卡域库存——与通关渠道分轨;池种退役后不可达) - if not _apply_bonus_pick(kind, tier, value, relic_id): - push_error("[冒险结构] apply_node_bonus 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id]) + if not _apply_curio_pick(kind, tier, value, relic_id): + push_error("[冒险结构] apply_node_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id]) return false _finish_node_bonus(kind, value, tier, relic_id) return true @@ -564,11 +564,11 @@ func apply_node_bonus(kind: String, tier: String = "", relic_id: String = "") -> # 节点渠道道具件(同调位约束):持有=关卡域库存+跨关携带,有位直接入本关库存(当关可用); # 满位转替换子交互(丢弃目标=库存件 handle——与通关渠道的 carry 序号分轨,容器语义保守不合)。 -# 掷定前置:消费 _node_bonus_pending.aux_detail 预掷值(缺省回退当场掷定)。 +# 掷定前置:消费 _node_curio_pending.aux_detail 预掷值(缺省回退当场掷定)。 func _apply_node_treasure(count: int) -> bool: - var picked: Array = _draw_pending_treasure(_node_bonus_pending, count, false) + var picked: Array = _draw_pending_treasure(_node_curio_pending, count, false) if picked.is_empty(): - push_error("[冒险结构] apply_node_bonus(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)") + push_error("[冒险结构] apply_node_curio(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)") return false var records: Array = [] if _runner != null and _runner.treasure_inventory != null: @@ -585,10 +585,10 @@ func _apply_node_treasure(count: int) -> bool: func _resolve_node_replace(discard_handle: int) -> bool: - if not _replace_in_transit(_node_bonus_pending): + if not _replace_in_transit(_node_curio_pending): push_error("[冒险结构] resolve_node_replace 拒绝——无替换在途") return false - var incoming: Array = _node_bonus_pending.get("incoming", []) as Array + var incoming: Array = _node_curio_pending.get("incoming", []) as Array if discard_handle >= 0: if _runner == null or _runner.treasure_inventory == null or not _runner.treasure_inventory.discard_item(discard_handle): push_error("[冒险结构] resolve_node_replace 拒绝——句柄不在持有集: %d" % discard_handle) @@ -629,40 +629,40 @@ func attune_refresh_left() -> int: # 节点停点在途态读回(组合根重 present 用——candidates/aux_detail 值拷贝) -func node_bonus_pending_state() -> Dictionary: +func node_curio_pending_state() -> Dictionary: return { - "candidates": (_node_bonus_pending.get("candidates", []) as Array).duplicate(true), - "aux_detail": (_node_bonus_pending.get("aux_detail", []) as Array).duplicate(true), + "candidates": (_node_curio_pending.get("candidates", []) as Array).duplicate(true), + "aux_detail": (_node_curio_pending.get("aux_detail", []) as Array).duplicate(true), } # 三选一刷新(组合根唯一调用口;无停点/无余量 false 零副作用)。通关停点重抽后重发 -# bonus_choice_presented(呈现层重建卡片+刷新余量);节点停点由组合根读 node_bonus_pending_state 重 present。 -func refresh_bonus() -> bool: +# curio_choice_presented(呈现层重建卡片+刷新余量);节点停点由组合根读 node_curio_pending_state 重 present。 +func refresh_curio() -> bool: if _attune_refresh_x <= 0 and _attune_refresh_y <= 0: return false - if not _bonus_pending.is_empty(): + if not _curio_pending.is_empty(): if _attune_refresh_x > 0: _attune_refresh_x -= 1 else: _attune_refresh_y -= 1 - var cands: Array = _draw_bonus_candidates() - _bonus_pending["candidates"] = cands.duplicate(true) - _bonus_pending["aux_detail"] = _last_draw_treasure.duplicate() - var payload: Dictionary = (_bonus_pending.get("payload", {}) as Dictionary).duplicate(true) + var cands: Array = _draw_curio_candidates() + _curio_pending["candidates"] = cands.duplicate(true) + _curio_pending["aux_detail"] = _last_draw_treasure.duplicate() + var payload: Dictionary = (_curio_pending.get("payload", {}) as Dictionary).duplicate(true) payload["candidates"] = cands payload["aux_detail"] = _last_draw_treasure.duplicate() - bonus_choice_presented.emit(payload) # 同信号重 present(零自动推进契约不破——停点仍在途) + curio_choice_presented.emit(payload) # 同信号重 present(零自动推进契约不破——停点仍在途) _snapshot_breakpoint() return true - if not _node_bonus_pending.is_empty(): + if not _node_curio_pending.is_empty(): if _attune_refresh_x > 0: _attune_refresh_x -= 1 else: _attune_refresh_y -= 1 - var cands2: Array = _draw_bonus_candidates() - _node_bonus_pending["candidates"] = cands2.duplicate(true) - _node_bonus_pending["aux_detail"] = _last_draw_treasure.duplicate() + var cands2: Array = _draw_curio_candidates() + _node_curio_pending["candidates"] = cands2.duplicate(true) + _node_curio_pending["aux_detail"] = _last_draw_treasure.duplicate() return true return false @@ -694,14 +694,14 @@ func _draw_pending_treasure(pending: Dictionary, count: int, allow_detail_fallba # 替换受理分发(单入口——节点停点在途优先按库存句柄,否则按通关停点的携带序号) -func resolve_bonus_replace(discard: int) -> bool: - if not _node_bonus_pending.is_empty() and _node_bonus_pending.has("incoming"): +func resolve_curio_replace(discard: int) -> bool: + if not _node_curio_pending.is_empty() and _node_curio_pending.has("incoming"): return _resolve_node_replace(discard) return resolve_treasure_replace(discard) func _finish_node_bonus(picked: String, value: int, tier: String = "", relic_id: String = "") -> void: - _node_bonus_pending = {} + _node_curio_pending = {} var payload := { "context": "reward_node", "picked": picked, @@ -710,7 +710,7 @@ func _finish_node_bonus(picked: String, value: int, tier: String = "", relic_id: "level_index": level_index, } payload.merge(_relic_payload_keys(relic_id)) - bonus_resolved.emit(payload) + curio_resolved.emit(payload) # 令牌池洗牌不放回抽 3(TP0 v2.2——Fisher-Yates 全库洗牌后按序收集;**同 kind 去重每 kind 至多 @@ -719,9 +719,9 @@ func _finish_node_bonus(picked: String, value: int, tier: String = "", relic_id: # 自动回池;池耗即枯竭——余库不足 N kind 时出齐剩余,空库 = 零候选合法态)。候选 = 令牌字典 # {"kind","tier"}。消费 reward_pick 封闭流——一次停点恰一 scope,同种子同候选;道具件掷定前置 # 随池种退役拆除(_last_draw_treasure 恒空集,机械惰性承载)。 -func _draw_bonus_candidates() -> Array: +func _draw_curio_candidates() -> Array: _last_draw_treasure = [] - var pool := _bonus_deck.duplicate() + var pool := _curio_deck.duplicate() var rg: RandomNumberGenerator = rng.open_stream("reward_pick") for i in pool.size(): var j := rg.randi_range(i, pool.size() - 1) @@ -832,7 +832,7 @@ func _start_current_level() -> bool: _runner.set_tree_rules(_tree_rules) # 迭代 12 T10——树 rule 键集下传(A/B 面消费) if _shop_mode == "pool": # 商店共享池注入(迭代 11 第②批 T3——库存账本引用+消费 handler 双下传;legacy 模式零注入零扰动) - _runner.set_shop_pool(_bonus_deck, Callable(self, "_consume_pool_purchase")) + _runner.set_shop_pool(_curio_deck, Callable(self, "_consume_pool_purchase")) # 三选一道具件跨关携带注入(迭代 10)——迭代 11 第①批 T7 退役:aux 卡出池后 carry 恒空集, # 注入面随停点渠道退役拆除(容器字段保留=替换子交互机械惰性承载,零投放零行为) level_started.emit(level_index, String(_runner.level_row.get("display_name", ""))) @@ -876,7 +876,7 @@ func _on_level_finished(payload: Dictionary) -> void: _reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转) var pick_payload: Dictionary = { "level_index": level_index, - "candidates": _draw_bonus_candidates(), + "candidates": _draw_curio_candidates(), "aux_detail": _last_draw_treasure.duplicate(), # 掷定前置(追加批二——道具件具体道具随候选抽定,卡片直显) "heal_gained": heal, "hp_before": hp_before, @@ -886,10 +886,10 @@ func _on_level_finished(payload: Dictionary) -> void: "gp": int(ledger.get("gp", 0)), "aux_cleared": int(payload.get("aux_cleared", 0)), # additive 扩签(迭代 7 T4——关卡末清空反馈取数) } - _bonus_pending = {"candidates": (pick_payload["candidates"] as Array).duplicate(true), + _curio_pending = {"candidates": (pick_payload["candidates"] as Array).duplicate(true), "aux_detail": _last_draw_treasure.duplicate(), "payload": pick_payload.duplicate(true)} - bonus_choice_presented.emit(pick_payload) + curio_choice_presented.emit(pick_payload) _snapshot_breakpoint() # 选择停点可中断(契约 5——续玩不重抽不静默丢失) return # 末关通关:不发三选一与部分回血(没有下一战可受益)、Boss 钥石照常入账——冒险完成结算 @@ -920,7 +920,7 @@ func _settle(result: String) -> void: } if _runner != null: _runner.close_wallet() # GP 冒险收束清零(三结局都清——经转发发 wallet_changed "clear" 供日志/呈现) - _reset_bonus_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化) + _reset_curio_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化) if _save_manager != null: _save_manager.clear_breakpoint() # 三结局清断点(迭代 8 卡①/T3——退出冒险=放弃流程+清除断点) _save_manager.write_to_disk() @@ -974,11 +974,11 @@ func _snapshot_breakpoint() -> void: "keystone": keystone, "visited_ids": _runner.visited_ids() if _runner != null else [], "aux_items": treasure_snapshot, - "bonus_taken": _bonus_taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载) - "bonus_deck": _bonus_deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原) + "bonus_taken": _curio_taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载) + "bonus_deck": _curio_deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原) "relics": _relics.duplicate(), # 特性持有集(迭代 12 T1——局内随机渠道获得态续玩防丢失) - "bonus_pending": _bonus_pending.duplicate(true), # 三选一停点态(空 = 无停点——选择停点可中断契约) - "bonus_result": _bonus_result.duplicate(true), # 已兑付待推进态(空 = 无——中断续玩不重兑付) + "bonus_pending": _curio_pending.duplicate(true), # 三选一停点态(空 = 无停点——选择停点可中断契约) + "bonus_result": _curio_result.duplicate(true), # 已兑付待推进态(空 = 无——中断续玩不重兑付) "bonus_aux_carry": _bonus_treasure_carry.duplicate(true), # 道具件跨关携带(注入前中断防静默丢失) "keystone_drops": _keystone_drops.duplicate(true), # 逐杀掉落明细(摘要归因续接——迭代 10) } diff --git a/game/core/config_db.gd b/game/core/config_db.gd index 45a6c13a..6f8a1bc9 100644 --- a/game/core/config_db.gd +++ b/game/core/config_db.gd @@ -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_curio_deck 消费;行 rarity→投放档映射) func relic_rows() -> Array: return relics.rows if relics != null else [] diff --git a/game/core/level_runner.gd b/game/core/level_runner.gd index 99a12096..cc29876c 100644 --- a/game/core/level_runner.gd +++ b/game/core/level_runner.gd @@ -69,8 +69,8 @@ var _attune_charges: Dictionary = {} # 同调主动件充能账本( # 关卡末清空+快照计数入 level_finished 载荷) # ---- 商店共享池模式态(迭代 11 第②批 T3——缺省 legacy=固定三件加定价 off 对照承载,零 RNG 零池消费;TP0 令牌池换代)---- var _shop_pool_mode := false # pool 模式旗(set_shop_pool 注入时置位——AdventureRunner 按 params.shop_mode 下传) -var _pool_deck: Array = [] # 共享池令牌库共享引用(AdventureRunner._bonus_deck——库存=剩余令牌数单点) -var _pool_consume := Callable() # 池消费 handler(AdventureRunner._consume_pool_purchase——_apply_bonus_pick 同口写加算区+账本+摘牌) +var _pool_deck: Array = [] # 共享池令牌库共享引用(AdventureRunner._curio_deck——库存=剩余令牌数单点) +var _pool_consume := Callable() # 池消费 handler(AdventureRunner._consume_pool_purchase——_apply_curio_pick 同口写加算区+账本+摘牌) var _shop_shelf: Array = [] # 当前货架(进店抽 3/买后全重抽——令牌字典 {"kind","tier"} 序,同 kind 去重每 kind 至多一卡) var _shop_visit_buys := 0 # 本次进店已购次数(A 链定价输入——离开清零) var _shop_paid_refreshes := 0 # 本次进店已付费刷新次数(MC-1——GP 刷新价曲线输入,离开清零) @@ -640,7 +640,7 @@ func grant_treasure_item(item_id: String, source: String) -> Dictionary: # wallet_changed 载荷带 source 供呈现归因) func grant_bonus_gp(amount: int) -> void: gp = maxi(0, gp + amount) - wallet_changed.emit("gain", gp, {"amount": amount, "source": "bonus_pick"}) + wallet_changed.emit("gain", gp, {"amount": amount, "source": "curio_pick"}) # ---- 玩家输入转发(呈现层只与 runner 对话)---- @@ -877,7 +877,7 @@ func shop_refresh() -> bool: # 购买(pool 模式唯一购买口):在架(kind→在架令牌)/余额前置校验零副作用 → handler 消费在架 -# 令牌本体(kind+tier——_apply_bonus_pick 同口写加算区+账本+摘牌,跨渠道共库)→ 出账 → 进店购买 +# 令牌本体(kind+tier——_apply_curio_pick 同口写加算区+账本+摘牌,跨渠道共库)→ 出账 → 进店购买 # 次计数 → wallet 事件 → 三件全重抽(未买不保留——重抽绑购买,无独立刷新动作) func buy_pool_item(kind: String) -> bool: var shelf_tok := {} diff --git a/game/data/schemas/shop_table.gd b/game/data/schemas/shop_table.gd index 46b2acb7..f8e5ac70 100644 --- a/game/data/schemas/shop_table.gd +++ b/game/data/schemas/shop_table.gd @@ -11,7 +11,7 @@ # 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 切边); -# 库存=上限即库存(账本在 冒险结构 _bonus_taken 单点共享,行族不记库存);price_a/price_b = 乘法价格 +# 库存=上限即库存(账本在 冒险结构 _curio_taken 单点共享,行族不记库存);price_a/price_b = 乘法价格 # 因子(公式 ceil(base × b^(关序−1) × A^(本次进店已购次数)),provisional 投值——P5 甲研究拍板后置追认)。 # products 行族 + 加法定价随 legacy 模式(shop=off 对照承载)保留,退役归 off 锚换代批。 # 行值全部 provisional 归 难度曲线与Boss(用户拍板骨架先行、数值整体后置)。 diff --git a/game/presentation/battle_narrator.gd b/game/presentation/battle_narrator.gd index 323cd6f7..851cb5c3 100644 --- a/game/presentation/battle_narrator.gd +++ b/game/presentation/battle_narrator.gd @@ -81,7 +81,7 @@ static func boss_name(runner, db) -> String: # 三选一停点头部行(回血反馈 + 选择引导;钥石增量行不上本面——单一展示面归战斗总结结算框, # level-clear.md v2 禁入清单口径) -static func bonus_pick_header(payload: Dictionary) -> String: +static func curio_pick_header(payload: Dictionary) -> String: var lines: Array[String] = ["第 %d 层通过!" % int(payload.get("level_index", 0))] var heal := int(payload.get("heal_gained", 0)) if heal > 0: diff --git a/game/presentation/bonus_interact_controller.gd b/game/presentation/curio_interact_controller.gd similarity index 82% rename from game/presentation/bonus_interact_controller.gd rename to game/presentation/curio_interact_controller.gd index 87abb5a2..cfe121d8 100644 --- a/game/presentation/bonus_interact_controller.gd +++ b/game/presentation/curio_interact_controller.gd @@ -2,17 +2,17 @@ # 提升三选一与替换子交互控制器(迭代 13 T4/D4——二期家族① 自 main.gd 迁出;规范 1.1 单一职责): # 三选一停点编排(presented→pick→confirm/refresh→resolved→continue)+ 同调位替换子态 #(presented→chosen)+ TP0 令牌池卡片文案装配("kind|tier|relic_id" 复合键)+ 工坊显示名投影。 -# 装配纪律(ADR-0001,仿 treasure_interact_controller 先例):信号连接不在此发生——bonus_overlay +# 装配纪律(ADR-0001,仿 treasure_interact_controller 先例):信号连接不在此发生——curio_overlay # 四信号与 adventure 两信号由组合根接至本类 on_* 入口;模态栈/演播后排窗/呈现刷新/终局面/ # 推进失败 fatal 经组合根回调注入;adventure/runner 随局更替——现值回调注入;兑付写调用唯一 -# 经 adventure.pick_bonus/apply_node_bonus/resolve_bonus_replace(core 侧单口,本类零直写)。 +# 经 adventure.pick_curio/apply_node_curio/resolve_curio_replace(core 侧单口,本类零直写)。 extends RefCounted const Narrator := preload("res://presentation/battle_narrator.gd") var db var _meta_get: Callable # () → 元进度账本现值(组合根 lambda——探针 isolate_save 换注面,迭代13 T4 实证) -var bonus_overlay: BonusPickOverlay +var curio_overlay: CurioPickOverlay var end_overlay: BattleEndOverlay var board_view: BoardView var toast: ToastStack @@ -27,20 +27,20 @@ var _refresh_presentations: Callable var _fatal_screen: Callable var _gp_summary: Callable # () → 战斗总结 GP 行文案(组合根 lambda——_last_gain_amount 取数面在根) -var _pending_bonus := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用) -var _bonus_context := "level_clear" # 三选一面上下文(level_clear=通关停点→推进下一关 / reward_node=奖励节点→回图选路) +var _pending_curio := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用) +var _curio_context := "level_clear" # 三选一面上下文(level_clear=通关停点→推进下一关 / reward_node=奖励节点→回图选路) var _last_replace_items: Array = [] # 替换子态续玩重建缓存(treasure_replace_presented 载荷) var _last_replace_incoming := {} # 同上——incoming 记录 -func _init(db_ref, meta_get: Callable, bonus_overlay_ref: BonusPickOverlay, end_overlay_ref: BattleEndOverlay, +func _init(db_ref, meta_get: Callable, curio_overlay_ref: CurioPickOverlay, end_overlay_ref: BattleEndOverlay, board_view_ref: BoardView, toast_ref: ToastStack, save_get: Callable, adventure_get: Callable, runner_get: Callable, adventure_mode_get: Callable, open_modal: Callable, open_modal_after_playback: Callable, close_modal: Callable, refresh_presentations: Callable, fatal_screen: Callable, gp_summary: Callable) -> void: db = db_ref _meta_get = meta_get - bonus_overlay = bonus_overlay_ref + curio_overlay = curio_overlay_ref end_overlay = end_overlay_ref board_view = board_view_ref toast = toast_ref @@ -58,12 +58,12 @@ func _init(db_ref, meta_get: Callable, bonus_overlay_ref: BonusPickOverlay, end_ # 停点在途判据(组合根 _on_battle_end_continue 分派面——战斗总结确认后开三选一) func has_pending() -> bool: - return not _pending_bonus.is_empty() + return not _pending_curio.is_empty() # 续玩停点恢复入口(true=已接手呈现,调用方中止后续分派) func resume_if_pending() -> bool: - if _pending_bonus.is_empty(): + if _pending_curio.is_empty(): return false show_pick() return true @@ -74,8 +74,8 @@ func resume_if_pending() -> bool: func on_presented(payload: Dictionary) -> void: if int(payload.get("aux_cleared", 0)) > 0: toast.show_toast("上关剩余 %d 件秘宝已随关卡结束清空" % int(payload.get("aux_cleared", 0)), "info") # 迭代 7 T4 清空反馈 - _pending_bonus = payload.duplicate(true) - _bonus_context = "level_clear" + _pending_curio = payload.duplicate(true) + _curio_context = "level_clear" if bool(payload.get("replace_pending", false)): # 续玩:替换子交互在途中断——重建三选一面后直落替换子态(候选不重抽) show_pick() @@ -88,26 +88,26 @@ func on_presented(payload: Dictionary) -> void: # 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 treasure_replace_presented 续发直落子态) -# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;BonusPickOverlay 共用面, +# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;CurioPickOverlay 共用面, # 道具件满位替换子交互同构)。原「秘宝投放渠道」三选一(迭代 7 过渡形态)随批退役。 -func present_node_bonus() -> void: +func present_node_curio() -> void: var adventure = _adventure_get.call() - _bonus_context = "reward_node" - var node_draw: Dictionary = adventure.node_bonus_candidates() + _curio_context = "reward_node" + var node_draw: Dictionary = adventure.node_curio_candidates() var node_cands: Array = node_draw.get("candidates", []) as Array - _open_modal.call(bonus_overlay) - bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(node_cands, node_draw.get("aux_detail", []) as Array)) - adventure.present_node_bonus(node_cands, node_draw.get("aux_detail", []) as Array) - bonus_overlay.set_refresh_info(adventure.attune_refresh_left()) # 迭代 11 第①批 T5b——刷新余量投喂 + _open_modal.call(curio_overlay) + curio_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(node_cands, node_draw.get("aux_detail", []) as Array)) + adventure.present_node_curio(node_cands, node_draw.get("aux_detail", []) as Array) + curio_overlay.set_refresh_info(adventure.attune_refresh_left()) # 迭代 11 第①批 T5b——刷新余量投喂 # 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 treasure_replace_presented 续发直落子态) func show_pick() -> void: - var payload: Dictionary = _pending_bonus - _open_modal.call(bonus_overlay) - bonus_overlay.open_pick(Narrator.bonus_pick_header(payload), _bonus_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array)) + var payload: Dictionary = _pending_curio + _open_modal.call(curio_overlay) + curio_overlay.open_pick(Narrator.curio_pick_header(payload), _bonus_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array)) var adventure = _adventure_get.call() - bonus_overlay.set_refresh_info(adventure.attune_refresh_left() if adventure != null else 0) # 迭代 11 第①批 T5b——刷新余量投喂 + curio_overlay.set_refresh_info(adventure.attune_refresh_left() if adventure != null else 0) # 迭代 11 第①批 T5b——刷新余量投喂 _refresh_presentations.call() @@ -157,18 +157,18 @@ func _treasure_card_line(iid: String) -> String: return "%s · %s" % [String(row.get("display_name", iid)), " ".join(parts)] -# 三选一刷新(迭代 11 第①批 T5b——P1 甲 X/Y 双源;通关停点经重发 bonus_choice_presented 自然重建, +# 三选一刷新(迭代 11 第①批 T5b——P1 甲 X/Y 双源;通关停点经重发 curio_choice_presented 自然重建, # 节点停点在此读在途态重 present——零自动推进契约不破,停点仍在途) func on_refresh() -> void: var adventure = _adventure_get.call() if adventure == null: return - if not adventure.refresh_bonus(): + if not adventure.refresh_curio(): return - if _bonus_context == "reward_node": - var st: Dictionary = adventure.node_bonus_pending_state() - bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(st.get("candidates", []) as Array, st.get("aux_detail", []) as Array)) - bonus_overlay.set_refresh_info(adventure.attune_refresh_left()) + if _curio_context == "reward_node": + var st: Dictionary = adventure.node_curio_pending_state() + curio_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(st.get("candidates", []) as Array, st.get("aux_detail", []) as Array)) + curio_overlay.set_refresh_info(adventure.attune_refresh_left()) func on_pick_confirmed(item_id: String) -> void: @@ -180,7 +180,7 @@ func on_pick_confirmed(item_id: String) -> void: var kind := parts[0] var tier := parts[1] if parts.size() > 1 else "" var rid := parts[2] if parts.size() > 2 else "" - var ok: bool = adventure.apply_node_bonus(kind, tier, rid) if _bonus_context == "reward_node" else adventure.pick_bonus(kind, tier, rid) + var ok: bool = adventure.apply_node_curio(kind, tier, rid) if _curio_context == "reward_node" else adventure.pick_curio(kind, tier, rid) if not ok: push_error("[bonus_interact] 三选一兑付被拒——见输出日志") @@ -188,8 +188,8 @@ func on_pick_confirmed(item_id: String) -> void: func on_treasure_replace_presented(items: Array, incoming: Dictionary) -> void: _last_replace_items = items.duplicate(true) _last_replace_incoming = incoming.duplicate(true) - if not bonus_overlay.visible: - _open_modal.call(bonus_overlay) # 续玩替换在途直落——三选一面未开时先入栈(模态栈纪律) + if not curio_overlay.visible: + _open_modal.call(curio_overlay) # 续玩替换在途直落——三选一面未开时先入栈(模态栈纪律) _open_replace(items, incoming) @@ -199,29 +199,29 @@ func _open_replace(items: Array, incoming: Dictionary) -> void: var rec: Dictionary = it.duplicate() rec["display_name"] = String(item_name_lookup().get(String(rec.get("item_id", "")), String(rec.get("item_id", "")))) named.append(rec) - bonus_overlay.open_replace(named, String(item_name_lookup().get(String(incoming.get("item_id", "")), String(incoming.get("item_id", "")))), + curio_overlay.open_replace(named, String(item_name_lookup().get(String(incoming.get("item_id", "")), String(incoming.get("item_id", "")))), named.size(), Attunement.slots(db, _save_get.call())) # 迭代 11 第①批 T7——呈现读数源切谓词表求值(过渡键退役) func on_replace_chosen(discard: int) -> void: var adventure = _adventure_get.call() - if adventure == null or not adventure.resolve_bonus_replace(discard): + if adventure == null or not adventure.resolve_curio_replace(discard): push_error("[bonus_interact] 同调位替换受理被拒——见输出日志") -# 兑付反馈(bonus_resolved 到达——结果行上屏,唯一出口 = 继续) +# 兑付反馈(curio_resolved 到达——结果行上屏,唯一出口 = 继续) func on_resolved(result: Dictionary) -> void: - _pending_bonus = {} - bonus_overlay.show_result(Narrator.bonus_result_text(result)) + _pending_curio = {} + curio_overlay.show_result(Narrator.bonus_result_text(result)) _refresh_presentations.call() # 继续钮:收三选一面 → 上下文分派(通关停点=推进下一关 / 奖励节点=兑付确认回图选路——零自动推进) func on_continue() -> void: - _close_modal.call(bonus_overlay) - bonus_overlay.close_pick() + _close_modal.call(curio_overlay) + curio_overlay.close_pick() var adventure = _adventure_get.call() - if _bonus_context == "reward_node": + if _curio_context == "reward_node": var runner = _runner_get.call() if runner != null: runner.continue_after_node() diff --git a/game/presentation/bonus_interact_controller.gd.uid b/game/presentation/curio_interact_controller.gd.uid similarity index 100% rename from game/presentation/bonus_interact_controller.gd.uid rename to game/presentation/curio_interact_controller.gd.uid diff --git a/game/presentation/main.gd b/game/presentation/main.gd index f4b44fd9..ffc46185 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -38,7 +38,7 @@ const DebugSessionScript := preload("res://presentation/debug_session.gd") const RowPacer := preload("res://presentation/row_pacer.gd") # 结算行排播泵(T3 拆件批) const TreasureInteractController := preload("res://presentation/treasure_interact_controller.gd") # 秘宝两步交互(T3 拆件批) const GpShadowLedgerScript := preload("res://presentation/gp_shadow_ledger.gd") # GP 影子账本(T3 拆件批) -const BonusInteractController := preload("res://presentation/bonus_interact_controller.gd") # 三选一与替换子交互(T4/D4 拆件批) +const CurioInteractController := preload("res://presentation/curio_interact_controller.gd") # 三选一与替换子交互(T4/D4 拆件批) const CampShopPresenter := preload("res://presentation/camp_shop_presenter.gd") # 营地/商店投影(T4/D4 拆件批) const SaveManagerScript := preload("res://core/save_manager.gd") # 存档承载(preload 避开全局类缓存时序——与 DebugSessionScript 同款) const MetaProgressionScript := preload("res://core/meta_progression.gd") # 元进度账本服务(元进度实现批——同 preload 纪律) @@ -66,7 +66,7 @@ var _settlement_stop_pending: bool: # 挂起态属性化(探针/冒烟直读 _settlement_stop_timer.disarm() var _pending_candidates: Array = [] # 结算框挂起期的选路候选暂存(choices_presented 载荷) var _last_gain_amount := 0 # 最近一笔钱包入账(卡⑥ GP 增量取数面——wallet_changed gain 载荷) -var bonus_overlay: BonusPickOverlay # 提升三选一覆盖层(迭代 10——level-clear.md v2 选择面承载;追加批起奖励节点共用) +var curio_overlay: CurioPickOverlay # 提升三选一覆盖层(迭代 10——level-clear.md v2 选择面承载;追加批起奖励节点共用) var _start_overlay: Control var _end_overlay: BattleEndOverlay var _inter_overlay: Control @@ -91,7 +91,7 @@ var settlement_fx: SettlementFxView # 结算演出覆盖层(跨区弹道 # T3 拆件批(2026-10-02)——三件迁出后的组合根持有面(职责见各类头注;装配见 _build_ui) var _row_pacer = null # 结算行排播泵(row_pacer.gd——本类只入队/排空/读队列) var _treasure_ui = null # 秘宝两步交互(treasure_interact_controller.gd——本类只接线与转发意图) -var _bonus = null # 三选一与替换子交互(bonus_interact_controller.gd——本类只接线与回调注入) +var _bonus = null # 三选一与替换子交互(curio_interact_controller.gd——本类只接线与回调注入) var _camp_pres = null # 营地/商店投影(camp_shop_presenter.gd——本类只接线、探针缝保缝转发) var _gp_ledger := GpShadowLedgerScript.new() # GP 冒险钱包影子账(gp_shadow_ledger.gd——wallet 事件投喂) @@ -261,20 +261,20 @@ func _build_ui() -> void: add_child(settings_overlay) settings_overlay.closed.connect(_on_settings_closed) # 提升三选一覆盖层(迭代 10——z 序 = 节点层之上、终局层之下;确认后同拍推进) - bonus_overlay = BonusPickOverlay.new() - add_child(bonus_overlay) - # 提升三选一与替换子交互(T4/D4 拆件批——迁 bonus_interact_controller.gd;本类只接线与回调注入, + curio_overlay = CurioPickOverlay.new() + add_child(curio_overlay) + # 提升三选一与替换子交互(T4/D4 拆件批——迁 curio_interact_controller.gd;本类只接线与回调注入, # ADR-0001——模态栈/演播后排窗/呈现刷新/fatal 经回调注入,adventure/runner 现值回调) - _bonus = BonusInteractController.new(db, func() -> Variant: return meta, bonus_overlay, _end_overlay, board_view, + _bonus = CurioInteractController.new(db, func() -> Variant: return meta, curio_overlay, _end_overlay, board_view, board_zone.toast, func() -> Variant: return save_manager, func() -> Variant: return adventure, func() -> Variant: return runner, func() -> Variant: return _adventure_mode, _open_modal, _open_modal_after_playback, _close_modal, _refresh_presentations, _fatal_screen, func() -> String: return _gp_summary_text({"gp_dropped": _last_gain_amount})) - bonus_overlay.pick_confirmed.connect(_bonus.on_pick_confirmed) - bonus_overlay.replace_chosen.connect(_bonus.on_replace_chosen) - bonus_overlay.continue_pressed.connect(_bonus.on_continue) - bonus_overlay.refresh_requested.connect(_bonus.on_refresh) - move_child(bonus_overlay, _end_overlay.get_index()) + curio_overlay.pick_confirmed.connect(_bonus.on_pick_confirmed) + curio_overlay.replace_chosen.connect(_bonus.on_replace_chosen) + curio_overlay.continue_pressed.connect(_bonus.on_continue) + curio_overlay.refresh_requested.connect(_bonus.on_refresh) + move_child(curio_overlay, _end_overlay.get_index()) var start: Dictionary = UiAssembler.build_start_overlay(self, db) _start_overlay = start.overlay @@ -329,7 +329,7 @@ func _build_ui() -> void: # 模态登记(缺陷⑤纪律——登记序 = 树序:含 move_child 调位后的关卡画面/三选一终位) _modals = ModalStack.new() - for m: Control in [legend_overlay, node_overlay, level_screen, bonus_overlay, _end_overlay, shop_overlay, camp_overlay, settings_overlay, confirm_dialog, debug_overlay]: + for m: Control in [legend_overlay, node_overlay, level_screen, curio_overlay, _end_overlay, shop_overlay, camp_overlay, settings_overlay, confirm_dialog, debug_overlay]: _register_modal(m) _tune_sim = TuneSimController.new(db, debug_overlay, int(db.hero.max_hp)) @@ -450,8 +450,8 @@ func _wire_adventure() -> void: adventure.choices_presented.connect(_on_choices_presented) adventure.wallet_changed.connect(_on_wallet_changed) # 迭代 10 奖励重构批——big_reward_paid 退役,三选一信号族接线 - adventure.bonus_choice_presented.connect(_bonus.on_presented) - adventure.bonus_resolved.connect(_bonus.on_resolved) + adventure.curio_choice_presented.connect(_bonus.on_presented) + adventure.curio_resolved.connect(_bonus.on_resolved) adventure.treasure_replace_presented.connect(_bonus.on_treasure_replace_presented) adventure.adventure_finished.connect(_on_adventure_finished) adventure.level_started.connect(_on_adventure_level_started) @@ -505,9 +505,9 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void: _show_board_fx_for_node(t) log_view.append_bb(Narrator.node_banner(runner, node, t)) elif t == "reward": - # 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;BonusPickOverlay 共用面)。 - # T4/D4 拆件:呈现编排归 bonus_interact_controller.present_node_bonus(_bonus_context 随迁)。 - _bonus.present_node_bonus() + # 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;CurioPickOverlay 共用面)。 + # T4/D4 拆件:呈现编排归 curio_interact_controller.present_node_curio(_curio_context 随迁)。 + _bonus.present_node_curio() log_view.append_bb(Narrator.reward_line()) elif t == "rest": var healed: int = int(info.get("healed", 0)) @@ -564,7 +564,7 @@ func _on_battle_end_continue() -> void: _close_modal(_end_overlay) _end_overlay.close() # 迭代 10——三选一停点挂起分支:战斗总结结算(Boss 胜利钥石增量行)确认后进三选一面 - if _bonus.resume_if_pending(): # T4/D4 拆件——三选一停点在途恢复入口(bonus_interact_controller) + if _bonus.resume_if_pending(): # T4/D4 拆件——三选一停点在途恢复入口(curio_interact_controller) return # 卡⑥ 挂起分派:战斗胜利总结结算框的「下一节点」≠ 终局——进选路页(暂存候选恢复呈现) if _settlement_stop_pending: diff --git a/game/presentation/views/bonus_pick_overlay.gd b/game/presentation/views/curio_pick_overlay.gd similarity index 99% rename from game/presentation/views/bonus_pick_overlay.gd rename to game/presentation/views/curio_pick_overlay.gd index 567ef910..9a03e3a9 100644 --- a/game/presentation/views/bonus_pick_overlay.gd +++ b/game/presentation/views/curio_pick_overlay.gd @@ -4,7 +4,7 @@ # 同调位替换子交互。高内聚边界:文案拼装归 Narrator(组合根喂入),选择意图经信号外发; # 视图零核心引用。交互范式 = node_overlay choice_box 按钮族 + 选路两段式(点选→确认)复用,零新原语。 # 状态机:pick(三卡点选→确认)→ [replace(满位 N+1 选 N)] → result(兑付反馈→继续)。 -class_name BonusPickOverlay +class_name CurioPickOverlay extends Control signal pick_confirmed(item_id: String) @@ -162,7 +162,7 @@ func selected_id() -> String: return _selected -# 兑付反馈态(bonus_resolved 到达——结果行追加在头部之后;唯一出口 = 继续) +# 兑付反馈态(curio_resolved 到达——结果行追加在头部之后;唯一出口 = 继续) func show_result(text: String) -> void: _label.text = _header + "\n" + text _choice_box.visible = false diff --git a/game/presentation/views/bonus_pick_overlay.gd.uid b/game/presentation/views/curio_pick_overlay.gd.uid similarity index 100% rename from game/presentation/views/bonus_pick_overlay.gd.uid rename to game/presentation/views/curio_pick_overlay.gd.uid diff --git a/game/presentation/views/hero_info_panel_view.gd b/game/presentation/views/hero_info_panel_view.gd index 0326a0c4..7c05ff86 100644 --- a/game/presentation/views/hero_info_panel_view.gd +++ b/game/presentation/views/hero_info_panel_view.gd @@ -19,8 +19,8 @@ var item_slots: HBoxContainer # 秘宝槽(迭代 7 T4 接线— var _slot_panels: Array = [] # 槽 PanelContainer 引用(选中高亮/置灰重刷) var _selected_handle := 0 # 选中态(0=无;组合根回写维持单源) var _slot_handles: Array = [] # 槽位→handle 映射(set_treasure_inventory 刷新) -var _bonus_section: VBoxContainer # 特性(预留——空态隐藏,接线归大奖励/锻造呈现批) -var _bonus_list: VBoxContainer +var _curio_section: VBoxContainer # 特性(预留——空态隐藏,接线归大奖励/锻造呈现批) +var _curio_list: VBoxContainer var _relic_section: VBoxContainer # 特性持有段(迭代 12 呈现补齐——空态隐藏) var _relic_list: VBoxContainer var _board_variant_section: VBoxContainer # 地形变异常驻角标(全局作用域变异首发批——空态隐藏) @@ -93,14 +93,14 @@ func _init() -> void: _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_list = VBoxContainer.new() - _bonus_list.add_theme_constant_override("separation", 2) - _bonus_section.add_child(_bonus_list) + _curio_section = VBoxContainer.new() + _curio_section.add_theme_constant_override("separation", 4) + _curio_section.visible = false + col.add_child(_curio_section) + _curio_section.add_child(UiKit.label("特性", 16, true)) + _curio_list = VBoxContainer.new() + _curio_list.add_theme_constant_override("separation", 2) + _curio_section.add_child(_curio_list) func set_attack(atk: int, mod_text: String) -> void: @@ -137,13 +137,13 @@ func set_board_variants(lines: Array) -> void: # 特性列表(预留接线面):lines = 文案行数组(如「锻造 +2 攻击」)——空数组 = 整节隐藏 func set_level_bonuses(lines: Array) -> void: - for c in _bonus_list.get_children(): - _bonus_list.remove_child(c) + for c in _curio_list.get_children(): + _curio_list.remove_child(c) c.queue_free() for line in lines: var l := UiKit.label(String(line), 16, false, COL_INK) # 字号下限(v3.5 批 B3——正文 ≥16) - _bonus_list.add_child(l) - _bonus_section.visible = not lines.is_empty() + _curio_list.add_child(l) + _curio_section.visible = not lines.is_empty() func attack_text() -> String: diff --git a/game/smoke/level_autoplay.gd b/game/smoke/level_autoplay.gd index 8ccb32cd..2a1d5e56 100644 --- a/game/smoke/level_autoplay.gd +++ b/game/smoke/level_autoplay.gd @@ -151,8 +151,8 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_ adr.wallet_changed.connect(_on_adventure_wallet.bind(st, logger)) # 三选一停点驱动(迭代 10 奖励重构批——big_reward_paid 退役:确定性策略候选取首件、 # 道具件满位替换放弃新件、兑付后推进下一关——零随机流自开,同种子逐位可复放) - adr.bonus_choice_presented.connect(_on_adventure_bonus.bind(st, adr)) - adr.bonus_resolved.connect(_on_adventure_bonus_resolved.bind(st, adr)) + adr.curio_choice_presented.connect(_on_adventure_bonus.bind(st, adr)) + adr.curio_resolved.connect(_on_adventure_curio_resolved.bind(st, adr)) adr.treasure_replace_presented.connect(_on_adventure_treasure_replace.bind(st, adr)) adr.adventure_finished.connect(_on_adventure_finished.bind(st, logger)) adr.set_hero_override(atk_pct, hp_pct) @@ -220,17 +220,17 @@ static func _on_adventure_node_entered(node: Dictionary, info: Dictionary, adr, elif t == "reward": # 奖励节点三选一(迭代 10 追加批——共享池确定性策略;TP0 起 316-f 判读:1 级最稀有、 # 同档并列取最左;替换放弃新件;兑付后 continue_after_node 回图选路) - var ndraw: Dictionary = adr.node_bonus_candidates() + var ndraw: Dictionary = adr.node_curio_candidates() var ncands: Array = ndraw.get("candidates", []) as Array - st.reward_events.append({"node_bonus_pick": ncands.duplicate(true)}) + st.reward_events.append({"node_curio_pick": ncands.duplicate(true)}) if ncands.is_empty(): r.continue_after_node() # 牌库空合法态直通(池耗即枯竭——防御保留) else: - adr.present_node_bonus(ncands, ndraw.get("aux_detail", []) as Array) + adr.present_node_curio(ncands, ndraw.get("aux_detail", []) as Array) var npick: Dictionary = _pick_token(ncands, String(st.get("relic_mode", "off"))) - if adr.apply_node_bonus(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))): - if not adr._node_bonus_pending.is_empty() and adr._node_bonus_pending.has("incoming"): - adr.resolve_bonus_replace(-1) # 满位替换:放弃新件 + if adr.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))): + if not adr._node_curio_pending.is_empty() and adr._node_curio_pending.has("incoming"): + adr.resolve_curio_replace(-1) # 满位替换:放弃新件 else: st.result = "advance_failed" elif t == "rest": @@ -260,13 +260,13 @@ static func _on_adventure_wallet(event: String, gp_val: int, extra: Dictionary, # 三选一停点驱动(迭代 10——确定性策略;TP0 起 316-f 判读:1 级最稀有、同档并列取最左; # 奖励事件随 reward_events 留档) static func _on_adventure_bonus(payload: Dictionary, st: Dictionary, adr) -> void: - st.reward_events.append({"bonus_pick": payload.duplicate(true)}) + st.reward_events.append({"curio_pick": payload.duplicate(true)}) var candidates: Array = payload.get("candidates", []) as Array if candidates.is_empty(): st.result = "advance_failed" return var pick: Dictionary = _pick_token(candidates, String(st.get("relic_mode", "off"))) - if not adr.pick_bonus(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("relic_id", ""))): + if not adr.pick_curio(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("relic_id", ""))): st.result = "advance_failed" @@ -304,13 +304,13 @@ static func _pick_token(candidates: Array, relic_mode: String = "off") -> Dictio # 同调位满位替换子交互(确定性策略:放弃新件不获取——分发面按停点上下文裁决节点/通关渠道) static func _on_adventure_treasure_replace(items: Array, incoming: Dictionary, st: Dictionary, adr) -> void: st.reward_events.append({"treasure_replace": incoming.duplicate(true)}) - if not adr.resolve_bonus_replace(-1): + if not adr.resolve_curio_replace(-1): st.result = "advance_failed" # 三选一兑付完成 → 上下文分派(通关停点=推进下一关 / 奖励节点=回图选路) -static func _on_adventure_bonus_resolved(result: Dictionary, st: Dictionary, adr) -> void: - st.reward_events.append({"bonus_resolved": result.duplicate(true)}) +static func _on_adventure_curio_resolved(result: Dictionary, st: Dictionary, adr) -> void: + st.reward_events.append({"curio_resolved": result.duplicate(true)}) if String(result.get("context", "")) == "reward_node": var r = adr.runner() if r == null or not r.continue_after_node(): diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index c613f74b..f2a9dc64 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -327,12 +327,12 @@ func _smoke_adventure() -> void: var resolves: Array = [] var node_picks := 0 for ev in (st["reward_events"] as Array): - if (ev as Dictionary).has("bonus_pick"): - picks.append((ev as Dictionary)["bonus_pick"]) - elif (ev as Dictionary).has("node_bonus_pick"): + if (ev as Dictionary).has("curio_pick"): + picks.append((ev as Dictionary)["curio_pick"]) + elif (ev as Dictionary).has("node_curio_pick"): node_picks += 1 # 奖励节点三选一(追加批——每关奖励节点各 1 次) - elif (ev as Dictionary).has("bonus_resolved"): - resolves.append((ev as Dictionary)["bonus_resolved"]) + elif (ev as Dictionary).has("curio_resolved"): + resolves.append((ev as Dictionary)["curio_resolved"]) check(picks.size() == 2 and resolves.size() == 2 + node_picks, "通关三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)") check(node_picks >= 1, "奖励节点三选一 ≥1 次(L1 链必经 1 个;L2/L3 图可选路经若干——实际经停 %d)" % node_picks) @@ -467,7 +467,7 @@ func _smoke_adventure() -> void: for ev in wallet: if String(ev["event"]) == "clear": clears += 1 - elif String(ev["event"]) == "gain" and String(ev.get("source", "")) != "bonus_pick": + elif String(ev["event"]) == "gain" and String(ev.get("source", "")) != "curio_pick": gains += 1 # 战斗完成入账计数(三选一 GP 袋件入账另有 source 标记——不计战斗口径) check(gains == int(st["battles"]), "每战完成入账恰一次(gain %d = battles %d——GP 袋件入账单列)" % [gains, int(st["battles"])]) check(clears == 1 and wallet.size() > 0 and String(wallet[wallet.size() - 1]["event"]) == "clear", @@ -505,13 +505,13 @@ func _smoke_adventure() -> void: check(acc_shop >= 3, "统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" % acc_shop) a8._on_level_finished({"result": "victory", "hp": int(r8.hero_hp), "node_index": r8.node_index, "node_id": r8.current_node_id(), "node_type": "shop"}) # 模拟 L2 通关(victory 载荷直调——非末关) - check(not a8._bonus_pending.is_empty(), "L2 通关三选一停点在案(直调兑付前——零自动推进)") + check(not a8._curio_pending.is_empty(), "L2 通关三选一停点在案(直调兑付前——零自动推进)") check(not a8.continue_to_next_level(), "未兑付推进拒绝(选择停点零自动推进契约)") - var l2_cands: Array = a8._bonus_pending["candidates"] + var l2_cands: Array = a8._curio_pending["candidates"] var l2_pick: Dictionary = l2_cands[0] - check(a8.pick_bonus(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", ""))), "三选一点选兑付受理(候选取首件)") - var picked2: String = String(a8._bonus_result.get("picked", "")) - var expect_acc: int = acc_shop + (int(a8._bonus_result.get("value", 0)) if picked2 == "max_hp" else 0) + check(a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", ""))), "三选一点选兑付受理(候选取首件)") + var picked2: String = String(a8._curio_result.get("picked", "")) + var expect_acc: int = acc_shop + (int(a8._curio_result.get("value", 0)) if picked2 == "max_hp" else 0) check(int(a8.bonus.get("max_hp", -1)) == expect_acc, "三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" % [int(a8.bonus.get("max_hp", -1)), acc_shop, picked2]) var max_eff8: int = a8.effective_max_hp() # T17 口径修复随批:账本生效上限含调试浮窗 pct(a8 已 set 400) @@ -545,11 +545,11 @@ func _smoke_adventure() -> void: var a11 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(11)) check(a11.start_adventure(11, {"first_script": true, "treasure_unlocked": {"blast_kit": true}}), "剔牌库/替换场景:冒险启动(seed 11——道具解锁集只含 blast_kit)") - for di in range(a11._bonus_deck.size() - 1, -1, -1): - var dk := String((a11._bonus_deck[di] as Dictionary).get("kind", "")) + for di in range(a11._curio_deck.size() - 1, -1, -1): + var dk := String((a11._curio_deck[di] as Dictionary).get("kind", "")) if dk == "attack" or dk == "ap": - a11._bonus_deck.remove_at(di) - var draw: Array = a11._draw_bonus_candidates() + a11._curio_deck.remove_at(di) + var draw: Array = a11._draw_curio_candidates() var draw_kinds := {} for t in draw: draw_kinds[String((t as Dictionary).get("kind", ""))] = true @@ -560,7 +560,7 @@ func _smoke_adventure() -> void: # white-box 测试随停点渠道退役拆除——机械惰性承载,零投放零行为) var treasure_seen := false for i in 40: - for t in a11._draw_bonus_candidates(): + for t in a11._draw_curio_candidates(): if String((t as Dictionary).get("kind", "")) == "treasure_item": treasure_seen = true break @@ -569,7 +569,7 @@ func _smoke_adventure() -> void: var a12 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(12)) a12.start_adventure(12, {"first_script": true}) var cap := {"payload": {}} - a12.bonus_choice_presented.connect(func(p): cap["payload"] = p) + a12.curio_choice_presented.connect(func(p): cap["payload"] = p) var max_full := int(_db.hero.max_hp) + int(a12._base_override.get("max_hp", 0)) a12.ledger["hero_hp"] = max_full a12._on_level_finished({"result": "victory", "hp": max_full, "node_index": 1, "node_id": 1, "node_type": "shop"}) @@ -579,10 +579,10 @@ func _smoke_adventure() -> void: var a13 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(13)) check(a13.start_adventure(13, {"first_script": true, "treasure_unlocked": {"blast_kit": true}}), "节点共享池场景:冒险启动(seed 13)") - for di13 in range(a13._bonus_deck.size() - 1, -1, -1): - if String((a13._bonus_deck[di13] as Dictionary).get("kind", "")) == "attack": - a13._bonus_deck.remove_at(di13) - var ndraw: Dictionary = a13.node_bonus_candidates() + for di13 in range(a13._curio_deck.size() - 1, -1, -1): + if String((a13._curio_deck[di13] as Dictionary).get("kind", "")) == "attack": + a13._curio_deck.remove_at(di13) + var ndraw: Dictionary = a13.node_curio_candidates() var ncands13: Array = ndraw.get("candidates", []) as Array var has_attack13 := false for t in ncands13: @@ -591,12 +591,12 @@ func _smoke_adventure() -> void: break check(not has_attack13, "奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" % str(ncands13)) - a13.present_node_bonus(ncands13, ndraw.get("aux_detail", []) as Array) - a13.present_node_bonus([{"kind": "gp", "tier": "common"}]) + a13.present_node_curio(ncands13, ndraw.get("aux_detail", []) as Array) + a13.present_node_curio([{"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.curio_value("gp", "common")), + check(a13.apply_node_curio("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.curio_value("gp", "common")), "奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" % int(_db.adventure.curio_value("gp", "common"))) - check(a13._node_bonus_pending.is_empty(), "节点停点态兑付后清零(恰一次语义同构)") + check(a13._node_curio_pending.is_empty(), "节点停点态兑付后清零(恰一次语义同构)") var s10: Dictionary = a10.summary check(String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)), "放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)") @@ -612,12 +612,12 @@ func _smoke_relics() -> void: 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: + for t in a_r0._curio_deck: if String((t as Dictionary).get("kind", "")) == "relic": relic_tokens += 1 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, + check(relic_tokens == 27 and rarest_tokens == 6 and a_r0._curio_deck.size() == 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 锚点) @@ -646,23 +646,23 @@ func _smoke_relics() -> void: 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 行临时表)") var deck_relic := 0 - for t in a_r._bonus_deck: + for t in a_r._curio_deck: if String((t as Dictionary).get("kind", "")) == "relic": deck_relic += 1 - check(deck_relic == 5 and a_r._bonus_deck.size() == 103, + check(deck_relic == 5 and a_r._curio_deck.size() == 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(): + for t in a_r._draw_curio_candidates(): if String((t as Dictionary).get("kind", "")) == "relic": relic_seen[String(t.get("relic_id", ""))] = true check(relic_seen.size() >= 2, "抽签出 relic 候选(60 次重抽 ≥2 种不同 relic——身份键 relic_id 同档可多件)") - a_r._bonus_pending = {"candidates": [{"kind": "relic", "tier": "rare", "relic_id": "m3"}, {"kind": "gp", "tier": "common"}], "payload": {}} + a_r._curio_pending = {"candidates": [{"kind": "relic", "tier": "rare", "relic_id": "m3"}, {"kind": "gp", "tier": "common"}], "payload": {}} var deck_m3 := 0 - for t in a_r._bonus_deck: + for t in a_r._curio_deck: if String((t as Dictionary).get("relic_id", "")) == "m3": deck_m3 += 1 - check(a_r.pick_bonus("relic", "rare", "m3") and (a_r.relics_held() as Array).has("m3") and a_r.pool_stock("relic") == deck_relic - 1, + check(a_r.pick_curio("relic", "rare", "m3") and (a_r.relics_held() as Array).has("m3") and a_r.pool_stock("relic") == deck_relic - 1, "relic 兑付:入持有集 + 牌库摘牌(选中踢池——池库存 %d→%d)" % [deck_relic, deck_relic - 1]) # 断点往返(持有集+余库 relic_id 随行) var sm_r = load("res://core/save_manager.gd").new("user://_smoke_relics_bp.json") @@ -771,10 +771,10 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool: return false if a._pending_next_level: # 三选一停点确认(迭代 10——确定性选件后推进,与 run_adventure 自动策略同口径;TP0 令牌化) - if not a._bonus_pending.is_empty(): - var bcands: Array = a._bonus_pending["candidates"] + if not a._curio_pending.is_empty(): + var bcands: Array = a._curio_pending["candidates"] var bpick: Dictionary = autoplay._pick_token(bcands) - a.pick_bonus(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("relic_id", ""))) + a.pick_curio(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("relic_id", ""))) a.continue_to_next_level() continue var r = a.runner() @@ -788,14 +788,14 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool: var t := String(r.current_node().get("type", "")) if t == "reward": # 奖励节点三选一(迭代 10 追加批——确定性选件、替换放弃新件、兑付确认回图;TP0 令牌化) - var ndraw: Dictionary = a.node_bonus_candidates() + var ndraw: Dictionary = a.node_curio_candidates() var ncands: Array = ndraw.get("candidates", []) as Array if not ncands.is_empty(): - a.present_node_bonus(ncands, ndraw.get("aux_detail", []) as Array) + a.present_node_curio(ncands, ndraw.get("aux_detail", []) as Array) var npick: Dictionary = autoplay._pick_token(ncands) - if a.apply_node_bonus(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))): - if not a._node_bonus_pending.is_empty() and a._node_bonus_pending.has("incoming"): - a.resolve_bonus_replace(-1) + if a.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))): + if not a._node_curio_pending.is_empty() and a._node_curio_pending.has("incoming"): + a.resolve_curio_replace(-1) r.continue_after_node() elif t == "rest": r.continue_after_node() @@ -3871,13 +3871,13 @@ func _smoke_shop_pool() -> void: var tier := String(buy_c.get("tier", "")) var val_map := {"attack": "atk", "max_hp": "max_hp", "armor": "armor_class", "ap": "ap"} var gp0: int = int(r8.gp) - var taken0: int = int(a8._bonus_taken.get(kind, 0)) + var taken0: int = int(a8._curio_taken.get(kind, 0)) 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.curio_value(kind, tier)), "加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.adventure.curio_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))]) + check(int(a8._curio_taken.get(kind, 0)) == taken0 + 1, "共享池账本 -1(跨渠道单点——taken %d → %d)" % [taken0, int(a8._curio_taken.get(kind, 0))]) # A 链:同visit 第二购现价 = ceil(base×b^(l2−1)×A^1)(货架已全重抽——新卡新价同公式) var second: Dictionary = {} for c in r8.shop_shelf(): @@ -3899,9 +3899,9 @@ func _smoke_shop_pool() -> void: cut_seen = true check(not cut_seen, "切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)") # ⑤ 池空硬闸(库存尽剔出牌库——TP0:white-box 耗尽 attack 令牌后货架零 attack + 消费口拒绝) - for di8 in range(a8._bonus_deck.size() - 1, -1, -1): - if String((a8._bonus_deck[di8] as Dictionary).get("kind", "")) == "attack": - a8._bonus_deck.remove_at(di8) + for di8 in range(a8._curio_deck.size() - 1, -1, -1): + if String((a8._curio_deck[di8] as Dictionary).get("kind", "")) == "attack": + a8._curio_deck.remove_at(di8) var attack_seen := false for i in 40: r8._shop_draw_shelf() @@ -3913,10 +3913,10 @@ func _smoke_shop_pool() -> void: check(not a8._consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before, "池空消费拒绝(handler 零副作用——买光即枯竭硬闸)") # ⑥ 跨渠道共库 shop→reward 向:回填一枚 attack 令牌 → 消费口受理 → 奖励牌库同步摘牌 - a8._bonus_deck.append({"kind": "attack", "tier": "common"}) - check(a8._consume_pool_purchase("attack", "common"), "商店消费口受理(_apply_bonus_pick 同口——真单点库)") + a8._curio_deck.append({"kind": "attack", "tier": "common"}) + check(a8._consume_pool_purchase("attack", "common"), "商店消费口受理(_apply_curio_pick 同口——真单点库)") var reward_attack_seen := false - for t6 in a8._draw_bonus_candidates(): + for t6 in a8._draw_curio_candidates(): if String((t6 as Dictionary).get("kind", "")) == "attack": reward_attack_seen = true break @@ -4265,11 +4265,11 @@ func _meta_settle_and_workshop() -> void: a12._attunement = ["pick_refresh"] a12._attune_refresh_x = 1 a12._attune_refresh_y = 2 - a12._bonus_pending = {"candidates": [{"kind": "gp", "tier": "common"}, {"kind": "attack", "tier": "common"}, {"kind": "max_hp", "tier": "common"}], "payload": {}} - check(a12.refresh_bonus() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2, + a12._curio_pending = {"candidates": [{"kind": "gp", "tier": "common"}, {"kind": "attack", "tier": "common"}, {"kind": "max_hp", "tier": "common"}], "payload": {}} + check(a12.refresh_curio() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2, "刷新双计数器——X 优先消耗(x1y2 → x0y2)") - check(a12.refresh_bonus() and a12.refresh_bonus() and a12._attune_refresh_y == 0 - and a12.refresh_bonus() == false, + check(a12.refresh_curio() and a12.refresh_curio() and a12._attune_refresh_y == 0 + and a12.refresh_curio() == false, "刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)") a12._reset_refresh_x() check(a12.attune_refresh_left() == 1, diff --git a/game/smoke/ui_click_probe.gd b/game/smoke/ui_click_probe.gd index 120c6618..ceb060c1 100644 --- a/game/smoke/ui_click_probe.gd +++ b/game/smoke/ui_click_probe.gd @@ -192,22 +192,22 @@ func _ready() -> void: main.node_overlay.press_continue() _flags_placed.clear() continue - if main.bonus_overlay.visible: - # 迭代 10 三选一停点(BonusPickOverlay——奖励节点/通关兑付共用面;T17 硬锁批适配—— + if main.curio_overlay.visible: + # 迭代 10 三选一停点(CurioPickOverlay——奖励节点/通关兑付共用面;T17 硬锁批适配—— # 本探针此前未适配该层,走到即卡死)。三态分派:替换子态(满位——放弃新件)/ # result 态(兑付反馈——点继续推进)/ 待选态(点首件+确认)。真实信号路径全线。 # T1 D1——三选一停点输入锁定断言(停点态断言面:替换子/待选/兑付三态共用此层)。 if not main._input_locked(): _problems.append("三选一停点打开时输入未锁定") - if main.bonus_overlay._replace_box.visible: - main.bonus_overlay.replace_chosen.emit(-1) - elif main.bonus_overlay._continue_btn.visible: - main.bonus_overlay.press_continue() - elif main.adventure != null and not main.adventure._bonus_pending.is_empty(): - main.bonus_overlay.press_card(0) - main.bonus_overlay.press_confirm() + if main.curio_overlay._replace_box.visible: + main.curio_overlay.replace_chosen.emit(-1) + elif main.curio_overlay._continue_btn.visible: + main.curio_overlay.press_continue() + elif main.adventure != null and not main.adventure._curio_pending.is_empty(): + main.curio_overlay.press_card(0) + main.curio_overlay.press_confirm() else: - main.bonus_overlay.press_continue() # 停点已消费的残面——继续关屏 + main.curio_overlay.press_continue() # 停点已消费的残面——继续关屏 _flags_placed.clear() continue if main.shop_overlay.visible: @@ -324,16 +324,16 @@ func _ready() -> void: main._end_overlay._btn.pressed.emit() else: break - elif main.bonus_overlay.visible: - if main.bonus_overlay._replace_box.visible: - main.bonus_overlay.replace_chosen.emit(-1) - elif main.bonus_overlay._continue_btn.visible: - main.bonus_overlay.press_continue() - elif main.adventure != null and not main.adventure._bonus_pending.is_empty(): - main.bonus_overlay.press_card(0) - main.bonus_overlay.press_confirm() + elif main.curio_overlay.visible: + if main.curio_overlay._replace_box.visible: + main.curio_overlay.replace_chosen.emit(-1) + elif main.curio_overlay._continue_btn.visible: + main.curio_overlay.press_continue() + elif main.adventure != null and not main.adventure._curio_pending.is_empty(): + main.curio_overlay.press_card(0) + main.curio_overlay.press_confirm() else: - main.bonus_overlay.press_continue() + main.curio_overlay.press_continue() elif main.node_overlay.visible: main.node_overlay.press_continue() elif main.shop_overlay.visible: @@ -609,7 +609,7 @@ func _locktest_tick() -> void: return _locktest_state = 1 elif _locktest_state == 1: - if main.bonus_overlay.visible or main._inter_overlay.visible or main._end_overlay.is_open() \ + if main.curio_overlay.visible or main._inter_overlay.visible or main._end_overlay.is_open() \ or String(settlement.battle_result) != "": _locktest_state = 2 # 前提消失守卫:终结演播竞态——静默弃测 return diff --git a/game/smoke/ui_struct_probe.gd b/game/smoke/ui_struct_probe.gd index 6c489519..444bb149 100644 --- a/game/smoke/ui_struct_probe.gd +++ b/game/smoke/ui_struct_probe.gd @@ -80,14 +80,14 @@ func _ready() -> void: main._end_overlay.close() _settle() - main.bonus_overlay.open_pick("结构探针·三选一", [ + main.curio_overlay.open_pick("结构探针·三选一", [ {"id": "probe_heal", "text": "回血\n恢复 3 点"}, {"id": "probe_magic", "text": "爆裂轰击\n下一击 +5"}, {"id": "probe_pierce", "text": "穿甲\n无视护甲"}, ]) _settle() - await _audit("bonus_pick", [main.bonus_overlay]) - main.bonus_overlay.visible = false + await _audit("curio_pick", [main.curio_overlay]) + main.curio_overlay.visible = false _settle() main.shop_overlay.open_shop([ diff --git a/game/tests/unit/core/breakpoint_resume_test.gd b/game/tests/unit/core/breakpoint_resume_test.gd index 0d30d15b..d3f966c5 100644 --- a/game/tests/unit/core/breakpoint_resume_test.gd +++ b/game/tests/unit/core/breakpoint_resume_test.gd @@ -93,9 +93,9 @@ func _init() -> void: adr_e.set_save_manager(sm_e) var params_e := {"hero_override": {}, "treasure_unlocked": {"blast_kit": true}} Kit.check_quiet(adr_e.start_adventure(4321, params_e), "P5 冒险启动(seed 4321)") - adr_e._bonus_taken["attack"] = 1 # 白盒预置计数(断点附随件往返的判据面——计数不可由加算区反推) + adr_e._curio_taken["attack"] = 1 # 白盒预置计数(断点附随件往返的判据面——计数不可由加算区反推) adr_e._on_level_finished({"result": "victory", "hp": 30, "node_index": 1, "node_id": 1, "node_type": "shop"}) - var cands_e: Array = (adr_e._bonus_pending.get("candidates", []) as Array).duplicate() + var cands_e: Array = (adr_e._curio_pending.get("candidates", []) as Array).duplicate() Kit.check_quiet(not cands_e.is_empty(), "P5 非末关通关三选一停点在案(直调 victory——L1 非末关)") var sm_f = SaveManagerScript.new(_bp_path) Kit.check_quiet(sm_f.load_from_disk() and sm_f.has_breakpoint(), "P5 选择停点断点在档(选择停点可中断契约)") @@ -106,24 +106,24 @@ func _init() -> void: var got_cands := {"cands": [] as Array} var adr_f = AdventureRunnerScript.new(db, load("res://core/rng_service.gd").new()) adr_f.set_save_manager(sm_f) # 兑付后停点快照落盘承载(与真机组合根同构) - adr_f.bonus_choice_presented.connect(func(p): got_cands["cands"] = (p.get("candidates", []) as Array).duplicate()) + adr_f.curio_choice_presented.connect(func(p): got_cands["cands"] = (p.get("candidates", []) as Array).duplicate()) Kit.check_quiet(adr_f.resume_adventure(bp_f, params_e), "P5 续玩受理(选择态分支)") Kit.check_quiet(adr_f.runner() == null, "P5 续玩不重跑关卡(三选一停点直回,_start_current_level 未走)") Kit.check_quiet(got_cands["cands"] == cands_e, "P5 续玩候选不重抽(逐位一致)") - Kit.check_quiet(int(adr_f._bonus_taken["attack"]) == 1, "P5 次数计数恢复一致(牌库剔除态续接)") + Kit.check_quiet(int(adr_f._curio_taken["attack"]) == 1, "P5 次数计数恢复一致(牌库剔除态续接)") Kit.check_quiet(not adr_f.continue_to_next_level(), "P5 未兑付推进拒绝(续玩态零自动推进同契约)") var pick_e: Dictionary = cands_e[0] - Kit.check_quiet(adr_f.pick_bonus(String(pick_e.get("kind", "")), String(pick_e.get("tier", ""))), "P5 点选兑付受理") - var picked_e: String = String(adr_f._bonus_result.get("picked", "")) - if adr_f._bonus_taken.has(picked_e): - Kit.check_quiet(int(adr_f._bonus_taken[picked_e]) == (2 if picked_e == "attack" else 1), + Kit.check_quiet(adr_f.pick_curio(String(pick_e.get("kind", "")), String(pick_e.get("tier", ""))), "P5 点选兑付受理") + var picked_e: String = String(adr_f._curio_result.get("picked", "")) + if adr_f._curio_taken.has(picked_e): + Kit.check_quiet(int(adr_f._curio_taken[picked_e]) == (2 if picked_e == "attack" else 1), "P5 兑付计数一致(%s——预置 1 + 本次兑付)" % picked_e) Kit.check_quiet(sm_f.has_breakpoint(), "P5 兑付后停点快照在档(已兑付待推进态可中断)") var bp_g: Dictionary = sm_f.breakpoint_snapshot() var adr_g = AdventureRunnerScript.new(db, load("res://core/rng_service.gd").new()) adr_g.set_save_manager(sm_f) # 已兑付态续玩的后续快照同承载 var got_result := {"r": {}} - adr_g.bonus_resolved.connect(func(r): got_result["r"] = r.duplicate()) + adr_g.curio_resolved.connect(func(r): got_result["r"] = r.duplicate()) Kit.check_quiet(adr_g.resume_adventure(bp_g, params_e), "P5 已兑付态续玩受理") Kit.check_quiet(String(got_result["r"].get("picked", "")) == picked_e, "P5 已兑付不重付(resolved 重发同件)") Kit.check_quiet(adr_g.continue_to_next_level(), "P5 已兑付续玩后推进受理(下一关开始)") diff --git a/game/tests/unit/presentation/reward_overlay_test.gd b/game/tests/unit/presentation/reward_overlay_test.gd index a9960934..aa457cfd 100644 --- a/game/tests/unit/presentation/reward_overlay_test.gd +++ b/game/tests/unit/presentation/reward_overlay_test.gd @@ -1,6 +1,6 @@ extends SceneTree # 三选一覆盖层 UI 直验套件(迭代 7 T5-hotfix 转正;迭代 10 追加批随面迁移改写)—— -# 原守 node_overlay.show_reward_items;奖励节点三选一与通关三选一合并共用 BonusPickOverlay 后, +# 原守 node_overlay.show_reward_items;奖励节点三选一与通关三选一合并共用 CurioPickOverlay 后, # 本套件改守共用面的「功能存在 ≠ 功能可达」盲区:选择外发/强制确认两段式/替换子面板两路/结果态。 # 运行:godot --headless --path game --script res://tests/unit/presentation/reward_overlay_test.gd(expect 0) # T6 收口批:断言走 test_kit 计数(Kit.check)+ 失败即停早退协议保留(顺序状态机——坏态下后继断言无效)。 @@ -18,7 +18,7 @@ func _initialize() -> void: call_deferred("_run") func _run() -> void: - var ov = load("res://presentation/views/bonus_pick_overlay.gd").new() + var ov = load("res://presentation/views/curio_pick_overlay.gd").new() root.add_child(ov) var picked := [] var replaced := [] @@ -32,10 +32,9 @@ func _run() -> void: {"id": "gp", "text": "金币袋\n金币 +20"}, {"id": "ap", "text": "行动点提升\n每战首轮 AP +1"}, ]) - var btns := 0 - for c in ov._choice_box.get_children(): - if c is Button: - btns += 1 + # M7 bcard 换装适配(2026-10-05 奇物批 T2 连带):三选一卡自 M7 起为 BCard(PanelContainer) 非 Button—— + # 原 `c is Button` 计数自 M7 后恒 0(基线红成因,主区 3c4291d 起红在案)。改数 _choice_box 子节点数,断言语义不变(三卡呈现)。 + var btns: int = ov._choice_box.get_child_count() if not _req("选择态应三卡呈现且确认/继续隐藏(btns=%d)" % btns, ov.visible and btns == 3 and not ov._confirm_btn.visible and not ov._continue_btn.visible): return From 6812ebbb7365cb5fad193fda4fd9e427c10d8a41 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 5 Oct 2026 19:28:20 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E5=A5=87=E7=89=A9=E6=89=B9T3+T4=EF=BC=9Are?= =?UTF-8?q?lic=5F*=E2=86=92feat=5F*=E8=AF=8D=E6=97=8F=E5=85=A8=E6=8D=A2?= =?UTF-8?q?=EF=BC=88feat=5Ftable.gd/feats.tres/feats=5Fengine.gd=E4=B8=89?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=94=B9=E5=90=8D+.uid+feat=5Fflags/feat=5Fi?= =?UTF-8?q?d/feat=5Frow=E7=AD=89=E5=85=A8token+config=5Fdb=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E9=94=AEfeats=E4=B8=A4=E7=AB=AF+entities.yaml?= =?UTF-8?q?=E6=8D=A2=E4=BB=A3=E6=B3=A8=E8=AE=B0=E2=80=94=E2=80=94kind?= =?UTF-8?q?=E5=80=BCrelic/=E8=BA=AB=E4=BB=BD=E5=89=8D=E7=BC=80relic:/relic?= =?UTF-8?q?s=E5=AD=98=E6=A1=A3=E9=94=AE/=E6=91=98=E8=A6=81=E9=94=AE?= =?UTF-8?q?=E6=8C=89=E6=95=B0=E6=8D=AE=E5=A5=91=E7=BA=A6=E4=BF=9D=E7=95=99?= =?UTF-8?q?=EF=BC=89=EF=BC=9BT4=E6=B8=A0=E9=81=93=E7=99=BD=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E8=BF=87=E6=BB=A4=EF=BC=88=5Fdraw=5Fcurio=5Fcandidate?= =?UTF-8?q?s(channel)=EF=BC=9A=E9=80=9A=E5=85=B3=E6=B8=A0=E9=81=93?= =?UTF-8?q?=E5=8F=AA=E5=87=BA=E4=B8=93=E9=95=BF/=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E6=B8=A0=E9=81=93=E9=9B=B6=E4=B8=93=E9=95=BF=E2=80=94=E2=80=94?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E5=BC=8F=E4=B8=8D=E6=8B=86=E6=B1=A0=EF=BC=8C?= =?UTF-8?q?TIER=5FQUOTAS=E9=94=9A=E9=9B=B6=E6=94=B9=E5=8A=A8=EF=BC=8Cdevou?= =?UTF-8?q?r=E8=B4=A7=E6=9E=B6=E4=BF=9D=E5=BA=95=E9=9A=8F=E6=B8=A0?= =?UTF-8?q?=E9=81=93=E5=AE=9A=E4=BD=8D=E9=80=80=E5=BD=B9=EF=BC=8C=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E4=BF=9Dfeat=5Fid=EF=BC=8Cpick=5Fcurio=E6=B5=8B?= =?UTF-8?q?=E8=AF=95feat=5Fid=E9=80=8F=E4=BC=A0=EF=BC=9B=E5=86=92=E7=83=9F?= =?UTF-8?q?773/0+run=5Fall=2016=E5=85=A8=E7=BB=BF+=E6=96=AD=E7=82=B9?= =?UTF-8?q?=E5=BE=80=E8=BF=94PASS=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/registry/entities.yaml | 6 +- game/core/action_economy.gd | 16 +- game/core/adventure_runner.gd | 138 ++++++++++-------- game/core/battle_orchestrator.gd | 128 ++++++++-------- game/core/config_db.gd | 14 +- game/core/damage_controller.gd | 56 +++---- .../{relics_engine.gd => feats_engine.gd} | 18 +-- ...lics_engine.gd.uid => feats_engine.gd.uid} | 0 game/core/level_runner.gd | 43 ++---- game/data/schemas/adventure_table.gd | 2 +- .../schemas/{relic_table.gd => feat_table.gd} | 24 +-- .../{relic_table.gd.uid => feat_table.gd.uid} | 0 game/data/schemas/meta_tree_table.gd | 2 +- game/data/tables/{relics.tres => feats.tres} | 60 ++++---- game/presentation/battle_narrator.gd | 14 +- .../presentation/curio_interact_controller.gd | 18 +-- game/presentation/hud_presenter.gd | 14 +- game/presentation/main.gd | 4 +- .../views/hero_info_panel_view.gd | 30 ++-- game/presentation/views/level_map_screen.gd | 18 +-- game/smoke/adventure_monte_carlo.gd | 22 +-- game/smoke/level_autoplay.gd | 22 +-- game/smoke/smoke_all.gd | 118 ++++++++------- .../tests/unit/core/breakpoint_resume_test.gd | 2 +- 24 files changed, 385 insertions(+), 384 deletions(-) rename game/core/{relics_engine.gd => feats_engine.gd} (73%) rename game/core/{relics_engine.gd.uid => feats_engine.gd.uid} (100%) rename game/data/schemas/{relic_table.gd => feat_table.gd} (89%) rename game/data/schemas/{relic_table.gd.uid => feat_table.gd.uid} (100%) rename game/data/tables/{relics.tres => feats.tres} (83%) diff --git a/design/registry/entities.yaml b/design/registry/entities.yaml index f386a869..f3a6db44 100644 --- a/design/registry/entities.yaml +++ b/design/registry/entities.yaml @@ -595,14 +595,14 @@ entities: channel_constraint_layers: 渠道约束分三层——一层 分类白名单(分类轴决定渠道);二层 可计算谓词(渠道与奇物的结构性关系,如「计价物不自售」:X 是渠道 C 的计价物 → X 不进 C;新渠道/新奇物自动判定,不用改表);三层 显式例外清单(人为设计选择,只能逐条加,每条须写理由)。要害不是有没有例外,而是例外能不能被算出来——结构性原因写成谓词,设计性原因进清单。在册约束两条:①金币不进商店(二层·谓词)②专长在非 Boss 渠道只给门票(一层·分类形态)。**注意**:属性值提升/资源不出现在 Boss 渠道**不是约束**——那是渠道轴的定位(Boss 是专长独占渠道),不属默认原则管辖。**「计价物不自售」形式上确是在白名单外加的一道过滤(用户质疑成立),与死特判的区别在于把枚举例外变成计算谓词** axes: 两根互不决定的轴——**分类轴**(给的是什么)与**渠道轴**(从哪来)。一个奇物属于哪个分类不决定它从哪个渠道来;交叉关系由 design/gdd/systems/冒险循环/奇物.md 第 2 节开关表定死 categories: 封闭三类 {专长 Feat, 属性值提升 ASI, 资源}(v2.7 定名,**去「类」字后缀**——此前为描述式叫法「机制类 / 属性提升类 / 资源类」,非专名;用户 v1.1 原话即「属性提升(ASI)」与「专长」,v1.1 落文时被加后缀而退化。v2.0 起为三类、v1.1 曾用两类、v1.0 曾用 {rule, stat},均已作废)。 - hierarchy: 玩家可见的三层(用户 2026-10-05 拍板)——一层总称「奇物」/二层三个分类/三层具体单件(如 奥术余烬、贯穿射击)。**命名与呈现是两件事**:分类各有一个正式专名,文档、代码词族、帮百科/成就名/效果描述的指代一律用它;但卡面与详情页只写单件名与效果、不附带分类(呈现面归 battle.md / node-reward.md 裁决,本条目不预断)专长(Feat)——在结算管线某个挂点上给自己加一条规则,同标签持有项数达阈值时质变,事实源 relics.tres;属性值提升(ASI)——抬高生命上限 / AC / 攻击三轴基础值,进冒险加算区,事实源 adventure.tres;资源——给"可用的量",不抬高战斗属性也不改规则:行动点上限 / 刷新次数 / 金币 / 回复,不进加算区、多数不进持有集,事实源 adventure.tres + hierarchy: 玩家可见的三层(用户 2026-10-05 拍板)——一层总称「奇物」/二层三个分类/三层具体单件(如 奥术余烬、贯穿射击)。**命名与呈现是两件事**:分类各有一个正式专名,文档、代码词族、帮百科/成就名/效果描述的指代一律用它;但卡面与详情页只写单件名与效果、不附带分类(呈现面归 battle.md / node-reward.md 裁决,本条目不预断)专长(Feat)——在结算管线某个挂点上给自己加一条规则,同标签持有项数达阈值时质变,事实源 feats.tres;属性值提升(ASI)——抬高生命上限 / AC / 攻击三轴基础值,进冒险加算区,事实源 adventure.tres;资源——给"可用的量",不抬高战斗属性也不改规则:行动点上限 / 刷新次数 / 金币 / 回复,不进加算区、多数不进持有集,事实源 adventure.tres scope: 作用对象 = 角色本人与其参与的结算(不改敌人、不改棋盘生成、不改关卡内容);**例外**——刷新次数作用在三选一界面而非角色身上,不进加算区;时间范围 = 整局冒险,专长与属性值提升跨关携带、关间不清零,金币与回复即时兑付;归属范围 = 不进账号永久进度,不带到下一次冒险 lifecycle: 候选池构建(冒险开局一次)→ 在渠道上出现(三选一形式)→ 选定(自池中剔除不再复现;选到「专长获取」时立刻再开一次专长 N 选一)→ 持有生效或即时兑付 → 冒险以任一结局收束时整集清零;断点记录持有集与剩余候选池 acquisition: 三条渠道的**目标分工(交叉开关,v2.2 收口)**——专长:主渠道=**关底 Boss 击败=通过这一关**(**唯一直接给的渠道**;不新增节点类型、挂 Boss 节点兑付面;末关的 Boss 击败后不给——无后续关卡可消费)。**兑付频率**:奇物不是打一场战斗给一份,关内普通战斗节点不给任何奇物,一关之内只兑付一次(奖励节点与商店同理,各节点各自兑付),奖励节点与商店**只给「专长获取」门票**——**拿到即开、不可囤积**,不进持有集(选中/买下同一时刻开专长三选一;可囤积会让玩家无限推迟关键决策,且把持有物语义从后门放回)。门票归专长(给的是一件以前不会的事,只是把选择推迟)。罕见**只在概率不在价格**,一局限购一次,**计步保底**:连续 5 次三选一展示未出现→第 6 次必出(计数=展示次数、刷新重展计入、奖励节点与商店共享、触发清零、限购用满后不再触发);属性值提升:奖励节点 + 商店(口径「训练」);资源:奖励节点 + 商店(**金币除外、回复在内**);后两类均不出现在 Boss 渠道。形状=「一块独占 + 两块并行」,专长靠打赢、其余靠抽取与购买。**默认原则**:除已登记的特殊规则外,分类内的东西都要在它该出现的渠道池子里——任何不出现都须登记理由**现行实现三渠道共用同一候选池、三类混编**,分工尚未实施,登记于 06-待办清单 BD 分层条目 boundary: 判据走生命周期——**一次冒险内获得、冒险收束即清零的是奇物;跨冒险跟着你走的是魔法道具**。奇物永不进同调位,魔法道具永不进当局随机渠道;与元进度天赋树两层互不代位 - data_sources: relics.tres(专长定义行)/ adventure.tres(属性值提升与资源的种类配额与值)/ shop.tres(货架取舍) + data_sources: feats.tres(专长定义行)/ adventure.tres(属性值提升与资源的种类配额与值)/ shop.tres(货架取舍) open_questions: ①类内异质性已登记——金币与回复是即时兑付、不进持有集不持续生效,与行动点上限/刷新次数生命周期不同,靠这条描述把"资源"从泛用词收回到本类定义内;②专长门票尚未入表(出现概率/配额/限购次数/保底阈值四参数均无事实源);③确定性渠道一局两次专长 vs 档位阈值(同标签 ≥2 档一 / ≥4 档二)——**档二在三关结构下几乎不可达**,用户定延后复裁(触发=第四关内容批后实测);④charge 标签仅 3 件、永远达不到档二(数据层缺陷,独立登记,勿被上条盖掉);⑤断点存档键 5 个(bonus_deck / bonus_taken / bonus_pending / bonus_result / bonus_aux_carry)是否随存档版本迁移换名待裁 - naming_note: 2026-10-05 命名统一 + 两次重构——此前按内部分类被叫成三个名字(规则类称「流派件」、属性类称「数值件」、界面称「本关强化」),v1.0 统一为「特性」,v2.0 改名为「奖励」,v2.4 定名为「奇物」(因筐里也装金币这类一次性兑付物,"自身提升 vs 持有物"已切不开,判据改由生命周期承担)。v2.5 第三类定名为「资源」(用户拍板)。**v2.7 三个子节点全部正式定名、去「类」字后缀**:专长(Feat)/ 属性值提升(ASI)/ 资源——此前是描述式叫法不是专名。**代码侧三套词族均尚未对齐**:`feature_*` → `curio_*`(103 处,顶层总称)、`relic_*` → `feat_*`(162 处,专长分类的词族——v2.7 定名后顶层与内层分类各有各的词族)、运行层 `bonus_*` → `curio_*`(160 处),属临时态,登记于 06-待办清单,须在可跑冒烟批次收口 + naming_note: 2026-10-05 命名统一 + 两次重构——此前按内部分类被叫成三个名字(规则类称「流派件」、属性类称「数值件」、界面称「本关强化」),v1.0 统一为「特性」,v2.0 改名为「奖励」,v2.4 定名为「奇物」(因筐里也装金币这类一次性兑付物,"自身提升 vs 持有物"已切不开,判据改由生命周期承担)。v2.5 第三类定名为「资源」(用户拍板)。**v2.7 三个子节点全部正式定名、去「类」字后缀**:专长(Feat)/ 属性值提升(ASI)/ 资源——此前是描述式叫法不是专名。**代码侧三套词族已于奇物批对齐(2026-10-05 迭代15②批 T1-T3)**:`feature_*` → `curio_*`(CURIO_KINDS/curio_quota/curio_value)、`relic_*` → `feat_*`(feat_table.gd/feats.tres/feats_engine.gd/feat_flags/feat_id——kind 值 "relic" 与身份前缀 "relic:" 系令牌池数据契约键沿用不动)、运行层 `bonus_*` → `curio_*`(curio_interact_controller/curio_pick_overlay/CurioPickOverlay/pick_curio);断点存档键(bonus_deck/bonus_taken/bonus_pending/bonus_result/bonus_aux_carry/relics)按甲案沿用历史名不迁移(存档兼容) referenced_by: [冒险结构, 关卡节点地图, 战斗结算, 行动经济, 地形变异, HUD框架, 魔法道具, 元进度, 数据配置] - name: adventure_table diff --git a/game/core/action_economy.gd b/game/core/action_economy.gd index 684d3da5..bceccfac 100644 --- a/game/core/action_economy.gd +++ b/game/core/action_economy.gd @@ -36,12 +36,12 @@ var _settlement_consumed := false # AC-14 门:notify_settlement_don var _next_round_mode := "" # 调试观测量(同板续轮 / 换新图) var _pending_ap_delta := 0 # 下轮行动点修正排队(格面资源八型批 2026-09-20——action 型 ±1 排队,轮开始兑现后清零) # 蓄力流运行态(迭代 12 T5——relic 件注入面;false=关着零扰动): -# relic_ap_carry=整备军备(轮末剩余 AP 转蓄力层替代 RAP 转存——cleared 作废口径改写的停手侧承载); -# relic_dig_extra=深挖钻头付费开掘对价(R4 甲非信息税形态;2026-10-04 试玩缺陷修复——对价挂付费 +# feat_ap_carry=整备军备(轮末剩余 AP 转蓄力层替代 RAP 转存——cleared 作废口径改写的停手侧承载); +# feat_dig_extra=深挖钻头付费开掘对价(R4 甲非信息税形态;2026-10-04 试玩缺陷修复——对价挂付费 # 开掘侧:AP 富余时挖掘付全价 1+extra 积付费计数、AP 不足时降级只付基础 1 照常开格不积计数—— # 原「持有期所有挖掘无条件 +1」实现把对价错挂成普通挖掘税,AP=1 锁死开格);charge_layers=蓄力层账本。 -var relic_ap_carry := false -var relic_dig_extra := 0 +var feat_ap_carry := false +var feat_dig_extra := 0 var _charge_layers := 0 var charge_layers: int: get: return _charge_layers @@ -95,8 +95,8 @@ func request_dig(chain_size: int, clears_map: bool) -> bool: _reject("phase") return false var cost := _cost("dig") - if relic_dig_extra > 0 and _ap >= cost + relic_dig_extra: - cost += relic_dig_extra + if feat_dig_extra > 0 and _ap >= cost + feat_dig_extra: + cost += feat_dig_extra _deep_paid_count += 1 if _ap < cost: _reject("resource_face") @@ -136,13 +136,13 @@ func _spend_and_arbitrate(action_kind: String, clears_map: bool) -> bool: # 停手:剩余 AP 1:1 转存 RAP;行动经济 直发 stopped 路径 round_end(双发射源)。 -# 整备军备改写(迭代 12 T5——relic_ap_carry=true 时剩余 AP 转蓄力层〔上限 3〕替代 RAP 转存: +# 整备军备改写(迭代 12 T5——feat_ap_carry=true 时剩余 AP 转蓄力层〔上限 3〕替代 RAP 转存: # 「不动也是行动」——cleared 作废口径改写的停手侧承载;cleared 分支 AP 恒清零维持既有作废语义) func request_stop() -> bool: if _phase != Phase.ACTIVE: _reject("phase") return false - if relic_ap_carry: + if feat_ap_carry: _charge_layers = mini(3, _charge_layers + _ap) else: _rap += _ap diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index b044da92..0861f5b6 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -81,7 +81,7 @@ var _resume_bp := {} # 续玩快照暂存(resume_adventur # ---- 三选一停点态(迭代 10 奖励重构批)---- var _curio_taken := {} # 件获取次数计数(kind 维度派生账本——断点附随件与摘要读数承载;池库存正主=_curio_deck) var _curio_deck: Array = [] # 令牌池(TP0 v2.2——持久多重集,元素 {"kind","tier"};洗牌抽候选/选中踢池未选回池/池耗即枯竭;商店货架同库共账;迭代 12 T1 起规则改写型特性带=relic 令牌随表追加) -var _relics: Array[String] = [] # 特性持有集(迭代 12 T1——局内随机渠道获得、冒险级跨关携带、收束清零;🔴 层级纪律承载面:不入同调位不入局外树) +var _feats: Array[String] = [] # 特性持有集(迭代 12 T1——局内随机渠道获得、冒险级跨关携带、收束清零;🔴 层级纪律承载面:不入同调位不入局外树) var _tree_rules: Array = [] # 树 rule 键集(迭代 12 T10——Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面) var _curio_pending := {} # 停点态:{"candidates": Array, "payload": Dictionary, "chosen"/"incoming": 替换在途}——空 = 无停点 var _curio_result := {} # 已兑付待推进态(curio_resolved 载荷副本——中断续玩不重兑付) @@ -130,12 +130,12 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool: _seed_attune_charges() # 迭代 11 第①批 T6——冒险开始充能账本按装备主动件重置(每冒险充能语义) # 特性开局注入(迭代 12 T7——MC 成型画像载体:params.relics_seed 非空=开局持有集〔七流派 # 各 2 件档一成型——测量面专用;真实局规则改写型特性只走局内随机渠道,红线②层级纪律不破〕) - var relic_seed: Array = params.get("relics_seed", []) if params.get("relics_seed", []) is Array else [] + var feat_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(): - _relics = [] - for r in relic_seed: - _relics.append(String(r)) + if not feat_seed.is_empty(): + _feats = [] + for r in feat_seed: + _feats.append(String(r)) ledger["hero_hp"] = effective_max_hp() # 生命账本置满(生效上限口径——T17 修复:含调试浮窗 pct,与战斗内同源) state = "running" return _start_current_level() @@ -179,7 +179,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: for id in _curio_taken.keys(): _curio_taken[id] = maxi(0, int(bp_taken.get(id, 0))) # 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径; - # 迭代 12 T1——relic 令牌 relic_id 随还原) + # 迭代 12 T1——relic 令牌 feat_id 随还原) var bp_deck = bp.get("bonus_deck", null) if bp_deck is Array: var restored: Array = [] @@ -187,13 +187,13 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: var tok: Dictionary = t as Dictionary var rtok := {"kind": String(tok.get("kind", "")), "tier": String(tok.get("tier", ""))} if String(rtok["kind"]) == "relic": - rtok["relic_id"] = String(tok.get("relic_id", "")) + rtok["feat_id"] = String(tok.get("feat_id", "")) restored.append(rtok) _curio_deck = restored # 特性持有集还原(迭代 12 T1——缺键 = 旧版断点防御空集) - _relics = [] + _feats = [] for r in (bp.get("relics", []) as Array): - _relics.append(String(r)) + _feats.append(String(r)) for v in (bp.get("bonus_aux_carry", []) as Array): _bonus_treasure_carry.append(String(v)) _keystone_drops = (bp.get("keystone_drops", []) as Array).duplicate(true) @@ -220,7 +220,11 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: var migrated: Array = [] for c in (bp_pending.get("candidates", []) as Array): if c is Dictionary: - migrated.append({"kind": String((c as Dictionary).get("kind", "")), "tier": String((c as Dictionary).get("tier", ""))}) + # T4——feat_id 随迁移(通关渠道候选=纯专长令牌,身份键 relic|tier|feat_id 三段全要) + var mig := {"kind": String((c as Dictionary).get("kind", "")), "tier": String((c as Dictionary).get("tier", ""))} + if String(mig["kind"]) == "relic": + mig["feat_id"] = String((c as Dictionary).get("feat_id", "")) + migrated.append(mig) else: migrated.append({"kind": String(c), "tier": db.adventure.TIER_COMMON}) _curio_pending = {"candidates": migrated} @@ -259,7 +263,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: func _reset_curio_state() -> void: _curio_taken = {"attack": 0, "max_hp": 0, "armor": 0, "gp": 0, "ap": 0, "heal": 0} _curio_deck = _build_curio_deck() - _relics = [] + _feats = [] _curio_pending = {} _curio_result = {} _bonus_treasure_carry = [] @@ -276,16 +280,16 @@ func _build_curio_deck() -> Array: for tier in db.adventure.TIERS: for i in int(db.adventure.curio_quota(String(kind), String(tier))): deck.append({"kind": String(kind), "tier": String(tier)}) - for row in db.relic_rows(): + for row in db.feat_rows(): var rtier := "rarest" if String(row.get("rarity", "")) == "rarest" else "rare" - deck.append({"kind": "relic", "tier": rtier, "relic_id": String(row.get("relic_id", ""))}) + deck.append({"kind": "relic", "tier": rtier, "feat_id": String(row.get("feat_id", ""))}) return deck -# 令牌唯一身份(抽签去重/兑付匹配共用——属性加成型特性=kind,规则改写型特性=relic_id;同档多张不同 relic 可同屏) +# 令牌唯一身份(抽签去重/兑付匹配共用——属性加成型特性=kind,规则改写型特性=feat_id;同档多张不同 relic 可同屏) func _token_identity(tok: Dictionary) -> String: if String(tok.get("kind", "")) == "relic": - return "relic:" + String(tok.get("relic_id", "")) + return "relic:" + String(tok.get("feat_id", "")) return String(tok.get("kind", "")) @@ -337,64 +341,64 @@ func continue_to_next_level() -> bool: # ---- 提升三选一(迭代 10 奖励重构批——选择停点零自动推进、重复兑付被拒;兑付恰一件、未选项零生效)---- -# 候选令牌匹配(身份键恒等——属性加成型特性 kind+tier、规则改写型特性 relic_id;迭代 12 T1 规则改写型特性带) -func _find_token(candidates: Array, kind: String, tier: String, relic_id: String = "") -> Dictionary: +# 候选令牌匹配(身份键恒等——属性加成型特性 kind+tier、规则改写型特性 feat_id;迭代 12 T1 规则改写型特性带) +func _find_token(candidates: Array, kind: String, tier: String, feat_id: String = "") -> Dictionary: for t in candidates: var tok: Dictionary = t as Dictionary if String(tok.get("kind", "")) != kind or String(tok.get("tier", "")) != tier: continue - if kind == "relic" and String(tok.get("relic_id", "")) != relic_id: + if kind == "relic" and String(tok.get("feat_id", "")) != feat_id: continue return tok return {} # 玩家点选兑付(呈现层/冒烟/自动模拟确认入口):令牌 ∈ 停点候选(身份键精确匹配——relic 件 -# 另须 relic_id)。属性件写加算区、AP 件写第四轴、GP 件走钱包「奖励入账」入口、恢复件当即回血、 +# 另须 feat_id)。属性件写加算区、AP 件写第四轴、GP 件走钱包「奖励入账」入口、恢复件当即回血、 # relic 件入特性持有集(🔴 层级纪律:只走局内随机渠道)、道具件受同调位约束(随池种退役不可达)。 # 消费=选中令牌踢池(未选回池由「抽签不摘牌、兑付才摘牌」语义承载——TP0 v2.2)。 -func pick_curio(kind: String, tier: String = "", relic_id: String = "") -> bool: +func pick_curio(kind: String, tier: String = "", feat_id: String = "") -> bool: if state != "running" or _curio_pending.is_empty(): push_error("[冒险结构] pick_curio 拒绝——无待选停点(state=%s)" % state) return false var candidates: Array = _curio_pending.get("candidates", []) as Array - if _find_token(candidates, kind, tier, relic_id).is_empty(): - push_error("[冒险结构] pick_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id]) + if _find_token(candidates, kind, tier, feat_id).is_empty(): + push_error("[冒险结构] pick_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, feat_id]) return false var value: int = int(db.adventure.curio_value(kind, tier)) var hp_before := int(ledger.get("hero_hp", 0)) if kind == "treasure_item": return _pick_treasure_item(value) # 道具件分派通关渠道(持有容器 = 跨关携带——与节点渠道分轨;池种退役后不可达) - if not _apply_curio_pick(kind, tier, value, relic_id): - push_error("[冒险结构] pick_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id]) + if not _apply_curio_pick(kind, tier, value, feat_id): + push_error("[冒险结构] pick_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, feat_id]) return false - _finish_curio_pick(kind, value, hp_before, tier, relic_id) + _finish_curio_pick(kind, value, hp_before, tier, feat_id) return true # 兑付载荷 relic 伴随键(迭代 12 T1——反馈行取数面;非 relic 件零新增键) -func _relic_payload_keys(relic_id: String) -> Dictionary: - if relic_id == "": +func _feat_payload_keys(feat_id: String) -> Dictionary: + if feat_id == "": return {} - var row: Dictionary = db.relic_row(relic_id) - return {"relic_id": relic_id, "relic_name": String(row.get("display_name", "")), "relic_archetype": String(row.get("archetype", ""))} + var row: Dictionary = db.feat_row(feat_id) + return {"feat_id": feat_id, "feat_name": String(row.get("display_name", "")), "feat_archetype": String(row.get("archetype", ""))} # 三选一件兑付共享体(2026-10-02 修复批双胞胎收口;TP0 改令牌消费——摘牌单点;迭代 12 T1 relic 分支): # 属性件写加算区 + 件获取计数、AP 件写第四轴、GP 件走钱包、恢复件当即回血(封顶生效上限—— # 与账本置满/通关回血同式 effective_max_hp)、relic 件入持有集(散件单体保底——效果接线面 -# 按挂点消费,档位质变由 RelicsEngine 求值)。兑付即从 _curio_deck 摘除该令牌(选中踢池; +# 按挂点消费,档位质变由 FeatsEngine 求值)。兑付即从 _curio_deck 摘除该令牌(选中踢池; # 未选候选从未摘牌=自动回池)。道具件不入此体——未知件返回 false 由调用方按渠道名报错。 -func _apply_curio_pick(kind: String, tier: String, value: int, relic_id: String = "") -> bool: - if kind == "relic" and db.relic_row(relic_id).is_empty(): - push_error("[冒险结构] relic 兑付失败——表无此件: %s" % relic_id) +func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String = "") -> bool: + if kind == "relic" and db.feat_row(feat_id).is_empty(): + push_error("[冒险结构] relic 兑付失败——表无此件: %s" % feat_id) return false # 先校验后摘牌(令牌零丢失) - if not _consume_deck_token(kind, tier, relic_id): - push_error("[冒险结构] 令牌消费失败——牌库无此令牌: %s|%s|%s" % [kind, tier, relic_id]) + if not _consume_deck_token(kind, tier, feat_id): + push_error("[冒险结构] 令牌消费失败——牌库无此令牌: %s|%s|%s" % [kind, tier, feat_id]) return false if kind == "relic": - if not _relics.has(relic_id): - _relics.append(relic_id) + if not _feats.has(feat_id): + _feats.append(feat_id) _curio_taken["relic"] = int(_curio_taken.get("relic", 0)) + 1 return true match kind: @@ -412,7 +416,7 @@ func _apply_curio_pick(kind: String, tier: String, value: int, relic_id: String _curio_taken["ap"] = int(_curio_taken.get("ap", 0)) + 1 "gp": if _runner != null: - _runner.grant_bonus_gp(value) + _runner.grant_curio_gp(value) _curio_taken["gp"] = int(_curio_taken.get("gp", 0)) + 1 "heal": ledger["hero_hp"] = mini(int(ledger.get("hero_hp", 0)) + value, effective_max_hp()) @@ -422,13 +426,13 @@ func _apply_curio_pick(kind: String, tier: String, value: int, relic_id: String return true -# 从牌库摘除一枚令牌(TP0——选中踢池单点;迭代 12 T1 relic 按 relic_id 精确摘;找不到 = false) -func _consume_deck_token(kind: String, tier: String, relic_id: String = "") -> bool: +# 从牌库摘除一枚令牌(TP0——选中踢池单点;迭代 12 T1 relic 按 feat_id 精确摘;找不到 = false) +func _consume_deck_token(kind: String, tier: String, feat_id: String = "") -> bool: for i in _curio_deck.size(): var tok: Dictionary = _curio_deck[i] as Dictionary if String(tok.get("kind", "")) != kind or String(tok.get("tier", "")) != tier: continue - if kind == "relic" and String(tok.get("relic_id", "")) != relic_id: + if kind == "relic" and String(tok.get("feat_id", "")) != feat_id: continue _curio_deck.remove_at(i) return true @@ -436,8 +440,8 @@ func _consume_deck_token(kind: String, tier: String, relic_id: String = "") -> b # 特性持有集读面(效果接线/求值/摘要唯一取数口——副本防外部改写) -func relics_held() -> Array: - return _relics.duplicate() +func feats_held() -> Array: + return _feats.duplicate() # ---- 商店共享池消费面(迭代 11 第②批 T3——P1 共享池单点账本;TP0 令牌池换代——库存=牌库剩余令牌)---- @@ -513,7 +517,7 @@ func resolve_treasure_replace(discard_index: int) -> bool: # 兑付收口(恰一次):清停点态 → 记已兑付态 → emit → 停点快照(等继续确认——中断续玩不重兑付)。 # picked = kind;tier 随载荷(TP0——大小档位读数面);relic 伴随键随载荷(迭代 12 T1——反馈行取数) -func _finish_curio_pick(picked: String, value: int, hp_before: int, tier: String = "", relic_id: String = "") -> void: +func _finish_curio_pick(picked: String, value: int, hp_before: int, tier: String = "", feat_id: String = "") -> void: _curio_pending = {} _curio_result = { "picked": picked, @@ -523,7 +527,7 @@ func _finish_curio_pick(picked: String, value: int, hp_before: int, tier: String "hp_after": int(ledger.get("hero_hp", 0)), "level_index": level_index, } - _curio_result.merge(_relic_payload_keys(relic_id)) + _curio_result.merge(_feat_payload_keys(feat_id)) curio_resolved.emit(_curio_result.duplicate(true)) _snapshot_breakpoint() @@ -534,7 +538,7 @@ func _finish_curio_pick(picked: String, value: int, hp_before: int, tier: String # 续玩重建到选路停点后重走重抽,恢复的全局计数器保证同候选逐位复现)。 # 掷定前置(追加批二):返回 dict 带 aux_detail(道具件具体道具)供卡片直显。 func node_curio_candidates() -> Dictionary: - var cands: Array = _draw_curio_candidates() + var cands: Array = _draw_curio_candidates("reward_node") return {"candidates": cands, "aux_detail": _last_draw_treasure.duplicate()} @@ -544,21 +548,21 @@ func present_node_curio(candidates: Array, treasure_detail: Array = []) -> void: # 点选兑付(节点域——无部分回血无钥石;令牌消费与牌库摘牌与通关三选一同库;relic 同权入持有集) -func apply_node_curio(kind: String, tier: String = "", relic_id: String = "") -> bool: +func apply_node_curio(kind: String, tier: String = "", feat_id: String = "") -> bool: if _node_curio_pending.is_empty(): push_error("[冒险结构] apply_node_curio 拒绝——无待选停点") return false var candidates: Array = _node_curio_pending.get("candidates", []) as Array - if _find_token(candidates, kind, tier, relic_id).is_empty(): - push_error("[冒险结构] apply_node_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id]) + if _find_token(candidates, kind, tier, feat_id).is_empty(): + push_error("[冒险结构] apply_node_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, feat_id]) return false var value: int = int(db.adventure.curio_value(kind, tier)) if kind == "treasure_item": return _apply_node_treasure(value) # 道具件分派节点渠道(持有容器 = 关卡域库存——与通关渠道分轨;池种退役后不可达) - if not _apply_curio_pick(kind, tier, value, relic_id): - push_error("[冒险结构] apply_node_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id]) + if not _apply_curio_pick(kind, tier, value, feat_id): + push_error("[冒险结构] apply_node_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, feat_id]) return false - _finish_node_bonus(kind, value, tier, relic_id) + _finish_node_bonus(kind, value, tier, feat_id) return true @@ -646,7 +650,7 @@ func refresh_curio() -> bool: _attune_refresh_x -= 1 else: _attune_refresh_y -= 1 - var cands: Array = _draw_curio_candidates() + var cands: Array = _draw_curio_candidates("level_clear") _curio_pending["candidates"] = cands.duplicate(true) _curio_pending["aux_detail"] = _last_draw_treasure.duplicate() var payload: Dictionary = (_curio_pending.get("payload", {}) as Dictionary).duplicate(true) @@ -660,7 +664,7 @@ func refresh_curio() -> bool: _attune_refresh_x -= 1 else: _attune_refresh_y -= 1 - var cands2: Array = _draw_curio_candidates() + var cands2: Array = _draw_curio_candidates("reward_node") _node_curio_pending["candidates"] = cands2.duplicate(true) _node_curio_pending["aux_detail"] = _last_draw_treasure.duplicate() return true @@ -700,7 +704,7 @@ func resolve_curio_replace(discard: int) -> bool: return resolve_treasure_replace(discard) -func _finish_node_bonus(picked: String, value: int, tier: String = "", relic_id: String = "") -> void: +func _finish_node_bonus(picked: String, value: int, tier: String = "", feat_id: String = "") -> void: _node_curio_pending = {} var payload := { "context": "reward_node", @@ -709,7 +713,7 @@ func _finish_node_bonus(picked: String, value: int, tier: String = "", relic_id: "value": value, "level_index": level_index, } - payload.merge(_relic_payload_keys(relic_id)) + payload.merge(_feat_payload_keys(feat_id)) curio_resolved.emit(payload) @@ -719,9 +723,17 @@ func _finish_node_bonus(picked: String, value: int, tier: String = "", relic_id: # 自动回池;池耗即枯竭——余库不足 N kind 时出齐剩余,空库 = 零候选合法态)。候选 = 令牌字典 # {"kind","tier"}。消费 reward_pick 封闭流——一次停点恰一 scope,同种子同候选;道具件掷定前置 # 随池种退役拆除(_last_draw_treasure 恒空集,机械惰性承载)。 -func _draw_curio_candidates() -> Array: +# 渠道白名单过滤(迭代 15 卡② T4——渠道轴定位:关底 Boss 击败=通关渠道只出专长(feat 令牌); +# 奖励节点/商店只出属性值提升+资源(非 relic 令牌),专长在非 Boss 渠道只以门票形态出现(T5)。 +# 过滤式而非物理拆池:deck 物理结构/存档键 bonus_deck 混编余库/消耗语义(选中踢池未选回池)原样, +# 白名单在候选视图层生效——配额结构锚 TIER_QUOTAS 70/25/3=98 零改动(专长令牌本系活行追加面,在锚外)。 +func _draw_curio_candidates(channel: String = "reward_node") -> Array: _last_draw_treasure = [] var pool := _curio_deck.duplicate() + if channel == "level_clear": + pool = pool.filter(func(tok): return String((tok as Dictionary).get("kind", "")) == "relic") + else: + pool = pool.filter(func(tok): return String((tok as Dictionary).get("kind", "")) != "relic") var rg: RandomNumberGenerator = rng.open_stream("reward_pick") for i in pool.size(): var j := rg.randi_range(i, pool.size() - 1) @@ -737,7 +749,7 @@ func _draw_curio_candidates() -> Array: seen.append(ident) var cand := {"kind": String((tok as Dictionary).get("kind", "")), "tier": String((tok as Dictionary).get("tier", ""))} if ident.begins_with("relic:"): - cand["relic_id"] = String((tok as Dictionary).get("relic_id", "")) + cand["feat_id"] = String((tok as Dictionary).get("feat_id", "")) out.append(cand) if out.size() >= int(db.adventure.curio_candidate_count): break @@ -828,7 +840,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_feats(_feats) # 迭代 12 T1——特性持有集下传(begin_battle enc.feats 求值面) _runner.set_tree_rules(_tree_rules) # 迭代 12 T10——树 rule 键集下传(A/B 面消费) if _shop_mode == "pool": # 商店共享池注入(迭代 11 第②批 T3——库存账本引用+消费 handler 双下传;legacy 模式零注入零扰动) @@ -876,7 +888,7 @@ func _on_level_finished(payload: Dictionary) -> void: _reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转) var pick_payload: Dictionary = { "level_index": level_index, - "candidates": _draw_curio_candidates(), + "candidates": _draw_curio_candidates("level_clear"), # T4 渠道白名单——关底 Boss 击败=通关渠道只出专长 "aux_detail": _last_draw_treasure.duplicate(), # 掷定前置(追加批二——道具件具体道具随候选抽定,卡片直显) "heal_gained": heal, "hp_before": hp_before, @@ -908,7 +920,7 @@ func _settle(result: String) -> void: "aux_cleared_last": _last_treasure_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": _feats.duplicate(), # additive 扩签(迭代 12 T1——特性终局持有集:清账前快照,成型判读取数面) } # 败退明细(数值验证工具取数面 2026-09-22——tools/balance_verifier.py 止步节点分布数据源; # 仅失败局附加,键缺省 = 未败退,既有五件事消费方零感知) @@ -976,7 +988,7 @@ func _snapshot_breakpoint() -> void: "aux_items": treasure_snapshot, "bonus_taken": _curio_taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载) "bonus_deck": _curio_deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原) - "relics": _relics.duplicate(), # 特性持有集(迭代 12 T1——局内随机渠道获得态续玩防丢失) + "relics": _feats.duplicate(), # 特性持有集(迭代 12 T1——局内随机渠道获得态续玩防丢失) "bonus_pending": _curio_pending.duplicate(true), # 三选一停点态(空 = 无停点——选择停点可中断契约) "bonus_result": _curio_result.duplicate(true), # 已兑付待推进态(空 = 无——中断续玩不重兑付) "bonus_aux_carry": _bonus_treasure_carry.duplicate(true), # 道具件跨关携带(注入前中断防静默丢失) diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index 11667074..adc16beb 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -72,13 +72,13 @@ 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 域安全开格计数 -# 特性运行态(迭代 12 T2——relic 持有集经 enc.relics 注入;求值旗注 damage_controller.relic_flags; +# 特性运行态(迭代 12 T2——relic 持有集经 enc.feats 注入;求值旗注 damage_controller.feat_flags; # 魔矢每战首次计数 battle 域。空集=零扰动) -var _relic_first_dig_used := false -var _relic_combo := 0 # 矿脉直觉连击计数(battle 域——safe +1/mine 清零) -var _relic_deep_given := 0 # 深挖钻头本轮已追加份数(battle 域——v2 本格双份制:safe 事件按付费数上限) -var _relic_paid_at_round_start := 0 # 轮初付费计数快照(v2——本轮付费次数=当前值−快照) -var _relic_magma_layers := 0 # 熔火之躯积层(battle 域——受地形伤 +1,轮末反打清零) +var _feat_first_dig_used := false +var _feat_combo := 0 # 矿脉直觉连击计数(battle 域——safe +1/mine 清零) +var _feat_deep_given := 0 # 深挖钻头本轮已追加份数(battle 域——v2 本格双份制:safe 事件按付费数上限) +var _feat_paid_at_round_start := 0 # 轮初付费计数快照(v2——本轮付费次数=当前值−快照) +var _feat_magma_layers := 0 # 熔火之躯积层(battle 域——受地形伤 +1,轮末反打清零) func _attune_has(item_id: String) -> bool: @@ -94,15 +94,15 @@ 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——持有集 × feat_table → damage_controller.feat_flags 注入面): # rider_flat = Σ rider 件 value(奥术余烬 +1 / 魔能共鸣 +2);magic 档一(≥2 件)rider +1、 # 档二(≥4 件)rider 可暴击;clamp 两件直调键;first_dig_spell 旗(魔能共鸣第二效果——魔矢值 # = 件 effects.value)。空集/空表 = 零旗零扰动。 -func _relic_flags_for(relic_ids: Array) -> void: +func _feat_flags_for(feat_ids: Array) -> void: var flags: Dictionary = {} - if not relic_ids.is_empty() and db.relics != null and not db.relics.rows.is_empty(): - var hooks := RelicsEngine.hooks_for(relic_ids, db.relics) - var tiers := RelicsEngine.tiers_for(relic_ids, db.relics) + if not feat_ids.is_empty() and db.feats != null and not db.feats.rows.is_empty(): + var hooks := FeatsEngine.hooks_for(feat_ids, db.feats) + var tiers := FeatsEngine.tiers_for(feat_ids, db.feats) for h in hooks: var hook: String = String(h.get("hook", "")) var fx: Dictionary = h.get("effects", {}) as Dictionary @@ -173,10 +173,10 @@ func _relic_flags_for(relic_ids: Array) -> void: flags["rider_flat"] = int(flags.get("rider_flat", 0)) + 1 # 档一质变(≥2 件):rider +1(provisional) if mt >= 2: flags["rider_crit"] = true # 档二质变(≥4 件):rider 可暴击(crit 时乘暴击倍率) - _damage.relic_flags = flags - # 蓄力流/深挖钻头 action_economy 注入(求值即注入——冒烟直呼 _relic_flags_for 同面生效) - action_economy.relic_ap_carry = int(flags.get("ap_carry_value", 0)) > 0 - action_economy.relic_dig_extra = int(flags.get("deep_dig_cost", 0)) + _damage.feat_flags = flags + # 蓄力流/深挖钻头 action_economy 注入(求值即注入——冒烟直呼 _feat_flags_for 同面生效) + action_economy.feat_ap_carry = int(flags.get("ap_carry_value", 0)) > 0 + action_economy.feat_dig_extra = int(flags.get("deep_dig_cost", 0)) var _flag_miss_seen := {} # 标旗未中行去重(每安全格实例一次) var _perfect_hit_true := 0 # 板级完美排雷计数(install 重置) var _perfect_hit_false := 0 @@ -247,21 +247,21 @@ 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 求值后注伤害控制器 - # relic_flags;空集/空表=零扰动。魔矢每战首次开格计数同步归零) - _relic_flags_for((enc.get("relics", []) as Array)) - _relic_first_dig_used = false - _relic_combo = 0 - _relic_deep_given = 0 - _relic_paid_at_round_start = 0 - _relic_magma_layers = 0 + # 特性运行态注入(迭代 12 T2 魔法流——enc.feats 携带持有集;FeatsEngine 求值后注伤害控制器 + # feat_flags;空集/空表=零扰动。魔矢每战首次开格计数同步归零) + _feat_flags_for((enc.get("feats", []) as Array)) + _feat_first_dig_used = false + _feat_combo = 0 + _feat_deep_given = 0 + _feat_paid_at_round_start = 0 + _feat_magma_layers = 0 # 树 rule 键消费(迭代 12 T10——Fork 激活侧 effects;真实接线首期两键:先手压制=战斗首轮 # AP+1(queue_ap_delta——先手压制语义)/稳健拓荒=敌方攻击减 1(伤害控制器 flags bulwark); # 其余 rule 键=stat 面投影不入战斗(T8 表内容 provisional 口径披露)) var tree_rules: Array = enc.get("tree_rules", []) as Array if tree_rules.has("posture_first_strike"): action_economy.queue_ap_delta(1) - _damage.relic_flags["bulwark"] = 1 if tree_rules.has("posture_bulwark") else 0 + _damage.feat_flags["bulwark"] = 1 if tree_rules.has("posture_bulwark") else 0 # 迭代 10 奖励重构批——跨冒险行动点加成轴(统一冒险加算区第四轴):经排队修正通道兑现 # 「每场战斗开始首轮 AP +加成值」(queue_ap_delta = 下轮开轮基线加算、notify_round_start 兑现后 # 清零——首轮即消费,后续轮次不重复兑现;语义锚 = 行动经济 GDD「跨冒险行动点加成轴」节) @@ -465,29 +465,29 @@ func _settle() -> void: # 作废(归因行在案);标空判定已随旗擦除自然缺席(被挖的旗不在轮末在场基线)。 _emit_row("reward_ineffective", "safe_cell", evt.cell, ri, "奖励作废(插旗格直接挖开——零惩罚零奖励)", 0, {}, "") continue - _grant_routed(_relic_yield_adjust(snap, true), "positive", "safe_cell", evt.cell, ri, "") + _grant_routed(_feat_yield_adjust(snap, true), "positive", "safe_cell", evt.cell, ri, "") # 开格流连击/深层产出(迭代 12 T5):矿脉直觉连击 = safe +1(mine 清零在负向分支); # 深挖钻头深层产出 = 每 cap 格额外 grant 一份(收益面补偿额外 AP 成本——R4 甲非信息税对价) - _relic_combo += 1 + _feat_combo += 1 # 深挖钻头 v2(2026-10-04 二次修正——用户实测「付 2AP 无可见提升纯挨打」:v1 配额制 # 4 次付费 8AP 换 1 份随机资源=净负期望+收益埋明细带不可感知):改**本格双份**—— # 付费挖掘的本格资源直接双份(多花 1AP 买多一份同格产出,账目直白期望微正)。 # 本轮付费次数 N=deep_paid_count−轮初快照;safe 事件按 N 上限逐格追加第二份(「深挖」)。 - var dd_cost := int(_damage.relic_flags.get("deep_dig_cost", 0)) + var dd_cost := int(_damage.feat_flags.get("deep_dig_cost", 0)) if dd_cost > 0: - var paid_this_round := int(action_economy.deep_paid_count) - _relic_paid_at_round_start - if paid_this_round > 0 and _relic_deep_given < paid_this_round: - _relic_deep_given += 1 + var paid_this_round := int(action_economy.deep_paid_count) - _feat_paid_at_round_start + if paid_this_round > 0 and _feat_deep_given < paid_this_round: + _feat_deep_given += 1 var dd_snap = _reward_economy.grant(evt.cell) if dd_snap is Dictionary: _grant_routed(dd_snap, "positive", "safe_cell", evt.cell, ri, "", "(深挖)") # 魔能共鸣·每战首次开安全格追加魔法矢(迭代 12 T2——relic 件「每关首次开格必出魔法矢」 # 落地为每战首次〔战斗域单事务无跨战状态——「每关」字面改「每战」台账披露待追认〕; # board_typed magic 必中,扫雷结算触发点=relic 挂 first_dig_spell 承载红线①) - if bool(_damage.relic_flags.get("first_dig_spell", false)) and not _relic_first_dig_used: - _relic_first_dig_used = true - var spell_t := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "relic_first_dig_spell", - "奥术矢", "board", int(_damage.relic_flags.get("spell_value", 0)), + if bool(_damage.feat_flags.get("first_dig_spell", false)) and not _feat_first_dig_used: + _feat_first_dig_used = true + var spell_t := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_first_dig_spell", + "奥术矢", "board", int(_damage.feat_flags.get("spell_value", 0)), DamageController.TARGET_FRONT_ALIVE, ri, evt.cell, "magic") _damage.submit(spell_t) # 掘进蓄能计数(迭代 11 同调批 T6——battle 域安全开格计数,每 charge_n 格积 1 点) @@ -499,8 +499,8 @@ func _settle() -> void: elif evt.kind == "mine": # 插旗不可逆四情况②:插雷格再挖开 = 只受挖雷一层惩罚(mine_hit 照常兑付——惊动巢穴), # 标中不发(旗已随挖掘擦除、不入轮末判定基线)。 - _grant_routed(_relic_yield_adjust(snap, false), "negative", "mine_hit", evt.cell, ri, "") - _relic_combo = 0 # 矿脉直觉连击清零(踩雷断连——「推理密度直接奖励」的对侧) + _grant_routed(_feat_yield_adjust(snap, false), "negative", "mine_hit", evt.cell, ri, "") + _feat_combo = 0 # 矿脉直觉连击清零(踩雷断连——「推理密度直接奖励」的对侧) # 地形变异 兑付段(授予段尾——触发枚举 on_open:挖开即伤、chain 通道同口径,轮末结算逐行兑现; # 信息型 hide_number 不进结算——呈现层读取,见 board_view 数字遮蔽;雷格恒无变异——布置段排除, # 双层叠罚不存在;插旗格直挖照吃变异伤害——地形后果同 mine_hit 口径,非奖励不作废) @@ -516,25 +516,25 @@ func _settle() -> void: _damage.submit(vt) # 吞噬流四面(迭代 12 T6——地形从纯税到第三资源带;伤害仍发生=乙稿判据 3 承载): # ①荆棘编织者=挖开伤害型地形格额外掉落一次格面资源(主动吃地形换资源) - if int(_damage.relic_flags.get("terrain_dig_double", 0)) > 0: + if int(_damage.feat_flags.get("terrain_dig_double", 0)) > 0: var tw_snap = _reward_economy.grant(evt.cell) if tw_snap is Dictionary: _grant_routed(tw_snap, "positive", "safe_cell", evt.cell, ri, "", "(荆棘编织)") # ②地脉采撷=受地形伤掉落小额资源补偿(薄矿化——每次受伤兑一份) - if int(_damage.relic_flags.get("terrain_refund", 0)) > 0: + if int(_damage.feat_flags.get("terrain_refund", 0)) > 0: var lh_snap = _reward_economy.grant(evt.cell) if lh_snap is Dictionary: _grant_routed(lh_snap, "positive", "safe_cell", evt.cell, ri, "", "(地脉补偿)") # ③熔火之躯=受地形伤积层(触发键=「受地形伤」通用不按种类特化——定稿红线承载) - if int(_damage.relic_flags.get("variant_counter_value", 0)) > 0: - _relic_magma_layers += 1 + if int(_damage.feat_flags.get("variant_counter_value", 0)) > 0: + _feat_magma_layers += 1 for evt in _facts_flags: _judge_flag(evt, ri) # 战旗军规开单段(迭代 12 T3 标旗流——flag_judged 批后统一开单:轮末每面标中旗对最前存活敌 # 结算 board_typed 必中伤害〔复用零新配方;「旗所在巢穴敌」简化为 default_target 最前存活敌—— # 台账披露待追认〕;完美布防=本轮旗批全中(hit>0 且 miss==0)时旗伤 ×mult——全对推理奖励)。 # 扫雷结算触发点=flag_strike/flag_all_hit/nest_aura 挂点承载红线①。 - if int(_damage.relic_flags.get("flag_strike_value", 0)) > 0: + if int(_damage.feat_flags.get("flag_strike_value", 0)) > 0: var hits := 0 var misses := 0 for fevt in _facts_flags: @@ -543,26 +543,26 @@ func _settle() -> void: else: misses += 1 if hits > 0: - var mult := int(_damage.relic_flags.get("flag_all_hit_mult", 1)) - var strike_value := int(_damage.relic_flags.get("flag_strike_value", 0)) + var mult := int(_damage.feat_flags.get("flag_all_hit_mult", 1)) + var strike_value := int(_damage.feat_flags.get("flag_strike_value", 0)) if misses == 0 and mult > 1: strike_value *= mult # 完美布防:同轮全中旗伤倍增(全对推理奖励) for fevt in _facts_flags: if not bool(fevt.get("hit", false)): continue - var st_t := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "relic_flag_strike", + var st_t := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_flag_strike", "战旗军规", "board", strike_value, DamageController.TARGET_FRONT_ALIVE, ri, fevt.cell, "normal") _damage.submit(st_t) # 熔火之躯轮末反打(迭代 12 T6 吞噬流——受地形伤积层,轮末每层对全体敌 ~1 必中反伤; # 触发键=「受地形伤」通用不按地形种类特化——定稿红线承载;反打后清零) - var magma := int(_damage.relic_flags.get("variant_counter_value", 0)) - if magma > 0 and _relic_magma_layers > 0: - var mg_t := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, "relic_magma_body", - "熔火反噬 ×%d" % _relic_magma_layers, "board", magma * _relic_magma_layers, + var magma := int(_damage.feat_flags.get("variant_counter_value", 0)) + if magma > 0 and _feat_magma_layers > 0: + var mg_t := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_magma_body", + "熔火反噬 ×%d" % _feat_magma_layers, "board", magma * _feat_magma_layers, DamageController.TARGET_ALL_ALIVE, ri, minesweeper.hero_pos(), "", 0, "", {}) _damage.submit(mg_t) - _relic_magma_layers = 0 + _feat_magma_layers = 0 if _facts_path == "cleared": _grant_clear_reward(ri) _damage.flush() # 授予段界结账(B 模式——段内开单攒批到此统一兑现;A 模式 no-op;清图批前另有同步结账点) @@ -592,7 +592,7 @@ func _settle() -> void: _emit_row("verdict", "", Vector2i(-1, -1), ri, "下一轮次", 0, {}, "") _exit_distribution() # 深挖钻头 v2 轮初快照滚动(本轮付费次数已消费——下一轮 N=当前值−新快照) - _relic_paid_at_round_start = int(action_economy.deep_paid_count) + _feat_paid_at_round_start = int(action_economy.deep_paid_count) _settling = false @@ -601,18 +601,18 @@ func _settle() -> void: # 开格产出乘区(迭代 12 T5 开格流——贪婪矿灯/矿脉直觉/赌徒的血签;duplicate 隔离不回写快照): # positive=power ×(1+静态 mult% + 矿脉直觉连击加成 min(cap,combo)×10%);negative=power ×(1+cost%)。 # 零旗=原快照原样返回(零扰动)。 -func _relic_yield_adjust(snap: Dictionary, positive: bool) -> Dictionary: - var pos_m := int(_damage.relic_flags.get("dig_yield_pos_mult", 0)) - var neg_m := int(_damage.relic_flags.get("dig_yield_neg_mult", 0)) - if positive and pos_m <= 0 and not _damage.relic_flags.has("combo_cap"): +func _feat_yield_adjust(snap: Dictionary, positive: bool) -> Dictionary: + var pos_m := int(_damage.feat_flags.get("dig_yield_pos_mult", 0)) + var neg_m := int(_damage.feat_flags.get("dig_yield_neg_mult", 0)) + if positive and pos_m <= 0 and not _damage.feat_flags.has("combo_cap"): return snap if not positive and neg_m <= 0: return snap var out: Dictionary = snap.duplicate() var power := int(out.get("power", int(_reward_economy.polarity_row(String(out.get("cell_type", "")), "positive" if positive else "negative").power))) var pct := pos_m if positive else neg_m - if positive and _damage.relic_flags.has("combo_cap"): - pct += mini(int(_damage.relic_flags.get("combo_cap", 5)), _relic_combo) * 10 + if positive and _damage.feat_flags.has("combo_cap"): + pct += mini(int(_damage.feat_flags.get("combo_cap", 5)), _feat_combo) * 10 out["power"] = maxi(1, int(round(float(power) * (1.0 + float(pct) / 100.0)))) return out @@ -832,7 +832,7 @@ func _judge_flag(evt: Dictionary, ri: int) -> void: # 净化工兵(迭代 12 T6 吞噬流——标中旗中和相邻伤害型地形格):🔴 R4 甲永不对冲边界= # **仅限伤害型**(thorn/magma/rockfall——variation 表 effect=flat_damage 判据),信息型 # (hide_number 族)永不清除(红线①看门税不可对冲);中和=变普通格(BoardData.clear_variants)。 - if int(_damage.relic_flags.get("flag_neutralize", 0)) > 0: + if int(_damage.feat_flags.get("flag_neutralize", 0)) > 0: var board: BoardData = minesweeper.current_board() for n in board.neighbors8(cell): var vids_n: Array = board.variant_ids(n) @@ -844,10 +844,10 @@ func _judge_flag(evt: Dictionary, ri: int) -> void: break if harmful: board.clear_variants(n) - _emit_row("terrain_neutralized", "relic_purge_sapper", n, ri, "净化工兵·地形中和", 0, {}, "") + _emit_row("terrain_neutralized", "feat_purge_sapper", n, ri, "净化工兵·地形中和", 0, {}, "") # 雷区图谱(迭代 12 T3 标旗流——标中雷额外获得一份格面资源:插旗开始产出对标开格; # 复用 grant 单点,额外兑付走同一 _grant_routed 正向路由) - if int(_damage.relic_flags.get("flag_resource_extra", 0)) > 0: + if int(_damage.feat_flags.get("flag_resource_extra", 0)) > 0: var extra_snap = _reward_economy.grant(cell) if extra_snap is Dictionary: _grant_routed(extra_snap, "positive", "flag_hit", cell, ri, "", "(雷区图谱)") @@ -952,10 +952,10 @@ func _combat_segment(ri: int) -> void: round_safe += 1 elif evt.kind == "mine": round_mine = true - _damage.relic_flags["dig_streak"] = 0 if round_mine else round_safe + _damage.feat_flags["dig_streak"] = 0 if round_mine else round_safe # 蓄力层注入(迭代 12 T5——整备军备「每层下轮首段 +1」/厚积薄发满层 spike/铁壁整备减伤—— # 三件同读 action_economy.charge_layers;battle 域每轮刷新) - _damage.relic_flags["charge_layers"] = action_economy.charge_layers + _damage.feat_flags["charge_layers"] = action_economy.charge_layers # ⓪ 主角先手攻击(基础攻击恒存在——2026-09-11 用户裁决;先于敌方行动;授予段负向直伤致死 → 致电短路不先手)。 # 单分量伤害构成(2026-09-19 充能退役批·用户拍板):主角攻击 = 唯一基础分量(atk,吃掷点含暴击; # 类型 = 主角 attack_type 表值——2026-09-18 营地扩展前置批:量与类型抽象分离;查甲效矩阵对应行取克制修正)。 @@ -971,11 +971,11 @@ func _combat_segment(ri: int) -> void: _damage.submit(ht) # 连珠矢(迭代 12 T4 穿刺流——开格节奏变攻击段数):本轮每开 ~3 安全格 +1 段穿刺附加 # (上限 cap;段值 provisional 固定值不乘 atk——台账披露)。扫描雷结算触发点=dig_count_segments。 - var chain_value := int(_damage.relic_flags.get("chain_value", 0)) + var chain_value := int(_damage.feat_flags.get("chain_value", 0)) if chain_value > 0: - var segments := mini(int(_damage.relic_flags.get("chain_cap", 2)), int(_damage.relic_flags.get("dig_streak", 0)) / 3) + var segments := mini(int(_damage.feat_flags.get("chain_cap", 2)), int(_damage.feat_flags.get("dig_streak", 0)) / 3) for s in segments: - var ct := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "relic_chain_arrows", + var ct := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_chain_arrows", "连珠矢", "board", chain_value, DamageController.TARGET_FRONT_ALIVE, ri, minesweeper.hero_pos(), "pierce") _damage.submit(ct) diff --git a/game/core/config_db.gd b/game/core/config_db.gd index 6f8a1bc9..9df68de8 100644 --- a/game/core/config_db.gd +++ b/game/core/config_db.gd @@ -27,7 +27,7 @@ const TABLE_MANIFEST := { "treasure_items": "treasure_items.tres", # 迭代 7 T1 秘宝内容批(TreasureItemTable 六行——定义表 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 豁免——TreasureItemTable 先例;空表合法态=主时间线零扰动) + "feats": "feats.tres", # 迭代 12 T1 双层 BD 规则改写型特性基建(FeatTable 定义表 Rule 4 豁免——TreasureItemTable 先例;空表合法态=主时间线零扰动) } var action_economy: ActionEconomyTable @@ -50,7 +50,7 @@ var meta_tree: MetaTreeTable # 迭代 12 T8 类型化持用(消费面走 tre var treasure_items: TreasureItemTable # 迭代 7 T1 类型化持用(消费面走 treasure_* 访问器) var adventure: AdventureTable var attunement_growth: AttunementGrowthTable # 迭代 11 第①批 T1 类型化持用(消费面走 attunement_* 访问器) -var relics: RelicTable # 迭代 12 T1 类型化持用(消费面走 relic_* 访问器) +var feats: FeatTable # 迭代 12 T1 类型化持用(消费面走 feat_* 访问器) var is_ready := false var _diagnostics: Array[Dictionary] = [] # {severity, category, message} @@ -142,7 +142,7 @@ func load_tables(root: String, collect_only: bool) -> bool: treasure_items = loaded.get("treasure_items") adventure = loaded.get("adventure") attunement_growth = loaded.get("attunement_growth") - relics = loaded.get("relics") + feats = loaded.get("feats") meta_tree = loaded.get("meta_tree") is_ready = not has_errors return not has_errors @@ -150,13 +150,13 @@ func load_tables(root: String, collect_only: bool) -> bool: # ---- relic 访问器(迭代 12 T1——消费面一律走本组,禁直读行集)---- -func relic_row(relic_id: String) -> Dictionary: - return relics.row_by_id(relic_id) if relics != null else {} +func feat_row(feat_id: String) -> Dictionary: + return feats.row_by_id(feat_id) if feats != null else {} # 规则改写型特性带令牌构建源(池接线——adventure_runner._build_curio_deck 消费;行 rarity→投放档映射) -func relic_rows() -> Array: - return relics.rows if relics != null else [] +func feat_rows() -> Array: + return feats.rows if feats != null else [] # 天赋树节点行(迭代 12 T8——MetaProgression 树段/营地树 UI 取数口) diff --git a/game/core/damage_controller.gd b/game/core/damage_controller.gd index 161dce06..93fc496b 100644 --- a/game/core/damage_controller.gd +++ b/game/core/damage_controller.gd @@ -60,10 +60,10 @@ 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 编排器经 FeatsEngine 求值后注入;空字典=零扰动。 # rider_flat=英雄侧必中附加值〔奥术余烬族〕/rider_crit=档二 rider 可暴击/first_dig_spell=每战首次 # 开安全格追加魔法矢〔消费在编排器授予段〕/clamp_magic_light·clamp_magic_heavy=克制点直调) -var relic_flags: Dictionary = {} +var feat_flags: Dictionary = {} func _init(db_ref, rng_ref, units_ref, effects_ref) -> void: @@ -200,40 +200,40 @@ func _resolve_hero_attack(t: Dictionary) -> void: var threshold: int = defender_ac(target) + int(db.settlement.attack_bonus) # 矿工重锤·命中交换(迭代 12 T4 普攻流——正负双修原生形态:命中判定减成 trade_hit 点 # 〔更易命中〕换攻击增伤 trade_atk%——高 AC 负期望/低 AC 正期望) - var trade_hit := int(relic_flags.get("hit_trade_hit", 0)) + var trade_hit := int(feat_flags.get("hit_trade_hit", 0)) if trade_hit > 0: threshold = maxi(1, threshold - trade_hit) var crit: bool = bool(roll_read.crit) var discount: int = _roll_discount(int(roll_read.roll), threshold) # 专注(迭代 12 T4 普攻流——免掷骰恒定折扣:方差买断成期望——稳定器人格;roll 记 0 归因) - var rewrite := int(relic_flags.get("armor_roll_rewrite", 0)) + var rewrite := int(feat_flags.get("armor_roll_rewrite", 0)) if rewrite > 0: discount = rewrite crit = false roll_read["roll"] = 0 # 厚积薄发满层必中(迭代 12 T5 蓄力流——spike ready 时 discount 恒全额、暴击面走倍乘不加掷) - if int(relic_flags.get("spike_mult", 0)) > 0 and int(relic_flags.get("charge_layers", 0)) >= 3: + if int(feat_flags.get("spike_mult", 0)) > 0 and int(feat_flags.get("charge_layers", 0)) >= 3: discount = 100 # 猎弱本能(迭代 12 T4 穿刺流——对轻甲敌折扣档改善 value 档:对冲 pierce 列 L1 −1 开局罚; # 改善 = 折扣表向全额方向移档,nat 最大面/已全额不重复) - var shift := int(relic_flags.get("discount_shift", 0)) + var shift := int(feat_flags.get("discount_shift", 0)) if shift > 0 and armor == "light" and discount < 100: discount = _discount_shift_up(discount, shift) - var cdelta: int = _counter_delta(atk_type, armor) + _relic_clamp_bonus(atk_type, armor) + var cdelta: int = _counter_delta(atk_type, armor) + _feat_clamp_bonus(atk_type, armor) # 淬毒刃纹(迭代 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 var base_value := float(t.base_value) - var trade_atk := int(relic_flags.get("hit_trade_atk", 0)) + var trade_atk := int(feat_flags.get("hit_trade_atk", 0)) if trade_atk > 0: base_value *= 1.0 + float(trade_atk) / 100.0 # 重锤增伤面(交换的正向侧) # 蓄力流(迭代 12 T5):整备军备「每层下轮首段 +1」(层数×value 加算)+厚积薄发满层(≥3) # spike 倍乘+强制必中(discount 100/crit 关闭掷骰面——方差买断的爆发出口) - var charge_layers := int(relic_flags.get("charge_layers", 0)) + var charge_layers := int(feat_flags.get("charge_layers", 0)) if charge_layers > 0: - base_value += float(charge_layers * int(relic_flags.get("ap_carry_value", 0))) - var spike_mult := int(relic_flags.get("spike_mult", 0)) + base_value += float(charge_layers * int(feat_flags.get("ap_carry_value", 0))) + var spike_mult := int(feat_flags.get("spike_mult", 0)) var spike_ready := spike_mult > 0 and charge_layers >= 3 if spike_ready: base_value *= float(spike_mult) @@ -250,22 +250,22 @@ func _resolve_hero_attack(t: Dictionary) -> void: # 英雄侧 rider(迭代 12 T2 魔法流——enemy_skill rider 对称位补齐):后置必中附加,独立于掷点 # 与乘区(奥术余烬「普攻命中后恒定结算」语义);档二(magic 4 件)rider 可暴击=当次 crit 时 # rider 值乘暴击倍率。rider 不改变下限语义(landed 含 rider 后不再过 floor)。 - var hero_rider := int(relic_flags.get("rider_flat", 0)) + var hero_rider := int(feat_flags.get("rider_flat", 0)) # 猎巢人(迭代 12 T3 标旗流——巢穴控制数转伤害光环):控制数 = 敌方 weaken(削弱)实例数 # 〔八型批 side-wide 状态行——标中旗控制巢穴的既有承载;原设计「对其中敌伤害」简化为全体 # 先手攻击附加值——台账披露待追认〕,每控制 1 巢 rider +Z。 - var nest_aura := int(relic_flags.get("nest_aura_value", 0)) + var nest_aura := int(feat_flags.get("nest_aura_value", 0)) if nest_aura > 0: for eff in effects.query_active("enemy"): if String(eff.effect_id) == "weaken": hero_rider += nest_aura # 矿工的执拗(迭代 12 T4 普攻流——「排雷排得多好,打得就有多疼」):本轮安全开格 streak - # 〔编排器授予段实时写入 relic_flags.dig_streak;踩雷清零〕×value,上限 cap。 - var rdig := int(relic_flags.get("round_dig_value", 0)) + # 〔编排器授予段实时写入 feat_flags.dig_streak;踩雷清零〕×value,上限 cap。 + var rdig := int(feat_flags.get("round_dig_value", 0)) if rdig > 0: - hero_rider += mini(int(relic_flags.get("round_dig_cap", 5)), int(relic_flags.get("dig_streak", 0)) * rdig) + hero_rider += mini(int(feat_flags.get("round_dig_cap", 5)), int(feat_flags.get("dig_streak", 0)) * rdig) if hero_rider > 0: - if crit and bool(relic_flags.get("rider_crit", false)): + if crit and bool(feat_flags.get("rider_crit", false)): hero_rider = int(round(float(hero_rider) * float(db.settlement.crit_multiplier))) landed += hero_rider payload["hero_rider"] = hero_rider @@ -273,9 +273,9 @@ func _resolve_hero_attack(t: Dictionary) -> void: payload["killed"] = not target.alive # 击杀标记在扣血后取(原语义不变) # 碎甲震击(迭代 12 T4 普攻流——命中重甲附加「护甲裂解」):复用 off_guard(enemy percent # +25 受伤加深——零新状态行);apply_effect 域闭/重复授予由状态容器自行守卫。 - var crack := int(relic_flags.get("crack_value", 0)) + var crack := int(feat_flags.get("crack_value", 0)) if crack > 0 and armor == "heavy": - effects.apply_effect("off_guard", {"source_class": "relic_armor_crack"}, -1) + effects.apply_effect("off_guard", {"source_class": "feat_armor_crack"}, -1) payload["cracked"] = true var label := _attack_label(String(t.display), crit, discount) if hero_rider > 0: @@ -284,10 +284,10 @@ func _resolve_hero_attack(t: Dictionary) -> void: # 贯穿射击(迭代 12 T4 穿刺流——击杀溢出弹射):击杀时对下一存活敌以穿刺类型弹射 # 本体半额〔原设计「溢出伤害」追踪在单分量模型不可行——半额弹射 provisional 裁定披露待追认; # 无次敌=弹射落空〕。B 模式入队段尾 flush 兑现;A 模式递归 resolve(目标存活守卫内建)。 - if bool(payload.get("killed", false)) and int(relic_flags.get("kill_overflow", 0)) > 0: + if bool(payload.get("killed", false)) and int(feat_flags.get("kill_overflow", 0)) > 0: var next_target = units.default_target() if next_target != null: - var of_t := open_ticket(DamageController.KIND_BOARD_TYPED, "relic_pierce_shot", + var of_t := open_ticket(DamageController.KIND_BOARD_TYPED, "feat_pierce_shot", "贯穿射击", "board", maxi(int(db.settlement.damage_floor), int(landed / 2)), DamageController.TARGET_FRONT_ALIVE, int(t.round_index), t.origin_cell, "pierce") if policy == POLICY_SEGMENT: @@ -309,9 +309,9 @@ func _resolve_enemy_skill(t: Dictionary) -> void: var pooled: int = DamageMath.pool_damage(rolled, clamped_pool("hero_defense"), clamped_pool("enemy_output")) var total: int = maxi(int(db.settlement.damage_floor), pooled) + int(t.rider) # rider 后置于伤害下限——独立必中加项,不进乘区 # 铁壁整备(迭代 12 T5 蓄力流——每层下轮开场 S 点减伤;减后过下限保底) - var shield := int(relic_flags.get("shield_per_layer", 0)) * int(relic_flags.get("charge_layers", 0)) + var shield := int(feat_flags.get("shield_per_layer", 0)) * int(feat_flags.get("charge_layers", 0)) # 稳健拓荒(迭代 12 T10——Fork posture B 侧 rule 键:受击减 1;两侧对称弱值=「不同强非强弱」) - if int(relic_flags.get("bulwark", 0)) > 0: + if int(feat_flags.get("bulwark", 0)) > 0: shield += 1 if shield > 0: total = maxi(int(db.settlement.damage_floor), total - shield) @@ -337,7 +337,7 @@ func _resolve_board_typed(t: Dictionary) -> void: _ineffective_row(t, "无存活敌人") _outcomes[int(t.ticket_id)] = "zero" return - var delta: int = _counter_delta(String(t.attack_type), String(target.get("armor_type", "light"))) + _relic_clamp_bonus(String(t.attack_type), String(target.get("armor_type", "light"))) + var delta: int = _counter_delta(String(t.attack_type), String(target.get("armor_type", "light"))) + _feat_clamp_bonus(String(t.attack_type), String(target.get("armor_type", "light"))) var dealt: int = DamageMath.counter_component(int(t.base_value), delta, int(db.settlement.damage_floor)) units.apply_damage(target, dealt) emit_row_cb.call("damage", String(t.source_class), t.origin_cell, int(t.round_index), "%s → %s" % [String(t.display), String(target.display_name)], dealt, @@ -422,16 +422,16 @@ func _counter_delta(attack_type: String, armor: String) -> int: # 空旗=0 零扰动):魔网织者=magic vs magic(矩阵罚 −1 → 0/正);湛蓝誓约=magic vs heavy(+1 → +2); # 猎手刺剑=pierce vs light(+1——对轻甲敌穿刺提升,对重甲无增益=正负双修)。hero_attack 与 # board_typed 两消费面同享。 -func _relic_clamp_bonus(attack_type: String, armor: String) -> int: +func _feat_clamp_bonus(attack_type: String, armor: String) -> int: var bonus := 0 if attack_type == "magic": if armor == "magic": - bonus += int(relic_flags.get("clamp_magic_magic", 0)) + bonus += int(feat_flags.get("clamp_magic_magic", 0)) elif armor == "heavy": - bonus += int(relic_flags.get("clamp_magic_heavy", 0)) + bonus += int(feat_flags.get("clamp_magic_heavy", 0)) elif attack_type == "pierce": if armor == "light": - bonus += int(relic_flags.get("clamp_pierce_light", 0)) + bonus += int(feat_flags.get("clamp_pierce_light", 0)) return bonus diff --git a/game/core/relics_engine.gd b/game/core/feats_engine.gd similarity index 73% rename from game/core/relics_engine.gd rename to game/core/feats_engine.gd index a37ec3bf..3b7a5ebb 100644 --- a/game/core/relics_engine.gd +++ b/game/core/feats_engine.gd @@ -4,15 +4,15 @@ # 同流派标签 relic 计数 ≥2 → 档一(流派核心规则);≥4 → 档二(完全体);<2 = 散件(单体保底—— # 每件效果独立生效,档位只做质变不做启动,反例 B-2)。 # 纯函数零 await 零 RNG 零副作用(Attunement 求值服务同款纪律);核心层零特判延伸——引擎只读 -# relic_table 行集与持有集,接线点消费 tiers/hooks 投影。 -class_name RelicsEngine +# feat_table 行集与持有集,接线点消费 tiers/hooks 投影。 +class_name FeatsEngine extends RefCounted -## 档位求值:relic_ids 持有集 × 行集 → {archetype: tier}(tier ∈ 0/1/2;无行流派不出现) -static func tiers_for(relic_ids: Array, table: RelicTable) -> Dictionary: +## 档位求值:feat_ids 持有集 × 行集 → {archetype: tier}(tier ∈ 0/1/2;无行流派不出现) +static func tiers_for(feat_ids: Array, table: FeatTable) -> Dictionary: var counts := {} - for rid in relic_ids: + for rid in feat_ids: var row: Dictionary = table.row_by_id(String(rid)) if table != null else {} if row.is_empty(): continue @@ -30,16 +30,16 @@ static func tiers_for(relic_ids: Array, table: RelicTable) -> Dictionary: return out -## 挂点投影:持有集 → [{relic_id, hook, archetype, tier, effects}](表序稳定——同集合同投影, +## 挂点投影:持有集 → [{feat_id, hook, archetype, tier, effects}](表序稳定——同集合同投影, ## MC 逐位可复放承载) -static func hooks_for(relic_ids: Array, table: RelicTable) -> Array: +static func hooks_for(feat_ids: Array, table: FeatTable) -> Array: var out: Array = [] - for rid in relic_ids: + for rid in feat_ids: var row: Dictionary = table.row_by_id(String(rid)) if table != null else {} if row.is_empty(): continue out.append({ - "relic_id": String(row.get("relic_id", "")), + "feat_id": String(row.get("feat_id", "")), "hook": String(row.get("hook", "")), "archetype": String(row.get("archetype", "")), "effects": (row.get("effects", {}) as Dictionary).duplicate(true), diff --git a/game/core/relics_engine.gd.uid b/game/core/feats_engine.gd.uid similarity index 100% rename from game/core/relics_engine.gd.uid rename to game/core/feats_engine.gd.uid diff --git a/game/core/level_runner.gd b/game/core/level_runner.gd index cc29876c..06582dd3 100644 --- a/game/core/level_runner.gd +++ b/game/core/level_runner.gd @@ -479,7 +479,7 @@ func _start_battle(node: Dictionary) -> bool: "hero_armor_class": effective_ac(), "hero_ap_bonus": effective_ap_bonus(), "attunement": _attunement, "attune_charges": _attune_charges, - "relics": _relics, + "feats": _feats, "tree_rules": _tree_rules, "node_type": String(node.get("type", ""))}): push_error("[关卡节点地图] 战斗启动失败(depth %s)" % str(node.get("node_depth", -1))) @@ -591,18 +591,18 @@ func set_attunement(ids: Array) -> void: # 特性持有集注入(迭代 12 T1——AdventureRunner._start_current_level 唯一调用方;begin_battle -# enc.relics 携带进战斗求值;**共享引用不复制**——关内奖励节点兑付的 relic 须当关后续战斗生效 +# enc.feats 携带进战斗求值;**共享引用不复制**——关内奖励节点兑付的 relic 须当关后续战斗生效 # (attune_charges 共享账本同款);空集=零扰动) -var _relics: Array = [] +var _feats: Array = [] -func set_relics(ids: Array) -> void: - _relics = ids +func set_feats(ids: Array) -> void: + _feats = ids # 特性持有集读面(迭代 12 呈现补齐——HUD C 区特性段数据源) -func relics_held() -> Array: - return _relics.duplicate() +func feats_held() -> Array: + return _feats.duplicate() # 树 rule 键集注入(迭代 12 T10——begin_battle enc.tree_rules 携带;Fork 激活侧 effects) @@ -638,7 +638,7 @@ func grant_treasure_item(item_id: String, source: String) -> Dictionary: # 三选一 GP 袋件入账(迭代 10——钱包收口扩「奖励入账」入口;复用战斗完成同一入账原语, # wallet_changed 载荷带 source 供呈现归因) -func grant_bonus_gp(amount: int) -> void: +func grant_curio_gp(amount: int) -> void: gp = maxi(0, gp + amount) wallet_changed.emit("gain", gp, {"amount": amount, "source": "curio_pick"}) @@ -779,26 +779,9 @@ func _shop_draw_shelf() -> void: _shop_shelf.append(tok) if _shop_shelf.size() >= 3: break - # 商店保底(迭代 12 T6——「本关变异交互件必刷」简化口径:货架零特性时**挤掉最后一张普通卡** - # 换吞噬流件〔地形交互件广义映射——按关变异精细绑定归数值批,台账披露待追认〕;占位制货架恒 - # ≤3;池内无 devour 令牌=不保底合法态) - var has_relic := false - for tok in _shop_shelf: - if String((tok as Dictionary).get("kind", "")) == "relic": - has_relic = true - break - if not has_relic and not _shop_shelf.is_empty(): - for row in db.relic_rows(): - if String(row.get("archetype", "")) != "devour": - continue - var rid := String(row.get("relic_id", "")) - for t in _pool_deck: - var tok2: Dictionary = t as Dictionary - if String(tok2.get("kind", "")) == "relic" and String(tok2.get("relic_id", "")) == rid: - _shop_shelf[_shop_shelf.size() - 1] = {"kind": "relic", "tier": String(tok2.get("tier", "")), "relic_id": rid} - break - if String((_shop_shelf[_shop_shelf.size() - 1] as Dictionary).get("kind", "")) == "relic": - break # 换一张即止 + # 商店保底退役(迭代 15 卡② T4——渠道轴定位裁决:商店渠道只出属性值提升+资源,专长在非 Boss + # 渠道只以门票形态出现(奇物.md v2.7/A4 断言)。迭代 12 T6「货架零特性挤卡换吞噬流件」简化口径 + # 与渠道白名单直接冲突(devour=规则改写型=专长系),随渠道分离退役;专长商店入口=T5 门票。 # 库存余量(TP0——令牌库内该 kind 剩余枚数单点读;未注入/防御读 = 0) @@ -836,13 +819,13 @@ func shop_shelf() -> Array: var display := String(row.get("display_name", kind)) var value := int(db.adventure.curio_value(kind, tier)) if row.is_empty() and kind == "relic": - var rrow: Dictionary = db.relic_row(String(tok.get("relic_id", ""))) + var rrow: Dictionary = db.feat_row(String(tok.get("feat_id", ""))) display = String(rrow.get("display_name", "特性")) value = 0 out.append({ "kind_id": kind, "tier": tier, - "relic_id": String(tok.get("relic_id", "")), + "feat_id": String(tok.get("feat_id", "")), "display_name": display, "value": value, "price": pool_price(kind), diff --git a/game/data/schemas/adventure_table.gd b/game/data/schemas/adventure_table.gd index e82a3a10..94ec89b4 100644 --- a/game/data/schemas/adventure_table.gd +++ b/game/data/schemas/adventure_table.gd @@ -13,7 +13,7 @@ class_name AdventureTable extends Resource -## 概念指路:本表的属性值提升与资源,与 relics.tres 的专长,同属一个对象—— +## 概念指路:本表的属性值提升与资源,与 feats.tres 的专长,同属一个对象—— ## 「奇物」(一次冒险内各渠道给予玩家的增益总称)。定义见 design/gdd/systems/冒险循环/奇物.md。 ## 它们是它的分类轴上的枚举(专长 / 属性值提升 / 资源),不是三套系统; ## 本表承载属性值提升与资源的种类配额与件值。 diff --git a/game/data/schemas/relic_table.gd b/game/data/schemas/feat_table.gd similarity index 89% rename from game/data/schemas/relic_table.gd rename to game/data/schemas/feat_table.gd index 67205374..6f43a3d4 100644 --- a/game/data/schemas/relic_table.gd +++ b/game/data/schemas/feat_table.gd @@ -6,25 +6,25 @@ # 「奇物」(一次冒险内各渠道给予玩家的增益总称)。定义见 design/gdd/systems/冒险循环/奇物.md。 # 它们是它分类轴上的枚举(专长 / 属性值提升 / 资源),不是三套系统;本表承载「专长」(专长 Feat)的逐项定义。 # (2026-10-05 命名统一 + 分类重构:旧称「流派件」→「规则改写型特性」→ 现定名「专长」; -# 表名与 relic_id 沿用历史命名,与统一词族对齐归后续批次——见 06-待办清单「命名统一」条目。 +# 表名与 feat_id 沿用历史命名,与统一词族对齐归后续批次——见 06-待办清单「命名统一」条目。 # 目标渠道:专长走关底 Boss 击败后专属三选一(不新增节点类型),见奇物文档 2.1 节。) -# 数据配置 RelicTable schema(奇物·专长定义表;行 = Dictionary 键 relic_id,Rule 4 豁免 +# 数据配置 FeatTable schema(奇物·专长定义表;行 = Dictionary 键 feat_id,Rule 4 豁免 # 定义表——TreasureItemTable 先例)。 -# 行字段:relic_id 主键 / display_name / rarity ∈ {common, rare, rarest}(对齐令牌池三档命名—— +# 行字段:feat_id 主键 / display_name / rarity ∈ {common, rare, rarest}(对齐令牌池三档命名—— # 投放档映射:common/rare→稀有令牌档、rarest→罕见令牌档;relic 令牌永不入普通档=「挂稀有/罕见档」 # 追认 b 接线口径)/ archetype ∈ ARCHETYPES 七流派标签 / hook ∈ HOOKS 挂点枚举(结算段槽——整合定稿 # §2.1 哈迪斯恩赐槽同构)/ effects Dictionary(hook 消费参数,provisional 投值)。 # 正负双修(整合定稿 B2 判据):effects 允许负向键(cost_* 族)——纯增益件须专项论证(反例 B-3),加载期 # 仅校验键域不禁纯增益(专项论证件以 positive_only 批注登记,人工清单呈报)。 # 档位激活(整合定稿 设计工具 1——以撒套装/小骨铭文):同标签计数 ≥2→档一(流派核心规则)/≥4→档二 -# (完全体);散件单体保底=每件效果独立生效,档位只做质变(反例 B-2)——引擎纯函数 RelicsEngine。 +# (完全体);散件单体保底=每件效果独立生效,档位只做质变(反例 B-2)——引擎纯函数 FeatsEngine。 # 🔴 层级纪律(任务书红线 2):专长只走局内随机渠道(三选一/商店/格面)——本表行永不入同调位 # (treasure_items 表)/局外树(T8 节点表);扫雷触发点铁律:每流派 ≥1 行 hook ∈ MINE_HOOKS(加载校验, # 违反=红线①触碰面)。 -class_name RelicTable +class_name FeatTable extends Resource -@export var rows: Array = [] # Array[Dictionary] 键 relic_id +@export var rows: Array = [] # Array[Dictionary] 键 feat_id ## 流派标签词表(七核心——喂雷/淘金不在本批,R5 甲/缓议) const ARCHETYPES := ["magic", "pierce", "normal", "dig", "flag", "charge", "devour"] @@ -85,12 +85,12 @@ func validate() -> Array: return errs # 空表合法态(T1 基建先行——T2 起逐流派填行) var seen := {} for row in rows: - var rid: String = row.get("relic_id", "") + var rid: String = row.get("feat_id", "") if rid == "": - errs.append({"category": "domain", "message": "relic_id 空"}) + errs.append({"category": "domain", "message": "feat_id 空"}) continue if seen.has(rid): - errs.append({"category": "dup_key", "message": "relic_id 重复: %s" % rid}) + errs.append({"category": "dup_key", "message": "feat_id 重复: %s" % rid}) continue seen[rid] = true if String(row.get("display_name", "")) == "": @@ -104,7 +104,7 @@ func validate() -> Array: if not HOOKS.has(hook): errs.append({"category": "enum", "message": "%s hook 出域(挂点枚举封闭): %s" % [rid, hook]}) for k in row.keys(): - if not ["relic_id", "display_name", "rarity", "archetype", "hook", "effects", "note"].has(String(k)): + if not ["feat_id", "display_name", "rarity", "archetype", "hook", "effects", "note"].has(String(k)): errs.append({"category": "zero_flat", "message": "%s 行键出白名单: %s" % [rid, String(k)]}) var fx: Dictionary = row.get("effects", {}) as Dictionary for ek in fx.keys(): @@ -126,8 +126,8 @@ func validate() -> Array: return errs -func row_by_id(relic_id: String) -> Dictionary: +func row_by_id(feat_id: String) -> Dictionary: for row in rows: - if String(row.get("relic_id", "")) == relic_id: + if String(row.get("feat_id", "")) == feat_id: return row return {} diff --git a/game/data/schemas/relic_table.gd.uid b/game/data/schemas/feat_table.gd.uid similarity index 100% rename from game/data/schemas/relic_table.gd.uid rename to game/data/schemas/feat_table.gd.uid diff --git a/game/data/schemas/meta_tree_table.gd b/game/data/schemas/meta_tree_table.gd index 603458c7..4ae7a4f5 100644 --- a/game/data/schemas/meta_tree_table.gd +++ b/game/data/schemas/meta_tree_table.gd @@ -3,7 +3,7 @@ # A/B 免费切换、彩蛋觉醒能否机械承载(P0——迭代 12 批次二 T8,R1 甲融合案) # Date: 2026-10-04(双层 BD 实施批 T8;设计权威 双层BD体系-整合定稿-2026-10-03 §1.4) # 数据配置 MetaTreeTable schema(元进度 回填 owner 批末落文;行 = Dictionary 键 node_id,Rule 4 豁免 -# 定义表——RelicTable 先例)。 +# 定义表——FeatTable 先例)。 # 行字段:node_id / display_name / vein ∈ VEINS(spine=主干脊柱 row-gating,GP 买深度;forge=锻造脉 # 数值专精;keystone=钥石脉内容解锁;attune=同调脉槽位与规则)/ kind ∈ KINDS(stat=四线数值/ # rule=全局规则/unlock=内容解锁/socket=同调位/fork=A/B 互斥对/easter=彩蛋觉醒)/ diff --git a/game/data/tables/relics.tres b/game/data/tables/feats.tres similarity index 83% rename from game/data/tables/relics.tres rename to game/data/tables/feats.tres index 560f2719..29d21854 100644 --- a/game/data/tables/relics.tres +++ b/game/data/tables/feats.tres @@ -1,11 +1,11 @@ -[gd_resource type="Resource" script_class="RelicTable" load_steps=2 format=3] +[gd_resource type="Resource" script_class="FeatTable" load_steps=2 format=3] -[ext_resource type="Script" path="res://data/schemas/relic_table.gd" id="1"] +[ext_resource type="Script" path="res://data/schemas/feat_table.gd" id="1"] [resource] script = ExtResource("1") rows = [{ -"relic_id": "arcane_ember", +"feat_id": "arcane_ember", "display_name": "奥术余烬", "rarity": "common", "archetype": "magic", @@ -14,7 +14,7 @@ rows = [{ "value": 1 } }, { -"relic_id": "mana_weaver", +"feat_id": "mana_weaver", "display_name": "魔网织者", "rarity": "rare", "archetype": "magic", @@ -24,7 +24,7 @@ rows = [{ "polarity": "magic_vs_magic" } }, { -"relic_id": "azure_oath", +"feat_id": "azure_oath", "display_name": "湛蓝誓约", "rarity": "rare", "archetype": "magic", @@ -34,7 +34,7 @@ rows = [{ "polarity": "magic_vs_heavy" } }, { -"relic_id": "mana_resonance", +"feat_id": "mana_resonance", "display_name": "魔能共鸣", "rarity": "rarest", "archetype": "magic", @@ -43,7 +43,7 @@ rows = [{ "value": 2 } }, { -"relic_id": "mine_atlas", +"feat_id": "mine_atlas", "display_name": "雷区图谱", "rarity": "common", "archetype": "flag", @@ -52,7 +52,7 @@ rows = [{ "value": 1 } }, { -"relic_id": "nest_hunter", +"feat_id": "nest_hunter", "display_name": "猎巢人", "rarity": "rare", "archetype": "flag", @@ -61,7 +61,7 @@ rows = [{ "value": 1 } }, { -"relic_id": "perfect_defense", +"feat_id": "perfect_defense", "display_name": "完美布防", "rarity": "rare", "archetype": "flag", @@ -70,7 +70,7 @@ rows = [{ "value": 2 } }, { -"relic_id": "war_banner", +"feat_id": "war_banner", "display_name": "战旗军规", "rarity": "rarest", "archetype": "flag", @@ -79,7 +79,7 @@ rows = [{ "value": 2 } }, { -"relic_id": "pierce_shot", +"feat_id": "pierce_shot", "display_name": "贯穿射击", "rarity": "rarest", "archetype": "pierce", @@ -88,7 +88,7 @@ rows = [{ "value": 1 } }, { -"relic_id": "chain_arrows", +"feat_id": "chain_arrows", "display_name": "连珠矢", "rarity": "rare", "archetype": "pierce", @@ -98,7 +98,7 @@ rows = [{ "cap": 2 } }, { -"relic_id": "weak_instinct", +"feat_id": "weak_instinct", "display_name": "猎弱本能", "rarity": "rare", "archetype": "pierce", @@ -107,7 +107,7 @@ rows = [{ "value": 1 } }, { -"relic_id": "hunter_rapier", +"feat_id": "hunter_rapier", "display_name": "猎手刺剑", "rarity": "common", "archetype": "pierce", @@ -117,7 +117,7 @@ rows = [{ "polarity": "pierce_vs_light" } }, { -"relic_id": "miner_stubborn", +"feat_id": "miner_stubborn", "display_name": "矿工的执拗", "rarity": "common", "archetype": "normal", @@ -127,7 +127,7 @@ rows = [{ "cap": 5 } }, { -"relic_id": "miner_hammer", +"feat_id": "miner_hammer", "display_name": "矿工重锤", "rarity": "common", "archetype": "normal", @@ -137,7 +137,7 @@ rows = [{ "trade_hit": 2 } }, { -"relic_id": "focus_mind", +"feat_id": "focus_mind", "display_name": "专注", "rarity": "rare", "archetype": "normal", @@ -146,7 +146,7 @@ rows = [{ "value": 75 } }, { -"relic_id": "armor_crack", +"feat_id": "armor_crack", "display_name": "碎甲震击", "rarity": "rare", "archetype": "normal", @@ -155,7 +155,7 @@ rows = [{ "value": 25 } }, { -"relic_id": "greedy_lantern", +"feat_id": "greedy_lantern", "display_name": "贪婪矿灯", "rarity": "common", "archetype": "dig", @@ -165,7 +165,7 @@ rows = [{ "cost_mult": 25 } }, { -"relic_id": "vein_intuition", +"feat_id": "vein_intuition", "display_name": "矿脉直觉", "rarity": "rare", "archetype": "dig", @@ -175,7 +175,7 @@ rows = [{ "cap": 5 } }, { -"relic_id": "gambler_pact", +"feat_id": "gambler_pact", "display_name": "赌徒的血签", "rarity": "rarest", "archetype": "dig", @@ -185,7 +185,7 @@ rows = [{ "cost_mult": 100 } }, { -"relic_id": "deep_drill", +"feat_id": "deep_drill", "display_name": "深挖钻头", "rarity": "rare", "archetype": "dig", @@ -195,7 +195,7 @@ rows = [{ "cap": 4 } }, { -"relic_id": "armory_prep", +"feat_id": "armory_prep", "display_name": "整备军备", "rarity": "rarest", "archetype": "charge", @@ -205,7 +205,7 @@ rows = [{ "cap": 3 } }, { -"relic_id": "iron_wall", +"feat_id": "iron_wall", "display_name": "铁壁整备", "rarity": "rare", "archetype": "charge", @@ -214,7 +214,7 @@ rows = [{ "shield_per_layer": 1 } }, { -"relic_id": "patient_burst", +"feat_id": "patient_burst", "display_name": "厚积薄发", "rarity": "rare", "archetype": "charge", @@ -223,7 +223,7 @@ rows = [{ "value": 2 } }, { -"relic_id": "ley_harvest", +"feat_id": "ley_harvest", "display_name": "地脉采撷", "rarity": "common", "archetype": "devour", @@ -232,7 +232,7 @@ rows = [{ "value": 1 } }, { -"relic_id": "thorn_weaver", +"feat_id": "thorn_weaver", "display_name": "荆棘编织者", "rarity": "rare", "archetype": "devour", @@ -241,7 +241,7 @@ rows = [{ "value": 1 } }, { -"relic_id": "purge_sapper", +"feat_id": "purge_sapper", "display_name": "净化工兵", "rarity": "rare", "archetype": "devour", @@ -250,7 +250,7 @@ rows = [{ "value": 1 } }, { -"relic_id": "magma_body", +"feat_id": "magma_body", "display_name": "熔火之躯", "rarity": "rarest", "archetype": "devour", @@ -261,7 +261,7 @@ rows = [{ }] ; 双层 BD 实施批(2026-10-04):规则改写型特性表——行 = 特性定义 -; (relic_id/display_name/rarity/archetype/hook/effects)。 +; (feat_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 / diff --git a/game/presentation/battle_narrator.gd b/game/presentation/battle_narrator.gd index 851cb5c3..f3b0b273 100644 --- a/game/presentation/battle_narrator.gd +++ b/game/presentation/battle_narrator.gd @@ -124,8 +124,8 @@ static func bonus_card_text(id: String, tier: String, value: int, treasure_line: # 三选一兑付反馈行(选中件效果 + 丢弃提示;tier 缺省普通档——旧载荷防御口径) static func bonus_result_text(result: Dictionary) -> String: var name := "" - if String(result.get("picked", "")) == "relic" and String(result.get("relic_id", "")) != "": - name = relic_card_text({"relic_id": String(result.get("relic_id", "")), "display_name": String(result.get("relic_name", "")), "archetype": String(result.get("relic_archetype", ""))}).replace("\n", " · ") + if String(result.get("picked", "")) == "relic" and String(result.get("feat_id", "")) != "": + name = feat_card_text({"feat_id": String(result.get("feat_id", "")), "display_name": String(result.get("feat_name", "")), "archetype": String(result.get("feat_archetype", ""))}).replace("\n", " · ") else: name = bonus_card_text(String(result.get("picked", "")), String(result.get("tier", "")), int(result.get("value", 0))).replace("\n", " · ") return "已选择:%s\n其余选项已丢弃(不可反悔)" % name @@ -133,8 +133,8 @@ static func bonus_result_text(result: Dictionary) -> String: # 特性卡片文案(迭代 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", "")) == "": +static func feat_card_text(row: Dictionary) -> String: + if row.is_empty() or String(row.get("feat_id", "")) == "": return "特性\n???" var arch_names := {"magic": "魔法", "pierce": "穿刺", "normal": "普攻", "dig": "开格", "flag": "标旗", "charge": "蓄力", "devour": "吞噬"} @@ -166,8 +166,8 @@ 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 · %s\n%s" % [String(row.get("display_name", row.get("feat_id", ""))), arch, summary] + return "特性 · %s\n%s" % [arch, String(row.get("display_name", row.get("feat_id", "")))] # Boss 胜利战斗总结结算行(settlement-summary v1.1——钥石增量行;组合根拼 GP 行后追加) @@ -176,7 +176,7 @@ static func bonus_keystone_line(payload: Dictionary) -> String: # 奖励节点三选一头部行(迭代 10 追加批——与通关三选一共享同池;node-reward.md v2) -static func node_bonus_header() -> String: +static func node_curio_header() -> String: return "关卡奖励点\n从提升池抽三选一(与通关三选一同池同权重同上限):\n选择一件带走:" diff --git a/game/presentation/curio_interact_controller.gd b/game/presentation/curio_interact_controller.gd index cfe121d8..696eaecc 100644 --- a/game/presentation/curio_interact_controller.gd +++ b/game/presentation/curio_interact_controller.gd @@ -1,7 +1,7 @@ # VERTICAL SLICE - NOT FOR PRODUCTION # 提升三选一与替换子交互控制器(迭代 13 T4/D4——二期家族① 自 main.gd 迁出;规范 1.1 单一职责): # 三选一停点编排(presented→pick→confirm/refresh→resolved→continue)+ 同调位替换子态 -#(presented→chosen)+ TP0 令牌池卡片文案装配("kind|tier|relic_id" 复合键)+ 工坊显示名投影。 +#(presented→chosen)+ TP0 令牌池卡片文案装配("kind|tier|feat_id" 复合键)+ 工坊显示名投影。 # 装配纪律(ADR-0001,仿 treasure_interact_controller 先例):信号连接不在此发生——curio_overlay # 四信号与 adventure 两信号由组合根接至本类 on_* 入口;模态栈/演播后排窗/呈现刷新/终局面/ # 推进失败 fatal 经组合根回调注入;adventure/runner 随局更替——现值回调注入;兑付写调用唯一 @@ -96,7 +96,7 @@ func present_node_curio() -> void: var node_draw: Dictionary = adventure.node_curio_candidates() var node_cands: Array = node_draw.get("candidates", []) as Array _open_modal.call(curio_overlay) - curio_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(node_cands, node_draw.get("aux_detail", []) as Array)) + curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(node_cands, node_draw.get("aux_detail", []) as Array)) adventure.present_node_curio(node_cands, node_draw.get("aux_detail", []) as Array) curio_overlay.set_refresh_info(adventure.attune_refresh_left()) # 迭代 11 第①批 T5b——刷新余量投喂 @@ -105,25 +105,25 @@ func present_node_curio() -> void: func show_pick() -> void: var payload: Dictionary = _pending_curio _open_modal.call(curio_overlay) - curio_overlay.open_pick(Narrator.curio_pick_header(payload), _bonus_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array)) + curio_overlay.open_pick(Narrator.curio_pick_header(payload), _curio_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array)) var adventure = _adventure_get.call() curio_overlay.set_refresh_info(adventure.attune_refresh_left() if adventure != null else 0) # 迭代 11 第①批 T5b——刷新余量投喂 _refresh_presentations.call() -func _bonus_card_texts(candidates: Array, treasure_detail: Array = []) -> Array: - # TP0 令牌池——候选 = {"kind","tier"} 令牌字典;卡片 id = "kind|tier|relic_id" 复合键(同 kind +func _curio_card_texts(candidates: Array, treasure_detail: Array = []) -> Array: + # TP0 令牌池——候选 = {"kind","tier"} 令牌字典;卡片 id = "kind|tier|feat_id" 复合键(同 kind # 大小两档/同档多 relic 可同屏,kind 单键不构成唯一身份),确认回程拆键(on_pick_confirmed) var out: Array = [] var treasure_i := 0 for tok in candidates: var kind := String((tok as Dictionary).get("kind", "")) var tier := String((tok as Dictionary).get("tier", "")) - var rid := String((tok as Dictionary).get("relic_id", "")) + var rid := String((tok as Dictionary).get("feat_id", "")) var cid := "%s|%s|%s" % [kind, tier, rid] var text := "" if kind == "relic": - text = Narrator.relic_card_text(db.relic_row(rid)) + text = Narrator.feat_card_text(db.feat_row(rid)) elif kind == "treasure_item": var iid := String(treasure_detail[treasure_i]) if treasure_i < treasure_detail.size() else "" treasure_i += 1 @@ -167,7 +167,7 @@ func on_refresh() -> void: return if _curio_context == "reward_node": var st: Dictionary = adventure.node_curio_pending_state() - curio_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(st.get("candidates", []) as Array, st.get("aux_detail", []) as Array)) + curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(st.get("candidates", []) as Array, st.get("aux_detail", []) as Array)) curio_overlay.set_refresh_info(adventure.attune_refresh_left()) @@ -175,7 +175,7 @@ func on_pick_confirmed(item_id: String) -> void: var adventure = _adventure_get.call() if adventure == null: return - # TP0 令牌池——卡片 id "kind|tier|relic_id" 拆键(缺 | = 旧载荷防御归普通档;relic 三段) + # TP0 令牌池——卡片 id "kind|tier|feat_id" 拆键(缺 | = 旧载荷防御归普通档;relic 三段) var parts := item_id.split("|") var kind := parts[0] var tier := parts[1] if parts.size() > 1 else "" diff --git a/game/presentation/hud_presenter.gd b/game/presentation/hud_presenter.gd index d6e48f48..e88824d8 100644 --- a/game/presentation/hud_presenter.gd +++ b/game/presentation/hud_presenter.gd @@ -35,11 +35,11 @@ static func refresh(stage: BattleStageView, info: HeroInfoPanelView, ap_bar: ApB else: info.set_level_bonuses([]) # 特性持有投影(迭代 12 呈现补齐 2026-10-04——用户反馈「局内奖励选了什么看不到」: - # 逐流派聚合行+成型档位;常驻可读+空态隐藏。档位求值=RelicsEngine 纯函数直调) + # 逐流派聚合行+成型档位;常驻可读+空态隐藏。档位求值=FeatsEngine 纯函数直调) if runner != null and level_active: - info.set_relic_holdings(relic_holdings_lines(db, runner.relics_held())) + info.set_feat_holdings(feat_holdings_lines(db, runner.feats_held())) else: - info.set_relic_holdings([]) + info.set_feat_holdings([]) if settlement == null: info.set_board_variants([]) # 战斗域角标出战斗即隐(首发批 2026-10-04——空态纪律) _refresh_out_of_battle(stage, info, ap_bar, runner, level_active, db) @@ -94,18 +94,18 @@ static func _refresh_economy(ap_bar: ApBarView, s: Dictionary, db, refresh_remai # 特性持有聚合文案(迭代 12 呈现补齐——战斗 C 区与关卡选择页两处共用;空持有=空数组) -static func relic_holdings_lines(db, held: Array) -> Array[String]: +static func feat_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(): + if db == null or db.feats == null or (db.feats.rows as Array).is_empty() or held.is_empty(): return rlines var arch_names := {"magic": "魔法", "pierce": "穿刺", "normal": "普攻", "dig": "开格", "flag": "标旗", "charge": "蓄力", "devour": "吞噬"} var counts := {} for rid in held: - var rrow: Dictionary = db.relic_row(String(rid)) + var rrow: Dictionary = db.feat_row(String(rid)) var arch := String(rrow.get("archetype", "")) counts[arch] = int(counts.get(arch, 0)) + 1 - var tiers: Dictionary = RelicsEngine.tiers_for(held, db.relics) + var tiers: Dictionary = FeatsEngine.tiers_for(held, db.feats) for arch in counts.keys(): var tier := int(tiers.get(arch, 0)) var tier_text := "档二" if tier >= 2 else ("档一" if tier == 1 else "散件") diff --git a/game/presentation/main.gd b/game/presentation/main.gd index ffc46185..7977e027 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -570,7 +570,7 @@ func _on_battle_end_continue() -> void: if _settlement_stop_pending: _settlement_stop_pending = false _enter_level_screen() - level_screen.set_relic_holdings(" · ".join(HudPresenter.relic_holdings_lines(db, runner.relics_held()))) + level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(db, runner.feats_held()))) level_screen.open_choices(_pending_candidates, runner) _pending_candidates = [] _refresh_presentations() @@ -856,7 +856,7 @@ func _on_choices_presented(candidates: Array) -> void: _refresh_presentations() return _enter_level_screen() - level_screen.set_relic_holdings(" · ".join(HudPresenter.relic_holdings_lines(db, runner.relics_held()))) + level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(db, runner.feats_held()))) level_screen.open_choices(candidates, runner) _refresh_presentations() diff --git a/game/presentation/views/hero_info_panel_view.gd b/game/presentation/views/hero_info_panel_view.gd index 7c05ff86..d1348578 100644 --- a/game/presentation/views/hero_info_panel_view.gd +++ b/game/presentation/views/hero_info_panel_view.gd @@ -21,8 +21,8 @@ var _selected_handle := 0 # 选中态(0=无;组合根回写 var _slot_handles: Array = [] # 槽位→handle 映射(set_treasure_inventory 刷新) var _curio_section: VBoxContainer # 特性(预留——空态隐藏,接线归大奖励/锻造呈现批) var _curio_list: VBoxContainer -var _relic_section: VBoxContainer # 特性持有段(迭代 12 呈现补齐——空态隐藏) -var _relic_list: VBoxContainer +var _feat_section: VBoxContainer # 特性持有段(迭代 12 呈现补齐——空态隐藏) +var _feat_list: VBoxContainer var _board_variant_section: VBoxContainer # 地形变异常驻角标(全局作用域变异首发批——空态隐藏) var _board_variant_list: VBoxContainer @@ -73,14 +73,14 @@ func _init() -> void: # 特性持有段(迭代 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_list = VBoxContainer.new() - _relic_list.add_theme_constant_override("separation", 2) - _relic_section.add_child(_relic_list) + _feat_section = VBoxContainer.new() + _feat_section.add_theme_constant_override("separation", 4) + _feat_section.visible = false + col.add_child(_feat_section) + _feat_section.add_child(UiKit.label("特性", 16, true)) + _feat_list = VBoxContainer.new() + _feat_list.add_theme_constant_override("separation", 2) + _feat_section.add_child(_feat_list) # 地形变异常驻角标(全局作用域变异首发批 2026-10-04——Boss 图整图型战斗全程可见防中局遗忘; # 空态整节隐藏=特性段先例;摆位/文案 provisional 归 HUD 框架批与本地化批) _board_variant_section = VBoxContainer.new() @@ -114,14 +114,14 @@ func set_armor(ac: int) -> void: # 特性持有段(迭代 12 呈现补齐):lines = 聚合文案行(「魔法 ×2 · 档一」式)——空数组 = 整节隐藏 -func set_relic_holdings(lines: Array) -> void: - for c2 in _relic_list.get_children(): - _relic_list.remove_child(c2) +func set_feat_holdings(lines: Array) -> void: + for c2 in _feat_list.get_children(): + _feat_list.remove_child(c2) c2.queue_free() for line in lines: var l2 := UiKit.label(String(line), 16, false, COL_INK) - _relic_list.add_child(l2) - _relic_section.visible = not lines.is_empty() + _feat_list.add_child(l2) + _feat_section.visible = not lines.is_empty() # 地形变异常驻角标(首发批 2026-10-04):lines = 文案行(板级变异 display_name+体感短语)——空数组 = 整节隐藏 diff --git a/game/presentation/views/level_map_screen.gd b/game/presentation/views/level_map_screen.gd index 6ffb3b5c..c94d19be 100644 --- a/game/presentation/views/level_map_screen.gd +++ b/game/presentation/views/level_map_screen.gd @@ -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 _feat_value: Label # 特性持有行(迭代 12 呈现补齐——组合根投喂聚合文案) var _cand_ids: Array[int] = [] # 本停点候选 id 集(图上命中合法集) var _content_size := Vector2(620, 240) # 图内容包络(open_choices 按图拟合——画布 min 尺寸随它) var _hover_id := -1 # 悬停预承诺候选(-1 = 无) @@ -121,7 +121,7 @@ func _init() -> void: box.add_child(_canvas) # 底部状态栏(M5 合并单条:亮米面板一站读齐=头像+生命+属性+本关加成/流派件——demo ④; # 道具槽不摆=道具读数无关卡画面成文出口(读快照私域越权),随 runner 出口批补——台账 352 注记; - # _hp_value/_attr_value/_bonus_value/_relic_value 投喂键全部保留) + # _hp_value/_attr_value/_bonus_value/_feat_value 投喂键全部保留) var bottom := PanelContainer.new() bottom.add_theme_stylebox_override("panel", UiKit.panel_flat(Color8(240, 228, 200), UiKit.CAMP_HEADER_BAND, 0, 2, Vector4(12, 6, 12, 6))) @@ -166,9 +166,9 @@ func _init() -> void: _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) + _feat_value = UiKit.label("", 13, false, UiKit.WARM_SOFT) + _feat_value.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + bonus_box.add_child(_feat_value) var spacer := Control.new() spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL strip.add_child(spacer) @@ -205,8 +205,8 @@ func _draw_hp_bar(bar: Control) -> void: # 放弃入口可见性(组合根在冒险启动时开、默认关——语义对象 = 整局冒险) # 特性持有投影(迭代 12 呈现补齐——组合根投喂聚合文案;空文案=行隐藏) -func set_relic_holdings(text: String) -> void: - _relic_value.text = text +func set_feat_holdings(text: String) -> void: + _feat_value.text = text func set_abandon_available(v: bool) -> void: @@ -339,8 +339,8 @@ func _refresh_status() -> void: if ac_bonus > 0: parts.append("AC +%d" % ac_bonus) _bonus_value.text = " · ".join(parts) - # 特性持有行可见性(与加成行并框——任一非空整框显示;文案归组合根 set_relic_holdings) - _bonus_row.visible = not parts.is_empty() or _relic_value.text != "" + # 特性持有行可见性(与加成行并框——任一非空整框显示;文案归组合根 set_feat_holdings) + _bonus_row.visible = not parts.is_empty() or _feat_value.text != "" # ---- 图上命中(布局决策 2——唯一提交通道;探针 press_candidate 同路径) ---- diff --git a/game/smoke/adventure_monte_carlo.gd b/game/smoke/adventure_monte_carlo.gd index 0936e43a..4608fbce 100644 --- a/game/smoke/adventure_monte_carlo.gd +++ b/game/smoke/adventure_monte_carlo.gd @@ -45,20 +45,20 @@ func _initialize() -> void: # 特性/树分层开关(迭代 12 T7/T10——off=基线;formed=成型画像开局注入七流派各 2 件档一成型集 # 〔测量面专用 params.relics_seed——真实局规则改写型特性只走局内随机渠道〕;ab_a/ab_b=Fork posture 两侧 # rule 键集——A/B 面两侧差 <~3pp 验收载体) - var relic_mode := "off" + var feat_mode := "off" if args.size() >= 7 and args[6] in ["off", "formed", "ab_a", "ab_b"]: - relic_mode = args[6] - var relic_profile := {} - if relic_mode == "formed": - relic_profile = {"mode": "on", "seed_ids": [ + feat_mode = args[6] + var feat_profile := {} + if feat_mode == "formed": + feat_profile = {"mode": "on", "seed_ids": [ "arcane_ember", "mana_weaver", "pierce_shot", "chain_arrows", "miner_stubborn", "miner_hammer", "greedy_lantern", "vein_intuition", "mine_atlas", "nest_hunter", "armory_prep", "iron_wall", "ley_harvest", "thorn_weaver"]} - elif relic_mode == "ab_a": - relic_profile = {"mode": "off", "tree_rules": ["posture_first_strike"]} - elif relic_mode == "ab_b": - relic_profile = {"mode": "off", "tree_rules": ["posture_bulwark"]} + elif feat_mode == "ab_a": + feat_profile = {"mode": "off", "tree_rules": ["posture_first_strike"]} + elif feat_mode == "ab_b": + feat_profile = {"mode": "off", "tree_rules": ["posture_bulwark"]} var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("表加载失败") @@ -103,12 +103,12 @@ func _initialize() -> void: var seed_val := seed0 + _run_cursor _run_cursor += 1 var st = autoplay.run_adventure(db, seed_val, 0, 0, false, logger, policy, route, {}, attune_profile, - {"mode": "pool"} if shop_mode == "on" else {}, relic_profile) + {"mode": "pool"} if shop_mode == "on" else {}, feat_profile) var sum: Dictionary = st["summary"] var rec := { "policy": policy, "seed": seed_val, "atk_pct": 0, "hp_pct": 0, "route": route, "attune": attune_mode, "equipped": attune_profile.get("equipped", []), - "relic_mode": relic_mode, + "feat_mode": feat_mode, "result": st["result"], "cleared": bool(sum.get("cleared", false)), "levels_cleared": int(sum.get("levels_cleared", -1)), diff --git a/game/smoke/level_autoplay.gd b/game/smoke/level_autoplay.gd index 2a1d5e56..9f50abdf 100644 --- a/game/smoke/level_autoplay.gd +++ b/game/smoke/level_autoplay.gd @@ -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) + "feat_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, @@ -227,8 +227,8 @@ static func _on_adventure_node_entered(node: Dictionary, info: Dictionary, adr, r.continue_after_node() # 牌库空合法态直通(池耗即枯竭——防御保留) else: adr.present_node_curio(ncands, ndraw.get("aux_detail", []) as Array) - var npick: Dictionary = _pick_token(ncands, String(st.get("relic_mode", "off"))) - if adr.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))): + var npick: Dictionary = _pick_token(ncands, String(st.get("feat_mode", "off"))) + if adr.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", ""))): if not adr._node_curio_pending.is_empty() and adr._node_curio_pending.has("incoming"): adr.resolve_curio_replace(-1) # 满位替换:放弃新件 else: @@ -265,8 +265,8 @@ static func _on_adventure_bonus(payload: Dictionary, st: Dictionary, adr) -> voi if candidates.is_empty(): st.result = "advance_failed" return - var pick: Dictionary = _pick_token(candidates, String(st.get("relic_mode", "off"))) - if not adr.pick_curio(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("relic_id", ""))): + var pick: Dictionary = _pick_token(candidates, String(st.get("feat_mode", "off"))) + if not adr.pick_curio(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("feat_id", ""))): st.result = "advance_failed" @@ -274,15 +274,15 @@ static func _on_adventure_bonus(payload: Dictionary, st: Dictionary, adr) -> voi # 候选并列同档取最左=稳定序保证。空候选/未知档位 = 回退最左防御口径。 # 成型画像(迭代 12 T7——relic 模式 on:候选含 relic 令牌时优先在 relic 集内按 316-f 择一 # 〔贪心捕捞→特性入局→成型画像全流派覆盖——R3 乙单锚承载〕;off=relic 与属性加成型特性同规则竞争) -static func _pick_token(candidates: Array, relic_mode: String = "off") -> Dictionary: +static func _pick_token(candidates: Array, feat_mode: String = "off") -> Dictionary: var pool: Array = candidates - if relic_mode == "on": - var relics_only: Array = [] + if feat_mode == "on": + var feats_only: Array = [] for tok in candidates: if String((tok as Dictionary).get("kind", "")) == "relic": - relics_only.append(tok) - if not relics_only.is_empty(): - pool = relics_only + feats_only.append(tok) + if not feats_only.is_empty(): + pool = feats_only var best: Dictionary = {} var best_rank := -1 for tok in pool: diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index f2a9dc64..129dfb27 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -112,7 +112,7 @@ func _run_core_smoke(db, rng) -> void: _smoke_adventure_table() _smoke_adventure_api() _smoke_adventure() - _smoke_relics() + _smoke_feats() _smoke_save() _smoke_meta() _smoke_tree() @@ -360,9 +360,9 @@ func _smoke_adventure() -> void: cand_ok = false var seen := {} for c in cands: - # 身份键 = kind|tier|relic_id(迭代14① T4 断言基线复核——TP0 v2.2 relic 按 relic_id 分列 + # 身份键 = kind|tier|feat_id(迭代14① T4 断言基线复核——TP0 v2.2 relic 按 feat_id 分列 # 不参与同 kind 去重〔_token_identity relic: 前缀〕,同 tier 三枚不同流派件=三个不同选项合法态) - var ckey := "%s|%s|%s" % [String((c as Dictionary).get("kind", "")), String((c as Dictionary).get("tier", "")), String((c as Dictionary).get("relic_id", ""))] + var ckey := "%s|%s|%s" % [String((c as Dictionary).get("kind", "")), String((c as Dictionary).get("tier", "")), String((c as Dictionary).get("feat_id", ""))] if seen.has(ckey): cand_ok = false seen[ckey] = true @@ -509,7 +509,7 @@ func _smoke_adventure() -> void: check(not a8.continue_to_next_level(), "未兑付推进拒绝(选择停点零自动推进契约)") var l2_cands: Array = a8._curio_pending["candidates"] var l2_pick: Dictionary = l2_cands[0] - check(a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", ""))), "三选一点选兑付受理(候选取首件)") + check(a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))), "三选一点选兑付受理(候选取首件)") var picked2: String = String(a8._curio_result.get("picked", "")) var expect_acc: int = acc_shop + (int(a8._curio_result.get("value", 0)) if picked2 == "max_hp" else 0) check(int(a8.bonus.get("max_hp", -1)) == expect_acc, @@ -602,47 +602,47 @@ func _smoke_adventure() -> void: "放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)") -# ---- 双层 BD 规则改写型特性基建冒烟(迭代 12 T1——relic_table/档位激活引擎/规则改写型特性带池接线)---- +# ---- 双层 BD 规则改写型特性基建冒烟(迭代 12 T1——feat_table/档位激活引擎/规则改写型特性带池接线)---- -func _smoke_relics() -> void: - print("--- 规则改写型特性基建(relic_table · 档位激活引擎 · 规则改写型特性带接线)---") +func _smoke_feats() -> void: + print("--- 规则改写型特性基建(feat_table · 档位激活引擎 · 规则改写型特性带接线)---") # ① 现行表牌库构成:七流派 27 件全量入池——98 数值令牌+27 relic 令牌(批次一铺量完成) - check(_db.relics != null and (_db.relics.rows as Array).size() == 27, "relic_table 已加载(七流派 27 件全量——批次一 T2-T6 铺量完成)") + check(_db.feats != null and (_db.feats.rows as Array).size() == 27, "feat_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)") - var relic_tokens := 0 + var feat_tokens := 0 var rarest_tokens := 0 for t in a_r0._curio_deck: if String((t as Dictionary).get("kind", "")) == "relic": - relic_tokens += 1 + feat_tokens += 1 if String((t as Dictionary).get("tier", "")) == "rarest": rarest_tokens += 1 - check(relic_tokens == 27 and rarest_tokens == 6 and a_r0._curio_deck.size() == 125, + check(feat_tokens == 27 and rarest_tokens == 6 and a_r0._curio_deck.size() == 125, "规则改写型特性带入池:27 件(rare 档 21 + rarest 锚点 6)——牌库 98+27=125") - check(_db.relics.validate().is_empty(), "relic_table.validate 零违例(有行流派扫雷端铁律——magic first_dig_spell 在案)") + check(_db.feats.validate().is_empty(), "feat_table.validate 零违例(有行流派扫雷端铁律——magic first_dig_spell 在案)") # ② 档位激活引擎(纯函数直测——构造表 4 件 magic + 1 件 flag + 1 件 rarest 锚点) - var rt := RelicTable.new() + var rt := FeatTable.new() rt.rows = [ - {"relic_id": "m1", "display_name": "魔一", "rarity": "common", "archetype": "magic", "hook": "hero_attack_rider", "effects": {"value": 1}}, - {"relic_id": "m2", "display_name": "魔二", "rarity": "common", "archetype": "magic", "hook": "first_dig_spell", "effects": {"value": 1}}, - {"relic_id": "m3", "display_name": "魔三", "rarity": "rare", "archetype": "magic", "hook": "clamp_modifier", "effects": {"value": 1}}, - {"relic_id": "m4", "display_name": "魔四", "rarity": "rare", "archetype": "magic", "hook": "hero_attack_rider", "effects": {"tier_value": 2}}, - {"relic_id": "f1", "display_name": "旗一", "rarity": "common", "archetype": "flag", "hook": "flag_resource", "effects": {"value": 1}}, + {"feat_id": "m1", "display_name": "魔一", "rarity": "common", "archetype": "magic", "hook": "hero_attack_rider", "effects": {"value": 1}}, + {"feat_id": "m2", "display_name": "魔二", "rarity": "common", "archetype": "magic", "hook": "first_dig_spell", "effects": {"value": 1}}, + {"feat_id": "m3", "display_name": "魔三", "rarity": "rare", "archetype": "magic", "hook": "clamp_modifier", "effects": {"value": 1}}, + {"feat_id": "m4", "display_name": "魔四", "rarity": "rare", "archetype": "magic", "hook": "hero_attack_rider", "effects": {"tier_value": 2}}, + {"feat_id": "f1", "display_name": "旗一", "rarity": "common", "archetype": "flag", "hook": "flag_resource", "effects": {"value": 1}}, ] check(rt.validate().is_empty(), "构造 relic 表 validate 零违例(magic 4 件含 first_dig_spell 扫雷端——红线①铁律过)") - var tiers := RelicsEngine.tiers_for(["m1", "m2", "m3", "m4", "f1"], rt) + var tiers := FeatsEngine.tiers_for(["m1", "m2", "m3", "m4", "f1"], rt) check(int(tiers.get("magic", -1)) == 2 and int(tiers.get("flag", -1)) == 0, "档位激活:magic 4 件→档二 · flag 1 件→散件档 0(集 2 档一/4 档二——以撒套装/小骨铭文先例)") - check(RelicsEngine.is_formed(tiers, "magic") and not RelicsEngine.is_formed(tiers, "flag"), + check(FeatsEngine.is_formed(tiers, "magic") and not FeatsEngine.is_formed(tiers, "flag"), "成型判读:magic 成型(tier≥1)· flag 未成型(散件单体保底——档位只质变不启动)") - var t2 := RelicsEngine.tiers_for(["m1", "m2"], rt) + var t2 := FeatsEngine.tiers_for(["m1", "m2"], rt) check(int(t2.get("magic", -1)) == 1, "档位激活:2 件→档一(同集合同档位确定性——MC 逐位可复放承载)") - var hooks := RelicsEngine.hooks_for(["m1", "f1"], rt) + var hooks := FeatsEngine.hooks_for(["m1", "f1"], rt) check(hooks.size() == 2 and String(hooks[0].get("hook", "")) == "hero_attack_rider", "挂点投影:表序稳定(同集合→同投影)") # ③ 规则改写型特性带接线(临时表注入——relic 令牌入牌库/兑付入持有集/踢池/断点往返;测试尾还原现行表) - var live_rows: RelicTable = _db.relics - _db.relics = rt + var live_rows: FeatTable = _db.feats + _db.feats = rt var a_r = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(72)) check(a_r.start_adventure(72, {"first_script": true}), "规则改写型特性带场景:冒险启动(seed 72——注入 5 行临时表)") var deck_relic := 0 @@ -651,21 +651,27 @@ func _smoke_relics() -> void: deck_relic += 1 check(deck_relic == 5 and a_r._curio_deck.size() == 103, "规则改写型特性带入池:5 行 relic 每件 1 张(common/rare→稀有档 rarest→罕见档)——牌库 98+5=103") - var relic_seen: Dictionary = {} + # T4 渠道白名单断言(迭代 15 卡②——渠道轴定位:通关渠道只出专长/节点渠道零专长) + var feat_seen: Dictionary = {} + var node_relic_seen := false for i in 60: - for t in a_r._draw_curio_candidates(): + for t in a_r._draw_curio_candidates("level_clear"): if String((t as Dictionary).get("kind", "")) == "relic": - relic_seen[String(t.get("relic_id", ""))] = true - check(relic_seen.size() >= 2, "抽签出 relic 候选(60 次重抽 ≥2 种不同 relic——身份键 relic_id 同档可多件)") - a_r._curio_pending = {"candidates": [{"kind": "relic", "tier": "rare", "relic_id": "m3"}, {"kind": "gp", "tier": "common"}], "payload": {}} + feat_seen[String(t.get("feat_id", ""))] = true + for t in a_r._draw_curio_candidates("reward_node"): + if String((t as Dictionary).get("kind", "")) == "relic": + node_relic_seen = true + check(feat_seen.size() >= 2, "通关渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)") + check(not node_relic_seen, "节点渠道零专长(60 次重抽零 relic——专长在非 Boss 渠道只以门票形态出现,T4 渠道白名单)") + a_r._curio_pending = {"candidates": [{"kind": "relic", "tier": "rare", "feat_id": "m3"}, {"kind": "gp", "tier": "common"}], "payload": {}} var deck_m3 := 0 for t in a_r._curio_deck: - if String((t as Dictionary).get("relic_id", "")) == "m3": + if String((t as Dictionary).get("feat_id", "")) == "m3": deck_m3 += 1 - check(a_r.pick_curio("relic", "rare", "m3") and (a_r.relics_held() as Array).has("m3") and a_r.pool_stock("relic") == deck_relic - 1, + check(a_r.pick_curio("relic", "rare", "m3") and (a_r.feats_held() as Array).has("m3") and a_r.pool_stock("relic") == deck_relic - 1, "relic 兑付:入持有集 + 牌库摘牌(选中踢池——池库存 %d→%d)" % [deck_relic, deck_relic - 1]) - # 断点往返(持有集+余库 relic_id 随行) - var sm_r = load("res://core/save_manager.gd").new("user://_smoke_relics_bp.json") + # 断点往返(持有集+余库 feat_id 随行) + var sm_r = load("res://core/save_manager.gd").new("user://_smoke_feats_bp.json") sm_r.load_from_disk() a_r.set_save_manager(sm_r) a_r._on_level_finished({"result": "victory", "hp": int(a_r.ledger.get("hero_hp", 26)), "node_index": 1, "node_id": 1, "node_type": "shop"}) @@ -673,34 +679,34 @@ func _smoke_relics() -> void: check((bp_r.get("relics", []) as Array).has("m3"), "断点附随件:relics 持有集入快照(迭代 12 T1 扩列)") var bp_deck_m3 := 0 for t in (bp_r.get("bonus_deck", []) as Array): - if String((t as Dictionary).get("relic_id", "")) == "m3": + if String((t as Dictionary).get("feat_id", "")) == "m3": bp_deck_m3 += 1 check(bp_deck_m3 == 0, "断点余库:已兑付 relic 令牌逐枚还原(m3 零残留——续玩不重复获得)") sm_r.clear_breakpoint() - _db.relics = live_rows # 还原现行表(共享 _db 防污染——后续冒烟段零感知) + _db.feats = live_rows # 还原现行表(共享 _db 防污染——后续冒烟段零感知) # ④ T2 魔法流求值面(现行表 magic 四件——flags 组装直测;rider 落地行为=伤害控制器消费面, - # 既有战斗冒烟全绿=relic_flags 空 dict 零扰动实证;全链分岔读数归 T7 MC) + # 既有战斗冒烟全绿=feat_flags 空 dict 零扰动实证;全链分岔读数归 T7 MC) var orch = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(73)) - orch._relic_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance"]) - var rf: Dictionary = orch._damage.relic_flags + orch._feat_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance"]) + var rf: Dictionary = orch._damage.feat_flags check(int(rf.get("rider_flat", -1)) == 2 and bool(rf.get("rider_crit", false)), "魔法流求值:rider_flat=余烬1+档一(4件)+1=2 · 档二 rider 可暴击(共鸣 rider 提升以奥术矢+档位承载——锚点双效果 provisional 披露)") check(int(rf.get("clamp_magic_magic", -1)) == 1 and int(rf.get("clamp_magic_heavy", -1)) == 1 and bool(rf.get("first_dig_spell", false)) and int(rf.get("spell_value", -1)) == 2, "魔法流求值:魔网织者 magic-vs-magic 罚修正 +1 · 湛蓝誓约 magic-vs-heavy +1 · 魔能共鸣奥术矢 value 2") # ⑤ T3 标旗流求值面(现行表 magic 4+flag 4——混合持有:flag 档一 4 件成型) - orch._relic_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance", + orch._feat_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance", "mine_atlas", "nest_hunter", "perfect_defense", "war_banner"]) - var rf8: Dictionary = orch._damage.relic_flags + var rf8: Dictionary = orch._damage.feat_flags check(int(rf8.get("flag_resource_extra", -1)) == 1 and int(rf8.get("nest_aura_value", -1)) == 1 and int(rf8.get("flag_all_hit_mult", -1)) == 2 and int(rf8.get("flag_strike_value", -1)) == 2, "标旗流求值:雷区图谱额外资源 1 · 猎巢人光环 +1/巢 · 完美布防 ×2 · 战旗军规旗伤 2(复用 board_typed 零新配方)") # ⑥ T4 穿刺/普攻流求值面(全 16 件持有——四流派档位成型) - orch._relic_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance", + orch._feat_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance", "mine_atlas", "nest_hunter", "perfect_defense", "war_banner", "pierce_shot", "chain_arrows", "weak_instinct", "hunter_rapier", "miner_stubborn", "miner_hammer", "focus_mind", "armor_crack"]) - var rf16: Dictionary = orch._damage.relic_flags + var rf16: Dictionary = orch._damage.feat_flags check(bool(rf16.get("kill_overflow", false)) and int(rf16.get("chain_value", -1)) == 2 and int(rf16.get("discount_shift", -1)) == 1 and int(rf16.get("clamp_pierce_light", -1)) == 1, "穿刺流求值:贯穿射击弹射旗 · 连珠矢段值 2 上限 2 · 猎弱本能折扣改善 1 档 · 猎手刺剑 pierce-vs-light +1") @@ -717,8 +723,8 @@ func _smoke_relics() -> void: "miner_stubborn", "miner_hammer", "focus_mind", "armor_crack", "greedy_lantern", "vein_intuition", "gambler_pact", "deep_drill", "armory_prep", "iron_wall", "patient_burst"] - orch._relic_flags_for(ids23) - var rf23: Dictionary = orch._damage.relic_flags + orch._feat_flags_for(ids23) + var rf23: Dictionary = orch._damage.feat_flags check(int(rf23.get("dig_yield_pos_mult", -1)) == 85 and int(rf23.get("dig_yield_neg_mult", -1)) == 125 and int(rf23.get("combo_cap", -1)) == 5, "开格流求值:正向乘区 25+10+50=85% · 负向加重 25+100=125% · 直觉连击上限 5(同 hook 累加——三件同持)") @@ -726,19 +732,19 @@ func _smoke_relics() -> void: and int(rf23.get("ap_carry_value", -1)) == 1 and int(rf23.get("shield_per_layer", -1)) == 1 and int(rf23.get("spike_mult", -1)) == 2, "深挖钻头=开掘 +1 AP/每 4 格深层产出(R4 甲非信息税对价)· 蓄力三件=层值 1/减伤 1/层/满层 ×2") - check(orch.action_economy.relic_ap_carry and orch.action_economy.relic_dig_extra == 1, + check(orch.action_economy.feat_ap_carry and orch.action_economy.feat_dig_extra == 1, "蓄力/钻头 action_economy 注入(ap_carry 改写停手转存 + 付费开掘对价注入——关着 false/0 零扰动)") # ⑦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, "特性段空态隐藏(视图空态纪律同特性段)") - panel.set_relic_holdings(["魔法 ×2 · 档一", "开格 ×1 · 散件"]) - check(panel._relic_section.visible and panel._relic_list.get_child_count() == 2, + panel.set_feat_holdings([]) + check(not panel._feat_section.visible, "特性段空态隐藏(视图空态纪律同特性段)") + panel.set_feat_holdings(["魔法 ×2 · 档一", "开格 ×1 · 散件"]) + check(panel._feat_section.visible and panel._feat_list.get_child_count() == 2, "特性段持有呈现(聚合行+成型档位常驻可读——「局内奖励选了什么看不到」缺口承载)") # ⑦b 深挖钻头付费开掘回归(2026-10-04 试玩缺陷修复——对价挂付费侧):AP=1 降级照常开格 # (用户实测「AP=1 无法开格只能标旗」的回归闸);AP=2 付全价积付费计数(深层产出配额取数面)。 var ae_dd = load("res://core/action_economy.gd").new(_db) - ae_dd.relic_dig_extra = 1 + ae_dd.feat_dig_extra = 1 ae_dd._phase = 1 if ae_dd.Phase.ACTIVE == 1 else ae_dd.Phase.ACTIVE # white-box 相位(冒烟专用) ae_dd._ap = 1 check(ae_dd.request_dig(0, false) and ae_dd.deep_paid_count == 0, @@ -750,14 +756,14 @@ func _smoke_relics() -> void: # ⑧ T6 吞噬流求值面(27 件全量——七流派档位成型) var ids27: Array = ids23.duplicate() ids27.append_array(["ley_harvest", "thorn_weaver", "purge_sapper", "magma_body"]) - orch._relic_flags_for(ids27) - var rf27: Dictionary = orch._damage.relic_flags + orch._feat_flags_for(ids27) + var rf27: Dictionary = orch._damage.feat_flags check(int(rf27.get("terrain_refund", -1)) == 1 and int(rf27.get("terrain_dig_double", -1)) == 1 and int(rf27.get("flag_neutralize", -1)) == 1 and int(rf27.get("variant_counter_value", -1)) == 1, "吞噬流求值:地脉采撷补偿 1 · 荆棘编织双产出 · 净化工兵中和旗(🔴 仅伤害型——过滤在消费面)· 熔火反噬 1/层") var orch0 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(74)) - orch0._relic_flags_for([]) - check((orch0._damage.relic_flags as Dictionary).is_empty(), + orch0._feat_flags_for([]) + check((orch0._damage.feat_flags as Dictionary).is_empty(), "零持有求值=零旗(relic 关着零扰动——伤害管线既有行为逐位不变)") @@ -774,7 +780,7 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool: if not a._curio_pending.is_empty(): var bcands: Array = a._curio_pending["candidates"] var bpick: Dictionary = autoplay._pick_token(bcands) - a.pick_curio(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("relic_id", ""))) + a.pick_curio(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("feat_id", ""))) a.continue_to_next_level() continue var r = a.runner() @@ -793,7 +799,7 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool: if not ncands.is_empty(): a.present_node_curio(ncands, ndraw.get("aux_detail", []) as Array) var npick: Dictionary = autoplay._pick_token(ncands) - if a.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))): + if a.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", ""))): if not a._node_curio_pending.is_empty() and a._node_curio_pending.has("incoming"): a.resolve_curio_replace(-1) r.continue_after_node() diff --git a/game/tests/unit/core/breakpoint_resume_test.gd b/game/tests/unit/core/breakpoint_resume_test.gd index d3f966c5..c0d351a5 100644 --- a/game/tests/unit/core/breakpoint_resume_test.gd +++ b/game/tests/unit/core/breakpoint_resume_test.gd @@ -113,7 +113,7 @@ func _init() -> void: Kit.check_quiet(int(adr_f._curio_taken["attack"]) == 1, "P5 次数计数恢复一致(牌库剔除态续接)") Kit.check_quiet(not adr_f.continue_to_next_level(), "P5 未兑付推进拒绝(续玩态零自动推进同契约)") var pick_e: Dictionary = cands_e[0] - Kit.check_quiet(adr_f.pick_curio(String(pick_e.get("kind", "")), String(pick_e.get("tier", ""))), "P5 点选兑付受理") + Kit.check_quiet(adr_f.pick_curio(String(pick_e.get("kind", "")), String(pick_e.get("tier", "")), String(pick_e.get("feat_id", ""))), "P5 点选兑付受理") var picked_e: String = String(adr_f._curio_result.get("picked", "")) if adr_f._curio_taken.has(picked_e): Kit.check_quiet(int(adr_f._curio_taken[picked_e]) == (2 if picked_e == "attack" else 1), From 95b66f0d607905ad54a7b11bcd6a9ce81debd4af Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 5 Oct 2026 20:03:20 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E5=A5=87=E7=89=A9=E6=89=B9T5+T6a=EF=BC=9A?= =?UTF-8?q?=E4=B8=93=E9=95=BF=E9=97=A8=E7=A5=A8=E5=85=A5=E8=A1=A8=EF=BC=88?= =?UTF-8?q?schema=E5=9B=9B=E5=AD=97=E6=AE=B5provisional=2020%/2/1/5+?= =?UTF-8?q?=E5=9F=9F=E6=A0=A1=E9=AA=8C+runner=E4=BF=9D=E5=BA=95/=E9=99=90?= =?UTF-8?q?=E8=B4=AD/=E5=87=BA=E7=8E=B0=E4=B8=89=E8=AE=A1=E6=95=B0+?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E4=B8=8E=E5=95=86=E5=BA=97=E5=8F=8C=E6=B3=A8?= =?UTF-8?q?=E5=85=A5(=E6=9B=BF=E6=8D=A2=E6=9C=AB=E4=BD=8D=E5=8D=A0?= =?UTF-8?q?=E4=B8=80=E5=B8=AD,=E5=90=8Cscope=E5=8F=96=E6=95=B0=E9=9B=B6?= =?UTF-8?q?=E6=BC=82=E7=A7=BB)+=E6=8B=BF=E5=88=B0=E5=8D=B3=E5=BC=80?= =?UTF-8?q?=E4=B8=89=E5=85=A5=E5=8F=A3=E5=88=86=E6=94=AF(=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E6=9C=AC=E5=9C=B0=E9=87=8D=E7=BB=98/=E5=BA=97?= =?UTF-8?q?=E5=86=85=E4=B8=B4=E6=97=B6=E5=81=9C=E7=82=B9payload.context)+?= =?UTF-8?q?=E6=96=AD=E7=82=B9=E6=89=A9=E5=88=97=E4=B8=A4=E9=94=AE=E5=90=91?= =?UTF-8?q?=E5=90=8E=E5=85=BC=E5=AE=B9+shop.tres=E9=97=A8=E7=A5=A8?= =?UTF-8?q?=E8=A1=8Cbase5+SHELF=5FKINDS=E6=89=A9+=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E8=B1=81=E5=85=8D+A5/A6=E6=96=AD=E8=A8=807=E6=9D=A1=EF=BC=89?= =?UTF-8?q?=EF=BC=9BT6a=E5=95=86=E5=BA=97=E8=A1=A5=E5=9B=9E=E5=A4=8Dheal?= =?UTF-8?q?=E8=A1=8C=EF=BC=88=E9=BB=98=E8=AE=A4=E5=8E=9F=E5=88=99=EF=BC=9A?= =?UTF-8?q?=E5=88=86=E7=B1=BB=E5=86=85=E6=88=90=E5=91=98=E9=83=BD=E5=85=A5?= =?UTF-8?q?=E8=AF=A5=E6=B8=A0=E9=81=93=E6=B1=A0=EF=BC=89+registry=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E8=BE=B9=E7=95=8C=E5=86=99=E6=AD=BB=EF=BC=88=E5=95=86?= =?UTF-8?q?=E5=BA=97=E8=87=AA=E8=BA=AB=E6=9C=8D=E5=8A=A1vs=E5=A5=87?= =?UTF-8?q?=E7=89=A9=E6=8E=88=E4=BA=88=E9=87=8D=E6=8A=BD=E6=AC=A1=E6=95=B0?= =?UTF-8?q?=EF=BC=89=EF=BC=9B=E7=BB=88=E5=B1=80=E6=B1=A0=E8=AF=BB=E6=95=B0?= =?UTF-8?q?=E6=96=AD=E8=A8=804=E2=86=926=E9=94=AE=E9=80=82=E9=85=8D?= =?UTF-8?q?=EF=BC=88=E5=86=92=E7=83=9F782/0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/registry/entities.yaml | 1 + game/core/adventure_runner.gd | 56 ++++++++++++++++++- game/core/config_db_cross_validator.gd | 2 + game/core/level_runner.gd | 8 ++- game/data/schemas/adventure_table.gd | 12 ++++ game/data/schemas/shop_table.gd | 2 +- game/data/tables/adventure.tres | 4 ++ game/data/tables/shop.tres | 10 +++- .../presentation/curio_interact_controller.gd | 20 ++++++- game/smoke/smoke_all.gd | 39 ++++++++++++- 10 files changed, 148 insertions(+), 6 deletions(-) diff --git a/design/registry/entities.yaml b/design/registry/entities.yaml index f3a6db44..ce4bb7b2 100644 --- a/design/registry/entities.yaml +++ b/design/registry/entities.yaml @@ -601,6 +601,7 @@ entities: acquisition: 三条渠道的**目标分工(交叉开关,v2.2 收口)**——专长:主渠道=**关底 Boss 击败=通过这一关**(**唯一直接给的渠道**;不新增节点类型、挂 Boss 节点兑付面;末关的 Boss 击败后不给——无后续关卡可消费)。**兑付频率**:奇物不是打一场战斗给一份,关内普通战斗节点不给任何奇物,一关之内只兑付一次(奖励节点与商店同理,各节点各自兑付),奖励节点与商店**只给「专长获取」门票**——**拿到即开、不可囤积**,不进持有集(选中/买下同一时刻开专长三选一;可囤积会让玩家无限推迟关键决策,且把持有物语义从后门放回)。门票归专长(给的是一件以前不会的事,只是把选择推迟)。罕见**只在概率不在价格**,一局限购一次,**计步保底**:连续 5 次三选一展示未出现→第 6 次必出(计数=展示次数、刷新重展计入、奖励节点与商店共享、触发清零、限购用满后不再触发);属性值提升:奖励节点 + 商店(口径「训练」);资源:奖励节点 + 商店(**金币除外、回复在内**);后两类均不出现在 Boss 渠道。形状=「一块独占 + 两块并行」,专长靠打赢、其余靠抽取与购买。**默认原则**:除已登记的特殊规则外,分类内的东西都要在它该出现的渠道池子里——任何不出现都须登记理由**现行实现三渠道共用同一候选池、三类混编**,分工尚未实施,登记于 06-待办清单 BD 分层条目 boundary: 判据走生命周期——**一次冒险内获得、冒险收束即清零的是奇物;跨冒险跟着你走的是魔法道具**。奇物永不进同调位,魔法道具永不进当局随机渠道;与元进度天赋树两层互不代位 data_sources: feats.tres(专长定义行)/ adventure.tres(属性值提升与资源的种类配额与值)/ shop.tres(货架取舍) + refresh_boundary: **刷新次数边界写死(T6 奇物批迭代15②——防后续混淆结算)**:「商店独立刷新机制」(shop_refresh_free/shop_refresh_cost_base/shop_refresh_cost_curve 三旋钮——进店后的免费次数+GP 付费刷新)系**商店自身服务**,作用对象=货架重抽;奇物 refresh 件(资源类成员,作为奇物授予的重抽次数)作用对象=**三选一界面**(兑付即 _attune_refresh_x 增量,X 余量=三选一重抽次数),不进加算区(§2.2 加算区四轴不含它)。两者不是一回事,禁止混算。 open_questions: ①类内异质性已登记——金币与回复是即时兑付、不进持有集不持续生效,与行动点上限/刷新次数生命周期不同,靠这条描述把"资源"从泛用词收回到本类定义内;②专长门票尚未入表(出现概率/配额/限购次数/保底阈值四参数均无事实源);③确定性渠道一局两次专长 vs 档位阈值(同标签 ≥2 档一 / ≥4 档二)——**档二在三关结构下几乎不可达**,用户定延后复裁(触发=第四关内容批后实测);④charge 标签仅 3 件、永远达不到档二(数据层缺陷,独立登记,勿被上条盖掉);⑤断点存档键 5 个(bonus_deck / bonus_taken / bonus_pending / bonus_result / bonus_aux_carry)是否随存档版本迁移换名待裁 naming_note: 2026-10-05 命名统一 + 两次重构——此前按内部分类被叫成三个名字(规则类称「流派件」、属性类称「数值件」、界面称「本关强化」),v1.0 统一为「特性」,v2.0 改名为「奖励」,v2.4 定名为「奇物」(因筐里也装金币这类一次性兑付物,"自身提升 vs 持有物"已切不开,判据改由生命周期承担)。v2.5 第三类定名为「资源」(用户拍板)。**v2.7 三个子节点全部正式定名、去「类」字后缀**:专长(Feat)/ 属性值提升(ASI)/ 资源——此前是描述式叫法不是专名。**代码侧三套词族已于奇物批对齐(2026-10-05 迭代15②批 T1-T3)**:`feature_*` → `curio_*`(CURIO_KINDS/curio_quota/curio_value)、`relic_*` → `feat_*`(feat_table.gd/feats.tres/feats_engine.gd/feat_flags/feat_id——kind 值 "relic" 与身份前缀 "relic:" 系令牌池数据契约键沿用不动)、运行层 `bonus_*` → `curio_*`(curio_interact_controller/curio_pick_overlay/CurioPickOverlay/pick_curio);断点存档键(bonus_deck/bonus_taken/bonus_pending/bonus_result/bonus_aux_carry/relics)按甲案沿用历史名不迁移(存档兼容) referenced_by: [冒险结构, 关卡节点地图, 战斗结算, 行动经济, 地形变异, HUD框架, 魔法道具, 元进度, 数据配置] diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index 0861f5b6..24fcd0ff 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -89,6 +89,9 @@ var _bonus_treasure_carry: Array[String] = [] # 道具件跨关携带(兑付 var _keystone_drops: Array = [] # Boss 逐杀掉落明细(additive 摘要键 keystone_drops——结算明细/复盘归因数据源) var _last_draw_treasure: Array[String] = [] # 最近一次抽签掷定的道具件(掷定前置——卡片直显具体道具;追加批二) var _node_curio_pending := {} # 奖励节点三选一停点态(迭代 10 追加批——共享池;断点不载:续玩重建到选路停点重走,恢复的计数器保证同候选重抽) +var _feat_ticket_pity := 0 # 门票保底计数(T5——展示次数:节点开面/货架抽签/刷新重展各计一次;触发清零) +var _feat_ticket_sold := 0 # 门票限购计数(T5——选中/买下即 +1;拿到即开不进持有集) +var _feat_ticket_appeared := 0 # 门票出现计数(T5——quota 上限面) var _shop_mode := "legacy" # 商店模式(迭代 11 第②批 T3——"legacy"=固定三件加定价〔off 对照承载,缺省零扰动〕/"pool"=共享池抽 3 买 1 重抽;真实入口 main.gd 显式投 pool) @@ -213,6 +216,8 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: _seed_attune_charges() # 迭代 11 第①批 T6——冒险开始充能账本按装备主动件重置(每冒险充能语义) # 三选一停点续玩分支(迭代 10——选择态/已兑付态不重跑关卡;奖励选择态不重抽、已兑付不重付) var bp_pending: Dictionary = bp.get("bonus_pending", {}) as Dictionary + _feat_ticket_pity = int(bp.get("feat_ticket_pity", 0)) # T5 扩列——旧档缺键缺省 0(存档兼容) + _feat_ticket_sold = int(bp.get("feat_ticket_sold", 0)) var bp_result: Dictionary = bp.get("bonus_result", {}) as Dictionary if not bp_pending.is_empty(): _pending_next_level = true @@ -365,6 +370,8 @@ func pick_curio(kind: String, tier: String = "", feat_id: String = "") -> bool: if _find_token(candidates, kind, tier, feat_id).is_empty(): push_error("[冒险结构] pick_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, feat_id]) return false + if kind == "feat_ticket": + return _redeem_feat_ticket() # T5 拿到即开——停点候选替换为专长三选一(零摘牌/不进持有集) var value: int = int(db.adventure.curio_value(kind, tier)) var hp_before := int(ledger.get("hero_hp", 0)) if kind == "treasure_item": @@ -461,6 +468,8 @@ func pool_stock(kind: String) -> int: # 写加算区+账本+摘牌(与奖励三选一真单点——买一件=奖励面少一件的库存语义由同库成立)。 # 未知 kind 拒绝(货架词表封闭的前置防御)。 func _consume_pool_purchase(kind: String, tier: String) -> bool: + if kind == "feat_ticket": + return _redeem_feat_ticket() # T5——门票买下即开(零摘牌不进持有集;临时停点开专长三选一面) if not db.adventure.CURIO_KINDS.has(kind): push_error("[冒险结构] 商店购买消费拒绝——件不在池词表: %s" % kind) return false @@ -549,6 +558,8 @@ func present_node_curio(candidates: Array, treasure_detail: Array = []) -> void: # 点选兑付(节点域——无部分回血无钥石;令牌消费与牌库摘牌与通关三选一同库;relic 同权入持有集) func apply_node_curio(kind: String, tier: String = "", feat_id: String = "") -> bool: + if kind == "feat_ticket": + return _redeem_feat_ticket() # T5 拿到即开——节点停点候选替换为专长三选一(呈现层本地重绘) if _node_curio_pending.is_empty(): push_error("[冒险结构] apply_node_curio 拒绝——无待选停点") return false @@ -753,9 +764,50 @@ func _draw_curio_candidates(channel: String = "reward_node") -> Array: out.append(cand) if out.size() >= int(db.adventure.curio_candidate_count): break + # T5 门票注入(非 Boss 渠道唯一专长入口——替换末位候选占一席,候选数不变;判定含 pity 清零记账) + if channel == "reward_node" and not out.is_empty() and _feat_ticket_consider(rg): + out[out.size() - 1] = {"kind": "feat_ticket", "tier": "rare"} return out +# —— 专长获取门票(T5 迭代 15 卡②——非 Boss 渠道的专长入口;四口径契约④:拿到即开不可囤积/ +# 罕见只在概率不在价格/一局限购一次/计步保底连续 5 展示未出第 6 必出——计数=展示次数、刷新 +# 重展计入、节点与商店共享、触发清零、限购满不再触发。调用即一次展示判定与记账)—— +func _feat_ticket_consider(rg: RandomNumberGenerator) -> bool: + if _feat_ticket_sold >= int(db.adventure.feat_ticket_limit): + return false # 限购满——不再触发(保底冻结) + if _feat_ticket_appeared >= int(db.adventure.feat_ticket_quota): + return false # 配额满——本局不再出现 + var offer: bool = _feat_ticket_pity >= int(db.adventure.feat_ticket_pity_step) or rg.randi_range(1, 100) <= int(db.adventure.feat_ticket_chance) + if offer: + _feat_ticket_pity = 0 + _feat_ticket_appeared += 1 + else: + _feat_ticket_pity += 1 + return offer + + +# 门票兑付(拿到即开——停点候选替换为专长三选一;不进持有集/零摘牌;限购计数 +1): +# 节点停点=本地替换(呈现层读在途态重绘);店内无停点=临时停点 + payload.context 标记(呈现层 +# 重 present 无胜利总结面,确认后只关面回店)。 +func _redeem_feat_ticket() -> bool: + _feat_ticket_sold += 1 + _feat_ticket_pity = 0 + var cands: Array = _draw_curio_candidates("level_clear") # 专长池(与通关渠道同白名单) + if cands.is_empty(): + push_error("[冒险结构] 门票兑付失败——专长池空集") + _feat_ticket_sold -= 1 + return false + if not _node_curio_pending.is_empty(): + _node_curio_pending["candidates"] = cands.duplicate(true) + return true + var payload: Dictionary = {"context": "feat_ticket", "candidates": cands.duplicate(true)} + _curio_pending = {"candidates": cands.duplicate(true), "payload": payload.duplicate(true)} + curio_choice_presented.emit(payload) + _snapshot_breakpoint() + return true + + func _roll_from(rg: RandomNumberGenerator) -> String: var pool: Array = _bonus_treasure_pool() return String(pool[rg.randi_range(0, pool.size() - 1)]) @@ -844,7 +896,7 @@ func _start_current_level() -> bool: _runner.set_tree_rules(_tree_rules) # 迭代 12 T10——树 rule 键集下传(A/B 面消费) if _shop_mode == "pool": # 商店共享池注入(迭代 11 第②批 T3——库存账本引用+消费 handler 双下传;legacy 模式零注入零扰动) - _runner.set_shop_pool(_curio_deck, Callable(self, "_consume_pool_purchase")) + _runner.set_shop_pool(_curio_deck, Callable(self, "_consume_pool_purchase"), Callable(self, "_feat_ticket_consider")) # 三选一道具件跨关携带注入(迭代 10)——迭代 11 第①批 T7 退役:aux 卡出池后 carry 恒空集, # 注入面随停点渠道退役拆除(容器字段保留=替换子交互机械惰性承载,零投放零行为) level_started.emit(level_index, String(_runner.level_row.get("display_name", ""))) @@ -990,6 +1042,8 @@ func _snapshot_breakpoint() -> void: "bonus_deck": _curio_deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原) "relics": _feats.duplicate(), # 特性持有集(迭代 12 T1——局内随机渠道获得态续玩防丢失) "bonus_pending": _curio_pending.duplicate(true), # 三选一停点态(空 = 无停点——选择停点可中断契约) + "feat_ticket_pity": _feat_ticket_pity, # T5 门票保底计数(扩列向后兼容——旧档缺键缺省 0) + "feat_ticket_sold": _feat_ticket_sold, # T5 门票限购计数(同上) "bonus_result": _curio_result.duplicate(true), # 已兑付待推进态(空 = 无——中断续玩不重兑付) "bonus_aux_carry": _bonus_treasure_carry.duplicate(true), # 道具件跨关携带(注入前中断防静默丢失) "keystone_drops": _keystone_drops.duplicate(true), # 逐杀掉落明细(摘要归因续接——迭代 10) diff --git a/game/core/config_db_cross_validator.gd b/game/core/config_db_cross_validator.gd index 583c3d2f..ce7a6fd8 100644 --- a/game/core/config_db_cross_validator.gd +++ b/game/core/config_db_cross_validator.gd @@ -106,6 +106,8 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void: if adv != null and shp != null: for row in shp.pool_shelf: var kind: String = String(row.get("kind_id", "")) + if kind == "feat_ticket": + continue # T5 门票豁免——货架专属机制种(非 deck 池种,零摘牌拿到即开),不入 CURIO_KINDS if not adv.CURIO_KINDS.has(kind): push_diag.call("error", "shelf_dangling_kind", "ShopTable.pool_shelf kind %s 不在 AdventureTable.CURIO_KINDS(共享池牌库外种)" % kind) continue diff --git a/game/core/level_runner.gd b/game/core/level_runner.gd index 06582dd3..e1c6776e 100644 --- a/game/core/level_runner.gd +++ b/game/core/level_runner.gd @@ -71,6 +71,7 @@ var _attune_charges: Dictionary = {} # 同调主动件充能账本( var _shop_pool_mode := false # pool 模式旗(set_shop_pool 注入时置位——AdventureRunner 按 params.shop_mode 下传) var _pool_deck: Array = [] # 共享池令牌库共享引用(AdventureRunner._curio_deck——库存=剩余令牌数单点) var _pool_consume := Callable() # 池消费 handler(AdventureRunner._consume_pool_purchase——_apply_curio_pick 同口写加算区+账本+摘牌) +var _pool_ticket := Callable() # 门票展示判定 handler(T5——AdventureRunner._feat_ticket_consider;节点与商店共享计数) var _shop_shelf: Array = [] # 当前货架(进店抽 3/买后全重抽——令牌字典 {"kind","tier"} 序,同 kind 去重每 kind 至多一卡) var _shop_visit_buys := 0 # 本次进店已购次数(A 链定价输入——离开清零) var _shop_paid_refreshes := 0 # 本次进店已付费刷新次数(MC-1——GP 刷新价曲线输入,离开清零) @@ -735,9 +736,10 @@ func player_request_refresh() -> bool: # 注入(AdventureRunner._start_current_level 唯一调用方——pool 模式专用):令牌库共享引用 + # 池消费 handler。legacy 模式不注入(_shop_pool_mode 恒 false——off 态零 RNG 零池消费零扰动)。 -func set_shop_pool(deck_ref: Array, consume_handler: Callable) -> void: +func set_shop_pool(deck_ref: Array, consume_handler: Callable, ticket_consider: Callable = Callable()) -> void: _pool_deck = deck_ref _pool_consume = consume_handler + _pool_ticket = ticket_consider # T5——门票展示判定(AdventureRunner._feat_ticket_consider;同流取数复用 shop_draw scope) _shop_pool_mode = true @@ -779,6 +781,10 @@ func _shop_draw_shelf() -> void: _shop_shelf.append(tok) if _shop_shelf.size() >= 3: break + # T5 门票注入(非 Boss 渠道唯一专长入口——货架与节点共享保底计数;判定含 pity 记账,替换末位占一席。 + # 取数复用本函数 shop_draw scope 的 stream——零新 scope,确定性序列不漂移) + if _pool_ticket.is_valid() and not _shop_shelf.is_empty() and bool(_pool_ticket.call(stream)): + _shop_shelf[_shop_shelf.size() - 1] = {"kind": "feat_ticket", "tier": "rare"} # 商店保底退役(迭代 15 卡② T4——渠道轴定位裁决:商店渠道只出属性值提升+资源,专长在非 Boss # 渠道只以门票形态出现(奇物.md v2.7/A4 断言)。迭代 12 T6「货架零特性挤卡换吞噬流件」简化口径 # 与渠道白名单直接冲突(devour=规则改写型=专长系),随渠道分离退役;专长商店入口=T5 门票。 diff --git a/game/data/schemas/adventure_table.gd b/game/data/schemas/adventure_table.gd index 94ec89b4..efc01d04 100644 --- a/game/data/schemas/adventure_table.gd +++ b/game/data/schemas/adventure_table.gd @@ -68,6 +68,15 @@ const TIER_QUOTAS := {"common": 70, "rare": 25, "rarest": 3} @export var curio_value_heal_rare: int = 6 # [0,999] knob——恢复(当即回血,封顶生效上限) @export var curio_value_ap_rarest: int = 1 # [0,10] knob——AP 件每场战斗首轮行动点基线增量 +# —— 专长获取门票(迭代 15 卡② T5——非 Boss 渠道的专长入口形态,奇物.md v2.7 渠道轴定位; +# 四口径契约④:拿到即开不可囤积/罕见只在概率不在价格/一局限购一次/计步保底连续 5 展示未出 +# 第 6 必出(计数=展示次数、刷新重展计入、节点与商店共享、触发清零、限购满不再触发)。 +# 四参数全部 provisional 归曲线调参批;limit/pity_step 系口径定死值调参批可复议)—— +@export var feat_ticket_chance: int = 20 # [0,100] knob(provisional)——每次非 Boss 渠道展示门票出现概率 % +@export var feat_ticket_quota: int = 2 # [0,9] knob(provisional)——每局门票出现次数上限 +@export var feat_ticket_limit: int = 1 # [1,9] knob(口径定死=1)——一局限购(选中/买下)次数 +@export var feat_ticket_pity_step: int = 5 # [1,99] knob(口径定死=5)——保底步数(连续未出展示次数) + ## 令牌配额读数(未列 kind×tier 组合 = 0——heal 无普通档/ap 无普通稀有档按 v2.2 构成) func curio_quota(kind: String, tier: String) -> int: @@ -125,6 +134,9 @@ func deck_total() -> int: func validate() -> Array: var errs: Array[Dictionary] = [] + for kv in [["feat_ticket_chance", feat_ticket_chance, 0, 100], ["feat_ticket_quota", feat_ticket_quota, 0, 9], ["feat_ticket_limit", feat_ticket_limit, 1, 9], ["feat_ticket_pity_step", feat_ticket_pity_step, 1, 99]]: + if int(kv[1]) < int(kv[2]) or int(kv[1]) > int(kv[3]): + errs.append({"category": "domain", "message": "%s 出域 [%d,%d]: %d" % [kv[0], kv[2], kv[3], kv[1]]}) 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 CURIO_KINDS: diff --git a/game/data/schemas/shop_table.gd b/game/data/schemas/shop_table.gd index f8e5ac70..f6de3465 100644 --- a/game/data/schemas/shop_table.gd +++ b/game/data/schemas/shop_table.gd @@ -28,7 +28,7 @@ extends Resource const GP_TIERS := ["normal", "elite", "boss"] const PRODUCT_ATTRS := ["atk", "max_hp", "armor_class"] -const SHELF_KINDS := ["attack", "max_hp", "armor", "ap"] # 货架候选词表封闭(四切边后种——切边面翻案须用户) +const SHELF_KINDS := ["attack", "max_hp", "armor", "ap", "feat_ticket", "heal"] # 货架候选词表(四切边后种 + T5 门票行 + T6 heal 补回复〔默认原则:分类内成员都入该渠道池〕——奇物批迭代15②) func validate() -> Array: diff --git a/game/data/tables/adventure.tres b/game/data/tables/adventure.tres index aa3390b1..4f7178ff 100644 --- a/game/data/tables/adventure.tres +++ b/game/data/tables/adventure.tres @@ -26,6 +26,10 @@ curio_value_gp_common = 20 curio_value_gp_rare = 50 curio_value_heal_rare = 6 curio_value_ap_rarest = 1 +feat_ticket_chance = 20 +feat_ticket_quota = 2 +feat_ticket_limit = 1 +feat_ticket_pity_step = 5 ; TP0 令牌池换代(2026-10-03,node-reward v2.2 追认 b 落地):三选一池 = 固定配额令牌池—— ; 普通 70/稀有 25/罕见 3 = 98 张(罕见档原列「道具件 2」随 treasure_item 池种退役收窄——台账自决披露项); diff --git a/game/data/tables/shop.tres b/game/data/tables/shop.tres index bd1203c1..ca4ce3f2 100644 --- a/game/data/tables/shop.tres +++ b/game/data/tables/shop.tres @@ -81,6 +81,14 @@ pool_shelf = [{ "kind_id": "ap", "display_name": "行动点件", "base_price": 5 +}, { +"kind_id": "feat_ticket", +"display_name": "奇物门票", +"base_price": 5 +}, { +"kind_id": "heal", +"display_name": "应急回复", +"base_price": 5 }] price_a = 1.8 price_b = 1.1 @@ -93,7 +101,7 @@ price_b = 1.1 ; (4+2)+(2+2)+(4+2)=16、全购欲望 ≈ 26–34——取舍空间成立;量级与方差全部 provisional-pending-曲线调参批 ;(用户拍板骨架先行、数值整体后置,行值归 难度曲线与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 袋负交易切边、 +; pool_shelf 行族 = 共享池货架(kind_id 词表 attack/max_hp/armor/ap/feat_ticket/heal 六行——heal 系 T6 商店补回复〔默认原则:分类内成员都要在它该出现的渠道池里,现行缺〕、GP 袋负交易切边计价物不自售、feat_ticket 系 T5 门票行〔非 Boss 渠道专长入口,价与其他商品相当=契约④口径②〕、 ; 规则改写型特性三轴 doctrine 切边;库存=上限即库存,账本在 冒险结构 _bonus_taken 单点共享,行族不记库存); ; 价格公式换代(pool 模式)= ceil(base_price × b^(关序−1) × A^(本次进店已购次数)),price_a=1.8/price_b=1.1 ; provisional 投值(零人工令改 provisional+后置追认,原 P5 甲研究拍板后置);products 行族 + 加法定价 diff --git a/game/presentation/curio_interact_controller.gd b/game/presentation/curio_interact_controller.gd index 696eaecc..e3d3c346 100644 --- a/game/presentation/curio_interact_controller.gd +++ b/game/presentation/curio_interact_controller.gd @@ -72,6 +72,13 @@ func resume_if_pending() -> bool: # 三选一停点(非末关通关恰一次):Boss 胜利先经战斗总结结算面(settlement-summary v1.1——钥石 # 增量行在其上),确认后进三选一面(level-clear.md v2);续玩停点恢复同入口(零自动推进契约) func on_presented(payload: Dictionary) -> void: + if String(payload.get("context", "")) == "feat_ticket": + # T5 店内门票兑付——临时停点开专长三选一面(零胜利总结面;确认后只关面回店) + _pending_curio = payload.duplicate(true) + _curio_context = "feat_ticket" + _open_modal.call(curio_overlay) + curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(payload.get("candidates", []) as Array)) + return if int(payload.get("aux_cleared", 0)) > 0: toast.show_toast("上关剩余 %d 件秘宝已随关卡结束清空" % int(payload.get("aux_cleared", 0)), "info") # 迭代 7 T4 清空反馈 _pending_curio = payload.duplicate(true) @@ -122,7 +129,10 @@ func _curio_card_texts(candidates: Array, treasure_detail: Array = []) -> Array: var rid := String((tok as Dictionary).get("feat_id", "")) var cid := "%s|%s|%s" % [kind, tier, rid] var text := "" - if kind == "relic": + if kind == "feat_ticket": + text = "奇物门票 +选定即开一次奇物三选一(T5 门票——拿到即开不进持有集)" + elif kind == "relic": text = Narrator.feat_card_text(db.feat_row(rid)) elif kind == "treasure_item": var iid := String(treasure_detail[treasure_i]) if treasure_i < treasure_detail.size() else "" @@ -183,6 +193,12 @@ func on_pick_confirmed(item_id: String) -> void: var ok: bool = adventure.apply_node_curio(kind, tier, rid) if _curio_context == "reward_node" else adventure.pick_curio(kind, tier, rid) if not ok: push_error("[bonus_interact] 三选一兑付被拒——见输出日志") + return + # T5——节点渠道门票拿到即开:候选已被替换为专长面,本地读在途态重绘(零 emit——context 保持 reward_node, + # 后续兑付/确认仍走节点路径回图选路) + if _curio_context == "reward_node" and kind == "feat_ticket": + var st2: Dictionary = adventure.node_curio_pending_state() + curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(st2.get("candidates", []) as Array)) func on_treasure_replace_presented(items: Array, incoming: Dictionary) -> void: @@ -221,6 +237,8 @@ func on_continue() -> void: _close_modal.call(curio_overlay) curio_overlay.close_pick() var adventure = _adventure_get.call() + if _curio_context == "feat_ticket": + return # T5 店内门票面——专长兑付完成后关面回店(无推进/无回图) if _curio_context == "reward_node": var runner = _runner_get.call() if runner != null: diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index 129dfb27..25066713 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -663,6 +663,43 @@ func _smoke_feats() -> void: node_relic_seen = true check(feat_seen.size() >= 2, "通关渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)") check(not node_relic_seen, "节点渠道零专长(60 次重抽零 relic——专长在非 Boss 渠道只以门票形态出现,T4 渠道白名单)") + # T5 门票行为断言(A5 拿到即开 / A6 保底计数——契约④四口径;独立 runner 防污染 a_r 牌库——后续 relic 兑付断言按 5 枚锚定) + var a_t = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(91)) + a_t.start_adventure(91, {"first_script": true}) + var stock_before := {} + for kk in ["attack", "gp", "heal"]: + stock_before[kk] = a_t.pool_stock(kk) + a_t._node_curio_pending = {"candidates": [{"kind": "feat_ticket", "tier": "rare"}, {"kind": "attack", "tier": "common"}], "aux_detail": []} + check(a_t.apply_node_curio("feat_ticket", "rare"), "A5 门票兑付受理(节点渠道——拿到即开)") + var tkt_cands: Array = (a_t.node_curio_pending_state().get("candidates", []) as Array) + var tkt_all_relic := tkt_cands.size() > 0 + for t in tkt_cands: + if String((t as Dictionary).get("kind", "")) != "relic": + tkt_all_relic = false + check(tkt_all_relic, "A5 拿到即开——停点候选已替换为专长三选一(全 relic)") + check(a_t.pool_stock("attack") == int(stock_before["attack"]) and a_t.pool_stock("gp") == int(stock_before["gp"]), + "A5 零摘牌——门票兑付不动令牌库(不进持有集)") + check(not (a_t.feats_held() as Array).has("feat_ticket"), "A5 无门票残留持有集") + var feats_n := (a_t.feats_held() as Array).size() + a_t.apply_node_curio("relic", String((tkt_cands[0] as Dictionary).get("tier", "rare")), String((tkt_cands[0] as Dictionary).get("feat_id", ""))) + check((a_t.feats_held() as Array).size() == feats_n + 1, "A5 专长面兑付受理(门票开出候选可正常入持有集)") + # A6 保底:pity 直填 5 → 下次展示必出(第 6 次必出口径);限购满 → 恒不触发 + a_t._feat_ticket_sold = 0 # A5 已兑付一枚门票(sold=1=limit)——A6 段重置计数隔离验证 + a_t._feat_ticket_pity = int(a_t.db.adventure.feat_ticket_pity_step) + var rg_t: RandomNumberGenerator = a_t.rng.open_stream("reward_pick") + check(a_t._feat_ticket_consider(rg_t), "A6 保底触发(pity=5 → 第 6 次展示必出——概率无关)") + check(a_t._feat_ticket_pity == 0, "A6 触发后清零") + a_t._feat_ticket_sold = int(a_t.db.adventure.feat_ticket_limit) + a_t._feat_ticket_pity = int(a_t.db.adventure.feat_ticket_pity_step) + check(not a_t._feat_ticket_consider(rg_t), "A6 限购满不再触发(保底冻结)") + a_t._feat_ticket_sold = 0 + a_t._feat_ticket_pity = 0 + var offered := false + for i in 200: + if a_t._feat_ticket_consider(rg_t): + offered = true + break + check(offered, "A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)") a_r._curio_pending = {"candidates": [{"kind": "relic", "tier": "rare", "feat_id": "m3"}, {"kind": "gp", "tier": "common"}], "payload": {}} var deck_m3 := 0 for t in a_r._curio_deck: @@ -3942,7 +3979,7 @@ func _smoke_shop_pool() -> void: # ⑧ 终局池读数(run_adventure pool 面——商店与奖励跨渠道消耗后的终局余量出讫) var st_pool: Dictionary = autoplay.run_adventure(_db, 42, 0, 0, false, null, "normal", "min", {}, {}, {"mode": "pool"}) var ps: Dictionary = st_pool.get("pool_stock", {}) - check(ps.size() == 4, "终局池读数四键出讫(run_adventure pool 面——读数只锚一次载体)") + check(ps.size() == 6, "终局池读数六键出讫(run_adventure pool 面——四池种+T5 门票+T6 heal 行,读数只锚一次载体)") var ps_ok := true for k in ps.keys(): var quota := 0 From ccd95c8215cfc9406db2e3ce696e73d5a26038f2 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 5 Oct 2026 20:58:34 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E5=A5=87=E7=89=A9=E6=89=B9T6b+T7+=E7=BC=BA?= =?UTF-8?q?=E9=99=B7=E4=BF=AE=EF=BC=9Arefresh=E5=85=A5=E6=B1=A0=EF=BC=88?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E8=A3=81B=E6=A1=88=E2=80=94=E2=80=94CURIO=5F?= =?UTF-8?q?KINDS=E6=89=A9=E7=AC=AC=E4=B8=83=E7=B1=BB/heal=5Frare=205?= =?UTF-8?q?=E2=86=923=E6=8C=A4=E5=8D=A0refresh=5Frare=3D2/=E6=80=BB?= =?UTF-8?q?=E9=87=8F98=E9=94=9A=E4=B8=8D=E5=8F=98/match=E8=A1=A8=E4=B8=A4?= =?UTF-8?q?=E8=A1=8C/=E5=85=91=E4=BB=98=3D=5Fattune=5Frefresh=5Fx=E5=A2=9E?= =?UTF-8?q?=E9=87=8F=E4=B8=8D=E8=BF=9B=E5=8A=A0=E7=AE=97=E5=8C=BA+registry?= =?UTF-8?q?=20pool=5Farchitecture=E5=A4=9A=E7=BA=A7=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E6=80=BB=E6=B1=A0-=E6=B8=A0=E9=81=93=E5=AD=90=E9=9B=86?= =?UTF-8?q?=E8=AF=AD=E4=B9=89+=E5=A5=91=E7=BA=A610=E6=8C=89=E8=A3=81?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=EF=BC=89=EF=BC=9BT7=20charge=E7=BC=BA?= =?UTF-8?q?=E9=99=B7=E7=99=BB=E8=AE=B0=EF=BC=883=E4=BB=B6<4=E6=A1=A3?= =?UTF-8?q?=E4=BA=8C=E4=B8=8D=E5=8F=AF=E8=BE=BE=E2=80=94=E2=80=94=E7=99=BD?= =?UTF-8?q?=E5=90=8D=E5=8D=954=E5=8F=AA=E7=99=BB=E8=AE=B0=E4=B8=8D?= =?UTF-8?q?=E7=A1=AC=E8=A1=A5=EF=BC=8Cregistry=20charge=5Fdefect=EF=BC=89?= =?UTF-8?q?=EF=BC=9Bautoplay=E9=97=A8=E7=A5=A8=E6=8B=BF=E5=88=B0=E5=8D=B3?= =?UTF-8?q?=E5=BC=80=E5=88=86=E6=94=AF=EF=BC=88=E7=BC=BA=E5=88=86=E6=94=AF?= =?UTF-8?q?=3D=E8=8A=82=E7=82=B9=E5=81=9C=E7=82=B9=E6=82=AC=E7=A9=BA?= =?UTF-8?q?=E9=80=89=E8=B7=AF=E5=8D=A1=E6=AD=BB=E2=80=94=E2=80=94MC=20time?= =?UTF-8?q?out=E5=BD=A2=E6=80=81=E6=89=B9=E5=86=85=E5=AE=9E=E6=B5=8B?= =?UTF-8?q?=EF=BC=8Cn20=E5=A4=8D=E7=8E=B01/2=E7=A9=BA=E7=BB=88=E5=B1=80?= =?UTF-8?q?=E4=BF=AE=E5=90=8E=E4=B8=8E=E5=9F=BA=E7=BA=BF=E9=80=90=E4=BD=8D?= =?UTF-8?q?=E4=B8=80=E8=87=B4=EF=BC=89=EF=BC=9B=E4=BB=BB=E5=8A=A1=E4=B9=A6?= =?UTF-8?q?T6b=E8=A3=81=E5=86=B3=E5=9B=9E=E5=86=99=EF=BC=88=E5=86=92?= =?UTF-8?q?=E7=83=9F782/0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iter15-2-curio-exit-report-2026-10-05.md | 61 +++++++++++++++++++ design/registry/entities.yaml | 2 + ...务书-奇物词族对齐与渠道分离批-2026-10-05.md | 2 + game/core/adventure_runner.gd | 3 + game/data/schemas/adventure_table.gd | 8 ++- game/data/tables/adventure.tres | 4 +- game/smoke/level_autoplay.gd | 7 +++ 7 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 design/qa/iter15-2-curio-exit-report-2026-10-05.md diff --git a/design/qa/iter15-2-curio-exit-report-2026-10-05.md b/design/qa/iter15-2-curio-exit-report-2026-10-05.md new file mode 100644 index 00000000..8caa7450 --- /dev/null +++ b/design/qa/iter15-2-curio-exit-report-2026-10-05.md @@ -0,0 +1,61 @@ +# 出口报告:迭代 15 卡② 奇物词族对齐与渠道分离批(2026-10-05) + +> 任务书 = `design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md`(T0 差异回写+T6b 裁决回写在册)。 +> 设计事实源 = `design/gdd/systems/冒险循环/奇物.md` v2.7。worktree = `g-plan2-curio-wordfamily`,分支 `curio-wordfamily-20261005`,基线 = master 413c936(冒烟 772/0)。 + +## 一、提交链(worktree) + +| # | 提交 | 内容 | +|---|---|---| +| 1 | d4cde73 | T1:feature_*→curio_*(CURIO_KINDS/curio_quota/curio_value/curio_candidate_count + adventure.tres 键名 + entities.yaml 换代续写) | +| 2 | 8e2694d | T2:运行层 bonus_*→curio_* 全族 + curio_interact_controller.gd/curio_pick_overlay.gd 两文件改名 + 裁点甲案 + reward_overlay 红转绿适配 | +| 3 | 6812ebb | T3+T4:relic_*→feat_*(三文件改名+全 token+config_db 注册键两端)+ 渠道白名单过滤(通关渠道只出专长/节点零专长)+ devour 货架保底退役 | +| 4 | 95b66f0 | T5+T6a:门票入表(四字段 provisional+三计数+双注入+拿到即开三入口+断点扩列+shop.tres 门票行+A5/A6 断言)+ T6a 商店补 heal 行 + registry 刷新边界写死 | +| 5 | (T6b+T7+T8) | refresh 入池(B 案挤占 heal 5→3)+ registry pool_architecture/charge_defect 落文 + 批末重闸 | + +## 二、批末读数(T8 回填) + +| 闸 | 基线(413c936) | 批末 | 判定 | +|---|---|---|---| +| 冒烟 smoke_all | 772/0 | 【T8_SMOKE】 | | +| run_all | 15/16(reward_overlay 红=M7 基线红) | 【T8_RUNALL】 | **红转绿(T2 适配)** | +| 行为探针四件+ui_struct | 全绿 | 【T8_PROBES】 | | +| doc 三闸+熵增闸 | 零新增基线 | 【T8_DOC】 | | +| MC n200 双策略(82000 窗) | 判式批改后锚(76000 系) | 【T8_MC】 | | + +## 三、验收断言(A1-A12 逐条) + +- **A1 词族归零**:【A1_CENSUS】(保名单:_bonus_row/_bonus_value/_bonus_summary_text 等加成区呈现+bonus_summary+存档键六形态+kind 值 "relic"/身份前缀 "relic:"/relics_seed 进程内参数——穷举见 §五) +- **A2 禁词未用**:reward_pick/reward_node/reward_preview 既有三处零新增(T2 全程未触) +- **A3 兑付频率**:专长兑付点=通关三选一(_pending_next_level 置位链=关底 Boss 击败同一时刻);关内普通战斗节点零奇物(node_type=battle 无三选一调用,结构保证+冒烟既有断言面) +- **A4 渠道分离**:_draw_curio_candidates(channel) 白名单过滤——通关渠道 60 次重抽全 relic/节点渠道 60 次零 relic(冒烟断言在案);货架白名单=shop_pool_rows 词表(feat_ticket 门票行+heal 回复行+四池种) +- **A5 拿到即开**:门票兑付=候选替换专长面+零摘牌+无残留持有集(冒烟 4 断言在案) +- **A6 保底计数**:pity=5→第 6 展示必出/触发清零/限购满冻结/概率面存在(冒烟 4 断言在案) +- **A7 值零变更**:T1 改名键值逐位相等(diff 实测)+ heal 5→3/refresh 0→2 系 T6b 用户裁 B 案结构面(非本断言范围) +- **A8 存档往返**:断点往返测试 PASS(全批三次验证:T1/T2/T4 后各一);存档键按 T2 裁点甲案+relics 第 6 键澄清(§四-3) +- **A9 池配额锚**:TIER_QUOTAS 70/25/3=98 零改动(B 案挤占——heal 5→3/refresh 0→2,总量不变,validate 绿) +- **A10 节点类型零扩类**:封闭枚举五种未动 +- **A11 玩家可见面无分类名**:门票卡面写「奇物门票/奇物三选一」(规避「专长」分类名);本批只做断言检查不改法(C1 归呈现批) +- **A12 档二通路**:charge=3 件(<4)——**显式登记不硬补**(白名单 4;registry charge_defect 落文,转内容批) + +## 四、裁点与自裁记录 + +1. **T2 存档键裁点(白名单 3)**:**甲案**——五存档键(bonus_deck/bonus_taken/bonus_pending/bonus_result/bonus_aux_carry)引号字面量禁改,代码侧变量全换 curio_。依据:与③批先例一致;全仓对拍六形态逐键全等。 +2. **bonus_aux_carry 语义澄清(T0 回写)**:该键系**秘宝件跨关携带**(③批已对齐 treasure 词族),非奇物概念——剔除出本批改名面;_bonus_treasure_carry 变量随③批保留。 +3. **"relics" 第 6 存档键(任务书漏登记)**:adventure_runner 断点快照/摘要 additive 键 "relics"(迭代 12 T1 扩列)——任务书 §2.5 只列 5 键漏此键;按甲案精神禁改+registry/任务书回写。kind 值 "relic"/身份前缀 "relic:"/relics_seed 参数同理保留(数据契约键,A1 扫描口径=relic_ 下划线形态)。 +4. **T6b refresh 入池配额(用户门呈报,用户裁 B 案)**:refresh rare=2 从 heal rare 5 挤占(heal 5→3),总量 98 锚不变;契约 10 按裁更新;**池架构语义=总池-渠道子集(多级缓存形)**确立落 registry pool_architecture。 +5. **devour 货架保底退役(T4 连带)**:迭代 12 T6「货架零特性挤卡换吞噬流件」简化口径与渠道白名单直接冲突(devour=规则改写型=专长系不得进商店货架)——随渠道定位退役,留痕注释。 +6. **reward_overlay_test 红转绿(T2 连带)**:成因考证=M7 bcard 换装(PanelContainer)致 `c is Button` 恒 0(主区 3c4291d 起红在案);断言改数 _choice_box 子节点数(语义不变)——run_all 16 套历史首次全绿。 + +## 五、例外清单(A1 扫描口径残存量,批末) + +【A1_EXCEPT】 + +## 六、遗留与移交 + +1. 【T8 07 登记】 +2. C1 玩家可见面违规改法+档位呈现三路 → 呈现批(06 独立条目) +3. C2 第四关+档二复裁+charge 补件 → 第四关内容批 +4. C3 门票四参数终值(现 provisional 20%/2/1/5)→ 曲线调参批 +5. C4 存档键正式迁移 → 存档批(本批甲案未迁移,兼容读旧键零破坏) +6. MC 运行含既有 script error 噪音(ticket_id/minesweeper_rules 链)——待基线对拍归属,读数采集不受影响 diff --git a/design/registry/entities.yaml b/design/registry/entities.yaml index ce4bb7b2..95014b70 100644 --- a/design/registry/entities.yaml +++ b/design/registry/entities.yaml @@ -601,6 +601,8 @@ entities: acquisition: 三条渠道的**目标分工(交叉开关,v2.2 收口)**——专长:主渠道=**关底 Boss 击败=通过这一关**(**唯一直接给的渠道**;不新增节点类型、挂 Boss 节点兑付面;末关的 Boss 击败后不给——无后续关卡可消费)。**兑付频率**:奇物不是打一场战斗给一份,关内普通战斗节点不给任何奇物,一关之内只兑付一次(奖励节点与商店同理,各节点各自兑付),奖励节点与商店**只给「专长获取」门票**——**拿到即开、不可囤积**,不进持有集(选中/买下同一时刻开专长三选一;可囤积会让玩家无限推迟关键决策,且把持有物语义从后门放回)。门票归专长(给的是一件以前不会的事,只是把选择推迟)。罕见**只在概率不在价格**,一局限购一次,**计步保底**:连续 5 次三选一展示未出现→第 6 次必出(计数=展示次数、刷新重展计入、奖励节点与商店共享、触发清零、限购用满后不再触发);属性值提升:奖励节点 + 商店(口径「训练」);资源:奖励节点 + 商店(**金币除外、回复在内**);后两类均不出现在 Boss 渠道。形状=「一块独占 + 两块并行」,专长靠打赢、其余靠抽取与购买。**默认原则**:除已登记的特殊规则外,分类内的东西都要在它该出现的渠道池子里——任何不出现都须登记理由**现行实现三渠道共用同一候选池、三类混编**,分工尚未实施,登记于 06-待办清单 BD 分层条目 boundary: 判据走生命周期——**一次冒险内获得、冒险收束即清零的是奇物;跨冒险跟着你走的是魔法道具**。奇物永不进同调位,魔法道具永不进当局随机渠道;与元进度天赋树两层互不代位 data_sources: feats.tres(专长定义行)/ adventure.tres(属性值提升与资源的种类配额与值)/ shop.tres(货架取舍) + charge_defect: **charge 档二不可达登记(T7 奇物批迭代15②——数据缺陷留痕,白名单4授权「只登记不硬补」)**:feats.tres 现行 charge 标签仅 3 件(<4),档二激活要求同标签 ≥4 件——charge 流派永远达不到档二,即使获取次数无限。补哪几件系内容裁决,转独立内容批(硬补占位件已否决);档二可达性复裁=第四关内容批落地后实测(C2 待裁点)。 + pool_architecture: **池架构=总池-渠道子集(多级缓存形,用户 2026-10-05 裁 T6 时确立)**:总池=单一令牌库(TIER_QUOTAS 70/25/3=98 + 专长活行追加);三渠道按分类白名单从总池取**子集视图**(关底 Boss 击败=只出专长子集;奖励节点/商店=属性值提升+资源子集,专长只以门票形态出现)——渠道不各建物理池,白名单在候选视图层生效(T4 过滤式)。配额挤占重裁:heal rare 5→3、refresh rare 0→2(方案B,总量 98 锚不变;用户裁「渠道从总量里拿子集」时同步裁)。 refresh_boundary: **刷新次数边界写死(T6 奇物批迭代15②——防后续混淆结算)**:「商店独立刷新机制」(shop_refresh_free/shop_refresh_cost_base/shop_refresh_cost_curve 三旋钮——进店后的免费次数+GP 付费刷新)系**商店自身服务**,作用对象=货架重抽;奇物 refresh 件(资源类成员,作为奇物授予的重抽次数)作用对象=**三选一界面**(兑付即 _attune_refresh_x 增量,X 余量=三选一重抽次数),不进加算区(§2.2 加算区四轴不含它)。两者不是一回事,禁止混算。 open_questions: ①类内异质性已登记——金币与回复是即时兑付、不进持有集不持续生效,与行动点上限/刷新次数生命周期不同,靠这条描述把"资源"从泛用词收回到本类定义内;②专长门票尚未入表(出现概率/配额/限购次数/保底阈值四参数均无事实源);③确定性渠道一局两次专长 vs 档位阈值(同标签 ≥2 档一 / ≥4 档二)——**档二在三关结构下几乎不可达**,用户定延后复裁(触发=第四关内容批后实测);④charge 标签仅 3 件、永远达不到档二(数据层缺陷,独立登记,勿被上条盖掉);⑤断点存档键 5 个(bonus_deck / bonus_taken / bonus_pending / bonus_result / bonus_aux_carry)是否随存档版本迁移换名待裁 naming_note: 2026-10-05 命名统一 + 两次重构——此前按内部分类被叫成三个名字(规则类称「流派件」、属性类称「数值件」、界面称「本关强化」),v1.0 统一为「特性」,v2.0 改名为「奖励」,v2.4 定名为「奇物」(因筐里也装金币这类一次性兑付物,"自身提升 vs 持有物"已切不开,判据改由生命周期承担)。v2.5 第三类定名为「资源」(用户拍板)。**v2.7 三个子节点全部正式定名、去「类」字后缀**:专长(Feat)/ 属性值提升(ASI)/ 资源——此前是描述式叫法不是专名。**代码侧三套词族已于奇物批对齐(2026-10-05 迭代15②批 T1-T3)**:`feature_*` → `curio_*`(CURIO_KINDS/curio_quota/curio_value)、`relic_*` → `feat_*`(feat_table.gd/feats.tres/feats_engine.gd/feat_flags/feat_id——kind 值 "relic" 与身份前缀 "relic:" 系令牌池数据契约键沿用不动)、运行层 `bonus_*` → `curio_*`(curio_interact_controller/curio_pick_overlay/CurioPickOverlay/pick_curio);断点存档键(bonus_deck/bonus_taken/bonus_pending/bonus_result/bonus_aux_carry/relics)按甲案沿用历史名不迁移(存档兼容) diff --git a/design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md b/design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md index 7d163423..8ea9d1d9 100644 --- a/design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md +++ b/design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md @@ -150,6 +150,8 @@ ### T6 刷新次数入表 + 商店补回复(≈0.5d) +> ✅ **T6b 裁决回写(2026-10-05 用户裁方案B)**:refresh rare=2 从 heal rare 5 挤占(heal 5→3),**总量 98 锚不变**——validate 硬断言零改动。契约 10「curio_quota_* 值零变更」按用户裁更新:本批结构面允许的配额变化=heal 5→3 挤占予 refresh 2(其余值零变更)。**池架构语义确立=总池-渠道子集(多级缓存形)**:总池单一令牌库,三渠道按白名单取子集视图(T4 过滤式即此形态),registry pool_architecture 已落文。 + - `FEATURE_KINDS` 扩项须走 schema + registry 同步修订(封闭词表,红线 §3.6) - ⚠️ **必须先划清边界**:既有「商店独立刷新机制」是**进店后的免费次数 + GP 刷新**(商店自身的服务),本项是**作为奇物授予的**重抽次数——**两者不是一回事**,入表前须在 registry 里把边界写死,防止后续混淆结算 - 刷新次数的作用对象是**三选一界面而非角色**——不进加算区(§2.2 加算区四轴不含它) diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index 24fcd0ff..3bc1846a 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -397,6 +397,9 @@ func _feat_payload_keys(feat_id: String) -> Dictionary: # 按挂点消费,档位质变由 FeatsEngine 求值)。兑付即从 _curio_deck 摘除该令牌(选中踢池; # 未选候选从未摘牌=自动回池)。道具件不入此体——未知件返回 false 由调用方按渠道名报错。 func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String = "") -> bool: + if kind == "refresh": + _attune_refresh_x += value # T6——刷新次数件兑付=X 余量增量(作用三选一界面非角色,不进加算区——registry refresh_boundary) + return true if kind == "relic" and db.feat_row(feat_id).is_empty(): push_error("[冒险结构] relic 兑付失败——表无此件: %s" % feat_id) return false # 先校验后摘牌(令牌零丢失) diff --git a/game/data/schemas/adventure_table.gd b/game/data/schemas/adventure_table.gd index efc01d04..3cc181b8 100644 --- a/game/data/schemas/adventure_table.gd +++ b/game/data/schemas/adventure_table.gd @@ -24,7 +24,7 @@ extends Resource ## 类内异质性:金币与回复是即时兑付,不进持有集、不持续生效。) ## 三选一提升池 kind 词表(封闭——断点附随件与牌库剔除态依赖它;扩件走本表+registry 修订)。 ## treasure_item 池种退役(迭代 11 第①批 T7——同调批出池);heal 恢复件新入池(v2.2 令牌池追认 b)。 -const CURIO_KINDS: Array[String] = ["attack", "max_hp", "armor", "gp", "ap", "heal"] +const CURIO_KINDS: Array[String] = ["attack", "max_hp", "armor", "gp", "ap", "heal", "refresh"] # T6 扩第七类(refresh=资源类成员——作为奇物授予的三选一重抽次数,作用对象=三选一界面非角色;见 registry refresh_boundary) ## 令牌档位词表(v2.2 三档位——稀有度命名对齐 MC 策略判读序 316-f:1 级最稀有=rarest 优先) const TIERS: Array[String] = ["common", "rare", "rarest"] const TIER_COMMON := "common" @@ -53,7 +53,8 @@ const TIER_QUOTAS := {"common": 70, "rare": 25, "rarest": 3} @export var curio_quota_armor_rare: int = 5 # [0,99] @export var curio_quota_gp_common: int = 18 # [0,99] @export var curio_quota_gp_rare: int = 5 # [0,99] -@export var curio_quota_heal_rare: int = 5 # [0,99]——恢复件(新入池种类,当即回血) +@export var curio_quota_heal_rare: int = 3 # [0,99]——恢复件(原 5;T6 挤占 2 席位予 refresh——用户 2026-10-05 裁方案B,总量 98 锚不变) +@export var curio_quota_refresh_rare: int = 2 # [0,99]——刷新次数件(T6 新入池资源类成员;provisional 归曲线调参批) @export var curio_quota_ap_rarest: int = 3 # [0,99]——AP 件(罕见档;道具件 2 席位随池种退役收窄) # —— 令牌件值(小/大数值对——v2.2 追认 b 定稿:20/50、+1/+2、+1/+2、+5/+10、恢复+6、AP+1)—— @@ -66,6 +67,7 @@ const TIER_QUOTAS := {"common": 70, "rare": 25, "rarest": 3} @export var curio_value_gp_common: int = 20 # [0,999] knob——小GP @export var curio_value_gp_rare: int = 50 # [0,999] knob——大GP @export var curio_value_heal_rare: int = 6 # [0,999] knob——恢复(当即回血,封顶生效上限) +@export var curio_value_refresh_rare: int = 1 # [0,9] knob(provisional)——刷新次数件兑付值(三选一重抽次数 +N——_attune_refresh_x 增量,不进加算区) @export var curio_value_ap_rarest: int = 1 # [0,10] knob——AP 件每场战斗首轮行动点基线增量 # —— 专长获取门票(迭代 15 卡② T5——非 Boss 渠道的专长入口形态,奇物.md v2.7 渠道轴定位; @@ -90,6 +92,7 @@ func curio_quota(kind: String, tier: String) -> int: ["gp", TIER_COMMON]: return curio_quota_gp_common ["gp", TIER_RARE]: return curio_quota_gp_rare ["heal", TIER_RARE]: return curio_quota_heal_rare + ["refresh", TIER_RARE]: return curio_quota_refresh_rare ["ap", TIER_RAREST]: return curio_quota_ap_rarest return 0 @@ -106,6 +109,7 @@ func curio_value(kind: String, tier: String = TIER_COMMON) -> int: ["gp", TIER_COMMON]: return curio_value_gp_common ["gp", TIER_RARE]: return curio_value_gp_rare ["heal", TIER_RARE]: return curio_value_heal_rare + ["refresh", TIER_RARE]: return curio_value_refresh_rare ["ap", TIER_RAREST]: return curio_value_ap_rarest return 0 diff --git a/game/data/tables/adventure.tres b/game/data/tables/adventure.tres index 4f7178ff..0dc71eed 100644 --- a/game/data/tables/adventure.tres +++ b/game/data/tables/adventure.tres @@ -14,7 +14,8 @@ curio_quota_armor_common = 17 curio_quota_armor_rare = 5 curio_quota_gp_common = 18 curio_quota_gp_rare = 5 -curio_quota_heal_rare = 5 +curio_quota_heal_rare = 3 +curio_quota_refresh_rare = 2 curio_quota_ap_rarest = 3 curio_value_attack_common = 1 curio_value_attack_rare = 2 @@ -25,6 +26,7 @@ curio_value_armor_rare = 2 curio_value_gp_common = 20 curio_value_gp_rare = 50 curio_value_heal_rare = 6 +curio_value_refresh_rare = 1 curio_value_ap_rarest = 1 feat_ticket_chance = 20 feat_ticket_quota = 2 diff --git a/game/smoke/level_autoplay.gd b/game/smoke/level_autoplay.gd index 9f50abdf..429a3dc2 100644 --- a/game/smoke/level_autoplay.gd +++ b/game/smoke/level_autoplay.gd @@ -229,6 +229,13 @@ static func _on_adventure_node_entered(node: Dictionary, info: Dictionary, adr, adr.present_node_curio(ncands, ndraw.get("aux_detail", []) as Array) var npick: Dictionary = _pick_token(ncands, String(st.get("feat_mode", "off"))) if adr.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", ""))): + # T5 门票拿到即开——候选已被替换为专长三选一,就地再选一轮(确定性策略同款 316-f); + # 不作此分支则节点停点悬空→选路卡死(MC timeout 形态,2026-10-05 批内实测) + if String(npick.get("kind", "")) == "feat_ticket": + var fcands: Array = (adr.node_curio_pending_state().get("candidates", []) as Array) + if not fcands.is_empty(): + var fpick: Dictionary = _pick_token(fcands, String(st.get("feat_mode", "off"))) + adr.apply_node_curio(String(fpick.get("kind", "")), String(fpick.get("tier", "")), String(fpick.get("feat_id", ""))) if not adr._node_curio_pending.is_empty() and adr._node_curio_pending.has("incoming"): adr.resolve_curio_replace(-1) # 满位替换:放弃新件 else: From 049aa62e2e4eb9ccf91059da0af1f331cd203296 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 5 Oct 2026 21:07:06 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E5=A5=87=E7=89=A9=E6=89=B9T8=EF=BC=9A?= =?UTF-8?q?=E6=89=B9=E6=9C=AB=E9=87=8D=E9=97=B8=E5=85=A8=E7=BB=BF=EF=BC=88?= =?UTF-8?q?run=5Fall=2016=E5=A5=97=E5=8E=86=E5=8F=B2=E9=A6=96=E6=AC=A1?= =?UTF-8?q?=E5=85=A8=E7=BB=BF+=E4=BA=94=E6=8E=A2=E9=92=88=E7=BB=BF+doc?= =?UTF-8?q?=E4=B8=89=E9=97=B8=E9=9B=B6=E6=96=B0=E5=A2=9E=E3=80=943?= =?UTF-8?q?=E5=A4=84=E6=9C=AC=E6=89=B9=E6=94=B9=E5=90=8D=E6=96=AD=E9=93=BE?= =?UTF-8?q?=E9=9A=8F=E4=BF=AE=EF=BC=9A=E5=A5=87=E7=89=A9.md=20relics.tres?= =?UTF-8?q?=E2=86=92feats.tres=C3=972+node-reward.md=20curio=5Fpick=5Fover?= =?UTF-8?q?lay=E3=80=95+=E7=86=B5=E5=A2=9E=E9=97=B8=E8=BF=9D=E4=BE=8B=3D?= =?UTF-8?q?=E4=B8=BB=E5=8C=BA=E5=90=8C=E6=AC=BE=E6=97=A2=E6=9C=89=E5=BD=92?= =?UTF-8?q?archify=E6=89=B9=EF=BC=89+MC=20n200=E8=AF=BB=E6=95=B0=E9=87=87?= =?UTF-8?q?=E5=AE=8C=EF=BC=88normal=200.5%/perfect=2077.5%,=E5=8F=AA?= =?UTF-8?q?=E6=B5=8B=E4=B8=8D=E5=88=A4,autoplay=E9=97=A8=E7=A5=A8=E5=88=86?= =?UTF-8?q?=E6=94=AF=E4=BF=AE=E5=90=8En20=E4=B8=8E=E4=B8=BB=E5=8C=BA?= =?UTF-8?q?=E5=9F=BA=E7=BA=BF=E9=80=90=E4=BD=8D=E4=B8=80=E8=87=B4=EF=BC=89?= =?UTF-8?q?+=E5=87=BA=E5=8F=A3=E6=8A=A5=E5=91=8A=E7=BB=88=E7=A8=BF+07?= =?UTF-8?q?=E2=91=B0=E4=BA=BA=E9=97=B8=E6=9D=90=E6=96=99=EF=BC=88=E4=B8=93?= =?UTF-8?q?=E9=95=BF=E5=85=91=E4=BB=98=E6=97=B6=E7=82=B9=E5=8D=95=E5=88=97?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E9=A1=B9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/gdd/07-体验验证清单.md | 8 ++++++++ design/gdd/systems/冒险循环/奇物.md | 4 ++-- design/gdd/ux/node-reward.md | 2 +- .../qa/iter15-2-curio-exit-report-2026-10-05.md | 16 ++++++++-------- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/design/gdd/07-体验验证清单.md b/design/gdd/07-体验验证清单.md index 6a20766e..836992c8 100644 --- a/design/gdd/07-体验验证清单.md +++ b/design/gdd/07-体验验证清单.md @@ -157,6 +157,14 @@ - **看什么(实机目检约 2 分钟)**:①标题屏应为左右双列——左操作列(冒险/营地与系统两组)与右立绘看台+玩法速览牌并排,互不遮挡,「退出游戏」按钮完整可见;②同组按钮等宽(菜单列/确认弹窗),按钮不再宽窄参差;③营地屏三柜台+底栏不出屏、不重叠。 - **红灯信号**:任何屏出现控件叠压/按钮宽窄不一回潮/内容超出屏幕——结构探针已拦机检面,红灯=探针盲区,报批补断言。 +### ⑰ 奇物渠道分离与门票目检(2026-10-05 奇物词族对齐与渠道分离批随批登记——迭代15②号卡) + +- **专长兑付时点(单列检查项——契约①核心)**:关底 Boss 被击败那一刻应弹出通关三选一(候选应全为专长件=卡面只写单件名与效果);**战斗结算面不应出现任何奇物授予**;普通战斗节点打完不给任何奇物。 +- **门票目检**:奖励节点/商店偶见「奇物门票」卡(价与其他商品相当);选中/买下同一时刻应**直接开出一次奇物三选一**(不进持有集、无库存残留);同局第二次买到应正常(限购 1——第三次起不再出现)。 +- **商店货架目检**:货架应偶见「应急回复」行(T6 补回复);原吞噬流件「零特性挤卡保底」已随渠道分离退役——货架不再出现替卡行为。 +- **红灯信号**:战斗结算后弹奇物;门票进持有集;货架出现专长单件(非门票形态);refresh 件写进角色加成区。 +- **结论回流**:呈现面违规改法+档位呈现三路=C1 待裁(呈现批);强度体感归 M-4 带判读(本批 MC 只测不判)。 + ## 验收读数重测义务(履行留痕 + 现行义务) **已履行**(按序换代,旧读数各随其批作废留痕不入本单):①验收读数重测批(2026-09-19——Boss 单独出列/承伤带/通关率基线全量重测,硬化翻倍期·乏力死效果期·充能期旧读数作废);②迭代 5 基线重测(统一随机流后 MC+verifier 双窗);③**数值修复批新尺重测(2026-10-01)**——模拟档位重构后全量重测,新锚 `sim-data/runsum_numfix_final_n200_20261001.jsonl`,全旧带作废只测不判([numfix 报告](../qa/numfix-newruler-readings-2026-10-01.md));④**迭代 10 奖励重构批重锚(2026-10-01)**——四代锚 77000/78000/79000/80000(改前/重构后/共享池后/掷定前置后,各 400 局),普通 L1 41.8%/L2 10.2% 带内([读数报告](../qa/m3-iter10-clear-rate-readings-2026-10-01.md));⑤**T19 真人标定局收口(2026-10-03 用户裁)**——有效局 2(1 通关 1 死于 L3 首战,[读数报告](../qa/t19-real-playtest-readings-2026-10-02.md)),用户裁定**佐证级收口不再补局**;k 预期带同日立(**正常 >0.5、预警 <0.3**),字段随 81000 重锚入库后补算终值。 diff --git a/design/gdd/systems/冒险循环/奇物.md b/design/gdd/systems/冒险循环/奇物.md index 61e6e397..4ce8891e 100644 --- a/design/gdd/systems/冒险循环/奇物.md +++ b/design/gdd/systems/冒险循环/奇物.md @@ -13,7 +13,7 @@ > > 状态:**已实现**(2026-10-04 双层 BD 实施批落地)。本文件是奇物的唯一定义文档。 > -> 数值纪律:本文件只写概念、取值域与指路;每件给多少、效果是什么一律不进正文,事实源是 `game/data/tables/relics.tres` 与 `game/data/tables/adventure.tres`。 +> 数值纪律:本文件只写概念、取值域与指路;每件给多少、效果是什么一律不进正文,事实源是 `game/data/tables/feats.tres` 与 `game/data/tables/adventure.tres`。 ## 设计目的 @@ -299,7 +299,7 @@ | 参数 | 取值域 | 事实源 | |---|---|---| -| 专长定义行(标识 / 显示名 / 稀有度 / 标签 / 挂点 / 效果字典) | 各字段封闭枚举,行键白名单封闭 | `game/data/tables/relics.tres` | +| 专长定义行(标识 / 显示名 / 稀有度 / 标签 / 挂点 / 效果字典) | 各字段封闭枚举,行键白名单封闭 | `game/data/tables/feats.tres` | | 专长门票(出现概率 / 配额 / 全局限量) | 整数 | **尚未入表**,见待办 | | 属性值提升种类 | 封闭三项:生命上限 / AC / 攻击,扩项走文档修订 | `game/data/schemas/adventure_table.gd` | | 资源种类 | 行动点上限 / 金币 / 回复(**刷新次数与专长获取两项尚未入表**,见待办) | 同上 | diff --git a/design/gdd/ux/node-reward.md b/design/gdd/ux/node-reward.md index 57f7461c..200b2ac0 100644 --- a/design/gdd/ux/node-reward.md +++ b/design/gdd/ux/node-reward.md @@ -1,7 +1,7 @@ # 节点奖励屏 UX spec(node-reward) > 状态:v2.4(2026-10-03 TP0 令牌池落地——三选一池改固定配额令牌池 70/25/3=98 张〔罕见档随道具件池种退役收窄〕,kind 六种+档位两维,恢复件新入池,MC 策略 316-f 落地) -> 事实源:`game/presentation/views/bonus_pick_overlay.gd`(三选一覆盖层共用面)+ `main.gd`(奖励节点分支——`adventure.node_bonus_candidates/apply_node_bonus`)+ `battle_narrator.gd`(node_bonus_header) +> 事实源:`game/presentation/views/curio_pick_overlay.gd`(三选一覆盖层共用面)+ `main.gd`(奖励节点分支——`adventure.node_bonus_candidates/apply_node_bonus`)+ `battle_narrator.gd`(node_bonus_header) > 相邻:[level-clear](level-clear.md)(通关三选一面——同一覆盖层的另一上下文)/ [level-map](level-map.md)(关卡画面)/ [秘宝](../systems/排雷循环/秘宝.md)(道具件内容权威——本批起为局外同调件,不再入三选一牌库)/ [冒险结构](../systems/冒险循环/冒险结构.md)(共享池令牌库账本) ## 定位 diff --git a/design/qa/iter15-2-curio-exit-report-2026-10-05.md b/design/qa/iter15-2-curio-exit-report-2026-10-05.md index 8caa7450..1b9af711 100644 --- a/design/qa/iter15-2-curio-exit-report-2026-10-05.md +++ b/design/qa/iter15-2-curio-exit-report-2026-10-05.md @@ -11,21 +11,21 @@ | 2 | 8e2694d | T2:运行层 bonus_*→curio_* 全族 + curio_interact_controller.gd/curio_pick_overlay.gd 两文件改名 + 裁点甲案 + reward_overlay 红转绿适配 | | 3 | 6812ebb | T3+T4:relic_*→feat_*(三文件改名+全 token+config_db 注册键两端)+ 渠道白名单过滤(通关渠道只出专长/节点零专长)+ devour 货架保底退役 | | 4 | 95b66f0 | T5+T6a:门票入表(四字段 provisional+三计数+双注入+拿到即开三入口+断点扩列+shop.tres 门票行+A5/A6 断言)+ T6a 商店补 heal 行 + registry 刷新边界写死 | -| 5 | (T6b+T7+T8) | refresh 入池(B 案挤占 heal 5→3)+ registry pool_architecture/charge_defect 落文 + 批末重闸 | +| 5 | 5 | ccd95c8 | T6b+T7+缺陷修:refresh 入池(B 案)+ charge 登记 + autoplay 门票分支(MC timeout 形态批内实测修复) | ## 二、批末读数(T8 回填) | 闸 | 基线(413c936) | 批末 | 判定 | |---|---|---|---| -| 冒烟 smoke_all | 772/0 | 【T8_SMOKE】 | | -| run_all | 15/16(reward_overlay 红=M7 基线红) | 【T8_RUNALL】 | **红转绿(T2 适配)** | -| 行为探针四件+ui_struct | 全绿 | 【T8_PROBES】 | | -| doc 三闸+熵增闸 | 零新增基线 | 【T8_DOC】 | | -| MC n200 双策略(82000 窗) | 判式批改后锚(76000 系) | 【T8_MC】 | | +| 冒烟 smoke_all | 772/0 | **782/0**(+10=双渠道白名单 2+门票 A5/A6 行为断言 7+门票兑付受理 1) | | +| run_all | 15/16(reward_overlay 红=M7 基线红) | **16/16 全绿** | **红转绿(T2 适配,run_all 历史首次全绿)** | +| 行为探针四件+ui_struct | 全绿 | **五探针全绿**(ui_probe/ui_click/ui_input/ui_tune/ui_struct exit 全 0) | | +| doc 三闸+熵增闸 | 零新增基线 | lint 8文件2不通过=基线同款/term 218命中3疑似=基线同款/link 死链 7→修后 4(3 处系本批改名断链已修:奇物.md:16/:302 relics.tres→feats.tres+node-reward.md:4 bonus_pick_overlay→curio_pick_overlay;余 4=worktree 假象×2+既有 ux 债×2)/熵增闸 struct 违例×1=主区同款既有(archify design/diagrams 越界,归该批) | **零新增** || +| MC n200 双策略(82000 窗,route=min 零加成) | 无同窗基线(判式批锚=76000 系旧结构;主区 413c936 同窗 n20 对照跑) | normal 0.5%(1/200 通关)/perfect 77.5%(155/200);修 autoplay 门票分支后 n20 与主区基线逐位一致(normal 0 空终局/perfect 16 完 4 败);**读数只测不判**(渠道分离+门票改变奇物流,带判读归 M-4;锚换代=82000 系新锚 n200 日志留盘 user://logs/playtest_82000_*.jsonl) | | ## 三、验收断言(A1-A12 逐条) -- **A1 词族归零**:【A1_CENSUS】(保名单:_bonus_row/_bonus_value/_bonus_summary_text 等加成区呈现+bonus_summary+存档键六形态+kind 值 "relic"/身份前缀 "relic:"/relics_seed 进程内参数——穷举见 §五) +- **A1 词族归零**:A1 模式残留 **40 处全保名单**——_bonus_ 37 处(_bonus_row/_bonus_row_l/_bonus_summary_text/_bonus_value=结算加成行呈现面;_bonus_treasure_carry/_bonus_treasure_item/_bonus_treasure_pool=③批秘宝语义)+ node_relic_seen 局部变量 3 处(冒烟断言变量,指称 relic 令牌语义)。引号数据契约键全形态保留:bonus_deck×3/bonus_taken×3/bonus_pending×3/bonus_result×2/bonus_aux_carry×2/"bonus"×15(断点属性区键)/relics×6/relic×25(kind 值+身份前缀+摘要键)(保名单:_bonus_row/_bonus_value/_bonus_summary_text 等加成区呈现+bonus_summary+存档键六形态+kind 值 "relic"/身份前缀 "relic:"/relics_seed 进程内参数——穷举见 §五) - **A2 禁词未用**:reward_pick/reward_node/reward_preview 既有三处零新增(T2 全程未触) - **A3 兑付频率**:专长兑付点=通关三选一(_pending_next_level 置位链=关底 Boss 击败同一时刻);关内普通战斗节点零奇物(node_type=battle 无三选一调用,结构保证+冒烟既有断言面) - **A4 渠道分离**:_draw_curio_candidates(channel) 白名单过滤——通关渠道 60 次重抽全 relic/节点渠道 60 次零 relic(冒烟断言在案);货架白名单=shop_pool_rows 词表(feat_ticket 门票行+heal 回复行+四池种) @@ -53,7 +53,7 @@ ## 六、遗留与移交 -1. 【T8 07 登记】 +1. 07 清单登记(T8 已落 §⑰):①专长兑付时点目检(关底 Boss 击败那一刻=通关三选一,**不是战斗结算后**)+门票拿到即开目检+商店门票/回复行目检;②refresh 件入池后 heal 出现率感知归 T7 类节奏重测(跑批待批) 2. C1 玩家可见面违规改法+档位呈现三路 → 呈现批(06 独立条目) 3. C2 第四关+档二复裁+charge 补件 → 第四关内容批 4. C3 门票四参数终值(现 provisional 20%/2/1/5)→ 曲线调参批 From 33f18427a6392168e6b79bf9967e8d1058228403 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 5 Oct 2026 21:11:13 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E5=A5=87=E7=89=A9=E6=89=B9=E6=94=B6?= =?UTF-8?q?=E5=8F=A3=EF=BC=9Aroadmap=E5=8D=A1=E2=91=A1=E2=9C=85=E5=85=A8?= =?UTF-8?q?=E8=90=BD=E6=A0=87=E6=B3=A8=EF=BC=88=E6=8F=90=E4=BA=A4=E9=93=BE?= =?UTF-8?q?+=E8=AF=BB=E6=95=B0+=E5=9B=9B=E7=A7=BB=E4=BA=A4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- roadmap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roadmap.md b/roadmap.md index 6034bc4a..353ec341 100644 --- a/roadmap.md +++ b/roadmap.md @@ -61,7 +61,7 @@ | 迭代 13 | **工程债与测试基建批(含 D6 爆破工兵包实施)**:六条目=ui_click_probe 完整局适配(D1——分派链序修正+停点/锁期/重放三态断言)/探针只读出口(D2——六出口 26 直入清零)/ArmedTimer helper(D3——P4 乙三处计时收口缝零动)/main.gd 二期瘦身(D4——bonus_interact_controller+camp_shop_presenter 拆件 1370→1119,家族②契约3绑定面留置;~900±50 锚差=族级清单上限不自洽已归因)/aux 二期合并(T5 判不合收口)/杂项尾差四件(D5)/爆破工兵包中立移除雷格(D6——P1 甲邻接冻结/P2 甲退色降档,判定逻辑零改动,充能表驱动零新码)。**✅ 已落籍(2026-10-04,台账 342)**:worktree m4iter13-debt-20261004 八提交 cd7d3ea…2402877 合并 eff8cb8;闸=冒烟 718/0(断言数持平基线)+run_all 15+click 三种子绿+全探针绿+doc 三闸零新增+熵增 PASS+MC 零跑(静态零漂移证明);合并后主区全量复跑同绿 | 迭代 12 出口 | T0-T9 ≈4.8-5.3d(知情超限单批,实际单会话) | | 迭代 14 | **初始展开收窄批(2026-10-04 用户拍板甲乙混合立项)**:甲=先布后删剃雷域收窄至出生 3×3∪外扩 r 环(参数化半径,现值 r=1——开阔地均值从板面 66.5% 根因性降至约三成)+乙=生成重掷闸 R₀≤cap(合法性门序列、cap=板面 30% 锚〔10×10=30/12×12=43/13×13=51〕、重试耗尽走既有三分出口);**玩法侧零改动路线**(排雷玩法「初始展开恰等」验收原样为真,minesweeper_rules.gd 零 diff);裸数字截断路否决退役(可见 0 格+未开邻居=扫雷不可能态)。可行性报告=`design/research/研究-初始展开收窄可行性-2026-10-04.md`,任务书=`design/research/任务书-初始展开收窄批-2026-10-04.md`(T0-T8≈5.25d,读数批 T5-T6 可切独立后批;拍板点 P1-P3 全读数后置;停机点 S1 相变警戒/S2 带越界回调)。**✅ 实施批落地(2026-10-05,worktree m4iter14-narrow-20261005 台账 357;S1 停机呈报已裁=用户方案A)**:T0-T4+T7+T8 全落(T5 结构读数随批出讫;T6 MC 四带复算读数批按切分预案后置,跑批待用户批准挂 07 现行义务)——甲=剃雷域收窄(shave_ring_radius 参数化,r=1→出生 5×5,全域 flood 剃雷退役,零 RNG 消费变化)+乙=R₀≤cap 重掷闸(合法性闸序列+同流重试+耗尽三分出口诚实降级+r0_cap_rejected 指标)+参数面两列(schema 域校验加载期致命+19 行投值)+断言面 +12(R₀≤cap/ring_removed 量级/cap 压线诚实降级/确定性逐位)+夹具复核 3 处(relic 候选身份按 TP0 v2.2=kind|tier|relic_id、商店购买笔数推导防 GP 流漂移、极性探针种子自愈);**S1 停机实录**=r=1 自然 R₀ 均值 39>cap30 锚结构性倒挂(94% 拒绝/inf 0.976→0.719/常态非法板)→用户裁方案A「10×10 cap 30→45」〔P1 上调预案字面〕,12×12/13×13 健康侧维持;改后结构读数=R₀ 均值 39.7/37.9/42.8/46.6(收窄自 68.6/83.2——10×10 −42%、12×12 −54%)/尾部闸精确截断/雷密度 19.2-21.6%<25% 相变线/inf 全行达标;玩法侧零改动(minesweeper_rules.gd 零 diff、「初始展开恰等」验收原样为真);闸=冒烟 767/0(+12);registry initial_expansion_narrowing 条+棋盘生成.md v0.5+07⑬ 四信号组;06 视野条目已于 745b92ea 清账批退役(收窄立项即正主)。**②挖掘风险不等式判式换代批(2026-10-04 用户裁编入本迭代,作为迭代内需求)**:判式三层换代(条款2 标中旗锚/条款3 聚合健康式「(1−ρ̂)E_dig−ρ̂V_mine≥0」/旗边际引理+条件二旗溢价)+β 比值带判读 [1.2,2.5](u=3 登记态)+标中兑付溢价 δ=1.25(形态 a 兑付公式一处·雷安同池冻结不动)+runsum 旗行为五字段+audit 工具常量刷新(池 77/直伤 6-8-8/AP 8);裁决全落零新拍板点(方案报告 §九(已退役,git 1da9a41)),任务书=`.zcode/plans/mine-inequality-revision-miniloop-plan-2026-10-04.md`(T0-T5≈2.5d;条件二 T3 落文按登记已知破·T4 机制转绿;契约红线=DamageController 零改/直伤雷 6-8-8 与 p_perfect 不动——iter8 缺口四选一解绑独立在案;开工门=U0)。**✅ 实施批落地(2026-10-05,worktree m4iter14-mineq-20261005 台账 356)**:T0-T5 全落——audit 工具常量刷新(改前静态锚与方案报告 §四 逐位吻合)+runsum 五字段(行侧判据考证=授予行专带 highlight,87行==87插旗1:1)+F1 条款3 重立三层口径(v0.8)+战斗结算 v0.25+难度联动表+6 行+registry clause3_inequality 重立条(改前锚+P7 修正注记)+06 破线条收口+**T4 标中兑付溢价机制**(settlement.flag_hit_premium=125 provisional,grant 单点只挂 flag_hit 正向·percent 幅度贯穿应用与呈现,标空/挖格不乘,DamageController 零改/同池冻结)+smoke 750/0(+3 断言;max 路径种子 42→43 重采);判读三件=β 带 u=1/2 带内 u=3 登记态/δ 敏感度行为无感(η 位移 <1.4pp<5pp,保留+登记)/条件二判式口径等号压线转绿(整数量级实现 u=1/2 绿·u=3 微欠 −1.1% 登记面);改后锚换代 runsum_m4iter14mineq_post_n200_20261005(perfect 冒险通关 65.3%→72.0% 增强向位移,四带判读归 M-4);出口报告=design/qa/m4iter14-mineq-exit-report-2026-10-05.md(provisional 追认单 8 项+07⑫ 人工项);damage_probe A1 红灯=改前既有非本批新增(在册)。**③全局作用域变异首发批(2026-10-04 用户拍板立项——调研报告 P1-P8+盘问 Q1-Q4 全按推荐案落,批内序位待排)**:L2/L3 Boss 图恒定一型 scope=global 变异,首发型=**案A 资源面·微贫向偏斜**(正向格面池合计不变、进攻族 attack/blast/pierce/magic 权重上浮/生存族 heal/defense 明显下调、EV 微降 3-5% provisional、gp/action 白名单不动;v1.0「沃土+雷密对冲」形勘误作废——雷数为生成器事实、布置段不可加,改生成端=踩结构面否决理由);**替换制**(Boss 图不再布 region 片——L2 完美 Boss 承伤 55.8% 已越带不叠加;迭代 12 四件地形应对件在 Boss 图失效=知情代价,registry/调参日志随批登记);L1 Boss 免疫(带B [50,85] 教学保护);三层预告(开战横幅+HUD 常驻角标+帮助手册行);带A 判读后置 M-4(落地 off/on 对拍只测不判;off 基线=替换制后零变异 Boss 图,与现行带 region 片 Boss 图语义不同须注明);偏运硬币合成序=图级先件级后(代拍随批锚登记)。调研报告(事实源,v1.1 裁决落盘)=`design/research/调研-全局作用域变异设计空间-2026-10-04.md`,任务书已退役(git 01bfc15 批;裁决全录另见调研报告 §7)(T0-T8≈5.75d,读数批 T7 可切独立后批→主体≈5.25d;零新拍板点·provisional 追认单随批;停机点 S1 普通图逐位漂移/S2 读数越带只呈报/S3 互锁误伤;开工门=U0;**⑥A4 批开工门含本批出口——本批落地图级偏斜投影点为⑥合成链中段**〔2026-10-05 注:⑥已移出迭代 14 挂起,此依赖句冻结待其重启〕)。与①②互不撞(收窄=普通图初始展开轴/判式=不等式判读口径/全局变异=Boss 图资源面轴),出口读数各自分开归因。**✅ 实施批落地(2026-10-04,worktree m4iter14-gvar-20261004 台账 345)**:T1-T8 实施批全落(T7b MC 读数批按切分预案切独立后批,跑批待用户批准)——skewed_veins 贫矿脉行+互锁三条+effect_value [0,50]/门控第三维节点身份/替换制+兜底回落/装板序全局决议前置(数据依赖调和,出口报告在册)/资源面偏斜投影(整数最大余数守恒 77+合成序图级先件级后)/三层预告三层全接/七正主落文+06 收口;闸=冒烟 747/0(+29)+run_all 15+golden 普通图逐位 12/12 全等+六探针+三闸熵增终录见批报告 design/qa/m4iter14-gvar-exit-report-2026-10-04.md(Q3 四件应对件知情代价登记+静态推演+provisional 追认单+人闸目检清单);**③出口成立——⑥A4 开工门解一半**(另半=②出口);off/on 双档读数待 T7b(带 A 判读归 M-4 只测不判)。**④技能池执行批(2026-10-04 用户点名入列——调研+四裁+任务书全齐,06 Alpha 验证条目随之收口)**:文档五件(敌人系统.md 池构成表补 L2/L3 五敌种行/护甲列改读表口径/Boss 三代错峰句/意图呈现节补解锁弹出规格/版本 v0.13+EV 锚整数口径重登记+enemy.tres 批注消歧)+**新机制一件=Boss 半血解锁新独占伤害技**(三 Boss 血量 ≤50%〔用户口径定死〕解锁:酋长「震地怒击」类 ×2.5/CD4·卓尔遏毒类 ×2.0/CD3·石君王「岩崩」类 ×2.5/CD4——参考档即工作值,命名 T5 定稿、倍率冷却 provisional 归调参;enemy.tres 池条目扩 unlock_hp_pct 键〔int [1,99] 缺省无=无门槛〕;未解锁条目双面过滤不进摇号与保底集合;解锁出一次归因明细行「XX 解锁了 YY」);首拍加权取消(杂鱼纹理注销——巨蜘蛛 52% 读数已被 Package B 抬血自愈、地精两行终裁不加工);呈现=明细行+既有类型级预告+解锁时技能名一次性弹出(零动画资产、不改演出);裁决 D1-D8 零待裁;任务书=`design/research/任务书-技能池执行批-2026-10-04.md`(T0-T7≈4.25d,切分预案在册)——开工=用户发令升计划书。**✅ 实施批落地(2026-10-05,worktree m4iter14-skillpool-20261005 台账 358;goal 开工令 U0)**:T0-T6+⑤ 全落(T7 读数批后置挂 07 现行义务,跑批待用户批准)——T0 表盘点零漂移;T1 敌人系统.md v0.13 五件全落;T2 skills.tres 有效倍率表批注〔D4 整数基线半点矩阵〕+enemy.tres EV 锚整数口径重登记+巨蜘蛛 1.11 消歧〔=atk1 口径非混用〕+registry enemy/skill 条目换代;T3-T4 机制件=unlock_hp_pct〔1,99] 可选键(schema 域校验,缺省无=无门槛)+容器闩 check_skill_unlocks(单向闩零随机)+双面过滤(保底不逼锁定)+编排归因行「XX 解锁了 YY」〔type=highlight 高光键 skill_unlock 零行型新增〕+呈现一次性技能名 toast〔排除 perfect_sweep 误触〕;T5 三 Boss 解锁技落表(震地怒击 ×2.5/CD4·遏毒 ×2.0/CD3·岩崩 ×2.5/CD4 案甲 provisional+池尾 weight2/unlock50)+断言基线换代 3 处〔技能表 12 行/池模板+门控条目/酋长 EV 锚重锚 1.10-1.30 实测 1.1948〕;T6 单测 skill_unlock_test 22 断言(run_all 15 套)+smoke +5 集成(半血→次轮解锁行恰一次/文本/行型零新增);闸=冒烟 772/0+run_all 15+探针绿+doc 三闸零新增(reward_overlay_test 红=master 既有在册)。**⑤✅ 提灯 +2 接线落地(同批顺路)**:board_view.set_fog_lantern 公共接线面(半径算式迷雾批已在位;载体道具仍归 Alpha 信息件重定批=接缝就绪态)+ui_input_probe case⑮ 接线断言+HUD框架 注记**⑤魔法提灯 +2 效果接线(小卡)**:零调研直入——2026-09-30 拍板④已裁(灯甲视野外扩 +2 格、类型微光不随视野扩),迷雾批已留常量 LIGHT_RADIUS_LANTERN_BONUS,纯接线+断言估 0.5d 内。原候选卡注记终态(2026-10-04):敌人系统内容批=已具化为④;教学引导定稿批=用户裁改排 Beta(06 教学节头注记在案),移出本迭代候选。**⑥冒险前资源配比预设批——已移出本迭代(2026-10-05 用户令挂起:暂不做;条目保留 06-待办清单「冒险前资源配比预设(构筑提案)」〔已裁待实施→挂起〕,八项裁决全数有效、任务书冻结保留=design/research/任务书-冒险前资源配比预设批-2026-10-04.md;重启须用户另行发令,届时开工门仍=②③号卡出口〔若②③已落籍 master 则自动解——合成序与包络数值锚依赖②判式换代与③变异投影,三者共用摇号投影点〕)**。 | 迭代 13 出口(**迭代 13 已落籍 2026-10-04 eff8cb8**,台账 342) | 初始展开批 T0-T8(可两段切分)+判式换代批 T0-T5≈2.5d(独立 worktree 合并)+全局变异首发批 T0-T8≈5.75d(读数批可切,独立 worktree 合并)+技能池执行批 T0-T7≈4.25d(独立 worktree 合并)+提灯接线小卡 0.5d(可并入任一批顺路)〔⑥配比预设批 A4 已移出本迭代挂起,2026-10-05 用户令〕 |**③全局作用域变异首发批(2026-10-04 用户拍板立项——调研报告 P1-P8+盘问 Q1-Q4 全按推荐案落,批内序位待排)**:L2/L3 Boss 图恒定一型 scope=global 变异,首发型=**案A 资源面·微贫向偏斜**(正向格面池合计不变、进攻族 attack/blast/pierce/magic 权重上浮/生存族 heal/defense 明显下调、EV 微降 3-5% provisional、gp/action 白名单不动;v1.0「沃土+雷密对冲」形勘误作废——雷数为生成器事实、布置段不可加,改生成端=踩结构面否决理由);**替换制**(Boss 图不再布 region 片——L2 完美 Boss 承伤 55.8% 已越带不叠加;迭代 12 四件地形应对件在 Boss 图失效=知情代价,registry/调参日志随批登记);L1 Boss 免疫(带B [50,85] 教学保护);三层预告(开战横幅+HUD 常驻角标+帮助手册行);带A 判读后置 M-4(落地 off/on 对拍只测不判;off 基线=替换制后零变异 Boss 图,与现行带 region 片 Boss 图语义不同须注明);偏运硬币合成序=图级先件级后(代拍随批锚登记)。调研报告(事实源,v1.1 裁决落盘)=`design/research/调研-全局作用域变异设计空间-2026-10-04.md`,任务书已退役(git 01bfc15 批;裁决全录另见调研报告 §7)(T0-T8≈5.75d,读数批 T7 可切独立后批→主体≈5.25d;零新拍板点·provisional 追认单随批;停机点 S1 普通图逐位漂移/S2 读数越带只呈报/S3 互锁误伤;开工门=U0;**⑥A4 批开工门含本批出口——本批落地图级偏斜投影点为⑥合成链中段**〔2026-10-05 注:⑥已移出迭代 14 挂起,此依赖句冻结待其重启〕)。与①②互不撞(收窄=普通图初始展开轴/判式=不等式判读口径/全局变异=Boss 图资源面轴),出口读数各自分开归因。**✅ 实施批落地(2026-10-04,worktree m4iter14-gvar-20261004 台账 345)**:T1-T8 实施批全落(T7b MC 读数批按切分预案切独立后批,跑批待用户批准)——skewed_veins 贫矿脉行+互锁三条+effect_value [0,50]/门控第三维节点身份/替换制+兜底回落/装板序全局决议前置(数据依赖调和,出口报告在册)/资源面偏斜投影(整数最大余数守恒 77+合成序图级先件级后)/三层预告三层全接/七正主落文+06 收口;闸=冒烟 747/0(+29)+run_all 15+golden 普通图逐位 12/12 全等+六探针+三闸熵增终录见批报告 design/qa/m4iter14-gvar-exit-report-2026-10-04.md(Q3 四件应对件知情代价登记+静态推演+provisional 追认单+人闸目检清单);**③出口成立——⑥A4 开工门解一半**(另半=②出口);off/on 双档读数待 T7b(带 A 判读归 M-4 只测不判)。**④技能池执行批(2026-10-04 用户点名入列——调研+四裁+任务书全齐,06 Alpha 验证条目随之收口)**:文档五件(敌人系统.md 池构成表补 L2/L3 五敌种行/护甲列改读表口径/Boss 三代错峰句/意图呈现节补解锁弹出规格/版本 v0.13+EV 锚整数口径重登记+enemy.tres 批注消歧)+**新机制一件=Boss 半血解锁新独占伤害技**(三 Boss 血量 ≤50%〔用户口径定死〕解锁:酋长「震地怒击」类 ×2.5/CD4·卓尔遏毒类 ×2.0/CD3·石君王「岩崩」类 ×2.5/CD4——参考档即工作值,命名 T5 定稿、倍率冷却 provisional 归调参;enemy.tres 池条目扩 unlock_hp_pct 键〔int [1,99] 缺省无=无门槛〕;未解锁条目双面过滤不进摇号与保底集合;解锁出一次归因明细行「XX 解锁了 YY」);首拍加权取消(杂鱼纹理注销——巨蜘蛛 52% 读数已被 Package B 抬血自愈、地精两行终裁不加工);呈现=明细行+既有类型级预告+解锁时技能名一次性弹出(零动画资产、不改演出);裁决 D1-D8 零待裁;任务书=`design/research/任务书-技能池执行批-2026-10-04.md`(T0-T7≈4.25d,切分预案在册)——开工=用户发令升计划书。**⑤魔法提灯 +2 效果接线(小卡)**:零调研直入——2026-09-30 拍板④已裁(灯甲视野外扩 +2 格、类型微光不随视野扩),迷雾批已留常量 LIGHT_RADIUS_LANTERN_BONUS,纯接线+断言估 0.5d 内。原候选卡注记终态(2026-10-04):敌人系统内容批=已具化为④;教学引导定稿批=用户裁改排 Beta(06 教学节头注记在案),移出本迭代候选。**⑥冒险前资源配比预设批——已移出本迭代(2026-10-05 用户令挂起:暂不做;条目保留 06-待办清单「冒险前资源配比预设(构筑提案)」〔已裁待实施→挂起〕,八项裁决全数有效、任务书冻结保留=design/research/任务书-冒险前资源配比预设批-2026-10-04.md;重启须用户另行发令,届时开工门仍=②③号卡出口〔若②③已落籍 master 则自动解——合成序与包络数值锚依赖②判式换代与③变异投影,三者共用摇号投影点〕)**。 | 迭代 13 出口(**迭代 13 已落籍 2026-10-04 eff8cb8**,台账 342) | 初始展开批 T0-T8(可两段切分)+判式换代批 T0-T5≈2.5d(独立 worktree 合并)+全局变异首发批 T0-T8≈5.75d(读数批可切,独立 worktree 合并)+技能池执行批 T0-T7≈4.25d(独立 worktree 合并)+提灯接线小卡 0.5d(可并入任一批顺路)〔⑥配比预设批 A4 已移出本迭代挂起,2026-10-05 用户令〕 | | 迭代 15 | **①调试功能整合批(2026-10-05 用户令自「14.5」临时卡改编入册——原周期外 0.5 卡,落地事实与验证读数零变化,只改编号归属)**:主菜单三调试散件(随机种子输入框/角色数值调整板/两层调试入口)收一张调试浮窗 DebugOverlay——入口=开始覆盖层右下角「调试」角标(与主按钮组非平级,DS_DEBUG=1 环境变量门控沿用迭代 8 卡④口径);浮窗=悬浮菜单之上 Dialog(不隐藏开始层,同确认弹窗形态;旧 TunePanel「与开始层互斥」形态与类整体吸收退役含 .uid);可扩展滚动列表页四区块(随机种子/数值调整/批量模拟/两层调试入口——后续调试项往下追加滚轮下探)。四拍板全落(2026-10-04 用户裁)=P1 甲 DS_DEBUG 门控/P2 乙全摊平/P3 仅开始菜单可开·对局不可达(_show_debug_overlay 门禁护栏+对局发起随开随收)/P4 可扩展列表。**✅ 已全批落籍(2026-10-05,worktree iter145-debug-hub 两提交 48c0b34/46dadbb,主区 merge --no-ff d0a3c6b/10c301b,台账 352)**:闸=冒烟 747/0+探针 ui_tune/ui_click/ui 三绿(worktree+主区双跑,主区跑前 --import 重刷类名缓存在册坑);遗留=实机目检归用户(DS_DEBUG=1 启动→右下角角标→浮窗四区块→两层调试入口) | 卡①门:迭代 14 出口(注:本卡先于迭代 14 出口落籍——编入时点迭代 14 ③已闭/①②④⑤在飞/⑥挂起,门列仅记编入时点状态,不构成追溯闸) | 单批已落地(原估 0.5d,实际一次会话);后续扩调试项=debug_overlay.gd _build() 列表区追加区块(P4 扩展口径) | -| 迭代 15 | **②奇物概念收口与词族对齐批(2026-10-05 用户令编入本迭代)**:概念层已由用户逐轮裁定定稿(《奇物》v1.0→v2.7,7 轮),本批只做**落地对齐**、不做二次设计。三批内容:**①三套旧词族全面对齐**(顶层已改名「奇物」而代码仍叫旧名=用户最初抱怨的"文档与代码分开"):`feature_*`→`curio_*`(103 处/7 文件)、`relic_*`→`feat_*`(162 处/12 文件)、运行层 `bonus_*`→`curio_*`(160 处/8 文件)。**硬约束:`reward_*` 已被 `reward_pick`/`reward_node`/`reward_preview` 占用,统一词族不得使用**;`relic_table.gd` 与两个控制器类改名须同步 Godot `.uid`/`.tscn`(非文本层无法自证)。**②三条渠道按分类分离**(现行三渠道共用同一个 98 张池、三类混编):关底 Boss 击败=通过这一关→只出专长;奖励节点/商店→属性值提升+资源、专长在此**只以门票形式**出现。**兑付频率红线**:奇物不是打一场战斗给一份,关内普通战斗节点打完不给任何奇物,一关之内只兑付一次(用户 2026-10-05 纠错)。**③两个新种类入池**=刷新次数(须先与"商店独立刷新机制"划清边界)+ 专长获取门票(拿到即开不可囤积/罕见只在概率/一局限购一次/计步保底:连续 5 次三选一展示未出→第 6 次必出,刷新重展计入、奖励节点与商店共享计数);附带 `shop.tres` 补回复(默认原则要求分类内成员都在该出现的渠道池里)与 charge 标签补至 ≥4 件(现仅 3 件,档二永远达不到——数据缺陷)。设计事实源=`design/gdd/systems/冒险循环/奇物.md` v2.7,任务书=`design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md`(T0-T8≈4.5d,两段切分预案在册〔段一词族 T0-T3≈2d/段二渠道+入表 T4-T8≈2.5d〕,切分点=T3 出口;11 条设计契约含 6 条 🔴 停机级;12 条批末验收断言;代拍板白名单 5 项;待用户裁点 4 项〔玩家可见面违规/第四关档二复裁/门票四参数终值/存档键正式迁移〕)。**开工门**:与在途 M-4 各批(判式换代/全局变异/配比预设)撞同一批文件(`adventure_table.gd`/`level_runner.gd`/`adventure.tres`),须确认不并行或用户明示插队 | 卡②门:迭代 14 出口 | 卡①已落籍(原估 0.5d);卡②=**文档+数据层落籍(c571af2),渠道分离与入池实施段+curio 换名回归迭代 15 继续完成(2026-10-05 用户令)**——原估 T0-T8≈4.5d(可两段切分:词族对齐段 + 渠道入表段);**迭代 15 整行保持开放不标完成** | +| 迭代 15 | **②奇物概念收口与词族对齐批(2026-10-05 用户令编入本迭代)**:概念层已由用户逐轮裁定定稿(《奇物》v1.0→v2.7,7 轮),本批只做**落地对齐**、不做二次设计。三批内容:**①三套旧词族全面对齐**(顶层已改名「奇物」而代码仍叫旧名=用户最初抱怨的"文档与代码分开"):`feature_*`→`curio_*`(103 处/7 文件)、`relic_*`→`feat_*`(162 处/12 文件)、运行层 `bonus_*`→`curio_*`(160 处/8 文件)。**硬约束:`reward_*` 已被 `reward_pick`/`reward_node`/`reward_preview` 占用,统一词族不得使用**;`relic_table.gd` 与两个控制器类改名须同步 Godot `.uid`/`.tscn`(非文本层无法自证)。**②三条渠道按分类分离**(现行三渠道共用同一个 98 张池、三类混编):关底 Boss 击败=通过这一关→只出专长;奖励节点/商店→属性值提升+资源、专长在此**只以门票形式**出现。**兑付频率红线**:奇物不是打一场战斗给一份,关内普通战斗节点打完不给任何奇物,一关之内只兑付一次(用户 2026-10-05 纠错)。**③两个新种类入池**=刷新次数(须先与"商店独立刷新机制"划清边界)+ 专长获取门票(拿到即开不可囤积/罕见只在概率/一局限购一次/计步保底:连续 5 次三选一展示未出→第 6 次必出,刷新重展计入、奖励节点与商店共享计数);附带 `shop.tres` 补回复(默认原则要求分类内成员都在该出现的渠道池里)与 charge 标签补至 ≥4 件(现仅 3 件,档二永远达不到——数据缺陷)。设计事实源=`design/gdd/systems/冒险循环/奇物.md` v2.7,任务书=`design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md`(T0-T8≈4.5d,两段切分预案在册〔段一词族 T0-T3≈2d/段二渠道+入表 T4-T8≈2.5d〕,切分点=T3 出口;11 条设计契约含 6 条 🔴 停机级;12 条批末验收断言;代拍板白名单 5 项;待用户裁点 4 项〔玩家可见面违规/第四关档二复裁/门票四参数终值/存档键正式迁移〕)。**开工门**:与在途 M-4 各批(判式换代/全局变异/配比预设)撞同一批文件(`adventure_table.gd`/`level_runner.gd`/`adventure.tres`),须确认不并行或用户明示插队 | 卡②门:迭代 14 出口 | 卡①已落籍(原估 0.5d);卡② ✅ **已全批落籍(2026-10-05,worktree curio-wordfamily 七提交 d4cde73..049aa62+master 同步 23547b5,主区 merge 见台账;出口报告=[iter15-2-curio-exit-report](design/qa/iter15-2-curio-exit-report-2026-10-05.md))**:三套词族代码层全换(feature_→curio_ 93 处/bonus_ 运行层→curio_/relic_→feat_——feat_table.gd/feats.tres/feats_engine.gd 改名);渠道白名单分离(总池-渠道子集多级缓存形=用户裁);门票入表(四参数 provisional 20%/2/1/5+拿到即开+计步保底+限购 1+节点商店共享计数);refresh 入池(B 案 heal 5→3 挤占)+shop.tres 补 heal 行+门票行;T2 裁点甲案=六存档键禁改(含任务书漏登记的 "relics" 键);charge 缺陷登记不硬补;autoplay 门票分支缺陷批内实测修复(MC timeout 形态);闸=冒烟 782/0(+10)+run_all 16 套历史首次全绿(reward_overlay 红转绿=M7 bcard 成因修复)+五探针绿+doc 三闸零新增+MC n200 只测不判(normal 0.5%/perfect 77.5%);遗留=07⑰ 目检归用户+C1 呈现批/C2 第四关批/C3 调参批/C4 存档批四移交。原估 T0-T8≈4.5d(实际一次会话);**迭代 15 整行保持开放不标完成** | | 迭代 15 | **③秘宝代码词族对齐批(2026-10-05 用户令编入本迭代)**:设计侧已完成(消歧更名批 `430419c`+`06e94b7`,26 个设计文档「魔法道具」→「**秘宝**」,同调位机制名保留不变),本批只做**代码侧对齐**、不做二次设计。内容:`aux_*`/`AuxItem*` 词族全族换词 **358 处 / 36 文件**(重灾 `adventure_runner.gd`79 / `level_runner.gd`27 / `bonus_interact_controller.gd`19 / `battle_orchestrator.gd`18 / `config_db.gd`14 / `main.gd`13),分五组落:**A 玩家可见文案(P0,5 处)**=`battle_narrator.gd:117-118` + `bonus_interact_controller.gd:76` + `hero_info_panel_view.gd:56`(槽位标题 `UiKit.label`)+ `:178`(tooltip)· B schema 与数据表(`aux_item_table.gd`/`aux_items.tres`/`aux_items.gd` 文件名+类名+`ConfigDB` 持有变量与访问器全链跟改,**须同步 Godot `.uid`/`.tscn` + `--import` 重刷类名缓存**——迭代 14.5 实证在册坑)· C 文档链接修复(含 `architecture.md` 两处既有断债顺带清偿)· D 中文注释层(**须分色**:`魔法矢`/`魔法反噬`/`魔法族`/`魔法护甲` 属敌方伤害护甲语汇🔴不动)· E 测试文件名+内部变量。**用户三项拍板**:全族彻底改(含数据表)· **断点存档键一律保留**(`bonus_aux_carry`/`aux_detail`/`aux_cleared`/`aux_cleared_last`/`aux_items` 断点数组/`attunement_charges` 共 34 处引用一字不改,破档即进度丢失)· registry 条目键 `aux_items` 不改(数据契约键)。设计事实源=`design/gdd/systems/排雷循环/秘宝.md` v0.9,任务书=`design/research/任务书-秘宝代码词族对齐批-2026-10-05.md`(T0-T9≈5.0d,**超软盒 3 日上限·知情预挂**:T7 段末闸后 L3 呈报切点不擅切;**三档降级面 G1/G2/G3 在册**〔G3 下限=仅组 A 完成〕;5 条设计契约含 5 条 🔴 停机级〔存档兼容零破坏/数值零漂移/schema 契约面零变更/零字段更名/核心层零 await〕;代拍板白名单 5 项;**C1/C2 已裁关闭·全改保一致·待裁点 0 项**)。**撞批已消解**:② 奇物批 2026-10-05 收口落库(合并 `c571af2`),并行风险解除,本批为迭代 15 唯一在途需求;**T0 须重取基线**(判式换代批 T5 读数 750/0、② 又动数据层,§1.2 的 747 为②前旧数禁沿用)并复核事实表。**✅ 裁决(用户 2026-10-05「全改保一致」)**:C1 `"aux_item"` 域串**照改**(实测连锁面收窄——`reward.tres` 已零 aux_item 数据行,池种随迭代 11 T7 退役;16 处字面量全在代码内闭合无存档依赖;唯一连锁项=`smoke_all.gd:3932` 断言须同步)· C2 `aux_items` 一名两义**拆分**(ConfigDB 持有变量+表注册键✅改 / 断点存档键+冒烟夹具🔴禁改——**「键名禁改」与「变量名禁改」是两回事**,禁改的只是落盘字面量)。新增**组 F**(分派语汇与域串 16 处),任务卡扩为 T0-T10≈5.5d。**开工门**:✅ 已无待裁点,可直接发 T0 | 卡③门:迭代 14 出口(**裁点已全决·可开工**) | 卡①已落籍(原估 0.5d);卡②文档+数据层落籍(`c571af2`,实施段回归迭代 15 在飞——2026-10-05 用户令);**卡③ ✅ 已全批落籍(2026-10-05,worktree m4iter15-mibao 九提交 b4b8bb3..收口笔,主区 merge --no-ff 哈希见台账/active.md 快照行;出口报告=[iter15-3-mibao-exit-report](design/qa/iter15-3-mibao-exit-report-2026-10-05.md))**:词族实数 364 处/36 文件全换尽(例外 43+2 处=存档键引号字面量+夹具,穷举在报告 §五);闸=冒烟 755/0+run_all 14/15(唯一红=②批遗留基线红 reward_overlay,对照零新增)+行为探针四件全绿+doc 三闸/熵增闸零新增+存档键六键普查零差异+MC 按 goal 令豁免(静态零漂移证明代);渲染像素五闸屏蔽挂账 07⑮;遗留=实机目检归用户(07⑮)+reward_overlay 基线红归②批处置 | | 迭代 16 | **①敌人表行集多行批(2026-10-05 用户令编入本迭代,纯功能与数值轴四卡之一)**:放开敌人表「同一(关卡序号 × 节点深度)行集恰一敌种」加载期校验(enemy_table.gd:108-112 考证在册),允许同场遭遇混编多种敌人——生成(unit_container spawn 行集受理)/编排(battle_orchestrator/narrator)/断言面全链接通 + GDD 结构冻结面修订先行(敌人系统.md:75/:263)。事实源=06「同深度第二敌种与精英/Boss 编码位」条剩余面(前半已由关卡节点地图 v1.0 消解);前置=技能池实施批已落籍(d3fa887)开工门已解。任务书=`design/research/任务书-敌人表行集多行批-2026-10-05.md`(T0-T8≈4.25d 含条件卡 T5 首行投放/3.75d 不含;🔴 契约 5 条含零内容期逐位零漂移;代拍板白名单 3 项;**待用户裁点 4 项**〔P1 混编数量语义三案/P2 行集解析双算法处置〔工作日志 312 既有待裁随批〕/P3 本批是否投首个混编行/P4 Boss 行是否放开〕——裁点裁决后 U0 开工) | 迭代 15 出口 | T0-T8≈4.25d(超软盒预挂,T4 末切分点) | | 迭代 16 | **②M4 数值批(2026-10-05 用户令编入本迭代)**:四件数值欠账合一批——①削弱档位终值(weaken 现值 −65/duration 2/refresh 考证在册;叠层改续时语义已定案,差 duration 终值+当量锚复算)②调参批数值点评(06 条目现行登记五条——任务口径「八条」与条目五条不符,任务书 §2.2 已呈报差异以条目为准)③迭代 10 奖励重构联动欠账①②(钥石产出重标 boss_keystone 首杀 3/3/3·复杀 2/2/3 provisional + 通关率带重扫〔通关回血 20%+AP 件首轮 +1〕)④元进度定价剩余面(forge base 20/比 150 考证在册——06 条目文本「base 10」滞后于 2026-09-22 对齿批已呈报;Boss 两档终值)。读数义务随批=迭代 14 两件后置复算(T6 MC 四带复算+T7 技能池节奏重测,均挂 07);顺路修复 damage_probe A1 改前既有红灯(判式换代批出口报告 §四.7 在册)。**硬约束:一切 MC/verifier 跑批发起前须用户批准;调参联动禁独立调参网络(β 带 [1.2,2.5] 逐 u 检算)**。任务书=`design/research/任务书-M4数值批-2026-10-05.md`(T0-T8≈6.0d,天然切分点 T4 末;🔴 契约 5 条含跑批批准制;**待用户裁点 4 项**〔P1 削弱终值三案推算后裁/P2 点评不对称裁决方向/P3 钥石产出终值/P4 工坊直购价立否〕) | 迭代 15 出口 | T0-T8≈6.0d(超软盒预挂,段一 T0-T4≈4.25d/段二 T5-T8≈2.0d) |