@playcraft/cli 0.0.43 → 0.0.46

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 (996) hide show
  1. package/dist/atom-tree/build-skeleton.js +180 -0
  2. package/dist/atom-tree/fusion-hints.js +86 -0
  3. package/dist/atom-tree/selection.js +402 -0
  4. package/dist/commands/build-all.js +33 -5
  5. package/dist/commands/build.js +13 -7
  6. package/dist/commands/create.js +774 -50
  7. package/dist/commands/image.js +11 -0
  8. package/dist/commands/prad.js +3 -3
  9. package/dist/commands/remix.js +20 -0
  10. package/dist/commands/skills.js +961 -185
  11. package/dist/commands/tools-detect.js +56 -0
  12. package/dist/commands/tools-generation.js +21 -0
  13. package/dist/commands/tools-video-review.js +100 -0
  14. package/dist/commands/tools.js +4 -0
  15. package/dist/index.js +2 -0
  16. package/dist/prad/check.test.js +23 -1
  17. package/dist/prad/skills-index.js +1 -1
  18. package/dist/remix/init-template.js +80 -0
  19. package/dist/scaffold/compose.js +211 -0
  20. package/engine-packs/phaser/game/adapters/phaser.ts +52 -0
  21. package/engine-packs/phaser/game/config/game-config.ts +46 -0
  22. package/engine-packs/phaser/game/contract/publish.ts +141 -0
  23. package/engine-packs/phaser/game/contract/types.ts +34 -0
  24. package/engine-packs/phaser/game/index.ts +81 -0
  25. package/engine-packs/phaser/game/runtime-contract.ts +28 -0
  26. package/engine-packs/phaser/manifest.json +1 -0
  27. package/engine-packs/phaser/package.deps.json +1 -0
  28. package/engine-packs/threejs/game/config/game-config.ts +46 -0
  29. package/engine-packs/threejs/game/contract/publish.ts +141 -0
  30. package/engine-packs/threejs/game/contract/types.ts +34 -0
  31. package/engine-packs/threejs/game/index.ts +76 -0
  32. package/engine-packs/threejs/game/runtime-contract.ts +15 -0
  33. package/engine-packs/threejs/manifest.json +1 -0
  34. package/engine-packs/threejs/package.deps.json +8 -0
  35. package/package.json +18 -4
  36. package/project-template/.claude/agents/artist.md +101 -0
  37. package/project-template/.claude/agents/developer.md +90 -93
  38. package/project-template/.claude/agents/game-designer.md +192 -0
  39. package/project-template/.claude/agents/refs/README.md +57 -51
  40. package/project-template/.claude/agents/refs/{designer-color-audio-recipes.md → artist-color-audio-recipes.md} +2 -2
  41. package/project-template/.claude/agents/refs/{designer-deliverable-spec.md → artist-deliverable-spec.md} +57 -57
  42. package/project-template/.claude/agents/refs/{designer-dimension-axis.md → artist-dimension-axis.md} +5 -5
  43. package/project-template/.claude/agents/refs/{designer-handoff-v2-checklist.md → artist-handoff-v2-checklist.md} +15 -15
  44. package/project-template/.claude/agents/refs/artist-legacy-fallback.md +14 -0
  45. package/project-template/.claude/agents/refs/{designer-master-composite-recipes.md → artist-master-composite-recipes.md} +3 -3
  46. package/project-template/.claude/agents/refs/artist-style-exploration-flow.md +55 -0
  47. package/project-template/.claude/agents/refs/developer-dev-handoff.md +83 -40
  48. package/project-template/.claude/agents/refs/developer-impl-cookbook.md +365 -67
  49. package/project-template/.claude/agents/refs/developer-legacy-fallback.md +15 -0
  50. package/project-template/.claude/agents/refs/developer-phase1-flow.md +131 -85
  51. package/project-template/.claude/agents/refs/framework-5-component-filter.md +252 -0
  52. package/project-template/.claude/agents/refs/framework-game-feel-juice.md +266 -0
  53. package/project-template/.claude/agents/refs/framework-mda.md +147 -0
  54. package/project-template/.claude/agents/refs/game-designer-gameplay-sufficiency.md +123 -0
  55. package/project-template/.claude/agents/refs/game-designer-legacy-fallback.md +15 -0
  56. package/project-template/.claude/agents/refs/game-designer-workflow-detail.md +69 -0
  57. package/project-template/.claude/agents/refs/implementation-plan-guide.md +316 -0
  58. package/project-template/.claude/agents/refs/reviewer-convergence-eval.md +247 -33
  59. package/project-template/.claude/agents/refs/reviewer-legacy-fallback.md +14 -0
  60. package/project-template/.claude/agents/refs/skill-selector-guide.md +465 -0
  61. package/project-template/.claude/agents/refs/skill-value-layers.md +145 -0
  62. package/project-template/.claude/agents/refs/storyboard-generation-guide.md +110 -0
  63. package/project-template/.claude/agents/refs/ta-atlas-deliverable-standard.md +4 -4
  64. package/project-template/.claude/agents/refs/ta-pipeline-cookbook.md +101 -104
  65. package/project-template/.claude/agents/refs/technical-artist-legacy-fallback.md +14 -0
  66. package/project-template/.claude/agents/reviewer.md +81 -95
  67. package/project-template/.claude/agents/technical-artist.md +71 -95
  68. package/project-template/.claude/hooks/README.md +83 -35
  69. package/project-template/.claude/hooks/asr-coverage-validate.mjs +19 -19
  70. package/project-template/.claude/hooks/block-developer-preflight.mjs +185 -0
  71. package/project-template/.claude/hooks/block-ring1-tasklist.mjs +95 -0
  72. package/project-template/.claude/hooks/gate-dispatcher.mjs +78 -0
  73. package/project-template/.claude/hooks/gate-runner.mjs +557 -0
  74. package/project-template/.claude/hooks/gate.mjs +170 -0
  75. package/project-template/.claude/hooks/snapshot-milestone.mjs +246 -0
  76. package/project-template/.claude/hooks/user-prompt.mjs +193 -0
  77. package/project-template/.claude/hooks/validate-atom-tree-selection.mjs +281 -0
  78. package/project-template/.claude/hooks/validate-workflow-stop.mjs +1535 -95
  79. package/project-template/.claude/settings.json +54 -9
  80. package/project-template/.claude/skills/brainstorming/SKILL.md +161 -0
  81. package/project-template/.claude/skills/brainstorming/scripts/frame-template.html +270 -0
  82. package/project-template/.claude/skills/brainstorming/scripts/helper.js +177 -0
  83. package/project-template/.claude/skills/brainstorming/scripts/server.cjs +354 -0
  84. package/project-template/.claude/skills/brainstorming/scripts/start-server.sh +148 -0
  85. package/project-template/.claude/skills/brainstorming/scripts/stop-server.sh +56 -0
  86. package/project-template/.claude/skills/brainstorming/scripts/wait-for-selection.sh +62 -0
  87. package/project-template/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  88. package/project-template/.claude/skills/brainstorming/visual-companion.md +309 -0
  89. package/project-template/.claude/skills/playcraft-ad-psychology/SKILL.md +3 -3
  90. package/project-template/.claude/skills/playcraft-art-style-guide/SKILL.md +3 -3
  91. package/project-template/.claude/skills/playcraft-asset-state-sheet/SKILL.md +35 -25
  92. package/project-template/.claude/skills/playcraft-audio-generation/SKILL.md +15 -15
  93. package/project-template/.claude/skills/playcraft-build-optimizer/SKILL.md +5 -5
  94. package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +5 -5
  95. package/project-template/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +5 -5
  96. package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +116 -27
  97. package/project-template/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +1 -1
  98. package/project-template/.claude/skills/playcraft-image-processing/SKILL.md +219 -0
  99. package/project-template/.claude/skills/playcraft-playable-optimization/SKILL.md +161 -0
  100. package/project-template/.claude/skills/playcraft-research/SKILL.md +24 -21
  101. package/project-template/.claude/skills/playcraft-skill-recommender/SKILL.md +331 -0
  102. package/project-template/.claude/skills/playcraft-sprite-generation/SKILL.md +48 -48
  103. package/project-template/.claude/skills/playcraft-sprite-remix/SKILL.md +158 -0
  104. package/project-template/.claude/skills/playcraft-sprite-sheet/SKILL.md +100 -0
  105. package/project-template/.claude/skills/playcraft-storyboard/SKILL.md +4 -4
  106. package/project-template/.claude/skills/playcraft-style-qa/SKILL.md +45 -45
  107. package/project-template/.claude/skills/playcraft-text-rendering/SKILL.md +27 -27
  108. package/project-template/.claude/skills/session-analyzer/SKILL.md +386 -0
  109. package/project-template/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +182 -0
  110. package/project-template/.claude/skills/session-analyzer/scripts/find-turns.mjs +72 -0
  111. package/project-template/.claude/skills/session-analyzer/scripts/heavy-output.mjs +121 -0
  112. package/project-template/.claude/skills/session-analyzer/scripts/resolve-session.mjs +103 -0
  113. package/project-template/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +127 -0
  114. package/project-template/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +106 -0
  115. package/project-template/.claude/skills/session-analyzer/scripts/time-gaps.mjs +128 -0
  116. package/project-template/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +67 -0
  117. package/project-template/.claude/snapshot.mjs +350 -0
  118. package/project-template/.codebuddy/settings.json +53 -0
  119. package/project-template/.cursor/hooks.json +1 -1
  120. package/project-template/.cursor/rules/playcraft-orchestrator.mdc +91 -86
  121. package/project-template/.cursor/rules/playcraft-subagent-boundary.mdc +16 -5
  122. package/project-template/CHANGELOG.md +105 -0
  123. package/project-template/CLAUDE.md +58 -254
  124. package/project-template/docs/DESIGN.md +65 -0
  125. package/project-template/docs/intent-clarifications.md +58 -0
  126. package/project-template/docs/pipeline-overview.md +69 -0
  127. package/project-template/docs/project-state.md +211 -0
  128. package/project-template/docs/team/agent-conduct.md +51 -105
  129. package/project-template/docs/team/agent-runtime-matrix.md +60 -74
  130. package/project-template/docs/team/atom-tree-format.md +169 -0
  131. package/project-template/docs/team/collaboration.md +319 -225
  132. package/project-template/docs/team/core-model.md +80 -46
  133. package/project-template/docs/team/game-designer-migration-todo.md +8 -0
  134. package/project-template/docs/team/harness-legacy-deprecation.md +23 -0
  135. package/project-template/docs/team/platform-capabilities.md +16 -13
  136. package/project-template/docs/team/workflow-changelog.md +95 -23
  137. package/project-template/docs/team/workflow-consistency-checklist.md +93 -45
  138. package/project-template/focus-lab/.gitkeep +0 -0
  139. package/project-template/game/.playcraft-scaffold-pending +0 -0
  140. package/project-template/game/index.ts +18 -0
  141. package/project-template/globals.d.ts +51 -0
  142. package/project-template/index.css +34 -0
  143. package/project-template/index.html +18 -0
  144. package/project-template/main.ts +9 -0
  145. package/project-template/package.json +79 -0
  146. package/project-template/skills/README.md +52 -0
  147. package/project-template/skills/_shared/agent-prep.md +136 -0
  148. package/project-template/skills/_shared/requirements.txt +1 -0
  149. package/project-template/skills/_shared/scripts/build-evidence-gate.mjs +87 -0
  150. package/project-template/skills/_shared/scripts/context-budget.mjs +215 -0
  151. package/project-template/skills/_shared/scripts/create-playable.mjs +140 -0
  152. package/project-template/skills/_shared/scripts/dev-start.mjs +151 -0
  153. package/project-template/skills/_shared/scripts/disclosure-config.mjs +480 -0
  154. package/project-template/skills/_shared/scripts/ensure-project.mjs +532 -0
  155. package/project-template/skills/_shared/scripts/gen-placeholder-audio.py +345 -0
  156. package/project-template/skills/_shared/scripts/gen-placeholder.py +244 -0
  157. package/project-template/skills/_shared/scripts/harness-smoke.mjs +257 -0
  158. package/project-template/skills/_shared/scripts/lib/validator-artifacts.mjs +129 -0
  159. package/project-template/skills/_shared/scripts/placeholder-pack-sheet.mjs +67 -0
  160. package/project-template/skills/_shared/scripts/placeholder_audio_lib.py +142 -0
  161. package/project-template/skills/_shared/scripts/placeholder_lib.py +993 -0
  162. package/project-template/skills/_shared/scripts/render-project-state.mjs +278 -0
  163. package/project-template/skills/_shared/scripts/ring-config.mjs +252 -0
  164. package/project-template/skills/_shared/scripts/skills-check.mjs +176 -0
  165. package/project-template/skills/_shared/scripts/slice-set.mjs +77 -0
  166. package/project-template/skills/_shared/scripts/state-advance-ring.mjs +63 -0
  167. package/project-template/skills/_shared/scripts/state-get.mjs +215 -0
  168. package/project-template/skills/_shared/scripts/state-handoff.mjs +120 -0
  169. package/project-template/skills/_shared/scripts/state-set.mjs +113 -0
  170. package/project-template/skills/_shared/scripts/state-store.mjs +893 -0
  171. package/project-template/skills/_shared/scripts/sync-template-skills.mjs +112 -0
  172. package/project-template/skills/_shared/scripts/verify-asset-code-sync.mjs +265 -0
  173. package/project-template/skills/_shared/scripts/verify-atom-conformance.mjs +181 -0
  174. package/project-template/skills/_shared/scripts/verify-env.mjs +285 -0
  175. package/project-template/skills/_shared/scripts/verify-placeholders.mjs +161 -0
  176. package/project-template/skills/_shared/scripts/workflow-doctor.mjs +348 -0
  177. package/project-template/skills/playable-animate/SKILL.md +52 -0
  178. package/project-template/skills/playable-animate/agents/openai.yaml +4 -0
  179. package/project-template/skills/playable-animate/references/board-refresh.md +30 -0
  180. package/project-template/skills/playable-animate/references/diagnosis-checklist.md +25 -0
  181. package/project-template/skills/playable-animate/references/entrance-sequencing.md +33 -0
  182. package/project-template/skills/playable-animate/references/interaction-timing.md +23 -0
  183. package/project-template/skills/playable-art/SKILL.md +203 -0
  184. package/project-template/skills/playable-art/agents/openai.yaml +4 -0
  185. package/project-template/skills/playable-art/references/alpha-postprocess.md +46 -0
  186. package/project-template/skills/playable-art/references/cta-art-spec.md +59 -0
  187. package/project-template/skills/playable-art/references/layout-proportions.md +30 -0
  188. package/project-template/skills/playable-art/references/prompt-patterns.md +83 -0
  189. package/project-template/skills/playable-art/references/reference-comparison.md +30 -0
  190. package/project-template/skills/playable-art/references/sheet-import-v2.md +91 -0
  191. package/project-template/skills/playable-art/scripts/build-atlas.py +332 -0
  192. package/project-template/skills/playable-art/scripts/chroma-key.py +50 -0
  193. package/project-template/skills/playable-art/scripts/extract-alpha-split.py +65 -0
  194. package/project-template/skills/playable-art/scripts/extract-dual-bg-alpha.py +118 -0
  195. package/project-template/skills/playable-art/scripts/import-gpt-art-sheets-v2.py +403 -0
  196. package/project-template/skills/playable-art/scripts/import-gpt-art-sheets.py +280 -0
  197. package/project-template/skills/playable-art/scripts/import-gpt-tile-sheet.py +114 -0
  198. package/project-template/skills/playable-art/scripts/import_gpt_art_pipeline.py +321 -0
  199. package/project-template/skills/playable-art/scripts/import_gpt_art_sheets_v2_test.py +126 -0
  200. package/project-template/skills/playable-art/scripts/placeholder_common.py +98 -0
  201. package/project-template/skills/playable-art/scripts/remove-bg.py +91 -0
  202. package/project-template/skills/playable-art/scripts/review-processed-assets.py +468 -0
  203. package/project-template/skills/playable-art/scripts/review_processed_assets_test.py +116 -0
  204. package/project-template/skills/playable-art/scripts/verify-alpha-clean.py +105 -0
  205. package/project-template/skills/playable-debug/SKILL.md +116 -0
  206. package/project-template/skills/playable-debug/agents/openai.yaml +4 -0
  207. package/project-template/skills/playable-debug/references/debug-config.md +40 -0
  208. package/project-template/skills/playable-focus/SKILL.md +148 -0
  209. package/project-template/skills/playable-focus/scripts/bootstrap-playground.mjs +84 -0
  210. package/project-template/skills/playable-focus/scripts/extract-focus-frames.mjs +87 -0
  211. package/project-template/skills/playable-focus/scripts/generate-focus-reports.mjs +121 -0
  212. package/project-template/skills/playable-focus/scripts/playground.mjs +390 -0
  213. package/project-template/skills/playable-focus/templates/focus-lab/frame.html +39 -0
  214. package/project-template/skills/playable-focus/templates/focus-lab/harness/clean-stage.js +114 -0
  215. package/project-template/skills/playable-focus/templates/focus-lab/harness/frame-lab-scene.js +94 -0
  216. package/project-template/skills/playable-focus/templates/focus-lab/harness/particle-lab-scene.js +224 -0
  217. package/project-template/skills/playable-focus/templates/focus-lab/model.html +16 -0
  218. package/project-template/skills/playable-focus/templates/focus-lab/particle.html +38 -0
  219. package/project-template/skills/playable-focus/templates/focus-lab/shatter.html +47 -0
  220. package/project-template/skills/playable-focus/templates/playground/registry.template.json +55 -0
  221. package/project-template/skills/playable-focus/templates/playground/vfx-catalog.template.json +280 -0
  222. package/project-template/skills/playable-fx-decompose/SKILL.md +212 -0
  223. package/project-template/skills/playable-fx-decompose/examples.md +89 -0
  224. package/project-template/skills/playable-fx-decompose/reference.md +201 -0
  225. package/project-template/skills/playable-fx-decompose/requirements.txt +2 -0
  226. package/project-template/skills/playable-fx-decompose/scripts/build-blank-tile-crack.py +192 -0
  227. package/project-template/skills/playable-fx-decompose/scripts/fix-shard-debris-frames.py +60 -0
  228. package/project-template/skills/playable-fx-decompose/scripts/generate-shard-debris.py +276 -0
  229. package/project-template/skills/playable-fx-peak/SKILL.md +237 -0
  230. package/project-template/skills/playable-fx-peak/requirements.txt +2 -0
  231. package/project-template/skills/playable-fx-peak/scripts/fix-frame-edges.py +70 -0
  232. package/project-template/skills/playable-fx-peak/scripts/repack-frame-sequence.py +526 -0
  233. package/project-template/skills/playable-fx-peak/scripts/validate-frame-anchors.py +130 -0
  234. package/project-template/skills/playable-placeholder/SKILL.md +239 -0
  235. package/project-template/skills/playable-record/SKILL.md +166 -0
  236. package/project-template/skills/playable-record/scripts/lib/build-artifact.mjs +92 -0
  237. package/project-template/skills/playable-record/scripts/lib/dev-server.mjs +104 -0
  238. package/project-template/skills/playable-record/scripts/lib/record-audio-bridge.js +141 -0
  239. package/project-template/skills/playable-record/scripts/lib/slice-structure-verify.mjs +199 -0
  240. package/project-template/skills/playable-record/scripts/record-playable.mjs +583 -0
  241. package/project-template/skills/playable-record/scripts/verify-build.mjs +185 -0
  242. package/project-template/skills/playable-record/scripts/verify-contract.mjs +261 -0
  243. package/project-template/skills/playable-record/scripts/verify-firstwin.mjs +398 -0
  244. package/project-template/skills/playable-record/scripts/verify-fusion.mjs +320 -0
  245. package/project-template/skills/playable-record/scripts/verify-lifecycle.mjs +78 -0
  246. package/project-template/skills/playable-record/scripts/verify-slice-artist.mjs +53 -0
  247. package/project-template/skills/playable-record/scripts/verify-slice-ta.mjs +53 -0
  248. package/project-template/skills/playable-record/scripts/verify-slice.mjs +142 -0
  249. package/project-template/skills/playable-record/scripts/verify-visual-ring.mjs +250 -0
  250. package/project-template/skills/playable-record/scripts/verify-visual-ring1.mjs +190 -0
  251. package/project-template/skills/playable-record/scripts/verify-vlm-video.mjs +227 -0
  252. package/project-template/skills/playable-validate/SKILL.md +260 -0
  253. package/project-template/skills/playable-validate/references/checklist-detail.md +36 -0
  254. package/project-template/skills/playable-validate/references/single-html-bundle.md +76 -0
  255. package/project-template/skills/playable-validate/validation-rules.md +200 -0
  256. package/project-template/skills/playable-verify-ui/SKILL.md +295 -0
  257. package/project-template/skills/playable-verify-ui/examples/asset-manifest.example.json +36 -0
  258. package/project-template/skills/playable-verify-ui/examples/mahjong-match.profile.yaml +334 -0
  259. package/project-template/skills/playable-verify-ui/profiles/template.profile.yaml +84 -0
  260. package/project-template/skills/playable-verify-ui/reference.md +165 -0
  261. package/project-template/skills/playable-verify-ui/references/phaser-patterns.md +67 -0
  262. package/project-template/skills/playable-verify-ui/references/scaling-decision-tree.md +51 -0
  263. package/project-template/skills/playable-verify-ui/requirements.txt +7 -0
  264. package/project-template/skills/playable-verify-ui/schema/asset-manifest.schema.json +68 -0
  265. package/project-template/skills/playable-verify-ui/schema/playable-ui-profile.schema.json +171 -0
  266. package/project-template/skills/playable-verify-ui/scripts/asset_manifest.py +231 -0
  267. package/project-template/skills/playable-verify-ui/scripts/lib/__init__.py +1 -0
  268. package/project-template/skills/playable-verify-ui/scripts/lib/config_expr.js +218 -0
  269. package/project-template/skills/playable-verify-ui/scripts/lib/node_utils.js +51 -0
  270. package/project-template/skills/playable-verify-ui/scripts/lib/profile_loader.py +59 -0
  271. package/project-template/skills/playable-verify-ui/scripts/lib/report.py +106 -0
  272. package/project-template/skills/playable-verify-ui/scripts/lib/runtime_contract.js +116 -0
  273. package/project-template/skills/playable-verify-ui/scripts/measure_frame_content.py +77 -0
  274. package/project-template/skills/playable-verify-ui/scripts/run_all.py +265 -0
  275. package/project-template/skills/playable-verify-ui/scripts/verify_asset_sizes.py +77 -0
  276. package/project-template/skills/playable-verify-ui/scripts/verify_assets_present.py +62 -0
  277. package/project-template/skills/playable-verify-ui/scripts/verify_atlas_budget.py +84 -0
  278. package/project-template/skills/playable-verify-ui/scripts/verify_atlas_frames.py +79 -0
  279. package/project-template/skills/playable-verify-ui/scripts/verify_bg_clean.py +365 -0
  280. package/project-template/skills/playable-verify-ui/scripts/verify_doc_asset_sync.py +90 -0
  281. package/project-template/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +59 -0
  282. package/project-template/skills/playable-verify-ui/scripts/verify_game_config_load.js +54 -0
  283. package/project-template/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +64 -0
  284. package/project-template/skills/playable-verify-ui/scripts/verify_layout_regions.js +110 -0
  285. package/project-template/skills/playable-verify-ui/scripts/verify_profile_schema.py +37 -0
  286. package/project-template/skills/playable-verify-ui/scripts/verify_ring1_visual.py +211 -0
  287. package/project-template/skills/playable-verify-ui/scripts/verify_runtime_contract.js +58 -0
  288. package/project-template/skills/playable-verify-ui/scripts/verify_script_load_order.js +73 -0
  289. package/project-template/skills/playable-verify-ui/scripts/verify_slice_plan.py +238 -0
  290. package/project-template/skills/playable-verify-ui/scripts/verify_slice_plan_test.py +125 -0
  291. package/project-template/skills/playable-verify-ui/scripts/verify_text_overlap.py +333 -0
  292. package/project-template/skills/playable-verify-ui/scripts/verify_tile_consistency.py +323 -0
  293. package/project-template/skills/playable-verify-ui/scripts/verify_vision_evidence.py +894 -0
  294. package/project-template/skills/playable-verify-ui/scripts/verify_vision_evidence_test.py +122 -0
  295. package/project-template/skills/playable-verify-ui/scripts/verify_visual.py +422 -0
  296. package/project-template/templates/{designer-log.template.md → artist-log.template.md} +26 -31
  297. package/project-template/templates/atom-tree.template.json +133 -0
  298. package/project-template/templates/design-brief.template.md +59 -162
  299. package/project-template/templates/design-lens-checklist.reference.md +14 -14
  300. package/project-template/templates/design-methodology.md +25 -25
  301. package/project-template/templates/design.template.md +50 -0
  302. package/project-template/templates/developer-log.template.md +187 -63
  303. package/project-template/templates/five-axis-framework.md +89 -44
  304. package/project-template/templates/gen-placeholder-audio.template.mjs +63 -0
  305. package/project-template/templates/gen-placeholders.template.mjs +81 -0
  306. package/project-template/templates/intent-clarifications.template.md +7 -7
  307. package/project-template/templates/layout-spec.template.md +32 -20
  308. package/project-template/templates/plan.template.md +80 -0
  309. package/project-template/templates/project-state.template.md +90 -191
  310. package/project-template/templates/review-report.template.md +208 -24
  311. package/project-template/templates/style-exploration.template.md +8 -8
  312. package/project-template/templates/ta-log.template.md +58 -35
  313. package/project-template/tsconfig.json +20 -0
  314. package/project-template/vite.config.ts +27 -0
  315. package/project-templates/phaser/.claude/agents/artist.md +101 -0
  316. package/project-templates/phaser/.claude/agents/developer.md +123 -0
  317. package/project-templates/phaser/.claude/agents/game-designer.md +192 -0
  318. package/project-templates/phaser/.claude/agents/refs/README.md +77 -0
  319. package/project-templates/phaser/.claude/agents/refs/artist-art-style-catalog.md +533 -0
  320. package/project-templates/phaser/.claude/agents/refs/artist-color-audio-recipes.md +153 -0
  321. package/project-templates/phaser/.claude/agents/refs/artist-deliverable-spec.md +230 -0
  322. package/project-templates/phaser/.claude/agents/refs/artist-dimension-axis.md +27 -0
  323. package/project-templates/phaser/.claude/agents/refs/artist-handoff-v2-checklist.md +76 -0
  324. package/project-templates/phaser/.claude/agents/refs/artist-legacy-fallback.md +14 -0
  325. package/project-templates/phaser/.claude/agents/refs/artist-master-composite-recipes.md +208 -0
  326. package/project-templates/phaser/.claude/agents/refs/artist-style-exploration-flow.md +55 -0
  327. package/project-templates/phaser/.claude/agents/refs/developer-dev-handoff.md +152 -0
  328. package/project-templates/phaser/.claude/agents/refs/developer-impl-cookbook.md +432 -0
  329. package/project-templates/phaser/.claude/agents/refs/developer-legacy-fallback.md +15 -0
  330. package/project-templates/phaser/.claude/agents/refs/developer-phase1-flow.md +404 -0
  331. package/project-templates/phaser/.claude/agents/refs/framework-5-component-filter.md +252 -0
  332. package/project-templates/phaser/.claude/agents/refs/framework-game-feel-juice.md +266 -0
  333. package/project-templates/phaser/.claude/agents/refs/framework-mda.md +147 -0
  334. package/project-templates/phaser/.claude/agents/refs/game-designer-gameplay-sufficiency.md +123 -0
  335. package/project-templates/phaser/.claude/agents/refs/game-designer-legacy-fallback.md +15 -0
  336. package/project-templates/phaser/.claude/agents/refs/game-designer-workflow-detail.md +69 -0
  337. package/project-templates/phaser/.claude/agents/refs/implementation-plan-guide.md +309 -0
  338. package/project-templates/phaser/.claude/agents/refs/reviewer-convergence-eval.md +350 -0
  339. package/project-templates/phaser/.claude/agents/refs/reviewer-legacy-fallback.md +14 -0
  340. package/project-templates/phaser/.claude/agents/refs/reviewer-six-dimension-eval.md +6 -0
  341. package/project-templates/phaser/.claude/agents/refs/skill-selector-guide.md +465 -0
  342. package/project-templates/phaser/.claude/agents/refs/skill-value-layers.md +145 -0
  343. package/project-templates/phaser/.claude/agents/refs/storyboard-generation-guide.md +110 -0
  344. package/project-templates/phaser/.claude/agents/refs/ta-3d-flip-recipe.md +88 -0
  345. package/project-templates/phaser/.claude/agents/refs/ta-atlas-deliverable-standard.md +67 -0
  346. package/project-templates/phaser/.claude/agents/refs/ta-batch-pipeline-recipes.md +120 -0
  347. package/project-templates/phaser/.claude/agents/refs/ta-image-generation-detail.md +300 -0
  348. package/project-templates/phaser/.claude/agents/refs/ta-image-ops-reference.md +495 -0
  349. package/project-templates/phaser/.claude/agents/refs/ta-pipeline-cookbook.md +1141 -0
  350. package/project-templates/phaser/.claude/agents/refs/ta-tools-reference.md +111 -0
  351. package/project-templates/phaser/.claude/agents/refs/ta-vfx-preset-catalog.md +365 -0
  352. package/project-templates/phaser/.claude/agents/refs/technical-artist-legacy-fallback.md +14 -0
  353. package/project-templates/phaser/.claude/agents/reviewer.md +116 -0
  354. package/project-templates/phaser/.claude/agents/technical-artist.md +100 -0
  355. package/project-templates/phaser/.claude/hooks/README.md +120 -0
  356. package/project-templates/phaser/.claude/hooks/asr-coverage-validate.mjs +381 -0
  357. package/project-templates/phaser/.claude/hooks/block-developer-preflight.mjs +185 -0
  358. package/project-templates/phaser/.claude/hooks/block-ring1-tasklist.mjs +95 -0
  359. package/project-templates/phaser/.claude/hooks/gate-dispatcher.mjs +78 -0
  360. package/project-templates/phaser/.claude/hooks/gate-runner.mjs +557 -0
  361. package/project-templates/phaser/.claude/hooks/gate.mjs +171 -0
  362. package/project-templates/phaser/.claude/hooks/snapshot-milestone.mjs +247 -0
  363. package/project-templates/phaser/.claude/hooks/user-prompt.mjs +194 -0
  364. package/project-templates/phaser/.claude/hooks/validate-atom-tree-selection.mjs +281 -0
  365. package/project-templates/phaser/.claude/hooks/validate-workflow-stop.mjs +1889 -0
  366. package/project-templates/phaser/.claude/settings.json +81 -0
  367. package/project-templates/phaser/.claude/skills/brainstorming/SKILL.md +161 -0
  368. package/project-templates/phaser/.claude/skills/brainstorming/scripts/frame-template.html +270 -0
  369. package/project-templates/phaser/.claude/skills/brainstorming/scripts/helper.js +177 -0
  370. package/project-templates/phaser/.claude/skills/brainstorming/scripts/server.cjs +354 -0
  371. package/project-templates/phaser/.claude/skills/brainstorming/scripts/start-server.sh +148 -0
  372. package/project-templates/phaser/.claude/skills/brainstorming/scripts/stop-server.sh +56 -0
  373. package/project-templates/phaser/.claude/skills/brainstorming/scripts/wait-for-selection.sh +62 -0
  374. package/project-templates/phaser/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  375. package/project-templates/phaser/.claude/skills/brainstorming/visual-companion.md +309 -0
  376. package/project-templates/phaser/.claude/skills/playcraft-ad-psychology/SKILL.md +182 -0
  377. package/project-templates/phaser/.claude/skills/playcraft-art-style-guide/SKILL.md +123 -0
  378. package/project-templates/phaser/.claude/skills/playcraft-asset-state-sheet/SKILL.md +205 -0
  379. package/project-templates/phaser/.claude/skills/playcraft-audio-generation/SKILL.md +280 -0
  380. package/project-templates/phaser/.claude/skills/playcraft-batch-pipeline/SKILL.md +184 -0
  381. package/project-templates/phaser/.claude/skills/playcraft-build-optimizer/SKILL.md +306 -0
  382. package/project-templates/phaser/.claude/skills/playcraft-image-generation/SKILL.md +298 -0
  383. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +123 -0
  384. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +254 -0
  385. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +324 -0
  386. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +97 -0
  387. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +118 -0
  388. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +137 -0
  389. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +397 -0
  390. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +296 -0
  391. package/project-templates/phaser/.claude/skills/playcraft-image-ops/SKILL.md +122 -0
  392. package/project-templates/phaser/.claude/skills/playcraft-image-processing/SKILL.md +219 -0
  393. package/project-templates/phaser/.claude/skills/playcraft-masking/SKILL.md +373 -0
  394. package/project-templates/phaser/.claude/skills/playcraft-playable-optimization/SKILL.md +161 -0
  395. package/project-templates/phaser/.claude/skills/playcraft-research/SKILL.md +215 -0
  396. package/project-templates/phaser/.claude/skills/playcraft-skill-recommender/SKILL.md +331 -0
  397. package/project-templates/phaser/.claude/skills/playcraft-sprite-generation/SKILL.md +423 -0
  398. package/project-templates/phaser/.claude/skills/playcraft-sprite-remix/SKILL.md +158 -0
  399. package/project-templates/phaser/.claude/skills/playcraft-sprite-sheet/SKILL.md +100 -0
  400. package/project-templates/phaser/.claude/skills/playcraft-storyboard/SKILL.md +167 -0
  401. package/project-templates/phaser/.claude/skills/playcraft-style-qa/SKILL.md +270 -0
  402. package/project-templates/phaser/.claude/skills/playcraft-text-rendering/SKILL.md +236 -0
  403. package/project-templates/phaser/.claude/skills/playcraft-vfx-animation/SKILL.md +130 -0
  404. package/project-templates/phaser/.claude/skills/playwright-cli/SKILL.md +390 -0
  405. package/project-templates/phaser/.claude/skills/playwright-cli/references/element-attributes.md +23 -0
  406. package/project-templates/phaser/.claude/skills/playwright-cli/references/playwright-tests.md +39 -0
  407. package/project-templates/phaser/.claude/skills/playwright-cli/references/request-mocking.md +87 -0
  408. package/project-templates/phaser/.claude/skills/playwright-cli/references/running-code.md +240 -0
  409. package/project-templates/phaser/.claude/skills/playwright-cli/references/session-management.md +226 -0
  410. package/project-templates/phaser/.claude/skills/playwright-cli/references/spec-driven-testing.md +308 -0
  411. package/project-templates/phaser/.claude/skills/playwright-cli/references/storage-state.md +275 -0
  412. package/project-templates/phaser/.claude/skills/playwright-cli/references/test-generation.md +134 -0
  413. package/project-templates/phaser/.claude/skills/playwright-cli/references/tracing.md +142 -0
  414. package/project-templates/phaser/.claude/skills/playwright-cli/references/video-recording.md +153 -0
  415. package/project-templates/phaser/.claude/skills/session-analyzer/SKILL.md +386 -0
  416. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +182 -0
  417. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/find-turns.mjs +72 -0
  418. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/heavy-output.mjs +121 -0
  419. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/resolve-session.mjs +103 -0
  420. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +127 -0
  421. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +106 -0
  422. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/time-gaps.mjs +128 -0
  423. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +67 -0
  424. package/project-templates/phaser/.claude/snapshot.mjs +257 -0
  425. package/project-templates/phaser/.cursor/hooks.json +17 -0
  426. package/project-templates/phaser/.cursor/rules/playcraft-orchestrator.mdc +142 -0
  427. package/project-templates/phaser/.cursor/rules/playcraft-subagent-boundary.mdc +29 -0
  428. package/project-templates/phaser/.playcraft-engine +1 -0
  429. package/project-templates/phaser/CHANGELOG.md +105 -0
  430. package/project-templates/phaser/CLAUDE.md +84 -0
  431. package/project-templates/phaser/assets/audio/bgm/.gitkeep +0 -0
  432. package/project-templates/phaser/assets/audio/sfx/.gitkeep +0 -0
  433. package/project-templates/phaser/assets/bundles/.gitkeep +0 -0
  434. package/project-templates/phaser/assets/images/bg/.gitkeep +0 -0
  435. package/project-templates/phaser/assets/images/reference/.gitkeep +0 -0
  436. package/project-templates/phaser/assets/images/storyboard/.gitkeep +0 -0
  437. package/project-templates/phaser/assets/images/tiles/.gitkeep +0 -0
  438. package/project-templates/phaser/assets/images/ui/.gitkeep +0 -0
  439. package/project-templates/phaser/assets/images/vfx/.gitkeep +0 -0
  440. package/project-templates/phaser/assets/models/.gitkeep +0 -0
  441. package/project-templates/phaser/docs/DESIGN.md +65 -0
  442. package/project-templates/phaser/docs/intent-clarifications.md +58 -0
  443. package/project-templates/phaser/docs/pipeline-overview.md +69 -0
  444. package/project-templates/phaser/docs/project-state.md +211 -0
  445. package/project-templates/phaser/docs/scaffold-manifest.md +10 -0
  446. package/project-templates/phaser/docs/team/agent-conduct.md +67 -0
  447. package/project-templates/phaser/docs/team/agent-runtime-matrix.md +80 -0
  448. package/project-templates/phaser/docs/team/atom-tree-format.md +114 -0
  449. package/project-templates/phaser/docs/team/collaboration.md +391 -0
  450. package/project-templates/phaser/docs/team/core-model.md +85 -0
  451. package/project-templates/phaser/docs/team/game-designer-migration-todo.md +8 -0
  452. package/project-templates/phaser/docs/team/harness-legacy-deprecation.md +23 -0
  453. package/project-templates/phaser/docs/team/platform-capabilities.md +18 -0
  454. package/project-templates/phaser/docs/team/workflow-changelog.md +137 -0
  455. package/project-templates/phaser/docs/team/workflow-consistency-checklist.md +240 -0
  456. package/project-templates/phaser/focus-lab/.gitkeep +0 -0
  457. package/project-templates/phaser/game/adapters/phaser.ts +52 -0
  458. package/project-templates/phaser/game/config/.gitkeep +0 -0
  459. package/project-templates/phaser/game/config/game-config.ts +46 -0
  460. package/project-templates/phaser/game/contract/publish.ts +141 -0
  461. package/project-templates/phaser/game/contract/types.ts +34 -0
  462. package/project-templates/phaser/game/gameplay/.gitkeep +0 -0
  463. package/project-templates/phaser/game/index.ts +81 -0
  464. package/project-templates/phaser/game/runtime-contract.ts +28 -0
  465. package/project-templates/phaser/game/scenes/.gitkeep +0 -0
  466. package/project-templates/phaser/globals.d.ts +51 -0
  467. package/project-templates/phaser/index.css +34 -0
  468. package/project-templates/phaser/index.html +18 -0
  469. package/project-templates/phaser/main.ts +9 -0
  470. package/project-templates/phaser/package.json +79 -0
  471. package/project-templates/phaser/skills/README.md +52 -0
  472. package/project-templates/phaser/skills/_shared/agent-prep.md +136 -0
  473. package/project-templates/phaser/skills/_shared/requirements.txt +1 -0
  474. package/project-templates/phaser/skills/_shared/scripts/build-evidence-gate.mjs +87 -0
  475. package/project-templates/phaser/skills/_shared/scripts/context-budget.mjs +215 -0
  476. package/project-templates/phaser/skills/_shared/scripts/create-playable.mjs +140 -0
  477. package/project-templates/phaser/skills/_shared/scripts/dev-start.mjs +151 -0
  478. package/project-templates/phaser/skills/_shared/scripts/disclosure-config.mjs +480 -0
  479. package/project-templates/phaser/skills/_shared/scripts/ensure-project.mjs +532 -0
  480. package/project-templates/phaser/skills/_shared/scripts/gen-placeholder-audio.py +345 -0
  481. package/project-templates/phaser/skills/_shared/scripts/gen-placeholder.py +244 -0
  482. package/project-templates/phaser/skills/_shared/scripts/harness-smoke.mjs +257 -0
  483. package/project-templates/phaser/skills/_shared/scripts/lib/validator-artifacts.mjs +129 -0
  484. package/project-templates/phaser/skills/_shared/scripts/placeholder-pack-sheet.mjs +67 -0
  485. package/project-templates/phaser/skills/_shared/scripts/placeholder_audio_lib.py +142 -0
  486. package/project-templates/phaser/skills/_shared/scripts/placeholder_lib.py +993 -0
  487. package/project-templates/phaser/skills/_shared/scripts/render-project-state.mjs +278 -0
  488. package/project-templates/phaser/skills/_shared/scripts/ring-config.mjs +252 -0
  489. package/project-templates/phaser/skills/_shared/scripts/skills-check.mjs +176 -0
  490. package/project-templates/phaser/skills/_shared/scripts/slice-set.mjs +77 -0
  491. package/project-templates/phaser/skills/_shared/scripts/state-advance-ring.mjs +63 -0
  492. package/project-templates/phaser/skills/_shared/scripts/state-get.mjs +215 -0
  493. package/project-templates/phaser/skills/_shared/scripts/state-handoff.mjs +120 -0
  494. package/project-templates/phaser/skills/_shared/scripts/state-set.mjs +113 -0
  495. package/project-templates/phaser/skills/_shared/scripts/state-store.mjs +893 -0
  496. package/project-templates/phaser/skills/_shared/scripts/sync-template-skills.mjs +112 -0
  497. package/project-templates/phaser/skills/_shared/scripts/verify-asset-code-sync.mjs +265 -0
  498. package/project-templates/phaser/skills/_shared/scripts/verify-atom-conformance.mjs +181 -0
  499. package/project-templates/phaser/skills/_shared/scripts/verify-env.mjs +285 -0
  500. package/project-templates/phaser/skills/_shared/scripts/verify-placeholders.mjs +161 -0
  501. package/project-templates/phaser/skills/_shared/scripts/workflow-doctor.mjs +348 -0
  502. package/project-templates/phaser/skills/playable-animate/SKILL.md +52 -0
  503. package/project-templates/phaser/skills/playable-animate/agents/openai.yaml +4 -0
  504. package/project-templates/phaser/skills/playable-animate/references/board-refresh.md +30 -0
  505. package/project-templates/phaser/skills/playable-animate/references/diagnosis-checklist.md +25 -0
  506. package/project-templates/phaser/skills/playable-animate/references/entrance-sequencing.md +33 -0
  507. package/project-templates/phaser/skills/playable-animate/references/interaction-timing.md +23 -0
  508. package/project-templates/phaser/skills/playable-art/SKILL.md +203 -0
  509. package/project-templates/phaser/skills/playable-art/agents/openai.yaml +4 -0
  510. package/project-templates/phaser/skills/playable-art/references/alpha-postprocess.md +46 -0
  511. package/project-templates/phaser/skills/playable-art/references/cta-art-spec.md +59 -0
  512. package/project-templates/phaser/skills/playable-art/references/layout-proportions.md +30 -0
  513. package/project-templates/phaser/skills/playable-art/references/prompt-patterns.md +83 -0
  514. package/project-templates/phaser/skills/playable-art/references/reference-comparison.md +30 -0
  515. package/project-templates/phaser/skills/playable-art/references/sheet-import-v2.md +91 -0
  516. package/project-templates/phaser/skills/playable-art/scripts/build-atlas.py +332 -0
  517. package/project-templates/phaser/skills/playable-art/scripts/chroma-key.py +50 -0
  518. package/project-templates/phaser/skills/playable-art/scripts/extract-alpha-split.py +65 -0
  519. package/project-templates/phaser/skills/playable-art/scripts/extract-dual-bg-alpha.py +118 -0
  520. package/project-templates/phaser/skills/playable-art/scripts/import-gpt-art-sheets-v2.py +403 -0
  521. package/project-templates/phaser/skills/playable-art/scripts/import-gpt-art-sheets.py +280 -0
  522. package/project-templates/phaser/skills/playable-art/scripts/import-gpt-tile-sheet.py +114 -0
  523. package/project-templates/phaser/skills/playable-art/scripts/import_gpt_art_pipeline.py +321 -0
  524. package/project-templates/phaser/skills/playable-art/scripts/import_gpt_art_sheets_v2_test.py +126 -0
  525. package/project-templates/phaser/skills/playable-art/scripts/placeholder_common.py +98 -0
  526. package/project-templates/phaser/skills/playable-art/scripts/remove-bg.py +91 -0
  527. package/project-templates/phaser/skills/playable-art/scripts/review-processed-assets.py +468 -0
  528. package/project-templates/phaser/skills/playable-art/scripts/review_processed_assets_test.py +116 -0
  529. package/project-templates/phaser/skills/playable-art/scripts/verify-alpha-clean.py +105 -0
  530. package/project-templates/phaser/skills/playable-debug/SKILL.md +116 -0
  531. package/project-templates/phaser/skills/playable-debug/agents/openai.yaml +4 -0
  532. package/project-templates/phaser/skills/playable-debug/references/debug-config.md +40 -0
  533. package/project-templates/phaser/skills/playable-focus/SKILL.md +148 -0
  534. package/project-templates/phaser/skills/playable-focus/scripts/bootstrap-playground.mjs +84 -0
  535. package/project-templates/phaser/skills/playable-focus/scripts/extract-focus-frames.mjs +87 -0
  536. package/project-templates/phaser/skills/playable-focus/scripts/generate-focus-reports.mjs +121 -0
  537. package/project-templates/phaser/skills/playable-focus/scripts/playground.mjs +390 -0
  538. package/project-templates/phaser/skills/playable-focus/templates/focus-lab/frame.html +39 -0
  539. package/project-templates/phaser/skills/playable-focus/templates/focus-lab/harness/clean-stage.js +114 -0
  540. package/project-templates/phaser/skills/playable-focus/templates/focus-lab/harness/frame-lab-scene.js +94 -0
  541. package/project-templates/phaser/skills/playable-focus/templates/focus-lab/harness/particle-lab-scene.js +224 -0
  542. package/project-templates/phaser/skills/playable-focus/templates/focus-lab/model.html +16 -0
  543. package/project-templates/phaser/skills/playable-focus/templates/focus-lab/particle.html +38 -0
  544. package/project-templates/phaser/skills/playable-focus/templates/focus-lab/shatter.html +47 -0
  545. package/project-templates/phaser/skills/playable-fx-decompose/SKILL.md +212 -0
  546. package/project-templates/phaser/skills/playable-fx-decompose/examples.md +89 -0
  547. package/project-templates/phaser/skills/playable-fx-decompose/reference.md +201 -0
  548. package/project-templates/phaser/skills/playable-fx-decompose/requirements.txt +2 -0
  549. package/project-templates/phaser/skills/playable-fx-decompose/scripts/build-blank-tile-crack.py +192 -0
  550. package/project-templates/phaser/skills/playable-fx-decompose/scripts/fix-shard-debris-frames.py +60 -0
  551. package/project-templates/phaser/skills/playable-fx-decompose/scripts/generate-shard-debris.py +276 -0
  552. package/project-templates/phaser/skills/playable-fx-peak/SKILL.md +237 -0
  553. package/project-templates/phaser/skills/playable-fx-peak/requirements.txt +2 -0
  554. package/project-templates/phaser/skills/playable-fx-peak/scripts/fix-frame-edges.py +70 -0
  555. package/project-templates/phaser/skills/playable-fx-peak/scripts/repack-frame-sequence.py +526 -0
  556. package/project-templates/phaser/skills/playable-fx-peak/scripts/validate-frame-anchors.py +130 -0
  557. package/project-templates/phaser/skills/playable-placeholder/SKILL.md +239 -0
  558. package/project-templates/phaser/skills/playable-record/SKILL.md +166 -0
  559. package/project-templates/phaser/skills/playable-record/scripts/lib/build-artifact.mjs +92 -0
  560. package/project-templates/phaser/skills/playable-record/scripts/lib/dev-server.mjs +104 -0
  561. package/project-templates/phaser/skills/playable-record/scripts/lib/record-audio-bridge.js +141 -0
  562. package/project-templates/phaser/skills/playable-record/scripts/lib/slice-structure-verify.mjs +199 -0
  563. package/project-templates/phaser/skills/playable-record/scripts/record-playable.mjs +583 -0
  564. package/project-templates/phaser/skills/playable-record/scripts/verify-build.mjs +185 -0
  565. package/project-templates/phaser/skills/playable-record/scripts/verify-contract.mjs +261 -0
  566. package/project-templates/phaser/skills/playable-record/scripts/verify-firstwin.mjs +398 -0
  567. package/project-templates/phaser/skills/playable-record/scripts/verify-fusion.mjs +320 -0
  568. package/project-templates/phaser/skills/playable-record/scripts/verify-lifecycle.mjs +78 -0
  569. package/project-templates/phaser/skills/playable-record/scripts/verify-slice-artist.mjs +53 -0
  570. package/project-templates/phaser/skills/playable-record/scripts/verify-slice-ta.mjs +53 -0
  571. package/project-templates/phaser/skills/playable-record/scripts/verify-slice.mjs +142 -0
  572. package/project-templates/phaser/skills/playable-record/scripts/verify-visual-ring.mjs +250 -0
  573. package/project-templates/phaser/skills/playable-record/scripts/verify-visual-ring1.mjs +190 -0
  574. package/project-templates/phaser/skills/playable-record/scripts/verify-vlm-video.mjs +227 -0
  575. package/project-templates/phaser/skills/playable-validate/SKILL.md +260 -0
  576. package/project-templates/phaser/skills/playable-validate/references/checklist-detail.md +36 -0
  577. package/project-templates/phaser/skills/playable-validate/references/single-html-bundle.md +76 -0
  578. package/project-templates/phaser/skills/playable-validate/validation-rules.md +200 -0
  579. package/project-templates/phaser/skills/playable-verify-ui/SKILL.md +295 -0
  580. package/project-templates/phaser/skills/playable-verify-ui/examples/asset-manifest.example.json +36 -0
  581. package/project-templates/phaser/skills/playable-verify-ui/examples/mahjong-match.profile.yaml +334 -0
  582. package/project-templates/phaser/skills/playable-verify-ui/profiles/template.profile.yaml +84 -0
  583. package/project-templates/phaser/skills/playable-verify-ui/reference.md +165 -0
  584. package/project-templates/phaser/skills/playable-verify-ui/references/phaser-patterns.md +67 -0
  585. package/project-templates/phaser/skills/playable-verify-ui/references/scaling-decision-tree.md +51 -0
  586. package/project-templates/phaser/skills/playable-verify-ui/requirements.txt +7 -0
  587. package/project-templates/phaser/skills/playable-verify-ui/schema/asset-manifest.schema.json +68 -0
  588. package/project-templates/phaser/skills/playable-verify-ui/schema/playable-ui-profile.schema.json +171 -0
  589. package/project-templates/phaser/skills/playable-verify-ui/scripts/asset_manifest.py +231 -0
  590. package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/__init__.py +1 -0
  591. package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/config_expr.js +218 -0
  592. package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/node_utils.js +51 -0
  593. package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/profile_loader.py +59 -0
  594. package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/report.py +106 -0
  595. package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/runtime_contract.js +116 -0
  596. package/project-templates/phaser/skills/playable-verify-ui/scripts/measure_frame_content.py +77 -0
  597. package/project-templates/phaser/skills/playable-verify-ui/scripts/run_all.py +265 -0
  598. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_asset_sizes.py +77 -0
  599. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_assets_present.py +62 -0
  600. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_atlas_budget.py +84 -0
  601. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_atlas_frames.py +79 -0
  602. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_bg_clean.py +365 -0
  603. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_doc_asset_sync.py +90 -0
  604. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +59 -0
  605. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_game_config_load.js +54 -0
  606. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +64 -0
  607. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_layout_regions.js +110 -0
  608. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_profile_schema.py +37 -0
  609. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_ring1_visual.py +211 -0
  610. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_runtime_contract.js +58 -0
  611. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_script_load_order.js +73 -0
  612. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_slice_plan.py +238 -0
  613. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_slice_plan_test.py +125 -0
  614. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_text_overlap.py +333 -0
  615. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_tile_consistency.py +323 -0
  616. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_vision_evidence.py +894 -0
  617. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_vision_evidence_test.py +122 -0
  618. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_visual.py +422 -0
  619. package/project-templates/phaser/ta-workspace/scripts/.gitkeep +0 -0
  620. package/project-templates/phaser/templates/artist-log.template.md +182 -0
  621. package/project-templates/phaser/templates/atom-tree.template.json +133 -0
  622. package/project-templates/phaser/templates/design-brief.template.md +92 -0
  623. package/project-templates/phaser/templates/design-lens-checklist.reference.md +117 -0
  624. package/project-templates/phaser/templates/design-methodology.md +99 -0
  625. package/project-templates/phaser/templates/design.template.md +50 -0
  626. package/project-templates/phaser/templates/developer-log.template.md +258 -0
  627. package/project-templates/phaser/templates/five-axis-framework.md +194 -0
  628. package/project-templates/phaser/templates/gen-placeholder-audio.template.mjs +63 -0
  629. package/project-templates/phaser/templates/gen-placeholders.template.mjs +81 -0
  630. package/project-templates/phaser/templates/intent-clarifications.template.md +58 -0
  631. package/project-templates/phaser/templates/layout-spec.template.md +198 -0
  632. package/project-templates/phaser/templates/plan.template.md +80 -0
  633. package/project-templates/phaser/templates/project-state.template.md +136 -0
  634. package/project-templates/phaser/templates/review-report.template.md +275 -0
  635. package/project-templates/phaser/templates/style-exploration.template.md +93 -0
  636. package/project-templates/phaser/templates/ta-log.template.md +394 -0
  637. package/project-templates/phaser/tsconfig.json +20 -0
  638. package/project-templates/phaser/vite.config.ts +27 -0
  639. package/project-templates/threejs/.claude/agents/artist.md +101 -0
  640. package/project-templates/threejs/.claude/agents/developer.md +123 -0
  641. package/project-templates/threejs/.claude/agents/game-designer.md +192 -0
  642. package/project-templates/threejs/.claude/agents/refs/README.md +77 -0
  643. package/project-templates/threejs/.claude/agents/refs/artist-art-style-catalog.md +533 -0
  644. package/project-templates/threejs/.claude/agents/refs/artist-color-audio-recipes.md +153 -0
  645. package/project-templates/threejs/.claude/agents/refs/artist-deliverable-spec.md +230 -0
  646. package/project-templates/threejs/.claude/agents/refs/artist-dimension-axis.md +27 -0
  647. package/project-templates/threejs/.claude/agents/refs/artist-handoff-v2-checklist.md +76 -0
  648. package/project-templates/threejs/.claude/agents/refs/artist-legacy-fallback.md +14 -0
  649. package/project-templates/threejs/.claude/agents/refs/artist-master-composite-recipes.md +208 -0
  650. package/project-templates/threejs/.claude/agents/refs/artist-style-exploration-flow.md +55 -0
  651. package/project-templates/threejs/.claude/agents/refs/developer-dev-handoff.md +152 -0
  652. package/project-templates/threejs/.claude/agents/refs/developer-impl-cookbook.md +432 -0
  653. package/project-templates/threejs/.claude/agents/refs/developer-legacy-fallback.md +15 -0
  654. package/project-templates/threejs/.claude/agents/refs/developer-phase1-flow.md +404 -0
  655. package/project-templates/threejs/.claude/agents/refs/framework-5-component-filter.md +252 -0
  656. package/project-templates/threejs/.claude/agents/refs/framework-game-feel-juice.md +266 -0
  657. package/project-templates/threejs/.claude/agents/refs/framework-mda.md +147 -0
  658. package/project-templates/threejs/.claude/agents/refs/game-designer-gameplay-sufficiency.md +123 -0
  659. package/project-templates/threejs/.claude/agents/refs/game-designer-legacy-fallback.md +15 -0
  660. package/project-templates/threejs/.claude/agents/refs/game-designer-workflow-detail.md +69 -0
  661. package/project-templates/threejs/.claude/agents/refs/implementation-plan-guide.md +309 -0
  662. package/project-templates/threejs/.claude/agents/refs/reviewer-convergence-eval.md +350 -0
  663. package/project-templates/threejs/.claude/agents/refs/reviewer-legacy-fallback.md +14 -0
  664. package/project-templates/threejs/.claude/agents/refs/reviewer-six-dimension-eval.md +6 -0
  665. package/project-templates/threejs/.claude/agents/refs/skill-selector-guide.md +465 -0
  666. package/project-templates/threejs/.claude/agents/refs/skill-value-layers.md +145 -0
  667. package/project-templates/threejs/.claude/agents/refs/storyboard-generation-guide.md +110 -0
  668. package/project-templates/threejs/.claude/agents/refs/ta-3d-flip-recipe.md +88 -0
  669. package/project-templates/threejs/.claude/agents/refs/ta-atlas-deliverable-standard.md +67 -0
  670. package/project-templates/threejs/.claude/agents/refs/ta-batch-pipeline-recipes.md +120 -0
  671. package/project-templates/threejs/.claude/agents/refs/ta-image-generation-detail.md +300 -0
  672. package/project-templates/threejs/.claude/agents/refs/ta-image-ops-reference.md +495 -0
  673. package/project-templates/threejs/.claude/agents/refs/ta-pipeline-cookbook.md +1141 -0
  674. package/project-templates/threejs/.claude/agents/refs/ta-tools-reference.md +111 -0
  675. package/project-templates/threejs/.claude/agents/refs/ta-vfx-preset-catalog.md +365 -0
  676. package/project-templates/threejs/.claude/agents/refs/technical-artist-legacy-fallback.md +14 -0
  677. package/project-templates/threejs/.claude/agents/reviewer.md +116 -0
  678. package/project-templates/threejs/.claude/agents/technical-artist.md +100 -0
  679. package/project-templates/threejs/.claude/hooks/README.md +120 -0
  680. package/project-templates/threejs/.claude/hooks/asr-coverage-validate.mjs +381 -0
  681. package/project-templates/threejs/.claude/hooks/block-developer-preflight.mjs +185 -0
  682. package/project-templates/threejs/.claude/hooks/block-ring1-tasklist.mjs +95 -0
  683. package/project-templates/threejs/.claude/hooks/gate-dispatcher.mjs +78 -0
  684. package/project-templates/threejs/.claude/hooks/gate-runner.mjs +557 -0
  685. package/project-templates/threejs/.claude/hooks/gate.mjs +171 -0
  686. package/project-templates/threejs/.claude/hooks/snapshot-milestone.mjs +247 -0
  687. package/project-templates/threejs/.claude/hooks/user-prompt.mjs +194 -0
  688. package/project-templates/threejs/.claude/hooks/validate-atom-tree-selection.mjs +281 -0
  689. package/project-templates/threejs/.claude/hooks/validate-workflow-stop.mjs +1889 -0
  690. package/project-templates/threejs/.claude/settings.json +81 -0
  691. package/project-templates/threejs/.claude/skills/brainstorming/SKILL.md +161 -0
  692. package/project-templates/threejs/.claude/skills/brainstorming/scripts/frame-template.html +270 -0
  693. package/project-templates/threejs/.claude/skills/brainstorming/scripts/helper.js +177 -0
  694. package/project-templates/threejs/.claude/skills/brainstorming/scripts/server.cjs +354 -0
  695. package/project-templates/threejs/.claude/skills/brainstorming/scripts/start-server.sh +148 -0
  696. package/project-templates/threejs/.claude/skills/brainstorming/scripts/stop-server.sh +56 -0
  697. package/project-templates/threejs/.claude/skills/brainstorming/scripts/wait-for-selection.sh +62 -0
  698. package/project-templates/threejs/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  699. package/project-templates/threejs/.claude/skills/brainstorming/visual-companion.md +309 -0
  700. package/project-templates/threejs/.claude/skills/playcraft-ad-psychology/SKILL.md +182 -0
  701. package/project-templates/threejs/.claude/skills/playcraft-art-style-guide/SKILL.md +123 -0
  702. package/project-templates/threejs/.claude/skills/playcraft-asset-state-sheet/SKILL.md +205 -0
  703. package/project-templates/threejs/.claude/skills/playcraft-audio-generation/SKILL.md +280 -0
  704. package/project-templates/threejs/.claude/skills/playcraft-batch-pipeline/SKILL.md +184 -0
  705. package/project-templates/threejs/.claude/skills/playcraft-build-optimizer/SKILL.md +306 -0
  706. package/project-templates/threejs/.claude/skills/playcraft-image-generation/SKILL.md +298 -0
  707. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +123 -0
  708. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +254 -0
  709. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +324 -0
  710. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +97 -0
  711. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +118 -0
  712. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +137 -0
  713. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +397 -0
  714. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +296 -0
  715. package/project-templates/threejs/.claude/skills/playcraft-image-ops/SKILL.md +122 -0
  716. package/project-templates/threejs/.claude/skills/playcraft-image-processing/SKILL.md +219 -0
  717. package/project-templates/threejs/.claude/skills/playcraft-masking/SKILL.md +373 -0
  718. package/project-templates/threejs/.claude/skills/playcraft-playable-optimization/SKILL.md +161 -0
  719. package/project-templates/threejs/.claude/skills/playcraft-research/SKILL.md +215 -0
  720. package/project-templates/threejs/.claude/skills/playcraft-skill-recommender/SKILL.md +331 -0
  721. package/project-templates/threejs/.claude/skills/playcraft-sprite-generation/SKILL.md +423 -0
  722. package/project-templates/threejs/.claude/skills/playcraft-sprite-remix/SKILL.md +158 -0
  723. package/project-templates/threejs/.claude/skills/playcraft-sprite-sheet/SKILL.md +100 -0
  724. package/project-templates/threejs/.claude/skills/playcraft-storyboard/SKILL.md +167 -0
  725. package/project-templates/threejs/.claude/skills/playcraft-style-qa/SKILL.md +270 -0
  726. package/project-templates/threejs/.claude/skills/playcraft-text-rendering/SKILL.md +236 -0
  727. package/project-templates/threejs/.claude/skills/playcraft-vfx-animation/SKILL.md +130 -0
  728. package/project-templates/threejs/.claude/skills/playwright-cli/SKILL.md +390 -0
  729. package/project-templates/threejs/.claude/skills/playwright-cli/references/element-attributes.md +23 -0
  730. package/project-templates/threejs/.claude/skills/playwright-cli/references/playwright-tests.md +39 -0
  731. package/project-templates/threejs/.claude/skills/playwright-cli/references/request-mocking.md +87 -0
  732. package/project-templates/threejs/.claude/skills/playwright-cli/references/running-code.md +240 -0
  733. package/project-templates/threejs/.claude/skills/playwright-cli/references/session-management.md +226 -0
  734. package/project-templates/threejs/.claude/skills/playwright-cli/references/spec-driven-testing.md +308 -0
  735. package/project-templates/threejs/.claude/skills/playwright-cli/references/storage-state.md +275 -0
  736. package/project-templates/threejs/.claude/skills/playwright-cli/references/test-generation.md +134 -0
  737. package/project-templates/threejs/.claude/skills/playwright-cli/references/tracing.md +142 -0
  738. package/project-templates/threejs/.claude/skills/playwright-cli/references/video-recording.md +153 -0
  739. package/project-templates/threejs/.claude/skills/session-analyzer/SKILL.md +386 -0
  740. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +182 -0
  741. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/find-turns.mjs +72 -0
  742. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/heavy-output.mjs +121 -0
  743. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/resolve-session.mjs +103 -0
  744. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +127 -0
  745. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +106 -0
  746. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/time-gaps.mjs +128 -0
  747. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +67 -0
  748. package/project-templates/threejs/.claude/snapshot.mjs +257 -0
  749. package/project-templates/threejs/.cursor/hooks.json +17 -0
  750. package/project-templates/threejs/.cursor/rules/playcraft-orchestrator.mdc +142 -0
  751. package/project-templates/threejs/.cursor/rules/playcraft-subagent-boundary.mdc +29 -0
  752. package/project-templates/threejs/.playcraft-engine +1 -0
  753. package/project-templates/threejs/CHANGELOG.md +105 -0
  754. package/project-templates/threejs/CLAUDE.md +84 -0
  755. package/project-templates/threejs/assets/audio/bgm/.gitkeep +0 -0
  756. package/project-templates/threejs/assets/audio/sfx/.gitkeep +0 -0
  757. package/project-templates/threejs/assets/bundles/.gitkeep +0 -0
  758. package/project-templates/threejs/assets/images/bg/.gitkeep +0 -0
  759. package/project-templates/threejs/assets/images/reference/.gitkeep +0 -0
  760. package/project-templates/threejs/assets/images/storyboard/.gitkeep +0 -0
  761. package/project-templates/threejs/assets/images/tiles/.gitkeep +0 -0
  762. package/project-templates/threejs/assets/images/ui/.gitkeep +0 -0
  763. package/project-templates/threejs/assets/images/vfx/.gitkeep +0 -0
  764. package/project-templates/threejs/assets/models/.gitkeep +0 -0
  765. package/project-templates/threejs/docs/DESIGN.md +65 -0
  766. package/project-templates/threejs/docs/intent-clarifications.md +58 -0
  767. package/project-templates/threejs/docs/pipeline-overview.md +69 -0
  768. package/project-templates/threejs/docs/project-state.md +211 -0
  769. package/project-templates/threejs/docs/scaffold-manifest.md +10 -0
  770. package/project-templates/threejs/docs/team/agent-conduct.md +67 -0
  771. package/project-templates/threejs/docs/team/agent-runtime-matrix.md +80 -0
  772. package/project-templates/threejs/docs/team/atom-tree-format.md +114 -0
  773. package/project-templates/threejs/docs/team/collaboration.md +391 -0
  774. package/project-templates/threejs/docs/team/core-model.md +85 -0
  775. package/project-templates/threejs/docs/team/game-designer-migration-todo.md +8 -0
  776. package/project-templates/threejs/docs/team/harness-legacy-deprecation.md +23 -0
  777. package/project-templates/threejs/docs/team/platform-capabilities.md +18 -0
  778. package/project-templates/threejs/docs/team/workflow-changelog.md +137 -0
  779. package/project-templates/threejs/docs/team/workflow-consistency-checklist.md +240 -0
  780. package/project-templates/threejs/focus-lab/.gitkeep +0 -0
  781. package/project-templates/threejs/game/config/.gitkeep +0 -0
  782. package/project-templates/threejs/game/config/game-config.ts +46 -0
  783. package/project-templates/threejs/game/contract/publish.ts +141 -0
  784. package/project-templates/threejs/game/contract/types.ts +34 -0
  785. package/project-templates/threejs/game/gameplay/.gitkeep +0 -0
  786. package/project-templates/threejs/game/index.ts +76 -0
  787. package/project-templates/threejs/game/runtime-contract.ts +15 -0
  788. package/project-templates/threejs/game/scenes/.gitkeep +0 -0
  789. package/project-templates/threejs/globals.d.ts +51 -0
  790. package/project-templates/threejs/index.css +34 -0
  791. package/project-templates/threejs/index.html +18 -0
  792. package/project-templates/threejs/main.ts +9 -0
  793. package/project-templates/threejs/package.json +79 -0
  794. package/project-templates/threejs/skills/README.md +52 -0
  795. package/project-templates/threejs/skills/_shared/agent-prep.md +136 -0
  796. package/project-templates/threejs/skills/_shared/requirements.txt +1 -0
  797. package/project-templates/threejs/skills/_shared/scripts/build-evidence-gate.mjs +87 -0
  798. package/project-templates/threejs/skills/_shared/scripts/context-budget.mjs +215 -0
  799. package/project-templates/threejs/skills/_shared/scripts/create-playable.mjs +140 -0
  800. package/project-templates/threejs/skills/_shared/scripts/dev-start.mjs +151 -0
  801. package/project-templates/threejs/skills/_shared/scripts/disclosure-config.mjs +480 -0
  802. package/project-templates/threejs/skills/_shared/scripts/ensure-project.mjs +532 -0
  803. package/project-templates/threejs/skills/_shared/scripts/gen-placeholder-audio.py +345 -0
  804. package/project-templates/threejs/skills/_shared/scripts/gen-placeholder.py +244 -0
  805. package/project-templates/threejs/skills/_shared/scripts/harness-smoke.mjs +257 -0
  806. package/project-templates/threejs/skills/_shared/scripts/lib/validator-artifacts.mjs +129 -0
  807. package/project-templates/threejs/skills/_shared/scripts/placeholder-pack-sheet.mjs +67 -0
  808. package/project-templates/threejs/skills/_shared/scripts/placeholder_audio_lib.py +142 -0
  809. package/project-templates/threejs/skills/_shared/scripts/placeholder_lib.py +993 -0
  810. package/project-templates/threejs/skills/_shared/scripts/render-project-state.mjs +278 -0
  811. package/project-templates/threejs/skills/_shared/scripts/ring-config.mjs +252 -0
  812. package/project-templates/threejs/skills/_shared/scripts/skills-check.mjs +176 -0
  813. package/project-templates/threejs/skills/_shared/scripts/slice-set.mjs +77 -0
  814. package/project-templates/threejs/skills/_shared/scripts/state-advance-ring.mjs +63 -0
  815. package/project-templates/threejs/skills/_shared/scripts/state-get.mjs +215 -0
  816. package/project-templates/threejs/skills/_shared/scripts/state-handoff.mjs +120 -0
  817. package/project-templates/threejs/skills/_shared/scripts/state-set.mjs +113 -0
  818. package/project-templates/threejs/skills/_shared/scripts/state-store.mjs +893 -0
  819. package/project-templates/threejs/skills/_shared/scripts/sync-template-skills.mjs +112 -0
  820. package/project-templates/threejs/skills/_shared/scripts/verify-asset-code-sync.mjs +265 -0
  821. package/project-templates/threejs/skills/_shared/scripts/verify-atom-conformance.mjs +181 -0
  822. package/project-templates/threejs/skills/_shared/scripts/verify-env.mjs +285 -0
  823. package/project-templates/threejs/skills/_shared/scripts/verify-placeholders.mjs +161 -0
  824. package/project-templates/threejs/skills/_shared/scripts/workflow-doctor.mjs +348 -0
  825. package/project-templates/threejs/skills/playable-animate/SKILL.md +52 -0
  826. package/project-templates/threejs/skills/playable-animate/agents/openai.yaml +4 -0
  827. package/project-templates/threejs/skills/playable-animate/references/board-refresh.md +30 -0
  828. package/project-templates/threejs/skills/playable-animate/references/diagnosis-checklist.md +25 -0
  829. package/project-templates/threejs/skills/playable-animate/references/entrance-sequencing.md +33 -0
  830. package/project-templates/threejs/skills/playable-animate/references/interaction-timing.md +23 -0
  831. package/project-templates/threejs/skills/playable-art/SKILL.md +203 -0
  832. package/project-templates/threejs/skills/playable-art/agents/openai.yaml +4 -0
  833. package/project-templates/threejs/skills/playable-art/references/alpha-postprocess.md +46 -0
  834. package/project-templates/threejs/skills/playable-art/references/cta-art-spec.md +59 -0
  835. package/project-templates/threejs/skills/playable-art/references/layout-proportions.md +30 -0
  836. package/project-templates/threejs/skills/playable-art/references/prompt-patterns.md +83 -0
  837. package/project-templates/threejs/skills/playable-art/references/reference-comparison.md +30 -0
  838. package/project-templates/threejs/skills/playable-art/references/sheet-import-v2.md +91 -0
  839. package/project-templates/threejs/skills/playable-art/scripts/build-atlas.py +332 -0
  840. package/project-templates/threejs/skills/playable-art/scripts/chroma-key.py +50 -0
  841. package/project-templates/threejs/skills/playable-art/scripts/extract-alpha-split.py +65 -0
  842. package/project-templates/threejs/skills/playable-art/scripts/extract-dual-bg-alpha.py +118 -0
  843. package/project-templates/threejs/skills/playable-art/scripts/import-gpt-art-sheets-v2.py +403 -0
  844. package/project-templates/threejs/skills/playable-art/scripts/import-gpt-art-sheets.py +280 -0
  845. package/project-templates/threejs/skills/playable-art/scripts/import-gpt-tile-sheet.py +114 -0
  846. package/project-templates/threejs/skills/playable-art/scripts/import_gpt_art_pipeline.py +321 -0
  847. package/project-templates/threejs/skills/playable-art/scripts/import_gpt_art_sheets_v2_test.py +126 -0
  848. package/project-templates/threejs/skills/playable-art/scripts/placeholder_common.py +98 -0
  849. package/project-templates/threejs/skills/playable-art/scripts/remove-bg.py +91 -0
  850. package/project-templates/threejs/skills/playable-art/scripts/review-processed-assets.py +468 -0
  851. package/project-templates/threejs/skills/playable-art/scripts/review_processed_assets_test.py +116 -0
  852. package/project-templates/threejs/skills/playable-art/scripts/verify-alpha-clean.py +105 -0
  853. package/project-templates/threejs/skills/playable-debug/SKILL.md +116 -0
  854. package/project-templates/threejs/skills/playable-debug/agents/openai.yaml +4 -0
  855. package/project-templates/threejs/skills/playable-debug/references/debug-config.md +40 -0
  856. package/project-templates/threejs/skills/playable-focus/SKILL.md +148 -0
  857. package/project-templates/threejs/skills/playable-focus/scripts/bootstrap-playground.mjs +84 -0
  858. package/project-templates/threejs/skills/playable-focus/scripts/extract-focus-frames.mjs +87 -0
  859. package/project-templates/threejs/skills/playable-focus/scripts/generate-focus-reports.mjs +121 -0
  860. package/project-templates/threejs/skills/playable-focus/scripts/playground.mjs +390 -0
  861. package/project-templates/threejs/skills/playable-focus/templates/focus-lab/frame.html +39 -0
  862. package/project-templates/threejs/skills/playable-focus/templates/focus-lab/harness/clean-stage.js +114 -0
  863. package/project-templates/threejs/skills/playable-focus/templates/focus-lab/harness/frame-lab-scene.js +94 -0
  864. package/project-templates/threejs/skills/playable-focus/templates/focus-lab/harness/particle-lab-scene.js +224 -0
  865. package/project-templates/threejs/skills/playable-focus/templates/focus-lab/model.html +16 -0
  866. package/project-templates/threejs/skills/playable-focus/templates/focus-lab/particle.html +38 -0
  867. package/project-templates/threejs/skills/playable-focus/templates/focus-lab/shatter.html +47 -0
  868. package/project-templates/threejs/skills/playable-fx-decompose/SKILL.md +212 -0
  869. package/project-templates/threejs/skills/playable-fx-decompose/examples.md +89 -0
  870. package/project-templates/threejs/skills/playable-fx-decompose/reference.md +201 -0
  871. package/project-templates/threejs/skills/playable-fx-decompose/requirements.txt +2 -0
  872. package/project-templates/threejs/skills/playable-fx-decompose/scripts/build-blank-tile-crack.py +192 -0
  873. package/project-templates/threejs/skills/playable-fx-decompose/scripts/fix-shard-debris-frames.py +60 -0
  874. package/project-templates/threejs/skills/playable-fx-decompose/scripts/generate-shard-debris.py +276 -0
  875. package/project-templates/threejs/skills/playable-fx-peak/SKILL.md +237 -0
  876. package/project-templates/threejs/skills/playable-fx-peak/requirements.txt +2 -0
  877. package/project-templates/threejs/skills/playable-fx-peak/scripts/fix-frame-edges.py +70 -0
  878. package/project-templates/threejs/skills/playable-fx-peak/scripts/repack-frame-sequence.py +526 -0
  879. package/project-templates/threejs/skills/playable-fx-peak/scripts/validate-frame-anchors.py +130 -0
  880. package/project-templates/threejs/skills/playable-placeholder/SKILL.md +239 -0
  881. package/project-templates/threejs/skills/playable-record/SKILL.md +166 -0
  882. package/project-templates/threejs/skills/playable-record/scripts/lib/build-artifact.mjs +92 -0
  883. package/project-templates/threejs/skills/playable-record/scripts/lib/dev-server.mjs +104 -0
  884. package/project-templates/threejs/skills/playable-record/scripts/lib/record-audio-bridge.js +141 -0
  885. package/project-templates/threejs/skills/playable-record/scripts/lib/slice-structure-verify.mjs +199 -0
  886. package/project-templates/threejs/skills/playable-record/scripts/record-playable.mjs +583 -0
  887. package/project-templates/threejs/skills/playable-record/scripts/verify-build.mjs +185 -0
  888. package/project-templates/threejs/skills/playable-record/scripts/verify-contract.mjs +261 -0
  889. package/project-templates/threejs/skills/playable-record/scripts/verify-firstwin.mjs +398 -0
  890. package/project-templates/threejs/skills/playable-record/scripts/verify-fusion.mjs +320 -0
  891. package/project-templates/threejs/skills/playable-record/scripts/verify-lifecycle.mjs +78 -0
  892. package/project-templates/threejs/skills/playable-record/scripts/verify-slice-artist.mjs +53 -0
  893. package/project-templates/threejs/skills/playable-record/scripts/verify-slice-ta.mjs +53 -0
  894. package/project-templates/threejs/skills/playable-record/scripts/verify-slice.mjs +142 -0
  895. package/project-templates/threejs/skills/playable-record/scripts/verify-visual-ring.mjs +250 -0
  896. package/project-templates/threejs/skills/playable-record/scripts/verify-visual-ring1.mjs +190 -0
  897. package/project-templates/threejs/skills/playable-record/scripts/verify-vlm-video.mjs +227 -0
  898. package/project-templates/threejs/skills/playable-validate/SKILL.md +260 -0
  899. package/project-templates/threejs/skills/playable-validate/references/checklist-detail.md +36 -0
  900. package/project-templates/threejs/skills/playable-validate/references/single-html-bundle.md +76 -0
  901. package/project-templates/threejs/skills/playable-validate/validation-rules.md +200 -0
  902. package/project-templates/threejs/skills/playable-verify-ui/SKILL.md +295 -0
  903. package/project-templates/threejs/skills/playable-verify-ui/examples/asset-manifest.example.json +36 -0
  904. package/project-templates/threejs/skills/playable-verify-ui/examples/mahjong-match.profile.yaml +334 -0
  905. package/project-templates/threejs/skills/playable-verify-ui/profiles/template.profile.yaml +84 -0
  906. package/project-templates/threejs/skills/playable-verify-ui/reference.md +165 -0
  907. package/project-templates/threejs/skills/playable-verify-ui/references/phaser-patterns.md +67 -0
  908. package/project-templates/threejs/skills/playable-verify-ui/references/scaling-decision-tree.md +51 -0
  909. package/project-templates/threejs/skills/playable-verify-ui/requirements.txt +7 -0
  910. package/project-templates/threejs/skills/playable-verify-ui/schema/asset-manifest.schema.json +68 -0
  911. package/project-templates/threejs/skills/playable-verify-ui/schema/playable-ui-profile.schema.json +171 -0
  912. package/project-templates/threejs/skills/playable-verify-ui/scripts/asset_manifest.py +231 -0
  913. package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/__init__.py +1 -0
  914. package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/config_expr.js +218 -0
  915. package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/node_utils.js +51 -0
  916. package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/profile_loader.py +59 -0
  917. package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/report.py +106 -0
  918. package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/runtime_contract.js +116 -0
  919. package/project-templates/threejs/skills/playable-verify-ui/scripts/measure_frame_content.py +77 -0
  920. package/project-templates/threejs/skills/playable-verify-ui/scripts/run_all.py +265 -0
  921. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_asset_sizes.py +77 -0
  922. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_assets_present.py +62 -0
  923. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_atlas_budget.py +84 -0
  924. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_atlas_frames.py +79 -0
  925. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_bg_clean.py +365 -0
  926. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_doc_asset_sync.py +90 -0
  927. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +59 -0
  928. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_game_config_load.js +54 -0
  929. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +64 -0
  930. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_layout_regions.js +110 -0
  931. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_profile_schema.py +37 -0
  932. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_ring1_visual.py +211 -0
  933. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_runtime_contract.js +58 -0
  934. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_script_load_order.js +73 -0
  935. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_slice_plan.py +238 -0
  936. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_slice_plan_test.py +125 -0
  937. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_text_overlap.py +333 -0
  938. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_tile_consistency.py +323 -0
  939. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_vision_evidence.py +894 -0
  940. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_vision_evidence_test.py +122 -0
  941. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_visual.py +422 -0
  942. package/project-templates/threejs/ta-workspace/scripts/.gitkeep +0 -0
  943. package/project-templates/threejs/templates/artist-log.template.md +182 -0
  944. package/project-templates/threejs/templates/atom-tree.template.json +133 -0
  945. package/project-templates/threejs/templates/design-brief.template.md +92 -0
  946. package/project-templates/threejs/templates/design-lens-checklist.reference.md +117 -0
  947. package/project-templates/threejs/templates/design-methodology.md +99 -0
  948. package/project-templates/threejs/templates/design.template.md +50 -0
  949. package/project-templates/threejs/templates/developer-log.template.md +258 -0
  950. package/project-templates/threejs/templates/five-axis-framework.md +194 -0
  951. package/project-templates/threejs/templates/gen-placeholder-audio.template.mjs +63 -0
  952. package/project-templates/threejs/templates/gen-placeholders.template.mjs +81 -0
  953. package/project-templates/threejs/templates/intent-clarifications.template.md +58 -0
  954. package/project-templates/threejs/templates/layout-spec.template.md +198 -0
  955. package/project-templates/threejs/templates/plan.template.md +80 -0
  956. package/project-templates/threejs/templates/project-state.template.md +136 -0
  957. package/project-templates/threejs/templates/review-report.template.md +275 -0
  958. package/project-templates/threejs/templates/style-exploration.template.md +93 -0
  959. package/project-templates/threejs/templates/ta-log.template.md +394 -0
  960. package/project-templates/threejs/tsconfig.json +20 -0
  961. package/project-templates/threejs/vite.config.ts +27 -0
  962. package/dist/atom-plan/validate-atom-plan.js +0 -298
  963. package/dist/playable/base-builder.js +0 -265
  964. package/dist/playable/builder.js +0 -1462
  965. package/dist/playable/converter.js +0 -150
  966. package/dist/playable/index.js +0 -3
  967. package/dist/playable/platforms/base.js +0 -12
  968. package/dist/playable/platforms/facebook.js +0 -37
  969. package/dist/playable/platforms/index.js +0 -24
  970. package/dist/playable/platforms/snapchat.js +0 -59
  971. package/dist/playable/playable-builder.js +0 -521
  972. package/dist/playable/types.js +0 -1
  973. package/dist/playable/vite/config-builder.js +0 -136
  974. package/dist/playable/vite/platform-configs.js +0 -102
  975. package/dist/playable/vite/plugin-model-compression.js +0 -63
  976. package/dist/playable/vite/plugin-platform.js +0 -65
  977. package/dist/playable/vite/plugin-playcanvas.js +0 -454
  978. package/dist/playable/vite-builder.js +0 -125
  979. package/project-template/.claude/agents/designer.md +0 -124
  980. package/project-template/.claude/agents/pm.md +0 -166
  981. package/project-template/.claude/agents/refs/designer-style-exploration-flow.md +0 -67
  982. package/project-template/.claude/agents/refs/pm-workflow-detail.md +0 -567
  983. package/project-template/.claude/hooks/validate-atom-plan.mjs +0 -224
  984. package/project-template/.claude/settings.local.json +0 -4
  985. package/project-template/.claude/skills/playcraft-workflow/SKILL.md +0 -485
  986. package/project-template/docs/team/atom-plan-format.md +0 -173
  987. package/project-template/templates/atom-plan.template.json +0 -44
  988. package/project-template/templates/atom-plan.template.md +0 -108
  989. /package/dist/{atom-plan → asr}/validate-asr-coverage.js +0 -0
  990. /package/{project-template → engine-packs/phaser}/game/config/.gitkeep +0 -0
  991. /package/{project-template → engine-packs/phaser}/game/gameplay/.gitkeep +0 -0
  992. /package/{project-template → engine-packs/phaser}/game/scenes/.gitkeep +0 -0
  993. /package/{project-template/logs → engine-packs/threejs/game/config}/.gitkeep +0 -0
  994. /package/{project-template/ta-workspace/logs → engine-packs/threejs/game/gameplay}/.gitkeep +0 -0
  995. /package/{project-template/ta-workspace/tmp → engine-packs/threejs/game/scenes}/.gitkeep +0 -0
  996. /package/project-template/.claude/agents/refs/{designer-art-style-catalog.md → artist-art-style-catalog.md} +0 -0
