merge(iter38): 迭代 38 六卡合入(用户特批带红合并、免测试重跑)——卡① levelmap装配越界与BFS缓存(golden 基线+对拍 0.00%)+卡③ 效果文案计数修正+卡④ 明细行扩展总型收敛(含 parity 重锚并账,裁点甲)+卡⑤ 奖励节点表述清扫(wa4 转正+停点态病根修正)+卡⑥ 结算行缓冲类型错修复(上轮摘要恢复)+卡⑦ 调试入口写死行删除(发布态恒隐);卡② 结构小件批停卡退上游(wr5/wr6 载体与闸机错误扫描互斥,WIP 补丁留存);S13 gdstyle 恶化 2 条经用户特批记存量红挂账;独立验收审计报告与三份产出物随批落 reports/

This commit is contained in:
DKChan committed 2026-10-26 18:08:57 +08:00
commit 8ea7cfba9c
30 files changed
+2334 -1739

No files matched your search

+1 -1
View File
@@ -13,7 +13,7 @@
1. 用 Godot 4.7.2 打开本目录(含 `project.godot` 的文件夹) 1. 用 Godot 4.7.2 打开本目录(含 `project.godot` 的文件夹)
2. 按 F5 运行(主场景 `presentation/main.tscn`) 2. 按 F5 运行(主场景 `presentation/main.tscn`)
3. **主入口:`▶ 开始冒险(三关连探:地精洞窟 → 幽暗地域 → 灰矮人矿城)`**——一条命走完三关(冒险结构实现批 2026-09-21:生命/GP/加算区跨关同账、非末关的关卡完成发大奖励〔上限提升+血回满+钥石〕、任意战斗倒下冒险终、结算屏出冒险摘要五件事;选路停点可「放弃冒险」(同失败结算,2026-09-21 用户拍板补入口));首关节点链:战→战→赏→战→战→疗→王。唯一试玩入口 = 完整冒险——单关测试直入口(L2/L3)已退役(2026-09-23 用户拍板) 3. **主入口:`▶ 开始冒险(三关连探:地精洞窟 → 幽暗地域 → 灰矮人矿城)`**——一条命走完三关(冒险结构实现批 2026-09-21:生命/GP/加算区跨关同账、非末关的关卡完成发大奖励〔上限提升+血回满+钥石〕、任意战斗倒下冒险终、结算屏出冒险摘要五件事;选路停点可「放弃冒险」(同失败结算,2026-09-21 用户拍板补入口));首关节点链:战→战→赏→战→战→疗→王。唯一试玩入口 = 完整冒险——单关测试直入口(L2/L3)已退役(2026-09-23 用户拍板)
4. `调试` 角标浮窗(恒开——2026-10-16 用户令 DS_DEBUG 写死,原环境变量门控退役;2026-10-04 迭代 14.5 调试整合批:原数值调整板/随机种子框/两层调试三散件收一):攻击/生命百分比加成(模拟天赋树解锁)+ 批量模拟(输出关卡完成率/承伤期望/败北节点分布)+ 两层调试入口 4. 调试浮窗(功能面:攻击/生命百分比加成〔模拟天赋树解锁〕+ 批量模拟〔输出关卡完成率/承伤期望/败北节点分布〕+ 两层调试入口;2026-10-04 迭代 14.5 调试整合批三散件收一)——发布态入口恒隐(2026-10-25 删写死行批:调试角标不显示、浮窗无 UI 开启口;开发期临时启用改 `_debug_entry` 缺省本地不提交),浮窗体系整体退役属发布工程批
headless 冒烟(752 项断言〔计数随批增长,现值以最新批末闸读数为准〕,含关卡线性推进/满配冒险成功/裸装败北/回放确定性;2026-10-02 性能报告实测 60.55 秒,报告在 `性能实测报告(已退役,git 历史可查)`): headless 冒烟(752 项断言〔计数随批增长,现值以最新批末闸读数为准〕,含关卡线性推进/满配冒险成功/裸装败北/回放确定性;2026-10-02 性能报告实测 60.55 秒,报告在 `性能实测报告(已退役,git 历史可查)`):
+17 -5
View File
@@ -12,11 +12,18 @@ signal round_started(round_index: int)
signal board_changed() signal board_changed()
signal settlement_snapshot(snap: Dictionary) ## 每结算末尾的全量状态快照(playtest_logger 消费——JSONL 数值分析) signal settlement_snapshot(snap: Dictionary) ## 每结算末尾的全量状态快照(playtest_logger 消费——JSONL 数值分析)
# 明细流水行型协议(战斗结算 Core Rule;9 类; # 明细流水行型协议(战斗结算 Core Rule;两层词表——基础 9 类封闭+扩展行 1 类;
# 清单改动须同步 battle_log_view 的 _format_row 与本文档头注): # 清单改动须同步 battle_log_view 的 _format_row 与本文档头注):
# 效果行 effect · 伤害行 damage · 治疗行 heal · # 基础 9 类:效果行 effect · 伤害行 damage · 治疗行 heal ·
# 奖励未生效行 reward_ineffective · 封印未中行 seal_miss · 施放行 cast · 衰减行 decay · # 奖励未生效行 reward_ineffective · 封印未中行 seal_miss · 施放行 cast · 衰减行 decay ·
# 判定行 verdict · 高光行 highlight # 判定行 verdict · 高光行 highlight
# 扩展行:行型值=extended(单一扩展行型),效果身份由 effect_id 载荷携带(_emit_row 强制必载,
# 缺载即断言失败零发射)。现役效果身份与发射点:
# fx_reveal(专长揭示掷格——battle_orchestrator _flush_pending_reveals 三发射点:兑付行+落空行×2)
# mine_neutralized(封印匠·危险格中立移除——grant_segment 封印命中 remove_mines 面)
# fx_preview_area(示警烟火·周围资源类型——grant_segment 封印命中 preview_area 面)
# insurance_used(完美保险·标错豁免——grant_segment 标错 forgive_seal_miss 面)
# 新增专长效果只新增 effect_id 身份不再新增行型;行型词表改动须同步 owner 文档「扩展行」节。
# 行来源断言豁免集合(行型层)——判定段 / 衰减段本就无棋盘来源,勿一刀切误炸 # 行来源断言豁免集合(行型层)——判定段 / 衰减段本就无棋盘来源,勿一刀切误炸
const SOURCE_EXEMPT_TYPES := ["decay", "verdict"] const SOURCE_EXEMPT_TYPES := ["decay", "verdict"]
# 行来源断言豁免集合(来源类别层)——事实来源是单位 / 清图而非棋盘格; # 行来源断言豁免集合(来源类别层)——事实来源是单位 / 清图而非棋盘格;
@@ -146,7 +153,7 @@ func _flush_pending_reveals(ri: int) -> void:
if not minesweeper.is_revealed(c) and not board.is_mine(c): if not minesweeper.is_revealed(c) and not board.is_mine(c):
cands.append(c) cands.append(c)
if cands.is_empty(): if cands.is_empty():
_emit_row("fx_reveal", "fx_reveal", Vector2i(-1, -1), ri, "揭示落空(无未揭示安全格)", 0, {}, "") _emit_row("extended", "fx_reveal", Vector2i(-1, -1), ri, "揭示落空(无未揭示安全格)", 0, {"effect_id": "fx_reveal"}, "")
continue continue
var rg: RandomNumberGenerator = rng.open_stream("fx_reveal") var rg: RandomNumberGenerator = rng.open_stream("fx_reveal")
var cell: Vector2i = cands[rg.randi_range(0, cands.size() - 1)] var cell: Vector2i = cands[rg.randi_range(0, cands.size() - 1)]
@@ -154,12 +161,12 @@ func _flush_pending_reveals(ri: int) -> void:
var ok: bool = minesweeper.fx_reveal(cell) var ok: bool = minesweeper.fx_reveal(cell)
_fx_revealing = false _fx_revealing = false
if not ok: if not ok:
_emit_row("fx_reveal", "fx_reveal", Vector2i(-1, -1), ri, "揭示落空(格子不可揭)", 0, {}, "") _emit_row("extended", "fx_reveal", Vector2i(-1, -1), ri, "揭示落空(格子不可揭)", 0, {"effect_id": "fx_reveal"}, "")
continue continue
var snap = _reward_economy.query_full_snapshot(cell) var snap = _reward_economy.query_full_snapshot(cell)
if snap is Dictionary: if snap is Dictionary:
_grant_routed(snap, "positive", "fx_reveal", cell, ri, "fx_reveal") _grant_routed(snap, "positive", "fx_reveal", cell, ri, "fx_reveal")
_emit_row("fx_reveal", "fx_reveal", cell, ri, "专长揭示", 0, {}, "fx_reveal") _emit_row("extended", "fx_reveal", cell, ri, "专长揭示", 0, {"effect_id": "fx_reveal"}, "fx_reveal")
# 补充事实面(fact 取值第二优先源——payload 未携带的运行态读数) # 补充事实面(fact 取值第二优先源——payload 未携带的运行态读数)
@@ -1260,6 +1267,11 @@ func _reset_round_facts() -> void:
# ---- 行协议(Core Rule:公共字段 + 行类型特有字段)---- # ---- 行协议(Core Rule:公共字段 + 行类型特有字段)----
func _emit_row(type: String, source_class: String, origin_cell: Vector2i, ri: int, display: String, value: int, extra: Dictionary, highlight: String = "") -> void: func _emit_row(type: String, source_class: String, origin_cell: Vector2i, ri: int, display: String, value: int, extra: Dictionary, highlight: String = "") -> void:
# 扩展行必载效果身份(行协议 fail-fast——违约行先炸后不发,零发射;开发期违约即炸,
# 与 _assert_row_source 同族 assert 形态)
if type == "extended" and String(extra.get("effect_id", "")) == "":
assert(false, "[战斗结算] 扩展行缺 effect_id 载荷——行协议违约")
return
_assert_row_source(type, source_class, origin_cell) _assert_row_source(type, source_class, origin_cell)
var row := { var row := {
"type": type, "type": type,
+2 -2
View File
@@ -18,7 +18,7 @@ const EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "attack_commi
## 原 MINE_HOOKS 的同义迁移:挖掘/封印/轮末结算面事件) ## 原 MINE_HOOKS 的同义迁移:挖掘/封印/轮末结算面事件)
const MINE_EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "seal_settle", "round_end", "reward_roll"] const MINE_EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "seal_settle", "round_end", "reward_roll"]
## 效果类型词汇(解释器 switch 闭集——需求卡片 §3.3 定稿 19 行;类型实数 24,含共享行拆型: ## 效果类型词汇(解释器 switch 闭集——词表实数以 EFFECT_KINDS 常量为准,含共享行拆型:
## grant_gp/bias_wallet、counter_add/counter_spend、reveal_tiles/remove_mines/refresh_pick、 ## grant_gp/bias_wallet、counter_add/counter_spend、reveal_tiles/remove_mines/refresh_pick、
## bias_rewards/preview。需求卡片标题计 23 系笔误,落码按行内容全收——commit 留痕) ## bias_rewards/preview。需求卡片标题计 23 系笔误,落码按行内容全收——commit 留痕)
const EFFECT_KINDS := ["add_ticket_rider", "modify_ticket", "damage_mult", "open_ticket", const EFFECT_KINDS := ["add_ticket_rider", "modify_ticket", "damage_mult", "open_ticket",
@@ -357,7 +357,7 @@ static func validate_trigger_row(rid: String, row: Dictionary, triggers: Array,
errs.append({"category": "zero_flat", "message": "%s when 键出白名单: %s" % [rid, String(k)]}) errs.append({"category": "zero_flat", "message": "%s when 键出白名单: %s" % [rid, String(k)]})
var fact := String(c.get("fact", "")) var fact := String(c.get("fact", ""))
if not FACTS.has(fact): if not FACTS.has(fact):
errs.append({"category": "enum", "message": "%s fact 出域(事实词汇封闭 9 项): %s" % [rid, fact]}) errs.append({"category": "enum", "message": "%s fact 出域(事实词汇封闭 %d 项): %s" % [rid, FACTS.size(), fact]})
if not OPS.has(String(c.get("op", "eq"))): if not OPS.has(String(c.get("op", "eq"))):
errs.append({"category": "enum", "message": "%s when 算子出域: %s" % [rid, String(c.get("op", ""))]}) errs.append({"category": "enum", "message": "%s when 算子出域: %s" % [rid, String(c.get("op", ""))]})
var do_d: Dictionary[Variant, Variant] = tr.get("do", {}) as Dictionary var do_d: Dictionary[Variant, Variant] = tr.get("do", {}) as Dictionary
+3 -3
View File
@@ -7,7 +7,7 @@
class_name LevelRunner class_name LevelRunner
extends RefCounted extends RefCounted
signal node_entered(node: Dictionary, info: Dictionary) ## info 按 type:battle/boss={} · reward={}(内容待定) · rest={healed,hp,max_hp} · shop={gp} signal node_entered(node: Dictionary, info: Dictionary) ## info 按 type:battle/boss={} · reward={}(三选一停点空载荷——内容经 present_node_curio 拉取) · rest={healed,hp,max_hp} · shop={gp}
signal choices_presented(candidates: Array) ## 选路口(兑付确认后——候选节点引用数组,零自动推进) signal choices_presented(candidates: Array) ## 选路口(兑付确认后——候选节点引用数组,零自动推进)
signal step_row(row: Dictionary) ## 转发当前 战斗结算 结算行 signal step_row(row: Dictionary) ## 转发当前 战斗结算 结算行
signal round_started(round_index: int) signal round_started(round_index: int)
@@ -474,8 +474,8 @@ func _enter_node(node: Dictionary) -> bool:
"battle", "boss": "battle", "boss":
return _start_battle(node) return _start_battle(node)
"reward": "reward":
# 奖励点内容未定(可选类道具不在池、功能道具渠道未定—— # 奖励节点 = 三选一停点(与关卡完成三选一同池同权重同上限);呈现编排归
# 节点保留占位、零兑付直通);推进 = 兑付确认后选路(零自动推进) # curio_interact_controller.present_node_curio;推进 = 兑付确认后选路(零自动推进)
node_entered.emit(node, {}) node_entered.emit(node, {})
return true return true
"rest": "rest":
+4 -4
View File
@@ -268,7 +268,7 @@ func _judge_seal(evt: Dictionary, ri: int) -> void:
"remove_mines": "remove_mines":
var removed: Array[Variant] = oc.minesweeper.remove_mines_in([cell]) var removed: Array[Variant] = oc.minesweeper.remove_mines_in([cell])
if not removed.is_empty(): if not removed.is_empty():
oc._emit_row("mine_neutralized", "fx_seal_smith", cell, ri, "封印匠·危险格中立移除", 0, {}, "") oc._emit_row("extended", "fx_seal_smith", cell, ri, "封印匠·危险格中立移除", 0, {"effect_id": "mine_neutralized"}, "")
"preview_area": "preview_area":
var board_p: BoardData = oc.minesweeper.current_board() var board_p: BoardData = oc.minesweeper.current_board()
var types: Array[Variant] = [] var types: Array[Variant] = []
@@ -278,7 +278,7 @@ func _judge_seal(evt: Dictionary, ri: int) -> void:
types.append(String((n_snap as Dictionary).get("cell_type", ""))) types.append(String((n_snap as Dictionary).get("cell_type", "")))
if oc.treasure_inventory != null and not types.is_empty(): if oc.treasure_inventory != null and not types.is_empty():
oc.treasure_inventory.apply_overlay("reward_preview", board_p.neighbors8(cell)) oc.treasure_inventory.apply_overlay("reward_preview", board_p.neighbors8(cell))
oc._emit_row("fx_preview_area", "", cell, ri, "示警烟火·周围资源类型", 0, {"types": types}, "") oc._emit_row("extended", "fx_warning_beacon", cell, ri, "示警烟火·周围资源类型", 0, {"effect_id": "fx_preview_area", "types": types}, "")
# 雷区图谱(封印流——标中雷额外获得一份格面资源:封印开始产出对标开格; # 雷区图谱(封印流——标中雷额外获得一份格面资源:封印开始产出对标开格;
# 复用 grant 单点,额外兑付走同一 _grant_routed 正向路由) # 复用 grant 单点,额外兑付走同一 _grant_routed 正向路由)
for h in fh_hits: for h in fh_hits:
@@ -306,8 +306,8 @@ func _judge_seal(evt: Dictionary, ri: int) -> void:
# _perfect_forgiven,绝不回写 _perfect_hit_false=契约 2 计数纯净;发射时机与消费点零改动=契约 3) # _perfect_forgiven,绝不回写 _perfect_hit_false=契约 2 计数纯净;发射时机与消费点零改动=契约 3)
oc._perfect_forgiven += 1 oc._perfect_forgiven += 1
_apply_counter_hit(h) # 豁免同时记账(insurance_used battle 域——下次标错 when lt 1 不再命中) _apply_counter_hit(h) # 豁免同时记账(insurance_used battle 域——下次标错 when lt 1 不再命中)
oc._emit_row("insurance_used", "seal_miss", cell, ri, "完美保险·标错豁免", 0, oc._emit_row("extended", "seal_miss", cell, ri, "完美保险·标错豁免", 0,
{"forgiven": oc._perfect_forgiven}, "") {"effect_id": "insurance_used", "forgiven": oc._perfect_forgiven}, "")
var hl := "perfect_broken" if oc._perfect_hit_false - oc._perfect_forgiven == 1 else "" var hl := "perfect_broken" if oc._perfect_hit_false - oc._perfect_forgiven == 1 else ""
oc._emit_row("seal_miss", "seal_miss", cell, ri, "封印未中", 0, {}, hl) oc._emit_row("seal_miss", "seal_miss", cell, ri, "封印未中", 0, {}, hl)
var snap = oc._reward_economy.grant(cell) var snap = oc._reward_economy.grant(cell)
+1 -1
View File
@@ -12,7 +12,7 @@ static func node_banner(runner: LevelRunner, node: Dictionary, t: String) -> Str
static func reward_line() -> String: static func reward_line() -> String:
return "[color=#%s]关卡奖励点(节点内容待定——直通)[/color]" % UiKit.HEX_ROUTE_NOTE return "[color=#%s]关卡奖励点(三选一——选毕推进)[/color]" % UiKit.HEX_ROUTE_NOTE
static func rest_line(healed: int, hp: int, max_hp: int) -> String: static func rest_line(healed: int, hp: int, max_hp: int) -> String:
+2 -3
View File
@@ -70,7 +70,7 @@ var meta: MetaProgressionCore:
get: get:
return get_node(AUTOLOAD_PATH_META) return get_node(AUTOLOAD_PATH_META)
var logger = null # 试玩日志器(JSONL 持久化 + 卡死诊断转储——呈现层组件) var logger = null # 试玩日志器(JSONL 持久化 + 卡死诊断转储——呈现层组件)
var _debug_entry := false # 调试会话门控(写死=true——恒开:启动即开浮窗+两屏角标点亮;_ready 赋值) var _debug_entry := false # 调试会话门控(缺省 false=发布态:角标恒隐、浮窗无 UI 开启口;沿革查 git log)
var _seed_edit: LineEdit var _seed_edit: LineEdit
var confirm_dialog: ConfirmDialog # 通用确认弹窗(消费方编排——现行消费方=选档删档确认;新冒险互斥确认已随卡②主菜单撤出,归卡④大门) var confirm_dialog: ConfirmDialog # 通用确认弹窗(消费方编排——现行消费方=选档删档确认;新冒险互斥确认已随卡②主菜单撤出,归卡④大门)
var _confirm_consumer := "" # 确认弹窗消费方状态量(契约 3 编排——open 前设、分派后清) var _confirm_consumer := "" # 确认弹窗消费方状态量(契约 3 编排——open 前设、分派后清)
@@ -196,8 +196,7 @@ func _ready() -> void:
_build_ui() _build_ui()
_begin_session(false) # 闲时会话(gp_ledger 等探针注入面自 _ready 起恒在——ui_probe 合成段时序) _begin_session(false) # 闲时会话(gp_ledger 等探针注入面自 _ready 起恒在——ui_probe 合成段时序)
_start_overlay.visible = true _start_overlay.visible = true
_debug_entry = true # 调试入口门控写死=true(不读 DS_DEBUG 环境变量;发布前清) debug_badge.visible = _debug_entry # 浮层角标可见性须门控落定后赋值(构建/连接在 _build_ui);_debug_entry 缺省 false=发布态恒隐
debug_badge.visible = _debug_entry # 浮层角标可见性须门控落定后赋值(构建/连接在 _build_ui)
# boot 菜单优先——启动不自动开浮窗(开始屏不得被调试面板盖住); # boot 菜单优先——启动不自动开浮窗(开始屏不得被调试面板盖住);
# 调试入口=UI 最顶层全局浮层角标(LAYER_CONTRACT 末位 float 类,任何 UI 不遮挡) # 调试入口=UI 最顶层全局浮层角标(LAYER_CONTRACT 末位 float 类,任何 UI 不遮挡)
+32
View File
@@ -120,6 +120,7 @@ static func _on_battle_end_continue(root: Variant) -> void:
_enter_level_screen(root) _enter_level_screen(root)
root.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held()))) root.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held())))
root.level_screen.open_choices(root.run_scope.pending_candidates, root.run_scope.runner) root.level_screen.open_choices(root.run_scope.pending_candidates, root.run_scope.runner)
root.level_screen.apply_status(level_map_status_payload(root.run_scope.runner))
root.run_scope.pending_candidates = [] root.run_scope.pending_candidates = []
root._refresh_presentations() root._refresh_presentations()
return return
@@ -171,6 +172,7 @@ static func _on_choices_presented(root: Variant, candidates: Array) -> void:
_enter_level_screen(root) _enter_level_screen(root)
root.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held()))) root.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held())))
root.level_screen.open_choices(candidates, root.run_scope.runner) root.level_screen.open_choices(candidates, root.run_scope.runner)
root.level_screen.apply_status(level_map_status_payload(root.run_scope.runner))
root._refresh_presentations() root._refresh_presentations()
static func _on_route_chosen(root: Variant, node_id: int) -> void: static func _on_route_chosen(root: Variant, node_id: int) -> void:
@@ -198,6 +200,36 @@ static func _exit_level_screen(root: Variant) -> void:
root._close_modal(root.level_screen) root._close_modal(root.level_screen)
root._sync_status_entry() # 同上 root._sync_status_entry() # 同上
# 选路屏状态栏装配(视图零核心态拼装——architecture §1.3:读核心态→拼展示文案集中于装配器)。
# 打开本屏/兑付确认回执后由组合根取成品喂入 apply_status(刷新时点与原视图内自刷一致)。
static func level_map_status_payload(runner: LevelRunner) -> Dictionary:
var atk_bonus := int(runner.run_ledger.bonus.get("atk", 0)) # 加算区住 RunLedger
var ac_bonus := int(runner.run_ledger.bonus.get("armor_class", 0))
var hp_bonus := int(runner.run_ledger.bonus.get("max_hp", 0))
var atk_txt := "%d" % runner.effective_atk()
if atk_bonus > 0:
atk_txt += " +%d" % atk_bonus # 加成角标(ASCII 加号——防像素字体缺 ⁺ 字形出豆腐块)
var ac_txt := "%d" % runner.effective_ac()
if ac_bonus > 0:
ac_txt += " +%d" % ac_bonus
var parts: Array[String] = []
if atk_bonus > 0:
parts.append("攻 +%d" % atk_bonus)
if hp_bonus > 0:
parts.append("上限 +%d" % hp_bonus)
if ac_bonus > 0:
parts.append("AC +%d" % ac_bonus)
return {
"level_label": String(runner.level_row.get("display_name", "")),
"gp_text": "◈ %d" % int(runner.gp),
"hp_text": "HP %d/%d" % [int(runner.hero_hp), int(runner.effective_max_hp())],
"hp_bar_redraw": true,
"attr_text": "攻击 %s · 护甲 AC %s" % [atk_txt, ac_txt],
"bonus_text": " · ".join(parts),
"bonus_row_visible": not parts.is_empty(),
}
static func _on_abandon_requested(root: Variant) -> void: static func _on_abandon_requested(root: Variant) -> void:
# 放弃入口(冒险结构.md 已放开「MVP 无界面入口」后置条款; # 放弃入口(冒险结构.md 已放开「MVP 无界面入口」后置条款;
# 入口挂选路停点 = 天然非战斗期,拒绝分支仅防御在案):受理 → 冒险放弃结算(同失败型, # 入口挂选路停点 = 天然非战斗期,拒绝分支仅防御在案):受理 → 冒险放弃结算(同失败型,
+1 -1
View File
@@ -14,7 +14,7 @@ var _log: RichTextLabel
var _summary: Label var _summary: Label
var _hint: Label var _hint: Label
var _mode := "dig" # dig = 收起摘要 / settle = 实时流(模式归组合根驱动) var _mode := "dig" # dig = 收起摘要 / settle = 实时流(模式归组合根驱动)
var _round_rows: Array[Dictionary] = [] # 本结算轮行缓冲(上轮摘要构建源) var _round_rows: Array[String] = [] # 本结算轮行缓冲(上轮摘要构建源——append 的是 _format_row 格式化产物 String)
var _pending_agg := {} # 聚合计数行 pending(type+batch_key → {row, count}) var _pending_agg := {} # 聚合计数行 pending(type+batch_key → {row, count})
+20 -31
View File
@@ -52,6 +52,7 @@ var _drawn_disp: Dictionary[int, Dictionary] = {} # 本停点实际绘制记
var _node_icons := {} # 节点图标贴图(load_tex 守卫——缺位 null → 字形回退,冒烟/无素材检出安全) var _node_icons := {} # 节点图标贴图(load_tex 守卫——缺位 null → 字形回退,冒烟/无素材检出安全)
var _noise_rect: TextureRect # 屏底石纹(换层色板渐变承载面——null = 纯色回退无挂点) var _noise_rect: TextureRect # 屏底石纹(换层色板渐变承载面——null = 纯色回退无挂点)
var _tint_level := 0 # 当前已应用 tint 的层号(0 = 未应用) var _tint_level := 0 # 当前已应用 tint 的层号(0 = 未应用)
var _layout_cache := {"nodes": [], "canvas_size": Vector2.ZERO, "pos": {}} # 布局缓存(_layout 纯函数于 (nodes, canvas)——键=nodes 内容等价+画布尺寸,图态换代或画布尺寸变即自然失效)
func _init() -> void: func _init() -> void:
@@ -274,7 +275,6 @@ func open_choices(cands: Array, runner: LevelRunner) -> void:
for cand in cands: for cand in cands:
_cand_ids.append(_id_of(cand)) _cand_ids.append(_id_of(cand))
_fit_canvas(_runner.nodes()) _fit_canvas(_runner.nodes())
_refresh_status()
_apply_level_tint(int(_runner.level_row.get("level_index", 0))) _apply_level_tint(int(_runner.level_row.get("level_index", 0)))
visible = true visible = true
if not was_visible: if not was_visible:
@@ -322,35 +322,17 @@ func _apply_level_tint(level_index: int) -> void:
Color(target.r, target.g, target.b, UiKit.BACKDROP_TILE_ALPHA), UiKit.motion(0.45)) # 0.14 弱质感档随 tint 保持 Color(target.r, target.g, target.b, UiKit.BACKDROP_TILE_ALPHA), UiKit.motion(0.45)) # 0.14 弱质感档随 tint 保持
# 状态栏刷新(命令式纪律——组合根打开本屏/兑付确认回执后重查;零推送信号订阅) # 状态栏投喂(命令式纪律——组合根经 level_map_status_payload 取成品后喂入;零推送信号订阅、零核心态拼装)
func _refresh_status() -> void: func apply_status(payload: Dictionary) -> void:
if _runner == null: _level_label.text = String(payload["level_label"])
return _gp_value.text = String(payload["gp_text"])
_level_label.text = String(_runner.level_row.get("display_name", "")) _hp_value.text = String(payload["hp_text"])
_gp_value.text = "◈ %d" % int(_runner.gp) if bool(payload["hp_bar_redraw"]):
var max_hp: int = _runner.effective_max_hp()
_hp_value.text = "HP %d/%d" % [int(_runner.hero_hp), int(max_hp)]
_hp_bar.queue_redraw() _hp_bar.queue_redraw()
var atk_bonus := int(_runner.run_ledger.bonus.get("atk", 0)) # 加算区住 RunLedger _attr_value.text = String(payload["attr_text"])
var ac_bonus := int(_runner.run_ledger.bonus.get("armor_class", 0)) _bonus_value.text = String(payload["bonus_text"])
var atk_txt := "%d" % _runner.effective_atk()
if atk_bonus > 0:
atk_txt += " +%d" % atk_bonus # 加成角标(ASCII 加号——防像素字体缺 ⁺ 字形出豆腐块)
var ac_txt := "%d" % _runner.effective_ac()
if ac_bonus > 0:
ac_txt += " +%d" % ac_bonus
_attr_value.text = "攻击 %s · 护甲 AC %s" % [atk_txt, ac_txt]
var hp_bonus := int(_runner.run_ledger.bonus.get("max_hp", 0))
var parts: Array[String] = []
if atk_bonus > 0:
parts.append("攻 +%d" % atk_bonus)
if hp_bonus > 0:
parts.append("上限 +%d" % hp_bonus)
if ac_bonus > 0:
parts.append("AC +%d" % ac_bonus)
_bonus_value.text = " · ".join(parts)
# 特性持有行可见性(与加成行并框——任一非空整框显示;文案归组合根 set_feat_holdings) # 特性持有行可见性(与加成行并框——任一非空整框显示;文案归组合根 set_feat_holdings)
_bonus_row.visible = not parts.is_empty() or _feat_value.text != "" _bonus_row.visible = bool(payload["bonus_row_visible"]) or _feat_value.text != ""
# ---- 图上命中(布局决策 2——唯一提交通道;探针 press_candidate 同路径) ---- # ---- 图上命中(布局决策 2——唯一提交通道;探针 press_candidate 同路径) ----
@@ -444,6 +426,7 @@ func _draw_graph(canvas: Control) -> void:
_drawn_disp.clear() # 绘制记录随本帧重建——只反映实际发生的绘制(接线坏的判据面) _drawn_disp.clear() # 绘制记录随本帧重建——只反映实际发生的绘制(接线坏的判据面)
var nodes: Array[Variant] = _runner.nodes() var nodes: Array[Variant] = _runner.nodes()
var pos := _layout(nodes, canvas) var pos := _layout(nodes, canvas)
var font := get_theme_default_font() # 绘制路径字体单次取用(三处 draw_string 共享——零语义差)
var current_id: int = _runner.current_node_id() var current_id: int = _runner.current_node_id()
# 当前节点 id(单链=下标 / 图=编号)——visited_ids() 已按编号空间解析 # 当前节点 id(单链=下标 / 图=编号)——visited_ids() 已按编号空间解析
var visited: Array[Variant] = _runner.visited_ids() var visited: Array[Variant] = _runner.visited_ids()
@@ -499,12 +482,11 @@ func _draw_graph(canvas: Control) -> void:
canvas.draw_texture_rect(icon, Rect2(p - Vector2(NODE_ICON_PX, NODE_ICON_PX) / 2.0, canvas.draw_texture_rect(icon, Rect2(p - Vector2(NODE_ICON_PX, NODE_ICON_PX) / 2.0,
Vector2(NODE_ICON_PX, NODE_ICON_PX)), false, tint) Vector2(NODE_ICON_PX, NODE_ICON_PX)), false, tint)
else: else:
var font := get_theme_default_font()
var label := String(NODE_LABELS.get(t, "?")) var label := String(NODE_LABELS.get(t, "?"))
canvas.draw_string(font, p + Vector2(-6, 2), label, HORIZONTAL_ALIGNMENT_LEFT, -1, 16, UiKit.INK_ROUTE) canvas.draw_string(font, p + Vector2(-6, 2), label, HORIZONTAL_ALIGNMENT_LEFT, -1, 16, UiKit.INK_ROUTE)
# 编号(右上角——与图标占位区零重叠;双色墨随态) # 编号(右上角——与图标占位区零重叠;双色墨随态)
var num_ink := UiKit.INK_ROUTE if col == COL_ROOM_CURRENT else UiKit.WARM var num_ink := UiKit.INK_ROUTE if col == COL_ROOM_CURRENT else UiKit.WARM
canvas.draw_string(get_theme_default_font(), Vector2(rect.end.x - 16.0, rect.position.y + 8.0), canvas.draw_string(font, Vector2(rect.end.x - 16.0, rect.position.y + 8.0),
str(nid), HORIZONTAL_ALIGNMENT_RIGHT, 14, 10, num_ink) str(nid), HORIZONTAL_ALIGNMENT_RIGHT, 14, 10, num_ink)
# 类型文字标签(demo ④:节点下方 11px;当前位=「当前位置」金、候选=类型名+「 · 可选」烛光黄、 # 类型文字标签(demo ④:节点下方 11px;当前位=「当前位置」金、候选=类型名+「 · 可选」烛光黄、
# 其余=类型名暖米弱化;文字+图标+形状三通道) # 其余=类型名暖米弱化;文字+图标+形状三通道)
@@ -512,12 +494,16 @@ func _draw_graph(canvas: Control) -> void:
rec["draw_label"] = true rec["draw_label"] = true
rec["label_text"] = String(disp["label_text"]) rec["label_text"] = String(disp["label_text"])
var type_ink := COL_ROOM_CURRENT if nid == current_id else (UiKit.CANDLE if cand_ids.has(nid) else UiKit.WARM_FAINT) var type_ink := COL_ROOM_CURRENT if nid == current_id else (UiKit.CANDLE if cand_ids.has(nid) else UiKit.WARM_FAINT)
canvas.draw_string(get_theme_default_font(), Vector2(p.x - 44.0, rect.end.y + 15.0), canvas.draw_string(font, Vector2(p.x - 44.0, rect.end.y + 15.0),
String(disp["label_text"]), HORIZONTAL_ALIGNMENT_CENTER, 88, 11, type_ink) String(disp["label_text"]), HORIZONTAL_ALIGNMENT_CENTER, 88, 11, type_ink)
_drawn_disp[nid] = rec _drawn_disp[nid] = rec
func _layout(nodes: Array, canvas: Control) -> Dictionary: func _layout(nodes: Array, canvas: Control) -> Dictionary:
# 缓存命中键 = nodes 内容等价(GDScript Array== 逐元素比较)+画布尺寸——布局是二者与
# _content_size(经 _fit_canvas 派生于 nodes)的纯函数,键间接覆盖派生链;换代即自然失效。
if _layout_cache.nodes == nodes and _layout_cache.canvas_size == canvas.size:
return _layout_cache.pos
var layer := {} var layer := {}
var slot := {} var slot := {}
_assign_layers(nodes, layer, slot) _assign_layers(nodes, layer, slot)
@@ -526,6 +512,9 @@ func _layout(nodes: Array, canvas: Control) -> Dictionary:
for node in nodes: for node in nodes:
pos[node] = origin + Vector2(LAYER_BASE_X + int(layer[node]) * LAYER_STEP_X, pos[node] = origin + Vector2(LAYER_BASE_X + int(layer[node]) * LAYER_STEP_X,
SLOT_BASE_Y + int(slot[node]) * SLOT_STEP_Y) SLOT_BASE_Y + int(slot[node]) * SLOT_STEP_Y)
_layout_cache.nodes = nodes
_layout_cache.canvas_size = canvas.size
_layout_cache.pos = pos
return pos return pos
+40 -8
View File
@@ -28,6 +28,7 @@ var _tested_refresh_chain := false
var _tested_tooltip := false var _tested_tooltip := false
var _tested_copy := false # 迭代36 卡A——C 区两段头真实入口断言(每 run 一次) var _tested_copy := false # 迭代36 卡A——C 区两段头真实入口断言(每 run 一次)
var _tested_curio_head := false # 迭代36 卡A——三选一头部旧话清零断言(奖励点停点捕获) var _tested_curio_head := false # 迭代36 卡A——三选一头部旧话清零断言(奖励点停点捕获)
var _tested_round_summary := false # 卡⑥ wr1-b——上轮摘要恢复断言(战中场一次性判据)
var _tested_map_cap := false # 迭代36 卡A——地图屏加成帽换代断言(每 run 一次) var _tested_map_cap := false # 迭代36 卡A——地图屏加成帽换代断言(每 run 一次)
var _tested_status := false # 状态面板角标开合/守卫断言(每 run 一次) var _tested_status := false # 状态面板角标开合/守卫断言(每 run 一次)
var _tested_map_status := false # 地图屏角标一次性 case(选路态零扰动) var _tested_map_status := false # 地图屏角标一次性 case(选路态零扰动)
@@ -320,10 +321,10 @@ func _ready() -> void:
_problems.append("音量滑杆未实时生效(master bus db %f ≠ %f)" % [AudioServer.get_bus_volume_db(master_idx), expect_db]) _problems.append("音量滑杆未实时生效(master bus db %f ≠ %f)" % [AudioServer.get_bus_volume_db(master_idx), expect_db])
if absf(iso_s.volume_linear - 0.3) > 0.001: if absf(iso_s.volume_linear - 0.3) > 0.001:
_problems.append("音量未入 store(%f ≠ 0.3)" % iso_s.volume_linear) _problems.append("音量未入 store(%f ≠ 0.3)" % iso_s.volume_linear)
# 调试入口断言(全局浮层角标=UI 最顶层、任何 UI 不遮挡——本段设置面 # 调试入口断言(卡⑦ c4 发布态换代——角标恒隐;原「最顶层浮层不被遮挡」
# 开中正是遮挡场景:浮层角标此时仍应可见;点击开浮窗路径归 tune_probe) # 回归语义随调试浮层发布态退役,申报见迭代 38 实现报告 §8.2)
if main.debug_badge == null or not main.debug_badge.visible: if main.debug_badge == null or main.debug_badge.visible:
_problems.append("设置面开中调试浮层角标不可见(最顶层浮层失守——被遮挡)") _problems.append("设置面开中调试角标可见(发布态恒隐失守)")
var settings_close: Button = ProbeUiUtil.find_button(main.settings_overlay, "返回", "prefix") var settings_close: Button = ProbeUiUtil.find_button(main.settings_overlay, "返回", "prefix")
if settings_close == null: if settings_close == null:
_problems.append("设置面返回按钮缺失") _problems.append("设置面返回按钮缺失")
@@ -415,16 +416,27 @@ func _ready() -> void:
# 未适配则走到即卡死)。两态分派:result 态(兑付反馈——点继续推进)/ # 未适配则走到即卡死)。两态分派:result 态(兑付反馈——点继续推进)/
# 待选态(点首件+确认)。真实信号路径全线。〔无替换子态——装配不可变契约。〕 # 待选态(点首件+确认)。真实信号路径全线。〔无替换子态——装配不可变契约。〕
# 三选一停点输入锁定断言(停点态断言面:待选/兑付两态共用此层)。 # 三选一停点输入锁定断言(停点态断言面:待选/兑付两态共用此层)。
if not _tested_curio_head and main.run_scope.adventure != null and not main.run_scope.adventure._curio_pending.is_empty() and String(main.curio_overlay._header).contains("关卡奖励点"): if not _tested_curio_head and main.run_scope.adventure != null and not main.run_scope.adventure._node_curio_pending.is_empty() and String(main.curio_overlay._header).contains("关卡奖励点"):
# 停点态判据 = _node_curio_pending(奖励节点三选一停点住此——原条件查
# _curio_pending〔关卡完成三选一态〕与奖励头部互斥、恒不可命中=拦截能力未证病根,
# 卡⑤ d4 转正随修;头部校验串本体零触碰)
_tested_curio_head = true _tested_curio_head = true
var ch := String(main.curio_overlay._header) var ch := String(main.curio_overlay._header)
if not ch.contains("冒险牌库") or not ch.contains("属性值提升与补给") or ch.contains("同权重") or ch.contains("同上限") or ch.contains("提升池"): if not ch.contains("冒险牌库") or not ch.contains("属性值提升与补给") or ch.contains("同权重") or ch.contains("同上限") or ch.contains("提升池"):
_problems.append("三选一头部旧话残留或新话缺失(wa4——%s)" % ch.replace(" _problems.append("三选一头部旧话残留或新话缺失(wa4——%s)" % ch.replace("
", "|")) ", "|"))
# 奖励节点战报文案换代断言(卡⑤ wr2——reward_line 同屏打印于停点拉起前,
# 同一驱动路径承载;旧「内容待定」残留或新话缺失即红)
var logtext: String = main.log_view.parsed_text()
if not logtext.contains("关卡奖励点(三选一——选毕推进)") or logtext.contains("内容待定"):
_problems.append("奖励节点战报文案未换代(卡⑤ wr2——旧话残留或新话缺失)")
if not main._input_locked(): if not main._input_locked():
_problems.append("三选一停点打开时输入未锁定") _problems.append("三选一停点打开时输入未锁定")
if main.curio_overlay._continue_btn.visible: if main.curio_overlay._continue_btn.visible:
main.curio_overlay.press_continue() main.curio_overlay.press_continue()
elif main.run_scope.adventure != null and not main.run_scope.adventure._node_curio_pending.is_empty():
main.curio_overlay.press_card(0) # 奖励节点待选态:点首件+确认(真实按钮信号路径——与关卡完成停点同形)
main.curio_overlay.press_confirm()
elif main.run_scope.adventure != null and not main.run_scope.adventure._curio_pending.is_empty(): elif main.run_scope.adventure != null and not main.run_scope.adventure._curio_pending.is_empty():
main.curio_overlay.press_card(0) main.curio_overlay.press_card(0)
main.curio_overlay.press_confirm() main.curio_overlay.press_confirm()
@@ -470,7 +482,16 @@ func _ready() -> void:
cap_found = true cap_found = true
if not cap_found: if not cap_found:
_problems.append("地图屏加成帽未换代为「属性值提升」(wa2)") _problems.append("地图屏加成帽未换代为「属性值提升」(wa2)")
main.level_screen.press_candidate(_route_clicks % 2) # 选路定向(卡⑤ d4——wa4 转正的必达前提):候选含奖励节点优先选(真实
# press_candidate 命中路径零变化,只改索引策略);无奖励候选回退交替(岔路使用率观测面保留)
var pick := _route_clicks % 2
if main.run_scope.runner != null:
for nd in main.run_scope.runner.nodes():
var nd_id: int = main.run_scope.runner.node_id_of(nd)
if String(nd.get("type", "")) == "reward" and main.level_screen._cand_ids.has(nd_id):
pick = main.level_screen._cand_ids.find(nd_id)
break
main.level_screen.press_candidate(pick)
_route_clicks += 1 _route_clicks += 1
_seals_placed.clear() _seals_placed.clear()
continue continue
@@ -536,8 +557,14 @@ func _ready() -> void:
"phase": main.run_scope.settlement.action_economy.phase, "ap": main.run_scope.settlement.action_economy.ap, "phase": main.run_scope.settlement.action_economy.phase, "ap": main.run_scope.settlement.action_economy.ap,
"settling": diag.settling, "settling": diag.settling,
} }
if JSON.stringify(st) == JSON.stringify(_last_state): # 上轮摘要恢复断言(卡⑥ wr1-b——改前 _round_rows 类型错 append 全数失败、缓冲恒空、
# 排播演播期豁免(硬锁 v3——合法停顿态):演播期棋盘锁生效、probe 点击被吞、 # 摘要自引入类型错以来从未显示):战中场观测点=round≥2 且排播队列已排空(轮首分隔行
# 已消费=finish_settlement_round 已刷);不挂循环出口——收尾的续玩重建段会再走 log_view.clear()
if not _tested_round_summary and int(st["round"]) >= 2 and main._row_queue.is_empty():
_tested_round_summary = true
if main.log_view._summary.text == "":
_problems.append("上轮摘要恒空(卡⑥ wr1-b——结算行缓冲仍失真)")
if JSON.stringify(st) == JSON.stringify(_last_state): # 排播演播期豁免(硬锁 v3——合法停顿态):演播期棋盘锁生效、probe 点击被吞、
# 逻辑态恒定属预期。判据=纯队列(与 main 锁同源)。Waiting 收尾等待期**不豁免**—— # 逻辑态恒定属预期。判据=纯队列(与 main 锁同源)。Waiting 收尾等待期**不豁免**——
# 锁若把收尾链卡死,此处 stall 照报(探测面)。 # 锁若把收尾链卡死,此处 stall 照报(探测面)。
# T1 D1 处置=保留豁免+注记:冻结/缓存/重放语义不再靠豁免绕行,由 _locktest_tick # T1 D1 处置=保留豁免+注记:冻结/缓存/重放语义不再靠豁免绕行,由 _locktest_tick
@@ -695,6 +722,11 @@ func _ready() -> void:
else: else:
print("[click_probe] 开场行②③判定:续玩重建开场行带后缀(bonus.atk=5、pct=0)— wait=%d 帧" % ol_r_wait) print("[click_probe] 开场行②③判定:续玩重建开场行带后缀(bonus.atk=5、pct=0)— wait=%d 帧" % ol_r_wait)
# wa4 必达判据(卡⑤ d4 转正——条件位退役):定向选路保证到达奖励节点三选一停点,
# 全程未命中即红(拦截能力必证——test-conventions §6 条件断言转正口径)
if not _tested_curio_head:
_problems.append("wa4 转正失守:探针全程未到达奖励节点三选一停点(定向选路失效或奖励节点不可达)〔卡⑤〕")
print("[click_probe] 循环退出(done=%s budget=%s)问题=%d" % [ print("[click_probe] 循环退出(done=%s budget=%s)问题=%d" % [
str(_done), str(action_budget), _problems.size()]) str(_done), str(action_budget), _problems.size()])
for p in _problems: for p in _problems:
+3 -5
View File
@@ -117,12 +117,10 @@ func _ready() -> void:
# 棋盘输入不锁、状态角标不置灰(「默认开×遮挡复活」下必锁死的破约回归面); # 棋盘输入不锁、状态角标不置灰(「默认开×遮挡复活」下必锁死的破约回归面);
# 对局中批量模拟/两层调试两钮禁用;当局数据六项与只读访问器逐项一致;关闭钮真实路径收窗恢复 # 对局中批量模拟/两层调试两钮禁用;当局数据六项与只读访问器逐项一致;关闭钮真实路径收窗恢复
var dbg_badge: Button = main.debug_badge var dbg_badge: Button = main.debug_badge
if dbg_badge == null or not dbg_badge.visible: if dbg_badge == null or dbg_badge.visible:
problems.append("对局中调试浮层角标未点亮(全局浮层最顶层失守)") problems.append("对局中调试浮层角标非恒隐(发布态语义失守——卡⑦ 删写死行批)")
else: else:
if dbg_badge.disabled: main._show_debug_overlay() # 对局中开浮窗回归经直调承载(:61 既有直调先例——发布态角标恒隐,按钮信号路径退役)
problems.append("战斗屏调试角标被置灰(T2 排除式判据失守)")
dbg_badge.pressed.emit() # 真实按钮信号路径 → 组合根 _on_debug_badge_pressed → C6 单口
if not main.debug_overlay.visible: if not main.debug_overlay.visible:
problems.append("对局中角标开浮窗失败(P2 甲对局中可达失守)") problems.append("对局中角标开浮窗失败(P2 甲对局中可达失守)")
if main._input_locked(): if main._input_locked():
+1 -1
View File
@@ -481,7 +481,7 @@ static func _on_node_entered(node: Dictionary, info: Dictionary, runner, st: Dic
st.boss_depth = int(node.get("depth", -1)) st.boss_depth = int(node.get("depth", -1))
st.hp_at_boss = runner.hero_hp st.hp_at_boss = runner.hero_hp
elif t == "reward": elif t == "reward":
runner.continue_after_node() # 奖励点 = 内容未定直通节点 runner.continue_after_node() # autoplay 直通不解三选一——sim 策略选择(测量期不进兑付面,非机制缺位)
elif t == "rest": elif t == "rest":
st.rest_healed = int(info.get("healed", 0)) st.rest_healed = int(info.get("healed", 0))
st.hp_before_rest = int(info.get("hp", 0)) - st.rest_healed st.hp_before_rest = int(info.get("hp", 0)) - st.rest_healed
+3 -3
View File
@@ -1,5 +1,5 @@
{ {
"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),其余条目原样保留。2026-10-22 测试术语与层级重构批 A 改键:01 冒烟全量→01 系统测试(同一断言面 smoke_all.gd,运行器换 tools/run_system_test.py,过渡期判定口径不变)、02 单元套件→02 单元与集成测试(同一 run_all.gd 入口,名从实)、新增 00 冒烟测试(tools/run_smoke.py+game/tests/smoke_boot.tscn,重写后单跑实测 17 项 0 失败 PASS);09 静态扫描基线 exit=1(fp=4354b2aa72a7)系滞后红,本批单跑实测 exit=0(与 2026-10-22 16:20 回执一致),基线改回绿;其余条目原样保留。;2026-10-23 主会话记 07 doc_link 为存量红(用户令忽略:锻造工坊.md 死链的修复在另一会话未合入,合回闸豁免;外部修复合入后应重生成基线归绿)", "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),其余条目原样保留。2026-10-22 测试术语与层级重构批 A 改键:01 冒烟全量→01 系统测试(同一断言面 smoke_all.gd,运行器换 tools/run_system_test.py,过渡期判定口径不变)、02 单元套件→02 单元与集成测试(同一 run_all.gd 入口,名从实)、新增 00 冒烟测试(tools/run_smoke.py+game/tests/smoke_boot.tscn,重写后单跑实测 17 项 0 失败 PASS);09 静态扫描基线 exit=1(fp=4354b2aa72a7)系滞后红,本批单跑实测 exit=0(与 2026-10-22 16:20 回执一致),基线改回绿;其余条目原样保留。;2026-10-23 主会话记 07 doc_link 为存量红(用户令忽略:锻造工坊.md 死链的修复在另一会话未合入,合回闸豁免;外部修复合入后应重生成基线归绿);2026-10-26 用户特批带红合并(迭代 38 收口):09 静态扫描记存量红——gdstyle 恶化 2 条(battle_orchestrator.gd max-file-length 1314>1302=卡④头注必载内容、level_map_screen.gd max-class-variables 22>21=卡①缓存成员,均为卡片义务的记账漂移非缺陷,压缩还债经计算不可行),用户裁「不管引入的恶化直接提交、特许免测试重跑」;后续批次若触及两文件可顺带还债或经批准重生成基线归绿",
"gates": { "gates": {
"00 冒烟测试": { "00 冒烟测试": {
"exit": "0", "exit": "0",
@@ -46,8 +46,8 @@
"fp": "green" "fp": "green"
}, },
"09 静态扫描": { "09 静态扫描": {
"exit": "0", "exit": "1",
"fp": "green" "fp": "user-approved-iter38"
}, },
"10 熵增闸": { "10 熵增闸": {
"exit": "0", "exit": "0",
+3 -3
View File
@@ -49,7 +49,7 @@
"rows": 9216 "rows": 9216
}, },
"perfect": { "perfect": {
"sha256": "a37ba2ca47f8619f22aa3dd8a761fffc71916d6c3172f5ff0aeb2b0557aba7e5", "sha256": "962b6f68440bcf95f2b21ac4cc07a8523655f044ae6355aa674d0db27bdecc1d",
"rows": 60774 "rows": 60774
} }
} }
@@ -103,7 +103,7 @@
"rows": 9670 "rows": 9670
}, },
"perfect": { "perfect": {
"sha256": "f6bb9fd5ba5fc183fd29adbb00ae526a5c6852e7f52f9f597a0b4d853baf05d6", "sha256": "06cb4dcc26c56343c61bc8d972238be81b88e3132d7e9b274399f4cfd04cb56e",
"rows": 62765 "rows": 62765
} }
} }
@@ -157,7 +157,7 @@
"rows": 7124 "rows": 7124
}, },
"perfect": { "perfect": {
"sha256": "7d16b4efb2bb48355a7919011b265de83ff907bd9c83818dc66e69108967d5a3", "sha256": "c3d0d37c5f40d208fb35d323b4c7a11c4f7ebe77f9f0eaba0fc3445f00971277",
"rows": 59435 "rows": 59435
} }
} }
+11 -2
View File
@@ -1,7 +1,8 @@
# 视觉 golden 基线(渲染闸的比对基准) # 视觉 golden 基线(渲染闸的比对基准)
四张基线:棋盘三图(`board_L1_dungeon` / `board_L2_underdark` / `board_L3_deep_underdark`, 五张基线:棋盘三图(`board_L1_dungeon` / `board_L2_underdark` / `board_L3_deep_underdark`,
裁剪区 900×760)+ 启动态菜单(`menu_boot`,全屏)。比对判据与工具见 `tools/golden_compare.py` 裁剪区 900×760)+ 启动态菜单(`menu_boot`,全屏)+ 关卡画面(`level_map`,全屏)。
比对判据与工具见 `tools/golden_compare.py`
(逐像素灰度差 >16 的像素占比 ≤2%)。 (逐像素灰度差 >16 的像素占比 ≤2%)。
## 时效判定纪律(迭代 29 T7 立) ## 时效判定纪律(迭代 29 T7 立)
@@ -27,3 +28,11 @@
字形——晚于 2026-10-08 建基提交,与本批零因果。呈报后用户裁「重制三张基线」, 字形——晚于 2026-10-08 建基提交,与本批零因果。呈报后用户裁「重制三张基线」,
经 `tools/golden_baseline_refresh.sh` 换代;复闸 changed = 0.00% / 0.00% / 0.00%, 经 `tools/golden_baseline_refresh.sh` 换代;复闸 changed = 0.00% / 0.00% / 0.00%,
启动态菜单 0.00%(未重制)。 启动态菜单 0.00%(未重制)。
- 2026-10-26(迭代 38 卡① l4):**新增基线 `level_map`**(改前建基——时点铁序)。
来源 = shot_probe `_snap("level")` 帧(入口停点出图后第 4 帧,淡入完成态),
跨跑次逐像素一致性实测 changed=0.00% 后落盘。承载迭代 38 卡① l1–l3 结构重构的
逐像素对拍(改后对拍 changed=0.00% 为验收主锚);对拍经 `tools/golden_compare.py`
直跑(render_gate 现行只对拍棋盘三图+menu_boot,闸件不在本批改动面)。
- 2026-10-26(迭代 38 卡⑦ c8):**menu_boot 显式重制**(预期面视觉变化——调试角标
发布态恒隐,基线右下角幽灵角标像素消失)。改后实测 changed=0.15%(≈卡面预估 0.14%,
容差内但按 render_gate「预期面视觉变化=显式重制基线」口径重制);复闸 changed=0.00%。
Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 311 KiB

