Files
gplan-2/game/core/config_db.gd
T
DKChan 4b5e3f27e8 卡④T2(迭代47·营地场景图批): CampSceneView场景视图+camp_scene_layout.tres布局表+V14单测——场景视图/布局坐标/V14三面落地
- 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)
2026-11-01 17:27:42 +08:00

188 lines
8.8 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
## 数值表单点访问入口:全部 .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