merge: 奇物词族对齐与渠道分离批T0-T8全落(迭代15②号卡;冒烟782/0+run_all 16套历史首次全绿+五探针绿+doc三闸零新增+MC n200只测不判+存档键六形态对拍全等)
This commit is contained in:
commit
3c6f2c91e2
40 files changed
+1001
-768
No files matched your search
@@ -157,6 +157,14 @@
|
|||||||
- **看什么(实机目检约 2 分钟)**:①标题屏应为左右双列——左操作列(冒险/营地与系统两组)与右立绘看台+玩法速览牌并排,互不遮挡,「退出游戏」按钮完整可见;②同组按钮等宽(菜单列/确认弹窗),按钮不再宽窄参差;③营地屏三柜台+底栏不出屏、不重叠。
|
- **看什么(实机目检约 2 分钟)**:①标题屏应为左右双列——左操作列(冒险/营地与系统两组)与右立绘看台+玩法速览牌并排,互不遮挡,「退出游戏」按钮完整可见;②同组按钮等宽(菜单列/确认弹窗),按钮不再宽窄参差;③营地屏三柜台+底栏不出屏、不重叠。
|
||||||
- **红灯信号**:任何屏出现控件叠压/按钮宽窄不一回潮/内容超出屏幕——结构探针已拦机检面,红灯=探针盲区,报批补断言。
|
- **红灯信号**:任何屏出现控件叠压/按钮宽窄不一回潮/内容超出屏幕——结构探针已拦机检面,红灯=探针盲区,报批补断言。
|
||||||
|
|
||||||
|
### ⑰ 奇物渠道分离与门票目检(2026-10-05 奇物词族对齐与渠道分离批随批登记——迭代15②号卡)
|
||||||
|
|
||||||
|
- **专长兑付时点(单列检查项——契约①核心)**:关底 Boss 被击败那一刻应弹出通关三选一(候选应全为专长件=卡面只写单件名与效果);**战斗结算面不应出现任何奇物授予**;普通战斗节点打完不给任何奇物。
|
||||||
|
- **门票目检**:奖励节点/商店偶见「奇物门票」卡(价与其他商品相当);选中/买下同一时刻应**直接开出一次奇物三选一**(不进持有集、无库存残留);同局第二次买到应正常(限购 1——第三次起不再出现)。
|
||||||
|
- **商店货架目检**:货架应偶见「应急回复」行(T6 补回复);原吞噬流件「零特性挤卡保底」已随渠道分离退役——货架不再出现替卡行为。
|
||||||
|
- **红灯信号**:战斗结算后弹奇物;门票进持有集;货架出现专长单件(非门票形态);refresh 件写进角色加成区。
|
||||||
|
- **结论回流**:呈现面违规改法+档位呈现三路=C1 待裁(呈现批);强度体感归 M-4 带判读(本批 MC 只测不判)。
|
||||||
|
|
||||||
## 验收读数重测义务(履行留痕 + 现行义务)
|
## 验收读数重测义务(履行留痕 + 现行义务)
|
||||||
|
|
||||||
**已履行**(按序换代,旧读数各随其批作废留痕不入本单):①验收读数重测批(2026-09-19——Boss 单独出列/承伤带/通关率基线全量重测,硬化翻倍期·乏力死效果期·充能期旧读数作废);②迭代 5 基线重测(统一随机流后 MC+verifier 双窗);③**数值修复批新尺重测(2026-10-01)**——模拟档位重构后全量重测,新锚 `sim-data/runsum_numfix_final_n200_20261001.jsonl`,全旧带作废只测不判([numfix 报告](../qa/numfix-newruler-readings-2026-10-01.md));④**迭代 10 奖励重构批重锚(2026-10-01)**——四代锚 77000/78000/79000/80000(改前/重构后/共享池后/掷定前置后,各 400 局),普通 L1 41.8%/L2 10.2% 带内([读数报告](../qa/m3-iter10-clear-rate-readings-2026-10-01.md));⑤**T19 真人标定局收口(2026-10-03 用户裁)**——有效局 2(1 通关 1 死于 L3 首战,[读数报告](../qa/t19-real-playtest-readings-2026-10-02.md)),用户裁定**佐证级收口不再补局**;k 预期带同日立(**正常 >0.5、预警 <0.3**),字段随 81000 重锚入库后补算终值。
|
**已履行**(按序换代,旧读数各随其批作废留痕不入本单):①验收读数重测批(2026-09-19——Boss 单独出列/承伤带/通关率基线全量重测,硬化翻倍期·乏力死效果期·充能期旧读数作废);②迭代 5 基线重测(统一随机流后 MC+verifier 双窗);③**数值修复批新尺重测(2026-10-01)**——模拟档位重构后全量重测,新锚 `sim-data/runsum_numfix_final_n200_20261001.jsonl`,全旧带作废只测不判([numfix 报告](../qa/numfix-newruler-readings-2026-10-01.md));④**迭代 10 奖励重构批重锚(2026-10-01)**——四代锚 77000/78000/79000/80000(改前/重构后/共享池后/掷定前置后,各 400 局),普通 L1 41.8%/L2 10.2% 带内([读数报告](../qa/m3-iter10-clear-rate-readings-2026-10-01.md));⑤**T19 真人标定局收口(2026-10-03 用户裁)**——有效局 2(1 通关 1 死于 L3 首战,[读数报告](../qa/t19-real-playtest-readings-2026-10-02.md)),用户裁定**佐证级收口不再补局**;k 预期带同日立(**正常 >0.5、预警 <0.3**),字段随 81000 重锚入库后补算终值。
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
>
|
>
|
||||||
> 状态:**已实现**(2026-10-04 双层 BD 实施批落地)。本文件是奇物的唯一定义文档。
|
> 状态:**已实现**(2026-10-04 双层 BD 实施批落地)。本文件是奇物的唯一定义文档。
|
||||||
>
|
>
|
||||||
> 数值纪律:本文件只写概念、取值域与指路;每件给多少、效果是什么一律不进正文,事实源是 `game/data/tables/relics.tres` 与 `game/data/tables/adventure.tres`。
|
> 数值纪律:本文件只写概念、取值域与指路;每件给多少、效果是什么一律不进正文,事实源是 `game/data/tables/feats.tres` 与 `game/data/tables/adventure.tres`。
|
||||||
|
|
||||||
## 设计目的
|
## 设计目的
|
||||||
|
|
||||||
@@ -299,7 +299,7 @@
|
|||||||
|
|
||||||
| 参数 | 取值域 | 事实源 |
|
| 参数 | 取值域 | 事实源 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 专长定义行(标识 / 显示名 / 稀有度 / 标签 / 挂点 / 效果字典) | 各字段封闭枚举,行键白名单封闭 | `game/data/tables/relics.tres` |
|
| 专长定义行(标识 / 显示名 / 稀有度 / 标签 / 挂点 / 效果字典) | 各字段封闭枚举,行键白名单封闭 | `game/data/tables/feats.tres` |
|
||||||
| 专长门票(出现概率 / 配额 / 全局限量) | 整数 | **尚未入表**,见待办 |
|
| 专长门票(出现概率 / 配额 / 全局限量) | 整数 | **尚未入表**,见待办 |
|
||||||
| 属性值提升种类 | 封闭三项:生命上限 / AC / 攻击,扩项走文档修订 | `game/data/schemas/adventure_table.gd` |
|
| 属性值提升种类 | 封闭三项:生命上限 / AC / 攻击,扩项走文档修订 | `game/data/schemas/adventure_table.gd` |
|
||||||
| 资源种类 | 行动点上限 / 金币 / 回复(**刷新次数与专长获取两项尚未入表**,见待办) | 同上 |
|
| 资源种类 | 行动点上限 / 金币 / 回复(**刷新次数与专长获取两项尚未入表**,见待办) | 同上 |
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 节点奖励屏 UX spec(node-reward)
|
# 节点奖励屏 UX spec(node-reward)
|
||||||
|
|
||||||
> 状态:v2.4(2026-10-03 TP0 令牌池落地——三选一池改固定配额令牌池 70/25/3=98 张〔罕见档随道具件池种退役收窄〕,kind 六种+档位两维,恢复件新入池,MC 策略 316-f 落地)
|
> 状态:v2.4(2026-10-03 TP0 令牌池落地——三选一池改固定配额令牌池 70/25/3=98 张〔罕见档随道具件池种退役收窄〕,kind 六种+档位两维,恢复件新入池,MC 策略 316-f 落地)
|
||||||
> 事实源:`game/presentation/views/bonus_pick_overlay.gd`(三选一覆盖层共用面)+ `main.gd`(奖励节点分支——`adventure.node_bonus_candidates/apply_node_bonus`)+ `battle_narrator.gd`(node_bonus_header)
|
> 事实源:`game/presentation/views/curio_pick_overlay.gd`(三选一覆盖层共用面)+ `main.gd`(奖励节点分支——`adventure.node_bonus_candidates/apply_node_bonus`)+ `battle_narrator.gd`(node_bonus_header)
|
||||||
> 相邻:[level-clear](level-clear.md)(通关三选一面——同一覆盖层的另一上下文)/ [level-map](level-map.md)(关卡画面)/ [秘宝](../systems/排雷循环/秘宝.md)(道具件内容权威——本批起为局外同调件,不再入三选一牌库)/ [冒险结构](../systems/冒险循环/冒险结构.md)(共享池令牌库账本)
|
> 相邻:[level-clear](level-clear.md)(通关三选一面——同一覆盖层的另一上下文)/ [level-map](level-map.md)(关卡画面)/ [秘宝](../systems/排雷循环/秘宝.md)(道具件内容权威——本批起为局外同调件,不再入三选一牌库)/ [冒险结构](../systems/冒险循环/冒险结构.md)(共享池令牌库账本)
|
||||||
|
|
||||||
## 定位
|
## 定位
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# 出口报告:迭代 15 卡② 奇物词族对齐与渠道分离批(2026-10-05)
|
||||||
|
|
||||||
|
> 任务书 = `design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md`(T0 差异回写+T6b 裁决回写在册)。
|
||||||
|
> 设计事实源 = `design/gdd/systems/冒险循环/奇物.md` v2.7。worktree = `g-plan2-curio-wordfamily`,分支 `curio-wordfamily-20261005`,基线 = master 413c936(冒烟 772/0)。
|
||||||
|
|
||||||
|
## 一、提交链(worktree)
|
||||||
|
|
||||||
|
| # | 提交 | 内容 |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | d4cde73 | T1:feature_*→curio_*(CURIO_KINDS/curio_quota/curio_value/curio_candidate_count + adventure.tres 键名 + entities.yaml 换代续写) |
|
||||||
|
| 2 | 8e2694d | T2:运行层 bonus_*→curio_* 全族 + curio_interact_controller.gd/curio_pick_overlay.gd 两文件改名 + 裁点甲案 + reward_overlay 红转绿适配 |
|
||||||
|
| 3 | 6812ebb | T3+T4:relic_*→feat_*(三文件改名+全 token+config_db 注册键两端)+ 渠道白名单过滤(通关渠道只出专长/节点零专长)+ devour 货架保底退役 |
|
||||||
|
| 4 | 95b66f0 | T5+T6a:门票入表(四字段 provisional+三计数+双注入+拿到即开三入口+断点扩列+shop.tres 门票行+A5/A6 断言)+ T6a 商店补 heal 行 + registry 刷新边界写死 |
|
||||||
|
| 5 | 5 | ccd95c8 | T6b+T7+缺陷修:refresh 入池(B 案)+ charge 登记 + autoplay 门票分支(MC timeout 形态批内实测修复) |
|
||||||
|
|
||||||
|
## 二、批末读数(T8 回填)
|
||||||
|
|
||||||
|
| 闸 | 基线(413c936) | 批末 | 判定 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 冒烟 smoke_all | 772/0 | **782/0**(+10=双渠道白名单 2+门票 A5/A6 行为断言 7+门票兑付受理 1) | |
|
||||||
|
| run_all | 15/16(reward_overlay 红=M7 基线红) | **16/16 全绿** | **红转绿(T2 适配,run_all 历史首次全绿)** |
|
||||||
|
| 行为探针四件+ui_struct | 全绿 | **五探针全绿**(ui_probe/ui_click/ui_input/ui_tune/ui_struct exit 全 0) | |
|
||||||
|
| doc 三闸+熵增闸 | 零新增基线 | lint 8文件2不通过=基线同款/term 218命中3疑似=基线同款/link 死链 7→修后 4(3 处系本批改名断链已修:奇物.md:16/:302 relics.tres→feats.tres+node-reward.md:4 bonus_pick_overlay→curio_pick_overlay;余 4=worktree 假象×2+既有 ux 债×2)/熵增闸 struct 违例×1=主区同款既有(archify design/diagrams 越界,归该批) | **零新增** ||
|
||||||
|
| MC n200 双策略(82000 窗,route=min 零加成) | 无同窗基线(判式批锚=76000 系旧结构;主区 413c936 同窗 n20 对照跑) | normal 0.5%(1/200 通关)/perfect 77.5%(155/200);修 autoplay 门票分支后 n20 与主区基线逐位一致(normal 0 空终局/perfect 16 完 4 败);**读数只测不判**(渠道分离+门票改变奇物流,带判读归 M-4;锚换代=82000 系新锚 n200 日志留盘 user://logs/playtest_82000_*.jsonl) | |
|
||||||
|
|
||||||
|
## 三、验收断言(A1-A12 逐条)
|
||||||
|
|
||||||
|
- **A1 词族归零**:A1 模式残留 **40 处全保名单**——_bonus_ 37 处(_bonus_row/_bonus_row_l/_bonus_summary_text/_bonus_value=结算加成行呈现面;_bonus_treasure_carry/_bonus_treasure_item/_bonus_treasure_pool=③批秘宝语义)+ node_relic_seen 局部变量 3 处(冒烟断言变量,指称 relic 令牌语义)。引号数据契约键全形态保留:bonus_deck×3/bonus_taken×3/bonus_pending×3/bonus_result×2/bonus_aux_carry×2/"bonus"×15(断点属性区键)/relics×6/relic×25(kind 值+身份前缀+摘要键)(保名单:_bonus_row/_bonus_value/_bonus_summary_text 等加成区呈现+bonus_summary+存档键六形态+kind 值 "relic"/身份前缀 "relic:"/relics_seed 进程内参数——穷举见 §五)
|
||||||
|
- **A2 禁词未用**:reward_pick/reward_node/reward_preview 既有三处零新增(T2 全程未触)
|
||||||
|
- **A3 兑付频率**:专长兑付点=通关三选一(_pending_next_level 置位链=关底 Boss 击败同一时刻);关内普通战斗节点零奇物(node_type=battle 无三选一调用,结构保证+冒烟既有断言面)
|
||||||
|
- **A4 渠道分离**:_draw_curio_candidates(channel) 白名单过滤——通关渠道 60 次重抽全 relic/节点渠道 60 次零 relic(冒烟断言在案);货架白名单=shop_pool_rows 词表(feat_ticket 门票行+heal 回复行+四池种)
|
||||||
|
- **A5 拿到即开**:门票兑付=候选替换专长面+零摘牌+无残留持有集(冒烟 4 断言在案)
|
||||||
|
- **A6 保底计数**:pity=5→第 6 展示必出/触发清零/限购满冻结/概率面存在(冒烟 4 断言在案)
|
||||||
|
- **A7 值零变更**:T1 改名键值逐位相等(diff 实测)+ heal 5→3/refresh 0→2 系 T6b 用户裁 B 案结构面(非本断言范围)
|
||||||
|
- **A8 存档往返**:断点往返测试 PASS(全批三次验证:T1/T2/T4 后各一);存档键按 T2 裁点甲案+relics 第 6 键澄清(§四-3)
|
||||||
|
- **A9 池配额锚**:TIER_QUOTAS 70/25/3=98 零改动(B 案挤占——heal 5→3/refresh 0→2,总量不变,validate 绿)
|
||||||
|
- **A10 节点类型零扩类**:封闭枚举五种未动
|
||||||
|
- **A11 玩家可见面无分类名**:门票卡面写「奇物门票/奇物三选一」(规避「专长」分类名);本批只做断言检查不改法(C1 归呈现批)
|
||||||
|
- **A12 档二通路**:charge=3 件(<4)——**显式登记不硬补**(白名单 4;registry charge_defect 落文,转内容批)
|
||||||
|
|
||||||
|
## 四、裁点与自裁记录
|
||||||
|
|
||||||
|
1. **T2 存档键裁点(白名单 3)**:**甲案**——五存档键(bonus_deck/bonus_taken/bonus_pending/bonus_result/bonus_aux_carry)引号字面量禁改,代码侧变量全换 curio_。依据:与③批先例一致;全仓对拍六形态逐键全等。
|
||||||
|
2. **bonus_aux_carry 语义澄清(T0 回写)**:该键系**秘宝件跨关携带**(③批已对齐 treasure 词族),非奇物概念——剔除出本批改名面;_bonus_treasure_carry 变量随③批保留。
|
||||||
|
3. **"relics" 第 6 存档键(任务书漏登记)**:adventure_runner 断点快照/摘要 additive 键 "relics"(迭代 12 T1 扩列)——任务书 §2.5 只列 5 键漏此键;按甲案精神禁改+registry/任务书回写。kind 值 "relic"/身份前缀 "relic:"/relics_seed 参数同理保留(数据契约键,A1 扫描口径=relic_ 下划线形态)。
|
||||||
|
4. **T6b refresh 入池配额(用户门呈报,用户裁 B 案)**:refresh rare=2 从 heal rare 5 挤占(heal 5→3),总量 98 锚不变;契约 10 按裁更新;**池架构语义=总池-渠道子集(多级缓存形)**确立落 registry pool_architecture。
|
||||||
|
5. **devour 货架保底退役(T4 连带)**:迭代 12 T6「货架零特性挤卡换吞噬流件」简化口径与渠道白名单直接冲突(devour=规则改写型=专长系不得进商店货架)——随渠道定位退役,留痕注释。
|
||||||
|
6. **reward_overlay_test 红转绿(T2 连带)**:成因考证=M7 bcard 换装(PanelContainer)致 `c is Button` 恒 0(主区 3c4291d 起红在案);断言改数 _choice_box 子节点数(语义不变)——run_all 16 套历史首次全绿。
|
||||||
|
|
||||||
|
## 五、例外清单(A1 扫描口径残存量,批末)
|
||||||
|
|
||||||
|
【A1_EXCEPT】
|
||||||
|
|
||||||
|
## 六、遗留与移交
|
||||||
|
|
||||||
|
1. 07 清单登记(T8 已落 §⑰):①专长兑付时点目检(关底 Boss 击败那一刻=通关三选一,**不是战斗结算后**)+门票拿到即开目检+商店门票/回复行目检;②refresh 件入池后 heal 出现率感知归 T7 类节奏重测(跑批待批)
|
||||||
|
2. C1 玩家可见面违规改法+档位呈现三路 → 呈现批(06 独立条目)
|
||||||
|
3. C2 第四关+档二复裁+charge 补件 → 第四关内容批
|
||||||
|
4. C3 门票四参数终值(现 provisional 20%/2/1/5)→ 曲线调参批
|
||||||
|
5. C4 存档键正式迁移 → 存档批(本批甲案未迁移,兼容读旧键零破坏)
|
||||||
|
6. MC 运行含既有 script error 噪音(ticket_id/minesweeper_rules 链)——待基线对拍归属,读数采集不受影响
|
||||||
@@ -595,14 +595,17 @@ entities:
|
|||||||
channel_constraint_layers: 渠道约束分三层——一层 分类白名单(分类轴决定渠道);二层 可计算谓词(渠道与奇物的结构性关系,如「计价物不自售」:X 是渠道 C 的计价物 → X 不进 C;新渠道/新奇物自动判定,不用改表);三层 显式例外清单(人为设计选择,只能逐条加,每条须写理由)。要害不是有没有例外,而是例外能不能被算出来——结构性原因写成谓词,设计性原因进清单。在册约束两条:①金币不进商店(二层·谓词)②专长在非 Boss 渠道只给门票(一层·分类形态)。**注意**:属性值提升/资源不出现在 Boss 渠道**不是约束**——那是渠道轴的定位(Boss 是专长独占渠道),不属默认原则管辖。**「计价物不自售」形式上确是在白名单外加的一道过滤(用户质疑成立),与死特判的区别在于把枚举例外变成计算谓词**
|
channel_constraint_layers: 渠道约束分三层——一层 分类白名单(分类轴决定渠道);二层 可计算谓词(渠道与奇物的结构性关系,如「计价物不自售」:X 是渠道 C 的计价物 → X 不进 C;新渠道/新奇物自动判定,不用改表);三层 显式例外清单(人为设计选择,只能逐条加,每条须写理由)。要害不是有没有例外,而是例外能不能被算出来——结构性原因写成谓词,设计性原因进清单。在册约束两条:①金币不进商店(二层·谓词)②专长在非 Boss 渠道只给门票(一层·分类形态)。**注意**:属性值提升/资源不出现在 Boss 渠道**不是约束**——那是渠道轴的定位(Boss 是专长独占渠道),不属默认原则管辖。**「计价物不自售」形式上确是在白名单外加的一道过滤(用户质疑成立),与死特判的区别在于把枚举例外变成计算谓词**
|
||||||
axes: 两根互不决定的轴——**分类轴**(给的是什么)与**渠道轴**(从哪来)。一个奇物属于哪个分类不决定它从哪个渠道来;交叉关系由 design/gdd/systems/冒险循环/奇物.md 第 2 节开关表定死
|
axes: 两根互不决定的轴——**分类轴**(给的是什么)与**渠道轴**(从哪来)。一个奇物属于哪个分类不决定它从哪个渠道来;交叉关系由 design/gdd/systems/冒险循环/奇物.md 第 2 节开关表定死
|
||||||
categories: 封闭三类 {专长 Feat, 属性值提升 ASI, 资源}(v2.7 定名,**去「类」字后缀**——此前为描述式叫法「机制类 / 属性提升类 / 资源类」,非专名;用户 v1.1 原话即「属性提升(ASI)」与「专长」,v1.1 落文时被加后缀而退化。v2.0 起为三类、v1.1 曾用两类、v1.0 曾用 {rule, stat},均已作废)。
|
categories: 封闭三类 {专长 Feat, 属性值提升 ASI, 资源}(v2.7 定名,**去「类」字后缀**——此前为描述式叫法「机制类 / 属性提升类 / 资源类」,非专名;用户 v1.1 原话即「属性提升(ASI)」与「专长」,v1.1 落文时被加后缀而退化。v2.0 起为三类、v1.1 曾用两类、v1.0 曾用 {rule, stat},均已作废)。
|
||||||
hierarchy: 玩家可见的三层(用户 2026-10-05 拍板)——一层总称「奇物」/二层三个分类/三层具体单件(如 奥术余烬、贯穿射击)。**命名与呈现是两件事**:分类各有一个正式专名,文档、代码词族、帮百科/成就名/效果描述的指代一律用它;但卡面与详情页只写单件名与效果、不附带分类(呈现面归 battle.md / node-reward.md 裁决,本条目不预断)专长(Feat)——在结算管线某个挂点上给自己加一条规则,同标签持有项数达阈值时质变,事实源 relics.tres;属性值提升(ASI)——抬高生命上限 / AC / 攻击三轴基础值,进冒险加算区,事实源 adventure.tres;资源——给"可用的量",不抬高战斗属性也不改规则:行动点上限 / 刷新次数 / 金币 / 回复,不进加算区、多数不进持有集,事实源 adventure.tres
|
hierarchy: 玩家可见的三层(用户 2026-10-05 拍板)——一层总称「奇物」/二层三个分类/三层具体单件(如 奥术余烬、贯穿射击)。**命名与呈现是两件事**:分类各有一个正式专名,文档、代码词族、帮百科/成就名/效果描述的指代一律用它;但卡面与详情页只写单件名与效果、不附带分类(呈现面归 battle.md / node-reward.md 裁决,本条目不预断)专长(Feat)——在结算管线某个挂点上给自己加一条规则,同标签持有项数达阈值时质变,事实源 feats.tres;属性值提升(ASI)——抬高生命上限 / AC / 攻击三轴基础值,进冒险加算区,事实源 adventure.tres;资源——给"可用的量",不抬高战斗属性也不改规则:行动点上限 / 刷新次数 / 金币 / 回复,不进加算区、多数不进持有集,事实源 adventure.tres
|
||||||
scope: 作用对象 = 角色本人与其参与的结算(不改敌人、不改棋盘生成、不改关卡内容);**例外**——刷新次数作用在三选一界面而非角色身上,不进加算区;时间范围 = 整局冒险,专长与属性值提升跨关携带、关间不清零,金币与回复即时兑付;归属范围 = 不进账号永久进度,不带到下一次冒险
|
scope: 作用对象 = 角色本人与其参与的结算(不改敌人、不改棋盘生成、不改关卡内容);**例外**——刷新次数作用在三选一界面而非角色身上,不进加算区;时间范围 = 整局冒险,专长与属性值提升跨关携带、关间不清零,金币与回复即时兑付;归属范围 = 不进账号永久进度,不带到下一次冒险
|
||||||
lifecycle: 候选池构建(冒险开局一次)→ 在渠道上出现(三选一形式)→ 选定(自池中剔除不再复现;选到「专长获取」时立刻再开一次专长 N 选一)→ 持有生效或即时兑付 → 冒险以任一结局收束时整集清零;断点记录持有集与剩余候选池
|
lifecycle: 候选池构建(冒险开局一次)→ 在渠道上出现(三选一形式)→ 选定(自池中剔除不再复现;选到「专长获取」时立刻再开一次专长 N 选一)→ 持有生效或即时兑付 → 冒险以任一结局收束时整集清零;断点记录持有集与剩余候选池
|
||||||
acquisition: 三条渠道的**目标分工(交叉开关,v2.2 收口)**——专长:主渠道=**关底 Boss 击败=通过这一关**(**唯一直接给的渠道**;不新增节点类型、挂 Boss 节点兑付面;末关的 Boss 击败后不给——无后续关卡可消费)。**兑付频率**:奇物不是打一场战斗给一份,关内普通战斗节点不给任何奇物,一关之内只兑付一次(奖励节点与商店同理,各节点各自兑付),奖励节点与商店**只给「专长获取」门票**——**拿到即开、不可囤积**,不进持有集(选中/买下同一时刻开专长三选一;可囤积会让玩家无限推迟关键决策,且把持有物语义从后门放回)。门票归专长(给的是一件以前不会的事,只是把选择推迟)。罕见**只在概率不在价格**,一局限购一次,**计步保底**:连续 5 次三选一展示未出现→第 6 次必出(计数=展示次数、刷新重展计入、奖励节点与商店共享、触发清零、限购用满后不再触发);属性值提升:奖励节点 + 商店(口径「训练」);资源:奖励节点 + 商店(**金币除外、回复在内**);后两类均不出现在 Boss 渠道。形状=「一块独占 + 两块并行」,专长靠打赢、其余靠抽取与购买。**默认原则**:除已登记的特殊规则外,分类内的东西都要在它该出现的渠道池子里——任何不出现都须登记理由**现行实现三渠道共用同一候选池、三类混编**,分工尚未实施,登记于 06-待办清单 BD 分层条目
|
acquisition: 三条渠道的**目标分工(交叉开关,v2.2 收口)**——专长:主渠道=**关底 Boss 击败=通过这一关**(**唯一直接给的渠道**;不新增节点类型、挂 Boss 节点兑付面;末关的 Boss 击败后不给——无后续关卡可消费)。**兑付频率**:奇物不是打一场战斗给一份,关内普通战斗节点不给任何奇物,一关之内只兑付一次(奖励节点与商店同理,各节点各自兑付),奖励节点与商店**只给「专长获取」门票**——**拿到即开、不可囤积**,不进持有集(选中/买下同一时刻开专长三选一;可囤积会让玩家无限推迟关键决策,且把持有物语义从后门放回)。门票归专长(给的是一件以前不会的事,只是把选择推迟)。罕见**只在概率不在价格**,一局限购一次,**计步保底**:连续 5 次三选一展示未出现→第 6 次必出(计数=展示次数、刷新重展计入、奖励节点与商店共享、触发清零、限购用满后不再触发);属性值提升:奖励节点 + 商店(口径「训练」);资源:奖励节点 + 商店(**金币除外、回复在内**);后两类均不出现在 Boss 渠道。形状=「一块独占 + 两块并行」,专长靠打赢、其余靠抽取与购买。**默认原则**:除已登记的特殊规则外,分类内的东西都要在它该出现的渠道池子里——任何不出现都须登记理由**现行实现三渠道共用同一候选池、三类混编**,分工尚未实施,登记于 06-待办清单 BD 分层条目
|
||||||
boundary: 判据走生命周期——**一次冒险内获得、冒险收束即清零的是奇物;跨冒险跟着你走的是魔法道具**。奇物永不进同调位,魔法道具永不进当局随机渠道;与元进度天赋树两层互不代位
|
boundary: 判据走生命周期——**一次冒险内获得、冒险收束即清零的是奇物;跨冒险跟着你走的是魔法道具**。奇物永不进同调位,魔法道具永不进当局随机渠道;与元进度天赋树两层互不代位
|
||||||
data_sources: relics.tres(专长定义行)/ adventure.tres(属性值提升与资源的种类配额与值)/ shop.tres(货架取舍)
|
data_sources: feats.tres(专长定义行)/ adventure.tres(属性值提升与资源的种类配额与值)/ shop.tres(货架取舍)
|
||||||
|
charge_defect: **charge 档二不可达登记(T7 奇物批迭代15②——数据缺陷留痕,白名单4授权「只登记不硬补」)**:feats.tres 现行 charge 标签仅 3 件(<4),档二激活要求同标签 ≥4 件——charge 流派永远达不到档二,即使获取次数无限。补哪几件系内容裁决,转独立内容批(硬补占位件已否决);档二可达性复裁=第四关内容批落地后实测(C2 待裁点)。
|
||||||
|
pool_architecture: **池架构=总池-渠道子集(多级缓存形,用户 2026-10-05 裁 T6 时确立)**:总池=单一令牌库(TIER_QUOTAS 70/25/3=98 + 专长活行追加);三渠道按分类白名单从总池取**子集视图**(关底 Boss 击败=只出专长子集;奖励节点/商店=属性值提升+资源子集,专长只以门票形态出现)——渠道不各建物理池,白名单在候选视图层生效(T4 过滤式)。配额挤占重裁:heal rare 5→3、refresh rare 0→2(方案B,总量 98 锚不变;用户裁「渠道从总量里拿子集」时同步裁)。
|
||||||
|
refresh_boundary: **刷新次数边界写死(T6 奇物批迭代15②——防后续混淆结算)**:「商店独立刷新机制」(shop_refresh_free/shop_refresh_cost_base/shop_refresh_cost_curve 三旋钮——进店后的免费次数+GP 付费刷新)系**商店自身服务**,作用对象=货架重抽;奇物 refresh 件(资源类成员,作为奇物授予的重抽次数)作用对象=**三选一界面**(兑付即 _attune_refresh_x 增量,X 余量=三选一重抽次数),不进加算区(§2.2 加算区四轴不含它)。两者不是一回事,禁止混算。
|
||||||
open_questions: ①类内异质性已登记——金币与回复是即时兑付、不进持有集不持续生效,与行动点上限/刷新次数生命周期不同,靠这条描述把"资源"从泛用词收回到本类定义内;②专长门票尚未入表(出现概率/配额/限购次数/保底阈值四参数均无事实源);③确定性渠道一局两次专长 vs 档位阈值(同标签 ≥2 档一 / ≥4 档二)——**档二在三关结构下几乎不可达**,用户定延后复裁(触发=第四关内容批后实测);④charge 标签仅 3 件、永远达不到档二(数据层缺陷,独立登记,勿被上条盖掉);⑤断点存档键 5 个(bonus_deck / bonus_taken / bonus_pending / bonus_result / bonus_aux_carry)是否随存档版本迁移换名待裁
|
open_questions: ①类内异质性已登记——金币与回复是即时兑付、不进持有集不持续生效,与行动点上限/刷新次数生命周期不同,靠这条描述把"资源"从泛用词收回到本类定义内;②专长门票尚未入表(出现概率/配额/限购次数/保底阈值四参数均无事实源);③确定性渠道一局两次专长 vs 档位阈值(同标签 ≥2 档一 / ≥4 档二)——**档二在三关结构下几乎不可达**,用户定延后复裁(触发=第四关内容批后实测);④charge 标签仅 3 件、永远达不到档二(数据层缺陷,独立登记,勿被上条盖掉);⑤断点存档键 5 个(bonus_deck / bonus_taken / bonus_pending / bonus_result / bonus_aux_carry)是否随存档版本迁移换名待裁
|
||||||
naming_note: 2026-10-05 命名统一 + 两次重构——此前按内部分类被叫成三个名字(规则类称「流派件」、属性类称「数值件」、界面称「本关强化」),v1.0 统一为「特性」,v2.0 改名为「奖励」,v2.4 定名为「奇物」(因筐里也装金币这类一次性兑付物,"自身提升 vs 持有物"已切不开,判据改由生命周期承担)。v2.5 第三类定名为「资源」(用户拍板)。**v2.7 三个子节点全部正式定名、去「类」字后缀**:专长(Feat)/ 属性值提升(ASI)/ 资源——此前是描述式叫法不是专名。**代码侧三套词族均尚未对齐**:`feature_*` → `curio_*`(103 处,顶层总称)、`relic_*` → `feat_*`(162 处,专长分类的词族——v2.7 定名后顶层与内层分类各有各的词族)、运行层 `bonus_*` → `curio_*`(160 处),属临时态,登记于 06-待办清单,须在可跑冒烟批次收口
|
naming_note: 2026-10-05 命名统一 + 两次重构——此前按内部分类被叫成三个名字(规则类称「流派件」、属性类称「数值件」、界面称「本关强化」),v1.0 统一为「特性」,v2.0 改名为「奖励」,v2.4 定名为「奇物」(因筐里也装金币这类一次性兑付物,"自身提升 vs 持有物"已切不开,判据改由生命周期承担)。v2.5 第三类定名为「资源」(用户拍板)。**v2.7 三个子节点全部正式定名、去「类」字后缀**:专长(Feat)/ 属性值提升(ASI)/ 资源——此前是描述式叫法不是专名。**代码侧三套词族已于奇物批对齐(2026-10-05 迭代15②批 T1-T3)**:`feature_*` → `curio_*`(CURIO_KINDS/curio_quota/curio_value)、`relic_*` → `feat_*`(feat_table.gd/feats.tres/feats_engine.gd/feat_flags/feat_id——kind 值 "relic" 与身份前缀 "relic:" 系令牌池数据契约键沿用不动)、运行层 `bonus_*` → `curio_*`(curio_interact_controller/curio_pick_overlay/CurioPickOverlay/pick_curio);断点存档键(bonus_deck/bonus_taken/bonus_pending/bonus_result/bonus_aux_carry/relics)按甲案沿用历史名不迁移(存档兼容)
|
||||||
referenced_by: [冒险结构, 关卡节点地图, 战斗结算, 行动经济, 地形变异, HUD框架, 魔法道具, 元进度, 数据配置]
|
referenced_by: [冒险结构, 关卡节点地图, 战斗结算, 行动经济, 地形变异, HUD框架, 魔法道具, 元进度, 数据配置]
|
||||||
|
|
||||||
- name: adventure_table
|
- name: adventure_table
|
||||||
@@ -613,12 +616,12 @@ entities:
|
|||||||
fields: |
|
fields: |
|
||||||
退役两字段(迭代 10 奖励重构批,2026-09-30 用户拍板):level_clear_hp_bonus(「血回满+上限提升」件随大奖励三件套退役——回血改 clear_heal_pct 部分回血、上限提升改三选一件值列)、keystone_per_level(钥石退役出大奖励改 Boss/精英击杀掉落)。
|
退役两字段(迭代 10 奖励重构批,2026-09-30 用户拍板):level_clear_hp_bonus(「血回满+上限提升」件随大奖励三件套退役——回血改 clear_heal_pct 部分回血、上限提升改三选一件值列)、keystone_per_level(钥石退役出大奖励改 Boss/精英击杀掉落)。
|
||||||
clear_heal_pct: int [0,100](knob 默认 20——非末关通关部分回血回复生效上限的百分比,封顶上限不回满、满血回 0 合法;难度上压方向,带重扫义务归 难度曲线与Boss)
|
clear_heal_pct: int [0,100](knob 默认 20——非末关通关部分回血回复生效上限的百分比,封顶上限不回满、满血回 0 合法;难度上压方向,带重扫义务归 难度曲线与Boss)
|
||||||
**2026-10-05 命名统一批**:本表的属性值提升奇物字段与常量整体换名(旧 → 新)——BONUS_IDS → FEATURE_KINDS、bonus_token_count() → feature_quota()、bonus_value() → feature_value()、bonus_candidate_count → feature_candidate_count、token_{kind}_{tier} → feature_quota_{kind}_{tier}、bonus_*_flat/_amount(_large) → feature_value_{kind}_{tier};值零变更(句柄改称不改数),断点附随键名沿用历史形态不动(存档兼容)。换名动因见 curio 条目(v2.0 顶层改名为「奇物」)。
|
**2026-10-05 命名统一批**:本表的属性值提升奇物字段与常量整体换名(旧 → 新)——BONUS_IDS → CURIO_KINDS、bonus_token_count() → curio_quota()(→ curio_quota(),本批)、bonus_value() → curio_value()、bonus_candidate_count → curio_candidate_count、token_{kind}_{tier} → curio_quota_{kind}_{tier}、bonus_*_flat/_amount(_large) → curio_value_{kind}_{tier};值零变更(句柄改称不改数),断点附随键名沿用历史形态不动(存档兼容)。换名动因见 curio 条目(v2.0 顶层改名为「奇物」)。
|
||||||
feature_value_attack_common / feature_value_max_hp_common / feature_value_armor_common: int [0,999](三选一属性值提升奇物兑付值——加算区攻击/生命上限/护甲轴累加,经覆盖值通道;值锚与上调约束归 难度曲线与Boss)
|
curio_value_attack_common / curio_value_max_hp_common / curio_value_armor_common: int [0,999](三选一属性值提升奇物兑付值——加算区攻击/生命上限/护甲轴累加,经覆盖值通道;值锚与上调约束归 难度曲线与Boss)
|
||||||
feature_value_gp_common: int [0,999](GP 袋件定额入钱包);feature_value_ap_rarest: int [0,10](AP 件每场战斗首轮行动点基线增量);bonus_aux_item_count: int [1,9](魔法道具件授予件数)
|
curio_value_gp_common: int [0,999](GP 袋件定额入钱包);curio_value_ap_rarest: int [0,10](AP 件每场战斗首轮行动点基线增量);bonus_aux_item_count: int [1,9](魔法道具件授予件数)
|
||||||
~~bonus_weight_*(E-S 加权洗牌权重族)~~ / ~~bonus_cap_*(种类次数上限族)~~:**v2.2 令牌池换代退役(迭代 12 TP0——2026-10-03 追认 b 实施批;洗牌改令牌 Fisher-Yates、池消耗即上限)**
|
~~bonus_weight_*(E-S 加权洗牌权重族)~~ / ~~bonus_cap_*(种类次数上限族)~~:**v2.2 令牌池换代退役(迭代 12 TP0——2026-10-03 追认 b 实施批;洗牌改令牌 Fisher-Yates、池消耗即上限)**
|
||||||
feature_quota_{kind}_{tier}: int [0,99](**TP0 令牌配额族**——kind×tier 计数:attack 18/5 · max_hp 17/5 · armor 17/5 · gp 18/5 · heal 0/5 · ap 0/0/3〔common/rare/rarest〕;未列组合=0;档位结构锚 70/25/3=98 张入 schema validate 硬断言〔罕见档原列「道具件 2」随 aux_item 池种退役收窄——台账自决披露项〕)
|
curio_quota_{kind}_{tier}: int [0,99](**TP0 令牌配额族**——kind×tier 计数:attack 18/5 · max_hp 17/5 · armor 17/5 · gp 18/5 · heal 0/5 · ap 0/0/3〔common/rare/rarest〕;未列组合=0;档位结构锚 70/25/3=98 张入 schema validate 硬断言〔罕见档原列「道具件 2」随 aux_item 池种退役收窄——台账自决披露项〕)
|
||||||
feature_value_attack_rare / feature_value_max_hp_rare / feature_value_armor_rare / feature_value_gp_rare: int [0,999](稀有档大数值对——+2/+10/+2/50);feature_value_heal_rare: int [0,999](恢复件 +6 当即回血——新入池种类 rare 档);MC 策略 316-f(autoplay 1 级最稀有/2 级最左)随批落地
|
curio_value_attack_rare / curio_value_max_hp_rare / curio_value_armor_rare / curio_value_gp_rare: int [0,999](稀有档大数值对——+2/+10/+2/50);curio_value_heal_rare: int [0,999](恢复件 +6 当即回血——新入池种类 rare 档);MC 策略 316-f(autoplay 1 级最稀有/2 级最左)随批落地
|
||||||
transitional_sync_slots: int [0,9](同调位过渡读数源,默认 1——道具持有上限;正主 = attunement_growth.tres 里程碑谓词表,同调实施批落地后切换读数源)
|
transitional_sync_slots: int [0,9](同调位过渡读数源,默认 1——道具持有上限;正主 = attunement_growth.tres 里程碑谓词表,同调实施批落地后切换读数源)
|
||||||
mvp_content: 单行全局默认值即 MVP 行(provisional 归 曲线调参批——迭代 10 扩列批注)
|
mvp_content: 单行全局默认值即 MVP 行(provisional 归 曲线调参批——迭代 10 扩列批注)
|
||||||
extension_reserve: 大奖励形态扩列位已消费(迭代 10 奖励重构批落地三选一/攻击型——本键使命完成退役;冒险结构 OQ2 收口)
|
extension_reserve: 大奖励形态扩列位已消费(迭代 10 奖励重构批落地三选一/攻击型——本键使命完成退役;冒险结构 OQ2 收口)
|
||||||
|
|||||||
@@ -101,6 +101,8 @@
|
|||||||
|
|
||||||
### T1 词族对齐第一批:`feature_*` → `curio_*`(≈0.7d)
|
### T1 词族对齐第一批:`feature_*` → `curio_*`(≈0.7d)
|
||||||
|
|
||||||
|
> ✅ **T0 复核差异回写(2026-10-05 实测,master 413c936)**:①`feature_*` 实测 **93 处/7 文件**(§2.1 的 103 系③秘宝批动 smoke/bonus_interact_controller 前口径);②运行层 `bonus_` 族实测远超 160 处(含信号 bonus_choice_presented/bonus_resolved/bonus_overlay 等全部形态);③**`bonus_aux_carry` 语义澄清=秘宝件跨关携带(③批已对齐 treasure 词族,内部变量现名 _bonus_treasure_carry),非奇物概念——本批改名面剔除,存档键引号字面量照③批先例禁改**;④基线=冒烟 772/0+run_all 14/15(reward_overlay 红=M7 bcard 换控件致 `c is Button` 断言失效,成因已考,T2 触碰该测试时适配);⑤res_ 仅 smoke 局部变量 res_box 4 处(语义无关,资源词族 res_* 不撞)。
|
||||||
|
|
||||||
- 范围:`FEATURE_KINDS` → `CURIO_KINDS`、`feature_quota()` → `curio_quota()`、`feature_value()` → `curio_value()`、`feature_candidate_count` → `curio_candidate_count`、`feature_quota_{kind}_{tier}` → `curio_quota_*`、`feature_value_{kind}_{tier}` → `curio_value_*`
|
- 范围:`FEATURE_KINDS` → `CURIO_KINDS`、`feature_quota()` → `curio_quota()`、`feature_value()` → `curio_value()`、`feature_candidate_count` → `curio_candidate_count`、`feature_quota_{kind}_{tier}` → `curio_quota_*`、`feature_value_{kind}_{tier}` → `curio_value_*`
|
||||||
- 涉及:`adventure_table.gd` + `adventure.tres`(键名)+ 6 个引用文件
|
- 涉及:`adventure_table.gd` + `adventure.tres`(键名)+ 6 个引用文件
|
||||||
- **值零变更**(只改键名与常量名)
|
- **值零变更**(只改键名与常量名)
|
||||||
@@ -148,6 +150,8 @@
|
|||||||
|
|
||||||
### T6 刷新次数入表 + 商店补回复(≈0.5d)
|
### T6 刷新次数入表 + 商店补回复(≈0.5d)
|
||||||
|
|
||||||
|
> ✅ **T6b 裁决回写(2026-10-05 用户裁方案B)**:refresh rare=2 从 heal rare 5 挤占(heal 5→3),**总量 98 锚不变**——validate 硬断言零改动。契约 10「curio_quota_* 值零变更」按用户裁更新:本批结构面允许的配额变化=heal 5→3 挤占予 refresh 2(其余值零变更)。**池架构语义确立=总池-渠道子集(多级缓存形)**:总池单一令牌库,三渠道按白名单取子集视图(T4 过滤式即此形态),registry pool_architecture 已落文。
|
||||||
|
|
||||||
- `FEATURE_KINDS` 扩项须走 schema + registry 同步修订(封闭词表,红线 §3.6)
|
- `FEATURE_KINDS` 扩项须走 schema + registry 同步修订(封闭词表,红线 §3.6)
|
||||||
- ⚠️ **必须先划清边界**:既有「商店独立刷新机制」是**进店后的免费次数 + GP 刷新**(商店自身的服务),本项是**作为奇物授予的**重抽次数——**两者不是一回事**,入表前须在 registry 里把边界写死,防止后续混淆结算
|
- ⚠️ **必须先划清边界**:既有「商店独立刷新机制」是**进店后的免费次数 + GP 刷新**(商店自身的服务),本项是**作为奇物授予的**重抽次数——**两者不是一回事**,入表前须在 registry 里把边界写死,防止后续混淆结算
|
||||||
- 刷新次数的作用对象是**三选一界面而非角色**——不进加算区(§2.2 加算区四轴不含它)
|
- 刷新次数的作用对象是**三选一界面而非角色**——不进加算区(§2.2 加算区四轴不含它)
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ var _settlement_consumed := false # AC-14 门:notify_settlement_don
|
|||||||
var _next_round_mode := "" # 调试观测量(同板续轮 / 换新图)
|
var _next_round_mode := "" # 调试观测量(同板续轮 / 换新图)
|
||||||
var _pending_ap_delta := 0 # 下轮行动点修正排队(格面资源八型批 2026-09-20——action 型 ±1 排队,轮开始兑现后清零)
|
var _pending_ap_delta := 0 # 下轮行动点修正排队(格面资源八型批 2026-09-20——action 型 ±1 排队,轮开始兑现后清零)
|
||||||
# 蓄力流运行态(迭代 12 T5——relic 件注入面;false=关着零扰动):
|
# 蓄力流运行态(迭代 12 T5——relic 件注入面;false=关着零扰动):
|
||||||
# relic_ap_carry=整备军备(轮末剩余 AP 转蓄力层替代 RAP 转存——cleared 作废口径改写的停手侧承载);
|
# feat_ap_carry=整备军备(轮末剩余 AP 转蓄力层替代 RAP 转存——cleared 作废口径改写的停手侧承载);
|
||||||
# relic_dig_extra=深挖钻头付费开掘对价(R4 甲非信息税形态;2026-10-04 试玩缺陷修复——对价挂付费
|
# feat_dig_extra=深挖钻头付费开掘对价(R4 甲非信息税形态;2026-10-04 试玩缺陷修复——对价挂付费
|
||||||
# 开掘侧:AP 富余时挖掘付全价 1+extra 积付费计数、AP 不足时降级只付基础 1 照常开格不积计数——
|
# 开掘侧:AP 富余时挖掘付全价 1+extra 积付费计数、AP 不足时降级只付基础 1 照常开格不积计数——
|
||||||
# 原「持有期所有挖掘无条件 +1」实现把对价错挂成普通挖掘税,AP=1 锁死开格);charge_layers=蓄力层账本。
|
# 原「持有期所有挖掘无条件 +1」实现把对价错挂成普通挖掘税,AP=1 锁死开格);charge_layers=蓄力层账本。
|
||||||
var relic_ap_carry := false
|
var feat_ap_carry := false
|
||||||
var relic_dig_extra := 0
|
var feat_dig_extra := 0
|
||||||
var _charge_layers := 0
|
var _charge_layers := 0
|
||||||
var charge_layers: int:
|
var charge_layers: int:
|
||||||
get: return _charge_layers
|
get: return _charge_layers
|
||||||
@@ -95,8 +95,8 @@ func request_dig(chain_size: int, clears_map: bool) -> bool:
|
|||||||
_reject("phase")
|
_reject("phase")
|
||||||
return false
|
return false
|
||||||
var cost := _cost("dig")
|
var cost := _cost("dig")
|
||||||
if relic_dig_extra > 0 and _ap >= cost + relic_dig_extra:
|
if feat_dig_extra > 0 and _ap >= cost + feat_dig_extra:
|
||||||
cost += relic_dig_extra
|
cost += feat_dig_extra
|
||||||
_deep_paid_count += 1
|
_deep_paid_count += 1
|
||||||
if _ap < cost:
|
if _ap < cost:
|
||||||
_reject("resource_face")
|
_reject("resource_face")
|
||||||
@@ -136,13 +136,13 @@ func _spend_and_arbitrate(action_kind: String, clears_map: bool) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
# 停手:剩余 AP 1:1 转存 RAP;行动经济 直发 stopped 路径 round_end(双发射源)。
|
# 停手:剩余 AP 1:1 转存 RAP;行动经济 直发 stopped 路径 round_end(双发射源)。
|
||||||
# 整备军备改写(迭代 12 T5——relic_ap_carry=true 时剩余 AP 转蓄力层〔上限 3〕替代 RAP 转存:
|
# 整备军备改写(迭代 12 T5——feat_ap_carry=true 时剩余 AP 转蓄力层〔上限 3〕替代 RAP 转存:
|
||||||
# 「不动也是行动」——cleared 作废口径改写的停手侧承载;cleared 分支 AP 恒清零维持既有作废语义)
|
# 「不动也是行动」——cleared 作废口径改写的停手侧承载;cleared 分支 AP 恒清零维持既有作废语义)
|
||||||
func request_stop() -> bool:
|
func request_stop() -> bool:
|
||||||
if _phase != Phase.ACTIVE:
|
if _phase != Phase.ACTIVE:
|
||||||
_reject("phase")
|
_reject("phase")
|
||||||
return false
|
return false
|
||||||
if relic_ap_carry:
|
if feat_ap_carry:
|
||||||
_charge_layers = mini(3, _charge_layers + _ap)
|
_charge_layers = mini(3, _charge_layers + _ap)
|
||||||
else:
|
else:
|
||||||
_rap += _ap
|
_rap += _ap
|
||||||
|
|||||||
+235
-166
@@ -41,9 +41,9 @@ class_name AdventureRunner
|
|||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
signal level_started(level_index: int, display_name: String) # 每关开始(含冒险首关)——呈现层重取 runner()、刷新图条与层名行
|
signal level_started(level_index: int, display_name: String) # 每关开始(含冒险首关)——呈现层重取 runner()、刷新图条与层名行
|
||||||
signal bonus_choice_presented(payload: Dictionary) # 提升三选一停点(非末关通关恰一次;迭代 10 大奖励重构——big_reward_paid 退役)——{level_index, candidates, heal_gained, hp_before, hp_after, keystone_gained, keystone_total, gp, aux_cleared}
|
signal curio_choice_presented(payload: Dictionary) # 提升三选一停点(非末关通关恰一次;迭代 10 大奖励重构——big_reward_paid 退役)——{level_index, candidates, heal_gained, hp_before, hp_after, keystone_gained, keystone_total, gp, aux_cleared}
|
||||||
signal bonus_resolved(result: Dictionary) # 三选一兑付完成(pick_bonus/替换受理后恰一次)——{picked, value, hp_before, hp_after, level_index}
|
signal curio_resolved(result: Dictionary) # 三选一兑付完成(pick_curio/替换受理后恰一次)——{picked, value, hp_before, hp_after, level_index}
|
||||||
signal treasure_replace_presented(items: Array, incoming: Dictionary) # 同调位满位替换子交互(pick_bonus 道具件满位时)——items=现有持有件记录、incoming=新件记录
|
signal treasure_replace_presented(items: Array, incoming: Dictionary) # 同调位满位替换子交互(pick_curio 道具件满位时)——items=现有持有件记录、incoming=新件记录
|
||||||
signal adventure_finished(summary: Dictionary) # 冒险结算(completed/failed/abandoned)——summary 五件事:打穿没有/通关几关/止步第几关/钥石合计/主种子(+additive keystone_drops 迭代 10)
|
signal adventure_finished(summary: Dictionary) # 冒险结算(completed/failed/abandoned)——summary 五件事:打穿没有/通关几关/止步第几关/钥石合计/主种子(+additive keystone_drops 迭代 10)
|
||||||
# ---- 转发族(签名与 LevelRunner 同款——呈现层对接透明;ADR-0001 S6 经 _forward_* 方法)----
|
# ---- 转发族(签名与 LevelRunner 同款——呈现层对接透明;ADR-0001 S6 经 _forward_* 方法)----
|
||||||
signal node_entered(node: Dictionary, info: Dictionary)
|
signal node_entered(node: Dictionary, info: Dictionary)
|
||||||
@@ -79,16 +79,19 @@ var attune_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 奖励重构批)----
|
||||||
var _bonus_taken := {} # 件获取次数计数(kind 维度派生账本——断点附随件与摘要读数承载;池库存正主=_bonus_deck)
|
var _curio_taken := {} # 件获取次数计数(kind 维度派生账本——断点附随件与摘要读数承载;池库存正主=_curio_deck)
|
||||||
var _bonus_deck: Array = [] # 令牌池(TP0 v2.2——持久多重集,元素 {"kind","tier"};洗牌抽候选/选中踢池未选回池/池耗即枯竭;商店货架同库共账;迭代 12 T1 起规则改写型特性带=relic 令牌随表追加)
|
var _curio_deck: Array = [] # 令牌池(TP0 v2.2——持久多重集,元素 {"kind","tier"};洗牌抽候选/选中踢池未选回池/池耗即枯竭;商店货架同库共账;迭代 12 T1 起规则改写型特性带=relic 令牌随表追加)
|
||||||
var _relics: Array[String] = [] # 特性持有集(迭代 12 T1——局内随机渠道获得、冒险级跨关携带、收束清零;🔴 层级纪律承载面:不入同调位不入局外树)
|
var _feats: Array[String] = [] # 特性持有集(迭代 12 T1——局内随机渠道获得、冒险级跨关携带、收束清零;🔴 层级纪律承载面:不入同调位不入局外树)
|
||||||
var _tree_rules: Array = [] # 树 rule 键集(迭代 12 T10——Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面)
|
var _tree_rules: Array = [] # 树 rule 键集(迭代 12 T10——Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面)
|
||||||
var _bonus_pending := {} # 停点态:{"candidates": Array, "payload": Dictionary, "chosen"/"incoming": 替换在途}——空 = 无停点
|
var _curio_pending := {} # 停点态:{"candidates": Array, "payload": Dictionary, "chosen"/"incoming": 替换在途}——空 = 无停点
|
||||||
var _bonus_result := {} # 已兑付待推进态(bonus_resolved 载荷副本——中断续玩不重兑付)
|
var _curio_result := {} # 已兑付待推进态(curio_resolved 载荷副本——中断续玩不重兑付)
|
||||||
var _bonus_treasure_carry: Array[String] = [] # 道具件跨关携带(兑付后注入下一关库存——清库后授予的体验窗)
|
var _bonus_treasure_carry: Array[String] = [] # 道具件跨关携带(兑付后注入下一关库存——清库后授予的体验窗)
|
||||||
var _keystone_drops: Array = [] # Boss 逐杀掉落明细(additive 摘要键 keystone_drops——结算明细/复盘归因数据源)
|
var _keystone_drops: Array = [] # Boss 逐杀掉落明细(additive 摘要键 keystone_drops——结算明细/复盘归因数据源)
|
||||||
var _last_draw_treasure: Array[String] = [] # 最近一次抽签掷定的道具件(掷定前置——卡片直显具体道具;追加批二)
|
var _last_draw_treasure: Array[String] = [] # 最近一次抽签掷定的道具件(掷定前置——卡片直显具体道具;追加批二)
|
||||||
var _node_bonus_pending := {} # 奖励节点三选一停点态(迭代 10 追加批——共享池;断点不载:续玩重建到选路停点重走,恢复的计数器保证同候选重抽)
|
var _node_curio_pending := {} # 奖励节点三选一停点态(迭代 10 追加批——共享池;断点不载:续玩重建到选路停点重走,恢复的计数器保证同候选重抽)
|
||||||
|
var _feat_ticket_pity := 0 # 门票保底计数(T5——展示次数:节点开面/货架抽签/刷新重展各计一次;触发清零)
|
||||||
|
var _feat_ticket_sold := 0 # 门票限购计数(T5——选中/买下即 +1;拿到即开不进持有集)
|
||||||
|
var _feat_ticket_appeared := 0 # 门票出现计数(T5——quota 上限面)
|
||||||
var _shop_mode := "legacy" # 商店模式(迭代 11 第②批 T3——"legacy"=固定三件加定价〔off 对照承载,缺省零扰动〕/"pool"=共享池抽 3 买 1 重抽;真实入口 main.gd 显式投 pool)
|
var _shop_mode := "legacy" # 商店模式(迭代 11 第②批 T3——"legacy"=固定三件加定价〔off 对照承载,缺省零扰动〕/"pool"=共享池抽 3 买 1 重抽;真实入口 main.gd 显式投 pool)
|
||||||
|
|
||||||
|
|
||||||
@@ -114,7 +117,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
|||||||
purchased = {}
|
purchased = {}
|
||||||
keystone = 0
|
keystone = 0
|
||||||
summary = {}
|
summary = {}
|
||||||
_reset_bonus_state()
|
_reset_curio_state()
|
||||||
levels_total = db.level_count() # 表访问收口(2026-10-02——行集直扫退役)
|
levels_total = db.level_count() # 表访问收口(2026-10-02——行集直扫退役)
|
||||||
level_index = 1
|
level_index = 1
|
||||||
_pending_next_level = false
|
_pending_next_level = false
|
||||||
@@ -130,12 +133,12 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
|||||||
_seed_attune_charges() # 迭代 11 第①批 T6——冒险开始充能账本按装备主动件重置(每冒险充能语义)
|
_seed_attune_charges() # 迭代 11 第①批 T6——冒险开始充能账本按装备主动件重置(每冒险充能语义)
|
||||||
# 特性开局注入(迭代 12 T7——MC 成型画像载体:params.relics_seed 非空=开局持有集〔七流派
|
# 特性开局注入(迭代 12 T7——MC 成型画像载体:params.relics_seed 非空=开局持有集〔七流派
|
||||||
# 各 2 件档一成型——测量面专用;真实局规则改写型特性只走局内随机渠道,红线②层级纪律不破〕)
|
# 各 2 件档一成型——测量面专用;真实局规则改写型特性只走局内随机渠道,红线②层级纪律不破〕)
|
||||||
var relic_seed: Array = params.get("relics_seed", []) if params.get("relics_seed", []) is Array else []
|
var feat_seed: Array = params.get("relics_seed", []) if params.get("relics_seed", []) is Array else []
|
||||||
_tree_rules = params.get("tree_rules", []) if params.get("tree_rules", []) is Array else [] # 迭代 12 T10——A/B 面 rule 键集
|
_tree_rules = params.get("tree_rules", []) if params.get("tree_rules", []) is Array else [] # 迭代 12 T10——A/B 面 rule 键集
|
||||||
if not relic_seed.is_empty():
|
if not feat_seed.is_empty():
|
||||||
_relics = []
|
_feats = []
|
||||||
for r in relic_seed:
|
for r in feat_seed:
|
||||||
_relics.append(String(r))
|
_feats.append(String(r))
|
||||||
ledger["hero_hp"] = effective_max_hp() # 生命账本置满(生效上限口径——T17 修复:含调试浮窗 pct,与战斗内同源)
|
ledger["hero_hp"] = effective_max_hp() # 生命账本置满(生效上限口径——T17 修复:含调试浮窗 pct,与战斗内同源)
|
||||||
state = "running"
|
state = "running"
|
||||||
return _start_current_level()
|
return _start_current_level()
|
||||||
@@ -174,12 +177,12 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
|||||||
levels_total = db.level_count() # 表访问收口(2026-10-02——行集直扫退役)
|
levels_total = db.level_count() # 表访问收口(2026-10-02——行集直扫退役)
|
||||||
level_index = int(bp.get("level_index", 1))
|
level_index = int(bp.get("level_index", 1))
|
||||||
_pending_next_level = false
|
_pending_next_level = false
|
||||||
_reset_bonus_state()
|
_reset_curio_state()
|
||||||
var bp_taken: Dictionary = bp.get("bonus_taken", {}) as Dictionary
|
var bp_taken: Dictionary = bp.get("bonus_taken", {}) as Dictionary
|
||||||
for id in _bonus_taken.keys():
|
for id in _curio_taken.keys():
|
||||||
_bonus_taken[id] = maxi(0, int(bp_taken.get(id, 0)))
|
_curio_taken[id] = maxi(0, int(bp_taken.get(id, 0)))
|
||||||
# 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径;
|
# 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径;
|
||||||
# 迭代 12 T1——relic 令牌 relic_id 随还原)
|
# 迭代 12 T1——relic 令牌 feat_id 随还原)
|
||||||
var bp_deck = bp.get("bonus_deck", null)
|
var bp_deck = bp.get("bonus_deck", null)
|
||||||
if bp_deck is Array:
|
if bp_deck is Array:
|
||||||
var restored: Array = []
|
var restored: Array = []
|
||||||
@@ -187,13 +190,13 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
|||||||
var tok: Dictionary = t as Dictionary
|
var tok: Dictionary = t as Dictionary
|
||||||
var rtok := {"kind": String(tok.get("kind", "")), "tier": String(tok.get("tier", ""))}
|
var rtok := {"kind": String(tok.get("kind", "")), "tier": String(tok.get("tier", ""))}
|
||||||
if String(rtok["kind"]) == "relic":
|
if String(rtok["kind"]) == "relic":
|
||||||
rtok["relic_id"] = String(tok.get("relic_id", ""))
|
rtok["feat_id"] = String(tok.get("feat_id", ""))
|
||||||
restored.append(rtok)
|
restored.append(rtok)
|
||||||
_bonus_deck = restored
|
_curio_deck = restored
|
||||||
# 特性持有集还原(迭代 12 T1——缺键 = 旧版断点防御空集)
|
# 特性持有集还原(迭代 12 T1——缺键 = 旧版断点防御空集)
|
||||||
_relics = []
|
_feats = []
|
||||||
for r in (bp.get("relics", []) as Array):
|
for r in (bp.get("relics", []) as Array):
|
||||||
_relics.append(String(r))
|
_feats.append(String(r))
|
||||||
for v in (bp.get("bonus_aux_carry", []) as Array):
|
for v in (bp.get("bonus_aux_carry", []) as Array):
|
||||||
_bonus_treasure_carry.append(String(v))
|
_bonus_treasure_carry.append(String(v))
|
||||||
_keystone_drops = (bp.get("keystone_drops", []) as Array).duplicate(true)
|
_keystone_drops = (bp.get("keystone_drops", []) as Array).duplicate(true)
|
||||||
@@ -213,6 +216,8 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
|||||||
_seed_attune_charges() # 迭代 11 第①批 T6——冒险开始充能账本按装备主动件重置(每冒险充能语义)
|
_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
|
||||||
|
_feat_ticket_pity = int(bp.get("feat_ticket_pity", 0)) # T5 扩列——旧档缺键缺省 0(存档兼容)
|
||||||
|
_feat_ticket_sold = int(bp.get("feat_ticket_sold", 0))
|
||||||
var bp_result: Dictionary = bp.get("bonus_result", {}) as Dictionary
|
var bp_result: Dictionary = bp.get("bonus_result", {}) as Dictionary
|
||||||
if not bp_pending.is_empty():
|
if not bp_pending.is_empty():
|
||||||
_pending_next_level = true
|
_pending_next_level = true
|
||||||
@@ -220,31 +225,35 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
|||||||
var migrated: Array = []
|
var migrated: Array = []
|
||||||
for c in (bp_pending.get("candidates", []) as Array):
|
for c in (bp_pending.get("candidates", []) as Array):
|
||||||
if c is Dictionary:
|
if c is Dictionary:
|
||||||
migrated.append({"kind": String((c as Dictionary).get("kind", "")), "tier": String((c as Dictionary).get("tier", ""))})
|
# T4——feat_id 随迁移(通关渠道候选=纯专长令牌,身份键 relic|tier|feat_id 三段全要)
|
||||||
|
var mig := {"kind": String((c as Dictionary).get("kind", "")), "tier": String((c as Dictionary).get("tier", ""))}
|
||||||
|
if String(mig["kind"]) == "relic":
|
||||||
|
mig["feat_id"] = String((c as Dictionary).get("feat_id", ""))
|
||||||
|
migrated.append(mig)
|
||||||
else:
|
else:
|
||||||
migrated.append({"kind": String(c), "tier": db.adventure.TIER_COMMON})
|
migrated.append({"kind": String(c), "tier": db.adventure.TIER_COMMON})
|
||||||
_bonus_pending = {"candidates": migrated}
|
_curio_pending = {"candidates": migrated}
|
||||||
if bp_pending.has("aux_detail"):
|
if bp_pending.has("aux_detail"):
|
||||||
_bonus_pending["aux_detail"] = (bp_pending.get("aux_detail", []) as Array).duplicate(true) # 掷定前置随停点快照续接
|
_curio_pending["aux_detail"] = (bp_pending.get("aux_detail", []) as Array).duplicate(true) # 掷定前置随停点快照续接
|
||||||
var payload: Dictionary = (bp_pending.get("payload", {}) as Dictionary).duplicate(true)
|
var payload: Dictionary = (bp_pending.get("payload", {}) as Dictionary).duplicate(true)
|
||||||
if bp_pending.has("incoming"):
|
if bp_pending.has("incoming"):
|
||||||
_bonus_pending["chosen"] = "treasure_item"
|
_curio_pending["chosen"] = "treasure_item"
|
||||||
var incoming: Array = (bp_pending.get("incoming", []) as Array).duplicate(true)
|
var incoming: Array = (bp_pending.get("incoming", []) as Array).duplicate(true)
|
||||||
_bonus_pending["incoming"] = incoming
|
_curio_pending["incoming"] = incoming
|
||||||
state = "running"
|
state = "running"
|
||||||
bonus_choice_presented.emit(payload)
|
curio_choice_presented.emit(payload)
|
||||||
# 替换在途续发(呈现层直落替换子态——持有件记录按恢复后 carry 重建)
|
# 替换在途续发(呈现层直落替换子态——持有件记录按恢复后 carry 重建)
|
||||||
treasure_replace_presented.emit(_carry_records(),
|
treasure_replace_presented.emit(_carry_records(),
|
||||||
{"index": -1, "item_id": String(incoming[0]) if not incoming.is_empty() else "", "count": incoming.size()})
|
{"index": -1, "item_id": String(incoming[0]) if not incoming.is_empty() else "", "count": incoming.size()})
|
||||||
return true
|
return true
|
||||||
state = "running"
|
state = "running"
|
||||||
bonus_choice_presented.emit(payload)
|
curio_choice_presented.emit(payload)
|
||||||
return true
|
return true
|
||||||
if not bp_result.is_empty():
|
if not bp_result.is_empty():
|
||||||
_pending_next_level = true
|
_pending_next_level = true
|
||||||
_bonus_result = bp_result.duplicate(true)
|
_curio_result = bp_result.duplicate(true)
|
||||||
state = "running"
|
state = "running"
|
||||||
bonus_resolved.emit(_bonus_result.duplicate(true))
|
curio_resolved.emit(_curio_result.duplicate(true))
|
||||||
return true
|
return true
|
||||||
_resume_bp = bp.duplicate(true)
|
_resume_bp = bp.duplicate(true)
|
||||||
# 迭代 11 第①批 T6——充能账本随断点回局(缺键 = 旧档/无断点,保持 _seed_attune_charges 种子态)
|
# 迭代 11 第①批 T6——充能账本随断点回局(缺键 = 旧档/无断点,保持 _seed_attune_charges 种子态)
|
||||||
@@ -256,36 +265,36 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
# 三选一停点态归零(冒险开始/结算收束共用;令牌池按表配额重建——池消耗即上限,账本随池重生)
|
# 三选一停点态归零(冒险开始/结算收束共用;令牌池按表配额重建——池消耗即上限,账本随池重生)
|
||||||
func _reset_bonus_state() -> void:
|
func _reset_curio_state() -> void:
|
||||||
_bonus_taken = {"attack": 0, "max_hp": 0, "armor": 0, "gp": 0, "ap": 0, "heal": 0}
|
_curio_taken = {"attack": 0, "max_hp": 0, "armor": 0, "gp": 0, "ap": 0, "heal": 0}
|
||||||
_bonus_deck = _build_bonus_deck()
|
_curio_deck = _build_curio_deck()
|
||||||
_relics = []
|
_feats = []
|
||||||
_bonus_pending = {}
|
_curio_pending = {}
|
||||||
_bonus_result = {}
|
_curio_result = {}
|
||||||
_bonus_treasure_carry = []
|
_bonus_treasure_carry = []
|
||||||
_keystone_drops = []
|
_keystone_drops = []
|
||||||
_node_bonus_pending = {}
|
_node_curio_pending = {}
|
||||||
|
|
||||||
|
|
||||||
# 令牌池构建(TP0 v2.2——按表 kind×tier 配额展开为持久多重集;构建序确定 = FEATURE_KINDS×TIERS 逐层,
|
# 令牌池构建(TP0 v2.2——按表 kind×tier 配额展开为持久多重集;构建序确定 = CURIO_KINDS×TIERS 逐层,
|
||||||
# 随机性只发生在洗牌消费 reward_pick 封闭流的时刻。迭代 12 T1:规则改写型特性带=relic 活行每件 1 张追加
|
# 随机性只发生在洗牌消费 reward_pick 封闭流的时刻。迭代 12 T1:规则改写型特性带=relic 活行每件 1 张追加
|
||||||
# (rarity common/rare→稀有令牌档、rarest→罕见令牌档——relic 永不入普通档;空表=零追加零扰动))
|
# (rarity common/rare→稀有令牌档、rarest→罕见令牌档——relic 永不入普通档;空表=零追加零扰动))
|
||||||
func _build_bonus_deck() -> Array:
|
func _build_curio_deck() -> Array:
|
||||||
var deck: Array = []
|
var deck: Array = []
|
||||||
for kind in db.adventure.FEATURE_KINDS:
|
for kind in db.adventure.CURIO_KINDS:
|
||||||
for tier in db.adventure.TIERS:
|
for tier in db.adventure.TIERS:
|
||||||
for i in int(db.adventure.feature_quota(String(kind), String(tier))):
|
for i in int(db.adventure.curio_quota(String(kind), String(tier))):
|
||||||
deck.append({"kind": String(kind), "tier": String(tier)})
|
deck.append({"kind": String(kind), "tier": String(tier)})
|
||||||
for row in db.relic_rows():
|
for row in db.feat_rows():
|
||||||
var rtier := "rarest" if String(row.get("rarity", "")) == "rarest" else "rare"
|
var rtier := "rarest" if String(row.get("rarity", "")) == "rarest" else "rare"
|
||||||
deck.append({"kind": "relic", "tier": rtier, "relic_id": String(row.get("relic_id", ""))})
|
deck.append({"kind": "relic", "tier": rtier, "feat_id": String(row.get("feat_id", ""))})
|
||||||
return deck
|
return deck
|
||||||
|
|
||||||
|
|
||||||
# 令牌唯一身份(抽签去重/兑付匹配共用——属性加成型特性=kind,规则改写型特性=relic_id;同档多张不同 relic 可同屏)
|
# 令牌唯一身份(抽签去重/兑付匹配共用——属性加成型特性=kind,规则改写型特性=feat_id;同档多张不同 relic 可同屏)
|
||||||
func _token_identity(tok: Dictionary) -> String:
|
func _token_identity(tok: Dictionary) -> String:
|
||||||
if String(tok.get("kind", "")) == "relic":
|
if String(tok.get("kind", "")) == "relic":
|
||||||
return "relic:" + String(tok.get("relic_id", ""))
|
return "relic:" + String(tok.get("feat_id", ""))
|
||||||
return String(tok.get("kind", ""))
|
return String(tok.get("kind", ""))
|
||||||
|
|
||||||
|
|
||||||
@@ -326,144 +335,151 @@ func continue_to_next_level() -> bool:
|
|||||||
if not _pending_next_level:
|
if not _pending_next_level:
|
||||||
push_error("[冒险结构] continue_to_next_level 拒绝——无待确认的关卡推进")
|
push_error("[冒险结构] continue_to_next_level 拒绝——无待确认的关卡推进")
|
||||||
return false
|
return false
|
||||||
if not _bonus_pending.is_empty():
|
if not _curio_pending.is_empty():
|
||||||
push_error("[冒险结构] continue_to_next_level 拒绝——三选一停点未兑付(零自动推进契约)")
|
push_error("[冒险结构] continue_to_next_level 拒绝——三选一停点未兑付(零自动推进契约)")
|
||||||
return false
|
return false
|
||||||
_pending_next_level = false
|
_pending_next_level = false
|
||||||
_bonus_result = {}
|
_curio_result = {}
|
||||||
level_index += 1
|
level_index += 1
|
||||||
return _start_current_level()
|
return _start_current_level()
|
||||||
|
|
||||||
|
|
||||||
# ---- 提升三选一(迭代 10 奖励重构批——选择停点零自动推进、重复兑付被拒;兑付恰一件、未选项零生效)----
|
# ---- 提升三选一(迭代 10 奖励重构批——选择停点零自动推进、重复兑付被拒;兑付恰一件、未选项零生效)----
|
||||||
|
|
||||||
# 候选令牌匹配(身份键恒等——属性加成型特性 kind+tier、规则改写型特性 relic_id;迭代 12 T1 规则改写型特性带)
|
# 候选令牌匹配(身份键恒等——属性加成型特性 kind+tier、规则改写型特性 feat_id;迭代 12 T1 规则改写型特性带)
|
||||||
func _find_token(candidates: Array, kind: String, tier: String, relic_id: String = "") -> Dictionary:
|
func _find_token(candidates: Array, kind: String, tier: String, feat_id: String = "") -> Dictionary:
|
||||||
for t in candidates:
|
for t in candidates:
|
||||||
var tok: Dictionary = t as Dictionary
|
var tok: Dictionary = t as Dictionary
|
||||||
if String(tok.get("kind", "")) != kind or String(tok.get("tier", "")) != tier:
|
if String(tok.get("kind", "")) != kind or String(tok.get("tier", "")) != tier:
|
||||||
continue
|
continue
|
||||||
if kind == "relic" and String(tok.get("relic_id", "")) != relic_id:
|
if kind == "relic" and String(tok.get("feat_id", "")) != feat_id:
|
||||||
continue
|
continue
|
||||||
return tok
|
return tok
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
# 玩家点选兑付(呈现层/冒烟/自动模拟确认入口):令牌 ∈ 停点候选(身份键精确匹配——relic 件
|
# 玩家点选兑付(呈现层/冒烟/自动模拟确认入口):令牌 ∈ 停点候选(身份键精确匹配——relic 件
|
||||||
# 另须 relic_id)。属性件写加算区、AP 件写第四轴、GP 件走钱包「奖励入账」入口、恢复件当即回血、
|
# 另须 feat_id)。属性件写加算区、AP 件写第四轴、GP 件走钱包「奖励入账」入口、恢复件当即回血、
|
||||||
# relic 件入特性持有集(🔴 层级纪律:只走局内随机渠道)、道具件受同调位约束(随池种退役不可达)。
|
# relic 件入特性持有集(🔴 层级纪律:只走局内随机渠道)、道具件受同调位约束(随池种退役不可达)。
|
||||||
# 消费=选中令牌踢池(未选回池由「抽签不摘牌、兑付才摘牌」语义承载——TP0 v2.2)。
|
# 消费=选中令牌踢池(未选回池由「抽签不摘牌、兑付才摘牌」语义承载——TP0 v2.2)。
|
||||||
func pick_bonus(kind: String, tier: String = "", relic_id: String = "") -> bool:
|
func pick_curio(kind: String, tier: String = "", feat_id: String = "") -> bool:
|
||||||
if state != "running" or _bonus_pending.is_empty():
|
if state != "running" or _curio_pending.is_empty():
|
||||||
push_error("[冒险结构] pick_bonus 拒绝——无待选停点(state=%s)" % state)
|
push_error("[冒险结构] pick_curio 拒绝——无待选停点(state=%s)" % state)
|
||||||
return false
|
return false
|
||||||
var candidates: Array = _bonus_pending.get("candidates", []) as Array
|
var candidates: Array = _curio_pending.get("candidates", []) as Array
|
||||||
if _find_token(candidates, kind, tier, relic_id).is_empty():
|
if _find_token(candidates, kind, tier, feat_id).is_empty():
|
||||||
push_error("[冒险结构] pick_bonus 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id])
|
push_error("[冒险结构] pick_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, feat_id])
|
||||||
return false
|
return false
|
||||||
var value: int = int(db.adventure.feature_value(kind, tier))
|
if kind == "feat_ticket":
|
||||||
|
return _redeem_feat_ticket() # T5 拿到即开——停点候选替换为专长三选一(零摘牌/不进持有集)
|
||||||
|
var value: int = int(db.adventure.curio_value(kind, tier))
|
||||||
var hp_before := int(ledger.get("hero_hp", 0))
|
var hp_before := int(ledger.get("hero_hp", 0))
|
||||||
if kind == "treasure_item":
|
if kind == "treasure_item":
|
||||||
return _pick_treasure_item(value) # 道具件分派通关渠道(持有容器 = 跨关携带——与节点渠道分轨;池种退役后不可达)
|
return _pick_treasure_item(value) # 道具件分派通关渠道(持有容器 = 跨关携带——与节点渠道分轨;池种退役后不可达)
|
||||||
if not _apply_bonus_pick(kind, tier, value, relic_id):
|
if not _apply_curio_pick(kind, tier, value, feat_id):
|
||||||
push_error("[冒险结构] pick_bonus 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id])
|
push_error("[冒险结构] pick_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, feat_id])
|
||||||
return false
|
return false
|
||||||
_finish_bonus_pick(kind, value, hp_before, tier, relic_id)
|
_finish_curio_pick(kind, value, hp_before, tier, feat_id)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
# 兑付载荷 relic 伴随键(迭代 12 T1——反馈行取数面;非 relic 件零新增键)
|
# 兑付载荷 relic 伴随键(迭代 12 T1——反馈行取数面;非 relic 件零新增键)
|
||||||
func _relic_payload_keys(relic_id: String) -> Dictionary:
|
func _feat_payload_keys(feat_id: String) -> Dictionary:
|
||||||
if relic_id == "":
|
if feat_id == "":
|
||||||
return {}
|
return {}
|
||||||
var row: Dictionary = db.relic_row(relic_id)
|
var row: Dictionary = db.feat_row(feat_id)
|
||||||
return {"relic_id": relic_id, "relic_name": String(row.get("display_name", "")), "relic_archetype": String(row.get("archetype", ""))}
|
return {"feat_id": feat_id, "feat_name": String(row.get("display_name", "")), "feat_archetype": String(row.get("archetype", ""))}
|
||||||
|
|
||||||
|
|
||||||
# 三选一件兑付共享体(2026-10-02 修复批双胞胎收口;TP0 改令牌消费——摘牌单点;迭代 12 T1 relic 分支):
|
# 三选一件兑付共享体(2026-10-02 修复批双胞胎收口;TP0 改令牌消费——摘牌单点;迭代 12 T1 relic 分支):
|
||||||
# 属性件写加算区 + 件获取计数、AP 件写第四轴、GP 件走钱包、恢复件当即回血(封顶生效上限——
|
# 属性件写加算区 + 件获取计数、AP 件写第四轴、GP 件走钱包、恢复件当即回血(封顶生效上限——
|
||||||
# 与账本置满/通关回血同式 effective_max_hp)、relic 件入持有集(散件单体保底——效果接线面
|
# 与账本置满/通关回血同式 effective_max_hp)、relic 件入持有集(散件单体保底——效果接线面
|
||||||
# 按挂点消费,档位质变由 RelicsEngine 求值)。兑付即从 _bonus_deck 摘除该令牌(选中踢池;
|
# 按挂点消费,档位质变由 FeatsEngine 求值)。兑付即从 _curio_deck 摘除该令牌(选中踢池;
|
||||||
# 未选候选从未摘牌=自动回池)。道具件不入此体——未知件返回 false 由调用方按渠道名报错。
|
# 未选候选从未摘牌=自动回池)。道具件不入此体——未知件返回 false 由调用方按渠道名报错。
|
||||||
func _apply_bonus_pick(kind: String, tier: String, value: int, relic_id: String = "") -> bool:
|
func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String = "") -> bool:
|
||||||
if kind == "relic" and db.relic_row(relic_id).is_empty():
|
if kind == "refresh":
|
||||||
push_error("[冒险结构] relic 兑付失败——表无此件: %s" % relic_id)
|
_attune_refresh_x += value # T6——刷新次数件兑付=X 余量增量(作用三选一界面非角色,不进加算区——registry refresh_boundary)
|
||||||
|
return true
|
||||||
|
if kind == "relic" and db.feat_row(feat_id).is_empty():
|
||||||
|
push_error("[冒险结构] relic 兑付失败——表无此件: %s" % feat_id)
|
||||||
return false # 先校验后摘牌(令牌零丢失)
|
return false # 先校验后摘牌(令牌零丢失)
|
||||||
if not _consume_deck_token(kind, tier, relic_id):
|
if not _consume_deck_token(kind, tier, feat_id):
|
||||||
push_error("[冒险结构] 令牌消费失败——牌库无此令牌: %s|%s|%s" % [kind, tier, relic_id])
|
push_error("[冒险结构] 令牌消费失败——牌库无此令牌: %s|%s|%s" % [kind, tier, feat_id])
|
||||||
return false
|
return false
|
||||||
if kind == "relic":
|
if kind == "relic":
|
||||||
if not _relics.has(relic_id):
|
if not _feats.has(feat_id):
|
||||||
_relics.append(relic_id)
|
_feats.append(feat_id)
|
||||||
_bonus_taken["relic"] = int(_bonus_taken.get("relic", 0)) + 1
|
_curio_taken["relic"] = int(_curio_taken.get("relic", 0)) + 1
|
||||||
return true
|
return true
|
||||||
match kind:
|
match kind:
|
||||||
"attack":
|
"attack":
|
||||||
bonus["atk"] = int(bonus.get("atk", 0)) + value
|
bonus["atk"] = int(bonus.get("atk", 0)) + value
|
||||||
_bonus_taken["attack"] = int(_bonus_taken.get("attack", 0)) + 1
|
_curio_taken["attack"] = int(_curio_taken.get("attack", 0)) + 1
|
||||||
"max_hp":
|
"max_hp":
|
||||||
bonus["max_hp"] = int(bonus.get("max_hp", 0)) + value
|
bonus["max_hp"] = int(bonus.get("max_hp", 0)) + value
|
||||||
_bonus_taken["max_hp"] = int(_bonus_taken.get("max_hp", 0)) + 1
|
_curio_taken["max_hp"] = int(_curio_taken.get("max_hp", 0)) + 1
|
||||||
"armor":
|
"armor":
|
||||||
bonus["armor_class"] = int(bonus.get("armor_class", 0)) + value
|
bonus["armor_class"] = int(bonus.get("armor_class", 0)) + value
|
||||||
_bonus_taken["armor"] = int(_bonus_taken.get("armor", 0)) + 1
|
_curio_taken["armor"] = int(_curio_taken.get("armor", 0)) + 1
|
||||||
"ap":
|
"ap":
|
||||||
bonus["ap"] = int(bonus.get("ap", 0)) + value
|
bonus["ap"] = int(bonus.get("ap", 0)) + value
|
||||||
_bonus_taken["ap"] = int(_bonus_taken.get("ap", 0)) + 1
|
_curio_taken["ap"] = int(_curio_taken.get("ap", 0)) + 1
|
||||||
"gp":
|
"gp":
|
||||||
if _runner != null:
|
if _runner != null:
|
||||||
_runner.grant_bonus_gp(value)
|
_runner.grant_curio_gp(value)
|
||||||
_bonus_taken["gp"] = int(_bonus_taken.get("gp", 0)) + 1
|
_curio_taken["gp"] = int(_curio_taken.get("gp", 0)) + 1
|
||||||
"heal":
|
"heal":
|
||||||
ledger["hero_hp"] = mini(int(ledger.get("hero_hp", 0)) + value, effective_max_hp())
|
ledger["hero_hp"] = mini(int(ledger.get("hero_hp", 0)) + value, effective_max_hp())
|
||||||
_bonus_taken["heal"] = int(_bonus_taken.get("heal", 0)) + 1
|
_curio_taken["heal"] = int(_curio_taken.get("heal", 0)) + 1
|
||||||
_:
|
_:
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
# 从牌库摘除一枚令牌(TP0——选中踢池单点;迭代 12 T1 relic 按 relic_id 精确摘;找不到 = false)
|
# 从牌库摘除一枚令牌(TP0——选中踢池单点;迭代 12 T1 relic 按 feat_id 精确摘;找不到 = false)
|
||||||
func _consume_deck_token(kind: String, tier: String, relic_id: String = "") -> bool:
|
func _consume_deck_token(kind: String, tier: String, feat_id: String = "") -> bool:
|
||||||
for i in _bonus_deck.size():
|
for i in _curio_deck.size():
|
||||||
var tok: Dictionary = _bonus_deck[i] as Dictionary
|
var tok: Dictionary = _curio_deck[i] as Dictionary
|
||||||
if String(tok.get("kind", "")) != kind or String(tok.get("tier", "")) != tier:
|
if String(tok.get("kind", "")) != kind or String(tok.get("tier", "")) != tier:
|
||||||
continue
|
continue
|
||||||
if kind == "relic" and String(tok.get("relic_id", "")) != relic_id:
|
if kind == "relic" and String(tok.get("feat_id", "")) != feat_id:
|
||||||
continue
|
continue
|
||||||
_bonus_deck.remove_at(i)
|
_curio_deck.remove_at(i)
|
||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
# 特性持有集读面(效果接线/求值/摘要唯一取数口——副本防外部改写)
|
# 特性持有集读面(效果接线/求值/摘要唯一取数口——副本防外部改写)
|
||||||
func relics_held() -> Array:
|
func feats_held() -> Array:
|
||||||
return _relics.duplicate()
|
return _feats.duplicate()
|
||||||
|
|
||||||
|
|
||||||
# ---- 商店共享池消费面(迭代 11 第②批 T3——P1 共享池单点账本;TP0 令牌池换代——库存=牌库剩余令牌)----
|
# ---- 商店共享池消费面(迭代 11 第②批 T3——P1 共享池单点账本;TP0 令牌池换代——库存=牌库剩余令牌)----
|
||||||
|
|
||||||
# 库存余量(TP0:stock = 牌库内该 kind 剩余令牌数——「池消耗即上限」,种类次数上限退役;
|
# 库存余量(TP0:stock = 牌库内该 kind 剩余令牌数——「池消耗即上限」,种类次数上限退役;
|
||||||
# 奖励三选一兑付摘牌 与 商店购买消费 由同一 _bonus_deck 承载——两渠道跨关共库,全冒险一次性)。
|
# 奖励三选一兑付摘牌 与 商店购买消费 由同一 _curio_deck 承载——两渠道跨关共库,全冒险一次性)。
|
||||||
func pool_stock(kind: String) -> int:
|
func pool_stock(kind: String) -> int:
|
||||||
var n := 0
|
var n := 0
|
||||||
for t in _bonus_deck:
|
for t in _curio_deck:
|
||||||
if String((t as Dictionary).get("kind", "")) == kind:
|
if String((t as Dictionary).get("kind", "")) == kind:
|
||||||
n += 1
|
n += 1
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
||||||
# 商店购买消费 handler(LevelRunner.buy_pool_item 经 Callable 调用——ADR-0001 S6 方法引用):
|
# 商店购买消费 handler(LevelRunner.buy_pool_item 经 Callable 调用——ADR-0001 S6 方法引用):
|
||||||
# 消费在架令牌本体(kind+tier 精确——货架卡即令牌,TP0 令牌级货架)→ _apply_bonus_pick 同口
|
# 消费在架令牌本体(kind+tier 精确——货架卡即令牌,TP0 令牌级货架)→ _apply_curio_pick 同口
|
||||||
# 写加算区+账本+摘牌(与奖励三选一真单点——买一件=奖励面少一件的库存语义由同库成立)。
|
# 写加算区+账本+摘牌(与奖励三选一真单点——买一件=奖励面少一件的库存语义由同库成立)。
|
||||||
# 未知 kind 拒绝(货架词表封闭的前置防御)。
|
# 未知 kind 拒绝(货架词表封闭的前置防御)。
|
||||||
func _consume_pool_purchase(kind: String, tier: String) -> bool:
|
func _consume_pool_purchase(kind: String, tier: String) -> bool:
|
||||||
if not db.adventure.FEATURE_KINDS.has(kind):
|
if kind == "feat_ticket":
|
||||||
|
return _redeem_feat_ticket() # T5——门票买下即开(零摘牌不进持有集;临时停点开专长三选一面)
|
||||||
|
if not db.adventure.CURIO_KINDS.has(kind):
|
||||||
push_error("[冒险结构] 商店购买消费拒绝——件不在池词表: %s" % kind)
|
push_error("[冒险结构] 商店购买消费拒绝——件不在池词表: %s" % kind)
|
||||||
return false
|
return false
|
||||||
return _apply_bonus_pick(kind, tier, int(db.adventure.feature_value(kind, tier)))
|
return _apply_curio_pick(kind, tier, int(db.adventure.curio_value(kind, tier)))
|
||||||
|
|
||||||
|
|
||||||
# 终局池快照(_settle 清账前调用——_reset_bonus_state 会重置 _bonus_taken,摘要 additive 键
|
# 终局池快照(_settle 清账前调用——_reset_curio_state 会重置 _curio_taken,摘要 additive 键
|
||||||
# pool_stock 须在此前取数;货架四种 stock=cap−taken,读数面 = 商店买走 vs 奖励领走 vs 枯竭)
|
# pool_stock 须在此前取数;货架四种 stock=cap−taken,读数面 = 商店买走 vs 奖励领走 vs 枯竭)
|
||||||
func _pool_stock_snapshot() -> Dictionary:
|
func _pool_stock_snapshot() -> Dictionary:
|
||||||
var out := {}
|
var out := {}
|
||||||
@@ -474,30 +490,30 @@ func _pool_stock_snapshot() -> Dictionary:
|
|||||||
|
|
||||||
|
|
||||||
# 道具件兑付(同调位约束):有位直接入跨关携带;满位转替换子交互(incoming 暂存、等 resolve_treasure_replace)。
|
# 道具件兑付(同调位约束):有位直接入跨关携带;满位转替换子交互(incoming 暂存、等 resolve_treasure_replace)。
|
||||||
# 掷定前置(追加批二):具体道具随候选抽定(_last_draw_treasure / _bonus_pending.aux_detail)——本函数消费预掷值,
|
# 掷定前置(追加批二):具体道具随候选抽定(_last_draw_treasure / _curio_pending.aux_detail)——本函数消费预掷值,
|
||||||
# 断点续玩/无预掷值时回退当场掷定(流序不同但消费数不变,确定性由调用序保证)。
|
# 断点续玩/无预掷值时回退当场掷定(流序不同但消费数不变,确定性由调用序保证)。
|
||||||
# 持有容器 = 跨关携带 _bonus_treasure_carry(与节点渠道的关卡域库存分轨——容器差异保守不合,注记在案)。
|
# 持有容器 = 跨关携带 _bonus_treasure_carry(与节点渠道的关卡域库存分轨——容器差异保守不合,注记在案)。
|
||||||
func _pick_treasure_item(count: int) -> bool:
|
func _pick_treasure_item(count: int) -> bool:
|
||||||
var picked: Array = _draw_pending_treasure(_bonus_pending, count, true)
|
var picked: Array = _draw_pending_treasure(_curio_pending, count, true)
|
||||||
if picked.is_empty():
|
if picked.is_empty():
|
||||||
push_error("[冒险结构] pick_bonus(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)")
|
push_error("[冒险结构] pick_curio(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)")
|
||||||
return false
|
return false
|
||||||
var hp_before := int(ledger.get("hero_hp", 0))
|
var hp_before := int(ledger.get("hero_hp", 0))
|
||||||
# 迭代 11 第①批 T7——同调位守卫随过渡键退役拆除(aux 卡出池后本路径不可达;能力件容量闸
|
# 迭代 11 第①批 T7——同调位守卫随过渡键退役拆除(aux 卡出池后本路径不可达;能力件容量闸
|
||||||
# 正主 = 营地装配面 Attunement.slots 满位禁选)
|
# 正主 = 营地装配面 Attunement.slots 满位禁选)
|
||||||
for iid in picked:
|
for iid in picked:
|
||||||
_bonus_treasure_carry.append(String(iid))
|
_bonus_treasure_carry.append(String(iid))
|
||||||
_finish_bonus_pick("treasure_item", picked.size(), hp_before)
|
_finish_curio_pick("treasure_item", picked.size(), hp_before)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
# 替换子交互受理(discard_index = -1 放弃新件不获取;否则丢该件腾位收新)——终态恒 ≤ 同调位
|
# 替换子交互受理(discard_index = -1 放弃新件不获取;否则丢该件腾位收新)——终态恒 ≤ 同调位
|
||||||
# 持有容器 = 跨关携带(序号寻址);与节点渠道(库存句柄寻址)的丢弃/授予/收尾三步保守不合。
|
# 持有容器 = 跨关携带(序号寻址);与节点渠道(库存句柄寻址)的丢弃/授予/收尾三步保守不合。
|
||||||
func resolve_treasure_replace(discard_index: int) -> bool:
|
func resolve_treasure_replace(discard_index: int) -> bool:
|
||||||
if not _replace_in_transit(_bonus_pending):
|
if not _replace_in_transit(_curio_pending):
|
||||||
push_error("[冒险结构] resolve_treasure_replace 拒绝——无替换在途")
|
push_error("[冒险结构] resolve_treasure_replace 拒绝——无替换在途")
|
||||||
return false
|
return false
|
||||||
var incoming: Array = _bonus_pending.get("incoming", []) as Array
|
var incoming: Array = _curio_pending.get("incoming", []) as Array
|
||||||
var hp_before := int(ledger.get("hero_hp", 0))
|
var hp_before := int(ledger.get("hero_hp", 0))
|
||||||
if discard_index >= 0:
|
if discard_index >= 0:
|
||||||
if discard_index >= _bonus_treasure_carry.size():
|
if discard_index >= _bonus_treasure_carry.size():
|
||||||
@@ -507,15 +523,15 @@ func resolve_treasure_replace(discard_index: int) -> bool:
|
|||||||
for iid in incoming:
|
for iid in incoming:
|
||||||
_bonus_treasure_carry.append(String(iid))
|
_bonus_treasure_carry.append(String(iid))
|
||||||
# 放弃新件(discard_index < 0)= 原持有集不动、新件不获取
|
# 放弃新件(discard_index < 0)= 原持有集不动、新件不获取
|
||||||
_finish_bonus_pick("treasure_item", incoming.size(), hp_before)
|
_finish_curio_pick("treasure_item", incoming.size(), hp_before)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
# 兑付收口(恰一次):清停点态 → 记已兑付态 → emit → 停点快照(等继续确认——中断续玩不重兑付)。
|
# 兑付收口(恰一次):清停点态 → 记已兑付态 → emit → 停点快照(等继续确认——中断续玩不重兑付)。
|
||||||
# picked = kind;tier 随载荷(TP0——大小档位读数面);relic 伴随键随载荷(迭代 12 T1——反馈行取数)
|
# picked = kind;tier 随载荷(TP0——大小档位读数面);relic 伴随键随载荷(迭代 12 T1——反馈行取数)
|
||||||
func _finish_bonus_pick(picked: String, value: int, hp_before: int, tier: String = "", relic_id: String = "") -> void:
|
func _finish_curio_pick(picked: String, value: int, hp_before: int, tier: String = "", feat_id: String = "") -> void:
|
||||||
_bonus_pending = {}
|
_curio_pending = {}
|
||||||
_bonus_result = {
|
_curio_result = {
|
||||||
"picked": picked,
|
"picked": picked,
|
||||||
"tier": tier,
|
"tier": tier,
|
||||||
"value": value,
|
"value": value,
|
||||||
@@ -523,8 +539,8 @@ func _finish_bonus_pick(picked: String, value: int, hp_before: int, tier: String
|
|||||||
"hp_after": int(ledger.get("hero_hp", 0)),
|
"hp_after": int(ledger.get("hero_hp", 0)),
|
||||||
"level_index": level_index,
|
"level_index": level_index,
|
||||||
}
|
}
|
||||||
_bonus_result.merge(_relic_payload_keys(relic_id))
|
_curio_result.merge(_feat_payload_keys(feat_id))
|
||||||
bonus_resolved.emit(_bonus_result.duplicate(true))
|
curio_resolved.emit(_curio_result.duplicate(true))
|
||||||
_snapshot_breakpoint()
|
_snapshot_breakpoint()
|
||||||
|
|
||||||
|
|
||||||
@@ -533,47 +549,49 @@ func _finish_bonus_pick(picked: String, value: int, hp_before: int, tier: String
|
|||||||
# 候选呈现(节点到达时调用——恰一 reward_pick scope 洗牌 + 道具件掷定 scope;本停点断点不载,
|
# 候选呈现(节点到达时调用——恰一 reward_pick scope 洗牌 + 道具件掷定 scope;本停点断点不载,
|
||||||
# 续玩重建到选路停点后重走重抽,恢复的全局计数器保证同候选逐位复现)。
|
# 续玩重建到选路停点后重走重抽,恢复的全局计数器保证同候选逐位复现)。
|
||||||
# 掷定前置(追加批二):返回 dict 带 aux_detail(道具件具体道具)供卡片直显。
|
# 掷定前置(追加批二):返回 dict 带 aux_detail(道具件具体道具)供卡片直显。
|
||||||
func node_bonus_candidates() -> Dictionary:
|
func node_curio_candidates() -> Dictionary:
|
||||||
var cands: Array = _draw_bonus_candidates()
|
var cands: Array = _draw_curio_candidates("reward_node")
|
||||||
return {"candidates": cands, "aux_detail": _last_draw_treasure.duplicate()}
|
return {"candidates": cands, "aux_detail": _last_draw_treasure.duplicate()}
|
||||||
|
|
||||||
|
|
||||||
func present_node_bonus(candidates: Array, treasure_detail: Array = []) -> void:
|
func present_node_curio(candidates: Array, treasure_detail: Array = []) -> void:
|
||||||
_reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转)
|
_reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转)
|
||||||
_node_bonus_pending = {"candidates": candidates.duplicate(true), "aux_detail": treasure_detail.duplicate(true)}
|
_node_curio_pending = {"candidates": candidates.duplicate(true), "aux_detail": treasure_detail.duplicate(true)}
|
||||||
|
|
||||||
|
|
||||||
# 点选兑付(节点域——无部分回血无钥石;令牌消费与牌库摘牌与通关三选一同库;relic 同权入持有集)
|
# 点选兑付(节点域——无部分回血无钥石;令牌消费与牌库摘牌与通关三选一同库;relic 同权入持有集)
|
||||||
func apply_node_bonus(kind: String, tier: String = "", relic_id: String = "") -> bool:
|
func apply_node_curio(kind: String, tier: String = "", feat_id: String = "") -> bool:
|
||||||
if _node_bonus_pending.is_empty():
|
if kind == "feat_ticket":
|
||||||
push_error("[冒险结构] apply_node_bonus 拒绝——无待选停点")
|
return _redeem_feat_ticket() # T5 拿到即开——节点停点候选替换为专长三选一(呈现层本地重绘)
|
||||||
|
if _node_curio_pending.is_empty():
|
||||||
|
push_error("[冒险结构] apply_node_curio 拒绝——无待选停点")
|
||||||
return false
|
return false
|
||||||
var candidates: Array = _node_bonus_pending.get("candidates", []) as Array
|
var candidates: Array = _node_curio_pending.get("candidates", []) as Array
|
||||||
if _find_token(candidates, kind, tier, relic_id).is_empty():
|
if _find_token(candidates, kind, tier, feat_id).is_empty():
|
||||||
push_error("[冒险结构] apply_node_bonus 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, relic_id])
|
push_error("[冒险结构] apply_node_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, feat_id])
|
||||||
return false
|
return false
|
||||||
var value: int = int(db.adventure.feature_value(kind, tier))
|
var value: int = int(db.adventure.curio_value(kind, tier))
|
||||||
if kind == "treasure_item":
|
if kind == "treasure_item":
|
||||||
return _apply_node_treasure(value) # 道具件分派节点渠道(持有容器 = 关卡域库存——与通关渠道分轨;池种退役后不可达)
|
return _apply_node_treasure(value) # 道具件分派节点渠道(持有容器 = 关卡域库存——与通关渠道分轨;池种退役后不可达)
|
||||||
if not _apply_bonus_pick(kind, tier, value, relic_id):
|
if not _apply_curio_pick(kind, tier, value, feat_id):
|
||||||
push_error("[冒险结构] apply_node_bonus 拒绝——未知件: %s|%s|%s" % [kind, tier, relic_id])
|
push_error("[冒险结构] apply_node_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, feat_id])
|
||||||
return false
|
return false
|
||||||
_finish_node_bonus(kind, value, tier, relic_id)
|
_finish_node_bonus(kind, value, tier, feat_id)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
# 节点渠道道具件(同调位约束):持有=关卡域库存+跨关携带,有位直接入本关库存(当关可用);
|
# 节点渠道道具件(同调位约束):持有=关卡域库存+跨关携带,有位直接入本关库存(当关可用);
|
||||||
# 满位转替换子交互(丢弃目标=库存件 handle——与通关渠道的 carry 序号分轨,容器语义保守不合)。
|
# 满位转替换子交互(丢弃目标=库存件 handle——与通关渠道的 carry 序号分轨,容器语义保守不合)。
|
||||||
# 掷定前置:消费 _node_bonus_pending.aux_detail 预掷值(缺省回退当场掷定)。
|
# 掷定前置:消费 _node_curio_pending.aux_detail 预掷值(缺省回退当场掷定)。
|
||||||
func _apply_node_treasure(count: int) -> bool:
|
func _apply_node_treasure(count: int) -> bool:
|
||||||
var picked: Array = _draw_pending_treasure(_node_bonus_pending, count, false)
|
var picked: Array = _draw_pending_treasure(_node_curio_pending, count, false)
|
||||||
if picked.is_empty():
|
if picked.is_empty():
|
||||||
push_error("[冒险结构] apply_node_bonus(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)")
|
push_error("[冒险结构] apply_node_curio(treasure_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)")
|
||||||
return false
|
return false
|
||||||
var records: Array = []
|
var records: Array = []
|
||||||
if _runner != null and _runner.treasure_inventory != null:
|
if _runner != null and _runner.treasure_inventory != null:
|
||||||
records = _runner.treasure_inventory.holding_records()
|
records = _runner.treasure_inventory.holding_records()
|
||||||
# 迭代 11 第①批 T7——守卫随过渡键退役拆除(FEATURE_KINDS 已除名 treasure_item,本分支不可达;
|
# 迭代 11 第①批 T7——守卫随过渡键退役拆除(CURIO_KINDS 已除名 treasure_item,本分支不可达;
|
||||||
# 替换子交互路由随容量闸一并退役——能力件容量闸正主 = 营地装配面满位禁选)
|
# 替换子交互路由随容量闸一并退役——能力件容量闸正主 = 营地装配面满位禁选)
|
||||||
if _runner != null:
|
if _runner != null:
|
||||||
for iid in picked:
|
for iid in picked:
|
||||||
@@ -585,10 +603,10 @@ func _apply_node_treasure(count: int) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
func _resolve_node_replace(discard_handle: int) -> bool:
|
func _resolve_node_replace(discard_handle: int) -> bool:
|
||||||
if not _replace_in_transit(_node_bonus_pending):
|
if not _replace_in_transit(_node_curio_pending):
|
||||||
push_error("[冒险结构] resolve_node_replace 拒绝——无替换在途")
|
push_error("[冒险结构] resolve_node_replace 拒绝——无替换在途")
|
||||||
return false
|
return false
|
||||||
var incoming: Array = _node_bonus_pending.get("incoming", []) as Array
|
var incoming: Array = _node_curio_pending.get("incoming", []) as Array
|
||||||
if discard_handle >= 0:
|
if discard_handle >= 0:
|
||||||
if _runner == null or _runner.treasure_inventory == null or not _runner.treasure_inventory.discard_item(discard_handle):
|
if _runner == null or _runner.treasure_inventory == null or not _runner.treasure_inventory.discard_item(discard_handle):
|
||||||
push_error("[冒险结构] resolve_node_replace 拒绝——句柄不在持有集: %d" % discard_handle)
|
push_error("[冒险结构] resolve_node_replace 拒绝——句柄不在持有集: %d" % discard_handle)
|
||||||
@@ -629,40 +647,40 @@ func attune_refresh_left() -> int:
|
|||||||
|
|
||||||
|
|
||||||
# 节点停点在途态读回(组合根重 present 用——candidates/aux_detail 值拷贝)
|
# 节点停点在途态读回(组合根重 present 用——candidates/aux_detail 值拷贝)
|
||||||
func node_bonus_pending_state() -> Dictionary:
|
func node_curio_pending_state() -> Dictionary:
|
||||||
return {
|
return {
|
||||||
"candidates": (_node_bonus_pending.get("candidates", []) as Array).duplicate(true),
|
"candidates": (_node_curio_pending.get("candidates", []) as Array).duplicate(true),
|
||||||
"aux_detail": (_node_bonus_pending.get("aux_detail", []) as Array).duplicate(true),
|
"aux_detail": (_node_curio_pending.get("aux_detail", []) as Array).duplicate(true),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# 三选一刷新(组合根唯一调用口;无停点/无余量 false 零副作用)。通关停点重抽后重发
|
# 三选一刷新(组合根唯一调用口;无停点/无余量 false 零副作用)。通关停点重抽后重发
|
||||||
# bonus_choice_presented(呈现层重建卡片+刷新余量);节点停点由组合根读 node_bonus_pending_state 重 present。
|
# curio_choice_presented(呈现层重建卡片+刷新余量);节点停点由组合根读 node_curio_pending_state 重 present。
|
||||||
func refresh_bonus() -> bool:
|
func refresh_curio() -> bool:
|
||||||
if _attune_refresh_x <= 0 and _attune_refresh_y <= 0:
|
if _attune_refresh_x <= 0 and _attune_refresh_y <= 0:
|
||||||
return false
|
return false
|
||||||
if not _bonus_pending.is_empty():
|
if not _curio_pending.is_empty():
|
||||||
if _attune_refresh_x > 0:
|
if _attune_refresh_x > 0:
|
||||||
_attune_refresh_x -= 1
|
_attune_refresh_x -= 1
|
||||||
else:
|
else:
|
||||||
_attune_refresh_y -= 1
|
_attune_refresh_y -= 1
|
||||||
var cands: Array = _draw_bonus_candidates()
|
var cands: Array = _draw_curio_candidates("level_clear")
|
||||||
_bonus_pending["candidates"] = cands.duplicate(true)
|
_curio_pending["candidates"] = cands.duplicate(true)
|
||||||
_bonus_pending["aux_detail"] = _last_draw_treasure.duplicate()
|
_curio_pending["aux_detail"] = _last_draw_treasure.duplicate()
|
||||||
var payload: Dictionary = (_bonus_pending.get("payload", {}) as Dictionary).duplicate(true)
|
var payload: Dictionary = (_curio_pending.get("payload", {}) as Dictionary).duplicate(true)
|
||||||
payload["candidates"] = cands
|
payload["candidates"] = cands
|
||||||
payload["aux_detail"] = _last_draw_treasure.duplicate()
|
payload["aux_detail"] = _last_draw_treasure.duplicate()
|
||||||
bonus_choice_presented.emit(payload) # 同信号重 present(零自动推进契约不破——停点仍在途)
|
curio_choice_presented.emit(payload) # 同信号重 present(零自动推进契约不破——停点仍在途)
|
||||||
_snapshot_breakpoint()
|
_snapshot_breakpoint()
|
||||||
return true
|
return true
|
||||||
if not _node_bonus_pending.is_empty():
|
if not _node_curio_pending.is_empty():
|
||||||
if _attune_refresh_x > 0:
|
if _attune_refresh_x > 0:
|
||||||
_attune_refresh_x -= 1
|
_attune_refresh_x -= 1
|
||||||
else:
|
else:
|
||||||
_attune_refresh_y -= 1
|
_attune_refresh_y -= 1
|
||||||
var cands2: Array = _draw_bonus_candidates()
|
var cands2: Array = _draw_curio_candidates("reward_node")
|
||||||
_node_bonus_pending["candidates"] = cands2.duplicate(true)
|
_node_curio_pending["candidates"] = cands2.duplicate(true)
|
||||||
_node_bonus_pending["aux_detail"] = _last_draw_treasure.duplicate()
|
_node_curio_pending["aux_detail"] = _last_draw_treasure.duplicate()
|
||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -694,14 +712,14 @@ func _draw_pending_treasure(pending: Dictionary, count: int, allow_detail_fallba
|
|||||||
|
|
||||||
|
|
||||||
# 替换受理分发(单入口——节点停点在途优先按库存句柄,否则按通关停点的携带序号)
|
# 替换受理分发(单入口——节点停点在途优先按库存句柄,否则按通关停点的携带序号)
|
||||||
func resolve_bonus_replace(discard: int) -> bool:
|
func resolve_curio_replace(discard: int) -> bool:
|
||||||
if not _node_bonus_pending.is_empty() and _node_bonus_pending.has("incoming"):
|
if not _node_curio_pending.is_empty() and _node_curio_pending.has("incoming"):
|
||||||
return _resolve_node_replace(discard)
|
return _resolve_node_replace(discard)
|
||||||
return resolve_treasure_replace(discard)
|
return resolve_treasure_replace(discard)
|
||||||
|
|
||||||
|
|
||||||
func _finish_node_bonus(picked: String, value: int, tier: String = "", relic_id: String = "") -> void:
|
func _finish_node_bonus(picked: String, value: int, tier: String = "", feat_id: String = "") -> void:
|
||||||
_node_bonus_pending = {}
|
_node_curio_pending = {}
|
||||||
var payload := {
|
var payload := {
|
||||||
"context": "reward_node",
|
"context": "reward_node",
|
||||||
"picked": picked,
|
"picked": picked,
|
||||||
@@ -709,8 +727,8 @@ func _finish_node_bonus(picked: String, value: int, tier: String = "", relic_id:
|
|||||||
"value": value,
|
"value": value,
|
||||||
"level_index": level_index,
|
"level_index": level_index,
|
||||||
}
|
}
|
||||||
payload.merge(_relic_payload_keys(relic_id))
|
payload.merge(_feat_payload_keys(feat_id))
|
||||||
bonus_resolved.emit(payload)
|
curio_resolved.emit(payload)
|
||||||
|
|
||||||
|
|
||||||
# 令牌池洗牌不放回抽 3(TP0 v2.2——Fisher-Yates 全库洗牌后按序收集;**同 kind 去重每 kind 至多
|
# 令牌池洗牌不放回抽 3(TP0 v2.2——Fisher-Yates 全库洗牌后按序收集;**同 kind 去重每 kind 至多
|
||||||
@@ -719,9 +737,17 @@ func _finish_node_bonus(picked: String, value: int, tier: String = "", relic_id:
|
|||||||
# 自动回池;池耗即枯竭——余库不足 N kind 时出齐剩余,空库 = 零候选合法态)。候选 = 令牌字典
|
# 自动回池;池耗即枯竭——余库不足 N kind 时出齐剩余,空库 = 零候选合法态)。候选 = 令牌字典
|
||||||
# {"kind","tier"}。消费 reward_pick 封闭流——一次停点恰一 scope,同种子同候选;道具件掷定前置
|
# {"kind","tier"}。消费 reward_pick 封闭流——一次停点恰一 scope,同种子同候选;道具件掷定前置
|
||||||
# 随池种退役拆除(_last_draw_treasure 恒空集,机械惰性承载)。
|
# 随池种退役拆除(_last_draw_treasure 恒空集,机械惰性承载)。
|
||||||
func _draw_bonus_candidates() -> Array:
|
# 渠道白名单过滤(迭代 15 卡② T4——渠道轴定位:关底 Boss 击败=通关渠道只出专长(feat 令牌);
|
||||||
|
# 奖励节点/商店只出属性值提升+资源(非 relic 令牌),专长在非 Boss 渠道只以门票形态出现(T5)。
|
||||||
|
# 过滤式而非物理拆池:deck 物理结构/存档键 bonus_deck 混编余库/消耗语义(选中踢池未选回池)原样,
|
||||||
|
# 白名单在候选视图层生效——配额结构锚 TIER_QUOTAS 70/25/3=98 零改动(专长令牌本系活行追加面,在锚外)。
|
||||||
|
func _draw_curio_candidates(channel: String = "reward_node") -> Array:
|
||||||
_last_draw_treasure = []
|
_last_draw_treasure = []
|
||||||
var pool := _bonus_deck.duplicate()
|
var pool := _curio_deck.duplicate()
|
||||||
|
if channel == "level_clear":
|
||||||
|
pool = pool.filter(func(tok): return String((tok as Dictionary).get("kind", "")) == "relic")
|
||||||
|
else:
|
||||||
|
pool = pool.filter(func(tok): return String((tok as Dictionary).get("kind", "")) != "relic")
|
||||||
var rg: RandomNumberGenerator = rng.open_stream("reward_pick")
|
var rg: RandomNumberGenerator = rng.open_stream("reward_pick")
|
||||||
for i in pool.size():
|
for i in pool.size():
|
||||||
var j := rg.randi_range(i, pool.size() - 1)
|
var j := rg.randi_range(i, pool.size() - 1)
|
||||||
@@ -737,13 +763,54 @@ func _draw_bonus_candidates() -> Array:
|
|||||||
seen.append(ident)
|
seen.append(ident)
|
||||||
var cand := {"kind": String((tok as Dictionary).get("kind", "")), "tier": String((tok as Dictionary).get("tier", ""))}
|
var cand := {"kind": String((tok as Dictionary).get("kind", "")), "tier": String((tok as Dictionary).get("tier", ""))}
|
||||||
if ident.begins_with("relic:"):
|
if ident.begins_with("relic:"):
|
||||||
cand["relic_id"] = String((tok as Dictionary).get("relic_id", ""))
|
cand["feat_id"] = String((tok as Dictionary).get("feat_id", ""))
|
||||||
out.append(cand)
|
out.append(cand)
|
||||||
if out.size() >= int(db.adventure.feature_candidate_count):
|
if out.size() >= int(db.adventure.curio_candidate_count):
|
||||||
break
|
break
|
||||||
|
# T5 门票注入(非 Boss 渠道唯一专长入口——替换末位候选占一席,候选数不变;判定含 pity 清零记账)
|
||||||
|
if channel == "reward_node" and not out.is_empty() and _feat_ticket_consider(rg):
|
||||||
|
out[out.size() - 1] = {"kind": "feat_ticket", "tier": "rare"}
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# —— 专长获取门票(T5 迭代 15 卡②——非 Boss 渠道的专长入口;四口径契约④:拿到即开不可囤积/
|
||||||
|
# 罕见只在概率不在价格/一局限购一次/计步保底连续 5 展示未出第 6 必出——计数=展示次数、刷新
|
||||||
|
# 重展计入、节点与商店共享、触发清零、限购满不再触发。调用即一次展示判定与记账)——
|
||||||
|
func _feat_ticket_consider(rg: RandomNumberGenerator) -> bool:
|
||||||
|
if _feat_ticket_sold >= int(db.adventure.feat_ticket_limit):
|
||||||
|
return false # 限购满——不再触发(保底冻结)
|
||||||
|
if _feat_ticket_appeared >= int(db.adventure.feat_ticket_quota):
|
||||||
|
return false # 配额满——本局不再出现
|
||||||
|
var offer: bool = _feat_ticket_pity >= int(db.adventure.feat_ticket_pity_step) or rg.randi_range(1, 100) <= int(db.adventure.feat_ticket_chance)
|
||||||
|
if offer:
|
||||||
|
_feat_ticket_pity = 0
|
||||||
|
_feat_ticket_appeared += 1
|
||||||
|
else:
|
||||||
|
_feat_ticket_pity += 1
|
||||||
|
return offer
|
||||||
|
|
||||||
|
|
||||||
|
# 门票兑付(拿到即开——停点候选替换为专长三选一;不进持有集/零摘牌;限购计数 +1):
|
||||||
|
# 节点停点=本地替换(呈现层读在途态重绘);店内无停点=临时停点 + payload.context 标记(呈现层
|
||||||
|
# 重 present 无胜利总结面,确认后只关面回店)。
|
||||||
|
func _redeem_feat_ticket() -> bool:
|
||||||
|
_feat_ticket_sold += 1
|
||||||
|
_feat_ticket_pity = 0
|
||||||
|
var cands: Array = _draw_curio_candidates("level_clear") # 专长池(与通关渠道同白名单)
|
||||||
|
if cands.is_empty():
|
||||||
|
push_error("[冒险结构] 门票兑付失败——专长池空集")
|
||||||
|
_feat_ticket_sold -= 1
|
||||||
|
return false
|
||||||
|
if not _node_curio_pending.is_empty():
|
||||||
|
_node_curio_pending["candidates"] = cands.duplicate(true)
|
||||||
|
return true
|
||||||
|
var payload: Dictionary = {"context": "feat_ticket", "candidates": cands.duplicate(true)}
|
||||||
|
_curio_pending = {"candidates": cands.duplicate(true), "payload": payload.duplicate(true)}
|
||||||
|
curio_choice_presented.emit(payload)
|
||||||
|
_snapshot_breakpoint()
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
func _roll_from(rg: RandomNumberGenerator) -> String:
|
func _roll_from(rg: RandomNumberGenerator) -> String:
|
||||||
var pool: Array = _bonus_treasure_pool()
|
var pool: Array = _bonus_treasure_pool()
|
||||||
return String(pool[rg.randi_range(0, pool.size() - 1)])
|
return String(pool[rg.randi_range(0, pool.size() - 1)])
|
||||||
@@ -828,11 +895,11 @@ func _start_current_level() -> bool:
|
|||||||
if ok:
|
if ok:
|
||||||
_runner.set_attunement(_attunement) # 迭代 11 第①批 T5——同调装配下传(enc 携带进战斗结算)
|
_runner.set_attunement(_attunement) # 迭代 11 第①批 T5——同调装配下传(enc 携带进战斗结算)
|
||||||
_runner.set_attune_charges(attune_charges) # 迭代 11 第①批 T6——充能账本下传(能力件骑库存面注入)
|
_runner.set_attune_charges(attune_charges) # 迭代 11 第①批 T6——充能账本下传(能力件骑库存面注入)
|
||||||
_runner.set_relics(_relics) # 迭代 12 T1——特性持有集下传(begin_battle enc.relics 求值面)
|
_runner.set_feats(_feats) # 迭代 12 T1——特性持有集下传(begin_battle enc.feats 求值面)
|
||||||
_runner.set_tree_rules(_tree_rules) # 迭代 12 T10——树 rule 键集下传(A/B 面消费)
|
_runner.set_tree_rules(_tree_rules) # 迭代 12 T10——树 rule 键集下传(A/B 面消费)
|
||||||
if _shop_mode == "pool":
|
if _shop_mode == "pool":
|
||||||
# 商店共享池注入(迭代 11 第②批 T3——库存账本引用+消费 handler 双下传;legacy 模式零注入零扰动)
|
# 商店共享池注入(迭代 11 第②批 T3——库存账本引用+消费 handler 双下传;legacy 模式零注入零扰动)
|
||||||
_runner.set_shop_pool(_bonus_deck, Callable(self, "_consume_pool_purchase"))
|
_runner.set_shop_pool(_curio_deck, Callable(self, "_consume_pool_purchase"), Callable(self, "_feat_ticket_consider"))
|
||||||
# 三选一道具件跨关携带注入(迭代 10)——迭代 11 第①批 T7 退役:aux 卡出池后 carry 恒空集,
|
# 三选一道具件跨关携带注入(迭代 10)——迭代 11 第①批 T7 退役: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", "")))
|
||||||
@@ -876,7 +943,7 @@ func _on_level_finished(payload: Dictionary) -> void:
|
|||||||
_reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转)
|
_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_curio_candidates("level_clear"), # T4 渠道白名单——关底 Boss 击败=通关渠道只出专长
|
||||||
"aux_detail": _last_draw_treasure.duplicate(), # 掷定前置(追加批二——道具件具体道具随候选抽定,卡片直显)
|
"aux_detail": _last_draw_treasure.duplicate(), # 掷定前置(追加批二——道具件具体道具随候选抽定,卡片直显)
|
||||||
"heal_gained": heal,
|
"heal_gained": heal,
|
||||||
"hp_before": hp_before,
|
"hp_before": hp_before,
|
||||||
@@ -886,10 +953,10 @@ func _on_level_finished(payload: Dictionary) -> void:
|
|||||||
"gp": int(ledger.get("gp", 0)),
|
"gp": int(ledger.get("gp", 0)),
|
||||||
"aux_cleared": int(payload.get("aux_cleared", 0)), # additive 扩签(迭代 7 T4——关卡末清空反馈取数)
|
"aux_cleared": int(payload.get("aux_cleared", 0)), # additive 扩签(迭代 7 T4——关卡末清空反馈取数)
|
||||||
}
|
}
|
||||||
_bonus_pending = {"candidates": (pick_payload["candidates"] as Array).duplicate(true),
|
_curio_pending = {"candidates": (pick_payload["candidates"] as Array).duplicate(true),
|
||||||
"aux_detail": _last_draw_treasure.duplicate(),
|
"aux_detail": _last_draw_treasure.duplicate(),
|
||||||
"payload": pick_payload.duplicate(true)}
|
"payload": pick_payload.duplicate(true)}
|
||||||
bonus_choice_presented.emit(pick_payload)
|
curio_choice_presented.emit(pick_payload)
|
||||||
_snapshot_breakpoint() # 选择停点可中断(契约 5——续玩不重抽不静默丢失)
|
_snapshot_breakpoint() # 选择停点可中断(契约 5——续玩不重抽不静默丢失)
|
||||||
return
|
return
|
||||||
# 末关通关:不发三选一与部分回血(没有下一战可受益)、Boss 钥石照常入账——冒险完成结算
|
# 末关通关:不发三选一与部分回血(没有下一战可受益)、Boss 钥石照常入账——冒险完成结算
|
||||||
@@ -908,7 +975,7 @@ func _settle(result: String) -> void:
|
|||||||
"aux_cleared_last": _last_treasure_cleared, # additive 扩签(迭代 7 T4——末关清空反馈;非末关走 bonus 载荷)
|
"aux_cleared_last": _last_treasure_cleared, # additive 扩签(迭代 7 T4——末关清空反馈;非末关走 bonus 载荷)
|
||||||
"keystone_drops": _keystone_drops.duplicate(true), # additive 扩签(迭代 10——逐杀掉落明细;settle 入账明细/复盘归因数据源)
|
"keystone_drops": _keystone_drops.duplicate(true), # additive 扩签(迭代 10——逐杀掉落明细;settle 入账明细/复盘归因数据源)
|
||||||
"pool_stock": _pool_stock_snapshot(), # additive 扩签(迭代 11 第②批 T5——终局池读数:清账前快照,商店买走 vs 奖励领走 vs 枯竭的终局面)
|
"pool_stock": _pool_stock_snapshot(), # additive 扩签(迭代 11 第②批 T5——终局池读数:清账前快照,商店买走 vs 奖励领走 vs 枯竭的终局面)
|
||||||
"relics": _relics.duplicate(), # additive 扩签(迭代 12 T1——特性终局持有集:清账前快照,成型判读取数面)
|
"relics": _feats.duplicate(), # additive 扩签(迭代 12 T1——特性终局持有集:清账前快照,成型判读取数面)
|
||||||
}
|
}
|
||||||
# 败退明细(数值验证工具取数面 2026-09-22——tools/balance_verifier.py 止步节点分布数据源;
|
# 败退明细(数值验证工具取数面 2026-09-22——tools/balance_verifier.py 止步节点分布数据源;
|
||||||
# 仅失败局附加,键缺省 = 未败退,既有五件事消费方零感知)
|
# 仅失败局附加,键缺省 = 未败退,既有五件事消费方零感知)
|
||||||
@@ -920,7 +987,7 @@ func _settle(result: String) -> void:
|
|||||||
}
|
}
|
||||||
if _runner != null:
|
if _runner != null:
|
||||||
_runner.close_wallet() # GP 冒险收束清零(三结局都清——经转发发 wallet_changed "clear" 供日志/呈现)
|
_runner.close_wallet() # GP 冒险收束清零(三结局都清——经转发发 wallet_changed "clear" 供日志/呈现)
|
||||||
_reset_bonus_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化)
|
_reset_curio_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化)
|
||||||
if _save_manager != null:
|
if _save_manager != null:
|
||||||
_save_manager.clear_breakpoint() # 三结局清断点(迭代 8 卡①/T3——退出冒险=放弃流程+清除断点)
|
_save_manager.clear_breakpoint() # 三结局清断点(迭代 8 卡①/T3——退出冒险=放弃流程+清除断点)
|
||||||
_save_manager.write_to_disk()
|
_save_manager.write_to_disk()
|
||||||
@@ -974,11 +1041,13 @@ func _snapshot_breakpoint() -> void:
|
|||||||
"keystone": keystone,
|
"keystone": keystone,
|
||||||
"visited_ids": _runner.visited_ids() if _runner != null else [],
|
"visited_ids": _runner.visited_ids() if _runner != null else [],
|
||||||
"aux_items": treasure_snapshot,
|
"aux_items": treasure_snapshot,
|
||||||
"bonus_taken": _bonus_taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
|
"bonus_taken": _curio_taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
|
||||||
"bonus_deck": _bonus_deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
|
"bonus_deck": _curio_deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
|
||||||
"relics": _relics.duplicate(), # 特性持有集(迭代 12 T1——局内随机渠道获得态续玩防丢失)
|
"relics": _feats.duplicate(), # 特性持有集(迭代 12 T1——局内随机渠道获得态续玩防丢失)
|
||||||
"bonus_pending": _bonus_pending.duplicate(true), # 三选一停点态(空 = 无停点——选择停点可中断契约)
|
"bonus_pending": _curio_pending.duplicate(true), # 三选一停点态(空 = 无停点——选择停点可中断契约)
|
||||||
"bonus_result": _bonus_result.duplicate(true), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
|
"feat_ticket_pity": _feat_ticket_pity, # T5 门票保底计数(扩列向后兼容——旧档缺键缺省 0)
|
||||||
|
"feat_ticket_sold": _feat_ticket_sold, # T5 门票限购计数(同上)
|
||||||
|
"bonus_result": _curio_result.duplicate(true), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
|
||||||
"bonus_aux_carry": _bonus_treasure_carry.duplicate(true), # 道具件跨关携带(注入前中断防静默丢失)
|
"bonus_aux_carry": _bonus_treasure_carry.duplicate(true), # 道具件跨关携带(注入前中断防静默丢失)
|
||||||
"keystone_drops": _keystone_drops.duplicate(true), # 逐杀掉落明细(摘要归因续接——迭代 10)
|
"keystone_drops": _keystone_drops.duplicate(true), # 逐杀掉落明细(摘要归因续接——迭代 10)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,13 +72,13 @@ var attunement: Array = [] # 装备中同调件 item_id 集
|
|||||||
var _attune_charges_ref: Dictionary = {} # 充能账本共享引用(迭代 11 第①批 T6——use_treasure 消费直改)
|
var _attune_charges_ref: Dictionary = {} # 充能账本共享引用(迭代 11 第①批 T6——use_treasure 消费直改)
|
||||||
var _attune_bank := 0 # 掘进蓄能积点(battle 域——每 charge_n 安全格 +1 点)
|
var _attune_bank := 0 # 掘进蓄能积点(battle 域——每 charge_n 安全格 +1 点)
|
||||||
var _attune_safe_seen := 0 # 掘进蓄能 battle 域安全开格计数
|
var _attune_safe_seen := 0 # 掘进蓄能 battle 域安全开格计数
|
||||||
# 特性运行态(迭代 12 T2——relic 持有集经 enc.relics 注入;求值旗注 damage_controller.relic_flags;
|
# 特性运行态(迭代 12 T2——relic 持有集经 enc.feats 注入;求值旗注 damage_controller.feat_flags;
|
||||||
# 魔矢每战首次计数 battle 域。空集=零扰动)
|
# 魔矢每战首次计数 battle 域。空集=零扰动)
|
||||||
var _relic_first_dig_used := false
|
var _feat_first_dig_used := false
|
||||||
var _relic_combo := 0 # 矿脉直觉连击计数(battle 域——safe +1/mine 清零)
|
var _feat_combo := 0 # 矿脉直觉连击计数(battle 域——safe +1/mine 清零)
|
||||||
var _relic_deep_given := 0 # 深挖钻头本轮已追加份数(battle 域——v2 本格双份制:safe 事件按付费数上限)
|
var _feat_deep_given := 0 # 深挖钻头本轮已追加份数(battle 域——v2 本格双份制:safe 事件按付费数上限)
|
||||||
var _relic_paid_at_round_start := 0 # 轮初付费计数快照(v2——本轮付费次数=当前值−快照)
|
var _feat_paid_at_round_start := 0 # 轮初付费计数快照(v2——本轮付费次数=当前值−快照)
|
||||||
var _relic_magma_layers := 0 # 熔火之躯积层(battle 域——受地形伤 +1,轮末反打清零)
|
var _feat_magma_layers := 0 # 熔火之躯积层(battle 域——受地形伤 +1,轮末反打清零)
|
||||||
|
|
||||||
|
|
||||||
func _attune_has(item_id: String) -> bool:
|
func _attune_has(item_id: String) -> bool:
|
||||||
@@ -94,15 +94,15 @@ func _attune_mag(item_id: String) -> int:
|
|||||||
return int(_attune_row(item_id).get("magnitude", 0))
|
return int(_attune_row(item_id).get("magnitude", 0))
|
||||||
|
|
||||||
|
|
||||||
# 特性求值(迭代 12 T2——持有集 × relic_table → damage_controller.relic_flags 注入面):
|
# 特性求值(迭代 12 T2——持有集 × feat_table → damage_controller.feat_flags 注入面):
|
||||||
# rider_flat = Σ rider 件 value(奥术余烬 +1 / 魔能共鸣 +2);magic 档一(≥2 件)rider +1、
|
# rider_flat = Σ rider 件 value(奥术余烬 +1 / 魔能共鸣 +2);magic 档一(≥2 件)rider +1、
|
||||||
# 档二(≥4 件)rider 可暴击;clamp 两件直调键;first_dig_spell 旗(魔能共鸣第二效果——魔矢值
|
# 档二(≥4 件)rider 可暴击;clamp 两件直调键;first_dig_spell 旗(魔能共鸣第二效果——魔矢值
|
||||||
# = 件 effects.value)。空集/空表 = 零旗零扰动。
|
# = 件 effects.value)。空集/空表 = 零旗零扰动。
|
||||||
func _relic_flags_for(relic_ids: Array) -> void:
|
func _feat_flags_for(feat_ids: Array) -> void:
|
||||||
var flags: Dictionary = {}
|
var flags: Dictionary = {}
|
||||||
if not relic_ids.is_empty() and db.relics != null and not db.relics.rows.is_empty():
|
if not feat_ids.is_empty() and db.feats != null and not db.feats.rows.is_empty():
|
||||||
var hooks := RelicsEngine.hooks_for(relic_ids, db.relics)
|
var hooks := FeatsEngine.hooks_for(feat_ids, db.feats)
|
||||||
var tiers := RelicsEngine.tiers_for(relic_ids, db.relics)
|
var tiers := FeatsEngine.tiers_for(feat_ids, db.feats)
|
||||||
for h in hooks:
|
for h in hooks:
|
||||||
var hook: String = String(h.get("hook", ""))
|
var hook: String = String(h.get("hook", ""))
|
||||||
var fx: Dictionary = h.get("effects", {}) as Dictionary
|
var fx: Dictionary = h.get("effects", {}) as Dictionary
|
||||||
@@ -173,10 +173,10 @@ func _relic_flags_for(relic_ids: Array) -> void:
|
|||||||
flags["rider_flat"] = int(flags.get("rider_flat", 0)) + 1 # 档一质变(≥2 件):rider +1(provisional)
|
flags["rider_flat"] = int(flags.get("rider_flat", 0)) + 1 # 档一质变(≥2 件):rider +1(provisional)
|
||||||
if mt >= 2:
|
if mt >= 2:
|
||||||
flags["rider_crit"] = true # 档二质变(≥4 件):rider 可暴击(crit 时乘暴击倍率)
|
flags["rider_crit"] = true # 档二质变(≥4 件):rider 可暴击(crit 时乘暴击倍率)
|
||||||
_damage.relic_flags = flags
|
_damage.feat_flags = flags
|
||||||
# 蓄力流/深挖钻头 action_economy 注入(求值即注入——冒烟直呼 _relic_flags_for 同面生效)
|
# 蓄力流/深挖钻头 action_economy 注入(求值即注入——冒烟直呼 _feat_flags_for 同面生效)
|
||||||
action_economy.relic_ap_carry = int(flags.get("ap_carry_value", 0)) > 0
|
action_economy.feat_ap_carry = int(flags.get("ap_carry_value", 0)) > 0
|
||||||
action_economy.relic_dig_extra = int(flags.get("deep_dig_cost", 0))
|
action_economy.feat_dig_extra = int(flags.get("deep_dig_cost", 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
|
||||||
@@ -247,21 +247,21 @@ func begin_battle(enc: Dictionary) -> bool:
|
|||||||
_attune_safe_seen = 0
|
_attune_safe_seen = 0
|
||||||
_damage.attune_venom = _attune_mag("venom_edge") if _attune_has("venom_edge") else 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
|
_reward_economy.luck_bias = _attune_mag("luck_coin") if _attune_has("luck_coin") else 0
|
||||||
# 特性运行态注入(迭代 12 T2 魔法流——enc.relics 携带持有集;RelicsEngine 求值后注伤害控制器
|
# 特性运行态注入(迭代 12 T2 魔法流——enc.feats 携带持有集;FeatsEngine 求值后注伤害控制器
|
||||||
# relic_flags;空集/空表=零扰动。魔矢每战首次开格计数同步归零)
|
# feat_flags;空集/空表=零扰动。魔矢每战首次开格计数同步归零)
|
||||||
_relic_flags_for((enc.get("relics", []) as Array))
|
_feat_flags_for((enc.get("feats", []) as Array))
|
||||||
_relic_first_dig_used = false
|
_feat_first_dig_used = false
|
||||||
_relic_combo = 0
|
_feat_combo = 0
|
||||||
_relic_deep_given = 0
|
_feat_deep_given = 0
|
||||||
_relic_paid_at_round_start = 0
|
_feat_paid_at_round_start = 0
|
||||||
_relic_magma_layers = 0
|
_feat_magma_layers = 0
|
||||||
# 树 rule 键消费(迭代 12 T10——Fork 激活侧 effects;真实接线首期两键:先手压制=战斗首轮
|
# 树 rule 键消费(迭代 12 T10——Fork 激活侧 effects;真实接线首期两键:先手压制=战斗首轮
|
||||||
# AP+1(queue_ap_delta——先手压制语义)/稳健拓荒=敌方攻击减 1(伤害控制器 flags bulwark);
|
# AP+1(queue_ap_delta——先手压制语义)/稳健拓荒=敌方攻击减 1(伤害控制器 flags bulwark);
|
||||||
# 其余 rule 键=stat 面投影不入战斗(T8 表内容 provisional 口径披露))
|
# 其余 rule 键=stat 面投影不入战斗(T8 表内容 provisional 口径披露))
|
||||||
var tree_rules: Array = enc.get("tree_rules", []) as Array
|
var tree_rules: Array = enc.get("tree_rules", []) as Array
|
||||||
if tree_rules.has("posture_first_strike"):
|
if tree_rules.has("posture_first_strike"):
|
||||||
action_economy.queue_ap_delta(1)
|
action_economy.queue_ap_delta(1)
|
||||||
_damage.relic_flags["bulwark"] = 1 if tree_rules.has("posture_bulwark") else 0
|
_damage.feat_flags["bulwark"] = 1 if tree_rules.has("posture_bulwark") else 0
|
||||||
# 迭代 10 奖励重构批——跨冒险行动点加成轴(统一冒险加算区第四轴):经排队修正通道兑现
|
# 迭代 10 奖励重构批——跨冒险行动点加成轴(统一冒险加算区第四轴):经排队修正通道兑现
|
||||||
# 「每场战斗开始首轮 AP +加成值」(queue_ap_delta = 下轮开轮基线加算、notify_round_start 兑现后
|
# 「每场战斗开始首轮 AP +加成值」(queue_ap_delta = 下轮开轮基线加算、notify_round_start 兑现后
|
||||||
# 清零——首轮即消费,后续轮次不重复兑现;语义锚 = 行动经济 GDD「跨冒险行动点加成轴」节)
|
# 清零——首轮即消费,后续轮次不重复兑现;语义锚 = 行动经济 GDD「跨冒险行动点加成轴」节)
|
||||||
@@ -465,29 +465,29 @@ 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(_relic_yield_adjust(snap, true), "positive", "safe_cell", evt.cell, ri, "")
|
_grant_routed(_feat_yield_adjust(snap, true), "positive", "safe_cell", evt.cell, ri, "")
|
||||||
# 开格流连击/深层产出(迭代 12 T5):矿脉直觉连击 = safe +1(mine 清零在负向分支);
|
# 开格流连击/深层产出(迭代 12 T5):矿脉直觉连击 = safe +1(mine 清零在负向分支);
|
||||||
# 深挖钻头深层产出 = 每 cap 格额外 grant 一份(收益面补偿额外 AP 成本——R4 甲非信息税对价)
|
# 深挖钻头深层产出 = 每 cap 格额外 grant 一份(收益面补偿额外 AP 成本——R4 甲非信息税对价)
|
||||||
_relic_combo += 1
|
_feat_combo += 1
|
||||||
# 深挖钻头 v2(2026-10-04 二次修正——用户实测「付 2AP 无可见提升纯挨打」:v1 配额制
|
# 深挖钻头 v2(2026-10-04 二次修正——用户实测「付 2AP 无可见提升纯挨打」:v1 配额制
|
||||||
# 4 次付费 8AP 换 1 份随机资源=净负期望+收益埋明细带不可感知):改**本格双份**——
|
# 4 次付费 8AP 换 1 份随机资源=净负期望+收益埋明细带不可感知):改**本格双份**——
|
||||||
# 付费挖掘的本格资源直接双份(多花 1AP 买多一份同格产出,账目直白期望微正)。
|
# 付费挖掘的本格资源直接双份(多花 1AP 买多一份同格产出,账目直白期望微正)。
|
||||||
# 本轮付费次数 N=deep_paid_count−轮初快照;safe 事件按 N 上限逐格追加第二份(「深挖」)。
|
# 本轮付费次数 N=deep_paid_count−轮初快照;safe 事件按 N 上限逐格追加第二份(「深挖」)。
|
||||||
var dd_cost := int(_damage.relic_flags.get("deep_dig_cost", 0))
|
var dd_cost := int(_damage.feat_flags.get("deep_dig_cost", 0))
|
||||||
if dd_cost > 0:
|
if dd_cost > 0:
|
||||||
var paid_this_round := int(action_economy.deep_paid_count) - _relic_paid_at_round_start
|
var paid_this_round := int(action_economy.deep_paid_count) - _feat_paid_at_round_start
|
||||||
if paid_this_round > 0 and _relic_deep_given < paid_this_round:
|
if paid_this_round > 0 and _feat_deep_given < paid_this_round:
|
||||||
_relic_deep_given += 1
|
_feat_deep_given += 1
|
||||||
var dd_snap = _reward_economy.grant(evt.cell)
|
var dd_snap = _reward_economy.grant(evt.cell)
|
||||||
if dd_snap is Dictionary:
|
if dd_snap is Dictionary:
|
||||||
_grant_routed(dd_snap, "positive", "safe_cell", evt.cell, ri, "", "(深挖)")
|
_grant_routed(dd_snap, "positive", "safe_cell", evt.cell, ri, "", "(深挖)")
|
||||||
# 魔能共鸣·每战首次开安全格追加魔法矢(迭代 12 T2——relic 件「每关首次开格必出魔法矢」
|
# 魔能共鸣·每战首次开安全格追加魔法矢(迭代 12 T2——relic 件「每关首次开格必出魔法矢」
|
||||||
# 落地为每战首次〔战斗域单事务无跨战状态——「每关」字面改「每战」台账披露待追认〕;
|
# 落地为每战首次〔战斗域单事务无跨战状态——「每关」字面改「每战」台账披露待追认〕;
|
||||||
# board_typed magic 必中,扫雷结算触发点=relic 挂 first_dig_spell 承载红线①)
|
# board_typed magic 必中,扫雷结算触发点=relic 挂 first_dig_spell 承载红线①)
|
||||||
if bool(_damage.relic_flags.get("first_dig_spell", false)) and not _relic_first_dig_used:
|
if bool(_damage.feat_flags.get("first_dig_spell", false)) and not _feat_first_dig_used:
|
||||||
_relic_first_dig_used = true
|
_feat_first_dig_used = true
|
||||||
var spell_t := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "relic_first_dig_spell",
|
var spell_t := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_first_dig_spell",
|
||||||
"奥术矢", "board", int(_damage.relic_flags.get("spell_value", 0)),
|
"奥术矢", "board", int(_damage.feat_flags.get("spell_value", 0)),
|
||||||
DamageController.TARGET_FRONT_ALIVE, ri, evt.cell, "magic")
|
DamageController.TARGET_FRONT_ALIVE, ri, evt.cell, "magic")
|
||||||
_damage.submit(spell_t)
|
_damage.submit(spell_t)
|
||||||
# 掘进蓄能计数(迭代 11 同调批 T6——battle 域安全开格计数,每 charge_n 格积 1 点)
|
# 掘进蓄能计数(迭代 11 同调批 T6——battle 域安全开格计数,每 charge_n 格积 1 点)
|
||||||
@@ -499,8 +499,8 @@ func _settle() -> void:
|
|||||||
elif evt.kind == "mine":
|
elif evt.kind == "mine":
|
||||||
# 插旗不可逆四情况②:插雷格再挖开 = 只受挖雷一层惩罚(mine_hit 照常兑付——惊动巢穴),
|
# 插旗不可逆四情况②:插雷格再挖开 = 只受挖雷一层惩罚(mine_hit 照常兑付——惊动巢穴),
|
||||||
# 标中不发(旗已随挖掘擦除、不入轮末判定基线)。
|
# 标中不发(旗已随挖掘擦除、不入轮末判定基线)。
|
||||||
_grant_routed(_relic_yield_adjust(snap, false), "negative", "mine_hit", evt.cell, ri, "")
|
_grant_routed(_feat_yield_adjust(snap, false), "negative", "mine_hit", evt.cell, ri, "")
|
||||||
_relic_combo = 0 # 矿脉直觉连击清零(踩雷断连——「推理密度直接奖励」的对侧)
|
_feat_combo = 0 # 矿脉直觉连击清零(踩雷断连——「推理密度直接奖励」的对侧)
|
||||||
# 地形变异 兑付段(授予段尾——触发枚举 on_open:挖开即伤、chain 通道同口径,轮末结算逐行兑现;
|
# 地形变异 兑付段(授予段尾——触发枚举 on_open:挖开即伤、chain 通道同口径,轮末结算逐行兑现;
|
||||||
# 信息型 hide_number 不进结算——呈现层读取,见 board_view 数字遮蔽;雷格恒无变异——布置段排除,
|
# 信息型 hide_number 不进结算——呈现层读取,见 board_view 数字遮蔽;雷格恒无变异——布置段排除,
|
||||||
# 双层叠罚不存在;插旗格直挖照吃变异伤害——地形后果同 mine_hit 口径,非奖励不作废)
|
# 双层叠罚不存在;插旗格直挖照吃变异伤害——地形后果同 mine_hit 口径,非奖励不作废)
|
||||||
@@ -516,25 +516,25 @@ func _settle() -> void:
|
|||||||
_damage.submit(vt)
|
_damage.submit(vt)
|
||||||
# 吞噬流四面(迭代 12 T6——地形从纯税到第三资源带;伤害仍发生=乙稿判据 3 承载):
|
# 吞噬流四面(迭代 12 T6——地形从纯税到第三资源带;伤害仍发生=乙稿判据 3 承载):
|
||||||
# ①荆棘编织者=挖开伤害型地形格额外掉落一次格面资源(主动吃地形换资源)
|
# ①荆棘编织者=挖开伤害型地形格额外掉落一次格面资源(主动吃地形换资源)
|
||||||
if int(_damage.relic_flags.get("terrain_dig_double", 0)) > 0:
|
if int(_damage.feat_flags.get("terrain_dig_double", 0)) > 0:
|
||||||
var tw_snap = _reward_economy.grant(evt.cell)
|
var tw_snap = _reward_economy.grant(evt.cell)
|
||||||
if tw_snap is Dictionary:
|
if tw_snap is Dictionary:
|
||||||
_grant_routed(tw_snap, "positive", "safe_cell", evt.cell, ri, "", "(荆棘编织)")
|
_grant_routed(tw_snap, "positive", "safe_cell", evt.cell, ri, "", "(荆棘编织)")
|
||||||
# ②地脉采撷=受地形伤掉落小额资源补偿(薄矿化——每次受伤兑一份)
|
# ②地脉采撷=受地形伤掉落小额资源补偿(薄矿化——每次受伤兑一份)
|
||||||
if int(_damage.relic_flags.get("terrain_refund", 0)) > 0:
|
if int(_damage.feat_flags.get("terrain_refund", 0)) > 0:
|
||||||
var lh_snap = _reward_economy.grant(evt.cell)
|
var lh_snap = _reward_economy.grant(evt.cell)
|
||||||
if lh_snap is Dictionary:
|
if lh_snap is Dictionary:
|
||||||
_grant_routed(lh_snap, "positive", "safe_cell", evt.cell, ri, "", "(地脉补偿)")
|
_grant_routed(lh_snap, "positive", "safe_cell", evt.cell, ri, "", "(地脉补偿)")
|
||||||
# ③熔火之躯=受地形伤积层(触发键=「受地形伤」通用不按种类特化——定稿红线承载)
|
# ③熔火之躯=受地形伤积层(触发键=「受地形伤」通用不按种类特化——定稿红线承载)
|
||||||
if int(_damage.relic_flags.get("variant_counter_value", 0)) > 0:
|
if int(_damage.feat_flags.get("variant_counter_value", 0)) > 0:
|
||||||
_relic_magma_layers += 1
|
_feat_magma_layers += 1
|
||||||
for evt in _facts_flags:
|
for evt in _facts_flags:
|
||||||
_judge_flag(evt, ri)
|
_judge_flag(evt, ri)
|
||||||
# 战旗军规开单段(迭代 12 T3 标旗流——flag_judged 批后统一开单:轮末每面标中旗对最前存活敌
|
# 战旗军规开单段(迭代 12 T3 标旗流——flag_judged 批后统一开单:轮末每面标中旗对最前存活敌
|
||||||
# 结算 board_typed 必中伤害〔复用零新配方;「旗所在巢穴敌」简化为 default_target 最前存活敌——
|
# 结算 board_typed 必中伤害〔复用零新配方;「旗所在巢穴敌」简化为 default_target 最前存活敌——
|
||||||
# 台账披露待追认〕;完美布防=本轮旗批全中(hit>0 且 miss==0)时旗伤 ×mult——全对推理奖励)。
|
# 台账披露待追认〕;完美布防=本轮旗批全中(hit>0 且 miss==0)时旗伤 ×mult——全对推理奖励)。
|
||||||
# 扫雷结算触发点=flag_strike/flag_all_hit/nest_aura 挂点承载红线①。
|
# 扫雷结算触发点=flag_strike/flag_all_hit/nest_aura 挂点承载红线①。
|
||||||
if int(_damage.relic_flags.get("flag_strike_value", 0)) > 0:
|
if int(_damage.feat_flags.get("flag_strike_value", 0)) > 0:
|
||||||
var hits := 0
|
var hits := 0
|
||||||
var misses := 0
|
var misses := 0
|
||||||
for fevt in _facts_flags:
|
for fevt in _facts_flags:
|
||||||
@@ -543,26 +543,26 @@ func _settle() -> void:
|
|||||||
else:
|
else:
|
||||||
misses += 1
|
misses += 1
|
||||||
if hits > 0:
|
if hits > 0:
|
||||||
var mult := int(_damage.relic_flags.get("flag_all_hit_mult", 1))
|
var mult := int(_damage.feat_flags.get("flag_all_hit_mult", 1))
|
||||||
var strike_value := int(_damage.relic_flags.get("flag_strike_value", 0))
|
var strike_value := int(_damage.feat_flags.get("flag_strike_value", 0))
|
||||||
if misses == 0 and mult > 1:
|
if misses == 0 and mult > 1:
|
||||||
strike_value *= mult # 完美布防:同轮全中旗伤倍增(全对推理奖励)
|
strike_value *= mult # 完美布防:同轮全中旗伤倍增(全对推理奖励)
|
||||||
for fevt in _facts_flags:
|
for fevt in _facts_flags:
|
||||||
if not bool(fevt.get("hit", false)):
|
if not bool(fevt.get("hit", false)):
|
||||||
continue
|
continue
|
||||||
var st_t := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "relic_flag_strike",
|
var st_t := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_flag_strike",
|
||||||
"战旗军规", "board", strike_value, DamageController.TARGET_FRONT_ALIVE, ri,
|
"战旗军规", "board", strike_value, DamageController.TARGET_FRONT_ALIVE, ri,
|
||||||
fevt.cell, "normal")
|
fevt.cell, "normal")
|
||||||
_damage.submit(st_t)
|
_damage.submit(st_t)
|
||||||
# 熔火之躯轮末反打(迭代 12 T6 吞噬流——受地形伤积层,轮末每层对全体敌 ~1 必中反伤;
|
# 熔火之躯轮末反打(迭代 12 T6 吞噬流——受地形伤积层,轮末每层对全体敌 ~1 必中反伤;
|
||||||
# 触发键=「受地形伤」通用不按地形种类特化——定稿红线承载;反打后清零)
|
# 触发键=「受地形伤」通用不按地形种类特化——定稿红线承载;反打后清零)
|
||||||
var magma := int(_damage.relic_flags.get("variant_counter_value", 0))
|
var magma := int(_damage.feat_flags.get("variant_counter_value", 0))
|
||||||
if magma > 0 and _relic_magma_layers > 0:
|
if magma > 0 and _feat_magma_layers > 0:
|
||||||
var mg_t := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, "relic_magma_body",
|
var mg_t := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_magma_body",
|
||||||
"熔火反噬 ×%d" % _relic_magma_layers, "board", magma * _relic_magma_layers,
|
"熔火反噬 ×%d" % _feat_magma_layers, "board", magma * _feat_magma_layers,
|
||||||
DamageController.TARGET_ALL_ALIVE, ri, minesweeper.hero_pos(), "", 0, "", {})
|
DamageController.TARGET_ALL_ALIVE, ri, minesweeper.hero_pos(), "", 0, "", {})
|
||||||
_damage.submit(mg_t)
|
_damage.submit(mg_t)
|
||||||
_relic_magma_layers = 0
|
_feat_magma_layers = 0
|
||||||
if _facts_path == "cleared":
|
if _facts_path == "cleared":
|
||||||
_grant_clear_reward(ri)
|
_grant_clear_reward(ri)
|
||||||
_damage.flush() # 授予段界结账(B 模式——段内开单攒批到此统一兑现;A 模式 no-op;清图批前另有同步结账点)
|
_damage.flush() # 授予段界结账(B 模式——段内开单攒批到此统一兑现;A 模式 no-op;清图批前另有同步结账点)
|
||||||
@@ -592,7 +592,7 @@ func _settle() -> void:
|
|||||||
_emit_row("verdict", "", Vector2i(-1, -1), ri, "下一轮次", 0, {}, "")
|
_emit_row("verdict", "", Vector2i(-1, -1), ri, "下一轮次", 0, {}, "")
|
||||||
_exit_distribution()
|
_exit_distribution()
|
||||||
# 深挖钻头 v2 轮初快照滚动(本轮付费次数已消费——下一轮 N=当前值−新快照)
|
# 深挖钻头 v2 轮初快照滚动(本轮付费次数已消费——下一轮 N=当前值−新快照)
|
||||||
_relic_paid_at_round_start = int(action_economy.deep_paid_count)
|
_feat_paid_at_round_start = int(action_economy.deep_paid_count)
|
||||||
_settling = false
|
_settling = false
|
||||||
|
|
||||||
|
|
||||||
@@ -601,18 +601,18 @@ func _settle() -> void:
|
|||||||
# 开格产出乘区(迭代 12 T5 开格流——贪婪矿灯/矿脉直觉/赌徒的血签;duplicate 隔离不回写快照):
|
# 开格产出乘区(迭代 12 T5 开格流——贪婪矿灯/矿脉直觉/赌徒的血签;duplicate 隔离不回写快照):
|
||||||
# positive=power ×(1+静态 mult% + 矿脉直觉连击加成 min(cap,combo)×10%);negative=power ×(1+cost%)。
|
# positive=power ×(1+静态 mult% + 矿脉直觉连击加成 min(cap,combo)×10%);negative=power ×(1+cost%)。
|
||||||
# 零旗=原快照原样返回(零扰动)。
|
# 零旗=原快照原样返回(零扰动)。
|
||||||
func _relic_yield_adjust(snap: Dictionary, positive: bool) -> Dictionary:
|
func _feat_yield_adjust(snap: Dictionary, positive: bool) -> Dictionary:
|
||||||
var pos_m := int(_damage.relic_flags.get("dig_yield_pos_mult", 0))
|
var pos_m := int(_damage.feat_flags.get("dig_yield_pos_mult", 0))
|
||||||
var neg_m := int(_damage.relic_flags.get("dig_yield_neg_mult", 0))
|
var neg_m := int(_damage.feat_flags.get("dig_yield_neg_mult", 0))
|
||||||
if positive and pos_m <= 0 and not _damage.relic_flags.has("combo_cap"):
|
if positive and pos_m <= 0 and not _damage.feat_flags.has("combo_cap"):
|
||||||
return snap
|
return snap
|
||||||
if not positive and neg_m <= 0:
|
if not positive and neg_m <= 0:
|
||||||
return snap
|
return snap
|
||||||
var out: Dictionary = snap.duplicate()
|
var out: Dictionary = snap.duplicate()
|
||||||
var power := int(out.get("power", int(_reward_economy.polarity_row(String(out.get("cell_type", "")), "positive" if positive else "negative").power)))
|
var power := int(out.get("power", int(_reward_economy.polarity_row(String(out.get("cell_type", "")), "positive" if positive else "negative").power)))
|
||||||
var pct := pos_m if positive else neg_m
|
var pct := pos_m if positive else neg_m
|
||||||
if positive and _damage.relic_flags.has("combo_cap"):
|
if positive and _damage.feat_flags.has("combo_cap"):
|
||||||
pct += mini(int(_damage.relic_flags.get("combo_cap", 5)), _relic_combo) * 10
|
pct += mini(int(_damage.feat_flags.get("combo_cap", 5)), _feat_combo) * 10
|
||||||
out["power"] = maxi(1, int(round(float(power) * (1.0 + float(pct) / 100.0))))
|
out["power"] = maxi(1, int(round(float(power) * (1.0 + float(pct) / 100.0))))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@@ -680,7 +680,7 @@ func set_treasure_unlocked(ids: Dictionary) -> void:
|
|||||||
|
|
||||||
func _grant_routed(snap: Dictionary, polarity: String, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "") -> bool:
|
func _grant_routed(snap: Dictionary, polarity: String, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "") -> bool:
|
||||||
# 迭代 11 第①批 T7——aux 授予分支随掉落/停点渠道退役拆除(reward 词表第十型 treasure_item 保留但零投放:
|
# 迭代 11 第①批 T7——aux 授予分支随掉落/停点渠道退役拆除(reward 词表第十型 treasure_item 保留但零投放:
|
||||||
# 无 aux 行入池、FEATURE_KINDS 词表已除名);剩余类型走极性字典路由。
|
# 无 aux 行入池、CURIO_KINDS 词表已除名);剩余类型走极性字典路由。
|
||||||
var row: Dictionary = _reward_economy.polarity_row(String(snap.get("cell_type", "")), polarity)
|
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])
|
||||||
@@ -832,7 +832,7 @@ func _judge_flag(evt: Dictionary, ri: int) -> void:
|
|||||||
# 净化工兵(迭代 12 T6 吞噬流——标中旗中和相邻伤害型地形格):🔴 R4 甲永不对冲边界=
|
# 净化工兵(迭代 12 T6 吞噬流——标中旗中和相邻伤害型地形格):🔴 R4 甲永不对冲边界=
|
||||||
# **仅限伤害型**(thorn/magma/rockfall——variation 表 effect=flat_damage 判据),信息型
|
# **仅限伤害型**(thorn/magma/rockfall——variation 表 effect=flat_damage 判据),信息型
|
||||||
# (hide_number 族)永不清除(红线①看门税不可对冲);中和=变普通格(BoardData.clear_variants)。
|
# (hide_number 族)永不清除(红线①看门税不可对冲);中和=变普通格(BoardData.clear_variants)。
|
||||||
if int(_damage.relic_flags.get("flag_neutralize", 0)) > 0:
|
if int(_damage.feat_flags.get("flag_neutralize", 0)) > 0:
|
||||||
var board: BoardData = minesweeper.current_board()
|
var board: BoardData = minesweeper.current_board()
|
||||||
for n in board.neighbors8(cell):
|
for n in board.neighbors8(cell):
|
||||||
var vids_n: Array = board.variant_ids(n)
|
var vids_n: Array = board.variant_ids(n)
|
||||||
@@ -844,10 +844,10 @@ func _judge_flag(evt: Dictionary, ri: int) -> void:
|
|||||||
break
|
break
|
||||||
if harmful:
|
if harmful:
|
||||||
board.clear_variants(n)
|
board.clear_variants(n)
|
||||||
_emit_row("terrain_neutralized", "relic_purge_sapper", n, ri, "净化工兵·地形中和", 0, {}, "")
|
_emit_row("terrain_neutralized", "feat_purge_sapper", n, ri, "净化工兵·地形中和", 0, {}, "")
|
||||||
# 雷区图谱(迭代 12 T3 标旗流——标中雷额外获得一份格面资源:插旗开始产出对标开格;
|
# 雷区图谱(迭代 12 T3 标旗流——标中雷额外获得一份格面资源:插旗开始产出对标开格;
|
||||||
# 复用 grant 单点,额外兑付走同一 _grant_routed 正向路由)
|
# 复用 grant 单点,额外兑付走同一 _grant_routed 正向路由)
|
||||||
if int(_damage.relic_flags.get("flag_resource_extra", 0)) > 0:
|
if int(_damage.feat_flags.get("flag_resource_extra", 0)) > 0:
|
||||||
var extra_snap = _reward_economy.grant(cell)
|
var extra_snap = _reward_economy.grant(cell)
|
||||||
if extra_snap is Dictionary:
|
if extra_snap is Dictionary:
|
||||||
_grant_routed(extra_snap, "positive", "flag_hit", cell, ri, "", "(雷区图谱)")
|
_grant_routed(extra_snap, "positive", "flag_hit", cell, ri, "", "(雷区图谱)")
|
||||||
@@ -952,10 +952,10 @@ func _combat_segment(ri: int) -> void:
|
|||||||
round_safe += 1
|
round_safe += 1
|
||||||
elif evt.kind == "mine":
|
elif evt.kind == "mine":
|
||||||
round_mine = true
|
round_mine = true
|
||||||
_damage.relic_flags["dig_streak"] = 0 if round_mine else round_safe
|
_damage.feat_flags["dig_streak"] = 0 if round_mine else round_safe
|
||||||
# 蓄力层注入(迭代 12 T5——整备军备「每层下轮首段 +1」/厚积薄发满层 spike/铁壁整备减伤——
|
# 蓄力层注入(迭代 12 T5——整备军备「每层下轮首段 +1」/厚积薄发满层 spike/铁壁整备减伤——
|
||||||
# 三件同读 action_economy.charge_layers;battle 域每轮刷新)
|
# 三件同读 action_economy.charge_layers;battle 域每轮刷新)
|
||||||
_damage.relic_flags["charge_layers"] = action_economy.charge_layers
|
_damage.feat_flags["charge_layers"] = action_economy.charge_layers
|
||||||
# ⓪ 主角先手攻击(基础攻击恒存在——2026-09-11 用户裁决;先于敌方行动;授予段负向直伤致死 → 致电短路不先手)。
|
# ⓪ 主角先手攻击(基础攻击恒存在——2026-09-11 用户裁决;先于敌方行动;授予段负向直伤致死 → 致电短路不先手)。
|
||||||
# 单分量伤害构成(2026-09-19 充能退役批·用户拍板):主角攻击 = 唯一基础分量(atk,吃掷点含暴击;
|
# 单分量伤害构成(2026-09-19 充能退役批·用户拍板):主角攻击 = 唯一基础分量(atk,吃掷点含暴击;
|
||||||
# 类型 = 主角 attack_type 表值——2026-09-18 营地扩展前置批:量与类型抽象分离;查甲效矩阵对应行取克制修正)。
|
# 类型 = 主角 attack_type 表值——2026-09-18 营地扩展前置批:量与类型抽象分离;查甲效矩阵对应行取克制修正)。
|
||||||
@@ -971,11 +971,11 @@ func _combat_segment(ri: int) -> void:
|
|||||||
_damage.submit(ht)
|
_damage.submit(ht)
|
||||||
# 连珠矢(迭代 12 T4 穿刺流——开格节奏变攻击段数):本轮每开 ~3 安全格 +1 段穿刺附加
|
# 连珠矢(迭代 12 T4 穿刺流——开格节奏变攻击段数):本轮每开 ~3 安全格 +1 段穿刺附加
|
||||||
# (上限 cap;段值 provisional 固定值不乘 atk——台账披露)。扫描雷结算触发点=dig_count_segments。
|
# (上限 cap;段值 provisional 固定值不乘 atk——台账披露)。扫描雷结算触发点=dig_count_segments。
|
||||||
var chain_value := int(_damage.relic_flags.get("chain_value", 0))
|
var chain_value := int(_damage.feat_flags.get("chain_value", 0))
|
||||||
if chain_value > 0:
|
if chain_value > 0:
|
||||||
var segments := mini(int(_damage.relic_flags.get("chain_cap", 2)), int(_damage.relic_flags.get("dig_streak", 0)) / 3)
|
var segments := mini(int(_damage.feat_flags.get("chain_cap", 2)), int(_damage.feat_flags.get("dig_streak", 0)) / 3)
|
||||||
for s in segments:
|
for s in segments:
|
||||||
var ct := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "relic_chain_arrows",
|
var ct := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_chain_arrows",
|
||||||
"连珠矢", "board", chain_value, DamageController.TARGET_FRONT_ALIVE, ri,
|
"连珠矢", "board", chain_value, DamageController.TARGET_FRONT_ALIVE, ri,
|
||||||
minesweeper.hero_pos(), "pierce")
|
minesweeper.hero_pos(), "pierce")
|
||||||
_damage.submit(ct)
|
_damage.submit(ct)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const TABLE_MANIFEST := {
|
|||||||
"treasure_items": "treasure_items.tres", # 迭代 7 T1 秘宝内容批(TreasureItemTable 六行——定义表 Rule 4 豁免;罗盘 retired/失义件 deferred 标记见 schema 头注)
|
"treasure_items": "treasure_items.tres", # 迭代 7 T1 秘宝内容批(TreasureItemTable 六行——定义表 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 豁免)
|
"attunement_growth": "attunement_growth.tres", # 迭代 11 第①批 同调实施批 T1(AttunementGrowthTable 单行全局——同调位里程碑谓词表;单行先例 Rule 4 豁免)
|
||||||
"relics": "relics.tres", # 迭代 12 T1 双层 BD 规则改写型特性基建(RelicTable 定义表 Rule 4 豁免——TreasureItemTable 先例;空表合法态=主时间线零扰动)
|
"feats": "feats.tres", # 迭代 12 T1 双层 BD 规则改写型特性基建(FeatTable 定义表 Rule 4 豁免——TreasureItemTable 先例;空表合法态=主时间线零扰动)
|
||||||
}
|
}
|
||||||
|
|
||||||
var action_economy: ActionEconomyTable
|
var action_economy: ActionEconomyTable
|
||||||
@@ -50,7 +50,7 @@ var meta_tree: MetaTreeTable # 迭代 12 T8 类型化持用(消费面走 tre
|
|||||||
var treasure_items: TreasureItemTable # 迭代 7 T1 类型化持用(消费面走 treasure_* 访问器)
|
var treasure_items: TreasureItemTable # 迭代 7 T1 类型化持用(消费面走 treasure_* 访问器)
|
||||||
var adventure: AdventureTable
|
var adventure: AdventureTable
|
||||||
var attunement_growth: AttunementGrowthTable # 迭代 11 第①批 T1 类型化持用(消费面走 attunement_* 访问器)
|
var attunement_growth: AttunementGrowthTable # 迭代 11 第①批 T1 类型化持用(消费面走 attunement_* 访问器)
|
||||||
var relics: RelicTable # 迭代 12 T1 类型化持用(消费面走 relic_* 访问器)
|
var feats: FeatTable # 迭代 12 T1 类型化持用(消费面走 feat_* 访问器)
|
||||||
|
|
||||||
var is_ready := false
|
var is_ready := false
|
||||||
var _diagnostics: Array[Dictionary] = [] # {severity, category, message}
|
var _diagnostics: Array[Dictionary] = [] # {severity, category, message}
|
||||||
@@ -142,7 +142,7 @@ func load_tables(root: String, collect_only: bool) -> bool:
|
|||||||
treasure_items = loaded.get("treasure_items")
|
treasure_items = loaded.get("treasure_items")
|
||||||
adventure = loaded.get("adventure")
|
adventure = loaded.get("adventure")
|
||||||
attunement_growth = loaded.get("attunement_growth")
|
attunement_growth = loaded.get("attunement_growth")
|
||||||
relics = loaded.get("relics")
|
feats = loaded.get("feats")
|
||||||
meta_tree = loaded.get("meta_tree")
|
meta_tree = loaded.get("meta_tree")
|
||||||
is_ready = not has_errors
|
is_ready = not has_errors
|
||||||
return not has_errors
|
return not has_errors
|
||||||
@@ -150,13 +150,13 @@ func load_tables(root: String, collect_only: bool) -> bool:
|
|||||||
|
|
||||||
# ---- relic 访问器(迭代 12 T1——消费面一律走本组,禁直读行集)----
|
# ---- relic 访问器(迭代 12 T1——消费面一律走本组,禁直读行集)----
|
||||||
|
|
||||||
func relic_row(relic_id: String) -> Dictionary:
|
func feat_row(feat_id: String) -> Dictionary:
|
||||||
return relics.row_by_id(relic_id) if relics != null else {}
|
return feats.row_by_id(feat_id) if feats != null else {}
|
||||||
|
|
||||||
|
|
||||||
# 规则改写型特性带令牌构建源(池接线——adventure_runner._build_bonus_deck 消费;行 rarity→投放档映射)
|
# 规则改写型特性带令牌构建源(池接线——adventure_runner._build_curio_deck 消费;行 rarity→投放档映射)
|
||||||
func relic_rows() -> Array:
|
func feat_rows() -> Array:
|
||||||
return relics.rows if relics != null else []
|
return feats.rows if feats != null else []
|
||||||
|
|
||||||
|
|
||||||
# 天赋树节点行(迭代 12 T8——MetaProgression 树段/营地树 UI 取数口)
|
# 天赋树节点行(迭代 12 T8——MetaProgression 树段/营地树 UI 取数口)
|
||||||
|
|||||||
@@ -99,18 +99,20 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void:
|
|||||||
for e in validate_skill_pool_links(skt, en, loaded.get("status_effects")):
|
for e in validate_skill_pool_links(skt, en, loaded.get("status_effects")):
|
||||||
push_diag.call("error", e.category, e.message)
|
push_diag.call("error", e.category, e.message)
|
||||||
# ⑥ 商店货架 × 冒险共享池跨表校验(M-4 迭代 11 第②批 T1——次数上限域):
|
# ⑥ 商店货架 × 冒险共享池跨表校验(M-4 迭代 11 第②批 T1——次数上限域):
|
||||||
# pool_shelf kind 须命中 adventure.FEATURE_KINDS 且为有限库存种(cap ∈ [1,99]——gp 无上限种/牌库外种
|
# pool_shelf kind 须命中 adventure.CURIO_KINDS 且为有限库存种(cap ∈ [1,99]——gp 无上限种/牌库外种
|
||||||
# 入货架 = 库存账本无锚,加载致命;gp 袋负交易切边/规则改写型特性三轴切边由 ShopTable 词表封闭另拦)
|
# 入货架 = 库存账本无锚,加载致命;gp 袋负交易切边/规则改写型特性三轴切边由 ShopTable 词表封闭另拦)
|
||||||
var adv = loaded.get("adventure")
|
var adv = loaded.get("adventure")
|
||||||
var shp = loaded.get("shop")
|
var shp = loaded.get("shop")
|
||||||
if adv != null and shp != null:
|
if adv != null and shp != null:
|
||||||
for row in shp.pool_shelf:
|
for row in shp.pool_shelf:
|
||||||
var kind: String = String(row.get("kind_id", ""))
|
var kind: String = String(row.get("kind_id", ""))
|
||||||
if not adv.FEATURE_KINDS.has(kind):
|
if kind == "feat_ticket":
|
||||||
push_diag.call("error", "shelf_dangling_kind", "ShopTable.pool_shelf kind %s 不在 AdventureTable.FEATURE_KINDS(共享池牌库外种)" % kind)
|
continue # T5 门票豁免——货架专属机制种(非 deck 池种,零摘牌拿到即开),不入 CURIO_KINDS
|
||||||
|
if not adv.CURIO_KINDS.has(kind):
|
||||||
|
push_diag.call("error", "shelf_dangling_kind", "ShopTable.pool_shelf kind %s 不在 AdventureTable.CURIO_KINDS(共享池牌库外种)" % kind)
|
||||||
continue
|
continue
|
||||||
# TP0 令牌池——货架件须为有限令牌种(配额 >0;种类次数上限退役后库存=剩余令牌数)
|
# TP0 令牌池——货架件须为有限令牌种(配额 >0;种类次数上限退役后库存=剩余令牌数)
|
||||||
var quota := int(adv.feature_quota(kind, adv.TIER_COMMON)) + int(adv.feature_quota(kind, adv.TIER_RARE)) + int(adv.feature_quota(kind, adv.TIER_RAREST))
|
var quota := int(adv.curio_quota(kind, adv.TIER_COMMON)) + int(adv.curio_quota(kind, adv.TIER_RARE)) + int(adv.curio_quota(kind, adv.TIER_RAREST))
|
||||||
if quota < 1:
|
if quota < 1:
|
||||||
push_diag.call("error", "shelf_zero_quota", "ShopTable.pool_shelf kind %s 令牌配额为 0(牌库空种入货架 = 库存无锚)" % kind)
|
push_diag.call("error", "shelf_zero_quota", "ShopTable.pool_shelf kind %s 令牌配额为 0(牌库空种入货架 = 库存无锚)" % kind)
|
||||||
|
|
||||||
|
|||||||
@@ -60,10 +60,10 @@ var _outcomes := {} # ticket_id → "redeemed"/"zero"/"voided"(
|
|||||||
var _seq := 0 # 流水号
|
var _seq := 0 # 流水号
|
||||||
var _halted := false # 主角倒下终止旗(死亡事件置位——flush 余单作废)
|
var _halted := false # 主角倒下终止旗(死亡事件置位——flush 余单作废)
|
||||||
var _hero_ac_override := -1 # 主角护甲等级合流入参(begin_battle 显式设定——-1 = 表值兜底,口径同战斗编排器)
|
var _hero_ac_override := -1 # 主角护甲等级合流入参(begin_battle 显式设定——-1 = 表值兜底,口径同战斗编排器)
|
||||||
# 特性求值旗(迭代 12 T2——begin_battle 编排器经 RelicsEngine 求值后注入;空字典=零扰动。
|
# 特性求值旗(迭代 12 T2——begin_battle 编排器经 FeatsEngine 求值后注入;空字典=零扰动。
|
||||||
# rider_flat=英雄侧必中附加值〔奥术余烬族〕/rider_crit=档二 rider 可暴击/first_dig_spell=每战首次
|
# rider_flat=英雄侧必中附加值〔奥术余烬族〕/rider_crit=档二 rider 可暴击/first_dig_spell=每战首次
|
||||||
# 开安全格追加魔法矢〔消费在编排器授予段〕/clamp_magic_light·clamp_magic_heavy=克制点直调)
|
# 开安全格追加魔法矢〔消费在编排器授予段〕/clamp_magic_light·clamp_magic_heavy=克制点直调)
|
||||||
var relic_flags: Dictionary = {}
|
var feat_flags: Dictionary = {}
|
||||||
|
|
||||||
|
|
||||||
func _init(db_ref, rng_ref, units_ref, effects_ref) -> void:
|
func _init(db_ref, rng_ref, units_ref, effects_ref) -> void:
|
||||||
@@ -200,40 +200,40 @@ func _resolve_hero_attack(t: Dictionary) -> void:
|
|||||||
var threshold: int = defender_ac(target) + int(db.settlement.attack_bonus)
|
var threshold: int = defender_ac(target) + int(db.settlement.attack_bonus)
|
||||||
# 矿工重锤·命中交换(迭代 12 T4 普攻流——正负双修原生形态:命中判定减成 trade_hit 点
|
# 矿工重锤·命中交换(迭代 12 T4 普攻流——正负双修原生形态:命中判定减成 trade_hit 点
|
||||||
# 〔更易命中〕换攻击增伤 trade_atk%——高 AC 负期望/低 AC 正期望)
|
# 〔更易命中〕换攻击增伤 trade_atk%——高 AC 负期望/低 AC 正期望)
|
||||||
var trade_hit := int(relic_flags.get("hit_trade_hit", 0))
|
var trade_hit := int(feat_flags.get("hit_trade_hit", 0))
|
||||||
if trade_hit > 0:
|
if trade_hit > 0:
|
||||||
threshold = maxi(1, threshold - trade_hit)
|
threshold = maxi(1, threshold - trade_hit)
|
||||||
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)
|
||||||
# 专注(迭代 12 T4 普攻流——免掷骰恒定折扣:方差买断成期望——稳定器人格;roll 记 0 归因)
|
# 专注(迭代 12 T4 普攻流——免掷骰恒定折扣:方差买断成期望——稳定器人格;roll 记 0 归因)
|
||||||
var rewrite := int(relic_flags.get("armor_roll_rewrite", 0))
|
var rewrite := int(feat_flags.get("armor_roll_rewrite", 0))
|
||||||
if rewrite > 0:
|
if rewrite > 0:
|
||||||
discount = rewrite
|
discount = rewrite
|
||||||
crit = false
|
crit = false
|
||||||
roll_read["roll"] = 0
|
roll_read["roll"] = 0
|
||||||
# 厚积薄发满层必中(迭代 12 T5 蓄力流——spike ready 时 discount 恒全额、暴击面走倍乘不加掷)
|
# 厚积薄发满层必中(迭代 12 T5 蓄力流——spike ready 时 discount 恒全额、暴击面走倍乘不加掷)
|
||||||
if int(relic_flags.get("spike_mult", 0)) > 0 and int(relic_flags.get("charge_layers", 0)) >= 3:
|
if int(feat_flags.get("spike_mult", 0)) > 0 and int(feat_flags.get("charge_layers", 0)) >= 3:
|
||||||
discount = 100
|
discount = 100
|
||||||
# 猎弱本能(迭代 12 T4 穿刺流——对轻甲敌折扣档改善 value 档:对冲 pierce 列 L1 −1 开局罚;
|
# 猎弱本能(迭代 12 T4 穿刺流——对轻甲敌折扣档改善 value 档:对冲 pierce 列 L1 −1 开局罚;
|
||||||
# 改善 = 折扣表向全额方向移档,nat 最大面/已全额不重复)
|
# 改善 = 折扣表向全额方向移档,nat 最大面/已全额不重复)
|
||||||
var shift := int(relic_flags.get("discount_shift", 0))
|
var shift := int(feat_flags.get("discount_shift", 0))
|
||||||
if shift > 0 and armor == "light" and discount < 100:
|
if shift > 0 and armor == "light" and discount < 100:
|
||||||
discount = _discount_shift_up(discount, shift)
|
discount = _discount_shift_up(discount, shift)
|
||||||
var cdelta: int = _counter_delta(atk_type, armor) + _relic_clamp_bonus(atk_type, armor)
|
var cdelta: int = _counter_delta(atk_type, armor) + _feat_clamp_bonus(atk_type, armor)
|
||||||
# 淬毒刃纹(迭代 11 同调批 T5——底稿 §3:对被克制敌人的穿刺/魔法直伤 +N;条件 = 克制成立 ×
|
# 淬毒刃纹(迭代 11 同调批 T5——底稿 §3:对被克制敌人的穿刺/魔法直伤 +N;条件 = 克制成立 ×
|
||||||
# 攻击类型限定,主角攻击面专属——棋盘克制直伤走 _resolve_board_typed 不吃本加成)
|
# 攻击类型限定,主角攻击面专属——棋盘克制直伤走 _resolve_board_typed 不吃本加成)
|
||||||
if attune_venom > 0 and (atk_type == "pierce" or atk_type == "magic") and cdelta > 0:
|
if attune_venom > 0 and (atk_type == "pierce" or atk_type == "magic") and cdelta > 0:
|
||||||
cdelta += attune_venom
|
cdelta += attune_venom
|
||||||
var base_value := float(t.base_value)
|
var base_value := float(t.base_value)
|
||||||
var trade_atk := int(relic_flags.get("hit_trade_atk", 0))
|
var trade_atk := int(feat_flags.get("hit_trade_atk", 0))
|
||||||
if trade_atk > 0:
|
if trade_atk > 0:
|
||||||
base_value *= 1.0 + float(trade_atk) / 100.0 # 重锤增伤面(交换的正向侧)
|
base_value *= 1.0 + float(trade_atk) / 100.0 # 重锤增伤面(交换的正向侧)
|
||||||
# 蓄力流(迭代 12 T5):整备军备「每层下轮首段 +1」(层数×value 加算)+厚积薄发满层(≥3)
|
# 蓄力流(迭代 12 T5):整备军备「每层下轮首段 +1」(层数×value 加算)+厚积薄发满层(≥3)
|
||||||
# spike 倍乘+强制必中(discount 100/crit 关闭掷骰面——方差买断的爆发出口)
|
# spike 倍乘+强制必中(discount 100/crit 关闭掷骰面——方差买断的爆发出口)
|
||||||
var charge_layers := int(relic_flags.get("charge_layers", 0))
|
var charge_layers := int(feat_flags.get("charge_layers", 0))
|
||||||
if charge_layers > 0:
|
if charge_layers > 0:
|
||||||
base_value += float(charge_layers * int(relic_flags.get("ap_carry_value", 0)))
|
base_value += float(charge_layers * int(feat_flags.get("ap_carry_value", 0)))
|
||||||
var spike_mult := int(relic_flags.get("spike_mult", 0))
|
var spike_mult := int(feat_flags.get("spike_mult", 0))
|
||||||
var spike_ready := spike_mult > 0 and charge_layers >= 3
|
var spike_ready := spike_mult > 0 and charge_layers >= 3
|
||||||
if spike_ready:
|
if spike_ready:
|
||||||
base_value *= float(spike_mult)
|
base_value *= float(spike_mult)
|
||||||
@@ -250,22 +250,22 @@ func _resolve_hero_attack(t: Dictionary) -> void:
|
|||||||
# 英雄侧 rider(迭代 12 T2 魔法流——enemy_skill rider 对称位补齐):后置必中附加,独立于掷点
|
# 英雄侧 rider(迭代 12 T2 魔法流——enemy_skill rider 对称位补齐):后置必中附加,独立于掷点
|
||||||
# 与乘区(奥术余烬「普攻命中后恒定结算」语义);档二(magic 4 件)rider 可暴击=当次 crit 时
|
# 与乘区(奥术余烬「普攻命中后恒定结算」语义);档二(magic 4 件)rider 可暴击=当次 crit 时
|
||||||
# rider 值乘暴击倍率。rider 不改变下限语义(landed 含 rider 后不再过 floor)。
|
# rider 值乘暴击倍率。rider 不改变下限语义(landed 含 rider 后不再过 floor)。
|
||||||
var hero_rider := int(relic_flags.get("rider_flat", 0))
|
var hero_rider := int(feat_flags.get("rider_flat", 0))
|
||||||
# 猎巢人(迭代 12 T3 标旗流——巢穴控制数转伤害光环):控制数 = 敌方 weaken(削弱)实例数
|
# 猎巢人(迭代 12 T3 标旗流——巢穴控制数转伤害光环):控制数 = 敌方 weaken(削弱)实例数
|
||||||
# 〔八型批 side-wide 状态行——标中旗控制巢穴的既有承载;原设计「对其中敌伤害」简化为全体
|
# 〔八型批 side-wide 状态行——标中旗控制巢穴的既有承载;原设计「对其中敌伤害」简化为全体
|
||||||
# 先手攻击附加值——台账披露待追认〕,每控制 1 巢 rider +Z。
|
# 先手攻击附加值——台账披露待追认〕,每控制 1 巢 rider +Z。
|
||||||
var nest_aura := int(relic_flags.get("nest_aura_value", 0))
|
var nest_aura := int(feat_flags.get("nest_aura_value", 0))
|
||||||
if nest_aura > 0:
|
if nest_aura > 0:
|
||||||
for eff in effects.query_active("enemy"):
|
for eff in effects.query_active("enemy"):
|
||||||
if String(eff.effect_id) == "weaken":
|
if String(eff.effect_id) == "weaken":
|
||||||
hero_rider += nest_aura
|
hero_rider += nest_aura
|
||||||
# 矿工的执拗(迭代 12 T4 普攻流——「排雷排得多好,打得就有多疼」):本轮安全开格 streak
|
# 矿工的执拗(迭代 12 T4 普攻流——「排雷排得多好,打得就有多疼」):本轮安全开格 streak
|
||||||
# 〔编排器授予段实时写入 relic_flags.dig_streak;踩雷清零〕×value,上限 cap。
|
# 〔编排器授予段实时写入 feat_flags.dig_streak;踩雷清零〕×value,上限 cap。
|
||||||
var rdig := int(relic_flags.get("round_dig_value", 0))
|
var rdig := int(feat_flags.get("round_dig_value", 0))
|
||||||
if rdig > 0:
|
if rdig > 0:
|
||||||
hero_rider += mini(int(relic_flags.get("round_dig_cap", 5)), int(relic_flags.get("dig_streak", 0)) * rdig)
|
hero_rider += mini(int(feat_flags.get("round_dig_cap", 5)), int(feat_flags.get("dig_streak", 0)) * rdig)
|
||||||
if hero_rider > 0:
|
if hero_rider > 0:
|
||||||
if crit and bool(relic_flags.get("rider_crit", false)):
|
if crit and bool(feat_flags.get("rider_crit", false)):
|
||||||
hero_rider = int(round(float(hero_rider) * float(db.settlement.crit_multiplier)))
|
hero_rider = int(round(float(hero_rider) * float(db.settlement.crit_multiplier)))
|
||||||
landed += hero_rider
|
landed += hero_rider
|
||||||
payload["hero_rider"] = hero_rider
|
payload["hero_rider"] = hero_rider
|
||||||
@@ -273,9 +273,9 @@ func _resolve_hero_attack(t: Dictionary) -> void:
|
|||||||
payload["killed"] = not target.alive # 击杀标记在扣血后取(原语义不变)
|
payload["killed"] = not target.alive # 击杀标记在扣血后取(原语义不变)
|
||||||
# 碎甲震击(迭代 12 T4 普攻流——命中重甲附加「护甲裂解」):复用 off_guard(enemy percent
|
# 碎甲震击(迭代 12 T4 普攻流——命中重甲附加「护甲裂解」):复用 off_guard(enemy percent
|
||||||
# +25 受伤加深——零新状态行);apply_effect 域闭/重复授予由状态容器自行守卫。
|
# +25 受伤加深——零新状态行);apply_effect 域闭/重复授予由状态容器自行守卫。
|
||||||
var crack := int(relic_flags.get("crack_value", 0))
|
var crack := int(feat_flags.get("crack_value", 0))
|
||||||
if crack > 0 and armor == "heavy":
|
if crack > 0 and armor == "heavy":
|
||||||
effects.apply_effect("off_guard", {"source_class": "relic_armor_crack"}, -1)
|
effects.apply_effect("off_guard", {"source_class": "feat_armor_crack"}, -1)
|
||||||
payload["cracked"] = true
|
payload["cracked"] = true
|
||||||
var label := _attack_label(String(t.display), crit, discount)
|
var label := _attack_label(String(t.display), crit, discount)
|
||||||
if hero_rider > 0:
|
if hero_rider > 0:
|
||||||
@@ -284,10 +284,10 @@ func _resolve_hero_attack(t: Dictionary) -> void:
|
|||||||
# 贯穿射击(迭代 12 T4 穿刺流——击杀溢出弹射):击杀时对下一存活敌以穿刺类型弹射
|
# 贯穿射击(迭代 12 T4 穿刺流——击杀溢出弹射):击杀时对下一存活敌以穿刺类型弹射
|
||||||
# 本体半额〔原设计「溢出伤害」追踪在单分量模型不可行——半额弹射 provisional 裁定披露待追认;
|
# 本体半额〔原设计「溢出伤害」追踪在单分量模型不可行——半额弹射 provisional 裁定披露待追认;
|
||||||
# 无次敌=弹射落空〕。B 模式入队段尾 flush 兑现;A 模式递归 resolve(目标存活守卫内建)。
|
# 无次敌=弹射落空〕。B 模式入队段尾 flush 兑现;A 模式递归 resolve(目标存活守卫内建)。
|
||||||
if bool(payload.get("killed", false)) and int(relic_flags.get("kill_overflow", 0)) > 0:
|
if bool(payload.get("killed", false)) and int(feat_flags.get("kill_overflow", 0)) > 0:
|
||||||
var next_target = units.default_target()
|
var next_target = units.default_target()
|
||||||
if next_target != null:
|
if next_target != null:
|
||||||
var of_t := open_ticket(DamageController.KIND_BOARD_TYPED, "relic_pierce_shot",
|
var of_t := open_ticket(DamageController.KIND_BOARD_TYPED, "feat_pierce_shot",
|
||||||
"贯穿射击", "board", maxi(int(db.settlement.damage_floor), int(landed / 2)),
|
"贯穿射击", "board", maxi(int(db.settlement.damage_floor), int(landed / 2)),
|
||||||
DamageController.TARGET_FRONT_ALIVE, int(t.round_index), t.origin_cell, "pierce")
|
DamageController.TARGET_FRONT_ALIVE, int(t.round_index), t.origin_cell, "pierce")
|
||||||
if policy == POLICY_SEGMENT:
|
if policy == POLICY_SEGMENT:
|
||||||
@@ -309,9 +309,9 @@ func _resolve_enemy_skill(t: Dictionary) -> void:
|
|||||||
var pooled: int = DamageMath.pool_damage(rolled, clamped_pool("hero_defense"), clamped_pool("enemy_output"))
|
var pooled: int = DamageMath.pool_damage(rolled, clamped_pool("hero_defense"), clamped_pool("enemy_output"))
|
||||||
var total: int = maxi(int(db.settlement.damage_floor), pooled) + int(t.rider) # rider 后置于伤害下限——独立必中加项,不进乘区
|
var total: int = maxi(int(db.settlement.damage_floor), pooled) + int(t.rider) # rider 后置于伤害下限——独立必中加项,不进乘区
|
||||||
# 铁壁整备(迭代 12 T5 蓄力流——每层下轮开场 S 点减伤;减后过下限保底)
|
# 铁壁整备(迭代 12 T5 蓄力流——每层下轮开场 S 点减伤;减后过下限保底)
|
||||||
var shield := int(relic_flags.get("shield_per_layer", 0)) * int(relic_flags.get("charge_layers", 0))
|
var shield := int(feat_flags.get("shield_per_layer", 0)) * int(feat_flags.get("charge_layers", 0))
|
||||||
# 稳健拓荒(迭代 12 T10——Fork posture B 侧 rule 键:受击减 1;两侧对称弱值=「不同强非强弱」)
|
# 稳健拓荒(迭代 12 T10——Fork posture B 侧 rule 键:受击减 1;两侧对称弱值=「不同强非强弱」)
|
||||||
if int(relic_flags.get("bulwark", 0)) > 0:
|
if int(feat_flags.get("bulwark", 0)) > 0:
|
||||||
shield += 1
|
shield += 1
|
||||||
if shield > 0:
|
if shield > 0:
|
||||||
total = maxi(int(db.settlement.damage_floor), total - shield)
|
total = maxi(int(db.settlement.damage_floor), total - shield)
|
||||||
@@ -337,7 +337,7 @@ func _resolve_board_typed(t: Dictionary) -> void:
|
|||||||
_ineffective_row(t, "无存活敌人")
|
_ineffective_row(t, "无存活敌人")
|
||||||
_outcomes[int(t.ticket_id)] = "zero"
|
_outcomes[int(t.ticket_id)] = "zero"
|
||||||
return
|
return
|
||||||
var delta: int = _counter_delta(String(t.attack_type), String(target.get("armor_type", "light"))) + _relic_clamp_bonus(String(t.attack_type), String(target.get("armor_type", "light")))
|
var delta: int = _counter_delta(String(t.attack_type), String(target.get("armor_type", "light"))) + _feat_clamp_bonus(String(t.attack_type), String(target.get("armor_type", "light")))
|
||||||
var dealt: int = DamageMath.counter_component(int(t.base_value), delta, int(db.settlement.damage_floor))
|
var dealt: int = DamageMath.counter_component(int(t.base_value), delta, int(db.settlement.damage_floor))
|
||||||
units.apply_damage(target, dealt)
|
units.apply_damage(target, dealt)
|
||||||
emit_row_cb.call("damage", String(t.source_class), t.origin_cell, int(t.round_index), "%s → %s" % [String(t.display), String(target.display_name)], dealt,
|
emit_row_cb.call("damage", String(t.source_class), t.origin_cell, int(t.round_index), "%s → %s" % [String(t.display), String(target.display_name)], dealt,
|
||||||
@@ -422,16 +422,16 @@ func _counter_delta(attack_type: String, armor: String) -> int:
|
|||||||
# 空旗=0 零扰动):魔网织者=magic vs magic(矩阵罚 −1 → 0/正);湛蓝誓约=magic vs heavy(+1 → +2);
|
# 空旗=0 零扰动):魔网织者=magic vs magic(矩阵罚 −1 → 0/正);湛蓝誓约=magic vs heavy(+1 → +2);
|
||||||
# 猎手刺剑=pierce vs light(+1——对轻甲敌穿刺提升,对重甲无增益=正负双修)。hero_attack 与
|
# 猎手刺剑=pierce vs light(+1——对轻甲敌穿刺提升,对重甲无增益=正负双修)。hero_attack 与
|
||||||
# board_typed 两消费面同享。
|
# board_typed 两消费面同享。
|
||||||
func _relic_clamp_bonus(attack_type: String, armor: String) -> int:
|
func _feat_clamp_bonus(attack_type: String, armor: String) -> int:
|
||||||
var bonus := 0
|
var bonus := 0
|
||||||
if attack_type == "magic":
|
if attack_type == "magic":
|
||||||
if armor == "magic":
|
if armor == "magic":
|
||||||
bonus += int(relic_flags.get("clamp_magic_magic", 0))
|
bonus += int(feat_flags.get("clamp_magic_magic", 0))
|
||||||
elif armor == "heavy":
|
elif armor == "heavy":
|
||||||
bonus += int(relic_flags.get("clamp_magic_heavy", 0))
|
bonus += int(feat_flags.get("clamp_magic_heavy", 0))
|
||||||
elif attack_type == "pierce":
|
elif attack_type == "pierce":
|
||||||
if armor == "light":
|
if armor == "light":
|
||||||
bonus += int(relic_flags.get("clamp_pierce_light", 0))
|
bonus += int(feat_flags.get("clamp_pierce_light", 0))
|
||||||
return bonus
|
return bonus
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,15 @@
|
|||||||
# 同流派标签 relic 计数 ≥2 → 档一(流派核心规则);≥4 → 档二(完全体);<2 = 散件(单体保底——
|
# 同流派标签 relic 计数 ≥2 → 档一(流派核心规则);≥4 → 档二(完全体);<2 = 散件(单体保底——
|
||||||
# 每件效果独立生效,档位只做质变不做启动,反例 B-2)。
|
# 每件效果独立生效,档位只做质变不做启动,反例 B-2)。
|
||||||
# 纯函数零 await 零 RNG 零副作用(Attunement 求值服务同款纪律);核心层零特判延伸——引擎只读
|
# 纯函数零 await 零 RNG 零副作用(Attunement 求值服务同款纪律);核心层零特判延伸——引擎只读
|
||||||
# relic_table 行集与持有集,接线点消费 tiers/hooks 投影。
|
# feat_table 行集与持有集,接线点消费 tiers/hooks 投影。
|
||||||
class_name RelicsEngine
|
class_name FeatsEngine
|
||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
|
|
||||||
## 档位求值:relic_ids 持有集 × 行集 → {archetype: tier}(tier ∈ 0/1/2;无行流派不出现)
|
## 档位求值:feat_ids 持有集 × 行集 → {archetype: tier}(tier ∈ 0/1/2;无行流派不出现)
|
||||||
static func tiers_for(relic_ids: Array, table: RelicTable) -> Dictionary:
|
static func tiers_for(feat_ids: Array, table: FeatTable) -> Dictionary:
|
||||||
var counts := {}
|
var counts := {}
|
||||||
for rid in relic_ids:
|
for rid in feat_ids:
|
||||||
var row: Dictionary = table.row_by_id(String(rid)) if table != null else {}
|
var row: Dictionary = table.row_by_id(String(rid)) if table != null else {}
|
||||||
if row.is_empty():
|
if row.is_empty():
|
||||||
continue
|
continue
|
||||||
@@ -30,16 +30,16 @@ static func tiers_for(relic_ids: Array, table: RelicTable) -> Dictionary:
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
## 挂点投影:持有集 → [{relic_id, hook, archetype, tier, effects}](表序稳定——同集合同投影,
|
## 挂点投影:持有集 → [{feat_id, hook, archetype, tier, effects}](表序稳定——同集合同投影,
|
||||||
## MC 逐位可复放承载)
|
## MC 逐位可复放承载)
|
||||||
static func hooks_for(relic_ids: Array, table: RelicTable) -> Array:
|
static func hooks_for(feat_ids: Array, table: FeatTable) -> Array:
|
||||||
var out: Array = []
|
var out: Array = []
|
||||||
for rid in relic_ids:
|
for rid in feat_ids:
|
||||||
var row: Dictionary = table.row_by_id(String(rid)) if table != null else {}
|
var row: Dictionary = table.row_by_id(String(rid)) if table != null else {}
|
||||||
if row.is_empty():
|
if row.is_empty():
|
||||||
continue
|
continue
|
||||||
out.append({
|
out.append({
|
||||||
"relic_id": String(row.get("relic_id", "")),
|
"feat_id": String(row.get("feat_id", "")),
|
||||||
"hook": String(row.get("hook", "")),
|
"hook": String(row.get("hook", "")),
|
||||||
"archetype": String(row.get("archetype", "")),
|
"archetype": String(row.get("archetype", "")),
|
||||||
"effects": (row.get("effects", {}) as Dictionary).duplicate(true),
|
"effects": (row.get("effects", {}) as Dictionary).duplicate(true),
|
||||||
File renamed without changes.
+25
-36
@@ -69,8 +69,9 @@ var _attune_charges: Dictionary = {} # 同调主动件充能账本(
|
|||||||
# 关卡末清空+快照计数入 level_finished 载荷)
|
# 关卡末清空+快照计数入 level_finished 载荷)
|
||||||
# ---- 商店共享池模式态(迭代 11 第②批 T3——缺省 legacy=固定三件加定价 off 对照承载,零 RNG 零池消费;TP0 令牌池换代)----
|
# ---- 商店共享池模式态(迭代 11 第②批 T3——缺省 legacy=固定三件加定价 off 对照承载,零 RNG 零池消费;TP0 令牌池换代)----
|
||||||
var _shop_pool_mode := false # pool 模式旗(set_shop_pool 注入时置位——AdventureRunner 按 params.shop_mode 下传)
|
var _shop_pool_mode := false # pool 模式旗(set_shop_pool 注入时置位——AdventureRunner 按 params.shop_mode 下传)
|
||||||
var _pool_deck: Array = [] # 共享池令牌库共享引用(AdventureRunner._bonus_deck——库存=剩余令牌数单点)
|
var _pool_deck: Array = [] # 共享池令牌库共享引用(AdventureRunner._curio_deck——库存=剩余令牌数单点)
|
||||||
var _pool_consume := Callable() # 池消费 handler(AdventureRunner._consume_pool_purchase——_apply_bonus_pick 同口写加算区+账本+摘牌)
|
var _pool_consume := Callable() # 池消费 handler(AdventureRunner._consume_pool_purchase——_apply_curio_pick 同口写加算区+账本+摘牌)
|
||||||
|
var _pool_ticket := Callable() # 门票展示判定 handler(T5——AdventureRunner._feat_ticket_consider;节点与商店共享计数)
|
||||||
var _shop_shelf: Array = [] # 当前货架(进店抽 3/买后全重抽——令牌字典 {"kind","tier"} 序,同 kind 去重每 kind 至多一卡)
|
var _shop_shelf: Array = [] # 当前货架(进店抽 3/买后全重抽——令牌字典 {"kind","tier"} 序,同 kind 去重每 kind 至多一卡)
|
||||||
var _shop_visit_buys := 0 # 本次进店已购次数(A 链定价输入——离开清零)
|
var _shop_visit_buys := 0 # 本次进店已购次数(A 链定价输入——离开清零)
|
||||||
var _shop_paid_refreshes := 0 # 本次进店已付费刷新次数(MC-1——GP 刷新价曲线输入,离开清零)
|
var _shop_paid_refreshes := 0 # 本次进店已付费刷新次数(MC-1——GP 刷新价曲线输入,离开清零)
|
||||||
@@ -479,7 +480,7 @@ func _start_battle(node: Dictionary) -> bool:
|
|||||||
"hero_armor_class": effective_ac(), "hero_ap_bonus": effective_ap_bonus(),
|
"hero_armor_class": effective_ac(), "hero_ap_bonus": effective_ap_bonus(),
|
||||||
"attunement": _attunement,
|
"attunement": _attunement,
|
||||||
"attune_charges": _attune_charges,
|
"attune_charges": _attune_charges,
|
||||||
"relics": _relics,
|
"feats": _feats,
|
||||||
"tree_rules": _tree_rules,
|
"tree_rules": _tree_rules,
|
||||||
"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)))
|
||||||
@@ -591,18 +592,18 @@ func set_attunement(ids: Array) -> void:
|
|||||||
|
|
||||||
|
|
||||||
# 特性持有集注入(迭代 12 T1——AdventureRunner._start_current_level 唯一调用方;begin_battle
|
# 特性持有集注入(迭代 12 T1——AdventureRunner._start_current_level 唯一调用方;begin_battle
|
||||||
# enc.relics 携带进战斗求值;**共享引用不复制**——关内奖励节点兑付的 relic 须当关后续战斗生效
|
# enc.feats 携带进战斗求值;**共享引用不复制**——关内奖励节点兑付的 relic 须当关后续战斗生效
|
||||||
# (attune_charges 共享账本同款);空集=零扰动)
|
# (attune_charges 共享账本同款);空集=零扰动)
|
||||||
var _relics: Array = []
|
var _feats: Array = []
|
||||||
|
|
||||||
|
|
||||||
func set_relics(ids: Array) -> void:
|
func set_feats(ids: Array) -> void:
|
||||||
_relics = ids
|
_feats = ids
|
||||||
|
|
||||||
|
|
||||||
# 特性持有集读面(迭代 12 呈现补齐——HUD C 区特性段数据源)
|
# 特性持有集读面(迭代 12 呈现补齐——HUD C 区特性段数据源)
|
||||||
func relics_held() -> Array:
|
func feats_held() -> Array:
|
||||||
return _relics.duplicate()
|
return _feats.duplicate()
|
||||||
|
|
||||||
|
|
||||||
# 树 rule 键集注入(迭代 12 T10——begin_battle enc.tree_rules 携带;Fork 激活侧 effects)
|
# 树 rule 键集注入(迭代 12 T10——begin_battle enc.tree_rules 携带;Fork 激活侧 effects)
|
||||||
@@ -638,9 +639,9 @@ func grant_treasure_item(item_id: String, source: String) -> Dictionary:
|
|||||||
|
|
||||||
# 三选一 GP 袋件入账(迭代 10——钱包收口扩「奖励入账」入口;复用战斗完成同一入账原语,
|
# 三选一 GP 袋件入账(迭代 10——钱包收口扩「奖励入账」入口;复用战斗完成同一入账原语,
|
||||||
# wallet_changed 载荷带 source 供呈现归因)
|
# wallet_changed 载荷带 source 供呈现归因)
|
||||||
func grant_bonus_gp(amount: int) -> void:
|
func grant_curio_gp(amount: int) -> void:
|
||||||
gp = maxi(0, gp + amount)
|
gp = maxi(0, gp + amount)
|
||||||
wallet_changed.emit("gain", gp, {"amount": amount, "source": "bonus_pick"})
|
wallet_changed.emit("gain", gp, {"amount": amount, "source": "curio_pick"})
|
||||||
|
|
||||||
|
|
||||||
# ---- 玩家输入转发(呈现层只与 runner 对话)----
|
# ---- 玩家输入转发(呈现层只与 runner 对话)----
|
||||||
@@ -735,9 +736,10 @@ func player_request_refresh() -> bool:
|
|||||||
|
|
||||||
# 注入(AdventureRunner._start_current_level 唯一调用方——pool 模式专用):令牌库共享引用 +
|
# 注入(AdventureRunner._start_current_level 唯一调用方——pool 模式专用):令牌库共享引用 +
|
||||||
# 池消费 handler。legacy 模式不注入(_shop_pool_mode 恒 false——off 态零 RNG 零池消费零扰动)。
|
# 池消费 handler。legacy 模式不注入(_shop_pool_mode 恒 false——off 态零 RNG 零池消费零扰动)。
|
||||||
func set_shop_pool(deck_ref: Array, consume_handler: Callable) -> void:
|
func set_shop_pool(deck_ref: Array, consume_handler: Callable, ticket_consider: Callable = Callable()) -> void:
|
||||||
_pool_deck = deck_ref
|
_pool_deck = deck_ref
|
||||||
_pool_consume = consume_handler
|
_pool_consume = consume_handler
|
||||||
|
_pool_ticket = ticket_consider # T5——门票展示判定(AdventureRunner._feat_ticket_consider;同流取数复用 shop_draw scope)
|
||||||
_shop_pool_mode = true
|
_shop_pool_mode = true
|
||||||
|
|
||||||
|
|
||||||
@@ -779,26 +781,13 @@ func _shop_draw_shelf() -> void:
|
|||||||
_shop_shelf.append(tok)
|
_shop_shelf.append(tok)
|
||||||
if _shop_shelf.size() >= 3:
|
if _shop_shelf.size() >= 3:
|
||||||
break
|
break
|
||||||
# 商店保底(迭代 12 T6——「本关变异交互件必刷」简化口径:货架零特性时**挤掉最后一张普通卡**
|
# T5 门票注入(非 Boss 渠道唯一专长入口——货架与节点共享保底计数;判定含 pity 记账,替换末位占一席。
|
||||||
# 换吞噬流件〔地形交互件广义映射——按关变异精细绑定归数值批,台账披露待追认〕;占位制货架恒
|
# 取数复用本函数 shop_draw scope 的 stream——零新 scope,确定性序列不漂移)
|
||||||
# ≤3;池内无 devour 令牌=不保底合法态)
|
if _pool_ticket.is_valid() and not _shop_shelf.is_empty() and bool(_pool_ticket.call(stream)):
|
||||||
var has_relic := false
|
_shop_shelf[_shop_shelf.size() - 1] = {"kind": "feat_ticket", "tier": "rare"}
|
||||||
for tok in _shop_shelf:
|
# 商店保底退役(迭代 15 卡② T4——渠道轴定位裁决:商店渠道只出属性值提升+资源,专长在非 Boss
|
||||||
if String((tok as Dictionary).get("kind", "")) == "relic":
|
# 渠道只以门票形态出现(奇物.md v2.7/A4 断言)。迭代 12 T6「货架零特性挤卡换吞噬流件」简化口径
|
||||||
has_relic = true
|
# 与渠道白名单直接冲突(devour=规则改写型=专长系),随渠道分离退役;专长商店入口=T5 门票。
|
||||||
break
|
|
||||||
if not has_relic and not _shop_shelf.is_empty():
|
|
||||||
for row in db.relic_rows():
|
|
||||||
if String(row.get("archetype", "")) != "devour":
|
|
||||||
continue
|
|
||||||
var rid := String(row.get("relic_id", ""))
|
|
||||||
for t in _pool_deck:
|
|
||||||
var tok2: Dictionary = t as Dictionary
|
|
||||||
if String(tok2.get("kind", "")) == "relic" and String(tok2.get("relic_id", "")) == rid:
|
|
||||||
_shop_shelf[_shop_shelf.size() - 1] = {"kind": "relic", "tier": String(tok2.get("tier", "")), "relic_id": rid}
|
|
||||||
break
|
|
||||||
if String((_shop_shelf[_shop_shelf.size() - 1] as Dictionary).get("kind", "")) == "relic":
|
|
||||||
break # 换一张即止
|
|
||||||
|
|
||||||
|
|
||||||
# 库存余量(TP0——令牌库内该 kind 剩余枚数单点读;未注入/防御读 = 0)
|
# 库存余量(TP0——令牌库内该 kind 剩余枚数单点读;未注入/防御读 = 0)
|
||||||
@@ -834,15 +823,15 @@ func shop_shelf() -> Array:
|
|||||||
var tier := String((tok as Dictionary).get("tier", ""))
|
var tier := String((tok as Dictionary).get("tier", ""))
|
||||||
var row: Dictionary = db.shop_pool_row(kind)
|
var row: Dictionary = db.shop_pool_row(kind)
|
||||||
var display := String(row.get("display_name", kind))
|
var display := String(row.get("display_name", kind))
|
||||||
var value := int(db.adventure.feature_value(kind, tier))
|
var value := int(db.adventure.curio_value(kind, tier))
|
||||||
if row.is_empty() and kind == "relic":
|
if row.is_empty() and kind == "relic":
|
||||||
var rrow: Dictionary = db.relic_row(String(tok.get("relic_id", "")))
|
var rrow: Dictionary = db.feat_row(String(tok.get("feat_id", "")))
|
||||||
display = String(rrow.get("display_name", "特性"))
|
display = String(rrow.get("display_name", "特性"))
|
||||||
value = 0
|
value = 0
|
||||||
out.append({
|
out.append({
|
||||||
"kind_id": kind,
|
"kind_id": kind,
|
||||||
"tier": tier,
|
"tier": tier,
|
||||||
"relic_id": String(tok.get("relic_id", "")),
|
"feat_id": String(tok.get("feat_id", "")),
|
||||||
"display_name": display,
|
"display_name": display,
|
||||||
"value": value,
|
"value": value,
|
||||||
"price": pool_price(kind),
|
"price": pool_price(kind),
|
||||||
@@ -877,7 +866,7 @@ func shop_refresh() -> bool:
|
|||||||
|
|
||||||
|
|
||||||
# 购买(pool 模式唯一购买口):在架(kind→在架令牌)/余额前置校验零副作用 → handler 消费在架
|
# 购买(pool 模式唯一购买口):在架(kind→在架令牌)/余额前置校验零副作用 → handler 消费在架
|
||||||
# 令牌本体(kind+tier——_apply_bonus_pick 同口写加算区+账本+摘牌,跨渠道共库)→ 出账 → 进店购买
|
# 令牌本体(kind+tier——_apply_curio_pick 同口写加算区+账本+摘牌,跨渠道共库)→ 出账 → 进店购买
|
||||||
# 次计数 → wallet 事件 → 三件全重抽(未买不保留——重抽绑购买,无独立刷新动作)
|
# 次计数 → wallet 事件 → 三件全重抽(未买不保留——重抽绑购买,无独立刷新动作)
|
||||||
func buy_pool_item(kind: String) -> bool:
|
func buy_pool_item(kind: String) -> bool:
|
||||||
var shelf_tok := {}
|
var shelf_tok := {}
|
||||||
|
|||||||
@@ -13,18 +13,18 @@
|
|||||||
class_name AdventureTable
|
class_name AdventureTable
|
||||||
extends Resource
|
extends Resource
|
||||||
|
|
||||||
## 概念指路:本表的属性值提升与资源,与 relics.tres 的专长,同属一个对象——
|
## 概念指路:本表的属性值提升与资源,与 feats.tres 的专长,同属一个对象——
|
||||||
## 「奇物」(一次冒险内各渠道给予玩家的增益总称)。定义见 design/gdd/systems/冒险循环/奇物.md。
|
## 「奇物」(一次冒险内各渠道给予玩家的增益总称)。定义见 design/gdd/systems/冒险循环/奇物.md。
|
||||||
## 它们是它的分类轴上的枚举(专长 / 属性值提升 / 资源),不是三套系统;
|
## 它们是它的分类轴上的枚举(专长 / 属性值提升 / 资源),不是三套系统;
|
||||||
## 本表承载属性值提升与资源的种类配额与件值。
|
## 本表承载属性值提升与资源的种类配额与件值。
|
||||||
## (2026-10-05 命名统一 + v2.0 改名:旧称「数值件」→ 属性提升;顶层「特性」→「奖励」→「奇物」。
|
## (2026-10-05 命名统一 + v2.0 改名:旧称「数值件」→ 属性提升;顶层「特性」→「奖励」→「奇物」。
|
||||||
## 常量与字段随之前缀 feature_*,**尚未**随顶层改名换到 reward_*——见 06-待办清单。
|
## 常量与字段随之前缀 curio_*,**尚未**随顶层改名换到 reward_*——见 06-待办清单。
|
||||||
## 分类轴三个枚举:属性值提升〔生命上限 / AC / 攻击〕;资源〔行动点上限 /
|
## 分类轴三个枚举:属性值提升〔生命上限 / AC / 攻击〕;资源〔行动点上限 /
|
||||||
## 刷新次数 / 金币 / 回复,命名待定〕——本表两类混编;刷新次数与专长获取两项尚未入表。
|
## 刷新次数 / 金币 / 回复,命名待定〕——本表两类混编;刷新次数与专长获取两项尚未入表。
|
||||||
## 类内异质性:金币与回复是即时兑付,不进持有集、不持续生效。)
|
## 类内异质性:金币与回复是即时兑付,不进持有集、不持续生效。)
|
||||||
## 三选一提升池 kind 词表(封闭——断点附随件与牌库剔除态依赖它;扩件走本表+registry 修订)。
|
## 三选一提升池 kind 词表(封闭——断点附随件与牌库剔除态依赖它;扩件走本表+registry 修订)。
|
||||||
## treasure_item 池种退役(迭代 11 第①批 T7——同调批出池);heal 恢复件新入池(v2.2 令牌池追认 b)。
|
## treasure_item 池种退役(迭代 11 第①批 T7——同调批出池);heal 恢复件新入池(v2.2 令牌池追认 b)。
|
||||||
const FEATURE_KINDS: Array[String] = ["attack", "max_hp", "armor", "gp", "ap", "heal"]
|
const CURIO_KINDS: Array[String] = ["attack", "max_hp", "armor", "gp", "ap", "heal", "refresh"] # T6 扩第七类(refresh=资源类成员——作为奇物授予的三选一重抽次数,作用对象=三选一界面非角色;见 registry refresh_boundary)
|
||||||
## 令牌档位词表(v2.2 三档位——稀有度命名对齐 MC 策略判读序 316-f:1 级最稀有=rarest 优先)
|
## 令牌档位词表(v2.2 三档位——稀有度命名对齐 MC 策略判读序 316-f:1 级最稀有=rarest 优先)
|
||||||
const TIERS: Array[String] = ["common", "rare", "rarest"]
|
const TIERS: Array[String] = ["common", "rare", "rarest"]
|
||||||
const TIER_COMMON := "common"
|
const TIER_COMMON := "common"
|
||||||
@@ -39,65 +39,78 @@ const TIER_QUOTAS := {"common": 70, "rare": 25, "rarest": 3}
|
|||||||
@export var clear_heal_pct: int = 20 # [0,100] knob——回复生效上限的百分比,封顶上限不回满
|
@export var clear_heal_pct: int = 20 # [0,100] knob——回复生效上限的百分比,封顶上限不回满
|
||||||
|
|
||||||
# —— 三选一候选数(2026-10-02 修复批 T2 魔法数结构化)——
|
# —— 三选一候选数(2026-10-02 修复批 T2 魔法数结构化)——
|
||||||
@export var feature_candidate_count: int = 3 # [1,6] knob——令牌洗牌不放回抽取张数上界
|
@export var curio_candidate_count: int = 3 # [1,6] knob——令牌洗牌不放回抽取张数上界
|
||||||
@export var shop_refresh_free: int = 0 # [0,9] knob(MC-1 provisional——进店免费刷新次数;基础 0=Q2 裁,天赋增益位预留)
|
@export var shop_refresh_free: int = 0 # [0,9] knob(MC-1 provisional——进店免费刷新次数;基础 0=Q2 裁,天赋增益位预留)
|
||||||
@export var shop_refresh_cost_base: int = 20 # [1,999] knob(MC-1 provisional——GP 刷新基价)
|
@export var shop_refresh_cost_base: int = 20 # [1,999] knob(MC-1 provisional——GP 刷新基价)
|
||||||
@export var shop_refresh_cost_curve: float = 1.8 # [1.0,4.0] knob(MC-1 provisional——按已付费刷新数幂曲线,A 链同族)
|
@export var shop_refresh_cost_curve: float = 1.8 # [1.0,4.0] knob(MC-1 provisional——按已付费刷新数幂曲线,A 链同族)
|
||||||
|
|
||||||
# —— 令牌配额(kind × tier 计数;未列组合 = 0——heal 只入稀有/ap 只入罕见,v2.2 档位构成)——
|
# —— 令牌配额(kind × tier 计数;未列组合 = 0——heal 只入稀有/ap 只入罕见,v2.2 档位构成)——
|
||||||
@export var feature_quota_attack_common: int = 18 # [0,99]
|
@export var curio_quota_attack_common: int = 18 # [0,99]
|
||||||
@export var feature_quota_attack_rare: int = 5 # [0,99]
|
@export var curio_quota_attack_rare: int = 5 # [0,99]
|
||||||
@export var feature_quota_max_hp_common: int = 17 # [0,99]
|
@export var curio_quota_max_hp_common: int = 17 # [0,99]
|
||||||
@export var feature_quota_max_hp_rare: int = 5 # [0,99]
|
@export var curio_quota_max_hp_rare: int = 5 # [0,99]
|
||||||
@export var feature_quota_armor_common: int = 17 # [0,99]
|
@export var curio_quota_armor_common: int = 17 # [0,99]
|
||||||
@export var feature_quota_armor_rare: int = 5 # [0,99]
|
@export var curio_quota_armor_rare: int = 5 # [0,99]
|
||||||
@export var feature_quota_gp_common: int = 18 # [0,99]
|
@export var curio_quota_gp_common: int = 18 # [0,99]
|
||||||
@export var feature_quota_gp_rare: int = 5 # [0,99]
|
@export var curio_quota_gp_rare: int = 5 # [0,99]
|
||||||
@export var feature_quota_heal_rare: int = 5 # [0,99]——恢复件(新入池种类,当即回血)
|
@export var curio_quota_heal_rare: int = 3 # [0,99]——恢复件(原 5;T6 挤占 2 席位予 refresh——用户 2026-10-05 裁方案B,总量 98 锚不变)
|
||||||
@export var feature_quota_ap_rarest: int = 3 # [0,99]——AP 件(罕见档;道具件 2 席位随池种退役收窄)
|
@export var curio_quota_refresh_rare: int = 2 # [0,99]——刷新次数件(T6 新入池资源类成员;provisional 归曲线调参批)
|
||||||
|
@export var curio_quota_ap_rarest: int = 3 # [0,99]——AP 件(罕见档;道具件 2 席位随池种退役收窄)
|
||||||
|
|
||||||
# —— 令牌件值(小/大数值对——v2.2 追认 b 定稿:20/50、+1/+2、+1/+2、+5/+10、恢复+6、AP+1)——
|
# —— 令牌件值(小/大数值对——v2.2 追认 b 定稿:20/50、+1/+2、+1/+2、+5/+10、恢复+6、AP+1)——
|
||||||
@export var feature_value_attack_common: int = 1 # [0,999] knob——小增伤
|
@export var curio_value_attack_common: int = 1 # [0,999] knob——小增伤
|
||||||
@export var feature_value_attack_rare: int = 2 # [0,999] knob——大增伤
|
@export var curio_value_attack_rare: int = 2 # [0,999] knob——大增伤
|
||||||
@export var feature_value_max_hp_common: int = 5 # [0,999] knob——小生命
|
@export var curio_value_max_hp_common: int = 5 # [0,999] knob——小生命
|
||||||
@export var feature_value_max_hp_rare: int = 10 # [0,999] knob——大生命
|
@export var curio_value_max_hp_rare: int = 10 # [0,999] knob——大生命
|
||||||
@export var feature_value_armor_common: int = 1 # [0,999] knob——小护甲
|
@export var curio_value_armor_common: int = 1 # [0,999] knob——小护甲
|
||||||
@export var feature_value_armor_rare: int = 2 # [0,999] knob——大护甲
|
@export var curio_value_armor_rare: int = 2 # [0,999] knob——大护甲
|
||||||
@export var feature_value_gp_common: int = 20 # [0,999] knob——小GP
|
@export var curio_value_gp_common: int = 20 # [0,999] knob——小GP
|
||||||
@export var feature_value_gp_rare: int = 50 # [0,999] knob——大GP
|
@export var curio_value_gp_rare: int = 50 # [0,999] knob——大GP
|
||||||
@export var feature_value_heal_rare: int = 6 # [0,999] knob——恢复(当即回血,封顶生效上限)
|
@export var curio_value_heal_rare: int = 6 # [0,999] knob——恢复(当即回血,封顶生效上限)
|
||||||
@export var feature_value_ap_rarest: int = 1 # [0,10] knob——AP 件每场战斗首轮行动点基线增量
|
@export var curio_value_refresh_rare: int = 1 # [0,9] knob(provisional)——刷新次数件兑付值(三选一重抽次数 +N——_attune_refresh_x 增量,不进加算区)
|
||||||
|
@export var curio_value_ap_rarest: int = 1 # [0,10] knob——AP 件每场战斗首轮行动点基线增量
|
||||||
|
|
||||||
|
# —— 专长获取门票(迭代 15 卡② T5——非 Boss 渠道的专长入口形态,奇物.md v2.7 渠道轴定位;
|
||||||
|
# 四口径契约④:拿到即开不可囤积/罕见只在概率不在价格/一局限购一次/计步保底连续 5 展示未出
|
||||||
|
# 第 6 必出(计数=展示次数、刷新重展计入、节点与商店共享、触发清零、限购满不再触发)。
|
||||||
|
# 四参数全部 provisional 归曲线调参批;limit/pity_step 系口径定死值调参批可复议)——
|
||||||
|
@export var feat_ticket_chance: int = 20 # [0,100] knob(provisional)——每次非 Boss 渠道展示门票出现概率 %
|
||||||
|
@export var feat_ticket_quota: int = 2 # [0,9] knob(provisional)——每局门票出现次数上限
|
||||||
|
@export var feat_ticket_limit: int = 1 # [1,9] knob(口径定死=1)——一局限购(选中/买下)次数
|
||||||
|
@export var feat_ticket_pity_step: int = 5 # [1,99] knob(口径定死=5)——保底步数(连续未出展示次数)
|
||||||
|
|
||||||
|
|
||||||
## 令牌配额读数(未列 kind×tier 组合 = 0——heal 无普通档/ap 无普通稀有档按 v2.2 构成)
|
## 令牌配额读数(未列 kind×tier 组合 = 0——heal 无普通档/ap 无普通稀有档按 v2.2 构成)
|
||||||
func feature_quota(kind: String, tier: String) -> int:
|
func curio_quota(kind: String, tier: String) -> int:
|
||||||
match [kind, tier]:
|
match [kind, tier]:
|
||||||
["attack", TIER_COMMON]: return feature_quota_attack_common
|
["attack", TIER_COMMON]: return curio_quota_attack_common
|
||||||
["attack", TIER_RARE]: return feature_quota_attack_rare
|
["attack", TIER_RARE]: return curio_quota_attack_rare
|
||||||
["max_hp", TIER_COMMON]: return feature_quota_max_hp_common
|
["max_hp", TIER_COMMON]: return curio_quota_max_hp_common
|
||||||
["max_hp", TIER_RARE]: return feature_quota_max_hp_rare
|
["max_hp", TIER_RARE]: return curio_quota_max_hp_rare
|
||||||
["armor", TIER_COMMON]: return feature_quota_armor_common
|
["armor", TIER_COMMON]: return curio_quota_armor_common
|
||||||
["armor", TIER_RARE]: return feature_quota_armor_rare
|
["armor", TIER_RARE]: return curio_quota_armor_rare
|
||||||
["gp", TIER_COMMON]: return feature_quota_gp_common
|
["gp", TIER_COMMON]: return curio_quota_gp_common
|
||||||
["gp", TIER_RARE]: return feature_quota_gp_rare
|
["gp", TIER_RARE]: return curio_quota_gp_rare
|
||||||
["heal", TIER_RARE]: return feature_quota_heal_rare
|
["heal", TIER_RARE]: return curio_quota_heal_rare
|
||||||
["ap", TIER_RAREST]: return feature_quota_ap_rarest
|
["refresh", TIER_RARE]: return curio_quota_refresh_rare
|
||||||
|
["ap", TIER_RAREST]: return curio_quota_ap_rarest
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
## 令牌件值(kind × tier;v2.2 数值对——小/大两档)
|
## 令牌件值(kind × tier;v2.2 数值对——小/大两档)
|
||||||
func feature_value(kind: String, tier: String = TIER_COMMON) -> int:
|
func curio_value(kind: String, tier: String = TIER_COMMON) -> int:
|
||||||
match [kind, tier]:
|
match [kind, tier]:
|
||||||
["attack", TIER_COMMON]: return feature_value_attack_common
|
["attack", TIER_COMMON]: return curio_value_attack_common
|
||||||
["attack", TIER_RARE]: return feature_value_attack_rare
|
["attack", TIER_RARE]: return curio_value_attack_rare
|
||||||
["max_hp", TIER_COMMON]: return feature_value_max_hp_common
|
["max_hp", TIER_COMMON]: return curio_value_max_hp_common
|
||||||
["max_hp", TIER_RARE]: return feature_value_max_hp_rare
|
["max_hp", TIER_RARE]: return curio_value_max_hp_rare
|
||||||
["armor", TIER_COMMON]: return feature_value_armor_common
|
["armor", TIER_COMMON]: return curio_value_armor_common
|
||||||
["armor", TIER_RARE]: return feature_value_armor_rare
|
["armor", TIER_RARE]: return curio_value_armor_rare
|
||||||
["gp", TIER_COMMON]: return feature_value_gp_common
|
["gp", TIER_COMMON]: return curio_value_gp_common
|
||||||
["gp", TIER_RARE]: return feature_value_gp_rare
|
["gp", TIER_RARE]: return curio_value_gp_rare
|
||||||
["heal", TIER_RARE]: return feature_value_heal_rare
|
["heal", TIER_RARE]: return curio_value_heal_rare
|
||||||
["ap", TIER_RAREST]: return feature_value_ap_rarest
|
["refresh", TIER_RARE]: return curio_value_refresh_rare
|
||||||
|
["ap", TIER_RAREST]: return curio_value_ap_rarest
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -113,8 +126,8 @@ func tier_rank(tier: String) -> int:
|
|||||||
## 档位令牌总数(货架/校验读面)
|
## 档位令牌总数(货架/校验读面)
|
||||||
func tier_total(tier: String) -> int:
|
func tier_total(tier: String) -> int:
|
||||||
var total := 0
|
var total := 0
|
||||||
for kind in FEATURE_KINDS:
|
for kind in CURIO_KINDS:
|
||||||
total += feature_quota(kind, tier)
|
total += curio_quota(kind, tier)
|
||||||
return total
|
return total
|
||||||
|
|
||||||
|
|
||||||
@@ -125,20 +138,23 @@ func deck_total() -> int:
|
|||||||
|
|
||||||
func validate() -> Array:
|
func validate() -> Array:
|
||||||
var errs: Array[Dictionary] = []
|
var errs: Array[Dictionary] = []
|
||||||
|
for kv in [["feat_ticket_chance", feat_ticket_chance, 0, 100], ["feat_ticket_quota", feat_ticket_quota, 0, 9], ["feat_ticket_limit", feat_ticket_limit, 1, 9], ["feat_ticket_pity_step", feat_ticket_pity_step, 1, 99]]:
|
||||||
|
if int(kv[1]) < int(kv[2]) or int(kv[1]) > int(kv[3]):
|
||||||
|
errs.append({"category": "domain", "message": "%s 出域 [%d,%d]: %d" % [kv[0], kv[2], kv[3], kv[1]]})
|
||||||
if clear_heal_pct < 0 or clear_heal_pct > 100:
|
if clear_heal_pct < 0 or clear_heal_pct > 100:
|
||||||
errs.append({"category": "domain", "message": "clear_heal_pct 越域 [0,100]: %d" % clear_heal_pct})
|
errs.append({"category": "domain", "message": "clear_heal_pct 越域 [0,100]: %d" % clear_heal_pct})
|
||||||
for kind in FEATURE_KINDS:
|
for kind in CURIO_KINDS:
|
||||||
for tier in TIERS:
|
for tier in TIERS:
|
||||||
var c := feature_quota(kind, tier)
|
var c := curio_quota(kind, tier)
|
||||||
if c < 0 or c > 99:
|
if c < 0 or c > 99:
|
||||||
errs.append({"category": "domain", "message": "token_count(%s,%s) 越域 [0,99]: %d" % [kind, tier, c]})
|
errs.append({"category": "domain", "message": "token_count(%s,%s) 越域 [0,99]: %d" % [kind, tier, c]})
|
||||||
var v := feature_value(kind, tier)
|
var v := curio_value(kind, tier)
|
||||||
if v < 0 or v > 999:
|
if v < 0 or v > 999:
|
||||||
errs.append({"category": "domain", "message": "feature_value(%s,%s) 越域 [0,999]: %d" % [kind, tier, v]})
|
errs.append({"category": "domain", "message": "curio_value(%s,%s) 越域 [0,999]: %d" % [kind, tier, v]})
|
||||||
for tier in TIERS:
|
for tier in TIERS:
|
||||||
var total := tier_total(tier)
|
var total := tier_total(tier)
|
||||||
if total != int(TIER_QUOTAS.get(tier, -1)):
|
if total != int(TIER_QUOTAS.get(tier, -1)):
|
||||||
errs.append({"category": "domain", "message": "档位配额结构失锚(v2.2 追认 b——%s 应=%d 实=%d;改总量须重裁)" % [tier, int(TIER_QUOTAS.get(tier, -1)), total]})
|
errs.append({"category": "domain", "message": "档位配额结构失锚(v2.2 追认 b——%s 应=%d 实=%d;改总量须重裁)" % [tier, int(TIER_QUOTAS.get(tier, -1)), total]})
|
||||||
if feature_candidate_count < 1 or feature_candidate_count > 6:
|
if curio_candidate_count < 1 or curio_candidate_count > 6:
|
||||||
errs.append({"category": "domain", "message": "feature_candidate_count 越域 [1,6]: %d" % feature_candidate_count})
|
errs.append({"category": "domain", "message": "curio_candidate_count 越域 [1,6]: %d" % curio_candidate_count})
|
||||||
return errs
|
return errs
|
||||||
@@ -6,25 +6,25 @@
|
|||||||
# 「奇物」(一次冒险内各渠道给予玩家的增益总称)。定义见 design/gdd/systems/冒险循环/奇物.md。
|
# 「奇物」(一次冒险内各渠道给予玩家的增益总称)。定义见 design/gdd/systems/冒险循环/奇物.md。
|
||||||
# 它们是它分类轴上的枚举(专长 / 属性值提升 / 资源),不是三套系统;本表承载「专长」(专长 Feat)的逐项定义。
|
# 它们是它分类轴上的枚举(专长 / 属性值提升 / 资源),不是三套系统;本表承载「专长」(专长 Feat)的逐项定义。
|
||||||
# (2026-10-05 命名统一 + 分类重构:旧称「流派件」→「规则改写型特性」→ 现定名「专长」;
|
# (2026-10-05 命名统一 + 分类重构:旧称「流派件」→「规则改写型特性」→ 现定名「专长」;
|
||||||
# 表名与 relic_id 沿用历史命名,与统一词族对齐归后续批次——见 06-待办清单「命名统一」条目。
|
# 表名与 feat_id 沿用历史命名,与统一词族对齐归后续批次——见 06-待办清单「命名统一」条目。
|
||||||
# 目标渠道:专长走关底 Boss 击败后专属三选一(不新增节点类型),见奇物文档 2.1 节。)
|
# 目标渠道:专长走关底 Boss 击败后专属三选一(不新增节点类型),见奇物文档 2.1 节。)
|
||||||
# 数据配置 RelicTable schema(奇物·专长定义表;行 = Dictionary 键 relic_id,Rule 4 豁免
|
# 数据配置 FeatTable schema(奇物·专长定义表;行 = Dictionary 键 feat_id,Rule 4 豁免
|
||||||
# 定义表——TreasureItemTable 先例)。
|
# 定义表——TreasureItemTable 先例)。
|
||||||
# 行字段:relic_id 主键 / display_name / rarity ∈ {common, rare, rarest}(对齐令牌池三档命名——
|
# 行字段:feat_id 主键 / display_name / rarity ∈ {common, rare, rarest}(对齐令牌池三档命名——
|
||||||
# 投放档映射:common/rare→稀有令牌档、rarest→罕见令牌档;relic 令牌永不入普通档=「挂稀有/罕见档」
|
# 投放档映射:common/rare→稀有令牌档、rarest→罕见令牌档;relic 令牌永不入普通档=「挂稀有/罕见档」
|
||||||
# 追认 b 接线口径)/ archetype ∈ ARCHETYPES 七流派标签 / hook ∈ HOOKS 挂点枚举(结算段槽——整合定稿
|
# 追认 b 接线口径)/ archetype ∈ ARCHETYPES 七流派标签 / hook ∈ HOOKS 挂点枚举(结算段槽——整合定稿
|
||||||
# §2.1 哈迪斯恩赐槽同构)/ effects Dictionary(hook 消费参数,provisional 投值)。
|
# §2.1 哈迪斯恩赐槽同构)/ effects Dictionary(hook 消费参数,provisional 投值)。
|
||||||
# 正负双修(整合定稿 B2 判据):effects 允许负向键(cost_* 族)——纯增益件须专项论证(反例 B-3),加载期
|
# 正负双修(整合定稿 B2 判据):effects 允许负向键(cost_* 族)——纯增益件须专项论证(反例 B-3),加载期
|
||||||
# 仅校验键域不禁纯增益(专项论证件以 positive_only 批注登记,人工清单呈报)。
|
# 仅校验键域不禁纯增益(专项论证件以 positive_only 批注登记,人工清单呈报)。
|
||||||
# 档位激活(整合定稿 设计工具 1——以撒套装/小骨铭文):同标签计数 ≥2→档一(流派核心规则)/≥4→档二
|
# 档位激活(整合定稿 设计工具 1——以撒套装/小骨铭文):同标签计数 ≥2→档一(流派核心规则)/≥4→档二
|
||||||
# (完全体);散件单体保底=每件效果独立生效,档位只做质变(反例 B-2)——引擎纯函数 RelicsEngine。
|
# (完全体);散件单体保底=每件效果独立生效,档位只做质变(反例 B-2)——引擎纯函数 FeatsEngine。
|
||||||
# 🔴 层级纪律(任务书红线 2):专长只走局内随机渠道(三选一/商店/格面)——本表行永不入同调位
|
# 🔴 层级纪律(任务书红线 2):专长只走局内随机渠道(三选一/商店/格面)——本表行永不入同调位
|
||||||
# (treasure_items 表)/局外树(T8 节点表);扫雷触发点铁律:每流派 ≥1 行 hook ∈ MINE_HOOKS(加载校验,
|
# (treasure_items 表)/局外树(T8 节点表);扫雷触发点铁律:每流派 ≥1 行 hook ∈ MINE_HOOKS(加载校验,
|
||||||
# 违反=红线①触碰面)。
|
# 违反=红线①触碰面)。
|
||||||
class_name RelicTable
|
class_name FeatTable
|
||||||
extends Resource
|
extends Resource
|
||||||
|
|
||||||
@export var rows: Array = [] # Array[Dictionary] 键 relic_id
|
@export var rows: Array = [] # Array[Dictionary] 键 feat_id
|
||||||
|
|
||||||
## 流派标签词表(七核心——喂雷/淘金不在本批,R5 甲/缓议)
|
## 流派标签词表(七核心——喂雷/淘金不在本批,R5 甲/缓议)
|
||||||
const ARCHETYPES := ["magic", "pierce", "normal", "dig", "flag", "charge", "devour"]
|
const ARCHETYPES := ["magic", "pierce", "normal", "dig", "flag", "charge", "devour"]
|
||||||
@@ -85,12 +85,12 @@ func validate() -> Array:
|
|||||||
return errs # 空表合法态(T1 基建先行——T2 起逐流派填行)
|
return errs # 空表合法态(T1 基建先行——T2 起逐流派填行)
|
||||||
var seen := {}
|
var seen := {}
|
||||||
for row in rows:
|
for row in rows:
|
||||||
var rid: String = row.get("relic_id", "")
|
var rid: String = row.get("feat_id", "")
|
||||||
if rid == "":
|
if rid == "":
|
||||||
errs.append({"category": "domain", "message": "relic_id 空"})
|
errs.append({"category": "domain", "message": "feat_id 空"})
|
||||||
continue
|
continue
|
||||||
if seen.has(rid):
|
if seen.has(rid):
|
||||||
errs.append({"category": "dup_key", "message": "relic_id 重复: %s" % rid})
|
errs.append({"category": "dup_key", "message": "feat_id 重复: %s" % rid})
|
||||||
continue
|
continue
|
||||||
seen[rid] = true
|
seen[rid] = true
|
||||||
if String(row.get("display_name", "")) == "":
|
if String(row.get("display_name", "")) == "":
|
||||||
@@ -104,7 +104,7 @@ func validate() -> Array:
|
|||||||
if not HOOKS.has(hook):
|
if not HOOKS.has(hook):
|
||||||
errs.append({"category": "enum", "message": "%s hook 出域(挂点枚举封闭): %s" % [rid, hook]})
|
errs.append({"category": "enum", "message": "%s hook 出域(挂点枚举封闭): %s" % [rid, hook]})
|
||||||
for k in row.keys():
|
for k in row.keys():
|
||||||
if not ["relic_id", "display_name", "rarity", "archetype", "hook", "effects", "note"].has(String(k)):
|
if not ["feat_id", "display_name", "rarity", "archetype", "hook", "effects", "note"].has(String(k)):
|
||||||
errs.append({"category": "zero_flat", "message": "%s 行键出白名单: %s" % [rid, String(k)]})
|
errs.append({"category": "zero_flat", "message": "%s 行键出白名单: %s" % [rid, String(k)]})
|
||||||
var fx: Dictionary = row.get("effects", {}) as Dictionary
|
var fx: Dictionary = row.get("effects", {}) as Dictionary
|
||||||
for ek in fx.keys():
|
for ek in fx.keys():
|
||||||
@@ -126,8 +126,8 @@ func validate() -> Array:
|
|||||||
return errs
|
return errs
|
||||||
|
|
||||||
|
|
||||||
func row_by_id(relic_id: String) -> Dictionary:
|
func row_by_id(feat_id: String) -> Dictionary:
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if String(row.get("relic_id", "")) == relic_id:
|
if String(row.get("feat_id", "")) == feat_id:
|
||||||
return row
|
return row
|
||||||
return {}
|
return {}
|
||||||
File renamed without changes.
@@ -3,7 +3,7 @@
|
|||||||
# A/B 免费切换、彩蛋觉醒能否机械承载(P0——迭代 12 批次二 T8,R1 甲融合案)
|
# A/B 免费切换、彩蛋觉醒能否机械承载(P0——迭代 12 批次二 T8,R1 甲融合案)
|
||||||
# Date: 2026-10-04(双层 BD 实施批 T8;设计权威 双层BD体系-整合定稿-2026-10-03 §1.4)
|
# Date: 2026-10-04(双层 BD 实施批 T8;设计权威 双层BD体系-整合定稿-2026-10-03 §1.4)
|
||||||
# 数据配置 MetaTreeTable schema(元进度 回填 owner 批末落文;行 = Dictionary 键 node_id,Rule 4 豁免
|
# 数据配置 MetaTreeTable schema(元进度 回填 owner 批末落文;行 = Dictionary 键 node_id,Rule 4 豁免
|
||||||
# 定义表——RelicTable 先例)。
|
# 定义表——FeatTable 先例)。
|
||||||
# 行字段:node_id / display_name / vein ∈ VEINS(spine=主干脊柱 row-gating,GP 买深度;forge=锻造脉
|
# 行字段:node_id / display_name / vein ∈ VEINS(spine=主干脊柱 row-gating,GP 买深度;forge=锻造脉
|
||||||
# 数值专精;keystone=钥石脉内容解锁;attune=同调脉槽位与规则)/ kind ∈ KINDS(stat=四线数值/
|
# 数值专精;keystone=钥石脉内容解锁;attune=同调脉槽位与规则)/ kind ∈ KINDS(stat=四线数值/
|
||||||
# rule=全局规则/unlock=内容解锁/socket=同调位/fork=A/B 互斥对/easter=彩蛋觉醒)/
|
# rule=全局规则/unlock=内容解锁/socket=同调位/fork=A/B 互斥对/easter=彩蛋觉醒)/
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
# price_level_bonus = 关级定价加值行(level_index 连续从 1 递增)。
|
# price_level_bonus = 关级定价加值行(level_index 连续从 1 递增)。
|
||||||
# pool_shelf = 共享池货架行(M-4 迭代 11 第②批 T1——任务书 v1.3 P1 共享池/P4 ceil):键 kind_id 词表
|
# pool_shelf = 共享池货架行(M-4 迭代 11 第②批 T1——任务书 v1.3 P1 共享池/P4 ceil):键 kind_id 词表
|
||||||
# 封闭 = 四切边后种(SHELF_KINDS:attack/max_hp/armor/ap——GP 袋负交易切边、规则改写型特性三轴 doctrine 切边);
|
# 封闭 = 四切边后种(SHELF_KINDS:attack/max_hp/armor/ap——GP 袋负交易切边、规则改写型特性三轴 doctrine 切边);
|
||||||
# 库存=上限即库存(账本在 冒险结构 _bonus_taken 单点共享,行族不记库存);price_a/price_b = 乘法价格
|
# 库存=上限即库存(账本在 冒险结构 _curio_taken 单点共享,行族不记库存);price_a/price_b = 乘法价格
|
||||||
# 因子(公式 ceil(base × b^(关序−1) × A^(本次进店已购次数)),provisional 投值——P5 甲研究拍板后置追认)。
|
# 因子(公式 ceil(base × b^(关序−1) × A^(本次进店已购次数)),provisional 投值——P5 甲研究拍板后置追认)。
|
||||||
# products 行族 + 加法定价随 legacy 模式(shop=off 对照承载)保留,退役归 off 锚换代批。
|
# products 行族 + 加法定价随 legacy 模式(shop=off 对照承载)保留,退役归 off 锚换代批。
|
||||||
# 行值全部 provisional 归 难度曲线与Boss(用户拍板骨架先行、数值整体后置)。
|
# 行值全部 provisional 归 难度曲线与Boss(用户拍板骨架先行、数值整体后置)。
|
||||||
@@ -28,7 +28,7 @@ extends Resource
|
|||||||
|
|
||||||
const GP_TIERS := ["normal", "elite", "boss"]
|
const GP_TIERS := ["normal", "elite", "boss"]
|
||||||
const PRODUCT_ATTRS := ["atk", "max_hp", "armor_class"]
|
const PRODUCT_ATTRS := ["atk", "max_hp", "armor_class"]
|
||||||
const SHELF_KINDS := ["attack", "max_hp", "armor", "ap"] # 货架候选词表封闭(四切边后种——切边面翻案须用户)
|
const SHELF_KINDS := ["attack", "max_hp", "armor", "ap", "feat_ticket", "heal"] # 货架候选词表(四切边后种 + T5 门票行 + T6 heal 补回复〔默认原则:分类内成员都入该渠道池〕——奇物批迭代15②)
|
||||||
|
|
||||||
|
|
||||||
func validate() -> Array:
|
func validate() -> Array:
|
||||||
|
|||||||
@@ -5,27 +5,33 @@
|
|||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
clear_heal_pct = 20
|
clear_heal_pct = 20
|
||||||
feature_candidate_count = 3
|
curio_candidate_count = 3
|
||||||
feature_quota_attack_common = 18
|
curio_quota_attack_common = 18
|
||||||
feature_quota_attack_rare = 5
|
curio_quota_attack_rare = 5
|
||||||
feature_quota_max_hp_common = 17
|
curio_quota_max_hp_common = 17
|
||||||
feature_quota_max_hp_rare = 5
|
curio_quota_max_hp_rare = 5
|
||||||
feature_quota_armor_common = 17
|
curio_quota_armor_common = 17
|
||||||
feature_quota_armor_rare = 5
|
curio_quota_armor_rare = 5
|
||||||
feature_quota_gp_common = 18
|
curio_quota_gp_common = 18
|
||||||
feature_quota_gp_rare = 5
|
curio_quota_gp_rare = 5
|
||||||
feature_quota_heal_rare = 5
|
curio_quota_heal_rare = 3
|
||||||
feature_quota_ap_rarest = 3
|
curio_quota_refresh_rare = 2
|
||||||
feature_value_attack_common = 1
|
curio_quota_ap_rarest = 3
|
||||||
feature_value_attack_rare = 2
|
curio_value_attack_common = 1
|
||||||
feature_value_max_hp_common = 5
|
curio_value_attack_rare = 2
|
||||||
feature_value_max_hp_rare = 10
|
curio_value_max_hp_common = 5
|
||||||
feature_value_armor_common = 1
|
curio_value_max_hp_rare = 10
|
||||||
feature_value_armor_rare = 2
|
curio_value_armor_common = 1
|
||||||
feature_value_gp_common = 20
|
curio_value_armor_rare = 2
|
||||||
feature_value_gp_rare = 50
|
curio_value_gp_common = 20
|
||||||
feature_value_heal_rare = 6
|
curio_value_gp_rare = 50
|
||||||
feature_value_ap_rarest = 1
|
curio_value_heal_rare = 6
|
||||||
|
curio_value_refresh_rare = 1
|
||||||
|
curio_value_ap_rarest = 1
|
||||||
|
feat_ticket_chance = 20
|
||||||
|
feat_ticket_quota = 2
|
||||||
|
feat_ticket_limit = 1
|
||||||
|
feat_ticket_pity_step = 5
|
||||||
|
|
||||||
; TP0 令牌池换代(2026-10-03,node-reward v2.2 追认 b 落地):三选一池 = 固定配额令牌池——
|
; TP0 令牌池换代(2026-10-03,node-reward v2.2 追认 b 落地):三选一池 = 固定配额令牌池——
|
||||||
; 普通 70/稀有 25/罕见 3 = 98 张(罕见档原列「道具件 2」随 treasure_item 池种退役收窄——台账自决披露项);
|
; 普通 70/稀有 25/罕见 3 = 98 张(罕见档原列「道具件 2」随 treasure_item 池种退役收窄——台账自决披露项);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
[gd_resource type="Resource" script_class="RelicTable" load_steps=2 format=3]
|
[gd_resource type="Resource" script_class="FeatTable" load_steps=2 format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://data/schemas/relic_table.gd" id="1"]
|
[ext_resource type="Script" path="res://data/schemas/feat_table.gd" id="1"]
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
rows = [{
|
rows = [{
|
||||||
"relic_id": "arcane_ember",
|
"feat_id": "arcane_ember",
|
||||||
"display_name": "奥术余烬",
|
"display_name": "奥术余烬",
|
||||||
"rarity": "common",
|
"rarity": "common",
|
||||||
"archetype": "magic",
|
"archetype": "magic",
|
||||||
@@ -14,7 +14,7 @@ rows = [{
|
|||||||
"value": 1
|
"value": 1
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "mana_weaver",
|
"feat_id": "mana_weaver",
|
||||||
"display_name": "魔网织者",
|
"display_name": "魔网织者",
|
||||||
"rarity": "rare",
|
"rarity": "rare",
|
||||||
"archetype": "magic",
|
"archetype": "magic",
|
||||||
@@ -24,7 +24,7 @@ rows = [{
|
|||||||
"polarity": "magic_vs_magic"
|
"polarity": "magic_vs_magic"
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "azure_oath",
|
"feat_id": "azure_oath",
|
||||||
"display_name": "湛蓝誓约",
|
"display_name": "湛蓝誓约",
|
||||||
"rarity": "rare",
|
"rarity": "rare",
|
||||||
"archetype": "magic",
|
"archetype": "magic",
|
||||||
@@ -34,7 +34,7 @@ rows = [{
|
|||||||
"polarity": "magic_vs_heavy"
|
"polarity": "magic_vs_heavy"
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "mana_resonance",
|
"feat_id": "mana_resonance",
|
||||||
"display_name": "魔能共鸣",
|
"display_name": "魔能共鸣",
|
||||||
"rarity": "rarest",
|
"rarity": "rarest",
|
||||||
"archetype": "magic",
|
"archetype": "magic",
|
||||||
@@ -43,7 +43,7 @@ rows = [{
|
|||||||
"value": 2
|
"value": 2
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "mine_atlas",
|
"feat_id": "mine_atlas",
|
||||||
"display_name": "雷区图谱",
|
"display_name": "雷区图谱",
|
||||||
"rarity": "common",
|
"rarity": "common",
|
||||||
"archetype": "flag",
|
"archetype": "flag",
|
||||||
@@ -52,7 +52,7 @@ rows = [{
|
|||||||
"value": 1
|
"value": 1
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "nest_hunter",
|
"feat_id": "nest_hunter",
|
||||||
"display_name": "猎巢人",
|
"display_name": "猎巢人",
|
||||||
"rarity": "rare",
|
"rarity": "rare",
|
||||||
"archetype": "flag",
|
"archetype": "flag",
|
||||||
@@ -61,7 +61,7 @@ rows = [{
|
|||||||
"value": 1
|
"value": 1
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "perfect_defense",
|
"feat_id": "perfect_defense",
|
||||||
"display_name": "完美布防",
|
"display_name": "完美布防",
|
||||||
"rarity": "rare",
|
"rarity": "rare",
|
||||||
"archetype": "flag",
|
"archetype": "flag",
|
||||||
@@ -70,7 +70,7 @@ rows = [{
|
|||||||
"value": 2
|
"value": 2
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "war_banner",
|
"feat_id": "war_banner",
|
||||||
"display_name": "战旗军规",
|
"display_name": "战旗军规",
|
||||||
"rarity": "rarest",
|
"rarity": "rarest",
|
||||||
"archetype": "flag",
|
"archetype": "flag",
|
||||||
@@ -79,7 +79,7 @@ rows = [{
|
|||||||
"value": 2
|
"value": 2
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "pierce_shot",
|
"feat_id": "pierce_shot",
|
||||||
"display_name": "贯穿射击",
|
"display_name": "贯穿射击",
|
||||||
"rarity": "rarest",
|
"rarity": "rarest",
|
||||||
"archetype": "pierce",
|
"archetype": "pierce",
|
||||||
@@ -88,7 +88,7 @@ rows = [{
|
|||||||
"value": 1
|
"value": 1
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "chain_arrows",
|
"feat_id": "chain_arrows",
|
||||||
"display_name": "连珠矢",
|
"display_name": "连珠矢",
|
||||||
"rarity": "rare",
|
"rarity": "rare",
|
||||||
"archetype": "pierce",
|
"archetype": "pierce",
|
||||||
@@ -98,7 +98,7 @@ rows = [{
|
|||||||
"cap": 2
|
"cap": 2
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "weak_instinct",
|
"feat_id": "weak_instinct",
|
||||||
"display_name": "猎弱本能",
|
"display_name": "猎弱本能",
|
||||||
"rarity": "rare",
|
"rarity": "rare",
|
||||||
"archetype": "pierce",
|
"archetype": "pierce",
|
||||||
@@ -107,7 +107,7 @@ rows = [{
|
|||||||
"value": 1
|
"value": 1
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "hunter_rapier",
|
"feat_id": "hunter_rapier",
|
||||||
"display_name": "猎手刺剑",
|
"display_name": "猎手刺剑",
|
||||||
"rarity": "common",
|
"rarity": "common",
|
||||||
"archetype": "pierce",
|
"archetype": "pierce",
|
||||||
@@ -117,7 +117,7 @@ rows = [{
|
|||||||
"polarity": "pierce_vs_light"
|
"polarity": "pierce_vs_light"
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "miner_stubborn",
|
"feat_id": "miner_stubborn",
|
||||||
"display_name": "矿工的执拗",
|
"display_name": "矿工的执拗",
|
||||||
"rarity": "common",
|
"rarity": "common",
|
||||||
"archetype": "normal",
|
"archetype": "normal",
|
||||||
@@ -127,7 +127,7 @@ rows = [{
|
|||||||
"cap": 5
|
"cap": 5
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "miner_hammer",
|
"feat_id": "miner_hammer",
|
||||||
"display_name": "矿工重锤",
|
"display_name": "矿工重锤",
|
||||||
"rarity": "common",
|
"rarity": "common",
|
||||||
"archetype": "normal",
|
"archetype": "normal",
|
||||||
@@ -137,7 +137,7 @@ rows = [{
|
|||||||
"trade_hit": 2
|
"trade_hit": 2
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "focus_mind",
|
"feat_id": "focus_mind",
|
||||||
"display_name": "专注",
|
"display_name": "专注",
|
||||||
"rarity": "rare",
|
"rarity": "rare",
|
||||||
"archetype": "normal",
|
"archetype": "normal",
|
||||||
@@ -146,7 +146,7 @@ rows = [{
|
|||||||
"value": 75
|
"value": 75
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "armor_crack",
|
"feat_id": "armor_crack",
|
||||||
"display_name": "碎甲震击",
|
"display_name": "碎甲震击",
|
||||||
"rarity": "rare",
|
"rarity": "rare",
|
||||||
"archetype": "normal",
|
"archetype": "normal",
|
||||||
@@ -155,7 +155,7 @@ rows = [{
|
|||||||
"value": 25
|
"value": 25
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "greedy_lantern",
|
"feat_id": "greedy_lantern",
|
||||||
"display_name": "贪婪矿灯",
|
"display_name": "贪婪矿灯",
|
||||||
"rarity": "common",
|
"rarity": "common",
|
||||||
"archetype": "dig",
|
"archetype": "dig",
|
||||||
@@ -165,7 +165,7 @@ rows = [{
|
|||||||
"cost_mult": 25
|
"cost_mult": 25
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "vein_intuition",
|
"feat_id": "vein_intuition",
|
||||||
"display_name": "矿脉直觉",
|
"display_name": "矿脉直觉",
|
||||||
"rarity": "rare",
|
"rarity": "rare",
|
||||||
"archetype": "dig",
|
"archetype": "dig",
|
||||||
@@ -175,7 +175,7 @@ rows = [{
|
|||||||
"cap": 5
|
"cap": 5
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "gambler_pact",
|
"feat_id": "gambler_pact",
|
||||||
"display_name": "赌徒的血签",
|
"display_name": "赌徒的血签",
|
||||||
"rarity": "rarest",
|
"rarity": "rarest",
|
||||||
"archetype": "dig",
|
"archetype": "dig",
|
||||||
@@ -185,7 +185,7 @@ rows = [{
|
|||||||
"cost_mult": 100
|
"cost_mult": 100
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "deep_drill",
|
"feat_id": "deep_drill",
|
||||||
"display_name": "深挖钻头",
|
"display_name": "深挖钻头",
|
||||||
"rarity": "rare",
|
"rarity": "rare",
|
||||||
"archetype": "dig",
|
"archetype": "dig",
|
||||||
@@ -195,7 +195,7 @@ rows = [{
|
|||||||
"cap": 4
|
"cap": 4
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "armory_prep",
|
"feat_id": "armory_prep",
|
||||||
"display_name": "整备军备",
|
"display_name": "整备军备",
|
||||||
"rarity": "rarest",
|
"rarity": "rarest",
|
||||||
"archetype": "charge",
|
"archetype": "charge",
|
||||||
@@ -205,7 +205,7 @@ rows = [{
|
|||||||
"cap": 3
|
"cap": 3
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "iron_wall",
|
"feat_id": "iron_wall",
|
||||||
"display_name": "铁壁整备",
|
"display_name": "铁壁整备",
|
||||||
"rarity": "rare",
|
"rarity": "rare",
|
||||||
"archetype": "charge",
|
"archetype": "charge",
|
||||||
@@ -214,7 +214,7 @@ rows = [{
|
|||||||
"shield_per_layer": 1
|
"shield_per_layer": 1
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "patient_burst",
|
"feat_id": "patient_burst",
|
||||||
"display_name": "厚积薄发",
|
"display_name": "厚积薄发",
|
||||||
"rarity": "rare",
|
"rarity": "rare",
|
||||||
"archetype": "charge",
|
"archetype": "charge",
|
||||||
@@ -223,7 +223,7 @@ rows = [{
|
|||||||
"value": 2
|
"value": 2
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "ley_harvest",
|
"feat_id": "ley_harvest",
|
||||||
"display_name": "地脉采撷",
|
"display_name": "地脉采撷",
|
||||||
"rarity": "common",
|
"rarity": "common",
|
||||||
"archetype": "devour",
|
"archetype": "devour",
|
||||||
@@ -232,7 +232,7 @@ rows = [{
|
|||||||
"value": 1
|
"value": 1
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "thorn_weaver",
|
"feat_id": "thorn_weaver",
|
||||||
"display_name": "荆棘编织者",
|
"display_name": "荆棘编织者",
|
||||||
"rarity": "rare",
|
"rarity": "rare",
|
||||||
"archetype": "devour",
|
"archetype": "devour",
|
||||||
@@ -241,7 +241,7 @@ rows = [{
|
|||||||
"value": 1
|
"value": 1
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "purge_sapper",
|
"feat_id": "purge_sapper",
|
||||||
"display_name": "净化工兵",
|
"display_name": "净化工兵",
|
||||||
"rarity": "rare",
|
"rarity": "rare",
|
||||||
"archetype": "devour",
|
"archetype": "devour",
|
||||||
@@ -250,7 +250,7 @@ rows = [{
|
|||||||
"value": 1
|
"value": 1
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"relic_id": "magma_body",
|
"feat_id": "magma_body",
|
||||||
"display_name": "熔火之躯",
|
"display_name": "熔火之躯",
|
||||||
"rarity": "rarest",
|
"rarity": "rarest",
|
||||||
"archetype": "devour",
|
"archetype": "devour",
|
||||||
@@ -261,7 +261,7 @@ rows = [{
|
|||||||
}]
|
}]
|
||||||
|
|
||||||
; 双层 BD 实施批(2026-10-04):规则改写型特性表——行 = 特性定义
|
; 双层 BD 实施批(2026-10-04):规则改写型特性表——行 = 特性定义
|
||||||
; (relic_id/display_name/rarity/archetype/hook/effects)。
|
; (feat_id/display_name/rarity/archetype/hook/effects)。
|
||||||
; T2 魔法流四件(整合定稿 §2.3 S1):奥术余烬〔普通·rider+1 必中附加〕/魔网织者〔稀有·magic vs
|
; T2 魔法流四件(整合定稿 §2.3 S1):奥术余烬〔普通·rider+1 必中附加〕/魔网织者〔稀有·magic vs
|
||||||
; magic 克制罚修正〕/湛蓝誓约〔稀有·magic vs heavy 克制加成〕/魔能共鸣〔罕见锚点·rider+2+每战首次
|
; magic 克制罚修正〕/湛蓝誓约〔稀有·magic vs heavy 克制加成〕/魔能共鸣〔罕见锚点·rider+2+每战首次
|
||||||
; 开格奥术矢——「每关首次」字面落地为「每战首次」台账披露待追认〕;magic 档一(≥2 件)rider+1 /
|
; 开格奥术矢——「每关首次」字面落地为「每战首次」台账披露待追认〕;magic 档一(≥2 件)rider+1 /
|
||||||
@@ -81,6 +81,14 @@ pool_shelf = [{
|
|||||||
"kind_id": "ap",
|
"kind_id": "ap",
|
||||||
"display_name": "行动点件",
|
"display_name": "行动点件",
|
||||||
"base_price": 5
|
"base_price": 5
|
||||||
|
}, {
|
||||||
|
"kind_id": "feat_ticket",
|
||||||
|
"display_name": "奇物门票",
|
||||||
|
"base_price": 5
|
||||||
|
}, {
|
||||||
|
"kind_id": "heal",
|
||||||
|
"display_name": "应急回复",
|
||||||
|
"base_price": 5
|
||||||
}]
|
}]
|
||||||
price_a = 1.8
|
price_a = 1.8
|
||||||
price_b = 1.1
|
price_b = 1.1
|
||||||
@@ -93,7 +101,7 @@ price_b = 1.1
|
|||||||
; (4+2)+(2+2)+(4+2)=16、全购欲望 ≈ 26–34——取舍空间成立;量级与方差全部 provisional-pending-曲线调参批
|
; (4+2)+(2+2)+(4+2)=16、全购欲望 ≈ 26–34——取舍空间成立;量级与方差全部 provisional-pending-曲线调参批
|
||||||
;(用户拍板骨架先行、数值整体后置,行值归 难度曲线与Boss)。
|
;(用户拍板骨架先行、数值整体后置,行值归 难度曲线与Boss)。
|
||||||
; GP 商店房重构批(M-4 迭代 11 第②批 T1,2026-10-03——任务书 v1.3 全裁 P1 共享池/P4 ceil/P5 甲):
|
; GP 商店房重构批(M-4 迭代 11 第②批 T1,2026-10-03——任务书 v1.3 全裁 P1 共享池/P4 ceil/P5 甲):
|
||||||
; pool_shelf 行族 = 共享池货架(kind_id 词表封闭四切边后种 attack/max_hp/armor/ap——GP 袋负交易切边、
|
; pool_shelf 行族 = 共享池货架(kind_id 词表 attack/max_hp/armor/ap/feat_ticket/heal 六行——heal 系 T6 商店补回复〔默认原则:分类内成员都要在它该出现的渠道池里,现行缺〕、GP 袋负交易切边计价物不自售、feat_ticket 系 T5 门票行〔非 Boss 渠道专长入口,价与其他商品相当=契约④口径②〕、
|
||||||
; 规则改写型特性三轴 doctrine 切边;库存=上限即库存,账本在 冒险结构 _bonus_taken 单点共享,行族不记库存);
|
; 规则改写型特性三轴 doctrine 切边;库存=上限即库存,账本在 冒险结构 _bonus_taken 单点共享,行族不记库存);
|
||||||
; 价格公式换代(pool 模式)= ceil(base_price × b^(关序−1) × A^(本次进店已购次数)),price_a=1.8/price_b=1.1
|
; 价格公式换代(pool 模式)= ceil(base_price × b^(关序−1) × A^(本次进店已购次数)),price_a=1.8/price_b=1.1
|
||||||
; provisional 投值(零人工令改 provisional+后置追认,原 P5 甲研究拍板后置);products 行族 + 加法定价
|
; provisional 投值(零人工令改 provisional+后置追认,原 P5 甲研究拍板后置);products 行族 + 加法定价
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ static func boss_name(runner, db) -> String:
|
|||||||
|
|
||||||
# 三选一停点头部行(回血反馈 + 选择引导;钥石增量行不上本面——单一展示面归战斗总结结算框,
|
# 三选一停点头部行(回血反馈 + 选择引导;钥石增量行不上本面——单一展示面归战斗总结结算框,
|
||||||
# level-clear.md v2 禁入清单口径)
|
# level-clear.md v2 禁入清单口径)
|
||||||
static func bonus_pick_header(payload: Dictionary) -> String:
|
static func curio_pick_header(payload: Dictionary) -> String:
|
||||||
var lines: Array[String] = ["第 %d 层通过!" % int(payload.get("level_index", 0))]
|
var lines: Array[String] = ["第 %d 层通过!" % int(payload.get("level_index", 0))]
|
||||||
var heal := int(payload.get("heal_gained", 0))
|
var heal := int(payload.get("heal_gained", 0))
|
||||||
if heal > 0:
|
if heal > 0:
|
||||||
@@ -124,8 +124,8 @@ static func bonus_card_text(id: String, tier: String, value: int, treasure_line:
|
|||||||
# 三选一兑付反馈行(选中件效果 + 丢弃提示;tier 缺省普通档——旧载荷防御口径)
|
# 三选一兑付反馈行(选中件效果 + 丢弃提示;tier 缺省普通档——旧载荷防御口径)
|
||||||
static func bonus_result_text(result: Dictionary) -> String:
|
static func bonus_result_text(result: Dictionary) -> String:
|
||||||
var name := ""
|
var name := ""
|
||||||
if String(result.get("picked", "")) == "relic" and String(result.get("relic_id", "")) != "":
|
if String(result.get("picked", "")) == "relic" and String(result.get("feat_id", "")) != "":
|
||||||
name = relic_card_text({"relic_id": String(result.get("relic_id", "")), "display_name": String(result.get("relic_name", "")), "archetype": String(result.get("relic_archetype", ""))}).replace("\n", " · ")
|
name = feat_card_text({"feat_id": String(result.get("feat_id", "")), "display_name": String(result.get("feat_name", "")), "archetype": String(result.get("feat_archetype", ""))}).replace("\n", " · ")
|
||||||
else:
|
else:
|
||||||
name = bonus_card_text(String(result.get("picked", "")), String(result.get("tier", "")), int(result.get("value", 0))).replace("\n", " · ")
|
name = bonus_card_text(String(result.get("picked", "")), String(result.get("tier", "")), int(result.get("value", 0))).replace("\n", " · ")
|
||||||
return "已选择:%s\n其余选项已丢弃(不可反悔)" % name
|
return "已选择:%s\n其余选项已丢弃(不可反悔)" % name
|
||||||
@@ -133,8 +133,8 @@ static func bonus_result_text(result: Dictionary) -> String:
|
|||||||
|
|
||||||
# 特性卡片文案(迭代 12 T1;2026-10-04 呈现补强——第三行=hook 效果摘要,用户反馈「不知道件是
|
# 特性卡片文案(迭代 12 T1;2026-10-04 呈现补强——第三行=hook 效果摘要,用户反馈「不知道件是
|
||||||
# 干嘛的」缺口承载;摘要=静态映射表 provisional 文案归代拍板白名单「件文案微调」)
|
# 干嘛的」缺口承载;摘要=静态映射表 provisional 文案归代拍板白名单「件文案微调」)
|
||||||
static func relic_card_text(row: Dictionary) -> String:
|
static func feat_card_text(row: Dictionary) -> String:
|
||||||
if row.is_empty() or String(row.get("relic_id", "")) == "":
|
if row.is_empty() or String(row.get("feat_id", "")) == "":
|
||||||
return "特性\n???"
|
return "特性\n???"
|
||||||
var arch_names := {"magic": "魔法", "pierce": "穿刺", "normal": "普攻", "dig": "开格",
|
var arch_names := {"magic": "魔法", "pierce": "穿刺", "normal": "普攻", "dig": "开格",
|
||||||
"flag": "标旗", "charge": "蓄力", "devour": "吞噬"}
|
"flag": "标旗", "charge": "蓄力", "devour": "吞噬"}
|
||||||
@@ -166,8 +166,8 @@ static func relic_card_text(row: Dictionary) -> String:
|
|||||||
}
|
}
|
||||||
var summary := String(summaries.get(String(row.get("hook", "")), ""))
|
var summary := String(summaries.get(String(row.get("hook", "")), ""))
|
||||||
if summary != "":
|
if summary != "":
|
||||||
return "%s · %s\n%s" % [String(row.get("display_name", row.get("relic_id", ""))), arch, summary]
|
return "%s · %s\n%s" % [String(row.get("display_name", row.get("feat_id", ""))), arch, summary]
|
||||||
return "特性 · %s\n%s" % [arch, String(row.get("display_name", row.get("relic_id", "")))]
|
return "特性 · %s\n%s" % [arch, String(row.get("display_name", row.get("feat_id", "")))]
|
||||||
|
|
||||||
|
|
||||||
# Boss 胜利战斗总结结算行(settlement-summary v1.1——钥石增量行;组合根拼 GP 行后追加)
|
# Boss 胜利战斗总结结算行(settlement-summary v1.1——钥石增量行;组合根拼 GP 行后追加)
|
||||||
@@ -176,7 +176,7 @@ static func bonus_keystone_line(payload: Dictionary) -> String:
|
|||||||
|
|
||||||
|
|
||||||
# 奖励节点三选一头部行(迭代 10 追加批——与通关三选一共享同池;node-reward.md v2)
|
# 奖励节点三选一头部行(迭代 10 追加批——与通关三选一共享同池;node-reward.md v2)
|
||||||
static func node_bonus_header() -> String:
|
static func node_curio_header() -> String:
|
||||||
return "关卡奖励点\n从提升池抽三选一(与通关三选一同池同权重同上限):\n选择一件带走:"
|
return "关卡奖励点\n从提升池抽三选一(与通关三选一同池同权重同上限):\n选择一件带走:"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+67
-49
@@ -1,18 +1,18 @@
|
|||||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||||
# 提升三选一与替换子交互控制器(迭代 13 T4/D4——二期家族① 自 main.gd 迁出;规范 1.1 单一职责):
|
# 提升三选一与替换子交互控制器(迭代 13 T4/D4——二期家族① 自 main.gd 迁出;规范 1.1 单一职责):
|
||||||
# 三选一停点编排(presented→pick→confirm/refresh→resolved→continue)+ 同调位替换子态
|
# 三选一停点编排(presented→pick→confirm/refresh→resolved→continue)+ 同调位替换子态
|
||||||
#(presented→chosen)+ TP0 令牌池卡片文案装配("kind|tier|relic_id" 复合键)+ 工坊显示名投影。
|
#(presented→chosen)+ TP0 令牌池卡片文案装配("kind|tier|feat_id" 复合键)+ 工坊显示名投影。
|
||||||
# 装配纪律(ADR-0001,仿 treasure_interact_controller 先例):信号连接不在此发生——bonus_overlay
|
# 装配纪律(ADR-0001,仿 treasure_interact_controller 先例):信号连接不在此发生——curio_overlay
|
||||||
# 四信号与 adventure 两信号由组合根接至本类 on_* 入口;模态栈/演播后排窗/呈现刷新/终局面/
|
# 四信号与 adventure 两信号由组合根接至本类 on_* 入口;模态栈/演播后排窗/呈现刷新/终局面/
|
||||||
# 推进失败 fatal 经组合根回调注入;adventure/runner 随局更替——现值回调注入;兑付写调用唯一
|
# 推进失败 fatal 经组合根回调注入;adventure/runner 随局更替——现值回调注入;兑付写调用唯一
|
||||||
# 经 adventure.pick_bonus/apply_node_bonus/resolve_bonus_replace(core 侧单口,本类零直写)。
|
# 经 adventure.pick_curio/apply_node_curio/resolve_curio_replace(core 侧单口,本类零直写)。
|
||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
const Narrator := preload("res://presentation/battle_narrator.gd")
|
const Narrator := preload("res://presentation/battle_narrator.gd")
|
||||||
|
|
||||||
var db
|
var db
|
||||||
var _meta_get: Callable # () → 元进度账本现值(组合根 lambda——探针 isolate_save 换注面,迭代13 T4 实证)
|
var _meta_get: Callable # () → 元进度账本现值(组合根 lambda——探针 isolate_save 换注面,迭代13 T4 实证)
|
||||||
var bonus_overlay: BonusPickOverlay
|
var curio_overlay: CurioPickOverlay
|
||||||
var end_overlay: BattleEndOverlay
|
var end_overlay: BattleEndOverlay
|
||||||
var board_view: BoardView
|
var board_view: BoardView
|
||||||
var toast: ToastStack
|
var toast: ToastStack
|
||||||
@@ -27,20 +27,20 @@ var _refresh_presentations: Callable
|
|||||||
var _fatal_screen: Callable
|
var _fatal_screen: Callable
|
||||||
var _gp_summary: Callable # () → 战斗总结 GP 行文案(组合根 lambda——_last_gain_amount 取数面在根)
|
var _gp_summary: Callable # () → 战斗总结 GP 行文案(组合根 lambda——_last_gain_amount 取数面在根)
|
||||||
|
|
||||||
var _pending_bonus := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用)
|
var _pending_curio := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用)
|
||||||
var _bonus_context := "level_clear" # 三选一面上下文(level_clear=通关停点→推进下一关 / reward_node=奖励节点→回图选路)
|
var _curio_context := "level_clear" # 三选一面上下文(level_clear=通关停点→推进下一关 / reward_node=奖励节点→回图选路)
|
||||||
var _last_replace_items: Array = [] # 替换子态续玩重建缓存(treasure_replace_presented 载荷)
|
var _last_replace_items: Array = [] # 替换子态续玩重建缓存(treasure_replace_presented 载荷)
|
||||||
var _last_replace_incoming := {} # 同上——incoming 记录
|
var _last_replace_incoming := {} # 同上——incoming 记录
|
||||||
|
|
||||||
|
|
||||||
func _init(db_ref, meta_get: Callable, bonus_overlay_ref: BonusPickOverlay, end_overlay_ref: BattleEndOverlay,
|
func _init(db_ref, meta_get: Callable, curio_overlay_ref: CurioPickOverlay, end_overlay_ref: BattleEndOverlay,
|
||||||
board_view_ref: BoardView, toast_ref: ToastStack, save_get: Callable,
|
board_view_ref: BoardView, toast_ref: ToastStack, save_get: Callable,
|
||||||
adventure_get: Callable, runner_get: Callable, adventure_mode_get: Callable,
|
adventure_get: Callable, runner_get: Callable, adventure_mode_get: Callable,
|
||||||
open_modal: Callable, open_modal_after_playback: Callable, close_modal: Callable,
|
open_modal: Callable, open_modal_after_playback: Callable, close_modal: Callable,
|
||||||
refresh_presentations: Callable, fatal_screen: Callable, gp_summary: Callable) -> void:
|
refresh_presentations: Callable, fatal_screen: Callable, gp_summary: Callable) -> void:
|
||||||
db = db_ref
|
db = db_ref
|
||||||
_meta_get = meta_get
|
_meta_get = meta_get
|
||||||
bonus_overlay = bonus_overlay_ref
|
curio_overlay = curio_overlay_ref
|
||||||
end_overlay = end_overlay_ref
|
end_overlay = end_overlay_ref
|
||||||
board_view = board_view_ref
|
board_view = board_view_ref
|
||||||
toast = toast_ref
|
toast = toast_ref
|
||||||
@@ -58,12 +58,12 @@ func _init(db_ref, meta_get: Callable, bonus_overlay_ref: BonusPickOverlay, end_
|
|||||||
|
|
||||||
# 停点在途判据(组合根 _on_battle_end_continue 分派面——战斗总结确认后开三选一)
|
# 停点在途判据(组合根 _on_battle_end_continue 分派面——战斗总结确认后开三选一)
|
||||||
func has_pending() -> bool:
|
func has_pending() -> bool:
|
||||||
return not _pending_bonus.is_empty()
|
return not _pending_curio.is_empty()
|
||||||
|
|
||||||
|
|
||||||
# 续玩停点恢复入口(true=已接手呈现,调用方中止后续分派)
|
# 续玩停点恢复入口(true=已接手呈现,调用方中止后续分派)
|
||||||
func resume_if_pending() -> bool:
|
func resume_if_pending() -> bool:
|
||||||
if _pending_bonus.is_empty():
|
if _pending_curio.is_empty():
|
||||||
return false
|
return false
|
||||||
show_pick()
|
show_pick()
|
||||||
return true
|
return true
|
||||||
@@ -72,10 +72,17 @@ func resume_if_pending() -> bool:
|
|||||||
# 三选一停点(非末关通关恰一次):Boss 胜利先经战斗总结结算面(settlement-summary v1.1——钥石
|
# 三选一停点(非末关通关恰一次):Boss 胜利先经战斗总结结算面(settlement-summary v1.1——钥石
|
||||||
# 增量行在其上),确认后进三选一面(level-clear.md v2);续玩停点恢复同入口(零自动推进契约)
|
# 增量行在其上),确认后进三选一面(level-clear.md v2);续玩停点恢复同入口(零自动推进契约)
|
||||||
func on_presented(payload: Dictionary) -> void:
|
func on_presented(payload: Dictionary) -> void:
|
||||||
|
if String(payload.get("context", "")) == "feat_ticket":
|
||||||
|
# T5 店内门票兑付——临时停点开专长三选一面(零胜利总结面;确认后只关面回店)
|
||||||
|
_pending_curio = payload.duplicate(true)
|
||||||
|
_curio_context = "feat_ticket"
|
||||||
|
_open_modal.call(curio_overlay)
|
||||||
|
curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(payload.get("candidates", []) as Array))
|
||||||
|
return
|
||||||
if int(payload.get("aux_cleared", 0)) > 0:
|
if int(payload.get("aux_cleared", 0)) > 0:
|
||||||
toast.show_toast("上关剩余 %d 件秘宝已随关卡结束清空" % int(payload.get("aux_cleared", 0)), "info") # 迭代 7 T4 清空反馈
|
toast.show_toast("上关剩余 %d 件秘宝已随关卡结束清空" % int(payload.get("aux_cleared", 0)), "info") # 迭代 7 T4 清空反馈
|
||||||
_pending_bonus = payload.duplicate(true)
|
_pending_curio = payload.duplicate(true)
|
||||||
_bonus_context = "level_clear"
|
_curio_context = "level_clear"
|
||||||
if bool(payload.get("replace_pending", false)):
|
if bool(payload.get("replace_pending", false)):
|
||||||
# 续玩:替换子交互在途中断——重建三选一面后直落替换子态(候选不重抽)
|
# 续玩:替换子交互在途中断——重建三选一面后直落替换子态(候选不重抽)
|
||||||
show_pick()
|
show_pick()
|
||||||
@@ -88,48 +95,51 @@ func on_presented(payload: Dictionary) -> void:
|
|||||||
|
|
||||||
|
|
||||||
# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 treasure_replace_presented 续发直落子态)
|
# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 treasure_replace_presented 续发直落子态)
|
||||||
# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;BonusPickOverlay 共用面,
|
# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;CurioPickOverlay 共用面,
|
||||||
# 道具件满位替换子交互同构)。原「秘宝投放渠道」三选一(迭代 7 过渡形态)随批退役。
|
# 道具件满位替换子交互同构)。原「秘宝投放渠道」三选一(迭代 7 过渡形态)随批退役。
|
||||||
func present_node_bonus() -> void:
|
func present_node_curio() -> void:
|
||||||
var adventure = _adventure_get.call()
|
var adventure = _adventure_get.call()
|
||||||
_bonus_context = "reward_node"
|
_curio_context = "reward_node"
|
||||||
var node_draw: Dictionary = adventure.node_bonus_candidates()
|
var node_draw: Dictionary = adventure.node_curio_candidates()
|
||||||
var node_cands: Array = node_draw.get("candidates", []) as Array
|
var node_cands: Array = node_draw.get("candidates", []) as Array
|
||||||
_open_modal.call(bonus_overlay)
|
_open_modal.call(curio_overlay)
|
||||||
bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(node_cands, node_draw.get("aux_detail", []) as Array))
|
curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(node_cands, node_draw.get("aux_detail", []) as Array))
|
||||||
adventure.present_node_bonus(node_cands, node_draw.get("aux_detail", []) as Array)
|
adventure.present_node_curio(node_cands, node_draw.get("aux_detail", []) as Array)
|
||||||
bonus_overlay.set_refresh_info(adventure.attune_refresh_left()) # 迭代 11 第①批 T5b——刷新余量投喂
|
curio_overlay.set_refresh_info(adventure.attune_refresh_left()) # 迭代 11 第①批 T5b——刷新余量投喂
|
||||||
|
|
||||||
|
|
||||||
# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 treasure_replace_presented 续发直落子态)
|
# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 treasure_replace_presented 续发直落子态)
|
||||||
func show_pick() -> void:
|
func show_pick() -> void:
|
||||||
var payload: Dictionary = _pending_bonus
|
var payload: Dictionary = _pending_curio
|
||||||
_open_modal.call(bonus_overlay)
|
_open_modal.call(curio_overlay)
|
||||||
bonus_overlay.open_pick(Narrator.bonus_pick_header(payload), _bonus_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array))
|
curio_overlay.open_pick(Narrator.curio_pick_header(payload), _curio_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array))
|
||||||
var adventure = _adventure_get.call()
|
var adventure = _adventure_get.call()
|
||||||
bonus_overlay.set_refresh_info(adventure.attune_refresh_left() if adventure != null else 0) # 迭代 11 第①批 T5b——刷新余量投喂
|
curio_overlay.set_refresh_info(adventure.attune_refresh_left() if adventure != null else 0) # 迭代 11 第①批 T5b——刷新余量投喂
|
||||||
_refresh_presentations.call()
|
_refresh_presentations.call()
|
||||||
|
|
||||||
|
|
||||||
func _bonus_card_texts(candidates: Array, treasure_detail: Array = []) -> Array:
|
func _curio_card_texts(candidates: Array, treasure_detail: Array = []) -> Array:
|
||||||
# TP0 令牌池——候选 = {"kind","tier"} 令牌字典;卡片 id = "kind|tier|relic_id" 复合键(同 kind
|
# TP0 令牌池——候选 = {"kind","tier"} 令牌字典;卡片 id = "kind|tier|feat_id" 复合键(同 kind
|
||||||
# 大小两档/同档多 relic 可同屏,kind 单键不构成唯一身份),确认回程拆键(on_pick_confirmed)
|
# 大小两档/同档多 relic 可同屏,kind 单键不构成唯一身份),确认回程拆键(on_pick_confirmed)
|
||||||
var out: Array = []
|
var out: Array = []
|
||||||
var treasure_i := 0
|
var treasure_i := 0
|
||||||
for tok in candidates:
|
for tok in candidates:
|
||||||
var kind := String((tok as Dictionary).get("kind", ""))
|
var kind := String((tok as Dictionary).get("kind", ""))
|
||||||
var tier := String((tok as Dictionary).get("tier", ""))
|
var tier := String((tok as Dictionary).get("tier", ""))
|
||||||
var rid := String((tok as Dictionary).get("relic_id", ""))
|
var rid := String((tok as Dictionary).get("feat_id", ""))
|
||||||
var cid := "%s|%s|%s" % [kind, tier, rid]
|
var cid := "%s|%s|%s" % [kind, tier, rid]
|
||||||
var text := ""
|
var text := ""
|
||||||
if kind == "relic":
|
if kind == "feat_ticket":
|
||||||
text = Narrator.relic_card_text(db.relic_row(rid))
|
text = "奇物门票
|
||||||
|
选定即开一次奇物三选一(T5 门票——拿到即开不进持有集)"
|
||||||
|
elif kind == "relic":
|
||||||
|
text = Narrator.feat_card_text(db.feat_row(rid))
|
||||||
elif kind == "treasure_item":
|
elif kind == "treasure_item":
|
||||||
var iid := String(treasure_detail[treasure_i]) if treasure_i < treasure_detail.size() else ""
|
var iid := String(treasure_detail[treasure_i]) if treasure_i < treasure_detail.size() else ""
|
||||||
treasure_i += 1
|
treasure_i += 1
|
||||||
text = Narrator.bonus_card_text(kind, tier, int(db.adventure.feature_value(kind, tier)), _treasure_card_line(iid))
|
text = Narrator.bonus_card_text(kind, tier, int(db.adventure.curio_value(kind, tier)), _treasure_card_line(iid))
|
||||||
else:
|
else:
|
||||||
text = Narrator.bonus_card_text(kind, tier, int(db.adventure.feature_value(kind, tier)))
|
text = Narrator.bonus_card_text(kind, tier, int(db.adventure.curio_value(kind, tier)))
|
||||||
out.append({"id": cid, "text": text})
|
out.append({"id": cid, "text": text})
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@@ -157,39 +167,45 @@ func _treasure_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 自然重建,
|
# 三选一刷新(迭代 11 第①批 T5b——P1 甲 X/Y 双源;通关停点经重发 curio_choice_presented 自然重建,
|
||||||
# 节点停点在此读在途态重 present——零自动推进契约不破,停点仍在途)
|
# 节点停点在此读在途态重 present——零自动推进契约不破,停点仍在途)
|
||||||
func on_refresh() -> void:
|
func on_refresh() -> void:
|
||||||
var adventure = _adventure_get.call()
|
var adventure = _adventure_get.call()
|
||||||
if adventure == null:
|
if adventure == null:
|
||||||
return
|
return
|
||||||
if not adventure.refresh_bonus():
|
if not adventure.refresh_curio():
|
||||||
return
|
return
|
||||||
if _bonus_context == "reward_node":
|
if _curio_context == "reward_node":
|
||||||
var st: Dictionary = adventure.node_bonus_pending_state()
|
var st: Dictionary = adventure.node_curio_pending_state()
|
||||||
bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(st.get("candidates", []) as Array, st.get("aux_detail", []) as Array))
|
curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(st.get("candidates", []) as Array, st.get("aux_detail", []) as Array))
|
||||||
bonus_overlay.set_refresh_info(adventure.attune_refresh_left())
|
curio_overlay.set_refresh_info(adventure.attune_refresh_left())
|
||||||
|
|
||||||
|
|
||||||
func on_pick_confirmed(item_id: String) -> void:
|
func on_pick_confirmed(item_id: String) -> void:
|
||||||
var adventure = _adventure_get.call()
|
var adventure = _adventure_get.call()
|
||||||
if adventure == null:
|
if adventure == null:
|
||||||
return
|
return
|
||||||
# TP0 令牌池——卡片 id "kind|tier|relic_id" 拆键(缺 | = 旧载荷防御归普通档;relic 三段)
|
# TP0 令牌池——卡片 id "kind|tier|feat_id" 拆键(缺 | = 旧载荷防御归普通档;relic 三段)
|
||||||
var parts := item_id.split("|")
|
var parts := item_id.split("|")
|
||||||
var kind := parts[0]
|
var kind := parts[0]
|
||||||
var tier := parts[1] if parts.size() > 1 else ""
|
var tier := parts[1] if parts.size() > 1 else ""
|
||||||
var rid := parts[2] if parts.size() > 2 else ""
|
var rid := parts[2] if parts.size() > 2 else ""
|
||||||
var ok: bool = adventure.apply_node_bonus(kind, tier, rid) if _bonus_context == "reward_node" else adventure.pick_bonus(kind, tier, rid)
|
var ok: bool = adventure.apply_node_curio(kind, tier, rid) if _curio_context == "reward_node" else adventure.pick_curio(kind, tier, rid)
|
||||||
if not ok:
|
if not ok:
|
||||||
push_error("[bonus_interact] 三选一兑付被拒——见输出日志")
|
push_error("[bonus_interact] 三选一兑付被拒——见输出日志")
|
||||||
|
return
|
||||||
|
# T5——节点渠道门票拿到即开:候选已被替换为专长面,本地读在途态重绘(零 emit——context 保持 reward_node,
|
||||||
|
# 后续兑付/确认仍走节点路径回图选路)
|
||||||
|
if _curio_context == "reward_node" and kind == "feat_ticket":
|
||||||
|
var st2: Dictionary = adventure.node_curio_pending_state()
|
||||||
|
curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(st2.get("candidates", []) as Array))
|
||||||
|
|
||||||
|
|
||||||
func on_treasure_replace_presented(items: Array, incoming: Dictionary) -> void:
|
func on_treasure_replace_presented(items: Array, incoming: Dictionary) -> void:
|
||||||
_last_replace_items = items.duplicate(true)
|
_last_replace_items = items.duplicate(true)
|
||||||
_last_replace_incoming = incoming.duplicate(true)
|
_last_replace_incoming = incoming.duplicate(true)
|
||||||
if not bonus_overlay.visible:
|
if not curio_overlay.visible:
|
||||||
_open_modal.call(bonus_overlay) # 续玩替换在途直落——三选一面未开时先入栈(模态栈纪律)
|
_open_modal.call(curio_overlay) # 续玩替换在途直落——三选一面未开时先入栈(模态栈纪律)
|
||||||
_open_replace(items, incoming)
|
_open_replace(items, incoming)
|
||||||
|
|
||||||
|
|
||||||
@@ -199,29 +215,31 @@ func _open_replace(items: Array, incoming: Dictionary) -> void:
|
|||||||
var rec: Dictionary = it.duplicate()
|
var rec: Dictionary = it.duplicate()
|
||||||
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", "")))),
|
curio_overlay.open_replace(named, String(item_name_lookup().get(String(incoming.get("item_id", "")), String(incoming.get("item_id", "")))),
|
||||||
named.size(), Attunement.slots(db, _save_get.call())) # 迭代 11 第①批 T7——呈现读数源切谓词表求值(过渡键退役)
|
named.size(), Attunement.slots(db, _save_get.call())) # 迭代 11 第①批 T7——呈现读数源切谓词表求值(过渡键退役)
|
||||||
|
|
||||||
|
|
||||||
func on_replace_chosen(discard: int) -> void:
|
func on_replace_chosen(discard: int) -> void:
|
||||||
var adventure = _adventure_get.call()
|
var adventure = _adventure_get.call()
|
||||||
if adventure == null or not adventure.resolve_bonus_replace(discard):
|
if adventure == null or not adventure.resolve_curio_replace(discard):
|
||||||
push_error("[bonus_interact] 同调位替换受理被拒——见输出日志")
|
push_error("[bonus_interact] 同调位替换受理被拒——见输出日志")
|
||||||
|
|
||||||
|
|
||||||
# 兑付反馈(bonus_resolved 到达——结果行上屏,唯一出口 = 继续)
|
# 兑付反馈(curio_resolved 到达——结果行上屏,唯一出口 = 继续)
|
||||||
func on_resolved(result: Dictionary) -> void:
|
func on_resolved(result: Dictionary) -> void:
|
||||||
_pending_bonus = {}
|
_pending_curio = {}
|
||||||
bonus_overlay.show_result(Narrator.bonus_result_text(result))
|
curio_overlay.show_result(Narrator.bonus_result_text(result))
|
||||||
_refresh_presentations.call()
|
_refresh_presentations.call()
|
||||||
|
|
||||||
|
|
||||||
# 继续钮:收三选一面 → 上下文分派(通关停点=推进下一关 / 奖励节点=兑付确认回图选路——零自动推进)
|
# 继续钮:收三选一面 → 上下文分派(通关停点=推进下一关 / 奖励节点=兑付确认回图选路——零自动推进)
|
||||||
func on_continue() -> void:
|
func on_continue() -> void:
|
||||||
_close_modal.call(bonus_overlay)
|
_close_modal.call(curio_overlay)
|
||||||
bonus_overlay.close_pick()
|
curio_overlay.close_pick()
|
||||||
var adventure = _adventure_get.call()
|
var adventure = _adventure_get.call()
|
||||||
if _bonus_context == "reward_node":
|
if _curio_context == "feat_ticket":
|
||||||
|
return # T5 店内门票面——专长兑付完成后关面回店(无推进/无回图)
|
||||||
|
if _curio_context == "reward_node":
|
||||||
var runner = _runner_get.call()
|
var runner = _runner_get.call()
|
||||||
if runner != null:
|
if runner != null:
|
||||||
runner.continue_after_node()
|
runner.continue_after_node()
|
||||||
File renamed without changes.
@@ -35,11 +35,11 @@ static func refresh(stage: BattleStageView, info: HeroInfoPanelView, ap_bar: ApB
|
|||||||
else:
|
else:
|
||||||
info.set_level_bonuses([])
|
info.set_level_bonuses([])
|
||||||
# 特性持有投影(迭代 12 呈现补齐 2026-10-04——用户反馈「局内奖励选了什么看不到」:
|
# 特性持有投影(迭代 12 呈现补齐 2026-10-04——用户反馈「局内奖励选了什么看不到」:
|
||||||
# 逐流派聚合行+成型档位;常驻可读+空态隐藏。档位求值=RelicsEngine 纯函数直调)
|
# 逐流派聚合行+成型档位;常驻可读+空态隐藏。档位求值=FeatsEngine 纯函数直调)
|
||||||
if runner != null and level_active:
|
if runner != null and level_active:
|
||||||
info.set_relic_holdings(relic_holdings_lines(db, runner.relics_held()))
|
info.set_feat_holdings(feat_holdings_lines(db, runner.feats_held()))
|
||||||
else:
|
else:
|
||||||
info.set_relic_holdings([])
|
info.set_feat_holdings([])
|
||||||
if settlement == null:
|
if settlement == null:
|
||||||
info.set_board_variants([]) # 战斗域角标出战斗即隐(首发批 2026-10-04——空态纪律)
|
info.set_board_variants([]) # 战斗域角标出战斗即隐(首发批 2026-10-04——空态纪律)
|
||||||
_refresh_out_of_battle(stage, info, ap_bar, runner, level_active, db)
|
_refresh_out_of_battle(stage, info, ap_bar, runner, level_active, db)
|
||||||
@@ -94,18 +94,18 @@ static func _refresh_economy(ap_bar: ApBarView, s: Dictionary, db, refresh_remai
|
|||||||
|
|
||||||
|
|
||||||
# 特性持有聚合文案(迭代 12 呈现补齐——战斗 C 区与关卡选择页两处共用;空持有=空数组)
|
# 特性持有聚合文案(迭代 12 呈现补齐——战斗 C 区与关卡选择页两处共用;空持有=空数组)
|
||||||
static func relic_holdings_lines(db, held: Array) -> Array[String]:
|
static func feat_holdings_lines(db, held: Array) -> Array[String]:
|
||||||
var rlines: Array[String] = []
|
var rlines: Array[String] = []
|
||||||
if db == null or db.relics == null or (db.relics.rows as Array).is_empty() or held.is_empty():
|
if db == null or db.feats == null or (db.feats.rows as Array).is_empty() or held.is_empty():
|
||||||
return rlines
|
return rlines
|
||||||
var arch_names := {"magic": "魔法", "pierce": "穿刺", "normal": "普攻", "dig": "开格",
|
var arch_names := {"magic": "魔法", "pierce": "穿刺", "normal": "普攻", "dig": "开格",
|
||||||
"flag": "标旗", "charge": "蓄力", "devour": "吞噬"}
|
"flag": "标旗", "charge": "蓄力", "devour": "吞噬"}
|
||||||
var counts := {}
|
var counts := {}
|
||||||
for rid in held:
|
for rid in held:
|
||||||
var rrow: Dictionary = db.relic_row(String(rid))
|
var rrow: Dictionary = db.feat_row(String(rid))
|
||||||
var arch := String(rrow.get("archetype", ""))
|
var arch := String(rrow.get("archetype", ""))
|
||||||
counts[arch] = int(counts.get(arch, 0)) + 1
|
counts[arch] = int(counts.get(arch, 0)) + 1
|
||||||
var tiers: Dictionary = RelicsEngine.tiers_for(held, db.relics)
|
var tiers: Dictionary = FeatsEngine.tiers_for(held, db.feats)
|
||||||
for arch in counts.keys():
|
for arch in counts.keys():
|
||||||
var tier := int(tiers.get(arch, 0))
|
var tier := int(tiers.get(arch, 0))
|
||||||
var tier_text := "档二" if tier >= 2 else ("档一" if tier == 1 else "散件")
|
var tier_text := "档二" if tier >= 2 else ("档一" if tier == 1 else "散件")
|
||||||
|
|||||||
+21
-21
@@ -38,7 +38,7 @@ const DebugSessionScript := preload("res://presentation/debug_session.gd")
|
|||||||
const RowPacer := preload("res://presentation/row_pacer.gd") # 结算行排播泵(T3 拆件批)
|
const RowPacer := preload("res://presentation/row_pacer.gd") # 结算行排播泵(T3 拆件批)
|
||||||
const TreasureInteractController := preload("res://presentation/treasure_interact_controller.gd") # 秘宝两步交互(T3 拆件批)
|
const TreasureInteractController := preload("res://presentation/treasure_interact_controller.gd") # 秘宝两步交互(T3 拆件批)
|
||||||
const GpShadowLedgerScript := preload("res://presentation/gp_shadow_ledger.gd") # GP 影子账本(T3 拆件批)
|
const GpShadowLedgerScript := preload("res://presentation/gp_shadow_ledger.gd") # GP 影子账本(T3 拆件批)
|
||||||
const BonusInteractController := preload("res://presentation/bonus_interact_controller.gd") # 三选一与替换子交互(T4/D4 拆件批)
|
const CurioInteractController := preload("res://presentation/curio_interact_controller.gd") # 三选一与替换子交互(T4/D4 拆件批)
|
||||||
const CampShopPresenter := preload("res://presentation/camp_shop_presenter.gd") # 营地/商店投影(T4/D4 拆件批)
|
const CampShopPresenter := preload("res://presentation/camp_shop_presenter.gd") # 营地/商店投影(T4/D4 拆件批)
|
||||||
const SaveManagerScript := preload("res://core/save_manager.gd") # 存档承载(preload 避开全局类缓存时序——与 DebugSessionScript 同款)
|
const SaveManagerScript := preload("res://core/save_manager.gd") # 存档承载(preload 避开全局类缓存时序——与 DebugSessionScript 同款)
|
||||||
const MetaProgressionScript := preload("res://core/meta_progression.gd") # 元进度账本服务(元进度实现批——同 preload 纪律)
|
const MetaProgressionScript := preload("res://core/meta_progression.gd") # 元进度账本服务(元进度实现批——同 preload 纪律)
|
||||||
@@ -66,7 +66,7 @@ var _settlement_stop_pending: bool: # 挂起态属性化(探针/冒烟直读
|
|||||||
_settlement_stop_timer.disarm()
|
_settlement_stop_timer.disarm()
|
||||||
var _pending_candidates: Array = [] # 结算框挂起期的选路候选暂存(choices_presented 载荷)
|
var _pending_candidates: Array = [] # 结算框挂起期的选路候选暂存(choices_presented 载荷)
|
||||||
var _last_gain_amount := 0 # 最近一笔钱包入账(卡⑥ GP 增量取数面——wallet_changed gain 载荷)
|
var _last_gain_amount := 0 # 最近一笔钱包入账(卡⑥ GP 增量取数面——wallet_changed gain 载荷)
|
||||||
var bonus_overlay: BonusPickOverlay # 提升三选一覆盖层(迭代 10——level-clear.md v2 选择面承载;追加批起奖励节点共用)
|
var curio_overlay: CurioPickOverlay # 提升三选一覆盖层(迭代 10——level-clear.md v2 选择面承载;追加批起奖励节点共用)
|
||||||
var _start_overlay: Control
|
var _start_overlay: Control
|
||||||
var _end_overlay: BattleEndOverlay
|
var _end_overlay: BattleEndOverlay
|
||||||
var _inter_overlay: Control
|
var _inter_overlay: Control
|
||||||
@@ -91,7 +91,7 @@ var settlement_fx: SettlementFxView # 结算演出覆盖层(跨区弹道
|
|||||||
# T3 拆件批(2026-10-02)——三件迁出后的组合根持有面(职责见各类头注;装配见 _build_ui)
|
# T3 拆件批(2026-10-02)——三件迁出后的组合根持有面(职责见各类头注;装配见 _build_ui)
|
||||||
var _row_pacer = null # 结算行排播泵(row_pacer.gd——本类只入队/排空/读队列)
|
var _row_pacer = null # 结算行排播泵(row_pacer.gd——本类只入队/排空/读队列)
|
||||||
var _treasure_ui = null # 秘宝两步交互(treasure_interact_controller.gd——本类只接线与转发意图)
|
var _treasure_ui = null # 秘宝两步交互(treasure_interact_controller.gd——本类只接线与转发意图)
|
||||||
var _bonus = null # 三选一与替换子交互(bonus_interact_controller.gd——本类只接线与回调注入)
|
var _bonus = null # 三选一与替换子交互(curio_interact_controller.gd——本类只接线与回调注入)
|
||||||
var _camp_pres = null # 营地/商店投影(camp_shop_presenter.gd——本类只接线、探针缝保缝转发)
|
var _camp_pres = null # 营地/商店投影(camp_shop_presenter.gd——本类只接线、探针缝保缝转发)
|
||||||
var _gp_ledger := GpShadowLedgerScript.new() # GP 冒险钱包影子账(gp_shadow_ledger.gd——wallet 事件投喂)
|
var _gp_ledger := GpShadowLedgerScript.new() # GP 冒险钱包影子账(gp_shadow_ledger.gd——wallet 事件投喂)
|
||||||
|
|
||||||
@@ -261,20 +261,20 @@ func _build_ui() -> void:
|
|||||||
add_child(settings_overlay)
|
add_child(settings_overlay)
|
||||||
settings_overlay.closed.connect(_on_settings_closed)
|
settings_overlay.closed.connect(_on_settings_closed)
|
||||||
# 提升三选一覆盖层(迭代 10——z 序 = 节点层之上、终局层之下;确认后同拍推进)
|
# 提升三选一覆盖层(迭代 10——z 序 = 节点层之上、终局层之下;确认后同拍推进)
|
||||||
bonus_overlay = BonusPickOverlay.new()
|
curio_overlay = CurioPickOverlay.new()
|
||||||
add_child(bonus_overlay)
|
add_child(curio_overlay)
|
||||||
# 提升三选一与替换子交互(T4/D4 拆件批——迁 bonus_interact_controller.gd;本类只接线与回调注入,
|
# 提升三选一与替换子交互(T4/D4 拆件批——迁 curio_interact_controller.gd;本类只接线与回调注入,
|
||||||
# ADR-0001——模态栈/演播后排窗/呈现刷新/fatal 经回调注入,adventure/runner 现值回调)
|
# ADR-0001——模态栈/演播后排窗/呈现刷新/fatal 经回调注入,adventure/runner 现值回调)
|
||||||
_bonus = BonusInteractController.new(db, func() -> Variant: return meta, bonus_overlay, _end_overlay, board_view,
|
_bonus = CurioInteractController.new(db, func() -> Variant: return meta, curio_overlay, _end_overlay, board_view,
|
||||||
board_zone.toast, func() -> Variant: return save_manager, func() -> Variant: return adventure,
|
board_zone.toast, func() -> Variant: return save_manager, func() -> Variant: return adventure,
|
||||||
func() -> Variant: return runner, func() -> Variant: return _adventure_mode,
|
func() -> Variant: return runner, func() -> Variant: return _adventure_mode,
|
||||||
_open_modal, _open_modal_after_playback, _close_modal, _refresh_presentations,
|
_open_modal, _open_modal_after_playback, _close_modal, _refresh_presentations,
|
||||||
_fatal_screen, func() -> String: return _gp_summary_text({"gp_dropped": _last_gain_amount}))
|
_fatal_screen, func() -> String: return _gp_summary_text({"gp_dropped": _last_gain_amount}))
|
||||||
bonus_overlay.pick_confirmed.connect(_bonus.on_pick_confirmed)
|
curio_overlay.pick_confirmed.connect(_bonus.on_pick_confirmed)
|
||||||
bonus_overlay.replace_chosen.connect(_bonus.on_replace_chosen)
|
curio_overlay.replace_chosen.connect(_bonus.on_replace_chosen)
|
||||||
bonus_overlay.continue_pressed.connect(_bonus.on_continue)
|
curio_overlay.continue_pressed.connect(_bonus.on_continue)
|
||||||
bonus_overlay.refresh_requested.connect(_bonus.on_refresh)
|
curio_overlay.refresh_requested.connect(_bonus.on_refresh)
|
||||||
move_child(bonus_overlay, _end_overlay.get_index())
|
move_child(curio_overlay, _end_overlay.get_index())
|
||||||
|
|
||||||
var start: Dictionary = UiAssembler.build_start_overlay(self, db)
|
var start: Dictionary = UiAssembler.build_start_overlay(self, db)
|
||||||
_start_overlay = start.overlay
|
_start_overlay = start.overlay
|
||||||
@@ -329,7 +329,7 @@ func _build_ui() -> void:
|
|||||||
|
|
||||||
# 模态登记(缺陷⑤纪律——登记序 = 树序:含 move_child 调位后的关卡画面/三选一终位)
|
# 模态登记(缺陷⑤纪律——登记序 = 树序:含 move_child 调位后的关卡画面/三选一终位)
|
||||||
_modals = ModalStack.new()
|
_modals = ModalStack.new()
|
||||||
for m: Control in [legend_overlay, node_overlay, level_screen, bonus_overlay, _end_overlay, shop_overlay, camp_overlay, settings_overlay, confirm_dialog, debug_overlay]:
|
for m: Control in [legend_overlay, node_overlay, level_screen, curio_overlay, _end_overlay, shop_overlay, camp_overlay, settings_overlay, confirm_dialog, debug_overlay]:
|
||||||
_register_modal(m)
|
_register_modal(m)
|
||||||
_tune_sim = TuneSimController.new(db, debug_overlay, int(db.hero.max_hp))
|
_tune_sim = TuneSimController.new(db, debug_overlay, int(db.hero.max_hp))
|
||||||
|
|
||||||
@@ -450,8 +450,8 @@ func _wire_adventure() -> void:
|
|||||||
adventure.choices_presented.connect(_on_choices_presented)
|
adventure.choices_presented.connect(_on_choices_presented)
|
||||||
adventure.wallet_changed.connect(_on_wallet_changed)
|
adventure.wallet_changed.connect(_on_wallet_changed)
|
||||||
# 迭代 10 奖励重构批——big_reward_paid 退役,三选一信号族接线
|
# 迭代 10 奖励重构批——big_reward_paid 退役,三选一信号族接线
|
||||||
adventure.bonus_choice_presented.connect(_bonus.on_presented)
|
adventure.curio_choice_presented.connect(_bonus.on_presented)
|
||||||
adventure.bonus_resolved.connect(_bonus.on_resolved)
|
adventure.curio_resolved.connect(_bonus.on_resolved)
|
||||||
adventure.treasure_replace_presented.connect(_bonus.on_treasure_replace_presented)
|
adventure.treasure_replace_presented.connect(_bonus.on_treasure_replace_presented)
|
||||||
adventure.adventure_finished.connect(_on_adventure_finished)
|
adventure.adventure_finished.connect(_on_adventure_finished)
|
||||||
adventure.level_started.connect(_on_adventure_level_started)
|
adventure.level_started.connect(_on_adventure_level_started)
|
||||||
@@ -505,9 +505,9 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void:
|
|||||||
_show_board_fx_for_node(t)
|
_show_board_fx_for_node(t)
|
||||||
log_view.append_bb(Narrator.node_banner(runner, node, t))
|
log_view.append_bb(Narrator.node_banner(runner, node, t))
|
||||||
elif t == "reward":
|
elif t == "reward":
|
||||||
# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;BonusPickOverlay 共用面)。
|
# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;CurioPickOverlay 共用面)。
|
||||||
# T4/D4 拆件:呈现编排归 bonus_interact_controller.present_node_bonus(_bonus_context 随迁)。
|
# T4/D4 拆件:呈现编排归 curio_interact_controller.present_node_curio(_curio_context 随迁)。
|
||||||
_bonus.present_node_bonus()
|
_bonus.present_node_curio()
|
||||||
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))
|
||||||
@@ -564,13 +564,13 @@ func _on_battle_end_continue() -> void:
|
|||||||
_close_modal(_end_overlay)
|
_close_modal(_end_overlay)
|
||||||
_end_overlay.close()
|
_end_overlay.close()
|
||||||
# 迭代 10——三选一停点挂起分支:战斗总结结算(Boss 胜利钥石增量行)确认后进三选一面
|
# 迭代 10——三选一停点挂起分支:战斗总结结算(Boss 胜利钥石增量行)确认后进三选一面
|
||||||
if _bonus.resume_if_pending(): # T4/D4 拆件——三选一停点在途恢复入口(bonus_interact_controller)
|
if _bonus.resume_if_pending(): # T4/D4 拆件——三选一停点在途恢复入口(curio_interact_controller)
|
||||||
return
|
return
|
||||||
# 卡⑥ 挂起分派:战斗胜利总结结算框的「下一节点」≠ 终局——进选路页(暂存候选恢复呈现)
|
# 卡⑥ 挂起分派:战斗胜利总结结算框的「下一节点」≠ 终局——进选路页(暂存候选恢复呈现)
|
||||||
if _settlement_stop_pending:
|
if _settlement_stop_pending:
|
||||||
_settlement_stop_pending = false
|
_settlement_stop_pending = false
|
||||||
_enter_level_screen()
|
_enter_level_screen()
|
||||||
level_screen.set_relic_holdings(" · ".join(HudPresenter.relic_holdings_lines(db, runner.relics_held())))
|
level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(db, runner.feats_held())))
|
||||||
level_screen.open_choices(_pending_candidates, runner)
|
level_screen.open_choices(_pending_candidates, runner)
|
||||||
_pending_candidates = []
|
_pending_candidates = []
|
||||||
_refresh_presentations()
|
_refresh_presentations()
|
||||||
@@ -856,7 +856,7 @@ func _on_choices_presented(candidates: Array) -> void:
|
|||||||
_refresh_presentations()
|
_refresh_presentations()
|
||||||
return
|
return
|
||||||
_enter_level_screen()
|
_enter_level_screen()
|
||||||
level_screen.set_relic_holdings(" · ".join(HudPresenter.relic_holdings_lines(db, runner.relics_held())))
|
level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(db, runner.feats_held())))
|
||||||
level_screen.open_choices(candidates, runner)
|
level_screen.open_choices(candidates, runner)
|
||||||
_refresh_presentations()
|
_refresh_presentations()
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@
|
|||||||
# 同调位替换子交互。高内聚边界:文案拼装归 Narrator(组合根喂入),选择意图经信号外发;
|
# 同调位替换子交互。高内聚边界:文案拼装归 Narrator(组合根喂入),选择意图经信号外发;
|
||||||
# 视图零核心引用。交互范式 = node_overlay choice_box 按钮族 + 选路两段式(点选→确认)复用,零新原语。
|
# 视图零核心引用。交互范式 = node_overlay choice_box 按钮族 + 选路两段式(点选→确认)复用,零新原语。
|
||||||
# 状态机:pick(三卡点选→确认)→ [replace(满位 N+1 选 N)] → result(兑付反馈→继续)。
|
# 状态机:pick(三卡点选→确认)→ [replace(满位 N+1 选 N)] → result(兑付反馈→继续)。
|
||||||
class_name BonusPickOverlay
|
class_name CurioPickOverlay
|
||||||
extends Control
|
extends Control
|
||||||
|
|
||||||
signal pick_confirmed(item_id: String)
|
signal pick_confirmed(item_id: String)
|
||||||
@@ -162,7 +162,7 @@ func selected_id() -> String:
|
|||||||
return _selected
|
return _selected
|
||||||
|
|
||||||
|
|
||||||
# 兑付反馈态(bonus_resolved 到达——结果行追加在头部之后;唯一出口 = 继续)
|
# 兑付反馈态(curio_resolved 到达——结果行追加在头部之后;唯一出口 = 继续)
|
||||||
func show_result(text: String) -> void:
|
func show_result(text: String) -> void:
|
||||||
_label.text = _header + "\n" + text
|
_label.text = _header + "\n" + text
|
||||||
_choice_box.visible = false
|
_choice_box.visible = false
|
||||||
File renamed without changes.
@@ -19,10 +19,10 @@ var item_slots: HBoxContainer # 秘宝槽(迭代 7 T4 接线—
|
|||||||
var _slot_panels: Array = [] # 槽 PanelContainer 引用(选中高亮/置灰重刷)
|
var _slot_panels: Array = [] # 槽 PanelContainer 引用(选中高亮/置灰重刷)
|
||||||
var _selected_handle := 0 # 选中态(0=无;组合根回写维持单源)
|
var _selected_handle := 0 # 选中态(0=无;组合根回写维持单源)
|
||||||
var _slot_handles: Array = [] # 槽位→handle 映射(set_treasure_inventory 刷新)
|
var _slot_handles: Array = [] # 槽位→handle 映射(set_treasure_inventory 刷新)
|
||||||
var _bonus_section: VBoxContainer # 特性(预留——空态隐藏,接线归大奖励/锻造呈现批)
|
var _curio_section: VBoxContainer # 特性(预留——空态隐藏,接线归大奖励/锻造呈现批)
|
||||||
var _bonus_list: VBoxContainer
|
var _curio_list: VBoxContainer
|
||||||
var _relic_section: VBoxContainer # 特性持有段(迭代 12 呈现补齐——空态隐藏)
|
var _feat_section: VBoxContainer # 特性持有段(迭代 12 呈现补齐——空态隐藏)
|
||||||
var _relic_list: VBoxContainer
|
var _feat_list: VBoxContainer
|
||||||
var _board_variant_section: VBoxContainer # 地形变异常驻角标(全局作用域变异首发批——空态隐藏)
|
var _board_variant_section: VBoxContainer # 地形变异常驻角标(全局作用域变异首发批——空态隐藏)
|
||||||
var _board_variant_list: VBoxContainer
|
var _board_variant_list: VBoxContainer
|
||||||
|
|
||||||
@@ -73,14 +73,14 @@ func _init() -> void:
|
|||||||
# 特性持有段(迭代 12 呈现补齐 2026-10-04——三选一特性兑付后常驻可读:逐流派聚合行+成型档位;
|
# 特性持有段(迭代 12 呈现补齐 2026-10-04——三选一特性兑付后常驻可读:逐流派聚合行+成型档位;
|
||||||
# 空态整节隐藏。用户试玩反馈「局内奖励选了什么看不到」缺口承载;2026-10-04 二次反馈上移——
|
# 空态整节隐藏。用户试玩反馈「局内奖励选了什么看不到」缺口承载;2026-10-04 二次反馈上移——
|
||||||
# 构筑核心信息优先级高于临时强化,且防 C 区下部裁切不可见)
|
# 构筑核心信息优先级高于临时强化,且防 C 区下部裁切不可见)
|
||||||
_relic_section = VBoxContainer.new()
|
_feat_section = VBoxContainer.new()
|
||||||
_relic_section.add_theme_constant_override("separation", 4)
|
_feat_section.add_theme_constant_override("separation", 4)
|
||||||
_relic_section.visible = false
|
_feat_section.visible = false
|
||||||
col.add_child(_relic_section)
|
col.add_child(_feat_section)
|
||||||
_relic_section.add_child(UiKit.label("特性", 16, true))
|
_feat_section.add_child(UiKit.label("特性", 16, true))
|
||||||
_relic_list = VBoxContainer.new()
|
_feat_list = VBoxContainer.new()
|
||||||
_relic_list.add_theme_constant_override("separation", 2)
|
_feat_list.add_theme_constant_override("separation", 2)
|
||||||
_relic_section.add_child(_relic_list)
|
_feat_section.add_child(_feat_list)
|
||||||
# 地形变异常驻角标(全局作用域变异首发批 2026-10-04——Boss 图整图型战斗全程可见防中局遗忘;
|
# 地形变异常驻角标(全局作用域变异首发批 2026-10-04——Boss 图整图型战斗全程可见防中局遗忘;
|
||||||
# 空态整节隐藏=特性段先例;摆位/文案 provisional 归 HUD 框架批与本地化批)
|
# 空态整节隐藏=特性段先例;摆位/文案 provisional 归 HUD 框架批与本地化批)
|
||||||
_board_variant_section = VBoxContainer.new()
|
_board_variant_section = VBoxContainer.new()
|
||||||
@@ -93,14 +93,14 @@ func _init() -> void:
|
|||||||
_board_variant_section.add_child(_board_variant_list)
|
_board_variant_section.add_child(_board_variant_list)
|
||||||
# 特性(预留):锻造/大奖励/关卡奖励节点的属性提升——中循环数据因改变战斗数学准入;
|
# 特性(预留):锻造/大奖励/关卡奖励节点的属性提升——中循环数据因改变战斗数学准入;
|
||||||
# 与战斗内状态效果(B 区站桩,随战斗消散)分区,防「战斗结束被清」误读;空态整节隐藏
|
# 与战斗内状态效果(B 区站桩,随战斗消散)分区,防「战斗结束被清」误读;空态整节隐藏
|
||||||
_bonus_section = VBoxContainer.new()
|
_curio_section = VBoxContainer.new()
|
||||||
_bonus_section.add_theme_constant_override("separation", 4)
|
_curio_section.add_theme_constant_override("separation", 4)
|
||||||
_bonus_section.visible = false
|
_curio_section.visible = false
|
||||||
col.add_child(_bonus_section)
|
col.add_child(_curio_section)
|
||||||
_bonus_section.add_child(UiKit.label("特性", 16, true))
|
_curio_section.add_child(UiKit.label("特性", 16, true))
|
||||||
_bonus_list = VBoxContainer.new()
|
_curio_list = VBoxContainer.new()
|
||||||
_bonus_list.add_theme_constant_override("separation", 2)
|
_curio_list.add_theme_constant_override("separation", 2)
|
||||||
_bonus_section.add_child(_bonus_list)
|
_curio_section.add_child(_curio_list)
|
||||||
|
|
||||||
|
|
||||||
func set_attack(atk: int, mod_text: String) -> void:
|
func set_attack(atk: int, mod_text: String) -> void:
|
||||||
@@ -114,14 +114,14 @@ func set_armor(ac: int) -> void:
|
|||||||
|
|
||||||
|
|
||||||
# 特性持有段(迭代 12 呈现补齐):lines = 聚合文案行(「魔法 ×2 · 档一」式)——空数组 = 整节隐藏
|
# 特性持有段(迭代 12 呈现补齐):lines = 聚合文案行(「魔法 ×2 · 档一」式)——空数组 = 整节隐藏
|
||||||
func set_relic_holdings(lines: Array) -> void:
|
func set_feat_holdings(lines: Array) -> void:
|
||||||
for c2 in _relic_list.get_children():
|
for c2 in _feat_list.get_children():
|
||||||
_relic_list.remove_child(c2)
|
_feat_list.remove_child(c2)
|
||||||
c2.queue_free()
|
c2.queue_free()
|
||||||
for line in lines:
|
for line in lines:
|
||||||
var l2 := UiKit.label(String(line), 16, false, COL_INK)
|
var l2 := UiKit.label(String(line), 16, false, COL_INK)
|
||||||
_relic_list.add_child(l2)
|
_feat_list.add_child(l2)
|
||||||
_relic_section.visible = not lines.is_empty()
|
_feat_section.visible = not lines.is_empty()
|
||||||
|
|
||||||
|
|
||||||
# 地形变异常驻角标(首发批 2026-10-04):lines = 文案行(板级变异 display_name+体感短语)——空数组 = 整节隐藏
|
# 地形变异常驻角标(首发批 2026-10-04):lines = 文案行(板级变异 display_name+体感短语)——空数组 = 整节隐藏
|
||||||
@@ -137,13 +137,13 @@ func set_board_variants(lines: Array) -> void:
|
|||||||
|
|
||||||
# 特性列表(预留接线面):lines = 文案行数组(如「锻造 +2 攻击」)——空数组 = 整节隐藏
|
# 特性列表(预留接线面):lines = 文案行数组(如「锻造 +2 攻击」)——空数组 = 整节隐藏
|
||||||
func set_level_bonuses(lines: Array) -> void:
|
func set_level_bonuses(lines: Array) -> void:
|
||||||
for c in _bonus_list.get_children():
|
for c in _curio_list.get_children():
|
||||||
_bonus_list.remove_child(c)
|
_curio_list.remove_child(c)
|
||||||
c.queue_free()
|
c.queue_free()
|
||||||
for line in lines:
|
for line in lines:
|
||||||
var l := UiKit.label(String(line), 16, false, COL_INK) # 字号下限(v3.5 批 B3——正文 ≥16)
|
var l := UiKit.label(String(line), 16, false, COL_INK) # 字号下限(v3.5 批 B3——正文 ≥16)
|
||||||
_bonus_list.add_child(l)
|
_curio_list.add_child(l)
|
||||||
_bonus_section.visible = not lines.is_empty()
|
_curio_section.visible = not lines.is_empty()
|
||||||
|
|
||||||
|
|
||||||
func attack_text() -> String:
|
func attack_text() -> String:
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ var _hp_bar: Control
|
|||||||
var _attr_value: Label
|
var _attr_value: Label
|
||||||
var _bonus_row: HBoxContainer
|
var _bonus_row: HBoxContainer
|
||||||
var _bonus_value: Label
|
var _bonus_value: Label
|
||||||
var _relic_value: Label # 特性持有行(迭代 12 呈现补齐——组合根投喂聚合文案)
|
var _feat_value: Label # 特性持有行(迭代 12 呈现补齐——组合根投喂聚合文案)
|
||||||
var _cand_ids: Array[int] = [] # 本停点候选 id 集(图上命中合法集)
|
var _cand_ids: Array[int] = [] # 本停点候选 id 集(图上命中合法集)
|
||||||
var _content_size := Vector2(620, 240) # 图内容包络(open_choices 按图拟合——画布 min 尺寸随它)
|
var _content_size := Vector2(620, 240) # 图内容包络(open_choices 按图拟合——画布 min 尺寸随它)
|
||||||
var _hover_id := -1 # 悬停预承诺候选(-1 = 无)
|
var _hover_id := -1 # 悬停预承诺候选(-1 = 无)
|
||||||
@@ -121,7 +121,7 @@ func _init() -> void:
|
|||||||
box.add_child(_canvas)
|
box.add_child(_canvas)
|
||||||
# 底部状态栏(M5 合并单条:亮米面板一站读齐=头像+生命+属性+本关加成/流派件——demo ④;
|
# 底部状态栏(M5 合并单条:亮米面板一站读齐=头像+生命+属性+本关加成/流派件——demo ④;
|
||||||
# 道具槽不摆=道具读数无关卡画面成文出口(读快照私域越权),随 runner 出口批补——台账 352 注记;
|
# 道具槽不摆=道具读数无关卡画面成文出口(读快照私域越权),随 runner 出口批补——台账 352 注记;
|
||||||
# _hp_value/_attr_value/_bonus_value/_relic_value 投喂键全部保留)
|
# _hp_value/_attr_value/_bonus_value/_feat_value 投喂键全部保留)
|
||||||
var bottom := PanelContainer.new()
|
var bottom := PanelContainer.new()
|
||||||
bottom.add_theme_stylebox_override("panel",
|
bottom.add_theme_stylebox_override("panel",
|
||||||
UiKit.panel_flat(Color8(240, 228, 200), UiKit.CAMP_HEADER_BAND, 0, 2, Vector4(12, 6, 12, 6)))
|
UiKit.panel_flat(Color8(240, 228, 200), UiKit.CAMP_HEADER_BAND, 0, 2, Vector4(12, 6, 12, 6)))
|
||||||
@@ -166,9 +166,9 @@ func _init() -> void:
|
|||||||
_bonus_value = UiKit.label("", 15, true, UiKit.WARM)
|
_bonus_value = UiKit.label("", 15, true, UiKit.WARM)
|
||||||
bonus_inner.add_child(_bonus_value)
|
bonus_inner.add_child(_bonus_value)
|
||||||
# 特性持有行(迭代 12 呈现补齐 2026-10-04——用户反馈关卡选择页看不到局内奖励;组合根投喂)
|
# 特性持有行(迭代 12 呈现补齐 2026-10-04——用户反馈关卡选择页看不到局内奖励;组合根投喂)
|
||||||
_relic_value = UiKit.label("", 13, false, UiKit.WARM_SOFT)
|
_feat_value = UiKit.label("", 13, false, UiKit.WARM_SOFT)
|
||||||
_relic_value.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
_feat_value.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
bonus_box.add_child(_relic_value)
|
bonus_box.add_child(_feat_value)
|
||||||
var spacer := Control.new()
|
var spacer := Control.new()
|
||||||
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
strip.add_child(spacer)
|
strip.add_child(spacer)
|
||||||
@@ -205,8 +205,8 @@ func _draw_hp_bar(bar: Control) -> void:
|
|||||||
|
|
||||||
# 放弃入口可见性(组合根在冒险启动时开、默认关——语义对象 = 整局冒险)
|
# 放弃入口可见性(组合根在冒险启动时开、默认关——语义对象 = 整局冒险)
|
||||||
# 特性持有投影(迭代 12 呈现补齐——组合根投喂聚合文案;空文案=行隐藏)
|
# 特性持有投影(迭代 12 呈现补齐——组合根投喂聚合文案;空文案=行隐藏)
|
||||||
func set_relic_holdings(text: String) -> void:
|
func set_feat_holdings(text: String) -> void:
|
||||||
_relic_value.text = text
|
_feat_value.text = text
|
||||||
|
|
||||||
|
|
||||||
func set_abandon_available(v: bool) -> void:
|
func set_abandon_available(v: bool) -> void:
|
||||||
@@ -339,8 +339,8 @@ func _refresh_status() -> void:
|
|||||||
if ac_bonus > 0:
|
if ac_bonus > 0:
|
||||||
parts.append("AC +%d" % ac_bonus)
|
parts.append("AC +%d" % ac_bonus)
|
||||||
_bonus_value.text = " · ".join(parts)
|
_bonus_value.text = " · ".join(parts)
|
||||||
# 特性持有行可见性(与加成行并框——任一非空整框显示;文案归组合根 set_relic_holdings)
|
# 特性持有行可见性(与加成行并框——任一非空整框显示;文案归组合根 set_feat_holdings)
|
||||||
_bonus_row.visible = not parts.is_empty() or _relic_value.text != ""
|
_bonus_row.visible = not parts.is_empty() or _feat_value.text != ""
|
||||||
|
|
||||||
|
|
||||||
# ---- 图上命中(布局决策 2——唯一提交通道;探针 press_candidate 同路径) ----
|
# ---- 图上命中(布局决策 2——唯一提交通道;探针 press_candidate 同路径) ----
|
||||||
|
|||||||
@@ -45,20 +45,20 @@ func _initialize() -> void:
|
|||||||
# 特性/树分层开关(迭代 12 T7/T10——off=基线;formed=成型画像开局注入七流派各 2 件档一成型集
|
# 特性/树分层开关(迭代 12 T7/T10——off=基线;formed=成型画像开局注入七流派各 2 件档一成型集
|
||||||
# 〔测量面专用 params.relics_seed——真实局规则改写型特性只走局内随机渠道〕;ab_a/ab_b=Fork posture 两侧
|
# 〔测量面专用 params.relics_seed——真实局规则改写型特性只走局内随机渠道〕;ab_a/ab_b=Fork posture 两侧
|
||||||
# rule 键集——A/B 面两侧差 <~3pp 验收载体)
|
# rule 键集——A/B 面两侧差 <~3pp 验收载体)
|
||||||
var relic_mode := "off"
|
var feat_mode := "off"
|
||||||
if args.size() >= 7 and args[6] in ["off", "formed", "ab_a", "ab_b"]:
|
if args.size() >= 7 and args[6] in ["off", "formed", "ab_a", "ab_b"]:
|
||||||
relic_mode = args[6]
|
feat_mode = args[6]
|
||||||
var relic_profile := {}
|
var feat_profile := {}
|
||||||
if relic_mode == "formed":
|
if feat_mode == "formed":
|
||||||
relic_profile = {"mode": "on", "seed_ids": [
|
feat_profile = {"mode": "on", "seed_ids": [
|
||||||
"arcane_ember", "mana_weaver", "pierce_shot", "chain_arrows",
|
"arcane_ember", "mana_weaver", "pierce_shot", "chain_arrows",
|
||||||
"miner_stubborn", "miner_hammer", "greedy_lantern", "vein_intuition",
|
"miner_stubborn", "miner_hammer", "greedy_lantern", "vein_intuition",
|
||||||
"mine_atlas", "nest_hunter", "armory_prep", "iron_wall",
|
"mine_atlas", "nest_hunter", "armory_prep", "iron_wall",
|
||||||
"ley_harvest", "thorn_weaver"]}
|
"ley_harvest", "thorn_weaver"]}
|
||||||
elif relic_mode == "ab_a":
|
elif feat_mode == "ab_a":
|
||||||
relic_profile = {"mode": "off", "tree_rules": ["posture_first_strike"]}
|
feat_profile = {"mode": "off", "tree_rules": ["posture_first_strike"]}
|
||||||
elif relic_mode == "ab_b":
|
elif feat_mode == "ab_b":
|
||||||
relic_profile = {"mode": "off", "tree_rules": ["posture_bulwark"]}
|
feat_profile = {"mode": "off", "tree_rules": ["posture_bulwark"]}
|
||||||
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("表加载失败")
|
||||||
@@ -103,12 +103,12 @@ func _initialize() -> void:
|
|||||||
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, {}, attune_profile,
|
var st = autoplay.run_adventure(db, seed_val, 0, 0, false, logger, policy, route, {}, attune_profile,
|
||||||
{"mode": "pool"} if shop_mode == "on" else {}, relic_profile)
|
{"mode": "pool"} if shop_mode == "on" else {}, feat_profile)
|
||||||
var sum: Dictionary = st["summary"]
|
var 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", []),
|
"attune": attune_mode, "equipped": attune_profile.get("equipped", []),
|
||||||
"relic_mode": relic_mode,
|
"feat_mode": feat_mode,
|
||||||
"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)),
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_
|
|||||||
"log_path": "", "policy": policy, "refreshes": 0, "mine_hits": 0, "mine_dmg": 0,
|
"log_path": "", "policy": policy, "refreshes": 0, "mine_hits": 0, "mine_dmg": 0,
|
||||||
"visited_ids": [], "choices": 0, "last_battle_enemies": [],
|
"visited_ids": [], "choices": 0, "last_battle_enemies": [],
|
||||||
"summary": {}, "wallet_events": [], "encounter_max_hp_seq": [], "reward_events": [],
|
"summary": {}, "wallet_events": [], "encounter_max_hp_seq": [], "reward_events": [],
|
||||||
"relic_mode": String(relic.get("mode", "off")), # 特性分层开关(迭代 12 T7——off=316-f 通用竞争/on=成型画像优先捕捞 relic)
|
"feat_mode": String(relic.get("mode", "off")), # 特性分层开关(迭代 12 T7——off=316-f 通用竞争/on=成型画像优先捕捞 relic)
|
||||||
"shop_gp_seq": [], # 商店房到达余额序(逐关 1 次——GP 跨关延续验收取数面)
|
"shop_gp_seq": [], # 商店房到达余额序(逐关 1 次——GP 跨关延续验收取数面)
|
||||||
# 旗经济五字段(迭代14②判式换代批 T1——与 run_level 同口径,见彼处注释)
|
# 旗经济五字段(迭代14②判式换代批 T1——与 run_level 同口径,见彼处注释)
|
||||||
"flag_placed": 0, "flag_hit": 0, "flag_miss": 0, "flag_hit_gain": 0, "flag_miss_penalty": 0,
|
"flag_placed": 0, "flag_hit": 0, "flag_miss": 0, "flag_hit_gain": 0, "flag_miss_penalty": 0,
|
||||||
@@ -151,8 +151,8 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_
|
|||||||
adr.wallet_changed.connect(_on_adventure_wallet.bind(st, logger))
|
adr.wallet_changed.connect(_on_adventure_wallet.bind(st, logger))
|
||||||
# 三选一停点驱动(迭代 10 奖励重构批——big_reward_paid 退役:确定性策略候选取首件、
|
# 三选一停点驱动(迭代 10 奖励重构批——big_reward_paid 退役:确定性策略候选取首件、
|
||||||
# 道具件满位替换放弃新件、兑付后推进下一关——零随机流自开,同种子逐位可复放)
|
# 道具件满位替换放弃新件、兑付后推进下一关——零随机流自开,同种子逐位可复放)
|
||||||
adr.bonus_choice_presented.connect(_on_adventure_bonus.bind(st, adr))
|
adr.curio_choice_presented.connect(_on_adventure_bonus.bind(st, adr))
|
||||||
adr.bonus_resolved.connect(_on_adventure_bonus_resolved.bind(st, adr))
|
adr.curio_resolved.connect(_on_adventure_curio_resolved.bind(st, adr))
|
||||||
adr.treasure_replace_presented.connect(_on_adventure_treasure_replace.bind(st, adr))
|
adr.treasure_replace_presented.connect(_on_adventure_treasure_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)
|
||||||
@@ -220,17 +220,24 @@ static func _on_adventure_node_entered(node: Dictionary, info: Dictionary, adr,
|
|||||||
elif t == "reward":
|
elif t == "reward":
|
||||||
# 奖励节点三选一(迭代 10 追加批——共享池确定性策略;TP0 起 316-f 判读:1 级最稀有、
|
# 奖励节点三选一(迭代 10 追加批——共享池确定性策略;TP0 起 316-f 判读:1 级最稀有、
|
||||||
# 同档并列取最左;替换放弃新件;兑付后 continue_after_node 回图选路)
|
# 同档并列取最左;替换放弃新件;兑付后 continue_after_node 回图选路)
|
||||||
var ndraw: Dictionary = adr.node_bonus_candidates()
|
var ndraw: Dictionary = adr.node_curio_candidates()
|
||||||
var ncands: Array = ndraw.get("candidates", []) as Array
|
var ncands: Array = ndraw.get("candidates", []) as Array
|
||||||
st.reward_events.append({"node_bonus_pick": ncands.duplicate(true)})
|
st.reward_events.append({"node_curio_pick": ncands.duplicate(true)})
|
||||||
if ncands.is_empty():
|
if ncands.is_empty():
|
||||||
r.continue_after_node() # 牌库空合法态直通(池耗即枯竭——防御保留)
|
r.continue_after_node() # 牌库空合法态直通(池耗即枯竭——防御保留)
|
||||||
else:
|
else:
|
||||||
adr.present_node_bonus(ncands, ndraw.get("aux_detail", []) as Array)
|
adr.present_node_curio(ncands, ndraw.get("aux_detail", []) as Array)
|
||||||
var npick: Dictionary = _pick_token(ncands, String(st.get("relic_mode", "off")))
|
var npick: Dictionary = _pick_token(ncands, String(st.get("feat_mode", "off")))
|
||||||
if adr.apply_node_bonus(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))):
|
if adr.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", ""))):
|
||||||
if not adr._node_bonus_pending.is_empty() and adr._node_bonus_pending.has("incoming"):
|
# T5 门票拿到即开——候选已被替换为专长三选一,就地再选一轮(确定性策略同款 316-f);
|
||||||
adr.resolve_bonus_replace(-1) # 满位替换:放弃新件
|
# 不作此分支则节点停点悬空→选路卡死(MC timeout 形态,2026-10-05 批内实测)
|
||||||
|
if String(npick.get("kind", "")) == "feat_ticket":
|
||||||
|
var fcands: Array = (adr.node_curio_pending_state().get("candidates", []) as Array)
|
||||||
|
if not fcands.is_empty():
|
||||||
|
var fpick: Dictionary = _pick_token(fcands, String(st.get("feat_mode", "off")))
|
||||||
|
adr.apply_node_curio(String(fpick.get("kind", "")), String(fpick.get("tier", "")), String(fpick.get("feat_id", "")))
|
||||||
|
if not adr._node_curio_pending.is_empty() and adr._node_curio_pending.has("incoming"):
|
||||||
|
adr.resolve_curio_replace(-1) # 满位替换:放弃新件
|
||||||
else:
|
else:
|
||||||
st.result = "advance_failed"
|
st.result = "advance_failed"
|
||||||
elif t == "rest":
|
elif t == "rest":
|
||||||
@@ -260,13 +267,13 @@ static func _on_adventure_wallet(event: String, gp_val: int, extra: Dictionary,
|
|||||||
# 三选一停点驱动(迭代 10——确定性策略;TP0 起 316-f 判读:1 级最稀有、同档并列取最左;
|
# 三选一停点驱动(迭代 10——确定性策略;TP0 起 316-f 判读:1 级最稀有、同档并列取最左;
|
||||||
# 奖励事件随 reward_events 留档)
|
# 奖励事件随 reward_events 留档)
|
||||||
static func _on_adventure_bonus(payload: Dictionary, st: Dictionary, adr) -> void:
|
static func _on_adventure_bonus(payload: Dictionary, st: Dictionary, adr) -> void:
|
||||||
st.reward_events.append({"bonus_pick": payload.duplicate(true)})
|
st.reward_events.append({"curio_pick": payload.duplicate(true)})
|
||||||
var candidates: Array = payload.get("candidates", []) as Array
|
var candidates: Array = payload.get("candidates", []) as Array
|
||||||
if candidates.is_empty():
|
if candidates.is_empty():
|
||||||
st.result = "advance_failed"
|
st.result = "advance_failed"
|
||||||
return
|
return
|
||||||
var pick: Dictionary = _pick_token(candidates, String(st.get("relic_mode", "off")))
|
var pick: Dictionary = _pick_token(candidates, String(st.get("feat_mode", "off")))
|
||||||
if not adr.pick_bonus(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("relic_id", ""))):
|
if not adr.pick_curio(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("feat_id", ""))):
|
||||||
st.result = "advance_failed"
|
st.result = "advance_failed"
|
||||||
|
|
||||||
|
|
||||||
@@ -274,15 +281,15 @@ static func _on_adventure_bonus(payload: Dictionary, st: Dictionary, adr) -> voi
|
|||||||
# 候选并列同档取最左=稳定序保证。空候选/未知档位 = 回退最左防御口径。
|
# 候选并列同档取最左=稳定序保证。空候选/未知档位 = 回退最左防御口径。
|
||||||
# 成型画像(迭代 12 T7——relic 模式 on:候选含 relic 令牌时优先在 relic 集内按 316-f 择一
|
# 成型画像(迭代 12 T7——relic 模式 on:候选含 relic 令牌时优先在 relic 集内按 316-f 择一
|
||||||
# 〔贪心捕捞→特性入局→成型画像全流派覆盖——R3 乙单锚承载〕;off=relic 与属性加成型特性同规则竞争)
|
# 〔贪心捕捞→特性入局→成型画像全流派覆盖——R3 乙单锚承载〕;off=relic 与属性加成型特性同规则竞争)
|
||||||
static func _pick_token(candidates: Array, relic_mode: String = "off") -> Dictionary:
|
static func _pick_token(candidates: Array, feat_mode: String = "off") -> Dictionary:
|
||||||
var pool: Array = candidates
|
var pool: Array = candidates
|
||||||
if relic_mode == "on":
|
if feat_mode == "on":
|
||||||
var relics_only: Array = []
|
var feats_only: Array = []
|
||||||
for tok in candidates:
|
for tok in candidates:
|
||||||
if String((tok as Dictionary).get("kind", "")) == "relic":
|
if String((tok as Dictionary).get("kind", "")) == "relic":
|
||||||
relics_only.append(tok)
|
feats_only.append(tok)
|
||||||
if not relics_only.is_empty():
|
if not feats_only.is_empty():
|
||||||
pool = relics_only
|
pool = feats_only
|
||||||
var best: Dictionary = {}
|
var best: Dictionary = {}
|
||||||
var best_rank := -1
|
var best_rank := -1
|
||||||
for tok in pool:
|
for tok in pool:
|
||||||
@@ -304,13 +311,13 @@ static func _pick_token(candidates: Array, relic_mode: String = "off") -> Dictio
|
|||||||
# 同调位满位替换子交互(确定性策略:放弃新件不获取——分发面按停点上下文裁决节点/通关渠道)
|
# 同调位满位替换子交互(确定性策略:放弃新件不获取——分发面按停点上下文裁决节点/通关渠道)
|
||||||
static func _on_adventure_treasure_replace(items: Array, incoming: Dictionary, st: Dictionary, adr) -> void:
|
static func _on_adventure_treasure_replace(items: Array, incoming: Dictionary, st: Dictionary, adr) -> void:
|
||||||
st.reward_events.append({"treasure_replace": incoming.duplicate(true)})
|
st.reward_events.append({"treasure_replace": incoming.duplicate(true)})
|
||||||
if not adr.resolve_bonus_replace(-1):
|
if not adr.resolve_curio_replace(-1):
|
||||||
st.result = "advance_failed"
|
st.result = "advance_failed"
|
||||||
|
|
||||||
|
|
||||||
# 三选一兑付完成 → 上下文分派(通关停点=推进下一关 / 奖励节点=回图选路)
|
# 三选一兑付完成 → 上下文分派(通关停点=推进下一关 / 奖励节点=回图选路)
|
||||||
static func _on_adventure_bonus_resolved(result: Dictionary, st: Dictionary, adr) -> void:
|
static func _on_adventure_curio_resolved(result: Dictionary, st: Dictionary, adr) -> void:
|
||||||
st.reward_events.append({"bonus_resolved": result.duplicate(true)})
|
st.reward_events.append({"curio_resolved": result.duplicate(true)})
|
||||||
if String(result.get("context", "")) == "reward_node":
|
if String(result.get("context", "")) == "reward_node":
|
||||||
var r = adr.runner()
|
var r = adr.runner()
|
||||||
if r == null or not r.continue_after_node():
|
if r == null or not r.continue_after_node():
|
||||||
|
|||||||
+158
-115
@@ -112,7 +112,7 @@ func _run_core_smoke(db, rng) -> void:
|
|||||||
_smoke_adventure_table()
|
_smoke_adventure_table()
|
||||||
_smoke_adventure_api()
|
_smoke_adventure_api()
|
||||||
_smoke_adventure()
|
_smoke_adventure()
|
||||||
_smoke_relics()
|
_smoke_feats()
|
||||||
_smoke_save()
|
_smoke_save()
|
||||||
_smoke_meta()
|
_smoke_meta()
|
||||||
_smoke_tree()
|
_smoke_tree()
|
||||||
@@ -220,12 +220,12 @@ func _smoke_adventure_table() -> void:
|
|||||||
check(int(_db.adventure.tier_total(tier)) == int(_db.adventure.TIER_QUOTAS[tier]),
|
check(int(_db.adventure.tier_total(tier)) == int(_db.adventure.TIER_QUOTAS[tier]),
|
||||||
"档位配额结构锚(v2.2 追认 b):%s = %d 张" % [tier, int(_db.adventure.TIER_QUOTAS[tier])])
|
"档位配额结构锚(v2.2 追认 b):%s = %d 张" % [tier, int(_db.adventure.TIER_QUOTAS[tier])])
|
||||||
check(int(_db.adventure.deck_total()) == 98, "令牌池总量 = 98 张(普通 70/稀有 25/罕见 3——罕见档随道具件池种退役收窄)")
|
check(int(_db.adventure.deck_total()) == 98, "令牌池总量 = 98 张(普通 70/稀有 25/罕见 3——罕见档随道具件池种退役收窄)")
|
||||||
check(int(_db.adventure.feature_value("gp", "common")) == 20 and int(_db.adventure.feature_value("gp", "rare")) == 50
|
check(int(_db.adventure.curio_value("gp", "common")) == 20 and int(_db.adventure.curio_value("gp", "rare")) == 50
|
||||||
and int(_db.adventure.feature_value("attack", "common")) == 1 and int(_db.adventure.feature_value("attack", "rare")) == 2
|
and int(_db.adventure.curio_value("attack", "common")) == 1 and int(_db.adventure.curio_value("attack", "rare")) == 2
|
||||||
and int(_db.adventure.feature_value("max_hp", "common")) == 5 and int(_db.adventure.feature_value("max_hp", "rare")) == 10
|
and int(_db.adventure.curio_value("max_hp", "common")) == 5 and int(_db.adventure.curio_value("max_hp", "rare")) == 10
|
||||||
and int(_db.adventure.feature_value("armor", "common")) == 1 and int(_db.adventure.feature_value("armor", "rare")) == 2,
|
and int(_db.adventure.curio_value("armor", "common")) == 1 and int(_db.adventure.curio_value("armor", "rare")) == 2,
|
||||||
"数值对(v2.2):小GP 20/大GP 50 · 小增伤 +1/大 +2 · 小生命 +5/大 +10 · 小护甲 +1/大 +2")
|
"数值对(v2.2):小GP 20/大GP 50 · 小增伤 +1/大 +2 · 小生命 +5/大 +10 · 小护甲 +1/大 +2")
|
||||||
check(int(_db.adventure.feature_value("heal", "rare")) == 6 and int(_db.adventure.feature_value("ap", "rarest")) == 1,
|
check(int(_db.adventure.curio_value("heal", "rare")) == 6 and int(_db.adventure.curio_value("ap", "rarest")) == 1,
|
||||||
"恢复件 +6 当即回血(新入池种类,rare 档)· AP +1(rarest 档)")
|
"恢复件 +6 当即回血(新入池种类,rare 档)· AP +1(rarest 档)")
|
||||||
check(_db.attunement_growth != null and int(_db.attunement_slot_cap()) == 3
|
check(_db.attunement_growth != null and int(_db.attunement_slot_cap()) == 3
|
||||||
and Attunement.slots(_db, null) == 0,
|
and Attunement.slots(_db, null) == 0,
|
||||||
@@ -327,12 +327,12 @@ func _smoke_adventure() -> void:
|
|||||||
var resolves: Array = []
|
var resolves: Array = []
|
||||||
var node_picks := 0
|
var node_picks := 0
|
||||||
for ev in (st["reward_events"] as Array):
|
for ev in (st["reward_events"] as Array):
|
||||||
if (ev as Dictionary).has("bonus_pick"):
|
if (ev as Dictionary).has("curio_pick"):
|
||||||
picks.append((ev as Dictionary)["bonus_pick"])
|
picks.append((ev as Dictionary)["curio_pick"])
|
||||||
elif (ev as Dictionary).has("node_bonus_pick"):
|
elif (ev as Dictionary).has("node_curio_pick"):
|
||||||
node_picks += 1 # 奖励节点三选一(追加批——每关奖励节点各 1 次)
|
node_picks += 1 # 奖励节点三选一(追加批——每关奖励节点各 1 次)
|
||||||
elif (ev as Dictionary).has("bonus_resolved"):
|
elif (ev as Dictionary).has("curio_resolved"):
|
||||||
resolves.append((ev as Dictionary)["bonus_resolved"])
|
resolves.append((ev as Dictionary)["curio_resolved"])
|
||||||
check(picks.size() == 2 and resolves.size() == 2 + node_picks,
|
check(picks.size() == 2 and resolves.size() == 2 + node_picks,
|
||||||
"通关三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)")
|
"通关三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)")
|
||||||
check(node_picks >= 1, "奖励节点三选一 ≥1 次(L1 链必经 1 个;L2/L3 图可选路经若干——实际经停 %d)" % node_picks)
|
check(node_picks >= 1, "奖励节点三选一 ≥1 次(L1 链必经 1 个;L2/L3 图可选路经若干——实际经停 %d)" % node_picks)
|
||||||
@@ -349,7 +349,7 @@ func _smoke_adventure() -> void:
|
|||||||
var cand_ok := picks.size() == 2
|
var cand_ok := picks.size() == 2
|
||||||
var ks_gain_ok := picks.size() == 2
|
var ks_gain_ok := picks.size() == 2
|
||||||
var resolve_ok := level_resolves.size() == 2
|
var resolve_ok := level_resolves.size() == 2
|
||||||
var max_flat := int(_db.adventure.feature_value_max_hp_common)
|
var max_flat := int(_db.adventure.curio_value_max_hp_common)
|
||||||
var heal_pct := int(_db.adventure.clear_heal_pct)
|
var heal_pct := int(_db.adventure.clear_heal_pct)
|
||||||
var hp_acc := 0 # max_hp 件累加(回血封顶断言的生效上限推导——TP0 起按兑付令牌实值,大小档分别计)
|
var hp_acc := 0 # max_hp 件累加(回血封顶断言的生效上限推导——TP0 起按兑付令牌实值,大小档分别计)
|
||||||
var ks_prefix := 0
|
var ks_prefix := 0
|
||||||
@@ -360,9 +360,9 @@ func _smoke_adventure() -> void:
|
|||||||
cand_ok = false
|
cand_ok = false
|
||||||
var seen := {}
|
var seen := {}
|
||||||
for c in cands:
|
for c in cands:
|
||||||
# 身份键 = kind|tier|relic_id(迭代14① T4 断言基线复核——TP0 v2.2 relic 按 relic_id 分列
|
# 身份键 = kind|tier|feat_id(迭代14① T4 断言基线复核——TP0 v2.2 relic 按 feat_id 分列
|
||||||
# 不参与同 kind 去重〔_token_identity relic: 前缀〕,同 tier 三枚不同流派件=三个不同选项合法态)
|
# 不参与同 kind 去重〔_token_identity relic: 前缀〕,同 tier 三枚不同流派件=三个不同选项合法态)
|
||||||
var ckey := "%s|%s|%s" % [String((c as Dictionary).get("kind", "")), String((c as Dictionary).get("tier", "")), String((c as Dictionary).get("relic_id", ""))]
|
var ckey := "%s|%s|%s" % [String((c as Dictionary).get("kind", "")), String((c as Dictionary).get("tier", "")), String((c as Dictionary).get("feat_id", ""))]
|
||||||
if seen.has(ckey):
|
if seen.has(ckey):
|
||||||
cand_ok = false
|
cand_ok = false
|
||||||
seen[ckey] = true
|
seen[ckey] = true
|
||||||
@@ -386,7 +386,7 @@ func _smoke_adventure() -> void:
|
|||||||
var r: Dictionary = level_resolves[i]
|
var r: Dictionary = level_resolves[i]
|
||||||
var rk := String(r.get("picked", ""))
|
var rk := String(r.get("picked", ""))
|
||||||
var rt := String(r.get("tier", ""))
|
var rt := String(r.get("tier", ""))
|
||||||
if _smoke_find_token(p.get("candidates", []) as Array, rk, rt).is_empty() or int(r.get("value", -1)) != int(_db.adventure.feature_value(rk, rt)):
|
if _smoke_find_token(p.get("candidates", []) as Array, rk, rt).is_empty() or int(r.get("value", -1)) != int(_db.adventure.curio_value(rk, rt)):
|
||||||
resolve_ok = false
|
resolve_ok = false
|
||||||
if rk == "max_hp":
|
if rk == "max_hp":
|
||||||
hp_acc += int(r.get("value", max_flat))
|
hp_acc += int(r.get("value", max_flat))
|
||||||
@@ -399,7 +399,7 @@ func _smoke_adventure() -> void:
|
|||||||
var exp_base := maxi(1, int(round(float(int(_db.hero.max_hp)) * k)))
|
var exp_base := maxi(1, int(round(float(int(_db.hero.max_hp)) * k)))
|
||||||
# 模拟档 v3.4 商店贪心购血(数值修复批)合法抬升——上限窗口 = 基础 + 加算区 3 件 + 购血余量;
|
# 模拟档 v3.4 商店贪心购血(数值修复批)合法抬升——上限窗口 = 基础 + 加算区 3 件 + 购血余量;
|
||||||
# TP0 数值对后 max_hp 件有大生命 +10(rare 档)——上界按大件最坏构成 3×10+购血 15=+45(TP0 放宽)
|
# TP0 数值对后 max_hp 件有大生命 +10(rare 档)——上界按大件最坏构成 3×10+购血 15=+45(TP0 放宽)
|
||||||
var hp_ceiling := maxi(1, int(round(float(int(_db.hero.max_hp) + 3 * int(_db.adventure.feature_value("max_hp", "rare")) + 15) * k)))
|
var hp_ceiling := maxi(1, int(round(float(int(_db.hero.max_hp) + 3 * int(_db.adventure.curio_value("max_hp", "rare")) + 15) * k)))
|
||||||
var seq_ok := seq.size() > 0 and int(seq[0]) == exp_base
|
var seq_ok := seq.size() > 0 and int(seq[0]) == exp_base
|
||||||
for v in (seq as Array):
|
for v in (seq as Array):
|
||||||
if int(v) < exp_base or int(v) > hp_ceiling:
|
if int(v) < exp_base or int(v) > hp_ceiling:
|
||||||
@@ -467,7 +467,7 @@ func _smoke_adventure() -> void:
|
|||||||
for ev in wallet:
|
for ev in wallet:
|
||||||
if String(ev["event"]) == "clear":
|
if String(ev["event"]) == "clear":
|
||||||
clears += 1
|
clears += 1
|
||||||
elif String(ev["event"]) == "gain" and String(ev.get("source", "")) != "bonus_pick":
|
elif String(ev["event"]) == "gain" and String(ev.get("source", "")) != "curio_pick":
|
||||||
gains += 1 # 战斗完成入账计数(三选一 GP 袋件入账另有 source 标记——不计战斗口径)
|
gains += 1 # 战斗完成入账计数(三选一 GP 袋件入账另有 source 标记——不计战斗口径)
|
||||||
check(gains == int(st["battles"]), "每战完成入账恰一次(gain %d = battles %d——GP 袋件入账单列)" % [gains, int(st["battles"])])
|
check(gains == int(st["battles"]), "每战完成入账恰一次(gain %d = battles %d——GP 袋件入账单列)" % [gains, int(st["battles"])])
|
||||||
check(clears == 1 and wallet.size() > 0 and String(wallet[wallet.size() - 1]["event"]) == "clear",
|
check(clears == 1 and wallet.size() > 0 and String(wallet[wallet.size() - 1]["event"]) == "clear",
|
||||||
@@ -505,13 +505,13 @@ func _smoke_adventure() -> void:
|
|||||||
check(acc_shop >= 3, "统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" % acc_shop)
|
check(acc_shop >= 3, "统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" % acc_shop)
|
||||||
a8._on_level_finished({"result": "victory", "hp": int(r8.hero_hp), "node_index": r8.node_index,
|
a8._on_level_finished({"result": "victory", "hp": int(r8.hero_hp), "node_index": r8.node_index,
|
||||||
"node_id": r8.current_node_id(), "node_type": "shop"}) # 模拟 L2 通关(victory 载荷直调——非末关)
|
"node_id": r8.current_node_id(), "node_type": "shop"}) # 模拟 L2 通关(victory 载荷直调——非末关)
|
||||||
check(not a8._bonus_pending.is_empty(), "L2 通关三选一停点在案(直调兑付前——零自动推进)")
|
check(not a8._curio_pending.is_empty(), "L2 通关三选一停点在案(直调兑付前——零自动推进)")
|
||||||
check(not a8.continue_to_next_level(), "未兑付推进拒绝(选择停点零自动推进契约)")
|
check(not a8.continue_to_next_level(), "未兑付推进拒绝(选择停点零自动推进契约)")
|
||||||
var l2_cands: Array = a8._bonus_pending["candidates"]
|
var l2_cands: Array = a8._curio_pending["candidates"]
|
||||||
var l2_pick: Dictionary = l2_cands[0]
|
var l2_pick: Dictionary = l2_cands[0]
|
||||||
check(a8.pick_bonus(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", ""))), "三选一点选兑付受理(候选取首件)")
|
check(a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))), "三选一点选兑付受理(候选取首件)")
|
||||||
var picked2: String = String(a8._bonus_result.get("picked", ""))
|
var picked2: String = String(a8._curio_result.get("picked", ""))
|
||||||
var expect_acc: int = acc_shop + (int(a8._bonus_result.get("value", 0)) if picked2 == "max_hp" else 0)
|
var expect_acc: int = acc_shop + (int(a8._curio_result.get("value", 0)) if picked2 == "max_hp" else 0)
|
||||||
check(int(a8.bonus.get("max_hp", -1)) == expect_acc,
|
check(int(a8.bonus.get("max_hp", -1)) == expect_acc,
|
||||||
"三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" % [int(a8.bonus.get("max_hp", -1)), acc_shop, picked2])
|
"三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" % [int(a8.bonus.get("max_hp", -1)), acc_shop, picked2])
|
||||||
var max_eff8: int = a8.effective_max_hp() # T17 口径修复随批:账本生效上限含调试浮窗 pct(a8 已 set 400)
|
var max_eff8: int = a8.effective_max_hp() # T17 口径修复随批:账本生效上限含调试浮窗 pct(a8 已 set 400)
|
||||||
@@ -545,11 +545,11 @@ func _smoke_adventure() -> void:
|
|||||||
var a11 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(11))
|
var a11 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(11))
|
||||||
check(a11.start_adventure(11, {"first_script": true, "treasure_unlocked": {"blast_kit": true}}),
|
check(a11.start_adventure(11, {"first_script": true, "treasure_unlocked": {"blast_kit": true}}),
|
||||||
"剔牌库/替换场景:冒险启动(seed 11——道具解锁集只含 blast_kit)")
|
"剔牌库/替换场景:冒险启动(seed 11——道具解锁集只含 blast_kit)")
|
||||||
for di in range(a11._bonus_deck.size() - 1, -1, -1):
|
for di in range(a11._curio_deck.size() - 1, -1, -1):
|
||||||
var dk := String((a11._bonus_deck[di] as Dictionary).get("kind", ""))
|
var dk := String((a11._curio_deck[di] as Dictionary).get("kind", ""))
|
||||||
if dk == "attack" or dk == "ap":
|
if dk == "attack" or dk == "ap":
|
||||||
a11._bonus_deck.remove_at(di)
|
a11._curio_deck.remove_at(di)
|
||||||
var draw: Array = a11._draw_bonus_candidates()
|
var draw: Array = a11._draw_curio_candidates()
|
||||||
var draw_kinds := {}
|
var draw_kinds := {}
|
||||||
for t in draw:
|
for t in draw:
|
||||||
draw_kinds[String((t as Dictionary).get("kind", ""))] = true
|
draw_kinds[String((t as Dictionary).get("kind", ""))] = true
|
||||||
@@ -560,7 +560,7 @@ func _smoke_adventure() -> void:
|
|||||||
# white-box 测试随停点渠道退役拆除——机械惰性承载,零投放零行为)
|
# white-box 测试随停点渠道退役拆除——机械惰性承载,零投放零行为)
|
||||||
var treasure_seen := false
|
var treasure_seen := false
|
||||||
for i in 40:
|
for i in 40:
|
||||||
for t in a11._draw_bonus_candidates():
|
for t in a11._draw_curio_candidates():
|
||||||
if String((t as Dictionary).get("kind", "")) == "treasure_item":
|
if String((t as Dictionary).get("kind", "")) == "treasure_item":
|
||||||
treasure_seen = true
|
treasure_seen = true
|
||||||
break
|
break
|
||||||
@@ -569,7 +569,7 @@ func _smoke_adventure() -> void:
|
|||||||
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})
|
||||||
var cap := {"payload": {}}
|
var cap := {"payload": {}}
|
||||||
a12.bonus_choice_presented.connect(func(p): cap["payload"] = p)
|
a12.curio_choice_presented.connect(func(p): cap["payload"] = p)
|
||||||
var max_full := int(_db.hero.max_hp) + int(a12._base_override.get("max_hp", 0))
|
var max_full := int(_db.hero.max_hp) + int(a12._base_override.get("max_hp", 0))
|
||||||
a12.ledger["hero_hp"] = max_full
|
a12.ledger["hero_hp"] = max_full
|
||||||
a12._on_level_finished({"result": "victory", "hp": max_full, "node_index": 1, "node_id": 1, "node_type": "shop"})
|
a12._on_level_finished({"result": "victory", "hp": max_full, "node_index": 1, "node_id": 1, "node_type": "shop"})
|
||||||
@@ -579,10 +579,10 @@ func _smoke_adventure() -> void:
|
|||||||
var a13 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(13))
|
var a13 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(13))
|
||||||
check(a13.start_adventure(13, {"first_script": true, "treasure_unlocked": {"blast_kit": true}}),
|
check(a13.start_adventure(13, {"first_script": true, "treasure_unlocked": {"blast_kit": true}}),
|
||||||
"节点共享池场景:冒险启动(seed 13)")
|
"节点共享池场景:冒险启动(seed 13)")
|
||||||
for di13 in range(a13._bonus_deck.size() - 1, -1, -1):
|
for di13 in range(a13._curio_deck.size() - 1, -1, -1):
|
||||||
if String((a13._bonus_deck[di13] as Dictionary).get("kind", "")) == "attack":
|
if String((a13._curio_deck[di13] as Dictionary).get("kind", "")) == "attack":
|
||||||
a13._bonus_deck.remove_at(di13)
|
a13._curio_deck.remove_at(di13)
|
||||||
var ndraw: Dictionary = a13.node_bonus_candidates()
|
var ndraw: Dictionary = a13.node_curio_candidates()
|
||||||
var ncands13: Array = ndraw.get("candidates", []) as Array
|
var ncands13: Array = ndraw.get("candidates", []) as Array
|
||||||
var has_attack13 := false
|
var has_attack13 := false
|
||||||
for t in ncands13:
|
for t in ncands13:
|
||||||
@@ -591,81 +591,124 @@ func _smoke_adventure() -> void:
|
|||||||
break
|
break
|
||||||
check(not has_attack13,
|
check(not has_attack13,
|
||||||
"奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" % str(ncands13))
|
"奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" % str(ncands13))
|
||||||
a13.present_node_bonus(ncands13, ndraw.get("aux_detail", []) as Array)
|
a13.present_node_curio(ncands13, ndraw.get("aux_detail", []) as Array)
|
||||||
a13.present_node_bonus([{"kind": "gp", "tier": "common"}])
|
a13.present_node_curio([{"kind": "gp", "tier": "common"}])
|
||||||
var gp13 := int(a13.ledger.get("gp", 0))
|
var gp13 := int(a13.ledger.get("gp", 0))
|
||||||
check(a13.apply_node_bonus("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.feature_value("gp", "common")),
|
check(a13.apply_node_curio("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.curio_value("gp", "common")),
|
||||||
"奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" % int(_db.adventure.feature_value("gp", "common")))
|
"奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" % int(_db.adventure.curio_value("gp", "common")))
|
||||||
check(a13._node_bonus_pending.is_empty(), "节点停点态兑付后清零(恰一次语义同构)")
|
check(a13._node_curio_pending.is_empty(), "节点停点态兑付后清零(恰一次语义同构)")
|
||||||
var s10: Dictionary = a10.summary
|
var s10: Dictionary = a10.summary
|
||||||
check(String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)),
|
check(String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)),
|
||||||
"放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)")
|
"放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)")
|
||||||
|
|
||||||
|
|
||||||
# ---- 双层 BD 规则改写型特性基建冒烟(迭代 12 T1——relic_table/档位激活引擎/规则改写型特性带池接线)----
|
# ---- 双层 BD 规则改写型特性基建冒烟(迭代 12 T1——feat_table/档位激活引擎/规则改写型特性带池接线)----
|
||||||
|
|
||||||
func _smoke_relics() -> void:
|
func _smoke_feats() -> void:
|
||||||
print("--- 规则改写型特性基建(relic_table · 档位激活引擎 · 规则改写型特性带接线)---")
|
print("--- 规则改写型特性基建(feat_table · 档位激活引擎 · 规则改写型特性带接线)---")
|
||||||
# ① 现行表牌库构成:七流派 27 件全量入池——98 数值令牌+27 relic 令牌(批次一铺量完成)
|
# ① 现行表牌库构成:七流派 27 件全量入池——98 数值令牌+27 relic 令牌(批次一铺量完成)
|
||||||
check(_db.relics != null and (_db.relics.rows as Array).size() == 27, "relic_table 已加载(七流派 27 件全量——批次一 T2-T6 铺量完成)")
|
check(_db.feats != null and (_db.feats.rows as Array).size() == 27, "feat_table 已加载(七流派 27 件全量——批次一 T2-T6 铺量完成)")
|
||||||
var a_r0 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(71))
|
var a_r0 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(71))
|
||||||
check(a_r0.start_adventure(71, {"first_script": true}), "规则改写型特性带场景:冒险启动(seed 71)")
|
check(a_r0.start_adventure(71, {"first_script": true}), "规则改写型特性带场景:冒险启动(seed 71)")
|
||||||
var relic_tokens := 0
|
var feat_tokens := 0
|
||||||
var rarest_tokens := 0
|
var rarest_tokens := 0
|
||||||
for t in a_r0._bonus_deck:
|
for t in a_r0._curio_deck:
|
||||||
if String((t as Dictionary).get("kind", "")) == "relic":
|
if String((t as Dictionary).get("kind", "")) == "relic":
|
||||||
relic_tokens += 1
|
feat_tokens += 1
|
||||||
if String((t as Dictionary).get("tier", "")) == "rarest":
|
if String((t as Dictionary).get("tier", "")) == "rarest":
|
||||||
rarest_tokens += 1
|
rarest_tokens += 1
|
||||||
check(relic_tokens == 27 and rarest_tokens == 6 and a_r0._bonus_deck.size() == 125,
|
check(feat_tokens == 27 and rarest_tokens == 6 and a_r0._curio_deck.size() == 125,
|
||||||
"规则改写型特性带入池:27 件(rare 档 21 + rarest 锚点 6)——牌库 98+27=125")
|
"规则改写型特性带入池:27 件(rare 档 21 + rarest 锚点 6)——牌库 98+27=125")
|
||||||
check(_db.relics.validate().is_empty(), "relic_table.validate 零违例(有行流派扫雷端铁律——magic first_dig_spell 在案)")
|
check(_db.feats.validate().is_empty(), "feat_table.validate 零违例(有行流派扫雷端铁律——magic first_dig_spell 在案)")
|
||||||
# ② 档位激活引擎(纯函数直测——构造表 4 件 magic + 1 件 flag + 1 件 rarest 锚点)
|
# ② 档位激活引擎(纯函数直测——构造表 4 件 magic + 1 件 flag + 1 件 rarest 锚点)
|
||||||
var rt := RelicTable.new()
|
var rt := FeatTable.new()
|
||||||
rt.rows = [
|
rt.rows = [
|
||||||
{"relic_id": "m1", "display_name": "魔一", "rarity": "common", "archetype": "magic", "hook": "hero_attack_rider", "effects": {"value": 1}},
|
{"feat_id": "m1", "display_name": "魔一", "rarity": "common", "archetype": "magic", "hook": "hero_attack_rider", "effects": {"value": 1}},
|
||||||
{"relic_id": "m2", "display_name": "魔二", "rarity": "common", "archetype": "magic", "hook": "first_dig_spell", "effects": {"value": 1}},
|
{"feat_id": "m2", "display_name": "魔二", "rarity": "common", "archetype": "magic", "hook": "first_dig_spell", "effects": {"value": 1}},
|
||||||
{"relic_id": "m3", "display_name": "魔三", "rarity": "rare", "archetype": "magic", "hook": "clamp_modifier", "effects": {"value": 1}},
|
{"feat_id": "m3", "display_name": "魔三", "rarity": "rare", "archetype": "magic", "hook": "clamp_modifier", "effects": {"value": 1}},
|
||||||
{"relic_id": "m4", "display_name": "魔四", "rarity": "rare", "archetype": "magic", "hook": "hero_attack_rider", "effects": {"tier_value": 2}},
|
{"feat_id": "m4", "display_name": "魔四", "rarity": "rare", "archetype": "magic", "hook": "hero_attack_rider", "effects": {"tier_value": 2}},
|
||||||
{"relic_id": "f1", "display_name": "旗一", "rarity": "common", "archetype": "flag", "hook": "flag_resource", "effects": {"value": 1}},
|
{"feat_id": "f1", "display_name": "旗一", "rarity": "common", "archetype": "flag", "hook": "flag_resource", "effects": {"value": 1}},
|
||||||
]
|
]
|
||||||
check(rt.validate().is_empty(), "构造 relic 表 validate 零违例(magic 4 件含 first_dig_spell 扫雷端——红线①铁律过)")
|
check(rt.validate().is_empty(), "构造 relic 表 validate 零违例(magic 4 件含 first_dig_spell 扫雷端——红线①铁律过)")
|
||||||
var tiers := RelicsEngine.tiers_for(["m1", "m2", "m3", "m4", "f1"], rt)
|
var tiers := FeatsEngine.tiers_for(["m1", "m2", "m3", "m4", "f1"], rt)
|
||||||
check(int(tiers.get("magic", -1)) == 2 and int(tiers.get("flag", -1)) == 0,
|
check(int(tiers.get("magic", -1)) == 2 and int(tiers.get("flag", -1)) == 0,
|
||||||
"档位激活:magic 4 件→档二 · flag 1 件→散件档 0(集 2 档一/4 档二——以撒套装/小骨铭文先例)")
|
"档位激活:magic 4 件→档二 · flag 1 件→散件档 0(集 2 档一/4 档二——以撒套装/小骨铭文先例)")
|
||||||
check(RelicsEngine.is_formed(tiers, "magic") and not RelicsEngine.is_formed(tiers, "flag"),
|
check(FeatsEngine.is_formed(tiers, "magic") and not FeatsEngine.is_formed(tiers, "flag"),
|
||||||
"成型判读:magic 成型(tier≥1)· flag 未成型(散件单体保底——档位只质变不启动)")
|
"成型判读:magic 成型(tier≥1)· flag 未成型(散件单体保底——档位只质变不启动)")
|
||||||
var t2 := RelicsEngine.tiers_for(["m1", "m2"], rt)
|
var t2 := FeatsEngine.tiers_for(["m1", "m2"], rt)
|
||||||
check(int(t2.get("magic", -1)) == 1, "档位激活:2 件→档一(同集合同档位确定性——MC 逐位可复放承载)")
|
check(int(t2.get("magic", -1)) == 1, "档位激活:2 件→档一(同集合同档位确定性——MC 逐位可复放承载)")
|
||||||
var hooks := RelicsEngine.hooks_for(["m1", "f1"], rt)
|
var hooks := FeatsEngine.hooks_for(["m1", "f1"], rt)
|
||||||
check(hooks.size() == 2 and String(hooks[0].get("hook", "")) == "hero_attack_rider",
|
check(hooks.size() == 2 and String(hooks[0].get("hook", "")) == "hero_attack_rider",
|
||||||
"挂点投影:表序稳定(同集合→同投影)")
|
"挂点投影:表序稳定(同集合→同投影)")
|
||||||
# ③ 规则改写型特性带接线(临时表注入——relic 令牌入牌库/兑付入持有集/踢池/断点往返;测试尾还原现行表)
|
# ③ 规则改写型特性带接线(临时表注入——relic 令牌入牌库/兑付入持有集/踢池/断点往返;测试尾还原现行表)
|
||||||
var live_rows: RelicTable = _db.relics
|
var live_rows: FeatTable = _db.feats
|
||||||
_db.relics = rt
|
_db.feats = rt
|
||||||
var a_r = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(72))
|
var a_r = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(72))
|
||||||
check(a_r.start_adventure(72, {"first_script": true}), "规则改写型特性带场景:冒险启动(seed 72——注入 5 行临时表)")
|
check(a_r.start_adventure(72, {"first_script": true}), "规则改写型特性带场景:冒险启动(seed 72——注入 5 行临时表)")
|
||||||
var deck_relic := 0
|
var deck_relic := 0
|
||||||
for t in a_r._bonus_deck:
|
for t in a_r._curio_deck:
|
||||||
if String((t as Dictionary).get("kind", "")) == "relic":
|
if String((t as Dictionary).get("kind", "")) == "relic":
|
||||||
deck_relic += 1
|
deck_relic += 1
|
||||||
check(deck_relic == 5 and a_r._bonus_deck.size() == 103,
|
check(deck_relic == 5 and a_r._curio_deck.size() == 103,
|
||||||
"规则改写型特性带入池:5 行 relic 每件 1 张(common/rare→稀有档 rarest→罕见档)——牌库 98+5=103")
|
"规则改写型特性带入池:5 行 relic 每件 1 张(common/rare→稀有档 rarest→罕见档)——牌库 98+5=103")
|
||||||
var relic_seen: Dictionary = {}
|
# T4 渠道白名单断言(迭代 15 卡②——渠道轴定位:通关渠道只出专长/节点渠道零专长)
|
||||||
|
var feat_seen: Dictionary = {}
|
||||||
|
var node_relic_seen := false
|
||||||
for i in 60:
|
for i in 60:
|
||||||
for t in a_r._draw_bonus_candidates():
|
for t in a_r._draw_curio_candidates("level_clear"):
|
||||||
if String((t as Dictionary).get("kind", "")) == "relic":
|
if String((t as Dictionary).get("kind", "")) == "relic":
|
||||||
relic_seen[String(t.get("relic_id", ""))] = true
|
feat_seen[String(t.get("feat_id", ""))] = true
|
||||||
check(relic_seen.size() >= 2, "抽签出 relic 候选(60 次重抽 ≥2 种不同 relic——身份键 relic_id 同档可多件)")
|
for t in a_r._draw_curio_candidates("reward_node"):
|
||||||
a_r._bonus_pending = {"candidates": [{"kind": "relic", "tier": "rare", "relic_id": "m3"}, {"kind": "gp", "tier": "common"}], "payload": {}}
|
if String((t as Dictionary).get("kind", "")) == "relic":
|
||||||
|
node_relic_seen = true
|
||||||
|
check(feat_seen.size() >= 2, "通关渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)")
|
||||||
|
check(not node_relic_seen, "节点渠道零专长(60 次重抽零 relic——专长在非 Boss 渠道只以门票形态出现,T4 渠道白名单)")
|
||||||
|
# T5 门票行为断言(A5 拿到即开 / A6 保底计数——契约④四口径;独立 runner 防污染 a_r 牌库——后续 relic 兑付断言按 5 枚锚定)
|
||||||
|
var a_t = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(91))
|
||||||
|
a_t.start_adventure(91, {"first_script": true})
|
||||||
|
var stock_before := {}
|
||||||
|
for kk in ["attack", "gp", "heal"]:
|
||||||
|
stock_before[kk] = a_t.pool_stock(kk)
|
||||||
|
a_t._node_curio_pending = {"candidates": [{"kind": "feat_ticket", "tier": "rare"}, {"kind": "attack", "tier": "common"}], "aux_detail": []}
|
||||||
|
check(a_t.apply_node_curio("feat_ticket", "rare"), "A5 门票兑付受理(节点渠道——拿到即开)")
|
||||||
|
var tkt_cands: Array = (a_t.node_curio_pending_state().get("candidates", []) as Array)
|
||||||
|
var tkt_all_relic := tkt_cands.size() > 0
|
||||||
|
for t in tkt_cands:
|
||||||
|
if String((t as Dictionary).get("kind", "")) != "relic":
|
||||||
|
tkt_all_relic = false
|
||||||
|
check(tkt_all_relic, "A5 拿到即开——停点候选已替换为专长三选一(全 relic)")
|
||||||
|
check(a_t.pool_stock("attack") == int(stock_before["attack"]) and a_t.pool_stock("gp") == int(stock_before["gp"]),
|
||||||
|
"A5 零摘牌——门票兑付不动令牌库(不进持有集)")
|
||||||
|
check(not (a_t.feats_held() as Array).has("feat_ticket"), "A5 无门票残留持有集")
|
||||||
|
var feats_n := (a_t.feats_held() as Array).size()
|
||||||
|
a_t.apply_node_curio("relic", String((tkt_cands[0] as Dictionary).get("tier", "rare")), String((tkt_cands[0] as Dictionary).get("feat_id", "")))
|
||||||
|
check((a_t.feats_held() as Array).size() == feats_n + 1, "A5 专长面兑付受理(门票开出候选可正常入持有集)")
|
||||||
|
# A6 保底:pity 直填 5 → 下次展示必出(第 6 次必出口径);限购满 → 恒不触发
|
||||||
|
a_t._feat_ticket_sold = 0 # A5 已兑付一枚门票(sold=1=limit)——A6 段重置计数隔离验证
|
||||||
|
a_t._feat_ticket_pity = int(a_t.db.adventure.feat_ticket_pity_step)
|
||||||
|
var rg_t: RandomNumberGenerator = a_t.rng.open_stream("reward_pick")
|
||||||
|
check(a_t._feat_ticket_consider(rg_t), "A6 保底触发(pity=5 → 第 6 次展示必出——概率无关)")
|
||||||
|
check(a_t._feat_ticket_pity == 0, "A6 触发后清零")
|
||||||
|
a_t._feat_ticket_sold = int(a_t.db.adventure.feat_ticket_limit)
|
||||||
|
a_t._feat_ticket_pity = int(a_t.db.adventure.feat_ticket_pity_step)
|
||||||
|
check(not a_t._feat_ticket_consider(rg_t), "A6 限购满不再触发(保底冻结)")
|
||||||
|
a_t._feat_ticket_sold = 0
|
||||||
|
a_t._feat_ticket_pity = 0
|
||||||
|
var offered := false
|
||||||
|
for i in 200:
|
||||||
|
if a_t._feat_ticket_consider(rg_t):
|
||||||
|
offered = true
|
||||||
|
break
|
||||||
|
check(offered, "A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)")
|
||||||
|
a_r._curio_pending = {"candidates": [{"kind": "relic", "tier": "rare", "feat_id": "m3"}, {"kind": "gp", "tier": "common"}], "payload": {}}
|
||||||
var deck_m3 := 0
|
var deck_m3 := 0
|
||||||
for t in a_r._bonus_deck:
|
for t in a_r._curio_deck:
|
||||||
if String((t as Dictionary).get("relic_id", "")) == "m3":
|
if String((t as Dictionary).get("feat_id", "")) == "m3":
|
||||||
deck_m3 += 1
|
deck_m3 += 1
|
||||||
check(a_r.pick_bonus("relic", "rare", "m3") and (a_r.relics_held() as Array).has("m3") and a_r.pool_stock("relic") == deck_relic - 1,
|
check(a_r.pick_curio("relic", "rare", "m3") and (a_r.feats_held() as Array).has("m3") and a_r.pool_stock("relic") == deck_relic - 1,
|
||||||
"relic 兑付:入持有集 + 牌库摘牌(选中踢池——池库存 %d→%d)" % [deck_relic, deck_relic - 1])
|
"relic 兑付:入持有集 + 牌库摘牌(选中踢池——池库存 %d→%d)" % [deck_relic, deck_relic - 1])
|
||||||
# 断点往返(持有集+余库 relic_id 随行)
|
# 断点往返(持有集+余库 feat_id 随行)
|
||||||
var sm_r = load("res://core/save_manager.gd").new("user://_smoke_relics_bp.json")
|
var sm_r = load("res://core/save_manager.gd").new("user://_smoke_feats_bp.json")
|
||||||
sm_r.load_from_disk()
|
sm_r.load_from_disk()
|
||||||
a_r.set_save_manager(sm_r)
|
a_r.set_save_manager(sm_r)
|
||||||
a_r._on_level_finished({"result": "victory", "hp": int(a_r.ledger.get("hero_hp", 26)), "node_index": 1, "node_id": 1, "node_type": "shop"})
|
a_r._on_level_finished({"result": "victory", "hp": int(a_r.ledger.get("hero_hp", 26)), "node_index": 1, "node_id": 1, "node_type": "shop"})
|
||||||
@@ -673,34 +716,34 @@ func _smoke_relics() -> void:
|
|||||||
check((bp_r.get("relics", []) as Array).has("m3"), "断点附随件:relics 持有集入快照(迭代 12 T1 扩列)")
|
check((bp_r.get("relics", []) as Array).has("m3"), "断点附随件:relics 持有集入快照(迭代 12 T1 扩列)")
|
||||||
var bp_deck_m3 := 0
|
var bp_deck_m3 := 0
|
||||||
for t in (bp_r.get("bonus_deck", []) as Array):
|
for t in (bp_r.get("bonus_deck", []) as Array):
|
||||||
if String((t as Dictionary).get("relic_id", "")) == "m3":
|
if String((t as Dictionary).get("feat_id", "")) == "m3":
|
||||||
bp_deck_m3 += 1
|
bp_deck_m3 += 1
|
||||||
check(bp_deck_m3 == 0, "断点余库:已兑付 relic 令牌逐枚还原(m3 零残留——续玩不重复获得)")
|
check(bp_deck_m3 == 0, "断点余库:已兑付 relic 令牌逐枚还原(m3 零残留——续玩不重复获得)")
|
||||||
sm_r.clear_breakpoint()
|
sm_r.clear_breakpoint()
|
||||||
_db.relics = live_rows # 还原现行表(共享 _db 防污染——后续冒烟段零感知)
|
_db.feats = live_rows # 还原现行表(共享 _db 防污染——后续冒烟段零感知)
|
||||||
# ④ T2 魔法流求值面(现行表 magic 四件——flags 组装直测;rider 落地行为=伤害控制器消费面,
|
# ④ T2 魔法流求值面(现行表 magic 四件——flags 组装直测;rider 落地行为=伤害控制器消费面,
|
||||||
# 既有战斗冒烟全绿=relic_flags 空 dict 零扰动实证;全链分岔读数归 T7 MC)
|
# 既有战斗冒烟全绿=feat_flags 空 dict 零扰动实证;全链分岔读数归 T7 MC)
|
||||||
var orch = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(73))
|
var orch = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(73))
|
||||||
orch._relic_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance"])
|
orch._feat_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance"])
|
||||||
var rf: Dictionary = orch._damage.relic_flags
|
var rf: Dictionary = orch._damage.feat_flags
|
||||||
check(int(rf.get("rider_flat", -1)) == 2 and bool(rf.get("rider_crit", false)),
|
check(int(rf.get("rider_flat", -1)) == 2 and bool(rf.get("rider_crit", false)),
|
||||||
"魔法流求值:rider_flat=余烬1+档一(4件)+1=2 · 档二 rider 可暴击(共鸣 rider 提升以奥术矢+档位承载——锚点双效果 provisional 披露)")
|
"魔法流求值:rider_flat=余烬1+档一(4件)+1=2 · 档二 rider 可暴击(共鸣 rider 提升以奥术矢+档位承载——锚点双效果 provisional 披露)")
|
||||||
check(int(rf.get("clamp_magic_magic", -1)) == 1 and int(rf.get("clamp_magic_heavy", -1)) == 1
|
check(int(rf.get("clamp_magic_magic", -1)) == 1 and int(rf.get("clamp_magic_heavy", -1)) == 1
|
||||||
and bool(rf.get("first_dig_spell", false)) and int(rf.get("spell_value", -1)) == 2,
|
and bool(rf.get("first_dig_spell", false)) and int(rf.get("spell_value", -1)) == 2,
|
||||||
"魔法流求值:魔网织者 magic-vs-magic 罚修正 +1 · 湛蓝誓约 magic-vs-heavy +1 · 魔能共鸣奥术矢 value 2")
|
"魔法流求值:魔网织者 magic-vs-magic 罚修正 +1 · 湛蓝誓约 magic-vs-heavy +1 · 魔能共鸣奥术矢 value 2")
|
||||||
# ⑤ T3 标旗流求值面(现行表 magic 4+flag 4——混合持有:flag 档一 4 件成型)
|
# ⑤ T3 标旗流求值面(现行表 magic 4+flag 4——混合持有:flag 档一 4 件成型)
|
||||||
orch._relic_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance",
|
orch._feat_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance",
|
||||||
"mine_atlas", "nest_hunter", "perfect_defense", "war_banner"])
|
"mine_atlas", "nest_hunter", "perfect_defense", "war_banner"])
|
||||||
var rf8: Dictionary = orch._damage.relic_flags
|
var rf8: Dictionary = orch._damage.feat_flags
|
||||||
check(int(rf8.get("flag_resource_extra", -1)) == 1 and int(rf8.get("nest_aura_value", -1)) == 1
|
check(int(rf8.get("flag_resource_extra", -1)) == 1 and int(rf8.get("nest_aura_value", -1)) == 1
|
||||||
and int(rf8.get("flag_all_hit_mult", -1)) == 2 and int(rf8.get("flag_strike_value", -1)) == 2,
|
and int(rf8.get("flag_all_hit_mult", -1)) == 2 and int(rf8.get("flag_strike_value", -1)) == 2,
|
||||||
"标旗流求值:雷区图谱额外资源 1 · 猎巢人光环 +1/巢 · 完美布防 ×2 · 战旗军规旗伤 2(复用 board_typed 零新配方)")
|
"标旗流求值:雷区图谱额外资源 1 · 猎巢人光环 +1/巢 · 完美布防 ×2 · 战旗军规旗伤 2(复用 board_typed 零新配方)")
|
||||||
# ⑥ T4 穿刺/普攻流求值面(全 16 件持有——四流派档位成型)
|
# ⑥ T4 穿刺/普攻流求值面(全 16 件持有——四流派档位成型)
|
||||||
orch._relic_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance",
|
orch._feat_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance",
|
||||||
"mine_atlas", "nest_hunter", "perfect_defense", "war_banner",
|
"mine_atlas", "nest_hunter", "perfect_defense", "war_banner",
|
||||||
"pierce_shot", "chain_arrows", "weak_instinct", "hunter_rapier",
|
"pierce_shot", "chain_arrows", "weak_instinct", "hunter_rapier",
|
||||||
"miner_stubborn", "miner_hammer", "focus_mind", "armor_crack"])
|
"miner_stubborn", "miner_hammer", "focus_mind", "armor_crack"])
|
||||||
var rf16: Dictionary = orch._damage.relic_flags
|
var rf16: Dictionary = orch._damage.feat_flags
|
||||||
check(bool(rf16.get("kill_overflow", false)) and int(rf16.get("chain_value", -1)) == 2
|
check(bool(rf16.get("kill_overflow", false)) and int(rf16.get("chain_value", -1)) == 2
|
||||||
and int(rf16.get("discount_shift", -1)) == 1 and int(rf16.get("clamp_pierce_light", -1)) == 1,
|
and int(rf16.get("discount_shift", -1)) == 1 and int(rf16.get("clamp_pierce_light", -1)) == 1,
|
||||||
"穿刺流求值:贯穿射击弹射旗 · 连珠矢段值 2 上限 2 · 猎弱本能折扣改善 1 档 · 猎手刺剑 pierce-vs-light +1")
|
"穿刺流求值:贯穿射击弹射旗 · 连珠矢段值 2 上限 2 · 猎弱本能折扣改善 1 档 · 猎手刺剑 pierce-vs-light +1")
|
||||||
@@ -717,8 +760,8 @@ func _smoke_relics() -> void:
|
|||||||
"miner_stubborn", "miner_hammer", "focus_mind", "armor_crack",
|
"miner_stubborn", "miner_hammer", "focus_mind", "armor_crack",
|
||||||
"greedy_lantern", "vein_intuition", "gambler_pact", "deep_drill",
|
"greedy_lantern", "vein_intuition", "gambler_pact", "deep_drill",
|
||||||
"armory_prep", "iron_wall", "patient_burst"]
|
"armory_prep", "iron_wall", "patient_burst"]
|
||||||
orch._relic_flags_for(ids23)
|
orch._feat_flags_for(ids23)
|
||||||
var rf23: Dictionary = orch._damage.relic_flags
|
var rf23: Dictionary = orch._damage.feat_flags
|
||||||
check(int(rf23.get("dig_yield_pos_mult", -1)) == 85 and int(rf23.get("dig_yield_neg_mult", -1)) == 125
|
check(int(rf23.get("dig_yield_pos_mult", -1)) == 85 and int(rf23.get("dig_yield_neg_mult", -1)) == 125
|
||||||
and int(rf23.get("combo_cap", -1)) == 5,
|
and int(rf23.get("combo_cap", -1)) == 5,
|
||||||
"开格流求值:正向乘区 25+10+50=85% · 负向加重 25+100=125% · 直觉连击上限 5(同 hook 累加——三件同持)")
|
"开格流求值:正向乘区 25+10+50=85% · 负向加重 25+100=125% · 直觉连击上限 5(同 hook 累加——三件同持)")
|
||||||
@@ -726,19 +769,19 @@ func _smoke_relics() -> void:
|
|||||||
and int(rf23.get("ap_carry_value", -1)) == 1 and int(rf23.get("shield_per_layer", -1)) == 1
|
and int(rf23.get("ap_carry_value", -1)) == 1 and int(rf23.get("shield_per_layer", -1)) == 1
|
||||||
and int(rf23.get("spike_mult", -1)) == 2,
|
and int(rf23.get("spike_mult", -1)) == 2,
|
||||||
"深挖钻头=开掘 +1 AP/每 4 格深层产出(R4 甲非信息税对价)· 蓄力三件=层值 1/减伤 1/层/满层 ×2")
|
"深挖钻头=开掘 +1 AP/每 4 格深层产出(R4 甲非信息税对价)· 蓄力三件=层值 1/减伤 1/层/满层 ×2")
|
||||||
check(orch.action_economy.relic_ap_carry and orch.action_economy.relic_dig_extra == 1,
|
check(orch.action_economy.feat_ap_carry and orch.action_economy.feat_dig_extra == 1,
|
||||||
"蓄力/钻头 action_economy 注入(ap_carry 改写停手转存 + 付费开掘对价注入——关着 false/0 零扰动)")
|
"蓄力/钻头 action_economy 注入(ap_carry 改写停手转存 + 付费开掘对价注入——关着 false/0 零扰动)")
|
||||||
# ⑦c 特性持有呈现段(2026-10-04 用户反馈补齐——C 区「特性」节:聚合行+成型档位;空态隐藏)
|
# ⑦c 特性持有呈现段(2026-10-04 用户反馈补齐——C 区「特性」节:聚合行+成型档位;空态隐藏)
|
||||||
var panel = load("res://presentation/views/hero_info_panel_view.gd").new()
|
var panel = load("res://presentation/views/hero_info_panel_view.gd").new()
|
||||||
panel.set_relic_holdings([])
|
panel.set_feat_holdings([])
|
||||||
check(not panel._relic_section.visible, "特性段空态隐藏(视图空态纪律同特性段)")
|
check(not panel._feat_section.visible, "特性段空态隐藏(视图空态纪律同特性段)")
|
||||||
panel.set_relic_holdings(["魔法 ×2 · 档一", "开格 ×1 · 散件"])
|
panel.set_feat_holdings(["魔法 ×2 · 档一", "开格 ×1 · 散件"])
|
||||||
check(panel._relic_section.visible and panel._relic_list.get_child_count() == 2,
|
check(panel._feat_section.visible and panel._feat_list.get_child_count() == 2,
|
||||||
"特性段持有呈现(聚合行+成型档位常驻可读——「局内奖励选了什么看不到」缺口承载)")
|
"特性段持有呈现(聚合行+成型档位常驻可读——「局内奖励选了什么看不到」缺口承载)")
|
||||||
# ⑦b 深挖钻头付费开掘回归(2026-10-04 试玩缺陷修复——对价挂付费侧):AP=1 降级照常开格
|
# ⑦b 深挖钻头付费开掘回归(2026-10-04 试玩缺陷修复——对价挂付费侧):AP=1 降级照常开格
|
||||||
# (用户实测「AP=1 无法开格只能标旗」的回归闸);AP=2 付全价积付费计数(深层产出配额取数面)。
|
# (用户实测「AP=1 无法开格只能标旗」的回归闸);AP=2 付全价积付费计数(深层产出配额取数面)。
|
||||||
var ae_dd = load("res://core/action_economy.gd").new(_db)
|
var ae_dd = load("res://core/action_economy.gd").new(_db)
|
||||||
ae_dd.relic_dig_extra = 1
|
ae_dd.feat_dig_extra = 1
|
||||||
ae_dd._phase = 1 if ae_dd.Phase.ACTIVE == 1 else ae_dd.Phase.ACTIVE # white-box 相位(冒烟专用)
|
ae_dd._phase = 1 if ae_dd.Phase.ACTIVE == 1 else ae_dd.Phase.ACTIVE # white-box 相位(冒烟专用)
|
||||||
ae_dd._ap = 1
|
ae_dd._ap = 1
|
||||||
check(ae_dd.request_dig(0, false) and ae_dd.deep_paid_count == 0,
|
check(ae_dd.request_dig(0, false) and ae_dd.deep_paid_count == 0,
|
||||||
@@ -750,14 +793,14 @@ func _smoke_relics() -> void:
|
|||||||
# ⑧ T6 吞噬流求值面(27 件全量——七流派档位成型)
|
# ⑧ T6 吞噬流求值面(27 件全量——七流派档位成型)
|
||||||
var ids27: Array = ids23.duplicate()
|
var ids27: Array = ids23.duplicate()
|
||||||
ids27.append_array(["ley_harvest", "thorn_weaver", "purge_sapper", "magma_body"])
|
ids27.append_array(["ley_harvest", "thorn_weaver", "purge_sapper", "magma_body"])
|
||||||
orch._relic_flags_for(ids27)
|
orch._feat_flags_for(ids27)
|
||||||
var rf27: Dictionary = orch._damage.relic_flags
|
var rf27: Dictionary = orch._damage.feat_flags
|
||||||
check(int(rf27.get("terrain_refund", -1)) == 1 and int(rf27.get("terrain_dig_double", -1)) == 1
|
check(int(rf27.get("terrain_refund", -1)) == 1 and int(rf27.get("terrain_dig_double", -1)) == 1
|
||||||
and int(rf27.get("flag_neutralize", -1)) == 1 and int(rf27.get("variant_counter_value", -1)) == 1,
|
and int(rf27.get("flag_neutralize", -1)) == 1 and int(rf27.get("variant_counter_value", -1)) == 1,
|
||||||
"吞噬流求值:地脉采撷补偿 1 · 荆棘编织双产出 · 净化工兵中和旗(🔴 仅伤害型——过滤在消费面)· 熔火反噬 1/层")
|
"吞噬流求值:地脉采撷补偿 1 · 荆棘编织双产出 · 净化工兵中和旗(🔴 仅伤害型——过滤在消费面)· 熔火反噬 1/层")
|
||||||
var orch0 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(74))
|
var orch0 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(74))
|
||||||
orch0._relic_flags_for([])
|
orch0._feat_flags_for([])
|
||||||
check((orch0._damage.relic_flags as Dictionary).is_empty(),
|
check((orch0._damage.feat_flags as Dictionary).is_empty(),
|
||||||
"零持有求值=零旗(relic 关着零扰动——伤害管线既有行为逐位不变)")
|
"零持有求值=零旗(relic 关着零扰动——伤害管线既有行为逐位不变)")
|
||||||
|
|
||||||
|
|
||||||
@@ -771,10 +814,10 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool:
|
|||||||
return false
|
return false
|
||||||
if a._pending_next_level:
|
if a._pending_next_level:
|
||||||
# 三选一停点确认(迭代 10——确定性选件后推进,与 run_adventure 自动策略同口径;TP0 令牌化)
|
# 三选一停点确认(迭代 10——确定性选件后推进,与 run_adventure 自动策略同口径;TP0 令牌化)
|
||||||
if not a._bonus_pending.is_empty():
|
if not a._curio_pending.is_empty():
|
||||||
var bcands: Array = a._bonus_pending["candidates"]
|
var bcands: Array = a._curio_pending["candidates"]
|
||||||
var bpick: Dictionary = autoplay._pick_token(bcands)
|
var bpick: Dictionary = autoplay._pick_token(bcands)
|
||||||
a.pick_bonus(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("relic_id", "")))
|
a.pick_curio(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("feat_id", "")))
|
||||||
a.continue_to_next_level()
|
a.continue_to_next_level()
|
||||||
continue
|
continue
|
||||||
var r = a.runner()
|
var r = a.runner()
|
||||||
@@ -788,14 +831,14 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool:
|
|||||||
var t := String(r.current_node().get("type", ""))
|
var t := String(r.current_node().get("type", ""))
|
||||||
if t == "reward":
|
if t == "reward":
|
||||||
# 奖励节点三选一(迭代 10 追加批——确定性选件、替换放弃新件、兑付确认回图;TP0 令牌化)
|
# 奖励节点三选一(迭代 10 追加批——确定性选件、替换放弃新件、兑付确认回图;TP0 令牌化)
|
||||||
var ndraw: Dictionary = a.node_bonus_candidates()
|
var ndraw: Dictionary = a.node_curio_candidates()
|
||||||
var ncands: Array = ndraw.get("candidates", []) as Array
|
var ncands: Array = ndraw.get("candidates", []) as Array
|
||||||
if not ncands.is_empty():
|
if not ncands.is_empty():
|
||||||
a.present_node_bonus(ncands, ndraw.get("aux_detail", []) as Array)
|
a.present_node_curio(ncands, ndraw.get("aux_detail", []) as Array)
|
||||||
var npick: Dictionary = autoplay._pick_token(ncands)
|
var npick: Dictionary = autoplay._pick_token(ncands)
|
||||||
if a.apply_node_bonus(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("relic_id", ""))):
|
if a.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", ""))):
|
||||||
if not a._node_bonus_pending.is_empty() and a._node_bonus_pending.has("incoming"):
|
if not a._node_curio_pending.is_empty() and a._node_curio_pending.has("incoming"):
|
||||||
a.resolve_bonus_replace(-1)
|
a.resolve_curio_replace(-1)
|
||||||
r.continue_after_node()
|
r.continue_after_node()
|
||||||
elif t == "rest":
|
elif t == "rest":
|
||||||
r.continue_after_node()
|
r.continue_after_node()
|
||||||
@@ -3871,13 +3914,13 @@ func _smoke_shop_pool() -> void:
|
|||||||
var tier := String(buy_c.get("tier", ""))
|
var tier := String(buy_c.get("tier", ""))
|
||||||
var val_map := {"attack": "atk", "max_hp": "max_hp", "armor": "armor_class", "ap": "ap"}
|
var val_map := {"attack": "atk", "max_hp": "max_hp", "armor": "armor_class", "ap": "ap"}
|
||||||
var gp0: int = int(r8.gp)
|
var gp0: int = int(r8.gp)
|
||||||
var taken0: int = int(a8._bonus_taken.get(kind, 0))
|
var taken0: int = int(a8._curio_taken.get(kind, 0))
|
||||||
var val0: int = int(a8.bonus.get(String(val_map[kind]), -999))
|
var val0: int = int(a8.bonus.get(String(val_map[kind]), -999))
|
||||||
check(r8.buy_pool_item(kind), "购买受理(pool 口——%s|%s)" % [kind, tier])
|
check(r8.buy_pool_item(kind), "购买受理(pool 口——%s|%s)" % [kind, tier])
|
||||||
check(int(r8.gp) == gp0 - int(buy_c.get("price", 0)), "钱包扣现价(%d → %d)" % [gp0, int(r8.gp)])
|
check(int(r8.gp) == gp0 - int(buy_c.get("price", 0)), "钱包扣现价(%d → %d)" % [gp0, int(r8.gp)])
|
||||||
check(int(a8.bonus.get(String(val_map[kind]), -999)) == val0 + int(_db.adventure.feature_value(kind, tier)),
|
check(int(a8.bonus.get(String(val_map[kind]), -999)) == val0 + int(_db.adventure.curio_value(kind, tier)),
|
||||||
"加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.adventure.feature_value(kind, tier))])
|
"加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.adventure.curio_value(kind, tier))])
|
||||||
check(int(a8._bonus_taken.get(kind, 0)) == taken0 + 1, "共享池账本 -1(跨渠道单点——taken %d → %d)" % [taken0, int(a8._bonus_taken.get(kind, 0))])
|
check(int(a8._curio_taken.get(kind, 0)) == taken0 + 1, "共享池账本 -1(跨渠道单点——taken %d → %d)" % [taken0, int(a8._curio_taken.get(kind, 0))])
|
||||||
# A 链:同visit 第二购现价 = ceil(base×b^(l2−1)×A^1)(货架已全重抽——新卡新价同公式)
|
# A 链:同visit 第二购现价 = ceil(base×b^(l2−1)×A^1)(货架已全重抽——新卡新价同公式)
|
||||||
var second: Dictionary = {}
|
var second: Dictionary = {}
|
||||||
for c in r8.shop_shelf():
|
for c in r8.shop_shelf():
|
||||||
@@ -3899,9 +3942,9 @@ func _smoke_shop_pool() -> void:
|
|||||||
cut_seen = true
|
cut_seen = true
|
||||||
check(not cut_seen, "切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)")
|
check(not cut_seen, "切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)")
|
||||||
# ⑤ 池空硬闸(库存尽剔出牌库——TP0:white-box 耗尽 attack 令牌后货架零 attack + 消费口拒绝)
|
# ⑤ 池空硬闸(库存尽剔出牌库——TP0:white-box 耗尽 attack 令牌后货架零 attack + 消费口拒绝)
|
||||||
for di8 in range(a8._bonus_deck.size() - 1, -1, -1):
|
for di8 in range(a8._curio_deck.size() - 1, -1, -1):
|
||||||
if String((a8._bonus_deck[di8] as Dictionary).get("kind", "")) == "attack":
|
if String((a8._curio_deck[di8] as Dictionary).get("kind", "")) == "attack":
|
||||||
a8._bonus_deck.remove_at(di8)
|
a8._curio_deck.remove_at(di8)
|
||||||
var attack_seen := false
|
var attack_seen := false
|
||||||
for i in 40:
|
for i in 40:
|
||||||
r8._shop_draw_shelf()
|
r8._shop_draw_shelf()
|
||||||
@@ -3913,10 +3956,10 @@ func _smoke_shop_pool() -> void:
|
|||||||
check(not a8._consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before,
|
check(not a8._consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before,
|
||||||
"池空消费拒绝(handler 零副作用——买光即枯竭硬闸)")
|
"池空消费拒绝(handler 零副作用——买光即枯竭硬闸)")
|
||||||
# ⑥ 跨渠道共库 shop→reward 向:回填一枚 attack 令牌 → 消费口受理 → 奖励牌库同步摘牌
|
# ⑥ 跨渠道共库 shop→reward 向:回填一枚 attack 令牌 → 消费口受理 → 奖励牌库同步摘牌
|
||||||
a8._bonus_deck.append({"kind": "attack", "tier": "common"})
|
a8._curio_deck.append({"kind": "attack", "tier": "common"})
|
||||||
check(a8._consume_pool_purchase("attack", "common"), "商店消费口受理(_apply_bonus_pick 同口——真单点库)")
|
check(a8._consume_pool_purchase("attack", "common"), "商店消费口受理(_apply_curio_pick 同口——真单点库)")
|
||||||
var reward_attack_seen := false
|
var reward_attack_seen := false
|
||||||
for t6 in a8._draw_bonus_candidates():
|
for t6 in a8._draw_curio_candidates():
|
||||||
if String((t6 as Dictionary).get("kind", "")) == "attack":
|
if String((t6 as Dictionary).get("kind", "")) == "attack":
|
||||||
reward_attack_seen = true
|
reward_attack_seen = true
|
||||||
break
|
break
|
||||||
@@ -3936,12 +3979,12 @@ func _smoke_shop_pool() -> void:
|
|||||||
# ⑧ 终局池读数(run_adventure pool 面——商店与奖励跨渠道消耗后的终局余量出讫)
|
# ⑧ 终局池读数(run_adventure pool 面——商店与奖励跨渠道消耗后的终局余量出讫)
|
||||||
var st_pool: Dictionary = autoplay.run_adventure(_db, 42, 0, 0, false, null, "normal", "min", {}, {}, {"mode": "pool"})
|
var st_pool: Dictionary = autoplay.run_adventure(_db, 42, 0, 0, false, null, "normal", "min", {}, {}, {"mode": "pool"})
|
||||||
var ps: Dictionary = st_pool.get("pool_stock", {})
|
var ps: Dictionary = st_pool.get("pool_stock", {})
|
||||||
check(ps.size() == 4, "终局池读数四键出讫(run_adventure pool 面——读数只锚一次载体)")
|
check(ps.size() == 6, "终局池读数六键出讫(run_adventure pool 面——四池种+T5 门票+T6 heal 行,读数只锚一次载体)")
|
||||||
var ps_ok := true
|
var ps_ok := true
|
||||||
for k in ps.keys():
|
for k in ps.keys():
|
||||||
var quota := 0
|
var quota := 0
|
||||||
for tier in _db.adventure.TIERS:
|
for tier in _db.adventure.TIERS:
|
||||||
quota += int(_db.adventure.feature_quota(String(k), tier))
|
quota += int(_db.adventure.curio_quota(String(k), tier))
|
||||||
if int(ps[k]) < 0 or int(ps[k]) > quota:
|
if int(ps[k]) < 0 or int(ps[k]) > quota:
|
||||||
ps_ok = false
|
ps_ok = false
|
||||||
check(ps_ok, "终局池余量在域 [0,配额](%s)" % str(ps))
|
check(ps_ok, "终局池余量在域 [0,配额](%s)" % str(ps))
|
||||||
@@ -4265,11 +4308,11 @@ func _meta_settle_and_workshop() -> void:
|
|||||||
a12._attunement = ["pick_refresh"]
|
a12._attunement = ["pick_refresh"]
|
||||||
a12._attune_refresh_x = 1
|
a12._attune_refresh_x = 1
|
||||||
a12._attune_refresh_y = 2
|
a12._attune_refresh_y = 2
|
||||||
a12._bonus_pending = {"candidates": [{"kind": "gp", "tier": "common"}, {"kind": "attack", "tier": "common"}, {"kind": "max_hp", "tier": "common"}], "payload": {}}
|
a12._curio_pending = {"candidates": [{"kind": "gp", "tier": "common"}, {"kind": "attack", "tier": "common"}, {"kind": "max_hp", "tier": "common"}], "payload": {}}
|
||||||
check(a12.refresh_bonus() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2,
|
check(a12.refresh_curio() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2,
|
||||||
"刷新双计数器——X 优先消耗(x1y2 → x0y2)")
|
"刷新双计数器——X 优先消耗(x1y2 → x0y2)")
|
||||||
check(a12.refresh_bonus() and a12.refresh_bonus() and a12._attune_refresh_y == 0
|
check(a12.refresh_curio() and a12.refresh_curio() and a12._attune_refresh_y == 0
|
||||||
and a12.refresh_bonus() == false,
|
and a12.refresh_curio() == false,
|
||||||
"刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)")
|
"刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)")
|
||||||
a12._reset_refresh_x()
|
a12._reset_refresh_x()
|
||||||
check(a12.attune_refresh_left() == 1,
|
check(a12.attune_refresh_left() == 1,
|
||||||
|
|||||||
@@ -192,22 +192,22 @@ func _ready() -> void:
|
|||||||
main.node_overlay.press_continue()
|
main.node_overlay.press_continue()
|
||||||
_flags_placed.clear()
|
_flags_placed.clear()
|
||||||
continue
|
continue
|
||||||
if main.bonus_overlay.visible:
|
if main.curio_overlay.visible:
|
||||||
# 迭代 10 三选一停点(BonusPickOverlay——奖励节点/通关兑付共用面;T17 硬锁批适配——
|
# 迭代 10 三选一停点(CurioPickOverlay——奖励节点/通关兑付共用面;T17 硬锁批适配——
|
||||||
# 本探针此前未适配该层,走到即卡死)。三态分派:替换子态(满位——放弃新件)/
|
# 本探针此前未适配该层,走到即卡死)。三态分派:替换子态(满位——放弃新件)/
|
||||||
# result 态(兑付反馈——点继续推进)/ 待选态(点首件+确认)。真实信号路径全线。
|
# result 态(兑付反馈——点继续推进)/ 待选态(点首件+确认)。真实信号路径全线。
|
||||||
# T1 D1——三选一停点输入锁定断言(停点态断言面:替换子/待选/兑付三态共用此层)。
|
# T1 D1——三选一停点输入锁定断言(停点态断言面:替换子/待选/兑付三态共用此层)。
|
||||||
if not main._input_locked():
|
if not main._input_locked():
|
||||||
_problems.append("三选一停点打开时输入未锁定")
|
_problems.append("三选一停点打开时输入未锁定")
|
||||||
if main.bonus_overlay._replace_box.visible:
|
if main.curio_overlay._replace_box.visible:
|
||||||
main.bonus_overlay.replace_chosen.emit(-1)
|
main.curio_overlay.replace_chosen.emit(-1)
|
||||||
elif main.bonus_overlay._continue_btn.visible:
|
elif main.curio_overlay._continue_btn.visible:
|
||||||
main.bonus_overlay.press_continue()
|
main.curio_overlay.press_continue()
|
||||||
elif main.adventure != null and not main.adventure._bonus_pending.is_empty():
|
elif main.adventure != null and not main.adventure._curio_pending.is_empty():
|
||||||
main.bonus_overlay.press_card(0)
|
main.curio_overlay.press_card(0)
|
||||||
main.bonus_overlay.press_confirm()
|
main.curio_overlay.press_confirm()
|
||||||
else:
|
else:
|
||||||
main.bonus_overlay.press_continue() # 停点已消费的残面——继续关屏
|
main.curio_overlay.press_continue() # 停点已消费的残面——继续关屏
|
||||||
_flags_placed.clear()
|
_flags_placed.clear()
|
||||||
continue
|
continue
|
||||||
if main.shop_overlay.visible:
|
if main.shop_overlay.visible:
|
||||||
@@ -324,16 +324,16 @@ func _ready() -> void:
|
|||||||
main._end_overlay._btn.pressed.emit()
|
main._end_overlay._btn.pressed.emit()
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
elif main.bonus_overlay.visible:
|
elif main.curio_overlay.visible:
|
||||||
if main.bonus_overlay._replace_box.visible:
|
if main.curio_overlay._replace_box.visible:
|
||||||
main.bonus_overlay.replace_chosen.emit(-1)
|
main.curio_overlay.replace_chosen.emit(-1)
|
||||||
elif main.bonus_overlay._continue_btn.visible:
|
elif main.curio_overlay._continue_btn.visible:
|
||||||
main.bonus_overlay.press_continue()
|
main.curio_overlay.press_continue()
|
||||||
elif main.adventure != null and not main.adventure._bonus_pending.is_empty():
|
elif main.adventure != null and not main.adventure._curio_pending.is_empty():
|
||||||
main.bonus_overlay.press_card(0)
|
main.curio_overlay.press_card(0)
|
||||||
main.bonus_overlay.press_confirm()
|
main.curio_overlay.press_confirm()
|
||||||
else:
|
else:
|
||||||
main.bonus_overlay.press_continue()
|
main.curio_overlay.press_continue()
|
||||||
elif main.node_overlay.visible:
|
elif main.node_overlay.visible:
|
||||||
main.node_overlay.press_continue()
|
main.node_overlay.press_continue()
|
||||||
elif main.shop_overlay.visible:
|
elif main.shop_overlay.visible:
|
||||||
@@ -609,7 +609,7 @@ func _locktest_tick() -> void:
|
|||||||
return
|
return
|
||||||
_locktest_state = 1
|
_locktest_state = 1
|
||||||
elif _locktest_state == 1:
|
elif _locktest_state == 1:
|
||||||
if main.bonus_overlay.visible or main._inter_overlay.visible or main._end_overlay.is_open() \
|
if main.curio_overlay.visible or main._inter_overlay.visible or main._end_overlay.is_open() \
|
||||||
or String(settlement.battle_result) != "":
|
or String(settlement.battle_result) != "":
|
||||||
_locktest_state = 2 # 前提消失守卫:终结演播竞态——静默弃测
|
_locktest_state = 2 # 前提消失守卫:终结演播竞态——静默弃测
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -80,14 +80,14 @@ func _ready() -> void:
|
|||||||
main._end_overlay.close()
|
main._end_overlay.close()
|
||||||
_settle()
|
_settle()
|
||||||
|
|
||||||
main.bonus_overlay.open_pick("结构探针·三选一", [
|
main.curio_overlay.open_pick("结构探针·三选一", [
|
||||||
{"id": "probe_heal", "text": "回血\n恢复 3 点"},
|
{"id": "probe_heal", "text": "回血\n恢复 3 点"},
|
||||||
{"id": "probe_magic", "text": "爆裂轰击\n下一击 +5"},
|
{"id": "probe_magic", "text": "爆裂轰击\n下一击 +5"},
|
||||||
{"id": "probe_pierce", "text": "穿甲\n无视护甲"},
|
{"id": "probe_pierce", "text": "穿甲\n无视护甲"},
|
||||||
])
|
])
|
||||||
_settle()
|
_settle()
|
||||||
await _audit("bonus_pick", [main.bonus_overlay])
|
await _audit("curio_pick", [main.curio_overlay])
|
||||||
main.bonus_overlay.visible = false
|
main.curio_overlay.visible = false
|
||||||
_settle()
|
_settle()
|
||||||
|
|
||||||
main.shop_overlay.open_shop([
|
main.shop_overlay.open_shop([
|
||||||
|
|||||||
@@ -93,9 +93,9 @@ func _init() -> void:
|
|||||||
adr_e.set_save_manager(sm_e)
|
adr_e.set_save_manager(sm_e)
|
||||||
var params_e := {"hero_override": {}, "treasure_unlocked": {"blast_kit": true}}
|
var params_e := {"hero_override": {}, "treasure_unlocked": {"blast_kit": true}}
|
||||||
Kit.check_quiet(adr_e.start_adventure(4321, params_e), "P5 冒险启动(seed 4321)")
|
Kit.check_quiet(adr_e.start_adventure(4321, params_e), "P5 冒险启动(seed 4321)")
|
||||||
adr_e._bonus_taken["attack"] = 1 # 白盒预置计数(断点附随件往返的判据面——计数不可由加算区反推)
|
adr_e._curio_taken["attack"] = 1 # 白盒预置计数(断点附随件往返的判据面——计数不可由加算区反推)
|
||||||
adr_e._on_level_finished({"result": "victory", "hp": 30, "node_index": 1, "node_id": 1, "node_type": "shop"})
|
adr_e._on_level_finished({"result": "victory", "hp": 30, "node_index": 1, "node_id": 1, "node_type": "shop"})
|
||||||
var cands_e: Array = (adr_e._bonus_pending.get("candidates", []) as Array).duplicate()
|
var cands_e: Array = (adr_e._curio_pending.get("candidates", []) as Array).duplicate()
|
||||||
Kit.check_quiet(not cands_e.is_empty(), "P5 非末关通关三选一停点在案(直调 victory——L1 非末关)")
|
Kit.check_quiet(not cands_e.is_empty(), "P5 非末关通关三选一停点在案(直调 victory——L1 非末关)")
|
||||||
var sm_f = SaveManagerScript.new(_bp_path)
|
var sm_f = SaveManagerScript.new(_bp_path)
|
||||||
Kit.check_quiet(sm_f.load_from_disk() and sm_f.has_breakpoint(), "P5 选择停点断点在档(选择停点可中断契约)")
|
Kit.check_quiet(sm_f.load_from_disk() and sm_f.has_breakpoint(), "P5 选择停点断点在档(选择停点可中断契约)")
|
||||||
@@ -106,24 +106,24 @@ func _init() -> void:
|
|||||||
var got_cands := {"cands": [] as Array}
|
var got_cands := {"cands": [] as Array}
|
||||||
var adr_f = AdventureRunnerScript.new(db, load("res://core/rng_service.gd").new())
|
var adr_f = AdventureRunnerScript.new(db, load("res://core/rng_service.gd").new())
|
||||||
adr_f.set_save_manager(sm_f) # 兑付后停点快照落盘承载(与真机组合根同构)
|
adr_f.set_save_manager(sm_f) # 兑付后停点快照落盘承载(与真机组合根同构)
|
||||||
adr_f.bonus_choice_presented.connect(func(p): got_cands["cands"] = (p.get("candidates", []) as Array).duplicate())
|
adr_f.curio_choice_presented.connect(func(p): got_cands["cands"] = (p.get("candidates", []) as Array).duplicate())
|
||||||
Kit.check_quiet(adr_f.resume_adventure(bp_f, params_e), "P5 续玩受理(选择态分支)")
|
Kit.check_quiet(adr_f.resume_adventure(bp_f, params_e), "P5 续玩受理(选择态分支)")
|
||||||
Kit.check_quiet(adr_f.runner() == null, "P5 续玩不重跑关卡(三选一停点直回,_start_current_level 未走)")
|
Kit.check_quiet(adr_f.runner() == null, "P5 续玩不重跑关卡(三选一停点直回,_start_current_level 未走)")
|
||||||
Kit.check_quiet(got_cands["cands"] == cands_e, "P5 续玩候选不重抽(逐位一致)")
|
Kit.check_quiet(got_cands["cands"] == cands_e, "P5 续玩候选不重抽(逐位一致)")
|
||||||
Kit.check_quiet(int(adr_f._bonus_taken["attack"]) == 1, "P5 次数计数恢复一致(牌库剔除态续接)")
|
Kit.check_quiet(int(adr_f._curio_taken["attack"]) == 1, "P5 次数计数恢复一致(牌库剔除态续接)")
|
||||||
Kit.check_quiet(not adr_f.continue_to_next_level(), "P5 未兑付推进拒绝(续玩态零自动推进同契约)")
|
Kit.check_quiet(not adr_f.continue_to_next_level(), "P5 未兑付推进拒绝(续玩态零自动推进同契约)")
|
||||||
var pick_e: Dictionary = cands_e[0]
|
var pick_e: Dictionary = cands_e[0]
|
||||||
Kit.check_quiet(adr_f.pick_bonus(String(pick_e.get("kind", "")), String(pick_e.get("tier", ""))), "P5 点选兑付受理")
|
Kit.check_quiet(adr_f.pick_curio(String(pick_e.get("kind", "")), String(pick_e.get("tier", "")), String(pick_e.get("feat_id", ""))), "P5 点选兑付受理")
|
||||||
var picked_e: String = String(adr_f._bonus_result.get("picked", ""))
|
var picked_e: String = String(adr_f._curio_result.get("picked", ""))
|
||||||
if adr_f._bonus_taken.has(picked_e):
|
if adr_f._curio_taken.has(picked_e):
|
||||||
Kit.check_quiet(int(adr_f._bonus_taken[picked_e]) == (2 if picked_e == "attack" else 1),
|
Kit.check_quiet(int(adr_f._curio_taken[picked_e]) == (2 if picked_e == "attack" else 1),
|
||||||
"P5 兑付计数一致(%s——预置 1 + 本次兑付)" % picked_e)
|
"P5 兑付计数一致(%s——预置 1 + 本次兑付)" % picked_e)
|
||||||
Kit.check_quiet(sm_f.has_breakpoint(), "P5 兑付后停点快照在档(已兑付待推进态可中断)")
|
Kit.check_quiet(sm_f.has_breakpoint(), "P5 兑付后停点快照在档(已兑付待推进态可中断)")
|
||||||
var bp_g: Dictionary = sm_f.breakpoint_snapshot()
|
var bp_g: Dictionary = sm_f.breakpoint_snapshot()
|
||||||
var adr_g = AdventureRunnerScript.new(db, load("res://core/rng_service.gd").new())
|
var adr_g = AdventureRunnerScript.new(db, load("res://core/rng_service.gd").new())
|
||||||
adr_g.set_save_manager(sm_f) # 已兑付态续玩的后续快照同承载
|
adr_g.set_save_manager(sm_f) # 已兑付态续玩的后续快照同承载
|
||||||
var got_result := {"r": {}}
|
var got_result := {"r": {}}
|
||||||
adr_g.bonus_resolved.connect(func(r): got_result["r"] = r.duplicate())
|
adr_g.curio_resolved.connect(func(r): got_result["r"] = r.duplicate())
|
||||||
Kit.check_quiet(adr_g.resume_adventure(bp_g, params_e), "P5 已兑付态续玩受理")
|
Kit.check_quiet(adr_g.resume_adventure(bp_g, params_e), "P5 已兑付态续玩受理")
|
||||||
Kit.check_quiet(String(got_result["r"].get("picked", "")) == picked_e, "P5 已兑付不重付(resolved 重发同件)")
|
Kit.check_quiet(String(got_result["r"].get("picked", "")) == picked_e, "P5 已兑付不重付(resolved 重发同件)")
|
||||||
Kit.check_quiet(adr_g.continue_to_next_level(), "P5 已兑付续玩后推进受理(下一关开始)")
|
Kit.check_quiet(adr_g.continue_to_next_level(), "P5 已兑付续玩后推进受理(下一关开始)")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
extends SceneTree
|
extends SceneTree
|
||||||
# 三选一覆盖层 UI 直验套件(迭代 7 T5-hotfix 转正;迭代 10 追加批随面迁移改写)——
|
# 三选一覆盖层 UI 直验套件(迭代 7 T5-hotfix 转正;迭代 10 追加批随面迁移改写)——
|
||||||
# 原守 node_overlay.show_reward_items;奖励节点三选一与通关三选一合并共用 BonusPickOverlay 后,
|
# 原守 node_overlay.show_reward_items;奖励节点三选一与通关三选一合并共用 CurioPickOverlay 后,
|
||||||
# 本套件改守共用面的「功能存在 ≠ 功能可达」盲区:选择外发/强制确认两段式/替换子面板两路/结果态。
|
# 本套件改守共用面的「功能存在 ≠ 功能可达」盲区:选择外发/强制确认两段式/替换子面板两路/结果态。
|
||||||
# 运行:godot --headless --path game --script res://tests/unit/presentation/reward_overlay_test.gd(expect 0)
|
# 运行:godot --headless --path game --script res://tests/unit/presentation/reward_overlay_test.gd(expect 0)
|
||||||
# T6 收口批:断言走 test_kit 计数(Kit.check)+ 失败即停早退协议保留(顺序状态机——坏态下后继断言无效)。
|
# T6 收口批:断言走 test_kit 计数(Kit.check)+ 失败即停早退协议保留(顺序状态机——坏态下后继断言无效)。
|
||||||
@@ -18,7 +18,7 @@ func _initialize() -> void:
|
|||||||
call_deferred("_run")
|
call_deferred("_run")
|
||||||
|
|
||||||
func _run() -> void:
|
func _run() -> void:
|
||||||
var ov = load("res://presentation/views/bonus_pick_overlay.gd").new()
|
var ov = load("res://presentation/views/curio_pick_overlay.gd").new()
|
||||||
root.add_child(ov)
|
root.add_child(ov)
|
||||||
var picked := []
|
var picked := []
|
||||||
var replaced := []
|
var replaced := []
|
||||||
@@ -32,10 +32,9 @@ func _run() -> void:
|
|||||||
{"id": "gp", "text": "金币袋\n金币 +20"},
|
{"id": "gp", "text": "金币袋\n金币 +20"},
|
||||||
{"id": "ap", "text": "行动点提升\n每战首轮 AP +1"},
|
{"id": "ap", "text": "行动点提升\n每战首轮 AP +1"},
|
||||||
])
|
])
|
||||||
var btns := 0
|
# M7 bcard 换装适配(2026-10-05 奇物批 T2 连带):三选一卡自 M7 起为 BCard(PanelContainer) 非 Button——
|
||||||
for c in ov._choice_box.get_children():
|
# 原 `c is Button` 计数自 M7 后恒 0(基线红成因,主区 3c4291d 起红在案)。改数 _choice_box 子节点数,断言语义不变(三卡呈现)。
|
||||||
if c is Button:
|
var btns: int = ov._choice_box.get_child_count()
|
||||||
btns += 1
|
|
||||||
if not _req("选择态应三卡呈现且确认/继续隐藏(btns=%d)" % btns,
|
if not _req("选择态应三卡呈现且确认/继续隐藏(btns=%d)" % btns,
|
||||||
ov.visible and btns == 3 and not ov._confirm_btn.visible and not ov._continue_btn.visible):
|
ov.visible and btns == 3 and not ov._confirm_btn.visible and not ov._continue_btn.visible):
|
||||||
return
|
return
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@
|
|||||||
| 迭代 13 | **工程债与测试基建批(含 D6 爆破工兵包实施)**:六条目=ui_click_probe 完整局适配(D1——分派链序修正+停点/锁期/重放三态断言)/探针只读出口(D2——六出口 26 直入清零)/ArmedTimer helper(D3——P4 乙三处计时收口缝零动)/main.gd 二期瘦身(D4——bonus_interact_controller+camp_shop_presenter 拆件 1370→1119,家族②契约3绑定面留置;~900±50 锚差=族级清单上限不自洽已归因)/aux 二期合并(T5 判不合收口)/杂项尾差四件(D5)/爆破工兵包中立移除雷格(D6——P1 甲邻接冻结/P2 甲退色降档,判定逻辑零改动,充能表驱动零新码)。**✅ 已落籍(2026-10-04,台账 342)**:worktree m4iter13-debt-20261004 八提交 cd7d3ea…2402877 合并 eff8cb8;闸=冒烟 718/0(断言数持平基线)+run_all 15+click 三种子绿+全探针绿+doc 三闸零新增+熵增 PASS+MC 零跑(静态零漂移证明);合并后主区全量复跑同绿 | 迭代 12 出口 | T0-T9 ≈4.8-5.3d(知情超限单批,实际单会话) |
|
| 迭代 13 | **工程债与测试基建批(含 D6 爆破工兵包实施)**:六条目=ui_click_probe 完整局适配(D1——分派链序修正+停点/锁期/重放三态断言)/探针只读出口(D2——六出口 26 直入清零)/ArmedTimer helper(D3——P4 乙三处计时收口缝零动)/main.gd 二期瘦身(D4——bonus_interact_controller+camp_shop_presenter 拆件 1370→1119,家族②契约3绑定面留置;~900±50 锚差=族级清单上限不自洽已归因)/aux 二期合并(T5 判不合收口)/杂项尾差四件(D5)/爆破工兵包中立移除雷格(D6——P1 甲邻接冻结/P2 甲退色降档,判定逻辑零改动,充能表驱动零新码)。**✅ 已落籍(2026-10-04,台账 342)**:worktree m4iter13-debt-20261004 八提交 cd7d3ea…2402877 合并 eff8cb8;闸=冒烟 718/0(断言数持平基线)+run_all 15+click 三种子绿+全探针绿+doc 三闸零新增+熵增 PASS+MC 零跑(静态零漂移证明);合并后主区全量复跑同绿 | 迭代 12 出口 | T0-T9 ≈4.8-5.3d(知情超限单批,实际单会话) |
|
||||||
| 迭代 14 | **初始展开收窄批(2026-10-04 用户拍板甲乙混合立项)**:甲=先布后删剃雷域收窄至出生 3×3∪外扩 r 环(参数化半径,现值 r=1——开阔地均值从板面 66.5% 根因性降至约三成)+乙=生成重掷闸 R₀≤cap(合法性门序列、cap=板面 30% 锚〔10×10=30/12×12=43/13×13=51〕、重试耗尽走既有三分出口);**玩法侧零改动路线**(排雷玩法「初始展开恰等」验收原样为真,minesweeper_rules.gd 零 diff);裸数字截断路否决退役(可见 0 格+未开邻居=扫雷不可能态)。可行性报告=`design/research/研究-初始展开收窄可行性-2026-10-04.md`,任务书=`design/research/任务书-初始展开收窄批-2026-10-04.md`(T0-T8≈5.25d,读数批 T5-T6 可切独立后批;拍板点 P1-P3 全读数后置;停机点 S1 相变警戒/S2 带越界回调)。**✅ 实施批落地(2026-10-05,worktree m4iter14-narrow-20261005 台账 357;S1 停机呈报已裁=用户方案A)**:T0-T4+T7+T8 全落(T5 结构读数随批出讫;T6 MC 四带复算读数批按切分预案后置,跑批待用户批准挂 07 现行义务)——甲=剃雷域收窄(shave_ring_radius 参数化,r=1→出生 5×5,全域 flood 剃雷退役,零 RNG 消费变化)+乙=R₀≤cap 重掷闸(合法性闸序列+同流重试+耗尽三分出口诚实降级+r0_cap_rejected 指标)+参数面两列(schema 域校验加载期致命+19 行投值)+断言面 +12(R₀≤cap/ring_removed 量级/cap 压线诚实降级/确定性逐位)+夹具复核 3 处(relic 候选身份按 TP0 v2.2=kind|tier|relic_id、商店购买笔数推导防 GP 流漂移、极性探针种子自愈);**S1 停机实录**=r=1 自然 R₀ 均值 39>cap30 锚结构性倒挂(94% 拒绝/inf 0.976→0.719/常态非法板)→用户裁方案A「10×10 cap 30→45」〔P1 上调预案字面〕,12×12/13×13 健康侧维持;改后结构读数=R₀ 均值 39.7/37.9/42.8/46.6(收窄自 68.6/83.2——10×10 −42%、12×12 −54%)/尾部闸精确截断/雷密度 19.2-21.6%<25% 相变线/inf 全行达标;玩法侧零改动(minesweeper_rules.gd 零 diff、「初始展开恰等」验收原样为真);闸=冒烟 767/0(+12);registry initial_expansion_narrowing 条+棋盘生成.md v0.5+07⑬ 四信号组;06 视野条目已于 745b92ea 清账批退役(收窄立项即正主)。**②挖掘风险不等式判式换代批(2026-10-04 用户裁编入本迭代,作为迭代内需求)**:判式三层换代(条款2 标中旗锚/条款3 聚合健康式「(1−ρ̂)E_dig−ρ̂V_mine≥0」/旗边际引理+条件二旗溢价)+β 比值带判读 [1.2,2.5](u=3 登记态)+标中兑付溢价 δ=1.25(形态 a 兑付公式一处·雷安同池冻结不动)+runsum 旗行为五字段+audit 工具常量刷新(池 77/直伤 6-8-8/AP 8);裁决全落零新拍板点(方案报告 §九(已退役,git 1da9a41)),任务书=`.zcode/plans/mine-inequality-revision-miniloop-plan-2026-10-04.md`(T0-T5≈2.5d;条件二 T3 落文按登记已知破·T4 机制转绿;契约红线=DamageController 零改/直伤雷 6-8-8 与 p_perfect 不动——iter8 缺口四选一解绑独立在案;开工门=U0)。**✅ 实施批落地(2026-10-05,worktree m4iter14-mineq-20261005 台账 356)**:T0-T5 全落——audit 工具常量刷新(改前静态锚与方案报告 §四 逐位吻合)+runsum 五字段(行侧判据考证=授予行专带 highlight,87行==87插旗1:1)+F1 条款3 重立三层口径(v0.8)+战斗结算 v0.25+难度联动表+6 行+registry clause3_inequality 重立条(改前锚+P7 修正注记)+06 破线条收口+**T4 标中兑付溢价机制**(settlement.flag_hit_premium=125 provisional,grant 单点只挂 flag_hit 正向·percent 幅度贯穿应用与呈现,标空/挖格不乘,DamageController 零改/同池冻结)+smoke 750/0(+3 断言;max 路径种子 42→43 重采);判读三件=β 带 u=1/2 带内 u=3 登记态/δ 敏感度行为无感(η 位移 <1.4pp<5pp,保留+登记)/条件二判式口径等号压线转绿(整数量级实现 u=1/2 绿·u=3 微欠 −1.1% 登记面);改后锚换代 runsum_m4iter14mineq_post_n200_20261005(perfect 冒险通关 65.3%→72.0% 增强向位移,四带判读归 M-4);出口报告=design/qa/m4iter14-mineq-exit-report-2026-10-05.md(provisional 追认单 8 项+07⑫ 人工项);damage_probe A1 红灯=改前既有非本批新增(在册)。**③全局作用域变异首发批(2026-10-04 用户拍板立项——调研报告 P1-P8+盘问 Q1-Q4 全按推荐案落,批内序位待排)**:L2/L3 Boss 图恒定一型 scope=global 变异,首发型=**案A 资源面·微贫向偏斜**(正向格面池合计不变、进攻族 attack/blast/pierce/magic 权重上浮/生存族 heal/defense 明显下调、EV 微降 3-5% provisional、gp/action 白名单不动;v1.0「沃土+雷密对冲」形勘误作废——雷数Line truncated
|
| 迭代 14 | **初始展开收窄批(2026-10-04 用户拍板甲乙混合立项)**:甲=先布后删剃雷域收窄至出生 3×3∪外扩 r 环(参数化半径,现值 r=1——开阔地均值从板面 66.5% 根因性降至约三成)+乙=生成重掷闸 R₀≤cap(合法性门序列、cap=板面 30% 锚〔10×10=30/12×12=43/13×13=51〕、重试耗尽走既有三分出口);**玩法侧零改动路线**(排雷玩法「初始展开恰等」验收原样为真,minesweeper_rules.gd 零 diff);裸数字截断路否决退役(可见 0 格+未开邻居=扫雷不可能态)。可行性报告=`design/research/研究-初始展开收窄可行性-2026-10-04.md`,任务书=`design/research/任务书-初始展开收窄批-2026-10-04.md`(T0-T8≈5.25d,读数批 T5-T6 可切独立后批;拍板点 P1-P3 全读数后置;停机点 S1 相变警戒/S2 带越界回调)。**✅ 实施批落地(2026-10-05,worktree m4iter14-narrow-20261005 台账 357;S1 停机呈报已裁=用户方案A)**:T0-T4+T7+T8 全落(T5 结构读数随批出讫;T6 MC 四带复算读数批按切分预案后置,跑批待用户批准挂 07 现行义务)——甲=剃雷域收窄(shave_ring_radius 参数化,r=1→出生 5×5,全域 flood 剃雷退役,零 RNG 消费变化)+乙=R₀≤cap 重掷闸(合法性闸序列+同流重试+耗尽三分出口诚实降级+r0_cap_rejected 指标)+参数面两列(schema 域校验加载期致命+19 行投值)+断言面 +12(R₀≤cap/ring_removed 量级/cap 压线诚实降级/确定性逐位)+夹具复核 3 处(relic 候选身份按 TP0 v2.2=kind|tier|relic_id、商店购买笔数推导防 GP 流漂移、极性探针种子自愈);**S1 停机实录**=r=1 自然 R₀ 均值 39>cap30 锚结构性倒挂(94% 拒绝/inf 0.976→0.719/常态非法板)→用户裁方案A「10×10 cap 30→45」〔P1 上调预案字面〕,12×12/13×13 健康侧维持;改后结构读数=R₀ 均值 39.7/37.9/42.8/46.6(收窄自 68.6/83.2——10×10 −42%、12×12 −54%)/尾部闸精确截断/雷密度 19.2-21.6%<25% 相变线/inf 全行达标;玩法侧零改动(minesweeper_rules.gd 零 diff、「初始展开恰等」验收原样为真);闸=冒烟 767/0(+12);registry initial_expansion_narrowing 条+棋盘生成.md v0.5+07⑬ 四信号组;06 视野条目已于 745b92ea 清账批退役(收窄立项即正主)。**②挖掘风险不等式判式换代批(2026-10-04 用户裁编入本迭代,作为迭代内需求)**:判式三层换代(条款2 标中旗锚/条款3 聚合健康式「(1−ρ̂)E_dig−ρ̂V_mine≥0」/旗边际引理+条件二旗溢价)+β 比值带判读 [1.2,2.5](u=3 登记态)+标中兑付溢价 δ=1.25(形态 a 兑付公式一处·雷安同池冻结不动)+runsum 旗行为五字段+audit 工具常量刷新(池 77/直伤 6-8-8/AP 8);裁决全落零新拍板点(方案报告 §九(已退役,git 1da9a41)),任务书=`.zcode/plans/mine-inequality-revision-miniloop-plan-2026-10-04.md`(T0-T5≈2.5d;条件二 T3 落文按登记已知破·T4 机制转绿;契约红线=DamageController 零改/直伤雷 6-8-8 与 p_perfect 不动——iter8 缺口四选一解绑独立在案;开工门=U0)。**✅ 实施批落地(2026-10-05,worktree m4iter14-mineq-20261005 台账 356)**:T0-T5 全落——audit 工具常量刷新(改前静态锚与方案报告 §四 逐位吻合)+runsum 五字段(行侧判据考证=授予行专带 highlight,87行==87插旗1:1)+F1 条款3 重立三层口径(v0.8)+战斗结算 v0.25+难度联动表+6 行+registry clause3_inequality 重立条(改前锚+P7 修正注记)+06 破线条收口+**T4 标中兑付溢价机制**(settlement.flag_hit_premium=125 provisional,grant 单点只挂 flag_hit 正向·percent 幅度贯穿应用与呈现,标空/挖格不乘,DamageController 零改/同池冻结)+smoke 750/0(+3 断言;max 路径种子 42→43 重采);判读三件=β 带 u=1/2 带内 u=3 登记态/δ 敏感度行为无感(η 位移 <1.4pp<5pp,保留+登记)/条件二判式口径等号压线转绿(整数量级实现 u=1/2 绿·u=3 微欠 −1.1% 登记面);改后锚换代 runsum_m4iter14mineq_post_n200_20261005(perfect 冒险通关 65.3%→72.0% 增强向位移,四带判读归 M-4);出口报告=design/qa/m4iter14-mineq-exit-report-2026-10-05.md(provisional 追认单 8 项+07⑫ 人工项);damage_probe A1 红灯=改前既有非本批新增(在册)。**③全局作用域变异首发批(2026-10-04 用户拍板立项——调研报告 P1-P8+盘问 Q1-Q4 全按推荐案落,批内序位待排)**:L2/L3 Boss 图恒定一型 scope=global 变异,首发型=**案A 资源面·微贫向偏斜**(正向格面池合计不变、进攻族 attack/blast/pierce/magic 权重上浮/生存族 heal/defense 明显下调、EV 微降 3-5% provisional、gp/action 白名单不动;v1.0「沃土+雷密对冲」形勘误作废——雷数Line truncated
|
||||||
| 迭代 15 | **①调试功能整合批(2026-10-05 用户令自「14.5」临时卡改编入册——原周期外 0.5 卡,落地事实与验证读数零变化,只改编号归属)**:主菜单三调试散件(随机种子输入框/角色数值调整板/两层调试入口)收一张调试浮窗 DebugOverlay——入口=开始覆盖层右下角「调试」角标(与主按钮组非平级,DS_DEBUG=1 环境变量门控沿用迭代 8 卡④口径);浮窗=悬浮菜单之上 Dialog(不隐藏开始层,同确认弹窗形态;旧 TunePanel「与开始层互斥」形态与类整体吸收退役含 .uid);可扩展滚动列表页四区块(随机种子/数值调整/批量模拟/两层调试入口——后续调试项往下追加滚轮下探)。四拍板全落(2026-10-04 用户裁)=P1 甲 DS_DEBUG 门控/P2 乙全摊平/P3 仅开始菜单可开·对局不可达(_show_debug_overlay 门禁护栏+对局发起随开随收)/P4 可扩展列表。**✅ 已全批落籍(2026-10-05,worktree iter145-debug-hub 两提交 48c0b34/46dadbb,主区 merge --no-ff d0a3c6b/10c301b,台账 352)**:闸=冒烟 747/0+探针 ui_tune/ui_click/ui 三绿(worktree+主区双跑,主区跑前 --import 重刷类名缓存在册坑);遗留=实机目检归用户(DS_DEBUG=1 启动→右下角角标→浮窗四区块→两层调试入口) | 卡①门:迭代 14 出口(注:本卡先于迭代 14 出口落籍——编入时点迭代 14 ③已闭/①②④⑤在飞/⑥挂起,门列仅记编入时点状态,不构成追溯闸) | 单批已落地(原估 0.5d,实际一次会话);后续扩调试项=debug_overlay.gd _build() 列表区追加区块(P4 扩展口径) |
|
| 迭代 15 | **①调试功能整合批(2026-10-05 用户令自「14.5」临时卡改编入册——原周期外 0.5 卡,落地事实与验证读数零变化,只改编号归属)**:主菜单三调试散件(随机种子输入框/角色数值调整板/两层调试入口)收一张调试浮窗 DebugOverlay——入口=开始覆盖层右下角「调试」角标(与主按钮组非平级,DS_DEBUG=1 环境变量门控沿用迭代 8 卡④口径);浮窗=悬浮菜单之上 Dialog(不隐藏开始层,同确认弹窗形态;旧 TunePanel「与开始层互斥」形态与类整体吸收退役含 .uid);可扩展滚动列表页四区块(随机种子/数值调整/批量模拟/两层调试入口——后续调试项往下追加滚轮下探)。四拍板全落(2026-10-04 用户裁)=P1 甲 DS_DEBUG 门控/P2 乙全摊平/P3 仅开始菜单可开·对局不可达(_show_debug_overlay 门禁护栏+对局发起随开随收)/P4 可扩展列表。**✅ 已全批落籍(2026-10-05,worktree iter145-debug-hub 两提交 48c0b34/46dadbb,主区 merge --no-ff d0a3c6b/10c301b,台账 352)**:闸=冒烟 747/0+探针 ui_tune/ui_click/ui 三绿(worktree+主区双跑,主区跑前 --import 重刷类名缓存在册坑);遗留=实机目检归用户(DS_DEBUG=1 启动→右下角角标→浮窗四区块→两层调试入口) | 卡①门:迭代 14 出口(注:本卡先于迭代 14 出口落籍——编入时点迭代 14 ③已闭/①②④⑤在飞/⑥挂起,门列仅记编入时点状态,不构成追溯闸) | 单批已落地(原估 0.5d,实际一次会话);后续扩调试项=debug_overlay.gd _build() 列表区追加区块(P4 扩展口径) |
|
||||||
| 迭代 15 | **②奇物概念收口与词族对齐批(2026-10-05 用户令编入本迭代)**:概念层已由用户逐轮裁定定稿(《奇物》v1.0→v2.7,7 轮),本批只做**落地对齐**、不做二次设计。三批内容:**①三套旧词族全面对齐**(顶层已改名「奇物」而代码仍叫旧名=用户最初抱怨的"文档与代码分开"):`feature_*`→`curio_*`(103 处/7 文件)、`relic_*`→`feat_*`(162 处/12 文件)、运行层 `bonus_*`→`curio_*`(160 处/8 文件)。**硬约束:`reward_*` 已被 `reward_pick`/`reward_node`/`reward_preview` 占用,统一词族不得使用**;`relic_table.gd` 与两个控制器类改名须同步 Godot `.uid`/`.tscn`(非文本层无法自证)。**②三条渠道按分类分离**(现行三渠道共用同一个 98 张池、三类混编):关底 Boss 击败=通过这一关→只出专长;奖励节点/商店→属性值提升+资源、专长在此**只以门票形式**出现。**兑付频率红线**:奇物不是打一场战斗给一份,关内普通战斗节点打完不给任何奇物,一关之内只兑付一次(用户 2026-10-05 纠错)。**③两个新种类入池**=刷新次数(须先与"商店独立刷新机制"划清边界)+ 专长获取门票(拿到即开不可囤积/罕见只在概率/一局限购一次/计步保底:连续 5 次三选一展示未出→第 6 次必出,刷新重展计入、奖励节点与商店共享计数);附带 `shop.tres` 补回复(默认原则要求分类内成员都在该出现的渠道池里)与 charge 标签补至 ≥4 件(现仅 3 件,档二永远达不到——数据缺陷)。设计事实源=`design/gdd/systems/冒险循环/奇物.md` v2.7,任务书=`design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md`(T0-T8≈4.5d,两段切分预案在册〔段一词族 T0-T3≈2d/段二渠道+入表 T4-T8≈2.5d〕,切分点=T3 出口;11 条设计契约含 6 条 🔴 停机级;12 条批末验收断言;代拍板白名单 5 项;待用户裁点 4 项〔玩家可见面违规/第四关档二复裁/门票四参数终值/存档键正式迁移〕)。**开工门**:与在途 M-4 各批(判式换代/全局变异/配比预设)撞同一批文件(`adventure_table.gd`/`level_runner.gd`/`adventure.tres`),须确认不并行或用户明示插队 | 卡②门:迭代 14 出口 | 卡①已落籍(原估 0.5d);卡②=**文档+数据层落籍(c571af2),渠道分离与入池实施段+curio 换名回归迭代 15 继续完成(2026-10-05 用户令)**——原估 T0-T8≈4.5d(可两段切分:词族对齐段 + 渠道入表段);**迭代 15 整行保持开放不标完成** |
|
| 迭代 15 | **②奇物概念收口与词族对齐批(2026-10-05 用户令编入本迭代)**:概念层已由用户逐轮裁定定稿(《奇物》v1.0→v2.7,7 轮),本批只做**落地对齐**、不做二次设计。三批内容:**①三套旧词族全面对齐**(顶层已改名「奇物」而代码仍叫旧名=用户最初抱怨的"文档与代码分开"):`feature_*`→`curio_*`(103 处/7 文件)、`relic_*`→`feat_*`(162 处/12 文件)、运行层 `bonus_*`→`curio_*`(160 处/8 文件)。**硬约束:`reward_*` 已被 `reward_pick`/`reward_node`/`reward_preview` 占用,统一词族不得使用**;`relic_table.gd` 与两个控制器类改名须同步 Godot `.uid`/`.tscn`(非文本层无法自证)。**②三条渠道按分类分离**(现行三渠道共用同一个 98 张池、三类混编):关底 Boss 击败=通过这一关→只出专长;奖励节点/商店→属性值提升+资源、专长在此**只以门票形式**出现。**兑付频率红线**:奇物不是打一场战斗给一份,关内普通战斗节点打完不给任何奇物,一关之内只兑付一次(用户 2026-10-05 纠错)。**③两个新种类入池**=刷新次数(须先与"商店独立刷新机制"划清边界)+ 专长获取门票(拿到即开不可囤积/罕见只在概率/一局限购一次/计步保底:连续 5 次三选一展示未出→第 6 次必出,刷新重展计入、奖励节点与商店共享计数);附带 `shop.tres` 补回复(默认原则要求分类内成员都在该出现的渠道池里)与 charge 标签补至 ≥4 件(现仅 3 件,档二永远达不到——数据缺陷)。设计事实源=`design/gdd/systems/冒险循环/奇物.md` v2.7,任务书=`design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md`(T0-T8≈4.5d,两段切分预案在册〔段一词族 T0-T3≈2d/段二渠道+入表 T4-T8≈2.5d〕,切分点=T3 出口;11 条设计契约含 6 条 🔴 停机级;12 条批末验收断言;代拍板白名单 5 项;待用户裁点 4 项〔玩家可见面违规/第四关档二复裁/门票四参数终值/存档键正式迁移〕)。**开工门**:与在途 M-4 各批(判式换代/全局变异/配比预设)撞同一批文件(`adventure_table.gd`/`level_runner.gd`/`adventure.tres`),须确认不并行或用户明示插队 | 卡②门:迭代 14 出口 | 卡①已落籍(原估 0.5d);卡② ✅ **已全批落籍(2026-10-05,worktree curio-wordfamily 七提交 d4cde73..049aa62+master 同步 23547b5,主区 merge 见台账;出口报告=[iter15-2-curio-exit-report](design/qa/iter15-2-curio-exit-report-2026-10-05.md))**:三套词族代码层全换(feature_→curio_ 93 处/bonus_ 运行层→curio_/relic_→feat_——feat_table.gd/feats.tres/feats_engine.gd 改名);渠道白名单分离(总池-渠道子集多级缓存形=用户裁);门票入表(四参数 provisional 20%/2/1/5+拿到即开+计步保底+限购 1+节点商店共享计数);refresh 入池(B 案 heal 5→3 挤占)+shop.tres 补 heal 行+门票行;T2 裁点甲案=六存档键禁改(含任务书漏登记的 "relics" 键);charge 缺陷登记不硬补;autoplay 门票分支缺陷批内实测修复(MC timeout 形态);闸=冒烟 782/0(+10)+run_all 16 套历史首次全绿(reward_overlay 红转绿=M7 bcard 成因修复)+五探针绿+doc 三闸零新增+MC n200 只测不判(normal 0.5%/perfect 77.5%);遗留=07⑰ 目检归用户+C1 呈现批/C2 第四关批/C3 调参批/C4 存档批四移交。原估 T0-T8≈4.5d(实际一次会话);**迭代 15 整行保持开放不标完成** |
|
||||||
| 迭代 15 | **③秘宝代码词族对齐批(2026-10-05 用户令编入本迭代)**:设计侧已完成(消歧更名批 `430419c`+`06e94b7`,26 个设计文档「魔法道具」→「**秘宝**」,同调位机制名保留不变),本批只做**代码侧对齐**、不做二次设计。内容:`aux_*`/`AuxItem*` 词族全族换词 **358 处 / 36 文件**(重灾 `adventure_runner.gd`79 / `level_runner.gd`27 / `bonus_interact_controller.gd`19 / `battle_orchestrator.gd`18 / `config_db.gd`14 / `main.gd`13),分五组落:**A 玩家可见文案(P0,5 处)**=`battle_narrator.gd:117-118` + `bonus_interact_controller.gd:76` + `hero_info_panel_view.gd:56`(槽位标题 `UiKit.label`)+ `:178`(tooltip)· B schema 与数据表(`aux_item_table.gd`/`aux_items.tres`/`aux_items.gd` 文件名+类名+`ConfigDB` 持有变量与访问器全链跟改,**须同步 Godot `.uid`/`.tscn` + `--import` 重刷类名缓存**——迭代 14.5 实证在册坑)· C 文档链接修复(含 `architecture.md` 两处既有断债顺带清偿)· D 中文注释层(**须分色**:`魔法矢`/`魔法反噬`/`魔法族`/`魔法护甲` 属敌方伤害护甲语汇🔴不动)· E 测试文件名+内部变量。**用户三项拍板**:全族彻底改(含数据表)· **断点存档键一律保留**(`bonus_aux_carry`/`aux_detail`/`aux_cleared`/`aux_cleared_last`/`aux_items` 断点数组/`attunement_charges` 共 34 处引用一字不改,破档即进度丢失)· registry 条目键 `aux_items` 不改(数据契约键)。设计事实源=`design/gdd/systems/排雷循环/秘宝.md` v0.9,任务书=`design/research/任务书-秘宝代码词族对齐批-2026-10-05.md`(T0-T9≈5.0d,**超软盒 3 日上限·知情预挂**:T7 段末闸后 L3 呈报切点不擅切;**三档降级面 G1/G2/G3 在册**〔G3 下限=仅组 A 完成〕;5 条设计契约含 5 条 🔴 停机级〔存档兼容零破坏/数值零漂移/schema 契约面零变更/零字段更名/核心层零 await〕;代拍板白名单 5 项;**C1/C2 已裁关闭·全改保一致·待裁点 0 项**)。**撞批已消解**:② 奇物批 2026-10-05 收口落库(合并 `c571af2`),并行风险解除,本批为迭代 15 唯一在途需求;**T0 须重取基线**(判式换代批 T5 读数 750/0、② 又动数据层,§1.2 的 747 为②前旧数禁沿用)并复核事实表。**✅ 裁决(用户 2026-10-05「全改保一致」)**:C1 `"aux_item"` 域串**照改**(实测连锁面收窄——`reward.tres` 已零 aux_item 数据行,池种随迭代 11 T7 退役;16 处字面量全在代码内闭合无存档依赖;唯一连锁项=`smoke_all.gd:3932` 断言须同步)· C2 `aux_items` 一名两义**拆分**(ConfigDB 持有变量+表注册键✅改 / 断点存档键+冒烟夹具🔴禁改——**「键名禁改」与「变量名禁改」是两回事**,禁改的只是落盘字面量)。新增**组 F**(分派语汇与域串 16 处),任务卡扩为 T0-T10≈5.5d。**开工门**:✅ 已无待裁点,可直接发 T0 | 卡③门:迭代 14 出口(**裁点已全决·可开工**) | 卡①已落籍(原估 0.5d);卡②文档+数据层落籍(`c571af2`,实施段回归迭代 15 在飞——2026-10-05 用户令);**卡③ ✅ 已全批落籍(2026-10-05,worktree m4iter15-mibao 九提交 b4b8bb3..收口笔,主区 merge --no-ff 哈希见台账/active.md 快照行;出口报告=[iter15-3-mibao-exit-report](design/qa/iter15-3-mibao-exit-report-2026-10-05.md))**:词族实数 364 处/36 文件全换尽(例外 43+2 处=存档键引号字面量+夹具,穷举在报告 §五);闸=冒烟 755/0+run_all 14/15(唯一红=②批遗留基线红 reward_overlay,对照零新增)+行为探针四件全绿+doc 三闸/熵增闸零新增+存档键六键普查零差异+MC 按 goal 令豁免(静态零漂移证明代);渲染像素五闸屏蔽挂账 07⑮;遗留=实机目检归用户(07⑮)+reward_overlay 基线红归②批处置 |
|
| 迭代 15 | **③秘宝代码词族对齐批(2026-10-05 用户令编入本迭代)**:设计侧已完成(消歧更名批 `430419c`+`06e94b7`,26 个设计文档「魔法道具」→「**秘宝**」,同调位机制名保留不变),本批只做**代码侧对齐**、不做二次设计。内容:`aux_*`/`AuxItem*` 词族全族换词 **358 处 / 36 文件**(重灾 `adventure_runner.gd`79 / `level_runner.gd`27 / `bonus_interact_controller.gd`19 / `battle_orchestrator.gd`18 / `config_db.gd`14 / `main.gd`13),分五组落:**A 玩家可见文案(P0,5 处)**=`battle_narrator.gd:117-118` + `bonus_interact_controller.gd:76` + `hero_info_panel_view.gd:56`(槽位标题 `UiKit.label`)+ `:178`(tooltip)· B schema 与数据表(`aux_item_table.gd`/`aux_items.tres`/`aux_items.gd` 文件名+类名+`ConfigDB` 持有变量与访问器全链跟改,**须同步 Godot `.uid`/`.tscn` + `--import` 重刷类名缓存**——迭代 14.5 实证在册坑)· C 文档链接修复(含 `architecture.md` 两处既有断债顺带清偿)· D 中文注释层(**须分色**:`魔法矢`/`魔法反噬`/`魔法族`/`魔法护甲` 属敌方伤害护甲语汇🔴不动)· E 测试文件名+内部变量。**用户三项拍板**:全族彻底改(含数据表)· **断点存档键一律保留**(`bonus_aux_carry`/`aux_detail`/`aux_cleared`/`aux_cleared_last`/`aux_items` 断点数组/`attunement_charges` 共 34 处引用一字不改,破档即进度丢失)· registry 条目键 `aux_items` 不改(数据契约键)。设计事实源=`design/gdd/systems/排雷循环/秘宝.md` v0.9,任务书=`design/research/任务书-秘宝代码词族对齐批-2026-10-05.md`(T0-T9≈5.0d,**超软盒 3 日上限·知情预挂**:T7 段末闸后 L3 呈报切点不擅切;**三档降级面 G1/G2/G3 在册**〔G3 下限=仅组 A 完成〕;5 条设计契约含 5 条 🔴 停机级〔存档兼容零破坏/数值零漂移/schema 契约面零变更/零字段更名/核心层零 await〕;代拍板白名单 5 项;**C1/C2 已裁关闭·全改保一致·待裁点 0 项**)。**撞批已消解**:② 奇物批 2026-10-05 收口落库(合并 `c571af2`),并行风险解除,本批为迭代 15 唯一在途需求;**T0 须重取基线**(判式换代批 T5 读数 750/0、② 又动数据层,§1.2 的 747 为②前旧数禁沿用)并复核事实表。**✅ 裁决(用户 2026-10-05「全改保一致」)**:C1 `"aux_item"` 域串**照改**(实测连锁面收窄——`reward.tres` 已零 aux_item 数据行,池种随迭代 11 T7 退役;16 处字面量全在代码内闭合无存档依赖;唯一连锁项=`smoke_all.gd:3932` 断言须同步)· C2 `aux_items` 一名两义**拆分**(ConfigDB 持有变量+表注册键✅改 / 断点存档键+冒烟夹具🔴禁改——**「键名禁改」与「变量名禁改」是两回事**,禁改的只是落盘字面量)。新增**组 F**(分派语汇与域串 16 处),任务卡扩为 T0-T10≈5.5d。**开工门**:✅ 已无待裁点,可直接发 T0 | 卡③门:迭代 14 出口(**裁点已全决·可开工**) | 卡①已落籍(原估 0.5d);卡②文档+数据层落籍(`c571af2`,实施段回归迭代 15 在飞——2026-10-05 用户令);**卡③ ✅ 已全批落籍(2026-10-05,worktree m4iter15-mibao 九提交 b4b8bb3..收口笔,主区 merge --no-ff 哈希见台账/active.md 快照行;出口报告=[iter15-3-mibao-exit-report](design/qa/iter15-3-mibao-exit-report-2026-10-05.md))**:词族实数 364 处/36 文件全换尽(例外 43+2 处=存档键引号字面量+夹具,穷举在报告 §五);闸=冒烟 755/0+run_all 14/15(唯一红=②批遗留基线红 reward_overlay,对照零新增)+行为探针四件全绿+doc 三闸/熵增闸零新增+存档键六键普查零差异+MC 按 goal 令豁免(静态零漂移证明代);渲染像素五闸屏蔽挂账 07⑮;遗留=实机目检归用户(07⑮)+reward_overlay 基线红归②批处置 |
|
||||||
| 迭代 16 | **①敌人表行集多行批(2026-10-05 用户令编入本迭代,纯功能与数值轴四卡之一)**:放开敌人表「同一(关卡序号 × 节点深度)行集恰一敌种」加载期校验(enemy_table.gd:108-112 考证在册),允许同场遭遇混编多种敌人——生成(unit_container spawn 行集受理)/编排(battle_orchestrator/narrator)/断言面全链接通 + GDD 结构冻结面修订先行(敌人系统.md:75/:263)。事实源=06「同深度第二敌种与精英/Boss 编码位」条剩余面(前半已由关卡节点地图 v1.0 消解);前置=技能池实施批已落籍(d3fa887)开工门已解。任务书=`design/research/任务书-敌人表行集多行批-2026-10-05.md`(T0-T8≈4.25d 含条件卡 T5 首行投放/3.75d 不含;🔴 契约 5 条含零内容期逐位零漂移;代拍板白名单 3 项;**待用户裁点 4 项**〔P1 混编数量语义三案/P2 行集解析双算法处置〔工作日志 312 既有待裁随批〕/P3 本批是否投首个混编行/P4 Boss 行是否放开〕——裁点裁决后 U0 开工) | 迭代 15 出口 | T0-T8≈4.25d(超软盒预挂,T4 末切分点) |
|
| 迭代 16 | **①敌人表行集多行批(2026-10-05 用户令编入本迭代,纯功能与数值轴四卡之一)**:放开敌人表「同一(关卡序号 × 节点深度)行集恰一敌种」加载期校验(enemy_table.gd:108-112 考证在册),允许同场遭遇混编多种敌人——生成(unit_container spawn 行集受理)/编排(battle_orchestrator/narrator)/断言面全链接通 + GDD 结构冻结面修订先行(敌人系统.md:75/:263)。事实源=06「同深度第二敌种与精英/Boss 编码位」条剩余面(前半已由关卡节点地图 v1.0 消解);前置=技能池实施批已落籍(d3fa887)开工门已解。任务书=`design/research/任务书-敌人表行集多行批-2026-10-05.md`(T0-T8≈4.25d 含条件卡 T5 首行投放/3.75d 不含;🔴 契约 5 条含零内容期逐位零漂移;代拍板白名单 3 项;**待用户裁点 4 项**〔P1 混编数量语义三案/P2 行集解析双算法处置〔工作日志 312 既有待裁随批〕/P3 本批是否投首个混编行/P4 Boss 行是否放开〕——裁点裁决后 U0 开工) | 迭代 15 出口 | T0-T8≈4.25d(超软盒预挂,T4 末切分点) |
|
||||||
| 迭代 16 | **②M4 数值批(2026-10-05 用户令编入本迭代)**:四件数值欠账合一批——①削弱档位终值(weaken 现值 −65/duration 2/refresh 考证在册;叠层改续时语义已定案,差 duration 终值+当量锚复算)②调参批数值点评(06 条目现行登记五条——任务口径「八条」与条目五条不符,任务书 §2.2 已呈报差异以条目为准)③迭代 10 奖励重构联动欠账①②(钥石产出重标 boss_keystone 首杀 3/3/3·复杀 2/2/3 provisional + 通关率带重扫〔通关回血 20%+AP 件首轮 +1〕)④元进度定价剩余面(forge base 20/比 150 考证在册——06 条目文本「base 10」滞后于 2026-09-22 对齿批已呈报;Boss 两档终值)。读数义务随批=迭代 14 两件后置复算(T6 MC 四带复算+T7 技能池节奏重测,均挂 07);顺路修复 damage_probe A1 改前既有红灯(判式换代批出口报告 §四.7 在册)。**硬约束:一切 MC/verifier 跑批发起前须用户批准;调参联动禁独立调参网络(β 带 [1.2,2.5] 逐 u 检算)**。任务书=`design/research/任务书-M4数值批-2026-10-05.md`(T0-T8≈6.0d,天然切分点 T4 末;🔴 契约 5 条含跑批批准制;**待用户裁点 4 项**〔P1 削弱终值三案推算后裁/P2 点评不对称裁决方向/P3 钥石产出终值/P4 工坊直购价立否〕) | 迭代 15 出口 | T0-T8≈6.0d(超软盒预挂,段一 T0-T4≈4.25d/段二 T5-T8≈2.0d) |
|
| 迭代 16 | **②M4 数值批(2026-10-05 用户令编入本迭代)**:四件数值欠账合一批——①削弱档位终值(weaken 现值 −65/duration 2/refresh 考证在册;叠层改续时语义已定案,差 duration 终值+当量锚复算)②调参批数值点评(06 条目现行登记五条——任务口径「八条」与条目五条不符,任务书 §2.2 已呈报差异以条目为准)③迭代 10 奖励重构联动欠账①②(钥石产出重标 boss_keystone 首杀 3/3/3·复杀 2/2/3 provisional + 通关率带重扫〔通关回血 20%+AP 件首轮 +1〕)④元进度定价剩余面(forge base 20/比 150 考证在册——06 条目文本「base 10」滞后于 2026-09-22 对齿批已呈报;Boss 两档终值)。读数义务随批=迭代 14 两件后置复算(T6 MC 四带复算+T7 技能池节奏重测,均挂 07);顺路修复 damage_probe A1 改前既有红灯(判式换代批出口报告 §四.7 在册)。**硬约束:一切 MC/verifier 跑批发起前须用户批准;调参联动禁独立调参网络(β 带 [1.2,2.5] 逐 u 检算)**。任务书=`design/research/任务书-M4数值批-2026-10-05.md`(T0-T8≈6.0d,天然切分点 T4 末;🔴 契约 5 条含跑批批准制;**待用户裁点 4 项**〔P1 削弱终值三案推算后裁/P2 点评不对称裁决方向/P3 钥石产出终值/P4 工坊直购价立否〕) | 迭代 15 出口 | T0-T8≈6.0d(超软盒预挂,段一 T0-T4≈4.25d/段二 T5-T8≈2.0d) |
|
||||||
|
|||||||
Reference in new issue
Block a user