export interface PresencePosition { x: number; y: number; z: number; } export interface PresencePose { position: { x: number; y?: number; z: number; }; rotationY?: number; rotationPitch?: number; externalId?: string; } export interface PresenceSession { homeGameId: TGameId; externalId: string; viewerChunkKey?: string; } /** Resident identity used to suppress offline actors while their owner is online. */ export interface PresenceResidentRow { actorExternalId: string; ownerActorId?: string; } export interface EnsurePresenceArgs { gameId: TGameId; externalId: string; } export interface EnsurePresenceResult { presenceId: string; position: PresencePosition; rotationY: number; } export interface LeavePresenceArgs { gameId: TGameId; externalId: string; } export interface PresenceFeeds { myPresenceLocation: TLocation | null | undefined; onlinePresences: readonly TRow[] | undefined; dormantPresences: readonly TRow[] | undefined; } export interface PresenceActions { ensurePresence(args: EnsurePresenceArgs): Promise; leavePresence(args: LeavePresenceArgs): Promise<{ left: boolean; }>; syncPose(pose: PresencePose): void; } /** * Backend seam for multiplayer presence. Feeds are reactive data and change * identity whenever any player's pose updates; actions MUST be identity-stable * for the lifetime of a mounted session so join/leave lifecycle effects can * depend on them without re-running per pose tick. The use* members are called * as React hooks by consumers, so a mounted transport must never change * identity — remount the subtree to switch backends. */ export interface PresenceTransport { useFeeds(session: PresenceSession | "skip"): PresenceFeeds; useActions(): PresenceActions; } export declare function createLocalPresenceTransport(): { transport: PresenceTransport; actions: PresenceActions; };