# @ferret/transform: API

Swapping one character for another with an effect, keeping position and AI state.

27 exports. Generated from the source (`packages/transform/src`), so it matches the code.

## Classes

### MemoryRecordStore

*class* · `packages/transform/src/proximity.ts:26`

```ts
class MemoryRecordStore implements RecordStore
```

In-process store with an expiry on an injected clock (tests, split screen, offline play). Several ProximityTransforms may share one.

- `constructor(private now: () => number, readonly stale = 3)`
- `post(name: string, record: SharedRecord)` — assert the record alive (any client that has a player in range)
- `read(name: string): SharedRecord | null` — the freshest record, or null when there is none or it went stale

### ModelLookKit

*class* · `packages/transform/src/looks.ts:34`

```ts
class ModelLookKit
```

Makes `Look`s for a `Transformer` from model files: loads the model and the clips (cached and shared), puts the clips on the
model's bones and reads the graph. Everything is loaded in `prepare`, so a profile's synchronous `build` only hands the look over:
`const look = await kit.prepare(entity, src); profiles.cat = { build: (_e, parent) => { look.nodes.forEach(n => n.parent = parent); return look; } }`.

- `constructor(private assets: AssetCache, private scene: BABYLON.Scene)`
- `async prepare(entity: Entity, src: LookSource): Promise<Look>`
- `dispose()`

### ProximityTransform

*class* · `packages/transform/src/proximity.ts:67`

```ts
class ProximityTransform extends Component<ProximityTransformParams>
```

Changes the entity's form while a player is near, from a record shared between clients rather than from a local trigger:
every client with a player within `radius` posts "form" each tick, every client reads the record each tick and shows
`form` while it is fresh and `rest` when it is not, so all players see the same state. Roaming stops the moment the
form starts: the entity stands still and faces the player that woke it (the local one when it was near and posted, else the nearest remote
player), following them with its eyes while the form lasts, and roaming resumes with the resting form. Needs a Transformer; a
NavAgent and Roam are used when present (also when added later). Emits `npc:state` { entity, form, player } on every change.

- `state: string` — the form currently shown
- `triggerer?: Entity` — the player that woke the NPC (while `form` lasts)
- `onAttach()`
- `onFixedUpdate(dt: number)`

### Transformer

*class* · `packages/transform/src/transformer.ts:39`

```ts
class Transformer extends Component<TransformerParams>
```

Swaps what an entity looks like and how it animates (mesh, skeleton, animation graph) with an effect, in place: the
entity, its position, its AI components and its collider stay, so a roaming cat that becomes a tall keeps walking from
where it stands. Events: `transform:start` { entity, from, to }, `transform:swap`, `transform:end`.

- `current: string`
- `previous: string` — the profile before the last change (what `revert` goes back to)
- `transforming: boolean`
- `look: BABYLON.TransformNode`
- `onAttach()`
- `transformTo(name: string): boolean` — Start changing to `name`. Returns false if it is the current look; a change during a change is queued.
- `revert(): boolean` — Go back to the profile before the last change.
- `onFixedUpdate(dt: number)`
- `onDetach()`

### ZoneTransform

*class* · `packages/transform/src/transformer.ts:153`

```ts
class ZoneTransform extends Component<ZoneTransformParams>
```

