بنقرة واحدة
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 或公共缩略图组件层扩展。