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,403 @@
1
+ # DragonRuby Rendering Primitives
2
+
3
+ Quick reference for `args.outputs` primitives. Screen: 1280x720, origin bottom-left.
4
+
5
+ ## Render Order
6
+
7
+ ### Recommended: FIFO with `args.outputs.primitives`
8
+
9
+ Use `args.outputs.primitives` for full control over render order. Items render first-in, first-out (FIFO) regardless of type:
10
+
11
+ ```ruby
12
+ args.outputs.primitives << { x: 0, y: 0, w: 100, h: 100, r: 255, primitive_marker: :solid }
13
+ args.outputs.primitives << { x: 50, y: 50, w: 64, h: 64, path: 'player.png' } # renders ON TOP of solid
14
+ args.outputs.primitives << { x: 60, y: 120, text: "Score: 100" } # renders ON TOP of sprite
15
+ ```
16
+
17
+ ### Fixed Layer Order (Typed Outputs)
18
+
19
+ When using typed outputs, DragonRuby renders in fixed order (bottom to top):
20
+
21
+ 1. `args.outputs.solids`
22
+ 2. `args.outputs.sprites`
23
+ 3. `args.outputs.primitives`
24
+ 4. `args.outputs.labels`
25
+ 5. `args.outputs.lines`
26
+ 6. `args.outputs.borders`
27
+ 7. `args.outputs.debug` (dev only)
28
+
29
+ **Limitation**: A solid will always render behind a sprite, even if added later. Use `args.outputs.primitives` to bypass this constraint.
30
+
31
+ ## Sprites
32
+
33
+ Most common primitive. Use hash syntax.
34
+
35
+ ```ruby
36
+ args.outputs.sprites << {
37
+ # Position & size (required)
38
+ x: 100, y: 100, w: 64, h: 64,
39
+ path: 'sprites/player.png',
40
+
41
+ # Anchors (0.0-1.0, default: 0)
42
+ anchor_x: 0.5, # 0=left, 0.5=center, 1=right
43
+ anchor_y: 0.5, # 0=bottom, 0.5=center, 1=top
44
+
45
+ # Rotation
46
+ angle: 45, # degrees clockwise
47
+ angle_anchor_x: 0.5, # rotation pivot (default: 0.5)
48
+ angle_anchor_y: 0.5,
49
+
50
+ # Flipping
51
+ flip_horizontally: false,
52
+ flip_vertically: false,
53
+
54
+ # Color tinting (0-255)
55
+ r: 255, g: 255, b: 255, # saturation
56
+ a: 255, # transparency
57
+
58
+ # Blending
59
+ blendmode_enum: 1 # 0=none, 1=alpha, 2=additive, 3=mod, 4=multiply
60
+ }
61
+ ```
62
+
63
+ ### Centering a Sprite
64
+
65
+ ```ruby
66
+ # Manual offset (avoid)
67
+ { x: 640 - 40, y: 360 - 40, w: 80, h: 80, path: 'sprite.png' }
68
+
69
+ # With anchors (preferred)
70
+ { x: 640, y: 360, w: 80, h: 80, path: 'sprite.png', anchor_x: 0.5, anchor_y: 0.5 }
71
+ ```
72
+
73
+ ### Cropping (Spritesheets)
74
+
75
+ Two coordinate systems:
76
+
77
+ ```ruby
78
+ # tile_* - top-left origin (common for texture atlases)
79
+ { tile_x: 0, tile_y: 0, tile_w: 64, tile_h: 64 }
80
+
81
+ # source_* - bottom-left origin
82
+ { source_x: 0, source_y: 0, source_w: 64, source_h: 64 }
83
+ ```
84
+
85
+ ### Sprite as Solid (Performance)
86
+
87
+ Prefer over `args.outputs.solids` for many rectangles:
88
+
89
+ ```ruby
90
+ args.outputs.sprites << {
91
+ x: 0, y: 0, w: 100, h: 100,
92
+ path: :solid,
93
+ r: 255, g: 0, b: 0, a: 128
94
+ }
95
+ ```
96
+
97
+ ## Solids
98
+
99
+ Filled rectangles. **Use sparingly** - textures not cached.
100
+
101
+ ```ruby
102
+ args.outputs.solids << {
103
+ x: 0, y: 0,
104
+ w: args.grid.w, h: args.grid.h, # full screen
105
+ r: 92, g: 120, b: 230, # RGB (0-255)
106
+ a: 255 # alpha (optional)
107
+ }
108
+ ```
109
+
110
+ For many solids, use sprites with `path: :solid` instead.
111
+
112
+ ## Labels
113
+
114
+ Text rendering. **Note**: Default anchor is TOP-LEFT (unlike other primitives).
115
+
116
+ ```ruby
117
+ args.outputs.labels << {
118
+ x: 640, y: 360,
119
+ text: "Score: #{args.state.score}",
120
+
121
+ # Size (choose one)
122
+ size_enum: 0, # -2=18px, -1=20px, 0=22px, 1=24px, 2=26px
123
+ size_px: 22, # explicit pixels (overrides size_enum)
124
+
125
+ # Alignment (legacy)
126
+ alignment_enum: 1, # 0=left, 1=center, 2=right
127
+ vertical_alignment_enum: 1, # 0=bottom, 1=center, 2=top
128
+
129
+ # Anchors (preferred over alignment_enum)
130
+ anchor_x: 0.5, # overrides alignment_enum
131
+ anchor_y: 0.5, # default: 1.0 (top)
132
+
133
+ # Color
134
+ r: 0, g: 0, b: 0, a: 255,
135
+
136
+ # Font
137
+ font: 'fonts/manaspc.ttf'
138
+ }
139
+ ```
140
+
141
+ ### Common Label Patterns
142
+
143
+ ```ruby
144
+ # Top-left score
145
+ { x: 40, y: args.grid.h - 40, text: "Score: #{score}", size_enum: 4 }
146
+
147
+ # Top-right timer
148
+ { x: args.grid.w - 40, y: args.grid.h - 40, text: "Time: #{time}",
149
+ alignment_enum: 2 }
150
+
151
+ # Centered title
152
+ { x: 640, y: 400, text: "GAME OVER", size_px: 48,
153
+ anchor_x: 0.5, anchor_y: 0.5 }
154
+ ```
155
+
156
+ ## Borders
157
+
158
+ Unfilled rectangles (outlines). Same properties as solids.
159
+
160
+ ```ruby
161
+ args.outputs.borders << {
162
+ x: 100, y: 100, w: 200, h: 150,
163
+ r: 255, g: 0, b: 0, a: 255
164
+ }
165
+ ```
166
+
167
+ ## Lines
168
+
169
+ Line segments between two points.
170
+
171
+ ```ruby
172
+ args.outputs.lines << {
173
+ x: 100, y: 100, # start
174
+ x2: 300, y2: 300, # end
175
+ r: 255, g: 0, b: 0, a: 255
176
+ }
177
+
178
+ # Alternative: relative with w/h
179
+ { x: 100, y: 100, w: 200, h: 200 } # equivalent to x2: 300, y2: 300
180
+ ```
181
+
182
+ ## Mixed Primitives (Recommended)
183
+
184
+ Use `args.outputs.primitives` for FIFO render order control. Requires `primitive_marker` for solids and borders:
185
+
186
+ ```ruby
187
+ def tick args
188
+ # All primitives render in insertion order (FIFO)
189
+ args.outputs.primitives << { x: 0, y: 0, w: 1280, h: 720, r: 30, g: 30, b: 50, primitive_marker: :solid } # background
190
+ args.outputs.primitives << { x: 100, y: 100, w: 64, h: 64, path: 'player.png' } # sprite (auto-detected via path)
191
+ args.outputs.primitives << { x: 100, y: 170, text: "Player 1" } # label (auto-detected via text)
192
+ args.outputs.primitives << { x: 90, y: 90, w: 84, h: 100, r: 255, g: 255, b: 0, primitive_marker: :border } # border ON TOP
193
+ end
194
+ ```
195
+
196
+ ### primitive_marker Values
197
+
198
+ | Type | primitive_marker | Auto-detected by |
199
+ |------|------------------|------------------|
200
+ | Solid | `:solid` | Required |
201
+ | Border | `:border` | Required |
202
+ | Sprite | `:sprite` | `path` property |
203
+ | Label | `:label` | `text` property |
204
+ | Line | `:line` | `x2`/`y2` properties |
205
+
206
+ ## Static Outputs (Performance)
207
+
208
+ Cache primitives that don't change:
209
+
210
+ ```ruby
211
+ # First frame only
212
+ if args.state.tick_count == 0
213
+ args.outputs.static_sprites << {
214
+ x: 0, y: 0, w: 1280, h: 720,
215
+ path: 'backgrounds/level1.png'
216
+ }
217
+ end
218
+
219
+ # Clear when needed
220
+ args.outputs.static_sprites.clear
221
+ ```
222
+
223
+ Available: `static_solids`, `static_sprites`, `static_labels`, `static_lines`, `static_borders`, `static_primitives`
224
+
225
+ ## Debug Output
226
+
227
+ Rendered only in development, hidden in production builds:
228
+
229
+ ```ruby
230
+ args.outputs.debug << { x: 10, y: 710, text: "FPS: #{args.gtk.current_framerate}" }.label!
231
+
232
+ # Quick watch
233
+ args.outputs.debug.watch args.state.player
234
+ ```
235
+
236
+ ## Background Color
237
+
238
+ ```ruby
239
+ args.outputs.background_color = [92, 120, 230] # RGB
240
+ args.outputs.background_color = [92, 120, 230, 255] # RGBA
241
+ ```
242
+
243
+ ## Class Syntax (Best Performance)
244
+
245
+ ```ruby
246
+ class Player
247
+ attr_sprite # adds all sprite properties
248
+
249
+ def initialize
250
+ @x, @y, @w, @h = 100, 100, 64, 64
251
+ @path = 'sprites/player.png'
252
+ end
253
+ end
254
+
255
+ args.outputs.sprites << Player.new
256
+ ```
257
+
258
+ Also available: `attr_label`, `attr_line`
259
+
260
+ ## Best Practices
261
+
262
+ 1. **Use hash syntax** - explicit, maintainable
263
+ 2. **Use `args.grid.w/h`** - not magic numbers
264
+ 3. **Use anchors for centering** - not manual offset calculations
265
+ 4. **Use `path: :solid`** - instead of `args.outputs.solids` for many rectangles
266
+ 5. **Use static outputs** - for backgrounds and unchanging elements
267
+ 6. **Render in order** - backgrounds first, UI last
268
+ 7. **Flatten arrays** - DragonRuby handles `sprites << [player, enemies, bullets]`
269
+ 8. **Use `args.outputs.primitives`** - for full FIFO control over render order
270
+
271
+ ## Common Antipatterns
272
+
273
+ ### Magic Numbers
274
+
275
+ ```ruby
276
+ # WRONG - hardcoded screen dimensions
277
+ if player.x > 1280
278
+ player.x = 1280
279
+ end
280
+
281
+ # CORRECT - use grid constants
282
+ if player.x > args.grid.w
283
+ player.x = args.grid.w
284
+ end
285
+ ```
286
+
287
+ **Why:** Screen dimensions may vary; grid constants ensure portability.
288
+
289
+ ### Array Syntax
290
+
291
+ ```ruby
292
+ # WRONG - positional arguments are hard to read
293
+ args.outputs.labels << [640, 500, 'Hello', 5, 1]
294
+
295
+ # CORRECT - hash syntax is explicit and maintainable
296
+ args.outputs.labels << { x: 640, y: 500, text: 'Hello', size_enum: 5, alignment_enum: 1 }
297
+ ```
298
+
299
+ **Why:** Array syntax is error-prone and difficult to maintain.
300
+
301
+ ### Manual Centering
302
+
303
+ ```ruby
304
+ # WRONG - manual offset calculation
305
+ { x: 640 - sprite_w / 2, y: 360 - sprite_h / 2, w: sprite_w, h: sprite_h, path: 'sprite.png' }
306
+
307
+ # CORRECT - use anchors
308
+ { x: 640, y: 360, w: sprite_w, h: sprite_h, path: 'sprite.png', anchor_x: 0.5, anchor_y: 0.5 }
309
+ ```
310
+
311
+ **Why:** Anchors are clearer and automatically handle size changes.
312
+
313
+ ### Many Solids
314
+
315
+ ```ruby
316
+ # WRONG - solids are not cached, poor performance
317
+ 100.times { |i| args.outputs.solids << { x: i * 10, y: 0, w: 8, h: 100 } }
318
+
319
+ # CORRECT - use sprites with path: :solid for many rectangles
320
+ 100.times { |i| args.outputs.sprites << { x: i * 10, y: 0, w: 8, h: 100, path: :solid, r: 255 } }
321
+ ```
322
+
323
+ **Why:** Sprites with `path: :solid` are GPU-cached and much faster for many rectangles.
324
+
325
+ ### Recreating Static Content
326
+
327
+ ```ruby
328
+ # WRONG - recreates background every frame
329
+ def tick(args)
330
+ args.outputs.sprites << { x: 0, y: 0, w: 1280, h: 720, path: 'background.png' }
331
+ end
332
+
333
+ # CORRECT - use static_sprites (renders once)
334
+ def tick(args)
335
+ if Kernel.tick_count == 0
336
+ args.outputs.static_sprites << { x: 0, y: 0, w: 1280, h: 720, path: 'background.png' }
337
+ end
338
+ end
339
+ ```
340
+
341
+ **Why:** Static outputs are only processed once, reducing per-frame overhead.
342
+
343
+ ## Property Defaults
344
+
345
+ | Property | Default |
346
+ |----------|---------|
347
+ | `anchor_x`, `anchor_y` | 0 (bottom-left) |
348
+ | `angle` | 0 |
349
+ | `r`, `g`, `b` | 255 (sprites), 0 (others) |
350
+ | `a` | 255 |
351
+ | `blendmode_enum` | 1 (alpha) |
352
+ | `flip_horizontally/vertically` | false |
353
+
354
+ ## Decision Tree
355
+
356
+ ```
357
+ What do you want to render?
358
+
359
+ ├─ Image/texture?
360
+ │ └─ Use sprites → examples/rendering/sprites.rb
361
+
362
+ ├─ Colored rectangle?
363
+ │ ├─ Few rectangles? → Use solids
364
+ │ └─ Many rectangles? → Use sprites with path: :solid → examples/rendering/solids.rb
365
+
366
+ ├─ Text?
367
+ │ └─ Use labels → examples/rendering/labels.rb
368
+
369
+ ├─ Outline rectangle?
370
+ │ └─ Use borders → examples/rendering/solids.rb
371
+
372
+ ├─ Line segment?
373
+ │ └─ Use lines
374
+
375
+ ├─ Need control over render order?
376
+ │ └─ Use args.outputs.primitives with primitive_marker (recommended for most cases)
377
+
378
+ ├─ Static content (doesn't change)?
379
+ │ └─ Use static_* outputs → once on first frame
380
+
381
+ └─ Debug info only?
382
+ └─ Use args.outputs.debug → hidden in production
383
+
384
+ Performance concern?
385
+
386
+ ├─ Many similar sprites?
387
+ │ └─ Use class with attr_sprite
388
+
389
+ ├─ Background never changes?
390
+ │ └─ Use static_sprites
391
+
392
+ └─ Rendering many rectangles?
393
+ └─ Use sprites with path: :solid instead of solids
394
+ ```
395
+
396
+ ## Examples Index
397
+
398
+ | Example | Purpose |
399
+ |---------|---------|
400
+ | `examples/rendering/solids.rb` | Filled rectangles, backgrounds, borders |
401
+ | `examples/rendering/sprites.rb` | Images, anchors, rotation, color tinting |
402
+ | `examples/rendering/labels.rb` | Text alignment, sizing, fonts |
403
+ | `examples/rendering/layering.rb` | Render order, z-index control |