Files
gplan-2/game/data/schemas/meta_tree_table.gd
T
DKChan 3b9ee9f134 T8+T9+T10批次二出口(迭代12——天赋树+存档v5+营地树UI+A/B验证):T8树系统=MetaTreeTable schema(vein四脉/kind六类/双币currency/row-gating SPINE_ROW_GATE=4/effects四线+rule键白名单/零archetype层级护栏/Fork互斥对结构/彩蛋≤3)+meta_tree.tres 33节点(脊柱4行×4线GP价10×row/锻造脉3收编forge_levels钥石付价零痛/钥石脉workshop收编+Y升级移植/同调socket3+Fork4对集中同调脉/彩蛋1锻造满排觉醒)+MetaProgression树段(tree_spent_total/tree_can_buy row-gating+前置+币+cap/tree_buy单币钥石承载披露/tree_buy_fork购即激活/tree_toggle_fork免费切换/tree_stat_flat+rule_keys投影/_check_easter_egg觉醒)+rule键消费面(posture_first_strike=首轮AP+1/posture_bulwark=受击减1——enc.tree_rules透传链);T9存档v5(SAVE_FORMAT_VERSION=5树域tree_nodes/fork_sides两新域+v4→v5迁移+三体系迁移migrate_tree=forge原样映射零亏损/item_unlocks→ks_workshop/同调Slots→socket点亮+迁移幂等+断点版本断言4→5换代)+营地树UI(树面板=图鉴柜台下段程序占位——三柜台→一树字面重构延后披露+信号tree_buy_requested/tree_fork_toggled+main接线+投影两函数);T10=MC第7参扩ab_a/ab_b+A/B两侧锚N=20配对(先手压制/稳健拓荒rule键集透传)——两侧差0.0pp<<3pp验收过(A-4反例不触发)+taken 25.0/22.9减伤方向兑现佐证;修缩进坑复发(adventure_runner:826——同坑三犯留痕)+迁移unlock检查全行集(aux_live_ids系过渡口径同调行不在——返工留痕);冒烟树段18断言→714/0+run_all 15+探针绿+doc三闸基线同款+熵增PASS;T10出口包design/qa
2026-10-04 03:02:03 +08:00

119 lines
6.5 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 豁免
# 定义表——RelicTable 先例)。
# 行字段:node_id / display_name / vein ∈ VEINS(spine=主干脊柱 row-gating,GP 买深度;forge=锻造脉
# 数值专精;keystone=钥石脉内容解锁;attune=同调脉槽位与规则)/ kind ∈ KINDS(stat=四线数值/
# rule=全局规则/unlock=内容解锁/socket=同调位/fork=A/B 互斥对/easter=彩蛋觉醒)/
# currency ∈ {gp, keystone, free}(双币铁律:GP 喂脊柱数值、钥石喂 Unlock/Socket/Fork——哈迪斯镜内
# 双币同构;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 追认单呈报)。
class_name MetaTreeTable
extends Resource
@export var rows: Array = [] # Array[Dictionary] 键 node_id
const VEINS := ["spine", "forge", "keystone", "attune"]
const KINDS := ["stat", "rule", "unlock", "socket", "fork", "easter"]
const CURRENCIES := ["gp", "keystone", "free"]
const STAT_KEYS := ["atk", "max_hp", "hit", "armor_class"]
## 规则键封闭词表(零裸 flat 同族护栏——effects.rule 键出域=加载致命;首期两键=T8 Fork 面真实接线)
const RULE_KEYS := ["posture_first_strike", "posture_bulwark"]
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)——「投入点数累计解锁新行」)
const SPINE_ROW_GATE := 4
## A/B 组词表(同组恰两侧 a/b——互斥对;营地免费切换承载面)
const FORK_GROUPS := ["fg_posture", "fg_sense", "fg_economy", "fg_mastery"]
func validate() -> Array:
var errs: Array = []
if rows.is_empty():
return errs # 空表合法态(T8 建表先行——内容随迁移批落)
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
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)]})
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]})
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)]})
# 🔴 层级纪律:局外树禁流派件(archetype 键出域=加载致命)
if fx.has("archetype"):
errs.append({"category": "load_bearing", "message": "%s effects 含 archetype(🔴 层级纪律——流派件禁入局外树)" % nid})
# A/B 互斥对结构:fork 行必带 fork_group+side;同组恰 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
# requires 悬空引用
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)]})
# 彩蛋 ≤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(定稿 §1.4 上限)" % easter})
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 {}
## row-gating 门槛(行 N 可购需全树总等级 ≥ GATE×(N−1);非脊柱节点跟随其 vein 父行——requires 主导)
func row_gate(row_num: int) -> int:
return SPINE_ROW_GATE * maxi(0, row_num - 1)