feat(敌人表深度带): 遭遇行集寻址改为 (关卡, row_kind, 深度带)

敌人行集不再按 (level_index, node_depth) 精确点寻址,改为 (level_index, row_kind, depth_band):
带切法单点 EnemyTable.band_of 取首个不小于请求深度的带(偏难一侧),高于顶带返回 -1 交
resolver 取顶带并发 resolve_depth_clamp(四元组去重不变);resolver 与 ConfigDb 门面同步
加 row_kind 第三参;unit_container.spawn 与 BattleOrchestrator 透传行类(battle/boss)。

敌人表 20 行键迁移(原 node_depth 数值就地成为新带键,值列零变更),新增 row_kind 列;
schema 校验随键换代:row_kind 封闭枚举、dup_key/rowset_rule 新键串、每关 battle 带 0 锚
与每关 boss 行集 ≥1;跨表校验 ④ 敌侧改带寻址,新增同日图多 Boss 同带报错与顶带消解报错。

读数:基线冒烟 1156 项 0 失败;本轮冒烟 1161 项 0 失败;单元 35 套件全绿;探针 --click 全绿。
This commit is contained in:
DKChan committed 2026-10-22 04:17:21 +08:00
1 parent 603eba3117
commit 07f3eb55e3
8 files changed
+191 -72

No files matched your search

