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):
| Field | Meaning |
|---|---|
_startX, _startY | Launch position. For most weapons this is the ship; the perpendicular-layout cannon offsets spawn along the perpendicular axis. |
_landX, _landY | Predicted landing position (lead + scatter applied). |
_arcTime | Total flight duration in seconds. From def.arcTime, default 1.0. |
_arcProgress | Flight progress 0 → 1. Lifetime parameter. |
_arcHeightMult | Override for the 0.45 height-scaling factor. Cannon uses 0.25 for a low lob. |
_arcHeight | Computed peak-height offset for rendering. Visual only — collision still resolves at (b.x, b.y). |
Flight loop
Each update tick:
- Advance progress:
_arcProgress += dt / _arcTime. - If
_arcProgress >= 1.0, snapb.x, b.yto_landX, _landY, setb.l = 0to trigger theaoe_finishonDeathhandler, and return. - Otherwise interpolate the ground position linearly:
b.x = _startX + (_landX - _startX) * t,b.y = _startY + (_landY - _startY) * t. - Compute the visual height offset:
_arcHeight = sin(t * π) * max(minHeight, flightDist * heightMult). The sine curve peaks at the midpoint, longer shots arc higher, andminHeightis30for low-arc weapons (heightMult < 0.35) or80otherwise. - Refresh
b.lto at least0.5so generic lifetime decay cannot kill the shell mid-flight.
Two consequences fall out of this:
- Lifetime is tied to
_arcProgress, notb.l. Raw range and the normal lifetime tick are irrelevant — a shell lives exactly_arcTimeseconds 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 forlandY.scatterRadiusreads fromdef.scatterRadius(level-scaled), default60. - Perpendicular layout. Triggered by
def.perpendicularLayoutwith more than one shell (cannon). Shells line up along the perpendicular to aim, spaced bydef.perpSpacing(default18px), 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_landdeath handler to spawn a persistent fire zone, and theplasma_mortar_trailparticle behavior in flight). - Cannon (perpendicular layout, low
_arcHeightMultfor 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.