Skip to main content

Teleport VFX System (February 2026)

Commits: 7bf0e14, ceb8909, 061e631
PR: #939
Author: dreaminglucid

Overview

Complete rewrite of the teleport visual effects system with object pooling, multi-phase animation, and TSL shader materials. Replaces the simple beam/ring/particles effect with a spectacular multi-component sequence featuring ground rune circles, dual beams with elastic overshoot, shockwave rings, helix spiral particles, burst particles with gravity, and dynamic point lighting.

Visual Design

Phase Timeline

The effect runs for 2.5 seconds with 4 distinct phases:

Components

Structural Elements (7 meshes per pool entry):
  1. Ground Rune Circle - Procedural canvas texture with concentric circles, radial spokes, and triangular glyphs
  2. Base Glow Disc - Pulsing cyan glow at ground level
  3. Inner Beam - White→cyan gradient cylinder with elastic height curve
  4. Outer Beam - Light cyan→dark blue cylinder, delayed 0.03s
  5. Core Flash - White sphere that pops at eruption (0.20-0.22s)
  6. Shockwave Ring 1 - Expanding ring with easeOutExpo (scale 1→13)
  7. Shockwave Ring 2 - Second ring delayed 0.024s (scale 1→11)
Particle Systems:
  • Helix Particles (8): 2 strands of 4, spiral upward with decreasing radius
  • Burst Particles (6): 3 white + 3 cyan, launched with gravity simulation
Lighting:
  • Point Light: Dynamic intensity (0→5.0 at eruption, fades to 0)

Technical Implementation

Object Pooling

Pool Size: 2 concurrent effects (both duel agents can teleport simultaneously) Zero Allocations: All materials compiled during init(), no pipeline compilations at spawn time Pool Entry Structure:

Shared Resources

Geometries (allocated once, shared by all pool entries):
  • particleGeo: PlaneGeometry(1, 1)
  • beamInnerGeo: CylinderGeometry with bottom pivot
  • beamOuterGeo: CylinderGeometry with bottom pivot
  • discGeo: CircleGeometry(0.5, 16)
  • runeCircleGeo: CircleGeometry(1.5, 32)
  • shockwaveGeo: RingGeometry(0.15, 0.4, 24)
  • sphereGeo: SphereGeometry(0.4, 8, 6)
Textures (allocated once):
  • runeTexture: CanvasTexture with procedural rune pattern
Particle Materials (2 total, shared by all pool entries):
  • particleCyanMat: Cyan glow for helix particles
  • particleWhiteMat: White glow for burst particles

TSL Shader Materials

Particle Glow Material (no per-instance opacity):
Particles fade by scaling down - glow pattern handles soft edges. Beam Material (vertical gradient + scrolling pulse):
Structural Glow Material (per-effect opacity uniform):

Animation Curves

Beam Elastic Curve (Hermite interpolation):
Creates elastic “pop” effect - beams overshoot to 1.3× height at 35% progress, then settle to 1.0×.

Easing Functions

  • Gather Phase: easeOutQuad (smooth fade-in)
  • Fade Phase: easeInQuad (smooth fade-out)
  • Shockwaves: easeOutExpo (fast expansion, slow deceleration)

Particle Behavior

Helix Spiral Particles

Count: 8 (2 strands of 4 particles each) Motion:
Recycling: When height > 16, particle resets to bottom (continuous spiral effect) Fade: Via scale (not opacity) - baseScale * heightFade

Burst Particles

Count: 6 (3 white + 3 cyan) Motion:
Fade: Via scale - baseScale * (1.0 - localTime / 1.8) Culling: Hidden when below ground (y < -0.5)

Event Handling

Suppressing Effects

Teleport effects can be suppressed via suppressEffect flag:
Use Cases:
  • Mid-fight proximity corrections (duel system)
  • Frequent position adjustments
  • Invisible teleports

Network Propagation

The suppressEffect flag is forwarded through the network stack:

Duplicate Effect Prevention

Fixed: Removed duplicate PLAYER_TELEPORTED emits that caused ghost effects:
  1. PlayerRemote.modify(): Removed emit (position may be stale)
  2. ClientNetwork.onPlayerTeleport(): Only emits for remote players (local player already emitted in localPlayer.teleport())

Performance Characteristics

Spawn Cost

  • Before: ~20 material compilations, ~20 geometry allocations
  • After: 0 allocations (grab from pool, reset state)

Update Cost

  • Before: ~20 material opacity updates, ~20 particle position updates
  • After: 7 uniform updates, 14 particle position updates (phase-gated)

Memory

  • Before: ~20 materials × 2 concurrent effects = 40 materials
  • After: 7 materials × 2 pool entries + 2 shared particle materials = 16 materials

Draw Calls

  • Before: ~20 draw calls per effect
  • After: ~20 draw calls per effect (same, but zero allocation overhead)

Debugging

Disable Cache for Testing

Visual Debugging

Performance Profiling

ClientTeleportEffectsSystem

File: packages/shared/src/systems/client/ClientTeleportEffectsSystem.ts Responsibilities:
  • Listen for PLAYER_TELEPORTED events
  • Spawn effects from object pool
  • Update all active effects each frame
  • Deactivate effects when complete

DuelOrchestrator

File: packages/server/src/systems/StreamingDuelScheduler/managers/DuelOrchestrator.ts Changes:
  • Removed suppressEffect: true from cleanup teleports (exit VFX now plays)
  • Victory emote delayed 600ms to prevent combat cleanup override
  • Emote reset to “idle” in stopCombat() so wave stops when agents teleport out

ServerNetwork

File: packages/shared/src/systems/client/ClientNetwork.ts Changes:
  • Forward suppressEffect through network packets
  • Removed duplicate PLAYER_TELEPORTED emits

Migration Guide

For Developers

No migration needed - changes are fully backward compatible. Triggering teleport effects:

For Asset Creators

Rune Circle Texture: Procedurally generated via canvas - no external assets needed. Colors: Hardcoded cyan/white theme - modify in createRuneTexture() and material factories if needed.

Known Issues

Beam Base Clipping

Symptom: Beam base clips through floor on uneven terrain. Cause: Beam geometry starts at y=0, floor may be below. Fix (commit ceb8909): Fade beam base to prevent VFX clipping through floor:

Duplicate Teleport VFX

Symptom: 3 teleport effects play when agents exit arena (should be 2). Cause: Race condition - clearDuelFlagsForCycle() called before cleanupAfterDuel() teleports, causing DuelSystem.ejectNonDuelingPlayersFromCombatArenas() to emit spurious extra teleport. Fix (commit 7bf0e14): Defer flag clear until cleanup teleports complete:

References