v1.5.0

Mailtrain-org/mailtrainv1.5.0Mar 6, 2026by github-actions[bot]

AI Summary

Overhauls the UI for right panels, layers, scenes, and presets. Introduces a new Morph effect with shape targets and audio reactivity, adds obstacle collision to particles, and implements WBOIT transparency.

Key Highlights

  • New Morph effect with shape targets and audio reactivity
  • Obstacle collision for all particle effects
  • New audio features (dominant_chroma, zcr)
  • UI overhaul (right panel, layers, scenes)

New Features

  • Morph effect with 5 transition styles
  • Obstacle collision for particles
  • New audio features (dominant_chroma, zcr)
  • WBOIT transparency support

Full Release Notes


### Fixed
- Scene cue delete button disappearing when the same preset is added as a cue multiple times (egui widget ID collision)
- Crash (index out of bounds) when deleting a scene cue while the timeline is playing

### UI
- Right panel restyle: single-row param layout (name/slider/value/M/O), compact 16×14 MIDI/OSC badges, smaller Reset All button, large particle count header with 3px health bar, standalone bordered blend mode badge, compact particle param rows replacing grid, tab-strip obstacle source selector with active state styling, compact obstacle slider rows with separate value labels, audio mapping hover background lift with fixed-width 52px feature names
- Remove redundant activity dots from Settings subsection enable rows; status bar always shows MIDI/OSC/WEB/NDI indicators with three-state dots (active/enabled-idle/disabled)
- Consolidate MIDI, OSC, Web, NDI/Outputs, and Global into single collapsible "Settings" section with subsections — header shows labeled status dots, subsection headers use smaller font/arrow for visual hierarchy, protocol-specific badge colors
- Trigger grids (MIDI + OSC) use fixed-width label columns (52px) for proper badge alignment across rows
- OSC TX settings wrapped in subtle bordered frame; Global settings use fixed-width labels aligned to dropdowns
- Layers panel visual refresh: type color legend (FX/MD/WC), left color strip per card, layer index numbers, type badges, type-colored add buttons (all three on one row), subtle "Clear All" text link with red hover/confirm, footer type breakdown counts, hover-only delete buttons, stronger dimming for disabled layers
- Reorder sidebar panels: Effects → Layers → Presets → Scenes
- Layer badge shows "N/8" capacity format
- Scenes panel visual refresh: 2-column scene grid tiles, two-line cue rows with bottom progress bars (hold elapsed + transition progress with stripes), accent active-cue highlighting, pulsing red LIVE dot, GO button green fill, STOP red outline, hover-reveal delete buttons, hold time always visible, scrollable cue list (up to 6 visible), add-cue bar with separator
- Timeline bar polish: accent-colored active cue, striped from-cue during transitions, two-line target cue label (name + "Morph 58%"), transition-typed playhead colors
- Presets panel: add "Save current state as preset:" label above name input, and make "+ New" button clear all layers to a fresh Phosphor state with two-click confirmation when unsaved changes exist
- Amber dirty-state styling for presets panel: custom header with pulsing dot and inline preset name, amber-tinted dirty bar with Update/Reset buttons, amber selected tile, pulsing card border, and bumped tile height to 26px
- Add effect type indicators to effects panel: left color strip (purple SH / orange PS / teal FB), two-char badge, type legend, and footer breakdown count
- Add hover tooltips to effect type color legend explaining Shader, Particle+Shader, and Feedback categories
- Show Copy and Edit buttons side by side — Copy enabled for visible built-ins only, Edit enabled for user effects only; fixes Copy being clickable on hidden Phosphor branding effect
- Add "msc: N" badge to PARTICLES panel showing `max_scaled_count` with tooltip; merge blend mode and feature badges into single wrapping horizontal row; normalize morph section to 9pt font
- Add `EffectType` enum (Shader, Particle, Feedback) with optional `effect_type` field in .pfx format — auto-detects from data when absent
- Tag 5 feedback effects (Accretion, Array, Chaos, Mycelium, Turing) with explicit `"effect_type": "feedback"` in their .pfx files

### Audio
- Add `dominant_chroma` feature: CPU-side argmax of 12 chroma bins, normalized 0–1, forwarded to both fragment and particle GPU uniforms
- Cymatics audio remapping: chroma-driven Chladni mode selection (curated visually-distinct (n,m) pairs per pitch class), mfcc[0] vibrational amplitude, chroma peakedness pattern clarity
- Murmur audio remapping: mfcc[1] flock cohesion (bright=tight, dark=dispersed), mfcc[3] predator aggressiveness, flux speed agitation, dominant_chroma rim/sky hue; fix emitter waves by probing whole screen + inheriting donor heading
- Symbiosis audio remapping: mfcc[4-6] per-species force modulation (timbre shifts ecology), dominant_chroma species color rotation, flux interaction radius expansion, zcr Brownian noise jitter
- Morph audio remapping: dominant_chroma target shape selection (pitch class picks morph target instead of sequential), mfcc[1] transition speed modulation, mfcc[2] mid-transition scatter/explosion strength, chroma-tinted background
- Flux audio remapping: mfcc[1] curl strength (bright=tight spirals, dark=loose), flux-driven turbulence agitation, dominant_chroma emission/background hue anchor
- Add `zcr` (zero-crossing rate) to ParticleUniforms for compute shader access

### Effects
- Add obstacle collision to all 11 remaining particle sim shaders (phosphor, flux, murmur, raster, tesla, genesis, cymatics, symbiosis, turing, morph, as) — obstacles now work across all 16 effects

### Fixes
- Fix preset showing as dirty during ParamMorph scene transitions — morph interpolation now resets param changed flags since it's timeline playback, not user edits
- Clamp particle count to GPU dispatch dimension limit (`max_compute_workgroups_per_dimension × 256`) to prevent validation errors on high-count effects
- Add per-effect `max_scaled_count` field — quality multiplier won't push past this ceiling; applied to Accretion (60K), Genesis (24K), Murmur (2.4M), and Symbiosis (4M)
- Hide Stress test effect from effects panel
- Fix morph slider animating when auto-cycle mode is "Off" — transition progress now only advances when auto-cycling is active
- Remove redundant image source UI (IMG badge, image selector dropdown, Load Image/Video/Webcam buttons) from particle panel — morph targets handle image loading
- Fix morph snapshot/image/text/video filling wrong slot when `target_count` diverges from def count — all handlers now use def vector length for slot selection and pad gaps