@@ -0,0 +1,993 @@
1
+ """Local placeholder image generation — placehold.co replacement with shape support."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Literal
10
+
11
+ from PIL import Image, ImageDraw, ImageFont
12
+
13
+ ShapeName = Literal[
14
+ "fill",
15
+ "rounded",
16
+ "circle",
17
+ "ellipse",
18
+ "diamond",
19
+ "ring",
20
+ "triangle-up",
21
+ "triangle-down",
22
+ "auto",
23
+ ]
24
+
25
+ CSS_COLORS: dict[str, str] = {
26
+ "transparent": "00000000",
27
+ "black": "000000",
28
+ "white": "ffffff",
29
+ "red": "ff0000",
30
+ "green": "00ff00",
31
+ "blue": "0000ff",
32
+ "orange": "ffa500",
33
+ "yellow": "ffff00",
34
+ "gray": "808080",
35
+ "grey": "808080",
36
+ }
37
+
38
+
39
+ def parse_color(value: str, *, alpha_default: int = 255) -> tuple[int, int, int, int]:
40
+ raw = (value or "").strip().lower()
41
+ if not raw:
42
+ raise ValueError("color value is required")
43
+ if raw in CSS_COLORS:
44
+ raw = CSS_COLORS[raw]
45
+ raw = raw.lstrip("#")
46
+ if raw == "transparent":
47
+ return 0, 0, 0, 0
48
+ if len(raw) == 3:
49
+ raw = "".join(ch * 2 for ch in raw)
50
+ if len(raw) == 8:
51
+ r, g, b, a = (int(raw[i : i + 2], 16) for i in (0, 2, 4, 6))
52
+ return r, g, b, a
53
+ if len(raw) != 6:
54
+ raise ValueError(f"invalid color: {value}")
55
+ r, g, b = (int(raw[i : i + 2], 16) for i in (0, 2, 4))
56
+ return r, g, b, alpha_default
57
+
58
+
59
+ GradientDirection = Literal["vertical", "horizontal", "diag-tl-br", "diag-tr-bl"]
60
+
61
+
62
+ @dataclass(frozen=True)
63
+ class ColorStop:
64
+ color: tuple[int, int, int, int]
65
+ position: float
66
+
67
+
68
+ @dataclass(frozen=True)
69
+ class SolidFill:
70
+ color: tuple[int, int, int, int]
71
+
72
+
73
+ @dataclass(frozen=True)
74
+ class LinearGradientFill:
75
+ stops: tuple[ColorStop, ...]
76
+ direction: GradientDirection = "vertical"
77
+
78
+
79
+ @dataclass(frozen=True)
80
+ class RadialGradientFill:
81
+ stops: tuple[ColorStop, ...]
82
+ center: tuple[float, float] = (0.5, 0.5)
83
+
84
+
85
+ FillSpec = SolidFill | LinearGradientFill | RadialGradientFill
86
+
87
+ _LINEAR_DIRECTIONS: dict[str, GradientDirection] = {
88
+ "vertical": "vertical",
89
+ "v": "vertical",
90
+ "horizontal": "horizontal",
91
+ "h": "horizontal",
92
+ "diag-tl-br": "diag-tl-br",
93
+ "diag-tr-bl": "diag-tr-bl",
94
+ "diagonal": "diag-tl-br",
95
+ }
96
+
97
+
98
+ def is_gradient_fill(value: str) -> bool:
99
+ raw = (value or "").strip().lower()
100
+ return raw.startswith("linear:") or raw.startswith("radial:")
101
+
102
+
103
+ def _split_gradient_stops(raw: str) -> list[str]:
104
+ normalized = raw.replace("->", "→")
105
+ return [part.strip() for part in normalized.split("→") if part.strip()]
106
+
107
+
108
+ def _parse_stop_position(token: str, *, radial: bool) -> float:
109
+ value = token.strip().lower()
110
+ if value in {"center", "c"}:
111
+ return 0.0
112
+ if value in {"edge", "e", "outer"}:
113
+ return 1.0
114
+ if value.endswith("%"):
115
+ return max(0.0, min(1.0, float(value[:-1]) / 100.0))
116
+ pos = float(value)
117
+ if pos > 1.0:
118
+ pos /= 100.0
119
+ return max(0.0, min(1.0, pos))
120
+
121
+
122
+ def _parse_color_stop_part(part: str, *, radial: bool) -> ColorStop:
123
+ if "@" in part:
124
+ color_raw, pos_raw = part.rsplit("@", 1)
125
+ position = _parse_stop_position(pos_raw, radial=radial)
126
+ else:
127
+ color_raw = part
128
+ position = -1.0
129
+ return ColorStop(color=parse_color(color_raw.strip()), position=position)
130
+
131
+
132
+ def _normalize_stops(parts: list[str], *, radial: bool) -> tuple[ColorStop, ...]:
133
+ if len(parts) < 2:
134
+ raise ValueError("gradient requires at least two color stops")
135
+ parsed = [_parse_color_stop_part(part, radial=radial) for part in parts]
136
+ unset = [stop for stop in parsed if stop.position < 0]
137
+ if unset and len(unset) != len(parsed):
138
+ raise ValueError("gradient stops must all specify @position or none")
139
+ if unset:
140
+ if len(parsed) == 2:
141
+ parsed = [
142
+ ColorStop(parsed[0].color, 0.0),
143
+ ColorStop(parsed[1].color, 1.0),
144
+ ]
145
+ else:
146
+ step = 1.0 / (len(parsed) - 1)
147
+ parsed = [
148
+ ColorStop(stop.color, i * step) for i, stop in enumerate(parsed)
149
+ ]
150
+ parsed = sorted(parsed, key=lambda stop: stop.position)
151
+ if parsed[0].position > 0.0:
152
+ parsed = [ColorStop(parsed[0].color, 0.0), *parsed]
153
+ if parsed[-1].position < 1.0:
154
+ parsed = [*parsed, ColorStop(parsed[-1].color, 1.0)]
155
+ return tuple(parsed)
156
+
157
+
158
+ def parse_fill(value: str) -> FillSpec:
159
+ raw = (value or "").strip()
160
+ if not raw:
161
+ raise ValueError("fill value is required")
162
+ lower = raw.lower()
163
+ if lower.startswith("linear:"):
164
+ body = raw.split(":", 1)[1]
165
+ direction: GradientDirection = "vertical"
166
+ if ":" in body:
167
+ stops_raw, dir_raw = body.rsplit(":", 1)
168
+ if dir_raw.strip().lower() in _LINEAR_DIRECTIONS:
169
+ body = stops_raw
170
+ direction = _LINEAR_DIRECTIONS[dir_raw.strip().lower()]
171
+ stops = _normalize_stops(_split_gradient_stops(body), radial=False)
172
+ return LinearGradientFill(stops=stops, direction=direction)
173
+ if lower.startswith("radial:"):
174
+ body = raw.split(":", 1)[1]
175
+ stops = _normalize_stops(_split_gradient_stops(body), radial=True)
176
+ return RadialGradientFill(stops=stops)
177
+ return SolidFill(color=parse_color(raw))
178
+
179
+
180
+ def rgba_to_hex(color: tuple[int, int, int, int], *, with_alpha: bool = False) -> str:
181
+ r, g, b, a = color
182
+ if with_alpha and a != 255:
183
+ return f"{r:02x}{g:02x}{b:02x}{a:02x}"
184
+ return f"{r:02x}{g:02x}{b:02x}"
185
+
186
+
187
+ def describe_fill_short(value: str) -> str:
188
+ if not is_gradient_fill(value):
189
+ return normalize_hex_color(value)
190
+ spec = parse_fill(value)
191
+ if isinstance(spec, LinearGradientFill):
192
+ a = rgba_to_hex(spec.stops[0].color)
193
+ b = rgba_to_hex(spec.stops[-1].color)
194
+ return f"lin #{a}→#{b}"
195
+ if isinstance(spec, RadialGradientFill):
196
+ a = rgba_to_hex(spec.stops[0].color, with_alpha=True)
197
+ b = rgba_to_hex(spec.stops[-1].color, with_alpha=True)
198
+ return f"rad #{a}→#{b}"
199
+ return normalize_hex_color(value)
200
+
201
+
202
+ def fill_to_sidecar_value(value: str) -> str | dict:
203
+ if not is_gradient_fill(value):
204
+ return f"#{normalize_hex_color(value)}"
205
+ spec = parse_fill(value)
206
+ if isinstance(spec, LinearGradientFill):
207
+ return {
208
+ "kind": "linear",
209
+ "direction": spec.direction,
210
+ "stops": [
211
+ {"color": f"#{rgba_to_hex(stop.color, with_alpha=True)}", "at": stop.position}
212
+ for stop in spec.stops
213
+ ],
214
+ }
215
+ if isinstance(spec, RadialGradientFill):
216
+ return {
217
+ "kind": "radial",
218
+ "center": {"x": spec.center[0], "y": spec.center[1]},
219
+ "stops": [
220
+ {"color": f"#{rgba_to_hex(stop.color, with_alpha=True)}", "at": stop.position}
221
+ for stop in spec.stops
222
+ ],
223
+ }
224
+ return f"#{normalize_hex_color(value)}"
225
+
226
+
227
+ def _lerp_channel(a: int, b: int, t: float) -> int:
228
+ return int(round(a + (b - a) * t))
229
+
230
+
231
+ def _sample_gradient(stops: tuple[ColorStop, ...], t: float) -> tuple[int, int, int, int]:
232
+ t = max(0.0, min(1.0, t))
233
+ for index in range(len(stops) - 1):
234
+ left = stops[index]
235
+ right = stops[index + 1]
236
+ if t <= right.position or index == len(stops) - 2:
237
+ span = max(right.position - left.position, 1e-9)
238
+ local = 0.0 if span == 0 else (t - left.position) / span
239
+ local = max(0.0, min(1.0, local))
240
+ return (
241
+ _lerp_channel(left.color[0], right.color[0], local),
242
+ _lerp_channel(left.color[1], right.color[1], local),
243
+ _lerp_channel(left.color[2], right.color[2], local),
244
+ _lerp_channel(left.color[3], right.color[3], local),
245
+ )
246
+ last = stops[-1].color
247
+ return last
248
+
249
+
250
+ def _linear_gradient_image(width: int, height: int, spec: LinearGradientFill) -> Image.Image:
251
+ image = Image.new("RGBA", (width, height))
252
+ pixels = image.load()
253
+ max_x = max(width - 1, 1)
254
+ max_y = max(height - 1, 1)
255
+ for y in range(height):
256
+ for x in range(width):
257
+ if spec.direction == "vertical":
258
+ t = y / max_y
259
+ elif spec.direction == "horizontal":
260
+ t = x / max_x
261
+ elif spec.direction == "diag-tl-br":
262
+ t = (x + y) / (max_x + max_y)
263
+ else:
264
+ t = (x + (max_y - y)) / (max_x + max_y)
265
+ pixels[x, y] = _sample_gradient(spec.stops, t)
266
+ return image
267
+
268
+
269
+ def _radial_gradient_image(width: int, height: int, spec: RadialGradientFill) -> Image.Image:
270
+ image = Image.new("RGBA", (width, height))
271
+ pixels = image.load()
272
+ cx = spec.center[0] * max(width - 1, 1)
273
+ cy = spec.center[1] * max(height - 1, 1)
274
+ max_dist = max(((cx) ** 2 + (cy) ** 2) ** 0.5, ((width - 1 - cx) ** 2 + (cy) ** 2) ** 0.5,
275
+ ((cx) ** 2 + (height - 1 - cy) ** 2) ** 0.5, ((width - 1 - cx) ** 2 + (height - 1 - cy) ** 2) ** 0.5, 1.0)
276
+ for y in range(height):
277
+ for x in range(width):
278
+ dist = ((x - cx) ** 2 + (y - cy) ** 2) ** 0.5
279
+ t = dist / max_dist
280
+ pixels[x, y] = _sample_gradient(spec.stops, t)
281
+ return image
282
+
283
+
284
+ def render_fill_layer(width: int, height: int, spec: FillSpec) -> Image.Image:
285
+ if isinstance(spec, SolidFill):
286
+ return Image.new("RGBA", (width, height), spec.color)
287
+ if isinstance(spec, LinearGradientFill):
288
+ return _linear_gradient_image(width, height, spec)
289
+ return _radial_gradient_image(width, height, spec)
290
+
291
+
292
+ def parse_size(value: str) -> tuple[int, int]:
293
+ raw = (value or "").strip().lower().replace(" ", "")
294
+ if not raw:
295
+ raise ValueError("size is required")
296
+ if "x" in raw:
297
+ w_s, h_s = raw.split("x", 1)
298
+ w, h = int(w_s), int(h_s)
299
+ else:
300
+ w = h = int(raw)
301
+ if w < 1 or h < 1 or w > 4096 or h > 4096:
302
+ raise ValueError(f"size out of range: {value}")
303
+ return w, h
304
+
305
+
306
+ def infer_shape(label: str, width: int, height: int) -> ShapeName:
307
+ name = (label or "").strip().lower()
308
+ if not name:
309
+ return "fill"
310
+ if name.startswith(("bg_", "background_", "scene_")):
311
+ return "fill"
312
+ if name.startswith(("btn_", "button_", "cta_", "tab_")):
313
+ return "rounded"
314
+ if name.startswith(("ring_", "halo_", "aura_")):
315
+ return "ring"
316
+ if name.startswith(("diamond_", "gem_", "crystal_")):
317
+ return "diamond"
318
+ if name.startswith(("arrow_up", "arrow-up", "caret_up")):
319
+ return "triangle-up"
320
+ if name.startswith(("arrow_down", "arrow-down", "caret_down")):
321
+ return "triangle-down"
322
+ if name.startswith(("tile_", "icon_", "token_", "piece_", "coin_")):
323
+ ratio = width / max(height, 1)
324
+ if 0.85 <= ratio <= 1.15:
325
+ return "circle"
326
+ return "rounded"
327
+ if name.startswith(("badge_", "chip_", "pill_")):
328
+ return "rounded"
329
+ if width == height and min(width, height) <= 192:
330
+ return "circle"
331
+ return "fill"
332
+
333
+
334
+ def normalize_hex_color(value: str) -> str:
335
+ raw = (value or "").strip().lower().lstrip("#")
336
+ if raw in CSS_COLORS:
337
+ raw = CSS_COLORS[raw]
338
+ if len(raw) == 3:
339
+ raw = "".join(ch * 2 for ch in raw)
340
+ if len(raw) != 6:
341
+ return raw or "??????"
342
+ return raw
343
+
344
+
345
+ def infer_role(label: str) -> str:
346
+ name = (label or "").strip().lower()
347
+ if not name:
348
+ return "asset · 资产"
349
+ rules: list[tuple[tuple[str, ...], str]] = [
350
+ (("bg_", "background_", "scene_"), "background · 背景"),
351
+ (("btn_", "button_", "cta_", "tab_"), "button · 按钮"),
352
+ (("tile_", "piece_", "token_", "coin_"), "tile · 方块"),
353
+ (("icon_",), "icon · 图标"),
354
+ (("ring_", "halo_", "aura_"), "halo/VFX · 光环"),
355
+ (("gem_", "diamond_", "crystal_"), "gem · 宝石"),
356
+ (("vfx_", "fx_", "particle_"), "VFX · 特效"),
357
+ (("ui_", "hud_", "panel_"), "UI · 界面"),
358
+ (("txt_", "digit_", "num_"), "digit · 数字"),
359
+ (("logo_", "brand_"), "logo · 标识"),
360
+ (("avatar_", "char_", "hero_"), "character · 角色"),
361
+ ]
362
+ for prefixes, role in rules:
363
+ if name.startswith(prefixes):
364
+ return role
365
+ if "bg" in name:
366
+ return "background · 背景"
367
+ if "btn" in name or "cta" in name:
368
+ return "button · 按钮"
369
+ return "asset · 资产"
370
+
371
+
372
+ def role_short(role: str) -> str:
373
+ if " · " in role:
374
+ return role.split(" · ", 1)[0].strip()
375
+ return role
376
+
377
+
378
+ def build_metadata_lines(
379
+ *,
380
+ width: int,
381
+ height: int,
382
+ bg: str,
383
+ fg: str,
384
+ label: str,
385
+ role: str | None = None,
386
+ minimal: bool = False,
387
+ ) -> list[tuple[str, float]]:
388
+ """Return (text, weight) lines; weight 1.0 = title scale."""
389
+ bg_label = describe_fill_short(bg)
390
+ fg_hex = normalize_hex_color(fg)
391
+ resolved_role = (role or infer_role(label)).strip()
392
+ min_side = min(width, height)
393
+ use_minimal = minimal or min_side < 200 or height < 140
394
+
395
+ if use_minimal:
396
+ # Two lines only — largest readable fonts on small contract sizes.
397
+ return [
398
+ (label, 1.0),
399
+ (f"{width}x{height} {bg_label} {role_short(resolved_role)}", 0.62),
400
+ ]
401
+
402
+ return [
403
+ (label, 1.0),
404
+ (f"{width} x {height} px", 0.68),
405
+ (f"bg {bg_label} fg #{fg_hex}", 0.56),
406
+ (resolved_role, 0.6),
407
+ ]
408
+
409
+
410
+ # Text-only overlay supersample — shapes stay 1×; text rasterized sharp then pasted.
411
+ TEXT_SUPERSAMPLE = 4
412
+
413
+
414
+ def _needs_cjk_font(text: str) -> bool:
415
+ return any(ord(ch) > 127 for ch in text)
416
+
417
+
418
+ def load_font(size: int, *, text: str | None = None) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
419
+ use_cjk = _needs_cjk_font(text or "")
420
+ if use_cjk:
421
+ candidates = [
422
+ "/System/Library/Fonts/PingFang.ttc",
423
+ "/System/Library/Fonts/STHeiti Medium.ttc",
424
+ "/System/Library/Fonts/STHeiti Light.ttc",
425
+ "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
426
+ ]
427
+ else:
428
+ candidates = [
429
+ "/System/Library/Fonts/Supplemental/Arial Bold.ttf",
430
+ "/Library/Fonts/Arial Bold.ttf",
431
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
432
+ "/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf",
433
+ "/System/Library/Fonts/Helvetica.ttc",
434
+ ]
435
+ for path in candidates:
436
+ try:
437
+ return ImageFont.truetype(path, size, layout_engine=ImageFont.Layout.BASIC)
438
+ except (OSError, ValueError):
439
+ try:
440
+ return ImageFont.truetype(path, size)
441
+ except OSError:
442
+ continue
443
+ return ImageFont.load_default()
444
+
445
+
446
+ def _text_size(draw: ImageDraw.ImageDraw, text: str, font: ImageFont.ImageFont) -> tuple[int, int]:
447
+ bbox = draw.textbbox((0, 0), text, font=font)
448
+ return bbox[2] - bbox[0], bbox[3] - bbox[1]
449
+
450
+
451
+ def _fit_minimal_subtitle(
452
+ draw: ImageDraw.ImageDraw,
453
+ inner_w: int,
454
+ *,
455
+ width: int,
456
+ height: int,
457
+ bg_hex: str,
458
+ role: str,
459
+ font_size: int,
460
+ ) -> str:
461
+ short = role_short(role)
462
+ for text in (
463
+ f"{width}x{height} {bg_hex} {short}",
464
+ f"{width}x{height} {bg_hex}",
465
+ f"{width}x{height}",
466
+ ):
467
+ font = load_font(font_size, text=text)
468
+ tw, _ = _text_size(draw, text, font)
469
+ if tw <= inner_w:
470
+ return text
471
+ return f"{width}x{height}"
472
+
473
+
474
+ def _stroke_for_fill(fill: tuple[int, int, int] | tuple[int, int, int, int]) -> tuple[int, int, int]:
475
+ r, g, b = fill[0], fill[1], fill[2]
476
+ luminance = 0.299 * r + 0.587 * g + 0.114 * b
477
+ return (0, 0, 0) if luminance > 140 else (255, 255, 255)
478
+
479
+
480
+ def _draw_metadata_direct(
481
+ draw: ImageDraw.ImageDraw,
482
+ box: tuple[int, int, int, int],
483
+ lines: list[tuple[str, float]],
484
+ fill: tuple[int, int, int] | tuple[int, int, int, int],
485
+ *,
486
+ meta: dict | None = None,
487
+ ) -> None:
488
+ x0, y0, x1, y1 = box
489
+ inner_w = max(1, x1 - x0)
490
+ inner_h = max(1, y1 - y0)
491
+ min_side = min(inner_w, inner_h)
492
+ line_count = max(1, len(lines))
493
+ two_line = line_count == 2
494
+
495
+ if two_line:
496
+ title = lines[0][0]
497
+ title_size = max(14, min(int(inner_h * 0.44), int(inner_w / max(len(title), 3))))
498
+ sub_size = max(10, min(int(inner_h * 0.24), title_size - 3))
499
+ gap = max(2, title_size // 7)
500
+ if meta:
501
+ subtitle = _fit_minimal_subtitle(
502
+ draw,
503
+ inner_w,
504
+ width=int(meta["width"]),
505
+ height=int(meta["height"]),
506
+ bg_hex=str(meta["bg_hex"]),
507
+ role=str(meta["role"]),
508
+ font_size=sub_size,
509
+ )
510
+ else:
511
+ subtitle = lines[1][0]
512
+ title_font = load_font(title_size, text=title)
513
+ sub_font = load_font(sub_size, text=subtitle)
514
+ th0 = _text_size(draw, title, title_font)[1]
515
+ th1 = _text_size(draw, subtitle, sub_font)[1]
516
+ total_h = th0 + gap + th1
517
+ y = int(y0 + (inner_h - total_h) / 2)
518
+ cx = int(x0 + inner_w / 2)
519
+ draw.text((cx, int(y + th0 / 2)), title, fill=fill, font=title_font, anchor="mm")
520
+ draw.text((cx, int(y + th0 + gap + th1 / 2)), subtitle, fill=fill, font=sub_font, anchor="mm")
521
+ return
522
+
523
+ base_size = max(14, min(56, min(inner_h // (line_count + 1), min_side // 3)))
524
+ for start in range(base_size, 12, -1):
525
+ sized: list[tuple[str, ImageFont.ImageFont, int, int]] = []
526
+ total_h = 0
527
+ gap = max(3, start // 5)
528
+ ok = True
529
+ for text, weight in lines:
530
+ size = max(12, int(start * weight))
531
+ font = load_font(size, text=text)
532
+ tw, th = _text_size(draw, text, font)
533
+ if tw > inner_w:
534
+ ok = False
535
+ break
536
+ sized.append((text, font, tw, th, size))
537
+ total_h += th + gap
538
+ if not ok:
539
+ continue
540
+ if sized:
541
+ total_h -= gap
542
+ if total_h <= inner_h:
543
+ y = int(y0 + (inner_h - total_h) / 2)
544
+ for text, font, tw, th, size in sized:
545
+ cx = int(x0 + inner_w / 2)
546
+ cy = int(y + th / 2)
547
+ kwargs: dict = {"fill": fill, "font": font, "anchor": "mm"}
548
+ if size >= 14:
549
+ kwargs["stroke_width"] = 1
550
+ kwargs["stroke_fill"] = _stroke_for_fill(fill)
551
+ draw.text((cx, cy), text, **kwargs)
552
+ y += th + gap
553
+ return
554
+
555
+ font = load_font(max(12, min_side // 5), text=lines[0][0])
556
+ cx = int(x0 + inner_w / 2)
557
+ cy = int(y0 + inner_h / 2)
558
+ draw.text(
559
+ (cx, cy),
560
+ lines[0][0],
561
+ fill=fill,
562
+ font=font,
563
+ anchor="mm",
564
+ )
565
+
566
+
567
+ def draw_metadata_stack(
568
+ image: Image.Image,
569
+ box: tuple[int, int, int, int],
570
+ lines: list[tuple[str, float]],
571
+ fill: tuple[int, int, int] | tuple[int, int, int, int],
572
+ *,
573
+ supersample: int = TEXT_SUPERSAMPLE,
574
+ meta: dict | None = None,
575
+ ) -> None:
576
+ x0, y0, x1, y1 = box
577
+ bw = max(1, x1 - x0)
578
+ bh = max(1, y1 - y0)
579
+ scale = max(1, int(supersample))
580
+
581
+ if scale == 1:
582
+ draw = ImageDraw.Draw(image)
583
+ _draw_metadata_direct(draw, box, lines, fill, meta=meta)
584
+ return
585
+
586
+ overlay = Image.new("RGBA", (bw * scale, bh * scale), (0, 0, 0, 0))
587
+ o_draw = ImageDraw.Draw(overlay)
588
+ scaled_meta = None
589
+ if meta:
590
+ scaled_meta = {**meta, "width": int(meta["width"]), "height": int(meta["height"])}
591
+ _draw_metadata_direct(o_draw, (0, 0, bw * scale, bh * scale), lines, fill, meta=scaled_meta)
592
+ overlay = overlay.resize((bw, bh), Image.Resampling.BOX)
593
+ image.paste(overlay, (x0, y0), overlay)
594
+
595
+
596
+ def fit_font(
597
+ draw: ImageDraw.ImageDraw,
598
+ text: str,
599
+ max_w: int,
600
+ max_h: int,
601
+ *,
602
+ start_size: int = 48,
603
+ min_size: int = 12,
604
+ ) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
605
+ for size in range(start_size, min_size - 1, -2):
606
+ font = load_font(size)
607
+ bbox = draw.textbbox((0, 0), text, font=font)
608
+ tw = bbox[2] - bbox[0]
609
+ th = bbox[3] - bbox[1]
610
+ if tw <= max_w and th <= max_h:
611
+ return font
612
+ return load_font(min_size)
613
+
614
+
615
+ def draw_centered_text(
616
+ draw: ImageDraw.ImageDraw,
617
+ box: tuple[int, int, int, int],
618
+ text: str,
619
+ fill: tuple[int, int, int] | tuple[int, int, int, int],
620
+ *,
621
+ font: ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None,
622
+ ) -> None:
623
+ x0, y0, x1, y1 = box
624
+ inner_w = x1 - x0
625
+ inner_h = y1 - y0
626
+ font = font or fit_font(draw, text, inner_w, inner_h)
627
+ bbox = draw.textbbox((0, 0), text, font=font)
628
+ tw = bbox[2] - bbox[0]
629
+ th = bbox[3] - bbox[1]
630
+ x = x0 + (inner_w - tw) / 2 - bbox[0]
631
+ y = y0 + (inner_h - th) / 2 - bbox[1]
632
+ draw.text((x, y), text, fill=fill, font=font)
633
+
634
+
635
+ def _content_box(width: int, height: int, *, pad_ratio: float = 0.08) -> tuple[int, int, int, int]:
636
+ pad = max(4, int(min(width, height) * pad_ratio))
637
+ return pad, pad, width - pad, height - pad
638
+
639
+
640
+ def _build_shape_mask(
641
+ width: int,
642
+ height: int,
643
+ shape: ShapeName,
644
+ box: tuple[int, int, int, int],
645
+ ) -> Image.Image:
646
+ mask = Image.new("L", (width, height), 0)
647
+ draw = ImageDraw.Draw(mask)
648
+ x0, y0, x1, y1 = box
649
+ w = x1 - x0
650
+ h = y1 - y0
651
+ cx = (x0 + x1) // 2
652
+ cy = (y0 + y1) // 2
653
+
654
+ if shape == "fill":
655
+ draw.rectangle(box, fill=255)
656
+ return mask
657
+
658
+ if shape == "rounded":
659
+ radius = max(8, min(w, h) // 6)
660
+ draw.rounded_rectangle(box, radius=radius, fill=255)
661
+ return mask
662
+
663
+ if shape == "circle":
664
+ side = min(w, h)
665
+ left = cx - side // 2
666
+ top = cy - side // 2
667
+ draw.ellipse((left, top, left + side, top + side), fill=255)
668
+ return mask
669
+
670
+ if shape == "ellipse":
671
+ draw.ellipse(box, fill=255)
672
+ return mask
673
+
674
+ if shape == "diamond":
675
+ points = [(cx, y0), (x1, cy), (cx, y1), (x0, cy)]
676
+ draw.polygon(points, fill=255)
677
+ return mask
678
+
679
+ if shape == "ring":
680
+ side = min(w, h)
681
+ left = cx - side // 2
682
+ top = cy - side // 2
683
+ outer = (left, top, left + side, top + side)
684
+ thickness = max(3, side // 8)
685
+ draw.ellipse(outer, outline=255, width=thickness)
686
+ return mask
687
+
688
+ if shape == "triangle-up":
689
+ points = [(cx, y0), (x1, y1), (x0, y1)]
690
+ draw.polygon(points, fill=255)
691
+ return mask
692
+
693
+ if shape == "triangle-down":
694
+ points = [(x0, y0), (x1, y0), (cx, y1)]
695
+ draw.polygon(points, fill=255)
696
+ return mask
697
+
698
+ draw.rectangle(box, fill=255)
699
+ return mask
700
+
701
+
702
+ def _composite_shape_fill(
703
+ image: Image.Image,
704
+ *,
705
+ shape: ShapeName,
706
+ box: tuple[int, int, int, int],
707
+ fill_spec: FillSpec,
708
+ ) -> tuple[int, int, int, int]:
709
+ x0, y0, x1, y1 = box
710
+ bw = max(1, x1 - x0)
711
+ bh = max(1, y1 - y0)
712
+ local_box = (0, 0, bw, bh)
713
+ fill_layer = render_fill_layer(bw, bh, fill_spec)
714
+ mask = _build_shape_mask(bw, bh, shape, local_box)
715
+ shaped = Image.new("RGBA", (bw, bh), (0, 0, 0, 0))
716
+ shaped.paste(fill_layer, (0, 0), mask)
717
+ image.paste(shaped, (x0, y0), shaped)
718
+ return box
719
+
720
+
721
+ def _draw_shape(
722
+ draw: ImageDraw.ImageDraw,
723
+ shape: ShapeName,
724
+ box: tuple[int, int, int, int],
725
+ fill: tuple[int, int, int, int],
726
+ ) -> tuple[int, int, int, int]:
727
+ x0, y0, x1, y1 = box
728
+ w = x1 - x0
729
+ h = y1 - y0
730
+ cx = (x0 + x1) // 2
731
+ cy = (y0 + y1) // 2
732
+
733
+ if shape == "fill":
734
+ draw.rectangle(box, fill=fill)
735
+ return box
736
+
737
+ if shape == "rounded":
738
+ radius = max(8, min(w, h) // 6)
739
+ draw.rounded_rectangle(box, radius=radius, fill=fill)
740
+ return box
741
+
742
+ if shape == "circle":
743
+ side = min(w, h)
744
+ left = cx - side // 2
745
+ top = cy - side // 2
746
+ circle_box = (left, top, left + side, top + side)
747
+ draw.ellipse(circle_box, fill=fill)
748
+ return circle_box
749
+
750
+ if shape == "ellipse":
751
+ draw.ellipse(box, fill=fill)
752
+ return box
753
+
754
+ if shape == "diamond":
755
+ points = [(cx, y0), (x1, cy), (cx, y1), (x0, cy)]
756
+ draw.polygon(points, fill=fill)
757
+ return box
758
+
759
+ if shape == "ring":
760
+ side = min(w, h)
761
+ left = cx - side // 2
762
+ top = cy - side // 2
763
+ outer = (left, top, left + side, top + side)
764
+ thickness = max(3, side // 8)
765
+ draw.ellipse(outer, outline=fill, width=thickness)
766
+ return outer
767
+
768
+ if shape == "triangle-up":
769
+ points = [(cx, y0), (x1, y1), (x0, y1)]
770
+ draw.polygon(points, fill=fill)
771
+ return box
772
+
773
+ if shape == "triangle-down":
774
+ points = [(x0, y0), (x1, y0), (cx, y1)]
775
+ draw.polygon(points, fill=fill)
776
+ return box
777
+
778
+ draw.rectangle(box, fill=fill)
779
+ return box
780
+
781
+
782
+ def shape_uses_transparent_canvas(shape: ShapeName) -> bool:
783
+ """Non-fill shapes leave pixels outside the silhouette transparent."""
784
+ return shape != "fill"
785
+
786
+
787
+ def resolve_transparent_bg(shape: ShapeName, transparent_bg: bool | None) -> bool:
788
+ if transparent_bg is None:
789
+ return shape_uses_transparent_canvas(shape)
790
+ return transparent_bg
791
+
792
+
793
+ def _render_placeholder_raster(
794
+ *,
795
+ width: int,
796
+ height: int,
797
+ bg: str,
798
+ fg: str,
799
+ label: str,
800
+ role: str | None,
801
+ resolved_shape: ShapeName,
802
+ transparent_bg: bool,
803
+ display_width: int | None = None,
804
+ display_height: int | None = None,
805
+ minimal: bool = False,
806
+ ) -> Image.Image:
807
+ fill_spec = parse_fill(bg)
808
+ bg_label = describe_fill_short(bg)
809
+
810
+ if transparent_bg:
811
+ image = Image.new("RGBA", (width, height), (0, 0, 0, 0))
812
+ elif resolved_shape == "fill" and isinstance(fill_spec, SolidFill):
813
+ image = Image.new("RGBA", (width, height), fill_spec.color)
814
+ else:
815
+ image = Image.new("RGBA", (width, height), (0, 0, 0, 0))
816
+
817
+ if resolved_shape == "fill":
818
+ text_box = (0, 0, width, height)
819
+ if is_gradient_fill(bg) or transparent_bg:
820
+ _composite_shape_fill(image, shape="fill", box=text_box, fill_spec=fill_spec)
821
+ else:
822
+ box = _content_box(width, height)
823
+ text_box = _composite_shape_fill(image, shape=resolved_shape, box=box, fill_spec=fill_spec)
824
+
825
+ fg_rgba = parse_color(fg)
826
+ text_fill: tuple[int, int, int] | tuple[int, int, int, int] = (
827
+ fg_rgba[:3] if fg_rgba[3] == 255 else fg_rgba
828
+ )
829
+ metadata_lines = build_metadata_lines(
830
+ width=display_width or width,
831
+ height=display_height or height,
832
+ bg=bg,
833
+ fg=fg,
834
+ label=label,
835
+ role=role,
836
+ minimal=minimal,
837
+ )
838
+ logical_w = display_width or width
839
+ logical_h = display_height or height
840
+ text_meta = {
841
+ "width": logical_w,
842
+ "height": logical_h,
843
+ "bg_hex": bg_label,
844
+ "role": (role or infer_role(label)),
845
+ }
846
+ draw_metadata_stack(image, text_box, metadata_lines, text_fill, meta=text_meta)
847
+ return image
848
+
849
+
850
+ def render_placeholder(
851
+ *,
852
+ width: int,
853
+ height: int,
854
+ bg: str,
855
+ fg: str,
856
+ text: str | None = None,
857
+ role: str | None = None,
858
+ shape: ShapeName = "fill",
859
+ transparent_bg: bool | None = None,
860
+ minimal: bool = False,
861
+ ) -> Image.Image:
862
+ label = (text or f"{width}x{height}").strip()
863
+ resolved_shape = infer_shape(label, width, height) if shape == "auto" else shape
864
+ return _render_placeholder_raster(
865
+ width=width,
866
+ height=height,
867
+ bg=bg,
868
+ fg=fg,
869
+ label=label,
870
+ role=role,
871
+ resolved_shape=resolved_shape,
872
+ transparent_bg=resolve_transparent_bg(resolved_shape, transparent_bg),
873
+ minimal=minimal,
874
+ )
875
+
876
+
877
+ DEFAULT_IMAGE_FORMAT = "webp"
878
+ SIDECAR_VERSION = 1
879
+
880
+
881
+ def normalize_output_path(path: str | Path) -> Path:
882
+ """Default to .webp when the output path has no extension."""
883
+ out = Path(path)
884
+ if not out.suffix:
885
+ return out.with_suffix(f".{DEFAULT_IMAGE_FORMAT}")
886
+ return out
887
+
888
+
889
+ def sidecar_path_for(image_path: str | Path) -> Path:
890
+ return Path(image_path).with_suffix(".json")
891
+
892
+
893
+ def build_sidecar_payload(
894
+ *,
895
+ image_path: str | Path,
896
+ label: str,
897
+ width: int,
898
+ height: int,
899
+ bg: str,
900
+ fg: str,
901
+ shape: str,
902
+ role: str | None,
903
+ transparent: bool,
904
+ retina: int = 1,
905
+ logical_width: int | None = None,
906
+ logical_height: int | None = None,
907
+ ) -> dict:
908
+ image_path = Path(image_path)
909
+ resolved_role = (role or infer_role(label)).strip()
910
+ lw = logical_width or width
911
+ lh = logical_height or height
912
+ return {
913
+ "kind": "playcraft.placeholder",
914
+ "version": SIDECAR_VERSION,
915
+ "label": label,
916
+ "role": resolved_role,
917
+ "purpose": role_short(resolved_role),
918
+ "size": {"width": lw, "height": lh},
919
+ "outputSize": {"width": width, "height": height},
920
+ "retina": retina,
921
+ "colors": {
922
+ "bg": fill_to_sidecar_value(bg),
923
+ "fg": f"#{normalize_hex_color(fg)}",
924
+ },
925
+ "shape": shape,
926
+ "transparent": transparent,
927
+ "format": output_format_from_path(image_path),
928
+ "image": image_path.name,
929
+ "note": "Develop-stage placeholder. TA replaces image at the same contract path; code unchanged.",
930
+ }
931
+
932
+
933
+ def write_placeholder_sidecar(image_path: str | Path, payload: dict) -> Path:
934
+ sidecar = sidecar_path_for(image_path)
935
+ sidecar.parent.mkdir(parents=True, exist_ok=True)
936
+ sidecar.write_text(f"{json.dumps(payload, ensure_ascii=False, indent=2)}\n", encoding="utf-8")
937
+ return sidecar
938
+
939
+
940
+ def output_format_from_path(path: str | Path) -> str:
941
+ suffix = Path(path).suffix.lower().lstrip(".")
942
+ if suffix in {"jpg", "jpeg", "png", "webp", "gif"}:
943
+ return suffix
944
+ return DEFAULT_IMAGE_FORMAT
945
+
946
+
947
+ def save_placeholder(image: Image.Image, path: str | Path, *, quality: int = 95) -> None:
948
+ out = Path(path)
949
+ out.parent.mkdir(parents=True, exist_ok=True)
950
+ fmt = output_format_from_path(out)
951
+
952
+ if fmt in {"jpg", "jpeg"}:
953
+ save_image = image
954
+ if save_image.mode == "RGBA":
955
+ background = Image.new("RGB", save_image.size, parse_color("ffffff")[:3])
956
+ background.paste(save_image, mask=save_image.split()[3])
957
+ save_image = background
958
+ save_image.save(out, format="JPEG", quality=quality)
959
+ return
960
+
961
+ if fmt == "webp":
962
+ image.save(out, format="WEBP", quality=quality, method=6)
963
+ return
964
+
965
+ if fmt == "gif":
966
+ if image.mode == "RGBA":
967
+ image.save(out, format="GIF", transparency=0)
968
+ else:
969
+ image.save(out, format="GIF")
970
+ return
971
+
972
+ image.save(out, format="PNG")
973
+
974
+
975
+ def normalize_batch_entry(entry: dict) -> dict:
976
+ label = entry.get("label") or entry.get("text") or Path(str(entry.get("path", ""))).stem
977
+ size = entry.get("size") or f"{entry.get('width', 128)}x{entry.get('height', 128)}"
978
+ return {
979
+ "path": str(normalize_output_path(str(entry["path"]))),
980
+ "size": str(size),
981
+ "bg": str(entry.get("bg", "1a1a2e")),
982
+ "fg": str(entry.get("fg", "cccccc")),
983
+ "label": str(label),
984
+ "role": (str(entry["role"]).strip() or None) if entry.get("role") is not None else None,
985
+ "shape": str(entry.get("shape", "auto")),
986
+ "retina": int(entry.get("retina", 1)),
987
+ "minimal": bool(entry.get("minimal", False)),
988
+ "transparent": entry.get("transparent"),
989
+ }
990
+
991
+
992
+ def slug_label(label: str) -> str:
993
+ return re.sub(r"[^a-zA-Z0-9_+-]+", "_", label).strip("_") or "asset"