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,389 @@
1
+ # State Management
2
+
3
+ ## args.state Container
4
+
5
+ Central property bag persisting across ticks.
6
+
7
+ ```ruby
8
+ # All state persists automatically between frames
9
+ args.state.player ||= { x: 120, y: 280 }
10
+ args.state.score ||= 0
11
+ args.state.enemies ||= []
12
+ ```
13
+
14
+ **Key characteristics:**
15
+ - Values retained across tick invocations
16
+ - Cleared on `$gtk.reset`
17
+ - Supports arbitrary nesting with automatic Entity backing
18
+
19
+ ## Lazy Initialization (||= Pattern)
20
+
21
+ Initialize values only on first access - prevents reset every frame.
22
+
23
+ ```ruby
24
+ # CORRECT: Set once, persist forever
25
+ args.state.player_x ||= 120
26
+ args.state.timer ||= 30 * 60
27
+
28
+ # WRONG: Resets every frame!
29
+ args.state.player_x = 120
30
+ ```
31
+
32
+ **Why critical:** `tick` runs 60 times/second - without `||=`, values reset constantly.
33
+
34
+ ## Frame-Based Timing
35
+
36
+ DragonRuby runs at 60 FPS. Convert seconds to frames:
37
+
38
+ ```ruby
39
+ FPS = 60
40
+
41
+ # 30 seconds = 1800 frames
42
+ args.state.timer ||= 30 * FPS
43
+
44
+ # Display as seconds
45
+ seconds_left = (args.state.timer / FPS).round
46
+ ```
47
+
48
+ ### Timer Countdown Pattern
49
+
50
+ ```ruby
51
+ # Decrement each frame
52
+ args.state.timer -= 1
53
+
54
+ # Check expiration
55
+ if args.state.timer < 0
56
+ game_over_tick(args)
57
+ return # Early return critical!
58
+ end
59
+ ```
60
+
61
+ ### Periodic Execution with zmod?
62
+
63
+ Execute code every N frames:
64
+
65
+ ```ruby
66
+ # Every second (60 frames)
67
+ if Kernel.tick_count.zmod?(60)
68
+ spawn_enemy(args)
69
+ end
70
+
71
+ # Every 3 frames
72
+ if Kernel.tick_count.zmod?(3)
73
+ update_animation(args)
74
+ end
75
+ ```
76
+
77
+ ### Elapsed Time Tracking
78
+
79
+ ```ruby
80
+ # Record event timestamp
81
+ args.state.last_click_at ||= 0
82
+ if args.inputs.mouse.click
83
+ args.state.last_click_at = Kernel.tick_count
84
+ end
85
+
86
+ # Check elapsed time
87
+ if args.state.last_click_at.elapsed_time > 120 # 2 seconds
88
+ show_hint(args)
89
+ end
90
+
91
+ # Check if expired
92
+ if args.state.spawn_at.elapsed?(60) # 1 second passed
93
+ do_spawn(args)
94
+ end
95
+ ```
96
+
97
+ ### Entity created_at (Automatic)
98
+
99
+ Entities track creation time automatically:
100
+
101
+ ```ruby
102
+ enemy = args.state.new_entity(:enemy, x: 100, y: 100)
103
+
104
+ # Available properties:
105
+ enemy.created_at # Kernel.tick_count at creation
106
+ enemy.created_at_elapsed # Frames since creation
107
+ enemy.global_created_at # Never resets with $gtk.reset
108
+ ```
109
+
110
+ ## Grace Periods
111
+
112
+ Use negative timer values for input delays:
113
+
114
+ ```ruby
115
+ # Timer continues counting negative
116
+ args.state.timer -= 1
117
+
118
+ if args.state.timer < 0
119
+ # Show game over
120
+ render_game_over(args)
121
+
122
+ # Wait 30 frames before accepting restart input
123
+ if args.state.timer < -30 && fire_input?(args)
124
+ $gtk.reset
125
+ end
126
+ return
127
+ end
128
+ ```
129
+
130
+ **Why:** Prevents accidental restarts when player is mashing buttons.
131
+
132
+ ## Scoring System
133
+
134
+ ```ruby
135
+ # Initialize
136
+ args.state.score ||= 0
137
+
138
+ # Update on events
139
+ if collision?(fireball, target)
140
+ args.state.score += 1
141
+ end
142
+
143
+ # Display
144
+ args.outputs.labels << {
145
+ x: 40, y: 700,
146
+ text: "Score: #{args.state.score}"
147
+ }
148
+ ```
149
+
150
+ ## Game State Transitions
151
+
152
+ ### Scene Pattern with send
153
+
154
+ ```ruby
155
+ def tick(args)
156
+ args.state.scene ||= :title
157
+ send("#{args.state.scene}_tick", args)
158
+ end
159
+
160
+ def title_tick(args)
161
+ # Title screen logic
162
+ if fire_input?(args)
163
+ args.state.scene = :gameplay
164
+ return # Early return on transition
165
+ end
166
+ end
167
+
168
+ def gameplay_tick(args)
169
+ # Main game logic
170
+ if args.state.timer < 0
171
+ args.state.scene = :game_over
172
+ return
173
+ end
174
+ end
175
+
176
+ def game_over_tick(args)
177
+ # Game over logic
178
+ if fire_input?(args)
179
+ $gtk.reset
180
+ end
181
+ end
182
+ ```
183
+
184
+ ### Early Return Pattern
185
+
186
+ **Critical:** After state changes, `return` to prevent subsequent code execution.
187
+
188
+ ```ruby
189
+ def tick(args)
190
+ if args.state.paused
191
+ render_pause_menu(args)
192
+ return # Skip game logic!
193
+ end
194
+
195
+ # Normal gameplay only runs when not paused
196
+ update_player(args)
197
+ update_enemies(args)
198
+ end
199
+ ```
200
+
201
+ ## State Reset
202
+
203
+ ### Full Reset ($gtk.reset)
204
+
205
+ Clears all `args.state` and resets `tick_count` to 0.
206
+
207
+ ```ruby
208
+ # Restart game
209
+ if args.inputs.keyboard.key_down.r
210
+ $gtk.reset
211
+ end
212
+ ```
213
+
214
+ **During development:** Add at file end to force reinitialization on code reload:
215
+
216
+ ```ruby
217
+ def tick(args)
218
+ # game code
219
+ end
220
+
221
+ $gtk.reset # Dev convenience - remove for production
222
+ ```
223
+
224
+ ### Custom Reset Handler
225
+
226
+ Define `reset` method for cleanup when `$gtk.reset` called:
227
+
228
+ ```ruby
229
+ def reset(args)
230
+ $game = nil # Clear global instances
231
+ puts "Game reset!"
232
+ end
233
+
234
+ def tick(args)
235
+ $game ||= Game.new
236
+ $game.tick(args)
237
+ end
238
+ ```
239
+
240
+ ### Partial Reset
241
+
242
+ Reset specific state without full reset:
243
+
244
+ ```ruby
245
+ def restart_level(args)
246
+ args.state.player.x = 120
247
+ args.state.player.y = 280
248
+ args.state.enemies = []
249
+ # Keep score, reset position
250
+ end
251
+ ```
252
+
253
+ ## One-Time Initialization
254
+
255
+ Execute code only on first tick:
256
+
257
+ ```ruby
258
+ def tick(args)
259
+ if Kernel.tick_count == 0
260
+ # First frame only
261
+ args.audio[:music] = { input: "sounds/theme.ogg", looping: true }
262
+ end
263
+ end
264
+ ```
265
+
266
+ **Alternative:** Use `boot` method:
267
+
268
+ ```ruby
269
+ def boot(args)
270
+ args.state = {}
271
+ # One-time setup
272
+ end
273
+
274
+ def tick(args)
275
+ # Normal game loop
276
+ end
277
+ ```
278
+
279
+ ## Class-Based State (Alternative)
280
+
281
+ For larger games, use classes with `attr_gtk`:
282
+
283
+ ```ruby
284
+ class Game
285
+ attr_gtk
286
+
287
+ def initialize
288
+ @player = { x: 0, y: 0 }
289
+ @enemies = []
290
+ end
291
+
292
+ def tick
293
+ # Access: @player, state, inputs, outputs
294
+ @player.x += 1 if inputs.right
295
+ end
296
+ end
297
+
298
+ def tick(args)
299
+ $game ||= Game.new
300
+ $game.args = args
301
+ $game.tick
302
+ end
303
+ ```
304
+
305
+ ## Common Antipatterns
306
+
307
+ ### Resetting State Every Frame
308
+
309
+ ```ruby
310
+ # WRONG
311
+ args.state.score = 0 # Resets to 0 every frame!
312
+
313
+ # CORRECT
314
+ args.state.score ||= 0
315
+ ```
316
+
317
+ ### Missing Early Return
318
+
319
+ ```ruby
320
+ # WRONG - game logic runs during game over
321
+ if args.state.game_over
322
+ render_game_over(args)
323
+ end
324
+ update_player(args) # Still runs!
325
+
326
+ # CORRECT
327
+ if args.state.game_over
328
+ render_game_over(args)
329
+ return
330
+ end
331
+ update_player(args)
332
+ ```
333
+
334
+ ### Forgetting Timer Conversion
335
+
336
+ ```ruby
337
+ # WRONG - timer is 30 frames (0.5 seconds)
338
+ args.state.timer ||= 30
339
+
340
+ # CORRECT - timer is 30 seconds
341
+ args.state.timer ||= 30 * 60
342
+ ```
343
+
344
+ ### Global State Pollution
345
+
346
+ ```ruby
347
+ # WRONG - pollutes global namespace
348
+ @player_x = 100
349
+
350
+ # CORRECT
351
+ args.state.player_x ||= 100
352
+ ```
353
+
354
+ ## Decision Tree
355
+
356
+ ```
357
+ Need to track time?
358
+ ├── Countdown to event → examples/game-logic/timers.rb (countdown pattern)
359
+ ├── Periodic action (every N frames) → examples/game-logic/timers.rb (zmod? pattern)
360
+ └── Time since event → elapsed_time / elapsed?
361
+
362
+ Need to track score?
363
+ ├── Simple increment → examples/game-logic/scoring.rb
364
+ ├── With combo/multiplier → examples/game-logic/scoring.rb (combo pattern)
365
+ └── With high score → examples/game-logic/scoring.rb + save_load.rb
366
+
367
+ Need multiple game states?
368
+ ├── Title/Gameplay/GameOver → examples/game-logic/state_transitions.rb
369
+ ├── Pause menu → Early return pattern
370
+ └── Level transitions → Partial reset pattern
371
+
372
+ Need to reset game?
373
+ ├── Full restart → $gtk.reset (examples/game-logic/reset_patterns.rb)
374
+ ├── Keep high score → Use ||= in defaults
375
+ ├── Restart level only → examples/game-logic/reset_patterns.rb (partial_reset)
376
+ └── Reset position only → examples/game-logic/reset_patterns.rb (soft_reset)
377
+
378
+ Need restart delay?
379
+ └── Grace period → examples/game-logic/state_transitions.rb (game_over_tick)
380
+ ```
381
+
382
+ ## Examples
383
+
384
+ | File | Demonstrates |
385
+ |------|--------------|
386
+ | `examples/game-logic/timers.rb` | Countdown, zmod? periodic, elapsed_time |
387
+ | `examples/game-logic/scoring.rb` | Score tracking, combo multipliers |
388
+ | `examples/game-logic/state_transitions.rb` | Scene management, send pattern, grace periods |
389
+ | `examples/game-logic/reset_patterns.rb` | Full reset, partial reset, soft reset |