# 자유 플링 실행 — createFlingAnimator

`ocGesture`의 마찰 감속을 프레임마다 실행하는 1차원 도구입니다. 입력 이벤트와 DOM을 소유하지 않습니다. 위치는 `px`, 속도는 `px/s`이며, 커스텀 scheduler만 기존 spring과 같은 **초 단위** 시계를 사용합니다.

```ts
import { createFlingAnimator } from '@orioncactuscorp/ui/utils/fling';
import { ocGesture } from '@orioncactuscorp/ui/utils/gesture';
import { ocSpring } from '@orioncactuscorp/ui/utils/spring';

const animator = createFlingAnimator({
  position: 0,
  velocity: 1500,
  rate: ocGesture.decelerationRates.fast,
  bounds: {
    min: -200,
    max: 200,
    behavior: 'spring',
    spring: ocSpring.smooth({ duration: 0.4 }),
  },
  onUpdate(position, frame) {
    // Apply position to the consumer-owned surface.
    renderPosition(position);
  },
});

// On a new pointer gesture, retain the current position and cancel the release.
animator.stop();
const current = animator.sample();
animator.destroy();
```

예시의 spring duration은 조절 가능한 예시값이며 플랫폼 동작을 재현한다는 보장은 없습니다. 일반 DOM 스크롤은 브라우저가 소유하게 하고, 마우스 드래그 추가는 [useDragScroll](./drag-scroll.md)을 우선하세요. 목적지 스냅 계산은 [ocGesture](./gesture.md), 목표값 추적은 기존 spring을 사용합니다.

## 계약

| 항목                                       | 동작                                                                      |
| ------------------------------------------ | ------------------------------------------------------------------------- |
| `bounds` 생략                              | 자유 감속                                                                 |
| `{ min, max, behavior: 'clamp' }`          | 경계에서 즉시 정지                                                        |
| `{ min, max, behavior: 'spring', spring }` | 경계 도달 시각·속도를 전달해 복귀. 기존 `ocSpring` 결과를 명시적으로 전달 |
| `rate`                                     | 기본 `ocGesture.decelerationRates.normal`                                 |
| `epsilon`, `velocityEpsilon`               | 기본 `0.5px`, `10px/s`. spring 정지는 위치·속도의 감쇠 상한으로 판정      |
| `autoplay`                                 | 기본 `true`. `false`이면 `start()`로 실행                                 |
| `sample()`                                 | 현재 위치·속도·`phase`를 반환. 콜백이나 DOM을 변경하지 않음               |
| `stop()` / `start()`                       | 현재 경과 시간에서 일시 정지 / 동일 궤적으로 재개                         |
| `destroy()`                                | 프레임 취소. 이후 `start()`는 오류. 반복 정리는 허용                      |
| `onComplete`                               | 자연 완료 시 한 번. 취소·정리에서는 호출하지 않음                         |

`phase`는 `decelerating | bouncing | done`입니다. 경계 밖에서 놓으면 바깥 방향 속도는 제거하고 안쪽 방향 속도는 유지합니다. spring 경계는 일시적인 경계 초과를 허용합니다. 경계가 바뀌면 `stop()` → `sample()` → `destroy()` 후 새 경계로 실행기를 생성하세요. 완료된 실행기는 다시 시작되지 않습니다.

자유 감속은 속도 임계값에 도달한 위치에서 정지하므로 `projectFling`의 무한 시간 투사 위치로 순간 이동하지 않습니다. 프레임을 건너뛰어도 같은 경과 시간의 궤적을 평가하며, 완료 알림은 완료를 관측한 프레임에 전달됩니다. `onUpdate`에서 취소·정리하면 후속 완료 알림도 취소됩니다.

모든 위치·속도·경계 입력은 유한해야 하고 `min <= max`, `0 < rate < 1`, 두 정지 허용치는 0 초과여야 합니다. spring은 양수 mass/stiffness/damping을 가진 `ocSpring` 결과를 전달합니다. 브라우저에서는 rAF가 기본이며, DOM 없는 실행에는 `scheduler`를 주입하세요. 모듈 import만으로 애니메이션을 시작하지 않습니다.

## 입력과 접근성

속도 추적부터 실행기 정리까지의 예시는 [제스처 표면 조합 가이드](./gesture-composition.md)를 참고하세요.

- 새 pointerdown, pointercancel, unmount 시 기존 실행기를 중단·정리합니다. 좌표계와 release 속도 추적은 호출부에서 일관되게 유지합니다.
- 캡처 대상은 실제 드래그 owner와 클릭 정책에 맞춥니다. 배경 닫기와 콘텐츠 탭이 섞이지 않게 이동 임계값과 click 처리도 검증하세요.
- 일반 UI의 브라우저 줌과 네이티브 스크롤을 보존합니다. 자체 줌 표면만 필요한 입력을 취소하며, 모든 `touchmove`를 차단하는 정책은 사용하지 않습니다.
- WebKit gesture와 pointer/wheel 경로를 함께 지원하면 중복 입력을 실제 브라우저에서 확인합니다. 특정 기기의 지연 시간 workaround를 공통 기본값으로 만들지 않습니다.
- 드래그 없이도 쓸 수 있는 버튼·키보드 조작을 제공합니다. 이 실행기는 접근성 위젯을 자동으로 만들지 않습니다.

React에서는 [usePrefersReducedMotion](./motion.md#react에서-사용자-모션-설정-읽기)으로 사용자 설정을 읽습니다. reduced-motion이면 자동 관성과 spring 복귀를 생략하고 유효 경계로 정리하는 보수적 방식이 기본 예시입니다. 설정이 실행 도중 바뀌어도 기존 실행기를 정리하세요. 필요한 짧은 non-spring release를 유지하는 예외는 호출부에서 목적과 정책을 정합니다.

## Migration Notes

- 기존 Modal·PositionSnap·스크롤 정책은 바뀌지 않습니다. 자유 팬 실행이 필요한 표면에서 선택적으로 사용하세요.
