merge: 迭代21天赋树实装批——12节点换代+六键接线+营地四柜台+旧柜台退役(T0-T7 全绿;实现报告=design/qa/iter21-实现报告-2026-10-12.md;残留人闸=实机目检四柜台)
This commit is contained in:
commit
2b142d1245
40 files changed
+1046
-1214
No files matched your search
@@ -22,22 +22,21 @@
|
||||
- **代拍板追认单十条已追认(2026-10-12 用户逐裁,06 在册)**:全数通过+连带裁定(血税保留/疼痛记忆「下场战斗」系笔误改「下一次攻击」/血税 desc 补生效时点修正案/流派=对外不显示对内保留事件族划分);**V20/V21/V22 人工核对半+实机目检五件仍待用户**(07 在册;V23 配额数字已随②③追认闭环)。
|
||||
- **GDD 反哺点①-④全部落文(2026-10-12 用户逐裁令)**:①奇物.md v3.16+血税 desc fe0970d(冒烟 864/0);②冒险结构.md v0.20(关卡大奖励=专长三选一按档配比加权+回血,提升池机制段移交奇物.md);③难度曲线与Boss.md v0.28(投放读数只测不判,数值不动);④02-系统清单.md v1.24(奇物行换代);⑤归奇物呈现条(06 在册)。
|
||||
- **回补带出四条待裁点**(秘宝卡两条+天赋树卡两条不阻塞;专长卡 §10.4 已随追认闭环)。
|
||||
- **现行文档事实源**:秘宝.md v2.9+**奇物.md v3.16**+天赋树.md v0.8+**锻造工坊.md v0.5**(17 裁点清零定案,2026-10-12)+术语表 v1.8;迭代粒度规矩=迭代周期 ≈ 一周左右。
|
||||
- **现行文档事实源**:秘宝.md v2.9+奇物.md v3.16+**天赋树.md v0.8(迭代 21 已按此实装入库)**+锻造工坊.md v0.5+术语表 v1.8;迭代粒度规矩=迭代周期 ≈ 一周左右。
|
||||
|
||||
## 项目当前位置(2026-10-12 刷新;实时细节以根目录 `roadmap.md` 与 06-待办清单 为准)
|
||||
- **里程碑**:M3=Alpha 版本的完整实现(2026-10-09 立规:用户未明令宣布 M3 完成前停留 M3,迭代只排 M3 需求,M4 一律不排期)
|
||||
- **迭代**:迭代 19 五卡已入库(2026-10-08);迭代 20 专长实装批已入库(2026-10-11 总收口,反哺①-④已落文);**迭代 21 天赋树实装批已 U0 开工在飞(2026-10-12,worktree g-plan2-iter21-tree,T1 起)**;迭代 22=秘宝实装批已编入待 U0 发令;M4=迭代 23+ 未排期
|
||||
- **近期主战场**:迭代 21 天赋树实装批在飞(T1 起);迭代 22 待 U0 发令;迭代 20 残留人闸=实机目检五件+V20/V21/V22 人工核对半;文档治理连续批详情见 git 提交史
|
||||
- **迭代**:迭代 19 五卡已入库(2026-10-08);迭代 20 专长实装批已入库(2026-10-11 总收口,反哺①-④已落文);**迭代 21 天赋树实装批已入库(2026-10-12,goal 会话 U0 开工总收口)**;迭代 22=秘宝实装批已编入待 U0 发令;M4=迭代 23+ 未排期
|
||||
- **近期主战场**:**迭代 21 残留人闸(实机目检营地四柜台〔V22〕+实现报告反哺点六条/追认单十二条待用户)**;迭代 22 待 U0 发令;迭代 20 残留人闸=实机目检五件+V20/V21/V22 人工核对半;文档治理连续批详情见 git 提交史
|
||||
|
||||
## 未决面(进行中/待人闸/待裁点指针;现行事实源登记处=`design/gdd/06-待办清单.md`)
|
||||
- 待用户裁决:美术审计两项 P0(art-bible 死样张指涉+主角像素管线重产;06 在册);去毒残留词扫否(魔法道具约 30 处/罕见约 18 处,呈报在案);喂雷流 R5 甲重启条件回落文(一句话件);「通关 vs 通过第 N 关」全库口径统一 273 处(待裁是否补登记 06);迭代 20 实机目检五件+V20/V21/V22 人工核对半(07 在册)
|
||||
- 待发令:迭代 22 秘宝实装批开工(U0);锻造工坊实装批(17 裁点清零、06 在册,与天赋树批排期合并评估)
|
||||
- 进行中:迭代 21 天赋树实装批在飞(worktree g-plan2-iter21-tree,T1 起);黑话清扫第二波(GDD 正文流程动词族约三千处,随时可派)
|
||||
- 进行中:黑话清扫第二波(GDD 正文流程动词族约三千处,随时可派)
|
||||
|
||||
## 状态记录(滚动窗口:仅当前迭代;退役规则见 doc-conventions §10)
|
||||
|
||||
- **469(2026-10-12,迭代 21 天赋树实装批开工)**:/goal+iter 技能调用=U0 开工令。开工检查全过:卡片 v1.7(73acee0 已入库)零阻塞待裁点——R1 按卡片既定默认=裁撤案执行(schema 只保留点亮前置),出口报告追认单列明;验收清单 V1-V23 三要素齐;自洽扫描 8 处事实抽查全符(33 行表/schema 词表/tree_buy 族/slots 谓词求值/存档 v6/_roll_gp_drop 挂点/:1116-1131 标错分支/结算=每轮末时序)。T0 四查结案:②聚乘算位=_roll_gp_drop() 返回表达式内(battle_orchestrator.gd:1461);③锋=mine_hit 现成挂点零改、守=_flag_miss_seen 去重闸后(:1119-1121)grant_ap mode:queue 下轮 notify_round_start 兑现,末轮判出无下一轮=契约 12 作废态自动成立;④锻造工坊 v0.5 两节读取完成——本批预留 0 锻造加值位与「4a 前缓后陡/6 满级特效留扩展位」零冲突、钥匙 free 无前置一致;⑤基线复读=冒烟 864/0 但 run_smoke 判 FAIL(SCRIPT ERROR 51 行:_flush_pending_reveals 三处 _emit_row("fx_reveal","",...) 空 source_class 触发 :1592 行来源断言——a57489e9 迭代20 T1 落地件存量红,两轮确定性复现,本批内顺手修复+实现报告单列)+run_all 21 套全绿+probe_gate --click 全绿残留 0。定义面已入库(并行会话 73acee0 含 roadmap/06/卡片 v1.7)。主区残留脏件全属他批遗产零碰(.zcode hook 三件/锻造工坊.md v0.5 未提交/playnote/四 untracked)。worktree g-plan2-iter21-tree 开工,T1 起。
|
||||
|
||||
- **470(2026-10-12,filter-repo 计划书裁决——明示放弃销号批)**:用户裁「.git 60MB 可接受,没必要清理」→ 三选裁**明示放弃**。06 条目移除(去向=本条,git 史留痕);active.md 未决面摘除该裁决项。计划书 `.zcode/plans/filter-repo-history-cleanup-plan-2026-10-02.md` 系 .zcode 未跟踪件、随裁作废但磁盘留存不删(永久删除不可逆;真要重启历史清理须重立计划)。零代码零他件触碰(worktree frdrop-20261012)。
|
||||
|
||||
- **471(2026-10-12,8a 连带 C/D 改名裁决 + 独立改名小批登记)**:用户最终命名——**C =「属性 + 祝福」**(局内商店货架三行 攻击锻升/生命锻升/护甲锻升 → **攻击祝福/生命祝福/护甲祝福**;后缀由用户自 D&D 调研候选选定〔Blessing〕,并裁定"参考书目有该词不构成禁用";调研要点:D&D 超自然赠礼分 Blessing 祝福〔永久·神赐〕/ Charm 护咒〔有时限·用完消失〕,按语义后者更贴局内临时层,用户仍取祝福);**D = 棋盘格 defense 正向行 + `ac_up` 状态的同名「护甲强化」→「盾牌加护」**(用户提案,理由=它是临时叠层状态、不是护甲本体;附带约定「加护=AC 判定层,护盾=保险丝那种吸收层,两者不得混用」)。机制分类名「属性值提升」保留不动,只换玩家向显示名。**执行面已核到行号**:C=`shop.tres` 六行(pool_shelf 现役 70/74/78 + products 遗留 38/45/52)——全库"锻升"命中恰此六行、`display_name` 非查找键(货架以 kind_id 为键,`config_db.gd:328`)、`smoke_all.gd:4415` 为"标签文本==表值"自洽式断言 ⇒ **C 零代码零测试连带**;**D 必须连带改 `smoke_all.gd:1199` 一条硬编码 `== "护甲强化"` 断言,否则冒烟红**(修正上一轮"C/D 皆零连带"的判断)。**按本会话"禁动 game/ 代码与数据表"纪律未代执行**——06 新增「玩家向显示名改名小批」条目,内附逐行替换表 + 近义词占用登记(石肤已被奇物占用)。落文:锻造工坊.md(边界节 + v0.5 版本行⑥)、06 新条目;启动=用户发令或并入锻造工坊实装批。附:本会话早先写入的 468/469 两条已被并行会话的"状态记录清理批"清档(完结条目清理,符合 §10),本条为补登。
|
||||
@@ -51,3 +50,5 @@
|
||||
- **474(2026-10-12,C/D 改名小批执行+合入+残留肃清——worktree 两连批)**:用户追加授权("可以简单地修改(smoke_all.gd 那一行断言),一并改完吧。但是注意我们的合入规则")后,C/D 同批走 worktree 执行。**主批**(分支 rename-blessing-20261012,基于 master 18b6cbd):`shop.tres` 六行 display_name 锻升→祝福(pool_shelf 现役 70/74/78 + products 遗留 38/45/52);`reward.tres:22` / `status_effects.tres:18` 护甲强化→盾牌加护;`smoke_all.gd:1199` 硬编码断言同步(不改则冒烟红)。冒烟 864 项检查 0 项失败(FAIL 仅存量 fx_reveal SCRIPT ERROR——a57489e9 遗留、iter21 在册,与本批无关;已核 `_smoke_ac_swap_guard()` 调用先于 `_smoke_save()`,存量错误不吞本批断言)。`--no-ff` 合入 master `0c93a71`(ort 零冲突;主区脏件全为文档侧,与分支 game/ 四文件零重叠)。**残留肃清批**(分支 blessing-residue-sweep-20261012,基于合入后 master):合入后全库复扫发现主改名未覆盖注记段 4 处——`reward.tres:219` / `status_effects.tres:99` 历史注记、`defense_scan.gd:24/:35` 打印标题与注释,更新为现名「盾牌加护」并括注"2026-10-12 由'护甲强化'改名"(历史动作部分保留);冒烟 864/0 → 合入 `0144f60`。**game/ 全库现役旧名归零**(仅存两处为有意留痕括注)。worktree 与三分支已清理(`git worktree remove --force` + `branch -d/-D`)。**文档跟随**:地图战斗资源.md v0.23 版本行(九型字典表+设计意图句)、art-bible.md §4.6.1/§4.6.2 键名列(契约句"随其修订自动跟随")、07 体验验证清单⑫目检行。**06 条目收口**:改名小批状态「已完成」,registry entities.yaml 登记「祝福」条目/旧名退役**未执行**——归锻造工坊实装批术语登记面(迁移面八项⑦同域)。**编号注记:473 已被并行会话(迭代 20 报告失真勘误批)占用,本条顺延 474;472 撞号(并行会话同写)已在彼条加接续指针。**
|
||||
|
||||
- **475(2026-10-12,奇物呈现修复批——僵尸呈现整改落地,呈现改法=甲逐件行)**:用户令「解决呈现问题」,按呈报推荐案甲落地(改乙/丙一句话另调)。代码两文件:`hud_presenter.gd` feat_holdings_lines 聚合行(新 29 行无 archetype 下空键兜底「流派 ×N · 档一」僵尸文案)→ 逐件行「件名 ×N」;`battle_narrator.gd` feat_card_text 摘「件名 · 流派」族名行(卡片=件名+效果摘要两行,兑付反馈行随之「件名 · 摘要」)+payload 死键 archetype 摘除;注释面五处换代(hud_presenter/hero_info_panel_view/level_map_screen)。断言面预查:smoke:982 只钉 desc 零族名断言,免改。文档同步:battle.md C 区两行(归 06 指针摘除改已整改事实)/07 HUD 条(验修复后逐件行)/报告 §五.5(同)/README 段(473 勘误句再换代为已修复)/06 奇物呈现条**收口移除**(三处违规+档位三路+呈现改法三面全清,去向=本条+git)/反哺点⑤✅销账(①-⑤全出清;反哺点条与追认单条随下次收口清理批一并移除)。FeatsEngine.tiers_for 呈现侧调用摘除后全仓零调用方(引擎库保留不动)。(worktree presentfix-20261012;**编号注记:474 已被并行改名批占用,本条顺延 475**)
|
||||
|
||||
- **476(2026-10-12,迭代 21 天赋树实装批总收口〔编号顺延——470-475 系并行批次占用,471/472 并行撞号已在彼条注记〕)**:T1-T7 全量交付(worktree g-plan2-iter21-tree 基于开工批 43f7230;master 并行十八笔已并入分支解 active 冲突后回合)。T1 schema 换代(FORK_GROUPS 4→3/RULE_KEYS 2→6/fork 单行双态 effects_a·b/row 退役门槛换轴/R1 裁撤案执行)+33→12 行重排(easter 摘攻防=契约 9);T2 购买面(easter 拒购/Fork 解锁默认 A/零币切态/彩蛋特化判定);T3 六键接线(EVENTS+flag_missed/FACTS+battle_index=L2 扩签两处/posture 每关首轮窗/sense 限次 counter 闸 domain:adventure 按关重置/守侧去重闸后发射=契约12/聚入账前×1.5/散双计价点×80%——数值全 provisional 归 M4);T4 注入换代(树淬炼 flat+锻造加值位预留 0/槽位判定源切树点亮数/ks_refresh 同账路由);T5 删除批(migrate_tree/buy_forge 家族/forge_products 行族/forge_levels 域/attunement_growth 谓词表/生涯三投影全删;tree_struct=2 标记废弃重置禁迁移);T6 四柜台并排(树=主界面/两钥匙门+零交互占位面板/图鉴常开/暂停层锻造段连带删)。存量红修复=fx_reveal 三处空 source_class(a57489e9 落地件,基线 51 行脚本错误清零——474 条 FAIL 注记随之销账)。验证:冒烟 886/0(V1-V10/V16 断言随批)+run_all 22 套(新增 V11 六键闭集单测)+probe --click --full 八探针全绿+render_gate golden 0.00%+doc 三闸/熵增闸对基线零新增(worktree 缺 ignored 素材两坑照 463 先例拷 artwork+game/assets/art 后绿)+V19 MC 三组同种子配对×60 局 perfect+max 档全部 61.7%、配对差 0.0pp 在带(normal 档基率地板读数换档记验证记录);守侧五场景=编排 rig 直注+引擎直构双承载。追认单十二条+反哺点六条+欠账三条落实现报告 design/qa/iter21-实现报告-2026-10-12.md 待用户;残留人闸=实机目检四柜台(V22)。
|
||||
@@ -110,17 +110,18 @@
|
||||
|
||||
| 模块 | Owns | Exposes | Consumes | 引擎 API |
|
||||
|---|---|---|---|---|
|
||||
| **battle_orchestrator**(B1) | 段序骨架(七段结构冻结)、轮 facts 缓冲、可回放步骤流、[明细行协议](../../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)(15 类行 + 字段分层)、条款③⑤闭合对账、**战斗域全部跨系统调用权** | `begin_battle(encounter)`;窗口/刷新意图提交面(扮演 D1);`level_clear()`;步骤流(输出契约) | B2/B3/B4 全调用面(**唯一调用方**);A3 request_refresh 代理 + 回发;A2 install/retire + 三事件订阅;A1 generate(attempt 非 scope 事件);D2 SettlementTable + set_reload_gate | 无(零随机、禁 await——切片 88 冒烟实证可回放) |
|
||||
| **battle_orchestrator**(B1) | 段序骨架(七段结构冻结)、轮 facts 缓冲、可回放步骤流、[明细行协议](../../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)(15 类行 + 字段分层)、条款③⑤闭合对账、**战斗域全部跨系统调用权**、效果引擎宿主(每战 rebuild/战末 adventure 计数器导出回写)+ 树规则挂点施加面(posture 窗/sense 限次/守侧 flag_missed 发射/聚乘算入账前) | `begin_battle(encounter)`;窗口/刷新意图提交面(扮演 D1);`level_clear()`;步骤流(输出契约) | B2/B3/B4 全调用面(**唯一调用方**);A3 request_refresh 代理 + 回发;A2 install/retire + 三事件订阅;A1 generate(attempt 非 scope 事件);D2 SettlementTable + set_reload_gate | 无(零随机、禁 await——切片 88 冒烟实证可回放) |
|
||||
| **effect_engine**(Core——内容数据驱动扩展批 iter17 建域) | 事件/事实/效果类型/算子四词表封闭(迭代 21 T3 扩签:EVENTS 18→19 +flag_missed 标错判定、FACTS +battle_index 关卡域战斗序)+ 树规则合成订阅六键(posture 每关首轮窗/sense 两组限次 counter 闸·domain=adventure 跨战斗持久) | rebuild(每战重建)/dispatch/validate_trigger_row/counters_export·counters_seed | ConfigDB(feats/treasure 行集) | 无(零随机全确定) |
|
||||
| **level_runner**(C1-lite,切片已建) | 关卡域生命周期(节点链推进、共享 B4 + HP 携带、关卡末 level_clear) | 节点推进接口;LevelTable(`level.tres`——节点 enum {battle,reward,rest,boss}、末节点必须 Boss、ConfigDB 跨表校验⑧) | B1 begin_battle、B4 grant_node_active、ConfigDB LevelTable | 无 |
|
||||
| **run_structure**(C2) | 冒险/关卡序列、失败重开、**master_seed 唯一接线方** | (未设计) | RngService.set_master_seed、C3/D2 | 无 |
|
||||
| **difficulty**(C3) | DifficultyTable 回填、全部曲线数值调参所有权、战斗期望表(切片 monte_carlo 为工具原型) | 调参输出(经 D2 R1 解析/编排器传递,禁反向调用生成) | ConfigDB、B1 对账输出(调参约束清单)、A1 报告 C | 无 |
|
||||
| **meta_progression**(C4,Alpha) | MetaTable、钥石/天赋 | (未设计) | D3、C2、ConfigDB | 无 |
|
||||
| **meta_progression**(C4,Alpha) | MetaTable、钥石账本、天赋树域(MetaTreeTable 12 节点 v0.8——Fork 一节点双态/row 字段退役/RULE_KEYS 六键/彩蛋特化觉醒,迭代 21 T1 换代)+ 同调位槽位判定(树点亮数单源——T4 槽位收编) | tree_can_buy/tree_buy/tree_buy_fork/tree_toggle_fork(写口唯一)、hero_override 注入(树淬炼 flat+锻造路线加值位预留 0)、tree_rule_keys | D3、C2、ConfigDB | 无 |
|
||||
|
||||
### Presentation 层
|
||||
|
||||
| 模块 | Owns | Exposes | Consumes | 引擎 API |
|
||||
|---|---|---|---|---|
|
||||
| **D1 HUD**(切片 lite 已拆 9 单元:BoardView / HUDView / HudPresenter / BattleLogView / ItemWindowOverlay / NodeOverlay / LevelStripView / TunePanel / UiKit + 组合根 main.gd) | 全部 Control/CanvasLayer(例外:BoardView 棋盘本体与 BattleStageView 站桩本体 = Control 壳 + SubViewportContainer + SubViewport + Node2D 世界〔BoardWorld/CellUnit;StageWorld/StandeeBody——B 区 slot_row 伴随件留壳层〕,探针契约面不变——ADR-0006 棋盘 Node2D 迁移批 Phase A 2026-09-23 / 战斗舞台 Phase B 2026-09-26)、输入→意图翻译(**唯一提交路径 = settle_batch,不直调 A3**)、呈现义务台账(A2×6 / B2×4 / B3×4 / B4×5 / B1×3) | 视图单元接口(信号外发、setter 只读注入) | A2 compute_move_path / ActionOutcome;A3 ap/rap;B1 步骤流行协议(数据源);ConfigDB display_name | **MEDIUM**:Control / CanvasLayer / draw_* / InputEvent / RichTextLabel——对照 breaking-changes.md 4.5–4.7 条目(CanvasItem 线条无抗锯齿羽化、设备 ID 常量、RichTextLabel 图片参数两轮变化) |
|
||||
| **D1 HUD**(切片 lite 已拆 9 单元:BoardView / HUDView / HudPresenter / BattleLogView / ItemWindowOverlay / NodeOverlay / LevelStripView / TunePanel / UiKit + 组合根 main.gd;营地覆盖层=四柜台并排〔树淬炼主界面/锻造工坊·秘宝匣钥匙门/图鉴常开——迭代 21 T6 程序占位重排〕) | 全部 Control/CanvasLayer(例外:BoardView 棋盘本体与 BattleStageView 站桩本体 = Control 壳 + SubViewportContainer + SubViewport + Node2D 世界〔BoardWorld/CellUnit;StageWorld/StandeeBody——B 区 slot_row 伴随件留壳层〕,探针契约面不变——ADR-0006 棋盘 Node2D 迁移批 Phase A 2026-09-23 / 战斗舞台 Phase B 2026-09-26)、输入→意图翻译(**唯一提交路径 = settle_batch,不直调 A3**)、呈现义务台账(A2×6 / B2×4 / B3×4 / B4×5 / B1×3) | 视图单元接口(信号外发、setter 只读注入) | A2 compute_move_path / ActionOutcome;A3 ap/rap;B1 步骤流行协议(数据源);ConfigDB display_name | **MEDIUM**:Control / CanvasLayer / draw_* / InputEvent / RichTextLabel——对照 breaking-changes.md 4.5–4.7 条目(CanvasItem 线条无抗锯齿羽化、设备 ID 常量、RichTextLabel 图片参数两轮变化) |
|
||||
| **battle_presentation**(B5,垂直切片后) | 结算动画 / 伤害数字 / 战斗舞台 | (未设计——前置"战斗表现美术测试"锁双舞台陷阱) | B1 步骤流(回放脚本源)、D1 | **MEDIUM**:AnimationPlayer(String→StringName 变更)/ Tween |
|
||||
| **audio**(D4,Alpha) | 音频挂点 | (挂点 = B1 明细行类型流;负反馈聚合——单结算至多一次) | B1 流水 | AudioStreamPlayer(4.7 area_mask 默认 1→0 注意) |
|
||||
| **tutorial**(D5,Alpha) | 教学条目 | (口径来源:A2/A3/B3/B1 各教学条目) | 各上游 | 无 |
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
|
||||
**归属**:[天赋树](systems/跨局循环/天赋树.md) v0.8 实装(树结构换代+营地统一;跨 [锻造工坊](systems/跨局循环/锻造工坊.md)(工作间=其柜台改造对象,攻防数值唯一实装面在彼批)/ [元进度](systems/跨局循环/元进度.md)(注入通道与柜台退役面)/ [秘宝](systems/跨局循环/秘宝.md)(同调位求值与秘宝匣工作间)/ 数值批(命中值域与全部 provisional 数值))
|
||||
|
||||
**状态**:待办(**2026-10-11 用户澄清:2026-10-09「先不要动」系暂缓口径、非全面冻结令——设计完成的批次即可做,本批不再以冻结为前置**;开工门=用户发令;**2026-10-12 完备性/跨系统歧义审查后:卡片侧零阻塞待裁点,只差 U0 发令**)。**需求卡片 v1.7(2026-10-12 完备性审查十一裁)**——在 v1.4→v1.6 三轮换代基础上新增裁决:①账号层攻/AC 空窗期**知情接受**(Alpha 开发阶段,不设过渡、不登记欠账);②**守侧=失误补偿**(契约 12:触发主语=当回合标错动作非在场标错旗;单格最多一次;末轮不返);③折扣(散)**只覆盖商品购买价格、不含刷新**,键名 `gp_cost_mult`(`cost_mult` 已被 `yield_mult` 占用),聚的乘算落**入账前**;④限次承载=`adventure_counters`+`domain:"adventure"`+关卡切换点清零(**不升 v7**);⑤**开发阶段无视任何文档层条款**,不为本批增设豁免/例外条款;⑥**生涯页三投影整行删**(`forge_level`/`workshop_status`/`tree_nodes`——生涯不统计天赋树与账号成长态);⑦三条登记欠账、本批不做=AP 上限 clamp / 三选一刷新软上限(另立待办)/ Y 不入断点;⑧§5 之外另查到的 42 处旧表述**不做清扫**(不做加法)。**需求卡片 v1.4→v1.6 连带换代(2026-10-11 攻防所有权切分+一线一节点+Fork一节点双态三轮)**:design/research/需求卡片-天赋树实装批-2026-10-11.md——结构目标 33→12 全卡换代(脊柱=生命/命中一线一节点×4 级、Fork=三节点双态〔购买解锁默认 A 态、零币切换、行持 a/b 双效果键〕)、锻造工坊钥匙前置改无、地脉共鸣条件效果换代、注入公式换代、营地呈现措辞对齐四柜台、行门槛平移节点等级(用户驳「合并破坏统计/门槛」之疑——节点等级可区分可计数,门槛主语=第 N 级);T 估时复核 ≈7.0-8.0d(T1 下修,其余不动);**开放待裁点 R1=schema「等级阈值前置」新形态去留(钥匙改无前置后现无消费者,推荐裁撤)**——裁决前不阻开工门(默认按裁撤案排程,用户推翻即先改文档);2026-10-11 用户令整卡编入迭代 21(不切分)不变。**历史落卡记录(v1.1→v1.3)**:grilling 五裁全落(Q1 刷新升级双入口并存·秘宝侧重复后续审查轮用户自办/Q2 槽位数量树说了算+旧条件作废删除随批清扫/Q3 开发阶段无迁移·旧存档废弃重置·迁移需求整体删除/Q4 本批程序占位重排/Q5 转排期规则默认先行);歧义扫描六裁(/grilling)已落 GDD v0.4:术语回归「危险格/安全格」/彩蛋三线/行门槛含全部节点/聚仅战斗掉落+散全消费面/限次生命周期/三处旧裁决换代(彩蛋三线后随 v0.6 攻防所有权切分换代为两线);架构面结论=现有分层承载得住零重构,三处小扩〔schema 阈值前置形态(去留=R1 待裁)/效果引擎关卡域状态/营地呈现重排〕随批
|
||||
**状态**:✅ **已入库(2026-10-12,/goal+iter U0 开工——worktree g-plan2-iter21-tree 全量交付;T1-T6 代码批+T7 文档同步批,批末重闸全绿;实现报告=design/qa/iter21-实现报告-2026-10-12.md)**。R1 按卡片既定默认=裁撤案执行(schema 只保留点亮前置,未增设 requires_level),出口报告追认单列明。**登记欠账三条随批入册**:①AP 上限 clamp 缺位(树+AP 件+时沙同走 _pending_ap_delta 无上限——新叠加源使既有缺陷可观测化);②三选一刷新软上限(停点内倍率增长×停点间基础价随进度递增,另立待办);③Y(_attune_refresh_y)不入断点存档漏洞(用完退菜单再续玩满血复活——V15 仅验账本一致性,刷新链路可用性不受本批影响)。**需求卡片 v1.7(2026-10-12 完备性审查十一裁)**——在 v1.4→v1.6 三轮换代基础上新增裁决:①账号层攻/AC 空窗期**知情接受**(Alpha 开发阶段,不设过渡、不登记欠账);②**守侧=失误补偿**(契约 12:触发主语=当回合标错动作非在场标错旗;单格最多一次;末轮不返);③折扣(散)**只覆盖商品购买价格、不含刷新**,键名 `gp_cost_mult`(`cost_mult` 已被 `yield_mult` 占用),聚的乘算落**入账前**;④限次承载=`adventure_counters`+`domain:"adventure"`+关卡切换点清零(**不升 v7**);⑤**开发阶段无视任何文档层条款**,不为本批增设豁免/例外条款;⑥**生涯页三投影整行删**(`forge_level`/`workshop_status`/`tree_nodes`——生涯不统计天赋树与账号成长态);⑦三条登记欠账、本批不做=AP 上限 clamp / 三选一刷新软上限(另立待办)/ Y 不入断点;⑧§5 之外另查到的 42 处旧表述**不做清扫**(不做加法)。**需求卡片 v1.4→v1.6 连带换代(2026-10-11 攻防所有权切分+一线一节点+Fork一节点双态三轮)**:design/research/需求卡片-天赋树实装批-2026-10-11.md——结构目标 33→12 全卡换代(脊柱=生命/命中一线一节点×4 级、Fork=三节点双态〔购买解锁默认 A 态、零币切换、行持 a/b 双效果键〕)、锻造工坊钥匙前置改无、地脉共鸣条件效果换代、注入公式换代、营地呈现措辞对齐四柜台、行门槛平移节点等级(用户驳「合并破坏统计/门槛」之疑——节点等级可区分可计数,门槛主语=第 N 级);T 估时复核 ≈7.0-8.0d(T1 下修,其余不动);**开放待裁点 R1=schema「等级阈值前置」新形态去留(钥匙改无前置后现无消费者,推荐裁撤)**——裁决前不阻开工门(默认按裁撤案排程,用户推翻即先改文档);2026-10-11 用户令整卡编入迭代 21(不切分)不变。**历史落卡记录(v1.1→v1.3)**:grilling 五裁全落(Q1 刷新升级双入口并存·秘宝侧重复后续审查轮用户自办/Q2 槽位数量树说了算+旧条件作废删除随批清扫/Q3 开发阶段无迁移·旧存档废弃重置·迁移需求整体删除/Q4 本批程序占位重排/Q5 转排期规则默认先行);歧义扫描六裁(/grilling)已落 GDD v0.4:术语回归「危险格/安全格」/彩蛋三线/行门槛含全部节点/聚仅战斗掉落+散全消费面/限次生命周期/三处旧裁决换代(彩蛋三线后随 v0.6 攻防所有权切分换代为两线);架构面结论=现有分层承载得住零重构,三处小扩〔schema 阈值前置形态(去留=R1 待裁)/效果引擎关卡域状态/营地呈现重排〕随批
|
||||
|
||||
#### 秘宝体系实现重构落地批 — 已编入迭代 22(2026-10-11 用户令)(2026-10-10 用户令「文档层已改完,记录新设计的实现重构落地」登记)
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
### ⑲ 生涯页目检(迭代 18 卡④生涯界面实装批·期一随批登记——主菜单新增一级只读页+存档 v6)
|
||||
|
||||
- **入口与开合**:主菜单「营地与系统」组出现「生涯」钮(营地钮与设置钮之间);点击开全屏页、点「返回」回主菜单(继续上局钮可见性随开合正确恢复)。
|
||||
- **页呈现六类九项**:组一·冒险战绩(冒险总局数 / 通关局数〔括注「三关全通」〕 / 各关通关次数〔一行三关,关名带层号〕 / Boss 首杀〔X/3 关〕 / Boss 总击杀);组二·资源与成长(钥石余额徽 / 属性锻造已购合计 / 秘宝匣已解锁 X/6 件 / 天赋树已购概览)——数值与存档实况一致(对照:玩几局后总局数含放弃与失败、三关全通才计通关)。
|
||||
- **页呈现两类六项**:组一·冒险战绩(冒险总局数 / 通关局数〔括注「三关全通」〕 / 各关通关次数〔一行三关,关名带层号〕 / Boss 首杀〔X/3 关〕 / Boss 总击杀);组二·资源(钥石余额徽)——数值与存档实况一致(对照:玩几局后总局数含放弃与失败、三关全通才计通关)。〔原「属性锻造已购合计」「天赋树已购概览」两观测项随生涯页三投影删除摘除——2026-10-12 用户裁⑧,天赋树实装批随批〕
|
||||
- **旧档载入**:v5 现役档(升级前存档)载入后生涯页正常显示,总局数/通关局数从 0 起计(终身计数域 v6 新增,缺省补零),钥石等既有进度原样。
|
||||
- **零跳转与局内零入口**:页内除「返回」外无任何前往他处的按钮;冒险进行中(战斗屏/关卡地图屏/暂停层)无生涯入口。
|
||||
- **对比度与零动效**:页文字亮墨族在深棕底上可读(机检 3 对已入 ui_contrast 闸);开页/关页无过渡动画。
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
| 各关通关次数 | `clear_count(level_index)`(`save_manager.gd:238`) | 现成 |
|
||||
| Boss 首杀与总击杀 | `boss_first_kill_marked`(`save_manager.gd:242`)/ `boss_kill_count`(`:250`) | 现成 |
|
||||
| 钥石余额 | `keystone_balance`(`save_manager.gd:202`) | 现成。累计收支流水不在存(加钥石与花钥石只动余额,`:206`/`:211`)——如要做属新增流水域,期一实装批另呈报,本文只登记 |
|
||||
| 三柜台解锁完成度概览 | 锻造 `forge_level`(`save_manager.gd:218`)/ 工坊 `workshop_status`(`meta_progression.gd:90`)/ 天赋树 `tree_nodes`(`save_manager.gd:256`) | 现成只读投影 |
|
||||
| ~~三柜台解锁完成度概览~~ | (整行删——2026-10-12 用户裁:生涯页不统计天赋树与账号成长态,只统计终身计数;数据源 forge_level/workshop_status/tree_nodes 三投影随天赋树实装批退役,07⑲两观测项同步摘除) | — |
|
||||
|
||||
### 历史冒险回看(期二)
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
- **敌人图鉴(优先,2026-10-06 用户裁 P3 甲)**:直接承接 06 在册「敌方技能图鉴 / 学习呈现」条(P2——见过 → 认得 → 记谱;归属 [HUD 框架](../表现与支撑/HUD框架.md) 与 [敌人系统](../战斗循环/敌人系统.md))。「见过」记账面 = 新增 meta 域,涉存档 schema 迁移(与期一终身计数域同属 schema 义务面;版本号按实装时点的现行版本顺延)。
|
||||
- **秘宝 / 奇物图鉴(随后)**:无在册条目,立项时另行登记。
|
||||
- **奖励格图鉴:只登记不激活**(P3 甲)。营地柜台三「奖励格图鉴」现况 = 柜台框在(`camp_overlay_view.gd:121`)、下段由天赋树行占用(`:128` 注记),图鉴本体空置;该柜台已随配比预设批整体挂起冻结(2026-10-05 用户令,06 在册),重启须用户另行发令。
|
||||
- **奖励格图鉴:只登记不激活**(P3 甲)。营地「奖励格图鉴」柜台现况 = 常开独立位(迭代 21 T6 四柜台并排重排——树淬炼柜台升主界面,图鉴本体仍空置、内容排 Alpha);该柜台已随配比预设批整体挂起冻结(2026-10-05 用户令,06 在册),重启须用户另行发令。
|
||||
|
||||
## 三、与既有界面的关系
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
## 定位与形态
|
||||
|
||||
**现行形态 = 局外同调件**:开局前在营地装配(营地·秘宝匣柜台「同调装配」段)、占同调位(0 起步、固定 3 格,每件固定占 1 格)、被动件整局生效/主动件每冒险充能(冒险开始重置)。实现面 = `game/data/tables/attunement_growth.tres`(现役同调增长表,将改造为解锁条件表)+ `treasure_items.tres` + `game/core/attunement.gd`;新目录实装批落地前,现役代码按本框架运行。
|
||||
**现行形态 = 局外同调件**:开局前在营地装配(营地·秘宝匣柜台「同调装配」段)、占同调位(0 起步、固定 3 格,每件固定占 1 格)、被动件整局生效/主动件每冒险充能(冒险开始重置)。实现面 = `treasure_items.tres` + `game/core/attunement.gd`(同调位数量判定源=天赋树同调位节点点亮数——2026-10-11 Q2 裁甲、迭代 21 T4 槽位收编落地;原 `attunement_growth.tres` 谓词表已随批退役;解锁条件表由秘宝实装批另建承载);新目录实装批落地前,现役代码按本框架运行。
|
||||
|
||||
**沿革一句话**:本系统前身为切片期掉落消耗品形态(2026-09-29 迭代 7),2026-10-03 随同调实施批整体替换为现行形态;旧目录九件已全数退役入素材库(2026-10-08/10 用户裁)。历史细节见版本历史与 git 提交史。
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
|
||||
### 道具从哪来:解锁与营地装配
|
||||
|
||||
秘宝不可购买、不可合成,唯一来路=**营地解锁**(2026-10-10 用户裁混合制——基础件钥石购买/特定件条件解锁)。**装配只在冒险开始前于营地配置;冒险中不存在任何更换途径**(2026-10-11 用户裁,无例外——拾取与效果均不产生装配变化;原当日早间试行的「冒险拾取未知秘宝」渠道同日废止,奇物侧无对应实现)。解锁细目:**特定件**凭条件解锁(游玩里程碑或行为条件,逐件定条件写解锁条件表——由现役同调增长表 `attunement_growth.tres` 改造承载、改表不动码,阈值 provisional 归调参批)。同调位槽位由天赋树同调位节点提供(1=普通格/2=优秀格/3=稀有格递增,共 3 格)——树与秘宝同为角色属性的贡献者,槽位参数的提供方在树侧。开局前在营地·秘宝匣柜台「同调装配」段装入同调位(每件固定占 1 格;分档装配规则见「定位与形态」速览)。
|
||||
秘宝不可购买、不可合成,唯一来路=**营地解锁**(2026-10-10 用户裁混合制——基础件钥石购买/特定件条件解锁)。**装配只在冒险开始前于营地配置;冒险中不存在任何更换途径**(2026-10-11 用户裁,无例外——拾取与效果均不产生装配变化;原当日早间试行的「冒险拾取未知秘宝」渠道同日废止,奇物侧无对应实现)。解锁细目:**特定件**凭条件解锁(游玩里程碑或行为条件,逐件定条件写解锁条件表——秘宝实装批另建承载表、改表不动码,阈值 provisional 归调参批(原「现役同调增长表 attunement_growth.tres 改造」句随表退役换代——迭代 21 T5))。同调位槽位由天赋树同调位节点提供(1=普通格/2=优秀格/3=稀有格递增,共 3 格)——树与秘宝同为角色属性的贡献者,槽位参数的提供方在树侧。开局前在营地·秘宝匣柜台「同调装配」段装入同调位(每件固定占 1 格;分档装配规则见「定位与形态」速览)。
|
||||
|
||||
|
||||
### 什么时候怎么用:挖掘期发起,战斗结算编排
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
# 迭代 21 实现报告——天赋树实装批(2026-10-12)
|
||||
|
||||
> 需求卡片:design/research/需求卡片-天赋树实装批-2026-10-11.md(**v1.7**)|设计权威:天赋树.md v0.8|执行形态:/goal + iter 技能 U0 开工令,worktree `g-plan2-iter21-tree`(分支 iter21-tree)单会话交付
|
||||
> 本报告=迭代收口六步之一:各卡状态+代拍板追认单+GDD 反哺点节+实机目检总清单。反哺点经用户逐点裁决后另批落文,不随批直改 GDD。
|
||||
|
||||
## 一、任务卡状态总表
|
||||
|
||||
| 任务卡 | 状态 | 交付要点 |
|
||||
|---|---|---|
|
||||
| T0 前置实查(0.25d) | ✅ | ①标错机制存在性=已预查结案(免查,实态复核一致:`_judge_flag` 去重闸 :1119-1121/保险 :1124-1127/归因 :1131,结算=每轮末 `_on_round_end`→`_maybe_settle`);②聚乘算位=`_roll_gp_drop()` 返回表达式内(battle_orchestrator.gd:1461);③锋挂点=mine_hit 现成零改、守挂点=去重闸后;④锻造工坊 v0.5「裁决结果清单+工作间内容清单」读取完成——预留 0 加值位与 4a/6 两裁零冲突、钥匙 free 无前置一致;⑤基线复读在案(见 §四)。⑥boss_kill_count 消费面=已预查结案(免查) |
|
||||
| T1 schema 换代(0.75-1.0d) | ✅ | FORK_GROUPS 4→3(fg_mastery 退役);RULE_KEYS 2→6(+sense_sharp/sense_guard/economy_hoard/economy_spend);fork 单行双态(effects_a/effects_b+name_a/name_b、validate 每组恰一节点+双侧效果键⊆RULE_KEYS+主 effects 须空);row 字段退役(KEY_WHITELIST 摘除、`level_gate(level)` 平移);requires 只留点亮前置(**R1 裁撤案执行**);meta_tree.tres 33→12 行重排;easter_ley effects 摘 atk/armor_class(契约 9) |
|
||||
| T2 购买与彩蛋面(0.5d) | ✅ | tree_can_buy:easter 拒购(手动购亮=红)+行门槛换轴(spine stat 节点第 N 级需总投入 ≥4×(N−1));tree_buy_fork=购即解锁默认 A 态;tree_toggle_fork=零币切态(同态/未解锁拒绝);tree_rule_keys 按激活态 effects_a/b 投影;_check_easter_egg 特化判定(两线 cap4+socket1/2/3 全解锁);营地树行板 12 行+Fork 双态钮 |
|
||||
| T3 六键效果接线(1.5d) | ✅ | EVENTS 18→19(+flag_missed)、FACTS +battle_index(**契约 5 L2 扩签两处,见 §三追认单**);rebuild 六键合成订阅(posture 双键=battle_index==1&&round_index==1 窗;sense 两组=counter<2 闸+counter_add 记账双行,domain=adventure);level_runner `battle_index` 注入(visited 内 battle 型计数,编号空间解耦);_judge_flag 去重闸后 flag_missed 发射+grant_ap queue 施加;聚=`_roll_gp_drop` 入账前 ×1.5(掷定+关卡加成两项,escrow 不乘);散=`_spend_discounted` ×80%(current_price+pool_price 双计价点,maxi(1,·) 下限;shop_refresh_cost/三选一付费刷新不折);限次按关重置=`_start_current_level`→`_reset_tree_limited_counters()`(恰两键清零) |
|
||||
| T4 注入换代与退役(1.0d) | ✅ | hero_override=树淬炼 flat(命中不注入=契约 6,override 无 hit 字段)+`FORGE_ROUTE_BONUS` 预留 0(锻造工坊批填充位);Attunement.slots 判定源切树点亮数(att_socket1..3);tree_buy("ks_refresh") 同账路由 buy_attune_refresh(价格/cap 单一事实源=Y 行表值;树投影 level 主语随账本);buy_forge/forge_flat/forge_level/forge_price 删除 |
|
||||
| T5 旧档废弃与旧逻辑删除(0.5d) | ✅ | save_manager:`TREE_STRUCT_VERSION=2` 结构版本标记+_normalize 白名单重建(退役键 forge_levels/migration_note 不流入不回写)+换代前结构树域废弃重置(禁迁移禁折算);_migrate_v4_v5 映射体注释退役(体=缺键补齐);migrate_tree 删除;生涯页三投影整行删(career_overlay 三行+main._career_data 三键);暂停层锻造段连带删(数据源死亡);attunement_growth.tres+attunement_growth_table.gd 删除(treasure_item_table 计数键词表就地内联 UNLOCK_COUNTER_KEYS);config_db 四处+meta_forge_row 删除;meta.tres forge_products 行族删除;meta_table.gd 行族四轴(FORGE_ATTRS 沿用作 conversion 轴名) |
|
||||
| T6 营地统一 UI 重排(1.5d) | ✅ | 四柜台并排+横向滚动(树淬炼=树脊柱界面形态主界面;锻造工坊柜台=钥匙门+点亮展开零交互占位面板;秘宝匣柜台=钥匙门+工坊六行/同调装配段/Y 升级行整体迁入;图鉴常开独立位);refresh_camp 接管树行板重建(购买后按钮禁用态同步);`_tree_buy_btns`/`_tree_fork_btns` 登记面(探针消费);占位面板零副作用(纯 Label、零按钮、零写档) |
|
||||
| T7 文档同步闸+回归收口(1.0d) | ✅ | §5 机械面七处更新完毕(见 §二);批末重闸+MC 配对锚见 §四;本报告+06/roadmap/active 记账 |
|
||||
|
||||
## 二、关联文档同步面(§5 机械面清单)执行记录
|
||||
|
||||
| 文档 | 更新内容 | 状态 |
|
||||
|---|---|---|
|
||||
| design/dev/architecture/architecture.md | meta_progression 行(12 节点 v0.8/槽位收编);新增 effect_engine 行(词表扩签+六键合成订阅);battle_orchestrator 行(效果引擎宿主+树规则挂点);D1 HUD 行(营地四柜台并排) | ✅ |
|
||||
| design/registry/entities.yaml | 新增 `meta_tree` 实体(结构/fork_groups/posture_window/rule_keys/sense_limit/economy_scope/workshop_keys/spine_gate/easter/attunement_slots 十attr) | ✅ |
|
||||
| roadmap.md | 迭代 21 行→✅ 已入库;进度头条目换代 | ✅ |
|
||||
| active.md | 469 开工+470 收口两条状态记录+快照换代 | ✅ |
|
||||
| design/gdd/06-待办清单.md | 天赋树实装批条→已入库+欠账三条登记 | ✅ |
|
||||
| game/README.md | 追加 2026-10-12 迭代 21 批次记录 | ✅ |
|
||||
| design/gdd/systems/跨局循环/生涯.md | 「三柜台解锁完成度概览」行整行删(⑧裁决)+图鉴柜台句随四柜台换代 | ✅ |
|
||||
| design/gdd/07-体验验证清单.md | ⑲两观测项(属性锻造已购合计/天赋树已购概览)摘除,六类九项→两类六项 | ✅ |
|
||||
| design/gdd/systems/跨局循环/秘宝.md | 同调位判定源句两处(:10/:161)——T5 用户明令豁免随批扫改(attunement_growth.tres 退役注记) | ✅ |
|
||||
| design/gdd/systems/表现与支撑/存档序列化.md | **不更新**(契约 4:开发阶段无视文档层条款,禁增设豁免/例外——⑦裁决) | ✅(未动) |
|
||||
|
||||
## 三、代拍板追认单(§9 白名单内执行,逐条列明待用户追认)
|
||||
|
||||
| # | 事项 | 代拍板内容 | 白名单依据 |
|
||||
|---|---|---|---|
|
||||
| 1 | **R1 裁撤案执行** | 「等级阈值前置」形态未实现(schema 无 requires_level 字段,requires 只认点亮)——按卡片 §10「默认按裁撤案排程」执行 | 卡片既定默认案( roadmap 行内「不阻开工门」明示) |
|
||||
| 2 | 12 行节点 id | sp_hp/sp_hit(旧层行 id 退役)、forge_workshop(新钥匙节点)、easter_ley/ks_workshop/ks_refresh/att_socket1..3/fork_posture/fork_sense/fork_economy(沿用现役命名法) | 白名单「节点 id 命名」 |
|
||||
| 3 | Fork 行双效果键承载 | `effects_a`/`effects_b` 双 Dictionary 字段+`name_a`/`name_b` 双态呈现名(KEY_WHITELIST 扩四键;fork 行主 effects 强制空) | 白名单「Fork 行双效果键的字段形态」 |
|
||||
| 4 | 限次 counter_id 与限值 | `tree_sense_sharp_used`/`tree_sense_guard_used`;每关 2 次(锋/守同值);散折率 80%(floor、下限 1);聚乘数 1.5×(round)——**数值全 provisional 归 M4** | 白名单「规则键常量名」+契约 7 |
|
||||
| 5 | 效果引擎词表扩签(L2) | EVENTS +`flag_missed`(18→19,命名避让 source_class 在册名 flag_miss);FACTS +`battle_index`(18→19);**效果类型/OPS/DO_KEYS 零扩**(六键全走在册 30 类型+既有闸/记账组合) | 契约 5 L2 记录继续——卡片 T3 预知两处 |
|
||||
| 6 | battle_index 载体 | level_runner `_battle_index()`=visited 内 battle 型节点计数(_enter_node 先追加后启动→含当前=1 基序);enc 携带、编排 `_fx_fact` 回读——避开 node_depth/数组下标编号空间混编碰撞 | 卡片 T3 选型注记(等效参数位陷阱规避) |
|
||||
| 7 | 旧档废弃检测选型 | 结构版本标记(meta.tree_struct=2,缺失或更低=树域废弃重置)而非节点 id 残留检测——save_manager 零玩法依赖约束下不内联 id 白名单 | 白名单「旧档废弃检测的实现选型」 |
|
||||
| 8 | _normalize 白名单重建 | meta 域按默认模型键集收口重建(退役键 forge_levels/migration_note 载入即丢弃不回写盘);breakpoint 等非 meta 顶层键零触碰 | 白名单「_normalize 补键细节」+契约 4 |
|
||||
| 9 | 营地四柜台布局 | 横向 ScrollContainer 包裹四柜台(各 300 min 宽等分)、钥匙门=锁定行 Label 与内容容器可见性切换、占位面板=纯 Label 双行(零按钮零写档) | 白名单「程序占位布局与控件复用」 |
|
||||
| 10 | 暂停层锻造段连带删除 | pause_overlay 属性锻造段+main._pause_data forge 投影随 forge_levels 域死亡一并删除(卡片未点名暂停层——数据源删除的编译级连带,非范围自扩) | T5 旧逻辑删除同生死原则(code-conventions §3.5 第 5 条) |
|
||||
| 11 | MC 载体扩 rules 模 | adventure_monte_carlo feat_mode 增 `rules`(args[7] 逗号分隔键集)——V19 三组配对载体(原 ab_a/ab_b 硬编码仅 posture 组) | V19 验收载体必须(代拍板「探针夹具构造细节」) |
|
||||
| 12 | ks_refresh 树行价 30 | 表行 price=30 仅为呈现元数据,实际计费走 Y 行 keystone_cost(30/60)——树 cap2 与 Y 表两级行天然对齐 | 白名单「价格沿旧表值/量纲锚」 |
|
||||
|
||||
## 四、验证闸读数
|
||||
|
||||
- **T0 基线(主区 HEAD=73acee0)**:冒烟 864/0 但 run_smoke 判 FAIL(SCRIPT ERROR 51 行——fx_reveal 空 source_class 触发 :1592 行来源断言,a57489e9 迭代 20 T1 落地件**存量红**,两轮确定性复现);run_all 21 套全绿;probe_gate --click 全绿残留 0。
|
||||
- **存量红修复(随 T3 批)**:`_flush_pending_reveals` 三处 `_emit_row("fx_reveal", "", ...)` 补 `fx_reveal` 来源标签+SOURCE_EXEMPT_CLASSES 增类级豁免(落空行无真实格=豁免集合设计意图内)。修复后脚本错误归零。
|
||||
- **批末轻闸(worktree)**:冒烟 **886/0**(864 基线−摘除断言+新增 V1/V2/V3/V8/V9/V10/V16 断言)三闸全过;run_all **22 套全绿**(新增 tree_rules_v11_test);probe_gate --click **全绿**(树购买/双态切换点击路径,问题=0)。
|
||||
- **V19 MC 配对锚(终读数)**:perfect+max 档同种子(seed0=88000)配对 ×60 局×六配置——六侧通关率全部 61.7%(37/60),三组配对差**全部 0.0pp**(判据 <~3pp 在带):开局姿态 A/B、危险直觉 锋/守、财富之道 聚/散两侧同强基线成立。〔载体注记:MC feat_mode 扩 `rules` 模(追认单 #11);normal+min 档基率地板(0-1.7%)无配对信号,改用 perfect+max 档取得可用基率——口径换代记验证记录。〕
|
||||
- **两入口铁规**:全部验证走 smoke_all.gd 与 tests/run_all 两执行口+probe_gate 包装器,零另开执行口。
|
||||
|
||||
## 五、GDD 反哺点清单(设计权威文档零触碰,待用户逐点裁决后另批落文)
|
||||
|
||||
| # | 反哺点 | 现状差异 | 建议去向 |
|
||||
|---|---|---|---|
|
||||
| ① | 天赋树.md「已建未接面」三条清账+P0 段 33→12 落地态+验收实态 | 文档仍标「实装未落,现役代码仍为 33 节点」 | 天赋树.md 换代批(反哺落文) |
|
||||
| ② | 元进度.md 注入公式终态句+属性柜台退役终态(v1.14 预登记过渡态) | 公式现役=树淬炼 flat+锻造路线加值位预留 0 | 元进度.md 换代批 |
|
||||
| ③ | 天赋树.md:111「设计避让:不触碰商店刷新语义与 GP 换算两处在册旋钮」句 | ⑪②裁决已改写为定义边界口径(折扣定义边界=商品标价,键 gp_cost_mult)——文档原句须改写 | 随①落文 |
|
||||
| ④ | 锻造工坊.md「树侧解锁门」接线落地态+注入公式「锻造路线加值」项落地态 | 钥匙节点 forge_workshop 已落地(free 无前置);加值位=FORGE_ROUTE_BONUS 预留 0 | 锻造工坊.md 反哺批 |
|
||||
| ⑤ | HUD框架.md 营地四柜台并排结构终态 | 程序占位已落地,视觉终稿归 HUD 营地视觉批 | HUD 视觉批输入 |
|
||||
| ⑥ | 验收实态:命中线「暂不注入」括注、彩蛋条件两线+socket 全解锁落地态 | 代码=契约 6 纪律维持 | 随①落文 |
|
||||
|
||||
## 六、登记欠账(本批不做·卡片 §4 不做清单,06 已入册)
|
||||
|
||||
1. **AP 上限 clamp 缺位**——树 posture AP+先手类 AP 件+时沙走同一条 `_pending_ap_delta` 无上限(action_economy.gd 只有 maxi(0,·) 下限),AP 10/8 可打出;**新叠加源使该既有缺陷可观测化**(⑨裁决甲:本批不碰)。
|
||||
2. **三选一刷新软上限**——现役缺「停点间基础价随关卡进度递增」维(基础价恒定);另立待办(⑩裁决),商店货架刷新共享旋钮同步跟涨。
|
||||
3. **Y(_attune_refresh_y)不入断点**——用完退菜单再续玩可满血复活;**V15 只验账本一致性,不代表刷新链路已可用**(⑪裁决:出口报告点名防误读)。
|
||||
|
||||
## 七、实机目检总清单(人闸——V22,07 在册)
|
||||
|
||||
1. 营地四柜台并排:树淬炼柜台=主界面(脊柱两行一线一节点行板+Fork 双态切换钮+脉分组头);锻造工坊柜台锁定态(钥未点亮)→点亮后展开占位面板(零交互观感);秘宝匣柜台锁定态→点亮展开(六行+同调装配段+Y 升级行);图鉴常开独立位。
|
||||
2. 树购买交互:余额不足按钮态、行门槛拦截 toast、购买后行板即时刷新。
|
||||
3. 宽度不足时四柜台横向滚动可用。
|
||||
4. 暂停层:属性锻造段已消失,钥石+工坊+天赋树概览呈现正常。
|
||||
5. 生涯页:两类六项(原三柜台概览行消失)。
|
||||
6. 冒险内:先手压制首战首轮 +1AP 体感、锋踩雷减半读数、守标错下轮 +AP、聚结算框 GP 增量即实得、散商店标价八折。
|
||||
|
||||
## 八、遗留与边界
|
||||
|
||||
- **R1 追认**:裁撤案已执行(追认单 #1),用户推翻即补 requires_level 形态(随批重立)。
|
||||
- **契约 12 编排级断言口径**:守侧五场景以「编排 rig(begin_battle 携 sense_guard + 授予段 _judge_flag 直注)+引擎直构」双承载(V10 断言面);②被挖旗零触发由发射面既有过滤承载(插旗不可逆批既有行为)。
|
||||
- **迭代 20 残留人闸不受影响**:实机目检五件+V20/V21/V22 人工核对半仍待用户(07 在册)。
|
||||
@@ -5,6 +5,21 @@
|
||||
# 命名注记(2026-09-15):本表历史上以 A1–D6 系统代号命名文档与义务,已全部清偿为中文规范名;英文代号对照表见 design/gdd/02-系统清单.md。owner 已改指新骨架文档路径(部分为待成文占位,随各文档定稿修订——doc-conventions §8 既定清偿路径);a2_/a5_/d1_/b1_/b4_ 前缀键名与夹具名同批改领域命名(minesweeper_/aux_/hud_/settlement_/reward_)。
|
||||
|
||||
entities:
|
||||
- name: meta_tree
|
||||
type: table
|
||||
owner: design/gdd/systems/跨局循环/天赋树.md
|
||||
attributes:
|
||||
structure: 12 节点 = 脊柱 2(生命/命中淬炼——一线一节点 cap4)+ 锻造脉 2(锻造工坊钥匙 free 无前置 + 彩蛋地脉共鸣)+ 钥石脉 2(秘宝匣/刷新升级)+ 同调脉 6(同调位 1/2/3 + Fork 3 双态节点)——迭代 21 T1 换代(33→12,2026-10-12 落地)
|
||||
fork_groups: [fg_posture=开局姿态, fg_sense=危险直觉, fg_economy=财富之道](封闭词表三组——每组恰一节点、购买解锁默认 A 态、营地零币切换;fg_mastery 克制专精已退役)
|
||||
posture_window: 开局姿态窗 = 每关首战首轮(battle_index==1 且 round_index==1;battle_index=关卡域战斗序,效果引擎 FACTS 扩签)
|
||||
rule_keys: [posture_first_strike, posture_bulwark, sense_sharp, sense_guard, economy_hoard, economy_spend](封闭六键——A/B 只放规则面)
|
||||
sense_limit: 危险直觉锋/守每关限次(adventure_counters domain=adventure 承载——随冒险持久/按关重置/新冒险清零/断点保留;provisional 2 次/关归 M4)
|
||||
economy_scope: 聚 = 仅战斗掉落渠道乘算(入账前,结算框显示实得);散 = 商品购买价折价(键 gp_cost_mult 语义、刷新价不折——2026-10-12 §11② 定义边界=商品标价)
|
||||
workshop_keys: 锻造工坊钥匙(free 无前置)/ 秘宝匣(前置=脊柱生命第 1 级)——点亮展开对应工作间柜台(营地四柜台并排)
|
||||
spine_gate: 行门槛 = 节点第 N 级需全树总等级 ≥ 4×(N−1)(SPINE_ROW_GATE=4,总等级口径=tree_nodes 全节点等级和;row 字段退役)
|
||||
easter: 地脉共鸣 = 生命/命中两线拉满 + 同调位全解锁自动觉醒(零币;effects 只持 max_hp/hit——攻防所有权切分红线)
|
||||
attunement_slots: 同调位数量 = 树同调位节点点亮数(单源——attunement_growth 谓词表判定链已退役)
|
||||
referenced_by: [天赋树, 元进度, 秘宝, 锻造工坊]
|
||||
- name: config_db
|
||||
type: service
|
||||
owner: design/gdd/systems/表现与支撑/数据配置.md
|
||||
|
||||
@@ -565,3 +565,15 @@ Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/a
|
||||
|
||||
**换代面**:feats.tres 整表换代(旧 27 件退役→重设计 29 行=基础 27+迁入淬毒刃纹/封印爆裂——订阅行 on/when/do 四级 rarity+cap);效果引擎词表扩签(事件 18/效果类型 30/事实 18/算子 +mod_eq——heal/ac_bonus/damage_reduce/bonus_delta/blast_scaled/preview_area/target_attack_bonus 七新类型+adventure 域计数器跨战账本);池结构案D(四级档位 70/25/9/0=104+曝光槽 2+1+概率上限解耦案②+Boss 按档配比加权抽取);凭证五件入池(刷新券 F-1 修复/储备金袋/同调电池〔choose_battery_target 玩家点选〕/时沙/完美保险);流派标签/档位激活机制退役(战斗路径 FeatsEngine 求值与 rider_tier 注入摘除;HUD 特性段呈现随 2026-10-12 呈现修复批换代——逐流派聚合行退役改逐件行「件名 ×N」、卡面摘族名,原聚合行在新 29 行下的空键兜底僵尸呈现随之消解)。
|
||||
**验证**:冒烟 864/0+run_all 21 套+probe_gate 全绿;MC 同种子重放 30 局逐位复现;新基线 normal/off 100 局通关 0%(master 对照同为 0%=零配基线本相,止步分布 90/10→93/7 可归因曝光槽)。设计权威=奇物.md(反哺点清单见 design/qa/iter20-实现报告-2026-10-11.md)。
|
||||
|
||||
## 2026-10-12 迭代 21 天赋树实装批(U0——需求卡片 v1.7;设计权威 = 天赋树.md v0.8)
|
||||
|
||||
**结构换代 33→12 节点**:脊柱 2(生命/命中淬炼一线一节点 cap4——攻/护淬炼线随攻防所有权切分砍除,攻唯一入口=锻造工坊武器路线、AC 唯一入口=甲路线)+ 锻造脉 2(forge_workshop 钥匙 free 无前置 + easter_ley 彩蛋〔effects 摘攻/护分量〕)+ 钥石脉 2(秘宝匣/刷新升级)+ 同调脉 6(同调位 1/2/3——socket1 改根节点无前置 + Fork 3 单行双态)。schema 换代:FORK_GROUPS 4→3(fg_mastery 退役)/ RULE_KEYS 2→6 / row 字段退役(行门槛主语=节点第 N 级需全树总投入 ≥4×(N−1))/ requires 只留点亮前置(R1 裁撤案执行)。
|
||||
|
||||
**六键真效果**:开局姿态窗=每关首战首轮(FACTS 扩签 battle_index=关卡域战斗序);危险直觉锋=踩雷减半·守=标错返还 AP(EVENTS 扩签 flag_missed、契约 12 去重闸语义)——两组每关限次(adventure_counters domain=adventure、按关重置);财富之道聚=战斗掉落 GP 入账前×1.5、散=商品购买价×80%(双计价单点,刷新价不折)。数值全 provisional 归 M4。
|
||||
|
||||
**退役删除**:属性锻造柜台全家(buy_forge/forge_flat/meta_forge_row/forge_products 行族/forge_levels 存档域/营地·暂停层·生涯三投影);migrate_tree 三体系迁移(旧档=tree_struct 标记检测破坏性废弃重置,禁迁移折算);attunement_growth 谓词表(同调位槽位判定源切树点亮数——Q2 裁甲)。
|
||||
|
||||
**营地统一**:四柜台并排(树淬炼=树脊柱界面形态主界面/锻造工坊柜台钥匙门+零交互占位面板/秘宝匣柜台钥匙门+工坊六行+同调装配段+Y 升级行迁入/图鉴常开);刷新升级双入口同账(树上 ks_refresh 路由 buy_attune_refresh)。
|
||||
|
||||
**验证**:冒烟 886/0(基线 864/0+存量红修复——fx_reveal 三处空 source_class 补标签,a57489e9 落地件 51 行脚本错误清零)+ run_all 22 套(新增 tree_rules_v11 六键闭集校验)+ probe --click 全绿(树购买/双态切换点击路径换代);V10 行为段=引擎直构+编排授予段直注(守侧五场景/聚散同种子配对/限次生命周期)。存量红修复与帐目细节见 design/qa/iter21-实现报告-2026-10-12.md。
|
||||
@@ -1053,8 +1053,17 @@ func abandon() -> bool:
|
||||
|
||||
# ---- 关卡推进(私有——序列编排单一出口)----
|
||||
|
||||
# 树限次计数按关重置(迭代 21 T3——危险直觉锋/守两组「每关限次」账本键:跨战持久由 adventure
|
||||
# 域导出/回灌承载,此处随关清零闭合生命周期四态;键集=效果引擎树合成行声明的 counter_id;
|
||||
# 独立成口=V10 断言面直呼)
|
||||
func _reset_tree_limited_counters() -> void:
|
||||
for cid in ["tree_sense_sharp_used", "tree_sense_guard_used"]:
|
||||
adventure_counters.erase(cid)
|
||||
|
||||
|
||||
func _start_current_level() -> bool:
|
||||
_runner = LevelRunner.new(db, rng)
|
||||
_reset_tree_limited_counters() # 树限次计数按关重置(V10 生命周期四态闭合——断言面直呼本口)
|
||||
# core↔core 接线只许方法 Callable(ADR-0001 S6——lambda 捕获 self 会延长 RefCounted 生命周期乃至成环)
|
||||
_runner.node_entered.connect(_forward_node_entered)
|
||||
_runner.choices_presented.connect(_forward_choices_presented)
|
||||
|
||||
+13
-21
@@ -1,27 +1,19 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# Validation Question: 同调位读数是否由「通关次数 + Boss 击杀数」经 attunement_growth.tres 谓词表
|
||||
# 实时求值(Σ达成 grant clamp slot_cap),迭代 10 过渡键 transitional_sync_slots 得以退役(P0)
|
||||
# Date: 2026-10-03(迭代 11 第①批 秘宝同调实施批 T3——需求卡片 v1.1 §六 T3;设计权威底稿 proposal §2)
|
||||
# 同调位求值服务:计数源组装(save 账本 → 计数字典)与实时求值,纯函数零副作用。
|
||||
# 读数源切换(迭代 10 欠账③):装配面消费方(营地装配覆盖层 T4/冒险开始装配注入 T6)一律经本服务取
|
||||
# Slots;过渡键 adventure.transitional_sync_slots 的在途读取面(treasure_items 持有上限 gate/战斗内沉没/
|
||||
# 停点替换交互/main 同调位呈现)系过渡渠道承载——T7 退役面切换时随渠道一并消亡(该批读数源切换的
|
||||
# 正主落点,06 欠账③销账注记随 T7/T9)。过渡期切早 = off 态 MC 对 81000 锚添新扰动(fresh 档计数全零
|
||||
# → Slots=0 ≠ 过渡键 1),违「attune=off 逐位一致」🔴 契约——故本服务先立、在途面 T7 切。
|
||||
# attune=off 纪律:零 await、零 RNG、零写副作用(🔴 契约 3 的服务侧承载)。
|
||||
# 同调位求值服务(迭代 21 天赋树实装批 T4 换代——槽位收编 Q2 裁甲:槽位数量=树上同调位节点点亮数,
|
||||
# 旧「按击杀 Boss 次数/通关次数自动涨」谓词表判定链随批删除〔attunement_growth.tres 与表访问器
|
||||
# 随 T5 清退;boss_kill_count/clear_count 计数域保留——各有非槽位硬消费,见卡片 T0⑥〕)。
|
||||
# 纯函数零副作用;attune=off 纪律:零 await、零 RNG、零写副作用。
|
||||
class_name Attunement
|
||||
extends RefCounted
|
||||
|
||||
|
||||
# 计数源组装(save = SaveManager 或 null——模拟/冒烟未注入档,按全零计数求值)
|
||||
static func counters_from_save(save) -> Dictionary:
|
||||
if save == null:
|
||||
return {}
|
||||
return save.attunement_counters()
|
||||
|
||||
|
||||
# 同调位实时求值(营地进冒险前求值一次 + 装配覆盖层实时呈现共用同一口径)
|
||||
# 同调位实时求值(营地进冒险前求值一次 + 装配覆盖层实时呈现共用同一口径):
|
||||
# 槽位数量 = att_socket1..3 树点亮数(tree_level>=1),未点亮=0。
|
||||
# db 参数保留(调用面两处签名不变——当前判定源零表依赖,参数弃置)。
|
||||
static func slots(db, save) -> int:
|
||||
if db == null or db.attunement_growth == null:
|
||||
if save == null:
|
||||
return 0
|
||||
return db.attunement_growth.slots(counters_from_save(save))
|
||||
var n := 0
|
||||
for i in range(1, 4):
|
||||
if save.tree_level("att_socket%d" % i) >= 1:
|
||||
n += 1
|
||||
return n
|
||||
@@ -38,8 +38,10 @@ signal settlement_snapshot(snap: Dictionary) # 每结算末尾的全量状态
|
||||
# 判定行 verdict · 高光行 highlight
|
||||
# 行来源断言豁免集合(行型层)——判定段 / 衰减段本就无棋盘来源,勿一刀切误炸
|
||||
const SOURCE_EXEMPT_TYPES := ["decay", "verdict"]
|
||||
# 行来源断言豁免集合(来源类别层)——事实来源是单位 / 清图而非棋盘格
|
||||
const SOURCE_EXEMPT_CLASSES := ["hero_attack", "enemy_action", "clear_reward"]
|
||||
# 行来源断言豁免集合(来源类别层)——事实来源是单位 / 清图而非棋盘格;
|
||||
# fx_reveal=专长揭示掷格(落空行无真实格〔迭代 21 存量红修复——a57489e9 落地件三处空来源
|
||||
# 触发行来源断言,51 行/冒烟,两轮确定性复现〕)
|
||||
const SOURCE_EXEMPT_CLASSES := ["hero_attack", "enemy_action", "clear_reward", "fx_reveal"]
|
||||
|
||||
var db: ConfigDb
|
||||
var rng: RngServiceCore
|
||||
@@ -154,7 +156,7 @@ func _flush_pending_reveals(ri: int) -> void:
|
||||
if not minesweeper.is_revealed(c) and not board.is_mine(c):
|
||||
cands.append(c)
|
||||
if cands.is_empty():
|
||||
_emit_row("fx_reveal", "", Vector2i(-1, -1), ri, "揭示落空(无未揭示安全格)", 0, {}, "")
|
||||
_emit_row("fx_reveal", "fx_reveal", Vector2i(-1, -1), ri, "揭示落空(无未揭示安全格)", 0, {}, "")
|
||||
continue
|
||||
var rg: RandomNumberGenerator = rng.open_stream("fx_reveal")
|
||||
var cell: Vector2i = cands[rg.randi_range(0, cands.size() - 1)]
|
||||
@@ -162,12 +164,12 @@ func _flush_pending_reveals(ri: int) -> void:
|
||||
var ok: bool = minesweeper.fx_reveal(cell)
|
||||
_fx_revealing = false
|
||||
if not ok:
|
||||
_emit_row("fx_reveal", "", Vector2i(-1, -1), ri, "揭示落空(格子不可揭)", 0, {}, "")
|
||||
_emit_row("fx_reveal", "fx_reveal", Vector2i(-1, -1), ri, "揭示落空(格子不可揭)", 0, {}, "")
|
||||
continue
|
||||
var snap = _reward_economy.query_full_snapshot(cell)
|
||||
if snap is Dictionary:
|
||||
_grant_routed(snap, "positive", "fx_reveal", cell, ri, "fx_reveal")
|
||||
_emit_row("fx_reveal", "", cell, ri, "专长揭示", 0, {}, "fx_reveal")
|
||||
_emit_row("fx_reveal", "fx_reveal", cell, ri, "专长揭示", 0, {}, "fx_reveal")
|
||||
|
||||
|
||||
# 补充事实面(fact 取值第二优先源——payload 未携带的运行态读数)
|
||||
@@ -183,6 +185,8 @@ func _fx_fact(fact: String):
|
||||
return n
|
||||
"round_index":
|
||||
return round_index
|
||||
"battle_index":
|
||||
return int(encounter.get("battle_index", 1)) # 关卡域战斗序(1 基——level_runner enc 携带;开局姿态窗条件面)
|
||||
"flags_count":
|
||||
var hits_n := 0
|
||||
for fevt in _facts_flags:
|
||||
@@ -1129,6 +1133,14 @@ func _judge_flag(evt: Dictionary, ri: int) -> void:
|
||||
if _perfect_hit_false == 1:
|
||||
hl = "perfect_broken" # 本板完美谓词已破的归因锚
|
||||
_emit_row("flag_miss", "flag_miss", cell, ri, "标旗未中", 0, {}, hl)
|
||||
# 危险直觉·守挂点(迭代 21 T3——flag_missed 发射:契约 12 触发主语=「当回合发生的标错
|
||||
# 动作」——去重闸=新旧标错判别器〔后续轮重判旧旗被闸挡回零触发〕;双条件②「结算时旗仍在
|
||||
# 场」由发射面既有过滤承载〔被挖开的旗已随挖掘擦除零兑付〕;grant_ap mode:queue 下一轮
|
||||
# 开轮兑现——末轮判出无下一轮=合法作废自动成立〔编排器每战新建,队列不跨战〕;限次=合成行
|
||||
# adventure 域计数器闸)
|
||||
for h in _fx_emit("flag_missed", {"cell": cell, "round": ri}):
|
||||
if String(h.get("kind", "")) == "grant_ap" and String(h.get("mode", "")) == "queue":
|
||||
action_economy.queue_ap_delta(int(h.get("value", 0)))
|
||||
var snap = _reward_economy.grant(cell)
|
||||
if not (snap is Dictionary):
|
||||
return
|
||||
@@ -1467,7 +1479,13 @@ func _roll_gp_drop() -> int:
|
||||
else:
|
||||
var stream: RandomNumberGenerator = rng.open_stream("gp_drop")
|
||||
roll = stream.randi_range(int(drops.get("roll_min", 0)), int(drops.get("roll_max", 0)))
|
||||
return roll + db.shop_level_gp_bonus(int(encounter.get("level_index", 0))) + escrow
|
||||
var drop := roll + db.shop_level_gp_bonus(int(encounter.get("level_index", 0)))
|
||||
# 财富之道·聚(迭代 21 T3——战斗掉落 GP 乘算·入账前:只乘战斗掉落两成分〔掷定+关卡加成〕,
|
||||
# 店内 escrow 不乘〔范围=仅战斗掉落渠道,卡片§11②裁决〕;本函数返回值=结算框「GP 增量」行
|
||||
# 显示源,落在入账前=显示即实得;乘数 provisional 150%,值归 M4 数值批)
|
||||
if (encounter.get("tree_rules", []) as Array).has("economy_hoard"):
|
||||
drop = int(round(float(drop) * 1.5))
|
||||
return drop + escrow
|
||||
|
||||
|
||||
# ---- 呈现状态面(E1 封装收口 2026-09-13):呈现层 / headless 自动玩家的唯一读取出口——
|
||||
|
||||
+3
-18
@@ -23,10 +23,9 @@ const TABLE_MANIFEST := {
|
||||
"difficulty": "difficulty.tres",
|
||||
"shop": "shop.tres", # GP+商店房实施批 2026-09-21(表与 schema 落籍归并行数据面——本批只接线加载与消费)
|
||||
"meta": "meta.tres", # 元进度实现批 2026-09-21(MetaTable 四行族——schema 与表同批落籍)
|
||||
"meta_tree": "meta_tree.tres", # 迭代 12 T8 双层 BD 天赋树(MetaTreeTable 定义表 Rule 4 豁免——R1 甲融合案 33 节点)
|
||||
"meta_tree": "meta_tree.tres", # 迭代 12 T8 建域、迭代 21 T1 换代 12 节点(MetaTreeTable 定义表 Rule 4 豁免——天赋树.md v0.8 结构事实源)
|
||||
"treasure_items": "treasure_items.tres", # 迭代 7 T1 秘宝内容批(TreasureItemTable 六行——定义表 Rule 4 豁免;罗盘 retired/失义件 deferred 标记见 schema 头注)
|
||||
"adventure": "adventure.tres", # 冒险结构实现批 2026-09-21(AdventureTable 建表——大奖励与钥石行值单点)
|
||||
"attunement_growth": "attunement_growth.tres", # 迭代 11 第①批 同调实施批 T1(AttunementGrowthTable 单行全局——同调位里程碑谓词表;单行先例 Rule 4 豁免)
|
||||
"feats": "feats.tres", # 迭代 12 T1 双层 BD 规则改写型特性基建(FeatTable 定义表 Rule 4 豁免——TreasureItemTable 先例;空表合法态=主时间线零扰动)
|
||||
}
|
||||
|
||||
@@ -49,7 +48,6 @@ var meta: MetaTable
|
||||
var meta_tree: MetaTreeTable # 迭代 12 T8 类型化持用(消费面走 tree_node 访问器)
|
||||
var treasure_items: TreasureItemTable # 迭代 7 T1 类型化持用(消费面走 treasure_* 访问器)
|
||||
var adventure: AdventureTable
|
||||
var attunement_growth: AttunementGrowthTable # 迭代 11 第①批 T1 类型化持用(消费面走 attunement_* 访问器)
|
||||
var feats: FeatTable # 迭代 12 T1 类型化持用(消费面走 feat_* 访问器)
|
||||
|
||||
var is_ready := false
|
||||
@@ -141,7 +139,6 @@ func load_tables(root: String, collect_only: bool) -> bool:
|
||||
meta = loaded.get("meta")
|
||||
treasure_items = loaded.get("treasure_items")
|
||||
adventure = loaded.get("adventure")
|
||||
attunement_growth = loaded.get("attunement_growth")
|
||||
feats = loaded.get("feats")
|
||||
meta_tree = loaded.get("meta_tree")
|
||||
is_ready = not has_errors
|
||||
@@ -277,13 +274,8 @@ func treasure_live_ids() -> Dictionary:
|
||||
return out
|
||||
|
||||
|
||||
# ── 同调轴访问器(迭代 11 第①批 T1——消费面一律走访问器,禁直读行集)──
|
||||
func attunement_slot_cap() -> int:
|
||||
return attunement_growth.slot_cap if attunement_growth != null else 0
|
||||
|
||||
|
||||
func attunement_milestones() -> Array:
|
||||
return attunement_growth.milestones if attunement_growth != null else []
|
||||
# ── 同调轴访问器已删除(迭代 21 T5——attunement_growth 谓词表随槽位收编退役:槽位数量=树同调位
|
||||
# 节点点亮数,判定源=Attunement.slots;boss_kill_count/clear_count 计数域保留——非槽位硬消费在册)──
|
||||
|
||||
|
||||
# 行族四轴(数据契约,行集归 shop.tres):gp_drops {tier, mean, roll_min, roll_max} ·
|
||||
@@ -361,13 +353,6 @@ func _shop_rows(family: String) -> Array:
|
||||
# workshop_items {item_id, display_name, clear_threshold} ·
|
||||
# conversion {attr, per_level_shift_pct}。
|
||||
|
||||
func meta_forge_row(attr: String) -> Dictionary:
|
||||
for row in _meta_rows("forge_products"):
|
||||
if String(row.get("attr", "")) == attr:
|
||||
return row
|
||||
return {}
|
||||
|
||||
|
||||
func meta_boss_keystone_row(level_index: int) -> Dictionary:
|
||||
for row in _meta_rows("boss_keystone"):
|
||||
if int(row.get("level_index", -1)) == level_index:
|
||||
|
||||
@@ -17,10 +17,12 @@ class_name EffectEngine
|
||||
extends RefCounted
|
||||
|
||||
## 事件词汇(迭代 20 专长实装批 T1 扩签——16→18:+dig_stop 停手挂点〔弃图兑换〕
|
||||
## +battle_won 战斗胜利〔战地包扎〕;原 16 发射点不动)
|
||||
## +battle_won 战斗胜利〔战地包扎〕;迭代 21 天赋树批 T3 扩签 18→19:+flag_missed 标错判定
|
||||
## 〔危险直觉·守挂点——_judge_flag 去重闸后发射,契约 12 触发主语=当回合标错动作;
|
||||
## 命名避让 _emit_row 在册 source_class 名 flag_miss〕;原发射点不动)
|
||||
const EVENTS := ["dig_reveal", "dig_paid", "flag_hit", "mine_hit", "attack_commit", "clamp_check",
|
||||
"armor_roll", "kill", "damage_taken", "enemy_hp_threshold", "round_start", "flag_settle",
|
||||
"round_end", "reward_roll", "treasure_used", "charge_full", "dig_stop", "battle_won"]
|
||||
"round_end", "reward_roll", "treasure_used", "charge_full", "dig_stop", "battle_won", "flag_missed"]
|
||||
|
||||
## 扫雷侧事件集(🔴 流派扫雷触发铁律新形态:每流派 ≥1 行订阅本集事件——schema 加载校验。
|
||||
## 原 MINE_HOOKS 的同义迁移:挖掘/标旗/轮末结算面事件)
|
||||
@@ -53,7 +55,8 @@ const EFFECT_KINDS := ["add_ticket_rider", "modify_ticket", "damage_mult", "open
|
||||
const FACTS := ["matchup", "is_first_dig_of_battle", "terrain_kind", "target_armor_class",
|
||||
"flags_count", "charge_layers", "nest_count", "round_index", "counter",
|
||||
"new_resource_type", "is_edge_cell", "is_blank_zero", "dug_under_flag",
|
||||
"ap_exhausted_last", "hp_ratio", "amount", "ap_left", "pct"]
|
||||
"ap_exhausted_last", "hp_ratio", "amount", "ap_left", "pct",
|
||||
"battle_index"] # 迭代 21 T3 扩签——关卡域战斗序(1 基,enc 携带;开局姿态组「每关首轮窗」条件面)
|
||||
|
||||
## when 算子词汇(封闭——fact 查询面落法随批登记:mod_zero=计数器取模判满〔掘进蓄能〕、
|
||||
## advantageous=克制矩阵正面判定〔淬毒刃纹〕、gte_ref/lt_ref=与另一计数器比较〔深挖钻头配额〕、
|
||||
@@ -113,17 +116,46 @@ func rebuild(feat_ids: Array, item_ids: Array, tree_rules: Array = []) -> void:
|
||||
continue
|
||||
for tr in (row.get("triggers", []) as Array):
|
||||
_push_trigger("attune", iid, tr, row)
|
||||
# 树规则合成订阅(T5——规则键效果走同面:非数据行,词表同闭集)
|
||||
# 树规则合成订阅(迭代 21 T3 换代——六键按 v0.8 三组双态):posture 两键=「每关首轮窗」语义
|
||||
# 〔battle_index==1 且 round_index==1;battle_index=关卡域战斗序由 enc 携带,原「每战首轮」
|
||||
# 实态随窗语义换代〕;sense 两组=每关限次〔adventure 域计数器 lt 上限闸 + 命中即 counter_add
|
||||
# 记账双行——同事件双行同预评估态见闸同记,序=装载序确定;生命周期四态:跨战持久=domain
|
||||
# adventure 导出/回灌、按关重置=AdventureRunner._start_current_level 清零、新冒险清零=账本
|
||||
# 归零、断点保留=断点快照随键〕;economy 两组零订阅行——消费点=GP 掉落乘算与商品计价折价的
|
||||
# tree_rules 成员判定(编排器/关卡 runner 读 enc 键集),不入事件面。数值全 provisional 归 M4。
|
||||
for key_v in tree_rules:
|
||||
var rk := String(key_v)
|
||||
match rk:
|
||||
"posture_first_strike":
|
||||
_push_trigger("tree", rk, {"on": "round_start",
|
||||
"when": [{"fact": "round_index", "op": "eq", "value": 1}],
|
||||
"when": [{"fact": "battle_index", "op": "eq", "value": 1},
|
||||
{"fact": "round_index", "op": "eq", "value": 1}],
|
||||
"do": {"kind": "grant_ap", "mode": "queue", "value": 1}}, {})
|
||||
"posture_bulwark":
|
||||
_push_trigger("tree", rk, {"on": "damage_taken",
|
||||
"when": [{"fact": "battle_index", "op": "eq", "value": 1},
|
||||
{"fact": "round_index", "op": "eq", "value": 1}],
|
||||
"do": {"kind": "shield", "value": 1}}, {})
|
||||
"sense_sharp":
|
||||
# 锋=踩中危险格伤害减半·每关限次——mine_hit 挂点现成(damage_mult 收集→halve_pct,
|
||||
# 与惊巢反制同路径;伤害在结算战斗段末兑付,探索阶段零改动)
|
||||
_push_trigger("tree", rk, {"on": "mine_hit",
|
||||
"when": [{"fact": "counter", "id": "tree_sense_sharp_used", "op": "lt", "value": 2}],
|
||||
"do": {"kind": "damage_mult", "value": 50}}, {})
|
||||
_push_trigger("tree", rk, {"on": "mine_hit",
|
||||
"when": [{"fact": "counter", "id": "tree_sense_sharp_used", "op": "lt", "value": 2}],
|
||||
"do": {"kind": "counter_add", "counter_id": "tree_sense_sharp_used",
|
||||
"value": 1, "domain": "adventure"}}, {})
|
||||
"sense_guard":
|
||||
# 守=标错返还行动点·每关限次(契约 12 失误补偿——flag_missed 由 _judge_flag 去重闸后
|
||||
# 发射;queue=下一轮开轮兑现,末轮判出无下一轮=合法作废自动成立)
|
||||
_push_trigger("tree", rk, {"on": "flag_missed",
|
||||
"when": [{"fact": "counter", "id": "tree_sense_guard_used", "op": "lt", "value": 2}],
|
||||
"do": {"kind": "grant_ap", "mode": "queue", "value": 1}}, {})
|
||||
_push_trigger("tree", rk, {"on": "flag_missed",
|
||||
"when": [{"fact": "counter", "id": "tree_sense_guard_used", "op": "lt", "value": 2}],
|
||||
"do": {"kind": "counter_add", "counter_id": "tree_sense_guard_used",
|
||||
"value": 1, "domain": "adventure"}}, {})
|
||||
# 计数器账本重建(T6——battle 域清零照旧;adventure 域由 counters_seed 回灌〔rebuild 后调用〕)
|
||||
_counters = {}
|
||||
|
||||
|
||||
@@ -483,6 +483,7 @@ func _start_battle(node: Dictionary) -> bool:
|
||||
"attune_charges": _attune_charges,
|
||||
"feats": _feats,
|
||||
"tree_rules": _tree_rules,
|
||||
"battle_index": _battle_index(),
|
||||
"bonus_ref": bonus,
|
||||
"adventure_counters": _adventure_counters_ref,
|
||||
"voucher_rap": _voucher_rap,
|
||||
@@ -501,6 +502,17 @@ func _forward_step_row(row: Dictionary) -> void:
|
||||
step_row.emit(row)
|
||||
|
||||
|
||||
# battle_index=第几场战斗(关卡域 1 基序——visited 已含当前节点〔_enter_node :437 先追加后启动〕;
|
||||
# 按已到节点 type=battle 计数,与图形态 node_depth/单链数组下标两编号空间解耦〔混编碰撞免疫〕;
|
||||
# 开局姿态组「每关首轮窗」条件面——迭代 21 T3)
|
||||
func _battle_index() -> int:
|
||||
var n := 0
|
||||
for v in visited:
|
||||
if String((v as Dictionary).get("type", "")) == "battle":
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
func _forward_round_started(ri: int) -> void:
|
||||
round_started.emit(ri)
|
||||
|
||||
@@ -724,9 +736,20 @@ func shop_products() -> Array:
|
||||
|
||||
|
||||
# 现价 = base_price + price_level_bonus(level_index) + price_increment × 该商品已购档数
|
||||
# (散折价随批:economy_spend 激活时商品标价 ×80%——见 _spend_discounted)
|
||||
func current_price(prow: Dictionary) -> int:
|
||||
return int(prow.get("base_price", 0)) + db.shop_price_level_bonus(int(level_row.level_index)) \
|
||||
+ int(prow.get("price_increment", 0)) * purchased_tier(String(prow.get("product_id", "")))
|
||||
return _spend_discounted(int(prow.get("base_price", 0)) + db.shop_price_level_bonus(int(level_row.level_index)) \
|
||||
+ int(prow.get("price_increment", 0)) * purchased_tier(String(prow.get("product_id", ""))))
|
||||
|
||||
|
||||
# 散折价单点(迭代 21 T3——财富之道·散 economy_spend 激活时商品购买价 ×80% provisional,
|
||||
# floor 取整偏向玩家、下限 1 防 1 元商品折成 0;范围=商品标价定义边界〔legacy 商品面 current_price
|
||||
# + 池货架面 pool_price 同折〕——商店刷新价 shop_refresh_cost 与三选一付费刷新不折〔卡片§11②裁决〕;
|
||||
# 折率归 M4 数值批)
|
||||
func _spend_discounted(price: int) -> int:
|
||||
if _tree_rules.has("economy_spend"):
|
||||
return maxi(1, int(floor(float(price) * 0.8)))
|
||||
return price
|
||||
|
||||
|
||||
func purchased_tier(product_id: String) -> int:
|
||||
@@ -842,12 +865,12 @@ func pool_price(kind: String) -> int:
|
||||
var row: Dictionary = db.shop_pool_row(kind)
|
||||
if row.is_empty():
|
||||
if kind == "relic":
|
||||
return int(ceil(15.0 * pow(db.shop_price_b(), float(maxi(0, int(level_row.level_index) - 1))) * pow(db.shop_price_a(), float(_shop_visit_buys))))
|
||||
return _spend_discounted(int(ceil(15.0 * pow(db.shop_price_b(), float(maxi(0, int(level_row.level_index) - 1))) * pow(db.shop_price_a(), float(_shop_visit_buys)))))
|
||||
return 999999
|
||||
var base := float(maxi(1, int(row.get("base_price", 1))))
|
||||
var price_f: float = base * pow(db.shop_price_b(), float(maxi(0, int(level_row.level_index) - 1))) \
|
||||
* pow(db.shop_price_a(), float(_shop_visit_buys))
|
||||
return int(ceil(price_f))
|
||||
return _spend_discounted(int(ceil(price_f)))
|
||||
|
||||
|
||||
# 货架只读投影(呈现层数据源——kind/显示名/件值/现价/库存余量齐备,UI 零复算)。
|
||||
|
||||
+67
-116
@@ -29,41 +29,10 @@ func keystone_balance() -> int:
|
||||
return save.keystone_balance()
|
||||
|
||||
|
||||
# ---- 属性锻造(钥石直购、可重复升级——几何递增定价)----
|
||||
|
||||
func forge_level(attr: String) -> int:
|
||||
return save.forge_level(attr)
|
||||
|
||||
|
||||
# 每级强化 flat 值 × 当前级(注入面的算料——投值语言锁定 flat 整数)
|
||||
func forge_flat(attr: String) -> int:
|
||||
return forge_level(attr) * int(db.meta_forge_row(attr).get("flat_value", 0))
|
||||
|
||||
|
||||
# 下一级价格:tier = 当前级,迭代 floor(p × ratio/100) 恰 tier 次(tier 0 = base_price)
|
||||
func forge_price(attr: String) -> int:
|
||||
var row: Dictionary = db.meta_forge_row(attr)
|
||||
if row.is_empty():
|
||||
return 0
|
||||
var p := int(row.get("base_price", 0))
|
||||
var ratio := int(row.get("price_ratio_pct", 100))
|
||||
for _i in forge_level(attr):
|
||||
p = int(floor(float(p) * float(ratio) / 100.0))
|
||||
return p
|
||||
|
||||
|
||||
# 购买:余额不足 / attr 出域 false 零副作用;成功 = 消费 + 升级 + 写盘
|
||||
func buy_forge(attr: String) -> bool:
|
||||
if db.meta_forge_row(attr).is_empty():
|
||||
return false
|
||||
if not save.spend_keystone(forge_price(attr)):
|
||||
return false
|
||||
if not save.set_forge_level(attr, forge_level(attr) + 1):
|
||||
return false
|
||||
return save.write_to_disk()
|
||||
|
||||
|
||||
# ---- 道具工坊(首版全走通关次数渠道——钥石直购价随定价批后补,最终双渠道并存)----
|
||||
# 〔属性锻造柜台退役——迭代 21 T4/T5:buy_forge/forge_flat/forge_level/forge_price 与 meta_forge
|
||||
# 表族随攻防所有权切分删除;生命唯一入口=树脊柱淬炼、攻/AC 唯一入口=锻造工坊路线(契约 9);
|
||||
# 注入面 hero_override 换树淬炼 flat+锻造路线加值位(预留 0)公式。〕
|
||||
|
||||
# 解锁判定量单点(可替换轴——2026-09-21 用户拍板:解锁条件须抽象、可替换、可扩展,禁与渠道实现耦合)。
|
||||
# 契约:渠道由 meta.tres workshop_items.channel 声明(缺省 total_clears,词表 MetaTable.WORKSHOP_CHANNELS
|
||||
@@ -142,15 +111,19 @@ func settle_adventure(summary: Dictionary) -> Dictionary:
|
||||
}
|
||||
|
||||
|
||||
# ---- 跨局注入(冒险开始参数面——字段清单随柜台扩展,现 = 属性锻造三档+树四线之三条)----
|
||||
# ---- 跨局注入(冒险开始参数面)----
|
||||
# 迭代 21 T4 注入公式换代:基础值 + 树生命/命中淬炼 flat(命中线在账未注入——契约 6,消费面归
|
||||
# 数值批)+ 锻造路线加值位(本批预留 0——锻造工坊实装批按当前武器/甲路线等级填充)+ 局内加算区
|
||||
# (bonus 区域不经本面)。旧属性柜台 forge_flat 项退役移除(V7/V12);攻/AC 树来源随所有权切分
|
||||
# 砍除(契约 9——树零攻防来源,现表零 atk/armor_class 行,tree_stat_flat 二轴恒 0 保留通式)。
|
||||
# 全零 = 白板合法态(无解锁不得改变任何局内规则);override 禁写表(表运行期只读)。
|
||||
# 迭代 12 T8:树四线 flat 并入(脊柱 stat+锻造脉+彩蛋——同一覆盖值通道;hit 线消费面=
|
||||
# 命中折扣档归数值批,本批不入 override 三字段通道——披露)。
|
||||
const FORGE_ROUTE_BONUS := {"atk": 0, "max_hp": 0, "armor_class": 0} # 锻造路线加值位(预留 0——锻造工坊实装批填充)
|
||||
|
||||
func hero_override() -> Dictionary:
|
||||
return {
|
||||
"atk": forge_flat("atk") + tree_stat_flat("atk"),
|
||||
"max_hp": forge_flat("max_hp") + tree_stat_flat("max_hp"),
|
||||
"armor_class": forge_flat("armor_class") + tree_stat_flat("armor_class"),
|
||||
"atk": int(FORGE_ROUTE_BONUS.get("atk", 0)) + tree_stat_flat("atk"),
|
||||
"max_hp": int(FORGE_ROUTE_BONUS.get("max_hp", 0)) + tree_stat_flat("max_hp"),
|
||||
"armor_class": int(FORGE_ROUTE_BONUS.get("armor_class", 0)) + tree_stat_flat("armor_class"),
|
||||
}
|
||||
|
||||
|
||||
@@ -164,29 +137,37 @@ func tree_spent_total() -> int:
|
||||
return total
|
||||
|
||||
|
||||
# 节点可购校验(row-gating + 显式前置 + 币 + cap;free 节点=迁移/彩蛋不经购买)
|
||||
# 节点可购校验(行门槛 + 显式前置 + 币 + cap;free 节点=钥匙不经购买,彩蛋=觉醒不经购买)
|
||||
func tree_can_buy(node_id: String) -> bool:
|
||||
var row: Dictionary = db.tree_node(node_id)
|
||||
if row.is_empty():
|
||||
return false
|
||||
# 彩蛋不经购买(觉醒唯一入口=_check_easter_egg 特化判定——V8 反例:手动购亮=红)
|
||||
if String(row.get("kind", "")) == "easter":
|
||||
return false
|
||||
var lvl := save.tree_level(node_id)
|
||||
if lvl >= int(row.get("cap", 1)):
|
||||
return false
|
||||
var row_num := int(row.get("row", 0))
|
||||
if row_num > 0 and tree_spent_total() < db.meta_tree.row_gate(row_num):
|
||||
# 刷新升级节点:cap 主语=Y 账本(单一事实源——树上入口与装配段同 cap 同价,防双账漂移)
|
||||
if node_id == "ks_refresh" and db.meta_attune_refresh_row(save.attune_refresh_level() + 1).is_empty():
|
||||
return false
|
||||
# 行门槛(一线一节点:脊柱节点第 N 级需全树总投入 ≥4×(N−1)——迭代 21 主语平移,row 字段退役)
|
||||
if String(row.get("vein", "")) == "spine" and String(row.get("kind", "")) == "stat":
|
||||
if tree_spent_total() < db.meta_tree.level_gate(lvl + 1):
|
||||
return false
|
||||
for req in (row.get("requires", []) as Array):
|
||||
if save.tree_level(String(req)) < 1:
|
||||
return false
|
||||
var fg := String(row.get("fork_group", ""))
|
||||
if fg != "" and save.fork_side(fg) != "":
|
||||
return false # 同组已激活一侧——互斥(切另侧走 tree_toggle_fork 免费切换)
|
||||
return true
|
||||
|
||||
|
||||
# 节点购买(营地树 UI 唯一写口):树全钥石单币(2026-10-07 用户裁甲——原 T8 双币骨架披露销账;
|
||||
# 表内 currency 字段保留为呈现元数据)。free=迁移点亮/彩蛋觉醒零币(price 0,零额消费无副作用)。
|
||||
# 节点购买(营地树 UI 唯一写口):树全钥石单币(2026-10-07 用户裁甲)。free=工作间钥匙零币点亮
|
||||
# (price 0,零额消费无副作用);彩蛋不经购买(can_buy 已拒——觉醒唯一入口=_check_easter_egg)。
|
||||
# 刷新升级节点走同账路由(Q1 裁乙——树上+秘宝匣装配段双入口写同一 attune_refresh_level 账本,
|
||||
# 价格/上限单一事实源=Y 行表值 keystone_cost;V15)。
|
||||
func tree_buy(node_id: String) -> bool:
|
||||
if node_id == "ks_refresh":
|
||||
return buy_attune_refresh()
|
||||
var row: Dictionary = db.tree_node(node_id)
|
||||
if row.is_empty() or not tree_can_buy(node_id):
|
||||
return false
|
||||
@@ -198,21 +179,19 @@ func tree_buy(node_id: String) -> bool:
|
||||
return save.write_to_disk()
|
||||
|
||||
|
||||
# Fork 激活/切换(营地树 UI——同组换侧零币=免费切换裁定;未激活组首次激活=消费购买面 tree_buy
|
||||
# 已购该侧后此处激活;同侧重复=零操作)
|
||||
# Fork 切换(营地树 UI——一节点双态:购买解锁组默认 A 态,换态走本口零币;同态重复=零操作;
|
||||
# 未解锁组切换=拒绝——解锁唯一入口=购买面 tree_buy_fork)
|
||||
func tree_toggle_fork(group: String, side: String) -> bool:
|
||||
if not db.meta_tree.FORK_GROUPS.has(group) or not (side == "a" or side == "b"):
|
||||
return false
|
||||
var cur := save.fork_side(group)
|
||||
if cur == side:
|
||||
return false # 同侧零操作
|
||||
if cur == "":
|
||||
return false # 未激活组走购买面(tree_buy 激活侧节点后由激活写入——见下)
|
||||
if cur == "" or cur == side:
|
||||
return false # 未解锁走购买面;同态零操作
|
||||
save.set_fork_side(group, side)
|
||||
return save.write_to_disk()
|
||||
|
||||
|
||||
# Fork 侧节点购买入口(购节点即激活该侧——组内互斥由 can_buy 拦截另侧)
|
||||
# Fork 节点购买入口(一节点双态——购=解锁开关并置默认 A 态;重复购买由 cap 经 can_buy 拦截)
|
||||
func tree_buy_fork(node_id: String) -> bool:
|
||||
var row: Dictionary = db.tree_node(node_id)
|
||||
if row.is_empty() or String(row.get("kind", "")) != "fork":
|
||||
@@ -223,7 +202,7 @@ func tree_buy_fork(node_id: String) -> bool:
|
||||
if not save.spend_keystone(price):
|
||||
return false
|
||||
save.set_tree_level(node_id, 1)
|
||||
save.set_fork_side(String(row.get("fork_group", "")), String(row.get("fork_side", "")))
|
||||
save.set_fork_side(String(row.get("fork_group", "")), "a") # 解锁默认 A 态
|
||||
return save.write_to_disk()
|
||||
|
||||
|
||||
@@ -244,10 +223,11 @@ func tree_rule_keys() -> Array:
|
||||
for row in db.tree_rows():
|
||||
if String(row.get("kind", "")) != "fork":
|
||||
continue
|
||||
var fg := String(row.get("fork_group", ""))
|
||||
if String(sides.get(fg, "")) != String(row.get("fork_side", "")):
|
||||
continue
|
||||
for k in (row.get("effects", {}) as Dictionary).keys():
|
||||
var active := String(sides.get(String(row.get("fork_group", "")), ""))
|
||||
if active == "":
|
||||
continue # 未解锁组零规则输出
|
||||
var fx: Dictionary = row.get("effects_" + active, {}) as Dictionary
|
||||
for k in fx.keys():
|
||||
if not out.has(String(k)):
|
||||
out.append(String(k))
|
||||
return out
|
||||
@@ -265,9 +245,9 @@ func tree_camp_rows() -> Array:
|
||||
"vein": String(row.get("vein", "")),
|
||||
"kind": String(row.get("kind", "")),
|
||||
"cap": int(row.get("cap", 1)),
|
||||
"level": save.tree_level(String(row.get("node_id", ""))),
|
||||
"level": save.attune_refresh_level() if String(row.get("node_id", "")) == "ks_refresh" else save.tree_level(String(row.get("node_id", ""))), # 刷新升级行主语=Y 账本(同账路由 V15)
|
||||
"price": int(row.get("price", 0)),
|
||||
"currency": String(row.get("currency", "gp")),
|
||||
"currency": String(row.get("currency", "keystone")),
|
||||
"can_buy": tree_can_buy(String(row.get("node_id", ""))),
|
||||
})
|
||||
return out
|
||||
@@ -276,72 +256,43 @@ func tree_camp_rows() -> Array:
|
||||
func tree_fork_groups() -> Array:
|
||||
var out: Array = []
|
||||
var sides: Dictionary = save.fork_sides()
|
||||
for fg in db.meta_tree.FORK_GROUPS:
|
||||
var group_rows: Array = []
|
||||
for row in db.tree_rows():
|
||||
if String(row.get("fork_group", "")) == String(fg):
|
||||
group_rows.append(row)
|
||||
if group_rows.is_empty():
|
||||
for row in db.tree_rows():
|
||||
if String(row.get("kind", "")) != "fork":
|
||||
continue
|
||||
var sides_out: Array = []
|
||||
for row in group_rows:
|
||||
sides_out.append({
|
||||
"side": String(row.get("fork_side", "")),
|
||||
"node_id": String(row.get("node_id", "")),
|
||||
"display_name": String(row.get("display_name", "")),
|
||||
"owned": save.tree_level(String(row.get("node_id", ""))) > 0,
|
||||
})
|
||||
var fg := String(row.get("fork_group", ""))
|
||||
out.append({
|
||||
"group": String(fg),
|
||||
"display_name": String(group_rows[0].get("display_name", String(fg))).split("·")[0],
|
||||
"active_side": String(sides.get(String(fg), "")),
|
||||
"sides": sides_out,
|
||||
"group": fg,
|
||||
"node_id": String(row.get("node_id", "")),
|
||||
"display_name": String(row.get("display_name", "")),
|
||||
"active_side": String(sides.get(fg, "")),
|
||||
"owned": save.tree_level(String(row.get("node_id", ""))) > 0,
|
||||
"can_buy": tree_can_buy(String(row.get("node_id", ""))),
|
||||
"price": int(row.get("price", 0)),
|
||||
"sides": [
|
||||
{"side": "a", "display_name": String(row.get("name_a", "")), "effects": row.get("effects_a", {}) as Dictionary},
|
||||
{"side": "b", "display_name": String(row.get("name_b", "")), "effects": row.get("effects_b", {}) as Dictionary},
|
||||
],
|
||||
})
|
||||
return out
|
||||
|
||||
|
||||
# 彩蛋觉醒检查(锻造脉三节点全满→easter_ley 免费点亮——发现率监控 T10 呈报)
|
||||
# 彩蛋觉醒检查(特化判定——生命/命中两线拉满 且 同调位 1/2/3 全解锁 → easter_ley 免费点亮;
|
||||
# 迭代 21 T2 换代:不再依赖 requires 点亮形态〔R1 裁撤〕,发现率监控呈报面沿袭)
|
||||
func _check_easter_egg() -> void:
|
||||
var row: Dictionary = db.tree_node("easter_ley")
|
||||
if row.is_empty() or save.tree_level("easter_ley") > 0:
|
||||
if save.tree_level("easter_ley") > 0:
|
||||
return
|
||||
for req in (row.get("requires", []) as Array):
|
||||
var req_row: Dictionary = db.tree_node(String(req))
|
||||
if save.tree_level(String(req)) < int(req_row.get("cap", 1)):
|
||||
for nid in ["sp_hp", "sp_hit"]:
|
||||
var row: Dictionary = db.tree_node(nid)
|
||||
if row.is_empty() or save.tree_level(nid) < int(row.get("cap", 1)):
|
||||
return
|
||||
for i in range(1, 4):
|
||||
if save.tree_level("att_socket%d" % i) < 1:
|
||||
return
|
||||
save.set_tree_level("easter_ley", 1)
|
||||
|
||||
|
||||
# 三体系迁移(迭代 12 T9——v4→v5 旧域只读→树域点亮;幂等=重复迁移零变化):
|
||||
# forge_levels 等级→锻造脉节点树级原样映射(钥石付价不变零亏损——「宁多勿少」以零亏损满足);
|
||||
# item_unlocks 任一真→ks_workshop 点亮;同调槽位解锁态→socket 点亮(谓词表 Slots 求值≥i)。
|
||||
func migrate_tree() -> Dictionary:
|
||||
var migrated := {"forge": 0, "unlock": 0, "socket": 0}
|
||||
for pair in [["forge_atk", "atk"], ["forge_hp", "max_hp"], ["forge_ac", "armor_class"]]:
|
||||
var lvl := save.forge_level(String(pair[1]))
|
||||
if lvl > 0 and save.tree_level(String(pair[0])) < lvl:
|
||||
save.set_tree_level(String(pair[0]), lvl)
|
||||
migrated["forge"] += 1
|
||||
var any_unlock := false
|
||||
for iid in db.treasure_live_ids().keys():
|
||||
if save.item_unlocked(String(iid)):
|
||||
any_unlock = true
|
||||
break
|
||||
if not any_unlock:
|
||||
for row in db.treasure_attunement_rows():
|
||||
if save.item_unlocked(String(row.get("item_id", ""))):
|
||||
any_unlock = true
|
||||
break
|
||||
if any_unlock and save.tree_level("ks_workshop") < 1:
|
||||
save.set_tree_level("ks_workshop", 1)
|
||||
migrated["unlock"] += 1
|
||||
var slots := Attunement.slots(db, save)
|
||||
for i in range(1, slots + 1):
|
||||
if save.tree_level("att_socket%d" % i) < 1:
|
||||
save.set_tree_level("att_socket%d" % i, 1)
|
||||
migrated["socket"] += 1
|
||||
save.write_to_disk()
|
||||
return migrated
|
||||
# 三体系迁移函数已删除(迭代 21 T5——旧档破坏性废弃〔Q3 用户裁:开发阶段无迁移〕;换代前结构
|
||||
# 由 SaveManager._normalize 的 tree_struct 标记检测废弃重置,禁迁移禁折算)。
|
||||
|
||||
|
||||
# ---- 同调位账本(迭代 11 第①批 T4——装配关系/同调位求值/Y 刷新升级;设计权威 proposal §2 + 需求卡片 P1 甲)----
|
||||
|
||||
+30
-35
@@ -8,12 +8,19 @@
|
||||
# RngService,可独立实例化独立验证〔存档序列化.md 验收标准〕)。
|
||||
#
|
||||
# 域清单与 owner 分工(内容 owner 定口径,本类照单承载、不替任何人定义进度):
|
||||
# keystone_balance / forge_levels / item_unlocks / level_clear_counts / boss_first_kill
|
||||
# —— 内容 owner = 元进度.md(营地三柜台:钥石余额/属性锻造/道具工坊/通关次数/首杀标记)
|
||||
# keystone_balance / item_unlocks / level_clear_counts / boss_first_kill
|
||||
# —— 内容 owner = 元进度.md(钥石余额/道具工坊/通关次数/首杀标记;属性锻造柜台已退役
|
||||
# 〔迭代 21 攻防所有权切分——forge_levels 域随批清退,生命唯一入口=树脊柱、攻/AC 唯一入口
|
||||
# =锻造工坊路线,见 meta_progression.gd 注入面〕)
|
||||
# boss_kill_count / attunement_equipped / attune_refresh_level
|
||||
# —— 内容 owner = 秘宝.md(同调位账本,M-4 迭代 11 第①批 T2):Boss 击杀总数〔含重复〕=
|
||||
# attunement_growth 谓词表计数源;装配关系 = 局外进度事实(元进度域同族——非契约 2 反面清单的
|
||||
# 「局内道具库存」,后者=冒险进行中瞬态);attune_refresh_level = Y 刷新升级购买等级
|
||||
# 生涯页终身计数与迭代 22 解锁条件消费面〔原 attunement_growth 谓词表计数源职责随迭代 21
|
||||
# T4 槽位收编退役——槽位数量=树同调位节点点亮数〕;装配关系 = 局外进度事实(元进度域同族
|
||||
# ——非契约 2 反面清单的「局内道具库存」,后者=冒险进行中瞬态);attune_refresh_level = Y 刷新
|
||||
# 升级购买等级
|
||||
# tree_nodes / fork_sides / tree_struct —— 内容 owner = 天赋树.md(迭代 12 T9 建域、迭代 21 T5
|
||||
# 换代承载:tree_nodes=node_id→已购等级;fork_sides=fork_group→激活态;tree_struct=树结构
|
||||
# 版本标记〔缺失=换代前结构→加载时树域破坏性废弃重置——开发阶段无迁移,Q3 用户裁〕)
|
||||
# breakpoint.attunement_charges(主动件充能余量)——每冒险重置语义的断点瞬态附随件,内容装配归
|
||||
# AdventureRunner(随 T6 接线入断点),本类照单承载;契约 2 域界注记同断点域豁免先例
|
||||
# recent_summaries(冒险摘要五件事副本)—— 内容 owner = 冒险结构.md(摘要五件事口径)
|
||||
@@ -42,7 +49,8 @@ extends RefCounted
|
||||
const SAVE_FORMAT_VERSION := 6 # registry schema_version:v6=生涯终身计数域(迭代 18 卡④——meta 两新域 adventure_total〔冒险总局数,开局即计含放弃与失败〕/adventure_cleared〔通关局数,三关全通才计 1——判定字段 summary.cleared〕,旧档缺键由 _normalize 按默认补零,收束自增唯一入账=apply_adventure_summary);v5=天赋树域(迭代 12 T9——meta 三新域 tree_nodes〔node_id→已购等级〕/fork_sides〔fork_group→a|b〕/migration_note〔三体系迁移补偿注记〕——M-4 迭代 12 批次二;需求卡片「v3→v4」系同调批未落时行文,v4 已被同调批占用顺延 v5 披露);v4=同调批扩域(meta 三新域 boss_kill_count/attunement_equipped/attune_refresh_level——M-4 迭代 11 第①批 T2;断点充能余量随 T6 接线,旧档缺键 = 合法空态);v3=断点附随件扩列+摘要 keystone_drops(M-3 迭代 10 奖励重构);v2=断点域出生(M-3 迭代 8 卡①);v1=首版;高版本拒读 owner=存档序列化
|
||||
const SUMMARY_CAP := 10 # 最近冒险摘要保留条数(最新在前)
|
||||
|
||||
const FORGE_ATTRS := ["max_hp", "atk", "armor_class"] # 属性锻造三档合法键(元进度.md 属性锻造柜台)
|
||||
const TREE_STRUCT_VERSION := 2 # 树结构版本标记(迭代 21 T5——12 节点 v0.8 结构;加载检测缺失
|
||||
# 或更低 = 换代前结构 → 树域破坏性废弃重置〔契约 4:禁迁移禁折算〕)
|
||||
|
||||
var _path: String # 存档文件路径(默认 user://save.json;冒烟注入隔离路径)
|
||||
var _read_only := false # 高版本拒读置位——write_to_disk 一律拒绝(契约 4 禁写)
|
||||
@@ -85,12 +93,9 @@ func _migrate_v3_v4(data: Dictionary) -> Dictionary:
|
||||
return data
|
||||
|
||||
|
||||
# v4→v5:天赋树域+三体系迁移(迭代 12 T9——R1 甲融合案;差额补偿原则=宁多勿少):
|
||||
# ①tree_nodes/fork_sides 两新域(缺键默认补齐);②forge_levels 已购等级→锻造脉节点等效点亮
|
||||
# (原样映射零亏损——钥石付价不变);③item_unlocks 已解锁→ks_workshop Unlock 节点点亮;
|
||||
# ④attunement 槽位解锁态→同调 socket 点亮(已拥有道具/装配关系不动——装配交互不变裁定);
|
||||
# ⑤迁移幂等由 MetaProgression.migrate_tree 侧承载(重复迁移零变化——T10 断言面)。
|
||||
# 旧三域只读保留一版周期(forge_levels/item_unlocks 读取面维持——回滚窗口)。
|
||||
# v4→v5:天赋树域(迭代 12 T9 建域——meta 三新域 tree_nodes/fork_sides/migration_note,缺键默认
|
||||
# 补齐;迭代 21 T5 换代:原「三体系迁移」映射体随 migrate_tree 一并删除——开发阶段破坏性废弃
|
||||
# 〔Q3 用户裁〕,换代前结构由 _normalize 的 tree_struct 标记检测废弃重置,本函数只剩缺键补齐)。
|
||||
func _migrate_v4_v5(data: Dictionary) -> Dictionary:
|
||||
var meta: Dictionary = data.get("meta", {}) as Dictionary
|
||||
if not meta.has("tree_nodes"):
|
||||
@@ -116,15 +121,15 @@ func _default_data() -> Dictionary[String, Variant]:
|
||||
"version": SAVE_FORMAT_VERSION,
|
||||
"meta": {
|
||||
"keystone_balance": 0,
|
||||
"forge_levels": {"max_hp": 0, "atk": 0, "armor_class": 0},
|
||||
"item_unlocks": {},
|
||||
"level_clear_counts": {"1": 0, "2": 0, "3": 0},
|
||||
"boss_first_kill": {},
|
||||
"boss_kill_count": 0,
|
||||
"attunement_equipped": [],
|
||||
"attune_refresh_level": 0,
|
||||
"tree_nodes": {}, # 天赋树已购等级(node_id→int——迭代 12 T9 树域)
|
||||
"fork_sides": {}, # A/B 激活侧(fork_group→"a"|"b"——营地免费切换承载)
|
||||
"tree_nodes": {}, # 天赋树已购等级(node_id→int——迭代 12 T9 建域)
|
||||
"fork_sides": {}, # Fork 激活态(fork_group→"a"|"b"——一节点双态切换承载,迭代 21 收编语义)
|
||||
"tree_struct": TREE_STRUCT_VERSION, # 树结构版本标记(迭代 21 T5——缺失=换代前结构,加载废弃重置)
|
||||
"recent_summaries": [],
|
||||
"first_adventure_done": false,
|
||||
"adventure_total": 0, # 生涯终身计数·冒险总局数(迭代 18 卡④——开局即计含放弃与失败,收束唯一入账)
|
||||
@@ -229,18 +234,6 @@ func spend_keystone(n: int) -> bool:
|
||||
return true
|
||||
|
||||
|
||||
func forge_level(attr: String) -> int:
|
||||
return int(_data["meta"]["forge_levels"].get(attr, 0))
|
||||
|
||||
|
||||
func set_forge_level(attr: String, n: int) -> bool:
|
||||
if not FORGE_ATTRS.has(attr):
|
||||
push_error("[存档序列化] 属性锻造键出域拒绝:%s(合法键 %s)" % [attr, str(FORGE_ATTRS)])
|
||||
return false
|
||||
_data["meta"]["forge_levels"][attr] = n
|
||||
return true
|
||||
|
||||
|
||||
func item_unlocked(item_id: String) -> bool:
|
||||
return _data["meta"]["item_unlocks"].has(item_id)
|
||||
|
||||
@@ -403,22 +396,24 @@ func _corrupt_fallback(reason: String) -> bool:
|
||||
return true
|
||||
|
||||
|
||||
# 迁移后/低版本档字段补齐(改版不弃档:缺新域按默认补齐,既有值保留)
|
||||
# 迁移后/低版本档字段补齐 + 退役域清退(改版不弃档:缺新域按默认补齐,既有值保留;
|
||||
# 退役域按默认模型键集白名单收口——换代退役键〔forge_levels/migration_note 等树域旧件〕不流入
|
||||
# 内存不回写盘,开发阶段破坏性废弃口径〔迭代 21 T5,Q3 用户裁:禁迁移禁折算〕)。
|
||||
# 树结构检查先于缺键补齐:换代前结构(tree_struct 标记缺失或更低)→ 树域废弃重置——
|
||||
# tree_nodes/fork_sides 清空、钥石余额等他域零触碰(V16 断言面)。
|
||||
func _normalize(data: Dictionary) -> Dictionary[String, Variant]:
|
||||
var merged := _default_data()
|
||||
var def_meta: Dictionary = merged["meta"] # 补齐源引用(先于 data 覆盖取出——data 自带 meta 时 merged["meta"] 被换走,此引用仍指默认值)
|
||||
for key in data:
|
||||
merged[key] = data[key]
|
||||
var meta: Dictionary = merged.get("meta", {})
|
||||
if int(meta.get("tree_struct", 0)) < TREE_STRUCT_VERSION:
|
||||
meta["tree_nodes"] = {}
|
||||
meta["fork_sides"] = {}
|
||||
var clean_meta := {}
|
||||
for key in def_meta:
|
||||
if not meta.has(key):
|
||||
meta[key] = def_meta[key]
|
||||
var forge: Dictionary = meta.get("forge_levels", {})
|
||||
for sk in def_meta["forge_levels"]:
|
||||
if not forge.has(sk):
|
||||
forge[sk] = def_meta["forge_levels"][sk]
|
||||
meta["forge_levels"] = forge
|
||||
merged["meta"] = meta
|
||||
clean_meta[key] = meta.get(key, def_meta[key])
|
||||
merged["meta"] = clean_meta
|
||||
return merged
|
||||
|
||||
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# Validation Question: 同调位增长是否由「通关次数 + Boss 击杀数」经数据驱动的里程碑谓词表实时求值,
|
||||
# 试玩后调增长曲线 = 改表不动码(proposal-magic-item-attunement-2026-09-22 §2.1 用户点名要求)
|
||||
# Date: 2026-10-03(迭代 11 第①批 秘宝同调实施批 T1——需求卡片 v1.1 §六 T1;设计权威底稿 proposal §2)
|
||||
# 数据配置 AttunementGrowthTable schema(秘宝 回填 owner)。
|
||||
# 单行全局常量表(slot_cap + milestones 行族——AdventureTable 单行全局先例,Rule 4 豁免)。
|
||||
# 行 = Dictionary:{id: String 唯一, grant: int, requires: [{key: String, threshold: int}]}——
|
||||
# requires 为 (计数键, 阈值) 对的 AND 列表,全部满足即授予 grant 槽;
|
||||
# 同调位读数唯一口径 = Σ已达成 grant clamp slot_cap(slots())。
|
||||
# 承重契约(需求卡片 §三 7 + 底稿 §2.1):
|
||||
# 计数键封闭枚举(COUNTER_KEYS——预留可插拔位 COUNTER_KEYS_RESERVED 本期不实现、加行即扩展不破坏
|
||||
# schema);阈值 [1,999];Σgrant ≤ slot_cap(超出部分永不可达,禁);数值全 provisional 归
|
||||
# 难度曲线与Boss——本表只锁形态,试玩调平衡的全部动作落在表上(改阈值/换计数键/slot_cap 加行)。
|
||||
# OR/复合需求出现时升级谓词形态走需求卡片修订(底稿 §2.1 同款约束)。
|
||||
class_name AttunementGrowthTable
|
||||
extends Resource
|
||||
|
||||
@export var slot_cap: int = 3
|
||||
@export var milestones: Array = [] # Array[Dictionary] 键 id/grant/requires
|
||||
|
||||
# 计数键封闭枚举(底稿 §2.1——save_file 逐域记账的键词表;T2 落 boss_kill_count 记账)
|
||||
const COUNTER_KEYS := ["clear_count", "boss_kill_count"]
|
||||
# 预留可插拔位(本期不实现,schema 枚举不收——底稿 §2.1 明列四件)
|
||||
const COUNTER_KEYS_RESERVED := ["boss_firstkill_count", "adventure_count", "no_damage_clear_count", "run_depth_best"]
|
||||
|
||||
|
||||
func validate() -> Array:
|
||||
var errs: Array = []
|
||||
if slot_cap < 0 or slot_cap > 9:
|
||||
errs.append({"category": "domain", "message": "slot_cap 越域 [0,9]: %d" % slot_cap})
|
||||
var seen := {}
|
||||
var grant_sum := 0
|
||||
for row in milestones:
|
||||
var mid: String = String(row.get("id", ""))
|
||||
if mid == "":
|
||||
errs.append({"category": "domain", "message": "milestone id 空"})
|
||||
continue
|
||||
if seen.has(mid):
|
||||
errs.append({"category": "dup_key", "message": "milestone id 重复: %s" % mid})
|
||||
continue
|
||||
seen[mid] = true
|
||||
var grant: int = int(row.get("grant", 0))
|
||||
if grant < 1 or grant > maxi(1, slot_cap):
|
||||
errs.append({"category": "domain", "message": "%s grant 越域 [1,slot_cap=%d]: %d" % [mid, slot_cap, grant]})
|
||||
grant_sum += maxi(0, grant)
|
||||
var reqs: Array = row.get("requires", [])
|
||||
if reqs.is_empty():
|
||||
errs.append({"category": "domain", "message": "%s requires 空(无条件授予非法——里程碑必有谓词)" % mid})
|
||||
var key_seen := {}
|
||||
for req in reqs:
|
||||
var key: String = String(req.get("key", ""))
|
||||
if not COUNTER_KEYS.has(key):
|
||||
errs.append({"category": "enum", "message": "%s 计数键出域 %s: %s" % [mid, str(COUNTER_KEYS), key]})
|
||||
if key_seen.has(key):
|
||||
errs.append({"category": "dup_key", "message": "%s 计数键重复: %s" % [mid, key]})
|
||||
key_seen[key] = true
|
||||
var th: int = int(req.get("threshold", 0))
|
||||
if th < 1 or th > 999:
|
||||
errs.append({"category": "domain", "message": "%s 阈值越域 [1,999]: %d" % [mid, th]})
|
||||
if grant_sum > slot_cap:
|
||||
errs.append({"category": "cross_field", "message": "Σgrant(%d) > slot_cap(%d)——超出部分永不可达,schema 禁" % [grant_sum, slot_cap]})
|
||||
return errs
|
||||
|
||||
|
||||
# 实时求值:Σ已达成 grant(谓词 = requires 全键达标;counters = {计数键: int},缺键按 0 计)
|
||||
func granted_slots(counters: Dictionary) -> int:
|
||||
var total := 0
|
||||
for row in milestones:
|
||||
var ok := true
|
||||
for req in row.get("requires", []):
|
||||
if int(counters.get(String(req.get("key", "")), 0)) < int(req.get("threshold", 0)):
|
||||
ok = false
|
||||
break
|
||||
if ok:
|
||||
total += maxi(0, int(row.get("grant", 0)))
|
||||
return total
|
||||
|
||||
|
||||
# 同调位读数唯一口径:Σ达成 grant clamp slot_cap(求值服务/装配覆盖层/运行期上限共读此口径)
|
||||
func slots(counters: Dictionary) -> int:
|
||||
return mini(granted_slots(counters), slot_cap)
|
||||
@@ -1 +0,0 @@
|
||||
uid://tfgsbs87xakg
|
||||
@@ -2,29 +2,28 @@
|
||||
# Validation Question: 玩家能否在营地用钥石换永久强化、且强化在下一局开局生效——存档变厚成为玩家可感的现实
|
||||
#(元进度 GDD 验收标准,本批 = 其实现批)
|
||||
# Date: 2026-09-21(元进度实现批——设计权威 design/gdd/systems/冒险循环/元进度.md 定稿 v1.2)
|
||||
# 数据配置 MetaTable schema(元进度/永久强化 行族四轴)。
|
||||
# 数据配置 MetaTable schema(元进度行族)。
|
||||
# 行族四轴(数据契约对齐 ConfigDB.meta_* 访问器——消费面一律走访问器,禁直读行集):
|
||||
# forge_products = 属性锻造商品行(键 attr:{max_hp, atk, armor_class} 恰三唯一——注入契约三字段必齐;
|
||||
# 投值语言锁定 flat 整数;几何定价 tier 级价格 = 迭代 p = floor(p × price_ratio_pct/100),tier 0 = base_price)。
|
||||
# boss_keystone = Boss 钥石两档行(level_index 连续从 1;first_kill ≥ repeat_kill——首杀全量、
|
||||
# 重复杀固定小额恒定不递减,两档数值归元进度设计批配读数)。
|
||||
# workshop_items = 道具工坊行(item_id = 秘宝六件词表封闭恰全;首版解锁渠道 = 总通关次数
|
||||
# 达标 clear_threshold——2026-09-20 排期裁决;局内效果整体归 Alpha 秘宝重设计批,本表只有解锁状态面)。
|
||||
# conversion = 区间平移换算率行(attr 与 forge 词表一致恰三;per_level_shift_pct ∈ [0,100]——
|
||||
# conversion = 区间平移换算率行(attr 与三属性轴词表一致恰三;per_level_shift_pct ∈ [0,100]——
|
||||
# registry 键 meta_progression_conversion_table;护甲行强条目:验收读数必须按解锁后参数重测)。
|
||||
# attune_refresh_upgrade = Y 刷新升级行族(迭代 11 第①批 T4——P1 甲:每冒险开局给 Y 次、冒险周期池、
|
||||
# 用完即无;level 连续从 1,等级 L → refresh_charges=Y 表值,钥石直购 keystone_cost;数值全 provisional)。
|
||||
# 〔forge_products 属性锻造行族已退役——迭代 21 T5 攻防所有权切分:生命唯一入口=树脊柱、攻/AC
|
||||
# 唯一入口=锻造工坊路线;行族、访问器与购买面随批删除,历史投值记载归 git 考古。〕
|
||||
# 行值全部 provisional 归 元进度设计批配读数(用户拍板骨架先行,先例 = GP+商店房批)。
|
||||
class_name MetaTable
|
||||
extends Resource
|
||||
|
||||
@export var forge_products: Array[Dictionary] = [] # Array[Dictionary] 键 attr
|
||||
@export var boss_keystone: Array[Dictionary] = [] # Array[Dictionary] 键 level_index
|
||||
@export var workshop_items: Array[Dictionary] = [] # Array[Dictionary] 键 item_id
|
||||
@export var conversion: Array[Dictionary] = [] # Array[Dictionary] 键 attr
|
||||
@export var attune_refresh_upgrade: Array[Dictionary] = [] # Array[Dictionary] 键 level——Y 刷新升级行族(迭代 11 第①批 T4)
|
||||
|
||||
const FORGE_ATTRS := ["max_hp", "atk", "armor_class"]
|
||||
const FORGE_ATTRS := ["max_hp", "atk", "armor_class"] # 三属性轴词表(conversion 行键域——原 forge 词表名沿用作轴名)
|
||||
const WORKSHOP_ITEM_IDS := ["probe_dust", "sapper_order", "loot_compass", "nest_pendulum", "catacomb_scroll", "blast_kit"]
|
||||
# 工坊解锁渠道词表(可替换轴——2026-09-21 用户拍板:解锁条件须抽象、可替换、可扩展。
|
||||
# 现行唯一合法值 total_clears = 总通关次数;扩新渠道 = 词表加值 + MetaProgression._workshop_progress
|
||||
@@ -34,34 +33,10 @@ const WORKSHOP_CHANNELS := ["total_clears"]
|
||||
|
||||
func validate() -> Array:
|
||||
var errs: Array[Dictionary] = []
|
||||
# 行族空护栏(五族皆为 ConfigDB 消费面——缺族即致命)
|
||||
for family in ["forge_products", "boss_keystone", "workshop_items", "conversion", "attune_refresh_upgrade"]:
|
||||
# 行族空护栏(四族皆为 ConfigDB 消费面——缺族即致命)
|
||||
for family in ["boss_keystone", "workshop_items", "conversion", "attune_refresh_upgrade"]:
|
||||
if get(family).is_empty():
|
||||
errs.append({"category": "empty_table", "message": "MetaTable 行族为空: %s" % family})
|
||||
# forge_products:attr 词表封闭 + 唯一 + 覆盖恰三(注入契约三字段必齐)+ flat/价格域 + 公比域 [100,400]
|
||||
var seen_attr := {}
|
||||
for row in forge_products:
|
||||
var attr: String = row.get("attr", "")
|
||||
if not FORGE_ATTRS.has(attr):
|
||||
errs.append({"category": "enum", "message": "forge_products attr 出域 {max_hp, atk, armor_class}: %s" % attr})
|
||||
if seen_attr.has(attr):
|
||||
errs.append({"category": "dup_key", "message": "forge_products attr 重复: %s" % attr})
|
||||
seen_attr[attr] = true
|
||||
if String(row.get("display_name", "")) == "":
|
||||
errs.append({"category": "domain", "message": "forge_products %s display_name 空" % attr})
|
||||
var flat: int = int(row.get("flat_value", 0))
|
||||
if flat < 1 or flat > 999:
|
||||
errs.append({"category": "domain", "message": "forge_products %s flat_value 越域 [1,999]: %d" % [attr, flat]})
|
||||
var base: int = int(row.get("base_price", 0))
|
||||
if base < 1 or base > 999:
|
||||
errs.append({"category": "domain", "message": "forge_products %s base_price 越域 [1,999]: %d" % [attr, base]})
|
||||
var ratio: int = int(row.get("price_ratio_pct", -1))
|
||||
if ratio < 100 or ratio > 400:
|
||||
errs.append({"category": "domain", "message": "forge_products %s price_ratio_pct 越域 [100,400]: %d" % [attr, ratio]})
|
||||
if not forge_products.is_empty():
|
||||
for attr_key in FORGE_ATTRS:
|
||||
if not seen_attr.has(attr_key):
|
||||
errs.append({"category": "coverage", "message": "forge_products 缺属性键: %s(注入契约三字段必齐——恰三)" % attr_key})
|
||||
# boss_keystone:level_index 唯一 + 连续从 1 + 两档域 + first ≥ repeat(首杀全量、重复小额恒定)
|
||||
var seen_li := {}
|
||||
for row in boss_keystone:
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# Validation Question: 三旧体系(锻造/钥石/同调)收敛为一棵营地天赋树后,双币消费、row-gating、
|
||||
# A/B 免费切换、彩蛋觉醒能否机械承载(P0——迭代 12 批次二 T8,R1 甲融合案)
|
||||
# Date: 2026-10-04(双层 BD 实施批 T8;设计权威 双层BD体系-整合定稿-2026-10-03 §1.4)
|
||||
# 数据配置 MetaTreeTable schema(元进度 回填 owner 批末落文;行 = Dictionary 键 node_id,Rule 4 豁免
|
||||
# 定义表——FeatTable 先例)。
|
||||
# 行字段:node_id / display_name / vein ∈ VEINS(spine=主干脊柱 row-gating,钥石买深度〔2026-10-07
|
||||
# 用户裁甲——树全钥石单币,原双币铁律骨架披露销账〕;forge=锻造脉
|
||||
# 数值专精;keystone=钥石脉内容解锁;attune=同调脉槽位与规则)/ kind ∈ KINDS(stat=四线数值/
|
||||
# rule=全局规则/unlock=内容解锁/socket=同调位/fork=A/B 互斥对/easter=彩蛋觉醒)/
|
||||
# currency ∈ {keystone, free}(树全钥石单币——2026-10-07 用户裁甲;free=迁移点亮与彩蛋)/
|
||||
# price [0,9999] / cap [1,9](等级上限;fork/unlock 恒 1)/
|
||||
# row(脊柱行号 1..N——row-gating:行 N 可购条件 = 全树已购等级总和 ≥ SPINE_ROW_GATE×(N-1))/
|
||||
# requires Array[node_id](显式前置——父开子脉内链)/ effects Dictionary(stat 四线 flat:
|
||||
# atk/max_hp/hit/armor_class;rule 键封闭 RULE_KEYS)/ fork_group/fork_side(a/b——同组互斥、
|
||||
# 营地免费切换)。
|
||||
# 🔴 层级纪律(需求卡片红线 2):局外树只放全局规则件——effects 禁流派标签(relic 特性永不入树,
|
||||
# archetype 键出域=加载致命);规则改写型特性只走局内随机渠道。
|
||||
# A/B 只放规则面(定稿 1.3——全树互斥面 ≤10-15% 首期 4 组);两侧「不同强非强弱」MC 验差 <~3pp(T10)。
|
||||
# 彩蛋 ≤3(定稿 1.4——点满某脉某排免费觉醒;发现率 <5% 改灰色预告——T10 追认单呈报)。
|
||||
# 数据配置 MetaTreeTable schema(迭代 21 天赋树实装批 T1 换代——设计权威
|
||||
# design/gdd/systems/跨局循环/天赋树.md v0.8,2026-10-11 三轮换代终态)。
|
||||
# 结构:12 节点 = 脊柱 2(生命淬炼/命中淬炼——一线一节点 cap4,2026-10-11 用户裁:一个内容一个
|
||||
# 节点、等级在节点内)+ 锻造脉 2(锻造工坊钥匙 free 无前置 + 彩蛋地脉共鸣——彩蛋含在此 2 内)
|
||||
# + 钥石脉 2(秘宝匣/刷新升级)+ 同调脉 6(同调位 1/2/3 + Fork 3 双态节点)。
|
||||
# 行字段:node_id / display_name / vein ∈ VEINS(spine=主干脊柱;forge=锻造脉;keystone=钥石脉内容
|
||||
# 解锁;attune=同调脉)/ kind ∈ KINDS(stat=数值线/rule=全局规则/unlock=内容解锁/socket=同调位/
|
||||
# fork=一节点双态开关/easter=彩蛋觉醒)/ currency ∈ {keystone, free}(树全钥石单币——2026-10-07
|
||||
# 用户裁甲;free=工作间钥匙与彩蛋)/ price [0,9999] / cap [1,9](等级上限;unlock/socket/fork/
|
||||
# easter 恒 1)/ requires Array[node_id](点亮前置——前置节点 tree_level>=1 即满足;
|
||||
# 「等级阈值前置」新形态经 R1 裁撤〔2026-10-12 卡片 v1.7 默认案执行〕,schema 只保留点亮前置)/
|
||||
# effects Dictionary(stat 线 flat:max_hp/hit;rule 键封闭 RULE_KEYS)。
|
||||
# Fork 一节点双态(2026-10-11 用户裁,原 A/B 双行结构收编):fork_group ∈ FORK_GROUPS 每组恰一节点;
|
||||
# 行持 effects_a/effects_b 双效果键(A/B 只放规则面——键 ⊆ RULE_KEYS)+ name_a/name_b 双态呈现名;
|
||||
# 购买解锁默认 A 态、营地零币切换(tree_toggle_fork),效果按激活态(save.fork_sides)取值。
|
||||
# 行门槛(一线一节点换代):脊柱节点第 N 级可购需全树已购总等级 ≥ SPINE_ROW_GATE×(N−1)(总等级口径
|
||||
# =tree_nodes 全部节点等级之和,免费点亮与 Fork 解锁计入——2026-10-11 歧义扫描裁甲);
|
||||
# row 字段随层行退役。
|
||||
# 🔴 层级纪律(需求卡片红线 2):局外树只放全局规则件——effects 禁流派标签(archetype 键出域=加载
|
||||
# 致命);规则改写型特性只走局内随机渠道,永不入树。
|
||||
# 彩蛋 ≤3(schema 校验);觉醒判定=MetaProgression._check_easter_egg 特化(生命/命中两线拉满+同调位
|
||||
# 全解锁),不依赖 requires 点亮形态。
|
||||
# 数值全 provisional 归 M4 数值批(契约 7);两侧「不同强非强弱」MC 验差 <~3pp(V19)。
|
||||
class_name MetaTreeTable
|
||||
extends Resource
|
||||
|
||||
@@ -27,21 +31,23 @@ const VEINS := ["spine", "forge", "keystone", "attune"]
|
||||
const KINDS := ["stat", "rule", "unlock", "socket", "fork", "easter"]
|
||||
const CURRENCIES := ["keystone", "free"] # 树全钥石单币(2026-10-07 用户裁甲——原 gp 枚举值退役,字段保留为呈现元数据)
|
||||
const STAT_KEYS := ["atk", "max_hp", "hit", "armor_class"]
|
||||
## 规则键封闭词表(零裸 flat 同族护栏——effects.rule 键出域=加载致命;首期两键=T8 Fork 面真实接线)
|
||||
const RULE_KEYS := ["posture_first_strike", "posture_bulwark"]
|
||||
## 规则键封闭词表(零裸 flat 同族护栏——effects.rule 键出域=加载致命;六键=三组 Fork 双态真实接线,
|
||||
## 迭代 21 T1 扩签:posture 两键现役沿袭,sense/economy 四键随危险直觉/财富之道两组建效面新增)
|
||||
const RULE_KEYS := ["posture_first_strike", "posture_bulwark", "sense_sharp", "sense_guard", "economy_hoard", "economy_spend"]
|
||||
const KEY_WHITELIST := ["node_id", "display_name", "vein", "kind", "currency", "price", "cap",
|
||||
"row", "requires", "effects", "fork_group", "fork_side", "note"]
|
||||
## 脊柱行门槛(row-gating:行 N 可购需全树已购总等级 ≥ GATE×(N−1)——「投入点数累计解锁新行」)
|
||||
"requires", "effects", "effects_a", "effects_b", "name_a", "name_b", "fork_group", "note"]
|
||||
## 脊柱行门槛(一线一节点:节点第 N 级可购需全树总等级 ≥ GATE×(N−1)——「投入点数累计解锁深度」)
|
||||
const SPINE_ROW_GATE := 4
|
||||
|
||||
## A/B 组词表(同组恰两侧 a/b——互斥对;营地免费切换承载面)
|
||||
const FORK_GROUPS := ["fg_posture", "fg_sense", "fg_economy", "fg_mastery"]
|
||||
## Fork 双态组词表(每组恰一节点——互斥由单态承载;营地零币切换承载面;
|
||||
## fg_mastery 克制专精整组砍除——2026-10-11 裁,类型表达归锻造工坊)
|
||||
const FORK_GROUPS := ["fg_posture", "fg_sense", "fg_economy"]
|
||||
|
||||
|
||||
func validate() -> Array:
|
||||
var errs: Array = []
|
||||
if rows.is_empty():
|
||||
return errs # 空表合法态(T8 建表先行——内容随迁移批落)
|
||||
return errs # 空表合法态(建表先行——内容随换代批落)
|
||||
var seen := {}
|
||||
var fork_seen := {}
|
||||
for row in rows:
|
||||
@@ -76,22 +82,28 @@ func validate() -> Array:
|
||||
var fx: Dictionary = row.get("effects", {}) as Dictionary
|
||||
for ek in fx.keys():
|
||||
if not (STAT_KEYS.has(String(ek)) or RULE_KEYS.has(String(ek))):
|
||||
errs.append({"category": "zero_flat", "message": "%s effects 键出域(四线/rule 词表封闭): %s" % [nid, String(ek)]})
|
||||
errs.append({"category": "zero_flat", "message": "%s effects 键出域(stat 线/rule 词表封闭): %s" % [nid, String(ek)]})
|
||||
# 🔴 层级纪律:局外树禁规则改写型特性(archetype 键出域=加载致命)
|
||||
if fx.has("archetype"):
|
||||
errs.append({"category": "load_bearing", "message": "%s effects 含 archetype(🔴 层级纪律——特性禁入局外树)" % nid})
|
||||
# A/B 互斥对结构:fork 行必带 fork_group+side;同组恰 a/b 各一
|
||||
# Fork 一节点双态结构:fork 行必带 fork_group(每组恰一节点);主 effects 须空,效果只放
|
||||
# a/b 双键且键 ⊆ RULE_KEYS(A/B 只放规则面——层级纪律)
|
||||
if kind == "fork":
|
||||
var fg := String(row.get("fork_group", ""))
|
||||
if not FORK_GROUPS.has(fg):
|
||||
errs.append({"category": "enum", "message": "%s fork_group 出域" % nid})
|
||||
var side := String(row.get("fork_side", ""))
|
||||
if side != "a" and side != "b":
|
||||
errs.append({"category": "enum", "message": "%s fork_side 出域: %s" % [nid, side]})
|
||||
var fk := fg + "|" + side
|
||||
if fork_seen.has(fk):
|
||||
errs.append({"category": "dup_key", "message": "%s fork 对重复: %s" % [nid, fk]})
|
||||
fork_seen[fk] = true
|
||||
if fork_seen.has(fg):
|
||||
errs.append({"category": "dup_key", "message": "%s fork 组重复(一组恰一节点): %s" % [nid, fg]})
|
||||
fork_seen[fg] = true
|
||||
if not fx.is_empty():
|
||||
errs.append({"category": "cross_field", "message": "%s fork 行主 effects 须空(效果只放 a/b 双键)" % nid})
|
||||
for skey in ["effects_a", "effects_b"]:
|
||||
var sd: Dictionary = row.get(skey, {}) as Dictionary
|
||||
if sd.is_empty():
|
||||
errs.append({"category": "domain", "message": "%s %s 缺失或空(双态节点须双侧效果)" % [nid, skey]})
|
||||
for k in sd.keys():
|
||||
if not RULE_KEYS.has(String(k)):
|
||||
errs.append({"category": "zero_flat", "message": "%s %s 键出域(A/B 只放规则面): %s" % [nid, skey, String(k)]})
|
||||
# requires 悬空引用
|
||||
for row in rows:
|
||||
for req in (row.get("requires", []) as Array):
|
||||
@@ -103,7 +115,7 @@ func validate() -> Array:
|
||||
if String(row.get("kind", "")) == "easter":
|
||||
easter += 1
|
||||
if easter > 3:
|
||||
errs.append({"category": "load_bearing", "message": "彩蛋节点 %d > 3(定稿 §1.4 上限)" % easter})
|
||||
errs.append({"category": "load_bearing", "message": "彩蛋节点 %d > 3(上限 3)" % easter})
|
||||
return errs
|
||||
|
||||
|
||||
@@ -114,6 +126,7 @@ func row_by_id(node_id: String) -> Dictionary:
|
||||
return {}
|
||||
|
||||
|
||||
## row-gating 门槛(行 N 可购需全树总等级 ≥ GATE×(N−1);非脊柱节点跟随其 vein 父行——requires 主导)
|
||||
func row_gate(row_num: int) -> int:
|
||||
return SPINE_ROW_GATE * maxi(0, row_num - 1)
|
||||
## 行门槛(一线一节点:脊柱节点第 N 级可购需全树总等级 ≥ GATE×(N−1);原 row-gating 行号主语
|
||||
## 随层行退役平移到节点等级轴)
|
||||
func level_gate(level: int) -> int:
|
||||
return SPINE_ROW_GATE * maxi(0, level - 1)
|
||||
@@ -22,7 +22,8 @@
|
||||
# form ∈ {"", "passive", "active_charged"}(空 = 切片期过渡形态行——掉落/停点渠道现行承载,T7 退役;
|
||||
# passive = 装配整局生效;active_charged = 每冒险充能、挖掘期发起);
|
||||
# charges_per_adventure(active_charged ≥1 / passive 恒 0);slot_cost(同调行恒 1——底稿裁决 5,schema 强制);
|
||||
# unlock_requires 谓词行 [{key ∈ AttunementGrowthTable.COUNTER_KEYS, threshold ∈ [1,999]}](空 = 已解锁——
|
||||
# unlock_requires 谓词行 [{key ∈ 计数键封闭枚举〔原 AttunementGrowthTable.COUNTER_KEYS,随迭代 21
|
||||
# T5 谓词表退役就地内联为 UNLOCK_COUNTER_KEYS〕, threshold ∈ [1,999]}](空 = 已解锁——
|
||||
# 首批全解锁合法态,底稿 §2.2 用户裁;正式解锁渠道归 元进度 道具工坊柜台);
|
||||
# effective_rounds(前 N 轮限时件轮数——P4 甲表值化;仅 ap_rounds_front 行消费);
|
||||
# charge_n(充能进度阈值 N——仅 ap_charge_safe 行消费)。
|
||||
@@ -34,6 +35,10 @@ extends Resource
|
||||
|
||||
@export var rows: Array = [] # Array[Dictionary] 键 item_id
|
||||
|
||||
# 计数键封闭枚举(原 AttunementGrowthTable.COUNTER_KEYS——迭代 21 T5 谓词表退役后就地内联;
|
||||
# boss_kill_count/clear_count 计数域保留〔各有非槽位硬消费,卡片 T0⑥〕)
|
||||
const UNLOCK_COUNTER_KEYS := ["clear_count", "boss_kill_count"]
|
||||
|
||||
const DIMENSIONS := ["info", "risk", "action", "plan", "counter"]
|
||||
# 同调批扩 enum(迭代 11 第①批 T1)——新增九 kind 全部条件触发/机制修正类;mine_reveal/ap_grant 语义不变。
|
||||
# cell_removal = 爆破工兵包中立移除格(T7 原位换装时启用——T6 影响评估前置);mine_hit_halve 系固定
|
||||
@@ -164,8 +169,8 @@ func validate() -> Array:
|
||||
var ukeys := {}
|
||||
for req in ureqs:
|
||||
var ukey: String = String(req.get("key", ""))
|
||||
if not AttunementGrowthTable.COUNTER_KEYS.has(ukey):
|
||||
errs.append({"category": "enum", "message": "%s unlock_requires 计数键出域 %s: %s" % [iid, str(AttunementGrowthTable.COUNTER_KEYS), ukey]})
|
||||
if not UNLOCK_COUNTER_KEYS.has(ukey):
|
||||
errs.append({"category": "enum", "message": "%s unlock_requires 计数键出域 %s: %s" % [iid, str(UNLOCK_COUNTER_KEYS), ukey]})
|
||||
if ukeys.has(ukey):
|
||||
errs.append({"category": "dup_key", "message": "%s unlock_requires 计数键重复: %s" % [iid, ukey]})
|
||||
ukeys[ukey] = true
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
[gd_resource type="Resource" script_class="AttunementGrowthTable" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://data/schemas/attunement_growth_table.gd" id="1"]
|
||||
|
||||
; 迭代 11 第①批 同调实施批 T1(2026-10-03)——同调位里程碑谓词表。
|
||||
; 数值全 provisional 归 难度曲线与Boss(需求卡片 §三 7);行形态 = 底稿 §2.1 示意形态。
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
slot_cap = 3
|
||||
milestones = [{
|
||||
"id": "m1",
|
||||
"grant": 1,
|
||||
"requires": [{
|
||||
"key": "clear_count",
|
||||
"threshold": 1
|
||||
}]
|
||||
}, {
|
||||
"id": "m2",
|
||||
"grant": 1,
|
||||
"requires": [{
|
||||
"key": "boss_kill_count",
|
||||
"threshold": 3
|
||||
}]
|
||||
}, {
|
||||
"id": "m3",
|
||||
"grant": 1,
|
||||
"requires": [{
|
||||
"key": "clear_count",
|
||||
"threshold": 3
|
||||
}, {
|
||||
"key": "boss_kill_count",
|
||||
"threshold": 6
|
||||
}]
|
||||
}]
|
||||
@@ -4,25 +4,6 @@
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
forge_products = [{
|
||||
"attr": "max_hp",
|
||||
"display_name": "生命锻造",
|
||||
"flat_value": 3,
|
||||
"base_price": 20,
|
||||
"price_ratio_pct": 150
|
||||
}, {
|
||||
"attr": "atk",
|
||||
"display_name": "攻击锻造",
|
||||
"flat_value": 1,
|
||||
"base_price": 20,
|
||||
"price_ratio_pct": 150
|
||||
}, {
|
||||
"attr": "armor_class",
|
||||
"display_name": "护甲锻造",
|
||||
"flat_value": 1,
|
||||
"base_price": 20,
|
||||
"price_ratio_pct": 150
|
||||
}]
|
||||
boss_keystone = [{
|
||||
"level_index": 1,
|
||||
"first_kill": 3,
|
||||
@@ -89,8 +70,10 @@ attune_refresh_upgrade = [{
|
||||
|
||||
; 元进度实现批(2026-09-21,设计权威 design/gdd/systems/冒险循环/元进度.md 定稿 v1.2):
|
||||
; 数值全 provisional 归元进度设计批配读数(骨架先行——元进度实现批 2026-09-21,先例 = GP+商店房批)。
|
||||
; forge flat 档与局内 GP 商店同族指数值档位非货币(血+3/攻+1/甲+1),base_price 现值 20(2026-09-22 对齿批投值)/ ratio 150 三行同;boss 两档 L1 5/1 · L2 8/2 · L3 12/3
|
||||
; 〔2026-10-07 天赋树货币甲案修复批订正——原句「GP 商店同族…base 10」陈旧:GP 字样误导(锻造消费货币=钥石,meta_progression.gd:59 spend_keystone),base 20 经 2026-09-22 对齿批改值〕
|
||||
; 〔forge_products 属性锻造行族已退役——迭代 21 T5 攻防所有权切分:生命唯一入口=树脊柱淬炼、
|
||||
; 攻/AC 唯一入口=锻造工坊路线(契约 9 红线),meta_forge_row 访问器与 buy_forge 购买面随批删除;
|
||||
; 历史投值记载(2026-09-22 对齿批 base 20/ratio 150、2026-09-23 标定批 6/9/1)系 conversion 行
|
||||
; 与 ForgeGDD 考古用,行族本体不再在表。〕boss 两档 L1 5/1 · L2 8/2 · L3 12/3
|
||||
; (首杀全量、重复杀固定小额恒定不递减);工坊六件阈值 1/1/2/2/3/3 按总通关次数(首版只挂通关次数渠道,
|
||||
; 钥石直购价随定价批后补);conversion 全 0 = 未标定占位(标定归设计批,护甲行强条目:验收读数必须按
|
||||
; 解锁后参数重测)。
|
||||
|
||||
+72
-319
@@ -5,242 +5,63 @@
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
rows = [{
|
||||
"node_id": "sp_hp_r1",
|
||||
"display_name": "生命淬炼·1 层",
|
||||
"node_id": "sp_hp",
|
||||
"display_name": "生命淬炼",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 10,
|
||||
"cap": 3,
|
||||
"row": 1,
|
||||
"cap": 4,
|
||||
"requires": [],
|
||||
"effects": {
|
||||
"max_hp": 5
|
||||
}
|
||||
}, {
|
||||
"node_id": "sp_atk_r1",
|
||||
"display_name": "攻击淬炼·1 层",
|
||||
"node_id": "sp_hit",
|
||||
"display_name": "命中淬炼",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 10,
|
||||
"cap": 3,
|
||||
"row": 1,
|
||||
"effects": {
|
||||
"atk": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "sp_hit_r1",
|
||||
"display_name": "命中淬炼·1 层",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 10,
|
||||
"cap": 3,
|
||||
"row": 1,
|
||||
"cap": 4,
|
||||
"requires": [],
|
||||
"effects": {
|
||||
"hit": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "sp_ac_r1",
|
||||
"display_name": "护甲淬炼·1 层",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 10,
|
||||
"cap": 3,
|
||||
"row": 1,
|
||||
"effects": {
|
||||
"armor_class": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "sp_hp_r2",
|
||||
"display_name": "生命淬炼·2 层",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 20,
|
||||
"cap": 3,
|
||||
"row": 2,
|
||||
"effects": {
|
||||
"max_hp": 5
|
||||
}
|
||||
}, {
|
||||
"node_id": "sp_atk_r2",
|
||||
"display_name": "攻击淬炼·2 层",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 20,
|
||||
"cap": 3,
|
||||
"row": 2,
|
||||
"effects": {
|
||||
"atk": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "sp_hit_r2",
|
||||
"display_name": "命中淬炼·2 层",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 20,
|
||||
"cap": 3,
|
||||
"row": 2,
|
||||
"effects": {
|
||||
"hit": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "sp_ac_r2",
|
||||
"display_name": "护甲淬炼·2 层",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 20,
|
||||
"cap": 3,
|
||||
"row": 2,
|
||||
"effects": {
|
||||
"armor_class": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "sp_hp_r3",
|
||||
"display_name": "生命淬炼·3 层",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 30,
|
||||
"cap": 3,
|
||||
"row": 3,
|
||||
"effects": {
|
||||
"max_hp": 5
|
||||
}
|
||||
}, {
|
||||
"node_id": "sp_atk_r3",
|
||||
"display_name": "攻击淬炼·3 层",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 30,
|
||||
"cap": 3,
|
||||
"row": 3,
|
||||
"effects": {
|
||||
"atk": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "sp_hit_r3",
|
||||
"display_name": "命中淬炼·3 层",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 30,
|
||||
"cap": 3,
|
||||
"row": 3,
|
||||
"effects": {
|
||||
"hit": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "sp_ac_r3",
|
||||
"display_name": "护甲淬炼·3 层",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 30,
|
||||
"cap": 3,
|
||||
"row": 3,
|
||||
"effects": {
|
||||
"armor_class": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "sp_hp_r4",
|
||||
"display_name": "生命淬炼·4 层",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 40,
|
||||
"cap": 3,
|
||||
"row": 4,
|
||||
"effects": {
|
||||
"max_hp": 5
|
||||
}
|
||||
}, {
|
||||
"node_id": "sp_atk_r4",
|
||||
"display_name": "攻击淬炼·4 层",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 40,
|
||||
"cap": 3,
|
||||
"row": 4,
|
||||
"effects": {
|
||||
"atk": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "sp_hit_r4",
|
||||
"display_name": "命中淬炼·4 层",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 40,
|
||||
"cap": 3,
|
||||
"row": 4,
|
||||
"effects": {
|
||||
"hit": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "sp_ac_r4",
|
||||
"display_name": "护甲淬炼·4 层",
|
||||
"vein": "spine",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 40,
|
||||
"cap": 3,
|
||||
"row": 4,
|
||||
"effects": {
|
||||
"armor_class": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "forge_atk",
|
||||
"display_name": "武器锻造",
|
||||
"node_id": "forge_workshop",
|
||||
"display_name": "锻造工坊",
|
||||
"vein": "forge",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 3,
|
||||
"cap": 5,
|
||||
"requires": ["sp_atk_r1"],
|
||||
"effects": {
|
||||
"atk": 1
|
||||
}
|
||||
"kind": "unlock",
|
||||
"currency": "free",
|
||||
"price": 0,
|
||||
"cap": 1,
|
||||
"requires": [],
|
||||
"note": "工作间钥匙——无前置直接可点亮(2026-10-11 攻防所有权切分裁);点亮展开锻造工坊柜台"
|
||||
}, {
|
||||
"node_id": "forge_hp",
|
||||
"display_name": "护身锻造",
|
||||
"node_id": "easter_ley",
|
||||
"display_name": "地脉共鸣",
|
||||
"vein": "forge",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 3,
|
||||
"cap": 5,
|
||||
"requires": ["sp_hp_r1"],
|
||||
"kind": "easter",
|
||||
"currency": "free",
|
||||
"price": 0,
|
||||
"cap": 1,
|
||||
"requires": [],
|
||||
"note": "彩蛋觉醒——判定归 _check_easter_egg 特化(生命/命中两线拉满+同调位全解锁),不经购买;🔴 effects 只持生命/命中两键(契约 9 攻防所有权切分——攻/护分量已摘除)",
|
||||
"effects": {
|
||||
"max_hp": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "forge_ac",
|
||||
"display_name": "甲胄锻造",
|
||||
"vein": "forge",
|
||||
"kind": "stat",
|
||||
"currency": "keystone",
|
||||
"price": 3,
|
||||
"cap": 5,
|
||||
"requires": ["sp_ac_r1"],
|
||||
"effects": {
|
||||
"armor_class": 1
|
||||
"max_hp": 5,
|
||||
"hit": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "ks_workshop",
|
||||
"display_name": "道具工坊",
|
||||
"display_name": "秘宝匣",
|
||||
"vein": "keystone",
|
||||
"kind": "unlock",
|
||||
"currency": "free",
|
||||
"price": 0,
|
||||
"cap": 1,
|
||||
"requires": ["sp_hp_r1"]
|
||||
"requires": ["sp_hp"],
|
||||
"note": "工作间钥匙——前置=脊柱生命第 1 级(v0.8 节点总表);点亮展开秘宝匣柜台(解锁+同调装配)"
|
||||
}, {
|
||||
"node_id": "ks_refresh",
|
||||
"display_name": "刷新升级",
|
||||
@@ -249,7 +70,8 @@ rows = [{
|
||||
"currency": "keystone",
|
||||
"price": 30,
|
||||
"cap": 2,
|
||||
"requires": ["ks_workshop"]
|
||||
"requires": ["ks_workshop"],
|
||||
"note": "树上刷新升级——购买写 attune_refresh_level 同账(迭代 21 T4 接线,双入口并存 Q1 裁乙)"
|
||||
}, {
|
||||
"node_id": "att_socket1",
|
||||
"display_name": "同调位 1",
|
||||
@@ -258,7 +80,8 @@ rows = [{
|
||||
"currency": "keystone",
|
||||
"price": 20,
|
||||
"cap": 1,
|
||||
"requires": ["sp_atk_r1"]
|
||||
"requires": [],
|
||||
"note": "同调脉根节点——无前置(v0.8 节点总表换代,原 sp_atk_r1 前置随攻防切分砍除)"
|
||||
}, {
|
||||
"node_id": "att_socket2",
|
||||
"display_name": "同调位 2",
|
||||
@@ -278,140 +101,70 @@ rows = [{
|
||||
"cap": 1,
|
||||
"requires": ["att_socket2"]
|
||||
}, {
|
||||
"node_id": "fork_posture_a",
|
||||
"display_name": "先手压制",
|
||||
"node_id": "fork_posture",
|
||||
"display_name": "开局姿态",
|
||||
"vein": "attune",
|
||||
"kind": "fork",
|
||||
"currency": "keystone",
|
||||
"price": 25,
|
||||
"cap": 1,
|
||||
"requires": ["att_socket1"],
|
||||
"effects": {
|
||||
"posture_first_strike": 1
|
||||
},
|
||||
"fork_group": "fg_posture",
|
||||
"fork_side": "a"
|
||||
"name_a": "先手压制",
|
||||
"name_b": "稳健拓荒",
|
||||
"effects_a": {
|
||||
"posture_first_strike": 1
|
||||
},
|
||||
"effects_b": {
|
||||
"posture_bulwark": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "fork_posture_b",
|
||||
"display_name": "稳健拓荒",
|
||||
"node_id": "fork_sense",
|
||||
"display_name": "危险直觉",
|
||||
"vein": "attune",
|
||||
"kind": "fork",
|
||||
"currency": "keystone",
|
||||
"price": 25,
|
||||
"cap": 1,
|
||||
"requires": ["att_socket1"],
|
||||
"effects": {
|
||||
"posture_bulwark": 1
|
||||
},
|
||||
"fork_group": "fg_posture",
|
||||
"fork_side": "b"
|
||||
}, {
|
||||
"node_id": "fork_sense_a",
|
||||
"display_name": "雷感直觉·锋",
|
||||
"vein": "attune",
|
||||
"kind": "fork",
|
||||
"currency": "keystone",
|
||||
"price": 25,
|
||||
"cap": 1,
|
||||
"requires": ["att_socket1"],
|
||||
"effects": {
|
||||
"posture_bulwark": 1
|
||||
},
|
||||
"fork_group": "fg_sense",
|
||||
"fork_side": "a"
|
||||
"name_a": "锋",
|
||||
"name_b": "守",
|
||||
"effects_a": {
|
||||
"sense_sharp": 1
|
||||
},
|
||||
"effects_b": {
|
||||
"sense_guard": 1
|
||||
}
|
||||
}, {
|
||||
"node_id": "fork_sense_b",
|
||||
"display_name": "雷感直觉·守",
|
||||
"node_id": "fork_economy",
|
||||
"display_name": "财富之道",
|
||||
"vein": "attune",
|
||||
"kind": "fork",
|
||||
"currency": "keystone",
|
||||
"price": 25,
|
||||
"cap": 1,
|
||||
"requires": ["att_socket1"],
|
||||
"effects": {
|
||||
"posture_first_strike": 1
|
||||
},
|
||||
"fork_group": "fg_sense",
|
||||
"fork_side": "b"
|
||||
}, {
|
||||
"node_id": "fork_economy_a",
|
||||
"display_name": "经济人格·聚",
|
||||
"vein": "attune",
|
||||
"kind": "fork",
|
||||
"currency": "keystone",
|
||||
"price": 25,
|
||||
"cap": 1,
|
||||
"requires": ["att_socket1"],
|
||||
"effects": {
|
||||
"posture_first_strike": 1
|
||||
},
|
||||
"fork_group": "fg_economy",
|
||||
"fork_side": "a"
|
||||
}, {
|
||||
"node_id": "fork_economy_b",
|
||||
"display_name": "经济人格·散",
|
||||
"vein": "attune",
|
||||
"kind": "fork",
|
||||
"currency": "keystone",
|
||||
"price": 25,
|
||||
"cap": 1,
|
||||
"requires": ["att_socket1"],
|
||||
"effects": {
|
||||
"posture_bulwark": 1
|
||||
"name_a": "聚",
|
||||
"name_b": "散",
|
||||
"effects_a": {
|
||||
"economy_hoard": 1
|
||||
},
|
||||
"fork_group": "fg_economy",
|
||||
"fork_side": "b"
|
||||
}, {
|
||||
"node_id": "fork_mastery_a",
|
||||
"display_name": "克制专精·穿刺",
|
||||
"vein": "attune",
|
||||
"kind": "fork",
|
||||
"currency": "keystone",
|
||||
"price": 25,
|
||||
"cap": 1,
|
||||
"requires": ["att_socket1"],
|
||||
"effects": {
|
||||
"posture_bulwark": 1
|
||||
},
|
||||
"fork_group": "fg_mastery",
|
||||
"fork_side": "a"
|
||||
}, {
|
||||
"node_id": "fork_mastery_b",
|
||||
"display_name": "克制专精·魔法",
|
||||
"vein": "attune",
|
||||
"kind": "fork",
|
||||
"currency": "keystone",
|
||||
"price": 25,
|
||||
"cap": 1,
|
||||
"requires": ["att_socket1"],
|
||||
"effects": {
|
||||
"posture_first_strike": 1
|
||||
},
|
||||
"fork_group": "fg_mastery",
|
||||
"fork_side": "b"
|
||||
}, {
|
||||
"node_id": "easter_ley",
|
||||
"display_name": "地脉共鸣",
|
||||
"vein": "forge",
|
||||
"kind": "easter",
|
||||
"currency": "free",
|
||||
"price": 0,
|
||||
"cap": 1,
|
||||
"requires": ["forge_atk", "forge_hp", "forge_ac"],
|
||||
"effects": {
|
||||
"atk": 1,
|
||||
"max_hp": 5,
|
||||
"hit": 1,
|
||||
"armor_class": 1
|
||||
"effects_b": {
|
||||
"economy_spend": 1
|
||||
}
|
||||
}]
|
||||
|
||||
; 双层 BD 实施批 T8(2026-10-04,R1 甲融合案——整合定稿 §1.4 一树双币三脉一脊+装配区+彩蛋):
|
||||
; 脊柱 4 行×4 线(生命/攻击/命中/护甲——钥石买深度 cap3 价 10×row〔2026-10-07 用户裁甲 gp→keystone〕;
|
||||
; row-gating=全树已购总等级
|
||||
; ≥ 4×(row−1));锻造脉 3 节点收编 forge_levels(钥石付价=现行迁移零痛);钥石脉=工坊解锁收编
|
||||
; (free 迁移点亮)+刷新升级移植;同调脉 socket 3(钥石=规则面首站)+Fork 4 对(A/B 集中同调脉
|
||||
; 营地免费切换——effects 首期两 rule 键真实接线余组 stat 面 provisional);彩蛋 1(锻造满排觉醒)。
|
||||
; 迭代 21 天赋树实装批 T1 换代(2026-10-12,设计权威 design/gdd/systems/跨局循环/天赋树.md v0.8):
|
||||
; 12 节点 = 脊柱 2(生命淬炼/命中淬炼——一线一节点 cap4,2026-10-11 用户裁,原 4 层×4 线 16 行收编;
|
||||
; 攻/护淬炼线随攻防所有权切分砍除——攻唯一入口=锻造工坊武器路线、AC 唯一入口=甲路线〔契约 9 红线〕)
|
||||
; + 锻造脉 2(forge_workshop 钥匙 free 无前置 + easter_ley 彩蛋——effects 只持生命/命中两键,
|
||||
; :402/:405 旧攻/护分量已摘除)+ 钥石脉 2(秘宝匣 ks_workshop〔display 旧名「道具工坊」退役〕/
|
||||
; 刷新升级 ks_refresh)+ 同调脉 6(同调位 1/2/3——socket1 改根节点无前置 + Fork 3 单行双态节点,
|
||||
; 原 8 行 A/B 双行结构与 fg_mastery 克制专精组退役)。
|
||||
; 行门槛=一线一节点主语:脊柱节点第 N 级需全树总等级 ≥4×(N−1)(SPINE_ROW_GATE=4 公式平移,
|
||||
; row 字段退役);requires=点亮前置(R1 裁撤案执行,schema 无等级阈值形态)。
|
||||
; 🔴 层级纪律:局外树只放全局规则件——零 archetype 键(schema 校验强制);规则改写型特性只走局内随机渠道。
|
||||
; 数值全 provisional 归 M-4 数值批;A/B 两侧差 <~3pp 验收=T10 MC。
|
||||
; 2026-10-07 用户裁甲:树全钥石单币落定(脊柱 16 行 currency gp→keystone;T8「GP 余额域缺位」双币骨架披露销账——GP 局内清零不入存档永不互兑条款不动,元进度.md:55)。数值 provisional 归 M4。
|
||||
; 数值全 provisional 归 M4 数值批(契约 7——价格沿旧量纲锚:脊柱 10=旧 r1 价、socket 20/40/60、
|
||||
; Fork 25、刷新 30 沿旧;生命 +5/级、命中 +1/级沿旧量纲);A/B 两侧差 <~3pp 验收=V19 MC。
|
||||
@@ -61,26 +61,15 @@ func on_camp_closed() -> void:
|
||||
_refresh_presentations.call() # 关闭后刷新顶栏(工坊解锁可能变了余额侧读数面)
|
||||
|
||||
|
||||
# 营地快照投影(纯读——锻造三行/工坊六行/余额/最近冒险;拼句归 CampOverlay,账面归 MetaProgression)
|
||||
# 营地快照投影(纯读——工坊六行/同调装配/树面/余额/最近冒险;拼句归 CampOverlay,账面归
|
||||
# MetaProgression。属性锻造三行投影已删——迭代 21 T4 柜台退役)
|
||||
func camp_data() -> Dictionary:
|
||||
var meta = _meta_get.call()
|
||||
var forge_rows: Array[Dictionary] = []
|
||||
if meta != null:
|
||||
for attr in MetaTable.FORGE_ATTRS:
|
||||
var row: Dictionary = db.meta_forge_row(attr)
|
||||
forge_rows.append({
|
||||
"attr": attr,
|
||||
"display_name": String(row.get("display_name", "")),
|
||||
"level": meta.forge_level(attr),
|
||||
"flat": int(row.get("flat_value", 0)),
|
||||
"price": meta.forge_price(attr),
|
||||
})
|
||||
return {
|
||||
"balance": meta.keystone_balance() if meta != null else 0,
|
||||
"forge": forge_rows,
|
||||
"workshop": meta.workshop_status() if meta != null else [],
|
||||
"attune": meta.attune_camp_status() if meta != null else {}, # 迭代 11 第①批 T4——同调装配面投影
|
||||
"tree": meta.tree_camp_rows() if meta != null else [], # 迭代 12 T9——天赋树面投影
|
||||
"tree": meta.tree_camp_rows() if meta != null else [], # 树面投影(含 forge_workshop/ks_workshop 钥匙门判据行)
|
||||
"fork_groups": meta.tree_fork_groups() if meta != null else [],
|
||||
"recent": _save_get.call().recent_summaries() if _save_get.call() != null else [],
|
||||
}
|
||||
@@ -90,17 +79,10 @@ func on_camp_buy(attr: String) -> void:
|
||||
var meta = _meta_get.call()
|
||||
if meta == null:
|
||||
return
|
||||
if attr == "attune_refresh":
|
||||
# Y 刷新升级行购买(迭代 11 第①批 T4——P1 甲;按钮禁用为常态防线,兜底拒绝可见 pattern 1)
|
||||
if not meta.buy_attune_refresh():
|
||||
toast.show_toast("钥石不足——买不起这次升级", "warn")
|
||||
return
|
||||
camp_overlay.refresh_camp(camp_data())
|
||||
_refresh_presentations.call()
|
||||
return
|
||||
if not meta.buy_forge(attr):
|
||||
# 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1)
|
||||
toast.show_toast("钥石不足——买不起这件强化", "warn")
|
||||
# Y 刷新升级行购买(迭代 11 第①批 T4——P1 甲;双入口之一 Q1 裁乙——树上 ks_refresh 同账;
|
||||
# 按钮禁用为常态防线,兜底拒绝可见 pattern 1。属性锻造面已退役——buy_forge 分支随批删除)
|
||||
if attr != "attune_refresh" or not meta.buy_attune_refresh():
|
||||
toast.show_toast("购买被拒", "warn")
|
||||
return
|
||||
camp_overlay.refresh_camp(camp_data())
|
||||
_refresh_presentations.call()
|
||||
|
||||
@@ -1013,20 +1013,11 @@ func _on_settings_closed() -> void:
|
||||
# ---- 局内暂停层·只读(迭代 17 卡③——入口角标在两屏装配段〔组 B〕;开合与数据喂入归组合根)----
|
||||
|
||||
# 暂停层快照投影(R4 数据只读同源:每次开层实时读 MetaProgression 访问器现值拼装——
|
||||
# 快照仅服务单次开层生命周期(open_pause 重建各行),跨开层零持有;零写调用)
|
||||
# 快照仅服务单次开层生命周期(open_pause 重建各行),跨开层零持有;零写调用。
|
||||
# 属性锻造三行投影已删——迭代 21 柜台退役连带〔暂停层锻造段同步删除〕)
|
||||
func _pause_data() -> Dictionary:
|
||||
var forge_rows: Array[Dictionary] = []
|
||||
for attr in MetaTable.FORGE_ATTRS:
|
||||
var row: Dictionary = db.meta_forge_row(attr)
|
||||
forge_rows.append({
|
||||
"attr": attr,
|
||||
"display_name": String(row.get("display_name", "")),
|
||||
"level": meta.forge_level(attr),
|
||||
"flat": int(row.get("flat_value", 0)),
|
||||
})
|
||||
return {
|
||||
"balance": meta.keystone_balance(),
|
||||
"forge": forge_rows,
|
||||
"workshop": meta.workshop_status(),
|
||||
"tree": meta.tree_camp_rows(),
|
||||
"fork_groups": meta.tree_fork_groups(),
|
||||
@@ -1058,7 +1049,9 @@ func _on_career_closed() -> void:
|
||||
|
||||
|
||||
# 生涯页快照投影(只读同源——每次开层实时读 SaveManager 计数访问器 + MetaProgression 访问器
|
||||
# 现值拼装(暂停层 _pause_data 同构);快照仅服务单次开层生命周期,跨开层零持有;零写调用)
|
||||
# 现值拼装(暂停层 _pause_data 同构);快照仅服务单次开层生命周期,跨开层零持有;零写调用。
|
||||
# 账号成长态三投影已删〔forge_levels_total/workshop/tree——2026-10-12 用户裁:生涯页不统计
|
||||
# 天赋树与账号成长态,只统计终身计数〕)
|
||||
func _career_data() -> Dictionary:
|
||||
var clears: Array[Dictionary] = []
|
||||
var boss_first := 0
|
||||
@@ -1068,9 +1061,6 @@ func _career_data() -> Dictionary:
|
||||
"count": save_manager.clear_count(li)})
|
||||
if save_manager.boss_first_kill_marked(li):
|
||||
boss_first += 1
|
||||
var forge_total := 0
|
||||
for attr in MetaTable.FORGE_ATTRS:
|
||||
forge_total += meta.forge_level(attr)
|
||||
return {
|
||||
"total": save_manager.adventure_total(),
|
||||
"cleared": save_manager.adventure_cleared(),
|
||||
@@ -1079,10 +1069,6 @@ func _career_data() -> Dictionary:
|
||||
"boss_first_total": db.level_count(),
|
||||
"boss_kills": save_manager.boss_kill_count(),
|
||||
"keystone": meta.keystone_balance(),
|
||||
"forge_levels_total": forge_total,
|
||||
"workshop": meta.workshop_status(),
|
||||
"tree": meta.tree_camp_rows(),
|
||||
"fork_groups": meta.tree_fork_groups(),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# Date: 2026-09-21(元进度实现批——营地覆盖层呈现;2026-09-30 迭代 9 卡⑭ 视觉终稿——对照营地屏
|
||||
# 方向稿 artwork/mockups/v3-draft-2026-09-29/4-camp.png 重排:石纹全屏底 + 顶栏铭牌/钥石徽 +
|
||||
# 三柜台列(属性锻造/道具工坊/奖励格图鉴,头带深棕+米色板身+内嵌行板,暗底→亮板双色墨制)+
|
||||
# 底栏关闭钮/操作注记;最近冒险收底栏条带。信号面/投影契约零变化:buy_requested(attr)/closed、
|
||||
# open_camp(data)/refresh_camp(data)、_forge_rows 行结构 {attr,level_l,price_l,btn}(探针依赖)。
|
||||
# Date: 2026-09-21(元进度实现批——营地覆盖层呈现;2026-10-12 迭代 21 T6 营地统一重排——
|
||||
# 四柜台并排〔2026-10-11 锻造工坊裁决会话 A② 裁〕:树淬炼柜台=树脊柱界面形态=主界面 /
|
||||
# 锻造工坊柜台〔钥匙门:forge_workshop 点亮前锁定态,点亮=工作间占位面板〕/ 秘宝匣柜台
|
||||
# 〔钥匙门:ks_workshop 点亮前锁定态,点亮=现工坊内容整体迁入——六行+同调装配段+Y 升级行〕/
|
||||
# 奖励格图鉴柜台常开独立位;宽度不足横向滚动条。旧属性锻造柜台退役〔攻防所有权切分——
|
||||
# 生命唯一入口=树脊柱、攻/AC 唯一入口=锻造工坊路线〕)。
|
||||
# 占位面板硬要求(2026-10-11 用户令):锻造工坊工作间占位面板打开/关闭零副作用——零写档、
|
||||
# 零报错、面板内无任何可交互购买路径(纯文案标签,视觉终稿归 HUD 营地视觉批)。
|
||||
# 信号面/投影契约:buy_requested(attr)〔现仅 Y 刷新升级行消费——装配段双入口之一 Q1 裁乙〕/closed、
|
||||
# attune_toggled/tree_buy_requested/tree_fork_toggled、open_camp(data)/refresh_camp(data)。
|
||||
# 挂接方式:开合与开始层互斥仿设置面先例(开始覆盖层非模态且树序在后——打开本层前由组合根
|
||||
# 隐藏开始层,规范 2.3 遮挡纪律)。
|
||||
# 高内聚边界:纯视图 setter 面——账本快照经组合根喂入(MetaProgression 只读投影),购买/关闭
|
||||
@@ -15,24 +20,26 @@ signal buy_requested(attr: String)
|
||||
signal closed
|
||||
signal attune_toggled(item_id: String) # 同调装配切换意图(迭代 11 第①批 T4——写口唯一归 MetaProgression.toggle_attunement)
|
||||
signal tree_buy_requested(node_id: String) # 天赋树购买意图(迭代 12 T9——写口唯一归 MetaProgression.tree_buy)
|
||||
signal tree_fork_toggled(group: String, side: String) # A/B 免费切换意图(同组换侧零币)
|
||||
signal tree_fork_toggled(group: String, side: String) # 双态切换意图(一节点双态——零币切态)
|
||||
|
||||
const ATTR_LABELS := {"atk": "攻击", "max_hp": "生命上限", "armor_class": "护甲"} # 档位文案轴(数据契约 attr 词表,与 ShopOverlay 同款)
|
||||
# 方向稿取色(4-camp.png——柜台头带深棕/行板暖米;双色墨=亮板配深墨)——值单点收 UiKit(T4 裸色收口批)
|
||||
# 方向稿取色(柜台头带深棕/行板暖米;双色墨=亮板配深墨)——值单点收 UiKit(T4 裸色收口批)
|
||||
const HEADER_BAND_COL := UiKit.CAMP_HEADER_BAND
|
||||
const ROW_PLATE_COL := UiKit.CAMP_ROW_PLATE
|
||||
const BADGE_PANEL_COL := UiKit.CAMP_BADGE_PANEL
|
||||
|
||||
var _balance_label: Label
|
||||
var _forge_holder: VBoxContainer
|
||||
var _workshop_holder: VBoxContainer
|
||||
var _tree_holder: VBoxContainer # 天赋树面板(迭代 12 T9 程序占位——行板复用行板结构)
|
||||
var _forge_rows: Array[Dictionary] = [] # [{attr, level_l, price_l, btn}]——refresh_camp 就地重刷(零重建回流)
|
||||
var _tree_holder: VBoxContainer # 树淬炼柜台内容(主界面——脊柱两行+钥匙/socket 行板+Fork 双态钮)
|
||||
var _tree_buy_btns: Array[Dictionary] = [] # [{node_id, btn}]——树购买/解锁钮登记面(open/refresh 重建时刷新;探针消费)
|
||||
var _tree_fork_btns: Array[Dictionary] = [] # [{group, side, btn}]——Fork 双态切换钮登记面(探针消费)
|
||||
var _forge_lock_l: Label # 锻造工坊柜台锁定态行(钥匙未点亮)
|
||||
var _forge_workbench: VBoxContainer # 锻造工坊工作间占位面板(点亮后展开——零交互纯文案)
|
||||
var _ws_scroll: ScrollContainer # 秘宝匣柜台内容容器(钥匙点亮后可见)
|
||||
var _ws_lock_l: Label # 秘宝匣柜台锁定态行
|
||||
var _workshop_holder: VBoxContainer # 秘宝匣柜台行板(六行+同调装配段+Y 升级行——迭代 11 第①批 T4 结构随迁)
|
||||
var _workshop_rows: Array[Dictionary] = [] # [{item_id, progress_l, status_l}]
|
||||
var _attune_header_l: Label # 同调装配段(迭代 11 第①批 T4 工坊扩容——Slots 点亮呈现)
|
||||
var _attune_header_l: Label # 同调装配段(Slots 点亮呈现)
|
||||
var _attune_btns: Array[Dictionary] = [] # [{item_id, btn}]——装配/卸下切换钮(满位禁选随刷)
|
||||
var _attune_nodes: Array[Control] = [] # 段内节点册(open_camp 重建时定向清理,不扰工坊六行)
|
||||
var _refresh_l: Label # Y 刷新升级行(P1 甲——每冒险开局给 Y 次、冒险周期池)
|
||||
var _refresh_l: Label # Y 刷新升级行(双入口之一——树上 ks_refresh 同账 Q1 裁乙)
|
||||
var _refresh_btn: Button
|
||||
var _recent_list: Label # 最近冒险(逐行拼接 label——只读账面,随 open_camp 重建)
|
||||
|
||||
@@ -69,64 +76,92 @@ func _init() -> void:
|
||||
top.add_child(plaque)
|
||||
var plaque_col := VBoxContainer.new()
|
||||
plaque.add_child(plaque_col)
|
||||
var title := UiKit.label("营地 · 永久强化",UiKit.FS_TITLE, true, UiKit.INK_TITLE)
|
||||
var title := UiKit.label("营地 · 天赋树",UiKit.FS_TITLE, true, UiKit.INK_TITLE)
|
||||
plaque_col.add_child(title)
|
||||
var sub := UiKit.label("跨局成长账本——钥石换永久属性",UiKit.FS_HINT, false, UiKit.STONE_DEEP) # 尾裁微批a 2026-10-08——STONE 对铭牌板 3.59:1 越带,换灰石深化 5.98:1
|
||||
var sub := UiKit.label("营地本身即树——钥石逐节点点亮,属性/规则/工作间都在树上",UiKit.FS_HINT, false, UiKit.STONE_DEEP) # 尾裁微批a 2026-10-08——STONE 对铭牌板 3.59:1 越带,换灰石深化 5.98:1
|
||||
plaque_col.add_child(sub)
|
||||
var spacer := Control.new()
|
||||
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
top.add_child(spacer)
|
||||
# M3(UI 落码批):钥石徽章烛光黄底+深墨字(demo ②——旧暗底 CAMP_BADGE_PANEL 退役;
|
||||
# BADGE_PANEL_COL 常量保留他用不动)。Q10 甲(2026-10-06):本地拼装迁 UiKit.keystone_badge
|
||||
# 工厂调用(badge 单点化——暂停层同款消费);形制参数逐项同原装配,观感零变化
|
||||
# M3(UI 落码批):钥石徽章烛光黄底+深墨字(demo ②)。Q10 甲(2026-10-06):UiKit.keystone_badge 工厂单点
|
||||
var badge := UiKit.keystone_badge("钥石 0")
|
||||
top.add_child(badge)
|
||||
_balance_label = badge.value_label
|
||||
|
||||
# ── 三柜台列(方向稿同构:头带深棕+米色板身)──
|
||||
# 三柜等宽分配(2026-10-06 用户实测缺陷修——原定宽 356+行标签按文字全宽算 min,进度行文案
|
||||
# 「累计通关 28/2 次·能力封存中」随档变长把面板撑爆,三柜总宽溢出 1248 可用宽=右柜裁出屏;
|
||||
# 改=EXPAND_FILL 等比分屏宽+行标签截断兜底,min 恒 ≤ 定宽)
|
||||
# ── 四柜台并排(A② 裁——迭代 21 T6 程序占位重排,视觉终稿归 HUD 营地视觉批)──
|
||||
# 宽度不足横向滚动(四柜 300 min 宽 > 窄窗可用宽时横滚兜底;纵向撑满余高)
|
||||
var camp_scroll := ScrollContainer.new()
|
||||
camp_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
camp_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO
|
||||
camp_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
col.add_child(camp_scroll)
|
||||
var mid := HBoxContainer.new()
|
||||
mid.add_theme_constant_override("separation", 18)
|
||||
mid.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
mid.add_theme_constant_override("separation", 14)
|
||||
mid.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
col.add_child(mid)
|
||||
_forge_holder = VBoxContainer.new()
|
||||
_forge_holder.add_theme_constant_override("separation", 6)
|
||||
var forge_panel := _counter_panel("属性锻造", UiKit.TEX_ICON_FORGE, _forge_holder, 356)
|
||||
camp_scroll.add_child(mid)
|
||||
|
||||
# 柜一·树淬炼(树脊柱界面形态=主界面:一线一节点行板内升级+Fork 双态切换钮)
|
||||
_tree_holder = VBoxContainer.new()
|
||||
_tree_holder.add_theme_constant_override("separation", 6)
|
||||
var tree_scroll := ScrollContainer.new()
|
||||
tree_scroll.custom_minimum_size = Vector2(300, 336)
|
||||
tree_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
tree_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
tree_scroll.add_child(_tree_holder)
|
||||
var tree_panel := _counter_panel("天赋树 · 淬炼", "", tree_scroll, 300)
|
||||
tree_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
mid.add_child(tree_panel)
|
||||
|
||||
# 柜二·锻造工坊(钥匙门——forge_workshop 点亮前锁定态;点亮=工作间占位面板,零交互)
|
||||
_forge_lock_l = UiKit.label("🔒 锻造工坊未开放\n\n点亮树上「锻造工坊」钥匙节点后展开工作间。\n(钥匙免费、无前置——树上直接点亮)",UiKit.FS_HINT, false, UiKit.STONE_DEEP)
|
||||
_forge_lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_forge_lock_l.custom_minimum_size = Vector2(280, 0)
|
||||
_forge_workbench = VBoxContainer.new()
|
||||
_forge_workbench.add_theme_constant_override("separation", 6)
|
||||
var wb_hint := UiKit.label("工作间两区并列:武器锻造区 / 护甲锻造区",UiKit.FS_HINT, true, UiKit.INK)
|
||||
wb_hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
wb_hint.custom_minimum_size = Vector2(280, 0)
|
||||
_forge_workbench.add_child(wb_hint)
|
||||
var wb_body := UiKit.label("武器/护甲路线升级随「锻造工坊实装批」开放——本批为占位面板,无任何购买入口。",UiKit.FS_HINT, false, UiKit.STONE_DEEP)
|
||||
wb_body.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
wb_body.custom_minimum_size = Vector2(280, 0)
|
||||
_forge_workbench.add_child(wb_body)
|
||||
var forge_holder := VBoxContainer.new()
|
||||
forge_holder.add_theme_constant_override("separation", 6)
|
||||
forge_holder.add_child(_forge_lock_l)
|
||||
forge_holder.add_child(_forge_workbench)
|
||||
var forge_panel := _counter_panel("锻造工坊", UiKit.TEX_ICON_FORGE, forge_holder, 300)
|
||||
forge_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
mid.add_child(forge_panel)
|
||||
# 迭代 11 第①批 T4 工坊扩容(P2 甲程序占位——同调装配段+Y 升级行入工坊柜台,行板复用;行数增长以
|
||||
# 柜台内滚承载,冻结三柜台版式与另两柜台零触碰,视觉终稿归营地批)
|
||||
var ws_scroll := ScrollContainer.new()
|
||||
ws_scroll.custom_minimum_size = Vector2(340, 336)
|
||||
ws_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL # 2026-10-06 用户实测修——纵向撑满面板余高(固定 336=行断流但面板下半空着,滚动条提前出现)
|
||||
ws_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
|
||||
# 柜三·秘宝匣(钥匙门——ks_workshop 点亮前锁定态;点亮=现工坊内容整体迁入:六行+同调装配段+Y 升级行)
|
||||
_ws_lock_l = UiKit.label("🔒 秘宝匣未开放\n\n点亮树上「秘宝匣」钥匙节点后展开。\n(前置=脊柱生命第 1 级)",UiKit.FS_HINT, false, UiKit.STONE_DEEP)
|
||||
_ws_lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_ws_lock_l.custom_minimum_size = Vector2(280, 0)
|
||||
_ws_scroll = ScrollContainer.new()
|
||||
_ws_scroll.custom_minimum_size = Vector2(300, 336)
|
||||
_ws_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
_ws_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
_workshop_holder = VBoxContainer.new()
|
||||
_workshop_holder.add_theme_constant_override("separation", 6)
|
||||
ws_scroll.add_child(_workshop_holder)
|
||||
var ws_panel := _counter_panel("道具工坊", UiKit.TEX_ICON_WORKSHOP, ws_scroll, 356)
|
||||
_ws_scroll.add_child(_workshop_holder)
|
||||
var ws_holder := VBoxContainer.new()
|
||||
ws_holder.add_theme_constant_override("separation", 6)
|
||||
ws_holder.add_child(_ws_lock_l)
|
||||
ws_holder.add_child(_ws_scroll)
|
||||
var ws_panel := _counter_panel("秘宝匣", UiKit.TEX_ICON_WORKSHOP, ws_holder, 300)
|
||||
ws_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
mid.add_child(ws_panel)
|
||||
|
||||
# 柜四·奖励格图鉴(常开独立位——结构位预留,内容排 Alpha)
|
||||
var atlas_holder := VBoxContainer.new()
|
||||
var atlas_panel := _counter_panel("奖励格图鉴", UiKit.TEX_ICON_ATLAS, atlas_holder, 356)
|
||||
var atlas_panel := _counter_panel("奖励格图鉴", UiKit.TEX_ICON_ATLAS, atlas_holder, 300)
|
||||
atlas_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
mid.add_child(atlas_panel)
|
||||
var lock_l := UiKit.label(" —— 柜台结构位预留:内容排 Alpha(地图战斗资源差异化批)",UiKit.FS_HINT, false, UiKit.STONE)
|
||||
lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
lock_l.custom_minimum_size = Vector2(300, 0)
|
||||
lock_l.custom_minimum_size = Vector2(280, 0)
|
||||
atlas_holder.add_child(lock_l)
|
||||
# 天赋树面板(迭代 12 T9——程序占位:图鉴柜台下段承载树行〔脉分组行板+购买/切换钮〕;
|
||||
# 「三柜台→一树」字面重构归营地视觉批披露,本批功能重构程序占位先行)
|
||||
_tree_holder = VBoxContainer.new()
|
||||
_tree_holder.add_theme_constant_override("separation", 6)
|
||||
var tree_scroll := ScrollContainer.new()
|
||||
tree_scroll.custom_minimum_size = Vector2(300, 200)
|
||||
tree_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL # 2026-10-06 同修——树段内滚撑满图鉴柜余高
|
||||
tree_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
tree_scroll.add_child(_tree_holder)
|
||||
atlas_holder.add_child(tree_scroll)
|
||||
|
||||
# ── 底栏:关闭钮(左)/ 操作注记(右)+ 最近冒险条带 ──
|
||||
var close_btn := UiKit.button("关闭营地(回开始界面)")
|
||||
@@ -137,7 +172,7 @@ func _init() -> void:
|
||||
var spacer2 := Control.new()
|
||||
spacer2.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
bottom.add_child(spacer2)
|
||||
var caption := UiKit.label("每购得一件永久强化,对应柜台亮起一格暖光 · 价格为钥石",UiKit.FS_HINT, false, UiKit.WARM_SOFT)
|
||||
var caption := UiKit.label("树上点亮 · 工作间随钥匙展开 · 图鉴常开",UiKit.FS_HINT, false, UiKit.WARM_SOFT)
|
||||
bottom.add_child(caption)
|
||||
col.add_child(bottom)
|
||||
_recent_list = UiKit.label("",UiKit.FS_HINT, false, UiKit.WARM_SOFT)
|
||||
@@ -163,7 +198,7 @@ func _counter_panel(title_text: String, icon_path: String, holder: Control, min_
|
||||
head.add_theme_constant_override("separation", 6)
|
||||
band.add_child(head)
|
||||
if icon_path != "":
|
||||
var ic := UiKit.icon_rect(icon_path, 32) # P6 甲②(2026-10-06):柜台头带 64 源 0.5×(原 20 违 §8.2);缺位 null → 纯文本回退
|
||||
var ic := UiKit.icon_rect(icon_path, 32) # P6 甲②(2026-10-06):柜台头带 64 源 0.5×;缺位 null → 纯文本回退
|
||||
if ic != null:
|
||||
head.add_child(ic)
|
||||
var l := UiKit.label(title_text,UiKit.FS_BODY, true, UiKit.WARM)
|
||||
@@ -174,15 +209,17 @@ func _counter_panel(title_text: String, icon_path: String, holder: Control, min_
|
||||
|
||||
# ---- 柜台行构建(open_camp 重建;行结构仿 ShopOverlay 商品卡——亮板双色墨制)----
|
||||
|
||||
# 天赋树段重建(迭代 12 T9 程序占位——脉分组行板:节点名/等级/价/购买钮 + Fork 组侧切换钮;
|
||||
# 天赋树段重建(迭代 21 T6——树淬炼柜台主界面:脉分组行板:节点名/等级/价/购买钮 + Fork 双态切换钮;
|
||||
# 树数据 = 组合根 _camp_data().tree 投影 [{node_id, display_name, vein, kind, level, price, can_buy,
|
||||
# currency}...] + fork_groups [{group, active_side, sides:[{node_id, side, display_name, owned}]}])
|
||||
# currency}...] + fork_groups [{group, node_id, active_side, owned, can_buy, price, sides:[{side, display_name, effects}]}])
|
||||
func _rebuild_tree(data: Dictionary) -> void:
|
||||
for c in _tree_holder.get_children():
|
||||
c.queue_free()
|
||||
_tree_buy_btns.clear()
|
||||
_tree_fork_btns.clear()
|
||||
var tree: Array = data.get("tree", []) as Array
|
||||
var forks: Array = data.get("fork_groups", []) as Array
|
||||
var vein_names := {"spine": "·主干脊柱·", "forge": "·锻造脉·", "keystone": "·钥石脉·", "attune": "·同调脉·"}
|
||||
var vein_names := {"spine": "·脊柱淬炼·", "forge": "·锻造脉·", "keystone": "·钥石脉·", "attune": "·同调脉·"}
|
||||
var last_vein := ""
|
||||
for row in tree:
|
||||
var vein := String(row.get("vein", ""))
|
||||
@@ -199,59 +236,70 @@ func _rebuild_tree(data: Dictionary) -> void:
|
||||
name_l.clip_text = true
|
||||
hb.add_child(name_l)
|
||||
if String(row.get("kind", "")) == "fork":
|
||||
continue # Fork 行去 UI 化——由 fork_groups 段承载切换钮
|
||||
continue # Fork 行去行板化——由 fork_groups 段承载双态切换钮
|
||||
var btn := UiKit.button("购买 %d 钥石" % int(row.get("price", 0)), 0.0) # 柜台行板紧凑钮豁免宽度基准(树全钥石单币 2026-10-07 裁甲)
|
||||
btn.pressed.connect(func(): tree_buy_requested.emit(String(row.get("node_id", ""))))
|
||||
btn.disabled = not bool(row.get("can_buy", false))
|
||||
hb.add_child(btn)
|
||||
_tree_buy_btns.append({"node_id": String(row.get("node_id", "")), "btn": btn})
|
||||
_tree_holder.add_child(hb)
|
||||
for fg in forks:
|
||||
var group := String(fg.get("group", ""))
|
||||
var active := String(fg.get("active_side", ""))
|
||||
var owned := bool(fg.get("owned", false))
|
||||
var fb := HBoxContainer.new()
|
||||
fb.add_theme_constant_override("separation", 6)
|
||||
var gl := UiKit.label("%s[激活:%s]" % [String(fg.get("display_name", group)), active if active != "" else "未激活"], 13, false, UiKit.INK_LABEL)
|
||||
var state_txt := "未解锁"
|
||||
if owned:
|
||||
state_txt = "激活:%s" % active
|
||||
var gl := UiKit.label("%s[%s]" % [String(fg.get("display_name", group)), state_txt], 13, false, UiKit.INK_LABEL)
|
||||
fb.add_child(gl)
|
||||
for side_row in (fg.get("sides", []) as Array):
|
||||
var sd := String(side_row.get("side", ""))
|
||||
var sb := UiKit.button(String(side_row.get("display_name", sd)), 0.0) # 行板紧凑钮豁免
|
||||
sb.pressed.connect(func(): tree_fork_toggled.emit(group, sd))
|
||||
sb.disabled = not bool(side_row.get("owned", false))
|
||||
fb.add_child(sb)
|
||||
if not owned:
|
||||
var bb := UiKit.button("解锁 %d 钥石" % int(fg.get("price", 0)), 0.0) # 行板紧凑钮豁免
|
||||
bb.pressed.connect(func(): tree_buy_requested.emit(String(fg.get("node_id", ""))))
|
||||
bb.disabled = not bool(fg.get("can_buy", false))
|
||||
fb.add_child(bb)
|
||||
_tree_buy_btns.append({"node_id": String(fg.get("node_id", "")), "btn": bb})
|
||||
else:
|
||||
for side_row in (fg.get("sides", []) as Array):
|
||||
var sd := String(side_row.get("side", ""))
|
||||
var sb := UiKit.button(String(side_row.get("display_name", sd)), 0.0) # 行板紧凑钮豁免
|
||||
sb.pressed.connect(func(): tree_fork_toggled.emit(group, sd))
|
||||
fb.add_child(sb)
|
||||
_tree_fork_btns.append({"group": group, "side": sd, "btn": sb})
|
||||
_tree_holder.add_child(fb)
|
||||
|
||||
|
||||
# 打开营地(全量重建三柜台行 + 同调装配段 + 天赋树段 + 最近冒险——数据 = 组合根 _camp_data() 投影)
|
||||
# 工作间钥匙门(迭代 21 T6——锻造工坊/秘宝匣柜台锁定态切换:节点点亮=展开、未点亮=锁定行;
|
||||
# 图鉴常开无门槛。判据读树投影行 level——零核心访问,纯视图)
|
||||
func _refresh_key_gates(data: Dictionary) -> void:
|
||||
var lvl_by_id := {}
|
||||
for row in (data.get("tree", []) as Array):
|
||||
lvl_by_id[String(row.get("node_id", ""))] = int(row.get("level", 0))
|
||||
var forge_open := int(lvl_by_id.get("forge_workshop", 0)) >= 1
|
||||
var ws_open := int(lvl_by_id.get("ks_workshop", 0)) >= 1
|
||||
_forge_lock_l.visible = not forge_open
|
||||
_forge_workbench.visible = forge_open
|
||||
_ws_lock_l.visible = not ws_open
|
||||
_ws_scroll.visible = ws_open
|
||||
|
||||
|
||||
# 打开营地(全量重建四柜台行 + 同调装配段 + 最近冒险;树段与钥匙门随 refresh_camp 重建——
|
||||
# 数据 = 组合根 _camp_data() 投影)
|
||||
func open_camp(data: Dictionary) -> void:
|
||||
_rebuild_forge(data)
|
||||
_rebuild_workshop(data)
|
||||
_rebuild_attune(data)
|
||||
_rebuild_tree(data)
|
||||
_rebuild_workshop(data) # 段尾链 _rebuild_attune(同调装配段+Y 升级行随秘宝匣柜台内容)
|
||||
_rebuild_recent(data)
|
||||
refresh_camp(data)
|
||||
visible = true
|
||||
|
||||
|
||||
# 状态重刷(购买后调用——余额/价格/禁用态/工坊状态就地改文案,零重建)
|
||||
# 状态重刷(购买后调用——余额/树行板/钥匙门/工坊状态就地重建或改文案;树行板随刷重建=按钮
|
||||
# 禁用态与门槛/cap 同步,12 行重建开销可忽略)
|
||||
func refresh_camp(data: Dictionary) -> void:
|
||||
var balance := int(data.get("balance", 0))
|
||||
_balance_label.text = "钥石 %d" % balance
|
||||
var forge_by_attr := {}
|
||||
for row in data.get("forge", []):
|
||||
forge_by_attr[String(row.get("attr", ""))] = row
|
||||
for card in _forge_rows:
|
||||
var row: Dictionary = forge_by_attr.get(String(card.attr), {})
|
||||
if row.is_empty():
|
||||
continue
|
||||
card.level_l.text = "Lv %d" % int(row.get("level", 0))
|
||||
card.price_l.text = "%d 钥石" % int(row.get("price", 0))
|
||||
# M3:满级态文案(demo ②——已满级替代价签与按钮动词;数据无 maxed 字段时零影响)
|
||||
if bool(row.get("maxed", false)):
|
||||
card.btn.text = "已满级"
|
||||
card.btn.disabled = true
|
||||
else:
|
||||
card.btn.text = "锻造 +1 级"
|
||||
card.btn.disabled = balance < int(row.get("price", 0)) # 余额不足禁用置灰
|
||||
_rebuild_tree(data)
|
||||
_refresh_key_gates(data)
|
||||
var ws_by_id := {}
|
||||
for row in data.get("workshop", []):
|
||||
ws_by_id[String(row.get("item_id", ""))] = row
|
||||
@@ -299,62 +347,15 @@ func refresh_camp(data: Dictionary) -> void:
|
||||
_refresh_btn.disabled = bool(rf.get("maxed", false)) or balance < int(rf.get("price", 0))
|
||||
|
||||
|
||||
func _rebuild_forge(data: Dictionary) -> void:
|
||||
_forge_rows.clear()
|
||||
for child in _forge_holder.get_children():
|
||||
child.queue_free()
|
||||
for row in data.get("forge", []):
|
||||
_forge_rows.append(_build_forge_row(_forge_holder, row))
|
||||
|
||||
|
||||
func _build_forge_row(holder: VBoxContainer, row: Dictionary) -> Dictionary:
|
||||
var card := PanelContainer.new()
|
||||
card.add_theme_stylebox_override("panel", UiKit.panel_flat(ROW_PLATE_COL, UiKit.PANEL_BORDER,UiKit.RADIUS_OBJECT, 1, Vector4(10, 4, 10, 4)))
|
||||
var line := HBoxContainer.new()
|
||||
line.add_theme_constant_override("separation", 8) # 2026-10-06 营地溢出修(12→8——行 min 压回定宽内)
|
||||
card.add_child(line)
|
||||
var attr := String(row.get("attr", ""))
|
||||
# M3(UI 落码批):行首 20px 语义图标(图标+形状+颜色三通道——demo ②;子串映射容错 attr 键名演进)
|
||||
var icon_key := ""
|
||||
var attr_l := attr.to_lower()
|
||||
if attr_l.contains("attack"):
|
||||
icon_key = UiKit.TEX_ICON_SWORD
|
||||
elif attr_l.contains("armor") or attr_l.contains("ac"):
|
||||
icon_key = UiKit.TEX_ICON_SHIELD
|
||||
elif attr_l.contains("hp") or attr_l.contains("life"):
|
||||
icon_key = UiKit.TEX_ICON_HEART
|
||||
elif attr_l.contains("dig"):
|
||||
icon_key = UiKit.TEX_ICON_SHOVEL
|
||||
if icon_key != "":
|
||||
var ic := UiKit.icon_rect(icon_key, 20)
|
||||
if ic != null:
|
||||
line.add_child(ic)
|
||||
var name_l := UiKit.label("%s %+d/级" % [String(row.get("display_name", "")), int(row.get("flat", 0))], 14, true, UiKit.INK)
|
||||
name_l.custom_minimum_size = Vector2(112, 0)
|
||||
name_l.clip_text = true # 2026-10-06 营地溢出修——文字全宽不入 min(三柜等宽分配前提)
|
||||
name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
line.add_child(name_l)
|
||||
var level_l := UiKit.label("",UiKit.FS_HINT, true, UiKit.INK_ON_DEEP)
|
||||
# M3:Lv 章化(木底小章——demo lvtag;Label normal 槽挂样式盒)
|
||||
level_l.add_theme_stylebox_override("normal", UiKit.panel_flat(UiKit.CAMP_HEADER_BAND, Color(0, 0, 0, 0),UiKit.RADIUS_OBJECT, 0, Vector4(6, 0, 6, 0)))
|
||||
level_l.custom_minimum_size = Vector2(44, 0)
|
||||
line.add_child(level_l)
|
||||
var price_l := UiKit.label("",UiKit.FS_HINT, false, UiKit.STONE_DEEP) # 尾裁微批a 2026-10-08——STONE 对行板 4.02:1 越带,换灰石深化 6.70:1
|
||||
price_l.custom_minimum_size = Vector2(64, 0)
|
||||
price_l.clip_text = true
|
||||
price_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
line.add_child(price_l)
|
||||
var btn := UiKit.button("锻造 +1 级", 0.0) # 柜台行板紧凑钮豁免宽度基准
|
||||
btn.pressed.connect(_emit_buy_requested.bind(attr))
|
||||
line.add_child(btn)
|
||||
holder.add_child(card)
|
||||
return {"attr": attr, "level_l": level_l, "price_l": price_l, "btn": btn}
|
||||
|
||||
|
||||
func _rebuild_workshop(data: Dictionary) -> void:
|
||||
_workshop_rows.clear()
|
||||
for child in _workshop_holder.get_children():
|
||||
child.queue_free()
|
||||
_attune_nodes.clear()
|
||||
_attune_btns.clear()
|
||||
_attune_header_l = null
|
||||
_refresh_l = null
|
||||
_refresh_btn = null
|
||||
for row in data.get("workshop", []):
|
||||
var card := PanelContainer.new()
|
||||
card.add_theme_stylebox_override("panel", UiKit.panel_flat(ROW_PLATE_COL, UiKit.PANEL_BORDER,UiKit.RADIUS_OBJECT, 1, Vector4(10, 4, 10, 4)))
|
||||
@@ -366,8 +367,8 @@ func _rebuild_workshop(data: Dictionary) -> void:
|
||||
name_l.clip_text = true
|
||||
name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
line.add_child(name_l)
|
||||
var progress_l := UiKit.label("",UiKit.FS_HINT, false, UiKit.STONE_DEEP) # 尾裁微批a 2026-10-08——同行价格墨同案(STONE 对行板 4.02:1 越带→6.70:1)
|
||||
progress_l.custom_minimum_size = Vector2(150, 0) # 2026-10-06 修——110 截掉阈值数字(「累计通关 28 / 2 次」≈154px)
|
||||
var progress_l := UiKit.label("",UiKit.FS_HINT, false, UiKit.STONE_DEEP) # 尾裁微批a 2026-10-08——同行价格墨同案
|
||||
progress_l.custom_minimum_size = Vector2(150, 0)
|
||||
progress_l.clip_text = true # 营地溢出修——文字全宽不撑柜
|
||||
progress_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
line.add_child(progress_l)
|
||||
@@ -379,14 +380,12 @@ func _rebuild_workshop(data: Dictionary) -> void:
|
||||
line.add_child(status_l)
|
||||
_workshop_holder.add_child(card)
|
||||
_workshop_rows.append({"item_id": String(row.get("item_id", "")), "progress_l": progress_l, "status_l": status_l})
|
||||
_rebuild_attune(data)
|
||||
|
||||
|
||||
# 同调装配段重建(迭代 11 第①批 T4——工坊柜台尾部追加;Slots 点亮+九件行板+Y 升级行,程序占位面)
|
||||
# 同调装配段重建(迭代 11 第①批 T4——秘宝匣柜台尾部段〔迭代 21 T6 随柜台迁入〕;Slots 点亮+
|
||||
# 九件行板+Y 升级行,程序占位面)
|
||||
func _rebuild_attune(data: Dictionary) -> void:
|
||||
for n in _attune_nodes:
|
||||
n.queue_free()
|
||||
_attune_nodes.clear()
|
||||
_attune_btns.clear()
|
||||
var att: Dictionary = data.get("attune", {})
|
||||
_attune_header_l = UiKit.label("",UiKit.FS_HINT, true, UiKit.INK)
|
||||
_workshop_holder.add_child(_attune_header_l)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 生涯覆盖层·只读全屏页(迭代 18 卡④ 生涯界面实装批·期一 2026-10-07——需求卡片 v1.2 裁点全甲;
|
||||
# 设计权威 design/gdd/systems/跨局循环/生涯.md v0.1 期一范围)。
|
||||
# 裁决落形:P1/P2 甲=汇总段六类九项(冒险总局数/通关局数/各关通关次数/Boss 首杀/Boss 总击杀/
|
||||
# 钥石余额/属性锻造/道具工坊/天赋树——逐行只读);P3 甲=登记 modal_stack 营地同档(设置层旁、
|
||||
# 确认弹窗之前——登记序=树序=z 序,组合根 addChild 同位);P4 甲=零跳转(页内零「前往他处」
|
||||
# 入口,仅只读展示+关闭)+局内不放生涯入口(开始覆盖层「营地与系统」组唯一入口)。
|
||||
# 裁决落形:P1/P2 甲=汇总段两类六项(冒险总局数/通关局数/各关通关次数/Boss 首杀/Boss 总击杀/
|
||||
# 钥石余额——逐行只读;原「三柜台解锁完成度概览」三投影〔属性锻造/道具工坊/天赋树〕整行删
|
||||
# ——2026-10-12 用户裁:生涯页不统计天赋树与账号成长态,只统计终身计数);P3 甲=登记 modal_stack
|
||||
# 营地同档(设置层旁、确认弹窗之前——登记序=树序=z 序,组合根 addChild 同位);P4 甲=零跳转
|
||||
# (页内零「前往他处」入口,仅只读展示+关闭)+局内不放生涯入口(开始覆盖层「营地与系统」组唯一入口)。
|
||||
# 期二/期三内容(历史回看/图鉴/单局明细)零触碰——页内无任何期二占位件。
|
||||
# 只读契约(需求卡片契约 3/4):数据快照由组合根 open_career 喂入(每次开层实时读 SaveManager
|
||||
# 计数访问器 + MetaProgression 访问器重建——暂停层 R4 同构,快照仅单次开层生命周期,跨开层
|
||||
@@ -23,9 +24,6 @@ var _clears_label: Label # 各关通关次数(一行三关
|
||||
var _boss_first_label: Label # Boss 首杀(已首杀关数/总关数)
|
||||
var _boss_kills_label: Label # Boss 总击杀(含重复)
|
||||
var _keystone_badge: UiKit.KeystoneBadge # 钥石余额(badge 工厂件——暂停层同形制)
|
||||
var _forge_label: Label # 属性锻造(已购等级合计)
|
||||
var _workshop_label: Label # 道具工坊(已解锁件数/总件数)
|
||||
var _tree_label: Label # 天赋树已购概览(聚合单条——暂停层同构)
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
@@ -57,8 +55,8 @@ func _init() -> void:
|
||||
_boss_first_label = _row(box, "Boss 首杀")
|
||||
_boss_kills_label = _row(box, "Boss 总击杀")
|
||||
|
||||
# 组二·资源与成长(钥石/三柜台解锁概览——现役只读投影)
|
||||
_section_head(box, "资源与成长")
|
||||
# 组二·资源(钥石余额——终身计数域同段只读投影;账号成长态概览三投影已删——2026-10-12 用户裁)
|
||||
_section_head(box, "资源")
|
||||
var bal_row := HBoxContainer.new()
|
||||
bal_row.add_theme_constant_override("separation", 8)
|
||||
box.add_child(bal_row)
|
||||
@@ -66,10 +64,6 @@ func _init() -> void:
|
||||
bal_row.add_child(bal_name)
|
||||
_keystone_badge = UiKit.keystone_badge("钥石 0")
|
||||
bal_row.add_child(_keystone_badge)
|
||||
_forge_label = _row(box, "属性锻造")
|
||||
_workshop_label = _row(box, "道具工坊")
|
||||
_tree_label = _row(box, "天赋树")
|
||||
_tree_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
|
||||
visible = false # 显式初始化——不靠树序巧合(缺陷⑤家族纪律)
|
||||
|
||||
@@ -86,39 +80,9 @@ func open_career(data: Dictionary) -> void:
|
||||
_boss_first_label.text = "%d/%d 关" % [int(data.get("boss_first_kills", 0)), int(data.get("boss_first_total", 0))]
|
||||
_boss_kills_label.text = "%d 次" % int(data.get("boss_kills", 0))
|
||||
_keystone_badge.set_text("钥石 %d" % int(data.get("keystone", 0)))
|
||||
_forge_label.text = "已购 %d 级(三属性合计)" % int(data.get("forge_levels_total", 0))
|
||||
var ws: Array = data.get("workshop", []) as Array
|
||||
var unlocked := 0
|
||||
for row in ws:
|
||||
if bool(row.get("unlocked", false)):
|
||||
unlocked += 1
|
||||
_workshop_label.text = "已解锁 %d/%d 件" % [unlocked, ws.size()] if not ws.is_empty() else "—"
|
||||
_tree_label.text = _tree_summary(data)
|
||||
visible = true
|
||||
|
||||
|
||||
# 天赋树聚合文案(暂停层 _rebuild_tree 同构——已购节点等级+Fork 激活侧;零已购=空态句)
|
||||
func _tree_summary(data: Dictionary) -> String:
|
||||
var owned: Array[String] = []
|
||||
for row in (data.get("tree", []) as Array):
|
||||
var lvl := int(row.get("level", 0))
|
||||
if lvl > 0:
|
||||
owned.append("%s Lv%d/%d" % [String(row.get("display_name", "")), lvl, int(row.get("cap", 1))])
|
||||
for fg in (data.get("fork_groups", []) as Array):
|
||||
var active := String(fg.get("active_side", ""))
|
||||
if active == "":
|
||||
continue
|
||||
var side_name := active
|
||||
for sd in (fg.get("sides", []) as Array):
|
||||
if String(sd.get("side", "")) == active:
|
||||
side_name = String(sd.get("display_name", active))
|
||||
break
|
||||
owned.append("%s·激活 %s" % [String(fg.get("display_name", "")), side_name])
|
||||
if owned.is_empty():
|
||||
return "尚未购买任何节点"
|
||||
return "已购 %d 项:%s" % [owned.size(), " · ".join(owned)]
|
||||
|
||||
|
||||
func _emit_closed() -> void:
|
||||
closed.emit()
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
# 局内暂停层·只读(迭代 17 卡③ 暂停层只读入口批 2026-10-06——需求卡片 v1.1;事实源=06-待办清单
|
||||
# 「解锁界面与钥石呈现的交互形态(残余:暂停层只读入口)」)。
|
||||
# 裁决落形:P1 丁=入口为战斗屏 HUD / 关卡地图屏常驻角标(入口装配在 ui_assembler/两屏,本类不含入口);
|
||||
# P2 甲=登记 modal_stack 设置层同档(确认弹窗之前)带 dim 遮罩;P3 甲=展示钥石余额+三柜台进度
|
||||
# (属性锻造三行已购等级 / 道具工坊六件解锁态 / 天赋树已购概览——层内行序与分组复用营地屏同序);
|
||||
# P4 甲=进出零动效直开直关(无 tween 无 AnimationPlayer——R5)。
|
||||
# P2 甲=登记 modal_stack 设置层同档(确认弹窗之前)带 dim 遮罩;P3 甲=展示钥石余额+进度概览
|
||||
# (道具工坊六件解锁态 / 天赋树已购概览——层内行序与分组复用营地屏同序;属性锻造三行段随
|
||||
# 迭代 21 柜台退役连带删除——数据源 forge_levels 域已清退);P4 甲=进出零动效直开直关
|
||||
# (无 tween 无 AnimationPlayer——R5)。
|
||||
# 只读契约:R4 数据只读同源——账面快照由组合根 open_pause 喂入(每次开层实时读 MetaProgression
|
||||
# 访问器重建,快照仅用单次开层生命周期,跨开层零持有);本类零核心访问、零写通路——唯一信号
|
||||
# closed(关闭意图,组合根经模态栈单口收口)。R1 公共件单点:全部构成件经 UiKit 工厂产出
|
||||
@@ -15,7 +16,6 @@ extends Control
|
||||
signal closed
|
||||
|
||||
var _balance_badge: UiKit.KeystoneBadge # 钥石余额徽(badge 工厂件——Q10 甲)
|
||||
var _forge_holder: VBoxContainer
|
||||
var _workshop_holder: VBoxContainer
|
||||
var _tree_label: Label
|
||||
|
||||
@@ -40,9 +40,6 @@ func _init() -> void:
|
||||
bal_row.add_child(_balance_badge)
|
||||
|
||||
box.add_child(HSeparator.new())
|
||||
_section_head(box, "属性锻造 · 已购等级")
|
||||
_forge_holder = _section_holder(box)
|
||||
|
||||
_section_head(box, "道具工坊 · 解锁态")
|
||||
_workshop_holder = _section_holder(box)
|
||||
|
||||
@@ -79,32 +76,11 @@ func _section_holder(box: VBoxContainer) -> VBoxContainer:
|
||||
|
||||
func open_pause(data: Dictionary) -> void:
|
||||
_balance_badge.set_text("钥石 %d" % int(data.get("balance", 0)))
|
||||
_rebuild_forge(data)
|
||||
_rebuild_workshop(data)
|
||||
_rebuild_tree(data)
|
||||
visible = true
|
||||
|
||||
|
||||
# 属性锻造三行(营地屏同序同文案族——名称+每级 flat / 已购等级;只读零价格零按钮)
|
||||
func _rebuild_forge(data: Dictionary) -> void:
|
||||
for c in _forge_holder.get_children():
|
||||
c.queue_free()
|
||||
for row in (data.get("forge", []) as Array):
|
||||
var line := HBoxContainer.new()
|
||||
line.add_theme_constant_override("separation", 8)
|
||||
var name_l := UiKit.label("%s %+d/级" % [String(row.get("display_name", "")), int(row.get("flat", 0))],UiKit.FS_HINT, true, UiKit.INK)
|
||||
name_l.custom_minimum_size = Vector2(150, 0)
|
||||
name_l.clip_text = true
|
||||
name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
line.add_child(name_l)
|
||||
var spacer := Control.new()
|
||||
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
line.add_child(spacer)
|
||||
var lvl_l := UiKit.label("Lv %d" % int(row.get("level", 0)),UiKit.FS_HINT, true, UiKit.INK_TITLE)
|
||||
line.add_child(lvl_l)
|
||||
_forge_holder.add_child(line)
|
||||
|
||||
|
||||
# 道具工坊六行(解锁态文案与营地柜台同口径——退役/封存/已解锁/未解锁;进度=解锁判定量读数)
|
||||
func _rebuild_workshop(data: Dictionary) -> void:
|
||||
for c in _workshop_holder.get_children():
|
||||
@@ -157,7 +133,7 @@ func _rebuild_tree(data: Dictionary) -> void:
|
||||
break
|
||||
owned.append("%s·激活 %s" % [String(fg.get("display_name", "")), side_name])
|
||||
if owned.is_empty():
|
||||
_tree_label.text = "尚未购买任何节点(天赋树在营地柜台三)"
|
||||
_tree_label.text = "尚未购买任何节点(天赋树在营地主柜台)"
|
||||
else:
|
||||
_tree_label.text = "已购 %d 项:%s" % [owned.size(), " · ".join(owned)]
|
||||
|
||||
|
||||
@@ -42,11 +42,11 @@ func _initialize() -> void:
|
||||
var shop_mode := "off"
|
||||
if args.size() >= 6 and args[5] in ["off", "on"]:
|
||||
shop_mode = args[5]
|
||||
# 特性/树分层开关(迭代 12 T7/T10——off=基线;formed=成型画像开局注入七流派各 2 件档一成型集
|
||||
# 特性/树分层开关(迭代 12 T7/T10——off=基线;formed=成型画像开局注入五事件族 14 件成型集
|
||||
# 〔测量面专用 params.relics_seed——真实局规则改写型特性只走局内随机渠道〕;ab_a/ab_b=Fork posture 两侧
|
||||
# rule 键集——A/B 面两侧差 <~3pp 验收载体)
|
||||
# rule 键集——A/B 面两侧差 <~3pp 验收载体;rules=任意树规则键集注入〔逗号分隔 args[7]——迭代 21 V19 三组配对载体〕)
|
||||
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", "rules"]:
|
||||
feat_mode = args[6]
|
||||
var feat_profile := {}
|
||||
if feat_mode == "formed":
|
||||
@@ -62,6 +62,8 @@ func _initialize() -> void:
|
||||
feat_profile = {"mode": "off", "tree_rules": ["posture_first_strike"]}
|
||||
elif feat_mode == "ab_b":
|
||||
feat_profile = {"mode": "off", "tree_rules": ["posture_bulwark"]}
|
||||
elif feat_mode == "rules":
|
||||
feat_profile = {"mode": "off", "tree_rules": String(args[7] if args.size() >= 8 else "").split(",")} # 迭代 21 V19——任意键集配对注入
|
||||
var db = load("res://core/config_db.gd").new()
|
||||
if not db.load_tables("res://data/tables", false):
|
||||
print("表加载失败")
|
||||
@@ -69,10 +71,12 @@ func _initialize() -> void:
|
||||
return
|
||||
var attune_profile := {}
|
||||
if attune_mode == "on":
|
||||
# 贪心画像:中间进度计数(通关 3/Boss 击杀 6)经谓词表求值 Slots;装配序 = 表序 passive 优先至
|
||||
# Slots-1 + 首个 active(slots=3 → [淬毒刃纹, 标中殉爆, 掘进蓄能],provisional 画像归调参批);
|
||||
# Y = 营地升级 Lv1 表值 1。autoplay 默认不刷——Y 仅注入容量,刷新消费归玩家策略面(钓鱼归玩家)。
|
||||
var slots: int = db.attunement_growth.slots({"clear_count": 3, "boss_kill_count": 6})
|
||||
# 贪心画像:Slots=3(迭代 21 V14 换代——槽位判定源=树同调位节点点亮数,模拟画像=三节点
|
||||
# 全点亮常量;原「中间进度计数经谓词表求值」随 attunement_growth 谓词表退役);装配序 =
|
||||
# 表序 passive 优先至 Slots-1 + 首个 active(slots=3 → [淬毒刃纹, 标中殉爆, 掘进蓄能],
|
||||
# provisional 画像归调参批);Y = 营地升级 Lv1 表值 1。autoplay 默认不刷——Y 仅注入容量,
|
||||
# 刷新消费归玩家策略面(钓鱼归玩家)。
|
||||
var slots: int = 3
|
||||
var passives: Array = []
|
||||
var actives: Array = []
|
||||
for r in db.treasure_attunement_rows():
|
||||
|
||||
+246
-151
@@ -116,6 +116,7 @@ func _run_core_smoke(db, rng) -> void:
|
||||
_smoke_save()
|
||||
_smoke_meta()
|
||||
_smoke_tree()
|
||||
_smoke_tree_rules()
|
||||
_smoke_node_map_templates()
|
||||
|
||||
|
||||
@@ -229,9 +230,8 @@ func _smoke_adventure_table() -> void:
|
||||
"恢复件 +6 当即回血(优秀档承袭)· AP +1(稀有档承袭)〔专长批 V14 换代〕")
|
||||
check(int(_db.adventure.curio_cap("gp")) == 99 and int(_db.adventure.curio_cap("attack")) == 99,
|
||||
"cap 占位在域(案②提前退役——99=实质无上限,实数归调参批)〔专长批 V8 形态面〕")
|
||||
check(_db.attunement_growth != null and int(_db.attunement_slot_cap()) == 3
|
||||
and Attunement.slots(_db, null) == 0,
|
||||
"同调位读数源=attunement_growth 谓词表(slot_cap 3;未注入档求值 0——transitional_sync_slots 过渡键已退役,迭代 11 第①批 T7)")
|
||||
check(Attunement.slots(_db, null) == 0,
|
||||
"同调位读数源=树同调位节点点亮数(迭代 21 T4 槽位收编——null 档求值 0;谓词表已退役)〔天赋树批 V14〕")
|
||||
# 迭代 19 卡④——提灯载体行(fog_lantern):新行加载/schema/同调目录投影+装配→战斗贯通(核心层半边;
|
||||
# 呈现半边归 ui_input_probe case⑮ 载体级段——测试三形态契约 9)
|
||||
var lantern_row: Dictionary = _db.treasure_items.row_by_id("fog_lantern")
|
||||
@@ -4327,19 +4327,19 @@ func _smoke_nine_types_gp_stream() -> void:
|
||||
|
||||
|
||||
# ---- 元进度·MetaTable 表结构(元进度实现批 T1——行值全 provisional 归元进度设计批配读数;
|
||||
# 注入契约三字段必齐 = forge/conversion 覆盖恰三,Boss 两档 first ≥ repeat)----
|
||||
# conversion 覆盖恰三,Boss 两档 first ≥ repeat;forge_products 行族已退役)----
|
||||
func _smoke_meta() -> void:
|
||||
print("--- 元进度·MetaTable 表结构 ---")
|
||||
var meta_tbl = _db.meta
|
||||
check(meta_tbl != null and meta_tbl.validate().is_empty(),
|
||||
"MetaTable 非空 + validate 零错(四行族词表封闭/唯一/连续/域界/first≥repeat/ratio 全过)")
|
||||
check(meta_tbl.forge_products.size() == 3 and meta_tbl.boss_keystone.size() == 3
|
||||
check(meta_tbl.boss_keystone.size() == 3
|
||||
and meta_tbl.workshop_items.size() == 6 and meta_tbl.conversion.size() == 3,
|
||||
"MetaTable 四行族规模 3/3/6/3(forge/Boss/换算恰三 · 工坊恰六)")
|
||||
"MetaTable 行族规模 3/6/3(Boss/工坊恰六/换算恰三——forge_products 行族已随迭代 21 T5 退役)")
|
||||
var attrs_ok := true
|
||||
for a in ["max_hp", "atk", "armor_class"]:
|
||||
attrs_ok = attrs_ok and not _db.meta_forge_row(a).is_empty() and not _db.meta_conversion_row(a).is_empty()
|
||||
check(attrs_ok, "forge/conversion attr 词表封闭恰全(注入契约三字段齐——meta_* 访问器可达)")
|
||||
attrs_ok = attrs_ok and not _db.meta_conversion_row(a).is_empty()
|
||||
check(attrs_ok, "conversion attr 词表封闭恰全(三属性轴——meta_* 访问器可达)")
|
||||
var boss_ok := true
|
||||
for li in [1, 2, 3]:
|
||||
var brow: Dictionary = _db.meta_boss_keystone_row(li)
|
||||
@@ -4360,41 +4360,20 @@ func _pause_overlay_projection() -> void:
|
||||
_meta_smoke_cleanup()
|
||||
var sv = _meta_new_save(_META_SMOKE_PATHS[0])
|
||||
var mp = load("res://core/meta_progression.gd").new(_db, sv)
|
||||
# 已购态夹具:余额/锻造两档非零 + 工坊首件解锁——投影可辨非全零白板
|
||||
# 已购态夹具:余额非零 + 工坊首件解锁——投影可辨非全零白板(属性锻造段已随柜台退役删除)
|
||||
sv.add_keystone(37)
|
||||
sv.set_forge_level("atk", 2)
|
||||
sv.set_forge_level("max_hp", 1)
|
||||
var ws_meta: Array = _db.meta_workshop_items()
|
||||
if not ws_meta.is_empty():
|
||||
sv.unlock_item(String(ws_meta[0].get("item_id", "")))
|
||||
sv.write_to_disk()
|
||||
# 投影拼装(main._pause_data 同构)+ 视图喂入
|
||||
var data := {"balance": mp.keystone_balance(), "forge": [], "workshop": mp.workshop_status(),
|
||||
var data := {"balance": mp.keystone_balance(), "workshop": mp.workshop_status(),
|
||||
"tree": mp.tree_camp_rows(), "fork_groups": mp.tree_fork_groups()}
|
||||
for attr in MetaTable.FORGE_ATTRS:
|
||||
var frow: Dictionary = _db.meta_forge_row(attr)
|
||||
data["forge"].append({"attr": attr, "display_name": String(frow.get("display_name", "")),
|
||||
"level": mp.forge_level(attr), "flat": int(frow.get("flat_value", 0))})
|
||||
var po = load("res://presentation/views/pause_overlay.gd").new()
|
||||
po.open_pause(data)
|
||||
check(po.visible, "暂停层开层可见(open_pause 直开——P4 甲零动效路径)")
|
||||
check(String(po._balance_badge.value_label.text) == "钥石 %d" % mp.keystone_balance(),
|
||||
"余额徽投影 = keystone_balance() 读数(%s)" % po._balance_badge.value_label.text)
|
||||
var forge_ok: bool = po._forge_holder.get_child_count() == MetaTable.FORGE_ATTRS.size()
|
||||
var fi := 0
|
||||
for attr in MetaTable.FORGE_ATTRS:
|
||||
if fi >= po._forge_holder.get_child_count():
|
||||
forge_ok = false
|
||||
break
|
||||
var lbls: Array = []
|
||||
for ch in po._forge_holder.get_child(fi).get_children():
|
||||
if ch is Label:
|
||||
lbls.append(ch)
|
||||
forge_ok = forge_ok and lbls.size() == 2 \
|
||||
and String(lbls[0].text).begins_with(String(_db.meta_forge_row(attr).get("display_name", ""))) \
|
||||
and String(lbls[1].text) == "Lv %d" % mp.forge_level(attr)
|
||||
fi += 1
|
||||
check(forge_ok, "锻造三行投影逐行 = display_name + forge_level() 读数(已购夹具 atk Lv2 / max_hp Lv1 在列)")
|
||||
var ws: Array = mp.workshop_status()
|
||||
var ws_ok: bool = po._workshop_holder.get_child_count() == ws.size()
|
||||
for i in mini(ws.size(), po._workshop_holder.get_child_count()):
|
||||
@@ -4421,7 +4400,7 @@ func _pause_overlay_projection() -> void:
|
||||
if int(trow.get("level", 0)) > 0:
|
||||
owned += 1
|
||||
var tree_txt := String(po._tree_label.text)
|
||||
check(tree_txt == "尚未购买任何节点(天赋树在营地柜台三)" or tree_txt.begins_with("已购 %d 项:" % owned),
|
||||
check(tree_txt == "尚未购买任何节点(天赋树在营地主柜台)" or tree_txt.begins_with("已购 %d 项:" % owned),
|
||||
"天赋树概览聚合文案与已购节点数一致(零已购=空态句)")
|
||||
po.visible = false
|
||||
po.free()
|
||||
@@ -4435,36 +4414,24 @@ func _career_overlay_projection() -> void:
|
||||
print("--- 生涯页·只读投影一致性(隔离档 smoke_meta_career/career_v5)---")
|
||||
var sv = _meta_new_save(_META_SMOKE_PATHS[9])
|
||||
var mp = load("res://core/meta_progression.gd").new(_db, sv)
|
||||
# 已玩态夹具:两局收束(一通一止)+ 钥石/锻造/工坊首件/树首节点非零——投影可辨非全零白板
|
||||
# 已玩态夹具:两局收束(一通一止)+ 钥石非零——投影可辨非全零白板
|
||||
# (账号成长态三投影已删——2026-10-12 用户裁:生涯页不统计天赋树与账号成长态,只统计终身计数)
|
||||
sv.add_keystone(45)
|
||||
sv.set_forge_level("atk", 2)
|
||||
sv.set_forge_level("max_hp", 1)
|
||||
var ws0: Array = _db.meta_workshop_items()
|
||||
if not ws0.is_empty():
|
||||
sv.unlock_item(String(ws0[0].get("item_id", "")))
|
||||
sv.apply_adventure_summary({"result": "completed", "cleared": true, "levels_cleared": 3,
|
||||
"stopped_at": 0, "keystone_total": 0, "master_seed": 21})
|
||||
sv.apply_adventure_summary({"result": "abandoned", "cleared": false, "levels_cleared": 1,
|
||||
"stopped_at": 2, "keystone_total": 0, "master_seed": 22})
|
||||
var tree_rows: Array = mp.tree_camp_rows()
|
||||
var tree_node := ""
|
||||
if not tree_rows.is_empty():
|
||||
tree_node = String(tree_rows[0].get("node_id", ""))
|
||||
sv.set_tree_level(tree_node, 1)
|
||||
sv.write_to_disk()
|
||||
# 投影拼装(main._career_data 同构)+ 视图喂入
|
||||
var data := {"total": sv.adventure_total(), "cleared": sv.adventure_cleared(),
|
||||
"clears": [], "boss_first_kills": 0, "boss_first_total": _db.level_count(),
|
||||
"boss_kills": sv.boss_kill_count(), "keystone": mp.keystone_balance(), "forge_levels_total": 0,
|
||||
"workshop": mp.workshop_status(), "tree": mp.tree_camp_rows(), "fork_groups": mp.tree_fork_groups()}
|
||||
"boss_kills": sv.boss_kill_count(), "keystone": mp.keystone_balance()}
|
||||
for i in _db.level_count():
|
||||
var li: int = i + 1
|
||||
data["clears"].append({"display_name": String(_db.level_row(li).get("display_name", "")),
|
||||
"count": sv.clear_count(li)})
|
||||
if sv.boss_first_kill_marked(li):
|
||||
data["boss_first_kills"] += 1
|
||||
for attr in MetaTable.FORGE_ATTRS:
|
||||
data["forge_levels_total"] += mp.forge_level(attr)
|
||||
var co = load("res://presentation/views/career_overlay.gd").new()
|
||||
co.open_career(data)
|
||||
check(co.visible, "生涯页开层可见(open_career 直开——零动效路径)")
|
||||
@@ -4481,20 +4448,6 @@ func _career_overlay_projection() -> void:
|
||||
"Boss 首杀/总杀投影 = 首杀标记计数 + boss_kill_count()(%s / %s)" % [co._boss_first_label.text, co._boss_kills_label.text])
|
||||
check(String(co._keystone_badge.value_label.text) == "钥石 %d" % mp.keystone_balance(),
|
||||
"钥石余额徽投影 = keystone_balance() 读数(%s)" % co._keystone_badge.value_label.text)
|
||||
check(String(co._forge_label.text) == "已购 %d 级(三属性合计)" % data["forge_levels_total"],
|
||||
"属性锻造投影 = forge_level() 三属性合计(%s)" % co._forge_label.text)
|
||||
var ws_unlocked := 0
|
||||
for row in (mp.workshop_status() as Array):
|
||||
if bool(row.get("unlocked", false)):
|
||||
ws_unlocked += 1
|
||||
check(String(co._workshop_label.text) == "已解锁 %d/%d 件" % [ws_unlocked, (mp.workshop_status() as Array).size()],
|
||||
"道具工坊投影 = workshop_status() 解锁计数(%s——首件解锁夹具在列)" % co._workshop_label.text)
|
||||
var tree_expect := "尚未购买任何节点"
|
||||
if tree_node != "":
|
||||
var trow: Dictionary = tree_rows[0]
|
||||
tree_expect = "已购 1 项:%s Lv1/%d" % [String(trow.get("display_name", "")), int(trow.get("cap", 1))]
|
||||
check(String(co._tree_label.text) == tree_expect,
|
||||
"天赋树概览投影 = tree_camp_rows() 已购聚合(%s)" % co._tree_label.text)
|
||||
co.visible = false
|
||||
co.free()
|
||||
# 旧档载入显示(完成定义 6):v5 现役档形态(计数域两键不存在)升 v6 → 生涯页正常呈现缺省值
|
||||
@@ -4507,8 +4460,7 @@ func _career_overlay_projection() -> void:
|
||||
var mp_v5 = load("res://core/meta_progression.gd").new(_db, v5)
|
||||
var data_v5 := {"total": v5.adventure_total(), "cleared": v5.adventure_cleared(),
|
||||
"clears": [], "boss_first_kills": 0, "boss_first_total": _db.level_count(),
|
||||
"boss_kills": v5.boss_kill_count(), "keystone": mp_v5.keystone_balance(), "forge_levels_total": 0,
|
||||
"workshop": mp_v5.workshop_status(), "tree": mp_v5.tree_camp_rows(), "fork_groups": mp_v5.tree_fork_groups()}
|
||||
"boss_kills": v5.boss_kill_count(), "keystone": mp_v5.keystone_balance()}
|
||||
for i in _db.level_count():
|
||||
data_v5["clears"].append({"display_name": String(_db.level_row(i + 1).get("display_name", "")),
|
||||
"count": v5.clear_count(i + 1)})
|
||||
@@ -4544,43 +4496,30 @@ func _meta_new_save(path: String):
|
||||
return sv
|
||||
|
||||
|
||||
# ---- 元进度 ②几何定价 ③购买(余额充足持久 / 不足拒绝 / attr 出域拒绝)----
|
||||
# ---- 元进度 ②树购买面(迭代 21 T2——购买受理/持久/余额不足拒绝/未知节点拒绝;
|
||||
# 几何定价段随属性锻造柜台退役消亡,行价=表值 flat 单价)----
|
||||
func _meta_pricing_and_buy() -> void:
|
||||
print("--- 元进度·几何定价与购买(隔离档 smoke_meta_buy/dry)---")
|
||||
print("--- 元进度·树购买面(隔离档 smoke_meta_buy/dry)---")
|
||||
_meta_smoke_cleanup()
|
||||
var sv = _meta_new_save(_META_SMOKE_PATHS[0])
|
||||
var mp = load("res://core/meta_progression.gd").new(_db, sv)
|
||||
# ② 几何定价:tier 0/1/2 价格 == 迭代 floor(p × ratio/100) 复算值(tier 0 = base_price)且严格递增
|
||||
for attr in MetaTable.FORGE_ATTRS:
|
||||
var row: Dictionary = _db.meta_forge_row(attr)
|
||||
var base := int(row.get("base_price", 0))
|
||||
var ratio := int(row.get("price_ratio_pct", 100))
|
||||
var all_ok := true
|
||||
var prev := 0
|
||||
for tier in 3:
|
||||
sv.set_forge_level(attr, tier)
|
||||
var expect := base
|
||||
for _i in tier:
|
||||
expect = int(floor(float(expect) * float(ratio) / 100.0))
|
||||
all_ok = all_ok and mp.forge_price(attr) == expect and (tier == 0 or expect > prev)
|
||||
prev = expect
|
||||
check(all_ok, "%s 几何定价 tier 0/1/2 = 迭代 floor 复算且严格递增(base %d × ratio %d%%)" % [attr, base, ratio])
|
||||
sv.set_forge_level("atk", 0)
|
||||
# ③ 购买成功:级+1、余额−价;buy 内含写盘——reload 后持久
|
||||
# ③ 购买成功:级+1、余额−价;buy 内含写盘——reload 后持久(树全钥石单币——2026-10-07 用户裁甲)
|
||||
sv.add_keystone(1000)
|
||||
var bal0: int = sv.keystone_balance()
|
||||
var lvl0: int = sv.forge_level("atk")
|
||||
var price0: int = mp.forge_price("atk")
|
||||
check(mp.buy_forge("atk") and sv.forge_level("atk") == lvl0 + 1 and sv.keystone_balance() == bal0 - price0,
|
||||
"购买成功(Lv %d→%d、余额 %d−%d)" % [lvl0, lvl0 + 1, bal0, price0])
|
||||
var price0: int = int(_db.tree_node("sp_hp").get("price", 0))
|
||||
check(mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 1 and sv.keystone_balance() == bal0 - price0,
|
||||
"树购买受理(Lv 0→1、余额 %d−%d)" % [bal0, price0])
|
||||
var sv_r = _meta_new_save(_META_SMOKE_PATHS[0])
|
||||
check(sv_r.forge_level("atk") == lvl0 + 1 and sv_r.keystone_balance() == bal0 - price0, "购买持久(reload 后级与余额不变)")
|
||||
# ③ 余额不足 / attr 出域:false 零副作用
|
||||
check(sv_r.tree_level("sp_hp") == 1 and sv_r.keystone_balance() == bal0 - price0, "购买持久(reload 后级与余额不变)")
|
||||
# ③ 余额不足 / 未知节点:false 零副作用
|
||||
var sv_dry = _meta_new_save(_META_SMOKE_PATHS[1])
|
||||
var mp_dry = load("res://core/meta_progression.gd").new(_db, sv_dry)
|
||||
check(not mp_dry.buy_forge("atk") and sv_dry.keystone_balance() == 0 and sv_dry.forge_level("atk") == 0,
|
||||
"余额不足 false 零副作用(0 余额购买拒绝)")
|
||||
check(not mp_dry.buy_forge("luck") and sv_dry.keystone_balance() == 0, "attr 出域 false 零副作用(luck 不在词表)")
|
||||
var tn_before: Dictionary = sv_dry.tree_nodes()
|
||||
var fs_before: Dictionary = sv_dry.fork_sides()
|
||||
check(not mp_dry.tree_buy("sp_hp") and sv_dry.keystone_balance() == 0
|
||||
and sv_dry.tree_nodes() == tn_before and sv_dry.fork_sides() == fs_before,
|
||||
"余额不足 false 零副作用(0 余额购买拒绝——三域零变化〔天赋树批 V3〕)")
|
||||
check(not mp_dry.tree_buy("no_such_node"), "未知节点 false 零副作用(词表外出域)")
|
||||
|
||||
|
||||
# ---- 元进度 ④注入可见 + 白板成立(验收 4/2/3——程序面;MC 逐位回归归 T7 全量验证)----
|
||||
@@ -4588,9 +4527,15 @@ func _meta_injection() -> void:
|
||||
print("--- 元进度·注入可见与白板(隔离档 smoke_meta_white/dry)---")
|
||||
var sv = _meta_new_save(_META_SMOKE_PATHS[1])
|
||||
var mp = load("res://core/meta_progression.gd").new(_db, sv)
|
||||
sv.add_keystone(mp.forge_price("atk"))
|
||||
check(mp.buy_forge("atk") and int(mp.hero_override().get("atk", -1)) == int(_db.meta_forge_row("atk").get("flat_value", 0)),
|
||||
"买 atk 1 级 → hero_override().atk == 行 flat(验收4)")
|
||||
# 树生命淬炼注入(迭代 21 T4 换代公式:基础值+树淬炼 flat+锻造路线加值位预留 0——2 级 = +10;
|
||||
# 注入走调试口径直置等级〔两分法——购买链已在 _smoke_tree 覆盖,此处隔离行门槛干扰〕)
|
||||
sv.set_tree_level("sp_hp", 2)
|
||||
check(int(mp.hero_override().get("max_hp", -1)) == 2 * int(_db.tree_node("sp_hp").get("effects", {}).get("max_hp", 0)),
|
||||
"生命淬炼 2 级 → hero_override().max_hp == Σ 节点 effects×等级(验收4 换代〔天赋树批 V12〕)")
|
||||
check(int(mp.hero_override().get("atk", -1)) == 0 and int(mp.hero_override().get("armor_class", -1)) == 0,
|
||||
"攻/AC 树零来源(契约 9——树表零 atk/armor_class 行,override 二轴恒 0+预留位 0)")
|
||||
check(not mp.hero_override().has("hit"),
|
||||
"命中线在账未注入(契约 6——override 无 hit 字段〔天赋树批 V13〕)")
|
||||
var adr = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(920251))
|
||||
check(adr.start_adventure(920251, {"hero_override": mp.hero_override()}), "注入参数冒险启动(start_adventure params 通道)")
|
||||
var r = adr.runner()
|
||||
@@ -4717,8 +4662,11 @@ func _meta_settle_and_workshop() -> void:
|
||||
"Y 升级购买入账(Lv1 → 每冒险 +1 次刷新)")
|
||||
mp_a.settle_adventure({"result": "failed", "cleared": false, "levels_cleared": 1,
|
||||
"stopped_at": 2, "keystone_total": 0, "master_seed": 21})
|
||||
check(Attunement.slots(_db, sv_a) == 1 and sv_a.boss_kill_count() == 1,
|
||||
"通关 1 关 → m1 达成 Slots=1 且 Boss 击杀总数=1(计数源收束入账)")
|
||||
check(Attunement.slots(_db, sv_a) == 0 and sv_a.boss_kill_count() == 1,
|
||||
"通关 1 关 → Slots 恒 0(旧「按击杀 Boss 次数自动涨」条件已删——槽位唯一来源=树点亮)且 Boss 击杀总数=1(计数域保留)〔天赋树批 V14〕")
|
||||
sv_a.set_tree_level("att_socket1", 1)
|
||||
check(Attunement.slots(_db, sv_a) == 1,
|
||||
"树上点亮同调位 1 → Slots=1(槽位收编树节点——Q2 裁甲)")
|
||||
check(mp_a.toggle_attunement("nest_counter") and mp_a.toggle_attunement("dawn_order") == false,
|
||||
"装配持久写入 + 满位禁选(Slots 1 装第二件拒绝)")
|
||||
check(mp_a.toggle_attunement("nest_counter") and mp_a.toggle_attunement("dawn_order"),
|
||||
@@ -4728,13 +4676,8 @@ func _meta_settle_and_workshop() -> void:
|
||||
var mp_a2 = load("res://core/meta_progression.gd").new(_db, sv_a2)
|
||||
check((mp_a2.attune_camp_status()["equipped"] as Array).has("luck_coin"),
|
||||
"装配关系读回持久(reload 后投影一致)")
|
||||
# ⑫ 同调批 T8 断言面:clamp / 充能重置 / 刷新双计数器(white-box——迭代 11 第①批)
|
||||
var tg := AttunementGrowthTable.new()
|
||||
tg.slot_cap = 1
|
||||
tg.milestones = [{"id": "x1", "grant": 1, "requires": [{"key": "clear_count", "threshold": 1}]},
|
||||
{"id": "x2", "grant": 1, "requires": [{"key": "boss_kill_count", "threshold": 1}]}]
|
||||
check(tg.slots({"clear_count": 5, "boss_kill_count": 5}) == 1,
|
||||
"同调位 clamp(Σ达成 grant 2 → slots 1 = slot_cap——谓词表口径)")
|
||||
# ⑫ 充能重置 / 刷新双计数器(迭代 11 第①批;clamp white-box 断言随 attunement_growth 谓词表
|
||||
# 退役摘除——迭代 21 T5,V20 摘除清单)
|
||||
var a12 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(12))
|
||||
check(a12.start_adventure(12, {"attunement": ["dig_charge", "nest_pendulum"]}),
|
||||
"充能重置场景:装配冒险启动(seed 12)")
|
||||
@@ -4794,51 +4737,191 @@ func _meta_settle_and_workshop() -> void:
|
||||
_meta_smoke_cleanup()
|
||||
|
||||
|
||||
# ---- 天赋树冒烟(迭代 12 T8/T9——R1 甲融合案:双币/row-gating/Fork 切换/彩蛋/迁移幂等)----
|
||||
# ---- 天赋树冒烟(迭代 21 T1/T2——v0.8 12 节点换代:schema/购买面/Fork 一节点双态/彩蛋特化)----
|
||||
|
||||
func _smoke_tree() -> void:
|
||||
print("--- 天赋树(MetaTreeTable · 购买面 · A/B 切换 · 三体系迁移)---")
|
||||
check(_db.meta_tree != null and (_db.meta_tree.rows as Array).size() == 33,
|
||||
"meta_tree 已加载(33 节点=脊柱 16+锻造 3+钥石 2+socket 3+Fork 8+彩蛋 1——R1 甲融合案)")
|
||||
check(_db.meta_tree.validate().is_empty(), "MetaTreeTable.validate 零违例(单币枚举/互斥对/彩蛋 ≤3/零 archetype 层级纪律护栏)")
|
||||
print("--- 天赋树(12 节点 · 购买面 · Fork 双态 · 彩蛋觉醒)---")
|
||||
# 〔天赋树批 V1〕结构换代:12 行 + schema 零违例
|
||||
check(_db.meta_tree != null and (_db.meta_tree.rows as Array).size() == 12,
|
||||
"meta_tree 已加载(12 节点=脊柱 2〔生命/命中淬炼一线一节点 cap4〕+锻造 2〔钥匙+彩蛋——彩蛋含在锻造 2 内非第 13 行〕+钥石 2〔秘宝匣/刷新升级〕+同调 6〔socket 3+Fork 3 双态〕——v0.8 终态)〔天赋树批 V1〕")
|
||||
check(_db.meta_tree.validate().is_empty(),
|
||||
"MetaTreeTable.validate 零违例(脉/类型/币词表出域即拒/Fork 三组每组恰一且持 a/b 双效果键/彩蛋 ≤3/effects 键封闭/零 archetype)")
|
||||
# 彩蛋行硬要求(契约 9 红线落点):effects 键集 ⊆ {max_hp, hit}——旧表 :402/:405 atk/armor_class 分量换代件
|
||||
var ley: Dictionary = _db.meta_tree.row_by_id("easter_ley")
|
||||
var ley_ok: bool = not ley.is_empty()
|
||||
for k in (ley.get("effects", {}) as Dictionary).keys():
|
||||
if not (String(k) == "max_hp" or String(k) == "hit"):
|
||||
ley_ok = false
|
||||
check(ley_ok, "easter_ley effects 键集 ⊆ {max_hp, hit}(攻/护分量已摘除——契约 9)〔天赋树批 V8 落点〕")
|
||||
# V1/V4 反例:出域行逐类构造被拒(复制行副本塞词表外值)
|
||||
var t2 = load("res://data/schemas/meta_tree_table.gd").new()
|
||||
var bad_vein: Dictionary = (_db.meta_tree.row_by_id("sp_hp") as Dictionary).duplicate(true)
|
||||
bad_vein["node_id"] = "bad_vein"
|
||||
bad_vein["vein"] = "underworld"
|
||||
t2.rows = [bad_vein]
|
||||
check(not t2.validate().is_empty(), "schema 反例:vein 出域被拒")
|
||||
var bad_fork: Dictionary = (_db.meta_tree.row_by_id("fork_posture") as Dictionary).duplicate(true)
|
||||
bad_fork["node_id"] = "bad_fork"
|
||||
bad_fork["fork_group"] = "fg_mastery"
|
||||
t2.rows = [bad_fork]
|
||||
check(not t2.validate().is_empty(), "schema 反例:fork_group 出域(fg_mastery 已退役组)被拒")
|
||||
var bad_arch: Dictionary = (_db.meta_tree.row_by_id("sp_hp") as Dictionary).duplicate(true)
|
||||
bad_arch["node_id"] = "bad_arch"
|
||||
bad_arch["effects"] = {"archetype": 1}
|
||||
t2.rows = [bad_arch]
|
||||
check(not t2.validate().is_empty(), "schema 反例:effects.archetype 加载致命(🔴 层级纪律护栏面)〔天赋树批 V4〕")
|
||||
var bad_dangling: Dictionary = (_db.meta_tree.row_by_id("sp_hp") as Dictionary).duplicate(true)
|
||||
bad_dangling["requires"] = ["ghost_node"]
|
||||
t2.rows = [bad_dangling]
|
||||
check(not t2.validate().is_empty(), "schema 反例:requires 悬空被拒")
|
||||
# 购买面(V2 主干)
|
||||
var sv = _meta_new_save(_META_SMOKE_PATHS[8])
|
||||
var mp = load("res://core/meta_progression.gd").new(_db, sv)
|
||||
# row-gating:行 2 可购需总等级 ≥4——零购时行 2 拒绝
|
||||
check(not mp.tree_can_buy("sp_hp_r2"), "row-gating:行 2 不可购(总等级 0 < 4——投入点数累计解锁新行)")
|
||||
check(mp.tree_can_buy("sp_hp_r1") == false or true, "行 1 可购性读面(白板钥石 0=余额面拒)")
|
||||
sv.add_keystone(1000)
|
||||
check(mp.tree_buy("sp_hp_r1") and sv.tree_level("sp_hp_r1") == 1, "树购买受理(树全钥石单币——2026-10-07 用户裁甲)")
|
||||
for i in 3:
|
||||
mp.tree_buy("sp_atk_r1")
|
||||
check(not mp.tree_can_buy("sp_atk_r1"), "cap 拦截(atk 行 1 已满 3 级——购买拒绝)")
|
||||
check(mp.tree_can_buy("sp_hp_r2"), "row-gating 解锁(总等级 4 ≥ 4×1 门槛恰达——行 2 开放)")
|
||||
# Fork 激活与免费切换(前置链:att_socket1 ← sp_atk_r1 已满级)
|
||||
check(mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 1, "树购买受理(脊柱生命 L1——树全钥石单币)")
|
||||
check(not mp.tree_can_buy("sp_hp"),
|
||||
"行门槛:第 2 级不可购(总等级 1 < 4——一线一节点主语,买第 N 级需全树总投入 ≥4×(N−1))〔天赋树批 V2〕")
|
||||
check(mp.tree_buy("sp_hit") and mp.tree_buy("forge_workshop") and mp.tree_buy("att_socket1"),
|
||||
"命中 L1/锻造钥匙(free 无前置=初始零购零币可点亮)/同调位 1(根节点无前置)受理(总等级 1→4)")
|
||||
check(int(_db.tree_node("forge_workshop").get("price", -1)) == 0, "锻造工坊钥匙零币(free)")
|
||||
check(mp.tree_can_buy("sp_hp"), "行门槛恰达开放(总等级 4 ≥ 4×1)")
|
||||
check(mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 2, "第 2 级购买受理(门槛恰达当刻开放)")
|
||||
# Fork 一节点双态(V9)
|
||||
sv.add_keystone(100)
|
||||
check(mp.tree_buy("att_socket1"), "Socket 购买受理(同调脉首站——钥石=规则面消费承载)")
|
||||
check(mp.tree_buy_fork("fork_posture_a"), "Fork 购买=激活侧(posture A——组内互斥承载)")
|
||||
check(sv.fork_side("fg_posture") == "a", "Fork 激活侧入档(fg_posture=a)")
|
||||
check(not mp.tree_can_buy("fork_posture_b"), "互斥拦截(同组已激活 A——B 不可购)")
|
||||
check(mp.tree_buy_fork("fork_posture") and sv.tree_level("fork_posture") == 1 and sv.fork_side("fg_posture") == "a",
|
||||
"Fork 购买=解锁+默认 A 态(一节点双态——单行承载)")
|
||||
check(not mp.tree_can_buy("fork_posture"), "已解锁 Fork 重复购买拒绝(cap 1)")
|
||||
check(not mp.tree_toggle_fork("fg_posture", "a"), "同态重复切换零操作(false)")
|
||||
check(not mp.tree_toggle_fork("fg_sense", "b"), "未解锁 Fork 切换拒绝(解锁唯一入口=购买面)")
|
||||
var bal_pre_toggle: int = sv.keystone_balance()
|
||||
check(mp.tree_toggle_fork("fg_posture", "b") and sv.fork_side("fg_posture") == "b",
|
||||
"A/B 免费切换(已激活组换侧零币——营地免费切换裁定)")
|
||||
check(not mp.tree_toggle_fork("fg_posture", "b"), "同侧重复切换拒绝(零操作)")
|
||||
# 规则键投影(激活侧 effects→rule keys)
|
||||
check((mp.tree_rule_keys() as Array).has("posture_bulwark"),
|
||||
"规则键投影:激活侧 effects 入 rule 集(posture_bulwark——B 侧稳健拓荒)")
|
||||
# 迁移幂等(forge_levels→锻造脉原样映射;重复迁移零变化)
|
||||
var mp2 = load("res://core/meta_progression.gd").new(_db, sv)
|
||||
sv.set_forge_level("atk", 2)
|
||||
sv.unlock_item("nest_counter")
|
||||
var m1: Dictionary = mp2.migrate_tree()
|
||||
check(int(m1.get("forge", 0)) == 1 and int(m1.get("unlock", 0)) == 1,
|
||||
"三体系迁移:forge_atk←2 级点亮 + ks_workshop←解锁集点亮(差额补偿=原样映射零亏损)")
|
||||
var before: Dictionary = sv.tree_nodes()
|
||||
var m2: Dictionary = mp2.migrate_tree()
|
||||
check(m2.get("forge", 1) == 0 and m2.get("unlock", 1) == 0 and before == sv.tree_nodes(),
|
||||
"迁移幂等(重复迁移零变化——T10 断言面)")
|
||||
check(int(sv.tree_level("forge_atk")) == 2 and sv.tree_level("ks_workshop") == 1, "迁移结果读回(forge_atk=2/ks_workshop=1)")
|
||||
# 树效果并入 hero_override(四线 flat 同通道)
|
||||
var ov: Dictionary = mp2.hero_override()
|
||||
check(int(ov.get("atk", -1)) >= 2, "树效果注入:树 atk flat 并入 hero_override(覆盖值通道——锻造成同源叠加)")
|
||||
"零币切态 b(购买解锁后 A/B 可切)")
|
||||
check(int(sv.keystone_balance()) == bal_pre_toggle, "切态零消费(钥石余额差分不动)")
|
||||
check((mp.tree_rule_keys() as Array).has("posture_bulwark") and not (mp.tree_rule_keys() as Array).has("posture_first_strike"),
|
||||
"规则键投影=激活态效果键(B 态稳健拓荒——tree_rule_keys 随切态换键集)")
|
||||
check(mp.tree_toggle_fork("fg_posture", "a") and (mp.tree_rule_keys() as Array).has("posture_first_strike"),
|
||||
"切回 A 态键集随换(六键词表在册——V11 校验面归单元段)")
|
||||
# 彩蛋特化觉醒(V8)
|
||||
check(not mp.tree_can_buy("easter_ley"), "彩蛋不经购买(手动购亮=红——觉醒唯一入口=特化判定)")
|
||||
check(sv.tree_level("easter_ley") == 0, "觉醒前置未满足不亮(两线未拉满)")
|
||||
sv.set_tree_level("sp_hp", 4)
|
||||
sv.set_tree_level("sp_hit", 4)
|
||||
sv.set_tree_level("att_socket2", 1)
|
||||
mp.tree_buy("att_socket3") # 经购买口触发 _check_easter_egg
|
||||
check(sv.tree_level("easter_ley") == 1, "两线拉满+同调位全解锁 → 地脉共鸣自动点亮(零币特化判定)")
|
||||
var bal_now: int = sv.keystone_balance()
|
||||
mp.tree_buy("sp_hp")
|
||||
check(int(sv.keystone_balance()) == bal_now, "觉醒零消费(钥石余额不动)")
|
||||
_meta_smoke_cleanup()
|
||||
|
||||
|
||||
# ---- 天赋树六键行为冒烟(迭代 21 T3——V10:开局姿态窗/危险直觉限次/财富之道聚散/守侧失误补偿;
|
||||
# 引擎直构 + 编排授予段直注(_judge_flag=结算授予段旗判定单口))----
|
||||
func _smoke_tree_rules() -> void:
|
||||
print("--- 天赋树六键行为(V10:窗/限次/聚散/守侧)---")
|
||||
# 开局姿态组:每关首轮窗(battle_index 事实——引擎直构)
|
||||
var fx = load("res://core/effect_engine.gd").new(null)
|
||||
fx.rebuild([], [], ["posture_first_strike", "posture_bulwark"])
|
||||
check((fx.dispatch("round_start", {"battle_index": 1, "round_index": 1}, Callable()) as Array).size() == 1,
|
||||
"先手压制:每关首战首轮窗命中(battle_index=1 且 round=1 → grant_ap queue)〔天赋树批 V10〕")
|
||||
check((fx.dispatch("round_start", {"battle_index": 1, "round_index": 2}, Callable()) as Array).is_empty()
|
||||
and (fx.dispatch("round_start", {"battle_index": 2, "round_index": 1}, Callable()) as Array).is_empty(),
|
||||
"先手压制:首轮窗外地不触发(第二轮/第二关首战首轮——「每战首轮」旧语义换代件)")
|
||||
check((fx.dispatch("damage_taken", {"battle_index": 1, "round_index": 1}, Callable()) as Array).size() == 1
|
||||
and (fx.dispatch("damage_taken", {"battle_index": 2, "round_index": 1}, Callable()) as Array).is_empty(),
|
||||
"稳健拓荒:首轮窗内受击出盾、窗外不出(shield damage_taken 同窗条件)")
|
||||
# 危险直觉·锋:限次闸(引擎直构——mine_hit 挂点现成,编排收集 halve_pct 系既有 damage_mult 路径)
|
||||
var fz = load("res://core/effect_engine.gd").new(null)
|
||||
fz.rebuild([], [], ["sense_sharp"])
|
||||
var got_mult := false
|
||||
var got_add := false
|
||||
for h in (fz.dispatch("mine_hit", {}, Callable()) as Array):
|
||||
if String(h.get("kind", "")) == "damage_mult":
|
||||
got_mult = true
|
||||
if String(h.get("kind", "")) == "counter_add":
|
||||
got_add = true
|
||||
check(got_mult and got_add, "锋:踩雷减半命中+限次记账(counter 0<2 闸开——damage_mult+counter_add 双行同预评估态)")
|
||||
fz.counter_add("tree_sense_sharp_used", 2) # 直调置数(V10 注入口径——置关卡域计数后走事件路径)
|
||||
check((fz.dispatch("mine_hit", {}, Callable()) as Array).is_empty(), "锋:限次耗尽全额(counter 2 不<2 闸关——每关限次 provisional 2)")
|
||||
var exported: Dictionary = fz.counters_export()
|
||||
var fz2 = load("res://core/effect_engine.gd").new(null)
|
||||
fz2.rebuild([], [], ["sense_sharp"])
|
||||
fz2.counters_seed(exported)
|
||||
check((fz2.dispatch("mine_hit", {}, Callable()) as Array).is_empty(),
|
||||
"锋:限次跨战持久(adventure 域 counters_export/seed——断点保留同源,不新增存档域不升 v7)")
|
||||
# 守侧五场景(契约 12):编排 rig——begin_battle 携 sense_guard,授予段 _judge_flag 直注
|
||||
var orch = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(777001))
|
||||
check(orch.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40,
|
||||
"hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {},
|
||||
"feats": [], "tree_rules": ["sense_guard"], "battle_index": 1,
|
||||
"bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {},
|
||||
"voucher_rap": 0, "voucher_time_sand": 0, "voucher_insurance": 0, "node_type": "battle"}),
|
||||
"守侧 rig:战斗编排启动(tree_rules=sense_guard 携带)")
|
||||
var board = orch.minesweeper.current_board()
|
||||
var safe := Vector2i(-1, -1)
|
||||
for c in board.all_cells():
|
||||
if not board.is_mine(c):
|
||||
safe = c
|
||||
break
|
||||
check(safe.x >= 0, "守侧 rig:安全格定位(非雷格=标错负样本)")
|
||||
check(int(orch.action_economy.pending_ap_delta()) == 0, "守侧 rig:首轮后 queue 干净(开局 posture queue 已兑现)")
|
||||
# 场景①:当回合新插旗、结算判标错且旗仍在场 → queue +1(本轮 AP 零变化=queue 承载、下轮 notify_round_start 兑现)
|
||||
orch._judge_flag({"cell": safe, "hit": false}, 1)
|
||||
check(int(orch.action_economy.pending_ap_delta()) == 1,
|
||||
"守①:当回合标错且旗在场 → 下轮 AP+1(queue 排队——本轮 AP 逐位零变化)")
|
||||
# 场景③④:之前回合标错重判(旗在场状态)与同格重复——去重闸挡回,最多返还一次
|
||||
orch._judge_flag({"cell": safe, "hit": false}, 2)
|
||||
orch._judge_flag({"cell": safe, "hit": false}, 3)
|
||||
check(int(orch.action_economy.pending_ap_delta()) == 1,
|
||||
"守③④:旧旗重判零触发+同格最多一次(_flag_miss_seen 去重闸=「当回合标错动作」判别器)")
|
||||
# 场景②负样本:标中旗(hit=true 正向兑付路径)零触发
|
||||
orch._judge_flag({"cell": safe, "hit": true}, 4)
|
||||
check(int(orch.action_economy.pending_ap_delta()) == 1, "守②负样本:标中/被挖旗零触发(标错事件只在 miss 分支发射——发射面过滤承载)")
|
||||
# 场景⑤:末轮判出→无下一轮=queue 不兑现(编排器每战新建——队列实例随战亡,合法作废态)
|
||||
var orch2 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(777002))
|
||||
check(orch2.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40,
|
||||
"hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {},
|
||||
"feats": [], "tree_rules": ["sense_guard"], "battle_index": 1,
|
||||
"bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {},
|
||||
"voucher_rap": 0, "voucher_time_sand": 0, "voucher_insurance": 0, "node_type": "battle"}),
|
||||
"守⑤ rig:次战编排启动(新战新队列实例)")
|
||||
check(int(orch2.action_economy.pending_ap_delta()) == 0,
|
||||
"守⑤:末轮判出不返(queue 只在 notify_round_start 兑现、战亡即弃——无跨战泄漏)")
|
||||
# 按关重置清零点(adventure_runner._start_current_level)
|
||||
var ar = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(777003))
|
||||
ar.adventure_counters = {"tree_sense_sharp_used": 2, "tree_sense_guard_used": 1, "unrelated_key": 9}
|
||||
ar._reset_tree_limited_counters()
|
||||
check(not ar.adventure_counters.has("tree_sense_sharp_used") and not ar.adventure_counters.has("tree_sense_guard_used")
|
||||
and int(ar.adventure_counters.get("unrelated_key", 0)) == 9,
|
||||
"限次按关重置:_start_current_level 清零点恰两键(他键零扰动——生命周期四态闭合)")
|
||||
# 财富之道·散:商品标价折价(level_runner 计价单点)
|
||||
var lr = load("res://core/level_runner.gd").new(_db, _fresh_rng(777004))
|
||||
lr.set_tree_rules([])
|
||||
check(int(lr._spend_discounted(100)) == 100, "散未激活:商品标价原价(无 economy_spend 键)")
|
||||
lr.set_tree_rules(["economy_spend"])
|
||||
check(int(lr._spend_discounted(100)) == 80 and int(lr._spend_discounted(1)) == 1,
|
||||
"散激活:商品购买价 ×80%(provisional 折率——floor 偏向玩家、下限 1;刷新价不折=另一计价点零触碰)")
|
||||
# 财富之道·聚:战斗掉落乘算·入账前(编排 _roll_gp_drop——同种子配对对拍 1.5×)
|
||||
var orch3 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(777005))
|
||||
check(orch3.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40,
|
||||
"hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {},
|
||||
"feats": [], "tree_rules": [], "battle_index": 1,
|
||||
"bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {},
|
||||
"voucher_rap": 0, "voucher_time_sand": 0, "voucher_insurance": 0, "node_type": "battle"}),
|
||||
"聚 rig:基线编排启动(无增益侧)")
|
||||
orch3.encounter["tree_rules"] = []
|
||||
var gp_base: int = orch3._roll_gp_drop()
|
||||
var orch4 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(777005))
|
||||
check(orch4.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40,
|
||||
"hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {},
|
||||
"feats": [], "tree_rules": ["economy_hoard"], "battle_index": 1,
|
||||
"bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {},
|
||||
"voucher_rap": 0, "voucher_time_sand": 0, "voucher_insurance": 0, "node_type": "battle"}),
|
||||
"聚 rig:增益编排启动(tree_rules=economy_hoard)")
|
||||
orch4.encounter["tree_rules"] = ["economy_hoard"]
|
||||
var gp_hoard: int = orch4._roll_gp_drop()
|
||||
check(gp_hoard == int(round(float(gp_base) * 1.5)) and gp_hoard >= gp_base,
|
||||
"聚:战斗掉落乘算 ×1.5(provisional——同种子配对 %d→%d,入账前=结算框显示实得)")
|
||||
_meta_smoke_cleanup()
|
||||
|
||||
|
||||
@@ -4896,7 +4979,7 @@ func _smoke_save() -> void:
|
||||
"user://smoke_save_domain.json", "user://smoke_save_cap.json",
|
||||
"user://smoke_save_integration.json", "user://smoke_save_breakpoint.json",
|
||||
"user://smoke_save_v2compat.json", "user://smoke_save_v5upgrade.json",
|
||||
"user://smoke_save_v5legacy.json"]
|
||||
"user://smoke_save_v5legacy.json", "user://smoke_save_v16discard.json"]
|
||||
var stale: Array = []
|
||||
for p in paths:
|
||||
stale.append(p)
|
||||
@@ -4909,8 +4992,9 @@ func _smoke_save() -> void:
|
||||
# ① 往返:全访问器写非默认值 → write → 新实例 load → 逐域逐值相等
|
||||
var w = sm_script.new(paths[0])
|
||||
w.add_keystone(7)
|
||||
check(w.set_forge_level("atk", 2) and w.set_forge_level("max_hp", 1) and w.set_forge_level("armor_class", 1),
|
||||
"属性锻造三档写入")
|
||||
w.set_tree_level("sp_hp", 2)
|
||||
w.set_tree_level("sp_hit", 1)
|
||||
w.set_fork_side("fg_posture", "b") # 树域写入(属性锻造域已退役——forge_levels 域清退换代件)
|
||||
w.unlock_item("probe_compass")
|
||||
w.apply_adventure_summary({"result": "failed", "cleared": false, "levels_cleared": 2,
|
||||
"stopped_at": 3, "keystone_total": 3, "master_seed": 99})
|
||||
@@ -4924,8 +5008,8 @@ func _smoke_save() -> void:
|
||||
var r = sm_script.new(paths[0])
|
||||
check(r.load_from_disk(), "读回成功")
|
||||
check(r.keystone_balance() == 10, "钥石余额往返相等(10)")
|
||||
check(r.forge_level("atk") == 2 and r.forge_level("max_hp") == 1 and r.forge_level("armor_class") == 1,
|
||||
"锻造三档往返相等")
|
||||
check(r.tree_level("sp_hp") == 2 and r.tree_level("sp_hit") == 1 and r.fork_side("fg_posture") == "b",
|
||||
"树域往返相等(tree_nodes/fork_sides——迭代 21 换代承载)")
|
||||
check(r.item_unlocked("probe_compass") and not r.item_unlocked("rope"), "道具解锁往返相等")
|
||||
check(r.clear_count(1) == 1 and r.clear_count(2) == 1 and r.clear_count(3) == 0, "通关次数表往返相等")
|
||||
check(r.boss_first_kill_marked(1) and r.boss_first_kill_marked(2) and not r.boss_first_kill_marked(3),
|
||||
@@ -4937,7 +5021,7 @@ func _smoke_save() -> void:
|
||||
|
||||
# ② 新档合法:不存在路径 load → true + 全零默认值
|
||||
var f2 = sm_script.new(paths[1])
|
||||
check(f2.load_from_disk() and f2.keystone_balance() == 0 and f2.forge_level("atk") == 0
|
||||
check(f2.load_from_disk() and f2.keystone_balance() == 0 and f2.tree_level("sp_hp") == 0
|
||||
and not f2.item_unlocked("anything") and f2.clear_count(1) == 0 and f2.recent_summaries().is_empty(),
|
||||
"新档合法态(load true + 全零默认)")
|
||||
check(f2.adventure_total() == 0 and f2.adventure_cleared() == 0,
|
||||
@@ -4949,10 +5033,6 @@ func _smoke_save() -> void:
|
||||
check(not f3.spend_keystone(8) and f3.keystone_balance() == 5, "余额 5 花 8 拒绝且余额不动")
|
||||
check(f3.spend_keystone(3) and f3.keystone_balance() == 2, "余额内消费成功(5→2)")
|
||||
|
||||
# ④ attr 出域拒绝(push_error,值不变)
|
||||
check(not f3.set_forge_level("luck", 1) and f3.forge_level("luck") == 0 and f3.forge_level("atk") == 0,
|
||||
"锻造键出域拒绝(值不变)")
|
||||
|
||||
# ⑤ 高版本拒读:load false、原文件字节不动、后续写盘拒绝且文件仍字节不动(契约 4)
|
||||
var hv_text := "{\"version\": 99, \"meta\": {\"keystone_balance\": 1}}"
|
||||
var fhv := FileAccess.open(paths[3], FileAccess.WRITE)
|
||||
@@ -5126,6 +5206,21 @@ func _smoke_save() -> void:
|
||||
check(f13b.adventure_total() == 2 and f13b.adventure_cleared() == 1,
|
||||
"收束自增恰一·中止局(放弃局:仅总局数+1,通关局数不动)")
|
||||
|
||||
# ⑮ 旧档破坏性废弃(迭代 21 T5——契约 4:换代前结构检测=树域废弃重置,禁迁移禁折算)〔天赋树批 V16〕
|
||||
var f15 = sm_script.new(paths[13])
|
||||
var fv16 := FileAccess.open(paths[13], FileAccess.WRITE)
|
||||
fv16.store_string(JSON.stringify({"version": 6, "meta": {"keystone_balance": 33,
|
||||
"forge_levels": {"max_hp": 3, "atk": 4, "armor_class": 2},
|
||||
"tree_nodes": {"sp_hp_r1": 3, "forge_atk": 2, "fork_posture_a": 1},
|
||||
"fork_sides": {"fg_posture": "a", "fg_mastery": "b"}}}))
|
||||
fv16.close()
|
||||
check(f15.load_from_disk() and f15.keystone_balance() == 33
|
||||
and f15.tree_nodes().is_empty() and f15.fork_sides().is_empty(),
|
||||
"换代前结构存档:树域废弃重置(旧节点 id/fork 侧/forge_levels 旧域残留清退)、钥石余额零折算零变动")
|
||||
var mp16 = load("res://core/meta_progression.gd").new(_db, f15)
|
||||
f15.add_keystone(50)
|
||||
check(mp16.tree_buy("sp_hp") and f15.tree_level("sp_hp") == 1, "废弃重置后后续购买正常受理(无残留脏节点)")
|
||||
|
||||
# ⑭ 收束链入账(迭代 18 卡④ T2——真实收束挂点全链恰一:主流程 adventure_finished →
|
||||
# MetaProgression.settle_adventure → apply_adventure_summary 单口;两夹具经同链入账)
|
||||
var mp13 = load("res://core/meta_progression.gd").new(_db, f13b)
|
||||
|
||||
@@ -60,28 +60,64 @@ func _ready() -> void:
|
||||
await get_tree().process_frame
|
||||
if not main.camp_overlay.visible:
|
||||
_problems.append("营地层未打开(点击路径)")
|
||||
elif main.camp_overlay._forge_rows.is_empty():
|
||||
_problems.append("锻造柜台行缺失(点击路径)")
|
||||
elif main.camp_overlay._tree_buy_btns.is_empty():
|
||||
_problems.append("树淬炼柜台购买钮缺失(点击路径——四柜台重排后树=主界面)")
|
||||
else:
|
||||
var buy_btn: Button = main.camp_overlay._forge_rows[0].btn
|
||||
if not buy_btn.disabled:
|
||||
_problems.append("零余额锻造购买按钮未禁用(禁用态失守)")
|
||||
# 树钮句柄用后即取(refresh_camp 随刷重建树行板——旧钮 queue_free,跨刷新句柄必失效)
|
||||
iso_c.add_keystone(9999)
|
||||
main.camp_overlay.refresh_camp(main._camp_data())
|
||||
await get_tree().process_frame
|
||||
if buy_btn.disabled:
|
||||
_problems.append("余额充足后购买按钮仍禁用(禁用态未随刷新)")
|
||||
var bal_before: int = iso_c.keystone_balance()
|
||||
var buy_attr: String = String(main.camp_overlay._forge_rows[0].attr)
|
||||
var price: int = main.meta.forge_price(buy_attr)
|
||||
buy_btn.pressed.emit() # 合成购买点击 → _on_camp_buy → meta.buy_forge(写调用唯一归组合根)
|
||||
await get_tree().process_frame
|
||||
if int(iso_c.keystone_balance()) != bal_before - price:
|
||||
_problems.append("营地购买未出账(%d→%d 应 −%d)" % [bal_before, iso_c.keystone_balance(), price])
|
||||
var iso_chk = load("res://core/save_manager.gd").new("user://smoke_click_probe_camp.json")
|
||||
iso_chk.load_from_disk()
|
||||
if int(iso_chk.forge_level(buy_attr)) != 1:
|
||||
_problems.append("营地购买未持久(reload 后级 != 1)")
|
||||
var buy_btn: Button = null
|
||||
for card in main.camp_overlay._tree_buy_btns:
|
||||
if String(card.node_id) == "sp_hp":
|
||||
buy_btn = card.btn
|
||||
if buy_btn == null:
|
||||
_problems.append("脊柱生命行缺失(点击路径)")
|
||||
else:
|
||||
var bal_before: int = iso_c.keystone_balance()
|
||||
var price: int = int(main.db.tree_node("sp_hp").get("price", 0))
|
||||
buy_btn.pressed.emit() # 合成购买点击 → _on_camp_tree_buy → meta.tree_buy(写调用唯一归组合根)
|
||||
await get_tree().process_frame
|
||||
if int(iso_c.keystone_balance()) != bal_before - price:
|
||||
_problems.append("营地树购买未出账(%d→%d 应 −%d)" % [bal_before, iso_c.keystone_balance(), price])
|
||||
var iso_chk = load("res://core/save_manager.gd").new("user://smoke_click_probe_camp.json")
|
||||
iso_chk.load_from_disk()
|
||||
if int(iso_chk.tree_level("sp_hp")) != 1:
|
||||
_problems.append("营地树购买未持久(reload 后级 != 1)")
|
||||
# Fork 双态切换点击路径(V20 换代——购=解锁默认 A,钮切 B;前置=同调位 1,先点亮)
|
||||
var socket_btn: Button = null
|
||||
for card in main.camp_overlay._tree_buy_btns:
|
||||
if String(card.node_id) == "att_socket1":
|
||||
socket_btn = card.btn
|
||||
if socket_btn != null:
|
||||
socket_btn.pressed.emit()
|
||||
await get_tree().process_frame
|
||||
main.camp_overlay.refresh_camp(main._camp_data())
|
||||
await get_tree().process_frame
|
||||
var fork_btn: Button = null
|
||||
for card in main.camp_overlay._tree_buy_btns:
|
||||
if String(card.node_id) == "fork_posture":
|
||||
fork_btn = card.btn
|
||||
if fork_btn == null:
|
||||
_problems.append("Fork 解锁钮缺失(点击路径)")
|
||||
else:
|
||||
fork_btn.pressed.emit()
|
||||
await get_tree().process_frame
|
||||
main.camp_overlay.refresh_camp(main._camp_data())
|
||||
await get_tree().process_frame
|
||||
var side_b: Button = null
|
||||
for fk in main.camp_overlay._tree_fork_btns:
|
||||
if String(fk.group) == "fg_posture" and String(fk.side) == "b":
|
||||
side_b = fk.btn
|
||||
if side_b == null:
|
||||
_problems.append("Fork 双态切换钮缺失(解锁后应展开 a/b 钮)")
|
||||
else:
|
||||
side_b.pressed.emit() # 合成切态点击 → _on_camp_fork_toggle → meta.tree_toggle_fork
|
||||
await get_tree().process_frame
|
||||
var iso_fk = load("res://core/save_manager.gd").new("user://smoke_click_probe_camp.json")
|
||||
iso_fk.load_from_disk()
|
||||
if String(iso_fk.fork_side("fg_posture")) != "b":
|
||||
_problems.append("Fork 切态未持久(reload 后激活态 != b)")
|
||||
main._on_camp_closed()
|
||||
await get_tree().process_frame
|
||||
if main.camp_overlay.visible or not main._start_overlay.visible:
|
||||
|
||||
@@ -38,14 +38,16 @@ func _ready() -> void:
|
||||
else:
|
||||
if not String(main.camp_overlay._balance_label.text).begins_with("钥石"):
|
||||
problems.append("营地余额文案缺失(%s)" % main.camp_overlay._balance_label.text)
|
||||
if main.camp_overlay._forge_rows.size() != 3:
|
||||
problems.append("锻造柜台行数错误(%d ≠ 3)" % main.camp_overlay._forge_rows.size())
|
||||
if main.camp_overlay._tree_buy_btns.is_empty():
|
||||
problems.append("树淬炼柜台购买钮缺失(四柜台重排后树=主界面)")
|
||||
else:
|
||||
var zero_disabled := true
|
||||
for card in main.camp_overlay._forge_rows:
|
||||
zero_disabled = zero_disabled and bool(card.btn.disabled) # 零余额全禁用
|
||||
if not zero_disabled:
|
||||
problems.append("零余额下锻造购买按钮未全禁用(余额不足禁用态失守)")
|
||||
var btn_by_id := {}
|
||||
for card2 in main.camp_overlay._tree_buy_btns:
|
||||
btn_by_id[String(card2.node_id)] = card2.btn
|
||||
if not btn_by_id.has("sp_hp") or bool(btn_by_id["sp_hp"].disabled):
|
||||
problems.append("脊柱生命 L1 白板可购钮未启用(无门槛——can_buy 不含余额面语义)")
|
||||
if not btn_by_id.has("att_socket2") or not bool(btn_by_id["att_socket2"].disabled):
|
||||
problems.append("同调位 2 前置未亮应禁用(点亮前置语义)")
|
||||
if main.camp_overlay._workshop_rows.size() != 6:
|
||||
problems.append("工坊柜台行数错误(%d ≠ 6)" % main.camp_overlay._workshop_rows.size())
|
||||
main._on_camp_closed()
|
||||
|
||||
@@ -24,10 +24,12 @@
|
||||
# 绝不碰 user://save.json(与 smoke 存档隔离纪律同源)。
|
||||
extends SceneTree
|
||||
|
||||
# 迭代 21 T5 换代——属性锻造柜台退役后花费策略主语=树脊柱两节点(钥石单币;攻/AC 无树来源
|
||||
# 〔契约 9——唯一入口=锻造工坊路线,本批未实装〕,原三轴序收敛两节点序)
|
||||
const SPEND_ORDERS := {
|
||||
"hp_first": ["max_hp", "atk", "armor_class"],
|
||||
"atk_first": ["atk", "max_hp", "armor_class"],
|
||||
"armor_first": ["armor_class", "max_hp", "atk"],
|
||||
"hp_first": ["sp_hp", "sp_hit"],
|
||||
"atk_first": ["sp_hit", "sp_hp"],
|
||||
"armor_first": ["sp_hp", "sp_hit"],
|
||||
}
|
||||
const OVERRIDE_KEYS := ["atk", "max_hp", "armor_class"] # 注入契约三键(同 AdventureRunner 白板形)
|
||||
|
||||
@@ -175,19 +177,19 @@ func _seg_campaign(db, autoplay, logger, opt: Dictionary, counts: Dictionary) ->
|
||||
row["keystone_income"] = int(settle.total)
|
||||
row["purchases"] = _spend(meta, String(opt.spend))
|
||||
row["keystone_after"] = meta.keystone_balance()
|
||||
row["forge"] = {"max_hp": meta.forge_level("max_hp"), "atk": meta.forge_level("atk"),
|
||||
"armor_class": meta.forge_level("armor_class")}
|
||||
row["tree"] = {"sp_hp": meta.save.tree_level("sp_hp"), "sp_hit": meta.save.tree_level("sp_hit")}
|
||||
row["new_unlocks"] = settle.new_unlocks
|
||||
_emit(row)
|
||||
counts.campaign += 1
|
||||
if (r + 1) % 10 == 0:
|
||||
print("进度 campaign %s %d/%d(锻造 血%d/攻%d/甲%d · 钥石 %d)" % [policy, r + 1, int(opt.k),
|
||||
meta.forge_level("max_hp"), meta.forge_level("atk"), meta.forge_level("armor_class"),
|
||||
print("进度 campaign %s %d/%d(树 血%d/命中%d · 钥石 %d)" % [policy, r + 1, int(opt.k),
|
||||
meta.save.tree_level("sp_hp"), meta.save.tree_level("sp_hit"),
|
||||
meta.keystone_balance()])
|
||||
_remove_save(save_path) # 段尾清理(与 smoke 存档纪律同源——不遗留隔离档)
|
||||
|
||||
|
||||
# 花费策略(贪心——按优先序反复买当前可负担的最高优先项;几何递增定价下单档连买价格上行自然外溢到次档)
|
||||
# 花费策略(贪心——按优先序反复买当前可负担的最高优先项;树行门槛随买随涨自然外溢到次节点
|
||||
# ——迭代 21 T5 换代:购买原语 buy_forge→tree_buy,行门槛=节点等级轴)
|
||||
func _spend(meta, policy_name: String) -> Array:
|
||||
var out: Array = []
|
||||
var order: Array = SPEND_ORDERS.get(policy_name, SPEND_ORDERS["hp_first"])
|
||||
@@ -195,10 +197,10 @@ func _spend(meta, policy_name: String) -> Array:
|
||||
while guard > 0:
|
||||
guard -= 1
|
||||
var bought := false
|
||||
for attr in order:
|
||||
var price: int = meta.forge_price(attr)
|
||||
if price > 0 and meta.keystone_balance() >= price and meta.buy_forge(attr):
|
||||
out.append({"attr": attr, "tier": int(meta.forge_level(attr)), "price": price})
|
||||
for nid in order:
|
||||
var price: int = int(meta.db.tree_node(nid).get("price", 0))
|
||||
if price > 0 and meta.keystone_balance() >= price and meta.tree_buy(nid):
|
||||
out.append({"node_id": nid, "level": meta.save.tree_level(nid), "price": price})
|
||||
bought = true
|
||||
break
|
||||
if not bought:
|
||||
|
||||
@@ -44,7 +44,8 @@ func _suites() -> Array:
|
||||
{"path": "res://tests/unit/presentation/ui_font_asset_test.gd", "expect": 0},
|
||||
{"path": "res://tests/unit/presentation/terrain_tile_sets_test.gd", "expect": 0},
|
||||
{"path": "res://tests/unit/core/fog_lantern_wiring_test.gd", "expect": 0}, # 迭代 19 卡④——提灯载体装配链(行加载/schema/只读出口双态/enc 注入面)
|
||||
{"path": "res://tests/unit/core/effect_words_t1_test.gd", "expect": 0}, # 迭代 20 T1——词表扩签封闭性(V1:新词在册/出域四路被拒/adventure 计数器往返/mod_eq 预评估口径)
|
||||
{"path": "res://tests/unit/core/effect_words_t1_test.gd", "expect": 0},
|
||||
{"path": "res://tests/unit/core/tree_rules_v11_test.gd", "expect": 0}, # 迭代 21 天赋树批 V11——六键合成行闭集校验 # 迭代 20 T1——词表扩签封闭性(V1:新词在册/出域四路被拒/adventure 计数器往返/mod_eq 预评估口径)
|
||||
{"path": "res://tests/unit/core/pool_case_d_test.gd", "expect": 0}, # 迭代 20 T2——池结构案D 行为(V6 令牌三态/V7 曝光槽 2+1 回退/V8 概率上限解耦提前退役)
|
||||
{"path": "res://tests/unit/core/feat_vouchers_test.gd", "expect": 0}, # 迭代 20 T4——凭证五件+留任二件(V9 路由逐件/F-1 存量账/V10 留任路由+AP 属性轴)
|
||||
]
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# tests/unit/core/tree_rules_v11_test.gd — 天赋树批 V11:六键合成订阅行走效果引擎加载期校验零违例
|
||||
# (契约 5——效果类型闭集不加新词:六键效果全部走现有 EVENTS/FACTS/OPS/EFFECT_KINDS/DO_KEYS
|
||||
# 在册词表组合;本测试直构 rebuild 同构合成行(与 effect_engine.gd rebuild 树规则段逐字段同源)
|
||||
# 调 validate_trigger_row 断零违例。实现侧扩闭集未呈报时,本断言以在册词表为准转红。
|
||||
# 载体:run_all 单元套件(显式实例化夹具——--script 模式无 autoload)。)
|
||||
extends SceneTree
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
var eng = load("res://core/effect_engine.gd")
|
||||
var tree_tbl = load("res://data/schemas/meta_tree_table.gd")
|
||||
var errs: Array = []
|
||||
# 六键合成行(与 rebuild 树规则段同构——逐字段对照,实现漂移在此转红)
|
||||
var synth := {
|
||||
"posture_first_strike": [{"on": "round_start",
|
||||
"when": [{"fact": "battle_index", "op": "eq", "value": 1},
|
||||
{"fact": "round_index", "op": "eq", "value": 1}],
|
||||
"do": {"kind": "grant_ap", "mode": "queue", "value": 1}}],
|
||||
"posture_bulwark": [{"on": "damage_taken",
|
||||
"when": [{"fact": "battle_index", "op": "eq", "value": 1},
|
||||
{"fact": "round_index", "op": "eq", "value": 1}],
|
||||
"do": {"kind": "shield", "value": 1}}],
|
||||
"sense_sharp": [{"on": "mine_hit",
|
||||
"when": [{"fact": "counter", "id": "tree_sense_sharp_used", "op": "lt", "value": 2}],
|
||||
"do": {"kind": "damage_mult", "value": 50}},
|
||||
{"on": "mine_hit",
|
||||
"when": [{"fact": "counter", "id": "tree_sense_sharp_used", "op": "lt", "value": 2}],
|
||||
"do": {"kind": "counter_add", "counter_id": "tree_sense_sharp_used", "value": 1, "domain": "adventure"}}],
|
||||
"sense_guard": [{"on": "flag_missed",
|
||||
"when": [{"fact": "counter", "id": "tree_sense_guard_used", "op": "lt", "value": 2}],
|
||||
"do": {"kind": "grant_ap", "mode": "queue", "value": 1}},
|
||||
{"on": "flag_missed",
|
||||
"when": [{"fact": "counter", "id": "tree_sense_guard_used", "op": "lt", "value": 2}],
|
||||
"do": {"kind": "counter_add", "counter_id": "tree_sense_guard_used", "value": 1, "domain": "adventure"}}],
|
||||
}
|
||||
var six := ["posture_first_strike", "posture_bulwark", "sense_sharp", "sense_guard", "economy_hoard", "economy_spend"]
|
||||
for key in six:
|
||||
if not tree_tbl.RULE_KEYS.has(key):
|
||||
errs.append("规则键出域 RULE_KEYS 词表: %s" % key)
|
||||
for tr in (synth.get(key, []) as Array):
|
||||
eng.validate_trigger_row("tree_synth_" + String(key), {}, [tr], errs)
|
||||
# 反例:出域事件/出域效果类型在校验面转红(护栏活着——独立数组,命中即护栏有效)
|
||||
var bad_errs: Array = []
|
||||
eng.validate_trigger_row("bad_event", {}, [{"on": "no_such_event", "do": {"kind": "grant_ap"}}], bad_errs)
|
||||
eng.validate_trigger_row("bad_kind", {}, [{"on": "round_start", "do": {"kind": "no_such_kind"}}], bad_errs)
|
||||
if bad_errs.is_empty():
|
||||
errs.append("反例护栏失效:出域事件/出域效果类型未被 validate_trigger_row 拒绝")
|
||||
if errs.is_empty():
|
||||
print("[tree_rules_v11] all green(六键合成行零违例 + 反例被拒——契约 5 闭集在册词表承载)")
|
||||
quit(0)
|
||||
return
|
||||
for e in errs:
|
||||
printerr("[tree_rules_v11] %s" % str(e))
|
||||
quit(1)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cuhmdpcunxhrn
|
||||
@@ -155,13 +155,7 @@ func _test_shop_accessors(db) -> void:
|
||||
# ---- MetaTable 消费面 ----
|
||||
|
||||
func _test_meta_accessors(db) -> void:
|
||||
var forge_rows: Array = db._meta_rows("forge_products")
|
||||
if forge_rows.is_empty():
|
||||
Kit.check("MetaTable forge_products 行族非空", false)
|
||||
else:
|
||||
var attr0: String = String(forge_rows[0].attr)
|
||||
Kit.check("meta_forge_row 按 attr 命中首行", db.meta_forge_row(attr0) == forge_rows[0])
|
||||
Kit.check("meta_forge_row 未知 attr 返回空字典", db.meta_forge_row("no_such_attr__").is_empty())
|
||||
# forge_products 行族断言已摘除(迭代 21 T5——行族/访问器随属性锻造柜台退役,V20 清单)
|
||||
var key_rows: Array = db._meta_rows("boss_keystone")
|
||||
if not key_rows.is_empty():
|
||||
var klv: int = int(key_rows[0].level_index)
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@
|
||||
## 当前位置(2026-10-11 刷新)
|
||||
|
||||
- **里程碑语义(2026-10-09 立规换代,详见下方里程碑表的换代注)**:现行里程碑 = **M3「Alpha 版本的完整实现」**——用户未明令宣布完成前停留 M3,迭代只排 M3 需求;M4=Beta 版本的完整实现,一律不排期。下文里程碑表的旧编号(M-3/M-4)保留作历史序列,语义以本节为准。
|
||||
- **迭代进度**:迭代 19 五卡全部已入库(2026-10-08);**迭代 20=专长实装批 ✅ 已入库**(2026-10-11,出口闸全绿;残留人闸=实机目检五件+V20/V21/V22 人工核对半,07 在册);**迭代 21=天赋树实装批已编入**(2026-10-11 用户令,整卡不切分),需求卡片 **v1.7**(2026-10-12 完备性审查十一裁已落),零阻塞待裁点、**开工门=U0 发令**;**迭代 22=秘宝体系实现重构落地批已编入**(卡片 v1.2 零待裁),**开工门=U0 发令**(原「规划冻结维持」句已由 2026-10-11 编入令取代)。
|
||||
- **迭代进度**:迭代 19 五卡全部已入库(2026-10-08);**迭代 20=专长实装批 ✅ 已入库**(2026-10-11,出口闸全绿;残留人闸=实机目检五件+V20/V21/V22 人工核对半,07 在册);**迭代 21=天赋树实装批 ✅ 已入库(2026-10-12 U0 开工单会话交付,T0-T7 全绿;残留人闸=实机目检四柜台)**;**迭代 22=秘宝体系实现重构落地批已编入**(卡片 v1.2 零待裁),**开工门=U0 发令**(原「规划冻结维持」句已由 2026-10-11 编入令取代)。
|
||||
- **近期主战场**:迭代 20 残留人闸(实机目检五件+V20/V21/V22 人工核对半);**下一前沿=迭代 21 天赋树实装批(卡片 v1.7 已就绪)、迭代 22 秘宝实装批,均待 U0 发令**;锻造工坊实装批设计面已收口(v0.5,17 裁点门已满足)待发令。
|
||||
- **实时状态**:以根目录 [active.md](active.md) 顶部「当前状态快照」节为准(`stage.txt` 已定性为旧阶段开关,仅存档)。
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
| 迭代 18 | **⑦新冒险种子随机化+调试浮窗升级(2026-10-07 用户令入迭代 18,同日两裁点全裁+浮窗目标态已定)**:**(a) 种子随机化**——现状考证 main.gd:1210 `_seed_value()` 调试种子框为空时硬编码返回 42=每次新冒险同一种子全程可复现;改法=玩家界面入口(主菜单开新冒险)默认分支随机生成;**✅ 裁点全裁(2026-10-07 用户令):①首次冒险维持固定种子(教学态可复现)/②测试脚本与调试接口维持显式指定种子(冒烟·MC·探针全链显式传种子零影响,锚零作废)/③种子不进玩家 UI**;断点续玩零影响(存档已载 master_seed 回填,main.gd:447 现役);与⑥收官批零耦合、排序自由。**(b) 调试浮窗升级**(承载「调试浮窗可查当局数据」裁点=种子的唯一查看位;**本项修订迭代 15 卡① P3 甲「仅开始菜单可开·对局不可达」裁决**)——目标态三件:独立于整体界面的**最高层浮窗**(对局中也可开)/DS_DEBUG=1 调试态下**默认打开**/新增**当局数据只读区块**(当前局种子/关卡/节点/HP/GP/钥石等——玩家 UI 不展示、调试浮窗可查)。考证在册(2026-10-07 实查):现行=开始覆盖层角标入口〔main.gd:292-293,DS_DEBUG=1 点亮〕+main.gd:1189 门禁「仅开始菜单可开」+Dialog 形态走 ModalStack 模态栈登记〔非最高层〕+visible=false 初始非默认开+五区块(胜利演出/种子输入/数值滑条/批量模拟/两层调试)无当局数据面。需求卡片待裁点:最高层与模态栈登记制的关系〔置顶/登记序末+move_child 或独立层〕/输入吞噬边界〔浮窗矩形外透传游戏输入〕/对局发起「随开随收」逻辑改向/ui_tune_probe 断言面换代/入口形态(角标宿主迁移或热键)。需求卡片=`design/research/需求卡片-种子随机化与调试浮窗升级批-2026-10-07.md`(v1.0——实装级裁点 P1 遮挡语义〔荐甲=维持登记制〕/P2 入口形态〔荐甲=角标迁宿主〕/P3 默认打开范围〔荐甲=启动即开〕/P4 当局数据字段集〔荐甲=六项〕/P5 首局种子值〔荐甲=沿用 42〕待裁;估时勘误 1d→2.0d 含探针换代与收口) | **✅ 已落地(2026-10-07 worktree iter18-seed:C1 种子三分+浮窗升级 P1-P6 甲全落;批末重闸全绿=冒烟 811/0+四探针+run_all 17 套+doc 三闸/熵增闸零新增;MC 零跑;实机人闸挂账待用户〔不阻塞已入库与后续卡④〕)** | T0-T5≈2.0d |
|
||||
| 迭代 19 | **卡⓪主菜单开局缺陷修复批(原迭代 20 头卡A;2026-10-08 用户令并入本迭代、执行序第一)**:①**主菜单开新冒险+继续上局两入口补传同调三键**(attunement/attune_refresh_y/tree_rules——main.gd:510/:470,接收面 adventure_runner.gd:129-138/215-217 现成;静态考证缺陷成立:全仓无补偿路径、真实入口同调/X/Y 恒空、仅冒烟夹具喂值);②**关卡开场行攻血改传当前生效值**(main.gd:519-521 现传初始表值,与战斗面板不一致=用户确认缺陷;「含面板加成」后缀判断同步改为认局外加成);③冒烟补主菜单入口传参断言;④顺手删 adventure_runner.gd:129-134 三行重复赋值(零行为变化)。范围外=被动同调件呈现(用户令不动)、Fork 规则归因行(呈现层)、X/Y 刷新双源语义倒置(实现与用户口径镜像互换——X=刷新件源每节点重置不结转/Y=营地源整局持久/先扣 X,见 adventure_runner.gd:658-659/:706-709;用户口径=X 持久·Y 每节点重置·先扣 Y;真实局因①此前恒 0 未暴露——待用户裁定甲改实现/乙认现状,裁定落 GDD)。**卡①自动化验收补齐批(2026-10-07 用户令立项——全迭代两阶段验收盘点〔功能性+展示性,均自动化口径,不含人工目检〕的落地批)**:三件主内容——①**渲染闸自动化**:新增 `tools/render_gate.sh` 窗口化自动闸(shot_probe 四屏→shot_checker.py A-D 语义断言自动判定+fx_shot/board_shot 抓帧取证),棋盘 L1-L3 golden 逐像素基线入库 `game/tests/golden/`(测试资产入库先例,.gitignore 已核只挡 artwork/);**probe_gate 头注释「渲染闸归真人目检」纪律句删除改写**(用户 2026-10-07 明示授权=历史偷懒标记),迭代 15③ 屏蔽挂账的机器补跑半随批核销〔07⑮ 目检半仍归用户不销号〕;②**探针收编**:ui_probe/ui_tune_probe/t17_defer_probe/fx_probe 四件游离探针(实测 17.4s/49.6s/63.0s/21.9s 全绿)收进 probe_gate `--full` 段挂重闸,轻闸默认段零改;damage_probe 出闸留盘标注诊断件、config_fatal_probe 删除(与 run_all 致命类单测重复)、零断言三件标注分析工具+ui_layout 补 1280/1920 最小断言;③**账务修正**:tests/README 套件数/断言数漂移(十五→17、752+→双口径说明)+integration 空目录处置(代拍板)。〔2026-10-07 范围改道:原「明示不收」的 B4/B5/B6 三缺口经用户令各立需求卡片并入本迭代,本卡改称卡①,新增卡②③④见后〕。需求卡片=`design/research/需求卡片-自动化验收补齐批-2026-10-07.md`(v1.1——**待裁点 0 项**,P1-P5+拷打轮一 Q1-Q8 全裁 2026-10-07〔重闸定义正式扩容 CLAUDE.md/过程截图即弃/shot_probe 扩屏三新屏/1280 单档/素材缺位 SKIP 而基线禁锚回退态/基线换代权限呈现批 agent/取证失败=闸红/integration 删空壳〕;🔴 契约 6 条含运行时代码零 diff/轻闸零删减/并行避让;停机点 S1 首跑存量 FAIL 全量呈报登记 06+S3 基线生成素材缺位停机;代拍板白名单四项)。**卡②旧界面呈现断言回补批(B5,2026-10-07 用户令立项)**:迭代 16 前落地旧屏追溯补 ui_contrast 测点对+ui_struct 契约——普查结论=对比度测点对旧屏 0 对(15 对现役全属 HUD/锚点/徽章/生涯)、专项契约仅 pause/career、legend/node 两屏从未过结构族;优先级=战斗结算框>商店>营地柜台+天赋树>设置面>三选一>迷雾锚定对;红线=只加断言零改旧屏、违例登记呈报不修(处置权归用户);需求卡片=`design/research/需求卡片-旧界面呈现断言回补批-2026-10-07.md`(v1.1 grilling 三轮全裁——待裁点 0 项,WL 豁免边界/迷雾合成对最坏判据/时长闸 ≤2s/出口报告/06 遗留件登记全钉死;停机点 2〔对比度首跑存量违例/legend-node 结构首跑〕,T0-T4≈1.0d,worktree iter19-uibackfill)〔2026-10-08 施工已入库·**S1 停机态**——T1+T2 测点对 15→38 对(+23:battle_end 3/shop 5/camp 9/settings 2/curio 2/迷雾合成对 1,全 UiKit 常量实时求值);**新对首跑暴露存量对比度违例 7 项**(结算 GP 账行 2.67:1/商店 legacy 行卡名 1.12:1/货架价签 1.46:1/营地铭牌副题 3.59:1/行价格 4.02:1/树分组头 2.67:1/工坊状态 3.00:1)——全量登记清单+修复建议案〔全现役常量换墨可落〕随出口报告呈用户裁,处置权归用户、批内零修(红线);违例对在册=闸红登记存量形态,修复批落后自然转绿;T3 struct 13→15 根(legend/node 全真路径开合)首跑全绿 S2 未命中、WL 零新增;冒烟 840/0+run_all 17 套件持平;出口报告=`design/qa/iter19-uibackfill-exit-report-2026-10-08.md`;settlement_review 遗留件已在 06 登记落位〕。**卡③CI 预接线批Line truncated
|
||||
| 迭代 20 | **专长实装批(奇物)**——需求卡片=design/research/需求卡片-专长实装批-2026-10-11.md(v1.1)。范围:T0-T10(效果引擎词表扩签/池结构案D 实装/专长 27 件换代/凭证五件/Boss 兑付换代〔已裁换代〕/状态账/断点扩容/MC 新基线/迁出行删除/空配置验收闸)。候选 B-F 留档不变。**✅ 已入库(2026-10-11 goal 会话 U0 开工——A/B/C 三段 T0-T10 全落 master:T1 词表扩签 a57489e/T2 案D 池 0640b0c/T3 29 行换代 54ab330/T4 凭证五件 1387aed/V4 补齐 5ebd05d/C 段收尾 3333187;冒烟 864/0+run_all 21 套+MC 同种子重放逐位复现;29 行件数口径+四级配额 104+疼痛记忆同战消费放宽+时沙/弃图兑换落法映射系代拍板追认单待用户)** | ✅ 已入库(出口闸全绿+实机目检归用户 07 在册) | T0-T10 ✅ |
|
||||
| 迭代 21 | **天赋树实装批(2026-10-11 用户令编入——整卡不切分,迭代容量放宽容纳全卡)**:按 [天赋树](design/gdd/systems/跨局循环/天赋树.md) **v0.8** 设计权威文档实装——**33→12 节点**结构换代(三轮换代:攻防所有权切分 29→21〔树零攻防来源=红线,攻/AC 唯一入口=锻造工坊路线〕/一线一节点 21→15〔生命·命中淬炼各一节点×4 级、row 字段退役〕/Fork 一节点双态 15→12〔开局姿态·危险直觉·财富之道三组各一节点、购买解锁后 A/B 零币切换〕;schema=Fork 词表 4→3 组/规则键词表 2→6 键、「等级阈值前置」形态去留=R1 开放待裁点〔推荐裁撤,**不阻开工门**〕)+三组 Fork 真效果(开局姿态每关首轮窗/危险直觉与财富之道·限次计数随冒险持久按关重置)+营地统一案甲(树=营地主界面/属性升级唯一入口/工作间钥匙模型/图鉴常开/占位面板零副作用硬要求)+注入公式换代(基础值+树淬炼+锻造路线加值位预留+局内加算)+旧属性柜台退役+旧档废弃与旧逻辑删除(开发阶段无迁移,Q3 裁决)+同调位槽位判定源切树节点(旧条件删除随批清扫)+树上刷新升级接线(双入口并存写同账)。需求卡片=design/research/需求卡片-天赋树实装批-2026-10-11.md(**v1.7**——grilling 五裁+歧义扫描六裁+**2026-10-12 完备性/跨系统歧义审查十一裁**全落;零阻塞待裁点);锻造工坊(原「武器锻造」)**17 裁点已清零**(2026-10-12 逐裁落文 v0.5,06 在册),Q5 排期前置已满足、默认本批先行;T0 的「标错机制存在性」与「boss_kill_count 消费面」两项**已预查结案**(标错机制查实存在且有代价,守侧 contingency 作废) | 卡片成立(2026-10-11 用户令编入;**开工门=U0 用户发令**) | T0-T7≈7.0-8.0d(整卡单迭代) |
|
||||
| 迭代 21 | **天赋树实装批(2026-10-11 用户令编入——整卡不切分,迭代容量放宽容纳全卡)**:按 [天赋树](design/gdd/systems/跨局循环/天赋树.md) **v0.8** 设计权威文档实装——**33→12 节点**结构换代(三轮换代:攻防所有权切分 29→21〔树零攻防来源=红线,攻/AC 唯一入口=锻造工坊路线〕/一线一节点 21→15〔生命·命中淬炼各一节点×4 级、row 字段退役〕/Fork 一节点双态 15→12〔开局姿态·危险直觉·财富之道三组各一节点、购买解锁后 A/B 零币切换〕;schema=Fork 词表 4→3 组/规则键词表 2→6 键、「等级阈值前置」形态去留=R1 开放待裁点〔推荐裁撤,**不阻开工门**〕)+三组 Fork 真效果(开局姿态每关首轮窗/危险直觉与财富之道·限次计数随冒险持久按关重置)+营地统一案甲(树=营地主界面/属性升级唯一入口/工作间钥匙模型/图鉴常开/占位面板零副作用硬要求)+注入公式换代(基础值+树淬炼+锻造路线加值位预留+局内加算)+旧属性柜台退役+旧档废弃与旧逻辑删除(开发阶段无迁移,Q3 裁决)+同调位槽位判定源切树节点(旧条件删除随批清扫)+树上刷新升级接线(双入口并存写同账)。需求卡片=design/research/需求卡片-天赋树实装批-2026-10-11.md(**v1.7**——grilling 五裁+歧义扫描六裁+**2026-10-12 完备性/跨系统歧义审查十一裁**全落;零阻塞待裁点);锻造工坊(原「武器锻造」)**17 裁点已清零**(2026-10-12 逐裁落文 v0.5,06 在册),Q5 排期前置已满足、默认本批先行;T0 的「标错机制存在性」与「boss_kill_count 消费面」两项**已预查结案**(标错机制查实存在且有代价,守侧 contingency 作废) | ✅ **已入库(2026-10-12,/goal+iter U0 开工令——T0-T7 全量交付;R1 按既定默认裁撤案执行;实现报告=design/qa/iter21-实现报告-2026-10-12.md;残留人闸=实机目检营地四柜台〔07 在册〕)** | T0-T7≈7.0-8.0d(整卡单迭代——实际 2026-10-12 单会话交付) |
|
||||
| 迭代 22 | **秘宝体系实现重构落地批**——需求卡片=design/research/需求卡片-秘宝体系实现重构落地批-2026-10-11.md(v1.2 零待裁点已成立);**2026-10-11 用户令编入**:新目录 12 件实装+解锁混合制(逐件分配锁定表在卡)+装配不可变(冒险中零更换)+旧 14 行数据清理+效果引擎接入;槽位来源不在本批(树接口接缝、无槽位不能带、测试走调试注入——C4 裁);估时 6d(区间 5-7d);开工门=U0
|
||||
| 迭代 23+ | **M4 数值批(**原 22+;迭代 21 已编入天赋树实装批、迭代 22 已编入秘宝实装批,均 2026-10-11 用户令**——**Alpha 收官位·未排期**〔2026-10-09 语义换代:M4=Beta 阶段、非 Alpha 收官位,M3 未由用户明令关闭前不排期维持〕——2026-10-05 用户令编入迭代 17 卡②;2026-10-06 用户令移出独立成批并定序「Alpha 阶段最后——内容补全先行、统一数值调整殿后」〔当日曾被编入迭代 18 为收官卡,2026-10-07 用户令撤回〕;**2026-10-07 用户令二次后移:后续新迭代(迭代 20 起)一律不先做 M4,除非用户明确要求启动 M4 批次**)**:四件数值欠账合一批——①削弱档位终值(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`(**v1.2 2026-10-06 案C 增补+联审修订**:v1.1 三事实与第五推算件照旧;联审=P3 转正顺延至重扫读数后/检算工具 WEAKEN_PCT 入同步面/F11 两句死文本全刷/T3·T5 四带复算并账一次跑批双认账/R5 改前锚=本批新立案C 锚/估时段一勘误 4.25d;案C 增补内容照旧=§2.6 三事实〔行为换代/锚全作废/难度预判〕+T0 推算稿第五件=对拍锚按新序重推导+T6 四带复算按案C 新行为首采;T0-T8≈6.25d,天然切分点 T4 末;🔴 契约 5 条含跑批批准制;**✅ 裁点全裁(2026-10-06 用户令:P1甲=维持 2 场·magnitude [60,85] 带内当量锚复算、具体值随出口 provisional 追认/P2甲 不对称有意/P3甲 3-3-3·2-2-3 维持、转正顺延至重扫读数到账且带内(Q4 二裁)/P4甲 不立直购价);开工=U0**) | 迭代 18 全部内容卡(③④⑤⑦)出口+全部内容批行为变更落定+**用户发令(未排期)**——重锚窗口须收尽全部内容批行为变更 | T0-T8≈6.25d(超软盒预挂,段一 T0-T4≈4.25d〔v1.2 勘误,原 4.5 系加总错误〕/段二 T5-T8≈2.0d) |
|
||||
|
||||
|
||||
Reference in new issue
Block a user