+1
View File
@@ -70,6 +70,7 @@ func _suites() -> Array:
{"path": "res://tests/unit/battle_settlement/round_flow_skill_pool_test.gd", "expect": 0}, # 轮内流与技能池宿主(全战斗回放/轮内刷新与 V5 快照单源/遭遇区间骰/结算五段全序/技能池数据面·回放独立·EV 锚·死亡打断·校验拒绝/妨害附加/四桶计数——批 B 自 smoke_all 迁入) {"path": "res://tests/unit/battle_settlement/round_flow_skill_pool_test.gd", "expect": 0}, # 轮内流与技能池宿主(全战斗回放/轮内刷新与 V5 快照单源/遭遇区间骰/结算五段全序/技能池数据面·回放独立·EV 锚·死亡打断·校验拒绝/妨害附加/四桶计数——批 B 自 smoke_all 迁入)
{"path": "res://tests/unit/battle_settlement/variant_placement_test.gd", "expect": 0}, # 变异布置宿主(战内布置段门控与数量公式/全局变异兜底回落与资源面偏斜——批 B 自 smoke_all 迁入) {"path": "res://tests/unit/battle_settlement/variant_placement_test.gd", "expect": 0}, # 变异布置宿主(战内布置段门控与数量公式/全局变异兜底回落与资源面偏斜——批 B 自 smoke_all 迁入)
{"path": "res://tests/unit/battle_settlement/skill_unlock_test.gd", "expect": 0}, # Boss 半血解锁技·容器机制(门谓词/闩不可逆/双面过滤/保底不逼锁定/零扰动) {"path": "res://tests/unit/battle_settlement/skill_unlock_test.gd", "expect": 0}, # Boss 半血解锁技·容器机制(门谓词/闩不可逆/双面过滤/保底不逼锁定/零扰动)
{"path": "res://tests/unit/battle_settlement/extended_row_violation_test.gd", "expect": 1, "negative": true}, # 卡④ wr2-np 扩展行必载 effect_id 负路径(行协议运行期违约面首个负面套件——独立进程豁免错误扫描)
{"path": "res://tests/unit/contracts/system_contracts_test.gd", "expect": 0}, {"path": "res://tests/unit/contracts/system_contracts_test.gd", "expect": 0},
{"path": "res://tests/unit/contracts/source_guard_test.gd", "expect": 0}, # 源码契约宿主(project.godot 警告配置与类型闸/产品码卫生死代码·影子账·层序单口——批 B 自 smoke_all 迁入;被测对象=仓库自身源文件,裁决五新设) {"path": "res://tests/unit/contracts/source_guard_test.gd", "expect": 0}, # 源码契约宿主(project.godot 警告配置与类型闸/产品码卫生死代码·影子账·层序单口——批 B 自 smoke_all 迁入;被测对象=仓库自身源文件,裁决五新设)
{"path": "res://tests/unit/presentation/ui_font_asset_test.gd", "expect": 0}, {"path": "res://tests/unit/presentation/ui_font_asset_test.gd", "expect": 0},
+2 -2
View File
@@ -203,8 +203,8 @@ func _smoke_boot_chain() -> void:
# 入口=开始屏右下角「调试」角标+对局中两屏角标) # 入口=开始屏右下角「调试」角标+对局中两屏角标)
check(not main.debug_overlay.visible, check(not main.debug_overlay.visible,
"V2f 调试浮窗 boot 不自动开(菜单优先)〔2026-10-16 用户令〕") "V2f 调试浮窗 boot 不自动开(菜单优先)〔2026-10-16 用户令〕")
check(main.debug_badge != null and main.debug_badge.visible, check(main.debug_badge != null and not main.debug_badge.visible,
"V2f 调试浮层角标 boot 可见(全局浮层入口在座)〔2026-10-16 用户令〕") "V2f 调试浮层角标发布态恒隐(2026-10-25 删写死行批)")
main.queue_free() main.queue_free()
await process_frame await process_frame
for p in ["user://smoke_boot_chain.json", "user://smoke_boot_chain.json.tmp"]: for p in ["user://smoke_boot_chain.json", "user://smoke_boot_chain.json.tmp"]:
@@ -0,0 +1,38 @@
# 致命类用例(独立进程,期望退出码 1)——迭代 38 卡④ wr2-np:扩展行必载 effect_id 负路径断言
# 违约直调 _emit_row(type=extended 无 effect_id 载荷)→ a2 行协议校验 assert 炸(debug 实况,
# 输出中的 SCRIPT ERROR 为其测试目标——负面套件全文折叠)→ 零发射;
# 回归态(校验段被移除)= 违约行照发 → 退出码 2 ≠ 期望 1(闸红)。
# 独立进程理由:GDScript assert 失败必产 SCRIPT ERROR 且不可捕获,非负面套件会被 run_all 错误
# 扫描判红(迭代 38 卡④ grilling 第二轮同款结论——负面豁免机制为此存在,counter_prefix 先例)。
# 运行:godot --headless --path game --script res://tests/unit/battle_settlement/extended_row_violation_test.gd
extends SceneTree
const Kit := preload("res://tests/unit/test_kit.gd")
func _init() -> void:
var db = load("res://core/config_db.gd").new()
if not db.load_tables("res://data/tables", false):
push_error("[extended_row_violation_test] 表加载失败")
quit(2)
return
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(74100)
var oc = load("res://core/battle_orchestrator.gd").new(db, rng)
var hits: Array = []
oc.step_row.connect(func(row): hits.append(row))
# 违约直调(缺 effect_id 载荷——行协议违约):debug 下 a2 校验 assert 炸中断 _emit_row 本体,
# 控制流回到本函数(headless assert 语义=中断当前函数不中止运行);release 下 return 零发射同效
oc._emit_row("extended", "fx_reveal", Vector2i.ZERO, 1, "负路径", 0, {}, "")
if not hits.is_empty():
push_error("[extended_row_violation_test] 违约行被发射——a2 校验失守(回归)")
quit(2)
return
# 对照组:合法扩展行(带 effect_id)照常发射——校验只拦违约形态
oc._emit_row("extended", "fx_reveal", Vector2i.ZERO, 1, "合法对照", 0, {"effect_id": "fx_reveal"}, "")
if hits.size() != 1 or String(hits[0].get("type", "")) != "extended":
push_error("[extended_row_violation_test] 合法扩展行未发射——校验误拦(回归)")
quit(2)
return
print("[extended_row_violation_test] 违约零发射+合法照发(a2 校验在位)")
quit(1)
@@ -38,6 +38,7 @@ func _init() -> void:
_smoke_settlement_segments() _smoke_settlement_segments()
_smoke_skill_pool() _smoke_skill_pool()
_smoke_counter_buckets() _smoke_counter_buckets()
_smoke_extended_rows()
quit(Kit.finish("round_flow_skill_pool_test")) quit(Kit.finish("round_flow_skill_pool_test"))
@@ -994,6 +995,8 @@ func _smoke_counter_buckets() -> void:
"node_type": "battle", "ledger": load("res://core/run_ledger.gd").new()} "node_type": "battle", "ledger": load("res://core/run_ledger.gd").new()}
var oA = RigS.new(_db, _fresh_rng(779401)) var oA = RigS.new(_db, _fresh_rng(779401))
oA.begin_battle(encA) oA.begin_battle(encA)
var v6_rows: Array = []
oA.step_row.connect(func(row): v6_rows.append(row)) # 卡④ wr2①——标错行捕获(真实 step_row 发射面)
var safe_a: Variant = null var safe_a: Variant = null
for ca in oA.minesweeper.current_board().all_cells(): for ca in oA.minesweeper.current_board().all_cells():
if not oA.minesweeper.current_board().is_mine(ca): if not oA.minesweeper.current_board().is_mine(ca):
@@ -1001,8 +1004,62 @@ func _smoke_counter_buckets() -> void:
break break
oA._grant_seg._judge_seal({"cell": safe_a, "hit": false}, 1) oA._grant_seg._judge_seal({"cell": safe_a, "hit": false}, 1)
Kit.check("V6 第1战标错落 battle 桶 insurance_used=1(_apply_counter_hit 静态写口经 ctx)〔无状态化 V6〕", EffectEngine.counter(oA.counters_ctx, "insurance_used") == 1) Kit.check("V6 第1战标错落 battle 桶 insurance_used=1(_apply_counter_hit 静态写口经 ctx)〔无状态化 V6〕", EffectEngine.counter(oA.counters_ctx, "insurance_used") == 1)
# 卡④ wr2①——完美保险标错行=单一扩展行型(原 insurance_used 实名行型收敛)
var v6_ins := {}
for r in v6_rows:
if String(r.get("effect_id", "")) == "insurance_used":
v6_ins = r
Kit.check("卡④ wr2①:完美保险标错行 type=extended 且 effect_id=insurance_used(effect_id 必载)〔明细收敛 V2〕",
not v6_ins.is_empty() and String(v6_ins.get("type", "")) == "extended"
and String(v6_ins.get("source_class", "")) == "seal_miss" and bool(v6_ins.get("forgiven", 0)))
var encB = encA.duplicate(true) var encB = encA.duplicate(true)
encB["battle_index"] = 2 encB["battle_index"] = 2
var oB = RigS.new(_db, _fresh_rng(779402)) var oB = RigS.new(_db, _fresh_rng(779402))
oB.begin_battle(encB) oB.begin_battle(encB)
Kit.check("V6 battle 桶随宿主销毁:次战新编排器 fx_counters_battle 天然空,读 insurance_used=0〔无状态化 V6〕", EffectEngine.counter(oB.counters_ctx, "insurance_used") == 0) Kit.check("V6 battle 桶随宿主销毁:次战新编排器 fx_counters_battle 天然空,读 insurance_used=0〔无状态化 V6〕", EffectEngine.counter(oB.counters_ctx, "insurance_used") == 0)
# ---- 扩展行收敛(迭代 38 卡④)——fx_reveal 兑付行与落空行经真实揭示管线发射 ----
# 驱动形态:装配揭示类专长(geologist)的编排器上走生产同一入队/兑付管线
# (_fx_enqueue_reveal → _flush_pending_reveals——grant/damage 揭示订阅面的共用出口)。
# V6 形态先例同源:直入真实管线缝(_judge_seal 直调同款口径)。
func _smoke_extended_rows() -> void:
print("--- 扩展行收敛:fx_reveal 兑付/落空 ---")
var RigS = load("res://core/battle_orchestrator.gd") # 局部装配别名(_smoke_counter_buckets 同款——跨函数不共享局部量)
var enc_fx = {"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40,
"hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {},
"feats": ["geologist"], "tree_rules": [], "battle_index": 1,
"bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "voucher_rap": 0, "voucher_time_sand": 0,
"node_type": "battle", "ledger": load("res://core/run_ledger.gd").new()}
# ② 兑付行:有未揭示安全格 → 掷格揭示成功 → 扩展行带真实格与 fx_reveal 身份
var o_fx = RigS.new(_db, _fresh_rng(779501))
o_fx.begin_battle(enc_fx)
var fx_rows: Array = []
o_fx.step_row.connect(func(row): fx_rows.append(row))
o_fx._fx_enqueue_reveal()
o_fx._flush_pending_reveals(1)
var paid := {}
for r in fx_rows:
if String(r.get("effect_id", "")) == "fx_reveal":
paid = r
Kit.check("卡④ wr2②:专长揭示兑付行 type=extended 且 effect_id=fx_reveal 且来源格真实〔明细收敛 V2〕",
not paid.is_empty() and String(paid.get("type", "")) == "extended"
and Vector2i(paid["origin_cell"][0], paid["origin_cell"][1]).x >= 0)
# ③ 落空行:无未揭示安全格态(全安全格先揭)→ 候选空 → 落空归因行(origin_cell 越界哨值)
var o_void = RigS.new(_db, _fresh_rng(779502))
o_void.begin_battle(enc_fx)
var void_rows: Array = []
o_void.step_row.connect(func(row): void_rows.append(row))
for cv in o_void.minesweeper.current_board().all_cells():
if not o_void.minesweeper.current_board().is_mine(cv):
o_void.minesweeper.fx_reveal(cv)
o_void._fx_enqueue_reveal()
o_void._flush_pending_reveals(1)
var missed := {}
for r in void_rows:
if String(r.get("effect_id", "")) == "fx_reveal" and String(r.get("display_name", "")).contains("落空"):
missed = r
Kit.check("卡④ wr2③:揭示落空行 type=extended 且 effect_id=fx_reveal 且 origin_cell=越界哨值〔明细收敛 V2〕",
not missed.is_empty() and String(missed.get("type", "")) == "extended"
and Vector2i(missed["origin_cell"][0], missed["origin_cell"][1]).x == -1)
@@ -45,6 +45,10 @@ func _init() -> void:
EffectEngineScript.validate_trigger_row("出域op", {}, [{"on": "dig_stop", "when": [{"fact": "ap_left", "op": "mod_odd", "value": 2}], "do": {"kind": "heal", "value": 1}}], errs) EffectEngineScript.validate_trigger_row("出域op", {}, [{"on": "dig_stop", "when": [{"fact": "ap_left", "op": "mod_odd", "value": 2}], "do": {"kind": "heal", "value": 1}}], errs)
EffectEngineScript.validate_trigger_row("出域kind", {}, [{"on": "dig_stop", "do": {"kind": "nope_kind", "value": 1}}], errs) EffectEngineScript.validate_trigger_row("出域kind", {}, [{"on": "dig_stop", "do": {"kind": "nope_kind", "value": 1}}], errs)
Kit.check_quiet(errs.size() == 4, "出域 on/fact/op/kind 各一行全部被拒(4 违例)〔专长批 V1〕") Kit.check_quiet(errs.size() == 4, "出域 on/fact/op/kind 各一行全部被拒(4 违例)〔专长批 V1〕")
# ③-b fact 出域文案随词表实数(动态取 FACTS.size()——禁写死计数,词表增删文案自动随)〔效果文案批 V1〕
Kit.check_quiet(String(errs[1].get("message", "")).contains(str(EffectEngineScript.FACTS.size()))
and not String(errs[1].get("message", "")).contains(" 9 项"),
"fact 出域文案含词表实数 %d、无写死计数〔效果文案批 V1〕" % EffectEngineScript.FACTS.size())
# ④ 运行期命中序(合法新事件;dispatch 出域 fail-fast=debug assert;引擎无状态——计数器经 ctx) # ④ 运行期命中序(合法新事件;dispatch 出域 fail-fast=debug assert;引擎无状态——计数器经 ctx)
var fx = EffectEngineScript.new(null) var fx = EffectEngineScript.new(null)
@@ -300,6 +300,60 @@ func _init() -> void:
and (adr14.runner()._attunement as Array) == snap14, and (adr14.runner()._attunement as Array) == snap14,
"装配不可变:战斗运行态装配集与冒险装配集逐位同源(运行零写口——快照对比)") "装配不可变:战斗运行态装配集与冒险装配集逐位同源(运行零写口——快照对比)")
# ⑮ 扩展行收敛(迭代 38 卡④ wr2④⑤——套件首次 feats 注入,V6 形态自 round_flow_skill_pool_test 移植:
# enc 带 feats 键+ledger,专长触发经 _judge_seal 真实封印判定管线)
var enc_feats = {"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40,
"hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {},
"feats": ["seal_smith"], "tree_rules": [], "battle_index": 1,
"bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "voucher_rap": 0, "voucher_time_sand": 0,
"node_type": "battle", "ledger": load("res://core/run_ledger.gd").new()}
var rng15 = load("res://core/rng_service.gd").new(); rng15.set_master_seed(341)
var ob15 = orch_s.new(db, rng15)
ob15.begin_battle(enc_feats)
var rows15: Array = []
ob15.step_row.connect(func(row): rows15.append(row))
# ④ 封印匠(每第 3 次封印命中→危险格中立移除):三枚不同危险格各封一次
#(_judge_seal 每格去重——_seal_consumed 以危险格实例为键,同格重复命中不计数)
var mines15: Array = []
for cv in ob15.minesweeper.current_board().all_cells():
if ob15.minesweeper.current_board().is_mine(cv):
mines15.append(cv)
if mines15.size() >= 3:
break
Kit.check_quiet(mines15.size() == 3, "封印匠:板上存在三枚危险格(断言前提)")
if mines15.size() == 3:
for mv in mines15:
ob15._grant_seg._judge_seal({"cell": mv, "hit": true}, 1)
var smith_row := {}
for r in rows15:
if String(r.get("effect_id", "")) == "mine_neutralized":
smith_row = r
Kit.check_quiet(not smith_row.is_empty() and String(smith_row.get("type", "")) == "extended"
and String(smith_row.get("source_class", "")) == "fx_seal_smith",
"封印匠第三次命中:危险格中立移除行=扩展行型(type=extended+effect_id=mine_neutralized)〔明细收敛 V2〕")
# ⑤ 示警烟火(封印命中→周围资源类型预告):fx_preview_area 扩展行+来源类别修复断言(wr3)
var enc_beacon = enc_feats.duplicate(true)
enc_beacon["feats"] = ["warning_beacon"]
var rng15b = load("res://core/rng_service.gd").new(); rng15b.set_master_seed(342)
var ob15b = orch_s.new(db, rng15b)
ob15b.begin_battle(enc_beacon)
var rows15b: Array = []
ob15b.step_row.connect(func(row): rows15b.append(row))
var cell15b: Variant = null
for cv in ob15b.minesweeper.current_board().all_cells():
if not ob15b.minesweeper.current_board().is_mine(cv):
cell15b = cv
break
if cell15b != null:
ob15b._grant_seg._judge_seal({"cell": cell15b, "hit": true}, 1)
var beacon_row := {}
for r in rows15b:
if String(r.get("effect_id", "")) == "fx_preview_area":
beacon_row = r
Kit.check_quiet(not beacon_row.is_empty() and String(beacon_row.get("type", "")) == "extended"
and String(beacon_row.get("source_class", "")) == "fx_warning_beacon",
"示警烟火命中行:type=extended+effect_id=fx_preview_area+来源类别=fx_warning_beacon(空串违约修复)〔明细收敛 V2〕")
quit(Kit.finish("treasure_effects_test", "PASS — 新目录 12 件逐件行为断言全绿〔秘宝批 V7 附属表〕")) quit(Kit.finish("treasure_effects_test", "PASS — 新目录 12 件逐件行为断言全绿〔秘宝批 V7 附属表〕"))
@@ -26,6 +26,7 @@ func _run() -> void:
_test_details_never_shown() _test_details_never_shown()
_test_label_three_states() _test_label_three_states()
await _test_big_graph_fit(db) await _test_big_graph_fit(db)
await _test_layout_cache(db)
quit(Kit.finish("level_map_info_fog_test")) quit(Kit.finish("level_map_info_fog_test"))
@@ -189,3 +190,38 @@ func _test_big_graph_fit(db) -> void:
and bool(disp[boss_ids[0]]["boss_border"]) and bool(disp[boss_ids[1]]["boss_border"])) and bool(disp[boss_ids[0]]["boss_border"]) and bool(disp[boss_ids[1]]["boss_border"]))
Kit.check("大图呈现:入口停点远处战斗节点(句柄 32,层 6)类型入雾〔fv8〕", not bool(disp[32]["draw_icon"])) Kit.check("大图呈现:入口停点远处战斗节点(句柄 32,层 6)类型入雾〔fv8〕", not bool(disp[32]["draw_icon"]))
screen.queue_free() screen.queue_free()
# ---- 布局缓存命中与换代失效〔levelmap批 V1/V2〕----
# 真实入口 = open_choices 后的真实绘制路径(_draw_graph→_layout,与 hover→_candidate_at 链同一缓存层);
# 命中读数 = is_same 同实例判别(重算必产新字典、命中返回缓存原实例)——零生产计数器。
func _test_layout_cache(db) -> void:
var nodes := _big_graph_nodes()
var runner = _runner(db, nodes, [nodes[1]])
var screen = ScreenScript.new()
root.add_child(screen)
screen.open_choices([nodes[1]], runner)
await process_frame
await process_frame
var p1: Dictionary = screen.layout_probe(nodes)
var p2: Dictionary = screen.layout_probe(nodes)
Kit.check("布局缓存:同 nodes 两次查询返回同一实例(第二次命中缓存)〔levelmap批 V1〕",
is_same(p1, p2))
# 换代失效:节点集内容变 → 缓存键(内容等价)击穿 → 重算换写。
# 判别式 = 同一性:若缓存未随换代失效,新图查询将命中旧缓存返回同实例;重算必产新字典。
# (不回查旧图——换代后 _content_size 已随 open_choices 重拟合到新图,旧图混查属生产外用法。)
var chain: Array = [
{"id": 0, "type": "battle", "depth": 0, "next": [1]},
{"id": 1, "type": "boss", "depth": 1, "next": []},
]
runner.level_row["nodes"] = chain
screen.open_choices([chain[0]], runner)
await process_frame
await process_frame # 换代图真实绘制路径重算(缓存随内容变失效重写)
var pc1: Dictionary = screen.layout_probe(chain)
var pc2: Dictionary = screen.layout_probe(chain)
Kit.check("布局缓存换代:新图查询返回新实例非旧缓存(失效重算生效)〔levelmap批 V2〕",
not is_same(p1, pc1) and is_same(pc1, pc2)
and (pc1[chain[0]] as Vector2) != (p1[nodes[0]] as Vector2))
screen.queue_free()
+1679 -1664
View File
File diff suppressed because it is too large. Load diff
+42
View File
@@ -0,0 +1,42 @@
# 迭代 38 代拍板追认单(2026-10-26)
> 各卡 §10 代拍板白名单内裁定的执行记录——授权发生在卡片审问阶段(白名单即预授权面),本单为批后备案确认;超白名单裁定零(如有会已停机呈报)。
## 卡①(levelmap 批)
1. payload 键名与 apply_status 形态:九键定值 level_label/gp_text/hp_text/hp_bar_redraw/attr_text/bonus_text/bonus_row_visible(hp_bar_redraw 恒 true——原逻辑无条件 queue_redraw 保真);视图方法名 apply_status。
2. 缓存命中读数实现形态:is_same 同实例判别(重算必产新字典、命中返回缓存原实例)——零生产计数器;换代失效判别改「新图查询非旧缓存实例+坐标不等」(原案「旧图回查」不可行:换代后 _content_size 已随 open_choices 重拟合,旧图混查属生产外用法——L2 行号漂移同级的实现事实修正,随卡记录)。
3. wr 断言挂点与消息文本:level_map_info_fog_test 新增 _test_layout_cache 段,尾缀〔levelmap批 V1/V2〕。
4. 提交切分与分支:单 worktree iter38、逐卡一笔提交。
## 卡③(效果文案批)
5. b2 头注表述:「词表实数以 EFFECT_KINDS 常量为准」;历史笔误叙事句按禁写变更记录红线保留。
6. 断言挂点与消息:effect_words_t1_test ③ 段后追加 ③-b,断言 message 含 str(FACTS.size()) 且不含「9 项」,尾缀〔效果文案批 V1〕。
## 卡④(明细收敛批)
7. 扩展行型串值:`extended`(推荐案);effect_id 取值:四效果沿用原行型串(推荐案)。
8. a3 来源类别:`fx_warning_beacon`(推荐案,对齐 fx_seal_smith 先例)。
9. a5 裸键呈现差异处置:申报差异(推荐案)——示警烟火行带 fx_warning_beacon 裸键,随目检清单覆盖。
10. 断言形态补充(L1/L2 白名单内两笔):①封印匠夹具改三枚不同雷格各封一次——_judge_seal 每格去重(_seal_consumed 以格实例为键),同格三封只计一次;②JSONL 对拍字段口径并入 source(playtest_logger.gd:133 对 source_class 的同值改名副本,随行平铺非本批新增)+剔除 session/diagnosis 墙钟行(parity_anchor 既有口径)。
11. 改前基线取证流程:改前 parity check 在 stash 干净态跑批(第一次跑批被并行改码污染、作废重取);T2 撤销证明按对照表三条全数实测(原案「任选其一」从全执行)。
## 卡⑤(奖励节点清扫批)
12. d1/d2/d3 措辞:「三选一停点」现状语义族;d3 定值「关卡奖励点(三选一——选毕推进)」。
13. wr2 断言:探针战报文本含新串/不含「内容待定」,挂 _tested_curio_head 捕获块(同一驱动路径承载)。
14. d4 必达形态:定向选路(候选含奖励节点优先)+捕获条件随修挂 `_node_curio_pending`(病根=原条件查通关停点态 `_curio_pending` 与奖励头部互斥恒不可命中——「拦截能力未证」实况病根,头部校验串零触碰;属白名单第 3 条「必达形态的具体实现」内)。
15. 中途故障两笔(异常分级处置留痕):①首轮探针 35 分钟无输出=wr2 断言行 `var logtext :=` 类型推断失败(Variant 返回)→ 解析错误探针未加载进程空转——显式 `: String` 修复(L1);②插桩诊断一轮(DBG 候选类型打印)取证后移除。
## 卡⑥(结算行缓冲批)
16. wr1-b 观测点落位:战中场(round≥2 且排播队列空)一次性判据——循环出口断言被收尾续玩重建段 log_view.clear() 合法冲掉(实测发现;卡面「T0 定落点」授权内)。
17. 错误行判据按实际标记执行:`Attempted to push_back … TypedArray`(卡面引述「SCRIPT ERROR 行」,实测该错误走 ERROR 通道非 SCRIPT ERROR——同一证据对象,判据更精确非放宽)。
## 卡⑦(调试入口批)
18. c2/c7 措辞:发布态恒隐语义族;c7 含「开发期临时启用改 `_debug_entry` 缺省本地不提交」运行者指引。
19. c5 直调落点:else 分支首行 `main._show_debug_overlay()`,后续断言原序保持;「置灰子断言」随恒隐语义退役(§8.2 申报随实现报告)。
20. c8 基线处置:显式重制(推荐案),改后 0.15% → 复闸 0.00%。
21. c6 清单同步范围:本批新增单测断言(卡①③④)随清单 --force 重生成一并同步(1588 条对账零差异)——清单维护锚「断言新增必须同步清单」的全量履行,超出卡面 c6 单行范围属对账工具全量口径(extract --diff 对全部新断言报新增,只改判一行无法过对账)。
+40
View File
@@ -0,0 +1,40 @@
# 迭代 38 反哺点清单(2026-10-26)
> 各卡 §6 关联文档同步面汇总——实现批禁写 `design/` 与根规则文件,以下逐条待用户裁决后由文档批落文。裁决方式:逐条「落文/不落/改写」。
## 来自卡①(levelmap 批)
1. **`待办清单.md`**——「结构审查未立项小件候审」条目拆注核对:件②归本卡(已收口 b85b3b45)、其余五件归卡②(**已停卡**,状态待回退,见实现报告 §三)。属状态核对注记。
2. **`design/engineering/architecture/architecture.md` §1.3**——「唯一越界者」清偿后该条纪律实况注记。**预期零改动**(纪律文本不变,T0 已核):l1 落地后视图内核心态拼装清零,纪律由「要求」变为「已达成」,无需改文;如文档批认为值得在纪律条目加「2026-10-26 清偿」凭据注,措辞自定。
## 来自卡③(效果文案批)
3. **`待办清单.md`**——「效果引擎校验文案计数错误」来源条目状态推进(已收口 a15370c5,完成即删)。
## 来自卡④(明细收敛批)
4. **`design/gdd/systems/战斗循环/战斗结算.md`「扩展行」节**——过渡句改写。原句:「现行实现的扩展行型值以专长实名串承载,效果身份清单与发射点以实现侧 `battle_orchestrator.gd` 文件头注为准;收敛为单一扩展行型+效果载荷的条目在待办清单在册」。落地后「实名串承载」半句与「条目在册」句均过时——建议改写为:「实现已收敛为单一扩展行型(type=extended)+effect_id 载荷;效果身份清单与发射点以实现侧 `battle_orchestrator.gd` 文件头注为准」(头注指引句保留,实现侧头注已换代两层词表+四效果身份发射点指引)。
5. **`roadmap.md`「当前位·下一步」句**——「结算明细日志行序重锚(2026-10-21 授权发起、时点另定)」指路句:重锚已随卡④裁点甲执行完毕(parity 锚换代+复闸绿),该句改写为已出账口径或摘除。
6. **`待办清单.md`**——「明细行扩展总型收敛批」来源条目状态推进(完成即删);「结算明细日志行序重锚批」条目同批出账(裁点甲已执行)。
## 来自卡⑤(奖励节点清扫批)
7. **`design/gdd/08-Alpha版本标准.md:113`**——「未结半=奖励节点现为零兑付直通占位,登记面=待办清单『奖励节点三选一实装』条」:注销后登记面死指+现状句与实现相悖——随注销改写(三选一已实装:奖励节点=三选一停点,呈现归 present_node_curio)。
8. **`roadmap.md`「当前位·下一步」待编池**——「奖励节点三选一实装(2026-10-25 新登记)」短句摘除(注销承接收口,卡⑤已落地)。
9. **`待办清单.md`**——「奖励节点过时表述清扫批」来源条目状态推进(完成即删)。
## 来自卡⑥(结算行缓冲批)
10. **`待办清单.md`**——「闸与探针存量噪声两件」条目拆注与状态推进:件①(产品码类型错)已收口 ce49a8ee;**件②(闸壳 per-suite 挂死与进程树回收,tools/probe_gate.sh+run_all.gd)留工程直改通道未实施**——条目不应整条删除,拆注保留件②。
## 来自卡⑦(调试入口批)
11. **`design/gdd/ux/battle.md:159/229`**——调试浮窗入口/DS_DEBUG 门控表述:删行后「入口在座」类陈述落空(含 2026-10-16 恒开批存量失真一并显形)——换代为发布态恒隐口径(角标恒隐、浮窗无 UI 开启口;开发期临时启用改 `_debug_entry` 缺省本地不提交)。
12. **`design/gdd/systems/跨局循环/生涯.md:18`**——调试入口描述随删行失真(T0 未逐句核,落文时按现行文本定位具体句),同口径换代。
13. **`design/engineering/test-conventions.md:49`**——「调试注入两层」节调试入口表述(DS_DEBUG/DebugOverlay 人工体验面):入口恒隐后该节「人工体验」通道描述需加发布态注记(调试注入两层的能力测试面不受影响——自动化测试不经 UI 直调核心写口,口径不变)。
14. **`design/engineering/architecture/code-architecture.json:227`**——「DS_DEBUG 门控」节点副标:写死行已删、发布态恒隐,副标换代。
15. **`待办清单.md`**——「调试入口写死行必须在发布前删除」来源条目状态推进(完成即删)。
## 来自卡②(停卡——随呈报非反哺)
16. 卡②待办条目状态回退与缺口补记拟稿见实现报告 §三(属呈报移交件,落笔归写卡段/用户,不属文档批反哺落文)。
+117
View File
@@ -0,0 +1,117 @@
# 迭代 38 实现报告(2026-10-26)
- 分支:`iter38`(worktree `D:\code\g-plan2-iter38`);基点 b23fa50d(编排提交),收口合并点 416ca84a(并入 master 2465a73b)。
- 编入七卡,实际收口六卡;卡②(结构小件定点修批)施工中途停卡呈报退上游(§卡②),改动已还原、零提交。
- 全量域闸:`tools/run_all_gates.sh --code` 批末一次(回执见 `game/tests/.gates/receipt.json`,结论见 §闸)。
## 一、各卡状态
| 卡 | 状态 | 收口提交 | 核心读数 |
|---|---|---|---|
| ① levelmap 装配越界与 BFS 缓存 | 已收口 | b85b3b45 | wr1 对拍 changed=0.00%;wr2 缓存命中/换代双绿 |
| ② 结构小件定点修(p1–p6) | **停卡呈报退上游** | (无提交) | wr5/wr6 验收载体与闸机错误扫描结构性互斥(§卡②) |
| ③ 效果引擎校验文案计数修正 | 已收口 | a15370c5 | wr1 断言绿;撤销红绿复现 |
| ④ 明细行扩展总型收敛 | 已收口 | d5a03db1 | 五断言+负面套件绿;重锚复闸三变体全同;JSONL 对拍 PASS |
| ⑤ 奖励节点过时表述清扫 | 已收口 | 00ed0236 | wr1 grep 零命中;wa4 必达真执行;撤销两条红绿复现 |
| ⑥ 结算行缓冲类型错修复 | 已收口 | ce49a8ee | 错误行 148→0;摘要恢复断言绿;撤销红绿复现 |
| ⑦ 调试入口写死行删除 | 已收口 | 5b5214f4 | 三断言反转绿;清单对账零差异;基线重制复闸 0.00% |
## 二、实际对照表(改动—验收,逐卡精要)
尾缀 grep 通道:〔levelmap批 V1/V2〕〔效果文案批 V1〕〔明细收敛 V2〕〔卡⑤〕〔卡⑥ wr1-b〕按各断言消息文本 grep 可互查。
### 卡①
| 需求点 | 改动 | 验收测试 | 层级 | 真实入口 | 撤销证明 | 结果 |
|---|---|---|---|---|---|---|
| wr1 呈现零变化 | l1 迁装+l2 缓存+l3 字体(level_map_screen.gd+main_adventure_flow.gd) | golden 对拍 changed=0.00%(level_map.png 改前基线)+shot_probe 问题=0 | 渲染+探针 | shot_probe 真实窗口跑批 | 等价重构申报(对拍差异非零即红) | 绿 |
| wr2 缓存生效与失效 | l2 缓存层 | 〔levelmap批 V1〕命中(is_same 同实例)+〔levelmap批 V2〕换代失效 | 单元 | level_map_info_fog_test 经 open_choices+layout_probe 真实路径 | 回退缓存 hunk → V1/V2 红(实测) | 绿 |
- l4 基线建立:改前 shot_probe level 帧落盘 `game/tests/golden/level_map.png`(跨跑次一致性 0.00% 实测在案);时点铁序遵守(基线先于一切码改)。
- T0 契约 3 论据链核验:`_fit_canvas`(open_choices :276)先于四调用点首触缓存——成立;`nodes()` 返回 level_row 内部数组,同关内内容稳定。
- 喂入通道:生产调用点恰两处(main_adventure_flow :122/:173),两处后接 `apply_status(level_map_status_payload(...))`;shot_probe GP 徽章断言经真实生产路径兼容验证。
### 卡③
| 需求点 | 改动 | 验收测试 | 层级 | 真实入口 | 撤销证明 | 结果 |
|---|---|---|---|---|---|---|
| wr1 文案与实数对齐 | effect_engine.gd :360 动态实数+:21-23 头注 | 〔效果文案批 V1〕message 含 FACTS.size() 且无「9 项」 | 单元 | validate_trigger_row static(与加载期同函数) | 回退 b1 hunk → 断言红(实测)→ 还原复绿 | 绿 |
- T0 复核:FACTS 19 项/EFFECT_KINDS 32 项(脚本计数在案);全文件 errs.append 10 处仅 :360 含计数——无同类残留。
### 卡④
| 需求点 | 改动 | 验收测试 | 层级 | 真实入口 | 撤销证明 | 结果 |
|---|---|---|---|---|---|---|
| wr1 行型收敛 | 六发射点 type→extended+effect_id 载荷 | 断言①–⑤+grep 判据+JSONL 读数对拍 | 单元+接口 | feats 装配驱动真实结算管线 | 回退被断言钉住的发射点 → 对应断言红(实测①) | 绿 |
| wr2 必载 effect_id | _emit_row 校验段(emit 之前) | 负面套件 extended_row_violation_test(expect=1, negative)+manifest 登记行 | 单元(负面套件) | _emit_row 真实直调(独立进程) | 回退校验段 → 违约行照发 exit 2(实测②) | 绿 |
| wr3 空来源修复 | grant :281 source_class→fx_warning_beacon | 断言⑤含 source_class==fx_warning_beacon | 单元 | warning_beacon 装配+封印命中 | 回退 a3 → wr3 红(实测③) | 绿 |
| wr4 parity 重锚(裁点甲) | anchor.json 重制 | 改前 check 绿基线+改后 freeze+复闸全同 | 集成 | parity_anchor 三变体真实跑批 | 重锚前后读数留档(§读数) | 绿 |
- JSONL 读数对拍(wr1 附属):改前/改后各 6 文件(71000/72000/73000×normal/perfect)逐行比对——type 迁移 fx_reveal→extended 77+120+67=264、fx_preview_area→extended 136+168=304、insurance_used/mine_neutralized 0→0,与 T0 基线计数逐一对账;**字段口径外零差异**(口径=type/effect_id/source_class/source 四字段+剔除 session/diagnosis 墙钟行——source 为 playtest_logger.gd:133 对 source_class 的同值改名副本,随行平铺;ts 属墙钟已按锚口径剔除)。
- a5 核验结论(零改动依据):battle_log_view `_format_row` 对四实名串本无 match 分支(全落 :210 fallback 按 display_name 呈现)、AGG_TYPES=["reward_ineffective"] 不含扩展行——收敛后扩展串同样落 fallback,a1 面呈现输出逐字节不变。**a3 面呈现差异如实申报**:示警烟火行从无来源标注变为带 `fx_warning_beacon` 裸键渲染(与 mine_neutralized 行 `fx_seal_smith` 裸键先例一致,来源归因可见性归位;§10 白名单第 4 条推荐案已执行)。
- 改动面封闭核验:基础 9 类发射零触碰;SOURCE_EXEMPT_TYPES/CLASSES 逐字不动(fx_reveal 豁免按来源类别层继续生效);随机流九名零触碰;grep 判据=六发射点第一参全 extended+A8① 行型值类残留零命中(②–⑦保留类各现位抽查在案)。
### 卡⑤
| 需求点 | 改动 | 验收测试 | 层级 | 真实入口 | 撤销证明 | 结果 |
|---|---|---|---|---|---|---|
| wr1 注释清扫 | level_runner :477-478/:10+autoplay :484 | grep 两串(零兑付直通/内容待定+内容未定)零命中 | 静态 | grep 真实跑批 | 恢复原句 → 命中即红 | 绿 |
| wr2 玩家文案换代 | battle_narrator :15 | 探针战报断言(含新串/不含旧串)〔卡⑤〕 | 探针 | 奖励节点真实战报路径 | 回退 d3 → wr2 单红(实测) | 绿 |
| wr3 wa4 转正 | ui_click_probe 捕获条件+必达判据+定向选路 | 直跑绿+真执行+未到停点即红 | 探针 | 三选一停点真实路径(press_candidate/press_card 真实按钮) | 回退 d4 条件 → wa4 必达红(实测) | 绿 |
- **wa4 转正病根修正(d4 实现形态,§10 白名单第 3 条)**:原捕获条件查 `_curio_pending`(通关三选一停点态)而奖励节点停点住 `_node_curio_pending`——两态互斥使原条件带「关卡奖励点」头部串恒不可命中,即「拦截能力未证」的实况病根;转正随修挂正确停点态,头部校验串本体零触碰。定向选路=候选含奖励节点优先(press_candidate 真实命中路径零变化)。
- wa4 真执行证据:直跑日志 `_tested_curio_head` 捕获成功、问题=0(/tmp/iter38_click6→c7_click 各跑次一致)。
- extract_checks 提取面核查:探针断言不进清单(工具扫 tests/unit|integration|system_test)——无清单同步义务。
### 卡⑥
| 需求点 | 改动 | 验收测试 | 层级 | 真实入口 | 撤销证明 | 结果 |
|---|---|---|---|---|---|---|
| wr1-a 错误行消失 | battle_log_view :17 类型一行 | 探针日志该 push_back 错误行归零(改前 148 处基线) | 探针+日志 | ui_click_probe 真实冒险路径 | 回退 g1 → 148 错误行复现(实测) | 绿 |
| wr1-b 摘要恢复 | 同上 | 正向断言(round≥2 且队列空时 _summary 非空)〔卡⑥ wr1-b〕 | 探针 | finish_settlement_round 真实轮界 | 回退 g1 → 断言红(实测同跑次) | 绿 |
- wr1-b 观测点落位说明(L1 白名单内):挂主循环战中场(round≥2 且排播队列已空=轮首分隔行已消费),不挂循环出口——收尾续玩重建段会再走 `log_view.clear()`,出口断言会被合法清空冲掉(实测发现并修正)。
- 错误行形态实证:`ERROR: Attempted to push_back a variable of type 'String' into a TypedArray of type 'Dictionary'`(非 SCRIPT ERROR——与 G2「断言仍绿」及闸机扫描不误报自洽;卡面引述的「SCRIPT ERROR 行」按实际标记执行,判据对象同一直接证据)。
### 卡⑦
| 需求点 | 改动 | 验收测试 | 层级 | 真实入口 | 撤销证明 | 结果 |
|---|---|---|---|---|---|---|
| wr1 发布态恒隐 | main.gd :199 删除+三断言反转+c6/c8 随动 | system_test V2f+click 设置面+tune 对局段+清单对账+新基线 | 系统+探针+渲染 | 三宿主真实跑批 | 恢复 :199 单行 → 三断言齐红(实测) | 绿 |
| wr2 对局中开窗回归保载 | c5 直调承载 | 浮窗可见/输入锁不锁/六项读数断言保持绿 | 探针 | _show_debug_overlay 真实单口直调 | 回退=c5 整段回原形(契约 4 口径) | 绿 |
- 契约 1 核验:`grep -c _debug_entry main.gd` 恰 2(:73 声明/:200 读取)。
- c8 基线重制(§10 落定值=显式重制):改后实测 changed=0.15%(幽灵角标消失,≈卡面预估 0.14%)→ 重制后复闸 0.00%;README 判定记录登记在案。
- 清单对账:`extract_checks.py --diff` 零差异(本批新增断言随清单重生成,1588 条——含卡①③④新增单测断言的全量同步)。
## 三、卡②停卡呈报(实现段零裁断——上游缺陷移交)
**停卡时点**:T1 实装中(p1–p6 六件全部实装完成、wr4 断言载体核查时发现结构性冲突)。改动已还原(WIP 补丁留存 /tmp/iter38_card2_wip.patch,172 行),零提交、不阻塞其余卡。
**缺陷**:wr5/wr6 验收载体与闸机错误扫描结构性互斥。卡片处方(§7 接口块)=在现役**非负面**单测套件内直调 `board_generator._fatal()`/`open_stream("bogus_purpose")` 触发 assert 取证断言;但 headless 实测(探针脚本在案):GDScript assert 失败必产 `SCRIPT ERROR` 行且**不可捕获、只中断当前函数**——run_all.gd:133-136 对非负面套件输出含该标记即判 FAIL。即:断言跑到的同跑次闸 02 必红,「wr5/wr6 绿+全套闸全绿」不可同时成立。此与卡④ grilling 第二轮发现的同款机制缺陷完全同型(卡④因此立了独立负面小套件),卡②两轮复核均未捕获。
**两种读法**:①按卡面处方实装 → wr5/wr6「绿」但全套闸红(完成定义第 1 条自相矛盾);②改载体为独立负面小套件 → 卡面「零新增文件、wr5 落 round_flow_skill_pool_test 增断言」等节全部失准,属卡片设计变更非实现细节。
**建议案**:退回写卡段,比照卡④先例改处方——wr5/wr6 断言落独立负面小套件(各一或合并一件,manifest `expect=1, negative=true`),撤销证明口径同步改「退出码 0 ≠ expect 1」;文件结构图「零新增文件」句、估时(+0.1d 量级)、§8.1 对照表落点随更。另:卡② §9 L3 已点名的 master_seed<0 分支同病根第二处(:104-106 assert 后无 return)仍在册待用户裁另立或并批——若并批,建议与 wr6 同套件承载(同文件同族负路径)。
**待办清单对应条目状态回退拟稿**:「结构审查未立项小件候审」条目状态「已写卡+已编迭代 38」→ 回退「已写卡(v1.2,退回补验收载体处方)」;缺口补记=wr5/wr6 载体改负面小套件+master_seed 分支处置待裁。落笔归写卡段/用户(本批禁写根规则文件)。
## 四、闸与读数
- 全量域闸:`tools/run_all_gates.sh --code` 批末一次(416ca84a 时点,exit=1 未全绿);其后闸红必修一笔(0cd86469 S11 四处禁用词换代——并 master 文档批「通关口径统一」63763565 退役词新增所致)+W2 尾缀笔误随暂停收尾修正。**分支尖闸面现状(未闭合,用户 2026-10-26 裁「暂停收口」)**:09 静态扫描 S13 gdstyle 恶化 2 条——`battle_orchestrator.gd` max-file-length 1314(基线 1302,卡④ a4 头注必载内容所致)、`level_map_screen.gd` max-class-variables 22(基线 21,卡①契约 3 缓存成员所致);压缩还债经计算不可行(极限 1305 > 1302 且削减卡片要求内容),闸壳「经用户批准随批重生成基线」通道待用户后续裁决。其余闸段(00/01/02/03/04/05/10)全绿;S11/S12 修复后单件复跑 ok 在案。
- MC 对拍:卡④触结算行协议面(零数值改动)——按裁点甲以 parity_anchor 三变体对拍承载:改前 check 全同(stash 干净跑批实测)→ 改后 freeze 立新锚 → 复闸 RUNSUM+JSONL 双面逐位一致;**审计独立复跑三变体对拍一致**(审计报告在案)。锚已随卡④提交换代。
- 卡④ T0/T2 读数:改前行型计数 fx_reveal 264/fx_preview_area 304/insurance_used 0/mine_neutralized 0;改后迁移计数逐一相抵;基线 JSONL 留存 /tmp/iter38_pre_jsonl(临时区,对拍读数已录本报告)。
## 五、实机目检总清单(登记不阻塞)
1. 选路屏状态栏/悬停高亮(卡①——l1 重构后目检一项)。
2. 选档删除确认流(卡②——未实施,随停卡不登记)。
3. 奖励节点战报行新文案「关卡奖励点(三选一——选毕推进)」(卡⑤)。
4. 结算明细带轮摘要显示(卡⑥——「上轮摘要」恢复后的观感)。
5. 结算明细屏四效果行目检+示警烟火行来源标注(卡④——扩展行 fallback 呈现与裸键标注)。
6. 菜单/设置面/对局中调试角标恒隐(卡⑦三项)。
## 六、遗留读数义务与在飞注记
- 卡②WIP 补丁:/tmp/iter38_card2_wip.patch(临时区——若写卡段改卡后重启施工,以此为底稿重做而非直接复用,行号须按合并后 master 复核)。
- 卡④ a3 面呈现差异(示警烟火行 `fx_warning_beacon` 裸键)随 §五.5 目检覆盖;settlement.tres source_label_map 补键属 §10 备选未裁不实装。
- parity 锚换代完成:「结算明细日志行序重锚批」待办条目出账依据随卡④提交在案(文档批落文见反哺点清单②)。
## 七、独立验收审计结论
审计报告:`reports/iter38-验收审计-2026-10-26.md`——结论**不通过**(合并前置未满足,与用户「暂停收口」裁决一致)。六卡实质面全部核实通过(逐卡对照表、撤销证明抽查、削弱核查零未申报、卡④ parity 重锚审计独立复跑一致);两项阻断均为闸面/账面而非码面:①S13 恶化 2 条(=§四已披露、用户已裁暂停待后续处置的事项);②验证集清单提交态失同步(合并解冲突时 git add 先于清单重生成——提交版为 master 侧旧 1591 条,正确的 1596 条重生成版滞留工作区;暂停收尾已补提交,`extract_checks --diff` PASS 在案)。轻微两项(W2 尾缀笔误、产出物未跟踪)已随暂停收尾修正/提交。
+120
View File
@@ -0,0 +1,120 @@
# 迭代 38 独立验收审计报告(2026-10-26)
- 审计人:独立验收审计会话(未参与实现);审计基点=iter38 分支尖 `0cd86469`(worktree `D:\code\g-plan2-iter38`,全程只读+最窄复跑,零文件改动——卡③卡①撤销复现的临时改动均当场 `git checkout --` 还原)。
- 审计对象:卡①③④⑤⑥⑦ 六卡收口提交链(b85b3b45→a15370c5→00ed0236→ce49a8ee→d5a03db1→5b5214f4→416ca84a 并 master→0cd86469 S11 闸红必修);卡②停卡零提交不在范围(停卡留痕 /tmp/iter38_card2_wip.patch 10215 字节在案,worktree 工作区无残码)。
- 输入合同:七张需求卡片 §8.1/§8.2/§8.3+实现报告 §二 对照表+代拍板追认单 21 条。
## 结论行
**不通过——六卡代码面、断言面、撤销证明、削弱申报全部核实通过,卡④ parity 重锚经审计独立复跑逐位一致;但分支尖存在两项闸面未闭合:①09 静态扫描仍红(gdstyle 两条指标恶化未还债未随批重生成基线,批末闸回执 all_green=false 且实现报告未披露);②合并把验证集清单退回 master 旧版(本批新增断言在提交版清单零命中),正确的再生成件躺在 worktree 未提交。两件清偿+闸复跑绿回执后方可合并。**
## 一、逐卡逐行核对表(实现报告 §二 vs 实码)
### 卡① levelmap 批(b85b3b45)
| 报告行 | 核对项 | 结果 | 依据 |
|---|---|---|---|
| wr1 | golden 基线+对拍 0.00% | 通过(读数采信,复跑受限) | level_map.png 与码改同提交(git log --diff-filter=A 唯一 b85b3b45);golden/README 登记行在案(改前建基、跨跑次 0.00% 后落盘);审计窗口化 shot_probe 复跑在本环境 180 秒挂起无输出(exit 124,引擎 OpenGL 启动后停滞——环境性问题非码问题),像素级未独立复跑;收口时点闸 05 渲染闸绿(shot_probe 七屏 A-D 断言)佐证渲染面健康 |
| wr2 | 〔levelmap批 V1/V2〕 | 通过 | level_map_info_fog_test.gd:209/:224 两断言在位;经 open_choices+layout_probe(视图 :524 既有件、ui_input_probe 同款)真实绘制路径;缓存层在 `_layout` 内、四调用点零改动(diff 核实) |
| l1 装配迁移 | 喂入通道 | 通过 | main_adventure_flow :122-123/:174-175 两生产调用点均 open_choices 后接 apply_status(level_map_status_payload(...));payload 七键与代拍板 #1 一致;视图侧拼装 grep 残留仅 :204 注释/:207 血条绘制读 max_hp(绘制面非拼文案)/:278 tint 层号——清零判据成立 |
| l3 字体提取 | 函数首单取 | 通过 | :429 `var font := get_theme_default_font()`,三处 draw_string 共享(diff 核实) |
### 卡③ 效果文案批(a15370c5)
| 报告行 | 核对项 | 结果 | 依据 |
|---|---|---|---|
| wr1 | 〔效果文案批 V1〕 | 通过 | effect_words_t1_test.gd:48-51 断言在位:message 含 str(FACTS.size()) 且不含「9 项」;validate_trigger_row static 与加载期同函数(卡面处方,非绕过) |
| b1/b2 | 动态实数+头注治理 | 通过 | effect_engine.gd:360 `%d 項 % FACTS.size()`;:21-23 头注去写死计数,历史笔误句按代拍板 #5 保留;errs.append 全文件仅 :360 含计数(grep 复核);FACTS 19/EFFECT_KINDS 32 脚本计数复核一致 |
| 改动面封闭 | 契约 1 | 通过 | 提交 diff 恰两文件共 6 行(+4/-2),零越界 |
### 卡④ 明细收敛批(d5a03db1)
| 报告行 | 核对项 | 结果 | 依据 |
|---|---|---|---|
| wr1 行型收敛 | 六发射点 | 通过 | orchestrator :156/:164/:169+grant_segment :271/:281/:309 第一参全 `extended`,display_name/value/highlight 逐字不动(diff 逐行核);grep 判据=四实名串作第一参零残留 |
| wr2 必载 effect_id | a2 校验段 | 通过 | _emit_row 体内 `step_row.emit` 之前(先于 _assert_row_source),缺载 assert+return 零发射;负面套件 extended_row_violation_test 直调 _emit_row 为卡面 §7 明文处方,非绕过 |
| wr2-np | 负面套件+manifest | 通过 | 套件在位(违约直调→零发射→合法对照行照发→quit(1));run_all.gd:73 登记 `expect=1, negative=true`;审计直跑实测 exit=1、输出「违约零发射+合法照发(a2 校验在位)」 |
| wr2 断言①–⑤ | 两现役套件 | 通过 | round_flow :1012/:1046/:1063(feats 装配驱动真实 step_row 信号捕获+_judge_seal/_flush_pending_reveals 真实管线)+treasure :333/:355(套件首次 feats 注入,V6 形态移植;三枚不同雷格各封一次=代拍板 #10①);其中两条用 Kit.check_quiet——不进验证集清单提取面(工具只提 `check(`),清单算术 1591+5=1596 与此吻合 |
| wr3 空来源 | a3 修复 | 通过 | grant :281 `""`→`fx_warning_beacon`;断言⑤含 source_class 判据 |
| wr4 parity 重锚 | 锚换代+复闸 | 通过(审计独立复跑) | anchor.json 在 d5a03db1 换代:恰 3/6 条 sha256 变更(三变体 perfect 面)、rows 字段零变化——与读数「迁移集中于部分文件、RUNSUM 数值面零变化」自洽;审计复跑 `python tools/parity_anchor.py check` exit=0,三变体 RUNSUM+JSONL 双面逐位一致,实算 JSONL 哈希(962b6f68…/06cb4dcc…/c3d0d37c…)与新锚逐位相同 |
| JSONL 读数对拍 | 264/304/0/0 | 自洽(原始件未独立复算) | 对拍原始 JSONL 留存 /tmp/iter38_pre_jsonl 临时区不可复取;锚 diff 形态(3/6 哈希变、行数零变)与读数口径一致;T0 计数随报告留档 |
| a5 呈现核验 | battle_log_view 零改动 | 通过 | d5a03db1 未触该文件 |
| 改动面封闭 | 契约 1/3 | 通过 | SOURCE_EXEMPT_TYPES/CLASSES 两常量在 diff 中仅作上下文出现、现值逐字未动;`open_stream("fx_reveal")` 等随机流零触碰;基础 9 类发射点零触碰(全 diff 仅六发射点+校验段+头注) |
### 卡⑤ 奖励节点清扫批(00ed0236)
| 报告行 | 核对项 | 结果 | 依据 |
|---|---|---|---|
| wr1 | 两串 grep 零命中 | 通过(复跑) | 审计复跑两条 grep 命令均零输出;E2 豁免面未被顺手改:autoplay :220/:381 行为性准确表述保留、ui_click_probe :697「卡片前提缺陷记录」历史注记保留 |
| wr2 | 战报文案断言〔卡⑤ wr2〕 | 通过(审读替代复现) | ui_click_probe :428-432:log_view 含「关卡奖励点(三选一——选毕推进)」且不含「内容待定」;battle_narrator :15 新文案在位;断言挂奖励节点停点捕获块,同一驱动路径 |
| wr3 wa4 转正 | 必达判据〔卡⑤〕+定向选路 | 通过(审读替代复现) | 捕获条件 `_curio_pending`→`_node_curio_pending`(病根修正,头部校验串本体零触碰);:725-728 全程未到停点即红;定向选路只改 press_candidate 索引策略、真实按钮命中路径零变化;press_card/press_confirm 真实按钮路径。撤销证明按审计指令以代码审读替代(探针复现 >10 分钟);报告声明回退 d3→wr2 单红、回退 d4→必达红两条实测在案 |
### 卡⑥ 结算行缓冲批(ce49a8ee)
| 报告行 | 核对项 | 结果 | 依据 |
|---|---|---|---|
| wr1-a | 错误行 148→0 | 通过(审读替代复现) | 改动恰 :17 一行 Array[Dictionary]→Array[String];148 基线读数与归零不可廉价复跑(探针全程约分钟级×改前改后),按审计指令以代码审读替代:错误机理(TypedArray 异型 push_back)与修复一行因果闭合 |
| wr1-b | 〔卡⑥ wr1-b〕断言 | 通过(审读替代复现) | ui_click_probe :31/:560-566:观测点在主循环战中场(round≥2 且 _row_queue 空=轮首分隔行已消费),一次性判据,非循环出口——与代拍板 #16 落位说明一致;判定读 `_summary.text` 非空 |
| 无断言删除 | 削弱面 | 通过 | 提交 diff 零断言删除,探针仅 +9/-2(新增状态位+断言块) |
### 卡⑦ 调试入口批(5b5214f4)
| 报告行 | 核对项 | 结果 | 依据 |
|---|---|---|---|
| wr1 发布态恒隐 | 删行+三断言反转 | 通过 | main.gd :199 写死行已删(diff 核实,:73/:200 保留);grep -c `_debug_entry` main.gd 恰 2(审计实测);system_test V2f、click :324-327、tune :117-118 三处反转形态在位 |
| wr2 直调承载 | c5 | 通过 | tune else 分支 `main._show_debug_overlay()` 直调(:61 先例),后续浮窗可见/输入锁/六项读数断言原序保持;:44-49 程序性段零触碰(diff 核实) |
| 撤销证明 | 恢复 :199 三断言齐红 | 通过(审读替代复现) | 三处反向断言判据直接钉 `_debug_entry` 恒 false 语义,恢复写死行必红——机制审读成立;报告声明实测在案。置灰子断言删除有代拍板 #19 记录 |
| c6/c7/c8 随动 | 清单/README/基线 | 通过(c6 见问题②例外) | game/README 发布态表述换代;menu_boot.png 重制(Bin 变更)+golden/README 判定记录在案;c6 于 5b5214f4 时点对账零差异属实(提交版基点 d5a03db1/1588),合并后回归见问题清单② |
## 二、撤销证明抽查记录
| 卡 | 方式 | 命令与输出摘要 |
|---|---|---|
| ③ | 复现红/绿实测 | 绿基线:`--script res://tests/unit/core/effect_words_t1_test.gd`→`all green (18 checks)`。改红:sed :360 `FACTS.size()`→`9`→`FAIL — fact 出域文案含词表实数 19、无写死计数〔效果文案批 V1〕`。还原 `git checkout --`→复绿 `all green (18 checks)` |
| ① | 复现红/绿实测 | 改红::505 缓存命中条件前置 `if false and ...`→`[FAIL] 〔levelmap批 V1〕`+`[FAIL] 〔levelmap批 V2〕` 双红。还原→复绿:`[PASS] V1`+`[PASS] V2`,`all green (21 checks)` |
| ④ | 直跑实测 | `--script res://tests/unit/battle_settlement/extended_row_violation_test.gd`→exit=1、`[extended_row_violation_test] 违约零发射+合法照发(a2 校验在位)`;manifest 登记行 run_all.gd:73 在位 |
| ⑤ | 代码审读替代(探针复现 >10 分钟) | wr2 断言与 wa4 必达判据代码在位(:428-432/:725-728),判据钉死新文案与停点态,机制上回退即红;报告两条实测记录在案 |
| ⑥ | 代码审读替代(探针复现代价高,按审计指令) | wr1-b 断言在位、观测点落位与代拍板 #16 一致;一行类型修复因果闭合 |
| ⑦ | 代码审读替代(探针复现代价高) | 三处反向断言判据直接依赖「无写死行」前提,恢复 :199 必红;报告实测记录在案 |
## 三、测试削弱核查(七卡 §8.2+代拍板单 vs 实际 diff)
逐提交核对 b23fa50d→0cd86469 区间(含合并带入的 master 通关换代批——该批有独立审计 57447386 在案,不计本批):
1. **卡④**:SOURCE_EXEMPT_TYPES/CLASSES 两常量零变化(diff 中仅上下文行)——申报吻合。基础 9 类行型发射零触碰:全区间 `_emit_row` 第一参改动恰六发射点+校验段,六点外无第一参改动(grep+diff 双核)。
2. **卡⑦**:三处断言反转=换代非削弱,申报在卡面 §8.2;置灰子断言删除有代拍板 #19 记录;ui_click_probe 设置面原「浮层不被遮挡」语义退役有卡面 §8.2 申报。既有断言零删除(三处全原位反转)。
3. **卡⑤**:wa4 条件转必达=判定面增强;头部校验串、autoplay 直通行为、:220/:381 豁免表述、:697 历史注记全部原样(审计逐处复核)。
4. **卡⑥**:零断言删除。**卡③/卡①**:既有断言零改动(effect_words +4/-0、level_map_info_fog +36/-0 纯增)。
5. **验证集清单**:断言改判(V2f 行 74)与新增同步在 5b5214f4 履行(1588 条对账零差异,卡内申报一致);合并回归单独列问题②。
**削弱核查结论:零未申报削弱。**
## 四、附加核对结果
1. **合并 416ca84a 代码面零触碰——成立(比指令口径更严的核法)**:区间 diff `5b5214f4..416ca84a` 含 master 侧通关换代批的全部码改(该批为 master 上已审计的独立批次,非合并引入);合并解的真实触碰面用 `git diff-tree --cc` 定位=7 文件(6 码件+README),6 个码件逐一以 patch-id 对比「合并结果 vs master 侧」恰等于「iter38 侧自身改动」——全部 MECHANICAL(机械合成),即合并解对手只落在 README.md 与验证集清单.md 两文档(与提交声明一致;清单侧取 master 整版引发问题②)。
2. **卡① golden 时序**:level_map.png 新增提交=b85b3b45=码改同一笔。git 历史无法证明提交内「先建基线后改码」时序——**该限制如实注明**,采信依据=golden/README 登记记载(「改前建基——时点铁序,跨跑次 0.00% 实测后落盘」)与卡面 T1 前置步处方。
3. **卡④ parity 锚**:d5a03db1 恰 3/6 哈希换代+rows 零变化,与 264/304/0/0 读数自洽;审计独立复跑 check 全同(见对照表 wr4 行)。
4. **卡⑦**:`grep -c "_debug_entry" main.gd`=2(实测)。
5. **卡⑤清单义务豁免**:探针不在 extract_checks 提取面(工具扫 tests/unit|integration|system_test)——「无清单同步义务」申述属实。
6. **闸回执**:`game/tests/.gates/receipt.json` commit=416ca84a(15:38:28)、domain=code、**all_green=false**——09 静态扫描 exit=1;其后 0cd86469(16:00:28)只修 S11 四处,**无修复后的复跑回执**。审计复跑静态扫描:S11 已转绿(238 文件 23 词零命中),S13 仍红(详见问题①)。
## 五、问题清单
**阻断(合回前必须清偿)**
1. **09 静态扫描在分支尖仍红——gdstyle 两条指标恶化未处置,实现报告未披露**。审计复跑实测:`battle_orchestrator.gd|quality/max-file-length = 1314(基线登记 1302)`(卡④ 校验段+头注加行所致)、`level_map_screen.gd|quality/max-class-variables = 22(基线登记 21)`(卡① _layout_cache 所致);master 侧分别为 1301/21,恶化归属本批无疑。批末闸回执(416ca84a)即因 S11+此两条判红,0cd86469 只修了 S11;七笔提交零触碰 tools/gdstyle_baseline.txt。按闸机口径须还债或经用户批准随批重生成基线,二者均未发生;完成定义「全套闸全绿」未满足,报告 §一「已收口」与 §四未提闸红,属呈报缺漏。
2. **验证集清单在提交态与码失同步**。合并 416ca84a 对清单冲突取 master 侧整版(基点 54395b8b/1591 条):本批新增断言(levelmap批 V1/V2、效果文案批 V1、明细收敛 V2×3、卡⑥ wr1-b)在 HEAD 提交版清单 **零命中**,V2f 行 74 回退旧 cond「boot 可见」与已反转的码面断言相抵。正确再生成件(1596 条、含两侧断言、`extract_checks --diff` PASS)已存在于 worktree 但**未提交**。c6「清单没跟上视同未完成」在提交态不成立。清偿=提交该再生成件(算术复核 1591+5=1596,check_quiet 与探针不在提取面,无遗漏)。
**轻微(不阻断,随批或批后顺手)**
3. level_map_info_fog_test.gd:195 段头注释尾缀误写〔levelmap批 W2〕——断言本体 V1/V2 正确,grep 通道不受影响,纯注释笔误。
4. 三份产出物(实现报告/代拍板/反哺点)在主区与 worktree 均为未跟踪态,随批后收尾提交即可(现状与「实现批产出物只写 reports/」不冲突,登记在案防遗漏)。
**限制声明(采信依据已注明,非缺陷)**
5. 卡① wr1 像素级对拍(changed=0.00%)与卡⑥ 148→0 基线读数未独立复跑:前者窗口化 shot_probe 在审计环境挂起(gate 05 渲染闸收口时点绿佐证),后者按审计指令以代码审读替代;两者均有实现报告读数与机制闭合支撑。
6. 卡④ JSONL 原始对拍件留 /tmp 临时区不可复取,读数采信以锚 diff 形态+check 复跑佐证。
## 六、审计操作性记录
撤销复现的临时改动(effect_engine.gd :360、level_map_screen.gd :505)均以 `git checkout -- <file>` 当场还原,还原后工作区与审计起点一致(仅存审计前的验证集清单未提交再生成件与未跟踪产出物,零新增改动);worktree 主区未触碰。parity check 复跑约 13 分钟(base 288s/attune 286s/shop 234s),exit 0。