### Morph Effect (New)
- **Shape target morphing**: particles store home positions for up to 4 target shapes and morph between them on beat drops with spring physics and turbulence
- **Multiple target types**: image files, geometry shapes (circle, ring, grid, spiral, heart, star), and random scatter — mixed freely across 4 slots
- **5 transition styles**: Spring (default), Explode-reform, Flow (curl noise), Cascade (left-to-right wave), and Direct (pure lerp) — selectable via UI dropdown
- **Per-particle stagger**: each particle transitions at a slightly different time for organic cascading motion
- **Audio reactivity**: onset triggers morph to next target, bass drives spring vibration, mid adds turbulence during transition, brilliance creates scatter
- **Strided aux buffer**: 4x normal aux size interleaves target data (32MB at 500K particles), no bind group changes
- **ParticleUniforms extended**: 4 new fields (morph_progress, morph_source, morph_dest, morph_flags) — 784→800 bytes, existing effects unaffected
- **Morph UI controls**: target slot display with click-to-morph buttons, transition progress bar, auto-cycle mode (Off/On Beat/Timed), hold duration slider, transition style dropdown, runtime image and geometry loading into morph slots
- **Text morph targets**: type any string in the morph panel and particles form the letters — uses fontdue to rasterize Inter-Bold glyphs into a bitmap, then samples through the standard image pipeline. Supports `"text:HELLO"` in .pfx files
- **Video frame morph targets**: click "+ Video" to load a video file — evenly-spaced frames fill available morph slots (up to 4), cycling between different video frames on beat
- **Snapshot morph targets**: click "Snapshot" to freeze current particle positions into a new morph target — GPU readback captures alive particles as a reusable shape

### WBOIT (Weighted Blended Order-Independent Transparency)
- **Order-independent transparency**: new `"blend": "wboit"` mode approximates correct alpha compositing in a single unsorted pass — no back-to-front sort needed
- **Two-pass rendering**: particles render into accumulation (Rgba16Float) + revealage (R8Unorm) targets with WBOIT weight function, then a fullscreen composite blends onto the scene
- **Automatic compute raster fallback**: WBOIT is incompatible with compute rasterization; `"render_mode": "compute"` + `"wboit"` falls back to billboard with a warning, and `"auto"` mode excludes WBOIT effects
- **Resize-safe**: WBOIT textures recreated on window resize alongside compute raster framebuffer

### Symbiosis Effect (New)
- **Multi-species particle life simulation**: 2–8 species interact via an asymmetric 8×8 force matrix, producing emergent ecosystems, crystals, and predator-prey dynamics
- **6 named presets**: Ecosystem, Crystals, Hunters, Membrane, Chaos, Symmetric — smoothly interpolated on switch
- **Audio-reactive matrix**: bass boosts force scale, mid modulates friction, presence/brilliance expand interaction radius, onset shuffles random matrix entries for emergent disruption
- **Toroidal topology**: particles wrap around screen edges with correct wrapped-distance neighbor queries
- **Force matrix in ParticleUniforms**: 8×8 float matrix (256 bytes) appended to uniforms — no new bind groups needed, all existing effects unaffected

### Fix: CR scatter bind group crash on particle quality change
- **Request adapter-supported buffer limits**: device now requests the GPU adapter's actual `max_storage_buffer_binding_size` and `max_buffer_size` instead of wgpu defaults (128MB/256MB), allowing high-VRAM GPUs to use their full capacity
- **Cap particle count to device limits**: after applying quality multiplier, particle count is clamped so the largest buffer (`sorted_particles_buffer` = N×9×4 bytes) stays within the device's storage buffer binding limit — prevents invalid bind groups and crash on quality change

### Gaussian Area Splat for Compute Rasterizer (Fix)
- **Soft Gaussian splat for particles >1.5px**: compute raster now matches billboard renderer output for larger particles (e.g., Array at ~2.7px radius). Previously all energy concentrated into a 2×2 bilinear kernel, making dim-colored effects nearly invisible
- **Three-tier kernel**: single-pixel (≤1px), bilinear 2×2 (1–1.5px), Gaussian area splat (>1.5px, capped at 8px). Weight `col.a × glow²` matches billboard's `SrcAlpha` blend
- **Multi-tile binning**: particles overlapping multiple 16×16 tiles are binned/scattered to all covered tiles (up to 3×3). Sorted buffer sized 9× max_particles accordingly
- **Tiled path support**: Gaussian loop in shared-memory tiled shader with per-pixel tile boundary clipping

### Preset: Particle Image Save/Restore (Fix)
- **Particle image path now saved in presets**: selecting a different particle image (e.g., skull → phoenix) and updating the preset persists the choice across preset loads
- **Mark dirty on particle/obstacle changes**: changing particle image, video source, webcam source, video transport settings, or obstacle settings now marks the preset as dirty so the Update button appears
- **Backward compatible**: old presets without `particle_image_path` load normally (default image from .pfx)

### Particle Quality Setting (New)
- **Global quality multiplier**: Low (0.25x), Medium (0.5x), High (1x), Ultra (2x), Max (4x) — scales both `max_count` and `emit_rate` proportionally so fill time stays consistent
- **Default: High (1x)** — no change for existing users; backward-compatible with old settings.json files
- **Combo box in Global panel**: same pattern as theme selector; changing quality mid-session reloads the active effect to rebuild GPU buffers
- **Built-in effects upgraded**: all effects now default to 1–2M particles with compute rasterization as the baseline

### Window Size
- **Default window size changed to 1920×1080** (was 1280×720)

