# Mobile Animation & Touch Engineering: Lessons Learned

This knowledge base documents battle-tested algorithms, mathematical formulas, mobile browser quirks, and animation techniques discovered across mobile web development. Autonomous agents should consult this document to avoid reinventing solved solutions.

---

## 1. Mobile Touch, Pointer & Gesture Engineering

### A. The Pointer Event Unification & Passive Listener Trap
* **The Problem:** Modern mobile browsers (iOS Safari, Android Chrome) generate both touch events and simulated mouse events. Using `touchstart` with `e.preventDefault()` inside passive event listeners throws console warnings and can cause input delays or broken scrolling.
* **The Solution:** Use Pointer Events (`pointerdown`, `pointermove`, `pointerup`, `pointercancel`) attached directly to the canvas element, coupled with CSS touch rules:
  ```css
  canvas {
    touch-action: none;
    -webkit-touch-callout: none;
    user-select: none;
    -webkit-user-select: none;
  }
  ```
* Call `canvas.setPointerCapture(e.pointerId)` on `pointerdown` so dragging outside the canvas or over browser bezels continues tracking until released.

### B. High-DPI Canvas & Thermal Throttling
* **The Problem:** High-end phones (e.g. iPhone 15 Pro, Galaxy S24) report `devicePixelRatio = 3.0` or higher. Rendering a 2D canvas at $3x$ resolution forces the mobile GPU to fill $1179 \times 2556 \times 3 \approx 9$ million pixels per frame, resulting in rapid phone overheating and aggressive 30 FPS thermal throttling after 60 seconds of play.
* **The Solution:** Clamp canvas internal resolution scaling:
  ```javascript
  const dpr = Math.min(window.devicePixelRatio || 1, 2.0);
  canvas.width = Math.round(rect.width * dpr);
  canvas.height = Math.round(rect.height * dpr);
  ctx.scale(dpr, dpr);
  ```
  This preserves retina crispness while saving over 50% of mobile GPU fill-rate.

### C. Safe-Area Inset Management (Notch & Home Indicator)
* When rendering UI overlays in mobile web apps, account for camera cutouts and bottom gesture bars:
  ```css
  padding-top: max(16px, env(safe-area-inset-top));
  padding-bottom: max(16px, env(safe-area-inset-bottom));
  padding-left: max(16px, env(safe-area-inset-left));
  padding-right: max(16px, env(safe-area-inset-right));
  ```

---

## 2. Procedural Animation & Kinematics Math

### A. Damped Harmonic Spring Oscillators
* For smooth, organic UI transitions, camera follow, and character wobble, use critically or slightly underdamped springs instead of linear interpolation (`lerp`).
* **Semi-Implicit Euler Spring Equation:**
  ```javascript
  updateSpring(current, target, velocity, stiffness = 180, damping = 14, dt) {
    const force = -stiffness * (current - target);
    const dampingForce = -damping * velocity;
    const acceleration = force + dampingForce;
    velocity += acceleration * dt;
    current += velocity * dt;
    return { current, velocity };
  }
  ```
* **Parameters:**
  * *Snappy UI Bounce:* `stiffness = 260, damping = 18`
  * *Gelatinous Jiggle:* `stiffness = 120, damping = 8`
  * *Smooth Camera Follow:* `stiffness = 60, damping = 12`

### B. Volume-Preserving Squash and Stretch
* In 2D animation, when an object squashes or stretches along its movement axis, its total apparent volume must remain constant:
  $$S_{\text{primary}} \times S_{\text{secondary}} = 1.0$$
* Given a stretch factor $s$ along the velocity or impact normal vector:
  $$s_x = s, \quad s_y = \frac{1}{\sqrt{s}}$$
* **Velocity Stretching Recipe:**
  ```javascript
  const speed = Math.hypot(vx, vy);
  const maxStretch = 1.6;
  const stretch = 1.0 + Math.min(speed / maxSpeed, 1.0) * (maxStretch - 1.0);
  const squash = 1.0 / stretch;
  
  ctx.save();
  ctx.translate(x, y);
  ctx.rotate(angle);
  ctx.scale(stretch, squash);
  // Render character centered at (0, 0)
  ctx.restore();
  ```

