bmad-method 6.0.0-alpha.17 → 6.0.0-alpha.19

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 (185) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/package.json +1 -1
  3. package/src/modules/bmgd/_module-installer/installer.js +160 -0
  4. package/src/modules/bmgd/_module-installer/platform-specifics/claude-code.js +23 -0
  5. package/src/modules/bmgd/_module-installer/platform-specifics/windsurf.js +18 -0
  6. package/src/modules/bmgd/agents/game-architect.agent.yaml +23 -8
  7. package/src/modules/bmgd/agents/game-designer.agent.yaml +38 -18
  8. package/src/modules/bmgd/agents/game-dev.agent.yaml +30 -14
  9. package/src/modules/bmgd/agents/game-qa.agent.yaml +64 -0
  10. package/src/modules/bmgd/agents/game-scrum-master.agent.yaml +27 -39
  11. package/src/modules/bmgd/agents/game-solo-dev.agent.yaml +56 -0
  12. package/src/modules/bmgd/docs/README.md +180 -0
  13. package/src/modules/bmgd/docs/agents-guide.md +407 -0
  14. package/src/modules/bmgd/docs/game-types-guide.md +503 -0
  15. package/src/modules/bmgd/docs/glossary.md +294 -0
  16. package/src/modules/bmgd/docs/quick-flow-guide.md +288 -0
  17. package/src/modules/bmgd/docs/quick-start.md +250 -0
  18. package/src/modules/bmgd/docs/troubleshooting.md +259 -0
  19. package/src/modules/bmgd/docs/workflow-overview.jpg +0 -0
  20. package/src/modules/bmgd/docs/workflows-guide.md +463 -0
  21. package/src/modules/bmgd/gametest/knowledge/balance-testing.md +220 -0
  22. package/src/modules/bmgd/gametest/knowledge/certification-testing.md +319 -0
  23. package/src/modules/bmgd/gametest/knowledge/compatibility-testing.md +228 -0
  24. package/src/modules/bmgd/gametest/knowledge/godot-testing.md +376 -0
  25. package/src/modules/bmgd/gametest/knowledge/input-testing.md +315 -0
  26. package/src/modules/bmgd/gametest/knowledge/localization-testing.md +304 -0
  27. package/src/modules/bmgd/gametest/knowledge/multiplayer-testing.md +322 -0
  28. package/src/modules/bmgd/gametest/knowledge/performance-testing.md +204 -0
  29. package/src/modules/bmgd/gametest/knowledge/playtesting.md +384 -0
  30. package/src/modules/bmgd/gametest/knowledge/qa-automation.md +190 -0
  31. package/src/modules/bmgd/gametest/knowledge/regression-testing.md +280 -0
  32. package/src/modules/bmgd/gametest/knowledge/save-testing.md +280 -0
  33. package/src/modules/bmgd/gametest/knowledge/smoke-testing.md +404 -0
  34. package/src/modules/bmgd/gametest/knowledge/test-priorities.md +271 -0
  35. package/src/modules/bmgd/gametest/knowledge/unity-testing.md +383 -0
  36. package/src/modules/bmgd/gametest/knowledge/unreal-testing.md +388 -0
  37. package/src/modules/bmgd/gametest/qa-index.csv +17 -0
  38. package/src/modules/bmgd/module.yaml +25 -9
  39. package/src/modules/bmgd/teams/default-party.csv +2 -0
  40. package/src/modules/bmgd/teams/team-gamedev.yaml +12 -1
  41. package/src/modules/bmgd/workflows/1-preproduction/brainstorm-game/steps/step-01-init.md +164 -0
  42. package/src/modules/bmgd/workflows/1-preproduction/brainstorm-game/steps/step-02-context.md +210 -0
  43. package/src/modules/bmgd/workflows/1-preproduction/brainstorm-game/steps/step-03-ideation.md +289 -0
  44. package/src/modules/bmgd/workflows/1-preproduction/brainstorm-game/steps/step-04-complete.md +275 -0
  45. package/src/modules/bmgd/workflows/1-preproduction/brainstorm-game/workflow.md +49 -0
  46. package/src/modules/bmgd/workflows/1-preproduction/brainstorm-game/workflow.yaml +29 -8
  47. package/src/modules/bmgd/workflows/1-preproduction/game-brief/steps/step-01-init.md +223 -0
  48. package/src/modules/bmgd/workflows/1-preproduction/game-brief/steps/step-01b-continue.md +151 -0
  49. package/src/modules/bmgd/workflows/1-preproduction/game-brief/steps/step-02-vision.md +218 -0
  50. package/src/modules/bmgd/workflows/1-preproduction/game-brief/steps/step-03-market.md +218 -0
  51. package/src/modules/bmgd/workflows/1-preproduction/game-brief/steps/step-04-fundamentals.md +231 -0
  52. package/src/modules/bmgd/workflows/1-preproduction/game-brief/steps/step-05-scope.md +242 -0
  53. package/src/modules/bmgd/workflows/1-preproduction/game-brief/steps/step-06-references.md +224 -0
  54. package/src/modules/bmgd/workflows/1-preproduction/game-brief/steps/step-07-content.md +282 -0
  55. package/src/modules/bmgd/workflows/1-preproduction/game-brief/steps/step-08-complete.md +296 -0
  56. package/src/modules/bmgd/workflows/1-preproduction/game-brief/workflow.md +62 -0
  57. package/src/modules/bmgd/workflows/1-preproduction/game-brief/workflow.yaml +40 -9
  58. package/src/modules/bmgd/workflows/2-design/gdd/steps/step-01-init.md +248 -0
  59. package/src/modules/bmgd/workflows/2-design/gdd/steps/step-01b-continue.md +173 -0
  60. package/src/modules/bmgd/workflows/2-design/gdd/steps/step-02-context.md +332 -0
  61. package/src/modules/bmgd/workflows/2-design/gdd/steps/step-03-platforms.md +245 -0
  62. package/src/modules/bmgd/workflows/2-design/gdd/steps/step-04-vision.md +229 -0
  63. package/src/modules/bmgd/workflows/2-design/gdd/steps/step-05-core-gameplay.md +258 -0
  64. package/src/modules/bmgd/workflows/2-design/gdd/steps/step-06-mechanics.md +249 -0
  65. package/src/modules/bmgd/workflows/2-design/gdd/steps/step-07-game-type.md +266 -0
  66. package/src/modules/bmgd/workflows/2-design/gdd/steps/step-08-progression.md +272 -0
  67. package/src/modules/bmgd/workflows/2-design/gdd/steps/step-09-levels.md +264 -0
  68. package/src/modules/bmgd/workflows/2-design/gdd/steps/step-10-art-audio.md +255 -0
  69. package/src/modules/bmgd/workflows/2-design/gdd/steps/step-11-technical.md +275 -0
  70. package/src/modules/bmgd/workflows/2-design/gdd/steps/step-12-epics.md +284 -0
  71. package/src/modules/bmgd/workflows/2-design/gdd/steps/step-13-metrics.md +250 -0
  72. package/src/modules/bmgd/workflows/2-design/gdd/steps/step-14-complete.md +335 -0
  73. package/src/modules/bmgd/workflows/2-design/gdd/workflow.md +61 -0
  74. package/src/modules/bmgd/workflows/2-design/gdd/workflow.yaml +27 -7
  75. package/src/modules/bmgd/workflows/2-design/narrative/steps/step-01-init.md +228 -0
  76. package/src/modules/bmgd/workflows/2-design/narrative/steps/step-01b-continue.md +163 -0
  77. package/src/modules/bmgd/workflows/2-design/narrative/steps/step-02-foundation.md +262 -0
  78. package/src/modules/bmgd/workflows/2-design/narrative/steps/step-03-story.md +238 -0
  79. package/src/modules/bmgd/workflows/2-design/narrative/steps/step-04-characters.md +297 -0
  80. package/src/modules/bmgd/workflows/2-design/narrative/steps/step-05-world.md +262 -0
  81. package/src/modules/bmgd/workflows/2-design/narrative/steps/step-06-dialogue.md +250 -0
  82. package/src/modules/bmgd/workflows/2-design/narrative/steps/step-07-environmental.md +244 -0
  83. package/src/modules/bmgd/workflows/2-design/narrative/steps/step-08-delivery.md +264 -0
  84. package/src/modules/bmgd/workflows/2-design/narrative/steps/step-09-integration.md +254 -0
  85. package/src/modules/bmgd/workflows/2-design/narrative/steps/step-10-production.md +262 -0
  86. package/src/modules/bmgd/workflows/2-design/narrative/steps/step-11-complete.md +331 -0
  87. package/src/modules/bmgd/workflows/2-design/narrative/workflow.md +57 -0
  88. package/src/modules/bmgd/workflows/2-design/narrative/workflow.yaml +53 -8
  89. package/src/modules/bmgd/workflows/3-technical/game-architecture/steps/step-01-init.md +223 -0
  90. package/src/modules/bmgd/workflows/3-technical/game-architecture/steps/step-01b-continue.md +153 -0
  91. package/src/modules/bmgd/workflows/3-technical/game-architecture/steps/step-02-context.md +262 -0
  92. package/src/modules/bmgd/workflows/3-technical/game-architecture/steps/step-03-starter.md +290 -0
  93. package/src/modules/bmgd/workflows/3-technical/game-architecture/steps/step-04-decisions.md +300 -0
  94. package/src/modules/bmgd/workflows/3-technical/game-architecture/steps/step-05-crosscutting.md +319 -0
  95. package/src/modules/bmgd/workflows/3-technical/game-architecture/steps/step-06-structure.md +304 -0
  96. package/src/modules/bmgd/workflows/3-technical/game-architecture/steps/step-07-patterns.md +349 -0
  97. package/src/modules/bmgd/workflows/3-technical/game-architecture/steps/step-08-validation.md +293 -0
  98. package/src/modules/bmgd/workflows/3-technical/game-architecture/steps/step-09-complete.md +302 -0
  99. package/src/modules/bmgd/workflows/3-technical/game-architecture/workflow.md +55 -0
  100. package/src/modules/bmgd/workflows/3-technical/game-architecture/workflow.yaml +50 -21
  101. package/src/modules/bmgd/workflows/4-production/code-review/checklist.md +23 -0
  102. package/src/modules/bmgd/workflows/4-production/code-review/instructions.xml +225 -0
  103. package/src/modules/bmgd/workflows/4-production/code-review/workflow.yaml +18 -15
  104. package/src/modules/bmgd/workflows/4-production/correct-course/checklist.md +1 -1
  105. package/src/modules/bmgd/workflows/4-production/correct-course/instructions.md +1 -1
  106. package/src/modules/bmgd/workflows/4-production/correct-course/workflow.yaml +11 -6
  107. package/src/modules/bmgd/workflows/4-production/create-story/checklist.md +332 -214
  108. package/src/modules/bmgd/workflows/4-production/create-story/instructions.xml +298 -0
  109. package/src/modules/bmgd/workflows/4-production/create-story/template.md +3 -5
  110. package/src/modules/bmgd/workflows/4-production/create-story/workflow.yaml +12 -7
  111. package/src/modules/bmgd/workflows/4-production/dev-story/checklist.md +65 -23
  112. package/src/modules/bmgd/workflows/4-production/dev-story/instructions.xml +409 -0
  113. package/src/modules/bmgd/workflows/4-production/dev-story/workflow.yaml +13 -3
  114. package/src/modules/bmgd/workflows/4-production/retrospective/instructions.md +4 -4
  115. package/src/modules/bmgd/workflows/4-production/retrospective/workflow.yaml +12 -7
  116. package/src/modules/bmgd/workflows/4-production/sprint-planning/instructions.md +32 -41
  117. package/src/modules/bmgd/workflows/4-production/sprint-planning/sprint-status-template.yaml +13 -13
  118. package/src/modules/bmgd/workflows/4-production/sprint-planning/workflow.yaml +6 -1
  119. package/src/modules/bmgd/workflows/4-production/sprint-status/instructions.md +229 -0
  120. package/src/modules/bmgd/workflows/4-production/sprint-status/workflow.yaml +35 -0
  121. package/src/modules/bmgd/workflows/bmgd-quick-flow/create-tech-spec/instructions.md +140 -0
  122. package/src/modules/bmgd/workflows/bmgd-quick-flow/create-tech-spec/workflow.yaml +27 -0
  123. package/src/modules/bmgd/workflows/bmgd-quick-flow/quick-dev/checklist.md +37 -0
  124. package/src/modules/bmgd/workflows/bmgd-quick-flow/quick-dev/instructions.md +220 -0
  125. package/src/modules/bmgd/workflows/bmgd-quick-flow/quick-dev/workflow.yaml +45 -0
  126. package/src/modules/bmgd/workflows/bmgd-quick-flow/quick-prototype/checklist.md +26 -0
  127. package/src/modules/bmgd/workflows/bmgd-quick-flow/quick-prototype/instructions.md +156 -0
  128. package/src/modules/bmgd/workflows/bmgd-quick-flow/quick-prototype/workflow.yaml +36 -0
  129. package/src/modules/bmgd/workflows/gametest/automate/checklist.md +93 -0
  130. package/src/modules/bmgd/workflows/gametest/automate/instructions.md +317 -0
  131. package/src/modules/bmgd/workflows/gametest/automate/workflow.yaml +50 -0
  132. package/src/modules/bmgd/workflows/gametest/performance/checklist.md +96 -0
  133. package/src/modules/bmgd/workflows/gametest/performance/instructions.md +323 -0
  134. package/src/modules/bmgd/workflows/gametest/performance/performance-template.md +256 -0
  135. package/src/modules/bmgd/workflows/gametest/performance/workflow.yaml +48 -0
  136. package/src/modules/bmgd/workflows/gametest/playtest-plan/checklist.md +93 -0
  137. package/src/modules/bmgd/workflows/gametest/playtest-plan/instructions.md +297 -0
  138. package/src/modules/bmgd/workflows/gametest/playtest-plan/playtest-template.md +208 -0
  139. package/src/modules/bmgd/workflows/gametest/playtest-plan/workflow.yaml +59 -0
  140. package/src/modules/bmgd/workflows/gametest/test-design/checklist.md +98 -0
  141. package/src/modules/bmgd/workflows/gametest/test-design/instructions.md +280 -0
  142. package/src/modules/bmgd/workflows/gametest/test-design/test-design-template.md +205 -0
  143. package/src/modules/bmgd/workflows/gametest/test-design/workflow.yaml +47 -0
  144. package/src/modules/bmgd/workflows/gametest/test-framework/checklist.md +103 -0
  145. package/src/modules/bmgd/workflows/gametest/test-framework/instructions.md +348 -0
  146. package/src/modules/bmgd/workflows/gametest/test-framework/workflow.yaml +48 -0
  147. package/src/modules/bmgd/workflows/gametest/test-review/checklist.md +87 -0
  148. package/src/modules/bmgd/workflows/gametest/test-review/instructions.md +272 -0
  149. package/src/modules/bmgd/workflows/gametest/test-review/test-review-template.md +203 -0
  150. package/src/modules/bmgd/workflows/gametest/test-review/workflow.yaml +48 -0
  151. package/src/modules/bmgd/workflows/workflow-status/init/instructions.md +299 -0
  152. package/src/modules/bmgd/workflows/workflow-status/init/workflow.yaml +29 -0
  153. package/src/modules/bmgd/workflows/workflow-status/instructions.md +395 -0
  154. package/src/modules/bmgd/workflows/workflow-status/paths/gamedev-brownfield.yaml +65 -0
  155. package/src/modules/bmgd/workflows/workflow-status/paths/gamedev-greenfield.yaml +71 -0
  156. package/src/modules/bmgd/workflows/workflow-status/paths/quickflow-brownfield.yaml +29 -0
  157. package/src/modules/bmgd/workflows/workflow-status/paths/quickflow-greenfield.yaml +39 -0
  158. package/src/modules/bmgd/workflows/workflow-status/project-levels.yaml +63 -0
  159. package/src/modules/bmgd/workflows/workflow-status/workflow-status-template.yaml +24 -0
  160. package/src/modules/bmgd/workflows/workflow-status/workflow.yaml +30 -0
  161. package/tools/cli/commands/install.js +9 -0
  162. package/tools/cli/installers/lib/core/installer.js +140 -592
  163. package/tools/cli/installers/lib/modules/manager.js +15 -3
  164. package/tools/cli/lib/agent/compiler.js +99 -0
  165. package/tools/cli/lib/ui.js +78 -27
  166. package/src/modules/bmgd/workflows/2-design/gdd/instructions-gdd.md +0 -502
  167. package/src/modules/bmgd/workflows/4-production/code-review/instructions.md +0 -398
  168. package/src/modules/bmgd/workflows/4-production/create-story/instructions.md +0 -256
  169. package/src/modules/bmgd/workflows/4-production/dev-story/instructions.md +0 -267
  170. package/src/modules/bmgd/workflows/4-production/epic-tech-context/checklist.md +0 -17
  171. package/src/modules/bmgd/workflows/4-production/epic-tech-context/instructions.md +0 -164
  172. package/src/modules/bmgd/workflows/4-production/epic-tech-context/template.md +0 -76
  173. package/src/modules/bmgd/workflows/4-production/epic-tech-context/workflow.yaml +0 -58
  174. package/src/modules/bmgd/workflows/4-production/story-context/checklist.md +0 -16
  175. package/src/modules/bmgd/workflows/4-production/story-context/context-template.xml +0 -34
  176. package/src/modules/bmgd/workflows/4-production/story-context/instructions.md +0 -209
  177. package/src/modules/bmgd/workflows/4-production/story-context/workflow.yaml +0 -63
  178. package/src/modules/bmgd/workflows/4-production/story-done/instructions.md +0 -111
  179. package/src/modules/bmgd/workflows/4-production/story-done/workflow.yaml +0 -28
  180. package/src/modules/bmgd/workflows/4-production/story-ready/instructions.md +0 -117
  181. package/src/modules/bmgd/workflows/4-production/story-ready/workflow.yaml +0 -25
  182. /package/src/modules/bmgd/workflows/1-preproduction/game-brief/{template.md → templates/game-brief-template.md} +0 -0
  183. /package/src/modules/bmgd/workflows/2-design/gdd/{gdd-template.md → templates/gdd-template.md} +0 -0
  184. /package/src/modules/bmgd/workflows/2-design/narrative/{narrative-template.md → templates/narrative-template.md} +0 -0
  185. /package/src/modules/bmgd/workflows/3-technical/game-architecture/{architecture-template.md → templates/architecture-template.md} +0 -0
