VFX Spell Effects — Game-Quality Visual Effects for Apple Platforms

SkillAI & models

Build game-quality spell VFX on Apple platforms with practical recipes for smoke, lightning, particles, bloom, layering, and stable 60fps performance.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the VFX Spell Effects — Game-Quality Visual Effects for Apple Platforms skill

What this skill tells your AI

The instructions your AI receives, as published by lev-os/agents in skills-db/design-ux/vfx-spell-effects/SKILL.md and read by ahel’s review.

Decision Tree

What effect do you want?
|
|-- Smoke / Fog / Clouds?
|   |-- Soft procedural blobs? -> Canvas noise blobs (like OCMiniOrb smoke)
|   |-- Volumetric 3D fog? -> Ray marching (Metal fragment shader)
|   |-- Quick particle smoke? -> SpriteKit SKEmitterNode or Vortex
|   \-- Fluid dynamics? -> Metal compute (Navier-Stokes, advanced)
|   -> See: references/smoke-fog.md
|
|-- Lightning / Electricity?
|   |-- Single bolt? -> Midpoint displacement (most practical)
|   |-- Smooth arc? -> Lissajous curves
|   |-- Branching tree? -> Recursive fractal (Lichtenberg figures)
|   \-- Glow on bolt? -> Additive bloom post-process
|   -> See: references/lightning-electricity.md
|
|-- Particles (fire, sparks, magic)?
|   |-- Quick preset? -> Vortex (SwiftUI-native)
|   |-- 2D overlay? -> CAEmitterLayer or SpriteKit
|   |-- Full GPU control? -> Metal compute shader pipeline
|   |-- SwiftUI-only? -> Canvas + TimelineView
|   -> See: references/particles.md
|
|-- Bloom / Glow / Post-processing?
|   |-- SwiftUI quick glow? -> .shadow() + .blur() stacking
|   |-- HDR bloom pipeline? -> Threshold -> Gaussian blur -> Composite (Metal)
|   |-- Per-object glow? -> Render to texture + blur + additive blend
|   -> See: references/bloom-glow.md
|
|-- Composing a full spell?
|   |-- State machine? -> .idle -> .charging -> .casting -> .impact -> .dissipating
|   |-- Layer ordering? -> base + particles + glow + screen shake + audio
|   |-- Audio-reactive? -> Bind intensity to RMS amplitude
|   -> See: references/spell-composition.md
|
\-- Performance issue?
    |-- Dropped frames? -> Check particle count, blur radius, draw calls
    |-- GPU bottleneck? -> Profile with Xcode GPU debugger
    |-- CPU bottleneck? -> Move particle update to compute shader
    -> See: Performance Budget Rules below

Quick Reference

EffectBest APIApproachRef
Soft smoke blobsSwiftUI CanvasNoise-offset circles + alpha blendreferences/smoke-fog.md
Volumetric fogMetal fragmentRay march density field, 4-8 samplesreferences/smoke-fog.md
Lightning boltSwiftUI Canvas / MetalMidpoint displacement, 4-5 levelsreferences/lightning-electricity.md
Branching lightningCanvas + recursionFractal tree, angle +/-30degreferences/lightning-electricity.md
Fire particlesVortex / SpriteKitPre-built emitter, warm palettereferences/particles.md
Magic sparksMetal computeGPU particle pool, 500-2000 particlesreferences/particles.md
Impact explosionVortex + CanvasBurst emit + radial force + fadereferences/particles.md
Object glowSwiftUI .shadowStacked .shadow(radius:) callsreferences/bloom-glow.md
HDR bloomMetal render passThreshold + separable Gaussian + compositereferences/bloom-glow.md
Full spellComposite patternState machine + layer stack + timelinereferences/spell-composition.md

Performance Budget Rules (NON-NEGOTIABLE)

  1. 16ms frame budget -- Target 60fps. All VFX layers combined must complete within 16ms GPU + CPU time.
  2. Particle limits -- Mobile: 500-2000 active particles max. Desktop: up to 10,000 with compute shader. Never allocate mid-frame.
  3. Blur radius cap -- .blur(radius:) over 20pt on mobile tanks performance. Use downscaled render targets for large blurs.
  4. Draw call budget -- Keep under 10 draw calls for VFX overlay. Use Canvas (1 draw call per layer) or instanced rendering.
  5. Compute shader particles -- Mandatory for >500 particles. CPU-side for loops over particle arrays cause frame drops on mobile.
  6. Ray march samples -- 4-8 samples per ray for real-time fog. More than 12 is offline-quality territory.
  7. Bloom passes -- 3-5 blur passes with ping-pong buffers. Downscale to half/quarter resolution for blur.
  8. Pool, never allocate -- Pre-allocate particle arrays. Use recycle-bin pattern (free list). Zero heap allocation during gameplay.
  9. Reduce motion -- @Environment(\.accessibilityReduceMotion) guards ALL effect starts. Provide static fallback.
  10. Profile on device -- Simulator GPU timing is meaningless. Test on oldest supported hardware.

