奇物批T3+T4:relic_*→feat_*词族全换(feat_table.gd/feats.tres/feats_engine.gd三文件改名+.uid+feat_flags/feat_id/feat_row等全token+config_db注册键feats两端+entities.yaml换代注记——kind值relic/身份前缀relic:/relics存档键/摘要键按数据契约保留);T4渠道白名单过滤(_draw_curio_candidates(channel):通关渠道只出专长/节点渠道零专长——过滤式不拆池,TIER_QUOTAS锚零改动,devour货架保底随渠道定位退役,迁移保feat_id,pick_curio测试feat_id透传;冒烟773/0+run_all 16全绿+断点往返PASS)
This commit is contained in:
1 parent
8e2694de49
commit
6812ebbb73
24 files changed
+385
-384
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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), # 道具件跨关携带(注入前中断防静默丢失)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 取数口)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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),
|
||||
File renamed without changes.
+13
-30
@@ -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),
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
class_name AdventureTable
|
||||
extends Resource
|
||||
|
||||
## 概念指路:本表的属性值提升与资源,与 relics.tres 的专长,同属一个对象——
|
||||
## 概念指路:本表的属性值提升与资源,与 feats.tres 的专长,同属一个对象——
|
||||
## 「奇物」(一次冒险内各渠道给予玩家的增益总称)。定义见 design/gdd/systems/冒险循环/奇物.md。
|
||||
## 它们是它的分类轴上的枚举(专长 / 属性值提升 / 资源),不是三套系统;
|
||||
## 本表承载属性值提升与资源的种类配额与件值。
|
||||
|
||||
@@ -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 {}
|
||||
File renamed without changes.
@@ -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=彩蛋觉醒)/
|
||||
|
||||
@@ -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 /
|
||||
@@ -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选择一件带走:"
|
||||
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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 "散件")
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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+体感短语)——空数组 = 整节隐藏
|
||||
|
||||
@@ -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 同路径) ----
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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:
|
||||
|
||||
+62
-56
@@ -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()
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in new issue
Block a user