anima-core 0.3.0 → 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 (269) hide show
  1. checksums.yaml +4 -4
  2. data/.reek.yml +27 -1
  3. data/CHANGELOG.md +4 -0
  4. data/README.md +219 -25
  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 +76 -28
  12. data/app/jobs/agent_request_job.rb +24 -0
  13. data/app/jobs/analytical_brain_job.rb +33 -0
  14. data/app/jobs/count_event_tokens_job.rb +1 -1
  15. data/app/models/concerns/event/broadcasting.rb +20 -2
  16. data/app/models/event.rb +1 -1
  17. data/app/models/goal.rb +91 -0
  18. data/app/models/session.rb +347 -22
  19. data/config/application.rb +2 -0
  20. data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
  21. data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
  22. data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
  23. data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
  24. data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
  25. data/db/migrate/20260315140843_create_goals.rb +16 -0
  26. data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
  27. data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
  28. data/lib/agent_loop.rb +65 -9
  29. data/lib/agents/definition.rb +116 -0
  30. data/lib/agents/registry.rb +106 -0
  31. data/lib/analytical_brain/runner.rb +276 -0
  32. data/lib/analytical_brain/tools/activate_skill.rb +52 -0
  33. data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
  34. data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
  35. data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
  36. data/lib/analytical_brain/tools/finish_goal.rb +62 -0
  37. data/lib/analytical_brain/tools/read_workflow.rb +58 -0
  38. data/lib/analytical_brain/tools/rename_session.rb +63 -0
  39. data/lib/analytical_brain/tools/set_goal.rb +60 -0
  40. data/lib/analytical_brain/tools/update_goal.rb +60 -0
  41. data/lib/analytical_brain.rb +23 -0
  42. data/lib/anima/cli/mcp/secrets.rb +76 -0
  43. data/lib/anima/cli/mcp.rb +197 -0
  44. data/lib/anima/cli.rb +4 -0
  45. data/lib/anima/installer.rb +168 -0
  46. data/lib/anima/settings.rb +226 -0
  47. data/lib/anima/version.rb +1 -1
  48. data/lib/anima.rb +9 -0
  49. data/lib/credential_store.rb +103 -0
  50. data/lib/environment_probe.rb +232 -0
  51. data/lib/llm/client.rb +29 -10
  52. data/lib/mcp/client_manager.rb +86 -0
  53. data/lib/mcp/config.rb +213 -0
  54. data/lib/mcp/health_check.rb +77 -0
  55. data/lib/mcp/secrets.rb +73 -0
  56. data/lib/mcp/stdio_transport.rb +206 -0
  57. data/lib/providers/anthropic.rb +8 -7
  58. data/lib/shell_session.rb +11 -10
  59. data/lib/skills/definition.rb +97 -0
  60. data/lib/skills/registry.rb +105 -0
  61. data/lib/tools/edit.rb +3 -4
  62. data/lib/tools/mcp_tool.rb +114 -0
  63. data/lib/tools/read.rb +15 -16
  64. data/lib/tools/registry.rb +14 -12
  65. data/lib/tools/request_feature.rb +121 -0
  66. data/lib/tools/return_result.rb +81 -0
  67. data/lib/tools/spawn_specialist.rb +109 -0
  68. data/lib/tools/spawn_subagent.rb +111 -0
  69. data/lib/tools/subagent_prompts.rb +12 -0
  70. data/lib/tools/web_get.rb +8 -9
  71. data/lib/tui/app.rb +332 -43
  72. data/lib/tui/message_store.rb +20 -0
  73. data/lib/tui/screens/chat.rb +207 -20
  74. data/lib/workflows/definition.rb +97 -0
  75. data/lib/workflows/registry.rb +89 -0
  76. data/skills/activerecord/SKILL.md +255 -0
  77. data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
  78. data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
  79. data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
  80. data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
  81. data/skills/activerecord/examples/associations/self_referential.rb +302 -0
  82. data/skills/activerecord/examples/associations/through_associations.rb +203 -0
  83. data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
  84. data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
  85. data/skills/activerecord/examples/basics/inheritance.rb +377 -0
  86. data/skills/activerecord/examples/basics/type_casting.rb +317 -0
  87. data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
  88. data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
  89. data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
  90. data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
  91. data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
  92. data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
  93. data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
  94. data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
  95. data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
  96. data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
  97. data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
  98. data/skills/activerecord/examples/querying/optimization.rb +275 -0
  99. data/skills/activerecord/examples/querying/scopes.rb +260 -0
  100. data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
  101. data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
  102. data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
  103. data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
  104. data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
  105. data/skills/activerecord/references/associations.md +709 -0
  106. data/skills/activerecord/references/basics.md +622 -0
  107. data/skills/activerecord/references/callbacks.md +738 -0
  108. data/skills/activerecord/references/migrations.md +657 -0
  109. data/skills/activerecord/references/querying.md +655 -0
  110. data/skills/activerecord/references/validations.md +596 -0
  111. data/skills/dragonruby/SKILL.md +250 -0
  112. data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
  113. data/skills/dragonruby/examples/audio/background_music.rb +29 -0
  114. data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
  115. data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
  116. data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
  117. data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
  118. data/skills/dragonruby/examples/core/hello_world.rb +24 -0
  119. data/skills/dragonruby/examples/core/labels.rb +22 -0
  120. data/skills/dragonruby/examples/core/sprites.rb +35 -0
  121. data/skills/dragonruby/examples/core/state_management.rb +29 -0
  122. data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
  123. data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
  124. data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
  125. data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
  126. data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
  127. data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
  128. data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
  129. data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
  130. data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
  131. data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
  132. data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
  133. data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
  134. data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
  135. data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
  136. data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
  137. data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
  138. data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
  139. data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
  140. data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
  141. data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
  142. data/skills/dragonruby/examples/input/controller_input.rb +28 -0
  143. data/skills/dragonruby/examples/input/directional_input.rb +24 -0
  144. data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
  145. data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
  146. data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
  147. data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
  148. data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
  149. data/skills/dragonruby/examples/rendering/labels.rb +32 -0
  150. data/skills/dragonruby/examples/rendering/layering.rb +51 -0
  151. data/skills/dragonruby/examples/rendering/solids.rb +61 -0
  152. data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
  153. data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
  154. data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
  155. data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
  156. data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
  157. data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
  158. data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
  159. data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
  160. data/skills/dragonruby/references/audio.md +396 -0
  161. data/skills/dragonruby/references/core.md +385 -0
  162. data/skills/dragonruby/references/distribution.md +434 -0
  163. data/skills/dragonruby/references/entities.md +516 -0
  164. data/skills/dragonruby/references/game-logic/persistence.md +386 -0
  165. data/skills/dragonruby/references/game-logic/state.md +389 -0
  166. data/skills/dragonruby/references/input.md +414 -0
  167. data/skills/dragonruby/references/rendering/animation.md +467 -0
  168. data/skills/dragonruby/references/rendering/primitives.md +403 -0
  169. data/skills/dragonruby/references/scenes.md +443 -0
  170. data/skills/draper-decorators/SKILL.md +344 -0
  171. data/skills/draper-decorators/examples/application_decorator.rb +61 -0
  172. data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
  173. data/skills/draper-decorators/examples/model_decorator.rb +152 -0
  174. data/skills/draper-decorators/references/anti-patterns.md +640 -0
  175. data/skills/draper-decorators/references/patterns.md +507 -0
  176. data/skills/draper-decorators/references/testing.md +559 -0
  177. data/skills/gh-issue.md +182 -0
  178. data/skills/mcp-server/SKILL.md +177 -0
  179. data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
  180. data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
  181. data/skills/mcp-server/examples/http_client.rb +48 -0
  182. data/skills/mcp-server/examples/http_server.rb +97 -0
  183. data/skills/mcp-server/examples/rails_integration.rb +88 -0
  184. data/skills/mcp-server/examples/stdio_server.rb +108 -0
  185. data/skills/mcp-server/examples/streaming_client.rb +95 -0
  186. data/skills/mcp-server/references/gotchas.md +183 -0
  187. data/skills/mcp-server/references/prompts.md +98 -0
  188. data/skills/mcp-server/references/resources.md +53 -0
  189. data/skills/mcp-server/references/server.md +140 -0
  190. data/skills/mcp-server/references/tools.md +146 -0
  191. data/skills/mcp-server/references/transport.md +104 -0
  192. data/skills/ratatui-ruby/SKILL.md +315 -0
  193. data/skills/ratatui-ruby/references/core-concepts.md +340 -0
  194. data/skills/ratatui-ruby/references/events.md +387 -0
  195. data/skills/ratatui-ruby/references/frameworks.md +522 -0
  196. data/skills/ratatui-ruby/references/layout.md +423 -0
  197. data/skills/ratatui-ruby/references/styling.md +268 -0
  198. data/skills/ratatui-ruby/references/testing.md +433 -0
  199. data/skills/ratatui-ruby/references/widgets.md +532 -0
  200. data/skills/rspec/SKILL.md +340 -0
  201. data/skills/rspec/examples/core/basic_structure.rb +69 -0
  202. data/skills/rspec/examples/core/configuration.rb +126 -0
  203. data/skills/rspec/examples/core/hooks.rb +126 -0
  204. data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
  205. data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
  206. data/skills/rspec/examples/core/shared_examples.rb +145 -0
  207. data/skills/rspec/examples/factory_bot/associations.rb +314 -0
  208. data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
  209. data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
  210. data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
  211. data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
  212. data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
  213. data/skills/rspec/examples/factory_bot/traits.rb +293 -0
  214. data/skills/rspec/examples/factory_bot/transients.rb +229 -0
  215. data/skills/rspec/examples/matchers/change.rb +115 -0
  216. data/skills/rspec/examples/matchers/collections.rb +154 -0
  217. data/skills/rspec/examples/matchers/comparisons.rb +79 -0
  218. data/skills/rspec/examples/matchers/composing.rb +155 -0
  219. data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
  220. data/skills/rspec/examples/matchers/equality.rb +58 -0
  221. data/skills/rspec/examples/matchers/errors.rb +136 -0
  222. data/skills/rspec/examples/matchers/output.rb +103 -0
  223. data/skills/rspec/examples/matchers/predicates.rb +87 -0
  224. data/skills/rspec/examples/matchers/truthiness.rb +101 -0
  225. data/skills/rspec/examples/matchers/types.rb +82 -0
  226. data/skills/rspec/examples/matchers/yield.rb +147 -0
  227. data/skills/rspec/examples/mocks/any_instance.rb +172 -0
  228. data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
  229. data/skills/rspec/examples/mocks/constants.rb +177 -0
  230. data/skills/rspec/examples/mocks/doubles.rb +139 -0
  231. data/skills/rspec/examples/mocks/expectations.rb +137 -0
  232. data/skills/rspec/examples/mocks/message_chains.rb +173 -0
  233. data/skills/rspec/examples/mocks/ordering.rb +144 -0
  234. data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
  235. data/skills/rspec/examples/mocks/responses.rb +223 -0
  236. data/skills/rspec/examples/mocks/spies.rb +149 -0
  237. data/skills/rspec/examples/mocks/stubbing.rb +133 -0
  238. data/skills/rspec/examples/rails/channels.rb +250 -0
  239. data/skills/rspec/examples/rails/controller_specs.rb +302 -0
  240. data/skills/rspec/examples/rails/helper_specs.rb +245 -0
  241. data/skills/rspec/examples/rails/job_specs.rb +256 -0
  242. data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
  243. data/skills/rspec/examples/rails/matchers.rb +374 -0
  244. data/skills/rspec/examples/rails/model_specs.rb +193 -0
  245. data/skills/rspec/examples/rails/request_specs.rb +275 -0
  246. data/skills/rspec/examples/rails/routing_specs.rb +276 -0
  247. data/skills/rspec/examples/rails/system_specs.rb +294 -0
  248. data/skills/rspec/examples/rails/transactions.rb +254 -0
  249. data/skills/rspec/examples/rails/view_specs.rb +252 -0
  250. data/skills/rspec/references/core.md +816 -0
  251. data/skills/rspec/references/factory_bot.md +641 -0
  252. data/skills/rspec/references/matchers.md +516 -0
  253. data/skills/rspec/references/mocks.md +381 -0
  254. data/skills/rspec/references/rails.md +528 -0
  255. data/templates/soul.md +40 -0
  256. data/workflows/commit.md +45 -0
  257. data/workflows/create_handoff.md +98 -0
  258. data/workflows/create_note.md +82 -0
  259. data/workflows/create_plan.md +457 -0
  260. data/workflows/decompose_ticket.md +109 -0
  261. data/workflows/feature.md +91 -0
  262. data/workflows/implement_plan.md +87 -0
  263. data/workflows/iterate_plan.md +247 -0
  264. data/workflows/research_codebase.md +210 -0
  265. data/workflows/resume_handoff.md +217 -0
  266. data/workflows/review_pr.md +320 -0
  267. data/workflows/thoughts_init.md +71 -0
  268. data/workflows/validate_plan.md +166 -0
  269. metadata +284 -1
