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,516 @@
1
+ # Entities Domain Reference
2
+
3
+ Game entity management: storage, spawning, collision detection, and lifecycle.
4
+
5
+ ## Entity Storage
6
+
7
+ Store entities in `args.state` arrays with lazy initialization:
8
+
9
+ ```ruby
10
+ def tick(args)
11
+ args.state.enemies ||= []
12
+ args.state.bullets ||= []
13
+ args.state.particles ||= []
14
+ end
15
+ ```
16
+
17
+ **Naming convention:** Use descriptive plurals (enemies, bullets, projectiles).
18
+
19
+ ## Entity Representation
20
+
21
+ ### Hash-Based Entities (Recommended for most games)
22
+
23
+ ```ruby
24
+ entity = {
25
+ # Required for rendering
26
+ x: 100, y: 100, w: 40, h: 40,
27
+ path: 'sprites/enemy.png',
28
+
29
+ # Optional visual properties
30
+ r: 255, g: 255, b: 255, a: 255,
31
+ angle: 0,
32
+ flip_horizontally: false,
33
+
34
+ # Custom properties
35
+ speed: 5,
36
+ health: 100,
37
+ dead: false
38
+ }
39
+ ```
40
+
41
+ ### Tracked Entities (with automatic metadata)
42
+
43
+ ```ruby
44
+ args.state.new_entity(:enemy) do |e|
45
+ e.x = 100
46
+ e.y = 200
47
+ e.w = 40
48
+ e.h = 40
49
+ e.path = 'sprites/enemy.png'
50
+ end
51
+ # Adds: entity_id, created_at, created_at_elapsed
52
+ ```
53
+
54
+ ### Class-Based Entities (for 1000+ entities)
55
+
56
+ ```ruby
57
+ class Enemy
58
+ attr_sprite # Enables x, y, w, h, path, etc.
59
+
60
+ def initialize(x, y)
61
+ @x, @y = x, y
62
+ @w, @h = 40, 40
63
+ @path = 'sprites/enemy.png'
64
+ @speed = 5
65
+ end
66
+
67
+ def update
68
+ @x += @speed
69
+ end
70
+ end
71
+ ```
72
+
73
+ ## Spawning Patterns
74
+
75
+ ### Factory Methods
76
+
77
+ ```ruby
78
+ def spawn_enemy(args)
79
+ size = 40
80
+ {
81
+ x: rand(args.grid.w - size),
82
+ y: rand(args.grid.h - size),
83
+ w: size,
84
+ h: size,
85
+ path: 'sprites/enemy.png',
86
+ speed: rand(3) + 1,
87
+ dead: false
88
+ }
89
+ end
90
+
91
+ # Usage
92
+ args.state.enemies << spawn_enemy(args)
93
+ ```
94
+
95
+ ### Random Positioning with Gutters
96
+
97
+ Prevent entities from spawning partially off-screen:
98
+
99
+ ```ruby
100
+ def spawn_in_bounds(args)
101
+ size = 64
102
+ {
103
+ # Gutter formula: rand(max - size * 2) + size
104
+ x: rand(args.grid.w - size * 2) + size,
105
+ y: rand(args.grid.h - size * 2) + size,
106
+ w: size,
107
+ h: size
108
+ }
109
+ end
110
+ ```
111
+
112
+ **Formula breakdown:**
113
+ - `args.grid.h - size * 2` = available height (subtract top + bottom gutters)
114
+ - `rand(available)` = random position in safe zone
115
+ - `+ size` = offset for bottom/left gutter
116
+
117
+ ### Spawn in Specific Region
118
+
119
+ ```ruby
120
+ # Right 40% of screen
121
+ x: rand(args.grid.w * 0.4) + args.grid.w * 0.6
122
+
123
+ # Top half with padding
124
+ y: rand(args.grid.h / 2 - 50) + args.grid.h / 2 + 25
125
+ ```
126
+
127
+ ### Batch Spawning
128
+
129
+ ```ruby
130
+ def spawn_wave(args, count)
131
+ count.times.map { spawn_enemy(args) }
132
+ end
133
+
134
+ args.state.enemies += spawn_wave(args, 10)
135
+ ```
136
+
137
+ ## Collision Detection
138
+
139
+ ### Basic Rectangle Collision
140
+
141
+ ```ruby
142
+ # Instance method
143
+ if player.intersect_rect?(enemy)
144
+ handle_collision
145
+ end
146
+
147
+ # Module method
148
+ if Geometry.intersect_rect?(player, enemy)
149
+ handle_collision
150
+ end
151
+
152
+ # With tolerance (default 0.1)
153
+ if player.intersect_rect?(enemy, 0.5)
154
+ handle_collision
155
+ end
156
+ ```
157
+
158
+ ### Find First Collision
159
+
160
+ ```ruby
161
+ # Returns first intersecting entity or nil
162
+ hit = Geometry.find_intersect_rect(bullet, args.state.enemies)
163
+ if hit
164
+ hit.dead = true
165
+ end
166
+ ```
167
+
168
+ ### Find All Collisions
169
+
170
+ ```ruby
171
+ # Returns array (empty if no collisions)
172
+ hits = Geometry.find_all_intersect_rect(explosion, args.state.enemies)
173
+ hits.each { |enemy| enemy.health -= 50 }
174
+ ```
175
+
176
+ ### Many-to-Many Collision
177
+
178
+ ```ruby
179
+ # Iterate through all collision pairs
180
+ Geometry.each_intersect_rect(bullets, enemies) do |bullet, enemy|
181
+ bullet.dead = true
182
+ enemy.health -= bullet.damage
183
+ end
184
+ ```
185
+
186
+ ### Nested Loop Pattern
187
+
188
+ ```ruby
189
+ args.state.bullets.each do |bullet|
190
+ args.state.enemies.each do |enemy|
191
+ if Geometry.intersect_rect?(bullet, enemy)
192
+ bullet.dead = true
193
+ enemy.dead = true
194
+ end
195
+ end
196
+ end
197
+ ```
198
+
199
+ ### Quad Trees (100+ entities)
200
+
201
+ ```ruby
202
+ # Create once for static/semi-static entities
203
+ args.state.quad_tree ||= Geometry.quad_tree_create(args.state.terrain)
204
+
205
+ # Fast collision lookup
206
+ hit = Geometry.find_intersect_rect_quad_tree(
207
+ args.state.player,
208
+ args.state.quad_tree
209
+ )
210
+ ```
211
+
212
+ ## Entity Lifecycle
213
+
214
+ ### Two-Phase Removal Pattern
215
+
216
+ **Phase 1:** Mark entities as dead during update loops:
217
+
218
+ ```ruby
219
+ args.state.bullets.each do |bullet|
220
+ bullet.x += bullet.speed
221
+
222
+ # Mark off-screen bullets
223
+ if bullet.x > args.grid.w
224
+ bullet.dead = true
225
+ next # Skip further processing
226
+ end
227
+
228
+ # Mark on collision
229
+ args.state.enemies.each do |enemy|
230
+ if Geometry.intersect_rect?(bullet, enemy)
231
+ bullet.dead = true
232
+ enemy.dead = true
233
+ end
234
+ end
235
+ end
236
+ ```
237
+
238
+ **Phase 2:** Reject dead entities after all processing:
239
+
240
+ ```ruby
241
+ args.state.bullets.reject! { |b| b.dead }
242
+ args.state.enemies.reject! { |e| e.dead }
243
+ ```
244
+
245
+ ### Off-Screen Removal
246
+
247
+ ```ruby
248
+ # Remove bullets past screen edge
249
+ args.state.bullets.reject! { |b| b.x > args.grid.w }
250
+
251
+ # Remove any entity outside bounds
252
+ args.state.entities.reject! do |e|
253
+ e.x < -100 || e.x > 1380 || e.y < -100 || e.y > 820
254
+ end
255
+ ```
256
+
257
+ ### Age-Based Removal
258
+
259
+ ```ruby
260
+ # Mark particles after certain age
261
+ args.state.particles.each do |p|
262
+ p.age ||= 0
263
+ p.age += 1
264
+ p.dead = true if p.age > 60 # 1 second at 60 FPS
265
+ end
266
+ args.state.particles.reject!(&:dead)
267
+ ```
268
+
269
+ ## Collection Rendering
270
+
271
+ ### Basic Rendering
272
+
273
+ ```ruby
274
+ # Render entire collection
275
+ args.outputs.sprites << args.state.enemies
276
+
277
+ # DragonRuby flattens nested arrays automatically
278
+ args.outputs.sprites << [
279
+ args.state.player,
280
+ args.state.enemies,
281
+ args.state.bullets
282
+ ]
283
+ ```
284
+
285
+ ### Conditional Rendering
286
+
287
+ ```ruby
288
+ # Only render alive entities
289
+ args.outputs.sprites << args.state.enemies.select(&:alive)
290
+
291
+ # Simple frustum culling
292
+ args.outputs.sprites << args.state.entities.select do |e|
293
+ e.x.between?(-100, 1380) && e.y.between?(-100, 820)
294
+ end
295
+ ```
296
+
297
+ ## Geometry API Reference
298
+
299
+ Access via `Geometry.*` (preferred) or `args.geometry.*`. Instance methods also available as mixins on Hash/Array/Entity.
300
+
301
+ | Method | Returns | Use Case |
302
+ |--------|---------|----------|
303
+ | `intersect_rect?(other)` | `true/false` | Basic collision check |
304
+ | `inside_rect?(other)` | `true/false` | Fully contained check |
305
+ | `find_intersect_rect(rect, coll)` | `rect/nil` | First collision |
306
+ | `find_all_intersect_rect(rect, coll)` | `Array` | All collisions |
307
+ | `each_intersect_rect(c1, c2) {}` | — | Iterate pairs |
308
+ | `quad_tree_create(coll)` | `quad_tree` | Build spatial index |
309
+ | `find_intersect_rect_quad_tree(r, tree)` | `rect/nil` | Fast lookup |
310
+ | `distance(p1, p2)` | `Float` | Distance between points |
311
+ | `angle_to(from, to)` | `Float` | Angle in degrees |
312
+
313
+ ## Performance Tiers
314
+
315
+ | Entity Count | Recommended Approach |
316
+ |--------------|---------------------|
317
+ | < 100 | Hash-based entities |
318
+ | 100-500 | `args.state.new_entity` or classes |
319
+ | 500-1000 | Classes with `attr_sprite` |
320
+ | 1000+ | `static_sprites` + `draw_override` |
321
+
322
+ For collision detection:
323
+
324
+ | Entity Count | Recommended Approach |
325
+ |--------------|---------------------|
326
+ | < 100 | `intersect_rect?` / nested loops |
327
+ | 100-500 | `find_intersect_rect` / `each_intersect_rect` |
328
+ | 500+ | Quad trees |
329
+
330
+ ## Common Antipatterns
331
+
332
+ ### ❌ Modifying Collection While Iterating
333
+
334
+ ```ruby
335
+ # BUG: Modifying during iteration
336
+ args.state.enemies.each do |enemy|
337
+ if enemy.health <= 0
338
+ args.state.enemies.delete(enemy) # WRONG
339
+ end
340
+ end
341
+ ```
342
+
343
+ **Fix:** Mark and reject in separate phases.
344
+
345
+ ### ❌ Keeping Dead Entities
346
+
347
+ ```ruby
348
+ # Memory leak: dead entities accumulate
349
+ args.state.bullets.each do |b|
350
+ b.x += b.speed
351
+ # Never removed when off-screen
352
+ end
353
+ ```
354
+
355
+ **Fix:** Always reject off-screen and dead entities.
356
+
357
+ ### ❌ Using Arrays Instead of Hashes
358
+
359
+ ```ruby
360
+ # Unreadable: what is [0]?
361
+ bullet = [x, y, w, h, 'sprites/bullet.png']
362
+ bullet[0] += speed
363
+ ```
364
+
365
+ **Fix:** Use hashes with named properties.
366
+
367
+ ### ❌ Forgetting Gutters
368
+
369
+ ```ruby
370
+ # Can spawn partially off-screen
371
+ y: rand(args.grid.h)
372
+ ```
373
+
374
+ **Fix:** Use `rand(max - size * 2) + size` formula.
375
+
376
+ ### ❌ Collision After Death
377
+
378
+ ```ruby
379
+ args.state.bullets.each do |bullet|
380
+ bullet.x += bullet.speed
381
+ # No check if already dead - wastes cycles
382
+ args.state.enemies.each do |enemy|
383
+ if Geometry.intersect_rect?(bullet, enemy)
384
+ bullet.dead = true
385
+ enemy.dead = true
386
+ end
387
+ end
388
+ end
389
+ ```
390
+
391
+ **Fix:** Use `next if bullet.dead` after marking.
392
+
393
+ ## Complete Entity System
394
+
395
+ ```ruby
396
+ def tick(args)
397
+ defaults(args)
398
+ update_entities(args)
399
+ check_collisions(args)
400
+ cleanup_entities(args)
401
+ render_entities(args)
402
+ end
403
+
404
+ def defaults(args)
405
+ args.state.player ||= { x: 100, y: 360, w: 40, h: 40, path: 'sprites/player.png' }
406
+ args.state.enemies ||= []
407
+ args.state.bullets ||= []
408
+ end
409
+
410
+ def update_entities(args)
411
+ args.state.bullets.each do |b|
412
+ b.x += 10
413
+ b.dead = true if b.x > args.grid.w
414
+ end
415
+
416
+ args.state.enemies.each do |e|
417
+ e.x -= e.speed
418
+ e.dead = true if e.x < -e.w
419
+ end
420
+ end
421
+
422
+ def check_collisions(args)
423
+ args.state.bullets.each do |bullet|
424
+ next if bullet.dead
425
+
426
+ args.state.enemies.each do |enemy|
427
+ next if enemy.dead
428
+
429
+ if Geometry.intersect_rect?(bullet, enemy)
430
+ bullet.dead = true
431
+ enemy.dead = true
432
+ args.state.score ||= 0
433
+ args.state.score += 100
434
+ end
435
+ end
436
+ end
437
+ end
438
+
439
+ def cleanup_entities(args)
440
+ args.state.bullets.reject!(&:dead)
441
+ args.state.enemies.reject!(&:dead)
442
+ end
443
+
444
+ def render_entities(args)
445
+ args.outputs.sprites << [
446
+ args.state.player,
447
+ args.state.enemies,
448
+ args.state.bullets
449
+ ]
450
+ end
451
+ ```
452
+
453
+ ## Decision Tree
454
+
455
+ **How should I store game objects?**
456
+ ```
457
+ Need to track many objects of same type?
458
+ ├─ Yes → Use args.state arrays with ||= initialization
459
+ │ See: examples/entities/entity_storage.rb
460
+ └─ No (single object like player) → Use args.state.player directly
461
+ ```
462
+
463
+ **Which entity representation?**
464
+ ```
465
+ How many entities?
466
+ ├─ < 100 → Hash-based entities (simplest)
467
+ │ See: examples/entities/entity_storage.rb
468
+ ├─ 100-1000 → args.state.new_entity (tracking) or classes
469
+ │ See: examples/entities/factory_methods.rb
470
+ └─ 1000+ → Classes with attr_sprite
471
+ ```
472
+
473
+ **How should I spawn entities?**
474
+ ```
475
+ Creating entities in multiple places?
476
+ ├─ Yes → Use factory methods (spawn_enemy, spawn_bullet)
477
+ │ See: examples/entities/factory_methods.rb
478
+ └─ No → Inline creation is fine
479
+
480
+ Need random positions?
481
+ ├─ Yes, anywhere on screen → Use gutter formula
482
+ │ See: examples/entities/random_spawning.rb
483
+ └─ Yes, specific region → Calculate bounds manually
484
+ ```
485
+
486
+ **Which collision method?**
487
+ ```
488
+ How many entities to check?
489
+ ├─ Single entity vs single entity → intersect_rect?
490
+ │ See: examples/entities/collision_detection.rb
491
+ ├─ Single entity vs collection (find first) → find_intersect_rect
492
+ ├─ Single entity vs collection (find all) → find_all_intersect_rect
493
+ ├─ Collection vs collection → each_intersect_rect or nested loops
494
+ │ See: examples/entities/entity_lifecycle.rb
495
+ └─ 100+ static entities → quad_tree
496
+ ```
497
+
498
+ **How should I remove entities?**
499
+ ```
500
+ When to remove?
501
+ ├─ On collision → Mark dead, reject after loop
502
+ │ See: examples/entities/entity_lifecycle.rb
503
+ ├─ Off-screen → Check bounds, mark dead
504
+ ├─ After time → Track age, mark when expired
505
+ └─ All at once → Use reject! after all updates
506
+ ```
507
+
508
+ ## Examples
509
+
510
+ | File | Demonstrates |
511
+ |------|--------------|
512
+ | `examples/entities/entity_storage.rb` | Arrays in args.state, ||= initialization |
513
+ | `examples/entities/factory_methods.rb` | spawn_* patterns, new_entity |
514
+ | `examples/entities/collision_detection.rb` | intersect_rect?, find methods |
515
+ | `examples/entities/entity_lifecycle.rb` | Create → update → mark → reject |
516
+ | `examples/entities/random_spawning.rb` | Gutter formula, bounded positions |