What this is
Newly spawned enemies are skipped by all damage, collision, and targeting checks during a brief window at the start of their fade-in. The window is controlled by a per-enemy countdown timer _spawnT that decreases each frame from a positive initial value down to zero. While _spawnT is above a fixed threshold, the enemy is effectively non-existent to every projectile, AoE, beam, and targeting scan in the game.
The threshold
Every gameplay loop that iterates over enemies tests (e as any)._spawnT > 0.15 and skips the enemy when true. The threshold is a literal 0.15 in seconds, hard-coded inline at every call site (no shared constant).
| Field | Value | Meaning |
|---|---|---|
_spawnT initial (standard enemy) | 0.3 s | Set in spawnEnemy alongside _spawnDur: 0.3 |
_spawnT initial (orb bodyguard follower, index i) | 0.3 + i * 0.1 s | Staggered fade-in for clustered followers |
_spawnT skip threshold | > 0.15 s | Enemy excluded from collisions/damage/targeting |
_spawnT tangible threshold | <= 0.15 s | Enemy enters the grid and receives hits |
_spawnT zero (pop trigger) | 0 s | Sets _spawnPopT = 0.12 for squash-stretch |
_spawnPopT duration | 0.12 s | Scale bounce 1.0 → 1.12 → 1.0 after fade-in |
_spawnT decrements by dt each frame in bridge.ts until it reaches zero; at that point it is clamped to 0 and the pop timer is triggered. Given the standard 0.3 initial value, the immune window lasts the first 0.15 s of the 0.3 s fade-in — the latter half of the fade is fully tangible.
Which collision paths honor it
The skip check appears at every site that scans the enemy array for damage, collision, separation, or target acquisition. While _spawnT > 0.15, the enemy is ignored by:
| Path | Site (file) |
|---|---|
| Player bullet → enemy spatial grid insertion | engine/combat/collision-resolver.ts (rebuildEnemyGrid) |
| Beam-trace line collision sweep | engine/combat/collision-resolver.ts |
| Splash-AoE damage on bullet impact | engine/combat/collision-resolver.ts |
| Mine proximity trigger scan | engine/weapons/bullets.ts (mine armed phase) |
| Mine pulse damage (post-detonation) | engine/weapons/bullets.ts |
| Mortar AoE damage with falloff | engine/weapons/bullets.ts |
| Chain-lightning next-target acquisition | engine/weapons/bullets.ts |
| Tesla orbit per-target contact loop | engine/weapons/bullets.ts |
| Cone weapon tick sweep | engine/weapons/bullets.ts |
| Arc/sword swing target scan | engine/weapons/bullets.ts |
| Shooting-star contact loop | engine/weapons/bullets.ts |
| Shooting-star AoE on landing | engine/weapons/bullets.ts |
| Bomb / hellfire / cluster impact AoE | engine/weapons/bullets.ts |
| Zone-tick damage (ground hazards) | engine/weapons/bullets.ts |
| Burst-fire target acquisition | engine/weapons/bullets.ts |
| Enemy-vs-enemy separation forces | engine/bridge.ts, engine/combat/collision-resolver.ts |
Because the spatial grid is rebuilt every frame and excludes spawning enemies, bullets using grid-accelerated lookups also miss spawning enemies automatically — the skip is enforced both at insertion and at every iteration site.
Why
The spawn comment in spawner.ts states: No intangibility — enemies are targetable immediately. That comment refers to the visual fade-in (0.3 s) not gating gameplay overall — but for the first half of that fade, the _spawnT > 0.15 check enforces a tangible-but-untouchable window. The purpose is twofold:
- Prevent self-hit on overlapping spawn. When enemies spawn in clusters (orb bodyguard followers, biome packs, mini-boss adds), bodies frequently overlap on frame 1. Without a brief immune window, in-flight player bullets, persistent AoE zones, orbiting blades, and beam traces would all immediately damage every overlapping fresh spawn — instantly wiping packs the moment they appear, before the player has reacted to them.
- Stabilize the spatial grid. Inserting an enemy into
enemyGridbefore its position has settled into the fade-in tween would cause inconsistent collision results across paths that query the grid versus paths that iterate the full enemy array. Excluding the spawning enemy from the grid for the first0.15s ensures all collision paths agree the enemy is non-targetable.
The threshold is not configurable — it is a literal 0.15 repeated at every call site. The initial _spawnT value (and thus the total fade-in duration) is configurable per enemy: standard spawns use 0.3, orb followers use 0.3 + i * 0.1 for staggered visual entry.