// Re-export App Runtime ambient declarations as a type-only module consumers can reference // Usage: // - Per-file: /// // - tsconfig: { "compilerOptions": { "types": ["hyperfy/app-runtime"] } } export * as AppRuntime from './types/app-runtime' export declare function server(env?: Record): Promise export declare function nodeClient(): Promise /* Hyperfy App Runtime TypeScript bindings - Global variables: app, world, props, num - Node system: strongly-typed app.create(nodeName, props) - World helpers and Player API - Minimal THREE-like math types (Vector3, Quaternion, Euler, Matrix4) These are ambient declarations intended for editor IntelliSense and type-checking. */ // ----------------------------- // Math primitives (minimal) // ----------------------------- interface Vector3 { x: number y: number z: number set(x: number, y: number, z: number): this } interface Quaternion { x: number y: number z: number w: number set(x: number, y: number, z: number, w: number): this } interface Euler { x: number y: number z: number order?: 'XYZ' | 'YXZ' | 'ZXY' | 'ZYX' | 'YZX' | 'XZY' set(x: number, y: number, z: number, order?: Euler['order']): this } interface Matrix4 { elements: number[] } type Vector3Like = Vector3 | [number, number, number] type EulerLike = Euler | [number, number, number] // ----------------------------- // Base Node // ----------------------------- interface BaseNode { // Identity id: string // Transform position: Vector3 quaternion: Quaternion rotation: Euler scale: Vector3 matrixWorld: Matrix4 // Hierarchy parent: BaseNode | null children: BaseNode[] // Methods add(child: T): this remove(child: T): this traverse(visitor: (node: BaseNode) => void): void } // ----------------------------- // Node specializations (Docs-driven) // ----------------------------- // Group-like interface GroupNode extends BaseNode {} interface AnchorNode extends GroupNode {} // Media interface AudioNode extends BaseNode { src: string | null volume: number loop: boolean group: 'music' | 'sfx' spatial: boolean distanceModel: 'linear' | 'inverse' | 'expontential' refDistance: number maxDistance: number rolloffFactor: number coneInnerAngle: number coneOuterAngle: number coneOuterGain: number currentTime: number play(): void pause(): void stop(): void } interface VideoNode extends BaseNode { src: string | null linked?: number | string | boolean loop: boolean visible: boolean color: string lit: boolean doubleside: boolean castShadow: boolean receiveShadow: boolean aspect: number fit: 'none' | 'contain' | 'cover' width: number | null height: number | null geometry?: unknown volume: number group: 'music' | 'sfx' spatial: boolean distanceModel: 'linear' | 'inverse' | 'expontential' refDistance: number maxDistance: number rolloffFactor: number coneInnerAngle: number coneOuterAngle: number coneOuterGain: number readonly isPlaying: boolean currentTime: number play(): void pause(): void stop(): void } interface ImageNode extends BaseNode { src: string | null width: number | null height: number | null fit: 'none' | 'contain' | 'cover' color: string pivot: | 'top-left' | 'top-center' | 'top-right' | 'center-left' | 'center' | 'center-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' lit: boolean doubleside: boolean castShadow: boolean receiveShadow: boolean } // Environment / Sky interface SkyNode extends BaseNode { bg: string | null hdr: string | null rotationY: number | null sunDirection: Vector3 | null sunIntensity: number | null sunColor: string | null fogNear: number | null fogFar: number | null fogColor: string | null } // Mesh family interface MeshNode extends BaseNode { castShadow: boolean receiveShadow: boolean } interface MaterialNode { textureX: number textureY: number color: string emissiveIntensity: number } interface SkinnedMeshNode extends BaseNode { anims: string[] castShadow: boolean receiveShadow: boolean play(opts: { name: string; fade?: number; loop?: boolean; speed?: number }): void stop(opts?: { fade?: number }): void getBone(name: string): { position: Vector3 quaternion: Quaternion rotation: Euler scale: Vector3 matrixWorld: Matrix4 } } // Snap point helper interface SnapNode extends BaseNode {} // Nametag overlay interface NametagNode extends BaseNode { label: string health: number } // Physics interface RigidBodyNode extends BaseNode { type: 'static' | 'kinematic' | 'dynamic' onContactStart?: (other: unknown) => void onContactEnd?: (other: unknown) => void onTriggerEnter?: (other: unknown) => void onTriggerLeave?: (other: unknown) => void } interface ColliderNode extends BaseNode { type: 'box' | 'sphere' | 'geometry' setSize(width: number, height: number, depth: number): void radius: number convex: boolean trigger: boolean } interface ControllerNode extends BaseNode { radius: number height: number layer: 'environment' | 'prop' | 'tool' tag?: string | null onContactStart?: (other: unknown) => void onContactEnd?: (other: unknown) => void readonly isGrounded: boolean move(vec3: Vector3Like): void teleport(vec3: Vector3Like): void } // Joint between rigidbodies type JointType = 'fixed' | 'socket' | 'hinge' | 'distance' interface JointNode extends BaseNode { type: JointType body0: RigidBodyNode | null offset0: Vector3 quaternion0: Quaternion rotation0: Euler body1: RigidBodyNode | null offset1: Vector3 quaternion1: Quaternion rotation1: Euler axis: Vector3 breakForce: number breakTorque: number limitY: number | null limitZ: number | null limitMin: number | null limitMax: number | null limitStiffness: number | null limitDamping: number | null collide: boolean } // LOD interface LODNode extends BaseNode { scaleAware: boolean insert(node: BaseNode, maxDistance: number): void } // Avatar interface AvatarNode extends BaseNode { src: string | null emote?: string | null visible: boolean getHeight(): number | null getBoneTransform(boneName: string): Matrix4 | null } // Particles type ParticleShape = | ['point'] | ['sphere', radius: number, thickness?: number] | ['hemisphere', radius: number, thickness?: number] | ['cone', radius: number, thickness?: number, angle?: number] | [ 'box', width: number, height: number, depth: number, thickness?: number, origin?: 'volume' | 'edge', spherize?: number, ] | ['circle', radius: number, thickness?: number, spherize?: number] | ['rectangle', width: number, depth: number, thickness?: number, spherize?: number] interface ParticlesNode extends BaseNode { emitting: boolean shape: ParticleShape direction: number // 0..1 rate: number bursts: { time: number; count: number }[] duration: number loop: boolean max: number timescale: number life: string speed: string size: string rotate: string color: string alpha: string emissive: string image: string | null spritesheet: [rows: number, columns: number, fps: number, loop: boolean] | null blending: 'normal' | 'additive' lit: boolean billboard: 'full' | 'y' | 'direction' space: 'local' | 'world' force: Vector3 | null velocityLinear: Vector3 | null velocityOrbital: Vector3 | null velocityRadial: number | null rateOverDistance?: number sizeOverLife?: string rotateOverLife?: string colorOverLife?: string alphaOverLife?: string emissiveOverLife?: string onEnd?: () => void } // Prim type PrimType = 'box' | 'sphere' | 'cylinder' | 'cone' | 'torus' | 'plane' interface PrimNode extends BaseNode { type: PrimType size: number[] color: string emissive: string | null emissiveIntensity: number metalness: number roughness: number opacity: number transparent: boolean texture: string | null castShadow: boolean receiveShadow: boolean doubleside: boolean physics: null | 'static' | 'kinematic' | 'dynamic' mass: number linearDamping: number angularDamping: number staticFriction: number dynamicFriction: number restitution: number layer: string trigger: boolean tag: string | null onContactStart?: (other: unknown) => void onContactEnd?: (other: unknown) => void onTriggerEnter?: (other: unknown) => void onTriggerLeave?: (other: unknown) => void } // UI type UIPivot = | 'top-left' | 'top-center' | 'top-right' | 'center-left' | 'center' | 'center-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' interface UINode extends BaseNode { space: 'world' | 'screen' width: number height: number size: number lit: boolean doubleside: boolean billboard: 'none' | 'full' | 'y' pivot: UIPivot offset: Vector3 scaler: [minDistance: number, maxDistance: number, baseScale?: number] | null pointerEvents: boolean backgroundColor: string | null borderWidth?: number borderColor?: string borderRadius?: number padding: number flexDirection: 'column' | 'column-reverse' | 'row' | 'row-reverse' justifyContent: 'flex-start' | 'flex-end' | 'center' alignItems: 'stretch' | 'flex-start' | 'flex-end' | 'center' | 'baseline' alignContent: 'flex-start' | 'flex-end' | 'stretch' | 'center' | 'space-between' | 'space-around' | 'space-evenly' flexWrap: 'no-wrap' | 'wrap' gap: number } interface UIViewNode extends BaseNode { display: 'none' | 'flex' width: number height: number backgroundColor: string | null borderWidth?: number borderColor?: string borderRadius?: number margin: number padding: number flexDirection: 'column' | 'column-reverse' | 'row' | 'row-reverse' justifyContent: 'flex-start' | 'flex-end' | 'center' alignItems: 'stretch' | 'flex-start' | 'flex-end' | 'center' | 'baseline' alignContent: 'flex-start' | 'flex-end' | 'stretch' | 'center' | 'space-between' | 'space-around' | 'space-evenly' flexBasis: number | null flexGrow: number | null flexShrink: number | null flexWrap: 'no-wrap' | 'wrap' gap: number } interface UITextNode extends BaseNode { display: 'none' | 'flex' backgroundColor: string | null borderRadius?: number margin: number padding: number value: string fontSize: number color: string lineHeight: number textAlign: 'left' | 'center' | 'right' fontFamily: string fontWeight: 'normal' | 'bold' | number } interface UIImageNode extends BaseNode { display: 'flex' | 'none' src: string | null height: number | null objectFit: 'contain' | 'cover' | 'fill' backgroundColor?: string | null borderRadius?: number flexDirection?: 'column' | 'column-reverse' | 'row' | 'row-reverse' justifyContent?: 'flex-start' | 'flex-end' | 'center' alignItems?: 'flex-start' | 'flex-end' | 'stretch' | 'center' | 'space-between' | 'space-around' | 'space-evenly' flexWrap?: 'no-wrap' | 'wrap' gap?: number margin?: number padding?: number borderWidth?: number borderColor?: string | null loadImage(src: string): Promise } // Action interface ActionNode extends BaseNode { label: string distance: number duration: number onStart?: () => void onTrigger?: () => void onCancel?: () => void } // ----------------------------- // Creation-time props (initializers) // ----------------------------- type NodeInitBase = Partial<{ id: string position: Vector3Like quaternion: Quaternion rotation: EulerLike scale: Vector3Like }> type AnchorInit = NodeInitBase type GroupInit = NodeInitBase type AudioInit = NodeInitBase & Partial< Pick< AudioNode, | 'src' | 'volume' | 'loop' | 'group' | 'spatial' | 'distanceModel' | 'refDistance' | 'maxDistance' | 'rolloffFactor' | 'coneInnerAngle' | 'coneOuterAngle' | 'coneOuterGain' > > type VideoInit = NodeInitBase & Partial< Pick< VideoNode, | 'src' | 'linked' | 'loop' | 'visible' | 'color' | 'lit' | 'doubleside' | 'castShadow' | 'receiveShadow' | 'aspect' | 'fit' | 'width' | 'height' | 'volume' | 'group' | 'spatial' | 'distanceModel' | 'refDistance' | 'maxDistance' | 'rolloffFactor' | 'coneInnerAngle' | 'coneOuterAngle' | 'coneOuterGain' > > type ImageInit = NodeInitBase & Partial< Pick< ImageNode, 'src' | 'width' | 'height' | 'fit' | 'color' | 'pivot' | 'lit' | 'doubleside' | 'castShadow' | 'receiveShadow' > > type MeshInit = NodeInitBase & Partial> type SkinnedMeshInit = NodeInitBase & Partial> type RigidBodyInit = NodeInitBase & Partial> type ColliderInit = NodeInitBase & Partial> type ControllerInit = NodeInitBase & Partial> type LODInit = NodeInitBase & Partial> type AvatarInit = NodeInitBase & Partial> type ParticlesInit = NodeInitBase & Partial< Pick< ParticlesNode, | 'emitting' | 'shape' | 'direction' | 'rate' | 'bursts' | 'duration' | 'loop' | 'max' | 'timescale' | 'life' | 'speed' | 'size' | 'rotate' | 'color' | 'alpha' | 'emissive' | 'image' | 'spritesheet' | 'blending' | 'lit' | 'billboard' | 'space' | 'force' | 'velocityLinear' | 'velocityOrbital' | 'velocityRadial' | 'rateOverDistance' | 'sizeOverLife' | 'rotateOverLife' | 'colorOverLife' | 'alphaOverLife' | 'emissiveOverLife' | 'onEnd' > > type PrimInit = NodeInitBase & Partial< Pick< PrimNode, | 'type' | 'size' | 'color' | 'emissive' | 'emissiveIntensity' | 'metalness' | 'roughness' | 'opacity' | 'transparent' | 'texture' | 'castShadow' | 'receiveShadow' | 'doubleside' | 'physics' | 'mass' | 'linearDamping' | 'angularDamping' | 'staticFriction' | 'dynamicFriction' | 'restitution' | 'layer' | 'trigger' | 'tag' | 'onContactStart' | 'onContactEnd' | 'onTriggerEnter' | 'onTriggerLeave' > > type UIInit = NodeInitBase & Partial< Pick< UINode, | 'space' | 'width' | 'height' | 'size' | 'lit' | 'doubleside' | 'billboard' | 'pivot' | 'offset' | 'scaler' | 'pointerEvents' | 'backgroundColor' | 'borderWidth' | 'borderColor' | 'borderRadius' | 'padding' | 'flexDirection' | 'justifyContent' | 'alignItems' | 'alignContent' | 'flexWrap' | 'gap' > > type UIViewInit = NodeInitBase & Partial< Pick< UIViewNode, | 'display' | 'width' | 'height' | 'backgroundColor' | 'borderWidth' | 'borderColor' | 'borderRadius' | 'margin' | 'padding' | 'flexDirection' | 'justifyContent' | 'alignItems' | 'alignContent' | 'flexBasis' | 'flexGrow' | 'flexShrink' | 'flexWrap' | 'gap' > > type UITextInit = NodeInitBase & Partial< Pick< UITextNode, | 'display' | 'backgroundColor' | 'borderRadius' | 'margin' | 'padding' | 'value' | 'fontSize' | 'color' | 'lineHeight' | 'textAlign' | 'fontFamily' | 'fontWeight' > > type UIImageInit = NodeInitBase & Partial< Pick< UIImageNode, | 'display' | 'src' | 'height' | 'objectFit' | 'backgroundColor' | 'borderRadius' | 'flexDirection' | 'justifyContent' | 'alignItems' | 'flexWrap' | 'gap' | 'margin' | 'padding' | 'borderWidth' | 'borderColor' > > type ActionInit = NodeInitBase & Partial> // ----------------------------- // Node name mapping // ----------------------------- type NodeNameToType = { // Core/world group: GroupNode anchor: AnchorNode mesh: MeshNode skinnedmesh: SkinnedMeshNode lod: LODNode sky: SkyNode // Physics rigidbody: RigidBodyNode collider: ColliderNode controller: ControllerNode joint: JointNode // Media audio: AudioNode video: VideoNode image: ImageNode avatar: AvatarNode particles: ParticlesNode prim: PrimNode action: ActionNode snap: SnapNode nametag: NametagNode // UI ui: UINode uiview: UIViewNode uitext: UITextNode uiimage: UIImageNode } type NodeName = keyof NodeNameToType type NodeNameToInit = { group: GroupInit anchor: AnchorInit mesh: MeshInit skinnedmesh: SkinnedMeshInit lod: LODInit sky: NodeInitBase & Partial< Pick< SkyNode, 'bg' | 'hdr' | 'rotationY' | 'sunDirection' | 'sunIntensity' | 'sunColor' | 'fogNear' | 'fogFar' | 'fogColor' > > rigidbody: RigidBodyInit collider: ColliderInit controller: ControllerInit joint: NodeInitBase & Partial< Pick< JointNode, | 'type' | 'breakForce' | 'breakTorque' | 'limitY' | 'limitZ' | 'limitMin' | 'limitMax' | 'limitStiffness' | 'limitDamping' | 'collide' > > audio: AudioInit video: VideoInit image: ImageInit avatar: AvatarInit particles: ParticlesInit prim: PrimInit action: ActionInit snap: NodeInitBase nametag: NodeInitBase & Partial> ui: UIInit uiview: UIViewInit uitext: UITextInit uiimage: UIImageInit } // ----------------------------- // Player API // ----------------------------- interface Player { // properties id: string name: string local: boolean admin: boolean position: Vector3 quaternion: Quaternion rotation: Euler // methods teleport(position: Vector3Like, rotationY?: number): void getBoneTransform(boneName: string): Matrix4 | null damage(amount: number): void heal(amount: number): void applyEffect( options: { anchor?: AnchorNode emote?: string snare?: number freeze?: boolean turn?: boolean duration?: number cancellable?: boolean onEnd?: () => void } | null ): void screenshare(screenId: string): void setVoiceLevel(level: 'disabled' | 'spatial' | 'global' | null): void } // ----------------------------- // World API // ----------------------------- type LayerGroup = 'environment' | 'player' | (string & {}) interface RaycastHit { point: Vector3 normal: Vector3 distance: number tag: string | null playerId: string | null } interface WorldAPI { // Identity / env readonly networkId: string readonly isServer: boolean readonly isClient: boolean // Scene management add(node: BaseNode): void remove(node: BaseNode): void attach(node: BaseNode): void // Events on(event: string, callback: (data?: any) => void): void off(event: string, callback: (data?: any) => void): void // Physics raycast( origin: Vector3, direction: Vector3, maxDistance?: number | null, layerMask?: number | null ): RaycastHit | null createLayerMask(...groups: LayerGroup[]): number // Players getPlayer(playerId?: string): Player | null getPlayers(): Player[] // URL helpers getQueryParam(key: string): string | null setQueryParam(key: string, value?: string | null): void open(url: string, newTab?: boolean): void // Time getTime(): number getTimestamp(format?: string): string // Storage (optional) get?(key: string): T | undefined set?(key: string, value: T): void // Loader (subset) load(type: 'avatar' | 'model', url: string): Promise } // ----------------------------- // Control API // ----------------------------- interface ControlButton { onPress?: () => void onRelease?: () => void down: boolean pressed: boolean released: boolean capture?: boolean } type ControlKeys = | 'keyA' | 'keyB' | 'keyC' | 'keyD' | 'keyE' | 'keyF' | 'keyG' | 'keyH' | 'keyI' | 'keyJ' | 'keyK' | 'keyL' | 'keyM' | 'keyN' | 'keyO' | 'keyP' | 'keyQ' | 'keyR' | 'keyS' | 'keyT' | 'keyU' | 'keyV' | 'keyW' | 'keyX' | 'keyY' | 'keyZ' | 'digit0' | 'digit1' | 'digit2' | 'digit3' | 'digit4' | 'digit5' | 'digit6' | 'digit7' | 'digit8' | 'digit9' | 'minus' | 'equal' | 'bracketLeft' | 'bracketRight' | 'backslash' | 'semicolon' | 'quote' | 'backquote' | 'comma' | 'period' | 'slash' | 'arrowUp' | 'arrowDown' | 'arrowLeft' | 'arrowRight' | 'home' | 'end' | 'pageUp' | 'pageDown' | 'tab' | 'capsLock' | 'shiftLeft' | 'shiftRight' | 'controlLeft' | 'controlRight' | 'altLeft' | 'altRight' | 'enter' | 'space' | 'backspace' | 'delete' | 'escape' | 'mouseLeft' | 'mouseRight' | 'metaLeft' interface ControlPointer { coords: Vector3 position: Vector3 delta: Vector3 locked: boolean lock(): void unlock(): void } interface ControlScroll { value: number capture?: boolean } interface ControlCamera { position: Vector3 quaternion: Quaternion rotation: Euler zoom: number write: boolean } interface ControlScreen { width: number height: number } type ControlAPI = { // dynamic key set [key in ControlKeys]: ControlButton } & { pointer: ControlPointer scrollDelta: ControlScroll camera: ControlCamera screen: ControlScreen release(): void } // ----------------------------- // App API // ----------------------------- type AppEventName = 'update' | 'fixedUpdate' | 'lateUpdate' | (string & {}) interface AppAPI extends BaseNode { // Properties readonly instanceId: string readonly version: string state: Record props: Record keepActive: boolean // Events on(name: AppEventName, callback: (arg?: any) => void): void off(name: AppEventName, callback: (arg?: any) => void): void // Networking send(name: string, data?: any, skipNetworkId?: string | boolean): void emit(name: string, data?: any): void // Nodes get(id: string): BaseNode | null create(name: TName, props?: NodeNameToInit[TName]): NodeNameToType[TName] // Control control(options?: Partial<{ priority: number }>): ControlAPI // Props UI configure(fields: AppFieldSpec[] | (() => AppFieldSpec[])): void } // ----------------------------- // App Props UI Spec // ----------------------------- interface FieldBase { key: string label?: string hint?: string } interface TextFieldSpec extends FieldBase { type: 'text' placeholder?: string initial?: string } interface TextareaFieldSpec extends FieldBase { type: 'textarea' placeholder?: string initial?: string } interface NumberFieldSpec extends FieldBase { type: 'number' dp?: number min?: number max?: number step?: number bigStep?: number initial?: number } interface RangeFieldSpec extends FieldBase { type: 'range' min?: number max?: number step?: number initial?: number } interface ToggleFieldSpec extends FieldBase { type: 'toggle' trueLabel?: string falseLabel?: string initial?: string | boolean } interface SwitchFieldSpec extends FieldBase { type: 'switch' options: { label: string; value: string }[] initial?: string } interface FileFieldSpec extends FieldBase { type: 'file' kind: 'avatar' | 'emote' | 'model' | 'texture' | 'hdr' | 'audio' } interface ColorFieldSpec extends FieldBase { type: 'color' hint?: string initial?: string } interface ButtonFieldSpec extends FieldBase { type: 'button' onClick: () => void } interface SectionFieldSpec extends FieldBase { type: 'section' label: string } type AppFieldSpec = | TextFieldSpec | TextareaFieldSpec | NumberFieldSpec | RangeFieldSpec | ToggleFieldSpec | SwitchFieldSpec | FileFieldSpec | ColorFieldSpec | ButtonFieldSpec | SectionFieldSpec // ----------------------------- // Global declarations // ----------------------------- declare global { const app: AppAPI const world: WorldAPI const props: Record function num(min: number, max: number, dp?: number): number // Additional scripting runtime globals function clamp(value: number, min: number, max: number): number function prng(...args: any[]): number function uuid(): string // three.js classes exposed in the runtime const Object3D: any const Vector3: { new (x?: number, y?: number, z?: number): Vector3 } const Quaternion: { new (x?: number, y?: number, z?: number, w?: number): Quaternion } const Euler: { new (x?: number, y?: number, z?: number, order?: Euler['order']): Euler } const Matrix4: { new (): Matrix4 } // Interpolation helpers (deprecated and buffered variants) const LerpVector3: any const LerpQuaternion: any const BufferedLerpVector3: any const BufferedLerpQuaternion: any // Curves and math constants const Curve: any const DEG2RAD: number const RAD2DEG: number } export {}