Ship contact cooldown
Why this page exists
The codebase has two unrelated cooldowns with nearly identical names. They live on different entities, solve different problems, and are configured in different places. Mixing them up is one of the most common sources of confusion when reading combat code.
| Cooldown | Lives on | Configured on | Prevents |
|---|---|---|---|
| Ship contact cooldown (this page) | the enemy (e._contactCooldown: number) | the ship (ship.contactCooldown, default 0.25s) | the same enemy from re-damaging / re-bumping the ship every frame while bodies are overlapping |
| Per-target contact cooldown (separate page) | the bullet (b._contactCooldowns: Map<enemy, timer>) | the weapon (def.contactCooldown, varies per weapon) | the same bullet (sword, orbit, beam, line) from re-hitting the same enemy every frame |
If you are reading code that says b._contactCooldowns (plural, map, on a bullet), that is the bullet/per-target version. See per-target-contact-cooldown.
If you are reading code that says e._contactCooldown (singular, scalar, on an enemy), or ship.contactCooldown (the stat), that is what this page documents.
What ship contact cooldown is
When the player’s hull physically overlaps an enemy’s collision circle, two things happen in resolveShipEnemyBodyCollisions:
- Mass-weighted separation — the ship and enemy are pushed apart; the heavier party moves less. If the ship is moving above
ramThreshold(default150 u/s), the enemy takes ram damage; otherwise it is a low-speed bump. - Cooldown stamp — that specific enemy receives
e._contactCooldown = ship.contactCooldown(default0.25s). For the next quarter-second, the body-collision pass skips that enemy entirely (if ((e._contactCooldown || 0) > 0) continue;).
Without this gate, an enemy sitting flush against the hull would re-trigger ram damage, knockback, and decel every frame at 60 Hz. The cooldown turns continuous contact into a discrete tick.
The timer counts down once per frame in the same resolve() loop: if (e._contactCooldown > 0) e._contactCooldown -= dt;.
How it differs from per-target contact cooldown
Both cooldowns gate “same body, same hitter, many frames” interactions, but the direction is opposite:
- Ship contact cooldown protects the ship from being hit repeatedly by one enemy. The cooldown is keyed by enemy (one timer per enemy), stamped by the ship.
- Per-target contact cooldown protects enemies from being hit repeatedly by one persistent bullet (orbit blade, tesla line, sweep cone, fire ring). The cooldown is keyed by enemy too, but the cooldown map lives on the bullet, not the enemy — because the same enemy can still be hit by other bullets and other weapons on the same frame.
A common confusion: both mechanisms call the data field contactCooldown in their respective configs (ships.ts and weapon definition files like sweep.ts). They are unrelated values that happen to share a name. The ship value is in data/ships.ts per ship class; the weapon values are in data/weapons/*.ts per weapon.
Where to look in code
| File | Symbol | Role |
|---|---|---|
engine/combat/collision-resolver.ts | resolveShipEnemyBodyCollisions | Body-collision pass; reads e._contactCooldown, stamps it from ship.contactCooldown, decrements per frame |
engine/enemies/spawner.ts | _contactCooldown: 0 | Initial value on every spawned enemy |
data/ships.ts | contactCooldown: 0.25 (BASELINE + per-ship overrides) | Ship-side stat fed into the cooldown stamp |
engine/bridge.ts | ship.contactCooldown = cs.contactCooldown ?? 0.25 | Wires the ship stat onto the live ship object |
engine/core/state.ts | default contactCooldown: 0.25 | Engine-side default |
Notes
- The ship contact cooldown is a per-enemy gate (timer lives on each enemy), not a global ship-wide invulnerability frame. Two different enemies bumping the hull in the same frame will both register; only re-hits from the same enemy are throttled.
- The cooldown affects the whole collision interaction, including separation push, ram damage, and knockback — not just damage. A second enemy stacking on top of the first will still resolve cleanly.
- Tuning this value is one of the biggest levers on how “sticky” body contact feels. Lower values let small fast enemies grind the ship; higher values make tank-style ships feel safer to plow into a crowd.
- The Playground exposes this as the
Contact CDslider in the Ships tab, range0.05–2.0s.