import type { RecordingBuffer } from "./recordingBuffer.js"; export interface ReplayLoopOptions { /** Blend between the two frames straddling the seek time; omit to hold the earlier frame. */ interpolate?: (before: T, after: T, alpha: number) => T; /** Returns `null` for this long at the start of every loop pass — a spawn-grace gap so a looping ghost blinks out instead of teleporting. Default `0`. */ spawnGraceSeconds?: number; } /** * Replays a finished `RecordingBuffer` on a loop — modulo-time seek, frame interpolation, and * spawn grace, the pieces every ghost-lap/echo feature hand-rolled (#286.4). `sample(t)` is a pure * function of absolute time; `null` means "the ghost isn't on track right now". */ export interface ReplayLoop { duration(): number; sample(t: number): T | null; } /** @internal */ export declare function createReplayLoop(buffer: RecordingBuffer, options?: ReplayLoopOptions): ReplayLoop; export interface ReplayEntityDeps { has(id: string): boolean; spawn(id: string): void; setPose(id: string, pose: { position: readonly [number, number, number]; rotationY?: number; dt?: number; }): unknown; despawn(id: string): unknown; } export interface RecordedPoseLike { position: readonly [number, number, number]; rotationY?: number; } /** Linear pose blend for `ReplayLoopOptions.interpolate` — position lerp plus shortest-arc yaw. * @internal */ export declare function interpolateRecordedPose(before: T, after: T, alpha: number): T; /** * Reconcile a replayed pose against an entity store: spawns the ghost on the first non-null sample, * poses it while samples flow, despawns it through grace gaps or after the recording empties. * `deps` matches `ctx.scene.entity` structurally — pass `{ has: (id) => ctx.scene.entity.get(id) !== null, spawn, setPose, despawn }`. * @internal */ export declare function syncReplayEntity(deps: ReplayEntityDeps, id: string, pose: RecordedPoseLike | null, dt?: number): void;