summer-engine 3.1.0 → 3.2.0

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 (366) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +27 -5
  3. package/.codex-plugin/plugin.json +28 -6
  4. package/.cursor-plugin/plugin.json +27 -5
  5. package/.factory-plugin/plugin.json +27 -5
  6. package/.opencode/plugins/summer.js +1 -1
  7. package/CHANGELOG.md +102 -0
  8. package/README.md +18 -3
  9. package/assets/audit/scene_audit.gd +4136 -0
  10. package/assets/seeing/seeing_probe.gd +1873 -0
  11. package/dist/cli/commands/skills.js +20 -2
  12. package/dist/core/api-client.d.ts +9 -1
  13. package/dist/core/api-client.js +21 -4
  14. package/dist/core/capabilities/agent-playbook.js +46 -0
  15. package/dist/core/capabilities/audit/accept.d.ts +55 -0
  16. package/dist/core/capabilities/audit/accept.js +147 -0
  17. package/dist/core/capabilities/audit/args.d.ts +95 -0
  18. package/dist/core/capabilities/audit/args.js +89 -0
  19. package/dist/core/capabilities/audit/audit.d.ts +56 -0
  20. package/dist/core/capabilities/audit/audit.js +287 -0
  21. package/dist/core/capabilities/audit/frame.d.ts +14 -0
  22. package/dist/core/capabilities/audit/frame.js +35 -0
  23. package/dist/core/capabilities/audit/gaps.d.ts +96 -0
  24. package/dist/core/capabilities/audit/gaps.js +257 -0
  25. package/dist/core/capabilities/audit/judge.d.ts +162 -0
  26. package/dist/core/capabilities/audit/judge.js +1134 -0
  27. package/dist/core/capabilities/audit/math.d.ts +110 -0
  28. package/dist/core/capabilities/audit/math.js +263 -0
  29. package/dist/core/capabilities/audit/summary.d.ts +74 -0
  30. package/dist/core/capabilities/audit/summary.js +212 -0
  31. package/dist/core/capabilities/board.d.ts +31 -0
  32. package/dist/core/capabilities/board.js +43 -0
  33. package/dist/core/capabilities/camera-view.d.ts +4 -2
  34. package/dist/core/capabilities/camera-view.js +6 -3
  35. package/dist/core/capabilities/connect-signal.d.ts +85 -0
  36. package/dist/core/capabilities/connect-signal.js +285 -0
  37. package/dist/core/capabilities/engine-fallbacks.d.ts +5 -0
  38. package/dist/core/capabilities/engine-fallbacks.js +5 -0
  39. package/dist/core/capabilities/engine-ops.d.ts +5 -1
  40. package/dist/core/capabilities/engine-ops.js +0 -3
  41. package/dist/core/capabilities/engine-receipt.d.ts +12 -0
  42. package/dist/core/capabilities/engine-receipt.js +26 -0
  43. package/dist/core/capabilities/file-read.d.ts +64 -0
  44. package/dist/core/capabilities/file-read.js +325 -0
  45. package/dist/core/capabilities/game-task-plan.js +4 -2
  46. package/dist/core/capabilities/grep.d.ts +60 -0
  47. package/dist/core/capabilities/grep.js +190 -0
  48. package/dist/core/capabilities/inspect-node.d.ts +56 -0
  49. package/dist/core/capabilities/inspect-node.js +225 -0
  50. package/dist/core/capabilities/inspect-resource.d.ts +61 -0
  51. package/dist/core/capabilities/inspect-resource.js +376 -0
  52. package/dist/core/capabilities/math3d.d.ts +28 -0
  53. package/dist/core/capabilities/math3d.js +88 -0
  54. package/dist/core/capabilities/motion-generation.d.ts +62 -0
  55. package/dist/core/capabilities/motion-generation.js +138 -0
  56. package/dist/core/capabilities/placement-batch.d.ts +102 -0
  57. package/dist/core/capabilities/placement-batch.js +483 -0
  58. package/dist/core/capabilities/placement-math.d.ts +70 -0
  59. package/dist/core/capabilities/placement-math.js +209 -0
  60. package/dist/core/capabilities/placement-script.d.ts +42 -0
  61. package/dist/core/capabilities/placement-script.js +1096 -0
  62. package/dist/core/capabilities/placement.d.ts +485 -0
  63. package/dist/core/capabilities/placement.js +1731 -0
  64. package/dist/core/capabilities/project-context.d.ts +33 -2
  65. package/dist/core/capabilities/project-context.js +87 -4
  66. package/dist/core/capabilities/replace-node.d.ts +91 -0
  67. package/dist/core/capabilities/replace-node.js +423 -0
  68. package/dist/core/capabilities/runtime-control.d.ts +18 -2
  69. package/dist/core/capabilities/runtime-control.js +67 -2
  70. package/dist/core/capabilities/scene-batch.d.ts +63 -0
  71. package/dist/core/capabilities/scene-batch.js +394 -0
  72. package/dist/core/capabilities/scene-readback.d.ts +36 -0
  73. package/dist/core/capabilities/scene-readback.js +54 -0
  74. package/dist/core/capabilities/seeing/args.d.ts +348 -0
  75. package/dist/core/capabilities/seeing/args.js +178 -0
  76. package/dist/core/capabilities/seeing/candidates.d.ts +127 -0
  77. package/dist/core/capabilities/seeing/candidates.js +282 -0
  78. package/dist/core/capabilities/seeing/math.d.ts +134 -0
  79. package/dist/core/capabilities/seeing/math.js +269 -0
  80. package/dist/core/capabilities/seeing/probe.d.ts +62 -0
  81. package/dist/core/capabilities/seeing/probe.js +188 -0
  82. package/dist/core/capabilities/seeing/scoring.d.ts +235 -0
  83. package/dist/core/capabilities/seeing/scoring.js +639 -0
  84. package/dist/core/capabilities/seeing/seeing.d.ts +233 -0
  85. package/dist/core/capabilities/seeing/seeing.js +1341 -0
  86. package/dist/core/capabilities/seeing/shot-store.d.ts +52 -0
  87. package/dist/core/capabilities/seeing/shot-store.js +204 -0
  88. package/dist/core/capabilities/surface-snap-mesh.d.ts +88 -0
  89. package/dist/core/capabilities/surface-snap-mesh.js +425 -0
  90. package/dist/core/capabilities/surface-snap.d.ts +77 -0
  91. package/dist/core/capabilities/surface-snap.js +461 -0
  92. package/dist/core/capabilities/tool-dispatch.js +231 -72
  93. package/dist/core/capabilities/tscn.d.ts +85 -0
  94. package/dist/core/capabilities/tscn.js +391 -0
  95. package/dist/core/capabilities/world-snapshot.d.ts +58 -0
  96. package/dist/core/capabilities/world-snapshot.js +150 -0
  97. package/dist/core/capability-skew.d.ts +5 -5
  98. package/dist/core/capability-skew.js +14 -6
  99. package/dist/core/headless/worker-engine-client.d.ts +1 -1
  100. package/dist/core/headless/worker-engine-client.js +2 -2
  101. package/dist/core/library-read.d.ts +37 -5
  102. package/dist/core/library-read.js +264 -33
  103. package/dist/core/library-search.d.ts +2 -2
  104. package/dist/installer/agent-config.js +5 -3
  105. package/dist/installer/agent-table.d.ts +16 -0
  106. package/dist/installer/agent-table.js +52 -17
  107. package/dist/mcp/server.js +9 -3
  108. package/dist/mcp/tools/asset-tools.js +21 -3
  109. package/dist/mcp/tools/audit-tools.d.ts +23 -0
  110. package/dist/mcp/tools/audit-tools.js +60 -0
  111. package/dist/mcp/tools/debug-tools.js +3 -1
  112. package/dist/mcp/tools/file-tools.js +17 -4
  113. package/dist/mcp/tools/generate-tools.js +74 -66
  114. package/dist/mcp/tools/library-tools.js +2 -0
  115. package/dist/mcp/tools/perception-tools.js +6 -19
  116. package/dist/mcp/tools/placement-tools.d.ts +24 -0
  117. package/dist/mcp/tools/placement-tools.js +91 -0
  118. package/dist/mcp/tools/project-tools.js +18 -15
  119. package/dist/mcp/tools/scene-tools.js +71 -83
  120. package/dist/mcp/tools/seeing-tools.d.ts +24 -0
  121. package/dist/mcp/tools/seeing-tools.js +64 -0
  122. package/dist/mcp/tools/spatial-tools.js +17 -9
  123. package/dist/mcp/tools/visual-tools.js +59 -7
  124. package/gemini-extension.json +26 -4
  125. package/integrations/amp/README.md +1 -1
  126. package/integrations/crush/README.md +1 -1
  127. package/integrations/grok-build/README.md +1 -1
  128. package/integrations/hermes/README.md +1 -1
  129. package/integrations/kimi-code/README.md +1 -1
  130. package/integrations/mistral-vibe/README.md +1 -1
  131. package/integrations/opencode/README.md +1 -1
  132. package/integrations/rovo-dev/README.md +1 -1
  133. package/integrations/vscode-copilot/README.md +1 -1
  134. package/integrations/warp/README.md +1 -1
  135. package/integrations/windsurf/README.md +1 -1
  136. package/library/references/mcp-tools-reference/mcp-tools-reference.md +59 -15
  137. package/library/references/mcp-tools-reference/resource.yaml +1 -1
  138. package/library/references/summer-folder/resource.yaml +1 -1
  139. package/library/references/summer-folder/summer-folder.md +1 -1
  140. package/library/skills/3d-lighting/SKILL.md +1 -1
  141. package/library/skills/README.md +36 -14
  142. package/library/skills/adaptive-music/SKILL.md +1 -1
  143. package/library/skills/agent-playtesting/SKILL.md +1 -1
  144. package/library/skills/ai-characters-in-games/SKILL.md +220 -0
  145. package/library/skills/ai-characters-in-games/resource.yaml +30 -0
  146. package/library/skills/ambient-bed/SKILL.md +1 -1
  147. package/library/skills/animated-loop/SKILL.md +1 -1
  148. package/library/skills/animation-tree/SKILL.md +1 -1
  149. package/library/skills/art-direction/SKILL.md +1 -1
  150. package/library/skills/asset-strategy/SKILL.md +2 -1
  151. package/library/skills/asset-strategy/resource.yaml +2 -1
  152. package/library/skills/audio-direction/SKILL.md +1 -1
  153. package/library/skills/auto-fire-targeting/SKILL.md +1 -1
  154. package/library/skills/brainstorm-game/SKILL.md +1 -1
  155. package/library/skills/brainstorming/SKILL.md +1 -1
  156. package/library/skills/browse-templates/SKILL.md +3 -3
  157. package/library/skills/browse-templates/resource.yaml +1 -1
  158. package/library/skills/camera-rigs/SKILL.md +128 -0
  159. package/library/skills/camera-rigs/resource.yaml +29 -0
  160. package/library/skills/celeste-momentum-platforming/SKILL.md +1 -1
  161. package/library/skills/character-animation-wiring/SKILL.md +1 -1
  162. package/library/skills/character-model/SKILL.md +1 -1
  163. package/library/skills/character-movement/SKILL.md +326 -0
  164. package/library/skills/character-movement/references/default-third-person-controller.gd +201 -0
  165. package/library/skills/character-movement/references/default-third-person-verification-probe.gd +154 -0
  166. package/library/skills/character-movement/references/movement-3d.md +136 -0
  167. package/library/skills/character-movement/references/platformer-feel.md +83 -0
  168. package/library/skills/character-movement/resource.yaml +40 -0
  169. package/library/skills/character-portrait/SKILL.md +1 -1
  170. package/library/skills/cinematic-cutscene/SKILL.md +1 -1
  171. package/library/skills/combat-basics/SKILL.md +159 -0
  172. package/library/skills/combat-basics/references/projectiles-and-aoe.md +100 -0
  173. package/library/skills/combat-basics/resource.yaml +34 -0
  174. package/library/skills/concept-art/SKILL.md +1 -1
  175. package/library/skills/create-asset-sheet/SKILL.md +1 -1
  176. package/library/skills/debug/SKILL.md +1 -1
  177. package/library/skills/debugging-game-feel/SKILL.md +1 -1
  178. package/library/skills/design-level/SKILL.md +1 -1
  179. package/library/skills/design-mechanic/SKILL.md +1 -1
  180. package/library/skills/design-npc/SKILL.md +1 -1
  181. package/library/skills/diagnosing-perf-regressions/SKILL.md +1 -1
  182. package/library/skills/dispatching-parallel-agents/SKILL.md +1 -1
  183. package/library/skills/driving-the-editor-ui/SKILL.md +1 -1
  184. package/library/skills/environment-kit/SKILL.md +1 -1
  185. package/library/skills/export-and-ship/SKILL.md +1 -1
  186. package/library/skills/fabricating-assets/SKILL.md +1 -1
  187. package/library/skills/facial-and-lipsync/SKILL.md +1 -1
  188. package/library/skills/fps-controller/SKILL.md +9 -7
  189. package/library/skills/fps-controller/resource.yaml +4 -1
  190. package/library/skills/game-feel/SKILL.md +1 -1
  191. package/library/skills/gameskill/SKILL.md +1 -1
  192. package/library/skills/gdscript-patterns/SKILL.md +1 -1
  193. package/library/skills/generate-motion/SKILL.md +9 -9
  194. package/library/skills/generate-motion/resource.yaml +2 -1
  195. package/library/skills/godogen-visual-proof-game-generation/SKILL.md +1 -1
  196. package/library/skills/headless-scripting/SKILL.md +1 -1
  197. package/library/skills/in-game-purchases/SKILL.md +254 -0
  198. package/library/skills/in-game-purchases/resource.yaml +29 -0
  199. package/library/skills/instantiate-asset-pack/SKILL.md +1 -1
  200. package/library/skills/investigating-bugs/SKILL.md +1 -1
  201. package/library/skills/lumera-single-image-scene-reconstruction/SKILL.md +1 -1
  202. package/library/skills/make-game/SKILL.md +25 -3
  203. package/library/skills/make-game/references/2d-platformer-polish.md +93 -0
  204. package/library/skills/make-game/references/2d-platformer.md +117 -0
  205. package/library/skills/make-game/references/2d-rpg-content.md +234 -0
  206. package/library/skills/make-game/references/2d-rpg.md +241 -0
  207. package/library/skills/make-game/references/2d-survivors-progression.md +201 -0
  208. package/library/skills/make-game/references/2d-survivors.md +237 -0
  209. package/library/skills/make-game/references/3d-fps-systems.md +216 -0
  210. package/library/skills/make-game/references/3d-fps.md +177 -0
  211. package/library/skills/make-game/references/3d-horror-systems.md +186 -0
  212. package/library/skills/make-game/references/3d-horror.md +201 -0
  213. package/library/skills/make-game/references/3d-third-person-systems.md +220 -0
  214. package/library/skills/make-game/references/3d-third-person.md +215 -0
  215. package/library/skills/make-game/resource.yaml +6 -1
  216. package/library/skills/multiplayer/SKILL.md +150 -0
  217. package/library/skills/multiplayer/resource.yaml +33 -0
  218. package/library/skills/multiplayer-movement/SKILL.md +510 -0
  219. package/library/skills/multiplayer-movement/files.md +501 -0
  220. package/library/skills/multiplayer-movement/resource.yaml +30 -0
  221. package/library/skills/multiplayer-project/SKILL.md +521 -0
  222. package/library/skills/multiplayer-project/files.md +448 -0
  223. package/library/skills/multiplayer-project/resource.yaml +36 -0
  224. package/library/skills/multiplayer-publish/SKILL.md +229 -0
  225. package/library/skills/multiplayer-publish/export-errors.md +89 -0
  226. package/library/skills/multiplayer-publish/resource.yaml +28 -0
  227. package/library/skills/multiplayer-state/SKILL.md +438 -0
  228. package/library/skills/multiplayer-state/files.md +592 -0
  229. package/library/skills/multiplayer-state/resource.yaml +35 -0
  230. package/library/skills/multiplayer-testing/SKILL.md +236 -0
  231. package/library/skills/multiplayer-testing/resource.yaml +39 -0
  232. package/library/skills/music-track/SKILL.md +1 -1
  233. package/library/skills/navigate-summer/SKILL.md +1 -1
  234. package/library/skills/new-project/SKILL.md +1 -1
  235. package/library/skills/organic-model/SKILL.md +1 -1
  236. package/library/skills/pixel-art/SKILL.md +1 -1
  237. package/library/skills/play/SKILL.md +1 -1
  238. package/library/skills/playtesting-a-feature/SKILL.md +1 -1
  239. package/library/skills/procedural-animation/SKILL.md +2 -2
  240. package/library/skills/procedural-animation/resource.yaml +1 -1
  241. package/library/skills/prop-model/SKILL.md +1 -1
  242. package/library/skills/psx-retro-rendering/SKILL.md +1 -1
  243. package/library/skills/realtime-wet-surfaces/SKILL.md +2 -2
  244. package/library/skills/remote-deploy/SKILL.md +1 -1
  245. package/library/skills/retarget/SKILL.md +1 -1
  246. package/library/skills/running-in-the-cloud/SKILL.md +1 -1
  247. package/library/skills/save-load/SKILL.md +156 -0
  248. package/library/skills/save-load/resource.yaml +27 -0
  249. package/library/skills/scene-composition/SKILL.md +1 -1
  250. package/library/skills/scene-hierarchy-design/SKILL.md +1 -1
  251. package/library/skills/scene-scripting/SKILL.md +1 -1
  252. package/library/skills/scene-to-level/SKILL.md +1 -1
  253. package/library/skills/skill-create/SKILL.md +1 -1
  254. package/library/skills/skill-improve/SKILL.md +1 -1
  255. package/library/skills/skill-test/SKILL.md +1 -1
  256. package/library/skills/skintokens-auto-rigging/SKILL.md +1 -1
  257. package/library/skills/skybox-panorama/SKILL.md +1 -1
  258. package/library/skills/sound-effect/SKILL.md +1 -1
  259. package/library/skills/spatial-placement/SKILL.md +134 -3
  260. package/library/skills/spatial-placement/references/kit-placement-tools.md +364 -0
  261. package/library/skills/spatial-placement/resource.yaml +16 -2
  262. package/library/skills/sprite-sheet/SKILL.md +1 -1
  263. package/library/skills/summer-analytics/SKILL.md +147 -0
  264. package/library/skills/summer-analytics/resource.yaml +28 -0
  265. package/library/skills/summer-friends/SKILL.md +226 -0
  266. package/library/skills/summer-friends/resource.yaml +28 -0
  267. package/library/skills/summer-leaderboards/SKILL.md +231 -0
  268. package/library/skills/summer-leaderboards/resource.yaml +30 -0
  269. package/library/skills/summer-match-results/SKILL.md +286 -0
  270. package/library/skills/summer-match-results/resource.yaml +32 -0
  271. package/library/skills/summer-matchmaking/SKILL.md +301 -0
  272. package/library/skills/summer-matchmaking/resource.yaml +32 -0
  273. package/library/skills/summer-parties/SKILL.md +253 -0
  274. package/library/skills/summer-parties/resource.yaml +30 -0
  275. package/library/skills/summer-player-data/SKILL.md +322 -0
  276. package/library/skills/summer-player-data/resource.yaml +30 -0
  277. package/library/skills/summer-store/SKILL.md +271 -0
  278. package/library/skills/summer-store/resource.yaml +29 -0
  279. package/library/skills/summer-world-chat/SKILL.md +195 -0
  280. package/library/skills/summer-world-chat/resource.yaml +30 -0
  281. package/library/skills/summer-world-saves/SKILL.md +284 -0
  282. package/library/skills/summer-world-saves/resource.yaml +29 -0
  283. package/library/skills/text-to-motion/SKILL.md +99 -0
  284. package/library/skills/text-to-motion/prompts-and-controls.md +34 -0
  285. package/library/skills/text-to-motion/resource.yaml +41 -0
  286. package/library/skills/text-to-motion/rigging-for-text-motion.md +54 -0
  287. package/library/skills/tileable-texture/SKILL.md +1 -1
  288. package/library/skills/trailer-shot/SKILL.md +1 -1
  289. package/library/skills/tune-performance/SKILL.md +1 -1
  290. package/library/skills/ui-basics/SKILL.md +10 -1
  291. package/library/skills/ui-graphics/SKILL.md +1 -1
  292. package/library/skills/use-widget-asset/SKILL.md +1 -1
  293. package/library/skills/using-summer/SKILL.md +2 -2
  294. package/library/skills/using-summer/resource.yaml +2 -2
  295. package/library/skills/vehicle-model/SKILL.md +1 -1
  296. package/library/skills/verification-before-completion/SKILL.md +1 -1
  297. package/library/skills/verifying-scenes/SKILL.md +76 -2
  298. package/library/skills/verifying-scenes/resource.yaml +13 -2
  299. package/library/skills/vfx-dissolve/SKILL.md +1 -1
  300. package/library/skills/vfx-fire/SKILL.md +1 -1
  301. package/library/skills/vfx-hit-spark/SKILL.md +1 -1
  302. package/library/skills/vfx-lightning/SKILL.md +1 -1
  303. package/library/skills/vfx-magic-glow/SKILL.md +1 -1
  304. package/library/skills/vfx-muzzle-flash/SKILL.md +1 -1
  305. package/library/skills/vfx-smoke/SKILL.md +1 -1
  306. package/library/skills/vfx-water-ripple/SKILL.md +1 -1
  307. package/library/skills/voice-line/SKILL.md +1 -1
  308. package/library/skills/web-and-touch-input/SKILL.md +38 -0
  309. package/library/skills/web-and-touch-input/resource.yaml +28 -0
  310. package/library/skills/web-compatibility-look/SKILL.md +62 -0
  311. package/library/skills/web-compatibility-look/resource.yaml +30 -0
  312. package/library/skills/world-building-3d/SKILL.md +8 -1
  313. package/library/skills/world-building-3d/references/spatial-tools.md +1 -1
  314. package/library/skills/world-building-3d/resource.yaml +2 -1
  315. package/library/skills/writing-plans/SKILL.md +1 -1
  316. package/library/skills/writing-skills/SKILL.md +1 -1
  317. package/library/templates/3d-lan-multiplayer-starter/resource.yaml +2 -2
  318. package/library/tools/attach-to-surface/resource.yaml +142 -0
  319. package/library/tools/batch/resource.yaml +9 -3
  320. package/library/tools/connect-ports/resource.yaml +120 -0
  321. package/library/tools/connect-signal/resource.yaml +3 -2
  322. package/library/tools/debug-views/resource.yaml +94 -0
  323. package/library/tools/frame-nodes/resource.yaml +104 -0
  324. package/library/tools/frame-shot/resource.yaml +134 -0
  325. package/library/tools/generate-motion/resource.yaml +44 -9
  326. package/library/tools/get-board/resource.yaml +50 -0
  327. package/library/tools/get-project-context/resource.yaml +13 -2
  328. package/library/tools/grep/resource.yaml +77 -0
  329. package/library/tools/inspect-asset/resource.yaml +71 -0
  330. package/library/tools/inspect-node/resource.yaml +9 -2
  331. package/library/tools/inspect-resource/resource.yaml +16 -5
  332. package/library/tools/instantiate-scene/resource.yaml +27 -2
  333. package/library/tools/measure/resource.yaml +96 -0
  334. package/library/tools/place-adjacent/resource.yaml +113 -0
  335. package/library/tools/play/resource.yaml +15 -2
  336. package/library/tools/raycast/resource.yaml +101 -0
  337. package/library/tools/read-file/resource.yaml +36 -2
  338. package/library/tools/read-library/resource.yaml +2 -2
  339. package/library/tools/repeat-along/resource.yaml +129 -0
  340. package/library/tools/replace-node/resource.yaml +11 -5
  341. package/library/tools/scene-audit/resource.yaml +144 -0
  342. package/library/tools/screenshot/resource.yaml +13 -2
  343. package/library/tools/search-assets/resource.yaml +18 -0
  344. package/library/tools/shot-sheet/resource.yaml +110 -0
  345. package/library/tools/snap-to-surface/resource.yaml +4 -1
  346. package/library/tools/world-snapshot/resource.yaml +24 -3
  347. package/library/tools/zoom/resource.yaml +105 -0
  348. package/package.json +1 -1
  349. package/registry/generated/aliases.json +17 -3
  350. package/registry/generated/counts.json +3 -3
  351. package/registry/generated/gemini-extension.json +26 -4
  352. package/registry/generated/index.json +2154 -469
  353. package/registry/generated/marketplace.claude.json +2 -2
  354. package/registry/generated/plugin.claude.json +27 -5
  355. package/registry/generated/plugin.codex.json +28 -6
  356. package/registry/generated/plugin.cursor.json +27 -5
  357. package/registry/generated/plugin.factory.json +27 -5
  358. package/registry/generated/skills-index.md +36 -14
  359. package/registry/generated/skills-registry.json +421 -124
  360. package/registry/generated/templates-registry.json +2 -2
  361. package/library/skills/host-authoritative-state/SKILL.md +0 -332
  362. package/library/skills/host-authoritative-state/resource.yaml +0 -28
  363. package/library/skills/peer-to-peer-multiplayer/SKILL.md +0 -373
  364. package/library/skills/peer-to-peer-multiplayer/resource.yaml +0 -28
  365. package/library/skills/setup-multiplayer/SKILL.md +0 -336
  366. package/library/skills/setup-multiplayer/resource.yaml +0 -28
