Files
gplan-2/game/data/schemas/variation_table.gd
T
DKChan 9a72394652 chore(代码卫生): 头注存量清偿批——82 文件按两类白名单改写为 ## 文档注释载体
按 code-conventions §1.4.1 第三轮定稿执行:core 22+schemas 18+presentation 根 14+views 28,全部头注 #→##、删日期/变更叙事/GDD 与架构反向链接/字段与域清单/机制描述,保留职责摘要+真实使用约束(防事故禁令/单一入口/信号契约每信号一行)。main.gd 31→5 行、save_manager 45→5 行、adventure_runner 37→5 行为最大单件。子代理分模块执行(波一 core+schemas、波二 presentation+views,并发≤2)。验收=tools/verify_comment_only_change.py 逐字节校验(82 文件头注区外逐行一致=零行为变更)+audit_file_headers 全绿;纯注释批免冒烟(先例 5843270)
2026-10-16 23:28:07 +08:00

88 lines
5.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
## 地形变异表 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 {}