fix: expose incomplete UI coverage honestly

This commit is contained in:
2026-08-10 21:37:24 +08:00
parent 88bdf8fbf3
commit ac0dec2584
7 changed files with 180 additions and 60 deletions
+9 -5
View File
@@ -25,14 +25,18 @@ progression, formulas, and expression must be original.
## Review The Design
The single owner-facing review artifact is the interactive HTML prototype in
[`prototype/site/`](prototype/site/). It shows one subject at a time and links
the complete design package behind these review paths:
The current owner-facing HTML in [`prototype/site/`](prototype/site/) is a
**coverage audit**, not a completed UI prototype. Owner review on 2026-08-10
proved that generic specification templates were not understandable as game
screens. Its page browser now distinguishes one dedicated page, 40 shared
family mockups, and 41 pages whose UI is still missing.
1. **Core flow:** walk through the 17-step title-to-restoration path.
1. **Flow specification:** inspect the required 17-step title-to-restoration
order; do not treat it as a validated clickable experience.
2. **Eight chapters:** inspect route, battle, and restored-world direction for
every chapter.
3. **All screens:** inspect all 82 player-facing pages and their key states.
3. **Page coverage:** inspect all 82 requirements and identify which screens
still need a dedicated composition.
4. **Content:** search all 30 characters, 36 professions, 100 enemies, 320
items, 48 achievements, and 20 collections.
5. **Production and readiness:** inspect the art/audio scope and the gates that
+16 -8
View File
@@ -11,7 +11,8 @@ The prototype must let the owner decide whether:
1. the landscape 2D hand-drawn direction fits the intended game;
2. the player can recognize Guild, company, route, battle, diagnosis, reward,
restoration, records, and settings without a card-wall interface;
3. the 82-screen inventory and eight chapters appear complete;
3. the 82-screen requirement inventory and eight chapters are present, while
unfinished UI is labeled as unfinished rather than presented as complete;
4. formation, telegraph, Directive, Cache, Anchor, Strain, profession, and item
concepts are sufficiently visible to proceed to a dynamic vertical slice;
5. any page family or chapter direction should be redesigned before runtime
@@ -66,9 +67,11 @@ Every screen entry shows:
- previous/next page actions plus direct selection from the left list;
- a full-size viewing mode without cropping the game canvas.
The mockup renderer uses the canonical shell for the selected prefix and adds
page-specific state. Core flow pages receive more detailed compositions. The
prototype must not imply that all 82 pages have final bespoke raster art.
Owner review on 2026-08-10 rejected the shared-template result as
incomprehensible. The coverage audit found `1` dedicated page, `40` shared
family mockups, and `41` pages without an individual UI composition. A shared
template is not a completed page. Until this gap is closed, the HTML may expose
requirements and coverage but must not call itself a complete UI prototype.
## 4. Compressed Click Path
@@ -90,8 +93,10 @@ Title SYS-004
-> Guild transformed GLD-001
```
This proves navigation and information hierarchy only. It does not simulate
combat formulas, persistence, pacing, audio, touch feel, or fun.
This currently proves only the required node order. It does not yet prove
navigation comprehension or information hierarchy because several core nodes
still lack their own page design. It also does not simulate combat formulas,
persistence, pacing, audio, touch feel, or fun.
## 5. Visual Assets In This Prototype
@@ -121,6 +126,9 @@ legible and do not depend on generated text.
## 7. Prototype Acceptance
- [x] All 82 screen IDs appear exactly once in the screen selector.
- [x] Coverage is reported truthfully as 1 dedicated / 40 family / 41 missing.
- [ ] All 82 requirements have page-specific, player-readable UI compositions.
- [ ] All 17 core-flow nodes have dedicated UI and pass owner comprehension.
- [x] All eight chapters and 24 chapter screen families are represented.
- [x] Content indexes show 30/36/100/320/48/20 exact counts.
- [x] Core click path reaches restoration without a dead control.
@@ -129,5 +137,5 @@ legible and do not depend on generated text.
- [x] Generated concepts remain labeled honestly and carry task provenance.
- [x] Local links, image paths, JSON, browser verification, Gallery publication,
and Gallery health/share checks pass.
- [x] Owner visual, comprehension, fun, audio, device, and release gates remain
explicitly open.
- [x] Owner comprehension failed on the generic screen presentation; visual,
fun, audio, device, and release gates remain explicitly open.
+14 -10
View File
@@ -4,10 +4,15 @@ Date: 2026-08-10
## Decision
The complete static design package and owner-facing HTML prototype are ready
for owner review. Formal game implementation remains frozen. This result does
not claim that the game is playable, understandable, fun, visually accepted,
heard, device-verified, packaged, or release-ready.
`FAIL`: the static system/content specifications are indexed, but the
owner-facing UI package is not complete and is not ready for approval. Owner
review on 2026-08-10 could not understand `SYS-001`; inspection proved that the
82 entries were rendered through six shared templates rather than 82 designed
pages. Formal game implementation remains frozen.
Earliest failed boundary: page-specific visual communication. `SYS-001`
incorrectly inherited a windmill background, save/cache HUD, return action and
`none` button even though its contract is an automatic boot/loading state.
## Verified Design Authority
@@ -17,10 +22,10 @@ heard, device-verified, packaged, or release-ready.
| Battle and systems | six-person formation, telegraphs, Directives, automation, failure and recovery | specified and cross-checked |
| Economy and save | 1,700 authored minutes, five pacing traces, prices, caps, atomic save and corruption handling | static validator pass |
| Content | 30 characters, 36 professions, 100 enemies, 320 items, 8 chapters, 48 achievements, 20 collections | complete indexed rows |
| Pages and states | 82 pages, 24 chapter visual families, onboarding, accessibility, localization, errors and exits | complete indexed rows |
| Pages and states | 82 requirements indexed | 1 dedicated page / 40 family mockups / 41 missing page compositions |
| Art production | character, profession, enemy, item, environment, UI, animation and VFX identities | production catalog complete; final assets open |
| Audio production | 30 music suites, 94 ambience configurations, at least 2,602 bilingual voice assets, 710 SFX identities | production catalog complete; generation/listening open |
| HTML prototype | 11 views, 82-screen selector, 17-step flow, 8 chapters and searchable content catalogs | browser verified |
| HTML shell | 11 views, selector, flow order, chapters and searchable content catalogs | technically browser verified; product comprehension failed |
## Reproducible Local Checks
@@ -53,12 +58,11 @@ ABG_PROTOTYPE_BROWSER_OK views=11 screens=82 flow_steps=17 chapters=8 responsive
## Owner Review Order
1. Complete the 17-step core flow and name any step whose purpose or result is
unclear.
1. Do not ask the owner to review the 82-page set again yet.
2. Inspect all eight chapter directions and judge whether each promises a
strategically and visually different problem.
3. Inspect the 82-screen map for missing pages, crowded pages, unclear primary
actions, and unsafe exits.
3. Produce dedicated title, Guild, formation, map, route, battle, result,
reward, Anchor and restoration pages before the next owner candidate.
4. Search the complete content catalogs and challenge roles, counters,
progression relationships, and production feasibility.
5. Accept, revise, or reject the visual direction and product scope before a
+99 -33
View File
@@ -10,8 +10,8 @@ const state = {
const viewNames = {
overview: "总览",
screens: "全部页面",
flow: "核心流程",
screens: "页面规格与缺口",
flow: "流程规格链",
chapters: "八章画面",
characters: "角色图鉴",
professions: "职业系统",
@@ -25,7 +25,7 @@ const viewNames = {
const screenCategoryNames = {
ALL: "全部",
SYS: "系统",
GLD: "Guild",
GLD: "公会",
WRK: "工坊",
REC: "档案",
WLD: "世界",
@@ -35,6 +35,52 @@ const screenCategoryNames = {
SET: "设置",
};
const localizedScreenNames = {
"SYS-001": "启动画面",
"SYS-002": "首次无障碍设置",
"SYS-003": "语言与安全区校准",
"SYS-004": "标题首页",
"SYS-005": "游戏存档",
"SYS-006": "新游戏设置",
"SYS-007": "回归摘要",
"SYS-008": "存档恢复",
"SYS-009": "完成度总览",
"SYS-010": "制作人员、许可与数据说明",
};
function screenName(screen) {
return localizedScreenNames[screen.id] || screen.name;
}
function layoutKind(screen) {
const prefix = screen.id.slice(0, 3);
if (screen.id === "SYS-001") return "dedicated";
if (screen.id === "GLD-005") return "family-formation";
if (["EXP-002", "EXP-003", "WLD-001", "WLD-002", "WLD-003"].includes(screen.id)) return "family-route";
if (prefix === "BAT" || ["EXP-004", "RES-001", "RES-002", "RES-003", "RES-004"].includes(screen.id)) return "family-battle";
if (prefix === "SET") return "family-settings";
if (prefix === "REC") return "family-records";
return "missing";
}
function coverage(screen) {
const kind = layoutKind(screen);
if (kind === "dedicated") return { key: "dedicated", label: "逐页设计" };
if (kind.startsWith("family-")) return { key: "family", label: "页面族示意" };
return { key: "missing", label: "未设计" };
}
function reviewCopy(screen) {
if (screen.id === "SYS-001") {
return {
focal: "游戏标志、加载光线、当前载入状态",
action: "无需操作;完成后自动进入首次设置或标题首页",
variants: "首次启动、内容迁移、可恢复载入错误;错误时才显示重试与诊断入口",
};
}
return screen;
}
const chapterImages = [
"assets/web/ch_ch01.webp",
"assets/web/ch_ch02.webp",
@@ -141,7 +187,7 @@ function renderOverview() {
</div>
</div>
<div class="metric-row">
${metric(counts.screens, "完整页面与状态定义")}
${metric(counts.screens, "页面需求定义;UI 尚未逐页完成")}
${metric("8 × 3", "章节路线 / 战场 / 修复画面族")}
${metric(counts.characters, "有姓名、有成长弧的角色")}
${metric(counts.professions, "12 基础 + 24 进阶职业")}
@@ -269,46 +315,61 @@ function renderRecordsStage(screen, art) {
</div>`;
}
function renderGenericStage(screen) {
const prefix = screen.id.slice(0, 3);
const light = prefix === "WRK" || prefix === "GLD";
function renderBootStage() {
return `
${stageTop(screen)}
<div class="stage-title"><small>${escapeHtml(screen.id)} · DESIGN SPEC</small><strong>${escapeHtml(screen.name)}</strong><p>${escapeHtml(screen.focal)}</p></div>
<div class="stage-generic-panel"><h3>当前操作</h3><p>${escapeHtml(screen.action)}</p><div class="stage-list-row">状态与成本可见</div><div class="stage-list-row">取消不会消耗资源</div><div class="stage-list-row">所有拖动都有点选路径</div></div>
<div class="stage-bottom"><button class="stage-secondary" type="button">返回</button><button class="stage-primary" type="button" data-flow-next>${escapeHtml(screen.action)}</button></div>
${light ? "" : ""}`;
<div class="boot-stage">
<div class="boot-sigil" aria-hidden="true"><span></span><i></i></div>
<div class="boot-title"><small>AETHERBOUND GUILD</small><strong>裂脉远征团</strong><p>让每一次修复,改变下一段远征。</p></div>
<div class="boot-progress"><i></i></div>
<p class="boot-status">正在校验远征档案 · 68%</p>
<small class="boot-auto">完成后自动进入,无需点击</small>
</div>`;
}
function renderMissingStage(screen) {
const copy = reviewCopy(screen);
return `
<div class="prototype-missing">
<span>UI COVERAGE GAP · ${escapeHtml(screen.id)}</span>
<strong>${escapeHtml(screenName(screen))}尚未完成页面设计</strong>
<p>目前只有页面需求,不能把这块内容当作游戏画面审核。</p>
<dl><div><dt>玩家首先需要看见</dt><dd>${escapeHtml(copy.focal)}</dd></div><div><dt>页面必须支持</dt><dd>${escapeHtml(copy.action || "无需操作")}</dd></div></dl>
</div>`;
}
function renderGameStage(screen) {
const prefix = screen.id.slice(0, 3);
const kind = layoutKind(screen);
const art = artForScreen(screen);
const light = prefix === "SET" || prefix === "REC";
let content;
if (screen.id === "GLD-005") content = renderFormationStage(screen);
else if (["EXP-002", "EXP-003", "WLD-001", "WLD-002", "WLD-003"].includes(screen.id)) content = renderRouteStage(screen);
else if (prefix === "BAT" || ["EXP-004", "RES-001", "RES-002", "RES-003", "RES-004"].includes(screen.id)) content = renderBattleStage(screen);
else if (prefix === "SET") content = renderSettingsStage(screen);
else if (prefix === "REC") content = renderRecordsStage(screen, art);
else content = renderGenericStage(screen);
if (kind === "dedicated") content = renderBootStage();
else if (kind === "family-formation") content = renderFormationStage(screen);
else if (kind === "family-route") content = renderRouteStage(screen);
else if (kind === "family-battle") content = renderBattleStage(screen);
else if (kind === "family-settings") content = renderSettingsStage(screen);
else if (kind === "family-records") content = renderRecordsStage(screen, art);
else content = renderMissingStage(screen);
const decorativeArt = art && prefix !== "REC" ? `<img class="stage-art" src="${art}" alt="" />` : "";
return `<div class="game-stage ${light ? "stage-light" : ""}" data-stage-id="${escapeHtml(screen.id)}">${decorativeArt}${content}</div>`;
const decorativeArt = art && prefix !== "REC" && kind !== "dedicated" && kind !== "missing" ? `<img class="stage-art" src="${art}" alt="" />` : "";
return `<div class="game-stage ${light ? "stage-light" : ""} stage-${coverage(screen).key}" data-stage-id="${escapeHtml(screen.id)}">${decorativeArt}${content}</div>`;
}
function screenMeta(screen, index, screens) {
const copy = reviewCopy(screen);
const status = coverage(screen);
const previous = screens[(index - 1 + screens.length) % screens.length];
const next = screens[(index + 1) % screens.length];
return `
<div class="screen-meta">
<div class="screen-meta-head">
<div><span class="screen-id">${escapeHtml(screen.id)}</span><h2>${escapeHtml(screen.name)}</h2>${chip("Design specification")} ${chip("Human review open")}</div>
<div><span class="screen-id">${escapeHtml(screen.id)}</span><h2>${escapeHtml(screenName(screen))}</h2>${chip(status.label)} ${chip("人类审核未通过")}</div>
<div class="pager"><button type="button" data-screen-id="${escapeHtml(previous.id)}">← 上一页</button><button type="button" data-screen-id="${escapeHtml(next.id)}">下一页 →</button></div>
</div>
<div class="spec-grid">
<div class="spec-cell"><span>三秒焦点顺序</span><p>${escapeHtml(screen.focal)}</p></div>
<div class="spec-cell"><span>唯一主操作</span><p>${escapeHtml(screen.action)}</p></div>
<div class="spec-cell"><span>状态、异常与退出</span><p>${escapeHtml(screen.variants)}</p></div>
<div class="spec-cell"><span>玩家首先需要看见</span><p>${escapeHtml(copy.focal)}</p></div>
<div class="spec-cell"><span>玩家在这里做什么</span><p>${escapeHtml(copy.action || "无需操作")}</p></div>
<div class="spec-cell"><span>异常与安全退出</span><p>${escapeHtml(copy.variants)}</p></div>
</div>
</div>`;
}
@@ -319,12 +380,16 @@ function renderScreens() {
if (!filtered.some((screen) => screen.id === state.screenId)) state.screenId = filtered[0].id;
const screen = getScreen(state.screenId);
const index = filtered.findIndex((entry) => entry.id === screen.id);
const coverageCounts = state.data.screens.reduce((result, entry) => {
result[coverage(entry).key] += 1;
return result;
}, { dedicated: 0, family: 0, missing: 0 });
return `
<section class="view">
${viewHeading("82 SCREEN DEFINITIONS", "每一个页面和关键状态", "页面按系统、Guild、工坊、档案、世界、远征、战斗、结果与设置分类。每一项都锁定玩家先看什么、主操作是什么,以及错误和返回怎么处理。")}
${viewHeading("UI COVERAGE AUDIT", "82 个页面需求,不是 82 张完成的 UI", "当前只有启动页完成逐页设计;部分页面仅有共用页面族示意,其余仍是规格缺口。这里用于识别要继续设计什么,不能作为完整 UI 验收。", `${chip(`${coverageCounts.dedicated} 逐页设计`)} ${chip(`${coverageCounts.family} 页面族示意`)} ${chip(`${coverageCounts.missing} 未设计`)}`)}
<div class="toolbar"><div class="segmented">${Object.entries(screenCategoryNames).map(([key, label]) => `<button class="${key === category ? "is-active" : ""}" type="button" data-screen-category="${key}">${escapeHtml(label)}</button>`).join("")}</div></div>
<div class="screen-layout">
<aside class="screen-index" aria-label="页面列表">${filtered.map((entry) => `<button class="${entry.id === screen.id ? "is-active" : ""}" type="button" data-screen-id="${escapeHtml(entry.id)}"><span>${escapeHtml(entry.id)}</span><b>${escapeHtml(entry.name)}</b></button>`).join("")}</aside>
<aside class="screen-index" aria-label="页面列表">${filtered.map((entry) => { const status = coverage(entry); return `<button class="${entry.id === screen.id ? "is-active" : ""}" type="button" data-screen-id="${escapeHtml(entry.id)}" data-coverage="${status.key}"><span>${escapeHtml(entry.id)}</span><b>${escapeHtml(screenName(entry))}</b><i class="coverage-${status.key}">${escapeHtml(status.label)}</i></button>`; }).join("")}</aside>
<div class="screen-detail"><div class="screen-stage-wrap">${renderGameStage(screen)}</div>${screenMeta(screen, index, filtered)}</div>
</div>
</section>`;
@@ -335,10 +400,10 @@ function renderFlow() {
const screen = flowScreens[state.flowIndex];
return `
<section class="view">
${viewHeading("CLICKABLE EXPERIENCE", "从首页到章节修复的完整点击链", "这是压缩的设计原型路径,用来检查玩家是否始终知道当前位置、压力、下一步和退出方式。它不模拟战斗公式或存档。", `${chip(`${state.flowIndex + 1} / ${flowScreens.length}`)} ${chip("无死按钮")}`)}
${viewHeading("NAVIGATION SPECIFICATION", "从首页到章节修复的流程规格链", "这条链只证明 17 个需求节点和顺序已经定义;其中多个节点仍没有独立 UI,因此它不是可以进行理解或乐趣验收的产品原型。", `${chip(`${state.flowIndex + 1} / ${flowScreens.length}`)} ${chip("UI 未完成")}`)}
<div class="flow-shell">
<div><div class="screen-stage-wrap">${renderGameStage(screen)}</div>${screenMeta(screen, state.flowIndex, flowScreens)}</div>
<aside class="flow-steps" aria-label="核心流程步骤">${flowScreens.map((entry, index) => `<button class="${index === state.flowIndex ? "is-active" : ""}" type="button" data-flow-index="${index}"><span>${String(index + 1).padStart(2, "0")}</span><b>${escapeHtml(entry.id)}</b>${escapeHtml(entry.name)}</button>`).join("")}</aside>
<aside class="flow-steps" aria-label="流程规格步骤">${flowScreens.map((entry, index) => `<button class="${index === state.flowIndex ? "is-active" : ""}" type="button" data-flow-index="${index}"><span>${String(index + 1).padStart(2, "0")}</span><b>${escapeHtml(entry.id)}</b>${escapeHtml(screenName(entry))}</button>`).join("")}</aside>
</div>
<div class="flow-controls"><button class="text-button" type="button" data-flow-prev>← 上一步</button><button class="flow-action" type="button" data-flow-next>${state.flowIndex === flowScreens.length - 1 ? "回到流程开头" : "执行主操作,进入下一步 →"}</button></div>
</section>`;
@@ -467,18 +532,19 @@ function renderReadiness() {
["战斗 / 职业 / 装备", "完整", "Deterministic combat, 12+24 professions, 320 items", "需通过可玩动态切片验证"],
["经济 / 难度 / 存档", "完整", "1700-min authored budget, five traces, atomic save/failure", "公式静态校验通过;运行时未实现"],
["世界与内容", "完整", "30 characters, 100 enemies, 8 chapters, ending/postgame", "数量与链接校验通过;无人工完整试玩"],
["页面与 UI", "完整规格", "82 screens, 24 chapter screen families, 14 layout families", "本 HTML 等 Owner 视觉/流程审核"],
["页面与 UI", "未完成", "82 个页面需求已定义;1 页逐页设计、40 页页面族示意", "41 页没有独立 UI;其余页面族也必须逐页深化"],
["美术与动画", "完整清单", "Identity/modular production and CH-01 slice", "仅 4 张概念图;无 final/integrated/runtime"],
["音乐 / 环境 / 语音 / SFX", "完整清单", "30 music, 94 ambience, 2602 voice, 710 events", "未生成、未试听、未授权批量 SFX/music"],
["HTML 产品原型", "当前候选", "All indexes + compressed clickable core path", "需 Owner 逐页审阅与修改"],
["HTML 产品原型", "失败待修", "内容索引和响应式外壳可用", "Owner 已证明页面表达不可理解;当前只能用于缺口审计"],
["引擎开发", "冻结", "No Godot project or runtime code", "完整设计 Owner 批准后另开 Goal"],
["设备 / 打包 / 上架", "开放", "No build, signing, device or store action", "必须在运行时完成后分别授权"],
];
const blockedStatuses = new Set(["冻结", "开放", "未完成", "失败待修"]);
return `
<section class="view">
${viewHeading("PRE-DEVELOPMENT GATE", "现在还没有进入正式开发", "策划与生产规格已经大幅补齐,但 Owner 仍需要先审核页面、八章画面和核心流程。开发不能把‘文档完整’误认为‘游戏好玩’。")}
<table class="readiness-table"><thead><tr><th>领域</th><th>状态</th><th>已经具备</th><th>仍然开放</th></tr></thead><tbody>${rows.map(([area, status, ready, open]) => `<tr><td><strong>${escapeHtml(area)}</strong></td><td class="${status === "冻结" || status === "开放" ? "state-open" : "state-ready"}">${escapeHtml(status)}</td><td>${escapeHtml(ready)}</td><td>${escapeHtml(open)}</td></tr>`).join("")}</tbody></table>
<section class="section-band"><h2>你的最短下一步</h2><p>先走一遍“核心流程”,再看八章画面和全部页面。把不理解、不喜欢或认为过于复杂的页面 ID 直接记下来。确认这些设计方向后,才适合制作 CH-01 可玩动态切片。</p><button class="flow-action" type="button" data-open-view="flow">开始 Owner 审核 →</button></section>
${viewHeading("PRE-DEVELOPMENT GATE", "UI 设计没有完成,不能进入正式开发", "系统和内容文档已经建立,但 82 页 UI 只有 1 页逐页设计。Owner 已确认现有页面难以理解,因此完整产品原型验收失败。")}
<table class="readiness-table"><thead><tr><th>领域</th><th>状态</th><th>已经具备</th><th>仍然开放</th></tr></thead><tbody>${rows.map(([area, status, ready, open]) => `<tr><td><strong>${escapeHtml(area)}</strong></td><td class="${blockedStatuses.has(status) ? "state-open" : "state-ready"}">${escapeHtml(status)}</td><td>${escapeHtml(ready)}</td><td>${escapeHtml(open)}</td></tr>`).join("")}</tbody></table>
<section class="section-band"><h2>下一步不是让你继续猜</h2><p>先逐页完成标题、公会、编队、地图、路线、战斗、结算、奖励、锚点和修复等核心链 UI,再扩展其余页面。新的审核候选必须让页面目的、当前状态和唯一主操作直接可见。</p><button class="flow-action" type="button" data-open-view="screens">查看 UI 缺口 →</button></section>
</section>`;
}
+2 -2
View File
@@ -22,8 +22,8 @@
</div>
<nav class="primary-nav" id="primary-nav">
<button class="nav-item is-active" data-view="overview" type="button"><span>01</span> 总览</button>
<button class="nav-item" data-view="screens" type="button"><span>02</span> 全部页面 <b>82</b></button>
<button class="nav-item" data-view="flow" type="button"><span>03</span> 核心流程</button>
<button class="nav-item" data-view="screens" type="button"><span>02</span> 页面规格与缺口 <b>82</b></button>
<button class="nav-item" data-view="flow" type="button"><span>03</span> 流程规格链</button>
<button class="nav-item" data-view="chapters" type="button"><span>04</span> 八章画面 <b>24</b></button>
<button class="nav-item" data-view="characters" type="button"><span>05</span> 角色 <b>30</b></button>
<button class="nav-item" data-view="professions" type="button"><span>06</span> 职业 <b>36</b></button>
+29 -2
View File
@@ -96,9 +96,13 @@ p { color: var(--ink-650); line-height: 1.7; }
.segmented button.is-active { color: #fff; background: var(--ink-800); }
.screen-layout { display: grid; grid-template-columns: minmax(220px, 300px) minmax(0, 1fr); gap: clamp(18px, 2.5vw, 34px); align-items: start; }
.screen-index { position: sticky; top: calc(var(--topbar) + 20px); max-height: calc(100vh - var(--topbar) - 40px); overflow: auto; background: var(--white); border: 1px solid var(--line); }
.screen-index button { width: 100%; min-height: 52px; display: grid; grid-template-columns: 68px minmax(0,1fr); gap: 8px; align-items: center; padding: 9px 12px; text-align: left; color: var(--ink-650); background: transparent; border: 0; border-bottom: 1px solid var(--line); }
.screen-index button { width: 100%; min-height: 58px; display: grid; grid-template-columns: 68px minmax(0,1fr) auto; gap: 8px; align-items: center; padding: 9px 12px; text-align: left; color: var(--ink-650); background: transparent; border: 0; border-bottom: 1px solid var(--line); }
.screen-index button span { font-size: 11px; font-weight: 800; color: var(--copper); }
.screen-index button b { overflow: hidden; font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
.screen-index button i { padding: 3px 5px; font-size: 9px; font-style: normal; font-weight: 800; white-space: nowrap; border: 1px solid var(--line); border-radius: 3px; }
.coverage-dedicated { color: #fff; background: var(--secured); }
.coverage-family { color: var(--ink-800); background: var(--paper-200); }
.coverage-missing { color: #fff; background: #a54640; }
.screen-index button.is-active { color: var(--ink-950); background: #f1ead8; box-shadow: inset 4px 0 var(--gold); }
.screen-detail { min-width: 0; }
.screen-stage-wrap { padding: clamp(8px, 1.2vw, 16px); background: #d9d4c7; border: 1px solid var(--line); box-shadow: var(--shadow); }
@@ -107,6 +111,7 @@ p { color: var(--ink-650); line-height: 1.7; }
.game-stage::after { content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none; background: linear-gradient(180deg, rgba(10,15,18,.48) 0, transparent 22%, transparent 70%, rgba(10,15,18,.68) 100%); }
.game-stage.stage-light { color: var(--ink-950); background: #e6e0d2; }
.game-stage.stage-light::after { background: linear-gradient(180deg, rgba(247,243,232,.2), rgba(247,243,232,.02) 55%, rgba(247,243,232,.55)); }
.game-stage.stage-dedicated::after, .game-stage.stage-missing::after { display: none; }
.game-stage button { min-height: 38px; color: inherit; border: 1px solid rgba(255,255,255,.34); border-radius: 4px; background: rgba(23,28,34,.84); }
.hud-top { position: absolute; inset: 2.2% 2.4% auto; height: 8%; display: flex; justify-content: space-between; gap: 8px; align-items: center; }
.hud-group { display: flex; gap: 7px; align-items: center; }
@@ -163,6 +168,26 @@ p { color: var(--ink-650); line-height: 1.7; }
.stage-generic-panel h3 { font-size: clamp(11px,1.3vw,20px); }
.stage-generic-panel p { color: #dce4e8; font-size: clamp(8px,.78vw,12px); }
.stage-list-row { padding: 5% 0; font-size: clamp(8px,.75vw,12px); border-bottom: 1px solid rgba(255,255,255,.15); }
.boot-stage { position: absolute; inset: 0; display: grid; place-content: center; justify-items: center; padding: 8%; text-align: center; background: radial-gradient(circle at 50% 44%, #2f4650 0, #182127 43%, #0e1418 100%); }
.boot-sigil { position: relative; width: clamp(72px, 9vw, 138px); aspect-ratio: 1; margin-bottom: 5%; border: 2px solid rgba(229,180,69,.5); transform: rotate(45deg); }
.boot-sigil span, .boot-sigil i { position: absolute; display: block; background: var(--gold); box-shadow: 0 0 20px rgba(229,180,69,.45); }
.boot-sigil span { left: 48%; top: 8%; width: 4%; height: 84%; }
.boot-sigil i { left: 8%; top: 48%; width: 84%; height: 4%; }
.boot-title small { color: #b8c5cc; font-size: clamp(8px,.8vw,12px); font-weight: 800; letter-spacing: .12em; }
.boot-title strong { display: block; margin-top: 4px; font-size: clamp(23px,3vw,48px); }
.boot-title p { margin: 7px 0 0; color: #d8e0e3; font-size: clamp(9px,.9vw,14px); }
.boot-progress { width: min(48%, 420px); height: 5px; margin-top: 6%; overflow: hidden; background: rgba(255,255,255,.16); }
.boot-progress i { display: block; width: 68%; height: 100%; background: var(--gold); }
.boot-status { margin: 10px 0 0; color: #fff; font-size: clamp(9px,.8vw,13px); }
.boot-auto { margin-top: 5px; color: #8f9ba2; font-size: clamp(8px,.7vw,11px); }
.prototype-missing { position: absolute; inset: 0; display: grid; align-content: center; padding: 10% 12%; color: var(--ink-950); background: repeating-linear-gradient(-45deg, #f4efe3, #f4efe3 12px, #ede5d5 12px, #ede5d5 24px); }
.prototype-missing > span { margin-bottom: 12px; color: #a54640; font-size: clamp(8px,.75vw,12px); font-weight: 900; }
.prototype-missing > strong { max-width: 720px; font-size: clamp(20px,2.7vw,42px); line-height: 1.15; }
.prototype-missing > p { max-width: 680px; margin: 10px 0 22px; font-size: clamp(10px,.9vw,14px); }
.prototype-missing dl { width: min(100%, 760px); margin: 0; display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 1px; background: var(--line); border: 1px solid var(--line); }
.prototype-missing dl div { padding: 14px; background: rgba(255,254,249,.92); }
.prototype-missing dt { margin-bottom: 6px; color: var(--copper); font-size: clamp(8px,.7vw,10px); font-weight: 900; }
.prototype-missing dd { margin: 0; color: var(--ink-650); font-size: clamp(9px,.8vw,12px); line-height: 1.45; }
.screen-meta { margin-top: 24px; padding: 0 2px; }
.screen-meta-head { display: flex; justify-content: space-between; gap: 16px; align-items: start; }
.screen-id { color: var(--copper); font-weight: 900; }
@@ -265,13 +290,15 @@ p { color: var(--ink-650); line-height: 1.7; }
.overview-hero img { aspect-ratio: 16/9; object-fit: contain; }
.screen-layout { display: block; }
.screen-index { position: static; display: flex; max-height: none; overflow-x: auto; margin-bottom: 14px; }
.screen-index button { flex: 0 0 150px; grid-template-columns: 1fr; border-right: 1px solid var(--line); border-bottom: 0; }
.screen-index button { flex: 0 0 170px; grid-template-columns: 1fr auto; border-right: 1px solid var(--line); border-bottom: 0; }
.screen-index button > span { grid-column: 1 / -1; }
.screen-index button.is-active { box-shadow: inset 0 -4px var(--gold); }
.screen-index button b { white-space: normal; }
.screen-stage-wrap { padding: 5px; }
.screen-meta-head { display: block; }
.pager { margin-top: 12px; }
.spec-grid { grid-template-columns: 1fr; }
.prototype-missing dl { grid-template-columns: 1fr; }
.spec-cell { min-height: 0; }
.chapter-tabs { display: flex; overflow-x: auto; }
.chapter-tabs button { flex: 0 0 70px; }
+11
View File
@@ -63,6 +63,17 @@ async function main() {
assert.equal(await page.locator(".screen-index button").count(), 82, "screen inventory count");
const ids = await page.locator(".screen-index button span").allTextContents();
assert.equal(new Set(ids).size, 82, "screen IDs must be unique");
assert.equal(await page.locator('[data-coverage="dedicated"]').count(), 1, "dedicated page count");
assert.equal(await page.locator('[data-coverage="family"]').count(), 40, "shared-family page count");
assert.equal(await page.locator('[data-coverage="missing"]').count(), 41, "missing page count");
await page.locator('[data-screen-id="SYS-001"]').first().click();
assert.ok(await page.locator('[data-stage-id="SYS-001"] .boot-stage').isVisible(), "dedicated boot page");
assert.equal(await page.locator('[data-stage-id="SYS-001"] button').count(), 0, "boot has no player action");
assert.equal(await page.locator('[data-stage-id="SYS-001"]').getByText(/Cache|Saved|none/).count(), 0, "boot excludes unrelated HUD");
await page.screenshot({ path: path.join(outputDir, "desktop-boot-screen.png"), fullPage: true });
await page.locator('[data-screen-id="SYS-004"]').first().click();
assert.ok(await page.locator('[data-stage-id="SYS-004"] .prototype-missing').isVisible(), "title gap is explicit");
await page.screenshot({ path: path.join(outputDir, "desktop-title-gap.png"), fullPage: true });
await page.locator('[data-screen-id="BAT-002"]').first().click();
assert.ok(await page.locator('[data-stage-id="BAT-002"]').isVisible(), "battle screen stage");
assert.ok(await page.locator(".battle-telegraph").isVisible(), "battle telegraph");