From 0da19a5b09c40129246e413506d9ee5320c27c24 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 18 Sep 2026 05:46:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(slice):=20=E6=8E=AA=E8=BE=9E=E6=94=B6?= =?UTF-8?q?=E5=8F=A3+=E6=95=B0=E5=80=BC=E5=AF=B9=E9=BD=90+=E5=9C=BA?= =?UTF-8?q?=E6=99=AF=E4=B8=80/=E4=B8=89=E6=8B=8D=E6=9D=BF=E8=90=BD?= =?UTF-8?q?=E5=9C=B0=E6=89=B9=E2=80=94=E2=80=94=E2=91=A0registry=20?= =?UTF-8?q?=E6=8C=87=E8=B7=AF=E5=8C=96=E6=94=B6=E5=8F=A3=EF=BC=9A=E4=B8=BB?= =?UTF-8?q?=E8=A7=92=E6=9D=A1=E7=9B=AE=E5=8E=BB=20MVP=20=E5=80=BC=E6=94=B9?= =?UTF-8?q?=E8=AF=BB=20hero.tres=E3=80=81=E6=8A=80=E8=83=BD=E6=9D=A1?= =?UTF-8?q?=E7=9B=AE=E4=B8=83=E8=A1=8C=E6=9E=9A=E4=B8=BE=EF=BC=88=E8=A1=A5?= =?UTF-8?q?=E6=94=B6=E6=92=95=E5=92=AC/=E6=AF=92=E7=88=86=EF=BC=89+?= =?UTF-8?q?=E5=80=8D=E7=8E=87=E5=86=B7=E5=8D=B4=E4=BF=9D=E5=BA=95=E6=8C=87?= =?UTF-8?q?=E8=B7=AF=20skills.tres=E3=80=81=E6=95=8C=E4=BA=BA=E6=9D=A1?= =?UTF-8?q?=E7=9B=AE=E4=BA=94=E6=95=8C=E7=A7=8D=E6=9E=9A=E4=B8=BE+?= =?UTF-8?q?=E6=B1=A0=E6=9D=83=E9=87=8D=20EV=20=E8=AF=BB=E6=95=B0=E6=8C=87?= =?UTF-8?q?=E8=B7=AF=20enemy.tres=E3=80=81charge=5Fgrants/equivalence=5Fre?= =?UTF-8?q?f=5Fdamage/board=5Fgen=20=E5=8F=82=E8=80=83=E8=A1=8C=E5=88=A0?= =?UTF-8?q?=E6=97=A7=E5=80=BC=E6=94=B9=E6=8C=87=E8=B7=AF=E3=80=81=E4=B8=AD?= =?UTF-8?q?=E7=82=B9=E9=94=9A=E6=94=B9=20floor=20=E5=90=91=E4=B8=8B?= =?UTF-8?q?=E5=8F=96=E6=95=B4=E5=8F=A3=E5=BE=84=EF=BC=88=E4=B8=8E=E5=88=87?= =?UTF-8?q?=E7=89=87=E6=A0=A1=E9=AA=8C=E5=BC=8F=E5=AF=B9=E9=BD=90=EF=BC=89?= =?UTF-8?q?=EF=BC=9B=E2=91=A1=E5=88=B7=E6=96=B0=E4=B8=B4=E6=97=B6=E5=90=8D?= =?UTF-8?q?=E5=85=A8=E9=93=BE=E9=80=80=E5=BD=B9=EF=BC=9Arefresh=5Fmax=5Fpe?= =?UTF-8?q?r=5Fround=E2=86=92refresh=5Fcap=5Fper=5Fround=E3=80=81request?= =?UTF-8?q?=5Frefresh=5Factive=E2=86=92request=5Frefresh=E3=80=81can=5Freq?= =?UTF-8?q?uest=5Frefresh=E2=86=92can=5Frefresh=EF=BC=888=20=E6=96=87?= =?UTF-8?q?=E4=BB=B6=20grep=20=E9=9B=B6=E6=AE=8B=E7=95=99=EF=BC=89+?= =?UTF-8?q?=E8=A1=8C=E5=8A=A8=E7=BB=8F=E6=B5=8E=E3=80=8C=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E5=90=8D=E5=AF=B9=E9=BD=90=E5=BE=85=E6=89=B9=E3=80=8D=E6=B3=A8?= =?UTF-8?q?=E8=AE=B0=E6=94=B6=E5=8F=A3=EF=BC=9B=E2=91=A2=E4=BA=94=E5=8C=BA?= =?UTF-8?q?=E6=AE=8B=E7=95=99=E6=B8=85=E9=9B=B6=EF=BC=9A=E7=8E=A9=E5=AE=B6?= =?UTF-8?q?=E5=8F=AF=E8=A7=81=E5=89=AF=E6=A0=87=E9=A2=98=E6=94=B9=E6=88=98?= =?UTF-8?q?=E6=96=97=E5=B1=8F=E5=9B=9B=E5=8C=BA=E3=80=81build=5Ffive=5Fzon?= =?UTF-8?q?e=E2=86=92build=5Fbattle=5Fui=E3=80=81=E6=8E=A2=E9=92=88?= =?UTF-8?q?=E6=96=87=E6=A1=88/=E6=B3=A8=E9=87=8A=2011=20=E5=A4=84=EF=BC=9B?= =?UTF-8?q?=E2=91=A3=E8=A1=8C=E5=8A=A8=E7=BB=8F=E6=B5=8E=20schema=20?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=80=BC=208/8=E2=86=926/6=20=E9=9A=8F?= =?UTF-8?q?=E5=8E=8B=E9=80=9F=E6=89=B9+=E6=98=8E=E7=BB=86=E5=AD=97?= =?UTF-8?q?=E5=8F=B7=E6=8A=AC=E6=A0=87=2014px=EF=BC=88HUD=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=E5=8F=AF=E8=AE=BF=E9=97=AE=E6=80=A7=E4=B8=8B=E9=99=90?= =?UTF-8?q?=EF=BC=89+RAP=E2=88=928=20=E5=AD=97=E9=9D=A2=E9=87=8F=206=20?= =?UTF-8?q?=E5=A4=84=E6=94=B9=E8=A1=A8=E5=80=BC=E9=A9=B1=E5=8A=A8=EF=BC=88?= =?UTF-8?q?ap=5Fbar=5Fview.set=5Frefresh=5Fcost=20=E8=A3=85=E9=85=8D?= =?UTF-8?q?=E6=B3=A8=E5=85=A5=E9=80=9A=E9=81=93=EF=BC=8Ctoast=20=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E5=8A=A8=E6=80=81=E5=8C=96=EF=BC=89=EF=BC=9B=E2=91=A4?= =?UTF-8?q?=E6=84=8F=E5=9B=BE=E5=BE=BD=E6=A0=87=E4=B8=89=E6=A1=A3=E5=8D=A0?= =?UTF-8?q?=E4=BD=8D=EF=BC=88=E6=99=AE=E6=94=BB=E2=80=94/=E4=BC=A4?= =?UTF-8?q?=E5=AE=B3=E6=8A=80=E2=9A=94/=E5=A2=9E=E7=9B=8A=E6=8A=80?= =?UTF-8?q?=E2=9C=A6/=E9=87=8D=E5=87=BB=E7=B1=BB=E8=93=84=E5=8A=9B?= =?UTF-8?q?=EF=BC=89=E2=80=94=E2=80=94=E6=8A=80=E8=83=BD=E5=90=8D=E9=80=80?= =?UTF-8?q?=E5=87=BA=E5=BE=BD=E6=A0=87=E5=A0=B5=E5=89=A7=E9=80=8F=EF=BC=88?= =?UTF-8?q?2026-09-18=20=E7=94=A8=E6=88=B7=E6=8B=8D=E6=9D=BF=EF=BC=8C?= =?UTF-8?q?=E7=BB=88=E7=A8=BF=E5=8A=A8=E7=94=BB=E4=B8=89=E6=A1=A3=E5=BD=92?= =?UTF-8?q?=E6=A0=B7=E5=BC=A0=E5=90=8E=EF=BC=8C=E7=BE=8E=E6=9C=AF=E6=B8=85?= =?UTF-8?q?=E5=8D=95=20=C2=A72.3=20=E5=9C=A8=E6=A1=88=EF=BC=89=EF=BC=9B?= =?UTF-8?q?=E2=91=A5R1=20=E8=BF=9B=E5=BA=A6=E8=A7=A3=E6=9E=90=E5=8F=A3?= =?UTF-8?q?=E5=BE=84=E4=BF=AE=E8=AE=A2=EF=BC=88=E6=95=B0=E6=8D=AE=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=20v0.4=20=E7=94=A8=E6=88=B7=E4=B8=A4=E6=AE=B5?= =?UTF-8?q?=E6=8B=8D=E6=9D=BF=EF=BC=89=EF=BC=9Aceil=20=E5=90=91=E4=B8=8A?= =?UTF-8?q?=E5=8F=96=E6=95=B4=E4=BF=9D=E8=AF=81=E5=81=8F=E9=9A=BE+?= =?UTF-8?q?=E8=B6=85=E5=87=BA=E6=9C=80=E5=A4=A7=E8=A1=8C=E5=B0=81=E9=A1=B6?= =?UTF-8?q?=E5=8F=96=E6=9C=80=E5=A4=A7=E5=80=BC+=E5=9F=9F=E8=B6=8A?= =?UTF-8?q?=E7=95=8C=20Fatal=20=E8=AF=8A=E6=96=AD=E8=BF=9B=E7=A8=8B?= =?UTF-8?q?=E9=80=80=E5=87=BA=E7=A0=81=201=EF=BC=88config=5Fdb=20=5Fceil?= =?UTF-8?q?=5Fkey/=5Ffatal=20=E9=80=9A=E9=81=93+config=5Ffatal=5Fprobe=20?= =?UTF-8?q?=E6=8E=A2=E9=92=88=E5=AE=9E=E6=B5=8B=E9=80=80=E5=87=BA=E7=A0=81?= =?UTF-8?q?=201+=E5=86=92=E7=83=9F=20ceil/clamp=20=E4=B8=A4=E6=96=AD?= =?UTF-8?q?=E8=A8=80=EF=BC=89+registry=20enemy=5Ftable=20=E8=BF=9B?= =?UTF-8?q?=E5=BA=A6=E9=94=AE=20ceil=20=E5=90=8C=E6=AD=A5=EF=BC=9B?= =?UTF-8?q?=E2=91=A6tune=20=E6=8E=A2=E9=92=88=E6=96=AD=E8=A8=80=E8=A1=A8?= =?UTF-8?q?=E5=80=BC=E9=A9=B1=E5=8A=A8=E4=BF=AE=E5=A4=8D=EF=BC=88=E6=97=A7?= =?UTF-8?q?=E5=AD=97=E9=9D=A2=E9=87=8F=206/50=E3=80=8112/75=20=E9=9A=8F?= =?UTF-8?q?=E6=95=B0=E5=80=BC=E5=8E=8B=E7=BC=A9=E6=89=B9=E5=A4=B1=E6=95=88?= =?UTF-8?q?=EF=BC=8C=E6=94=B9=E8=AF=BB=20db=20=E5=9F=BA=E7=BA=BF=E6=B4=BE?= =?UTF-8?q?=E7=94=9F=EF=BC=89=EF=BC=9B=E2=91=A706=20=E5=BE=85=E5=8A=9E?= =?UTF-8?q?=E5=9B=9B=E6=9D=A1=E7=9B=AE=EF=BC=88=E6=84=8F=E5=9B=BE=E5=BE=BD?= =?UTF-8?q?=E6=A0=87=E5=8D=A0=E4=BD=8D=E5=B0=8F=E4=BF=AE=E5=B7=B2=E8=A7=A3?= =?UTF-8?q?=E5=86=B3/=E5=88=86=E8=BE=A8=E7=8E=87=E6=A1=A3=E4=BD=8D=201280?= =?UTF-8?q?=C3=97800+1920=C3=971080=20=E5=AE=9A=E6=A1=88/=E5=8F=98?= =?UTF-8?q?=E5=BC=82=E5=AF=B9=E5=88=9D=E5=A7=8B=E5=B1=95=E5=BC=80=E5=BD=B1?= =?UTF-8?q?=E5=93=8D=E8=AF=AD=E4=B9=89=E6=8C=82=E8=B5=B7=EF=BC=89+?= =?UTF-8?q?=E7=BE=8E=E6=9C=AF=E8=B5=84=E6=BA=90=E4=BE=9D=E8=B5=96=E6=B8=85?= =?UTF-8?q?=E5=8D=95=E6=88=90=E6=96=87=EF=BC=88=E5=9C=BA=E6=99=AF=E4=B8=80?= =?UTF-8?q?=E4=BA=A7=E7=89=A9=EF=BC=89=EF=BC=9B=E5=86=92=E7=83=9F=20296=20?= =?UTF-8?q?=E5=85=A8=E7=BB=BF+ui/click/layout/tune=20=E5=9B=9B=E6=8E=A2?= =?UTF-8?q?=E9=92=88=20PASS+fatal=20=E6=8E=A2=E9=92=88=E9=80=80=E5=87=BA?= =?UTF-8?q?=E7=A0=81=201=EF=BC=88=E4=BC=A4=E5=AE=B3=E9=81=93=E5=85=B7?= =?UTF-8?q?=E6=89=B9=E3=80=81=E6=8A=A4=E7=94=B2=E9=87=8D=E9=85=8D=E6=89=B9?= =?UTF-8?q?=E3=80=81=E6=95=B0=E5=80=BC=E7=BA=AA=E5=BE=8B=E6=94=B6=E5=8F=A3?= =?UTF-8?q?=E6=89=B9=E5=BD=92=E5=B9=B6=E8=A1=8C=E4=BC=9A=E8=AF=9D=20a2fc39?= =?UTF-8?q?f/cdc226e=20=E5=9C=A8=E5=85=88=E6=8F=90=E4=BA=A4=EF=BC=8C?= =?UTF-8?q?=E8=A1=8C=E5=8A=A8=E7=BB=8F=E6=B5=8E=E6=94=B9=E5=90=8D=20hunks?= =?UTF-8?q?=20=E6=9C=AC=E6=89=B9=E5=85=A8=E9=93=BE=E6=94=B6=E5=8F=A3?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/art/art-assets-checklist.md | 131 ++++++++++++++++++ design/gdd/06-待办清单.md | 48 ++++++- design/gdd/systems/表现与支撑/数据配置.md | 9 +- design/registry/entities.yaml | 2 +- .../core/action_economy.gd | 2 +- .../core/battle_orchestrator.gd | 14 +- .../core/config_db.gd | 106 +++++++------- .../data/schemas/action_economy_table.gd | 10 +- .../data/tables/action_economy.tres | 2 +- .../presentation/hud_presenter.gd | 6 +- .../presentation/main.gd | 26 ++-- .../presentation/playtest_logger.gd | 2 +- .../presentation/ui_assembler.gd | 11 +- .../presentation/views/ap_bar_view.gd | 19 ++- .../presentation/views/battle_log_view.gd | 2 +- .../presentation/views/enemy_card_view.gd | 10 +- .../presentation/views/level_strip_view.gd | 2 +- .../smoke/auto_player.gd | 2 +- .../smoke/config_fatal_probe.gd | 22 +++ .../smoke/smoke_all.gd | 4 + .../smoke/ui_click_probe.gd | 2 +- .../smoke/ui_layout_probe.gd | 4 +- .../smoke/ui_probe.gd | 6 +- .../smoke/ui_tune_probe.gd | 19 ++- 24 files changed, 346 insertions(+), 115 deletions(-) create mode 100644 design/art/art-assets-checklist.md create mode 100644 prototypes/dungeon-sweeper-vertical-slice/smoke/config_fatal_probe.gd diff --git a/design/art/art-assets-checklist.md b/design/art/art-assets-checklist.md new file mode 100644 index 00000000..55888dba --- /dev/null +++ b/design/art/art-assets-checklist.md @@ -0,0 +1,131 @@ +# 美术资源依赖清单(Art Assets Checklist) + +> **性质**:表现层升级批与战斗表现批的生产侧清单——把散在战斗表现 / HUD框架 / 关卡节点地图 / 敌人系统里的资产需求收拢成一张可执行的工单视图。**规范效力归各 owner 文档**(战斗表现、美术圣经、04-美术音频方向),本文只补「生产视角的需求规格」,冲突时以 owner 文档为准。 +> **建立**:2026-09-18 场景一(战斗画面呈现)讨论产物。意图动画三档为用户当日拍板,文档改写随实施批。 + +## 0. 生产通道与通用规格 + +**两条生产通道**: + +- ✦ **ComfyUI(用户自跑)**:角色、插画级素材。每个敌种/主角建议出 **2–3 版候选**供裁决,不直接出终稿。 +- ⚙ **脚本管线(代理出)**:图标、格面、样张类精度资产(先例:`samples/generate_hero_sample.py` 站姿样张)。 + +**风格宪法**:全部资产受 [art-bible](art-bible.md) 约束,出图前先读三节—— + +| 条款 | 对出图的具体要求 | +|---|---| +| 一句话视觉规则 | 明快的地牢,轻快的扫荡,仔细的推理——**明亮像素幻想,全年龄,非暗黑**,角色 2~3 头身 | +| 原则一「预告上格」 | 格内绘制优先级 **数字 > 小人 > 旗 > 徽章 > 变异纹理 > 底色**;徽章不得遮挡数字字形区 | +| 原则二「明亮为皮,对比为骨」 | 高亮度暖倾向;格底相对亮度 L≥0.30、HSV 饱和 ≤0.40;数字墨 L≤0.027(对比 ≥4.5:1) | +| 原则三「一砖一世界」 | 变体优先换色板不加新资产;危险状态必须**形状+颜色双通道**(色弱灰度可辨) | +| 剪影哲学(§3.1) | **所有角色自带轮廓通道:亮色描边+落影底盘**,识别不依赖背景;五类原型见各条目 | +| 语义色规则(§4.2) | 红只说危险(旗朱 `#C63D22` / 危险红 `#A8321F`)、金只说收益(`#F0A828`)、蓝只说己方(`#2E6E8E`)、**紫赤 `#8F2F6A` 为 Boss 独占,HSV 色相 300°–345° 全场禁入带** | +| UI 形状语法(§3.3) | 半拟物:面板=石板圆角矩形(与地砖同圆角率),按钮=可按的砖,仪式确认=圆形蜡封;数据区直角细线无纹理 | + +**参考图**:`design/art/references/`(Into the Breach / Moonlighter / Peglin 等九款×2 张)。 + +--- + +## 1. P0 —— 样张(一切终稿资产的硬前置) + +### 1.1 格子样张(砖面形态定稿闸)⚙ + +- **要什么**:一张按 art-bible §1.4 参数画成的格子风格试验图。内容清单(缺项即无效):未开格(覆尘完整砖)、已开格(碎裂裸露,含 0 与 1–8 全档数字)、雷格(被惊动的巢穴纹)、旗、小人两态(独立占格+与数字同格)、biome 色板变体组(四槽换色至少两套)。 +- **风格/规格**:色板 token 直接取 §4.1(掩尘 `#C9AF83`、裸土 `#B39A78`、数字墨三档、旗朱、危险红、奖励金、小人蓝);砖面=圆角方形衍生,只以完整度叙事。 +- **验收**:§1.4 四项按序判定(数字可读性 WCAG ≥4.5:1 硬指标 → 格态三形灰度可辨 → 焦点预算 → biome 换皮抽测)。**通过 = 砖面形态与色板冻结**。 +- **现状**:验收标准已成文,测试未跑。 + +### 1.2 战斗一屏样张(双舞台,2–3 版候选)⚙格面骨架 + ✦角色剪影候选 + +- **要什么**:按双舞台布局(战斗画面与扫雷图同屏常驻)出的整屏试验图。要锁的四件事:**角色剪影方向 / 动画方案 / 战斗空间 / 敌人层绘制优先级插入位置**;顺带裁决**敌人卡形态:卡片式 vs 站桩式(无框立绘+底座)**——两版候选各出一版。 +- **风格/规格**:敌人剪影按五类原型出(见 2.2);战斗舞台背景=明亮地窟,紫赤仅 Boss。 +- **验收**:战斗表现「一屏样张的验收标准」(已成文)。 +- **联动**:样张未过,**不启动任何终稿资产与动画设计**(文档明文的排期硬前置);HUD框架「格边长两档定值」条款将由表现层升级批按分辨率档位计算口径改写(2026-09-18 用户拍板:固定几档分辨率、窗口不可调、格子量增加则格边长相应变小;**候选档位 1280×800 + 1920×1080 待拍板**)。 + +--- + +## 2. P1 —— 角色级(样张通过后开工,ComfyUI) + +### 2.1 主角战斗小人 + +- **要什么**:待机 / 攻击 / 受击 三态起步(移动态样张后定);透明底 PNG。 +- **风格/规格**:小人原型——**窄、直立、对称,轮廓凹凸 ≤2**;2~3 头身两档下剪影均成立;主色小人蓝 `#2E6E8E`;格内占位最窄、不吞数字位。 +- **现状**:站姿样张已有(`samples/hero-standing-pose.png`,脚本管线产物),战斗三态未出。 + +### 2.2 六敌种形象 + +| 敌种 | 剪影原型(§3.1) | 风格要点 | +|---|---|---| +| 地精 goblin | 普通怪:近圆墩形,宽≥高,零锐角 | Peglin 式胖体量,先读轮廓后读颜色 | +| 熊地精 bugbear | 普通怪或精英档(加一处垂直延伸:角/冠/棘) | 第一关显示名「熊地精」 | +| 食人魔酋长 ogre_chief | 精英档:普通怪形+恰一处垂直延伸,底盘不变 | 重甲语义可走色板不作形状独占 | +| 巨蜘蛛 giant_spider | 普通怪(多足轮廓注意凹凸计数控制) | 群行 [2,3],同屏最多 3 只,剪影需小体量可辨 | +| 兄弟精(bugbear 变体) | 与熊地精**同剪影换色板**(L2 幽暗地域部族) | 换色不换形——biome 换皮原则的角色侧应用 | +| 蛛化卓尔精灵 drider(Boss) | **敌类唯一允许锐角与不对称**,体量最大 | 紫赤 `#8F2F6A` 色相独占;「形状独占+色相独占」双通道 | + +- **通用规格**:亮色描边+落影底盘轮廓通道硬规则;透明底 PNG;每敌种 2–3 版候选。 +- **联动**:显示名以 registry / 敌人表为准(兄弟精=熊地精复用,2026-09-17 用户拍板)。 + +### 2.3 意图准备动画三档(2026-09-18 用户拍板) + +- **要什么**:用**动作动画**替代文字徽标预告敌方下一拍,三档: + 1. **普通攻击 = 常态准备动画**——待机姿态本身就是预示,不另做动作; + 2. **攻击技能 = 特殊准备动画**——放光、高举武器等「摇号感」动作; + 3. **重击/硬化类 = 蓄力动作或防御姿态**。 +- **风格/规格**:三档在**灰度/剪影层也可辨**(双通道纪律);每档动画时长与结算节拍对齐(预告时序 = 下一拍、挖掘期全程可见)。 +- **联动**:HUD框架「意图徽标不预告技能名与数值」条款由本拍板承接为动画承载(文字与技能名彻底退出);战斗表现「特殊技两型动画」输入清单按三档改写;映射按 enemy_id 全局恒定(跨 run 禁重掷)。 +- **现状**:占位符号已落地(2026-09-18:普通攻击「—」/ ⚔ / ✦ / 重击类「蓄力」,技能名退出徽标);终稿动画待样张后开工。 + +--- + +## 3. P2 —— 图标与格面(脚本管线,可与样张并行) + +### 3.1 格面资源徽章 14 枚(七型 × 正负)⚙ + +- **要什么**:增伤 / 护甲 / 恢复 / 爆裂 / 削弱 / 穿刺 / 魔法,各正负一枚(负向:反噬 / 伏击加层 / 恢复倒扣 / 失衡 / 削弱自伤 / 穿刺陷阱 / 魔法反噬)。 +- **风格/规格**:图标键契约 §4.6(冻结);**极性色相槽只有两族——有利兑付=金族、有害兑付=红族**,不新增语义色;徽章=附属小图标,非完整凸形(不构成第二焦点),栈位在旗之下、变异纹理之上。 +- **现状**:程序化占位徽标(文本/符号),够试玩不够观感。 + +### 3.2 关卡节点图图标组 ⚙/✦ + +- **要什么**:四类节点图标(战斗 / 奖励 / 恢复 / Boss)+ 图版行进器形象(棋子)。 +- **风格/规格**:容器走石板圆角语法;Boss 节点可用紫赤;奖励节点可用奖励金;恢复节点绿=自然生机(零逻辑语义,仅氛围);行进器=小人同族蓝。 +- **联动**:正式节点图 UI 设计未成文(登记待办:排魔法道具内容批后与美术批同期待拍板);HUD框架义务清单漏「关卡节点地图」行,随批补。 + +### 3.3 雷 / 旗 / 数字格面像素化 ⚙ + +- **要什么**:格子样张通过后按冻结参数出全套砖面资产(含 biome 换色板组)。 +- **前置**:§1.1 格子样张通过。 +- **现状**:程序化绘制,可读性达标(对比硬指标已是现行实现依据),观感待升级。 + +--- + +## 4. P3 —— 演出类(战斗打磨项,样张后排期) + +| 资产 | 需求 | 风格/规格要点 | +|---|---|---| +| 受击反馈 / 死亡表现 / 顺位标记 | 敌人系统登记的资产需求**下限三类** | 死亡=血条归零后**灰化+碎裂退场,禁止瞬间消失**;顺位标记需在敌多时可读 | +| 特殊技动画终稿 | 并入 §2.3 三档意图动画,不另起体系 | 攻/防两型旧口径由三档替代 | +| 连锁坍塌显形演出 | 连锁展开触发时的连锁演出 | 与音频方向同批配对登记;「坍塌显形」=连锁展开的玩家可见名 | +| 弹道/退场等动画密度 | 战斗表现输入清单,方案未定 | 样张锁动画方案后启动 | + +--- + +## 5. 明确不在本清单 + +- **音乐音效**:文档定稿「MVP 占位即可,核心循环验证后再定」。 +- **地形变异专属纹理**:变异表为空,纹带种类待地形变异批定夺(形状通道规则已备)。 +- **精英/Boss 编码位相关新资产**:随关卡节点地图改键批裁决后再议。 + +## 6. 执行顺序(依赖链) + +``` +格子样张(1.1) ──┬─> 雷旗数字格面(3.3) + │ +战斗一屏样张(1.2) ──> 角色终稿(2.1/2.2) ──> 意图动画三档(2.3) ──> 演出类(P3) + │ + └─> 敌人卡形态裁决(卡片式 vs 站桩式,用户看图拍板) +徽章(3.1)/节点图(3.2) —— 独立线,可随时插队 +``` + +**用户侧待拍板小项**(场景一遗留):① ~~占位期意图符号小修~~——已落地(2026-09-18,技能名退出徽标);② ~~分辨率档位~~——已定案(1280×800 + 1920×1080 两档,后续调整走修订)。 diff --git a/design/gdd/06-待办清单.md b/design/gdd/06-待办清单.md index fd5ff29f..2f980527 100644 --- a/design/gdd/06-待办清单.md +++ b/design/gdd/06-待办清单.md @@ -58,6 +58,14 @@ **状态**:待办 +#### 变异对初始展开(开局免费点开区域)的影响语义 + +**现状**:2026-09-18 用户拍板:雾格移除后变异体系暂缓,本项挂起随首批变异定稿一并迭代。两件挂账——① 数据层残留一处「带变异的 0 格不参与初始展开传播」分支(board_data.gd,变异表为空从未触发),与排雷玩法规则层的展开实现口径分裂(规则层不看变异);② 变异格该不该影响开局免费展开、影响规则是什么,未定稿。届时二选一:删死分支对齐「零特判」纪律,或在文档登记该行为为正式语义 + +**归属**:地形变异首批类型定稿批 + +**状态**:待办 + ### 魔法道具 #### 显影尘效果重定 @@ -336,7 +344,7 @@ #### 雷格与安全格的资源分布差异化(Alpha 批) -**现状**:现行雷安同池——安全格与雷格从同一类型池、同一权重分布摇定类型身份(结构冻结项,地图战斗资源 v0.4 七项之一)。用户 2026-09-17 拍板要做这件差异化的事,定位 **Alpha 版本**上线;垂直切片期先「把游戏做完整」不动结构(读数基线刚被护甲换装+技能池批与数值压缩批归零,验收重测与真人试玩未跑)。调研底座已交付:[proposal-reward-cell-differentiation-2026-09-17.md](../research/proposal-reward-cell-differentiation-2026-09-17.md)——三条候选路径(双池切分〔旧提案 mine_cell 有成案可翻〕/ 单池双列 weight_mine / 修饰位摇后过滤);差异化必破「雷安同池」冻结项、走 GDD 修订流程(用户已明示冻结约束执行侧不约束 owner,允许下破冻结即合法修订);纯权重调整零破冻结但有 9 处连带同步位(含 2 处 v0.7 漏网过期注释实证);外部无「同池不同权」中间态先例 +**现状**:现行雷安同池——安全格与雷格从同一类型池、同一权重分布摇定类型身份(结构冻结项,地图战斗资源 v0.4 七项之一)。用户 2026-09-17 拍板要做这件差异化的事,定位 **Alpha 版本**上线;垂直切片期先「把游戏做完整」不动结构(读数基线刚被护甲换装+技能池批与数值压缩批归零,验收重测与真人试玩未跑)。调研底座已交付:[proposal-reward-cell-differentiation-2026-09-17.md](../research/proposal-reward-cell-differentiation-2026-09-17.md)——三条候选路径(双池切分〔旧提案 mine_cell 有成案可翻〕/ 单池双列 weight_mine / 修饰位摇后过滤);差异化必破「雷安同池」冻结项、走 GDD 修订流程(用户已明示冻结约束执行侧不约束 owner,允许下破冻结即合法修订);纯权重调整零破冻结但有 9 处连带同步位(含 2 处 v0.7 漏网过期注释实证);外部无「同池不同权」中间态先例。关联条目:见本节「冒险前资源配比预设(构筑提案)」(2026-09-18 登记的玩家侧分布控制权提案——同为类型分布语义的结构修订,立项时评估合并) **前置**:① 验收批读数重测与真人试玩先跑——差异化核心未知数(玩家是否利用类型分布先验绕雷/追格)只有真人数据能回答;② 拍掉调研报告第 8 章八个待决项(差异化区分的轴 / 雷·安当量分化后的行动经济条款③⑤与恒等式对账窗口 / heal 权重整数档位 / tier_min_buff 口径 / 同步流程口径扩充等)后出定稿提案 @@ -344,6 +352,28 @@ **状态**:待办(方向已拍板 2026-09-17;实施窗口 = Alpha) +#### 冒险前资源配比预设(构筑提案) + +**现状**:2026-09-18 用户提案,方向评估通过,登记在案。核心想法 = 玩家在**开始冒险前**预设地图战斗资源的类型配比——预设的是**分布**(七型在格面上的权重占比),不是落位:装板摇号仍按专属随机流摇出各格类型身份,玩家定分布、摇号定落位,无固定出招剧本(与既定随机性立场相容)。两分法:① **基础资源配比**——玩家对七型格面池(attack 增伤 / defense 护甲 / heal 回血 / blast 群伤 / weaken 削弱 / pierce 穿刺 / magic 魔法)的正向行权重做配比,经覆盖值通道在开局注入,摇号按覆盖后的分布落位;② **关卡特殊资源**——不同关卡(board_biome 图主题)的地图携带配比池之外的特殊资源与差异化分布。设计红利:雷安同池 + 极性矩阵使配比自带代价轴——调高某型权重,安全格正向与雷格负向同步变多,配比 =「赌自己挖掘少失误 + 赌关卡敌人构成怕什么」的双重下注,不是免费午餐,无需另发明平衡机制。架构红利:① 的注入通道 = [元进度](systems/冒险循环/元进度.md) v1.0 刚定稿的「冒险开始参数面 + 覆盖值通道」(追加/替换集合操作已登记),零新机制;② 正是[棋盘生成](systems/排雷循环/棋盘生成.md) board_biome 上下文与[地图战斗资源](systems/战斗循环/地图战斗资源.md)`clear_<图类型>` 差异化插槽、P1 功能道具渠道等预留位的启用场景。 + +**影响面(按循环)**: +- 排雷循环:[行动经济](systems/排雷循环/行动经济.md)——经济契约对账以资源行值为原材料,类型频次构成随配比漂移,对账窗口须按配比包络复算;[棋盘生成](systems/排雷循环/棋盘生成.md)——生成管线零改动(资源身份摇号不在其域),board_biome 词表定稿是关卡特殊资源的前置;[排雷玩法](systems/排雷循环/排雷玩法.md)——挖掘/标旗动作决定极性、承载配比的风险轴,机制零改动;[魔法道具](systems/排雷循环/魔法道具.md)——相邻:预读类道具读取身份查询面,配比改变分布先验后「资源预读与推理的边界」读感联动,不做机制耦合。 +- 战斗循环:[地图战斗资源](systems/战斗循环/地图战斗资源.md)——主战场:候选集合权重来源从纯表值扩为「表值 × 玩家覆盖」;基础配比是整体平移分布,「雷安同池」(同池同分布)语义不破,不动结构冻结;[战斗结算](systems/战斗循环/战斗结算.md)——唯一调用方:摇号入口需受理开局注入的权重覆盖,兑付编排零改动;[增益状态](systems/战斗循环/增益状态.md)——零改动(percent 行效果引用不变,配比只改触发频率不改状态语义);[敌人系统](systems/战斗循环/敌人系统.md)——间接:各关敌种构成是配比预测的对象,混编遭遇行集的走向影响配比博弈读数。 +- 冒险循环:[元进度](systems/冒险循环/元进度.md)——配比能力的家:可用类型 / 预算点数 / 预设槽位走营地柜台解锁,配比预设 = 覆盖值通道的又一生产者;[冒险结构](systems/冒险循环/冒险结构.md)——冒险开始参数面扩容,配比覆盖域的字段语义待裁;[难度曲线与Boss](systems/冒险循环/难度曲线与Boss.md)——杠杆主权转移:设计者从「定权重」改为「定预算包络、玩家在包络内分配」,通关率验收带随配比漂移,切片与验收期必须锁默认权重;[关卡节点地图](systems/冒险循环/关卡节点地图.md)——关卡特殊资源与节点奖励(功能道具渠道)的分工须一次说清:特殊资源走图内格面池差异化,功能道具走节点奖励,两渠道不混。 +- 表现与支撑:[HUD 框架](systems/表现与支撑/HUD框架.md)——新增构筑界面(冒险前的配比设置面)与配比效果的局内可读呈现;[教学引导](systems/表现与支撑/教学引导.md)——新增「读敌配比」教学口径;[数据配置](systems/表现与支撑/数据配置.md)——配比预设存档域与预算参数表建账,registry 新条目;[存档序列化](systems/表现与支撑/存档序列化.md)——预设档契约域随批登记(与元进度存档四件同批)。 + +**依赖(本提案依赖谁)**:① 冒险开始参数面 + 覆盖值通道([元进度](systems/冒险循环/元进度.md) v1.0 已定稿口径,现成);② 七型候选集合与权重语义([地图战斗资源](systems/战斗循环/地图战斗资源.md) v0.10 现行结构);③ board_biome 词表定稿([棋盘生成](systems/排雷循环/棋盘生成.md),关卡特殊资源的前置);④ 数据配置表承载与存档域(预设持久化);⑤ 元进度解锁状态(可用配比容量)。 + +**被依赖(谁会依赖本提案)**:HUD 构筑界面呈现;教学新口径;难度曲线与Boss 验收判读口径(配比包络下的通关率带复算,换算率表强条目纪律同类);行动经济对账窗口;registry 与架构概念图的结构登记(新节点落地时 archify 图须重交付)。 + +**开放问题(立项前裁决)**:① **预算机制**——自由滑条必出支配策略(全拉穿刺 + 魔法〔必中克制直伤〕大概率唯一解,构筑空间反死),候选 = 点数总预算 / 每型上限 / 配比槽位本身当解锁内容——最大设计题;② **杠杆主权切分**——切片与验收期锁默认权重(测量保护),正式期「设计者定包络、玩家分配」的参数归属;③ **与永久强化的分工**——配比容量(可用类型/点数/槽位)映射到营地三柜台哪几个;④ **基础配比的注入语义**——权重覆盖走数值覆盖还是集合操作(追加/替换),参数面字段裁决;⑤ **关卡特殊资源与功能道具渠道的边界**——图内格面池差异化与节点奖励不混池的口径。 + +**前置**:① 验收批读数重测与真人试玩先跑——配比博弈的核心未知数(玩家配比是否收敛到唯一解、分布先验怎么被利用)只有真人数据能回答(与「雷格与安全格的资源分布差异化(Alpha 批)」同前置);② 当前批序列走完:本批审批 → 验收重测 → 曲线调参 → 魔法道具内容批;③ 立项时与「雷格与安全格的资源分布差异化(Alpha 批)」合并评估——同为类型分布语义的结构修订、同一冻结面,一批做省一次修订流程。 + +**归属**:[地图战斗资源](systems/战斗循环/地图战斗资源.md)修订批(基础配比的权重覆盖语义 + GDD 修订)与[元进度](systems/冒险循环/元进度.md)(解锁容量与注入生产者)共裁;呈现面归[HUD 框架](systems/表现与支撑/HUD框架.md)与[教学引导](systems/表现与支撑/教学引导.md)随批。 + +**状态**:待办(2026-09-18 用户提案、方向评估通过;实施窗口 = Alpha,与「雷格与安全格的资源分布差异化(Alpha 批)」合并立项) + ### 战斗表现 #### 敌人卡形态 @@ -576,6 +606,22 @@ **状态**:待办 +#### 意图徽标占位期符号小修 + +**现状**:切片意图徽标直接显示技能名(剧透强度);2026-09-18 用户拍板终稿方向 = 动画三档承载意图预告(常态=普攻预示 / 攻击技=发光举武器 / 重击硬化=蓄力或防御姿态,见 [美术资源依赖清单](../../art/art-assets-checklist.md) §2.3),终稿动画须样张后开工——占位期是否先把文字换成 ⚔/✦/蓄力三个符号堵住剧透,待拍板 + +**归属**:表现层升级批(占位期小修已先行落地) + +**状态**:已解决(2026-09-18 占位符号落地:普通攻击「—」/ 伤害技 ⚔ / 增益技 ✦ / 重击类「蓄力」三档占位,技能名退出徽标;终稿三档动画仍归表现层升级批,见 [美术资源依赖清单](../../art/art-assets-checklist.md) §2.3) + +#### 分辨率档位与棋盘格边长口径 + +**现状**:2026-09-18 用户拍板:格边长不取固定两档,按分辨率档位计算——最终呈现固定几个分辨率、窗口不可调;棋盘格子量增加则界面框架不变、格边长相应变小。[HUD框架](systems/表现与支撑/HUD框架.md)现行「两档定值 36/45px」冻结条款待按此修订;分辨率档位已定案(2026-09-18 用户拍板:**1280×800 + 1920×1080 两档**,后续调整走修订) + +**归属**:表现层升级批(HUD框架 修订随批) + +**状态**:待办(档位已定案,条款修订随表现层升级批) + ### 数据配置 #### 变异表旧字段集是否沿用 diff --git a/design/gdd/systems/表现与支撑/数据配置.md b/design/gdd/systems/表现与支撑/数据配置.md index a918932e..bca09cea 100644 --- a/design/gdd/systems/表现与支撑/数据配置.md +++ b/design/gdd/systems/表现与支撑/数据配置.md @@ -119,7 +119,7 @@ ### 进度坐标查行:跟着冒险进度成长的表带两维坐标 -跟着冒险进度成长的表(难度、生成参数、敌人配置)每行带(关卡序号 × 节点深度)两维坐标——难度曲线的雕刻轴从第一天就留在表结构层,而不是散落各处的魔法数字。解析规则两条:其一,进度坐标就近取用,无精确行时取不超过请求坐标的最近已有行并留一条警告(警告按"哪张表、缺哪维、停在哪个键"去重,是开发期异常信号而非审计工具);其二,实体键精确匹配,那一行没有这个名字就是悬空引用,直接致命中止——进度可以就近近似,"是哪种敌人"不能近似,身份近似会静默改变遭遇构成。 +跟着冒险进度成长的表(难度、生成参数、敌人配置)每行带(关卡序号 × 节点深度)两维坐标——难度曲线的雕刻轴从第一天就留在表结构层,而不是散落各处的魔法数字。解析规则两条:其一,进度坐标向上取整(2026-09-18 用户拍板修订,原「就近向下」退役)——无精确行时取**不低于**请求坐标的最近已有行并留一条警告(警告按"哪张表、缺哪维、停在哪个键"去重,是开发期异常信号而非审计工具);取整方向的理由是**保证偏难**:宁可在内容缺口上高估难度让工具读数保守,不可静默降难掩盖断档;请求坐标低于合法域(关卡序号 <1、节点深度 <0)不属于取整,走运行期致命;请求高于最大已有行时**取最大已有行封顶**(2026-09-18 追加拍板:可取范围内向上取整,超出最大值就用最大值,封顶同样留警告)。其二,实体键精确匹配,那一行没有这个名字就是悬空引用,直接致命中止——进度可以向上近似,"是哪种敌人"不能近似,身份近似会静默改变遭遇构成。 三条配套边界。每关必须含深度 0 的锚行(关卡入口行),缺失即加载期致命,防止入口请求静默落到深处行造成难度突刺。请求越域(关卡序号小于 1 或深度为负)在进入解析前致命中止。没有兜底行、没有外推:超过表范围的成长外推归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)的公式职责,本系统只解析到最近已有行——解析永不发明数据。 @@ -187,7 +187,7 @@ | 表路径清单 | data/tables/ 固定(调试注入根除外) | 冻结:散装路径破坏单点访问 | | 表结构版本常量 `SCHEMA_VERSION` | 实现期定(MVP = 1,provisional) | 全部表结构携带版本字段;高于当前支持的版本拒读,比较逻辑归[存档序列化](存档序列化.md) | | 随机流清单 | 封闭三流(board_gen / reward_roll / encounter) | 冻结清单;增流 = 改代码 + 评审,有意设卡 | -| 进度坐标解析策略 | 就近取已有行 + 警告;无兜底行、无外推 | 冻结:引入静默外推会掩盖难度断档 | +| 进度坐标解析策略 | 可取范围内向上取整 + 警告(保证偏难);超出最大行封顶取最大值 + 警告;域越界致命(2026-09-18 两段拍板,原「就近向下」退役) | 冻结:静默降难会掩盖难度断档 | | 入口锚行校验 | 写死开 | 非调参项;重议走文档修订流程 | | 调试覆盖开关 | 仅调试构建,默认关 | 正式构建结构性不存在;覆盖值永不回写表文件 | @@ -198,8 +198,8 @@ - 致命真的会死:任一致命夹具经正式通道加载,进程非 0 退出,而非仅"校验器能发现"。 - 黄金向量:随机派生纯函数对登记向量逐个复算相等;同输入恒同输出;同流同序号重复调用恒等。 - 计数器纪律:连续取号跨模拟关卡切换,计数器单调不重置;热重载成败都不改变主种子与计数器。 -- 就近解析:请求无精确行时返回不超过请求坐标的最近行并留一条警告,同类警告去重;精确命中零警告。 -- 越域与未播种:坐标越域在进入解析前致命中止;未设主种子就取随机数致命中止,无默认种子兜底。 +- 向上取整解析:请求无精确行时返回**不低于**请求坐标的最近行并留一条警告;请求超出最大已有行时封顶取最大行并留警告;同类警告去重;精确命中零警告。 +- 越域与未播种:坐标低于合法域(关卡序号 <1、节点深度 <0)在进入解析前致命中止(Fatal 级诊断 + 进程退出码 1,切片探针 `smoke/config_fatal_probe.gd` 验证);高于最大已有行走封顶取最大行(警告留痕,不致命);未设主种子就取随机数致命中止,无默认种子兜底。 - 热重载门:战斗中重载被拒绝且读数不变、不发通知;门未置位时新值可读且通知恰一次;校验失败保留旧表停留就绪态。 - 版本常量在位:表结构版本常量存在且全部表结构携带版本字段(高版本拒读判定归存档序列化)。 - 零字面量、零第二路径:静态扫描加人工复核确认玩法代码无数值字面量、无读取口之外的表加载、无全局随机函数调用。 @@ -217,3 +217,4 @@ | v0.1 | 2026-09-15 | 自归档 GDD 压缩改写 | | v0.2 | 2026-09-15 | 雾格残留清理:关闭「待拍板」的棋盘生成参数表雾字段清理登记(fog_region_count / fog_target_ratio 两字段、雾占比绝对上限与雾域跨字段校验)——字段与校验随雾格移除同批删除,registry `board_gen_table` 条目同步修订。表清单与校验纪律不变 | | v0.3 | 2026-09-17 | 代币→钥石统一拍板随批措辞清偿:表清单冒险表行「代币记账」改「钥石入账」;registry adventure_table 同批改键 token_per_level→keystone_per_level。表清单与校验纪律不变 | +| v0.4 | 2026-09-18 | **进度坐标解析口径修订(用户拍板,场景三讨论产物)**:① 取整方向「就近向下」→「**向上取整**」——无精确行取不低于请求坐标的最近行,理由 = 保证偏难(宁高估难度让工具读数保守,不静默降难掩盖断档);② 越域致命补齐——低于合法域(关卡序号 <1、节点深度 <0)走运行期致命:Fatal 级诊断 + 进程退出码 1,与装载期致命纪律并轨;**同日追加拍板**:高于最大已有行不致命,封顶取最大已有行并留警告(用户裁定「可取范围内向上取整,超过了最大值就用最大值」);③ registry enemy_table 条目「进度键 floor」随批改 ceil;切片 config_db._resolve_progress 同批落地(ceil 选择器 + _fatal 通道 + `smoke/config_fatal_probe.gd` 探针 + 冒烟 ceil 断言) | diff --git a/design/registry/entities.yaml b/design/registry/entities.yaml index 3091cea2..c38d67c2 100644 --- a/design/registry/entities.yaml +++ b/design/registry/entities.yaml @@ -248,7 +248,7 @@ entities: type: schema_data owner: design/gdd/systems/战斗循环/敌人系统.md attributes: - table: EnemyTable(数据配置 承载,键 (level_index, node_depth, enemy_id);敌人系统 回填 owner,2026-09-09 定稿回填——进度键 floor(数据配置 R1)+ 实体键精确匹配) + table: EnemyTable(数据配置 承载,键 (level_index, node_depth, enemy_id);敌人系统 回填 owner,2026-09-09 定稿回填——进度键 ceil 向上取整保证偏难、超出最大行封顶取最大值(数据配置 R1,2026-09-18 用户两段拍板修订,原 floor 退役)+ 实体键精确匹配) fields: enemy_id: String(行集内主键;意图视觉映射键——视觉推测型 HUD框架×4①) display_name: String(本地化归 设置与本地化) diff --git a/prototypes/dungeon-sweeper-vertical-slice/core/action_economy.gd b/prototypes/dungeon-sweeper-vertical-slice/core/action_economy.gd index 38cf588f..269dfb48 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/core/action_economy.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/core/action_economy.gd @@ -100,7 +100,7 @@ func request_stop() -> bool: # 轮内刷新(2026-09-12 方案 A 用户拍板;等待期通道 2026-09-15 用户拍板整体移除):唯一入口 = # Active 相位——仅扣 RAP,相位与 AP 均不动。刷新不打断轮次、不触发结算;换图编排归 战斗结算 # (retire + install 立即执行,玩家同轮继续挖新图)。决策主体 = 玩家(经 HUD框架发起)纪律不变。 -func request_refresh_active() -> bool: +func request_refresh() -> bool: if phase != PHASE_ACTIVE: _reject("phase") return false diff --git a/prototypes/dungeon-sweeper-vertical-slice/core/battle_orchestrator.gd b/prototypes/dungeon-sweeper-vertical-slice/core/battle_orchestrator.gd index 33e76cf5..6ed9386b 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/core/battle_orchestrator.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/core/battle_orchestrator.gd @@ -51,7 +51,7 @@ var _facts_reveals: Array = [] var _facts_flags: Array = [] var _facts_path := "" var _settling := false -var _refresh_used_this_round := 0 # 每轮刷新计数(Active 相位合计,兜底上限 = ActionEconomyTable.refresh_max_per_round) +var _refresh_used_this_round := 0 # 每轮刷新计数(Active 相位合计,兜底上限 = ActionEconomyTable.refresh_cap_per_round) var _board_seq := 0 # 板序号(本战斗第几张板——快照归因用) var _board_from_midround_refresh := false # 装板 provenance:轮内刷新换来的图 = true(清图奖励守卫——刷新图不发清图奖励) var _flag_consumed := {} # 兑付资格以雷格实例为键(板级;换图重置) @@ -688,9 +688,9 @@ func _exit_distribution() -> void: # 呈现层查询面(HUD 刷新按钮可用性)——挖掘期唯一通道 -func can_request_refresh() -> bool: +func can_refresh() -> bool: return battle_result == "" \ - and _refresh_used_this_round < int(db.action_economy.refresh_max_per_round) \ + and _refresh_used_this_round < int(db.action_economy.refresh_cap_per_round) \ and action_economy.phase == action_economy.PHASE_ACTIVE \ and action_economy.rap >= int(db.action_economy.rap_refresh_cost) @@ -699,9 +699,9 @@ func can_request_refresh() -> bool: # 挖掘期唯一通道(2026-09-12 方案 A 拍板 + 2026-09-15 等待期通道整体移除):Active 相位立即换图 # 继续挖(RAP -8;AP 保留、相位不动、不触发结算);非挖掘期请求一律拒绝——非清图轮恒同图。 func player_request_refresh() -> bool: - if not can_request_refresh(): + if not can_refresh(): return false - if not action_economy.request_refresh_active(): + if not action_economy.request_refresh(): return false _refresh_used_this_round += 1 _reward_economy.retire_mapping() # 旧图批次退役(地图战斗资源 Ready→retired;E6 新批重置前提) @@ -725,7 +725,7 @@ func _battle_over(result: String) -> void: # ---- 呈现状态面(E1 封装收口 2026-09-13):呈现层 / headless 自动玩家的唯一读取出口—— # 协作对象(_enemy_system/_status_effects/_reward_economy)私有化后,跨层读取一律经本快照或下列命名面(diagnostic_snapshot 同款模式, # 禁再链式穿透 settlement.enemy_system.hero 一类路径)。载荷 schema 成文(code-conventions 2.1): -# round / battle_result / phase / ap / rap / can_request_refresh / hero{hp,max_hp,atk,alive,armor_class} / +# round / battle_result / phase / ap / rap / can_refresh / hero{hp,max_hp,atk,alive,armor_class} / # enemies[{order,display_name,hp,max_hp,enemy_id,armor_type,armor_class,pending_skill,pending_type,pending_name,effects[],ambush_layers,alive}] / # (敌人技能池批批 2 定稿:beat/ring_size/ring_index 拆除 → pending_* 三字段 + 敌方增益 effects 投影;池与权重不公开) # ambush_layers / hero_effects[](增益状态 query_active 投影) @@ -769,7 +769,7 @@ func presentation_snapshot() -> Dictionary: "battle_result": battle_result, "phase": String(action_economy.phase), "ap": int(action_economy.ap), "rap": int(action_economy.rap), - "can_request_refresh": can_request_refresh(), + "can_refresh": can_refresh(), "hero": hero, "enemies": enemies, "ambush_layers": ambush, diff --git a/prototypes/dungeon-sweeper-vertical-slice/core/config_db.gd b/prototypes/dungeon-sweeper-vertical-slice/core/config_db.gd index c01a57c4..fd82e6d5 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/core/config_db.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/core/config_db.gd @@ -42,10 +42,21 @@ var _reload_gate := false # 数据配置 EC8——战斗结 func push_diag(severity: String, category: String, message: String) -> void: _diagnostics.append({"severity": severity, "category": category, "message": message}) - if severity == "error": - push_error("[ConfigDB/%s] %s" % [category, message]) - elif severity == "warning": + if severity == "warning": push_warning("[ConfigDB/%s] %s" % [category, message]) + else: + push_error("[ConfigDB/%s] %s" % [category, message]) + + +# 运行期数据致命(2026-09-18 用户拍板:越域记 Fatal 级日志后立即退出——数据配置通道 fail-fast 纪律补齐到查询期) +func _fatal(category: String, message: String) -> void: + push_diag("fatal", category, message) + push_error("[ConfigDB/%s] FATAL: %s —— 数据配置致命,进程退出" % [category, message]) + var main_loop := Engine.get_main_loop() + if main_loop is SceneTree: + main_loop.quit(1) # 帧末退出,退出码 1——调用方须立即 return,勿再消费返回值 + else: + OS.crash("ConfigDB/%s fatal: %s" % [category, message]) func reset_diagnostics() -> void: @@ -188,7 +199,7 @@ func validate_skill_pool_links(skill_table, enemy_table) -> Array: return errs -# ---- R1 难度行解析(floor + warning 四元组去重;请求越域致命)---- +# ---- R1 难度行解析(2026-09-18 用户拍板修订:ceil 向上取整保证偏难;域越界与内容缺口 Fatal 退出)---- func resolve_board_gen(L: int, D: int) -> Dictionary: return _resolve_progress(board_gen.rows, L, D, "board_gen") @@ -213,66 +224,65 @@ func resolve_difficulty_modifiers(L: int, D: int) -> Dictionary: func _resolve_progress(rows: Array, L: int, D: int, table_id: String) -> Dictionary: if L < 1 or D < 0: - push_diag("error", "request_oob", "%s 请求越域 (%d,%d)" % [table_id, L, D]) + _fatal("request_oob", "%s 请求越域 (%d,%d)——进度坐标低于合法域(关卡序号≥1、节点深度≥0)" % [table_id, L, D]) return {} - var levels := {} + var level_keys: Array = [] for row in rows: - levels[int(row.get("level_index", 0))] = true - var level_keys := levels.keys() + var lv := int(row.get("level_index", 0)) + if not level_keys.has(lv): + level_keys.append(lv) + if level_keys.is_empty(): + _fatal("structural", "%s 行集为空——锚行校验应已拦截" % table_id) + return {} level_keys.sort() - var l_star: int - var mt_level := "exact" - if L < level_keys[0]: - l_star = level_keys[0] - mt_level = "ceil" - else: - l_star = level_keys[0] - for lv in level_keys: - if lv <= L: - l_star = lv - if l_star < L: - mt_level = "floor" - var depth_keys := {} + var l_star := _ceil_key(level_keys, L) + var l_clamped := l_star < 0 + if l_clamped: + l_star = int(level_keys[-1]) # 超出最大行 → 取最大行封顶(2026-09-18 追加拍板:可取范围内向上取整,超出用最大值) + var depth_keys: Array = [] for row in rows: if int(row.get("level_index", 0)) == l_star: - depth_keys[int(row.get("node_depth", 0))] = true - var d_keys := depth_keys.keys() - d_keys.sort() - var d_star: int - var mt_depth := "exact" - if d_keys.is_empty(): - push_diag("error", "structural", "%s level %d 无行(锚行校验应已拦截)" % [table_id, l_star]) - return {} - if D < d_keys[0]: - d_star = d_keys[0] - mt_depth = "floor" - else: - d_star = d_keys[0] - for dv in d_keys: - if dv <= D: - d_star = dv - if d_star < D: - mt_depth = "floor" + var dv := int(row.get("node_depth", 0)) + if not depth_keys.has(dv): + depth_keys.append(dv) + depth_keys.sort() + var d_star := _ceil_key(depth_keys, D) + var d_clamped := d_star < 0 + if d_clamped: + d_star = int(depth_keys[-1]) var missing := "" - if mt_level == "ceil": - missing = "base_ceil" - elif mt_level == "floor" and mt_depth == "floor": - missing = "both" - elif mt_level == "floor": - missing = "level_floor" - elif mt_depth == "floor": - missing = "depth_floor" + if l_clamped and d_clamped: + missing = "both_clamp" + elif l_clamped: + missing = "level_clamp" + elif d_clamped: + missing = "depth_clamp" + elif l_star != L and d_star != D: + missing = "both_ceil" + elif l_star != L: + missing = "level_ceil" + elif d_star != D: + missing = "depth_ceil" if missing != "": var dedup_key := "%s|%s|%d|%d" % [table_id, missing, l_star, d_star] if not _warn_dedup.has(dedup_key): _warn_dedup[dedup_key] = true - push_diag("warning", "resolve_" + missing, "%s 请求 (%d,%d) floor 解析 → (%d,%d) [%s]" % [table_id, L, D, l_star, d_star, missing]) + var verb := "clamp 解析(超出最大行取最大值)" if missing.contains("clamp") else "ceil 解析(向上取整保证偏难)" + push_diag("warning", "resolve_" + missing, "%s 请求 (%d,%d) %s → (%d,%d) [%s]——2026-09-18 拍板" % [table_id, L, D, verb, l_star, d_star, missing]) for row in rows: if int(row.get("level_index", 0)) == l_star and int(row.get("node_depth", 0)) == d_star: return row return {} +# 取 sorted_keys 中第一个 >= request 的键;无(请求高于最大行)返回 -1 → 调用方 Fatal +func _ceil_key(sorted_keys: Array, request: int) -> int: + for k in sorted_keys: + if int(k) >= request: + return int(k) + return -1 + + # ---- 地形变异 variant_pool_gate:候选池门控(∅ biome_tag = 通配)---- func variant_pool(level_index: int, board_biomes: Array) -> Array: diff --git a/prototypes/dungeon-sweeper-vertical-slice/data/schemas/action_economy_table.gd b/prototypes/dungeon-sweeper-vertical-slice/data/schemas/action_economy_table.gd index c11e1539..32ad8117 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/data/schemas/action_economy_table.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/data/schemas/action_economy_table.gd @@ -6,12 +6,12 @@ class_name ActionEconomyTable extends Resource -@export var ap_per_round: int = 8 # [1,32] knob +@export var ap_per_round: int = 6 # [1,32] knob(默认值 = 现行表值基线,2026-09-16 AP 压速批 8→6 随动) @export var dig_ap_cost: int = 1 # [1,8] knob(域下限 1 = 零预算系统失效拒绝) @export var flag_place_ap_cost: int = 1 # [0,8] knob @export var flag_pull_ap_cost: int = 1 # [0,8] knob -@export var rap_refresh_cost: int = 8 # [1,64] knob -@export var refresh_max_per_round: int = 2 # [1,8] knob(2026-09-12 轮内刷新批——每轮刷新兜底上限,Active/Waiting 合计) +@export var rap_refresh_cost: int = 6 # [1,64] knob(默认值随 AP 压速批 8→6 随动) +@export var refresh_cap_per_round: int = 2 # [1,8] knob(2026-09-12 轮内刷新批——每轮刷新兜底上限,Active/Waiting 合计) @export var move_ap_cost: int = 0 # 冻结 0(P4 氛围承载) @export var chain_ap_cost: int = 0 # 冻结 0(v2 定稿) @@ -28,8 +28,8 @@ func validate() -> Array: errs.append({"category": "domain", "message": "flag_pull_ap_cost 越域 [0,8]: %d" % flag_pull_ap_cost}) if rap_refresh_cost < 1 or rap_refresh_cost > 64: errs.append({"category": "domain", "message": "rap_refresh_cost 越域 [1,64]: %d" % rap_refresh_cost}) - if refresh_max_per_round < 1 or refresh_max_per_round > 8: - errs.append({"category": "domain", "message": "refresh_max_per_round 越域 [1,8]: %d" % refresh_max_per_round}) + if refresh_cap_per_round < 1 or refresh_cap_per_round > 8: + errs.append({"category": "domain", "message": "refresh_cap_per_round 越域 [1,8]: %d" % refresh_cap_per_round}) if move_ap_cost != 0: errs.append({"category": "frozen", "message": "move_ap_cost 结构冻结为 0: %d" % move_ap_cost}) if chain_ap_cost != 0: diff --git a/prototypes/dungeon-sweeper-vertical-slice/data/tables/action_economy.tres b/prototypes/dungeon-sweeper-vertical-slice/data/tables/action_economy.tres index d9eea51f..0968f635 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/data/tables/action_economy.tres +++ b/prototypes/dungeon-sweeper-vertical-slice/data/tables/action_economy.tres @@ -9,7 +9,7 @@ dig_ap_cost = 1 flag_place_ap_cost = 1 flag_pull_ap_cost = 1 rap_refresh_cost = 6 -refresh_max_per_round = 2 +refresh_cap_per_round = 2 move_ap_cost = 0 chain_ap_cost = 0 diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/hud_presenter.gd b/prototypes/dungeon-sweeper-vertical-slice/presentation/hud_presenter.gd index f612ebcc..66f849a2 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/hud_presenter.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/presentation/hud_presenter.gd @@ -1,5 +1,5 @@ # VERTICAL SLICE - NOT FOR PRODUCTION -# 实现 GDD:design/ux/battle.md(五区呈现装配器)——读核心只读面(战斗结算 呈现快照 / runner)→ +# 实现 GDD:design/ux/battle.md(四区呈现装配器)——读核心只读面(战斗结算 呈现快照 / runner)→ # 组装文案/投影 → 驱动各视图 setter。 # 高内聚边界:"核心状态 → 呈现文案"的全部翻译集中于此;视图保持零核心依赖; # 静态无状态——呈现不持久化任何核心句柄。 @@ -9,7 +9,7 @@ # 更新触发注记(battle.md Data Requirements):旗/拔/移动成功反馈零事件外发(排雷玩法 发射面封闭)—— # 经 ActionOutcome 返回值命令式驱动;AP/RAP 无推送信号——组合根每次调用返回后重查; # 增益状态 纯被动容器零信号——状态栏刷新绑 战斗结算 结算编排完成时点。 -# Date: 2026-09-12(五区改造批;基础 = 2026-09-11 HUD 装配器;2026-09-13 快照面改造) +# Date: 2026-09-12(战斗屏改造批;基础 = 2026-09-11 HUD 装配器;2026-09-13 快照面改造) class_name HudPresenter extends RefCounted @@ -68,7 +68,7 @@ static func _refresh_economy(info: HeroInfoPanelView, ap_bar: ApBarView, s: Dict info.set_rap(int(s.rap), int(db.action_economy.rap_refresh_cost)) var digging := String(s.phase) == ActionEconomy.PHASE_ACTIVE ap_bar.set_refresh_state(refresh_remaining, - bool(s.can_request_refresh) and digging) # Waiting 代理已随 OQ12 退役——UI 仅挖掘期受理 + bool(s.can_refresh) and digging) # Waiting 代理已随 OQ12 退役——UI 仅挖掘期受理 ap_bar.set_stop_enabled(bool(s.hero.get("alive", false)) and digging) diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/main.gd b/prototypes/dungeon-sweeper-vertical-slice/presentation/main.gd index 796bf843..e1c8ac4a 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/main.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/presentation/main.gd @@ -1,7 +1,7 @@ # VERTICAL SLICE - NOT FOR PRODUCTION # Validation Question: 线性第一关(战×2→道具→战×2→恢复→Boss)能否承载"数值提升→通关期望改善"的元进度验证 # 实现 GDD:design/ux/battle.md(战斗屏 A/B/C/D 四区 + 全屏 States + 遥测;E 区随可选类删除下线 2026-09-15)。 -# Date: 2026-09-12(battle.md 五区改造批;基础 = 2026-09-11 HUD框架-lite 组合根;code-review 修复批重构) +# Date: 2026-09-12(battle.md 战斗屏改造批;基础 = 2026-09-11 HUD框架-lite 组合根;code-review 修复批重构) # 2026-09-13(封装审查修复批):核心状态读取收口至 战斗结算/runner 成文只读面(presentation_snapshot / # current_board / current_battle 等——禁链式穿透 settlement.enemy_system.hero 一类路径);调试模式编排抽离 # debug_session.gd(规范 1.1 审视线 + 调试语义单一实现)。 @@ -76,19 +76,19 @@ func _ready() -> void: _start_overlay.visible = true -# ==================== 视图接线(battle.md 五区布局 1280×800;节点构建归 UiAssembler) ==================== +# ==================== 视图接线(battle.md 四区布局 1280×800;节点构建归 UiAssembler) ==================== func _build_ui() -> void: var root := VBoxContainer.new() add_child(root) - var five: Dictionary = UiAssembler.build_five_zone(root, db) - strip_view = five.strip_view - stage = five.stage - log_view = five.log_view - info_panel = five.info_panel - board_zone = five.board_zone - board_view = five.board_view # 探针兼容别名(= board_zone.board_view) - ap_bar = five.ap_bar # 探针兼容别名(= board_zone.ap_bar) + var battle_ui: Dictionary = UiAssembler.build_battle_ui(root, db) + strip_view = battle_ui.strip_view + stage = battle_ui.stage + log_view = battle_ui.log_view + info_panel = battle_ui.info_panel + board_zone = battle_ui.board_zone + board_view = battle_ui.board_view # 探针兼容别名(= board_zone.board_view) + ap_bar = battle_ui.ap_bar # 探针兼容别名(= board_zone.ap_bar) log_view.review_requested.connect(_open_review_from_band) board_zone.action_requested.connect(_on_board_action) board_zone.stop_pressed.connect(_on_stop_pressed) @@ -490,7 +490,7 @@ func _on_stop_pressed() -> void: func _on_refresh_confirmed() -> void: - # 弃图刷新(挖掘期 Active 当场换图继续挖——D4;RAP−8 · 旧板旗作废,每轮上限 2 次) + # 弃图刷新(挖掘期 Active 当场换图继续挖——D4;RAP 按门槛扣费 · 旧板旗作废,每轮上限读表) if _input_locked(): return var ok: bool = false @@ -502,7 +502,7 @@ func _on_refresh_confirmed() -> void: if ok: _refresh_used_round += 1 logger.log_ux("ux_refresh_attempt", {"result": "accepted", "round_refresh_index": _refresh_used_round}) - board_zone.toast.show_toast("RAP−8 · 旧板旗作废——新图已就位,AP 保留继续挖", "gold") + board_zone.toast.show_toast("RAP−%d · 旧板旗作废——新图已就位,AP 保留继续挖" % int(db.action_economy.rap_refresh_cost), "gold") else: # 资源面/上限拒绝(EC13 合法拒绝可见;行动经济 无载荷——客户端按上下文推断) var rap: int = int(settlement.action_economy.rap) if settlement != null else 0 @@ -533,7 +533,7 @@ func _handle_outcome_feedback(outcome: Dictionary) -> void: func _refresh_presentations() -> void: # 每个核心信号/写调用返回后的全量重查装配(更新触发注记——无推送信号,命令式重查) - var refresh_remaining: int = maxi(0, int(db.action_economy.refresh_max_per_round) - _refresh_used_round) + var refresh_remaining: int = maxi(0, int(db.action_economy.refresh_cap_per_round) - _refresh_used_round) HudPresenter.refresh(stage, info_panel, ap_bar, settlement, runner, _level_active, db, refresh_remaining) _sync_settlement_visuals() diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/playtest_logger.gd b/prototypes/dungeon-sweeper-vertical-slice/presentation/playtest_logger.gd index 6a503886..af34496f 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/playtest_logger.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/presentation/playtest_logger.gd @@ -1,6 +1,6 @@ # VERTICAL SLICE - NOT FOR PRODUCTION # 实现 GDD:design/ux/battle.md(§Events Fired 遥测义务——ux 行族)。 -# Date: 2026-09-12(五区改造批增 ux 遥测;基础 = 2026-09-12 日志器首版) +# Date: 2026-09-12(战斗屏改造批增 ux 遥测;基础 = 2026-09-12 日志器首版) # 试玩日志器(呈现层组件——订阅核心信号持久化 JSONL,供数值分析;卡死诊断转储)。 # 职责边界:核心层只发 settlement_snapshot 信号(战斗结算)/既有信号(battle_finished/node_entered/ # level_finished/step_row);本组件负责订阅、缓冲与写文件——核心零日志感知(零 UI 依赖不变)。 diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/ui_assembler.gd b/prototypes/dungeon-sweeper-vertical-slice/presentation/ui_assembler.gd index 64ba6019..86910cf6 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/ui_assembler.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/presentation/ui_assembler.gd @@ -1,5 +1,5 @@ # VERTICAL SLICE - NOT FOR PRODUCTION -# 实现 GDD:design/ux/battle.md(五区装配器——组合根的 UI 构建拆分载体)。 +# 实现 GDD:design/ux/battle.md(四区装配器——组合根的 UI 构建拆分载体)。 # Date: 2026-09-12(code-review 修复批:main.gd 回涨 761 行触规范 1.1 审视线——构建拆出) # 高内聚边界(规范 1.1/1.3):视图构建装配器——只做 new / add_child / 布局, # 零信号连接、零核心写调用;返回句柄供组合根接线(ADR-0001:接线拓扑唯一归组合根)。 @@ -9,9 +9,9 @@ extends RefCounted # 战斗屏四区(A 进度条 / B 战斗画面 / C·D 两列——E 区道具面板已随可选类删除下线,2026-09-15 格面资源批)。 # 返回句柄字典:{strip_view, stage, log_view, info_panel, board_zone, board_view, ap_bar} -static func build_five_zone(root: Control, db) -> Dictionary: +static func build_battle_ui(root: Control, db) -> Dictionary: # set_anchors_and_offsets_preset(非 set_anchors_preset):后者会把控件重置回最小内容尺寸 - # (实测 927×613——五区缩顶、棋盘 45px 格 ×10 行超出 D 区 333px 高被裁,2026-09-13 用户报告回归) + # (实测 927×613——缩顶、棋盘 45px 格 ×10 行超出 D 区 333px 高被裁,2026-09-13 用户报告回归) root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) root.add_theme_constant_override("separation", 0) @@ -41,6 +41,7 @@ static func build_five_zone(root: Control, db) -> Dictionary: var board_zone := BoardZoneView.new() bottom.add_child(board_zone) + board_zone.ap_bar.set_refresh_cost(int(db.action_economy.rap_refresh_cost)) # RAP 读数表值驱动 return { "strip_view": strip_view, @@ -89,7 +90,7 @@ static func build_start_overlay(host: Control, db = null) -> Dictionary: var title := UiKit.label("Dungeon Sweeper", 34, true) title.add_theme_color_override("font_color", Color8(240, 226, 200)) box.add_child(title) - var sub := UiKit.label("战斗屏五区改造批 —— 扫雷的因,逐行兑现的果", 16, false) + var sub := UiKit.label("战斗屏战斗屏改造批 —— 扫雷的因,逐行兑现的果", 16, false) sub.add_theme_color_override("font_color", Color8(200, 188, 166)) box.add_child(sub) box.add_child(HSeparator.new()) @@ -113,7 +114,7 @@ static func build_start_overlay(host: Control, db = null) -> Dictionary: # 玩法帮助文案(表值驱动——2026-09-16 AP 压速批遗留硬编码 8 随手修正;护甲等级掷点批同步"减伤"→"护甲强化") var help_text := "玩法:每轮 %d AP——挖开(1AP)/插旗(1AP)/移动(免费)/连锁(免费)。\n每个非封锁格都藏一种格面资源(增伤/护甲强化/回血/爆裂/削弱)。开安全格、标中雷 = 正向兑付;\n开雷格、标错安全格 = 负向兑付——全部在轮末结算逐行兑现;吃掷点的基础攻击按护甲等级判定,无 miss、最低保底 1 点。\n停手:剩余 AP 转存 RAP;弃图刷新:RAP−%d 当场换图继续挖(每轮剩 %d 次)。\n敌人全灭胜、主角倒下负。悬停可看三态光标与可挖域微光。" % [ - int(db.action_economy.ap_per_round), int(db.action_economy.rap_refresh_cost), int(db.action_economy.refresh_max_per_round)] + int(db.action_economy.ap_per_round), int(db.action_economy.rap_refresh_cost), int(db.action_economy.refresh_cap_per_round)] var help := UiKit.label(help_text, 13, false) help.add_theme_color_override("font_color", Color8(178, 166, 146)) box.add_child(help) diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/ap_bar_view.gd b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/ap_bar_view.gd index 133b865a..7396baac 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/ap_bar_view.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/ap_bar_view.gd @@ -1,7 +1,7 @@ # VERTICAL SLICE - NOT FOR PRODUCTION # 实现 GDD:design/ux/battle.md(D 区 AP 条 44px——点阵+数字〔pattern 5 点阵资源显示〕、 # 停手按钮居左、图例+弃图刷新按钮居右〔雷图右上角〕;弃图刷新确认微交互与常规确认不同位 -# 〔pattern 7 破坏性确认异位:RAP−8 · 旧板旗作废显式代价〕)。 +# 〔pattern 7 破坏性确认异位:RAP 扣费 · 旧板旗作废显式代价〕)。 # Date: 2026-09-12 # 高内聚边界:纯视图——意图经信号外发(stop_pressed/refresh_confirmed/legend_toggled), # 状态由组合根经 setter 注入;刷新可用性预判 = RAP≥8 读数(行动经济 无推送信号——每次调用返回后重查)。 @@ -23,6 +23,8 @@ var _btn_legend: Button var _btn_refresh: Button var _refresh_sub: Label var _confirm_row: HBoxContainer +var _confirm_lab: Label +var refresh_cost: int = 0 # 表值驱动(装配经 set_refresh_cost 注入 ActionEconomyTable.rap_refresh_cost) var _confirm_armed := false var _arm_tween: Tween @@ -67,9 +69,9 @@ func _init() -> void: _confirm_row.add_theme_constant_override("separation", 4) _confirm_row.visible = false row.add_child(_confirm_row) - var confirm_lab := UiKit.label("RAP−8 · 旧板旗作废", 12, true) - confirm_lab.add_theme_color_override("font_color", COL_INK) - _confirm_row.add_child(confirm_lab) + _confirm_lab = UiKit.label("RAP−%d · 旧板旗作废" % refresh_cost, 14, true) + _confirm_lab.add_theme_color_override("font_color", COL_INK) + _confirm_row.add_child(_confirm_lab) var confirm_btn := UiKit.button("确认弃图") confirm_btn.pressed.connect(_disarm_and_fire) _confirm_row.add_child(confirm_btn) @@ -87,7 +89,7 @@ func _init() -> void: _btn_refresh = UiKit.button("弃图刷新") _btn_refresh.pressed.connect(_on_refresh_pressed) refresh_box.add_child(_btn_refresh) - _refresh_sub = UiKit.label("RAP−8 · 剩 0 次", 10, false) + _refresh_sub = UiKit.label("RAP−%d · 剩 0 次" % refresh_cost, 14, false) _refresh_sub.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER refresh_box.add_child(_refresh_sub) @@ -107,8 +109,13 @@ func set_ap(ap: int, ap_max: int) -> void: _ap_label.text = "AP %d/%d" % [ap, ap_max] +func set_refresh_cost(cost: int) -> void: + refresh_cost = cost + _confirm_lab.text = "RAP−%d · 旧板旗作废" % cost + + func set_refresh_state(remaining: int, ready: bool) -> void: - _refresh_sub.text = "RAP−8 · 剩 %d 次" % remaining + _refresh_sub.text = "RAP−%d · 剩 %d 次" % [refresh_cost, remaining] _btn_refresh.disabled = not ready if not ready: _disarm() diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/battle_log_view.gd b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/battle_log_view.gd index bd08787b..0062aca8 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/battle_log_view.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/battle_log_view.gd @@ -42,7 +42,7 @@ func _init(db_ref) -> void: _summary.clip_text = true _summary.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS head.add_child(_summary) - _hint = UiKit.label("结算明细 ▸ 点击展开回看", 12, false) + _hint = UiKit.label("结算明细 ▸ 点击展开回看", 14, false) _hint.size_flags_horizontal = Control.SIZE_SHRINK_END head.add_child(_hint) _log = RichTextLabel.new() diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/enemy_card_view.gd b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/enemy_card_view.gd index b35f3db8..a009acd8 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/enemy_card_view.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/enemy_card_view.gd @@ -19,6 +19,8 @@ const PENDING_STYLE := { "damage": ["⚔", Color8(198, 61, 34)], "buff": ["✦", Color8(46, 110, 142)], } +# 蓄力档技能集(2026-09-18 用户拍板:重击类独立蓄力提示——伤害技 ⚔ / 增益技 ✦ / 重击类 蓄力 三档占位) +const CHARGE_SKILL_IDS := ["heavy"] # 护甲类型呈现(攻击类型×护甲克制批——可读性前提:玩家须能看出该用穿刺还是魔法打它) const ARMOR_NAMES := {"light": "轻甲", "ward": "魔法护甲", "heavy": "重甲"} @@ -82,7 +84,7 @@ func set_data(d: Dictionary) -> void: _set_order(int(d.get("order", 1))) _armor = String(d.get("armor_type", "light")) _armor_class = int(d.get("armor_class", 0)) - set_pending(String(d.get("pending_type", "")), String(d.get("pending_name", ""))) + set_pending(String(d.get("pending_type", "")), String(d.get("pending_skill", ""))) # 状态行:伏击池(flat 共享池,单独通道)+ 敌方增益 percent 状态(蓄力/硬化/削弱/暴走——side-wide) var parts: Array = [] var ambush: int = int(d.get("ambush_layers", 0)) @@ -105,15 +107,17 @@ func _set_order(i: int) -> void: # 摇定动作渲染(敌人技能池批——presenter 不翻译,渲染映射唯一定义处归本类〔code-review 2026-09-12 先例〕): # pending_type = 战斗结算 快照投影(basic/damage/buff;"" = 未摇定/死亡残位)。 # 信号/入参契约:set_data 每结算行刷新、set_pending 每轮开轮刷新(开轮摇号时点)。 -func set_pending(ptype: String, pname: String) -> void: +func set_pending(ptype: String, pid: String) -> void: if _dead: return var style: Array = PENDING_STYLE.get(ptype, ["—", Color8(120, 106, 88)]) _intent_badge.add_theme_color_override("font_color", style[1]) if ptype == "" or ptype == "basic": _intent_badge.text = "意图:—" # 普通攻击零表示(不可预测——§2.4) + elif pid in CHARGE_SKILL_IDS: + _intent_badge.text = "意图:蓄力" # 重击类蓄力档(2026-09-18 用户三档拍板占位;终稿动画归战斗表现样张批) else: - _intent_badge.text = "意图:%s %s" % [String(style[0]), pname] # 特殊技类型级占位(纯提醒,不可撤销) + _intent_badge.text = "意图:%s" % String(style[0]) # 特殊技类型级占位——不预告技能名与数值(HUD框架;2026-09-18 剧透堵口) # 护甲行(环点拆除——护甲类型 + AC 恒常驻,克制对策与掷点阈值的可读前提) _armor_line.text = "%s · AC %d" % [_armor_tag(_armor), _armor_class] diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/level_strip_view.gd b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/level_strip_view.gd index 6966f2cb..8397d6ea 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/level_strip_view.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/level_strip_view.gd @@ -1,6 +1,6 @@ # VERTICAL SLICE - NOT FOR PRODUCTION # 实现 GDD:design/ux/battle.md(A 区关卡进度条——全宽 × 28px 顶栏,当前节点高亮、已过节点暗金)。 -# Date: 2026-09-12(battle.md 五区改造批——对齐 28px 顶栏与高亮样式;基础 = 2026-09-11 关卡节点地图-lite 呈现) +# Date: 2026-09-12(battle.md 战斗屏改造批——对齐 28px 顶栏与高亮样式;基础 = 2026-09-11 关卡节点地图-lite 呈现) # 2026-09-17 分支节点图批:图形态行集渲染 = 行进序列(已到暗金 → 当前金 → 可选下一跳亮边候选); # 单链形态(第一关零迁移)保持全链横条渲染。 # 高内聚边界:只读 runner 的节点链/进度/候选做渲染,无任何写面。 diff --git a/prototypes/dungeon-sweeper-vertical-slice/smoke/auto_player.gd b/prototypes/dungeon-sweeper-vertical-slice/smoke/auto_player.gd index 24fec97c..665558f0 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/smoke/auto_player.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/smoke/auto_player.gd @@ -47,7 +47,7 @@ static func perfect_action(minesweeper, settlement) -> Dictionary: for c in ded["safe"]: if not minesweeper.is_revealed(c) and not minesweeper.has_flag(c): return _plan_on(minesweeper, c, "dig") - if settlement != null and bool(settlement.can_request_refresh()): + if settlement != null and bool(settlement.can_refresh()): return {"kind": "refresh"} return {"kind": "stop"} diff --git a/prototypes/dungeon-sweeper-vertical-slice/smoke/config_fatal_probe.gd b/prototypes/dungeon-sweeper-vertical-slice/smoke/config_fatal_probe.gd new file mode 100644 index 00000000..de7ad1d8 --- /dev/null +++ b/prototypes/dungeon-sweeper-vertical-slice/smoke/config_fatal_probe.gd @@ -0,0 +1,22 @@ +# VERTICAL SLICE - NOT FOR PRODUCTION +# 验证问题:运行期数据致命通道是否成立(2026-09-18 用户拍板——进度坐标越域记 Fatal 级日志后进程退出,退出码 1) +# 运行:Godot_console.exe --headless --path . --script res://smoke/config_fatal_probe.gd +# 预期:进程以退出码 1 终止;打印 FAIL 行或退出码 0 即致命通道未生效。 +extends SceneTree + +var _frames := 0 + +func _initialize() -> void: + var db = load("res://core/config_db.gd").new() + root.add_child(db) # 入树——autoload 同款生命周期 + db.load_tables("res://data/tables", false) + db.resolve_enemy_rowset(0, 0) # 越域:关卡序号 <1 → Fatal,quit(1) 排队帧末退出 + print("FATAL-PROBE — Fatal 已记,等待帧末退出(退出码应为 1)") + +func _process(_delta: float) -> bool: + _frames += 1 + if _frames > 5: + print("FATAL-PROBE FAIL — 5 帧内未退出,致命通道未生效") + quit(0) + return true + return false diff --git a/prototypes/dungeon-sweeper-vertical-slice/smoke/smoke_all.gd b/prototypes/dungeon-sweeper-vertical-slice/smoke/smoke_all.gd index db84eea8..7297bbfa 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/smoke/smoke_all.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/smoke/smoke_all.gd @@ -409,6 +409,10 @@ func _smoke_board_generator() -> void: for L in [1, 2]: var row = _db.resolve_board_gen(L, 0) check(not row.is_empty(), "BoardGen 行解析 (%d,0)" % L) + if L == 2: + # R1 ceil 口径(2026-09-18 拍板):无精确行向上取整保证偏难——L2 深度 3 为恢复点无行,解析到深度 4 + var ceil_row: Dictionary = _db.resolve_board_gen(2, 3) + check(int(ceil_row.get("node_depth", -1)) == 4, "R1 ceil 解析 (2,3)→(2,4)(向上取整偏难)") var b1 = board_generator.generate(L, 0, _rng.open_stream("board_gen"), _db) check(b1 != null, "L%d 生成产物非空" % L) if b1 == null: diff --git a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_click_probe.gd b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_click_probe.gd index 861d43e4..07df663d 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_click_probe.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_click_probe.gd @@ -1,6 +1,6 @@ # VERTICAL SLICE - NOT FOR PRODUCTION # 实现 GDD:design/ux/battle.md(§AC 场景级测试 + §Interaction Map——UI 行为随机化探针 v3)。 -# Date: 2026-09-12(五区改造批扩展;基础 = 2026-09-11 问题3 复现器 v2——既有断言全保留) +# Date: 2026-09-12(战斗屏改造批扩展;基础 = 2026-09-11 问题3 复现器 v2——既有断言全保留) # 合成鼠标事件走真实呈现层路径(BoardView.click_cell / 按钮信号 → 组合根动作通道), # 随机混合玩家行为——插旗/拔旗、提前停手、弃图刷新确认链、图例模态、明细回看模态、 # tooltip 悬停、无效点击——扫描结算停滞(缺陷②回归载体;缺陷④窗口契约随道具窗口删除退役 2026-09-15) diff --git a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_layout_probe.gd b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_layout_probe.gd index c1521c2a..5b08e6e7 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_layout_probe.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_layout_probe.gd @@ -1,5 +1,5 @@ # VERTICAL SLICE - NOT FOR PRODUCTION -# Date: 2026-09-13(五区布局诊断探针——"内容未满幅/棋盘底裁切"回归定位:打印窗口/视口/五区实际矩形 +# Date: 2026-09-13(四区布局诊断探针——"内容未满幅/棋盘底裁切"回归定位:打印窗口/视口/四区实际矩形 # 与最小尺寸、展开旗标,对照 battle.md §Layout 1280×800 基座规格) # 运行:Godot_console.exe --headless --path . res://smoke/ui_layout_probe.tscn extends Node @@ -33,7 +33,7 @@ func _ready() -> void: print("%-16s pos=%s size=%s min=%s hflag=%d vflag=%d" % [k, ctl.position, ctl.size, ctl.custom_minimum_size, ctl.size_flags_horizontal, ctl.size_flags_vertical]) var root_box: Control = main.get_child(0) - print("five-zone root: anchors=(%s,%s)-(%s,%s) offsets=(%s,%s,%s,%s) size=%s" % [ + print("four-zone root: anchors=(%s,%s)-(%s,%s) offsets=(%s,%s,%s,%s) size=%s" % [ root_box.anchor_left, root_box.anchor_top, root_box.anchor_right, root_box.anchor_bottom, root_box.offset_left, root_box.offset_top, root_box.offset_right, root_box.offset_bottom, root_box.size]) print("board_view cell_size=%s board=%s" % [main.board_view.get("cell_size"), main.board_view.board()]) diff --git a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_probe.gd b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_probe.gd index 4c95d150..b60de633 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_probe.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_probe.gd @@ -1,6 +1,6 @@ # VERTICAL SLICE - NOT FOR PRODUCTION -# 实现 GDD:design/ux/battle.md(场景级 headless 探针——五区布局实例化断言;entry 17②③)。 -# Date: 2026-09-12(五区改造批扩展;基础 = 2026-09-11 首战棋盘探针——既有断言全保留) +# 实现 GDD:design/ux/battle.md(场景级 headless 探针——四区布局实例化断言;entry 17②③)。 +# Date: 2026-09-12(战斗屏改造批扩展;基础 = 2026-09-11 首战棋盘探针——既有断言全保留) # 项目运行模式(autoload 在位)实例化 main.tscn,直接驱动 _start_full_level(), # 断言四区真实构建(棋盘/进度条/英雄卡/敌人卡/AP 点阵/明细带/数据框;E 区道具面板已删除)。 # 运行:Godot_console.exe --headless --path . res://smoke/ui_probe.tscn @@ -29,7 +29,7 @@ func _ready() -> void: problems.append("首战未处于进行中") if not main.log_view.parsed_text().contains("节点 1/"): problems.append("节点头日志缺失") - # ---- 五区布局断言(battle.md §Layout)---- + # ---- 四区布局断言(battle.md §Layout)---- if not main.stage.round_label.text.contains("第 1 轮"): problems.append("B 区轮次·阶段指示缺失(%s)" % main.stage.round_label.text) if main.stage.enemy_box.get_child_count() < 1: diff --git a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_tune_probe.gd b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_tune_probe.gd index 49a208ac..3ddc0802 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_tune_probe.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_tune_probe.gd @@ -13,6 +13,11 @@ func _ready() -> void: add_child(main) await get_tree().process_frame var problems: Array = [] + # 基线读表(2026-09-18 随数值压缩批改表值驱动——旧字面量 6/50、12/75 已随 hero 表压缩失效) + var base_atk := int(main.db.hero.atk) + var base_hp := int(main.db.hero.max_hp) + var boosted_atk := base_atk * 2 # 攻击滑条 100 → ×2 + var boosted_hp := int(round(base_hp * 1.5)) # 生命滑条 50 → +50% # ① 打开可见性(缺陷⑤回归:打开面板必须隐藏不透明的开始覆盖层) main._show_tune_panel() if not main.tune_panel.visible: @@ -21,7 +26,7 @@ func _ready() -> void: problems.append("开始覆盖层未隐藏——面板被遮挡(缺陷⑤复发)") # ② 滑条 → 生效预览联动 main.tune_panel.set_sliders(100, 50) - if "12" not in main.tune_panel.effect_text() or "75" not in main.tune_panel.effect_text(): + if str(boosted_atk) not in main.tune_panel.effect_text() or str(boosted_hp) not in main.tune_panel.effect_text(): problems.append("生效预览未联动(%s)" % main.tune_panel.effect_text()) # ③ 应用(真实信号路径:apply_requested → 组合根)——E4 后加成零表写,生效断言移至 ⑧ 战斗快照 main.tune_panel.press_apply_for_probe() @@ -50,19 +55,19 @@ func _ready() -> void: if main.tune_panel.visible or not main._start_overlay.visible: problems.append("关闭后层级状态错误") # ⑦ hero 表运行期不可变(E4 收口回归断言:任何代码 reintroduce 表写在此被抓获——原"表值残留"断言语义升级) - if int(main.db.hero.atk) != 6 or int(main.db.hero.max_hp) != 50: - problems.append("hero 表被运行时改写(E4 违例:atk=%s hp=%s,期望恒基线 6/50)" % [str(main.db.hero.atk), str(main.db.hero.max_hp)]) + if int(main.db.hero.atk) != base_atk or int(main.db.hero.max_hp) != base_hp: + problems.append("hero 表被运行时改写(E4 违例:atk=%s hp=%s,期望恒基线 %d/%d)" % [str(main.db.hero.atk), str(main.db.hero.max_hp), base_atk, base_hp]) # ⑧ 单次加成生效回归断言:应用加成 + 启动单场战斗 → 呈现快照 hero 必须是"基线 × 单次加成" - # (6×2=12 / 50×1.5=75)——旧缺陷(残留值被当基线再乘 → 24/112)在 encounter 参数链上同样被抓获 + # (攻击 ×2 / 生命 +50%)——旧缺陷(残留值被当基线再乘)在 encounter 参数链上同样被抓获 main.tune_panel.set_sliders(100, 50) main.tune_panel.press_apply_for_probe() main._start_full_level() # 完整关卡路径(_start_single 按语义清零滑条 = 恒基线,不承载加成断言) await get_tree().process_frame await get_tree().process_frame var hero: Dictionary = main.settlement.presentation_snapshot().hero - if int(hero.get("atk", -1)) != 12 or int(hero.get("max_hp", -1)) != 75: - problems.append("战斗加成单次生效违例(atk=%s max_hp=%s,期望 12/75)" % [ - str(hero.get("atk", "?")), str(hero.get("max_hp", "?"))]) + if int(hero.get("atk", -1)) != boosted_atk or int(hero.get("max_hp", -1)) != boosted_hp: + problems.append("战斗加成单次生效违例(atk=%s max_hp=%s,期望 %d/%d)" % [ + str(hero.get("atk", "?")), str(hero.get("max_hp", "?")), boosted_atk, boosted_hp]) if problems.is_empty(): print("[tune_probe] PASS — 打开/联动/应用/重置/模拟/关闭/基线无残留/单次加成 全链路通过(模拟 N=10 输出 %.30s…)" % out) get_tree().quit(0)