feat(卡② T5): 计数器引用闭合校验 + V9 负面套件 + manifest 登记

- config_db_cross_validator ⑦ 引用闭合:声明集=feats+treasure 各行 do.counter_id,
  读点(when counter id / value_counter / counter_spend counter_id)无同名写入行→加载致命
  (判据只做成员判定,不重写前缀规则——契约 6 两落点同源)
- 新建 counter_prefix_fatal_test.gd(expect1/negative):①meta_ 非法前缀②悬空读点两断言,均正确拒绝
- run_all.gd manifest 登记负面套件
real 数据 config_db_test 26 全绿(闭合零误伤);insurance_used 经 forgive_seal_miss 行 do.counter_id 声明纳入。
This commit is contained in:
DKChan committed 2026-10-20 23:12:29 +08:00
1 parent d402ea143a
commit 4c5bdbc9aa
4 files changed
+107

No files matched your search

+34
View File
@@ -109,6 +109,40 @@ 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")
var trig_rows: Array = []
if feats_tab != null:
for r in feats_tab.rows:
trig_rows.append(r)
if treasure_tab != null:
for r in treasure_tab.rows:
trig_rows.append(r)
var counter_decls := {}
for row in trig_rows:
for tr_v in (row.get("triggers", []) as Array):
var dd: Dictionary = (tr_v as Dictionary).get("do", {}) as Dictionary
# 声明集=任一 do.counter_id 行(counter_add 直写、forgive_seal_miss 经 _apply_counter_hit 写——皆写入行)
if dd.has("counter_id"):
counter_decls[String(dd.counter_id)] = true
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 = tr_v as Dictionary
for c_v in (tr.get("when", []) as Array):
var c: Dictionary = c_v as Dictionary
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])
var do2: Dictionary = tr.get("do", {}) as Dictionary
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)])
if String(do2.get("kind", "")) == "counter_spend" and do2.has("counter_id") and not counter_decls.has(String(do2.counter_id)):
push_diag.call("error", "counter_dangling_ref", "%s counter_spend 无同名写入行: %s" % [rid, String(do2.counter_id)])
# 池模板 × 技能行跨表校验(加载期致命;返回错误列表——ConfigDB 诊断通道与冒烟直验共用):
# skill_id 悬空致命 + 池构成纪律(每池恰一行普攻、增益技 ≤1、妨害技 ≤1——proposal §2.1 结构保证 +