## VERTICAL SLICE - NOT FOR PRODUCTION ## 数值表单点访问入口:全部 .tres 表的加载、校验诊断与类型化访问器(垂直切片验证:改表即改玩法,数值零硬编码)。 ## 玩法代码禁止绕过本单点自建 load();消费面一律走访问器,禁直读行集。 ## headless --script 模式无 autoload——冒烟脚本显式实例化本类;加载失败 is_ready=false 加 fatal 封印,调用方须查。 class_name ConfigDb extends Node const SCHEMA_VERSION := 1 const TABLE_MANIFEST := { "action_economy": "action_economy.tres", "variation": "variation.tres", "status_effects": "status_effects.tres", "board_gen": "board_gen.tres", "enemy": "enemy.tres", "skill": "skills.tres", "hero": "hero.tres", "reward": "reward.tres", "settlement": "settlement.tres", "level": "level.tres", "difficulty": "difficulty.tres", "level_gen_params": "level_gen_params.tres", # LevelGenParams 参数档三行(节点图生成器核心批 卡①——五旋钮 provisional) "shop": "shop.tres", # ShopTable(表与 schema 已入库归数据面——本处只接线加载与消费) "meta": "meta.tres", # MetaTable 四行族(schema 与表同源已入库) "meta_tree": "meta_tree.tres", # MetaTreeTable 12 节点(定义表 Rule 4 豁免——天赋树.md v0.8 结构事实源) "treasure_items": "treasure_items.tres", # TreasureItemTable 六行(定义表 Rule 4 豁免;罗盘 retired/失义件 deferred 标记见 schema 头注) "adventure": "adventure.tres", # AdventureTable(大奖励与钥石行值单点) "feats": "feats.tres", # FeatTable 规则改写型特性(定义表 Rule 4 豁免——TreasureItemTable 先例;空表合法态=主时间线零扰动) "forge": "forge.tres", # ForgeTable——两根类型轴默认值+六路线行族;攻防所有权切分唯一数据面 } var action_economy: ActionEconomyTable var variation: VariationTable var status_effects: StatusEffectTable var board_gen: BoardGenTable var enemy: EnemyTable var skill: SkillTable var hero: HeroTable var reward: RewardTable var settlement: SettlementTable var level: LevelTable var difficulty: DifficultyTable var level_gen_params: LevelGenParams # 参数档类型化持用(生成器只吃参数档、不认关卡号——卡① 契约 6) # ShopTable 类型化持用(schema class_name 已已入库; # 消费面一律走下方 shop_* 访问器,禁直读行集) var shop: ShopTable = null # MetaTable 类型化持用(schema 与表同源已入库) var meta: MetaTable var meta_tree: MetaTreeTable # 类型化持用(消费面走 tree_node 访问器) var treasure_items: TreasureItemTable # 类型化持用(消费面走 treasure_* 访问器) var adventure: AdventureTable var feats: FeatTable # 类型化持用(消费面走 feat_* 访问器) var forge: ForgeTable # 锻造工坊配置(默认两轴+六路线行族——消费面走 forge_* 访问器) var is_ready := false var _diagnostics: Array[Dictionary] = [] # {severity, category, message} var _resolver: ConfigDbResolver # R1 难度行解析协作类(门面公有签名不变) var _validator := ConfigDbCrossValidator.new() # 跨表校验协作类(无状态——声明期即建) var _reload_gate := false # 数据配置 EC8——战斗结算 唯一调用方 func _init() -> void: _resolver = ConfigDbResolver.new(push_diag, func() -> Variant: return board_gen.rows, func() -> Variant: return enemy.rows, func() -> Variant: return difficulty) # 协作类持门面引用(诊断/致命通道回呼) func push_diag(severity: String, category: String, message: String) -> void: _diagnostics.append({"severity": severity, "category": category, "message": message}) if severity == "warning": push_warning("[ConfigDB/%s] %s" % [category, message]) else: push_error("[ConfigDB/%s] %s" % [category, message]) # 运行期数据致命(fail-fast;退出执行归根:核心层不自携 # quit/crash,越域记 Fatal 级日志后置 _fatal_flag,进程退出决策归组合根(main._ready 表加载 # 失败已有同通道 fatal 屏+fail-fast 边界)——查询期消费方按返回值安全面走既有默认值回退) func _fatal(category: String, message: String) -> void: push_diag("fatal", category, message) push_error("[ConfigDB/%s] FATAL: %s —— 数据配置致命(退出决策归组合根)" % [category, message]) _fatal_flag = true var _fatal_flag := false # 运行期致命封印(退出归根承载;组合根/闸读面) func is_fatal() -> bool: return _fatal_flag func reset_diagnostics() -> void: _diagnostics.clear() _resolver.reset_warn_dedup() # R1 告警去重注册表(协作类持钥——清空时点与拆分前一致) func diagnostics() -> Array: return _diagnostics.duplicate() func diagnostics_count(severity: String) -> int: var n := 0 for d in _diagnostics: if d.severity == severity: n += 1 return n func _ready() -> void: load_tables("res://data/tables", false) func load_tables(root: String, collect_only: bool) -> bool: is_ready = false _diagnostics.clear() _resolver.reset_warn_dedup() var loaded := {} for key in TABLE_MANIFEST.keys(): var path := root.path_join(TABLE_MANIFEST[key]) if not ResourceLoader.exists(path): push_diag("error", "missing_file", "表文件缺失: %s" % path) continue var res: Resource = load(path) if res == null: push_diag("error", "load_failed", "表加载失败: %s" % path) continue loaded[key] = res for e in res.validate(): push_diag("error", e.category, "%s → %s" % [key, e.message]) # 跨表校验(悬空引用 / 池护栏——全部加载期致命) _cross_validate(loaded) var has_errors := diagnostics_count("error") > 0 if has_errors and not collect_only: # 游戏通道:致命(Rule 6)——is_ready 恒 false,调用方(主场景)中止构建 return false action_economy = loaded.get("action_economy") variation = loaded.get("variation") status_effects = loaded.get("status_effects") board_gen = loaded.get("board_gen") enemy = loaded.get("enemy") skill = loaded.get("skill") hero = loaded.get("hero") reward = loaded.get("reward") settlement = loaded.get("settlement") level = loaded.get("level") difficulty = loaded.get("difficulty") level_gen_params = loaded.get("level_gen_params") shop = loaded.get("shop") meta = loaded.get("meta") treasure_items = loaded.get("treasure_items") adventure = loaded.get("adventure") feats = loaded.get("feats") forge = loaded.get("forge") meta_tree = loaded.get("meta_tree") is_ready = not has_errors return not has_errors # ---- relic 访问器(消费面一律走本组,禁直读行集)---- func feat_row(feat_id: String) -> Dictionary: return feats.row_by_id(feat_id) if feats != null else {} # 规则改写型特性带令牌构建源(池接线——adventure_runner._build_curio_deck 消费;行 rarity→投放档映射) func feat_rows() -> Array: return feats.rows if feats != null else [] # 天赋树节点行(MetaProgression 树段/营地树 UI 取数口) func tree_node(node_id: String) -> Dictionary: return meta_tree.row_by_id(node_id) if meta_tree != null else {} func tree_rows() -> Array: return meta_tree.rows if meta_tree != null else [] # 跨表校验(悬空引用 / 池护栏——全部加载期致命)本体: # ConfigDbCrossValidator(core/config_db_cross_validator.gd)承载,门面公有签名不变。 func _cross_validate(loaded: Dictionary) -> void: _validator.cross_validate(loaded, push_diag) # 池模板 × 技能行跨表校验(冒烟直验独立入口——签名与消费面零变化,实现归协作类) func validate_skill_pool_links(skill_table: SkillTable, enemy_table: EnemyTable, status_table: StatusEffectTable = null) -> Array: return _validator.validate_skill_pool_links(skill_table, enemy_table, status_table) # ---- R1 难度行解析(ceil 向上取整保证偏难;域越界与内容缺口 Fatal 退出)---- # 解析本体:ConfigDbResolver(core/config_db_resolver.gd)承载—— # 含 _resolve_progress/_ceil_key 与 R1 告警去重注册表;门面同名方法委托,越域 Fatal 语义一致。 func resolve_board_gen(L: int, D: int) -> Dictionary: return _resolver.resolve_board_gen(L, D) func resolve_enemy_rowset(L: int, D: int, row_kind: String) -> Array[Dictionary]: return _resolver.resolve_enemy_rowset(L, D, row_kind) # 行集数组形态(非单行 Dictionary;D 维带寻址) # 难度曲线与Boss DifficultyTable 消费面(敌人系统.spawn 调用):(L,D) 精确匹配修饰乘数,越域值 clamp(GDD Edge Case——组合事故非配置事故) func resolve_difficulty_modifiers(L: int, D: int) -> Dictionary: return _resolver.resolve_difficulty_modifiers(L, D) # ---- 地形变异 variant_pool_gate:候选池门控(∅ board_types = 通配;字段名对齐 # board_type 词表,无 biome_tags)---- # 门控第三维 = 节点身份(全局作用域变异):scope=global 行仅 boss/elite 节点入池, # 其余节点(battle 等)静默跳过——门控兜底 P0 口径(零报错零警告);elite 系前向词位(首发零表行)。 # node_type 缺省 "" = 旧双参调用口径(global 行跳过)——既有测试/冒烟调用零扰动。 func variant_pool(level_index: int, board_type: String, node_type: String = "") -> Array: var pool: Array[Dictionary] = [] for row in variation.rows: if String(row.get("scope", "region")) == "global" \ and node_type != "boss" and node_type != "elite": continue if int(row.get("unlock_level", 999)) > level_index: continue var tags: Array[Variant] = row.get("board_types", []) if tags.is_empty(): pool.append(row) continue if tags.has(board_type): pool.append(row) return pool # 全局型单行决议(战斗编排装板期消费——访问器纪律:消费面禁直扫表): # boss/elite 节点取候选池内首个 scope=global 行(首发批全局池恒恰一行;多行为配置越界态, # 按表序取首行确定性兜底);空 = 无全局型(调用方回落 region 布置——兜底回落合法态) func global_variant_row(level_index: int, board_type: String, node_type: String) -> Dictionary: if node_type != "boss" and node_type != "elite": return {} for row in variant_pool(level_index, board_type, node_type): if String(row.get("scope", "region")) == "global": return row return {} func status_effect_row(effect_id: String) -> Dictionary: return status_effects.row_by_id(effect_id) # ---- 关卡行集访问器(表访问收口——level_runner/meta_progression/adventure_runner # 不直扫 db.level.rows;消费面一律走本访问器,行集解析单点归 LevelTable)---- # 关卡行单点读取(键 = level_index,缺行返回空字典——消费方按既有空行防御路径处理) func level_row(level_index: int) -> Dictionary: return level.row_by_level_index(level_index) if level != null else {} # 参数档单点读取(节点图生成器核心批 卡①——键 = level_index,缺行返回空字典;调用方据此产图,生成器本体不读关卡号) func level_gen_params_row(level_index: int) -> Dictionary: return level_gen_params.row_by_level_index(level_index) if level_gen_params != null else {} # 序列总关数(AdventureRunner levels_total / MetaProgression 通关累加域的取数口径) func level_count() -> int: return level.rows.size() if level != null else 0 # ---- ShopTable 消费面(行集解析单点——runner/战斗编排禁自扫表)---- # ── 秘宝访问器(消费面一律走访问器,禁直读行集)── # 活行集(非 retired 非 deferred)——投放校验/奖励节点池/呈现可用性的唯一取数口径 func treasure_item_row(item_id: String) -> Dictionary: return treasure_items.row_by_id(item_id) if treasure_items != null else {} # 过渡渠道活行(form 空 = 切片期过渡形态)——掉落投放/停点池/工坊解锁取数口径;同调行(form 非空) # 永不入过渡渠道 func treasure_transitional_rows() -> Array: return treasure_items.transitional_rows() if treasure_items != null else [] # 同调行集(form 非空——装配覆盖层/效果接线的取数口径) func treasure_attunement_rows() -> Array: return treasure_items.attunement_rows() if treasure_items != null else [] # 活行词表(掉落投放 effect_id 白名单——过渡渠道口径) # ── 同调槽位数=树同调位节点点亮数,判定源=Attunement.slots(无 attunement_growth 谓词表访问器); # boss_kill_count/clear_count 计数域保留——非槽位硬消费在册 ── # 行族四轴(数据契约,行集归 shop.tres):gp_drops {tier, mean, roll_min, roll_max} · # level_gp_bonus {level_index, bonus} · products {product_id, display_name, attr, flat_value, # base_price, price_increment} · price_level_bonus {level_index, bonus}。 # 行族经 ShopTable 同名数组属性读取(.get 容缺——表已入库前查询恒空,掉落按 0 兑付的兜底在调用方)。 func shop_gp_drop_row(tier: String) -> Dictionary: for row in _shop_rows("gp_drops"): if String(row.get("tier", "")) == tier: return row return {} func shop_level_gp_bonus(level_index: int) -> int: for row in _shop_rows("level_gp_bonus"): if int(row.get("level_index", -1)) == level_index: return int(row.get("bonus", 0)) return 0 func shop_price_level_bonus(level_index: int) -> int: for row in _shop_rows("price_level_bonus"): if int(row.get("level_index", -1)) == level_index: return int(row.get("bonus", 0)) return 0 func shop_products() -> Array: return _shop_rows("products") func shop_product_row(product_id: String) -> Dictionary: for row in _shop_rows("products"): if String(row.get("product_id", "")) == product_id: return row return {} # ── 共享池货架访问器(M-4——消费面一律走访问器,禁直读行集)── # 货架行族({kind_id, display_name, base_price}——行序即抽签/呈现稳定序) func shop_pool_rows() -> Array: return _shop_rows("pool_shelf") func shop_pool_row(kind_id: String) -> Dictionary: for row in _shop_rows("pool_shelf"): if String(row.get("kind_id", "")) == kind_id: return row return {} # 乘法价格因子(provisional 投值——0 = 表未投值,消费方按 fail-fast 域校验拦截) func shop_price_a() -> float: return float(shop.price_a) if shop != null else 0.0 func shop_price_b() -> float: return float(shop.price_b) if shop != null else 0.0 # 行族数组读取(ShopTable 属性名 = 行族名;表未已入库/属性缺失 = 空集非错误态) func _shop_rows(family: String) -> Array: if shop == null: return [] var arr = shop.get(family) return arr if arr is Array else [] # ---- MetaTable 消费面(行集解析单点——MetaProgression/呈现层禁自扫表)---- # 行族四轴(数据契约,行集归 meta.tres,数值全 provisional): # forge_products {attr, display_name, flat_value, base_price, price_ratio_pct} · # boss_keystone {level_index, first_kill, repeat_kill} · # workshop_items {item_id, display_name, clear_threshold} · # conversion {attr, per_level_shift_pct}。 func meta_boss_keystone_row(level_index: int) -> Dictionary: for row in _meta_rows("boss_keystone"): if int(row.get("level_index", -1)) == level_index: return row return {} # 秘宝解锁混合制挂 treasure_items 行内(无 meta_workshop_items 访问器); # 秘宝解锁投影面=MetaProgression.treasure_shop_status func meta_conversion_row(attr: String) -> Dictionary: for row in _meta_rows("conversion"): if String(row.get("attr", "")) == attr: return row return {} # Y 刷新升级行单点读取(键 = 等级,缺行返回空字典=未购/已满级合法态) func meta_attune_refresh_row(level: int) -> Dictionary: for row in _meta_rows("attune_refresh_upgrade"): if int(row.get("level", -1)) == level: return row return {} # 行族数组读取(MetaTable 属性名 = 行族名;表缺失/属性缺失 = 空集非错误态) func _meta_rows(family: String) -> Array: if meta == null: return [] var arr = meta.get(family) return arr if arr is Array else [] # ---- 锻造工坊(ForgeTable 消费面:两根类型轴默认值+六路线行族;禁自扫行集)---- # 设计权威 design/gdd/systems/跨局循环/锻造工坊.md v0.6:默认值归本表常驻持有(hero.tres 两轴已删—— # 契约 2);路线行族=攻防数值成长唯一数据面(契约 1)。数值全 provisional 归 M4。 # 两根类型轴默认值(表未已入库=裸体态兜底 normal/light——与 ForgeTable 缺省值同口径) func forge_default_attack_type() -> String: return String(forge.default_attack_type) if forge != null else "normal" func forge_default_armor_type() -> String: return String(forge.default_armor_type) if forge != null else "light" # 六路线行族(副本——调用方禁改) func forge_routes() -> Array: return forge.routes.duplicate() if forge != null else [] # 路线行单点(键 route_id;缺行=空字典) func forge_route(route_id: String) -> Dictionary: for row in forge_routes(): if String(row.get("route_id", "")) == route_id: return row return {} # 按类型轴反查武器路线(attack_type → 路线行;甲路线 id=甲型同值的显式化入口,避免消费侧依赖该巧合) func forge_weapon_route_by_type(attack_type: String) -> Dictionary: for row in forge_routes(): if String(row.get("kind", "")) == "weapon" and String(row.get("type_axis", "")) == attack_type: return row return {} # 按类型轴反查护甲路线(armor_type → 路线行——入战 AC 三层之「甲型基础 AC」取数口) func forge_armor_route_by_type(armor_type: String) -> Dictionary: for row in forge_routes(): if String(row.get("kind", "")) == "armor" and String(row.get("type_axis", "")) == armor_type: return row return {} # 技能行单点读取(敌人技能池批——单位容器执行上报 / 战斗结算兑现的消费面) func skill_row(skill_id: String) -> Dictionary: return skill.row_by_id(skill_id) # ---- 数据配置 EC8:debug 热重载门(战斗结算 唯一调用方;切片实现 = 门标志 + 拒绝语义)---- func set_reload_gate(gated: bool) -> void: _reload_gate = gated func reload() -> bool: if _reload_gate: push_diag("warning", "reload_gated", "战斗中热重载被拒绝(数据配置 EC8)") return false var ok := load_tables("res://data/tables", false) if ok: push_diag("debug", "config_reloaded", "表热重载完成") return ok