@@ -0,0 +1,234 @@
1
+ # 2D Action RPG - Content Systems
2
+
3
+ The content layer on top of `2d-rpg.md`: enemies, NPCs and dialogue, quests,
4
+ inventory, loot, perks, status effects, and save/load. Everything here talks
5
+ to the rest of the game only through EventBus and the autoloads.
6
+
7
+ ## Enemies (data-driven, five archetypes)
8
+
9
+ EnemyData resource defines stats + behavior; enemy_base.gd implements all
10
+ five behaviors and reads the data. No per-enemy scenes.
11
+
12
+ ```gdscript
13
+ enum BehaviorType { CHASE, RANGED, SWARM, TANK, BOSS }
14
+ ```
15
+
16
+ - CHASE (slime): direct pursuit, melee lunge, low knockback resistance.
17
+ - RANGED (skeleton archer): holds preferred distance, fires projectiles,
18
+ strafes at range, backs off when crowded.
19
+ - SWARM (bat): fast, random jitter movement, contact damage.
20
+ - TANK (golem): slow, high HP, knockback_resistance 0.8, ground slam with
21
+ screen shake.
22
+ - BOSS (dark mage): cycling pattern (single shot -> fan of 3 -> charge),
23
+ phase 2 at 40% HP (faster everything + aura), can summon minions. Emits
24
+ `boss_health_changed` for the HUD boss bar.
25
+
26
+ EnemyData fields that matter for tuning: max_health, contact_damage,
27
+ move_speed, detection_range 200, attack_range 35, attack_cooldown 1.2,
28
+ knockback_resistance, xp_value, gold_min/max.
29
+
30
+ EnemyDatabase registers everything in code (no .tres files):
31
+ `db.get_enemy("slime")`, `db.get_enemies_for_wave(5)` returns a
32
+ wave-appropriate mix. Built-ins: slime, skeleton_archer, bat, golem,
33
+ dark_mage.
34
+
35
+ Contract: every enemy is in the "enemies" group and exposes
36
+ `take_hit(damage: int, source_position: Vector2, is_crit: bool = false)`.
37
+ Weapons and projectiles call it directly. Death drops XP + gold orbs via
38
+ `_drop_loot()` and emits `EventBus.enemy_killed(enemy, xp_value)`.
39
+
40
+ EnemySpawner is wave-based: `wave_interval` 12s, `enemies_per_wave_base` 3,
41
+ `enemies_per_wave_growth` 1, `max_enemies` 20, `difficulty_scale` 1.08
42
+ (HP/damage multiplier per wave). `spawn_boss("dark_mage", pos)` for arenas.
43
+
44
+ ## NPCs and dialogue
45
+
46
+ DialogueData is a resource with a speaker name and a lines array. Each line
47
+ is a dictionary supporting optional keys:
48
+
49
+ ```gdscript
50
+ {
51
+ "text": "Hello!",
52
+ "speaker": "Elder", # override
53
+ "choices": [{"text": "Yes", "next": 2}], # branching
54
+ "action": "start_quest:slay_monsters",
55
+ "condition": "quest_complete:slay_monsters"
56
+ }
57
+ ```
58
+
59
+ Actions: `start_quest:<id>`, `give_item:<resource_path>`, `give_gold:<n>`,
60
+ `heal`. Conditions: `quest_complete:<id>`, `quest_active:<id>`,
61
+ `has_item:<id>`. This tiny string DSL covers most RPG dialogue needs -
62
+ resist building a graph editor.
63
+
64
+ Interaction flow (the pause dance):
65
+
66
+ ```gdscript
67
+ func interact(player: Node2D) -> void:
68
+ EventBus.dialogue_started.emit(npc_name)
69
+ GameManager.change_state(GameManager.GameState.DIALOGUE) # pauses tree
70
+ await _run_dialogue(start_index)
71
+ EventBus.dialogue_ended.emit()
72
+ GameManager.change_state(GameManager.GameState.PLAYING)
73
+ player.end_interaction()
74
+ ```
75
+
76
+ NPC = CharacterBody2D + CollisionShape2D + child Area2D named EXACTLY
77
+ "InteractionArea" on the interactions layer. Player entering shows a prompt;
78
+ interact key starts dialogue. When building NPCs in code, add the node tree
79
+ first, `add_child` it, THEN set script properties (npc_name, dialogue,
80
+ quest_to_give) - properties set before the script enters the tree can be
81
+ stomped by `_ready`.
82
+
83
+ DialogueBox: typewriter at 40 chars/sec, click skips to full text, click
84
+ again advances, choices render as buttons. Must be process_mode ALWAYS (tree
85
+ is paused during dialogue).
86
+
87
+ ## Quests
88
+
89
+ QuestData: id, title, description, objectives array
90
+ (`{"id": "kill_slimes", "description": "Kill 3 slimes", "target": 3}`),
91
+ xp_reward, gold_reward, prerequisite_quest_id.
92
+
93
+ QuestManager (autoload) API: `start_quest(data)`,
94
+ `progress_objective(quest_id, objective_id, amount = 1)`,
95
+ `is_quest_active/complete(id)`, `get_objective_progress(id, obj_id)`. When
96
+ all objectives hit target the quest auto-completes, grants rewards, and
97
+ emits `quest_completed`.
98
+
99
+ Wiring kills to quests happens in the LEVEL script, not the enemy:
100
+
101
+ ```gdscript
102
+ func _ready() -> void:
103
+ EventBus.enemy_killed.connect(_on_enemy_killed)
104
+
105
+ func _on_enemy_killed(_enemy: Node2D, _xp: int) -> void:
106
+ QuestManager.progress_objective("slay_monsters", "kill_enemies")
107
+ ```
108
+
109
+ Enemies stay dumb; levels decide what counts.
110
+
111
+ ## Inventory and items
112
+
113
+ InventoryManager (autoload): 20 item slots, 3 equipment slots (weapon,
114
+ armor, accessory), gold. `add_item()` returns false when full;
115
+ `use_item()` only works on consumables; `equip_item()` auto-routes by the
116
+ item's slot; `get_total_stat_bonus("attack")` sums equipment bonuses for the
117
+ stats pipeline; `clear()` on restart.
118
+
119
+ ItemData: ItemType (CONSUMABLE/WEAPON/ARMOR/ACCESSORY/KEY_ITEM), Rarity
120
+ (COMMON/UNCOMMON/RARE/EPIC with white/green/blue/purple colors),
121
+ `stat_bonuses: Dictionary` ("attack" -> 5), heal_amount, buy/sell price.
122
+
123
+ ## Loot
124
+
125
+ - LootTable: guaranteed gold range + guaranteed XP, then `drop_chance` roll
126
+ over weighted entries (`{"item", "weight", "min_count", "max_count"}`).
127
+ `table.roll()` returns the drops.
128
+ - PickupOrb: XP/gold orbs from dead enemies, magnet-pull within 100px,
129
+ accelerating toward the player.
130
+ - TreasureChest: interactable static body - guaranteed item + gold orbs +
131
+ a loot table roll on open.
132
+ - ItemDrop: dropped items render with a rarity glow.
133
+
134
+ ## Perks (level-up progression)
135
+
136
+ PerkData: id, name, description (supports a `{value}` token), max_level 3,
137
+ `stat_modifiers: Dictionary`, `special_effect: String` + effect_value for
138
+ non-stat perks. Built-in pool:
139
+
140
+ | ID | Effect per level |
141
+ |----|------|
142
+ | attack_up | +3 attack |
143
+ | speed_boost | +20 speed |
144
+ | thick_skin | +2 defense |
145
+ | vampirism | 3% lifesteal |
146
+ | burning_strikes | apply burn on hit |
147
+ | frost_touch | 30% slow on hit |
148
+ | critical_eye | +5% crit |
149
+ | multishot | +1 projectile |
150
+ | thorns | 20% reflect |
151
+ | regeneration | 1 HP/sec |
152
+ | xp_boost | +25% XP |
153
+ | last_stand | +50% damage below 30% HP |
154
+
155
+ PerkPool: `roll_selection(3)` (unowned or upgradable only),
156
+ `acquire_perk()`, `get_stat_bonus("attack")`, `has_effect("lifesteal")` +
157
+ `get_effect_value()`, `reset()` per run.
158
+
159
+ Level-up flow: `stats.add_xp()` true -> emit `player_leveled_up` then
160
+ `level_up_ready` -> PerkSelectionUI shows 3 cards (LEVEL_UP state pauses
161
+ tree) -> pick emits `perk_selected` -> player acquires and applies stat
162
+ modifiers via `stats.add_modifier` -> resume. Special effects (lifesteal,
163
+ thorns, multishot) are checked at their point of use via
164
+ `pool.has_effect()`, not applied as stats.
165
+
166
+ ## Status effects
167
+
168
+ StatusEffect base (Node): effect_id, duration 3s, tick_interval 1s,
169
+ max_stacks 3. Override `tick()`, `on_expire()`, `on_max_stacks()`,
170
+ `get_speed_multiplier()`. `add_stack()` refreshes duration.
171
+
172
+ EffectManager attaches to any entity: `apply_effect(BurnEffect, entity)`
173
+ handles stacking, `get_speed_multiplier()` returns the product of all active
174
+ effects (feed it into movement), `remove_effect/remove_all`.
175
+
176
+ Built-ins and their max-stack payoffs (the interesting design bit -
177
+ stacking to cap triggers a burst):
178
+
179
+ - Burn: 3 dmg per 0.5s, 5 stacks; at cap, a 15x damage burst.
180
+ - Freeze: 30% slow per stack, 3 stacks; at cap, full 2s stun.
181
+ - Poison: 2 dmg/s, 3 stacks; each new stack extends duration 2s; supports
182
+ lifesteal back to the source.
183
+
184
+ ## Juice quick reference
185
+
186
+ ```gdscript
187
+ Juice.shake(4.0) # camera shake
188
+ Juice.hitstop(0.05) # freeze frame on big hits
189
+ Juice.flash_node(enemy, Color.WHITE, 0.1)
190
+ Juice.spawn_float_text(node, "+50 XP", Color.GOLD)
191
+ Juice.spawn_hit_particles(pos, Color.RED, 6)
192
+ Juice.bounce(node, 1.2) # scale bounce
193
+ Juice.register_camera(camera) # once, from player _ready()
194
+ ```
195
+
196
+ If `register_camera` is never called, shake silently does nothing - a
197
+ classic "why is there no juice" bug.
198
+
199
+ ## HUD and notifications
200
+
201
+ HUD subscribes to EventBus only: health/energy/XP bars, level, gold, active
202
+ quest list, and a notification label (fade in, 2s hold, fade out) driven by
203
+ `EventBus.notification_requested.emit("Quest complete!", Color.GOLD)`.
204
+ HUD also owns pause and inventory toggles.
205
+
206
+ ## Audio
207
+
208
+ AudioManager: `play_music(path)` crossfades (pass false for hard cut),
209
+ `play_sfx(stream, pitch_variation)`, `play_sfx_at(stream, pos, variation)`
210
+ positional. Auto-creates Music and SFX buses if missing. Town theme in the
211
+ hub, battle music in combat areas, switch on `scene_ready`.
212
+
213
+ ## Save/load
214
+
215
+ GameManager saves to `user://savegame.cfg` (ConfigFile): current level path
216
+ + spawn position, player stats (level, XP, health, attack, defense),
217
+ inventory + equipment as resource paths, gold, quest state (active with
218
+ progress, completed ids). `save_game()` from the pause menu,
219
+ `load_game() -> bool` from Continue, `has_save()` gates the button.
220
+ `restart_game()` clears inventory, resets perks, returns to town.
221
+
222
+ Save items by resource path, not serialized objects - paths survive schema
223
+ changes; serialized Resources do not.
224
+
225
+ ## Order of content work
226
+
227
+ 1. Enemy roster (CHASE first, then RANGED; TANK/SWARM/BOSS after combat
228
+ feels right) + spawner in the forest level.
229
+ 2. Loot orbs + level-up perks (progression pull).
230
+ 3. One NPC with dialogue in town, one kill quest wired through the level.
231
+ 4. Inventory + a treasure chest + equipment stat bonuses.
232
+ 5. Status effects on weapons/perks (burning_strikes, frost_touch).
233
+ 6. Boss + dungeon kill gate, then save/load last - it serializes everything
234
+ above, so build it when the shapes are stable.
@@ -0,0 +1,241 @@
1
+ # 2D Action RPG - Core
2
+
3
+ Top-down action RPG blueprint: event-driven architecture, player controller,
4
+ stats pipeline, combat components, weapons, and area transitions. NPCs,
5
+ dialogue, quests, inventory, perks, status effects, and enemies live in
6
+ `2d-rpg-content.md`.
7
+
8
+ ## Minimum playable base (build this first)
9
+
10
+ One level extending LevelBase: player with 8-way movement + one sword swing +
11
+ dodge roll, a couple of chase enemies with `take_hit()`, HUD health bar. Prove
12
+ "move, hit enemy, get hit, see numbers" before any dialogue or quest exists.
13
+
14
+ Two rules for every scene:
15
+
16
+ - Dynamic viewport: `display/window/stretch/mode = "canvas_items"`,
17
+ `stretch/aspect = "expand"`. UI via Control anchors; compute spawn points and
18
+ bounds from `get_viewport_rect().size`, never hardcoded 1920x1080.
19
+ - Playable slice: every input gets feedback through the Juice autoload (shake,
20
+ flash, float text, hitstop). A silent build fails even if every system works.
21
+
22
+ ## Architecture: modules + EventBus
23
+
24
+ Systems NEVER reference each other directly. All cross-system communication
25
+ flows through EventBus, a global signal bus autoload. Each module can be added
26
+ or removed without breaking others.
27
+
28
+ ```
29
+ core/
30
+ autoloads/{event_bus,game_manager}.gd
31
+ levels/level_base.gd
32
+ player/{player.gd,player.tscn}
33
+ modules/
34
+ audio/audio_manager.gd
35
+ combat/{hitbox_component,hurtbox_component,knockback_component,
36
+ stats,modified_value}.gd
37
+ dialogue/{dialogue_box,dialogue_data,npc}.gd
38
+ enemies/{enemy_base,enemy_data,enemy_database,enemy_spawner}.gd
39
+ effects/{status_effect,effect_manager,burn_effect,freeze_effect,poison_effect}.gd
40
+ inventory/{inventory_manager,inventory_ui,item_data}.gd
41
+ juice/juice.gd
42
+ loot/{area_transition,item_drop,loot_table,pickup_orb,treasure_chest}.gd
43
+ progression/{perk_data,perk_database,perk_pool,perk_selection_ui}.gd
44
+ quests/{quest_data,quest_manager}.gd
45
+ weapons/{weapon_base,weapon_data,weapon_manager,projectile}.gd
46
+ weapons/weapons/{sword,bow,staff,orb}.gd
47
+ content/levels/{town,forest,dungeon}.gd
48
+ ui/{hud/hud.gd, menus/{main_menu,pause_menu,game_over}.gd}
49
+ ```
50
+
51
+ Six autoloads, in this order: EventBus, GameManager, AudioManager,
52
+ QuestManager, Juice, InventoryManager.
53
+
54
+ ### EventBus signal groups (define up front)
55
+
56
+ Player: `player_health_changed`, `player_energy_changed`, `player_died`,
57
+ `player_leveled_up`, `player_xp_gained`. Combat: `damage_dealt`,
58
+ `enemy_killed(enemy, xp_value)`, `player_attack_executed`. Interaction:
59
+ `interaction_available/unavailable`, `dialogue_started/ended`. Inventory:
60
+ `item_picked_up/used/equipped/unequipped`, `gold_changed`. Quests:
61
+ `quest_started/updated/completed`, `quest_objective_progressed`. Progression:
62
+ `level_up_ready`, `perk_selected`, `perk_applied`. Scene:
63
+ `scene_transition_started/completed`, `scene_ready`. Spawner:
64
+ `wave_started/cleared`. UI: `notification_requested(text, color)`,
65
+ `boss_health_changed`.
66
+
67
+ ## Physics layers and input
68
+
69
+ | Layer | Name |
70
+ |-------|------|
71
+ | 1 | terrain |
72
+ | 2 | player |
73
+ | 3 | enemies |
74
+ | 4 | player_hitbox |
75
+ | 5 | enemy_hitbox |
76
+ | 6 | interactions |
77
+ | 7 | pickups |
78
+
79
+ Hitbox on 4/5, hurtbox monitors the opposite side's layer.
80
+
81
+ Input map (9 actions): move_up/down/left/right (WASD + arrows + stick), attack
82
+ (LMB/Space), dodge (RMB/Shift), interact (E), pause (Esc), inventory (I).
83
+
84
+ ## GameManager: states and transitions
85
+
86
+ ```gdscript
87
+ enum GameState { MAIN_MENU, PLAYING, PAUSED, DIALOGUE, INVENTORY, LEVEL_UP, GAME_OVER }
88
+ ```
89
+
90
+ Everything except MAIN_MENU/PLAYING pauses the tree; `change_state()` handles
91
+ pause/unpause. Gate gameplay input on `GameManager.is_gameplay_active()`.
92
+
93
+ Scene transition flow (fade overlay owned by GameManager):
94
+ `transition_to_scene(path, spawn_point, spawn_direction)` -> fade to black
95
+ 0.3s -> emit `scene_transition_started` -> `change_scene_to_file` -> wait TWO
96
+ process frames -> emit `scene_ready`, state PLAYING -> fade in -> emit
97
+ `scene_transition_completed`. The player reads
98
+ `GameManager.player_spawn_point` / `player_spawn_direction` in `_ready()`.
99
+ Guard with an `_is_transitioning` flag; never call transition from inside a
100
+ transition callback.
101
+
102
+ Area transitions are trivial once this exists - an Area2D with three exports
103
+ (`target_scene`, `spawn_point`, `spawn_direction`) whose `body_entered` calls
104
+ `GameManager.transition_to_scene()`. Place at map edges and doorways.
105
+
106
+ ## Player controller
107
+
108
+ ```
109
+ Player (CharacterBody2D, layer 2, group "player")
110
+ CollisionShape2D (circle, radius 8)
111
+ WeaponManager (Node2D)
112
+ HurtboxComponent (Area2D)
113
+ CollisionShape2D (circle, radius 10)
114
+ InteractionArea (Area2D)
115
+ CollisionShape2D (circle, radius 30)
116
+ Camera2D
117
+ ```
118
+
119
+ State machine: `IDLE, MOVE, ATTACK, DODGE, HURT, DEAD, INTERACT`.
120
+
121
+ ```gdscript
122
+ var input := Input.get_vector("move_left", "move_right", "move_up", "move_down")
123
+ if input.length() > 0.1:
124
+ facing = input.normalized()
125
+ velocity = facing * stats.get_speed()
126
+ state = State.MOVE
127
+ else:
128
+ velocity = velocity.move_toward(Vector2.ZERO, stats.get_speed() * 10.0 * delta)
129
+ state = State.IDLE
130
+ move_and_slide()
131
+ ```
132
+
133
+ - Attack: manual aims at mouse direction; optional auto_attack targets the
134
+ nearest enemy within 250px. Delegate to
135
+ `weapon_manager.attack(direction)` gated by `can_attack()`.
136
+ - Dodge roll: costs 20 energy (`stats.use_energy` returns false if broke),
137
+ 0.25s duration, invincible for the duration, fading trail rects.
138
+ - Hurt: ignore if invincible/dead/dodging. `stats.take_damage`, 0.5s i-frames,
139
+ knockback velocity, `Juice.shake(5)`, red flash, float text, emit
140
+ `player_health_changed`.
141
+
142
+ ## Stats: the ModifiedValue pipeline
143
+
144
+ `final = (base + additive_sum) * (1.0 + multiplicative_sum)`. Modifiers are
145
+ tagged with a source string so a whole buff can be removed in one call:
146
+
147
+ ```gdscript
148
+ stats.add_modifier("attack", 5, "fire_buff")
149
+ stats.remove_modifier("attack", "fire_buff")
150
+ ```
151
+
152
+ Stats resource: base_max_health 100, base_attack 10, base_defense 5,
153
+ base_speed 120, base_crit_chance 0.05, base_crit_multiplier 1.5. Key methods:
154
+ `take_damage()` returns `{damage, killed}`, `calculate_damage(is_crit)`,
155
+ `roll_crit()`, `add_xp()` returns true on level-up, `use_energy()`.
156
+
157
+ MUST call `stats.initialize()` in `_ready()` - forgetting it leaves the
158
+ pipeline null and everything crashes on first hit.
159
+
160
+ Level-up: +10 max HP, +2 attack, +1 defense, +3 speed, full heal.
161
+ XP needed = `50 + level * 30`.
162
+
163
+ ## Combat components (reusable trio)
164
+
165
+ - HitboxComponent (Area2D): the damage dealer. `damage`, `knockback_force`,
166
+ `hit_cooldown` (per-target cooldown dictionary), `try_hit(target) -> bool`,
167
+ `activate()/deactivate()` (deactivate clears cooldowns). Emits
168
+ `hit_landed(target, damage, knockback)`.
169
+ - HurtboxComponent (Area2D): the receiver. `invincibility_duration` 0.3s,
170
+ emits `damage_received(amount, source, knockback)` via
171
+ `take_hit(damage, source, knockback)`.
172
+ - KnockbackComponent (Node on any CharacterBody2D): decaying velocity,
173
+ `resistance` 0-1, `decay_rate` 8. `apply_knockback(force)` then add
174
+ `get_knockback_velocity()` to movement.
175
+
176
+ Attach these to player, enemies, chests - anything hittable. This trio is
177
+ what keeps combat code out of entity scripts.
178
+
179
+ ## Weapon system (data-driven)
180
+
181
+ WeaponData resource carries everything: type string, base_damage,
182
+ attack_speed (attacks/sec), knockback, range, piercing, projectile
183
+ speed/count/fan_angle, area_radius, orbit_count/speed, color. WeaponManager
184
+ (child of player) equips, switches (1-4 keys / scroll), and delegates attacks;
185
+ a `WEAPON_SCRIPTS` const maps type string -> preloaded script.
186
+
187
+ Four archetypes:
188
+
189
+ - Sword: 120-degree arc swing, alternating direction, ease-out for weight,
190
+ forward lunge, physics cone query, trail.
191
+ - Bow: 0.15s draw, then a fan of projectiles (count + fan_angle from data).
192
+ - Staff: homing bolts (homing_strength 3.0) + optional `trigger_nova()` AoE.
193
+ - Orb: passive - orbits the player, contact damage with per-enemy cooldown,
194
+ always active regardless of selected weapon.
195
+
196
+ One generic projectile script serves bow, staff, AND enemies: straight or
197
+ homing, piercing (tracks hit bodies to prevent double-hit), knockback, trail,
198
+ lifetime despawn. Configure per-shot via properties + `collision_mask` and a
199
+ `source_node` so it never hits its shooter.
200
+
201
+ ## LevelBase and level authoring
202
+
203
+ All levels extend LevelBase, which does the boring wiring once:
204
+
205
+ ```gdscript
206
+ func _ready() -> void:
207
+ _spawn_player() # instantiate player.tscn
208
+ _setup_ui() # HUD, PauseMenu, DialogueBox, Inventory, GameOver, PerkSelectionUI
209
+ _setup_connections()
210
+ ```
211
+
212
+ A concrete level sets `level_name`, calls `super._ready()`, then adds its
213
+ spawner / NPCs / AreaTransitions. Town = safe hub with NPCs. Forest = combat
214
+ area with wave spawner. Dungeon = boss arena with a kill gate.
215
+
216
+ ## Build order
217
+
218
+ 1. project.godot: 6 autoloads in order, input map, layer names, 1920x1080
219
+ canvas_items/expand.
220
+ 2. EventBus with the full signal catalog (empty handlers are fine).
221
+ 3. Stats + ModifiedValue, then the combat component trio.
222
+ 4. Player: movement -> dodge -> WeaponManager with sword only.
223
+ 5. One chase enemy + Juice feedback + HUD health bar. Playable slice check.
224
+ 6. GameManager transitions + LevelBase + a second area with AreaTransition.
225
+ 7. Content systems from `2d-rpg-content.md`: enemies, NPCs, quests,
226
+ inventory, perks, effects, save/load.
227
+
228
+ ## Traps
229
+
230
+ - Direct module references. Never `get_node("/root/QuestManager")` from a
231
+ weapon script - emit an EventBus signal. Modules only know EventBus and
232
+ their own data types; anything else rots into circular dependencies.
233
+ - Skipping `stats.initialize()`. Null pipeline, crash on first damage.
234
+ - Huge `_ready()` chains needing other nodes. Await
235
+ `get_tree().process_frame` or connect to `EventBus.scene_ready` instead.
236
+ - UI without `process_mode = ALWAYS`. Dialogue, inventory, level-up, and
237
+ pause all pause the tree; the UI must keep processing.
238
+ - Wrong hitbox/hurtbox layers. Hitbox layer 4/5, hurtbox masks the OPPOSING
239
+ side. Getting this backwards silently produces no hits.
240
+ - Renaming InteractionArea. NPC scripts access `$InteractionArea` by exact
241
+ name; a rename kills interaction with no error at parse time.
@@ -0,0 +1,201 @@
1
+ # 2D Survivors - Progression
2
+
3
+ The systems layer on top of `2d-survivors.md`: enemy roster, wave-driven
4
+ spawning, elites, level-up upgrades, the between-wave shop, weapon evolution,
5
+ and characters. Build these only after the core loop (move + auto-fire + chase)
6
+ runs.
7
+
8
+ ## Enemy roster (one script, many behaviors)
9
+
10
+ All types share `enemy_base.gd` (Area2D, "enemies" group), configured through
11
+ `setup(type, wave)` - no per-type scenes. HP/speed scale with wave number:
12
+
13
+ | Type | Behavior | Health | Speed | Special |
14
+ |---|---|---|---|---|
15
+ | Walker | chase + wobble | 15 + wave*8 | 55 + wave*2 | standard horde unit |
16
+ | Runner | fast chase | 8 + wave*4 | 110 + wave*3 | 0.7x scale |
17
+ | Tank | slow, big | 50 + wave*15 | 35 + wave*1 | 1.5x scale, 3 XP |
18
+ | Boss | massive | 500 + wave*80 | 45 | 2.8x scale, 25 XP |
19
+ | Ranged | keeps distance | 12 + wave*5 | 50 + wave*1.5 | fires projectiles |
20
+ | Charger | telegraph + dash | 20 + wave*7 | 50 + wave*1.5 | 550px/s burst |
21
+ | Spawner | stays back | 80 + wave*20 | 30 | spawns 2-3 minions |
22
+
23
+ Behavior dispatch is a `match behavior:` in `_physics_process`. Give every
24
+ enemy a randomized wobble (`_wobble_phase = randf() * TAU`, per-enemy speed and
25
+ amplitude) so hordes look organic instead of converging in a straight line.
26
+
27
+ Charger is a 4-state machine: WALK -> TELEGRAPH (0.45s, within 320px trigger
28
+ range) -> CHARGE (0.4s at 550px/s in the locked direction) -> RECOVER (0.3s),
29
+ then 3.5s cooldown. The telegraph is what makes it fair - flash/scale the sprite
30
+ during it.
31
+
32
+ Knockback: on hit, set `_knockback_vel = (pos - source).normalized() * 180`,
33
+ decay with `move_toward(Vector2.ZERO, 900 * delta)` and add to movement.
34
+
35
+ ### Hit feedback and death (non-negotiable)
36
+
37
+ `take_damage(amount, source_pos, is_crit)` must: flash the sprite, spawn a
38
+ floating damage number (bigger + colored on crit), spawn directional particles,
39
+ apply knockback, show the health bar. Death: `GameManager.add_kill()`,
40
+ `add_xp(xp_value)`, drop gold (rare health orb), particle burst, then disable
41
+ before animating out:
42
+
43
+ ```gdscript
44
+ set_physics_process(false)
45
+ set_deferred("monitoring", false)
46
+ set_deferred("monitorable", false)
47
+ var tween := create_tween().set_parallel(true)
48
+ tween.tween_property(self, "scale", scale * 1.5, 0.1)
49
+ tween.tween_property(self, "modulate:a", 0.0, 0.1)
50
+ tween.chain().tween_callback(queue_free)
51
+ ```
52
+
53
+ The `set_deferred` pair matters: freeing an Area2D mid-physics-callback without
54
+ disabling monitoring first throws errors.
55
+
56
+ ## Spawner and wave data
57
+
58
+ The spawner reads a per-wave config and spawns groups on an interval, always
59
+ off-screen (650-900px from the player, clamped to arena bounds):
60
+
61
+ ```gdscript
62
+ func _random_spawn_pos() -> Vector2:
63
+ var angle := randf() * TAU
64
+ var dist := randf_range(650.0, 900.0)
65
+ var pos := GameManager.player_node.global_position + Vector2.from_angle(angle) * dist
66
+ pos.x = clampf(pos.x, -ARENA_HALF_SIZE, ARENA_HALF_SIZE)
67
+ pos.y = clampf(pos.y, -ARENA_HALF_SIZE, ARENA_HALF_SIZE)
68
+ return pos
69
+ ```
70
+
71
+ Each WaveConfig: `enemy_weights: Dictionary`, `spawn_interval`, `group_size`,
72
+ `max_alive`, `elite_chance`, `boss: bool`, `hazards: int`, `duration`. Type
73
+ selection is weighted random over the dictionary. Hand-tune 20 waves; the ramp
74
+ that matters:
75
+
76
+ | Wave | Types | Interval | Group | Max alive | Elite % | Boss | Duration |
77
+ |---|---|---|---|---|---|---|---|
78
+ | 1 | walker | 2.5s | 2 | 25 | 0% | no | 20s |
79
+ | 3 | +runner | 2.0s | 4 | 45 | 0% | no | 20s |
80
+ | 5 | +charger | 1.7s | 5 | 65 | 3% | no | 22s |
81
+ | 8 | +tank +ranged | 1.3s | 8 | 100 | 6% | no | 22s |
82
+ | 10 | all 6 | 1.1s | 10 | 120 | 8% | YES | 30s |
83
+ | 15 | tank heavy | 0.75s | 15 | 190 | 13% | no | 28s |
84
+ | 20 | max density | 0.45s | 22 | 250 | 20% | YES | 30s |
85
+
86
+ Do NOT scale difficulty by HP alone. The ramp is spawn rate + group size +
87
+ max alive + type diversity + elite chance + new mechanics at fixed waves.
88
+ Bosses spawn 3s into their combat phase via a scene-tree timer.
89
+
90
+ Arena hazards from wave 3+: spike traps (static) and fire pools (drift at
91
+ 15-35px/s). Tick damage every 0.6s (6 + wave*1.5) to enemies in radius, half
92
+ damage to the player - positioning pressure for both sides.
93
+
94
+ ## Elites
95
+
96
+ `elite_system.gd` is a static RefCounted helper applied at spawn time with
97
+ `elite_chance` probability. One of four modifiers, 3x XP:
98
+
99
+ - FAST: speed x1.8, HP x1.3, lightened color
100
+ - TANKY: HP x3, scale x1.3, contact damage x1.5, darkened
101
+ - SPLITTING: HP x1.5, green; on death spawns 2 walker copies at 0.6x scale,
102
+ 0.3x HP (use `call_deferred("add_child", copy)` - you are inside a physics
103
+ callback)
104
+ - VAMPIRIC: HP x2; heals 8% max HP every 3s
105
+
106
+ Give elites a visible pulse tween so players can prioritize them.
107
+
108
+ ## Two-track progression
109
+
110
+ Track 1 - mid-wave level-ups: on level-up the tree pauses, 3 random stat
111
+ upgrades from the pool are offered, each stackable to level 3. Also vacuum all
112
+ pickups (`attract()` on the "pickups" group) as a reward beat.
113
+
114
+ | Upgrade | Per level | Stat touched |
115
+ |---|---|---|
116
+ | Might | +15% damage | player_damage_mult |
117
+ | Haste | +12% attack speed | player_attack_speed_mult |
118
+ | Swiftness | +20 speed | player_speed |
119
+ | Vitality | +15 max HP (and heal 15) | player_max_health |
120
+ | Magnetism | +20 pickup range | player_pickup_range |
121
+ | Armor | +1 flat reduction | player_armor |
122
+ | Regeneration | +0.5 HP/s | player_regen |
123
+ | Critical Eye | +5% crit | player_crit_chance |
124
+
125
+ `get_random_choices(3)` filters out maxed upgrades, shuffles, takes 3.
126
+ `apply_upgrade` bumps level and writes the stat straight onto GameManager.
127
+ Armor applies as `maxi(1, amount - player_armor)` - always at least 1 damage.
128
+
129
+ Track 2 - between-wave shop: 4 random items, gold-priced, 4 tiers (Common /
130
+ Uncommon / Rare / Legendary). Tier odds shift with wave - by wave 16+:
131
+ 15% Legendary / 30% Rare / 35% Uncommon / 20% Common; waves 1-5 have no
132
+ Legendary at all. Prices scale `cost_base * (1.0 + wave * 0.12)`; reroll costs
133
+ `2 + reroll_count + wave / 5` (escalates within a single shop visit).
134
+
135
+ Items are either stat dictionaries applied to GameManager
136
+ (`{"player_armor": 2}`) or weapon grants that call
137
+ `player.add_or_upgrade_weapon(id)`. The interesting ones trade off:
138
+
139
+ ```gdscript
140
+ _add("glass_cannon", "Glass Cannon", 2, 40,
141
+ {"player_damage_mult": 0.35, "player_max_health": -25}, ["offense"])
142
+ _add("berserker_mask", "Berserker Mask", 2, 45,
143
+ {"player_damage_mult": 0.25, "player_attack_speed_mult": 0.20,
144
+ "player_armor": -3}, ["offense"])
145
+ ```
146
+
147
+ Do not skip the shop. Without a between-wave decision point the game is just
148
+ "survive longer"; trade-off items are what create builds.
149
+
150
+ ## Weapon evolution
151
+
152
+ A base weapon evolves when the player owns it AND a specific catalyst upgrade
153
+ is maxed (level 3). Check after every upgrade pick and shop purchase; emit
154
+ `evolution_available` once per recipe:
155
+
156
+ | Evolved | Base weapon | Catalyst | Effect |
157
+ |---|---|---|---|
158
+ | Piercing Barrage | Projectile | Multi Shot | pierces 4+, faster bullets |
159
+ | Radiant Barrier | Orbital | Armor | 4 large orbitals + damage pulses |
160
+ | Supernova | Holy Nova | Might | bigger AoE + burning ground DOT |
161
+
162
+ Evolving replaces the node: `queue_free()` the old weapon, erase from the
163
+ player's `_weapons` dict, instantiate the evolved script, screen flash + zoom
164
+ pulse. DOT zones (Supernova) are a Polygon2D + tween loop: N tick callbacks at
165
+ 0.5s intervals doing a group distance scan, then fade and free. Guard every
166
+ tick with `is_instance_valid` - the zone can outlive enemies and vice versa.
167
+
168
+ ## Characters
169
+
170
+ Four picks before the run, each a starting weapon + stat override dictionary:
171
+
172
+ - Sentinel: projectile, no overrides (baseline)
173
+ - Berserker: projectile, damage_mult 1.4, speed 240, max HP 80
174
+ - Warden: orbital, max HP 150, armor 3, attack_speed 0.85
175
+ - Arcanist: nova, attack_speed 1.25, damage_mult 1.15, max HP 70
176
+
177
+ `GameManager.reset_run()` resets all stats to base, then applies
178
+ `selected_character.stat_overrides` via `set(key, value)` and fills health to
179
+ max. Character select is just 4 cards that set `selected_character` and load
180
+ the arena.
181
+
182
+ ## UI stack (all procedural, all CanvasLayer, all process_mode ALWAYS)
183
+
184
+ - HUD: health + dash bars top-left, wave counter + timer top-center,
185
+ gold/kills/level top-right, full-width 6px XP bar at the bottom.
186
+ - LevelUpUI (layer 20): dark overlay, 3 cards, gamepad focus.
187
+ - ShopUI (layer 25): stats panel left, 4 tier-colored item cards, reroll +
188
+ next-wave buttons, "SOLD" placeholders, live stat refresh after purchase.
189
+ - PauseMenu / GameOverUI (layer 40): game over shows victory or death plus
190
+ time / kills / level / wave, retry + menu.
191
+
192
+ Retry = `get_tree().paused = false` THEN `reload_current_scene()`. Forgetting
193
+ the unpause is the classic frozen-retry bug.
194
+
195
+ ## Audio integration points
196
+
197
+ Pooled AudioManager (16 global + 16 positional players, round-robin; all play
198
+ methods null-safe so missing streams never crash). Pitch variance 5% global /
199
+ 10% positional prevents machine-gun repetition. Hook: enemy hit + death
200
+ (positional), pickup collect, level-up, shop purchase, weapon fire (-6dB, it
201
+ fires constantly), music on combat start with fade.