Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| acf021de2a | |||
| 3fba76fd6e | |||
| 3b4f9b2e10 | |||
| 547fb0dbce | |||
| 06ffa7b6ac | |||
| e4634928c6 | |||
| faa510e999 | |||
| 0b19df467e | |||
| 96b44d8660 | |||
| 33308a93e8 | |||
| f6dad1950f | |||
| a4b103a1b0 | |||
| 05d455d4ef | |||
| d8b37e0da9 | |||
| 21c3bd7a2b | |||
| 756297d2df | |||
| e02811588c | |||
| e1780b21b0 |
@@ -30,7 +30,6 @@ The hosted MCP offers the following tools. Choose the task tool when its action
|
||||
| `PATCH /api/external/v1/editor/assets/{assetId}` | `organize_asset_library` (`update_asset`) | `update_editor_asset` |
|
||||
| `DELETE /api/external/v1/editor/assets/{assetId}` | `delete_resources` (`delete_asset`) | `delete_editor_asset` |
|
||||
| `POST /api/external/v1/editor/images/generations` | `generate_image`, `modify_image` (`variation`, fixed `kind="quick-edit"`) | `generate_external_editor_image` |
|
||||
| `POST /api/external/v1/editor/scenes/generations` | structured game-scene generation (no hosted MCP tool yet) | `generate_external_editor_scene` |
|
||||
| `POST /api/external/v1/editor/images/edits` | `modify_image` (`edit`) | `edit_external_editor_image` |
|
||||
| `POST /api/external/v1/editor/images/background-removals` | `modify_image` (`remove_background`) | `remove_external_editor_image_background` |
|
||||
| `POST /api/external/v1/editor/icon-spritesheets/generations` | `generate_icon_spritesheet` | `generate_external_editor_icon_spritesheet` |
|
||||
@@ -90,7 +89,6 @@ Every generation row requires a stable `Idempotency-Key` header and returns HTTP
|
||||
| Capability | POST path | Required body fields | Common optional body fields |
|
||||
| ------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Image generation | `/api/external/v1/editor/images/generations` | `prompt` | `kind`, `style`, `model`, `aspectRatio`, `imageSize`, `size`, `referenceImageSrcs`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
|
||||
| Game scene | `/api/external/v1/editor/scenes/generations` | `sceneContent`, `stylePreset` | `customStyle` (required when `stylePreset="custom"`), `model`, `aspectRatio`, `imageSize`, `referenceImageSrcs`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
|
||||
| Image edit/redraw | `/api/external/v1/editor/images/edits` | `prompt`, `sourceReferenceId` | `referenceImageSrcs`, `model`, `size`, `projectId`, `assetFolderId`, `assetLabel`, `targetLayerId`, `canvasCompletion` |
|
||||
| Background removal | `/api/external/v1/editor/images/background-removals` | `sourceImageSrc` | `projectId`, `sourceResourceId`, `targetLayerId`, static-image `assetKind`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
|
||||
| Icon spritesheet | `/api/external/v1/editor/icon-spritesheets/generations` | `referenceId`, `iconDescriptions`, `sliceMode` | `gridX`, `gridY`, `sliceCount`, `style`, `referenceImageSrcs`, `screenColor`, `model`, `aspectRatio`, `imageSize`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion` |
|
||||
@@ -100,7 +98,7 @@ Every generation row requires a stable `Idempotency-Key` header and returns HTTP
|
||||
| Sound effect | `/api/external/v1/editor/audios/sound-effects/generations` | `prompt` | `model`, `duration`, `loop`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
|
||||
| Background music | `/api/external/v1/editor/audios/background-music/generations` | `gptDescriptionPrompt`, `makeInstrumental` | `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
|
||||
|
||||
Poll all ten through:
|
||||
Poll all nine through:
|
||||
|
||||
```text
|
||||
GET /api/external/v1/generations/{operationId}
|
||||
@@ -142,7 +140,7 @@ The icon-spritesheet primary `referenceId` is intentionally stricter than ordina
|
||||
Use OpenAPI as the final authority; these common values are a routing aid:
|
||||
|
||||
- Image `kind`: `spec`, `character`, `quick-edit`, `ui-design`, `publication-material`; ordinary image generation may omit it.
|
||||
- Game scenes must use the dedicated structured route `POST /api/external/v1/editor/scenes/generations` (`sceneContent` + `stylePreset`; `customStyle` required for `custom`). Do not send `kind: "scene"` or `assetKind: "scene"` through generic image generation; the server rejects both before queueing. The scene route assembles the full provider prompt server-side and never accepts a caller-assembled `prompt`.
|
||||
- External v1 currently has no structured game-scene generation operation. Do not send `kind: "scene"` or `assetKind: "scene"` through generic image generation; the server rejects both before queueing.
|
||||
- Image `model`: `gpt-image-2`, `gemini-3.1-flash-image-preview`, `nanobanana2`, `nano-banana`.
|
||||
- Image `aspectRatio`: `1:1`, `2:3`, `3:2`, `9:16`, `16:9`.
|
||||
- Image `imageSize`: `0.5K`, `1K`, `2K`.
|
||||
|
||||
@@ -148,7 +148,7 @@ For the lower-level asset/resource creation endpoints, `generationInputs` is rep
|
||||
|
||||
## Art Spec and Image Request
|
||||
|
||||
Game scenes have a dedicated structured route: `POST /api/external/v1/editor/scenes/generations` with `sceneContent` and `stylePreset` (`customStyle` required when `stylePreset` is `custom`). The server assembles the full provider prompt; a caller-assembled `prompt` is not accepted. `kind: "scene"` and `assetKind: "scene"` remain invalid on generic image generation and return HTTP `400` before any generation job is queued.
|
||||
Generic External v1 image generation does not expose the main-site structured game-scene contract. `kind: "scene"` and `assetKind: "scene"` are both invalid and return HTTP `400` before any generation job is queued. Do not replace the structured scene fields and server-owned prompt assembly with a generic image prompt.
|
||||
|
||||
When maintaining a reusable art spec, carry it in `generationInputs.artSpec` and reflect important constraints in the prompt. This is an example with both canvas and library destinations, not a requirement for every generation:
|
||||
|
||||
|
||||
@@ -57,6 +57,8 @@ temp*build*/
|
||||
/apps/ai-game-creator-shell/logs/
|
||||
/apps/ai-game-creator-shell/src-tauri/resources/node-runtime/
|
||||
/apps/ai-game-creator-shell/src-tauri/resources/node-runtime-staging-*/
|
||||
/apps/ai-game-creator-shell/src-tauri/resources/plugins-staging-*/
|
||||
/apps/ai-game-creator-shell/src-tauri/resources/codex/*-staging-*/
|
||||
/apps/ai-game-creator-shell/.llm-drafts/
|
||||
/apps/ai-game-creator-shell/game-creator.config.local.json
|
||||
/apps/mobile-shell/.expo/
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# resources/plugins 由 build.rs 从 plugins/ 复制生成,属于构建产物。
|
||||
# 它在 dev 监听范围内,重新生成会让 Tauri dev 误判为源码改动而触发
|
||||
# “构建 -> 监听 -> 再构建”的自触发循环。
|
||||
resources/plugins/
|
||||
@@ -13,6 +13,10 @@
|
||||
"skill-pack:check": "node scripts/check-skill-pack.mjs",
|
||||
"skill-pack:sync": "node scripts/check-skill-pack.mjs --write",
|
||||
"skill-pack:test": "node --test scripts/check-skill-pack.test.mjs",
|
||||
"bundled-resources:check": "node scripts/check-package-layout.mjs",
|
||||
"bundled-resources:sync": "node scripts/check-package-layout.mjs --write",
|
||||
"bundled-resources:prepare": "node scripts/prepare-bundled-resources.mjs",
|
||||
"bundled-resources:test": "node --test scripts/prepare-bundled-resources.test.mjs",
|
||||
"llm-status": "node scripts/run-cli-with-config.mjs --llm-status",
|
||||
"agent-task": "node scripts/run-cli-with-config.mjs --agent-task",
|
||||
"config": "node scripts/game-creator-config-wizard.mjs",
|
||||
@@ -24,7 +28,7 @@
|
||||
"agent-runtime:supervisor-swarm-tool-plan-handoff-runner-kill-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-tool-plan-handoff-runner-kill",
|
||||
"agent-runtime:steer-real-e2e": "node scripts/agent-runtime-steer-real-e2e.mjs",
|
||||
"agent-runtime:steer-runner-kill-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite steer-runner-kill",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit && npm run skill-pack:check && node scripts/check-config.mjs"
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit && npm run skill-pack:check && npm run bundled-resources:check && node scripts/check-config.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cubone/react-file-manager": "^1.35.0",
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from './agc-global-version.mjs';
|
||||
import {
|
||||
defaultEditorFeatures,
|
||||
resolveEditorFeatures,
|
||||
withDefaultCargoFeatures,
|
||||
} from './cargo-features.mjs';
|
||||
import {
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
resolveReleaseChannel,
|
||||
} from './channel-identity.mjs';
|
||||
import { prepareNsisToolsetForRelease } from './nsis-toolset.mjs';
|
||||
import { prepareBundledResources } from './prepare-bundled-resources.mjs';
|
||||
import { stageNodeRuntime } from './stage-node-runtime.mjs';
|
||||
|
||||
const appRoot = fileURLToPath(new URL('..', import.meta.url));
|
||||
@@ -431,10 +433,30 @@ function writeChannelConfigFile(channel, target, includeNodeRuntime = false) {
|
||||
return configPath;
|
||||
}
|
||||
|
||||
/// 随包资源必须在打包工具之前生成:构建脚本只做只读校验,不再生成。
|
||||
export function stageBundledResources(
|
||||
target,
|
||||
{ prepare = prepareBundledResources, features } = {},
|
||||
) {
|
||||
const summaries = prepare({
|
||||
target,
|
||||
features: new Set(features ?? defaultEditorFeatures(target)),
|
||||
profile: 'release',
|
||||
log: (line) => console.log(`[ai-game-creator-shell] ${line}`),
|
||||
});
|
||||
for (const summary of summaries) {
|
||||
console.log(`[ai-game-creator-shell] ${summary}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function runTauriBuild(
|
||||
args = [],
|
||||
context = resolveReleaseContext(args),
|
||||
{ spawn = spawnSync, stageRuntime = stageNodeRuntime } = {},
|
||||
{
|
||||
spawn = spawnSync,
|
||||
stageRuntime = stageNodeRuntime,
|
||||
stageBundled = stageBundledResources,
|
||||
} = {},
|
||||
) {
|
||||
if (
|
||||
explicitBuildTarget(args) &&
|
||||
@@ -444,7 +466,12 @@ export function runTauriBuild(
|
||||
}
|
||||
const tauriArguments = buildTauriBuildArguments(args, context.target);
|
||||
const { channel, target } = context;
|
||||
if (!args.includes('--no-bundle')) stageRuntime(target);
|
||||
const features = resolveEditorFeatures({ argv: args, target });
|
||||
if (!args.includes('--no-bundle')) {
|
||||
stageRuntime(target);
|
||||
}
|
||||
// app 构建本身就需要随包资源,因此 --no-bundle 也要 staging;只有总号发布是打包专属。
|
||||
stageBundled(target, { features });
|
||||
const configPath = writeChannelConfigFile(
|
||||
channel,
|
||||
target,
|
||||
|
||||
@@ -363,6 +363,8 @@ test('packaged renderer receives the same channel as the updater manifest', () =
|
||||
// 必须 stub:真实 staging 会用宿主平台(如 macOS 的 darwin/arm64)去对默认的
|
||||
// Windows 目标做一致性校验,在非 Windows 主机上直接失败——本用例只关心渠道注入。
|
||||
stageRuntime: () => {},
|
||||
// 同上:随包资源准备会读取真实上游包,本用例只关心渠道环境变量。
|
||||
stageBundled: () => {},
|
||||
spawn: (_binary, _args, options) => {
|
||||
spawnOptions = options;
|
||||
return { status: 0 };
|
||||
@@ -456,6 +458,8 @@ test('explicit macOS target drives version lookup, Tauri endpoint, artifact and
|
||||
seenContexts.push(context);
|
||||
runTauriBuild(args, context, {
|
||||
stageRuntime: () => {},
|
||||
// 必须 stub:随包资源准备会读取真实上游包与仓库插件工作区,本用例只关心参数。
|
||||
stageBundled: () => {},
|
||||
spawn: (_binary, command) => {
|
||||
const configIndex = command.lastIndexOf('--config');
|
||||
const config = JSON.parse(
|
||||
@@ -705,6 +709,7 @@ test('Windows remains the default and explicit Windows overrides macOS environme
|
||||
context,
|
||||
{
|
||||
stageRuntime: () => {},
|
||||
stageBundled: () => {},
|
||||
spawn: (_binary, command) => {
|
||||
assert.ok(
|
||||
command.includes(
|
||||
@@ -818,6 +823,10 @@ test('release stages Node before Tauri and injects its resource mapping only for
|
||||
assert.equal(target, windowsTarget);
|
||||
events.push('stage');
|
||||
},
|
||||
stageBundled(target) {
|
||||
assert.equal(target, windowsTarget);
|
||||
events.push('bundled');
|
||||
},
|
||||
spawn(_binary, args) {
|
||||
events.push('build');
|
||||
const config = JSON.parse(
|
||||
@@ -829,10 +838,21 @@ test('release stages Node before Tauri and injects its resource mapping only for
|
||||
return { status: 0 };
|
||||
},
|
||||
});
|
||||
assert.deepEqual(events, ['stage', 'build']);
|
||||
assert.deepEqual(events, ['stage', 'bundled', 'build']);
|
||||
events.length = 0;
|
||||
runTauriBuild(['--no-bundle', '--target', windowsTarget], context, {
|
||||
stageRuntime() {
|
||||
assert.fail('no-bundle must not stage resources');
|
||||
assert.fail('no-bundle must not stage node runtime');
|
||||
},
|
||||
stageBundled(target, options) {
|
||||
// app 构建本身就需要随包资源,因此 --no-bundle 也要 staging,
|
||||
// 且必须拿到与 cargo 相同的 feature 集(避免「cargo 开、staging 没开」)。
|
||||
assert.equal(target, windowsTarget);
|
||||
assert.ok(
|
||||
options?.features?.length > 0,
|
||||
'bundled staging 必须收到 feature 集',
|
||||
);
|
||||
events.push('bundled');
|
||||
},
|
||||
spawn(_binary, args) {
|
||||
const config = JSON.parse(
|
||||
@@ -848,6 +868,9 @@ test('release stages Node before Tauri and injects its resource mapping only for
|
||||
stageRuntime() {
|
||||
throw new Error('missing runtime');
|
||||
},
|
||||
stageBundled() {
|
||||
assert.fail('invalid runtime must prevent bundled staging');
|
||||
},
|
||||
spawn() {
|
||||
assert.fail('invalid runtime must prevent build');
|
||||
},
|
||||
@@ -960,6 +983,8 @@ for (const channel of ['release', 'beta-2']) {
|
||||
);
|
||||
runTauriBuild([`--target=${target}`], context, {
|
||||
stageRuntime: () => {},
|
||||
// 必须 stub:随包资源准备会读取真实上游包与仓库插件工作区,本用例只关心参数。
|
||||
stageBundled: () => {},
|
||||
spawn: (_binary, command) => {
|
||||
const config = JSON.parse(
|
||||
readFileSync(
|
||||
|
||||
@@ -22,3 +22,48 @@ export function defaultEditorFeatures(target) {
|
||||
? ['cocos-editor-execute', 'unity-editor-execute', 'godot-editor-execute']
|
||||
: [];
|
||||
}
|
||||
|
||||
/// 单一声明式解析:本次构建实际生效的编辑器 feature 集。
|
||||
/// 解析顺序:环境变量 `AGC_DEV_CARGO_FEATURES` > 命令行 `--features` > 目标平台默认值。
|
||||
/// dev 入口、发布入口的 cargo 参数与随包资源准备步骤都必须用这里的返回值,
|
||||
/// 否则会出现「cargo 开了 feature、staging 没开(或反之)」的窗口。
|
||||
export function resolveEditorFeatures({
|
||||
argv = [],
|
||||
target,
|
||||
env = process.env,
|
||||
} = {}) {
|
||||
// 顺序:环境变量(dev 的显式覆盖)> 命令行 --features > 目标平台默认值。
|
||||
const fromEnv = String(env.AGC_DEV_CARGO_FEATURES ?? '').trim();
|
||||
if (fromEnv) {
|
||||
return fromEnv
|
||||
.split(',')
|
||||
.map((name) => name.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
const fromArgv = featuresFromArgv(argv);
|
||||
if (fromArgv) {
|
||||
return fromArgv;
|
||||
}
|
||||
return defaultEditorFeatures(target);
|
||||
}
|
||||
|
||||
/// 从 argv 里解析 `--features a,b` / `--features=a,b`;没有则返回 null。
|
||||
export function featuresFromArgv(argv = []) {
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const value = String(argv[index]);
|
||||
if (value.startsWith('--features=')) {
|
||||
return value
|
||||
.slice('--features='.length)
|
||||
.split(',')
|
||||
.map((name) => name.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
if (value === '--features' && argv[index + 1] !== undefined) {
|
||||
return String(argv[index + 1])
|
||||
.split(',')
|
||||
.map((name) => name.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
+750
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,9 @@
|
||||
import { resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { buildLocalRustProcessEnv } from '../../../scripts/dev.mjs';
|
||||
import {
|
||||
defaultEditorFeatures,
|
||||
resolveEditorFeatures,
|
||||
withDefaultCargoFeatures,
|
||||
} from './cargo-features.mjs';
|
||||
import {
|
||||
@@ -10,6 +11,10 @@ import {
|
||||
resolveAgcDevEndpoint,
|
||||
withAgcDevEndpointEnv,
|
||||
} from './dev-port.mjs';
|
||||
import {
|
||||
prepareBundledResources,
|
||||
supportedHostTarget,
|
||||
} from './prepare-bundled-resources.mjs';
|
||||
import {
|
||||
isAiGameCreatorServer,
|
||||
preflightExistingVite,
|
||||
@@ -52,21 +57,37 @@ function buildTauriArguments(argv, devUrl = readAgcDevEndpoint().url) {
|
||||
|
||||
// 开发和发行构建使用同一平台编辑器 feature 集合。
|
||||
// 可用 AGC_DEV_CARGO_FEATURES(逗号分隔)覆盖,传空串即关闭。
|
||||
function readDevCargoFeatures(env = process.env) {
|
||||
const override = env.AGC_DEV_CARGO_FEATURES;
|
||||
if (override !== undefined) {
|
||||
return override
|
||||
.split(',')
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
return defaultEditorFeatures(process.platform);
|
||||
function readDevCargoFeatures(argv = []) {
|
||||
return resolveEditorFeatures({ argv, target: process.platform });
|
||||
}
|
||||
|
||||
function withDevCargoFeatures(argv, features = readDevCargoFeatures()) {
|
||||
function withDevCargoFeatures(argv, features = readDevCargoFeatures(argv)) {
|
||||
return withDefaultCargoFeatures(argv, features);
|
||||
}
|
||||
|
||||
/// 随包资源必须在 Tauri 之前生成:构建脚本只做只读校验,不再生成资源。
|
||||
/// 命中缓存的重复调用不写任何文件,因此每次 dev 启动都会先跑一次。
|
||||
function prepareBundledResourcesBeforeTauri(
|
||||
features = readDevCargoFeatures(),
|
||||
{ prepare = prepareBundledResources, log = console.log } = {},
|
||||
) {
|
||||
const target = supportedHostTarget();
|
||||
if (!target) {
|
||||
log(
|
||||
'[ai-game-creator-shell] 当前平台不受随包资源声明覆盖,跳过随包资源准备',
|
||||
);
|
||||
return;
|
||||
}
|
||||
const summaries = prepare({
|
||||
target,
|
||||
features: new Set(features),
|
||||
log: (line) => log(`[ai-game-creator-shell] ${line}`),
|
||||
});
|
||||
for (const summary of summaries) {
|
||||
log(`[ai-game-creator-shell] ${summary}`);
|
||||
}
|
||||
}
|
||||
|
||||
function spawnTauriCli(argv, { env = process.env } = {}) {
|
||||
return spawnChild(process.execPath, [tauriCliPath, ...argv], {
|
||||
cwd: appRoot,
|
||||
@@ -75,6 +96,17 @@ function spawnTauriCli(argv, { env = process.env } = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
/// Tauri dev 的 Cargo 直接继承启动器环境,用户级 / 仓库级 Cargo 配置里的
|
||||
/// `rustc-wrapper`(本地常见为 sccache)会在这里生效。本地 sccache daemon 状态
|
||||
/// 一旦损坏,`cargo` 的首次 rustc 探测就会失败并阻断整个 AGC 启动;因此这里复用
|
||||
/// `npm run dev` 的本地 Rust 环境规则,由脚本而不是本机 Cargo 配置决定 wrapper。
|
||||
function buildTauriDevProcessEnv(endpoint, env = process.env) {
|
||||
return buildLocalRustProcessEnv({
|
||||
...withAgcDevEndpointEnv(endpoint, env),
|
||||
[AGC_DESIGN_DEBUG_ENV]: designDebugEnabled,
|
||||
});
|
||||
}
|
||||
|
||||
async function runTauriDev(
|
||||
argv = process.argv.slice(2),
|
||||
{
|
||||
@@ -84,6 +116,7 @@ async function runTauriDev(
|
||||
spawnCli = spawnTauriCli,
|
||||
waitForCli = waitForChildTermination,
|
||||
terminateTree = terminateChildTree,
|
||||
prepareResources = prepareBundledResourcesBeforeTauri,
|
||||
} = {},
|
||||
) {
|
||||
const endpoint = await resolveDevEndpoint();
|
||||
@@ -130,15 +163,14 @@ async function runTauriDev(
|
||||
shutdownRequested.then(() => false),
|
||||
]);
|
||||
if (!prepared || shutdownSignal) return 1;
|
||||
const devFeatures = readDevCargoFeatures();
|
||||
prepareResources(devFeatures);
|
||||
const tauriArguments = buildTauriArguments(
|
||||
withDevCargoFeatures(argv),
|
||||
withDevCargoFeatures(argv, devFeatures),
|
||||
endpoint.url,
|
||||
);
|
||||
child = spawnCli(tauriArguments, {
|
||||
env: {
|
||||
...withAgcDevEndpointEnv(endpoint),
|
||||
[AGC_DESIGN_DEBUG_ENV]: designDebugEnabled,
|
||||
},
|
||||
env: buildTauriDevProcessEnv(endpoint),
|
||||
});
|
||||
const childResult = waitForCli(child);
|
||||
const outcome = await Promise.race([
|
||||
@@ -225,7 +257,9 @@ function isDirectModuleExecution() {
|
||||
|
||||
export {
|
||||
buildTauriArguments,
|
||||
buildTauriDevProcessEnv,
|
||||
isDirectModuleExecution,
|
||||
prepareBundledResourcesBeforeTauri,
|
||||
runTauriDev,
|
||||
spawnTauriCli,
|
||||
withDevCargoFeatures,
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# resources/plugins 由 build.rs 从 plugins/ 复制生成,属于构建产物。
|
||||
# 它在 dev 监听范围内,重新生成会让 Tauri dev 误判为源码改动而触发
|
||||
# “构建 -> 监听 -> 再构建”的自触发循环。
|
||||
resources/plugins/
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,18 @@
|
||||
//! 构建与运行共用的平台布局;只允许分发锁定原生包里的明确组件。
|
||||
//!
|
||||
//! 布局、组件白名单与版本常量来自唯一声明 `build_support/package-layout.json`
|
||||
//! (Rust 侧经 `build_support/package-layout.generated.rs` 取得编译期常量,
|
||||
//! 由 `scripts/check-package-layout.mjs` 生成并在门禁中校验一致)。
|
||||
//! 本模块只读声明,不写任何随包资源。
|
||||
|
||||
pub const VERSION: &str = "0.155.1";
|
||||
pub const CLI_VERSION: &str = "codex-cli 0.155.1";
|
||||
pub const SCHEMA: &str = "genarrative-codex-sidecar.v2";
|
||||
// 共享声明模块:构建脚本、运行期与测试各自只用到其中一部分,未用到的入口不算缺陷。
|
||||
#[allow(dead_code)]
|
||||
#[path = "package_layout.rs"]
|
||||
pub(crate) mod package_layout;
|
||||
|
||||
pub const VERSION: &str = package_layout::CODEX_VERSION;
|
||||
pub const CLI_VERSION: &str = package_layout::CODEX_CLI_VERSION;
|
||||
pub const SCHEMA: &str = package_layout::CODEX_MANIFEST_SCHEMA;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Layout {
|
||||
@@ -12,46 +22,13 @@ pub struct Layout {
|
||||
pub files: &'static [&'static str],
|
||||
}
|
||||
|
||||
const WINDOWS_FILES: &[&str] = &[
|
||||
"bin/codex.exe",
|
||||
"bin/codex-code-mode-host.exe",
|
||||
"codex-path/rg.exe",
|
||||
"codex-resources/codex-command-runner.exe",
|
||||
"codex-resources/codex-windows-sandbox-setup.exe",
|
||||
"codex-package.json",
|
||||
];
|
||||
const MAC_FILES: &[&str] = &[
|
||||
"bin/codex",
|
||||
"bin/codex-code-mode-host",
|
||||
"codex-path/rg",
|
||||
"codex-resources/zsh/bin/zsh",
|
||||
"codex-package.json",
|
||||
];
|
||||
|
||||
pub fn for_target(target: &str) -> Option<Layout> {
|
||||
match target {
|
||||
"x86_64-pc-windows-msvc" => Some(Layout {
|
||||
platform: "win32-x64",
|
||||
directory: "win-x64",
|
||||
executable: "bin/codex.exe",
|
||||
files: WINDOWS_FILES,
|
||||
}),
|
||||
"aarch64-apple-darwin" | "x86_64-apple-darwin" => Some(Layout {
|
||||
platform: if target.starts_with("aarch64") {
|
||||
"darwin-arm64"
|
||||
} else {
|
||||
"darwin-x64"
|
||||
},
|
||||
directory: if target.starts_with("aarch64") {
|
||||
"mac-native/darwin-arm64"
|
||||
} else {
|
||||
"mac-native/darwin-x64"
|
||||
},
|
||||
executable: "bin/codex",
|
||||
files: MAC_FILES,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
package_layout::codex_target(target).map(|declared| Layout {
|
||||
platform: declared.platform,
|
||||
directory: declared.directory,
|
||||
executable: declared.executable,
|
||||
files: declared.files,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -80,4 +57,11 @@ mod tests {
|
||||
assert!(for_target("aarch64-pc-windows-msvc").is_none());
|
||||
assert!(for_target("x86_64-unknown-linux-gnu").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constants_come_from_the_shared_declaration() {
|
||||
assert_eq!(VERSION, "0.155.1");
|
||||
assert_eq!(CLI_VERSION, format!("codex-cli {VERSION}"));
|
||||
assert_eq!(SCHEMA, "genarrative-codex-sidecar.v2");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
//! 随包阶段的原生包元数据校验,不进入运行时生产模块。
|
||||
|
||||
use super::codex_bundle::{Layout, VERSION};
|
||||
|
||||
pub fn validate_package_metadata(
|
||||
metadata: &serde_json::Value,
|
||||
target: &str,
|
||||
layout: Layout,
|
||||
) -> Result<(), String> {
|
||||
if metadata["layoutVersion"] == 1
|
||||
&& metadata["version"] == VERSION
|
||||
&& metadata["target"] == target
|
||||
&& metadata["entrypoint"] == layout.executable
|
||||
&& metadata["resourcesDir"] == "codex-resources"
|
||||
&& metadata["pathDir"] == "codex-path"
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Codex 原生包版本、布局或架构不匹配目标 {target}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::codex_bundle::for_target;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn metadata_rejects_version_architecture_and_layout_drift() {
|
||||
let target = "aarch64-apple-darwin";
|
||||
let layout = for_target(target).unwrap();
|
||||
let valid = serde_json::json!({
|
||||
"layoutVersion": 1,
|
||||
"version": VERSION,
|
||||
"target": target,
|
||||
"entrypoint": "bin/codex",
|
||||
"resourcesDir": "codex-resources",
|
||||
"pathDir": "codex-path",
|
||||
});
|
||||
assert!(validate_package_metadata(&valid, target, layout).is_ok());
|
||||
for (key, value) in [
|
||||
("layoutVersion", serde_json::json!(2)),
|
||||
("version", serde_json::json!("0.0.0")),
|
||||
("target", serde_json::json!("x86_64-apple-darwin")),
|
||||
("entrypoint", serde_json::json!("bin/codex.exe")),
|
||||
("resourcesDir", serde_json::json!("../private")),
|
||||
("pathDir", serde_json::json!(null)),
|
||||
] {
|
||||
let mut invalid = valid.clone();
|
||||
invalid[key] = value;
|
||||
assert!(
|
||||
validate_package_metadata(&invalid, target, layout).is_err(),
|
||||
"{key}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
|
||||
pub const BUNDLE_FILES: [&str; 4] = [
|
||||
"bin/win-x64/agc_godot_editor.dll",
|
||||
"bin/win-x64/metadata.json",
|
||||
"vendor/LICENSE.txt",
|
||||
"vendor/provenance.json",
|
||||
];
|
||||
// 共享随包资源声明:Godot 随包文件清单与构建期校验共用同一份来源。
|
||||
#[allow(dead_code)]
|
||||
#[path = "package_layout.rs"]
|
||||
mod package_layout;
|
||||
|
||||
pub const BUNDLE_FILES: &[&str] = package_layout::GODOT_BUNDLE_FILES;
|
||||
|
||||
fn plain_metadata(path: &Path) -> Result<fs::Metadata, String> {
|
||||
let metadata = fs::symlink_metadata(path)
|
||||
@@ -74,39 +74,6 @@ pub fn validate(root: &Path) -> Result<Vec<(&'static str, Vec<u8>)>, String> {
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
pub fn stage(root: &Path, destination: &Path, target: &str, enabled: bool) -> Result<(), String> {
|
||||
if target != "x86_64-pc-windows-msvc" || !enabled {
|
||||
return Ok(());
|
||||
}
|
||||
for (relative, bytes) in validate(root)? {
|
||||
let path = destination.join(relative);
|
||||
fs::create_dir_all(path.parent().expect("Godot resource parent"))
|
||||
.map_err(|error| format!("创建 Godot 资源目录失败:{error}"))?;
|
||||
fs::write(&path, bytes).map_err(|error| format!("写入 Godot 资源失败:{error}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn source_files(root: &Path) -> Result<Vec<PathBuf>, String> {
|
||||
plain_metadata(root)?;
|
||||
let mut sources = Vec::new();
|
||||
for entry in fs::read_dir(root).map_err(|error| format!("读取 Godot 源码失败:{error}"))?
|
||||
{
|
||||
let entry = entry.map_err(|error| format!("读取 Godot 源码目录项失败:{error}"))?;
|
||||
if matches!(entry.file_name().to_str(), Some("bin" | ".build")) {
|
||||
continue;
|
||||
}
|
||||
let metadata = plain_metadata(&entry.path())?;
|
||||
if metadata.is_dir() {
|
||||
sources.extend(source_files(&entry.path())?);
|
||||
} else if metadata.is_file() {
|
||||
sources.push(entry.path());
|
||||
}
|
||||
}
|
||||
sources.sort();
|
||||
Ok(sources)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -134,82 +101,20 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stage_only_verified_windows_runtime_and_not_build_inputs() {
|
||||
let source = tempfile::tempdir().unwrap();
|
||||
let destination = tempfile::tempdir().unwrap();
|
||||
fixture(source.path());
|
||||
fs::write(source.path().join("bridge.gd"), "source").unwrap();
|
||||
fs::write(source.path().join("bin/win-x64/extra.dll"), "excluded").unwrap();
|
||||
stage(
|
||||
source.path(),
|
||||
destination.path(),
|
||||
"x86_64-pc-windows-msvc",
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
fn validate_rejects_incomplete_bundle() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let error = validate(temp.path()).expect_err("空目录必须被拒绝");
|
||||
assert!(!error.is_empty(), "失败原因不能为空");
|
||||
|
||||
// 只有文件名、内容不合法的「像样」目录也必须被拒绝。
|
||||
for relative in BUNDLE_FILES {
|
||||
assert_eq!(
|
||||
fs::read(source.path().join(relative)).unwrap(),
|
||||
fs::read(destination.path().join(relative)).unwrap()
|
||||
);
|
||||
let file = temp.path().join(relative);
|
||||
fs::create_dir_all(file.parent().expect("parent")).expect("create dir");
|
||||
fs::write(&file, b"not a real artifact").expect("write file");
|
||||
}
|
||||
assert!(!destination.path().join("bridge.gd").exists());
|
||||
assert!(!destination.path().join("bin/win-x64/extra.dll").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_or_disabled_targets_need_no_native_artifacts() {
|
||||
let destination = tempfile::tempdir().unwrap();
|
||||
for (target, enabled) in [
|
||||
("aarch64-apple-darwin", true),
|
||||
("x86_64-apple-darwin", true),
|
||||
("x86_64-unknown-linux-gnu", true),
|
||||
("aarch64-pc-windows-msvc", true),
|
||||
("x86_64-pc-windows-msvc", false),
|
||||
] {
|
||||
stage(
|
||||
Path::new("missing-godot-native"),
|
||||
destination.path(),
|
||||
target,
|
||||
enabled,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(fs::read_dir(destination.path()).unwrap().count(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incomplete_or_tampered_bundle_fails_before_copying() {
|
||||
let source = tempfile::tempdir().unwrap();
|
||||
let destination = tempfile::tempdir().unwrap();
|
||||
fixture(source.path());
|
||||
fs::write(source.path().join(BUNDLE_FILES[0]), b"tampered").unwrap();
|
||||
assert!(stage(
|
||||
source.path(),
|
||||
destination.path(),
|
||||
"x86_64-pc-windows-msvc",
|
||||
true
|
||||
)
|
||||
.unwrap_err()
|
||||
.contains("SHA256"));
|
||||
assert_eq!(fs::read_dir(destination.path()).unwrap().count(), 0);
|
||||
fixture(source.path());
|
||||
fs::remove_file(source.path().join("vendor/LICENSE.txt")).unwrap();
|
||||
assert!(validate(source.path()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_watch_list_excludes_build_outputs() {
|
||||
let source = tempfile::tempdir().unwrap();
|
||||
fixture(source.path());
|
||||
fs::create_dir(source.path().join(".build")).unwrap();
|
||||
fs::write(source.path().join(".build/bridge.obj"), "generated").unwrap();
|
||||
fs::write(source.path().join("bridge.gd"), "source").unwrap();
|
||||
let sources = source_files(source.path()).unwrap();
|
||||
assert_eq!(sources.len(), 3);
|
||||
assert!(sources.contains(&source.path().join("bridge.gd")));
|
||||
assert!(!sources.iter().any(|path| path
|
||||
.components()
|
||||
.any(|component| component.as_os_str() == "bin" || component.as_os_str() == ".build")));
|
||||
assert!(
|
||||
validate(temp.path()).is_err(),
|
||||
"内容不合法的随包库必须被拒绝",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
// @generated by apps/ai-game-creator-shell/scripts/check-package-layout.mjs
|
||||
// 来源:build_support/package-layout.json。不要手工编辑本文件。
|
||||
// 修改随包资源布局请编辑声明文件,然后运行
|
||||
// npm run agc:bundled-resources:sync(在仓库根目录)
|
||||
// 门禁会校验两者一致(npm run agc:typecheck 链内含 check-package-layout.mjs)。
|
||||
|
||||
pub const DECLARATION_SCHEMA: &str = "agc-package-layout.v1";
|
||||
pub const LAYOUT_VERSION: u64 = 1;
|
||||
|
||||
pub const CODEX_VERSION: &str = "0.155.1";
|
||||
pub const CODEX_CLI_VERSION: &str = "codex-cli 0.155.1";
|
||||
pub const CODEX_MANIFEST_SCHEMA: &str = "genarrative-codex-sidecar.v2";
|
||||
|
||||
pub const GODOT_BUNDLE_FILES: &[&str] = &["bin/win-x64/agc_godot_editor.dll", "bin/win-x64/metadata.json", "vendor/LICENSE.txt", "vendor/provenance.json"];
|
||||
|
||||
pub const CODEX: Codex = Codex {
|
||||
package_metadata: PackageMetadata {
|
||||
layout_version: 1,
|
||||
resources_dir: "codex-resources",
|
||||
path_dir: "codex-path",
|
||||
},
|
||||
resource_directory: "resources/codex",
|
||||
manifest_file_name: "manifest.json",
|
||||
package_metadata_file_name: "codex-package.json",
|
||||
notice_file_name: "NOTICE.md",
|
||||
source_roots: &["app", "repo"],
|
||||
source_relative_paths: &["node_modules/@openai/codex-<platform>/vendor/<target>", "node_modules/@openai/codex/node_modules/@openai/codex-<platform>/vendor/<target>"],
|
||||
notice_sources: &[
|
||||
NoticeSource {
|
||||
targets: &["aarch64-apple-darwin", "x86_64-apple-darwin"],
|
||||
source: "resources/codex/【声明】Mac内置Codex组件-2026-09-18.md",
|
||||
preserve: false,
|
||||
},
|
||||
NoticeSource {
|
||||
targets: &["x86_64-pc-windows-msvc"],
|
||||
source: "resources/codex/win-x64/NOTICE.md",
|
||||
preserve: true,
|
||||
}
|
||||
],
|
||||
universal_groups: &[
|
||||
UniversalGroup {
|
||||
name: "mac-native",
|
||||
directory: "mac-native",
|
||||
targets: &["aarch64-apple-darwin", "x86_64-apple-darwin"],
|
||||
}
|
||||
],
|
||||
targets: &[
|
||||
CodexTarget {
|
||||
target: "x86_64-pc-windows-msvc",
|
||||
platform: "win32-x64",
|
||||
directory: "win-x64",
|
||||
executable: "bin/codex.exe",
|
||||
files: &["bin/codex.exe", "bin/codex-code-mode-host.exe", "codex-path/rg.exe", "codex-resources/codex-command-runner.exe", "codex-resources/codex-windows-sandbox-setup.exe", "codex-package.json"],
|
||||
},
|
||||
CodexTarget {
|
||||
target: "aarch64-apple-darwin",
|
||||
platform: "darwin-arm64",
|
||||
directory: "mac-native/darwin-arm64",
|
||||
executable: "bin/codex",
|
||||
files: &["bin/codex", "bin/codex-code-mode-host", "codex-path/rg", "codex-resources/zsh/bin/zsh", "codex-package.json"],
|
||||
},
|
||||
CodexTarget {
|
||||
target: "x86_64-apple-darwin",
|
||||
platform: "darwin-x64",
|
||||
directory: "mac-native/darwin-x64",
|
||||
executable: "bin/codex",
|
||||
files: &["bin/codex", "bin/codex-code-mode-host", "codex-path/rg", "codex-resources/zsh/bin/zsh", "codex-package.json"],
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
pub const PLUGINS: Plugins = Plugins {
|
||||
source_directory: "plugins",
|
||||
destination_directory: "resources/plugins",
|
||||
manifest_file_name: "plugin.json",
|
||||
target_contains_any: &["windows", "apple-darwin"],
|
||||
subdirectories: &[
|
||||
Subdirectory {
|
||||
path: "src",
|
||||
plugin: "",
|
||||
origin: "source",
|
||||
target_contains: &[],
|
||||
targets: &[],
|
||||
features: &[],
|
||||
},
|
||||
Subdirectory {
|
||||
path: "panels",
|
||||
plugin: "",
|
||||
origin: "source",
|
||||
target_contains: &[],
|
||||
targets: &[],
|
||||
features: &[],
|
||||
},
|
||||
Subdirectory {
|
||||
path: "skills",
|
||||
plugin: "",
|
||||
origin: "source",
|
||||
target_contains: &[],
|
||||
targets: &[],
|
||||
features: &[],
|
||||
},
|
||||
Subdirectory {
|
||||
path: "native/payload",
|
||||
plugin: "agc-cocos-editor",
|
||||
origin: "prepared",
|
||||
target_contains: &["windows"],
|
||||
targets: &[],
|
||||
features: &["cocos-editor-injection"],
|
||||
},
|
||||
Subdirectory {
|
||||
path: "dotnet/publish/win-x64",
|
||||
plugin: "agc-unity-editor",
|
||||
origin: "prepared",
|
||||
target_contains: &[],
|
||||
targets: &["x86_64-pc-windows-msvc"],
|
||||
features: &["unity-editor-execute"],
|
||||
}
|
||||
],
|
||||
library_staging: &[
|
||||
LibraryStaging {
|
||||
plugin: "agc-godot-editor",
|
||||
source_subdirectory: "native/gdextension",
|
||||
targets: &["x86_64-pc-windows-msvc"],
|
||||
features: &["godot-editor-execute"],
|
||||
layout: "godot-bundle",
|
||||
}
|
||||
],
|
||||
skip_directory_names: &["target", "node_modules"],
|
||||
skip_directory_name_prefixes: &["."],
|
||||
skip_file_name_prefixes: &["."],
|
||||
skip_file_name_fragments: &[".test."],
|
||||
};
|
||||
@@ -0,0 +1,258 @@
|
||||
{
|
||||
"schema": "agc-package-layout.v1",
|
||||
"layoutVersion": 1,
|
||||
"description": "AGC 随包资源布局与复制规则的唯一声明。Rust 侧构建期校验与 Node 侧准备步骤共用本文件,任何一侧都不得再写第二份布局或组件白名单。含 <platform>、<target> 占位符的字段由调用方按目标三元展开。修改布局时同步递增 layoutVersion(准备步骤的缓存 key 组成部分)。",
|
||||
"codex": {
|
||||
"version": "0.155.1",
|
||||
"cliVersionPrefix": "codex-cli ",
|
||||
"manifestSchema": "genarrative-codex-sidecar.v2",
|
||||
"packageMetadata": {
|
||||
"layoutVersion": 1,
|
||||
"resourcesDir": "codex-resources",
|
||||
"pathDir": "codex-path"
|
||||
},
|
||||
"resourceDirectory": "resources/codex",
|
||||
"manifestFileName": "manifest.json",
|
||||
"packageMetadataFileName": "codex-package.json",
|
||||
"noticeFileName": "NOTICE.md",
|
||||
"sourceRoots": [
|
||||
"app",
|
||||
"repo"
|
||||
],
|
||||
"sourceRelativePaths": [
|
||||
"node_modules/@openai/codex-<platform>/vendor/<target>",
|
||||
"node_modules/@openai/codex/node_modules/@openai/codex-<platform>/vendor/<target>"
|
||||
],
|
||||
"noticeSources": [
|
||||
{
|
||||
"targets": [
|
||||
"aarch64-apple-darwin",
|
||||
"x86_64-apple-darwin"
|
||||
],
|
||||
"source": "resources/codex/【声明】Mac内置Codex组件-2026-09-18.md",
|
||||
"preserve": false
|
||||
},
|
||||
{
|
||||
"targets": [
|
||||
"x86_64-pc-windows-msvc"
|
||||
],
|
||||
"source": "resources/codex/win-x64/NOTICE.md",
|
||||
"preserve": true
|
||||
}
|
||||
],
|
||||
"universalGroups": [
|
||||
{
|
||||
"name": "mac-native",
|
||||
"directory": "mac-native",
|
||||
"targets": [
|
||||
"aarch64-apple-darwin",
|
||||
"x86_64-apple-darwin"
|
||||
]
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"target": "x86_64-pc-windows-msvc",
|
||||
"platform": "win32-x64",
|
||||
"directory": "win-x64",
|
||||
"executable": "bin/codex.exe",
|
||||
"files": [
|
||||
"bin/codex.exe",
|
||||
"bin/codex-code-mode-host.exe",
|
||||
"codex-path/rg.exe",
|
||||
"codex-resources/codex-command-runner.exe",
|
||||
"codex-resources/codex-windows-sandbox-setup.exe",
|
||||
"codex-package.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "aarch64-apple-darwin",
|
||||
"platform": "darwin-arm64",
|
||||
"directory": "mac-native/darwin-arm64",
|
||||
"executable": "bin/codex",
|
||||
"files": [
|
||||
"bin/codex",
|
||||
"bin/codex-code-mode-host",
|
||||
"codex-path/rg",
|
||||
"codex-resources/zsh/bin/zsh",
|
||||
"codex-package.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "x86_64-apple-darwin",
|
||||
"platform": "darwin-x64",
|
||||
"directory": "mac-native/darwin-x64",
|
||||
"executable": "bin/codex",
|
||||
"files": [
|
||||
"bin/codex",
|
||||
"bin/codex-code-mode-host",
|
||||
"codex-path/rg",
|
||||
"codex-resources/zsh/bin/zsh",
|
||||
"codex-package.json"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": {
|
||||
"sourceDirectory": "plugins",
|
||||
"destinationDirectory": "resources/plugins",
|
||||
"manifestFileName": "plugin.json",
|
||||
"targetContainsAny": [
|
||||
"windows",
|
||||
"apple-darwin"
|
||||
],
|
||||
"subdirectories": [
|
||||
{
|
||||
"path": "src",
|
||||
"origin": "source"
|
||||
},
|
||||
{
|
||||
"path": "panels",
|
||||
"origin": "source"
|
||||
},
|
||||
{
|
||||
"path": "skills",
|
||||
"origin": "source"
|
||||
},
|
||||
{
|
||||
"path": "native/payload",
|
||||
"origin": "prepared",
|
||||
"targetContains": [
|
||||
"windows"
|
||||
],
|
||||
"plugin": "agc-cocos-editor",
|
||||
"prepare": "cocos-bridge-build",
|
||||
"features": [
|
||||
"cocos-editor-injection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "dotnet/publish/win-x64",
|
||||
"origin": "prepared",
|
||||
"prepare": "unity-helper-publish",
|
||||
"targets": [
|
||||
"x86_64-pc-windows-msvc"
|
||||
],
|
||||
"features": [
|
||||
"unity-editor-execute"
|
||||
],
|
||||
"plugin": "agc-unity-editor"
|
||||
}
|
||||
],
|
||||
"libraryStaging": [
|
||||
{
|
||||
"plugin": "agc-godot-editor",
|
||||
"sourceSubdirectory": "native/gdextension",
|
||||
"prepare": "godot-extension-build",
|
||||
"targets": [
|
||||
"x86_64-pc-windows-msvc"
|
||||
],
|
||||
"features": [
|
||||
"godot-editor-execute"
|
||||
],
|
||||
"layout": "godot-bundle",
|
||||
"files": [
|
||||
"bin/win-x64/agc_godot_editor.dll",
|
||||
"bin/win-x64/metadata.json",
|
||||
"vendor/LICENSE.txt",
|
||||
"vendor/provenance.json"
|
||||
]
|
||||
}
|
||||
],
|
||||
"nativePayloads": [
|
||||
{
|
||||
"plugin": "agc-cocos-editor",
|
||||
"prepare": "cocos-bridge-build",
|
||||
"sourceFileName": "cocos_editor_bridge.dll",
|
||||
"destinationSubdirectory": "native/payload",
|
||||
"targets": [
|
||||
"x86_64-pc-windows-msvc"
|
||||
],
|
||||
"features": [
|
||||
"cocos-editor-injection"
|
||||
],
|
||||
"destinationFileName": "cocos-editor-bridge.dll"
|
||||
}
|
||||
],
|
||||
"prepareSteps": [
|
||||
{
|
||||
"name": "unity-helper-publish",
|
||||
"kind": "powershell",
|
||||
"workingDirectory": "plugins/agc-unity-editor/dotnet",
|
||||
"scriptFileName": "build.ps1",
|
||||
"fingerprint": {
|
||||
"roots": [
|
||||
"."
|
||||
],
|
||||
"excludeDirectoryNames": [
|
||||
"bin",
|
||||
"obj",
|
||||
"publish",
|
||||
"native-build"
|
||||
],
|
||||
"stampRelativePath": "publish/win-x64/.agc-source.sha256"
|
||||
},
|
||||
"requiredOutputs": [
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/Agc.Unity.Attach.exe",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/NOTICE",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/THIRD-PARTY-NOTICES.txt",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/licenses/DotCraft-Apache-2.0.txt",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/licenses/Roslyn-MIT.txt",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/licenses/upstream.json",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/licenses/dotnet-LICENSE.TXT",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/licenses/dotnet-THIRD-PARTY-NOTICES.TXT",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/licenses/microsoft.codeanalysis.common-ThirdPartyNotices.rtf",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/licenses/microsoft.codeanalysis.csharp-ThirdPartyNotices.rtf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "godot-extension-build",
|
||||
"kind": "powershell",
|
||||
"workingDirectory": "plugins/agc-godot-editor/native/gdextension",
|
||||
"scriptFileName": "build.ps1",
|
||||
"removeEnvironment": [
|
||||
"PSModulePath"
|
||||
],
|
||||
"requiredOutputs": [
|
||||
"plugins/agc-godot-editor/native/gdextension/bin/win-x64/agc_godot_editor.dll",
|
||||
"plugins/agc-godot-editor/native/gdextension/bin/win-x64/metadata.json",
|
||||
"plugins/agc-godot-editor/native/gdextension/vendor/LICENSE.txt",
|
||||
"plugins/agc-godot-editor/native/gdextension/vendor/provenance.json"
|
||||
],
|
||||
"fingerprint": {
|
||||
"roots": [
|
||||
"."
|
||||
],
|
||||
"excludeDirectoryNames": [
|
||||
"bin",
|
||||
".build",
|
||||
"native-build"
|
||||
],
|
||||
"stampRelativePath": "bin/win-x64/.agc-source.sha256"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cocos-bridge-build",
|
||||
"kind": "cargo",
|
||||
"packageDirectory": "plugins/agc-cocos-editor/native/cocos-editor-bridge",
|
||||
"features": [
|
||||
"windows-injection"
|
||||
],
|
||||
"requiredOutputs": []
|
||||
}
|
||||
],
|
||||
"skipDirectoryNames": [
|
||||
"target",
|
||||
"node_modules"
|
||||
],
|
||||
"skipDirectoryNamePrefixes": [
|
||||
"."
|
||||
],
|
||||
"skipFileNamePrefixes": [
|
||||
"."
|
||||
],
|
||||
"skipFileNameFragments": [
|
||||
".test."
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
共享过程文件(如需维护,请使用这些相对路径):
|
||||
- project/analysis.md:重要取舍的依据与当前结论。
|
||||
- project/决策台账.md:待处理事项与下一步,必要时引用相关文档。
|
||||
- project/dialog.md:仅在用户需要时记录对话摘要或交接信息。
|
||||
- project/analysis.md
|
||||
- project/决策台账.md
|
||||
- project/dialog.md
|
||||
正式产物使用当前阶段指定的相对路径。
|
||||
五个策划阶段的审批:当你判断当前策划阶段必需产物已完成时,必须提交阶段审批。用户批准后进入下一阶段。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
概念阶段定稿时,创建或更新 `project/速览卡.md`,简要介绍当前游戏。后续仅在核心体验、范围、平台等概览内容变化时更新,不复制完整决策清单。下面是速览卡的参考结构;根据游戏类型、项目规模和用户要求选择适用字段,同类内容可以合并,复杂项目可以增加必要字段。表格和列表中的示例行按实际对象逐行扩展:
|
||||
概念阶段定稿时,创建或更新 `project/速览卡.md`。下面是速览卡的参考结构;根据游戏类型、项目规模和用户要求选择适用字段,同类内容可以合并,复杂项目可以增加必要字段。表格和列表中的示例行按实际对象逐行扩展:
|
||||
|
||||
# 速览卡:《游戏名》
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
当前阶段:顶层设计。明确游玩过程、关键规则与反馈、版本范围和验证计划,为系统划分提供依据。
|
||||
当前阶段:顶层设计。明确玩家持续游玩的循环、资源流、节奏和系统范围。
|
||||
|
||||
File diff suppressed because one or more lines are too long
+64
-6
@@ -1,8 +1,66 @@
|
||||
# 决策台账:《星露谷物语》示例项目
|
||||
# 决策台账:《星露谷物语》金样项目
|
||||
|
||||
仅列仍需跟进的事项和下一步。详细依据与采用的规则见相关分析和设计文档;事项完成后移出待办。
|
||||
版本:v3 | 规则:台账放活队列——design 只放结论、分析只放论证、决定与开放问题住这里。编号连续不复用;被推翻的行标 overturned 挂新行,不删行。
|
||||
状态六态:`confirmed`(用户亲口/亲选)/ `auto_decided`(技术类代决,必带理由+推翻条件,用户一键可翻)/ `default_pending`(默认建议兜底,用户未点头)/ `prototype_pending`(待原型验证)/ `pending_user`(等用户拍板)/ `overturned`(被推翻,挂旧行编号)。
|
||||
|
||||
- 体力与战斗是否共享资源:涉及探索压力和恢复规则,结合用户对战斗体验的要求确认,再更新相关系统设计。
|
||||
- 背包使用格子还是重量限制:在确定存档和界面结构前确认,选择依据记录在分析文档。
|
||||
- 战斗判定窗口的手感:通过矿井原型试玩观察是否易懂、是否拖慢探索,依据见分析文档“战斗判定窗口是否需要调整”。
|
||||
- 天气权重是否合适:用前几天的游玩样本检查连续雨天或长期无雨的影响,再调整配置。
|
||||
> 编号口径:D-01~D-13 与 templates/stardew-analysis.md 台账节选一致(D-04~D-06、D-08~D-10、D-12 原为"就地小权衡,直接登记未开条目",此处按登记口径展开);D-14 起为技术文档期新增,与 stardew-tdd-tech.md 开放问题回执互引。
|
||||
|
||||
## 当前待办(活队列)
|
||||
|
||||
### 等用户拍板(pending_user)
|
||||
|
||||
| 编号 | 决定 | 层 | 谁 | 依据 | 推翻条件 | 状态 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| D-14 | 体力与战斗共享单池 | TDD | user | 风险资源统一制造取舍(概念张力一);**暂按共享实现,改单拆只需改 S02 成本入口** | 战斗参与率实测过低(玩家回避矿井) | pending_user(暂按共享实现) |
|
||||
| D-15 | 背包格子制 vs 重量制 | TDD | user | 格子制直觉、重量制焦虑感与 T5"休闲不打卡"冲突;暂按格子制实现、存档预留 capacity_type 字段 | 格子管理成为主要负面反馈 | pending_user(B 级阻断存档结构,暂按格子制) |
|
||||
|
||||
### 待原型验证(prototype_pending)
|
||||
|
||||
| 编号 | 决定 | 层 | 谁 | 依据 | 推翻条件 | 状态 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| D-13b | 战斗判定窗口手感(前摇帧数/无敌帧 450ms 基准) | 系统 | user | 数值可定、手感不可纸面验证 | 原型显示节奏拖慢/玩家困惑 | prototype_pending(规则本体见 D-13 confirmed) |
|
||||
|
||||
### 默认建议兜底(default_pending)
|
||||
|
||||
| 编号 | 决定 | 层 | 谁 | 依据 | 推翻条件 | 状态 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| D-19 | 天气权重表具体数值(晴/雨/风暴按季节) | TDD | agent | 概念层只定"雨免浇水"定性;数值推内容期填 | 前 5 日出现连续 3 日雨/全无雨 | default_pending(默认值已进数据表,带 designer_note) |
|
||||
|
||||
## 已采用决定
|
||||
|
||||
### 用户确认(confirmed)
|
||||
|
||||
| 编号 | 决定 | 层 | 谁 | 依据 | 推翻条件 | 状态 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| D-01 | 定调:牧场物语系参照、治愈慢节奏 | 概念 | user | 用户原始需求 | — | confirmed |
|
||||
| D-02 | 单人体验,无多人 | 概念 | user | 概念层非目标 | — | confirmed |
|
||||
| D-03 | 战斗保持伴生风险,不做装备驱动主轴 | 概念 | user | 概念期问题一 | 矿井流失率过半且归因战斗 | confirmed |
|
||||
| D-07 | 日目标自设,季节与社区提供低频牵引 | 顶层 | user | 顶层期问题一 | 新手周流失归因无方向 | confirmed |
|
||||
| D-11 | 采集/钓鱼/战斗统一"活动结果"接口 | 架构 | user | 架构期问题一 | 第三活动类型出现结构性差异 | confirmed |
|
||||
| D-13 | 战斗采用节奏/指令判定 | 系统 | user | S06 问题一 | 原型显示节奏拖慢/玩家困惑 | confirmed(手感部分拆 D-13b prototype_pending) |
|
||||
|
||||
### 技术代决(auto_decided——带理由与推翻条件,用户一键可翻)
|
||||
|
||||
| 编号 | 决定 | 层 | 谁 | 依据(理由) | 推翻条件 | 状态 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| D-04 | 时间片制:700ms=10 游戏分钟 | 概念 | agent | 原作实证节拍;一天≈14 分钟真实时间贴合 T5"休闲" | 内测一天体感过短/过长 | auto_decided |
|
||||
| D-05 | 分区域切换(区域独立场景,非连续地图) | 概念 | agent | 概念层"不是什么:无边界开放世界";区域小网络全部步行可达 | 场景切换成为移动负担反馈 | auto_decided |
|
||||
| D-06 | 28 日/季、四季/年 | 顶层 | agent | 季节窗口制造"本季计划"节奏(支柱二) | 换季频率在测试中被无视 | auto_decided |
|
||||
| D-09 | 商店营业时段走条件表 | 架构 | agent | 与配方/区域解锁共用 check(condition_id) 单一入口 | 条件表规模膨胀难维护 | auto_decided |
|
||||
| D-10 | 出货箱日终统一结算 | 架构 | agent | 收入集中进日终面板,强化"一天一结算"叙事;商店现卖保留即时通道 | 玩家普遍绕开出货箱 | auto_decided |
|
||||
| D-12 | 工具升级期间该工具不可用 | 系统 | agent | 升级=时间成本换效率(顶层张力二);备用旧工具暂不做(开放问题) | 升级期挫败感集中爆发 | auto_decided |
|
||||
| D-16 | 矿井逐层生成本期不做(P2) | TDD | agent | GDD 已标"不做无限地牢";首期按布局池 8~12 模板拼装 | 内测要求深度爬塔玩法 | auto_decided |
|
||||
| D-17 | 换装首期 5 层(基础体/裤/衣/发型/饰件),非 19 层 | TDD | agent | 外观自定义非首期卖点;层结构预留到 19 层 | 外观系统成核心诉求 | auto_decided |
|
||||
| D-18 | 作物品质三档:普通/银/金 | TDD | agent | 经济分层需要(即时变现 vs 等待升值的取舍) | 银金档无人区分、一律普通出售 | auto_decided |
|
||||
|
||||
### 已推翻(overturned——旧行保留,挂新行)
|
||||
|
||||
| 编号 | 决定 | 层 | 谁 | 依据 | 推翻条件 | 状态 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| D-08 | 作物品质两档:普通/银 | TDD | agent | 早期小权衡:两档最简 | — | **overturned → D-18**(经济分层不足,改三档;铱档留 P1) |
|
||||
|
||||
## 队列纪律(使用说明)
|
||||
|
||||
- 新决定入队:拿下一号(当前最大 D-19,下一号 D-20);就现代决可登记不开条目,但状态必须写 auto_decided 并带理由+推翻条件。
|
||||
- 用户翻案:旧行标 overturned 挂新行,受影响文档节重写(本台账只记录,不代改)。
|
||||
- 概念层变更定稿后:速览卡"决定状态与原型验证项"字段随本文件最新版同步。
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user