diff --git a/game/tests/static_scan.sh b/game/tests/static_scan.sh index a104e618..67258794 100644 --- a/game/tests/static_scan.sh +++ b/game/tests/static_scan.sh @@ -118,8 +118,6 @@ hits=$(grep -rnE '(^|[^.[:alnum:]_"/])(ConfigDB|RngService)[[:space:]]*\.' "$DIR if [ "$hits" -gt 0 ]; then echo "[static_scan] S5 FAIL: core 裸引用 autoload 全局标识符命中 $hits 处(--script 模式 parse error)"; grep -rnE '(^|[^.[:alnum:]_"/])(ConfigDB|RngService)[[:space:]]*\.' "$DIR_CORE" --include='*.gd' 2>/dev/null | grep -vE '^[^:]+:[0-9]+:[[:space:]]*#'; fail=1; else echo "[static_scan] S5 ok(扫描面 $DIR_CORE 共 $(scan_files "$DIR_CORE") 个 .gd)"; fi fi -# TODO(正式工程落地后精化为扫描配置并升格为硬失败): -# S7 B2/B3/B4 API 符号命中文件 ⊆ B1 文件集(编排唯一性) # ── S11 — 代码注释禁用词 ── # 禁「板级」「门票」「正主」「落籍」「完美排雷」等退役词进注释面(完整词表见术语表解析) @@ -316,4 +314,65 @@ else fi fi +# ── S7 — B1 编排唯一性:B2/B3/B4 API 符号命中文件 ⊆ B1 域文件集 ── +# 规则(architecture.md「静态扫描域 S1–S8 承接清单」S7 行 / B1 AC-43;依赖约束节同款): +# B2/B3/B4 的对外 API 只允许被 B1 域文件集调用——全调用面集中 B1、禁第二调用方。 +# 附带覆盖「A2/B3/B4 零相互直调」:B2/B3/B4 自身文件 ∉ B1 文件集,命中彼家符号即红 +# (同族自调用豁免只放自家符号,彼家符号照红)。 +# 符号清单权威源:architecture.md「模块归属」各表「对外暴露」列——B2(unit_container)14 符号、 +# B3(status_container)5 符号、B4(reward_economy)9 符号;脚本字面值与文档冲突时以文档为准 +# (执行侧字面值豁免,同 S8 手法)。B4 表内 is_mapped 静默布尔投影注明批量工具专用、不在暴露面清单。 +# 扫描面:$DIR_CORE(含 settlement/,B1 文件集在其中)。 +# 判据:文件内出现 `.符号(` 调用形态(符号后紧跟括号,天然词边界;`.my_grant(` 不误伤 `grant`) +# 且该文件 ∉ B1 文件集 → 红。排除注释行与字符串字面量(口径仿 S1/S2/S8 的 grep 排除手法)。 +# 同族自调用豁免:unit_container/status_container/reward_economy 自身文件命中自家符号不算 +# (容器内部互调合法;GDScript 裸调用无 `.` 本就不命中,带 self. 的自家调用由本条兜住)。 +# 误报零容忍:spawn/heal/phase/grant 等通用名撞车同名异对象方法属误报——逐条判真伪后收紧判据 +# 或加行内豁免注记 `s7-b1-exempt`(仿 rng-seed-exempt 惯例),禁放宽符号清单换绿。 +# 自检:扫描面为空即判红(require_scan_face);B1 文件集与三族自家文件缺位即判红 +# (文件被改名=豁免名单失效,宁可红不可静默放行)。 +if require_scan_face "S7" "$DIR_CORE"; then +S7_B1_SET="game/core/battle_orchestrator.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/damage_controller.gd" +S7_MISS=0 +for f in $S7_B1_SET game/core/unit_container.gd game/core/status_container.gd game/core/reward_economy.gd; do + if [ ! -f "$f" ]; then + echo "[static_scan] S7 FAIL: 豁免名单缺位——$f 不存在(路径写错/被改名导致的静默空转)" + S7_MISS=1 + fi +done +if [ "$S7_MISS" -eq 1 ]; then + fail=1 +else + S7_HITS="" + s7_scan() { # s7_scan <族名> <自家文件> <符号交替式> — 扫 core 面,豁免 B1 文件集与自家文件 + local grp="$1" own="$2" syms="$3" f h + while IFS= read -r f; do + case " $S7_B1_SET " in *" $f "*) continue ;; esac + [ "$f" = "$own" ] && continue + h=$(grep -nE "\.($syms)[[:space:]]*\(" "$f" 2>/dev/null \ + | grep -vE '^[0-9]+:[[:space:]]*#' \ + | grep -vE "\"[^\"]*\\.($syms)[[:space:]]*\\([^\"]*\"" \ + | grep -v 's7-b1-exempt' \ + | sed "s|^|[$grp] $f:|") + [ -n "$h" ] && S7_HITS="$S7_HITS$h +" + done < <(find "$DIR_CORE" -name '*.gd' 2>/dev/null | sort) + } + # 符号清单(2026-10-25 对 architecture.md「模块归属」表核录;改符号先改文档再改此处) + s7_scan B2 game/core/unit_container.gd \ + 'spawn|query_alive|rollable_skills|special_skills|check_skill_unlocks|set_pending_action|pending_action|basic_streak|commit_action|tick_cooldowns|apply_damage|heal|default_target|despawn_all' + s7_scan B3 game/core/status_container.gd \ + 'notify_battle_start|apply_effect|query_active|decay_round|notify_battle_end' + s7_scan B4 game/core/reward_economy.gd \ + 'roll_board|query_identity|query_full_snapshot|grant|grant_clear|retire_mapping|polarity_row|phase|weighted_pick' + if [ -n "$S7_HITS" ]; then + echo "[static_scan] S7 FAIL: B2/B3/B4 API 符号越出 B1 文件集命中 $(printf '%s' "$S7_HITS" | grep -c .) 处(编排唯一性,B1 AC-43)" + printf '%s' "$S7_HITS" + fail=1 + else + echo "[static_scan] S7 ok(扫描面 $DIR_CORE 共 $(scan_files "$DIR_CORE") 个 .gd,B2/B3/B4 API 符号全部收敛于 B1 文件集)" + fi +fi +fi + exit $fail