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.
- checksums.yaml +4 -4
- data/.reek.yml +27 -1
- data/CHANGELOG.md +4 -0
- data/README.md +219 -25
- data/agents/codebase-analyzer.md +88 -0
- data/agents/codebase-pattern-finder.md +83 -0
- data/agents/documentation-researcher.md +59 -0
- data/agents/thoughts-analyzer.md +102 -0
- data/agents/web-search-researcher.md +71 -0
- data/anima-core.gemspec +3 -0
- data/app/channels/session_channel.rb +76 -28
- data/app/jobs/agent_request_job.rb +24 -0
- data/app/jobs/analytical_brain_job.rb +33 -0
- data/app/jobs/count_event_tokens_job.rb +1 -1
- data/app/models/concerns/event/broadcasting.rb +20 -2
- data/app/models/event.rb +1 -1
- data/app/models/goal.rb +91 -0
- data/app/models/session.rb +347 -22
- data/config/application.rb +2 -0
- data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
- data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
- data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
- data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
- data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
- data/db/migrate/20260315140843_create_goals.rb +16 -0
- data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
- data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
- data/lib/agent_loop.rb +65 -9
- data/lib/agents/definition.rb +116 -0
- data/lib/agents/registry.rb +106 -0
- data/lib/analytical_brain/runner.rb +276 -0
- data/lib/analytical_brain/tools/activate_skill.rb +52 -0
- data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
- data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
- data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
- data/lib/analytical_brain/tools/finish_goal.rb +62 -0
- data/lib/analytical_brain/tools/read_workflow.rb +58 -0
- data/lib/analytical_brain/tools/rename_session.rb +63 -0
- data/lib/analytical_brain/tools/set_goal.rb +60 -0
- data/lib/analytical_brain/tools/update_goal.rb +60 -0
- data/lib/analytical_brain.rb +23 -0
- data/lib/anima/cli/mcp/secrets.rb +76 -0
- data/lib/anima/cli/mcp.rb +197 -0
- data/lib/anima/cli.rb +4 -0
- data/lib/anima/installer.rb +168 -0
- data/lib/anima/settings.rb +226 -0
- data/lib/anima/version.rb +1 -1
- data/lib/anima.rb +9 -0
- data/lib/credential_store.rb +103 -0
- data/lib/environment_probe.rb +232 -0
- data/lib/llm/client.rb +29 -10
- data/lib/mcp/client_manager.rb +86 -0
- data/lib/mcp/config.rb +213 -0
- data/lib/mcp/health_check.rb +77 -0
- data/lib/mcp/secrets.rb +73 -0
- data/lib/mcp/stdio_transport.rb +206 -0
- data/lib/providers/anthropic.rb +8 -7
- data/lib/shell_session.rb +11 -10
- data/lib/skills/definition.rb +97 -0
- data/lib/skills/registry.rb +105 -0
- data/lib/tools/edit.rb +3 -4
- data/lib/tools/mcp_tool.rb +114 -0
- data/lib/tools/read.rb +15 -16
- data/lib/tools/registry.rb +14 -12
- data/lib/tools/request_feature.rb +121 -0
- data/lib/tools/return_result.rb +81 -0
- data/lib/tools/spawn_specialist.rb +109 -0
- data/lib/tools/spawn_subagent.rb +111 -0
- data/lib/tools/subagent_prompts.rb +12 -0
- data/lib/tools/web_get.rb +8 -9
- data/lib/tui/app.rb +332 -43
- data/lib/tui/message_store.rb +20 -0
- data/lib/tui/screens/chat.rb +207 -20
- data/lib/workflows/definition.rb +97 -0
- data/lib/workflows/registry.rb +89 -0
- data/skills/activerecord/SKILL.md +255 -0
- data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
- data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
- data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
- data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
- data/skills/activerecord/examples/associations/self_referential.rb +302 -0
- data/skills/activerecord/examples/associations/through_associations.rb +203 -0
- data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
- data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
- data/skills/activerecord/examples/basics/inheritance.rb +377 -0
- data/skills/activerecord/examples/basics/type_casting.rb +317 -0
- data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
- data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
- data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
- data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
- data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
- data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
- data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
- data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
- data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
- data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
- data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
- data/skills/activerecord/examples/querying/optimization.rb +275 -0
- data/skills/activerecord/examples/querying/scopes.rb +260 -0
- data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
- data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
- data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
- data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
- data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
- data/skills/activerecord/references/associations.md +709 -0
- data/skills/activerecord/references/basics.md +622 -0
- data/skills/activerecord/references/callbacks.md +738 -0
- data/skills/activerecord/references/migrations.md +657 -0
- data/skills/activerecord/references/querying.md +655 -0
- data/skills/activerecord/references/validations.md +596 -0
- data/skills/dragonruby/SKILL.md +250 -0
- data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
- data/skills/dragonruby/examples/audio/background_music.rb +29 -0
- data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
- data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
- data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
- data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
- data/skills/dragonruby/examples/core/hello_world.rb +24 -0
- data/skills/dragonruby/examples/core/labels.rb +22 -0
- data/skills/dragonruby/examples/core/sprites.rb +35 -0
- data/skills/dragonruby/examples/core/state_management.rb +29 -0
- data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
- data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
- data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
- data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
- data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
- data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
- data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
- data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
- data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
- data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
- data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
- data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
- data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
- data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
- data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
- data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
- data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
- data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
- data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
- data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
- data/skills/dragonruby/examples/input/controller_input.rb +28 -0
- data/skills/dragonruby/examples/input/directional_input.rb +24 -0
- data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
- data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
- data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
- data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
- data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
- data/skills/dragonruby/examples/rendering/labels.rb +32 -0
- data/skills/dragonruby/examples/rendering/layering.rb +51 -0
- data/skills/dragonruby/examples/rendering/solids.rb +61 -0
- data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
- data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
- data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
- data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
- data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
- data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
- data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
- data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
- data/skills/dragonruby/references/audio.md +396 -0
- data/skills/dragonruby/references/core.md +385 -0
- data/skills/dragonruby/references/distribution.md +434 -0
- data/skills/dragonruby/references/entities.md +516 -0
- data/skills/dragonruby/references/game-logic/persistence.md +386 -0
- data/skills/dragonruby/references/game-logic/state.md +389 -0
- data/skills/dragonruby/references/input.md +414 -0
- data/skills/dragonruby/references/rendering/animation.md +467 -0
- data/skills/dragonruby/references/rendering/primitives.md +403 -0
- data/skills/dragonruby/references/scenes.md +443 -0
- data/skills/draper-decorators/SKILL.md +344 -0
- data/skills/draper-decorators/examples/application_decorator.rb +61 -0
- data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
- data/skills/draper-decorators/examples/model_decorator.rb +152 -0
- data/skills/draper-decorators/references/anti-patterns.md +640 -0
- data/skills/draper-decorators/references/patterns.md +507 -0
- data/skills/draper-decorators/references/testing.md +559 -0
- data/skills/gh-issue.md +182 -0
- data/skills/mcp-server/SKILL.md +177 -0
- data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
- data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
- data/skills/mcp-server/examples/http_client.rb +48 -0
- data/skills/mcp-server/examples/http_server.rb +97 -0
- data/skills/mcp-server/examples/rails_integration.rb +88 -0
- data/skills/mcp-server/examples/stdio_server.rb +108 -0
- data/skills/mcp-server/examples/streaming_client.rb +95 -0
- data/skills/mcp-server/references/gotchas.md +183 -0
- data/skills/mcp-server/references/prompts.md +98 -0
- data/skills/mcp-server/references/resources.md +53 -0
- data/skills/mcp-server/references/server.md +140 -0
- data/skills/mcp-server/references/tools.md +146 -0
- data/skills/mcp-server/references/transport.md +104 -0
- data/skills/ratatui-ruby/SKILL.md +315 -0
- data/skills/ratatui-ruby/references/core-concepts.md +340 -0
- data/skills/ratatui-ruby/references/events.md +387 -0
- data/skills/ratatui-ruby/references/frameworks.md +522 -0
- data/skills/ratatui-ruby/references/layout.md +423 -0
- data/skills/ratatui-ruby/references/styling.md +268 -0
- data/skills/ratatui-ruby/references/testing.md +433 -0
- data/skills/ratatui-ruby/references/widgets.md +532 -0
- data/skills/rspec/SKILL.md +340 -0
- data/skills/rspec/examples/core/basic_structure.rb +69 -0
- data/skills/rspec/examples/core/configuration.rb +126 -0
- data/skills/rspec/examples/core/hooks.rb +126 -0
- data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
- data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
- data/skills/rspec/examples/core/shared_examples.rb +145 -0
- data/skills/rspec/examples/factory_bot/associations.rb +314 -0
- data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
- data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
- data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
- data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
- data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
- data/skills/rspec/examples/factory_bot/traits.rb +293 -0
- data/skills/rspec/examples/factory_bot/transients.rb +229 -0
- data/skills/rspec/examples/matchers/change.rb +115 -0
- data/skills/rspec/examples/matchers/collections.rb +154 -0
- data/skills/rspec/examples/matchers/comparisons.rb +79 -0
- data/skills/rspec/examples/matchers/composing.rb +155 -0
- data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
- data/skills/rspec/examples/matchers/equality.rb +58 -0
- data/skills/rspec/examples/matchers/errors.rb +136 -0
- data/skills/rspec/examples/matchers/output.rb +103 -0
- data/skills/rspec/examples/matchers/predicates.rb +87 -0
- data/skills/rspec/examples/matchers/truthiness.rb +101 -0
- data/skills/rspec/examples/matchers/types.rb +82 -0
- data/skills/rspec/examples/matchers/yield.rb +147 -0
- data/skills/rspec/examples/mocks/any_instance.rb +172 -0
- data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
- data/skills/rspec/examples/mocks/constants.rb +177 -0
- data/skills/rspec/examples/mocks/doubles.rb +139 -0
- data/skills/rspec/examples/mocks/expectations.rb +137 -0
- data/skills/rspec/examples/mocks/message_chains.rb +173 -0
- data/skills/rspec/examples/mocks/ordering.rb +144 -0
- data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
- data/skills/rspec/examples/mocks/responses.rb +223 -0
- data/skills/rspec/examples/mocks/spies.rb +149 -0
- data/skills/rspec/examples/mocks/stubbing.rb +133 -0
- data/skills/rspec/examples/rails/channels.rb +250 -0
- data/skills/rspec/examples/rails/controller_specs.rb +302 -0
- data/skills/rspec/examples/rails/helper_specs.rb +245 -0
- data/skills/rspec/examples/rails/job_specs.rb +256 -0
- data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
- data/skills/rspec/examples/rails/matchers.rb +374 -0
- data/skills/rspec/examples/rails/model_specs.rb +193 -0
- data/skills/rspec/examples/rails/request_specs.rb +275 -0
- data/skills/rspec/examples/rails/routing_specs.rb +276 -0
- data/skills/rspec/examples/rails/system_specs.rb +294 -0
- data/skills/rspec/examples/rails/transactions.rb +254 -0
- data/skills/rspec/examples/rails/view_specs.rb +252 -0
- data/skills/rspec/references/core.md +816 -0
- data/skills/rspec/references/factory_bot.md +641 -0
- data/skills/rspec/references/matchers.md +516 -0
- data/skills/rspec/references/mocks.md +381 -0
- data/skills/rspec/references/rails.md +528 -0
- data/templates/soul.md +40 -0
- data/workflows/commit.md +45 -0
- data/workflows/create_handoff.md +98 -0
- data/workflows/create_note.md +82 -0
- data/workflows/create_plan.md +457 -0
- data/workflows/decompose_ticket.md +109 -0
- data/workflows/feature.md +91 -0
- data/workflows/implement_plan.md +87 -0
- data/workflows/iterate_plan.md +247 -0
- data/workflows/research_codebase.md +210 -0
- data/workflows/resume_handoff.md +217 -0
- data/workflows/review_pr.md +320 -0
- data/workflows/thoughts_init.md +71 -0
- data/workflows/validate_plan.md +166 -0
- 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` |
|