Ram Mechanics

Body-contact damage when the ship moves into an enemy fast enough. Resolved each frame inside CollisionResolver.resolveShipEnemyBodyCollisions (engine/combat/collision-resolver.ts). Ram fields are per-ship and ship from data/ships.ts.

Trigger

A ram fires when the ship’s body overlaps an enemy collision circle AND the ship’s current speed (hypot(ship.vx, ship.vy)) is greater than or equal to ramThreshold. Default ramThreshold = 150 u/s. Below that speed the contact is treated as a low-speed bump (mass-weighted decel + light/heavy juice cue, no damage).

The narrow-phase check uses circleIntersectsConvexPolygon against the ship’s hullPoly so the enemy must actually touch the hull, not just the bounding circle.

Damage

Ram damage scales linearly with speed between two anchors:

t      = clamp01((shipSpeed - ramThreshold) / (5000 - ramThreshold))
ramDmg = round((ramDamageLo + t * (ramDamageHi - ramDamageLo)) * meleeMult)
  • ramDamageLo — damage at the threshold. Default 40.
  • ramDamageHi — damage at or above 5000 u/s (the hard-coded RAM_SPD_HI). Default 1000.
  • meleeMult — per-ship melee multiplier applied to the final number. Default 1.0.

Damage routes through damageEnemy() so armor, shields, and on-hit signals all fire normally.

Per-enemy contact cooldown

After any ram or low-speed contact, the touched enemy gets _contactCooldown = ship.contactCooldown (default 0.25 s). While the cooldown is non-zero that enemy is skipped by the ram pass, so a single sustained overlap can only deal one ram hit every cooldown tick. The cooldown is decremented in CollisionResolver.resolve once per frame.

This is what prevents drive-through enemies from getting shredded by per-frame damage and keeps ram balanced against multi-enemy pileups.

Speed bleed

On a successful ram the ship’s velocity is scaled by a mass-weighted bleed factor:

ramBleed = 1 - clamp(shipFrac, 0.15, 0.85)
ship.vx *= ramBleed
ship.vy *= ramBleed

Where shipFrac = enemyMass / (shipMass + enemyMass). Heavier enemy = closer to a full stop; lighter enemy = plow through with little speed loss. The clamp guarantees you always lose at least 15% and never lose more than 85%.

ramSpeedBleed is a data field on ShipDef (default 0.50) intended for future per-ship override of this bleed; the live formula in resolveShipEnemyBodyCollisions currently derives the bleed from the mass ratio rather than reading this field directly.

Below ramThreshold but above 30 u/s, a separate contactBleed = 1 - clamp(shipFrac * 0.8, 0.1, 0.7) applies — gentler decel for low-speed bumps.

T-bone label

If the enemy sits in the ship’s forward arc (within ~40° of the heading vector) at the moment of the ram, the hit is tagged as a T-bone: spawns the T-BONED damage label and fires tbone_hit with the tbone payload. Off-axis rams still fire tbone_hit but with the ram payload — used by juice / effect systems to distinguish a clean front-axis hit from a glancing side-swipe.

  • ramThreshold, ramDamageLo, ramDamageHi, ramSpeedBleed, meleeMult, contactCooldown — all on ShipDef (data/ships.ts).
  • shipClass and shipScale — feed shipMass (heavy=3, medium=2, light=1, multiplied by scale) which drives push-apart and bleed.
  • Enemy mass derives from collision radius via ENEMY_MASS_SCALE = 0.07.