anima-core 0.2.1 → 1.0.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 (280) hide show
  1. checksums.yaml +4 -4
  2. data/.reek.yml +27 -1
  3. data/CHANGELOG.md +19 -0
  4. data/README.md +213 -43
  5. data/agents/codebase-analyzer.md +88 -0
  6. data/agents/codebase-pattern-finder.md +83 -0
  7. data/agents/documentation-researcher.md +59 -0
  8. data/agents/thoughts-analyzer.md +102 -0
  9. data/agents/web-search-researcher.md +71 -0
  10. data/anima-core.gemspec +3 -0
  11. data/app/channels/session_channel.rb +195 -45
  12. data/app/decorators/user_message_decorator.rb +16 -5
  13. data/app/jobs/agent_request_job.rb +55 -2
  14. data/app/jobs/analytical_brain_job.rb +33 -0
  15. data/app/jobs/count_event_tokens_job.rb +15 -4
  16. data/app/models/concerns/event/broadcasting.rb +81 -0
  17. data/app/models/event.rb +20 -1
  18. data/app/models/goal.rb +91 -0
  19. data/app/models/session.rb +366 -21
  20. data/config/application.rb +2 -0
  21. data/config/initializers/event_subscribers.rb +0 -1
  22. data/config/routes.rb +0 -6
  23. data/db/migrate/20260313010000_add_status_to_events.rb +8 -0
  24. data/db/migrate/20260313020000_add_processing_to_sessions.rb +7 -0
  25. data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
  26. data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
  27. data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
  28. data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
  29. data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
  30. data/db/migrate/20260315140843_create_goals.rb +16 -0
  31. data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
  32. data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
  33. data/lib/agent_loop.rb +65 -6
  34. data/lib/agents/definition.rb +116 -0
  35. data/lib/agents/registry.rb +106 -0
  36. data/lib/analytical_brain/runner.rb +276 -0
  37. data/lib/analytical_brain/tools/activate_skill.rb +52 -0
  38. data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
  39. data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
  40. data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
  41. data/lib/analytical_brain/tools/finish_goal.rb +62 -0
  42. data/lib/analytical_brain/tools/read_workflow.rb +58 -0
  43. data/lib/analytical_brain/tools/rename_session.rb +63 -0
  44. data/lib/analytical_brain/tools/set_goal.rb +60 -0
  45. data/lib/analytical_brain/tools/update_goal.rb +60 -0
  46. data/lib/analytical_brain.rb +23 -0
  47. data/lib/anima/cli/mcp/secrets.rb +76 -0
  48. data/lib/anima/cli/mcp.rb +197 -0
  49. data/lib/anima/cli.rb +5 -40
  50. data/lib/anima/installer.rb +168 -0
  51. data/lib/anima/settings.rb +226 -0
  52. data/lib/anima/version.rb +1 -1
  53. data/lib/anima.rb +9 -0
  54. data/lib/credential_store.rb +103 -0
  55. data/lib/environment_probe.rb +232 -0
  56. data/lib/events/subscribers/persister.rb +1 -0
  57. data/lib/events/user_message.rb +17 -0
  58. data/lib/llm/client.rb +29 -10
  59. data/lib/mcp/client_manager.rb +86 -0
  60. data/lib/mcp/config.rb +213 -0
  61. data/lib/mcp/health_check.rb +77 -0
  62. data/lib/mcp/secrets.rb +73 -0
  63. data/lib/mcp/stdio_transport.rb +206 -0
  64. data/lib/providers/anthropic.rb +11 -20
  65. data/lib/shell_session.rb +11 -10
  66. data/lib/skills/definition.rb +97 -0
  67. data/lib/skills/registry.rb +105 -0
  68. data/lib/tools/edit.rb +226 -0
  69. data/lib/tools/mcp_tool.rb +114 -0
  70. data/lib/tools/read.rb +151 -0
  71. data/lib/tools/registry.rb +14 -12
  72. data/lib/tools/request_feature.rb +121 -0
  73. data/lib/tools/return_result.rb +81 -0
  74. data/lib/tools/spawn_specialist.rb +109 -0
  75. data/lib/tools/spawn_subagent.rb +111 -0
  76. data/lib/tools/subagent_prompts.rb +12 -0
  77. data/lib/tools/web_get.rb +8 -9
  78. data/lib/tools/write.rb +86 -0
  79. data/lib/tui/app.rb +985 -26
  80. data/lib/tui/cable_client.rb +69 -31
  81. data/lib/tui/message_store.rb +103 -8
  82. data/lib/tui/screens/chat.rb +293 -45
  83. data/lib/workflows/definition.rb +97 -0
  84. data/lib/workflows/registry.rb +89 -0
  85. data/skills/activerecord/SKILL.md +255 -0
  86. data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
  87. data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
  88. data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
  89. data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
  90. data/skills/activerecord/examples/associations/self_referential.rb +302 -0
  91. data/skills/activerecord/examples/associations/through_associations.rb +203 -0
  92. data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
  93. data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
  94. data/skills/activerecord/examples/basics/inheritance.rb +377 -0
  95. data/skills/activerecord/examples/basics/type_casting.rb +317 -0
  96. data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
  97. data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
  98. data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
  99. data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
  100. data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
  101. data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
  102. data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
  103. data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
  104. data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
  105. data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
  106. data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
  107. data/skills/activerecord/examples/querying/optimization.rb +275 -0
  108. data/skills/activerecord/examples/querying/scopes.rb +260 -0
  109. data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
  110. data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
  111. data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
  112. data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
  113. data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
  114. data/skills/activerecord/references/associations.md +709 -0
  115. data/skills/activerecord/references/basics.md +622 -0
  116. data/skills/activerecord/references/callbacks.md +738 -0
  117. data/skills/activerecord/references/migrations.md +657 -0
  118. data/skills/activerecord/references/querying.md +655 -0
  119. data/skills/activerecord/references/validations.md +596 -0
  120. data/skills/dragonruby/SKILL.md +250 -0
  121. data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
  122. data/skills/dragonruby/examples/audio/background_music.rb +29 -0
  123. data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
  124. data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
  125. data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
  126. data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
  127. data/skills/dragonruby/examples/core/hello_world.rb +24 -0
  128. data/skills/dragonruby/examples/core/labels.rb +22 -0
  129. data/skills/dragonruby/examples/core/sprites.rb +35 -0
  130. data/skills/dragonruby/examples/core/state_management.rb +29 -0
  131. data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
  132. data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
  133. data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
  134. data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
  135. data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
  136. data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
  137. data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
  138. data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
  139. data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
  140. data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
  141. data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
  142. data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
  143. data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
  144. data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
  145. data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
  146. data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
  147. data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
  148. data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
  149. data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
  150. data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
  151. data/skills/dragonruby/examples/input/controller_input.rb +28 -0
  152. data/skills/dragonruby/examples/input/directional_input.rb +24 -0
  153. data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
  154. data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
  155. data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
  156. data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
  157. data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
  158. data/skills/dragonruby/examples/rendering/labels.rb +32 -0
  159. data/skills/dragonruby/examples/rendering/layering.rb +51 -0
  160. data/skills/dragonruby/examples/rendering/solids.rb +61 -0
  161. data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
  162. data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
  163. data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
  164. data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
  165. data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
  166. data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
  167. data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
  168. data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
  169. data/skills/dragonruby/references/audio.md +396 -0
  170. data/skills/dragonruby/references/core.md +385 -0
  171. data/skills/dragonruby/references/distribution.md +434 -0
  172. data/skills/dragonruby/references/entities.md +516 -0
  173. data/skills/dragonruby/references/game-logic/persistence.md +386 -0
  174. data/skills/dragonruby/references/game-logic/state.md +389 -0
  175. data/skills/dragonruby/references/input.md +414 -0
  176. data/skills/dragonruby/references/rendering/animation.md +467 -0
  177. data/skills/dragonruby/references/rendering/primitives.md +403 -0
  178. data/skills/dragonruby/references/scenes.md +443 -0
  179. data/skills/draper-decorators/SKILL.md +344 -0
  180. data/skills/draper-decorators/examples/application_decorator.rb +61 -0
  181. data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
  182. data/skills/draper-decorators/examples/model_decorator.rb +152 -0
  183. data/skills/draper-decorators/references/anti-patterns.md +640 -0
  184. data/skills/draper-decorators/references/patterns.md +507 -0
  185. data/skills/draper-decorators/references/testing.md +559 -0
  186. data/skills/gh-issue.md +182 -0
  187. data/skills/mcp-server/SKILL.md +177 -0
  188. data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
  189. data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
  190. data/skills/mcp-server/examples/http_client.rb +48 -0
  191. data/skills/mcp-server/examples/http_server.rb +97 -0
  192. data/skills/mcp-server/examples/rails_integration.rb +88 -0
  193. data/skills/mcp-server/examples/stdio_server.rb +108 -0
  194. data/skills/mcp-server/examples/streaming_client.rb +95 -0
  195. data/skills/mcp-server/references/gotchas.md +183 -0
  196. data/skills/mcp-server/references/prompts.md +98 -0
  197. data/skills/mcp-server/references/resources.md +53 -0
  198. data/skills/mcp-server/references/server.md +140 -0
  199. data/skills/mcp-server/references/tools.md +146 -0
  200. data/skills/mcp-server/references/transport.md +104 -0
  201. data/skills/ratatui-ruby/SKILL.md +315 -0
  202. data/skills/ratatui-ruby/references/core-concepts.md +340 -0
  203. data/skills/ratatui-ruby/references/events.md +387 -0
  204. data/skills/ratatui-ruby/references/frameworks.md +522 -0
  205. data/skills/ratatui-ruby/references/layout.md +423 -0
  206. data/skills/ratatui-ruby/references/styling.md +268 -0
  207. data/skills/ratatui-ruby/references/testing.md +433 -0
  208. data/skills/ratatui-ruby/references/widgets.md +532 -0
  209. data/skills/rspec/SKILL.md +340 -0
  210. data/skills/rspec/examples/core/basic_structure.rb +69 -0
  211. data/skills/rspec/examples/core/configuration.rb +126 -0
  212. data/skills/rspec/examples/core/hooks.rb +126 -0
  213. data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
  214. data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
  215. data/skills/rspec/examples/core/shared_examples.rb +145 -0
  216. data/skills/rspec/examples/factory_bot/associations.rb +314 -0
  217. data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
  218. data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
  219. data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
  220. data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
  221. data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
  222. data/skills/rspec/examples/factory_bot/traits.rb +293 -0
  223. data/skills/rspec/examples/factory_bot/transients.rb +229 -0
  224. data/skills/rspec/examples/matchers/change.rb +115 -0
  225. data/skills/rspec/examples/matchers/collections.rb +154 -0
  226. data/skills/rspec/examples/matchers/comparisons.rb +79 -0
  227. data/skills/rspec/examples/matchers/composing.rb +155 -0
  228. data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
  229. data/skills/rspec/examples/matchers/equality.rb +58 -0
  230. data/skills/rspec/examples/matchers/errors.rb +136 -0
  231. data/skills/rspec/examples/matchers/output.rb +103 -0
  232. data/skills/rspec/examples/matchers/predicates.rb +87 -0
  233. data/skills/rspec/examples/matchers/truthiness.rb +101 -0
  234. data/skills/rspec/examples/matchers/types.rb +82 -0
  235. data/skills/rspec/examples/matchers/yield.rb +147 -0
  236. data/skills/rspec/examples/mocks/any_instance.rb +172 -0
  237. data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
  238. data/skills/rspec/examples/mocks/constants.rb +177 -0
  239. data/skills/rspec/examples/mocks/doubles.rb +139 -0
  240. data/skills/rspec/examples/mocks/expectations.rb +137 -0
  241. data/skills/rspec/examples/mocks/message_chains.rb +173 -0
  242. data/skills/rspec/examples/mocks/ordering.rb +144 -0
  243. data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
  244. data/skills/rspec/examples/mocks/responses.rb +223 -0
  245. data/skills/rspec/examples/mocks/spies.rb +149 -0
  246. data/skills/rspec/examples/mocks/stubbing.rb +133 -0
  247. data/skills/rspec/examples/rails/channels.rb +250 -0
  248. data/skills/rspec/examples/rails/controller_specs.rb +302 -0
  249. data/skills/rspec/examples/rails/helper_specs.rb +245 -0
  250. data/skills/rspec/examples/rails/job_specs.rb +256 -0
  251. data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
  252. data/skills/rspec/examples/rails/matchers.rb +374 -0
  253. data/skills/rspec/examples/rails/model_specs.rb +193 -0
  254. data/skills/rspec/examples/rails/request_specs.rb +275 -0
  255. data/skills/rspec/examples/rails/routing_specs.rb +276 -0
  256. data/skills/rspec/examples/rails/system_specs.rb +294 -0
  257. data/skills/rspec/examples/rails/transactions.rb +254 -0
  258. data/skills/rspec/examples/rails/view_specs.rb +252 -0
  259. data/skills/rspec/references/core.md +816 -0
  260. data/skills/rspec/references/factory_bot.md +641 -0
  261. data/skills/rspec/references/matchers.md +516 -0
  262. data/skills/rspec/references/mocks.md +381 -0
  263. data/skills/rspec/references/rails.md +528 -0
  264. data/templates/soul.md +40 -0
  265. data/workflows/commit.md +45 -0
  266. data/workflows/create_handoff.md +98 -0
  267. data/workflows/create_note.md +82 -0
  268. data/workflows/create_plan.md +457 -0
  269. data/workflows/decompose_ticket.md +109 -0
  270. data/workflows/feature.md +91 -0
  271. data/workflows/implement_plan.md +87 -0
  272. data/workflows/iterate_plan.md +247 -0
  273. data/workflows/research_codebase.md +210 -0
  274. data/workflows/resume_handoff.md +217 -0
  275. data/workflows/review_pr.md +320 -0
  276. data/workflows/thoughts_init.md +71 -0
  277. data/workflows/validate_plan.md +166 -0
  278. metadata +290 -3
  279. data/app/controllers/api/sessions_controller.rb +0 -25
  280. data/lib/events/subscribers/action_cable_bridge.rb +0 -59
