feat(迭代27 T5): 波五a 类型闸拉起+存量收口——审计器 0 存量+warning 配置在位

P1-6 类型闸(须 T1-T4 全落后——本波按执行顺序锁殿后):
- tools/audit_type_hints.py 新机器闸(V7): R1 无类型容器注解/R2 有值返回缺返回类型/
  R3 参数缺类型三规则+行级 # type-hint: ignore 显式白名单通道;产品码 core/data/presentation
- 存量收口: 368 处显式 Array/Dictionary 非参数化注解→机械参数化(Array[Variant]/
  Dictionary[Variant, Variant] 零行为面);13 处参数真类型(SaveManagerCore/ConfigDb/
  BattleOrchestrator/MinesweeperRules/LevelRunner/SettingsStore 等)
- 白名单豁免四处: board_generator generate/_attempt/legality/_finalize 的 db 参——
  测量面 _BoardRowStub 冒烟夹具鸭子型注入(typed 参拒 stub)
- project.godot 拉起 gdscript/warnings/untyped_declaration=1(V7 源码断言在 smoke _smoke_type_gate)
- static_scan 新 S9 类型闸规则;顺修历史 cd 错位(9-24 布局迁移后 /.. 落 game/ 致守卫
  恒跳过静默空转——修复后 S1-S4/S9 首次实跑全绿)+S1/S2 增 ADR-0007-exempt 标记豁免通道
  (main _seed_value 引擎熵源=文档化豁免面)

验收: 冒烟 1081/0(+V7×2)+run_all 27 套+static_scan 全绿+probe click+render golden+对拍三变体全同
This commit is contained in:
DKChan committed 2026-10-15 01:05:17 +08:00
1 parent 8b9e55673f
commit b605022001
58 files changed
+503 -384

No files matched your search

