bmad-method 4.43.0 → 4.44.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 (96) hide show
  1. package/CONTRIBUTING.md +2 -9
  2. package/README.md +0 -80
  3. package/bmad-core/tasks/validate-next-story.md +1 -1
  4. package/dist/agents/dev.txt +1 -1
  5. package/dist/agents/po.txt +1 -1
  6. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.txt +1 -1
  7. package/dist/expansion-packs/bmad-2d-unity-game-dev/teams/unity-2d-game-team.txt +1 -1
  8. package/dist/expansion-packs/bmad-godot-game-dev/agents/bmad-orchestrator.txt +1513 -0
  9. package/dist/expansion-packs/bmad-godot-game-dev/agents/game-analyst.txt +3190 -0
  10. package/dist/expansion-packs/bmad-godot-game-dev/agents/game-architect.txt +4499 -0
  11. package/dist/expansion-packs/bmad-godot-game-dev/agents/game-designer.txt +3925 -0
  12. package/dist/expansion-packs/bmad-godot-game-dev/agents/game-developer.txt +666 -0
  13. package/dist/expansion-packs/bmad-godot-game-dev/agents/game-pm.txt +2381 -0
  14. package/dist/expansion-packs/bmad-godot-game-dev/agents/game-po.txt +1612 -0
  15. package/dist/expansion-packs/bmad-godot-game-dev/agents/game-qa.txt +1745 -0
  16. package/dist/expansion-packs/bmad-godot-game-dev/agents/game-sm.txt +1208 -0
  17. package/dist/expansion-packs/bmad-godot-game-dev/agents/game-ux-expert.txt +958 -0
  18. package/dist/expansion-packs/bmad-godot-game-dev/teams/godot-game-team.txt +27721 -0
  19. package/dist/teams/team-all.txt +1 -1
  20. package/dist/teams/team-fullstack.txt +1 -1
  21. package/dist/teams/team-ide-minimal.txt +1 -1
  22. package/dist/teams/team-no-ui.txt +1 -1
  23. package/docs/GUIDING-PRINCIPLES.md +3 -3
  24. package/docs/flattener.md +91 -0
  25. package/docs/versions.md +1 -1
  26. package/docs/working-in-the-brownfield.md +15 -6
  27. package/expansion-packs/bmad-godot-game-dev/README.md +244 -0
  28. package/expansion-packs/bmad-godot-game-dev/agent-teams/godot-game-team.yaml +18 -0
  29. package/expansion-packs/bmad-godot-game-dev/agents/bmad-orchestrator.md +147 -0
  30. package/expansion-packs/bmad-godot-game-dev/agents/game-analyst.md +84 -0
  31. package/expansion-packs/bmad-godot-game-dev/agents/game-architect.md +146 -0
  32. package/expansion-packs/bmad-godot-game-dev/agents/game-designer.md +78 -0
  33. package/expansion-packs/bmad-godot-game-dev/agents/game-developer.md +124 -0
  34. package/expansion-packs/bmad-godot-game-dev/agents/game-pm.md +82 -0
  35. package/expansion-packs/bmad-godot-game-dev/agents/game-po.md +115 -0
  36. package/expansion-packs/bmad-godot-game-dev/agents/game-qa.md +160 -0
  37. package/expansion-packs/bmad-godot-game-dev/agents/game-sm.md +66 -0
  38. package/expansion-packs/bmad-godot-game-dev/agents/game-ux-expert.md +75 -0
  39. package/expansion-packs/bmad-godot-game-dev/checklists/game-architect-checklist.md +377 -0
  40. package/expansion-packs/bmad-godot-game-dev/checklists/game-change-checklist.md +250 -0
  41. package/expansion-packs/bmad-godot-game-dev/checklists/game-design-checklist.md +225 -0
  42. package/expansion-packs/bmad-godot-game-dev/checklists/game-po-checklist.md +448 -0
  43. package/expansion-packs/bmad-godot-game-dev/checklists/game-story-dod-checklist.md +202 -0
  44. package/expansion-packs/bmad-godot-game-dev/config.yaml +30 -0
  45. package/expansion-packs/bmad-godot-game-dev/data/bmad-kb.md +811 -0
  46. package/expansion-packs/bmad-godot-game-dev/data/brainstorming-techniques.md +36 -0
  47. package/expansion-packs/bmad-godot-game-dev/data/development-guidelines.md +893 -0
  48. package/expansion-packs/bmad-godot-game-dev/data/elicitation-methods.md +156 -0
  49. package/expansion-packs/bmad-godot-game-dev/data/technical-preferences.md +3 -0
  50. package/expansion-packs/bmad-godot-game-dev/tasks/advanced-elicitation.md +110 -0
  51. package/expansion-packs/bmad-godot-game-dev/tasks/apply-qa-fixes.md +224 -0
  52. package/expansion-packs/bmad-godot-game-dev/tasks/brownfield-create-epic.md +162 -0
  53. package/expansion-packs/bmad-godot-game-dev/tasks/brownfield-create-story.md +149 -0
  54. package/expansion-packs/bmad-godot-game-dev/tasks/correct-course-game.md +159 -0
  55. package/expansion-packs/bmad-godot-game-dev/tasks/create-deep-research-prompt.md +278 -0
  56. package/expansion-packs/bmad-godot-game-dev/tasks/create-doc.md +103 -0
  57. package/expansion-packs/bmad-godot-game-dev/tasks/create-game-story.md +202 -0
  58. package/expansion-packs/bmad-godot-game-dev/tasks/document-project.md +343 -0
  59. package/expansion-packs/bmad-godot-game-dev/tasks/execute-checklist.md +88 -0
  60. package/expansion-packs/bmad-godot-game-dev/tasks/facilitate-brainstorming-session.md +136 -0
  61. package/expansion-packs/bmad-godot-game-dev/tasks/game-brownfield-create-epic.md +160 -0
  62. package/expansion-packs/bmad-godot-game-dev/tasks/game-brownfield-create-story.md +147 -0
  63. package/expansion-packs/bmad-godot-game-dev/tasks/game-design-brainstorming.md +290 -0
  64. package/expansion-packs/bmad-godot-game-dev/tasks/game-risk-profile.md +368 -0
  65. package/expansion-packs/bmad-godot-game-dev/tasks/game-test-design.md +219 -0
  66. package/expansion-packs/bmad-godot-game-dev/tasks/generate-ai-frontend-prompt.md +51 -0
  67. package/expansion-packs/bmad-godot-game-dev/tasks/kb-mode-interaction.md +77 -0
  68. package/expansion-packs/bmad-godot-game-dev/tasks/review-game-story.md +364 -0
  69. package/expansion-packs/bmad-godot-game-dev/tasks/shard-doc.md +187 -0
  70. package/expansion-packs/bmad-godot-game-dev/tasks/validate-game-story.md +208 -0
  71. package/expansion-packs/bmad-godot-game-dev/templates/brainstorming-output-tmpl.yaml +156 -0
  72. package/expansion-packs/bmad-godot-game-dev/templates/brownfield-prd-tmpl.yaml +281 -0
  73. package/expansion-packs/bmad-godot-game-dev/templates/competitor-analysis-tmpl.yaml +306 -0
  74. package/expansion-packs/bmad-godot-game-dev/templates/game-architecture-tmpl.yaml +1111 -0
  75. package/expansion-packs/bmad-godot-game-dev/templates/game-brief-tmpl.yaml +356 -0
  76. package/expansion-packs/bmad-godot-game-dev/templates/game-design-doc-tmpl.yaml +724 -0
  77. package/expansion-packs/bmad-godot-game-dev/templates/game-prd-tmpl.yaml +209 -0
  78. package/expansion-packs/bmad-godot-game-dev/templates/game-qa-gate-tmpl.yaml +186 -0
  79. package/expansion-packs/bmad-godot-game-dev/templates/game-story-tmpl.yaml +406 -0
  80. package/expansion-packs/bmad-godot-game-dev/templates/game-ui-spec-tmpl.yaml +601 -0
  81. package/expansion-packs/bmad-godot-game-dev/templates/level-design-doc-tmpl.yaml +620 -0
  82. package/expansion-packs/bmad-godot-game-dev/templates/market-research-tmpl.yaml +418 -0
  83. package/expansion-packs/bmad-godot-game-dev/utils/bmad-doc-template.md +327 -0
  84. package/expansion-packs/bmad-godot-game-dev/utils/workflow-management.md +71 -0
  85. package/expansion-packs/bmad-godot-game-dev/workflows/game-dev-greenfield.yaml +245 -0
  86. package/expansion-packs/bmad-godot-game-dev/workflows/game-prototype.yaml +213 -0
  87. package/package.json +1 -1
  88. package/release_notes.md +11 -2
  89. package/tools/flattener/ignoreRules.js +2 -0
  90. package/tools/installer/bin/bmad.js +2 -1
  91. package/tools/installer/config/install.config.yaml +16 -7
  92. package/tools/installer/lib/ide-setup.js +192 -80
  93. package/tools/installer/package.json +1 -1
  94. package/tools/upgraders/v3-to-v4-upgrader.js +1 -0
  95. package/test.md +0 -1
  96. /package/{implement-fork-friendly-ci.sh → tools/implement-fork-friendly-ci.sh} +0 -0
