127 lines
6.6 KiB
GDScript
127 lines
6.6 KiB
GDScript
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||
## 棋盘生成表 schema:逐 (level_index, node_depth) 行生成参数的类型化读取(垂直切片验证:完整战斗逐行归因)。
|
||
## validate() 违例加载期致命(fail-fast);solver_rules 结构冻结 single_subset——可解性/成本护栏随行校验强制。
|
||
class_name BoardGenTable
|
||
extends Resource
|
||
|
||
@export var rows: Array[Dictionary] = [] # Array[Dictionary]
|
||
@export var spawn_edge: int = 0 # 出生区边长(奇数 [3,5]——零哨兵:表头缺字段=加载期域校验拒)
|
||
const SOLVER_RULES := ["single", "single_subset"]
|
||
const COST_BUDGET := 16384 # F2c provisional 量级护栏(禁墙钟逃生)
|
||
const TARGET_ABS_FLOOR := 0.85 # 战斗结算 修订——target 域下限(P1 绝对锚)
|
||
const BOARD_TYPE_VOCABULARY := ["dungeon", "underdark", "deep_underdark"] # 图类型词表 v1.1(含 deep_underdark 灰矮人矿道;玩家侧显示名「地图类型」)
|
||
|
||
|
||
func validate() -> Array:
|
||
var errs: Array[Dictionary] = []
|
||
if spawn_edge < 3 or spawn_edge > 5 or spawn_edge % 2 == 0:
|
||
errs.append({"category": "domain", "message": "spawn_edge 越域(奇数 [3,5]——零缺省): %d" % spawn_edge})
|
||
if rows.is_empty():
|
||
errs.append({"category": "empty_table", "message": "BoardGenTable 为空"})
|
||
return errs
|
||
var levels := {}
|
||
var anchor_ok := {}
|
||
for row in rows:
|
||
errs.append_array(_validate_row(row, levels, anchor_ok))
|
||
errs.append_array(_validate_row_constraints(row))
|
||
errs.append_array(_validate_shave_params(row))
|
||
for L in anchor_ok.keys():
|
||
if not anchor_ok[L]:
|
||
errs.append({"category": "anchor", "message": "level %d 缺 depth-0 锚行(EC15)" % L})
|
||
return errs
|
||
|
||
|
||
## 行域:键唯一与进度/尺寸/密度/推理目标/solver 规则冻结(levels/anchor_ok 引用累计)
|
||
func _validate_row(row: Dictionary, levels: Dictionary, anchor_ok: Dictionary) -> Array[Dictionary]:
|
||
var errs: Array[Dictionary] = []
|
||
var L: int = int(row.get("level_index", 0))
|
||
var D: int = int(row.get("node_depth", -1))
|
||
var key := "%d_%d" % [L, D]
|
||
if levels.has(key):
|
||
errs.append({"category": "dup_key", "message": "BoardGen 键重复: %s" % key})
|
||
levels[key] = true
|
||
if L < 1:
|
||
errs.append({"category": "domain", "message": "level_index 须 ≥1"})
|
||
if D < 0:
|
||
errs.append({"category": "domain", "message": "node_depth 须 ≥0"})
|
||
var w: int = int(row.get("board_width", 0))
|
||
var h: int = int(row.get("board_height", 0))
|
||
if w < 8 or w > 64 or h < 8 or h > 64:
|
||
errs.append({"category": "domain", "message": "(%d,%d) 尺寸越域 [8,64]" % [L, D]})
|
||
var d: float = float(row.get("mine_density", 0.0))
|
||
if d <= 0.0 or d > 0.5:
|
||
errs.append({"category": "domain", "message": "(%d,%d) mine_density 越域 (0,0.5]" % [L, D]})
|
||
var t: float = float(row.get("inferability_target", 0.0))
|
||
if t < TARGET_ABS_FLOOR or t > 1.0:
|
||
errs.append({"category": "domain", "message": "(%d,%d) inferability_target 越域 [0.85,1](P1 绝对锚)" % [L, D]})
|
||
if not SOLVER_RULES.has(row.get("solver_rules", "")):
|
||
errs.append({"category": "enum", "message": "(%d,%d) solver_rules 出域" % [L, D]})
|
||
if row.get("solver_rules", "") != "single_subset":
|
||
errs.append({"category": "frozen", "message": "(%d,%d) solver_rules 冻结 single_subset" % [L, D]})
|
||
anchor_ok[L] = anchor_ok.get(L, false) or D == 0
|
||
return errs
|
||
|
||
|
||
## 约束域:min_safe/生成尝试预算/布雷数下限与可行性/成本护栏/图类型词表
|
||
func _validate_row_constraints(row: Dictionary) -> Array[Dictionary]:
|
||
var errs: Array[Dictionary] = []
|
||
var L: int = int(row.get("level_index", 0))
|
||
var D: int = int(row.get("node_depth", -1))
|
||
var w: int = int(row.get("board_width", 0))
|
||
var h: int = int(row.get("board_height", 0))
|
||
var d: float = float(row.get("mine_density", 0.0))
|
||
var ms: int = int(row.get("min_safe_count", 0))
|
||
if ms < 1 or ms > 999:
|
||
errs.append({"category": "domain", "message": "(%d,%d) min_safe_count 越域 [1,999]" % [L, D]})
|
||
var att: int = int(row.get("max_gen_attempts", 0))
|
||
if att < 1 or att > 32:
|
||
errs.append({"category": "domain", "message": "(%d,%d) max_gen_attempts 越域 [1,32]" % [L, D]})
|
||
# 布雷数下限(R3)——零雷板拒绝(−spawn_edge² = 出生区格数,表头单点导出)
|
||
var n_mines := int(round(d * float(w * h - spawn_edge * spawn_edge)))
|
||
if n_mines < 1:
|
||
errs.append({"category": "cross_field", "message": "(%d,%d) 布雷数下限违例 round(d×(W×H−9)) ≥ 1" % [L, D]})
|
||
# F2b min_safe 可行性
|
||
if w * h - n_mines < ms:
|
||
errs.append({"category": "cross_field", "message": "(%d,%d) min_safe_feasible 违例" % [L, D]})
|
||
# F2c 成本可行性
|
||
if att * w * h > COST_BUDGET:
|
||
errs.append({"category": "cross_field", "message": "(%d,%d) cost_feasible 违例 attempts×W×H ≤ %d" % [L, D, COST_BUDGET]})
|
||
# 图类型词表 v1.0(dungeon=地牢基线 / underdark=幽暗地域;扩词走本表修订)
|
||
var bt: String = str(row.get("board_type", ""))
|
||
if not BOARD_TYPE_VOCABULARY.has(bt):
|
||
errs.append({"category": "cross_field", "message": "(%d,%d) board_type 越词表: %s" % [L, D, bt]})
|
||
return errs
|
||
|
||
|
||
## 初始展开收窄域(加载期校验违例致命;缺省 shave=1 现行投值 / cap=0 闸关防御口径)
|
||
func _validate_shave_params(row: Dictionary) -> Array[Dictionary]:
|
||
var errs: Array[Dictionary] = []
|
||
var L: int = int(row.get("level_index", 0))
|
||
var D: int = int(row.get("node_depth", -1))
|
||
var w: int = int(row.get("board_width", 0))
|
||
var h: int = int(row.get("board_height", 0))
|
||
var sr: int = int(row.get("shave_ring_radius", 1))
|
||
if sr < 0 or sr > 2:
|
||
errs.append({"category": "domain", "message": "(%d,%d) shave_ring_radius 越域 [0,2]: %d" % [L, D, sr]})
|
||
var r0_cap: int = int(row.get("r0_max_cap", 0))
|
||
if r0_cap > 0:
|
||
# 护栏(需求卡片 §二):cap ≥ 出生格数(spawn_edge² 表头导出)、cap ≤ 板格数一半
|
||
if r0_cap < spawn_edge * spawn_edge:
|
||
errs.append({"category": "cross_field", "message": "(%d,%d) r0_max_cap 低于出生保障面 %d: %d" % [L, D, spawn_edge * spawn_edge, r0_cap]})
|
||
if r0_cap > (w * h) / 2:
|
||
errs.append({"category": "cross_field", "message": "(%d,%d) r0_max_cap 超板格数一半 %d: %d" % [L, D, (w * h) / 2, r0_cap]})
|
||
return errs
|
||
|
||
|
||
var _resolver # R1 解析协作件(ConfigDB load_tables 装配段经 bind_resolver 注入——resolver 文件与键零搬移)
|
||
|
||
|
||
# 解析协作件挂载口(ConfigDB 装配段公开面〔5.1 封装修复——直写 _resolver 改经此口〕)
|
||
func bind_resolver(resolver: ConfigDbResolver) -> void:
|
||
_resolver = resolver
|
||
|
||
|
||
## 进度行解析委托(消费面 db.combat.board_gen.resolve_row——R1 ceil/clamp 语义归 ConfigDbResolver)
|
||
func resolve_row(L: int, D: int) -> Dictionary:
|
||
return _resolver.resolve_board_gen(L, D)
|