Magnet Pickup / Global Magnet Event
Map-wide XP vacuum — a single “magnet” trigger flips every active XP orb on the map into exponential lock-on toward the ship and a golden magnet icon flashes above the ship as visual confirmation. The “vacuum up the field” beat that clears anything the player let drift off-screen.
The trigger primitive
The only entry point is triggerGlobalMagnet() in engine/world/xp-orbs.ts. It is dead simple — it walks every active orb in the SoA pool and:
- Sets the
_collectedbit (_flags[i] |= 2) on the orb, which the per-frame magnet update reads as “skip layer 1 / layer 2, go straight to layer 3.” - Resets the orb’s
_magTimeto 0 (so the exponential acceleration ramp restarts cleanly from this instant). - Zeroes the orb’s velocity (
_vx,_vy→ 0) so any leftover spawn-burst momentum cannot fight the lock-on.
After the flag flips, the existing per-orb update loop in xp-orbs.ts:325-335 handles the rest — layer 3 lock-on multiplies acceleration by 200 * 3.0^_magTime per second, capped at 95% of the remaining distance per frame, so the orb arrives at the ship within ~0.5-1 second regardless of how far away it spawned.
The pickups.ts PickupSystem.triggerGlobalMagnet(world) wrapper exists only as a thin adapter so legacy callers (boss death, magnet events, dev tooling, tests) don’t have to import the SoA module directly. It delegates to the same function.
What fires it
Three distinct callers in the codebase invoke triggerGlobalMagnet():
1. Magnet world event completion
The canonical case. In engine/bridge.ts:2586, when EventManager.update() returns a completed event whose cev.type === 'magnet', bridge calls xpOrbs.triggerGlobalMagnet() and sets game._magnetIconTimer = 1.5 (1.5 seconds of UI display). This is the world-event reward path that ships alongside levelup, weapon, and artifact event rewards — see wiki/gameplay/events.md for the full event reward pipeline.
The event itself is the “pickup” framing — the player walks through the magnet event marker (which behaves like every other completable world event: charge / defend / race / instant), and on completion the global magnet fires once.
2. Scrap Pile prop-break synergy (15% chance)
Defined in engine/world/props.ts:480. When the player breaks a scrap_pile prop, there is a SCRAP_MAGNET_CHANCE = 0.15 roll. On success, spawnScrapMagnetPulse() fires (props.ts:830):
- Draws a grey magnet-pulse ring (10 dust particles travelling inward toward the ship from 60-90 px out) to sell the “buff lands on you” beat even when no orbs are nearby to vacuum.
- Calls
xpOrbs.triggerGlobalMagnet()— same primitive, same effect on the orb field. - Fires the
extraction_doneaudio cue. - Records
prop_break:scrap_magnet_pulseto telemetry with the live orb count.
Note: this synergy does not flash the magnet icon (it has no _magnetIconTimer write) — the in-world inward-particle ring is the feedback channel. Scrap Pile is the workhorse prop tier (densityMult 1.0), so the 15% rate keeps the proc feeling like a found bonus rather than a routine drop.
3. Boss death
The xp-orbs source comment (xp-orbs.ts:140) names “boss death” as a caller. The actual call site lives in the boss-death cinematic dispatch; the effect is identical — every active orb on the map locks onto the player as part of the boss-clear payoff alongside the standard chest / artifact drops.
The magnet icon UI
A single golden magnet icon flashes above the ship for 1.5 seconds whenever the magnet-event path completes. Rendering lives in engine/rendering/hud.ts:2451-2510:
- Reads
game._magnetIconTimer(game-state field defined inengine/core/types.ts:365, initialised inengine/core/state.ts:185). - Position: screen-center X,
H * 0.32Y (just above the ship in normal play). - Size: 28 px (scaled by
uiScale). - Shape: drawn procedurally — black rectangular arms with red tips (classic horseshoe-magnet silhouette), white radial-gradient glow behind it.
- Animation: 70 ms fade-in, hold at full alpha, 20% fade-out tail, multiplied by an 18 Hz sine flash for the “active suction” feel.
The timer decays in bridge.ts:3270-3273 (game._magnetIconTimer = Math.max(0, _magnetIconTimer - dt) per frame). When it hits 0 the HUD branch is skipped.
The icon is intentionally only wired to the magnet-event path. Scrap-pile pulses and boss-death pulses use their own in-world feedback (particle ring / boss-clear cinematic) so the icon stays a clean “an explicit magnet reward landed” signal.
Cross-references
engine/world/xp-orbs.ts— the primitive plus all three magnet layers (snap-on / base pull / exponential lock-on).engine/world/pickups.ts— thinPickupSystemadapter.engine/bridge.ts:2586— magnet-event completion → primitive + icon timer.engine/world/props.ts:830— Scrap Pile synergy.engine/rendering/hud.ts:2451— magnet icon render.wiki/gameplay/events.md— full event reward pipeline (where magnet sits alongside levelup / weapon / artifact).wiki/gameplay/concepts/destructibles.md— Scrap Pile and the other prop-break synergies.