Files
aetherbound-guild/docs/runtime/M52_SHARED_UI_RULES.md
T

243 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# M52 Shared UI Rules
这是当前 Q 版日系手绘方向的运行时 UI 规则。页面可以有不同构图,但不能
改变下面的材质、层级和伸缩语法。
## 设计画布与伸缩
- 运行时背景使用 `aspect-cover` 铺满视口,允许裁切,不允许压扁或拉长。
- 交互层使用居中的 16:9 设计画布;宽屏增加两侧世界空间,窄屏在上下留
安全区,画布本身不变形。
- 设计画布内部先建立安全边距、列数和槽间距,再放置页面组件。等宽操作行
必须由共享网格计算,不允许页面手写一组近似归一化坐标。
- 参考版式的稳定性来自“同一画布、同一网格、同一组件槽位”的组合;固定画布
只能解决缩放漂移,不能替代网格和内容槽合同。Aetherbound 采用这条原则,
但保留本项目自己的日式手绘材质和页面构图。
- 静态主图若已经包含按钮外框、图标或标签底板,运行时只能挂载文字槽和
透明命中区;不得在同一位置再画一套按钮 chrome。动态状态需要视觉变化时,
先为主图准备独立状态层,再接入运行时。
- 图标、头像、纹章使用保持比例的固定槽位,不使用九宫格。
- 木牌、羊皮纸、名册、资源徽章使用可拉伸的外框;圆角、内边距、角钉和
文字槽由组件提供,内部图标和文字保持原尺寸。
- 所有点击区域至少 48px 高;可见材质可以比逻辑点击区域更小,但文字和
图标必须对齐可见槽位。
## 组件语法
`runtime/ui/shared/market_chrome.gd` 是当前共享材质原型,后续应迁移到
统一的 `UIFactory` / token 层,而不是页面自行创建新的边框。
| 组件 | 视觉职责 | 动态内容 |
| --- | --- | --- |
| `title` | 米白纸牌、青绿色布带、黄铜角钉、柔和投影 | 页面名、阶段名 |
| `resource` | 小型纸徽章、左右角钉 | 队伍、金币、数量 |
| `card` | 悬挂绳、羊皮纸、职业彩带、特质印章、价格条 | 头像、姓名、职业、特质、价格、状态 |
| `ledger` | 暖色冒险者名册、头像槽、单一行动码头 | 候选详情、说明、价格、主动作 |
| `wood_button` | 棕木牌、底部厚度、黄铜角钉 | 返回、离开、导航动作 |
| `ledger_action` | 青绿色操作牌、内描边、黄铜角钉 | 查看详情、确认 |
| `selection_ring`(M66) | 半透明金色场 + 亮金描边环,共享 `PageChrome` 变体 | 队伍线成员选中、任何肖像槽选中 |
| `Tokens.seal_style(color)`(M66) | 彩色小徽记:页面给语义色,边框/圆角/线宽共享 | 风险事实图标、风险身份标记 |
| `Tokens.hit_area_style(state)`(M66) | 透明命中反馈:normal 微海军蓝、hover 金、pressed/focus 天蓝 | 纸质卡片的悬停/按压/焦点反馈 |
| `Tokens.font_size("caption")` = 8(M68 收口) | 窄卡面(市场招募卡姓名/特质芯片)的合法降档字号;宽槽位仍用 `micro` | 招募卡姓名、特质芯片 |
| 卡内模块的鼠标过滤(M76-C) | 卡片本身是 Button 时,卡内所有模块根节点必须 `mouse_filter = IGNORE`(含 identity/trait/cost/profession/portrait/guild_header)。示例缺陷:`IdentityModule` 默认 STOP 吃掉点击 → 新开局的招募卡完全点不动 | 卡片类视图(market 卡/详情栏/公会卡)|
| 运行时音频接线(M76-C) | 页面进入 = `_audio_enter(page, family)`(page_open + 音乐族);玩家提交动作 = `_audio_sfx("事件名")`(合同 §8 事件名)+ 动作函数首行;服务在 shell 宿主节点上(页面重建会清空 shell 子节点)| entry shell 全页 |
| 停靠行边界(M75) | 运行时自绘的命令按钮必须对齐页面自身栅格(左/右线与同页其他元素共享),同排同高(±2px)同基线(±2px);贴附绘制美术角牌/底带的按钮可跟随美术,但必须确认美术确有该牌面 | market 探针断言 `ABG_M52_MARKET_DOCK_BOUNDS_OK`(back 57..268 / return 1049..1267)|
| 命令按钮尺度(M74) | 所有玩家可见命令按钮用 `UITokens.command_height/size`:primary 52 / 其余 48;实拍容差 ±8px(缩放/边距)。页脚条=牌面内的 ~59px 行,而非大板。卡片与大命中区不受此限;触控目标 ≥48×48 | 所有页面实渲染审计(market/guild/party/risk/contract/result/roster)|
| 130% 文本排版(M73) | 无障碍放大档下:卡片/详情栏把头像高度让给文字行(不缩字号);成本条的图标/可用性/金额三列重分;页脚与徽章把图标列让给标签;区块标题允许两行。所有槽位保留 ≥6% 余量(曾因 0.4px 差裁掉一个汉字) | 所有现行页面 × `text_scale=1.3`;探针 1.0+1.3 双档严格覆盖 |
| 市场卡状态徽记(M60-I03 / M72) | 已招募候选卡保留在卡列但必须明示:顶部丝带位换共享 `card` 材质的状态牌("已招募/HIRED")+ 整卡 `INK` 16% 降调;卡片仍可点开查看(inspect-only) | Market 招募卡;同类"已购/已用"状态可复用 |
| `Tokens.COLOR_SUCCESS`(M67) | 羊皮纸上的成功/存活墨绿 `#3d7449`(深于旧深色底用的亮绿) | 结果存活、恢复承诺、无击倒状态、风险页正向后 |
## 资产九宫格材质(M57)
`runtime/ui/shared/asset_chrome.gd` 是运行时共享材质工厂,供首屏页面复用,
不允许页面自行创建同类边框。
| 组件 | 用途 | 规则 |
| --- | --- | --- |
| `rail()` | 顶栏、底栏等整幅宽木牌 | `name_plate` 九宫格;左右装饰角是固定角块,中间只拉伸素面 |
| `paper()` | 摘要、警告、状态羊皮纸面板 | `parchment_panel` 九宫格;木框与内斜面作为角/边,中心只拉伸纸面 |
| `market()` | 小控件(名牌、命令底板) | 直接使用 `market_chrome.gd` 的可缩放矢量材质,避免把装饰像素拉成条带 |
规则:
- 九宫格边距必须覆盖素材的装饰环(水平与垂直可不同);边距过小会把角部
纹样拉成横条,属于禁用状态。
- 小尺寸控件(高度低于装饰环两倍)不得使用装饰型九宫格;改用 `market()`
的可缩放材质。
- 文字一律引擎渲染,颜色使用纸面深墨或深底浅金,禁止中灰压在花色背景上。
- 新增材质必须先记录在本文件,再被第二个页面复用。
### M57 逐区域手绘材质(`page_chrome.gd`)
一个通用牌匾套所有区域属于禁用做法。`runtime/ui/shared/page_chrome.gd`
按区域提供手绘材质,全部走"按比例九宫格"(角与边按自身比例缩放,中段拉伸),
因此在 760/844/1280 各视口下边框粗细一致。
| 变体 | 源素材 | 尺寸 | 边距(x/y 比例) | 用途与状态 |
| --- | --- | --- | --- | --- |
| `command_plate` | `ui_m57_chrome/command_plate_{secondary,primary,disabled}.png` | 440x160(2.75:1) | 0.062 / 0.235 | 唯一可点材质;绿=主动作、米=次要、灰=禁用 |
| `header_band` | `ui_m57_chrome/panel_parchment.png` | 840x140(6:1) | 0.055 / 0.15 | 页面顶栏;分格线由引擎绘制 |
| `card` | `ui_m57_chrome/panel_parchment.png` | 同上 | 同上 | 警告条、状态卡、摘要卡等信息面 |
| `utility_plate` | `ui_m57_chrome/command_plate_utility.png` | 158x112(1.41:1) | 0.115 / 0.15 | 顶栏设置等方形工具按钮 |
| `dock_band` | `ui_m57_chrome/dock_rail.png` | 1456x104(14:1) | 0.022 / 0.20 | 底部命令带,命令牌落于其上 |
| `progress` | 共享绘制(轨道+进度填充) | 模块内比例 | `value` 由 `configure`/`set_progress` 传入 | 进度条;禁止页面自绘或自带 `StyleBoxFlat` 填充 |
| `warning_icon` | `ui_m57_chrome/badge_{warning,notice,info}.png` | 43x44 | 固定方块 | 严重度色对:红=阻断、琥珀=提示、青=信息,标记由引擎绘制 |
| `title_plaque` | `ui_m52_market/v2/title_plaque.png` | 984x560(1.76:1) | 24/16 源像素 | Market 标题银牌;文字槽必须落进实测开口 |
**标题银牌开口契约(实测量得,2026-09-13)**:源图 984×560,羊皮纸开口在
rows 225–425 / cols 199–830;经九宫格拉伸后在模块坐标里开口为
x 0.221–0.821、y 0.421–0.706(1280 画布上 y 79–116)。开口是**卡图什椭圆**,
下半部收窄,所以文字要排在纵向中带,排版后必须**实测墨迹**:标题墨迹
y 81–95、副题墨迹 y 100–111,均在开口内且互不重叠。当前槽位:
标题 (0.26, 0.378, 0.52, 0.210) 字号 `10 × text_scale`;副题
(0.28, 0.557, 0.48, 0.132) 字号 `7 × text_scale`。槽位 y 必须按
"目标墨迹 y − 0.08×字号"反推,不能直接把槽位中心对到开口中心。
规则:
- 材质分工固定:**木牌=可交互**,**羊皮纸=信息**,两者不得互换或混用。
- 命令牌的文字与图标槽位必须落在牌"面"内(左右 6%、上下保留边框后居中),
不得压到木框。
- 警告严重度必须与数据一致:生产数据 `severity` 为
`["warning","notice","info"]`(`entry_shell._readiness_warning_facts`);
视图在缺省时按行序回退到同一序列,禁止三行同色。
- 顶栏文字必须落在带面内:首列与末列按 `PANEL_EDGE_X * 带宽` 加内缩,
不允许文字压在角部支架或金线上。
- 同一区域不得再叠加第二层自绘边框、徽章或警示图标。
- 全部材质启用 mipmap(`.import` 中 `mipmaps/generate=true`),否则宽高比
差异大的区域在缩小时会出现锯齿状边缘。
## 失败教训与防复发机制(2026-09-13,M58-C)
Owner 在 M58-C 验收中连续指出三个同类缺陷(银牌文字落框、卡图什椭圆开口、
右上徽章文字压图标),根因不是“不够仔细”,而是流程缺口。以下记录为
可复用规则,接入任何承载文字的新组件前必须逐条对照。
**三类根因(必须避免)**
1. **算而不量**:用九宫格拉伸公式推算开口位置,并假设开口是矩形。异形开口
(卡图什/椭圆/收窄)必须量渲染结果。正确顺序是:量源图开口 →
定**目标墨迹带** → 反推槽位(`槽位 y = 目标墨迹 y − 0.08 × 字号`)→
渲染后**实测墨迹**复核。
2. **固定 px 与比例混用**:同一组件内一侧用写死像素(如文字内缩 36px)、
另一侧用比例锚点(图标 0.14–0.32),缩放/换语言必然重叠。文字槽一律
锚在兄弟图标边界之外(如图标 0.08–0.24 → 文字 0.26–0.96),
禁止绝对内缩。
3. **验证尺度错 + 无最长文案 fixture**:整页 1:1 看不清 4–6px 的压字或
截断;fixture 不含线上最长字符串时,英文(如 `Last Witness`)会先被
省略。每个槽位都必须有最长线上文案的中英用例。
**四条机制(后续执行)**
- **契约前置**:组件接入页面前,先在本文档声明源素材、内尺寸、九宫格
边距比例、**实测开口/面边界**、文字/图标槽、最长文案与对比度。
- **机器墨迹检查(建议加入探针)**:遍历承载文字的节点,断言墨迹盒
(`Font.get_string_size`)不与兄弟图标相交、不越出所属材质的“面”、
且不超槽宽;违反即 fail,不允许只靠肉眼。
- **最长文案 fixture**:每个槽位放线上最长中英字符串,并断言
“文本宽 ≤ 槽宽”。
- **量测证据随交付**:组件交接除整页图外,必须附放大裁切 + 数字
(墨迹带、槽宽/文本宽、对比度);整页图不作为唯一依据。
**内框规则(2026-09-13,M59)**:标签槽必须等于材质的**可见内框**
(端帽、饰边、铆钉之外),不能锚在模块整幅。已实测:名牌文字 0.14–0.86、
特性缎带 0.15–0.85;账本成本行状态 0.19–0.56 / 金额 0.58–1.0;账本头
0.08–0.92。违反时端帽会盖住首尾字,而"文字宽度 ≤ 标签矩形"的检查测不到。
Market 视觉探针现已内置 `runtime/tools/ui_ink_check.gd`(标签槽宽/行高、
标签—图标相交、标签—模块包含)+ 最长线上文案 fixture,见 `M59_PROBE_INK_CHECKS.md`。
**本次已落地的测量契约**:标题银牌开口(见上表 `title_plaque` 行)、
资源徽章图标/文字分带(0.08–0.24 / 0.26–0.96)、账本上下文行槽宽、
卡片特性缎带槽(0.06–0.94 放置、0.08–0.92 文字)。
## 信息预算
- 每页一个主动作;首屏只放 2–3 个核心事实。
- 标题、资源、对象、下一步动作按这个顺序建立层级。
- 调试 hash、内部 ID、服务状态不得出现在玩家可见层。
- 中文正文使用共享字体和固定字号;长文案优先换行,其次省略,不能越过
容器边界。
- 禁止“纯文本按钮”。任何可点击文案必须有材质、图标或明确的选中/禁用
状态。
## 页面迁移顺序
1. 先把页面分成 `Entry Shell / Selection / Formation / Battle / Outcome`。
2. 选择一个主材质组件,不在页面内叠第二套同类边框。
3. 先做静态层,再放运行时文字、数值和透明命中区。
4. 在 760×360、844×390、1280×720 以及中英文截图中检查:裁切、文字槽、
主动作、选中态、禁用态和无重复框。
5. 只有截图和试玩都通过后,才把该组件扩展到下一页。
## Market 模块边界(M52-B)
Market 不是一张平面图,而是下面这棵组合树;每个叶子模块拥有自己的
文字槽、裁切边界和响应式布局,页面只负责排列模块与转发 intent:
```text
M52MarketDesignPage
├─ WorldBackground(aspect-cover,只负责场景)
├─ MarketHeader
│ ├─ TitlePlaque(NinePatch 外框)
│ ├─ TitleLabel
│ └─ StageLabel
├─ ResourceStatusGroup
│ ├─ CompanyBadge
│ ├─ CoinBadge
│ └─ SettingsButton
├─ RecruitBoard
│ └─ RecruitCard × 3
│ ├─ CardShell / ProfessionRibbon
│ ├─ PortraitModule(Frame + Viewport + Image)
│ ├─ IdentityModule(NamePlate + NameLabel)
│ ├─ TraitModule(Badge + TraitLabel)
│ ├─ CostModule(CoinSocket + Status + Amount)
│ └─ StateLayer / HitArea
├─ CandidateLedger
│ ├─ LedgerShell / LedgerHeader
│ ├─ PortraitModule
│ ├─ IdentityModule
│ ├─ ProfessionModule
│ ├─ DetailCopy
│ ├─ TraitModule
│ ├─ CostModule
│ ├─ ActionDock
│ └─ HitArea(ActionDock 自带)
└─ FooterNav(BackGuild / ReturnContract)
```
Market 的层所有权明确为:`m52_q_market_world_only.png` 只负责场景、木架、
布旗、柜台和远景;三块中央委托板不再属于背景。它们的纸面、类别图标、
人物框、姓名、特质、价格和选中态全部由运行时模块绘制。旧的
`m52_q_market_background.png` 仍保留作历史候选,但禁止再作为 Market
运行时底图。
当前已抽成可复用脚本的是 `MarketHeader`、`ResourceStatusGroup`、
`RecruitCard`、`CandidateLedger` 及其头像、姓名、职业、详情文案、特质、
价格、动作子模块;
`FooterNav` 也已独立;Market View 现在只保留模块排列、数据绑定和 intent
转发,不能再在页面内新增同类边框或文字按钮。
所有页面级标题必须先挂到 Header 的本地槽位,再挂 Header 到设计画布;禁止
把子标题直接按画布坐标定位。每个垂直槽位之间必须留出至少 4% 的设计画布
间距,紧凑尺寸还要通过自动换行或省略号保护容器边界。
### CandidateLedger 细粒度拆分(2026-09-10)
右侧详情不是一个“文字块”,而是一个可复用的 `CandidateLedger` 组合件:
`LedgerShell`、`LedgerHeader`、`PortraitModule`、`IdentityModule`、
`ProfessionModule`、`DetailCopy`、`TraitModule`、`CostModule`、
`ActionDock`。每个子模块只拥有自己的背景/文字槽位,父模块只负责垂直
排列和转发动作意图。`ActionDock` 同时承载视觉按钮和触摸命中区,避免
“看起来像按钮、实际点击区在别处”。
截图探针必须为每个候选人提供真实的本地化职业、特质和详情文案;空数据
只能用于空状态验收,不能用来判断最终文字布局。
### RecruitCard 紧凑布局(2026-09-10)
`RecruitCard` 与 `CandidateLedger` 共用头像、姓名、特质和价格模块,但不
强行共用同一套尺寸。卡片进入手机横屏紧凑模式后,会给头像、姓名、特质、
价格重新分配独立的垂直槽位;姓名和特质牌切换到共享的 `compact_label`
材质,保留纸面、细边和阴影,同时避免装饰端帽压到文字。费用模块继续
隐藏卡片中的重复可用状态,只显示硬币图标和金额。