# 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` 材质,保留纸面、细边和阴影,同时避免装饰端帽压到文字。费用模块继续 隐藏卡片中的重复可用状态,只显示硬币图标和金额。