/** * Seedance VFX register — a PURE, deterministic physical-effects vocabulary. * No I/O, no Date, no Math.random. * * VFX prompts need material behavior, source, timing, and consequence: every * effect starts somewhere, interacts with light and objects, changes over time, * and ends in a visible state. Generic words (magical/explosive/cinematic) * become noisy overlays. Each effect here carries a PHYSICAL prompt-ready phrase * + a stability constraint; `vfxEffectLine` wraps it with the integration rules * (one hero effect, anchored to its source, respecting physics, resolving * forms -> travels -> dissipates to a visible endpoint). * * Core five harvested verbatim from the MIT `Emily2040/seedance-2.0` seedance-vfx * Effects Contract (@63b32dc); fire/water/explosion authored in the same register. */ export interface VfxEffect { id: string; label: string; /** The physical, prompt-ready phrase (source -> behavior -> endpoint). */ phrase: string; /** The stability constraint that keeps the effect from corrupting the subject. */ stability: string; } const EFFECTS: VfxEffect[] = [ { id: 'particle', label: 'Particles', phrase: 'fine gold dust particles spiral up from behind the subject, catch the backlight, then settle on the surface', stability: 'keep the anchored object rigid', }, { id: 'energy', label: 'Energy', phrase: 'thin blue electrical arcs crawl along the conductor, briefly illuminating the texture they pass', stability: 'keep the arcs attached to their source', }, { id: 'smoke', label: 'Smoke / vapor', phrase: 'cold white vapor rolls over the rim, sinks down the side, and thins near the surface', stability: 'describe density and direction, never a uniform haze', }, { id: 'transformation', label: 'Transformation', phrase: 'the edge chars inward from one corner, flakes curl and fall away, and the core identity is left untouched', stability: 'protect the identity anchor through the change', }, { id: 'weather', label: 'Weather', phrase: 'wind pushes rain diagonally across the frame and puddles ripple outward from each footstep', stability: 'tie the weather to real surfaces', }, { id: 'fire', label: 'Fire', phrase: 'flame climbs from its fuel source, licks upward through a rising heat shimmer, then drops to glowing embers and a thin trail of smoke', stability: 'anchor the flame to a clear fuel source', }, { id: 'water', label: 'Water', phrase: 'water sheets off the surface, beads along the lowest edge, then drips and pools below', stability: 'tie the flow to gravity and the surface it runs on', }, { id: 'explosion', label: 'Explosion', phrase: 'a bright core flashes from a single origin, a shockwave throws debris outward, and rolling smoke billows up and curls back inward', stability: 'one origin point; keep foreground subjects intact', }, ]; export const VFX_EFFECTS: Record = Object.fromEntries( EFFECTS.map((effect) => [effect.id, effect]), ); export const VFX_EFFECT_IDS: string[] = EFFECTS.map((effect) => effect.id); /** * The effect contract line for `id`: the physical phrase + its stability * constraint + the standing integration rules. Throws on an unknown id (no * silent fallback). */ export function vfxEffectLine(id: string): string { const effect = VFX_EFFECTS[id]; if (!effect) { throw new Error(`unknown vfx effect: ${id}. Expected one of ${VFX_EFFECT_IDS.join(', ')}.`); } return ( `${effect.phrase}. ${capitalize(effect.stability)}. ` + 'One hero effect only; anchor it to its source and let it respect gravity, wind, collision, reflection, and occlusion, ' + 'resolving forms → travels → dissipates to a visible endpoint.' ); } function capitalize(text: string): string { return text.length === 0 ? text : `${text[0]!.toUpperCase()}${text.slice(1)}`; }