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] =?UTF-8?q?=E5=A5=87=E7=89=A9=E6=89=B9T3+T4=EF=BC=9Arelic?= =?UTF-8?q?=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),