### Compute Rasterization (New)
- **Atomic framebuffer rendering**: 3-pass compute raster pipeline bypasses vertex→rasterizer→fragment for sub-pixel particles. Eliminates 2×2 fragment quad waste and rasterizer bottleneck for high particle counts
- **Fixed-point encoding**: 12-bit fractional precision (4096 scale) via `atomicAdd` on 4 separate i32 storage buffers (R/G/B/A), supporting up to ~1000 overlapping particles per pixel within i32 range
- **Size-adaptive kernel**: single-pixel fast path for particles ≤1.5px, Gaussian kernel footprint for 1.5–5px, billboard fallback for larger particles
- **Render mode field**: `"render_mode"` in .pfx files — `"billboard"` (default), `"compute"`, or `"auto"` (auto-selects compute for ≥100K particles with no sprites/trails and size ≤0.005)
- **Fullscreen resolve**: render pass with hardware blend state (additive or alpha) reads decoded framebuffer with Reinhard tonemapping for additive mode
- **COMPUTE badge**: cyan badge in particle panel when compute rasterization is active
- **Tiled shared-memory accumulation**: 4-pass bin→prefix-sum→scatter→tile-raster pipeline for ≥50K particles. Accumulates in 4 KB workgroup shared memory per 16×16 tile (~100× faster atomics), then flushes to global framebuffer with plain stores. Direct draw path retained as fallback for low counts. Automatic path selection based on 1-frame-latent alive count

### Performance & Build Optimization
- **Build profiles**: dev builds use opt-level 1 (deps at 2 for faster shader compilation), release uses thin LTO + codegen-units=1
- **GPU buffer clears**: spatial hash dispatch uses `encoder.clear_buffer()` instead of allocating a zeroed Vec per frame (up to 262KB saved per frame at max grid)
- **Sort params cached**: bitonic sort parameters written once at buffer creation via `mapped_at_creation` instead of ~210 `write_buffer` calls per frame
- **Parallel prefix sum**: spatial hash prefix sum upgraded from single-thread sequential to 256-thread Blelloch scan with shared memory (supports up to 65K cells)
- **Pipeline cache**: compiled shader data persisted to `~/.config/phosphor/pipeline_cache.bin` for faster startup on subsequent launches
- **Device loss handling**: GPU device lost callback + uncaptured error handler + graceful render-time detection (no more hard crash on driver reset)
- **GPU profiler** (`--features profiling`): wgpu-profiler integration with egui overlay panel, timestamp query support
- **Clippy pedantic**: workspace-level clippy lints enabled, auto-fixed 500+ warnings, rustfmt config added
- **FxHashMap**: `rustc-hash` for runtime-only hash maps (MIDI trigger tracking)

### Audio Panel Redesign (Improved)
- **BPM ring**: circular beat-phase arc with orbiting dot replaces static BPM number; flashes white on downbeat
- **Chroma wheel**: radial 12-segment pie chart with energy-proportional radius replaces linear bars; dominant note displayed in center
- **MFCC heatmap**: single-row dark blue→cyan→white timbral fingerprint replaces bar graph; selective labels (DC, Slope, Shape, Formant, Det6)
- **Spectrum bars**: vertical gradient mesh bars (bright top→faded bottom) with peak-hold markers (0.95 decay)
- **Dynamics section**: 7 labeled rows with thin track bars + percentage values; kick uses boolean dot indicator
- **Section headers**: self-documenting sections (SPECTRUM 7 bands, DYNAMICS 7 features, CHROMA 12 pitch classes, TIMBRE·MFCC 13 coefficients)
- **Tooltips**: per-bar spectrum tooltips (name, Hz range, character), per-row dynamics tooltips (feature description), per-cell MFCC tooltips (coefficient meaning), chroma wheel and BPM ring tooltips
- **Footer**: feature count summary (7 bands · 7 dynamics · 12 chroma · 13 mfcc · 512 fft)

### MFCC + Chroma Audio Feature Extraction (New)
- **13 Mel-Frequency Cepstral Coefficients**: captures timbral content — different instruments/voices produce distinct MFCC patterns. Computed from 26-band mel filterbank + DCT-II on the existing 4096-pt FFT
- **12 Chroma pitch-class energies**: maps spectral energy to musical pitch classes (C through B). Individual notes/chords light up specific bars. Frequency range 20–5000 Hz
- **GPU-accessible**: new `mfcc(i)` and `chroma_val(i)` WGSL helper functions in both fragment and compute shaders. ShaderUniforms 256→288 bytes, ParticleUniforms 416→528 bytes
- **Audio panel visualization**: chromatic-colored chroma bars (12 pitch classes) and MFCC bar graph in the audio panel overlay
- **Smoothed + normalized**: 25 new features go through the existing adaptive normalizer and asymmetric EMA smoother (attack 0.03s, release 0.15s)
- **Non-breaking**: existing effects unchanged — they simply don't read the new fields. NUM_FEATURES 20→45

### Spatial Hash Grid (Improved)
- **Dynamic grid sizing**: `grid_dims(N)` computes `clamp(sqrt(N/16), 40, 256)` — grid scales automatically with particle count instead of fixed 40×40
- **Shader constant patching**: SH_GRID_W/H constants in particle_lib, count, prefix_sum, and scatter shaders patched at pipeline creation time
- **Particle panel slider fix**: emit_rate slider max now scales to 10% of max_count for high-count effects (e.g. 100K emit_rate for 1M particles)

### Murmur Effect (Upgraded)
- **Topological K=7 nearest neighbors**: replaced fixed-radius Boids with scale-free K-nearest neighbor queries — flock correlations work from 40K to 1M particles without parameter tuning
- **Vicsek noise phase transitions**: angular noise (eta parameter) modulated by bass drives order→chaos transitions; proper research-accurate model instead of ad-hoc disorder
- **Predator avoidance**: Lissajous-drifting predator with onset-triggered strike jumps; exponential falloff repulsion causes realistic flock splits
- **Heading-level roost centering**: quadratic ramp centering force feeds into heading computation (not just position) — prevents alignment-consensus drift that plagued the old position-only correction
- **KNN-based rim lighting**: edge detection from neighbor anisotropy (COM displacement / k-radius); color_mode slider (0=silhouette, 1=full rim); audio-reactive brightness (RMS) and color temperature (centroid: cool blue ↔ warm amber)
- **Non-accumulating color**: base color re-derived from gradient each frame instead of reading previous frame's output — prevents wash-out over time
- **Adaptive separation**: K-th neighbor distance as interaction scale; density-invariant at any particle count
- **Donor-based emission**: new particles spawn near existing flock members for organic growth
- 1M particles, 8 params (noise_eta, cohesion, color_mode, predator, separation, speed, smoothing, audio_drive), alpha blending, bloom postprocess

