@playcraft/cli 0.0.55 → 0.0.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +21 -0
  2. package/REMIX.md +103 -19
  3. package/dist/atom-tree/fusion-hints.js +8 -3
  4. package/dist/atom-tree/fusion-intent.js +62 -0
  5. package/dist/atom-tree/selection.js +3 -1
  6. package/dist/commands/create.js +299 -85
  7. package/dist/commands/image.js +79 -69
  8. package/dist/commands/levels.js +29 -0
  9. package/dist/commands/patterns.js +75 -0
  10. package/dist/commands/remix.js +47 -0
  11. package/dist/commands/skills.js +48 -17
  12. package/dist/index.js +9 -5
  13. package/dist/remix/clone-source.js +208 -0
  14. package/dist/remix/clone.js +255 -0
  15. package/dist/scaffold/templates.js +1 -1
  16. package/dist/utils/agent-api-client.js +10 -3
  17. package/dist/utils/remix-image-attachments.js +4 -32
  18. package/dist/utils/sandbox-path.js +20 -0
  19. package/package.json +3 -3
  20. package/project-template/.claude/agents/creative-director.md +114 -0
  21. package/project-template/.claude/agents/developer.md +5 -2
  22. package/project-template/.claude/agents/game-designer.md +41 -20
  23. package/project-template/.claude/agents/refs/README.md +8 -0
  24. package/project-template/.claude/agents/refs/creative-director-guide.md +35 -0
  25. package/project-template/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
  26. package/project-template/.claude/agents/refs/creative-discovery-guide.md +76 -0
  27. package/project-template/.claude/agents/refs/creative-north-star-examples.md +82 -0
  28. package/project-template/.claude/agents/refs/developer-dev-handoff.md +1 -1
  29. package/project-template/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
  30. package/project-template/.claude/agents/refs/game-designer-gameplay-sufficiency.md +4 -4
  31. package/project-template/.claude/agents/reviewer.md +11 -0
  32. package/project-template/.claude/hooks/README.md +12 -12
  33. package/project-template/.claude/hooks/block-developer-preflight.mjs +49 -13
  34. package/project-template/.claude/hooks/block-ring1-tasklist.mjs +1 -1
  35. package/project-template/.claude/hooks/user-prompt.mjs +3 -13
  36. package/project-template/.claude/hooks/validate-workflow-stop.mjs +209 -12
  37. package/project-template/.claude/settings.json +2 -2
  38. package/project-template/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
  39. package/project-template/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -1
  40. package/project-template/.claude/snapshot.mjs +0 -1
  41. package/project-template/.codebuddy/settings.json +1 -1
  42. package/project-template/.cursor/rules/playcraft-orchestrator.mdc +1 -1
  43. package/project-template/CLAUDE.md +9 -9
  44. package/project-template/docs/project-state.md +1 -1
  45. package/project-template/docs/team/agent-conduct.md +18 -18
  46. package/project-template/docs/team/collaboration.md +19 -7
  47. package/project-template/docs/team/workflow-changelog.md +15 -0
  48. package/project-template/fusion-patterns/catalog.json +77 -0
  49. package/project-template/package.json +2 -0
  50. package/project-template/skills/_shared/scripts/create-playable.mjs +31 -11
  51. package/project-template/skills/_shared/scripts/dev-start.mjs +3 -3
  52. package/project-template/skills/_shared/scripts/disclosure-config.mjs +65 -39
  53. package/project-template/skills/_shared/scripts/harness-smoke.mjs +3 -1
  54. package/project-template/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
  55. package/project-template/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
  56. package/project-template/skills/_shared/scripts/slice-set.mjs +29 -2
  57. package/project-template/skills/_shared/scripts/state-get.mjs +124 -7
  58. package/project-template/skills/_shared/scripts/state-store.mjs +25 -2
  59. package/project-template/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
  60. package/project-template/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
  61. package/project-template/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
  62. package/project-template/skills/playable-record/scripts/record-playable.mjs +2 -1
  63. package/project-template/skills/playable-record/scripts/verify-contract.mjs +4 -4
  64. package/project-template/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
  65. package/project-template/skills/playable-record/scripts/verify-fusion.mjs +37 -2
  66. package/project-template/skills/playable-record/scripts/verify-slice.mjs +2 -2
  67. package/project-template/templates/design-brief.template.md +34 -0
  68. package/project-template/templates/design-methodology.md +1 -2
  69. package/project-template/templates/design.template.md +65 -0
  70. package/project-template/templates/developer-log.template.md +12 -1
  71. package/project-template/templates/five-axis-framework.md +12 -0
  72. package/project-template/templates/game-designer-creative-process.template.md +99 -0
  73. package/project-template/templates/level-sketch.template.json +56 -0
  74. package/project-template/templates/north-star.template.md +43 -0
  75. package/project-template/templates/project-state.template.md +1 -1
  76. package/project-template/vite.config.ts +4 -5
  77. package/project-templates/phaser/.claude/agents/creative-director.md +114 -0
  78. package/project-templates/phaser/.claude/agents/game-designer.md +41 -20
  79. package/project-templates/phaser/.claude/agents/refs/creative-director-guide.md +35 -0
  80. package/project-templates/phaser/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
  81. package/project-templates/phaser/.claude/agents/refs/creative-discovery-guide.md +76 -0
  82. package/project-templates/phaser/.claude/agents/refs/creative-north-star-examples.md +82 -0
  83. package/project-templates/phaser/.claude/agents/refs/developer-dev-handoff.md +1 -1
  84. package/project-templates/phaser/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
  85. package/project-templates/phaser/.claude/agents/reviewer.md +11 -0
  86. package/project-templates/phaser/.claude/hooks/block-ring1-tasklist.mjs +2 -2
  87. package/project-templates/phaser/.claude/hooks/user-prompt.mjs +17 -18
  88. package/project-templates/phaser/.claude/hooks/validate-workflow-stop.mjs +209 -12
  89. package/project-templates/phaser/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
  90. package/project-templates/phaser/.cursor/rules/playcraft-orchestrator.mdc +1 -1
  91. package/project-templates/phaser/CLAUDE.md +88 -54
  92. package/project-templates/phaser/docs/DESIGN.md +25 -0
  93. package/project-templates/phaser/docs/project-state.md +1 -1
  94. package/project-templates/phaser/docs/team/collaboration.md +19 -7
  95. package/project-templates/phaser/docs/team/workflow-changelog.md +14 -0
  96. package/project-templates/phaser/fusion-patterns/catalog.json +77 -0
  97. package/project-templates/phaser/package.json +3 -1
  98. package/project-templates/phaser/skills/_shared/scripts/dev-start.mjs +3 -3
  99. package/project-templates/phaser/skills/_shared/scripts/disclosure-config.mjs +65 -39
  100. package/project-templates/phaser/skills/_shared/scripts/harness-smoke.mjs +3 -1
  101. package/project-templates/phaser/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
  102. package/project-templates/phaser/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
  103. package/project-templates/phaser/skills/_shared/scripts/state-get.mjs +124 -7
  104. package/project-templates/phaser/skills/_shared/scripts/state-store.mjs +25 -2
  105. package/project-templates/phaser/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
  106. package/project-templates/phaser/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
  107. package/project-templates/phaser/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
  108. package/project-templates/phaser/skills/playable-record/scripts/record-playable.mjs +2 -1
  109. package/project-templates/phaser/skills/playable-record/scripts/verify-contract.mjs +4 -4
  110. package/project-templates/phaser/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
  111. package/project-templates/phaser/skills/playable-record/scripts/verify-fusion.mjs +37 -2
  112. package/project-templates/phaser/skills/playable-record/scripts/verify-slice.mjs +2 -2
  113. package/project-templates/phaser/templates/design-brief.template.md +42 -11
  114. package/project-templates/phaser/templates/design-methodology.md +1 -2
  115. package/project-templates/phaser/templates/design.template.md +77 -0
  116. package/project-templates/phaser/templates/five-axis-framework.md +50 -1
  117. package/project-templates/phaser/templates/game-designer-creative-process.template.md +99 -0
  118. package/project-templates/phaser/templates/level-sketch.template.json +56 -0
  119. package/project-templates/phaser/templates/north-star.template.md +43 -0
  120. package/project-templates/phaser/templates/project-state.template.md +1 -1
  121. package/project-templates/phaser/vite.config.ts +4 -5
  122. package/project-templates/threejs/.claude/agents/creative-director.md +114 -0
  123. package/project-templates/threejs/.claude/agents/game-designer.md +41 -20
  124. package/project-templates/threejs/.claude/agents/refs/creative-director-guide.md +35 -0
  125. package/project-templates/threejs/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
  126. package/project-templates/threejs/.claude/agents/refs/creative-discovery-guide.md +76 -0
  127. package/project-templates/threejs/.claude/agents/refs/creative-north-star-examples.md +82 -0
  128. package/project-templates/threejs/.claude/agents/refs/developer-dev-handoff.md +1 -1
  129. package/project-templates/threejs/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
  130. package/project-templates/threejs/.claude/agents/reviewer.md +11 -0
  131. package/project-templates/threejs/.claude/hooks/block-ring1-tasklist.mjs +2 -2
  132. package/project-templates/threejs/.claude/hooks/user-prompt.mjs +17 -18
  133. package/project-templates/threejs/.claude/hooks/validate-workflow-stop.mjs +209 -12
  134. package/project-templates/threejs/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
  135. package/project-templates/threejs/.cursor/rules/playcraft-orchestrator.mdc +1 -1
  136. package/project-templates/threejs/CLAUDE.md +88 -54
  137. package/project-templates/threejs/docs/DESIGN.md +25 -0
  138. package/project-templates/threejs/docs/project-state.md +1 -1
  139. package/project-templates/threejs/docs/team/collaboration.md +19 -7
  140. package/project-templates/threejs/docs/team/workflow-changelog.md +14 -0
  141. package/project-templates/threejs/fusion-patterns/catalog.json +77 -0
  142. package/project-templates/threejs/package.json +3 -1
  143. package/project-templates/threejs/skills/_shared/scripts/dev-start.mjs +3 -3
  144. package/project-templates/threejs/skills/_shared/scripts/disclosure-config.mjs +65 -39
  145. package/project-templates/threejs/skills/_shared/scripts/harness-smoke.mjs +3 -1
  146. package/project-templates/threejs/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
  147. package/project-templates/threejs/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
  148. package/project-templates/threejs/skills/_shared/scripts/state-get.mjs +124 -7
  149. package/project-templates/threejs/skills/_shared/scripts/state-store.mjs +25 -2
  150. package/project-templates/threejs/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
  151. package/project-templates/threejs/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
  152. package/project-templates/threejs/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
  153. package/project-templates/threejs/skills/playable-record/scripts/record-playable.mjs +2 -1
  154. package/project-templates/threejs/skills/playable-record/scripts/verify-contract.mjs +4 -4
  155. package/project-templates/threejs/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
  156. package/project-templates/threejs/skills/playable-record/scripts/verify-fusion.mjs +37 -2
  157. package/project-templates/threejs/skills/playable-record/scripts/verify-slice.mjs +2 -2
  158. package/project-templates/threejs/templates/design-brief.template.md +42 -11
  159. package/project-templates/threejs/templates/design-methodology.md +1 -2
  160. package/project-templates/threejs/templates/design.template.md +77 -0
  161. package/project-templates/threejs/templates/five-axis-framework.md +50 -1
  162. package/project-templates/threejs/templates/game-designer-creative-process.template.md +99 -0
  163. package/project-templates/threejs/templates/level-sketch.template.json +56 -0
  164. package/project-templates/threejs/templates/north-star.template.md +43 -0
  165. package/project-templates/threejs/templates/project-state.template.md +1 -1
  166. package/project-templates/threejs/vite.config.ts +4 -5
  167. package/project-template/docs/DESIGN.md +0 -65
