iter54-卡3:精英专属池批(elite 行类与字段互锁/dangling_base/effective_pool 池继承合并/ENCOUNTER_ROW_KINDS 寻址门暂不接线/goblin_elite 落表+存量冒烟同步+图鉴导出认得精英行)——对照表 R1-R7 全绿(elite_row_test 六组/五件存量套件直跑绿/生产表 error 诊断 0),T3.8 里程碑出口 --code 六闸全绿【验收通过】
This commit is contained in:
1 parent
4c424af358
commit
f9c9caf56b
14 files changed
+540
-40
No files matched your search
@@ -8,8 +8,10 @@ extends Resource
|
||||
|
||||
@export var rows: Array[Dictionary] = [] # Array[Dictionary]
|
||||
|
||||
# 行类封闭枚举(对齐关卡节点 type 词表):battle = 战斗节点配池,boss = 终点行集
|
||||
const ROW_KINDS := ["battle", "boss"]
|
||||
# 行类封闭枚举:表内允许出现的行类(battle/boss 对齐关卡节点 type 词表,elite=精英行)
|
||||
const ROW_KINDS := ["battle", "boss", "elite"]
|
||||
# 开战寻址可读的行类——elite 接线前不在此列(敌人系统 v1.9 暂不接线;resolver 行类门单点消费)
|
||||
const ENCOUNTER_ROW_KINDS := ["battle", "boss"]
|
||||
|
||||
# 护甲类型封闭枚举(克制矩阵本体在 SettlementTable;
|
||||
# 语义键 {light, ward, heavy} = 轻甲 / 魔法护甲 / 重甲)
|
||||
@@ -49,6 +51,7 @@ func validate() -> Array:
|
||||
errs.append_array(_validate_row_numbers(row, eid2))
|
||||
errs.append_array(_validate_row_atk_range(row, eid2))
|
||||
errs.append_array(_validate_row_pool(row, eid2))
|
||||
errs.append_array(_validate_elite_fields(row, eid2))
|
||||
errs.append_array(_validate_row_types(row, eid2))
|
||||
# 行集构成纪律②(行序即契约,敌人系统 v0.15):同带键多行必须
|
||||
# 文件内相邻。迭代中记录上一行行集键;当前行集键曾出现过但 ≠ 上一行行集键 = 同带键行被隔断,报错。
|
||||
@@ -67,6 +70,7 @@ func validate() -> Array:
|
||||
boss_ok[L2] = true
|
||||
if not anchor_ok.has(L2):
|
||||
anchor_ok[L2] = false
|
||||
errs.append_array(_validate_elite_refs())
|
||||
errs.append_array(_validate_anchor_rows(anchor_ok, boss_ok))
|
||||
return errs
|
||||
|
||||
@@ -85,7 +89,7 @@ func _validate_row_base(row: Dictionary, seen: Dictionary) -> Array[Dictionary]:
|
||||
if L < 1 or band < 0:
|
||||
errs.append({"category": "domain", "message": "进度键越域 (%d,band %d)" % [L, band]})
|
||||
if not ROW_KINDS.has(rk):
|
||||
errs.append({"category": "domain", "message": "%s row_kind 未知枚举: \"%s\"(域 {battle, boss})" % [eid, rk]})
|
||||
errs.append({"category": "domain", "message": "%s row_kind 未知枚举: \"%s\"(域 {battle, boss, elite})" % [eid, rk]})
|
||||
if eid == "":
|
||||
errs.append({"category": "domain", "message": "enemy_id 空"})
|
||||
if String(row.get("display_name", "")) == "":
|
||||
@@ -136,25 +140,99 @@ func _validate_row_atk_range(row: Dictionary, eid: String) -> Array[Dictionary]:
|
||||
## 非空池 + 条目 {skill_id 非空, weight > 0} + 池内 skill_id 不重复。
|
||||
## 普攻冷却恒 0(SkillTable schema 锁死)→ 可摇集合恒非空,"空池不可能"由此结构保证。
|
||||
func _validate_row_pool(row: Dictionary, eid: String) -> Array[Dictionary]:
|
||||
var no_errs: Array[Dictionary] = []
|
||||
if String(row.get("row_kind", "")) == "elite":
|
||||
return no_errs # elite 行不带 pool 键(pool_extra 承载附加技——条目域校验随 _validate_elite_fields)
|
||||
var errs: Array[Dictionary] = []
|
||||
var pool: Array[Variant] = row.get("pool", [])
|
||||
if pool.is_empty():
|
||||
errs.append({"category": "domain", "message": "%s pool 空池(禁——技能池行必带池模板)" % eid})
|
||||
errs.append_array(_validate_pool_entries(pool, eid, "pool"))
|
||||
return errs
|
||||
|
||||
|
||||
## 池条目域共用校验(pool 与 pool_extra 共用——权重上限 9999 不出现第二份,契约 3 单点);
|
||||
## field 承载报错定位段("pool"=既有文案逐字不变,"pool_extra"=附加技清单定位)。
|
||||
func _validate_pool_entries(entries: Array, eid: String, field: String) -> Array[Dictionary]:
|
||||
var errs: Array[Dictionary] = []
|
||||
var pool_ids := {}
|
||||
for entry_v in pool:
|
||||
for entry_v in entries:
|
||||
if not (entry_v is Dictionary):
|
||||
errs.append({"category": "domain", "message": "%s pool 条目非字典: %s" % [eid, str(entry_v)]})
|
||||
errs.append({"category": "domain", "message": "%s %s 条目非字典: %s" % [eid, field, str(entry_v)]})
|
||||
continue
|
||||
var entry: Dictionary[Variant, Variant] = entry_v
|
||||
var sid: String = String(entry.get("skill_id", ""))
|
||||
if sid == "":
|
||||
errs.append({"category": "domain", "message": "%s pool 条目 skill_id 空" % eid})
|
||||
errs.append({"category": "domain", "message": "%s %s 条目 skill_id 空" % [eid, field]})
|
||||
elif pool_ids.has(sid):
|
||||
errs.append({"category": "dup_key", "message": "%s pool 内 skill_id 重复: %s" % [eid, sid]})
|
||||
errs.append({"category": "dup_key", "message": "%s %s 内 skill_id 重复: %s" % [eid, field, sid]})
|
||||
pool_ids[sid] = true
|
||||
var w: int = int(entry.get("weight", 0))
|
||||
if w <= 0 or w > 9999:
|
||||
errs.append({"category": "domain", "message": "%s pool 权重越域 (0,9999]: %d" % [eid, w]})
|
||||
errs.append({"category": "domain", "message": "%s %s 权重越域 (0,9999]: %d" % [eid, field, w]})
|
||||
return errs
|
||||
|
||||
|
||||
## 精英行字段互锁(敌人系统 v1.9 :281/:282/:283):elite 行必备 base_ref/pool_extra、禁 pool、
|
||||
## enemy_id 不与 base_ref 同名;非 elite 行禁带 base_ref/pool_extra。base_ref 引用存在性归 _validate_elite_refs。
|
||||
func _validate_elite_fields(row: Dictionary, eid: String) -> Array[Dictionary]:
|
||||
var errs: Array[Dictionary] = []
|
||||
if String(row.get("row_kind", "")) == "elite":
|
||||
var base_ref := String(row.get("base_ref", ""))
|
||||
if base_ref == "":
|
||||
errs.append({"category": "cross_field", "message": "%s elite 行 base_ref 缺或空(基础敌种引用必带)" % eid})
|
||||
if base_ref == eid:
|
||||
errs.append({"category": "cross_field", "message": "%s elite 行 base_ref 与自身 enemy_id 同名(禁)" % eid})
|
||||
if not row.has("pool_extra") or (row.get("pool_extra") as Array).is_empty():
|
||||
errs.append({"category": "cross_field", "message": "%s elite 行 pool_extra 缺键或空(附加技清单非空必带)" % eid})
|
||||
if row.has("pool"):
|
||||
errs.append({"category": "cross_field", "message": "%s elite 行带 pool 键(禁——池走继承+附加)" % eid})
|
||||
if row.has("pool_extra") and (row.get("pool_extra") as Array).size() > 0:
|
||||
errs.append_array(_validate_pool_entries(row.get("pool_extra"), eid, "pool_extra"))
|
||||
else:
|
||||
if row.has("base_ref"):
|
||||
errs.append({"category": "cross_field", "message": "%s 非 elite 行带 base_ref 键(禁——基础敌种引用为 elite 行专属)" % eid})
|
||||
if row.has("pool_extra"):
|
||||
errs.append({"category": "cross_field", "message": "%s 非 elite 行带 pool_extra 键(禁——附加技清单为 elite 行专属)" % eid})
|
||||
return errs
|
||||
|
||||
|
||||
## 精英行跨行引用校验(敌人系统 v1.9 :148/:282):base_ref 指向同关 battle 行类敌种(缺/空已由
|
||||
## _validate_elite_fields 报——此处跳过);指向 elite 行判别同门 _min_band_row;附加技与基础池
|
||||
## skill_id 交集非空报 dup_key(敌人系统.md:149 附加技与基础池重复拒绝)。
|
||||
func _validate_elite_refs() -> Array[Dictionary]:
|
||||
var errs: Array[Dictionary] = []
|
||||
for row in rows:
|
||||
if String(row.get("row_kind", "")) != "elite":
|
||||
continue
|
||||
var base_ref := String(row.get("base_ref", ""))
|
||||
if base_ref == "":
|
||||
continue
|
||||
var level := int(row.get("level_index", 0))
|
||||
var eid := String(row.get("enemy_id", ""))
|
||||
var base := _min_band_row(level, base_ref, "battle")
|
||||
if base.is_empty():
|
||||
if not _min_band_row(level, base_ref, "elite").is_empty():
|
||||
errs.append({"category": "dangling_base", "message": "%s base_ref 指向 elite 行: %s(基础敌种须为 battle 行类)" % [eid, base_ref]})
|
||||
else:
|
||||
errs.append({"category": "dangling_base", "message": "%s base_ref 同关 battle 行类无此敌种: %s" % [eid, base_ref]})
|
||||
continue
|
||||
errs.append_array(_extra_overlap_errs(row, base, eid))
|
||||
return errs
|
||||
|
||||
|
||||
## 附加技与基础池重叠判(_validate_elite_refs 交集段提出——分支预算内聚)
|
||||
func _extra_overlap_errs(row: Dictionary, base_row: Dictionary, eid: String) -> Array[Dictionary]:
|
||||
var errs: Array[Dictionary] = []
|
||||
var base_ids := {}
|
||||
for entry_v in base_row.get("pool", []):
|
||||
if entry_v is Dictionary:
|
||||
base_ids[String(entry_v.get("skill_id", ""))] = true
|
||||
for entry_v in row.get("pool_extra", []):
|
||||
if entry_v is Dictionary:
|
||||
var sid := String(entry_v.get("skill_id", ""))
|
||||
if sid != "" and base_ids.has(sid):
|
||||
errs.append({"category": "dup_key", "message": "%s pool_extra 与基础池 skill_id 重复: %s" % [eid, sid]})
|
||||
return errs
|
||||
|
||||
|
||||
@@ -193,6 +271,43 @@ func _validate_anchor_rows(anchor_ok: Dictionary, boss_ok: Dictionary) -> Array[
|
||||
return errs
|
||||
|
||||
|
||||
# 基础行查找单点(按 (关, 敌种, 行类) 取深度带最小行——精英有效池与「指向 elite 判别」共用,
|
||||
# 禁第二份查找算法):无匹配返回空字典。行字段一律 row.get 读取(夹具行可缺键)。
|
||||
func _min_band_row(level: int, eid: String, kind: String) -> Dictionary:
|
||||
var best := {}
|
||||
var best_band := -1
|
||||
for row in rows:
|
||||
if int(row.get("level_index", -1)) != level or String(row.get("row_kind", "")) != kind \
|
||||
or String(row.get("enemy_id", "")) != eid:
|
||||
continue
|
||||
var band := int(row.get("depth_band", -1))
|
||||
if best.is_empty() or band < best_band:
|
||||
best = row
|
||||
best_band = band
|
||||
return best
|
||||
|
||||
|
||||
## 精英有效池(按需现算、零缓存零写回——敌人系统.md:149 继承不复制):非 elite 行 = 自身 pool
|
||||
## 字典条目副本;elite 行 = 基础行(同关 battle 行类基础敌种深度带最小行)pool 条目 + 本行
|
||||
## pool_extra 条目(均只取 is Dictionary 条目并 duplicate——返回值改写不回灌表内行)。
|
||||
func effective_pool(row: Dictionary) -> Array[Dictionary]:
|
||||
var out: Array[Dictionary] = []
|
||||
if String(row.get("row_kind", "")) != "elite":
|
||||
for entry_v in row.get("pool", []):
|
||||
if entry_v is Dictionary:
|
||||
out.append((entry_v as Dictionary).duplicate())
|
||||
return out
|
||||
var base := _min_band_row(int(row.get("level_index", 0)), String(row.get("base_ref", "")), "battle")
|
||||
if not base.is_empty():
|
||||
for entry_v in base.get("pool", []):
|
||||
if entry_v is Dictionary:
|
||||
out.append((entry_v as Dictionary).duplicate())
|
||||
for entry_v in row.get("pool_extra", []):
|
||||
if entry_v is Dictionary:
|
||||
out.append((entry_v as Dictionary).duplicate())
|
||||
return out
|
||||
|
||||
|
||||
# 深度带切法单点(唯一实现,禁第二份算法):band_keys = 该 (level_index, row_kind) 的
|
||||
# depth_band 键升序数组;返回首个 ≥ D 的键(请求落带取偏难一侧)。
|
||||
# D 高于全部键 → 返回 -1——resolver 侧 clamp 顶带+warning,跨表校验侧判「顶 clamp 消解」违例;
|
||||
|
||||
@@ -207,6 +207,25 @@ rows = [{
|
||||
"armor_type": "light",
|
||||
"armor_class": 8
|
||||
}, {
|
||||
"level_index": 1,
|
||||
"row_kind": "elite",
|
||||
"depth_band": 0,
|
||||
"enemy_id": "goblin_elite",
|
||||
"display_name": "精英·地精",
|
||||
"unit_count": 1,
|
||||
"hp": 20,
|
||||
"atk": 2,
|
||||
"atk_min": 1,
|
||||
"atk_max": 3,
|
||||
"base_ref": "goblin",
|
||||
"pool_extra": [{
|
||||
"skill_id": "heavy",
|
||||
"weight": 2
|
||||
}],
|
||||
"attack_type": "normal",
|
||||
"armor_type": "light",
|
||||
"armor_class": 2
|
||||
}, {
|
||||
"level_index": 2,
|
||||
"row_kind": "battle",
|
||||
"depth_band": 0,
|
||||
@@ -943,6 +962,7 @@ rows = [{
|
||||
; 不做差异化(pierce/magic 属新设计另立项)。全局平移效应=主角穿重甲受伤 −1 / 魔法护甲 +1 / 轻甲 0,
|
||||
; 锻造甲路线首次具备战斗意义;克制矩阵数值零触碰(settlement.tres armor_effectiveness 不改)。
|
||||
;
|
||||
; 行键 = (level_index, row_kind, depth_band, enemy_id);row_kind ∈ {battle, boss}——battle=战斗节点配池,boss=终点行集;
|
||||
; 行键 = (level_index, row_kind, depth_band, enemy_id);row_kind ∈ {battle, boss, elite}——battle=战斗节点配池,boss=终点行集,elite=精英行(独立 enemy_id、池走继承+附加,暂不接线任何节点寻址);
|
||||
; depth_band=节点深度所在的带键,请求落带取首个 ≥ 深度的键(偏难一侧);越顶带由 resolver clamp、跨表校验判为调参事故。
|
||||
; (1,3) 大地精行 2026-11-04 用户裁退役,改地精+熊地精混编(行序熊地精在前、行值整行复用 (1,2)/(1,0)、静态骰和 3);沿革见 git 提交史
|
||||
; 精英专属池批首只精英=地精精英(1,elite,0,goblin_elite),行值 provisional(投值所有权归难度曲线与Boss 统一修正),来源=2026-11-06 用户点头草案 v0
|
||||
Reference in new issue
Block a user