Compare commits

..

1 Commits

Author SHA1 Message Date
k88936 bd94db73c9 修复资源卡片 Lucide 图标描边宽度
Project CI / Repository checks (pull_request) Failing after 13s
Project CI / Backend tests (pull_request) Failing after 9s
Project CI / Frontend tests (pull_request) Successful in 3m14s
Project CI / Native shell tests (pull_request) Successful in 18m2s
恢复 CanvasWorld 超采样下资源卡片 SVG 图标的 inverse-scale 描边补偿

新增资源工作台 CSS 回归断言,防止局部图标规则被误删

同步 CanvasWorld 超采样常量与图标描边约定文档
2026-09-12 15:04:08 +08:00
222 changed files with 535 additions and 27875 deletions
-31
View File
@@ -232,37 +232,6 @@ jobs:
done done
done done
- name: Prepare standalone Rust crate dependencies
shell: bash
run: |
set -euo pipefail
# agent-runtime-core / agent-runtime-orchestration 被 server-rs/Cargo.toml 的
# exclude 排除,不参与上面的 workspace 锁文件,因此上面那次锁定 fetch 覆盖不到它们;
# 而 check:native-shells 会经 agent-runtime-*:check 用 `cargo test --manifest-path`
# 单独跑这两个 crate。不在这里预热的话,这两条测试会在测试阶段自己
# `Updating crates.io index`crates.io 一抖动整条 native shell 作业就红
# (见 #327 / PR #316 run 1950)。
# 两个 crate 都没有提交 Cargo.lock,所以这里只能做不带锁标志的 fetch:
# 加锁标志会因为缺少锁文件直接失败。生成的 Cargo.lock 落在两个 crate 目录内,
# 已被各自的 .gitignore 忽略,只留在容器里;随后的测试阶段因此能用锁定版本
# 解析,不再触碰 registry index。
for manifest_path in \
server-rs/crates/agent-runtime-core/Cargo.toml \
server-rs/crates/agent-runtime-orchestration/Cargo.toml; do
for attempt in $(seq 1 5); do
if cargo fetch \
--target x86_64-unknown-linux-gnu \
--manifest-path "${manifest_path}"; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo "standalone crate dependency fetch failed after 5 attempts: ${manifest_path}" >&2
exit 1
fi
sleep $((attempt * 2))
done
done
- name: Run native shell gates - name: Run native shell gates
run: npm run check:native-shells run: npm run check:native-shells
-2
View File
@@ -40,8 +40,6 @@ temp*build*/
/apps/ai-game-creator-shell/src-tauri/resources/codex/win-x64/codex-path/ /apps/ai-game-creator-shell/src-tauri/resources/codex/win-x64/codex-path/
/apps/ai-game-creator-shell/src-tauri/resources/codex/win-x64/codex-resources/ /apps/ai-game-creator-shell/src-tauri/resources/codex/win-x64/codex-resources/
/apps/ai-game-creator-shell/src-tauri/resources/codex/win-x64/codex-package.json /apps/ai-game-creator-shell/src-tauri/resources/codex/win-x64/codex-package.json
/apps/ai-game-creator-shell/src-tauri/resources/plugins/
/plugins/agc-cocos-editor/native/payload/
/apps/ai-game-creator-shell/logs/ /apps/ai-game-creator-shell/logs/
/apps/ai-game-creator-shell/.llm-drafts/ /apps/ai-game-creator-shell/.llm-drafts/
/apps/ai-game-creator-shell/game-creator.config.local.json /apps/ai-game-creator-shell/game-creator.config.local.json
-4
View File
@@ -1,4 +0,0 @@
# resources/plugins 由 build.rs 从 plugins/ 复制生成,属于构建产物。
# 它在 dev 监听范围内,重新生成会让 Tauri dev 误判为源码改动而触发
# “构建 -> 监听 -> 再构建”的自触发循环。
resources/plugins/
@@ -4,11 +4,6 @@ import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import {
defaultEditorFeatures,
withDefaultCargoFeatures,
} from './cargo-features.mjs';
const appRoot = fileURLToPath(new URL('..', import.meta.url)); const appRoot = fileURLToPath(new URL('..', import.meta.url));
const defaultReleaseTarget = 'x86_64-pc-windows-msvc'; const defaultReleaseTarget = 'x86_64-pc-windows-msvc';
const releaseTarget = const releaseTarget =
@@ -164,30 +159,10 @@ export async function prepareReleaseVersion() {
return nextVersion; return nextVersion;
} }
export function buildTauriBuildArguments(
args = [],
target = releaseTarget,
platform = process.platform,
) {
const noBundle = args.includes('--no-bundle');
const targetIndex = args.indexOf('--target');
const explicitTarget =
targetIndex >= 0
? args[targetIndex + 1]
: args
.find((value) => value.startsWith('--target='))
?.slice('--target='.length);
const targetArgs = noBundle || explicitTarget ? [] : ['--target', target];
const features = defaultEditorFeatures(
explicitTarget || (noBundle ? platform : target),
);
return [
'build',
...withDefaultCargoFeatures([...targetArgs, ...args], features),
];
}
export function runTauriBuild(args = []) { export function runTauriBuild(args = []) {
const noBundle = args.includes('--no-bundle');
const hasTarget = args.includes('--target');
const targetArgs = noBundle || hasTarget ? [] : ['--target', releaseTarget];
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const result = spawnSync( const result = spawnSync(
npmCommand, npmCommand,
@@ -197,7 +172,9 @@ export function runTauriBuild(args = []) {
'exec', 'exec',
'tauri', 'tauri',
'--', '--',
...buildTauriBuildArguments(args), 'build',
...targetArgs,
...args,
], ],
{ cwd: appRoot, stdio: 'inherit', shell: process.platform === 'win32' }, { cwd: appRoot, stdio: 'inherit', shell: process.platform === 'win32' },
); );
@@ -1,24 +0,0 @@
/** 默认桌面能力;显式 feature 参数优先,不把应用参数当 Cargo 参数。 */
export function withDefaultCargoFeatures(argv, features) {
const separator = argv.indexOf('--');
const cargoArgs = separator < 0 ? argv : argv.slice(0, separator);
if (
!features.length ||
cargoArgs.some(
(value) =>
value === '--features' ||
value === '-f' ||
value.startsWith('--features=') ||
/^-f.+/u.test(value),
)
) {
return argv;
}
return [`--features=${features.join(',')}`, ...argv];
}
export function defaultEditorFeatures(target) {
return target === 'win32' || target.includes('windows')
? ['cocos-editor-execute']
: [];
}
@@ -1,49 +0,0 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { buildTauriBuildArguments } from './build-release.mjs';
import { withDefaultCargoFeatures } from './cargo-features.mjs';
test('Windows release includes the same editor feature as development', () => {
assert.deepEqual(
buildTauriBuildArguments([], 'x86_64-pc-windows-msvc', 'win32'),
[
'build',
'--features=cocos-editor-execute',
'--target',
'x86_64-pc-windows-msvc',
],
);
assert.deepEqual(
buildTauriBuildArguments(
['--no-bundle'],
'x86_64-pc-windows-msvc',
'linux',
),
['build', '--no-bundle'],
);
assert.deepEqual(
buildTauriBuildArguments(['--target=aarch64-apple-darwin']),
['build', '--target=aarch64-apple-darwin'],
);
});
test('explicit Cargo features override defaults in every supported spelling', () => {
for (const args of [
['--features', 'custom'],
['--features=custom'],
['-f', 'custom'],
['-fcustom'],
]) {
assert.deepEqual(
withDefaultCargoFeatures(args, ['cocos-editor-execute']),
args,
);
}
assert.deepEqual(
withDefaultCargoFeatures(
['--', '--features=app'],
['cocos-editor-execute'],
),
['--features=cocos-editor-execute', '--', '--features=app'],
);
});
@@ -114,20 +114,9 @@ const allowedUncalledTauriCommands = [
'open_game_creator_launcher_window', 'open_game_creator_launcher_window',
'open_game_creator_workspace_window', 'open_game_creator_workspace_window',
'read_direct_project_conversation', 'read_direct_project_conversation',
'reset_design_agent_session',
'stop_local_game_preview_if_matches', 'stop_local_game_preview_if_matches',
'start_game_creator_external_mcp', 'start_game_creator_external_mcp',
'stop_game_creator_external_mcp', 'stop_game_creator_external_mcp',
'list_agc_plugins',
'list_agc_extensions',
'refresh_agc_plugins',
'start_agc_plugin',
'stop_agc_plugin',
'reload_agc_plugin',
'call_agc_plugin',
'read_agc_plugin_panel',
'set_agc_plugin_project_path',
'set_agc_plugin_enabled',
]; ];
const sourceExtensions = new Set([ const sourceExtensions = new Set([
'.json', '.json',
@@ -1296,10 +1285,7 @@ if (tauriConfig.identifier !== 'world.genarrative.ai-game-creator') {
throw new Error('AI game creator shell identifier drifted'); throw new Error('AI game creator shell identifier drifted');
} }
const expectedBundledDesignAgentResources = { const expectedBundledCodexResources = {
'design-agent': 'design-agent',
};
const expectedBundledWindowsResources = {
'resources/codex/win-x64/bin/codex.exe': 'codex/win-x64/bin/codex.exe', 'resources/codex/win-x64/bin/codex.exe': 'codex/win-x64/bin/codex.exe',
'resources/codex/win-x64/bin/codex-code-mode-host.exe': 'resources/codex/win-x64/bin/codex-code-mode-host.exe':
'codex/win-x64/bin/codex-code-mode-host.exe', 'codex/win-x64/bin/codex-code-mode-host.exe',
@@ -1313,35 +1299,17 @@ const expectedBundledWindowsResources = {
'codex/win-x64/codex-package.json', 'codex/win-x64/codex-package.json',
'resources/codex/win-x64/NOTICE.md': 'codex/win-x64/NOTICE.md', 'resources/codex/win-x64/NOTICE.md': 'codex/win-x64/NOTICE.md',
'resources/codex/win-x64/manifest.json': 'codex/win-x64/manifest.json', 'resources/codex/win-x64/manifest.json': 'codex/win-x64/manifest.json',
'resources/plugins': 'plugins',
}; };
assert.deepEqual( if (tauriConfig.bundle?.resources !== undefined) {
tauriConfig.bundle?.resources, throw new Error(
expectedBundledDesignAgentResources, 'AI game creator shell base Tauri config must not require Windows-only Codex resources',
'AI game creator shell base Tauri config must bundle the design-agent resource pack', );
);
for (const key of Object.keys(tauriConfig.bundle?.resources ?? {})) {
if (String(key).includes('codex')) {
throw new Error(
'AI game creator shell base Tauri config must not require Windows-only Codex resources',
);
}
} }
assert.deepEqual( assert.deepEqual(
windowsTauriConfig.bundle?.resources, windowsTauriConfig.bundle?.resources,
expectedBundledWindowsResources, expectedBundledCodexResources,
'AI game creator shell Windows Tauri config must bundle the complete pinned Codex resource set and Cocos bridge payload directory', 'AI game creator shell Windows Tauri config must bundle the complete pinned Codex resource set',
); );
if (
Object.prototype.hasOwnProperty.call(
windowsTauriConfig.bundle?.resources ?? {},
'design-agent',
)
) {
throw new Error(
'design-agent resource pack must not be mixed into the Windows Codex sidecar bundle',
);
}
if (windowsTauriConfig.bundle?.useLocalToolsDir !== true) { if (windowsTauriConfig.bundle?.useLocalToolsDir !== true) {
throw new Error( throw new Error(
'AI game creator shell Windows Tauri config must cache bundling tools in the project target directory', 'AI game creator shell Windows Tauri config must cache bundling tools in the project target directory',
@@ -436,10 +436,6 @@ function spawnChild(command, args, options, spawnImpl = spawn) {
const child = spawnImpl(command, args, { const child = spawnImpl(command, args, {
...options, ...options,
shell: useShell, shell: useShell,
// npm.cmd and the Windows shell otherwise create a visible console for
// every service in the dev stack. Their stdout/stderr is already inherited
// by the launcher, so no separate terminal window is useful.
windowsHide: process.platform === 'win32' ? true : options.windowsHide,
// POSIX 下让每个长驻服务拥有独立进程组,退出时可以连同 npm、 // POSIX 下让每个长驻服务拥有独立进程组,退出时可以连同 npm、
// Node、Cargo 及其子进程一起清理,避免残留订阅任务持续刷日志。 // Node、Cargo 及其子进程一起清理,避免残留订阅任务持续刷日志。
detached: isPosix, detached: isPosix,
@@ -1,7 +1,6 @@
import { resolve } from 'node:path'; import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { withDefaultCargoFeatures } from './cargo-features.mjs';
import { import {
readAgcDevEndpoint, readAgcDevEndpoint,
resolveAgcDevEndpoint, resolveAgcDevEndpoint,
@@ -21,10 +20,6 @@ import {
const appRoot = fileURLToPath(new URL('..', import.meta.url)); const appRoot = fileURLToPath(new URL('..', import.meta.url));
const repoRoot = resolve(appRoot, '../..'); const repoRoot = resolve(appRoot, '../..');
const tauriCliPath = resolve(repoRoot, 'node_modules/@tauri-apps/cli/tauri.js'); const tauriCliPath = resolve(repoRoot, 'node_modules/@tauri-apps/cli/tauri.js');
const AGC_DESIGN_DEBUG_ENV = 'GENARRATIVE_AGC_DESIGN_DEBUG';
const AGC_DESIGN_DEBUG_VITE_ENV = 'VITE_GENARRATIVE_AGC_DESIGN_DEBUG';
const designDebugEnabled =
process.env[AGC_DESIGN_DEBUG_ENV]?.trim() === '0' ? '0' : '1';
function buildTauriArguments(argv, devUrl = readAgcDevEndpoint().url) { function buildTauriArguments(argv, devUrl = readAgcDevEndpoint().url) {
const args = [...argv]; const args = [...argv];
@@ -48,24 +43,6 @@ function buildTauriArguments(argv, devUrl = readAgcDevEndpoint().url) {
]; ];
} }
// `agc_cocos_execute` 与 Cocos 编辑器适配器只在 `cocos-editor-execute` feature 下
// 注册。开发构建默认在 Windows 打开它,否则 Agent 的工具清单里根本没有该工具,
// 只能退化成改写脚本。可用 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 process.platform === 'win32' ? ['cocos-editor-execute'] : [];
}
function withDevCargoFeatures(argv, features = readDevCargoFeatures()) {
return withDefaultCargoFeatures(argv, features);
}
function spawnTauriCli(argv, { env = process.env } = {}) { function spawnTauriCli(argv, { env = process.env } = {}) {
return spawnChild(process.execPath, [tauriCliPath, ...argv], { return spawnChild(process.execPath, [tauriCliPath, ...argv], {
cwd: appRoot, cwd: appRoot,
@@ -129,16 +106,9 @@ async function runTauriDev(
shutdownRequested.then(() => false), shutdownRequested.then(() => false),
]); ]);
if (!prepared || shutdownSignal) return 1; if (!prepared || shutdownSignal) return 1;
const tauriArguments = buildTauriArguments( const tauriArguments = buildTauriArguments(argv, endpoint.url);
withDevCargoFeatures(argv),
endpoint.url,
);
child = spawnCli(tauriArguments, { child = spawnCli(tauriArguments, {
env: { env: withAgcDevEndpointEnv(endpoint),
...withAgcDevEndpointEnv(endpoint),
[AGC_DESIGN_DEBUG_ENV]: designDebugEnabled,
[AGC_DESIGN_DEBUG_VITE_ENV]: designDebugEnabled,
},
}); });
const childResult = waitForCli(child); const childResult = waitForCli(child);
const outcome = await Promise.race([ const outcome = await Promise.race([
@@ -187,13 +157,7 @@ async function prepareFrontendDev(endpoint, { onChild, signal }) {
const frontend = spawnChild( const frontend = spawnChild(
process.platform === 'win32' ? 'npm.cmd' : 'npm', process.platform === 'win32' ? 'npm.cmd' : 'npm',
['run', 'agc:serve'], ['run', 'agc:serve'],
{ { cwd: repoRoot, env: withAgcDevEndpointEnv(endpoint) },
cwd: repoRoot,
env: {
...withAgcDevEndpointEnv(endpoint),
[AGC_DESIGN_DEBUG_VITE_ENV]: designDebugEnabled,
},
},
); );
onChild(frontend); onChild(frontend);
console.log( console.log(
@@ -229,7 +193,6 @@ export {
isDirectModuleExecution, isDirectModuleExecution,
runTauriDev, runTauriDev,
spawnTauriCli, spawnTauriCli,
withDevCargoFeatures,
}; };
if (isDirectModuleExecution()) { if (isDirectModuleExecution()) {
@@ -1,4 +0,0 @@
# resources/plugins 由 build.rs 从 plugins/ 复制生成,属于构建产物。
# 它在 dev 监听范围内,重新生成会让 Tauri dev 误判为源码改动而触发
# “构建 -> 监听 -> 再构建”的自触发循环。
resources/plugins/
-25
View File
@@ -733,20 +733,6 @@ dependencies = [
"error-code", "error-code",
] ]
[[package]]
name = "cocos-editor-bridge"
version = "0.1.0"
dependencies = [
"cc",
"editor-adapter-api",
"reqwest 0.12.28",
"serde",
"serde_json",
"sha2",
"tungstenite",
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "combine" name = "combine"
version = "4.6.7" version = "4.6.7"
@@ -1219,14 +1205,6 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]]
name = "editor-adapter-api"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.16.0" version = "1.16.0"
@@ -1731,8 +1709,6 @@ dependencies = [
"axum", "axum",
"base64 0.22.1", "base64 0.22.1",
"chromiumoxide", "chromiumoxide",
"cocos-editor-bridge",
"editor-adapter-api",
"futures", "futures",
"getrandom 0.3.4", "getrandom 0.3.4",
"http", "http",
@@ -4305,7 +4281,6 @@ dependencies = [
"cookie", "cookie",
"cookie_store", "cookie_store",
"encoding_rs", "encoding_rs",
"futures-channel",
"futures-core", "futures-core",
"futures-util", "futures-util",
"h2", "h2",
@@ -6,9 +6,6 @@ publish = false
[features] [features]
default = [] default = []
cocos-editor = ["cocos-editor-bridge/process-discovery"]
cocos-editor-execute = ["cocos-editor", "cocos-editor-bridge/windows-bootstrap"]
cocos-editor-injection = ["cocos-editor-execute", "cocos-editor-bridge/windows-injection"]
[build-dependencies] [build-dependencies]
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
@@ -22,8 +19,6 @@ ts-rs = "12.0.1"
typed_floats = { version = "1.0.7", features = ["serde"] } typed_floats = { version = "1.0.7", features = ["serde"] }
nalgebra = { version = "0.35.0", features = ["serde-serialize"] } nalgebra = { version = "0.35.0", features = ["serde-serialize"] }
agent-runtime-core = { path = "../../../server-rs/crates/agent-runtime-core" } agent-runtime-core = { path = "../../../server-rs/crates/agent-runtime-core" }
cocos-editor-bridge = { path = "../../../plugins/agc-cocos-editor/native/cocos-editor-bridge", default-features = false }
editor-adapter-api = { path = "../../../server-rs/crates/editor-adapter-api" }
base64 = "0.22" base64 = "0.22"
axum = "0.8" axum = "0.8"
chromiumoxide = "0.9.1" chromiumoxide = "0.9.1"
@@ -182,8 +182,6 @@ fn main() {
); );
let manifest_path = manifest_dir.join("prompts/runtime/manifest.json"); let manifest_path = manifest_dir.join("prompts/runtime/manifest.json");
stage_bundled_codex_cli(&manifest_dir); stage_bundled_codex_cli(&manifest_dir);
stage_plugin_workspace(&manifest_dir);
stage_cocos_editor_payload(&manifest_dir);
let compiled = runtime_prompt_bundle::compile_manifest(&manifest_path) let compiled = runtime_prompt_bundle::compile_manifest(&manifest_path)
.unwrap_or_else(|error| panic!("Prompt Bundle 编译失败:{error}")); .unwrap_or_else(|error| panic!("Prompt Bundle 编译失败:{error}"));
validate_seed_task_catalog(&compiled); validate_seed_task_catalog(&compiled);
@@ -205,146 +203,3 @@ fn main() {
} }
tauri_build::build() tauri_build::build()
} }
#[cfg(windows)]
fn stage_cocos_editor_payload(manifest_dir: &std::path::Path) {
if std::env::var_os("CARGO_FEATURE_COCOS_EDITOR_INJECTION").is_none() {
return;
}
let out_dir = std::path::PathBuf::from(std::env::var_os("OUT_DIR").expect("OUT_DIR"));
let profile_dir = out_dir
.ancestors()
.find(|path| path.file_name().is_some_and(|name| name == "build"))
.and_then(|build_dir| build_dir.parent())
.expect("AGC Cargo profile directory not found");
let candidates = [
profile_dir.join("deps/cocos_editor_bridge.dll"),
profile_dir.join("cocos_editor_bridge.dll"),
];
let source = candidates
.iter()
.find(|path| path.is_file())
.unwrap_or_else(|| {
panic!(
"Cocos bridge native payload 未构建:{}",
candidates
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join("")
)
});
for destination in [
// 插件工作区里的 payload 是开发态与打包态的唯一真源。
manifest_dir
.join("../../../plugins/agc-cocos-editor/native/payload/cocos-editor-bridge.dll"),
// 随包资源目录与 tauri.windows.conf.json 的 `resources/plugins` 映射保持一致。
manifest_dir
.join("resources/plugins/agc-cocos-editor/native/payload/cocos-editor-bridge.dll"),
] {
std::fs::create_dir_all(destination.parent().expect("payload resource parent"))
.expect("创建 Cocos bridge payload 目录失败");
std::fs::copy(source, &destination).expect("复制 Cocos bridge native payload 失败");
}
println!("cargo:rerun-if-changed={}", source.display());
}
#[cfg(not(windows))]
fn stage_cocos_editor_payload(_manifest_dir: &std::path::Path) {}
/// 把 `plugins/` 工作区里的插件包随包映射到应用资源目录。
///
/// 只复制插件运行需要的清单、入口、面板和 native payload,不复制 native 源码、
/// Cargo target 目录或 node_modules。
#[cfg(windows)]
fn stage_plugin_workspace(manifest_dir: &std::path::Path) {
let repo_root = manifest_dir
.parent()
.and_then(|app_root| app_root.parent())
.and_then(|apps_dir| apps_dir.parent())
.expect("AGC 应用必须位于仓库 apps 目录下")
.to_path_buf();
let workspace = repo_root.join("plugins");
let destination_root = manifest_dir.join("resources/plugins");
std::fs::create_dir_all(&destination_root).expect("创建插件资源目录失败");
let entries = match std::fs::read_dir(&workspace) {
Ok(entries) => entries,
Err(_) => return,
};
for entry in entries.flatten() {
let plugin_root = entry.path();
if !plugin_root.is_dir() || !plugin_root.join("plugin.json").is_file() {
continue;
}
let name = entry.file_name();
let destination = destination_root.join(&name);
copy_plugin_file(
&plugin_root.join("plugin.json"),
&destination.join("plugin.json"),
);
for relative in [
std::path::PathBuf::from("src"),
std::path::PathBuf::from("panels"),
std::path::PathBuf::from("native/payload"),
] {
copy_plugin_tree(&plugin_root.join(&relative), &destination.join(&relative));
}
println!("cargo:rerun-if-changed={}", plugin_root.display());
}
}
#[cfg(windows)]
fn stage_plugin_file(source: &std::path::Path, destination: &std::path::Path) {
let Ok(bytes) = std::fs::read(source) else {
return;
};
if std::fs::read(destination).is_ok_and(|existing| existing == bytes) {
return;
}
if let Some(parent) = destination.parent() {
std::fs::create_dir_all(parent).expect("创建插件资源目录失败");
}
std::fs::write(destination, bytes).expect("复制插件资源失败");
}
#[cfg(windows)]
fn copy_plugin_tree(source: &std::path::Path, destination: &std::path::Path) {
let entries = match std::fs::read_dir(source) {
Ok(entries) => entries,
Err(_) => return,
};
for entry in entries.flatten() {
let target = destination.join(entry.file_name());
let path = entry.path();
if path.is_dir() {
let name = entry.file_name();
let name = name.to_string_lossy();
if matches!(name.as_ref(), "target" | "node_modules" | ".git") {
continue;
}
std::fs::create_dir_all(&target).expect("创建插件资源目录失败");
copy_plugin_tree(&path, &target);
} else {
// 测试文件不随包分发。
let name = entry.file_name();
let name = name.to_string_lossy();
if name.contains(".test.") {
continue;
}
stage_plugin_file(&path, &target);
}
}
}
#[cfg(windows)]
fn copy_plugin_file(source: &std::path::Path, destination: &std::path::Path) {
if !source.is_file() {
return;
}
std::fs::create_dir_all(destination.parent().expect("插件资源父目录"))
.expect("创建插件资源目录失败");
std::fs::copy(source, destination).expect("复制插件资源失败");
}
#[cfg(not(windows))]
fn stage_plugin_workspace(_manifest_dir: &std::path::Path) {}
@@ -1 +0,0 @@
当前阶段:系统架构。明确系统清单、职责边界、依赖和数据归属。
@@ -1,6 +0,0 @@
共享过程文件(如需维护,请使用这些相对路径):
- project/analysis.md
- project/决策台账.md
- project/dialog.md
不要把正式产物写在工作区根目录,也不要等审批失败后再迁移。
阶段审批工具:当你判断本阶段必需产物已完成时,必须提交阶段审批。用户批准后 Runtime 自动进入下一阶段;你不能自行切换阶段。
@@ -1 +0,0 @@
当前阶段:概念设计。明确游戏是什么、不是什么,并形成概念设计产物。
@@ -1,4 +0,0 @@
顾问阶段不需要继续自主推动项目或主动安排下一步;遵照用户的具体指示行动。
根据用户指示回答问题、读取相关文档、修改工作区文件,并说明改动可能影响的已有产物。
涉及方向性变化或多个可行方案时,先向用户说明影响并等待用户决定;不要替用户做决定。
顾问阶段没有下一层,也不需要提交阶段审批。
@@ -1 +0,0 @@
当前阶段:项目顾问。五个策划阶段已经完成,后续由用户指示驱动协作。
@@ -1,44 +0,0 @@
概念阶段定稿时,还必须创建或更新 `project/速览卡.md`。Runtime 只检查该文件是否存在,不检查内容。请使用下面的固定结构,不要加入审批操作说明或独立的决定状态段落:
# 速览卡:《游戏名》
## 1. 游戏名称
## 2. 游戏分类
## 3. 美术风格
- 视觉类型:
- 风格关键词:
- 色彩与氛围:
- MVP 美术边界:
## 4. 一句话描述
## 5. 游戏支柱
| 支柱 | 玩家感受 | 实现机制 |
|---|---|---|
## 6. 核心循环
## 7. 目标用户
- 核心用户:
- 游戏偏好:
- 单次游玩时长:
- 参考游戏与参考点:
## 8. 平台事实
## 9. 最小 MVP 系统
| 系统 | 最小功能 | 为什么必须有 | 验证方法 |
|---|---|---|---|
## 10. 给创作者的关键提示
- 先做:
- 暂时不做:
- 这样验证:
- 达标再扩展:
### 待原型验证项
- 问题:
- 原型:
- 观察:
@@ -1 +0,0 @@
当前阶段:系统文档。逐个完成已确定系统的内部规则、接口和验证标准。
@@ -1 +0,0 @@
当前阶段:技术文档。完成数据与配表、技术实现、美术圣经和总册。
@@ -1 +0,0 @@
当前阶段:顶层设计。明确玩家持续游玩的循环、资源流、节奏和系统范围。
File diff suppressed because one or more lines are too long
@@ -1,384 +0,0 @@
{
"version": 1,
"resources": [
{
"path": "skills/concept.md",
"id": "skills.concept",
"summary": "概念阶段写作规则。",
"category": "skills",
"title": "概念设计分册",
"inject_phases": [
"concept"
]
},
{
"path": "skills/top_design.md",
"id": "skills.top_design",
"summary": "顶层设计阶段写作规则。",
"category": "skills",
"title": "顶层设计分册",
"inject_phases": [
"top_design"
]
},
{
"path": "skills/architecture.md",
"id": "skills.architecture",
"summary": "系统架构阶段写作规则。",
"category": "skills",
"title": "系统架构分册",
"inject_phases": [
"architecture"
]
},
{
"path": "skills/systems.md",
"id": "skills.systems",
"summary": "系统文档阶段写作规则。",
"category": "skills",
"title": "系统文档分册",
"inject_phases": [
"systems"
]
},
{
"path": "skills/tdd.md",
"id": "skills.tdd",
"summary": "技术文档阶段写作规则。",
"category": "skills",
"title": "技术文档分册",
"inject_phases": [
"tdd"
]
},
{
"id": "templates.analysis",
"category": "templates",
"title": "analysis",
"summary": "策划文档结构模板。",
"path": "templates/analysis.md"
},
{
"id": "templates.architecture",
"category": "templates",
"title": "architecture",
"summary": "策划文档结构模板。",
"path": "templates/architecture.md"
},
{
"id": "templates.concept_design",
"category": "templates",
"title": "concept-design",
"summary": "策划文档结构模板。",
"path": "templates/concept-design.md"
},
{
"id": "templates.stardew_analysis",
"category": "templates",
"title": "stardew-analysis",
"summary": "策划文档结构模板。",
"path": "templates/stardew-analysis.md"
},
{
"id": "templates.tdd_art_bible",
"category": "templates",
"title": "tdd-art-bible",
"summary": "策划文档结构模板。",
"path": "templates/tdd-art-bible.md"
},
{
"id": "templates.tdd_data",
"category": "templates",
"title": "tdd-data",
"summary": "策划文档结构模板。",
"path": "templates/tdd-data.md"
},
{
"id": "templates.tdd_master",
"category": "templates",
"title": "tdd-master",
"summary": "策划文档结构模板。",
"path": "templates/tdd-master.md"
},
{
"id": "templates.tdd_tech",
"category": "templates",
"title": "tdd-tech",
"summary": "策划文档结构模板。",
"path": "templates/tdd-tech.md"
},
{
"id": "templates.top_design",
"category": "templates",
"title": "top-design",
"summary": "策划文档结构模板。",
"path": "templates/top-design.md"
},
{
"id": "exemplars.decision_log",
"category": "exemplars",
"title": "decision-log",
"summary": "策划文档范例或需求附件。",
"path": "exemplars/decision-log.md"
},
{
"id": "exemplars.fast_gdd",
"category": "exemplars",
"title": "fast-gdd",
"summary": "策划文档范例或需求附件。",
"path": "exemplars/fast-gdd.md"
},
{
"id": "exemplars.overview_card",
"category": "exemplars",
"title": "overview-card",
"summary": "策划文档范例或需求附件。",
"path": "exemplars/overview-card.md"
},
{
"id": "exemplars.stardew_architecture",
"category": "exemplars",
"title": "stardew-architecture",
"summary": "策划文档范例或需求附件。",
"path": "exemplars/stardew-architecture.md"
},
{
"id": "exemplars.stardew_concept",
"category": "exemplars",
"title": "stardew-concept",
"summary": "策划文档范例或需求附件。",
"path": "exemplars/stardew-concept.md"
},
{
"id": "exemplars.stardew_s06_combat",
"category": "exemplars",
"title": "stardew-s06-combat",
"summary": "策划文档范例或需求附件。",
"path": "exemplars/stardew-s06-combat.md"
},
{
"id": "exemplars.stardew_tdd_art_bible",
"category": "exemplars",
"title": "stardew-tdd-art-bible",
"summary": "策划文档范例或需求附件。",
"path": "exemplars/stardew-tdd-art-bible.md"
},
{
"id": "exemplars.stardew_tdd_data",
"category": "exemplars",
"title": "stardew-tdd-data",
"summary": "策划文档范例或需求附件。",
"path": "exemplars/stardew-tdd-data.md"
},
{
"id": "exemplars.stardew_tdd_master",
"category": "exemplars",
"title": "stardew-tdd-master",
"summary": "策划文档范例或需求附件。",
"path": "exemplars/stardew-tdd-master.md"
},
{
"id": "exemplars.stardew_tdd_tech",
"category": "exemplars",
"title": "stardew-tdd-tech",
"summary": "策划文档范例或需求附件。",
"path": "exemplars/stardew-tdd-tech.md"
},
{
"id": "exemplars.stardew_top_design",
"category": "exemplars",
"title": "stardew-top-design",
"summary": "策划文档范例或需求附件。",
"path": "exemplars/stardew-top-design.md"
},
{
"id": "exemplars.tdd_art_bible_SKILL",
"category": "exemplars",
"title": "tdd-art-bible-SKILL",
"summary": "策划文档范例或需求附件。",
"path": "exemplars/tdd-art-bible-SKILL.md"
},
{
"id": "exemplars.tdd_data_SKILL",
"category": "exemplars",
"title": "tdd-data-SKILL",
"summary": "策划文档范例或需求附件。",
"path": "exemplars/tdd-data-SKILL.md"
},
{
"id": "exemplars.tdd_tech_SKILL",
"category": "exemplars",
"title": "tdd-tech-SKILL",
"summary": "策划文档范例或需求附件。",
"path": "exemplars/tdd-tech-SKILL.md"
},
{
"id": "system_types.核心玩法编排.skill",
"category": "system_types",
"title": "01_核心玩法编排 skill",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/01_核心玩法编排/SKILL.md"
},
{
"id": "system_types.核心玩法编排.template",
"category": "system_types",
"title": "01_核心玩法编排 template",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/01_核心玩法编排/模板.md"
},
{
"id": "system_types.时间与日程.skill",
"category": "system_types",
"title": "02_时间与日程 skill",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/02_时间与日程/SKILL.md"
},
{
"id": "system_types.时间与日程.template",
"category": "system_types",
"title": "02_时间与日程 template",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/02_时间与日程/模板.md"
},
{
"id": "system_types.生产种植经营.skill",
"category": "system_types",
"title": "03_生产种植经营 skill",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/03_生产种植经营/SKILL.md"
},
{
"id": "system_types.生产种植经营.template",
"category": "system_types",
"title": "03_生产种植经营 template",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/03_生产种植经营/模板.md"
},
{
"id": "system_types.地图与探索.skill",
"category": "system_types",
"title": "04_地图与探索 skill",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/04_地图与探索/SKILL.md"
},
{
"id": "system_types.地图与探索.template",
"category": "system_types",
"title": "04_地图与探索 template",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/04_地图与探索/模板.md"
},
{
"id": "system_types.采集与支线活动.skill",
"category": "system_types",
"title": "05_采集与支线活动 skill",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/05_采集与支线活动/SKILL.md"
},
{
"id": "system_types.采集与支线活动.template",
"category": "system_types",
"title": "05_采集与支线活动 template",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/05_采集与支线活动/模板.md"
},
{
"id": "system_types.战斗与敌人.skill",
"category": "system_types",
"title": "06_战斗与敌人 skill",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/06_战斗与敌人/SKILL.md"
},
{
"id": "system_types.战斗与敌人.template",
"category": "system_types",
"title": "06_战斗与敌人 template",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/06_战斗与敌人/模板.md"
},
{
"id": "system_types.物品背包与制作.skill",
"category": "system_types",
"title": "07_物品背包与制作 skill",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/07_物品背包与制作/SKILL.md"
},
{
"id": "system_types.物品背包与制作.template",
"category": "system_types",
"title": "07_物品背包与制作 template",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/07_物品背包与制作/模板.md"
},
{
"id": "system_types.成长与技能.skill",
"category": "system_types",
"title": "08_成长与技能 skill",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/08_成长与技能/SKILL.md"
},
{
"id": "system_types.成长与技能.template",
"category": "system_types",
"title": "08_成长与技能 template",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/08_成长与技能/模板.md"
},
{
"id": "system_types.NPC关系与任务.skill",
"category": "system_types",
"title": "09_NPC关系与任务 skill",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/09_NPC关系与任务/SKILL.md"
},
{
"id": "system_types.NPC关系与任务.template",
"category": "system_types",
"title": "09_NPC关系与任务 template",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/09_NPC关系与任务/模板.md"
},
{
"id": "system_types.经济与商店.skill",
"category": "system_types",
"title": "10_经济与商店 skill",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/10_经济与商店/SKILL.md"
},
{
"id": "system_types.经济与商店.template",
"category": "system_types",
"title": "10_经济与商店 template",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/10_经济与商店/模板.md"
},
{
"id": "system_types.事件与节日.skill",
"category": "system_types",
"title": "11_事件与节日 skill",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/11_事件与节日/SKILL.md"
},
{
"id": "system_types.事件与节日.template",
"category": "system_types",
"title": "11_事件与节日 template",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/11_事件与节日/模板.md"
},
{
"id": "system_types.UI与文本呈现.skill",
"category": "system_types",
"title": "12_UI与文本呈现 skill",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/12_UI与文本呈现/SKILL.md"
},
{
"id": "system_types.UI与文本呈现.template",
"category": "system_types",
"title": "12_UI与文本呈现 template",
"summary": "系统类型写法规则或模板。",
"path": "modules/system-types/12_UI与文本呈现/模板.md"
}
]
}
@@ -1,66 +0,0 @@
# 决策台账:《星露谷物语》金样项目
版本:v3 | 规则:台账放活队列——design 只放结论、分析只放论证、决定与开放问题住这里。编号连续不复用;被推翻的行标 overturned 挂新行,不删行。
状态六态:`confirmed`(用户亲口/亲选)/ `auto_decided`(技术类代决,必带理由+推翻条件,用户一键可翻)/ `default_pending`(默认建议兜底,用户未点头)/ `prototype_pending`(待原型验证)/ `pending_user`(等用户拍板)/ `overturned`(被推翻,挂旧行编号)。
> 编号口径:D-01~D-13 与 exemplars/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) |
## 队列纪律(给 agent 的使用说明)
- 新决定入队:拿下一号(当前最大 D-19,下一号 D-20);就现代决可登记不开条目,但状态必须写 auto_decided 并带理由+推翻条件。
- 用户翻案:旧行标 overturned 挂新行,受影响文档节重写(本台账只记录,不代改)。
- 概念层变更定稿后:速览卡"决定状态与原型验证项"字段随本文件最新版同步(prompt 级纪律)。

Some files were not shown because too many files have changed in this diff Show More