卡①T2(核心数据面结构收敛批): 校验族小表组+跨表校验器+解析器八文件拆分——board_gen 行/约束/收窄三域/level_gen_params 行头(skip)/形态/计数三域+层深尾段/meta_table 三行族域/skill_table 行头(skip)/公共/四类型域+表不变量/variation 行域+效果互锁+全局互锁/status_effect 行头+量纲域/cross_validator 七编号块域函数化(_cv_reward_dangling/_cv_reward_treasure/_cv_poolmap/_cv_source_labels/_cv_skill_pool/_cv_shelf_pool/_cv_counter_closure+decls/reads分层)+validate_skill_pool_links计数束化/resolver resolve_enemy_rowset三纯行扫描+_resolve_progress三段(坐标对束化);错误清单与告警序逐字,23键全清,data面13套件全绿
This commit is contained in:
1 parent
c56b816fee
commit
9646ce545d
8 files changed
+545
-321
No files matched your search
@@ -11,21 +11,43 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void:
|
||||
var st_tab: SettlementTable = loaded.get("settlement")
|
||||
if st == null or rt == null or st_tab == null:
|
||||
return # 单表校验已报错,不连锁
|
||||
# ①c F2 投放维度覆盖校验不启用:挖格不掉道具,秘宝
|
||||
# 唯一来路=三选一共享池〔节点/关卡完成〕;共享池为「抽到道具位再动态掷定具体道具」,维度覆盖不可
|
||||
# 静态校验。
|
||||
var effect_ids := _effect_ids_of(st)
|
||||
_cv_reward_dangling(rt, effect_ids, push_diag) # ① 增益词表悬空
|
||||
_cv_reward_treasure(loaded, rt, push_diag) # ①b aux 投放行双表
|
||||
_cv_poolmap(st_tab, effect_ids, push_diag) # ② 池映射悬空
|
||||
_cv_source_labels(st_tab, push_diag) # ③ 来源标签键集
|
||||
_cv_skill_pool(loaded, push_diag) # ⑤ 技能池引用完整性
|
||||
_cv_shelf_pool(loaded, push_diag) # ⑥ 商店货架 × 冒险共享池
|
||||
_cv_counter_closure(loaded, push_diag) # ⑦ 计数器引用闭合
|
||||
|
||||
|
||||
## 增益状态词表全集(effect_id → true)
|
||||
func _effect_ids_of(st: StatusEffectTable) -> Dictionary:
|
||||
var effect_ids := {}
|
||||
for row in st.rows:
|
||||
effect_ids[row.get("effect_id", "")] = true
|
||||
# ① RewardTable effect_id 悬空(percent 行引用 StatusEffectTable——悬空加载致命)。
|
||||
# 例外:gp_gain/gp_loss = GP 战斗内缓冲(escrow)通道的路由键
|
||||
# (战斗结算 _apply_gp_row 兑付——不入增益状态容器),词表封闭按字面豁免悬空引用校验。
|
||||
return effect_ids
|
||||
|
||||
|
||||
## ① RewardTable effect_id 悬空(percent 行引用 StatusEffectTable——悬空加载致命)。
|
||||
## 例外:gp_gain/gp_loss = GP 战斗内缓冲(escrow)通道的路由键
|
||||
## (战斗结算 _apply_gp_row 兑付——不入增益状态容器),词表封闭按字面豁免悬空引用校验。
|
||||
func _cv_reward_dangling(rt: RewardTable, effect_ids: Dictionary, push_diag: Callable) -> void:
|
||||
for row in rt.rows:
|
||||
if String(row.get("cell_type", "")) == RewardTable.TREASURE_ITEM_TYPE:
|
||||
continue # aux 行 effect_id=item_id——归 ①b 双表校验(reward_dangling_treasure),不入增益词表面
|
||||
var eff: String = row.get("effect_id", "")
|
||||
if eff != "" and eff != "gp_gain" and eff != "gp_loss" and not effect_ids.has(eff):
|
||||
push_diag.call("error", "dangling_effect", "RewardTable %s 引用不存在的 effect_id: %s" % [row.get("reward_id"), eff])
|
||||
# ①b RewardTable aux 投放行双表校验(reward_dangling_treasure):aux 行 effect_id = item_id,
|
||||
# 须命中 TreasureItemTable 活行(非 retired 非 deferred——罗盘退役/失义件不投放)。retired 行被引用=退役
|
||||
# 后仍投放(连带错误),同拦。
|
||||
|
||||
|
||||
## ①b RewardTable aux 投放行双表校验(reward_dangling_treasure):aux 行 effect_id = item_id,
|
||||
## 须命中 TreasureItemTable 活行(非 retired 非 deferred——罗盘退役/失义件不投放)。retired 行被引用=退役
|
||||
## 后仍投放(连带错误),同拦。
|
||||
func _cv_reward_treasure(loaded: Dictionary, rt: RewardTable, push_diag: Callable) -> void:
|
||||
var treasure_live := {}
|
||||
var treasure_tab = loaded.get("treasure_items")
|
||||
if treasure_tab != null:
|
||||
@@ -39,27 +61,36 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void:
|
||||
continue
|
||||
if not treasure_live.has(treasure_eff):
|
||||
push_diag.call("error", "reward_dangling_treasure", "RewardTable %s aux 行引用无效道具 %s(retired/deferred/不存在): %s" % [row.get("reward_id"), treasure_eff, "双表校验"])
|
||||
# ①c F2 投放维度覆盖校验不启用:挖格不掉道具,秘宝
|
||||
# 唯一来路=三选一共享池〔节点/关卡完成〕;共享池为「抽到道具位再动态掷定具体道具」,维度覆盖不可
|
||||
# 静态校验。
|
||||
# ② SettlementTable.effect_pool_map 悬空
|
||||
|
||||
|
||||
## ② SettlementTable.effect_pool_map 悬空
|
||||
func _cv_poolmap(st_tab: SettlementTable, effect_ids: Dictionary, push_diag: Callable) -> void:
|
||||
for eff in st_tab.core.effect_pool_map.keys():
|
||||
if not effect_ids.has(eff):
|
||||
push_diag.call("error", "poolmap_dangling", "SettlementTable.effect_pool_map 悬空 effect_id: %s" % eff)
|
||||
# ③ source_label_map 键集封闭性(键 = 增益状态 source_class ∪ {enemy_action, hero_attack, seal_miss})
|
||||
|
||||
|
||||
## ③ source_label_map 键集封闭性(键 = 增益状态 source_class ∪ {enemy_action, hero_attack, seal_miss})
|
||||
func _cv_source_labels(st_tab: SettlementTable, push_diag: Callable) -> void:
|
||||
var required_sources := ["safe_cell", "seal_hit", "mine_hit", "clear_reward", "hero_attack", "enemy_action", "seal_miss"]
|
||||
for s in required_sources:
|
||||
if not st_tab.core.source_label_map.has(s):
|
||||
push_diag.call("error", "segorder_violation", "source_label_map 缺必需键: %s" % s)
|
||||
# ⑤ 技能池引用完整性(敌人技能池批——敌人表池 × 技能表;独立入口 validate_skill_pool_links 供冒烟直验)
|
||||
|
||||
|
||||
## ⑤ 技能池引用完整性(敌人技能池批——敌人表池 × 技能表;独立入口 validate_skill_pool_links 供冒烟直验)
|
||||
func _cv_skill_pool(loaded: Dictionary, push_diag: Callable) -> void:
|
||||
var en: EnemyTable = loaded.get("enemy")
|
||||
var skt = loaded.get("skill")
|
||||
if skt != null and en != null:
|
||||
for e in validate_skill_pool_links(skt, en, loaded.get("status_effects")):
|
||||
push_diag.call("error", e.category, e.message)
|
||||
# ⑥ 商店货架 × 冒险共享池跨表校验(M-4——次数上限域):
|
||||
# pool_shelf kind 须命中 adventure.CURIO_KINDS 且为有限库存种(cap ∈ [1,99]——gp 无上限种/牌库外种
|
||||
# 入货架 = 库存账本无锚,加载致命;gp 袋负交易切边/规则改写型特性三轴切边由 ShopTable 词表封闭另拦)
|
||||
|
||||
|
||||
## ⑥ 商店货架 × 冒险共享池跨表校验(M-4——次数上限域):
|
||||
## pool_shelf kind 须命中 adventure.CURIO_KINDS 且为有限库存种(cap ∈ [1,99]——gp 无上限种/牌库外种
|
||||
## 入货架 = 库存账本无锚,加载致命;gp 袋负交易切边/规则改写型特性三轴切边由 ShopTable 词表封闭另拦)
|
||||
func _cv_shelf_pool(loaded: Dictionary, push_diag: Callable) -> void:
|
||||
var adv = loaded.get("adventure")
|
||||
var shp = loaded.get("shop")
|
||||
if adv != null and shp != null:
|
||||
@@ -78,34 +109,42 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void:
|
||||
if quota < 1:
|
||||
push_diag.call("error", "shelf_zero_quota", "ShopTable.pool_shelf kind %s 令牌配额为 0(牌库空种入货架 = 库存无锚)" % kind)
|
||||
|
||||
# ⑦ 计数器引用闭合(卡② V9——读点 id 必命中同名写入行,否则加载致命):
|
||||
# 声明集 = feats + treasure 各行 triggers 内 kind=counter_add 的 do.counter_id;
|
||||
# 读点 = when fact=counter 的 id、do.value_counter、kind=counter_spend 的 counter_id。
|
||||
var feats_tab = loaded.get("feats")
|
||||
|
||||
## ⑦ 计数器引用闭合(卡② V9——读点 id 必命中同名写入行,否则加载致命):
|
||||
## 声明集 = feats + treasure 各行 triggers 内 kind=counter_add 的 do.counter_id;
|
||||
## 读点 = when fact=counter 的 id、do.value_counter、kind=counter_spend 的 counter_id。
|
||||
func _cv_counter_closure(loaded: Dictionary, push_diag: Callable) -> void:
|
||||
var trig_rows: Array[Variant] = []
|
||||
var feats_tab = loaded.get("feats")
|
||||
if feats_tab != null:
|
||||
for r in feats_tab.rows:
|
||||
trig_rows.append(r)
|
||||
var treasure_tab = loaded.get("treasure_items")
|
||||
if treasure_tab != null:
|
||||
for r in treasure_tab.rows:
|
||||
trig_rows.append(r)
|
||||
var counter_decls := _counter_decls_of(trig_rows)
|
||||
_cv_counter_reads(trig_rows, counter_decls, push_diag)
|
||||
|
||||
|
||||
## 计数器声明集收集:任一 do.counter_id 行(counter_add 直写、forgive_seal_miss 经 _apply_counter_hit 写——皆写入行)
|
||||
func _counter_decls_of(trig_rows: Array) -> Dictionary:
|
||||
var counter_decls := {}
|
||||
for row in trig_rows:
|
||||
for tr_v in (row.get("triggers", []) as Array):
|
||||
var dd: Dictionary[Variant, Variant] = (tr_v as Dictionary).get("do", {}) as Dictionary[Variant, Variant]
|
||||
# 声明集=任一 do.counter_id 行(counter_add 直写、forgive_seal_miss 经 _apply_counter_hit 写——皆写入行)
|
||||
if dd.has("counter_id"):
|
||||
counter_decls[String(dd.counter_id)] = true
|
||||
return counter_decls
|
||||
|
||||
|
||||
## 计数器读点校验:when counter id/do.value_counter/counter_spend 的 counter_id 必命中声明集
|
||||
func _cv_counter_reads(trig_rows: Array, counter_decls: Dictionary, push_diag: Callable) -> void:
|
||||
for row in trig_rows:
|
||||
var rid := String(row.get("feat_id", row.get("item_id", "")))
|
||||
for tr_v in (row.get("triggers", []) as Array):
|
||||
var tr: Dictionary[Variant, Variant] = tr_v as Dictionary[Variant, Variant]
|
||||
for c_v in (tr.get("when", []) as Array):
|
||||
var c: Dictionary[Variant, Variant] = c_v as Dictionary[Variant, Variant]
|
||||
if String(c.get("fact", "")) == "counter" and c.has("id"):
|
||||
var rd := String(c.id)
|
||||
if not counter_decls.has(rd):
|
||||
push_diag.call("error", "counter_dangling_ref", "%s when counter 读点无同名写入行: %s(引用闭合)" % [rid, rd])
|
||||
_check_counter_when(rid, tr, counter_decls, push_diag)
|
||||
var do2: Dictionary[Variant, Variant] = tr.get("do", {}) as Dictionary[Variant, Variant]
|
||||
if do2.has("value_counter") and not counter_decls.has(String(do2.value_counter)):
|
||||
push_diag.call("error", "counter_dangling_ref", "%s value_counter 读点无同名写入行: %s" % [rid, String(do2.value_counter)])
|
||||
@@ -113,6 +152,16 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void:
|
||||
push_diag.call("error", "counter_dangling_ref", "%s counter_spend 无同名写入行: %s" % [rid, String(do2.counter_id)])
|
||||
|
||||
|
||||
## when counter 读点校验(fact=counter 条件的 id 必命中声明集)
|
||||
func _check_counter_when(rid: String, tr: Dictionary, counter_decls: Dictionary, push_diag: Callable) -> void:
|
||||
for c_v in (tr.get("when", []) as Array):
|
||||
var c: Dictionary[Variant, Variant] = c_v as Dictionary[Variant, Variant]
|
||||
if String(c.get("fact", "")) == "counter" and c.has("id"):
|
||||
var rd := String(c.id)
|
||||
if not counter_decls.has(rd):
|
||||
push_diag.call("error", "counter_dangling_ref", "%s when counter 读点无同名写入行: %s(引用闭合)" % [rid, rd])
|
||||
|
||||
|
||||
# 池模板 × 技能行跨表校验(加载期致命;返回错误列表——ConfigDB 诊断通道与冒烟直验共用):
|
||||
# skill_id 悬空致命 + 池构成纪律(每池恰一行普攻、增益技 ≤1、妨害技 ≤1——proposal §2.1 结构保证 +
|
||||
# proposal-status-vocabulary §1.2)+ 施放类 side 守卫(增益引用 enemy 侧 / 妨害引用 hero 侧状态行)。
|
||||
@@ -141,33 +190,25 @@ func validate_skill_pool_links(skill_table: SkillTable, enemy_table: EnemyTable,
|
||||
errs.append_array(_pool_cast_side_errs(skill_table, status_table)) # 施放类 side 守卫(buff→敌方侧/debuff→主角侧,防反向挂接——全表恰一次)
|
||||
for row in enemy_table.rows:
|
||||
var eid: String = String(row.get("enemy_id", ""))
|
||||
var basic_n := 0
|
||||
var buff_n := 0
|
||||
var debuff_n := 0
|
||||
var uncond_special_n := 0
|
||||
var pool_n := {"basic": 0, "buff": 0, "debuff": 0, "uncond_special": 0}
|
||||
for entry in row.get("pool", []):
|
||||
var sid: String = String(entry.get("skill_id", ""))
|
||||
if not skill_types.has(sid):
|
||||
errs.append({"category": "dangling_skill", "message": "EnemyTable %s 池引用不存在的 skill_id: %s(悬空致命)" % [eid, sid]})
|
||||
continue
|
||||
match skill_types[sid]:
|
||||
"basic":
|
||||
basic_n += 1
|
||||
"buff":
|
||||
buff_n += 1
|
||||
"debuff":
|
||||
debuff_n += 1
|
||||
if pool_n.has(skill_types[sid]):
|
||||
pool_n[skill_types[sid]] += 1
|
||||
# 无条件特殊技计数(普攻不计;trigger 缺省行计数——触发条件批 2026-10-26 池构成纪律)
|
||||
if skill_types[sid] != "basic" and not (skill_table.row_by_id(sid) if skill_table.has_method("row_by_id") else {}).is_empty():
|
||||
if skill_types[sid] != "basic":
|
||||
var srow_c: Dictionary[Variant, Variant] = skill_table.row_by_id(sid) if skill_table.has_method("row_by_id") else {}
|
||||
if not srow_c.has("trigger"):
|
||||
uncond_special_n += 1
|
||||
if basic_n != 1:
|
||||
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池普攻行数 ≠ 1 (%d)——普攻冷却恒 0 = 空池不可能的结构前提" % [eid, basic_n]})
|
||||
if buff_n > 1:
|
||||
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池增益技 > 1 (%d)——每池增益 ≤1 纪律(纠偏三)" % [eid, buff_n]})
|
||||
if debuff_n > 1:
|
||||
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池妨害技 > 1 (%d)——每池妨害 ≤1 纪律(新效应词汇批 2026-09-19)" % [eid, debuff_n]})
|
||||
if uncond_special_n < 1:
|
||||
if not srow_c.is_empty() and not srow_c.has("trigger"):
|
||||
pool_n["uncond_special"] += 1
|
||||
if pool_n["basic"] != 1:
|
||||
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池普攻行数 ≠ 1 (%d)——普攻冷却恒 0 = 空池不可能的结构前提" % [eid, pool_n["basic"]]})
|
||||
if pool_n["buff"] > 1:
|
||||
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池增益技 > 1 (%d)——每池增益 ≤1 纪律(纠偏三)" % [eid, pool_n["buff"]]})
|
||||
if pool_n["debuff"] > 1:
|
||||
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池妨害技 > 1 (%d)——每池妨害 ≤1 纪律(新效应词汇批 2026-09-19)" % [eid, pool_n["debuff"]]})
|
||||
if pool_n["uncond_special"] < 1:
|
||||
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池无条件特殊技行数 = 0——每池至少一行无条件特殊技纪律(触发条件批 2026-10-26)" % [eid]})
|
||||
return errs
|
||||
@@ -43,11 +43,7 @@ func resolve_enemy_rowset(L: int, D: int, row_kind: String) -> Array[Dictionary]
|
||||
return []
|
||||
var rows: Array[Dictionary] = []
|
||||
rows.assign(_enemy_rows_get.call())
|
||||
var level_keys: Array[int] = []
|
||||
for row in rows:
|
||||
var lv := int(row.get("level_index", 0))
|
||||
if not level_keys.has(lv):
|
||||
level_keys.append(lv)
|
||||
var level_keys: Array[int] = _level_keys_of(rows)
|
||||
if level_keys.is_empty():
|
||||
_diag.call("fatal", "structural", "enemy 行集为空——锚行校验应已拦截")
|
||||
return []
|
||||
@@ -58,12 +54,7 @@ func resolve_enemy_rowset(L: int, D: int, row_kind: String) -> Array[Dictionary]
|
||||
_warn_resolve("enemy", "resolve_level_clamp", "enemy 请求 (%d,%d) clamp 解析(超出最大关卡取最大值)→ 关卡 %d" % [L, D, l_star], l_star, D)
|
||||
elif l_star != L:
|
||||
_warn_resolve("enemy", "resolve_level_ceil", "enemy 请求 (%d,%d) ceil 解析(向上取整保证偏难)→ 关卡 %d" % [L, D, l_star], l_star, D)
|
||||
var band_keys: Array[int] = []
|
||||
for row in rows:
|
||||
if int(row.get("level_index", 0)) == l_star and String(row.get("row_kind", "")) == row_kind:
|
||||
var bv := int(row.get("depth_band", -1))
|
||||
if not band_keys.has(bv):
|
||||
band_keys.append(bv)
|
||||
var band_keys: Array[int] = _band_keys_of(rows, l_star, row_kind)
|
||||
if band_keys.is_empty():
|
||||
return []
|
||||
band_keys.sort()
|
||||
@@ -73,6 +64,32 @@ func resolve_enemy_rowset(L: int, D: int, row_kind: String) -> Array[Dictionary]
|
||||
_warn_resolve("enemy", "resolve_depth_clamp", "enemy 请求 (%d,%d) clamp 解析(超出顶带取顶带)→ (%d,band %d)" % [L, D, l_star, d_star], l_star, d_star)
|
||||
elif d_star != D:
|
||||
_warn_resolve("enemy", "resolve_depth_ceil", "enemy 请求 (%d,%d) ceil 解析(向上取整保证偏难)→ (%d,band %d)" % [L, D, l_star, d_star], l_star, d_star)
|
||||
return _band_rows(rows, l_star, row_kind, d_star)
|
||||
|
||||
|
||||
## 行集关卡键收集(纯行扫描——告警发射序不变)
|
||||
func _level_keys_of(rows: Array) -> Array[int]:
|
||||
var level_keys: Array[int] = []
|
||||
for row in rows:
|
||||
var lv := int(row.get("level_index", 0))
|
||||
if not level_keys.has(lv):
|
||||
level_keys.append(lv)
|
||||
return level_keys
|
||||
|
||||
|
||||
## 带键收集(纯行扫描——l_star × row_kind 的 depth_band 去重集)
|
||||
func _band_keys_of(rows: Array, l_star: int, row_kind: String) -> Array[int]:
|
||||
var band_keys: Array[int] = []
|
||||
for row in rows:
|
||||
if int(row.get("level_index", 0)) == l_star and String(row.get("row_kind", "")) == row_kind:
|
||||
var bv := int(row.get("depth_band", -1))
|
||||
if not band_keys.has(bv):
|
||||
band_keys.append(bv)
|
||||
return band_keys
|
||||
|
||||
|
||||
## 带行收集(纯行扫描——命中带全部行、按表文件行序)
|
||||
func _band_rows(rows: Array, l_star: int, row_kind: String, d_star: int) -> Array[Dictionary]:
|
||||
var all_out: Array[Dictionary] = []
|
||||
for row in rows:
|
||||
if int(row.get("level_index", 0)) == l_star and String(row.get("row_kind", "")) == row_kind \
|
||||
@@ -108,11 +125,7 @@ func _resolve_progress(rows: Array, L: int, D: int, table_id: String) -> Variant
|
||||
if L < 1 or D < 0:
|
||||
_diag.call("fatal", "request_oob", "%s 请求越域 (%d,%d)——进度坐标低于合法域(关卡序号≥1、节点深度≥0)" % [table_id, L, D])
|
||||
return {}
|
||||
var level_keys: Array[int] = []
|
||||
for row in rows:
|
||||
var lv := int(row.get("level_index", 0))
|
||||
if not level_keys.has(lv):
|
||||
level_keys.append(lv)
|
||||
var level_keys: Array[int] = _level_keys_of(rows)
|
||||
if level_keys.is_empty():
|
||||
_diag.call("fatal", "structural", "%s 行集为空——锚行校验应已拦截" % table_id)
|
||||
return {}
|
||||
@@ -121,40 +134,59 @@ func _resolve_progress(rows: Array, L: int, D: int, table_id: String) -> Variant
|
||||
var l_clamped := l_star < 0
|
||||
if l_clamped:
|
||||
l_star = int(level_keys[-1]) # 超出最大行 → 取最大行封顶(可取范围内向上取整,超出用最大值)
|
||||
var depth_keys: Array[int] = _depth_keys_of(rows, l_star)
|
||||
depth_keys.sort()
|
||||
var d_star := _ceil_key(depth_keys, D)
|
||||
var d_clamped := d_star < 0
|
||||
if d_clamped:
|
||||
d_star = int(depth_keys[-1])
|
||||
var missing := _missing_label(l_clamped, d_clamped, l_star != L, d_star != D)
|
||||
if missing != "":
|
||||
_emit_missing_warning(table_id, missing, [l_star, d_star], [L, D])
|
||||
for row in rows:
|
||||
if int(row.get("level_index", 0)) == l_star and int(row.get("node_depth", 0)) == d_star:
|
||||
return row
|
||||
return {}
|
||||
|
||||
|
||||
## 层键收集(纯行扫描——l_star 行的 node_depth 去重集)
|
||||
func _depth_keys_of(rows: Array, l_star: int) -> Array[int]:
|
||||
var depth_keys: Array[int] = []
|
||||
for row in rows:
|
||||
if int(row.get("level_index", 0)) == l_star:
|
||||
var dv := int(row.get("node_depth", 0))
|
||||
if not depth_keys.has(dv):
|
||||
depth_keys.append(dv)
|
||||
depth_keys.sort()
|
||||
var d_star := _ceil_key(depth_keys, D)
|
||||
var d_clamped := d_star < 0
|
||||
if d_clamped:
|
||||
d_star = int(depth_keys[-1])
|
||||
var missing := ""
|
||||
return depth_keys
|
||||
|
||||
|
||||
## 解析偏移形态标签(clamp/ceil × level/depth/both 六形态——空串 = 精确命中;
|
||||
## l_ceil/d_ceil = 解析值 ≠ 请求值)
|
||||
func _missing_label(l_clamped: bool, d_clamped: bool, l_ceil: bool, d_ceil: bool) -> String:
|
||||
var label := ""
|
||||
if l_clamped and d_clamped:
|
||||
missing = "both_clamp"
|
||||
label = "both_clamp"
|
||||
elif l_clamped:
|
||||
missing = "level_clamp"
|
||||
label = "level_clamp"
|
||||
elif d_clamped:
|
||||
missing = "depth_clamp"
|
||||
elif l_star != L and d_star != D:
|
||||
missing = "both_ceil"
|
||||
elif l_star != L:
|
||||
missing = "level_ceil"
|
||||
elif d_star != D:
|
||||
missing = "depth_ceil"
|
||||
if missing != "":
|
||||
var dedup_key := "%s|%s|%d|%d" % [table_id, missing, l_star, d_star]
|
||||
if not _warn_dedup.has(dedup_key):
|
||||
_warn_dedup[dedup_key] = true
|
||||
var verb := "clamp 解析(超出最大行取最大值)" if missing.contains("clamp") else "ceil 解析(向上取整保证偏难)"
|
||||
_diag.call("warning", "resolve_" + missing, "%s 请求 (%d,%d) %s → (%d,%d) [%s]——2026-09-18 拍板" % [table_id, L, D, verb, l_star, d_star, missing])
|
||||
for row in rows:
|
||||
if int(row.get("level_index", 0)) == l_star and int(row.get("node_depth", 0)) == d_star:
|
||||
return row
|
||||
return {}
|
||||
label = "depth_clamp"
|
||||
elif l_ceil and d_ceil:
|
||||
label = "both_ceil"
|
||||
elif l_ceil:
|
||||
label = "level_ceil"
|
||||
elif d_ceil:
|
||||
label = "depth_ceil"
|
||||
return label
|
||||
|
||||
|
||||
## 偏移告警发射(四元组去重注册表语义逐字——R1 既有去重;
|
||||
## req/resolved = [L, D]/[l_star, d_star] 进度坐标对)
|
||||
func _emit_missing_warning(table_id: String, missing: String, resolved: Array, req: Array) -> void:
|
||||
var dedup_key := "%s|%s|%d|%d" % [table_id, missing, resolved[0], resolved[1]]
|
||||
if not _warn_dedup.has(dedup_key):
|
||||
_warn_dedup[dedup_key] = true
|
||||
var verb := "clamp 解析(超出最大行取最大值)" if missing.contains("clamp") else "ceil 解析(向上取整保证偏难)"
|
||||
_diag.call("warning", "resolve_" + missing, "%s 请求 (%d,%d) %s → (%d,%d) [%s]——2026-09-18 拍板" % [table_id, req[0], req[1], verb, resolved[0], resolved[1], missing])
|
||||
|
||||
|
||||
# 取 sorted_keys 中第一个 >= request 的键;无(请求高于最大行)返回 -1 → 调用方 Fatal
|
||||
|
||||
Reference in new issue
Block a user