## VERTICAL SLICE - NOT FOR PRODUCTION ## 天赋树表 schema:逐 node_id 行(脊柱/锻造/钥石/同调四脉)的类型化读取(空表合法态;数值 provisional)。 ## 🔴 层级纪律:局外树只放全局规则件——effects 禁流派标签(archetype 键出域=加载致命); ## 规则改写型特性只走局内随机渠道,永不入树。 class_name MetaTreeTable extends Resource @export var rows: Array[Variant] = [] # Array[Dictionary] 键 node_id const VEINS := ["spine", "forge", "keystone", "attune"] const KINDS := ["stat", "rule", "unlock", "socket", "fork", "easter"] const CURRENCIES := ["keystone", "free"] # 树全钥石单币(无 gp 枚举值,字段保留为呈现元数据) const STAT_KEYS := ["atk", "max_hp", "hit", "armor_class"] ## 规则键封闭词表(零裸 flat 同族护栏——effects.rule 键出域=加载致命;六键=三组 Fork 双态真实接线, ## 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", "requires_level", "effects", "effects_a", "effects_b", "name_a", "name_b", "fork_group", "note"] ## 脊柱行门槛(一线一节点:节点第 N 级可购需全树总等级 ≥ GATE×(N−1)——「投入点数累计解锁深度」) const SPINE_ROW_GATE := 4 ## Fork 双态组词表(每组恰一节点——互斥由单态承载;营地零币切换承载面; ## 无 fg_mastery 克制专精组——类型表达归锻造工坊) const FORK_GROUPS := ["fg_posture", "fg_sense", "fg_economy"] ## 规则键参数域表(键集与取值域逐字=天赋树.md v0.12 规则键参数表——文档定域、schema 执行) const RULE_KEY_PARAMS := { "posture_first_strike": {"ap_bonus": [1, 4]}, "posture_bulwark": {"shield": [1, 9]}, "sense_sharp": {"halve_pct": [1, 99], "cap_per_level": [1, 9]}, "sense_guard": {"ap_refund": [1, 4], "cap_per_level": [1, 9]}, "economy_hoard": {"mult_pct": [101, 300]}, "economy_spend": {"discount_pct": [1, 99]}, } func validate() -> Array: var errs: Array[Variant] = [] if rows.is_empty(): return errs # 空表合法态(建表先行——内容后补) var seen := {} var fork_seen := {} for row in rows: var nid: String = row.get("node_id", "") if nid == "": errs.append({"category": "domain", "message": "node_id 空"}) continue if seen.has(nid): errs.append({"category": "dup_key", "message": "node_id 重复: %s" % nid}) continue seen[nid] = true errs.append_array(_validate_row_head(row, nid)) errs.append_array(_validate_row_numbers(row, nid)) errs.append_array(_validate_row_effects(row, nid)) errs.append_array(_validate_rule_key_params(row, nid)) errs.append_array(_validate_row_fork(row, nid, fork_seen)) errs.append_array(_validate_requires_dangling(rows, seen)) errs.append_array(_validate_requires_level(rows, seen)) # 彩蛋 ≤3 var easter := 0 for row in rows: if String(row.get("kind", "")) == "easter": easter += 1 if easter > 3: errs.append({"category": "load_bearing", "message": "彩蛋节点 %d > 3(上限 3)" % easter}) return errs ## 行头域:命名与枚举判据(display_name/vein/kind/currency/行键白名单) func _validate_row_head(row: Dictionary, nid: String) -> Array[Variant]: var errs: Array[Variant] = [] if String(row.get("display_name", "")) == "": errs.append({"category": "domain", "message": "%s display_name 空" % nid}) if not VEINS.has(String(row.get("vein", ""))): errs.append({"category": "enum", "message": "%s vein 出域" % nid}) var kind := String(row.get("kind", "")) if not KINDS.has(kind): errs.append({"category": "enum", "message": "%s kind 出域" % nid}) if not CURRENCIES.has(String(row.get("currency", ""))): errs.append({"category": "enum", "message": "%s currency 出域" % nid}) for k in row.keys(): if not KEY_WHITELIST.has(String(k)): errs.append({"category": "zero_flat", "message": "%s 行键出白名单: %s" % [nid, String(k)]}) return errs ## 数值域:cap 越域/kind×cap 交叉/price 越域 func _validate_row_numbers(row: Dictionary, nid: String) -> Array[Variant]: var errs: Array[Variant] = [] var kind := String(row.get("kind", "")) var cap := int(row.get("cap", 1)) if cap < 1 or cap > 9: errs.append({"category": "domain", "message": "%s cap 越域 [1,9]: %d" % [nid, cap]}) if kind in ["unlock", "socket", "fork", "easter"] and cap != 1: errs.append({"category": "cross_field", "message": "%s %s 行 cap 恒 1" % [nid, kind]}) var price := int(row.get("price", 0)) if price < 0 or price > 9999: errs.append({"category": "domain", "message": "%s price 越域 [0,9999]: %d" % [nid, price]}) return errs ## 效果域:effects 键词表封闭与局外树层级纪律(archetype 禁入) func _validate_row_effects(row: Dictionary, nid: String) -> Array[Variant]: var errs: Array[Variant] = [] var fx: Dictionary[Variant, Variant] = 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 键出域(stat 线/rule 词表封闭): %s" % [nid, String(ek)]}) # 🔴 层级纪律:局外树禁规则改写型特性(archetype 键出域=加载致命) if fx.has("archetype"): errs.append({"category": "load_bearing", "message": "%s effects 含 archetype(🔴 层级纪律——特性禁入局外树)" % nid}) return errs ## 规则键参数域校验:effects/effects_a/effects_b 中凡 RULE_KEYS 成员的值必须是 Dictionary、 ## 键集恰等 RULE_KEY_PARAMS[键] 的键集(缺参数/多参数即拒)、每值 int 且在闭域内(零缺省——缺字段=加载期报错) func _validate_rule_key_params(row: Dictionary, nid: String) -> Array[Variant]: var errs: Array[Variant] = [] for fx_key in ["effects", "effects_a", "effects_b"]: var fx: Dictionary[Variant, Variant] = row.get(fx_key, {}) as Dictionary for rk in fx.keys(): if not RULE_KEYS.has(String(rk)): continue errs.append_array(_validate_one_rule_key_params(nid, fx_key, String(rk), fx[rk])) return errs ## 单规则键参数字典校验(值须为 Dictionary、键集恰等参数域表、每值 int 且在闭域内) func _validate_one_rule_key_params(nid: String, fx_key: String, rk: String, params_v: Variant) -> Array[Variant]: var errs: Array[Variant] = [] if not (params_v is Dictionary): errs.append({"category": "domain", "message": "%s %s.%s 缺参数(规则键值须为参数字典——零缺省)" % [nid, fx_key, rk]}) return errs var params: Dictionary[Variant, Variant] = params_v var expect: Dictionary[Variant, Variant] = RULE_KEY_PARAMS[rk] for pk in params.keys(): if not expect.has(String(pk)): errs.append({"category": "domain", "message": "%s %s.%s 多参数: %s(参数域表封闭)" % [nid, fx_key, rk, String(pk)]}) elif not (params[pk] is int): errs.append({"category": "domain", "message": "%s %s.%s.%s 越域(须 int)" % [nid, fx_key, rk, String(pk)]}) else: var dom: Array[Variant] = expect[String(pk)] var v := int(params[pk]) if v < int(dom[0]) or v > int(dom[1]): errs.append({"category": "domain", "message": "%s %s.%s.%s 越域 [%d,%d]: %d" % [nid, fx_key, rk, String(pk), int(dom[0]), int(dom[1]), v]}) for pk in expect.keys(): if not params.has(String(pk)): errs.append({"category": "domain", "message": "%s %s.%s 缺参数: %s(参数域表封闭)" % [nid, fx_key, rk, String(pk)]}) return errs ## Fork 结构域:一节点双态结构判据(fork_group 词表/组唯一/主 effects 须空/a/b 双键规则面) func _validate_row_fork(row: Dictionary, nid: String, fork_seen: Dictionary) -> Array[Variant]: var errs: Array[Variant] = [] var kind := String(row.get("kind", "")) 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}) if fork_seen.has(fg): errs.append({"category": "dup_key", "message": "%s fork 组重复(一组恰一节点): %s" % [nid, fg]}) fork_seen[fg] = true var fx: Dictionary[Variant, Variant] = row.get("effects", {}) as Dictionary 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[Variant, Variant] = 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)]}) return errs ## requires 悬空引用域 func _validate_requires_dangling(rows: Array, seen: Dictionary) -> Array[Variant]: var errs: Array[Variant] = [] for row in rows: 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)]}) return errs ## requires_level 等级阈值前置域(R1 机制位——键须为在册节点、阈值整数 ≥1;类型错即拒) func _validate_requires_level(rows: Array, seen: Dictionary) -> Array[Variant]: var errs: Array[Variant] = [] 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}) return errs func row_by_id(node_id: String) -> Dictionary: for row in rows: if String(row.get("node_id", "")) == node_id: return row return {} ## 行门槛(一线一节点:脊柱节点第 N 级可购需全树总等级 ≥ GATE×(N−1); ## 门槛主语=节点等级轴) func level_gate(level: int) -> int: return SPINE_ROW_GATE * maxi(0, level - 1)