@@ -0,0 +1,376 @@
1
+ # Godot GUT Testing Guide
2
+
3
+ ## Overview
4
+
5
+ GUT (Godot Unit Test) is the standard unit testing framework for Godot. It provides a full-featured testing framework with assertions, mocking, and CI integration.
6
+
7
+ ## Installation
8
+
9
+ ### Via Asset Library
10
+
11
+ 1. Open AssetLib in Godot
12
+ 2. Search for "GUT"
13
+ 3. Download and install
14
+ 4. Enable the plugin in Project Settings
15
+
16
+ ### Via Git Submodule
17
+
18
+ ```bash
19
+ git submodule add https://github.com/bitwes/Gut.git addons/gut
20
+ ```
21
+
22
+ ## Project Structure
23
+
24
+ ```
25
+ project/
26
+ ├── addons/
27
+ │ └── gut/
28
+ ├── src/
29
+ │ ├── player/
30
+ │ │ └── player.gd
31
+ │ └── combat/
32
+ │ └── damage_calculator.gd
33
+ └── tests/
34
+ ├── unit/
35
+ │ └── test_damage_calculator.gd
36
+ └── integration/
37
+ └── test_player_combat.gd
38
+ ```
39
+
40
+ ## Basic Test Structure
41
+
42
+ ### Simple Test Class
43
+
44
+ ```gdscript
45
+ # tests/unit/test_damage_calculator.gd
46
+ extends GutTest
47
+
48
+ var calculator: DamageCalculator
49
+
50
+ func before_each():
51
+ calculator = DamageCalculator.new()
52
+
53
+ func after_each():
54
+ calculator.free()
55
+
56
+ func test_calculate_base_damage():
57
+ var result = calculator.calculate(100.0, 1.0)
58
+ assert_eq(result, 100.0, "Base damage should equal input")
59
+
60
+ func test_calculate_critical_hit():
61
+ var result = calculator.calculate(100.0, 2.0)
62
+ assert_eq(result, 200.0, "Critical hit should double damage")
63
+
64
+ func test_calculate_with_zero_multiplier():
65
+ var result = calculator.calculate(100.0, 0.0)
66
+ assert_eq(result, 0.0, "Zero multiplier should result in zero damage")
67
+ ```
68
+
69
+ ### Parameterized Tests
70
+
71
+ ```gdscript
72
+ func test_damage_scenarios():
73
+ var scenarios = [
74
+ {"base": 100.0, "mult": 1.0, "expected": 100.0},
75
+ {"base": 100.0, "mult": 2.0, "expected": 200.0},
76
+ {"base": 50.0, "mult": 1.5, "expected": 75.0},
77
+ {"base": 0.0, "mult": 2.0, "expected": 0.0},
78
+ ]
79
+
80
+ for scenario in scenarios:
81
+ var result = calculator.calculate(scenario.base, scenario.mult)
82
+ assert_eq(
83
+ result,
84
+ scenario.expected,
85
+ "Base %s * %s should equal %s" % [
86
+ scenario.base, scenario.mult, scenario.expected
87
+ ]
88
+ )
89
+ ```
90
+
91
+ ## Testing Nodes
92
+
93
+ ### Scene Testing
94
+
95
+ ```gdscript
96
+ # tests/integration/test_player.gd
97
+ extends GutTest
98
+
99
+ var player: Player
100
+ var player_scene = preload("res://src/player/player.tscn")
101
+
102
+ func before_each():
103
+ player = player_scene.instantiate()
104
+ add_child(player)
105
+
106
+ func after_each():
107
+ player.queue_free()
108
+
109
+ func test_player_initial_health():
110
+ assert_eq(player.health, 100, "Player should start with 100 health")
111
+
112
+ func test_player_takes_damage():
113
+ player.take_damage(30)
114
+ assert_eq(player.health, 70, "Health should be reduced by damage")
115
+
116
+ func test_player_dies_at_zero_health():
117
+ player.take_damage(100)
118
+ assert_true(player.is_dead, "Player should be dead at 0 health")
119
+ ```
120
+
121
+ ### Testing with Signals
122
+
123
+ ```gdscript
124
+ func test_damage_emits_signal():
125
+ watch_signals(player)
126
+
127
+ player.take_damage(10)
128
+
129
+ assert_signal_emitted(player, "health_changed")
130
+ assert_signal_emit_count(player, "health_changed", 1)
131
+
132
+ func test_death_emits_signal():
133
+ watch_signals(player)
134
+
135
+ player.take_damage(100)
136
+
137
+ assert_signal_emitted(player, "died")
138
+ ```
139
+
140
+ ### Testing with Await
141
+
142
+ ```gdscript
143
+ func test_attack_cooldown():
144
+ player.attack()
145
+ assert_true(player.is_attacking)
146
+
147
+ # Wait for cooldown
148
+ await get_tree().create_timer(player.attack_cooldown).timeout
149
+
150
+ assert_false(player.is_attacking)
151
+ assert_true(player.can_attack)
152
+ ```
153
+
154
+ ## Mocking and Doubles
155
+
156
+ ### Creating Doubles
157
+
158
+ ```gdscript
159
+ func test_enemy_uses_pathfinding():
160
+ var mock_pathfinding = double(Pathfinding).new()
161
+ stub(mock_pathfinding, "find_path").to_return([Vector2(0, 0), Vector2(10, 10)])
162
+
163
+ var enemy = Enemy.new()
164
+ enemy.pathfinding = mock_pathfinding
165
+
166
+ enemy.move_to(Vector2(10, 10))
167
+
168
+ assert_called(mock_pathfinding, "find_path")
169
+ ```
170
+
171
+ ### Partial Doubles
172
+
173
+ ```gdscript
174
+ func test_player_inventory():
175
+ var player_double = partial_double(Player).new()
176
+ stub(player_double, "save_to_disk").to_do_nothing()
177
+
178
+ player_double.add_item("sword")
179
+
180
+ assert_eq(player_double.inventory.size(), 1)
181
+ assert_called(player_double, "save_to_disk")
182
+ ```
183
+
184
+ ## Physics Testing
185
+
186
+ ### Testing Collision
187
+
188
+ ```gdscript
189
+ func test_projectile_hits_enemy():
190
+ var projectile = Projectile.new()
191
+ var enemy = Enemy.new()
192
+
193
+ add_child(projectile)
194
+ add_child(enemy)
195
+
196
+ projectile.global_position = Vector2(0, 0)
197
+ enemy.global_position = Vector2(100, 0)
198
+
199
+ projectile.velocity = Vector2(200, 0)
200
+
201
+ # Simulate physics frames
202
+ for i in range(60):
203
+ await get_tree().physics_frame
204
+
205
+ assert_true(enemy.was_hit, "Enemy should be hit by projectile")
206
+
207
+ projectile.queue_free()
208
+ enemy.queue_free()
209
+ ```
210
+
211
+ ### Testing Area2D
212
+
213
+ ```gdscript
214
+ func test_pickup_collected():
215
+ var pickup = Pickup.new()
216
+ var player = player_scene.instantiate()
217
+
218
+ add_child(pickup)
219
+ add_child(player)
220
+
221
+ pickup.global_position = Vector2(50, 50)
222
+ player.global_position = Vector2(50, 50)
223
+
224
+ # Wait for physics to process overlap
225
+ await get_tree().physics_frame
226
+ await get_tree().physics_frame
227
+
228
+ assert_true(pickup.is_queued_for_deletion(), "Pickup should be collected")
229
+
230
+ player.queue_free()
231
+ ```
232
+
233
+ ## Input Testing
234
+
235
+ ### Simulating Input
236
+
237
+ ```gdscript
238
+ func test_jump_on_input():
239
+ var input_event = InputEventKey.new()
240
+ input_event.keycode = KEY_SPACE
241
+ input_event.pressed = true
242
+
243
+ Input.parse_input_event(input_event)
244
+ await get_tree().process_frame
245
+
246
+ player._unhandled_input(input_event)
247
+
248
+ assert_true(player.is_jumping, "Player should jump on space press")
249
+ ```
250
+
251
+ ### Testing Input Actions
252
+
253
+ ```gdscript
254
+ func test_attack_action():
255
+ # Simulate action press
256
+ Input.action_press("attack")
257
+ await get_tree().process_frame
258
+
259
+ player._process(0.016)
260
+
261
+ assert_true(player.is_attacking)
262
+
263
+ Input.action_release("attack")
264
+ ```
265
+
266
+ ## Resource Testing
267
+
268
+ ### Testing Custom Resources
269
+
270
+ ```gdscript
271
+ func test_weapon_stats_resource():
272
+ var weapon = WeaponStats.new()
273
+ weapon.base_damage = 10.0
274
+ weapon.attack_speed = 2.0
275
+
276
+ assert_eq(weapon.dps, 20.0, "DPS should be damage * speed")
277
+
278
+ func test_save_load_resource():
279
+ var original = PlayerData.new()
280
+ original.level = 5
281
+ original.gold = 1000
282
+
283
+ ResourceSaver.save(original, "user://test_save.tres")
284
+ var loaded = ResourceLoader.load("user://test_save.tres")
285
+
286
+ assert_eq(loaded.level, 5)
287
+ assert_eq(loaded.gold, 1000)
288
+
289
+ DirAccess.remove_absolute("user://test_save.tres")
290
+ ```
291
+
292
+ ## GUT Configuration
293
+
294
+ ### gut_config.json
295
+
296
+ ```json
297
+ {
298
+ "dirs": ["res://tests/"],
299
+ "include_subdirs": true,
300
+ "prefix": "test_",
301
+ "suffix": ".gd",
302
+ "should_exit": true,
303
+ "should_exit_on_success": true,
304
+ "log_level": 1,
305
+ "junit_xml_file": "results.xml",
306
+ "font_size": 16
307
+ }
308
+ ```
309
+
310
+ ## CI Integration
311
+
312
+ ### Command Line Execution
313
+
314
+ ```bash
315
+ # Run all tests
316
+ godot --headless -s addons/gut/gut_cmdln.gd
317
+
318
+ # Run specific tests
319
+ godot --headless -s addons/gut/gut_cmdln.gd \
320
+ -gdir=res://tests/unit \
321
+ -gprefix=test_
322
+
323
+ # With JUnit output
324
+ godot --headless -s addons/gut/gut_cmdln.gd \
325
+ -gjunit_xml_file=results.xml
326
+ ```
327
+
328
+ ### GitHub Actions
329
+
330
+ ```yaml
331
+ test:
332
+ runs-on: ubuntu-latest
333
+ container:
334
+ image: barichello/godot-ci:4.2
335
+ steps:
336
+ - uses: actions/checkout@v4
337
+
338
+ - name: Run Tests
339
+ run: |
340
+ godot --headless -s addons/gut/gut_cmdln.gd \
341
+ -gjunit_xml_file=results.xml
342
+
343
+ - name: Publish Results
344
+ uses: mikepenz/action-junit-report@v4
345
+ with:
346
+ report_paths: results.xml
347
+ ```
348
+
349
+ ## Best Practices
350
+
351
+ ### DO
352
+
353
+ - Use `before_each`/`after_each` for setup/teardown
354
+ - Free nodes after tests to prevent leaks
355
+ - Use meaningful assertion messages
356
+ - Group related tests in the same file
357
+ - Use `watch_signals` for signal testing
358
+ - Await physics frames when testing physics
359
+
360
+ ### DON'T
361
+
362
+ - Don't test Godot's built-in functionality
363
+ - Don't rely on execution order between test files
364
+ - Don't leave orphan nodes
365
+ - Don't use `yield` (use `await` in Godot 4)
366
+ - Don't test private methods directly
367
+
368
+ ## Troubleshooting
369
+
370
+ | Issue | Cause | Fix |
371
+ | -------------------- | ------------------ | ------------------------------------ |
372
+ | Tests not found | Wrong prefix/path | Check gut_config.json |
373
+ | Orphan nodes warning | Missing cleanup | Add `queue_free()` in `after_each` |
374
+ | Signal not detected | Signal not watched | Call `watch_signals()` before action |
375
+ | Physics not working | Missing frames | Await `physics_frame` |
376
+ | Flaky tests | Timing issues | Use proper await/signals |
@@ -0,0 +1,315 @@
1
+ # Input Testing Guide
2
+
3
+ ## Overview
4
+
5
+ Input testing validates that all supported input devices work correctly across platforms. Poor input handling frustrates players instantly—responsive, accurate input is foundational to game feel.
6
+
7
+ ## Input Categories
8
+
9
+ ### Device Types
10
+
11
+ | Device | Platforms | Key Concerns |
12
+ | ----------------- | -------------- | ----------------------------------- |
13
+ | Keyboard + Mouse | PC | Key conflicts, DPI sensitivity |
14
+ | Gamepad (Xbox/PS) | PC, Console | Deadzone, vibration, button prompts |
15
+ | Touch | Mobile, Switch | Multi-touch, gesture recognition |
16
+ | Motion Controls | Switch, VR | Calibration, drift, fatigue |
17
+ | Specialty | Various | Flight sticks, wheels, fight sticks |
18
+
19
+ ### Input Characteristics
20
+
21
+ | Characteristic | Description | Test Focus |
22
+ | -------------- | ---------------------------- | -------------------------------- |
23
+ | Responsiveness | Input-to-action delay | Should feel instant (< 100ms) |
24
+ | Accuracy | Input maps to correct action | No ghost inputs or missed inputs |
25
+ | Consistency | Same input = same result | Deterministic behavior |
26
+ | Accessibility | Alternative input support | Remapping, assist options |
27
+
28
+ ## Test Scenarios
29
+
30
+ ### Keyboard and Mouse
31
+
32
+ ```
33
+ SCENARIO: All Keybinds Functional
34
+ GIVEN default keyboard bindings
35
+ WHEN each bound key is pressed
36
+ THEN corresponding action triggers
37
+ AND no key conflicts exist
38
+
39
+ SCENARIO: Key Remapping
40
+ GIVEN player remaps "Jump" from Space to F
41
+ WHEN F is pressed
42
+ THEN jump action triggers
43
+ AND Space no longer triggers jump
44
+ AND remapping persists after restart
45
+
46
+ SCENARIO: Mouse Sensitivity
47
+ GIVEN sensitivity set to 5 (mid-range)
48
+ WHEN mouse moves 10cm
49
+ THEN camera rotation matches expected degrees
50
+ AND movement feels consistent at different frame rates
51
+
52
+ SCENARIO: Mouse Button Support
53
+ GIVEN mouse with 5+ buttons
54
+ WHEN side buttons are pressed
55
+ THEN they can be bound to actions
56
+ AND they function correctly in gameplay
57
+ ```
58
+
59
+ ### Gamepad
60
+
61
+ ```
62
+ SCENARIO: Analog Stick Deadzone
63
+ GIVEN controller with slight stick drift
64
+ WHEN stick is in neutral position
65
+ THEN no movement occurs (deadzone filters drift)
66
+ AND intentional small movements still register
67
+
68
+ SCENARIO: Trigger Pressure
69
+ GIVEN analog triggers
70
+ WHEN trigger is partially pressed
71
+ THEN partial values are read (e.g., 0.5 for half-press)
72
+ AND full press reaches 1.0
73
+
74
+ SCENARIO: Controller Hot-Swap
75
+ GIVEN game running with keyboard
76
+ WHEN gamepad is connected
77
+ THEN input prompts switch to gamepad icons
78
+ AND gamepad input works immediately
79
+ AND keyboard still works if used
80
+
81
+ SCENARIO: Vibration Feedback
82
+ GIVEN rumble-enabled controller
83
+ WHEN damage is taken
84
+ THEN controller vibrates appropriately
85
+ AND vibration intensity matches damage severity
86
+ ```
87
+
88
+ ### Touch Input
89
+
90
+ ```
91
+ SCENARIO: Multi-Touch Accuracy
92
+ GIVEN virtual joystick and buttons
93
+ WHEN left thumb on joystick AND right thumb on button
94
+ THEN both inputs register simultaneously
95
+ AND no interference between touch points
96
+
97
+ SCENARIO: Gesture Recognition
98
+ GIVEN swipe-to-attack mechanic
99
+ WHEN player swipes right
100
+ THEN attack direction matches swipe
101
+ AND swipe is distinguished from tap
102
+
103
+ SCENARIO: Touch Target Size
104
+ GIVEN minimum touch target of 44x44 points
105
+ WHEN buttons are placed
106
+ THEN all interactive elements meet minimum size
107
+ AND elements have adequate spacing
108
+ ```
109
+
110
+ ## Platform-Specific Testing
111
+
112
+ ### PC
113
+
114
+ - Multiple keyboard layouts (QWERTY, AZERTY, QWERTZ)
115
+ - Different mouse DPI settings (400-3200+)
116
+ - Multiple monitors (cursor confinement)
117
+ - Background application conflicts
118
+ - Steam Input API integration
119
+
120
+ ### Console
121
+
122
+ | Platform | Specific Tests |
123
+ | ----------- | ------------------------------------------ |
124
+ | PlayStation | Touchpad, adaptive triggers, haptics |
125
+ | Xbox | Impulse triggers, Elite controller paddles |
126
+ | Switch | Joy-Con detachment, gyro, HD rumble |
127
+
128
+ ### Mobile
129
+
130
+ - Different screen sizes and aspect ratios
131
+ - Notch/cutout avoidance
132
+ - External controller support
133
+ - Apple MFi / Android gamepad compatibility
134
+
135
+ ## Automated Test Examples
136
+
137
+ ### Unity
138
+
139
+ ```csharp
140
+ using UnityEngine.InputSystem;
141
+
142
+ [UnityTest]
143
+ public IEnumerator Movement_WithGamepad_RespondsToStick()
144
+ {
145
+ var gamepad = InputSystem.AddDevice<Gamepad>();
146
+
147
+ yield return null;
148
+
149
+ // Simulate stick input
150
+ Set(gamepad.leftStick, new Vector2(1, 0));
151
+ yield return new WaitForSeconds(0.1f);
152
+
153
+ Assert.Greater(player.transform.position.x, 0f,
154
+ "Player should move right");
155
+
156
+ InputSystem.RemoveDevice(gamepad);
157
+ }
158
+
159
+ [UnityTest]
160
+ public IEnumerator InputLatency_UnderLoad_StaysAcceptable()
161
+ {
162
+ float inputTime = Time.realtimeSinceStartup;
163
+ bool actionTriggered = false;
164
+
165
+ player.OnJump += () => {
166
+ float latency = (Time.realtimeSinceStartup - inputTime) * 1000;
167
+ Assert.Less(latency, 100f, "Input latency should be under 100ms");
168
+ actionTriggered = true;
169
+ };
170
+
171
+ var keyboard = InputSystem.AddDevice<Keyboard>();
172
+ Press(keyboard.spaceKey);
173
+
174
+ yield return new WaitForSeconds(0.2f);
175
+
176
+ Assert.IsTrue(actionTriggered, "Jump should have triggered");
177
+ }
178
+
179
+ [Test]
180
+ public void Deadzone_FiltersSmallInputs()
181
+ {
182
+ var settings = new InputSettings { stickDeadzone = 0.2f };
183
+
184
+ // Input below deadzone
185
+ var filtered = InputProcessor.ApplyDeadzone(new Vector2(0.1f, 0.1f), settings);
186
+ Assert.AreEqual(Vector2.zero, filtered);
187
+
188
+ // Input above deadzone
189
+ filtered = InputProcessor.ApplyDeadzone(new Vector2(0.5f, 0.5f), settings);
190
+ Assert.AreNotEqual(Vector2.zero, filtered);
191
+ }
192
+ ```
193
+
194
+ ### Unreal
195
+
196
+ ```cpp
197
+ bool FInputTest::RunTest(const FString& Parameters)
198
+ {
199
+ // Test gamepad input mapping
200
+ APlayerController* PC = GetWorld()->GetFirstPlayerController();
201
+
202
+ // Simulate gamepad stick input
203
+ FInputKeyParams Params;
204
+ Params.Key = EKeys::Gamepad_LeftX;
205
+ Params.Delta = FVector(1.0f, 0, 0);
206
+ PC->InputKey(Params);
207
+
208
+ // Verify movement
209
+ APawn* Pawn = PC->GetPawn();
210
+ FVector Velocity = Pawn->GetVelocity();
211
+
212
+ TestTrue("Pawn should be moving", Velocity.SizeSquared() > 0);
213
+
214
+ return true;
215
+ }
216
+ ```
217
+
218
+ ### Godot
219
+
220
+ ```gdscript
221
+ func test_input_action_mapping():
222
+ # Verify action exists
223
+ assert_true(InputMap.has_action("jump"))
224
+
225
+ # Simulate input
226
+ var event = InputEventKey.new()
227
+ event.keycode = KEY_SPACE
228
+ event.pressed = true
229
+
230
+ Input.parse_input_event(event)
231
+ await get_tree().process_frame
232
+
233
+ assert_true(Input.is_action_just_pressed("jump"))
234
+
235
+ func test_gamepad_deadzone():
236
+ var input = Vector2(0.15, 0.1)
237
+ var deadzone = 0.2
238
+
239
+ var processed = input_processor.apply_deadzone(input, deadzone)
240
+
241
+ assert_eq(processed, Vector2.ZERO, "Small input should be filtered")
242
+
243
+ func test_controller_hotswap():
244
+ # Simulate controller connect
245
+ Input.joy_connection_changed(0, true)
246
+ await get_tree().process_frame
247
+
248
+ var prompt_icon = ui.get_action_prompt("jump")
249
+
250
+ assert_true(prompt_icon.texture.resource_path.contains("gamepad"),
251
+ "Should show gamepad prompts after controller connect")
252
+ ```
253
+
254
+ ## Accessibility Testing
255
+
256
+ ### Requirements Checklist
257
+
258
+ - [ ] Full keyboard navigation (no mouse required)
259
+ - [ ] Remappable controls for all actions
260
+ - [ ] Button hold alternatives to rapid press
261
+ - [ ] Toggle options for hold actions
262
+ - [ ] One-handed control schemes
263
+ - [ ] Colorblind-friendly UI indicators
264
+ - [ ] Screen reader support for menus
265
+
266
+ ### Accessibility Test Scenarios
267
+
268
+ ```
269
+ SCENARIO: Keyboard-Only Navigation
270
+ GIVEN mouse is disconnected
271
+ WHEN navigating through all menus
272
+ THEN all menu items are reachable via keyboard
273
+ AND focus indicators are clearly visible
274
+
275
+ SCENARIO: Button Hold Toggle
276
+ GIVEN "sprint requires hold" is toggled OFF
277
+ WHEN sprint button is tapped once
278
+ THEN sprint activates
279
+ AND sprint stays active until tapped again
280
+
281
+ SCENARIO: Reduced Button Mashing
282
+ GIVEN QTE assist mode enabled
283
+ WHEN QTE sequence appears
284
+ THEN single press advances sequence
285
+ AND no rapid input required
286
+ ```
287
+
288
+ ## Performance Metrics
289
+
290
+ | Metric | Target | Maximum Acceptable |
291
+ | ----------------------- | --------------- | ------------------ |
292
+ | Input-to-render latency | < 50ms | 100ms |
293
+ | Polling rate match | 1:1 with device | No input loss |
294
+ | Deadzone processing | < 1ms | 5ms |
295
+ | Rebind save/load | < 100ms | 500ms |
296
+
297
+ ## Best Practices
298
+
299
+ ### DO
300
+
301
+ - Test with actual hardware, not just simulated input
302
+ - Support simultaneous keyboard + gamepad
303
+ - Provide sensible default deadzones
304
+ - Show device-appropriate button prompts
305
+ - Allow complete control remapping
306
+ - Test at different frame rates
307
+
308
+ ### DON'T
309
+
310
+ - Assume controller layout (Xbox vs PlayStation)
311
+ - Hard-code input mappings
312
+ - Ignore analog input precision
313
+ - Skip accessibility considerations
314
+ - Forget about input during loading/cutscenes
315
+ - Neglect testing with worn/drifting controllers