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,386 @@
1
+ # Persistence (Save/Load)
2
+
3
+ ## File System Overview
4
+
5
+ DragonRuby uses a sandboxed filesystem:
6
+
7
+ | Environment | Read Location | Write Location |
8
+ |-------------|---------------|----------------|
9
+ | Development | Game directory | Game directory |
10
+ | Windows | Data dir, then game | `C:\Users\<name>\AppData\Roaming\[devtitle]\[gametitle]` |
11
+ | macOS | Data dir, then game | `$HOME/Library/Application Support/[gametitle]` |
12
+ | Linux | Data dir, then game | `$HOME/.local/share/[gametitle]` |
13
+ | HTML5 | IndexedDB | IndexedDB |
14
+
15
+ ## Basic File Operations
16
+
17
+ ### Reading Files
18
+
19
+ ```ruby
20
+ contents = $gtk.read_file("save.txt")
21
+
22
+ if contents
23
+ puts contents
24
+ else
25
+ puts "File does not exist"
26
+ end
27
+ ```
28
+
29
+ **Returns:** String content or `nil` if file doesn't exist.
30
+
31
+ ### Writing Files
32
+
33
+ ```ruby
34
+ $gtk.write_file("save.txt", "Player score: 100")
35
+ ```
36
+
37
+ **Behavior:** Overwrites existing file. Creates if doesn't exist.
38
+
39
+ ### Appending to Files
40
+
41
+ ```ruby
42
+ $gtk.append_file("log.txt", "Event at #{Kernel.tick_count}\n")
43
+ ```
44
+
45
+ **Behavior:** Adds to end of file. Creates if doesn't exist.
46
+
47
+ ### Deleting Files
48
+
49
+ ```ruby
50
+ # Always check existence first
51
+ if $gtk.stat_file("temp.txt")
52
+ $gtk.delete_file("temp.txt")
53
+ end
54
+ ```
55
+
56
+ **Caution:** Raises exceptions if file open, permissions denied, or directory not empty.
57
+
58
+ ### File Info
59
+
60
+ ```ruby
61
+ info = $gtk.stat_file("save.txt")
62
+
63
+ if info
64
+ puts info.file_size # Integer
65
+ puts info.mod_time # Integer timestamp
66
+ puts info.file_type # :regular, :directory, :symlink
67
+ puts info.readonly # Boolean
68
+ end
69
+ ```
70
+
71
+ ### Listing Directory
72
+
73
+ ```ruby
74
+ files = $gtk.list_files("sprites/")
75
+ # Returns: ["player.png", "enemy.png", ...]
76
+ ```
77
+
78
+ ## Type Conversion Patterns
79
+
80
+ ### Reading Numbers (Nil-Safe)
81
+
82
+ ```ruby
83
+ # .to_i on nil returns 0 - perfect for missing files
84
+ HIGH_SCORE_FILE = "high-score.txt"
85
+
86
+ args.state.high_score ||= $gtk.read_file(HIGH_SCORE_FILE).to_i
87
+ ```
88
+
89
+ ### Writing Numbers
90
+
91
+ ```ruby
92
+ # Convert to string before writing
93
+ $gtk.write_file(HIGH_SCORE_FILE, args.state.score.to_s)
94
+ ```
95
+
96
+ ## Save-Once Pattern
97
+
98
+ **Critical:** Without flags, file writes 60 times/second!
99
+
100
+ ```ruby
101
+ def game_over_tick(args)
102
+ # Load high score (once per game over)
103
+ args.state.high_score ||= $gtk.read_file("high-score.txt").to_i
104
+
105
+ # Save new high score (once)
106
+ if !args.state.saved_high_score && args.state.score > args.state.high_score
107
+ $gtk.write_file("high-score.txt", args.state.score.to_s)
108
+ args.state.saved_high_score = true # Prevent repeated saves
109
+ end
110
+ end
111
+ ```
112
+
113
+ ## High Score Pattern (Complete)
114
+
115
+ ```ruby
116
+ HIGH_SCORE_FILE = "high-score.txt"
117
+
118
+ def game_over_tick(args)
119
+ # Load lazily (once)
120
+ args.state.high_score ||= $gtk.read_file(HIGH_SCORE_FILE).to_i
121
+
122
+ # Save if beaten (once)
123
+ if !args.state.saved && args.state.score > args.state.high_score
124
+ $gtk.write_file(HIGH_SCORE_FILE, args.state.score.to_s)
125
+ args.state.saved = true
126
+ end
127
+
128
+ # Display
129
+ if args.state.score > args.state.high_score
130
+ args.outputs.labels << { x: 260, y: 630, text: "New high-score!" }
131
+ else
132
+ args.outputs.labels << { x: 260, y: 630, text: "Best: #{args.state.high_score}" }
133
+ end
134
+
135
+ # Restart prompt
136
+ if fire_input?(args)
137
+ $gtk.reset
138
+ end
139
+ end
140
+ ```
141
+
142
+ ## Complex State Serialization
143
+
144
+ For saving full game state (player, enemies, inventory, etc.):
145
+
146
+ ### Serialize State
147
+
148
+ ```ruby
149
+ # Save to file
150
+ $gtk.serialize_state("game_state.txt", args.state)
151
+
152
+ # Or get string without saving
153
+ serialized = $gtk.serialize_state(args.state)
154
+ ```
155
+
156
+ **Warning:** Serialization over 20KB triggers performance warning.
157
+
158
+ ### Deserialize State
159
+
160
+ ```ruby
161
+ # Load from file
162
+ loaded_state = $gtk.deserialize_state("game_state.txt")
163
+
164
+ if loaded_state
165
+ args.state = loaded_state
166
+ else
167
+ puts "No save file found"
168
+ end
169
+ ```
170
+
171
+ ### Full Save/Load Implementation
172
+
173
+ ```ruby
174
+ def save_game(args)
175
+ $gtk.serialize_state("save.txt", args.state)
176
+ $gtk.notify!("Game saved!")
177
+ end
178
+
179
+ def load_game(args)
180
+ loaded = $gtk.deserialize_state("save.txt")
181
+ if loaded
182
+ args.state = loaded
183
+ $gtk.notify!("Game loaded!")
184
+ else
185
+ $gtk.notify!("No save found")
186
+ end
187
+ end
188
+
189
+ def tick(args)
190
+ if args.inputs.keyboard.key_down.f5
191
+ save_game(args)
192
+ elsif args.inputs.keyboard.key_down.f9
193
+ load_game(args)
194
+ end
195
+ end
196
+ ```
197
+
198
+ ### Timestamped Backups
199
+
200
+ ```ruby
201
+ def save_with_backup(args)
202
+ timestamp = Time.now.to_i
203
+ $gtk.serialize_state("save_#{timestamp}.txt", args.state)
204
+ $gtk.serialize_state("save.txt", args.state) # Main save
205
+ end
206
+ ```
207
+
208
+ ## Simple Data Formats
209
+
210
+ ### Single Value (Text)
211
+
212
+ ```ruby
213
+ # Save
214
+ $gtk.write_file("level.txt", args.state.current_level.to_s)
215
+
216
+ # Load
217
+ args.state.current_level = $gtk.read_file("level.txt").to_i
218
+ ```
219
+
220
+ ### Multiple Values (Delimited)
221
+
222
+ ```ruby
223
+ # Save
224
+ data = "#{args.state.score},#{args.state.level},#{args.state.lives}"
225
+ $gtk.write_file("progress.txt", data)
226
+
227
+ # Load
228
+ contents = $gtk.read_file("progress.txt")
229
+ if contents
230
+ parts = contents.split(",")
231
+ args.state.score = parts[0].to_i
232
+ args.state.level = parts[1].to_i
233
+ args.state.lives = parts[2].to_i
234
+ end
235
+ ```
236
+
237
+ ### JSON-Like (Using serialize_state)
238
+
239
+ For structured data, prefer `serialize_state`/`deserialize_state` - handles complex nested objects automatically.
240
+
241
+ ## File Path Conventions
242
+
243
+ ```ruby
244
+ # Simple filenames in root
245
+ "high-score.txt"
246
+ "save.txt"
247
+
248
+ # Subdirectories (created automatically)
249
+ "saves/slot1.txt"
250
+ "data/progress.txt"
251
+
252
+ # Descriptive extensions
253
+ "player-data.txt" # Simple text
254
+ "game-state.dat" # Serialized state
255
+ ```
256
+
257
+ ## Development vs Production
258
+
259
+ ### Development Mode
260
+
261
+ ```ruby
262
+ # Writes directly to mygame/ directory
263
+ $gtk.write_file("debug.txt", "test")
264
+
265
+ # Useful for level editors, debug output
266
+ ```
267
+
268
+ ### Get Data Directory
269
+
270
+ ```ruby
271
+ path = $gtk.get_game_dir
272
+ # Development: mygame/
273
+ # Production: OS-specific data path
274
+ ```
275
+
276
+ ### Development-Only Functions
277
+
278
+ ```ruby
279
+ # Write outside sandbox (dev only!)
280
+ $gtk.write_file_root("../external.txt", "data")
281
+ $gtk.append_file_root("../log.txt", "entry\n")
282
+ ```
283
+
284
+ ## Common Antipatterns
285
+
286
+ ### Missing Save-Once Flag
287
+
288
+ ```ruby
289
+ # WRONG - writes 60 times/second!
290
+ if args.state.score > args.state.high_score
291
+ $gtk.write_file("high-score.txt", args.state.score.to_s)
292
+ end
293
+
294
+ # CORRECT
295
+ if !args.state.saved && args.state.score > args.state.high_score
296
+ $gtk.write_file("high-score.txt", args.state.score.to_s)
297
+ args.state.saved = true
298
+ end
299
+ ```
300
+
301
+ ### Unchecked File Read
302
+
303
+ ```ruby
304
+ # WRONG - crashes on nil (actually OK, nil.to_i = 0)
305
+ score = $gtk.read_file("score.txt").to_i
306
+
307
+ # WRONG - crashes if file missing
308
+ name = $gtk.read_file("name.txt").strip # NoMethodError!
309
+
310
+ # CORRECT
311
+ contents = $gtk.read_file("name.txt")
312
+ name = contents ? contents.strip : "Player"
313
+ ```
314
+
315
+ ### Deleting Without Check
316
+
317
+ ```ruby
318
+ # WRONG - raises exception if missing
319
+ $gtk.delete_file("temp.txt")
320
+
321
+ # CORRECT
322
+ if $gtk.stat_file("temp.txt")
323
+ $gtk.delete_file("temp.txt")
324
+ end
325
+ ```
326
+
327
+ ### Saving Every Frame
328
+
329
+ ```ruby
330
+ # WRONG - massive I/O overhead
331
+ def tick(args)
332
+ $gtk.write_file("autosave.txt", args.state.to_s)
333
+ end
334
+
335
+ # CORRECT - periodic saves
336
+ def tick(args)
337
+ if Kernel.tick_count.zmod?(60 * 30) # Every 30 seconds
338
+ save_game(args)
339
+ end
340
+ end
341
+ ```
342
+
343
+ ## Quick Reference
344
+
345
+ | Operation | Method | Returns |
346
+ |-----------|--------|---------|
347
+ | Read | `$gtk.read_file(path)` | String or nil |
348
+ | Write | `$gtk.write_file(path, data)` | - |
349
+ | Append | `$gtk.append_file(path, data)` | - |
350
+ | Delete | `$gtk.delete_file(path)` | - |
351
+ | Info | `$gtk.stat_file(path)` | Hash or nil |
352
+ | List | `$gtk.list_files(dir)` | Array |
353
+ | Serialize | `$gtk.serialize_state(path, state)` | String |
354
+ | Deserialize | `$gtk.deserialize_state(path)` | Object or nil |
355
+
356
+ ## Decision Tree
357
+
358
+ ```
359
+ What to save?
360
+ ├── Single value (high score, level) → examples/game-logic/save_load.rb (simple pattern)
361
+ │ └── Use write_file + .to_s / read_file + .to_i
362
+ ├── Multiple values → Delimited string or serialize_state
363
+ └── Full game state → examples/game-logic/save_load.rb (serialize pattern)
364
+ └── Use serialize_state / deserialize_state
365
+
366
+ When to save?
367
+ ├── On event (game over, checkpoint) → Save-once flag pattern
368
+ ├── Periodically → zmod?(60 * 30) for every 30 seconds
369
+ └── On user action (F5 key) → examples/game-logic/save_load.rb
370
+
371
+ How to handle missing files?
372
+ ├── Numeric data → .to_i handles nil (returns 0)
373
+ ├── String data → Check for nil: contents ? contents : "default"
374
+ └── Complex data → deserialize_state returns nil if missing
375
+
376
+ File not saving correctly?
377
+ ├── Writing 60x/second? → Add save-once flag
378
+ ├── File empty? → Check .to_s conversion
379
+ └── Wrong location? → Development vs production paths differ
380
+ ```
381
+
382
+ ## Examples
383
+
384
+ | File | Demonstrates |
385
+ |------|--------------|
386
+ | `examples/game-logic/save_load.rb` | High score, serialize/deserialize, save-once flag |