Home · Architecture · Combat · Extensions · Known gaps · Source index
Commands and Events
Why commands exist
Behavior hooks often need to affect entities other than the one currently borrowed. src/command.rs turns those writes into data. A pass appends commands, releases its vector/grid borrows, and then src/world.rs applies every command at one non-reentrant mutation point.
hook mutates its local entity
+ queues cross-world effects
|
v
CommandBuffer
|
pass borrow ends
|
v
apply_commands
| |
World mutation GameEvent list
|
v
deliver_events
Command set
| Command | Applied behavior | Status |
|---|---|---|
SpawnProjectile |
Allocate a new ID and append | Live, but no current behavior emits one |
SpawnPool |
Append without allocating an ID | Callable; resulting pools are unwired |
SpawnEnemy |
Allocate a new ID and append | Live-capable |
Damage |
Resolve against current enemy, record meter, emit kill event if lethal | Live |
ApplyStatus |
Append boxed status to current enemy | Live |
Despawn |
Remove matching enemy and projectile IDs | Live |
GrantResource |
Add WP as player XP; ignore SP and TP | WP live; SP/TP stubs |
Emit |
Append a game event to the current event wave | Live |
SpawnAnimation |
Allocate ID and append | Live |
Replicate |
Rebuild an enemy of same kind/tier, clone behaviors, relocate, allocate ID | Live through blob splitting |
CommandBuffer offers one convenience method per variant. There is no validation, deduplication, scheduling timestamp, cancellation, or stable ordering beyond vector insertion order.
Damage application
Command::Damage looks up the target by ID. Missing targets are silently ignored. For a present target it calls Enemy::apply_damage, creates an EnemyKilled(Corpse) event when lethal, and records actual post-resistance damage in the tower's delivery-specific meter only when source is Tower.
The command carries a DamageMap, not a scalar. Each kind resolves flat/increased/more math before resistance. The DamageRegistry's boxed DamageBehavior is not called in this path.
Event set and delivery
| Event | Handler |
|---|---|
EnemyKilled(Corpse) |
Spawn 2.5-second death animation; grant corpse XP as WP |
DespawnEnemy(EntityId) |
Queue Despawn |
Custom(String) |
No action |
Events are delivered in waves. Each wave gets a fresh command buffer, a fresh enemy snapshot/grid rebuild, and a forked RNG. Event handlers queue commands; applying those commands produces the next wave.
events[0]
-> handle all, queue commands[0]
-> apply, collect events[1]
-> handle all, queue commands[1]
-> ...
-> stop when empty or after 8 waves
The eight-wave guard prevents unbounded cascades. Remaining events are silently dropped; the suggested logging/assertion exists only as a comment.
Ordering guarantees
- Commands within one buffer apply in insertion order.
- Event handlers see world state after the preceding command buffer has fully applied.
- All events in one wave share a snapshot taken before any handler-generated command from that wave applies.
- Each later system phase creates a newer snapshot and therefore sees prior phase mutations.
- No behavior callback runs from inside
apply_commands; reactions occur only through later event waves or later frame phases.
Replication semantics
Replicate(Enemy) finds the live source by ID, copies its kind, tier, and cloned behavior list, asks EnemyRegistry for a fresh template, replaces the template behaviors with the clones, places the child, allocates an ID, and appends directly.
This preserves mutated behavior state such as the decremented SplitOnDeath generation counter. It resets other enemy state to registry defaults: HP, XP, statuses, resistances, velocity, killer, drawing, and radius come from the factory. If the source is already absent, replication does nothing.
Current edge cases
- A lethal damage command emits
EnemyKilled, anddeath_sweepemits it again for the same still-present dead enemy. XP and VFX can therefore be duplicated. - Several lethal commands against an enemy already below zero can each emit another kill event before removal.
DespawnEnemyis a two-wave route: event to command, then command to mutation.- Pool spawn commands create permanently retained, inert pools under the current frame schedule.
See Frame Lifecycle for when buffers drain and Behavior System for how hooks receive command access.
Home · Architecture · Combat · Extensions · Known gaps · Source index