feat(cheap-worker): per-genre 脚手架路由接入 scaffold_template/scaffold_desc(扩模板数据点)
扩模板需让难品类 AI 从预置品类骨架起步、少写。接入(全缺省回落 _template、create 路零改): - tools.mjs scaffoldSaa(gameId, templateName='_template'):clone 指定模板;CLI 加第 3 参;模板缺失 warn 回落。 - cheap_run.scaffold(game_id, template=None):shell-out 透传模板名。 - cheap_studio.run_studio(scaffold_template, scaffold_desc):线程到 prep + build_system_prompt。 - cheap_roles:system prompt 脚手架描述参数化(⟦SCAFFOLD_DESC⟧),让 AI 知道起点已是该品类完整游戏、换皮而非重写。 管线 routing 单测 27/27、prompt 参数化 8/8。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9ddd173c06
commit
47d6394347
@ -25,7 +25,7 @@ _SYSTEM_PROMPT = """你是 A-model 游戏生成 agent。目标产物 = 一款**
|
||||
【这一档的定位(钉死)】AI 参与深度低 ≠ 做简单玩具——「轻量≠简单」。手感、物理、碰撞、粒子、调色这些复杂能力,平台已用工程脚手架 + L2 插件库替你承担,你**少写、多调插件**就能做出卖相与深度;你的活是在脚手架上把 brief 做成一款**真能让人玩进去**的游戏,**不是产个没人会玩的简单玩具**。
|
||||
|
||||
【工作目录已就绪】
|
||||
- 我已把克隆起点 _template 拷到 ⟦G⟧/(一个跑通的脚手架起点「点圆得分」:menu→play→over 骨架 + 五法齐全 + 编排插件示范)。你的活是据 brief **借插件能力把它改造成一款高质游戏**——别把它当"这档就该这么简单"的天花板。
|
||||
- 我已把克隆起点拷到 ⟦G⟧/(⟦SCAFFOLD_DESC⟧)。你的活是据 brief **借插件能力把它改造成一款高质游戏**——别把它当"这档就该这么简单"的天花板。
|
||||
- **L1 固定 plumbing 别动(write 会被拒)**:⟦G⟧/index.html、⟦G⟧/entry-bundle.js、⟦G⟧/src/game.js(薄 wrapper)、**⟦G⟧/src/host-config.js**(host 装配:已替你注入**全 11 件能力插件** + viewport 固定 390×844 + 工厂 wiring;你无需装配任何插件)。
|
||||
- **你只写 L3 游戏本体**:**⟦G⟧/src/game-logic.js(必写,游戏核心)** + 按需 core.js(纯逻辑)/ render.js(画面)/ balance.js(数值)/ assets.js(资产)。结构简单可把逻辑/画面集中在 game-logic.js 内;多系统/多场景再拆 core/render。把脚手架起点改造成 brief 要的**高质游戏**(多借插件能力做卖相与深度);**画面读 viewport.w/h(390×844 竖屏)**。
|
||||
- **入口契约(钉死)**:game-logic.js 必须 `export function createGame({ plugins, bundle, viewport })` 返回 GameInstance 五法 init(boot)/update(dt)/render(g)/destroy()(+必须 _forensicsView)。**插件已扁平注入 plugins**,用到才解构:sceneFsm/sessionScore/hudUi/timerScheduler/save/gamefeel/juice/palettePost/audioMusic/collision/physics。
|
||||
@ -63,17 +63,24 @@ A 读手册(1)+ 读你的起点 game-logic.js(4)→ B 按 brief 定玩
|
||||
现在开始:**先 read_file 读手册,别直接写码;核心玩法实现完、check+build 绿了就立即 finish。**"""
|
||||
|
||||
|
||||
def build_system_prompt(game_id: str) -> str:
|
||||
"""据 game_id 拼出本 run 的 system prompt(把 ⟦G⟧ 占位替换成 repo 根相对的 game 目录)。
|
||||
# 默认脚手架描述(create 路 = 通用 _template「点圆得分」起点)。扩模板:per-genre 黄金骨架传各自描述覆盖,
|
||||
# 让 AI 知道起点已是一款该品类的完整游戏、按 brief 换皮而非重写(与 scaffold_template 配对,见 run_studio)。
|
||||
_DEFAULT_SCAFFOLD_DESC = "一个跑通的脚手架起点「点圆得分」:menu→play→over 骨架 + 五法齐全 + 编排插件示范"
|
||||
|
||||
|
||||
def build_system_prompt(game_id: str, scaffold_desc: str = None) -> str:
|
||||
"""据 game_id 拼出本 run 的 system prompt(把 ⟦SCAFFOLD_DESC⟧/⟦G⟧ 占位替换)。
|
||||
|
||||
Args:
|
||||
game_id: 本 run 的游戏 id(cheap-worker 统一用 cheap-<xxx> 前缀,与 Node 旧路 node-<xxx> 隔离)。
|
||||
scaffold_desc: 起点脚手架的一句话描述;None=默认通用「点圆得分」(create 路不变);
|
||||
扩模板传 per-genre 描述(如经营=「完整经营点客游戏…按 brief 换皮」),让 AI 从品类骨架换皮、少写。
|
||||
|
||||
Returns:
|
||||
薄 system prompt 字符串(指 agent read 真 skill,不手抄)。
|
||||
"""
|
||||
g = f"game-runtime/games/amgen-{game_id}"
|
||||
return _SYSTEM_PROMPT.replace("⟦G⟧", g)
|
||||
return _SYSTEM_PROMPT.replace("⟦SCAFFOLD_DESC⟧", scaffold_desc or _DEFAULT_SCAFFOLD_DESC).replace("⟦G⟧", g)
|
||||
|
||||
|
||||
def _contract_block() -> str:
|
||||
|
||||
@ -153,10 +153,18 @@ def _shell_env() -> dict:
|
||||
return env
|
||||
|
||||
|
||||
def scaffold(game_id: str) -> dict:
|
||||
"""scaffold:shell-out `node tools.mjs scaffold-saa <id>`(clone _template + SAA 信封 __GameBundle)。返回 {ok, output}。"""
|
||||
def scaffold(game_id: str, template: Optional[str] = None) -> dict:
|
||||
"""scaffold:shell-out `node tools.mjs scaffold-saa <id> [template]`(clone 模板 + SAA 信封 __GameBundle)。返回 {ok, output}。
|
||||
|
||||
template=None(默认)→ 通用 _template(点圆得分起点);扩模板:传 per-genre 黄金骨架目录名
|
||||
(经营=_template-shop 等),让难品类 AI 从预置经营循环起步、少写。模板不存在则 node 侧回落 _template。
|
||||
"""
|
||||
try:
|
||||
r = _run_node_tools("scaffold-saa", game_id)
|
||||
argv = ["node", str(_AMODEL_GEN / "tools.mjs"), "scaffold-saa", game_id]
|
||||
if template:
|
||||
argv.append(template)
|
||||
r = subprocess.run(argv, cwd=str(_GAME_RUNTIME), capture_output=True, text=True,
|
||||
timeout=90.0, check=False)
|
||||
return {"ok": r.returncode == 0, "output": (r.stdout + r.stderr).strip()}
|
||||
except Exception as e: # noqa: BLE001
|
||||
return {"ok": False, "output": f"scaffold shell-out 异常:{e}"}
|
||||
|
||||
@ -110,7 +110,8 @@ def build_trace_source(verdict, driver_type, attempts, stage, model) -> dict:
|
||||
|
||||
async def run_studio(game_id, brief, *, max_iters=40, max_resumes=6, max_tokens=16000,
|
||||
port=4320, cdp_port=9222, run_gates=True,
|
||||
system_prompt=None, initial_kick=None, write_whitelist=None, prepare=None):
|
||||
system_prompt=None, initial_kick=None, write_whitelist=None, prepare=None,
|
||||
scaffold_template=None, scaffold_desc=None):
|
||||
"""跑便宜档一局生成(scaffold → ReAct 写 src/ → done 门 → 收口 stage+smoke+九门)。返回 run-summary dict。
|
||||
|
||||
run_gates=True(默认,spike 单局):收口跑全套 stage→smoke→ensure_play_spec→九门 play。
|
||||
@ -129,7 +130,8 @@ async def run_studio(game_id, brief, *, max_iters=40, max_resumes=6, max_tokens=
|
||||
t0 = time.time()
|
||||
_rec(f"model={_bootstrap.SPIKE_MODEL} id={game_id} brief=「{brief}」max_iters={max_iters} max_resumes={max_resumes}")
|
||||
|
||||
prep = prepare or cheap_run.scaffold # create=scaffold clone _template;modify=上游已预备的 noop
|
||||
# create=scaffold clone 模板(扩模板:scaffold_template 传 per-genre 黄金骨架名,如经营=_template-shop);modify=上游已预备的 noop
|
||||
prep = prepare or (lambda gid: cheap_run.scaffold(gid, scaffold_template))
|
||||
sc = prep(game_id)
|
||||
if not sc["ok"]:
|
||||
return {"ok": False, "gameId": game_id, "fail": "准备失败:" + sc["output"]}
|
||||
@ -146,7 +148,7 @@ async def run_studio(game_id, brief, *, max_iters=40, max_resumes=6, max_tokens=
|
||||
|
||||
writer = Agent(
|
||||
name="cheap-writer",
|
||||
system_prompt=system_prompt or build_system_prompt(game_id), # modify 传 build_modify_system_prompt
|
||||
system_prompt=system_prompt or build_system_prompt(game_id, scaffold_desc), # modify 传 build_modify_system_prompt;扩模板传 scaffold_desc
|
||||
model=model,
|
||||
toolkit=toolkit,
|
||||
middlewares=[tracer, breaker], # trace 外层、熔断内层(列表序=洋葱序)
|
||||
|
||||
@ -378,16 +378,25 @@ export function saaGameDir(gameId) {
|
||||
}
|
||||
|
||||
/**
|
||||
* scaffoldSaa:clone _template → amgen-<gameId>/,换上 A-model SAA 信封 plumbing(治 review B2)。
|
||||
* scaffoldSaa:clone 模板 → amgen-<gameId>/,换上 A-model SAA 信封 plumbing(治 review B2)。
|
||||
* - entry-bundle.js = entry-bundle.amodel.template.js(导出 bootGameHost,经 --global-name=__GameBundle 挂 window.__GameBundle)
|
||||
* - index.html = _wg1-gen/_shared/index.template.html(调 __GameBundle.bootGameHost,置 __genBooted)
|
||||
* - 去 spike 入口(entry.js/src/main.js;SAA 不用)+ dist/test
|
||||
* agent 仍只写 src/{game,core,render,host-config,balance,assets}.js(与 spike 同;信封差异全在 plumbing)。
|
||||
*
|
||||
* @param {string} gameId 本 run 游戏 id。
|
||||
* @param {string} [templateName='_template'] 起始模板目录名(扩模板:经营=_template-shop 等 per-genre 黄金骨架;
|
||||
* 缺省 _template=通用点圆得分起点)。模板不存在则回落 _template(不静默失败,打 warn 可查)。
|
||||
*/
|
||||
export function scaffoldSaa(gameId) {
|
||||
export function scaffoldSaa(gameId, templateName = '_template') {
|
||||
const gd = gameDir(gameId);
|
||||
let tmplDir = join(GAMES_DIR, templateName || '_template');
|
||||
if (!existsSync(tmplDir)) {
|
||||
console.warn(`[scaffold-saa] 模板 ${templateName} 不存在,回落 _template`);
|
||||
tmplDir = TEMPLATE_DIR;
|
||||
}
|
||||
if (existsSync(gd)) rmSync(gd, { recursive: true, force: true });
|
||||
cpSync(TEMPLATE_DIR, gd, { recursive: true });
|
||||
cpSync(tmplDir, gd, { recursive: true });
|
||||
rmSync(join(gd, 'dist'), { recursive: true, force: true });
|
||||
rmSync(join(gd, 'test'), { recursive: true, force: true });
|
||||
// 换 SAA 信封 plumbing(覆盖 _template 的 spike 信封 entry.js/index.html)
|
||||
@ -482,9 +491,9 @@ export function ensurePlaySpec(gameId, state) {
|
||||
// ───────────────────────── CLI(独立验证用)─────────────────────────
|
||||
// 用法:node tools.mjs scaffold <id> | check <id> | build <id>
|
||||
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
const [, , cmd, id] = process.argv;
|
||||
const [, , cmd, id, arg3] = process.argv;
|
||||
if (!cmd || !id) {
|
||||
console.error('用法:node tools.mjs <scaffold|check|build|scaffold-saa|build-saa|stage> <id>');
|
||||
console.error('用法:node tools.mjs <scaffold|check|build|scaffold-saa|build-saa|stage> <id> [templateName]');
|
||||
process.exit(2);
|
||||
}
|
||||
if (cmd === 'scaffold') {
|
||||
@ -500,8 +509,8 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur
|
||||
console.log('[build]', r.ok ? 'PASS' : 'FAIL');
|
||||
if (!r.ok) { console.log(r.error); process.exit(1); }
|
||||
} else if (cmd === 'scaffold-saa') {
|
||||
const gd = scaffoldSaa(id);
|
||||
console.log('[scaffold-saa] →', relative(REPO_ROOT, gd), '(SAA 信封 plumbing)');
|
||||
const gd = scaffoldSaa(id, arg3);
|
||||
console.log('[scaffold-saa] →', relative(REPO_ROOT, gd), `(SAA 信封 plumbing, 模板=${arg3 || '_template'})`);
|
||||
} else if (cmd === 'build-saa') {
|
||||
const r = buildSaa(id);
|
||||
console.log('[build-saa]', r.ok ? 'PASS' : 'FAIL');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user