From 36f652c63a98c418e1536a49ae7800c6b4c38111 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Thu, 1 Oct 2026 05:57:12 +0800 Subject: [PATCH] =?UTF-8?q?iter9-numfix-e:=20L2/L3=20=E6=95=B0=E5=AD=97?= =?UTF-8?q?=E5=8F=AF=E8=AF=BB=E6=80=A7=E4=BA=8C=E6=AC=A1=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E2=80=94=E2=80=94=E7=94=B2=E6=A1=88=E8=AF=81=E4=BC=AA=E6=94=B9?= =?UTF-8?q?=E8=89=B2=E7=89=8C=E5=9E=AB=E5=BA=95=EF=BC=88=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E5=B8=A7=E5=8F=96=E8=AF=81=E9=A9=B1=E5=8A=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 用户实机复测「还是看不清」+ 追责「未经验证不许报完成」:补 board_shot_probe 渲染帧取证探针 (窗口化真实 LevelRunner+novice 18 步+BoardView 真渲染+视口 PNG,L1/L2/L3 三关)—— 深墨直画在 underdark 系地板放大后可辨但观感隐形(双峰纹理:近黑斑+近白筋并存, 任何墨族直画必融一峰)——甲案(按板自适应墨族)证伪 - 修复改三态呈现:dungeon 亮族直画(p90≥4.5/均值≥2.5)/中暗板深墨直画(均值门收紧 3.0)/ 双峰板色牌垫底(素牌+深墨 12:1 纹理免疫——v2 色牌形制复役) - 目视验证(本会话亲验渲染帧):L2/L3 数字素牌深墨清晰可读,取证图 artwork/qa-board-shots-20261001/(board_L1/L2/L3 + _zoom_L2 证伪对照) - 机检同步:terrain_tile_sets_test 三态断言 19/19;art-bible v3.4(三态+目视验证面入流程); 冒烟断言随新语义更新(雷当量 6/8/8 钉值/大奖励与 L3 上限允许模拟档购血叠加——v3.4 购物 改变 max_hp 序列语义,非功能回归) - 轻闸:冒烟 596/0+四探针 PASS;剂量-响应点(slip 半档 N=100)L1 36%/战役 1%——悬崖三点齐 --- design/art/art-bible.md | 5 +- game/presentation/views/board_view.gd | 43 ++++++++----- game/smoke/board_shot_probe.gd | 62 +++++++++++++++++++ game/smoke/board_shot_probe.tscn | 6 ++ game/smoke/smoke_all.gd | 18 ++++-- .../presentation/terrain_tile_sets_test.gd | 43 +++++++------ 6 files changed, 139 insertions(+), 38 deletions(-) create mode 100644 game/smoke/board_shot_probe.gd create mode 100644 game/smoke/board_shot_probe.tscn diff --git a/design/art/art-bible.md b/design/art/art-bible.md index 858c6136..49a528d9 100644 --- a/design/art/art-bible.md +++ b/design/art/art-bible.md @@ -1,6 +1,6 @@ # 美术圣经(Art Bible)— Dungeon Sweeper -> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**(§6 环境/§7 UI/§8 资产规范/§9 参考与反参考已于 2026-09-23 v1.8 按 D1/D2/D4/D5 框架成文;D3=§5 角色已于 v1.6 落地、v1.7 抽离独立文档;框架 2026-09-22 用户拍板,原登记于中间产物修订指导,经裁决不入库,明细归台账 125);历次修订明细见文末版本历史(现行为 v3.3) +> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**(§6 环境/§7 UI/§8 资产规范/§9 参考与反参考已于 2026-09-23 v1.8 按 D1/D2/D4/D5 框架成文;D3=§5 角色已于 v1.6 落地、v1.7 抽离独立文档;框架 2026-09-22 用户拍板,原登记于中间产物修订指导,经裁决不入库,明细归台账 125);历次修订明细见文末版本历史(现行为 v3.4) > > **Art Director Sign-Off (AD-ART-BIBLE)**:历次批均 SKIPPED(v1.8 §6–9 补全亦 SKIPPED)— Lean/Solo mode(非 PHASE-GATE;§6–9 已成文,full 门禁签核时机待用户表态) > 风格基调(2026-09-10 用户定稿):地牢题材但**非暗黑风**——轻快明亮的幻想冒险风,全年龄;像素风;角色 2~3 头身 @@ -191,7 +191,7 @@ - **掩尘 `#C9AF83`**|未开格底色,覆尘完整砖:全作的"未知",画面最大沉默面。←原二;§2.1 - **裸土 `#B39A78`**|已开格底色,碎裂裸露:更收更退;2026-09-29 v3.0 明度分层——已开格(挖出的空间)走**世界底层档**,明度低于未开格墙体一档(与 §8.5 图底分离同轴),候选 L 0.08–0.14 带(样张 v3 实测修正:L0.20+ 底配纯白也仅 3.4:1,数学不可达 4.5:1),样张 v3 冻结。←原三 -- **数字墨双色制(2026-09-29 v3.0)**|1–8 **不做八色分色**,按密度分三档色温(冷=稀疏,暖=密集);**字形为唯一数值载体**,色温只作密度氛围。①**棋盘数字墨=按板自适应双族**(2026-10-01 v3.3 修订——L2/L3 亮瓦融字缺陷修复,用户裁甲案):暗纹理板(dungeon 系)=**亮族**三档 1–3 冷亮青白 #D8ECF0/4–5 暖白 #F0E2C8/6–8 金亮 #F8E3A8 直画(样张 v3 冻结原样);亮筋纹板(underdark/deep_underdark 系)=**深墨族**三档 1–3 青墨 #132E36/4–5 赭墨 #3C250C/6–8 锈墨 #4A1E14(色温语义同构;旧深墨三档自垫牌场景复役)。选族判据=归一后地板逐像素明度分位数:所选族对 p90 亮斑 ≥4.5:1 且对均值 ≥2.0:1(亮白筋纹上亮墨实测 CR 1.0–1.9 融字,深墨族 5.1–5.3);选族管线落码单点 `board_view._digit_ink_mode`,机检面=`terrain_tile_sets_test` 三关对比断言;缺盘平色回退态(#B39A78 中明度底)默认深墨族;②**垫牌与 UI 数据墨=深墨族 `#33261B` 系**(L≤0.027 不变,§7.2)。八色必与红金蓝紫赤语义冲突,故弃。←原一/原二;P3 +- **数字墨双色制(2026-09-29 v3.0)**|1–8 **不做八色分色**,按密度分三档色温(冷=稀疏,暖=密集);**字形为唯一数值载体**,色温只作密度氛围。①**棋盘数字呈现=按板自适应三态**(2026-10-01 v3.4 修订——v3.3 深墨族方案经渲染帧取证证伪:underdark 系地板近黑斑与近白筋并存〔双峰纹理〕,深墨直画融黑斑同样隐形,任何墨族直画不可救):暗纹理板(dungeon 系)=**亮族**三档 1–3 冷亮青白 #D8ECF0/4–5 暖白 #F0E2C8/6–8 金亮 #F8E3A8 直画(样张 v3 冻结原样;判据=族最弱成员对 p90 ≥4.5:1 且对均值 ≥2.5:1);双峰纹理板(underdark/deep_underdark 系)=**色牌垫底**(素牌 DIGIT_PLATE + 深墨 #33261B 12:1,纹理免疫——v2 色牌形制在双峰板复役;判据=亮深两族皆不过门,深族均值门 3.0)。选模式管线落码单点 `board_view._digit_ink_mode`(三态:亮直/深直/落牌),机检面=`terrain_tile_sets_test` 三关呈现模式断言;**目视验证面=board_shot_probe 渲染帧取证**(机检≠看得清,2026-10-01 用户教训);缺盘平色回退态(#B39A78 中明度底)默认深墨直画;②**垫牌与 UI 数据墨=深墨族 `#33261B` 系**(L≤0.027 不变,§7.2)。八色必与红金蓝紫赤语义冲突,故弃。←原一/原二;P3 - **封印黑 `#000000`**|封印(插旗)宣告色,无色相:玩家的宣告"此格封印"(色史:旗朱 `#C63D22` →封印紫 `#7C3AA8` 2026-09-26→**封印黑 2026-09-28 用户拍板**——紫色与微光蓝族〔魔法微光 256° 等〕色相冲突,黑色无色相天然避开 300°–345° 禁入带与全部微光色带;未开格墙顶明度 ≈113 对纯黑对比 ≥4.4:1,旗杆米白保可读)。旗朱随封印紫批退役〔棋盘侧〕,红族棋盘侧自此只剩危险红(2026-09-28 起人设背包旗=旗朱红矩形旗——旗色脱钩拍板,人设侧详见 character-design 背包旗条)。←原一;P2;封印紫批 2026-09-26;封印黑批 2026-09-28 - **危险红 `#A8321F`**|危险色:雷=被惊动的巢穴,兑现档红,更深更沉(色值不变)。←原三;P2 - **奖励金 `#F0A828`**|已兑现的收益:金币/宝箱/蜡封,全作唯一亮金。←P2;§2.3/2.4 @@ -484,6 +484,7 @@ ## 版本历史 +- **v3.4(2026-10-01)数字呈现三态修订(甲案深墨族方案渲染帧取证证伪后改版)**:v3.3 的「L2/L3 切深墨族直画」经 board_shot_probe 渲染帧取证证伪——underdark 系地板双峰纹理(近黑斑 p10 0.011+近白筋 p90 0.40+)上深墨直画融黑斑隐形(用户实机复测「还是看不清」实证),任何墨族直画不可救;改**三态**:dungeon 亮族直画(门 p90≥4.5/均值≥2.5)/中暗板深墨直画(门 p90≥4.5/均值≥3.0)/双峰板**色牌垫底**(素牌+深墨 12:1,v2 色牌形制复役);机检三关呈现模式断言 19/19;目视验证面=board_shot_probe 渲染帧取证入流程(机检≠看得清)。§1.4 样张闸第四验收项判读随三态口径件注记。 - **v3.3(2026-10-01)数字墨按板自适应修订(L2/L3 亮瓦融字缺陷修复,用户裁甲案「墨色按关自适应+补三关对比断言」)**:§4.1 数字墨双色制①改**按板自适应双族**——暗纹理板维持亮族三档(样张 v3 冻结原样),亮筋纹板(underdark/deep_underdark)切深墨族三档(青墨/赭墨/锈墨复役);选族判据=所选族对归一后地板 p90 亮斑 ≥4.5:1 且对均值 ≥2.0:1,落码单点 `board_view._digit_ink_mode`,机检=`terrain_tile_sets_test` 三关对比断言 21/21;缺盘平色回退默认深墨族。§4.1 其余条款(字形唯一载体/三档色温/垫牌后备)不变。 - **v3.1(2026-09-30)越权清理批(用户指令:按域内审查修订案修改,概念定义归还 GDD 正主)**:①**微光口径四处改指路**——§1.2 原则三/§1.4 样张清单去「常显」、§4.2 色带条去「常显」、§4.6.3 去「全部未开格」,统一指路《HUD框架》「未开格类型微光」节(2026-09-30 收口批落位的唯一正主,他文只许指路);②**概念越权剥离**——雷格叙事属名与换轴史、封印插旗语义(→registry「封印」条+《排雷玩法》玩家说法)、极性定义(→《地图战斗资源》极性提案)、完美排雷定义与兑付结构(→同)、敌意图预告语义与「待机即预示」(→《敌人系统》意图线索节)、奖励 weight 与兑付/库存语义(→《地图战斗资源》)、键名沿革史(empower/fortify 来源=敌方技能施放、alert 2026-09-20 八型批换装、ac_up 2026-09-16 换装——自 §4.6.1 移除留痕)、甲档 armor 数据实态(L1 全轻甲/L2 除 drider 外全轻甲/L3 duergar·duergar_king 重甲)与旧键集史({grub, ambusher} 随 DND 更名退役——自 §4.6.4 移除留痕)、「敌不占格」断言、「类型分布即先验」机制 rationale、「分布属初始化随机」归属断言(改画法口径「分布每板一次跨轮不复掷」);③**实装状态与排期注记移本历史留痕**——游戏 UiKit.HERO_BLUE 已同步 #2E6E8E→#2E86DE、UiKit.CONTACT_GLOW 同值 #D6ECFF、旧可挖域绿相 DIG_EDGE 已随接触带方案退役、死亡表现切片实装=程序化 tween(灰化罩+碎块弹飞 5 块 0.45s,挂 unit_died)、封印特效「待表现批」、§6.1 背景插画「排期归表现层升级批」、panel_stone/button_stone 接线归最近表现批、§7.2 对比度违例「修复归表现批」、§7.5「占位不空窗」注记;④**角嵌退役条改写**为现行「未开格预读通道(微光单通道)」条(退役史见 v2.12);⑤**残留矛盾清除**——§3.2 呈现基准条「装饰变体一律纹理级」旧措辞随 v3.0 物件级解禁同步;⑥§2.8 营地承载元素/§7.5 钥石呈现位改指 §6.2(事实源边界在该节);⑦**补漏五处(同批完工审计发现,同类剥离留痕)**——§4.1 危险红「机关红」更名史、§6.2 营地贴图「排期归 UI 贴图重构批」与「占位不空窗」注记、§7.4 字体「接入归表现批」注记、§8.1 扩板与 1920 档换算值「归表现层升级批成文」(改债务指路 06「分辨率档位与棋盘格边长口径」条)、§8.3 「终稿目录归表现批定」注记、§1.4 整屏样张闸「表现层落码批开工前」改屏级落码开工前、§7.1 「样式终稿归样张后表现批」改随样张闸后定稿。**零美术信息损失**:全部色值/机检断言/纹样描述/画法参数不动,样张 v2 四冻结之②③维持 - **v3.2(2026-09-30)样张 v3 冻结批+缩放分级修订+案甲落定(迭代 9 T1/T2/T9;任务书=`design/research/任务书-美术圣经v3.5冻结与落码-2026-09-29.md`)**:①§1.4 补**样张 v3 冻结记录**——机检 17/17 全绿,断言定值两件(A3b 图底分离定值 Δ≥30〔实测 38〕/A4 尺寸基线硬等冻结 立绘 46×48+小人源 32×32),F6 沿用 v3.4;**四冻结①④重冻**——①砖面形态=E 格高墙 30% 词表扩展+格底明度分档(世界底层档 L≈0.10)+装饰物件级解禁/④数字形制=亮墨三档直画+色牌后备;②色板③动画维持冻结。产物 `artwork/mockups/sample-batch-v3-final-2026-09-30.png`+机检报告【冻结】;气质终审=2026-09-29 用户验收豁免(台账 281)。②**§8.2 缩放纪律分级修订**(2026-09-26 用户裁「改规则不豁免」落文,修订窗口=v3.0 落籍):甲类文本语义件(数字/图标/旗/砖面/纹样)维持严格整数倍;乙类角色精灵允许运行时非整数倍(NEAREST 不变,判据=实机目检可感不适即回整数档);1920 档策略经三裁决**案甲落定**(全局等比 stretch keep——v3.2 当日 T9 落文,详见 §8.2)。头注「现行为」随批 v3.2。 diff --git a/game/presentation/views/board_view.gd b/game/presentation/views/board_view.gd index a7a655ae..d117cea6 100644 --- a/game/presentation/views/board_view.gd +++ b/game/presentation/views/board_view.gd @@ -132,7 +132,8 @@ var _flag_tex: Texture2D = null # 封印旗纹理(缺盘=null=字形「旗 var _tiles_board_type := "dungeon" # 当前装载瓦片套的 board_type(rebuild 按板切换——T6 换色批) var _digit_dark_ink := true # 数字墨族按板自适应(true=深墨三档)。缺盘平色回退态 #B39A78 裸土 # 为中明度底——深墨族 CR 4.96 达标、亮墨族仅 2.2,故缺盘默认深墨;瓦片装载后按实测分位数覆写 - #(L2/L3 亮白筋纹瓦片上亮墨融字缺陷修复 2026-10-01,判据见 _digit_ink_mode) +var _digit_plate := false # 数字色牌垫底模式(双峰纹理板——直画两族皆不过门槛时落牌, + # 素牌+深墨 12:1 纹理免疫;2026-10-01 渲染帧取证 underdark 系深墨直画隐形后启用) var cell_size: float: # 只读属性(ui_layout_probe 断言面——旧私有名 _cell 的契约化出口) get: @@ -727,7 +728,13 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void: u.bg_sb = sb u.glyph = glyph u.glyph_color = digit_col - u.plate = revealed and (glyph == "雷" or glyph == "?") # 色牌后备(v3.5 批 B2——数字直画退牌;雷/「?」维持牌载保读性;复杂格底〔物件纹样/装饰变体格〕判定随装饰瓦片自产批扩) + # 色牌后备(v3.5 批 B2——数字直画退牌;雷/「?」维持牌载保读性)+ 2026-10-01 v3.4:双峰纹理板 + #(_digit_plate——underdark 系亮白筋+近黑斑并存的直画不可救纹理)数字落牌,牌上深墨 12:1 + var plate_digit := _digit_plate and glyph != "" and glyph != "雷" and glyph != "?" and glyph != "旗" and glyph != "人" + if plate_digit: + digit_col = COL_DIGIT # 牌上深墨族(素牌 12:1——双峰纹理免疫) + u.glyph_color = digit_col + u.plate = revealed and (glyph == "雷" or glyph == "?" or plate_digit) u.outline_sides.assign([]) # typed Array[int](代码审查批 3 类型收紧——assign 保持引用语义) u.facade_south = false u.proj_band = 0.0 @@ -884,6 +891,7 @@ func _load_board_tiles(board_type: String = "dungeon") -> void: floors[i] = _normalize_floor_luma(floors[i], 0.10, lumas) var ink_mode := _digit_ink_mode(lumas) _digit_dark_ink = bool(ink_mode.dark) + _digit_plate = bool(ink_mode.plate) # 六镜像件(词表 §二:wall_e←wall_w / wall_ne←wall_nw / wall_se←wall_sw / # wall_end_e←wall_end_w / wall_in_ne←wall_in_nw / wall_in_se←wall_in_sw;只水平翻转禁旋转 # ——北受光不受损) @@ -954,18 +962,21 @@ static func _srgb_lin(c: float) -> float: # ---- 数字墨族按板自适应(L2/L3 亮瓦融字缺陷修复 2026-10-01——甲案:墨色按关自适应+三关对比断言)---- const DIGIT_INK_CR_P90 := 4.5 # 墨族对地板 p90 亮斑对比度门槛(失效模式=亮白筋纹冲字,WCAG 数字体同源) -const DIGIT_INK_CR_MEAN := 2.0 # 墨族对地板均值对比度门槛(次要防中调底线——引擎测光三关实测 2.4–5.8;选族主判据=p90) +const DIGIT_INK_CR_MEAN_BRIGHT := 2.5 # 亮族对地板均值门槛(中调防吃字) +const DIGIT_INK_CR_MEAN_DARK := 3.0 # 深族对地板均值门槛(大字 AA 级——2.4 的实测观感=隐形,v3.4 收紧) -# 归一后地板逐像素明度 → 选墨族。规则:家族最弱成员(亮族=金亮/深族=锈墨)对 p90 ≥ 4.5 且对均值 -# ≥ 2.5;亮族过选亮族,否则深族;两族皆不过取对 p90 较强者并告警(机检断言面=terrain_tile_sets_test)。 -# 实测(2026-10-01):dungeon p90=0.121 亮族 5.01/6.02 ✓ 维持;underdark p90=0.406 亮族 1.88/4.33 ✗ -# 深族 6.20/2.69 ✓;deep_underdark p90=0.395 亮族 1.93/4.29 ✗ 深族 6.05/2.72 ✓——L2/L3 切深墨族 +# 归一后地板逐像素明度 → 选数字呈现模式。三态:亮族直画(暗纹理板)/深族直画(中暗板)/ +# 色牌垫底(双峰纹理板——近黑斑与近白筋并存时任何墨族直画必融一峰,2026-10-01 渲染帧取证 +# 实证 underdark 系深墨隐形,牌上深墨 12:1 纹理免疫)。家族最弱成员(亮族=金亮/深族=锈墨) +# 对 p90 与均值双门槛;亮族过选亮族,否则深族(均值门 3.0),两族皆不过落牌。 +# 实测(2026-10-01):dungeon p90=0.121 亮族 4.79/5.80 ✓ 直画;underdark p90=0.406 亮族 ✗、 +# 深族 5.34/**2.41**(均值 <3.0 ✗)→ 落牌;deep_underdark 同→落牌 static func _digit_ink_mode(lumas: Array) -> Dictionary: var sorted := lumas.duplicate() sorted.sort() var n := sorted.size() if n == 0: - return {"dark": false, "cr_p90_bright": 0.0, "cr_mean_bright": 0.0, "cr_p90_dark": 0.0, "cr_mean_dark": 0.0} + return {"dark": false, "plate": false, "cr_p90_bright": 0.0, "cr_mean_bright": 0.0, "cr_p90_dark": 0.0, "cr_mean_dark": 0.0} var p90: float = sorted[int(n * 0.9)] var mean := 0.0 for v in sorted: @@ -975,13 +986,17 @@ static func _digit_ink_mode(lumas: Array) -> Dictionary: var cr_mean_b := _contrast_y(UiKit.INK_L_GOLD, mean) var cr_p90_d := _contrast_y(UiKit.INK_RUST, p90) var cr_mean_d := _contrast_y(UiKit.INK_RUST, mean) - var bright_ok := cr_p90_b >= DIGIT_INK_CR_P90 and cr_mean_b >= DIGIT_INK_CR_MEAN - var dark_ok := cr_p90_d >= DIGIT_INK_CR_P90 and cr_mean_d >= DIGIT_INK_CR_MEAN - var dark := not bright_ok - if dark and not dark_ok and cr_p90_b > cr_p90_d: + var bright_ok := cr_p90_b >= DIGIT_INK_CR_P90 and cr_mean_b >= DIGIT_INK_CR_MEAN_BRIGHT + var dark_ok := cr_p90_d >= DIGIT_INK_CR_P90 and cr_mean_d >= DIGIT_INK_CR_MEAN_DARK + var plate := false + var dark := false + if bright_ok: dark = false - push_warning("[棋盘] 地板纹理两族墨皆不过对比门槛——取对亮斑较强亮族(cr_p90=%.2f)" % cr_p90_b) - return {"dark": dark, "cr_p90_bright": cr_p90_b, "cr_mean_bright": cr_mean_b, + elif dark_ok: + dark = true + else: + plate = true # 双峰纹理:直画不可救,色牌兜底(素牌+深墨 12:1) + return {"dark": dark, "plate": plate, "cr_p90_bright": cr_p90_b, "cr_mean_bright": cr_mean_b, "cr_p90_dark": cr_p90_d, "cr_mean_dark": cr_mean_d} diff --git a/game/smoke/board_shot_probe.gd b/game/smoke/board_shot_probe.gd new file mode 100644 index 00000000..b340052a --- /dev/null +++ b/game/smoke/board_shot_probe.gd @@ -0,0 +1,62 @@ +# VERTICAL SLICE - NOT FOR PRODUCTION +# 棋盘渲染帧取证探针(2026-10-01——数字墨族按板自适应修复的目视验证面:机检≠看得清)。 +# 必须**窗口化**运行(headless 无渲染面): +# Godot_console.exe --path . res://smoke/board_shot_probe.tscn -- <输出目录> +# 流程:L1/L2/L3 各起一板(真实 LevelRunner+settle_batch 驱动 ~18 步 novice——自然揭示数字/旗), +# BoardView 真渲染,frame_post_draw 同步后抓视口 PNG。零存档触碰(无 save_manager 引用)。 +extends Node + +const AutoPlayer := preload("res://smoke/auto_player.gd") + + +func _ready() -> void: + var args := OS.get_cmdline_user_args() + var out := args[0] if args.size() >= 1 and args[0] != "" else "user://board-shots" + DirAccess.make_dir_recursive_absolute(out) + await get_tree().process_frame + var db = load("res://core/config_db.gd").new() + var ok: bool = db.load_tables("res://data/tables", true) + if not ok: + push_error("[board_shot] 表加载失败") + get_tree().quit(1) + return + var ui_font: Font = load("res://presentation/views/ui_kit.gd").load_font(load("res://presentation/views/ui_kit.gd").FONT_UI) + var targets := [[1, "L1_dungeon"], [2, "L2_underdark"], [3, "L3_deep_underdark"]] + for t in targets: + var level_index: int = t[0] + var tag: String = t[1] + var rng = load("res://core/rng_service.gd").new() + rng.set_master_seed(420000 + level_index) + var runner = load("res://core/level_runner.gd").new(db, rng) + if not runner.start_level(level_index, 420000 + level_index, {}, 0): + push_error("[board_shot] 开关失败 level=%d" % level_index) + get_tree().quit(1) + return + var settlement = runner.current_battle() + for i in range(18): + var act: Dictionary = AutoPlayer.novice_action(settlement.minesweeper) + var kind := String(act.get("kind", "stop")) + if kind == "stop": + break + var op := kind + if kind == "flag": + op = "flag_place" + settlement.minesweeper.settle_batch([{"action": op, "cell": act.get("cell")}]) + var view = load("res://presentation/views/board_view.gd").new() + view.size = Vector2(900, 900) + view.position = Vector2(40, 40) + if ui_font != null: + view.set_world_font(ui_font) + add_child(view) + view.rebuild(settlement.minesweeper.current_board(), settlement.minesweeper) + for i in range(6): + await get_tree().process_frame + await RenderingServer.frame_post_draw + var img := get_viewport().get_texture().get_image() + var path := "%s/board_%s.png" % [out, tag] + img.save_png(path) + print("[board_shot] saved %s (%dx%d)" % [path, img.get_width(), img.get_height()]) + view.queue_free() + await get_tree().process_frame + print("[board_shot] done") + get_tree().quit(0) diff --git a/game/smoke/board_shot_probe.tscn b/game/smoke/board_shot_probe.tscn new file mode 100644 index 00000000..447adae5 --- /dev/null +++ b/game/smoke/board_shot_probe.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://smoke/board_shot_probe.gd" id="1"] + +[node name="BoardShotProbe" type="Node"] +script = ExtResource("1") diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index f4a34ab8..3ce0aace 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -295,8 +295,11 @@ func _smoke_adventure() -> void: if reward_ok: for i in rewards.size(): var r: Dictionary = rewards[i] + var exp_max := int(_db.hero.max_hp) + hp_bonus * (i + 1) + # new_max_hp:i==0 精确(L1 模板无商店);i>=1 模拟档商店购血(2026-10-01 v3.4)合法叠加 → 单调下界 + var max_ok := int(r.get("new_max_hp", -1)) == exp_max if i == 0 else int(r.get("new_max_hp", -1)) >= exp_max if int(r.get("hp_bonus", -1)) != hp_bonus or int(r.get("from_level", -1)) != i + 1 \ - or int(r.get("new_max_hp", -1)) != int(_db.hero.max_hp) + hp_bonus * (i + 1) \ + or not max_ok \ or int(r.get("keystone_gained", -1)) != int(_db.adventure.keystone_per_level): reward_ok = false check(reward_ok, "大奖励读数:hp_bonus=表值 · 新上限累加 · 回满=新上限 · 钥石逐关注账") @@ -307,10 +310,17 @@ func _smoke_adventure() -> void: var exp_l3 := maxi(1, int(round(float(int(_db.hero.max_hp) + 2 * hp_bonus) * k))) var idx_l2 := seq.find(exp_l2) var idx_l3 := seq.find(exp_l3) + var has_l3 := idx_l3 > idx_l2 + if not has_l3: + for j in range(maxi(idx_l2, 1), seq.size()): + if int(seq[j]) >= exp_l3: + has_l3 = true + break check(seq.size() > 0 and int(seq[0]) == exp_base, "L1 首战上限入参 = 基础表值(%d——加算区冒险开始全 0)" % exp_base) - check(idx_l2 > 0 and idx_l3 > idx_l2, - "L2/L3 首战上限入参 = 基础+加算(%d→%d——大奖励经覆盖值通道传播)" % [exp_l2, exp_l3]) + # L3 精确值可被模拟档商店购血(2026-10-01 v3.4)先行抬高 → 退化为「存在 ≥ exp_l3」单调检查 + check(idx_l2 > 0 and has_l3, + "L2/L3 首战上限入参 = 基础+加算(%d→%d≥——大奖励覆盖通道传播,L3 允许购血先行叠加)" % [exp_l2, exp_l3]) # ③ 种子域可复现(同种子两局:summary + 上限序列 + 行进序列逐位一致) var st2: Dictionary = autoplay.run_adventure(_db, 42, 900, 400) check(st2["summary"] == sum and st2["encounter_max_hp_seq"] == seq @@ -3249,7 +3259,7 @@ func _smoke_nine_types_gp_stream() -> void: gp_neg = int(row.weight) check(cell_rows == 22, "奖励字典 22 行(九型×正负 18 格 + aux 正三投放/负一占位——魔法道具内容批 2026-09-29;清图槽独立批尾不计)") # 雷格负向直伤当量(迭代 8 卡⑧——真人反馈「三雷 3/4 血无后果感」投值:三行各抬 +3 档,负向≈正向 2 倍定价) - var mine_power := {"heal_backlash": 4, "pierce_snare": 5, "magic_recoil": 5} + var mine_power := {"heal_backlash": 6, "pierce_snare": 8, "magic_recoil": 8} # 数值修复批 2026-10-01 再抬档(旧 4/5/5,见 numfix 报告) for row in _db.reward.rows: var rid := String(row.get("reward_id", "")) if mine_power.has(rid): diff --git a/game/tests/unit/presentation/terrain_tile_sets_test.gd b/game/tests/unit/presentation/terrain_tile_sets_test.gd index e243349e..ba6f4c81 100644 --- a/game/tests/unit/presentation/terrain_tile_sets_test.gd +++ b/game/tests/unit/presentation/terrain_tile_sets_test.gd @@ -131,13 +131,13 @@ func _load_stats(board_dir: String, fname: String, top_half: bool) -> Dictionary return {"h": h_sum / n_h, "v": v_sum / n_v} -# 数字墨族按板自适应对比断言(L2/L3 亮瓦融字缺陷修复 2026-10-01——甲案机检面): -# 每个图型走 BoardView 同一归一+测光+选族管线(禁测试侧另写算法),断言: -# ①所选墨族对 p90 亮斑 ≥4.5 且对均值 ≥2.5(双门槛,同 _digit_ink_mode 判据常量) -# ②期望族面:dungeon=亮族(样张 v3.5 冻结④原样);underdark/deep_underdark=深族(亮白筋纹 p95 -# 0.66–0.77 上亮墨 CR 1.0–1.9 融字实证)——期望表防「选族逻辑被顺手改坏」回归 +# 数字呈现模式对比断言(L2/L3 亮瓦融字缺陷修复 2026-10-01——甲案机检面,v3.4 扩三态): +# 每个图型走 BoardView 同一归一+测光+选模式管线(禁测试侧另写算法),断言: +# ①dungeon=亮墨族直画(对 p90 ≥4.5 且对均值 ≥2.5);②underdark/deep_underdark=色牌垫底 +# (双峰纹理直画不可救——渲染帧取证深墨隐形实证,牌上深墨 12:1);③直画模式的双门槛值照抄 +# BoardView 判据常量(防判据漂移) func _test_digit_ink_modes(board_dir: String, types: Array) -> void: - var expect_dark := {"dungeon": false, "underdark": true, "deep_underdark": true} + var expect_plate := {"dungeon": false, "underdark": true, "deep_underdark": true} var variants: int = int(BoardViewScript.TILE_FLOOR_VARIANTS) for bt in types: var lumas: Array = [] @@ -153,16 +153,23 @@ func _test_digit_ink_modes(board_dir: String, types: Array) -> void: _check("图型 %s 数字墨族断言样本可读" % String(bt), false) continue var mode: Dictionary = BoardViewScript._digit_ink_mode(lumas) + var plate: bool = bool(mode["plate"]) var dark: bool = bool(mode["dark"]) - var cr_p90 := float(mode["cr_p90_bright"]) if not dark else float(mode["cr_p90_dark"]) - var cr_mean := float(mode["cr_mean_bright"]) if not dark else float(mode["cr_mean_dark"]) - var family := "亮墨族" if not dark else "深墨族" - _check("图型 %s 所选%s 对 p90 ≥%.1f(实测 %.2f)" % [String(bt), family, - float(BoardViewScript.DIGIT_INK_CR_P90), cr_p90], - cr_p90 >= float(BoardViewScript.DIGIT_INK_CR_P90)) - _check("图型 %s 所选%s 对均值 ≥%.1f(实测 %.2f)" % [String(bt), family, - float(BoardViewScript.DIGIT_INK_CR_MEAN), cr_mean], - cr_mean >= float(BoardViewScript.DIGIT_INK_CR_MEAN)) - if expect_dark.has(bt): - _check("图型 %s 墨族期望=%s(实测=%s)" % [String(bt), - "深族" if bool(expect_dark[bt]) else "亮族", family], dark == bool(expect_dark[bt])) + var mode_name := "色牌垫底" if plate else ("深墨族直画" if dark else "亮墨族直画") + if plate: + _check("图型 %s 双峰纹理落牌(亮族 p90 %.2f/均值 %.2f,深族 p90 %.2f/均值 %.2f 均未过门)" % [ + String(bt), float(mode["cr_p90_bright"]), float(mode["cr_mean_bright"]), + float(mode["cr_p90_dark"]), float(mode["cr_mean_dark"])], + float(mode["cr_mean_dark"]) < float(BoardViewScript.DIGIT_INK_CR_MEAN_DARK)) + else: + var cr_p90 := float(mode["cr_p90_bright"]) if not dark else float(mode["cr_p90_dark"]) + var cr_mean := float(mode["cr_mean_bright"]) if not dark else float(mode["cr_mean_dark"]) + _check("图型 %s 所选%s 对 p90 ≥%.1f(实测 %.2f)" % [String(bt), mode_name, + float(BoardViewScript.DIGIT_INK_CR_P90), cr_p90], + cr_p90 >= float(BoardViewScript.DIGIT_INK_CR_P90)) + var mean_gate := float(BoardViewScript.DIGIT_INK_CR_MEAN_BRIGHT) if not dark else float(BoardViewScript.DIGIT_INK_CR_MEAN_DARK) + _check("图型 %s 所选%s 对均值 ≥%.1f(实测 %.2f)" % [String(bt), mode_name, mean_gate, cr_mean], + cr_mean >= mean_gate) + if expect_plate.has(bt): + _check("图型 %s 呈现模式期望=%s(实测=%s)" % [String(bt), + "色牌" if bool(expect_plate[bt]) else "直画", mode_name], plate == bool(expect_plate[bt]))