choice-resolution
پارامترهای متناقض موشن را به خروجیای پایدار، قابلپیشبینی و سازگار تبدیل میکند.
این Skill لایههای Recipe، Emotion، Brand و Platform را در یک pipeline مشخص ادغام میکند و پارامترهایی مثل duration، easing، exaggeration و secondary effects را به خروجی نهایی تبدیل میکند. ترتیب اعمال، محدودسازی و قوانین ادغام برای همه ورودیها ثابت است.
برای ساخت انیمیشنهای هماهنگ در Web، Mobile و Video مفید است؛ چون محدودیتهای برند، بازههای زمانی پلتفرم و تنظیمات reduced motion را بدون انباشت یا تناقض کنترل میکند.
چه چیزی میگیرید
- ادغام وزندار چند Emotion و انتخاب مقدار غالب برای ویژگیهای گسسته
- اعمال محدودیتهای Brand و حذف افکتهای غیرمجاز مانند particles یا squash-stretch
- سازگارسازی duration برای Web، Video، Mobile و دستگاههای Low-end
- Clamp کردن پارامترها به بازههای معتبر و پشتیبانی از reduced motion
- خروجی deterministic برای تولید موشن یکسان با ورودیهای یکسان
برای چه کاری خوب است
- تیمهایی که یک سیستم موشن مشترک برای محصول، ویدیو و پلتفرمهای مختلف میسازند
- پروژههایی با چند ورودی همزمان مثل Recipe، Emotion و Brand که ممکن است با هم تعارض داشته باشند
- طراحی Design System یا animation pipeline که به خروجی قابلپیشبینی و قابلاعتبارسنجی نیاز دارد
برای چه کاری نیست
- ساخت مستقیم انیمیشن یا تولید فایل ویدیویی بدون داشتن لایههای انتخاب و پارامترهای ورودی
- پروژههای سادهای که فقط به یک duration و easing ثابت نیاز دارند
دربارهٔ این اسکیل
Choice Resolution
Resolves conflicting animation parameters from multiple sources into a single, deterministic output. Transforms parallel choice systems into a sequential resolution pipeline.
The Problem
Without unified resolution:
- Emotion multipliers stack without bounds (1.2 × 1.4 = 1.68 → exceeds M3's 600ms max)
- Brand "elegant" and emotion "playful" produce contradictory squash-stretch values
- Video timing rules exist separately from the main choice pipeline
- Same inputs can produce different outputs depending on evaluation order
Resolution Pipeline
Recipe (base) → Emotion (modifier) → Brand (constraint) → Platform (adaptation) → Bounds → Accessibility (override)
Each layer has a specific role:
| Layer | Role | Operation |
|---|---|---|
| Recipe | Base | Starting values (duration, easing, principles) |
| Emotion | Modifier | Multiplicative adjustments |
| Brand | Constraint | Upper bounds, feature restrictions |
| Platform | Adaptation | Platform-specific multipliers |
| Bounds | Clamp | Final M3-compliant range enforcement |
| Accessibility | Override | Reduced motion bypasses everything |
Resolution Algorithm
function resolveChoice(
recipe: Recipe,
emotions: WeightedEmotion[],
brand: Brand,
platform: Platform,
context: Context
): ResolvedSpec {
// 1. Start with recipe base
let duration = recipe.duration;
let easing = recipe.easing;
let exaggeration = recipe.exaggeration ?? 1.0;
let squashStretch = recipe.squashStretch ?? "none";
let secondaryEffects = recipe.secondaryEffects ?? [];
// 2. Blend emotions if multiple provided
const blendedEmotion = blendEmotions(emotions);
// 3. Apply blended emotion as modifier
if (blendedEmotion) {
duration *= blendedEmotion.durationMultiplier;
easing = blendedEmotion.easing ?? easing;
exaggeration *= blendedEmotion.exaggerationBoost ?? 1.0;
squashStretch = blendedEmotion.squashStretch ?? squashStretch;
secondaryEffects = mergeEffects(secondaryEffects, blendedEmotion.effects);
}
// 4. Apply brand as constraint
if (brand) {
exaggeration = Math.min(exaggeration, EXAGGERATION_LEVELS[brand.maxExaggeration]);
if (!brand.allowsSquashStretch) {
squashStretch = "none";
}
if (!brand.allowsParticles) {
secondaryEffects = secondaryEffects.filter(e => e.type !== "particles");
}
}
// 5. Apply platform adaptation
duration *= platform.durationMultiplier;
// 6. Clamp to bounds
duration = clamp(duration, platform.minDuration, platform.maxDuration);
exaggeration = clamp(exaggeration, 0, 1.25);
// 7. Accessibility override
if (context.reducedMotion) {
return recipe.reducedMotion ?? DEFAULT_REDUCED_MOTION;
}
return { duration, easing, exaggeration, squashStretch, secondaryEffects };
}
Emotion Blending
When multiple emotions are specified, blend using weighted average:
function blendEmotions(emotions: WeightedEmotion[]): BlendedEmotion {
if (emotions.length === 0) return null;
if (emotions.length === 1) return emotions[0].emotion;
// Normalize weights
const totalWeight = emotions.reduce((sum, e) => sum + e.weight, 0);
const normalized = emotions.map(e => ({ ...e, weight: e.weight / totalWeight }));
// Numeric values: weighted average
const durationMultiplier = normalized.reduce(
(sum, e) => sum + e.emotion.durationMultiplier * e.weight, 0
);
// Discrete values: use dominant emotion
const dominant = normalized.reduce((a, b) => a.weight > b.weight ? a : b);
const easing = dominant.emotion.easing;
const spring = dominant.emotion.spring;
// Effects: union if either enables
const effects = mergeEffects(
...normalized.map(e => e.emotion.secondaryEffects)
);
return { durationMultiplier, easing, spring, effects };
}
Merge Rules by Parameter
| Parameter | Merge Type | Description |
|---|---|---|
duration | multiply | Recipe × emotion × platform, then clamp |
easing | override | Emotion > recipe (if specified) |
spring | override | Emotion > recipe (if specified) |
exaggeration | multiply-cap | Recipe × emotion, capped by brand |
squash-stretch | minimum | More conservative of emotion/brand |
secondary-effects | union-filter | Combine all, remove brand-forbidden |
Platform Multipliers
| Platform | Duration | Max | Notes |
|---|---|---|---|
| Web | 1.0× | 600ms | M3 baseline |
| Video | 0.6× | 400ms | Faster for engagement |
| Mobile | 1.1× | 500ms | Touch feedback needs |
| Low-end | 0.8× | 400ms | Performance priority |
Brand Constraints
| Brand | Max Exaggeration | Squash | Particles | Bounce |
|---|---|---|---|---|
| elegant | subtle | ✗ | ✗ | ✗ |
| professional | subtle | ✗ | ✗ | ✗ |
| friendly | moderate | ✓ | ✓ | ✓ |
| playful | expressive | ✓ | ✓ | ✓ |
| bold | expressive | ✓ | ✓ | ✓ |
Worked Example
Input:
- Recipe:
success-notification(duration: 300ms, spring-bouncy) - Emotion:
celebratory(1.2× duration, expressive exaggeration) - Brand:
professional(max subtle, no particles) - Platform:
video(0.6×)
Resolution:
1. Recipe base: 300ms, spring-bouncy, exaggeration=1.0
2. + Emotion: 300 × 1.2 = 360ms, expressive → 1.0
3. + Brand cap: exaggeration capped to subtle (0.5), no particles
4. + Platform: 360 × 0.6 = 216ms
5. + Bounds: 216ms (within 50-400ms) ✓
6. Accessibility: Not reduced motion, proceed
Final: 216ms, spring-bouncy, exaggeration=0.5, no particles
Video-Specific Rules
When platform === "video":
- Apply 0.6× duration multiplier
- Enforce 400ms maximum
- Prefer immediate content start (no intro delay)
- Use 3-frame minimum (100ms at 30fps)
- Never auto-trigger celebrations
Validation Checklist
After resolution, verify:
- ☐ Duration within platform bounds (50-600ms web, 50-400ms video)
- ☐ Exaggeration between 0 and 1.25
- ☐ No brand-forbidden features present
- ☐ Reduced motion respected if enabled
- ☐ Same inputs produce same outputs (deterministic)
Related Skills
animation-recipes- Recipe definitionsemotion-mapping- Emotion parameter setsplatform-adaptation- Platform-specific adjustmentsaccessibility- Reduced motion handling
See references/resolution-examples.md for additional worked examples.