What this is

The manual-fire override is a per-weapon mode that takes a weapon out of the auto-aim resolver loop and instead fires it only when the player triggers the slot. Each of the four weapon slots can be set independently to auto (default) or manual from the in-game settings cog; the choice is persisted to localStorage under the key ss_weapon_modes and reapplied to weapons that are picked up later in the run.

When manual mode is active for a slot, two coordinated things happen on the next fire: the bridge bypasses the auto-aim resolver and computes a fixed aim angle from ship facing alone, and a module-level flag is raised around the fire call so the spawn pipeline can tag every bullet produced by that fire with a _manualFire marker. Downstream behaviors then read that marker and disable their normal target-seeking logic.

FieldValueWhere it lives
Per-slot mode'auto' or 'manual'weapon.fireMode
Player trigger flagPer-weapon boolean, cleared every frameweapon._manualTrigger
Module-level fire flagSet true around manual fire call, cleared after_nextFireIsManual in engine/weapons/weapons.ts
Per-bullet markerStamped at spawn from the module flagbullet._manualFire
Bridge entry pointSets _manualTrigger = true on the slot’s weaponmission.fireWeapon(slotIndex)
Persistence keylocalStoragess_weapon_modes
Default slot modeAuto{ fireMode: 'auto' } (all four slots)

The player trigger is non-buffering: if the cooldown is not ready when the player presses fire, the trigger flag is cleared on that frame and the press is silently discarded. The next press is only honored after the cooldown has cleared.

How it overrides auto-fire

The override happens at three points in the fire pipeline, all gated on the manual flag.

StageAuto-mode behaviorManual-mode behavior
Target acquisitionBridge calls WeaponManager.getAutoAimAngle and routes the resulting angle into the fire call. If no enemy is found, the fire abortsBridge skips the resolver. Aim angle is computed as ship.angle + (defaultAngle * pi / 180) and the fire call runs even if no enemy exists
Fire dispatchCooldown timer drives the call; the call happens every tick the timer is ready and a target existsCall only runs on frames where _manualTrigger is set and the cooldown timer is at zero. Warmup frames continue ticking once a manual fire has begun a warmup
Bullet spawn tag_manualFire field on each spawned bullet is set to falseBridge wraps the fire call with setManualFireFlag(true) / setManualFireFlag(false). spawnBullet stamps bullet._manualFire = _nextFireIsManual onto every bullet produced
Spawn-time homing scrubBullet keeps its declared homingStrengthIf homingStrength > 0 and the module flag is set, spawn code zeroes the bullet’s homingStrength outright (belt-and-suspenders alongside the _manualFire tag)

Once a bullet carries _manualFire = true, every behavior that would normally steer or re-acquire is suppressed.

BehaviorAuto-mode actionManual-mode action
Homing steering (homing behavior in bullets.ts)Per-tick steer toward closest enemy by homingStrength rad/secEarly-out at top of update — bullet flies straight in its fired direction for its entire lifetime
Manual-fire homing ramp (when the spawn-time scrub did not zero out homing)homingMult = 1 over the full lifetimehomingMult = 0 at launch, ramps linearly to 1 at 50% of maxLifetime, then stays at 1
Burst-fire sub-shot targeting (burst_fire)Lazily acquires and re-acquires a _burstTarget between sub-shots, aims each sub-shot at the lockSkips target acquisition; every sub-shot uses the original _burstAimAngle from the cast
Chain-arc first-hit search (chain_arc in collision-resolver.ts)Picks the closest enemy inside firstTargetRange from the shipSame closest search, but candidates are filtered to a 90° cone (cosine ≥ cos(pi/4)) centered on the arc’s aim angle. Enemies outside the cone are skipped
Arc-mortar target prediction (fireArcMortar)Scans world.enemies with the weapon’s target mode and leads the chosen enemy’s velocity over arcTimeSkips the scan entirely. Landing point is pinned to ship + cos/sin(aimAngle) * min(acquireRange * 0.8, 200)

The composite effect is: no auto-target lock, no per-shot re-aim during travel, no target re-acquisition between burst sub-shots, and a fixed aim angle that comes from ship facing plus the weapon’s spec-defined offset.

Which weapons honor it

Manual mode is a property of the weapon slot, not of the weapon definition — any weapon placed in a manual-mode slot will receive the _manualFire tag on its bullets and will skip the resolver. What differs is how each weapon’s behavior responds to that tag.

Weapon familyEffect of manual mode
Standard projectile weapons (no homing behavior, no burst_fire, no chain_arc)Aim direction switches from auto-aim to ship facing + defaultAngle. Projectile travel is unchanged
Homing projectiles (homing behavior, e.g. missiles)Bullets fly straight in the fired direction for their entire lifetime — the early-out in the homing update suppresses all steering
Burst weapons (burst_fire, e.g. Revolver)Sub-shots no longer track a locked target — every sub-shot in the burst uses the cast’s original aim angle
Chain-arc weapons (chain_arc)First-hit search is constrained to a 90° forward cone around the manual aim angle instead of picking the global nearest enemy
Arc mortars (fireArcMortar)Landing point is fixed at ship + aim direction * min(acquireRange * 0.8, 200), no lead prediction
Rear-facing weapons (defaultAngle = 180, e.g. mines)Manual aim angle becomes ship.angle + pi, so the weapon fires behind the ship rather than at the nearest auto-aim target
Always-forward defensive weapons (e.g. Barrier)Already fire on ship facing in auto mode. Manual mode adds the per-trigger gating but does not change aim direction

The aim-angle offset is read from weapon.defaultAngle (in degrees, copied from the weapon spec’s defaultAngle at activation time, defaulting to 0). A weapon with no declared defaultAngle fires straight ahead in manual mode regardless of where the auto-aim resolver would have aimed.

UX context

Manual mode is exposed per-slot through the in-game settings cog and persisted across sessions.

SurfaceBehavior
Slot mode storagelocalStorage[ss_weapon_modes] — array of four { fireMode } records
Hot-swapWhen a new weapon is picked up into a slot whose mode is unset, the saved slot mode is applied on the next tick
Mobile triggerTap on the on-screen weapon slot icon — hitTestWeaponSlot resolves the slot index and calls mission.fireWeapon(slotIndex) if that slot is in manual mode. Taps on auto-mode slots are ignored and fall through to ship movement
Desktop triggerKeyboard keys 1 / 2 / 3 / 4 map to slots 0–3 and call mission.fireWeapon(slotIndex). The same keys are reused for reward-card selection when the reward screen is active — that consumer takes priority
Trigger bufferingNone. _manualTrigger is cleared every frame regardless of whether the fire was eligible
Stall interactionWhen the ship is stalled (heat overload), the per-frame loop clears _manualTrigger on every weapon so queued triggers do not fire on recovery
Auto-mode interactionAuto-mode slots run their normal cooldown-driven fire path each tick, independent of the manual triggers on other slots. Mode is per-slot, not per-ship