Files
gplan-2/game/data/schemas/meta_table.gd
T

140 lines
9.1 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: 玩家能否在营地用钥石换永久强化、且强化在下一局开局生效——存档变厚成为玩家可感的现实
#(元进度 GDD 验收标准,本批 = 其实现批)
# Date: 2026-09-21(元进度实现批——设计权威 design/gdd/systems/冒险循环/元进度.md 定稿 v1.2)
# 数据配置 MetaTable schema(元进度/永久强化 行族四轴)。
# 行族四轴(数据契约对齐 ConfigDB.meta_* 访问器——消费面一律走访问器,禁直读行集):
# forge_products = 属性锻造商品行(键 attr:{max_hp, atk, armor_class} 恰三唯一——注入契约三字段必齐;
# 投值语言锁定 flat 整数;几何定价 tier 级价格 = 迭代 p = floor(p × price_ratio_pct/100),tier 0 = base_price)。
# boss_keystone = Boss 钥石两档行(level_index 连续从 1;first_kill ≥ repeat_kill——首杀全量、
# 重复杀固定小额恒定不递减,两档数值归元进度设计批配读数)。
# workshop_items = 道具工坊行(item_id = 秘宝六件词表封闭恰全;首版解锁渠道 = 总通关次数
# 达标 clear_threshold——2026-09-20 排期裁决;局内效果整体归 Alpha 秘宝重设计批,本表只有解锁状态面)。
# conversion = 区间平移换算率行(attr 与 forge 词表一致恰三;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)。
# 行值全部 provisional 归 元进度设计批配读数(用户拍板骨架先行,先例 = GP+商店房批)。
class_name MetaTable
extends Resource
@export var forge_products: Array[Dictionary] = [] # Array[Dictionary] 键 attr
@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"]
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 ["forge_products", "boss_keystone", "workshop_items", "conversion", "attune_refresh_upgrade"]:
if get(family).is_empty():
errs.append({"category": "empty_table", "message": "MetaTable 行族为空: %s" % family})
# forge_products:attr 词表封闭 + 唯一 + 覆盖恰三(注入契约三字段必齐)+ flat/价格域 + 公比域 [100,400]
var seen_attr := {}
for row in forge_products:
var attr: String = row.get("attr", "")
if not FORGE_ATTRS.has(attr):
errs.append({"category": "enum", "message": "forge_products attr 出域 {max_hp, atk, armor_class}: %s" % attr})
if seen_attr.has(attr):
errs.append({"category": "dup_key", "message": "forge_products attr 重复: %s" % attr})
seen_attr[attr] = true
if String(row.get("display_name", "")) == "":
errs.append({"category": "domain", "message": "forge_products %s display_name 空" % attr})
var flat: int = int(row.get("flat_value", 0))
if flat < 1 or flat > 999:
errs.append({"category": "domain", "message": "forge_products %s flat_value 越域 [1,999]: %d" % [attr, flat]})
var base: int = int(row.get("base_price", 0))
if base < 1 or base > 999:
errs.append({"category": "domain", "message": "forge_products %s base_price 越域 [1,999]: %d" % [attr, base]})
var ratio: int = int(row.get("price_ratio_pct", -1))
if ratio < 100 or ratio > 400:
errs.append({"category": "domain", "message": "forge_products %s price_ratio_pct 越域 [100,400]: %d" % [attr, ratio]})
if not forge_products.is_empty():
for attr_key in FORGE_ATTRS:
if not seen_attr.has(attr_key):
errs.append({"category": "coverage", "message": "forge_products 缺属性键: %s(注入契约三字段必齐——恰三)" % attr_key})
# 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