页面从设计稿到可玩页面的进度
这里汇总每一个玩家页面的 AI 稿、解构板、素材清单、运行时预览、当前生产门和剩余缺口。解构评审与素材评审独立于编码,避免错误理解直接进入 Godot。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Build the durable Aetherbound UI design/status gallery.""" from __future__ import annotations from datetime import datetime, timezone import json from pathlib import Path import re import shutil from typing import Any ROOT = Path(__file__).resolve().parents[1] OUTPUT_DIR = ROOT / "docs/design-authority/gallery" OUTPUT = OUTPUT_DIR / "index.html" ASSET_DIR = OUTPUT_DIR / "assets" DESIGN_INDEX = ROOT / "docs/design-authority/design-index.json" PAGE_PLAN = ROOT / "docs/03_PAGES_AND_UX.md" STAGES = [ "SPEC_READY", "AI_DRAFT", "DRAFT_REVIEW", "APPROVED_FOR_RECONSTRUCTION", "DECONSTRUCTION_IN_PROGRESS", "DECONSTRUCTION_REVIEW", "ASSET_GENERATION_IN_PROGRESS", "ASSET_REVIEW", "RECONSTRUCTION_READY", "IMPLEMENTATION_IN_PROGRESS", "IMPLEMENTED_AWAITING_OWNER_RUNTIME_REVIEW", "VERIFIED_AWAITING_OWNER_ACCEPTANCE", "COMPLETE", ] STAGE_DEFINITIONS = [ ("SPEC_READY", "规格", "页面合同、状态矩阵、响应式与文案约束"), ("AI_DRAFT", "AI 设计稿", "提示词、生成记录、原始稿与来源"), ("DRAFT_REVIEW", "AI 稿评审", "Owner 确认构图、层级、主体和文字槽位"), ("APPROVED_FOR_RECONSTRUCTION", "批准解构", "冻结视觉方向,允许进入解构,不等于允许编码"), ("DECONSTRUCTION_IN_PROGRESS", "解构中", "图层、焦点、引擎文字、响应式锚点与交互映射"), ("DECONSTRUCTION_REVIEW", "解构评审", "Owner 检查解构是否忠实且可实现"), ("ASSET_GENERATION_IN_PROGRESS", "素材生成中", "背景清理、角色、道具、图标和组件素材"), ("ASSET_REVIEW", "素材评审", "检查来源、裁切、比例、透明度、无烘焙文字"), ("RECONSTRUCTION_READY", "重建包就绪", "runtime spec、素材清单、命中区域和状态绑定"), ("IMPLEMENTATION_IN_PROGRESS", "编码中", "Godot 页面、状态、输入与本地化实现"), ("IMPLEMENTED_AWAITING_OWNER_RUNTIME_REVIEW", "运行候选", "行为完成,等待真实截图审查"), ("VERIFIED_AWAITING_OWNER_ACCEPTANCE", "证据待验收", "触控、响应式、截图和诊断证据齐全"), ("COMPLETE", "完成", "Owner runtime acceptance 已记录"), ] STATUS_LABELS = { "COMPLETE": ("已完成", "complete"), "SPEC_READY": ("规格已就绪", "active"), "AI_DRAFT": ("AI 稿生成中", "active"), "DRAFT_REVIEW": ("AI 稿待评审", "active"), "APPROVED_FOR_RECONSTRUCTION": ("已批准解构", "active"), "DECONSTRUCTION_IN_PROGRESS": ("解构中", "active"), "DECONSTRUCTION_REVIEW": ("解构待评审", "active"), "ASSET_GENERATION_IN_PROGRESS": ("素材生成中", "active"), "ASSET_REVIEW": ("素材待评审", "active"), "RECONSTRUCTION_READY": ("重建包就绪", "active"), "IMPLEMENTATION_IN_PROGRESS": ("编码中", "active"), "IMPLEMENTED_AWAITING_OWNER_RUNTIME_REVIEW": ("运行候选待验收", "active"), "VERIFIED_AWAITING_OWNER_ACCEPTANCE": ("证据完成待验收", "active"), "NOT_STARTED": ("未开始", "not-started"), } RUNTIME_PREVIEWS = { "SYS-001": "runtime/assets/entry/boot/background.png", "SYS-002": "runtime/assets/entry/first_launch/background.png", "SYS-003": "runtime/assets/backgrounds/title_master_v002.png", "SYS-005": "runtime/assets/backgrounds/p9_3_guild_hall.png", "HUB-001": "runtime/assets/backgrounds/guild_master_b_composite.png", } DECONSTRUCTION_BOARDS = { "HUB-001": "docs/design-authority/pages/HUB-001-guild-desk/reconstruction/v001/deconstruction-board.png", "PTY-001": "docs/design-authority/pages/PTY-001-party-line/reconstruction/v001/deconstruction-board.png", "PTY-002": "docs/design-authority/pages/PTY-002-readiness-review/reconstruction/v001/deconstruction-board.png", "RSK-002": "docs/design-authority/pages/RSK-002-risk-commitment/reconstruction/v001/deconstruction-board.png", "BAT-001": "docs/design-authority/pages/BAT-001-battle-opening/reconstruction/v001/deconstruction-board.png", "BAT-002": "docs/design-authority/pages/BAT-002-live-battle/reconstruction/v001/deconstruction-board.png", } ASSET_CONTACT_SHEETS = { "HUB-001": "docs/design-authority/pages/HUB-001-guild-desk/reconstruction/v001/asset-contact-sheet.png", "PTY-001": "docs/design-authority/pages/PTY-001-party-line/reconstruction/v001/asset-contact-sheet.png", "PTY-002": "docs/design-authority/pages/PTY-002-readiness-review/reconstruction/v001/asset-contact-sheet.png", "RSK-002": "docs/design-authority/pages/RSK-002-risk-commitment/reconstruction/v001/asset-contact-sheet.png", "BAT-001": "docs/design-authority/pages/BAT-001-battle-opening/reconstruction/v001/asset-contact-sheet.png", "BAT-002": "docs/design-authority/pages/BAT-002-live-battle/reconstruction/v001/asset-contact-sheet.png", } DRAFTS = { "SYS-003": "docs/design-authority/pages/SYS-003-title/drafts/v002/title-v002-raw.png", "PTY-002": "docs/design-authority/pages/PTY-002-readiness-review/drafts/v001/readiness-v001-raw.png", } GROUPS = { "SYS-001": "入口模块", "SYS-002": "入口模块", "SYS-003": "入口模块", "SYS-005": "入口模块", "SYS-008": "入口模块", } def relative_url(path: Path) -> str | None: if not path.is_file(): return None return Path(__import__("os").path.relpath(path, OUTPUT_DIR)).as_posix() def copy_gallery_asset(source: Path, key: str) -> str | None: if not source.is_file(): return None ASSET_DIR.mkdir(parents=True, exist_ok=True) target = ASSET_DIR / f"{key}{source.suffix.lower()}" shutil.copy2(source, target) return (Path("assets") / target.name).as_posix() def page_rows() -> list[tuple[str, str]]: pattern = re.compile(r"^\|\s*`([A-Z]{3}-\d{3})`\s*\|\s*([^|]+?)\s*\|", re.MULTILINE) rows: list[tuple[str, str]] = [] seen: set[str] = set() for page_id, name in pattern.findall(PAGE_PLAN.read_text(encoding="utf-8")): if page_id not in seen: rows.append((page_id, name.strip())) seen.add(page_id) return rows def image_field(entry: dict[str, Any], field: str, page_id: str) -> str | None: value = entry.get(field) if not value: return None candidate = ROOT / "docs/design-authority" / str(value) return copy_gallery_asset(candidate, f"{page_id.lower()}-{field}") def build_page(page_id: str, name: str, entry: dict[str, Any]) -> dict[str, Any]: status = str(entry.get("status", "NOT_STARTED")) if not entry: status = "NOT_STARTED" approved = image_field(entry, "approved", page_id) or image_field(entry, "master", page_id) draft = image_field(entry, "draft", page_id) or image_field(entry, "next_draft", page_id) if page_id in DRAFTS: draft = copy_gallery_asset(ROOT / DRAFTS[page_id], f"{page_id.lower()}-draft") runtime = copy_gallery_asset(ROOT / RUNTIME_PREVIEWS[page_id], f"{page_id.lower()}-runtime") if page_id in RUNTIME_PREVIEWS else None if entry.get("runtime_preview"): runtime = image_field(entry, "runtime_preview", page_id) runtime_captures = [] for index, capture in enumerate(entry.get("runtime_captures", [])): copied = copy_gallery_asset(ROOT / "docs/design-authority" / str(capture), f"{page_id.lower()}-runtime-capture-{index + 1}") if copied: runtime_captures.append({"src": copied, "path": str(capture)}) deconstruction = copy_gallery_asset(ROOT / DECONSTRUCTION_BOARDS[page_id], f"{page_id.lower()}-deconstruction") if page_id in DECONSTRUCTION_BOARDS else None deconstruction_detail = image_field(entry, "deconstruction_detail", page_id) assets = copy_gallery_asset(ROOT / ASSET_CONTACT_SHEETS[page_id], f"{page_id.lower()}-assets") if page_id in ASSET_CONTACT_SHEETS else None label, tone = STATUS_LABELS.get(status, (status, "not-started")) stage = STAGES.index(status) + 1 if status in STAGES else 0 if status == "COMPLETE": missing = "无,已完成设计、实现、验证和 Owner 验收。" elif status == "SPEC_READY": missing = "生成并评审个人 AI 设计稿,完成重建素材和 runtime 设计包。" elif status == "DRAFT_REVIEW": missing = "等待 Owner 评审 AI 设计稿;通过后才能进入批准解构。" elif status == "APPROVED_FOR_RECONSTRUCTION": missing = "视觉方向已批准;下一步先完成图层/文字/交互解构与 Owner 解构评审。" elif status == "DECONSTRUCTION_IN_PROGRESS": missing = "完成图像取证、图层归属、引擎文字槽位、响应式锚点和命中区域。" elif status == "DECONSTRUCTION_REVIEW": missing = "等待 Owner 检查解构映射;通过后才能生成和处理素材。" elif status == "ASSET_GENERATION_IN_PROGRESS": missing = "按已审查的解构清单生成背景、角色、道具、图标和组件素材。" elif status == "ASSET_REVIEW": missing = "等待 Owner 检查素材来源、比例、裁切、透明度和无烘焙文字;通过后才能进入重建包。" elif status == "RECONSTRUCTION_READY": missing = "重建包已就绪,下一步才允许进入 Godot 编码。" elif status == "IMPLEMENTATION_IN_PROGRESS": missing = "先通过批准稿与 Runtime 的视觉保真检查,再继续页面状态、输入、响应式和本地化验收。" elif status == "DECONSTRUCTION_REVIEW": missing = "等待 Owner 检查图层归属、引擎文字槽位、响应式锚点、命中区域和素材 provenance;通过后才能进入素材/重建就绪。" elif status == "NOT_STARTED": missing = "尚未进入当前页面生产流程。" else: missing = "继续当前状态的页面生产门,不能跳过 Owner 评审。" stage_details = [] current_index = stage - 1 for index, (stage_id, stage_label, deliverable) in enumerate(STAGE_DEFINITIONS): if status == "COMPLETE" or index < current_index: phase = "done" elif index == current_index and status != "NOT_STARTED": phase = "current" else: phase = "pending" stage_details.append({ "id": stage_id, "label": stage_label, "deliverable": deliverable, "phase": phase, }) group = GROUPS.get(page_id) if group is None: prefix = page_id.split("-", 1)[0] group = { "SET": "设置与数据", "HUB": "公会与经营", "SHP": "市场与招募", "PTY": "队伍准备", "RSK": "风险选择", "BAT": "战斗循环", "OUT": "结算与恢复", "PRO": "成长与进度", "PST": "结局与后期", }.get(prefix, "未分类页面") references = [] for key in ("contract", "state_matrix", "review", "approved", "approved_manifest", "visual_reference", "scale_reference", "rejected_draft", "rejected_draft_manifest", "rejected_draft_review", "deconstruction", "deconstruction_detail", "deconstruction_plan", "deconstruction_review", "deconstruction_text_slots", "deconstruction_hit_areas", "top_rail_decomposition", "asset_plan", "asset_manifest", "asset_contact_sheet", "asset_review", "rail_slice_manifest", "rail_fit_preview", "reconstruction_spec", "runtime_asset_manifest", "runtime_hit_areas", "runtime", "preflight", "verification", "visual_fidelity", "owner_acceptance", "provenance"): if entry.get(key): path = str(entry[key]) prefix = "../" if path.startswith(("pages/", "modules/")) else "../../../" references.append({"label": key, "path": path, "url": prefix + path}) return { "id": page_id, "name": name, "group": group, "status": status, "statusLabel": label, "tone": tone, "stage": stage, "stageTotal": len(STAGES), "stageDetails": stage_details, "approved": approved, "draft": draft, "deconstruction": deconstruction, "deconstructionDetail": deconstruction_detail, "assets": assets, "runtime": runtime, "runtimeCaptures": runtime_captures, "missing": missing, "references": references, "search": f"{page_id} {name} {group} {label} {missing}".lower(), } def gallery_data() -> dict[str, Any]: index = json.loads(DESIGN_INDEX.read_text(encoding="utf-8")) entries = index.get("pages", {}) pages = [build_page(page_id, name, entries.get(page_id, {})) for page_id, name in page_rows()] counts = { "total": len(pages), "complete": sum(page["status"] == "COMPLETE" for page in pages), "active": sum(page["status"] != "NOT_STARTED" and page["status"] != "COMPLETE" for page in pages), "notStarted": sum(page["status"] == "NOT_STARTED" for page in pages), "withDesign": sum(bool(page["approved"] or page["draft"]) for page in pages), "withRuntime": sum(bool(page["runtime"]) for page in pages), } return { "generatedAt": datetime.now(timezone.utc).astimezone().isoformat(timespec="minutes"), "stages": STAGES, "pages": pages, "counts": counts, } HTML = r'''
这里汇总每一个玩家页面的 AI 稿、解构板、素材清单、运行时预览、当前生产门和剩余缺口。解构评审与素材评审独立于编码,避免错误理解直接进入 Godot。