迭代11第①批:魔法道具同调实施批 T0-T10 全落(重闸 645/0 + MC off 逐位 PASS)
- T1 表与schema:attunement_growth.tres 谓词表(m1/m2/m3+Σgrant≤slot_cap 校验+slots() 求值口径)+道具表扩列(form/charges/slot_cost/unlock_requires/effective_rounds/charge_n+effect_kinds 九kind+KEY_WHITELIST 零裸flat护栏)+七同调行 - T2 save v4(boss_kill_count 收束入账+attunement_equipped+attune_refresh_level+断点 attunement_charges+迁移链 v3→v4) - T3 Attunement 求值服务(纯函数零 await 零 RNG) - T4 营地装配覆盖层(工坊扩容程序占位+Y 升级行 attune_refresh_upgrade 行族) - T5 被动六件接线+装配注入链+刷新 X/Y 双源(BonusPickOverlay 刷新钮,autoplay 默认不刷) - T6 掘进蓄能全链+摆钟能力面(骑库存面差额注入+断点充能键);炸格 P5 甲影响评估破面大→用户裁拆后批,blast_kit deferred 留位 - T7 退役面五件(BONUS_IDS 除名 aux_item+transitional_sync_slots 过渡键退役=迭代10欠账③清偿+授予路/四容量闸拆除+nest_pendulum 换装 active_charged) - T8 冒烟六新断言(645/0)+MC attune 开关+贪心画像分层 off/on 各 N=60 双策略(on 画像功率显著带平移归调参批;off 新锚=对拍正主留盘主区 sim-data,81000 降批入口基准) - T9 连带修订九件(魔法道具定位重写/元进度v1.6/地图战斗资源v0.16/行动经济v0.7/05不做v1.3/关卡节点地图v1.4/node-reward v2.2/registry 三处/06 收口+炸格拆后批新条) - T10 重闸全绿:冒烟 645/0+熵增 PASS+doc 三闸零新增+MC off 复跑同种子逐位一致 PASS;出口人闸后移迭代 12(用户 2026-10-03 令)
This commit is contained in:
1 parent
02cb92a972
commit
a6a2bf885e
37 files changed
+1075
-173
No files matched your search
@@ -33,3 +33,4 @@
|
|||||||
| v1.0 | 2026-09-14 | 初版随 GDD 骨架建立:确定不做 / 现阶段不做两节 |
|
| v1.0 | 2026-09-14 | 初版随 GDD 骨架建立:确定不做 / 现阶段不做两节 |
|
||||||
| v1.1 | 2026-09-21 | 元进度实现批:元进度行注首批已落地(营地三柜台 / 钥石账本 / 跨局注入),数值 provisional 归设计批标定 |
|
| v1.1 | 2026-09-21 | 元进度实现批:元进度行注首批已落地(营地三柜台 / 钥石账本 / 跨局注入),数值 provisional 归设计批标定 |
|
||||||
| v1.2 | 2026-10-03 | R3 清账批(gdd_lint 分域随批):元进度行日期批注迁出至本表 v1.1 行;语义零变更 |
|
| v1.2 | 2026-10-03 | R3 清账批(gdd_lint 分域随批):元进度行日期批注迁出至本表 v1.1 行;语义零变更 |
|
||||||
|
| v1.3 | 2026-10-03 | 道具替代推理边界锚复核(迭代 11 第①批同调实施批——任务书 §十连带):「允许恢复推理原料,不允许道具替代推理」边界锚**原样保留即通过**——同调目录九件逐件对表:真视/预读类=信息给付(探巢摆钟 3×3 窄域护栏承重),六被动=机制修正(禁裸 flat schema 强制),无一替代推理判定本体;复核零改动 |
|
||||||
+11
-3
@@ -62,7 +62,15 @@
|
|||||||
|
|
||||||
**归属**:魔法道具同调实施批(主责)+ 元进度/存档序列化/地图战斗资源/行动经济/关卡节点地图 随批连带
|
**归属**:魔法道具同调实施批(主责)+ 元进度/存档序列化/地图战斗资源/行动经济/关卡节点地图 随批连带
|
||||||
|
|
||||||
**状态**:**已编入迭代 11、序位第一(2026-10-03 用户裁)**;任务书 v1.1 全裁(2026-10-02),开工=用户发令→任务书升 loop 计划书 U0 批准即 T0。〔原登记:待立项(2026-09-30 用户裁决甲落定方向;开工=用户发任务书;排期不在此登记。⚠ 事故留痕:本提案 2026-09-22 落档后未登记未立项,迭代 7 裁决时不可见致两态并存七日——登记本条即堵此洞,教训已入 doc-conventions v8 概念落位条款)
|
**状态**:**已实施落籍(2026-10-03,M-4 迭代 11 第①批,本条收口)**——worktree `m4-iter11a-attune-20261003`(攒 T10 一次提交):T0-T8 落 A 表与 schema(attunement_growth.tres 谓词表+道具表扩列+七同调行)/save v4(boss_kill_count/attunement_equipped/attune_refresh_level+断点充能余量)/求值服务/营地装配覆盖层(工坊扩容程序占位+Y 升级行)/被动六件+主动两件(掘进蓄能/探巢摆钟)接线/三选一刷新件 X/Y 双源/退役面五件(aux 卡出池+过渡键退役+授予路拆除,迭代 10 欠账③随之清偿);P5 甲炸格影响评估破面判定=大→用户裁**拆后批**(blast_kit 行标 deferred 留位,cell_removal kind 留 schema,后批立项再启——见下「爆破工兵包中立移除格态」条);锚语义判读追认(T8 起 off 换新锚为对拍正主)。遗留:装配 UI 视觉终稿归营地批;attune=on 带平移投值归调参批;维度词表收缩形态开放点(T10 追认单随批)。〔原登记:已编入迭代 11、序位第一(2026-10-03 用户裁);任务书 v1.1 全裁(2026-10-02),开工=用户发令→任务书升 loop 计划书 U0 批准即 T0。⚠ 事故留痕:本提案 2026-09-22 落档后未登记未立项,迭代 7 裁决时不可见致两态并存七日——登记本条即堵此洞,教训已入 doc-conventions v8 概念落位条款〕
|
||||||
|
|
||||||
|
#### 爆破工兵包中立移除格态(2026-10-03 同调批 T6 拆出——后批立项)
|
||||||
|
|
||||||
|
**现状**:同调批 T6 前置半日影响评估(P5 甲)破面判定=**大**:中立移除格态触排雷玩法(第三格态「已移除」——挖掘/插旗/和弦/邻接数字/清空判定全要问它)+战斗结算(完美排雷读数与旗判定基线需定义移除格语义)+清图判定(移除安全格改分母——BoardData 清空判定+MC 清图率读数口径)三面,且底稿欠规格两处口径(被移除雷格是否仍计入邻接数字/移除格数字呈现)实做需追加拍板。用户裁(2026-10-03):拆后批+blast_kit 行标 deferred 留位(effect_kinds cell_removal 已在 schema 枚举、行位保留,后批立项时换装 form=active_charged 即启)。后批开工前置=两处口径拍板+三面改动面任务书。
|
||||||
|
|
||||||
|
**归属**:后批(迭代 12 或独立 mini-loop,与反制批/双层 BD 批次排序待用户发令)
|
||||||
|
|
||||||
|
**状态**:待立项(拆后批)
|
||||||
|
|
||||||
## 战斗循环
|
## 战斗循环
|
||||||
|
|
||||||
@@ -270,9 +278,9 @@
|
|||||||
|
|
||||||
#### 迭代 10 奖励重构联动欠账(2026-10-01 登记)
|
#### 迭代 10 奖励重构联动欠账(2026-10-01 登记)
|
||||||
|
|
||||||
**现状**:迭代 10 奖励重构批(worktree 分支 m3-iter10-20261001,不合入 master、渲染层验证冻结,待用户批准合并)落地面产生三条后续义务——①钥石产出重标:渠道改「Boss/精英击杀掉落」(大奖励 keystone_per_level 退役、boss_keystone 重锚首杀 3/复杀 2 provisional),现行元进度产出结构作废,标定义务重开;②通关率带重扫:通关回血 20%(血回满→部分回血,难度上压方向)+ 三选一件值/权重/次数上限 knob(AP 件 = 每场战斗首轮 +1 AP 强度敏感轴)——难度曲线与Boss P1 行已登记;③同调位读数源切换:本批挂过渡键 transitional_sync_slots(provisional 1),同调实施批落地后切换为 attunement_growth.tres 谓词表实时求值,切换注记随该批。
|
**现状**:迭代 10 奖励重构批(worktree 分支 m3-iter10-20261001,不合入 master、渲染层验证冻结,待用户批准合并)落地面产生三条后续义务——①钥石产出重标:渠道改「Boss/精英击杀掉落」(大奖励 keystone_per_level 退役、boss_keystone 重锚首杀 3/复杀 2 provisional),现行元进度产出结构作废,标定义务重开;②通关率带重扫:通关回血 20%(血回满→部分回血,难度上压方向)+ 三选一件值/权重/次数上限 knob(AP 件 = 每场战斗首轮 +1 AP 强度敏感轴)——难度曲线与Boss P1 行已登记;③同调位读数源切换:本批挂过渡键 transitional_sync_slots(provisional 1),同调实施批落地后切换为 attunement_growth.tres 谓词表实时求值,切换注记随该批。〔③已清偿(2026-10-03 迭代 11 第①批 T7:过渡键表/schema/断言三处退役,读取点归零,呈现与求值正主=Attunement.slots 谓词表实时求值);①②仍开放〕
|
||||||
|
|
||||||
**归属**:①元进度设计批 + 曲线调参批 ②曲线调参批(现行窗) ③魔法道具同调实施批
|
**归属**:①元进度设计批 + 曲线调参批 ②曲线调参批(现行窗) ③~~魔法道具同调实施批~~(已清偿 2026-10-03)
|
||||||
|
|
||||||
**状态**:已登记(2026-10-01,迭代 10 批随批)
|
**状态**:已登记(2026-10-01,迭代 10 批随批)
|
||||||
|
|
||||||
|
|||||||
@@ -189,3 +189,4 @@ Boss 兑付(胜利或英雄死亡)触发关卡收束点,此后不再有选
|
|||||||
| v1.1 | 2026-09-20 | **商店房扩类(GP+商店房批,用户拍板——与冒险结构 / 战斗结算 / 地图战斗资源 / 难度曲线与Boss / registry / 切片同批)**:① 节点封闭枚举四类→五类(+商店房);② 编排规则定稿——每关恰 1、关底 Boss 前一位必经位(哈迪斯式"决战前最后一个补给点"定位);必经位由冒烟断言承载、图合法性六检零新增;③ 兑付定稿——打开商店覆盖层、购买即时生效写统一冒险加算区(与关卡大奖励同账,归冒险结构)、离开后兑付确认;商店房不携带战斗行;④ 节点兑付表 / 纪律表 / 依赖表 / P0 闭环 / 验收标准 / P2「事件类节点候选」注记同步 |
|
| v1.1 | 2026-09-20 | **商店房扩类(GP+商店房批,用户拍板——与冒险结构 / 战斗结算 / 地图战斗资源 / 难度曲线与Boss / registry / 切片同批)**:① 节点封闭枚举四类→五类(+商店房);② 编排规则定稿——每关恰 1、关底 Boss 前一位必经位(哈迪斯式"决战前最后一个补给点"定位);必经位由冒烟断言承载、图合法性六检零新增;③ 兑付定稿——打开商店覆盖层、购买即时生效写统一冒险加算区(与关卡大奖励同账,归冒险结构)、离开后兑付确认;商店房不携带战斗行;④ 节点兑付表 / 纪律表 / 依赖表 / P0 闭环 / 验收标准 / P2「事件类节点候选」注记同步 |
|
||||||
| v1.2 | 2026-09-23 | **停点前移批(冒险入口流程倒置修复,用户 2026-09-23 拍板;上位裁决 `../../../dev/architecture/adr-0006-board-node2d-rendering.md` 与 `../../../dev/architecture/board-node2d-migration-plan.md` §1,代码实装另批并行)**:① 行进循环改停点前移口径——关卡开始即呈现节点图选路停点(关卡画面 = 选路覆盖层常驻化,入口节点不自动进入,第一次选路就是"出发"),选路确认进节点,兑付确认后返回关卡画面再选路(战后回图);兑付后确认、零自动推进两条纪律语义不变,只改承载位;② 呈现侧口径补进度承载条款——关卡画面本体即节点链进度呈现载体(节点态直接读图:已过暗金 / 当前金 / 候选下一跳通道亮金 / Boss 旗朱描边),不另设进度条组件(战斗屏 A 区进度条随批退役);③ P0 闭环两条、依赖表 HUD 框架行、验收标准「行进自主」条同步。**批内代拍板三项已追认(2026-09-24 用户确认)**:① B/C/D 字母保持不重编;② 28px 纵向空间划归 C/D 底行;③ 关卡画面进度呈现由选路覆盖层节点态承载(登记面见 HUD框架 v0.8) |
|
| v1.2 | 2026-09-23 | **停点前移批(冒险入口流程倒置修复,用户 2026-09-23 拍板;上位裁决 `../../../dev/architecture/adr-0006-board-node2d-rendering.md` 与 `../../../dev/architecture/board-node2d-migration-plan.md` §1,代码实装另批并行)**:① 行进循环改停点前移口径——关卡开始即呈现节点图选路停点(关卡画面 = 选路覆盖层常驻化,入口节点不自动进入,第一次选路就是"出发"),选路确认进节点,兑付确认后返回关卡画面再选路(战后回图);兑付后确认、零自动推进两条纪律语义不变,只改承载位;② 呈现侧口径补进度承载条款——关卡画面本体即节点链进度呈现载体(节点态直接读图:已过暗金 / 当前金 / 候选下一跳通道亮金 / Boss 旗朱描边),不另设进度条组件(战斗屏 A 区进度条随批退役);③ P0 闭环两条、依赖表 HUD 框架行、验收标准「行进自主」条同步。**批内代拍板三项已追认(2026-09-24 用户确认)**:① B/C/D 字母保持不重编;② 28px 纵向空间划归 C/D 底行;③ 关卡画面进度呈现由选路覆盖层节点态承载(登记面见 HUD框架 v0.8) |
|
||||||
| v1.3 | 2026-10-01 | **图合法性第七检(T17 目检用户拍板「节点出度最大为 2」)**:`validate` 新增 `fanout_violation`(单节点出边 >2 加载致命);规则连带层宽上限 4(原 5 与出度约束不相容,废止)——生成侧规则与模板池重建归[关卡节点图生成](关卡节点图生成.md) v0.6 同批 |
|
| v1.3 | 2026-10-01 | **图合法性第七检(T17 目检用户拍板「节点出度最大为 2」)**:`validate` 新增 `fanout_violation`(单节点出边 >2 加载致命);规则连带层宽上限 4(原 5 与出度约束不相容,废止)——生成侧规则与模板池重建归[关卡节点图生成](关卡节点图生成.md) v0.6 同批 |
|
||||||
|
| v1.4 | 2026-10-03 | **奖励节点道具渠道退役注记(迭代 11 第①批同调实施批——与 node-reward.md v2.2 / registry 同批)**:奖励节点的魔法道具件渠道随停点渠道退役拆除(BONUS_IDS 词表除名 aux_item,池五种;三选一刷新件面新增见 node-reward.md v2.2)——奖励节点兑付面=提升三选一(属性/GP/AP 五种)+刷新,零结构改动(节点类型词表/图合法性六检零新增) |
|
||||||
@@ -247,3 +247,4 @@
|
|||||||
| v0.13 | 2026-09-20 | **伏击单轮上限删除批同步(与战斗结算 v0.19 / 敌人系统 v0.11 等同批)**:负向即时节 blast 负向的伏击上限护栏与溢出行协议措辞删除,改直授无上限 |
|
| v0.13 | 2026-09-20 | **伏击单轮上限删除批同步(与战斗结算 v0.19 / 敌人系统 v0.11 等同批)**:负向即时节 blast 负向的伏击上限护栏与溢出行协议措辞删除,改直授无上限 |
|
||||||
| v0.14 | 2026-09-20 | **格面资源八型批(用户拍板——与敌人系统 / 战斗结算 / 行动经济 / registry / 切片代码同批)**:① 七型→八型(+action 行动点——正向 ap_up 下轮行动点 +1、负向 ap_drain 迟滞 −1,经 action_economy 排队、下一轮兑现);② blast 负向 ambush_double 退役换装 enemy_alert 警觉(敌方全体护甲等级 +1/层,`status_effects.tres` alert 行,side-wide);③ 极性矩阵/八型字典/负向即时节/候选集合/调参表/结构冻结/验收标准同步;正向权重合计 67→74(attack 14 / defense 11 / blast 16 / heal 2 / weaken 8 / pierce 8 / magic 8 / 清障炸药 1 / action 6) |
|
| v0.14 | 2026-09-20 | **格面资源八型批(用户拍板——与敌人系统 / 战斗结算 / 行动经济 / registry / 切片代码同批)**:① 七型→八型(+action 行动点——正向 ap_up 下轮行动点 +1、负向 ap_drain 迟滞 −1,经 action_economy 排队、下一轮兑现);② blast 负向 ambush_double 退役换装 enemy_alert 警觉(敌方全体护甲等级 +1/层,`status_effects.tres` alert 行,side-wide);③ 极性矩阵/八型字典/负向即时节/候选集合/调参表/结构冻结/验收标准同步;正向权重合计 67→74(attack 14 / defense 11 / blast 16 / heal 2 / weaken 8 / pierce 8 / magic 8 / 清障炸药 1 / action 6) |
|
||||||
| v0.15 | 2026-09-20 | **格面资源九型批(GP+商店房批,用户拍板——格面 GP 获取途径推翻提案"格面池零修订"推荐:定位"低概率惊喜获取"、量与战斗掉落相当;与战斗结算 v0.23 / 冒险结构 v0.5 / 关卡节点地图 v1.1 / registry / 切片代码同批)**:① 八型→九型(+gp 金币——正向 gp_gain 金币袋 weight 2、负向 gp_loss 破财 weight=0 纯字典,power 语义 = 战斗内 GP 缓冲 ±,兑付编排归战斗结算、钱包账本归冒险结构);② 负向恒即时节补 gp 两向缓冲语义(战斗完成随 gp_dropped 统一到账,无库存语义不变);③ 候选集合/调参表/结构冻结(九型封闭枚举)/验收标准同步——正向权重合计 74→76;字典 16 格→18 格 |
|
| v0.15 | 2026-09-20 | **格面资源九型批(GP+商店房批,用户拍板——格面 GP 获取途径推翻提案"格面池零修订"推荐:定位"低概率惊喜获取"、量与战斗掉落相当;与战斗结算 v0.23 / 冒险结构 v0.5 / 关卡节点地图 v1.1 / registry / 切片代码同批)**:① 八型→九型(+gp 金币——正向 gp_gain 金币袋 weight 2、负向 gp_loss 破财 weight=0 纯字典,power 语义 = 战斗内 GP 缓冲 ±,兑付编排归战斗结算、钱包账本归冒险结构);② 负向恒即时节补 gp 两向缓冲语义(战斗完成随 gp_dropped 统一到账,无库存语义不变);③ 候选集合/调参表/结构冻结(九型封闭枚举)/验收标准同步——正向权重合计 74→76;字典 16 格→18 格 |
|
||||||
|
| v0.16 | 2026-10-03 | **偏运硬币权重投影注记(迭代 11 第①批同调实施批——任务书 §十连带;池结构与冻结项零触碰)**:同调被动件「偏运硬币」装配时对装板摇号(roll_board 身份批,reward_roll 封闭流)注入池内权重投影——稀有正向 kinds {heal, gp, pierce, magic} 行 weight +N(表值 magnitude,provisional),雷安仍同池同分布、池成员集合与取号次数零变化(reward_economy._biased_cell_rows,luck_bias=0 时逐位返回原行集=off 态零扰动);摇号权重合计面 76 为九型基准值、投影后合计随 N 浮动(逐行归因不受影响——抽签按调整后池权重消费同一取号) |
|
||||||
@@ -233,3 +233,4 @@
|
|||||||
| v0.4 | 2026-09-19 | 插旗不可逆批(用户拍板全套落地):拔旗动作退役——设计目的/统一价目/价目表/布尔入口清单/P0 闭环/调参表六处删"拔旗",flag_pull_ap_cost 字段随 schema 与 data 表同批删除;插旗行补"旗 = 一次性承诺、纠错 = 直接挖开"口径 |
|
| v0.4 | 2026-09-19 | 插旗不可逆批(用户拍板全套落地):拔旗动作退役——设计目的/统一价目/价目表/布尔入口清单/P0 闭环/调参表六处删"拔旗",flag_pull_ap_cost 字段随 schema 与 data 表同批删除;插旗行补"旗 = 一次性承诺、纠错 = 直接挖开"口径 |
|
||||||
| v0.5 | 2026-09-20 | **格面资源八型批同步(用户拍板——与敌人系统 / 战斗结算 / 地图战斗资源 / registry / 切片代码同批)**:① F1 条款 3 改写——原"敌方伏击暴露不等式"随伏击机制退役作废,惩罚当量对账模型归难度曲线与Boss 调参批重立;② 新增「下轮行动点修正」节——action 型格面资源经 queue_ap_delta 排队、下一轮开始兑现(基线 ap_per_round + 净值、下限 0、兑现后清零),跨轮携带量口径改写(RAP 唯一显式携带资源 + 一次性修正队列);③ 节拍不变式(满停一轮 = 恰好一刷)补扰动注记 |
|
| v0.5 | 2026-09-20 | **格面资源八型批同步(用户拍板——与敌人系统 / 战斗结算 / 地图战斗资源 / registry / 切片代码同批)**:① F1 条款 3 改写——原"敌方伏击暴露不等式"随伏击机制退役作废,惩罚当量对账模型归难度曲线与Boss 调参批重立;② 新增「下轮行动点修正」节——action 型格面资源经 queue_ap_delta 排队、下一轮开始兑现(基线 ap_per_round + 净值、下限 0、兑现后清零),跨轮携带量口径改写(RAP 唯一显式携带资源 + 一次性修正队列);③ 节拍不变式(满停一轮 = 恰好一刷)补扰动注记 |
|
||||||
| v0.6 | 2026-10-01 | **失同步清偿 + 跨冒险行动点加成轴节(迭代 10 奖励重构批——与冒险结构 / registry 同批)**:①失同步欠账清偿——ap_per_round 真实配置 = **8**(action_economy.tres,Package B 2026-09-22 批 6→8 回调,表内批注在案);v0.2 批 8→6 系最后一次文档面记录,此后配置面单独回调未回写本文,本批补记(rap_refresh_cost 与 ap_per_round 的「满预算停手 = 恰好一刷」节拍耦合以现行 8/8 维持);②新增「跨冒险行动点加成轴」节——统一冒险加算区扩第四轴(行动点),经战斗开始入参覆盖值通道、只在每场战斗开轮首轮兑现一次;价值锚 = 冒险表 bonus_ap_flat / bonus_cap_ap(迭代 10 三选一 AP 件,全冒险 2 次上限),调参表补跨表行 |
|
| v0.6 | 2026-10-01 | **失同步清偿 + 跨冒险行动点加成轴节(迭代 10 奖励重构批——与冒险结构 / registry 同批)**:①失同步欠账清偿——ap_per_round 真实配置 = **8**(action_economy.tres,Package B 2026-09-22 批 6→8 回调,表内批注在案);v0.2 批 8→6 系最后一次文档面记录,此后配置面单独回调未回写本文,本批补记(rap_refresh_cost 与 ap_per_round 的「满预算停手 = 恰好一刷」节拍耦合以现行 8/8 维持);②新增「跨冒险行动点加成轴」节——统一冒险加算区扩第四轴(行动点),经战斗开始入参覆盖值通道、只在每场战斗开轮首轮兑现一次;价值锚 = 冒险表 bonus_ap_flat / bonus_cap_ap(迭代 10 三选一 AP 件,全冒险 2 次上限),调参表补跨表行 |
|
||||||
|
| v0.7 | 2026-10-03 | **两 AP 件兑现通道注记(迭代 11 第①批同调实施批——任务书 §十连带)**:同调目录两 AP 件兑现通道落位——①工兵晨令(被动·限时)=战斗开始首轮 queue_ap_delta 入队 + 轮始段续队(前 effective_rounds 轮每轮 +1,表值化 P4 甲),复用「跨冒险行动点加成轴」同族排队通道;②掘进蓄能(主动·充能)=grant_ap 当轮加量(同挖掘期 ap_grant 通道),受理闸=战斗域安全开格积点(charge_n 格/点);账本零新机制(queue_ap_delta/grant_ap 两既有原语),充能扣减经共享账本(attune_charges)同引用直改 |
|
||||||
@@ -1,18 +1,26 @@
|
|||||||
# 魔法道具
|
# 魔法道具
|
||||||
|
|
||||||
> 一句话:扫雷侧魔法道具(显影、探测类)
|
> 一句话:扫雷侧魔法道具(同调机制件——营地装配、机制轴唯一来源)
|
||||||
> 状态:已定稿(自旧体系迁移)
|
> 状态:已定稿(2026-10-03 迭代 11 第①批同调实施批——终局形态落籍,过渡形态退役)
|
||||||
> 形态沿革:现行正文=切片期过渡形态;终局=局外同调件(2026-09-30 用户裁决甲)——见下节「定位与形态沿革」
|
> 形态沿革:~~过渡形态~~已退役(2026-10-03);现行=局外同调件——见下节「定位与形态沿革」
|
||||||
> 旧版参考:../../../archive/gdd-2026-09-14/a5-minesweeper-aux-items.md
|
> 旧版参考:../../../archive/gdd-2026-09-14/a5-minesweeper-aux-items.md
|
||||||
|
|
||||||
## 定位与形态沿革(2026-09-30 用户裁决甲)
|
## 定位与形态沿革(2026-09-30 用户裁决甲;2026-10-03 同调实施批落籍终局形态)
|
||||||
|
|
||||||
魔法道具存在**两态**,关系=形态替换而非机制积累:
|
魔法道具存在**两态**,关系=形态替换而非机制积累。**替换已于 2026-10-03 完成**(M-4 迭代 11 第①批,worktree m4-iter11a-attune-20261003):
|
||||||
|
|
||||||
- **现行(游戏内已实装,2026-09-29 迭代 7 落籍)=切片期过渡形态**:道具作为掉落奖励类别进入掉落池(现行投放三件:工兵令/探巢摆钟/爆破工兵包)、奖励节点三选一构筑点取用、关卡域库存、挖掘期两步使用——即本文以下全部正文章节所述形态。
|
- ~~**过渡形态**(2026-09-29 迭代 7 落籍)~~ **已退役**:掉落投放(2026-10-01 已随「挖格不掉道具」退役)/奖励节点三选一道具卡(2026-10-03 T7 出池)/关卡域库存/挖掘期两步使用——本文以下正文章节「掉落池获取」「关卡域库存」「什么时候怎么用」诸节随之整体退役,保留为历史口径(本节横幅为准)。
|
||||||
- **终局=局外同调件**:魔法道具重定义为「开局前在营地装配、占同调位、带每冒险充能的机制增强件」。设计权威底稿 = [proposal-magic-item-attunement-2026-09-22.md](../../../research/proposal-magic-item-attunement-2026-09-22.md)(2026-09-22 用户八条拍板在案:局内不掉落机制道具、三轴 doctrine〔跨局数值轴=属性锻造/局内数值轴=GP 商店/局外装配机制轴=魔法道具〕、同调位 0 起步硬上限 3、增长=通关次数+Boss 击杀数走里程碑谓词表、真视窄域护栏等承重资产继承清单见提案 §1.1)。
|
- **终局形态(现行)= 局外同调件**:开局前在营地装配(营地·道具工坊柜台「同调装配」段)、占同调位(0 起步、硬上限 3,slot_cost 恒 1)、被动件整局生效/主动件每冒险充能(冒险开始重置)。设计权威底稿 = [proposal-magic-item-attunement-2026-09-22.md](../../../research/proposal-magic-item-attunement-2026-09-22.md)(2026-09-22 用户八条拍板)+ 任务书 v1.1(P1-P8 全裁)。
|
||||||
|
|
||||||
**衔接纪律**:过渡形态的各项现行拍板(探宝罗盘退役、工坊解锁门控、三选一投放、投放权重)在同调实施批替换前继续有效;同调实施批立项时按提案 §1.1/§6/§9 执行替换——本文「掉落池获取」「关卡域库存」「奖励节点投放」诸节整体退役改写。实施批登记见 [06-待办清单](../../06-待办清单.md)「魔法道具同调实施批」条。
|
**现行形态速览**(实现面 = `game/data/tables/attunement_growth.tres` + `aux_items.tres` + `game/core/attunement.gd`):
|
||||||
|
|
||||||
|
- **同调位增长**:通关次数 + Boss 击杀数(含重复,收束入账 save v4)经 `attunement_growth.tres` 里程碑谓词表实时求值(Σ达成 grant clamp slot_cap)——试玩调平衡=改表不动码(迭代 10 欠账③已清偿:过渡键 transitional_sync_slots 退役)。
|
||||||
|
- **目录九件**(值全 provisional 归难度曲线与Boss):被动六件=淬毒刃纹(克制面穿刺/魔法直伤 +N)/标中殉爆(每标中一旗全体存活敌吃 N 群伤)/惊巢反制(挖雷惩罚减半+反弹 N)/工兵晨令(前 effective_rounds 轮每轮 +1 AP,P4 甲表值化)/偏运硬币(装板摇号稀有正向 kinds 权重 +N,池结构零触碰)/三选一刷新件(X 次/每碰三选一);主动三件=掘进蓄能(每开够 N 安全格积 1 点发起临时 AP,charges 2/冒险)/探巢摆钟(3×3 真视窄域,charges 2/冒险)/爆破工兵包(**deferred 留位**——P5 甲影响评估破面大,2026-10-03 用户裁拆后批,见 06「爆破工兵包中立移除格态」条)。
|
||||||
|
- **三选一刷新双源**(P1 甲):X=刷新件每碰三选一重给(优先消耗、不结转)+ Y=营地刷新升级行(每冒险开局注入冒险周期池、用完即无;meta.tres attune_refresh_upgrade 行族);重抽消费 reward_pick 既有洗牌流,池规则全保留;autoplay 默认不刷(钓鱼归玩家)。
|
||||||
|
- **🔴 承重契约**(schema 加载期强制):机制轴禁裸 flat 数值(行键白名单封闭);真视窄域 ≤3×3;核心层零 await;零随机;排雷玩法零特判;雷安同池冻结(硬币只动权重投影)。
|
||||||
|
- **验收分层**:attune=off 续用现行带;attune=on 带平移归难度曲线与Boss 投值(商店 shop=off/on 分层先例同法)。
|
||||||
|
|
||||||
|
**衔接纪律**(历史):过渡形态各项拍板在实施批替换前有效——替换已完成,本节留作事故留痕与口径溯源。实施批执行记录见 06「魔法道具同调实施批」条(已收口)。
|
||||||
|
|
||||||
> ⚠ 事故留痕(防复发):同调提案 2026-09-22 落档后未登记正主、未立项、未进待办,后续规划会话与 2026-09-29 迭代 7 裁决时点均不可见,致两套均有用户拍板记录的形态无文档声明关系并存七日(2026-09-30 审计发现、用户裁决甲收口,全录见审计报告 git 提交 c095916 C29,报告已退役)。
|
> ⚠ 事故留痕(防复发):同调提案 2026-09-22 落档后未登记正主、未立项、未进待办,后续规划会话与 2026-09-29 迭代 7 裁决时点均不可见,致两套均有用户拍板记录的形态无文档声明关系并存七日(2026-09-30 审计发现、用户裁决甲收口,全录见审计报告 git 提交 c095916 C29,报告已退役)。
|
||||||
|
|
||||||
|
|||||||
@@ -162,3 +162,4 @@ MVP 不要求本系统的闭环([不做清单](../../05-不做清单.md)整体
|
|||||||
| v1.3 | 2026-09-21 | **元进度实现批(与切片代码 / registry / 02 / 03 / 05 / 06 同批)**:本批 = 本文的实现批——MetaTable 四行族建表(`data/tables/meta.tres`,数值全 provisional 归设计批配读数);MetaProgression 账本服务(余额/几何定价/购买/工坊解锁/Boss 两档入账/注入计算,Core 层零 UI 零信号);注入通道落地(start_adventure params.hero_override → LevelRunner base_override,叠加序 = 表值+永久强化 flat+加算区 flat × 测试面板 pct);呈现面(营地覆盖层三柜台+主菜单入口+A 区顶栏钥石余额+结算屏入账明细,ui_kit 程序占位——视觉终稿归 HUD 协作批)。正文零改动(实现面不回流正文——正文零历史叙述纪律) |
|
| v1.3 | 2026-09-21 | **元进度实现批(与切片代码 / registry / 02 / 03 / 05 / 06 同批)**:本批 = 本文的实现批——MetaTable 四行族建表(`data/tables/meta.tres`,数值全 provisional 归设计批配读数);MetaProgression 账本服务(余额/几何定价/购买/工坊解锁/Boss 两档入账/注入计算,Core 层零 UI 零信号);注入通道落地(start_adventure params.hero_override → LevelRunner base_override,叠加序 = 表值+永久强化 flat+加算区 flat × 测试面板 pct);呈现面(营地覆盖层三柜台+主菜单入口+A 区顶栏钥石余额+结算屏入账明细,ui_kit 程序占位——视觉终稿归 HUD 协作批)。正文零改动(实现面不回流正文——正文零历史叙述纪律) |
|
||||||
| v1.4 | 2026-09-23 | **换算率标定批(用户指令「现在标定」;与切片代码 / registry / 06-待办清单 / 判定端工具同批)**:换算率节回填现行表值(生命 6 / 攻击 9 / 护甲 1 点每级——OFAT 冒险注入档与白板同种子配对的逐关条件通关率差实测,冒险条件口径;同日单关直入口口径退役使首轮直入口面读数作废重测);护甲行强条目重测履行并暴露修复护甲断链(begin_battle hero_armor_class 参数零消费→_defender_ac 接线);判定端带平移表值驱动接线(带重锚后随换算率平移判);排期表 P1 行销项 |
|
| v1.4 | 2026-09-23 | **换算率标定批(用户指令「现在标定」;与切片代码 / registry / 06-待办清单 / 判定端工具同批)**:换算率节回填现行表值(生命 6 / 攻击 9 / 护甲 1 点每级——OFAT 冒险注入档与白板同种子配对的逐关条件通关率差实测,冒险条件口径;同日单关直入口口径退役使首轮直入口面读数作废重测);护甲行强条目重测履行并暴露修复护甲断链(begin_battle hero_armor_class 参数零消费→_defender_ac 接线);判定端带平移表值驱动接线(带重锚后随换算率平移判);排期表 P1 行销项 |
|
||||||
| v1.5 | 2026-10-01 | **钥石渠道改一批(迭代 10 奖励重构——用户 2026-09-30 拍板;与冒险结构 v0.12 / registry 同批)**:钥石渠道自二改一——「关卡大奖励入账(keystone_per_level)」随大奖励三件套退役,渠道 = Boss/精英怪击杀掉落(逐杀实时入账,行值读 meta.tres boss_keystone 首杀/复杀两档;精英掉 1 预留);首杀大额/复杀小额规则不变。**产出重标义务重开**:现行两关冒险产出结构从「大奖励 10×2 + Boss 首杀 5」改「Boss 掉落 ×N(精英未实装零贡献)」,标定随元进度设计批/调参批重开(06 登记) |
|
| v1.5 | 2026-10-01 | **钥石渠道改一批(迭代 10 奖励重构——用户 2026-09-30 拍板;与冒险结构 v0.12 / registry 同批)**:钥石渠道自二改一——「关卡大奖励入账(keystone_per_level)」随大奖励三件套退役,渠道 = Boss/精英怪击杀掉落(逐杀实时入账,行值读 meta.tres boss_keystone 首杀/复杀两档;精英掉 1 预留);首杀大额/复杀小额规则不变。**产出重标义务重开**:现行两关冒险产出结构从「大奖励 10×2 + Boss 首杀 5」改「Boss 掉落 ×N(精英未实装零贡献)」,标定随元进度设计批/调参批重开(06 登记) |
|
||||||
|
| v1.6 | 2026-10-03 | **同调位账本批(迭代 11 第①批同调实施批——save v4 / meta.tres attune_refresh_upgrade 行族 / registry 同批)**:①道具工坊柜台职责改写——解锁→谓词表形态(unlock_requires 行,首批全解锁=空谓词合法态,底稿 §2.2 用户裁),柜台内新增「同调装配」段(装配/换装/Slots 点亮 + Y 刷新升级行购买——程序占位面,视觉终稿归营地批;满位禁选/写档持久 MetaProgression.toggle_attunement 唯一写口);②Boss 击杀总数计数登记(boss_kill_count 含重复,收束入账=apply_adventure_summary 逐通关关次 +1,attunement_growth 谓词表计数源);③Y 刷新升级行登记(attune_refresh_upgrade 行族 L1→Y1/L2→Y2,钥石直购,provisional);④装配关系入存档持久(attunement_equipped save v4 域,冒险中途不可换装——底稿 §8.5) |
|
||||||
@@ -1,27 +1,26 @@
|
|||||||
# 节点奖励屏 UX spec(node-reward)
|
# 节点奖励屏 UX spec(node-reward)
|
||||||
|
|
||||||
> 状态:v2.0(2026-10-01 迭代 10 追加批重写——用户拍板:奖励节点三选一与通关三选一**共享同池**;原「魔法道具投放渠道」过渡形态退役)
|
> 状态:v2.2(2026-10-03 迭代 11 第①批同调实施批——aux_item 池种退役出三选一牌库,池五种;新增三选一刷新件面)
|
||||||
> 事实源:`game/presentation/views/bonus_pick_overlay.gd`(三选一覆盖层共用面)+ `main.gd`(奖励节点分支——`adventure.node_bonus_candidates/apply_node_bonus`)+ `battle_narrator.gd`(node_bonus_header)
|
> 事实源:`game/presentation/views/bonus_pick_overlay.gd`(三选一覆盖层共用面)+ `main.gd`(奖励节点分支——`adventure.node_bonus_candidates/apply_node_bonus`)+ `battle_narrator.gd`(node_bonus_header)
|
||||||
> 相邻:[level-clear](level-clear.md)(通关三选一面——同一覆盖层的另一上下文)/ [level-map](level-map.md)(关卡画面)/ [魔法道具](../systems/排雷循环/魔法道具.md)(道具件内容权威)/ [冒险结构](../systems/冒险循环/冒险结构.md)(提升池单例与次数上限)
|
> 相邻:[level-clear](level-clear.md)(通关三选一面——同一覆盖层的另一上下文)/ [level-map](level-map.md)(关卡画面)/ [魔法道具](../systems/排雷循环/魔法道具.md)(道具件内容权威——本批起为局外同调件,不再入三选一牌库)/ [冒险结构](../systems/冒险循环/冒险结构.md)(提升池单例与次数上限)
|
||||||
|
|
||||||
## 定位
|
## 定位
|
||||||
|
|
||||||
奖励节点(reward node)到达即开的**提升三选一选择面**:与通关三选一共享同一提升池(六种:攻击/生命上限/护甲/GP 袋/魔法道具/行动点件)——同权重、同全冒险次数上限、同 reward_pick 封闭流。与通关面的差异仅在上下文:无部分回血、无钥石增量行,兑付后「继续」回图选路(不推进关卡)。
|
奖励节点(reward node)到达即开的**提升三选一选择面**:与通关三选一共享同一提升池(**五种**:攻击/生命上限/护甲/GP 袋/行动点件——v2.2 起魔法道具件随同调批退役出池)——同权重、同全冒险次数上限、同 reward_pick 封闭流。与通关面的差异仅在上下文:无部分回血、无钥石增量行,兑付后「继续」回图选路(不推进关卡)。
|
||||||
|
|
||||||
## 布局与交互
|
## 布局与交互
|
||||||
|
|
||||||
- 载体 = 提升三选一覆盖层(BonusPickOverlay,与 [level-clear](level-clear.md) 共用组件——零新增交互原语)。
|
- 载体 = 提升三选一覆盖层(BonusPickOverlay,与 [level-clear](level-clear.md) 共用组件——零新增交互原语)。
|
||||||
- 头部行:「关卡奖励点 / 从提升池抽三选一(与通关三选一同池同权重同上限):选择一件带走:」(Narrator.node_bonus_header)。
|
- 头部行:「关卡奖励点 / 从提升池抽三选一(与通关三选一同池同权重同上限):选择一件带走:」(Narrator.node_bonus_header)。
|
||||||
- 三卡点选(✓ 高亮,确认前可改选)→「确认选择」→ 兑付反馈(选中件效果 + 丢弃提示)→「继续」→ 回图选路(零自动推进同款)。
|
- 三卡点选(✓ 高亮,确认前可改选)→「确认选择」→ 兑付反馈(选中件效果 + 丢弃提示)→「继续」→ 回图选路(零自动推进同款)。
|
||||||
- **魔法道具件直显具体道具**(掷定前置,v2.1):候选抽定时即掷定具体道具,卡片显示「魔法道具 / 探巢摆钟 · 真视 3×3」——不再是「随机获得一件」盲选。
|
- ~~魔法道具件直显/满位替换~~(v2.1 两面随池种退役——迭代 11 第①批 T7;魔法道具改走营地同调装配面,见[魔法道具](../systems/排雷循环/魔法道具.md))。
|
||||||
- 魔法道具件满位:替换子面板(现有库存件 + 新件 N+1 选 N,丢旧收新 / 放弃新件两路——与通关面同构;丢弃目标 = 关卡域库存件句柄)。
|
- **三选一刷新**(v2.2 新增——P1 甲 X/Y 双源):装配「三选一刷新件」给 X 次(每次碰三选一重给、优先消耗、不结转)+ 营地「刷新升级」给 Y 次(每冒险开局注入冒险周期池、用完即无);刷新钮余量 >0 时现形,点按重抽三候选(reward_pick 既有洗牌流,池规则全保留:不放回/达上限剔牌/掷定前置直显/停点可中断)。autoplay 默认不刷(钓鱼归玩家)。
|
||||||
|
|
||||||
## 口径
|
## 口径
|
||||||
|
|
||||||
- **同池同上限**:件获取次数计数为冒险级单例——奖励节点与通关三选一两渠道合计判上限(达上限件在两渠道同样剔牌库);权重与牌库口径读冒险表(registry adventure_table 为准)。
|
- **同池同上限**:件获取次数计数为冒险级单例——奖励节点与通关三选一两渠道合计判上限(达上限件在两渠道同样剔牌库);权重与牌库口径读冒险表(registry adventure_table 为准)。
|
||||||
- 道具件入**本关库存**(即时可用,来源归因 `reward_node`)——区别于通关三选一的跨关携带(清库后授予、下一关生效)。
|
- ~~道具件入本关库存~~(v2.2 退役——魔法道具局内不掉落机制道具,三轴 doctrine 锁死)。
|
||||||
- 「池空直通」合法态随共享池退役:池含无次数上限的 GP/道具件,牌库恒非空。
|
- 「池空直通」合法态随共享池退役:池含无次数上限的 GP 件,牌库恒非空。
|
||||||
- 退役件(探宝罗盘)/失义件不入道具件随机池(同调位约束口径不变,归[魔法道具](../systems/排雷循环/魔法道具.md))。
|
|
||||||
|
|
||||||
## 版本历史
|
## 版本历史
|
||||||
|
|
||||||
@@ -30,4 +29,5 @@
|
|||||||
| v1.0 | 2026-09-29 | 迭代 7 T5 随批出生(三选一构筑点形态——用户拍板②丙) |
|
| v1.0 | 2026-09-29 | 迭代 7 T5 随批出生(三选一构筑点形态——用户拍板②丙) |
|
||||||
| v2.0 | 2026-10-01 | **共享池改写(迭代 10 追加批——用户拍板「奖励节点与通关三选一相同、共享同一个池子」)**:①定位改提升三选一选择面(六种共享池、同权重同次数上限、同 reward_pick 流);②载体改 BonusPickOverlay 共用面(原 node_overlay 道具三选一面退役);③道具件满位升级为替换子交互两路(原过渡形态=满位不授予);④「池空直通」合法态退役(牌库恒非空);⑤道具件入本关库存(即时可用),区别于通关渠道的跨关携带 |
|
| v2.0 | 2026-10-01 | **共享池改写(迭代 10 追加批——用户拍板「奖励节点与通关三选一相同、共享同一个池子」)**:①定位改提升三选一选择面(六种共享池、同权重同次数上限、同 reward_pick 流);②载体改 BonusPickOverlay 共用面(原 node_overlay 道具三选一面退役);③道具件满位升级为替换子交互两路(原过渡形态=满位不授予);④「池空直通」合法态退役(牌库恒非空);⑤道具件入本关库存(即时可用),区别于通关渠道的跨关携带 |
|
||||||
| v2.1 | 2026-10-01 | **掷定前置(目检反馈批——用户反馈「抽到魔法道具不显示详情不直观」,推荐案获批)**:道具件具体道具随候选抽定(reward_pick 流序内挪,消费数不变),卡片直显「魔法道具 / 道具名 · 效果摘要」;断点停点快照随载 aux_detail(续玩卡片详情不丢);node_bonus_candidates 返回扩 dict {candidates, aux_detail} |
|
| v2.1 | 2026-10-01 | **掷定前置(目检反馈批——用户反馈「抽到魔法道具不显示详情不直观」,推荐案获批)**:道具件具体道具随候选抽定(reward_pick 流序内挪,消费数不变),卡片直显「魔法道具 / 道具名 · 效果摘要」;断点停点快照随载 aux_detail(续玩卡片详情不丢);node_bonus_candidates 返回扩 dict {candidates, aux_detail} |
|
||||||
|
| v2.2 | 2026-10-03 | **池种退役+刷新件面(迭代 11 第①批同调实施批 T7/T5b——P1 甲)**:①aux_item 池种退役出三选一牌库(BONUS_IDS 除名+权重/件数/掷定前置字段随批退役)——魔法道具改走营地同调装配面(三轴 doctrine),v2.1 直显/满位替换两面随渠道退役(机械惰性承载零投放);②三选一刷新件面新增(X/Y 双源:刷新件每碰三选一重给优先消耗不结转+营地刷新升级冒险周期池;重抽消费 reward_pick 既有洗牌流池规则全保留;autoplay 默认不刷) |
|
||||||
| v2.2 | 2026-10-03 | **三档位令牌池(用户全裁——待 M-4 数值批实施,现行 v2.1 平铺池维持到实施)**:池改固定配额令牌池(普通 70/稀有 25/罕见 5=100 张;洗牌不放回抽 3、选中踢池未选回池、池耗即枯竭);档位构成=普通〔小GP 18/小增伤 18/小护甲 17/小生命 17〕+稀有〔大GP/大增伤/大护甲/大生命/恢复 各 5〕+罕见〔AP 3/道具 2〕;数值对 20/50、+1/+2、+1/+2、+5/+10、恢复+6、AP+1、道具 1 件;**种类次数上限退役**(池消耗即上限;同调位上限保留);恢复件=新入池种类;MC 策略改判 1 级最稀有/2 级最左(确定性)——设计全稿与执行口径见 [06 待办「奖励池三档位令牌池重设计」条](../06-待办清单.md);卡片呈现面(本 spec 交互节)零改动 |
|
| v2.2 | 2026-10-03 | **三档位令牌池(用户全裁——待 M-4 数值批实施,现行 v2.1 平铺池维持到实施)**:池改固定配额令牌池(普通 70/稀有 25/罕见 5=100 张;洗牌不放回抽 3、选中踢池未选回池、池耗即枯竭);档位构成=普通〔小GP 18/小增伤 18/小护甲 17/小生命 17〕+稀有〔大GP/大增伤/大护甲/大生命/恢复 各 5〕+罕见〔AP 3/道具 2〕;数值对 20/50、+1/+2、+1/+2、+5/+10、恢复+6、AP+1、道具 1 件;**种类次数上限退役**(池消耗即上限;同调位上限保留);恢复件=新入池种类;MC 策略改判 1 级最稀有/2 级最左(确定性)——设计全稿与执行口径见 [06 待办「奖励池三档位令牌池重设计」条](../06-待办清单.md);卡片呈现面(本 spec 交互节)零改动 |
|
||||||
@@ -69,8 +69,8 @@ entities:
|
|||||||
attributes:
|
attributes:
|
||||||
path: user://save.json(单档 MVP——多存档槽位 P2 保留)
|
path: user://save.json(单档 MVP——多存档槽位 P2 保留)
|
||||||
format: JSON 单档(人可读可调试;字典键一律 String)
|
format: JSON 单档(人可读可调试;字典键一律 String)
|
||||||
version: SAVE_FORMAT_VERSION=2(registry schema_version 同源;v2=断点域出生 2026-09-29 迭代 8 卡①,生产迁移链 v1→v2 补空;高版本拒读 + 禁写盘 + 内存默认新档运行;低版本读档时链式迁移 register_migration,任一步失败按损坏兜底)
|
version: SAVE_FORMAT_VERSION=4(registry schema_version 同源;v4=同调批扩域 2026-10-03 迭代 11 第①批——meta 三新域 boss_kill_count/attunement_equipped/attune_refresh_level + 断点附随件 attunement_charges,旧档缺键由 _normalize 按默认补齐;v3=断点附随件扩列+摘要 keystone_drops 2026-10-01 迭代 10;v2=断点域出生 2026-09-29 迭代 8 卡①;生产迁移链 v1→v4 链式补空;高版本拒读 + 禁写盘 + 内存默认新档运行;低版本读档时链式迁移 register_migration,任一步失败按损坏兜底)
|
||||||
domains: meta 域六件——keystone_balance(内容 owner=元进度)/ forge_levels 三档 max_hp·atk·armor_class(元进度)/ item_unlocks(元进度·道具工坊消费)/ level_clear_counts(元进度)/ boss_first_kill(元进度)/ recent_summaries cap 10 冒险摘要五件事副本(内容 owner=冒险结构;元素附 boss_drops 副本——Boss 两档金额入账随元进度实现批 2026-09-21 接通,apply_adventure_summary 第二参);+ breakpoint 域(v2 新增——冒险断点快照,内容 owner=冒险结构:七字段拍板面+附随件〔主种子/取数计数器/钥石累计/已到节点序/道具库存〕,空字典=无断点合法态,三结局清除)
|
domains: meta 域九件——keystone_balance(元进度)/ forge_levels 三档(元进度)/ item_unlocks(元进度)/ level_clear_counts(元进度)/ boss_first_kill(元进度)/ boss_kill_count(魔法道具——同调位里程碑计数源,Boss 恒唯一终点故收束入账=levels_cleared)/ attunement_equipped(魔法道具——装配关系有序数组,局外进度事实非局内道具库存,装配序即呈现序)/ attune_refresh_level(魔法道具——Y 刷新升级购买等级)/ recent_summaries cap 10(冒险结构,元素附 boss_drops 副本);+ breakpoint 域(断点快照:七字段拍板面+附随件〔主种子/取数计数器/钥石累计/已到节点序/道具库存/bonus_* 附随件/attunement_charges 充能余量——迭代 11 第①批 T6〕,空字典=无断点合法态,三结局清除)
|
||||||
write_timing: 启动读盘;正式冒险收束(adventure_finished → apply_adventure_summary)写盘;营地每笔钥石变更后即时写(元进度实现批接线);冒险选路停点断点快照写盘 + 三结局清断点写盘(迭代 8 卡①);调试两层与单关测试路径(LevelRunner.start_level,仅 QA 工具)不写档
|
write_timing: 启动读盘;正式冒险收束(adventure_finished → apply_adventure_summary)写盘;营地每笔钥石变更后即时写(元进度实现批接线);冒险选路停点断点快照写盘 + 三结局清断点写盘(迭代 8 卡①);调试两层与单关测试路径(LevelRunner.start_level,仅 QA 工具)不写档
|
||||||
atomic_write: tmp + rename 原子写(写一半断电不留烂档——钥石不丢判据承载)
|
atomic_write: tmp + rename 原子写(写一半断电不留烂档——钥石不丢判据承载)
|
||||||
corruption_policy: 坏档改名 <path>.corrupt.bak(已存在覆盖)+ 默认新档继续 + 警告留痕(不静默丢档不 crash)
|
corruption_policy: 坏档改名 <path>.corrupt.bak(已存在覆盖)+ 默认新档继续 + 警告留痕(不静默丢档不 crash)
|
||||||
@@ -505,8 +505,29 @@ entities:
|
|||||||
ux_flag: 有 UI 贡献(库存条/使用交互/叠加层呈现)——Phase 4 前置 /ux-design;asset-spec 候选(art-bible 样张批后)
|
ux_flag: 有 UI 贡献(库存条/使用交互/叠加层呈现)——Phase 4 前置 /ux-design;asset-spec 候选(art-bible 样张批后)
|
||||||
closures: 地图战斗资源 OQ2 魔法道具 侧收口(呈现深度 = 道具驱动预读;未预读格不呈现、自动寻路不做——终态与否 OQ6)· 创意风险 #1 道具侧兑现(plan 维度预读——结构闭环最后一块,呈现归 HUD框架)· 显影语义(原为雾格显影 = fog_reveal)随雾格移除失义、载体待重定(随魔法道具整体重设计归 Alpha,2026-09-20 用户拍板)
|
closures: 地图战斗资源 OQ2 魔法道具 侧收口(呈现深度 = 道具驱动预读;未预读格不呈现、自动寻路不做——终态与否 OQ6)· 创意风险 #1 道具侧兑现(plan 维度预读——结构闭环最后一块,呈现归 HUD框架)· 显影语义(原为雾格显影 = fog_reveal)随雾格移除失义、载体待重定(随魔法道具整体重设计归 Alpha,2026-09-20 用户拍板)
|
||||||
warnings: 预警①…④ playtest 预注册归 难度曲线与Boss(道具依赖度/预读覆盖率/AP 转存联动/稀有感知)
|
warnings: 预警①…④ playtest 预注册归 难度曲线与Boss(道具依赖度/预读覆盖率/AP 转存联动/稀有感知)
|
||||||
|
attunement_refactor: 迭代 11 第①批 同调实施批 T1(2026-10-03)——表扩列 {form ∈ {"", passive, active_charged}(空=切片过渡形态行), charges_per_adventure, slot_cost(同调行恒 1 校验), unlock_requires 谓词行〔空=首批全解锁合法态,底稿 §2.2 用户裁〕, effective_rounds(P4 甲表值化), charge_n} + effect_kinds 扩 {venom_rider, flag_burst, mine_hit_halve, reflect_damage, ap_rounds_front, ap_charge_safe, cell_removal, reward_weight_bias, bonus_refresh}(mine_reveal/ap_grant 语义不变)+ 🔴 零裸 flat 结构护栏 = 行键白名单封闭(KEY_WHITELIST 外键=加载致命——机制轴禁无条件属性加项的结构承载);七件同调行入表(venom_edge 淬毒刃纹/flag_burst 标中殉爆/nest_counter 惊巢反制/dawn_order 工兵晨令〔effective_rounds=3〕/dig_charge 掘进蓄能〔charges=2·charge_n=3〕/luck_coin 偏运硬币/pick_refresh 三选一刷新件〔X=1〕——值全 provisional)+ 取数口径三分(attunement_rows/transitional_rows/active_rows——同调行永不入过渡渠道);nest_pendulum/blast_kit 维持过渡行 T7 原位换装,sapper_order/probe_dust/catacomb_scroll/loot_compass T7 逐件销账(底稿 §3);维度词表收缩形态 = 开放拍板点(任务书 P1-P8 未覆盖,底稿 §1.1 明挂——T10 追认单随批,行标签 provisional)
|
||||||
referenced_by: [战斗结算, 地图战斗资源, 地形变异, 行动经济, HUD框架, 数据配置, 教学引导, art-bible]
|
referenced_by: [战斗结算, 地图战斗资源, 地形变异, 行动经济, HUD框架, 数据配置, 教学引导, art-bible]
|
||||||
|
|
||||||
|
- name: attunement_growth
|
||||||
|
type: schema_data
|
||||||
|
owner: design/gdd/systems/排雷循环/魔法道具.md
|
||||||
|
attributes:
|
||||||
|
table: AttunementGrowthTable(数据配置 承载,单行全局常量表——AdventureTable 单行先例 Rule 4 豁免;迭代 11 第①批 同调实施批 T1 落籍 2026-10-03;设计权威 = proposal-magic-item-attunement-2026-09-22 §2.1 用户点名要求)
|
||||||
|
fields: slot_cap int [0,9](硬上限 3——底稿裁决)· milestones 行族 {id 唯一, grant ∈ [1,slot_cap], requires = (计数键,阈值) AND 列表非空}——同调位读数唯一口径 = Σ达成 grant clamp slot_cap
|
||||||
|
counter_keys: 封闭枚举 {clear_count(通关次数按关累计——save 复用零改动), boss_kill_count(Boss 击杀总数含重复——本批 T2 新增收束记账)};预留可插拔位 boss_firstkill_count/adventure_count/no_damage_clear_count/run_depth_best(加行即扩展,底稿 §2.1)
|
||||||
|
schema_guards: 阈值 [1,999] · Σgrant ≤ slot_cap · 计数键出域=加载致命;数值全 provisional 归 难度曲线与Boss(形态锁死、调平衡=改表不动码)
|
||||||
|
mvp_content: m1 clear_count≥1→grant 1 · m2 boss_kill_count≥3→grant 1 · m3 {clear_count≥3 AND boss_kill_count≥6}→grant 1(行值 provisional;底稿 §2.1 行示例形态)
|
||||||
|
referenced_by: [魔法道具, 元进度, 数据配置, 冒险结构]
|
||||||
|
|
||||||
|
- name: attunement_slots
|
||||||
|
type: convention
|
||||||
|
owner: design/gdd/systems/排雷循环/魔法道具.md
|
||||||
|
attributes:
|
||||||
|
rule: 同调位=机制轴总功率闸(底稿 §2)——初始 0 起步(裁决 3)· 硬上限 slot_cap=3 · 单件占 1 槽(裁决 5,slot_cost=1 schema 强制)· 装配=冒险开始前的营地决定(底稿 §8.5 默认中途不可换装)· 装配关系入存档持久(save v4,本批 T2)
|
||||||
|
growth_source: 通关次数 + Boss 击杀数(含重复)经 attunement_growth 谓词表(裁决 4)——读数源自 transitional_sync_slots 过渡键(adventure.tres provisional)切换随本批 T3,迭代 10 欠账③清偿
|
||||||
|
layered_acceptance: attune=off 续用现行带 · attune=on 带平移归 难度曲线与Boss 投值(商店 shop=off/on 分层先例同法)
|
||||||
|
referenced_by: [魔法道具, 元进度, 数据配置]
|
||||||
|
|
||||||
- name: difficulty_table
|
- name: difficulty_table
|
||||||
type: table
|
type: table
|
||||||
owner: design/gdd/systems/冒险循环/难度曲线与Boss.md
|
owner: design/gdd/systems/冒险循环/难度曲线与Boss.md
|
||||||
|
|||||||
+105
-33
@@ -72,6 +72,10 @@ var _base_override := {"atk": 0, "max_hp": 0, "armor_class": 0}
|
|||||||
var _first_script := false # 首次脚本模式(关卡节点图生成 v0.4——true = 全关零抽签取池内模板 0;测量夹具恒 false) # 永久强化 flat 注入(元进度实现批——冒险开始参数面 params.hero_override 存入,共享给 LevelRunner;全零 = 白板)
|
var _first_script := false # 首次脚本模式(关卡节点图生成 v0.4——true = 全关零抽签取池内模板 0;测量夹具恒 false) # 永久强化 flat 注入(元进度实现批——冒险开始参数面 params.hero_override 存入,共享给 LevelRunner;全零 = 白板)
|
||||||
var _last_aux_cleared := 0 # additive(迭代 7 T4——最近一次关卡末清空计数,summary 取数面)
|
var _last_aux_cleared := 0 # additive(迭代 7 T4——最近一次关卡末清空计数,summary 取数面)
|
||||||
var _aux_unlocked := {} # aux 解锁门控集(迭代 7 T5——params.aux_unlocked 接收面)
|
var _aux_unlocked := {} # aux 解锁门控集(迭代 7 T5——params.aux_unlocked 接收面)
|
||||||
|
var _attunement: Array = [] # 同调装配集(迭代 11 第①批 T5——params.attunement 接收面,start_level 外下传 LevelRunner)
|
||||||
|
var _attune_refresh_x := 0 # 刷新件余量(迭代 11 第①批 T5b P1 甲——每碰三选一重置=X 表值;优先消耗、不结转)
|
||||||
|
var _attune_refresh_y := 0 # Y 冒险周期池(开局参数面注入,用完即无——营地升级行表值)
|
||||||
|
var attune_charges := {} # 同调主动件冒险充能账本(迭代 11 第①批 T6——跨关卡共享 dict;settlement use_aux 扣减直改同引用;断点随 attunement_charges 键入档)
|
||||||
var _save_manager = null # 断点承载注入(迭代 8 卡①——null = 不写断点;模拟/冒烟缺省)
|
var _save_manager = null # 断点承载注入(迭代 8 卡①——null = 不写断点;模拟/冒烟缺省)
|
||||||
var _resume_bp := {} # 续玩快照暂存(resume_adventure → _start_current_level 单跳传递)
|
var _resume_bp := {} # 续玩快照暂存(resume_adventure → _start_current_level 单跳传递)
|
||||||
# ---- 三选一停点态(迭代 10 奖励重构批)----
|
# ---- 三选一停点态(迭代 10 奖励重构批)----
|
||||||
@@ -112,7 +116,13 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
|||||||
_pending_next_level = false
|
_pending_next_level = false
|
||||||
_base_override = _normalize_override(params.get("hero_override", {}))
|
_base_override = _normalize_override(params.get("hero_override", {}))
|
||||||
_first_script = bool(params.get("first_script", false)) # 缺省 = 非首次(抽签路径;真实侧由呈现层读存档布尔位传入)
|
_first_script = bool(params.get("first_script", false)) # 缺省 = 非首次(抽签路径;真实侧由呈现层读存档布尔位传入)
|
||||||
_aux_unlocked = params.get("aux_unlocked", {}) if params.get("aux_unlocked", {}) is Dictionary else {} # additive(迭代 7 T5)
|
_aux_unlocked = params.get("aux_unlocked", {}) if params.get("aux_unlocked", {}) is Dictionary else {}
|
||||||
|
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive(迭代 11 第①批 T5)
|
||||||
|
_attune_refresh_y = maxi(0, int(params.get("attune_refresh_y", 0))) # additive(迭代 11 第①批 T5b——Y 冒险周期池)
|
||||||
|
_seed_attune_charges() # 迭代 11 第①批 T6——冒险开始充能账本按装备主动件重置(每冒险充能语义) # additive(迭代 7 T5)
|
||||||
|
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive(迭代 11 第①批 T5)
|
||||||
|
_attune_refresh_y = maxi(0, int(params.get("attune_refresh_y", 0))) # additive(迭代 11 第①批 T5b——Y 冒险周期池)
|
||||||
|
_seed_attune_charges() # 迭代 11 第①批 T6——冒险开始充能账本按装备主动件重置(每冒险充能语义)
|
||||||
ledger["hero_hp"] = effective_max_hp() # 生命账本置满(生效上限口径——T17 修复:含 TunePanel pct,与战斗内同源)
|
ledger["hero_hp"] = effective_max_hp() # 生命账本置满(生效上限口径——T17 修复:含 TunePanel pct,与战斗内同源)
|
||||||
state = "running"
|
state = "running"
|
||||||
return _start_current_level()
|
return _start_current_level()
|
||||||
@@ -168,6 +178,9 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
|||||||
int(round(float(int(db.hero.max_hp) + int(_base_override.get("max_hp", 0))) * (100.0 + float(_override[1])) / 100.0))))
|
int(round(float(int(db.hero.max_hp) + int(_base_override.get("max_hp", 0))) * (100.0 + float(_override[1])) / 100.0))))
|
||||||
_first_script = false # 续玩模板已随快照强制(已抽模板零重抽)——脚本模式不重放
|
_first_script = false # 续玩模板已随快照强制(已抽模板零重抽)——脚本模式不重放
|
||||||
_aux_unlocked = params.get("aux_unlocked", {}) if params.get("aux_unlocked", {}) is Dictionary else {}
|
_aux_unlocked = params.get("aux_unlocked", {}) if params.get("aux_unlocked", {}) is Dictionary else {}
|
||||||
|
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive(迭代 11 第①批 T5)
|
||||||
|
_attune_refresh_y = maxi(0, int(params.get("attune_refresh_y", 0))) # additive(迭代 11 第①批 T5b——Y 冒险周期池)
|
||||||
|
_seed_attune_charges() # 迭代 11 第①批 T6——冒险开始充能账本按装备主动件重置(每冒险充能语义)
|
||||||
# 三选一停点续玩分支(迭代 10——选择态/已兑付态不重跑关卡;奖励选择态不重抽、已兑付不重付)
|
# 三选一停点续玩分支(迭代 10——选择态/已兑付态不重跑关卡;奖励选择态不重抽、已兑付不重付)
|
||||||
var bp_pending: Dictionary = bp.get("bonus_pending", {}) as Dictionary
|
var bp_pending: Dictionary = bp.get("bonus_pending", {}) as Dictionary
|
||||||
var bp_result: Dictionary = bp.get("bonus_result", {}) as Dictionary
|
var bp_result: Dictionary = bp.get("bonus_result", {}) as Dictionary
|
||||||
@@ -197,6 +210,10 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
|||||||
bonus_resolved.emit(_bonus_result.duplicate(true))
|
bonus_resolved.emit(_bonus_result.duplicate(true))
|
||||||
return true
|
return true
|
||||||
_resume_bp = bp.duplicate(true)
|
_resume_bp = bp.duplicate(true)
|
||||||
|
# 迭代 11 第①批 T6——充能账本随断点回局(缺键 = 旧档/无断点,保持 _seed_attune_charges 种子态)
|
||||||
|
var bp_charges = bp.get("attunement_charges", null)
|
||||||
|
if bp_charges is Dictionary:
|
||||||
|
attune_charges = (bp_charges as Dictionary).duplicate(true)
|
||||||
state = "running"
|
state = "running"
|
||||||
return _start_current_level()
|
return _start_current_level()
|
||||||
|
|
||||||
@@ -315,15 +332,11 @@ func _pick_aux_item(count: int) -> bool:
|
|||||||
push_error("[冒险结构] pick_bonus(aux_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)")
|
push_error("[冒险结构] pick_bonus(aux_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)")
|
||||||
return false
|
return false
|
||||||
var hp_before := int(ledger.get("hero_hp", 0))
|
var hp_before := int(ledger.get("hero_hp", 0))
|
||||||
if AuxItems.can_accept(picked.size(), _bonus_aux_carry.size(), db): # 同调位守卫单点(2026-10-02)
|
# 迭代 11 第①批 T7——同调位守卫随过渡键退役拆除(aux 卡出池后本路径不可达;能力件容量闸
|
||||||
for iid in picked:
|
# 正主 = 营地装配面 Attunement.slots 满位禁选)
|
||||||
_bonus_aux_carry.append(String(iid))
|
for iid in picked:
|
||||||
_finish_bonus_pick("aux_item", picked.size(), hp_before)
|
_bonus_aux_carry.append(String(iid))
|
||||||
return true
|
_finish_bonus_pick("aux_item", picked.size(), hp_before)
|
||||||
# 满位替换子交互:现有持有件 + 新件 N+1 选 N(丢旧收新 / 放弃新件两路,终态恒 ≤ 同调位)
|
|
||||||
_bonus_pending["chosen"] = "aux_item"
|
|
||||||
_bonus_pending["incoming"] = picked.duplicate()
|
|
||||||
aux_replace_presented.emit(_carry_records(), {"index": -1, "item_id": String(picked[0]), "count": picked.size()})
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
@@ -372,6 +385,7 @@ func node_bonus_candidates() -> Dictionary:
|
|||||||
|
|
||||||
|
|
||||||
func present_node_bonus(candidates: Array, aux_detail: Array = []) -> void:
|
func present_node_bonus(candidates: Array, aux_detail: Array = []) -> void:
|
||||||
|
_reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转)
|
||||||
_node_bonus_pending = {"candidates": candidates.duplicate(true), "aux_detail": aux_detail.duplicate(true)}
|
_node_bonus_pending = {"candidates": candidates.duplicate(true), "aux_detail": aux_detail.duplicate(true)}
|
||||||
|
|
||||||
|
|
||||||
@@ -405,18 +419,14 @@ func _apply_node_aux(count: int) -> bool:
|
|||||||
var records: Array = []
|
var records: Array = []
|
||||||
if _runner != null and _runner.aux_inventory != null:
|
if _runner != null and _runner.aux_inventory != null:
|
||||||
records = _runner.aux_inventory.holding_records()
|
records = _runner.aux_inventory.holding_records()
|
||||||
var holding := records.size() + _bonus_aux_carry.size()
|
# 迭代 11 第①批 T7——守卫随过渡键退役拆除(BONUS_IDS 已除名 aux_item,本分支不可达;
|
||||||
if AuxItems.can_accept(picked.size(), holding, db): # 同调位守卫单点(2026-10-02)
|
# 替换子交互路由随容量闸一并退役——能力件容量闸正主 = 营地装配面满位禁选)
|
||||||
if _runner != null:
|
if _runner != null:
|
||||||
for iid in picked:
|
for iid in picked:
|
||||||
var rec: Dictionary = _runner.grant_aux_item(String(iid), "reward_node")
|
var rec: Dictionary = _runner.grant_aux_item(String(iid), "reward_node")
|
||||||
if rec.is_empty():
|
if rec.is_empty():
|
||||||
push_warning("[冒险结构] 奖励节点道具授予被拒(防御兜底留痕): %s" % String(iid))
|
push_warning("[冒险结构] 奖励节点道具授予被拒(防御兜底留痕): %s" % String(iid))
|
||||||
_finish_node_bonus("aux_item", picked.size())
|
_finish_node_bonus("aux_item", picked.size())
|
||||||
return true
|
|
||||||
_node_bonus_pending["chosen"] = "aux_item"
|
|
||||||
_node_bonus_pending["incoming"] = picked.duplicate()
|
|
||||||
aux_replace_presented.emit(records, {"handle": -1, "item_id": String(picked[0]), "count": picked.size()})
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
@@ -436,6 +446,71 @@ func _resolve_node_replace(discard_handle: int) -> bool:
|
|||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
# 冒险开始充能账本重置(T6——装备 active_charged 件各回 charges_per_adventure;被动件不入账)
|
||||||
|
func _seed_attune_charges() -> void:
|
||||||
|
attune_charges = {}
|
||||||
|
for iid in _attunement:
|
||||||
|
var arow: Dictionary = db.aux_item_row(String(iid))
|
||||||
|
if String(arow.get("form", "")) == "active_charged":
|
||||||
|
attune_charges[String(iid)] = maxi(0, int(arow.get("charges_per_adventure", 0)))
|
||||||
|
|
||||||
|
|
||||||
|
# ---- 三选一刷新(迭代 11 第①批 T5b——P1 甲 X/Y 双源:X=刷新件每碰三选一重给〔优先消耗、不结转〕、
|
||||||
|
# Y=营地升级行冒险周期池〔用完即无〕;重抽消费 reward_pick 既有洗牌流,池规则全保留——不放回/
|
||||||
|
# 达上限剔牌/掷定前置直显;停点可中断)----
|
||||||
|
|
||||||
|
func _attune_refresh_x_total() -> int:
|
||||||
|
return int(db.aux_item_row("pick_refresh").get("magnitude", 0)) if _attunement.has("pick_refresh") else 0
|
||||||
|
|
||||||
|
|
||||||
|
func _reset_refresh_x() -> void:
|
||||||
|
_attune_refresh_x = _attune_refresh_x_total()
|
||||||
|
|
||||||
|
|
||||||
|
# 当前可刷新次数(X 优先 + Y 池——呈现层余量读数口)
|
||||||
|
func attune_refresh_left() -> int:
|
||||||
|
return _attune_refresh_x + _attune_refresh_y
|
||||||
|
|
||||||
|
|
||||||
|
# 节点停点在途态读回(组合根重 present 用——candidates/aux_detail 值拷贝)
|
||||||
|
func node_bonus_pending_state() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"candidates": (_node_bonus_pending.get("candidates", []) as Array).duplicate(true),
|
||||||
|
"aux_detail": (_node_bonus_pending.get("aux_detail", []) as Array).duplicate(true),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# 三选一刷新(组合根唯一调用口;无停点/无余量 false 零副作用)。通关停点重抽后重发
|
||||||
|
# bonus_choice_presented(呈现层重建卡片+刷新余量);节点停点由组合根读 node_bonus_pending_state 重 present。
|
||||||
|
func refresh_bonus() -> bool:
|
||||||
|
if _attune_refresh_x <= 0 and _attune_refresh_y <= 0:
|
||||||
|
return false
|
||||||
|
if not _bonus_pending.is_empty():
|
||||||
|
if _attune_refresh_x > 0:
|
||||||
|
_attune_refresh_x -= 1
|
||||||
|
else:
|
||||||
|
_attune_refresh_y -= 1
|
||||||
|
var cands: Array = _draw_bonus_candidates()
|
||||||
|
_bonus_pending["candidates"] = cands.duplicate(true)
|
||||||
|
_bonus_pending["aux_detail"] = _last_draw_aux.duplicate()
|
||||||
|
var payload: Dictionary = (_bonus_pending.get("payload", {}) as Dictionary).duplicate(true)
|
||||||
|
payload["candidates"] = cands
|
||||||
|
payload["aux_detail"] = _last_draw_aux.duplicate()
|
||||||
|
bonus_choice_presented.emit(payload) # 同信号重 present(零自动推进契约不破——停点仍在途)
|
||||||
|
_snapshot_breakpoint()
|
||||||
|
return true
|
||||||
|
if not _node_bonus_pending.is_empty():
|
||||||
|
if _attune_refresh_x > 0:
|
||||||
|
_attune_refresh_x -= 1
|
||||||
|
else:
|
||||||
|
_attune_refresh_y -= 1
|
||||||
|
var cands2: Array = _draw_bonus_candidates()
|
||||||
|
_node_bonus_pending["candidates"] = cands2.duplicate(true)
|
||||||
|
_node_bonus_pending["aux_detail"] = _last_draw_aux.duplicate()
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
# 替换在途守卫(2026-10-02 修复批——双渠道替换受理的共用前置谓词:停点态非空且 chosen=aux_item)
|
# 替换在途守卫(2026-10-02 修复批——双渠道替换受理的共用前置谓词:停点态非空且 chosen=aux_item)
|
||||||
func _replace_in_transit(pending: Dictionary) -> bool:
|
func _replace_in_transit(pending: Dictionary) -> bool:
|
||||||
return not pending.is_empty() and String(pending.get("chosen", "")) == "aux_item"
|
return not pending.is_empty() and String(pending.get("chosen", "")) == "aux_item"
|
||||||
@@ -501,12 +576,8 @@ func _draw_bonus_candidates() -> Array:
|
|||||||
keyed.sort_custom(func(a, b): return float(a["key"]) > float(b["key"]))
|
keyed.sort_custom(func(a, b): return float(a["key"]) > float(b["key"]))
|
||||||
var out: Array = []
|
var out: Array = []
|
||||||
for i in mini(int(db.adventure.bonus_candidate_count), keyed.size()): # 候选数读表(2026-10-02 魔法数结构化——默认 3 = 现行行为)
|
for i in mini(int(db.adventure.bonus_candidate_count), keyed.size()): # 候选数读表(2026-10-02 魔法数结构化——默认 3 = 现行行为)
|
||||||
var cid := String(keyed[i]["id"])
|
out.append(String(keyed[i]["id"]))
|
||||||
out.append(cid)
|
# 迭代 11 第①批 T7——aux_item 掷定前置分支随池种退役拆除(BONUS_IDS 已除名,候选恒无 aux 卡)
|
||||||
if cid == "aux_item":
|
|
||||||
var roll_rg: RandomNumberGenerator = rng.open_stream("reward_pick")
|
|
||||||
for j in maxi(1, int(db.adventure.bonus_aux_item_count)):
|
|
||||||
_last_draw_aux.append(_roll_from(roll_rg))
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
@@ -592,11 +663,10 @@ func _start_current_level() -> bool:
|
|||||||
var ok: bool = _runner.start_level_external(level_index, ledger, bonus, purchased,
|
var ok: bool = _runner.start_level_external(level_index, ledger, bonus, purchased,
|
||||||
Callable(self, "apply_rest"), _base_override, true, template_pick, resume_state)
|
Callable(self, "apply_rest"), _base_override, true, template_pick, resume_state)
|
||||||
if ok:
|
if ok:
|
||||||
# 三选一道具件跨关携带注入(迭代 10——上关兑付的道具件进本关库存〔奖励只朝下一战生效〕;
|
_runner.set_attunement(_attunement) # 迭代 11 第①批 T5——同调装配下传(enc 携带进战斗结算)
|
||||||
# 注入即移交——carry 清空,本关末随关卡域库存清空)
|
_runner.set_attune_charges(attune_charges) # 迭代 11 第①批 T6——充能账本下传(能力件骑库存面注入)
|
||||||
for iid in _bonus_aux_carry:
|
# 三选一道具件跨关携带注入(迭代 10)——迭代 11 第①批 T7 退役:aux 卡出池后 carry 恒空集,
|
||||||
_runner.grant_aux_item(iid, "bonus_pick")
|
# 注入面随停点渠道退役拆除(容器字段保留=替换子交互机械惰性承载,零投放零行为)
|
||||||
_bonus_aux_carry = []
|
|
||||||
level_started.emit(level_index, String(_runner.level_row.get("display_name", "")))
|
level_started.emit(level_index, String(_runner.level_row.get("display_name", "")))
|
||||||
# 入口停点断点(node_id=-1)与 defer_entry 同态(node_index=-1+单候选)——走入口呈现;
|
# 入口停点断点(node_id=-1)与 defer_entry 同态(node_index=-1+单候选)——走入口呈现;
|
||||||
# 中途停点(node_id>=0)走停点呈现(restore_aux_items 先于呈现=C 区库存就位)。
|
# 中途停点(node_id>=0)走停点呈现(restore_aux_items 先于呈现=C 区库存就位)。
|
||||||
@@ -635,6 +705,7 @@ func _on_level_finished(payload: Dictionary) -> void:
|
|||||||
ledger["hero_hp"] = hp_before + heal
|
ledger["hero_hp"] = hp_before + heal
|
||||||
# 置位先于 emit(信号同步派发——处理器内立即确认时 _pending_next_level 必须已就位)
|
# 置位先于 emit(信号同步派发——处理器内立即确认时 _pending_next_level 必须已就位)
|
||||||
_pending_next_level = true
|
_pending_next_level = true
|
||||||
|
_reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转)
|
||||||
var pick_payload: Dictionary = {
|
var pick_payload: Dictionary = {
|
||||||
"level_index": level_index,
|
"level_index": level_index,
|
||||||
"candidates": _draw_bonus_candidates(),
|
"candidates": _draw_bonus_candidates(),
|
||||||
@@ -724,6 +795,7 @@ func _snapshot_breakpoint() -> void:
|
|||||||
},
|
},
|
||||||
"gp": int(ledger.get("gp", 0)),
|
"gp": int(ledger.get("gp", 0)),
|
||||||
"shop_purchased": purchased.duplicate(true),
|
"shop_purchased": purchased.duplicate(true),
|
||||||
|
"attunement_charges": attune_charges.duplicate(true), # 迭代 11 第①批 T6——同调主动件充能余量随断点(save v4 域)
|
||||||
"template_pick": _runner.active_template_index if _runner != null else -1,
|
"template_pick": _runner.active_template_index if _runner != null else -1,
|
||||||
"master_seed": master_seed,
|
"master_seed": master_seed,
|
||||||
"rng_counter": rng.counter(),
|
"rng_counter": rng.counter(),
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||||
|
# Validation Question: 同调位读数是否由「通关次数 + Boss 击杀数」经 attunement_growth.tres 谓词表
|
||||||
|
# 实时求值(Σ达成 grant clamp slot_cap),迭代 10 过渡键 transitional_sync_slots 得以退役(P0)
|
||||||
|
# Date: 2026-10-03(迭代 11 第①批 魔法道具同调实施批 T3——任务书 v1.1 §六 T3;设计权威底稿 proposal §2)
|
||||||
|
# 同调位求值服务:计数源组装(save 账本 → 计数字典)与实时求值,纯函数零副作用。
|
||||||
|
# 读数源切换(迭代 10 欠账③):装配面消费方(营地装配覆盖层 T4/冒险开始装配注入 T6)一律经本服务取
|
||||||
|
# Slots;过渡键 adventure.transitional_sync_slots 的在途读取面(aux_items 持有上限 gate/战斗内沉没/
|
||||||
|
# 停点替换交互/main 同调位呈现)系过渡渠道承载——T7 退役面切换时随渠道一并消亡(该批读数源切换的
|
||||||
|
# 正主落点,06 欠账③销账注记随 T7/T9)。过渡期切早 = off 态 MC 对 81000 锚添新扰动(fresh 档计数全零
|
||||||
|
# → Slots=0 ≠ 过渡键 1),违「attune=off 逐位一致」🔴 契约——故本服务先立、在途面 T7 切。
|
||||||
|
# attune=off 纪律:零 await、零 RNG、零写副作用(🔴 契约 3 的服务侧承载)。
|
||||||
|
class_name Attunement
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
|
||||||
|
# 计数源组装(save = SaveManager 或 null——模拟/冒烟未注入档,按全零计数求值)
|
||||||
|
static func counters_from_save(save) -> Dictionary:
|
||||||
|
if save == null:
|
||||||
|
return {}
|
||||||
|
return save.attunement_counters()
|
||||||
|
|
||||||
|
|
||||||
|
# 同调位实时求值(营地进冒险前求值一次 + 装配覆盖层实时呈现共用同一口径)
|
||||||
|
static func slots(db, save) -> int:
|
||||||
|
if db == null or db.attunement_growth == null:
|
||||||
|
return 0
|
||||||
|
return db.attunement_growth.slots(counters_from_save(save))
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bwccv5x51gs23
|
||||||
@@ -73,13 +73,9 @@ func holding_count() -> int:
|
|||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
||||||
# 同调位守卫谓词(2026-10-02 修复批——四处手抄收口单点:战斗结算授予路由 / 关卡授予 / 三选一双渠道):
|
# 同调位守卫谓词——迭代 11 第①批 T7 退役(读取源 transitional_sync_slots 过渡键已随表退役;
|
||||||
# holding = 调用方渠道的当前实际持有数(库存渠道传 holding_count()、通关渠道传跨关携带数、
|
# 容量闸正主 = 营地装配面 Attunement.slots 满位禁选。函数体保留空壳防四散引用误删遗漏——
|
||||||
# 节点渠道传库存+携带合计);读数源 = db.adventure.transitional_sync_slots(负值钳 0 口径同前)。
|
# T8 冒烟「过渡键读取点归零」断言覆盖)
|
||||||
# 返回再入 n 件是否仍在位内。挂 static:谓词纯算术不依赖实例态——通关渠道(跨关携带)没有
|
|
||||||
# AuxItems 实例可用,实例方法形态不成立;db 参数不标类型(同 _db 桩表注入纪律)。
|
|
||||||
static func can_accept(n: int, holding: int, db) -> bool:
|
|
||||||
return holding + n <= maxi(0, int(db.adventure.transitional_sync_slots))
|
|
||||||
|
|
||||||
|
|
||||||
# 持有件记录(迭代 10 追加批——奖励节点替换子交互数据源:只列未消耗件,handle 定位丢弃目标)
|
# 持有件记录(迭代 10 追加批——奖励节点替换子交互数据源:只列未消耗件,handle 定位丢弃目标)
|
||||||
@@ -108,7 +104,7 @@ func _usable_now(item_id: String, board_has_preview_info: Callable) -> bool:
|
|||||||
if row.is_empty():
|
if row.is_empty():
|
||||||
return false
|
return false
|
||||||
var kinds: Array = row.get("effect_kinds", [])
|
var kinds: Array = row.get("effect_kinds", [])
|
||||||
if kinds.has("mine_reveal") or kinds.has("ap_grant"):
|
if kinds.has("mine_reveal") or kinds.has("ap_grant") or kinds.has("ap_charge_safe"):
|
||||||
return true
|
return true
|
||||||
return bool(board_has_preview_info.call()) if board_has_preview_info.is_valid() else true
|
return bool(board_has_preview_info.call()) if board_has_preview_info.is_valid() else true
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,24 @@ var _refresh_used_this_round := 0 # 每轮刷新计数(Active 相位
|
|||||||
var _board_seq := 0 # 板序号(本战斗第几张板——快照归因用)
|
var _board_seq := 0 # 板序号(本战斗第几张板——快照归因用)
|
||||||
var _board_from_midround_refresh := false # 装板 provenance:轮内刷新换来的图 = true(清图奖励守卫——刷新图不发清图奖励)
|
var _board_from_midround_refresh := false # 装板 provenance:轮内刷新换来的图 = true(清图奖励守卫——刷新图不发清图奖励)
|
||||||
var _flag_consumed := {} # 兑付资格以雷格实例为键(板级;换图重置)
|
var _flag_consumed := {} # 兑付资格以雷格实例为键(板级;换图重置)
|
||||||
|
# 同调装配运行态(迭代 11 第①批 T5——begin_battle 经 enc.attunement 注入;off=空集全部效果零扰动)
|
||||||
|
var attunement: Array = [] # 装备中同调件 item_id 集
|
||||||
|
var _attune_charges_ref: Dictionary = {} # 充能账本共享引用(迭代 11 第①批 T6——use_aux 消费直改)
|
||||||
|
var _attune_bank := 0 # 掘进蓄能积点(battle 域——每 charge_n 安全格 +1 点)
|
||||||
|
var _attune_safe_seen := 0 # 掘进蓄能 battle 域安全开格计数
|
||||||
|
|
||||||
|
|
||||||
|
func _attune_has(item_id: String) -> bool:
|
||||||
|
return attunement.has(item_id)
|
||||||
|
|
||||||
|
|
||||||
|
# 装备件表值读取(magnitude/effective_rounds 等——AuxItemTable 行;未装配不读表)
|
||||||
|
func _attune_row(item_id: String) -> Dictionary:
|
||||||
|
return db.aux_item_row(item_id) if _attune_has(item_id) else {}
|
||||||
|
|
||||||
|
|
||||||
|
func _attune_mag(item_id: String) -> int:
|
||||||
|
return int(_attune_row(item_id).get("magnitude", 0))
|
||||||
var _flag_miss_seen := {} # 标旗未中行去重(每安全格实例一次)
|
var _flag_miss_seen := {} # 标旗未中行去重(每安全格实例一次)
|
||||||
var _perfect_hit_true := 0 # 板级完美排雷计数(install 重置)
|
var _perfect_hit_true := 0 # 板级完美排雷计数(install 重置)
|
||||||
var _perfect_hit_false := 0
|
var _perfect_hit_false := 0
|
||||||
@@ -129,12 +147,23 @@ func begin_battle(enc: Dictionary) -> bool:
|
|||||||
if not _install_new_board():
|
if not _install_new_board():
|
||||||
return false
|
return false
|
||||||
_reset_round_facts() # 首板 install 的初始展开事件不入轮 facts(resource 恒 false 的双保险)
|
_reset_round_facts() # 首板 install 的初始展开事件不入轮 facts(resource 恒 false 的双保险)
|
||||||
|
# 同调装配运行态注入(迭代 11 第①批 T5——enc.attunement 携带;off=空集零扰动。淬毒 N 注入
|
||||||
|
# 伤害控制器、偏运 N 注入地图战斗资源——均须先于首板 install)
|
||||||
|
attunement = (enc.get("attunement", []) as Array).duplicate()
|
||||||
|
_attune_charges_ref = enc.get("attune_charges", {}) # 共享 dict 引用(level/adventure 同账本——use_aux 扣减直改)
|
||||||
|
_attune_bank = 0
|
||||||
|
_attune_safe_seen = 0
|
||||||
|
_damage.attune_venom = _attune_mag("venom_edge") if _attune_has("venom_edge") else 0
|
||||||
|
_reward_economy.luck_bias = _attune_mag("luck_coin") if _attune_has("luck_coin") else 0
|
||||||
# 迭代 10 奖励重构批——跨冒险行动点加成轴(统一冒险加算区第四轴):经排队修正通道兑现
|
# 迭代 10 奖励重构批——跨冒险行动点加成轴(统一冒险加算区第四轴):经排队修正通道兑现
|
||||||
# 「每场战斗开始首轮 AP +加成值」(queue_ap_delta = 下轮开轮基线加算、notify_round_start 兑现后
|
# 「每场战斗开始首轮 AP +加成值」(queue_ap_delta = 下轮开轮基线加算、notify_round_start 兑现后
|
||||||
# 清零——首轮即消费,后续轮次不重复兑现;语义锚 = 行动经济 GDD「跨冒险行动点加成轴」节)
|
# 清零——首轮即消费,后续轮次不重复兑现;语义锚 = 行动经济 GDD「跨冒险行动点加成轴」节)
|
||||||
var ap_bonus: int = int(enc.get("hero_ap_bonus", 0))
|
var ap_bonus: int = int(enc.get("hero_ap_bonus", 0))
|
||||||
if ap_bonus > 0:
|
if ap_bonus > 0:
|
||||||
action_economy.queue_ap_delta(ap_bonus)
|
action_economy.queue_ap_delta(ap_bonus)
|
||||||
|
# 工兵晨令(迭代 11 同调批 T5——前 effective_rounds 轮每轮 +1 AP;首轮在此入队,后续轮在轮始段续队)
|
||||||
|
if _attune_has("dawn_order"):
|
||||||
|
action_economy.queue_ap_delta(_attune_mag("dawn_order"))
|
||||||
if not action_economy.notify_round_start():
|
if not action_economy.notify_round_start():
|
||||||
_fatal("行动经济 首轮 notify_round_start 被拒")
|
_fatal("行动经济 首轮 notify_round_start 被拒")
|
||||||
return false
|
return false
|
||||||
@@ -305,6 +334,12 @@ func _settle() -> void:
|
|||||||
_emit_row("reward_ineffective", "safe_cell", evt.cell, ri, "奖励作废(插旗格直接挖开——零惩罚零奖励)", 0, {}, "")
|
_emit_row("reward_ineffective", "safe_cell", evt.cell, ri, "奖励作废(插旗格直接挖开——零惩罚零奖励)", 0, {}, "")
|
||||||
continue
|
continue
|
||||||
_grant_routed(snap, "positive", "safe_cell", evt.cell, ri, "")
|
_grant_routed(snap, "positive", "safe_cell", evt.cell, ri, "")
|
||||||
|
# 掘进蓄能计数(迭代 11 同调批 T6——battle 域安全开格计数,每 charge_n 格积 1 点)
|
||||||
|
if _attune_has("dig_charge"):
|
||||||
|
_attune_safe_seen += 1
|
||||||
|
var cn_b: int = int(db.aux_item_row("dig_charge").get("charge_n", 999))
|
||||||
|
if cn_b > 0 and _attune_safe_seen % cn_b == 0:
|
||||||
|
_attune_bank += 1
|
||||||
elif evt.kind == "mine":
|
elif evt.kind == "mine":
|
||||||
# 插旗不可逆四情况②:插雷格再挖开 = 只受挖雷一层惩罚(mine_hit 照常兑付——惊动巢穴),
|
# 插旗不可逆四情况②:插雷格再挖开 = 只受挖雷一层惩罚(mine_hit 照常兑付——惊动巢穴),
|
||||||
# 标中不发(旗已随挖掘擦除、不入轮末判定基线)。
|
# 标中不发(旗已随挖掘擦除、不入轮末判定基线)。
|
||||||
@@ -360,30 +395,8 @@ func _settle() -> void:
|
|||||||
# 「动作 × 格型」矩阵的执行面:snap = 缓冲期冻结身份快照(cell_type = 该格装板期摇定的类型身份),
|
# 「动作 × 格型」矩阵的执行面:snap = 缓冲期冻结身份快照(cell_type = 该格装板期摇定的类型身份),
|
||||||
# polarity 由动作决定(开安全格/标中旗 = positive,开雷格/标错旗 = negative),
|
# polarity 由动作决定(开安全格/标中旗 = positive,开雷格/标错旗 = negative),
|
||||||
# 兑付行 = 奖励字典(类型, 极性)行。返回 true = percent 行实际生效(flat 行恒 false——兑付结果由行协议承载)。
|
# 兑付行 = 奖励字典(类型, 极性)行。返回 true = percent 行实际生效(flat 行恒 false——兑付结果由行协议承载)。
|
||||||
# ---- 魔法道具(迭代 7 T2/T3——库存授予路由 + use_aux 四步受理编排;结构冻结见魔法道具 GDD)----
|
# ---- 魔法道具(use_aux 四步受理编排保留给同调主动件复用——迭代 11 第①批 T7:过渡授予路由
|
||||||
|
# _grant_aux 随掉落/停点渠道退役拆除;能力件注入归 LevelRunner.set_attune_charges 骑库存面)----
|
||||||
# 授予路由 aux 分支:正=入库存(发行「获得」行——逐行归因纪律);负=零兑付占位行。
|
|
||||||
func _grant_aux(snap: Dictionary, polarity: String, source_class: String, cell: Vector2i, ri: int) -> bool:
|
|
||||||
if polarity != "positive" or aux_inventory == null:
|
|
||||||
# 负路由占位行=effect 型零兑付(雷安同池下标错旗/雷格摇中 aux 身份的负向字典行——逐行归因纪律:
|
|
||||||
# 该格资源的负向兑付存在且为零;effect_id 空不入增益容器)
|
|
||||||
_emit_row("effect", source_class, cell, ri, "道具随雷沉没(惩罚格不入库存)", 0,
|
|
||||||
{"effect_id": "", "aux_sink": String(snap.get("effect_id", ""))}, "")
|
|
||||||
return false
|
|
||||||
var iid := String(snap.get("effect_id", ""))
|
|
||||||
# 同调位持有上限(迭代 10 奖励重构批过渡承载——transitional_sync_slots,provisional 1):
|
|
||||||
# 战斗内渠道遇满位沉没不入(战斗域零交互纪律——替换交互归停点渠道;正主 = 同调位谓词表,
|
|
||||||
# 全渠道统一收口随魔法道具同调实施批)。逐行归因纪律:沉没行照发(「获得」的零兑付形态)。
|
|
||||||
# 谓词单点 = AuxItems.can_accept(2026-10-02 修复批——四处手抄收口)。
|
|
||||||
if not AuxItems.can_accept(1, aux_inventory.holding_count(), db):
|
|
||||||
_emit_row("effect", source_class, cell, ri, "同调位已满,%s 沉没" % String(db.aux_item_row(iid).get("display_name", iid)), 0,
|
|
||||||
{"effect_id": "", "aux_sink": iid, "sink_reason": "sync_slots_full"}, "")
|
|
||||||
return false
|
|
||||||
var arow: Dictionary = db.aux_item_row(iid)
|
|
||||||
var rec: Dictionary = aux_inventory.add_item(iid, source_class, cell, ri)
|
|
||||||
_emit_row("effect", source_class, cell, ri, "获得 %s" % String(arow.get("display_name", iid)), 1,
|
|
||||||
{"aux_item": iid, "handle": int(rec.get("handle", 0))}, "")
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
# use_aux 四步受理(registry use_orchestration 规范序——唯一调用权归本类;序即原子:
|
# use_aux 四步受理(registry use_orchestration 规范序——唯一调用权归本类;序即原子:
|
||||||
@@ -402,6 +415,12 @@ func use_aux(handle: int, target: Vector2i) -> Dictionary:
|
|||||||
func(c: Vector2i): return not board.is_revealed(c))
|
func(c: Vector2i): return not board.is_revealed(c))
|
||||||
if not bool(v.ok):
|
if not bool(v.ok):
|
||||||
return v # ② 拒绝零消耗
|
return v # ② 拒绝零消耗
|
||||||
|
# 掘进蓄能受理闸(迭代 11 同调批 T6——bank>0 才可发起,一行一点;积点不足拒绝零消耗)
|
||||||
|
if String(v.item_id) == "dig_charge" and _attune_has("dig_charge"):
|
||||||
|
if _attune_bank <= 0:
|
||||||
|
var cn_info: int = int(db.aux_item_row("dig_charge").get("charge_n", 0))
|
||||||
|
return {"ok": false, "reason": "掘进蓄能:安全格积点未达(%d/%d)" % [_attune_safe_seen % cn_info if cn_info > 0 else 0, cn_info]}
|
||||||
|
_attune_bank -= 1
|
||||||
var kinds: Array = v.kinds
|
var kinds: Array = v.kinds
|
||||||
if kinds.has("mine_reveal"):
|
if kinds.has("mine_reveal"):
|
||||||
aux_inventory.apply_overlay("mine_reveal", v.resolved)
|
aux_inventory.apply_overlay("mine_reveal", v.resolved)
|
||||||
@@ -410,7 +429,16 @@ func use_aux(handle: int, target: Vector2i) -> Dictionary:
|
|||||||
if kinds.has("ap_grant"):
|
if kinds.has("ap_grant"):
|
||||||
if not action_economy.grant_ap(int(v.magnitude)):
|
if not action_economy.grant_ap(int(v.magnitude)):
|
||||||
return {"ok": false, "reason": "行动点加量被拒(相位防御)"}
|
return {"ok": false, "reason": "行动点加量被拒(相位防御)"}
|
||||||
|
if kinds.has("ap_charge_safe"):
|
||||||
|
# 掘进蓄能兑付(迭代 11 同调批 T6——临时 AP 当轮生效,grant_ap 同 sapper_order 通道)
|
||||||
|
if not action_economy.grant_ap(int(v.magnitude)):
|
||||||
|
return {"ok": false, "reason": "行动点加量被拒(相位防御)"}
|
||||||
var rec: Dictionary = aux_inventory.consume(handle)
|
var rec: Dictionary = aux_inventory.consume(handle)
|
||||||
|
# 同调主动件充能扣减(迭代 11 第①批 T6——共享账本直改:adventure/level/settlement 同一字典引用)
|
||||||
|
if not rec.is_empty() and String(rec.get("source_class", "")) == "attunement":
|
||||||
|
var used_iid: String = String(rec.get("item_id", ""))
|
||||||
|
if _attune_charges_ref.has(used_iid):
|
||||||
|
_attune_charges_ref[used_iid] = maxi(0, int(_attune_charges_ref[used_iid]) - 1)
|
||||||
return {"ok": true, "item_id": v.item_id, "resolved": v.resolved, "record": rec}
|
return {"ok": true, "item_id": v.item_id, "resolved": v.resolved, "record": rec}
|
||||||
|
|
||||||
|
|
||||||
@@ -420,17 +448,26 @@ func set_aux_unlocked(ids: Dictionary) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _grant_routed(snap: Dictionary, polarity: String, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "") -> bool:
|
func _grant_routed(snap: Dictionary, polarity: String, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "") -> bool:
|
||||||
# 迭代 7 T3——aux 投放行授予路由(魔法道具 GDD:授予路由唯一调用方=战斗结算):正路由(挖开/标中)
|
# 迭代 11 第①批 T7——aux 授予分支随掉落/停点渠道退役拆除(reward 词表第十型 aux_item 保留但零投放:
|
||||||
# → 道具入关卡域库存(来源可归因);负路由(雷格/标错旗摇中 aux 身份——雷安同池)→ 零兑付
|
# 无 aux 行入池、BONUS_IDS 词表已除名);剩余类型走极性字典路由。
|
||||||
# 「道具随雷沉没」(雷格奖励沉没既有语义);清图批追加份同路由(清图翻倍语义对 aux 同样成立)。
|
|
||||||
if String(snap.get("cell_type", "")) == RewardTable.AUX_ITEM_TYPE:
|
|
||||||
return _grant_aux(snap, polarity, source_class, cell, ri)
|
|
||||||
var row: Dictionary = _reward_economy.polarity_row(String(snap.get("cell_type", "")), polarity)
|
var row: Dictionary = _reward_economy.polarity_row(String(snap.get("cell_type", "")), polarity)
|
||||||
if row.is_empty():
|
if row.is_empty():
|
||||||
_fatal("极性字典缺行: %s %s" % [String(snap.get("cell_type", "")), polarity])
|
_fatal("极性字典缺行: %s %s" % [String(snap.get("cell_type", "")), polarity])
|
||||||
return false
|
return false
|
||||||
if polarity == "positive":
|
if polarity == "positive":
|
||||||
row["power"] = int(snap.get("power", int(row.power))) # 区间行快照 power(身份批 roll 值)覆盖表值——授予期零随机口径(E14)
|
row["power"] = int(snap.get("power", int(row.power))) # 区间行快照 power(身份批 roll 值)覆盖表值——授予期零随机口径(E14)
|
||||||
|
# 惊巢反制(迭代 11 同调批 T5——底稿 §3:挖开雷格该次惩罚减半 + 反弹 N;duplicate 隔离——减半
|
||||||
|
# 不回写极性字典;仅 source_class=mine_hit 受理,标错旗/负向字典行走原通道)
|
||||||
|
if polarity == "negative" and source_class == "mine_hit" and _attune_has("nest_counter"):
|
||||||
|
row = row.duplicate()
|
||||||
|
var half: int = int(ceil(float(int(row.power)) / 2.0))
|
||||||
|
_emit_row("effect", source_class, cell, ri, "惊巢反制——挖雷惩罚减半(%d→%d)" % [int(row.power), half], 0, {"effect_id": "nest_counter"}, highlight)
|
||||||
|
row.power = half
|
||||||
|
var reflect: int = _attune_mag("nest_counter")
|
||||||
|
if reflect > 0:
|
||||||
|
var nct := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_nest_counter", "惊巢反制·反弹", "board",
|
||||||
|
reflect, DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, highlight, {})
|
||||||
|
_submit(nct, false)
|
||||||
return _dispatch_row(row, source_class, cell, ri, highlight, label_suffix)
|
return _dispatch_row(row, source_class, cell, ri, highlight, label_suffix)
|
||||||
|
|
||||||
|
|
||||||
@@ -537,6 +574,11 @@ func _judge_flag(evt: Dictionary, ri: int) -> void:
|
|||||||
return # 去重跳过(资格以雷格实例为键——排雷玩法 诚实重复上报的对侧);零明细行
|
return # 去重跳过(资格以雷格实例为键——排雷玩法 诚实重复上报的对侧);零明细行
|
||||||
_flag_consumed[cell] = true
|
_flag_consumed[cell] = true
|
||||||
_perfect_hit_true += 1
|
_perfect_hit_true += 1
|
||||||
|
# 标中殉爆(迭代 11 同调批 T5——底稿 §3:每标中一面旗,全体存活敌吃 N 群伤;无类型走 blast 通道)
|
||||||
|
if _attune_has("flag_burst"):
|
||||||
|
var fbt := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_flag_burst", "标中殉爆", "board",
|
||||||
|
_attune_mag("flag_burst"), DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, "highlight", {})
|
||||||
|
_submit(fbt, false)
|
||||||
var snap = _reward_economy.grant(cell)
|
var snap = _reward_economy.grant(cell)
|
||||||
if not (snap is Dictionary):
|
if not (snap is Dictionary):
|
||||||
return
|
return
|
||||||
@@ -747,6 +789,9 @@ func _exit_distribution() -> void:
|
|||||||
if not _install_new_board():
|
if not _install_new_board():
|
||||||
_fatal("换图编排失败")
|
_fatal("换图编排失败")
|
||||||
return
|
return
|
||||||
|
# 工兵晨令续队(迭代 11 同调批 T5——upcoming 轮号 = round_index+1 ≤ effective_rounds 才续,P4 甲表值化)
|
||||||
|
if _attune_has("dawn_order") and round_index + 1 <= int(db.aux_item_row("dawn_order").get("effective_rounds", 0)):
|
||||||
|
action_economy.queue_ap_delta(_attune_mag("dawn_order"))
|
||||||
action_economy.notify_settlement_done(mode)
|
action_economy.notify_settlement_done(mode)
|
||||||
action_economy.notify_round_start()
|
action_economy.notify_round_start()
|
||||||
_reset_round_facts()
|
_reset_round_facts()
|
||||||
|
|||||||
+33
-2
@@ -25,6 +25,7 @@ const TABLE_MANIFEST := {
|
|||||||
"meta": "meta.tres", # 元进度实现批 2026-09-21(MetaTable 四行族——schema 与表同批落籍)
|
"meta": "meta.tres", # 元进度实现批 2026-09-21(MetaTable 四行族——schema 与表同批落籍)
|
||||||
"aux_items": "aux_items.tres", # 迭代 7 T1 魔法道具内容批(AuxItemTable 六行——定义表 Rule 4 豁免;罗盘 retired/失义件 deferred 标记见 schema 头注)
|
"aux_items": "aux_items.tres", # 迭代 7 T1 魔法道具内容批(AuxItemTable 六行——定义表 Rule 4 豁免;罗盘 retired/失义件 deferred 标记见 schema 头注)
|
||||||
"adventure": "adventure.tres", # 冒险结构实现批 2026-09-21(AdventureTable 建表——大奖励与钥石行值单点)
|
"adventure": "adventure.tres", # 冒险结构实现批 2026-09-21(AdventureTable 建表——大奖励与钥石行值单点)
|
||||||
|
"attunement_growth": "attunement_growth.tres", # 迭代 11 第①批 同调实施批 T1(AttunementGrowthTable 单行全局——同调位里程碑谓词表;单行先例 Rule 4 豁免)
|
||||||
}
|
}
|
||||||
|
|
||||||
var action_economy: ActionEconomyTable
|
var action_economy: ActionEconomyTable
|
||||||
@@ -45,6 +46,7 @@ var shop: ShopTable = null
|
|||||||
var meta: MetaTable
|
var meta: MetaTable
|
||||||
var aux_items: AuxItemTable # 迭代 7 T1 类型化持用(消费面走 aux_* 访问器)
|
var aux_items: AuxItemTable # 迭代 7 T1 类型化持用(消费面走 aux_* 访问器)
|
||||||
var adventure: AdventureTable
|
var adventure: AdventureTable
|
||||||
|
var attunement_growth: AttunementGrowthTable # 迭代 11 第①批 T1 类型化持用(消费面走 attunement_* 访问器)
|
||||||
|
|
||||||
var is_ready := false
|
var is_ready := false
|
||||||
var _diagnostics: Array[Dictionary] = [] # {severity, category, message}
|
var _diagnostics: Array[Dictionary] = [] # {severity, category, message}
|
||||||
@@ -135,6 +137,7 @@ func load_tables(root: String, collect_only: bool) -> bool:
|
|||||||
meta = loaded.get("meta")
|
meta = loaded.get("meta")
|
||||||
aux_items = loaded.get("aux_items")
|
aux_items = loaded.get("aux_items")
|
||||||
adventure = loaded.get("adventure")
|
adventure = loaded.get("adventure")
|
||||||
|
attunement_growth = loaded.get("attunement_growth")
|
||||||
is_ready = not has_errors
|
is_ready = not has_errors
|
||||||
return not has_errors
|
return not has_errors
|
||||||
|
|
||||||
@@ -211,14 +214,34 @@ func aux_item_row(item_id: String) -> Dictionary:
|
|||||||
return aux_items.row_by_id(item_id) if aux_items != null else {}
|
return aux_items.row_by_id(item_id) if aux_items != null else {}
|
||||||
|
|
||||||
|
|
||||||
# 活行词表(掉落投放 effect_id 白名单)
|
# 过渡渠道活行(form 空 = 切片期过渡形态)——掉落投放/停点池/工坊解锁取数口径;同调行(form 非空)
|
||||||
|
# 永不入过渡渠道(迭代 11 第①批 T1;T7 退役面切换后本口径随渠道消亡)
|
||||||
|
func aux_transitional_rows() -> Array:
|
||||||
|
return aux_items.transitional_rows() if aux_items != null else []
|
||||||
|
|
||||||
|
|
||||||
|
# 同调行集(form 非空——装配覆盖层/效果接线的取数口径,迭代 11 第①批 T1/T4)
|
||||||
|
func aux_attunement_rows() -> Array:
|
||||||
|
return aux_items.attunement_rows() if aux_items != null else []
|
||||||
|
|
||||||
|
|
||||||
|
# 活行词表(掉落投放 effect_id 白名单——过渡渠道口径)
|
||||||
func aux_live_ids() -> Dictionary:
|
func aux_live_ids() -> Dictionary:
|
||||||
var out := {}
|
var out := {}
|
||||||
for row in aux_active_rows():
|
for row in aux_transitional_rows():
|
||||||
out[String(row.get("item_id", ""))] = true
|
out[String(row.get("item_id", ""))] = true
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# ── 同调轴访问器(迭代 11 第①批 T1——消费面一律走访问器,禁直读行集)──
|
||||||
|
func attunement_slot_cap() -> int:
|
||||||
|
return attunement_growth.slot_cap if attunement_growth != null else 0
|
||||||
|
|
||||||
|
|
||||||
|
func attunement_milestones() -> Array:
|
||||||
|
return attunement_growth.milestones if attunement_growth != null else []
|
||||||
|
|
||||||
|
|
||||||
# 行族四轴(数据契约,行集归 shop.tres):gp_drops {tier, mean, roll_min, roll_max} ·
|
# 行族四轴(数据契约,行集归 shop.tres):gp_drops {tier, mean, roll_min, roll_max} ·
|
||||||
# level_gp_bonus {level_index, bonus} · products {product_id, display_name, attr, flat_value,
|
# level_gp_bonus {level_index, bonus} · products {product_id, display_name, attr, flat_value,
|
||||||
# base_price, price_increment} · price_level_bonus {level_index, bonus}。
|
# base_price, price_increment} · price_level_bonus {level_index, bonus}。
|
||||||
@@ -296,6 +319,14 @@ func meta_conversion_row(attr: String) -> Dictionary:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
# Y 刷新升级行单点读取(迭代 11 第①批 T4——键 = 等级,缺行返回空字典=未购/已满级合法态)
|
||||||
|
func meta_attune_refresh_row(level: int) -> Dictionary:
|
||||||
|
for row in _meta_rows("attune_refresh_upgrade"):
|
||||||
|
if int(row.get("level", -1)) == level:
|
||||||
|
return row
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
# 行族数组读取(MetaTable 属性名 = 行族名;表缺失/属性缺失 = 空集非错误态)
|
# 行族数组读取(MetaTable 属性名 = 行族名;表缺失/属性缺失 = 空集非错误态)
|
||||||
func _meta_rows(family: String) -> Array:
|
func _meta_rows(family: String) -> Array:
|
||||||
if meta == null:
|
if meta == null:
|
||||||
|
|||||||
@@ -196,7 +196,12 @@ func _resolve_hero_attack(t: Dictionary) -> void:
|
|||||||
var threshold: int = defender_ac(target) + int(db.settlement.attack_bonus)
|
var threshold: int = defender_ac(target) + int(db.settlement.attack_bonus)
|
||||||
var crit: bool = bool(roll_read.crit)
|
var crit: bool = bool(roll_read.crit)
|
||||||
var discount: int = _roll_discount(int(roll_read.roll), threshold)
|
var discount: int = _roll_discount(int(roll_read.roll), threshold)
|
||||||
var rolled: int = DamageMath.rolled_component(float(t.base_value), discount, crit, int(db.settlement.crit_multiplier), _counter_delta(atk_type, armor))
|
var cdelta: int = _counter_delta(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 rolled: int = DamageMath.rolled_component(float(t.base_value), discount, crit, int(db.settlement.crit_multiplier), cdelta)
|
||||||
var payload: Dictionary = {"target": String(target.display_name)}
|
var payload: Dictionary = {"target": String(target.display_name)}
|
||||||
payload[atk_type] = rolled # 分量键 = 实际攻击类型(归因字段,单分量模型)
|
payload[atk_type] = rolled # 分量键 = 实际攻击类型(归因字段,单分量模型)
|
||||||
payload["roll"] = int(roll_read.roll)
|
payload["roll"] = int(roll_read.roll)
|
||||||
@@ -326,6 +331,9 @@ func _counter_delta(attack_type: String, armor: String) -> int:
|
|||||||
return int(row.get(armor, 0))
|
return int(row.get(armor, 0))
|
||||||
|
|
||||||
|
|
||||||
|
# 淬毒刃纹 N(迭代 11 同调批 T5——战斗结算 begin_battle 注入;0 = 未装配零扰动)
|
||||||
|
var attune_venom: int = 0
|
||||||
|
|
||||||
# 池内加算 → clamp [-100,+100](池间乘算归 DamageMath.pool_damage 调用侧)
|
# 池内加算 → clamp [-100,+100](池间乘算归 DamageMath.pool_damage 调用侧)
|
||||||
func clamped_pool(pool: String) -> int:
|
func clamped_pool(pool: String) -> int:
|
||||||
var total := 0
|
var total := 0
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ var _shop_purchased := {} # product_id → 已购档数(现价
|
|||||||
var _settlement: BattleOrchestrator = null # 当前战斗编排器(关卡结束置 null)
|
var _settlement: BattleOrchestrator = null # 当前战斗编排器(关卡结束置 null)
|
||||||
var aux_inventory: AuxItems = null # 魔法道具 关卡域库存(迭代 7 T3——AuxItems;跨战斗延续,
|
var aux_inventory: AuxItems = null # 魔法道具 关卡域库存(迭代 7 T3——AuxItems;跨战斗延续,
|
||||||
var _aux_unlocked := {} # aux 解锁门控集(迭代 7 T5——拍板③甲;空集=零投放)
|
var _aux_unlocked := {} # aux 解锁门控集(迭代 7 T5——拍板③甲;空集=零投放)
|
||||||
|
var _attunement: Array = [] # 同调装配集(迭代 11 第①批 T5——adventure params.attunement 下传,begin_battle enc 携带进战斗结算;空集=off 零扰动)
|
||||||
|
var _attune_charges: Dictionary = {} # 同调主动件充能账本(迭代 11 第①批 T6——adventure 共享 dict 引用;settlement use_aux 扣减直改同引用)
|
||||||
# 关卡末清空+快照计数入 level_finished 载荷)
|
# 关卡末清空+快照计数入 level_finished 载荷)
|
||||||
|
|
||||||
var hero_atk_pct := 0 # 测试面板覆盖值(切片专用)
|
var hero_atk_pct := 0 # 测试面板覆盖值(切片专用)
|
||||||
@@ -464,6 +466,8 @@ func _start_battle(node: Dictionary) -> bool:
|
|||||||
if not _settlement.begin_battle({"level_index": int(level_row.level_index), "node_depth": int(node.get("node_depth", 0)),
|
if not _settlement.begin_battle({"level_index": int(level_row.level_index), "node_depth": int(node.get("node_depth", 0)),
|
||||||
"hero_hp": hero_hp, "hero_atk": effective_atk(), "hero_max_hp": effective_max_hp(),
|
"hero_hp": hero_hp, "hero_atk": effective_atk(), "hero_max_hp": effective_max_hp(),
|
||||||
"hero_armor_class": effective_ac(), "hero_ap_bonus": effective_ap_bonus(),
|
"hero_armor_class": effective_ac(), "hero_ap_bonus": effective_ap_bonus(),
|
||||||
|
"attunement": _attunement,
|
||||||
|
"attune_charges": _attune_charges,
|
||||||
"node_type": String(node.get("type", ""))}):
|
"node_type": String(node.get("type", ""))}):
|
||||||
push_error("[关卡节点地图] 战斗启动失败(depth %s)" % str(node.get("node_depth", -1)))
|
push_error("[关卡节点地图] 战斗启动失败(depth %s)" % str(node.get("node_depth", -1)))
|
||||||
return false
|
return false
|
||||||
@@ -567,15 +571,32 @@ func set_aux_unlocked(ids: Dictionary) -> void:
|
|||||||
if _settlement != null:
|
if _settlement != null:
|
||||||
_settlement.set_aux_unlocked(ids)
|
_settlement.set_aux_unlocked(ids)
|
||||||
|
|
||||||
|
|
||||||
|
# 同调装配集注入(迭代 11 第①批 T5——冒险开始参数面;AdventureRunner 唯一调用方)
|
||||||
|
func set_attunement(ids: Array) -> void:
|
||||||
|
_attunement = ids.duplicate()
|
||||||
|
|
||||||
|
|
||||||
|
# 同调主动件充能账本注入(迭代 11 第①批 T6——共享 dict 引用直改;能力件骑库存面注入:
|
||||||
|
# 差额注入防续玩双份——resume 路径 restore_inventory 已含断点内 attune 记录)
|
||||||
|
func set_attune_charges(charges: Dictionary) -> void:
|
||||||
|
_attune_charges = charges
|
||||||
|
if aux_inventory == null:
|
||||||
|
aux_inventory = AuxItems.new(db)
|
||||||
|
for iid in charges:
|
||||||
|
var existing := 0
|
||||||
|
for r in aux_inventory.snapshot_inventory():
|
||||||
|
if String(r.item_id) == String(iid) and String(r.source_class) == "attunement" and not bool(r.used):
|
||||||
|
existing += 1
|
||||||
|
for i in range(maxi(0, int(charges[iid]) - existing)):
|
||||||
|
aux_inventory.add_item(String(iid), "attunement", Vector2i(-1, -1), 0)
|
||||||
|
|
||||||
# 奖励节点兑付透传(迭代 7 T5——呈现层三选一 → 关卡域库存;来源归因 reward_node)。
|
# 奖励节点兑付透传(迭代 7 T5——呈现层三选一 → 关卡域库存;来源归因 reward_node)。
|
||||||
# 同调位持有上限(迭代 10 奖励重构批过渡承载):满位拒绝授予(奖励节点渠道过渡态 = 满位候选
|
# 迭代 11 第①批 T7——同调位持有闸随过渡键退役拆除(aux 卡出池后仅剩防御面;能力件容量闸
|
||||||
# 由呈现侧过滤不出;此处防御兜底)——替换交互归三选一渠道,全渠道统一收口随同调实施批。
|
# 正主 = 营地装配面 Attunement.slots 满位禁选)。
|
||||||
func grant_aux_item(item_id: String, source: String) -> Dictionary:
|
func grant_aux_item(item_id: String, source: String) -> Dictionary:
|
||||||
if aux_inventory == null:
|
if aux_inventory == null:
|
||||||
aux_inventory = AuxItems.new(db)
|
aux_inventory = AuxItems.new(db)
|
||||||
if not AuxItems.can_accept(1, aux_inventory.holding_count(), db): # 同调位守卫单点(2026-10-02)
|
|
||||||
push_warning("[关卡节点地图] 同调位已满,授予拒绝: %s(%s)" % [item_id, source])
|
|
||||||
return {}
|
|
||||||
return aux_inventory.add_item(item_id, source, Vector2i(-1, -1), 0)
|
return aux_inventory.add_item(item_id, source, Vector2i(-1, -1), 0)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -150,3 +150,65 @@ func hero_override() -> Dictionary:
|
|||||||
"max_hp": forge_flat("max_hp"),
|
"max_hp": forge_flat("max_hp"),
|
||||||
"armor_class": forge_flat("armor_class"),
|
"armor_class": forge_flat("armor_class"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ---- 同调位账本(迭代 11 第①批 T4——装配关系/同调位求值/Y 刷新升级;设计权威 proposal §2 + 任务书 P1 甲)----
|
||||||
|
|
||||||
|
# 装配切换(营地装配覆盖层唯一写口):已装 → 卸下恒可;未装 → 非同调行拒绝 + 满位禁选;
|
||||||
|
# 装配关系写存档持久(冒险中途不可换装——底稿 §8.5,本口只服务营地)。
|
||||||
|
func toggle_attunement(item_id: String) -> bool:
|
||||||
|
var equipped: Array = save.attunement_equipped()
|
||||||
|
if not equipped.has(item_id):
|
||||||
|
var arow: Dictionary = db.aux_item_row(item_id)
|
||||||
|
if arow.is_empty() or String(arow.get("form", "")) == "":
|
||||||
|
return false # 非同调行(过渡/退役行)禁装配
|
||||||
|
if equipped.size() >= Attunement.slots(db, save):
|
||||||
|
return false # 满位禁选
|
||||||
|
equipped.append(item_id)
|
||||||
|
else:
|
||||||
|
equipped.erase(item_id)
|
||||||
|
save.set_attunement_equipped(equipped)
|
||||||
|
return save.write_to_disk()
|
||||||
|
|
||||||
|
|
||||||
|
# 营地装配面投影(账面归本类、拼句归 CampOverlay——_camp_data 同款分工)
|
||||||
|
func attune_camp_status() -> Dictionary:
|
||||||
|
var equipped: Array = save.attunement_equipped()
|
||||||
|
var items: Array[Dictionary] = []
|
||||||
|
for row in db.aux_attunement_rows():
|
||||||
|
var iid := String(row.get("item_id", ""))
|
||||||
|
items.append({
|
||||||
|
"item_id": iid,
|
||||||
|
"display_name": String(row.get("display_name", "")),
|
||||||
|
"form": String(row.get("form", "")),
|
||||||
|
"equipped": equipped.has(iid),
|
||||||
|
})
|
||||||
|
var lvl := save.attune_refresh_level()
|
||||||
|
var next_row: Dictionary = db.meta_attune_refresh_row(lvl + 1)
|
||||||
|
return {
|
||||||
|
"slots": Attunement.slots(db, save),
|
||||||
|
"equipped": equipped,
|
||||||
|
"items": items,
|
||||||
|
"refresh": {
|
||||||
|
"level": lvl,
|
||||||
|
"charges": attune_refresh_charges(),
|
||||||
|
"price": int(next_row.get("keystone_cost", 0)),
|
||||||
|
"maxed": next_row.is_empty(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# 每冒险开局刷新次数 Y(当前等级行表值;0 级 = 0 次即无刷新能力——P1 甲初始 0 同构)
|
||||||
|
func attune_refresh_charges() -> int:
|
||||||
|
return int(db.meta_attune_refresh_row(save.attune_refresh_level()).get("refresh_charges", 0))
|
||||||
|
|
||||||
|
|
||||||
|
# Y 升级购买(直购价 = 下一级行表值 keystone_cost;已满级/余额不足 false 零副作用——buy_forge 同款)
|
||||||
|
func buy_attune_refresh() -> bool:
|
||||||
|
var next_row: Dictionary = db.meta_attune_refresh_row(save.attune_refresh_level() + 1)
|
||||||
|
if next_row.is_empty():
|
||||||
|
return false
|
||||||
|
if not save.spend_keystone(int(next_row.get("keystone_cost", 0))):
|
||||||
|
return false
|
||||||
|
save.set_attune_refresh_level(save.attune_refresh_level() + 1)
|
||||||
|
return save.write_to_disk()
|
||||||
@@ -59,7 +59,7 @@ func roll_board(eligible: Array) -> bool:
|
|||||||
_clear_granted = false
|
_clear_granted = false
|
||||||
_clear_snapshot = {}
|
_clear_snapshot = {}
|
||||||
var stream: RandomNumberGenerator = rng.open_stream("reward_roll") # 恰一 scope 批(格批 + 清图批尾 + 区间行追加取号)
|
var stream: RandomNumberGenerator = rng.open_stream("reward_roll") # 恰一 scope 批(格批 + 清图批尾 + 区间行追加取号)
|
||||||
var cell_rows: Array = _rollable_rows() # cell 池正向行 weight>0(类型频次)
|
var cell_rows: Array = _biased_cell_rows() # cell 池正向行 weight>0(偏运硬币装配时稀有正向 kinds 权重 +N——池结构零触碰)
|
||||||
var clear_rows: Array = _pool_rows("clear_default")
|
var clear_rows: Array = _pool_rows("clear_default")
|
||||||
# ① 身份批:每非封锁格恰一类型身份(雷安同池——同一行集同一权重分布)
|
# ① 身份批:每非封锁格恰一类型身份(雷安同池——同一行集同一权重分布)
|
||||||
var draft := {}
|
var draft := {}
|
||||||
@@ -174,6 +174,27 @@ func polarity_row(cell_type: String, polarity: String) -> Dictionary:
|
|||||||
|
|
||||||
# ---- F1 选行算子纯函数 ----
|
# ---- F1 选行算子纯函数 ----
|
||||||
|
|
||||||
|
# 偏运硬币 N(迭代 11 同调批 T5——战斗结算 begin_battle 注入;0 = 未装配零扰动)
|
||||||
|
var luck_bias: int = 0
|
||||||
|
# 稀有正向资源 kinds(底稿 §3——权重投影面,池结构零触碰)
|
||||||
|
const LUCK_BIAS_KINDS := ["heal", "gp", "pierce", "magic"]
|
||||||
|
|
||||||
|
|
||||||
|
# 偏运硬币权重投影(底稿 §3:只动池内权重、雷安仍同池同分布;luck_bias=0 逐位返回原行集——off 态零扰动)
|
||||||
|
func _biased_cell_rows() -> Array:
|
||||||
|
if luck_bias <= 0:
|
||||||
|
return _rollable_rows()
|
||||||
|
var out: Array = []
|
||||||
|
for row in _rollable_rows():
|
||||||
|
if LUCK_BIAS_KINDS.has(String(row.get("cell_type", ""))):
|
||||||
|
var biased: Dictionary = row.duplicate()
|
||||||
|
biased["weight"] = int(row.weight) + luck_bias
|
||||||
|
out.append(biased)
|
||||||
|
else:
|
||||||
|
out.append(row)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
func weighted_pick(rows: Array, r: int) -> String:
|
func weighted_pick(rows: Array, r: int) -> String:
|
||||||
var total := _pool_weight(rows)
|
var total := _pool_weight(rows)
|
||||||
assert(r >= 0 and r < total, "weighted_pick 取号越域(纯函数域守卫)")
|
assert(r >= 0 and r < total, "weighted_pick 取号越域(纯函数域守卫)")
|
||||||
|
|||||||
@@ -10,6 +10,12 @@
|
|||||||
# 域清单与 owner 分工(内容 owner 定口径,本类照单承载、不替任何人定义进度):
|
# 域清单与 owner 分工(内容 owner 定口径,本类照单承载、不替任何人定义进度):
|
||||||
# keystone_balance / forge_levels / item_unlocks / level_clear_counts / boss_first_kill
|
# keystone_balance / forge_levels / item_unlocks / level_clear_counts / boss_first_kill
|
||||||
# —— 内容 owner = 元进度.md(营地三柜台:钥石余额/属性锻造/道具工坊/通关次数/首杀标记)
|
# —— 内容 owner = 元进度.md(营地三柜台:钥石余额/属性锻造/道具工坊/通关次数/首杀标记)
|
||||||
|
# boss_kill_count / attunement_equipped / attune_refresh_level
|
||||||
|
# —— 内容 owner = 魔法道具.md(同调位账本,M-4 迭代 11 第①批 T2):Boss 击杀总数〔含重复〕=
|
||||||
|
# attunement_growth 谓词表计数源;装配关系 = 局外进度事实(元进度域同族——非契约 2 反面清单的
|
||||||
|
# 「局内道具库存」,后者=冒险进行中瞬态);attune_refresh_level = Y 刷新升级购买等级
|
||||||
|
# breakpoint.attunement_charges(主动件充能余量)——每冒险重置语义的断点瞬态附随件,内容装配归
|
||||||
|
# AdventureRunner(随 T6 接线入断点),本类照单承载;契约 2 域界注记同断点域豁免先例
|
||||||
# recent_summaries(冒险摘要五件事副本)—— 内容 owner = 冒险结构.md(摘要五件事口径)
|
# recent_summaries(冒险摘要五件事副本)—— 内容 owner = 冒险结构.md(摘要五件事口径)
|
||||||
# breakpoint(冒险断点快照——停点粒度续玩域)—— 内容 owner = 冒险结构.md(七字段拍板面
|
# breakpoint(冒险断点快照——停点粒度续玩域)—— 内容 owner = 冒险结构.md(七字段拍板面
|
||||||
# +恢复必需附随件:主种子/取数计数器/钥石累计/已到节点序/道具库存);契约 2 域界反面清单
|
# +恢复必需附随件:主种子/取数计数器/钥石累计/已到节点序/道具库存);契约 2 域界反面清单
|
||||||
@@ -30,8 +36,7 @@
|
|||||||
class_name SaveManager
|
class_name SaveManager
|
||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
const SAVE_FORMAT_VERSION := 3 # registry schema_version:v3=断点附随件扩列+摘要 keystone_drops(M-3 迭代 10 奖励重构;
|
const SAVE_FORMAT_VERSION := 4 # registry schema_version:v4=同调批扩域(meta 三新域 boss_kill_count/attunement_equipped/attune_refresh_level——M-4 迭代 11 第①批 T2;断点充能余量随 T6 接线,旧档缺键 = 合法空态);v3=断点附随件扩列+摘要 keystone_drops(M-3 迭代 10 奖励重构);v2=断点域出生(M-3 迭代 8 卡①);v1=首版;高版本拒读 owner=存档序列化
|
||||||
# 旧档缺键 = 合法空态);v2=断点域出生(M-3 迭代 8 卡①);v1=首版;高版本拒读 owner=存档序列化
|
|
||||||
const SUMMARY_CAP := 10 # 最近冒险摘要保留条数(最新在前)
|
const SUMMARY_CAP := 10 # 最近冒险摘要保留条数(最新在前)
|
||||||
|
|
||||||
const FORGE_ATTRS := ["max_hp", "atk", "armor_class"] # 属性锻造三档合法键(元进度.md 属性锻造柜台)
|
const FORGE_ATTRS := ["max_hp", "atk", "armor_class"] # 属性锻造三档合法键(元进度.md 属性锻造柜台)
|
||||||
@@ -48,6 +53,7 @@ func _init(path: String = "user://save.json") -> void:
|
|||||||
# 生产迁移链自注册(v2 起——测试钩 register_migration 仍可叠挂更早版本步骤)
|
# 生产迁移链自注册(v2 起——测试钩 register_migration 仍可叠挂更早版本步骤)
|
||||||
register_migration(1, _migrate_v1_v2)
|
register_migration(1, _migrate_v1_v2)
|
||||||
register_migration(2, _migrate_v2_v3)
|
register_migration(2, _migrate_v2_v3)
|
||||||
|
register_migration(3, _migrate_v3_v4)
|
||||||
|
|
||||||
|
|
||||||
# v1→v2:断点域出生(旧档无断点 = 合法空态——「继续上局」隐藏,非损坏非债务)
|
# v1→v2:断点域出生(旧档无断点 = 合法空态——「继续上局」隐藏,非损坏非债务)
|
||||||
@@ -66,6 +72,14 @@ func _migrate_v2_v3(data: Dictionary) -> Dictionary:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
# v3→v4:同调批扩域(迭代 11 第①批 T2——meta 三新域 boss_kill_count/attunement_equipped/
|
||||||
|
# attune_refresh_level,旧档缺键由 _normalize 按默认补齐,无结构迁移体;版本号推进仅为
|
||||||
|
# schema_version 对齐——v2→v3 先例)
|
||||||
|
func _migrate_v3_v4(data: Dictionary) -> Dictionary:
|
||||||
|
data["version"] = SAVE_FORMAT_VERSION
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
# ---- 默认数据模型(新档合法态——全零;白板存档 = 完整可玩〔元进度 单局循环零依赖〕)----
|
# ---- 默认数据模型(新档合法态——全零;白板存档 = 完整可玩〔元进度 单局循环零依赖〕)----
|
||||||
|
|
||||||
func _default_data() -> Dictionary[String, Variant]:
|
func _default_data() -> Dictionary[String, Variant]:
|
||||||
@@ -77,6 +91,9 @@ func _default_data() -> Dictionary[String, Variant]:
|
|||||||
"item_unlocks": {},
|
"item_unlocks": {},
|
||||||
"level_clear_counts": {"1": 0, "2": 0, "3": 0},
|
"level_clear_counts": {"1": 0, "2": 0, "3": 0},
|
||||||
"boss_first_kill": {},
|
"boss_first_kill": {},
|
||||||
|
"boss_kill_count": 0,
|
||||||
|
"attunement_equipped": [],
|
||||||
|
"attune_refresh_level": 0,
|
||||||
"recent_summaries": [],
|
"recent_summaries": [],
|
||||||
"first_adventure_done": false,
|
"first_adventure_done": false,
|
||||||
},
|
},
|
||||||
@@ -207,14 +224,55 @@ func boss_first_kill_marked(level_index: int) -> bool:
|
|||||||
return _data["meta"]["boss_first_kill"].has(str(level_index))
|
return _data["meta"]["boss_first_kill"].has(str(level_index))
|
||||||
|
|
||||||
|
|
||||||
|
# ---- 同调位账本域(迭代 11 第①批 T2——里程碑谓词表计数源 + 装配关系 + Y 升级态)----
|
||||||
|
|
||||||
|
# Boss 击杀总数(含重复——底稿裁决 4 计数源;收束入账 = apply_adventure_summary 逐通关关次 +1,
|
||||||
|
# Boss 恒唯一终点故 levels_cleared 即本次冒险 Boss 击杀数)
|
||||||
|
func boss_kill_count() -> int:
|
||||||
|
return int(_data["meta"].get("boss_kill_count", 0))
|
||||||
|
|
||||||
|
|
||||||
|
# 装配关系(同调目录 item_id 有序数组——装配序即呈现序;跨冒险持久,冒险中途不可换装〔底稿 §8.5〕)。
|
||||||
|
# 局外进度事实(元进度域同族),非契约 2 反面清单的「局内道具库存」——存档序列化.md 契约 2 随批注记。
|
||||||
|
func attunement_equipped() -> Array:
|
||||||
|
return (_data["meta"].get("attunement_equipped", []) as Array).duplicate()
|
||||||
|
|
||||||
|
|
||||||
|
func set_attunement_equipped(item_ids: Array) -> void:
|
||||||
|
var out: Array = []
|
||||||
|
for v in item_ids:
|
||||||
|
out.append(String(v))
|
||||||
|
_data["meta"]["attunement_equipped"] = out
|
||||||
|
|
||||||
|
|
||||||
|
# Y 刷新升级购买等级(0 = 未购 → 每冒险刷新次数 0;等级行值归营地升级行族——T4 接线)
|
||||||
|
func attune_refresh_level() -> int:
|
||||||
|
return int(_data["meta"].get("attune_refresh_level", 0))
|
||||||
|
|
||||||
|
|
||||||
|
func set_attune_refresh_level(n: int) -> void:
|
||||||
|
_data["meta"]["attune_refresh_level"] = maxi(0, n)
|
||||||
|
|
||||||
|
|
||||||
|
# 同调位计数源组装(Attunement 服务取数口——clear_count = 逐关通关次数合计〔含重复关,底稿 §2.1 口径〕;
|
||||||
|
# clear_count(level_index) 逐关访问器零改动复用)
|
||||||
|
func attunement_counters() -> Dictionary:
|
||||||
|
var total := 0
|
||||||
|
var counts: Dictionary = _data["meta"]["level_clear_counts"]
|
||||||
|
for k in counts.keys():
|
||||||
|
total += int(counts[k])
|
||||||
|
return {"clear_count": total, "boss_kill_count": boss_kill_count()}
|
||||||
|
|
||||||
|
|
||||||
func recent_summaries() -> Array:
|
func recent_summaries() -> Array:
|
||||||
return _data["meta"]["recent_summaries"].duplicate(true) # 值拷贝出户(本类契约「禁碰内部结构」——消费方直改不得渗入存档内存态)
|
return _data["meta"]["recent_summaries"].duplicate(true) # 值拷贝出户(本类契约「禁碰内部结构」——消费方直改不得渗入存档内存态)
|
||||||
|
|
||||||
|
|
||||||
# 域更新原语(正式冒险收束唯一入账口——组合根经本函数入账,禁直改内部结构):
|
# 域更新原语(正式冒险收束唯一入账口——组合根经本函数入账,禁直改内部结构):
|
||||||
# 钥石入账(summary.keystone_total——迭代 10 起逐杀掉落已在冒险内计入,boss_drops 参数降级为
|
# 钥石入账(summary.keystone_total——迭代 10 起逐杀掉落已在冒险内计入,boss_drops 参数降级为
|
||||||
# 摘要归档副本不重复入账)+ 逐通关关次 clear_count +1 与 Boss 首杀标记 + 摘要头部插入
|
# 摘要归档副本不重复入账)+ 逐通关关次 clear_count +1 与 Boss 首杀标记 + Boss 击杀总数累计
|
||||||
# (cap 10,最新在前,元素附 keystone_drops 副本——复盘可归因)。
|
# (迭代 11 第①批 T2——同调位里程碑计数源)+ 摘要头部插入(cap 10,最新在前,元素附
|
||||||
|
# keystone_drops 副本——复盘可归因)。
|
||||||
func apply_adventure_summary(summary: Dictionary, boss_drops: Array = []) -> void:
|
func apply_adventure_summary(summary: Dictionary, boss_drops: Array = []) -> void:
|
||||||
add_keystone(int(summary.get("keystone_total", 0)))
|
add_keystone(int(summary.get("keystone_total", 0)))
|
||||||
var cleared := int(summary.get("levels_cleared", 0))
|
var cleared := int(summary.get("levels_cleared", 0))
|
||||||
@@ -222,6 +280,7 @@ func apply_adventure_summary(summary: Dictionary, boss_drops: Array = []) -> voi
|
|||||||
var key := str(li)
|
var key := str(li)
|
||||||
_data["meta"]["level_clear_counts"][key] = int(_data["meta"]["level_clear_counts"].get(key, 0)) + 1
|
_data["meta"]["level_clear_counts"][key] = int(_data["meta"]["level_clear_counts"].get(key, 0)) + 1
|
||||||
_data["meta"]["boss_first_kill"][key] = true
|
_data["meta"]["boss_first_kill"][key] = true
|
||||||
|
_data["meta"]["boss_kill_count"] = boss_kill_count() + cleared # 迭代 11 T2——Boss 恒唯一终点:levels_cleared 即本次冒险 Boss 击杀数(含重复)
|
||||||
var entry: Dictionary = summary.duplicate(true)
|
var entry: Dictionary = summary.duplicate(true)
|
||||||
entry["boss_drops"] = boss_drops.duplicate(true)
|
entry["boss_drops"] = boss_drops.duplicate(true)
|
||||||
var arr: Array = _data["meta"]["recent_summaries"]
|
var arr: Array = _data["meta"]["recent_summaries"]
|
||||||
|
|||||||
@@ -10,9 +10,10 @@
|
|||||||
class_name AdventureTable
|
class_name AdventureTable
|
||||||
extends Resource
|
extends Resource
|
||||||
|
|
||||||
## 三选一提升池件 id 词表(封闭——断点附随件与牌库剔除态依赖它;扩件走本表+registry 修订)
|
## 三选一提升池件 id 词表(封闭——断点附随件与牌库剔除态依赖它;扩件走本表+registry 修订)。
|
||||||
const BONUS_IDS: Array[String] = ["attack", "max_hp", "armor", "gp", "aux_item", "ap"]
|
## aux_item 池种退役(迭代 11 第①批 T7——同调批出池:魔法道具件改走营地同调装配面,不入三选一牌库)
|
||||||
## 无次数上限件的 cap 哨兵值(gp/aux_item——牌库恒非空的结构保证)
|
const BONUS_IDS: Array[String] = ["attack", "max_hp", "armor", "gp", "ap"]
|
||||||
|
## 无次数上限件的 cap 哨兵值(gp——牌库恒非空的结构保证)
|
||||||
const BONUS_CAP_UNLIMITED := -1
|
const BONUS_CAP_UNLIMITED := -1
|
||||||
|
|
||||||
# —— 通关部分回血(固定型,非末关通关兑付)——
|
# —— 通关部分回血(固定型,非末关通关兑付)——
|
||||||
@@ -28,14 +29,12 @@ const BONUS_CAP_UNLIMITED := -1
|
|||||||
@export var bonus_armor_flat: int = 1 # [0,999] knob——护甲轴累加
|
@export var bonus_armor_flat: int = 1 # [0,999] knob——护甲轴累加
|
||||||
@export var bonus_gp_amount: int = 20 # [0,999] knob——GP 袋定额入钱包
|
@export var bonus_gp_amount: int = 20 # [0,999] knob——GP 袋定额入钱包
|
||||||
@export var bonus_ap_flat: int = 1 # [0,10] knob——AP 件每场战斗首轮行动点基线增量
|
@export var bonus_ap_flat: int = 1 # [0,10] knob——AP 件每场战斗首轮行动点基线增量
|
||||||
@export var bonus_aux_item_count: int = 1 # [1,9] knob——魔法道具件授予件数(受同调位约束)
|
|
||||||
|
|
||||||
# —— 三选一权重(加权洗牌不放回,E-S weight-key)——
|
# —— 三选一权重(加权洗牌不放回,E-S weight-key)——
|
||||||
@export var bonus_weight_attack: int = 3 # [1,99] knob
|
@export var bonus_weight_attack: int = 3 # [1,99] knob
|
||||||
@export var bonus_weight_max_hp: int = 3 # [1,99] knob
|
@export var bonus_weight_max_hp: int = 3 # [1,99] knob
|
||||||
@export var bonus_weight_armor: int = 3 # [1,99] knob
|
@export var bonus_weight_armor: int = 3 # [1,99] knob
|
||||||
@export var bonus_weight_gp: int = 3 # [1,99] knob
|
@export var bonus_weight_gp: int = 3 # [1,99] knob
|
||||||
@export var bonus_weight_aux: int = 1 # [1,99] knob——魔法道具件(受同调位约束)
|
|
||||||
@export var bonus_weight_ap: int = 1 # [1,99] knob——AP 件(全冒险最紧上限)
|
@export var bonus_weight_ap: int = 1 # [1,99] knob——AP 件(全冒险最紧上限)
|
||||||
|
|
||||||
# —— 全冒险获取次数上限(达上限件先剔出牌库;gp/aux_item 无上限)——
|
# —— 全冒险获取次数上限(达上限件先剔出牌库;gp/aux_item 无上限)——
|
||||||
@@ -44,9 +43,6 @@ const BONUS_CAP_UNLIMITED := -1
|
|||||||
@export var bonus_cap_armor: int = 3 # [0,99] knob
|
@export var bonus_cap_armor: int = 3 # [0,99] knob
|
||||||
@export var bonus_cap_ap: int = 2 # [0,99] knob——全冒险最紧
|
@export var bonus_cap_ap: int = 2 # [0,99] knob——全冒险最紧
|
||||||
|
|
||||||
# —— 同调位过渡读数源(正主 = attunement_growth.tres 谓词表,同调实施批落地后切换)——
|
|
||||||
@export var transitional_sync_slots: int = 1 # [0,9] knob——道具持有上限
|
|
||||||
|
|
||||||
|
|
||||||
func bonus_value(id: String) -> int:
|
func bonus_value(id: String) -> int:
|
||||||
match id:
|
match id:
|
||||||
@@ -55,7 +51,6 @@ func bonus_value(id: String) -> int:
|
|||||||
"armor": return bonus_armor_flat
|
"armor": return bonus_armor_flat
|
||||||
"gp": return bonus_gp_amount
|
"gp": return bonus_gp_amount
|
||||||
"ap": return bonus_ap_flat
|
"ap": return bonus_ap_flat
|
||||||
"aux_item": return bonus_aux_item_count
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -65,12 +60,11 @@ func bonus_weight(id: String) -> int:
|
|||||||
"max_hp": return bonus_weight_max_hp
|
"max_hp": return bonus_weight_max_hp
|
||||||
"armor": return bonus_weight_armor
|
"armor": return bonus_weight_armor
|
||||||
"gp": return bonus_weight_gp
|
"gp": return bonus_weight_gp
|
||||||
"aux_item": return bonus_weight_aux
|
|
||||||
"ap": return bonus_weight_ap
|
"ap": return bonus_weight_ap
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
## 次数上限(gp/aux_item 返回 BONUS_CAP_UNLIMITED = 无上限,牌库恒非空的结构保证)
|
## 次数上限(gp 返回 BONUS_CAP_UNLIMITED = 无上限,牌库恒非空的结构保证)
|
||||||
func bonus_cap(id: String) -> int:
|
func bonus_cap(id: String) -> int:
|
||||||
match id:
|
match id:
|
||||||
"attack": return bonus_cap_attack
|
"attack": return bonus_cap_attack
|
||||||
@@ -95,10 +89,6 @@ func validate() -> Array:
|
|||||||
var c := bonus_cap(id)
|
var c := bonus_cap(id)
|
||||||
if c < 0 or c > 99:
|
if c < 0 or c > 99:
|
||||||
errs.append({"category": "domain", "message": "bonus_cap(%s) 越域 [0,99]: %d" % [id, c]})
|
errs.append({"category": "domain", "message": "bonus_cap(%s) 越域 [0,99]: %d" % [id, c]})
|
||||||
if bonus_aux_item_count < 1 or bonus_aux_item_count > 9:
|
|
||||||
errs.append({"category": "domain", "message": "bonus_aux_item_count 越域 [1,9]: %d" % bonus_aux_item_count})
|
|
||||||
if bonus_candidate_count < 1 or bonus_candidate_count > BONUS_IDS.size():
|
if bonus_candidate_count < 1 or bonus_candidate_count > BONUS_IDS.size():
|
||||||
errs.append({"category": "domain", "message": "bonus_candidate_count 越域 [1,%d]: %d" % [BONUS_IDS.size(), bonus_candidate_count]})
|
errs.append({"category": "domain", "message": "bonus_candidate_count 越域 [1,%d]: %d" % [BONUS_IDS.size(), bonus_candidate_count]})
|
||||||
if transitional_sync_slots < 0 or transitional_sync_slots > 9:
|
|
||||||
errs.append({"category": "domain", "message": "transitional_sync_slots 越域 [0,9]: %d" % transitional_sync_slots})
|
|
||||||
return errs
|
return errs
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||||
|
# Validation Question: 同调位增长是否由「通关次数 + Boss 击杀数」经数据驱动的里程碑谓词表实时求值,
|
||||||
|
# 试玩后调增长曲线 = 改表不动码(proposal-magic-item-attunement-2026-09-22 §2.1 用户点名要求)
|
||||||
|
# Date: 2026-10-03(迭代 11 第①批 魔法道具同调实施批 T1——任务书 v1.1 §六 T1;设计权威底稿 proposal §2)
|
||||||
|
# 数据配置 AttunementGrowthTable schema(魔法道具 回填 owner)。
|
||||||
|
# 单行全局常量表(slot_cap + milestones 行族——AdventureTable 单行全局先例,Rule 4 豁免)。
|
||||||
|
# 行 = Dictionary:{id: String 唯一, grant: int, requires: [{key: String, threshold: int}]}——
|
||||||
|
# requires 为 (计数键, 阈值) 对的 AND 列表,全部满足即授予 grant 槽;
|
||||||
|
# 同调位读数唯一口径 = Σ已达成 grant clamp slot_cap(slots())。
|
||||||
|
# 承重契约(任务书 §三 7 + 底稿 §2.1):
|
||||||
|
# 计数键封闭枚举(COUNTER_KEYS——预留可插拔位 COUNTER_KEYS_RESERVED 本期不实现、加行即扩展不破坏
|
||||||
|
# schema);阈值 [1,999];Σgrant ≤ slot_cap(超出部分永不可达,禁);数值全 provisional 归
|
||||||
|
# 难度曲线与Boss——本表只锁形态,试玩调平衡的全部动作落在表上(改阈值/换计数键/slot_cap 加行)。
|
||||||
|
# OR/复合需求出现时升级谓词形态走任务书修订(底稿 §2.1 同款约束)。
|
||||||
|
class_name AttunementGrowthTable
|
||||||
|
extends Resource
|
||||||
|
|
||||||
|
@export var slot_cap: int = 3
|
||||||
|
@export var milestones: Array = [] # Array[Dictionary] 键 id/grant/requires
|
||||||
|
|
||||||
|
# 计数键封闭枚举(底稿 §2.1——save_file 逐域记账的键词表;T2 落 boss_kill_count 记账)
|
||||||
|
const COUNTER_KEYS := ["clear_count", "boss_kill_count"]
|
||||||
|
# 预留可插拔位(本期不实现,schema 枚举不收——底稿 §2.1 明列四件)
|
||||||
|
const COUNTER_KEYS_RESERVED := ["boss_firstkill_count", "adventure_count", "no_damage_clear_count", "run_depth_best"]
|
||||||
|
|
||||||
|
|
||||||
|
func validate() -> Array:
|
||||||
|
var errs: Array = []
|
||||||
|
if slot_cap < 0 or slot_cap > 9:
|
||||||
|
errs.append({"category": "domain", "message": "slot_cap 越域 [0,9]: %d" % slot_cap})
|
||||||
|
var seen := {}
|
||||||
|
var grant_sum := 0
|
||||||
|
for row in milestones:
|
||||||
|
var mid: String = String(row.get("id", ""))
|
||||||
|
if mid == "":
|
||||||
|
errs.append({"category": "domain", "message": "milestone id 空"})
|
||||||
|
continue
|
||||||
|
if seen.has(mid):
|
||||||
|
errs.append({"category": "dup_key", "message": "milestone id 重复: %s" % mid})
|
||||||
|
continue
|
||||||
|
seen[mid] = true
|
||||||
|
var grant: int = int(row.get("grant", 0))
|
||||||
|
if grant < 1 or grant > maxi(1, slot_cap):
|
||||||
|
errs.append({"category": "domain", "message": "%s grant 越域 [1,slot_cap=%d]: %d" % [mid, slot_cap, grant]})
|
||||||
|
grant_sum += maxi(0, grant)
|
||||||
|
var reqs: Array = row.get("requires", [])
|
||||||
|
if reqs.is_empty():
|
||||||
|
errs.append({"category": "domain", "message": "%s requires 空(无条件授予非法——里程碑必有谓词)" % mid})
|
||||||
|
var key_seen := {}
|
||||||
|
for req in reqs:
|
||||||
|
var key: String = String(req.get("key", ""))
|
||||||
|
if not COUNTER_KEYS.has(key):
|
||||||
|
errs.append({"category": "enum", "message": "%s 计数键出域 %s: %s" % [mid, str(COUNTER_KEYS), key]})
|
||||||
|
if key_seen.has(key):
|
||||||
|
errs.append({"category": "dup_key", "message": "%s 计数键重复: %s" % [mid, key]})
|
||||||
|
key_seen[key] = true
|
||||||
|
var th: int = int(req.get("threshold", 0))
|
||||||
|
if th < 1 or th > 999:
|
||||||
|
errs.append({"category": "domain", "message": "%s 阈值越域 [1,999]: %d" % [mid, th]})
|
||||||
|
if grant_sum > slot_cap:
|
||||||
|
errs.append({"category": "cross_field", "message": "Σgrant(%d) > slot_cap(%d)——超出部分永不可达,schema 禁" % [grant_sum, slot_cap]})
|
||||||
|
return errs
|
||||||
|
|
||||||
|
|
||||||
|
# 实时求值:Σ已达成 grant(谓词 = requires 全键达标;counters = {计数键: int},缺键按 0 计)
|
||||||
|
func granted_slots(counters: Dictionary) -> int:
|
||||||
|
var total := 0
|
||||||
|
for row in milestones:
|
||||||
|
var ok := true
|
||||||
|
for req in row.get("requires", []):
|
||||||
|
if int(counters.get(String(req.get("key", "")), 0)) < int(req.get("threshold", 0)):
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
if ok:
|
||||||
|
total += maxi(0, int(row.get("grant", 0)))
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
# 同调位读数唯一口径:Σ达成 grant clamp slot_cap(求值服务/装配覆盖层/运行期上限共读此口径)
|
||||||
|
func slots(counters: Dictionary) -> int:
|
||||||
|
return mini(granted_slots(counters), slot_cap)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://tfgsbs87xakg
|
||||||
@@ -18,15 +18,39 @@
|
|||||||
# 真视窄域——effect_kinds 含 mine_reveal 的活行 target 必须 area_3x3(真视一次最多 3×3,禁全图看雷);
|
# 真视窄域——effect_kinds 含 mine_reveal 的活行 target 必须 area_3x3(真视一次最多 3×3,禁全图看雷);
|
||||||
# board_wide 仅恢复推理原料类——target=board_wide 的活行 effect_kinds 不得含 mine_reveal(现行无此类活行,
|
# board_wide 仅恢复推理原料类——target=board_wide 的活行 effect_kinds 不得含 mine_reveal(现行无此类活行,
|
||||||
# 显影载体待 Alpha 重定后落位)。
|
# 显影载体待 Alpha 重定后落位)。
|
||||||
|
# 同调实施批扩列(迭代 11 第①批 T1,2026-10-03——任务书 v1.1;设计权威 proposal-magic-item-attunement-2026-09-22):
|
||||||
|
# form ∈ {"", "passive", "active_charged"}(空 = 切片期过渡形态行——掉落/停点渠道现行承载,T7 退役;
|
||||||
|
# passive = 装配整局生效;active_charged = 每冒险充能、挖掘期发起);
|
||||||
|
# charges_per_adventure(active_charged ≥1 / passive 恒 0);slot_cost(同调行恒 1——底稿裁决 5,schema 强制);
|
||||||
|
# unlock_requires 谓词行 [{key ∈ AttunementGrowthTable.COUNTER_KEYS, threshold ∈ [1,999]}](空 = 已解锁——
|
||||||
|
# 首批全解锁合法态,底稿 §2.2 用户裁;正式解锁渠道归 元进度 道具工坊柜台);
|
||||||
|
# effective_rounds(前 N 轮限时件轮数——P4 甲表值化;仅 ap_rounds_front 行消费);
|
||||||
|
# charge_n(充能进度阈值 N——仅 ap_charge_safe 行消费)。
|
||||||
|
# 🔴 零裸 flat 结构护栏(任务书 §三 1——机制轴禁无条件属性加项):行键白名单封闭(KEY_WHITELIST 外键 =
|
||||||
|
# 加载致命)+ effect_kinds 封闭枚举只收条件触发/机制修正类 kind——任何「无条件属性加项」类效果不得以
|
||||||
|
# 新 kind 或新键入表(返工红线)。
|
||||||
class_name AuxItemTable
|
class_name AuxItemTable
|
||||||
extends Resource
|
extends Resource
|
||||||
|
|
||||||
@export var rows: Array = [] # Array[Dictionary] 键 item_id
|
@export var rows: Array = [] # Array[Dictionary] 键 item_id
|
||||||
|
|
||||||
const DIMENSIONS := ["info", "risk", "action", "plan", "counter"]
|
const DIMENSIONS := ["info", "risk", "action", "plan", "counter"]
|
||||||
const EFFECT_KINDS := ["mine_reveal", "reward_preview", "ap_grant"]
|
# 同调批扩 enum(迭代 11 第①批 T1)——新增九 kind 全部条件触发/机制修正类;mine_reveal/ap_grant 语义不变。
|
||||||
|
# cell_removal = 爆破工兵包中立移除格(T7 原位换装时启用——T6 影响评估前置);mine_hit_halve 系固定
|
||||||
|
# 「减半」语义不消费 magnitude(反弹 N 由 reflect_damage 行 magnitude 承载)。
|
||||||
|
const EFFECT_KINDS := ["mine_reveal", "reward_preview", "ap_grant", "venom_rider", "flag_burst",
|
||||||
|
"mine_hit_halve", "reflect_damage", "ap_rounds_front", "ap_charge_safe", "cell_removal",
|
||||||
|
"reward_weight_bias", "bonus_refresh"]
|
||||||
const TARGETS := ["none", "area_3x3", "board_wide"]
|
const TARGETS := ["none", "area_3x3", "board_wide"]
|
||||||
const USE_PHASES := ["active_dig"]
|
const USE_PHASES := ["active_dig"]
|
||||||
|
const FORMS := ["", "passive", "active_charged"]
|
||||||
|
# magnitude 消费面(≥1 校验):ap_grant 与同调 N 值 kind;其余 kind 活行 magnitude 恒 0
|
||||||
|
const MAGNITUDE_KINDS := ["ap_grant", "venom_rider", "flag_burst", "reflect_damage", "ap_rounds_front",
|
||||||
|
"ap_charge_safe", "reward_weight_bias", "bonus_refresh"]
|
||||||
|
# 🔴 零裸 flat 结构护栏:行键白名单封闭(键出域=加载致命)——机制轴禁无条件属性加项的表结构承载
|
||||||
|
const KEY_WHITELIST := ["item_id", "display_name", "dimension_tags", "effect_kinds", "target",
|
||||||
|
"magnitude", "use_phase", "retired", "deferred", "form", "charges_per_adventure", "slot_cost",
|
||||||
|
"unlock_requires", "effective_rounds", "charge_n"]
|
||||||
|
|
||||||
|
|
||||||
func validate() -> Array:
|
func validate() -> Array:
|
||||||
@@ -62,6 +86,13 @@ func validate() -> Array:
|
|||||||
var phase: String = String(row.get("use_phase", "active_dig"))
|
var phase: String = String(row.get("use_phase", "active_dig"))
|
||||||
if not USE_PHASES.has(phase):
|
if not USE_PHASES.has(phase):
|
||||||
errs.append({"category": "enum", "message": "%s use_phase 出域 %s: %s" % [iid, str(USE_PHASES), phase]})
|
errs.append({"category": "enum", "message": "%s use_phase 出域 %s: %s" % [iid, str(USE_PHASES), phase]})
|
||||||
|
# 🔴 零裸 flat 结构护栏:键白名单封闭(同调批 T1——机制轴禁无条件属性加项)
|
||||||
|
for k in row.keys():
|
||||||
|
if not KEY_WHITELIST.has(String(k)):
|
||||||
|
errs.append({"category": "zero_flat", "message": "%s 行键出白名单: %s(KEY_WHITELIST 外键禁入)" % [iid, String(k)]})
|
||||||
|
var form: String = String(row.get("form", ""))
|
||||||
|
if not FORMS.has(form):
|
||||||
|
errs.append({"category": "enum", "message": "%s form 出域 %s: %s" % [iid, str(FORMS), form]})
|
||||||
if retired or deferred:
|
if retired or deferred:
|
||||||
# 退役/失义行:免 effect_kinds 非空与承重护栏校验(登记面保留,不入投放与 F2)
|
# 退役/失义行:免 effect_kinds 非空与承重护栏校验(登记面保留,不入投放与 F2)
|
||||||
continue
|
continue
|
||||||
@@ -82,17 +113,76 @@ func validate() -> Array:
|
|||||||
# 承重护栏②board_wide 仅恢复推理类:全图域行禁替推理效果
|
# 承重护栏②board_wide 仅恢复推理类:全图域行禁替推理效果
|
||||||
if target == "board_wide" and kind_seen.has("mine_reveal"):
|
if target == "board_wide" and kind_seen.has("mine_reveal"):
|
||||||
errs.append({"category": "load_bearing", "message": "%s board_wide 行 effect_kinds 不得含 mine_reveal(全图域仅恢复推理原料类——Alpha 重定后落位)" % iid})
|
errs.append({"category": "load_bearing", "message": "%s board_wide 行 effect_kinds 不得含 mine_reveal(全图域仅恢复推理原料类——Alpha 重定后落位)" % iid})
|
||||||
# magnitude:仅 ap_grant 行消费(≥1);非 ap_grant 活行恒 0
|
# magnitude:MAGNITUDE_KINDS 行消费(≥1);其余活行恒 0(同调批 T1——消费面自 ap_grant 扩至 N 值 kind)
|
||||||
var mag: int = int(row.get("magnitude", 0))
|
var mag: int = int(row.get("magnitude", 0))
|
||||||
if mag < 0 or mag > 999:
|
if mag < 0 or mag > 999:
|
||||||
errs.append({"category": "domain", "message": "%s magnitude 越域 [0,999]: %d" % [iid, mag]})
|
errs.append({"category": "domain", "message": "%s magnitude 越域 [0,999]: %d" % [iid, mag]})
|
||||||
if kind_seen.has("ap_grant") and mag < 1:
|
var mag_consuming := false
|
||||||
errs.append({"category": "cross_field", "message": "%s ap_grant 行 magnitude 应 ≥1,实得 %d" % [iid, mag]})
|
for k in kind_seen.keys():
|
||||||
if not kind_seen.has("ap_grant") and mag != 0:
|
if MAGNITUDE_KINDS.has(String(k)):
|
||||||
errs.append({"category": "cross_field", "message": "%s 非 ap_grant 活行 magnitude 应 0,实得 %d" % [iid, mag]})
|
mag_consuming = true
|
||||||
|
if mag_consuming and mag < 1:
|
||||||
|
errs.append({"category": "cross_field", "message": "%s N 值 kind 行 magnitude 应 ≥1,实得 %d" % [iid, mag]})
|
||||||
|
if not mag_consuming and mag != 0:
|
||||||
|
errs.append({"category": "cross_field", "message": "%s 非 N 值 kind 活行 magnitude 应 0,实得 %d" % [iid, mag]})
|
||||||
|
# ---- 同调行约束(form 非空——装配面;过渡行 form="" 不校验)----
|
||||||
|
if form != "":
|
||||||
|
if int(row.get("slot_cost", 1)) != 1:
|
||||||
|
errs.append({"category": "load_bearing", "message": "%s 同调占位统一 1 槽(slot_cost 恒 1——底稿裁决 5,schema 强制)" % iid})
|
||||||
|
var charges: int = int(row.get("charges_per_adventure", 0))
|
||||||
|
if form == "passive" and charges != 0:
|
||||||
|
errs.append({"category": "cross_field", "message": "%s passive 行 charges_per_adventure 应 0,实得 %d" % [iid, charges]})
|
||||||
|
if form == "active_charged" and charges < 1:
|
||||||
|
errs.append({"category": "cross_field", "message": "%s active_charged 行 charges_per_adventure 应 ≥1,实得 %d" % [iid, charges]})
|
||||||
|
var eff_rounds: int = int(row.get("effective_rounds", 0))
|
||||||
|
if eff_rounds < 0 or eff_rounds > 999:
|
||||||
|
errs.append({"category": "domain", "message": "%s effective_rounds 越域 [0,999]: %d" % [iid, eff_rounds]})
|
||||||
|
if kind_seen.has("ap_rounds_front") and eff_rounds < 1:
|
||||||
|
errs.append({"category": "cross_field", "message": "%s ap_rounds_front 行 effective_rounds 应 ≥1(P4 甲表值化),实得 %d" % [iid, eff_rounds]})
|
||||||
|
if not kind_seen.has("ap_rounds_front") and eff_rounds != 0:
|
||||||
|
errs.append({"category": "cross_field", "message": "%s 非 ap_rounds_front 行 effective_rounds 应 0,实得 %d" % [iid, eff_rounds]})
|
||||||
|
var charge_n: int = int(row.get("charge_n", 0))
|
||||||
|
if charge_n < 0 or charge_n > 999:
|
||||||
|
errs.append({"category": "domain", "message": "%s charge_n 越域 [0,999]: %d" % [iid, charge_n]})
|
||||||
|
if kind_seen.has("ap_charge_safe") and charge_n < 1:
|
||||||
|
errs.append({"category": "cross_field", "message": "%s ap_charge_safe 行 charge_n 应 ≥1(每次充能需 N 安全格),实得 %d" % [iid, charge_n]})
|
||||||
|
if not kind_seen.has("ap_charge_safe") and charge_n != 0:
|
||||||
|
errs.append({"category": "cross_field", "message": "%s 非 ap_charge_safe 行 charge_n 应 0,实得 %d" % [iid, charge_n]})
|
||||||
|
# unlock_requires 谓词行:空 = 已解锁(首批全解锁合法态,底稿 §2.2);非空键 ∈ 计数键封闭枚举
|
||||||
|
var ureqs: Array = row.get("unlock_requires", [])
|
||||||
|
var ukeys := {}
|
||||||
|
for req in ureqs:
|
||||||
|
var ukey: String = String(req.get("key", ""))
|
||||||
|
if not AttunementGrowthTable.COUNTER_KEYS.has(ukey):
|
||||||
|
errs.append({"category": "enum", "message": "%s unlock_requires 计数键出域 %s: %s" % [iid, str(AttunementGrowthTable.COUNTER_KEYS), ukey]})
|
||||||
|
if ukeys.has(ukey):
|
||||||
|
errs.append({"category": "dup_key", "message": "%s unlock_requires 计数键重复: %s" % [iid, ukey]})
|
||||||
|
ukeys[ukey] = true
|
||||||
|
var uth: int = int(req.get("threshold", 0))
|
||||||
|
if uth < 1 or uth > 999:
|
||||||
|
errs.append({"category": "domain", "message": "%s unlock_requires 阈值越域 [1,999]: %d" % [iid, uth]})
|
||||||
return errs
|
return errs
|
||||||
|
|
||||||
|
|
||||||
|
# 同调行词表(form 非空)——装配覆盖层/效果接线/同调位上限的唯一取数口径(迭代 11 第①批 T1)
|
||||||
|
func attunement_rows() -> Array:
|
||||||
|
var out: Array = []
|
||||||
|
for row in rows:
|
||||||
|
if String(row.get("form", "")) != "" and not bool(row.get("retired", false)) and not bool(row.get("deferred", false)):
|
||||||
|
out.append(row)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# 过渡行词表(form 空 = 切片期过渡形态活行)——掉落投放/停点池/过渡覆盖面的取数口径;
|
||||||
|
# 同调行永不入过渡渠道。T7 退役面切换后本口径随渠道消亡。
|
||||||
|
func transitional_rows() -> Array:
|
||||||
|
var out: Array = []
|
||||||
|
for row in rows:
|
||||||
|
if String(row.get("form", "")) == "" and not bool(row.get("retired", false)) and not bool(row.get("deferred", false)):
|
||||||
|
out.append(row)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
# 活行词表(非 retired 非 deferred)——投放校验/奖励节点池/F2 覆盖面的唯一取数口径
|
# 活行词表(非 retired 非 deferred)——投放校验/奖励节点池/F2 覆盖面的唯一取数口径
|
||||||
func active_rows() -> Array:
|
func active_rows() -> Array:
|
||||||
var out: Array = []
|
var out: Array = []
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
# 达标 clear_threshold——2026-09-20 排期裁决;局内效果整体归 Alpha 魔法道具重设计批,本表只有解锁状态面)。
|
# 达标 clear_threshold——2026-09-20 排期裁决;局内效果整体归 Alpha 魔法道具重设计批,本表只有解锁状态面)。
|
||||||
# conversion = 区间平移换算率行(attr 与 forge 词表一致恰三;per_level_shift_pct ∈ [0,100]——
|
# conversion = 区间平移换算率行(attr 与 forge 词表一致恰三;per_level_shift_pct ∈ [0,100]——
|
||||||
# registry 键 meta_progression_conversion_table;护甲行强条目:验收读数必须按解锁后参数重测)。
|
# registry 键 meta_progression_conversion_table;护甲行强条目:验收读数必须按解锁后参数重测)。
|
||||||
|
# attune_refresh_upgrade = Y 刷新升级行族(迭代 11 第①批 T4——P1 甲:每冒险开局给 Y 次、冒险周期池、
|
||||||
|
# 用完即无;level 连续从 1,等级 L → refresh_charges=Y 表值,钥石直购 keystone_cost;数值全 provisional)。
|
||||||
# 行值全部 provisional 归 元进度设计批配读数(用户拍板骨架先行,先例 = GP+商店房批)。
|
# 行值全部 provisional 归 元进度设计批配读数(用户拍板骨架先行,先例 = GP+商店房批)。
|
||||||
class_name MetaTable
|
class_name MetaTable
|
||||||
extends Resource
|
extends Resource
|
||||||
@@ -20,6 +22,7 @@ extends Resource
|
|||||||
@export var boss_keystone: Array[Dictionary] = [] # Array[Dictionary] 键 level_index
|
@export var boss_keystone: Array[Dictionary] = [] # Array[Dictionary] 键 level_index
|
||||||
@export var workshop_items: Array[Dictionary] = [] # Array[Dictionary] 键 item_id
|
@export var workshop_items: Array[Dictionary] = [] # Array[Dictionary] 键 item_id
|
||||||
@export var conversion: Array[Dictionary] = [] # Array[Dictionary] 键 attr
|
@export var conversion: Array[Dictionary] = [] # Array[Dictionary] 键 attr
|
||||||
|
@export var attune_refresh_upgrade: Array[Dictionary] = [] # Array[Dictionary] 键 level——Y 刷新升级行族(迭代 11 第①批 T4)
|
||||||
|
|
||||||
const FORGE_ATTRS := ["max_hp", "atk", "armor_class"]
|
const FORGE_ATTRS := ["max_hp", "atk", "armor_class"]
|
||||||
const WORKSHOP_ITEM_IDS := ["probe_dust", "sapper_order", "loot_compass", "nest_pendulum", "catacomb_scroll", "blast_kit"]
|
const WORKSHOP_ITEM_IDS := ["probe_dust", "sapper_order", "loot_compass", "nest_pendulum", "catacomb_scroll", "blast_kit"]
|
||||||
@@ -31,8 +34,8 @@ const WORKSHOP_CHANNELS := ["total_clears"]
|
|||||||
|
|
||||||
func validate() -> Array:
|
func validate() -> Array:
|
||||||
var errs: Array[Dictionary] = []
|
var errs: Array[Dictionary] = []
|
||||||
# 行族空护栏(四族皆为 ConfigDB 消费面——缺族即致命)
|
# 行族空护栏(五族皆为 ConfigDB 消费面——缺族即致命)
|
||||||
for family in ["forge_products", "boss_keystone", "workshop_items", "conversion"]:
|
for family in ["forge_products", "boss_keystone", "workshop_items", "conversion", "attune_refresh_upgrade"]:
|
||||||
if get(family).is_empty():
|
if get(family).is_empty():
|
||||||
errs.append({"category": "empty_table", "message": "MetaTable 行族为空: %s" % family})
|
errs.append({"category": "empty_table", "message": "MetaTable 行族为空: %s" % family})
|
||||||
# forge_products:attr 词表封闭 + 唯一 + 覆盖恰三(注入契约三字段必齐)+ flat/价格域 + 公比域 [100,400]
|
# forge_products:attr 词表封闭 + 唯一 + 覆盖恰三(注入契约三字段必齐)+ flat/价格域 + 公比域 [100,400]
|
||||||
@@ -115,4 +118,22 @@ func validate() -> Array:
|
|||||||
for conv_key in FORGE_ATTRS:
|
for conv_key in FORGE_ATTRS:
|
||||||
if not seen_conv.has(conv_key):
|
if not seen_conv.has(conv_key):
|
||||||
errs.append({"category": "coverage", "message": "conversion 缺属性行: %s(三行与 forge 词表一致)" % conv_key})
|
errs.append({"category": "coverage", "message": "conversion 缺属性行: %s(三行与 forge 词表一致)" % conv_key})
|
||||||
|
# attune_refresh_upgrade:level 唯一 + 连续从 1 + charges/cost 域(迭代 11 第①批 T4)
|
||||||
|
var seen_al := {}
|
||||||
|
for row in attune_refresh_upgrade:
|
||||||
|
var al: int = int(row.get("level", -1))
|
||||||
|
if seen_al.has(al):
|
||||||
|
errs.append({"category": "dup_key", "message": "attune_refresh_upgrade level 重复: %d" % al})
|
||||||
|
seen_al[al] = true
|
||||||
|
var ach: int = int(row.get("refresh_charges", -1))
|
||||||
|
if ach < 1 or ach > 9:
|
||||||
|
errs.append({"category": "domain", "message": "attune_refresh_upgrade L%d refresh_charges 越域 [1,9]: %d" % [al, ach]})
|
||||||
|
var acost: int = int(row.get("keystone_cost", -1))
|
||||||
|
if acost < 1 or acost > 999:
|
||||||
|
errs.append({"category": "domain", "message": "attune_refresh_upgrade L%d keystone_cost 越域 [1,999]: %d" % [al, acost]})
|
||||||
|
for i in attune_refresh_upgrade.size():
|
||||||
|
var al_at: int = int(attune_refresh_upgrade[i].get("level", 0))
|
||||||
|
if al_at != i + 1:
|
||||||
|
errs.append({"category": "structural", "message": "attune_refresh_upgrade level 不连续(须从 1 连续递增): 位 %d = %d" % [i + 1, al_at]})
|
||||||
|
break
|
||||||
return errs
|
return errs
|
||||||
@@ -10,24 +10,24 @@ bonus_max_hp_flat = 5
|
|||||||
bonus_armor_flat = 1
|
bonus_armor_flat = 1
|
||||||
bonus_gp_amount = 20
|
bonus_gp_amount = 20
|
||||||
bonus_ap_flat = 1
|
bonus_ap_flat = 1
|
||||||
bonus_aux_item_count = 1
|
|
||||||
bonus_weight_attack = 3
|
bonus_weight_attack = 3
|
||||||
bonus_weight_max_hp = 3
|
bonus_weight_max_hp = 3
|
||||||
bonus_weight_armor = 3
|
bonus_weight_armor = 3
|
||||||
bonus_weight_gp = 3
|
bonus_weight_gp = 3
|
||||||
bonus_weight_aux = 1
|
|
||||||
bonus_weight_ap = 1
|
bonus_weight_ap = 1
|
||||||
bonus_cap_attack = 3
|
bonus_cap_attack = 3
|
||||||
bonus_cap_max_hp = 3
|
bonus_cap_max_hp = 3
|
||||||
bonus_cap_armor = 3
|
bonus_cap_armor = 3
|
||||||
bonus_cap_ap = 2
|
bonus_cap_ap = 2
|
||||||
transitional_sync_slots = 1
|
|
||||||
|
|
||||||
; 迭代 10 奖励重构批(2026-10-01 扩列——registry adventure_table 条目同批改写):
|
; 迭代 10 奖励重构批(2026-10-01 扩列——registry adventure_table 条目同批改写):
|
||||||
; 大奖励 = 非末关通关恰一次:部分回血(clear_heal_pct,封顶上限不回满、满血回 0 合法)
|
; 大奖励 = 非末关通关恰一次:部分回血(clear_heal_pct,封顶上限不回满、满血回 0 合法)
|
||||||
; + 提升三选一(池六种加权洗牌不放回 E-S weight-key 掷 3,玩家点选兑付恰一件;消费 reward_pick 封闭流)。
|
; + 提升三选一(池六种加权洗牌不放回 E-S weight-key 掷 3,玩家点选兑付恰一件;消费 reward_pick 封闭流)。
|
||||||
; 权重:属性/GP 各 3、道具/AP 各 1;次数上限:属性件各 3、AP 件 2(达上限件先剔牌库)——gp/道具无次数上限。
|
; 权重:属性/GP 各 3、AP 1;次数上限:属性件各 3、AP 件 2(达上限件先剔牌库)——gp 无次数上限。
|
||||||
; 同调位过渡键 transitional_sync_slots=1(道具持有上限;正主 = attunement_growth.tres 谓词表,同调实施批切换)。
|
; 迭代 11 第①批 同调实施批 T7(2026-10-03):aux_item 池种退役出三选一牌库(bonus_weight_aux/
|
||||||
|
; bonus_aux_item_count 两字段随批退役);transitional_sync_slots 过渡键退役(正主 = attunement_growth.tres
|
||||||
|
; 谓词表求值,Attunement.slots——迭代 10 欠账③清偿)。
|
||||||
; 末关通关不发三选一与回血(奖励只朝下一战生效)。
|
; 末关通关不发三选一与回血(奖励只朝下一战生效)。
|
||||||
; 退役字段:level_clear_hp_bonus(血回满+上限提升件)、keystone_per_level(钥石改 Boss/精英击杀掉落,行值归 meta.tres boss_keystone)。
|
; 退役字段:level_clear_hp_bonus(血回满+上限提升件)、keystone_per_level(钥石改 Boss/精英击杀掉落,行值归 meta.tres boss_keystone)、
|
||||||
|
; transitional_sync_slots / bonus_weight_aux / bonus_aux_item_count(本批 T7 退役三件)。
|
||||||
; 全部 provisional-pending-曲线调参批(值锚与上调约束归 难度曲线与Boss;回血 20% 系难度上压方向,带重扫随批呈报)。
|
; 全部 provisional-pending-曲线调参批(值锚与上调约束归 难度曲线与Boss;回血 20% 系难度上压方向,带重扫随批呈报)。
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
[gd_resource type="Resource" script_class="AttunementGrowthTable" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://data/schemas/attunement_growth_table.gd" id="1"]
|
||||||
|
|
||||||
|
; 迭代 11 第①批 同调实施批 T1(2026-10-03)——同调位里程碑谓词表。
|
||||||
|
; 数值全 provisional 归 难度曲线与Boss(任务书 §三 7);行形态 = 底稿 §2.1 示意形态。
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
slot_cap = 3
|
||||||
|
milestones = [{
|
||||||
|
"id": "m1",
|
||||||
|
"grant": 1,
|
||||||
|
"requires": [{
|
||||||
|
"key": "clear_count",
|
||||||
|
"threshold": 1
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"id": "m2",
|
||||||
|
"grant": 1,
|
||||||
|
"requires": [{
|
||||||
|
"key": "boss_kill_count",
|
||||||
|
"threshold": 3
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"id": "m3",
|
||||||
|
"grant": 1,
|
||||||
|
"requires": [{
|
||||||
|
"key": "clear_count",
|
||||||
|
"threshold": 3
|
||||||
|
}, {
|
||||||
|
"key": "boss_kill_count",
|
||||||
|
"threshold": 6
|
||||||
|
}]
|
||||||
|
}]
|
||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
|
; 迭代 11 第①批 T1(2026-10-03):追加七件同调行(form 非空——永不入过渡渠道);值全 provisional 归 难度曲线与Boss。
|
||||||
|
; nest_pendulum/blast_kit 维持过渡行,T7 渠道退役时原位换装 form=active_charged;sapper_order/显影尘/地穴全图卷/罗盘 T7 逐件销账。
|
||||||
rows = [{
|
rows = [{
|
||||||
"item_id": "probe_dust",
|
"item_id": "probe_dust",
|
||||||
"display_name": "显影尘",
|
"display_name": "显影尘",
|
||||||
@@ -20,7 +22,8 @@ rows = [{
|
|||||||
"effect_kinds": ["ap_grant"],
|
"effect_kinds": ["ap_grant"],
|
||||||
"target": "none",
|
"target": "none",
|
||||||
"magnitude": 2,
|
"magnitude": 2,
|
||||||
"use_phase": "active_dig"
|
"use_phase": "active_dig",
|
||||||
|
"retired": true
|
||||||
}, {
|
}, {
|
||||||
"item_id": "loot_compass",
|
"item_id": "loot_compass",
|
||||||
"display_name": "探宝罗盘",
|
"display_name": "探宝罗盘",
|
||||||
@@ -37,7 +40,10 @@ rows = [{
|
|||||||
"effect_kinds": ["mine_reveal"],
|
"effect_kinds": ["mine_reveal"],
|
||||||
"target": "area_3x3",
|
"target": "area_3x3",
|
||||||
"magnitude": 0,
|
"magnitude": 0,
|
||||||
"use_phase": "active_dig"
|
"use_phase": "active_dig",
|
||||||
|
"form": "active_charged",
|
||||||
|
"slot_cost": 1,
|
||||||
|
"charges_per_adventure": 2
|
||||||
}, {
|
}, {
|
||||||
"item_id": "catacomb_scroll",
|
"item_id": "catacomb_scroll",
|
||||||
"display_name": "地穴全图卷",
|
"display_name": "地穴全图卷",
|
||||||
@@ -54,5 +60,79 @@ rows = [{
|
|||||||
"effect_kinds": ["ap_grant", "mine_reveal"],
|
"effect_kinds": ["ap_grant", "mine_reveal"],
|
||||||
"target": "area_3x3",
|
"target": "area_3x3",
|
||||||
"magnitude": 4,
|
"magnitude": 4,
|
||||||
"use_phase": "active_dig"
|
"use_phase": "active_dig",
|
||||||
|
"deferred": true
|
||||||
|
}, {
|
||||||
|
"item_id": "venom_edge",
|
||||||
|
"display_name": "淬毒刃纹",
|
||||||
|
"dimension_tags": ["counter"],
|
||||||
|
"effect_kinds": ["venom_rider"],
|
||||||
|
"target": "none",
|
||||||
|
"magnitude": 2,
|
||||||
|
"use_phase": "active_dig",
|
||||||
|
"form": "passive",
|
||||||
|
"slot_cost": 1
|
||||||
|
}, {
|
||||||
|
"item_id": "flag_burst",
|
||||||
|
"display_name": "标中殉爆",
|
||||||
|
"dimension_tags": ["action"],
|
||||||
|
"effect_kinds": ["flag_burst"],
|
||||||
|
"target": "none",
|
||||||
|
"magnitude": 2,
|
||||||
|
"use_phase": "active_dig",
|
||||||
|
"form": "passive",
|
||||||
|
"slot_cost": 1
|
||||||
|
}, {
|
||||||
|
"item_id": "nest_counter",
|
||||||
|
"display_name": "惊巢反制",
|
||||||
|
"dimension_tags": ["risk"],
|
||||||
|
"effect_kinds": ["mine_hit_halve", "reflect_damage"],
|
||||||
|
"target": "none",
|
||||||
|
"magnitude": 2,
|
||||||
|
"use_phase": "active_dig",
|
||||||
|
"form": "passive",
|
||||||
|
"slot_cost": 1
|
||||||
|
}, {
|
||||||
|
"item_id": "dawn_order",
|
||||||
|
"display_name": "工兵晨令",
|
||||||
|
"dimension_tags": ["action"],
|
||||||
|
"effect_kinds": ["ap_rounds_front"],
|
||||||
|
"target": "none",
|
||||||
|
"magnitude": 1,
|
||||||
|
"use_phase": "active_dig",
|
||||||
|
"form": "passive",
|
||||||
|
"slot_cost": 1,
|
||||||
|
"effective_rounds": 3
|
||||||
|
}, {
|
||||||
|
"item_id": "dig_charge",
|
||||||
|
"display_name": "掘进蓄能",
|
||||||
|
"dimension_tags": ["action"],
|
||||||
|
"effect_kinds": ["ap_charge_safe"],
|
||||||
|
"target": "none",
|
||||||
|
"magnitude": 1,
|
||||||
|
"use_phase": "active_dig",
|
||||||
|
"form": "active_charged",
|
||||||
|
"slot_cost": 1,
|
||||||
|
"charges_per_adventure": 2,
|
||||||
|
"charge_n": 3
|
||||||
|
}, {
|
||||||
|
"item_id": "luck_coin",
|
||||||
|
"display_name": "偏运硬币",
|
||||||
|
"dimension_tags": ["risk"],
|
||||||
|
"effect_kinds": ["reward_weight_bias"],
|
||||||
|
"target": "none",
|
||||||
|
"magnitude": 2,
|
||||||
|
"use_phase": "active_dig",
|
||||||
|
"form": "passive",
|
||||||
|
"slot_cost": 1
|
||||||
|
}, {
|
||||||
|
"item_id": "pick_refresh",
|
||||||
|
"display_name": "三选一刷新件",
|
||||||
|
"dimension_tags": ["info"],
|
||||||
|
"effect_kinds": ["bonus_refresh"],
|
||||||
|
"target": "none",
|
||||||
|
"magnitude": 1,
|
||||||
|
"use_phase": "active_dig",
|
||||||
|
"form": "passive",
|
||||||
|
"slot_cost": 1
|
||||||
}]
|
}]
|
||||||
@@ -77,6 +77,15 @@ conversion = [{
|
|||||||
"attr": "armor_class",
|
"attr": "armor_class",
|
||||||
"per_level_shift_pct": 1
|
"per_level_shift_pct": 1
|
||||||
}]
|
}]
|
||||||
|
attune_refresh_upgrade = [{
|
||||||
|
"level": 1,
|
||||||
|
"refresh_charges": 1,
|
||||||
|
"keystone_cost": 30
|
||||||
|
}, {
|
||||||
|
"level": 2,
|
||||||
|
"refresh_charges": 2,
|
||||||
|
"keystone_cost": 60
|
||||||
|
}]
|
||||||
|
|
||||||
; 元进度实现批(2026-09-21,设计权威 design/gdd/systems/冒险循环/元进度.md 定稿 v1.2):
|
; 元进度实现批(2026-09-21,设计权威 design/gdd/systems/冒险循环/元进度.md 定稿 v1.2):
|
||||||
; 数值全 provisional 归元进度设计批配读数(骨架先行——元进度实现批 2026-09-21,先例 = GP+商店房批)。
|
; 数值全 provisional 归元进度设计批配读数(骨架先行——元进度实现批 2026-09-21,先例 = GP+商店房批)。
|
||||||
@@ -107,3 +116,7 @@ conversion = [{
|
|||||||
; 钥石渠道自本批起 =
|
; 钥石渠道自本批起 =
|
||||||
; Boss/精英击杀逐杀掉落(大奖励 keystone_per_level 渠道退役;精英掉 1 预留行随怪物批实装落)。
|
; Boss/精英击杀逐杀掉落(大奖励 keystone_per_level 渠道退役;精英掉 1 预留行随怪物批实装落)。
|
||||||
; 产出结构变化 → 元进度标定义务重开(06-待办清单 2026-10-01 登记条)。provisional 归曲线调参批。
|
; 产出结构变化 → 元进度标定义务重开(06-待办清单 2026-10-01 登记条)。provisional 归曲线调参批。
|
||||||
|
;
|
||||||
|
; 迭代 11 第①批 同调实施批 T4(2026-10-03):attune_refresh_upgrade 行族 = Y 刷新升级行(任务书 P1 甲——
|
||||||
|
; 每冒险开局给 Y 次、冒险周期池、用完即无;等级 L → Y=L 次刷新,钥石直购)。数值全 provisional 归
|
||||||
|
; 难度曲线与Boss(任务书 §三 7)。
|
||||||
@@ -34,12 +34,13 @@ func _init(db_ref, meta_ref, info_panel_ref: HeroInfoPanelView, toast_ref: Toast
|
|||||||
_runner_get = runner_get
|
_runner_get = runner_get
|
||||||
|
|
||||||
|
|
||||||
# aux 解锁门控集组装(迭代 7 T5——拍板③甲:工坊解锁 ∩ 活行词表;罗盘退役/失义件不入集)
|
# aux 解锁门控集组装(迭代 7 T5——拍板③甲:工坊解锁 ∩ 过渡活行词表;罗盘退役/失义件/同调行不入集
|
||||||
|
# ——迭代 11 第①批 T1 切 aux_transitional_rows 口径,同调行永不入过渡渠道)
|
||||||
func unlocked_set() -> Dictionary:
|
func unlocked_set() -> Dictionary:
|
||||||
var out := {}
|
var out := {}
|
||||||
if meta == null:
|
if meta == null:
|
||||||
return out
|
return out
|
||||||
for row in db.aux_active_rows():
|
for row in db.aux_transitional_rows():
|
||||||
var iid := String(row.get("item_id", ""))
|
var iid := String(row.get("item_id", ""))
|
||||||
if meta.is_item_unlocked(iid):
|
if meta.is_item_unlocked(iid):
|
||||||
out[iid] = true
|
out[iid] = true
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ func _build_ui() -> void:
|
|||||||
shop_overlay.buy_requested.connect(_on_shop_buy)
|
shop_overlay.buy_requested.connect(_on_shop_buy)
|
||||||
shop_overlay.leave_pressed.connect(_on_shop_leave)
|
shop_overlay.leave_pressed.connect(_on_shop_leave)
|
||||||
camp_overlay.buy_requested.connect(_on_camp_buy)
|
camp_overlay.buy_requested.connect(_on_camp_buy)
|
||||||
|
camp_overlay.attune_toggled.connect(_on_camp_attune_toggle)
|
||||||
camp_overlay.closed.connect(_on_camp_closed)
|
camp_overlay.closed.connect(_on_camp_closed)
|
||||||
_end_overlay.review_pressed.connect(_on_end_review)
|
_end_overlay.review_pressed.connect(_on_end_review)
|
||||||
_end_overlay.continue_pressed.connect(_on_battle_end_continue)
|
_end_overlay.continue_pressed.connect(_on_battle_end_continue)
|
||||||
@@ -269,6 +270,7 @@ func _build_ui() -> void:
|
|||||||
bonus_overlay.pick_confirmed.connect(_on_bonus_pick_confirmed)
|
bonus_overlay.pick_confirmed.connect(_on_bonus_pick_confirmed)
|
||||||
bonus_overlay.replace_chosen.connect(_on_bonus_replace_chosen)
|
bonus_overlay.replace_chosen.connect(_on_bonus_replace_chosen)
|
||||||
bonus_overlay.continue_pressed.connect(_on_bonus_continue)
|
bonus_overlay.continue_pressed.connect(_on_bonus_continue)
|
||||||
|
bonus_overlay.refresh_requested.connect(_on_bonus_refresh)
|
||||||
move_child(bonus_overlay, _end_overlay.get_index())
|
move_child(bonus_overlay, _end_overlay.get_index())
|
||||||
|
|
||||||
var start: Dictionary = UiAssembler.build_start_overlay(self, db)
|
var start: Dictionary = UiAssembler.build_start_overlay(self, db)
|
||||||
@@ -486,6 +488,7 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void:
|
|||||||
_open_modal(bonus_overlay)
|
_open_modal(bonus_overlay)
|
||||||
bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(node_cands, node_draw.get("aux_detail", []) as Array))
|
bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(node_cands, node_draw.get("aux_detail", []) as Array))
|
||||||
adventure.present_node_bonus(node_cands, node_draw.get("aux_detail", []) as Array)
|
adventure.present_node_bonus(node_cands, node_draw.get("aux_detail", []) as Array)
|
||||||
|
bonus_overlay.set_refresh_info(adventure.attune_refresh_left()) # 迭代 11 第①批 T5b——刷新余量投喂
|
||||||
log_view.append_bb(Narrator.reward_line())
|
log_view.append_bb(Narrator.reward_line())
|
||||||
elif t == "rest":
|
elif t == "rest":
|
||||||
var healed: int = int(info.get("healed", 0))
|
var healed: int = int(info.get("healed", 0))
|
||||||
@@ -527,6 +530,7 @@ func _show_bonus_pick() -> void:
|
|||||||
var payload: Dictionary = _pending_bonus
|
var payload: Dictionary = _pending_bonus
|
||||||
_open_modal(bonus_overlay)
|
_open_modal(bonus_overlay)
|
||||||
bonus_overlay.open_pick(Narrator.bonus_pick_header(payload), _bonus_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array))
|
bonus_overlay.open_pick(Narrator.bonus_pick_header(payload), _bonus_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array))
|
||||||
|
bonus_overlay.set_refresh_info(adventure.attune_refresh_left() if adventure != null else 0) # 迭代 11 第①批 T5b——刷新余量投喂
|
||||||
_refresh_presentations()
|
_refresh_presentations()
|
||||||
|
|
||||||
var _last_replace_items: Array = [] # 替换子态续玩重建缓存(aux_replace_presented 载荷)
|
var _last_replace_items: Array = [] # 替换子态续玩重建缓存(aux_replace_presented 载荷)
|
||||||
@@ -572,6 +576,19 @@ func _aux_card_line(iid: String) -> String:
|
|||||||
return "%s · %s" % [String(row.get("display_name", iid)), " ".join(parts)]
|
return "%s · %s" % [String(row.get("display_name", iid)), " ".join(parts)]
|
||||||
|
|
||||||
|
|
||||||
|
# 三选一刷新(迭代 11 第①批 T5b——P1 甲 X/Y 双源;通关停点经重发 bonus_choice_presented 自然重建,
|
||||||
|
# 节点停点在此读在途态重 present——零自动推进契约不破,停点仍在途)
|
||||||
|
func _on_bonus_refresh() -> void:
|
||||||
|
if adventure == null:
|
||||||
|
return
|
||||||
|
if not adventure.refresh_bonus():
|
||||||
|
return
|
||||||
|
if _bonus_context == "reward_node":
|
||||||
|
var st: Dictionary = adventure.node_bonus_pending_state()
|
||||||
|
bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(st.get("candidates", []) as Array, st.get("aux_detail", []) as Array))
|
||||||
|
bonus_overlay.set_refresh_info(adventure.attune_refresh_left())
|
||||||
|
|
||||||
|
|
||||||
func _on_bonus_pick_confirmed(item_id: String) -> void:
|
func _on_bonus_pick_confirmed(item_id: String) -> void:
|
||||||
if adventure == null:
|
if adventure == null:
|
||||||
return
|
return
|
||||||
@@ -595,7 +612,7 @@ func _open_bonus_replace(items: Array, incoming: Dictionary) -> void:
|
|||||||
rec["display_name"] = String(_item_name_lookup().get(String(rec.get("item_id", "")), String(rec.get("item_id", ""))))
|
rec["display_name"] = String(_item_name_lookup().get(String(rec.get("item_id", "")), String(rec.get("item_id", ""))))
|
||||||
named.append(rec)
|
named.append(rec)
|
||||||
bonus_overlay.open_replace(named, String(_item_name_lookup().get(String(incoming.get("item_id", "")), String(incoming.get("item_id", "")))),
|
bonus_overlay.open_replace(named, String(_item_name_lookup().get(String(incoming.get("item_id", "")), String(incoming.get("item_id", "")))),
|
||||||
named.size(), int(db.adventure.transitional_sync_slots))
|
named.size(), Attunement.slots(db, save_manager)) # 迭代 11 第①批 T7——呈现读数源切谓词表求值(过渡键退役)
|
||||||
|
|
||||||
|
|
||||||
func _on_bonus_replace_chosen(discard: int) -> void:
|
func _on_bonus_replace_chosen(discard: int) -> void:
|
||||||
@@ -1028,6 +1045,7 @@ func _camp_data() -> Dictionary:
|
|||||||
"balance": meta.keystone_balance() if meta != null else 0,
|
"balance": meta.keystone_balance() if meta != null else 0,
|
||||||
"forge": forge_rows,
|
"forge": forge_rows,
|
||||||
"workshop": meta.workshop_status() if meta != null else [],
|
"workshop": meta.workshop_status() if meta != null else [],
|
||||||
|
"attune": meta.attune_camp_status() if meta != null else {}, # 迭代 11 第①批 T4——同调装配面投影
|
||||||
"recent": save_manager.recent_summaries() if save_manager != null else [],
|
"recent": save_manager.recent_summaries() if save_manager != null else [],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1035,6 +1053,14 @@ func _camp_data() -> Dictionary:
|
|||||||
func _on_camp_buy(attr: String) -> void:
|
func _on_camp_buy(attr: String) -> void:
|
||||||
if meta == null:
|
if meta == null:
|
||||||
return
|
return
|
||||||
|
if attr == "attune_refresh":
|
||||||
|
# Y 刷新升级行购买(迭代 11 第①批 T4——P1 甲;按钮禁用为常态防线,兜底拒绝可见 pattern 1)
|
||||||
|
if not meta.buy_attune_refresh():
|
||||||
|
board_zone.toast.show_toast("钥石不足——买不起这次升级", "warn")
|
||||||
|
return
|
||||||
|
camp_overlay.refresh_camp(_camp_data())
|
||||||
|
_refresh_presentations()
|
||||||
|
return
|
||||||
if not meta.buy_forge(attr):
|
if not meta.buy_forge(attr):
|
||||||
# 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1)
|
# 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1)
|
||||||
board_zone.toast.show_toast("钥石不足——买不起这件强化", "warn")
|
board_zone.toast.show_toast("钥石不足——买不起这件强化", "warn")
|
||||||
@@ -1043,6 +1069,16 @@ func _on_camp_buy(attr: String) -> void:
|
|||||||
_refresh_presentations()
|
_refresh_presentations()
|
||||||
|
|
||||||
|
|
||||||
|
# 同调装配切换(迭代 11 第①批 T4——装配关系唯一写口经 MetaProgression.toggle_attunement;满位兜底拒绝可见)
|
||||||
|
func _on_camp_attune_toggle(item_id: String) -> void:
|
||||||
|
if meta == null:
|
||||||
|
return
|
||||||
|
if not meta.toggle_attunement(item_id):
|
||||||
|
board_zone.toast.show_toast("同调位已满——先卸下再装配", "warn")
|
||||||
|
return
|
||||||
|
camp_overlay.refresh_camp(_camp_data())
|
||||||
|
|
||||||
|
|
||||||
# ---- 商店覆盖层与 GP 钱包(GP+商店房实施批——写调用唯一归组合根)----
|
# ---- 商店覆盖层与 GP 钱包(GP+商店房实施批——写调用唯一归组合根)----
|
||||||
|
|
||||||
func _on_shop_buy(product_id: String) -> void:
|
func _on_shop_buy(product_id: String) -> void:
|
||||||
|
|||||||
@@ -10,12 +10,16 @@ extends Control
|
|||||||
signal pick_confirmed(item_id: String)
|
signal pick_confirmed(item_id: String)
|
||||||
signal replace_chosen(discard_index: int) # -1 = 放弃新件不获取
|
signal replace_chosen(discard_index: int) # -1 = 放弃新件不获取
|
||||||
signal continue_pressed
|
signal continue_pressed
|
||||||
|
signal refresh_requested # 三选一刷新意图(迭代 11 第①批 T5b——P1 甲 X/Y 双源;重抽归组合根)
|
||||||
|
|
||||||
var _label: Label
|
var _label: Label
|
||||||
var _choice_box: HBoxContainer # 三卡按钮区(pick 态)
|
var _choice_box: HBoxContainer # 三卡按钮区(pick 态)
|
||||||
var _replace_box: VBoxContainer # 替换子面板(replace 态)
|
var _replace_box: VBoxContainer # 替换子面板(replace 态)
|
||||||
var _confirm_btn: Button # 确认钮(点选后点亮——两段式第二拍)
|
var _confirm_btn: Button # 确认钮(点选后点亮——两段式第二拍)
|
||||||
var _continue_btn: Button # 继续钮(兑付反馈态唯一出口)
|
var _continue_btn: Button # 继续钮(兑付反馈态唯一出口)
|
||||||
|
var _refresh_btn: Button # 刷新钮(迭代 11 第①批 T5b——余量>0 才显示)
|
||||||
|
var _refresh_l: Label # 刷新余量读数(X 优先 + Y 冒险池)
|
||||||
|
var _refresh_allowance := 0 # 组合根投喂余量(本视图零核心引用)
|
||||||
var _selected := "" # 点选暂存(确认前可改选)
|
var _selected := "" # 点选暂存(确认前可改选)
|
||||||
var _header := "" # 停点头部文案(选择态保持显示)
|
var _header := "" # 停点头部文案(选择态保持显示)
|
||||||
var _card_meta: Array = [] # open_pick 缓存 [{id, text}]——高亮还原读面
|
var _card_meta: Array = [] # open_pick 缓存 [{id, text}]——高亮还原读面
|
||||||
@@ -34,6 +38,13 @@ func _init() -> void:
|
|||||||
_confirm_btn.pressed.connect(_on_confirm_pressed)
|
_confirm_btn.pressed.connect(_on_confirm_pressed)
|
||||||
_confirm_btn.visible = false
|
_confirm_btn.visible = false
|
||||||
box.add_child(_confirm_btn)
|
box.add_child(_confirm_btn)
|
||||||
|
# 三选一刷新面(迭代 11 第①批 T5b——程序占位用 UiKit 既有件;余量 0 = 隐藏)
|
||||||
|
_refresh_btn = UiKit.button("刷新候选")
|
||||||
|
_refresh_btn.pressed.connect(_emit_refresh_requested)
|
||||||
|
_refresh_btn.visible = false
|
||||||
|
box.add_child(_refresh_btn)
|
||||||
|
_refresh_l = UiKit.label("", 13, false, UiKit.STONE)
|
||||||
|
box.add_child(_refresh_l)
|
||||||
_replace_box = VBoxContainer.new()
|
_replace_box = VBoxContainer.new()
|
||||||
_replace_box.add_theme_constant_override("separation", 8)
|
_replace_box.add_theme_constant_override("separation", 8)
|
||||||
_replace_box.alignment = BoxContainer.ALIGNMENT_CENTER
|
_replace_box.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||||
@@ -64,9 +75,22 @@ func open_pick(header: String, cards: Array) -> void:
|
|||||||
_confirm_btn.visible = false
|
_confirm_btn.visible = false
|
||||||
_replace_box.visible = false
|
_replace_box.visible = false
|
||||||
_continue_btn.visible = false
|
_continue_btn.visible = false
|
||||||
|
_refresh_btn.visible = false # 刷新余量随 set_refresh_info 投喂后现形(open 先归零防残留)
|
||||||
|
_refresh_l.text = ""
|
||||||
visible = true
|
visible = true
|
||||||
|
|
||||||
|
|
||||||
|
# 三选一刷新余量投喂(组合根读 adventure.attune_refresh_left()——0 = 刷新面隐藏)
|
||||||
|
func set_refresh_info(allowance: int) -> void:
|
||||||
|
_refresh_allowance = maxi(0, int(allowance))
|
||||||
|
_refresh_btn.visible = _refresh_allowance > 0
|
||||||
|
_refresh_l.text = "可刷新 %d 次(装配刷新件 X 优先消耗,Y 为冒险池)" % _refresh_allowance if _refresh_allowance > 0 else ""
|
||||||
|
|
||||||
|
|
||||||
|
func _emit_refresh_requested() -> void:
|
||||||
|
refresh_requested.emit()
|
||||||
|
|
||||||
|
|
||||||
func _select_card(id: String) -> void:
|
func _select_card(id: String) -> void:
|
||||||
_selected = id
|
_selected = id
|
||||||
for i in _choice_box.get_child_count():
|
for i in _choice_box.get_child_count():
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ extends Control
|
|||||||
|
|
||||||
signal buy_requested(attr: String)
|
signal buy_requested(attr: String)
|
||||||
signal closed
|
signal closed
|
||||||
|
signal attune_toggled(item_id: String) # 同调装配切换意图(迭代 11 第①批 T4——写口唯一归 MetaProgression.toggle_attunement)
|
||||||
|
|
||||||
const ATTR_LABELS := {"atk": "攻击", "max_hp": "生命上限", "armor_class": "护甲"} # 档位文案轴(数据契约 attr 词表,与 ShopOverlay 同款)
|
const ATTR_LABELS := {"atk": "攻击", "max_hp": "生命上限", "armor_class": "护甲"} # 档位文案轴(数据契约 attr 词表,与 ShopOverlay 同款)
|
||||||
# 方向稿取色(4-camp.png——柜台头带深棕/行板暖米;双色墨=亮板配深墨)——值单点收 UiKit(T4 裸色收口批)
|
# 方向稿取色(4-camp.png——柜台头带深棕/行板暖米;双色墨=亮板配深墨)——值单点收 UiKit(T4 裸色收口批)
|
||||||
@@ -25,6 +26,11 @@ var _forge_holder: VBoxContainer
|
|||||||
var _workshop_holder: VBoxContainer
|
var _workshop_holder: VBoxContainer
|
||||||
var _forge_rows: Array[Dictionary] = [] # [{attr, level_l, price_l, btn}]——refresh_camp 就地重刷(零重建回流)
|
var _forge_rows: Array[Dictionary] = [] # [{attr, level_l, price_l, btn}]——refresh_camp 就地重刷(零重建回流)
|
||||||
var _workshop_rows: Array[Dictionary] = [] # [{item_id, progress_l, status_l}]
|
var _workshop_rows: Array[Dictionary] = [] # [{item_id, progress_l, status_l}]
|
||||||
|
var _attune_header_l: Label # 同调装配段(迭代 11 第①批 T4 工坊扩容——Slots 点亮呈现)
|
||||||
|
var _attune_btns: Array[Dictionary] = [] # [{item_id, btn}]——装配/卸下切换钮(满位禁选随刷)
|
||||||
|
var _attune_nodes: Array[Control] = [] # 段内节点册(open_camp 重建时定向清理,不扰工坊六行)
|
||||||
|
var _refresh_l: Label # Y 刷新升级行(P1 甲——每冒险开局给 Y 次、冒险周期池)
|
||||||
|
var _refresh_btn: Button
|
||||||
var _recent_list: Label # 最近冒险(逐行拼接 label——只读账面,随 open_camp 重建)
|
var _recent_list: Label # 最近冒险(逐行拼接 label——只读账面,随 open_camp 重建)
|
||||||
|
|
||||||
|
|
||||||
@@ -91,9 +97,15 @@ func _init() -> void:
|
|||||||
_forge_holder = VBoxContainer.new()
|
_forge_holder = VBoxContainer.new()
|
||||||
_forge_holder.add_theme_constant_override("separation", 6)
|
_forge_holder.add_theme_constant_override("separation", 6)
|
||||||
mid.add_child(_counter_panel("属性锻造", UiKit.TEX_ICON_FORGE, _forge_holder, 356))
|
mid.add_child(_counter_panel("属性锻造", UiKit.TEX_ICON_FORGE, _forge_holder, 356))
|
||||||
|
# 迭代 11 第①批 T4 工坊扩容(P2 甲程序占位——同调装配段+Y 升级行入工坊柜台,行板复用;行数增长以
|
||||||
|
# 柜台内滚承载,冻结三柜台版式与另两柜台零触碰,视觉终稿归营地批)
|
||||||
|
var ws_scroll := ScrollContainer.new()
|
||||||
|
ws_scroll.custom_minimum_size = Vector2(340, 336)
|
||||||
|
ws_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||||
_workshop_holder = VBoxContainer.new()
|
_workshop_holder = VBoxContainer.new()
|
||||||
_workshop_holder.add_theme_constant_override("separation", 6)
|
_workshop_holder.add_theme_constant_override("separation", 6)
|
||||||
mid.add_child(_counter_panel("道具工坊", UiKit.TEX_ICON_WORKSHOP, _workshop_holder, 356))
|
ws_scroll.add_child(_workshop_holder)
|
||||||
|
mid.add_child(_counter_panel("道具工坊", UiKit.TEX_ICON_WORKSHOP, ws_scroll, 356))
|
||||||
var atlas_holder := VBoxContainer.new()
|
var atlas_holder := VBoxContainer.new()
|
||||||
mid.add_child(_counter_panel("奖励格图鉴", UiKit.TEX_ICON_ATLAS, atlas_holder, 356))
|
mid.add_child(_counter_panel("奖励格图鉴", UiKit.TEX_ICON_ATLAS, atlas_holder, 356))
|
||||||
var lock_l := UiKit.label(" —— 柜台结构位预留:内容排 Alpha(地图战斗资源差异化批)", 13, false, UiKit.STONE)
|
var lock_l := UiKit.label(" —— 柜台结构位预留:内容排 Alpha(地图战斗资源差异化批)", 13, false, UiKit.STONE)
|
||||||
@@ -121,7 +133,7 @@ func _init() -> void:
|
|||||||
|
|
||||||
|
|
||||||
# 柜台列面板(方向稿同构):PanelContainer 米板身 + 头带深棕(图标+标题暖白)
|
# 柜台列面板(方向稿同构):PanelContainer 米板身 + 头带深棕(图标+标题暖白)
|
||||||
func _counter_panel(title_text: String, icon_path: String, holder: VBoxContainer, min_w: int) -> PanelContainer:
|
func _counter_panel(title_text: String, icon_path: String, holder: Control, min_w: int) -> PanelContainer:
|
||||||
var panel := PanelContainer.new()
|
var panel := PanelContainer.new()
|
||||||
panel.custom_minimum_size = Vector2(min_w, 0)
|
panel.custom_minimum_size = Vector2(min_w, 0)
|
||||||
panel.add_theme_stylebox_override("panel",
|
panel.add_theme_stylebox_override("panel",
|
||||||
@@ -147,10 +159,11 @@ func _counter_panel(title_text: String, icon_path: String, holder: VBoxContainer
|
|||||||
|
|
||||||
# ---- 柜台行构建(open_camp 重建;行结构仿 ShopOverlay 商品卡——亮板双色墨制)----
|
# ---- 柜台行构建(open_camp 重建;行结构仿 ShopOverlay 商品卡——亮板双色墨制)----
|
||||||
|
|
||||||
# 打开营地(全量重建三柜台行 + 最近冒险——数据 = 组合根 _camp_data() 投影)
|
# 打开营地(全量重建三柜台行 + 同调装配段 + 最近冒险——数据 = 组合根 _camp_data() 投影)
|
||||||
func open_camp(data: Dictionary) -> void:
|
func open_camp(data: Dictionary) -> void:
|
||||||
_rebuild_forge(data)
|
_rebuild_forge(data)
|
||||||
_rebuild_workshop(data)
|
_rebuild_workshop(data)
|
||||||
|
_rebuild_attune(data)
|
||||||
_rebuild_recent(data)
|
_rebuild_recent(data)
|
||||||
refresh_camp(data)
|
refresh_camp(data)
|
||||||
visible = true
|
visible = true
|
||||||
@@ -196,6 +209,24 @@ func refresh_camp(data: Dictionary) -> void:
|
|||||||
st_col = UiKit.GOLD_DEEP
|
st_col = UiKit.GOLD_DEEP
|
||||||
card.status_l.text = st_text
|
card.status_l.text = st_text
|
||||||
card.status_l.add_theme_color_override("font_color", st_col)
|
card.status_l.add_theme_color_override("font_color", st_col)
|
||||||
|
# 同调装配段状态就地刷(迭代 11 第①批 T4——Slots 点亮/满位禁选/Y 升级行余额联动)
|
||||||
|
var att: Dictionary = data.get("attune", {})
|
||||||
|
if _attune_header_l != null:
|
||||||
|
var slots_n := int(att.get("slots", 0))
|
||||||
|
var eq: Array = att.get("equipped", [])
|
||||||
|
_attune_header_l.text = "── 同调装配 Slots %d/%d ──" % [eq.size(), slots_n]
|
||||||
|
var eq_set := {}
|
||||||
|
for v in eq:
|
||||||
|
eq_set[String(v)] = true
|
||||||
|
for card2 in _attune_btns:
|
||||||
|
var on: bool = eq_set.has(String(card2.item_id))
|
||||||
|
card2.btn.text = "卸下" if on else "装配"
|
||||||
|
card2.btn.disabled = (not on) and eq.size() >= slots_n # 满位禁选
|
||||||
|
if _refresh_l != null:
|
||||||
|
var rf: Dictionary = att.get("refresh", {})
|
||||||
|
_refresh_l.text = "三选一刷新 +%d 次/冒险 · Lv%d" % [int(rf.get("charges", 0)), int(rf.get("level", 0))]
|
||||||
|
_refresh_btn.text = "已满级" if bool(rf.get("maxed", false)) else "升级 %d 钥石" % int(rf.get("price", 0))
|
||||||
|
_refresh_btn.disabled = bool(rf.get("maxed", false)) or balance < int(rf.get("price", 0))
|
||||||
|
|
||||||
|
|
||||||
func _rebuild_forge(data: Dictionary) -> void:
|
func _rebuild_forge(data: Dictionary) -> void:
|
||||||
@@ -252,6 +283,43 @@ func _rebuild_workshop(data: Dictionary) -> void:
|
|||||||
_workshop_rows.append({"item_id": String(row.get("item_id", "")), "progress_l": progress_l, "status_l": status_l})
|
_workshop_rows.append({"item_id": String(row.get("item_id", "")), "progress_l": progress_l, "status_l": status_l})
|
||||||
|
|
||||||
|
|
||||||
|
# 同调装配段重建(迭代 11 第①批 T4——工坊柜台尾部追加;Slots 点亮+九件行板+Y 升级行,程序占位面)
|
||||||
|
func _rebuild_attune(data: Dictionary) -> void:
|
||||||
|
for n in _attune_nodes:
|
||||||
|
n.queue_free()
|
||||||
|
_attune_nodes.clear()
|
||||||
|
_attune_btns.clear()
|
||||||
|
var att: Dictionary = data.get("attune", {})
|
||||||
|
_attune_header_l = UiKit.label("", 13, true, UiKit.INK)
|
||||||
|
_workshop_holder.add_child(_attune_header_l)
|
||||||
|
_attune_nodes.append(_attune_header_l)
|
||||||
|
for row in att.get("items", []):
|
||||||
|
var iid := String(row.get("item_id", ""))
|
||||||
|
var line := HBoxContainer.new()
|
||||||
|
line.add_theme_constant_override("separation", 6)
|
||||||
|
var name_l := UiKit.label(String(row.get("display_name", "")), 13, true, UiKit.INK)
|
||||||
|
name_l.custom_minimum_size = Vector2(96, 0)
|
||||||
|
line.add_child(name_l)
|
||||||
|
var form_l := UiKit.label("被动" if String(row.get("form", "")) == "passive" else "主动·充能", 12, false, UiKit.STONE)
|
||||||
|
form_l.custom_minimum_size = Vector2(58, 0)
|
||||||
|
line.add_child(form_l)
|
||||||
|
var btn := UiKit.button("装配")
|
||||||
|
btn.pressed.connect(attune_toggled.emit.bind(iid))
|
||||||
|
line.add_child(btn)
|
||||||
|
_workshop_holder.add_child(line)
|
||||||
|
_attune_nodes.append(line)
|
||||||
|
_attune_btns.append({"item_id": iid, "btn": btn})
|
||||||
|
var refresh_line := HBoxContainer.new()
|
||||||
|
refresh_line.add_theme_constant_override("separation", 6)
|
||||||
|
_refresh_l = UiKit.label("", 12, false, UiKit.STONE)
|
||||||
|
refresh_line.add_child(_refresh_l)
|
||||||
|
_refresh_btn = UiKit.button("升级")
|
||||||
|
_refresh_btn.pressed.connect(_emit_buy_requested.bind("attune_refresh"))
|
||||||
|
refresh_line.add_child(_refresh_btn)
|
||||||
|
_workshop_holder.add_child(refresh_line)
|
||||||
|
_attune_nodes.append(refresh_line)
|
||||||
|
|
||||||
|
|
||||||
func _rebuild_recent(data: Dictionary) -> void:
|
func _rebuild_recent(data: Dictionary) -> void:
|
||||||
var lines: Array[String] = []
|
var lines: Array[String] = []
|
||||||
var recent: Array = data.get("recent", [])
|
var recent: Array = data.get("recent", [])
|
||||||
|
|||||||
@@ -34,11 +34,41 @@ func _initialize() -> void:
|
|||||||
var route := "min"
|
var route := "min"
|
||||||
if args.size() >= 4 and args[3] in ["min", "max"]:
|
if args.size() >= 4 and args[3] in ["min", "max"]:
|
||||||
route = args[3]
|
route = args[3]
|
||||||
|
# 同调分层开关(迭代 11 第①批 T8——off=现行基线零装配零扰动;on=贪心装配画像,确定性同种子可复放)
|
||||||
|
var attune_mode := "off"
|
||||||
|
if args.size() >= 5 and args[4] in ["off", "on"]:
|
||||||
|
attune_mode = args[4]
|
||||||
var db = load("res://core/config_db.gd").new()
|
var db = load("res://core/config_db.gd").new()
|
||||||
if not db.load_tables("res://data/tables", false):
|
if not db.load_tables("res://data/tables", false):
|
||||||
print("表加载失败")
|
print("表加载失败")
|
||||||
quit(1)
|
quit(1)
|
||||||
return
|
return
|
||||||
|
var attune_profile := {}
|
||||||
|
if attune_mode == "on":
|
||||||
|
# 贪心画像:中间进度计数(通关 3/Boss 击杀 6)经谓词表求值 Slots;装配序 = 表序 passive 优先至
|
||||||
|
# Slots-1 + 首个 active(slots=3 → [淬毒刃纹, 标中殉爆, 掘进蓄能],provisional 画像归调参批);
|
||||||
|
# Y = 营地升级 Lv1 表值 1。autoplay 默认不刷——Y 仅注入容量,刷新消费归玩家策略面(钓鱼归玩家)。
|
||||||
|
var slots: int = db.attunement_growth.slots({"clear_count": 3, "boss_kill_count": 6})
|
||||||
|
var passives: Array = []
|
||||||
|
var actives: Array = []
|
||||||
|
for r in db.aux_attunement_rows():
|
||||||
|
if String(r.get("form", "")) == "passive":
|
||||||
|
passives.append(String(r.get("item_id", "")))
|
||||||
|
elif String(r.get("form", "")) == "active_charged":
|
||||||
|
actives.append(String(r.get("item_id", "")))
|
||||||
|
var equipped: Array = []
|
||||||
|
for p in passives:
|
||||||
|
if equipped.size() >= maxi(0, slots - 1):
|
||||||
|
break
|
||||||
|
equipped.append(p)
|
||||||
|
if slots > equipped.size() and not actives.is_empty():
|
||||||
|
equipped.append(actives[0])
|
||||||
|
for p in passives:
|
||||||
|
if equipped.size() >= slots:
|
||||||
|
break
|
||||||
|
if not equipped.has(p):
|
||||||
|
equipped.append(p)
|
||||||
|
attune_profile = {"equipped": equipped, "refresh_y": 1}
|
||||||
var autoplay = load("res://smoke/level_autoplay.gd")
|
var autoplay = load("res://smoke/level_autoplay.gd")
|
||||||
var t0 := Time.get_ticks_msec()
|
var t0 := Time.get_ticks_msec()
|
||||||
print("=== 冒险全流程扫描:三关连探 · N=%d/策略 · 种子 %d 起逐局独立 · 策略=%s · atk+0%%/hp+0%% 零加成 · route=%s ===" % [n, seed0, policy_arg, route])
|
print("=== 冒险全流程扫描:三关连探 · N=%d/策略 · 种子 %d 起逐局独立 · 策略=%s · atk+0%%/hp+0%% 零加成 · route=%s ===" % [n, seed0, policy_arg, route])
|
||||||
@@ -51,10 +81,11 @@ func _initialize() -> void:
|
|||||||
for i in range(n):
|
for i in range(n):
|
||||||
var seed_val := seed0 + _run_cursor
|
var seed_val := seed0 + _run_cursor
|
||||||
_run_cursor += 1
|
_run_cursor += 1
|
||||||
var st = autoplay.run_adventure(db, seed_val, 0, 0, false, logger, policy, route)
|
var st = autoplay.run_adventure(db, seed_val, 0, 0, false, logger, policy, route, {}, attune_profile)
|
||||||
var sum: Dictionary = st["summary"]
|
var sum: Dictionary = st["summary"]
|
||||||
var rec := {
|
var rec := {
|
||||||
"policy": policy, "seed": seed_val, "atk_pct": 0, "hp_pct": 0, "route": route,
|
"policy": policy, "seed": seed_val, "atk_pct": 0, "hp_pct": 0, "route": route,
|
||||||
|
"attune": attune_mode, "equipped": attune_profile.get("equipped", []),
|
||||||
"result": st["result"],
|
"result": st["result"],
|
||||||
"cleared": bool(sum.get("cleared", false)),
|
"cleared": bool(sum.get("cleared", false)),
|
||||||
"levels_cleared": int(sum.get("levels_cleared", -1)),
|
"levels_cleared": int(sum.get("levels_cleared", -1)),
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ static func _drive_level(runner, st: Dictionary, logger, policy: String, route:
|
|||||||
# (last_battle_enemies——末场战斗敌方显示名序列,失败局即死亡遭遇)。
|
# (last_battle_enemies——末场战斗敌方显示名序列,失败局即死亡遭遇)。
|
||||||
static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_rows: bool = false,
|
static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_rows: bool = false,
|
||||||
logger = null, policy: String = "normal", route: String = "min",
|
logger = null, policy: String = "normal", route: String = "min",
|
||||||
hero_override: Dictionary = {}) -> Dictionary:
|
hero_override: Dictionary = {}, attune: Dictionary = {}) -> Dictionary:
|
||||||
var own_session := logger == null
|
var own_session := logger == null
|
||||||
if own_session:
|
if own_session:
|
||||||
logger = load("res://presentation/playtest_logger.gd").new()
|
logger = load("res://presentation/playtest_logger.gd").new()
|
||||||
@@ -149,7 +149,12 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_
|
|||||||
adr.aux_replace_presented.connect(_on_adventure_aux_replace.bind(st, adr))
|
adr.aux_replace_presented.connect(_on_adventure_aux_replace.bind(st, adr))
|
||||||
adr.adventure_finished.connect(_on_adventure_finished.bind(st, logger))
|
adr.adventure_finished.connect(_on_adventure_finished.bind(st, logger))
|
||||||
adr.set_hero_override(atk_pct, hp_pct)
|
adr.set_hero_override(atk_pct, hp_pct)
|
||||||
if not adr.start_adventure(seed_val, {"hero_override": hero_override, "first_script": false}): # 测量夹具恒抽签(v0.4——首次脚本分支不进任何读数)
|
# 同调分层注入(迭代 11 第①批 T8——缺省 {} 与既有调用方逐位一致零行为变更)
|
||||||
|
var adv_params := {"hero_override": hero_override, "first_script": false}
|
||||||
|
if not attune.is_empty():
|
||||||
|
adv_params["attunement"] = attune.get("equipped", [])
|
||||||
|
adv_params["attune_refresh_y"] = int(attune.get("refresh_y", 0))
|
||||||
|
if not adr.start_adventure(seed_val, adv_params): # 测量夹具恒抽签(v0.4——首次脚本分支不进任何读数)
|
||||||
st.result = "start_failed"
|
st.result = "start_failed"
|
||||||
if own_session:
|
if own_session:
|
||||||
logger.close_session()
|
logger.close_session()
|
||||||
|
|||||||
+69
-28
@@ -210,11 +210,11 @@ func _smoke_adventure_table() -> void:
|
|||||||
check(int(_db.adventure.bonus_cap_ap) == 2 and int(_db.adventure.bonus_cap_attack) == int(_db.adventure.bonus_cap_max_hp)
|
check(int(_db.adventure.bonus_cap_ap) == 2 and int(_db.adventure.bonus_cap_attack) == int(_db.adventure.bonus_cap_max_hp)
|
||||||
and int(_db.adventure.bonus_cap_max_hp) == int(_db.adventure.bonus_cap_armor),
|
and int(_db.adventure.bonus_cap_max_hp) == int(_db.adventure.bonus_cap_armor),
|
||||||
"次数上限口径:AP 件=%d(全冒险最紧)· 属性件=%d(三件同)" % [int(_db.adventure.bonus_cap_ap), int(_db.adventure.bonus_cap_attack)])
|
"次数上限口径:AP 件=%d(全冒险最紧)· 属性件=%d(三件同)" % [int(_db.adventure.bonus_cap_ap), int(_db.adventure.bonus_cap_attack)])
|
||||||
check(int(_db.adventure.bonus_cap("gp")) == _db.adventure.BONUS_CAP_UNLIMITED
|
check(int(_db.adventure.bonus_cap("gp")) == _db.adventure.BONUS_CAP_UNLIMITED,
|
||||||
and int(_db.adventure.bonus_cap("aux_item")) == _db.adventure.BONUS_CAP_UNLIMITED,
|
"GP 件无次数上限(牌库恒非空的结构保证;aux_item 池种已随同调批 T7 除名)")
|
||||||
"GP/道具件无次数上限(牌库恒非空的结构保证)")
|
check(_db.attunement_growth != null and int(_db.attunement_slot_cap()) == 3
|
||||||
check(int(_db.adventure.transitional_sync_slots) >= 0 and int(_db.adventure.transitional_sync_slots) <= 9,
|
and Attunement.slots(_db, null) == 0,
|
||||||
"transitional_sync_slots ∈ [0,9](%d——同调位过渡读数源)" % int(_db.adventure.transitional_sync_slots))
|
"同调位读数源=attunement_growth 谓词表(slot_cap 3;未注入档求值 0——transitional_sync_slots 过渡键已退役,迭代 11 第①批 T7)")
|
||||||
|
|
||||||
|
|
||||||
# ② 冒险结构 API 闸(状态机受理面——重复开始拒绝 / 入口停点 / 放弃两态)
|
# ② 冒险结构 API 闸(状态机受理面——重复开始拒绝 / 入口停点 / 放弃两态)
|
||||||
@@ -527,23 +527,15 @@ func _smoke_adventure() -> void:
|
|||||||
var draw: Array = a11._draw_bonus_candidates()
|
var draw: Array = a11._draw_bonus_candidates()
|
||||||
check(not (draw as Array).has("attack") and not (draw as Array).has("ap"),
|
check(not (draw as Array).has("attack") and not (draw as Array).has("ap"),
|
||||||
"达上限件剔牌库(attack 计满 %d / ap 计满 %d 不出现在候选——%s)" % [int(_db.adventure.bonus_cap_attack), int(_db.adventure.bonus_cap_ap), str(draw)])
|
"达上限件剔牌库(attack 计满 %d / ap 计满 %d 不出现在候选——%s)" % [int(_db.adventure.bonus_cap_attack), int(_db.adventure.bonus_cap_ap), str(draw)])
|
||||||
check((draw as Array).size() >= 2, "剔牌库后候选恒 ≥2(GP/道具件无次数上限——防「三选项全不可选」死角)")
|
check((draw as Array).size() >= 2, "剔牌库后候选恒 ≥2(GP 件无次数上限——防「三选项全不可选」死角)")
|
||||||
# 同调位替换·丢旧收新路(slots 过渡键=1,carry 满 1 件再取道具件 → 满位转替换)
|
# 池收缩(迭代 11 同调批 T7——aux 卡退役出三选一牌库:重复抽样零 aux 候选;同调位替换两路
|
||||||
var slots := int(_db.adventure.transitional_sync_slots)
|
# white-box 测试随停点渠道退役拆除——机械惰性承载,零投放零行为)
|
||||||
a11._bonus_pending = {"candidates": ["aux_item", "gp", "attack"], "payload": {}}
|
var aux_seen := false
|
||||||
a11._bonus_aux_carry.assign(["probe_dust"])
|
for i in 40:
|
||||||
check(a11.pick_bonus("aux_item") and a11._bonus_pending.has("incoming"),
|
if (a11._draw_bonus_candidates() as Array).has("aux_item"):
|
||||||
"满位道具件转替换子交互(持有 %d/%d)" % [a11._bonus_aux_carry.size(), slots])
|
aux_seen = true
|
||||||
check(a11.resolve_aux_replace(0) and (a11._bonus_aux_carry as Array).has("blast_kit") \
|
break
|
||||||
and (a11._bonus_aux_carry as Array).size() <= slots,
|
check(not aux_seen, "池收缩——40 次重抽零 aux 候选(停点渠道 aux 卡退役,迭代 11 第①批 T7)")
|
||||||
"替换受理·丢旧收新(终态 ≤ 同调位 %d——%s)" % [slots, str(a11._bonus_aux_carry)])
|
|
||||||
# 同调位替换·放弃新件路(原持有集不动、新件不获取)
|
|
||||||
a11._bonus_pending = {"candidates": ["aux_item"], "payload": {}}
|
|
||||||
a11._bonus_result = {}
|
|
||||||
a11._bonus_aux_carry.assign(["probe_dust"])
|
|
||||||
check(a11.pick_bonus("aux_item"), "替换场景二:满位再转替换子交互")
|
|
||||||
check(a11.resolve_aux_replace(-1) and a11._bonus_aux_carry.size() == 1 and String(a11._bonus_aux_carry[0]) == "probe_dust",
|
|
||||||
"替换受理·放弃新件(原持有集不动、新件不获取——两路终态恒 ≤ 同调位)")
|
|
||||||
# 满血回 0 合法(通关回血封顶口径)
|
# 满血回 0 合法(通关回血封顶口径)
|
||||||
var a12 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(12))
|
var a12 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(12))
|
||||||
a12.start_adventure(12, {"first_script": true})
|
a12.start_adventure(12, {"first_script": true})
|
||||||
@@ -3456,7 +3448,8 @@ func _smoke_meta() -> void:
|
|||||||
# 冒烟隔离存档名册(段首清陈旧 + 段尾清理——契约:测试不污染 user://save.json 真实档)
|
# 冒烟隔离存档名册(段首清陈旧 + 段尾清理——契约:测试不污染 user://save.json 真实档)
|
||||||
const _META_SMOKE_PATHS := ["user://smoke_meta_buy.json", "user://smoke_meta_dry.json",
|
const _META_SMOKE_PATHS := ["user://smoke_meta_buy.json", "user://smoke_meta_dry.json",
|
||||||
"user://smoke_meta_settle.json", "user://smoke_meta_workshop.json",
|
"user://smoke_meta_settle.json", "user://smoke_meta_workshop.json",
|
||||||
"user://smoke_meta_cap.json", "user://smoke_meta_white.json"]
|
"user://smoke_meta_cap.json", "user://smoke_meta_white.json",
|
||||||
|
"user://smoke_meta_attune.json", "user://smoke_meta_attune2.json"]
|
||||||
|
|
||||||
|
|
||||||
func _meta_smoke_cleanup() -> void:
|
func _meta_smoke_cleanup() -> void:
|
||||||
@@ -3632,6 +3625,54 @@ func _meta_settle_and_workshop() -> void:
|
|||||||
"stopped_at": 1, "keystone_total": 0, "master_seed": i})
|
"stopped_at": 1, "keystone_total": 0, "master_seed": i})
|
||||||
check(sv_c.recent_summaries().size() == 10 and int(sv_c.recent_summaries()[0].get("master_seed", -1)) == 11,
|
check(sv_c.recent_summaries().size() == 10 and int(sv_c.recent_summaries()[0].get("master_seed", -1)) == 11,
|
||||||
"摘要 cap 10 且最新在前(12 连 settle 截断后 seed 11 居首)")
|
"摘要 cap 10 且最新在前(12 连 settle 截断后 seed 11 居首)")
|
||||||
|
# ⑪ 同调位账本(迭代 11 第①批 T4——装配持久/满位禁选/Y 升级语义;写口 = MetaProgression 同调段)
|
||||||
|
var sv_a = _meta_new_save(_META_SMOKE_PATHS[6])
|
||||||
|
var mp_a = load("res://core/meta_progression.gd").new(_db, sv_a)
|
||||||
|
check(Attunement.slots(_db, sv_a) == 0 and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 0,
|
||||||
|
"白板档同调位求值 = 0(0 起步裁决)且 Y=0(未购无刷新能力——P1 甲初始 0 同构)")
|
||||||
|
check(mp_a.toggle_attunement("venom_edge") == false,
|
||||||
|
"满位禁选(Slots 0 时装配拒绝)")
|
||||||
|
sv_a.add_keystone(200)
|
||||||
|
check(mp_a.buy_attune_refresh() and sv_a.attune_refresh_level() == 1
|
||||||
|
and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 1,
|
||||||
|
"Y 升级购买入账(Lv1 → 每冒险 +1 次刷新)")
|
||||||
|
mp_a.settle_adventure({"result": "failed", "cleared": false, "levels_cleared": 1,
|
||||||
|
"stopped_at": 2, "keystone_total": 0, "master_seed": 21})
|
||||||
|
check(Attunement.slots(_db, sv_a) == 1 and sv_a.boss_kill_count() == 1,
|
||||||
|
"通关 1 关 → m1 达成 Slots=1 且 Boss 击杀总数=1(计数源收束入账)")
|
||||||
|
check(mp_a.toggle_attunement("venom_edge") and mp_a.toggle_attunement("flag_burst") == false,
|
||||||
|
"装配持久写入 + 满位禁选(Slots 1 装第二件拒绝)")
|
||||||
|
check(mp_a.toggle_attunement("venom_edge") and mp_a.toggle_attunement("flag_burst"),
|
||||||
|
"卸下恒可再装配(换装语义——装配序数组承载)")
|
||||||
|
var sv_a2 = _meta_new_save(_META_SMOKE_PATHS[7])
|
||||||
|
sv_a2.set_attunement_equipped(["luck_coin"])
|
||||||
|
var mp_a2 = load("res://core/meta_progression.gd").new(_db, sv_a2)
|
||||||
|
check((mp_a2.attune_camp_status()["equipped"] as Array).has("luck_coin"),
|
||||||
|
"装配关系读回持久(reload 后投影一致)")
|
||||||
|
# ⑫ 同调批 T8 断言面:clamp / 充能重置 / 刷新双计数器(white-box——迭代 11 第①批)
|
||||||
|
var tg := AttunementGrowthTable.new()
|
||||||
|
tg.slot_cap = 1
|
||||||
|
tg.milestones = [{"id": "x1", "grant": 1, "requires": [{"key": "clear_count", "threshold": 1}]},
|
||||||
|
{"id": "x2", "grant": 1, "requires": [{"key": "boss_kill_count", "threshold": 1}]}]
|
||||||
|
check(tg.slots({"clear_count": 5, "boss_kill_count": 5}) == 1,
|
||||||
|
"同调位 clamp(Σ达成 grant 2 → slots 1 = slot_cap——谓词表口径)")
|
||||||
|
var a12 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(12))
|
||||||
|
check(a12.start_adventure(12, {"attunement": ["dig_charge", "nest_pendulum"]}),
|
||||||
|
"充能重置场景:装配冒险启动(seed 12)")
|
||||||
|
check(int(a12.attune_charges.get("dig_charge", -1)) == 2 and int(a12.attune_charges.get("nest_pendulum", -1)) == 2,
|
||||||
|
"充能账本按装备 active_charged 件重置(每冒险 charges_per_adventure 表值)")
|
||||||
|
a12._attunement = ["pick_refresh"]
|
||||||
|
a12._attune_refresh_x = 1
|
||||||
|
a12._attune_refresh_y = 2
|
||||||
|
a12._bonus_pending = {"candidates": ["gp", "attack", "max_hp"], "payload": {}}
|
||||||
|
check(a12.refresh_bonus() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2,
|
||||||
|
"刷新双计数器——X 优先消耗(x1y2 → x0y2)")
|
||||||
|
check(a12.refresh_bonus() and a12.refresh_bonus() and a12._attune_refresh_y == 0
|
||||||
|
and a12.refresh_bonus() == false,
|
||||||
|
"刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)")
|
||||||
|
a12._reset_refresh_x()
|
||||||
|
check(a12.attune_refresh_left() == 1,
|
||||||
|
"刷新 X 每碰三选一重置(不结转——重碰面 x 回表值 1)")
|
||||||
_meta_smoke_cleanup()
|
_meta_smoke_cleanup()
|
||||||
|
|
||||||
|
|
||||||
@@ -3771,8 +3812,8 @@ func _smoke_save() -> void:
|
|||||||
fm.store_string(JSON.stringify({"version": 0, "keystone_old": 12}))
|
fm.store_string(JSON.stringify({"version": 0, "keystone_old": 12}))
|
||||||
fm.close()
|
fm.close()
|
||||||
check(f7.load_from_disk() and f7.keystone_balance() == 12, "v0 旧档经迁移链入账(keystone_old 12 → 余额 12)")
|
check(f7.load_from_disk() and f7.keystone_balance() == 12, "v0 旧档经迁移链入账(keystone_old 12 → 余额 12)")
|
||||||
check(f7.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[5], FileAccess.READ).get_as_text()).get("version", -1)) == 3,
|
check(f7.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[5], FileAccess.READ).get_as_text()).get("version", -1)) == 4,
|
||||||
"迁移链 0→1(测试钩)→3(生产) 写盘 version == 3(v3=断点附随件扩列,迭代 10 奖励重构)")
|
"迁移链 0→1(测试钩)→4(生产) 写盘 version == 4(v4=同调批扩域,迭代 11 第①批)")
|
||||||
|
|
||||||
# ⑧ 域界干净:meta 永久进度域不含局内资源键(行动点/RAP/GP/道具库存/生命账本不入档——契约 2;
|
# ⑧ 域界干净:meta 永久进度域不含局内资源键(行动点/RAP/GP/道具库存/生命账本不入档——契约 2;
|
||||||
# 迭代 8 卡①修订:扫描收窄到 meta 域——断点域=冒险进行中瞬态快照,生命/GP 按拍板面入域豁免)
|
# 迭代 8 卡①修订:扫描收窄到 meta 域——断点域=冒险进行中瞬态快照,生命/GP 按拍板面入域豁免)
|
||||||
@@ -3868,9 +3909,9 @@ func _smoke_save() -> void:
|
|||||||
fold.store_string("{\"version\": 1, \"meta\": {\"keystone_balance\": 4}}")
|
fold.store_string("{\"version\": 1, \"meta\": {\"keystone_balance\": 4}}")
|
||||||
fold.close()
|
fold.close()
|
||||||
check(fo.load_from_disk() and fo.keystone_balance() == 4 and not fo.has_breakpoint(),
|
check(fo.load_from_disk() and fo.keystone_balance() == 4 and not fo.has_breakpoint(),
|
||||||
"v1 旧档迁移 v3:断点域补空(合法空态,无断点域不炸)")
|
"v1 旧档迁移 v4:断点域补空(合法空态,无断点域不炸)")
|
||||||
check(fo.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[9], FileAccess.READ).get_as_text()).get("version", -1)) == 3,
|
check(fo.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[9], FileAccess.READ).get_as_text()).get("version", -1)) == 4,
|
||||||
"v1 旧档迁移后写盘 version == 3")
|
"v1 旧档迁移后写盘 version == 4(v1→v4 链式——同调批扩域 2026-10-03)")
|
||||||
|
|
||||||
# ⑫ v2 旧档→v3 兼容(迭代 10 g 裁——semver 向后兼容:旧断点零丢失、新代码可续玩)
|
# ⑫ v2 旧档→v3 兼容(迭代 10 g 裁——semver 向后兼容:旧断点零丢失、新代码可续玩)
|
||||||
var fv2 = sm_script.new(paths[10])
|
var fv2 = sm_script.new(paths[10])
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://7nn2b0crxvas
|
||||||
Reference in new issue
Block a user