### Mycelium Effect (New)
- **Chain-based growth system**: 2,500 pre-allocated chains (80 segments max each, 200K particles total) — leader particles at tips follow curl noise flow fields while depositing follower particles that form spring-connected tendrils
- **Self-activation architecture**: dead particles detect when they should activate by reading chain state from the input buffer — no cross-thread writes, no race conditions. Growth propagates via time-based segment intervals
- **Branching on audio onset**: reserve chains (beyond initial 100 leaders) self-activate by branching from random active chain tips; onset boosts branch probability 15×, brilliance widens branch angles
- **Spring physics**: followers connected by asymmetric springs (strong toward tip, weak toward root) with critical damping; creates organic elastic motion through the network
- **Death cascade**: roots age faster with spectral flux; when a root dies, death propagates tip-ward with 50ms/segment delay — visual dissolve from root to tip, then chain slot recycles for new branches
- **4 color modes**: depth (teal-blue root → forest green → phosphorescent tip), generation (teal/gold/magenta by branch depth), velocity (dim blue → bright green), age (bright green → dark brown)
- **Beat-phase traveling wave**: periodic brightness pulse travels root→tip synced to detected beat, modulated by RMS
- **Bioluminescent feedback trails**: background shader with differential RGB decay (green persists longest), organic noise warp for living feel, HDR clamp
- **Audio integration**: bass→growth speed, onset→branching, mid→curl intensity, brilliance→branch angle, flux→death rate, beat_phase→traveling glow, rms→brightness, centroid→hue shift
- 200K particles, 8 params, additive blending, bloom postprocess, obstacle collision

### Chaos Effect (New)
- **Strange attractor dynamics**: 5 classic attractors (Lorenz, Rössler, Halvorsen, Thomas, Chen) with RK4 integration — particles trace chaotic trajectories that reveal butterfly, torus, and knot-like shapes through accumulated feedback trails
- **Attractor morphing**: spectral centroid smoothly blends between adjacent attractor types; slider + audio drive crossfade the derivative functions (all attractors normalized to [-1,1] space)
- **Audio-reactive bifurcation**: bass shifts the bifurcation parameter across chaos boundaries (e.g. Lorenz ρ through ~24.74), onset provides impulse push past critical points for dramatic order→chaos transitions
- **RK4 integrator**: 4th-order Runge-Kutta for accurate trajectory integration; brilliance modulates time step for faster/more chaotic motion at high frequencies
- **4 color modes**: velocity (blue→gold speed ramp), z-depth (cool→warm gradient), wing (cyan vs magenta by sign of attractor x), age (bright warm→dim cool fade)
- **Feedback trail rendering**: background shader with centripetal UV warp, differential RGB decay (red fades fastest → cool aging trails), HDR clamp
- **3D projection**: slow constant-speed Y-axis rotation, perspective depth scaling via projection param, depth-based size and brightness modulation
- **Divergence guard**: NaN/out-of-bounds particles reset to random position near origin; soft clamp to normalized space prevents runaway
- 50K particles, 8 params, additive blending, bloom postprocess, obstacle collision

### Turing Effect (New)
- **Gray-Scott reaction-diffusion**: full 2D R-D simulation on ping-pong compute textures, running N steps/frame (default 8, audio-modulated up to 32)
- **Hybrid R-D + particles**: 200K particles sample the R-D field for gradient-based chemotactic forces, flowing toward high-concentration pattern regions
- **Particle coloring from chemistry**: color, size, and alpha derived from local B concentration — particles are visible in pattern regions, transparent in substrate
- **Audio-reactive chemistry**: bass→feed rate (2.5× boost), mid→kill rate (2.5× boost), brilliance→diffuse_b, onset injects 4 randomized B drops per beat (PCG hash, 0.35 strength), beat doubles sim steps
- **Particle audio reactivity**: onset pulses gradient force (3× surge), bass pumps particle size, beat kicks velocity, RMS brightness (0.8 coeff), centroid shifts palette (0.5 coeff)
- **Gray-Scott parameter space**: feed/kill rate sliders traverse spots → worms → mitosis → coral → chaos regimes
- **R-D infrastructure**: new `ReactionDiffusionDef` in particle types, `RDUniforms` struct, group 4 bind group for particle compute, `create_rd_resources()` factory
- 512×512 R-D grid, 8 params (gradient_strength, drag, feed_rate, kill_rate, diffuse_b, sim_speed, drop_size, brightness)

### Accretion Effect (New)
- **Tiled N-body gravity**: O(N²) gravitational simulation using workgroup shared-memory tiling (GPU Gems 3 Ch. 31) — every particle attracts every other, forming orbital systems, accretion discs, and slingshot ejections
- **Angular-momentum-preserving damping**: only radial velocity is damped, preserving tangential (orbital) velocity for stable long-lived disc structure
- **Audio seed injection**: beat onsets inject "seed" particles (local attractors within the disc); seed mass scales with `mid`, gravity strength modulated by `bass`
- **Spiral feedback trails**: background shader warps trails with inward pull + rotation, creating galaxy-like spiral arms; differential RGB decay shifts aging trails from warm gold to cool blue
- **4 init patterns**: disc (tangential orbits), ring (near-circular), two-body (binary system), collapse (zero-velocity infall)
- **3 color modes**: velocity (blue→gold), proximity to seeds (blue→orange), orbital energy (blue=bound, red=escaping)
- **Audio reactivity**: onset brightness flash (warm-tinted), bass particle size breathing, RMS glow, bass/mid drive feedback warp strength
- 30K particles, 8 params, additive blending, central pressure support, obstacle collision

### Array Effect (New)
- **Audio-band speaker emitters**: 5 toroidal ring emitters, one per frequency band (sub-bass through brilliance), each firing disc-shaped particle rings outward like speaker cones pushing air
- **Two layout modes**: vertical speaker stack (default) and concentric rings — crossfade with `layout` param
- **Per-band audio reactivity**: each emitter responds to its own frequency band for emission density, speed, and glow
- **Beat-phase breathing**: ring radii modulate with beat phase for rhythmic pulsing
- **ParticleUniforms expanded** 400→416 bytes: added `low_mid`, `upper_mid`, `presence`, `brilliance` audio fields to compute shaders (all 7 bands now available)
- 300K particles, 8 params (trail_decay, ring_radius, spread, layout, color_mode, speed_mult, beat_pulse, emitter_glow)