@@ -5,36 +5,61 @@
5
5
  * real listening URL. Previously these were copy-pasted into verify-contract,
6
6
  * verify-firstwin and record-playable, which let the port-detection logic drift.
7
7
  *
8
- * Vite runs with strictPort:false, so when the preferred port (3000) is taken it
8
+ * Vite runs with strictPort:false, so when the preferred port (5173) is taken it
9
9
  * auto-increments. `detectDevUrl` parses Vite's `Local:` stdout line to recover
10
10
  * the actual port instead of guessing — this is what keeps devUrl correct under
11
11
  * port drift (see docs/harness/experiment-lessons.md §9.2).
12
+ * Do not use 6000: Chromium treats it as an unsafe port (ERR_UNSAFE_PORT).
12
13
  */
13
14
 
14
15
  import { spawn } from 'node:child_process';
16
+ import http from 'node:http';
17
+
18
+ /** Preferred Vite dev port — avoids PlayCraft frontend (:3000) and backend (:3001). */
19
+ export const DEFAULT_DEV_PORT = 5173;
20
+ export const DEFAULT_DEV_URL = `http://127.0.0.1:${DEFAULT_DEV_PORT}/`;
15
21
 
16
22
  const LOCAL_URL_RE = /Local:\s+(http:\/\/(?:localhost|127\.0\.0\.1):\d+\/)/;
