@playcraft/skills 0.0.2

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 (479) hide show
  1. package/README.md +333 -0
  2. package/dist/src/index.d.ts +3 -0
  3. package/dist/src/index.d.ts.map +1 -0
  4. package/dist/src/index.js +2 -0
  5. package/dist/src/index.js.map +1 -0
  6. package/dist/src/skills.loader.d.ts +31 -0
  7. package/dist/src/skills.loader.d.ts.map +1 -0
  8. package/dist/src/skills.loader.js +200 -0
  9. package/dist/src/skills.loader.js.map +1 -0
  10. package/dist/src/skills.types.d.ts +44 -0
  11. package/dist/src/skills.types.d.ts.map +1 -0
  12. package/dist/src/skills.types.js +9 -0
  13. package/dist/src/skills.types.js.map +1 -0
  14. package/package.json +35 -0
  15. package/skills/2048_core.aigameplay/SKILL.md +123 -0
  16. package/skills/2048_core.aigameplay/manifest.json +159 -0
  17. package/skills/2048_core.aigameplay/ref/pgs-schema.json +194 -0
  18. package/skills/2048_core.aigameplay/ref/reducer.template.ts +189 -0
  19. package/skills/SKILL.md +46 -0
  20. package/skills/ad_compliance_rules.aiconfig/SKILL.md +85 -0
  21. package/skills/ad_compliance_rules.aiconfig/manifest.json +60 -0
  22. package/skills/agent_remix_orchestrator.aicomponent/SKILL.md +257 -0
  23. package/skills/agent_remix_orchestrator.aicomponent/manifest.json +12 -0
  24. package/skills/app_metadata.aiconfig/SKILL.md +53 -0
  25. package/skills/app_metadata.aiconfig/manifest.json +32 -0
  26. package/skills/appear_sfx.aiaudio/SKILL.md +59 -0
  27. package/skills/appear_sfx.aiaudio/manifest.json +45 -0
  28. package/skills/appear_sfx.aiaudio/ref/appear.mp3 +0 -0
  29. package/skills/arrow_move_error_sfx.aiaudio/SKILL.md +59 -0
  30. package/skills/arrow_move_error_sfx.aiaudio/manifest.json +45 -0
  31. package/skills/arrow_move_error_sfx.aiaudio/ref/arrow_move_error.mp3 +0 -0
  32. package/skills/arrow_path_data_format.aicomponent/SKILL.md +152 -0
  33. package/skills/arrow_path_data_format.aicomponent/manifest.json +57 -0
  34. package/skills/arrow_path_data_format.aicomponent/ref/LevelParser.ts +188 -0
  35. package/skills/arrow_path_data_format.aicomponent/ref/LevelTypes.ts +131 -0
  36. package/skills/arrow_pick_match_rules.aigameplay/SKILL.md +117 -0
  37. package/skills/arrow_pick_match_rules.aigameplay/manifest.json +105 -0
  38. package/skills/arrow_pick_match_rules.aigameplay/ref/pgs-schema.json +180 -0
  39. package/skills/asset_pipeline_scripts.aicomponent/SKILL.md +117 -0
  40. package/skills/asset_pipeline_scripts.aicomponent/manifest.json +17 -0
  41. package/skills/asset_pipeline_scripts.aicomponent/ref/gen-sprite.sh +105 -0
  42. package/skills/ball_sort.aigameplay/SKILL.md +109 -0
  43. package/skills/ball_sort.aigameplay/manifest.json +155 -0
  44. package/skills/ball_sort.aigameplay/ref/pgs-schema.json +192 -0
  45. package/skills/ball_sort.aigameplay/ref/reducer.template.ts +156 -0
  46. package/skills/basketball_shot.aigameplay/SKILL.md +94 -0
  47. package/skills/basketball_shot.aigameplay/manifest.json +150 -0
  48. package/skills/basketball_shot.aigameplay/ref/pgs-schema.json +229 -0
  49. package/skills/basketball_shot.aigameplay/ref/reducer.template.ts +189 -0
  50. package/skills/bg_music.aiaudio/SKILL.md +61 -0
  51. package/skills/bg_music.aiaudio/manifest.json +48 -0
  52. package/skills/bg_music.aiaudio/ref/bg.mp3 +0 -0
  53. package/skills/big_watermelon.aigameplay/SKILL.md +94 -0
  54. package/skills/big_watermelon.aigameplay/manifest.json +155 -0
  55. package/skills/big_watermelon.aigameplay/ref/pgs-schema.json +191 -0
  56. package/skills/big_watermelon.aigameplay/ref/reducer.template.ts +202 -0
  57. package/skills/block_puzzle.aigameplay/SKILL.md +121 -0
  58. package/skills/block_puzzle.aigameplay/manifest.json +154 -0
  59. package/skills/block_puzzle.aigameplay/ref/pgs-schema.json +170 -0
  60. package/skills/block_puzzle.aigameplay/ref/reducer.template.ts +182 -0
  61. package/skills/board_entity_sprite.aiimage/SKILL.md +255 -0
  62. package/skills/board_entity_sprite.aiimage/manifest.json +153 -0
  63. package/skills/board_entity_sprite.aiimage/ref/ProceduralCar.ts +357 -0
  64. package/skills/board_entity_sprite.aiimage/ref/car.png +0 -0
  65. package/skills/board_entity_sprite.aiimage/ref/car.webp +0 -0
  66. package/skills/bottom_ui_bar.aicomponent/SKILL.md +50 -0
  67. package/skills/bottom_ui_bar.aicomponent/manifest.json +31 -0
  68. package/skills/bottom_ui_bar.aicomponent/ref/GameBottomUI.ts +27 -0
  69. package/skills/bubble_shooter.aigameplay/SKILL.md +129 -0
  70. package/skills/bubble_shooter.aigameplay/manifest.json +181 -0
  71. package/skills/bubble_shooter.aigameplay/ref/pgs-schema.json +226 -0
  72. package/skills/bubble_shooter.aigameplay/ref/reducer.template.ts +228 -0
  73. package/skills/button_click_sfx.aiaudio/SKILL.md +59 -0
  74. package/skills/button_click_sfx.aiaudio/manifest.json +45 -0
  75. package/skills/button_click_sfx.aiaudio/ref/button_click.mp3 +0 -0
  76. package/skills/calm_piano.aiaudio/SKILL.md +68 -0
  77. package/skills/calm_piano.aiaudio/manifest.json +50 -0
  78. package/skills/camera_controller_3d.aicomponent/SKILL.md +100 -0
  79. package/skills/camera_controller_3d.aicomponent/manifest.json +52 -0
  80. package/skills/camera_controller_3d.aicomponent/ref/CameraController.ts +199 -0
  81. package/skills/camera_controller_3d.aicomponent/ref/OrbitControlsAdapter.ts +152 -0
  82. package/skills/candy_tile.aiimage/SKILL.md +62 -0
  83. package/skills/candy_tile.aiimage/manifest.json +49 -0
  84. package/skills/car_parking.aigameplay/SKILL.md +105 -0
  85. package/skills/car_parking.aigameplay/manifest.json +164 -0
  86. package/skills/car_parking.aigameplay/ref/pgs-schema.json +163 -0
  87. package/skills/car_parking.aigameplay/ref/reducer.template.ts +166 -0
  88. package/skills/castle.aiimage/SKILL.md +64 -0
  89. package/skills/castle.aiimage/manifest.json +48 -0
  90. package/skills/cheerful_ukulele.aiaudio/SKILL.md +72 -0
  91. package/skills/cheerful_ukulele.aiaudio/manifest.json +50 -0
  92. package/skills/click_sfx.aiaudio/SKILL.md +59 -0
  93. package/skills/click_sfx.aiaudio/manifest.json +45 -0
  94. package/skills/click_sfx.aiaudio/ref/click.mp3 +0 -0
  95. package/skills/combo_display.aicomponent/SKILL.md +45 -0
  96. package/skills/combo_display.aicomponent/manifest.json +31 -0
  97. package/skills/combo_display.aicomponent/ref/ComboManager.ts +362 -0
  98. package/skills/combo_display.aicomponent/ref/ComboSmallUI.ts +232 -0
  99. package/skills/combo_praise_text.aicomponent/SKILL.md +56 -0
  100. package/skills/combo_praise_text.aicomponent/manifest.json +34 -0
  101. package/skills/combo_praise_text.aicomponent/ref/ComboManager.ts +362 -0
  102. package/skills/countdown_timer.aicomponent/SKILL.md +44 -0
  103. package/skills/countdown_timer.aicomponent/manifest.json +35 -0
  104. package/skills/countdown_timer.aicomponent/ref/CountdownDisplay.ts +213 -0
  105. package/skills/cta_platform_config.aiconfig/SKILL.md +72 -0
  106. package/skills/cta_platform_config.aiconfig/manifest.json +56 -0
  107. package/skills/debug_overlay.aicomponent/SKILL.md +75 -0
  108. package/skills/debug_overlay.aicomponent/manifest.json +17 -0
  109. package/skills/debug_overlay.aicomponent/ref/DebugOverlay.ts +144 -0
  110. package/skills/desert.aiimage/SKILL.md +63 -0
  111. package/skills/desert.aiimage/manifest.json +49 -0
  112. package/skills/difficulty_curve_designer.aiconfig/SKILL.md +68 -0
  113. package/skills/difficulty_curve_designer.aiconfig/manifest.json +36 -0
  114. package/skills/digit_renderer.aicomponent/SKILL.md +43 -0
  115. package/skills/digit_renderer.aicomponent/manifest.json +25 -0
  116. package/skills/digit_renderer.aicomponent/ref/DigitRenderer.ts +237 -0
  117. package/skills/download_button.aicomponent/SKILL.md +44 -0
  118. package/skills/download_button.aicomponent/manifest.json +25 -0
  119. package/skills/download_button.aicomponent/ref/DownloadButton.ts +137 -0
  120. package/skills/draw_line_puzzle.aigameplay/SKILL.md +84 -0
  121. package/skills/draw_line_puzzle.aigameplay/manifest.json +152 -0
  122. package/skills/draw_line_puzzle.aigameplay/ref/pgs-schema.json +189 -0
  123. package/skills/draw_line_puzzle.aigameplay/ref/reducer.template.ts +194 -0
  124. package/skills/easy_to_hard.aiconfig/SKILL.md +52 -0
  125. package/skills/easy_to_hard.aiconfig/manifest.json +33 -0
  126. package/skills/eight_ball_pool.aigameplay/SKILL.md +104 -0
  127. package/skills/eight_ball_pool.aigameplay/manifest.json +152 -0
  128. package/skills/eight_ball_pool.aigameplay/ref/pgs-schema.json +205 -0
  129. package/skills/eight_ball_pool.aigameplay/ref/reducer.template.ts +198 -0
  130. package/skills/energetic_electronic.aiaudio/SKILL.md +69 -0
  131. package/skills/energetic_electronic.aiaudio/manifest.json +51 -0
  132. package/skills/fail_sfx.aiaudio/SKILL.md +59 -0
  133. package/skills/fail_sfx.aiaudio/manifest.json +44 -0
  134. package/skills/fail_sfx.aiaudio/ref/fail.mp3 +0 -0
  135. package/skills/figer_icon.aiimage/SKILL.md +58 -0
  136. package/skills/figer_icon.aiimage/manifest.json +48 -0
  137. package/skills/figer_icon.aiimage/ref/figer.png +0 -0
  138. package/skills/figer_icon.aiimage/ref/figer.webp +0 -0
  139. package/skills/forest.aiimage/SKILL.md +74 -0
  140. package/skills/forest.aiimage/manifest.json +48 -0
  141. package/skills/fruit_tile.aiimage/SKILL.md +68 -0
  142. package/skills/fruit_tile.aiimage/manifest.json +48 -0
  143. package/skills/game_over_panel.aicomponent/SKILL.md +47 -0
  144. package/skills/game_over_panel.aicomponent/manifest.json +61 -0
  145. package/skills/game_over_panel.aicomponent/ref/GameOverPanel.ts +74 -0
  146. package/skills/game_scene.aicomponent/SKILL.md +57 -0
  147. package/skills/game_scene.aicomponent/manifest.json +36 -0
  148. package/skills/game_scene.aicomponent/ref/Game.ts +748 -0
  149. package/skills/gameplay_balance_check.aivalidator/SKILL.md +69 -0
  150. package/skills/gameplay_balance_check.aivalidator/manifest.json +21 -0
  151. package/skills/gameplay_unit_test.aivalidator/SKILL.md +89 -0
  152. package/skills/gameplay_unit_test.aivalidator/manifest.json +17 -0
  153. package/skills/gameplay_unit_test.aivalidator/ref/gameplay.test.ts +202 -0
  154. package/skills/gameplay_unit_test.aivalidator/ref/vitest.config.ts +13 -0
  155. package/skills/grid_board_layout.aicomponent/SKILL.md +85 -0
  156. package/skills/grid_board_layout.aicomponent/manifest.json +69 -0
  157. package/skills/grid_board_layout.aicomponent/ref/BoardLayout.ts +416 -0
  158. package/skills/grid_board_layout.aicomponent/ref/BoardLayout3D.ts +125 -0
  159. package/skills/grid_board_layout.aicomponent/ref/BoardLayoutMath.ts +85 -0
  160. package/skills/grid_board_layout.aicomponent/ref/BoardRenderer3D.ts +298 -0
  161. package/skills/heart_lives.aicomponent/SKILL.md +42 -0
  162. package/skills/heart_lives.aicomponent/manifest.json +25 -0
  163. package/skills/heart_lives.aicomponent/ref/HeartDisplay.ts +96 -0
  164. package/skills/idle-breathe.aicomponent/SKILL.md +27 -0
  165. package/skills/idle-breathe.aicomponent/manifest.json +20 -0
  166. package/skills/idle-breathe.aicomponent/ref/phaser.js +25 -0
  167. package/skills/idle-breathe.aicomponent/ref/playcanvas.js +29 -0
  168. package/skills/jewel_tile.aiimage/SKILL.md +63 -0
  169. package/skills/jewel_tile.aiimage/manifest.json +49 -0
  170. package/skills/knife_hit.aigameplay/SKILL.md +103 -0
  171. package/skills/knife_hit.aigameplay/manifest.json +151 -0
  172. package/skills/knife_hit.aigameplay/ref/pgs-schema.json +202 -0
  173. package/skills/knife_hit.aigameplay/ref/reducer.template.ts +196 -0
  174. package/skills/left_right_parkour.aigameplay/SKILL.md +120 -0
  175. package/skills/left_right_parkour.aigameplay/manifest.json +165 -0
  176. package/skills/left_right_parkour.aigameplay/ref/pgs-schema.json +200 -0
  177. package/skills/left_right_parkour.aigameplay/ref/reducer.template.ts +194 -0
  178. package/skills/level_data_pack.aiconfig/SKILL.md +64 -0
  179. package/skills/level_data_pack.aiconfig/manifest.json +37 -0
  180. package/skills/level_data_validator.aivalidator/SKILL.md +85 -0
  181. package/skills/level_data_validator.aivalidator/manifest.json +17 -0
  182. package/skills/level_data_validator.aivalidator/ref/LevelValidator.ts +113 -0
  183. package/skills/level_lifecycle.aicomponent/SKILL.md +81 -0
  184. package/skills/level_lifecycle.aicomponent/manifest.json +87 -0
  185. package/skills/level_lifecycle.aicomponent/ref/LevelLifecycle.ts +235 -0
  186. package/skills/level_solvability_validator.aivalidator/SKILL.md +74 -0
  187. package/skills/level_solvability_validator.aivalidator/manifest.json +23 -0
  188. package/skills/level_solvability_validator.aivalidator/ref/LevelValidator.ts +186 -0
  189. package/skills/level_state.aicomponent/SKILL.md +55 -0
  190. package/skills/level_state.aicomponent/manifest.json +34 -0
  191. package/skills/level_state.aicomponent/ref/LevelDataManager.ts +26 -0
  192. package/skills/level_state.aicomponent/ref/LevelManager.ts +26 -0
  193. package/skills/loading_screen.aicomponent/SKILL.md +57 -0
  194. package/skills/loading_screen.aicomponent/manifest.json +43 -0
  195. package/skills/loading_screen.aicomponent/ref/LoadingUI.ts +339 -0
  196. package/skills/lose_result_panel.aiimage/SKILL.md +58 -0
  197. package/skills/lose_result_panel.aiimage/manifest.json +47 -0
  198. package/skills/lose_result_panel.aiimage/ref/lose.png +0 -0
  199. package/skills/lose_result_panel.aiimage/ref/lose.webp +0 -0
  200. package/skills/lose_sfx.aiaudio/SKILL.md +59 -0
  201. package/skills/lose_sfx.aiaudio/manifest.json +45 -0
  202. package/skills/lose_sfx.aiaudio/ref/lose.mp3 +0 -0
  203. package/skills/match-pop.aicomponent/SKILL.md +29 -0
  204. package/skills/match-pop.aicomponent/manifest.json +19 -0
  205. package/skills/match-pop.aicomponent/ref/phaser.js +30 -0
  206. package/skills/match-pop.aicomponent/ref/playcanvas.js +22 -0
  207. package/skills/match3_core.aigameplay/SKILL.md +125 -0
  208. package/skills/match3_core.aigameplay/manifest.json +107 -0
  209. package/skills/match3_core.aigameplay/ref/pgs-schema.json +154 -0
  210. package/skills/match3_core.aigameplay/ref/reducer.template.ts +191 -0
  211. package/skills/match_engine.aicomponent/SKILL.md +84 -0
  212. package/skills/match_engine.aicomponent/manifest.json +23 -0
  213. package/skills/match_engine.aicomponent/ref/MatchEngine.ts +75 -0
  214. package/skills/memory_match.aigameplay/SKILL.md +111 -0
  215. package/skills/memory_match.aigameplay/manifest.json +164 -0
  216. package/skills/memory_match.aigameplay/ref/pgs-schema.json +190 -0
  217. package/skills/memory_match.aigameplay/ref/reducer.template.ts +162 -0
  218. package/skills/merge_core.aigameplay/SKILL.md +112 -0
  219. package/skills/merge_core.aigameplay/manifest.json +154 -0
  220. package/skills/merge_core.aigameplay/ref/pgs-schema.json +202 -0
  221. package/skills/merge_core.aigameplay/ref/reducer.template.ts +179 -0
  222. package/skills/mistake_sfx.aiaudio/SKILL.md +59 -0
  223. package/skills/mistake_sfx.aiaudio/manifest.json +45 -0
  224. package/skills/mistake_sfx.aiaudio/ref/mistake.mp3 +0 -0
  225. package/skills/nomove_hint_overlay.aiimage/SKILL.md +58 -0
  226. package/skills/nomove_hint_overlay.aiimage/manifest.json +48 -0
  227. package/skills/nomove_hint_overlay.aiimage/ref/nomove_hint.png +0 -0
  228. package/skills/nomove_hint_overlay.aiimage/ref/nomove_hint.webp +0 -0
  229. package/skills/normal_dot.aiimage/SKILL.md +58 -0
  230. package/skills/normal_dot.aiimage/manifest.json +46 -0
  231. package/skills/normal_dot.aiimage/ref/normal_dot.png +0 -0
  232. package/skills/normal_dot.aiimage/ref/normal_dot.webp +0 -0
  233. package/skills/ocean.aiimage/SKILL.md +71 -0
  234. package/skills/ocean.aiimage/manifest.json +49 -0
  235. package/skills/particle-confetti.aicomponent/SKILL.md +32 -0
  236. package/skills/particle-confetti.aicomponent/manifest.json +21 -0
  237. package/skills/particle-confetti.aicomponent/ref/html5-spritesheet.js +17 -0
  238. package/skills/particle-confetti.aicomponent/ref/phaser.js +38 -0
  239. package/skills/particle-explosion.aicomponent/SKILL.md +37 -0
  240. package/skills/particle-explosion.aicomponent/manifest.json +24 -0
  241. package/skills/particle-explosion.aicomponent/ref/html5-spritesheet.js +40 -0
  242. package/skills/particle-explosion.aicomponent/ref/phaser.js +46 -0
  243. package/skills/particle-explosion.aicomponent/ref/playcanvas.js +33 -0
  244. package/skills/particle-trail.aicomponent/SKILL.md +25 -0
  245. package/skills/particle-trail.aicomponent/manifest.json +22 -0
  246. package/skills/particle-trail.aicomponent/ref/phaser.js +31 -0
  247. package/skills/path_animation.aicomponent/SKILL.md +82 -0
  248. package/skills/path_animation.aicomponent/manifest.json +77 -0
  249. package/skills/path_animation.aicomponent/ref/AnimationManager.ts +694 -0
  250. package/skills/path_animation.aicomponent/ref/AnimationManager3D.ts +317 -0
  251. package/skills/path_elimination_rules.aigameplay/SKILL.md +88 -0
  252. package/skills/path_elimination_rules.aigameplay/manifest.json +119 -0
  253. package/skills/path_elimination_rules.aigameplay/ref/pgs-schema.json +227 -0
  254. package/skills/path_input_handler.aicomponent/SKILL.md +87 -0
  255. package/skills/path_input_handler.aicomponent/manifest.json +97 -0
  256. package/skills/path_input_handler.aicomponent/ref/InputHandler.ts +714 -0
  257. package/skills/path_input_handler.aicomponent/ref/InputHandler3D.ts +204 -0
  258. package/skills/path_renderer.aicomponent/SKILL.md +101 -0
  259. package/skills/path_renderer.aicomponent/manifest.json +69 -0
  260. package/skills/path_renderer.aicomponent/ref/PathRenderer.ts +509 -0
  261. package/skills/path_renderer.aicomponent/ref/PathRenderer3D.ts +176 -0
  262. package/skills/path_renderer.aicomponent/ref/PathRenderers.ts +640 -0
  263. package/skills/phaser.aicomponent/SKILL.md +315 -0
  264. package/skills/phaser.aicomponent/manifest.json +156 -0
  265. package/skills/phaser.aicomponent/ref/BootScene.ts +29 -0
  266. package/skills/phaser.aicomponent/ref/GameConfig.ts +29 -0
  267. package/skills/phaser.aicomponent/ref/GameScene.ts +88 -0
  268. package/skills/phaser.aicomponent/ref/PreloaderScene.ts +78 -0
  269. package/skills/phaser.aicomponent/ref/SceneKeys.ts +7 -0
  270. package/skills/phaser.aicomponent/ref/SoundUtils.ts +21 -0
  271. package/skills/phaser.aicomponent/ref/UiLayout.ts +74 -0
  272. package/skills/phaser.aicomponent/ref/globals.d.ts +52 -0
  273. package/skills/phaser.aicomponent/ref/index.css +40 -0
  274. package/skills/phaser.aicomponent/ref/index.html +14 -0
  275. package/skills/phaser.aicomponent/ref/index.ts +48 -0
  276. package/skills/phaser.aicomponent/ref/main.ts +16 -0
  277. package/skills/phaser.aicomponent/ref/package.json +22 -0
  278. package/skills/phaser.aicomponent/ref/tsconfig.json +25 -0
  279. package/skills/phaser.aicomponent/ref/webpack.config.js +57 -0
  280. package/skills/phaser_scene_lifecycle.aicomponent/SKILL.md +63 -0
  281. package/skills/phaser_scene_lifecycle.aicomponent/manifest.json +33 -0
  282. package/skills/pick_and_match_rules.aigameplay/SKILL.md +113 -0
  283. package/skills/pick_and_match_rules.aigameplay/manifest.json +121 -0
  284. package/skills/pick_and_match_rules.aigameplay/ref/pgs-schema.json +354 -0
  285. package/skills/pin_pull.aigameplay/SKILL.md +110 -0
  286. package/skills/pin_pull.aigameplay/manifest.json +164 -0
  287. package/skills/pin_pull.aigameplay/ref/pgs-schema.json +201 -0
  288. package/skills/pin_pull.aigameplay/ref/reducer.template.ts +216 -0
  289. package/skills/playable_app_logo.aiimage/SKILL.md +58 -0
  290. package/skills/playable_app_logo.aiimage/manifest.json +47 -0
  291. package/skills/playable_app_logo.aiimage/ref/logo.png +0 -0
  292. package/skills/playable_app_logo.aiimage/ref/logo.webp +0 -0
  293. package/skills/playable_end_screen_layout.aicomponent/SKILL.md +75 -0
  294. package/skills/playable_end_screen_layout.aicomponent/manifest.json +24 -0
  295. package/skills/playable_guidance_layer.aicomponent/SKILL.md +89 -0
  296. package/skills/playable_guidance_layer.aicomponent/manifest.json +24 -0
  297. package/skills/playable_hud_layout.aicomponent/SKILL.md +96 -0
  298. package/skills/playable_hud_layout.aicomponent/manifest.json +24 -0
  299. package/skills/playable_scripts_build.aicomponent/SKILL.md +69 -0
  300. package/skills/playable_scripts_build.aicomponent/manifest.json +53 -0
  301. package/skills/playable_scripts_build.aicomponent/ref/builds.config.js +257 -0
  302. package/skills/playcraft-3d-flip-sprite/SKILL.md +336 -0
  303. package/skills/playcraft-3d-flip-sprite/renderer/flatten_glb.mjs +62 -0
  304. package/skills/playcraft-3d-flip-sprite/renderer/render.mjs +325 -0
  305. package/skills/playcraft-3d-flip-sprite/renderer/render_single.mjs +138 -0
  306. package/skills/playcraft-asset-management/SKILL.md +73 -0
  307. package/skills/playcraft-audio-generation/SKILL.md +126 -0
  308. package/skills/playcraft-build/SKILL.md +44 -0
  309. package/skills/playcraft-code-editor/SKILL.md +71 -0
  310. package/skills/playcraft-create-remix/SKILL.md +62 -0
  311. package/skills/playcraft-deploy/SKILL.md +59 -0
  312. package/skills/playcraft-image-generation/SKILL.md +148 -0
  313. package/skills/playcraft-image-processing/SKILL.md +216 -0
  314. package/skills/playcraft-platform-intro/SKILL.md +41 -0
  315. package/skills/playcraft-prefab/SKILL.md +98 -0
  316. package/skills/playcraft-project-management/SKILL.md +57 -0
  317. package/skills/playcraft-remix-workflow/SKILL.md +119 -0
  318. package/skills/playcraft-remix-workflow/references/xplatform.schema.json5 +31 -0
  319. package/skills/playcraft-save/SKILL.md +46 -0
  320. package/skills/playcraft-skill-recommender/SKILL.md +152 -0
  321. package/skills/playcraft-sprite-generation/SKILL.md +534 -0
  322. package/skills/playcraft-sprite-remix/SKILL.md +155 -0
  323. package/skills/playcraft-sprite-sheet/SKILL.md +97 -0
  324. package/skills/playtest_report.aivalidator/SKILL.md +103 -0
  325. package/skills/playtest_report.aivalidator/manifest.json +21 -0
  326. package/skills/preloader_scene.aicomponent/SKILL.md +58 -0
  327. package/skills/preloader_scene.aicomponent/manifest.json +43 -0
  328. package/skills/preloader_scene.aicomponent/ref/Preloader.ts +339 -0
  329. package/skills/progress_bar.aicomponent/SKILL.md +38 -0
  330. package/skills/progress_bar.aicomponent/manifest.json +25 -0
  331. package/skills/progress_bar.aicomponent/ref/ProgressDisplay.ts +140 -0
  332. package/skills/references/xplatform.schema.json5 +31 -0
  333. package/skills/responsive_2d_layout.aicomponent/SKILL.md +161 -0
  334. package/skills/responsive_2d_layout.aicomponent/manifest.json +43 -0
  335. package/skills/responsive_2d_layout.aicomponent/ref/UiLayout.ts +56 -0
  336. package/skills/run_context_state.aicomponent/SKILL.md +54 -0
  337. package/skills/run_context_state.aicomponent/manifest.json +40 -0
  338. package/skills/run_context_state.aicomponent/ref/RunContextRuntime.ts +30 -0
  339. package/skills/score-fly.aicomponent/SKILL.md +31 -0
  340. package/skills/score-fly.aicomponent/manifest.json +21 -0
  341. package/skills/score-fly.aicomponent/ref/phaser.js +26 -0
  342. package/skills/score-fly.aicomponent/ref/playcanvas.js +28 -0
  343. package/skills/score_goal.aiconfig/SKILL.md +46 -0
  344. package/skills/score_goal.aiconfig/manifest.json +33 -0
  345. package/skills/screen-flash.aicomponent/SKILL.md +30 -0
  346. package/skills/screen-flash.aicomponent/manifest.json +20 -0
  347. package/skills/screen-flash.aicomponent/ref/phaser.js +48 -0
  348. package/skills/screen-shake.aicomponent/SKILL.md +30 -0
  349. package/skills/screen-shake.aicomponent/manifest.json +19 -0
  350. package/skills/screen-shake.aicomponent/ref/phaser.js +23 -0
  351. package/skills/screen-shake.aicomponent/ref/playcanvas.js +44 -0
  352. package/skills/screw_puzzle.aigameplay/SKILL.md +116 -0
  353. package/skills/screw_puzzle.aigameplay/manifest.json +164 -0
  354. package/skills/screw_puzzle.aigameplay/ref/pgs-schema.json +235 -0
  355. package/skills/screw_puzzle.aigameplay/ref/reducer.template.ts +213 -0
  356. package/skills/settings_state.aicomponent/SKILL.md +61 -0
  357. package/skills/settings_state.aicomponent/manifest.json +33 -0
  358. package/skills/settings_state.aicomponent/ref/SettingsManager.ts +130 -0
  359. package/skills/slide_out_to_tray_animation.aicomponent/SKILL.md +64 -0
  360. package/skills/slide_out_to_tray_animation.aicomponent/manifest.json +58 -0
  361. package/skills/slide_out_to_tray_animation.aicomponent/ref/AnimationManager.ts +164 -0
  362. package/skills/slide_out_to_tray_animation.aicomponent/ref/AnimationManager3D.ts +198 -0
  363. package/skills/slide_out_to_tray_animation.aicomponent/ref/BezierUtils.ts +59 -0
  364. package/skills/slot_machine.aigameplay/SKILL.md +119 -0
  365. package/skills/slot_machine.aigameplay/manifest.json +166 -0
  366. package/skills/slot_machine.aigameplay/ref/pgs-schema.json +212 -0
  367. package/skills/slot_machine.aigameplay/ref/reducer.template.ts +212 -0
  368. package/skills/sniper_shot.aigameplay/SKILL.md +84 -0
  369. package/skills/sniper_shot.aigameplay/manifest.json +153 -0
  370. package/skills/sniper_shot.aigameplay/ref/pgs-schema.json +211 -0
  371. package/skills/sniper_shot.aigameplay/ref/reducer.template.ts +211 -0
  372. package/skills/solitaire.aigameplay/SKILL.md +101 -0
  373. package/skills/solitaire.aigameplay/manifest.json +151 -0
  374. package/skills/solitaire.aigameplay/ref/pgs-schema.json +200 -0
  375. package/skills/solitaire.aigameplay/ref/reducer.template.ts +176 -0
  376. package/skills/sound-effects/SKILL.md +46 -0
  377. package/skills/sound_utils.aicomponent/SKILL.md +52 -0
  378. package/skills/sound_utils.aicomponent/manifest.json +30 -0
  379. package/skills/sound_utils.aicomponent/ref/SoundUtils.ts +29 -0
  380. package/skills/sprite-animation-2d.aicomponent/SKILL.md +45 -0
  381. package/skills/sprite-animation-2d.aicomponent/manifest.json +28 -0
  382. package/skills/sprite-animation-2d.aicomponent/ref/html5.js +83 -0
  383. package/skills/sprite-animation-2d.aicomponent/ref/phaser.js +38 -0
  384. package/skills/sprite-animation-2d.aicomponent/ref/playcanvas.js +67 -0
  385. package/skills/sprite_entity_renderer.aicomponent/SKILL.md +148 -0
  386. package/skills/sprite_entity_renderer.aicomponent/manifest.json +17 -0
  387. package/skills/sprite_entity_renderer.aicomponent/ref/SpriteEntityRenderer.ts +126 -0
  388. package/skills/success_sfx.aiaudio/SKILL.md +59 -0
  389. package/skills/success_sfx.aiaudio/manifest.json +45 -0
  390. package/skills/success_sfx.aiaudio/ref/success.mp3 +0 -0
  391. package/skills/tap_blast.aigameplay/SKILL.md +116 -0
  392. package/skills/tap_blast.aigameplay/manifest.json +153 -0
  393. package/skills/tap_blast.aigameplay/ref/pgs-schema.json +208 -0
  394. package/skills/tap_blast.aigameplay/ref/reducer.template.ts +202 -0
  395. package/skills/theme_state.aicomponent/SKILL.md +53 -0
  396. package/skills/theme_state.aicomponent/manifest.json +33 -0
  397. package/skills/theme_state.aicomponent/ref/ThemeManager.ts +174 -0
  398. package/skills/theme_switcher_build.aicomponent/SKILL.md +62 -0
  399. package/skills/theme_switcher_build.aicomponent/manifest.json +38 -0
  400. package/skills/theme_switcher_build.aicomponent/ref/theme-index.ts +8 -0
  401. package/skills/theme_variant.aiconfig/SKILL.md +78 -0
  402. package/skills/theme_variant.aiconfig/manifest.json +39 -0
  403. package/skills/theme_variant.aiconfig/ref/ExampleTheme/index.ts +16 -0
  404. package/skills/theme_variant.aiconfig/ref/ExampleTheme/levelEasy.json +141 -0
  405. package/skills/threejs.aicomponent/SKILL.md +422 -0
  406. package/skills/threejs.aicomponent/manifest.json +166 -0
  407. package/skills/threejs.aicomponent/ref/AssetLoader.ts +158 -0
  408. package/skills/threejs.aicomponent/ref/BaseScene.ts +88 -0
  409. package/skills/threejs.aicomponent/ref/GameConfig.ts +47 -0
  410. package/skills/threejs.aicomponent/ref/MainScene.ts +170 -0
  411. package/skills/threejs.aicomponent/ref/PreloaderScene.ts +93 -0
  412. package/skills/threejs.aicomponent/ref/SceneKeys.ts +6 -0
  413. package/skills/threejs.aicomponent/ref/SceneManager.ts +69 -0
  414. package/skills/threejs.aicomponent/ref/ViewportManager.ts +62 -0
  415. package/skills/threejs.aicomponent/ref/globals.d.ts +74 -0
  416. package/skills/threejs.aicomponent/ref/index.css +80 -0
  417. package/skills/threejs.aicomponent/ref/index.html +20 -0
  418. package/skills/threejs.aicomponent/ref/index.ts +117 -0
  419. package/skills/threejs.aicomponent/ref/main.ts +10 -0
  420. package/skills/threejs.aicomponent/ref/package.json +23 -0
  421. package/skills/threejs.aicomponent/ref/tsconfig.json +24 -0
  422. package/skills/threejs.aicomponent/ref/webpack.config.js +61 -0
  423. package/skills/time_clear_goal.aiconfig/SKILL.md +46 -0
  424. package/skills/time_clear_goal.aiconfig/manifest.json +32 -0
  425. package/skills/time_icon.aiimage/SKILL.md +58 -0
  426. package/skills/time_icon.aiimage/manifest.json +47 -0
  427. package/skills/time_icon.aiimage/ref/time.png +0 -0
  428. package/skills/time_icon.aiimage/ref/time.webp +0 -0
  429. package/skills/top_ui_bar.aicomponent/SKILL.md +45 -0
  430. package/skills/top_ui_bar.aicomponent/manifest.json +27 -0
  431. package/skills/top_ui_bar.aicomponent/ref/GameTopUI.ts +229 -0
  432. package/skills/tower_defence.aigameplay/SKILL.md +117 -0
  433. package/skills/tower_defence.aigameplay/manifest.json +175 -0
  434. package/skills/tower_defence.aigameplay/ref/pgs-schema.json +247 -0
  435. package/skills/tower_defence.aigameplay/ref/reducer.template.ts +239 -0
  436. package/skills/tray_container.aicomponent/SKILL.md +143 -0
  437. package/skills/tray_container.aicomponent/manifest.json +58 -0
  438. package/skills/tray_container.aicomponent/ref/TrayRenderer.ts +155 -0
  439. package/skills/tray_container.aicomponent/ref/TrayRenderer3D.ts +281 -0
  440. package/skills/tray_container.aicomponent/ref/TrayRendererHtmlOverlay.ts +228 -0
  441. package/skills/tutorial_overlay.aicomponent/SKILL.md +46 -0
  442. package/skills/tutorial_overlay.aicomponent/manifest.json +34 -0
  443. package/skills/tutorial_overlay.aicomponent/ref/TutorialManager.ts +367 -0
  444. package/skills/tween-animation-2d.aicomponent/SKILL.md +35 -0
  445. package/skills/tween-animation-2d.aicomponent/manifest.json +28 -0
  446. package/skills/tween-animation-2d.aicomponent/ref/phaser.js +49 -0
  447. package/skills/tween-animation-2d.aicomponent/ref/playcanvas.js +36 -0
  448. package/skills/typescript_module_patterns.aicomponent/SKILL.md +209 -0
  449. package/skills/typescript_module_patterns.aicomponent/manifest.json +28 -0
  450. package/skills/typescript_module_patterns.aicomponent/ref/globals.d.ts +31 -0
  451. package/skills/ui-transition.aicomponent/SKILL.md +33 -0
  452. package/skills/ui-transition.aicomponent/manifest.json +19 -0
  453. package/skills/ui-transition.aicomponent/ref/phaser.js +45 -0
  454. package/skills/ux_flow_spec.aiconfig/SKILL.md +82 -0
  455. package/skills/ux_flow_spec.aiconfig/manifest.json +39 -0
  456. package/skills/wave_difficulty.aiconfig/SKILL.md +46 -0
  457. package/skills/wave_difficulty.aiconfig/manifest.json +33 -0
  458. package/skills/webpack_build.aicomponent/SKILL.md +204 -0
  459. package/skills/webpack_build.aicomponent/manifest.json +29 -0
  460. package/skills/webpack_build.aicomponent/ref/package.json +16 -0
  461. package/skills/webpack_build.aicomponent/ref/webpack.config.js +53 -0
  462. package/skills/win_anim_sfx.aiaudio/SKILL.md +59 -0
  463. package/skills/win_anim_sfx.aiaudio/manifest.json +46 -0
  464. package/skills/win_anim_sfx.aiaudio/ref/win_anim.mp3 +0 -0
  465. package/skills/win_dot.aiimage/SKILL.md +58 -0
  466. package/skills/win_dot.aiimage/manifest.json +47 -0
  467. package/skills/win_dot.aiimage/ref/win_dot.png +0 -0
  468. package/skills/win_dot.aiimage/ref/win_dot.webp +0 -0
  469. package/skills/win_result_panel.aiimage/SKILL.md +58 -0
  470. package/skills/win_result_panel.aiimage/manifest.json +47 -0
  471. package/skills/win_result_panel.aiimage/ref/win.png +0 -0
  472. package/skills/win_result_panel.aiimage/ref/win.webp +0 -0
  473. package/skills/word_connect.aigameplay/SKILL.md +86 -0
  474. package/skills/word_connect.aigameplay/manifest.json +154 -0
  475. package/skills/word_connect.aigameplay/ref/pgs-schema.json +212 -0
  476. package/skills/word_connect.aigameplay/ref/reducer.template.ts +186 -0
  477. package/src/index.ts +10 -0
  478. package/src/skills.loader.ts +225 -0
  479. package/src/skills.types.ts +42 -0