### Obstacle Refinements
- **Contain mode**: new collision mode that traps particles inside the obstacle shape — inverted collision test, binary search, and normals
- **Flow Around improvement**: redirects approach energy into tangential direction instead of stripping it, preventing pile-ups
- **Video obstacles**: luminance-to-alpha conversion so video frames work as obstacles (bright = solid, dark = passable); UI now shows "Video: filename"
- **Webcam cleanup**: Clear and source-switching now properly stop webcam/depth when no longer needed
- **Tooltips**: all obstacle panel controls have descriptive hover text

### Monocular Depth Estimation (New)
- Feature-gated `depth`: `cargo run --features depth,webcam` for MiDaS v2.1 small monocular depth estimation
- Webcam frames → background `phosphor-depth` thread → ONNX Runtime (ort crate) → 256×256 depth map → obstacle texture
- Particles collide with person's depth silhouette — nearby objects block, far objects pass through
- Model download from HuggingFace (~66 MB), user-initiated via UI button with progress indicator
- Model cached at `~/.config/phosphor/models/midas_v21_small_256.onnx`
- 1-2 frame latency depth pipeline: bounded(1) channels, try_send drops stale frames
- UI: "Depth" button in Obstacle panel (auto-shows "Download" if model missing, progress % during download)
- Preset save/load: `obstacle_depth` field with backward-compatible serde defaults
- 7 new tests (downscale, model path, download progress states, preset serde) — 352 total

### Obstacle Collision (New)
- Image-based 2D obstacle textures for particle collision — load PNG/JPEG/WebP with alpha channel
- 3 collision modes: **Bounce** (reflect velocity along surface normal, scale by elasticity), **Stick** (zero velocity, hold at surface), **Flow Around** (tangent projection, remove normal component)
- Obstacle texture extends compute group 1 bind group (bindings 2+3) alongside existing flow field
- WGSL collision helpers in `particle_lib.wgsl`: `obstacle_alpha()`, `obstacle_normal()` (central-difference gradient), `apply_obstacle_collision()` — any `*_sim.wgsl` can call them with 3 lines
- `ParticleUniforms` extended 384→400 bytes: obstacle_enabled, obstacle_threshold, obstacle_mode, obstacle_elasticity
- Collapsible "Obstacle" UI panel in right sidebar: enable toggle, Load Image (rfd), Clear, mode dropdown, threshold/elasticity sliders
- Integrated into Cascade effect; same 3-line pattern (`prev_pos`, integrate, `apply_obstacle_collision`) works in any particle sim
- Preset save/load with backward-compatible serde defaults
- Opaque image support: images without alpha (JPEG, opaque PNG) auto-detect and use luminance as collision mask — dark areas are passthrough, bright areas are solid
- Anti-strobe: binary search finds exact surface contact point along integration step (4 iterations), places particle just outside surface; velocity reflection only when moving into obstacle
- Texture format `Rgba8Unorm` (not sRGB) for raw data sampling; UV Y-flip for correct clip→texture mapping
- 6 new tests (ObstacleMode conversions, preset serde, backward compat, alpha preprocessing) — 351 total

### Cascade Effect (Fix)
- Fixed beat flash strobe: replaced binary `u.beat` full-screen flash with smooth `beat_phase` envelope (fast 25%-of-beat quadratic fade), reduced amplitude from 0.05 to 0.03

### Cascade Effect (New)
- **Cascade**: solid walls of particles emit inward from all 4 screen edges, audio-segmented by frequency band
- Pixel-perfect wall emission: particle index maps deterministically to perimeter pixels (1920 top/bottom + 1080 left/right at 1080p), guaranteeing gap-free solid walls that scale with resolution
- Band-to-edge mapping: bottom=bass+sub_bass (red-orange), left=mid (teal), right=mid (blue-violet), top=centroid (white-cyan)
- Audio directly drives wall penetration depth: band energy controls particle speed, so walls extend/retract with the music like Aurora's ribbons
- Continuous audio push force during simulation keeps accelerating particles when their band is active
- Curl noise turbulence for organic lateral drift, beat-triggered velocity pulse, onset jitter
- 3 color modes: band colors (4 distinct per edge), monochrome white-blue, speed/energy gradient
- Background shader: feedback trails with directional inward UV warp, per-band audio-reactive edge glow strips (width scales with audio), beat flash
- Functions as both standalone effect and layerable edge frame via convergence param + brightness alpha compositing
- 8 params: trail_decay, inward_speed, spread, curl_strength, color_mode, edge_glow, convergence, beat_sync

### Genesis Effect (Retuned)
- **Genesis** (Particle Lenia): retuned from reference implementation (znah.net/lenia) for proper self-organizing behavior
- Ring kernel calibrated to reference ratios: peak at 30% of R, width 23% of R (proper ring with falloff)
- Kernel weight W_K scaled from 0.003 → 0.02 for correct field density at particle count
- Interaction radius narrowed to [0.05, 0.20] from [0.10, 0.50] — manageable search radius
- Removed curl rotation (was compensating for over-smooth density field at 20K)
- Stronger growth forces (step×0.10) and higher speed cap (MAX_SPEED=0.25) for VJ-paced dynamics
- Soft radial containment spring past screen edge + hard clamp safety net (replaces square boundary)
- Beat-triggered seed drops: ~3% of particles teleport to a random screen location on each beat, creating fresh organisms that self-organize from the music
- 9 initial seed clusters in 3×3 grid (fills screen), zero-mean paired-hash noise (no drift bias)
- Stronger bloom (threshold 0.15, intensity 0.55) for visual presence

