From 6e1b52a45080f4ed1146cc0859f9c979838c24c2 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 13 Oct 2026 06:13:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(R1=E6=9C=BA=E5=88=B6=E4=BD=8D):=20requires?= =?UTF-8?q?=5Flevel=E7=AD=89=E7=BA=A7=E9=98=88=E5=80=BC=E5=89=8D=E7=BD=AE?= =?UTF-8?q?=E9=9A=8F=E6=89=B9=E9=87=8D=E7=AB=8B=E2=80=94=E2=80=94=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=A8=E7=BF=BB=E8=BF=AD=E4=BB=A321=E8=BF=BD?= =?UTF-8?q?=E8=AE=A4=E5=8D=95#1=E8=A3=81=E6=92=A4=E6=A1=88(=E8=A3=81?= =?UTF-8?q?=E3=80=8Cb)=E6=8E=A8=E7=BF=BB,=E8=A1=A5=E6=9C=BA=E5=88=B6?= =?UTF-8?q?=E4=BD=8D=E5=B0=8F=E6=89=B9=E3=80=8D)=3Dschema=E5=AD=97?= =?UTF-8?q?=E6=AE=B5Dictionary{node=5Fid:n}+validate=E6=82=AC=E7=A9=BA/?= =?UTF-8?q?=E9=98=88=E5=80=BC<1/=E7=B1=BB=E5=9E=8B=E9=94=99=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E5=8D=B3=E6=8B=92+tree=5Fcan=5Fbuy=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=E5=88=86=E6=94=AF+=E8=A1=8C=E6=9D=BF=E9=97=A8=E6=A7=9B?= =?UTF-8?q?=E5=91=88=E7=8E=B0(=E6=9C=AA=E8=BE=BE=E6=A0=87=F0=9F=94=92+?= =?UTF-8?q?=E6=82=AC=E5=81=9Cgate=5Ftext)+=E6=96=B0=E5=8D=95=E6=B5=8Btree?= =?UTF-8?q?=5Frequires=5Flevel=5Ftest=E5=85=AB=E6=96=AD=E8=A8=80(run=5Fall?= =?UTF-8?q?=2024=E2=86=9225=E5=A5=97;=E9=A6=96=E8=B7=91=E6=8A=93=E5=87=BA?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E8=87=AA=E8=BA=AB=E6=97=B6=E5=BA=8Fbug:?= =?UTF-8?q?=E9=9B=B6=E9=85=8D=E7=BD=AE=E8=A1=8C=E6=96=AD=E8=A8=80=E8=B7=91?= =?UTF-8?q?=E5=9C=A8cap=E6=BB=A1=E7=8A=B6=E6=80=81=E5=90=8E,=E9=87=8D?= =?UTF-8?q?=E6=8E=92=E4=BF=AE=E6=AD=A3)+=E7=8E=B0=E5=BD=B912=E8=A1=8C?= =?UTF-8?q?=E9=9B=B6=E9=85=8D=E7=BD=AE=E7=A9=BA=E8=BD=AC=E9=9B=B6=E8=A1=8C?= =?UTF-8?q?=E4=B8=BA=E5=BD=B1=E5=93=8D(=E5=86=92=E7=83=9F938/0+probe=20--c?= =?UTF-8?q?lick/--full+render=5Fgate=E5=85=A8=E7=BB=BF=E9=9B=B6=E6=BC=82?= =?UTF-8?q?=E7=A7=BB;MC=E8=B1=81=E5=85=8D=3D=E9=9B=B6=E6=95=B0=E5=80=BC?= =?UTF-8?q?=E9=9B=B6=E8=A1=8C=E4=B8=BA=E5=8F=98=E5=8C=96)+=E5=A4=A9?= =?UTF-8?q?=E8=B5=8B=E6=A0=91v0.10=E5=85=AD=E5=A4=84R1=E8=A1=A8=E8=BF=B0?= =?UTF-8?q?=E6=8D=A2=E4=BB=A3+iter21=E6=8A=A5=E5=91=8A=C2=A7=E5=9B=9B#1?= =?UTF-8?q?=E6=8E=A8=E7=BF=BB=E6=A0=87=E6=B3=A8=E4=B8=8E=C2=A7=E5=85=AB?= =?UTF-8?q?=E5=85=91=E7=8E=B0=E6=B3=A8=E2=80=94=E2=80=94doc=E4=B8=89?= =?UTF-8?q?=E9=97=B8=E5=AF=B9=E5=9F=BA=E7=BA=BF=E9=9B=B6=E6=96=B0=E5=A2=9E?= =?UTF-8?q?+=E7=86=B5=E5=A2=9EPASS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/gdd/systems/跨局循环/天赋树.md | 13 +-- design/qa/iter21-实现报告-2026-10-12.md | 4 +- game/core/meta_progression.gd | 22 ++++- game/data/schemas/meta_tree_table.gd | 20 ++++- game/presentation/views/camp_overlay_view.gd | 4 + game/tests/run_all.gd | 1 + .../unit/core/tree_requires_level_test.gd | 85 +++++++++++++++++++ 7 files changed, 137 insertions(+), 12 deletions(-) create mode 100644 game/tests/unit/core/tree_requires_level_test.gd diff --git a/design/gdd/systems/跨局循环/天赋树.md b/design/gdd/systems/跨局循环/天赋树.md index d7223d49..a4aec7a1 100644 --- a/design/gdd/systems/跨局循环/天赋树.md +++ b/design/gdd/systems/跨局循环/天赋树.md @@ -1,7 +1,7 @@ # 天赋树 > 一句话:营地本身——跨局成长的结构承载:钥石逐节点点亮一棵树,属性淬炼、工作间解锁、规则抉择与同调挂载全部组织在树上 -> 状态:**v0.9 迭代 21 反哺落文(2026-10-13,用户裁「按呈报落文」)**——全文档换代为 12 节点落地实态(实装 = 迭代 21 天赋树实装批 2026-10-12 入库,worktree g-plan2-iter21-tree 单会话交付):头部实装态 / 节点总表前言 / schema 连带 / 已建未接面(三条→两接一维持)/ P0 段 / 依赖表 / 验收实态七处换代;「设计避让」句按卡片 v1.7 ⑪②裁决改写为折扣定义边界口径;R1 按卡片既定裁撤案执行(追认单在册)。承 v0.8 Fork 一节点双态换代(2026-10-11 用户裁——开局姿态/危险直觉/财富之道三天赋=三节点、购买解锁后 A/B 双态可切,现役 A/B 双行结构收编、全树 15→12) +> 状态:**v0.10 R1 机制位批(2026-10-13,用户推翻迭代 21 追认单 #1 裁撤案)**——「等级阈值前置」以机制位形态随批重立:schema 新增 `requires_level: {node_id: n}`(前置节点等级 ≥ n 才可购;键悬空/阈值 <1/类型错加载即拒)+ 购买判定分支 + 行板门槛呈现(未达标 🔒 标+悬停门槛文案)+ 单测八断言;**现役 12 行零配置=机制位空转零行为影响**,配置归表行随需填。承 v0.9 迭代 21 反哺落文(2026-10-13,用户裁「按呈报落文」)——全文档换代为 12 节点落地实态:头部实装态 / 节点总表前言 / schema 连带 / 已建未接面(三条→两接一维持)/ P0 段 / 依赖表 / 验收实态七处换代;「设计避让」句按卡片 v1.7 ⑪②裁决改写为折扣定义边界口径。承 v0.8 Fork 一节点双态换代(2026-10-11 用户裁——开局姿态/危险直觉/财富之道三天赋=三节点、购买解锁后 A/B 双态可切,现役 A/B 双行结构收编、全树 15→12) > **📖 节点台账指引(读这份文档先看这里)**:本树全部 12 个节点=下方「核心规则 → 节点总表」,逐节点列名称/脉/类型/前置/效果方向;**全部数值行(价格 / 等级上限 / 每级效果取值 / 限次值)不在本文**,事实源 = 配置文件 `game/data/tables/meta_tree.tres`(provisional 归数值批)。要查"有哪些节点、怎么连"看本文总表,要查"多少钱、加多少"开配置文件。 @@ -19,7 +19,7 @@ | 负责 | 说明 | |---|---| -| 节点结构与解锁规则 | 12 节点的一脊三脉构成、脊柱行门槛(节点第 N 级)、点亮前置(「等级阈值前置」形态已按 R1 裁撤案执行——requires 只认点亮前置,追认单 #1 在册)、Fork 一节点双态与免费切换、彩蛋觉醒——结构事实源 = `data/tables/meta_tree.tres` + schema MetaTreeTable(**已实装在役,迭代 21 T1**) | +| 节点结构与解锁规则 | 12 节点的一脊三脉构成、脊柱行门槛(节点第 N 级)、点亮前置(requires)+ **等级阈值前置(requires_level——R1 机制位 2026-10-13 随批重立,现役零配置空转)**、Fork 一节点双态与免费切换、彩蛋觉醒——结构事实源 = `data/tables/meta_tree.tres` + schema MetaTreeTable(**已实装在役,迭代 21 T1**) | | 树的购买与货币规则 | 全树钥石单币;free 节点零币;购买校验四件套(等级上限 / 行门槛 / 前置 / 未解锁) | | 营地统一模型 | 树=营地本体(呈现=四柜台并排):生命/命中淬炼唯一入口=树脊柱淬炼节点,攻/AC 唯一入口=锻造工坊路线(所有权切分,2026-10-11 裁);工作间钥匙模型(锻造工坊 / 秘宝匣);图鉴常开;旧属性锻造柜台退役(生命归脊柱、攻防归锻造工坊) | | 树域存档的内容语义 | tree_nodes(node_id→已购等级)与 fork_sides(fork_group→激活态)两域语义;旧档废弃规则(开发阶段无迁移——2026-10-11 用户裁) | @@ -67,7 +67,7 @@ | 钥石脉(keystone) | 2 | 秘宝匣(kind=unlock,free,工作间钥匙)/ 刷新升级(kind=rule) | 前置链:秘宝匣 ← 脊柱生命第 1 级;刷新升级 ← 秘宝匣 | | 同调脉(attune) | 6 | 同调位 1/2/3 三节点(kind=socket)+ Fork 3 节点(kind=fork,一节点双态)= **3 同调位 + 3 双态开关节点** | **同调位 1 为根节点、无前置**,其后逐级前置(1→2→3);全部 Fork 前置 = 同调位 1;Fork=开局姿态/危险直觉/财富之道各一节点(2026-10-11 用户裁:一节点双态、解锁后 A/B 可切——非六节点) | -节点类型是封闭枚举六类:stat(生命/命中两线数值)/ rule(全局规则)/ unlock(内容解锁)/ socket(同调位)/ fork(一节点双态开关)/ easter(彩蛋觉醒)。字段、词表与跨字段约束由 schema 校验(MetaTreeTable.validate:脉 / 类型 / 币出域即拒、effects 键封闭、Fork 三组每组恰一节点、彩蛋 ≤3、禁 archetype 键——加载期校验)。**schema 连带已随迭代 21 T1 落地**:Fork 由 A/B 双行改单行双态(行持 a/b 双效果键 effects_a/b+name_a/b,效果按激活态取值;Fork 组词表 4→3 组)、规则键词表 2→6 键(见 Fork 节)、row 字段随一线一节点退役;「等级阈值前置」形态已按 R1 裁撤案执行——requires 只留点亮前置(追认单 #1 在册)。 +节点类型是封闭枚举六类:stat(生命/命中两线数值)/ rule(全局规则)/ unlock(内容解锁)/ socket(同调位)/ fork(一节点双态开关)/ easter(彩蛋觉醒)。字段、词表与跨字段约束由 schema 校验(MetaTreeTable.validate:脉 / 类型 / 币出域即拒、effects 键封闭、Fork 三组每组恰一节点、彩蛋 ≤3、禁 archetype 键——加载期校验)。**schema 连带已随迭代 21 T1 落地**:Fork 由 A/B 双行改单行双态(行持 a/b 双效果键 effects_a/b+name_a/b,效果按激活态取值;Fork 组词表 4→3 组)、规则键词表 2→6 键(见 Fork 节)、row 字段随一线一节点退役;「等级阈值前置」机制位 **2026-10-13 随批重立**(requires_level 字段+加载校验,现役零配置——见头部状态行)。 ### 节点总表(12 节点) @@ -94,7 +94,7 @@ 所有付费节点只收钥石(2026-10-07 用户裁甲落定;行内 currency 字段保留为呈现元数据)。free 节点零币:两枚工作间钥匙(锻造工坊 / 秘宝匣)与彩蛋(觉醒)不经购买消费。 -购买校验四件套,全部通过才受理:①当前等级未满上限;②行门槛——脊柱节点第 N 级需全树已购总等级 ≥ 4×(N−1)(总等级口径 = tree_nodes 全部节点等级之和,免费点亮与分叉激活均计入——2026-10-11 歧义扫描裁甲沿现役实态;常量 SPINE_ROW_GATE=4;2026-10-11 一线一节点换代后门槛主语=节点目标等级,原层行判定作废;达成节奏重算归数值批);③requires 前置满足——点亮前置(前置节点全部已点亮,现行);「等级阈值前置」新形态(指定线的累计等级达标,v0.2 为锻造工坊钥匙而设)随钥匙改无前置失去唯一消费者,去留=开放待裁点 R1(2026-10-11 呈报,推荐裁撤——无消费者不加机制位,需要时随批重立);④Fork 节点未解锁(一节点双态——解锁后的态切换走零币切换口,不经购买)。执行购买时另查钥石余额:余额不足则购买失败,存档零副作用(等级、余额、激活态均不动)。 +购买校验四件套,全部通过才受理:①当前等级未满上限;②行门槛——脊柱节点第 N 级需全树已购总等级 ≥ 4×(N−1)(总等级口径 = tree_nodes 全部节点等级之和,免费点亮与分叉激活均计入——2026-10-11 歧义扫描裁甲沿现役实态;常量 SPINE_ROW_GATE=4;2026-10-11 一线一节点换代后门槛主语=节点目标等级,原层行判定作废;达成节奏重算归数值批);③requires 前置满足——点亮前置(前置节点全部已点亮)+ **等级阈值前置(requires_level:指定前置节点等级 ≥ n——R1 机制位 2026-10-13 随批重立,2026-10-12 曾按卡片默认裁撤案执行、用户推翻补机制位;现役 12 行零配置空转,配置归表行随需填)**;④Fork 节点未解锁(一节点双态——解锁后的态切换走零币切换口,不经购买)。执行购买时另查钥石余额:余额不足则购买失败,存档零副作用(等级、余额、激活态均不动)。 写口唯一:营地界面只发购买意图信号(tree_buy_requested),扣币、升级、写盘全部经 MetaProgression.tree_buy;Fork 节点购买走 tree_buy_fork(购即解锁、默认 A 态),态切换走 tree_toggle_fork(零币、只改激活态)。 @@ -174,7 +174,7 @@ A/B 态只放规则面:节点行持 a/b 双效果键(封闭规则键词表 R | 表 / 常量 | 键结构归属 | 数值行 | |---|---|---| -| 节点表 `data/tables/meta_tree.tres`(换代后 12 行:价格 / 等级上限 / requires(点亮前置;等级阈值形态去留=R1 待裁)/ effects 取值(Fork 行持 a/b 双效果键)/ 限次值;row 字段随一线一节点退役) | schema MetaTreeTable,本文 owner | 全 provisional,归数值批投值 | +| 节点表 `data/tables/meta_tree.tres`(换代后 12 行:价格 / 等级上限 / requires(点亮前置)/ **requires_level(等级阈值前置——R1 机制位,现役零配置)** / effects 取值(Fork 行持 a/b 双效果键)/ 限次值;row 字段随一线一节点退役) | schema MetaTreeTable,本文 owner | 全 provisional,归数值批投值 | | 脊柱行门槛常量 SPINE_ROW_GATE = 4 | `meta_tree_table.gd` 常量,本文 owner | 常量即规则(节点第 N 级需全树总等级 ≥ 4×(N−1)) | | Fork 组词表 FORK_GROUPS(三组)/ 规则键词表 RULE_KEYS(换代后六键,扩表随实装批) | `meta_tree_table.gd` 封闭词表 | 词表即规则;规则键效果值硬编码于效果引擎 rebuild | | 钥石产出、刷新升级行族 | 现行事实源 = [元进度](元进度.md)调参表 | `data/tables/meta.tres` | @@ -185,7 +185,7 @@ A/B 态只放规则面:节点行持 a/b 双效果键(封闭规则键词表 R - 结构封闭:meta_tree.tres 通过 schema 校验零违例——12 节点、脉 / 类型 / 币词表出域即拒、Fork 三组每组恰一节点且持 a/b 双效果键、彩蛋 ≤3、effects 键封闭、零 archetype 键。 - 行门槛可判:全树已购总等级 < 4×(N−1) 时脊柱节点第 N 级不可购;总等级达标当刻该级开放购买。 -- 前置可判(点亮前置):requires 任一未点亮则节点不可购,逐一点亮后可购;锻造工坊钥匙无前置——初始即可点亮(free 零币)。(「等级阈值前置」形态去留=R1 待裁,裁撤则本行即前置全量。) +- 前置可判(点亮前置):requires 任一未点亮则节点不可购,逐一点亮后可购;锻造工坊钥匙无前置——初始即可点亮(free 零币)。**等级阈值前置(requires_level):配置了该键的节点,指定前置节点等级 < n 时不可购、达标当刻开放(单测八断言承载——schema 四拒一过+判定三态+投影两态;现役零配置,判定面经测试夹具直构行验证)。** - 工作间可判:钥匙节点(锻造工坊 / 秘宝匣)未点亮时对应柜台锁定态、工作间不可进入;点亮后展开;图鉴无门槛常开。 - 单币可判:任一付费节点购买只扣钥石;余额不足购买失败,且存档零变化(等级、余额、激活态均不动)。 - 双态可判:Fork 节点购买解锁后默认 A 态;切换零币,切换后规则键集=新激活态效果键;同态重复切换零操作;未解锁 Fork 不可切换、不可重复购买。 @@ -201,6 +201,7 @@ A/B 态只放规则面:节点行持 a/b 双效果键(封闭规则键词表 R | 版本 | 日期 | 改动 | |---|---|---| +| v0.10 | 2026-10-13 | **R1 机制位批(用户推翻迭代 21 追认单 #1 裁撤案)**:「等级阈值前置」以机制位形态随批重立——schema requires_level 字段(Dictionary{node_id: n},键悬空/阈值 <1/类型错加载即拒)+ tree_can_buy 判定分支 + 行板门槛呈现(未达标 🔒+悬停文案 gate_text)+ 单测 tree_requires_level_test 八断言(run_all 24→25 套);现役 12 行零配置=空转零行为影响(冒烟 938/0+全闸复跑零漂移),配置归表行随需填。六处 R1 表述换代(状态行/范围表/schema 连带/购买校验③/调参表/验收前置行) | | v0.9 | 2026-10-13 | **迭代 21 反哺落文批(实现报告 §五①③⑥,用户裁「按呈报落文」)**:①实装落地态全文档换代七处——头部状态行 / 系统范围表 / 节点总表前言与三行 / schema 连带句 / 依赖表 / P0 段 / 验收实态注(原「实装未落、现役 33 节点」过渡态终结;迭代 21 = 2026-10-12 入库);③「财富之道」设计避让句按卡片 v1.7 ⑪②裁决改写为折扣定义边界口径(散=只覆盖商品购买价,键 gp_cost_mult,不含两处刷新——定义边界非临时避让);⑥验收实态注(机检读数+命中线契约 6 括注定性);连带:已建未接面节换代(同调位判定源已切树点亮数 / 刷新升级同账路由 / 命中线契约 6 维持——两接一维持)。机制与数值零改动 | | v0.8 | 2026-10-11 | **Fork 一节点双态换代批(用户裁)**:①开局姿态/危险直觉/财富之道三天赋=三节点——现役 A/B 双行结构(迭代 12 沿袭,「付一次钱得双态开关」裁决未落进节点组织=落文错误已纠正)收编为单行双态,同调脉 9→6(同调位 3+Fork 3)、全树 15→12;②节点语义=购买解锁双态开关(cap1、零重复购买)、解锁默认 A 态、营地零币切换(互斥由单态承载);fork_sides 存档语义不变(组→激活态),tree_buy_fork=购买解锁/tree_toggle_fork=零币切态;schema=Fork 行持 a/b 双效果键、效果按激活态取值、Fork 组词表校验改每组恰一节点;③R1(「等级阈值前置」形态去留)不随本批变化,仍开放待裁(推荐裁撤)。实装归 06「天赋树实装批」(33→12 连带,需求卡片 v1.6 同批) | | v0.7 | 2026-10-11 | **一线一节点换代批(用户裁)**:①脊柱淬炼一线一节点——生命淬炼/命中淬炼各收编为单节点 4 级(原「一线 4 层×层内 3 购」层=级收编;用户明裁:一个内容一个节点、等级在节点内、不拆多节点),脊柱 8→2、全树 21→15;②行门槛平移——SPINE_ROW_GATE=4 公式不变,门槛主语从「层 N」改「节点第 N 级」(第 2~4 级需全树总投入 ≥4/8/12),row 字段随层行退役;③彩蛋条件随结构换代为「生命/命中两节点 4 级拉满 且 同调位 1/2/3 全部解锁」;④R1(「等级阈值前置」形态去留)不随本批变化,仍开放待裁(推荐裁撤)。实装归 06「天赋树实装批」(33→15 连带,需求卡片 v1.5 同批) | diff --git a/design/qa/iter21-实现报告-2026-10-12.md b/design/qa/iter21-实现报告-2026-10-12.md index 3ecb7ffb..829a4eed 100644 --- a/design/qa/iter21-实现报告-2026-10-12.md +++ b/design/qa/iter21-实现报告-2026-10-12.md @@ -35,7 +35,7 @@ | # | 事项 | 代拍板内容 | 白名单依据 | |---|---|---|---| -| 1 | **R1 裁撤案执行** | 「等级阈值前置」形态未实现(schema 无 requires_level 字段,requires 只认点亮)——按卡片 §10「默认按裁撤案排程」执行 | 卡片既定默认案( roadmap 行内「不阻开工门」明示) | +| 1 | **R1 裁撤案执行** | 「等级阈值前置」形态未实现(schema 无 requires_level 字段,requires 只认点亮)——按卡片 §10「默认按裁撤案排程」执行。**〔2026-10-13 追认结果:推翻——用户裁「b) 推翻,补机制位小批」;requires_level 机制位已随批重立(schema 字段+校验+判定+呈现+单测八断言,现役零配置空转),见天赋树.md v0.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 | @@ -84,6 +84,6 @@ ## 八、遗留与边界 -- **R1 追认**:裁撤案已执行(追认单 #1),用户推翻即补 requires_level 形态(随批重立)。 +- **R1 追认**:裁撤案已执行(追认单 #1),用户推翻即补 requires_level 形态(随批重立)。**〔已兑现(2026-10-13):用户推翻——requires_level 机制位随批重立完毕,天赋树.md v0.10;追认单 #1 状态=推翻后重立收口〕** - **契约 12 编排级断言口径**:守侧五场景以「编排 rig(begin_battle 携 sense_guard + 授予段 _judge_flag 直注)+引擎直构」双承载(V10 断言面);②被挖旗零触发由发射面既有过滤承载(插旗不可逆批既有行为)。 - **迭代 20 残留人闸不受影响**:实机目检五件+V20/V21/V22 人工核对半仍待用户(07 在册)。 diff --git a/game/core/meta_progression.gd b/game/core/meta_progression.gd index dbc001f4..449e156b 100644 --- a/game/core/meta_progression.gd +++ b/game/core/meta_progression.gd @@ -341,6 +341,12 @@ func tree_can_buy(node_id: String) -> bool: for req in (row.get("requires", []) as Array): if save.tree_level(String(req)) < 1: return false + # 等级阈值前置(R1 机制位——requires_level {node_id: n}:前置节点等级 ≥ n 才可购; + # 2026-10-13 用户推翻裁撤案随批重立,现役表零配置=空转零行为影响) + var req_lv: Dictionary = row.get("requires_level", {}) as Dictionary + for req_id in req_lv.keys(): + if save.tree_level(String(req_id)) < int(req_lv[req_id]): + return false return true @@ -432,10 +438,24 @@ func tree_camp_rows() -> Array: "price": int(row.get("price", 0)), "currency": String(row.get("currency", "keystone")), "can_buy": tree_can_buy(String(row.get("node_id", ""))), + "gate_text": tree_gate_text(String(row.get("node_id", ""))), }) return out +# 行板门槛文案(R1 机制位呈现——requires_level 未达标项拼「需 {前置名} Lv{n}」;达标/未配置=空串。 +# 只呈现未达标项:达标后锁标消失,不做全量前置罗列) +func tree_gate_text(node_id: String) -> String: + var row: Dictionary = db.tree_node(node_id) + var rl: Dictionary = row.get("requires_level", {}) as Dictionary + var parts: Array[String] = [] + for req_id in rl.keys(): + if save.tree_level(String(req_id)) < int(rl[req_id]): + var req_row: Dictionary = db.tree_node(String(req_id)) + parts.append("需 %s Lv%d" % [String(req_row.get("display_name", req_id)), int(rl[req_id])]) + return "、".join(parts) + + func tree_fork_groups() -> Array: var out: Array = [] var sides: Dictionary = save.fork_sides() @@ -460,7 +480,7 @@ func tree_fork_groups() -> Array: # 彩蛋觉醒检查(特化判定——生命/命中两线拉满 且 同调位 1/2/3 全解锁 → easter_ley 免费点亮; -# 迭代 21 T2 换代:不再依赖 requires 点亮形态〔R1 裁撤〕,发现率监控呈报面沿袭) +# 迭代 21 T2 换代:不依赖 requires/requires_level 前置形态,发现率监控呈报面沿袭) func _check_easter_egg() -> void: if save.tree_level("easter_ley") > 0: return diff --git a/game/data/schemas/meta_tree_table.gd b/game/data/schemas/meta_tree_table.gd index 3d7b4e70..fb6b4e7e 100644 --- a/game/data/schemas/meta_tree_table.gd +++ b/game/data/schemas/meta_tree_table.gd @@ -8,8 +8,10 @@ # 解锁;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 只保留点亮前置)/ +# easter 恒 1)/ requires Array[node_id](点亮前置——前置节点 tree_level>=1 即满足)/ +# requires_level Dictionary{node_id: n}(等级阈值前置——R1 机制位:2026-10-12 卡片 v1.7 默认按 +# 裁撤案执行后,2026-10-13 用户推翻补机制位随批重立;前置节点等级 ≥ n 才可购;现役 12 行 +# 零配置=机制位空转零行为影响,配置归表行随需填;键悬空/阈值 <1 加载即拒)/ # 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 双态呈现名; @@ -35,7 +37,7 @@ const STAT_KEYS := ["atk", "max_hp", "hit", "armor_class"] ## 迭代 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", - "requires", "effects", "effects_a", "effects_b", "name_a", "name_b", "fork_group", "note"] + "requires", "requires_level", "effects", "effects_a", "effects_b", "name_a", "name_b", "fork_group", "note"] ## 脊柱行门槛(一线一节点:节点第 N 级可购需全树总等级 ≥ GATE×(N−1)——「投入点数累计解锁深度」) const SPINE_ROW_GATE := 4 @@ -109,6 +111,18 @@ func validate() -> Array: for req in (row.get("requires", []) as Array): if not seen.has(String(req)): errs.append({"category": "dangling", "message": "%s requires 悬空: %s" % [String(row.get("node_id", "")), String(req)]}) + # requires_level 等级阈值前置(R1 机制位——键须为在册节点、阈值整数 ≥1;类型错即拒) + for row in rows: + var nid2: String = String(row.get("node_id", "")) + var rl_v: Variant = row.get("requires_level", {}) + if rl_v is Dictionary: + for req_id in (rl_v as Dictionary).keys(): + if not seen.has(String(req_id)): + errs.append({"category": "dangling", "message": "%s requires_level 悬空: %s" % [nid2, String(req_id)]}) + if int(rl_v[req_id]) < 1: + errs.append({"category": "domain", "message": "%s requires_level 阈值须 ≥1: %s=%d" % [nid2, String(req_id), int(rl_v[req_id])]}) + elif row.has("requires_level"): + errs.append({"category": "domain", "message": "%s requires_level 须为 Dictionary {node_id: n}" % nid2}) # 彩蛋 ≤3 var easter := 0 for row in rows: diff --git a/game/presentation/views/camp_overlay_view.gd b/game/presentation/views/camp_overlay_view.gd index 40338066..4b9b5552 100644 --- a/game/presentation/views/camp_overlay_view.gd +++ b/game/presentation/views/camp_overlay_view.gd @@ -245,6 +245,10 @@ func _rebuild_tree(data: Dictionary) -> void: var name_l := UiKit.label("%s Lv%d/%d" % [String(row.get("display_name", "")), lvl, cap], 13, false, UiKit.INK_LABEL) name_l.custom_minimum_size = Vector2(150, 0) name_l.clip_text = true + var gate := String(row.get("gate_text", "")) + if gate != "": + name_l.text += " 🔒" # 等级阈值前置未达标标记(R1 机制位——悬停看门槛;达标后随 refresh 消失) + name_l.tooltip_text = "等级门槛:%s" % gate hb.add_child(name_l) if String(row.get("kind", "")) == "fork": continue # Fork 行去行板化——由 fork_groups 段承载双态切换钮 diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index 7299f26d..38a09c7e 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -49,6 +49,7 @@ func _suites() -> Array: {"path": "res://tests/unit/core/effect_words_t1_test.gd", "expect": 0}, {"path": "res://tests/unit/core/treasure_effects_test.gd", "expect": 0}, # 迭代 22 秘宝批 T3——新目录 12 件逐件行为(V7 附属表正常/边界/反例) {"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/tree_requires_level_test.gd", "expect": 0}, # R1 机制位批 2026-10-13——requires_level 等级阈值前置(schema 四拒一过+判定三态+投影两态) {"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 属性轴) ] diff --git a/game/tests/unit/core/tree_requires_level_test.gd b/game/tests/unit/core/tree_requires_level_test.gd new file mode 100644 index 00000000..35cdf596 --- /dev/null +++ b/game/tests/unit/core/tree_requires_level_test.gd @@ -0,0 +1,85 @@ +# tests/unit/core/tree_requires_level_test.gd — R1 机制位:requires_level 等级阈值前置 +# (2026-10-13 用户推翻迭代 21 追认单 #1 裁撤案随批重立——卡片 v1.7 留任案形态落地: +# schema requires_level Dictionary{node_id: n} + validate 悬空/阈值 <1/类型错即拒 + +# tree_can_buy 等级阈值判定 + 行板 gate_text 门槛文案投影。 +# 现役 meta_tree.tres 12 行零配置=机制位空转,判定面经本夹具直构行承载—— +# 承接卡片 V2 括注「若 R1 留任阈值形态→恢复『指定节点累计等级达标前不可购』断言」。) +# 载体:run_all 单元套件(显式实例化夹具——--script 模式无 autoload)。 +extends SceneTree + + +func _init() -> void: + var failures := 0 + var db = load("res://core/config_db.gd").new() + var mt = load("res://data/schemas/meta_tree_table.gd").new() + # 夹具两行:gate_pre(普通 rule 行,cap2)+ gate_dep(点亮前置+等级阈值前置都指向 gate_pre) + var base_pre := { + "node_id": "gate_pre", "display_name": "门槛前置", "vein": "attune", "kind": "rule", + "currency": "keystone", "price": 10, "cap": 2, "requires": [], "effects": {}, + } + var base_dep := { + "node_id": "gate_dep", "display_name": "门槛依赖", "vein": "attune", "kind": "rule", + "currency": "keystone", "price": 20, "cap": 1, "requires": ["gate_pre"], + "requires_level": {"gate_pre": 2}, "effects": {}, + } + # ① schema:合法 requires_level 零违例 + mt.rows = [base_pre.duplicate(true), base_dep.duplicate(true)] + var errs: Array = mt.validate() + if not errs.is_empty(): + failures += 1 + print("[r1] FAIL 合法 requires_level 应零违例: %s" % str(errs)) + # ② schema:悬空键拒 + mt.rows = [base_pre.duplicate(true), base_dep.duplicate(true)] + mt.rows[1]["requires_level"] = {"ghost_node": 1} + errs = mt.validate() + if errs.is_empty(): + failures += 1 + print("[r1] FAIL 悬空 requires_level 键应拒") + # ③ schema:阈值 <1 拒 + mt.rows = [base_pre.duplicate(true), base_dep.duplicate(true)] + mt.rows[1]["requires_level"] = {"gate_pre": 0} + errs = mt.validate() + if errs.is_empty(): + failures += 1 + print("[r1] FAIL 阈值 0 应拒") + # ④ schema:类型错(数组)拒 + mt.rows = [base_pre.duplicate(true), base_dep.duplicate(true)] + mt.rows[1]["requires_level"] = ["gate_pre"] + errs = mt.validate() + if errs.is_empty(): + failures += 1 + print("[r1] FAIL 非Dictionary requires_level 应拒") + # ⑤ 判定面:真 SaveManager + MetaProgression(db 只挂直构 meta_tree——gate 行走 attune/rule + # 分支,零脊柱行门槛、非 ks_refresh 零刷新分支干扰) + var sv = load("res://core/save_manager.gd").new("user://r1_mech_test_save.json") + var mp = load("res://core/meta_progression.gd").new(db, sv) + mt.rows = [base_pre.duplicate(true), base_dep.duplicate(true)] + db.set("meta_tree", mt) + sv.set_tree_level("gate_pre", 1) + if mp.tree_can_buy("gate_dep"): + failures += 1 + print("[r1] FAIL 前置 Lv1 < 阈值 2 应拒购") + # ⑥ 判定面:零配置行不受影响(gate_pre 自身无 requires_level——Lv1 < cap2 未满可购) + if not mp.tree_can_buy("gate_pre"): + failures += 1 + print("[r1] FAIL 无 requires_level 行判定不受机制位影响") + # ⑦ 判定面:达标开放 + sv.set_tree_level("gate_pre", 2) + if not mp.tree_can_buy("gate_dep"): + failures += 1 + print("[r1] FAIL 前置 Lv2 达阈值应可购") + # ⑧ 投影:gate_text 未达标拼门槛文案、达标清空 + sv.set_tree_level("gate_pre", 1) + var gate: String = mp.tree_gate_text("gate_dep") + if gate != "需 门槛前置 Lv2": + failures += 1 + print("[r1] FAIL 未达标 gate_text 应=「需 门槛前置 Lv2」实得「%s」" % gate) + sv.set_tree_level("gate_pre", 2) + if mp.tree_gate_text("gate_dep") != "": + failures += 1 + print("[r1] FAIL 达标后 gate_text 应为空串") + if failures == 0: + print("[r1_requires_level_test] PASS — schema 四拒一过 + 判定三态 + 投影两态 全绿") + else: + print("[r1_requires_level_test] FAIL — %d 项红" % failures) + quit(1 if failures > 0 else 0)