Home · Architecture · Combat · Extensions · Known gaps · Source index
Frame Lifecycle
Top-level frame
src/main.rs clamps Macroquad's frame delta to at most 0.05 seconds, reads wall-clock time, and then performs:
dt = min(get_frame_time(), 0.05)
step_world(world, dt)
draw_simulation(world)
action = weapon_crate(...) if crate is open
if no crate action and Space: try open crate
if action: apply_action
if Q: exit(1)
next_frame().await
step_world is unconditional. Therefore the crate overlay is live UI over a continuing simulation, not a pause screen.
Simulation order
src/systems.rs advances world.now and runs this exact order:
| Phase | Live work | Important deferred consequences |
|---|---|---|
| 1. Wave | Decrement timer; spawn one blob when due and below 8 enemies | Spawn is direct, not commanded |
| 2. Enemies | Enemy hooks, status hooks, then position integration | DoT damage and center despawns apply after the whole pass |
| 3. Tower | Aim at nearest live-vector enemy; cool down; create shot | Newly dead-but-unswept enemies may still be targeted |
| 4. Projectiles | Projectile ticks, movement, one enemy/wall collision, lifetime check | Hit damage/status commands apply after all projectiles |
| 5. Death sweep | Run death hooks, emit kill events, then retain alive enemies | Splits, VFX, and XP are applied through commands/events |
| 6. Animations | Increment elapsed, call draw method, remove expired | This phase also draws once before render proper |
Notably, the defined pool_pass is absent from this sequence, so ground pools never tick.
A behavior pass
Enemy, projectile, and death phases use the same run_system shell:
create CommandBuffer
fork world RNG
run pass:
collect EnemySnapshot
rebuild Grid
create SpatialQuery
dispatch hooks / mutate current entity
append commands
apply_commands
deliver_events
Commands generated by one entity do not mutate the vector while it is being iterated. They become visible only after that phase. Each call builds its own snapshot; consequently later phases see commands applied by earlier phases.
Enemy phase details
For each enemy:
dispatch_enemy_tick
dispatch_status_tick
enemy.pos += enemy.vel * dt
SeekCenter sets velocity and emits DespawnEnemy when close enough. Statuses queue Damage commands; they are retained unless on_tick returns true. Because commands apply after iteration, all statuses scheduled for the pass run even if an earlier command will kill the enemy.
Projectile phase details
For each projectile:
- Dispatch
on_tick. - Integrate position.
- Build a hit context.
- If an enemy overlaps, dispatch all
on_hit_enemyhooks, queue accumulated damage and statuses, and consume unless reflection or pierce says otherwise. - Otherwise, if outside arena bounds, dispatch
on_hit_wall. - Independently check lifetime, dispatch
on_expire(Lifetime), and mark expired. - Retain projectiles not marked expired.
Only one enemy can be hit per projectile per frame. A consumed projectile can still reach the lifetime branch in that same iteration. The default shot lifetime is 1000 simulation seconds.
Death and event timing
Damage application can immediately push EnemyKilled(Corpse) into an event list. Event delivery then grants XP and spawns death VFX, but the dead Enemy remains in the vector until death_sweep.
During death_sweep, every dead enemy runs on_death, emits another EnemyKilled(Corpse), and is then retained away. Thus the current live path can emit the kill event twice for the same death: once in apply_commands(Command::Damage) and once in death_sweep. Since the kill handler grants XP and VFX, rewards/animations can be duplicated. This is current behavior, not an intended contract.
After sweeping, pending level-ups auto-open a crate unless the player manually closed one and the screen is Playing.
Rendering order
draw_simulation clears, draws arena and spawn markers, tower, enemies, projectiles, animations, then HUD. animation_system also calls animation.animate() earlier in the frame, before the later clear, making that first draw invisible in normal rendering. The crate cards are drawn after the simulation and HUD.
See Commands and Events for cascade semantics and World and Entities for storage.
Home · Architecture · Combat · Extensions · Known gaps · Source index