23
+ const ANSI_RE = /\x1b\[[0-9;]*m/g;
24
+
25
+ /** Probe a URL with node:http (undici `fetch` can fail on 127.0.0.1 in some runtimes). */
26
+ function probeHttp(url) {
27
+ return new Promise((resolve, reject) => {
28
+ const req = http.get(url, (res) => {
29
+ res.resume();
30
+ resolve(res.statusCode ?? 0);
31
+ });
32
+ req.setTimeout(2000, () => {
33
+ req.destroy();
34
+ reject(new Error('probe timeout'));
35
+ });
36
+ req.on('error', reject);
37
+ });
38
+ }
17
39
 
18
- /** Poll a URL with HEAD until it responds or the timeout elapses. */
40
+ /** Poll a URL until any non-5xx response arrives. */
19
41
  export async function waitForHttp(url, timeoutMs = 30000) {
20
42
  const start = Date.now();
43
+ let lastErr = '';
21
44
  while (Date.now() - start < timeoutMs) {
22
45
  try {
23
- const res = await fetch(url, { method: 'HEAD' });
24
- if (res.ok) return;
25
- } catch {
26
- /* retry */
46
+ const status = await probeHttp(url);
47
+ if (status < 500) return;
48
+ lastErr = `HTTP ${status}`;
49
+ } catch (err) {
50
+ lastErr = err instanceof Error ? err.message : String(err);
27
51
  }
28
52
  await new Promise((r) => setTimeout(r, 250));
29
53
  }
30
- throw new Error(`Server not ready: ${url}`);
54
+ throw new Error(`Server not ready: ${url}${lastErr ? ` (${lastErr})` : ''}`);
31
55
  }
32
56
 
33
57
  /** Spawn `npm run dev` (Vite) bound to 127.0.0.1 with piped stdio for URL detection. */
34
58
  export function startDevServer(cwd) {
35
- return spawn('npm', ['run', 'dev', '--', '--host', '127.0.0.1'], {
59
+ return spawn('npm', ['run', 'dev', '--', '--host', '127.0.0.1', '--port', String(DEFAULT_DEV_PORT), '--no-open'], {
36
60
  cwd,
37
61
  stdio: ['ignore', 'pipe', 'pipe'],
62
+ env: { ...process.env, BROWSER: 'none' },
38
63
  });
39
64
  }
40
65
 
@@ -53,7 +78,7 @@ export function startHttpServer(port, cwd) {
53
78
  * @returns {string | null}
54
79
  */
55
80
  export function parseLocalUrl(text) {
56
- const m = String(text).match(LOCAL_URL_RE);
81
+ const m = String(text).replace(ANSI_RE, '').match(LOCAL_URL_RE);
57
82
  return m ? m[1] : null;
58
83
  }
59
84
 
@@ -66,25 +91,35 @@ export function parseLocalUrl(text) {
66
91
  * @returns {Promise<string>}
67
92
  */
68
93
  export function detectDevUrl(child, preferredUrl, timeoutMs = 30000) {
69
- return new Promise((res) => {
94
+ return new Promise((res, rej) => {
95
+ let buf = '';
70
96
  const timer = setTimeout(() => {
71
97
  cleanup();
72
98
  res(preferredUrl);
73
99
  }, timeoutMs);
74
100
  const onData = (data) => {
75
- const url = parseLocalUrl(data);
101
+ buf += String(data);
102
+ const url = parseLocalUrl(buf);
76
103
  if (url) {
77
104
  clearTimeout(timer);
78
105
  cleanup();
79
106
  res(url);
80
107
  }
81
108
  };
109
+ const onExit = (code) => {
110
+ clearTimeout(timer);
111
+ cleanup();
112
+ const tail = buf.trim().slice(-1500);
113
+ rej(new Error(`dev server exited ${code} before publishing a Local URL${tail ? `\n${tail}` : ''}`));
114
+ };
82
115
  const cleanup = () => {
83
116
  child.stdout?.off('data', onData);
84
117
  child.stderr?.off('data', onData);
118
+ child.off('exit', onExit);
85
119
  };
86
120
  child.stdout?.on('data', onData);
87
121
  child.stderr?.on('data', onData);
122
+ child.once('exit', onExit);
88
123
  });
89
124
  }
90
125
 
@@ -11,6 +11,7 @@ import { chromium } from 'playwright';
11
11
  import { evaluateContract, snapshotContractInPage } from './verify-contract.mjs';
12
12
  import {
13
13
  detectDevUrl,
14
+ DEFAULT_DEV_URL,
14
15
  pickServerPort,
15
16
  startDevServer,
16
17
  startHttpServer,
@@ -314,7 +315,7 @@ async function main() {
314
315
  externalServer = true;
315
316
  } else if (opts.useDev) {
316
317
  server = startDevServer(projectRoot);
317
- const preferredUrl = opts.url || 'http://127.0.0.1:3000/';
318
+ const preferredUrl = opts.url || DEFAULT_DEV_URL;
318
319
  console.log(`[record] Starting dev server, waiting for ${preferredUrl}`);
319
320
  const detectedUrl = await detectDevUrl(server, preferredUrl, 30000);
320
321
  opts.url = detectedUrl;
@@ -19,14 +19,14 @@
19
19
  * Exit 0 = contract satisfied, 2 = contract broken, 1 = unexpected error.
20
20
  *
21
21
  * Usage:
22
- * node skills/playable-record/scripts/verify-contract.mjs --root=. --use-dev --url=http://localhost:3000/
22
+ * node skills/playable-record/scripts/verify-contract.mjs --root=. --use-dev --url=http://localhost:5173/
23
23
  * node skills/playable-record/scripts/verify-contract.mjs --root=. # standalone http-server
24
- * node skills/playable-record/scripts/verify-contract.mjs --url=http://localhost:3000/ --json
24
+ * node skills/playable-record/scripts/verify-contract.mjs --url=http://localhost:5173/ --json
25
25
  */
26
26
 
27
27
  import { resolve } from 'node:path';
28
28
  import { fileURLToPath } from 'node:url';
29
- import { detectDevUrl, startDevServer, startHttpServer, waitForHttp } from './lib/dev-server.mjs';
29
+ import { detectDevUrl, startDevServer, startHttpServer, waitForHttp, DEFAULT_DEV_URL } from './lib/dev-server.mjs';
30
30
  import { writeValidatorArtifact } from '../../_shared/scripts/lib/validator-artifacts.mjs';
31
31
 
32
32
  const DEFAULT_SCENE = 'MainScene';
@@ -178,7 +178,7 @@ export async function runContractProbe(opts) {
178
178
  external = true;
179
179
  } else if (opts.useDev) {
180
180
  server = startDevServer(projectRoot);
181
- url = await detectDevUrl(server, opts.url || 'http://127.0.0.1:3000/', 30000);
181
+ url = await detectDevUrl(server, opts.url || DEFAULT_DEV_URL, 30000);
182
182
  } else {
183
183
  server = startHttpServer(opts.port, projectRoot);
184
184
  url = `http://127.0.0.1:${opts.port}/game/index.html`;
@@ -6,7 +6,7 @@
6
6
  * polls getState() until isGameOver && currentPhase === 'ENDCARD', then exits 0.
7
7
  *
8
8
  * Usage:
9
- * node skills/playable-record/scripts/verify-firstwin.mjs --root=. --use-dev --url=http://localhost:3000/
9
+ * node skills/playable-record/scripts/verify-firstwin.mjs --root=. --use-dev --url=http://localhost:5173/
10
10
  * npm run verify:firstwin
11
11
  *
12
12
  * Exit 0 = first level winnable (ENDCARD reached)
@@ -18,7 +18,7 @@ import { readdir, readFile } from 'node:fs/promises';
18
18
  import { resolve } from 'node:path';
19
19
  import { fileURLToPath } from 'node:url';
20
20
  import { evaluateContract, snapshotContractInPage } from './verify-contract.mjs';
21
- import { detectDevUrl, startDevServer, startHttpServer, waitForHttp } from './lib/dev-server.mjs';
21
+ import { detectDevUrl, startDevServer, startHttpServer, waitForHttp, DEFAULT_DEV_URL } from './lib/dev-server.mjs';
22
22
  import { writeValidatorArtifact } from '../../_shared/scripts/lib/validator-artifacts.mjs';
23
23
 
24
24
  const DEFAULT_SCENE = 'MainScene';
@@ -198,7 +198,7 @@ export async function runFirstWinProbe(opts) {
198
198
  /* external server */
199
199
  } else if (opts.useDev) {
200
200
  server = startDevServer(projectRoot);
201
- url = await detectDevUrl(server, opts.url || 'http://127.0.0.1:3000/', 30000);
201
+ url = await detectDevUrl(server, opts.url || DEFAULT_DEV_URL, 30000);
202
202
  } else {
203
203
  server = startHttpServer(opts.port, projectRoot);
204
204
  url = `http://127.0.0.1:${opts.port}/game/index.html`;
@@ -19,8 +19,14 @@ import { readFile, readdir } from 'node:fs/promises';
19
19
  import { existsSync } from 'node:fs';
20
20
  import { join, resolve, basename } from 'node:path';
21
21
  import { writeValidatorArtifact } from '../../_shared/scripts/lib/validator-artifacts.mjs';
22
+ import {
23
+ detectFusionIntent,
24
+ readFusionIntentFromProject,
25
+ } from '../../_shared/scripts/lib/fusion-intent.mjs';
22
26
 
23
27
  // `[*_\s]*` tolerates markdown emphasis in the §6 template form `- **Fusion type**:`.
28
+ const FUSION_COMPOSITE_ATOMS = new Set(['arrow_pick_match_rules.aigameplay']);
29
+
24
30
  const FUSION_TYPE_RE =
25
31
  /Fusion\s+type[*_\s]*:\s*(Single|A-Visual|B1-Passive|B2-Active|C-Sequential|D-Parallel)/i;
26
32
  const REAL_FUSION_TYPES = new Set(['A-Visual', 'B1-Passive', 'B2-Active', 'C-Sequential', 'D-Parallel']);
@@ -56,6 +62,19 @@ export const GAMEPLAY_SIGNATURES = {
56
62
  'escape_core',
57
63
  'canMoveArrow',
58
64
  ],
65
+ 'path_elimination_rules.aigameplay': [
66
+ 'path_elimination',
67
+ 'canPathExitBoard',
68
+ 'removedPaths',
69
+ 'findFirstRemovablePath',
70
+ ],
71
+ 'arrow_pick_match_rules.aigameplay': [
72
+ 'arrow_pick_match',
73
+ 'slideOutToTray',
74
+ 'trayCapacity',
75
+ 'LevelValidator',
76
+ 'pick_and_match',
77
+ ],
59
78
  };
60
79
 
61
80
  function parseArgs(argv) {
@@ -212,7 +231,23 @@ export async function verifyFusion(root, overrides = {}) {
212
231
  gameplayAtoms = readSelectedGameplayAtoms(tree);
213
232
  }
214
233
 
215
- if (gameplayAtoms.length < 2) {
234
+ const singleCompositeFusion =
235
+ gameplayAtoms.length === 1 && FUSION_COMPOSITE_ATOMS.has(gameplayAtoms[0] ?? '');
236
+
237
+ if (gameplayAtoms.length < 2 && !singleCompositeFusion) {
238
+ const { intentTags, rawIntent } = readFusionIntentFromProject(root);
239
+ if (detectFusionIntent(intentTags, rawIntent)) {
240
+ const selected = gameplayAtoms[0] ?? '(none)';
241
+ return {
242
+ ok: false,
243
+ skipped: false,
244
+ problems: [
245
+ `fusion intent detected but only one gameplay atom selected (${selected}) — pick a fusion skill (e.g. arrow_pick_match_rules.aigameplay) or document Single + downgrade reason in DESIGN.md ## 6`,
246
+ ],
247
+ gameplayAtoms,
248
+ fusionIntent: true,
249
+ };
250
+ }
216
251
  return { ok: true, skipped: true, reason: 'single gameplay atom — fusion no-op PASS' };
217
252
  }
218
253
 
@@ -224,7 +259,7 @@ export async function verifyFusion(root, overrides = {}) {
224
259
 
225
260
  if (!type) {
226
261
  problems.push('DESIGN.md missing Fusion type: — classify fusion in ## 6. Gameplay Fusion');
227
- } else if (type === 'Single' && !isPseudoFusion) {
262
+ } else if (type === 'Single' && !isPseudoFusion && !singleCompositeFusion) {
228
263
  problems.push(
229
264
  `DESIGN.md Fusion type is Single but atom-tree selected ${gameplayAtoms.length} gameplay atoms — use a real fusion type or document pseudo-fusion`,
230
265
  );
@@ -21,7 +21,7 @@
21
21
  import fs from 'node:fs';
22
22
  import path from 'node:path';
23
23
  import { fileURLToPath } from 'node:url';
24
- import { detectDevUrl, startDevServer, startHttpServer, waitForHttp } from './lib/dev-server.mjs';
24
+ import { detectDevUrl, startDevServer, startHttpServer, waitForHttp, DEFAULT_DEV_URL } from './lib/dev-server.mjs';
25
25
  import { getActiveSlice, readState } from '../../_shared/scripts/state-store.mjs';
26
26
 
27
27
  function parseArgs(argv) {
@@ -57,7 +57,7 @@ export async function runSliceProbe(opts) {
57
57
  /* external server */
58
58
  } else if (opts.useDev) {
59
59
  server = startDevServer(root);
60
- url = await detectDevUrl(server, opts.url || 'http://127.0.0.1:3000/', 30000);
60
+ url = await detectDevUrl(server, opts.url || DEFAULT_DEV_URL, 30000);
61
61
  } else {
62
62
  server = startHttpServer(opts.port, root);
63
63
  url = `http://127.0.0.1:${opts.port}/game/index.html`;
@@ -56,6 +56,40 @@ Append-only evidence (newest first or chronological — pick one and stay consis
56
56
 
57
57
  ## Brainstorm Log
58
58
 
59
+ ### Creative Angle
60
+
61
+ - **Idea hook**: {If only one core moment survives — from Phase 1 candidates}
62
+ - **Emotional payoff**: {What the player should feel after the first win}
63
+ - **Anti-generic**: {Why this is not "ordinary arrow + tray match-3"}
64
+
65
+ ### Pattern picked
66
+
67
+ - Pattern: {id from `playcraft patterns match`} [{SOURCE}]
68
+ - Why this over alternatives: {one line}
69
+
70
+ ### Reference insight
71
+
72
+ - {3–5 bullets from patterns match or `playcraft tools research --focus gameplay`} [{WEB|SKILL}]
73
+ - researchSkippedReason: {only if no research/patterns — explain why}
74
+
75
+ ### Motion moments (Ring 1)
76
+
77
+ - First tap: {slide_out visual — what moves, how long}
78
+ - First blocked: {blocked shake/tint — teach dependency}
79
+ - First match-3: {match_clear in tray — payoff moment}
80
+ - Rejected motion: {why not swap/gravity cascade / board match3 — e.g. "主手势是 path tap,三消仅在托盘"}
81
+
82
+ ### Mechanic variation
83
+
84
+ - {One axis beyond stitching two skills: e.g. tray pressure curve / blocked dependency layers / board phase unlock}
85
+ - rejectedShallowFusion: {why not all-free opening or ≤4 paths on 8×8}
86
+
87
+ ### Gameplay Lens (3 questions)
88
+
89
+ 1. Core action in one verb: {…}
90
+ 2. After 3–5 steps the player gets: {…}
91
+ 3. With sound off for 10s, gameplay guessable? {yes/no + why}
92
+
59
93
  ### Round 1
60
94
 
61
95
  - Asked: {question and why it mattered for core action / feedback}
@@ -1,7 +1,6 @@
1
1
  # Design Methodology Foundations
2
2
 
3
- > **When to read**: Game Designer reads this before Step 4 (Five-Axis Evaluation) to ground decisions in theory.
4
- > Artist and Reviewer may also reference this for context on emotional intent.
3
+ > **When to read**: `@creative-director` and `@game-designer` during Ring 1 creative/spec (via `npm run state:get` → `referenceReads`). Ground Narrative Level and Emotional Arc before writing north-star or DESIGN §0.
5
4
 
6
5
  ---
7
6
 
@@ -13,6 +13,32 @@
13
13
 
14
14
  ---
15
15
 
16
+ ## 0. Creative North Star
17
+
18
+ > Ring 1 reader entry. Write this **before** mechanics tables. Copy from `docs/north-star.md`: **One-liner** verbatim; **Micro-Story Spine** field-value match (trim per field).
19
+
20
+ ### Micro-Story Spine
21
+
22
+ **Narrative level**: {L0 | L1 | L2}
23
+ **Who**: {from north-star}
24
+ **Situation**: {from north-star}
25
+ **Stakes**: {from north-star}
26
+ **Payoff image**: {from north-star}
27
+ **Emotional arc**: {from north-star}
28
+
29
+ **One-liner**: {One vivid sentence — what feeling/tension the player is playing with, not a mechanic checklist.}
30
+
31
+ **System Dynamic**: {One sentence — what system tension keeps the player acting.}
32
+
33
+ **Why this fusion**: {2–3 sentences — how arrow / elimination / tray **serve** the idea above, not "because the user asked for fusion". Start with: "This fusion serves §0 because…"}
34
+
35
+ **Rejected vibe**:
36
+
37
+ - {Not this — e.g. not a casual match-3 binge}
38
+ - {Not this — e.g. not a pure arrow logic puzzle with no payoff}
39
+
40
+ ---
41
+
16
42
  ## Player Action
17
43
 
18
44
  {What the player can do first: tap / drag / aim / choose. Name the visible target and why the action is obvious.}
@@ -48,3 +74,42 @@
48
74
  ## Open Assumptions
49
75
 
50
76
  {Only list assumptions that materially affect Ring 1. Use "none" if clear.}
77
+
78
+ ---
79
+
80
+ ## 6. Gameplay Fusion
81
+
82
+ Fusion type: {Single | A-Visual | B1-Passive | B2-Active | C-Sequential | D-Parallel}
83
+
84
+ This fusion serves §0 because {link back to One-liner / System Dynamic keywords}.
85
+
86
+ {One paragraph: which parent mechanics combine, interaction contract (not just skill names), and why this fusion is playable first.}
87
+
88
+ ## 7. Gameplay Seed
89
+
90
+ First Level Scenario: {8×8 board, path count, blocked vs opening moves, tray pressure — must match docs/level-sketch.json}
91
+
92
+ Player Fantasy: {2–3 sentences from the player's POV. **Expand from Micro-Story Who + Situation** — not a generic "solve puzzle + match-3" line.}
93
+
94
+ Decision Moment: {One aha or mis-tap moment — e.g. blocked path feedback → find unlock order.}
95
+
96
+ ## 8. Feedback & Animation Spine
97
+
98
+ ### 8.1 Event Catalog(必填)
99
+
100
+ | Event | Trigger | Visual (Ring 1) | Audio placeholder | Duration budget | Blocks input? |
101
+ | ----------- | --------------------------- | --------------------------------------- | ----------------- | --------------- | ------------- |
102
+ | slide_out | valid tap on free path | path container tween along direction | sfx_slide | 300–400ms | yes |
103
+ | blocked | invalid tap on blocked path | board shake + path red tint | sfx_blocked | ~300ms | no |
104
+ | fly_to_tray | after slide_out | head bezier to tray slot | sfx_land | 350–450ms | yes |
105
+ | match_clear | 3 same color in tray | tray burst + scale pop | sfx_match | 200–300ms | yes |
106
+ | win | board empty + tray clear | overlay + optional confetti placeholder | sfx_win | ≤800ms | yes |
107
+
108
+ ### 8.2 Atom / Skill refs
109
+
110
+ - Primary: `slide_out_to_tray_animation.aicomponent` (defaults: slide 350ms, fly 400ms)
111
+ - Secondary: `path_animation.aicomponent` (blocked shake)
112
+
113
+ ### 8.3 Juice escalation (Ring 1 minimal)
114
+
115
+ - Combo/streak: optional; if n/a, state "flat feedback, escalate in Ring 2"
@@ -18,6 +18,17 @@
18
18
 
19
19
  ---
20
20
 
21
+ ## Dispatch Context
22
+
23
+ > **Source of disclosure**: `dispatchPacket.requiredContext.mustRead` is the only authority for this run's required reading set.
24
+
25
+ - **mustRead loaded**: {{yes/no — list every path from dispatchPacket.requiredContext.mustRead}}
26
+ - **atomTreeFocus readFields**: {{meta.engineDecision / meta.selectedAtoms / meta.skillsMatch.mediaGroups / meta.rings}}
27
+ - **selectedAtomsPolicy**: {{read-all-selected-skills-before-game-edit / n-a}}
28
+ - **slice skillRefs focus**: {{current slice skillRefs or fromScratchReason}}
29
+
30
+ ---
31
+
21
32
  ## Spec Quick-Check
22
33
 
23
34
  > **Before coding**, confirm the spec is actually implementable in the current project.
@@ -31,7 +42,7 @@
31
42
 
32
43
  ## Skill Preflight
33
44
 
34
- > **Before coding**, read the Skills referenced in your atoms and record what they contribute.
45
+ > **Before coding**, read every selected atom Skill disclosed by `dispatchPacket.requiredContext.mustRead`. Add one row per `docs/atom-tree.json → meta.selectedAtoms[]` item; `skillRefs` only narrows current implementation focus.
35
46
 
36
47
  | selectedAtomRef | Read path / source | 提供了什么 | 怎么用 | 注意 |
37
48
  | ---------------------- | -------------------------------------- | ------------------------ | ----------------------------------------- | ------------ |
@@ -65,6 +65,18 @@ For every non-`Single` option, answer:
65
65
 
66
66
  If these answers are weak, the fusion is probably decorative complexity.
67
67
 
68
+ ### Mechanic Depth beyond Stitching
69
+
70
+ | Pattern | When to use | Depth signal |
71
+ | --------------------------------------------- | ------------------------------------------ | ---------------------------------------------------------- |
72
+ | **Composite atom** (`arrow_pick_match_rules`) | Path + tray match already fused in one PGS | Single interaction contract; prefer over stitching parents |
73
+ | **Pseudo-fusion B1** | Visual dual system, single reducer | Secondary mechanic auto-triggers — document reducer |
74
+ | **Board phase B1** | Same 8×8 grid, mask unlock | Board "changes" via phase, not resize |
75
+
76
+ **Forbidden in DESIGN:** listing two parent skill names without **interaction contract** (who acts, where match happens, what blocked teaches).
77
+
78
+ See also: `.claude/agents/refs/game-designer-fusion-depth-playbook.md`
79
+
68
80
  ### Decision Rules
69
81
 
70
82
  ```
@@ -0,0 +1,99 @@
1
+ # Creative Process — {Game Name}
2
+
3
+ <!--
4
+ Ring 1 clarify audit trail. Fill phases 1–4 BEFORE finalizing DESIGN.md.
5
+ Source: templates/game-designer-creative-process.template.md
6
+ -->
7
+
8
+ **Created**: {YYYY-MM-DD}
9
+ **Creative arm**: {A1 | A2 | A3 — from GD_CREATIVE_ARM env if harness}
10
+
11
+ ---
12
+
13
+ ## Phase 1 — Radical Diverge
14
+
15
+ List ≥3 **gameplay core-moment** candidates. Do not write DESIGN.md yet.
16
+
17
+ ### Candidate A
18
+
19
+ - **Core moment**: {one sentence}
20
+ - **Story hook**: {15s filmable Hook frame}
21
+ - **Who / Stakes**: {one line each}
22
+ - **Why interesting**: {…}
23
+ - **Core challenge**: {…}
24
+ - **Worst case**: {what breaks if player ignores rules}
25
+
26
+ ### Candidate B
27
+
28
+ - **Core moment**: {…}
29
+ - **Story hook**: {…}
30
+ - **Who / Stakes**: {…}
31
+ - **Why interesting**: {…}
32
+ - **Core challenge**: {…}
33
+ - **Worst case**: {…}
34
+
35
+ ### Candidate C
36
+
37
+ - **Core moment**: {…}
38
+ - **Story hook**: {…}
39
+ - **Who / Stakes**: {…}
40
+ - **Why interesting**: {…}
41
+ - **Core challenge**: {…}
42
+ - **Worst case**: {…}
43
+
44
+ ---
45
+
46
+ ## Phase 2 — Adversarial Review (five questions)
47
+
48
+ For each candidate, answer **evidence-based** questions — not aesthetic veto.
49
+
50
+ | Candidate | Q1 Second tap? | Q2 Ignore mechanism? | Q3 White-box fun? | Q4 Depth ceiling? | Q5 Strip story — still care? |
51
+ | --------- | -------------- | -------------------- | ----------------- | ----------------- | ---------------------------- |
52
+ | A | {…} | {…} | {…} | {…} | {…} |
53
+ | B | {…} | {…} | {…} | {…} | {…} |
54
+ | C | {…} | {…} | {…} | {…} | {…} |
55
+
56
+ **Vulnerability list** (falsifiable, not "not fun"):
57
+
58
+ - {bullet}
59
+ - {bullet}
60
+
61
+ ---
62
+
63
+ ## Phase 3 — Commitment
64
+
65
+ **Chosen candidate**: {A | B | C}
66
+
67
+ **Narrative level**: {L0 | L1 | L2} — {CD decides; if L0, quote user's explicit opt-out request here}
68
+
69
+ **Patch promises** (how this design addresses vulnerabilities):
70
+
71
+ - {…}
72
+
73
+ **Rejected alternatives**:
74
+
75
+ - {B}: deferred because {…}
76
+ - {C}: deferred because {…}
77
+
78
+ **Pressure test memo** (≤30 lines):
79
+
80
+ - White-box: {…}
81
+ - 1-minute test: {…}
82
+ - Depth test: {…}
83
+
84
+ ---
85
+
86
+ ## Phase 4 — Design Intent
87
+
88
+ **Design Intent**: {e.g. "Player aha at move 3 when blocked path unlocks tray combo"}
89
+
90
+ > **Mandatory sync**: Copy **Design Intent verbatim** into `docs/DESIGN.md` §0 **One-liner**. Copy **System Dynamic** into §0 second field. Copy **Micro-Story Spine** fields field-for-field from `docs/north-star.md`.
91
+
92
+ **Mechanic variation** (vs pure stitch): {one axis — tray pressure / phase unlock / segment density}
93
+
94
+ **Rejected shallow fusion**: {e.g. all-free 4 paths on 8×8}
95
+
96
+ **Pattern / skills locked after commitment**:
97
+
98
+ - Pattern: {from playcraft patterns match}
99
+ - Primary skill: {e.g. arrow_pick_match_rules.aigameplay}
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": 1,
3
+ "row": 8,
4
+ "col": 8,
5
+ "levelTime": 0,
6
+ "trayCapacity": 7,
7
+ "matchCount": 3,
8
+ "colors": ["red", "red", "red", "blue", "blue", "blue"],
9
+ "arrows": [
10
+ [
11
+ [0, 0],
12
+ [1, 0],
13
+ [2, 0],
14
+ [3, 0],
15
+ [4, 0],
16
+ [5, 0]
17
+ ],
18
+ [
19
+ [7, 1],
20
+ [6, 1],
21
+ [5, 1],
22
+ [4, 1],
23
+ [3, 1],
24
+ [2, 1]
25
+ ],
26
+ [
27
+ [2, 2],
28
+ [2, 3],
29
+ [2, 4],
30
+ [2, 5],
31
+ [2, 6]
32
+ ],
33
+ [
34
+ [0, 7],
35
+ [1, 7],
36
+ [2, 7],
37
+ [3, 7],
38
+ [4, 7]
39
+ ],
40
+ [
41
+ [7, 6],
42
+ [6, 6],
43
+ [5, 6],
44
+ [4, 6],
45
+ [3, 6]
46
+ ],
47
+ [
48
+ [6, 3],
49
+ [6, 4],
50
+ [6, 5],
51
+ [5, 5],
52
+ [4, 5],
53
+ [3, 5]
54
+ ]
55
+ ]
56
+ }
@@ -0,0 +1,43 @@
1
+ # Creative North Star — {Game Name}
2
+
3
+ <!--
4
+ Ring 1 creative discovery output (written by @creative-director).
5
+ @game-designer-spec copies §0 fields from this file into docs/DESIGN.md §0.
6
+ One-liner: verbatim. Micro-Story Spine: field-value match (trimmed per field).
7
+ Source: templates/north-star.template.md
8
+ -->
9
+
10
+ **Date**: {YYYY-MM-DD}
11
+
12
+ ---
13
+
14
+ ## Micro-Story Spine
15
+
16
+ **Narrative level**: L1 | L2 <!-- default L1; L0 only when user explicitly opts out of story; L3 forbidden -->
17
+ **Who**: {player identity; L0 may use n/a — pure mechanic showcase}
18
+ **Situation**: {opening Hook frame — one glance readable scene/situation}
19
+ **Stakes**: {what core pressure means in the player's world — not abstract game over}
20
+ **Payoff image**: {first core payoff moment — must film a PGS beat: path slide out, fly to tray, or tray match burst}
21
+ **Emotional arc**: {Urgency | Challenge | Competence | Curiosity | Relaxation}
22
+
23
+ ## One-liner
24
+
25
+ {L1/L2: one vivid pitch — must name Who or Situation; no pure debt/bet abstract stacking.}
26
+ {L0: Hook spectacle + mechanic payoff; Who optional.}
27
+
28
+ ## System Dynamic
29
+
30
+ {One sentence — what system tension keeps the player acting.}
31
+
32
+ ## Why this fusion
33
+
34
+ {2–3 sentences — how parent mechanics **serve** the idea above. Start with intent, not grid sizes.}
35
+
36
+ ## Rejected vibe
37
+
38
+ - {Not this — e.g. not a casual match-3 binge}
39
+ - {Not this — e.g. not a pure arrow logic puzzle with no payoff}
40
+
41
+ ## Design Intent
42
+
43
+ {Phase 4 single line — must match One-liner verbatim when copied to DESIGN §0.}
@@ -55,7 +55,7 @@
55
55
 
56
56
  Each iteration runs inner loops: `clarify` → `build` → `verify` → **Ring Gate** (user acceptance) → next iteration.
57
57
 
58
- <!-- Iteration definitions: see CLAUDE.md § Growth-Ring Model -->
58
+ <!-- Iteration definitions: see docs/team/collaboration.md (growth-rings-v1) -->
59
59
 
60
60
  ## Ring DoD (current iteration)
61
61