+47 -21
View File
@@ -1,12 +1,16 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 敌人表 schema:逐 (level_index, node_depth, enemy_id) 行集的类型化读取(行集可多行混编;垂直切片验证:完整战斗逐行归因)。
## 行序即契约:同坐标多行必须文件内相邻——resolver 返回序/生成序/叙述取首行全依赖文件行序。
## 敌人表 schema:逐 (level_index, row_kind, depth_band, enemy_id) 行集的类型化读取(行集可多行混编;垂直切片验证:完整战斗逐行归因)。
## 行序即契约:同带键多行必须文件内相邻——resolver 返回序/生成序/叙述取首行全依赖文件行序。
## 深度带 = 节点深度所在的区间:配池按带不按点,难度坡随带递增;row_kind 承载「Boss 行按终点配置」。
## 行集实例总数 ≤4 是开战骰后守卫(加载期不可判,闸在单位容器 spawn);技能池构成纪律需跨表视野,校验在 ConfigDB 跨表通道。
class_name EnemyTable
extends Resource
@export var rows: Array[Dictionary] = [] # Array[Dictionary]
# 行类封闭枚举(对齐关卡节点 type 词表):battle = 战斗节点配池,boss = 终点行集
const ROW_KINDS := ["battle", "boss"]
# 护甲类型封闭枚举(克制矩阵本体在 SettlementTable;
# 语义键 {light, ward, heavy} = 轻甲 / 魔法护甲 / 重甲)
const ARMOR_TYPES := ["light", "ward", "heavy"]
@@ -35,19 +39,23 @@ func validate() -> Array:
errs.append({"category": "empty_table", "message": "EnemyTable 为空"})
return errs
var seen := {}
var last_rk := ""
var closed_rk := {}
var last_rowset_key := ""
var closed_rowset := {}
var anchor_ok := {}
var boss_ok := {}
for row in rows:
var L: int = int(row.get("level_index", 0))
var D: int = int(row.get("node_depth", -1))
var rk: String = String(row.get("row_kind", ""))
var band: int = int(row.get("depth_band", -1))
var eid: String = row.get("enemy_id", "")
var key := "%d_%d_%s" % [L, D, eid]
var key := "%d_%s_%d_%s" % [L, rk, band, eid]
if seen.has(key):
errs.append({"category": "dup_key", "message": "EnemyTable 键重复: %s" % key})
seen[key] = true
if L < 1 or D < 0:
errs.append({"category": "domain", "message": "进度键越域 (%d,%d)" % [L, D]})
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]})
if eid == "":
errs.append({"category": "domain", "message": "enemy_id 空"})
if String(row.get("display_name", "")) == "":
@@ -118,23 +126,41 @@ func validate() -> Array:
var ac: int = int(row.get("armor_class", 0))
if ac < 0 or ac > 999:
errs.append({"category": "domain", "message": "%s armor_class 越域 [0,999]: %d" % [eid, ac]})
# 行集构成纪律②(行序即契约,敌人系统 v0.15):同坐标多行必须
# 文件内相邻。迭代中记录上一行坐标;当前坐标曾出现过但 ≠ 上一行坐标 = 同坐标行被隔断,报错。
# 同坐标多敌种混编合法;同坐标同敌种第二行仍由
# 行集构成纪律②(行序即契约,敌人系统 v0.15):同带键多行必须
# 文件内相邻。迭代中记录上一行行集键;当前行集键曾出现过但 ≠ 上一行行集键 = 同带键行被隔断,报错。
# 同带键多敌种混编合法;同带键同敌种第二行仍由
# dup_key 拦截(键含 enemy_id)。错误类别沿用 rowset_rule(定位面不变)。
var rk := "%d_%d" % [L, D]
if rk != last_rk:
if closed_rk.has(rk):
errs.append({"category": "rowset_rule", "message": "(%d,%d) 行集行序违例:同坐标多行必须文件内相邻(行序即契约——resolver 返回序/生成序/叙述取首行全依赖文件行序)" % [L, D]})
closed_rk[rk] = true
last_rk = rk
anchor_ok[L] = anchor_ok.get(L, false) or D == 0
var rowset_key := "%d_%s_%d" % [L, rk, band]
if rowset_key != last_rowset_key:
if closed_rowset.has(rowset_key):
errs.append({"category": "rowset_rule", "message": "(%d,%s,%d) 行集行序违例:同带键多行必须文件内相邻(行序即契约——resolver 返回序/生成序/叙述取首行全依赖文件行序)" % [L, rk, band]})
closed_rowset[rowset_key] = true
last_rowset_key = rowset_key
if rk == "battle" and band == 0:
anchor_ok[L] = true
elif rk == "boss":
boss_ok[L] = true
if not anchor_ok.has(L):
anchor_ok[L] = false
for L in anchor_ok.keys():
if not anchor_ok[L]:
errs.append({"category": "anchor", "message": "EnemyTable level %d 缺 depth-0 锚行" % L})
errs.append({"category": "anchor", "message": "EnemyTable level %d 缺 (battle, band 0) 锚行" % L})
if not boss_ok.has(L):
errs.append({"category": "anchor", "message": "EnemyTable level %d 缺 Boss 行集(row_kind=boss 行 ≥1)" % L})
return errs
# 深度带切法单点(唯一实现,禁第二份算法):band_keys = 该 (level_index, row_kind) 的
# depth_band 键升序数组;返回首个 ≥ D 的键(请求落带取偏难一侧)。
# D 高于全部键 → 返回 -1——resolver 侧 clamp 顶带+warning,跨表校验侧判「顶 clamp 消解」违例;
# band_keys 空 → 同样返回 -1(resolver 随之返回空行集,交消费侧既有空行集 fatal 路径)。
static func band_of(band_keys: Array, D: int) -> int:
for k in band_keys:
if int(k) >= D:
return int(k)
return -1
# 进度键解析单点收口(全仓统走
# ConfigDbResolver.resolve_enemy_rowset ceil 版行集数组:ceil 保证偏难,
# 禁双算法并存〔状态记录 312〕;坐标须全精确命中)。
# ConfigDbResolver.resolve_enemy_rowset 带寻址行集数组:D 维经本表 band_of 落带(偏难一侧),
# 禁双算法并存〔状态记录 312〕;L 维 ceil/clamp 与越顶 clamp 语义归 resolver)。
+44 -20
View File
@@ -6,7 +6,8 @@
script = ExtResource("1")
rows = [{
"level_index": 1,
"node_depth": 0,
"row_kind": "battle",
"depth_band": 0,
"enemy_id": "goblin",
"display_name": "地精",
"unit_count": 1,
@@ -27,7 +28,8 @@ rows = [{
"armor_class": 0
}, {
"level_index": 1,
"node_depth": 1,
"row_kind": "battle",
"depth_band": 1,
"enemy_id": "goblin",
"display_name": "地精一伙",
"unit_count": 2,
@@ -48,7 +50,8 @@ rows = [{
"armor_class": 1
}, {
"level_index": 1,
"node_depth": 2,
"row_kind": "battle",
"depth_band": 2,
"enemy_id": "bugbear",
"display_name": "熊地精",
"unit_count": 1,
@@ -71,7 +74,8 @@ rows = [{
"armor_class": 2
}, {
"level_index": 1,
"node_depth": 3,
"row_kind": "battle",
"depth_band": 3,
"enemy_id": "hobgoblin",
"display_name": "大地精",
"unit_count": 2,
@@ -95,7 +99,8 @@ rows = [{
"armor_class": 2
}, {
"level_index": 1,
"node_depth": 4,
"row_kind": "boss",
"depth_band": 4,
"enemy_id": "ogre_chief",
"display_name": "食人魔酋长",
"unit_count": 1,
@@ -122,7 +127,8 @@ rows = [{
"armor_class": 8
}, {
"level_index": 2,
"node_depth": 0,
"row_kind": "battle",
"depth_band": 0,
"enemy_id": "bugbear",
"display_name": "熊地精",
"unit_count": 1,
@@ -145,7 +151,8 @@ rows = [{
"armor_class": 1
}, {
"level_index": 2,
"node_depth": 1,
"row_kind": "battle",
"depth_band": 1,
"enemy_id": "bugbear",
"display_name": "熊地精猎群",
"unit_count": 2,
@@ -169,7 +176,8 @@ rows = [{
"armor_class": 2
}, {
"level_index": 2,
"node_depth": 4,
"row_kind": "battle",
"depth_band": 4,
"enemy_id": "giant_spider",
"display_name": "巨蜘蛛",
"unit_count": 2,
@@ -192,7 +200,8 @@ rows = [{
"armor_class": 2
}, {
"level_index": 2,
"node_depth": 5,
"row_kind": "battle",
"depth_band": 5,
"enemy_id": "bugbear",
"display_name": "熊地精重击手",
"unit_count": 1,
@@ -215,7 +224,8 @@ rows = [{
"armor_class": 8
}, {
"level_index": 2,
"node_depth": 5,
"row_kind": "battle",
"depth_band": 5,
"enemy_id": "giant_spider",
"display_name": "巨蜘蛛巢群",
"unit_count": 2,
@@ -242,7 +252,8 @@ rows = [{
"armor_class": 2
}, {
"level_index": 2,
"node_depth": 6,
"row_kind": "battle",
"depth_band": 6,
"enemy_id": "giant_spider",
"display_name": "巨蜘蛛巢群",
"unit_count": 2,
@@ -269,7 +280,8 @@ rows = [{
"armor_class": 2
}, {
"level_index": 2,
"node_depth": 7,
"row_kind": "battle",
"depth_band": 7,
"enemy_id": "giant_spider",
"display_name": "巨蜘蛛巢群",
"unit_count": 2,
@@ -296,7 +308,8 @@ rows = [{
"armor_class": 3
}, {
"level_index": 2,
"node_depth": 9,
"row_kind": "boss",
"depth_band": 9,
"enemy_id": "drider",
"display_name": "蛛化卓尔精灵",
"unit_count": 1,
@@ -326,7 +339,8 @@ rows = [{
"armor_class": 10
}, {
"level_index": 3,
"node_depth": 0,
"row_kind": "battle",
"depth_band": 0,
"enemy_id": "duergar",
"display_name": "灰矮人",
"unit_count": 1,
@@ -352,7 +366,8 @@ rows = [{
"armor_class": 2
}, {
"level_index": 3,
"node_depth": 1,
"row_kind": "battle",
"depth_band": 1,
"enemy_id": "hook_horror",
"display_name": "钩爪恐魔",
"unit_count": 2,
@@ -375,7 +390,8 @@ rows = [{
"armor_class": 3
}, {
"level_index": 3,
"node_depth": 4,
"row_kind": "battle",
"depth_band": 4,
"enemy_id": "hook_horror",
"display_name": "钩爪恐魔",
"unit_count": 2,
@@ -399,7 +415,8 @@ rows = [{
"armor_class": 3
}, {
"level_index": 3,
"node_depth": 5,
"row_kind": "battle",
"depth_band": 5,
"enemy_id": "duergar",
"display_name": "灰矮人重装手",
"unit_count": 1,
@@ -425,7 +442,8 @@ rows = [{
"armor_class": 9
}, {
"level_index": 3,
"node_depth": 6,
"row_kind": "battle",
"depth_band": 6,
"enemy_id": "hook_horror",
"display_name": "钩爪恐魔巢群",
"unit_count": 2,
@@ -449,7 +467,8 @@ rows = [{
"armor_class": 3
}, {
"level_index": 3,
"node_depth": 7,
"row_kind": "battle",
"depth_band": 7,
"enemy_id": "duergar",
"display_name": "灰矮人小队",
"unit_count": 2,
@@ -476,7 +495,8 @@ rows = [{
"armor_class": 4
}, {
"level_index": 3,
"node_depth": 9,
"row_kind": "boss",
"depth_band": 9,
"enemy_id": "duergar_king",
"display_name": "灰矮人石君王",
"unit_count": 1,
@@ -669,3 +689,7 @@ rows = [{
; N3 敌方攻击类型轴开列(2026-10-13 迭代 24 卡①,承接锻造工坊实装批迁移面⑥):20 行全填 normal 挂敌种行——
; 不做差异化(pierce/magic 属新设计另立项)。全局平移效应=主角穿重甲受伤 −1 / 魔法护甲 +1 / 轻甲 0,
; 锻造甲路线首次具备战斗意义;克制矩阵数值零触碰(settlement.tres armor_effectiveness 不改)。
;
; 行键 = (level_index, row_kind, depth_band, enemy_id);row_kind ∈ {battle, boss}——battle=战斗节点配池,boss=终点行集;
; depth_band=节点深度所在的带键,请求落带取首个 ≥ 深度的键(偏难一侧);越顶带由 resolver clamp、跨表校验判为调参事故。
; hobgoblin 行为缺口行:无立绘、不在敌种表——留任在役,补齐三件套(立绘+语义模板+敌种表行)归敌种内容批。