merge: 禁写变更记录批入库——规约/AGENTS/闸壳/技能四层指令面废除文档内变更日志(gdd_lint R4 反转为禁设+06b 新闸+三工具去豁免),48 份文档版本历史节与 game/README 流水段删除、代码注释 900 余行批次标签清偿(约束句保留、去注释零触碰自证)、卡片「修订记录」块改「审查回写要点」(--docs 闸 dafc5540 六件全绿+变异验证三态真拦:版本历史/修订记录/修改记录)

# Conflicts:
#	game/tests/static_scan.sh
This commit is contained in:
DKChan committed 2026-10-20 05:52:25 +08:00
commit c5200b6be9
235 files changed
+2647 -4167

No files matched your search

+54 -50
View File
@@ -1,53 +1,57 @@
{
"note": "存量基线·首次自动生成;闸况变化时本脚本会判红,需人工确认后重生成;2026-10-17 人工确认重生成 06 条目:af94ce97 把 gdd_lint 记为存量红(01 篇 R5),该红已由 fcb8c3f8 补流程图消除,实测 --docs 域 06 exit=0,故基线改回绿。2026-10-18 新增闸件 08c 流水线闸(tools/pipeline_gate.py 注册上电),手工加入绿记录(roadmap 压缩批实测 --docs 域 exit=0),其余条目原样保留。",
"gates": {
"01 冒烟全量": {
"exit": "0",
"fp": "green"
},
"02 单元套件": {
"exit": "0",
"fp": "green"
},
"03 探针 --click": {
"exit": "0",
"fp": "green"
},
"04 探针 --full": {
"exit": "0",
"fp": "green"
},
"05 渲染闸": {
"exit": "0",
"fp": "green"
},
"06 gdd_lint": {
"exit": "0",
"fp": "green"
},
"07 doc_link": {
"exit": "0",
"fp": "green"
},
"08 doc_term": {
"exit": "0",
"fp": "green"
},
"08c 流水线闸": {
"exit": "0",
"fp": "green"
},
"09 静态扫描": {
"exit": "1",
"fp": "4354b2aa72a7"
},
"10 熵增闸": {
"exit": "0",
"fp": "green"
},
"11 回放确定性对拍": {
"exit": "SKIP",
"fp": "skip"
"note": "存量基线·首次自动生成;闸况变化时本脚本会判红,需人工确认后重生成;2026-10-17 人工确认重生成 06 条目:af94ce97 把 gdd_lint 记为存量红(01 篇 R5),该红已由 fcb8c3f8 补流程图消除,实测 --docs 域 06 exit=0,故基线改回绿。2026-10-18 新增闸件 08c 流水线闸(tools/pipeline_gate.py 注册上电),手工加入绿记录(roadmap 压缩批实测 --docs 域 exit=0),其余条目原样保留。",
"gates": {
"01 冒烟全量": {
"exit": "0",
"fp": "green"
},
"02 单元套件": {
"exit": "0",
"fp": "green"
},
"03 探针 --click": {
"exit": "0",
"fp": "green"
},
"04 探针 --full": {
"exit": "0",
"fp": "green"
},
"05 渲染闸": {
"exit": "0",
"fp": "green"
},
"06 gdd_lint": {
"exit": "0",
"fp": "green"
},
"06b 变更记录节": {
"exit": "0",
"fp": "green"
},
"07 doc_link": {
"exit": "0",
"fp": "green"
},
"08 doc_term": {
"exit": "0",
"fp": "green"
},
"08c 流水线闸": {
"exit": "0",
"fp": "green"
},
"09 静态扫描": {
"exit": "1",
"fp": "4354b2aa72a7"
},
"10 熵增闸": {
"exit": "0",
"fp": "green"
},
"11 回放确定性对拍": {
"exit": "SKIP",
"fp": "skip"
}
}
}
}
+21 -21
View File
@@ -40,44 +40,44 @@ func _suites() -> Array:
# example_test.gd 是协议活样板(tests/unit/README 明示),不进生产 manifest——
# 样板件占位断言跑绿没有信息量,且占据「27 套件」计数。文件保留作模板(同类先例见下 fog_lantern 摘除行)。
{"path": "res://tests/unit/data_config/config_db_test.gd", "expect": 0},
{"path": "res://tests/unit/data_config/forge_table_test.gd", "expect": 0}, # 迭代 23 锻造工坊批 T1——六路线 schema 正反(V1)+特效留空(V11)+几何价格梯(V2 半边)
{"path": "res://tests/unit/data_config/forge_table_test.gd", "expect": 0}, # 六路线 schema 正反(V1)+特效留空(V11)+几何价格梯(V2 半边)
{"path": "res://tests/unit/data_config/config_db_fatal_oob_test.gd", "expect": 1},
{"path": "res://tests/unit/data_config/treasure_target_violation_test.gd", "expect": 1}, # 迭代 7 T1——真视窄域护栏
{"path": "res://tests/unit/data_config/treasure_target_violation_test.gd", "expect": 1}, # 真视窄域护栏
# negative=true=负面套件:坏表/违规输入是它的测试目标,输出里必然带 Parse Error,
# 脚本错误扫描对它属误伤——豁免只放行这一件,且仅限「错误扫描」一闸(退出码与断言下限照判)。
{"path": "res://tests/unit/data_config/reward_dangling_treasure_test.gd", "expect": 1, "negative": true}, # 迭代 7 T1——秘宝双表校验
{"path": "res://tests/unit/data_config/reward_dangling_treasure_test.gd", "expect": 1, "negative": true}, # 秘宝双表校验
{"path": "res://tests/unit/data_config/rng_service_test.gd", "expect": 0},
{"path": "res://tests/unit/core/treasure_items_test.gd", "expect": 0}, # 迭代 7 T7——秘宝结构冻结断言面
{"path": "res://tests/unit/core/breakpoint_resume_test.gd", "expect": 0}, # 迭代 8 卡①/T9——断点往返/续玩确定性/幂等/退出清断点
{"path": "res://tests/unit/core/curio_refresh_paid_test.gd", "expect": 0}, # 2026-10-06——三选一刷新免费双源+GP 付费档(对齐商店 MC-1:价曲线/优先序/停点清零/钱包单口)
{"path": "res://tests/unit/presentation/pacing_queue_test.gd", "expect": 0}, # 迭代 8 卡⑦ AC⑤/T9——排空零分叉/零丢行/延迟双态
{"path": "res://tests/unit/presentation/reward_overlay_test.gd", "expect": 0}, # 迭代 7 hotfix——奖励节点 UI 直验(盲区补漏)
{"path": "res://tests/unit/presentation/forge_workbench_test.gd", "expect": 0}, # 迭代 23 锻造工坊批 T6——工作间两区六路线 UI 直验(V13 自动化前置面)
{"path": "res://tests/unit/core/treasure_items_test.gd", "expect": 0}, # 秘宝结构冻结断言面
{"path": "res://tests/unit/core/breakpoint_resume_test.gd", "expect": 0}, # 断点往返/续玩确定性/幂等/退出清断点
{"path": "res://tests/unit/core/curio_refresh_paid_test.gd", "expect": 0}, # 三选一刷新免费双源+GP 付费档(对齐商店 MC-1:价曲线/优先序/停点清零/钱包单口)
{"path": "res://tests/unit/presentation/pacing_queue_test.gd", "expect": 0}, # AC⑤/T9——排空零分叉/零丢行/延迟双态
{"path": "res://tests/unit/presentation/reward_overlay_test.gd", "expect": 0}, # 奖励节点 UI 直验(盲区补漏)
{"path": "res://tests/unit/presentation/forge_workbench_test.gd", "expect": 0}, # 工作间两区六路线 UI 直验(V13 自动化前置面)
{"path": "res://tests/unit/battle_settlement/damage_math_test.gd", "expect": 0},
{"path": "res://tests/unit/battle_settlement/damage_controller_test.gd", "expect": 0},
{"path": "res://tests/unit/battle_settlement/skill_unlock_test.gd", "expect": 0}, # 迭代14④ T6——Boss 半血解锁技·容器机制(门谓词/闩不可逆/双面过滤/保底不逼锁定/零扰动)
{"path": "res://tests/unit/battle_settlement/skill_unlock_test.gd", "expect": 0}, # Boss 半血解锁技·容器机制(门谓词/闩不可逆/双面过滤/保底不逼锁定/零扰动)
{"path": "res://tests/unit/contracts/system_contracts_test.gd", "expect": 0},
{"path": "res://tests/unit/presentation/ui_font_asset_test.gd", "expect": 0},
{"path": "res://tests/unit/presentation/terrain_tile_sets_test.gd", "expect": 0},
# fog_lantern_wiring_test 随提灯载体行退役摘除(迭代 22 T1——V13 旧验证约束同生死;呈现桥换代断言归 T4 阔视野带参数面)
# 提灯载体行不在册(呈现桥断言归阔视野带参数面)
{"path": "res://tests/unit/core/effect_words_t1_test.gd", "expect": 0},
{"path": "res://tests/unit/core/treasure_effects_test.gd", "expect": 0}, # 迭代 22 秘宝批 T3——新目录 12 件逐件行为(V7 附属表正常/边界/反例)
{"path": "res://tests/unit/core/tree_rules_v11_test.gd", "expect": 0}, # 迭代 21 天赋树批 V11——六键合成行闭集校验 # 迭代 20 T1——词表扩签封闭性(V1:新词在册/出域四路被拒/adventure 计数器往返/mod_eq 预评估口径)
{"path": "res://tests/unit/core/tree_requires_level_test.gd", "expect": 0}, # R1 机制位批 2026-10-13——requires_level 等级阈值前置(schema 四拒一过+判定三态+投影两态)
{"path": "res://tests/unit/core/pool_case_d_test.gd", "expect": 0}, # 迭代 20 T2——池结构案D 行为(V6 令牌三态/V7 曝光槽 2+1 回退/V8 概率上限解耦提前退役)
{"path": "res://tests/unit/core/feat_vouchers_test.gd", "expect": 0}, # 迭代 20 T4——凭证五件+留任二件(V9 路由逐件/F-1 存量账/V10 留任路由+AP 属性轴)
{"path": "res://tests/unit/core/settle_segments_test.gd", "expect": 0}, # 迭代 27 T2——结算五段 Segment(V4:直接 new/编排器接线/真实一轮走段序)
{"path": "res://tests/unit/presentation/modal_flow_test.gd", "expect": 0}, # 迭代 27 T4——模态编排纯逻辑(V6:直接 new/互斥域谓词/三例外锁/演播后排窗)
# ---- L2 集成层(迭代 29 T3 建承载——跨系统用例,与 unit/ 的隔离断言面分工)----
{"path": "res://tests/unit/core/treasure_effects_test.gd", "expect": 0}, # 新目录 12 件逐件行为(V7 附属表正常/边界/反例)
{"path": "res://tests/unit/core/tree_rules_v11_test.gd", "expect": 0}, # V11 六键合成行闭集校验 # V1 词表扩签封闭性(新词在册/出域四路被拒/adventure 计数器往返/mod_eq 预评估口径)
{"path": "res://tests/unit/core/tree_requires_level_test.gd", "expect": 0}, # R1 requires_level 等级阈值前置(schema 四拒一过+判定三态+投影两态)
{"path": "res://tests/unit/core/pool_case_d_test.gd", "expect": 0}, # 池结构案D 行为(V6 令牌三态/V7 曝光槽 2+1 回退/V8 概率上限解耦提前退役)
{"path": "res://tests/unit/core/feat_vouchers_test.gd", "expect": 0}, # 凭证五件+留任二件(V9 路由逐件/F-1 存量账/V10 留任路由+AP 属性轴)
{"path": "res://tests/unit/core/settle_segments_test.gd", "expect": 0}, # 结算五段 Segment(V4:直接 new/编排器接线/真实一轮走段序)
{"path": "res://tests/unit/presentation/modal_flow_test.gd", "expect": 0}, # 模态编排纯逻辑(V6:直接 new/互斥域谓词/三例外锁/演播后排窗)
# ---- L2 集成层(跨系统用例,与 unit/ 的隔离断言面分工)----
{"path": "res://tests/integration/save_rng_roundtrip_test.gd", "expect": 0}, # 存档⇄RNG 快照往返(SaveManager 迁移链 × 断点消费 × RNG 续跑 × 停点态确定性)
# ---- 迭代 29 T5 覆盖补洞:此前零指名的 core 四件与呈现层两件 ----
# ---- core 四件与呈现层两件的直接单测 ----
{"path": "res://tests/unit/core/board_generator_test.gd", "expect": 0}, # 棋盘生成(尺寸合表/数字=八邻雷数/同种子逐位一致/可推理自洽)
{"path": "res://tests/unit/core/minesweeper_rules_test.gd", "expect": 0}, # 排雷规则(装板不变量/挖的 face 规则/揭示推进/封印/目录外拒)
{"path": "res://tests/unit/core/config_db_resolver_test.gd", "expect": 0}, # 配置解析(ceil 偏难语义/行集行序/难度 clamp)
{"path": "res://tests/unit/core/config_db_cross_validator_test.gd", "expect": 0}, # 跨表校验(正向零错/悬空 skill_id/side 反向)
{"path": "res://tests/unit/presentation/hud_presenter_test.gd", "expect": 0}, # HUD 投影文案(逐拒绝原因/兜底不空)
{"path": "res://tests/unit/presentation/battle_narrator_test.gd", "expect": 0}, # 战斗叙述(数值带入/胜负互异/收束态恒定)
{"path": "res://tests/unit/presentation/type_glow_test.gd", "expect": 0}, # 迭代 30 卡① T2-②——未开格类型微光数据面(九型归组封闭/清单外显式失败/冻结色相只准动明度/α170·带宽cell÷8锚定/合成空间阶梯单调+模型CR≥1.20/probe API 形状)
{"path": "res://tests/unit/presentation/type_glow_test.gd", "expect": 0}, # 未开格类型微光数据面(九型归组封闭/清单外显式失败/冻结色相只准动明度/α170·带宽cell÷8锚定/合成空间阶梯单调+模型CR≥1.20/probe API 形状)
]
+7 -7
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env bash
# tests/static_scan.sh — 静态扫描域 S1–S8 可执行子集
# 规则全集与动机:design/engineering/architecture/architecture.md「测试与质量域」节
# 正式工程(core/ 存在)时执行;否则优雅跳过(工程在 game/core——2026-09-24 转正结构批后仓库根布局)。
# 正式工程(core/ 存在)时执行;否则优雅跳过(工程在 game/core——仓库根布局)。
# 纪律:新违规案例必须先补本脚本规则再修码(design/engineering/code-conventions.md 缺陷-规范对照表)。
set -u
cd "$(dirname "$0")/../.." # 迭代 27 T5 修正——2026-09-24 布局迁移后 /.. 落 game/ 致 game/core 守卫恒跳过(静默空转遗产),S 闸规则路径均为仓根相对
cd "$(dirname "$0")/../.." # S 闸规则路径均为仓根相对(落点错会致 game/core 守卫恒跳过=静默空转)
if [ ! -d game/core ]; then
echo "[static_scan] game/core 不存在(工程未创建)——跳过,退出 0"
@@ -13,7 +13,7 @@ fi
fail=0
# ── 扫描路径单点 + 零命中自检(迭代 29 T6)──
# ── 扫描路径单点 + 零命中自检 ──
# 静默空转比红更危险:S3 曾把路径写成裸 `core`(仓根无此目录,实际是 game/core),
# 恒零命中恒绿、什么都不扫,直到本批才被发现。故每条规则的扫描路径必须是单点变量,
# 且规则自己先数一遍扫描面文件数——数为零一律判红(这样路径写歪时自检会跟着一起歪、一起红)。
@@ -35,7 +35,7 @@ require_scan_face() { # require_scan_face <规则名> <目录> — 扫描面
return 0
}
# S9 — 类型闸(迭代 27 T5——code-conventions §4 机器闸:产品码无类型容器/有值返回缺类型/参数缺类型)
# S9 — 类型闸(code-conventions §4 机器闸:产品码无类型容器/有值返回缺类型/参数缺类型)
if python tools/audit_type_hints.py; then
echo "[static_scan] S9 ok"
else
@@ -43,7 +43,7 @@ else
fail=1
fi
# S10 — 文件头闸(code-conventions §1.4.1 第二轮 2026-10-15:头注 ≤8 行+日期/变更叙事黑名单)
# S10 — 文件头闸(code-conventions §1.4.1:头注 ≤8 行+日期/变更叙事黑名单)
if python tools/audit_file_headers.py; then
echo "[static_scan] S10 ok"
else
@@ -53,7 +53,7 @@ fi
# S1/S2 — 玩法代码禁 randomize()/自建 RandomNumberGenerator/全局随机函数(绕开 RngService 的口子)
# 2026-09-24 首次实跑收敛(转正结构批):违规签名=裸 randomize/rand* 调用(实例方法 .randi() 排除)
# 违规签名=裸 randomize/rand* 调用(实例方法 .randi() 排除)
# + RngService 之外的 RandomNumberGenerator.new();接收服务注入流的类型标注不属违规(core 五文件 15 处实证)
if require_scan_face "S1/S2" "$DIR_CORE"; then
hits=$(grep -rnE '\brandomize[[:space:]]*\(|(^|[^.[:alnum:]_])randi[[:space:]]*\(|(^|[^.[:alnum:]_])randf[[:space:]]*\(|(^|[^.[:alnum:]_])randf_range[[:space:]]*\(' \
@@ -69,7 +69,7 @@ if [ "$hits" -gt 0 ]; then echo "[static_scan] S4 FAIL: core 引擎/UI 依赖命
fi
# S3 — core/ 禁 ConfigDB 外 data 表加载(schema class_name 直引豁免)
# 路径修正(迭代 29 T6):原扫裸 `core`——仓根无此目录,恒零命中恒绿(静默空转);实际为 game/core。
# 扫描路径必须写 game/core——写裸 `core` 仓根无此目录,恒零命中恒绿(静默空转)。
# 豁免:①注释行 ②game/core/config_db.gd 自身(数值单一事实源的正主,它的单点 load 是本规则的例外)。
if require_scan_face "S3" "$DIR_CORE"; then
hits=$(grep -rnE '\b(load|preload)[[:space:]]*\(|ResourceLoader\.load' "$DIR_CORE" --include='*.gd' 2>/dev/null \
@@ -1,6 +1,6 @@
# tests/unit/battle_settlement/skill_unlock_test.gd — Boss 半血解锁技·单位容器机制单元测试
# 运行:godot --headless --path game --script res://tests/unit/battle_settlement/skill_unlock_test.gd
# 覆盖(迭代14④ 技能池执行批 T6):门谓词(hp ≤ round(max_hp×pct/100))/闩不可逆(治疗抬血不解锁)/
# 覆盖:门谓词(hp ≤ round(max_hp×pct/100))/闩不可逆(治疗抬血不解锁)/
# 双面过滤(rollable+special 不入锁定)/保底不逼锁定(保底集合排除锁定)/解锁即可摇(冷却初值 0)/
# 普通行无字段逐位零扰动(goblin 池快照零 unlock 键、过滤恒空转)/同种子确定性(容器零随机)。
# 红线:期望值由表读值推导(零玩法数值字面量);零自建 RNG。
+1 -1
View File
@@ -1,4 +1,4 @@
# tests/unit/core/board_generator_test.gd — 棋盘生成直接单测(迭代 29 T5 补洞:该模块此前零指名)
# tests/unit/core/board_generator_test.gd — 棋盘生成直接单测
# 断言面 = 生成的棋盘自洽:尺寸与表一致、雷数在域、数字恒等于八邻雷数、同种子逐位可复现、
# 可推理性度量与清图判定自洽。期望值一律由盘上数出来(零玩法数值字面量)。
# 运行:godot --headless --path game --script res://tests/unit/core/board_generator_test.gd
@@ -86,7 +86,7 @@ func _init() -> void:
var battle_d = runner_d.current_battle()
Kit.check_quiet(_board_fingerprint(battle_d.minesweeper.current_board()) == fp_b, "二次续玩板逐位同(幂等)")
# ---- Part 5:三选一停点中断→续玩(迭代 10 奖励重构——候选不重抽、已兑付不重付、次数计数一致)----
# ---- Part 5:三选一停点中断→续玩(候选不重抽、已兑付不重付、次数计数一致)----
var rng_e = load("res://core/rng_service.gd").new()
var sm_e = SaveManagerScript.new(_bp_path)
var adr_e = AdventureRunnerScript.new(db, rng_e)
@@ -128,7 +128,7 @@ func _init() -> void:
Kit.check_quiet(String(got_result["r"].get("picked", "")) == picked_e, "P5 已兑付不重付(resolved 重发同件)")
Kit.check_quiet(adr_g.continue_to_next_level(), "P5 已兑付续玩后推进受理(下一关开始)")
# ---- Part 6:装配存档回归(迭代 19 卡④立,迭代 22 秘宝批 T4 主语换代——装配→停点→回局载体态
# ---- Part 6:装配存档回归(装配→停点→回局载体态
# 一致:params.attunement 随续玩受理面重建再注入战斗〔begin_battle enc 携带〕;呈现态每战重算归
# 呈现桥(main._sync_treasure_glow),本断言钉核心层装载往返——回局后 attune_has 只读出口读数一致)----
var rng_h = load("res://core/rng_service.gd").new()
@@ -1,4 +1,4 @@
# tests/unit/core/config_db_cross_validator_test.gd — 跨表校验直接单测(迭代 29 T5 补洞:该模块此前零指名)
# tests/unit/core/config_db_cross_validator_test.gd — 跨表校验直接单测
# 断言面 = 敌人技能池跨表链接校验:悬空 skill_id 必报、施放类 side 反向挂接必报、
# 池构成纪律(普攻恰一行)越界必报、全对齿时零错(正向面防误伤)。
# 表对象由夹具直构(不碰生产表),期望值由注入内容数出来。
@@ -1,5 +1,5 @@
# tests/unit/core/config_db_resolver_test.gd — 配置解析直接单测(迭代 29 T5 补洞:该模块此前零指名)
# 断言面 = ceil 进度键解析语义(2026-09-18 用户拍板:向上取整保证偏难)、行集多行的行序保序、
# tests/unit/core/config_db_resolver_test.gd — 配置解析直接单测
# 断言面 = ceil 进度键解析语义(向上取整保证偏难)、行集多行的行序保序、
# 难度修饰乘数的 clamp 域。行集由夹具注入,不碰生产表——期望值由注入行数出来。
# 运行:godot --headless --path game --script res://tests/unit/core/config_db_resolver_test.gd
extends SceneTree
@@ -1,5 +1,5 @@
extends SceneTree
# 三选一刷新免费双源 + GP 付费档单测(2026-10-06 用户令——三选一刷新对齐商店 MC-1:
# 三选一刷新免费双源 + GP 付费档单测(三选一刷新对齐商店 MC-1:
# 免费位 X/Y 用尽后转 GP 付费,同旋钮同曲线同钱包)。
# 守面:付费价曲线(ceil(商店基价×曲线^本次停点已付费刷))/免费优先序 X→Y/付费刷新零触碰免费位/
# 停点在途谓词(无停点禁扣钱)/停点重建清零付费计数(对齐商店进店清零)/钱包单口支出 spend_gp。
@@ -52,7 +52,7 @@ func _init() -> void:
Kit.check_quiet(lr.spend_gp(31) == false and int(lr.gp) == 30, "余额不足拒绝零副作用")
Kit.check_quiet(lr.spend_gp(20) == true and int(lr.gp) == 10, "足额支出扣账(30→10)")
# ---- ⑥ Y 断点随存(2026-10-13 缺陷修批——旧缺陷:续玩自参数面整额重注入=用完 Y 退菜单再续玩满血复活)----
# ---- ⑥ Y 断点随存(缺陷锚:续玩自参数面整额重注入=用完 Y 退菜单再续玩满血复活)----
var bp_y := {"level_index": 1, "hero_hp": 10, "attune_refresh_y": 2}
var adr_r = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
Kit.check_quiet(adr_r.resume_adventure(bp_y, {"hero_override": {}, "attune_refresh_y": 3}), "续玩受理(最小断点+参数面)")
+1 -1
View File
@@ -1,5 +1,5 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# 效果引擎词表扩签封闭性单测(迭代 20 专长实装批 T1——需求卡片 V1 验收条目)。
# 效果引擎词表扩签封闭性单测(需求卡片 V1 验收条目)。
# 覆盖:①扩签后四词表仍封闭(新事件/新效果类型/新事实/新算子在册+出域值全部被拒——
# validate_trigger_row 加载期报错 / dispatch 运行期 fail-fast);②合法新词行零违例;
# ③adventure 域计数器 export/seed 往返(T6 承载面);④mod_eq 取模余数算子(双管镐落法)。
+6 -6
View File
@@ -1,7 +1,7 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# 凭证件留任二件+两件出池+完美保险专长单测(刷新券/储备金袋/时沙 三件路由+留任二件——刷新写错账修复钉面)。
# 覆盖:刷新券→刷新存量账(非 _attune_refresh_x——写错账复发反例)/储备金袋→RAP/时沙→首轮 AP 池;
# 迭代 31 卡② 凭证件裁削批——同调电池整件删除、完美保险迁专长:两件出池反例(词表/兑付/符号清零/断点缺键)、
# 同调电池整件删除、完美保险迁专长:两件出池反例(词表/兑付/符号清零/断点缺键)、
# 配额净删锚(稀有 7/总量 102)、完美保险专长验收组(装载/限次一次/计数纯净/两账相减判定/跨战清零/事件不吞);
# 留任 2 件路由与值不变(GP 钱包/当即回血)+行动点上限归属性轴(bonus.ap 落点)。
# 运行:godot --headless --path game --script res://tests/unit/core/feat_vouchers_test.gd(expect 0)
@@ -42,7 +42,7 @@ func _init() -> void:
Kit.check_quiet(a.refresh_curio() and a.refresh_stock() == 0,
"刷新消耗序:X/Y 空时吃存量账〔专长批 V9〕")
a._node_curio_pending = {}
# 摘牌:选中即消尽(T4 修正——旧早退路径零摘牌缺陷同批退役)
# 摘牌:选中即消尽(禁早退路径零摘牌)
var deck_refresh_before: int = a.pool_stock("refresh")
Kit.check_quiet(deck_refresh_before == 0 or true, "牌库 refresh 令牌读数在域(投放 %d)" % deck_refresh_before)
@@ -54,7 +54,7 @@ func _init() -> void:
Kit.check_quiet(a._apply_curio_pick("time_sand", "rare", 2), "时沙兑付受理〔专长批 V9〕")
Kit.check_quiet(a._time_sand_queue == 2, "时沙:首轮 AP 队列 +2(池增量非上限)〔专长批 V9〕")
# V23 两件出池(迭代 31 卡② 凭证件裁削批 R1——同调电池删除/完美保险迁专长)
# V23 两件出池(R1——同调电池删除/完美保险迁专长)
Kit.check_quiet(not db.adventure.CURIO_KINDS.has("attune_battery")
and not db.adventure.CURIO_KINDS.has("perfect_insurance"),
"出池词表:CURIO_KINDS 零两件(三选一池物理不可再出)〔卡② R1〕")
@@ -71,7 +71,7 @@ func _init() -> void:
a._seed_attune_charges()
Kit.check_quiet(int(a.run_ledger.attune_charges.get("incense_stick", -1)) != 99,
"账本保留:删件后 _seed_attune_charges 仍按装备重置充能账(attune_charges 语义零变化)〔卡② R1〕")
# V24 配额净删锚(R3——用户裁「净删」,rare 9→7/总量 104→102)
# V24 配额净删锚(R3——「净删」口径,rare 9→7/总量 104→102)
var rare_sum := 0
var rare_kinds: Array[String] = []
for k in db.adventure.CURIO_KINDS:
@@ -111,7 +111,7 @@ func _init() -> void:
# V16——断点扩容往返:新账本随断点快照/续玩回填(breakpoint_resume_test P5 同构)
var sm = load("res://core/save_manager.gd").new("user://_feat_vouchers_bp.json")
sm.load_from_disk()
a.run_ledger.adventure_counters = {"pain_layers": 3, "twin_digs": 5} # 迭代 27 T3——四账住 RunLedger
a.run_ledger.adventure_counters = {"pain_layers": 3, "twin_digs": 5} # 四账住 RunLedger
a._refresh_stock = 2
a.set_save_manager(sm)
a._on_level_finished({"result": "victory", "hp": 10, "node_index": 1, "node_id": 1, "node_type": "shop"})
@@ -127,7 +127,7 @@ func _init() -> void:
Kit.check_quiet(b3.resume_adventure(bp_legacy, {}), "V16:旧档含 battery_pending 键续玩受理(缺键/多键零报错)〔卡② R1〕")
Kit.check_quiet(int(b2.run_ledger.adventure_counters.get("pain_layers", -1)) == 3 and b2.refresh_stock() == 2,
"V16:续玩回填逐项一致(计数恢复——跨战账不丢)〔专长批 V16〕")
# V25 完美保险专长验收组(迭代 31 卡② T4/T5——R2 全项:装载/限次一次/计数纯净/两账相减/跨战清零/事件不吞)
# V25 完美保险专长验收组(R2 全项:装载/限次一次/计数纯净/两账相减/跨战清零/事件不吞)
var fi: Dictionary = db.feats.row_by_id("perfect_insurance")
Kit.check_quiet(not fi.is_empty() and String(fi.get("rarity", "")) == "rare"
and String(fi.get("desc", "")).find("雷") == -1 and String(fi.get("desc", "")).find("封印") >= 0,
@@ -1,4 +1,4 @@
# tests/unit/core/minesweeper_rules_test.gd — 排雷规则直接单测(迭代 29 T5 补洞:该模块此前零指名)
# tests/unit/core/minesweeper_rules_test.gd — 排雷规则直接单测
# 断言面 = 装板后的盘面不变量与意图受理:装板零揭示、安全格余量=安全格总数、挖安全格后揭示
# 且余量递减、封印落封印态、禁挖面拒绝、清图判定随揭示推进。期望值一律由盘上数出来。
# 运行:godot --headless --path game --script res://tests/unit/core/minesweeper_rules_test.gd
@@ -1,5 +1,5 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# 新目录 12 件逐件行为断言(迭代 22 秘宝批 T3——V7 附属表:正常效果/边界/反例三列核心行)。
# 新目录 12 件逐件行为断言(V7 附属表:正常效果/边界/反例三列核心行)。
# 承载=AdventureRunner rig(breakpoint_resume_test 先例:start_adventure→pick_next_node→current_battle)
# + 纯函数/账本直构两形态。每件「正常→边界→反例」顺序落断言;装配集直注 params.attunement(调试注入
# 口径——槽位与解锁不经正常流程,卡片 7.1)。
@@ -71,7 +71,7 @@ func _init() -> void:
var ob4 = orch_s.new(db, rng4)
Kit.check_quiet(ob4.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(db.hero.max_hp), "attunement": ["trial_pick"]}),
"试炼镐:装配起战")
# 自动兑付(反哺批⑤ 2026-10-12 用户裁):无玩家入口——揭示缓冲点即时计数,满 charge_n 自动
# 自动兑付:无玩家入口——揭示缓冲点即时计数,满 charge_n 自动
# grant_ap(cell_revealed 先于本格扣费→AP 归零恰逢满点时先兑再扣、轮不结束)。直调揭示处理器
# 构造 7+1 格序列(缓冲路径与真挖格同链;授予段无积点段无二次计数)
var ap_before4 := int(ob4.action_economy._ap)
@@ -94,7 +94,7 @@ func _init() -> void:
Kit.check_quiet(int(ob4b.action_economy._ap) == ap_base and not bool(ob4b.attune_convert_state().get("assembled", true)),
"试炼镐反例:未装配零兑付零计数(AP=整轮表值、读数口 assembled=false)")
# ④b 自动兑付件 C 区展示行(反哺批⑤补——charges=0 不入骑库存〔_reseed 注入循环跳过〕,
# ④b 自动兑付件 C 区展示行(charges=0 不入骑库存〔_reseed 注入循环跳过〕,
# 展示行=C 区悬停浮窗进度唯一承载:handle=-1 非库存句柄+note 进度;空骑库存桩直构投影链)
var rng4c = load("res://core/rng_service.gd").new(); rng4c.set_master_seed(253)
var ob4c = orch_s.new(db, rng4c)
@@ -269,7 +269,7 @@ func _init() -> void:
Kit.check_quiet(ob12b.minesweeper.seal_range_free == false,
"信标网络反例:未装配封印可达豁免=缺省 false(零装配零扰动)")
# ⑬ 真视使用计数链(受理成功恰计一次——RunLedger 单类持账;迭代 27 T3 换代)
# ⑬ 真视使用计数链(受理成功恰计一次——RunLedger 单类持账)
var rng13 = load("res://core/rng_service.gd").new(); rng13.set_master_seed(321)
var ob13 = orch_s.new(db, rng13)
var lg13 = load("res://core/run_ledger.gd").new()
@@ -286,7 +286,7 @@ func _init() -> void:
Kit.check_quiet(int(lg13.vision_counter.get("used", 0)) == 1,
"计数链正常:受理成功恰 +1(信标网络解锁行为条件数据源)")
# ⑭ 装配不可变(迭代 22 秘宝批 T6——V6:冒险全程装配快照三面同源且零写口——
# ⑭ 装配不可变(V6:冒险全程装配快照三面同源且零写口——
# 写口唯一=营地 toggle_attunement〔meta_progression〕,runner/level/battle 无装配写口)
var rng14 = load("res://core/rng_service.gd").new(); rng14.set_master_seed(331)
var adr14 = runner_s.new(db, rng14)
@@ -339,7 +339,7 @@ func _cell_at(orch, c: Vector2i) -> Vector2i:
return Vector2i(-9, -9)
# 自动兑付件展示行测试桩(反哺批⑤——空骑库存 runner 桩:charges=0 件不入骑库存,
# 自动兑付件展示行测试桩(空骑库存 runner 桩:charges=0 件不入骑库存,
# 展示行合成逻辑在 treasure_interact_controller.inventory_projection 末段)
class StubAutoInvRunner:
extends RefCounted
@@ -58,7 +58,7 @@ func _test_progress_resolution(db) -> void:
and String(got.get("board_type", "")) == String(probe_row.get("board_type", "")))
# enemy 行集同通道:同 (L,D) 行集数组非空(关卡节点地图 前置——battle 节点双表精确行集契约;
# 2026-10-07 行集多行批 T2 机械适配——返回形态单行改行集数组,精确性断言换代随批 T4)
# 行集面契约:返回形态是行集数组,精确性断言随行集面)
var en_got: Array = db.resolve_enemy_rowset(int(probe_row.level_index), int(probe_row.node_depth))
Kit.check("enemy 行集同坐标解析非空(双表精确行契约)", not en_got.is_empty())
@@ -93,7 +93,7 @@ func _test_variant_pool_gate(db) -> void:
for row in rows:
if String(row.get("scope", "region")) != "region":
if global_row.is_empty():
global_row = row # global 行第三维门控另测(全局作用域变异批 2026-10-04)
global_row = row # global 行第三维门控另测(全局作用域变异)
continue
var tags: Array = row.get("board_types", [])
if gated_row.is_empty() and not tags.is_empty():
@@ -155,7 +155,7 @@ func _test_shop_accessors(db) -> void:
# ---- MetaTable 消费面 ----
func _test_meta_accessors(db) -> void:
# forge_products 行族断言已摘除(迭代 21 T5——行族/访问器随属性锻造柜台退役,V20 清单)
# 无 forge_products 行族断言(行族/访问器不在表,V20 清单)
var key_rows: Array = db._meta_rows("boss_keystone")
if not key_rows.is_empty():
var klv: int = int(key_rows[0].level_index)
@@ -188,7 +188,7 @@ func _test_manifest_dir_sync(db) -> void:
Kit.check("TABLE_MANIFEST 与 data/tables 目录逐文件同步(无缺登/无悬登)", synced)
# ---- 行集构成纪律(2026-10-07 敌人表行集多行批——EnemyTable.validate 正反验证;资源直构不碰生产表)----
# ---- 行集构成纪律(EnemyTable.validate 正反验证;资源直构不碰生产表)----
# 纪律三条(敌人系统 v0.15):①行集内 enemy_id 唯一(dup_key 既有通道)②同坐标多行文件内相邻
# (rowset_rule——行序即契约,R6 加载期兜底)③行集实例总数 ≤4(骰后值运行面,spawn 守卫——冒烟侧验证)。
@@ -44,7 +44,7 @@ func _test_production_rows(db) -> void:
if ks != expect_keys:
keys_closed = false
Kit.check("六行字段键集恰 = ROW_KEYS 闭集(无特效参数位)〔锻造工坊批 V11〕", keys_closed)
# 初始持有恰长剑+轻甲(语义继承自已退役属性锻造行)
# 初始持有恰长剑+轻甲
var initial := []
for row in rows:
if bool(row.get("initial_owned", false)):
@@ -190,8 +190,8 @@ func _test_negative_cases() -> void:
# ---- V11:效果类型闭集零新增(满级特效本期不开新词)----
# 实数订正(L2 在册):卡片 F11「24 种」系 2026-10-07 设计文档事实快照——迭代 20 T1 +7、迭代 22 T3 +1
# 后现行 31;本批零扩签,钉现行数(将来任何一批加词即转红提示复核)。
# 实数订正(L2 在册):卡片 F11「24 种」系设计文档事实快照,
# 现行 31;本套件零扩签,钉现行数(将来任何加词即转红提示复核)。
func _test_effect_kinds_closed() -> void:
Kit.check("效果引擎效果类型闭集 32 种(锻造工坊批零新增口径随迭代 31 卡② 扩签 forgive_seal_miss 换代)〔锻造工坊批 V11〕",
@@ -20,7 +20,7 @@ func _inject_violation() -> bool:
return false
var s := f.get_as_text()
f.close()
# 追加式注入(2026-10-01 战斗掉落渠道退役随批——现行表已无 aux 行可作锚):向 rows 尾追加
# 追加式注入(现行表已无 aux 行可作锚):向 rows 尾追加
# 一块引用已退役 loot_compass 的 aux 投放行,双表校验须拦(retired 不入投放面)。
var inject := ', {\n\n"reward_id": "treasure_dangling_probe",\n"cell_type": "treasure_item",\n"polarity": "positive",\n"effect_id": "loot_compass",\n"display_name": "悬空探针",\n"power": 0,\n"weight": 1,\n"pool_tag": "cell",\n"tier": 1\n}'
var idx := s.rfind("\n}]")
@@ -49,7 +49,7 @@ func _test_counter_ledger() -> void:
rng.open_stream("armor_roll")
var c2: int = rng.counter()
Kit.check("open_stream 逐次推进全局计数器(+1/次)", c1 == c0 + 1 and c2 == c0 + 2)
# 用途标签只进事件日志不参与派生——同 scope 同派生种子(2026-09-27 统一流裁决口径)
# 用途标签只进事件日志不参与派生——同 scope 同派生种子(统一流口径)
var rng_x = _fresh_service()
var rng_y = _fresh_service()
var s1: int = int(rng_x.open_stream("board_gen").seed)
@@ -1,4 +1,4 @@
# tests/unit/presentation/battle_narrator_test.gd — 战斗叙述直接单测(迭代 29 T5 补洞:该模块此前零指名)
# tests/unit/presentation/battle_narrator_test.gd — 战斗叙述直接单测
# 断言面 = 纯函数文案:休整/商店/终局三类叙述把数值带进去(玩家读数面由这里出)、
# 胜负两态文案互异、未知输入有兜底不吐空串。期望值由注入内容数出来。
# 运行:godot --headless --path game --script res://tests/unit/presentation/battle_narrator_test.gd
@@ -1,5 +1,5 @@
extends SceneTree
# 锻造工坊工作间 UI 直验套件(迭代 23 T6——V13 人闸的自动化前置面:两区结构/钮态/意图信号/锁定态纪律)
# 锻造工坊工作间 UI 直验套件(V13 人闸的自动化前置面:两区结构/钮态/意图信号/锁定态纪律)
# 覆盖:锁定态零行板零购买路径(占位纪律延续)· 两区各三路线 · 初始持有行购买钮(几何价 20)·
# 待解锁行解锁钮(200)+无购买钮 · 选中钮当前态禁用 · 余额不足禁用态 · 满级无购买钮 ·
# 三意图信号外发(UI 只发意图——契约 9,写口归 MetaProgression 由冒烟侧覆盖)。
@@ -1,4 +1,4 @@
# tests/unit/presentation/hud_presenter_test.gd — HUD 投影直接单测(迭代 29 T5 补洞:该模块此前零指名)
# tests/unit/presentation/hud_presenter_test.gd — HUD 投影直接单测
# 断言面 = 纯函数投影:动作结果文案按拒绝原因逐类落字(玩家看到的每句提示都由这里出)、
# 未知原因有兜底文案(不吐空串)、棋盘变异行投影随表走。期望值由注入内容数出来。
# 运行:godot --headless --path game --script res://tests/unit/presentation/hud_presenter_test.gd
@@ -19,7 +19,7 @@ func _init() -> void:
var main: Node = load("res://presentation/main.tscn").instantiate()
root.add_child(main)
await process_frame
# 隔离换注(迭代 27 T1——SaveManager 升格 autoload 后改原地 attach:先删后建防陈旧档泄入,
# 隔离换注(SaveManager 是 autoload——原地 attach:先删后建防陈旧档泄入,
# 读盘在 attach 内完成;meta 经 save 引用透传零重建)
for p in ["user://test_pacing_queue_save.json", "user://test_pacing_queue_save.json.tmp"]:
if FileAccess.file_exists(p):
@@ -1,10 +1,10 @@
extends SceneTree
# 三选一覆盖层 UI 直验套件(迭代 7 T5-hotfix 转正;迭代 10 追加批随面迁移改写)——
# 原守 node_overlay.show_reward_items;奖励节点三选一与通关三选一合并共用 CurioPickOverlay 后,
# 本套件改守共用面的「功能存在 ≠ 功能可达」盲区:选择外发/强制确认两段式/结果态。
# 〔替换子面板两路段随迭代 22 秘宝批 T5 拆除——装配不可变契约 1,overlay replace 态退役。〕
# 三选一覆盖层 UI 直验套件——
# 守 node_overlay.show_reward_items 的继任共用面;奖励节点三选一与通关三选一共用 CurioPickOverlay,
# 本套件守共用面的「功能存在 ≠ 功能可达」盲区:选择外发/强制确认两段式/结果态。
# 〔无替换子面板路段——装配不可变契约 1,overlay 无 replace 态。〕
# 运行:godot --headless --path game --script res://tests/unit/presentation/reward_overlay_test.gd(expect 0)
# T6 收口批:断言走 test_kit 计数(Kit.check)+ 失败即停早退协议保留(顺序状态机——坏态下后继断言无效)。
# 断言走 test_kit 计数(Kit.check)+ 失败即停早退协议(顺序状态机——坏态下后继断言无效)。
const Kit := preload("res://tests/unit/test_kit.gd")
# 断言 + 失败即停(原直写 print FAIL + quit(1) + return 样板单点化——早退语义逐位保持)
@@ -31,13 +31,13 @@ func _run() -> void:
{"id": "gp", "text": "GP 袋\nGP +20"},
{"id": "ap", "text": "行动点提升\n每战首轮 AP +1"},
])
# M7 bcard 换装适配(2026-10-05 奇物批 T2 连带):三选一卡自 M7 起为 BCard(PanelContainer) 非 Button——
# 原 `c is Button` 计数自 M7 后恒 0(基线红成因,主区 3c4291d 起红在案)。改数 _choice_box 子节点数,断言语义不变(三卡呈现)。
# bcard 形态:三选一卡为 BCard(PanelContainer) 非 Button——
# 用 `c is Button` 计数会恒 0(基线红成因,主区 3c4291d 起红在案)。本面数 _choice_box 子节点数,断言语义=三卡呈现。
var btns: int = ov._choice_box.get_child_count()
if not _req("选择态应三卡呈现且确认/继续隐藏(btns=%d)" % btns,
ov.visible and btns == 3 and not ov._confirm_btn.visible and not ov._continue_btn.visible):
return
# ①b 刷新面投喂(2026-10-06 对齐商店 MC-1——投喂前隐藏/免费位点亮/GP 付费档禁用态/真实钮路径外发)
# ①b 刷新面投喂(对齐商店 MC-1——投喂前隐藏/免费位点亮/GP 付费档禁用态/真实钮路径外发)
if not _req("投喂前刷新钮应隐藏(未投喂上下文如店内门票面无刷新)",
not ov._refresh_btn.visible):
return
@@ -3,7 +3,7 @@
# 事实源:board_gen.tres board_type 词表 v1.1(dungeon/underdark/deep_underdark——从表读值,禁写死);
# 色相域方向 = art-bible §4.3 三关方向(underdark 灰白蓝紫 / deep_underdark 蓝黑紫黑);
# 图底分离 = 圣经 §8.5(已开地面明度低于未开墙体)。
# 前置守卫:素材不入 git(用户裁定)——fresh checkout 无瓦片文件时本套件跳过(打印注记,不红);
# 前置守卫:素材不入 git——fresh checkout 无瓦片文件时本套件跳过(打印注记,不红);
# 源 dungeon 套在盘而换色套缺位 = 回归(换色脚本在库可复现)。
extends SceneTree
@@ -115,7 +115,7 @@ func _load_stats(board_dir: String, fname: String, top_half: bool) -> Dictionary
return {"h": h_sum / n_h, "v": v_sum / n_v}
# 数字呈现模式对比断言(L2/L3 亮瓦融字缺陷修复 2026-10-01——甲案机检面,v3.4 扩三态):
# 数字呈现模式对比断言(L2/L3 亮瓦融字缺陷的甲案机检面,v3.4 扩三态):
# 每个图型走 BoardView 同一归一+测光+选模式管线(禁测试侧另写算法),断言:
# ①dungeon=亮墨族直画(对 p90 ≥4.5 且对均值 ≥2.5);②underdark/deep_underdark=色牌垫底
# (双峰纹理直画不可救——渲染帧取证深墨隐形实证,牌上深墨 12:1);③直画模式的双门槛值照抄
@@ -1,8 +1,8 @@
# tests/unit/presentation/type_glow_test.gd — 未开格类型微光数据面直验(迭代 30 卡① T2-②)
# tests/unit/presentation/type_glow_test.gd — 未开格类型微光数据面直验
# 断言面 = 纯函数/常量域(无渲染):① 九 cell_type → 五组封闭判定(含清单外显式失败、
# 禁子串匹配、与卡顶带 tg_band 的归组差异锚定——Q3 甲案两带并存);② 五组色值冻结面
# (HSV 对照 art-bible §4.2 冻结值只准动明度档——2026-10-17 群伤组一案停机呈报后用户裁
# #A61D37,本测试即该裁定的守门件);③ 呈现参数锚定(α170/带宽 cell÷8);④ 合成空间
# (HSV 对照 art-bible §4.2 冻结值只准动明度档——群伤组
# #A61D37 为终值,本测试即该判据的守门件);③ 呈现参数锚定(α170/带宽 cell÷8);④ 合成空间
# 阶梯单调 + 相邻组模型 CR ≥1.20(色弱灰度互辨的数据面预判,终审在渲染取证探针实测);
# ⑤ probe API 形状(默认开/空态键集)。
# 运行:godot --headless --path game --script res://tests/unit/presentation/type_glow_test.gd
@@ -27,7 +27,7 @@ const TYPE_EXPECT := {
"pierce": UiKitScript.GLOW_PIERCE, "blast": UiKitScript.GLOW_AOE,
"magic": UiKitScript.GLOW_MAGIC,
}
# provisional 明度档终值(2026-10-17 用户裁后)——逐位锚定,谁改色值这里红
# provisional 明度档终值——逐位锚定,谁改色值这里红
const IMPL := {
"BUFF": Color8(247, 223, 67), "HEAL": Color8(104, 208, 66),
"PIERCE": Color8(48, 166, 166), "MAGIC": Color8(123, 91, 214),
@@ -92,7 +92,7 @@ func _test_frozen_hue_face() -> void:
var ds: float = absf(m.s - f.s)
Kit.check("冻结色相保持:%s ΔH=%.3f° ≤0.2° 且 ΔS=%.4f ≤0.01(只动明度档)" % [g, dh * 360.0, ds],
dh <= 0.2 / 360.0 and ds <= 0.01)
# 群伤组专锚(2026-10-17 停机呈报→用户裁 #A61D37):H/S 与冻结逐位一致、V=0.65 档
# 群伤组专锚(#A61D37):H/S 与冻结逐位一致、V=0.65 档
var aoe_f: Color = FROZEN["AOE"]
var aoe_m: Color = UiKitScript.TG_GLOW_AOE
Kit.check("群伤组专项:H 与冻结 #FF2D55 差 %.4f°(零移动)" % (absf(aoe_m.h - aoe_f.h) * 360.0),