diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index d166db11..5f245aa1 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -48,14 +48,6 @@ jobs: rm -rf game/.godot/imported /usr/local/bin/godot --headless --path game --editor --quit - - name: 棋盘黄金图自愈补采(full 批——缺基准时 runner 会话渲染→提交回 master→下一轮拉取生效) - if: steps.route.outputs.mode == 'full' - env: - GODOT_BIN: /usr/local/bin/godot - run: | - cd "$GITHUB_WORKSPACE/repo" - bash tools/ci/golden_selfheal.sh - - name: 跑闸 if: steps.route.outputs.mode != 'skip' env: diff --git a/design/engineering/process-conventions.md b/design/engineering/process-conventions.md index 0a268de9..fbac578a 100644 --- a/design/engineering/process-conventions.md +++ b/design/engineering/process-conventions.md @@ -25,7 +25,7 @@ | 二·文档设计/美术设计 | doc-design(文档线,兼任反哺点写入)/asset-ingest(美术录入;样张素材由用户主导产出) | GDD 文档或 art-bible/过审样张素材;画面类需求另出预览图(文档线,2026-11-05 用户裁) | 用户提起或从待办清单取出;会话内进行、用户主导、禁无监管自跑;经用户点头确认心智一致才许写入文档;完成判据=待裁点清零,带未裁点的是讨论稿;设计线闭合凭据记入待办清单实况清单(文档线=文档路径+版本号,美术线=art-bible 条款版本);缺哪条线写在缺口属性,闭合与否写在状态段。画面类需求(新增画面要素/布局/配色/动效等触碰画面呈现规格的改动)设计段先出预览图=目标画面的 HTML/SVG 效果图(diagram-design 通道,AGENTS.md 图优先的同款载体),经用户人审通过才许进写卡段;预览图内嵌对应 ux/ 规格文档并随附像素级视觉判据句(§7 第 2 条同款写法);缺陷修复以现行规格与在库基线为准,不触发新预览 | | 三·写需求卡片 | req-card | 需求卡片 | 设计线闭合后用户发令;新玩法/新系统/新内容必须有设计定稿前置,例外=需求性工程条目(缺陷修复/重构等改游戏行为)凭实况里的证据指认跳过设计线;配套维护类(工程债/测试基建/工具维护,不动游戏行为)=工程改动直改类,不走本段,由用户发令直改;卡片必经 grilling 多轮复核、零待裁点才成立;复核发现按四类去向处置(2026-11-05 用户立规,定义在 req-card 技能「复核发现四类分流」:自判=查权威文档或查代码现状有唯一答案,复核者自己下结论改卡;补写=卡里漏写,回炉补全;待裁=几种做法都不违规且代价不同、影响玩法取向,才交用户;退回上游=文档设计线未闭合、美术设计线未闭合、或架构与代码规范本身冲突,停笔退对应段);成立凭据=卡内「复核记录」节(2026-11-06 用户裁:不另设复核报告文件),顺序未走完禁对用户称完成、禁落 commit(同 §1 第 2 条卡片复核前置判据);写完在待办清单状态记「已写卡」,卡片路径记入实况清单(防遗忘);画面类需求卡片必引设计段已人审的预览图(验收判据的判据出处行锚指向 ux/ 内嵌预览节,test-conventions §8.3 锚口径不变) | | 四·迭代编排 | iter-plan | roadmap 迭代行 | **开工前预检(2026-11-07 用户立)**:收令点名拟编卡集后、五查与落表前,对卡集做一轮只读事实面预检——八类检查、P1/P2/P3 分级去向与只读子代理执行方式=iter-plan 技能「第 3 步·开工前预检」;P1 红项停编排、退回写卡段,禁编入。预检目标=尽量减少实现期停工、非杜绝——环境漂移与卡片并行修改仍可在实现期停工,由实现段异常分级与 iter T0 开工日窄复核兜底。编排五查:①每卡卡片文件存在 ②每卡设计闭合凭据齐(或需求性工程条目的证据指认)③估时与冻结线——所选卡估时按区间**中值**求和(单位工作日、写精确值不取整):**卡片任何工作量均可编入**(2026-10-25 用户立:工作量限制属迭代层、非卡片层);编入后迭代总量 <5 个工作日=迭代行状态「冻结待凑批」、不可开工,凑批 ≥5 后由编排段改「待开工」(迭代体量偏好,2026-10-21 用户立、2026-10-25 改冻结制);5–10 个工作日为常规区间;超过 10 个工作日须经用户确认(确认前呈报整批/拆批两案等裁,禁自行拆分或塞入),确认后迭代行随带日期特批标记;超过 20 个工作日禁止编(硬上限,用户确认也不放行,必须拆批)④每卡复核记录在位(2026-11-05 用户立规;2026-11-06 用户裁凭据换代——复核凭据=「复核记录」节自身,不另查报告文件)——卡内有「复核记录」节;缺节或节内任一条发现没有去向标记则呈报退回写卡段,禁编入,本段不代跑复核也不代补记录(生效=卡片文件名日期 ≥2026-11-06,旧卡不回头改)⑤写完 roadmap 行后跑流水线闸确认绿;编入未完成迭代,新开迭代行经用户确认 | -| 五·实现 | iter | 代码与配置,可运行的游戏 | 需求卡片永不单独施工,统一由用户指定迭代号触发(=U0 开工令),按 iter 技能与本文 §3 执行;**本段是代码实现批:禁写 `design/` 与根规则文件,产出物只落 `reports/`(见 §3 第 12 条)**;设计权威文档(GDD/art-bible)批内一律不更新;完成人闸=实机目检清单+实现决定记录写齐即完成批内义务;画面类需求实现批另按 test-conventions §8.2 黄金图基线冻结口径执行(实跑截图入基线前用户对照预览图确认一眼,此后渲染闸自动盯) | +| 五·实现 | iter | 代码与配置,可运行的游戏 | 需求卡片永不单独施工,统一由用户指定迭代号触发(=U0 开工令),按 iter 技能与本文 §3 执行;**本段是代码实现批:禁写 `design/` 与根规则文件,产出物只落 `reports/`(见 §3 第 12 条)**;设计权威文档(GDD/art-bible)批内一律不更新;完成人闸=实机目检清单+实现决定记录写齐即完成批内义务;画面类需求实现批另按 test-conventions §8.2 黄金图基线冻结口径执行(实跑截图入基线前用户逐张审核确认+审核记录落档,此后渲染闸自动盯);**golden 基线改动批(含非卡片批的迁移/重构/容差修订)merge 前须用户审核通过(test-conventions §8.2 审核关卡),未审核的基线禁合回** | **实现段零裁断**:实现段的输入在编排段出口已锁死(卡片零待裁点+设计闭合凭据齐+编排五查过)。实现中冒出卡片没写清的分歧=上游缺陷——停该卡、待办清单状态回退+缺口补记,退回上游;禁自答、禁内联提问求裁断;该卡停摆不阻塞同迭代其他卡。环境故障与测试红走异常分级(自救重试/记录继续/停机呈报)。代拍板白名单保留:授权发生在卡片审问阶段,实现只执行预授权;超白名单/超卡片明列范围事项记入实现报告「实现决定记录」节备案,不另立追认单。 diff --git a/design/engineering/test-conventions.md b/design/engineering/test-conventions.md index 851e47d9..f3b8e568 100644 --- a/design/engineering/test-conventions.md +++ b/design/engineering/test-conventions.md @@ -97,7 +97,8 @@ - **接口**:模块边界的公开契约——数据表加载、存档与断点、事件载荷、命令接口;测试从公开入口进出。接口级断言随单元套件落(`unit/contracts/`),不另设承载目录。 - **跨宿主**:跨宿主的接线、持久、生命周期——例如 LevelRunner 把 level_counters 传给 BattleOrchestrator、两场战斗共享同一个桶、断点续玩;测试必须实例化真实宿主链,禁止用裸 EffectEngine 代替。断言载体归单元层跨系统闭环套件(§1.1 三元组表),验收入口不变。 - **探针/golden**:玩家可达性与观感;真实实例化场景、驱动真实输入。 -- **golden 测试项扩项(2026-11-05 立)**:触碰画面呈现规格的需求批(新增画面要素/布局/配色/动效;判据=设计段出了预览图),实现段把该画面纳入渲染闸机器面:截图采集清单扩项+实跑截图冻结进 `game/tests/golden/`(基线属入库测试资产,render_gate.sh 在册例外)。黄金图=游戏真实渲染截屏冻结的逐像素基准,比对器与容差口径见 render_gate.sh 头注释(脚本即清单,本文不另列)。基线冻结(新建与换代同口径)=用户对照设计段预览图确认后执行,触发原因写进当批提交说明(`tools/golden_baseline_refresh.sh` 现役义务);此后渲染闸逐像素自动盯、零人工。预览图(HTML/SVG 效果图)与引擎渲染是两套管线,禁直接充当比对基线——预览图只作人审参照与验收判据句的出处(判据出处行锚指向 ux/ 内嵌预览节);缺陷修复以现行规格与在库基线为准,不触发新预览。 +- **golden 测试项扩项(2026-11-05 立)**:触碰画面呈现规格的需求批(新增画面要素/布局/配色/动效;判据=设计段出了预览图),实现段把该画面纳入渲染闸机器面:截图采集清单扩项+实跑截图冻结进 `game/tests/golden/`(基线属入库测试资产,render_gate.sh 在册例外)。黄金图=游戏真实渲染截屏冻结的逐像素基准,比对器与容差口径见 render_gate.sh 头注释(脚本即清单,本文不另列)。基线冻结(新建与换代同口径)=**用户逐张审核确认后执行**,触发原因写进当批提交说明(`tools/golden_baseline_refresh.sh` 现役义务);此后渲染闸逐像素自动盯、零人工。预览图(HTML/SVG 效果图)与引擎渲染是两套管线,禁直接充当比对基线——预览图只作人审参照与验收判据句的出处(判据出处行锚指向 ux/ 内嵌预览节);缺陷修复以现行规格与在库基线为准,不触发新预览。 +- **golden 基线冻结审核关卡(2026-11-08 用户立)**:基线=人定标准、机器守标准——**任何基线的新建/换代/迁移/容差修订,呈报用户逐张审核、确认后才能提交入库生效**;该关卡适用于两类批:①画面类需求批(候选基线对照该卡预览图逐张呈报)②**非卡片批的基线操作**(目录重构/平台迁移/容差修订等工程批——同样逐张呈报审核,禁以「纯搬运/纯工程」为由跳过)。确认记录登记于 `game/tests/golden/README.md` 审核记录节(审核人+时间+基线清单),**未审核的基线禁合回**。机器侧前置:探针自检 PASS(结构断言在案)才允许出候选图,采集失败不得送审。基线**跨平台单套**(同日用户裁:跨平台渲染必须一致,全平台对拍同一份基线,跨平台字形锯齿噪声由容差带吸收,禁以加基线方式消化差异;细则=golden/README「基线冻结与审核」节)。 分层不是形式。改动发生在哪一层,验收就必须从那一层的真实入口进入。测试绕过了改动点,就不算这个改动的验收——这正是「断言存在、接线坏了却不红」的来源。 diff --git a/game/probe/shot_probe.gd b/game/probe/shot_probe.gd index 2fd6a340..9b66db5a 100644 --- a/game/probe/shot_probe.gd +++ b/game/probe/shot_probe.gd @@ -1,6 +1,7 @@ # VERTICAL SLICE - NOT FOR PRODUCTION -# 整屏截图探针(呈现层测试分级 B 档基建:视口截图程序化产出;七屏—— -# 菜单/设置面/生涯页/营地/关卡画面/战斗/状态面板,现行事实源=tools/render_gate.sh)。 +# 整屏截图探针(呈现层测试分级 B 档基建:视口截图程序化产出;七屏+九态—— +# 菜单/设置面/生涯页/营地/关卡画面/战斗/状态面板+营地五窗开态/图例/战末结算/三选一/调试浮窗, +# 现行事实源=tools/render_gate.sh;九态=golden 补锚批 2026-11-08)。 # 必须**窗口化**运行(真渲染设备——headless 无渲染面,get_image 得到空纹理): # Godot_console.exe --path . res://probe/shot_probe.tscn -- <输出目录> # 流程 = 真实按钮信号路径驱动(ui_click_probe 同款;存档/设置/元进度三面换注隔离档——禁碰真实档, @@ -129,6 +130,30 @@ func _ready() -> void: else: await get_tree().process_frame await _snap("camp") + # ②b 营地五窗打开态(真点热点开窗→截→「返回」前缀真点关——screen_flow_probe 窗壳链同款) + # 判据出处: design/ux/camp.md:45 五窗统一窗壳形态·互斥单开(营地窗态组) + for k: StringName in [&"forge", &"chest", &"dummy", &"board", &"notebook"]: + ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(k).get_center()) + for i in 3: + await get_tree().process_frame + var wv: Control = main.ui.modals.camp.forge if k == &"forge" else ( + main.ui.modals.camp.treasure if k == &"chest" else ( + main.ui.modals.camp.tree if k == &"dummy" else ( + main.ui.modals.camp.codex if k == &"board" else main.ui.modals.menu.career_overlay))) + # 截图键=基线语义名(dummy→tree 天赋树/board→codex 图鉴——热点键≠呈现名,映射单点在此) + var wkey: String = {"forge": "forge", "chest": "chest", "dummy": "tree", + "board": "codex", "notebook": "notebook"}[String(k)] + if not wv.visible: + _problems.append("营地窗未打开: %s(真点热点路径)" % String(k)) + else: + await _snap("camp_%s" % wkey) + var back: Button = ProbeUiUtil.find_button(wv, "返回", "prefix") + if back == null: + _problems.append("营地窗返回钮缺失: %s" % String(k)) + else: + ProbeUiUtil.push_click(main, back) + for i in 3: + await get_tree().process_frame ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"campfire").get_center()) # 收闭改接篝火动线(营地根态无关闭钮——卡④ T5) await get_tree().process_frame ProbeUiUtil.push_click(main, main.ui.modals.menu.confirm_dialog._confirm_btn) # 二次确认离场(关层收闭——选档层随收) @@ -152,9 +177,14 @@ func _ready() -> void: # ④ 战斗(节点一——candidate_rect 命中矩形真点选路提交,ui_click_probe 同款) # 判据出处: design/ux/battle.md:122 B 区构建:站桩/明细带/轮次指示(战斗出图与几何锚组) if main.ui.modals.run.level_screen != null and main.ui.modals.run.level_screen.visible: + # 接触带呼吸相位冻结(board_shot_probe 同款:motion_scale<1.0 ⇒ 施静态带色)—— + # golden 基线逐像素比对禁动画相位抖动;必须在进战 rebuild 前置位(rebuild/带色施用按当时相位取样) + UiKit.motion_scale = 0.5 ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center()) for i in 10: await get_tree().process_frame + # 全板淡入+涟漪完结锚定(board_shot_probe 同款 1.2s=2×余量——基线禁采动画中途帧) + await get_tree().create_timer(1.2).timeout await _snap("battle") # 战斗屏文字框/明度带锚点(全局 rect——checker 取样坐标源;明细带锚 S1 裁后改认现行文案) _meta["battle_log_hint"] = ProbeUiUtil.label_rect(main.ui.battle.stage, "上轮摘要") @@ -190,6 +220,63 @@ func _ready() -> void: await get_tree().process_frame if main.ui.modals.run.status_panel.visible: _problems.append("状态面板关闭后未隐藏(继续钮路径)") + # ⑤b 图例浮窗(战斗屏 AP 条真点开→截→close() 关——ui_click_probe 真点先例) + # 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:88 图例面板=模态族(图例组) + var lgb: Button = main.ui.battle.board_zone.ap_bar._btn_legend + if lgb == null or not lgb.visible: + _problems.append("图例钮缺失/不可见(战斗屏 AP 条)") + else: + ProbeUiUtil.push_click(main, lgb) + for i in 3: + await get_tree().process_frame + if not main.ui.modals.run.legend_overlay.visible: + _problems.append("图例层未打开(图例钮路径)") + else: + await _snap("legend") + main.ui.modals.run.legend_overlay.close() + for i in 3: + await get_tree().process_frame + # ⑤c 战末结算浮层(直调 open_victory 喂真实形态摘要——ui_struct_probe 合成先例) + # 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:276 终局:胜利金蜡封(战末结算组) + main.ui.modals.run.end_overlay.open_victory("胜利", "继续", Rect2(200, 200, 400, 300), + {"gp_summary": "GP +3", "ks_summary": "击杀 2", "bonus_summary": "无伤奖励", "gp_gain": 3, "gp_total": 3}) + for i in 3: + await get_tree().process_frame + if not main.ui.modals.run.end_overlay.visible: + _problems.append("战末结算浮层未打开(open_victory 直调)") + else: + await _snap("battle_end") + main.ui.modals.run.end_overlay.close() + for i in 3: + await get_tree().process_frame + # ⑤d 三选一浮层(直调 open_pick 喂真实形态卡——ui_struct_probe 合成先例) + # 判据出处: design/gdd/systems/冒险循环/奇物.md:166 三选一呈现(三选一组) + main.ui.modals.run.curio_overlay.open_pick("三选一", [ + {"id": "probe_heal", "text": "回血\n恢复 3 点"}, + {"id": "probe_magic", "text": "爆裂轰击\n下一击 +5"}, + {"id": "probe_pierce", "text": "穿甲\n无视护甲"}, + ]) + for i in 3: + await get_tree().process_frame + if not main.ui.modals.run.curio_overlay.visible: + _problems.append("三选一浮层未打开(open_pick 直调)") + else: + await _snap("curio_pick") + main.ui.modals.run.curio_overlay.visible = false + for i in 3: + await get_tree().process_frame + # ⑤e 调试浮窗(函数直调开/关——ui_struct_probe/ui_tune_probe 先例;对局中可开) + # 判据出处: design/engineering/test-conventions.md:65 人工体验=DebugOverlay/DS_DEBUG 调试面(调试浮窗组) + main._show_debug_overlay() + for i in 3: + await get_tree().process_frame + if not main.ui.modals.menu.debug_overlay.visible: + _problems.append("调试浮窗未打开(_show_debug_overlay 直调)") + else: + await _snap("debug") + main._close_debug_overlay() + for i in 3: + await get_tree().process_frame else: _problems.append("关卡画面未出现(函数直调中间态路径)") # ── 几何元数据落盘(shot_checker 取样坐标源) ── @@ -197,8 +284,10 @@ func _ready() -> void: if mf != null: mf.store_string(JSON.stringify(_meta)) mf.close() - # ── 最小自检:七件存在且非零字节 ── - for s in ["menu", "settings", "career", "camp", "level", "battle", "status"]: + # ── 最小自检:十六件存在且非零字节 ── + for s in ["menu", "settings", "career", "camp", "level", "battle", "status", + "camp_forge", "camp_chest", "camp_tree", "camp_codex", "camp_notebook", + "legend", "battle_end", "curio_pick", "debug"]: var p := out_dir.path_join("shot_%s.png" % s) if not FileAccess.file_exists(p): _problems.append("截图缺失: %s" % p) diff --git a/game/tests/golden/README.md b/game/tests/golden/README.md index 5921d948..64c50693 100644 --- a/game/tests/golden/README.md +++ b/game/tests/golden/README.md @@ -1,33 +1,56 @@ # 视觉 golden 基线(渲染闸的比对基准) -基线**按平台各存**(2026-11-03 用户裁:跨显卡/字形渲染机制差异使跨平台逐像素必失配—— -`windows/` 与 `mac/` 子目录各持一套,平台内部照常严格比对,跨平台不互比; -平台选择实现单点 = `tools/gate_platform.sh` 的 `gate_platform_id`,比较器 `tools/golden_compare.py` -自动拼平台子目录)。本目录现持:`windows/`(棋盘三图 `board_L1_dungeon` / `board_L2_underdark` / -`board_L3_deep_underdark`,裁剪区 1036×548(D 区基座档全幅;2026-11-07 棋盘地形换代后由探针视图全幅裁剪, -原 900×760 口径退役)+ 启动态菜单 `menu_boot`,全屏 + 关卡视口两 Shot -`lmap_entry` / `lmap_boss`——2026-11-05 用户裁两 Shot 面:只对拍关卡最开始(入口停点)与 -关卡 Boss 节点(末列锚定),中间过程不对拍)、 -`level_map`(关卡画面全屏,历史基线留根——现行闸件未对拍它,由两 Shot 面接替)、 -`palette_baseline/`(色板基线,纯文件内容比对与平台无关)。 -比对判据与工具见 `tools/golden_compare.py` -(逐像素灰度差 >16 的像素占比 ≤2%)。两 Shot 基线采集=`res://probe/level_map_shot_probe.tscn` -(固定种子单关直构——确定性采集),换代入口=`tools/golden_baseline_refresh.sh --lmap`; -他平台(mac)的关卡视口基线尚未冻结——渲染闸对本平台缺基线记 SKIP 不判红,冻结义务归批末呈报。 +基线**跨平台单套**(2026-11-08 用户裁:跨平台渲染必须一致——Windows 与 macOS 对拍同一份基线, +差异必须落在容差带内;跨平台实测噪声棋盘 ≤0.033% / menu ≤0.68% changed,口径 ≤16/像素、≤2% 面积, +比较器 `tools/golden_compare.py`,禁以加基线方式消化差异)。本目录现持(单层,无平台子目录): -**未建锚面备案**:营地/设置/战斗三屏与战末结算/三选一/图例/调试浮窗四浮层无改前 golden 锚;这些呈现面的等价证据承载=现役探针/单元测试断言面+实机目检(体验验证清单 ㊵ 节)。 +- 棋盘三图 `board_L1_dungeon` / `board_L2_underdark` / `board_L3_deep_underdark`——裁剪区由探针按 + `board_meta.json` 采样坐标决定(禁硬编码像素) +- 启动态菜单 `menu_boot`(全屏,迭代 28 T4 立) +- 关卡视口两 Shot `lmap_entry` / `lmap_boss`——2026-11-05 用户裁:只对拍关卡最开始(入口停点)与 + 关卡 Boss 节点(末列锚定),中间过程不对拍 +- 屏态五锚 `camp_screen` / `settings_screen` / `battle_screen` / `career_screen` / `status_panel` + (全屏,shot_probe 七屏采集——golden 补锚批 2026-11-08:battle 段带呼吸相位冻结) +- 营地窗态五锚 `camp_forge` / `camp_chest` / `camp_tree` / `camp_codex` / `camp_notebook` + (真点热点开窗态,camp.md:45 五窗窗壳) +- 浮层四锚 `battle_end`(战末结算)/ `curio_pick`(三选一)/ `legend`(图例)/ `debug_overlay`(调试浮窗) +- `palette_baseline/`(色板基线,纯文件内容比对,与平台无关) + +采集=**真实引擎渲染**:探针以固定种子跑到指定状态截屏(棋盘 golden 帧=微光关态,呼吸相位冻结; +确定性采集)。换代入口=`tools/golden_baseline_refresh.sh`(缺省棋盘/`--menu`/`--lmap`), +提交说明须写明触发原因(强制)。两 Shot 基线采集=`res://probe/level_map_shot_probe.tscn`。 + +## 基线冻结与审核(2026-11-08 用户立) + +基线=**人定标准、机器守标准**。任何基线的新建/换代/迁移/容差修订,须**呈报用户逐张审核、 +确认后才能提交入库生效**;确认记录登记于下方审核记录节(审核人+时间+基线清单),未审核的 +基线禁合回。机器侧前置:探针自检 PASS(结构断言——非生效格零变化/生效格实绘非空等——在案) +才允许出候选图;候选图不满足自检=采集失败,不得送审。 + +## 审核记录 + +| 日期 | 基线清单 | 审核人 | 结果 | +|---|---|---|---| +| 2026-11-08 | 棋盘三图+menu_boot+lmap 两 Shot+单基线合并(6 张) | DKChan | 确认 | +| 2026-11-08 | 屏态五锚+营地窗态五锚+浮层四锚(14 张补锚) | DKChan | 确认 | ## 时效判定纪律 -基线冻结于 2026-09-30;关卡视口两 Shot(lmap_entry/lmap_boss)冻结于 2026-11-05;棋盘三图 2026-11-07 换代(采集态扩型——cell_mark_overlay 六绘制域入帧,需求卡片「QA覆盖余量收口批」卡修轮 2 V14 渲染通道裁定,新帧用户过目后重制)——两 Shot 面口径(只对拍入口停点与 Boss 节点)见上文现持清单行。**判定先于动作**:每代换代面(视觉/素材/布局改动) -跑批时先跑一次渲染闸看差异落不落在裁剪区与容差内—— +基线冻结时点:menu_boot=迭代 28 T4;lmap 两 Shot=2026-11-05;棋盘三图=2026-11-08 迭代 56 卡 2 +换代(封印受理即揭示渲染入帧);单基线合并=2026-11-08。**判定先于动作**:每代换代面 +(视觉/素材/布局改动)跑批时先跑一次渲染闸看差异落不落在裁剪区与容差内—— - **判有效**(changed 远低于 2% 阈值):基线**留在位**,不得顺手重制; -- **判失效**(changed 超阈值或换代面落在裁剪区外):**显式重制 + 呈报 + 复闸**, +- **判失效**(changed 超阈值或换代面落在裁剪区外):**显式重制+呈报+复闸**, 禁止先动后判、禁止用「重制一下就绿了」掩盖真实视觉回归。 -- **加严口径(棋盘三图)**:棋盘图 changed>0 且差异像素落在棋盘裁剪区内(1036×548)一律按**判失效** - 处理——占比远低于 2% 阈值也不判有效,显式呈报后由用户定是否重制。该口径只收紧棋盘三图的判读, - 不改写 2% 容差本身的语义(`golden_compare.py` 判据不动);启动态菜单不适用本口径。 -- **越格口径(2026-11-07 棋盘地形换代)**:棋盘地形允许越格绘制——岩顶抬升、南/东崖面、四段投影、 +- **加严口径(棋盘三图·同平台复跑)**:windows 本地对拍棋盘 changed>0 一律按**判失效**处理—— + 同种子同参数应逐位一致,任何非零差异都可能是真回归,显式呈报后由用户定是否重制; + 该口径只收紧棋盘三图的判读,不改写 2% 容差本身的语义;启动态菜单不适用本条。 +- **跨平台对拍(CI mac)**:不适用上一条——跨平台字形锯齿噪声按容差带吸收(2026-11-08 用户裁), + changed 落容差带内判有效;超容差即红停批呈报,禁加基线消化。 +- **越格口径(棋盘地形换代立)**:棋盘地形允许越格绘制——岩顶抬升、南/东崖面、四段投影、 浮空数字与引导线均按 art-bible §3.2 越出逻辑格边界;**「禁越圈」只约束类型微光层**(按该格抬升后 - 岩顶面 rect 判,不按逻辑格 rect),不约束地形/小人/浮空层。棋盘金标仍按上一条加严口径整幅严格比对。 + 岩顶面 rect 判,不按逻辑格 rect),不约束地形/小人/浮空层。棋盘金标仍按加严口径整幅严格比对。 + +**未建锚面备案**:(2026-11-08 补锚批后备案清零——上列营地/设置/战斗三屏、生涯/状态两屏顺带锚、 +营地五窗、四浮层全部入锚;此后新画面按 §8.2 扩项机制随批立锚,发现缺失即补、不再登记待置。) diff --git a/game/tests/golden/battle_end.png b/game/tests/golden/battle_end.png new file mode 100644 index 00000000..d7ae60ed Binary files /dev/null and b/game/tests/golden/battle_end.png differ diff --git a/game/tests/golden/battle_screen.png b/game/tests/golden/battle_screen.png new file mode 100644 index 00000000..7bdad634 Binary files /dev/null and b/game/tests/golden/battle_screen.png differ diff --git a/game/tests/golden/windows/board_L1_dungeon.png b/game/tests/golden/board_L1_dungeon.png similarity index 100% rename from game/tests/golden/windows/board_L1_dungeon.png rename to game/tests/golden/board_L1_dungeon.png diff --git a/game/tests/golden/windows/board_L2_underdark.png b/game/tests/golden/board_L2_underdark.png similarity index 100% rename from game/tests/golden/windows/board_L2_underdark.png rename to game/tests/golden/board_L2_underdark.png diff --git a/game/tests/golden/windows/board_L3_deep_underdark.png b/game/tests/golden/board_L3_deep_underdark.png similarity index 100% rename from game/tests/golden/windows/board_L3_deep_underdark.png rename to game/tests/golden/board_L3_deep_underdark.png diff --git a/game/tests/golden/camp_chest.png b/game/tests/golden/camp_chest.png new file mode 100644 index 00000000..92cedcf7 Binary files /dev/null and b/game/tests/golden/camp_chest.png differ diff --git a/game/tests/golden/camp_codex.png b/game/tests/golden/camp_codex.png new file mode 100644 index 00000000..c5c2bef0 Binary files /dev/null and b/game/tests/golden/camp_codex.png differ diff --git a/game/tests/golden/camp_forge.png b/game/tests/golden/camp_forge.png new file mode 100644 index 00000000..83fd0dc0 Binary files /dev/null and b/game/tests/golden/camp_forge.png differ diff --git a/game/tests/golden/camp_notebook.png b/game/tests/golden/camp_notebook.png new file mode 100644 index 00000000..77adc619 Binary files /dev/null and b/game/tests/golden/camp_notebook.png differ diff --git a/game/tests/golden/camp_screen.png b/game/tests/golden/camp_screen.png new file mode 100644 index 00000000..9d7de57e Binary files /dev/null and b/game/tests/golden/camp_screen.png differ diff --git a/game/tests/golden/camp_tree.png b/game/tests/golden/camp_tree.png new file mode 100644 index 00000000..80a0c02a Binary files /dev/null and b/game/tests/golden/camp_tree.png differ diff --git a/game/tests/golden/career_screen.png b/game/tests/golden/career_screen.png new file mode 100644 index 00000000..8339806c Binary files /dev/null and b/game/tests/golden/career_screen.png differ diff --git a/game/tests/golden/curio_pick.png b/game/tests/golden/curio_pick.png new file mode 100644 index 00000000..b8ffc1e4 Binary files /dev/null and b/game/tests/golden/curio_pick.png differ diff --git a/game/tests/golden/debug_overlay.png b/game/tests/golden/debug_overlay.png new file mode 100644 index 00000000..e8c92157 Binary files /dev/null and b/game/tests/golden/debug_overlay.png differ diff --git a/game/tests/golden/legend.png b/game/tests/golden/legend.png new file mode 100644 index 00000000..cce97e2c Binary files /dev/null and b/game/tests/golden/legend.png differ diff --git a/game/tests/golden/level_map.png b/game/tests/golden/level_map.png deleted file mode 100644 index 01135c41..00000000 Binary files a/game/tests/golden/level_map.png and /dev/null differ diff --git a/game/tests/golden/windows/lmap_boss.png b/game/tests/golden/lmap_boss.png similarity index 100% rename from game/tests/golden/windows/lmap_boss.png rename to game/tests/golden/lmap_boss.png diff --git a/game/tests/golden/windows/lmap_entry.png b/game/tests/golden/lmap_entry.png similarity index 100% rename from game/tests/golden/windows/lmap_entry.png rename to game/tests/golden/lmap_entry.png diff --git a/game/tests/golden/mac/board_L1_dungeon.png b/game/tests/golden/mac/board_L1_dungeon.png deleted file mode 100644 index 93920d11..00000000 Binary files a/game/tests/golden/mac/board_L1_dungeon.png and /dev/null differ diff --git a/game/tests/golden/mac/board_L2_underdark.png b/game/tests/golden/mac/board_L2_underdark.png deleted file mode 100644 index af6118c0..00000000 Binary files a/game/tests/golden/mac/board_L2_underdark.png and /dev/null differ diff --git a/game/tests/golden/mac/board_L3_deep_underdark.png b/game/tests/golden/mac/board_L3_deep_underdark.png deleted file mode 100644 index 3231bd4c..00000000 Binary files a/game/tests/golden/mac/board_L3_deep_underdark.png and /dev/null differ diff --git a/game/tests/golden/mac/menu_boot.png b/game/tests/golden/mac/menu_boot.png deleted file mode 100644 index b81a1dc6..00000000 Binary files a/game/tests/golden/mac/menu_boot.png and /dev/null differ diff --git a/game/tests/golden/windows/menu_boot.png b/game/tests/golden/menu_boot.png similarity index 100% rename from game/tests/golden/windows/menu_boot.png rename to game/tests/golden/menu_boot.png diff --git a/game/tests/golden/settings_screen.png b/game/tests/golden/settings_screen.png new file mode 100644 index 00000000..58b28861 Binary files /dev/null and b/game/tests/golden/settings_screen.png differ diff --git a/game/tests/golden/status_panel.png b/game/tests/golden/status_panel.png new file mode 100644 index 00000000..7130b8cc Binary files /dev/null and b/game/tests/golden/status_panel.png differ diff --git a/game/tests/unit/battle_settlement/reward_ineffective_reason_test.gd.uid b/game/tests/unit/battle_settlement/reward_ineffective_reason_test.gd.uid new file mode 100644 index 00000000..e47b8ae5 --- /dev/null +++ b/game/tests/unit/battle_settlement/reward_ineffective_reason_test.gd.uid @@ -0,0 +1 @@ +uid://dpk58o5aqeht5 diff --git a/game/tests/unit/presentation/battle_log_zero_text_test.gd.uid b/game/tests/unit/presentation/battle_log_zero_text_test.gd.uid new file mode 100644 index 00000000..80f504a2 --- /dev/null +++ b/game/tests/unit/presentation/battle_log_zero_text_test.gd.uid @@ -0,0 +1 @@ +uid://djqye4hpwgjjr diff --git a/game/tests/unit/presentation/playtest_logger_step_fields_test.gd.uid b/game/tests/unit/presentation/playtest_logger_step_fields_test.gd.uid new file mode 100644 index 00000000..2bae3652 --- /dev/null +++ b/game/tests/unit/presentation/playtest_logger_step_fields_test.gd.uid @@ -0,0 +1 @@ +uid://ch2u8pvad2upd diff --git a/reports/基线合一批-反哺点-2026-11-08.md b/reports/基线合一批-反哺点-2026-11-08.md new file mode 100644 index 00000000..3148a9df --- /dev/null +++ b/reports/基线合一批-反哺点-2026-11-08.md @@ -0,0 +1,27 @@ +# 基线合一批·反哺点(2026-11-08) + +批性质:工程批(golden 单基线跨平台合并+闸件去平台化+拆自愈采集+基线审核关卡立规+无锚面补齐 14 张)。 +以下两条待用户裁决后由文档批落文。 + +## 1. 渲染确定性参数显式化(待裁) + +背景:跨平台实测(2026-11-08,mac 真渲染 vs 库内 windows 基线)RGB 均值差为零、结构面逐位一致—— +现状渲染参数已天然确定性,本批未做任何「源头压噪」改动。防漂建议:把渲染确定性相关参数显式写死并 +登记在案——纹理导入无损模式、字体 hinting/subpixel 定位、2D 渲染法与 MSAA——任一参数静默漂移都会 +破坏单基线跨平台比对的前提。裁决后归文档批落文(候选落点:test-conventions §8.2 或 architecture)。 + +## 2. 跨平台差异实测数据(归档) + +采集方式:用户在 Mac 执行一次性测量脚本(master 同提交,runner 图形会话窗口化渲染,探针同渲染闸), +指标=changed%(>16 灰度,比较器同口径)/最大通道差/RGB 均值差/4×4 块平均差: + +| 面 | changed% | 最大通道差 | RGB 均值差 | 4×4 块平均差 | +|---|---|---|---|---| +| board_L1_dungeon | 0.025 | 180 | 0.0/0.0/0.0 | 0.01 | +| board_L2_underdark | 0.033 | 238 | 0.0/0.0/0.0 | 0.03 | +| board_L3_deep_underdark | 0.026 | 238 | 0.0/0.0/0.0 | 0.03 | +| menu_boot | 0.674 | 234 | 0.7/0.6/0.5 | 0.50 | + +判据口径(≤16/像素、≤2% 面积)内全过:棋盘余量约 60~80 倍、menu 约 3 倍。单基线跨平台可行性依据即此; +跨平台字形锯齿噪声按容差带吸收(2026-11-08 用户裁),menu 面预算余量 3 倍为常驻背景,已知真实回归 +量级(2.2~2.4%)仍会被 2% 线抓住。 diff --git a/tools/ci/golden_selfheal.sh b/tools/ci/golden_selfheal.sh deleted file mode 100644 index 7d7a0193..00000000 --- a/tools/ci/golden_selfheal.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash -# 棋盘黄金图 CI 自愈补采(2026-11-08 用户裁:补采自动化——渲染能力在 runner 会话里,人手不做二进制搬运)。 -# 背景:golden 基线按平台分目录(render_gate.sh:37 gate_platform_id),改版批只能重制本平台侧; -# 他平台基线缺失=渲染闸红(基准未建),而补采必须在他平台真实渲染——只有 runner 会话做得到。 -# 本件挂 CI full 批(ci.yml 导入步之后、跑闸步之前):缺棋盘三件基线时跑 golden_baseline_refresh -# 棋盘模式,产物提交回 master 并推送,下一轮 full 批拉取后渲染闸绿。 -# 护栏: -# - 只认 CI 跑批环境(GITHUB_WORKSPACE 必在)——本地误跑立即拒(防人手把本平台基线提交出去)。 -# - 三件基线齐盘即零动作退出(幂等——不会每轮提交,无循环)。 -# - 只 add 三件 PNG;刷新红/落盘缺/提交败/push 败一律置红不静默,下一轮 full 批自动重试。 -set -u -: "${GITHUB_WORKSPACE:?本件只属 CI 跑批环境(GITHUB_WORKSPACE 必在)——人手补采走 tools/golden_baseline_refresh.sh}" -REPO="$(cd "$(dirname "$0")/../.." && pwd)" -cd "$REPO" -. "$REPO/tools/gate_platform.sh" -PLAT="$(gate_platform_id)" -GOLDEN_DIR="game/tests/golden/$PLAT" -BOARD_FILES="board_L1_dungeon.png board_L2_underdark.png board_L3_deep_underdark.png" - -_miss=0 -for b in $BOARD_FILES; do - [ -s "$GOLDEN_DIR/$b" ] || { _miss=1; echo "[selfheal] 缺基准: $GOLDEN_DIR/$b"; } -done -if [ "$_miss" -eq 0 ]; then - echo "[selfheal] $PLAT 棋盘三件基线在库——零动作(幂等护栏)" - exit 0 -fi - -GODOT_BIN="${GODOT_BIN:?GODOT_BIN 必注(runner 自举件安装位)}" \ - bash "$REPO/tools/golden_baseline_refresh.sh" game || { - echo "[selfheal] FAIL — 补采刷新脚本红(探针自检/裁剪失败见上)"; exit 1 -} -for b in $BOARD_FILES; do - [ -s "$GOLDEN_DIR/$b" ] || { echo "[selfheal] FAIL — 刷新后仍缺: $b"; exit 1; } -done -for b in $BOARD_FILES; do - git add "$GOLDEN_DIR/$b" -done -git -c user.name="gitea-actions" -c user.email="actions@noreply.git.kuaimeo.com" \ - commit -m "黄金图自愈补采($PLAT·棋盘三件): 渲染闸缺基准——golden_baseline_refresh棋盘模式在runner会话渲染产出,本提交由CI采集步自动落库,下一轮full批拉取后渲染闸绿;触发源提交见父提交" \ - || { echo "[selfheal] FAIL — git commit 失败(无差异或工作区异常)"; exit 1; } -git push origin HEAD:master || { echo "[selfheal] FAIL — push 失败(令牌权限/分支保护;修复前每轮 full 批会重试)"; exit 1; } -echo "[selfheal] 完成 — 基线已落库,下一轮 CI 拉取生效" diff --git a/tools/golden_baseline_refresh.sh b/tools/golden_baseline_refresh.sh index 7aba1e93..d4742c31 100755 --- a/tools/golden_baseline_refresh.sh +++ b/tools/golden_baseline_refresh.sh @@ -5,8 +5,12 @@ # 缺位时先拷主区素材+导入边车再跑)。 # 模式: # --menu:shot_probe 启动态菜单整屏帧 → game/tests/golden/menu_boot.png。 +# --screens:shot_probe 十六图(七屏+营地五窗+四浮层)全量采集 → 按基线名 cp 入库 +# (camp_screen/settings_screen/battle_screen/career_screen/status_panel/camp_forge/ +# camp_chest/camp_tree/camp_codex/camp_notebook/battle_end/curio_pick/legend/debug_overlay +# ——golden 补锚批 2026-11-08)。候选图仍须用户逐张审核确认后才算冻结(审核关卡)。 # --lmap:level_map_shot_probe 关卡视口两 Shot(入口停点+Boss 列锚定——2026-11-05 用户裁 -# 两 Shot 面)→ game/tests/golden//lmap_entry.png + lmap_boss.png。 +# 两 Shot 面)→ game/tests/golden/lmap_entry.png + lmap_boss.png。 # 全屏基线可行性前置已验证:菜单连续帧差 0.0000%/max_diff=0(光柱为 # 程序生成静态贴片、零动画着色器,全屏基线不抖动);采集确定性=探针隔离档(无断点+出厂 # 设置 1280×800),与 render_gate 的 shot_menu 对比段同源同帧。 @@ -24,12 +28,13 @@ for a in "$@"; do case "$a" in --menu) MODE="menu" ;; --lmap) MODE="lmap" ;; + --screens) MODE="screens" ;; *) if [ -z "$GAME" ]; then GAME="$a"; fi ;; esac done GAME="${GAME:-$REPO/game}" GAME_WIN="$(gate_game_path "$GAME")" # Godot --path 接受形式(Windows 混合斜杠,其他平台原样) -PLAT="$(gate_platform_id)" # 基线落点=本平台子目录(game/tests/golden//) +GOLDEN="$REPO/game/tests/golden" # 单基线跨平台落点(2026-11-08 用户裁,无平台子目录) TMP="$(mktemp -d /tmp/goldenrefresh_XXXXXX)" trap 'rm -rf "$TMP"' EXIT @@ -50,9 +55,42 @@ if [ "$MODE" = "menu" ]; then echo "[golden_refresh] FAIL — 菜单截图缺失或 shot_probe 自检红(st=$st,见上)" exit 1 fi - mkdir -p "$REPO/game/tests/golden/$PLAT" - cp "$TMP/shots/shot_menu.png" "$REPO/game/tests/golden/$PLAT/menu_boot.png" || exit 1 - echo "[golden_refresh] 完成 — 菜单全屏基线已代入 game/tests/golden/$PLAT/menu_boot.png(提交说明须写明触发原因)" + mkdir -p "$GOLDEN" + cp "$TMP/shots/shot_menu.png" "$GOLDEN/menu_boot.png" || exit 1 + echo "[golden_refresh] 完成 — 菜单全屏基线已代入 game/tests/golden/menu_boot.png(提交说明须写明触发原因)" + exit 0 +fi + +if [ "$MODE" = "screens" ]; then + # 七屏+九态候选基线采集(golden 补锚批 2026-11-08):跑完整 shot_probe(十六图+meta), + # 全部按基线名 cp 入库——候选图仍须用户逐张审核确认后才算冻结(审核关卡=README「基线冻结与审核」)。 + for a in assets/fonts/ui_font_fusion_pixel_zh_hans.ttf \ + assets/art/ui/button_stone.png; do + if [ ! -f "$GAME/$a" ]; then + echo "[golden_refresh] 拒跑 — 素材缺位: ${a}(S3 停机点口径)" + exit 1 + fi + done + gate_run_timeout 420 "$TMP/run.log" "$G" --path "$GAME_WIN" --quit-after 3000 res://probe/shot_probe.tscn -- "$TMP/shots" + st=$? + grep -E "\[shot_probe\]" "$TMP/run.log" | tail -3 + if ! grep -q "问题=0" "$TMP/run.log"; then + echo "[golden_refresh] FAIL — shot_probe 自检红(st=$st,见上)" + exit 1 + fi + mkdir -p "$GOLDEN" + for m in "shot_camp.png camp_screen.png" "shot_settings.png settings_screen.png" \ + "shot_battle.png battle_screen.png" "shot_career.png career_screen.png" \ + "shot_status.png status_panel.png" "shot_camp_forge.png camp_forge.png" \ + "shot_camp_chest.png camp_chest.png" "shot_camp_tree.png camp_tree.png" \ + "shot_camp_codex.png camp_codex.png" "shot_camp_notebook.png camp_notebook.png" \ + "shot_battle_end.png battle_end.png" "shot_curio_pick.png curio_pick.png" \ + "shot_legend.png legend.png" "shot_debug.png debug_overlay.png"; do + set -- $m + [ -s "$TMP/shots/$1" ] || { echo "[golden_refresh] FAIL — 候选缺失: $1"; exit 1; } + cp "$TMP/shots/$1" "$GOLDEN/$2" || exit 1 + done + echo "[golden_refresh] 完成 — 14 张屏态/浮层候选基线已代入 game/tests/golden/(呈报用户逐张审核后随批提交)" exit 0 fi @@ -77,10 +115,10 @@ if [ "$MODE" = "lmap" ]; then echo "[golden_refresh] FAIL — 关卡视口截图缺失或探针自检红(st=$st,见上)" exit 1 fi - mkdir -p "$REPO/game/tests/golden/$PLAT" - cp "$TMP/shots/lmap_entry.png" "$REPO/game/tests/golden/$PLAT/lmap_entry.png" || exit 1 - cp "$TMP/shots/lmap_boss.png" "$REPO/game/tests/golden/$PLAT/lmap_boss.png" || exit 1 - echo "[golden_refresh] 完成 — 关卡视口两 Shot 基线已代入 game/tests/golden/$PLAT/(提交说明须写明触发原因)" + mkdir -p "$GOLDEN" + cp "$TMP/shots/lmap_entry.png" "$GOLDEN/lmap_entry.png" || exit 1 + cp "$TMP/shots/lmap_boss.png" "$GOLDEN/lmap_boss.png" || exit 1 + echo "[golden_refresh] 完成 — 关卡视口两 Shot 基线已代入 game/tests/golden/(提交说明须写明触发原因)" exit 0 fi @@ -105,8 +143,8 @@ if [ ! -s "$TMP/shots/board_meta.json" ] || ! grep -q "PASS — 三关双帧取 exit 1 fi python3 tools/golden_board_crop.py "$TMP/shots/board_meta.json" "$TMP/shots" "$TMP/crop" || exit 1 -mkdir -p "$REPO/game/tests/golden/$PLAT" +mkdir -p "$GOLDEN" for b in board_L1_dungeon.png board_L2_underdark.png board_L3_deep_underdark.png; do - cp "$TMP/crop/$b" "$REPO/game/tests/golden/$PLAT/$b" || exit 1 + cp "$TMP/crop/$b" "$GOLDEN/$b" || exit 1 done -echo "[golden_refresh] 完成 — 三张基线已换代入 game/tests/golden/$PLAT/(提交说明须写明触发原因)" +echo "[golden_refresh] 完成 — 三张基线已换代入 game/tests/golden/(提交说明须写明触发原因)" diff --git a/tools/golden_compare.py b/tools/golden_compare.py index c87decc8..01544c1a 100644 --- a/tools/golden_compare.py +++ b/tools/golden_compare.py @@ -19,13 +19,9 @@ from PIL import Image, ImageChops TOLERANCE = 16 # 灰度差容差(单通道) FAIL_RATIO = 0.02 # changed 占比失败线 -# 基线平台子目录:<黄金图根目录>/——渲染基线按平台各存(gate_platform_id 同口径), -# 平台内部照常逐像素严格比对;跨平台不互比(字形/抗锯齿机制差异,2026-11-03 用户裁)。 -_PLAT = {"win32": "windows", "darwin": "mac"}.get(sys.platform, "linux") - - -def plat_missing(base: Path) -> bool: - return not (base / _PLAT).is_dir() +# 单基线跨平台(2026-11-08 用户裁:跨平台渲染必须一致)——全平台对拍同一份基线, +# 跨平台字形锯齿噪声由容差带吸收(实测:棋盘 ≤0.033% / menu ≤0.68% changed,预算 ≤2%); +# 超容差即红(视觉回归或跨平台失配,均须人工排查,禁加基线消化)。 def compare(base: Path, curr: Path, name: str, failures: list) -> None: @@ -56,10 +52,9 @@ def main() -> int: print("用法: python tools/golden_compare.py <黄金图目录> <新截图目录> [屏清单...]") return 1 base, curr = Path(sys.argv[1]), Path(sys.argv[2]) - base = base / _PLAT if not base.is_dir(): - print("[golden] FAIL — 本平台(%s)基线目录未建:%s" % (_PLAT, base)) - print(" 处置:在该平台跑 tools/golden_baseline_refresh.sh 重制基线并提交(基线换代权限 Q6)") + print("[golden] FAIL — 基线目录未建:%s" % base) + print(" 处置:跑 tools/golden_baseline_refresh.sh 重制基线并提交(基线换代权限 Q6)") return 1 shots = sys.argv[3:] or ["shot_menu.png", "shot_camp.png", "shot_level.png", "shot_battle.png"] failures = [] diff --git a/tools/render_gate.sh b/tools/render_gate.sh index d8bfb16f..34f4656d 100755 --- a/tools/render_gate.sh +++ b/tools/render_gate.sh @@ -34,7 +34,7 @@ REPO="$(cd "$(dirname "$0")/.." && pwd)" . "$REPO/tools/gate_platform.sh" # 跨平台件(平台探测/路径形式/带超时执行/孤儿清杀) GAME="${1:-$REPO/game}" GAME_WIN="$(gate_game_path "$GAME")" # Godot --path 接受形式(Windows 混合斜杠,其他平台原样) -GOLDEN="$REPO/game/tests/golden/$(gate_platform_id)" # 本平台基线目录(按平台各存,比较器同口径) +GOLDEN="$REPO/game/tests/golden" # 单基线跨平台(2026-11-08 用户裁:跨平台渲染必须一致,差异由容差带吸收) TMP="$(mktemp -d /tmp/rendergate_XXXXXX)" trap 'rm -rf "$TMP"' EXIT _rc=0 @@ -75,7 +75,7 @@ if ! grep -q "问题=0" "$TMP/shot.log"; then echo "[render_gate] FAIL — shot_probe 未干净完成(exit=${st},缺失屏/空文件/路径断裂见上)"; _rc=1 fi if [ "$_rc" -eq 0 ]; then - python3 tools/shot_checker.py "$SHOT" shot_menu.png shot_settings.png shot_career.png shot_camp.png shot_level.png shot_battle.png shot_status.png | sed 's/^/[render_gate] /' + python3 tools/shot_checker.py "$SHOT" shot_menu.png shot_settings.png shot_career.png shot_camp.png shot_level.png shot_battle.png shot_status.png shot_camp_forge.png shot_camp_chest.png shot_camp_tree.png shot_camp_codex.png shot_camp_notebook.png shot_battle_end.png shot_curio_pick.png shot_legend.png shot_debug.png | sed 's/^/[render_gate] /' _rc="${PIPESTATUS[0]}" [ "$_rc" -ne 0 ] && echo "[render_gate] FAIL — shot_checker A-D 断言存在红(S1 停机点:首跑存量 FAIL 全量呈报登记待办清单)" fi @@ -118,7 +118,7 @@ done echo "======== 棋盘 golden 逐像素(T2——裁剪→对比,容差 provisional) ========" if [ ! -d "$GOLDEN" ]; then - echo "[render_gate] FAIL — 本平台($(gate_platform_id))基线目录缺失:${GOLDEN}(基线按平台各存、在库=验收断言②)"; _rc=1 + echo "[render_gate] FAIL — 基线目录缺失:${GOLDEN}(基线入库=验收断言②)"; _rc=1 elif [ ! -s "$BD/board_meta.json" ]; then echo "[render_gate] SKIP — board_meta.json 缺失(取证段已红),golden 对比不判" else @@ -150,7 +150,7 @@ fi echo "======== 菜单 golden 逐像素(迭代 28 T4——启动态全屏基线)========" if [ ! -s "$GOLDEN/menu_boot.png" ]; then - echo "[render_gate] FAIL — $GOLDEN/menu_boot.png 基线缺失(菜单 golden 未建——本平台跑 tools/golden_baseline_refresh.sh --menu)"; _rc=1 + echo "[render_gate] FAIL — $GOLDEN/menu_boot.png 基线缺失(菜单 golden 未建——跑 tools/golden_baseline_refresh.sh --menu)"; _rc=1 elif [ ! -s "$SHOT/shot_menu.png" ]; then echo "[render_gate] SKIP — shot_menu.png 缺失(shot_probe 段已红),菜单 golden 不判" elif [ ! -f "$GAME/assets/fonts/ui_font_fusion_pixel_zh_hans.ttf" ] || [ ! -f "$GAME/assets/art/ui/button_stone.png" ]; then @@ -159,12 +159,41 @@ else MENUG="$TMP/menugolden" mkdir -p "$MENUG" cp "$SHOT/shot_menu.png" "$MENUG/menu_boot.png" - python3 tools/golden_compare.py "$REPO/game/tests/golden" "$MENUG" menu_boot.png | sed 's/^/[render_gate] /' + python3 tools/golden_compare.py "$REPO/game/tests/golden" "$MENUG" menu_boot.png | sed 's/^/[render_gate] /' if [ "${PIPESTATUS[0]}" -ne 0 ]; then echo "[render_gate] FAIL — 菜单 golden 超容差(S2 停机点口径:核对热区——按钮换墨/布局变更属预期面=显式重制基线+呈报;超预期面=L3)"; _rc=1 fi fi +echo "======== 屏态/浮层 golden 逐像素(golden 补锚批 2026-11-08——七屏锚+营地五窗+四浮层)========" +if [ ! -s "$SHOT/shot_camp.png" ]; then + echo "[render_gate] SKIP — shot_camp.png 缺失(shot_probe 段已红),屏态/浮层 golden 不判" +else + SG="$TMP/sgolden" + mkdir -p "$SG" + cp "$SHOT/shot_camp.png" "$SG/camp_screen.png" + cp "$SHOT/shot_settings.png" "$SG/settings_screen.png" + cp "$SHOT/shot_battle.png" "$SG/battle_screen.png" + cp "$SHOT/shot_career.png" "$SG/career_screen.png" + cp "$SHOT/shot_status.png" "$SG/status_panel.png" + cp "$SHOT/shot_camp_forge.png" "$SG/camp_forge.png" + cp "$SHOT/shot_camp_chest.png" "$SG/camp_chest.png" + cp "$SHOT/shot_camp_tree.png" "$SG/camp_tree.png" + cp "$SHOT/shot_camp_codex.png" "$SG/camp_codex.png" + cp "$SHOT/shot_camp_notebook.png" "$SG/camp_notebook.png" + cp "$SHOT/shot_battle_end.png" "$SG/battle_end.png" + cp "$SHOT/shot_curio_pick.png" "$SG/curio_pick.png" + cp "$SHOT/shot_legend.png" "$SG/legend.png" + cp "$SHOT/shot_debug.png" "$SG/debug_overlay.png" + python3 tools/golden_compare.py "$REPO/game/tests/golden" "$SG" \ + camp_screen.png settings_screen.png battle_screen.png career_screen.png status_panel.png \ + camp_forge.png camp_chest.png camp_tree.png camp_codex.png camp_notebook.png \ + battle_end.png curio_pick.png legend.png debug_overlay.png | sed 's/^/[render_gate] /' + if [ "${PIPESTATUS[0]}" -ne 0 ]; then + echo "[render_gate] FAIL — 屏态/浮层 golden 超容差(S2 停机点口径:核对热区——预期面=显式重制基线+呈报审核;超预期面=L3)"; _rc=1 + fi +fi + echo "======== 关卡视口 golden 逐像素(卷轴视口批——两 Shot 面:入口停点+Boss 列锚定)========" LM="$TMP/lmapshots" @@ -182,9 +211,8 @@ scan_log "level_map_shot_probe" "$TMP/lm.log" if [ "$_rc" -eq 0 ]; then for b in lmap_entry.png lmap_boss.png; do if [ ! -s "$GOLDEN/$b" ]; then - # 两 Shot 面(2026-11-05 用户裁)各平台独立冻结——本平台缺基线=未冻结态记 SKIP 不判红 - #(windows 先锚;他平台冻结义务归收尾呈报),已锚平台照常逐像素严判 - echo "[render_gate] SKIP — $GOLDEN/$b 基线缺失(本平台未冻结——跑 tools/golden_baseline_refresh.sh --lmap)" + # 两 Shot 面(2026-11-05 用户裁)缺基线=未冻结态记 SKIP 不判红(先锚后盯),已锚照常逐像素严判 + echo "[render_gate] SKIP — $GOLDEN/$b 基线缺失(未冻结——跑 tools/golden_baseline_refresh.sh --lmap)" else LMG="$TMP/lmgolden" mkdir -p "$LMG"