### C. Procedural Eye Tracking & Secondary Animation
* Having eyes look in the direction of intended motion or drag intent gives instant personality.
* Anchor the pupil with a small offset proportional to the aim/velocity vector, clamped inside the eyeball radius:
  ```javascript
  const aimAngle = Math.atan2(targetY - eyeY, targetX - eyeX);
  const pupilOffset = Math.min(distance * 0.15, maxPupilRadius);
  const px = eyeX + Math.cos(aimAngle) * pupilOffset;
  const py = eyeY + Math.sin(aimAngle) * pupilOffset;
  ```

---

## 3. Pure WebAudio on Mobile Devices

### A. Mobile Audio Context Unlock Trick
* Mobile browsers require an explicit user gesture (`pointerdown` or `touchstart`) before an `AudioContext` is permitted to produce sound.
* **Bulletproof Unlock Sequence:**
  ```javascript
  let audioCtx = null;
  function initAudio() {
    if (!audioCtx) {
      const AudioContext = window.AudioContext || window.webkitAudioContext;
      audioCtx = new AudioContext();
    }
    if (audioCtx.state === 'suspended') {
      audioCtx.resume();
    }
  }
  window.addEventListener('pointerdown', initAudio, { once: false });
  ```

### B. Ephemeral Sound Recycling & Zero Garbage Collection
* Reconnect and release nodes properly on mobile to prevent memory leaks during rapid action:
  ```javascript
  function playTone(freq, duration, type = 'sine', gainVal = 0.1) {
    if (!audioCtx || audioCtx.state !== 'running') return;
    const now = audioCtx.currentTime;
    const osc = audioCtx.createOscillator();
    const gain = audioCtx.createGain();
    
    osc.type = type;
    osc.frequency.setValueAtTime(freq, now);
    
    gain.gain.setValueAtTime(gainVal, now);
    gain.gain.exponentialRampToValueAtTime(0.0001, now + duration);
    
    osc.connect(gain);
    gain.connect(audioCtx.destination);
    
    osc.start(now);
    osc.stop(now + duration);
    
    osc.onended = () => {
      osc.disconnect();
      gain.disconnect();
    };
  }
  ```

---

## 4. Mobile Haptic Feedback API

### A. Safe Haptic Vibration Integration
* Always guard `navigator.vibrate` calls with feature detection and user preference checks:
  ```javascript
  export const Haptics = {
    enabled: true,
    tap() {
      if (this.enabled && 'vibrate' in navigator) navigator.vibrate(15);
    },
    impact() {
      if (this.enabled && 'vibrate' in navigator) navigator.vibrate([25, 20, 25]);
    },
    success() {
      if (this.enabled && 'vibrate' in navigator) navigator.vibrate([40, 30, 80]);
    }
  };
  ```

---

## 5. Mobile 2D Canvas Performance Safeguards

### A. Object Pooling for Zero Garbage Collection
* In mobile JavaScript engines, frequent allocation of small objects (`new Particle()`, `new Vector()`) inside the 60Hz loop causes periodic V8/JavaScriptCore Garbage Collection pauses (15–40ms spikes), resulting in perceptible micro-stutters.
* **The Rule:** Pre-allocate fixed pools for particles, floating text items, and temporary vectors. Re-initialize existing objects in place instead of creating new instances.

### B. Avoiding `ctx.shadowBlur` on Mobile
* While desktop GPUs can handle `shadowBlur = 15` on glowing laser bolts, mobile tile-based deferred renderers (TBDRs) incur severe penalty from Gaussian blur passes.
* **The Mobile Solution:**
  1. Render glows using dual-pass alpha strokes (e.g., a thick 8px semi-transparent outer stroke followed by a 2px opaque inner stroke).
  2. If `shadowBlur` is used, set `ctx.shadowBlur = 0` immediately after drawing the single glowing element.
