Arc Flight Model

The arc-mortar flight model is the parabolic ballistic trajectory shared by mortar, plasma-mortar, cannon, and arc-class weapons. Shells do not steer, do not collide in flight, and do not respect raw range — they fly from a fixed launch point to a fixed landing point along a precomputed parabolic visual arc, then detonate on landing via the aoe_finish death handler. The flight is parameterised entirely on launch.

Stored fields

The arc_mortar bullet behavior (engine/weapons/bullets.ts:923) consumes these fields written by fireArcMortar (engine/weapons/weapons.ts:1375):

FieldMeaning
_startX, _startYLaunch position. For most weapons this is the ship; the perpendicular-layout cannon offsets spawn along the perpendicular axis.
_landX, _landYPredicted landing position (lead + scatter applied).
_arcTimeTotal flight duration in seconds. From def.arcTime, default 1.0.
_arcProgressFlight progress 0 → 1. Lifetime parameter.
_arcHeightMultOverride for the 0.45 height-scaling factor. Cannon uses 0.25 for a low lob.
_arcHeightComputed peak-height offset for rendering. Visual only — collision still resolves at (b.x, b.y).

Flight loop

Each update tick:

  1. Advance progress: _arcProgress += dt / _arcTime.
  2. If _arcProgress >= 1.0, snap b.x, b.y to _landX, _landY, set b.l = 0 to trigger the aoe_finish onDeath handler, and return.
  3. Otherwise interpolate the ground position linearly: b.x = _startX + (_landX - _startX) * t, b.y = _startY + (_landY - _startY) * t.
  4. Compute the visual height offset: _arcHeight = sin(t * π) * max(minHeight, flightDist * heightMult). The sine curve peaks at the midpoint, longer shots arc higher, and minHeight is 30 for low-arc weapons (heightMult < 0.35) or 80 otherwise.
  5. Refresh b.l to at least 0.5 so generic lifetime decay cannot kill the shell mid-flight.

Two consequences fall out of this:

  • Lifetime is tied to _arcProgress, not b.l. Raw range and the normal lifetime tick are irrelevant — a shell lives exactly _arcTime seconds and lands exactly where it was told.
  • The shell is not collidable in flight. Bullet ground-position tracks (b.x, b.y) but enemies/destructibles only matter at landing through the blast.

Target lead

Lead prediction runs once at fire time inside fireArcMortar. Target selection respects the weapon’s targetMode:

  • furthest — picks the most-distant eligible enemy (mortar default; targets backline).
  • flanking — scores by perpendicularity to ship facing (cannon).
  • closest — default.

Once a target is chosen, the predicted landing centre is (e.x + e.vx * arcTime, e.y + e.vy * arcTime) — straight-line lead by exactly the flight time. Manual fire skips the search and uses aimAngle * min(acquireRange * 0.8, 200) from the ship as the centre. If no target is found, the same forward-projected centre is used as a fallback.

Landing scatter

After the centre is decided, each shell in the salvo gets its own landing point. Two layouts:

  • Random scatter (default). Per shell: scatterAngle = random() * 2π, scatterDist = random() * scatterRadius. landX = centerX + cos(scatterAngle) * scatterDist, same for landY. scatterRadius reads from def.scatterRadius (level-scaled), default 60.
  • Perpendicular layout. Triggered by def.perpendicularLayout with more than one shell (cannon). Shells line up along the perpendicular to aim, spaced by def.perpSpacing (default 18px), and the launch position is offset by the same perpendicular amount so each shell flies parallel to its neighbours.

Multi-shot procs interact with scatter as a multiplier on shell count: doubleShotChance doubles finalShellCount for the salvo, which means twice as many scatter rolls around the same centre. more_projectiles adds extras the same way.

Landing fix-up

When _arcProgress crosses 1.0, the update routine writes b.x = _landX, b.y = _landY before zeroing b.l. This fix-up matters because the linear interpolation only reaches the exact landing point at t == 1.0; without snapping, the AOE could detonate a frame short. The aoe_finish handler (bullets.ts:831) treats arc-mortar and artillery-rain bullets as landing shells that are exempt from the “no phantom explosion if zero hits” suppression — the landing itself is the intended detonation, not a target-locked hit, so blast damage applies even if no enemy is currently inside the radius.

Where it’s used

The arc_mortar behavior is mounted by:

  • Mortar and its plasma-mortar variant (which adds the plasma_mortar_land death handler to spawn a persistent fire zone, and the plasma_mortar_trail particle behavior in flight).
  • Cannon (perpendicular layout, low _arcHeightMult for a flat trajectory).
  • Arc-class weapons that want a ballistic, called-shot feel without inflight collision.

All variants share the same flight loop, target lead, scatter logic, and landing fix-up — they differ only in the data passed into fireArcMortar and any extra death/trail behaviors layered alongside arc_mortar and aoe_finish.