# VERTICAL SLICE - NOT FOR PRODUCTION # Validation Question: 玩家能否无指导在一场完整战斗中经历「开战→挖掘规划→轮末结算逐行兑现→胜/负」并逐行归因(P2) # Date: 2026-09-10 # 数据配置 BoardGenTable schema(棋盘生成 回填值;键 (level_index, node_depth)——首个进度键表)。 # 依据 design/archive/gdd-2026-09-14/a1-board-generation.md "BoardGenTable Schema 定稿" + F2/F2b/F2c 谓词。 class_name BoardGenTable extends Resource @export var rows: Array[Dictionary] = [] # Array[Dictionary] 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(2026-09-20 L3 内容批 +deep_underdark 灰矮人矿道;玩家侧显示名「地图类型」) func validate() -> Array: var errs: Array[Dictionary] = [] if rows.is_empty(): errs.append({"category": "empty_table", "message": "BoardGenTable 为空"}) return errs var levels := {} var anchor_ok := {} for row in rows: 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]}) 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)——零雷板拒绝 var n_mines := int(round(d * float(w * h - 9))) 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(2026-09-19 用户拍板定稿: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]}) anchor_ok[L] = anchor_ok.get(L, false) or D == 0 for L in anchor_ok.keys(): if not anchor_ok[L]: errs.append({"category": "anchor", "message": "level %d 缺 depth-0 锚行(EC15)" % L}) return errs