@@ -0,0 +1,893 @@
1
+ # Game Development Guidelines (Godot, GDScript & C#)
2
+
3
+ ## Overview
4
+
5
+ This document establishes coding standards, architectural patterns, and development practices for game development using Godot Engine with GDScript and C#. These guidelines ensure consistency, performance (60+ FPS target), maintainability, and enforce Test-Driven Development (TDD) across all game development stories.
6
+
7
+ ## Performance Philosophy
8
+
9
+ Following John Carmack's principles:
10
+
11
+ - **"Measure, don't guess"** - Profile everything with Godot's built-in profiler
12
+ - **"Focus on what matters: framerate and responsiveness"** - 60+ FPS is the minimum, not the target
13
+ - **"The best code is no code"** - Simplicity beats cleverness
14
+ - **"Think about cache misses, not instruction counts"** - Memory access patterns matter most
15
+
16
+ ## GDScript Standards
17
+
18
+ ### Naming Conventions
19
+
20
+ **Classes and Scripts:**
21
+
22
+ - PascalCase for class names: `PlayerController`, `GameData`, `InventorySystem`
23
+ - Snake_case for file names: `player_controller.gd`, `game_data.gd`
24
+ - Descriptive names that indicate purpose: `GameStateManager` not `GSM`
25
+
26
+ **Functions and Methods:**
27
+
28
+ - Snake_case for functions: `calculate_damage()`, `process_input()`
29
+ - Descriptive verb phrases: `activate_shield()` not `shield()`
30
+ - Private methods prefix with underscore: `_update_health()`
31
+
32
+ **Variables and Properties:**
33
+
34
+ - Snake_case for variables: `player_health`, `movement_speed`
35
+ - Constants in UPPER_SNAKE_CASE: `MAX_HEALTH`, `GRAVITY_FORCE`
36
+ - Export variables with clear names: `@export var jump_height: float = 5.0`
37
+ - Boolean variables with is/has/can prefix: `is_alive`, `has_key`, `can_jump`
38
+ - Signal names in snake_case: `health_changed`, `level_completed`
39
+
40
+ ### Static Typing (MANDATORY for Performance)
41
+
42
+ **Always use static typing for 10-20% performance gain:**
43
+
44
+ ```gdscript
45
+ # GOOD - Static typing
46
+ extends CharacterBody2D
47
+
48
+ @export var max_health: int = 100
49
+ @export var movement_speed: float = 300.0
50
+
51
+ var current_health: int
52
+ var velocity_multiplier: float = 1.0
53
+
54
+ func take_damage(amount: int) -> void:
55
+ current_health -= amount
56
+ if current_health <= 0:
57
+ _die()
58
+
59
+ func _die() -> void:
60
+ queue_free()
61
+
62
+ # BAD - Dynamic typing (avoid)
63
+ var health = 100 # No type specified
64
+ func take_damage(amount): # No parameter or return type
65
+ health -= amount
66
+ ```
67
+
68
+ ## C# Standards (for Performance-Critical Systems)
69
+
70
+ ### When to Use C# vs GDScript
71
+
72
+ **Use C# for:**
73
+
74
+ - Complex algorithms (pathfinding, procedural generation)
75
+ - Heavy mathematical computations
76
+ - Performance-critical systems identified by profiler
77
+ - External .NET library integration
78
+ - Large-scale data processing
79
+
80
+ **Use GDScript for:**
81
+
82
+ - Rapid prototyping and iteration
83
+ - UI and menu systems
84
+ - Simple game logic
85
+ - Editor tools and scene management
86
+ - Quick gameplay tweaks
87
+
88
+ ### C# Naming Conventions
89
+
90
+ ```csharp
91
+ using Godot;
92
+
93
+ public partial class PlayerController : CharacterBody2D
94
+ {
95
+ // Public fields (use sparingly, prefer properties)
96
+ [Export] public float MoveSpeed = 300.0f;
97
+
98
+ // Private fields with underscore prefix
99
+ private int _currentHealth;
100
+ private float _jumpVelocity;
101
+
102
+ // Properties with PascalCase
103
+ public int MaxHealth { get; set; } = 100;
104
+
105
+ // Methods with PascalCase
106
+ public void TakeDamage(int amount)
107
+ {
108
+ _currentHealth -= amount;
109
+ if (_currentHealth <= 0)
110
+ {
111
+ Die();
112
+ }
113
+ }
114
+
115
+ private void Die()
116
+ {
117
+ QueueFree();
118
+ }
119
+ }
120
+ ```
121
+
122
+ ## Godot Architecture Patterns
123
+
124
+ ### Node-Based Architecture
125
+
126
+ **Scene Composition Over Inheritance:**
127
+
128
+ ```gdscript
129
+ # Player.tscn structure:
130
+ # Player (CharacterBody2D)
131
+ # ├── Sprite2D
132
+ # ├── CollisionShape2D
133
+ # ├── PlayerHealth (Node)
134
+ # ├── PlayerMovement (Node)
135
+ # └── PlayerInput (Node)
136
+
137
+ # PlayerHealth.gd - Single responsibility component
138
+ extends Node
139
+ class_name PlayerHealth
140
+
141
+ signal health_changed(new_health: int)
142
+ signal died
143
+
144
+ @export var max_health: int = 100
145
+ var current_health: int
146
+
147
+ func _ready() -> void:
148
+ current_health = max_health
149
+
150
+ func take_damage(amount: int) -> void:
151
+ current_health = max(0, current_health - amount)
152
+ health_changed.emit(current_health)
153
+ if current_health == 0:
154
+ died.emit()
155
+ ```
156
+
157
+ ### Signal-Based Communication
158
+
159
+ **Decouple Systems with Signals:**
160
+
161
+ ```gdscript
162
+ # GameManager.gd - Singleton/Autoload
163
+ extends Node
164
+
165
+ signal game_started
166
+ signal game_over
167
+ signal level_completed
168
+
169
+ var score: int = 0
170
+ var current_level: int = 1
171
+
172
+ func start_game() -> void:
173
+ score = 0
174
+ current_level = 1
175
+ game_started.emit()
176
+ get_tree().change_scene_to_file("res://scenes/levels/level_1.tscn")
177
+
178
+ # Player.gd - Connects to signals
179
+ extends CharacterBody2D
180
+
181
+ func _ready() -> void:
182
+ GameManager.game_over.connect(_on_game_over)
183
+
184
+ func _on_game_over() -> void:
185
+ set_physics_process(false) # Stop player movement
186
+ $AnimationPlayer.play("death")
187
+ ```
188
+
189
+ ### Resource-Based Data Management
190
+
191
+ **Use Custom Resources for Game Data:**
192
+
193
+ ```gdscript
194
+ # WeaponData.gd - Custom Resource
195
+ extends Resource
196
+ class_name WeaponData
197
+
198
+ @export var weapon_name: String = "Sword"
199
+ @export var damage: int = 10
200
+ @export var attack_speed: float = 1.0
201
+ @export var sprite: Texture2D
202
+
203
+ # Weapon.gd - Uses the resource
204
+ extends Node2D
205
+ class_name Weapon
206
+
207
+ @export var weapon_data: WeaponData
208
+
209
+ func _ready() -> void:
210
+ if weapon_data:
211
+ $Sprite2D.texture = weapon_data.sprite
212
+
213
+ func attack() -> int:
214
+ return weapon_data.damage if weapon_data else 0
215
+ ```
216
+
217
+ ## Performance Optimization
218
+
219
+ ### Object Pooling (MANDATORY for Spawned Objects)
220
+
221
+ ```gdscript
222
+ # ObjectPool.gd - Generic pooling system
223
+ extends Node
224
+ class_name ObjectPool
225
+
226
+ @export var pool_scene: PackedScene
227
+ @export var initial_size: int = 20
228
+
229
+ var _pool: Array[Node] = []
230
+
231
+ func _ready() -> void:
232
+ for i in initial_size:
233
+ var instance := pool_scene.instantiate()
234
+ instance.set_process(false)
235
+ instance.set_physics_process(false)
236
+ instance.visible = false
237
+ add_child(instance)
238
+ _pool.append(instance)
239
+
240
+ func get_object() -> Node:
241
+ for obj in _pool:
242
+ if not obj.visible:
243
+ obj.visible = true
244
+ obj.set_process(true)
245
+ obj.set_physics_process(true)
246
+ return obj
247
+
248
+ # Expand pool if needed
249
+ var new_obj := pool_scene.instantiate()
250
+ add_child(new_obj)
251
+ _pool.append(new_obj)
252
+ return new_obj
253
+
254
+ func return_object(obj: Node) -> void:
255
+ obj.set_process(false)
256
+ obj.set_physics_process(false)
257
+ obj.visible = false
258
+ obj.position = Vector2.ZERO
259
+ ```
260
+
261
+ ### Process Optimization
262
+
263
+ **Use Appropriate Process Methods:**
264
+
265
+ ```gdscript
266
+ extends Node2D
267
+
268
+ # For physics calculations (fixed timestep)
269
+ func _physics_process(delta: float) -> void:
270
+ # Movement, collision detection
271
+ pass
272
+
273
+ # For visual updates and input
274
+ func _process(delta: float) -> void:
275
+ # Animations, UI updates
276
+ pass
277
+
278
+ # Use timers or signals instead of checking every frame
279
+ func _ready() -> void:
280
+ var timer := Timer.new()
281
+ timer.wait_time = 1.0
282
+ timer.timeout.connect(_check_condition)
283
+ add_child(timer)
284
+ timer.start()
285
+
286
+ func _check_condition() -> void:
287
+ # Check something once per second instead of 60 times
288
+ pass
289
+ ```
290
+
291
+ ### Memory Management
292
+
293
+ **Prevent Memory Leaks:**
294
+
295
+ ```gdscript
296
+ extends Node
297
+
298
+ var _connections: Array[Callable] = []
299
+
300
+ func _ready() -> void:
301
+ # Store connections for cleanup
302
+ var callable := GameManager.score_changed.connect(_on_score_changed)
303
+ _connections.append(callable)
304
+
305
+ func _exit_tree() -> void:
306
+ # Clean up connections
307
+ for connection in _connections:
308
+ if connection.is_valid():
309
+ connection.disconnect()
310
+ _connections.clear()
311
+
312
+ # Use queue_free() not free() for nodes
313
+ func remove_enemy(enemy: Node) -> void:
314
+ enemy.queue_free() # Safe deletion
315
+ ```
316
+
317
+ ## Test-Driven Development (MANDATORY)
318
+
319
+ ### GUT (Godot Unit Test) for GDScript
320
+
321
+ **Write Tests FIRST:**
322
+
323
+ ```gdscript
324
+ # test/unit/test_player_health.gd
325
+ extends GutTest
326
+
327
+ var player_health: PlayerHealth
328
+
329
+ func before_each() -> void:
330
+ player_health = PlayerHealth.new()
331
+ player_health.max_health = 100
332
+
333
+ func test_take_damage_reduces_health() -> void:
334
+ # Arrange
335
+ player_health.current_health = 100
336
+
337
+ # Act
338
+ player_health.take_damage(30)
339
+
340
+ # Assert
341
+ assert_eq(player_health.current_health, 70, "Health should be reduced by damage amount")
342
+
343
+ func test_health_cannot_go_negative() -> void:
344
+ # Arrange
345
+ player_health.current_health = 10
346
+
347
+ # Act
348
+ player_health.take_damage(20)
349
+
350
+ # Assert
351
+ assert_eq(player_health.current_health, 0, "Health should not go below 0")
352
+
353
+ func test_died_signal_emitted_at_zero_health() -> void:
354
+ # Arrange
355
+ player_health.current_health = 10
356
+ watch_signals(player_health)
357
+
358
+ # Act
359
+ player_health.take_damage(10)
360
+
361
+ # Assert
362
+ assert_signal_emitted(player_health, "died")
363
+ ```
364
+
365
+ ### GoDotTest for C#
366
+
367
+ ```csharp
368
+ using Godot;
369
+ using GoDotTest;
370
+
371
+ [TestClass]
372
+ public class PlayerControllerTests : TestClass
373
+ {
374
+ private PlayerController _player;
375
+
376
+ [TestInitialize]
377
+ public void Setup()
378
+ {
379
+ _player = new PlayerController();
380
+ _player.MaxHealth = 100;
381
+ }
382
+
383
+ [Test]
384
+ public void TakeDamage_ReducesHealth()
385
+ {
386
+ // Arrange
387
+ _player.CurrentHealth = 100;
388
+
389
+ // Act
390
+ _player.TakeDamage(30);
391
+
392
+ // Assert
393
+ AssertThat(_player.CurrentHealth).IsEqualTo(70);
394
+ }
395
+
396
+ [Test]
397
+ public void TakeDamage_EmitsDiedSignal_WhenHealthReachesZero()
398
+ {
399
+ // Arrange
400
+ _player.CurrentHealth = 10;
401
+ var signalEmitted = false;
402
+ _player.Died += () => signalEmitted = true;
403
+
404
+ // Act
405
+ _player.TakeDamage(10);
406
+
407
+ // Assert
408
+ AssertThat(signalEmitted).IsTrue();
409
+ }
410
+ }
411
+ ```
412
+
413
+ ## Input Handling
414
+
415
+ ### Godot Input System
416
+
417
+ **Input Map Configuration:**
418
+
419
+ ```gdscript
420
+ # Configure in Project Settings -> Input Map
421
+ # Actions: "move_left", "move_right", "jump", "attack"
422
+
423
+ extends CharacterBody2D
424
+
425
+ @export var speed: float = 300.0
426
+ @export var jump_velocity: float = -400.0
427
+
428
+ func _physics_process(delta: float) -> void:
429
+ # Add gravity
430
+ if not is_on_floor():
431
+ velocity.y += ProjectSettings.get_setting("physics/2d/default_gravity") * delta
432
+
433
+ # Handle jump
434
+ if Input.is_action_just_pressed("jump") and is_on_floor():
435
+ velocity.y = jump_velocity
436
+
437
+ # Handle movement
438
+ var direction := Input.get_axis("move_left", "move_right")
439
+ velocity.x = direction * speed
440
+
441
+ move_and_slide()
442
+
443
+ # For responsive input (use _unhandled_input for UI priority)
444
+ func _unhandled_input(event: InputEvent) -> void:
445
+ if event.is_action_pressed("attack"):
446
+ _perform_attack()
447
+ ```
448
+
449
+ ## Scene Management
450
+
451
+ ### Scene Loading and Transitions
452
+
453
+ ```gdscript
454
+ # SceneManager.gd - Autoload singleton
455
+ extends Node
456
+
457
+ var current_scene: Node = null
458
+
459
+ func _ready() -> void:
460
+ var root := get_tree().root
461
+ current_scene = root.get_child(root.get_child_count() - 1)
462
+
463
+ func change_scene(path: String) -> void:
464
+ call_deferred("_deferred_change_scene", path)
465
+
466
+ func _deferred_change_scene(path: String) -> void:
467
+ # Free current scene
468
+ current_scene.queue_free()
469
+
470
+ # Load new scene
471
+ var new_scene := ResourceLoader.load(path) as PackedScene
472
+ current_scene = new_scene.instantiate()
473
+ get_tree().root.add_child(current_scene)
474
+ get_tree().current_scene = current_scene
475
+
476
+ # With loading screen
477
+ func change_scene_with_loading(path: String) -> void:
478
+ # Show loading screen
479
+ var loading_screen := preload("res://scenes/ui/loading_screen.tscn").instantiate()
480
+ get_tree().root.add_child(loading_screen)
481
+
482
+ # Load in background
483
+ ResourceLoader.load_threaded_request(path)
484
+
485
+ # Wait for completion
486
+ while ResourceLoader.load_threaded_get_status(path) != ResourceLoader.THREAD_LOAD_LOADED:
487
+ await get_tree().process_frame
488
+
489
+ # Switch scenes
490
+ loading_screen.queue_free()
491
+ change_scene(path)
492
+ ```
493
+
494
+ ## Project Structure
495
+
496
+ ```
497
+ res://
498
+ ├── scenes/
499
+ │ ├── main/
500
+ │ │ ├── main_menu.tscn
501
+ │ │ └── game.tscn
502
+ │ ├── levels/
503
+ │ │ ├── level_1.tscn
504
+ │ │ └── level_2.tscn
505
+ │ ├── player/
506
+ │ │ └── player.tscn
507
+ │ └── ui/
508
+ │ ├── hud.tscn
509
+ │ └── pause_menu.tscn
510
+ ├── scripts/
511
+ │ ├── player/
512
+ │ │ ├── player_controller.gd
513
+ │ │ └── player_health.gd
514
+ │ ├── enemies/
515
+ │ │ └── enemy_base.gd
516
+ │ ├── systems/
517
+ │ │ ├── game_manager.gd
518
+ │ │ └── scene_manager.gd
519
+ │ └── ui/
520
+ │ └── hud_controller.gd
521
+ ├── resources/
522
+ │ ├── weapons/
523
+ │ │ └── sword_data.tres
524
+ │ └── enemies/
525
+ │ └── slime_data.tres
526
+ ├── assets/
527
+ │ ├── sprites/
528
+ │ ├── audio/
529
+ │ └── fonts/
530
+ ├── tests/
531
+ │ ├── unit/
532
+ │ │ └── test_player_health.gd
533
+ │ └── integration/
534
+ │ └── test_level_loading.gd
535
+ └── project.godot
536
+ ```
537
+
538
+ ## Development Workflow
539
+
540
+ ### TDD Story Implementation Process
541
+
542
+ 1. **Read Story Requirements:**
543
+ - Understand acceptance criteria
544
+ - Identify performance requirements (60+ FPS)
545
+ - Determine GDScript vs C# needs
546
+
547
+ 2. **Write Tests FIRST (Red Phase):**
548
+ - Write failing unit tests in GUT/GoDotTest
549
+ - Define expected behavior
550
+ - Run tests to confirm they fail
551
+
552
+ 3. **Implement Feature (Green Phase):**
553
+ - Write minimal code to pass tests
554
+ - Follow Godot patterns and conventions
555
+ - Use static typing in GDScript
556
+ - Choose appropriate language (GDScript/C#)
557
+
558
+ 4. **Refactor (Refactor Phase):**
559
+ - Optimize for performance
560
+ - Clean up code structure
561
+ - Ensure 60+ FPS maintained
562
+ - Run profiler to validate
563
+
564
+ 5. **Integration Testing:**
565
+ - Test scene interactions
566
+ - Validate performance targets
567
+ - Test on all platforms
568
+
569
+ 6. **Update Documentation:**
570
+ - Mark story checkboxes complete
571
+ - Document performance metrics
572
+ - Update File List
573
+
574
+ ### Performance Checklist
575
+
576
+ - [ ] Stable 60+ FPS achieved
577
+ - [ ] Static typing used in all GDScript
578
+ - [ ] Object pooling for spawned entities
579
+ - [ ] No memory leaks detected
580
+ - [ ] Draw calls optimized
581
+ - [ ] Appropriate process methods used
582
+ - [ ] Signals properly connected/disconnected
583
+ - [ ] Tests written FIRST (TDD)
584
+ - [ ] 80%+ test coverage
585
+
586
+ ## Performance Targets
587
+
588
+ ### Frame Rate Requirements
589
+
590
+ - **Desktop**: 60+ FPS minimum (144 FPS for high-refresh)
591
+ - **Mobile**: 60 FPS on mid-range devices
592
+ - **Web**: 60 FPS with appropriate export settings
593
+ - **Frame Time**: <16.67ms consistently
594
+
595
+ ### Memory Management
596
+
597
+ - **Scene Memory**: Keep under platform limits
598
+ - **Texture Memory**: Optimize imports, use compression
599
+ - **Object Pooling**: Required for bullets, particles, enemies
600
+ - **Reference Cleanup**: Prevent memory leaks
601
+
602
+ ### Optimization Priorities
603
+
604
+ 1. **Profile First**: Use Godot profiler to identify bottlenecks
605
+ 2. **Optimize Algorithms**: Better algorithms beat micro-optimizations
606
+ 3. **Reduce Draw Calls**: Batch rendering, use atlases
607
+ 4. **Static Typing**: 10-20% performance gain in GDScript
608
+ 5. **Language Choice**: Use C# for compute-heavy operations
609
+
610
+ ## General Optimization
611
+
612
+ ### Anti-Patterns
613
+
614
+ 1. **Security Holes**
615
+ - Buffer overflows
616
+ - SQL injection vectors
617
+ - Unvalidated user input
618
+ - Timing attacks
619
+ - Memory disclosure
620
+ - Race conditions with security impact
621
+
622
+ 2. **Platform Sabotage**
623
+ - Fighting Godot's scene system
624
+ - Reimplementing platform features
625
+ - Ignoring hardware capabilities
626
+
627
+ ## GDScript Optimization
628
+
629
+ ### Performance Destroyers
630
+
631
+ 1. **Type System Crimes**
632
+ - Dynamic typing anywhere (10-20% performance loss)
633
+ - Variant usage in hot paths
634
+ - Dictionary/Array without typed variants
635
+ - Missing return type hints
636
+ - Untyped function parameters
637
+
638
+ 2. **Allocation Disasters**
639
+ - Creating Arrays/Dictionaries in loops
640
+ - String concatenation with +
641
+ - Unnecessary Node instantiation
642
+ - Resource loading in game loop
643
+ - Signal connections without caching
644
+
645
+ 3. **Process Method Abuse**
646
+ - \_process() when \_physics_process() suffices
647
+ - Frame-by-frame checks for rare events
648
+ - get_node() calls every frame
649
+ - Node path resolution in loops
650
+ - Unnecessary process enabling
651
+
652
+ ### GDScript Death Sentences
653
+
654
+ ```gdscript
655
+ # CRIME: Dynamic typing
656
+ var health = 100 # Dies. var health: int = 100
657
+
658
+ # CRIME: String concatenation in loop
659
+ for i in range(1000):
660
+ text += str(i) # Dies. Use StringBuffer or Array.join()
661
+
662
+ # CRIME: get_node every frame
663
+ func _process(delta):
664
+ $UI/Score.text = str(score) # Dies. Cache the node reference
665
+
666
+ # CRIME: Creating objects in loop
667
+ for enemy in enemies:
668
+ var bullet = Bullet.new() # Dies. Object pool
669
+
670
+ # CRIME: Untyped arrays
671
+ var enemies = [] # Dies. var enemies: Array[Enemy] = []
672
+
673
+ # CRIME: Path finding every frame
674
+ func _process(delta):
675
+ find_node("Player") # Dies. Store reference in _ready()
676
+
677
+ # CRIME: Signal spam
678
+ for i in range(100):
679
+ emit_signal("updated", i) # Dies. Batch updates
680
+
681
+ # CRIME: Resource loading in game
682
+ func shoot():
683
+ var bullet_scene = load("res://bullet.tscn") # Dies. Preload
684
+
685
+ # CRIME: Checking rare conditions every frame
686
+ func _process(delta):
687
+ if player_died: # Dies. Use signals
688
+ game_over()
689
+
690
+ # CRIME: Node creation without pooling
691
+ func spawn_particle():
692
+ var p = Particle.new() # Dies. Pool everything spawned
693
+ add_child(p)
694
+ ```
695
+
696
+ ### The Only Acceptable GDScript Patterns
697
+
698
+ ```gdscript
699
+ # GOOD: Static typing everywhere
700
+ var health: int = 100
701
+ var speed: float = 300.0
702
+ var enemies: Array[Enemy] = []
703
+
704
+ # GOOD: Cached node references
705
+ @onready var score_label: Label = $UI/Score
706
+ @onready var health_bar: ProgressBar = $UI/HealthBar
707
+
708
+ # GOOD: Preloaded resources
709
+ const BULLET_SCENE: PackedScene = preload("res://bullet.tscn")
710
+ const EXPLOSION_SOUND: AudioStream = preload("res://explosion.ogg")
711
+
712
+ # GOOD: Object pooling
713
+ var bullet_pool: Array[Bullet] = []
714
+ func _ready() -> void:
715
+ for i in 50:
716
+ var bullet := BULLET_SCENE.instantiate() as Bullet
717
+ bullet.visible = false
718
+ bullet_pool.append(bullet)
719
+
720
+ # GOOD: Typed dictionaries
721
+ var player_stats: Dictionary = {
722
+ "health": 100,
723
+ "armor": 50,
724
+ "speed": 300.0
725
+ }
726
+
727
+ # GOOD: Efficient string building
728
+ func build_text(count: int) -> String:
729
+ var parts: PackedStringArray = []
730
+ for i in count:
731
+ parts.append(str(i))
732
+ return "".join(parts)
733
+
734
+ # GOOD: Timer-based checks
735
+ func _ready() -> void:
736
+ var timer := Timer.new()
737
+ timer.wait_time = 1.0
738
+ timer.timeout.connect(_check_rare_condition)
739
+ add_child(timer)
740
+ timer.start()
741
+
742
+ # GOOD: Batch operations
743
+ var updates_pending: Array[int] = []
744
+ func queue_update(value: int) -> void:
745
+ updates_pending.append(value)
746
+ if updates_pending.size() == 1:
747
+ call_deferred("_process_updates")
748
+
749
+ func _process_updates() -> void:
750
+ # Process all updates at once
751
+ for value in updates_pending:
752
+ # Do work
753
+ pass
754
+ updates_pending.clear()
755
+
756
+ # GOOD: Const for compile-time optimization
757
+ const MAX_ENEMIES: int = 100
758
+ const GRAVITY: float = 980.0
759
+ const DEBUG_MODE: bool = false
760
+ ```
761
+
762
+ ### GDScript-Specific Optimization Rules
763
+
764
+ 1. **ALWAYS use static typing** - Non-negotiable 10-20% free performance
765
+ 2. **NEVER use get_node() in loops** - Cache everything in @onready
766
+ 3. **NEVER load() in gameplay** - preload() or ResourceLoader
767
+ 4. **NEVER create nodes without pooling** - Pool or die
768
+ 5. **NEVER concatenate strings in loops** - PackedStringArray.join()
769
+ 6. **ALWAYS use const for constants** - Compile-time optimization
770
+ 7. **ALWAYS specify Array types** - Array[Type] not Array
771
+ 8. **NEVER check conditions every frame** - Use signals and timers
772
+ 9. **ALWAYS batch similar operations** - One update, not many
773
+ 10. **NEVER trust the profiler isn't watching** - It always is
774
+
775
+ ## Godot C# Optimization
776
+
777
+ ### Anti-Patterns
778
+
779
+ 1. **Performance Destroyers**
780
+ - ANY allocation in render/game loop
781
+ - String operations in hot paths
782
+ - LINQ anywhere (it allocates, period)
783
+ - Boxing/unboxing in performance code
784
+ - Virtual calls when direct calls possible
785
+ - Cache-hostile data layouts
786
+ - Synchronous I/O blocking computation
787
+ 2. **Algorithmic Incompetence**
788
+ - O(n²) when O(n log n) exists
789
+ - O(n³) = fired
790
+ - Linear search in sorted data
791
+ - Recalculating invariants
792
+ - Branches in SIMD loops
793
+ - Random memory access patterns
794
+
795
+ 3. **Architectural Cancer**
796
+ - Abstractions that don't eliminate code
797
+ - Single-implementation interfaces
798
+ - Factory factories
799
+ - 3+ levels of indirection
800
+ - Reflection in performance paths
801
+ - Manager classes (lazy design)
802
+ - Event systems for direct calls
803
+ - Not using SIMD where available
804
+ - Thread-unsafe code in parallel contexts
805
+
806
+ ## C#/GODOT SPECIFIC DEATH SENTENCES
807
+
808
+ ### Instant Rejection Patterns
809
+
810
+ ```csharp
811
+ // CRIME: LINQ in game code
812
+ units.Where(u => u.IsAlive).ToList() // Dies. Pre-filtered array.
813
+
814
+ // CRIME: String operations
815
+ $"Player {name} scored {score}" // Dies. StringBuilder or byte buffer.
816
+
817
+ // CRIME: Boxing
818
+ object value = 42; // Dies. Generic or specific type.
819
+
820
+ // CRIME: Foreach on List<T>
821
+ foreach(var item in list) // Dies. for(int i = 0; i < list.Count; i++)
822
+
823
+ // CRIME: Properties doing work
824
+ public int Count => CalculateCount(); // Dies. Cache or field.
825
+
826
+ // CRIME: Virtual by default
827
+ public virtual void Update() // Dies. Sealed unless NEEDED.
828
+
829
+ // CRIME: Events for direct calls
830
+ public event Action OnUpdate; // Dies. Direct method call.
831
+
832
+ // CRIME: Reflection
833
+ typeof(T).GetMethod("Update") // Dies. Direct call or delegates.
834
+
835
+ // CRIME: Async in game loop
836
+ await LoadDataAsync(); // Dies. Preload or synchronous.
837
+
838
+ // CRIME: GD.Print in production
839
+ GD.Print($"Debug: {value}"); // Dies. Conditional compilation.
840
+ ```
841
+
842
+ ### Godot-Specific Crimes
843
+
844
+ ```csharp
845
+ // CRIME: GetNode every frame
846
+ GetNode<Label>("UI/Score") // Dies. Cache in _Ready().
847
+
848
+ // CRIME: Creating Nodes dynamically
849
+ var bullet = bulletScene.Instantiate(); // Dies. Object pool.
850
+
851
+ // CRIME: Signal connections in loops
852
+ unit.HealthChanged += OnHealthChanged; // Dies. Batch updates.
853
+
854
+ // CRIME: _Process without need
855
+ public override void _Process(double delta) // Dies. Use _PhysicsProcess or events.
856
+
857
+ // CRIME: Autoload abuse
858
+ GetNode<GameManager>("/root/GameManager") // Dies. Direct reference.
859
+ ```
860
+
861
+ ### The Only Acceptable Patterns
862
+
863
+ ```csharp
864
+ // GOOD: Pre-allocated buffers
865
+ private readonly Unit[] _units = new Unit[MAX_UNITS];
866
+ private readonly int[] _indices = new int[MAX_UNITS];
867
+
868
+ // GOOD: Struct over class
869
+ public struct UnitData { public int Health; public Vector2I Position; }
870
+
871
+ // GOOD: Data-oriented design
872
+ public struct Units {
873
+ public int[] Health;
874
+ public Vector2I[] Positions;
875
+ public bool[] IsAlive;
876
+ }
877
+
878
+ // GOOD: Zero-allocation update
879
+ public void Update() {
880
+ int count = _activeCount;
881
+ for (int i = 0; i < count; i++) {
882
+ ref Unit unit = ref _units[i];
883
+ unit.Position += unit.Velocity;
884
+ }
885
+ }
886
+
887
+ // GOOD: Compile-time elimination
888
+ #if DEBUG
889
+ GD.Print("Debug info");
890
+ #endif
891
+ ```
892
+
893
+ These guidelines ensure consistent, high-quality Godot game development that meets performance targets, maintains code quality, and follows TDD practices across all implementation stories.