Key Patterns

State Machine for Spell Lifecycle

enum SpellPhase: String, CaseIterable {
    case idle, charging, casting, impact, dissipating
}

@Observable
class SpellState {
    var phase: SpellPhase = .idle
    var intensity: Float = 0.0  // 0.0-1.0
    var elapsed: TimeInterval = 0.0

    func advance(dt: TimeInterval) {
        elapsed += dt
        switch phase {
        case .charging:   intensity = min(1.0, Float(elapsed / 0.5))
        case .casting:    intensity = 1.0
        case .impact:     intensity = max(0.0, 1.0 - Float(elapsed / 0.3))
        case .dissipating: intensity = max(0.0, 0.5 - Float(elapsed / 0.6))
        case .idle:       intensity = 0.0
        }
    }
}

Canvas-Based Effect Layer

struct EffectLayer: View {
    let state: SpellState
    @Environment(\.accessibilityReduceMotion) private var reduceMotion

    var body: some View {
        if !reduceMotion && state.phase != .idle {
            TimelineView(.animation(minimumInterval: 1.0 / 60.0)) { ctx in
                Canvas { context, size in
                    // Draw effect here -- single draw call
                    let t = ctx.date.timeIntervalSinceReferenceDate
                    drawEffect(context: context, size: size, time: t, intensity: state.intensity)
                }
            }
        }
    }
}

Midpoint Displacement Lightning (Minimal)

func generateBolt(from: CGPoint, to: CGPoint, levels: Int = 5, jaggedness: CGFloat = 0.4) -> [CGPoint] {
    var points = [from, to]
    for level in 0..<levels {
        var newPoints = [points[0]]
        let scale = jaggedness * pow(0.5, CGFloat(level))
        for i in 0..<(points.count - 1) {
            let mid = CGPoint(
                x: (points[i].x + points[i+1].x) / 2 + CGFloat.random(in: -1...1) * scale * from.distance(to: to),
                y: (points[i].y + points[i+1].y) / 2 + CGFloat.random(in: -1...1) * scale * from.distance(to: to)
            )
            newPoints.append(mid)
            newPoints.append(points[i+1])
        }
        points = newPoints
    }
    return points
}

References

FileContent
references/smoke-fog.mdNoise-based smoke, ray marching fog, Canvas blobs, Metal fluid dynamics
references/lightning-electricity.mdFractal midpoint displacement, Lissajous arcs, branching trees, glow rendering
references/particles.mdAPI comparison, GPU particle architecture, emission patterns, pool pattern, Vortex presets
references/bloom-glow.mdHDR bloom pipeline, threshold/blur/composite, Metal + SwiftUI integration
references/spell-composition.mdState machine, layer stacking, audio-reactive binding, timing curves, full spell examples

External Resources

Related Skills

  • orb -- OCMiniOrb implementation (smoke layer, fractal layer, glow compositing)
  • swiftui-animation -- Animation curves, springs, Metal shader integration in SwiftUI
  • swiftui-performance-audit -- Runtime GPU/CPU profiling when effects cause frame drops

Technique Map

  • Effect type routing — Smoke/fog, lightning, particles, bloom, full spell; because each has distinct API and approach.
  • State machine for lifecycle — idle→charging→casting→impact→dissipating; because spells have phases; state drives intensity.
  • 16ms frame budget — 60fps non-negotiable; because dropped frames break immersion.
  • Particle limits — 500-2000 mobile; 10k desktop with compute; because CPU particle loops tank mobile.
  • Canvas for overlay — Single draw call per layer; because draw call budget matters.
  • Pool, never allocate — Recycle bin pattern; because mid-frame allocation causes hitches.
  • Reduce motion guard — Static fallback when accessibilityReduceMotion; because necessary for compliance.
  • Midpoint displacement — For lightning; 4-5 levels; because simple, effective, performant.

Technique Notes

References: smoke-fog, lightning-electricity, particles, bloom-glow, spell-composition. APIs: Vortex, SpriteKit, Metal compute, Canvas. Profile on device; simulator GPU timing meaningless. Related: orb, swiftui-animation, swiftui-performance-audit.


Prompt Architect Overlay

Role Definition: VFX spell effects builder. Game-quality smoke, lightning, particles, bloom, spell composition. 60fps performance rules. Apple platforms.

Input Contract: Accepts effect type (smoke, lightning, fire, glow, full spell), target API (Vortex, Metal, Canvas), or performance issue. Platform, particle count, style.

Output Contract: Decision tree result. Quick reference row. Code pattern (state machine, canvas layer, midpoint displacement). Performance budget rules. Reference to deep-dive. Related skills.

Edge Cases & Fallbacks: If performance issue→check particle count, blur radius, draw calls. If >500 particles→compute shader mandatory. If bloom→downscale for blur passes. If reduce motion→guard all effects; provide static fallback.

Signals

GitHub stars
22
Forks
2
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
vfx-spell-effects
Source
github.com/lev-os/agents