@@ -0,0 +1,250 @@
1
+ ---
2
+ name: dragonruby
3
+ description: "DragonRuby 2D game development — game loops, sprites, input, collisions, scenes. Activate when building games with DragonRuby/DRGTK, working with args.outputs/state/inputs, or editing game files."
4
+ ---
5
+
6
+ # DragonRuby Game Toolkit
7
+
8
+ This skill provides comprehensive guidance for building 2D games with DragonRuby Game Toolkit (DRGTK). Use for game loop implementation, sprite rendering, input handling, collision detection, animation, and scene management.
9
+
10
+ ## Quick Reference
11
+
12
+ ### Basic Game Structure
13
+
14
+ ```ruby
15
+ def boot args
16
+ args.state = {}
17
+ end
18
+
19
+ def tick args
20
+ # Called 60 times per second
21
+ args.state.player ||= { x: 640, y: 360, w: 50, h: 50, path: 'player.png' }
22
+
23
+ # Handle input
24
+ args.state.player.x += 5 if args.inputs.right
25
+ args.state.player.x -= 5 if args.inputs.left
26
+
27
+ # Render
28
+ args.outputs.sprites << args.state.player
29
+ end
30
+ ```
31
+
32
+ ### Key Concepts
33
+
34
+ | Concept | Purpose |
35
+ |---------|---------|
36
+ | `def tick(args)` | Main game loop (60 FPS) |
37
+ | `args.outputs` | Render sprites, labels, primitives |
38
+ | `args.state` | Persistent game data storage |
39
+ | `args.inputs` | Keyboard, mouse, controller input |
40
+ | `args.grid` | Screen dimensions (1280x720) |
41
+ | `Geometry` | Collision detection helpers |
42
+
43
+ ### Coordinate System
44
+
45
+ - **Screen**: 1280x720 pixels
46
+ - **Origin**: Bottom-left (0, 0)
47
+ - **Y-axis**: Increases upward
48
+
49
+ ```
50
+ (0, 720) ─────────────── (1280, 720)
51
+ │ │
52
+ │ 1280 × 720 │
53
+ │ │
54
+ (0, 0) ─────────────── (1280, 0)
55
+ ```
56
+
57
+ ## Rendering Primitives
58
+
59
+ Use `args.outputs.primitives` for FIFO (first-in, first-out) render order control.
60
+
61
+ ### Sprites (Images)
62
+
63
+ ```ruby
64
+ args.outputs.primitives << {
65
+ x: 100, y: 100, w: 64, h: 64,
66
+ path: 'sprites/player.png',
67
+ angle: 45,
68
+ anchor_x: 0.5, anchor_y: 0.5,
69
+ r: 255, g: 255, b: 255, a: 255,
70
+ flip_horizontally: false
71
+ }
72
+ ```
73
+
74
+ ### Labels (Text)
75
+
76
+ ```ruby
77
+ args.outputs.primitives << {
78
+ x: 640, y: 360,
79
+ text: "Score: #{args.state.score}",
80
+ size_px: 22,
81
+ anchor_x: 0.5, anchor_y: 0.5,
82
+ r: 255, g: 255, b: 255
83
+ }
84
+ ```
85
+
86
+ ### Solids and Borders
87
+
88
+ ```ruby
89
+ # Filled rectangle (use primitive_marker: :solid)
90
+ args.outputs.primitives << {
91
+ x: 0, y: 0, w: 100, h: 100,
92
+ r: 255, g: 0, b: 0,
93
+ primitive_marker: :solid
94
+ }
95
+
96
+ # For many rectangles, use path: :solid for better performance
97
+ args.outputs.primitives << { x: 0, y: 0, w: 100, h: 100, path: :solid, r: 255, g: 0, b: 0 }
98
+
99
+ # Outline rectangle
100
+ args.outputs.primitives << { x: 0, y: 0, w: 100, h: 100, r: 0, g: 0, b: 0, primitive_marker: :border }
101
+ ```
102
+
103
+ ## State Management
104
+
105
+ Use `args.state` with `||=` for lazy initialization:
106
+
107
+ ```ruby
108
+ def tick args
109
+ args.state.player ||= { x: 640, y: 360 }
110
+ args.state.enemies ||= []
111
+ args.state.score ||= 0
112
+ args.state.scene ||= :title
113
+ end
114
+ ```
115
+
116
+ ## Input Handling
117
+
118
+ ### Unified Input (Keyboard + Controller)
119
+
120
+ ```ruby
121
+ # Directional (arrows, WASD, gamepad)
122
+ args.inputs.up / down / left / right
123
+
124
+ # Magnitude values
125
+ args.inputs.left_right # -1, 0, or 1
126
+ args.inputs.up_down # -1, 0, or 1
127
+ ```
128
+
129
+ ### Keyboard
130
+
131
+ ```ruby
132
+ args.inputs.keyboard.key_down.space # Pressed this frame
133
+ args.inputs.keyboard.key_held.space # Held down
134
+ args.inputs.keyboard.key_up.space # Released this frame
135
+ ```
136
+
137
+ ### Mouse
138
+
139
+ ```ruby
140
+ args.inputs.mouse.click # Any button clicked
141
+ args.inputs.mouse.x / .y # Position
142
+ args.inputs.mouse.inside_rect?(rect) # Collision check
143
+ ```
144
+
145
+ ## Collision Detection
146
+
147
+ ```ruby
148
+ if Geometry.intersect_rect?(player, enemy)
149
+ enemy.dead = true
150
+ args.state.score += 1
151
+ end
152
+
153
+ # Clean up dead entities
154
+ args.state.enemies.reject! { |e| e.dead }
155
+ ```
156
+
157
+ ## Animation
158
+
159
+ ```ruby
160
+ # Frame-based animation
161
+ sprite_index = 0.frame_index(count: 6, hold_for: 8, repeat: true)
162
+ args.state.player.path = "sprites/player-#{sprite_index}.png"
163
+ ```
164
+
165
+ ## Scene Management
166
+
167
+ ```ruby
168
+ def tick args
169
+ args.state.scene ||= :title
170
+ send("#{args.state.scene}_tick", args)
171
+ end
172
+
173
+ def title_tick args
174
+ args.outputs.labels << { x: 640, y: 400, text: "Press SPACE", anchor_x: 0.5 }
175
+ args.state.scene = :gameplay if args.inputs.keyboard.key_down.space
176
+ end
177
+
178
+ def gameplay_tick args
179
+ # Game logic here
180
+ end
181
+ ```
182
+
183
+ ## Best Practices
184
+
185
+ ### Do
186
+
187
+ - Use hash syntax for sprites/labels (clearer than arrays)
188
+ - Use `||=` for state initialization
189
+ - Remove offscreen entities to prevent memory leaks
190
+ - Update logic before rendering
191
+ - Use `$gtk.reset` during development to reset state
192
+ - Use `args.outputs.primitives` for FIFO render order control
193
+ - Use `primitive_marker: :solid` or `:border` for rectangle types
194
+
195
+ ### Don't
196
+
197
+ - Hardcode magic numbers (use constants like `FPS = 60`)
198
+ - Forget early returns in scene methods
199
+ - Render before updating state (causes 1-frame lag)
200
+ - Let collections grow infinitely (reject dead entities)
201
+
202
+ ## Additional Resources
203
+
204
+ ### Reference Files
205
+
206
+ For detailed API documentation and patterns:
207
+
208
+ - **`references/core.md`** - Game loop, args object, rendering, coordinates
209
+ - **`references/input.md`** - Keyboard, mouse, controller input patterns
210
+ - **`references/entities.md`** - Entity spawning, collision, lifecycle
211
+ - **`references/game-logic/state.md`** - Timers, scoring, scene transitions
212
+ - **`references/game-logic/persistence.md`** - Save/load, file I/O patterns
213
+ - **`references/audio.md`** - Sound effects, music playback, audio controls
214
+ - **`references/rendering/primitives.md`** - Sprites, labels, solids, borders, layering
215
+ - **`references/rendering/animation.md`** - frame_index, spritesheets, easing functions
216
+
217
+ ### Example Files
218
+
219
+ Working code in `examples/`:
220
+
221
+ - **`examples/core/`** - Hello world, sprites, labels, state, coordinates
222
+ - **`examples/input/`** - Directional, keyboard, mouse, movement
223
+ - **`examples/entities/`** - Storage, factories, collision, lifecycle
224
+ - **`examples/game-logic/`** - Timers, scoring, save/load, state transitions
225
+ - **`examples/audio/`** - Sound effects, background music, pause/resume
226
+ - **`examples/rendering/`** - Sprites, labels, animation, layering
227
+
228
+ ## Development Workflow
229
+
230
+ ```ruby
231
+ def tick args
232
+ # Game logic
233
+ end
234
+
235
+ $gtk.reset # Add at end during development
236
+ ```
237
+
238
+ ### Reset Methods
239
+
240
+ ```ruby
241
+ $gtk.reset # Immediate reset
242
+ $gtk.reset_next_tick # Reset before next tick (safer)
243
+ ```
244
+
245
+ ### Debug Output
246
+
247
+ ```ruby
248
+ args.outputs.debug << "Frame: #{Kernel.tick_count}"
249
+ args.outputs.debug.watch args.state.player
250
+ ```
@@ -0,0 +1,55 @@
1
+ # audio_events.rb
2
+ # Sound effects triggered by game events
3
+
4
+ def tick(args)
5
+ defaults(args)
6
+ input(args)
7
+ calc(args)
8
+ render(args)
9
+ end
10
+
11
+ def defaults(args)
12
+ args.state.player ||= { x: 640, y: 360, w: 50, h: 50, dy: 0 }
13
+ args.state.coins ||= [{ x: 700, y: 360, w: 30, h: 30 }]
14
+ args.state.timer ||= 0
15
+ end
16
+
17
+ def input(args)
18
+ player = args.state.player
19
+
20
+ # Sound on input event (jump)
21
+ if args.inputs.keyboard.key_down.space && player.dy == 0
22
+ args.outputs.sounds << "sounds/jump.wav"
23
+ player.dy = 10
24
+ end
25
+ end
26
+
27
+ def calc(args)
28
+ player = args.state.player
29
+
30
+ # Gravity
31
+ player.dy -= 0.5
32
+ player.y += player.dy
33
+ player.y = 360 if player.y <= 360
34
+ player.dy = 0 if player.y == 360
35
+
36
+ # Sound on collision event (coin pickup)
37
+ args.state.coins.reject! do |coin|
38
+ if player.intersect_rect?(coin)
39
+ args.outputs.sounds << { path: "sounds/coin.wav", gain: 0.8 }
40
+ true
41
+ end
42
+ end
43
+
44
+ # Sound on timer event (every 3 seconds)
45
+ args.state.timer += 1
46
+ if args.state.timer.zmod?(180)
47
+ args.outputs.sounds << { path: "sounds/ambient.wav", gain: 0.3 }
48
+ end
49
+ end
50
+
51
+ def render(args)
52
+ args.outputs.solids << args.state.player
53
+ args.outputs.solids << args.state.coins.map { |c| c.merge(r: 255, g: 200, b: 0) }
54
+ args.outputs.labels << { x: 10, y: 720, text: "SPACE to jump (input event)" }
55
+ end
@@ -0,0 +1,29 @@
1
+ # background_music.rb
2
+ # Looping music using args.audio[:key]
3
+
4
+ def tick(args)
5
+ # Initialize music once - loops automatically
6
+ if Kernel.tick_count == 0
7
+ args.audio[:music] = {
8
+ input: "sounds/music.ogg", # Path to music file
9
+ gain: 0.8, # Volume (0.0 to 1.0)
10
+ looping: true # Loop when finished
11
+ }
12
+ end
13
+
14
+ # Stop music with Q key
15
+ if args.inputs.keyboard.key_down.q
16
+ args.audio[:music] = nil
17
+ end
18
+
19
+ # Start music with P key (if stopped)
20
+ if args.inputs.keyboard.key_down.p && !args.audio[:music]
21
+ args.audio[:music] = { input: "sounds/music.ogg", looping: true }
22
+ end
23
+
24
+ status = args.audio[:music] ? "Playing" : "Stopped"
25
+ args.outputs.labels << [
26
+ { x: 640, y: 400, text: "Music: #{status}", alignment_enum: 1 },
27
+ { x: 640, y: 360, text: "P = Play | Q = Stop", alignment_enum: 1 }
28
+ ]
29
+ end
@@ -0,0 +1,51 @@
1
+ # crossfade.rb
2
+ # Smooth crossfade between two music tracks
3
+
4
+ def tick(args)
5
+ defaults(args)
6
+ update_crossfade(args)
7
+ input(args)
8
+ render(args)
9
+ end
10
+
11
+ def defaults(args)
12
+ return if args.audio[:track_a]
13
+
14
+ # Initialize two tracks - one playing, one silent
15
+ args.audio[:track_a] = { input: "sounds/music.ogg", gain: 1.0, looping: true }
16
+ args.audio[:track_b] = { input: "sounds/music2.ogg", gain: 0.0, looping: true }
17
+ args.state.active_track = :track_a
18
+ end
19
+
20
+ def update_crossfade(args)
21
+ active = args.state.active_track
22
+ inactive = active == :track_a ? :track_b : :track_a
23
+
24
+ # Fade in active track
25
+ if args.audio[active]
26
+ args.audio[active].gain = (args.audio[active].gain + 0.01).clamp(0, 1)
27
+ end
28
+
29
+ # Fade out inactive track
30
+ if args.audio[inactive]
31
+ args.audio[inactive].gain = (args.audio[inactive].gain - 0.01).clamp(0, 1)
32
+ end
33
+ end
34
+
35
+ def input(args)
36
+ # Swap tracks on SPACE
37
+ if args.inputs.keyboard.key_down.space
38
+ args.state.active_track = args.state.active_track == :track_a ? :track_b : :track_a
39
+ end
40
+ end
41
+
42
+ def render(args)
43
+ a_vol = (args.audio[:track_a]&.gain.to_f * 100).to_i
44
+ b_vol = (args.audio[:track_b]&.gain.to_f * 100).to_i
45
+
46
+ args.outputs.labels << [
47
+ { x: 640, y: 400, text: "Press SPACE to crossfade", alignment_enum: 1 },
48
+ { x: 640, y: 360, text: "Track A: #{a_vol}%", alignment_enum: 1 },
49
+ { x: 640, y: 330, text: "Track B: #{b_vol}%", alignment_enum: 1 }
50
+ ]
51
+ end
@@ -0,0 +1,51 @@
1
+ # music_controls.rb
2
+ # Music controls: pause, volume, seeking
3
+
4
+ def tick(args)
5
+ defaults(args)
6
+ input(args)
7
+ render(args)
8
+ end
9
+
10
+ def defaults(args)
11
+ return if args.audio[:music]
12
+
13
+ args.audio[:music] = {
14
+ input: "sounds/music.ogg",
15
+ looping: true,
16
+ gain: 0.5,
17
+ paused: false
18
+ }
19
+ end
20
+
21
+ def input(args)
22
+ music = args.audio[:music]
23
+ return unless music
24
+
25
+ # Toggle pause
26
+ music.paused = !music.paused if args.inputs.keyboard.key_down.space
27
+
28
+ # Volume control
29
+ if args.inputs.keyboard.key_down.up
30
+ music.gain = (music.gain + 0.1).clamp(0, 1)
31
+ elsif args.inputs.keyboard.key_down.down
32
+ music.gain = (music.gain - 0.1).clamp(0, 1)
33
+ end
34
+
35
+ # Seeking
36
+ if args.inputs.keyboard.key_down.right
37
+ music.playtime += 5
38
+ elsif args.inputs.keyboard.key_down.left
39
+ music.playtime = [music.playtime - 5, 0].max
40
+ end
41
+ end
42
+
43
+ def render(args)
44
+ music = args.audio[:music]
45
+ args.outputs.labels << [
46
+ { x: 10, y: 700, text: "SPACE: #{music.paused ? 'Resume' : 'Pause'}" },
47
+ { x: 10, y: 670, text: "UP/DOWN: Volume (#{(music.gain * 100).to_i}%)" },
48
+ { x: 10, y: 640, text: "LEFT/RIGHT: Seek (+/- 5s)" },
49
+ { x: 10, y: 610, text: "Position: #{music.playtime.to_i}s" }
50
+ ]
51
+ end
@@ -0,0 +1,30 @@
1
+ # sound_effects.rb
2
+ # One-shot sound effects using args.outputs.sounds
3
+
4
+ def tick(args)
5
+ args.state.notes ||= [:c3, :d3, :e3, :f3, :g3, :a3, :b3, :c4]
6
+
7
+ # Basic sound playback - add path string
8
+ if args.inputs.mouse.click
9
+ args.outputs.sounds << "sounds/#{args.state.notes.sample}.wav"
10
+ end
11
+
12
+ # Sound with volume control - use hash
13
+ if args.inputs.keyboard.key_down.space
14
+ args.outputs.sounds << {
15
+ path: "sounds/#{args.state.notes.sample}.wav",
16
+ gain: 0.5 # Volume: 0.0 to 1.0
17
+ }
18
+ end
19
+
20
+ # Multiple sounds at once
21
+ if args.inputs.keyboard.key_down.enter
22
+ args.outputs.sounds << "sounds/jump.wav"
23
+ args.outputs.sounds << { path: "sounds/coin.wav", gain: 0.3 }
24
+ end
25
+
26
+ args.outputs.labels << [
27
+ { x: 640, y: 400, text: "Click or SPACE for sounds", alignment_enum: 1 },
28
+ { x: 640, y: 360, text: "ENTER for multiple sounds", alignment_enum: 1 }
29
+ ]
30
+ end
@@ -0,0 +1,27 @@
1
+ # Demonstrates DragonRuby's coordinate system
2
+ # Origin (0, 0) is at BOTTOM-LEFT corner
3
+ # Screen size is 1280 x 720 by default
4
+
5
+ def tick args
6
+ # Show mouse position (updates in real-time)
7
+ pos = args.inputs.mouse.position
8
+ args.outputs.labels << {
9
+ x: pos.x + 10,
10
+ y: pos.y + 10,
11
+ text: "x: #{pos.x.to_i}, y: #{pos.y.to_i}"
12
+ }
13
+
14
+ # Draw axis lines
15
+ args.outputs.lines << { x: 0, y: 360, x2: 1280, y2: 360, r: 100, g: 100, b: 100 }
16
+ args.outputs.lines << { x: 640, y: 0, x2: 640, y2: 720, r: 100, g: 100, b: 100 }
17
+
18
+ # Corner labels to show coordinate system
19
+ args.outputs.labels << { x: 10, y: 710, text: "Top-Left (0, 720)" }
20
+ args.outputs.labels << { x: 10, y: 30, text: "Bottom-Left (0, 0) - ORIGIN" }
21
+ args.outputs.labels << { x: 1000, y: 710, text: "Top-Right (1280, 720)" }
22
+ args.outputs.labels << { x: 1000, y: 30, text: "Bottom-Right (1280, 0)" }
23
+
24
+ # Center marker
25
+ args.outputs.solids << { x: 638, y: 358, w: 4, h: 4, r: 255, g: 0, b: 0 }
26
+ args.outputs.labels << { x: 640, y: 340, text: "Center (640, 360)", anchor_x: 0.5 }
27
+ end
@@ -0,0 +1,24 @@
1
+ # Minimal DragonRuby example - Shows a simple label
2
+ # The tick method is called 60 times per second
3
+
4
+ def tick args
5
+ # Display text at position (640, 360) - center of the 1280x720 screen
6
+ args.outputs.labels << {
7
+ x: 640,
8
+ y: 360,
9
+ text: "Hello, DragonRuby!",
10
+ size_px: 22,
11
+ anchor_x: 0.5,
12
+ anchor_y: 0.5
13
+ }
14
+
15
+ # Show the current frame count
16
+ args.outputs.labels << {
17
+ x: 640,
18
+ y: 320,
19
+ text: "Frame: #{Kernel.tick_count}",
20
+ size_px: 18,
21
+ anchor_x: 0.5,
22
+ anchor_y: 0.5
23
+ }
24
+ end
@@ -0,0 +1,22 @@
1
+ # Demonstrates text rendering with alignment, size, and color
2
+
3
+ def tick args
4
+ # Different text sizes
5
+ args.outputs.labels << { x: 100, y: 600, text: "Small text", size_px: 18 }
6
+ args.outputs.labels << { x: 100, y: 550, text: "Medium text", size_px: 22 }
7
+ args.outputs.labels << { x: 100, y: 500, text: "Large text", size_px: 26 }
8
+
9
+ # Text alignment (centered at x: 640)
10
+ args.outputs.labels << { x: 640, y: 400, text: "Left aligned", anchor_x: 0, anchor_y: 0.5 }
11
+ args.outputs.labels << { x: 640, y: 350, text: "Center aligned", anchor_x: 0.5, anchor_y: 0.5 }
12
+ args.outputs.labels << { x: 640, y: 300, text: "Right aligned", anchor_x: 1, anchor_y: 0.5 }
13
+
14
+ # Colored text (RGB values 0-255)
15
+ args.outputs.labels << { x: 100, y: 200, text: "Red text", r: 255, g: 0, b: 0 }
16
+ args.outputs.labels << { x: 100, y: 150, text: "Green text", r: 0, g: 255, b: 0 }
17
+ args.outputs.labels << { x: 100, y: 100, text: "Blue text", r: 0, g: 0, b: 255 }
18
+ args.outputs.labels << { x: 100, y: 50, text: "Faded text", r: 0, g: 0, b: 0, a: 128 }
19
+
20
+ # Reference line for alignment demonstration
21
+ args.outputs.lines << { x: 640, y: 0, x2: 640, y2: 720 }
22
+ end
@@ -0,0 +1,35 @@
1
+ # Demonstrates sprite rendering with hash properties
2
+ # Shows position, size, path, angle, and alpha (transparency)
3
+
4
+ def tick args
5
+ # Basic sprite rendering
6
+ args.outputs.sprites << {
7
+ x: 100,
8
+ y: 200,
9
+ w: 128,
10
+ h: 101,
11
+ path: 'dragonruby.png'
12
+ }
13
+
14
+ # Sprite with animated alpha (fading effect)
15
+ args.outputs.sprites << {
16
+ x: 300,
17
+ y: 200,
18
+ w: 128,
19
+ h: 101,
20
+ path: 'dragonruby.png',
21
+ a: Kernel.tick_count % 255
22
+ }
23
+
24
+ # Rotating sprite with anchoring
25
+ args.outputs.sprites << {
26
+ x: 500,
27
+ y: 250,
28
+ w: 128,
29
+ h: 101,
30
+ path: 'dragonruby.png',
31
+ angle: Kernel.tick_count % 360,
32
+ anchor_x: 0.5,
33
+ anchor_y: 0.5
34
+ }
35
+ end
@@ -0,0 +1,29 @@
1
+ # Demonstrates state management using args.state with ||= pattern
2
+ # The ||= operator initializes values only on first tick
3
+
4
+ def tick args
5
+ # Initialize player only once (on first frame)
6
+ # ||= means "assign if nil" - persists across frames
7
+ args.state.player ||= {
8
+ x: 640,
9
+ y: 360,
10
+ w: 50,
11
+ h: 50,
12
+ path: 'sprites/square/green.png'
13
+ }
14
+
15
+ # Initialize counter if not set
16
+ args.state.counter ||= 0
17
+ args.state.counter += 1
18
+
19
+ # Move player with arrow keys
20
+ args.state.player.x += 5 if args.inputs.right
21
+ args.state.player.x -= 5 if args.inputs.left
22
+ args.state.player.y += 5 if args.inputs.up
23
+ args.state.player.y -= 5 if args.inputs.down
24
+
25
+ # Render player and counter
26
+ args.outputs.sprites << args.state.player
27
+ args.outputs.labels << { x: 10, y: 710, text: "Counter: #{args.state.counter}" }
28
+ args.outputs.labels << { x: 10, y: 680, text: "Use arrow keys to move" }
29
+ end
@@ -0,0 +1,42 @@
1
+ # Handle background/unfocused state gracefully
2
+ # Important for web and mobile where users tab away
3
+
4
+ def tick(args)
5
+ if game_paused?(args)
6
+ render_pause_screen(args)
7
+ return
8
+ end
9
+
10
+ tick_game(args)
11
+ end
12
+
13
+ def game_paused?(args)
14
+ # Only pause in production builds when window loses focus
15
+ !args.inputs.keyboard.has_focus &&
16
+ args.gtk.production &&
17
+ Kernel.tick_count > 0
18
+ end
19
+
20
+ def render_pause_screen(args)
21
+ args.outputs.background_color = [0, 0, 0]
22
+ args.outputs.labels << {
23
+ x: 640,
24
+ y: 360,
25
+ text: "Game Paused",
26
+ size_enum: 10,
27
+ alignment_enum: 1,
28
+ r: 255, g: 255, b: 255
29
+ }
30
+ args.outputs.labels << {
31
+ x: 640,
32
+ y: 300,
33
+ text: "Click to resume",
34
+ size_enum: 2,
35
+ alignment_enum: 1,
36
+ r: 180, g: 180, b: 180
37
+ }
38
+ end
39
+
40
+ def tick_game(args)
41
+ # Normal game logic here
42
+ end
@@ -0,0 +1,26 @@
1
+ #!/bin/bash
2
+ # DragonRuby publishing workflow
3
+
4
+ # 1. Clean up player-specific data before packaging
5
+ rm -f mygame/high-score.txt
6
+ rm -f mygame/saves/*
7
+ rm -rf mygame/tmp/*
8
+
9
+ # 2. Package only (creates ./builds/ directory)
10
+ ./dragonruby-publish --only-package
11
+
12
+ # 3. Test locally before uploading
13
+ # - Linux: ./builds/mygame-linux-amd64.bin
14
+ # - macOS: open ./builds/mygame-mac-*/My\ Game.app
15
+ # - Windows: ./builds/mygame-windows-amd64.exe
16
+
17
+ # 4. Publish to itch.io (after first manual setup)
18
+ ./dragonruby-publish mygame
19
+
20
+ # Alternative: Manual upload mode
21
+ # ./dragonruby-publish --package mygame
22
+ # Then upload ./builds/*.zip to itch.io manually
23
+
24
+ # 5. Publish to Steam (requires steam_metadata.txt)
25
+ # ./dragonruby-publish
26
+ # Uses steamcmd for authentication
@@ -0,0 +1,16 @@
1
+ # Production-ready cvars.txt
2
+ # Location: mygame/metadata/cvars.txt
3
+
4
+ # Minimize logging noise
5
+ log.level=info
6
+ log.filter_subsystems=HTTPServer,HTTP
7
+
8
+ # Disable web server in production
9
+ # webserver.enabled=false
10
+
11
+ # Keep game running when backgrounded (0 = no sleep)
12
+ # Useful for music games or web where users tab away
13
+ # renderer.background_sleep=0
14
+
15
+ # Fullscreen by default (ensure quit option in game!)
16
+ # renderer.fullscreen=true