### Tesla Effect (New)
- **Tesla** (magnetic field): 200K charged particles follow magnetic field lines as a flow field, creating interweaving helical trajectories
- Magnetic monopole flow field: particles follow superposition of dipole field directions; charge sign determines direction along field lines (+charge follows B, -charge follows -B)
- 4 dipole arrangements via `dipole_mode` param: parallel (same polarity), antiparallel (bar magnet, default), ring (4 alternating), quadrupole (4 + oscillating center)
- Orbital correction near poles: smoothly blends field-following to tangential orbit, preventing convergence at sink poles
- Helical oscillation: per-particle perpendicular sine wave with random phase, controlled by `helix_tightness`
- Proximity dimming: particles near dipoles render dim, brightening as they arc away
- Charge-based coloring: cyan for positive, magenta for negative; `charge_ratio` controls mix
- Audio-reactive: bass drives flow speed, onset triggers velocity jitter, beat triggers per-particle polarity flips (cyan↔magenta)
- 3 color modes: charge-based (cyan/magenta), speed-based (cool→hot), lifetime gradient (cool→warm)
- Feedback trails with electric UV shimmer, dim background field line visualization
- 8 params: trail_decay, field_strength, charge_ratio, dipole_mode, field_rotation, color_mode, helix_tightness, flip_sensitivity

### AoS → SoA Particle Buffer Refactor
- **Structure of Arrays layout**: split single 64-byte `Particle` storage buffer into 4 separate 16-byte SoA buffers (`pos_life`, `vel_size`, `color`, `flags`) — position-only reads (spatial hash, sort keygen) now load 16 instead of 64 bytes per particle (4× bandwidth savings)
- **13-entry compute bind group**: bindings 1-4 read, 5-8 write, 9 counters, 10 aux, 11 dead, 12 alive indices
- **6-entry render bind group**: 4 SoA read buffers + uniforms + alive indices
- **`read_particle()` / `write_particle()` helpers** in particle_lib.wgsl — effect compute shaders use convenience functions, hot-path neighbor reads (murmur boids) access individual arrays directly
- **Spatial hash and sort keygen optimized**: count/scatter shaders bind only `pos_life`, sort keygen binds only `vel_size`
- Request `max_storage_buffers_per_shader_stage: 16` (all desktop GPUs support this)
- Total memory identical (64 bytes/particle), just reorganized for better GPU cache line utilization

### Raster Shard Flex
- **Flexible shard motion**: Voronoi shards deform organically instead of moving as rigid blocks — distance-dependent rotation twist, per-particle noise within shards, and wider burst spread with outer particles flying further

### Raster Grid Artifact Fix
- **Jittered grid sampling**: ±0.4×step deterministic offset per particle breaks up visible row/column banding in smooth gradient areas
- **Bilinear color interpolation**: fractional pixel positions sample from 4 surrounding pixels, eliminating color stairstepping in gradients
- **Gradient-based size modulation**: luminance gradient magnitude stored in `home.w`; smooth areas get 30% larger particles (fill gaps), edges stay neutral
- **Sparkle boost**: bright pixels at high-gradient locations (stars, glints) get audio-reactive size pulsing — per-particle phase offset creates independent twinkling driven by onset and mids
- New unit tests for jitter, bilinear sampling, and gradient helpers

### Raster Image Loading Fixes
- **Fix 2048x2048 image loading**: `upload_aux_data()` no longer shrinks aux buffer below `max_particles` size — subsequent `update_aux_in_place()` calls for larger images were silently dropped
- **Fix image selector not updating**: switching built-in images now updates `ps.def.emitter.image` so the ComboBox reflects the active selection
- **Fix particle slider clamping**: slider ranges now dynamically extend to include the current value — prevents Raster's `emit_rate=100K` / `lifetime=999` from being silently clamped to slider defaults, which corrupted the effect every frame the panel was drawn
- **Built-in effects are runtime-only**: particle slider changes no longer write back to `effect_loader` for built-in effects — use presets to persist tweaks
- **Warning on aux buffer overflow**: `update_aux_in_place()` now logs a warning when a write is skipped due to buffer size
- Raster particle cap raised from 500K to 2M (covers 2048² images at grid step=1)
- Raster default particle size reduced from 0.004 to 0.002

### Particle Video/Webcam Source + Transitions
- **Video as particle source**: Raster (and any image-emitter effect) can now use video files as particle source — particles update home positions per-frame tracking video content
- **Webcam as particle source**: live webcam feed drives particle positions in real-time (feature-gated `webcam`)
- **Source transitions**: smooth 0.5s interpolation of particle home positions when switching between image/video/webcam sources (no hard snap)
- **Per-frame aux updates**: pre-allocated aux buffer at `max_particles` size enables `write_buffer` updates without recreating GPU bind groups (8MB DMA transfer for 500K particles)
- **`sample_rgba_buffer()`**: new function samples raw RGBA byte data directly, skipping file I/O — used for video frame and webcam frame particle sampling
- **UI source controls**: particle panel shows source type badge (IMG/VIDEO/CAM), source name, Load Video / Webcam buttons (feature-gated), video transport (play/pause, speed, loop, seek)
- **Preset save/load**: particle video path, speed, looping, and webcam source persisted in `LayerPreset` with serde defaults for backward compat
- **Load Image button**: users can load custom PNG/JPEG/WebP images as particle source (replaces default image)
- **Animated GIF as particle source**: GIFs with multiple frames auto-detect as animated source with per-frame particle position updates (same as video, no feature gate required)
- **Background loading**: all particle source loading (image, GIF, video) happens on a background thread via `ParticleSourceLoader` — no UI freeze during decode
- **EmitterDef `video` field**: `.pfx` files can specify `"video": "clip.mp4"` or `"video": "webcam"` for built-in video/webcam source at effect load time
- Video decode reuses existing `media/video.rs` infrastructure (ffmpeg pre-decode to RAM, 60s max)
- **Built-in image selector**: combo box in particle panel for quick-switching between bundled raster images (skull, phoenix, jellyfish, hand, samurai_mask) without file dialog
- **Morph-safe preset loading**: when a preset layer has the same effect already loaded, skip the full effect reload — keeps particle systems alive so morph transitions interpolate params smoothly instead of destroying and rebuilding all particles
- 12 new tests: `sample_rgba_buffer` (5), `ParticleImageSource` (2), `SourceTransition` (3), `LayerPreset` serde (3), `EmitterDef` serde with video (2)

