Files
gplan-2/game/core/config_db_resolver.gd

198 lines
8.8 KiB
GDScript
Raw Permalink 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
## R1 难度行解析协作类:进度坐标 ceil 取行、越域 Fatal、告警去重,由 ConfigDb 门面持有(垂直切片验证:数值缺行不静默)。
## 非直调面——全部消费方经 ConfigDb 同名方法取数;数据面与诊断通道均经构造注入 Callable(类型环断根)。
class_name ConfigDbResolver
extends RefCounted
var _diag: Callable # 诊断/致命通道(门面类型反持解耦,validator 先例:签名 (level, category, message))
var _board_gen_rows_get: Callable # () -> Array——board_gen 行集活读(数据面经 Callable 注入,ConfigDb 类型环断根)
var _enemy_rows_get: Callable # () -> Array——enemy 行集活读
var _difficulty_get: Callable # () -> DifficultyTable(可空——表缺省合法态)
var _warn_dedup: Dictionary[String, bool] = {} # R1 四元组去重注册表
func _init(diag_cb: Callable, board_gen_rows_cb: Callable,
enemy_rows_cb: Callable, difficulty_cb: Callable) -> void:
_diag = diag_cb
_board_gen_rows_get = board_gen_rows_cb
_enemy_rows_get = enemy_rows_cb
_difficulty_get = difficulty_cb
# 去重注册表清空(门面 load_tables / reset_diagnostics 调用)
func reset_warn_dedup() -> void:
_warn_dedup.clear()
# ---- R1 难度行解析(ceil 向上取整保证偏难;域越界与内容缺口 Fatal 退出)----
func resolve_board_gen(L: int, D: int) -> Dictionary:
return _resolve_progress(_board_gen_rows_get.call(), L, D, "board_gen")
func resolve_enemy_rowset(L: int, D: int, row_kind: String) -> Array[Dictionary]:
# 带寻址:L 维沿进度行解析现状(ceil 向上取整保证偏难、越最大行 clamp),
# D 维经 EnemyTable.band_of 落带——命中该 (L, row_kind) 的某一个 depth_band,
# 返回该带全部行、按表文件行序(行序即契约——同带键多行文件内相邻由 EnemyTable.validate 兜底)。
# 空集表达 = 空数组(band_keys 空时可达,交消费侧既有空行集 fatal 路径——validate 已拦无 Boss 行集)。
if L < 1 or D < 0:
_diag.call("fatal", "request_oob", "enemy 请求越域 (%d,%d)——进度坐标低于合法域(关卡序号≥1、节点深度≥0)" % [L, D])
return []
if not EnemyTable.ROW_KINDS.has(row_kind):
_diag.call("fatal", "request_oob", "enemy 请求行类非法 (%d,%d,\"%s\")——row_kind 域 {battle, boss}" % [L, D, row_kind])
return []
var rows: Array[Dictionary] = []
rows.assign(_enemy_rows_get.call())
var level_keys: Array[int] = _level_keys_of(rows)
if level_keys.is_empty():
_diag.call("fatal", "structural", "enemy 行集为空——锚行校验应已拦截")
return []
level_keys.sort()
var l_star := _ceil_key(level_keys, L)
if l_star < 0:
l_star = int(level_keys[-1]) # 超出最大行 → 取最大行封顶
_warn_resolve("enemy", "resolve_level_clamp", "enemy 请求 (%d,%d) clamp 解析(超出最大关卡取最大值)→ 关卡 %d" % [L, D, l_star], l_star, D)
elif l_star != L:
_warn_resolve("enemy", "resolve_level_ceil", "enemy 请求 (%d,%d) ceil 解析(向上取整保证偏难)→ 关卡 %d" % [L, D, l_star], l_star, D)
var band_keys: Array[int] = _band_keys_of(rows, l_star, row_kind)
if band_keys.is_empty():
return []
band_keys.sort()
var d_star := EnemyTable.band_of(band_keys, D)
if d_star < 0:
d_star = int(band_keys[-1]) # 越顶 clamp 顶带(调参事故面,跨表校验另报)
_warn_resolve("enemy", "resolve_depth_clamp", "enemy 请求 (%d,%d) clamp 解析(超出顶带取顶带)→ (%d,band %d)" % [L, D, l_star, d_star], l_star, d_star)
elif d_star != D:
_warn_resolve("enemy", "resolve_depth_ceil", "enemy 请求 (%d,%d) ceil 解析(向上取整保证偏难)→ (%d,band %d)" % [L, D, l_star, d_star], l_star, d_star)
return _band_rows(rows, l_star, row_kind, d_star)
## 行集关卡键收集(纯行扫描——告警发射序不变)
func _level_keys_of(rows: Array) -> Array[int]:
var level_keys: Array[int] = []
for row in rows:
var lv := int(row.get("level_index", 0))
if not level_keys.has(lv):
level_keys.append(lv)
return level_keys
## 带键收集(纯行扫描——l_star × row_kind 的 depth_band 去重集)
func _band_keys_of(rows: Array, l_star: int, row_kind: String) -> Array[int]:
var band_keys: Array[int] = []
for row in rows:
if int(row.get("level_index", 0)) == l_star and String(row.get("row_kind", "")) == row_kind:
var bv := int(row.get("depth_band", -1))
if not band_keys.has(bv):
band_keys.append(bv)
return band_keys
## 带行收集(纯行扫描——命中带全部行、按表文件行序)
func _band_rows(rows: Array, l_star: int, row_kind: String, d_star: int) -> Array[Dictionary]:
var all_out: Array[Dictionary] = []
for row in rows:
if int(row.get("level_index", 0)) == l_star and String(row.get("row_kind", "")) == row_kind \
and int(row.get("depth_band", -1)) == d_star:
all_out.append(row)
return all_out
# 解析告警单点(类别 + 命中键四元组去重——R1 既有去重注册表)
func _warn_resolve(table_id: String, category: String, message: String, l_key: int, d_key: int) -> void:
var dedup_key := "%s|%s|%d|%d" % [table_id, category, l_key, d_key]
if _warn_dedup.has(dedup_key):
return
_warn_dedup[dedup_key] = true
_diag.call("warning", category, message)
# 难度曲线与Boss DifficultyTable 消费面(敌人系统.spawn 调用):(L,D) 精确匹配修饰乘数,越域值 clamp(GDD Edge Case——组合事故非配置事故)
func resolve_difficulty_modifiers(L: int, D: int) -> Dictionary:
var dt = _difficulty_get.call()
if dt == null:
return {}
for row in dt.rows:
if int(row.get("level_index", 0)) == L and int(row.get("node_depth", -1)) == D:
return {
"hp_mult": clampf(float(row.get("hp_mult", 1.0)), 0.1, 10.0),
"atk_mult": clampf(float(row.get("atk_mult", 1.0)), 0.1, 10.0),
}
return {}
func _resolve_progress(rows: Array, L: int, D: int, table_id: String) -> Variant:
if L < 1 or D < 0:
_diag.call("fatal", "request_oob", "%s 请求越域 (%d,%d)——进度坐标低于合法域(关卡序号≥1、节点深度≥0)" % [table_id, L, D])
return {}
var level_keys: Array[int] = _level_keys_of(rows)
if level_keys.is_empty():
_diag.call("fatal", "structural", "%s 行集为空——锚行校验应已拦截" % table_id)
return {}
level_keys.sort()
var l_star := _ceil_key(level_keys, L)
var l_clamped := l_star < 0
if l_clamped:
l_star = int(level_keys[-1]) # 超出最大行 → 取最大行封顶(可取范围内向上取整,超出用最大值)
var depth_keys: Array[int] = _depth_keys_of(rows, l_star)
depth_keys.sort()
var d_star := _ceil_key(depth_keys, D)
var d_clamped := d_star < 0
if d_clamped:
d_star = int(depth_keys[-1])
var missing := _missing_label(l_clamped, d_clamped, l_star != L, d_star != D)
if missing != "":
_emit_missing_warning(table_id, missing, [l_star, d_star], [L, D])
for row in rows:
if int(row.get("level_index", 0)) == l_star and int(row.get("node_depth", 0)) == d_star:
return row
return {}
## 层键收集(纯行扫描——l_star 行的 node_depth 去重集)
func _depth_keys_of(rows: Array, l_star: int) -> Array[int]:
var depth_keys: Array[int] = []
for row in rows:
if int(row.get("level_index", 0)) == l_star:
var dv := int(row.get("node_depth", 0))
if not depth_keys.has(dv):
depth_keys.append(dv)
return depth_keys
## 解析偏移形态标签(clamp/ceil × level/depth/both 六形态——空串 = 精确命中;
## l_ceil/d_ceil = 解析值 ≠ 请求值)
func _missing_label(l_clamped: bool, d_clamped: bool, l_ceil: bool, d_ceil: bool) -> String:
var label := ""
if l_clamped and d_clamped:
label = "both_clamp"
elif l_clamped:
label = "level_clamp"
elif d_clamped:
label = "depth_clamp"
elif l_ceil and d_ceil:
label = "both_ceil"
elif l_ceil:
label = "level_ceil"
elif d_ceil:
label = "depth_ceil"
return label
## 偏移告警发射(四元组去重注册表语义逐字——R1 既有去重;
## req/resolved = [L, D]/[l_star, d_star] 进度坐标对)
func _emit_missing_warning(table_id: String, missing: String, resolved: Array, req: Array) -> void:
var dedup_key := "%s|%s|%d|%d" % [table_id, missing, resolved[0], resolved[1]]
if not _warn_dedup.has(dedup_key):
_warn_dedup[dedup_key] = true
var verb := "clamp 解析(超出最大行取最大值)" if missing.contains("clamp") else "ceil 解析(向上取整保证偏难)"
_diag.call("warning", "resolve_" + missing, "%s 请求 (%d,%d) %s → (%d,%d) [%s]——2026-09-18 拍板" % [table_id, req[0], req[1], verb, resolved[0], resolved[1], missing])
# 取 sorted_keys 中第一个 >= request 的键;无(请求高于最大行)返回 -1 → 调用方 Fatal
func _ceil_key(sorted_keys: Array, request: int) -> int:
for k in sorted_keys:
if int(k) >= request:
return int(k)
return -1