Skip to content

Adaptive Quality System

Runtime particle count adjustment based on device capabilities.

Overview

The quality system ensures smooth performance across a wide range of devices by dynamically adjusting the particle count at startup.

Detection Inputs

typescript
interface SimulationHeuristicsInput {
  hardwareConcurrency?: number; // CPU cores
  deviceMemory?: number; // Device RAM (GB)
  isFallbackAdapter: boolean; // Software rendering?
  maxStorageBufferBindingSize: number;
  viewportPixels: number; // Width × Height × DPR²
}
InputSourceRange
hardwareConcurrencynavigator.hardwareConcurrency1-64+
deviceMemorynavigator.deviceMemory1-8+ GB
isFallbackAdapteradapter.isFallbackAdapterboolean
viewportPixelsCanvas dimensions × DPR²varies

Scaling Rules

ConditionMax ScaleParticle Count
Fallback adapter40%4,000
RAM ≤ 2 GB45%4,500
RAM ≤ 4 GB65%6,500
CPU cores ≤ 245%4,500
CPU cores ≤ 470%7,000
4K viewport65%6,500
QHD viewport85%8,500
High-end device100%10,000

Quality Tiers

typescript
type SimulationQualityTier = 'low' | 'medium' | 'high';

interface RuntimeSimulationSettings {
  particleCount: number;
  qualityTier: SimulationQualityTier;
  scale: number;
}
TierParticle CountTarget Devices
Low2,500Integrated GPUs, 4GB RAM, mobile
Medium5,000Mid-range laptops, 8GB RAM
High10,000Dedicated GPUs, 16GB+ RAM

Implementation

typescript
function resolveSimulationSettings(
  input: SimulationHeuristicsInput,
  preferredParticleCount?: number
): RuntimeSimulationSettings {
  const target = preferredParticleCount ?? PARTICLE_COUNT;
  let scale = 1.0;

  // Apply heuristics
  if (input.isFallbackAdapter) {
    scale = Math.min(scale, 0.4);
  }
  if (input.deviceMemory !== undefined) {
    if (input.deviceMemory <= 2) scale = Math.min(scale, 0.45);
    else if (input.deviceMemory <= 4) scale = Math.min(scale, 0.65);
  }
  // ... more heuristics

  const particleCount = Math.floor(target * scale);
  const tier = particleCount <= 3000 ? 'low' : particleCount <= 6000 ? 'medium' : 'high';

  return { particleCount, qualityTier: tier, scale };
}

Device Examples

DeviceCoresRAMViewportResult
MacBook Pro M1816 GB1440p10,000 (high)
Surface Laptop48 GB1080p7,000 (medium)
Budget Laptop24 GB1080p4,500 (low)
Desktop RTX 30801632 GB4K6,500 (medium*)

*4K viewport triggers 65% cap

Why Not Runtime Adjustment?

The system sets particle count once at startup rather than dynamically:

Advantages:

  • Predictable memory allocation
  • No runtime stutter from buffer reallocation
  • Simpler testing and debugging

Trade-offs:

  • Cannot adapt to changing conditions
  • User must refresh for different quality

Browser Compatibility

typescript
// Fallback for missing APIs
const hardwareConcurrency = navigator.hardwareConcurrency ?? 4;
const deviceMemory = (navigator as any).deviceMemory ?? 8;
BrowserhardwareConcurrencydeviceMemory
Chrome
Firefox❌ (defaults to 8)
Safari❌ (defaults to 8)
Edge

User Override

Users can force a specific particle count via URL parameter:

?particles=5000

This bypasses heuristics for testing or specific use cases.

Source Files

FilePurpose
src/core/quality.tsHeuristics implementation
src/core/webgpu.tsDevice info gathering

Next Steps

Built with VitePress