@playcraft/cli 0.0.55 → 0.0.57

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 (170) hide show
  1. package/README.md +18 -0
  2. package/REMIX.md +115 -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 +93 -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 +256 -0
  15. package/dist/remix/local-git.js +56 -0
  16. package/dist/remix/pull.js +64 -0
  17. package/dist/remix/push.js +70 -0
  18. package/dist/scaffold/templates.js +1 -1
  19. package/dist/utils/agent-api-client.js +10 -3
  20. package/dist/utils/remix-image-attachments.js +4 -32
  21. package/dist/utils/sandbox-path.js +20 -0
  22. package/package.json +3 -3
  23. package/project-template/.claude/agents/creative-director.md +114 -0
  24. package/project-template/.claude/agents/developer.md +5 -2
  25. package/project-template/.claude/agents/game-designer.md +41 -20
  26. package/project-template/.claude/agents/refs/README.md +8 -0
  27. package/project-template/.claude/agents/refs/creative-director-guide.md +35 -0
  28. package/project-template/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
  29. package/project-template/.claude/agents/refs/creative-discovery-guide.md +76 -0
  30. package/project-template/.claude/agents/refs/creative-north-star-examples.md +82 -0
  31. package/project-template/.claude/agents/refs/developer-dev-handoff.md +1 -1
  32. package/project-template/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
  33. package/project-template/.claude/agents/refs/game-designer-gameplay-sufficiency.md +4 -4
  34. package/project-template/.claude/agents/reviewer.md +11 -0
  35. package/project-template/.claude/hooks/README.md +12 -12
  36. package/project-template/.claude/hooks/block-developer-preflight.mjs +49 -13
  37. package/project-template/.claude/hooks/block-ring1-tasklist.mjs +1 -1
  38. package/project-template/.claude/hooks/user-prompt.mjs +3 -13
  39. package/project-template/.claude/hooks/validate-workflow-stop.mjs +209 -12
  40. package/project-template/.claude/settings.json +2 -2
  41. package/project-template/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
  42. package/project-template/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -1
  43. package/project-template/.claude/snapshot.mjs +0 -1
  44. package/project-template/.codebuddy/settings.json +1 -1
  45. package/project-template/.cursor/rules/playcraft-orchestrator.mdc +1 -1
  46. package/project-template/CLAUDE.md +9 -9
  47. package/project-template/docs/project-state.md +1 -1
  48. package/project-template/docs/team/agent-conduct.md +18 -18
  49. package/project-template/docs/team/collaboration.md +19 -7
  50. package/project-template/docs/team/workflow-changelog.md +15 -0
  51. package/project-template/fusion-patterns/catalog.json +77 -0
  52. package/project-template/package.json +2 -0
  53. package/project-template/skills/_shared/scripts/create-playable.mjs +31 -11
  54. package/project-template/skills/_shared/scripts/dev-start.mjs +3 -3
  55. package/project-template/skills/_shared/scripts/disclosure-config.mjs +65 -39
  56. package/project-template/skills/_shared/scripts/harness-smoke.mjs +3 -1
  57. package/project-template/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
  58. package/project-template/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
  59. package/project-template/skills/_shared/scripts/slice-set.mjs +29 -2
  60. package/project-template/skills/_shared/scripts/state-get.mjs +124 -7
  61. package/project-template/skills/_shared/scripts/state-store.mjs +25 -2
  62. package/project-template/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
  63. package/project-template/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
  64. package/project-template/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
  65. package/project-template/skills/playable-record/scripts/record-playable.mjs +2 -1
  66. package/project-template/skills/playable-record/scripts/verify-contract.mjs +4 -4
  67. package/project-template/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
  68. package/project-template/skills/playable-record/scripts/verify-fusion.mjs +37 -2
  69. package/project-template/skills/playable-record/scripts/verify-slice.mjs +2 -2
  70. package/project-template/templates/design-brief.template.md +34 -0
  71. package/project-template/templates/design-methodology.md +1 -2
  72. package/project-template/templates/design.template.md +65 -0
  73. package/project-template/templates/developer-log.template.md +12 -1
  74. package/project-template/templates/five-axis-framework.md +12 -0
  75. package/project-template/templates/game-designer-creative-process.template.md +99 -0
  76. package/project-template/templates/level-sketch.template.json +56 -0
  77. package/project-template/templates/north-star.template.md +43 -0
  78. package/project-template/templates/project-state.template.md +1 -1
  79. package/project-template/vite.config.ts +4 -5
  80. package/project-templates/phaser/.claude/agents/creative-director.md +114 -0
  81. package/project-templates/phaser/.claude/agents/game-designer.md +41 -20
  82. package/project-templates/phaser/.claude/agents/refs/creative-director-guide.md +35 -0
  83. package/project-templates/phaser/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
  84. package/project-templates/phaser/.claude/agents/refs/creative-discovery-guide.md +76 -0
  85. package/project-templates/phaser/.claude/agents/refs/creative-north-star-examples.md +82 -0
  86. package/project-templates/phaser/.claude/agents/refs/developer-dev-handoff.md +1 -1
  87. package/project-templates/phaser/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
  88. package/project-templates/phaser/.claude/agents/reviewer.md +11 -0
  89. package/project-templates/phaser/.claude/hooks/block-ring1-tasklist.mjs +2 -2
  90. package/project-templates/phaser/.claude/hooks/user-prompt.mjs +17 -18
  91. package/project-templates/phaser/.claude/hooks/validate-workflow-stop.mjs +209 -12
  92. package/project-templates/phaser/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
  93. package/project-templates/phaser/.cursor/rules/playcraft-orchestrator.mdc +1 -1
  94. package/project-templates/phaser/CLAUDE.md +88 -54
  95. package/project-templates/phaser/docs/DESIGN.md +25 -0
  96. package/project-templates/phaser/docs/project-state.md +1 -1
  97. package/project-templates/phaser/docs/team/collaboration.md +19 -7
  98. package/project-templates/phaser/docs/team/workflow-changelog.md +14 -0
  99. package/project-templates/phaser/fusion-patterns/catalog.json +77 -0
  100. package/project-templates/phaser/package.json +3 -1
  101. package/project-templates/phaser/skills/_shared/scripts/dev-start.mjs +3 -3
  102. package/project-templates/phaser/skills/_shared/scripts/disclosure-config.mjs +65 -39
  103. package/project-templates/phaser/skills/_shared/scripts/harness-smoke.mjs +3 -1
  104. package/project-templates/phaser/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
  105. package/project-templates/phaser/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
  106. package/project-templates/phaser/skills/_shared/scripts/state-get.mjs +124 -7
  107. package/project-templates/phaser/skills/_shared/scripts/state-store.mjs +25 -2
  108. package/project-templates/phaser/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
  109. package/project-templates/phaser/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
  110. package/project-templates/phaser/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
  111. package/project-templates/phaser/skills/playable-record/scripts/record-playable.mjs +2 -1
  112. package/project-templates/phaser/skills/playable-record/scripts/verify-contract.mjs +4 -4
  113. package/project-templates/phaser/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
  114. package/project-templates/phaser/skills/playable-record/scripts/verify-fusion.mjs +37 -2
  115. package/project-templates/phaser/skills/playable-record/scripts/verify-slice.mjs +2 -2
  116. package/project-templates/phaser/templates/design-brief.template.md +42 -11
  117. package/project-templates/phaser/templates/design-methodology.md +1 -2
  118. package/project-templates/phaser/templates/design.template.md +77 -0
  119. package/project-templates/phaser/templates/five-axis-framework.md +50 -1
  120. package/project-templates/phaser/templates/game-designer-creative-process.template.md +99 -0
  121. package/project-templates/phaser/templates/level-sketch.template.json +56 -0
  122. package/project-templates/phaser/templates/north-star.template.md +43 -0
  123. package/project-templates/phaser/templates/project-state.template.md +1 -1
  124. package/project-templates/phaser/vite.config.ts +4 -5
  125. package/project-templates/threejs/.claude/agents/creative-director.md +114 -0
  126. package/project-templates/threejs/.claude/agents/game-designer.md +41 -20
  127. package/project-templates/threejs/.claude/agents/refs/creative-director-guide.md +35 -0
  128. package/project-templates/threejs/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
  129. package/project-templates/threejs/.claude/agents/refs/creative-discovery-guide.md +76 -0
  130. package/project-templates/threejs/.claude/agents/refs/creative-north-star-examples.md +82 -0
  131. package/project-templates/threejs/.claude/agents/refs/developer-dev-handoff.md +1 -1
  132. package/project-templates/threejs/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
  133. package/project-templates/threejs/.claude/agents/reviewer.md +11 -0
  134. package/project-templates/threejs/.claude/hooks/block-ring1-tasklist.mjs +2 -2
  135. package/project-templates/threejs/.claude/hooks/user-prompt.mjs +17 -18
  136. package/project-templates/threejs/.claude/hooks/validate-workflow-stop.mjs +209 -12
  137. package/project-templates/threejs/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
  138. package/project-templates/threejs/.cursor/rules/playcraft-orchestrator.mdc +1 -1
  139. package/project-templates/threejs/CLAUDE.md +88 -54
  140. package/project-templates/threejs/docs/DESIGN.md +25 -0
  141. package/project-templates/threejs/docs/project-state.md +1 -1
  142. package/project-templates/threejs/docs/team/collaboration.md +19 -7
  143. package/project-templates/threejs/docs/team/workflow-changelog.md +14 -0
  144. package/project-templates/threejs/fusion-patterns/catalog.json +77 -0
  145. package/project-templates/threejs/package.json +3 -1
  146. package/project-templates/threejs/skills/_shared/scripts/dev-start.mjs +3 -3
  147. package/project-templates/threejs/skills/_shared/scripts/disclosure-config.mjs +65 -39
  148. package/project-templates/threejs/skills/_shared/scripts/harness-smoke.mjs +3 -1
  149. package/project-templates/threejs/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
  150. package/project-templates/threejs/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
  151. package/project-templates/threejs/skills/_shared/scripts/state-get.mjs +124 -7
  152. package/project-templates/threejs/skills/_shared/scripts/state-store.mjs +25 -2
  153. package/project-templates/threejs/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
  154. package/project-templates/threejs/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
  155. package/project-templates/threejs/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
  156. package/project-templates/threejs/skills/playable-record/scripts/record-playable.mjs +2 -1
  157. package/project-templates/threejs/skills/playable-record/scripts/verify-contract.mjs +4 -4
  158. package/project-templates/threejs/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
  159. package/project-templates/threejs/skills/playable-record/scripts/verify-fusion.mjs +37 -2
  160. package/project-templates/threejs/skills/playable-record/scripts/verify-slice.mjs +2 -2
  161. package/project-templates/threejs/templates/design-brief.template.md +42 -11
  162. package/project-templates/threejs/templates/design-methodology.md +1 -2
  163. package/project-templates/threejs/templates/design.template.md +77 -0
  164. package/project-templates/threejs/templates/five-axis-framework.md +50 -1
  165. package/project-templates/threejs/templates/game-designer-creative-process.template.md +99 -0
  166. package/project-templates/threejs/templates/level-sketch.template.json +56 -0
  167. package/project-templates/threejs/templates/north-star.template.md +43 -0
  168. package/project-templates/threejs/templates/project-state.template.md +1 -1
  169. package/project-templates/threejs/vite.config.ts +4 -5
  170. 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`;
@@ -3,16 +3,13 @@
3
3
  <!--
4
4
  Living discovery log — NOT a substitute for docs/DESIGN.md (the playable seed contract).
5
5
 
6
- Ring 1 (slice-axis):
7
- - Record how core action / feedback / progress / constraints were discovered.
8
- - Feed the minimal slice queue; do NOT write layout, style, CTA polish, or production plans here.
9
-
10
- Rings 2–4:
11
- - Deepen periphery, layout intent, Hook/CTA, style direction as those rings disclose.
12
- - Ring 2 (slice-axis): append periphery discovery → 2–4 developer slices; not a feature backlog.
6
+ Maintained across all rings — each ring's clarify phase appends new discovery.
7
+ Record how core action / feedback / progress / constraints were discovered.
8
+ Feed the slice queue; do NOT write layout, style, CTA polish, or production plans here.
13
9
 
14
10
  Update rules:
15
11
  - Create at the start of clarify; append after each brainstorm round.
12
+ - Maintain across rings — do not discard prior-ring discovery.
16
13
  - Preserve source tags; do not polish away evidence.
17
14
 
18
15
  Source tags: [USER] [WEB] [SKILL] [AGENT] [ASSUMPTION][confidence: high/medium/low]
@@ -56,6 +53,40 @@ Append-only evidence (newest first or chronological — pick one and stay consis
56
53
 
57
54
  ## Brainstorm Log
58
55
 
56
+ ### Creative Angle
57
+
58
+ - **Idea hook**: {If only one core moment survives — from Phase 1 candidates}
59
+ - **Emotional payoff**: {What the player should feel after the first win}
60
+ - **Anti-generic**: {Why this is not "ordinary arrow + tray match-3"}
61
+
62
+ ### Pattern picked
63
+
64
+ - Pattern: {id from `playcraft patterns match`} [{SOURCE}]
65
+ - Why this over alternatives: {one line}
66
+
67
+ ### Reference insight
68
+
69
+ - {3–5 bullets from patterns match or `playcraft tools research --focus gameplay`} [{WEB|SKILL}]
70
+ - researchSkippedReason: {only if no research/patterns — explain why}
71
+
72
+ ### Motion moments (Ring 1)
73
+
74
+ - First tap: {slide_out visual — what moves, how long}
75
+ - First blocked: {blocked shake/tint — teach dependency}
76
+ - First match-3: {match_clear in tray — payoff moment}
77
+ - Rejected motion: {why not swap/gravity cascade / board match3 — e.g. "主手势是 path tap,三消仅在托盘"}
78
+
79
+ ### Mechanic variation
80
+
81
+ - {One axis beyond stitching two skills: e.g. tray pressure curve / blocked dependency layers / board phase unlock}
82
+ - rejectedShallowFusion: {why not all-free opening or ≤4 paths on 8×8}
83
+
84
+ ### Gameplay Lens (3 questions)
85
+
86
+ 1. Core action in one verb: {…}
87
+ 2. After 3–5 steps the player gets: {…}
88
+ 3. With sound off for 10s, gameplay guessable? {yes/no + why}
89
+
59
90
  ### Round 1
60
91
 
61
92
  - Asked: {question and why it mattered for core action / feedback}
@@ -64,9 +95,9 @@ Append-only evidence (newest first or chronological — pick one and stay consis
64
95
 
65
96
  ---
66
97
 
67
- ## Core Discovery (Ring 1 scope)
98
+ ## Core Discovery
68
99
 
69
- > Full Hook / Tutorial / Gameplay / End Card packaging is **Ring 2+**. Ring 1 only needs enough to seed verifiable developer slices.
100
+ > Record core gameplay discovery for the current ring. Later rings deepen periphery, layout intent, Hook/CTA, and style direction as those rings disclose.
70
101
 
71
102
  ### Player action chain
72
103
 
@@ -78,11 +109,11 @@ Append-only evidence (newest first or chronological — pick one and stay consis
78
109
 
79
110
  - Engine hint basis: {phaser / threejs + why} [{SOURCE}]
80
111
  - Semantic placeholders: {player; target; feedback; reward; …} [{SOURCE}]
81
- - Out of scope for Ring 1: layout zones, style direction, CTA copy, asset production
112
+ - Out of scope: layout zones, style direction, CTA copy, asset production (unless current ring discloses them)
82
113
 
83
114
  ---
84
115
 
85
- ## Quality check (Ring 1)
116
+ ## Quality check
86
117
 
87
118
  - [ ] Core action is explicit, not implied
88
119
  - [ ] Feedback timing is stated
@@ -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.}
@@ -29,6 +55,18 @@
29
55
 
30
56
  {How the first level guarantees success: constrained setup, guided target, no failure timer, locked failure path, or simplified rule.}
31
57
 
58
+ ## Core Fun & Feedback
59
+
60
+ ### Peak Moment
61
+
62
+ {1-2 core satisfying moments: when triggered → what the player sees/hears → emotional response. Be concrete — name the visual element, the sound cue, the motion.}
63
+
64
+ ### Feedback Layers
65
+
66
+ | Action | Instant (<100ms) | Result | Enhancement (combo/special) |
67
+ | -------- | ---------------------- | ------------------------- | --------------------------- |
68
+ | {action} | {press/touch feedback} | {success animation/sound} | {combo/chain payoff} |
69
+
32
70
  ## Semantic Placeholders
33
71
 
34
72
  | Placeholder | Meaning in Ring 1 | Required? |
@@ -48,3 +86,42 @@
48
86
  ## Open Assumptions
49
87
 
50
88
  {Only list assumptions that materially affect Ring 1. Use "none" if clear.}
89
+
90
+ ---
91
+
92
+ ## 6. Gameplay Fusion
93
+
94
+ Fusion type: {Single | A-Visual | B1-Passive | B2-Active | C-Sequential | D-Parallel}
95
+
96
+ This fusion serves §0 because {link back to One-liner / System Dynamic keywords}.
97
+
98
+ {One paragraph: which parent mechanics combine, interaction contract (not just skill names), and why this fusion is playable first.}
99
+
100
+ ## 7. Gameplay Seed
101
+
102
+ First Level Scenario: {8×8 board, path count, blocked vs opening moves, tray pressure — must match docs/level-sketch.json}
103
+
104
+ Player Fantasy: {2–3 sentences from the player's POV. **Expand from Micro-Story Who + Situation** — not a generic "solve puzzle + match-3" line.}
105
+
106
+ Decision Moment: {One aha or mis-tap moment — e.g. blocked path feedback → find unlock order.}
107
+
108
+ ## 8. Feedback & Animation Spine
109
+
110
+ ### 8.1 Event Catalog(必填)
111
+
112
+ | Event | Trigger | Visual (Ring 1) | Audio placeholder | Duration budget | Blocks input? |
113
+ | ----------- | --------------------------- | --------------------------------------- | ----------------- | --------------- | ------------- |
114
+ | slide_out | valid tap on free path | path container tween along direction | sfx_slide | 300–400ms | yes |
115
+ | blocked | invalid tap on blocked path | board shake + path red tint | sfx_blocked | ~300ms | no |
116
+ | fly_to_tray | after slide_out | head bezier to tray slot | sfx_land | 350–450ms | yes |
117
+ | match_clear | 3 same color in tray | tray burst + scale pop | sfx_match | 200–300ms | yes |
118
+ | win | board empty + tray clear | overlay + optional confetti placeholder | sfx_win | ≤800ms | yes |
119
+
120
+ ### 8.2 Atom / Skill refs
121
+
122
+ - Primary: `slide_out_to_tray_animation.aicomponent` (defaults: slide 350ms, fly 400ms)
123
+ - Secondary: `path_animation.aicomponent` (blocked shake)
124
+
125
+ ### 8.3 Juice escalation (Ring 1 minimal)
126
+
127
+ - Combo/streak: optional; if n/a, state "flat feedback, escalate in Ring 2"
@@ -5,12 +5,35 @@
5
5
  > **Stage relevance**:
6
6
  >
7
7
  > - **ring 1 `loop: clarify`**: Only **Axis 1 (Mechanic)** and **Axis 3 (Interaction Modality)** must be resolved — they define core playability (可解性) and time/operation budget. The other axes only need a rough direction signal (e.g. "2D", "L1 character present", "Competence arc").
8
- > - **ring 2 `loop: clarify` (after MC)**: Axis 2 (Dimension), Axis 4 (Narrative Level), and Axis 5 (Emotional Arc) get their full specification when visual direction is confirmed by MC selection.
8
+ > - **ring 2 `loop: clarify` (after MC (Master Composite, 主合成图))**: Axis 2 (Dimension), Axis 4 (Narrative Level), and Axis 5 (Emotional Arc) get their full specification when visual direction is confirmed by MC selection.
9
9
  >
10
10
  > **This framework is a thinking aid, not a production template.** Game Designer should focus on gameplay first; use these axes to check coverage, not to fill out forms.
11
11
 
12
12
  The five axes are **orthogonal** — each is an independent decision. The same Match-3 game can be Flat 2D or 2.5D, Tap-only or Drag, L0 or L1, Competence Arc or Challenge Arc. Game Designer evaluates all five internally, then presents 2-3 complete options (covering all axes) for the user to choose from.
13
13
 
14
+ ## Playable-Ad First Principle
15
+
16
+ The five axes are not a recipe for adding more ideas. They are a filter for one short playable-ad delight loop:
17
+
18
+ ```text
19
+ Hook Conflict
20
+ → First Action
21
+ → Instant Feedback
22
+ → Escalating Decision
23
+ → Payoff Release
24
+ → Install Curiosity
25
+ ```
26
+
27
+ Before applying any axis, Game Designer must know the primary player desire:
28
+
29
+ - What makes the user stop?
30
+ - What makes the first action obvious and tempting?
31
+ - What feedback makes the player feel in control?
32
+ - What payoff happens within 3-5 actions?
33
+ - What unfinished promise carries the CTA (Call To Action, 行动号召)?
34
+
35
+ Any axis choice or fusion idea is valid only if it strengthens at least one part of this loop. If a secondary mechanic only makes the concept sound richer, but does not improve hook, first action, feedback, payoff, or install curiosity, downgrade it to visual dressing or pseudo-fusion.
36
+
14
37
  ---
15
38
 
16
39
  ## Axis 1 — Mechanic (Gameplay Fusion Types)
@@ -28,6 +51,32 @@ The five axes are **orthogonal** — each is an independent decision. The same M
28
51
 
29
52
  When the user insists on Type C/D, recommend pseudo-fusion: **visually appear as two systems, but the underlying mechanic is single**. Example: Match-3 elimination count drives a monster HP bar — the user only performs match-3 the entire time, but perceives "elimination + combat."
30
53
 
54
+ ### Fusion Quality Bar
55
+
56
+ For every non-`Single` option, answer:
57
+
58
+ | Question | Pass Signal |
59
+ | ----------------------------------------------------------- | ---------------------------------------------------------------- |
60
+ | What is the primary joy? | One clear emotion source, not a list of features |
61
+ | Which loop stage does the secondary mechanic strengthen? | Hook, first action, feedback, payoff, or install curiosity |
62
+ | Does the secondary mechanic require a new tutorial gesture? | No, or the cost is explicitly justified |
63
+ | Does the payoff become stronger? | The player sees a bigger release than the primary mechanic alone |
64
+ | Can it be understood in the ad timeline? | The user can grasp it without reading a rules paragraph |
65
+
66
+ If these answers are weak, the fusion is probably decorative complexity.
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
+
31
80
  ### Decision Rules
32
81
 
33
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}