- schema两件: CampSceneLayerSpec(层键+贴图矩形+命中矩形+命中键)+CampSceneLayoutTable(canvas/hud_band_h/gate_gap/wall_band/layers+validate()按面拆四查:身份贴图/命中边界/命中互斥/围墙带包络) - 数据表camp_scene_layout.tres: 10层坐标唯一事实源(布局草案值+§5已裁:布告板(845,268,130,140)/门扇(520,250,240,220)门洞对齐/帐篷300x170;贴图矩形=contain终稿实测尺寸锚定:底中心/顶锚/底锚按物件语义,campfire命中(560,640,160,80)只取下半) - ConfigDB接线: 表注册+表现域容器camp_scene_layout字段+装载期validate()逐问题_fatal(fail-fast红线:命中区越画面/相交/侵顶栏=数据致命) - CampSceneView: setup_layers/bind_hotspots/get_hotspot_rect/set_feedback_state/set_hud_band五公开口+object_pressed信号; Control根+TextureRect层子节点序=z序(禁CanvasLayer/z_index/Sprite2D); 根单点_gui_input命中判定(层全IGNORE); 四态反馈:悬停=Candle轮廓+名牌(FS_HINT/INK_ON_DEEP/BG_ROOT板,画面内钳制)+Tween逐级整像素浮动/按下=位移1px/禁用=ShaderMaterial去饱和+降明度(零新素材,modulate只有明度轴); 素材缺位程序化占位冒烟安全; 命中键=七物件绑定(gate/forge/dummy/chest/notebook/board/campfire) - V14单测camp_scene_layout_test.gd(11断言): 校验函数通过/七键集/两两不相交/不越画面不侵顶栏/篝火只下半/门扇对齐缺口/布告板挂墙带/10层序/ConfigDB装载链——全表关系谓词零坐标字面量; run_all manifest登记expect=0 - 验证: --editor --quit刷类缓存后V14孤跑绿(11项)+视图孤检绿(9项:子节点序/合成点击信号/悬停名牌/拖出取消/禁用不发/HUD槽——临时件不入库,T5探针承载正式验收); S9/S13归基线(gdstyle新违规4条已按职责轴拆函数清偿:validate按面四拆/_gui_input三拆/setup_layers构建段三拆/循环内分配提_build_layer_node)
188 lines
8.8 KiB
GDScript
188 lines
8.8 KiB
GDScript
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||
## 数值表单点访问入口:全部 .tres 表的加载、校验诊断与门面(垂直切片验证:改表即改玩法,数值零硬编码)。
|
||
## 玩法代码禁止绕过本单点自建 load();行查询归各表类(表管自己的行),
|
||
## 表持有按域收进四容器(combat 战斗编排域/run 冒险 runner 域/crossrun 跨局元进度域/
|
||
## presentation 表现层布局域),消费面经 db.<容器>.<表> 直连。
|
||
## 「经 ConfigDB 单点读取」=经其持有的容器与表对象(架构 :88)。
|
||
## 冒烟脚本可显式实例化本类(headless --script 亦会加载 autoload——共享 .tres 上的 resolver 归最后装载的实例);
|
||
## 加载失败 is_ready=false 加 fatal 封印,调用方须查。
|
||
class_name ConfigDb
|
||
extends Node
|
||
|
||
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——两根类型轴默认值+六路线行族;攻防所有权切分唯一数据面
|
||
"battle_layout": "battle_layout.tres", # BattleLayoutTable——战斗屏分区几何与明细带高度单行
|
||
"camp_scene_layout": "camp_scene_layout.tres", # CampSceneLayoutTable——营地场景 10 层贴图/命中矩形与门洞缺口单行
|
||
}
|
||
|
||
var combat: ConfigDomainCombat # 战斗编排域七表(board_gen/variation/status_effects/enemy/skill/hero/settlement)
|
||
var run: ConfigDomainRun # 冒险 runner 域七表(level/difficulty/level_gen_params/reward/adventure/action_economy/shop)
|
||
var crossrun: ConfigDomainCrossrun # 跨局元进度域五表(meta/meta_tree/treasure_items/feats/forge)
|
||
var presentation: ConfigDomainPresentation # 表现层布局域两表(battle_layout/camp_scene_layout)
|
||
|
||
var is_ready := false
|
||
var _diagnostics: Array[Dictionary] = [] # {severity, category, message}
|
||
var _resolver: ConfigDbResolver # R1 难度行解析协作类(门面公有签名不变;load_tables 装配段注入三表)
|
||
var _validator := ConfigDbCrossValidator.new() # 跨表校验协作类(无状态——声明期即建)
|
||
var _reload_gate := false # 数据配置 EC8——战斗结算 唯一调用方
|
||
|
||
|
||
func _init() -> void:
|
||
combat = ConfigDomainCombat.new()
|
||
run = ConfigDomainRun.new()
|
||
crossrun = ConfigDomainCrossrun.new()
|
||
presentation = ConfigDomainPresentation.new()
|
||
_resolver = ConfigDbResolver.new(push_diag,
|
||
func() -> Variant: return combat.board_gen.rows, func() -> Variant: return combat.enemy.rows, func() -> Variant: return run.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
|
||
# 容器装配(表行按域收进四容器+resolver 注入)归私有协函数——load_tables 只留加载/校验/致命门面
|
||
_assign_loaded(loaded)
|
||
is_ready = not has_errors
|
||
return not has_errors
|
||
|
||
|
||
# 容器装配协函数:20 表成员按域收进四容器(2026-10-27 全清设计定稿,2026-10-31 增表现层布局域)
|
||
func _assign_loaded(loaded: Dictionary) -> void:
|
||
combat.board_gen = loaded.get("board_gen")
|
||
combat.variation = loaded.get("variation")
|
||
combat.status_effects = loaded.get("status_effects")
|
||
combat.enemy = loaded.get("enemy")
|
||
combat.skill = loaded.get("skill")
|
||
combat.hero = loaded.get("hero")
|
||
combat.settlement = loaded.get("settlement")
|
||
run.level = loaded.get("level")
|
||
run.difficulty = loaded.get("difficulty")
|
||
run.level_gen_params = loaded.get("level_gen_params")
|
||
run.reward = loaded.get("reward")
|
||
run.adventure = loaded.get("adventure")
|
||
run.action_economy = loaded.get("action_economy")
|
||
run.shop = loaded.get("shop")
|
||
crossrun.meta = loaded.get("meta")
|
||
crossrun.meta_tree = loaded.get("meta_tree")
|
||
crossrun.treasure_items = loaded.get("treasure_items")
|
||
crossrun.feats = loaded.get("feats")
|
||
crossrun.forge = loaded.get("forge")
|
||
presentation.battle_layout = loaded.get("battle_layout")
|
||
presentation.camp_scene_layout = loaded.get("camp_scene_layout")
|
||
for problem: String in presentation.camp_scene_layout.validate():
|
||
_fatal("camp_scene_layout", problem) # 布局几何红线(命中区越画面/相交/侵顶栏)=数据致命
|
||
# resolver 协作件注入三表(resolve 委托承载——resolver 文件与键零搬移)
|
||
combat.board_gen._resolver = _resolver
|
||
combat.enemy._resolver = _resolver
|
||
run.difficulty._resolver = _resolver
|
||
|
||
|
||
# 跨表校验(悬空引用 / 池护栏——全部加载期致命)本体:
|
||
# ConfigDbCrossValidator(core/config_db_cross_validator.gd)承载,门面公有签名不变。
|
||
func _cross_validate(loaded: Dictionary) -> void:
|
||
_validator.cross_validate(loaded, push_diag)
|
||
|
||
|
||
# 池模板 × 技能行跨表校验(冒烟直验独立入口——签名与消费面零变化,实现归协作类;门面私有——
|
||
# 消费面经门面者改 _ 前缀直调,cross_validator 直验入口公开面不变)
|
||
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)
|
||
|
||
|
||
# ---- 数据配置 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
|