### Raster Effect (New)
- **Raster** (video wall): 500K particles map to image pixel positions, colored by source image
- Voronoi shard fragmentation: 32 seed points create irregular fragments with per-shard rotation, translation, and depth variation
- Frayed edges: particles near Voronoi cell boundaries break free with individual jitter
- Three bass displacement modes via `bass_mode` param: shards (default), tangential swirl, radial push
- 2D sinusoidal wave displacement from mids, onset-driven per-particle scatter from highs
- Spring-return physics pull particles back to displaced home positions (sustained audio holds displacement)
- Beat onset impulse with shard-coherent burst direction
- Luminance-based particle sizing: brighter pixels render larger for visual depth
- 8 exposed params: trail_decay, spring_k, bass_strength, mid_wave, high_scatter, burst_force, depth_scale, bass_mode
- Optional feedback trails via trail_decay for motion blur during scatter
- Image source `MAX_DIM` raised from 512 to 2048 for higher-resolution sampling

### Particle Effects Overhaul
- **Delete 4 effects**: removed Coral, Helix, Nova, Vortex (low quality, never worked well)
- **Murmuration → Murmur**: renamed, removed feedback trails (root cause of "sperm" look), brighter sky for contrast, larger/darker/more opaque particles (0.014 size, 0.9 alpha), reduced count 70K→40K
- **Ribbons fix**: lower feedback clamp 0.12→0.06, stronger vignette, halved particle brightness and alpha to prevent additive washout
- **Veil fix**: lower feedback cap 0.4→0.15, stronger decay dampening, halved particle brightness and alpha, minimum dampening floor even without audio
- **Cymatics enhancement**: 25K→50K particles, emit rate 1700→3500, burst 100→500, 3 new params (rotation, symmetry, glow), more vivid color gradient, stronger nodal line contrast
- **Swarm → Spirograph**: complete reimagine — hypotrochoid parametric curves with 5 arms of different petal-count patterns, drifting centers, audio-reactive ratio morphing. 6 params: trail_decay, draw_speed, pattern_scale, complexity, drift, color_spread
- **Spirograph** (new): multi-pattern hypotrochoid curves with 5 arms of distinct petal-count patterns, drifting centers, audio-reactive ratio morphing. Replaces Swarm.
- **Compute shader `param()` access**: particle compute shaders can now access effect params 0-7 via `param(i)` helper, forwarded from ParamStore through `effect_params` fields in ParticleUniforms (repurposed padding bytes, no size change)
- Updated Crucible and Spectral Eye presets (Swarm→Spirograph)
- Effect count: 15 curated effects (was 12 before particle additions, peaked at 19, now curated down to 15)

### Particle System Hardening for 1M Support
- **GPU-side buffer zero-init**: replaced CPU `vec![0u8; 128MB]` + `write_buffer` with `encoder.clear_buffer()` — eliminates 128MB+ CPU allocation at 1M particles
- **Device limits validation**: `max_count` clamped to device `max_storage_buffer_binding_size` with log warning
- **Bitonic sort auto-cap**: depth sort auto-disabled above 65K particles (would require 210 dispatches/frame at 1M)
- **Trail buffer safety**: trails disabled above 500K particles; trail length capped to fit device binding limit

### Parameter Persistence
- **Slider values persist to .pfx**: adjusting parameters in the Parameters panel now updates the `default` values in the .pfx file for user effects, so values survive effect reload and app restart
- Debounced 500ms save — writes only after slider stops moving, not on every frame
- Editor's Effect tab stays in sync when params are saved from UI

### Shader Editor
- **Fix: Save now persists both tabs**: Save (Ctrl+S) writes both the active and paired file when either has unsaved changes — previously only saved the currently active tab, losing edits to the other file

### Effect Browser
- **Particle badge**: small accent-colored dot in top-right corner of effect buttons that use particles
- **Particle count in hover**: hover text shows particle count (e.g. "70K particles", "1M particles")

### Veil Effect
- Fix feedback blowout on loud audio: particle brightness now decreases with volume, stronger loudness dampening on alpha, audio-reactive decay, lowered feedback clamp

### Helix Effect Overhaul
- **Dipole field-guided particles**: replaced uniform B_z Lorentz force with magnetic dipole field — particles follow curved field lines from emitter, creating visible force field pattern through trails
- **Helical oscillation**: perpendicular oscillation around field lines (charge-dependent CW/CCW), bass tightens helix frequency
- **Feedback warps along field**: particle trails flow along magnetic field direction with chromatic aberration, creating persistent field line visualization
- **Dark background**: replaced overpowering painted field lines with near-black background + very subtle dipole hints; particles are the main visual
- **Bright particles**: brightness 0.22→0.70, alpha 0.30→0.55, particle size 0.010→0.018; emit rate 100→200 for dense field coverage
- **Beat burst**: speed pulse along field lines on beat detection; onset scatters particles laterally off field lines
- **Tuned post-processing**: bloom threshold 0.50→0.35, bloom intensity 0.45→0.60 for force-field glow; brighter saturated color gradient

### Chaos Effect Overhaul
- **Visible Lorenz butterfly**: fixed base rho from 22 (below chaos threshold) to 28 (canonical chaotic regime); particles now trace the iconic butterfly attractor pattern
- **XZ butterfly projection**: replaced Y-axis rotation with XZ plane view (the classic Lorenz butterfly orientation) plus gentle ±8° tilt oscillation for 3D depth feel
- **Stable attractor dynamics**: reduced audio modulation of sigma/rho/beta to subtle range (was dramatically reshaping the attractor every frame); fixed `time * audio-varying` rotation bug that caused violent camera jumps
- **Trail-based visualization**: dim particles (alpha × 0.2) with high trail persistence (decay 0.92) accumulate into visible trajectory lines; clean feedback without twist distortion preserves tight attractor lines
- **Density increase**: 20K particles at 500/s emit (was 6K at 80/s) for dense attractor coverage
- **Removed dead params**: attractor_mix, zoom, speed inputs were not wired to compute shader (compute shaders cannot access `param()`)
- **Brightness fixes**: removed × 0.18 fallback color dim, raised initial alpha 0.3→1.0, unclamped color from 0.5→1.0 max