@@ -0,0 +1,325 @@
1
+ /**
2
+ * GLB → Flip Sprite Sheet renderer (pure JS, no native deps)
3
+ *
4
+ * Architecture: "White model + face texture injection"
5
+ * - GLB provides the 3D shape (mesh + UV atlas for body/edges/back)
6
+ * - An optional face texture overrides ONLY the front-face triangles
7
+ * - Batch: run once per tile image, reuse the same GLB
8
+ *
9
+ * Usage:
10
+ * node render.mjs [glb] [out.png] [face-texture.png]
11
+ *
12
+ * Pipeline:
13
+ * 1. Parse GLB → extract geometry + body texture (UV atlas)
14
+ * 2. Classify each triangle as "face" (front) or "body" (edges/back)
15
+ * by checking model-space normal Y > FACE_THRESHOLD
16
+ * 3. For face triangles, derive UV from model-space XZ position
17
+ * (maps tile width/height → [0,1] range of the supplied face texture)
18
+ * 4. Perspective rasterize with Phong shading + Z-buffer + SSAA
19
+ * 5. Assemble sprite sheet
20
+ */
21
+ import { NodeIO } from '@gltf-transform/core';
22
+ import sharp from 'sharp';
23
+ import { existsSync } from 'fs';
24
+
25
+ // ── CLI args ─────────────────────────────────────────────────────────────────
26
+ const GLB_PATH = process.argv[2] || '/tmp/mahjong-tile/tile_1wan.glb';
27
+ const OUT_PATH = process.argv[3] || '/tmp/mahjong-tile/tile_flip_sprite.png';
28
+ const FACE_TEX_PATH = process.argv[4] || null; // optional face override texture
29
+
30
+ // ── Config ────────────────────────────────────────────────────────────────────
31
+ const FRAMES = 16;
32
+ const COLS = 8;
33
+ const W = 280, H = 280;
34
+ const SS = 2; // SSAA factor (2 = 4× samples)
35
+ const RW = W*SS, RH = H*SS;
36
+
37
+ // Triangle normal Y > this threshold (model space) = front face
38
+ const FACE_THRESHOLD = 0.7;
39
+
40
+ // ── Vector math ───────────────────────────────────────────────────────────────
41
+ const v3 = (x,y,z) => ({x,y,z});
42
+ const add = (a,b) => v3(a.x+b.x,a.y+b.y,a.z+b.z);
43
+ const sub = (a,b) => v3(a.x-b.x,a.y-b.y,a.z-b.z);
44
+ const scale = (v,s) => v3(v.x*s,v.y*s,v.z*s);
45
+ const dot = (a,b) => a.x*b.x+a.y*b.y+a.z*b.z;
46
+ const cross = (a,b) => v3(a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x);
47
+ function normalize(v) { const l=Math.sqrt(dot(v,v))||1e-9; return scale(v,1/l); }
48
+ function rotY(p,ry) { const c=Math.cos(ry),s=Math.sin(ry); return v3(p.x*c+p.z*s,p.y,-p.x*s+p.z*c); }
49
+ function rotX(p,rx) { const c=Math.cos(rx),s=Math.sin(rx); return v3(p.x,p.y*c-p.z*s,p.y*s+p.z*c); }
50
+
51
+ // ── Camera & lighting ─────────────────────────────────────────────────────────
52
+ const FOCAL = 2.8;
53
+ const CAMERA_Z = 3.2;
54
+ const LIGHT = normalize(v3(0.3, 0.8, 1.0));
55
+ const VIEW_DIR = v3(0, 0, 1);
56
+
57
+ // ── Pose: RX_INIT = +1.908 rad (+109.3°)
58
+ // • At ry=0: front face toward camera, 一 at screen-top (correct orientation)
59
+ // • At ry=π: back face toward camera (flip START position)
60
+ // • Flip runs ry: π → 0 (back → front reveal)
61
+ const RX_INIT = 1.908;
62
+
63
+ // ── Projection ────────────────────────────────────────────────────────────────
64
+ function project(p) {
65
+ const dz = CAMERA_Z - p.z;
66
+ return { sx:(p.x/dz)*FOCAL*RW*0.42+RW/2, sy:(-p.y/dz)*FOCAL*RH*0.42+RH/2, wClip:1/dz };
67
+ }
68
+
69
+ // ── Bilinear texture sample ───────────────────────────────────────────────────
70
+ function sampleBilinear(tex, tw, th, u, v) {
71
+ const fx = Math.max(0, Math.min(tw-1.001, u*(tw-1)));
72
+ const fy = Math.max(0, Math.min(th-1.001, v*(th-1)));
73
+ const x0=Math.floor(fx), y0=Math.floor(fy), sx=fx-x0, sy=fy-y0;
74
+ const I = (r,c) => (r*tw+c)*4;
75
+ const out = new Float32Array(4);
76
+ for (let ch=0; ch<4; ch++)
77
+ out[ch] = (tex[I(y0,x0)+ch]*(1-sx)+tex[I(y0,x0+1)+ch]*sx)*(1-sy)
78
+ + (tex[I(y0+1,x0)+ch]*(1-sx)+tex[I(y0+1,x0+1)+ch]*sx)*sy;
79
+ return out;
80
+ }
81
+
82
+ // ── Triangle rasterizer ───────────────────────────────────────────────────────
83
+ function rasterizeTri(pixels, zbuf, p0, p1, p2, u0,v0, u1,v1, u2,v2,
84
+ n0, n1, n2, tex, tw, th, flipU) {
85
+ const mnX=Math.max(0,Math.floor(Math.min(p0.sx,p1.sx,p2.sx)));
86
+ const mxX=Math.min(RW-1,Math.ceil(Math.max(p0.sx,p1.sx,p2.sx)));
87
+ const mnY=Math.max(0,Math.floor(Math.min(p0.sy,p1.sy,p2.sy)));
88
+ const mxY=Math.min(RH-1,Math.ceil(Math.max(p0.sy,p1.sy,p2.sy)));
89
+ const area2 = (p1.sx-p0.sx)*(p0.sy-p2.sy)-(p0.sx-p2.sx)*(p1.sy-p0.sy);
90
+ if (Math.abs(area2) < 1e-6) return;
91
+ const isMirrored = area2 < 0;
92
+ const invArea = 1/Math.abs(area2);
93
+ const sign = isMirrored ? -1 : 1;
94
+
95
+ for (let py=mnY; py<=mxY; py++) {
96
+ for (let px=mnX; px<=mxX; px++) {
97
+ const w0=sign*((px-p1.sx)*(p2.sy-p1.sy)-(py-p1.sy)*(p2.sx-p1.sx))*invArea;
98
+ const w1=sign*((px-p2.sx)*(p0.sy-p2.sy)-(py-p2.sy)*(p0.sx-p2.sx))*invArea;
99
+ const w2=1-w0-w1;
100
+ if (w0<0||w1<0||w2<0) continue;
101
+ const wI = w0*p0.wClip+w1*p1.wClip+w2*p2.wClip;
102
+ const pidx = py*RW+px;
103
+ if (wI < zbuf[pidx]) continue;
104
+ zbuf[pidx] = wI;
105
+
106
+ const uI = (w0*u0*p0.wClip+w1*u1*p1.wClip+w2*u2*p2.wClip)/wI;
107
+ const vI = (w0*v0*p0.wClip+w1*v1*p1.wClip+w2*v2*p2.wClip)/wI;
108
+
109
+ const nx=(w0*n0.x*p0.wClip+w1*n1.x*p1.wClip+w2*n2.x*p2.wClip)/wI;
110
+ const ny=(w0*n0.y*p0.wClip+w1*n1.y*p1.wClip+w2*n2.y*p2.wClip)/wI;
111
+ const nz=(w0*n0.z*p0.wClip+w1*n1.z*p1.wClip+w2*n2.z*p2.wClip)/wI;
112
+ const nLen=Math.sqrt(nx*nx+ny*ny+nz*nz)||1e-9;
113
+ const nN={x:nx/nLen,y:ny/nLen,z:nz/nLen};
114
+
115
+ const NdL = Math.max(0, dot(nN, LIGHT));
116
+ const half = normalize(add(LIGHT, VIEW_DIR));
117
+ const NdH = Math.max(0, dot(nN, half));
118
+ const light = 0.32 + 0.60*NdL;
119
+ const spec = 0.18*Math.pow(NdH, 48);
120
+
121
+ // U flip: body uses isMirrored (UV atlas), face uses explicit flipU flag
122
+ const uSample = (flipU ?? isMirrored) ? 1-uI : uI;
123
+ const col = sampleBilinear(tex, tw, th, Math.max(0,Math.min(1,uSample)), Math.max(0,Math.min(1,vI)));
124
+ if (col[3] < 8) continue;
125
+
126
+ const oi = pidx*4;
127
+ pixels[oi ] = Math.min(255, col[0]*light+255*spec);
128
+ pixels[oi+1] = Math.min(255, col[1]*light+255*spec);
129
+ pixels[oi+2] = Math.min(255, col[2]*light+255*spec);
130
+ pixels[oi+3] = col[3];
131
+ }
132
+ }
133
+ }
134
+
135
+ // ── Load face override texture (optional) ────────────────────────────────────
136
+ let faceTexData = null, faceTexW = 0, faceTexH = 0;
137
+ if (FACE_TEX_PATH && existsSync(FACE_TEX_PATH)) {
138
+ const { data, info } = await sharp(FACE_TEX_PATH)
139
+ .resize(512, 512, { fit: 'fill' }).ensureAlpha().raw()
140
+ .toBuffer({ resolveWithObject: true });
141
+ faceTexData = new Uint8ClampedArray(data);
142
+ faceTexW = info.width; faceTexH = info.height;
143
+ console.log(`Face texture loaded: ${FACE_TEX_PATH} (${faceTexW}×${faceTexH})`);
144
+ } else {
145
+ console.log('No face texture — using GLB atlas for all faces');
146
+ }
147
+
148
+ // ── Parse GLB ─────────────────────────────────────────────────────────────────
149
+ console.log(`Loading GLB: ${GLB_PATH}`);
150
+ const doc = await (new NodeIO()).read(GLB_PATH);
151
+ const meshData = [];
152
+
153
+ for (const mesh of doc.getRoot().listMeshes()) {
154
+ for (const prim of mesh.listPrimitives()) {
155
+ const posAcc = prim.getAttribute('POSITION');
156
+ if (!posAcc) continue;
157
+ const normAcc = prim.getAttribute('NORMAL');
158
+ const uvAcc = prim.getAttribute('TEXCOORD_0');
159
+ const idxAcc = prim.getIndices();
160
+ const positions = posAcc.getArray();
161
+ const normals = normAcc?.getArray() ?? null;
162
+ const uvs = uvAcc?.getArray() ?? null;
163
+ const indices = idxAcc?.getArray() ?? null;
164
+ const triCount = indices ? indices.length/3 : positions.length/9;
165
+ const triList = [];
166
+ for (let t=0; t<triCount; t++) {
167
+ const i0 = indices ? indices[t*3] : t*3;
168
+ const i1 = indices ? indices[t*3+1] : t*3+1;
169
+ const i2 = indices ? indices[t*3+2] : t*3+2;
170
+ triList.push(i0,i1,i2);
171
+ }
172
+ const mat = prim.getMaterial();
173
+ let texData=null, texW=1, texH=1;
174
+ if (mat) {
175
+ const baseColorTex = mat.getBaseColorTexture();
176
+ if (baseColorTex?.getImage()) {
177
+ const { data, info } = await sharp(Buffer.from(baseColorTex.getImage()))
178
+ .resize(512, 512, { fit:'fill' }).ensureAlpha().raw()
179
+ .toBuffer({ resolveWithObject: true });
180
+ texData = new Uint8ClampedArray(data); texW=info.width; texH=info.height;
181
+ }
182
+ }
183
+ if (!texData) {
184
+ const f = mat?.getBaseColorFactor() ?? [0.8,0.8,0.8,1];
185
+ texData = new Uint8ClampedArray([f[0]*255,f[1]*255,f[2]*255,f[3]*255|0]);
186
+ texW=1; texH=1;
187
+ }
188
+ meshData.push({ positions, normals, uvs, triList, texData, texW, texH });
189
+ }
190
+ }
191
+ console.log(` Loaded ${meshData.length} primitive(s)`);
192
+
193
+ // ── Normalize model to unit cube ──────────────────────────────────────────────
194
+ let mnX=Infinity,mnY=Infinity,mnZ=Infinity,mxX=-Infinity,mxY=-Infinity,mxZ=-Infinity;
195
+ for (const m of meshData) {
196
+ const p=m.positions;
197
+ for (let i=0; i<p.length; i+=3) {
198
+ if(p[i ]<mnX)mnX=p[i ]; if(p[i ]>mxX)mxX=p[i ];
199
+ if(p[i+1]<mnY)mnY=p[i+1]; if(p[i+1]>mxY)mxY=p[i+1];
200
+ if(p[i+2]<mnZ)mnZ=p[i+2]; if(p[i+2]>mxZ)mxZ=p[i+2];
201
+ }
202
+ }
203
+ const cx=(mnX+mxX)/2, cy=(mnY+mxY)/2, cz=(mnZ+mxZ)/2;
204
+ const ext=Math.max(mxX-mnX,mxY-mnY,mxZ-mnZ)/2||1;
205
+ for (const m of meshData) {
206
+ const p=m.positions;
207
+ for (let i=0; i<p.length; i+=3) { p[i]=(p[i]-cx)/ext; p[i+1]=(p[i+1]-cy)/ext; p[i+2]=(p[i+2]-cz)/ext; }
208
+ }
209
+ console.log(` Normalized: extent=${ext.toFixed(3)}`);
210
+
211
+ // ── Compute face bounding box (model space, after normalization) ───────────────
212
+ // Face triangles: original normal Y > FACE_THRESHOLD (front face of the tile)
213
+ // We use their model-space XZ extent to generate UV coords for the face texture.
214
+ // faceU = (x - faceXMin) / (faceXMax - faceXMin) → tile left→right
215
+ // faceV = (z - faceZMin) / (faceZMax - faceZMin) → 一(zMin)→万(zMax)
216
+ //
217
+ // Standard tile image (一 at top, 万 at bottom) maps directly onto this space.
218
+ let faceXMin=Infinity,faceXMax=-Infinity,faceZMin=Infinity,faceZMax=-Infinity;
219
+ for (const m of meshData) {
220
+ const { positions, normals } = m;
221
+ if (!normals) continue;
222
+ for (let i=0; i<positions.length/3; i++) {
223
+ if (normals[i*3+1] > FACE_THRESHOLD) {
224
+ const x=positions[i*3], z=positions[i*3+2];
225
+ if(x<faceXMin)faceXMin=x; if(x>faceXMax)faceXMax=x;
226
+ if(z<faceZMin)faceZMin=z; if(z>faceZMax)faceZMax=z;
227
+ }
228
+ }
229
+ }
230
+ const faceXRange = faceXMax-faceXMin || 1;
231
+ const faceZRange = faceZMax-faceZMin || 1;
232
+ console.log(` Face bbox: X=[${faceXMin.toFixed(3)},${faceXMax.toFixed(3)}] Z=[${faceZMin.toFixed(3)},${faceZMax.toFixed(3)}]`);
233
+
234
+ // ── Render frames ─────────────────────────────────────────────────────────────
235
+ async function renderFrame(ry) {
236
+ const pixels = new Uint8ClampedArray(RW*RH*4);
237
+ const zbuf = new Float32Array(RW*RH).fill(-Infinity);
238
+
239
+ for (const m of meshData) {
240
+ const { positions, normals, uvs, triList, texData, texW, texH } = m;
241
+ const nTris = triList.length/3;
242
+
243
+ for (let t=0; t<nTris; t++) {
244
+ const i0=triList[t*3], i1=triList[t*3+1], i2=triList[t*3+2];
245
+
246
+ // Pose: rotX then rotY
247
+ function pose(i) { return rotY(rotX(v3(positions[i*3],positions[i*3+1],positions[i*3+2]),RX_INIT),ry); }
248
+ const p0=pose(i0), p1=pose(i1), p2=pose(i2);
249
+
250
+ // Backface cull
251
+ const faceN = normalize(cross(sub(p1,p0),sub(p2,p0)));
252
+ if (dot(faceN, sub(v3(0,0,CAMERA_Z),p0)) < 0) continue;
253
+
254
+ // World normals
255
+ function rotN(i) { return normals ? rotY(rotX(v3(normals[i*3],normals[i*3+1],normals[i*3+2]),RX_INIT),ry) : faceN; }
256
+ const n0=rotN(i0), n1=rotN(i1), n2=rotN(i2);
257
+
258
+ // ── Face texture injection ──────────────────────────────────────────────
259
+ // Detect face triangle by avg model-space normal Y
260
+ const avgNY = normals ? (normals[i0*3+1]+normals[i1*3+1]+normals[i2*3+1])/3 : 0;
261
+ const isFaceTri = faceTexData && avgNY > FACE_THRESHOLD;
262
+
263
+ let tu0,tv0,tu1,tv1,tu2,tv2, activeTex,activeTW,activeTH, flipU;
264
+
265
+ if (isFaceTri) {
266
+ // Model-space XZ → face UV (一=zMin→faceV=0, 万=zMax→faceV=1)
267
+ tu0=(positions[i0*3]-faceXMin)/faceXRange; tv0=(positions[i0*3+2]-faceZMin)/faceZRange;
268
+ tu1=(positions[i1*3]-faceXMin)/faceXRange; tv1=(positions[i1*3+2]-faceZMin)/faceZRange;
269
+ tu2=(positions[i2*3]-faceXMin)/faceXRange; tv2=(positions[i2*3+2]-faceZMin)/faceZRange;
270
+ activeTex=faceTexData; activeTW=faceTexW; activeTH=faceTexH;
271
+ flipU = false; // face never needs U-flip (only visible at ry≈0, CCW winding)
272
+ } else {
273
+ // Body/edges/back: use GLB UV atlas with isMirrored correction
274
+ tu0=uvs?uvs[i0*2]:0; tv0=uvs?uvs[i0*2+1]:0;
275
+ tu1=uvs?uvs[i1*2]:0; tv1=uvs?uvs[i1*2+1]:0;
276
+ tu2=uvs?uvs[i2*2]:0; tv2=uvs?uvs[i2*2+1]:0;
277
+ activeTex=texData; activeTW=texW; activeTH=texH;
278
+ flipU = null; // null = use isMirrored auto-detection in rasterizer
279
+ }
280
+
281
+ rasterizeTri(pixels, zbuf,
282
+ project(p0), project(p1), project(p2),
283
+ tu0,tv0, tu1,tv1, tu2,tv2,
284
+ n0, n1, n2,
285
+ activeTex, activeTW, activeTH,
286
+ flipU);
287
+ }
288
+ }
289
+
290
+ // Downsample SSAA → output resolution
291
+ const out = new Uint8ClampedArray(W*H*4);
292
+ for (let y=0; y<H; y++) for (let x=0; x<W; x++) {
293
+ let r=0,g=0,b=0,a=0;
294
+ for (let sy=0; sy<SS; sy++) for (let sx=0; sx<SS; sx++) {
295
+ const i=((y*SS+sy)*RW+(x*SS+sx))*4;
296
+ r+=pixels[i]; g+=pixels[i+1]; b+=pixels[i+2]; a+=pixels[i+3];
297
+ }
298
+ const n2=SS*SS, oi=(y*W+x)*4;
299
+ out[oi]=r/n2; out[oi+1]=g/n2; out[oi+2]=b/n2; out[oi+3]=a/n2;
300
+ }
301
+ return Buffer.from(out.buffer);
302
+ }
303
+
304
+ // ── Generate frames ───────────────────────────────────────────────────────────
305
+ const ROWS = Math.ceil(FRAMES/COLS);
306
+ const frames = [];
307
+ console.log(`Rendering ${FRAMES} frames (${W}×${H} × ${SS}² AA)...`);
308
+ for (let i=0; i<FRAMES; i++) {
309
+ // ry: π → 0 (back face first → front face revealed)
310
+ const ry = Math.PI - (i/(FRAMES-1))*Math.PI;
311
+ process.stdout.write(`\r Frame ${i+1}/${FRAMES}`);
312
+ frames.push(await renderFrame(ry));
313
+ }
314
+ console.log('\n Compositing...');
315
+
316
+ const pngFrames = await Promise.all(
317
+ frames.map(buf => sharp(buf,{raw:{width:W,height:H,channels:4}}).png().toBuffer())
318
+ );
319
+ await sharp({ create:{width:W*COLS,height:H*ROWS,channels:4,background:{r:0,g:0,b:0,alpha:0}} })
320
+ .composite(pngFrames.map((buf,i)=>({input:buf,left:(i%COLS)*W,top:Math.floor(i/COLS)*H})))
321
+ .png().toFile(OUT_PATH);
322
+
323
+ console.log(`\nSaved → ${OUT_PATH} (${COLS}×${ROWS} frames, ${W}×${H}px each)`);
324
+ await sharp(pngFrames[0]).png().toFile(OUT_PATH.replace('.png','_frame0.png'));
325
+ await sharp(pngFrames[FRAMES-1]).png().toFile(OUT_PATH.replace('.png','_frameN.png'));
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Single-frame 3D renderer: GLB + face texture → one PNG
3
+ * Usage: node render_single.mjs <face-texture.png> <output.png> [ry=0]
4
+ * Optimised for batch: SS=1, smaller canvas, preloaded GLB data
5
+ */
6
+ import { NodeIO } from '@gltf-transform/core';
7
+ import sharp from 'sharp';
8
+ import { existsSync } from 'fs';
9
+
10
+ // ── Config ─────────────────────────────────────────────────────────────────
11
+ const FACE_TEX = process.argv[2];
12
+ const OUT_PATH = process.argv[3];
13
+ const RY = parseFloat(process.argv[4] ?? '0');
14
+ const GLB_PATH = process.env.GLB ?? '/tmp/mahjong-tile/tile_1wan.glb';
15
+ const W = 180, H = 180;
16
+ const FACE_THRESHOLD = 0.7;
17
+ const RX_INIT = 1.908;
18
+ const FOCAL = 2.8, CAMERA_Z = 3.2;
19
+
20
+ if (!FACE_TEX || !OUT_PATH) {
21
+ console.error('Usage: node render_single.mjs <face.png> <out.png> [ry]');
22
+ process.exit(1);
23
+ }
24
+
25
+ // ── Math ───────────────────────────────────────────────────────────────────
26
+ const v3=(x,y,z)=>({x,y,z}),add=(a,b)=>v3(a.x+b.x,a.y+b.y,a.z+b.z),
27
+ sub=(a,b)=>v3(a.x-b.x,a.y-b.y,a.z-b.z),scl=(v,s)=>v3(v.x*s,v.y*s,v.z*s),
28
+ dot=(a,b)=>a.x*b.x+a.y*b.y+a.z*b.z,
29
+ cross=(a,b)=>v3(a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x);
30
+ function nm(v){const l=Math.sqrt(dot(v,v))||1e-9;return scl(v,1/l);}
31
+ function rotX(p,rx){const c=Math.cos(rx),s=Math.sin(rx);return v3(p.x,p.y*c-p.z*s,p.y*s+p.z*c);}
32
+ function rotY(p,ry){const c=Math.cos(ry),s=Math.sin(ry);return v3(p.x*c+p.z*s,p.y,-p.x*s+p.z*c);}
33
+ const LIGHT=nm(v3(0.3,0.8,1)),VIEW=v3(0,0,1);
34
+ function proj(p){const dz=CAMERA_Z-p.z;return{sx:(p.x/dz)*FOCAL*W*0.42+W/2,sy:(-p.y/dz)*FOCAL*H*0.42+H/2,wClip:1/dz};}
35
+
36
+ function bilin(tex,tw,th,u,v){
37
+ const fx=Math.max(0,Math.min(tw-1.001,u*(tw-1))),fy=Math.max(0,Math.min(th-1.001,v*(th-1)));
38
+ const x0=Math.floor(fx),y0=Math.floor(fy),sx=fx-x0,sy=fy-y0;
39
+ const I=(r,c)=>(r*tw+c)*4,o=new Float32Array(4);
40
+ for(let ch=0;ch<4;ch++)o[ch]=(tex[I(y0,x0)+ch]*(1-sx)+tex[I(y0,x0+1)+ch]*sx)*(1-sy)+(tex[I(y0+1,x0)+ch]*(1-sx)+tex[I(y0+1,x0+1)+ch]*sx)*sy;
41
+ return o;
42
+ }
43
+
44
+ function rasterize(px,zb,p0,p1,p2,u0,v0,u1,v1,u2,v2,n0,n1,n2,tex,tw,th,flipU){
45
+ const mxX=Math.min(W-1,Math.ceil(Math.max(p0.sx,p1.sx,p2.sx))),mnX=Math.max(0,Math.floor(Math.min(p0.sx,p1.sx,p2.sx)));
46
+ const mxY=Math.min(H-1,Math.ceil(Math.max(p0.sy,p1.sy,p2.sy))),mnY=Math.max(0,Math.floor(Math.min(p0.sy,p1.sy,p2.sy)));
47
+ const a2=(p1.sx-p0.sx)*(p0.sy-p2.sy)-(p0.sx-p2.sx)*(p1.sy-p0.sy);
48
+ if(Math.abs(a2)<1e-6)return;
49
+ const iM=a2<0,ia=1/Math.abs(a2),sg=iM?-1:1;
50
+ for(let py=mnY;py<=mxY;py++)for(let ppx=mnX;ppx<=mxX;ppx++){
51
+ const w0=sg*((ppx-p1.sx)*(p2.sy-p1.sy)-(py-p1.sy)*(p2.sx-p1.sx))*ia;
52
+ const w1=sg*((ppx-p2.sx)*(p0.sy-p2.sy)-(py-p2.sy)*(p0.sx-p2.sx))*ia;
53
+ const w2=1-w0-w1;
54
+ if(w0<0||w1<0||w2<0)continue;
55
+ const wI=w0*p0.wClip+w1*p1.wClip+w2*p2.wClip,pi=py*W+ppx;
56
+ if(wI<zb[pi])continue;zb[pi]=wI;
57
+ const uI=(w0*u0*p0.wClip+w1*u1*p1.wClip+w2*u2*p2.wClip)/wI;
58
+ const vI=(w0*v0*p0.wClip+w1*v1*p1.wClip+w2*v2*p2.wClip)/wI;
59
+ const nx=(w0*n0.x*p0.wClip+w1*n1.x*p1.wClip+w2*n2.x*p2.wClip)/wI;
60
+ const ny=(w0*n0.y*p0.wClip+w1*n1.y*p1.wClip+w2*n2.y*p2.wClip)/wI;
61
+ const nz=(w0*n0.z*p0.wClip+w1*n1.z*p1.wClip+w2*n2.z*p2.wClip)/wI;
62
+ const nL=Math.sqrt(nx*nx+ny*ny+nz*nz)||1e-9;
63
+ const nN={x:nx/nL,y:ny/nL,z:nz/nL};
64
+ const NdL=Math.max(0,dot(nN,LIGHT)),half=nm(add(LIGHT,VIEW)),NdH=Math.max(0,dot(nN,half));
65
+ const light=0.32+0.60*NdL,spec=0.18*Math.pow(NdH,48);
66
+ const uS=(flipU??iM)?1-uI:uI;
67
+ const col=bilin(tex,tw,th,Math.max(0,Math.min(1,uS)),Math.max(0,Math.min(1,vI)));
68
+ if(col[3]<8)continue;
69
+ const oi=pi*4;
70
+ px[oi]=Math.min(255,col[0]*light+255*spec);px[oi+1]=Math.min(255,col[1]*light+255*spec);
71
+ px[oi+2]=Math.min(255,col[2]*light+255*spec);px[oi+3]=col[3];
72
+ }
73
+ }
74
+
75
+ // ── Load GLB (cached via env GLB_CACHE_SKIP or read fresh) ─────────────────
76
+ const doc = await (new NodeIO()).read(GLB_PATH);
77
+ const meshData=[];
78
+ for(const mesh of doc.getRoot().listMeshes()){
79
+ for(const prim of mesh.listPrimitives()){
80
+ const posA=prim.getAttribute('POSITION');if(!posA)continue;
81
+ const normA=prim.getAttribute('NORMAL'),uvA=prim.getAttribute('TEXCOORD_0'),idxA=prim.getIndices();
82
+ const pos=posA.getArray(),norms=normA?.getArray()??null,uvs=uvA?.getArray()??null,idx=idxA?.getArray()??null;
83
+ const tc=idx?idx.length/3:pos.length/9,tl=[];
84
+ for(let t=0;t<tc;t++){tl.push(idx?idx[t*3]:t*3,idx?idx[t*3+1]:t*3+1,idx?idx[t*3+2]:t*3+2);}
85
+ const mat=prim.getMaterial();let texData=null,texW=1,texH=1;
86
+ if(mat?.getBaseColorTexture()?.getImage()){
87
+ const{data,info}=await sharp(Buffer.from(mat.getBaseColorTexture().getImage()))
88
+ .resize(256,256,{fit:'fill'}).ensureAlpha().raw().toBuffer({resolveWithObject:true});
89
+ texData=new Uint8ClampedArray(data);texW=info.width;texH=info.height;
90
+ }
91
+ if(!texData){const f=mat?.getBaseColorFactor()??[0.8,0.8,0.8,1];texData=new Uint8ClampedArray([f[0]*255|0,f[1]*255|0,f[2]*255|0,f[3]*255|0]);texW=1;texH=1;}
92
+ meshData.push({pos,norms,uvs,tl,texData,texW,texH});
93
+ }
94
+ }
95
+ // Normalize
96
+ let mnX=1e9,mnY=1e9,mnZ=1e9,mxX=-1e9,mxY=-1e9,mxZ=-1e9;
97
+ for(const m of meshData){const p=m.pos;for(let i=0;i<p.length;i+=3){if(p[i]<mnX)mnX=p[i];if(p[i]>mxX)mxX=p[i];if(p[i+1]<mnY)mnY=p[i+1];if(p[i+1]>mxY)mxY=p[i+1];if(p[i+2]<mnZ)mnZ=p[i+2];if(p[i+2]>mxZ)mxZ=p[i+2];}}
98
+ const cx=(mnX+mxX)/2,cy=(mnY+mxY)/2,cz=(mnZ+mxZ)/2,ext=Math.max(mxX-mnX,mxY-mnY,mxZ-mnZ)/2||1;
99
+ for(const m of meshData){const p=m.pos;for(let i=0;i<p.length;i+=3){p[i]=(p[i]-cx)/ext;p[i+1]=(p[i+1]-cy)/ext;p[i+2]=(p[i+2]-cz)/ext;}}
100
+ // Face bbox
101
+ let fxMn=1e9,fxMx=-1e9,fzMn=1e9,fzMx=-1e9;
102
+ for(const m of meshData){if(!m.norms)continue;for(let i=0;i<m.pos.length/3;i++){if(m.norms[i*3+1]>FACE_THRESHOLD){const x=m.pos[i*3],z=m.pos[i*3+2];if(x<fxMn)fxMn=x;if(x>fxMx)fxMx=x;if(z<fzMn)fzMn=z;if(z>fzMx)fzMx=z;}}}
103
+ const fxR=fxMx-fxMn||1,fzR=fzMx-fzMn||1;
104
+
105
+ // ── Load face texture ───────────────────────────────────────────────────────
106
+ const{data:fData,info:fInfo}=await sharp(FACE_TEX).resize(256,256,{fit:'fill'}).ensureAlpha().raw().toBuffer({resolveWithObject:true});
107
+ const faceTexData=new Uint8ClampedArray(fData),faceW=fInfo.width,faceH=fInfo.height;
108
+
109
+ // ── Render ─────────────────────────────────────────────────────────────────
110
+ const pixels=new Uint8ClampedArray(W*H*4),zbuf=new Float32Array(W*H).fill(-Infinity);
111
+ const ry=RY;
112
+ for(const m of meshData){
113
+ const{pos,norms,uvs,tl,texData,texW,texH}=m;
114
+ for(let t=0;t<tl.length/3;t++){
115
+ const i0=tl[t*3],i1=tl[t*3+1],i2=tl[t*3+2];
116
+ const pp=i=>rotY(rotX(v3(pos[i*3],pos[i*3+1],pos[i*3+2]),RX_INIT),ry);
117
+ const[p0,p1,p2]=[pp(i0),pp(i1),pp(i2)];
118
+ const fN=nm(cross(sub(p1,p0),sub(p2,p0)));
119
+ if(dot(fN,sub(v3(0,0,CAMERA_Z),p0))<0)continue;
120
+ const rn=i=>norms?rotY(rotX(v3(norms[i*3],norms[i*3+1],norms[i*3+2]),RX_INIT),ry):fN;
121
+ const avgNY=norms?(norms[i0*3+1]+norms[i1*3+1]+norms[i2*3+1])/3:0;
122
+ const isFace=avgNY>FACE_THRESHOLD;
123
+ let u0,v0,u1,v1,u2,v2,tex,tw,th,flipU;
124
+ if(isFace){
125
+ u0=(pos[i0*3]-fxMn)/fxR;v0=(pos[i0*3+2]-fzMn)/fzR;
126
+ u1=(pos[i1*3]-fxMn)/fxR;v1=(pos[i1*3+2]-fzMn)/fzR;
127
+ u2=(pos[i2*3]-fxMn)/fxR;v2=(pos[i2*3+2]-fzMn)/fzR;
128
+ tex=faceTexData;tw=faceW;th=faceH;flipU=false;
129
+ }else{
130
+ u0=uvs?uvs[i0*2]:0;v0=uvs?uvs[i0*2+1]:0;
131
+ u1=uvs?uvs[i1*2]:0;v1=uvs?uvs[i1*2+1]:0;
132
+ u2=uvs?uvs[i2*2]:0;v2=uvs?uvs[i2*2+1]:0;
133
+ tex=texData;tw=texW;th=texH;flipU=null;
134
+ }
135
+ rasterize(pixels,zbuf,proj(p0),proj(p1),proj(p2),u0,v0,u1,v1,u2,v2,rn(i0),rn(i1),rn(i2),tex,tw,th,flipU);
136
+ }
137
+ }
138
+ await sharp(Buffer.from(pixels.buffer),{raw:{width:W,height:H,channels:4}}).png().toFile(OUT_PATH);
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: playcraft-asset-management
3
+ description: PlayCraft 项目素材管理。查看项目资产列表、生成或处理图片/音频素材、替换现有资产文件。
4
+ compatibility: agent
5
+ ---
6
+
7
+ ## Skill Definition
8
+ tools:
9
+ - bash
10
+ - read
11
+ - write
12
+ prompt_extension: |
13
+ 你负责 PlayCraft 项目的素材(资产)查询与管理。
14
+ 通过 bash 工具运行 `playcraft tools` CLI 命令完成素材生成与处理。
15
+
16
+ ## 能力清单
17
+
18
+ ### 查询项目资产
19
+ 通过 bash 运行 `playcraft tools list-assets --project-id <projectId>` 获取指定项目中的所有资产。
20
+ - 输入:projectId(数字,如 10000013);可选过滤:`--types texture,audio,script`、`--search <名称>`
21
+ - 输出:按类型分组的资产列表,含 id、name、type、fileSize
22
+
23
+ ### 读取资产文件内容
24
+ 资产文件存储在 sandbox 中。用 read 工具读取指定路径的文件,路径通常为 `/assets/<assetId>/asset.json` 或 `/files/<name>`。
25
+
26
+ ### 生成新素材
27
+ - 图片:`playcraft tools generate-image --prompt "<描述>" --output <路径> [--aspect-ratio ...] [--image-model ...]`(文生图/图生图)
28
+ - 音效:`playcraft tools generate-sfx --prompt "<英文描述>" --output <路径>`
29
+ - BGM:`playcraft tools generate-bgm --prompt "<描述>" --output <路径>`
30
+ - 精灵图合并:`playcraft image sprite-sheet --inputs <图1,图2,...> --output <基路径>`(本地生成 PNG + JSON 元数据,见 playcraft-sprite-sheet)
31
+
32
+ ### 处理/替换现有素材
33
+ - 图片:`playcraft image <子命令>`(如 `resize` / `crop` / `convert` / `remove-background` / `overlay` 等),见 playcraft-image-processing;**不存在** `playcraft tools process-image`
34
+ - write:将新文件内容写入 sandbox 中的资产路径,完成替换
35
+
36
+ ## 工作流示例
37
+
38
+ ### 查看项目中所有图片
39
+ 1. `playcraft tools list-assets --project-id <ID> --types texture`
40
+ 2. terminate — 汇报图片列表
41
+
42
+ ### 替换某张背景图
43
+ 1. `playcraft tools list-assets --search "background"` 定位资产 ID
44
+ 2. `playcraft tools generate-image --prompt "..." --output <路径>` 生成新图片
45
+ 3. `playcraft image resize --input <路径> --output <路径> --width <W> --height <H> --fit contain`(或其它 `playcraft image` 子命令)调整至目标规格
46
+ 4. write 写入资产路径
47
+ 5. terminate — 汇报完成
48
+
49
+ ## 注意事项
50
+ - projectId 是数字型 ID,可从 list_remixes 或编辑器 URL 获取
51
+ - 资产 ID 是数字(numericId),不是 UUID
52
+ - 替换文件后,变体需要重新构建才会反映到发布版本
53
+
54
+ ## 我做什么
55
+
56
+ - 列出项目中所有资产,支持按类型、名称筛选
57
+ - 生成新的图片、音频、精灵图素材
58
+ - 处理现有图片(缩放、裁剪、格式转换)
59
+ - 将生成的素材写入项目,完成替换
60
+
61
+ ## 何时用我
62
+
63
+ - "项目 X 里有哪些图片素材?"
64
+ - "帮我为项目 X 生成一张新的背景图"
65
+ - "把项目 X 的 logo 替换成一个圆角版本"
66
+ - "列出项目 X 中所有音频文件"
67
+
68
+ ## 工作流
69
+
70
+ 1. **`playcraft tools list-assets`** — 通过 bash 获取资产列表,定位目标资产
71
+ 2. **`playcraft tools generate-image` / `generate-sfx` / `generate-bgm`** 与 **`playcraft image …`**(本地处理)— 通过 bash 生成或处理素材(如需)
72
+ 3. **write** — 将新素材写入 sandbox 资产路径(如需替换)
73
+ 4. **terminate** — 汇报结果(资产清单 / 生成文件路径 / 替换状态)
@@ -0,0 +1,126 @@
1
+ ---
2
+ name: playcraft-audio-generation
3
+ description: 为可玩广告生成、处理和优化音频资产(BGM + SFX)。通过 playcraft CLI 调用平台 AI 生成与本地 ffmpeg 处理,支持体积预算管理。
4
+ compatibility: agent,opencode
5
+ ---
6
+
7
+ ## CLI 与 Atom skill(`*.aiaudio`)
8
+
9
+ - **AI 音频入口**:`playcraft tools generate-sfx`(音效)、`playcraft tools generate-bgm`(BGM)。当前 CLI **不提供** `playcraft audio gen`;后处理用 `playcraft audio <子命令>`(见下表 `## Skill Definition` → tools)。
10
+ - **参数来源**:某音频 Atom 的提示词、时长等以该目录 **`manifest.json` → `generation`** 为准,再映射到上述命令行选项。
11
+
12
+ ## Skill Definition
13
+ tools:
14
+ - bash
15
+ - read
16
+ - write
17
+ - generate_sfx
18
+ - generate_bgm
19
+ - audio_info
20
+ - audio_compress
21
+ - audio_trim
22
+ - audio_convert
23
+ - audio_fade
24
+ - audio_concat
25
+ - audio_normalize
26
+ - audio_loop
27
+ - audio_mix
28
+ prompt_extension: |
29
+ 你是可玩广告的专业音频设计师。核心原则:
30
+ - **仅 CLI**:AI 生成只用 `playcraft tools generate-sfx` / `generate-bgm`,后处理只用 `playcraft audio *`;不要索取、配置或讨论任何第三方 API 密钥,不要用 curl/SDK 直连云厂商
31
+ 1. 体积优先:可玩广告有严格体积限制(Facebook 2MB / ironSource 5MB),音频要尽可能小
32
+ 2. 先生成再优化:AI 生成高质量音频 → 本地压缩/裁剪到目标体积
33
+ 3. BGM 必须可循环:用 `playcraft audio loop --crossfade 0.5` 处理无缝循环
34
+ 4. SFX 要短促有力:通常 0.1-3s
35
+ 5. 风格一致:用 `playcraft audio normalize` 统一全套音效音量
36
+ 6. **SFX 的 `--prompt` 必须英文**:`generate-sfx` 不支持有效中文/多语种描述;用户给中文时要译成简洁英文再调用。提示词风格与基础技能 `sound-effects` 一致——具体场景、材质、风格、情绪;避免对白式长句,减少类 TTS 听感
37
+
38
+ 典型工作流程:
39
+ 1. 用 `playcraft audio info` 评估已有音频,计算体积预算
40
+ 2. 用 `playcraft tools generate-bgm` 生成 BGM,`playcraft tools generate-sfx` 生成各音效
41
+ 3. 用 `playcraft audio loop` 处理 BGM 无缝循环
42
+ 4. 用 `playcraft audio normalize` 统一所有音频音量
43
+ 5. 用 `playcraft audio info` 检查总体积
44
+ 6. 体积超标时,用 `playcraft audio compress --target-size 30KB` 逐个压缩
45
+ 7. 必要时用 `playcraft audio trim` 裁剪、`playcraft audio fade` 加淡入淡出
46
+
47
+ ## 我做什么
48
+
49
+ - 为可玩广告生成 BGM(20-30s 循环)和 SFX(点击、爆炸、胜利等)
50
+ - 本地处理:压缩、裁剪、循环化、归一化、混音
51
+ - 管理音频体积预算,确保满足平台限制
52
+
53
+ ## 何时用我
54
+
55
+ 在可玩广告项目中,需要为游戏添加背景音乐或音效时。
56
+
57
+ ## AI 生成命令(仅用 CLI)
58
+
59
+ 凭当前 Agent 会话身份调用平台;**不要**配置或讨论任何第三方 API 密钥。
60
+
61
+ ```bash
62
+ # 生成音效(SFX,--prompt 仅英文)
63
+ playcraft tools generate-sfx \
64
+ --prompt "Crisp UI button click, short tail, no voice" \
65
+ --duration 0.5 \
66
+ --output ./assets/audio/click.mp3
67
+
68
+ # 生成 BGM(Google Lyria 3,固定 30s)
69
+ playcraft tools generate-bgm \
70
+ --prompt "轻快的休闲游戏背景音乐" \
71
+ --style casual \
72
+ --bpm 100 \
73
+ --output ./assets/audio/bgm.mp3
74
+ ```
75
+
76
+ ## 本地音频处理命令(不调 API,纯本地 ffmpeg)
77
+
78
+ ```bash
79
+ # 获取音频信息(体积、时长、比特率)
80
+ playcraft audio info --input bgm.mp3
81
+ # 输出: { "duration": 30.0, "fileSize": 52000, "bitrate": 128, ... }
82
+
83
+ # 压缩(指定比特率)
84
+ playcraft audio compress --input bgm.mp3 --output bgm.mp3 --bitrate 64 --mono
85
+
86
+ # 压缩(指定目标体积,自动计算比特率)
87
+ playcraft audio compress --input bgm.mp3 --output bgm_small.mp3 --target-size 30KB
88
+
89
+ # 裁剪
90
+ playcraft audio trim --input bgm.mp3 --output intro.mp3 --start 0 --end 10
91
+
92
+ # 格式转换
93
+ playcraft audio convert --input sound.wav --output sound.mp3 --bitrate 128
94
+
95
+ # 淡入淡出
96
+ playcraft audio fade --input bgm.mp3 --output bgm.mp3 --fade-in 0.5 --fade-out 1.0
97
+
98
+ # 无缝循环处理(BGM 必做)
99
+ playcraft audio loop --input bgm.mp3 --output bgm.mp3 --crossfade 0.5
100
+
101
+ # 音量归一化(统一所有音效的音量)
102
+ playcraft audio normalize --input bgm.mp3 --output bgm.mp3 --target-loudness -16
103
+
104
+ # 拼接多个音频
105
+ playcraft audio concat --inputs intro.mp3 loop.mp3 outro.mp3 --output full.mp3
106
+
107
+ # 多轨混音(指定音量/时间偏移)
108
+ playcraft audio mix \
109
+ --inputs bgm.mp3 click.mp3 \
110
+ --volumes 0.8 1.0 \
111
+ --offsets 0 2.5 \
112
+ --output preview.mp3
113
+ ```
114
+
115
+ ## 体积预算参考
116
+
117
+ | 平台 | 总限制 | 音频建议预算 | BGM 目标大小 | SFX 目标大小 |
118
+ |------|--------|------------|------------|------------|
119
+ | Facebook | 2MB | 200-300KB | 30-60KB | 5-15KB |
120
+ | ironSource | 5MB | 500KB | 60-100KB | 10-20KB |
121
+ | Google | 5MB | 500KB | 60-100KB | 10-20KB |
122
+
123
+ ## 输出
124
+
125
+ - 通常为 MP3 格式
126
+ - 每次完成后输出体积报告(各文件大小 + 总计)