SKILL.md
NEVER Do in Signal Architecture
- NEVER use the legacy string-based
Object.connect() — Typos result in silent failures. Always use signal.connect(_callback) for compile-time validation.
- NEVER use signals to dictate behavior top-down — Signals are past-tense events (e.g., "died"). Use direct method calls for commands (e.g., "kill").
- NEVER connect a signal twice to the same Callable — This throws an
ERRINVALIDPARAMETER at runtime unless using the Object.CONNECTREFERENCECOUNTED flag to stack connections.
- NEVER use a Global Signal Bus for local data — Pollutes global state and makes debugging harder. Use local connections for scene-specific logic.
- NEVER assume callbacks must accept all signal arguments — Use
unbind() to drop unwanted parameters and keep your API clean.
- NEVER create circular signal dependencies — A signals B, B signals back to A? Use a mediator (parent or AutoLoad) to break the loop.
- NEVER skip signal typing —
signal moved without types lacks editor support. Always use signal moved(dir: Vector2).
- NEVER forget to disconnect dynamic signals — Ghost connections cause "call on null instance" errors. Disconnect in
exittree() or when retargeting ([disconnectghostsignals.gd](scripts/disconnectghostsignals.gd)).
- NEVER emit signals with immediate side effects on the emitter — If
died.emit() calls queue_free(), listeners might fail to respond. Emit first.
- NEVER use signals for high-frequency data streams — Sending 1000+ signals/second (like per-particle updates) is inefficient. Use shared arrays or direct buffers.
Signal Up / Call Down
- Children → parents: past-tense signals (
health_changed, died).
- Parents → children: direct calls / properties (
applydamage, playanim).
- Siblings: parent mediator or carefully scoped Autoload bus — never sibling hard refs.
Use signals for: UI presses, death → game over, loot → inventory, cross-scene bus events. Use direct calls for: parent commanding child, local property access.
Decision Tree: Where to Connect
| Scope |
Pattern |
MANDATORY script |
| Child notifies parent / UI |
Local signal.connect in parent _ready |
[signalupcalldownpattern.gd](scripts/signalupcalldownpattern.gd) |
| Parent orchestrates children |
Method calls down (not signals) |
same |
| Cross-scene / systems (achievements, save) |
Autoload bus |
[globalsignalbusrouter.gd](scripts/globalsignalbusrouter.gd) / [globaleventbus.gd](scripts/globaleventbus.gd) |
| Linear async steps (load → fade → spawn) |
await signal sequence |
[awaitsignalsequencing.gd](scripts/awaitsignalsequencing.gd) / [complexsignalsequencer.gd](scripts/complexsignalsequencer.gd) |
| Retarget tracking (new enemy) |
Disconnect old first |
[disconnectghostsignals.gd](scripts/disconnectghostsignals.gd) |
| One-shot / physics-safe |
CONNECTONESHOT / CONNECT_DEFERRED |
[oneshotdeferredconnections.gd](scripts/oneshotdeferredconnections.gd) |
| Extra context / drop args |
Callable.bind / unbind |
[callablebindcontext.gd](scripts/callablebindcontext.gd) / [unbindunwantedargs.gd](scripts/unbindunwantedargs.gd) |
Available Scripts
- [signalupcalldownpattern.gd](scripts/signalupcalldownpattern.gd) — MANDATORY before hierarchy wiring.
- [globalsignalbusrouter.gd](scripts/globalsignalbusrouter.gd) / [globaleventbus.gd](scripts/globaleventbus.gd) — MANDATORY before Autoload buses.
- [disconnectghostsignals.gd](scripts/disconnectghostsignals.gd) — MANDATORY when switching tracked emitters.
- [awaitsignalsequencing.gd](scripts/awaitsignalsequencing.gd) / [complexsignalsequencer.gd](scripts/complexsignalsequencer.gd) — MANDATORY for multi-step awaits.
- [safedynamicconnections.gd](scripts/safedynamicconnections.gd) —
is_connected guards.
- [oneshotdeferredconnections.gd](scripts/oneshotdeferredconnections.gd) — one-shot / deferred flags.
- [callablebindcontext.gd](scripts/callablebindcontext.gd) / [unbindunwantedargs.gd](scripts/unbindunwantedargs.gd) — bind/unbind.
- [tracksignalemittersource.gd](scripts/tracksignalemittersource.gd) —
CONNECTAPPENDSOURCE_OBJECT.
- [signaldebugger.gd](scripts/signaldebugger.gd) / [signalspy.gd](scripts/signalspy.gd) — debug / test spies.
Lambda Capture Cleanup (complete)
Godot auto-disconnects most connections when a node frees. Exception: lambdas that capture locals — you must disconnect manually.
var my_lambda: Callable
func _ready() -> void:
var x := 10
my_lambda = func(): print(x)
player.died.connect(my_lambda)
func _exit_tree() -> void:
if player and player.died.is_connected(my_lambda):
player.died.disconnect(my_lambda)
Prefer named methods or [disconnectghostsignals.gd](scripts/disconnectghostsignals.gd) when retargeting.
CONNECTREFERENCECOUNTED — Correct Semantics
CONNECTREFERENCECOUNTED means multiple identical connects share one connection with a refcount (connect N times / disconnect N times). It is not "auto-cleanup when the emitter frees" and does not fix capturing-lambda leaks.
- Auto-cleanup on free: normal connections to Object methods (non-capturing) are cleared when either side is freed.
- Capturing lambdas: always manual
disconnect (see above).
- One-shot auto-remove after fire:
CONNECTONESHOT.
Deep recipes (on demand)
LLM-ignorance rule: if a general agent would not know it before reading, it lives here or in scripts/ — never delete, only move.
| Topic |
Reference |
| Patterns 1–7 + gotchas |
[implementation-patterns.md](references/implementation-patterns.md) |
Reference
Progressive disclosure: open Official Documentation links only when researching a specific API; load Related Skills when routing to a peer domain — do not preload the whole lattice.
Official Documentation
- Using signals — Core emit/connect model and why signals decouple nodes without hard references.
- Scene organization — Canonical “signal up, call down” ownership rules that keep parent→child command flows explicit.
- Instancing with signals — Emit from spawned scenes so parents/managers receive bullets, loot, and other products without fixed node paths.
- Autoloads versus regular nodes — When a global EventBus is justified vs when scene-local signal wiring is safer.
- Singletons (Autoload) — How to register a typed signal bus that survives scene changes.
- Signal — Typed
Signal API: emit, connect, isconnected, and disconnect helpers used throughout this skill.
- Callable —
bind() / unbind() for injecting or discarding callback context without wrapper lambdas.
- Object —
CONNECTONESHOT, CONNECTDEFERRED, CONNECTREFERENCECOUNTED, and CONNECTAPPENDSOURCE_OBJECT flags.
- GDScript basics — Typed
signal declarations and await on signals for linear async sequences.
- Using SceneTree — Connection lifetime across enter/exit tree and why dynamic listeners must disconnect when retargeting.
- Godot notifications — Safe connection timing relative to
_ready, parent caches, and user signals.
- Idle and Physics Processing — Why deferred signal handlers matter when callbacks mutate physics bodies mid-step.
Related Skills
Prerequisites
Complements
- godot-composition — Component nodes emit past-tense events; parents compose by connecting those signals and calling down.
- godot-scene-management — Scene swaps and loaders must reconnect or re-emit through buses without ghost listeners.
- godot-state-machine-advanced — State enter/exit often drives signal fan-out; keeps FSM transitions from becoming circular signal graphs.
- godot-resource-data-patterns — Prefer Resources for shared config; signals carry change events, not duplicated mutable state blobs.
- godot-testing-patterns —
watch_signals / spies pair with this skill’s emit contracts for unit and integration tests.
- godot-ui-containers — Buttons and menus should signal intent upward; controllers call down to update Control trees.
Downstream / consumers
- godot-dialogue-system — Line/choice completion events should follow signal-up orchestration into UI and quest listeners.
- godot-ability-system — Cooldown, cast, and hit payloads need typed signals so HUD/VFX stay decoupled from ability nodes.
- godot-combat-system — Damage/death/score chains are the classic signal-up fan-out into UI, audio, and progression.
- godot-performance-optimization — Escalate when high-frequency emit storms show up; replace per-tick signals with buffers or direct reads.
Master
- godot-master — Library router and mirrored module entry; open when discovering which Domain Skill owns a cross-cutting architecture concern.