一键导入
create-new-game
BoardGame 新游戏创建或资源/data intake 流程。用于新增游戏、只给图片/位置先开工;按现有游戏模式分阶段推进并验收。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
BoardGame 新游戏创建或资源/data intake 流程。用于新增游戏、只给图片/位置先开工;按现有游戏模式分阶段推进并验收。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
BoardGame 项目内参考图生成 Three.js 程序化模型流程。用于 img2threejs、图生模型、参考图重建、Three.js 游戏资产原型、书本/棋盘/卡牌/道具 3D 模型任务;在动手写模型前先拆参考图结构并建立“参考图区域到模型部件/材质”的追踪表,禁止把参考图当整面贴图贴皮,也禁止脱离参考图生成泛主题模型。
BoardGame 线上反馈批量收口流程。用于未关闭反馈、排重、真假 bug 分诊、并行修复、状态回写、关闭误报/重复反馈。
BoardGame 规则 bug 修复流程。用于卡牌、技能、Token、状态、阶段、伤害、资源、升级/基础差异、结算顺序和审计漏审。
BoardGame Git 操作入口。用于提交、推送、同步主分支、pre-push 阻塞、PR、worktree、fork、merge 等协作场景。
BoardGame 截图交付流程。用于打开图、给我看图、看截图、图呢、端到端截图;最终验收图默认走图片预览站。
Smash Up 新增派系端到端流程。用于新增派系、卡牌/基地素材、从素材做到可玩;含 intake、上传、数据、玩法、审计、E2E。
| name | create-new-game |
| description | BoardGame 新游戏创建或资源/data intake 流程。用于新增游戏、只给图片/位置先开工;按现有游戏模式分阶段推进并验收。 |
核心原则:每个阶段独立可验证、独立可提交。阶段之间不留 TODO 缺口。AI 必须在完成当前阶段验收后才能进入下一阶段。 新增游戏第一步不是写 Board:在规则来源和素材目录已给出时,第一批实质动作必须是规则数据录入、规则对象抽取、素材候选定位、语义命名与正式落盘/替代裁定。低保真程序化界面只能算原型,不能绕过素材 intake。 用户给出 URL / 哈希素材 / DOM / 结算界面时先录入:用户当轮或历史上下文里已经给出可用素材 URL、Steam CDN/Workshop 哈希、DOM、CSS、结算截图、BGG 教程页或本地素材目录时,第一动作必须把这些来源写进 intake 表,执行下载/定位/命名/裁切/落盘或明确失败;不得先做程序化 UI、空素材 Board、提案文档或截图验收。若未找到用户点名素材,必须把“查找命令、失败路径、最小补救”写成缺口,不得标完成。 第一步必须产生可执行矩阵:阶段 0 不是一句“已看素材”,而是必须落成规则对象素材矩阵;矩阵每行必须能驱动下一步查找、重命名、裁切、落盘、接线、替代批准或阻塞。 多剧本 / 多模式 / 多场景必须先建子账本:如果游戏规则由多个剧本、关卡、模式、boss、事件包、地图、作祟、任务或胜利条件组合而成,不能用 1 个代表样例外推完整规则。必须先建立总索引、官方源段映射和逐项子账本门禁;未完成子账本的条目只能标为
source-mapped-contract-pending、representative-only或blocked,不得称为基础功能完成。 执行顺序红线:阶段 0 未形成可验证的规则对象素材矩阵前,不得把目录骨架、Board、E2E、截图、提案或收口文档当成主交付;如果已经误入后续阶段,必须立刻降回in_progress并先补阶段 0。 录入账本必须一致:同一源图、DOM/HTML/TTS 存档、裁切合同、manifest、运行时代码和完成状态不得互相打架;只要某个源图已被裁切落盘或运行时引用,候选表和分类合同必须同步改为pass,不得仍保留blocked、基础版不接入或intake-closed。 指定参考未命中必须阻塞:用户点名 DOM、截图、电子版、规则书、BGG 页面、TTS 存档或某个素材文件时,必须先证明该参考已读取且可用;若文件缺失、为空、不可解析或内容不覆盖目标,必须停在blocked/in_progress并汇报缺口,不能改用相邻来源继续实施或标完成。若某个参考为空,只能说明“这个参考为空”,还必须继续查同批目录、用户 URL、缓存和可爬取页面;只有全部候选来源都实际查过且失败,才可把素材/布局标为blocked。 素材来源不等于 UI 风格:TTS/Workshop/BGG/DOM/HTML 只能作为素材、结构、位置或交互参考;视觉风格必须按docs/ai-rules/ui-ux.md、design-system/styles/和每个游戏自己的风格裁定执行。禁止因为使用 TTS 素材或坐标,就把主 UI 做成 TTS 桌面风格。 每个游戏必须有自己的风格:新增游戏不是把通用牌桌、其它游戏壳层或 Tailwind 面板换名。进入 Board/UI 实现前,必须先写design-system/games/<gameId>.md,并落成可截图验收的独立风格合同:主题气质、主视觉素材、桌面关系、色彩材质、按钮/HUD 语气、框体数量来源、与同仓其它游戏的差异。首屏看不出该游戏自己的主题和素材语法时,UI 必须保持in_progress。 Board/UI 前先走 Design I/O(强制):阶段 0 已锁定规则对象、素材和布局真相源后,进入新游戏主 UI、设计稿、布局收敛或 Board 实现前,必须先读D:\codex-home\skills\ui-design-pipeline\SKILL.md,产出本游戏的设计声明、执行契约和 evaluator;它只补“为什么这样设计、按什么验收”,不替代素材 intake、OpenSpec、位图生图批准或真实页面验收。 桌面先闭环,手机后降级:固定牌桌/棋盘/桌面区位类新游戏默认先完成桌面真实页面和 AI 复看;桌面截图未过,不得切到手机适配宣称推进。手机只能在已通过桌面合同上做响应式降级,不能反向污染桌面构图、增加多层框体或压缩主对象来凑屏幕。用户未验收桌面时,手机阶段只能记录为后续缺口,不能继续实施。 主桌面少框优先:用户反馈“框多/像框/这么多框”时,默认指半透明面板、黑色矩形容器、重复胶囊壳、占位边框、按钮壳、分区壳、冗余状态框和全局悬浮黑圆等 UI 容器感;不指扑克牌、筹码、棋子等实体素材边界。桌面未过验收前,必须先消除这些框感来源,让游戏实体和桌面素材成为视觉主体,不得转去手机或用 E2E 绿灯收口。 满元素截图是桌面验收基线:桌面 UI 验收不能只截空桌、开场或结算面板;至少要有一张“玩家已经拿过历史标记/筹码、正在拿新标记/筹码、中央主对象和公共资源同屏”的过程态满元素截图。若游戏没有筹码,替换成该游戏的历史选择、当前选择和中央主对象。用户要求看图时必须实际打开当前截图。 缺口必须继续推进或汇报,不得标完成:只要存在可本地推进的数据录入、素材查找、语义命名、DOM/BGG/截图抽取、OpenSpec 补证、真实 UI 修复、截图复看或任务状态更新,就必须继续执行下一步。只有整体证据全过、真实阻塞已记录、或用户明确暂停时,才允许停止;阶段完成、E2E 通过、截图存在、提案写完都不是完成。
详细规则已拆到
references/workflow-boundaries.md。进入 proposal/spec/design/tasks、创建新游戏 worktree、处理主工作区与游戏 worktree 分线、或判断是否上升总框架/百游戏模式时,先读该 reference。
最小执行口径:
本 skill 只做“分阶段流程 + 验收门禁 + 单阶段闭环”。 任何规范/红线/最佳实践若在下列文档中已有定义,必须以它们为准;本 skill 不重复展开。 若本文与下列权威文档出现路径、组件、命令或门禁冲突,先按权威文档执行,并立即修正本文,不得用本文内的旧示例覆盖实施规范。
AGENTS.mddocs/ai-rules/engine-systems.mdD:\codex-home\skills\ui-design-pipeline\SKILL.mddocs/ai-rules/ui-ux.mddocs/ai-rules/golden-rules.mddocs/ai-rules/animation-effects.mddocs/ai-rules/data-entry.mddocs/ai-rules/asset-pipeline.md./.codex/skill/audio-integration/SKILL.md(workflow) + docs/audio/audio-usage.md(合同);新增音频资产流程见 docs/audio/add-audio.mddocs/tools.mddocs/games/smashup/workflows/smashup-faction-intake.mddocs/ai-rules/doc-index.md进入任何目录创建、素材落盘、压缩、资源引用、thumbnail.tsx、criticalImageResolver 或 manifest 资源字段之前,先执行对应实施规范;本 skill 不允许自带第二套路由。
docs/ai-rules/asset-pipeline.md 为单一实施合同。docs/ai-rules/ui-ux.md 为实施合同。docs/ai-rules/engine-systems.md 为实施合同。docs/ai-rules/golden-rules.md 为实施合同。资源实施最低门禁:
public/assets/i18n/zh-CN/<gameId>/...;public/assets/<gameId>/... 只作为历史兼容或 asset-pipeline 明确允许的例外,不得作为新资源默认落点。public/assets/i18n/zh-CN/<gameId>/thumbnails/,运行时由 ManifestGameThumbnail / OptimizedImage 解析。<gameId>/thumbnails/cover;禁止硬编码 /assets/、compressed/、.webp 或版本参数。docs/games/<gameId>/design/,禁止继续散落到 evidence/<gameId>/、temp/、仓库根目录或其它平行入口。design-system/games/<gameId>.md 是可实现 UI 规范 / 实现约束文档;docs/games/<gameId>/design/generated/*.png|jpg|webp 才是位图设计稿 / 视觉稿 / 生图稿;design-system 文档本身当成“设计稿已交付”。docs/games/<gameId>/design/reference/:外部参考、量测底稿、参考 HTML/SVG、brief。docs/games/<gameId>/design/implementable/:可前端复刻的实现骨架稿、布局红稿、实现说明。docs/games/<gameId>/design/generated/:保留的位图生图或最终概念稿。reference 负责“看什么”;implementable 负责“按什么落代码”;generated 负责“最终保留哪张图”。README.md 或等价索引,至少写清:
evidence/ 只放验证、审计、截图结论和收口证据;docs/games/<gameId>/design/ 只放设计输入、实现骨架和保留稿。详细规则已拆到
references/preflight-gates.md。开始目录创建、规则录入、素材落盘、PDF 转 Markdown、图片/位置驱动 intake、对象粒度或资源准入裁定前,先读该 reference。
最小执行口径:
详细规则见
references/preflight-gates.md、references/asset-intake.md与references/mechanics-data-design.md。新增游戏不能先做低保真 Board 再事后补素材;必须先把规则对象和运行时素材需求锁住。
本阶段是新增游戏的第一批实际工作,不是验收时补写的说明。若用户已经给出规则来源和素材目录,默认不得先创建 Board、写 E2E、跑截图或写完成提案;必须先完成下面的录入和素材映射动作。若只给了图片/位置而规则尚不完整,仍先走素材 intake,并把缺失规则对象标为 待补证据,不能用原型 UI 代替 intake。
如果阶段 0 已经被跳过,后续发现缺规则对象、缺素材、缺数据录入、缺 UI 承载或缺正式运行时资源时,不能继续沿当前实现硬凑闭环;必须先把状态改回 in_progress,补 proposal/tasks/spec 与规则对象素材矩阵,再回到实现。
在进入目录骨架、Board、manifest、E2E 或“完成”判断前,必须先完成以下最小闭环:
基础版必需 / 基础版可程序化 / 只作视觉参考 / 扩展后续 / 无法识别待补证据。public/assets/i18n/zh-CN/<gameId>/... 并生成压缩/manifest 证据。Transform、截图或桌面存档,必须先抽取桌面、牌槽、手牌区、token 区、帮助区和主要对象坐标/层级为布局合同;不得因为某个 DOM 文件为空,就跳过其它布局真相源。若用户当轮点名某个来源(例如 DOM 或 BGG 电子版),该来源未读取、为空或不可解析时必须先阻塞汇报,除非用户明确批准改用其它来源。阶段 0 的最低产物必须真实存在:
本阶段的一票否决:
blocked/不接入,但另一个合同或运行时代码已经使用它,必须先修正录入口径并重新验证;不得带着矛盾账本进入完成判断。in_progress,不得宣称“布局已复刻”或“UI 已完成”。in_progress。blocked、base-runtime-candidate、needs-visual-confirmation 或无明确替代批准,整体状态必须保持 in_progress。blocked 并停止完成判断;不得把“后续美术优化”当作绕过理由。未完成本阶段时,只能说“低保真规则链路/原型已跑通”,不得说“新游戏基础版完成”。
目标:建立完整目录结构与最小占位实现,npm run generate:manifests 可成功运行。
默认拆分:中等以上复杂度游戏(命令数 ≥5 或有多阶段回合)从第一天就用拆分结构。
src/games/<gameId>/
manifest.ts # 清单元数据
game.ts # 引擎适配器组装(只做组装,不写逻辑)
Board.tsx # UI 布局组装(逻辑拆到 hooks/,子组件拆到 ui/)
thumbnail.tsx # 缩略图组件
tutorial.ts # 教学配置(占位)
audio.config.ts # 音频配置(占位)
criticalImageResolver.ts # 关键图片预加载(若有精灵图)
domain/
index.ts # 领域内核入口
types.ts # re-export barrel(导出 core-types + commands + events)
core-types.ts # 状态接口(PlayerState, GameCore, 基础类型)
commands.ts # 命令类型 + XX_COMMANDS 常量
events.ts # 事件类型 + XX_EVENTS 常量
ids.ts # 领域 ID 常量表
utils.ts # 游戏内共享工具(从第一天就建立)
rule/
<游戏名>规则.md # 规则文档占位
hooks/ # 游戏业务 hooks
ui/ # 游戏 UI 子组件
__tests__/
smoke.test.ts # 冒烟测试占位
import type { GameManifestEntry } from '../manifest.types';
const entry: GameManifestEntry = {
id: '<gameId>',
type: 'game',
enabled: true,
titleKey: 'games.<gameId>.title',
descriptionKey: 'games.<gameId>.description',
category: 'strategy', // strategy | casual | party | abstract
playersKey: 'games.<gameId>.players',
icon: '🎮',
thumbnailPath: '<gameId>/thumbnails/cover',
allowLocalMode: false, // 默认仅联机
playerOptions: [2], // 可选 [2,3,4]
tags: [], // dice_driven | card_driven | tactical 等
bestPlayers: [2],
};
export const <GAME_ID>_MANIFEST: GameManifestEntry = entry;
export default entry;
core-types.ts — 状态接口:
import type { PlayerId } from '../../../engine/types';
export type GamePhase = 'factionSelect' | 'startTurn' | 'playCards' | ...;
export const PHASE_ORDER: GamePhase[] = [...];
export interface PlayerState { id: PlayerId; /* ... */ }
export interface <GameId>Core {
players: Record<PlayerId, PlayerState>;
turnNumber: number;
gameResult?: { winner?: string; draw?: boolean };
}
commands.ts — 命令类型:
import type { Command } from '../../../engine/types';
export const XX_COMMANDS = { DO_SOMETHING: 'DO_SOMETHING', ... } as const;
export interface DoSomethingCommand extends Command<'DO_SOMETHING'> { payload: { ... }; }
export type <GameId>Command = DoSomethingCommand | ...;
events.ts — 事件类型:
import type { GameEvent } from '../../../engine/types';
export const XX_EVENTS = { SOMETHING_DONE: 'SOMETHING_DONE', ... } as const;
export interface SomethingDoneEvent extends GameEvent<'SOMETHING_DONE'> { payload: { ... }; }
export type <GameId>Event = SomethingDoneEvent | ...;
types.ts — re-export barrel:
export * from './core-types';
export * from './commands';
export * from './events';
所有稳定 ID 必须在此定义,禁止字符串字面量。
import type { DomainCore, PlayerId, RandomFn, GameOverResult } from '../../../engine/types';
import type { <GameId>Core } from './types';
export const <GameId>Domain: DomainCore<<GameId>Core> = {
gameId: '<gameId>',
setup: (playerIds: PlayerId[], random: RandomFn): <GameId>Core => ({
// 最小初始状态
players: Object.fromEntries(playerIds.map(pid => [pid, createPlayerState(pid)])),
turnNumber: 1,
// ...其他必要字段
}),
validate: (state, command) => ({ valid: true }), // 占位
execute: (state, command, random) => [], // 占位
reduce: (core, event) => core, // 占位
isGameOver: (core) => core.gameResult,
};
import { createGameEngine, createBaseSystems, createFlowSystem } from '../../engine';
import { <GameId>Domain } from './domain';
import type { <GameId>Core } from './domain/types';
// FlowHooks 占位(阶段 4 实现)
const flowHooks = {
initialPhase: '<firstPhase>',
getNextPhase: () => '<firstPhase>',
getActivePlayerId: ({ state }) => Object.keys(state.core.players)[0],
};
const systems = [
createFlowSystem<<GameId>Core>({ hooks: flowHooks }),
...createBaseSystems<<GameId>Core>(),
];
export const <GameId> = createGameEngine<<GameId>Core>({
domain: <GameId>Domain,
systems,
minPlayers: 2,
maxPlayers: 2,
commandTypes: [], // 阶段 4 填充
});
export default <GameId>;
import React from 'react';
import type { GameBoardProps } from '../../engine/transport/protocol';
import type { <GameId>Core } from './domain/types';
type Props = GameBoardProps<<GameId>Core>;
const <GameId>Board: React.FC<Props> = ({ G, playerID }) => {
return <div className="p-4 text-white">
<h1>{'<gameId> - 骨架占位'}</h1>
<p>当前玩家:{playerID ?? 'observer'}</p>
<pre>{JSON.stringify(G.core, null, 2)}</pre>
</div>;
};
export default <GameId>Board;
ManifestGameThumbnail 组件TutorialManifest({ id: '<gameId>-basic', steps: [] })GameAudioConfigpublic/assets/i18n/zh-CN/<gameId>/
thumbnails/.gitkeep
board/.gitkeep
cards/.gitkeep
创建 public/locales/zh-CN/game-<gameId>.json 和 public/locales/en/game-<gameId>.json,包含 title/description/players。
npm run generate:manifests # 成功生成清单
npx vitest run src/games/<gameId> # 冒烟测试通过
npm run dev # 编译无报错(游戏可在大厅列表看到)
详细规则已拆到
references/mechanics-data-design.md。进入机制分解、数据结构设计、静态数据录入、类型定义、领域建模或引擎能力缺口分析时,先读该 reference。
最小执行口径:
新游戏默认遵循这条原则:能继续兼容就继续兼容,真缺关键能力才提示。
/play/:gameId/* 的拦截条件。matchMedia、监听 API 差异(addEventListener('change') vs addListener)这类能力,优先在通用工具层或游戏层补 fallback。/play/* 的统一硬门槛。gameId 或页面前缀精确判断。ResizeObserver 视为高风险能力,但不是全站默认门槛
ResizeObserver,且缺失后会导致棋盘/地图/主操作区明显错位或不可操作时,才允许把它加入该游戏的拦截条件。目标:完成确定性核心逻辑,测试通过。
// domain/commands.ts 或 domain/validate.ts
export function validate(state: MatchState<Core>, command: Command): ValidationResult {
// 1. 检查是否是当前玩家的回合
// 2. 检查当前阶段是否允许此命令
// 3. 检查命令参数合法性
// 4. 检查资源/条件是否满足
}
三个游戏共同模式:
domain/commands.ts → validateCommand()domain/validate.ts → validateCommand()domain/commands.ts → validate()// domain/execute.ts 或 domain/reducer.ts
export function execute(state: MatchState<Core>, command: Command, random?: RandomFn): GameEvent[] {
// 根据 command.type 分发处理
// 返回一系列事件(不直接修改状态)
}
// domain/reducer.ts
export function reduce(core: Core, event: GameEvent): Core {
switch (event.type) {
case 'DAMAGE_DEALT': {
// ✅ 结构共享:只 spread 变更路径
const { targetId, amount } = event.payload;
const target = core.players[targetId];
if (!target) return core;
return {
...core,
players: {
...core.players,
[targetId]: { ...target, hp: Math.max(0, target.hp - amount) },
},
};
}
// 每种事件类型一个 case
default: return core;
}
}
关键约束:
JSON.parse(JSON.stringify())(性能灾难)。只 spread 变更路径,未变路径保持原引用。updatePlayer() 等 helper 到 domain/utils.ts。docs/ai-rules/engine-systems.md「Reducer 结构共享范例」。isGameOver: (core): GameOverResult | undefined => {
// 检查胜利条件
// 返回 { winner: playerId } 或 { draw: true } 或 undefined
}
在 __tests__/ 创建测试文件,覆盖:
测试辅助模式(参考 smashup/tests/helpers.ts):
export function makePlayer(id: string, overrides?: Partial<PlayerState>): PlayerState { ... }
export function makeState(overrides?: Partial<Core>): Core { ... }
export function makeMatchState(core: Core): MatchState<Core> { ... }
npx vitest run src/games/<gameId> # 所有测试通过
核心规则正常 + 异常场景有覆盖。
目标:接入 FlowSystem 完成阶段流转,game.ts 组装完毕。
创建 domain/flowHooks.ts(参考 summonerwars/domain/flowHooks.ts):
import type { FlowHooks, PhaseExitResult } from '../../../engine/systems/FlowSystem';
export const flowHooks: FlowHooks<Core> = {
// 初始阶段(通常为 factionSelect 或第一个游戏阶段)
initialPhase: 'factionSelect',
// 是否允许推进
canAdvance: ({ state }) => ({ ok: true }),
// 下一阶段计算
getNextPhase: ({ state, from }) => {
const idx = PHASE_ORDER.indexOf(from as GamePhase);
return PHASE_ORDER[(idx + 1) % PHASE_ORDER.length];
},
// 当前活跃玩家
getActivePlayerId: ({ state }) => state.core.currentPlayer,
// 阶段退出副作用(如:抽牌/切换回合/结算伤害)
onPhaseExit: ({ state, from }): PhaseExitResult => {
const events: GameEvent[] = [];
// 按阶段处理副作用
return { events };
},
// 阶段进入副作用(如:回合开始事件/状态重置)
onPhaseEnter: ({ state, from, to }): GameEvent[] => {
const events: GameEvent[] = [];
// 按阶段处理副作用
return events;
},
// 自动推进检查(如:非交互阶段自动跳过)
onAutoContinueCheck: ({ state, events }) => {
// 如 startTurn/endTurn 等纯自动阶段
return undefined;
},
};
三个游戏的 FlowHooks 复杂度对比:
domain/index.ts 内联(~150 行),阶段退出处理记分逻辑domain/flowHooks.ts(~250 行),阶段进退处理抽牌/换人/技能触发game.ts 内联(~500 行),最复杂,攻防阶段有大量分支// 系统选择模式(三个游戏共同模式)
const systems = [
createFlowSystem<Core>({ hooks: flowHooks }),
// 方式 A:逐个选择(dicethrone/summonerwars 风格,精细控制)
createEventStreamSystem(),
createLogSystem(),
createActionLogSystem({ commandAllowlist: ACTION_ALLOWLIST, formatEntry }),
createUndoSystem({ snapshotCommandAllowlist: UNDO_ALLOWLIST }),
createInteractionSystem(),
createRematchSystem(),
createResponseWindowSystem({ // 需要响应窗口时配置注入
allowedCommands: ['PLAY_CARD'], // 响应期间允许的游戏命令
responseAdvanceEvents: [ // 触发响应者推进的事件
{ eventType: 'CARD_PLAYED' },
],
// interactionLock: { ... }, // 多步交互锁定(可选)
}),
createTutorialSystem(),
createCheatSystem<Core>(cheatModifier),
// 方式 B:默认集合(smashup 风格,简洁)
// ...createBaseSystems<Core>(),
// createCheatSystem<Core>(cheatModifier),
];
// 命令类型(只列业务命令,系统命令由 adapter 自动合并)
const commandTypes = [
...Object.values(XX_COMMANDS),
];
参考 summonerwars/game.ts 的 summonerWarsCheatModifier,至少实现:
getResource / setResourcesetPhasedealCardByIndex(如有牌库)强制先读(权威单一来源):
docs/ai-rules/engine-action-log.md(ActionLogSystem 使用规范)evidence/dicethrone/action-log-card-preview.md(卡牌预览注册表模式 + 数据流说明)你在新游戏里只需要做这些(最小闭环):
game.ts 配置 createActionLogSystem({ commandAllowlist, formatEntry }),formatEntry 产出包含 segments 的 ActionLogEntry。ui/cardPreviewHelper.ts 提供 cardId → CardPreviewRef 查询,并在 game.ts 文件末尾调用 registerCardPreviewGetter(gameId, getter) 注册。GameHUD / FAB 悬浮球承载。新游戏只负责产出正确 ActionLog 数据和接入 Undo 上下文,不在牌桌主界面、侧栏或底部再加日志面板、最近操作列表或第二套撤回按钮。关键点:Vite SSR 的函数提升陷阱与“注册必须放文件末尾”的原因,详见
AGENTS.md/docs/ai-rules/golden-rules.md。
npx vitest run src/games/<gameId>/__tests__/flow.test.ts
npm run generate:manifests # 清单生成成功
npx vitest run src/games/<gameId> # 所有测试通过
npm run dev # 游戏可从大厅创建对局,基础回合可推进
详细规则已拆到
references/ui-implementation-gates.md。进入新游戏 Board/UI、设计稿批准、实现骨架、交互映射、选择 UI 或基础玩法截图链时,先读该 reference。
最小执行口径:
D:\codex-home\skills\ui-design-pipeline\SKILL.md,产出 spec/domain/design/components/craft/template/evaluator 这组最小设计声明与回流契约;若用户要位图设计稿,再继续走 boardgame-ui-imagegen。详细规则已拆到
references/finalization-checklist.md。补 i18n、教学、音频、关键图片预加载、debug 配置、资源命名落盘和最终验证时,先读该 reference。
最小执行口径:
docs/ai-rules/audio-assets.md 和 .codex/skill/audio-integration/SKILL.md。docs/ai-rules/critical-image-preload.md。docs/ai-rules/asset-pipeline.md。权威来源:系统清单/红线/反模式以 AGENTS.md + docs/ai-rules/engine-systems.md 为准,本节不再重复抄写。
createBaseSystems() 默认包含:EventStream + Log + ActionLog + Undo + Interaction + Rematch + ResponseWindow + TutorialcreateBaseSystems() 不包含 FlowSystem / CheatSystem:需要自行追加commandTypes 只列业务命令:系统命令由 adapter 自动合并allowedCommands / responseAdvanceEvents(禁止改引擎文件)engine/primitives/ability.tsengine/primitives/tags.tsengine/primitives/modifier.tsengine/primitives/attribute.ts(纯资源消耗仍用 resources.ts)src/engine/sessionContext.ts 这一层语义,不再在共享层手写 currentPlayer/currentPlayerId/currentPlayerIndex 分支object ref + provenance,禁止直接复制历史 owner/originalOwner/fromPlayerId/toPlayerId 弱协议deferred snapshot,禁止把创建时事实偷偷挂在 ad hoc runtimeContext/context 上src/games/dicethrone/(角色系统/骰子/攻防/状态效果/Token响应)src/games/summonerwars/(网格棋盘/单位管理/阵营牌组/技能系统)src/games/smashup/(多人支持/基地记分/派系混搭/持续效果)src/components/game/framework/src/engine/systems/src/engine/primitives/import manifest from './manifest';
import { ManifestGameThumbnail } from '../../components/lobby/thumbnails';
export default function Thumbnail() {
return <ManifestGameThumbnail manifest={manifest} />;
}
manifest.ts 中配置 thumbnailPath: '<gameId>/thumbnails/cover'(不含扩展名、不含 compressed/)。npm run compress:images -- public/assets/i18n/zh-CN/<gameId>/thumbnails 压缩。thumbnail.tsx 中硬编码 /assets/<gameId>/.../compressed/*.webp;如需定制视觉,在 ManifestGameThumbnail 或公共缩略图组件层扩展。