T1 schema 换代:MetaTreeTable FORK_GROUPS 4→3(fg_mastery 退役)/RULE_KEYS 2→6(sense/economy 四键扩签)/ fork 单行双态(effects_a/b+name_a/b、每组恰一校验)/row 字段退役(行门槛主语=节点等级 level_gate)/ requires 只留点亮前置(R1 裁撤案执行);meta_tree.tres 33→12 行重排(脊柱2/锻造2含彩蛋/钥石2/同调6, att_socket1 改根节点无前置,easter_ley 摘 atk/armor_class 分量=契约9) T2 购买与彩蛋面:tree_can_buy easter 拒购+行门槛换轴;tree_buy_fork=解锁默认A;tree_toggle_fork 零币切态; tree_rule_keys 按激活态 effects_a/b 投影;_check_easter_egg 特化判定(两线拉满+socket全解锁);营地树行板双态钮 T3 六键接线:EVENTS+flag_missed/FACTS+battle_index(L2 扩签两处);rebuild 六键合成订阅 (posture 窗=battle_index&&round_index 双闸/sense 两组=counter 闸+记账双行 domain:adventure 限次2次 provisional); level_runner battle_index 注入(visited battle 型计数);_judge_flag 去重闸后 flag_missed 发射(契约12); 聚=_roll_gp_drop 入账前×1.5 provisional(escrow 不乘);散=_spend_discounted ×80%(current_price/pool_price 双计价点,刷新价不折=§11②定义边界);_start_current_level 限次按关清零(_reset_tree_limited_counters 可测口) T4 注入换代:hero_override=树淬炼flat+FORGE_ROUTE_BONUS预留0(命中不注入=契约6);Attunement.slots 判定源切 树点亮数(谓词表退役);tree_buy(ks_refresh) 同账路由 buy_attune_refresh(V15 双入口) T5 旧档废弃与删除:save_manager tree_struct=2 结构版本标记+_normalize 白名单重建+树域废弃重置(禁迁移折算); migrate_tree/buy_forge/forge_flat/forge_level/forge_price/meta_forge_row/forge_products 行族/ attunement_growth 表+访问器/生涯页三投影/career forge_total 全删;treasure_item_table UNLOCK_COUNTER_KEYS 内联; verifier_batch 花费策略换代树节点;MC slots 画像常量3 T6 营地统一:四柜台并排横滚(树淬炼主界面/锻造工坊钥匙门+零交互占位面板/秘宝匣钥匙门+内容迁入/图鉴常开); 暂停层锻造段连带删;camp data forge 投影删 存量红修复:_flush_pending_reveals 三处空 source_class 补 fx_reveal+类级豁免(a57489e9 落地件,冒烟51行) 测试随批:smoke 树段全重写(V1/V2/V3/V8/V9/V16)+_smoke_tree_rules V10 行为段(窗/限次/守侧五场景编排rig/聚散 同种子对拍)+单元 tree_rules_v11_test(六键合成行闭集校验)+run_all 22 套;ui_probe/ui_click_probe 换代树点击路径 轻闸全绿:冒烟886/0+run_all 22套+probe --click 全绿
115 lines
7.6 KiB
GDScript
115 lines
7.6 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# Validation Question: 玩家能否在营地用钥石换永久强化、且强化在下一局开局生效——存档变厚成为玩家可感的现实
|
||
#(元进度 GDD 验收标准,本批 = 其实现批)
|
||
# Date: 2026-09-21(元进度实现批——设计权威 design/gdd/systems/冒险循环/元进度.md 定稿 v1.2)
|
||
# 数据配置 MetaTable schema(元进度行族)。
|
||
# 行族四轴(数据契约对齐 ConfigDB.meta_* 访问器——消费面一律走访问器,禁直读行集):
|
||
# boss_keystone = Boss 钥石两档行(level_index 连续从 1;first_kill ≥ repeat_kill——首杀全量、
|
||
# 重复杀固定小额恒定不递减,两档数值归元进度设计批配读数)。
|
||
# workshop_items = 道具工坊行(item_id = 秘宝六件词表封闭恰全;首版解锁渠道 = 总通关次数
|
||
# 达标 clear_threshold——2026-09-20 排期裁决;局内效果整体归 Alpha 秘宝重设计批,本表只有解锁状态面)。
|
||
# 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 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"] # 三属性轴词表(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
|
||
# 增一分支,阈值比较与解锁写档零改动。缺省(行无 channel 字段)= total_clears)
|
||
const WORKSHOP_CHANNELS := ["total_clears"]
|
||
|
||
|
||
func validate() -> Array:
|
||
var errs: Array[Dictionary] = []
|
||
# 行族空护栏(四族皆为 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})
|
||
# boss_keystone:level_index 唯一 + 连续从 1 + 两档域 + first ≥ repeat(首杀全量、重复小额恒定)
|
||
var seen_li := {}
|
||
for row in boss_keystone:
|
||
var li: int = int(row.get("level_index", 0))
|
||
if seen_li.has(li):
|
||
errs.append({"category": "dup_key", "message": "boss_keystone level_index 重复: %d" % li})
|
||
seen_li[li] = true
|
||
var first: int = int(row.get("first_kill", -1))
|
||
var rep: int = int(row.get("repeat_kill", -1))
|
||
if first < 1 or first > 999 or rep < 0 or rep > 999:
|
||
errs.append({"category": "domain", "message": "boss_keystone L%d 两档金额越域 first [1,999] / repeat [0,999]: %d/%d" % [li, first, rep]})
|
||
if first < rep:
|
||
errs.append({"category": "cross_field", "message": "boss_keystone L%d 违反 first_kill(%d) ≥ repeat_kill(%d)——首杀全量、重复杀小额" % [li, first, rep]})
|
||
for i in boss_keystone.size():
|
||
var li_at: int = int(boss_keystone[i].get("level_index", 0))
|
||
if li_at != i + 1:
|
||
errs.append({"category": "structural", "message": "boss_keystone level_index 不连续(须从 1 连续递增): 位 %d = %d" % [i + 1, li_at]})
|
||
break
|
||
# workshop_items:item_id 词表封闭 + 唯一 + 覆盖恰六(秘宝词表)+ 阈值域 [1,99]
|
||
var seen_item := {}
|
||
for row in workshop_items:
|
||
var iid: String = row.get("item_id", "")
|
||
if not WORKSHOP_ITEM_IDS.has(iid):
|
||
errs.append({"category": "enum", "message": "workshop_items item_id 出域秘宝六件词表: %s" % iid})
|
||
if seen_item.has(iid):
|
||
errs.append({"category": "dup_key", "message": "workshop_items item_id 重复: %s" % iid})
|
||
seen_item[iid] = true
|
||
if String(row.get("display_name", "")) == "":
|
||
errs.append({"category": "domain", "message": "workshop_items %s display_name 空" % iid})
|
||
var th: int = int(row.get("clear_threshold", -1))
|
||
if th < 1 or th > 99:
|
||
errs.append({"category": "domain", "message": "workshop_items %s clear_threshold 越域 [1,99]: %d" % [iid, th]})
|
||
# channel:可替换解锁渠道(缺省 total_clears——词表封闭,扩渠道 = 词表加值 + 判定量分支)
|
||
var channel: String = row.get("channel", "total_clears")
|
||
if not WORKSHOP_CHANNELS.has(channel):
|
||
errs.append({"category": "enum", "message": "workshop_items %s channel 出域 %s: %s" % [iid, str(WORKSHOP_CHANNELS), channel]})
|
||
if not workshop_items.is_empty():
|
||
for iid_key in WORKSHOP_ITEM_IDS:
|
||
if not seen_item.has(iid_key):
|
||
errs.append({"category": "coverage", "message": "workshop_items 缺道具键: %s(六件词表恰全)" % iid_key})
|
||
# conversion:attr 词表与 forge 一致 + 唯一 + 覆盖恰三 + 平移量域 [0,100]
|
||
var seen_conv := {}
|
||
for row in conversion:
|
||
var cattr: String = row.get("attr", "")
|
||
if not FORGE_ATTRS.has(cattr):
|
||
errs.append({"category": "enum", "message": "conversion attr 出域 {max_hp, atk, armor_class}: %s" % cattr})
|
||
if seen_conv.has(cattr):
|
||
errs.append({"category": "dup_key", "message": "conversion attr 重复: %s" % cattr})
|
||
seen_conv[cattr] = true
|
||
var shift: int = int(row.get("per_level_shift_pct", -1))
|
||
if shift < 0 or shift > 100:
|
||
errs.append({"category": "domain", "message": "conversion %s per_level_shift_pct 越域 [0,100]: %d" % [cattr, shift]})
|
||
if not conversion.is_empty():
|
||
for conv_key in FORGE_ATTRS:
|
||
if not seen_conv.has(conv_key):
|
||
errs.append({"category": "coverage", "message": "conversion 缺属性行: %s(三行与 forge 词表一致)" % conv_key})
|
||
# attune_refresh_upgrade:level 唯一 + 连续从 1 + charges/cost 域(迭代 11 第①批 T4)
|
||
var seen_al := {}
|
||
for row in attune_refresh_upgrade:
|
||
var al: int = int(row.get("level", -1))
|
||
if seen_al.has(al):
|
||
errs.append({"category": "dup_key", "message": "attune_refresh_upgrade level 重复: %d" % al})
|
||
seen_al[al] = true
|
||
var ach: int = int(row.get("refresh_charges", -1))
|
||
if ach < 1 or ach > 9:
|
||
errs.append({"category": "domain", "message": "attune_refresh_upgrade L%d refresh_charges 越域 [1,9]: %d" % [al, ach]})
|
||
var acost: int = int(row.get("keystone_cost", -1))
|
||
if acost < 1 or acost > 999:
|
||
errs.append({"category": "domain", "message": "attune_refresh_upgrade L%d keystone_cost 越域 [1,999]: %d" % [al, acost]})
|
||
for i in attune_refresh_upgrade.size():
|
||
var al_at: int = int(attune_refresh_upgrade[i].get("level", 0))
|
||
if al_at != i + 1:
|
||
errs.append({"category": "structural", "message": "attune_refresh_upgrade level 不连续(须从 1 连续递增): 位 %d = %d" % [i + 1, al_at]})
|
||
break
|
||
return errs
|