## VERTICAL SLICE - NOT FOR PRODUCTION ## 地形变异表 schema:逐 variant_id 行的类型化读取(空清单=零变异合法态;垂直切片验证:完整战斗逐行归因)。 ## 数据驱动扩展契约:新增类型=表加行+必要时扩枚举词表,不改读取方。 ## 跨字段互锁(效果↔触发↔布置↔作用域)违例加载期致命(fail-fast)。 class_name VariationTable extends Resource @export var rows: Array[Dictionary] = [] # Array[Dictionary] # 布置参数(表头字段;数量公式 = round(候选池格数 × percent_of_cells / 100) + (关卡序号 − 1) × per_level_bonus, # 连通片生长、单片 ≤ pack_max——2026-09-19 用户拍板:图规模百分比 × 关卡递增,全连通、单片不超 4; # 2026-09-21 用户拍板基数修正:百分比基数 = 候选池(安全格 − 初始展开区,即实际可挖格)而非总格数) @export var percent_of_cells: int = 5 @export var per_level_bonus: int = 2 @export var pack_max: int = 4 const SCOPES := ["region", "global"] # resource_face = 资源面偏斜(全局作用域变异首发批 2026-10-04——整图修摇号分布:生存族按 effect_value% # 下调、进攻族等比上浮重归一,不动任何格子数据;effect_value 语义随行域切换,不适用 flat_damage [1,9]) const EFFECTS := ["none", "flat_damage", "hide_number", "resource_face"] # 效果词表:直伤 / 数字遮蔽 / 资源面偏斜(可扩充) const TRIGGERS := ["on_open", "on_number_shown", "always"] # 触发词表:被打开即触发 / 打开且显示数字时触发 / 板级恒在无触发时机(可扩充) const PLACEMENTS := ["closed_only", "any"] # 布置约束词表:只落布置时未开格 / 任意格(可扩充) func validate() -> Array: # 空清单是合法状态(地形变异 P0)——零行即零变体格,配置加载零错误、零警告 var errs: Array[Dictionary] = [] var seen := {} for row in rows: var vid: String = row.get("variant_id", "") if vid == "": errs.append({"category": "domain", "message": "variant_id 缺失/空"}) continue if seen.has(vid): errs.append({"category": "dup_key", "message": "variant_id 重复: %s" % vid}) seen[vid] = true if String(row.get("display_name", "")) == "": errs.append({"category": "domain", "message": "%s display_name 空" % vid}) var ul: int = int(row.get("unlock_level", 0)) if ul < 1 or ul > 999: errs.append({"category": "domain", "message": "%s unlock_level 越域 [1,999]: %d" % [vid, ul]}) if not SCOPES.has(row.get("scope", "")): errs.append({"category": "enum", "message": "%s scope 出域: %s" % [vid, row.get("scope")]}) if not EFFECTS.has(row.get("effect", "")): errs.append({"category": "enum", "message": "%s effect 出域: %s" % [vid, row.get("effect")]}) if not TRIGGERS.has(row.get("trigger", "")): errs.append({"category": "enum", "message": "%s trigger 出域: %s" % [vid, row.get("trigger")]}) if not PLACEMENTS.has(row.get("placement", "")): errs.append({"category": "enum", "message": "%s placement 出域: %s" % [vid, row.get("placement")]}) # 跨字段互锁(加载期致命——枚举语义一致性随首批类型定稿批 2026-09-19 立闸): # 伤害型 = 挖开即伤 ⇒ 触发 on_open + 只落未开格(closed_only)+ 伤害量为加法核小整数 if row.get("effect", "") == "flat_damage": if row.get("trigger", "") != "on_open": errs.append({"category": "cross_field", "message": "%s flat_damage ⇒ trigger 必为 on_open" % vid}) if row.get("placement", "") != "closed_only": errs.append({"category": "cross_field", "message": "%s flat_damage ⇒ placement 必为 closed_only(只出现在未打开的格子)" % vid}) var ev: int = int(row.get("effect_value", 0)) if ev < 1 or ev > 9: errs.append({"category": "domain", "message": "%s effect_value 越域 [1,9](加法核小整数): %d" % [vid, ev]}) # 信息型 = 被打开且显示数字时起作用 ⇒ 触发 on_number_shown if row.get("effect", "") == "hide_number" and row.get("trigger", "") != "on_number_shown": errs.append({"category": "cross_field", "message": "%s hide_number ⇒ trigger 必为 on_number_shown" % vid}) # 信息遮蔽型只允许落安全格(遮蔽对象是数字;雷格无数字可遮)——placement=any 时由读取方 # 呈现面按 kind=safe 自然短路,此处不设布置约束 # 全局作用域型互锁(2026-10-04 首发批——resource_face ⟺ scope=global 三条 + 新值域; # 判定键=effect/scope 词值,既有六 region 行(flat_damage/hide_number)不入本组——零误伤) if row.get("effect", "") == "resource_face": if row.get("scope", "") != "global": errs.append({"category": "cross_field", "message": "%s resource_face ⇒ scope 必为 global(整图作用域)" % vid}) # resource_face 行 effect_value = 生存族权重下调百分比(0=中性合法态——off 锚用) var gev: int = int(row.get("effect_value", 0)) if gev < 0 or gev > 50: errs.append({"category": "domain", "message": "%s effect_value 越域 [0,50](资源面偏斜百分比): %d" % [vid, gev]}) if row.get("scope", "") == "global": if row.get("effect", "") != "resource_face": errs.append({"category": "cross_field", "message": "%s scope=global ⇒ effect 必为 resource_face" % vid}) if row.get("placement", "") != "any": errs.append({"category": "cross_field", "message": "%s scope=global ⇒ placement 必为 any(整图无格子布置域)" % vid}) if row.get("trigger", "") != "always": errs.append({"category": "cross_field", "message": "%s scope=global ⇒ trigger 必为 always(板级恒在,无触发时机)" % vid}) return errs func row_by_id(variant_id: String) -> Dictionary: for row in rows: if row.get("variant_id", "") == variant_id: return row return {}