### Particle Effect Review & Enhancement
- **Murmuration** (hero effect): full Boids flocking model with separation, cohesion, and alignment; angular heading smoothing eliminates jitter; alpha blend for opaque bird silhouettes; twilight sky gradient background; depth-based sizing; 15K particles; audio: beat→cohesion spike, bass→disorder, onset→flock split
- **Coral** (fix): replaced numerical central-difference gradient with analytical derivative of Turing cosine waves; added soft exponential boundary repulsion (no more hard clamp collapse); FBM curl noise for organic diffusion; reduced attraction_strength 2.5→1.2; aspect ratio correction; color gradient and size curve
- **Chaos** (fix): adaptive sub-stepping (4 steps) prevents divergence; NaN/divergence detection auto-kills bad particles; proper Rossler scaling (native scale then rescale); centered perspective projection around attractor center; color gradient for temperature-based trajectory coloring; size/opacity curves
- **Helix** (fix): increased E-field upward from 0.05→0.20; reduced B_z from 3.0→1.5 base (wider spirals that actually rise); beat adds upward levitation pulse; moved emitter from y=-0.6 to y=-0.4; RMS-reactive upward drift; two-strand DNA color gradient; size/opacity curves
- **Nova** (overhaul): color gradient lifecycle (white-hot→vivid→orange→dim red); size/opacity lifetime curves; ground bounce for trailing sparks (restitution 0.3); speed/size/life variance for natural variation; 15→85% shell/spark ratio; staggered burst emission; stronger ground glow with beat pulse
- **Phosphor** (polish): reduced sparkle overbright (mix 0.55→0.3, mult 1.6→1.2); tightened feedback decay 0.88→0.84; stronger beat inward pull 0.02→0.06; size/opacity curves for crisp particle falloff; reduced bloom intensity 1.2→1.1
- **Veil** (bloom fix): raised background cap 0.2→0.6 for richer fabric; lowered bloom_threshold 0.9→0.7
- **Ribbons** (bloom fix): raised bloom_threshold 0.4→0.60; reduced bloom_intensity 0.6→0.40; reduced trail_width 0.008→0.005; capped feedback at 0.8
- **Cymatics** (polish): analytical Chladni gradient (no numerical eps); smooth bilinear mode crossfade between integer modes; raised background cap 0.5→0.85; sharper nodal line glow (exp factor 12→18); soft boundary repulsion; color gradient and size curve
- **Vortex**: chromatic aberration in gravitational lensing (R/G/B channel separation); accretion glow flash near event horizon on beat; temperature-based color gradient for disk coloring
- **Swarm**: wider orbit radius 0.3→0.5; reduced radial damping 8.0→4.5; stronger beat orbit expansion
- **Flux**: sparkle system (8% of particles get 2.5x brightness with flicker); opacity curve for gentle fade-out; raised background cap 0.5→0.8

### Advanced Particle Forces
- ParticleUniforms extended from 192 to 384 bytes with new force, emitter, curve, and sort fields
- FBM noise forces: configurable octaves, lacunarity, persistence with turbulence and curl noise modes
- Wind force: constant directional force via `wind: [x, y]`
- Vortex field: rotational force with center, strength, and falloff radius
- Ground bounce: configurable y-level and restitution coefficient
- New emitter shapes: disc (uniform area fill), cone (directional spread with angle control)
- Emitter velocity inheritance: particles inherit emitter motion for trailing effects
- Speed, lifetime, and size variance: per-particle randomization (0-1 range)
- Lifetime curves: 8-point LUT for size and opacity over particle lifetime
- Color gradient: up to 8 hex colors interpolated over lifetime
- Particle spin: per-particle rotation with configurable speed, rendered via vertex rotation
- `apply_builtin_forces()` helper in `particle_lib.wgsl`: single call applies all forces (gravity, wind, drag, noise, attraction, vortex, flow field)
- Curve/gradient evaluation helpers: `eval_size_curve()`, `eval_opacity_curve()`, `eval_color_gradient()`
- Bitonic depth sort: optional GPU merge-sort on alive indices for correct alpha-blended rendering
- `parse_hex_color()` utility for `#RRGGBB` / `#RRGGBBAA` to packed u32
- All new fields default to zero/disabled — existing .pfx files and custom shaders work unchanged

### PFX Hot-Reload + Editor Integration
- `.pfx` file hot-reload: editing effect metadata (params, postprocess, passes, particles) live-updates without restarting
- Differential updates: param-only or postprocess-only changes skip GPU pipeline rebuilds
- `merge_from_defs` on ParamStore preserves slider positions when param definitions change
- Editor tab switching: Shader and Effect tabs in code editor for `.wgsl` and `.pfx` side by side
- JSON syntax highlighting for `.pfx` editing
- "Edit Shader" → "Edit Effect", "Copy Shader" → "Copy Effect" button labels
- 12 new unit tests for PfxDiff and merge_from_defs

### Bug Fixes
- Fix BPM detection reporting double tempo (~290 instead of ~145 BPM): remove cascading octave-up correction that fought multi-ratio disambiguation, tighten tempo prior σ from 1.5 to 1.0, widen frame time clamp to tolerate real audio thread timing, reduce Kalman snap escape from 50 to 30 frames

## Downloads

| Platform | File | Notes |
|----------|------|-------|
| **macOS** | `.dmg` | Universal binary (Apple Silicon + Intel) |
| **Linux x86_64** | `.tar.gz` | glibc 2.35+ (Ubuntu 22.04+, Debian 12+) |
| **Windows x64** | `.zip` | Windows 10+ |

## Getting started

**macOS**: Open the DMG, drag Phosphor.app to Applications (or run directly). Signed and notarized.

**Linux / Windows**: Extract, run `phosphor` from the extracted directory. The `assets/` folder must be next to the binary.

## Requirements

- **GPU**: Vulkan (Linux/Windows) or Metal (macOS)
- **Audio**: Built-in mic or line-in for audio-reactive visuals
- **NDI®** (optional): [NDI® SDK runtime](https://ndi.video) for network video output
- **Video playback** (optional): `ffmpeg` on PATH for media layers

---
NDI® is a registered trademark of Vizrt NDI AB.