Artifact Pickup Flow

Collecting an artifact box opens the reward UI. The box is the on-field handoff between the artifact-type world event and the upgrade picker — artifact-type events that complete with the player owning 2+ artifacts spawn a box, ship-proximity collects it, and the resulting reward family artifact_box rolls 2 upgrade cards from owned non-legendary artifacts.

Spawn — only from completed artifact events

Boxes live in world.artifactBoxes. The dormant KPM-based timed-drop scheduler (engine/bridge.ts:3413-3462, gated if (false)) is the only other path that could push to this list, so in shipped builds every artifact box originates from one place: an artifact-type world event finishing.

The completed-event handler at bridge.ts:2611-2631 applies two gates before queuing the reward:

  1. Gate 1 — owned count. countOwnedArtifacts() >= 2. Below that the starting-artifact loop is still bootstrapping and the artifact event silently falls through.
  2. Gate 2 — upgradeable pool. canRollArtifactUpgrade() — at least one owned artifact must be below ARTIFACT_TIER_MAX (legendary).

If both gates pass: game.rewardQueue.push({ type: 'artifact_box' }) queues the reward directly (no on-field crate required for this path) — but in practice the artifact-event spawn path constructs a physical artifactBoxes entry at the event center as the player-facing handoff. If either gate fails, the artifact event substitutes a weapon_box chest at cev.x, cev.y instead so the event still pays out.

Pickup — proximity collect with weapon-chest pattern

The artifact-box update loop at bridge.ts:3007-3054 mirrors the weapon-chest collect:

  • Elite-drop pop-and-fly. _updateBoxFlight(ab, rawDt) handles the arc when the box dropped from an elite.
  • Proximity trigger. Math.hypot(ship.x - ab.x, ship.y - ab.y) < 50 + ship.radius — same 50px mandala radius as weapon chests.
  • Fly-to-center animation. On trigger the box flips _collecting=true, _collectDur=0.48 seconds. The ship’s camera position is the target; cubic-eased position lerp + scale shrink to 0.15. Teal #44ffcc and #22aa88 spark particles trail the box and burst at completion. game._weaponChestFreeze=true and game.timeDilation=0 halt the world during the animation.
  • Collect completion. When t >= 1, the box is swap-removed from world.artifactBoxes, Juice.fire('pickup_weapon') plays the chest pickup cue, and game.rewardQueue.push({ type: 'artifact_box' }) queues the reward.

Reward dispatch — artifact_box family

The reward dispatcher at bridge.ts:3085-3092 consumes the queued entry:

choices = rollArtifactChoices(2, 'upgrade_only');

rollArtifactChoices (engine/world/artifacts.ts:1251) in 'upgrade_only' mode:

  • Filters ARTIFACT_DEFS to artifacts the player owns AND that are below ARTIFACT_TIER_MAX. Banished artifact_upgrade|<id> keys are excluded.
  • Picks up to count=2 distinct artifact IDs — never duplicates the same artifact across both cards.
  • Each card carries artifactIsLevelUp: true, the tier-specific statLabel, and the next tier’s rarity name (e.g. 'uncommon''rare').
  • The completion-path gates above guarantee at least 1 upgradeable artifact exists when this branch runs, so the result should never be empty. The function may still return length < count when the player owns exactly one upgradeable artifact — the picker then shows a single forced card with no skip.

Fallback when the upgrade pool can’t fill two cards

The pre-spawn gates only guarantee >= 1 upgradeable artifact. The owned-count-< 2 case is handled before the reward queues — the event substitutes a weapon_box chest at the event center (bridge.ts:2625-2630). So the “falls back to weapon chest if owned < 2” rule is enforced at event-completion time, not at pickup time. Once an artifact_box reward is in the queue, the upgrade-only roll proceeds even if it returns only 1 card.

Reroll behavior

If the player burns a reroll charge while the artifact_box picker is open, bridge.ts:8231-8232 re-runs rollArtifactChoices(2, 'upgrade_only'). An empty roll no-ops without consuming the charge.

Confirmation SFX + level-up animation

When the player picks a card:

  • _rewardFamily === 'artifact_box' routes to SampleSfx.playGong() (bridge.ts:8199-8200) — shared with weapon_box and shooting_star.
  • prepareUpgradeShow(choice, ship, game) snapshots the old artifact tier, applyReward bumps the runtime tier via the artifact grant path, then fadeoutReward kicks the standard fly-to-slot upgrade-show animation.

Source files

  • engine/bridge.ts — completed-event gates (2611-2631), artifact-box update + collect (3007-3054), reward dispatch (3085-3092), reroll (8231-8232), gong SFX (8199-8201).
  • engine/world/artifacts.tsrollArtifactChoices (line 1251), canRollArtifactUpgrade (1209), countOwnedArtifacts (1226), ArtifactRollMode type (1204), ARTIFACT_TIER_MAX legendary cap.
  • See also: crate-drop-system for the full crate taxonomy (artifact boxes vs. weapon chests vs. destructibles).