Changes the entity's look when it enters a trigger zone and back when it leaves. Needs a Transformer and a Collider on the
entity, and a zone entity with a trigger Collider (see

- `onAttach()`
- `onDetach()`

## Functions

### combine

*function* · `packages/transform/src/effects.ts:72`

```ts
function combine(...fs: EffectFactory[]): EffectFactory
```

Several effects at once; the swap happens at the earliest swap time of the first one.

### graphClips

*function* · `packages/transform/src/looks.ts:17`

```ts
function graphClips(g: GraphDef): string[]
```

The clip names a graph plays (clips, blend points, random entries).

### instant

*function* · `packages/transform/src/effects.ts:19`

```ts
function instant: EffectFactory
```

No effect: the look changes at once.

### layeredEffect

*function* · `packages/transform/src/layers.ts:65`

```ts
function layeredEffect(layers: Layer[], opts: LayeredEffectOptions = {}): EffectFactory
```

An effect made of layers (particle streams and bursts, a light flash, a lightning bolt) around the entity: what a game rebuilds an
authored particle system (Unreal Niagara) as, from numbers it read from it. The layers are anchored where the entity stood when the
effect started. `duration` is the end of the last layer, including the life of its particles.

### particleBurst

*function* · `packages/transform/src/effects.ts:38`

```ts
function particleBurst(opts: { color?: [number, number, number]; count?: number; duration?: number; sound?: string; size?: number } = {}): EffectFactory
```

A puff of particles at the entity (CPU particle system; a soft round sprite drawn at run time, no asset), plus a `sound:play`
event for the game's audio. Combine with `scalePulse` through `combine`.

### scalePulse

*function* · `packages/transform/src/effects.ts:22`

```ts
function scalePulse(opts: { peak?: number; duration?: number } = {}): EffectFactory
```

The body swells and shrinks back around the swap (works on any entity).

## Interfaces

### BoltLayer

*interface* · `packages/transform/src/layers.ts:47`

```ts
interface BoltLayer
```

A lightning bolt: a jagged tube from `offset + [0, height, 0]` down to `offset`, redrawn `flicker` times a second, fading out.

- `kind: 'bolt'`
- `delay?: number`
- `duration: number`
- `color: RGB`
- `height: number`
- `width: number`
- `segments?: number`
- `jitter?: number`
- `flicker?: number`
- `offset?: V3`

### Effect

*interface* · `packages/transform/src/effects.ts:7`

```ts
interface Effect
```

A transformation effect: runs for `duration` seconds around the entity; the look is swapped at `swapAt` (hidden behind the
flash or the particles). `update` gets progress 0..1 each fixed step; `end` cleans up.

- `duration: number`
- `swapAt: number` — seconds into the effect when the old look is replaced by the new one
- `update?(t: number): void`
- `end?(): void`

### EffectContext

*interface* · `packages/transform/src/effects.ts:15`

```ts
interface EffectContext
```

- `entity: Entity`
- `world: World`
- `position: BABYLON.Vector3`

### LayeredEffectOptions

*interface* · `packages/transform/src/layers.ts:51`

```ts
interface LayeredEffectOptions
```

- `swapAt?: number` — the look is swapped at this fraction of the effect (default 0.3, hidden in the flash)
- `sound?: string` — `sound:play` event name for the game's audio, emitted when the effect starts
- `speed?: number` — play the layers at this speed (Unreal's custom time dilation); default 1

### LightLayer

*interface* · `packages/transform/src/layers.ts:44`

```ts
interface LightLayer
```

A flash of light: a point light that peaks and fades over `duration`. Costly in a big level (a new light makes every lit material recompile its shader): prefer a large additive sprite there.

- `kind: 'light'`
- `delay?: number`
- `duration: number`
- `color: RGB`
- `intensity: number`
- `range: number`
- `offset?: V3`

### Look

*interface* · `packages/transform/src/transformer.ts:6`

```ts
interface Look
```

One look of a character: its model, skeleton and animation graph.

- `groups: Record<string, BABYLON.AnimationGroup>`
- `graph: GraphDef`
- `notifies?: Record<string, Notify[]>`
- `loops?: Record<string, boolean>`
- `nodes: BABYLON.TransformNode[]` — the nodes of this look: shown while it is current, removed when it is replaced
- `dispose?(): void` — free what the look created beyond its nodes (animation groups, skeletons)

### LookSource

*interface* · `packages/transform/src/looks.ts:7`

```ts
interface LookSource
```

Where one look comes from: a skinned model, the clips file of its own skeleton (bones matched by name, no retargeting) and its animation graph.

- `model: string` — the model's url (glTF/glb)
- `clips: string` — the clips file of the model's skeleton
- `graph: string | GraphDef` — the graph, or the url of a JSON file that is one or has it as `graph` (what tools/ue-export/animbp.mjs writes)

### ProximityTransformParams

*interface* · `packages/transform/src/proximity.ts:39`

```ts
interface ProximityTransformParams
```

- `store: RecordStore`
- `record?: string` — record name; default `npc:<entity name>` like the original's `npc:<ActorName>`
- `radius?: number` — a local player closer than this keeps the record alive (metres, default 3)
- `tick?: number` — seconds between posting and reading (default 1)
- `form: string` — the profile shown while the record is fresh
- `rest?: string` — the profile otherwise (default: the one the Transformer started with)
- `player?: string` — the entity whose distance counts: found by tag or name (default tag "player")
- `facePlayer?: boolean` — hold still and keep turned (yaw only) to the player that woke it, for as long as the form lasts (default true)
- `others?: string` — tag of the other clients' players (remote avatars): the one that woke the NPC may be one of them (default `remote-player`)

### RecordStore

*interface* · `packages/transform/src/proximity.ts:18`

```ts
interface RecordStore
```

Where the shared record lives. A store forgets a record by itself when nobody has posted it for a while (the presence server
does, ~3 s), so "nobody is near" is the absence of a record and no client ever has to post the resting form.

- `post(name: string, record: SharedRecord): void` — assert the record alive (any client that has a player in range)
- `read(name: string): SharedRecord | null` — the freshest record, or null when there is none or it went stale

### SharedRecord

*interface* · `packages/transform/src/proximity.ts:6`

```ts
interface SharedRecord
```

What one client posts about an NPC while a player is near it (the wire of the original game's presence server: name, location, yaw, model).

- `location: [number, number, number]`
- `yaw: number` — degrees
- `model: string` — the form the posting client saw: only ever the proximity form, never the resting one

### SpriteLayer

*interface* · `packages/transform/src/layers.ts:19`

```ts
interface SpriteLayer
```

Particles: a stream (`rate` per second) or a burst (`count` at the start), from a point, a sphere or a ring. Distances in metres.

- `kind: 'sprites'`
- `delay?: number` — seconds from the start of the effect until it begins, and how long it emits
- `duration: number`
- `rate?: number`
- `count?: number`
- `lifetime: [number, number]` — seconds of life, [min, max]
- `size: [number, number]` — sprite size, metres, [min, max]
- `color: RGB`
- `grow?: number` — how much the sprite grows (1 = not at all) by the end of its life; default 1
- `speed?: [number, number]` — launch speed, m/s, [min, max] (default [0, 0]) and the gravity (m/s^2, downwards; default 0)
- `gravity?: number`
- `shape?: { sphere: number } | { ring: number } | 'point'` — where they start, around `offset` (relative to the entity's feet): a sphere or a ring (radius, metres)
- `offset?: V3`
- `blend?: 'add' | 'alpha'` — `add` glows (default), `alpha` is smoke

### TransformerParams

*interface* · `packages/transform/src/transformer.ts:24`

```ts
interface TransformerParams
```

- `initial: string` — the profile the entity starts as
- `profiles: Record<string, TransformProfile>`
- `effect?: EffectFactory` — effect when a profile has none of its own
- `freezeDuringEffect?: boolean` — hold the entity still while the effect runs (needs a NavAgent or CharacterMovement `speedScale`)

### TransformProfile

*interface* · `packages/transform/src/transformer.ts:17`

```ts
interface TransformProfile
```

- `build(entity: Entity, parent: BABYLON.TransformNode): Look` — make the look under `parent` (the entity's look root); runs when the profile first becomes current
- `effect?: EffectFactory` — effect used when changing to this profile (default: the Transformer's)

### ZoneTransformParams

*interface* · `packages/transform/src/transformer.ts:140`

```ts
interface ZoneTransformParams
```

- `zone: string` — the zone's entity name or tag
- `inside: string` — profile while inside the zone
- `outside?: string` — profile after leaving (default: the one the entity started with)

## Types

### EffectFactory

*type* · `packages/transform/src/effects.ts:16`

```ts
type EffectFactory = (ctx: EffectContext) => Effect
```

### Layer

*type* · `packages/transform/src/layers.ts:49`

```ts
type Layer = SpriteLayer | LightLayer | BoltLayer
```

