feat(R1机制位): requires_level等级阈值前置随批重立——用户推翻迭代21追认单#1裁撤案(裁「b)推翻,补机制位小批」)=schema字段Dictionary{node_id:n}+validate悬空/阈值<1/类型错加载即拒+tree_can_buy判定分支+行板门槛呈现(未达标🔒+悬停gate_text)+新单测tree_requires_level_test八断言(run_all 24→25套;首跑抓出测试自身时序bug:零配置行断言跑在cap满状态后,重排修正)+现役12行零配置空转零行为影响(冒烟938/0+probe --click/--full+render_gate全绿零漂移;MC豁免=零数值零行为变化)+天赋树v0.10六处R1表述换代+iter21报告§四#1推翻标注与§八兑现注——doc三闸对基线零新增+熵增PASS

This commit is contained in:
DKChan committed 2026-10-13 06:13:10 +08:00
1 parent 89b0008b03
commit 6e1b52a450
7 files changed
+137 -12

No files matched your search

+7 -6
View File
@@ -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 同批) |
+2 -2
View File
@@ -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 在册)。
+21 -1
View File
@@ -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
+17 -3
View File
@@ -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:
@@ -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 段承载双态切换钮
+1
View File
@@ -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 属性轴)
]
@@ -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)