← SplineCONTENT HISTORY

Update to Spline

Snapshot Oct 24, 2026 · 13:29 UTC · version 1.2.0

Collection source: downloaded plugin package.

WHAT CHANGED

First saved snapshot

No earlier snapshot is available to establish a change.

Save Spline and check future changes in Following.

Up to 10 plugins free · no card required.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "description": "Particle emitters: creating and tuning them with particles() (emission, motion, sprite look, emitter shapes, timing), recipes (snow, rain, fire, smoke, confetti, auras), and runtime control via the Particles action (start/stop/burst on events). Load for prompts like \"particles\", \"snow\", \"rain\", \"fire\", \"sparks\", \"smoke\", \"confetti\", \"fireworks\", \"magic dust\".",
  "included_files": [],
  "name": "spline-3d-particles",
  "skill_md_contents": "---\nname: spline-3d-particles\ndescription: \"Particle emitters: creating and tuning them with particles() (emission, motion, sprite look, emitter shapes, timing), recipes (snow, rain, fire, smoke, confetti, auras), and runtime control via the Particles action (start/stop/burst on events). Load for prompts like \\\"particles\\\", \\\"snow\\\", \\\"rain\\\", \\\"fire\\\", \\\"sparks\\\", \\\"smoke\\\", \\\"confetti\\\", \\\"fireworks\\\", \\\"magic dust\\\".\"\n---\n\n# Particles (emitters & runtime control)\n\nA particle emitter is its OWN object type (`'Particle'` in the scene tree),\nnot a material layer. It continuously spawns short-lived billboard sprites —\nthe tool for snow, rain, fire, smoke, sparks, confetti, dust, magic.\n\n## Create & edit\n\n```\nparticles('new', { birthRatePerSec: 120, life: 2 });  // create + configure\nrename('Sparks');\n```\n- `particles('new', props?)` creates an emitter (returns it like `mesh` does);\n  `add('Particle')` is the bare equivalent.\n- `particles(props)` (no 'new') MERGES props over the SELECTED emitter —\n  partial props edit it in place, other selected object types are skipped.\n  `select(o => o.type === 'Particle')` or by name first when editing later.\n- `get_objects([id])` returns a `particles` block in exactly this prop shape —\n  read, tweak, pass back.\n- The emitter is a normal object: `position`/`rotation`/`rename`/`group`/\n  `hide` all work. Rotating the emitter reorients its emission — often easier\n  than direction math.\n\nThe default emitter is a decorative pink→amber fountain (50/sec, 1s life,\ndrifting up from a 100-unit plane, auto-playing forever). Treat every recipe\nbelow as overrides of that.\n\n## The mental model\n\nEach particle: spawns somewhere on/in the `shape` → flies with `speed` along\n`direction` (or the shape's surface normal) → is bent by `gravity` and noise\nover its `life` → its sprite blends `color`→`color2` and fades/shrinks by the\ntwo eases → dies. `birthRatePerSec × life` ≈ how many are alive at once; keep\nit in the low thousands (hard engine cap 100k).\n\n## Props (units matter)\n\nEmission & motion:\n- `birthRatePerSec` — particles per second (UI range 1–2000).\n- `life` — seconds each particle lives (max 10).\n- `speed` — initial velocity, 0–200.\n- `direction: [x,y,z]` — Euler DEGREES rotating the emission axis, which\n  starts as +Y (straight up). `[0,0,180]` = down, `[90,0,0]` = toward +Z.\n- `directionMode` — `'axis'` (all particles fly parallel to `direction`) or\n  `'normals'` (each flies out along the shape's surface normal — radial\n  bursts, auras).\n- `gravity` — signed Y force, ±50: negative falls (snow `-1`, rain `-20`),\n  positive rises (fire `2`). Default is `1` — a gentle updrift.\n\nTurbulence & per-particle variation:\n- `noiseType: 'curl' | 'simplex' | 'fbm'` + `noiseStrength` (0–10 is plenty;\n  0 = off) + `noiseScale` + `noiseSeed` — bends trajectories; curl gives the\n  swirly drift that sells snow and smoke.\n- `randomScale` (0–1), `randomRotation` (0–5), `randomMass` (0–1 — varies how\n  hard gravity pulls each particle, giving natural arcs).\n\nSprite look — nested under `material`:\n- `color`, `color2` — hex strings fine. `coloring: 'gradient'` blends\n  color→color2 over each particle's life; `'random'` picks one of the two per\n  particle (confetti).\n- `ease` (alpha over life) and `easeSize` (size over life):\n  `'linearFadeIn' | 'linearFadeOut' | 'linearInOut' | 'quickFadeInOut' |\n  'exponentialInOut' | 'constant'`.\n- `size: [start, end]` — sprite size in pixels-ish units (1–256 typical).\n  Grow smoke (`[10, 60]`), shrink sparks (`[24, 4]`).\n- `spriteRotation: [start, end]` (0–1) — spins the sprite over life.\n- The sprite texture is NOT settable from code — the default is a soft round\n  dot (fine for almost everything); users can swap the image in the Particles\n  panel.\n\nWhere particles spawn — nested under `shape`:\n- `type: 'Plane' | 'Box' | 'Sphere' | 'Cone' | 'Torus' | 'Mesh'` +\n  `size: [x,y,z]` in world units. A wide flat Plane overhead = weather; a\n  Sphere + `directionMode: 'normals'` = radial burst.\n- `'Mesh'` emits from another object's surface: `shape: { type: 'Mesh',\n  fromMeshId: obj.id }` (size acts as a ~[1,1,1] multiplier there). Pair with\n  `'normals'` for an aura that hugs the object.\n\nWhen it emits:\n- `autoPlay` — start emitting on load (default true). Set `false` for\n  anything an event should trigger.\n- `emitTimeCycle` — `'infinity'` (continuous, default), `'one_time'` (emit\n  for `emitTimeDuration` seconds then stop — a burst), `'loop'` (emit\n  `emitTimeDuration`, wait `emitTimeDelay`, repeat). All in seconds.\n\nCollisions (optional): `add('ParticleCollider')`, position it, then\n`particles({ colliderEntityId: collider.id, collisionBounce: 0.75 })` on the\nemitter (one collider per emitter; bounce 0–1).\n\n## Recipes\n\nSnow (the library preset's real numbers):\n```\nparticles('new', {\n  birthRatePerSec: 400, life: 3, speed: 5, gravity: -1,\n  direction: [0, 0, 180],                       // straight down\n  noiseType: 'curl', noiseStrength: 0.8,        // the drift\n  material: { color: '#ffffff', color2: '#ffffff',\n              ease: 'constant', easeSize: 'constant', size: [10, 10] },\n  shape: { type: 'Plane', size: [1000, 1, 1000] },\n});\nposition({ y: 400 });                            // sky-wide sheet overhead\nrename('Snow');\n```\nRain is the same idea, harder: `speed: 120, gravity: -20, life: 1.2,\nnoiseStrength: 0, material: { size: [3, 3], color: '#9db8d9' }`.\n\nFire:\n```\nparticles('new', {\n  birthRatePerSec: 150, life: 1.2, speed: 30, gravity: 2,\n  noiseType: 'curl', noiseStrength: 1.5,\n  material: { color: '#ffd166', color2: '#ff3b00', coloring: 'gradient',\n              ease: 'quickFadeInOut', easeSize: 'linearFadeOut', size: [26, 6] },\n  shape: { type: 'Sphere', size: [40, 10, 40] },\n});\nrename('Fire');\n```\nThe gradient's hot colors read as fire on their own — add\n`fx('bloom', { intensity: 0.8 })` only if the user asks for glow. Smoke above\nit: gray-on-gray gradient, `speed: 12, gravity: 1.5, life: 3, noiseType: 'fbm',\nnoiseStrength: 2, material: { size: [14, 70], ease: 'linearInOut' }`.\n\nAura hugging an object (emit from its surface):\n```\nconst gem = mesh('Sphere', { width: 120, height: 120, depth: 120 });\nconst em = particles('new', {\n  birthRatePerSec: 60, life: 1.5, speed: 6, directionMode: 'normals',\n  material: { color: '#b28dff', color2: '#4dc3ff', size: [10, 2] },\n  shape: { type: 'Mesh', fromMeshId: gem.id },\n});\n```\nPosition the emitter at the object (or `group` them) so the surface and the\nspawn volume line up.\n\n## Controlling emitters with events\n\nThe `'Particles'` ACTION drives an emitter from any object's event (allowed on\nmost events — not Scroll/LookAt/Follow; also usable inside GameControl):\n\n```\n// A confetti burst that fires when the button is clicked\nconst em = particles('new', {\n  autoPlay: false,                               // wait for the event\n  emitTimeCycle: 'one_time', emitTimeDuration: 0.25,\n  birthRatePerSec: 800, life: 2, speed: 70, gravity: -8,\n  directionMode: 'normals', randomMass: 0.8, randomRotation: 2,\n  material: { coloring: 'random', color: '#ff3b7b', color2: '#33d1ff',\n              ease: 'constant', easeSize: 'constant',\n              size: [12, 12], spriteRotation: [0, 1] },\n  shape: { type: 'Sphere', size: [40, 40, 40] },\n});\nrename('Confetti');\n\nselect(o => o.name === 'Button');                // the emitter's controller\nconst e = addEvent('MouseDown');\nconst a = addAction(e, 'Particles');\nupdateAction(e, a.actionId, { emitterObjectId: em.id, emission: 'start' });\n```\n\n`updateAction` props for a Particles action:\n- `emitterObjectId` — the emitter's id (from `particles('new')`'s return,\n  `select(...)[0].id`, or the `<editor-context>` snapshot).\n- `emission`:\n  - `'start'` — begin an emission cycle (the burst trigger).\n  - `'stop'` — stop emitting; particles already in the air live out.\n  - `'pause'` / `'pauseToggle'` — freeze / resume the whole system mid-air.\n  - `'emitToggle'` — start/stop on alternate firings (ambient on/off switch).\n  - `'reset'` — clear all live particles.\n- `delay` — milliseconds before the action runs (same unit as tween delays;\n  the inspector displays it in seconds).\n\nAmbient vs triggered, in short: weather/fire = `autoPlay: true` +\n`'infinity'` and no wiring needed; bursts = `autoPlay: false` +\n`'one_time'` + a `'start'` action; toggleable ambience = `autoPlay` as you\nlike + `'emitToggle'`.\n\nStack them like any action — e.g. one MouseDown with a Transition (button\nsquash) AND a Particles 'start'. Editing existing wiring: `get_objects` shows\neach Particles action's `emitterObjectId`/`emission`/`delay` next to its ids.\n\n## Verifying\n\nStatic screenshots lie twice here: events don't fire outside play mode, and a\njust-started emitter has spawned almost nothing. `play()`, give it a beat,\nthen `take_screenshot()` — for click-triggered bursts you still see the scene,\nnot the burst, so trust the wiring check via `get_objects` over pixels. In the\neditor, an emitter previews live while it is selected.\n"
}

SHA-256 of public snapshot: 7d28414c15abb8f4df819d23986020daa4a2436dbfdbcfe190e19625e690e2e9