|
| 1 | +### GECS AI Coding Guide |
| 2 | + |
| 3 | +Concise, codebase-specific instructions for AI agents. Focus only on proven patterns in this repo. |
| 4 | + |
| 5 | +#### Core Runtime (under `addons/gecs/ecs/`) |
| 6 | + |
| 7 | +Entity (`entity.gd`): Node holding components (data) + relationships. Provides `add_component()`, `has_component()`, `add_relationship()`. |
| 8 | +Component (`component.gd`): Resource, data-only `@export` fields. Emits `property_changed` manually to trigger observers. |
| 9 | +System (`system.gd`): Override `query()` returning a `QueryBuilder`; implement `process(entities, components, delta)`. Use `iterate([...])` in query for batch column access (components array order matches iterate list). Optional `sub_systems()` returns `[QueryBuilder, Callable]` tuples. |
| 10 | +Observer (`observer.gd`): Reactive system: implement `watch()` (returns component instance) and handlers (`on_component_added/removed/changed`). Property changes require explicit signal emission in component setter. |
| 11 | +World (`world.gd`): Owns entities, systems, observers, archetype & relationship indices. Provides `world.query` (pooled `QueryBuilder`), archetype cache, enabled/disabled filtering baked into signatures. |
| 12 | +ECS (`ecs.gd`): Autoload singleton exposing `ECS.world` and `ECS.process(delta, group?)`. |
| 13 | + |
| 14 | +#### QueryBuilder Essentials (`query_builder.gd`) |
| 15 | + |
| 16 | +Chaining: `with_all([...])`, `with_any([...])`, `with_none([...])`, `with_relationship([...])`, `without_relationship([...])`, `with_group([...])`, `without_group([...])`, `.enabled()`, `.disabled()`, `.iterate([CompA, CompB])`. |
| 17 | +Component property filters supported via dictionaries: `with_all([{C_Health: {"current": {"_lt": 20}}}])`. |
| 18 | +`execute()` returns entities; `archetypes()` returns matching archetypes for high-performance column access. |
| 19 | +Cache keys (FNV-1a) reused between World `_query` and archetype retrieval; relationship changes invalidate query cache. |
| 20 | + |
| 21 | +#### Archetype & Performance Model |
| 22 | + |
| 23 | +Entities grouped by component signature (+ enabled bit) → O(1) query intersection using archetype match + result flattening only when needed. Enable/disable moves entity to distinct archetype; `.enabled()` / `.disabled()` skip entity-level filtering. |
| 24 | +Use `iterate()` or `archetypes()` inside systems for tight loops: access columns via `archetype.get_column(component_resource_path)`. |
| 25 | +Parallel processing: set `parallel_processing=true` and `parallel_threshold` on a System; only use pure data logic (no scene tree access) inside `process()` when parallel. |
| 26 | + |
| 27 | +#### Relationships (`relationship.gd`) |
| 28 | + |
| 29 | +Create: `Relationship.new(C_Likes.new(), target_entity)` or with property queries: `Relationship.new({C_Buff: {'duration': {'_gt':10}}}, {C_Player: {'level': {'_gte':5}}})`. |
| 30 | +Wildcard: pass `null` as relation or target. Removal supports count limiting: `entity.remove_relationship(Relationship.new(C_Damage.new(), null), 2)`. |
| 31 | +Reverse queries: `with_reverse_relationship([...])` maps target → sources via index. |
| 32 | + |
| 33 | +#### CommandBuffer (`command_buffer.gd`) |
| 34 | + |
| 35 | +Callable-based deferred execution for safe structural changes during system iteration. Each queue method appends a lambda with baked-in `is_instance_valid` guard to `Array[Callable]`. Commands execute in exact queued order. |
| 36 | + |
| 37 | +System property: `cmd: CommandBuffer` (lazy-initialized). Queue methods: `add_component()`, `remove_component()`, `add_components()`, `remove_components()`, `add_entity()`, `remove_entity()`, `add_relationship()`, `remove_relationship()`, `add_custom()`. Inspection: `is_empty()`, `size()`, `get_stats()`. Manual: `execute()`, `clear()`. |
| 38 | + |
| 39 | +Flush modes (`command_buffer_flush_mode` export on System): |
| 40 | +- **PER_SYSTEM** (default): auto-executes after each system completes |
| 41 | +- **PER_GROUP**: auto-executes after all systems in group complete |
| 42 | +- **MANUAL**: requires explicit `ECS.world.flush_command_buffers()` |
| 43 | + |
| 44 | +Pattern: use `cmd.remove_entity(entity)` instead of `ECS.world.remove_entity(entity)` inside system `process()` for safe forward iteration. Use `cmd.add_component(entity, comp)` instead of `entity.add_component(comp)` when modifying entities during iteration. |
| 45 | + |
| 46 | +#### Reactive Patterns |
| 47 | + |
| 48 | +Emit `property_changed` from component setters to enable observers. Observers internally call `match()` query then fire callbacks if entity remains in result set for add/change; removal bypasses query check. |
| 49 | + |
| 50 | +#### Testing & Perf Workflow |
| 51 | + |
| 52 | +Test root: `addons/gecs/tests/` (core + performance). Always prefix paths with `res://`. |
| 53 | +Windows: `addons/gdUnit4/runtest.cmd -a "res://addons/gecs/tests"`. Linux/macOS: `addons/gdUnit4/runtest.sh -a "res://addons/gecs/tests"`. |
| 54 | +Specific test method: `runtest.sh -a "res://addons/gecs/tests/core/test_entity.gd::test_add_and_get_component"` (no spaces around `::`). |
| 55 | +Performance tests log JSONL to `reports/perf/` with fields: `timestamp,test,scale,time_ms,godot_version`. Keep filenames stable for tooling (`tools/perf_viewer`). |
| 56 | +Perf viewer task (uv): see VS Code task "Perf Viewer: Generate Report & Open" or run `uv run perf-viewer --dir reports/perf --out perf_report.html`. |
| 57 | + |
| 58 | +#### Conventions |
| 59 | + |
| 60 | +Naming: Components `C_Foo`, Systems `FooSystem`, Observers `FooObserver`. Files often prefixed (`c_foo.gd`, `s_foo.gd`). Data-only components—push logic to Systems/Observers. |
| 61 | +System grouping via `@export var group`; process selectively: `ECS.process(delta, "physics")`. |
| 62 | +Use `q` shortcut (world-bound) inside systems & observers; avoid manual scene-tree scans beyond groups (QueryBuilder handles indexing). |
| 63 | + |
| 64 | +#### Safe Extension Guidelines |
| 65 | + |
| 66 | +Add new query predicates by following patterns in `query_builder.gd` (invalidate cache on structural changes; integrate with relationship signals if needed). |
| 67 | +When altering archetype logic, maintain signature stability (sorted component paths + enabled bit) to preserve cache correctness. |
| 68 | +Document user-facing API changes in `addons/gecs/README.md`; add/adjust tests (include property query + relationship edge cases; perf tests for scaling). |
| 69 | + |
| 70 | +#### Common Pitfalls |
| 71 | + |
| 72 | +Missing `res://` in test paths → tests not discovered. |
| 73 | +Adding behavior to Components → breaks data-only design (move to System/Observer). |
| 74 | +Forgetting to emit `property_changed` → observers won’t trigger on mutations. |
| 75 | +Not using `iterate()` for batch loops → unnecessary per-entity `get_component()` overhead. |
| 76 | +Scene tree access inside parallel system processing → undefined behavior (avoid). |
| 77 | + |
| 78 | +#### Release Process |
| 79 | + |
| 80 | +Tag (`git tag vX.Y.Z && git push`) to generate `release-vX.Y.Z` and `godot-asset-library-vX.Y.Z` (no tests). Don’t hand-edit release branches. |
| 81 | + |
| 82 | +#### Example Optimized System |
| 83 | + |
| 84 | +```gdscript |
| 85 | +class_name VelocitySystem |
| 86 | +extends System |
| 87 | +func query(): |
| 88 | + return q.with_all([C_Velocity, C_Transform]).iterate([C_Velocity, C_Transform]) |
| 89 | +func process(entities: Array[Entity], components: Array, delta: float) -> void: |
| 90 | + var velocities = components[0] |
| 91 | + var transforms = components[1] |
| 92 | + for i in entities.size(): |
| 93 | + transforms[i].transform.global_position += velocities[i].velocity * delta |
| 94 | +``` |
| 95 | + |
| 96 | +#### Agent Checklist Before Commit |
| 97 | + |
| 98 | +1. Query usage follows builder chain & avoids manual scans. |
| 99 | +2. Components remain data-only; property setters emit signal if observers required. |
| 100 | +3. Systems using performance paths rely on `iterate()` / `archetypes()`; parallel flag only with safe code. |
| 101 | +4. Added/changed behaviors have matching tests; performance-impacting changes log JSONL. |
| 102 | +5. No release branch modification; docs updated if public API changed. |
| 103 | + |
| 104 | +Feedback welcome—request clarification for any unclear section to iterate. |
0 commit comments