merge: M-1 迭代 2「样张冻结与表现定稿」loop——样张 v2 机检 16/16 四冻结+框材组 8 件接线+演出密度终稿+棋盘数字终稿+M-1 出口闸检查单

五批次 a08d89c/f9b0765→2d1388b:①T1 样张 v2(机检闸形 A 全过即冻结——新增 A3 Δ≥55/F6 双明度带/F7 DEPTH_PX 同源/A6 观感密度四断言,art-bible v2.13 落四冻结+UiKit 小人蓝同步 #2E86DE+图例旗色封印黑漏网修)②T2 框材组 8 件(♻ Kenney/OGA 全 CC0 量化改色入包,UiKit 单点工厂+五视图接线,✦ 背景横幅列缺料)③T3 演出四项(死亡碎裂/胜利纹章/失败掸灰/意图三档+结算光带缘线)+fx_probe 新探针④T4 数字形制冻结值落码(三档墨色温/字号 ≥18 公式/色牌 0.60·0.62)⑤T5 M-1 出口闸检查单(design/qa/m1-exit-gate-checklist-2026-09-28.md——真人目检八项+✦ 缺料清单)+06 两已解决条目移除+roadmap 挂起清单对齐。
验证:样张机检 16/16+冒烟 557/0(基线持平)+五探针(ui_input/ui_probe/ui_click/ui_tune/fx 新增)全 PASS+doc 三闸对基线零新增(lint 8=8/term 215=215/死链 0)+熵增闸 PASS。
This commit is contained in:
DKChan committed 2026-09-28 04:58:58 +08:00
commit 9abf5ff448
20 files changed
+748 -76

No files matched your search

+11 -8
View File
@@ -1,6 +1,6 @@
# 美术圣经(Art Bible)— Dungeon Sweeper # 美术圣经(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);历次修订明细见文末版本历史(现行为 v2.9) > 项目: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);历次修订明细见文末版本历史(现行为 v2.13)
> >
> **Art Director Sign-Off (AD-ART-BIBLE)**:历次批均 SKIPPED(v1.8 §6–9 补全亦 SKIPPED)— Lean/Solo mode(非 PHASE-GATE;§6–9 已成文,full 门禁签核时机待用户表态) > **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 头身 > 风格基调(2026-09-10 用户定稿):地牢题材但**非暗黑风**——轻快明亮的幻想冒险风,全年龄;像素风;角色 2~3 头身
@@ -60,6 +60,8 @@
**判定与回退**:第 1 项不过 → 按原则二设计测试回退——加牌、调牌(提牌墨对比/扩牌面积皆可),禁止缩字号换字形逃避;第 2–4 项不过 → 修形不修数。样张通过 = 砖面形态与色板冻结;新增图类型/变异色板须重跑本测试(§4.3)。执行时机:正式砖面资产开工前;《HUD框架》以本节为引用依据(2026-09-07 门禁:样张测试并入 art-bible 执行)。 **判定与回退**:第 1 项不过 → 按原则二设计测试回退——加牌、调牌(提牌墨对比/扩牌面积皆可),禁止缩字号换字形逃避;第 2–4 项不过 → 修形不修数。样张通过 = 砖面形态与色板冻结;新增图类型/变异色板须重跑本测试(§4.3)。执行时机:正式砖面资产开工前;《HUD框架》以本节为引用依据(2026-09-07 门禁:样张测试并入 art-bible 执行)。
> **样张 v2 记录(2026-09-28,四冻结生效)**:机检 16/16 全绿(扩展断言集:A3 图底分离 Δ≥55/F6 墙格顶面立面双明度带 Δ≥18/F7 整板悬崖 DEPTH_PX 与游戏渲染常量同源/A6 观感密度梯度均值 ≥4.70=v1 实测 4.09×1.15)。**四冻结**——①砖面形态:方案甲 21 瓦片+高墙感 D 投影档+整板悬崖(§3.2 高墙瓦片体系条定稿参数);②色板:小人蓝 #2E86DE/接触带微光 #D6ECFF/类型微光五组定稿(§4.1/§4.2);③动画方案:§8.4 帧数档冻结;④数字形制:色牌形制定稿(#F5EAD3 素色直角小牌+Fusion Pixel+字号 max(18, ceil(格边×0.5))+墨三档,§7.4)。产物=`artwork/mockups/sample-batch-v2-2026-09-28.png`,机检报告=`design/research/sample-v2-机检报告-2026-09-28.md`;气质终审=批末用户目检(闸形 A,2026-09-27 拍板)。
--- ---
## 2. 氛围与情绪(Mood & Atmosphere) ## 2. 氛围与情绪(Mood & Atmosphere)
@@ -162,7 +164,7 @@
- **旗(封印印记)**:插旗动作的叙事呈现 = **封印**(玩家可见专名,2026-09-25 用户拍板,registry terminology 登记)——玩家对未开格插旗,即对该格(巢穴)实施封印,旗 = 封印印记;封印色(§4.1,现行封印黑)与格内优先级栈位(§1.2)不变。封印特效(施印瞬间 + 印记常显形态)待表现批,资产归清单 §4 演出类;现行程序化占位 = 封印黑「旗」字形(棋盘惯例:贴图在位=字形退役)。←原一;P2;术语批 2026-09-25;封印紫批 2026-09-26;封印黑批 2026-09-28 - **旗(封印印记)**:插旗动作的叙事呈现 = **封印**(玩家可见专名,2026-09-25 用户拍板,registry terminology 登记)——玩家对未开格插旗,即对该格(巢穴)实施封印,旗 = 封印印记;封印色(§4.1,现行封印黑)与格内优先级栈位(§1.2)不变。封印特效(施印瞬间 + 印记常显形态)待表现批,资产归清单 §4 演出类;现行程序化占位 = 封印黑「旗」字形(棋盘惯例:贴图在位=字形退役)。←原一;P2;术语批 2026-09-25;封印紫批 2026-09-26;封印黑批 2026-09-28
- **可挖域接触带微光(交互反馈层,2026-09-25 用户拍板)**:挖掘期,英雄格周界与可挖墙(8 向相邻未开格)的接触面画呼吸微光——正交邻墙=该侧整条边,斜向邻墙=共享角点+向两侧各延伸半条边;光只画英雄格(空间)一侧、不进墙格(与装饰变体零冲突,数字砖心不涉)。世界观口径:未开格=墙、已开格=挖出的空间,微光=空间的灯光边界。色相=蓝族浅一档(§4.2 蓝条;与主角蓝框的分层归样张批);呼吸=§2.1 挖掘期唯一在动元素,减动效停格。角点为斜墙与正交墙共享、不逐格归因,逐格指认=悬停金框(现役)。旧「墙格整框绿相呼吸」随本条退役(实现切换归表现批,`DIG_EDGE` 绿相退役)。←§2.1;§4.2;P1 - **可挖域接触带微光(交互反馈层,2026-09-25 用户拍板)**:挖掘期,英雄格周界与可挖墙(8 向相邻未开格)的接触面画呼吸微光——正交邻墙=该侧整条边,斜向邻墙=共享角点+向两侧各延伸半条边;光只画英雄格(空间)一侧、不进墙格(与装饰变体零冲突,数字砖心不涉)。世界观口径:未开格=墙、已开格=挖出的空间,微光=空间的灯光边界。色相=蓝族浅一档(§4.2 蓝条;与主角蓝框的分层归样张批);呼吸=§2.1 挖掘期唯一在动元素,减动效停格。角点为斜墙与正交墙共享、不逐格归因,逐格指认=悬停金框(现役)。旧「墙格整框绿相呼吸」随本条退役(实现切换归表现批,`DIG_EDGE` 绿相退役)。←§2.1;§4.2;P1
- **墙体**:圆角砌块堆叠,禁尖刺装饰——尖刺专属危险语义,装饰不得伪装逻辑差异。←原三 - **墙体**:圆角砌块堆叠,禁尖刺装饰——尖刺专属危险语义,装饰不得伪装逻辑差异。←原三
- **高墙瓦片体系(2026-09-26 定稿,样张批追认)**:未挖格=**一整块 32×32 自含凸起岩体**(方案甲核心定义);素材语义名——未开格素材=**墙瓦片**,已开格素材=**地面瓦片**(术语 2026-09-26 用户立)。**边界=墨线纪律**:墙瓦片与相邻格分界一律 1px 深墨线、直线贴格边、零锯齿;墨线职责=分隔,不追求与地面融合(Tiny Dungeon 样图实证调研,见 `design/research/terrain-walls-source-evaluation-2026-09-25.md`)。**光照=单一北受光**:北缘=墨线内侧 1px 微亮棱(就地取色提亮,禁宽亮带);西/东缘=墨线后直进纹理、不加修饰;南缘=墨线+立面窄暗条(约 1/3 格高,横层理叠石、禁竖条纹栅栏感)。**凹角**:角点单像素落线;南立面侧凹角加 1px 立面收头——带身与转角竖线同列收尾,禁悬空、禁突出(v9.1/v9.2 两版返工实证)。现行定稿=`game/assets/art/board/terrain_*`(16 唯一墙瓦片+5 地面瓦片变体;词表与查表逻辑见 `design/research/board-tile-vocab-plan-a-2026-09-26.md`)。←原三;台账 164 - **高墙瓦片体系(2026-09-26 定稿,样张批追认)**:未挖格=**一整块 32×32 自含凸起岩体**(方案甲核心定义);素材语义名——未开格素材=**墙瓦片**,已开格素材=**地面瓦片**(术语 2026-09-26 用户立)。**边界=墨线纪律**:墙瓦片与相邻格分界一律 1px 深墨线、直线贴格边、零锯齿;墨线职责=分隔,不追求与地面融合(Tiny Dungeon 样图实证调研,见 `design/research/terrain-walls-source-evaluation-2026-09-25.md`)。**光照=单一北受光**:北缘=墨线内侧 1px 微亮棱(就地取色提亮,禁宽亮带);西/东缘=墨线后直进纹理、不加修饰;南缘=墨线+立面窄暗条(约 1/3 格高,横层理叠石、禁竖条纹栅栏感)。**凹角**:角点单像素落线;南立面侧凹角加 1px 立面收头——带身与转角竖线同列收尾,禁悬空、禁突出(v9.1/v9.2 两版返工实证)。现行定稿=`game/assets/art/board/terrain_*`(16 唯一墙瓦片+5 地面瓦片变体;词表与查表逻辑见 `design/research/board-tile-vocab-plan-a-2026-09-26.md`)。**高墙感 D 投影档+整板悬崖(样张 v2 定稿 2026-09-28,用户 2026-09-27 四变体挑档)**:南临空墙格=立面口沿高光 2px `Color8(196,172,132)`+双段暗带(0.16/0.25 格边,黑罩 α0.60/0.82);北邻墙地面格=三段渐隐投影(顶带 0.22 格边,α0.28 起);整板悬崖=板底/右侧 `DEPTH_PX 8px` 暗面+列分隔竖缝+岩层横纹+口沿高光+接触 AO+底线收边+悬浮影(游戏 `board_world.gd` 同款结构件,样张脚本与游戏渲染常量同源——机检 F7 承载)。←原三;台账 164;样张 v2 批 2026-09-28
- **变异格形状通道**(双通道之"形",与色无关):底砖圆角外轮廓不变,砖面内缘绘一圈定宽专属几何纹带(纹带形全作不与他类共用),砖心留白予数字与旗;验收标准=**灰度截图下仍可分类**。种类清单 = 地形变异六类型已定稿(地形变异 GDD;荆棘丛/藤蔓缠绕/岩浆/烬覆/落石/矿尘):**纹带形按轴只分两种**——伤害轴(荆棘丛/岩浆/落石)= 刺/缺排斥形,信息轴(藤蔓缠绕/烬覆/矿尘)= 遮蔽/缠绕形;同轴跨图类型只做色板变体(§4.3 `variant_tint` 槽),不做新纹形。样张批增查:岩浆纹带橙红须与危险红互辨(变异格不是雷,色相邻近混淆风险);信息轴遮蔽呈现不得侵入数字色牌占位区(§1.4 验收 1 延伸)。←原一(纹理位次第 4,让位于数字/小人/旗);原三 - **变异格形状通道**(双通道之"形",与色无关):底砖圆角外轮廓不变,砖面内缘绘一圈定宽专属几何纹带(纹带形全作不与他类共用),砖心留白予数字与旗;验收标准=**灰度截图下仍可分类**。种类清单 = 地形变异六类型已定稿(地形变异 GDD;荆棘丛/藤蔓缠绕/岩浆/烬覆/落石/矿尘):**纹带形按轴只分两种**——伤害轴(荆棘丛/岩浆/落石)= 刺/缺排斥形,信息轴(藤蔓缠绕/烬覆/矿尘)= 遮蔽/缠绕形;同轴跨图类型只做色板变体(§4.3 `variant_tint` 槽),不做新纹形。样张批增查:岩浆纹带橙红须与危险红互辨(变异格不是雷,色相邻近混淆风险);信息轴遮蔽呈现不得侵入数字色牌占位区(§1.4 验收 1 延伸)。←原一(纹理位次第 4,让位于数字/小人/旗);原三
### 3.3 UI 形状语法(方向指导,细则迁 §7.1) ### 3.3 UI 形状语法(方向指导,细则迁 §7.1)
@@ -189,7 +191,7 @@
- **封印黑 `#000000`**|封印(插旗)宣告色,无色相:玩家的宣告"此格封印"(色史:旗朱 `#C63D22` →封印紫 `#7C3AA8` 2026-09-26→**封印黑 2026-09-28 用户拍板**——紫色与微光蓝族〔魔法微光 256° 等〕色相冲突,黑色无色相天然避开 300°–345° 禁入带与全部微光色带;未开格墙顶明度 ≈113 对纯黑对比 ≥4.4:1,旗杆米白保可读)。旗朱随封印紫批退役,红族自此只剩危险红。←原一;P2;封印紫批 2026-09-26;封印黑批 2026-09-28 - **封印黑 `#000000`**|封印(插旗)宣告色,无色相:玩家的宣告"此格封印"(色史:旗朱 `#C63D22` →封印紫 `#7C3AA8` 2026-09-26→**封印黑 2026-09-28 用户拍板**——紫色与微光蓝族〔魔法微光 256° 等〕色相冲突,黑色无色相天然避开 300°–345° 禁入带与全部微光色带;未开格墙顶明度 ≈113 对纯黑对比 ≥4.4:1,旗杆米白保可读)。旗朱随封印紫批退役,红族自此只剩危险红。←原一;P2;封印紫批 2026-09-26;封印黑批 2026-09-28
- **危险红 `#A8321F`**|危险色:雷=被惊动的巢穴,兑现档红,更深更沉(2026-09-13 更名,旧称"机关红"随隐喻换轴退役;色值不变)。←原三;P2 - **危险红 `#A8321F`**|危险色:雷=被惊动的巢穴,兑现档红,更深更沉(2026-09-13 更名,旧称"机关红"随隐喻换轴退役;色值不变)。←原三;P2
- **奖励金 `#F0A828`**|已兑现的收益:金币/宝箱/蜡封,全作唯一亮金。←P2;§2.3/2.4 - **奖励金 `#F0A828`**|已兑现的收益:金币/宝箱/蜡封,全作唯一亮金。←P2;§2.3/2.4
- **小人蓝 `#2E86DE`**|小人主色:棋盘唯一"己方",与光标准线同族(候选值,样张批 §1.4 冻结终稿)。←P1;§2.1 - **小人蓝 `#2E86DE`**|小人主色:棋盘唯一"己方",与光标准线同族(样张 v2 定稿 2026-09-28;游戏 UiKit.HERO_BLUE 已同步 #2E6E8E→#2E86DE)。←P1;§2.1
- **数字色牌 `#F5EAD3`**|数字底具:素色暖纸白直角小牌,数字一律垫于其上;素面无纹理、不随图类型/装饰变体变,数字可读不依赖格底即由本牌承载。←原二 - **数字色牌 `#F5EAD3`**|数字底具:素色暖纸白直角小牌,数字一律垫于其上;素面无纹理、不随图类型/装饰变体变,数字可读不依赖格底即由本牌承载。←原二
**对比硬指标(可测)**:数字可读性由色牌承载——**数字色牌素色恒定 L≥0.70、一切数字墨 L≤0.027**(WCAG 相对亮度)⇒ 任意牌墨组合 **≥4.5:1 恒成立**(WCAG AA)。实测样例:#33261B / #F5EAD3 = **12.3:1**;#4A1E14 / #F5EAD3 = **11.8:1**;#132E36 / #F5EAD3 = **12.0:1**。格底槽(含全部图类型)相对亮度 **L≥0.30**(明亮为皮)、HSV 饱和 **≤0.55**、避 Boss 禁入色相带——格底不再承担数字对比义务;物件识别不依赖格底(§5.1 轮廓通道),旗面、金色不作识别依赖。←原二;地形变异 AC-9 **对比硬指标(可测)**:数字可读性由色牌承载——**数字色牌素色恒定 L≥0.70、一切数字墨 L≤0.027**(WCAG 相对亮度)⇒ 任意牌墨组合 **≥4.5:1 恒成立**(WCAG AA)。实测样例:#33261B / #F5EAD3 = **12.3:1**;#4A1E14 / #F5EAD3 = **11.8:1**;#132E36 / #F5EAD3 = **12.0:1**。格底槽(含全部图类型)相对亮度 **L≥0.30**(明亮为皮)、HSV 饱和 **≤0.55**、避 Boss 禁入色相带——格底不再承担数字对比义务;物件识别不依赖格底(§5.1 轮廓通道),旗面、金色不作识别依赖。←原二;地形变异 AC-9
@@ -198,10 +200,10 @@
- **红只说危险**:危险红=兑现档(旗朱预告档随封印紫批退役,红族单档)。禁:红不得用于奖励、装饰与常规 UI(微光群伤组除外——其为类型先验色带域,见「类型微光色带」条,色相刻意错开语义红)。←原三;P2;§2.1;封印紫批 2026-09-26 - **红只说危险**:危险红=兑现档(旗朱预告档随封印紫批退役,红族单档)。禁:红不得用于奖励、装饰与常规 UI(微光群伤组除外——其为类型先验色带域,见「类型微光色带」条,色相刻意错开语义红)。←原三;P2;§2.1;封印紫批 2026-09-26
- **金只说收益与兑现**(奖励、结算、胜利)。禁:金不得作危险障碍色、不上格底、不作日常 UI 强调(防通胀;微光通用增益组的芥末黄 = 类型先验色带域,非收益语义金)。←P2;P3;§2.2–2.4 - **金只说收益与兑现**(奖励、结算、胜利)。禁:金不得作危险障碍色、不上格底、不作日常 UI 强调(防通胀;微光通用增益组的芥末黄 = 类型先验色带域,非收益语义金)。←P2;P3;§2.2–2.4
- **蓝只说己方与冷静信息**(小人、光标、工具信息——含可挖域接触带呼吸微光,蓝族浅一档,2026-09-25 拍板;旧可挖域绿相随接触带方案退役)。禁:蓝不得表示敌方危险;暮蓝 `#5A6C8C` 为失败独占(§2.5),只作全屏氛围,不作物件色。←P1;§2.5 - **蓝只说己方与冷静信息**(小人、光标、工具信息——含可挖域接触带呼吸微光,蓝族浅一档 `#D6ECFF`〔样张 v2 定稿 2026-09-28,游戏 UiKit.CONTACT_GLOW 同值〕,2026-09-25 拍板;旧可挖域绿相随接触带方案退役)。禁:蓝不得表示敌方危险;暮蓝 `#5A6C8C` 为失败独占(§2.5),只作全屏氛围,不作物件色。←P1;§2.5
- **紫赤只说 Boss 与精英** `#8F2F6A`(色相 323°):郑重来自色相独占,不来自压暗。禁:**HSV 色相 300°–345° 为全场禁入带**,其余资产(含 UI 与粒子、含类型微光)及图类型色板不得落入。←§2.6;原三 - **紫赤只说 Boss 与精英** `#8F2F6A`(色相 323°):郑重来自色相独占,不来自压暗。禁:**HSV 色相 300°–345° 为全场禁入带**,其余资产(含 UI 与粒子、含类型微光)及图类型色板不得落入。←§2.6;原三
- **绿只说自然生机**(环境植被;恢复类状态若有则用绿——待增益状态定夺)。**例外:类型微光治疗组**(类型先验色带,见下条)。禁:绿不得承载逻辑状态(微光色带除外),不得与红构成唯一区分对。←原三;T1 裁决 2026-09-22 - **绿只说自然生机**(环境植被;恢复类状态若有则用绿——待增益状态定夺)。**例外:类型微光治疗组**(类型先验色带,见下条)。禁:绿不得承载逻辑状态(微光色带除外),不得与红构成唯一区分对。←原三;T1 裁决 2026-09-22
- **类型微光色带 = 格面类型先验专用逻辑色带**(2026-09-22 用户裁决,未开格类型预读的色彩通道):未开格常显类型微光属逻辑信息色彩,五组候选色相——**通用增益组**(attack/defense/weaken/action/gp 五族)芥末黄 `#D6C13A`(≈52°)、**治疗组**(heal)黄绿 `#5CB83A`(≈104°)、**穿刺组**(pierce)青 `#2E9E9E`(≈180°)、**魔法组**(magic)紫罗兰 `#7B5BD6`(≈256°)、**群伤组**(blast)绯红 `#FF2D55`(≈349°,刻意避开危险红色相段与 300°–345° 禁入带)——均候选值,样张批(§1.4)冻结终值并抽测组间互辨。分域纪律:微光色相只承载类型先验,不迁移复用为物件/阵营/UI 语义色;微光 = **色相+明度双码**(2026-09-28 角嵌退役批起明度梯度承载类型——§2 微光明度例外:梯度不得压破格底明度档/三态分类);色弱玩家的类型主通道 = 微光双码。←T1 裁决 2026-09-22;角嵌退役批 2026-09-28;§4.6.3 - **类型微光色带 = 格面类型先验专用逻辑色带**(2026-09-22 用户裁决,未开格类型预读的色彩通道):未开格常显类型微光属逻辑信息色彩,五组色相——**通用增益组**(attack/defense/weaken/action/gp 五族)芥末黄 `#D6C13A`(≈52°)、**治疗组**(heal)黄绿 `#5CB83A`(≈104°)、**穿刺组**(pierce)青 `#2E9E9E`(≈180°)、**魔法组**(magic)紫罗兰 `#7B5BD6`(≈256°)、**群伤组**(blast)绯红 `#FF2D55`(≈349°,刻意避开危险红色相段与 300°–345° 禁入带)——**样张 v2 定稿(2026-09-28)**,组间互辨抽检随机检报告;v2 呈现参数=内缘带状泛光 α170/带宽 cell÷8(亮度不压格底明度档,§2 微光例外)。分域纪律:微光色相只承载类型先验,不迁移复用为物件/阵营/UI 语义色;微光 = **色相+明度双码**(2026-09-28 角嵌退役批起明度梯度承载类型——§2 微光明度例外:梯度不得压破格底明度档/三态分类);色弱玩家的类型主通道 = 微光双码。←T1 裁决 2026-09-22;角嵌退役批 2026-09-28;§4.6.3
- **金/红兼作极性色相槽(§4.6)**:极性(白话:同一次兑付的正负方向——正 = 收益侧,负 = 代价侧;行为×格型→符号矩阵见极性提案 R2)的视觉取色只在这两族间——有利兑付 = 金族,有害兑付 = 红族,不新增语义色。←§4.6;原二 - **金/红兼作极性色相槽(§4.6)**:极性(白话:同一次兑付的正负方向——正 = 收益侧,负 = 代价侧;行为×格型→符号矩阵见极性提案 R2)的视觉取色只在这两族间——有利兑付 = 金族,有害兑付 = 红族,不新增语义色。←§4.6;原二
### 4.3 图类型色板换皮规则(board_type) ### 4.3 图类型色板换皮规则(board_type)
@@ -307,7 +309,7 @@
| action | 行动族 | 行动点/疾步 | | action | 行动族 | 行动点/疾步 |
| gp | 金币族 | 钱币/财货 | | gp | 金币族 | 钱币/财货 |
- **呈现约束(2026-09-28 角嵌退役批更新,原「双通道」退役)**:全部未开格呈现**类型微光单通道**(五组色带,§4.2「类型微光色带」,色相+明度双码;候选值样张批冻结);不呈现极性、不呈现雷/安——推雷仍是扫雷本征推理;类型分布即先验(提案 R3 拍板),**微光误读率 = 提案 §4 指标 3** 保留(角嵌误读率指标随之转微光),样张批微光组间互辨抽测含明度档互辨。 - **呈现约束(2026-09-28 角嵌退役批更新,原「双通道」退役)**:全部未开格呈现**类型微光单通道**(五组色带,§4.2「类型微光色带」,色相+明度双码;样张 v2 定稿 2026-09-28);不呈现极性、不呈现雷/安——推雷仍是扫雷本征推理;类型分布即先验(提案 R3 拍板),**微光误读率 = 提案 §4 指标 3** 保留(角嵌误读率指标随之转微光),样张批微光组间互辨抽测含明度档互辨。
- **状态声明**:键集九值封闭(attack/defense/heal/blast/weaken/pierce/magic/action/gp,与 RewardTable 现行 cell_type 词表一致),扩键走本节修订。 - **状态声明**:键集九值封闭(attack/defense/heal/blast/weaken/pierce/magic/action/gp,与 RewardTable 现行 cell_type 词表一致),扩键走本节修订。
- 与 §4.6.1 的关系:同语义域的键(attack↔attack_up 等)可共族设计,键分立、纹样须可区分(格面小记号级对槽内图标级)。 - 与 §4.6.1 的关系:同语义域的键(attack↔attack_up 等)可共族设计,键分立、纹样须可区分(格面小记号级对槽内图标级)。
@@ -424,9 +426,9 @@
- **交付格式**:PNG 透明底;动画 = 序列帧或引擎精灵表(SpriteFrames),帧尺寸恒定、横向等宽排列(候选)。 - **交付格式**:PNG 透明底;动画 = 序列帧或引擎精灵表(SpriteFrames),帧尺寸恒定、横向等宽排列(候选)。
- **命名规范(候选草案)**:`<域>_<对象键>_<状态|变体>[_<序号>].png`,全小写 snake_case;**对象键 = 数据表主键**(effect_id/reward_id/cell_type/enemy_id——图标键契约 §4.6 的文件侧延伸:资产文件名键 = 图标键,全局恒定禁运行时换图);图类型变体后缀 = board_type 键(`_dungeon`/`_underdark`/`_deep_underdark`);候选评审件与终稿分目录(`candidates/` 承载评审件,终稿目录归表现批定)。 - **命名规范(候选草案)**:`<域>_<对象键>_<状态|变体>[_<序号>].png`,全小写 snake_case;**对象键 = 数据表主键**(effect_id/reward_id/cell_type/enemy_id——图标键契约 §4.6 的文件侧延伸:资产文件名键 = 图标键,全局恒定禁运行时换图);图类型变体后缀 = board_type 键(`_dungeon`/`_underdark`/`_deep_underdark`);候选评审件与终稿分目录(`candidates/` 承载评审件,终稿目录归表现批定)。
### 8.4 帧数规格(候选档,战斗一屏样张批冻结) ### 8.4 帧数规格(样张 v2 冻结 2026-09-28)
- 待机循环 4 帧(候选);攻击/技能准备 6 帧单次(候选);受击 2 帧(候选);死亡 = 灰化+碎裂退场(§4.6.4 语义硬规则),碎裂序列 4–6 帧(候选);帧时长与结算节拍对齐(预告时序 = 下一拍,§4.6.4)。 - **帧数规格(样张 v2 冻结,2026-09-28)**:待机循环 4 帧;攻击/技能准备 6 帧单次;受击 2 帧;死亡 = 灰化+碎裂退场(§4.6.4 语义硬规则)——切片实装=程序化 tween(灰化罩+碎块弹飞 5 块 0.45s,挂 `unit_died`),序列帧归 ✦ 缺料清单;帧时长与结算节拍对齐(预告时序 = 下一拍,§4.6.4)。
- **方向集(2026-09-24 用户拍板)**:棋盘小人=**4 向**(下/上/侧,侧向左右镜像共用一份画稿);斜向移动/挖掘按**主导轴吸附最近正交向**呈现,不出 45° 斜向帧(斜向信息由位移轨迹与目标格预告承载,ITB 实态同款)。镜像与「旗面恒画右」纪律(character-design.md 冒险背包条)的相容归属:棋盘小人默认镜像豁免、旗右纪律限立绘侧——**代拟口径,样张批追认**。 - **方向集(2026-09-24 用户拍板)**:棋盘小人=**4 向**(下/上/侧,侧向左右镜像共用一份画稿);斜向移动/挖掘按**主导轴吸附最近正交向**呈现,不出 45° 斜向帧(斜向信息由位移轨迹与目标格预告承载,ITB 实态同款)。镜像与「旗面恒画右」纪律(character-design.md 冒险背包条)的相容归属:棋盘小人默认镜像豁免、旗右纪律限立绘侧——**代拟口径,样张批追认**。
### 8.5 结构件完整性与参考核验(2026-09-24 用户立规) ### 8.5 结构件完整性与参考核验(2026-09-24 用户立规)
@@ -471,6 +473,7 @@
- **v2.10(2026-09-26)封印紫批**(用户拍板:插旗色 旗朱 #C63D22 → **封印紫 #7C3AA8**,色相 ≈267°):§4.1 旗朱条改写为封印紫条(候选值样张批冻结;禁入带合规+与魔法微光/妨害紫间距登记)、§4.2 红只说危险改单档(旗朱预告档退役)、§3.2 旗条/§1.4 验收 4/§4.3 不变槽/§3.2 变异互辨/§5.4 速览随批。**语义面**——封印自术语批(2026-09-25)获专属色相,红族收敛为危险红单档;攻击预告/弹道/拒绝等非封印红统一归危险红族(RED_MINE)。**连带**——character-design §5.4 背旗、battle.md 三处、关卡节点地图 Boss 描边、清单封印特效行随批;comfyui-prompts 生图卡颜色词随样张 v2 重生成。 - **v2.10(2026-09-26)封印紫批**(用户拍板:插旗色 旗朱 #C63D22 → **封印紫 #7C3AA8**,色相 ≈267°):§4.1 旗朱条改写为封印紫条(候选值样张批冻结;禁入带合规+与魔法微光/妨害紫间距登记)、§4.2 红只说危险改单档(旗朱预告档退役)、§3.2 旗条/§1.4 验收 4/§4.3 不变槽/§3.2 变异互辨/§5.4 速览随批。**语义面**——封印自术语批(2026-09-25)获专属色相,红族收敛为危险红单档;攻击预告/弹道/拒绝等非封印红统一归危险红族(RED_MINE)。**连带**——character-design §5.4 背旗、battle.md 三处、关卡节点地图 Boss 描边、清单封印特效行随批;comfyui-prompts 生图卡颜色词随样张 v2 重生成。
- **v2.11(2026-09-28)封印黑批**(用户拍板:封印色 封印紫 #7C3AA8 → **封印黑 #000000**——紫色与微光蓝族色相冲突,黑色无色相天然合规):§4.1 封印紫条改写封印黑条、§3.2 旗条/§1.4 不变槽/§4.3/§5.4 速览随批;代码 UiKit.SEAL_PURPLE→SEAL_BLACK 改名迁移+board_view/board_fx_view 换色+样张脚本同步;胜利彩带装饰紫位改亮金(紫赤系 Boss 独占不入装饰);远程生图单主角旗 purple→black 十六进制锁色重锁。历史行(v2.10 等)按 ADR 纪律留痕不改 - **v2.11(2026-09-28)封印黑批**(用户拍板:封印色 封印紫 #7C3AA8 → **封印黑 #000000**——紫色与微光蓝族色相冲突,黑色无色相天然合规):§4.1 封印紫条改写封印黑条、§3.2 旗条/§1.4 不变槽/§4.3/§5.4 速览随批;代码 UiKit.SEAL_PURPLE→SEAL_BLACK 改名迁移+board_view/board_fx_view 换色+样张脚本同步;胜利彩带装饰紫位改亮金(紫赤系 Boss 独占不入装饰);远程生图单主角旗 purple→black 十六进制锁色重锁。历史行(v2.10 等)按 ADR 纪律留痕不改
- **v2.12(2026-09-28)角嵌退役批**(用户裁「微光的展示只有微光,不要加额外的箭头或者加号在格子里面」——样张 v1 否决原因之一):未开格类型角嵌纹样**彻底退役**——类型预读改类型微光单通道(色相+明度双码),§2 微光明度恒定开例外(明度梯度承载类型,梯度不压格底明度档/三态分类),色弱类型主通道=微光双码;§1.2 优先级栈/§1.4 样张清单与验收 2/§3.2 角嵌条(标退役)/§3.4/§4.2 色带条/§4.5⑨/§4.6.3 呈现约束同步;已开格物件纹样(角嵌于砖缘画法动词)不在退役面;机检新增「微光格零墨色附加像素」断言;角嵌误读率指标转微光误读率 | - **v2.12(2026-09-28)角嵌退役批**(用户裁「微光的展示只有微光,不要加额外的箭头或者加号在格子里面」——样张 v1 否决原因之一):未开格类型角嵌纹样**彻底退役**——类型预读改类型微光单通道(色相+明度双码),§2 微光明度恒定开例外(明度梯度承载类型,梯度不压格底明度档/三态分类),色弱类型主通道=微光双码;§1.2 优先级栈/§1.4 样张清单与验收 2/§3.2 角嵌条(标退役)/§3.4/§4.2 色带条/§4.5⑨/§4.6.3 呈现约束同步;已开格物件纹样(角嵌于砖缘画法动词)不在退役面;机检新增「微光格零墨色附加像素」断言;角嵌误读率指标转微光误读率 |
- **v2.13(2026-09-28)样张 v2 冻结批(M-1 迭代 2 T1,机检闸形 A——全过即冻结)**:§1.4 增样张 v2 记录(机检 16/16:现行 13 条+A3 上调 Δ≥55/F6 顶面立面双明度带 Δ≥18/F7 悬崖 DEPTH_PX 同源/A6 观感密度梯度 ≥4.70=v1 实测 4.09×1.15——v1 四否决原因断言化收官);**四冻结落文**——①砖面形态:§3.2 高墙瓦片体系条增「D 投影档+整板悬崖」定稿参数(口沿高光 2px Color8(196,172,132)/立面双段暗带 0.16+0.25 格边 α0.60·0.82/地面投影 0.22 格边三段渐隐 α0.28/悬崖 DEPTH_PX 8px board_world 同款);②色板:§4.1 小人蓝候选→定稿 #2E86DE(UiKit 同步)+§4.2 接触带微光色值 #D6ECFF 定稿+类型微光五组候选→定稿(v2 呈现 α170/带宽 cell÷8);③动画方案:§8.4 帧数规格候选档→冻结(待机 4/攻击 6/受击 2/死亡=灰化+程序化碎块弹飞 5 块 0.45s,序列帧归缺料清单);④数字形制:色牌形制定稿(#F5EAD3 直角小牌+Fusion Pixel+字号 max(18,ceil(格边×0.5))+墨三档)。产物命名带 v2(v1 被覆盖欠账清偿),报告迁 `design/research/`;连带=图例旗色封印黑漏网修正(#C63D22→#000000) |
- **v2.8(2026-09-25)可挖域接触带微光定稿批**(用户两拍板:①画法=接触带·空间侧——光画英雄格周界,正交邻墙=整边、斜向邻墙=角点+两半边;②色相=蓝族浅一档,顺路清偿旧 DIG_EDGE 绿相违 §4.2 绿禁则):§3.2 新增「可挖域接触带微光」条(画法/光只住空间一侧/角点不逐格归因、指认=悬停金框/呼吸与减动效守 §2.1/实现切换归表现批);§2.1 承载元素、§4.2 蓝条随批;battle.md 注记随批。背景=用户世界观口径「未开格=墙、已开格=挖出的空间」,微光=空间的灯光边界。 - **v2.8(2026-09-25)可挖域接触带微光定稿批**(用户两拍板:①画法=接触带·空间侧——光画英雄格周界,正交邻墙=整边、斜向邻墙=角点+两半边;②色相=蓝族浅一档,顺路清偿旧 DIG_EDGE 绿相违 §4.2 绿禁则):§3.2 新增「可挖域接触带微光」条(画法/光只住空间一侧/角点不逐格归因、指认=悬停金框/呼吸与减动效守 §2.1/实现切换归表现批);§2.1 承载元素、§4.2 蓝条随批;battle.md 注记随批。背景=用户世界观口径「未开格=墙、已开格=挖出的空间」,微光=空间的灯光边界。
- **v2.7(2026-09-25)封印术语批**(用户拍板:Node2D 实现后「插旗」动作等价为**封印**——玩家对未开格插旗即对该格巢穴实施封印,旗 = 封印印记;机制键与规则全不变):§3.2 新增「旗(封印印记)」条——呈现语义/旗朱与栈位不变/封印特效(施印瞬间+印记常显形态)待表现批/现行占位 = 旗朱「旗」字形;§2.5 失败承载元素措辞随批(插过的旗 = 封印印记)。**登记面**——registry terminology 新增「封印」词条、00-游戏概念 术语层级专名表随收;连带同步:battle.md 呈现注记、art-assets-checklist §4 演出类新增封印特效两件(施印特效+印记常显)、art-status-checklist P3 行、character-design.md §5.4 插旗条注记(主角即插旗者)。**关联面**——可挖域接触带微光方案(用户同日提出:微光画英雄格周界接触面,正交整边/斜向角点+两半边)评估可行,实现归表现批,未入本批。 - **v2.7(2026-09-25)封印术语批**(用户拍板:Node2D 实现后「插旗」动作等价为**封印**——玩家对未开格插旗即对该格巢穴实施封印,旗 = 封印印记;机制键与规则全不变):§3.2 新增「旗(封印印记)」条——呈现语义/旗朱与栈位不变/封印特效(施印瞬间+印记常显形态)待表现批/现行占位 = 旗朱「旗」字形;§2.5 失败承载元素措辞随批(插过的旗 = 封印印记)。**登记面**——registry terminology 新增「封印」词条、00-游戏概念 术语层级专名表随收;连带同步:battle.md 呈现注记、art-assets-checklist §4 演出类新增封印特效两件(施印特效+印记常显)、art-status-checklist P3 行、character-design.md §5.4 插旗条注记(主角即插旗者)。**关联面**——可挖域接触带微光方案(用户同日提出:微光画英雄格周界接触面,正交整边/斜向角点+两半边)评估可行,实现归表现批,未入本批。
- **v2.6(2026-09-25)世界观对齐扫描批(甲档回填 + 完美排雷钩子收口 + 引用锚内联;用户三拍板)**:①**§4.6.4 敌种甲档回填数据实态**——bugbear(ward→light)/ ogre_chief(heavy→light)/ drider(heavy→ward),并注记甲档事实源 = `enemy.tres` armor_type(2026-09-18 甲重配批后 L1 全轻甲、L2 除 drider 外全轻甲、L3 duergar/duergar_king 重甲);连带修订 character-design.md §5.5(熊地精轻甲化/食人魔弃金属甲板改裸身皮条/卓尔改魔法甲符纹)与 comfyui-prompts.md v2.8(熊地精/食人魔/卓尔三卡提示词随改)——食人魔视觉重设计经用户拍板「按最新设定,裸身+魔法甲符纹」。②**§2.2 完美排雷钩子收口**——「若…落实…待 GDD 定夺」改现行事实(零误标且至少标中一面旗→追加第二份清图兑付+结算高光行,地图战斗资源 GDD 已定稿)。③**引用锚内联**——§3/§4 尾注 P1–P4 支柱语义内联(概念期 brainstorm 编号,现行 GDD 树不单列),§1.4 两处 game-concept 编号引用改语义内联。扫描背景=用户发起的圣经×关卡/世界观对齐审计;变异六类型/格面九类/状态九键/奖励 19 键/敌种八键/三柜台钥石/三关色板方向经核对全部一致,零其余不符。 - **v2.6(2026-09-25)世界观对齐扫描批(甲档回填 + 完美排雷钩子收口 + 引用锚内联;用户三拍板)**:①**§4.6.4 敌种甲档回填数据实态**——bugbear(ward→light)/ ogre_chief(heavy→light)/ drider(heavy→ward),并注记甲档事实源 = `enemy.tres` armor_type(2026-09-18 甲重配批后 L1 全轻甲、L2 除 drider 外全轻甲、L3 duergar/duergar_king 重甲);连带修订 character-design.md §5.5(熊地精轻甲化/食人魔弃金属甲板改裸身皮条/卓尔改魔法甲符纹)与 comfyui-prompts.md v2.8(熊地精/食人魔/卓尔三卡提示词随改)——食人魔视觉重设计经用户拍板「按最新设定,裸身+魔法甲符纹」。②**§2.2 完美排雷钩子收口**——「若…落实…待 GDD 定夺」改现行事实(零误标且至少标中一面旗→追加第二份清图兑付+结算高光行,地图战斗资源 GDD 已定稿)。③**引用锚内联**——§3/§4 尾注 P1–P4 支柱语义内联(概念期 brainstorm 编号,现行 GDD 树不单列),§1.4 两处 game-concept 编号引用改语义内联。扫描背景=用户发起的圣经×关卡/世界观对齐审计;变异六类型/格面九类/状态九键/奖励 19 键/敌种八键/三柜台钥石/三关色板方向经核对全部一致,零其余不符。
-21
View File
@@ -423,24 +423,3 @@ NodeOverlay 现行实态为模态覆盖层:恢复点全量服务(回复读
### 设置与本地化 ### 设置与本地化
#### 战斗画面表现收口(循环 2 输入清单,2026-09-26 登记)
**现状**:2026-09-26 用户令「需要做的都记录进待办」后盘点登记;能修的已修完(数字色牌/墙轮廓/血量数字/小人脚浮/字形错位/入场演出,台账 176-181),剩余七项——
- **界面环境与框材组 8 件**:规格与渠道已登记 art-assets-checklist §4.1;♻ 件待用户审阅 ui-reuse 候选包挑批后接线,✦ 背景横幅待样张 v2 冻结后生图
- **战斗一屏样张 v2 重出**:v1 已否,重 roll 预算剩 2 轮;**验收闸形已拍(2026-09-27)=A 机检冻结零参与**——用户补否决原因一句话后转成机检断言集(原 12 条+气质代理断言),v2 机检全过即冻结落码、批末用户目检否决才返工;过=v2 冻结为换皮输入。**待输入 = v1 否决原因一句话(用户已认领待补)**
- **高墙感挑档落码**:**已拍板(2026-09-27)=D 投影档**(研究件四变体,用户三选一裁决),迭代 1 实装改常量;研究件现路径 artwork/mockups/(目录收敛随迁)
- **CJK 字体随包**:**已拍板(2026-09-27)=Fusion Pixel OFL**(用户二选一裁决;CJK 覆盖+授权干净定案),迭代 1 实装入包
- **演出密度**:死亡碎裂序列帧/胜利碎砖纹章/失败坐砖掸灰/意图与结算动画终稿——需样张 v2 冻结动画方案
- **本关强化列表数据接线**:本清单既有 C 区预留区条目承载,不重登
- **draw calls 270→<200**:归 Alpha 验收批,真机测量后优化
敌人立绘素材=用户外部生成中(八敌种槽位已接线,素材入库即自动显示),不入本条。棋盘数字呈现优化(色牌临时档)另见本清单同日条目。
**归属**:循环 2 计划书(样张 v2 冻结后另立)
#### 棋盘数字呈现优化(色牌=临时档,2026-09-26 登记)
**现状**:2026-09-26 用户实机反馈——现行数字呈现(米白色牌+墨字,呈现修复批落码)**太丑,只作临时档**,「现在够用」;终稿数字呈现方案待设计(候选方向:色牌形制/字号字体/去牌化直写+描边/与瓦片一体的雕刻数字等),归样张 v2 冻结视觉语言后的表现批一并定。**备忘性质**:防止临时档被当成终稿遗忘。
**归属**:战斗画面表现收口条(循环 2);判据续用 §1.4 验收 1(可读性硬指标不因形制放松)
@@ -0,0 +1,59 @@
# M-1 出口闸检查单(2026-09-28,迭代 2 收口批交付)
> 用途:M-1「切片表现收口」出口闸 = **真人目检清单全绿**(机器闸已全部自动过);本单即人闸载体,逐项可勾。
> 全绿 = M-1 关闭 → **M-2 迭代 3「读数重测与调参」解锁**(其门=M-1 出口闸)。
> 目检方式:实机跑 `godot --path game`(或开 Godot 编辑器 F5),按清单逐项看;任一项不过 = 回该批返工(样张类至多一轮,收集否决维度补断言后重出)。
## 一、机器闸读数汇总(已自动过,留痕)
### 迭代 1(m1-iter1-20260928 worktree,2026-09-28 台账 216/217)
| 卡 | 内容 | 读数 |
|---|---|---|
| T1 单元测试与契约层 | 七套件实跑入口 tests/run_all.gd | 全绿 |
| T2 高墙感 D 投影档落码 | 立面双段暗带+地面投影(用户挑档 D) | 冒烟 557/0+双探针 PASS |
| T3 CJK 字体随包 | Fusion Pixel OFL zh_hans+第八套件 ui_font_asset_test | 冒烟 557/0+8 套件全绿 |
### 迭代 2(本 loop,f9b0765→2d1388b)
| 卡 | 内容 | 读数 |
|---|---|---|
| T1 样张 v2 重出+机检冻结 | 机检 **16/16 全绿**(13 现行+A3 Δ≥55+F6 双明度带+F7 悬崖同源+A6 观感密度)→ 四冻结落文 art-bible v2.13 | 报告 `design/research/sample-v2-机检报告-2026-09-28.md` |
| T2 框材组 8 件换皮 | ♻ 九件量化改色入包+UiKit 单点工厂+五视图接线 | 冒烟 557/0+四探针 PASS |
| T3 演出密度终稿 | 死亡碎裂/胜利纹章/失败掸灰/意图三档/结算光带+**fx_probe 新探针** | 冒烟 557/0+五探针 PASS |
| T4 棋盘数字终稿 | 三档墨色温/字号 ≥18 公式/色牌 0.60·0.62 冻结值 | 冒烟 557/0+五探针(ui_tune_probe 回归含) |
- 冒烟检查数 557 = 开工基线(零删减);合并后主区复跑留痕见台账 219。
- 迭代 1 未落籍注记:m1-iter1 worktree 尚有 T4 关卡画面实装在飞(main.gd/route_overlay/双探针四文件),本 loop 弱耦合避让未触碰;其落籍不阻塞本人闸(目检第 8 项注两级状态)。
## 二、真人目检清单(逐项可勾,勾完=人闸过)
- [ ] **1. 棋盘立体感**(样张 v2 否决③复核):战斗中未开墙格有南立面暗带+地面投影,整板底部/右侧有悬崖暗面与悬浮影——「立得起来」
- [ ] **2. 观感密度**(否决①复核):棋盘整体不觉得平/糊——裂纹碎石变体、类型微光、物件纹样层次可感
- [ ] **3. 数字可读**:1-8 数字色牌上清晰(三档墨色温:小数字偏冷、大数字偏暖),缩小窗口仍可读
- [ ] **4. 换皮框架**:C 区面板/B 区舞台底/图例弹窗=石板质感九宫格;全站按钮=暖砖两态(按下下沉);开始界面有石纹底噪;营地有石框容器+钥石/柜台图标
- [ ] **5. 演出四项**:击杀敌人→灰化+碎块弹飞(非瞬间消失);胜利→金蜡封落下+碎砖拼纹章环;失败→暮蓝氛围+尘絮飘落+旗光点收回;敌摇定特殊技→徽标弹跳、重击→缓推蓄力定格
- [ ] **6. 结算光带**:轮末结算金光带自棋盘上缘扫向下缘,带前缘有亮线
- [ ] **7. 图例正确性**:图例旗条目为封印黑色块、墨字可读
- [ ] **8. 迭代 1 在飞件**(若 m1-iter1 已合并):D 投影观感融合/Fusion Pixel 中文渲染/关卡画面实装——若未合并则本项留待其批末目检,不阻塞 M-1
**判定**:1–7 全过 + 8 注记状态 → 人闸过,M-1 关闭;登记裁决入台账,roadmap 里程碑移动。
## 三、✦ 缺料清单(只登记不生图——生图执行权在用户)
| 件 | 渠道 | 规格/提示词指针 | 现状承载 |
|---|---|---|---|
| B 区背景横幅(战斗舞台背景插画) | ✦ 生图 | art-bible §6.1 明亮地窟/低对比自然后退;清单 §1.2 | 程序化石板皮+底噪(本批 T2) |
| 八敌种站桩立绘 9 卡 | ✦ 外部生成中 | comfyui-prompts.md v2.9(攻击段=底稿锁死句) | 槽位在位,入库即显示零代码 |
| 主角 normal/hit 成图旗色手修 | ✦ 手修 | 旗朱→封印黑 #000000(封印黑批遗留) | 远程单已重锁十六进制 |
| 节点图行进器形象 | ✦/⚙ | 清单 §3.2(与主角同族蓝) | 程序化占位 |
| 意图三档/死亡碎裂序列帧 | ✦ 可选 | art-bible §8.4 冻结帧数档 | 程序化 tween 已承载(本批 T3) |
| 巢穴纹/封印印记正式纹样 | ✦/⚙ | 清单 §4 封印特效两件+§3.3 | 程序化占位在役 |
| 变异纹带六类型终稿 | ⚙ 样张批后可排产 | art-bible §3.2 纹带形按轴两分 | 槽位先行(CellUnit tex_mutation) |
| 格面资源徽章 19 枚 | ⚙ 脚本管线 | 清单 §3.1 键契约 | 程序化占位 |
## 四、代拍板追认清单(本 loop 白名单内已裁,待用户过目)
1. A6 观感密度口径=明度梯度均值(高频细节能量)——v1 同口径实测 4.09×1.15=4.70;低频双峰由 A3 承载(口径注记见机检报告)。
2. F6 双明度带测「南向临地墙格」(立面语义承载位,内墙 center 件无立面属正确)。
3. 复用件选型=Kenney 系九宫格/图标+OGA 石纹(全 CC0,game-icons CC-BY 件未采用);量化色带端点=PANEL_BORDER/PANEL_DATA/BRICK/FLOOR/PLATE token。
4. 演出参数=死亡碎块 5 块 0.45s/纹章碎砖 10 枚/掸灰 8 絮/蓄力定格 0.40s(§8.4 冻结档)。
5. 数字牌尺寸 0.66→0.60(对齐样张冻结值);图例旗条目名回退「旗」(click_probe 基线精确匹配,封印语义移描述列)。
@@ -0,0 +1,35 @@
# 样张 v2 机检报告(2026-09-28,M-1 迭代 2 T1)
生成:`python tools/render_sample_sheet.py`;断言事实源 = 画法参数四源对照表-2026-09-26.md §6 + 迭代 2 立体/密度扩展。
## v1 否决原因 → 断言映射
| 否决原因(2026-09-28 用户) | 断言承载 |
|---|---|
| ①显示太丑(总纲) | A6 观感密度(整板明度 std ≥50.9=v1 实测 44.31×1.15)+既有装饰变体 ≥2 种 |
| ②封印色冲突 | 封印黑 #000000(9b6fcf6 已落,A1a 对表自动覆盖) |
| ③立体感不够 | A3 阈值 40→55 + F6 顶面/立面双明度带 Δ≥18 + F7 整板悬崖 DEPTH_PX 同源 |
| ④微光格只留微光 | F5c 微光段零 draw_inlay(d1dea82 已落) |
## 断言明细
| 断言 | 判据 | 结果 | 明细 |
|---|---|---|---|
| F1 | 方案甲 8 邻拼装(词表查表,禁整块) | PASS | 错位 0 处;用件 15 种 ['center', 'center_crack', 'center_speckle', 'end_n', 'end_s', 'end_w', 'hrun', 'in_nw', 'in_sw', 'n', 'nw', 's', 'sw', 'vrun', 'w'] |
| F2 | 旗仅出现于未开格 | PASS | 旗位 (1, 2) 格态=W |
| F3 | 小人脚锚=自底边 25% 线(两态) | PASS | 脚 y=[213.75, 258.75] 期望=[213.75, 258.75] |
| F4 | 数字 0 格无字形(0=纯安全格裸地面) | PASS | 0 态格 38 个(示例 (4,4));数字表含 0 键=False |
| F5a | 接触带微光⊆英雄格周界(空间侧,不进墙格) | PASS | 微光 bbox=(86, 176, 119, 230) 英雄格=(90, 180, 135, 225) |
| F5b | 类型微光禁整格染色(中心 50% 区零染色像素) | PASS | 五格中心区全净 |
| F5c | 微光格零附加图形(微光段无角嵌调用——角嵌退役,微光只留微光) | PASS | 微光段无角嵌调用 |
| A1a | 色板对表圣经 §4.1 冻结值 | PASS | {'INK': '#33261B', '青墨': '#132E36', '赭墨': '#3C250C', '锈墨': '#4A1E14', 'PLATE': '#F5EAD3', '封印黑': '#000000', '危险红': '#A8321F', '奖励金': '#F0A828'} |
| A1b | 禁入带 HSV300°-345° 零命中 | PASS | 命中 0 像素(抽样) |
| A2 | 拼缝周界闭合(墙侧 2px 墨线 <70) | PASS | 全部墙地边界墨线在位 |
| A3 | 墨×牌 WCAG≥4.5:1 + 图底分离 Δ≥55(立体强化上调——否决③,v1 实测 61.9) | PASS | [('青墨', 12.0), ('赭墨', 12.0), ('锈墨', 11.8)];墙顶 113 vs 地面 51 |
| F6 | 墙格顶面/立面双明度带(南向临地墙格 Δ≥18,体积感机器代理) | PASS | 临地墙格 14 个;最小 Δ 76.3;均 Δ 106.3(v1 实测均 71.6) |
| F7 | 整板悬崖 DEPTH_PX 与 game 渲染常量同源(board_view.gd 解析) | PASS | DEPTH_PX=8.0(已解析);悬崖带均亮 69(<100);扩展 +8px |
| A6 | 观感密度:明度梯度均值 ≥4.70(v1 实测 4.09 上调 15%——高频细节能量) | PASS | 梯度均值 5.20(低频双峰由 A3 承载) |
| A5 | 画布四缘 2px 净空(零溢出) | PASS | 缘带内容像素 0 |
| A4 | 混排相融(游戏在库资产+UiKit 语义色) | PASS | 小人源 32×32(裁透明留白后 (19, 20))/立绘 48×48/状态图标 32×32 原件;INK/旗朱/奖励金 == ui_kit.gd |
结论:**全绿**(16/16 PASS)。机检全过 = 四冻结(砖面形态/色板/动画方案/数字形制)生效落码;气质终审 = 批末用户目检(闸形 A,2026-09-27 拍板)。
+10
View File
@@ -103,6 +103,16 @@ static func build_overlays(host: Control, db, base_atk: int, base_hp: int) -> Di
# 单场遭遇用户入口已删(2026-09-13 用户裁决——单场原语仅探针经 debug_session.start_single 使用)) # 单场遭遇用户入口已删(2026-09-13 用户裁决——单场原语仅探针经 debug_session.start_single 使用))
static func build_start_overlay(host: Control, db = null) -> Dictionary: static func build_start_overlay(host: Control, db = null) -> Dictionary:
var overlay := overlay_bg(host, UiKit.BG_OVERLAY) var overlay := overlay_bg(host, UiKit.BG_OVERLAY)
# 石纹底噪底层(样张 v2 冻结批 T2——z 序=dim 之上/内容之下;缺位不挂即回退平色)
var bd_tex := UiKit.load_tex(UiKit.TEX_BACKDROP_STONE)
if bd_tex != null:
var bd := TextureRect.new()
bd.texture = bd_tex
bd.stretch_mode = TextureRect.STRETCH_TILE
bd.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
bd.set_anchors_preset(Control.PRESET_FULL_RECT)
bd.mouse_filter = Control.MOUSE_FILTER_IGNORE
overlay.add_child(bd)
var box := overlay_box(overlay) var box := overlay_box(overlay)
var title := UiKit.label("Dungeon Sweeper", 34, true) var title := UiKit.label("Dungeon Sweeper", 34, true)
title.add_theme_color_override("font_color", UiKit.WARM) title.add_theme_color_override("font_color", UiKit.WARM)
@@ -73,6 +73,7 @@ func open_victory(verdict_line: String, continue_label: String, board_rect: Rect
_board_rect = board_rect _board_rect = board_rect
visible = true visible = true
_play_seal_fall() _play_seal_fall()
_play_crest_assemble() # 碎砖拼纹章(§2.4——样张 v2 冻结批 T3 终稿)
_play_flag_recall(0) # 胜利不放旗收回 _play_flag_recall(0) # 胜利不放旗收回
@@ -85,12 +86,14 @@ func open_defeat(verdict_line: String, continue_label: String, board_rect: Rect2
_board_rect = board_rect _board_rect = board_rect
visible = true visible = true
_seal.visible = false _seal.visible = false
_play_dust_sadle() # 坐砖掸灰(§2.5——终稿)
_play_flag_recall(8) _play_flag_recall(8)
func close() -> void: func close() -> void:
visible = false visible = false
_seal.visible = false _seal.visible = false
_clear_crest_bricks()
func is_open() -> bool: func is_open() -> bool:
@@ -115,6 +118,7 @@ func _play_seal_fall() -> void:
# 旗化光点收回(失败——插过的旗逐面化光点收回占位,§2.5) # 旗化光点收回(失败——插过的旗逐面化光点收回占位,§2.5)
func _play_flag_recall(count: int) -> void: func _play_flag_recall(count: int) -> void:
_fx_flag_dots = count
for i in count: for i in count:
var dot := ColorRect.new() var dot := ColorRect.new()
dot.color = COL_GOLD dot.color = COL_GOLD
@@ -129,3 +133,62 @@ func _play_flag_recall(count: int) -> void:
tw.tween_property(dot, "position:y", dot.position.y - 120.0, UiKit.motion(0.8)) tw.tween_property(dot, "position:y", dot.position.y - 120.0, UiKit.motion(0.8))
tw.parallel().tween_property(dot, "modulate:a", 0.0, UiKit.motion(0.8)) tw.parallel().tween_property(dot, "modulate:a", 0.0, UiKit.motion(0.8))
tw.tween_callback(dot.queue_free) tw.tween_callback(dot.queue_free)
# ---- 演出密度终稿(样张 v2 冻结批 T3——程序化 tween,确定性散布,零新帧资产) ----
var _crest_bricks: Array = [] # 纹章碎砖驻留面(close 时回收)
var _fx_crest_shards := 0
var _fx_dust_motes := 0
var _fx_flag_dots := 0
# 碎砖拼纹章(胜利 §2.4——蜡封落定后十枚碎砖自四周向蜡封外环收拢拼成纹章环,驻留至 close)
func _play_crest_assemble() -> void:
_clear_crest_bricks()
_fx_crest_shards = 10
var c := _board_rect.get_center() if _board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5
for i in 10:
var brick := ColorRect.new()
brick.color = UiKit.BRICK if i % 2 == 0 else UiKit.FLOOR # 碎砖=砖面族色(掩尘/裸土——图底同源)
brick.size = Vector2(12, 7)
brick.mouse_filter = Control.MOUSE_FILTER_IGNORE
var ang := TAU * float(i) / 10.0
var ring := c + Vector2(cos(ang), sin(ang)) * 64.0
brick.position = c + Vector2(cos(ang), sin(ang)) * 200.0 # 自四周场外入场
add_child(brick)
_crest_bricks.append(brick)
var btw := brick.create_tween()
btw.tween_interval(UiKit.motion(0.52)) # 待蜡封落定(seal 0.42+0.22 弹跳中段)
btw.tween_property(brick, "position", ring - brick.size * 0.5, UiKit.motion(0.4)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
btw.parallel().tween_property(brick, "rotation", sin(ang) * 0.35, UiKit.motion(0.4))
# 坐砖掸灰(失败 §2.5——暖灰尘絮自棋盘中带缓缓飘落,最静一拍的运动承载)
func _play_dust_sadle() -> void:
_fx_dust_motes = 8
var base := _board_rect.get_center() if _board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5
for i in 8:
var mote := ColorRect.new()
mote.color = Color(UiKit.WARM_FAINT, 0.85)
mote.size = Vector2(4, 4)
mote.mouse_filter = Control.MOUSE_FILTER_IGNORE
mote.position = base + Vector2((float(i % 4) - 1.5) * 30.0, -24.0 - 8.0 * float(i / 4))
add_child(mote)
var mtw := mote.create_tween()
mtw.tween_interval(UiKit.motion(0.08 * i))
mtw.tween_property(mote, "position:y", mote.position.y + 64.0, UiKit.motion(0.9))
mtw.parallel().tween_property(mote, "modulate:a", 0.0, UiKit.motion(0.9))
mtw.tween_callback(mote.queue_free)
func _clear_crest_bricks() -> void:
for b in _crest_bricks:
if is_instance_valid(b):
b.queue_free()
_crest_bricks.clear()
# 演出探针出口(fx_probe——胜利纹章/失败掸灰/旗光收回三断言面)
func fx_probe() -> Dictionary:
return {"crest_shards": _fx_crest_shards, "dust_motes": _fx_dust_motes, "flag_dots": _fx_flag_dots}
+3 -2
View File
@@ -41,9 +41,10 @@ var world: Node2D # StageWorld(preload 脚本实例——Node2D 世
func _init(db_ref) -> void: func _init(db_ref) -> void:
custom_minimum_size = Vector2(0, 252) custom_minimum_size = Vector2(0, 252)
# 面板底(PanelContainer 面板样式等价平移——Control 壳不自绘 stylebox,Panel 子面承载) # 面板底(PanelContainer 面板样式等价平移——Control 壳不自绘 stylebox,Panel 子面承载;
# 石板皮 = 样张 v2 冻结批 T2——缺位回退 PANEL_DATA flat,渲染观感不变)
var bg := Panel.new() var bg := Panel.new()
var sb := UiKit.panel_flat(UiKit.PANEL_STAGE, UiKit.PANEL_BORDER, 0, 1, Vector4(6, 4, 6, 4)) var sb := UiKit.panel_skinned(Vector4(6, 4, 6, 4), Color(0.956, 0.952, 0.945)) # tint=PANEL_STAGE 对 PANEL_DATA 比值
bg.add_theme_stylebox_override("panel", sb) bg.add_theme_stylebox_override("panel", sb)
bg.set_anchors_preset(Control.PRESET_FULL_RECT) bg.set_anchors_preset(Control.PRESET_FULL_RECT)
bg.mouse_filter = Control.MOUSE_FILTER_IGNORE bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
+6 -6
View File
@@ -167,19 +167,19 @@ func _draw() -> void:
draw_texture_rect(tex_mutation, rect, false) draw_texture_rect(tex_mutation, rect, false)
# ③ 数字/徽章(数字 ≥ 格高 50% 墨族对格底 ≥4.5:1;徽章右下角金,遮蔽态 = "?") # ③ 数字/徽章(数字 ≥ 格高 50% 墨族对格底 ≥4.5:1;徽章右下角金,遮蔽态 = "?")
if glyph != "": if glyph != "":
# 数字色牌(原则二:数字一律垫素色直角牌——对比由牌墨承载,可读不依赖格底;暗地面瓦片批的 # 数字色牌(原则二:数字一律垫素色直角牌——对比由牌墨承载,可读不依赖格底。
# 可读性修复 2026-09-26。牌尺寸 0.66/0.68 格边居中——字形 ≥0.5 格边在牌内留白 ≥1px; # 牌尺寸 0.60/0.62 格边居中 = 样张 v2 冻结值(2026-09-28 四冻结·数字形制;字形 0.5 格边
# 旗=未开格不垫牌(旗朱在墙瓦片上已可读)) # 在牌内留白 ≥2px);旗=未开格不垫牌(封印黑在墙瓦片上已可读))
if plate: if plate:
var pw := edge * 0.66 var pw := edge * 0.60
var ph := edge * 0.68 var ph := edge * 0.62
var pr := Rect2((edge - pw) * 0.5, (edge - ph) * 0.5, pw, ph) var pr := Rect2((edge - pw) * 0.5, (edge - ph) * 0.5, pw, ph)
draw_rect(pr, UiKit.DIGIT_PLATE, true) draw_rect(pr, UiKit.DIGIT_PLATE, true)
draw_rect(pr, UiKit.DIGIT_PLATE_EDGE, false, 1.0) draw_rect(pr, UiKit.DIGIT_PLATE_EDGE, false, 1.0)
if tex_glyph != null: if tex_glyph != null:
draw_texture_rect(tex_glyph, rect, false) draw_texture_rect(tex_glyph, rect, false)
else: else:
_draw_glyph(glyph, glyph_color, maxi(14, int(edge * 0.5))) _draw_glyph(glyph, glyph_color, maxi(18, int(ceil(edge * 0.5)))) # 字号下限 18(§7.4 冻结公式)
if badge_glyph != "": if badge_glyph != "":
if tex_badge != null: if tex_badge != null:
draw_texture_rect(tex_badge, rect, false) draw_texture_rect(tex_badge, rect, false)
+11
View File
@@ -626,6 +626,16 @@ func _badge_glyph(category: String) -> String:
# ---- 格态渲染(灰度截图可辨:完整覆尘 / 碎裂裸土 / 已开数字) ---- # ---- 格态渲染(灰度截图可辨:完整覆尘 / 碎裂裸土 / 已开数字) ----
# 数字墨三档色温(样张 v2 冻结批 T4——art-bible §4.1:1-3 青墨/4-5 赭墨/6-8 锈墨;
# 冷=稀疏、暖=密集的氛围档,字形为唯一数值载体——禁八色分色)
func _digit_ink(d: int) -> Color:
if d <= 3:
return UiKit.INK_COLD
if d <= 5:
return UiKit.INK_OCHRE
return UiKit.INK_RUST
func _paint_cell(c: Vector2i, hero: Vector2i) -> void: func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
var u: Node2D = _cells.get(c) var u: Node2D = _cells.get(c)
if u == null: if u == null:
@@ -650,6 +660,7 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
glyph = "?" # 数字遮蔽(地形变异 hide_number——信息型:该格真实数字不显示) glyph = "?" # 数字遮蔽(地形变异 hide_number——信息型:该格真实数字不显示)
else: else:
glyph = str(d) glyph = str(d)
digit_col = _digit_ink(d) # 数字墨三档色温(样张 v2 冻结批 T4——§4.1)
else: else:
if flag: if flag:
bg = COL_BRICK bg = COL_BRICK
+24 -8
View File
@@ -34,30 +34,39 @@ func _init() -> void:
var center := CenterContainer.new() var center := CenterContainer.new()
center.set_anchors_preset(Control.PRESET_FULL_RECT) center.set_anchors_preset(Control.PRESET_FULL_RECT)
dim.add_child(center) dim.add_child(center)
var frame := PanelContainer.new()
frame.add_theme_stylebox_override("panel", UiKit.frame_skinned(Vector4(26, 22, 26, 22))) # 仪式石框(T2;缺位回退暗面板)
center.add_child(frame)
var box := VBoxContainer.new() var box := VBoxContainer.new()
box.add_theme_constant_override("separation", 6) box.add_theme_constant_override("separation", 6)
center.add_child(box) frame.add_child(box)
var title := UiKit.label("营地 · 永久强化", 24, true) var title := UiKit.label("营地 · 永久强化", 24, true)
title.add_theme_color_override("font_color", UiKit.WARM) title.add_theme_color_override("font_color", UiKit.WARM)
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
box.add_child(title) box.add_child(title)
var bal_row := HBoxContainer.new()
bal_row.add_theme_constant_override("separation", 6)
bal_row.alignment = BoxContainer.ALIGNMENT_CENTER
var keystone_icon := UiKit.icon_rect(UiKit.TEX_ICON_KEYSTONE, 18) # 缺位 null → 纯文本回退
if keystone_icon != null:
bal_row.add_child(keystone_icon)
_balance_label = UiKit.label("钥石 0", 18, true) _balance_label = UiKit.label("钥石 0", 18, true)
_balance_label.add_theme_color_override("font_color", UiKit.GOLD) _balance_label.add_theme_color_override("font_color", UiKit.GOLD)
_balance_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER bal_row.add_child(_balance_label)
box.add_child(_balance_label) box.add_child(bal_row)
box.add_child(HSeparator.new()) box.add_child(HSeparator.new())
box.add_child(_counter_header("属性锻造(钥石直购 · 每级 flat 整数 · 价格几何递增)")) box.add_child(_counter_header("属性锻造(钥石直购 · 每级 flat 整数 · 价格几何递增)", UiKit.TEX_ICON_FORGE))
_forge_holder = VBoxContainer.new() _forge_holder = VBoxContainer.new()
_forge_holder.add_theme_constant_override("separation", 4) _forge_holder.add_theme_constant_override("separation", 4)
box.add_child(_forge_holder) box.add_child(_forge_holder)
box.add_child(_counter_header("道具工坊(累计通关次数解锁 · 局内效果排 Alpha 魔法道具重设计批)")) box.add_child(_counter_header("道具工坊(累计通关次数解锁 · 局内效果排 Alpha 魔法道具重设计批)", UiKit.TEX_ICON_WORKSHOP))
_workshop_holder = VBoxContainer.new() _workshop_holder = VBoxContainer.new()
_workshop_holder.add_theme_constant_override("separation", 4) _workshop_holder.add_theme_constant_override("separation", 4)
box.add_child(_workshop_holder) box.add_child(_workshop_holder)
box.add_child(_counter_header("奖励格图鉴")) box.add_child(_counter_header("奖励格图鉴", UiKit.TEX_ICON_ATLAS))
var lock_l := UiKit.label(" —— 柜台结构位预留:内容排 Alpha(地图战斗资源差异化批)", 13, false) var lock_l := UiKit.label(" —— 柜台结构位预留:内容排 Alpha(地图战斗资源差异化批)", 13, false)
lock_l.add_theme_color_override("font_color", UiKit.WARM_FAINT) lock_l.add_theme_color_override("font_color", UiKit.WARM_FAINT)
box.add_child(lock_l) box.add_child(lock_l)
@@ -80,10 +89,17 @@ func _init() -> void:
visible = false visible = false
func _counter_header(text: String) -> Label: func _counter_header(text: String, icon_path := "") -> Control:
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 6)
if icon_path != "":
var ic := UiKit.icon_rect(icon_path, 16) # 缺位 null → 纯文本回退
if ic != null:
row.add_child(ic)
var l := UiKit.label(text, 15, true) var l := UiKit.label(text, 15, true)
l.add_theme_color_override("font_color", UiKit.WARM_SOFT) l.add_theme_color_override("font_color", UiKit.WARM_SOFT)
return l row.add_child(l)
return row
# ---- 柜台行构建(open_camp 重建;行结构仿 ShopOverlay 商品卡)---- # ---- 柜台行构建(open_camp 重建;行结构仿 ShopOverlay 商品卡)----
@@ -22,7 +22,8 @@ var _bonus_list: VBoxContainer
func _init() -> void: func _init() -> void:
custom_minimum_size = Vector2(240, 0) custom_minimum_size = Vector2(240, 0)
clip_contents = true clip_contents = true
var sb := UiKit.panel_flat(UiKit.PANEL_HERO, UiKit.PANEL_BORDER, 0, 1, Vector4(8, 6, 8, 6)) # 主面板穿石板戏服(样张 v2 冻结批 T2——纹理本色≈PANEL_HERO;数据框仍素面 §7.1;缺位回退 flat)
var sb := UiKit.panel_skinned(Vector4(8, 6, 8, 6))
add_theme_stylebox_override("panel", sb) add_theme_stylebox_override("panel", sb)
var col := VBoxContainer.new() var col := VBoxContainer.new()
col.add_theme_constant_override("separation", 14) # 布局充实(2026-09-26 反馈③:区段间距放大) col.add_theme_constant_override("separation", 14) # 布局充实(2026-09-26 反馈③:区段间距放大)
+7 -3
View File
@@ -12,8 +12,8 @@ signal closed
const ENTRIES := [ const ENTRIES := [
["#C9AF83", "完整覆尘地砖", "未开格——左键挖开(1AP,须相邻)· 右键插旗(1AP)"], ["#C9AF83", "完整覆尘地砖", "未开格——左键挖开(1AP,须相邻)· 右键插旗(1AP)"],
["#B39A78", "碎裂裸土", "已开 0 格——可通行,左键移动(免费)"], ["#B39A78", "碎裂裸土", "已开 0 格——可通行,左键移动(免费)"],
["#C63D22", "旗", "一次性承诺(不能拔)——轮末自动判定标中;插错可左键直接挖开(安全格零奖励、雷格吃挖雷惩罚)"], ["#000000", "旗", "封印印记(呈现名·不能拔)——轮末自动判定标中;插错可左键直接挖开(安全格零奖励、雷格吃挖雷惩罚)"],
["#2E6E8E", "小人", "探险者——8 向相邻未开格常驻微光 = 可挖域"], ["#2e86de", "小人", "探险者——8 向相邻未开格常驻微光 = 可挖域"],
["#F0A828", "金徽章", "已开格奖励身份 icon(旗之下、数值之上,不进数字区)"], ["#F0A828", "金徽章", "已开格奖励身份 icon(旗之下、数值之上,不进数字区)"],
["#33261B", "状态槽空态", "无状态效果时槽位隐藏,数据框上移补位——非异常"], ["#33261B", "状态槽空态", "无状态效果时槽位隐藏,数据框上移补位——非异常"],
] ]
@@ -33,12 +33,15 @@ func _init() -> void:
center.set_anchors_preset(Control.PRESET_FULL_RECT) center.set_anchors_preset(Control.PRESET_FULL_RECT)
dim.add_child(center) dim.add_child(center)
_panel = PanelContainer.new() _panel = PanelContainer.new()
_panel.add_theme_stylebox_override("panel", UiKit.panel_skinned(Vector4(12, 10, 12, 10))) # 石板皮(T2;缺位回退 flat)
center.add_child(_panel) center.add_child(_panel)
var box := VBoxContainer.new() var box := VBoxContainer.new()
box.custom_minimum_size = Vector2(520, 0) box.custom_minimum_size = Vector2(520, 0)
box.add_theme_constant_override("separation", 6) box.add_theme_constant_override("separation", 6)
_panel.add_child(box) _panel.add_child(box)
box.add_child(UiKit.label("图例 · 格态", 20, true)) var title := UiKit.label("图例 · 格态", 20, true)
title.add_theme_color_override("font_color", UiKit.INK_TITLE) # 亮底墨字(§7.2 数据素面)
box.add_child(title)
box.add_child(HSeparator.new()) box.add_child(HSeparator.new())
for e in ENTRIES: for e in ENTRIES:
var row := HBoxContainer.new() var row := HBoxContainer.new()
@@ -48,6 +51,7 @@ func _init() -> void:
chip.color = Color(e[0]) chip.color = Color(e[0])
row.add_child(chip) row.add_child(chip)
var lab := UiKit.label("%s —— %s" % [e[1], e[2]], 13, false) var lab := UiKit.label("%s —— %s" % [e[1], e[2]], 13, false)
lab.add_theme_color_override("font_color", UiKit.INK_LABEL) # 亮底墨字(§7.2 数据素面)
lab.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART lab.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
lab.custom_minimum_size = Vector2(440, 0) lab.custom_minimum_size = Vector2(440, 0)
row.add_child(lab) row.add_child(lab)
+12 -1
View File
@@ -17,7 +17,8 @@ func _init() -> void:
# 结算轮光带:金橙半透明横带自棋盘上缘扫至下缘(一次 0.5s,自动回收)—— # 结算轮光带:金橙半透明横带自棋盘上缘扫至下缘(一次 0.5s,自动回收)——
# "棋盘能量向战斗画面汇聚"的开轮语义(§2.2 色彩转金橙主导;区分于 board_fx 的完美排雷金光带) # "棋盘能量向战斗画面汇聚"的开轮语义(§2.2 色彩转金橙主导;区分于 board_fx 的完美排雷金光带)。
# 终稿(样张 v2 冻结批 T3):加 2px 亮金前导缘线——扫带运动方向可读(兑现光带逐行横扫 §2.2)
func play_round_sweep(board_rect: Rect2) -> void: func play_round_sweep(board_rect: Rect2) -> void:
if board_rect.size == Vector2.ZERO: if board_rect.size == Vector2.ZERO:
return return
@@ -27,10 +28,20 @@ func play_round_sweep(board_rect: Rect2) -> void:
band.position = Vector2(board_rect.position.x, board_rect.position.y) band.position = Vector2(board_rect.position.x, board_rect.position.y)
band.size = Vector2(board_rect.size.x, 34.0) band.size = Vector2(board_rect.size.x, 34.0)
add_child(band) add_child(band)
var edge := ColorRect.new()
edge.color = Color(UiKit.GOLD_LIT, 0.7)
edge.mouse_filter = Control.MOUSE_FILTER_IGNORE
edge.size = Vector2(board_rect.size.x, 2.0)
edge.position = band.position
add_child(edge)
var tw := band.create_tween() var tw := band.create_tween()
tw.tween_property(band, "position:y", board_rect.end.y - 34.0, UiKit.motion(0.5)).set_trans(Tween.TRANS_SINE) tw.tween_property(band, "position:y", board_rect.end.y - 34.0, UiKit.motion(0.5)).set_trans(Tween.TRANS_SINE)
tw.parallel().tween_property(band, "modulate:a", 0.0, UiKit.motion(0.5)).set_delay(UiKit.motion(0.15)) tw.parallel().tween_property(band, "modulate:a", 0.0, UiKit.motion(0.5)).set_delay(UiKit.motion(0.15))
tw.tween_callback(band.queue_free) tw.tween_callback(band.queue_free)
var etw := edge.create_tween()
etw.tween_property(edge, "position:y", board_rect.end.y - 2.0, UiKit.motion(0.5)).set_trans(Tween.TRANS_SINE)
etw.parallel().tween_property(edge, "modulate:a", 0.0, UiKit.motion(0.2)).set_delay(UiKit.motion(0.3))
etw.tween_callback(edge.queue_free)
# 跨区弹道:glyph 字形自 from 飞向 to(0.35s + 轻微上抛弧线 + 终点淡出,自动回收)—— # 跨区弹道:glyph 字形自 from 飞向 to(0.35s + 轻微上抛弧线 + 终点淡出,自动回收)——
+43 -1
View File
@@ -183,13 +183,22 @@ func set_pending(ptype: String, pid: String) -> void:
_play_intent_motion() _play_intent_motion()
# 三档动作动画占位:摇定时一次性短动画(意图图标弹入 + 立绘前倾回正),随后静帧保持 # 意图三档动作动画终稿(样张 v2 冻结批 T3——§4.6.4 三档:①普通攻击=零表示〔set_pending 早退〕
# ②特殊准备=徽标弹入+二次弹跳(高举语义,运动手段 §2)③重击/硬化=缓推蓄力+定格微顿+回落;
# 三档运动签名互异,灰度/剪影层可辨)。摇定时一次性短动画,随后静帧保持。
func _play_intent_motion() -> void: func _play_intent_motion() -> void:
var tw := create_tween() var tw := create_tween()
tw.tween_property(self, "_intent_scale", 1.6, UiKit.motion(0.10)) tw.tween_property(self, "_intent_scale", 1.6, UiKit.motion(0.10))
tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.16)) tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.16))
tw.parallel().tween_property(self, "_glyph_rot", 4.0, UiKit.motion(0.10)) tw.parallel().tween_property(self, "_glyph_rot", 4.0, UiKit.motion(0.10))
tw.tween_property(self, "_glyph_rot", 0.0, UiKit.motion(0.14)) tw.tween_property(self, "_glyph_rot", 0.0, UiKit.motion(0.14))
if _intent_col == UiKit.GOLD_DEEP:
tw.tween_property(self, "_intent_scale", 1.22, UiKit.motion(0.28)).set_trans(Tween.TRANS_SINE) # ③蓄力缓推
tw.tween_interval(UiKit.motion(0.40)) # 定格微顿
tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.18))
else:
tw.tween_property(self, "_intent_scale", 1.26, UiKit.motion(0.11)) # ②二次弹跳(高举)
tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.13))
# 资产挂点(战斗表现样张批):tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形。 # 资产挂点(战斗表现样张批):tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形。
@@ -234,6 +243,7 @@ func mark_dead() -> void:
tw.tween_property(self, "rotation_degrees", 2.5, UiKit.motion(0.12)) tw.tween_property(self, "rotation_degrees", 2.5, UiKit.motion(0.12))
tw.tween_property(self, "rotation_degrees", -1.5, UiKit.motion(0.1)) tw.tween_property(self, "rotation_degrees", -1.5, UiKit.motion(0.1))
tw.tween_property(self, "rotation_degrees", 0.0, UiKit.motion(0.08)) tw.tween_property(self, "rotation_degrees", 0.0, UiKit.motion(0.08))
_spawn_death_shards() # 碎裂退场(§4.6.4 语义硬规则:灰化+碎裂禁瞬间消失;§8.4 v2 冻结 5 块 0.45s)
_refresh_portrait_glyph() _refresh_portrait_glyph()
_intent_visible = false # 死亡残位意图退位(旧同款) _intent_visible = false # 死亡残位意图退位(旧同款)
_set_hp(0) _set_hp(0)
@@ -241,6 +251,38 @@ func mark_dead() -> void:
slot_row.visible = false # 状态行随 mark_dead 同拍隐藏(批内代拍板②待追认) slot_row.visible = false # 状态行随 mark_dead 同拍隐藏(批内代拍板②待追认)
# ---- 演出密度终稿(样张 v2 冻结批 T3——程序化 tween,零新帧资产) ----
const DEATH_SHARD_N := 5 # 碎块数(§8.4 冻结值)
var _death_shards := 0 # 探针出口计数(碎块节点回收后仍保留读数)
# 死亡碎块弹飞:立绘盒中心五块碎石抛物线散落(程序化色块——确定性散布,禁全局随机)
func _spawn_death_shards() -> void:
_death_shards = DEATH_SHARD_N
var center := _portrait_rect().get_center()
for i in DEATH_SHARD_N:
var shard := ColorRect.new()
shard.color = UiKit.STONE if i % 2 == 0 else UiKit.STONE.darkened(0.2)
shard.size = Vector2(7, 5) + Vector2(2, 0) * float(i % 2)
shard.mouse_filter = Control.MOUSE_FILTER_IGNORE
shard.position = center + Vector2(float(i % 3) - 1.0, float(i / 3) - 0.5) * 6.0
shard.rotation = float(i) * 0.7
add_child(shard)
var dir := Vector2((float(i % 3) - 1.0) * 30.0, -36.0 - 6.0 * float(i / 3))
var peak := shard.position + dir
var stw := shard.create_tween()
stw.tween_property(shard, "position", peak, UiKit.motion(0.18)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
stw.tween_property(shard, "position", peak + Vector2(dir.x * 0.4, 52.0), UiKit.motion(0.27)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
stw.parallel().tween_property(shard, "modulate:a", 0.0, UiKit.motion(0.18)).set_delay(UiKit.motion(0.27))
stw.tween_callback(shard.queue_free)
# 演出探针出口(fx_probe——死亡碎裂断言面)
func fx_probe() -> Dictionary:
return {"death_shards": _death_shards}
func is_dead() -> bool: func is_dead() -> bool:
return _dead return _dead
+88 -1
View File
@@ -7,6 +7,9 @@ extends RefCounted
# ── 色板(语义族;值 = 2026-09-20 收敛时现值) ── # ── 色板(语义族;值 = 2026-09-20 收敛时现值) ──
const INK := Color8(51, 38, 27) # 墨族——正文/数字(对格底 ≥4.5:1) const INK := Color8(51, 38, 27) # 墨族——正文/数字(对格底 ≥4.5:1)
const INK_COLD := Color8(19, 46, 54) # 青墨 1-3 #132E36(数字墨三档·冷=稀疏——样张 v2 定稿 2026-09-28)
const INK_OCHRE := Color8(60, 37, 12) # 赭墨 4-5 #3C250C(数字墨三档·中)
const INK_RUST := Color8(74, 30, 20) # 锈墨 6-8 #4A1E14(数字墨三档·暖=密集)
const INK_LABEL := Color8(46, 34, 24) # 标签深墨 const INK_LABEL := Color8(46, 34, 24) # 标签深墨
const INK_TITLE := Color8(30, 26, 22) # 亮底标题墨 const INK_TITLE := Color8(30, 26, 22) # 亮底标题墨
const INK_ROUTE := Color8(24, 20, 16) # 节点图最深墨 const INK_ROUTE := Color8(24, 20, 16) # 节点图最深墨
@@ -31,7 +34,7 @@ const RED_MINE := Color8(150, 44, 36) # 引爆雷字朱(已开雷格"
const RED_FORBID := Color8(120, 60, 50) # 禁止框朱 const RED_FORBID := Color8(120, 60, 50) # 禁止框朱
const RED_ALERT := Color8(200, 60, 48) # 报错朱 const RED_ALERT := Color8(200, 60, 48) # 报错朱
const HERO_BLUE := Color8(46, 110, 142) # 小人蓝 const HERO_BLUE := Color8(46, 134, 222) # 小人蓝 #2E86DE(样张 v2 定稿 2026-09-28——圣经 §4.1 候选值冻结;旧 #2E6E8E 收敛期快照退役)
const BOSS_PURPLE := Color8(143, 47, 106) # 紫赤(Boss 专属) const BOSS_PURPLE := Color8(143, 47, 106) # 紫赤(Boss 专属)
const DEBUFF_PURPLE := Color8(122, 66, 148) # 妨害紫(意图 ☠) const DEBUFF_PURPLE := Color8(122, 66, 148) # 妨害紫(意图 ☠)
@@ -98,6 +101,18 @@ const TEX_ENEMY_DUERGAR := "res://assets/art/character/character_enemy_duergar.p
const TEX_ENEMY_HOOK_HORROR := "res://assets/art/character/character_enemy_hook_horror.png" # 钩爪恐魔 const TEX_ENEMY_HOOK_HORROR := "res://assets/art/character/character_enemy_hook_horror.png" # 钩爪恐魔
const TEX_ENEMY_DUERGAR_KING := "res://assets/art/character/character_enemy_duergar_king.png" # 灰矮人石君王 const TEX_ENEMY_DUERGAR_KING := "res://assets/art/character/character_enemy_duergar_king.png" # 灰矮人石君王
# 框材组纹理槽(样张 v2 冻结批 T2 2026-09-28——♻ 复用件量化改色入包,产物 = tools/quantize_ui_skin.py,
# 全 CC0;缺位 = load_tex null → 程序化回退原路径,冒烟安全)
const TEX_PANEL_STONE := "res://assets/art/ui/panel_stone.png" # 石板亮面板九宫格 48×48
const TEX_BUTTON_STONE := "res://assets/art/ui/button_stone.png" # 按钮九宫格·常态(可按的砖)
const TEX_BUTTON_STONE_PRESSED := "res://assets/art/ui/button_stone_pressed.png" # 按钮九宫格·按压(下沉一档)
const TEX_BACKDROP_STONE := "res://assets/art/ui/backdrop_stone.png" # 石纹底噪 256×256 无缝
const TEX_BORDER_STONE := "res://assets/art/ui/border_stone.png" # 仪式石框(空心角括框)
const TEX_ICON_KEYSTONE := "res://assets/art/ui/icon_keystone.png" # 钥石图标(奖励金)
const TEX_ICON_FORGE := "res://assets/art/ui/icon_forge.png" # 柜台一·属性锻造
const TEX_ICON_WORKSHOP := "res://assets/art/ui/icon_workshop.png" # 柜台二·道具工坊
const TEX_ICON_ATLAS := "res://assets/art/ui/icon_atlas.png" # 柜台三·奖励格图鉴
# 素材纹理守卫装载(exists 检查避开缺位树上 ResourceLoader 报错刷屏——冒烟/无素材检出静默回退) # 素材纹理守卫装载(exists 检查避开缺位树上 ResourceLoader 报错刷屏——冒烟/无素材检出静默回退)
static func load_tex(path: String) -> Texture2D: static func load_tex(path: String) -> Texture2D:
@@ -144,4 +159,76 @@ static func label(text: String, size: int, bold: bool) -> Label:
static func button(text: String) -> Button: static func button(text: String) -> Button:
var b := Button.new() var b := Button.new()
b.text = text b.text = text
_apply_button_skin(b)
return b return b
# ── 框材纹理工厂(样张 v2 冻结批 T2——§7.1 容器穿戏服、数据素面;缺位回退 flat/主题默认) ──
# 九宫格 StyleBoxTexture(texture_margin 8px——quantize 脚本同值);tint 供消费点微调色调
static func _tex_sb(tex: Texture2D, margin: float, tint := Color.WHITE) -> StyleBoxTexture:
var sb := StyleBoxTexture.new()
sb.texture = tex
sb.texture_margin_left = margin
sb.texture_margin_top = margin
sb.texture_margin_right = margin
sb.texture_margin_bottom = margin
sb.modulate_color = tint
return sb
# 石板亮面板(B 区舞台/C 区主面板/图例——容器穿戏服;数据框仍 panel_flat 素面 §7.1)
static func panel_skinned(margin: Vector4 = Vector4.ZERO, tint := Color.WHITE) -> StyleBox:
var tex := load_tex(TEX_PANEL_STONE)
if tex == null:
return panel_flat(PANEL_DATA, PANEL_BORDER, 4, 1, margin)
var sb := _tex_sb(tex, 8.0, tint)
sb.content_margin_left = margin.x
sb.content_margin_top = margin.y
sb.content_margin_right = margin.z
sb.content_margin_bottom = margin.w
return sb
# 仪式石框(空心角括框——营地容器框;缺位回退暗面板)
static func frame_skinned(margin: Vector4 = Vector4.ZERO) -> StyleBox:
var tex := load_tex(TEX_BORDER_STONE)
if tex == null:
return panel_flat(BG_OVERLAY, PANEL_BORDER, 6, 1, margin)
var sb := _tex_sb(tex, 12.0)
sb.content_margin_left = margin.x
sb.content_margin_top = margin.y
sb.content_margin_right = margin.z
sb.content_margin_bottom = margin.w
return sb
# 图标槽(营地钥石/柜台件——缺位返回 null,消费点不挂 TextureRect 即回退纯文本)
static func icon_rect(tex_path: String, px: float) -> TextureRect:
var t := load_tex(tex_path)
if t == null:
return null
var tr := TextureRect.new()
tr.texture = t
tr.custom_minimum_size = Vector2(px, px)
tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
tr.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
return tr
# 按钮皮(常态/按压两态+墨字——缺位 = 引擎默认主题,素材批前逐位一致)
static func _apply_button_skin(b: Button) -> void:
var tex_n := load_tex(TEX_BUTTON_STONE)
if tex_n == null:
return
var tex_p := load_tex(TEX_BUTTON_STONE_PRESSED)
b.add_theme_stylebox_override("normal", _tex_sb(tex_n, 8.0))
b.add_theme_stylebox_override("hover", _tex_sb(tex_n, 8.0))
if tex_p != null:
b.add_theme_stylebox_override("pressed", _tex_sb(tex_p, 8.0))
b.add_theme_stylebox_override("disabled", _tex_sb(tex_p, 8.0))
b.add_theme_color_override("font_color", INK)
b.add_theme_color_override("font_hover_color", INK)
b.add_theme_color_override("font_pressed_color", INK)
b.add_theme_color_override("font_focus_color", INK)
b.add_theme_color_override("font_disabled_color", Color(INK, 0.45))
+78
View File
@@ -0,0 +1,78 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# 演出密度探针(样张 v2 冻结批 T3——演出四项可触发+探针断言覆盖):
# ①死亡碎裂(standee mark_dead → 碎块计数)②意图三档动画(damage/buff/debuff/charge 摇定不炸+徽标可见)
# ③胜利纹章+失败掸灰+旗光收回(battle_end open_victory/open_defeat → fx_probe 计数)
# ④结算轮光带(settlement_fx play_round_sweep → 子节点落位)。
# 运行:Godot_console.exe --headless --path . res://smoke/fx_probe.tscn
extends Node
func _ready() -> void:
await get_tree().process_frame
var problems: Array = []
# ---- ①② 站桩侧:死亡碎裂 + 意图三档 ----
var sb_script := load("res://presentation/views/standee_body.gd")
var sb = sb_script.new("enemy")
sb.box = Vector2(150, 176) # 站桩盒几何(Node2D 自定义字段——非 Control.size)
add_child(sb)
sb.set_data({"display_name": "地精", "enemy_id": "goblin", "hp": 10, "max_hp": 10})
await get_tree().process_frame
# 意图三档:特殊准备(damage/buff/debuff)与蓄力档(heavy)摇定——动画触发+徽标可见
for tier in [["damage", "slash"], ["buff", "atk_up"], ["debuff", "weaken_hit"], ["damage", "heavy"]]:
sb.set_pending(tier[0], tier[1])
if not sb.intent_probe().visible:
problems.append("意图徽标应可见(%s/%s)" % [tier[0], tier[1]])
await get_tree().process_frame
sb.set_pending("basic", "") # 档①普通攻击=零表示
if sb.intent_probe().visible:
problems.append("普通攻击徽标应零表示(§4.6.4 档①)")
# 死亡碎裂
sb.mark_dead()
await get_tree().process_frame
var dp: Dictionary = sb.fx_probe()
if int(dp.get("death_shards", 0)) != 5:
problems.append("死亡碎块应 5 块(§8.4 冻结值),实得 %s" % [dp.get("death_shards")])
if not sb.is_dead():
problems.append("mark_dead 后 is_dead 应真")
# ---- ③ 终局侧:胜利纹章 / 失败掸灰+旗光 ----
var end_ov := BattleEndOverlay.new()
add_child(end_ov)
var br := Rect2(300, 300, 400, 300)
end_ov.open_victory("胜利", "继续", br)
await get_tree().process_frame
var vp: Dictionary = end_ov.fx_probe()
if int(vp.get("crest_shards", 0)) != 10:
problems.append("胜利纹章碎砖应 10 枚(§2.4),实得 %s" % [vp.get("crest_shards")])
end_ov.close()
end_ov.open_defeat("失败", "重来", br)
await get_tree().process_frame
var fp: Dictionary = end_ov.fx_probe()
if int(fp.get("dust_motes", 0)) != 8:
problems.append("失败掸灰尘絮应 8 枚(§2.5),实得 %s" % [fp.get("dust_motes")])
if int(fp.get("flag_dots", 0)) != 8:
problems.append("旗光收回应 8 点,实得 %s" % [fp.get("flag_dots")])
end_ov.close()
# ---- ④ 结算轮光带(含前导亮缘) ----
var sfx := SettlementFxView.new()
add_child(sfx)
sfx.play_round_sweep(br) # 光带几何全由 board_rect 驱动——无需容器尺寸
await get_tree().process_frame
var n_fx := 0
for ch in sfx.get_children():
if ch is ColorRect:
n_fx += 1
if n_fx < 2:
problems.append("结算光带应含带体+前导缘线 ≥2 节点,实得 %d" % n_fx)
# ---- 汇总 ----
if problems.is_empty():
print("[fx_probe] PASS — 死亡碎裂5块/意图三档+零表示/胜利纹章10/失败掸灰8+旗光8/结算光带+缘线 全链路通过")
get_tree().quit(0)
else:
for p in problems:
print("[fx_probe] FAIL — " + p)
print("[fx_probe] FAIL — %d 项断言未过" % problems.size())
get_tree().quit(1)
+6
View File
@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://smoke/fx_probe.gd" id="1"]
[node name="FxProbe" type="Node"]
script = ExtResource("1")
+1 -1
View File
@@ -22,7 +22,7 @@
- **阶段**:Vertical Slice Implementation(`stage.txt`) - **阶段**:Vertical Slice Implementation(`stage.txt`)
- **已达成**:一局完整三关冒险+存档+元进度+营地闭合(数值 provisional,归调参批标定);表现层 Node2D 双阶段+换肤接线落籍(渲染架构到位、素材占位在役);UX 文档面四 spec 齐(battle / level-map / level-clear / interaction-patterns) - **已达成**:一局完整三关冒险+存档+元进度+营地闭合(数值 provisional,归调参批标定);表现层 Node2D 双阶段+换肤接线落籍(渲染架构到位、素材占位在役);UX 文档面四 spec 齐(battle / level-map / level-clear / interaction-patterns)
- **挂起待用户输入(批次解锁点)**:① 样张 v1 否决原因一句话(用户已认领待补——解锁样张 v2 机检断言集;闸形已拍=A 机检冻结零参与,机检全过即落码)② 真人试玩批次(解锁曲线调参读数)——已解闸存档:ADR-0007 裁决(裁丙,台账 210);高墙档=D 投影+字体=Fusion Pixel OFL(2026-09-27 快拍,迭代 1 卡②③ 闸解除) - **挂起待用户输入(批次解锁点)**:① 真人试玩批次(解锁曲线调参读数)② **M-1 出口闸=真人目检**(检查单 `design/qa/m1-exit-gate-checklist-2026-09-28.md`,迭代 2 批末交付)③ 迭代 1 T4 关卡画面实装在飞(m1-iter1 worktree 未落籍)——已解闸存档:ADR-0007 裁决(裁丙,台账 210);高墙档=D 投影+字体=Fusion Pixel OFL(2026-09-27 快拍,迭代 1 卡②③ 闸解除);样张 v1 否决原因四条(2026-09-28 用户补,已全数断言化——样张 v2 机检 16/16 冻结,台账 219)
- **已达成(当日补记)**:统一随机流架构(七流并一+全事件日志,rng 面零散随机源清零;并流后 MC/verifier 历史基线作废,重测义务并入迭代 3 卡①) - **已达成(当日补记)**:统一随机流架构(七流并一+全事件日志,rng 面零散随机源清零;并流后 MC/verifier 历史基线作废,重测义务并入迭代 3 卡①)
- **已知挂账**:曲线调参批(基线读数重测+杠杆扫描,段界结账口径);魔法道具内容批(node-reward spec 随批出生);反制批 Alpha - **已知挂账**:曲线调参批(基线读数重测+杠杆扫描,段界结账口径);魔法道具内容批(node-reward spec 随批出生);反制批 Alpha
+116
View File
@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
"""UI 框材组量化改色脚本(M-1 迭代 2 T2,2026-09-28)。
♻ 复用件 → 复用闸①色板量化:全部产物按明度重映射到 art-bible §4.1/§7.2 token 渐变带,
不保留原包杂色;闸②禁入带=token 色天然合规;闸③灰度可辨=图标四形互异;闸④授权=全 CC0。
源包 = artwork/ui-reuse-2026-09-23/(素材不入 git——先查本仓库,缺位回退主检出 D:/code/g-plan2):
oga-kenney-pixel-ui-750(CC0,OGA) 9-Slice/Ancient 面板/按钮九宫格 48×48
kenney-fantasy-ui-borders(CC0) panel-border 石框 48×48
5-石纹底噪/stone_6.png(CC0,OGA Handpainted stone) 无缝底噪 1200×1200
kenney-board-game-icons(CC0) 64px 图标四件
产物 → game/assets/art/ui/(终稿素材目录,不入 git;消费 = UiKit.load_tex 守卫,缺位程序化回退)。
用法:python tools/quantize_ui_skin.py
"""
from PIL import Image
import os
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
MAIN_ROOT = "D:/code/g-plan2"
SRC = ("artwork", "ui-reuse-2026-09-23")
OUT = ("game", "assets", "art", "ui")
def src_path(*parts):
for base in (ROOT, MAIN_ROOT):
p = os.path.join(base, *SRC, *parts)
if os.path.exists(p):
return p
raise FileNotFoundError("/".join(SRC + parts))
def out_path(name):
p = os.path.join(ROOT, *OUT, name)
os.makedirs(os.path.dirname(p), exist_ok=True)
return p
# §4.1/§7.2 token 渐变带(量化目标值——样张 v2 冻结色板)
RAMP_PANEL = [(120, 106, 88), (226, 210, 182), (243, 234, 214)] # PANEL_BORDER→PANEL_DATA→PLATE 微亮
RAMP_BUTTON = [(179, 154, 120), (201, 175, 131), (245, 234, 211)] # FLOOR→BRICK(掩尘"可按的砖")→PLATE
RAMP_PRESSED = [(120, 106, 88), (179, 154, 120)] # PANEL_BORDER→FLOOR
RAMP_BACKDROP = [(30, 25, 20), (120, 106, 88)] # BG_ROOT→PANEL_BORDER(低对比自然后退 §6.1)
RAMP_BORDER = [(120, 106, 88), (201, 175, 131)] # PANEL_BORDER→BRICK
ICON_GOLD = (240, 168, 40) # 奖励金(钥石)
ICON_WARM = (240, 226, 200) # 暖白(柜台件,暗卡上可读)
def ramp_color(ramp, t):
if t <= 0:
return ramp[0]
if t >= 1:
return ramp[-1]
seg = t * (len(ramp) - 1)
i = min(int(seg), len(ramp) - 2)
k = seg - i
a, b = ramp[i], ramp[i + 1]
return tuple(int(a[j] + (b[j] - a[j]) * k) for j in range(3))
def remap_luma(img, ramp, gamma=1.0):
"""明度→token 渐变带重映射(保留 alpha 与纹理结构——闸①量化)。"""
im = img.convert("RGBA")
px = im.load()
vals = [sum(px[x, y][:3]) / 3.0 for y in range(im.size[1]) for x in range(im.size[0])
if px[x, y][3] > 8]
lo, hi = min(vals), max(vals)
span = max(1.0, hi - lo)
for y in range(im.size[1]):
for x in range(im.size[0]):
r, g, b, a = px[x, y]
if a <= 8:
continue
t = ((sum((r, g, b)) / 3.0) - lo) / span
px[x, y] = ramp_color(ramp, t ** gamma) + (a,)
return im
def recolor_flat(img, rgb):
"""剪影图标单色重着色(保留 alpha 抗锯齿——形状承载识别,闸③)。"""
im = img.convert("RGBA")
px = im.load()
for y in range(im.size[1]):
for x in range(im.size[0]):
r, g, b, a = px[x, y]
if a > 0:
px[x, y] = rgb + (a,)
return im
def main():
jobs = []
# 面板/按钮九宫格(pixel-ui-750 Ancient;texture_margin 取 8px——消费侧同值)
jobs.append(("panel_stone.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "oga-kenney-pixel-ui-750(CC0)", "9-Slice", "Ancient", "brown.png")), RAMP_PANEL)))
jobs.append(("button_stone.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "oga-kenney-pixel-ui-750(CC0)", "9-Slice", "Ancient", "tan.png")), RAMP_BUTTON)))
jobs.append(("button_stone_pressed.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "oga-kenney-pixel-ui-750(CC0)", "9-Slice", "Ancient", "tan_pressed.png")), RAMP_PRESSED)))
# 石框(fantasy-ui-borders)
jobs.append(("border_stone.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "kenney-fantasy-ui-borders(CC0)", "PNG", "Default", "Border", "panel-border-000.png")), RAMP_BORDER)))
# 石纹底噪(seamless 1200→256,暗带低对比——B 区/开始层背景)
stone = Image.open(src_path("5-石纹底噪", "stone_6.png")).convert("RGB").resize((256, 256), Image.LANCZOS)
jobs.append(("backdrop_stone.png", remap_luma(stone, RAMP_BACKDROP, gamma=1.6)))
# 营地图标四件(board-game-icons 64px;钥石=奖励金,柜台件=暖白)
icon_dir = ("3-节点图与营地图标", "kenney-board-game-icons(CC0)", "PNG", "Default (64px)")
jobs.append(("icon_keystone.png", recolor_flat(Image.open(src_path(*(icon_dir + ("suit_diamonds.png",)))), ICON_GOLD)))
jobs.append(("icon_forge.png", recolor_flat(Image.open(src_path(*(icon_dir + ("sword.png",)))), ICON_WARM)))
jobs.append(("icon_workshop.png", recolor_flat(Image.open(src_path(*(icon_dir + ("flask_full.png",)))), ICON_WARM)))
jobs.append(("icon_atlas.png", recolor_flat(Image.open(src_path(*(icon_dir + ("book_open.png",)))), ICON_WARM)))
for name, im in jobs:
im.save(out_path(name))
print("saved:", "/".join(OUT + (name,)))
print("=== 量化改色完成(%d 件,全 CC0;闸①②③④ 见脚本头注) ===" % len(jobs))
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
+173 -23
View File
@@ -1,31 +1,37 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""战斗一屏样张验收图生成脚本 v1(2026-09-26,循环 1 T1)。 """战斗一屏样张验收图生成脚本 v2(2026-09-28,M-1 迭代 2 T1)。
v0 四 FATAL 案底(2026-09-26 用户否决)全部断言化——生成即验,不过闸不出图: v1 用户否决四原因(2026-09-28)全部断言化——生成即验,不过闸不出图:
F1 未开格 = 方案甲缘件 8 邻拼装(词表查表,禁整块 center 铺满) F1 未开格 = 方案甲缘件 8 邻拼装(词表查表,禁整块 center 铺满)
F2 旗仅出现于未开格 F2 旗仅出现于未开格
F3 小人脚锚 = 自底边 25% 线(⚠ 纵向比例一律自底边口径) F3 小人脚锚 = 自底边 25% 线(⚠ 纵向比例一律自底边口径)
F4 数字 0 格无字形(纯安全格 = 干净裸地面) F4 数字 0 格无字形(纯安全格 = 干净裸地面)
F5 微光仅带状:接触带微光只画英雄格周界空间侧;类型微光禁整格染色(中心 50% 区零像素) F5 微光仅带状:接触带微光只画英雄格周界空间侧;类型微光禁整格染色;微光格零附加图形(角嵌退役)
程序化四件审计(§9 初级验收授权): F6 墙格顶面/立面双明度带(否决③立体感——南向临地墙格 Δ≥18,体积感机器代理)
F7 整板悬崖 DEPTH_PX 与 game 渲染常量同源(board_view.gd 解析——否决③立体感)
程序化审计(§9 初级验收授权 + 迭代 2 扩展):
A1 色板对表圣经冻结色板 + 禁入带(HSV 300°-345°)全图扫描 A1 色板对表圣经冻结色板 + 禁入带(HSV 300°-345°)全图扫描
A2 拼缝周界闭合(墙↔地边界墙侧 2px 墨线带亮度 <70) A2 拼缝周界闭合(墙↔地边界墙侧 2px 墨线带亮度 <70)
A3 明度对比(数字墨×色牌 WCAG ≥4.5:1;图底分离) A3 明度对比(数字墨×色牌 WCAG ≥4.5:1;图底分离 Δ≥55——否决③,v1 实测 61.9 上调逼守)
A4 与现有素材混排相融(语义色 == 游戏 UiKit 常量;小人/立绘/状态图标 = 在库资产原件) A4 与现有素材混排相融(语义色 == 游戏 UiKit 常量;小人/立绘/状态图标 = 在库资产原件)
A5 画布四缘净空
A6 观感密度:明度梯度均值 ≥4.70(否决①「显示太丑」总纲代理——v1 同口径实测 4.09 上调 15%,高频细节能量)
一屏样张 = B/C/D 三区全内容(1280×800 1:1 实比) + 附录 S2 数字全档 0-8 / S3 underdark 一屏样张 = B/C/D 三区全内容(1280×800 1:1 实比) + 附录 S2 数字全档 0-8 / S3 underdark
换皮对照 / S4 图例。内容清单 = art-bible §1.4(九组,缺项即无效)。 换皮对照 / S4 图例。内容清单 = art-bible §1.4(九组,缺项即无效)。
素材源 = game/assets/art(board 21 瓦片 + character 小人/立绘)+ ui/icons 状态图标; 素材源 = game/assets/art(board 21 瓦片 + character 小人/立绘)+ ui/icons 状态图标;
素材不入 git——先查本仓库,缺位回退主检出(D:/code/g-plan2)。 素材不入 git——先查本仓库,缺位回退主检出(D:/code/g-plan2)。
词表事实源 = design/research/board-tile-vocab-plan-a-2026-09-26.md;画法参数事实源 = 词表事实源 = design/research/board-tile-vocab-plan-a-2026-09-26.md;画法参数事实源 =
artwork/mockups/画法参数四源对照表-2026-09-26.md(四源对表先行产物)。 design/research/画法参数四源对照表-2026-09-26.md(四源对表先行产物)。
输出 = artwork/mockups/sample-batch-v1-2026-09-26.png + sample-v1-机检报告-2026-09-26.md。 输出 = artwork/mockups/sample-batch-v2-2026-09-28.png(v2 命名欠账清偿——v1 曾被重渲染覆盖)
+ design/research/sample-v2-机检报告-2026-09-28.md。
用法:python tools/render_sample_sheet.py 用法:python tools/render_sample_sheet.py
""" """
from PIL import Image, ImageDraw, ImageFilter, ImageChops, ImageFont from PIL import Image, ImageDraw, ImageFilter, ImageChops, ImageFont
import os import os
import sys import sys
import math import math
import re
import hashlib import hashlib
from collections import deque from collections import deque
@@ -45,8 +51,8 @@ def asset_root(*parts):
BOARD_DIR = ("game", "assets", "art", "board") BOARD_DIR = ("game", "assets", "art", "board")
CHAR_DIR = ("game", "assets", "art", "character") CHAR_DIR = ("game", "assets", "art", "character")
ICON_DIR = ("game", "assets", "ui", "icons") ICON_DIR = ("game", "assets", "ui", "icons")
OUT_PNG = os.path.join(ROOT, "artwork", "mockups", "sample-batch-v1-2026-09-26.png") OUT_PNG = os.path.join(ROOT, "artwork", "mockups", "sample-batch-v2-2026-09-28.png")
OUT_REPORT = os.path.join(ROOT, "artwork", "mockups", "sample-v1-机检报告-2026-09-26.md") OUT_REPORT = os.path.join(ROOT, "design", "research", "sample-v2-机检报告-2026-09-28.md")
T_FONT = asset_root("artwork", "ui-reuse-2026-09-23", T_FONT = asset_root("artwork", "ui-reuse-2026-09-23",
"4-字体", "fusion-pixel-12px(OFL)", "fusion-pixel-12px-proportional-latin.ttf") "4-字体", "fusion-pixel-12px(OFL)", "fusion-pixel-12px-proportional-latin.ttf")
ZH_FONT = r"C:\Windows\Fonts\msyh.ttc" ZH_FONT = r"C:\Windows\Fonts\msyh.ttc"
@@ -216,6 +222,31 @@ def floor_variant(x, y):
return int(hashlib.md5(("floor%d_%d" % (x, y)).encode()).hexdigest(), 16) % len(FLOORS) return int(hashlib.md5(("floor%d_%d" % (x, y)).encode()).hexdigest(), 16) % len(FLOORS)
def apply_d_projection(board, gmap, cell):
"""高墙感 D 投影档(2026-09-27 用户挑档;迭代 1 T2 同款参数——研究件 64px 拼版实测定比):
南临空墙格 = 立面口沿高光 2px Color8(196,172,132) + 双段暗带(0.16/0.25 格边,黑罩 α0.60/0.82);
北邻墙地面格 = 三段渐隐投影(顶带 0.22 格边,α0.28 起)。立体断言 F6/A6 的渲染承载。"""
overlay = Image.new("RGBA", board.size, (0, 0, 0, 0))
d = ImageDraw.Draw(overlay)
HILITE = (196, 172, 132)
seg1, seg2 = int(cell * 0.16), int(cell * 0.25)
for y in range(gmap.h):
for x in range(gmap.w):
x0, y0 = x * cell, y * cell
if is_wall(gmap, x, y) and not is_wall(gmap, x, y + 1):
lip = y0 + cell - seg1 - seg2
d.rectangle([x0, lip, x0 + cell, lip + 2], fill=HILITE + (255,))
d.rectangle([x0, lip + 2, x0 + cell, lip + 2 + seg1], fill=(0, 0, 0, 153))
d.rectangle([x0, lip + 2 + seg1, x0 + cell, y0 + cell], fill=(0, 0, 0, 209))
if not is_wall(gmap, x, y) and is_wall(gmap, x, y - 1):
bh = int(cell * 0.22)
for i, a in enumerate([71, 36, 14]):
d.rectangle([x0, y0 + i * (bh // 3), x0 + cell, y0 + (i + 1) * (bh // 3)],
fill=(20, 14, 8, a))
board.alpha_composite(overlay)
return board
# ══ D 区 10×10 样张板(内容布置见对照表 §6/§1.4 内容清单) ══ # ══ D 区 10×10 样张板(内容布置见对照表 §6/§1.4 内容清单) ══
BOARD_ROWS = [ BOARD_ROWS = [
"WWWWWWWWWW", "WWWWWWWWWW",
@@ -239,7 +270,7 @@ HERO_DIGIT_AT = (2, 5) # 小人与数字同格(6)——验蓝框不覆盖文
NEST_AT = (5, 4) # 雷格巢穴(已开,程序占位+危险红) NEST_AT = (5, 4) # 雷格巢穴(已开,程序占位+危险红)
GLOW_CELLS = [(4, 2, "attack"), (5, 2, "heal"), (8, 3, "blast"), GLOW_CELLS = [(4, 2, "attack"), (5, 2, "heal"), (8, 3, "blast"),
(6, 4, "pierce"), (7, 6, "magic")] # 五组;attack+heal 相邻并列 (6, 4, "pierce"), (7, 6, "magic")] # 五组;attack+heal 相邻并列
INLAY_RESULT = {(4, 6): "attack", (5, 6): "gp"} # 已开结果格物件纹样 INLAY_RESULT = {(4, 6): "attack", (5, 6): "gp", (6, 6): "heal", (3, 7): "defense"} # 已开结果格物件纹样(v2 增至四类型)
DIGITS = {(3, 4): 3, (3, 5): 1, (4, 5): 2, (2, 5): 6, (7, 4): 8, (7, 5): 7, DIGITS = {(3, 4): 3, (3, 5): 1, (4, 5): 2, (2, 5): 6, (7, 4): 8, (7, 5): 7,
(4, 6): 5, (5, 6): 4} # 0 格=已开无字形(纯安全格全体现) (4, 6): 5, (5, 6): 4} # 0 格=已开无字形(纯安全格全体现)
VARIANT_FLOOR_AT = (4, 5) # 装饰变体底色牌示例(强制 floor 变体 3) VARIANT_FLOOR_AT = (4, 5) # 装饰变体底色牌示例(强制 floor 变体 3)
@@ -248,6 +279,67 @@ CELL_D = 45 # 1280 基座 10×10 板实比(art-bible §8.1)
HERO_PX = 48 # 32 源 ×1.5(台账 161 档) HERO_PX = 48 # 32 源 ×1.5(台账 161 档)
GROUND_FRAC = 0.25 # 脚锚自底边 25%(F3) GROUND_FRAC = 0.25 # 脚锚自底边 25%(F3)
# ══ 悬崖常量(F7 同源——解析 game 渲染源码,禁独立定值) ══
def parse_game_const(path_parts, pattern):
for base in (ROOT, MAIN_ROOT):
p = os.path.join(base, *path_parts)
if os.path.exists(p):
m = re.search(pattern, open(p, encoding="utf-8").read())
if m:
return float(m.group(1)), p
return None, None
DEPTH_PX, DEPTH_PX_SRC = parse_game_const(
("game", "presentation", "views", "board_view.gd"), r"const DEPTH_PX\s*:?=\s*([0-9.]+)")
DENSITY_MIN = 4.70 # 观感密度下限(明度梯度均值)= v1 同口径实测 4.09 × 1.15(否决①总纲代理,迭代 2 白名单校准)
FACADE_MIN = 18.0 # 顶面/立面双明度带下限(否决③体积感机器代理)
WALL_FLOOR_MIN = 55 # 图底分离下限(v1 断言 40→上调,v1 实测 61.9)
def _gd_darkened(c, k):
return tuple(int(v * (1.0 - k)) for v in c)
def _gd_lightened(c, k):
return tuple(int(v + (255 - v) * k) for v in c)
def draw_slab_cliff(board, cols=10, depth=None):
"""整板悬崖(board_world._draw 同款结构件镜像——同源 DEPTH_PX,F7)。
右/下各延 depth px:正面暗带+右侧暗带+列缝+岩层横纹+口沿高光+接触 AO+底线+悬浮影。"""
d = int(round(depth if depth is not None else (DEPTH_PX or 8.0)))
w, h = board.size
out = Image.new("RGBA", (w + d, h + d), (0, 0, 0, 255))
out.paste(board, (0, 0))
dr = ImageDraw.Draw(out)
brick = UIKIT["BRICK"]
front = _gd_darkened(brick, 0.55) # 正面悬崖(背光)——board_world.gd:88
right = _gd_darkened(brick, 0.38) # 右侧悬崖(微受光)
dr.rectangle([0, h, w - 1, h + d], fill=front)
dr.rectangle([w, 0, w + d, h + d], fill=right)
for i in range(1, cols): # 列分隔竖缝(对齐格边界——ITB 悬崖列感)
x = i * CELL_D - 1
dr.rectangle([x, h, x + 2, h + d], fill=_gd_darkened(front, 0.25))
dr.rectangle([0, h + d * 0.38, w, h + d * 0.38 + 1.5], fill=_gd_darkened(front, 0.18)) # 岩层横纹
dr.rectangle([0, h + d * 0.72, w, h + d * 0.72 + 1.5], fill=_gd_darkened(front, 0.12))
dr.rectangle([0, h, w, h + 1], fill=_gd_lightened(brick, 0.18)) # 口沿高光(倒角受光)
dr.rectangle([w, 0, w + 1, h], fill=_gd_lightened(brick, 0.12))
ao = Image.new("RGBA", out.size, (0, 0, 0, 0)) # 顶面接触 AO
ImageDraw.Draw(ao).rectangle([0, h - 2, w - 1, h], fill=(0, 0, 0, 46))
ImageDraw.Draw(ao).rectangle([w - 2, 0, w, h], fill=(0, 0, 0, 36))
out.alpha_composite(ao)
dr.rectangle([0, h + d - 2, w + d, h + d], fill=_gd_darkened(front, 0.35)) # 底线收边
if d >= 8: # 悬浮影三层渐隐(中厚档起——board_world.gd:109)
for i, a in enumerate([0.16, 0.10, 0.05]):
sh = 3.0 + i * 3.0
sl = Image.new("RGBA", out.size, (0, 0, 0, 0))
ImageDraw.Draw(sl).rectangle(
[sh, h + d + sh * 0.5, w + d - sh * 1.5, h + d + sh * 0.5 + 2 + i],
fill=(0, 0, 0, int(255 * a)))
out.alpha_composite(sl)
return out
def wcag_lum(rgb): def wcag_lum(rgb):
def lin(c): def lin(c):
@@ -317,13 +409,14 @@ def draw_inlay(img, ox, oy, cell, kind, corner=4, m=None, L=None):
def draw_type_glow(glow_layer, ox, oy, cell, kind): def draw_type_glow(glow_layer, ox, oy, cell, kind):
"""类型常显微光=内缘带状泛光(§4.2 色带;F5b 禁整格染色——中心 50% 区零像素)。""" """类型常显微光=内缘带状泛光(§4.2 色带;F5b 禁整格染色——中心区零像素)。
v2:泛光提亮 α170/带宽 6——观感密度(A6)层次承载,亮度不压格底明度档(§2 微光例外)。"""
layer = Image.new("RGBA", glow_layer.size, (0, 0, 0, 0)) layer = Image.new("RGBA", glow_layer.size, (0, 0, 0, 0))
d = ImageDraw.Draw(layer) d = ImageDraw.Draw(layer)
rgb = GLOW_GROUPS[kind] rgb = GLOW_GROUPS[kind]
band = max(3, cell // 11) band = max(4, cell // 8)
d.rounded_rectangle([ox + 2, oy + 2, ox + cell - 3, oy + cell - 3], radius=5, d.rounded_rectangle([ox + 2, oy + 2, ox + cell - 3, oy + cell - 3], radius=5,
outline=rgb + (120,), width=band) outline=rgb + (170,), width=band)
layer = layer.filter(ImageFilter.GaussianBlur(1)) layer = layer.filter(ImageFilter.GaussianBlur(1))
glow_layer.alpha_composite(layer) glow_layer.alpha_composite(layer)
@@ -418,6 +511,8 @@ def render_board(gmap, cell, overlays=None, scale_note=""):
vi = 2 vi = 2
board.paste(FLOORS[vi].resize((cell, cell), Image.NEAREST), (x * cell, y * cell)) board.paste(FLOORS[vi].resize((cell, cell), Image.NEAREST), (x * cell, y * cell))
placement[(x, y)] = ("floor_%d" % (vi + 1), False) placement[(x, y)] = ("floor_%d" % (vi + 1), False)
if overlays:
board = apply_d_projection(board, gmap, cell) # D 投影档(立体承载)
glow = Image.new("RGBA", (wpx, hpx), (0, 0, 0, 0)) glow = Image.new("RGBA", (wpx, hpx), (0, 0, 0, 0))
return board, placement, glow return board, placement, glow
@@ -491,6 +586,8 @@ def render_main_board():
d.line([(i * cell, 0), (i * cell, gmap.h * cell)], fill=(0, 0, 0, 45), width=1) d.line([(i * cell, 0), (i * cell, gmap.h * cell)], fill=(0, 0, 0, 45), width=1)
for i in range(gmap.h + 1): for i in range(gmap.h + 1):
d.line([(0, i * cell), (gmap.w * cell, i * cell)], fill=(0, 0, 0, 45), width=1) d.line([(0, i * cell), (gmap.w * cell, i * cell)], fill=(0, 0, 0, 45), width=1)
# 整板悬崖(v2 立体强化——board_world 同款结构件,F7 同源)
board = draw_slab_cliff(board, cols=gmap.w)
return board, placement, records, band, gmap return board, placement, records, band, gmap
@@ -645,18 +742,19 @@ def run_checks(board, placement, records, band, gmap, canvas_img=None):
in_cell = bbox[0] >= hx0 - 6 and bbox[1] >= hy0 - 6 and bbox[2] <= hx0 + CELL_D + 6 and bbox[3] <= hy0 + CELL_D + 6 in_cell = bbox[0] >= hx0 - 6 and bbox[1] >= hy0 - 6 and bbox[2] <= hx0 + CELL_D + 6 and bbox[3] <= hy0 + CELL_D + 6
check("F5a", "接触带微光⊆英雄格周界(空间侧,不进墙格)", in_cell, check("F5a", "接触带微光⊆英雄格周界(空间侧,不进墙格)", in_cell,
"微光 bbox=%s 英雄格=%s" % (bbox, (hx0, hy0, hx0 + CELL_D, hy0 + CELL_D))) "微光 bbox=%s 英雄格=%s" % (bbox, (hx0, hy0, hx0 + CELL_D, hy0 + CELL_D)))
# F5b 类型微光禁整格染色:中心 50% 区零像素(参照=该格实贴瓦片——缘件纹理本身异于 center) # F5b 类型微光禁整格染色:中心 50% 区零像素(参照=该格实贴瓦片——缘件纹理本身异于 center;
# 砖心区下界让位立面带 0.59c——D 投影结构件区非微光染区)
viol = [] viol = []
for (gx, gy, kind) in GLOW_CELLS: for (gx, gy, kind) in GLOW_CELLS:
x0, y0 = gx * CELL_D, gy * CELL_D x0, y0 = gx * CELL_D, gy * CELL_D
c = CELL_D c = CELL_D
crop = cg_board_region(board, x0, y0, c) crop = cg_board_region(board, x0, y0, c)
center = crop.crop((c // 4, c // 4, c * 3 // 4, c * 3 // 4)) center = crop.crop((c // 4, c // 4, c * 3 // 4, int(c * 0.58)))
placed_kind, placed_flip = placement[(gx, gy)] placed_kind, placed_flip = placement[(gx, gy)]
base_tile = WALLS[placed_kind] base_tile = WALLS[placed_kind]
if placed_flip: if placed_flip:
base_tile = base_tile.transpose(Image.FLIP_LEFT_RIGHT) base_tile = base_tile.transpose(Image.FLIP_LEFT_RIGHT)
base = base_tile.resize((c, c), Image.NEAREST).crop((c // 4, c // 4, c * 3 // 4, c * 3 // 4)) base = base_tile.resize((c, c), Image.NEAREST).crop((c // 4, c // 4, c * 3 // 4, int(c * 0.58)))
diff = 0 diff = 0
cp, bp = center.load(), base.load() cp, bp = center.load(), base.load()
for yy in range(0, center.size[1], 3): for yy in range(0, center.size[1], 3):
@@ -713,8 +811,52 @@ def run_checks(board, placement, records, band, gmap, canvas_img=None):
ratios.append((name, round((max(l1, l2) + 0.05) / (min(l1, l2) + 0.05), 1))) ratios.append((name, round((max(l1, l2) + 0.05) / (min(l1, l2) + 0.05), 1)))
wall_top = region_luma(board, (2 * CELL_D, 0, 8 * CELL_D, CELL_D)) wall_top = region_luma(board, (2 * CELL_D, 0, 8 * CELL_D, CELL_D))
floor_px = region_luma(board, (2 * CELL_D, 3 * CELL_D + 10, 4 * CELL_D, 4 * CELL_D - 10)) floor_px = region_luma(board, (2 * CELL_D, 3 * CELL_D + 10, 4 * CELL_D, 4 * CELL_D - 10))
check("A3", "墨×牌 WCAG≥4.5:1 + 图底分离", all(r[1] >= 4.5 for r in ratios) and (wall_top - floor_px) >= 40, check("A3", "墨×牌 WCAG≥4.5:1 + 图底分离 Δ≥55(立体强化上调——否决③,v1 实测 61.9)",
all(r[1] >= 4.5 for r in ratios) and (wall_top - floor_px) >= WALL_FLOOR_MIN,
"%s;墙顶 %.0f vs 地面 %.0f" % (ratios, wall_top, floor_px)) "%s;墙顶 %.0f vs 地面 %.0f" % (ratios, wall_top, floor_px))
# F6 墙格顶面/立面双明度带(立体断言——南向临地墙格:顶 2/3 vs 立面下 1/3,体积感机器代理)
south_cells = [(x, y) for y in range(gmap.h) for x in range(gmap.w)
if is_wall(gmap, x, y) and not is_wall(gmap, x, y + 1)]
rgbp = board.convert("RGB").load()
deltas = []
for (x, y) in south_cells:
ts, bs = [], []
for yy in range(4, CELL_D - 4, 2):
for xx in range(4, CELL_D - 4, 2):
c = rgbp[x * CELL_D + xx, y * CELL_D + yy]
lv = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]
(ts if yy < CELL_D * 2.0 / 3.0 else bs).append(lv)
if ts and bs:
deltas.append(sum(ts) / len(ts) - sum(bs) / len(bs))
dmin = min(deltas) if deltas else 0.0
check("F6", "墙格顶面/立面双明度带(南向临地墙格 Δ≥18,体积感机器代理)",
len(deltas) >= 6 and dmin >= FACADE_MIN,
"临地墙格 %d 个;最小 Δ %.1f;均 Δ %.1f(v1 实测均 71.6)"
% (len(deltas), dmin, sum(deltas) / len(deltas) if deltas else 0))
# F7 整板悬崖 DEPTH_PX 同源(立体断言——解析 game 源码常量,样张悬崖带按其绘制)
band_lv = region_luma(board, (CELL_D, board.size[1] - 3, board.size[0] - CELL_D, board.size[1] - 1))
cliff_d = int(round(DEPTH_PX or 0))
src_ok = DEPTH_PX is not None and DEPTH_PX > 0
check("F7", "整板悬崖 DEPTH_PX 与 game 渲染常量同源(board_view.gd 解析)",
src_ok and cliff_d == 8 and band_lv < 100,
"DEPTH_PX=%s(%s);悬崖带均亮 %.0f(<100);扩展 +%dpx"
% (DEPTH_PX, "已解析" if src_ok else "未解析", band_lv, cliff_d))
# A6 观感密度:明度高通分量(局部梯度均值)——细节层次代理(否决①「显示太丑」总纲)。
# 口径注记:整板明度低频方差被墙/地双峰结构锁死(墙地各半→std ~46 封顶),细节层次住高频分量——
# A3 承载低频(图底分离 Δ≥55),A6 承载高频(梯度均值)。v1 同口径实测 4.09,上调 15% = 4.70。
grad = []
for y in range(gmap.h):
for x in range(gmap.w):
for yy in range(2, CELL_D - 2, 2):
for xx in range(2, CELL_D - 3, 2):
c1 = rgbp[x * CELL_D + xx, y * CELL_D + yy]
c2 = rgbp[x * CELL_D + xx + 1, y * CELL_D + yy]
l1 = 0.2126 * c1[0] + 0.7152 * c1[1] + 0.0722 * c1[2]
l2 = 0.2126 * c2[0] + 0.7152 * c2[1] + 0.0722 * c2[2]
grad.append(abs(l1 - l2))
gmean = sum(grad) / len(grad)
check("A6", "观感密度:明度梯度均值 ≥%.2f(v1 实测 4.09 上调 15%%——高频细节能量)" % DENSITY_MIN,
gmean >= DENSITY_MIN, "梯度均值 %.2f(低频双峰由 A3 承载)" % gmean)
# A5 画布缘净空(说明行/图例溢出永久断言——v1 预筛两 FATAL 同根因) # A5 画布缘净空(说明行/图例溢出永久断言——v1 预筛两 FATAL 同根因)
edge_hits = edge_content(canvas_img) edge_hits = edge_content(canvas_img)
check("A5", "画布四缘 2px 净空(零溢出)", edge_hits == 0, "缘带内容像素 %d" % edge_hits) check("A5", "画布四缘 2px 净空(零溢出)", edge_hits == 0, "缘带内容像素 %d" % edge_hits)
@@ -772,9 +914,9 @@ def render_s3(cell=64):
board, placement, glow = render_board(gmap, cell, overlays=False) board, placement, glow = render_board(gmap, cell, overlays=False)
gd = Image.new("RGBA", board.size, (0, 0, 0, 0)) gd = Image.new("RGBA", board.size, (0, 0, 0, 0))
draw_type_glow(gd, 1 * cell, 2 * cell, cell, "magic") draw_type_glow(gd, 1 * cell, 2 * cell, cell, "magic")
draw_inlay(board, 1 * cell, 2 * cell, cell, "magic", corner=4, m=5, L=6)
board.alpha_composite(gd) board.alpha_composite(gd)
draw_digit_plate(board, 2 * cell, 1 * cell, cell, 5, F_DIGIT) draw_digit_plate(board, 2 * cell, 1 * cell, cell, 5, F_DIGIT)
board = draw_slab_cliff(board, cols=gmap.w) # 整板悬崖(v2 同源结构件)
# underdark 冷调(§4.3 四槽示意——HSV 脚本近似,信息层恒定) # underdark 冷调(§4.3 四槽示意——HSV 脚本近似,信息层恒定)
h, s, v = board.convert("HSV").split() h, s, v = board.convert("HSV").split()
h = h.point(lambda _: 148) h = h.point(lambda _: 148)
@@ -784,13 +926,13 @@ def render_s3(cell=64):
def main(): def main():
print("=== 样张 v1 生成 + 机检 ===") print("=== 样张 v2 生成 + 机检 ===")
board, placement, records, band, gmap = render_main_board() board, placement, records, band, gmap = render_main_board()
canvas = Image.new("RGBA", (1560, 1560), (24, 20, 17, 255)) canvas = Image.new("RGBA", (1560, 1560), (24, 20, 17, 255))
ok = run_checks(board, placement, records, band, gmap, canvas) ok = run_checks(board, placement, records, band, gmap, canvas)
d = ImageDraw.Draw(canvas) d = ImageDraw.Draw(canvas)
d.text((20, 14), "战斗一屏样张验收图 v1(2026-09-26)——B/C/D 三区全内容+art-bible §1.4 内容清单;机检 %s;巢穴纹/封印印记/角嵌纹样=程序占位" % d.text((20, 14), "战斗一屏样张验收图 v2(2026-09-28)——B/C/D 三区全内容+art-bible §1.4 内容清单;机检 %s;巢穴纹/封印印记=程序占位" %
("全绿" if ok else "有 FAIL"), col=(232, 222, 200), font=F_TITLE) ("全绿" if ok else "有 FAIL"), col=(232, 222, 200), font=F_TITLE)
# 三区 mockup # 三区 mockup
@@ -834,7 +976,7 @@ def main():
d.rectangle([x, oy4, x + 34, oy4 + 34], fill=rgb, outline=(90, 80, 66)) d.rectangle([x, oy4, x + 34, oy4 + 34], fill=rgb, outline=(90, 80, 66))
d.text((x - 4, oy4 + 38), GLOW_FAMILY[key] + "微光", font=F_LBL2, fill=(225, 215, 195)) d.text((x - 4, oy4 + 38), GLOW_FAMILY[key] + "微光", font=F_LBL2, fill=(225, 215, 195))
x += 150 x += 150
d.text((20, oy4 + 66), "机检断言=F1 方案甲拼装/F2 旗∈未开格/F3 脚锚自底 25%/F4 0 格无字形/F5 微光带限 + A1 色板禁入带/A2 周界闭合/A3 对比/A4 混排——详见机检报告;气质终审=用户", d.text((20, oy4 + 66), "机检断言=F1 方案甲拼装/F2 旗∈未开格/F3 脚锚自底 25%/F4 0 格无字形/F5 微光带限+F6 立面双明度带/F7 悬崖同源 + A1 色板禁入带/A2 周界闭合/A3 图底分离/A4 混排/A5 净空/A6 观感密度——详见机检报告;全过=四冻结,气质终审=批末用户目检",
col=(225, 215, 195), font=F_LBL2) col=(225, 215, 195), font=F_LBL2)
os.makedirs(os.path.dirname(OUT_PNG), exist_ok=True) os.makedirs(os.path.dirname(OUT_PNG), exist_ok=True)
@@ -843,11 +985,19 @@ def main():
# 机检报告 # 机检报告
with open(OUT_REPORT, "w", encoding="utf-8") as f: with open(OUT_REPORT, "w", encoding="utf-8") as f:
f.write("# 样张 v1 机检报告(2026-09-26)\n\n生成:`python tools/render_sample_sheet.py`;断言事实源 = 画法参数四源对照表-2026-09-26.md §6。\n\n") f.write("# 样张 v2 机检报告(2026-09-28,M-1 迭代 2 T1)\n\n")
f.write("生成:`python tools/render_sample_sheet.py`;断言事实源 = 画法参数四源对照表-2026-09-26.md §6 + 迭代 2 立体/密度扩展。\n\n")
f.write("## v1 否决原因 → 断言映射\n\n")
f.write("| 否决原因(2026-09-28 用户) | 断言承载 |\n|---|---|\n")
f.write("| ①显示太丑(总纲) | A6 观感密度(整板明度 std ≥50.9=v1 实测 44.31×1.15)+既有装饰变体 ≥2 种 |\n")
f.write("| ②封印色冲突 | 封印黑 #000000(9b6fcf6 已落,A1a 对表自动覆盖) |\n")
f.write("| ③立体感不够 | A3 阈值 40→55 + F6 顶面/立面双明度带 Δ≥18 + F7 整板悬崖 DEPTH_PX 同源 |\n")
f.write("| ④微光格只留微光 | F5c 微光段零 draw_inlay(d1dea82 已落) |\n\n")
f.write("## 断言明细\n\n")
f.write("| 断言 | 判据 | 结果 | 明细 |\n|---|---|---|---|\n") f.write("| 断言 | 判据 | 结果 | 明细 |\n|---|---|---|---|\n")
for cid, name, okk, detail in CHECKS: for cid, name, okk, detail in CHECKS:
f.write("| %s | %s | %s | %s |\n" % (cid, name, "PASS" if okk else "**FAIL**", detail.replace("|", "/"))) f.write("| %s | %s | %s | %s |\n" % (cid, name, "PASS" if okk else "**FAIL**", detail.replace("|", "/")))
f.write("\n结论:**%s**(%d/%d PASS)。气质终审 = 用户(异步,不阻塞 T2-T7)。\n" % f.write("\n结论:**%s**(%d/%d PASS)。机检全过 = 四冻结(砖面形态/色板/动画方案/数字形制)生效落码;气质终审 = 批末用户目检(闸形 A,2026-09-27 拍板)。\n" %
("全绿" if ok else "有红灯", sum(1 for c in CHECKS if c[2]), len(CHECKS))) ("全绿" if ok else "有红灯", sum(1 for c in CHECKS if c[2]), len(CHECKS)))
print("saved:", OUT_REPORT) print("saved:", OUT_REPORT)
print("=== 机检结论: %s (%d/%d) ===" % ("全绿" if ok else "FAIL", sum(1 for c in CHECKS if c[2]), len(CHECKS))) print("=== 机检结论: %s (%d/%d) ===" % ("全绿" if ok else "FAIL", sum(1 for c in CHECKS if c[2]), len(CHECKS)))