+2 -2
View File
@@ -88,7 +88,7 @@ func validate() -> Array:
# 池模板(敌人技能池批——单表结构校验;跨表构成纪律在 ConfigDB):
# 非空池 + 条目 {skill_id 非空, weight > 0} + 池内 skill_id 不重复。
# 普攻冷却恒 0(SkillTable schema 锁死)→ 可摇集合恒非空,"空池不可能"由此结构保证。
var pool: Array = row.get("pool", [])
var pool: Array[Variant] = row.get("pool", [])
if pool.is_empty():
errs.append({"category": "domain", "message": "%s pool 空池(禁——技能池行必带池模板)" % eid})
var pool_ids := {}
@@ -96,7 +96,7 @@ func validate() -> Array:
if not (entry_v is Dictionary):
errs.append({"category": "domain", "message": "%s pool 条目非字典: %s" % [eid, str(entry_v)]})
continue
var entry: Dictionary = entry_v
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})
+2 -2
View File
@@ -24,7 +24,7 @@
class_name FeatTable
extends Resource
@export var rows: Array = [] # Array[Dictionary] 键 feat_id
@export var rows: Array[Variant] = [] # Array[Dictionary] 键 feat_id
## 稀有度词表(四级——与令牌池 TIERS 同名词族;令牌档 = rarity 直映射)
const RARITIES := ["common", "excellent", "rare", "legendary"]
@@ -37,7 +37,7 @@ const TRIGGER_KEYS := ["on", "when", "do"]
func validate() -> Array:
var errs: Array = []
var errs: Array[Variant] = []
if rows.is_empty():
return errs # 空表合法态(T1 基建先行——逐件填行)
var seen := {}
+1 -1
View File
@@ -62,7 +62,7 @@ func validate() -> Array:
continue
# 类型轴:武器=attack_type 域、甲=armor_type 域;每型恰一路线(三武器三甲各一)
var axis := String(row.get("type_axis", ""))
var domain: Array = ATTACK_TYPES if kind == "weapon" else ARMOR_TYPES
var domain: Array[Variant] = ATTACK_TYPES if kind == "weapon" else ARMOR_TYPES
if not domain.has(axis):
errs.append({"category": "enum", "message": "%s type_axis 出域 %s: %s" % [rid, domain, axis]})
var type_key := "%s|%s" % [kind, axis]
+4 -4
View File
@@ -49,7 +49,7 @@ func validate() -> Array:
# 模板池(关卡节点图生成 v0.4——M-3 迭代 5 T5):row.templates 非空 = 池形态,池内全模板加载期校验
# (违规致命——不只抽中的那张);空 = 旧单图形态(row.nodes 直读,向后兼容零迁移)。
var node_lists: Array[Array] = [] # [ [上下文标签, nodes 数组], ... ]
var templates: Array = row.get("templates", [])
var templates: Array[Variant] = row.get("templates", [])
if templates.is_empty():
node_lists.append(["", row.get("nodes", [])])
else:
@@ -115,7 +115,7 @@ func _validate_graph(L: int, nodes: Array, is_graph: bool, tag: String = "") ->
var nexts: Array[Array] = [] # 每节点出边 id 数组
var id_to_idx := {}
for i in nodes.size():
var node: Dictionary = nodes[i]
var node: Dictionary[Variant, Variant] = nodes[i]
var nid: int = int(node.get("node_depth", i)) if is_graph else i
if is_graph and not node.has("node_depth"):
errs.append({"category": "structural", "message": "level %d%s 图形态节点[%d] 缺 node_depth(全节点须编号化)" % [L, tag, i]})
@@ -190,7 +190,7 @@ func _validate_graph(L: int, nodes: Array, is_graph: bool, tag: String = "") ->
errs.append({"category": "structural", "message": "level %d 节点 %d 沿连线不可达 Boss" % [L, ids[i]]})
# ⑧ U2 目检新规则(迭代 5 返工批 2026-09-29):BFS 同深度层节点数上限 5(分支宽度天花板)
var layer_of := {}
var bfs: Array = [entries[0]]
var bfs: Array[Variant] = [entries[0]]
layer_of[entries[0]] = 0
while not bfs.is_empty():
var cur: int = bfs.pop_front()
@@ -214,7 +214,7 @@ func _reachable(start: int, adj: Array, n: int) -> Dictionary:
var seen := {}
if start < 0 or start >= n:
return seen
var queue: Array = [start]
var queue: Array[Variant] = [start]
seen[start] = true
while not queue.is_empty():
var cur: int = queue.pop_front()
+4 -4
View File
@@ -27,7 +27,7 @@
class_name MetaTreeTable
extends Resource
@export var rows: Array = [] # Array[Dictionary] 键 node_id
@export var rows: Array[Variant] = [] # Array[Dictionary] 键 node_id
const VEINS := ["spine", "forge", "keystone", "attune"]
const KINDS := ["stat", "rule", "unlock", "socket", "fork", "easter"]
@@ -47,7 +47,7 @@ const FORK_GROUPS := ["fg_posture", "fg_sense", "fg_economy"]
func validate() -> Array:
var errs: Array = []
var errs: Array[Variant] = []
if rows.is_empty():
return errs # 空表合法态(建表先行——内容随换代批落)
var seen := {}
@@ -81,7 +81,7 @@ func validate() -> Array:
var price := int(row.get("price", 0))
if price < 0 or price > 9999:
errs.append({"category": "domain", "message": "%s price 越域 [0,9999]: %d" % [nid, price]})
var fx: Dictionary = row.get("effects", {}) as Dictionary
var fx: Dictionary[Variant, Variant] = row.get("effects", {}) as Dictionary
for ek in fx.keys():
if not (STAT_KEYS.has(String(ek)) or RULE_KEYS.has(String(ek))):
errs.append({"category": "zero_flat", "message": "%s effects 键出域(stat 线/rule 词表封闭): %s" % [nid, String(ek)]})
@@ -100,7 +100,7 @@ func validate() -> Array:
if not fx.is_empty():
errs.append({"category": "cross_field", "message": "%s fork 行主 effects 须空(效果只放 a/b 双键)" % nid})
for skey in ["effects_a", "effects_b"]:
var sd: Dictionary = row.get(skey, {}) as Dictionary
var sd: Dictionary[Variant, Variant] = row.get(skey, {}) as Dictionary
if sd.is_empty():
errs.append({"category": "domain", "message": "%s %s 缺失或空(双态节点须双侧效果)" % [nid, skey]})
for k in sd.keys():
+1 -1
View File
@@ -83,7 +83,7 @@ func validate() -> Array:
if d < 1 or d > 99 or (i > 0 and d >= int(roll_tier_discounts[i - 1])):
errs.append({"category": "cross_field", "message": "roll_tier_discounts[%d] 越域/非降序(档位不设零档): %d" % [i, d]})
for atk_type in ATTACK_TYPE_KEYS:
var row: Dictionary = armor_effectiveness.get(atk_type, {})
var row: Dictionary[Variant, Variant] = armor_effectiveness.get(atk_type, {})
for armor in ARMOR_KEYS:
var pct: int = int(row.get(armor, -99))
if pct < -3 or pct > 3:
+1 -1
View File
@@ -57,7 +57,7 @@ func validate() -> Array:
errs.append({"category": "cross_field", "message": "gp_drops %s 违反 roll_min(%d) ≤ mean(%d) ≤ roll_max(%d)——均值恒等期望锚" % [tier, rmin, mean, rmax]})
# level_gp_bonus / price_level_bonus:level_index 连续从 1 递增 + 加值域
for family in ["level_gp_bonus", "price_level_bonus"]:
var arr: Array = get(family)
var arr: Array[Variant] = get(family)
var seen := {}
for row in arr:
var li: int = int(row.get("level_index", 0))
+8 -8
View File
@@ -32,7 +32,7 @@
class_name TreasureItemTable
extends Resource
@export var rows: Array = [] # Array[Dictionary] 键 item_id
@export var rows: Array[Variant] = [] # Array[Dictionary] 键 item_id
# 计数键封闭枚举(原 AttunementGrowthTable.COUNTER_KEYS——迭代 21 T5 谓词表退役后就地内联;
# 迭代 22 T1 换代:clear_count(旧工坊按关累计渠道)与 C6 通关口径(通关=打通整局冒险)不符,
@@ -85,7 +85,7 @@ const KEY_WHITELIST := ["item_id", "display_name", "dimension_tags", "effect_kin
func validate() -> Array:
var errs: Array = []
var errs: Array[Variant] = []
if rows.is_empty():
# 空表合法态(2026-09-17 用户拍板——切片期无道具内容;F2 覆盖检查仅对非空清单执行)
return errs
@@ -104,13 +104,13 @@ func validate() -> Array:
var retired: bool = bool(row.get("retired", false))
var deferred: bool = bool(row.get("deferred", false))
# dimension_tags:五维词表 + 非空(退役/失义行仍须登记维度——语义面保留)
var tags: Array = row.get("dimension_tags", [])
var tags: Array[Variant] = row.get("dimension_tags", [])
if tags.is_empty():
errs.append({"category": "domain", "message": "%s dimension_tags 空" % iid})
for tag in tags:
if not DIMENSIONS.has(String(tag)):
errs.append({"category": "enum", "message": "%s dimension_tag 出域 %s: %s" % [iid, str(DIMENSIONS), str(tag)]})
var kinds: Array = row.get("effect_kinds", [])
var kinds: Array[Variant] = row.get("effect_kinds", [])
var target: String = String(row.get("target", "none"))
if not TARGETS.has(target):
errs.append({"category": "enum", "message": "%s target 出域 %s: %s" % [iid, str(TARGETS), target]})
@@ -217,7 +217,7 @@ func validate() -> Array:
if not kind_seen.has("ap_charge_safe") and charge_n != 0:
errs.append({"category": "cross_field", "message": "%s 非 ap_charge_safe 行 charge_n 应 0,实得 %d" % [iid, charge_n]})
# unlock_requires 谓词行:空 = 已解锁(首批全解锁合法态,底稿 §2.2);非空键 ∈ 计数键封闭枚举
var ureqs: Array = row.get("unlock_requires", [])
var ureqs: Array[Variant] = row.get("unlock_requires", [])
var ukeys := {}
for req in ureqs:
var ukey: String = String(req.get("key", ""))
@@ -234,7 +234,7 @@ func validate() -> Array:
# 同调行词表(form 非空)——装配覆盖层/效果接线/同调位上限的唯一取数口径(迭代 11 第①批 T1)
func attunement_rows() -> Array:
var out: Array = []
var out: Array[Variant] = []
for row in rows:
if String(row.get("form", "")) != "" and not bool(row.get("retired", false)) and not bool(row.get("deferred", false)):
out.append(row)
@@ -244,7 +244,7 @@ func attunement_rows() -> Array:
# 过渡行词表(form 空 = 切片期过渡形态活行)——掉落投放/停点池/过渡覆盖面的取数口径;
# 同调行永不入过渡渠道。T7 退役面切换后本口径随渠道消亡。
func transitional_rows() -> Array:
var out: Array = []
var out: Array[Variant] = []
for row in rows:
if String(row.get("form", "")) == "" and not bool(row.get("retired", false)) and not bool(row.get("deferred", false)):
out.append(row)
@@ -253,7 +253,7 @@ func transitional_rows() -> Array:
# 活行词表(非 retired 非 deferred)——投放校验/奖励节点池/F2 覆盖面的唯一取数口径
func active_rows() -> Array:
var out: Array = []
var out: Array[Variant] = []
for row in rows:
if not bool(row.get("retired", false)) and not bool(row.get("deferred", false)):
out.append(row)