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,443 @@
1
+ # DragonRuby Scenes Reference
2
+
3
+ ## Overview
4
+
5
+ Scenes separate distinct game states (title, gameplay, game over, pause). Each scene has its own logic, rendering, and input handling. DragonRuby provides no built-in scene system—implement patterns below.
6
+
7
+ ## Scene State Variable
8
+
9
+ Store current scene in `args.state`:
10
+
11
+ ```ruby
12
+ args.state.scene ||= :title # Symbol (recommended)
13
+ args.state.scene ||= "gameplay" # String (also works)
14
+ ```
15
+
16
+ ## Pattern 1: Send-Based Dispatch (Recommended)
17
+
18
+ Dynamic method dispatch using Ruby's `send`. Simple and extensible.
19
+
20
+ ```ruby
21
+ def tick args
22
+ args.state.scene ||= :title
23
+ send("#{args.state.scene}_tick", args)
24
+ end
25
+
26
+ def title_tick args
27
+ args.outputs.labels << { x: 640, y: 400, text: "Press SPACE to start", anchor_x: 0.5 }
28
+ if args.inputs.keyboard.key_down.space
29
+ args.state.scene = :gameplay
30
+ return # Early return prevents executing old scene code
31
+ end
32
+ end
33
+
34
+ def gameplay_tick args
35
+ # Main game logic
36
+ if game_over_condition?
37
+ args.state.scene = :game_over
38
+ return
39
+ end
40
+ end
41
+
42
+ def game_over_tick args
43
+ args.outputs.labels << { x: 640, y: 400, text: "Game Over!", anchor_x: 0.5 }
44
+ if args.inputs.keyboard.key_down.space
45
+ $gtk.reset # Full state reset
46
+ end
47
+ end
48
+ ```
49
+
50
+ ### Naming Convention
51
+
52
+ Scene methods **must** match pattern: `{scene_name}_tick`
53
+
54
+ | `args.state.scene` | Method Called |
55
+ |---|---|
56
+ | `:title` | `title_tick` |
57
+ | `:gameplay` | `gameplay_tick` |
58
+ | `:game_over` | `game_over_tick` |
59
+ | `:pause` | `pause_tick` |
60
+
61
+ ## Pattern 2: Case-Based Dispatch
62
+
63
+ Explicit control flow with case statement. Catches undefined scenes.
64
+
65
+ ```ruby
66
+ def tick args
67
+ args.state.current_scene ||= :title
68
+
69
+ case args.state.current_scene
70
+ when :title
71
+ tick_title args
72
+ when :game
73
+ tick_game args
74
+ when :game_over
75
+ tick_game_over args
76
+ else
77
+ raise "Unknown scene: #{args.state.current_scene}"
78
+ end
79
+ end
80
+ ```
81
+
82
+ ## Pattern 3: Safe Scene Transitions
83
+
84
+ Prevent mid-tick scene changes (debugging aid):
85
+
86
+ ```ruby
87
+ def tick args
88
+ args.state.current_scene ||= :title
89
+ scene_before = args.state.current_scene
90
+
91
+ case args.state.current_scene
92
+ when :title then tick_title(args)
93
+ when :game then tick_game(args)
94
+ end
95
+
96
+ # Validate no direct scene change
97
+ if args.state.current_scene != scene_before
98
+ raise "Scene changed mid-tick! Use args.state.next_scene instead."
99
+ end
100
+
101
+ # Apply deferred transition
102
+ if args.state.next_scene
103
+ args.state.current_scene = args.state.next_scene
104
+ args.state.next_scene = nil
105
+ end
106
+ end
107
+
108
+ def tick_title args
109
+ if args.inputs.keyboard.key_down.space
110
+ args.state.next_scene = :game # Deferred, not immediate
111
+ end
112
+ end
113
+ ```
114
+
115
+ ## Scene Transitions
116
+
117
+ ### Direct Transition
118
+
119
+ ```ruby
120
+ args.state.scene = :new_scene
121
+ return # IMPORTANT: Prevent further execution
122
+ ```
123
+
124
+ ### With State Reset
125
+
126
+ ```ruby
127
+ def transition_to_gameplay args
128
+ args.state.score = 0
129
+ args.state.timer = 30 * 60
130
+ args.state.player = nil # Will reinitialize
131
+ args.state.scene = :gameplay
132
+ end
133
+ ```
134
+
135
+ ### Full Game Reset
136
+
137
+ ```ruby
138
+ $gtk.reset # Clears all state, restarts from tick 0
139
+ ```
140
+
141
+ ### Tracked Transitions
142
+
143
+ Track when scene changed for animations:
144
+
145
+ ```ruby
146
+ def change_to_scene args, scene
147
+ args.state.scene = scene
148
+ args.state.scene_at = Kernel.tick_count
149
+ args.inputs.keyboard.clear # Prevent input bleed
150
+ end
151
+
152
+ # Use scene_at for animations
153
+ def title_tick args
154
+ elapsed = Kernel.tick_count - args.state.scene_at
155
+ alpha = [255, elapsed * 5].min # Fade in
156
+ args.outputs.labels << { x: 640, y: 400, text: "Title", a: alpha }
157
+ end
158
+ ```
159
+
160
+ ## Title Scene Pattern
161
+
162
+ ```ruby
163
+ def title_tick args
164
+ # Display title and instructions
165
+ labels = []
166
+ labels << { x: 640, y: 500, text: "Game Title", size_enum: 10, anchor_x: 0.5 }
167
+ labels << { x: 640, y: 400, text: "by Author Name", anchor_x: 0.5 }
168
+ labels << { x: 640, y: 200, text: "Arrow keys to move | Z to shoot", anchor_x: 0.5 }
169
+ labels << { x: 640, y: 150, text: "Press SPACE to start", size_enum: 2, anchor_x: 0.5 }
170
+ args.outputs.labels << labels
171
+
172
+ # Start game on input
173
+ if args.inputs.keyboard.key_down.space ||
174
+ args.inputs.controller_one.key_down.a
175
+ args.outputs.sounds << "sounds/start.wav"
176
+ args.state.scene = :gameplay
177
+ return
178
+ end
179
+ end
180
+ ```
181
+
182
+ ## Gameplay Scene Pattern
183
+
184
+ ```ruby
185
+ def gameplay_tick args
186
+ # Initialize state
187
+ args.state.player ||= { x: 640, y: 360, w: 64, h: 64 }
188
+ args.state.score ||= 0
189
+ args.state.timer ||= 60 * 60 # 60 seconds
190
+
191
+ # Timer countdown
192
+ args.state.timer -= 1
193
+
194
+ # Check game over conditions
195
+ if args.state.timer <= 0
196
+ args.outputs.sounds << "sounds/game-over.wav"
197
+ args.state.scene = :game_over
198
+ return
199
+ end
200
+
201
+ # Handle input
202
+ handle_player_input args
203
+
204
+ # Update game logic
205
+ update_entities args
206
+
207
+ # Render
208
+ render_gameplay args
209
+ end
210
+ ```
211
+
212
+ ## Game Over Scene Pattern
213
+
214
+ ```ruby
215
+ def game_over_tick args
216
+ # Continue timer for grace period
217
+ args.state.timer -= 1
218
+
219
+ # Display results
220
+ labels = []
221
+ labels << { x: 640, y: 450, text: "Game Over!", size_enum: 10, anchor_x: 0.5 }
222
+ labels << { x: 640, y: 350, text: "Score: #{args.state.score}", size_enum: 4, anchor_x: 0.5 }
223
+ labels << { x: 640, y: 200, text: "Press SPACE to restart", anchor_x: 0.5 }
224
+ args.outputs.labels << labels
225
+
226
+ # Grace period before accepting restart input
227
+ return if args.state.timer > -30
228
+
229
+ if args.inputs.keyboard.key_down.space
230
+ $gtk.reset # Full restart
231
+ end
232
+ end
233
+ ```
234
+
235
+ ## Pause Scene Pattern
236
+
237
+ Overlay pause on gameplay:
238
+
239
+ ```ruby
240
+ def tick args
241
+ args.state.scene ||= :gameplay
242
+
243
+ # Check for pause toggle
244
+ if args.inputs.keyboard.key_down.escape && args.state.scene == :gameplay
245
+ args.state.scene = :paused
246
+ args.state.paused_at = Kernel.tick_count
247
+ return
248
+ end
249
+
250
+ send("#{args.state.scene}_tick", args)
251
+ end
252
+
253
+ def paused_tick args
254
+ # Render frozen gameplay underneath
255
+ render_gameplay_static args
256
+
257
+ # Dark overlay
258
+ args.outputs.solids << { x: 0, y: 0, w: 1280, h: 720, r: 0, g: 0, b: 0, a: 180 }
259
+
260
+ # Pause menu
261
+ args.outputs.labels << { x: 640, y: 400, text: "PAUSED", size_enum: 10, anchor_x: 0.5, r: 255, g: 255, b: 255 }
262
+ args.outputs.labels << { x: 640, y: 300, text: "Press ESC to resume", anchor_x: 0.5, r: 255, g: 255, b: 255 }
263
+
264
+ if args.inputs.keyboard.key_down.escape
265
+ args.state.scene = :gameplay
266
+ end
267
+ end
268
+ ```
269
+
270
+ ## State Persistence Across Scenes
271
+
272
+ ### Shared State
273
+
274
+ State persists automatically:
275
+
276
+ ```ruby
277
+ def gameplay_tick args
278
+ args.state.score += 1 # Score survives scene change
279
+ end
280
+
281
+ def game_over_tick args
282
+ # args.state.score still accessible
283
+ args.outputs.labels << { x: 640, y: 350, text: "Final Score: #{args.state.score}" }
284
+ end
285
+ ```
286
+
287
+ ### Selective Reset
288
+
289
+ Reset only what's needed:
290
+
291
+ ```ruby
292
+ def reset_gameplay args
293
+ args.state.player = nil
294
+ args.state.enemies = []
295
+ args.state.timer = 60 * 60
296
+ # args.state.high_score preserved
297
+ end
298
+ ```
299
+
300
+ ## Conditional Rendering Pattern
301
+
302
+ Alternative to dispatch—render based on scene state:
303
+
304
+ ```ruby
305
+ def tick args
306
+ args.state.scene ||= :menu
307
+
308
+ render_background args
309
+ render_ui args
310
+
311
+ render_menu args if args.state.scene == :menu
312
+ render_game args if args.state.scene == :game
313
+ end
314
+
315
+ def render_menu args
316
+ return unless args.state.scene == :menu
317
+ # Menu rendering
318
+ end
319
+ ```
320
+
321
+ ## Class-Based Scenes (Advanced)
322
+
323
+ For complex games with many scenes:
324
+
325
+ ```ruby
326
+ class TitleScene
327
+ attr_accessor :args
328
+
329
+ def id; :title; end
330
+
331
+ def tick
332
+ args.outputs.labels << { x: 640, y: 400, text: "Title" }
333
+ args.state.next_scene = :game if args.inputs.keyboard.key_down.space
334
+ end
335
+ end
336
+
337
+ class GameScene
338
+ attr_accessor :args
339
+
340
+ def id; :game; end
341
+
342
+ def tick
343
+ # Game logic
344
+ end
345
+ end
346
+
347
+ def tick args
348
+ $scenes ||= [TitleScene.new, GameScene.new]
349
+ args.state.scene ||= :title
350
+
351
+ scene = $scenes.find { |s| s.id == args.state.scene }
352
+ scene.args = args
353
+ scene.tick
354
+
355
+ if args.state.next_scene
356
+ args.state.scene = args.state.next_scene
357
+ args.state.next_scene = nil
358
+ end
359
+ end
360
+ ```
361
+
362
+ ## Common Antipatterns
363
+
364
+ ### Missing Early Return
365
+
366
+ ```ruby
367
+ # WRONG - executes both scenes
368
+ def gameplay_tick args
369
+ if game_over?
370
+ args.state.scene = :game_over
371
+ end
372
+ update_player args # Still runs after scene change!
373
+ end
374
+
375
+ # CORRECT - early return
376
+ def gameplay_tick args
377
+ if game_over?
378
+ args.state.scene = :game_over
379
+ return
380
+ end
381
+ update_player args
382
+ end
383
+ ```
384
+
385
+ ### Undefined Scene Methods
386
+
387
+ ```ruby
388
+ # WRONG - crashes if scene method missing
389
+ send("#{args.state.scene}_tick", args)
390
+
391
+ # CORRECT - validate scene exists
392
+ valid_scenes = [:title, :gameplay, :game_over]
393
+ unless valid_scenes.include?(args.state.scene)
394
+ raise "Invalid scene: #{args.state.scene}"
395
+ end
396
+ ```
397
+
398
+ ### Mid-Tick Scene Changes
399
+
400
+ ```ruby
401
+ # WRONG - hard to debug
402
+ def some_helper args
403
+ args.state.scene = :game_over # Hidden transition
404
+ end
405
+
406
+ # CORRECT - centralized transitions
407
+ def transition_to_game_over args
408
+ args.state.scene = :game_over
409
+ args.state.game_ended_at = Kernel.tick_count
410
+ end
411
+ ```
412
+
413
+ ## Decision Tree
414
+
415
+ ```
416
+ What scene pattern to use?
417
+ ├─ Simple game (2-4 scenes) → examples/scenes/send_dispatch.rb
418
+ ├─ Need explicit scene validation → examples/scenes/case_dispatch.rb
419
+ ├─ Debugging scene transitions → examples/scenes/safe_transitions.rb
420
+ ├─ Complex game with shared data → examples/scenes/class_based.rb
421
+ └─ Pause overlay on gameplay → examples/scenes/pause_overlay.rb
422
+ ```
423
+
424
+ ## Examples
425
+
426
+ | File | Demonstrates |
427
+ |------|--------------|
428
+ | `examples/scenes/send_dispatch.rb` | Dynamic method dispatch with send |
429
+ | `examples/scenes/case_dispatch.rb` | Case-based scene switching |
430
+ | `examples/scenes/safe_transitions.rb` | Deferred scene transitions |
431
+ | `examples/scenes/class_based.rb` | OOP scene management |
432
+ | `examples/scenes/pause_overlay.rb` | Pause menu overlay |
433
+
434
+ ## Quick Reference
435
+
436
+ | Task | Solution |
437
+ |---|---|
438
+ | Initialize scene | `args.state.scene \|\|= :title` |
439
+ | Change scene | `args.state.scene = :new_scene; return` |
440
+ | Full reset | `$gtk.reset` |
441
+ | Track transition time | `args.state.scene_at = Kernel.tick_count` |
442
+ | Grace period | `return if args.state.timer > -30` |
443
+ | Clear input bleed | `args.inputs.keyboard.clear` |