@@ -0,0 +1,103 @@
1
+ # State Transitions in DragonRuby
2
+ # Demonstrates scene management and game over detection
3
+
4
+ def tick(args)
5
+ args.state.scene ||= :title
6
+ send("#{args.state.scene}_tick", args)
7
+ end
8
+
9
+ # === TITLE SCENE ===
10
+ def title_tick(args)
11
+ args.outputs.labels << {
12
+ x: 640, y: 450, text: "MY GAME",
13
+ size_enum: 15, anchor_x: 0.5
14
+ }
15
+ args.outputs.labels << {
16
+ x: 640, y: 350, text: "Press SPACE to start",
17
+ size_enum: 3, anchor_x: 0.5
18
+ }
19
+
20
+ if args.inputs.keyboard.key_down.space
21
+ # Initialize game state
22
+ args.state.player = { x: 640, y: 100, health: 3 }
23
+ args.state.score = 0
24
+ args.state.scene = :gameplay
25
+ return # Early return on transition
26
+ end
27
+ end
28
+
29
+ # === GAMEPLAY SCENE ===
30
+ def gameplay_tick(args)
31
+ # Initialize
32
+ args.state.player ||= { x: 640, y: 100, health: 3 }
33
+ args.state.score ||= 0
34
+
35
+ # Update
36
+ handle_gameplay_input(args)
37
+ check_game_over(args)
38
+
39
+ # Render
40
+ render_gameplay(args)
41
+ end
42
+
43
+ def handle_gameplay_input(args)
44
+ player = args.state.player
45
+ player.x -= 5 if args.inputs.left
46
+ player.x += 5 if args.inputs.right
47
+
48
+ # Simulate damage
49
+ if args.inputs.keyboard.key_down.x
50
+ player.health -= 1
51
+ end
52
+
53
+ # Simulate scoring
54
+ if args.inputs.keyboard.key_down.space
55
+ args.state.score += 10
56
+ end
57
+ end
58
+
59
+ def check_game_over(args)
60
+ if args.state.player.health <= 0
61
+ args.state.game_over_at = Kernel.tick_count
62
+ args.state.scene = :game_over
63
+ return # Early return critical!
64
+ end
65
+ end
66
+
67
+ def render_gameplay(args)
68
+ # Player
69
+ args.outputs.solids << {
70
+ x: args.state.player.x - 25, y: args.state.player.y,
71
+ w: 50, h: 50, r: 0, g: 200, b: 0
72
+ }
73
+
74
+ # HUD
75
+ args.outputs.labels << { x: 40, y: 700, text: "Score: #{args.state.score}" }
76
+ args.outputs.labels << { x: 40, y: 670, text: "Health: #{args.state.player.health}" }
77
+ args.outputs.labels << { x: 40, y: 40, text: "X: Take damage | SPACE: Score" }
78
+ end
79
+
80
+ # === GAME OVER SCENE ===
81
+ def game_over_tick(args)
82
+ args.outputs.labels << {
83
+ x: 640, y: 450, text: "GAME OVER",
84
+ size_enum: 15, anchor_x: 0.5, r: 255, g: 0, b: 0
85
+ }
86
+ args.outputs.labels << {
87
+ x: 640, y: 380, text: "Final Score: #{args.state.score}",
88
+ size_enum: 5, anchor_x: 0.5
89
+ }
90
+
91
+ # Grace period before accepting restart (30 frames = 0.5 sec)
92
+ elapsed = Kernel.tick_count - args.state.game_over_at
93
+ if elapsed > 30
94
+ args.outputs.labels << {
95
+ x: 640, y: 300, text: "Press SPACE to restart",
96
+ size_enum: 3, anchor_x: 0.5
97
+ }
98
+
99
+ if args.inputs.keyboard.key_down.space
100
+ $gtk.reset
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,87 @@
1
+ # Timer Patterns in DragonRuby
2
+ # Demonstrates frame-based timing, countdowns, and periodic execution
3
+
4
+ FPS = 60
5
+
6
+ def tick(args)
7
+ defaults(args)
8
+ tick_countdown_timer(args)
9
+ tick_progress_bar(args)
10
+ tick_periodic_spawner(args)
11
+ render(args)
12
+ end
13
+
14
+ def defaults(args)
15
+ # Countdown timer (30 seconds)
16
+ args.state.countdown ||= 30 * FPS
17
+
18
+ # Progress bar (fills over 5 seconds)
19
+ args.state.progress ||= 0
20
+ args.state.progress_max ||= 5 * FPS
21
+
22
+ # Periodic spawner
23
+ args.state.spawn_rate ||= FPS # Every second
24
+ args.state.spawned_count ||= 0
25
+ end
26
+
27
+ # Pattern 1: Countdown Timer
28
+ def tick_countdown_timer(args)
29
+ return if args.state.countdown <= 0
30
+
31
+ args.state.countdown -= 1
32
+
33
+ if args.state.countdown <= 0
34
+ args.state.time_up = true
35
+ end
36
+ end
37
+
38
+ # Pattern 2: Progress Bar (fills up)
39
+ def tick_progress_bar(args)
40
+ return if args.state.progress >= args.state.progress_max
41
+
42
+ args.state.progress += 1
43
+ end
44
+
45
+ # Pattern 3: Periodic Execution with zmod?
46
+ def tick_periodic_spawner(args)
47
+ # Execute every spawn_rate frames
48
+ if Kernel.tick_count.zmod?(args.state.spawn_rate)
49
+ args.state.spawned_count += 1
50
+ end
51
+ end
52
+
53
+ def render(args)
54
+ # Display countdown as seconds
55
+ seconds_left = (args.state.countdown / FPS).ceil
56
+ args.outputs.labels << {
57
+ x: 640, y: 600, text: "Time: #{seconds_left}s",
58
+ size_enum: 5, anchor_x: 0.5
59
+ }
60
+
61
+ # Display progress percentage
62
+ progress_pct = (args.state.progress.fdiv(args.state.progress_max) * 100).round
63
+ args.outputs.labels << {
64
+ x: 640, y: 500, text: "Progress: #{progress_pct}%",
65
+ size_enum: 3, anchor_x: 0.5
66
+ }
67
+
68
+ # Progress bar visual
69
+ bar_width = 400
70
+ filled = (args.state.progress.fdiv(args.state.progress_max) * bar_width).round
71
+ args.outputs.solids << { x: 440, y: 450, w: bar_width, h: 20, r: 100, g: 100, b: 100 }
72
+ args.outputs.solids << { x: 440, y: 450, w: filled, h: 20, r: 0, g: 200, b: 0 }
73
+
74
+ # Spawn counter
75
+ args.outputs.labels << {
76
+ x: 640, y: 400, text: "Spawned: #{args.state.spawned_count}",
77
+ size_enum: 3, anchor_x: 0.5
78
+ }
79
+
80
+ # Time up message
81
+ if args.state.time_up
82
+ args.outputs.labels << {
83
+ x: 640, y: 300, text: "TIME UP!",
84
+ size_enum: 10, anchor_x: 0.5, r: 255, g: 0, b: 0
85
+ }
86
+ end
87
+ end
@@ -0,0 +1,36 @@
1
+ # One-shot actions using key_down pattern
2
+ # Perfect for jump, shoot, interact buttons
3
+
4
+ def tick args
5
+ args.state.player ||= { x: 640, y: 100, dy: 0, on_ground: true }
6
+ args.state.bullets ||= []
7
+
8
+ # Jump: only trigger once per press (not while held)
9
+ if args.inputs.keyboard.key_down.space && args.state.player.on_ground
10
+ args.state.player.dy = 15 # Jump velocity
11
+ args.state.player.on_ground = false
12
+ end
13
+
14
+ # Shoot: fire one bullet per keypress
15
+ if args.inputs.keyboard.key_down.f
16
+ args.state.bullets << { x: args.state.player.x, y: args.state.player.y,
17
+ created_at: Kernel.tick_count }
18
+ end
19
+
20
+ # Simple gravity and ground collision
21
+ args.state.player.dy -= 0.5
22
+ args.state.player.y += args.state.player.dy
23
+
24
+ if args.state.player.y <= 100
25
+ args.state.player.y = 100
26
+ args.state.player.dy = 0
27
+ args.state.player.on_ground = true
28
+ end
29
+
30
+ # Cleanup old bullets
31
+ args.state.bullets.reject! { |b| b.created_at.elapsed_time > 60 }
32
+
33
+ # Render
34
+ args.outputs.sprites << args.state.player.merge(w: 32, h: 32, path: 'sprites/square/blue.png')
35
+ args.outputs.sprites << args.state.bullets.map { |b| b.merge(w: 8, h: 8, path: 'sprites/square/red.png') }
36
+ end
@@ -0,0 +1,28 @@
1
+ # Smooth analog movement using percentage values
2
+ # Allows for speed variation based on stick deflection
3
+
4
+ def tick args
5
+ controller = args.inputs.controller_one
6
+ args.state.player ||= { x: 640, y: 360, w: 32, h: 32, dx: 0, dy: 0 }
7
+
8
+ max_speed = 8
9
+
10
+ # Analog stick returns -1.0 to 1.0, multiply by max speed
11
+ # Small tilts = slow movement, full tilt = max speed
12
+ args.state.player.dx = controller.left_analog_x_perc * max_speed
13
+ args.state.player.dy = controller.left_analog_y_perc * max_speed
14
+
15
+ # Apply velocity
16
+ args.state.player.x += args.state.player.dx
17
+ args.state.player.y += args.state.player.dy
18
+
19
+ # Clamp to screen
20
+ args.state.player.x = args.state.player.x.clamp(0, 1280 - args.state.player.w)
21
+ args.state.player.y = args.state.player.y.clamp(0, 720 - args.state.player.h)
22
+
23
+ # Visual speed indicator
24
+ speed = Math.sqrt(args.state.player.dx**2 + args.state.player.dy**2)
25
+ args.outputs.labels << { x: 640, y: 700, text: "Speed: #{speed.to_sf} (tilt stick for variable speed)", size_enum: 5, alignment_enum: 1 }
26
+
27
+ args.outputs.sprites << args.state.player.merge(path: 'sprites/square/blue.png')
28
+ end
@@ -0,0 +1,28 @@
1
+ # Controller button and analog stick input
2
+ # Access via args.inputs.controller_one (or controller_two, etc.)
3
+
4
+ def tick args
5
+ controller = args.inputs.controller_one
6
+ args.state.player ||= { x: 640, y: 360, w: 32, h: 32 }
7
+
8
+ # Button inputs (key_down, key_held, key_up work like keyboard)
9
+ if controller.key_down.a
10
+ args.outputs.labels << { x: 640, y: 600, text: "A button pressed!", size_enum: 5, alignment_enum: 1 }
11
+ end
12
+
13
+ # D-pad directions
14
+ if controller.key_held.up
15
+ args.state.player.y += 5
16
+ end
17
+
18
+ # Analog stick positions: left_analog_x_perc and left_analog_y_perc
19
+ # Returns value from -1.0 to 1.0 (0.0 when centered)
20
+ args.state.player.x += controller.left_analog_x_perc * 8
21
+ args.state.player.y += controller.left_analog_y_perc * 8
22
+
23
+ # Display analog values
24
+ args.outputs.labels << { x: 10, y: 700, text: "Left stick X: #{controller.left_analog_x_perc.to_sf}" }
25
+ args.outputs.labels << { x: 10, y: 670, text: "Left stick Y: #{controller.left_analog_y_perc.to_sf}" }
26
+
27
+ args.outputs.sprites << args.state.player.merge(path: 'sprites/square/blue.png')
28
+ end
@@ -0,0 +1,24 @@
1
+ # Basic directional movement using args.inputs.up/down/left/right
2
+ # These shortcuts check keyboard arrows, WASD, and controller d-pad
3
+
4
+ def tick args
5
+ # Initialize player on first tick
6
+ args.state.player ||= { x: 640, y: 360, w: 32, h: 32,
7
+ path: 'sprites/square/blue.png' }
8
+
9
+ # Check directional inputs - returns true when pressed or held
10
+ if args.inputs.up
11
+ args.state.player.y += 5
12
+ elsif args.inputs.down
13
+ args.state.player.y -= 5
14
+ end
15
+
16
+ if args.inputs.left
17
+ args.state.player.x -= 5
18
+ elsif args.inputs.right
19
+ args.state.player.x += 5
20
+ end
21
+
22
+ # Render player
23
+ args.outputs.sprites << args.state.player
24
+ end
@@ -0,0 +1,28 @@
1
+ # Detecting specific keyboard events: key_down, key_held, key_up
2
+ # key_down: fires once when key is first pressed
3
+ # key_held: fires every frame while key is held
4
+ # key_up: fires once when key is released
5
+
6
+ def tick args
7
+ args.state.bullets ||= []
8
+
9
+ # key_down: fires ONCE per press (good for actions)
10
+ if args.inputs.keyboard.key_down.space
11
+ args.state.bullets << { x: 100, y: 100, created_at: Kernel.tick_count }
12
+ end
13
+
14
+ # key_held: fires EVERY FRAME while pressed (good for continuous movement)
15
+ if args.inputs.keyboard.key_held.w
16
+ args.outputs.labels << { x: 640, y: 360, text: "W is being held", size_enum: 5, alignment_enum: 1 }
17
+ end
18
+
19
+ # key_up: fires ONCE when released (good for charging mechanics)
20
+ if args.inputs.keyboard.key_up.escape
21
+ args.state.menu_opened_at = Kernel.tick_count
22
+ end
23
+
24
+ # Display status
25
+ args.outputs.labels << { x: 10, y: 700, text: "Press SPACE to fire (key_down)" }
26
+ args.outputs.labels << { x: 10, y: 670, text: "Hold W for message (key_held)" }
27
+ args.outputs.labels << { x: 10, y: 640, text: "Press ESC to log release (key_up)" }
28
+ end
@@ -0,0 +1,26 @@
1
+ # Handling mouse clicks and position
2
+ # args.inputs.mouse.click returns event info when clicked
3
+
4
+ def tick args
5
+ # Check for mouse click this frame
6
+ if args.inputs.mouse.click
7
+ # Store the click event (has x, y, created_at properties)
8
+ args.state.last_click = args.inputs.mouse.click
9
+ end
10
+
11
+ # Display click information
12
+ if args.state.last_click
13
+ click = args.state.last_click
14
+
15
+ # Access click position
16
+ args.outputs.labels << { x: 640, y: 400, text: "Clicked at: #{click.x}, #{click.y}", size_enum: 5, alignment_enum: 1 }
17
+
18
+ # How many frames ago did it happen?
19
+ args.outputs.labels << { x: 640, y: 350, text: "Clicked #{click.created_at_elapsed} ticks ago", size_enum: 5, alignment_enum: 1 }
20
+
21
+ # Draw a marker at click position
22
+ args.outputs.primitives << { x: click.x - 5, y: click.y - 5, w: 10, h: 10, r: 255, g: 0, b: 0, primitive_marker: :solid }
23
+ else
24
+ args.outputs.labels << { x: 640, y: 360, text: "Click anywhere!", size_enum: 5, alignment_enum: 1 }
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ # Movement with screen boundary clamping using .clamp method
2
+ # Prevents player from moving outside screen bounds
3
+
4
+ def tick args
5
+ args.state.player ||= { x: 640, y: 360, w: 64, h: 64 }
6
+
7
+ speed = 5
8
+
9
+ # Apply movement using unified directional helpers
10
+ args.state.player.x += args.inputs.left_right * speed
11
+ args.state.player.y += args.inputs.up_down * speed
12
+
13
+ # Clamp position to stay within screen bounds
14
+ # args.grid.w is 1280, args.grid.h is 720 by default
15
+ args.state.player.x = args.state.player.x.clamp(0, args.grid.w - args.state.player.w)
16
+ args.state.player.y = args.state.player.y.clamp(0, args.grid.h - args.state.player.h)
17
+
18
+ # Visual feedback at boundaries
19
+ args.outputs.primitives << { x: 0, y: 0, w: args.grid.w, h: args.grid.h, r: 255, g: 0, b: 0, primitive_marker: :border }
20
+ args.outputs.sprites << args.state.player.merge(path: 'sprites/square/blue.png')
21
+ args.outputs.labels << { x: 640, y: 700, text: "Try moving off screen - player is clamped", size_enum: 5, alignment_enum: 1 }
22
+ end
@@ -0,0 +1,32 @@
1
+ # Normalized diagonal movement using left_right and up_down
2
+ # Prevents faster diagonal movement by normalizing the vector
3
+
4
+ def tick args
5
+ args.state.player ||= { x: 640, y: 360, w: 32, h: 32 }
6
+
7
+ speed = 5
8
+
9
+ # args.inputs.left_right returns -1, 0, or 1
10
+ # args.inputs.up_down returns -1, 0, or 1
11
+ dx = args.inputs.left_right
12
+ dy = args.inputs.up_down
13
+
14
+ # Calculate vector length (diagonal movement without normalization = 1.41x speed)
15
+ length = Math.sqrt(dx * dx + dy * dy)
16
+
17
+ # Normalize: divide by length to get unit vector, then multiply by speed
18
+ if length > 0
19
+ dx = (dx / length) * speed
20
+ dy = (dy / length) * speed
21
+ end
22
+
23
+ # Apply normalized movement
24
+ args.state.player.x += dx
25
+ args.state.player.y += dy
26
+
27
+ # Show speed comparison
28
+ args.outputs.labels << { x: 640, y: 700, text: "Move diagonally - speed stays constant", size_enum: 5, alignment_enum: 1 }
29
+ args.outputs.labels << { x: 640, y: 670, text: "Current speed: #{Math.sqrt(dx**2 + dy**2).to_sf}", size_enum: 5, alignment_enum: 1 }
30
+
31
+ args.outputs.sprites << args.state.player.merge(path: 'sprites/square/blue.png')
32
+ end
@@ -0,0 +1,32 @@
1
+ # Frame animation using separate PNG files
2
+
3
+ def tick args
4
+ # Looping animation: 6 frames, 4 ticks each, repeat forever
5
+ looping_frame = 0.frame_index(6, 4, true)
6
+
7
+ args.outputs.sprites << {
8
+ x: 200, y: 400, w: 100, h: 100,
9
+ path: "sprites/dragon_fly_#{looping_frame}.png"
10
+ }
11
+
12
+ # One-time animation triggered by spacebar
13
+ if args.inputs.keyboard.key_down.space
14
+ args.state.attack_at = Kernel.tick_count
15
+ end
16
+
17
+ if args.state.attack_at
18
+ frame = args.state.attack_at.frame_index(6, 4, false)
19
+ frame ||= 0 # Stay on first frame when complete
20
+
21
+ args.outputs.sprites << {
22
+ x: 600, y: 400, w: 100, h: 100,
23
+ path: "sprites/dragon_fly_#{frame}.png"
24
+ }
25
+ end
26
+
27
+ args.outputs.labels << {
28
+ x: 640, y: 100,
29
+ text: "Press SPACE for one-time animation",
30
+ anchor_x: 0.5
31
+ }
32
+ end
@@ -0,0 +1,32 @@
1
+ # Text rendering with alignment and sizing
2
+
3
+ def tick args
4
+ # Size options
5
+ args.outputs.labels << { x: 640, y: 650, text: "Basic Label" }
6
+ args.outputs.labels << { x: 640, y: 600, text: "Small", size_enum: -1 }
7
+ args.outputs.labels << { x: 640, y: 570, text: "Medium", size_enum: 0 }
8
+ args.outputs.labels << { x: 640, y: 540, text: "Large", size_enum: 1 }
9
+ args.outputs.labels << { x: 640, y: 500, text: "Custom", size_px: 32 }
10
+
11
+ # Alignment with anchors
12
+ args.outputs.labels << { x: 640, y: 450, text: "Left", anchor_x: 0, anchor_y: 0.5 }
13
+ args.outputs.labels << { x: 640, y: 420, text: "Center", anchor_x: 0.5, anchor_y: 0.5 }
14
+ args.outputs.labels << { x: 640, y: 390, text: "Right", anchor_x: 1, anchor_y: 0.5 }
15
+
16
+ # Colors
17
+ args.outputs.labels << { x: 640, y: 340, text: "Red", r: 255, g: 0, b: 0 }
18
+ args.outputs.labels << { x: 640, y: 310, text: "Green", r: 0, g: 255, b: 0 }
19
+ args.outputs.labels << { x: 640, y: 280, text: "Blue", r: 0, g: 0, b: 255 }
20
+ args.outputs.labels << { x: 640, y: 250, text: "Faded", a: 128 }
21
+
22
+ # Custom font
23
+ args.outputs.labels << {
24
+ x: 640, y: 200,
25
+ text: "Custom Font",
26
+ font: "fonts/manaspc.ttf",
27
+ size_px: 24, anchor_x: 0.5
28
+ }
29
+
30
+ # Reference line
31
+ args.outputs.lines << { x: 640, y: 0, x2: 640, y2: 720, r: 100, g: 100, b: 100 }
32
+ end
@@ -0,0 +1,51 @@
1
+ # FIFO render order demonstration using args.outputs.primitives
2
+ # Items render in insertion order - first added = behind, last added = on top
3
+
4
+ def tick args
5
+ # All rendering uses args.outputs.primitives for FIFO control
6
+ # Each primitive renders ON TOP of previously added primitives
7
+
8
+ # 1. Background (rendered first = bottom layer)
9
+ args.outputs.primitives << {
10
+ x: 0, y: 0, w: 1280, h: 720,
11
+ r: 50, g: 50, b: 80,
12
+ primitive_marker: :solid
13
+ }
14
+
15
+ # 2. Ground
16
+ args.outputs.primitives << {
17
+ x: 0, y: 0, w: 1280, h: 200,
18
+ r: 100, g: 150, b: 100,
19
+ primitive_marker: :solid
20
+ }
21
+
22
+ # 3. Objects (back to front based on insertion order)
23
+ args.outputs.primitives << { x: 100, y: 150, w: 100, h: 150, path: :solid, r: 34, g: 100, b: 34 }
24
+ args.outputs.primitives << { x: 180, y: 150, w: 100, h: 150, path: :solid, r: 34, g: 139, b: 34 }
25
+ args.outputs.primitives << { x: 260, y: 150, w: 100, h: 150, path: :solid, r: 50, g: 200, b: 50 }
26
+
27
+ # Labels for objects
28
+ args.outputs.primitives << { x: 150, y: 120, text: "Back", anchor_x: 0.5 }
29
+ args.outputs.primitives << { x: 230, y: 120, text: "Middle", anchor_x: 0.5 }
30
+ args.outputs.primitives << { x: 310, y: 120, text: "Front", anchor_x: 0.5 }
31
+
32
+ # 4. UI bar (rendered last = top layer)
33
+ args.outputs.primitives << {
34
+ x: 0, y: 680, w: 1280, h: 40,
35
+ r: 0, g: 0, b: 0, a: 200,
36
+ primitive_marker: :solid
37
+ }
38
+
39
+ args.outputs.primitives << {
40
+ x: 640, y: 700,
41
+ text: "UI Layer - rendered last with primitives, appears on top",
42
+ anchor_x: 0.5, r: 255, g: 255, b: 255
43
+ }
44
+
45
+ # Explanation
46
+ args.outputs.primitives << {
47
+ x: 640, y: 550,
48
+ text: "FIFO: First In = Behind, Last In = On Top",
49
+ anchor_x: 0.5, size_px: 20
50
+ }
51
+ end
@@ -0,0 +1,61 @@
1
+ # Filled rectangles and borders using args.outputs.primitives
2
+ # Use primitive_marker: :solid or :border for rectangle types
3
+
4
+ def tick args
5
+ # Background using primitives
6
+ args.outputs.primitives << {
7
+ x: 0, y: 0, w: 1280, h: 720,
8
+ r: 32, g: 32, b: 32,
9
+ primitive_marker: :solid
10
+ }
11
+
12
+ # Solids with primitive_marker (recommended for FIFO control)
13
+ args.outputs.primitives << {
14
+ x: 100, y: 500, w: 100, h: 100,
15
+ primitive_marker: :solid
16
+ }
17
+
18
+ args.outputs.primitives << {
19
+ x: 220, y: 500, w: 100, h: 100,
20
+ r: 255, g: 0, b: 0,
21
+ primitive_marker: :solid
22
+ }
23
+
24
+ args.outputs.primitives << {
25
+ x: 340, y: 500, w: 100, h: 100,
26
+ r: 0, g: 255, b: 0, a: 128,
27
+ primitive_marker: :solid
28
+ }
29
+
30
+ args.outputs.primitives << {
31
+ x: 460, y: 500, w: 100, h: 100,
32
+ r: 0, g: 128, b: 255, a: 200,
33
+ primitive_marker: :solid
34
+ }
35
+
36
+ # path: :solid for better performance with many rectangles
37
+ # (auto-detected as sprite, no primitive_marker needed)
38
+ args.outputs.primitives << {
39
+ x: 580, y: 500, w: 100, h: 100,
40
+ path: :solid,
41
+ r: 255, g: 255, b: 0
42
+ }
43
+
44
+ # Borders with primitive_marker
45
+ args.outputs.primitives << {
46
+ x: 100, y: 300, w: 100, h: 100,
47
+ r: 255, g: 255, b: 255,
48
+ primitive_marker: :border
49
+ }
50
+
51
+ args.outputs.primitives << {
52
+ x: 220, y: 300, w: 100, h: 100,
53
+ r: 0, g: 255, b: 255,
54
+ primitive_marker: :border
55
+ }
56
+
57
+ # Labels explaining the approach
58
+ args.outputs.primitives << { x: 340, y: 640, text: "primitive_marker: :solid", size_px: 18 }
59
+ args.outputs.primitives << { x: 580, y: 640, text: "path: :solid (performance)", size_px: 18 }
60
+ args.outputs.primitives << { x: 160, y: 440, text: "primitive_marker: :border", size_px: 18 }
61
+ end
@@ -0,0 +1,33 @@
1
+ # Image rendering with anchors, rotation, and color tinting
2
+
3
+ def tick args
4
+ # Basic sprite
5
+ args.outputs.sprites << {
6
+ x: 100, y: 500, w: 128, h: 101,
7
+ path: 'dragonruby.png'
8
+ }
9
+
10
+ # Centered with anchors
11
+ args.outputs.sprites << {
12
+ x: 400, y: 500, w: 128, h: 101,
13
+ path: 'dragonruby.png',
14
+ anchor_x: 0.5, anchor_y: 0.5
15
+ }
16
+
17
+ # Rotation around center
18
+ args.outputs.sprites << {
19
+ x: 700, y: 500, w: 128, h: 101,
20
+ path: 'dragonruby.png',
21
+ angle: Kernel.tick_count % 360,
22
+ angle_anchor_x: 0.5, angle_anchor_y: 0.5
23
+ }
24
+
25
+ # Color tinting, transparency, flipping
26
+ args.outputs.sprites << {
27
+ x: 1000, y: 500, w: 128, h: 101,
28
+ path: 'dragonruby.png',
29
+ r: 255, g: 128, b: 128,
30
+ a: Kernel.tick_count % 255,
31
+ flip_horizontally: true
32
+ }
33
+ end
@@ -0,0 +1,39 @@
1
+ # Spritesheet animation using tile_x and tile_y
2
+
3
+ def tick args
4
+ args.state.player ||= { x: 640, y: 360, w: 64, h: 64 }
5
+
6
+ moving = args.inputs.left || args.inputs.right
7
+
8
+ if moving
9
+ args.state.run_start ||= Kernel.tick_count
10
+
11
+ # 6 frames, 3 ticks each, looping
12
+ tile_index = args.state.run_start.frame_index(6, 3, true)
13
+
14
+ args.outputs.sprites << {
15
+ x: args.state.player.x,
16
+ y: args.state.player.y,
17
+ w: args.state.player.w,
18
+ h: args.state.player.h,
19
+ path: 'sprites/horizontal-run.png',
20
+ tile_x: tile_index * 64,
21
+ tile_y: 0,
22
+ tile_w: 64,
23
+ tile_h: 64,
24
+ flip_horizontally: args.inputs.left
25
+ }
26
+ else
27
+ args.state.run_start = nil
28
+
29
+ args.outputs.sprites << {
30
+ x: args.state.player.x,
31
+ y: args.state.player.y,
32
+ w: args.state.player.w,
33
+ h: args.state.player.h,
34
+ path: 'sprites/horizontal-stand.png'
35
+ }
36
+ end
37
+
38
+ args.outputs.labels << { x: 640, y: 100, text: "Hold LEFT/RIGHT to run", anchor_x: 0.5 }
39
+ end